commit 31e9432ba08a08f68b47dd8d934195199c99d79f Author: wangxinglong <2371974647@qq.com> Date: Tue Feb 22 17:27:27 2022 +0800 初始化 diff --git a/.env.bak b/.env.bak new file mode 100644 index 0000000..d35b5f6 --- /dev/null +++ b/.env.bak @@ -0,0 +1,19 @@ +APP_DEBUG = true +APP_TRACE = true + +[APP] +DEFAULT_TIMEZONE = Asia/Shanghai + +[DATABASE] +TYPE = mysql +HOSTNAME = 211.149.245.223 +DATABASE = dev_bee_cms +USERNAME = dev_bee_cms +PASSWORD = dT7yH5fmd28JG6ER +HOSTPORT = 3306 +CHARSET = utf8mb4 +DEBUG = true +PREFIX = bee_ + +[LANG] +default_lang = zh-cn diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..79e5639 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +.env +/.idea +/.vscode +backup/data/* +/config/extra +*.log +runtime/* +storage/* +public/storage/* +.DS_Store +/Test.php +nginx.htaccess +dump.rdb +/app/controller/api/Test.php \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..574a39c --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,32 @@ + +ThinkPHP遵循Apache2开源协议发布,并提供免费使用。 +版权所有Copyright © 2006-2016 by ThinkPHP (http://thinkphp.cn) +All rights reserved。 +ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。 + +Apache Licence是著名的非盈利开源组织Apache采用的协议。 +该协议和BSD类似,鼓励代码共享和尊重原作者的著作权, +允许代码修改,再作为开源或商业软件发布。需要满足 +的条件: +1. 需要给代码的用户一份Apache Licence ; +2. 如果你修改了代码,需要在被修改的文件中说明; +3. 在延伸的代码中(修改和有源代码衍生的代码中)需要 +带有原来代码中的协议,商标,专利声明和其他原来作者规 +定需要包含的说明; +4. 如果再发布的产品中包含一个Notice文件,则在Notice文 +件中需要带有本协议内容。你可以在Notice中增加自己的 +许可,但不可以表现为对Apache Licence构成更改。 +具体的协议参考:http://www.apache.org/licenses/LICENSE-2.0 + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..156dc98 --- /dev/null +++ b/README.md @@ -0,0 +1,61 @@ +## 本CMS基于ThinkPHP 6.0开发 + +> - 运行环境要求 PHP7.4+ +> - MySql版本限制 5.7+ + +

+ +### 功能列表 ### + +> - [x] 后台菜单管理 +> - [x] 后台权限管理 +> - [x] 管理员管理 +> - [x] 操作日志 +> - [x] 配置文件配置 +> - [x] 素材管理 +> - [x] 内容管理 +> - [x] 栏目管理 +> - [x] 模型管理 +> - [x] 配置管理 + +

+ +### 待办功能列表 ### + +> - 迁移|重构老版CMS功能 +>> 碎片管理【block高度自定义配置】 +> - 系统 +> - 配置管理重构 + >> 需求 配置优先查询配置文件,文件不存在则根据数据库生成配置文件 +> - 内容管理重构 + >> 拆分内容表为基础表,根据模型建表创建额外表 +> - 模型管理重构 +> - 相关SEO配置 +> - 组图插件完善 +>> 可排序、可自定义关联字段 如:标题、url、alt、描述、等等关联可自由增减 + + +

+ +### PHP扩展依赖列表 ### + +> - fileinfo +> - exif 需要安装在mbsting扩展之后 +> - gd + +

+ +### PHP组件列表 ### + +> - [图片处理 Intervention Image](http://image.intervention.io/getting_started/introduction) +> - [权限验证 think-authz](https://github.com/php-casbin/think-authz) + +

+ +### 前端组件列表 ### + +> - [xm-Select 下拉选择框](https://gitee.com/maplemei/xm-select) +> - [富文本编辑器选择 wangEditorv3.1.1](https://github.com/wangfupeng1988/wangEditor) + +

+ diff --git a/app/.htaccess b/app/.htaccess new file mode 100644 index 0000000..3418e55 --- /dev/null +++ b/app/.htaccess @@ -0,0 +1 @@ +deny from all \ No newline at end of file diff --git a/app/common.php b/app/common.php new file mode 100644 index 0000000..8ae718d --- /dev/null +++ b/app/common.php @@ -0,0 +1,666 @@ + +// +---------------------------------------------------------------------- + +use think\Collection; +use think\db\exception\DbException; +use think\exception\ClassNotFoundException; +use think\Model; +use think\Paginator; + +// 应用公共文件 + +if (!function_exists('widget')) { + /** + * 渲染输出Widget + * @param string $name Widget名称 + * @param array $data 传入的参数 + * @return mixed + * milo 2019-05-08 从TP5.1代码中拿来修改的 + */ + function widget($name, $data = []) + { + return action($name, $data, 'widget'); + } +} +if (!function_exists('action')) { + /** + * 调用模块的操作方法 参数格式 [模块/控制器/]操作 + * @param string $url 调用地址 + * @param string|array $vars 调用参数 支持字符串和数组 + * @param string $layer 要调用的控制层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return mixed + * milo 2019-05-08 从TP5.1代码中拿来修改的 + */ + function action($url, $vars = [], $layer = 'controller', $appendSuffix = false) + { + $info = pathinfo($url); + $action = $info['basename']; + $module = '.' != $info['dirname'] ? $info['dirname'] : request()->controller(); + $class = controller($module, $layer); + if (is_scalar($vars)) { + if (strpos($vars, '=')) { + parse_str($vars, $vars); + } else { + $vars = [$vars]; + } + } + return app()->invokeMethod([$class, $action . config('route.action_suffix')], $vars); + } +} +if (!function_exists('controller')) { + /** + * 实例化(分层)控制器 格式:[模块名/]控制器名 + * @access public + * @param string $name 资源地址 + * @param string $layer 控制层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @param string $empty 空控制器名称 + * @return object + * @throws ClassNotFoundException + * + * milo 2019-05-08 从TP5.1代码中拿来修改的 + */ + function controller($name, $layer = 'controller', $empty = '') + { + $class = parseClass($name, $layer); + if (class_exists($class)) { + return app()->make($class); + } elseif ($empty && class_exists($emptyClass = app()->parseClass($layer, $empty))) { + return app()->make($emptyClass); + } + + throw new ClassNotFoundException('class not exists:' . $class, $class); + } +} + +if (!function_exists('parseClass')) { + /** + * 解析模块和类名 + * @access protected + * @param string $name 资源地址 + * @param string $layer 验证层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return array + * + * milo 2019-05-08 从TP5.1代码中拿来修改的 + */ + function parseClass($name, $layer) + { + if (false !== strpos($name, '\\')) { + $class = $name; + } else { + if (strpos($name, '/')) { + $names = explode('/', $name, 2); + $name = $names[1]; + } + $class = app()->parseClass($layer, $name); + } + return $class; + } +} + + +if (!function_exists('randomStr')) { + /** + * 获取随机字符串 + * @param int $type 0:数字(默认);1:全部;2:小写字母;3:大写字母;4:字母; + * @param int $len 字符串长度 + * @return string + */ + function randomStr($type = 0, $len = 5) + { + $strPol = "0123456789"; + if ($type == 1) { + $strPol = "ABCDEFGHIJKLMOPQRSTUVWYZ0123456789abcdefghijklmopqrstuvwyz"; + } elseif ($type == 2) { + $strPol = "abcdefghijklmopqrstuvwyz"; + } elseif ($type == 3) { + $strPol = "ABCDEFGHIJKLMOPQRSTUVWYZ"; + } elseif ($type == 4) { + $strPol = "ABCDEFGHIJKLMOPQRSTUVWYZabcdefghijklmopqrstuvwyz"; + } + $max = strlen($strPol) - 1; + $str = ''; + for ($i = 0; $i < $len; $i++) { + $str .= $strPol[rand(0, $max)]; + } + return $str; + } +} + +if (!function_exists('isMobile')) { + //判断访问终端是否为移动端 + function isMobile() + { + // 如果有HTTP_X_WAP_PROFILE则一定是移动设备 + if (isset($_SERVER['HTTP_X_WAP_PROFILE'])) { + return true; + } + // 如果via信息含有wap则一定是移动设备,部分服务商会屏蔽该信息 + if (isset($_SERVER['HTTP_VIA'])) { + // 找不到为flase,否则为true + return stristr($_SERVER['HTTP_VIA'], "wap") ? true : false; + } + // 脑残法,判断手机发送的客户端标志,兼容性有待提高。其中'MicroMessenger'是电脑微信 + if (isset($_SERVER['HTTP_USER_AGENT'])) { + $clientkeywords = [ + 'nokia', 'sony', 'ericsson', 'mot', 'samsung', 'htc', 'sgh', 'lg', 'sharp', 'sie-', 'philips', + 'panasonic', 'alcatel', 'lenovo', 'iphone', 'ipod', 'blackberry', 'meizu', 'android', 'netfront', + 'symbian', 'ucweb', 'windowsce', 'palm', 'operamini', 'operamobi', 'openwave', 'nexusone', 'cldc', + 'midp', 'wap', 'mobile', 'MicroMessenger' + ]; + // 从HTTP_USER_AGENT中查找手机浏览器的关键字 + if (preg_match("/(" . implode('|', $clientkeywords) . ")/i", strtolower($_SERVER['HTTP_USER_AGENT']))) { + return true; + } + } + // 协议法,因为有可能不准确,放到最后判断 + if (isset ($_SERVER['HTTP_ACCEPT'])) { + // 如果只支持wml并且不支持html那一定是移动设备 + // 如果支持wml和html但是wml在html之前则是移动设备 + if ((strpos($_SERVER['HTTP_ACCEPT'], 'vnd.wap.wml') !== false) && (strpos($_SERVER['HTTP_ACCEPT'], + 'text/html') === false || (strpos($_SERVER['HTTP_ACCEPT'], + 'vnd.wap.wml') < strpos($_SERVER['HTTP_ACCEPT'], 'text/html')))) { + return true; + } + } + return false; + } +} +//根据栏目获取路径 +if (!function_exists('getUri')) { + function getUri($category) + { + if (!empty($category) && isset($category['id'])) { + if (empty($category['route'])) { + return '/page/' . $category['id'] . ".html"; + } else { + return $category['route']; + } + } + return ''; + } +} + +//根据文件大小转换为文字 +if (!function_exists('sizeToStr')) { + function sizeToStr($size) + { + if (!is_numeric($size) || $size <= 0) { + return ''; + } + $size = $size / 1024; + if ($size < 1024) { + return sprintf("%.2fK", $size); + } + $size = $size / 1024; + if ($size < 1024) { + return sprintf("%.2fM", $size); + } + $size = $size / 1024; + return sprintf("%.2fG", $size); + } +} + +//根据路径获取文件名 +if (!function_exists('getKeyByPath')) { + function getKeyByPath($path) + { + return substr($path, strrpos($path, '/') + 1); + } +} + +//富文本中提取图片路径 +if (!function_exists('getImageUrlFromText')) { + function getImageUrlFromText($content) + { + preg_match_all('//is', $content, $imgs); + $data = []; + if (!empty($imgs) && !empty($imgs[1])) { + foreach ($imgs[1] as $img) { + if (substr($img, 0, 4) != 'http') { + $key = getKeyByPath($img); + $data[$key] = $img; + } + } + } + return $data; + } +} + +//富文本中提取视频路径 +if (!function_exists('getVideoUrlFromText')) { + function getVideoUrlFromText($content) + { + preg_match_all('//is', $content, $videos); + $data = []; + if (!empty($videos) && !empty($videos[1])) { + foreach ($videos[1] as $video) { + if (substr($video, 0, 4) != 'http') { + $key = getKeyByPath($video); + $data[$key] = $video; + } + } + } + return $data; + } +} + +//获取目录下的所有文件 +if (!function_exists('getAllFilesByPath')) { + function getAllFilesByPath($path, $rootPath) + { + if (is_dir($path) && file_exists($path)) { + $items = scandir($path); + $files = []; + foreach ($items as $item) { + if (substr($item, 0, 1) != '.' && strpos($item, '_') == false) { + $itemPath = $path . '/' . $item; + if (is_file($itemPath)) { + $size = filesize($itemPath); + $files[$item] = [ + 'path' => str_replace($rootPath, '/', $itemPath), + 'realPath' => $itemPath, + 'size' => $size, + 'sizeStr' => sizeToStr($size), + 'suffix' => strtolower(substr($item, strrpos($item, '.') + 1)) //后缀 + ]; + } elseif (is_dir($itemPath)) { + $childFiles = getAllFilesByPath($itemPath, $rootPath); + if (!empty($childFiles)) { + $files = array_merge($files, $childFiles); + } else { + rmdir($itemPath); + } + } + } + } + return $files; + } + return []; + } +} + +//过滤get输入 +if (!function_exists('getFilter')) { + function getFilter($value) + { + $getFilter = "'|(and|or)\b.+?(>|<|=|in|like)|\/\*.+?\*\/|<\s*script\b|\bEXEC\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\s+(TABLE|DATABASE)"; + $forArray = false; + if (is_array($value)) { + $forFilter = implode($value); + $forArray = true; + } else { + $forFilter = $value; + } + if (preg_match("/" . $getFilter . "/is", $forFilter) == 1) { + $value = $forArray ? [] : ''; + } + return filterExp($value); + } +} + +//过滤post录入 +if (!function_exists('postFilter')) { + function postFilter($value) + { + $postFilter = "\b(and|or)\b.{1,6}?(=|>|<|\bin\b|\blike\b)|\/\*.+?\*\/|<\s*script\b|\bEXEC\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\s+(TABLE|DATABASE)"; + $forArray = false; + if (is_array($value)) { + $forFilter = implode($value); + $forArray = true; + } else { + $forFilter = $value; + } + if (preg_match("/" . $postFilter . "/is", $forFilter) == 1) { + $value = $forArray ? [] : ''; + } + return filterExp($value); + } +} + +//过滤cookie数据 +if (!function_exists('cookieFilter')) { + function cookieFilter($value) + { + $cookieFilter = "\b(and|or)\b.{1,6}?(=|>|<|\bin\b|\blike\b)|\/\*.+?\*\/|<\s*script\b|\bEXEC\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\s+(TABLE|DATABASE)"; + $forArray = false; + if (is_array($value)) { + $forFilter = implode($value); + $forArray = true; + } else { + $forFilter = $value; + } + if (preg_match("/" . $cookieFilter . "/is", $forFilter) == 1) { + $value = $forArray ? [] : ''; + } + return filterExp($value); + } +} + +if (!function_exists('filterExp')) { + function filterExp($value) + { + $filter = '/^(\s)+(EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT LIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOTIN|NOT IN|IN)(\s)+$/i'; + $forArray = false; + if (is_array($value)) { + $forFilter = implode($value); + $forArray = true; + } else { + $forFilter = $value; + } + if (preg_match($filter, $forFilter) == 1) { + $value = $forArray ? [] : ''; + } + return $value; + } +} + +if (!function_exists('arrayHtmlFilter')) { + function arrayHtmlFilter($arr) + { + foreach ($arr as $k => $one) { + if (is_array($one)) { + $arr[$k] = arrayHtmlFilter($one); + } else { + $one = trim($one); + $arr[$k] = strip_tags($one); + } + } + return $arr; + } +} + +if (!function_exists('toCamelString')) { + /** + * 转驼峰 + * + * @param string $string + * @param false $small 默认false 为true首字母小写 + * @return string + */ + function toCamelString(string $string, bool $small = false): string + { + //例: xxx_yYy_zzZ 和 xxX-yyy-Zzz + //1. 字符串转小写 xxx_yyy_zzz xxx-yyy-zzz + //2. -和_转空格 xxx yyy zzz + //3. 单词首字母大写 Xxx Yyy Zzz + //4. 清除空格 XxxYyyZzz + //处理下划线、减号 统统专程大驼峰 如xxx_yyy_zzz xxx-yyy-zzz 均转为 XxxYyyZzz + $string = strtolower($string); + $string = str_replace('-', ' ', $string); + $string = str_replace('_', ' ', $string); + $string = str_replace(' ', '', ucwords($string)); + if ($small) { + $string = lcfirst($string); + } + return $string; + } +} + +if (!function_exists('unCamelize')) { + /** + * 驼峰命名转特定分隔符[如下划线]命名 + * @param string $camelCaps + * @param string $separator + * @return string + */ + function unCamelize(string $camelCaps, string $separator = '-'): string + { + return strtolower(preg_replace('/([a-z])([A-Z])/', "$1" . $separator . "$2", $camelCaps)); + } +} + + +if (!function_exists('generateRand')) { + /** + * 生成随机数 + * + * @param int $length 长度 + * @param string $type 模式 默认mix混合 number纯数字 alpha字母 + * @return string + */ + function generateRand(int $length = 8, string $type = 'mix'): string + { + $alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + $number = '0123456789'; + $alphabetLen = strlen($alphabet) - 1; + $numberLen = 9; + + switch ($type) { + case 'number': + $str = $number; + $len = $numberLen; + break; + case 'alpha': + $str = $alphabet; + $len = $alphabetLen; + break; + default: + $str = $alphabet . $number; + $len = $alphabetLen + $numberLen; + } + + $randStr = ''; + $str = str_shuffle($str); + for ($i = 0; $i < $length; $i++) { + $num = mt_rand(0, $len); + $randStr .= $str[$num]; + } + return $randStr; + } +} + +if (!function_exists('generateCode')) { + /** + * 生成特定编码 + * + * @param string $type 类型 默认mix混合 number纯数字 alpha字母 + * @param int $len 随机数长度 + * @return string + */ + function generateCode(string $type = 'number', int $len = 6): string + { + //#时间戳+微秒+6位随机数 + $time = microtime(true); + $timeStr = str_replace('.', '', $time); + return sprintf("%s%s", $timeStr, generateRand($len, $type)); + } +} + +if (!function_exists('checkMobile')) { + /** + * 检测手机号 + * + * @param string $mobile + * @return bool + */ + function checkMobile(string $mobile): bool + { + if (preg_match("/^1[3456789]{1}\d{9}$/", $mobile)) { + return true; + } else { + return false; + } + } +} + +if (!function_exists('arrayKeysFilter')) { + /** + * 数组键名过滤 + * + * @param array $data + * @param array $allowKeys + * @return array + */ + function arrayKeysFilter(array $data, array $allowKeys): array + { + $list = []; + foreach ($data as $key => $val) { + if (in_array($key, $allowKeys)) { + $list[$key] = $val; + } + } + return $list; + } +} + +if (!function_exists('getFilesize')) { + /** + * 尺寸单位转换 + * + * @param $num + * @return string + */ + function getFilesize($num): string + { + $p = 0; + $format = 'B'; + if ($num > 0 && $num < 1024) { + return number_format($num) . ' ' . $format; + } + if ($num >= 1024 && $num < pow(1024, 2)) { + $p = 1; + $format = 'KB'; + } + if ($num >= pow(1024, 2) && $num < pow(1024, 3)) { + $p = 2; + $format = 'MB'; + } + if ($num >= pow(1024, 3) && $num < pow(1024, 4)) { + $p = 3; + $format = 'GB'; + } + if ($num >= pow(1024, 4) && $num < pow(1024, 5)) { + $p = 3; + $format = 'TB'; + } + $num /= pow(1024, $p); + return number_format($num, 3) . ' ' . $format; + } +} + +if (!function_exists('arrayNullToString')) { + /** + * 数组|或数据集中null值转为空字符串,并以数组格式返回 + * 通常用于api json 返回内容null转换 + * + * @param array $data 【array|collection】 + * @return array + */ + function arrayNullToString($data) + { + if ($data instanceof Collection || $data instanceof Model) { + $data = $data->toArray(); + } + // 判断是否可以遍历 + if (is_iterable($data)) { + foreach ($data as $key => $val) { + if ($val instanceof Collection || $data instanceof Model) { + $val = $val->toArray(); + } + if (is_iterable($val)) { + $data[$key] = arrayNullToString($val); + } elseif ($val === null) { + $data[$key] = ''; + } + } + } else { + $data = []; + } + return $data; + } +} + +if (!function_exists('arrayKeysExcludeFilter')) { + /** + * 数组键名排除过滤 + * + * @param array $data + * @param array $excludeKeys 排除的字段 + * @return array + */ + function arrayKeysExcludeFilter(array $data, array $excludeKeys): array + { + foreach ($data as $key => $val) { + if (in_array($key, $excludeKeys)) { + unset($data[$key]); + } + } + return $data; + } +} + +if (!function_exists('getLatelyWeekDate')) { + /** + * 获取本周的周一 到周日的日期 + */ + function getLatelyWeekDate() + { + //本周一 + $oneDate = date('Y-m-d 00:00:00', (time() - ((date('w') == 0 ? 7 : date('w')) - 1) * 86400)); //w为星期几的数字形式,这里0为周日 + $oneDateTime = strtotime($oneDate); + //返回周一到周天 1-7 + return [ + "1" => ["date"=>$oneDate], + "2" => ["date"=>date('Y-m-d 00:00:00', $oneDateTime + ((86400 * 1)))], + "3" => ["date"=>date('Y-m-d 00:00:00', $oneDateTime + ((86400 * 2)))], + "4" => ["date"=>date('Y-m-d 00:00:00', $oneDateTime + ((86400 * 3)))], + "5" => ["date"=>date('Y-m-d 00:00:00', $oneDateTime + ((86400 * 4)))], + "6" => ["date"=>date('Y-m-d 00:00:00', $oneDateTime + ((86400 * 5)))], + "7" => ["date"=>date('Y-m-d 00:00:00', $oneDateTime + ((86400 * 6)))], + ]; + } +} + +if (!function_exists('stringDesensitization')) { + /** + * 字符串脱敏 默认给手机号脱敏 其他未兼容 + * + * @param string $string + * @param string $s + * @param int $start + * @param int $len + * @return string + */ + function stringDesensitization(string $string, string $s = '****', int $start = 3, int $len = 4): string + { + return substr_replace($string, $s, $start, $len); + } +} + +if (!function_exists('checkPathExistWithMake')) { + /** + * 检测文件夹是否存在,不存在时自动创建(需要有写入权限) + * 支持递归创建 + * + * @param string $absolutePath + * @return bool + */ + function checkPathExistWithMake(string $absolutePath): bool + { + try { + $absolutePath = rtrim($absolutePath, '/'); + if (empty($absolutePath)) { + return false; + } + + if (!is_dir($absolutePath)) { + return mkdir($absolutePath, 0777, true); + } + + return true; + } catch (\Exception $e) { + return false; + } + } +} \ No newline at end of file diff --git a/app/controller/Base.php b/app/controller/Base.php new file mode 100644 index 0000000..40597e3 --- /dev/null +++ b/app/controller/Base.php @@ -0,0 +1,85 @@ + 0,//当前选中的顶级栏目 + "links"=> [],//友情连接 + "article"=> [],//文章 + "blocks"=> [],//碎片 + ]; + //系统配置信息 + protected $system = []; + + protected $auth = []; + + protected $authId = 0; + + protected $aboutCategory = []; + + // 初始化 + protected function initialize() + { + $this->auth = session('frontend_auth') ?? []; + $this->data['auth'] = $this->auth; + $this->authId = $this->auth['id'] ?? 0; + //加载基础配置 + Config::load('extra/base', 'extra_system'); + $this->system = config("extra_system"); + $this->data['system'] = $this->system; + $this->setSeo(); + $this->setLinks(); + $this->setActiveCategory(ArchivesCategoryModel::index_id); + } + + //设置选中的栏目 + protected function setActiveCategory($id) + { + $this->data["active_category_id"] = $id; + } + + //设置友情链接 缓存1小时 + protected function setLinks() + { + if(Cache::has("links")){ + $this->data["links"] = Cache::get("links",[]); + }else{ + $links = Link::findList([],[],1,20,null,["sort"=>"asc"]); + if(isset( $links['list'])){ + $this->data["links"] = $links['list']->toArray(); + }else{ + $this->data["links"] = []; + } + Cache::set("links",$this->data["links"],3600); + } + } + + //设置SEO信息 + protected function setSeo($title = '', $keywords = '', $description = '') + { + $this->data['seoTitle'] = $title ?: $this->system['seo_title'] ?? ''; + $this->data['seoKeywords'] = $keywords ?: $this->system['seo_keywords'] ?? ''; + $this->data['seoDescription'] = $description ?: $this->system['seo_description'] ?? ''; + } + + //模板 + protected function view($template = '') + { + return view($template)->assign($this->data); + } +} diff --git a/app/controller/BaseController.php b/app/controller/BaseController.php new file mode 100644 index 0000000..35482fa --- /dev/null +++ b/app/controller/BaseController.php @@ -0,0 +1,254 @@ +app = $app; + $this->request = $this->app->request; + + // 控制器初始化 + $this->initialize(); + } + + // 初始化 + protected function initialize() + { + } + + /** + * 验证数据 + * @access protected + * @param array $data 数据 + * @param string|array $validate 验证器名或者验证规则数组 + * @param array $message 提示信息 + * @param bool $batch 是否批量验证 + * @return array|string|true + * @throws ValidateException + */ + protected function validate(array $data, $validate, array $message = [], bool $batch = false) + { + if (is_array($validate)) { + $v = new Validate(); + $v->rule($validate); + } else { + if (strpos($validate, '.')) { + // 支持场景 + list($validate, $scene) = explode('.', $validate); + } + $class = false !== strpos($validate, '\\') ? $validate : $this->app->parseClass('validate', $validate); + $v = new $class(); + if (!empty($scene)) { + $v->scene($scene); + } + } + + $v->message($message); + + // 是否批量验证 + if ($batch || $this->batchValidate) { + $v->batch(true); + } + + return $v->failException(true)->check($data); + } + + /** + * 验证器 + * + * @param array $data + * @param $validate + * @param array $message + * @param bool $batch + * @return Json|bool + * @throws Exception + */ + protected function validateByApi(array $data, $validate, array $message = [], bool $batch = false) + { + try { + $this->validate($data, $validate, $message, $batch); + return true; + } catch (ValidateException $e) { + $msg = $e->getMessage(); + if ($batch) { + $msg = implode(',', $e->getError()); + } + + return $this->json(4000, $msg); + } catch (Exception $e) { + throw $e; + } + } + + /** + * 验证器 + * + * @param array $data + * @param $validate + * @param array $message + * @param bool $batch + * @return Redirect + * @throws Exception + */ + protected function validateByView(array $data, $validate, array $message = [], bool $batch = false): Redirect + { + try { + $this->validate($data, $validate, $message, $batch); + } catch (ValidateException $e) { + $msg = $e->getMessage(); + if ($batch) { + $msg = implode(',', $e->getError()); + } + + return $this->error( $msg); + } catch (Exception $e) { + throw $e; + } + } + + /** + * 操作成功跳转的快捷方法 + * @access protected + * @param mixed $msg 提示信息 + * @param string|null $url 跳转的URL地址 + * @param mixed $data 返回的数据 + * @param integer $wait 跳转等待时间 + * @param array $header 发送的Header信息 + * @return Redirect + */ + protected function success($msg = '', string $url = null, $data = '', int $wait = 3, array $header = []): Redirect + { + if (is_null($url) && isset($_SERVER["HTTP_REFERER"])) { + $url = $_SERVER["HTTP_REFERER"]; + } elseif ($url) { + $url = (strpos($url, '://') || 0 === strpos($url, '/')) ? $url : $this->app->route->buildUrl($url); + } + + $result = [ + 'code' => 1, + 'msg' => $msg, + 'data' => $data, + 'url' => $url, + 'wait' => $wait, + ]; + return $this->redirect(url('error/404', $result)); + } + + /** + * 操作错误跳转的快捷方法 + * @access protected + * @param mixed $msg 提示信息 + * @param string|null $url 跳转的URL地址 + * @param mixed $data 返回的数据 + * @param integer $wait 跳转等待时间 + * @return Redirect + */ + protected function error($msg = '', string $url = null, $data = '', int $wait = 3): Redirect + { + if (is_null($url)) { + $referer = $_SERVER['HTTP_REFERER'] ?? null; + if (empty($referer)) { + $url = $this->request->isAjax() ? '' : '/'; + } else { + $url = $this->request->isAjax() ? '' : 'javascript:history.back(-1);'; + } + } elseif ($url) { + $url = (strpos($url, '://') || 0 === strpos($url, '/')) ? $url : $this->app->route->buildUrl($url); + } + $result = [ + 'code' => 0, + 'msg' => $msg, + 'data' => $data, + 'url' => $url, + 'wait' => $wait, + ]; + + return $this->redirect(url('error/404', $result)); + } + + /** + * 返回封装后的API数据到客户端 + * 以json格式抛出异常 + * @access protected + * @param integer $code 返回的code + * @param mixed $msg 提示信息 + * @param mixed $data 要返回的数据 + * @return Json + */ + protected function json(int $code = 0, $msg = '操作成功', $data = []): Json + { + $result = [ + 'code' => $code, + 'msg' => $msg, + 'data' => $data + ]; + return json($result); + } + + /** + * URL重定向 + * @access protected + * @param string $url 跳转的URL表达式 + * @return Redirect + */ + protected function redirect($url): Redirect + { + if (!is_string($url)) { + $url = $url->__toString(); + } + return redirect($url); + } +} \ No newline at end of file diff --git a/app/controller/Error.php b/app/controller/Error.php new file mode 100644 index 0000000..972f2c5 --- /dev/null +++ b/app/controller/Error.php @@ -0,0 +1,48 @@ +isAjax()) { + return $this->json(4004, 'error request!'); + } else { + $referer = $_SERVER['HTTP_REFERER'] ?? null; + if (empty($referer)) { + $url = '/'; + } else { + $domain = $this->request->domain(); + $urlInfo = parse_url($referer); + $scheme = $urlInfo['scheme'] ?? ''; + $requestSrc = ''; + if (!empty($scheme)) { + $requestSrc = $scheme.'://'.($urlInfo['host'] ?? ''); + } + if($domain != $requestSrc) { + $url = '/'; + } else { + $url = 'javascript:history.back(-1);'; + } + } + $result = [ + 'code' => 404, + 'msg' => '无效请求! 没有找到相关资源', + 'data' => [], + 'url' => $url, + 'wait' => 5, + ]; + return view('/manager/error/jump')->assign($result); + } + } + + + public function jump() + { + $param = request()->param(); + return view()->assign($param); + } +} \ No newline at end of file diff --git a/app/controller/Feedback.php b/app/controller/Feedback.php new file mode 100644 index 0000000..1f46982 --- /dev/null +++ b/app/controller/Feedback.php @@ -0,0 +1,75 @@ +request->isPost()) { + return $this->json(4001, '请求方式错误!'); + } + + $params = [ + 'user_name' => $this->request->post('user_name/s', ''), + 'user_tel' => $this->request->post('user_tel/s', ''), + 'user_email' => $this->request->post('user_email/s', ''), + 'content' => $this->request->post('content/s', ''), + 'code' => $this->request->post('code/s', ''), + ]; + + try{ + $validate = $this->validateByApi($params, [ + 'code|验证码'=>'require|captcha', + 'user_name|姓名' => 'chs|min:2|max:30', + 'user_tel|联系电话' => 'min:5|max:20|mobile', + 'user_email|邮箱地址' => 'email', + 'content|留言内容' => 'require|min:6|max:500', + ]); + if ($validate !== true) { + return $validate; + } + + if ($this->authId > 0) { + $account = AccountRepository::getInstance()->findById($this->authId); + if ($account) { + $params['user_name'] = $params['user_name'] ?: ($account->nickname ?? ''); + $params['user_tel'] = $params['user_tel'] ?: ($account->mobile ?? ''); + } + } + + FeedbackModel::create([ + 'account_id' => $this->authId, + 'user_name' => $params['user_name'] ?: '', + 'user_tel' => $params['user_tel'] ?: '', + 'user_email' => $params['user_email'], + 'content' => $params['content'], + 'created_at' => date('Y-m-d H:i:s'), + ]); + + + return $this->json(0, '感谢你的留言!'); + } catch (\Exception $e) { + return $this->json(5001, '服务器异常,留言信息提交失败!'); + } + + } + + public function code() + { + return Captcha::create('verify'); + } +} \ No newline at end of file diff --git a/app/controller/Index.php b/app/controller/Index.php new file mode 100644 index 0000000..c321167 --- /dev/null +++ b/app/controller/Index.php @@ -0,0 +1,39 @@ +data['slide'] = Slide::findList([["position","=",SlidePosition::home_position]])['list']; + $this->data['topCategoryId'] = ArchivesCategory::index_id ; + + //碎片 + $this->data['blocks'] = Block:: getByCategoryId(ArchivesCategory::index_id); + + return $this->view(); + }catch (RepositoryException $e){ + return $this->error("服务器错误"); + } + + + } +} \ No newline at end of file diff --git a/app/controller/Page.php b/app/controller/Page.php new file mode 100644 index 0000000..3e98c42 --- /dev/null +++ b/app/controller/Page.php @@ -0,0 +1,150 @@ +redirect("/"); + } + $category = ArchivesCategoryModel::findById($categoryId); + if(empty($category)){ + return $this->error("内容不存在"); + } + + + //如果有链接 + if(!empty($category['link'])){ + return $this->redirect($category['link']); + } + + $this->data["category"] = $category; + $this->setActiveCategory($category['id']); + $this->data['topCategoryId'] = ArchivesCategoryModel::firstGradeById($category['id']) ; + + $categoryModel = ArchivesModel::allModel(); + //所有类型都要把碎片加上 + $this->data["blocks"] = Block:: getByCategoryId($category['id']); + + switch ($category['model_id']){ + //文章模型 + case $categoryModel[ArchivesModel::MODEL_ARCHIVES]: + return $this->archives($category,$category['cover_template']); + break; + default: + return $this->redirect("/"); + } + + } + + //文章列表 + + /** + * + * @param $categoryId 栏目id + * @param $categoryTemplate 模板 + */ + protected function archives($category,$categoryTemplate) + { + //动态设置当前分页驱动 + app('think\App')->bind(Paginator::class, DxtcPage::class); + $this->data["archives"] = Archives::getListPageByCategory($category['id'],$category['page_size']); + return $this->view(empty($categoryTemplate)?"archives_default":$categoryTemplate); + } + + + /** + * + * @param $categoryId 栏目id + * @param $categoryTemplate 模板 + */ + protected function page($categoryTemplate) + { + return $this->view(empty($categoryTemplate)?"page_default":$categoryTemplate); + } + + /** + * 文章详情 + * + * @param $articleId + */ + public function archivesInfo() + { + $articleId = input("articleId/d",0); + $archive = Archives::findOne([["id","=",$articleId]]); + if(empty($archive)){ + return $this->error("内容不存在"); + } + $archive->inc("views")->update(); + $this->data["archive"] = $archive; + + + $category = ArchivesCategoryModel::findById($archive['category_id']); + if(empty($category)){ + return $this->error("内容不存在"); + } + $this->data["category"] = $category; + $this->setActiveCategory($category['id']); + $this->data['topCategoryId'] = ArchivesCategoryModel::firstGradeById($category['id']) ; + + + //所有类型都要把碎片加上 + $this->data["blocks"] = Block:: getByCategoryId($category['id']); + + + $seo_title = empty($archive['seo_title']) + ? + $archive['title'] + : + $archive['seo_title']; + $seo_keywords = empty($archive['seo_keywords']) + ? + "" + : + $archive['seo_keywords']; + $seo_description = empty($archive['seo_description']) + ? + "" + : + $archive['seo_description']; + + + $this->setSeo( $seo_title,$seo_keywords,$seo_description); + + + // 上一篇 + $this->data["prev"] = Archives::findOne([["category_id","=",$category['id']], ['sort', '<', $archive['sort']]] + , [], function ($q) { + return $q->with(["archivesCategory"])->order(['sort'=> 'desc']); + }); + + // 下一篇 + $this->data["next"] = Archives::findOne([["category_id","=",$category['id']], ['sort', '>', $archive['sort']]] + , [], function ($q) { + return $q->with(["archivesCategory"])->order(['sort'=> 'asc']); + }); + + return $this->view(!empty($category['detail_template'])?$category['detail_template']:"archives_default"); + } + + +} \ No newline at end of file diff --git a/app/controller/api/Archives.php b/app/controller/api/Archives.php new file mode 100644 index 0000000..46347a0 --- /dev/null +++ b/app/controller/api/Archives.php @@ -0,0 +1,20 @@ +middleware = [ + 'jwt', + 'apiLogin' => ['except' => $this->noNeedLogin] + ]; + } + + public function __call($method, $args) + { + return $this->json(4004, 'error request!'); + } +} \ No newline at end of file diff --git a/app/controller/api/Common.php b/app/controller/api/Common.php new file mode 100644 index 0000000..d97a3b9 --- /dev/null +++ b/app/controller/api/Common.php @@ -0,0 +1,82 @@ +scene('send_sms')->check($input)) { + return $this->json(4001, '参数错误'); + } + if (!in_array($input['type'], ['register', 'login', 'binding'])) { + return $this->json(4002, '参数错误'); + } + + CommonRepository::getInstance()->sendSms($input['phone'], $input['type']); + return $this->json(); + } + + /** + * 查看轮播图可视位置配置信息 + */ + public function slidePositions(): Json + { + $repo = OperationRepository::getInstance(); + $list = $repo->slidePositions(); + return $this->json(0, 'success', $list); + } + + + /** + * 轮播图 + */ + public function slides(): Json + { + $size = $this->request->param('size/d', 0); + $position = trim($this->request->param('position/s', '')); + if (empty($position)) { + return $this->json(); + } + + try { + $repo = OperationRepository::getInstance(); + $list = $repo->slideListByPosition($position, $size); + } catch (\Exception $e) { + $list = new Collection(); + } + + return $this->json(0, 'success', $list); + } + + +} \ No newline at end of file diff --git a/app/controller/api/Index.php b/app/controller/api/Index.php new file mode 100644 index 0000000..8dbd961 --- /dev/null +++ b/app/controller/api/Index.php @@ -0,0 +1,41 @@ + 0, 'msg' => 'I am index']); + } + + /** + * 测试用 + * + * @return Json + * @throws RepositoryException + */ + public function test(): Json + { + $userId = $this->request->middleware('userInfo')['user_id'] ?? 0; + $user = AccountRepository::getInstance()->info($userId, []); + return json(['code' => 0, 'msg' => 'I am test ', 'data' => $user]); + } + + + + +} \ No newline at end of file diff --git a/app/controller/api/User.php b/app/controller/api/User.php new file mode 100644 index 0000000..4089826 --- /dev/null +++ b/app/controller/api/User.php @@ -0,0 +1,15 @@ + 0, 'msg' => 'I am index']); + } + + +} \ No newline at end of file diff --git a/app/controller/api/file/Upload.php b/app/controller/api/file/Upload.php new file mode 100644 index 0000000..afe4f94 --- /dev/null +++ b/app/controller/api/file/Upload.php @@ -0,0 +1,132 @@ +isCompress = $system['compress'] ?? true; + } + $this->validate = new VUpload(); + $this->uploadPath = Config::get('filesystem.disks.local.url'); + if(is_writable(app()->getRootPath() . 'public' . $this->uploadPath)){ + $this->uploadPathIsWritable = true; + } + + $this->cancelTimeLimit(); + } + + + /** + * 通用文件上传 + * @return Json + */ + public function file() + { + $file = request()->file('file'); + if (empty($file)) { + return $this->json(4001, '请上传的文件'); + } + + if($this->validate->checkFile($file)){ + try{ + if(!$this->uploadPathIsWritable){ + throw new \Exception('上传文件夹需要写入权限'); + } + $src = Filesystem::putFile('files/'.date('Ym'), $file, 'uniqid'); + $src = $this->uploadPath . '/' . $src; + $return['src'] = $src; + $return['name'] = $file->getOriginalName(); + + //加入上传文件表 + File::add($file, $src, $file->md5()); + } catch (\Exception $e) { + return $this->json(4003, $e->getMessage()); + } + + return $this->json(0,'success', $return); + }else{ + + $errorMsg = Lang::get($this->validate->getError()); + return $this->json(4002, $errorMsg); + } + } + + /** + * 通用图片上传 + * @return Json + */ + public function image() + { + $image = request()->file('image'); + if (empty($image)) { + return $this->json(4001, '请上传图片文件'); + } + $md5 = $image->md5();//文件md5 + if($this->validate->checkImage($image)){ + try{ + if(!$this->uploadPathIsWritable){ + throw new \Exception('上传文件夹需要写入权限'); + } + $src = Filesystem::putFile('images/'.date('Ym'), $image, 'uniqid'); + $src = $this->uploadPath . '/' . $src; + $return['src'] = $src; + if($this->isCompress){ + Image::resize($src); + } + + //加入上传文件表 + File::add($image, $src,$md5); + } catch (\Exception $e) { + return $this->json(4003, $e->getMessage()); + } + + return $this->json(0, 'success', $return); + }else{ + + $errorMsg = Lang::get($this->validate->getError()); + return $this->json(4002, $errorMsg); + } + } + + + /** + * 同步到OOS服务器存储 + * @param string $src + */ + private function syncToOos(string $src) + { + + } +} \ No newline at end of file diff --git a/app/controller/manager/Account.php b/app/controller/manager/Account.php new file mode 100644 index 0000000..d36af98 --- /dev/null +++ b/app/controller/manager/Account.php @@ -0,0 +1,736 @@ +findById($id, [], function ($q) { + return $q->with(['serviceList']); + }); + + $statusList = [ + Order::STATUS_SHIPPED, Order::STATUS_PAID, Order::STATUS_COMPLETED + ]; + $consumption = OrderRepository::getInstance()->userOrderList($id, [], 1, 0, $statusList); + $orderNum = 0; + $orderScoreNum = 0; + $totalPrice = 0; + $totalScore = 0; + $totalCoin = 0; + $consumption->each(function ($item) use (&$totalPrice, &$totalScore, &$totalCoin, &$orderScoreNum, &$orderNum) { + if ($item->is_score == AccountModel::COMMON_ON) { + $orderScoreNum += 1; + } else { + $orderNum += 1; + } + $totalPrice += $item->price; + $totalScore += $item->score; + $totalCoin += $item->coin; + }); + $item['total_price'] = Math::fen2Yuan($totalPrice); + $item['total_score'] = $totalScore; + $item['total_coin'] = $totalCoin; + $item['order_num'] = $orderNum; + $item['order_score_num'] = $orderScoreNum; + $item['order_newest'] = $consumption->toArray()[0] ?? []; + $item['customer_service'] = $item->serviceList->name ?? ''; + $item['source_text'] = AccountRepository::getInstance()->getSourceDetail($id); + $item['channel_text'] = AccountModel::channelTextList()[$item['channel']] ?? ''; + + $this->data['item'] = $item; + + return $this->view(); + } + + /** + * 编辑 + * + * @return Json|View + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + * @throws Exception + */ + public function edit() + { + $id = input('id/d', 0); + if (!$info = AccountRepository::getInstance()->findById($id)) { + if ($this->request->isPost()) { + return $this->json(4000, '用户不存在'); + } else { + return $this->error('用户不存在'); + } + } + + if ($this->request->isPost()) { + $item = input('post.'); + $validate = $this->validateByApi($item, [ + 'nickname' => 'require', + ]); + + if ($validate !== true) { + return $validate; + } + + try { + $info->save($item); + return $this->json(); + } catch (ValidateException $e) { + return $this->json(4001, $e->getError()); + } + } + + $this->data['item'] = $info; + + return $this->view(); + } + + /** + * 单个字段编辑 + * + * @return Json + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + * @throws Exception + */ + public function modify(): Json + { + if ($this->request->isPost()) { + $item = input('post.'); + $validate = $this->validateByApi($item, [ + 'field' => 'require', + 'value' => 'require', + ]); + + if ($validate !== true) { + return $validate; + } + + if (!$info = AccountModel::findById($item['id'])) { + return $this->json(4001, '记录不存在'); + } + + $update = [$item['field'] => $item['value']]; + + try { + $info->save($update); + return $this->json(); + } catch (ValidateException $e) { + return $this->json(4001, $e->getError()); + } + } + return $this->json(4000, '非法请求'); + } + + /** + * 列表 + * + * @return View|Json + * @throws Exception + */ + public function index() + { + if ($this->request->isPost()) { + $page = input('page/d', 1); + $size = input('size/d', 20); + $searchParams = input('searchParams'); + $search = []; + $other = []; + if ($searchParams) { + foreach ($searchParams as $key => $param) { + if ($key == 'tag' && !empty($param)) { + $other['tag_id'] = $param; + continue; + } + if ($param || $param == '0') { + $search[] = [$key, 'like', '%'.$param.'%']; + } + } + } + + $search[] = ['phone_active', '=', AccountModel::COMMON_ON]; + + // 后台绑定的账号 + $accountId = $this->auth['account_id'] ?? 0; + + try { + $items = AccountRepository::getInstance()->customerList($search, [], $accountId, $page, $size, function ($q) use ($other) { + return $q->when(isset($other['tag_id']), function ($query) use ($other) { + $query->leftJoin('account_tag_pivot atp', 'atp.account_id = id')->where('atp.tag_id', $other['tag_id']); + }); + }); + return $this->json(0, '操作成功', $items); + } catch (RepositoryException $e) { + return $this->json(4001, $e->getMessage()); + } catch (Exception $e) { + return $this->json(5001, '获取用户列表失败'.$e->getMessage()); + } + } + + $this->data['channelList'] = AccountModel::channelTextList(); + $this->data['customerList'] = Staff::getCustomerServiceList(); + $this->data['tagList'] = AccountTag::getTags(); + + return $this->view(); + } + + /** + * 分配员工 + * + * @return View|Json + * @throws Exception + */ + public function staff() + { + $id = input('id/s', ''); + if ($this->request->isPost()) { + $ids = input('ids/s'); + $staffId = input('staff_id/d', 0); + if (empty($ids)) { + return $this->json(4001, '请选择要操作的用户'); + } + + if (!$staffId) { + return $this->json(4001, '请选择分配的员工'); + } + + $ids = explode(',', $ids); + + try { + CustomerReceive::allotServiceByBatch($ids, $staffId); + return $this->json(0, '操作成功'); + } catch (RepositoryException $e) { + return $this->json(4001, $e->getMessage()); + } catch (Exception $e) { + Log::error('分配指定员工失败'.$e->getMessage()); + return $this->json(5001, '分配指定员工失败'); + } + } + + $staffList = Staff::getCustomerServiceList(); + // 分配的是客服分组下所有客服 + $this->data['servicerList'] = json_encode($staffList, JSON_UNESCAPED_UNICODE); + $this->data['id'] = $id; + + return $this->view(); + } + + /** + * 分配客户标签 + * + * @return View|Json + * @throws Exception + */ + public function tag() + { + $id = input('id/s', ''); + if ($this->request->isPost()) { + $ids = input('ids/s'); + $tagId = input('tag_id/s'); + if (empty($ids)) { + return $this->json(4001, '请选择要操作的用户'); + } + + if (empty($tagId)) { + return $this->json(4001, '请选择分配的标签'); + } + + $ids = explode(',', $ids); + $tags = explode(',', $tagId); + + Db::startTrans(); + try { + // 删除所有人标签 + AccountTagPivot::whereIn('account_id', $ids)->delete(); + // 新增标签 + $insert = []; + foreach ($ids as $id) { + foreach ($tags as $tag) { + $arr = []; + $arr['account_id'] = $id; + $arr['tag_id'] = $tag; + + $insert[] = $arr; + } + } + + (new AccountTagPivot())->saveAll($insert); + Db::commit(); + return $this->json(0, '操作成功'); + } catch (RepositoryException $e) { + Db::rollback(); + return $this->json(4001, $e->getMessage()); + } catch (Exception $e) { + Db::rollback(); + Log::error('分配客户标签失败'.$e->getMessage()); + return $this->json(5001, '分配客户标签失败'); + } + } + + $tagList = AccountTag::order('sort', 'desc')->order('id', 'asc')->select()->toArray(); + + // 分配的是线上客服 + $this->data['tagList'] = json_encode($tagList, JSON_UNESCAPED_UNICODE); + $this->data['id'] = $id; + + return $this->view(); + } + + /** + * 分配客户来源 + * + * @return View|Json + * @throws Exception + */ + public function source() + { + $id = input('id/s', ''); + if ($this->request->isPost()) { + $ids = input('ids/s'); + $channel = input('channel/s'); + if (empty($ids)) { + return $this->json(4001, '请选择要操作的用户'); + } + + if (empty($channel)) { + return $this->json(4001, '请选择分配的客户来源'); + } + + $value = $channel == AccountModel::CHANNEL_MEMBER ? input('type_staff') : input('type_'.$channel); + $ids = explode(',', $ids); + + Db::startTrans(); + try { + $field = 'id,channel,inviter_account_id,inviter_parent_id,source_code'; + $accountList = AccountModel::whereIn('id', $ids)->column($field, 'id'); + $update = [];//更新account表 + $insert = [];//插入account_operate_log表 + + switch ($channel) { + case AccountModel::CHANNEL_NORMAL: + // 设为自然流量 清空上级邀请人和上上级邀请人 + $update = ['inviter_account_id' => 0, 'inviter_parent_id' => 0, 'channel' => $channel, 'source_code' => null]; + break; + case AccountModel::CHANNEL_CUSTOMER: + case AccountModel::CHANNEL_MEMBER: + // 客户分享或员工分享 修改上级邀请人ID + $update = ['inviter_account_id' => $value, 'channel' => $channel, 'source_code' => null]; + break; + case AccountModel::CHANNEL_ACTIVITY: + // 活码分享 + $update = ['inviter_account_id' => 0, 'source_code' => $value, 'channel' => $channel]; + break; + } + + $now = date('Y-m-d H:i:s'); + $createdBy = $this->auth['user_id'] ?? 0; + foreach ($ids as $id) { + $log = []; + $originalSource = $accountList[$id] ?? []; + switch ($channel) { + case AccountModel::CHANNEL_NORMAL: + $log['description'] = sprintf("从原始来源【channel=%s,inviter_account_id=%s,source_code=%s】变更为新来源【channel=%s,inviter_account_id=%s,source_code=%s】", + $originalSource['channel'] ?? '', $originalSource['inviter_account_id'] ?? 0, + $originalSource['source_code'] ?? '', $channel, 0, null); + break; + case AccountModel::CHANNEL_CUSTOMER: + case AccountModel::CHANNEL_MEMBER: + $log['description'] = sprintf("从原始来源【channel=%s,inviter_account_id=%s,source_code=%s】变更为新来源【channel=%s,inviter_account_id=%s,source_code=%s】", + $originalSource['channel'] ?? '', $originalSource['inviter_account_id'] ?? 0, + $originalSource['source_code'] ?? '', $channel, $value, null); + break; + case AccountModel::CHANNEL_ACTIVITY: + $log['description'] = sprintf("从原始来源【channel=%s,inviter_account_id=%s,source_code=%s】变更为新来源【channel=%s,inviter_account_id=%s,source_code=%s】", + $originalSource['channel'] ?? '', $originalSource['inviter_account_id'] ?? 0, + $originalSource['source_code'] ?? '', $channel, 0, $value); + break; + } + + $log['type'] = AccountOperateLog::TYPE_CHANGE_SOURCE; + $log['account_id'] = $id; + $log['created_at'] = $now; + $log['created_by'] = $createdBy; + $insert[] = $log; + } + + (new AccountOperateLog())->saveAll($insert); + (new AccountModel())->whereIn('id', $ids)->save($update); + Db::commit(); + return $this->json(0, '操作成功'); + } catch (RepositoryException $e) { + Db::rollback(); + return $this->json(4001, $e->getMessage()); + } catch (Exception $e) { + Db::rollback(); + Log::error('分配客户标签失败'.$e->getMessage()); + return $this->json(5001, '分配客户标签失败'); + } + } + + // 客服来源 + $this->data['channelList'] = AccountModel::channelTextList(); + $this->data['id'] = $id; + + return $this->view(); + } + + /** + * 到店设置 + * + * @return View|Json + * @throws Exception + */ + public function sign() + { + $id = input('id/s', ''); + if ($this->request->isPost()) { + $ids = input('ids/s'); + $sign = input('sign/d'); + if (empty($ids)) { + return $this->json(4001, '请选择要操作的用户'); + } + + if (!in_array($sign, [AccountModel::COMMON_ON, AccountModel::COMMON_OFF])) { + + return $this->json(4001, '请选择是否到店'); + } + + $ids = explode(',', $ids); + + Db::startTrans(); + try { + (new AccountModel())->whereIn('id', $ids)->save(['is_sign' => $sign]); + Db::commit(); + return $this->json(0, '操作成功'); + } catch (Exception $e) { + Db::rollback(); + Log::error('是否到店操作失败'.$e->getMessage()); + return $this->json(5001, '是否到店操作失败'); + } + } + + $this->data['id'] = $id; + + return $this->view(); + } + + /** + * 获取员工列表 + * + * @return Json + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException|Exception + */ + public function getStaffList(): Json + { + if ($this->request->isPost()) { + $keyword = input('keyword/s', ''); + $type = input('type/s', '');//员工类型标识 如在线客服=customer-online + $page = input('page/d', 1); + $size = input('size/d', 0); + $id = input('id/d', 0); + + $relationIds = [];//已选记录 + if ($id > 0 && $activity = Activity::findById($id)) { + $relationIds = explode(',', $activity['account_id']); + } + + $res = Staff::getStaff($type, $keyword, $page, $size); + + if ($res['total'] > 0) { + $res['list'] = $res['list']->toArray(); + foreach ($res['list'] as &$item) { + if (count($relationIds) > 0 && in_array($item['id'], $relationIds)) { + $item['selected'] = true; + } + } + } + + return $this->json(0, '操作成功', $res); + } + return $this->json(4001, '非法请求'); + } + + /** + * 获取客户列表 + * + * @return Json + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException|Exception + */ + public function getAccountList(): Json + { + if ($this->request->isPost()) { + $keyword = input('keyword/s', ''); + $page = input('page/d', 1); + $size = input('size/d', 10); + $id = input('id', ''); + + $relationIds = explode(',', $id);//已选记录 + + $where = []; + $where[] = ['is_staff', '=', AccountModel::COMMON_OFF]; + if (!empty($keyword)) { + $where[] = ['nickname|real_name|mobile', 'like', '%'.$keyword.'%']; + } + + $res = AccountModel::findList($where, ['id', 'nickname', 'real_name', 'mobile'], $page, $size); + + if ($res['total'] > 0 && $relationIds) { + $res['list'] = $res['list']->toArray(); + foreach ($res['list'] as &$item) { + $item['name_text'] = sprintf("昵称:%s;真实姓名:%s,手机号:%s", $item['nickname'], $item['real_name'], $item['mobile']); + if (count($relationIds) > 0 && in_array($item['id'], $relationIds)) { + $item['selected'] = true; + } + } + } + + return $this->json(0, '操作成功', $res); + } + return $this->json(4001, '非法请求'); + } + + /** + * 充值孔雀币 + * */ + public function rechargeCoin() + { + $id = input('id/s'); + if ($this->request->isPost()) { + $ids = input('ids/s'); + if (empty($ids)) { + return $this->json("4003", "请选择用户"); + } + $ids = explode(",", $ids); + if (count($ids) > 1000) { + return $this->json("4003", "一次最多选择1000条"); + } + + $coin = input("coin/d", 1, "abs"); + + $Account = AccountRepository::getInstance()->getModel() + ->where("id", "in", $ids)->lock(true) + ->select(); + + Db::startTrans(); + try { + AccountRepository::getInstance()->getModel()->where("id", "in", $ids)->inc("coin", $coin)->update(); + $time = date("Y-m-d H:i:s"); + $dataLog = []; + $dataOperationLog = []; + $Account->each(function ($item) use ($coin, $time, &$dataLog, &$dataOperationLog) { + $dataLog[] = [ + "account_id" => $item["id"], + "operator" => ($this->auth['nickname'] ?? ""), + "operator_id" => $this->auth['user_id'] ?? 0, + "name" => "后台充值孔雀币", + "num" => $coin, + "type" => AccountDataLog::TYPE_COIN, + "action" => AccountDataLog::ACTION_ADMIN_RECHARGE, + "created_at" => $time, + "surplus" => ($item["coin"] + $coin), + ]; + $dataOperationLog[] = [ + "account_id" => $item["id"], + "operator" => ($this->auth['nickname'] ?? ""), + "num" => $coin, + "remark" => "后台充值孔雀币", + "type" => AccountDataLog::TYPE_COIN, + "created_at" => $time, + ]; + }); + + AccountDataLog::insertAll($dataLog); + AccountDataOperationLog::insertAll($dataOperationLog); + + Db::commit(); + return $this->json(); + } catch (Exception $e) { + Db::rollback(); + return $this->json("5003", "充值失败-1:".$e->getMessage()); + } catch (RepositoryException $e) { + Db::rollback(); + return $this->json("5003", "充值失败-2:".$e->getMessage()); + } + + } + $this->data['id'] = $id; + + return $this->view(); + } + + /** + * 用户等级管理 + * */ + public function accountLevel() + { + if ($this->request->isPost()) { + $page = input('page/d', 1); + $limit = input('size/d', 10); + $items = AccountLevel::findList([], [], $page, $limit, null, ["value" => "desc"]); + + $items["list"]->each(function (&$item) { + if (empty($item["rights"])) { + return; + } + $str = ""; + foreach (explode(",", $item["rights"]) as $ritem) { + foreach (AccountLevel::$rightsArray as $mitem) { + if ($mitem["key"] == $ritem) { + $str .= $mitem["title"]." "; + } + } + } + $item->rights = $str; + }); + + return $this->json(0, '操作成功', $items); + } + return $this->view(); + } + + /** + * 添加用户等级管理 + * */ + public function addAccountLevel() + { + if ($this->request->isPost()) { + $item = input('post.'); + $rule = [ + 'name|名称' => 'require', + 'value|成长值' => 'require', + ]; + $validate = $this->validateByApi($item, $rule); + if ($validate !== true) { + return $validate; + } + try { + AccountLevel::create($item); + return $this->json(); + } catch (ValidateException $e) { + return $this->json(4001, $e->getError()); + } + } + + //权益列表 + $rightsArray = AccountLevel::$rightsArray; + $this->data['rightsJson'] = json_encode($rightsArray); + + return $this->view(); + } + + /** + * 添加用户等级管理 + * */ + public function delAccountLevel() + { + if ($this->request->isPost()) { + $id = input('id/d'); + $item = AccountLevel::findById($id); + if (empty($item)) { + return $this->json(4001, "信息不存在"); + } + AccountLevel::destroy($id); + return $this->json(); + } + + } + + /** + * 编辑用户等级管理 + * */ + public function editAccountLevel() + { + $id = input("id/d"); + $info = AccountLevel::findById($id); + if ($this->request->isPost()) { + if (empty($info)) { + return $this->json(4001, "信息不存在"); + } + $item = $this->request->only(["name", "rights", "poster", "value", "content"]); + $rule = [ + 'name|名称' => 'require', + 'value|成长值' => 'require', + ]; + $validate = $this->validateByApi($item, $rule); + if ($validate !== true) { + return $validate; + } + try { + AccountLevel::updateById($id, $item); + return $this->json(); + } catch (ValidateException $e) { + return $this->json(4001, $e->getError()); + } + } + + if (empty($info)) { + return $this->error("信息不存在"); + } + + + $this->data['item'] = $info; + + //权益列表 + $rightsArray = AccountLevel::$rightsArray; + $selectd = []; + if (!empty($info['rights'])) { + $selectd = explode(",", $info['rights']); + } + $this->data['rightsJson'] = AccountLevel::xmSelectJson($selectd); + + return $this->view(); + } + + +} \ No newline at end of file diff --git a/app/controller/manager/Archives.php b/app/controller/manager/Archives.php new file mode 100644 index 0000000..d9d0dd0 --- /dev/null +++ b/app/controller/manager/Archives.php @@ -0,0 +1,306 @@ +request->isPost()) { + $ids = input('post.ids/a', []); + if (empty($ids)) { + $ids[] = input('post.id/d'); + } + ArchivesModel::deleteByIds($ids); + return $this->json(); + } + return $this->json(4001, '非法请求!'); + } + + /** + * 编辑 + * + * @return Json|View + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + * @throws Exception + */ + public function edit() + { + $id = input('id/d', 0); + + if (!$info = ArchivesModel::findById($id)) { + return $this->json(4001, '记录不存在'); + } + + if ($this->request->isPost()) { + $item = input('post.'); + if (isset($item['video_src'])) { + $item['video'] = $item['video_src']; + unset($item['video_src']); + } + + $validate = $this->validateByApi($item, [ + 'category_id|栏目' => 'require|gt:0', + 'title|标题' => 'require|max:255', + 'summary|摘要' => 'max:255', + 'content|内容' => 'require', + ], ['category_id.gt' => '请选择栏目']); + + if ($validate !== true) { + return $validate; + } + + try { + $now = date('Y-m-d H:i:s'); + $item['updated_at'] = $now; + $item['updated_by'] = $this->auth['user_id']; + $info->save($item); + return $this->json(); + } catch (ValidateException $e) { + return $this->json(4001, $e->getError()); + } + } + + $showFieldList = ArchivesModelField::showFieldList();//所有栏目 可展示字段列表 + $currentShowFields = $showFieldList[$info['category_id']] ?? [];//当前选中栏目 可展示字段列表 + + $this->data['item'] = $info; + $this->data['jsonList'] = $this->xmSelectJson([$info['category_id']]); + $this->data['currentList'] = $currentShowFields; + + return $this->view(); + } + + /** + * 单个字段编辑 + * + * @return Json + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + * @throws Exception + */ + public function modify(): Json + { + if ($this->request->isPost()) { + $item = input('post.'); + $validate = $this->validateByApi($item, [ + 'field' => 'require', + 'value' => 'require', + ]); + + if ($validate !== true) { + return $validate; + } + + if (!$info = ArchivesModel::findById($item['id'])) { + return $this->json(4001, '记录不存在'); + } + + $update = [$item['field'] => $item['value']]; + + try { + $info->save($update); + return $this->json(); + } catch (ValidateException $e) { + return $this->json(4001, $e->getError()); + } + } + return $this->json(4000, '非法请求'); + } + + /** + * 添加 + * + * @return Json|View + * @throws Exception + */ + public function add() + { + $categoryId = input('category_id/d', 0); + + if ($this->request->isPost()) { + $item = input('post.'); + if (isset($item['video_src'])) { + $item['video'] = $item['video_src']; + unset($item['video_src']); + } + + $validate = $this->validateByApi($item, [ + 'category_id|栏目' => 'require|gt:0', + 'title|标题' => 'require|max:255', + 'summary|摘要' => 'max:255', + 'content|内容' => 'require', + ], ['category_id.gt' => '请选择栏目']); + + if ($validate !== true) { + return $validate; + } + + try { + $now = date('Y-m-d H:i:s'); + $item['created_at'] = $now; + $item['published_at'] = $now; + $item['created_by'] = $this->auth['user_id']; + ArchivesModel::create($item); + + return $this->json(); + } catch (ValidateException $e) { + return $this->json(4001, $e->getError()); + } + } + + $showFieldList = ArchivesModelField::showFieldList();//所有栏目 可展示字段列表 + //有指定栏目获取指定栏目 否则获取第一个 可展示字段列表 + $currentShowFields = $categoryId > 0 ? ($showFieldList[$categoryId] ?? []) : array_values($showFieldList)[0]; + + $this->data['categoryId'] = $categoryId ?? 0; + $this->data['diaryId'] = $diaryId ?? 0; + $this->data['jsonList'] = $this->xmSelectJson([$categoryId]); + $this->data['showList'] = json_encode($showFieldList, JSON_UNESCAPED_UNICODE); + $this->data['currentList'] = $currentShowFields; + + return $this->view(); + } + + /** + * 列表 + * + * @return View|Json + * @throws Exception + */ + public function index() + { + $categoryId = input('category_id/d', 0); + if ($this->request->isPost()) { + $page = input('page/d', 1); + $limit = input('size/d', 20); + $searchParams = input('searchParams'); + $where = []; + + if ($categoryId > 0) { + $where[] = ['category_id', '=', $categoryId]; + } + + if ($searchParams) { + foreach ($searchParams as $key => $param) { + if (!empty($param)) { + if (is_string($param)) { + $where[] = [$key, 'like', '%'.$param.'%']; + } elseif (is_array($param)) { + //数组空元素去除 + foreach ($param as $k => $val) { + if (empty($val)) { + unset($param[$k]); + } + } + if (!empty($param)) { + $where[] = [$key, 'in', $param]; + } + } + } + } + } + + $items = ArchivesModel::findList($where, [], $page, $limit, function ($q) { + return $q->with(['member', 'category']) + ->order('sort', 'desc') + ->order('id', 'desc'); + }); + + + + return $this->json(0, '操作成功', $items); + } + + $selected = $categoryId > 0 ? [$categoryId] : []; + + $this->data['categoryId'] = $categoryId; + $this->data['categoryJson'] = $this->categoryJson($selected); + $this->data['archivesPath'] = '/'.Config::MINI_PATH_ARCHIVES; + + return $this->view(); + } + + /** + * 构造分类 json数据[zTree用] + * + * @param array $selected + * @return false|string + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + */ + private function categoryJson(array $selected = []) + { + $category = ArticleCategoryModel::order('sort', 'desc') + ->field('id,pid,title') + ->select() + ->toArray(); + foreach ($category as $k => $m) { + $category[$k]['checked'] = in_array($m['id'], $selected); + $category[$k]['spread'] = true; + } + + $category = CmsRepository::getInstance()->buildMenuChild(0, $category); + return json_encode($category, JSON_UNESCAPED_UNICODE); + } + + /** + * 内容分类 构造xmSelect json数据[xmSelect用] + * + * @param array $selected + * @param array $disabled + * @return false|string + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + */ + private function xmSelectJson(array $selected = [], array $disabled = []) + { + $category = ArticleCategoryModel::order('sort', 'desc') + ->field('id,pid,title') + ->select() + ->toArray(); + foreach ($category as $k => $m) { + $category[$k]['selected'] = in_array($m['id'], $selected); + $category[$k]['disabled'] = in_array($m['id'], $disabled); + } + + $category = CmsRepository::getInstance()->buildMenuChild(0, $category); + $category = CmsRepository::getInstance()->handleSelectedList($category); + return json_encode($category, JSON_UNESCAPED_UNICODE); + } +} \ No newline at end of file diff --git a/app/controller/manager/ArchivesCategory.php b/app/controller/manager/ArchivesCategory.php new file mode 100644 index 0000000..9fb9a46 --- /dev/null +++ b/app/controller/manager/ArchivesCategory.php @@ -0,0 +1,256 @@ +request->isPost()) { + $ids = input('post.ids/a', []); + if (empty($ids)) { + $ids[] = input('post.id/d'); + } + if (ArchivesCategoryModel::hasChildrenByIds($ids)) { + return $this->json(4002, '待删除数据存在子数据'); + } + + if (ArchivesCategoryModel::hasContentByIds($ids)) { + return $this->json(4002, '待删除数据存在内容文章'); + } + ArchivesCategoryModel::deleteByIds($ids); +// Log::write(get_class().'Del', 'del', '涉及到的ID为:'.implode(',', $ids)); + return $this->json(); + } + return $this->json(4001, '非法请求!'); + } + + /** + * 编辑 + * + * @return Json|View + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + * @throws Exception + */ + public function edit() + { + Cache::delete("categoryNames");//删除缓存 + $id = input('id/d', 0); + + if (!$info = ArchivesCategoryModel::findById($id)) { + return $this->json(4001, '记录不存在'); + } + + if ($this->request->isPost()) { + $item = input('post.'); + $validate = $this->validateByApi($item, [ + 'pid|父级分类' => 'require|number', + 'model_id|所属模型' => 'require|number|gt:0', + 'title|标题' => 'require|max:100', + 'name|标识' => 'unique:archives_category,name,'.$info['id'] ?? 0, + 'description|描述' => 'max:255', + ], ['model_id' => '所属模型必需选择']); + + if ($validate !== true) { + return $validate; + } + + Db::startTrans(); + try { + $oldPath = $info['path'] ?? ''; + $item['path'] = ArchivesCategoryModel::getPath($item['pid']); + $info->save($item); + + //刷新所有路径 + $oldPath = $oldPath.','.$id; + $newPath = $item['path'].','.$id; + if ($oldPath != $newPath) { + ArchivesCategoryModel::refreshPath(); + } + Db::commit(); + return $this->json(); + } catch (ValidateException $e) { + Db::rollback(); + return $this->json(4001, $e->getError()); + } + } + + $disabled = ArchivesCategoryModel::getAllChildrenIds($id); + $disabled[] = $id; + $this->data['jsonList'] = $this->categoryJson([$info['pid']], $disabled); + $this->data['modelList'] = $this->modelJson([$info['model_id']], []); + $this->data['item'] = $info; + + return $this->view(); + } + + /** + * 单个字段编辑 + * + * @return Json + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + */ + public function modify(): Json + { + Cache::delete("categoryNames");//删除缓存 + if ($this->request->isPost()) { + $item = input('post.'); + $validate = new MenuValidate(); + if (!$validate->scene('menu_modify')->check($item)) { + return $this->json(4002, $validate->getError()); + } + + if (!$info = ArchivesCategoryModel::findById($item['id'])) { + return $this->json(4001, '记录不存在'); + } + + $update = [$item['field'] => $item['value']]; + + try { + $info->save($update); + return $this->json(); + } catch (ValidateException $e) { + return $this->json(4001, $e->getError()); + } + } + return $this->json(4000, '非法请求'); + } + + /** + * 添加 + * + * @return Json|View + * @throws Exception + */ + public function add() + { + Cache::delete("categoryNames");//删除缓存 + if ($this->request->isPost()) { + $item = input('post.'); + $validate = $this->validateByApi($item, [ + 'pid|父级分类' => 'require|number', + 'model_id|所属模型' => 'require|number|gt:0', + 'title|标题' => 'require|max:100', + 'name|标识' => 'require|unique:archives_category', + 'description|描述' => 'max:255', + ], ['model_id' => '所属模型必需选择']); + + if ($validate !== true) { + return $validate; + } + try { + $item['path'] = ArchivesCategoryModel::getPath($item['pid']); + ArchivesCategoryModel::create($item); + return $this->json(); + } catch (ValidateException $e) { + return $this->json(4001, $e->getError()); + } + } + + $this->data['jsonList'] = $this->categoryJson(); + $this->data['modelList'] = $this->modelJson(); + + return $this->view(); + } + + /** + * 列表 + * + * @return Json|View + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + */ + public function index() + { + if ($this->request->isPost()) { + $menus = ArchivesCategoryModel::getList(); + $res = [ + 'code' => 0, + 'msg' => 'success', + 'count' => $menus->count(), + 'data' => $menus->toArray(), + ]; + return json($res); + } + return $this->view(); + } + + /** + * @param array $selected + * @param array $disabled + * @return false|string + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + */ + private function categoryJson(array $selected = [], array $disabled = []) + { + $categoryList[] = ['title' => '顶级分类', 'id' => 0, 'disabled' => false, 'selected' => in_array(0, $selected)]; + $menus = ArchivesCategoryModel::getList(); + $menus = $menus->toArray(); + foreach ($menus as $k => $m) { + $menus[$k]['selected'] = in_array($m['id'], $selected); + $menus[$k]['disabled'] = in_array($m['id'], $disabled); + } + $menus = CmsRepository::getInstance()->buildMenuChild(0, $menus); + $categoryList = array_merge($categoryList, CmsRepository::getInstance()->handleSelectedList($menus)); + return json_encode($categoryList, JSON_UNESCAPED_UNICODE); + } + + /** + * @param array $selected + * @param array $disabled + * @return false|string + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + */ + private function modelJson(array $selected = [], array $disabled = []) + { + $categoryList[] = ['title' => '全部', 'id' => 0, 'disabled' => false, 'selected' => in_array(0, $selected)]; + $menus = \app\model\ArchivesModel::field('id,0 as pid,title,name,sort,true as open') + ->order('sort', 'desc') + ->order('id', 'asc') + ->select();; + $menus = $menus->toArray(); + foreach ($menus as $k => $m) { + $menus[$k]['selected'] = in_array($m['id'], $selected); + $menus[$k]['disabled'] = in_array($m['id'], $disabled); + } + $menus = CmsRepository::getInstance()->buildMenuChild(0, $menus); + $categoryList = array_merge($categoryList, CmsRepository::getInstance()->handleSelectedList($menus)); + return json_encode($categoryList, JSON_UNESCAPED_UNICODE); + } +} \ No newline at end of file diff --git a/app/controller/manager/ArchivesModel.php b/app/controller/manager/ArchivesModel.php new file mode 100644 index 0000000..dd89032 --- /dev/null +++ b/app/controller/manager/ArchivesModel.php @@ -0,0 +1,173 @@ +request->isPost()) { + $ids = input('post.ids/a', []); + if (empty($ids)) { + $ids[] = input('post.id/d'); + } + $groupNames = MArchivesModel::whereIn('id', $ids)->column('name'); + if (ArchivesModelField::whereIn('name', $groupNames)->count() > 0) { + return $this->json(4002, '模型下已存在字段,无法删除!'); + } + + MArchivesModel::deleteByIds($ids); + +// Log::write(get_class().'Del', 'del', '涉及到的ID为:'.implode(',', $ids)); + return $this->json(); + } + return $this->json(4001, '非法请求!'); + } + + /** + * 编辑 + * + * @return Json|View + * @throws Exception + */ + public function edit() + { + $id = input('id/d', 0); + + if (!$info = MArchivesModel::findById($id)) { + return $this->json(4001, '记录不存在'); + } + + if ($this->request->isPost()) { + $item = input('post.'); + $validate = $this->validateByApi($item, [ + 'title|模型标题' => 'require', + 'name|模型标识' => 'alphaDash|unique:archives_model,name,'.$id, + ]); + + if ($validate !== true) { + return $validate; + } + + try { + ArchivesModelField::setFieldList($id, $info['name']); + $info->save($item); + return $this->json(); + } catch (ValidateException $e) { + return $this->json(4001, $e->getError()); + } + } + + $this->data['item'] = $info; + + return $this->view(); + } + + /** + * 单个字段编辑 + * + * @return Json + * @throws Exception + */ + public function modify(): Json + { + if ($this->request->isPost()) { + $item = input('post.'); + $validate = $this->validateByApi($item, [ + 'field' => 'require', + 'value' => 'require', + ]); + + if ($validate !== true) { + return $validate; + } + + if (!$info = MArchivesModel::findById($item['id'])) { + return $this->json(4001, '记录不存在'); + } + + $update = [$item['field'] => $item['value']]; + + try { + $info->save($update); + return $this->json(); + } catch (ValidateException $e) { + return $this->json(4001, $e->getError()); + } + } + return $this->json(4000, '非法请求'); + } + + /** + * 添加 + * + * @return Json|View + * @throws Exception + */ + public function add() + { + if ($this->request->isPost()) { + $item = input('post.'); + + $validate = $this->validateByApi($item, [ + 'title|模型标题' => 'require', + 'name|模型标识' => 'require|alphaDash|unique:archives_model', + ]); + + if ($validate !== true) { + return $validate; + } + + try { + $model = MArchivesModel::create($item); + ArchivesModelField::setFieldList($model['id'], $model['name']); + return $this->json(); + } catch (ValidateException $e) { + return $this->json(4001, $e->getError()); + } + } + + return $this->view(); + } + + /** + * 列表 + * + * @return View|Json + * @throws Exception + */ + public function index() + { + if ($this->request->isPost()) { + $page = input('page/d', 1); + $limit = input('size/d', 20); + $items = MArchivesModel::findList([], [], $page, $limit, function ($q) { + return $q->order('sort', 'desc')->order('id', 'asc'); + }); + + return $this->json(0, '操作成功', $items); + } + return $this->view(); + } +} \ No newline at end of file diff --git a/app/controller/manager/ArchivesModelField.php b/app/controller/manager/ArchivesModelField.php new file mode 100644 index 0000000..f7c849c --- /dev/null +++ b/app/controller/manager/ArchivesModelField.php @@ -0,0 +1,210 @@ +request->isPost()) { + $ids = input('post.ids/a', []); + if (empty($ids)) { + $ids[] = input('post.id/d'); + } + + MArchivesModelField::deleteByIds($ids); + +// Log::write(get_class().'Del', 'del', '涉及到的ID为:'.implode(',', $ids)); + return $this->json(); + } + return $this->json(4001, '非法请求!'); + } + + /** + * 编辑 + * + * @return Json|View + * @throws Exception + */ + public function edit() + { + $id = input('id/d', 0); + + if (!$info = MArchivesModelField::findById($id)) { + return $this->json(4001, '记录不存在'); + } + + if ($this->request->isPost()) { + $item = input('post.'); + $validate = $this->validateByApi($item, [ + 'title|模型标题' => 'require', + 'name|模型标识' => 'alphaDash', + ]); + + if ($validate !== true) { + return $validate; + } + + try { + $info->save($item); + return $this->json(); + } catch (ValidateException $e) { + return $this->json(4001, $e->getError()); + } + } + + $this->data['item'] = $info; + + return $this->view(); + } + + /** + * 单个字段编辑 + * + * @return Json + * @throws Exception + */ + public function modify(): Json + { + if ($this->request->isPost()) { + $item = input('post.'); + $validate = $this->validateByApi($item, [ + 'field' => 'require', + 'value' => 'require', + ]); + + if ($validate !== true) { + return $validate; + } + + if (!$info = MArchivesModelField::findById($item['id'])) { + return $this->json(4001, '记录不存在'); + } + + $update = [$item['field'] => $item['value']]; + + try { + $info->save($update); + return $this->json(); + } catch (ValidateException $e) { + return $this->json(4001, $e->getError()); + } + } + return $this->json(4000, '非法请求'); + } + + /** + * 添加 + * + * @return Json|View + * @throws Exception + */ + public function add() + { + $modelId = input('model_id/d', 0); + + if ($this->request->isPost()) { + $item = input('post.'); + $item['model_id'] = $modelId; + + $validate = $this->validateByApi($item, [ + 'title|字段标题' => 'require', + 'model_id|模型' => 'require|number|gt:0', + 'name|字段标识' => 'require|alphaDash|unique:archives_model_field', + ], ['model_id.gt' => '模型不存在']); + + if ($validate !== true) { + return $validate; + } + + try { + MArchivesModelField::create($item); + return $this->json(); + } catch (ValidateException $e) { + return $this->json(4001, $e->getError()); + } + } + + $this->data['modelId'] = $modelId; + + return $this->view(); + } + + /** + * 列表 + * + * @return View|Json + * @throws Exception + */ + public function index() + { + $modelId = input('model_id/d', 0); + if (!$modelId) { + return $this->json(4001, '请选择正确的模型'); + } + + if ($this->request->isPost()) { + $page = input('page/d', 1); + $limit = input('size/d', 20); + $where[] = ['model_id', '=', $modelId]; + $items = MArchivesModelField::findList($where, [], $page, $limit, function ($q) { + return $q->order('status', 'desc')->order('id', 'asc'); + }); + + return $this->json(0, '操作成功', $items); + } + + $this->data['modelId'] = $modelId; + + return $this->view(); + } + + /** + * 同步字段 + * + * @return Json + * @throws Exception + */ + public function sync(): Json + { + if ($this->request->isPost()) { + $modelId = input('model_id/d', 0); + + if (!$modelId) { + return $this->json(4001, '模型错误'); + } + + + if (!$info = ArchivesModel::findOne(['id' => $modelId])) { + return $this->json(4001, '模型不存在'); + } + + try { + MArchivesModelField::syncFieldList($modelId, $info['name']); + return $this->json(); + } catch (ValidateException $e) { + return $this->json(4001, $e->getError()); + } + } + return $this->json(4000, '非法请求'); + } +} \ No newline at end of file diff --git a/app/controller/manager/Attachment.php b/app/controller/manager/Attachment.php new file mode 100644 index 0000000..1f9a82c --- /dev/null +++ b/app/controller/manager/Attachment.php @@ -0,0 +1,488 @@ +DIRECTORY_SEPARATOR = DIRECTORY_SEPARATOR == "\\" ? "/" : DIRECTORY_SEPARATOR; + } + + /** + * 删除 + * + * @return Json + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + */ + public function del(): Json + { + if ($this->request->isPost()) { + $ids = input('post.ids/a', []); + if (empty($ids)) { + $ids[] = input('post.id/d'); + } + + $items = AttachmentModel::whereIn('id', $ids)->where('is_dir', AttachmentModel::COMMON_ON)->select(); + if ($items->where('is_dir', AttachmentModel::COMMON_ON)->count()) { + $dirPaths = []; + foreach ($items->toArray() as $item) { + $dirPaths[] = $item['path'].$item['name']. $this->DIRECTORY_SEPARATOR ; + } + if (AttachmentModel::where('path', 'in', $dirPaths)->count()) { + return $this->json(4001, '待删除目录下存在内容!'); + } + } + AttachmentModel::deleteByIds($ids); + +// Log::write(get_class().'Del', 'del', '涉及到的ID为:'.implode(',', $ids)); + return $this->json(); + } + return $this->json(4001, '非法请求!'); + } + + /** + * 单个字段编辑 + * + * @return Json + * @throws Exception + */ + public function modify(): Json + { + if ($this->request->isPost()) { + $item = input('post.'); + $validate = $this->validateByApi($item, [ + 'field' => 'require', + 'value' => 'require', + ]); + + if ($validate !== true) { + return $validate; + } + + if (!$info = AttachmentModel::findById($item['id'])) { + return $this->json(4001, '记录不存在'); + } + + if ($item['field'] == 'name' && $info['is_dir'] == AttachmentModel::COMMON_ON) { + return $this->json(4002, '目录名称不能修改'); + + } + + $update = [$item['field'] => $item['value']]; + + try { + $info->save($update); + return $this->json(); + } catch (ValidateException $e) { + return $this->json(4001, $e->getError()); + } + } + return $this->json(4000, '非法请求'); + } + + /** + * 添加 + * + * @return Json|View + * @throws Exception + */ + public function add() + { + if ($this->request->isPost()) { + $item = input('post.'); + + $validate = $this->validateByApi($item, [ + 'title|合集标题' => 'require', + 'user|虚拟用户' => 'require', + 'headimg|虚拟用户头像' => 'require', + ]); + + if ($validate !== true) { + return $validate; + } + + try { + $now = date('Y-m-d H:i:s'); + $item['created_at'] = $now; + $item['created_by'] = $this->auth['user_id']; + AttachmentModel::create($item); + return $this->json(); + } catch (ValidateException $e) { + return $this->json(4001, $e->getError()); + } + } + + return $this->view(); + } + + /** + * 添加文件夹 + * + * @return Json|View + * @throws Exception + */ + public function addFolder() + { + if ($this->request->isPost()) { + $item = input('post.'); + + $validate = $this->validateByApi($item, [ + 'name|文件夹名称' => 'require|alphaDash|max:20', + 'path|文件路径' => 'require', + ]); + + // 例 name=dir4 path=/storage/dir1/dir2/dir3 + + // 去首尾/ + $path = trim($item['path'], $this->DIRECTORY_SEPARATOR ); + // 全路径 如 /storage/dir1/dir2/dir3/dir4/ 注意前后都有/ + $fullPath = $this->DIRECTORY_SEPARATOR .$path. $this->DIRECTORY_SEPARATOR .$item['name']. $this->DIRECTORY_SEPARATOR ; + + if ($validate !== true) { + return $validate; + } + + AttachmentModel::pathDirHandle($fullPath); + return $this->json(); + } + + $path = input('path/s', AttachmentModel::ROOT_PATH); + + $this->data['path'] = $path; + return $this->view(); + } + + /** + * 图片列表 + * + * @return View|Json + * @throws Exception + */ + public function image() + { + $path = input('post.path', AttachmentModel::ROOT_PATH); + $path = $this->DIRECTORY_SEPARATOR . trim($path, $this->DIRECTORY_SEPARATOR). $this->DIRECTORY_SEPARATOR; + $path = str_replace("\\","/",$path); + $type = input('type/s', 'image'); + $selected = input('selected', false); + $multiple = input('multiple', false); + + Config::load('extra/alioss', 'alioss'); + $config = config('alioss'); + $oss = $config['customDomain']; + if ($this->request->isPost()) { + $items = $this->list($path, ['image']); + + return $this->json(0, '操作成功', $items); + } + + $this->data['path'] = $path; + $this->data['oss'] = $oss; + $this->data['type'] = $type; + $this->data['multiple'] = $multiple; + $this->data['selected'] = $selected; + return $this->view(); + } + + /** + * 视频列表 + * + * @return View|Json + * @throws Exception + */ + public function video() + { + $path = input('post.path', AttachmentModel::ROOT_PATH); + $path = $this->DIRECTORY_SEPARATOR . trim($path, $this->DIRECTORY_SEPARATOR ) . $this->DIRECTORY_SEPARATOR ; + $path = str_replace("\\","/",$path); + $type = input('type/s', 'video'); + $selected = input('selected', false); + $multiple = input('multiple', false); + + Config::load('extra/alioss', 'alioss'); + $config = config('alioss'); + $oss = $config['customDomain']; + if ($this->request->isPost()) { + $items = $this->list($path, ['video']); + + return $this->json(0, '操作成功', $items); + } + + $this->data['path'] = $path; + $this->data['oss'] = $oss; + $this->data['type'] = $type; + $this->data['multiple'] = $multiple; + $this->data['selected'] = $selected; + return $this->view(); + } + + /** + * 文件列表 + * + * @return View|Json + * @throws Exception + */ + public function file() + { + Config::load('extra/alioss', 'alioss'); + $config = config('alioss'); + $oss = $config['customDomain']; + $type = input('type/s', 'all'); + $selected = input('selected', false); + $multiple = input('multiple', false); + + if ($this->request->isPost()) { + $page = input('post.page', 1); + $size = input('post.size', 20); + $searchParams = input('searchParams'); + $where = []; + + if ($searchParams) { + foreach ($searchParams as $key => $param) { + if (!empty($param)) { + if (is_string($param)) { + if ($key == 'is_oss') { + if ($param >= 0) { + $where[] = ['is_oss', '=', $param]; + } + } else { + $where[] = [$key, 'like', '%'.$param.'%']; + } + } elseif (is_array($param)) { + //数组空元素去除 + foreach ($param as $k => $val) { + if (empty($val)) { + unset($param[$k]); + } + } + if (!empty($param)) { + $where[] = [$key, 'in', $param]; + } + } + } + } + } + + + if ($type !== 'all') { + $where[] = ['type', '=', $type]; + } + + $items = AttachmentModel::findList($where, [], $page, $size, function ($q) { + return $q->where('type', '<>', 'dir')->order('updated_at', 'desc'); + }); + + $items['list']->each(function ($item) { + $item->size_text = getFilesize($item['size'] ?? 0); + }); + + return $this->json(0, '操作成功', $items); + } + + $this->data['oss'] = $oss; + $this->data['type'] = $type; + $this->data['multiple'] = $multiple; + $this->data['selected'] = $selected; + return $this->view(); + } + + /** + * 一键删除失效记录 即oss不存在&&本地不存在 + * + * @return Json + */ + public function delLostFile(): Json + { + if ($this->request->isPost()) { + $total = AttachmentModel::where('type', '<>', AttachmentModel::TYPE_DIR) + ->where('is_dir', AttachmentModel::COMMON_OFF) + ->where('is_oss', AttachmentModel::COMMON_OFF) + ->where('has_local', AttachmentModel::COMMON_OFF) + ->count(); + if ($total === 0) { + return $this->json(0, 'success', ['total' => $total]); + } + + if (AttachmentModel::where('type', '<>', AttachmentModel::TYPE_DIR) + ->where('is_dir', AttachmentModel::COMMON_OFF) + ->where('is_oss', AttachmentModel::COMMON_OFF) + ->where('has_local', AttachmentModel::COMMON_OFF) + ->delete()) { + return $this->json(0, 'success', ['total' => $total]); + } + return $this->json(4004, '删除失败'); + } + return $this->json(4000, '请求错误'); + } + + /** + * 一键上传本地文件到OSS + * + * @return Json + */ + public function toOss(): Json + { + if ($this->request->isPost()) { + Config::load('extra/alioss', 'alioss'); + $config = config('alioss'); + + $ossObject = AliOss::instance(); + $bucket = $config['bucket']; + + $total = AttachmentModel::where('type', '<>', AttachmentModel::TYPE_DIR) + ->where('is_dir', AttachmentModel::COMMON_OFF) + ->where('is_oss', AttachmentModel::COMMON_OFF) + ->field('id') + ->count(); + $done = 0; + $none = 0; + if ($total === 0) { + return $this->json(0, 'success', ['total' => $total, 'done' => $done, 'none' => $none]); + } + try { + AttachmentModel::where('type', '<>', AttachmentModel::TYPE_DIR) + ->where('is_dir', AttachmentModel::COMMON_OFF) + ->where('is_oss', AttachmentModel::COMMON_OFF) + ->field('id,src') + ->chunk(3, function ($items) use ($ossObject, $bucket, &$done, &$none) { + $doneIds = []; + $noneIds = []; + foreach ($items as $item) { + if ($item['src']) { + $realPath = public_path().ltrim($item['src'], $this->DIRECTORY_SEPARATOR ); + if (!file_exists($realPath)) { + $none++; + $noneIds[] = $item['id']; + continue; + } + $pathInfo = pathinfo($item['src']); + $object = ltrim($item['src'], $this->DIRECTORY_SEPARATOR ); + //是否存在 + if (!$ossObject->doesObjectExist($bucket, $object)) { + //创建目录 + $ossObject->createObjectDir($bucket, ltrim($pathInfo['dirname'], $this->DIRECTORY_SEPARATOR )); + + $ossObject->uploadFile($bucket, $object, $realPath); + } + $doneIds[] = $item['id']; + $done++; + } + } + + // 失效标记 + if ($noneIds) { + $update = ['is_oss' => AttachmentModel::COMMON_OFF, 'has_local' => AttachmentModel::COMMON_OFF]; + (new AttachmentModel())->where('id', 'in', $noneIds)->update($update); + } + + // 完成标记 + if ($doneIds) { + $update = ['is_oss' => AttachmentModel::COMMON_ON]; + (new AttachmentModel())->where('id', 'in', $doneIds)->update($update); + } + }); + + return $this->json(0, 'success', ['total' => $total, 'done' => $done, 'none' => $none]); + } catch (Exception $e) { + \think\facade\Log::error('本地文件一键上传OSS失败 '.$e->getMessage()); + return $this->json(0, 'success', ['total' => $total, 'done' => $done, 'none' => $none]); + } + } + } + + /** + * 指定类型附件列表 + * + * @param array $type + * @param string $path + * @return array + * @throws Exception + */ + protected function list(string $path, array $type): array + { + $type[] = 'dir'; + $where[] = ['path', '=', $path]; + $where[] = ['type', 'in', $type]; + $items = AttachmentModel::findList($where, [], 1, 0, function ($q) { + return $q->order('is_dir', 'desc')->order('updated_at', 'desc'); + }); + $items['list']->each(function ($item) { + $item->size_text = getFilesize($item['size'] ?? 0); + }); + $items['path'] = $path; + + return $items; + } + + /** + * 获取文件大小 + * + * @return Json + */ + public function getSize(): Json + { + $path = input('post.path', ''); + $types = input('post.type/a', []); + + $size = ''; + if (empty($path)) { + return $this->json(0, '操作成功', $size); + } + $path = str_replace("\\","/",$path); + $total = AttachmentModel::where('path', 'like', $path.'%') + ->when(!empty($types), function ($q) use ($types) { + $q->where('type', 'in', $types); + }) + ->sum('size'); + + return $this->json(0, '操作成功', getFilesize($total)); + } + + // 将没有md5的文件 更新md5 仅针对本地文件 + public function md5List() + { + $noMd5List = AttachmentModel::whereNull('md5')->select(); + $update = []; + foreach ($noMd5List as $item) { + try { + if (!empty($item['src'])) { + $arr = []; + $path = public_path().ltrim($item['src'], $this->DIRECTORY_SEPARATOR ); + $file = new \think\File($path); + $arr['md5'] = $file->md5(); + $arr['id'] = $item['id']; + + $update[] = $arr; + } + } catch (Exception $e) { + continue; + } + + } + (new AttachmentModel())->saveAll($update); + } +} \ No newline at end of file diff --git a/app/controller/manager/Backup.php b/app/controller/manager/Backup.php new file mode 100644 index 0000000..cdb0700 --- /dev/null +++ b/app/controller/manager/Backup.php @@ -0,0 +1,153 @@ + 0) { + $dataStr = 'INSERT INTO `'.$tableName.'` VALUE '; + foreach ($data as $k => $item) { + $valStr = '('; + foreach ($item as $val) { + // 字符串转义 + $val = addslashes($val); + $valStr .= '"'.$val.'", '; + } + // 去除最后的逗号和空格 + $valStr = substr($valStr,0,strlen($valStr)-2); + // 限制单条sql语句在16K以内(可根据mysql配置条件进行调整) + $sqlLength = strlen($dataStr) + strlen($valStr); + if($sqlLength >= (1024 * 16 - 10)) { + $dataStr .= $valStr.');'.$eol; + file_put_contents($fileName, $dataStr, FILE_APPEND); + // 提前分段写入后需重置数据语句 + if ($k <= ($count-1)) { + $dataStr = 'INSERT INTO `'.$tableName.'` VALUE '; + } else { + $dataStr = ''; + } + } else { + if ($k < ($count-1)) { + $dataStr .= $valStr.'),'.$eol; + } else { + $dataStr .= $valStr.');'.$eolB; + } + } + } + file_put_contents($fileName, $dataStr, FILE_APPEND); + } + } + clearstatcache(); + + $backups = explode('/', $fileName); + $backupName = $backups[count($backups)-1]; + $src = Config::get('filesystem.disks.backup.url') . '/'; + return $this->json(0, '备份成功:' . $src . $backupName); + } + + + public function index() + { + $path = Config::get('filesystem.disks.backup.root') . '/'; + $src = Config::get('filesystem.disks.backup.url') . '/'; + $items = []; + if(is_dir($path)) { + if(!is_readable($path)) { + chmod($path,0755); + } + $files = scandir($path); + foreach ($files as $file) { + if($file != '.' && $file != '..') { + $ext = substr($file, -4); + if ($ext == '.sql') { + $creatTime = substr($file, -18, 14); + $items[] = [ + 'file' => $file, + 'path' => $src . $file, + 'time' =>is_numeric($creatTime) ? date('Y-m-d H:i:s', strtotime($creatTime)) : '', + ]; + } + } + } + } + clearstatcache(); + + $this->data['items'] = $items; + $this->data['backupPath'] = str_replace('\\','/', $path); + return $this->view(); + } + + public function del() + { + if (request()->isPost()) { + $filePath = input('post.id'); + Tool::delFile($filePath); + MLog::write('backup', 'del', '删除了备份数据文件,文件路径为:' . $filePath); + return $this->json(); + } else { + return $this->json(1, '非法请求'); + } + } +} \ No newline at end of file diff --git a/app/controller/manager/Base.php b/app/controller/manager/Base.php new file mode 100644 index 0000000..a9308e4 --- /dev/null +++ b/app/controller/manager/Base.php @@ -0,0 +1,105 @@ +middleware = [ + 'auth' => ['except' => array_merge($this->noNeedLogin, $this->noNeedRight)], + 'log' + // 'jwt' => ['except' => $this->noNeedRight], + ]; + $this->auth = session('auth'); + $this->data['member'] = $this->auth; + $this->data['_token'] = $this->auth['token'] ?? ''; + $this->data['groupId'] = $this->auth['groupId'] ?? 0; + + $this->fileDomain(); + } + + //变量赋值到模板 + protected function view(string $template = '') + { + return view($template)->assign($this->data); + } + + /** + * @param string $msg + * @param string|null $url + * @param string $data + * @param int $wait + * @return Redirect + */ + protected function error($msg = '', string $url = null, $data = '', int $wait = 3): Redirect + { + if (is_null($url)) { + $url = $this->request->isAjax() ? '' : 'javascript:history.back(-1);'; + } elseif ($url) { + $url = (strpos($url, '://') || 0 === strpos($url, '/')) ? $url : $this->app->route->buildUrl($url); + } + $result = [ + 'code' => 0, + 'msg' => $msg, + 'data' => $data, + 'url' => $url, + 'wait' => $wait, + ]; + + return $this->redirect(url('/manager/error/jump', $result)); + } + + public function __call($name, $args) + { + return $this->view('/manager/error/jump'); + } + + /** + * 验证器 + * + * @param array $data + * @param $validate + * @param array $message + * @param bool $batch + * @return Redirect + * @throws Exception + */ + protected function validateError(array $data, $validate, array $message = [], bool $batch = false): Redirect + { + try { + parent::validate($data, $validate, $message, $batch); + } catch (ValidateException $e) { + $msg = $e->getMessage(); + if ($batch) { + $msg = implode(',', $e->getError()); + } + + return $this->error($msg); + } catch (Exception $e) { + throw $e; + } + } + + /** + * 文件域名前缀 + */ + public function fileDomain() + { + $this->data['fileDomain'] = FileTool::getFileDomain(); + } +} \ No newline at end of file diff --git a/app/controller/manager/Block.php b/app/controller/manager/Block.php new file mode 100644 index 0000000..7dfc544 --- /dev/null +++ b/app/controller/manager/Block.php @@ -0,0 +1,224 @@ +,开发者QQ群:50304283 +// +---------------------------------------------------------------------- + +namespace app\controller\manager; +use app\model\ArchivesCategory as ArticleCategoryModel; +use app\model\ArchivesModelField; +use app\model\Block as BlockModel; +use app\model\System; +use app\validate\Block as VBlock; +use app\repository\CmsRepository; +use think\db\exception\DataNotFoundException; +use think\db\exception\DbException; +use think\db\exception\ModelNotFoundException; +use think\exception\ValidateException; + + +/** + * 碎片控制器 + * @package app\controller\cms + */ +class Block extends Base +{ + + protected function initialize() + { + parent::initialize(); + $action = $this->request->action(); + $cid = $this->request->param('cid/d'); + if (($action == 'add' || $action == 'edit') && !$this->request->isPost()) { + + $showFieldList = ArchivesModelField::showFieldList();//所有栏目 可展示字段列表 + $currentShowFields = $showFieldList[$cid] ?? [];//当前选中栏目 可展示字段列表 + + $this->data['system'] = System::getSystem(); + + $this->data['currentList'] = $currentShowFields; + } + $this->data['jsonList'] = $this->xmSelectJson([$cid]); + + } + + public function index() + { + if ($this->request->isAjax()) { + $page = input('page/d', 1); + $limit = input('size/d', 20); + $keyword = input('searchParams.keyword/s'); + $categoryId = input('searchParams.category_id/d', 0); + $where = []; + + if ($categoryId > 0) { + $where[] = ['category_id', '=', $categoryId]; + } + + if (!empty($keyword)) { + $where[] = ["name|title", 'like', '%'.$keyword.'%']; + } + + $items = BlockModel::findList($where, [], $page, $limit, function ($q) { + return $q->with(["category"])->withAttr("content",function ($value,$data){ + switch ($data["type"]){ + case BlockModel::BLOCK:// + return $value; + break; + case BlockModel::TEXT: + return $value; + break; + case BlockModel::IMG: + return ""; + break; + case BlockModel::GROUP: + $data = explode(",",$value); + $str = ""; + foreach ($data as $vdata){ + $str.=""; + } + return $str; + case BlockModel::FILE: + return $value; + break; + case BlockModel::VIDEO: + return $value; + break; + case BlockModel::ING_LIST: + return $value; + break; + } + }); + },["id"=>"desc"]); + + return $this->json(0, '操作成功', $items); + + } + + return $this->view(); + } + + public function add(){ + if ($this->request->isPost()) { + $postData = $this->request->post(); + try { + $this->validate($postData, VBlock::class); + $postData["content"] = $postData["content".$postData["type"]]; + + $hasWhere =[["name","=",$postData["name"]]] ; + if( $postData["category_id"] > 0 ){ + $hasWhere[] = ["category_id","=",$postData["category_id"]]; + + } + $other = BlockModel::findOne($hasWhere); + if(!empty($other)){ + throw new ValidateException("键值重复"); + } + }catch (ValidateException $e){ + return $this->json(4001,$e->getError()); + } + + + try { + + BlockModel::create($postData); + return $this->json(); + }catch (\Exception $e){ + return $this->json(0,'保存失败'. $e->getMessage()); + } + } + $this->data["types"] = BlockModel::getTypes(); + return $this->view(); + } + + public function edit(){ + $id = input("id/d"); + $item = BlockModel::findById($id); + if ($this->request->isPost()) { + $postData = $this->request->post(); + try { + $this->validate($postData, VBlock::class); + $postData["content"] = $postData["content".$postData["type"]]; + $hasWhere =[["name","=",$postData["name"]],["id","<>",$id]] ; + if( $postData["category_id"] > 0 ){ + $hasWhere[] = ["category_id","=",$postData["category_id"]]; + }else{ + $hasWhere[] = ["category_id","=",0]; + } + $other = BlockModel::findOne($hasWhere); + if(!empty($other)){ + throw new ValidateException("键值重复"); + } + }catch (ValidateException $e){ + return $this->json(4001,$e->getError()); + } + try { + $item->save($postData); + return $this->json(); + }catch (\Exception $e){ + return $this->json(0,'保存失败'. $e->getMessage()); + } + } + + if(empty($item)){ + return $this->error("碎片不存在"); + } + $item =$item->toArray(); + if($item['type'] == BlockModel::ING_LIST){ + $contentKey = array_keys($item['content']); + $this->data['maxKey'] = max($contentKey)+1; + }else{ + $this->data['maxKey'] = 0; + } + $this->data["item"] = $item; + + $this->data["types"] = BlockModel::getTypes(); + return $this->view(); + } + + + /** + * 删除 + * + * @return Json + */ + public function del() + { + if ($this->request->isPost()) { + $id = input('id/d', 0); + BlockModel::deleteById($id); + return $this->json(); + } + return $this->json(4001, '非法请求!'); + } + /** + * 内容分类 构造xmSelect json数据[xmSelect用] + * + * @param array $selected + * @param array $disabled + * @return false|string + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + */ + private function xmSelectJson(array $selected = [], array $disabled = []) + { + $category = ArticleCategoryModel::order('sort', 'desc') + ->field('id,pid,title') + ->select() + ->toArray(); + foreach ($category as $k => $m) { + $category[$k]['selected'] = in_array($m['id'], $selected); + $category[$k]['disabled'] = in_array($m['id'], $disabled); + } + + $category = CmsRepository::getInstance()->buildMenuChild(0, $category); + $category = CmsRepository::getInstance()->handleSelectedList($category); + return json_encode($category, JSON_UNESCAPED_UNICODE); + } +} \ No newline at end of file diff --git a/app/controller/manager/Config.php b/app/controller/manager/Config.php new file mode 100644 index 0000000..471691c --- /dev/null +++ b/app/controller/manager/Config.php @@ -0,0 +1,85 @@ +extraPath = config_path() . 'extra/'; + if (!is_dir($this->extraPath)) { + if (is_writable(config_path())) { + mkdir($this->extraPath, 0777, true); + } else { + halt('请联系系统管理人员配置文件夹读写权限!请添加'.$this->extraPath.'文件夹的读写权限'); + } + } elseif (!is_writable($this->extraPath)) { + halt('请联系系统管理人员配置文件夹读写权限!请添加'.$this->extraPath.'文件夹的读写权限'); + } + } + + + + public function wechat() + { + if ($this->request->isPost()) { + $data = input("post."); + unset($data['_token']); + $php = var_export($data, true); + file_put_contents($this->extraPath . 'wechat.php', 'json(); + } else { + CConfig::load('extra/wechat', 'wechat'); + $this->data['item'] = config('wechat'); + return $this->view(); + } + } + + public function alipay() + { + if ($this->request->isPost()) { + $data = input("post."); + unset($data['_token']); + $php = var_export($data, true); + file_put_contents($this->extraPath . 'alipay.php', 'json(); + } else { + CConfig::load('extra/alipay', 'alipay'); + $this->data['item'] = config('alipay'); + return $this->view(); + } + } + + + public function __call($name, $args) + { + if ($this->request->isPost()) { + try { + $data = input("post."); + $php = var_export($data, true); + file_put_contents(config_path().'extra/'.$name.'.php', 'json(); + } catch (Exception $e) { + return $this->json(4001, $e->getMessage()); + } + } else { + CConfig::load('extra/'.$name, $name); + $this->data['item'] = config($name); + $this->data['action'] = $name; + return $this->view('manager/config/'.$name); + } + } +} \ No newline at end of file diff --git a/app/controller/manager/Error.php b/app/controller/manager/Error.php new file mode 100644 index 0000000..a10fc30 --- /dev/null +++ b/app/controller/manager/Error.php @@ -0,0 +1,11 @@ +param(); + return view()->assign($param); + } +} diff --git a/app/controller/manager/Feedback.php b/app/controller/manager/Feedback.php new file mode 100644 index 0000000..6bf1a96 --- /dev/null +++ b/app/controller/manager/Feedback.php @@ -0,0 +1,82 @@ +request->isPost()) { + $ids = input('post.ids/a', []); + if (empty($ids)) { + $ids[] = input('post.id/d'); + } + FeedbackModel::deleteByIds($ids); + Log::write(get_class().'Del', 'del', '涉及到的ID为:'.implode(',', $ids)); + return $this->json(); + } + return $this->json(4001, '非法请求!'); + } + + /** + * 列表 + * + * @return View|Json + * @throws Exception + */ + public function index() + { + if ($this->request->isPost()) { + $page = input('page/d', 1); + $limit = input('size/d', 20); + $searchParams = input('searchParams', []); + $search = []; + if ($searchParams) { + $searchParams = array_map('trim', $searchParams); + if (isset($searchParams['user_keyword']) && !empty($searchParams['user_keyword'])) { + $search[] = ['user_name|user_tel|user_email', 'like', '%'.$searchParams['user_keyword'].'%']; + } + unset($searchParams['user_keyword']); + + foreach ($searchParams as $key => $param) { + if ($param) { + $search[] = [$key, 'like', '%'.$param.'%']; + } + } + } + + $items = FeedbackModel::findList($search, [], $page, $limit, function ($q) { + return $q->with(['account'])->order('id', 'desc'); + }); + + $items['list'] = $items['list']->each(function ($item) { + if ($item->account) { + $item->user_name = empty($item->user_name) ? ($item->account->nickname ?? '') : $item->user_name; + $item->user_tel = empty($item->user_tel) ? ($item->account->mobile ?? '') : $item->user_tel; + } + }); + + return $this->json(0, '操作成功', $items); + } + + + return $this->view(); + } +} \ No newline at end of file diff --git a/app/controller/manager/File.php b/app/controller/manager/File.php new file mode 100644 index 0000000..73de0b8 --- /dev/null +++ b/app/controller/manager/File.php @@ -0,0 +1,143 @@ +request->isPost()) { + $paths = input('post.paths/a'); + if(!empty($paths)){ + foreach($paths as $path){ + Tool::delFile($path); + } + Log::write('file', 'delPath', '批量删除了磁盘文件,涉及到的文件路径为:' . implode(',', $paths)); + return $this->json(); + } + return $this->json(2, '待删除文件列表为空'); + } + return $this->json(1, '非法请求!'); + } + //删除文件记录 + public function del() + { + if ($this->request->isPost()) { + $ids = input('post.ids/a'); + if(empty($ids) || !is_array($ids)) { + return $this->json(2, '参数错误,请核对之后再操作!'); + } + $items = MFile::getListByIds($ids); + if(!empty($items)){ + $delIds = []; + foreach($items as $item){ + $delIds[] = $item['id']; + if($item['type'] == MFile::IMG){ + Tool::delFile($item['src'], 1); + }else{ + Tool::delFile($item['src']); + } + } + MFile::destroy($delIds); + Log::write('file', 'del', '批量删除了文件,涉及到的文件ID为:' . implode(',', $delIds)); + return $this->json(); + }else{ + return $this->json(3, '待删除文件列表为空'); + } + } + return $this->json(1, '非法请求!'); + } + /** + * 未使用文件列表, + * 1. 遍历数据库中使用的图片视频及文件路径 + * 2. 遍历上传目录中的文件 + * 3. 数据对比,找出存在目录中的文件&不在数据库中的文件 + * 4. 页面上显示查找出来的文件 + */ + public function unuse() + { + $filesInUse = $this->getAllFilesInUse(); //数据库中在使用的文件 + $rootPath = app()->getRootPath(); + $uploadPath = $rootPath . 'storage'; + $uploadedFiles = getAllFilesByPath($uploadPath, $rootPath); //磁盘上上传的文件 + $files = MFile::getAll(); + $dbUploadedFiles = []; //数据库中上传的文件 + foreach($files as $file){ + $src = trim($file['src']); + if(!empty($src)){ + $key = getKeyByPath($src); + $dbUploadedFiles[$key] = $file; + } + } + + $uploadedNotInUseFiles = array_diff_key($uploadedFiles, $filesInUse); //磁盘上上传未使用的文件 + $dbUploadedNotInUseFiles = array_diff_key($dbUploadedFiles, $filesInUse); //数据库中上传未使用的文件 + $bothNotInUseFiles = array_intersect_key($uploadedNotInUseFiles, $dbUploadedNotInUseFiles); //磁盘和数据库中,两边都未使用 + $this->data['uploadedNotInUseFiles'] = $uploadedNotInUseFiles; + $this->data['dbUploadedNotInUseFiles'] = $dbUploadedNotInUseFiles; + $this->data['bothNotInUseFilesKey'] = array_keys($bothNotInUseFiles); + return $this->view(); + } + + //获取所有在使用的文件 + private function getAllFilesInUse() + { + $files = []; + $blockFiles = Block::getFilesInUse(); + if(!empty($blockFiles)){ + $files = array_merge($files, $blockFiles); + } + $slideFiles = Slide::getFilesInUse(); + if(!empty($slideFiles)){ + $files = array_merge($files, $slideFiles); + } + $linkFiles = Link::getFilesInUse(); + if(!empty($linkFiles)){ + $files = array_merge($files, $linkFiles); + } + $categoryFiles = Category::getFilesInUse(); + if(!empty($categoryFiles)){ + $files = array_merge($files, $categoryFiles); + } + $articleFiles = Archives::getFilesInUse(); + if(!empty($articleFiles)){ + $files = array_merge($files, $articleFiles); + } + return $files; + } + + + //ajax获取文件列表 + public function list() + { + if($this->request->isAjax()){ + $page = input('param.page/d', 1); + $size = input('param.size/d', 20); + if(!is_integer($page) || $page < 1){ + $page = 1; + } + if (!is_integer($size) || $size < 1) { + $size = 20; + } + $type = input('param.type', ''); + if(!in_array($type, array_keys(MFile::getTypes()))){ + $type = ''; + } + $items = MFile::getList($type, $page, $size); + return $this->json(0, 'ok', $items); + } + return $this->json(1, '无此操作'); + } + //列表 + public function index() + { + $items = MFile::getListPage(); + $this->data['items'] = $items; + $this->data['types'] = MFile::getTypes(); + return $this->view(); + } +} diff --git a/app/controller/manager/Index.php b/app/controller/manager/Index.php new file mode 100644 index 0000000..8366805 --- /dev/null +++ b/app/controller/manager/Index.php @@ -0,0 +1,83 @@ +data['user'] = Member::findById($auth['user_id'] ?? 0, ['id', 'username', 'nickname', 'mobile']); + return $this->view(); + } + + /** + * 控制台 + * + * @return View + * @throws Exception + */ + public function dashboard(): View + { + return $this->view(); + } + + + /** + * 菜单初始化 + * + * @return Json + */ + public function init(): Json + { + $res = []; + $res['homeInfo'] = ['title' => '控制台', 'href' => "manager/index/dashboard"]; + $res['logoInfo'] = ['title' => 'CMS管理后台', 'href' => "", 'image' => '/static/manager/image/logo1.png']; + + $menus = CmsRepository::getInstance()->getMenuList(Menu::TYPE_MENU, Menu::SHOW_YES)->toArray(); + $userId = $this->auth['user_id'] ?? 0; + $menus = CmsRepository::getInstance()->handMenuRule($userId, $menus); + $menus = CmsRepository::getInstance()->buildMenuChild(0, $menus, 'child'); + $res['menuInfo'] = $menus; + + return json($res); + } + + /** + * 缓存清理 + * + * @return Json + */ + public function clear(): Json + { + $res = ['code' => 1, 'msg' => '服务端清理缓存成功']; + sleep(2); + + return json($res); + } +} diff --git a/app/controller/manager/Link.php b/app/controller/manager/Link.php new file mode 100644 index 0000000..101502f --- /dev/null +++ b/app/controller/manager/Link.php @@ -0,0 +1,134 @@ +request->isPost()) { + $ids = input('post.ids/a'); + if(empty($ids) || !is_array($ids)) { + return $this->json(2, '参数错误,请核对之后再操作!'); + } + $items = MLink::getListByIds($ids); + if(!empty($items)){ + $delIds = []; + foreach($items as $item){ + $delIds[] = $item['id']; + } + MLink::destroy($delIds); + Log::write('link', 'del', '删除了友情链接,涉及到的ID为:' . implode(',', $delIds)); + return $this->json(); + }else{ + return $this->json(3, '待删除友情链接为空'); + } + } + return $this->json(1, '非法请求!'); + } + + //排序 + public function sort() + { + Cache::delete('links');//删除缓存 + if($this->request->isPost()){ + $id = input('post.id/d'); + $sort = input('post.sort/d'); + + $item = MLink::findById($id); + if(empty($item)){ + return $this->json(3, '该友情链接信息不存在!'); + } + $item->sort = $sort; + $item->save(); + return $this->json(); + + } + return $this->json(1, '非法请求!'); + } + + //编辑 + public function edit() + { + Cache::delete('links');//删除缓存 + if($this->request->isPost()){ + $item = input('post.item/a'); + $id = input('post.id/d'); + $img = input('post.img'); + if(is_numeric($id) && $id > 0) { + $link = MLink::findById($id); + if(empty($link)) { + return $this->json(2, '该友情链接信息不存在!'); + } + if(!empty($img)){ + $item['src'] = $img; + } + try { + validate(VLink::class)->check($item); + $link->save( $item); + Log::write('link', 'edit', "友情链接编辑,ID:{$id} ,标题:{$item['title']}"); + return $this->json(); + } catch (ValidateException $e) { + return $this->json(3, $e->getError()); + } + } + return $this->json(1, '参数错误,请核对之后再操作!'); + } else { + $id = input('param.id/d'); + $item = MLink::getById($id); + $imgSize = System::getLinkImageSize(); + + $this->data['item'] = $item; + $this->data['img_size'] = $imgSize; + return $this->view(); + } + } + + //添加 + public function add() + { + Cache::delete('links');//删除缓存 + if($this->request->isPost()){ + $item = input('post.item/a'); + $img = input('post.img'); + + if(!empty($img)){ + $item['src'] = $img; + } + try { + validate(VLink::class)->check($item); + $link = MLink::create($item); + Log::write('link', 'add', "友情链接新增,ID:{$link->id} ,标题:{$item['title']}"); + return $this->json(); + } catch (ValidateException $e) { + return $this->json(2, $e->getError()); + } + } else { + $imgSize = System::getLinkImageSize(); + $this->data['img_size'] = $imgSize; + return $this->view(); + } + } + + public function index() + { + if ($this->request->isPost()) { + $page = $this->request->param('page/d', 1); + $size = $this->request->param('size/d', 30); + + $whereMap = []; + $orders = ['sort'=>'asc']; + + $list = MLink::findList($whereMap, [], $page, $size, null, $orders); + + return $this->json(0, 'success', $list); + } + return $this->view(); + } +} diff --git a/app/controller/manager/Log.php b/app/controller/manager/Log.php new file mode 100644 index 0000000..ed794c5 --- /dev/null +++ b/app/controller/manager/Log.php @@ -0,0 +1,56 @@ +request->isPost()) { + $page = input('page/d', 1); + $limit = input('size/d', 20); + $searchParams = input('searchParams'); + $search = []; + if ($searchParams) { + foreach ($searchParams as $key => $param) { + if ($param) { + if ($key == 'begin_time') { + $begin = strtotime($param.' 00:00:00'); + $search[] = ['create_time', '>', $begin]; + } elseif ($key == 'end_time') { + $end = strtotime($param.' 23:59:59'); + $search[] = ['create_time', '<', $end]; + } else { + $search[] = [$key, 'like', '%'.$param.'%']; + } + } + } + } + + $items = LogModel::findList($search, [], $page, $limit, function ($q) { + return $q->with(['memberName'])->order('create_time', 'desc'); + }); + + return $this->json(0, '操作成功', $items); + } + + return $this->view(); + } +} \ No newline at end of file diff --git a/app/controller/manager/Login.php b/app/controller/manager/Login.php new file mode 100644 index 0000000..7c54522 --- /dev/null +++ b/app/controller/manager/Login.php @@ -0,0 +1,76 @@ +isPost()) { + $param = input('post.data'); + $username = trim($param['username']); + $password = trim($param['password']); + $captcha = trim($param['captcha'] ?? ''); + if (!captcha_check($captcha)) { + return $this->json(4001, '验证码错误'.$captcha); + } + + if (empty($username) || empty($password)) { + return $this->json(4001, '用户名和密码不能为空'); + } + $member = Member::getByUserName($username); + if (empty($member)) { + return $this->json(4002, '用户名或密码错误'); + } + if ($member['password'] != md5($password.$username)) { + return $this->json(4003, '用户名或密码错误'); + } + if ($member['status'] != Member::STATUS_NORMAL) { + return $this->json(4004, '账号已被禁用'); + } + + $userInfo = [ + 'user_id' => $member['id'], + 'username' => $member['username'], + 'nickname' => $member['nickname'], + 'account_id' => $member['account_id'],//绑定的前台用户ID + ]; + + $jwtToken = Jwt::generate($userInfo, env('app.expire', 7200)); + + $userInfo['token'] = $jwtToken;//jwt生成token + + //记录最后登陆时间 + $ip = request()->ip(); + $time = time(); + Member::updateById($member['id'], [ + 'login_time' => $time, + 'login_ip' => $ip + ]); + LoginLog::create([ + 'member_id' => $member['id'], + 'name' => $member['username'], + 'ip' => $ip, + 'create_time' => $time + ]); + session('auth', $userInfo); + return $this->json(0, 'success', ['url' => '/manager']); + } + + $viewData = []; + return view()->assign($viewData); + } +} diff --git a/app/controller/manager/Logout.php b/app/controller/manager/Logout.php new file mode 100644 index 0000000..dd4f8ea --- /dev/null +++ b/app/controller/manager/Logout.php @@ -0,0 +1,21 @@ +request->isPost()) { + $ids = input('post.ids/a', []); + if (empty($ids)) { + $ids[] = input('post.id/d'); + } + MemberModel::deleteByIds($ids); + foreach ($ids as $id) { + Enforcer::deleteRolesForUser($id); + } + Log::write(get_class().'Del', 'del', '涉及到的ID为:'.implode(',', $ids)); + return $this->json(); + } + return $this->json(4001, '非法请求!'); + } + + /** + * 个人详情 + * + * @return Json|View|Redirect + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + * @throws Exception + */ + public function profile() + { + $id = $this->auth['user_id'] ?? 0; + + if (!$item = MemberModel::findById($id)) { + if ($this->request->isAjax()) { + return $this->json(4001, '记录不存在'); + } + return $this->error('记录不存在'); + } + + if ($this->request->isPost()) { + $post = input('post.'); + + $validate = $this->validateByApi($post, [ + 'mobile|手机号' => 'require|unique:member,mobile,'.$id, + 'nickname|昵称' => 'require|chsAlphaNum|min:2|max:10', + 'remark|备注信息' => 'max:255', + ]); + + if ($validate !== true) { + return $validate; + } + + if (!checkMobile($post['mobile'])) { + return $this->json(4002, '请输入正确的手机号码'); + } + + try { + $item->save($post); + return $this->json(); + } catch (ValidateException $e) { + return $this->json(4001, $e->getError()); + } + } + + $this->data['item'] = $item; + + return $this->view(); + } + + /** + * 编辑 + * + * @return Json|View + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + * @throws Exception + */ + public function edit() + { + $id = input('id/d', 0); + + if (!$info = MemberModel::findById($id)) { + return $this->json(4001, '记录不存在'); + } + + if ($this->request->isPost()) { + $item = input('post.'); + + $validate = $this->validateByApi($item, [ + 'mobile|手机号' => 'require|unique:member,mobile,'.$id, + 'nickname|昵称' => 'require|chsAlphaNum|min:2|max:10', + 'remark|备注信息' => 'max:255', + ]); + + if ($validate !== true) { + return $validate; + } + + if (!checkMobile($item['mobile'])) { + return $this->json(4002, '请输入正确的手机号码'); + } + + $roles = []; + if ($item['roles']) { + $roles = $item['roles']; + $item['roles'] = implode(',', $item['roles']); + } + + Db::startTrans(); + try { + $info->save($item); + //删除所有角色 + Enforcer::deleteRolesForUser($id); + //新增角色 + foreach ($roles as $role) { + Enforcer::addRoleForUser($id, $role); + } + Db::commit(); + return $this->json(); + } catch (ValidateException $e) { + Db::rollback(); + return $this->json(4001, $e->getError()); + } + } + + $this->data['item'] = $info; + $this->data['roleJson'] = $this->roleJson(explode(',', $info['roles'])); + + return $this->view(); + } + + /** + * 单个字段编辑 + * + * @return Json + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + * @throws Exception + */ + public function modify(): Json + { + if ($this->request->isPost()) { + $item = input('post.'); + $validate = $this->validateByApi($item, [ + 'field' => 'require', + 'value' => 'require', + ]); + + if ($validate !== true) { + return $validate; + } + + if (!$info = MemberModel::findById($item['id'])) { + return $this->json(4001, '记录不存在'); + } + + $update = [$item['field'] => $item['value']]; + + try { + $info->save($update); + return $this->json(); + } catch (ValidateException $e) { + return $this->json(4001, $e->getError()); + } + } + return $this->json(4000, '非法请求'); + } + + /** + * 添加 + * + * @return Json|View + * @throws Exception + */ + public function add() + { + if ($this->request->isPost()) { + $item = input('post.'); + + $validate = $this->validateByApi($item, [ + 'username|用户名' => 'require|alphaDash|min:4|max:16|unique:member', + 'mobile|手机号' => 'require|unique:member', + 'nickname|昵称' => 'require|chsAlphaNum|min:2|max:10', + 'password|密码' => 'require|min:4|max:16', + 'remark|备注信息' => 'max:255', + ]); + + if ($validate !== true) { + return $validate; + } + + if (!checkMobile($item['mobile'])) { + return $this->json(4002, '请输入正确的手机号码'); + } + + $roles = []; + if ($item['roles']) { + $roles = $item['roles']; + $item['roles'] = implode(',', $item['roles']); + } + + Db::startTrans(); + try { + $item['password'] = md5($item['password'].$item['username']); + $member = MemberModel::create($item); + foreach ($roles as $role) { + Enforcer::addRoleForUser($member['id'], $role); + } + Db::commit(); + return $this->json(); + } catch (ValidateException $e) { + Db::rollback(); + return $this->json(4001, $e->getError()); + } + } + + $this->data['roleJson'] = $this->roleJson(); + return $this->view(); + } + + /** + * 修改密码 + * + * @return Json|View|Redirect + * @throws Exception + */ + public function password() + { + $id = input('id/d', 0); + + if (!$item = MemberModel::findById($id)) { + if ($this->request->isAjax()) { + return $this->json(4001, '记录不存在'); + } + return $this->error('记录不存在'); + } + + if ($this->request->isPost()) { + $post = input('post.'); + $validate = $this->validateByApi($post, [ + 'password|密码' => 'require|confirm', + ]); + + if ($validate !== true) { + return $validate; + } + + $password = md5($post['password'].$item['username']); + + try { + $item->save(['password' => $password]); + return $this->json(); + } catch (ValidateException $e) { + return $this->json(4001, $e->getError()); + } + } + + $this->data['item'] = $item; + + return $this->view(); + } + + /** + * 个人修改密码 + * + * @return Json|View + * @throws Exception + */ + public function myPassword() + { + $id = $this->auth['user_id'] ?? 0; + if (!$item = MemberModel::findById($id)) { + return $this->json(4001, '记录不存在'); + } + + if ($this->request->isPost()) { + $post = input('post.'); + $validate = $this->validateByApi($post, [ + 'old-password|旧密码' => 'require', + 'password|密码' => 'require|confirm', + ]); + + if ($validate !== true) { + return $validate; + } + + if ($item['password'] !== md5($post['old-password'].$item['username'])) { + return $this->json(4002, '原始密码错误'); + } + + $password = md5($post['password'].$item['username']); + + try { + $item->save(['password' => $password]); + return $this->json(); + } catch (ValidateException $e) { + return $this->json(4001, $e->getError()); + } + } + + $this->data['item'] = $item; + return $this->view(); + } + + /** + * 列表 + * + * @return View|Json + * @throws Exception + */ + public function index() + { + if ($this->request->isPost()) { + $page = input('page/d', 1); + $limit = input('size/d', 20); + $searchParams = input('searchParams'); + $where = []; + if ($searchParams) { + foreach ($searchParams as $key => $param) { + if (!empty($param)) { + $where[] = [$key, 'like', '%'.$param.'%']; + } + } + } + + $items = MemberModel::findList($where, [], $page, $limit, function ($q) { + return $q->order('id', 'desc'); + }); + + return $this->json(0, '操作成功', $items); + } + return $this->view(); + } + + /** + * 构造角色json数据 + * + * @param array $selected + * @return false|string + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + */ + private function roleJson(array $selected = []) + { + $roles = RoleModel::where('status', RoleModel::STATUS_NORMAL) + ->order('sort', 'desc') + ->select() + ->toArray(); + foreach ($roles as $k => $m) { + $roles[$k]['checked'] = in_array($m['id'], $selected); + $roles[$k]['spread'] = true; + } + return json_encode($roles, JSON_UNESCAPED_UNICODE); + } +} \ No newline at end of file diff --git a/app/controller/manager/Menu.php b/app/controller/manager/Menu.php new file mode 100644 index 0000000..c5bf342 --- /dev/null +++ b/app/controller/manager/Menu.php @@ -0,0 +1,259 @@ +request->isPost()) { + $ids = input('post.ids/a', []); + if (empty($ids)) { + $ids[] = input('post.id/d'); + } + $repo = CmsRepository::getInstance(); + $repo->delMenuByIds($ids); + return $this->json(); + } + return $this->json(4001, '非法请求!'); + } + + /** + * 规则 + * + * @return string[] + */ + private function rule(): array + { + return [ + 'pid|父级菜单' => 'require|number', + 'title|标题' => 'require|max:100', + 'name|路由标识' => 'require', + 'remark|备注信息' => 'max:255', + ]; + } + + + /** + * 编辑 + * + * @return Json|View + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + * @throws Exception + */ + public function edit() + { + $id = input('id/d', 0); + + if (!$info = MenuModel::findById($id)) { + return $this->json(4001, '记录不存在'); + } + + if ($this->request->isPost()) { + $item = input('post.'); + $validate = $this->validateByApi($item, $this->rule()); + if ($validate !== true) { + return $validate; + } + + try { + $oldPath = $info['path']; + $item['path'] = MenuModel::getPath($item['pid']); + $info->save($item); + + //刷新所有路径 + $oldPath = $oldPath.','.$id; + $newPath = $item['path'].','.$id; + if ($oldPath != $newPath) { + MenuModel::refreshPath(); + } + return $this->json(); + } catch (ValidateException $e) { + return $this->json(4001, $e->getError()); + } + } + + $disabled = MenuModel::getAllChildrenIds($id); + $disabled[] = $id; + $this->data['menuList'] = $this->menuJson([$info['pid']], $disabled); + $this->data['item'] = $info; + + return $this->view(); + } + + /** + * 单个字段编辑 + * + * @return Json + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + */ + public function modify(): Json + { + if ($this->request->isPost()) { + $item = input('post.'); + $validate = new MenuValidate(); + if (!$validate->scene('menu_modify')->check($item)) { + return $this->json(4002, $validate->getError()); + } + + if (!$info = MenuModel::findById($item['id'])) { + return $this->json(4001, '记录不存在'); + } + + $update = [$item['field'] => $item['value']]; + + try { + $info->save($update); + return $this->json(); + } catch (ValidateException $e) { + return $this->json(4001, $e->getError()); + } + } + return $this->json(4000, '非法请求'); + } + + /** + * 添加 + * + * @return Json|View + * @throws Exception + */ + public function add() + { + $id = input('id/d', 0); + + if ($this->request->isPost()) { + $item = input('post.'); + $validate = $this->validateByApi($item, $this->rule()); + if ($validate !== true) { + return $validate; + } + try { + $item['path'] = MenuModel::getPath($item['pid']); + MenuModel::create($item); + return $this->json(); + } catch (ValidateException $e) { + return $this->json(4001, $e->getError()); + } + } + + $selected = $id > 0 ? [$id] : []; + $this->data['menuList'] = $this->menuJson($selected); + + return $this->view(); + } + + /** + * 常规权限生成 + * + * @return Json|View + * @throws Exception + */ + public function generate() + { + $id = input('id/d', 0); + + if ($this->request->isPost()) { + $id = input('id/d', 0); + if (!$item = MenuModel::findById($id)) { + return $this->json(4002, '记录不存在'); + } + + if ($item['type'] != MenuModel::TYPE_MENU) { + return $this->json(4003, '仅菜单类型可操作'); + } + + Db::startTrans(); + try { + //自动生成常规操作 + MenuModel::generate($id, $item['name'], $item['path']); + Db::commit(); + return $this->json(); + } catch (ValidateException $e) { + Db::rollback(); + return $this->json(4001, $e->getError()); + } + } + + $selected = $id > 0 ? [$id] : []; + $this->data['menuList'] = $this->menuJson($selected); + + return $this->view(); + } + + /** + * 列表 + * + * @return View|Json + */ + public function index() + { + if ($this->request->isPost()) { + $menus = CmsRepository::getInstance()->getMenuList(); + $menus->each(function ($item) { + if ($item['type'] == 'menu') { + $item->open = false; + } + }); + $res = [ + 'code' => 0, + 'msg' => 'success', + 'count' => $menus->count(), + 'data' => $menus->toArray(), + ]; + return json($res); + } + return $this->view(); + } + + /** + * xmSelect插件 json数据 + * + * @param array $selected + * @param array $disabled + * @return false|string + */ + private function menuJson(array $selected = [], array $disabled = []) + { + $categoryList[] = ['title' => '顶级菜单', 'id' => 0, 'prefix' => '', 'disabled' => false, 'open' => true, 'selected' => in_array(0, $selected)]; + $menus = CmsRepository::getInstance()->getMenuList(); + $menus = $menus->toArray(); + foreach ($menus as $k => $m) { + $menus[$k]['selected'] = in_array($m['id'], $selected); + $menus[$k]['disabled'] = in_array($m['id'], $disabled); + } + $menus = CmsRepository::getInstance()->buildMenuChild(0, $menus); + $categoryList = array_merge($categoryList, CmsRepository::getInstance()->handleSelectedList($menus)); + return json_encode($categoryList, JSON_UNESCAPED_UNICODE); + } +} \ No newline at end of file diff --git a/app/controller/manager/Role.php b/app/controller/manager/Role.php new file mode 100644 index 0000000..f7c84c9 --- /dev/null +++ b/app/controller/manager/Role.php @@ -0,0 +1,270 @@ +request->isPost()) { + $ids = input('post.ids/a', []); + if (empty($ids)) { + $ids[] = input('post.id/d'); + } + RoleModel::deleteByIds($ids); + Log::write(get_class().'Del', 'del', '涉及到的ID为:'.implode(',', $ids)); + return $this->json(); + } + return $this->json(4001, '非法请求!'); + } + + /** + * 编辑 + * + * @return Json|View + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + * @throws Exception + */ + public function edit() + { + $id = input('id/d', 0); + + if (!$info = RoleModel::findById($id)) { + return $this->json(4001, '记录不存在'); + } + + if ($this->request->isPost()) { + $item = input('post.'); + $validate = $this->validateByApi($item, [ + 'title' => 'require', + ]); + + if ($validate !== true) { + return $validate; + } + + try { + $info->save($item); + return $this->json(); + } catch (ValidateException $e) { + return $this->json(4001, $e->getError()); + } + } + + $this->data['item'] = $info; + + return $this->view(); + } + + /** + * 单个字段编辑 + * + * @return Json + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + * @throws Exception + */ + public function modify(): Json + { + if ($this->request->isPost()) { + $item = input('post.'); + $validate = $this->validateByApi($item, [ + 'field' => 'require', + 'value' => 'require', + ]); + + if ($validate !== true) { + return $validate; + } + + if (!$info = RoleModel::findById($item['id'])) { + return $this->json(4001, '记录不存在'); + } + + $update = [$item['field'] => $item['value']]; + + try { + $info->save($update); + return $this->json(); + } catch (ValidateException $e) { + return $this->json(4001, $e->getError()); + } + } + return $this->json(4000, '非法请求'); + } + + /** + * 添加 + * + * @return Json|View + * @throws Exception + */ + public function add() + { + if ($this->request->isPost()) { + $item = input('post.'); + + $validate = $this->validateByApi($item, [ + 'title' => 'require', + ]); + + if ($validate !== true) { + return $validate; + } + + try { + RoleModel::create($item); + return $this->json(); + } catch (ValidateException $e) { + return $this->json(4001, $e->getError()); + } + } + + return $this->view(); + } + + /** + * 角色权限 + * + * @return Json|View + * @throws Exception + */ + public function rule() + { + $id = input('id/d', 0); + + if (!$item = RoleModel::findById($id)) { + return $this->json(4001, '记录不存在'); + } + + if ($this->request->isPost()) { + $ids = input('post.ids'); + $roleUpdate = $ids;//角色更新数据 + $ids = explode(',', $ids); + + Db::startTrans(); + try { + //查询角色已有权限 + $hasRules = Rules::where('ptype', 'p')->where('v0', $id)->select()->toArray(); + //角色最新权限列表 + $currentRules = MenuModel::where('id', 'in', $ids)->field('name')->select()->toArray(); + + foreach ($currentRules as &$rule) { + $route = explode(':', $rule['name']); + $v1 = $route[0]; + $v2 = $route[1] ?? 'index'; + + $rule['ptype'] = 'p'; + $rule['v0'] = $id; + $rule['v1'] = $v1; + $rule['v2'] = $v2; + } + + foreach ($hasRules as $k => $has) { + foreach ($currentRules as $m => $current) { + if ($has['ptype'] == $current['ptype'] && $has['v0'] == $current['v0'] && $has['v1'] == $current['v1'] && $has['v2'] == $current['v2']) { + unset($currentRules[$m]);//删除当前权限列表已存在的 currentRules剩下的就是需要添加的记录 + unset($hasRules[$k]);//删除已有权限中存在的 hasRules剩下的就是需要删除的记录 + } + } + } + + $insert = $currentRules;//需要添加的数据 + $delete = $hasRules;//需要删除的数据 + + $deleteIds = array_column($delete, 'id');//需要删除的ID + (new Rules())->saveAll($insert); + (new Rules())->where('id', 'in', $deleteIds)->delete(); + cache('tauthz', null);//权限缓存清空 + + $item->save(['rules' => $roleUpdate]); + Db::commit(); + return $this->json(); + } catch (ValidateException $e) { + Db::rollback(); + return $this->json(4001, $e->getError()); + } + } + + $selected = explode(',', $item['rules']); + + $this->data['authJson'] = $this->authJson($selected); + $this->data['item'] = $item; + + return $this->view(); + } + + /** + * 构造json数据 + * + * @param array $selected + * @return false|string + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + */ + private function authJson(array $selected = []) + { + $menus = Menu::field("id,pid,title,sort") + ->where('status', Menu::STATUS_NORMAL) + ->order('sort', 'desc') + ->order('id', 'asc') + ->select()->toArray(); + foreach ($menus as $k => $m) { + $menus[$k]['checked'] = in_array($m['id'], $selected); + $menus[$k]['open'] = true; + } + $menus = CmsRepository::getInstance()->buildMenuChild(0, $menus); + return json_encode($menus, JSON_UNESCAPED_UNICODE); + } + + /** + * 列表 + * + * @return View|Json + * @throws Exception + */ + public function index() + { + if ($this->request->isPost()) { + $page = input('page/d', 1); + $limit = input('size/d', 20); + $items = RoleModel::findList([], [], $page, $limit, function ($q) { + return $q->order('sort', 'desc')->order('id', 'asc'); + }); + + return $this->json(0, '操作成功', $items); + } + return $this->view(); + } +} \ No newline at end of file diff --git a/app/controller/manager/Rule.php b/app/controller/manager/Rule.php new file mode 100644 index 0000000..6a83a80 --- /dev/null +++ b/app/controller/manager/Rule.php @@ -0,0 +1,183 @@ +request->isAjax()) { + $id = input('post.id'); + $sort = input('post.sort'); + $num = input('post.num/d', 1); + if($num <= 0){ + $num = 1; + } + if(!in_array($sort, ['up', 'down'], true)){ + return $this->json(2, '参数错误'); + } + $item = AuthRule::getById($id); + if(empty($item)){ + return $this->json(3, '权限不存在'); + } + if($sort == 'up'){ + $where = "parent_id = {$item['parent_id']} and sort < {$item['sort']}"; + $order = "sort desc"; + }else{ + $where = "parent_id = {$item['parent_id']} and sort > {$item['sort']}"; + $order = "sort asc"; + } + $forSortItems = AuthRule::getListByWhereAndOrder($where, $order, $num); + if(!empty($forSortItems)){ + $updateData = []; + $forSortCount = count($forSortItems); + for($i = 0; $i < $forSortCount; $i++){ + if($i == 0){ + $updateData[] = [ + 'id' => $forSortItems[$i]['id'], + 'sort' => $item['sort'] + ]; + }else{ + $updateData[] = [ + 'id' => $forSortItems[$i]['id'], + 'sort' => $forSortItems[$i - 1]['sort'] + ]; + } + } + $updateData[] = [ + 'id' => $item['id'], + 'sort' => $forSortItems[$i - 1]['sort'] + ]; + if(!empty($updateData)){ + $model = new AuthRule(); + $model->saveAll($updateData); + $sortStr = $sort == 'up' ? '上移' : '下调'; + Log::write('rule', 'sort', "权限排序,ID:{$id} ,标题:{$item['title']},{$sortStr}了{$num}位"); + return $this->json(); + } + } + return $this->json(4, '无须调整排序!'); + } + return $this->json(1, '非法请求!'); + } + + /** + * 权限删除 + */ + public function del() + { + if ($this->request->isAjax()) { + $ids = input('post.ids/a'); + $items = AuthRule::where('id', 'in', $ids)->select(); + if(!$items){ + return $this->json(1, '无此权限'); + } + if(AuthRule::where('parent_id', 'in', $ids)->count()){ + return $this->json(2, '当前权限有下级权限,不可删除'); + } + AuthRule::destroy($ids); + AuthGroup::resetGroupRulesCache(); + $ids = implode(',', $ids); + Log::write('rule', 'del', "权限删除,ID:{$ids}"); + return $this->json(); + } + return $this->json(1, '非法请求!'); + } + + /** + * 权限修改 + */ + public function edit() + { + if($this->request->isPost()){ + $item = input('post.item/a'); + $id = input('post.id'); + $rule = AuthRule::getById($id); + if(empty($rule)){ + return $this->json(1, '请选择正确的权限'); + } + $rule2 = AuthRule::getByName($item['name']); + if(!empty($rule2) && $rule2['id'] != $id){ + return $this->json(2, '已存在相同权限['.$item['name'].']'); + } + try { + validate(VAuthRule::class)->check($item); + AuthRule::updateById($id, $item); + AuthGroup::resetGroupRulesCache(); + Log::write('rule', 'edit', "权限编辑,ID:{$id}, 标题:{$item['title']}"); + return $this->json(); + } catch (ValidateException $e) { + return $this->json(3, $e->getError()); + } + } + $id = input('param.id/d'); + $rule = AuthRule::getById($id); + if(empty($rule)){ + return $this->json(1,'无此权限信息,请核对之后再操作!'); + }else{ + $this->data['item'] = $rule; + if($rule['parent_id'] > 0){ + $parent = AuthRule::getById($rule['parent_id']); + $this->data['parent'] = $parent; + } + return $this->view(); + } + } + + /** + * 权限添加 + */ + public function add() + { + if($this->request->isPost()){ + $item = input('post.item/a'); + try { + validate(VAuthRule::class)->check($item); + $rule = AuthRule::getByName($item['name']); + if(!empty($rule)){ + return $this->json(1, '已存在相同权限'); + } + $rule = AuthRule::create($item); + //基本权限的话需要重置所有已有角色权限缓存 + if ($item['is_base'] > 0) { + AuthGroup::resetGroupRulesCache(); + } else { + AuthGroup::resetGroupRulesCache(1); + } + Log::write('rule', 'add', "权限新增,ID:{$rule->id}, 标题:{$item['title']}"); + return $this->json(); + } catch (ValidateException $e) { + return $this->json(2, $e->getError()); + } + } + $parentId = input('param.parent_id/d',0); + if($parentId > 0){ + $parent = AuthRule::getById($parentId); + $this->data['parent'] = $parent; + } + $this->data['parentId'] = $parentId; + return $this->view(); + } + + /** + * 权限列表(全部) + */ + public function index() + { + $list = AuthRule::getListTree(); + $this->data['items'] = $list; + return $this->view(); + } +} \ No newline at end of file diff --git a/app/controller/manager/Slide.php b/app/controller/manager/Slide.php new file mode 100644 index 0000000..9f20e17 --- /dev/null +++ b/app/controller/manager/Slide.php @@ -0,0 +1,277 @@ +request->param('id/d', 0); + + if (!$slide = OperationRepository::getInstance()->findSlideById($id)) { + return $this->json(4001, '数据不存在'); + } + + if ($this->request->isPost()) { + $item = input('post.item/a'); + $validate = new VSlide(); + if (!$validate->scene('slide')->check($item)) { + return $this->json(4002, $validate->getError()); + } + + unset($item['id']); + OperationRepository::getInstance()->updateSlide($item, $id); + return $this->json(); + } + + $this->data['item'] = $slide; + $this->data['positionsJson'] = $this->xmSelectPositionsJson([$slide['position']]); + $this->data['id'] = $id; + + return $this->view(); + } + + /** + * 添加 + * + * @return View|Json + */ + public function add() + { + $repo = OperationRepository::getInstance(); + + if ($this->request->isPost()) { + $item = input('post.item/a'); + $validate = new VSlide(); + if (!$validate->scene('slide')->check($item)) { + return $this->json(4002, $validate->getError()); + } + + $item['type'] = $item['type'] ?? 'img'; + $repo->createSlide($item); + return $this->json(); + } + + $this->data['positionsJson'] = $this->xmSelectPositionsJson(); + return $this->view(); + } + + /** + * 轮播图列表 + * + * @return Json|View + * @throws Exception + */ + public function index() + { + $repo = OperationRepository::getInstance(); + if ($this->request->isPost()) { + $position = $this->request->param('position/s', ''); + $page = $this->request->param('page/d', 1); + $size = $this->request->param('size/d', 30); + + $whereMap = []; + $orders = ['sort'=>'asc']; + if (!empty($position)) { + $whereMap[] = ['position', '=', $position]; + } + + $list = $repo->slideList($whereMap, [], $page, $size, null, $orders); + + return $this->json(0, 'success', $list); + } + + $this->data['positions'] = $repo->slidePositions(); + return $this->view(); + } + + /** + * 排序 + * @return Json + */ + public function sort() + { + if (!$this->request->isPost()) { + return $this->json(4000, '非法请求'); + } + try { + $id = $this->request->param('id/d', 0); + $sort = $this->request->param('sort/d', 0); + OperationRepository::getInstance()->updateSlide(['sort'=>$sort], $id); + } catch (Exception $e) { + return $this->json(4001, '排序失败'); + } + return $this->json(); + } + + /** + * 删除 + * @return Json + */ + public function del() + { + if (!$this->request->isPost()) { + return $this->json(4000, '非法请求'); + } + + $ids = $this->request->param('ids/a', []); + if (empty($ids)) { + $ids[] = $this->request->param('id/d', 0); + $ids = array_filter($ids); + } + + if (count($ids)) { + OperationRepository::getInstance()->deleteSlides($ids); + Log::write(get_class(), 'del', '删除了轮播图,涉及到的ID为:'.implode(',', $ids)); + } + return $this->json(); + } + + /** + * 显示位置下拉选项数据 + * + * @param array $selected + * @param array $disabled + * @return false|string + */ + private function xmSelectPositionsJson(array $selected = [], array $disabled = []) + { + $positionList = OperationRepository::getInstance()->slidePositions(); + foreach ($positionList as $k => $item) { + $positionList[$k]['selected'] = in_array($item['key'], $selected); + $positionList[$k]['disabled'] = in_array($item['key'], $disabled); + } + $positionList = CmsRepository::getInstance()->handleSelectedList($positionList); + return json_encode($positionList, JSON_UNESCAPED_UNICODE); + } + + /** + * 轮播图显示位置管理 + * + */ + public function position() + { + $repo = OperationRepository::getInstance(); + + if ($this->request->isPost()) { + $list = $repo->slidePositionList([], [], 1, 0); + + return $this->json(0, 'success', $list); + } + return $this->view(); + } + + /** + * 添加显示位置信息 + * + * @return Json|View + */ + public function addPosition() + { + $repo = OperationRepository::getInstance(); + + if ($this->request->isPost()) { + $item = input('post.item/a'); + try { + $this->validate($item, [ + 'title|标题' => 'max:250', + 'key|位置标识' => 'require|max:100|alphaDash' + ]); + } catch (ValidateException $e) { + return $this->json(4002, $e->getError()); + } + + if ($repo->slidePositionExists($item['key'])) { + return $this->json(4003, '当前位置标识已存在!'); + } + + $repo->createSlidePosition($item); + return $this->json(); + } + + return $this->view(); + } + + /** + * 编辑显示位置信息 + * + * @return Json|View + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function editPosition() + { + $id = $this->request->param('id/d', 0); + + if (!$position = OperationRepository::getInstance()->findSlidePositionById($id)) { + return $this->json(4001, '数据不存在'); + } + + if ($this->request->isPost()) { + $item = input('post.item/a'); + try { + $this->validate($item, [ + 'title|标题' => 'max:250' + ]); + } catch (ValidateException $e) { + return $this->json(4002, $e->getError()); + } + + unset($item['id']); + unset($item['key']); + OperationRepository::getInstance()->updateSlidePosition($item, $id); + return $this->json(); + } + + $this->data['item'] = $position; + $this->data['id'] = $id; + + return $this->view(); + } + + /** + * 删除显示位置信息 + * @return Json + */ + public function delPosition() + { + if (!$this->request->isPost()) { + return $this->json(4000, '非法请求'); + } + + $ids = $this->request->param('ids/a', []); + if (empty($ids)) { + $ids[] = $this->request->param('id/d', 0); + $ids = array_filter($ids); + } + + if (count($ids)) { + OperationRepository::getInstance()->deleteSlidePositions($ids); + Log::write(get_class(), 'delPosition', '删除了轮播显示位置,涉及到的ID为:'.implode(',', $ids)); + } + + return $this->json(); + } + + +} \ No newline at end of file diff --git a/app/controller/manager/Upload.php b/app/controller/manager/Upload.php new file mode 100644 index 0000000..abff79b --- /dev/null +++ b/app/controller/manager/Upload.php @@ -0,0 +1,239 @@ +isCompress = $system['compress'] ?? true; + } + $this->validate = new VUpload(); + $this->uploadPath = Config::get('filesystem.disks.local.url'); + $this->videoUploadPath = Config::get('filesystem.disks.video.url'); + + $uploadDiskPath = public_path().$this->uploadPath; + $videoUploadDiskPath = public_path().$this->videoUploadPath; + if (!is_dir($uploadDiskPath)) { + @mkdir($uploadDiskPath, 0777, true); + } + if (!is_dir($videoUploadDiskPath)) { + @mkdir($videoUploadDiskPath, 0777, true); + } + if (is_writable($uploadDiskPath)) { + $this->uploadPathIsWritable = 1; + } + if (is_writable($videoUploadDiskPath)) { + $this->videoUploadPathIsWritable = 1; + } + + + $this->DIRECTORY_SEPARATOR = DIRECTORY_SEPARATOR == "\\" ? "/" : DIRECTORY_SEPARATOR; + ini_set('max_execution_time', '0'); + ini_set("memory_limit", '-1'); + set_time_limit(0); + } + + //视频上传 + public function video() + { + $video = request()->file('video_video'); + if (!$this->videoUploadPathIsWritable) { + return $this->json(1, '上传文件夹需要写入权限'); + } + if ($this->validate->checkVideo($video)) { + $md5 = $video->md5();//文件md5 + if ($fileItem = File::where('md5', $md5)->find()) { + $return['src'] = $fileItem['src']; + $fileItem['updated_at'] = date('Y-m-d H:i:s'); + $fileItem->save(); + return $this->json(200, '该文件已存在 路径为:'.$fileItem['path'], $return); + } + + $path = request()->param('path/s', '');//指定路径 基于public下 若为空则默认 + $hasPath = !empty($path); + + // 去除以/storage/开头的部分 如/storage/20210808/test => 20210808/test + $path = ltrim(trim($path, $this->DIRECTORY_SEPARATOR), \app\model\Attachment::ROOT_NAME.$this->DIRECTORY_SEPARATOR); + $datePath = $hasPath ? $path : 'videos'.$this->DIRECTORY_SEPARATOR.date('Ym');//自定义目录 + checkPathExistWithMake(public_path().$this->uploadPath.'/'.$datePath); + $src = Filesystem::putFile($datePath, $video, 'uniqid'); + $src = $this->uploadPath.'/'.$src; + $return['src'] = $src; + File::add($video, $src, $md5, 'video'); //加入上传文件表 + return $this->json(0, 'ok', $return); + } else { + + $errorMsg = Lang::get($this->validate->getError()); + return $this->json(1, $errorMsg); + } + } + + //文件上传(通用) + public function file() + { + $file = request()->file('file_file'); + $md5 = $file->md5();//文件md5 + $fileName = $file->getOriginalName();//原始文件名 + if ($fileItem = File::where('md5', $md5)->find()) { + $return['src'] = $fileItem['src']; + $return['name'] = $fileName; + $fileItem['updated_at'] = date('Y-m-d H:i:s'); + return $this->json(200, '该文件已存在 路径为:'.$fileItem['path'], $return); + } + if ($this->validate->checkFile($file)) { + try { + if (!$this->uploadPathIsWritable) { + throw new \Exception('上传文件夹需要写入权限'); + } + $path = request()->param('path/s', '');//指定路径 基于public下 若为空则默认 + $hasPath = !empty($path); + $datePath = $hasPath ? $path : 'files'.$this->DIRECTORY_SEPARATOR.date('Ym');//自定义目录 + checkPathExistWithMake(public_path().$this->uploadPath.'/'.$datePath); + $src = Filesystem::putFile('files'.$this->DIRECTORY_SEPARATOR.date('Ym'), $file, 'uniqid'); + $src = $this->uploadPath.$this->DIRECTORY_SEPARATOR.$src; + $return['src'] = $src; + $return['name'] = $fileName; + File::add($file, $src, $md5, 'file'); //加入上传文件表 + } catch (\Exception $e) { + return $this->json(1, $e->getMessage()); + } + return $this->json(0, 'ok', $return); + } else { + $errorMsg = Lang::get($this->validate->getError()); + return $this->json(1, $errorMsg); + } + } + + //图片上传(通用) + public function image() + { + // 字段名 image-image避免冲突 layui组件自动生成的隐藏file input框中name容易重名冲突 + $image = request()->file('image_image'); + + // $img = ImageManagerStatic::cache(function($imageObj) use ($image) { + // $imageObj->make($image)->resize(300, 200)->greyscale(); + // }, 10, true); + // $img = (string)ImageManagerStatic::make($image)->fit(350, 610)->encode('png'); + // echo $img; + // exit; + + $md5 = $image->md5();//文件md5 + $path = request()->param('path/s', '');//指定路径 基于public下 若为空则默认 + $hasPath = !empty($path); + + if ($this->validate->checkImage($image)) { + if ($fileItem = File::where('md5', $md5)->find()) { + $return['src'] = $fileItem['src']; + $fileItem['updated_at'] = date('Y-m-d H:i:s'); + + return $this->json(200, '该文件已存在 路径为:'.$fileItem['path'], $return); + } + + try { + if (!$this->uploadPathIsWritable) { + throw new \Exception('上传文件夹需要写入权限'); + } + + // 去除以/storage/开头的部分 如/storage/20210808/test => 20210808/test + if (strpos(trim($path, $this->DIRECTORY_SEPARATOR), \app\model\Attachment::ROOT_NAME.$this->DIRECTORY_SEPARATOR) === 0) { + $path = substr(trim($path, $this->DIRECTORY_SEPARATOR), strlen(\app\model\Attachment::ROOT_NAME.$this->DIRECTORY_SEPARATOR)); + } + + $datePath = $hasPath ? $path : 'images'.$this->DIRECTORY_SEPARATOR.date('Ym');//自定义目录 + $datePath = ($datePath == $this->DIRECTORY_SEPARATOR."storage") ? $this->DIRECTORY_SEPARATOR : $datePath; + + checkPathExistWithMake(public_path().$this->uploadPath.'/'.$datePath); + + $src = Filesystem::putFile($datePath, $image, 'uniqid'); + + $src = $this->uploadPath.$this->DIRECTORY_SEPARATOR.$src; + $suffix = strtolower($image->getOriginalExtension()); + + $cutJson= input('cut'); + $cutList = json_decode($cutJson, true); + // 裁剪尺寸 + if (!empty($cutList)) { + $srcArr = explode('.', $src); + foreach ($cutList as $cut) { + ImageManagerStatic::make($image)->fit($cut['width'], $cut['height'])->save(public_path().$srcArr[0].'_'.$cut['name'].'.'.$suffix); + } + } + $return['src'] = $src; + File::add($image, $src, $md5); //加入上传文件表 + } catch (\Exception $e) { + return $this->json(1, $e->getMessage()); + } + return $this->json(0, 'ok', $return); + } else { + $errorMsg = Lang::get($this->validate->getError()); + return $this->json(1, $errorMsg); + } + } + + //富文本编辑器商城图片 + public function wangImage() + { + + $imageArr = request()->file('wang_img'); // 该方式,前端js上传方法中字段名称必须以数组形式传参 如 wang_img[] = 值 + $errno = 0; + $data = []; + + if (!$this->uploadPathIsWritable) { + $errno = 1; + $data[] = '上传文件夹需要写入权限'; + } else { + foreach ($imageArr as $image) { + $md5 = $image->md5();//文件md5 + if ($fileItem = File::where('md5', $md5)->find()) { + $return['src'] = $fileItem['src']; + $fileItem['updated_at'] = date('Y-m-d H:i:s'); + + $data[] = $fileItem['src']; + $fileItem->save(); + continue; + } + + if ($this->validate->checkImage($image)) { + checkPathExistWithMake(public_path().$this->uploadPath.'/images/'.date('Ym')); + $src = Filesystem::putFile('images/'.date('Ym'), $image, 'uniqid'); + $src = $this->uploadPath.$this->DIRECTORY_SEPARATOR.$src; + $data[] = $src; + if ($this->isCompress) { + Image::resize($src); + } + File::add($image, $src, $md5); //加入上传文件表 + } else { + $errno = 1; + $data = []; + $data[] = Lang::get($this->validate->getError()); + break; + } + } + } + + $return['errno'] = $errno; + $return['data'] = $data; + return json($return); + } +} \ No newline at end of file diff --git a/app/controller/manager/Wechat.php b/app/controller/manager/Wechat.php new file mode 100644 index 0000000..9ee7a27 --- /dev/null +++ b/app/controller/manager/Wechat.php @@ -0,0 +1,27 @@ +view(); + } + + + + +} \ No newline at end of file diff --git a/app/event.php b/app/event.php new file mode 100644 index 0000000..44550ba --- /dev/null +++ b/app/event.php @@ -0,0 +1,16 @@ + [ + ], + + 'listen' => [ + 'AppInit' => [], + 'HttpRun' => [], + 'HttpEnd' => [], + 'LogLevel' => [], + 'LogWrite' => [], + ], + + 'subscribe' => [], +]; diff --git a/app/exception/ApiRedirectException.php b/app/exception/ApiRedirectException.php new file mode 100644 index 0000000..40585eb --- /dev/null +++ b/app/exception/ApiRedirectException.php @@ -0,0 +1,10 @@ +attempts() > 3) { +// //通过这个方法可以检查这个任务已经重试了几次了 +// echo sprintf('发送短信失败过多'); +// } + + + //如果任务执行成功后 记得删除任务,不然这个任务会重复执行,直到达到最大重试次数后失败后,执行failed方法 + $job->delete(); + + // 也可以重新发布这个任务 +// $job->release(3); //$delay为延迟时间 + + } + + public function failed($data){ + + // ...任务达到最大重试次数后,失败了 + } +} \ No newline at end of file diff --git a/app/middleware.php b/app/middleware.php new file mode 100644 index 0000000..e588a2d --- /dev/null +++ b/app/middleware.php @@ -0,0 +1,10 @@ +authorization ?? ''; + if (empty($authorization)) { + return json(['code' => 6001, 'msg' => '请填写token']); + } + if (!JwtService::validate($authorization)) { + return json(['code' => 6001, 'msg' => 'token验证失败或已失效']); + } + + $userInfo = $request->user ?? []; + if (!isset($userInfo['user_id']) || empty($userInfo['user_id'])) { + return json(['code' => 6001, 'msg' => 'token已失效']); + } + + // 自定义过期时间校验。 + if(isset($userInfo['expire_time']) && time() >= $userInfo['expire_time']) { + return json(['code' => 6001, 'msg' => 'token已失效']); + } + + return $next($request); + } +} \ No newline at end of file diff --git a/app/middleware/Auth.php b/app/middleware/Auth.php new file mode 100644 index 0000000..3f72304 --- /dev/null +++ b/app/middleware/Auth.php @@ -0,0 +1,50 @@ +controller()); + $controller = str_replace($module.'.', '', $controller); + $controller = str_replace('.', '/', $controller);//兼容多层级目录 如 /manager/test/article/index + $action = unCamelize(request()->action()); + $roles = Enforcer::getRolesForUser($auth['user_id']); +// $per = Enforcer::getPermissionsForUser($roles[0]); +// var_dump($controller); +// var_dump($action); +// var_dump($roles); +// var_dump($per); +// exit; +// return $next($request);//暂时停用权限校验 +// var_dump($controller); +// var_dump($action); +// var_dump(Enforcer::hasPermissionForUser(1, $controller, 'group-make'));exit; + + foreach ($roles as $role) { + // TODO 关注批量权限检测是否可用 + //只需要有一个角色具有权限就放通 此处第一个参数不是用户 而是 角色 此方法是检测用户|角色是否具有某个权限的公用方法 + if (Enforcer::hasPermissionForUser($role, $controller, $action)) { + return $next($request); + } + } + + if (request()->isAjax()) { + return json(['code' => 4001, 'msg' => '没有权限']); + } else { + return view('/manager/error/jump')->assign('msg', '很抱歉,您还没有权限,请联系管理员开通!'); + } + } +} \ No newline at end of file diff --git a/app/middleware/Csrf.php b/app/middleware/Csrf.php new file mode 100644 index 0000000..c6bfccf --- /dev/null +++ b/app/middleware/Csrf.php @@ -0,0 +1,56 @@ +isPost()){ + $check = $request->checkToken(); + if(false === $check) { +// return $this->csrfError($request); + } + } + return $next($request); + } + + protected function csrfError($request, $msg = '非法请求, 用户身份认证失败!') + { + if($request->isAjax()) { + return json(['code' => 401, 'msg' => $msg], 200); + } else { + $referer = $_SERVER['HTTP_REFERER'] ?? null; + if (empty($referer)) { + $url = '/'; + } else { + $domain = $request->domain(); + $urlInfo = parse_url($referer); + $scheme = $urlInfo['scheme'] ?? ''; + $requestSrc = ''; + if (!empty($scheme)) { + $requestSrc = $scheme.'://'.($urlInfo['host'] ?? ''); + } + if($domain != $requestSrc) { + $url = '/'; + } else { + $url = 'javascript:history.back(-1);'; + } + } + $errorData = [ + 'code'=> 401, + 'msg' => $msg, + 'data' => [], + 'wait' => 5, + 'url' => $url + ]; + return view('error/400', $errorData); + // 返回401视图 response type has html、json、jsonp、xml、file、view、redirect + } + } +} \ No newline at end of file diff --git a/app/middleware/JWT.php b/app/middleware/JWT.php new file mode 100644 index 0000000..3ed39b5 --- /dev/null +++ b/app/middleware/JWT.php @@ -0,0 +1,37 @@ +header('Authorization'); + $tokenStr = $request->param('token/s', ''); + + if ($authorization) { + $authorization = str_replace('Bearer ', '', $authorization); + } + + //优先取header中token + $token = $authorization ?: $tokenStr; + $userInfo = []; + if (!empty($token)) { + $userInfo = JwtService::parse($token);//token中携带的简易用户信息 + } + + $request->user = $userInfo; + // authorization用于移交ApiLogin认证 + $request->authorization = $token; + + return $next($request); + } +} \ No newline at end of file diff --git a/app/middleware/Log.php b/app/middleware/Log.php new file mode 100644 index 0000000..4280db8 --- /dev/null +++ b/app/middleware/Log.php @@ -0,0 +1,26 @@ +controller(), $request->action(), $request->pathinfo(), $request->method()); + + return $response; + + } +} \ No newline at end of file diff --git a/app/middleware/Login.php b/app/middleware/Login.php new file mode 100644 index 0000000..40080f3 --- /dev/null +++ b/app/middleware/Login.php @@ -0,0 +1,25 @@ +url(true); + return redirect(url('login/index').'?url='.urlencode($url)); + } + + return $next($request); + } +} \ No newline at end of file diff --git a/app/model/Account.php b/app/model/Account.php new file mode 100644 index 0000000..70bcee6 --- /dev/null +++ b/app/model/Account.php @@ -0,0 +1,11 @@ + $per, + 'query' => $param + ]; + return self::with(["archivesCategory"])->where($where) + ->order(["sort"=>"desc"]) + ->paginate($paginate, false); + } + + + /** + * 创建人信息 + * + * @return HasOne + */ + public function member(): HasOne + { + return $this->hasOne(Member::class, 'id', 'created_by')->bind(['nickname']); + } + + + /** + * 栏目 后台用 + * + * @return HasOne + */ + public function category(): HasOne + { + return $this->hasOne(ArchivesCategory::class, 'id', 'category_id')->bind(['category' => 'title']); + } + + + /** + * 栏目 + * @return HasOne + */ + public function archivesCategory(): HasOne + { + return $this->hasOne(ArchivesCategory::class, 'id', 'category_id'); + } + + + public static function onAfterInsert( $archives) + { + $archives->sort = ceil(self::max("sort")+1); + $archives->save(); + } + + public static function onBeforeUpdate( $archives) + { + //检查sort + $hasFlag = self::where([ + ["sort","=",$archives->sort], + ["id","<>",$archives->id] + ])->find(); + if(!empty($hasFlag)){ + throw new RepositoryException('sort不能重复,保留小数点后2位'); + } + } +} diff --git a/app/model/ArchivesCategory.php b/app/model/ArchivesCategory.php new file mode 100644 index 0000000..fef4e82 --- /dev/null +++ b/app/model/ArchivesCategory.php @@ -0,0 +1,145 @@ +count() > 0; + } + + /** + * 检测数据下 是否有子内容 + * + * @param array $ids + * @return bool + */ + public static function hasContentByIds(array $ids): bool + { + return Archives::whereIn('category_id', $ids)->count() > 0; + } + + /** + * 获取列表 + * + * @return ArchivesCategory[]|array|Collection + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + */ + public static function getList() + { + return self::field('id,pid,title,name,sort,path,true as open') + ->order('sort', 'desc') + ->order('id', 'asc') + ->select(); + } + + /** + * 模型 + * + * @return HasOne + */ + public function model(): HasOne + { + return $this->hasOne(ArchivesModel::class, 'id', 'model_id')->bind(['model' => 'name']); + } + + +//--王兴龙 auth start -------------------------------------------------------------------------------------------------- + //获取前台菜单 + public static function getListForFrontMenu() + { + $items = self::withJoin(["model"]) + ->order(['sort' =>'desc',"id"=>"asc"]) + ->select() + ->toArray(); + return self::getCates($items); + } + + //获取递归栏目 + public static function getCates($cates, $parentId = 0) + { + $menus = []; + foreach ($cates as $cate) { + if ($cate['pid'] == $parentId) { + $children = self::getCates($cates, $cate['id']); + if (!empty($children)) { + $cate['children'] = $children; + } + $menus[] = $cate; + } + } + return $menus; + } + + //当前分类的最高级分类Id + public static function firstGradeById($id) + { + $res = 0; + $item = self::getById($id); + if ($item) { + $res = $id; + if ($item['pid'] > 0) { + $items = self::select()->toArray(); + $first = self::getFirstGrade($items, $item['pid']); + if (!empty($first)) { + $res = $first['id']; + } + } + } + return $res; + } + + public static function getFirstGrade($items, $parentId) + { + $data = []; + foreach ($items as $key => $item) { + if ($item['id'] == $parentId) { + if ($item['pid'] > 0) { + unset($items[$key]); + $parent = self::getFirstGrade($items, $item['pid']); + if (!empty($parent)) { + $data = $parent; + } else { + $data = $item; + } + } else { + $data = $item; + } + } + } + return $data; + } + + /** + * 获取每个栏目文章封面大小 数组 + * */ + public static function getArchiveCoverSizeArr() + { + return json_encode(self::column(["id","archive_cover_size"],"id")); + } + /** + * 获取每个栏目 设置路由 + * */ + public static function getRoute() + { + return self::column(["id","name","route"],"id"); + } + +//--王兴龙 auth end -------------------------------------------------------------------------------------------------- +} diff --git a/app/model/ArchivesModel.php b/app/model/ArchivesModel.php new file mode 100644 index 0000000..c90dd7d --- /dev/null +++ b/app/model/ArchivesModel.php @@ -0,0 +1,19 @@ +"1", + ]; + } +} \ No newline at end of file diff --git a/app/model/ArchivesModelField.php b/app/model/ArchivesModelField.php new file mode 100644 index 0000000..05c75e9 --- /dev/null +++ b/app/model/ArchivesModelField.php @@ -0,0 +1,118 @@ +count() <= 0) { + $rs = Db::query("SHOW FULL COLUMNS FROM ".Archives::ORIGINAL_TABLE); + $insert = []; + foreach ($rs as $val) { + $arr = []; + $arr['model_id'] = $modelId; + $arr['model'] = $modelName; + $arr['name'] = $val['Field']; + $arr['title'] = $val['Comment']; + $arr['remark'] = $val['Comment']; + $insert[] = $arr; + } + + (new self())->saveAll($insert); + } + } + + /** + * 同步字段 + * + * @param int $modelId + * @param string $modelName + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + */ + public static function syncFieldList(int $modelId, string $modelName) + { + $rs = Db::query("SHOW FULL COLUMNS FROM ".Archives::ORIGINAL_TABLE); + $oldFieldList = self::where('model_id', $modelId)->where('is_virtual', self::VIRTUAL_NO)->select(); + $oldFields = $oldFieldList->column('name'); + + + $newestFields = []; + foreach ($rs as $val) { + $newestFields[] = $val['Field']; + } + + //待删除字段 + $delete = array_diff($oldFields, $newestFields); + + //待新增字段 + $needInsertFields = array_diff($newestFields, $oldFields); + + $insert = [];//新增字段 + foreach ($rs as $val) { + if (in_array($val['Field'], $needInsertFields)) { + $arr = []; + $arr['model_id'] = $modelId; + $arr['model'] = $modelName; + $arr['name'] = $val['Field']; + $arr['title'] = $val['Comment']; + $arr['remark'] = $val['Comment']; + $insert[] = $arr; + } + } + + (new self())->saveAll($insert); + (new self())->whereIn('name', $delete)->delete(); + } + + /** + * 各栏目[模型] 可展示字段列表 + * + * @return array + */ + public static function showFieldList(): array + { + $list = self::alias('amf') + ->leftJoin('archives_category ac', 'ac.model_id = amf.model_id') + ->field('amf.*, ac.id as category_id, ac.title as category_title') + ->where('amf.status', self::COMMON_ON) + ->where('ac.id', '>', 0) + ->select(); + + $res = []; + + $list = $list->toArray(); + foreach ($list as $item) { + if (!isset($res[$item['category_id']])) { + $res[$item['category_id']] = []; + } + + $res[$item['category_id']][] = $item['name']; + } + + return $res; + } +} \ No newline at end of file diff --git a/app/model/Attachment.php b/app/model/Attachment.php new file mode 100644 index 0000000..229f4d9 --- /dev/null +++ b/app/model/Attachment.php @@ -0,0 +1,69 @@ + $p) { + $currentPath = '/'; + $currentArr = array_slice($pathArr, 0, $k); + if ($currentArr) { + $currentPath = "/".implode('/', $currentArr)."/"; + } + if ($currentPath != '/' && self::where('path', $currentPath)->where('name', $p)->count() <= 0) { + $arr = []; + $arr['path'] = $currentPath; + $arr['name'] = $p; + $arr['created_at'] = $now; + $arr['is_dir'] = self::COMMON_ON; + $arr['type'] = self::TYPE_DIR; + + $insert[] = $arr; + } + } + + (new self())->saveAll($insert); + return true; + } catch (Exception $e) { + return false; + } + } +} diff --git a/app/model/Base.php b/app/model/Base.php new file mode 100644 index 0000000..1900ec3 --- /dev/null +++ b/app/model/Base.php @@ -0,0 +1,390 @@ +select()->toArray(); + } + + //根据ID获取单条数据 + public static function getById($id) + { + if ($id <= 0) { + return []; + } + return self::where('id', $id)->findOrEmpty()->toArray(); + } + + /** + * 通过ID查找记录 + * + * @param int $id + * @param array $fields + * @param callable|null $call + * @return array|Model|null + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + */ + public static function findById(int $id, array $fields = [], callable $call = null) + { + $q = self::when(!empty($fields), function ($q) use ($fields) { + $q->field($fields); + }); + if ($call !== null) { + $q = $call($q); + } + return $q->find($id); + } + + /** + * 查找单个记录 + * + * @param array $where + * @param array $fields + * @param callable|null $call + * @return array|Model|null + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + */ + public static function findOne(array $where = [], array $fields = [], callable $call = null) + { + $q = self::when(!empty($fields), function ($q) use ($fields) { + $q->field($fields); + })->where($where); + + if ($call !== null) { + $q = $call($q); + } + return $q->find(); + } + + //根据ID更新数据 + public static function updateById($id, $data) + { + return self::where('id', $id)->update($data); + } + + //根据where条件和排序获取记录 + public static function getListByWhereAndOrder($where, $order, $limit = 1) + { + return self::where($where) + ->order($order) + ->limit($limit) + ->select() + ->toArray(); + } + + /** + * 根据ID删除数据 + * + * @param int $id + * @return bool + */ + public static function deleteById(int $id): bool + { + return self::where('id', $id)->delete(); + } + + /** + * 根据ID列表删除数据 + * + * @param array $ids + * @return bool + */ + public static function deleteByIds(array $ids): bool + { + return self::whereIn('id', $ids)->delete(); + } + + /** + * 排序 + * + * @param int $id 调整ID + * @param string $type 本次操作类型 向上、向下 + * @param int $num 移动位数 + * @param string $listType 列表的排序方式 默认为降序 + * @param array $where 额外条件 如限制在指定分类下 where[] = ['category_id', '=', 6] + * @return array + * @throws Exception + */ + public static function sort(int $id, string $type, int $num = 1, string $listType = 'desc', array $where = []): array + { + $res = ['code' => 0, 'msg' => 'success']; + $item = self::getById($id); + + if (!$item) { + $res['code'] = 1; + $res['msg'] = '记录不存在'; + return $res; + } + + if ($listType == 'desc') { + if ($type == 'down') { + $where[] = ['sort', '<', $item['sort']]; + $order = "sort desc"; + } else { + $where[] = ['sort', '>', $item['sort']]; + $order = "sort asc"; + } + } else { + if ($type == 'up') { + $where[] = ['sort', '<', $item['sort']]; + $order = "sort desc"; + } else { + $where[] = ['sort', '>', $item['sort']]; + $order = "sort asc"; + } + } + + $forSortItems = self::getListByWhereAndOrder($where, $order, $num); + if (!empty($forSortItems)) { + $updateData = []; + $forSortCount = count($forSortItems); + for ($i = 0; $i < $forSortCount; $i++) { + if ($i == 0) { + $updateData[] = [ + 'id' => $forSortItems[$i]['id'], + 'sort' => $item['sort'] + ]; + } else { + $updateData[] = [ + 'id' => $forSortItems[$i]['id'], + 'sort' => $forSortItems[$i - 1]['sort'] + ]; + } + } + $updateData[] = [ + 'id' => $item['id'], + 'sort' => $forSortItems[$i - 1]['sort'] + ]; + + if (!empty($updateData)) { + $obj = new static(); + $obj->saveAll($updateData); + return $res; + } + } + $res['code'] = 1; + $res['msg'] = '无需调整'; + return $res; + } + + /** + * 查询列表 [带分页 适用于后台] + * + * @param array $data 查询数据 格式如下 + * [ + * 'fields' => ['id','title','desc'],//查询字段 + * 'where' => [ + * ['name', 'like', '%thinkphp%'], + * ['title', 'like', '%thinkphp%'], + * ['id', '>', 0], + * ['status', '=', 1], + * ],//查询条件 + * 'order' => ['order'=>'desc','id'=>'desc'],//排序 + * 'size' => 50,//每页数量 + * ] + * @param array $pageParams 分页参数 具体参考:https://www.kancloud.cn/manual/thinkphp6_0/1037638 + * @param callable|null $callback 复杂查询条件 使用闭包查询 此时建议data留空 + * @return Paginator + * @throws DbException + */ + public static function findListWithPaginate(array $data = [], array $pageParams = [], callable $callback = null): Paginator + { + $q = new static(); + $fields = isset($data['fields']) && !empty($data['fields']) ? $data['fields'] : []; + $where = isset($data['where']) && !empty($data['where']) ? $data['where'] : []; + $order = isset($data['order']) && !empty($data['order']) ? $data['order'] : []; + $limit = $data['size'] ?? 20; + + if (count($where)) { + $q = $q->where($where); + } + + if (count($fields)) { + $q = $q->field($fields); + } + + if ($callback) { + $q = $callback($q); + } + + if (count($order)) { + $q = $q->order($order); + } + + $pageParams['list_rows'] = $limit; + + return $q->paginate($pageParams); + } + + /** + * 查询列表 + * + * @param array $simpleWhere 简易查询条件 + * @param array $fields 查询字段 []表示全部 + * @param int $page 默认第一页 0不限制 + * @param int $limit 限制条数 0不限制 + * @param callable|null $callback 复杂的条件 使用闭包查询 + * @param array $orders 键值对,排序 + * @return array + * @throws Exception + */ + public static function findList(array $simpleWhere = [], array $fields = [], int $page = 1, int $limit = 0, callable $callback = null, array $orders = []): array + { + $q = new static(); + $data = [ + 'total' => 0, + 'current' => $page, + 'size' => $limit, + 'list' => new Collection(), + ]; + + if (count($fields)) { + $q = $q->field($fields); + } + + if (count($simpleWhere)) { + $q = $q->where($simpleWhere); + } + + if ($callback) { + $q = $callback($q); + } + + $data['total'] = $q->count(); + + if ($data['total']) { + if (count($orders)) { + $q = $q->order($orders); + } + if ($page) { + $q = $q->page($page); + } + if ($limit == 0) { + $q = $q->limit(1000); + } else { + $q = $q->limit($limit); + } + + $data['list'] = $q->select(); + } + + return $data; + } + + /** + * 获取路径 + * + * @param int $pid + * @return string + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + */ + public static function getPath(int $pid): string + { + if ($pid == 0) { + $path = ',0,'; + } else { + $parent = self::findById($pid); + if (empty($parent)) { + $path = ',0,'; + } else { + $path = $parent['path'].$parent['id'].','; + } + } + + return $path; + } + + /** + * 刷新路径 + * + * @param int $pid + * @param array $data 默认全部 若data不为空 至少包含[id,path, $pidField] + * @param string $pidField 父级ID字段名 默认 pid + * @throws Exception + */ + public static function refreshPath(int $pid = 0, array $data = [], string $pidField = 'pid') + { + $data = !empty($data) ? $data : self::column('id,path,'.$pidField); + $updateAllPaths = []; + self::recursionPath($pid, $data, $updateAllPaths); + + (new static())->saveAll($updateAllPaths); + } + + /** + * 获取递归最新路径 + * + * @param int $pid + * @param array $data 全部数据 尽量传全部数据 + * @param array $paths + */ + public static function recursionPath(int $pid, array $data, array &$paths = []) + { + foreach ($data as $k => $v) { + if ($pid == $v['pid']) { + $arr = []; + $arr['id'] = $v['id']; + if ($pid == 0) { + $arr['path'] = ',0,'; + } else { + $arr['path'] = $paths[$v['pid']]['path'].$v['pid'].','; + } + $paths[$v['id']] = $arr; + + unset($data[$k]); + + self::recursionPath($v['id'], $data, $paths); + } + } + } + + /** + * 获取所有后代ID[包含孙级] 仅对拥有path字段的表生效 + * + * @param int $id + * @return array + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + */ + public static function getAllChildrenIds(int $id): array + { + $item = self::find($id); + if ($item && isset($item['path'])) { + $path = $item['path'].$id.','; + return self::where('path', 'like', $path.'%')->column('id'); + } else { + return []; + } + } +} \ No newline at end of file diff --git a/app/model/Block.php b/app/model/Block.php new file mode 100644 index 0000000..e9d38e4 --- /dev/null +++ b/app/model/Block.php @@ -0,0 +1,147 @@ + '富文本', + self::TEXT => '纯文本', + self::IMG => '图片', + self::GROUP => '多图', + self::FILE => '文件', + self::VIDEO => '视频', + self::ING_LIST => '图文列表', + ]; + } + + //根据键值和类目获取数据 + public static function getByKeyword($keyword, $categoryId) + { + return self::where('keyword', $keyword)->where('category_id', $categoryId)->findOrEmpty()->toArray(); + } + + //根据栏目ID获取块列表 + public static function getByCategoryId($categoryId) + { + + if($categoryId <= 0){ + return []; + } + + $items = self::where('category_id', $categoryId) + ->order('id desc') + ->select() + ->toArray(); + if(empty($items)){ + return []; + } + $data = []; + foreach($items as $item){ + $data[$item['name']] = $item; + } + return $data; + } + + //获取在使用中的文件 + public static function getFilesInUse() + { + $items = self::whereIn('type', [self::IMG, self::GROUP, self::VIDEO, self::TEXT]) + ->select() + ->toArray(); + $data = []; + foreach($items as $item){ + if($item['type'] == self::IMG){ + $file = trim($item['value']); + if(!empty($file)){ + $key = getKeyByPath($file); + $data[$key] = $file; + } + }elseif($item['type'] == self::GROUP){ + $val = trim($item['value']); + if (!empty($val) && $val != '[]' && $val != 'null') { + $files = json_decode($val, true); + foreach($files as $file){ + $src = trim($file['src']); + if(!empty($src)){ + $key = getKeyByPath($src); + $data[$key] = $src; + } + } + } + }elseif($item['type'] == self::VIDEO){ + $video = trim($item['value']); + if(!empty($video)){ + $key = getKeyByPath($video); + $data[$key] = $video; + } + $img = trim($item['img']); + if(!empty($img)){ + $key = getKeyByPath($img); + $data[$key] = $img; + } + }elseif($item['type'] == self::TEXT){ + $imgs = getImageUrlFromText($item['value']); + if(!empty($imgs)){ + $data = array_merge($data, $imgs); + } + $videos = getVideoUrlFromText($item['value']); + if(!empty($videos)){ + $data = array_merge($data, $videos); + } + } + } + return $data; + } + + /** + * 栏目 + * + * @return HasOne + */ + public function category(): HasOne + { + return $this->HasOne(ArchivesCategory::class, 'id', 'category_id'); + } + + /** + * 设置内容 + * */ + public function setContentAttr($value,$data) + { + if($data['type'] == self::ING_LIST){ + $content = array_column($value,null,"sort"); + $contentKey = array_keys($content); + sort($contentKey); + $contentData =[]; + foreach ($contentKey as $ck){ + $contentData[]= $content[$ck]; + } + return json_encode($contentData); + } + return $value; + } + + /** + * 设置内容 + * */ + public function getContentAttr($value,$data) + { + if($data['type'] == self::ING_LIST){ + return json_decode($value,true) + } + return $value; + } +} diff --git a/app/model/Config.php b/app/model/Config.php new file mode 100644 index 0000000..4aae7a5 --- /dev/null +++ b/app/model/Config.php @@ -0,0 +1,19 @@ +hasOne(Account::class, 'id', 'account_id'); + } + +} \ No newline at end of file diff --git a/app/model/File.php b/app/model/File.php new file mode 100644 index 0000000..2141df1 --- /dev/null +++ b/app/model/File.php @@ -0,0 +1,129 @@ + '图片', + 'video' => '视频', + 'file' => '文件' + ]; + } + + //获取文件列表 + public static function getList($type = '', $page = 1, $per = 20) + { + $limit = ($page - 1) * $per; + if ($type != '') { + if (!in_array($type, array_keys(self::getTypes()))) { + return []; + } + $items = self::where('type', $type) + ->order('id desc'); + } else { + $items = self::order('id desc'); + } + $items = $items->limit($limit, $per)->select()->toArray(); + foreach ($items as &$item) { + $item['sizeStr'] = sizeToStr($item['size']); + } + return $items; + } + + //获取分页列表 + public static function getListPage($type = '', $per = 20) + { + if ($type != '') { + if (!in_array($type, array_keys(self::getTypes()))) { + return []; + } + return self::where('type', $type) + ->order('id desc') + ->paginate([ + 'list_rows' => $per, + 'query' => [ + 'type' => $type + ] + ], false); + } else { + return self::order('id desc') + ->paginate([ + 'list_rows' => $per + ], false); + } + } + + //添加,$w_h图片尺寸大小,单位像素,只对type=img时有效 + public static function add($file, $src, $md5, $type = 'image') + { + $realPath = public_path().ltrim($src, '/'); + $oss = false; + if (is_file($realPath) && $type == 'image') { + $img = Image::open($realPath); + list($w, $h) = $img->size(); + $w_h = $w.'px * '.$h.'px'; + } else { + $w_h = ''; + } + + $now = date('Y-m-d H:i:s'); + Attachment::pathDirHandle($src); + + Config::load('extra/base', 'base'); + $baseConfig = config('base'); + if (isset($baseConfig['oss']) && $baseConfig['oss'] == 'true') { + $ossObject = AliOss::instance(); + try { + $pathInfo = pathinfo($src); + + $ossConfig = AliOss::config(); + $bucket = $ossConfig['bucket']; + //是否存在 + if (!$ossObject->doesObjectExist($bucket, ltrim($src, '/'))) { + //创建目录 + $ossObject->createObjectDir($bucket, ltrim($pathInfo['dirname'], '/')); + + $ossObject->uploadFile($bucket, ltrim($src, '/'), $realPath); + } + $oss = true; + } catch (Exception $e) { + \think\facade\Log::error('阿里云OSS上传文件失败 '.$e->getMessage()); + } + } + + // 将src中路径创建 + return self::create([ + 'type' => $type, + 'name' => $file->getOriginalName(), + 'md5' => $md5, + 'src' => $src, + 'path' => isset(pathinfo($src)['dirname']) ? pathinfo($src)['dirname'].'/' : '', + 'size' => $file->getSize(), + 'suffix' => $file->getOriginalExtension(), + 'mime_type' => $file->getOriginalMime(), + 'created_at' => $now, + 'updated_at' => $now, + 'is_oss' => $oss, + 'w_h' => $w_h + ]); + } + + //获取所有记录 + public static function getAll() + { + return self::select()->toArray(); + } +} diff --git a/app/model/Link.php b/app/model/Link.php new file mode 100644 index 0000000..ab8f602 --- /dev/null +++ b/app/model/Link.php @@ -0,0 +1,39 @@ +delete(); + } + + //获取友情链接 + public static function getList() + { + return self::order('sort asc') + ->select() + ->toArray(); + } + + public static function onAfterInsert($item) + { + $item->sort = $item->id; + $item->save(); + } + + //获取友情链接涉及到的文件 + public static function getFilesInUse() + { + $items = self::select()->toArray(); + $data = []; + foreach($items as $item){ + $src = trim($item['src']); + if(!empty($src)){ + $key = getKeyByPath($src); + $data[$key] = $src; + } + } + return $data; + } +} diff --git a/app/model/Log.php b/app/model/Log.php new file mode 100644 index 0000000..868791c --- /dev/null +++ b/app/model/Log.php @@ -0,0 +1,41 @@ + $auth['user_id'] ?? 0, + 'name' => $auth['username'] ?? 0, + 'ip' => request()->ip(), + 'create_time' => time(), + 'controller' => $controller, + 'request_type' => $requestType, + 'action' => $action, + 'content' => $content + ]); + } + + /** + * @return HasOne + */ + public function memberName(): HasOne + { + return $this->hasOne(Member::class, 'id', 'member_id')->bind(['operator' => 'nickname']); + } + + public function getCreateTimeAttr($value) + { + if (empty($value)) { + return $value; + } + + return date('Y-m-d H:i:s', $value); + } +} \ No newline at end of file diff --git a/app/model/LoginLog.php b/app/model/LoginLog.php new file mode 100644 index 0000000..35bb334 --- /dev/null +++ b/app/model/LoginLog.php @@ -0,0 +1,6 @@ +leftjoin('auth_group g', 'g.id=m.group_id') + ->field('m.id,m.username,m.login_time,m.group_id,g.title') + ->order('m.id', 'asc') + ->paginate($limit); + } + + /** + * 根据角色分组返回用户 + * @param int $groupId 角色分组ID + * @param int $limit 每页数量 + */ + public static function getListByGroup($groupId, $limit = 40) + { + return self::alias('m') + ->leftjoin('auth_group g', 'g.id=m.group_id') + ->field('m.id,m.username,m.login_time,m.group_id,g.title') + ->where('m.group_id', '=', $groupId) + ->order('m.id', 'asc') + ->paginate($limit); + } + + //根据用户名获取管理账号 + public static function getByUserName($username) + { + return self::where('username', trim($username)) + ->findOrEmpty() + ->toArray(); + } + + //根据ID获取管理账户和相关权限 + public static function getMemberAndRulesByID($memberId) + { + return self::alias('m') + ->join('auth_group g', 'm.group_id = g.id', 'LEFT') + ->field('m.group_id,g.rules') + ->where('m.id', $memberId) + ->findOrEmpty() + ->toArray(); + } + + public static function updateCates($id, $cates) + { + $cates = implode(',', $cates); + $data = ['cates' => $cates]; + self::updateById($id, $data); + } +} \ No newline at end of file diff --git a/app/model/Menu.php b/app/model/Menu.php new file mode 100644 index 0000000..3e60209 --- /dev/null +++ b/app/model/Menu.php @@ -0,0 +1,71 @@ + '查看', + 'add' => '添加', + 'edit' => '编辑', + 'del' => '删除', + 'sort' => '排序', + 'modify' => '属性设置', + ]; + } + + /** + * 自从生成常规操作权限 + * + * @param int $id + * @param string $name + * @param string $path + * @throws Exception + */ + public static function generate(int $id, string $name, string $path) + { + $actions = self::defaultAction(); + $delete = []; + $insert = []; + $created = date('Y-m-d H:i:s'); + foreach ($actions as $key => $action) { + $name = explode(':', $name)[0]; + $delete[] = $name.':'.$key; + + $arr = []; + $arr['title'] = $action; + $arr['pid'] = $id; + $arr['name'] = $name.':'.$key; + $arr['type'] = self::TYPE_ACTION; + $arr['path'] = $path.$id.','; + $arr['remark'] = sprintf("自动生成[%s][%s]操作", $name, $action); + $arr['created_at'] = $created; + + $insert[] = $arr; + } + + //删除已有常规操作 + self::where('pid', $id)->whereIn('name', $delete)->delete(); + + //新增常规操作 + (new self())->saveAll($insert); + } +} diff --git a/app/model/Model.php b/app/model/Model.php new file mode 100644 index 0000000..7bc8c1e --- /dev/null +++ b/app/model/Model.php @@ -0,0 +1,18 @@ +select() + ->toArray(); + } + public static function onAfterInsert($model) + { + $model->sort = $model->id; + $model->save(); + } +} \ No newline at end of file diff --git a/app/model/Role.php b/app/model/Role.php new file mode 100644 index 0000000..504c5a1 --- /dev/null +++ b/app/model/Role.php @@ -0,0 +1,8 @@ + '在售', + (string)self::STATUS_OFF => '已下架', + ]; + } + + public static function allowScoreTextList(): array + { + return [ + (string)self::ALLOW_ONLY_BUY => '购买', + (string)self::ALLOW_BUY_AND_SCORE => '购买或积分兑换', + (string)self::ALLOW_ONLY_SCORE => '积分兑换', + ]; + } + + public static function onAfterInsert(Model $item) + { + $item->sort = $item->id; + $item->save(); + } + + /** + * 模型关联:商品分类 + * + * @return HasOne + */ + public function category(): HasOne + { + return $this->hasOne(SkuCategory::class, 'id', 'category_id'); + } + + /** + * 生成 SKU coding + * 年份 + 2位随机码 + 时间戳(秒[10位] + 微妙[4位]) + * @length 18 + */ + public static function generateCoding(): string + { + $randStr = str_pad(mt_rand(1,99), 2, '0', STR_PAD_LEFT); + $prefix = date('y').$randStr; + $mt = microtime(true); + list($time, $micro) = explode('.', $mt); + $micro = str_pad($micro, 4, '0', STR_PAD_RIGHT); + $micro = substr($micro, 0, 4); + + return $prefix.$time.$micro; + } + + /** + * @return HasOne + */ + public function spu(): HasOne + { + return $this->hasOne(Spu::class, 'id', 'spu_id'); + } + + /** + * 关联活动商品 + * + * @return HasOne + */ + public function activity(): HasOne + { + return $this->hasOne(SpuActivity::class, 'id', 'spu_activity_id'); + } +} \ No newline at end of file diff --git a/app/model/Slide.php b/app/model/Slide.php new file mode 100644 index 0000000..b6b795f --- /dev/null +++ b/app/model/Slide.php @@ -0,0 +1,40 @@ +delete(); + } + + //获取幻灯片列表 + public static function getList() + { + return self::order("sort asc") + ->select() + ->toArray(); + } + + public static function onAfterInsert($slide) + { + $slide->sort = $slide->id; + $slide->save(); + } + + //获取轮播图涉及到的文件 + public static function getFilesInUse() + { + $items = self::select()->toArray(); + $data = []; + foreach ($items as $item) { + $src = trim($item['src']); + if (!empty($src)) { + $key = getKeyByPath($src); + $data[$key] = $src; + } + } + return $data; + } +} diff --git a/app/model/SlidePosition.php b/app/model/SlidePosition.php new file mode 100644 index 0000000..a598bf9 --- /dev/null +++ b/app/model/SlidePosition.php @@ -0,0 +1,17 @@ + $slideSize, + 'slide_mobile_size' => $slideMobileSize + ]; + } + //获取文章图片尺寸 + public static function getArticleImageSize() + { + $system = self::getSystem(); + if(!empty($system['article_w']) && !empty($system['article_h'])){ + $articleSize = $system['article_w'] . '像素 X ' . $system['article_h'] . '像素'; + }else{ + $articleSize = ''; + } + return $articleSize; + } + //获取系统配置信息 + public static function getSystem() + { + return self::order('id asc') + ->findOrEmpty() + ->toArray(); + } +} diff --git a/app/repository/AccountRepository.php b/app/repository/AccountRepository.php new file mode 100644 index 0000000..253f849 --- /dev/null +++ b/app/repository/AccountRepository.php @@ -0,0 +1,1070 @@ +findList(); + } + + /** + * 获取指定账户记录By手机号 + * + * @param string $phone + * @param array $fields + * @return Model|null + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + */ + public function infoByPhone(string $phone, array $fields = []): ?Model + { + $where[] = ['mobile', '=', $phone]; + return $this->findOneByWhere($where, $fields); + } + + /** + * 获取指定账户记录By用户名 + * + * @param string $username + * @param array $fields + * @return Model|null + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + */ + public function infoByUsername(string $username, array $fields = []): ?Model + { + $where[] = ['username', '=', $username]; + return $this->findOneByWhere($where, $fields); + } + + /** + * 混合查找记录 + * + * @param string $username 混合查询 手机号或用户名 + * @return array|Model|null + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + */ + public function findByHybrid(string $username) + { + return $this->model->whereOr('username', $username)->whereOr('mobile', $username)->find(); + } + + /** + * 通过微信小程序的openID查询 + * + * @param string $openID + * @return array|Model|null + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + */ + public function findByOpenID(string $openID) + { + return $this->model->where('openid', $openID)->find(); + } + + /** + * 通过个人邀请码查询用户信息 + * + * @param string $inviteCode + * @return array|Model|null + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + */ + public function findByInviteCode(string $inviteCode) + { + if (empty($inviteCode)) { + return null; + } + return $this->model->where('invite_code', $inviteCode)->find(); + } + + /** + * 通过个人编号查询用户信息 + * + * @param string $coding + * @return array|Model|null + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + */ + public function findByUserCoding(string $coding) + { + if (empty($coding)) { + return null; + } + return $this->model->where('coding', $coding)->find(); + } + + /** + * 修改密码 + * + * @param int $accountId + * @param string $oldPwd + * @param string $newPwd + * @return bool + * @throws RepositoryException + */ + public function modifyPwd(int $accountId, string $oldPwd, string $newPwd): bool + { + if (!$user = $this->findById($accountId)) { + throw new RepositoryException('用户不存在'); + } + + if ($user['password'] != md5($oldPwd)) { + throw new RepositoryException('原密码错误'); + } + + $user->password = md5($newPwd); + + return $user->save(); + } + + /********************************************** + * TODO 分割线,上述与本项目无关的代码需要清除 + *********************************************/ + + /** + * 修改用户数据 + * + * @param int $accountId + * @param array $fieldsKV 修改内容:键值对 + * @return mixed + * @throws RepositoryException + */ + public function accountEditInfo(int $accountId, array $fieldsKV) + { + $allowFields = ['headimgurl', 'real_name', 'nickname', 'mobile', 'gender', 'province', 'city', 'county', 'birthday']; + $fieldsKV = arrayKeysFilter($fieldsKV, $allowFields); + + if (isset($fieldsKV['mobile']) && !empty($fieldsKV['mobile'])) { + if (!checkMobile($fieldsKV['mobile'])) { + throw new RepositoryException('手机号不正确'); + } + } + + if (!$account = $this->findById($accountId)) { + throw new RepositoryException('用户信息错误'); + } + + return $account->save($fieldsKV); + } + + /** + * 统计用户分销人数(二级) + */ + public function shareAccountCount(int $accountId): array + { + $data = [ + 'first' => 0, + 'second' => 0, + 'total' => 0, + ]; + try { + $data['first'] = ShareRegLog::where('inviter_account_id', $accountId)->where('is_active', ShareRegLog::COMMON_ON)->count(); + $data['second'] = ShareRegLog::where('inviter_parent_account_id', $accountId)->where('is_active', ShareRegLog::COMMON_ON)->count(); + $data['total'] = $data['first'] + $data['second']; + } catch (Exception $e) { + + } + + return $data; + } + + + /** + * 添加分享注册关系绑定日志 + * + * @param int $accountId + * @param int $inviterId + * @param string $grade 分享绑定关系层级 + * @param bool $accountActive 是否为有效用户 + * @return bool + */ + public function addShareRegLogBak(int $accountId, int $inviterId, string $grade, bool $accountActive = true): bool + { + if ($accountId <= 0 || $inviterId <= 0) { + return false; + } + + Db::startTrans(); + try { + $shareConf = ExtraConfig::share(); + $score = $shareConf[$grade] ?? 0; + + $regLog = ShareRegLog::where('reg_account_id', $accountId) + ->where('inviter_account_id', $inviterId) + ->find(); + + $isCreate = false; + $isUpdate = false; + + if ($accountActive) { + if (!$regLog) { + $isCreate = true; + } elseif ($regLog['score'] == 0) { + // 用户激活后,获得积分 + $isUpdate = true; + } + + } else { + if ($regLog) { + throw new RepositoryException('已有绑定记录!'); + } else { + // 未有效激活的用户,相关绑定的人员只有在该用户成功激活后才能获得积分 + $score = 0; + $isCreate = true; + } + } + + switch ($grade) { + case self::SHARE_CURRENT: + $grade = ShareRegLog::SHARE_GRADE_FIRST; + break; + case self::SHARE_PARENT: + $grade = ShareRegLog::SHARE_GRADE_SECOND; + break; + case self::SHARE_SERVICE: + $grade = ShareRegLog::SHARE_GRADE_SERVICE; + break; + default: + return false; + } + + if ($isCreate) { + ShareRegLog::create([ + 'reg_account_id' => $accountId, + 'inviter_account_id' => $inviterId, + 'grade' => $grade, + 'score' => $score, + 'created_at' => date('Y-m-d H:i:s') + ]); + } elseif ($isUpdate) { + $regLog->save(['score' => $score]); + } + + if ($isCreate || $isUpdate) { + $inviter = $this->model->where('id', $inviterId)->lock(true)->find(); + if ($inviter) { + Account::updateById($inviterId, [ + 'score' => $inviter['score'] + $score + ]); + } + } + + Db::commit(); + } catch (RepositoryException | Exception $e) { + Db::rollback(); + return false; + } + + return true; + } + + /** + * 注册时邀请关系绑定 + * 存在邀请人时。邀请人、邀请人当前的客服、邀请人的邀请人三者均可获得相应积分 + * + * @param int $regAccountId 注册人ID + * @param int $inviterId 邀请人ID + * @param int $inviterParentAid 邀请人的邀请人ID + * @param int $serviceId 邀请人的当前客服ID + * @return bool + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + */ + public function addShareRegLog(int $regAccountId, int $inviterId, int $inviterParentAid = 0, int $serviceId = 0): bool + { + if ($regAccountId <= 0 || $inviterId <= 0) { + return false; + } + + $shareConf = ExtraConfig::share(); + + if (ShareRegLog::where('reg_account_id', $regAccountId)->find()) { + return true; + } + + ShareRegLog::create([ + 'reg_account_id' => $regAccountId, + 'inviter_account_id' => $inviterId, + 'inviter_parent_account_id' => $inviterParentAid, + 'inviter_service_account_id' => $serviceId, + 'score' => $shareConf[self::SHARE_CURRENT] ?? 0, + 'parent_score' => $shareConf[self::SHARE_PARENT] ?? 0, + 'service_score' => $shareConf[self::SHARE_SERVICE] ?? 0, + 'is_active' => ShareRegLog::COMMON_OFF, + 'created_at' => date('Y-m-d H:i:s'), + ]); + return true; + } + + /** + * 分享注册激活 【手机绑定时激活】 + * 激活时:1、日志变更 2、积分发放【邀请人、邀请人上级、邀请人客服】 3、积分日志添加 + * + * @param int $regAccountId + * @return bool + */ + public function activeShareRegLog(int $regAccountId): bool + { + if ($regAccountId <= 0) { + return false; + } + + Db::startTrans(); + try { + if (!$regLog = ShareRegLog::where('reg_account_id', $regAccountId)->find()) { + return false; + } + + if ($regLog['is_active'] == ShareRegLog::COMMON_ON) { + return true; + } + + ShareRegLog::where('reg_account_id', $regAccountId)->save( + ['is_active' => ShareRegLog::COMMON_ON, 'activated_at' => date('Y-m-d H:i:s')] + ); + + $accountFields = ['id', 'score']; + + // 邀请人积分添加 + if ($regLog['score'] > 0) { + $inviter = Account::findById($regLog['inviter_account_id'], $accountFields); + + AccountDataLog::log($regLog['inviter_account_id'], '分享注册', $regLog['score'], + AccountDataLog::TYPE_SCORE, AccountDataLog::ACTION_SHARE_REG, + $inviter['score'], '系统自动发放'); + $inviter->save( + ['score' => Db::raw('`score` + '.$regLog['score'])] + ); + } + + // 邀请人父级积分添加 + if ($regLog['inviter_parent_account_id'] > 0 && $regLog['parent_score'] > 0) { + $inviterParent = Account::findById($regLog['inviter_parent_account_id'], $accountFields); + + AccountDataLog::log($regLog['inviter_parent_account_id'], '分享注册-下级分享', $regLog['parent_score'], + AccountDataLog::TYPE_SCORE, AccountDataLog::ACTION_SHARE_REG_CHILD, + $inviterParent['score'], '系统自动发放'); + $inviterParent->save( + ['score' => Db::raw('`score` + '.$regLog['parent_score'])] + ); + } + + // 邀请人的客服积分添加 + if ($regLog['inviter_service_account_id'] > 0 && $regLog['service_score']) { + $inviterService = Account::findById($regLog['inviter_service_account_id'], $accountFields); + + AccountDataLog::log($regLog['inviter_service_account_id'], '分享注册-分享人客服获得积分', $regLog['service_score'], + AccountDataLog::TYPE_SCORE, AccountDataLog::ACTION_SHARE_REG_SERVICE, + $inviterService['score'], '系统自动发放'); + $inviterService->save( + ['score' => Db::raw('`score` + '.$regLog['service_score'])] + ); + } + + Db::commit(); + } catch (Exception $e) { + self::log($e); + Db::rollback(); + return false; + } + return true; + } + + /** + * 查看分享绑定的用户信息 + * + * @param int $accountId + * @param string $grade + * @param int $page + * @param int $size + * @return array|null + */ + public function shareUsers(int $accountId, string $grade, int $page = 1, int $size = 10): ?array + { + $data = [ + 'total' => 0, + 'current' => $page, + 'size' => $size, + 'list' => new Collection(), + ]; + + try { + if (!in_array($grade, [self::SHARE_GRADE_FIRST, self::SHARE_GRADE_SECOND])) { + throw new RepositoryException('层级参数错误'); + } + + $fields = ['id', 'real_name', 'nickname', 'headimgurl', 'invite_source', 'phone_active']; + $whereMap = []; + + switch ($grade) { + case self::SHARE_GRADE_FIRST: + $whereMap[] = ['inviter_account_id', '=', $accountId]; + break; + case self::SHARE_GRADE_SECOND: + $whereMap[] = ['inviter_parent_account_id', '=', $accountId]; + break; + } + $whereMap[] = ['is_active', '=', self::BOOL_TRUE]; + $orders = ['id' => 'desc']; + + $data = ShareRegLog::findList($whereMap, [], $page, $size, function ($q) use ($fields) { + return $q->with([ + 'account' => function ($q2) use ($fields) { + $q2->field($fields); + } + ]); + }, $orders); + + $inviteSourceTextList = self::inviteSourceList(); + foreach ($data['list'] as $item) { + $item['desc'] = ''; + $item['grade'] = $grade; + + if ($grade == self::SHARE_GRADE_SECOND) { + $item['score'] = $item['parent_score']; + } + + if ($grade == self::SHARE_GRADE_SERVICE) { + $item['score'] = $item['service_score']; + } + + unset($item['parent_score']); + unset($item['service_score']); + + $regAccount = $item['account']; + if ($regAccount) { + $regAccount['invite_source_desc'] = ''; + if ($regAccount['invite_source'] != self::INVITE_SOURCE_DEF) { + $regAccount['invite_source_desc'] = $inviteSourceTextList[$regAccount['invite_source']] ?? ''; + } + $regAccount['headimgurl'] = File::convertCompleteFileUrl($regAccount['headimgurl']); + } + $item['account'] = $regAccount; + } + + } catch (RepositoryException | Exception $e) { + + } + + return $data; + } + + /** + * 用户行为记录统计 + * 点赞、收藏、分享等 + * @param string $type + * @param string $action + * @param int $accountId + * @return int + */ + public function countRecordByAction(string $type, string $action, int $accountId = 0): int + { + if (!in_array($type, AccountRecord::allowTypes())) { + return 0; + } + + if (!in_array($action, AccountRecord::allowActions())) { + return 0; + } + + return AccountRecord::countByAction($type, $action, $accountId); + } + + /** + * 获取用户绑定的客服人员Id + */ + public function getBoundServiceAId(int $accountId) + { + try { + return CustomerReceive::getBoundService($accountId); + } catch (Exception $e) { + return 0; + } + } + + /** + * 获取用户绑定的客服人员 + */ + public function getBoundService(int $customerAId, string $action) + { + try { + return CustomerReceive::where('customer_aid', $customerAId) + ->where('action', $action) + ->find(); + } catch (Exception $e) { + return null; + } + } + + /** + * 获取客户接待流转记录 + * + * @param int $customerAId + * @param array $actions + * @return CustomerReceive[]|array|Collection + */ + public function findListCustomerReceive(int $customerAId, array $actions) + { + try { + return CustomerReceive::where('customer_aid', $customerAId) + ->whereIn('action', $actions) + ->select(); + } catch (Exception $e) { + return new Collection(); + } + } + + // 足迹列表 + public function footmarks(int $accountId, int $customerId = 0, string $keyword = '', int $page = 1, int $size = 20): array + { + $customerIds = []; + if (!$account = Account::findById($accountId)) { + throw new RepositoryException('账号信息不存在'); + } + if (!$account->is_staff) { + throw new RepositoryException('您不是员工'); + } + + $exclude = $accountId == $customerId ? 0 : $accountId; + + $rules = AccountRepository::getInstance()->getAccountRules($accountId); + $ruleChildren = in_array(AccountRule::RULE_FOOTMARKS, $rules);// 查看下级客户 + $ruleAll = in_array(AccountRule::RULE_VIEW_ALL_FOOTMARKS, $rules); // 查看所有足迹 + + // 查看指定人足迹 检查该员工是否可查看 + if (!$ruleChildren && !$ruleAll) { + throw new RepositoryException('权限不足'); + } + if ($customerId > 0) { + $customerIds[] = $customerId; + } + + if ($ruleAll) { + $accountId = 0;// 查看全部权限 则不指定员工account_id + } + + return AccountFootmarks::fetch($accountId, $customerIds, $keyword, $page, $size, $exclude); + } + + /** + * 客户列表 + * + * @param array $where + * @param array $field + * @param int $accountId + * @param int $page + * @param int $size + * @param callable|null $call + * @return array + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + * @throws RepositoryException + */ + public function customerList(array $where = [], array $field = [], int $accountId = 0, int $page = 1, int $size = 20, callable $call = null): array + { + if ($accountId > 0) { + if (!$staff = Staff::findOne(['account_id' => $accountId])) { + throw new RepositoryException('你不是员工'); + } + + $staffRules = explode(',', $staff['rules']); + + if (!in_array('customer-list', $staffRules)) { + throw new RepositoryException('请获取客户列表查看权限'); + } + + // 是否具有查看所有用户权限 + if (!in_array('view-all-customer-list', $staffRules)) { + // 查看自己的客户 + // 获取指定用户的客户 TODO 此处若下级过多 会有性能影响 + $customerIds = CustomerReceive::getChildrenIds($accountId); + + $where[] = ['id', 'in', $customerIds]; + } + } + + return $this->getAndHandleAccountList($where, $field, $page, $size, $call); + } + + /** + * 获取并处理用户列表 【后台用户列表】 + * + * @param array $where + * @param array $field 必传字段coin_total + * @param int $page + * @param int $size + * @param callable|null $call + * @return array|null + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + * @throws RepositoryException + */ + public function getAndHandleAccountList(array $where, array $field, int $page = 1, int $size = 20, callable $call = null): ?array + { + $items = self::findList($where, $field, $page, $size, function ($q) use ($call) { + if ($call !== null) { + $q = $call($q); + } + return $q + ->with(['tags', 'staff', 'customer', 'serviceList']) + ->order('id', 'desc'); + }); + + $levelList = AccountRepository::getInstance()->getLevelList(); + $items['list']->each(function ($item) use ($levelList) { + $item->channel_text = $this->model::channelTextList()[$item['channel']] ?? '未知来源'; + + $item->share_source = $item->is_staff > 0 ? ($item->staff->name ?? '') : ($item->customer->nickname ?? ''); + $item->is_sign_text = $item->is_sign ? '是' : '否'; + $item->tag = $item->tags->column('name'); + $item->service = $item->serviceList->name ?? ''; + $item->service_name = $item->serviceList->name ?? ''; + $item->source_detail = AccountRepository::getInstance()->getSourceDetail($item->id); + $item->levelInfo = AccountRepository::getInstance()->getCurrentLevel($item->coin_total, $levelList); + + $genderText = '保密'; + if ($item->gender == 1) { + $genderText = '男'; + } + if ($item->gender == 2) { + $genderText = '女'; + } + $item->gender_text = $genderText; + }); + + return $items; + } + + /** + * 获取账号详细来源 + * + * @param int $accountId + * @return string + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + * @throws RepositoryException + */ + public function getSourceDetail(int $accountId): string + { + if (!$account = $this->findById($accountId)) { + return ''; + } + + switch ($account['channel']) { + case AccountModel::CHANNEL_NORMAL: + return '自然流量进入'; + case AccountModel::CHANNEL_MEMBER: + // 员工信息 + if (!$source = Staff::findOne(['account_id' => $account['inviter_account_id']])) { + //员工不存在 查找用户信息 + if (!$source = AccountModel::findById($account['inviter_account_id'])) { + //用户不存在 + $member = '用户已不存在'; + } else { + $member = $source['name']; + } + } else { + $member = $source['name']; + } + return sprintf("员工【%s】分享进入", $member); + case AccountModel::CHANNEL_CUSTOMER: + //客户不存在 + $ddd = $account['inviter_account_id']; + if (!$source = AccountModel::findById($account['inviter_account_id'])) { + //用户不存在 + $member = '客户已不存在'; + } else { + $member = $source['nickname'].':'.$source['mobile']; + } + return sprintf("客户【%s】分享进入", $member); + case AccountModel::CHANNEL_ACTIVITY: + //活码 + if (!$source = Activity::findOne(['code' => $account['source_code']])) { + //用户不存在 + $member = '活码已不存在'; + } else { + $member = $source['name']; + } + return sprintf("通过活码【%s】进入", $member); + default: + return AccountModel::channelTextList()[$account['channel']] ?? '未知渠道'; + } + } + + /** + * 个人海报模板列表 + * + * @param int $accountId + * @param int $page + * @param int $size + * @return array + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + * @throws RepositoryException + */ + public function poster(int $accountId, int $page = 1, int $size = 10): array + { + if (!$account = Account::findById($accountId)) { + throw new RepositoryException('请先登录'); + } + + $list = []; + // 获取可用的会员海报 + $levelPoster = AccountLevel::where('value', '<', $account['coin_total'])->order('value', 'desc')->column('poster'); + + foreach ($levelPoster as $poster) { + $list = array_merge($list, explode(',', $poster)); + } + // 获取基础海报列表 + Config::load('extra/mini_program', 'mini_program'); + $conf = config('mini_program'); + $basePoster = $conf['poster'] ?? []; + $list = array_merge($list, $basePoster); + $list = array_filter($list); + return [ + 'total' => count($list), + 'current' => $page, + 'size' => $size, + 'list' => array_slice($list, ($page - 1) * $size, $size), + ]; + } + + /** + * @throws RepositoryException + */ + public function posterInfo(int $accountId, string $posterSrc, string $domain) + { + $bgImg = $posterSrc; + $bgImg = empty($bgImg) ? '' : File::convertCompleteFileUrl($bgImg); + + try { + if (!$account = AccountRepository::getInstance()->findById($accountId)) { + throw new RepositoryException('请先登录'); + } + + $inviteCode = $account['invite_code'] ?: md5($account['id']); + $inviteUrl = $domain.'/share?invite_code='.$inviteCode; + $logoImg = app()->getRootPath().'public/static/images/icon-logo.jpg'; + + $result = Builder::create() + ->writer(new PngWriter()) + ->writerOptions([]) + ->data($inviteUrl) + ->encoding(new Encoding('UTF-8')) + ->errorCorrectionLevel(new ErrorCorrectionLevelHigh()) + ->size(300) + ->margin(10) + ->roundBlockSizeMode(new RoundBlockSizeModeMargin()) + ->logoPath($logoImg) + ->logoResizeToHeight(100) + ->logoResizeToWidth(100) + ->logoPunchoutBackground(true) + ->build(); + + $dataUri = $result->getDataUri(); + return GdTool::generatePoster($dataUri, $bgImg); + } catch (Exception $e) { + AccountRepository::log('个人海报生成失败', $e); + throw $e; + } + } + + /** + * 获取个人日记 + * + * @param int $accountId + * @param int $page + * @param int $size + * @return array + * @throws Exception + */ + public function diary(int $accountId, int $page = 1, int $size = 10): array + { + $where = []; + $where[] = ['publish_account', '=', $accountId]; + // $where[] = ['diary_check', '=', Archives::COMMON_ON]; + + $field = []; + return Archives::findList($where, $field, $page, $size, null, ['sort' => 'desc', 'id' => 'desc']); + } + + /** + * 日记添加 + * + * @param array $params + * @return bool + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + * @throws RepositoryException + */ + public function diarySave(array $params): bool + { + $id = $params['id'] ?? 0; + $now = date('Y-m-d H:i:s'); + + if ($id > 0) { + // 修改 + $item = Archives::findById($id); + if ($item) { + $item->save($params); + } else { + $id = 0; + } + } + + $doctorArr = []; + if (isset($params['doctor_id'])) { + $doctorArr = explode(',', $params['doctor_id']); + } + + // 新建日记 + if ($id <= 0) { + $params['category_id'] = ArchivesCategory::where('name', ArchivesCategory::NAME_DIARY)->value('id'); + $params['created_at'] = $now; + $params['published_at'] = $now; + $params['created_by'] = $params['publish_account']; + + $diaryCollection = $this->getDiaryCollection($params['publish_account']); + if (!$diaryCollection) { + $diaryCollection = $this->createDiaryCollection($params['publish_account'], $params['disease_id'], $doctorArr); + } + + $params['diary_id'] = $diaryCollection['id']; + + $archives = Archives::create($params); + $id = $archives['id']; + } + + if ($doctorArr) { + //关联医生 + DoctorRelation::associateDoctor($doctorArr, $id, DoctorRelation::TYPE_CONTENT); + } + + return true; + } + + /** + * 删除日记 + * + * @param array $ids + * @return bool + */ + public function diaryDel(array $ids): bool + { + Archives::whereIn('id', $ids)->delete(); + // 删除关联医生 + DoctorRelation::whereIn('relation_id', $ids)->where('type', DoctorRelation::TYPE_CONTENT)->delete(); + return true; + } + + /** + * 日记详情 + * + * @param int $id + * @return array|Model|null + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + */ + public function diaryInfo(int $id) + { + return Archives::findById($id); + } + + /** + * 获取用户日记合集 + * + * @param int $accountId + * @return array|Diary|Model|null + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + */ + public function getDiaryCollection(int $accountId) + { + if (!$diary = Diary::where('account_id', $accountId)->find()) { + return null; + } + return $diary; + } + + /** + * 创建日记合集 + * + * @param int $accountId + * @param int $diseaseId + * @param array $doctorIds 关联医生 + * @return Diary|Model + * @throws RepositoryException + */ + public function createDiaryCollection(int $accountId, int $diseaseId, array $doctorIds = []) + { + if (!$account = AccountRepository::getInstance()->findById($accountId)) { + throw new RepositoryException('用户信息错误'); + } + + $now = date('Y-m-d H:i:s'); + $diary = Diary::create([ + 'title' => '日记合集-'.$account['nickname'], + 'disease_id' => $diseaseId, + 'user' => $account['nickname'], + 'headimg' => $account['headimgurl'], + 'created_at' => $now, + 'published_at' => $now, + 'account_id' => $accountId, + ]); + + if ($doctorIds) { + DoctorRelation::associateDoctor($doctorIds, $diary['id'], DoctorRelation::TYPE_DIARY); + } + return $diary; + } + + + /** + * 创建消息 支持推送订阅消息和短信 支持批量 + * + * @param array $item + * item[title]: 是生生世世 消息标题 + * item[type]: notice 消息类型 此处固定为notice 通知 + * item[target]: part 目标类型 all=所有人 part=部分人 + * item[subscribe_temp_id]: d0efR-Ga27c6eIvx9mAwJcnAqzhM_Sq68XiFvjvlBJM 订阅消息模版ID + * item[subscribe_data][thing1]: 事实上 订阅消息内容 subscribe_data根据模版变动参数 + * item[subscribe_data][time4]: 坎坎坷坷 + * item[subscribe_data][thing5]: 哈对方的身份 + * item[sms_temp_id]: SMS_231436568 短信模版ID + * item[content]: 收拾收拾 通知内容 + * + * @param array $targetList [13,15] 发送人ID列表 + * @throws GuzzleException + */ + public function createMessage(array $item, array $targetList) + { + $repo = AccountRepository::getInstance(); + + try { + $type = $item['type'] ?? ''; + $target = $item['target'] ?? ''; + + $subscribeData = $item['subscribe_data'] ?? []; + $smsData = $item['sms_data'] ?? []; + + $item["send_at"] = date('Y-m-d H:i:s'); + $item["content"] = $item['content']; + + $item['is_push'] = MessageModel::COMMON_ON; + $item['to_subscribe'] = (int) !empty($item['subscribe_temp_id']); + $item['to_sms'] = (int) !empty($item['sms_temp_id']); + + $item['subscribe_data'] = json_encode($subscribeData, JSON_UNESCAPED_UNICODE); + $item['sms_data'] = json_encode($smsData, JSON_UNESCAPED_UNICODE); + $message = $repo->addMessage($type, $target, $targetList, $item); + } catch (Exception $e) { + AccountRepository::log('创建消息通知失败 ', $e); + } + + try { + if ($item['to_sms']) { + // 批量发送短信 + $res = AccountRepository::getInstance()->smsSend($message->id, $item['sms_temp_id'], $targetList, $smsData); + // var_dump($res); + } + } catch (Exception $e) { + AccountRepository::log('短信发送失败 ', $e); + } + + try { + if ($item['to_subscribe'] > 0) { + // 订阅消息发送 + AccountRepository::getInstance()->subscribeSend($message->id, $item['subscribe_temp_id'], + $targetList, $subscribeData, + \app\model\Config::MINI_PATH_MESSAGE_CENTER); + } + } catch (Exception $e) { + AccountRepository::log('订阅消息发送失败 ', $e); + } + } + +} \ No newline at end of file diff --git a/app/repository/ArchivesRepository.php b/app/repository/ArchivesRepository.php new file mode 100644 index 0000000..8b3cb26 --- /dev/null +++ b/app/repository/ArchivesRepository.php @@ -0,0 +1,33 @@ +value('id'); + if ($categoryId) { + return Block::getByCategoryId($categoryId); + } + return []; + } +} \ No newline at end of file diff --git a/app/repository/CmsRepository.php b/app/repository/CmsRepository.php new file mode 100644 index 0000000..69140e5 --- /dev/null +++ b/app/repository/CmsRepository.php @@ -0,0 +1,75 @@ + 'aa', 'value' => 1, 'selected' => true, 'prefix' => '    ']] + * + * @param array $data 待处理的数据 + * @param string $symbol 分隔符号 默认   + * @param int $repeatNum 重复次数 默认4 + * @return array + */ + public function handleSelectedList(array $data, string $symbol = ' ', int $repeatNum = 4): array + { + $list = []; + foreach ($data as $item) { + $level = $item['level'] ?? 0; + $arr = $item; + $arr['children'] = $arr['children'] ?? []; + $arr['prefix'] = str_repeat($symbol, $level * $repeatNum); + $list[] = $arr; + } + + return $list; + } + + /** + * 获取后台用户权限列表 + * + * @param int $accountId + * @return array + */ + public function getUserRules(int $accountId): array + { + $rules = []; + $roles = Enforcer::getRolesForUser($accountId); + foreach ($roles as $role) { + $rules = array_merge($rules, Enforcer::getPermissionsForUser($role)); + } + + $ruleNameList = []; + foreach ($rules as $rule) { + if (isset($rule[2])) { + $ruleNameList[] = $rule[1].':'.$rule[2]; + } else { + $ruleNameList[] = $rule[1]; + } + } + + return array_unique($ruleNameList); + } +} \ No newline at end of file diff --git a/app/repository/CommonRepository.php b/app/repository/CommonRepository.php new file mode 100644 index 0000000..77cd60d --- /dev/null +++ b/app/repository/CommonRepository.php @@ -0,0 +1,112 @@ + $phone, + 'type' => $type, + 'code' => rand(100000, 999999), + 'created_at' => date('Y-m-d H:i:s', $now), + 'expired_at' => date('Y-m-d H:i:s', $now + 5 * 60), + ]); + + $res = Sms::send($phone, $res['code']); + if (is_bool($res)) { + return $res; + } + + Log::error(json_encode($res, JSON_FORCE_OBJECT)); + return false; + } catch (Exception $e) { + Log::error(sprintf("[发送短信验证码失败]%s:%d %s", $e->getFile(), $e->getLine(), $e->getMessage())); + return false; + } + } + + /** + * 检查短信验证码 + * + * @param string $phone + * @param string $code + * @param string $type + * @return bool + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + */ + public function checkSms(string $phone, string $code, string $type): bool + { + $item = (new SmsLog())->where('phone', $phone) + ->where('type', $type) + ->where('code', $code) + ->order('created_at', 'desc') + ->order('id', 'desc') + ->find(); + if (!$item) { + return false; + } + + if ($item['expired_at'] < date('Y-m-d H:i:s')) { + return false; + } + + return $item->save(['status' => self::SMS_STATUS_OK]); + } + + /** + * 日志记录 + * + * @param string $msg + * @param Exception|null $e + * @param string $level + * @param string $channel + */ + public static function log(string $msg, Exception $e = null, string $level = 'error', string $channel = 'file') + { + if ($e != null) { + $msg = sprintf("[%s]%s:%s %s", $msg, $e->getFile(), $e->getLine(), $e->getMessage()); + } else { + $msg = sprintf("%s", $msg); + + } + Log::channel($channel)->$level($msg); + } +} \ No newline at end of file diff --git a/app/repository/OperationRepository.php b/app/repository/OperationRepository.php new file mode 100644 index 0000000..47311e3 --- /dev/null +++ b/app/repository/OperationRepository.php @@ -0,0 +1,212 @@ +toArray(); + } catch (Exception $e) { + return []; + } + } + + /** + * 获取轮播 + * + * @param int $id + * @return array|Model|null + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + */ + public function findSlideById(int $id) + { + return Slide::findById($id); + } + + /** + * 轮播列表 + * + * @param array $where + * @param array $fields + * @param int $page + * @param int $size + * @param callable|null $call + * @param array $orders + * @return array + * @throws Exception + */ + public function slideList(array $where=[], array $fields=[], int $page=1, int $size=20, callable $call=null, array $orders=[]): array + { + return Slide::findList($where, $fields, $page, $size, $call, $orders); + } + + /** + * 更新轮播 + * + * @param array $data + * @param int $id + * @return bool + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + */ + public function updateSlide(array $data, int $id): bool + { + $item = (new Slide())->where('id', $id)->find(); + if ($item) { + return $item->save($data); + } + return false; + } + + /** + * 创建轮播 + * + * @param array $data + * @return Slide + */ + public function createSlide(array $data): Slide + { + $data['created_at'] = date('y-m-d H:i:s'); + return Slide::create($data); + } + + /** + * 删除轮播图 + * + * @param array $ids + * @return bool + */ + public function deleteSlides(array $ids): bool + { + return Slide::deleteByIds($ids); + } + + /** + * 轮播位置是否存在 + * + * @param string $position + * @param int $exceptId 需要排除的显示位置ID + * @return bool + */ + public function slidePositionExists(string $position, int $exceptId = 0): bool + { + return (new SlidePosition())->when($exceptId > 0, function ($q) use ($exceptId) { + $q->where('id', '<>', $exceptId); + })->where('key', $position)->count() > 0; + } + + /** + * 根据显示位置查询相关的轮播图信息 + * + * @param string $position 轮播图位置标识 + * @param int $size 限制查询数量 + * @throws Exception + */ + public function slideListByPosition(string $position, int $size=0): ?Collection + { + $where[] = ['position', '=', $position]; + $orders = ['sort'=>'asc']; + return Slide::findList($where, [], 1, $size, null, $orders)['list']; + } + + + /** + * 轮播显示位置列表 + * + * @param array $where + * @param array $fields + * @param int $page + * @param int $size + * @param callable|null $call + * @param array $orders + * @return array + * @throws Exception + */ + public function slidePositionList(array $where=[], array $fields=[], int $page=1, int $size=20, callable $call=null, array $orders=[]): array + { + return SlidePosition::findList($where, $fields, $page, $size, $call, $orders); + } + + /** + * 更新轮播位置 + * + * @param array $data + * @param int $id + * @return bool + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + */ + public function updateSlidePosition(array $data, int $id): bool + { + $item = (new SlidePosition())->where('id', $id)->find(); + if ($item) { + $item->save($data); + } + return false; + } + + /** + * 创建轮播位置 + * + * @param array $data + * @return SlidePosition + */ + public function createSlidePosition(array $data): SlidePosition + { + $data['created_at'] = date('y-m-d H:i:s'); + return SlidePosition::create($data); + } + + /** + * 删除轮播位置 + * + * @param array $ids + * @return bool + */ + public function deleteSlidePositions(array $ids): bool + { + return SlidePosition::deleteByIds($ids); + } + + /** + * 获取轮播位置 + * + * @param int $id + * @return array|Model|null + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + */ + public function findSlidePositionById(int $id) + { + return SlidePosition::findById($id); + } + +} \ No newline at end of file diff --git a/app/service.php b/app/service.php new file mode 100644 index 0000000..8c2052c --- /dev/null +++ b/app/service.php @@ -0,0 +1,9 @@ +getMessage()); + return null; + } + } + return self::$oss; + } +} \ No newline at end of file diff --git a/app/service/Alipay.php b/app/service/Alipay.php new file mode 100644 index 0000000..ff0b4aa --- /dev/null +++ b/app/service/Alipay.php @@ -0,0 +1,64 @@ + trim($conf['appId']), + 'notify_url' => trim($conf['notify_url']), + 'return_url' => trim($conf['return_url']) ?? trim($conf['notify_url']), + 'ali_public_key' => trim($conf['aliPubKey']),//注意 这里的是支付宝的公钥 + // 加密方式: **RSA2** + 'private_key' => trim($conf['priKey']), + // 使用公钥证书模式,请配置下面两个参数,同时修改ali_public_key为以.crt结尾的支付宝公钥证书路径, + // 如(./cert/alipayCertPublicKey_RSA2.crt) + // 'app_cert_public_key' => './cert/appCertPublicKey.crt', //应用公钥证书路径 + // 'alipay_root_cert' => './cert/alipayRootCert.crt', //支付宝根证书路径 + 'log' => [ // optional + 'file' => './logs/alipay.log', + 'level' => 'debug', // 建议生产环境等级调整为 info,开发环境为 debug + 'type' => 'single', // optional, 可选 daily. + 'max_file' => 30, // optional, 当 type 为 daily 时有效,默认 30 天 + ], + 'http' => [ // optional + 'timeout' => 5.0, + 'connect_timeout' => 5.0, + // 更多配置项请参考 [Guzzle](https://guzzle-cn.readthedocs.io/zh_CN/latest/request-options.html) + ], + // 'mode' => 'dev', // optional,设置此参数,将进入沙箱模式 + ]; + } + + //支付宝支付实例 单例模式 + public static function getInstance(): ?\Yansongda\Pay\Gateways\Alipay + { + if (self::$app == null) { + self::$app = Pay::alipay(self::config()); + } + return self::$app; + } +} \ No newline at end of file diff --git a/app/service/DxtcPage.php b/app/service/DxtcPage.php new file mode 100644 index 0000000..9a5a405 --- /dev/null +++ b/app/service/DxtcPage.php @@ -0,0 +1,204 @@ +currentPage() <= 1) { + return ('上一页'); + } + + $url = $this->url( + $this->currentPage() - 1 + ); + +// return ($this->getPageLinkWrapper($url, "上一页" ,"") .$this->getPageLinkWrapper($url, $text ,"prev")); + return ($this->getPageLinkWrapper($url, "上一页" ,"prev")); + } + + /** + * 下一页按钮 + * @param string $text + * @return string + */ + protected function getNextButton(string $text = '>'): string + { + if (!$this->hasMore) { + //return (''); + return ('下一页'); + } + + $url = $this->url($this->currentPage() + 1); + + return ($this->getPageLinkWrapper($url, "下一页",'next')) ; + } + + /** + * 页码按钮 + * @return string + */ + protected function getLinks(): string + { + if ($this->simple) { + return ''; + } + + $block = [ + 'first' => null, + 'slider' => null, + 'last' => null, + ]; + + $side = 3; + $window = $side * 1; + + if ($this->lastPage < $window + 3) { + $block['first'] = $this->getUrlRange(1, $this->lastPage); + } elseif ($this->currentPage <= $window) { + $block['first'] = $this->getUrlRange(1, $window ); + $block['last'] = $this->getUrlRange($this->lastPage - 1, $this->lastPage); + } elseif ($this->currentPage > ($this->lastPage - $window)) { + $block['first'] = $this->getUrlRange(1, 2); + $block['last'] = $this->getUrlRange($this->lastPage - ($window ), $this->lastPage); + } else { + $block['first'] = $this->getUrlRange(1, 2); + $block['slider'] = $this->getUrlRange($this->currentPage , $this->currentPage ); + $block['last'] = $this->getUrlRange($this->lastPage - 1, $this->lastPage); + } + + $html = ''; + + if (is_array($block['first'])) { + $html .= $this->getUrlLinks($block['first']); + } + + if (is_array($block['slider'])) { + $html .= $this->getDots(); + $html .= $this->getUrlLinks($block['slider']); + } + + if (is_array($block['last'])) { + $html .= $this->getDots(); + $html .= $this->getUrlLinks($block['last']); + } + + return $html; + } + + /** + * 渲染分页html + * @return mixed + */ + public function render($linkStr='') + { + $this->linkStr=$linkStr; + if ($this->hasPages()) { + if ($this->simple) { + return sprintf( + '
%s %s
', + $this->getPreviousButton(), + $this->getNextButton() + ); + } else { + return sprintf( + '
%s %s %s
', + $this->getPreviousButton(), + $this->getLinks(), + $this->getNextButton() + ); + } + } + } + + /** + * 生成一个可点击的按钮 + * + * @param string $url + * @param string $page + * @return string + */ + protected function getAvailablePageWrapper(string $url, string $page,$class=""): string + { + //return '
  • ' . $page . '
  • '; + return '' . $page . ''; + } + + /** + * 生成一个禁用的按钮 + * + * @param string $text + * @return string + */ + protected function getDisabledTextWrapper(string $text,$class=""): string + { + //return '
  • ' . $text . '
  • '; + return '' . $text . ''; + } + + /** + * 生成一个激活的按钮 + * + * @param string $text + * @return string + */ + protected function getActivePageWrapper(string $text,$class=""): string + { + //return '
  • ' . $text . '
  • '; + return '' . $text . ''; + } + + /** + * 生成省略号按钮 + * + * @return string + */ + protected function getDots(): string + { + return $this->getDisabledTextWrapper('...'); + } + + /** + * 批量生成页码按钮. + * + * @param array $urls + * @return string + */ + protected function getUrlLinks(array $urls): string + { + $html = ''; + + foreach ($urls as $page => $url) { + $html .= $this->getPageLinkWrapper($url, $page); + } + + return $html; + } + + /** + * 生成普通页码按钮 + * + * @param string $url + * @param string $page + * @return string + */ + protected function getPageLinkWrapper(string $url, string $page ,$class=""): string + { + if ($this->currentPage() == $page) { + return $this->getActivePageWrapper($page,'active'); + } + + return $this->getAvailablePageWrapper($url, $page,$class); + } +} diff --git a/app/service/Excel.php b/app/service/Excel.php new file mode 100644 index 0000000..8a1c7ab --- /dev/null +++ b/app/service/Excel.php @@ -0,0 +1,201 @@ + [ + 'name' => '宋体', + ], + 'alignment' => [ + 'horizontal' => Alignment::HORIZONTAL_CENTER, // 水平居中 + 'vertical' => Alignment::VERTICAL_CENTER, // 垂直居中 + 'wrapText' => true, + ], + 'borders' => [ + 'allBorders' => [ + 'borderStyle' => Border::BORDER_THIN, + 'color' => ['rgb' => 'eeeeee'], + ] + ], + ]; + + public static array $defaultSetting = [ + 'cell_width' => 30, // 默认列宽 + 'font_size' => 12, // 默认excel内容字体大小 + ]; + + //导出 + static public function export($spreadsheet,$filename) + { + $writer = new Xlsx($spreadsheet); + header("Content-Type: application/force-download"); + header("Content-Type: application/octet-stream"); + header("Content-Type: application/download"); + header('Content-Disposition:inline;filename="'.$filename.'"'); + header("Content-Transfer-Encoding: binary"); + header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); + header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); + header("Cache-Control: must-revalidate, post-check=0, pre-check=0"); + header("Pragma: no-cache"); + $writer->save('php://output'); + $spreadsheet->disconnectWorksheets(); + unset($spreadsheet); + exit; + } + + public static function cancelTimeLimit() + { + ini_set('max_execution_time', '0'); + ini_set("memory_limit", '-1'); + set_time_limit(0); + } + + /** + * 根据header字段获取可配置列的cellId列表 + * 默认前26列为可配置列A~Z, $headerLength不能超过26 * 27 = 702 + * + * @param int $headerLength + * @return array + */ + public static function getCellIds(int $headerLength): array + { + $defaultCellIds = range('A', 'Z', 1); + $cellIds = $defaultCellIds; + $loop = ceil($headerLength / 26); + if($loop>1) { + $maxPrefixIndex = ($loop - 2) >= 25 ? 25 : ($loop - 2); + for ($prefixIndex = 0; $prefixIndex<= $maxPrefixIndex; $prefixIndex++) { + $prefix = $defaultCellIds[$prefixIndex]; + $cellIds = array_merge($cellIds, array_map(function ($val) use($prefix) { + return $prefix.$val; + }, $defaultCellIds)); + } + } + return $cellIds; + } + + /** + * 设置导出表数据 + * + * @param Worksheet $sheet 工作表对象 + * @param array $cellValues 数据信息,二维数组(ps:若字段值需要指定样式以数组格式传递,如[$val, DataType::TYPE_STRING])。第二行开始填充 + * @param array $header 表头信息, 第一行为表头 + * @param string $sheetTitle 工作表标题 + * @param array $cellWidthList 列宽样式,键值对,键名为列序号(从0开始计算) + * @param array $excelStyle 表数据样式 + * @param array $defaultList 默认设置样式 + * @param array $cellWrapList 列换行样式 + * @return bool + */ + public static function setExcelCells(Worksheet $sheet, array $cellValues, array $header, string $sheetTitle = '数据列表', $cellWidthList = [], $excelStyle = [], $defaultList = [], $cellWrapList = []): bool + { + $defaultStyle = [ + 'font' => [ + 'name' => '宋体', + ], + 'alignment' => [ + 'horizontal' => Alignment::HORIZONTAL_CENTER, // 水平居中 + 'vertical' => Alignment::VERTICAL_CENTER, // 垂直居中 + 'wrapText' => false, + ], + 'borders' => [ + 'allBorders' => [ + 'borderStyle' => Border::BORDER_THIN, + 'color' => ['rgb'=>'eeeeee'], + ] + ], + ]; + $headerLength = count($header); + + if($headerLength === 0) return false; + $cellIds = self::getCellIds($headerLength); + $lastCellId = $cellIds[$headerLength-1]; + $contentIndex = 2; + foreach ($cellValues as $n => $itemCell) { + foreach ($itemCell as $i => $cellValue) { + if(is_array($cellValue)) { + $sheet->setCellValueExplicit($cellIds[$i] . $contentIndex, ...$cellValue); + } else { + $sheet->setCellValue($cellIds[$i] . $contentIndex, $cellValue); + } + } + $contentIndex++; + } + + try { + $headerStr = 'A1:' . $lastCellId.'1'; + $bodyStr = 'A2:' . $lastCellId . ($contentIndex - 1); + + $defaultWidth = 24; // 默认列宽24个字符 + $fontSize = 12; // 默认字体大小为12号 + if(!empty($defaultList)) { + if(isset($defaultList['cell_width']) && is_numeric($defaultList['cell_width']) && $defaultList['cell_width'] > 0) { + $defaultWidth = $defaultList['cell_width']; + } + if(isset($defaultList['font_size']) && is_numeric($defaultList['font_size']) && $defaultList['font_size'] > 0) { + $fontSize = $defaultList['font_size']; + } + } + + + $sheet->setTitle(empty($sheetTitle) ? '数据列表': $sheetTitle); + $sheet->getDefaultColumnDimension()->setAutoSize($fontSize); + $sheet->getStyle($headerStr)->getFont()->setBold(false)->setSize($fontSize+2); + $sheet->getDefaultColumnDimension()->setWidth($defaultWidth); + $sheet->fromArray($header, null, 'A1'); + $sheet->getStyle($headerStr)->applyFromArray($defaultStyle); + $sheet->getStyle($bodyStr)->applyFromArray(empty($excelStyle) ? $defaultStyle : $excelStyle); + + // 自定义列宽 + if(!empty($cellWidthList)) { + foreach ($cellWidthList as $cellId => $widthVal) { + if(isset($cellIds[$cellId]) && is_numeric($widthVal) && $widthVal > 0) { + $sheet->getColumnDimension($cellIds[$cellId])->setWidth($widthVal); + } + } + } + + //自定义列是否换行 + if(!empty($cellWrapList)) { + foreach ($cellWrapList as $cellId => $boolVal) { + if(isset($cellIds[$cellId])) { + $wrap = $boolVal ? true : false; + $sheet->getStyle($cellIds[$cellId])->getAlignment()->setWrapText($wrap); + } + } + } + + return true; + } catch (\Exception $e) { + return false; + } + } + + // excel导入时间转换 + public static function getExcelTime($timeStr, $format = 'Y-m-d') + { + $timezone = ini_get('date.timezone'); + $timeStr = trim($timeStr); + if (!empty($timeStr)) { + if (is_numeric($timeStr)) { + $toTimestamp = EDate::excelToTimestamp($timeStr, $timezone); + $timeStr = date($format, $toTimestamp); + } else { + $toTimestamp = strtotime($timeStr); + $timeStr = ($toTimestamp === false) ? '' : date($format, $toTimestamp); + } + } else { + $timeStr = ''; + } + return $timeStr; + } +} \ No newline at end of file diff --git a/app/service/ExtraConfig.php b/app/service/ExtraConfig.php new file mode 100644 index 0000000..53dac3d --- /dev/null +++ b/app/service/ExtraConfig.php @@ -0,0 +1,62 @@ +getRootPath() . $upload_path; + $filename = uniqid() . '.' . $file->extension(); + $upload_filename = '/' . $upload_path . '/' . $filename; + return [$file->move($path, $filename), $file, $upload_filename]; + } + + /** + * 文件访问路径转换为完整的url + * @param string|null $fileUrl + * @param bool $ossAnalysis 是否进行OSS解析 + * @return string + * @todo 若启用OOS存储,需根据业务配置调整$fileDomain + * + */ + public static function convertCompleteFileUrl(?string $fileUrl, bool $ossAnalysis=true): string + { + if (empty($fileUrl)) { + return ''; + } + if ($ossAnalysis) { + $fileDomain = self::getFileDomain(); + } else { + $fileDomain = request()->domain(); + } + $prefix = substr($fileUrl, 0, 4); + if (!($prefix == 'http')) { + $fileUrl = $fileDomain.'/'.ltrim($fileUrl, '/'); + } + + return $fileUrl; + } + + /** + * 文件访问域名前缀 + * + * @return string + */ + public static function getFileDomain(): string + { + $confBase = ExtraConfig::base(); + $confOss = ExtraConfig::aliOss(); + $isOss = $confBase['oss'] ?? 'false'; + $ossDomain = $confOss['customDomain'] ?? ''; + + // 默认为当前域名 + $fileDomain = request()->domain(); + if ($isOss == 'true' && !empty($ossDomain)) { + $fileDomain = $ossDomain; + } + + $fileDomain = trim($fileDomain); + return rtrim($fileDomain, '/'); + } +} \ No newline at end of file diff --git a/app/service/GdTool.php b/app/service/GdTool.php new file mode 100644 index 0000000..35c9bef --- /dev/null +++ b/app/service/GdTool.php @@ -0,0 +1,140 @@ +getRootPath().'public/static/images/poster-bg1.png'; + $srcBga = empty($bgImg) ? $defBga : $bgImg; + $bgInfo = @getimagesize($srcBga); + if ($bgInfo[0] != 750 || $bgInfo[1] != 1334) { + throw new \Exception('海报模板尺寸不正确!'); + } + + if (!$bgInfo) { + throw new \Exception('海报背景图资源不存在!'); + } + + $ext = $bgInfo['mime']; + $extName = ''; + $img = null; + switch ($ext) { + case 'image/jpeg': + $img = @imagecreatefromjpeg($srcBga); + $extName = 'jpg'; + break; + case 'image/png': + $img = @imagecreatefrompng($srcBga); + $extName = 'png'; + break; + } + if (!$img) { + throw new \Exception('无效背景图'); + } + + //2、准备颜色 + $black = imagecolorallocate($img,0,0,0); + $while = imagecolorallocate($img,255,255,255); + $faColor = imagecolorallocate($img,0,104,51); + + // 邀请人头像 +// $headimgurl = 'https://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTJXE3Zz0U5edXYI2icicYibSNwwezWe0X92fovRtpUwdCF5lAmjsYK5EWT3R8ItO0BEqynElYhWibRqDg/132'; +// $headimg = imagecreatefromstring(file_get_contents($headimgurl)); +// imagecopymerge($img, $headimg, 50, 1000, 0, 0, 120, 120, 100); +// imagecopyresampled($img, $headimg, 90, 900, 0, 0, 120, 120, 120, 120); + + // 添加文字 +// imagettftext($img, 18, 0, 220, 1100, 250, public_path().'static/simheittf.ttf', '超级凉面...邀您关注'); + + //填充画布(背景色) + imagefill($img,0,0, $while); + + // 组合二维码图片 ,坐标:x:246; y:959 + $prefixPng = 'data:image/png;base64,'; + $qrStr = base64_decode(str_replace($prefixPng,"",$srcQr)); + $qrImg = @imagecreatefromstring($qrStr); + list($qrWidth, $qrHeight) = getimagesize($srcQr); + if(!$qrImg) { + imagedestroy($img); + throw new \Exception('无效二维码'); + } + $imgQrW = $imgQrH = 274; + imagecopyresampled($img, $qrImg, 456, 959, 0, 0, $imgQrW, $imgQrH, $qrWidth, $qrHeight); + + imagedestroy($qrImg); + //4、输出与保存最终图像(保存文件或返回base64) + + + if (empty($savePath)) { + /* 返回base64 */ + ob_start(); + if ($ext == 'image/jpeg') { + imagejpeg($img); + } else { + imagepng($img); + } + + $imgData = ob_get_contents(); + ob_end_clean(); + imagedestroy($img); + + $prefix = 'data:image/jpg/png/gif;base64,'; + return $prefix.base64_encode($imgData); + + } else { + /* 保存到文件*/ + $fileName = md5(microtime(true)).'.'.$extName; + $saveFile = $savePath."/".$fileName; + if ($ext == 'image/jpeg') { + imagejpeg($img, $saveFile); + } else { + imagepng($img, $saveFile); + } + } + + /* + * 输出显示 + header("content-type: image/png"); + imagepng($img); + */ + + //5、释放画布资源 + imagedestroy($img); + } catch (\Exception $e) { + throw $e; + } + + return true; + } +} \ No newline at end of file diff --git a/app/service/Image.php b/app/service/Image.php new file mode 100644 index 0000000..d653ce2 --- /dev/null +++ b/app/service/Image.php @@ -0,0 +1,130 @@ +getRootPath() . 'public/' . ltrim($src,'/'); + if(is_file($realPath)){ + $img = TImage::open($realPath); + list($img_w,$img_h) = $img->size(); + if($max > 0 && $img_w > $max){ + $img->thumb($max, $max * ($img_h / $img_w))->save($realPath); + } + } + } + + /** + * 添加水印 + * milo + * 2018-01-17 + */ + public static function mark($src) + { + $rootPath = app()->getRootPath(); + if(!empty($src)){ + $system = System::getSystem(); + $realPath = $rootPath . 'public/' . ltrim($src, '/'); + if(is_file($realPath)){ + if($system['is_mark']){ + $mark = $rootPath . ltrim($system['mark_img'], '/'); + if(is_file($mark)){ + $mark_position = $system['mark_position']??5; + $mark_opacity = $system['mark_opacity']??50; + $img = TImage::Open($realPath); + $img->water($mark,$mark_position,$mark_opacity)->save($realPath); + } + } + } + } + } + + //获取水印位置键值对 + public static function getMarkPosition() + { + return [ + "1" => '上左', + "2" => '上中', + "3" => '上右', + "4" => '中左', + "5" => '正中', + "6" => '中右', + "7" => '下左', + "8" => '下中', + "9" => '下右' + ]; + } + + /** + * 删除图片 + * milo + * 2018-01-15 + */ + public static function delImg($src) + { + if(!empty(trim($src))){ + $realPath = app()->getRootPath() . 'public/' . ltrim($src, '/'); + if (file_exists($realPath)) { + $info = pathinfo($realPath); + $source = $info['dirname'] . "/" . $info['filename'] . '*.' . $info['extension']; + foreach(glob($source) as $filename){ + if(is_file($filename)){ + unlink($filename); + } + } + clearstatcache();// 清除缓存 + } + + } + } + /** + * 获取缩略图 + * milo + * 2019-10-24修改 + * 避免跨平台出错,目录分隔符全部转换为'/' + * app()->getRuntimePath() = app()->getRootPath().'runtime/当前应用模块(api)/' + */ + public static function getThumb($src,$width=0,$height=0,$type = TImage::THUMB_CENTER) + { + if(empty($src)){ + return ''; + } + $rootPath = app()->getRootPath(); + $realPath = $rootPath . 'public/' . ltrim($src, '/'); + $realPath = str_replace('\\', '/', $realPath); + if(!file_exists($realPath)){ + return ''; + } + $info = pathinfo($src); + if($width <= 0 && $height <= 0){ //高宽都小于或等于0,则返回原图片 + return $src; + } + $image = TImage::open($realPath); + list($imageWidth, $imageHeight) = $image->size(); + if($width <= 0){ + $width = floor($height * ($imageWidth / $imageHeight)); + }elseif($height <= 0){ + $height = floor($width * ($imageHeight / $imageWidth)); + } + if($width >= $imageWidth || $height >= $imageHeight){ + return $src; + } + $thumbName = $info['dirname']. "/" .$info['filename'].'_'.$width.'_'.$height.'.'.$info['extension']; + $realThumbName = $rootPath . 'public/' . ltrim($thumbName, '/'); + $realThumbName = str_replace('\\', '/', $realThumbName); + if(!file_exists($realThumbName)){ + $image->thumb($width, $height, $type)->save($realThumbName); + } + return str_replace('\\', '/', $thumbName); + } +} diff --git a/app/service/Jwt.php b/app/service/Jwt.php new file mode 100644 index 0000000..ef25d15 --- /dev/null +++ b/app/service/Jwt.php @@ -0,0 +1,133 @@ +builder() + // Configures the issuer (iss claim) + ->issuedBy(self::$iss) + // Configures the audience (aud claim) + ->permittedFor(self::$aud) + // Configures the id (jti claim) + // ->identifiedBy($this->jti) + // Configures the time that the token was issue (iat claim) + ->issuedAt($now) + // Configures the expiration time of the token (exp claim) + ->expiresAt($now->modify(sprintf('+%d seconds', $expire))) + // Configures a new claim, called "uid" + ->withClaim('data', $data) + // Configures a new header, called "foo" + // ->withHeader('foo', 'bar') + // Builds a new token + ->getToken(self::config()->signer(), self::config()->signingKey()); + + return $token->toString(); + } + + /** + * 解析 + * + * @param string $tokenStr + * @return array|mixed + */ + public static function parse(string $tokenStr) + { + $config = self::config(); + + try { + $token = $config->parser()->parse($tokenStr); + assert($token instanceof UnencryptedToken); + return $token->claims()->all()['data'] ?? []; + } catch (Exception $e) { + return []; + } + } + + /** + * 验证token + * + * @param string $tokenStr + * @return bool + */ + public static function validate(string $tokenStr): bool + { + $config = self::config(); + try { + $token = $config->parser()->parse($tokenStr); + assert($token instanceof UnencryptedToken); + + //验证签发人iss是否正确 + $validateIssued = new IssuedBy(self::$iss); + $config->setValidationConstraints($validateIssued); + //验证客户端aud是否匹配 + $validateAud = new PermittedFor(self::$aud); + $config->setValidationConstraints($validateAud); + + //验证是否过期 exp + $timezone = new DateTimeZone('Asia/Shanghai'); + $now = new SystemClock($timezone); + $validateExpired = new LooseValidAt($now); + $config->setValidationConstraints($validateExpired); + + $constraints = $config->validationConstraints(); + + return $config->validator()->validate($token, ...$constraints); + } catch (Exception $e) { + return false; + } + } +} \ No newline at end of file diff --git a/app/service/Kd100.php b/app/service/Kd100.php new file mode 100644 index 0000000..fe68dd4 --- /dev/null +++ b/app/service/Kd100.php @@ -0,0 +1,79 @@ + $com, //快递公司编码, 一律用小写字母 + 'num' => $num, //快递单号 + // 'phone' => '', //手机号 + // 'from' => '', //出发地城市 + // 'to' => '', //目的地城市 + // 'resultv2' => '1' //开启行政区域解析 + ]; + + //请求参数 + $post_data = []; + $post_data["customer"] = self::$customer; + $post_data["param"] = json_encode($param); + $sign = md5($post_data["param"].self::$key.$post_data["customer"]); + $post_data["sign"] = strtoupper($sign); + + $params = ""; + foreach ($post_data as $k => $v) { + $params .= "$k=".urlencode($v)."&"; //默认UTF-8编码格式 + } + $post_data = substr($params, 0, -1); + + //发送post请求 + $ch = curl_init(); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_HEADER, 0); + curl_setopt($ch, CURLOPT_URL, self::$url); + curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + $result = curl_exec($ch); + return json_decode($result, $returnArray); + } + + /** + * 状态 + * + * @return string[] + */ + public static function state(): array + { + return [ + 0 => '在途', + 1 => '揽收', + 2 => '疑难', + 3 => '签收', + 4 => '退签', + 5 => '派件', + 6 => '退回', + 7 => '转单', + 10 => '待清关', + 11 => '清关中', + 12 => '已清关', + 13 => '清关异常', + 14 => '收件人拒签', + ]; + } + + /** + * 不在继续查询的配送状态 + * @return int[] + */ + public static function unSearchState(): array + { + return [3,4,12]; + } +} diff --git a/app/service/Math.php b/app/service/Math.php new file mode 100644 index 0000000..37e6a67 --- /dev/null +++ b/app/service/Math.php @@ -0,0 +1,60 @@ +model = $model; + } elseif (strpos($class, '\\repository') > 0) { + $model = str_replace('\\repository', '\\model', $class); + //去掉末尾Repository app\model\AccountRepository =》 app\model\Account + $model = substr($model, 0, strlen($model) - strlen('Repository')); + if (class_exists($model)) { + $obj->model = new $model; + } + } + + self::$objects[$class] = $obj; + return $obj; + } + + /** + * @param callable $callback + * @param mixed $failReturn + * @param bool $transaction + * @return mixed + * @throws RepositoryException + */ + protected function access(callable $callback, $failReturn, bool $transaction = false) + { + $exception = null; + try { + if ($transaction) { + Db::startTrans(); + } + + $r = $callback(); + + if ($transaction) { + Db::commit(); + } + + if ($r) { + return $r; + } + + if ($failReturn instanceof Exception) { + return null; + } + + return $failReturn; + } catch (Exception $e) { + if ($transaction) { + Db::rollback(); + } + + if ($e instanceof RepositoryException) { + throw $e; + } + + $name = 'Domain - Repository - 未知错误'; + + $traces = $e->getTrace(); + + foreach ($traces as $i => $trace) { + if (!empty($trace['class']) && $trace['class'] === Repository::class && $trace['function'] === 'access') { + $trace = $traces[$i - 2] ?? null; + break; + } + } + + if (!empty($trace) && !empty($trace['file'][1]) && !empty($trace['line'])) { + $parts = explode('application\\', $trace['file']); + if (!empty($parts[1])) { + $name = $parts[1].':'.$trace['line']; + } else { + $name = $trace['file'].':'.$trace['line']; + } + } + + $exception = $e; + + $line = '['.$name.'] '.$e->getMessage(); + Log::error($line); + + throw new RepositoryException('Repository异常'.(Env::get('app_debug') ? ': '.$line : ''), 5009); + } finally { + if ($exception && $failReturn instanceof RepositoryException) { + if (Env::get('app_debug')) { + $failReturn = new RepositoryException($failReturn->getMessage().': '.$exception->getMessage(), + $failReturn->getCode()); + } + throw $failReturn; + } + } + } + + /** + * 获取当前model对象 + * + * @return Model + */ + public function getModel(): Model + { + return $this->model; + } + + /** + * 根据条件查询列表 + * + * @param array $where 查询条件 + * @param array $fields 查询字段 []表示全部 + * @param int $page 默认第一页 0不限制 + * @param int $limit 限制条数 0不限制 + * @param callable|null $callback 更为复杂的条件 使用闭包查询 + * @return array + * @throws RepositoryException + */ + public function findList(array $where = [], array $fields = [], int $page = 1, int $limit = 0, callable $callback = null, array $order = []): ?array + { + $failData = [ + 'total' => 0, + 'current' => $page, + 'size' => $limit, + 'list' => new Collection(), + ]; + return $this->access(function () use ($where, $fields, $page, $limit, $callback, $order) { + return $this->model->findList($where, $fields, $page, $limit, $callback, $order); + }, $failData); + } + + /** + * 根据条件查询列表[带分页 适用于后台] + * + * @param array $data 查询数据 + * @param array $pageParams 分页参数 + * @param callable|null $callback 复杂查询条件 使用闭包查询 + * @return Paginator + */ + public function findListWithPaginate(array $data = [], array $pageParams = [], callable $callback = null): Paginator + { + return $this->model->findListWithPaginate($data, $pageParams, $callback); + } + + /** + * 根据主键 ID 查询 + * + * @param int $id ID + * @param array $fields 要返回的字段,默认全部 + * @return Mixed + * @throws RepositoryException + */ + public function findById(int $id, array $fields = [], callable $callback = null) + { + return $this->access(function () use ($id, $fields, $callback) { + return $this->model->findById($id, $fields, $callback); + }, null); + } + + /** + * @param array $where + * @param array $fields + * @return array|Model|null + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + */ + public function findOneByWhere(array $where, array $fields = []) + { + return $this->model->field($fields)->where($where)->find(); + } + + /** + * 创建 + * + * @param array $data 数据 + * @return Model + * @throws RepositoryException + */ + public function create(array $data): Model + { + return $this->access(function () use ($data) { + return $this->model->create($data); + }, new RepositoryException('创建失败')); + } + + /** + * 更新 + * + * @param array $data 数据 + * @param array $where 条件 + * @return bool|Exception + * @throws RepositoryException + */ + public function update(array $data, array $where): bool + { + return $this->access(function () use ($data, $where) { + return $this->model->where($where)->find()->save($data); + }, new RepositoryException('更新失败')); + } + + /** + * 删除 + * + * @param array $where 删除条件 + * @param bool $softDelete 是否软删除 默认false + * @param string $softDeleteTime 删除时间 softDelete=true时有效 + * @param string $softDeleteField 软删除字段 softDelete=true时有效 + * @return bool + * @throws RepositoryException + */ + public function delete(array $where, bool $softDelete = false, string $softDeleteTime = '', string $softDeleteField = 'deleted_at'): bool + { + return $this->access(function () use ($where, $softDelete, $softDeleteField, $softDeleteTime) { + // 注意:如果model中引入了软删除trait,$softDelete又设置false 将无法正确删除 + return $this->model->where($where) + ->when($softDelete, function ($q) use ($softDeleteField, $softDeleteTime) { + $softDeleteTime = $softDeleteTime ?: date('Y-m-d H:i:s'); + $q->useSoftDelete($softDeleteField, $softDeleteTime); + })->delete(); + }, false); + } + + /** + * 排序 + * + * @param int $id 排序ID + * @param string $type 排序类型 向上、向下 + * @param int $num 移动位数 + * @param string $listType 列表的排序类型 降序|升序 + * @param array $where 额外条件 格式如: + * $map[] = ['name','like','think']; + * $map[] = ['status','=',1]; + * @return array + */ + public function sort(int $id,string $type,int $num,string $listType, array $where = []): array + { + return $this->model->sort($id, $type, $num, $listType, $where); + } + + /** + * 日志记录 + * + * @param string $msg + * @param Exception|null $e + * @param string $level + * @param string $channel + */ + public static function log(string $msg, Exception $e = null, string $level = 'error', string $channel = 'file') + { + if ($e != null) { + $msg = sprintf("[%s]%s:%s %s", $msg, $e->getFile(), $e->getLine(), $e->getMessage()); + } else { + $msg = sprintf("%s", $msg); + + } + Log::channel($channel)->$level($msg); + } +} diff --git a/app/service/Sms.php b/app/service/Sms.php new file mode 100644 index 0000000..27f8e65 --- /dev/null +++ b/app/service/Sms.php @@ -0,0 +1,189 @@ + $code + // "product" => "大头拍卖" + ); + + // fixme 可选: 设置发送短信流水号 + // $params['OutId'] = "12345"; + + // fixme 可选: 上行短信扩展码, 扩展码字段控制在7位或以下,无特殊需求用户请忽略此字段 + // $params['SmsUpExtendCode'] = "1234567"; + + + // *** 需用户填写部分结束, 以下代码若无必要无需更改 *** + if (!empty($params["TemplateParam"]) && is_array($params["TemplateParam"])) { + $params["TemplateParam"] = json_encode($params["TemplateParam"], JSON_UNESCAPED_UNICODE); + } + + // 初始化SignatureHelper实例用于设置参数,签名以及发送请求 + $helper = new SignatureHelper(); + + // 此处可能会抛出异常,注意catch + $res = $helper->request( + $accessKeyId, + $accessKeySecret, + "apisms.landui.com", + array_merge($params, array( + "RegionId" => "cn-hangzhou", + "Action" => "SendSms", + "Version" => "2017-05-25", + )), + $security + ); + + if (isset($res->Code) && $res->Code == 'OK') { + return true; + } + return false; + } + + /** + * 发送短信 + */ + public static function sendContent(string $tel, array $content, string $templateCode = '130'): bool + { + $params = array(); + + // *** 需用户填写部分 *** + // fixme 必填:是否启用https + $security = false; + + // fixme 必填: 请参阅 https://ak-console.aliyun.com/ 取得您的AK信息 + $accessKeyId = self::$accessKeyId; + $accessKeySecret = self::$accessKeySecret; + + // fixme 必填: 短信接收号码 + $params["PhoneNumbers"] = $tel; + + // fixme 必填: 短信签名,应严格按"签名名称"填写,请参考: https://dysms.console.aliyun.com/dysms.htm#/develop/sign + $params["SignName"] = "大头拍卖"; + + // fixme 必填: 短信模板Code,应严格按"模板CODE"填写, 请参考: https://dysms.console.aliyun.com/dysms.htm#/develop/template + $params["TemplateCode"] = $templateCode;//"code"; + + $params['TemplateParam'] = self::handleContent($templateCode, $content); + + // *** 需用户填写部分结束, 以下代码若无必要无需更改 *** + if (!empty($params["TemplateParam"]) && is_array($params["TemplateParam"])) { + $params["TemplateParam"] = json_encode($params["TemplateParam"], JSON_UNESCAPED_UNICODE); + } + + // 初始化SignatureHelper实例用于设置参数,签名以及发送请求 + $helper = new SignatureHelper(); + + // 此处可能会抛出异常,注意catch + $res = $helper->request( + $accessKeyId, + $accessKeySecret, + "apisms.landui.com", + array_merge($params, array( + "RegionId" => "cn-hangzhou", + "Action" => "SendSms", + "Version" => "2017-05-25", + )), + $security + ); + + if (isset($res->Code) && $res->Code == 'OK') { + return true; + } + return false; + } + + /** + * 处理内容 + * + * @param string $templateCode + * @param array $content + * @return array|string[] + * @throws Exception + */ + private static function handleContent(string $templateCode, array $content): array + { + switch ($templateCode) { + //788 活动开始通知 + case self::TEMPLATE_BEGIN: + if (!isset($content['name']) || !isset($content['time'])) { + throw new Exception('参数错误'); + } + $res = [ + "name" => $content['name'] ?? '', + "time" => $content['time'] ?? '' + ]; + break; + //789 竞拍成功 + case self::TEMPLATE_SUCCESS: + if (!isset($content['name'])) { + throw new Exception('参数错误'); + } + $res = [ + "name" => $content['name'] ?? '', + ]; + break; + //790 发货通知 + //791 新订单通知管理员 + case self::TEMPLATE_SHIPPED: + case self::TEMPLATE_NEW_ORDER: + if (!isset($content['order'])) { + throw new Exception('参数错误'); + } + $res = [ + "order" => $content['order'] ?? '', + ]; + break; + default: + //默认130 短信验证码 + if (!isset($content['code'])) { + throw new Exception('参数错误'); + } + $res = [ + "code" => $content['code'] ?? '' + ]; + } + return $res; + } +} diff --git a/app/service/Test.php b/app/service/Test.php new file mode 100644 index 0000000..ea46ace --- /dev/null +++ b/app/service/Test.php @@ -0,0 +1,27 @@ +getRootPath() . ltrim($path, '/'); + if (file_exists($realPath)) { + $info = pathinfo($realPath); + $source = $info['dirname'] . "/" . $info['filename'] . '*.' . $info['extension']; + foreach(glob($source) as $filename){ + if(is_file($filename)){ + unlink($filename); + } + } + clearstatcache();// 清除缓存 + } + } + } + + /** + * 删除目录下的所有文件和子目录 + * 调用完毕后请用clearstatcache()清理缓存 + */ + public static function removeByPath($path) + { + if(is_dir($path)) { + if($handle = @opendir($path)) { + while (($file = readdir($handle)) !== false){ + if($file != '.' && $file != '..') { + $child = $path.'/'.$file; + is_dir($child) ? self::removeByPath($child) : @unlink($child); + } + } + } + closedir($handle); + } elseif (is_file($path)) { + @unlink($path); + } else { + return false; + } + return true; + } + + //去除字符串空格 + public static function trimSpace($str) + { + return str_replace(' ', '', trim($str)); + } +} diff --git a/app/service/sms/SignatureHelper.php b/app/service/sms/SignatureHelper.php new file mode 100644 index 0000000..3b07dde --- /dev/null +++ b/app/service/sms/SignatureHelper.php @@ -0,0 +1,97 @@ + "HMAC-SHA1", + "SignatureNonce" => uniqid(mt_rand(0,0xffff), true), + "SignatureVersion" => "1.0", + "AccessKeyId" => $accessKeyId, + "Timestamp" => gmdate("Y-m-d\TH:i:s\Z"), + "Format" => "JSON", + ), $params); + ksort($apiParams); + + $sortedQueryStringTmp = ""; + foreach ($apiParams as $key => $value) { + $sortedQueryStringTmp .= "&" . $this->encode($key) . "=" . $this->encode($value); + } + + $stringToSign = "${method}&%2F&" . $this->encode(substr($sortedQueryStringTmp, 1)); + + $sign = base64_encode(hash_hmac("sha1", $stringToSign, $accessKeySecret . "&",true)); + + $signature = $this->encode($sign); + + $url = ($security ? 'https' : 'http')."://{$domain}/"; + + try { + $content = $this->fetchContent($url, $method, "Signature={$signature}{$sortedQueryStringTmp}"); + return json_decode($content); + } catch( \Exception $e) { + return false; + } + } + + private function encode($str) + { + $res = urlencode($str); + $res = preg_replace("/\+/", "%20", $res); + $res = preg_replace("/\*/", "%2A", $res); + $res = preg_replace("/%7E/", "~", $res); + return $res; + } + + private function fetchContent($url, $method, $body) { + $ch = curl_init(); + + if($method == 'POST') { + curl_setopt($ch, CURLOPT_POST, 1);//post提交方式 + curl_setopt($ch, CURLOPT_POSTFIELDS, $body); + } else { + $url .= '?'.$body; + } + + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_TIMEOUT, 5); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_HTTPHEADER, array( + "x-sdk-client" => "php/2.0.0" + )); + + if(substr($url, 0,5) == 'https') { + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); + } + + $rtn = curl_exec($ch); + + if($rtn === false) { + // 大多由设置等原因引起,一般无法保障后续逻辑正常执行, + // 所以这里触发的是E_USER_ERROR,会终止脚本执行,无法被try...catch捕获,需要用户排查环境、网络等故障 + trigger_error("[CURL_" . curl_errno($ch) . "]: " . curl_error($ch), E_USER_ERROR); + } + curl_close($ch); + + return $rtn; + } +} \ No newline at end of file diff --git a/app/service/wx/Wechat.php b/app/service/wx/Wechat.php new file mode 100644 index 0000000..6c404f9 --- /dev/null +++ b/app/service/wx/Wechat.php @@ -0,0 +1,46 @@ + $conf['appId'], + 'secret' => $conf['appSecret'], + + // 指定 API 调用返回结果的类型:array(default)/collection/object/raw/自定义类名 + 'response_type' => 'array', + + //... + ]; + + self::$app = Factory::officialAccount($config); + } + return self::$app; + } +} \ No newline at end of file diff --git a/app/service/wx/WechatApplets.php b/app/service/wx/WechatApplets.php new file mode 100644 index 0000000..ceb9280 --- /dev/null +++ b/app/service/wx/WechatApplets.php @@ -0,0 +1,112 @@ + $conf['applets_appId'], + 'secret' => $conf['applets_appSecret'], + // 返回数据类型 array | xml + 'response_type' => 'array', + ]; + self::$app = Factory::miniProgram($config); + } + return self::$app; + } + + /** + * 生成微信小程序链接 + * + * @param string $path + * @param string $sourceCode + * @param bool $tokenRefresh 是否强制刷新access_token 默认false 非特殊条件请勿设为true + * @return string + * @throws GuzzleException + * @throws HttpException + * @throws InvalidArgumentException + * @throws InvalidConfigException + * @throws RuntimeException + * @throws \Psr\SimpleCache\InvalidArgumentException + */ + public static function generateActivityUrl(string $path, string $sourceCode, bool $tokenRefresh = false): string + { + $accessToken = self::getInstance()->access_token; + + $client = new Client(); + + $url = 'https://api.weixin.qq.com/wxa/generate_urllink?access_token='; + + $params = []; + $query = ''; + $params['path'] = 'pages/tabbar/pagehome/pagehome';//首页路径 + if (!empty($path)) { + $pathArr = explode('?', $path); + $query = isset($pathArr[1]) ? $pathArr[1].'&' : ''; + $params['path'] = $pathArr[0]; + } + + $params['query'] = $query.'channel=activity&source_code='.$sourceCode; + $jsonStr = !empty($params) ? json_encode($params, JSON_UNESCAPED_UNICODE) : ''; + + Log::info('【小程序链接生成请求参数】'.$jsonStr); + + $response = $client->request('POST', $url.($accessToken->getToken($tokenRefresh)['access_token'] ?? ''), [ + 'body' => $jsonStr + ]); + + $res = json_decode($response->getBody(), true); + Log::info('【小程序链接生成响应】'.json_encode($res, JSON_UNESCAPED_UNICODE)); + if ($res['errcode'] == 0) { + return $res['url_link'] ?? ''; + } + + if ($res['errcode'] == 40001 && $tokenRefresh == false) { + // 可能是token过期 刷新一次 + // tokenRefresh 防止无限循环 + return self::generateActivityUrl($path, $sourceCode, true); + } + + throw new Exception('链接生成失败 '.$res['errmsg'] ?? ''); + } +} \ No newline at end of file diff --git a/app/service/wx/WechatPay.php b/app/service/wx/WechatPay.php new file mode 100644 index 0000000..dd2b117 --- /dev/null +++ b/app/service/wx/WechatPay.php @@ -0,0 +1,47 @@ + $conf['applets_appId'],//此处使用的小程序APPID + 'mch_id' => $conf['mchid'], + 'key' => $conf['key'], // API 密钥 + + // 如需使用敏感接口(如退款、发送红包等)需要配置 API 证书路径(登录商户平台下载 API 证书) + 'cert_path' => root_path().'cert/apiclient_cert.pem', // XXX: 绝对路径!!!! + 'key_path' => root_path().'cert/apiclient_key.pem', // XXX: 绝对路径!!!! + + 'notify_url' => $conf['applets_notify_url'], // 你也可以在下单时单独设置来想覆盖它 + ]; + self::$app = Factory::payment($config); + } + return self::$app; + } +} \ No newline at end of file diff --git a/app/traits/SignInTrait.php b/app/traits/SignInTrait.php new file mode 100644 index 0000000..68a1916 --- /dev/null +++ b/app/traits/SignInTrait.php @@ -0,0 +1,159 @@ +whereBetweenTime("created_at", date("Y-m-d 00:00:00", time()), date("Y-m-d 23:59:59", time())) + ->count(); + return ($signInCount > 0) ? true : false; + } + + /** + * 线上签到 + * + * @param object $account + */ + public function SignInOnline($account, $score) + { + //删除重复的记录 + AccountSignOnline:: + where('account_id', $account["id"]) + ->whereBetweenTime("created_at", date("Y-m-d 00:00:00", time()), date("Y-m-d 23:59:59", time())) + ->delete(); + + //奖励积分 + $this->accountReward($account, Task::reward_type_score, $score); + + //更新最后签到时间和连续签到次数 + $account->save(["continuity_sign" => ($account['continuity_sign'] + 1), "last_sign_online" => date("Y-m-d H:i:s")]); + + //写入签到记录 + AccountSignOnline::create(["account_id" => $account["id"], "created_at" => date("Y-m-d H:i:s"), "score" => $score]); + } + + + /** + * 检查线下签到 + * + * @param int $accountId + */ + public function checkSignIn($accountId) + { + $signInCount = AccountSign:: + where('account_id', $accountId) + ->whereBetweenTime("created_at", date("Y-m-d 00:00:00", time()), date("Y-m-d 23:59:59", time())) + ->count(); + return ($signInCount > 0) ? true : false; + } + + /** + * 线下签到 + * + * @param int $accountId + */ + public function SignIn($accountId) + { + AccountSign::where('account_id', $accountId) + ->whereBetweenTime("created_at", date("Y-m-d 00:00:00", time()), date("Y-m-d 23:59:59", time())) + ->delete(); + AccountSign::create(["account_id" => $accountId, "created_at" => date("Y-m-d H:i:s")]); + } + + + /** + * 操作用户孔雀币 + * @param $account array|object 用户对象 + * @param $rewardType string 奖励类型 + * @param $totalReward int 奖励总数 + * */ + public function accountReward($account, $rewardType, $totalReward) + { + //写入--积分孔雀币日志 + AccountDataLog::log($account['id'], + "签到奖励积分", + $totalReward, + $rewardType, + AccountDataLog::ACTION_SIGN, + $account[$rewardType] + $totalReward + ); + + //加积分 + $accountSaveData = [$rewardType => ($account[$rewardType] + $totalReward)]; + $account->save($accountSaveData); + + } + + /** + * 本周的签到记录 + * + * @param string $accountId + */ + public function weedSignInOnlineRecord($accountId, $startDate, $endData) + { + return AccountSignOnline::where('account_id', $accountId) + ->whereBetweenTime("created_at", $startDate, $endData) + ->order("created_at desc") + ->select(); + } + + /** + * 签到记录分页 + * + * @param string $accountId + */ + public function onlineSignRecordList($accountId, $page, $size) + { + return AccountSignOnline::where('account_id', $accountId) + ->page($page, $size) + ->field("created_at,score") + ->order("created_at desc") + ->select(); + } + + /** + * 验证连续签到次数 + * 上次签到 距离当天已经超过1天 连续签到中断 + * @param object $account + */ + public function checkContinuitySign($account) + { + if (empty($account["last_sign_online"])) { + $account->save(["continuity_sign" => 0]); + } + $prevDayTime = strtotime(date("Y-m-d", strtotime("-1 day")));//昨天 00:00:00的时间戳 + $lastSignOnlineTime = strtotime($account["last_sign_online"]); + + //如果上次签到 在一天之前 归零连续签到次数 + if ($lastSignOnlineTime < $prevDayTime) { + $account->save(["continuity_sign" => 0]); + } + } + + /* + * 后台统计当天签到数 + * */ + public function todaySignInCount() + { + return AccountSignOnline::whereBetweenTime("created_at", date("Y-m-d 00:00:00"), date("Y-m-d 23:59:59"))->count(); + } + + +} \ No newline at end of file diff --git a/app/traits/cms/ArticleTrait.php b/app/traits/cms/ArticleTrait.php new file mode 100644 index 0000000..4c80119 --- /dev/null +++ b/app/traits/cms/ArticleTrait.php @@ -0,0 +1,59 @@ +where('status', Menu::STATUS_NORMAL) + ->when($type != 'all', function ($q) use ($show) { + $q->where('is_show', $show); + }) + ->when($type != 'all', function ($q) use ($type) { + $q->where('type', $type); + }) + ->order('sort', 'desc') + ->order('id', 'asc') + ->select(); + } + + //递归获取子菜单 + public function buildMenuChild(int $pid, array $menus, string $childName = 'children'): array + { + $treeList = []; + + foreach ($menus as $v) { + if ($pid == $v['pid']) { + $node = $v; + + $node['has_children'] = false; + $child = $this->buildMenuChild($v['id'], $menus, $childName); + if (!empty($child)) { + $node[$childName] = $child; + $node['has_children'] = true; + } + + $treeList[] = $node; + + } + } + return $treeList; + } + + //菜单权限过滤 + public function handMenuRule(int $accountId, array $menus): array + { + $rules = CmsRepository::getInstance()->getUserRules($accountId); + + foreach ($menus as $k => $m) { + $menus[$k]['icon'] = !empty($m['icon']) ? 'fa '.$m['icon'] : ''; + $menus[$k]['href'] = ltrim($m['href'], '/'); + + if ($m['pid'] !== 0) { + $name = $m['name']; + $nameArr = explode(':', $name); + if (count($nameArr) <= 1) { + $name = $name.':index'; + } + + if (!in_array($name, $rules)) { + unset($menus[$k]); + } + } + } + return $menus; + } + + /** + * 检测批量数据下 是否有子栏目 + * + * @param array $ids + * @return bool + */ + public function hasChildrenMenuByIds(array $ids): bool + { + return Menu::whereIn('pid', $ids)->count() > 0; + } + + /** + * 批量删除 + * + * @param array $ids + * @return bool + */ + public function delMenuByIds(array $ids): bool + { + return Menu::deleteByIds($ids); + } +} \ No newline at end of file diff --git a/app/validate/Account.php b/app/validate/Account.php new file mode 100644 index 0000000..d16b97f --- /dev/null +++ b/app/validate/Account.php @@ -0,0 +1,32 @@ + 'require|mobile', + 'old_password|旧密码' => 'require|length:6,16', + 'password|密码' => 'require|length:6,16', + 'confirm_password|确认密码' => 'require|confirm:password|length:6,16', + 'password_confirm|确认密码' => 'require|confirm:password|length:6,16', + 'code|验证码' => 'require|number|length:6', + 'type|类型' => 'require', + 'username|用户名' => 'require|min:4', + + 'id_card|身份证' => 'require', + 'real_name|真实姓名' => 'require', + 'bank_number|银行卡号' => 'require', + 'bank_name|银行名称' => 'require', + ]; + + protected $scene = [ + 'edit_password' => ['old_password', 'password', 'confirm_password'], //修改密码 + 'register' => ['username', 'nickname', 'phone', 'password', 'password_confirm', 'code'], //注册 + 'send_sms' => ['phone', 'type'], //发送短信 + 'binding' => ['phone', 'code'], //绑定手机号 + 'real_name' => ['id_card', 'real_name', 'bank_number', 'bank_name'], //实名认证 + ]; +} \ No newline at end of file diff --git a/app/validate/AuthGroup.php b/app/validate/AuthGroup.php new file mode 100644 index 0000000..cebd727 --- /dev/null +++ b/app/validate/AuthGroup.php @@ -0,0 +1,17 @@ + 'require', + 'status' => 'require|number', + ]; + protected $message = [ + 'title.require' => '角色名称不能为空', + 'status.require' => '角色状态必须设置', + 'status.number' => '角色状态参数值只能为数字类型', + ]; +} \ No newline at end of file diff --git a/app/validate/AuthRule.php b/app/validate/AuthRule.php new file mode 100644 index 0000000..3dc11b7 --- /dev/null +++ b/app/validate/AuthRule.php @@ -0,0 +1,19 @@ + 'require', + 'name' => 'require', + 'status' =>'require|number', + ]; + protected $message = [ + 'title.require' => '名称必须', + 'name.require'=> '标识必须', + 'status.require'=> '显示状态必须传值', + 'status.number'=> '显示状态传值只能为数字表示', + ]; +} \ No newline at end of file diff --git a/app/validate/Block.php b/app/validate/Block.php new file mode 100644 index 0000000..171a21d --- /dev/null +++ b/app/validate/Block.php @@ -0,0 +1,51 @@ +,开发者QQ群:50304283 +// +---------------------------------------------------------------------- +namespace app\validate; + +use think\Validate; + +/** + * 碎片验证器 + * @package app\controller\cms\validate + */ +class Block extends Validate +{ + //定义验证规则 + protected $rule = [ + 'name|别名' => [ + 'require', + 'length' => '1,30', + 'regex' => '/^[A-Za-z0-9\_]+$/', + ], + 'title|名称' => [ + 'require', + 'length' => '1,20', + ], + 'content1|碎片内容' => [ + 'requireIf' => 'type,1' + ], + 'content2|碎片内容' => [ + 'requireIf' => 'type,2' + ], + 'content3|碎片内容' => [ + 'requireIf' => 'type,3' + ], + 'content4|碎片内容' => [ + 'requireIf' => 'type,4' + ], + 'content5|碎片内容' => [ + 'requireIf' => 'type,5' + ], + 'content6|碎片内容' => [ + 'requireIf' => 'type,6' + ], + ]; +} diff --git a/app/validate/CommonValidate.php b/app/validate/CommonValidate.php new file mode 100644 index 0000000..c7fb8da --- /dev/null +++ b/app/validate/CommonValidate.php @@ -0,0 +1,17 @@ + 'require|mobile', + 'type|类型' => 'require', + ]; + + protected $scene = [ + 'send_sms' => ['phone', 'type'], //发送短信 + ]; +} \ No newline at end of file diff --git a/app/validate/Link.php b/app/validate/Link.php new file mode 100644 index 0000000..ba7adfc --- /dev/null +++ b/app/validate/Link.php @@ -0,0 +1,16 @@ + 'require', + 'url' => 'url', + ]; + protected $message = [ + 'title.require' => '名称必须', + 'url.url' => '请填写有效的网址,以http://或https://开头' + ]; +} \ No newline at end of file diff --git a/app/validate/MenuValidate.php b/app/validate/MenuValidate.php new file mode 100644 index 0000000..08c22c8 --- /dev/null +++ b/app/validate/MenuValidate.php @@ -0,0 +1,21 @@ + 'require|number', + 'title|标题' => 'require', + 'id|ID' => 'require|number', + 'field|字段名' => 'require', + 'value|值' => 'require', + ]; + + protected $scene = [ + 'menu_add_and_edit' => ['pid', 'title'],//菜单添加 + 'menu_modify' => ['id', 'field', 'value'],//单元格编辑 + ]; +} \ No newline at end of file diff --git a/app/validate/Slide.php b/app/validate/Slide.php new file mode 100644 index 0000000..c391c4a --- /dev/null +++ b/app/validate/Slide.php @@ -0,0 +1,17 @@ + 'require', + 'position|位置' => 'require', + 'src|轮播图' => 'require', + ]; + + protected $scene = [ + 'slide' => ['title', 'position', 'src'],//轮播图 + ]; +} \ No newline at end of file diff --git a/app/validate/Upload.php b/app/validate/Upload.php new file mode 100644 index 0000000..f34e16a --- /dev/null +++ b/app/validate/Upload.php @@ -0,0 +1,60 @@ +system = System::getSystem(); + $this->lang = new Lang; + } + + //验证图片上传 + public function checkImage($image) + { + $ext = str_replace(',', ',', $this->system['img_type']); + $size = $this->system['img_size'] * 1024 * 1024; + $this->rule = [ + 'image' => [ + 'fileExt' => $ext, + 'fileSize' => (int)$size + ] + ]; + return $this->check(['image' => $image]); + } + + //验证视频上传 + public function checkVideo($video) + { + $ext = str_replace(',', ',', $this->system['video_type']); + $size = $this->system['video_size'] * 1024 * 1024; + $this->rule = [ + 'video' => [ + 'fileExt' => $ext, + 'fileSize' => (int)$size + ] + ]; + return $this->check(['video' => $video]); + } + + //验证文件上传 + public function checkFile($file) + { + $ext = str_replace(',', ',', $this->system['file_type']); + $size = $this->system['file_size'] * 1024 * 1024; + $this->rule = [ + 'file' => [ + 'fileExt' => $ext, + 'fileSize' => (int)$size + ] + ]; + return $this->check(['file' => $file]); + } +} \ No newline at end of file diff --git a/app/widget/Crumbs.php b/app/widget/Crumbs.php new file mode 100644 index 0000000..c43fa6a --- /dev/null +++ b/app/widget/Crumbs.php @@ -0,0 +1,17 @@ + Category::getCatesCrumbs($categoryId) + ]; + return View::assign($data)->fetch('public/crumbs'); + } + +} \ No newline at end of file diff --git a/app/widget/Menu.php b/app/widget/Menu.php new file mode 100644 index 0000000..204c052 --- /dev/null +++ b/app/widget/Menu.php @@ -0,0 +1,49 @@ + 0) { + $currentFirstId = Category::firstGradeById($categoryId); + } + + //产品菜单 + + $product_children=Category::alias('c') + ->leftJoin('model m', 'c.model_id=m.id') + ->field('c.*, m.manager, m.template, m.name as modelName') + ->where('c.parent_id', 5) + ->order('c.sort','asc') + ->select() + ->toArray(); + + $data = [ + 'categoryId' => $categoryId, + 'menus' => $menus, + 'product_children' => $product_children?$product_children:[], + ]; + + + $this_menu= \app\model\Category::alias('c') + ->leftJoin('model m', 'c.model_id=m.id') + ->field('c.*, m.manager, m.name as modelName') + ->when(3, function($query) { + $query->whereIn('c.id', []); + }) + ->order('sort','asc') + ->select() + ->toArray(); + return View::assign($data)->fetch('public/menu'); + } +} \ No newline at end of file diff --git a/app/widget/Slide.php b/app/widget/Slide.php new file mode 100644 index 0000000..4677bd7 --- /dev/null +++ b/app/widget/Slide.php @@ -0,0 +1,16 @@ + WSlide::getList(), + ]; + return View::assign($data)->fetch('public/slide'); + } +} \ No newline at end of file diff --git a/app/widget/manager/Crumbs.php b/app/widget/manager/Crumbs.php new file mode 100644 index 0000000..9b5e066 --- /dev/null +++ b/app/widget/manager/Crumbs.php @@ -0,0 +1,44 @@ +controller()); + $action = unCamelize($request->action()); + $controller = str_replace('manager.', '', $controller); + $name = $controller.'/'.$action; + if ($action == 'index') { + $rule = AuthRule::getByTwoName($name, $controller); + } else { + $rule = AuthRule::getByName($name); + } + $parent = []; + if (!empty($rule) && $rule['parent_id']) { + $parent = AuthRule::getById($rule['parent_id']); + } + $cateCrumbs = []; + $isContent = false; + if ($controller == 'content') { + $isContent = true; + $categoryId = $request->param('category_id', 0); + if (is_numeric($categoryId) && $categoryId > 0) { + $cateCrumbs = Category::getCatesCrumbs($categoryId); + } + } + + $data = [ + 'rule' => $rule, + 'parent' => $parent, + 'isContent' => $isContent, + 'cateCrumbs' => $cateCrumbs + ]; + return View::assign($data)->fetch('manager/widget/crumbs'); + } +} \ No newline at end of file diff --git a/app/widget/manager/Menu.php b/app/widget/manager/Menu.php new file mode 100644 index 0000000..5ae38bb --- /dev/null +++ b/app/widget/manager/Menu.php @@ -0,0 +1,95 @@ +getMenuRules($rules); + + $current = strtolower(request()->controller()); + $current = str_replace('manager.', '', $current); + $currentAction = strtolower($current.'/'.request()->action()); + // message 留言管理是否集成在内容管理中,后期开发中根据情况调整 + if(in_array($current, ['article', 'product', 'page'], true)){ + $current = 'content'; + } + if($auth['groupId'] == 1) { + $menus = $this->getMenus(Category::getList(false)); + } else { + $menus = $this->getMenus(Category::getList(true, $authCates)); + } + foreach ($menus as $key => $menu) { + if ($menu['backend_show'] == 0) { + unset($menus[$key]); + } + } + $data = [ + 'rules' => $menuRules, + 'categoryId' => $categoryId, + 'menus' => $menus, + 'current' => $current, + 'currentAction' => $currentAction + ]; + return View::assign($data)->fetch('manager/widget/left'); + } + + /** + * 过滤出权限菜单 + * @param $rules + * @return array + */ + private function getMenuRules($rules) + { + $menuRules = []; + if (!empty($rules)) { + foreach ($rules as $rule) { + $hasChildren = $rule['hasChildren'] ?? false; + if ($hasChildren) { + $rule['children'] = $this->getMenuRules($rule['children']); + if(count($rule['children']) > 0) { + $rule['status'] = 1; + } + } + if($rule['status'] > 0) { + $menuRules[] = $rule; + } + } + } + return $menuRules; + } + + /** + * 内容栏目菜单 + * @param $cates + * @param int $parent_id + * @return array + */ + private function getMenus($cates,$parentId=0) + { + $menus = []; + foreach($cates as $cate){ + if($cate['parent_id'] == $parentId && $cate['state'] == 1){ + $children = $this->getMenus($cates,$cate['id']); + if(!empty($children)){ + $cate['children'] = $children; + } + if(!empty($cate['children']) || !empty($cate['manager'])){ + $menus[] = $cate; + } + } + } + return $menus; + } +} diff --git a/app/widget/manager/Upload.php b/app/widget/manager/Upload.php new file mode 100644 index 0000000..5181ec1 --- /dev/null +++ b/app/widget/manager/Upload.php @@ -0,0 +1,111 @@ + $src, + 'append' => $append + ]; + return View::assign($data)->fetch('manager/widget/video'); + } + + //图片(layui自带上传控件),若同一页面内徐亚加载多层本上传控件则需要传不同的$append来区分控件ID + public function image($src = '', $append = '', $imgSize = 0, $thumb = 0, $fullName = '') + { + $data = [ + 'src' => $src, + 'append' => !empty($append) ? $fullName : '',//预防只传了fullName + 'imgSize' => $imgSize, + 'fieldName' => $fullName ?: 'img'.$append,//最终后端接收数据的name + 'thumb' => $thumb, + ]; + return View::assign($data)->fetch('manager/widget/image'); + } + + //上传文件,目前在文章中添加附件 + public function files($files = [], $num = 10, $append = '') + { + if (!empty($files) && $files == 'null') { + $files = []; + } + $data = [ + 'files' => $files, + 'append' => $append, + 'num' => $num, + ]; + return View::assign($data)->fetch('manager/widget/files'); + } + + /** + * 水印图片上传 + * milo + * 2018-01-13 + */ + public function mark($src = '') + { + return View::assign(['src' => $src])->fetch('manager/widget/mark'); + } + + /** + * layui组图上传 + * milo + */ + public function multi_bak($imgs = [], $num = 10, $append = '', $imgSize = '') + { + if (!empty($imgs) && $imgs == 'null') { + $imgs = []; + } + $data = [ + 'imgs' => $imgs, + 'append' => $append, + 'imgSize' => $imgSize, + 'num' => $num + ]; + return View::assign($data)->fetch('manager/widget/multi_bak'); + } + + /** + * @param array $imgs 需要展示的图片列表 + * @param int $num 最大数量 + * @param string $append 追加名称,用于多组图片时区别名称 当$fullName存在时不生效 + * @param string $imgSize 图片建议尺寸 + * @param string $fullName 组图name 传递到后台程序的名字 如image 后台则收到image的数组 + * @param array|bool $fields 图片额外树形字段,默认有alt link desc time 若此处设置后将替换默认值。为false则不填写 格式如下 + * ['alt' => 'alt', 'desc' => '描述,选填', 'link' => '链接,选填'] + * @return string + * @throws Exception + */ + public function multi(array $imgs = [], int $num = 10, string $append = 'img', string $imgSize = '', string $fullName = '', $fields = []): string + { + if (!empty($imgs) && $imgs == 'null') { + $imgs = []; + } + + if (empty($fields)) { + $fields = $fields === false ? [] : [ + 'alt' => 'alt', + 'desc' => '描述', + 'link' => '链接,选填', + 'time' => '日期,选填', + ]; + } + + $data = [ + 'imgs' => $imgs, + 'append' => $append ?: $fullName,//预防只传了fullName + 'imgSize' => $imgSize, + 'fields' => $fields, + 'fieldName' => $fullName ?: 'img'.$append,//最终后端接收数据的name + 'num' => $num + ]; + return View::assign($data)->fetch('manager/widget/multi'); + } +} diff --git a/build.example.php b/build.example.php new file mode 100644 index 0000000..0f2222f --- /dev/null +++ b/build.example.php @@ -0,0 +1,26 @@ + +// +---------------------------------------------------------------------- + +/** + * php think build 自动生成应用的目录结构的定义示例 + */ +return [ + // 需要自动创建的文件 + '__file__' => [], + // 需要自动创建的目录 + '__dir__' => ['controller', 'model', 'view'], + // 需要自动创建的控制器 + 'controller' => ['Index'], + // 需要自动创建的模型 + 'model' => ['User'], + // 需要自动创建的模板 + 'view' => ['index/index'], +]; diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..ef6cb1e --- /dev/null +++ b/composer.json @@ -0,0 +1,63 @@ +{ + "name": "topthink/think", + "description": "the new thinkphp framework", + "type": "project", + "keywords": [ + "framework", + "thinkphp", + "ORM" + ], + "homepage": "http://thinkphp.cn/", + "license": "Apache-2.0", + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "require": { + "php": ">=7.4.0", + "topthink/framework": "^6.0.0", + "topthink/think-orm": "^2.0", + "topthink/think-view": "^1.0", + "topthink/think-image": "^1.0", + "lcobucci/jwt": "^4.1", + "overtrue/socialite": "3.x", + "overtrue/wechat": "~5.0", + "ext-json": "*", + "yansongda/pay": "^2.10", + "ext-curl": "*", + "topthink/think-queue": "^3.0", + "topthink/think-captcha": "^3.0", + "casbin/think-authz": "^1.2", + "phpoffice/phpspreadsheet": "^1.17", + "endroid/qr-code": "^4.2", + "aliyuncs/oss-sdk-php": "^2.4", + "ext-bcmath": "*", + "intervention/image": "^2.7" + }, + "require-dev": { + "symfony/var-dumper": "^4.2", + "topthink/think-trace":"^1.0" + }, + "autoload": { + "psr-4": { + "app\\": "app" + }, + "psr-0": { + "": "extend/" + } + }, + "config": { + "preferred-install": "dist", + "allow-plugins": { + "easywechat-composer/easywechat-composer": true + } + }, + "scripts": { + "post-autoload-dump": [ + "@php think service:discover", + "@php think vendor:publish" + ] + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..03e0663 --- /dev/null +++ b/composer.lock @@ -0,0 +1,4986 @@ +{ + "_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": "bc14049f509e9c8a07504af2702db3cc", + "packages": [ + { + "name": "aliyuncs/oss-sdk-php", + "version": "v2.4.3", + "source": { + "type": "git", + "url": "https://github.com/aliyun/aliyun-oss-php-sdk.git", + "reference": "4ccead614915ee6685bf30016afb01aabd347e46" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aliyun/aliyun-oss-php-sdk/zipball/4ccead614915ee6685bf30016afb01aabd347e46", + "reference": "4ccead614915ee6685bf30016afb01aabd347e46", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.3" + }, + "require-dev": { + "phpunit/phpunit": "*", + "satooshi/php-coveralls": "*" + }, + "type": "library", + "autoload": { + "psr-4": { + "OSS\\": "src/OSS" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aliyuncs", + "homepage": "http://www.aliyun.com" + } + ], + "description": "Aliyun OSS SDK for PHP", + "homepage": "http://www.aliyun.com/product/oss/", + "support": { + "issues": "https://github.com/aliyun/aliyun-oss-php-sdk/issues", + "source": "https://github.com/aliyun/aliyun-oss-php-sdk/tree/v2.4.3" + }, + "time": "2021-08-25T13:03:58+00:00" + }, + { + "name": "bacon/bacon-qr-code", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/Bacon/BaconQrCode.git", + "reference": "f73543ac4e1def05f1a70bcd1525c8a157a1ad09" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/f73543ac4e1def05f1a70bcd1525c8a157a1ad09", + "reference": "f73543ac4e1def05f1a70bcd1525c8a157a1ad09", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "dasprid/enum": "^1.0.3", + "ext-iconv": "*", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phly/keep-a-changelog": "^1.4", + "phpunit/phpunit": "^7 | ^8 | ^9", + "squizlabs/php_codesniffer": "^3.4" + }, + "suggest": { + "ext-imagick": "to generate QR code images" + }, + "type": "library", + "autoload": { + "psr-4": { + "BaconQrCode\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Ben Scholzen 'DASPRiD'", + "email": "mail@dasprids.de", + "homepage": "https://dasprids.de/", + "role": "Developer" + } + ], + "description": "BaconQrCode is a QR code generator for PHP.", + "homepage": "https://github.com/Bacon/BaconQrCode", + "support": { + "issues": "https://github.com/Bacon/BaconQrCode/issues", + "source": "https://github.com/Bacon/BaconQrCode/tree/2.0.4" + }, + "time": "2021-06-18T13:26:35+00:00" + }, + { + "name": "casbin/casbin", + "version": "v3.20.0", + "source": { + "type": "git", + "url": "https://github.com/php-casbin/php-casbin.git", + "reference": "f433e3d6dd5d803d0e19c088b6bf6c9262b645c1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-casbin/php-casbin/zipball/f433e3d6dd5d803d0e19c088b6bf6c9262b645c1", + "reference": "f433e3d6dd5d803d0e19c088b6bf6c9262b645c1", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1.0", + "s1lentium/iptools": "^1.1", + "symfony/expression-language": "^3.4|^4.0|^5.0|^6.0" + }, + "require-dev": { + "mockery/mockery": "^1.2", + "php-coveralls/php-coveralls": "^2.1", + "phpstan/phpstan": "^1.2", + "phpunit/phpunit": "~7.0|~8.0|~9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Casbin\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "TechLee", + "email": "techlee@qq.com" + } + ], + "description": "a powerful and efficient open-source access control library for php projects.", + "keywords": [ + "abac", + "access control", + "acl", + "authorization", + "casbin", + "permission", + "rbac" + ], + "support": { + "issues": "https://github.com/php-casbin/php-casbin/issues", + "source": "https://github.com/php-casbin/php-casbin/tree/v3.20.0" + }, + "funding": [ + { + "url": "https://opencollective.com/casbin", + "type": "open_collective" + } + ], + "time": "2021-12-18T15:07:27+00:00" + }, + { + "name": "casbin/psr3-bridge", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/php-casbin/psr3-bridge.git", + "reference": "846be79ddb0cbf1bcc85f0f63879cf966538ad36" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-casbin/psr3-bridge/zipball/846be79ddb0cbf1bcc85f0f63879cf966538ad36", + "reference": "846be79ddb0cbf1bcc85f0f63879cf966538ad36", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "casbin/casbin": "^2.0|^3.0", + "psr/log": "^1.1" + }, + "require-dev": { + "mockery/mockery": "^1.2", + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "~7.0|~8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Casbin\\Bridge\\Logger\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "TechLee", + "email": "techlee@qq.com" + } + ], + "description": "This library provides a PSR-3 compliant bridge for Casbin Logger.", + "keywords": [ + "acl", + "casbin", + "logger", + "permission", + "psr-3", + "rbac" + ], + "support": { + "issues": "https://github.com/php-casbin/psr3-bridge/issues", + "source": "https://github.com/php-casbin/psr3-bridge/tree/v1.3.0" + }, + "time": "2020-12-24T09:24:22+00:00" + }, + { + "name": "casbin/think-authz", + "version": "v1.5.2", + "source": { + "type": "git", + "url": "https://github.com/php-casbin/think-authz.git", + "reference": "71b7ff5f318496b617ae976d52a7559615674d7d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-casbin/think-authz/zipball/71b7ff5f318496b617ae976d52a7559615674d7d", + "reference": "71b7ff5f318496b617ae976d52a7559615674d7d", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "casbin/casbin": "~3.0", + "casbin/psr3-bridge": "^1.1", + "topthink/framework": "^6.0.0", + "topthink/think-migration": "^3.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "~7.0|~8.0|~9.0", + "topthink/think": "^6.0.0" + }, + "type": "library", + "extra": { + "think": { + "services": [ + "tauthz\\TauthzService" + ] + } + }, + "autoload": { + "psr-4": { + "tauthz\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "TechLee", + "email": "techlee@qq.com" + } + ], + "description": "An authorization library that supports access control models like ACL, RBAC, ABAC in ThinkPHP 6. ", + "keywords": [ + "abac", + "access-control", + "acl", + "authorization", + "authz", + "casbin", + "permission", + "rbac", + "thinkphp" + ], + "support": { + "issues": "https://github.com/php-casbin/think-authz/issues", + "source": "https://github.com/php-casbin/think-authz/tree/v1.5.2" + }, + "time": "2021-11-05T02:08:00+00:00" + }, + { + "name": "dasprid/enum", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/DASPRiD/Enum.git", + "reference": "5abf82f213618696dda8e3bf6f64dd042d8542b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/5abf82f213618696dda8e3bf6f64dd042d8542b2", + "reference": "5abf82f213618696dda8e3bf6f64dd042d8542b2", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require-dev": { + "phpunit/phpunit": "^7 | ^8 | ^9", + "squizlabs/php_codesniffer": "^3.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "DASPRiD\\Enum\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Ben Scholzen 'DASPRiD'", + "email": "mail@dasprids.de", + "homepage": "https://dasprids.de/", + "role": "Developer" + } + ], + "description": "PHP 7.1 enum implementation", + "keywords": [ + "enum", + "map" + ], + "support": { + "issues": "https://github.com/DASPRiD/Enum/issues", + "source": "https://github.com/DASPRiD/Enum/tree/1.0.3" + }, + "time": "2020-10-02T16:03:48+00:00" + }, + { + "name": "easywechat-composer/easywechat-composer", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/mingyoung/easywechat-composer.git", + "reference": "3fc6a7ab6d3853c0f4e2922539b56cc37ef361cd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mingyoung/easywechat-composer/zipball/3fc6a7ab6d3853c0f4e2922539b56cc37ef361cd", + "reference": "3fc6a7ab6d3853c0f4e2922539b56cc37ef361cd", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=7.0" + }, + "require-dev": { + "composer/composer": "^1.0 || ^2.0", + "phpunit/phpunit": "^6.5 || ^7.0" + }, + "type": "composer-plugin", + "extra": { + "class": "EasyWeChatComposer\\Plugin" + }, + "autoload": { + "psr-4": { + "EasyWeChatComposer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "张铭阳", + "email": "mingyoungcheung@gmail.com" + } + ], + "description": "The composer plugin for EasyWeChat", + "support": { + "issues": "https://github.com/mingyoung/easywechat-composer/issues", + "source": "https://github.com/mingyoung/easywechat-composer/tree/1.4.1" + }, + "time": "2021-07-05T04:03:22+00:00" + }, + { + "name": "endroid/qr-code", + "version": "4.4.4", + "source": { + "type": "git", + "url": "https://github.com/endroid/qr-code.git", + "reference": "361b43bbdfa4360442369d0a236e7d8756160523" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/endroid/qr-code/zipball/361b43bbdfa4360442369d0a236e7d8756160523", + "reference": "361b43bbdfa4360442369d0a236e7d8756160523", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "bacon/bacon-qr-code": "^2.0", + "php": "^7.4||^8.0" + }, + "require-dev": { + "endroid/quality": "dev-master", + "ext-gd": "*", + "khanamiryan/qrcode-detector-decoder": "^1.0.4", + "setasign/fpdf": "^1.8.2" + }, + "suggest": { + "ext-gd": "Enables you to write PNG images", + "khanamiryan/qrcode-detector-decoder": "Enables you to use the image validator", + "roave/security-advisories": "Makes sure package versions with known security issues are not installed", + "setasign/fpdf": "Enables you to use the PDF writer" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "Endroid\\QrCode\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jeroen van den Enden", + "email": "info@endroid.nl" + } + ], + "description": "Endroid QR Code", + "homepage": "https://github.com/endroid/qr-code", + "keywords": [ + "code", + "endroid", + "php", + "qr", + "qrcode" + ], + "support": { + "issues": "https://github.com/endroid/qr-code/issues", + "source": "https://github.com/endroid/qr-code/tree/4.4.4" + }, + "funding": [ + { + "url": "https://github.com/endroid", + "type": "github" + } + ], + "time": "2021-12-13T11:01:54+00:00" + }, + { + "name": "ezyang/htmlpurifier", + "version": "v4.14.0", + "source": { + "type": "git", + "url": "https://github.com/ezyang/htmlpurifier.git", + "reference": "12ab42bd6e742c70c0a52f7b82477fcd44e64b75" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/12ab42bd6e742c70c0a52f7b82477fcd44e64b75", + "reference": "12ab42bd6e742c70c0a52f7b82477fcd44e64b75", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.2" + }, + "type": "library", + "autoload": { + "psr-0": { + "HTMLPurifier": "library/" + }, + "files": [ + "library/HTMLPurifier.composer.php" + ], + "exclude-from-classmap": [ + "/library/HTMLPurifier/Language/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-or-later" + ], + "authors": [ + { + "name": "Edward Z. Yang", + "email": "admin@htmlpurifier.org", + "homepage": "http://ezyang.com" + } + ], + "description": "Standards compliant HTML filter written in PHP", + "homepage": "http://htmlpurifier.org/", + "keywords": [ + "html" + ], + "support": { + "issues": "https://github.com/ezyang/htmlpurifier/issues", + "source": "https://github.com/ezyang/htmlpurifier/tree/v4.14.0" + }, + "time": "2021-12-25T01:21:49+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.4.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "ee0a041b1760e6a53d2a39c8c34115adc2af2c79" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/ee0a041b1760e6a53d2a39c8c34115adc2af2c79", + "reference": "ee0a041b1760e6a53d2a39c8c34115adc2af2c79", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.5", + "guzzlehttp/psr7": "^1.8.3 || ^2.1", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "ext-curl": "*", + "php-http/client-integration-tests": "^3.0", + "phpunit/phpunit": "^8.5.5 || ^9.3.5", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.4.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2021-12-06T18:43:05+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/fe752aedc9fd8fcca3fe7ad05d419d32998a06da", + "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.4 || ^5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/1.5.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2021-10-22T20:56:57+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "089edd38f5b8abba6cb01567c2a8aaa47cec4c72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/089edd38f5b8abba6cb01567c2a8aaa47cec4c72", + "reference": "089edd38f5b8abba6cb01567c2a8aaa47cec4c72", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "http-interop/http-factory-tests": "^0.9", + "phpunit/phpunit": "^8.5.8 || ^9.3.10" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.1.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2021-10-06T17:43:30+00:00" + }, + { + "name": "intervention/image", + "version": "2.7.1", + "source": { + "type": "git", + "url": "https://github.com/Intervention/image.git", + "reference": "744ebba495319501b873a4e48787759c72e3fb8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Intervention/image/zipball/744ebba495319501b873a4e48787759c72e3fb8c", + "reference": "744ebba495319501b873a4e48787759c72e3fb8c", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-fileinfo": "*", + "guzzlehttp/psr7": "~1.1 || ^2.0", + "php": ">=5.4.0" + }, + "require-dev": { + "mockery/mockery": "~0.9.2", + "phpunit/phpunit": "^4.8 || ^5.7 || ^7.5.15" + }, + "suggest": { + "ext-gd": "to use GD library based image processing.", + "ext-imagick": "to use Imagick based image processing.", + "intervention/imagecache": "Caching extension for the Intervention Image library" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + }, + "laravel": { + "providers": [ + "Intervention\\Image\\ImageServiceProvider" + ], + "aliases": { + "Image": "Intervention\\Image\\Facades\\Image" + } + } + }, + "autoload": { + "psr-4": { + "Intervention\\Image\\": "src/Intervention/Image" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Oliver Vogel", + "email": "oliver@olivervogel.com", + "homepage": "http://olivervogel.com/" + } + ], + "description": "Image handling and manipulation library with support for Laravel integration", + "homepage": "http://image.intervention.io/", + "keywords": [ + "gd", + "image", + "imagick", + "laravel", + "thumbnail", + "watermark" + ], + "support": { + "issues": "https://github.com/Intervention/image/issues", + "source": "https://github.com/Intervention/image/tree/2.7.1" + }, + "funding": [ + { + "url": "https://www.paypal.me/interventionphp", + "type": "custom" + }, + { + "url": "https://github.com/Intervention", + "type": "github" + } + ], + "time": "2021-12-16T16:49:26+00:00" + }, + { + "name": "lcobucci/clock", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/lcobucci/clock.git", + "reference": "353d83fe2e6ae95745b16b3d911813df6a05bfb3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lcobucci/clock/zipball/353d83fe2e6ae95745b16b3d911813df6a05bfb3", + "reference": "353d83fe2e6ae95745b16b3d911813df6a05bfb3", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "infection/infection": "^0.17", + "lcobucci/coding-standard": "^6.0", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-deprecation-rules": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/php-code-coverage": "9.1.4", + "phpunit/phpunit": "9.3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Lcobucci\\Clock\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Luís Cobucci", + "email": "lcobucci@gmail.com" + } + ], + "description": "Yet another clock abstraction", + "support": { + "issues": "https://github.com/lcobucci/clock/issues", + "source": "https://github.com/lcobucci/clock/tree/2.0.x" + }, + "funding": [ + { + "url": "https://github.com/lcobucci", + "type": "github" + }, + { + "url": "https://www.patreon.com/lcobucci", + "type": "patreon" + } + ], + "time": "2020-08-27T18:56:02+00:00" + }, + { + "name": "lcobucci/jwt", + "version": "4.1.5", + "source": { + "type": "git", + "url": "https://github.com/lcobucci/jwt.git", + "reference": "fe2d89f2eaa7087af4aa166c6f480ef04e000582" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/fe2d89f2eaa7087af4aa166c6f480ef04e000582", + "reference": "fe2d89f2eaa7087af4aa166c6f480ef04e000582", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-hash": "*", + "ext-json": "*", + "ext-mbstring": "*", + "ext-openssl": "*", + "ext-sodium": "*", + "lcobucci/clock": "^2.0", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "infection/infection": "^0.21", + "lcobucci/coding-standard": "^6.0", + "mikey179/vfsstream": "^1.6.7", + "phpbench/phpbench": "^1.0", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-deprecation-rules": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/php-invoker": "^3.1", + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Lcobucci\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Luís Cobucci", + "email": "lcobucci@gmail.com", + "role": "Developer" + } + ], + "description": "A simple library to work with JSON Web Token and JSON Web Signature", + "keywords": [ + "JWS", + "jwt" + ], + "support": { + "issues": "https://github.com/lcobucci/jwt/issues", + "source": "https://github.com/lcobucci/jwt/tree/4.1.5" + }, + "funding": [ + { + "url": "https://github.com/lcobucci", + "type": "github" + }, + { + "url": "https://www.patreon.com/lcobucci", + "type": "patreon" + } + ], + "time": "2021-09-28T19:34:56+00:00" + }, + { + "name": "league/flysystem", + "version": "1.1.9", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "094defdb4a7001845300334e7c1ee2335925ef99" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/094defdb4a7001845300334e7c1ee2335925ef99", + "reference": "094defdb4a7001845300334e7c1ee2335925ef99", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-fileinfo": "*", + "league/mime-type-detection": "^1.3", + "php": "^7.2.5 || ^8.0" + }, + "conflict": { + "league/flysystem-sftp": "<1.0.6" + }, + "require-dev": { + "phpspec/prophecy": "^1.11.1", + "phpunit/phpunit": "^8.5.8" + }, + "suggest": { + "ext-ftp": "Allows you to use FTP server storage", + "ext-openssl": "Allows you to use FTPS server storage", + "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2", + "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3", + "league/flysystem-azure": "Allows you to use Windows Azure Blob storage", + "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching", + "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem", + "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files", + "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib", + "league/flysystem-webdav": "Allows you to use WebDAV storage", + "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter", + "spatie/flysystem-dropbox": "Allows you to use Dropbox storage", + "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Filesystem abstraction: Many filesystems, one API.", + "keywords": [ + "Cloud Files", + "WebDAV", + "abstraction", + "aws", + "cloud", + "copy.com", + "dropbox", + "file systems", + "files", + "filesystem", + "filesystems", + "ftp", + "rackspace", + "remote", + "s3", + "sftp", + "storage" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem/issues", + "source": "https://github.com/thephpleague/flysystem/tree/1.1.9" + }, + "funding": [ + { + "url": "https://offset.earth/frankdejonge", + "type": "other" + } + ], + "time": "2021-12-09T09:40:50+00:00" + }, + { + "name": "league/flysystem-cached-adapter", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-cached-adapter.git", + "reference": "d1925efb2207ac4be3ad0c40b8277175f99ffaff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-cached-adapter/zipball/d1925efb2207ac4be3ad0c40b8277175f99ffaff", + "reference": "d1925efb2207ac4be3ad0c40b8277175f99ffaff", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "league/flysystem": "~1.0", + "psr/cache": "^1.0.0" + }, + "require-dev": { + "mockery/mockery": "~0.9", + "phpspec/phpspec": "^3.4", + "phpunit/phpunit": "^5.7", + "predis/predis": "~1.0", + "tedivm/stash": "~0.12" + }, + "suggest": { + "ext-phpredis": "Pure C implemented extension for PHP" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\Cached\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "frankdejonge", + "email": "info@frenky.net" + } + ], + "description": "An adapter decorator to enable meta-data caching.", + "support": { + "issues": "https://github.com/thephpleague/flysystem-cached-adapter/issues", + "source": "https://github.com/thephpleague/flysystem-cached-adapter/tree/master" + }, + "time": "2020-07-25T15:56:04+00:00" + }, + { + "name": "league/mime-type-detection", + "version": "1.9.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/mime-type-detection.git", + "reference": "aa70e813a6ad3d1558fc927863d47309b4c23e69" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/aa70e813a6ad3d1558fc927863d47309b4c23e69", + "reference": "aa70e813a6ad3d1558fc927863d47309b4c23e69", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-fileinfo": "*", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.2", + "phpstan/phpstan": "^0.12.68", + "phpunit/phpunit": "^8.5.8 || ^9.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\MimeTypeDetection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Mime-type detection for Flysystem", + "support": { + "issues": "https://github.com/thephpleague/mime-type-detection/issues", + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.9.0" + }, + "funding": [ + { + "url": "https://github.com/frankdejonge", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" + } + ], + "time": "2021-11-21T11:48:40+00:00" + }, + { + "name": "maennchen/zipstream-php", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/maennchen/ZipStream-PHP.git", + "reference": "c4c5803cc1f93df3d2448478ef79394a5981cc58" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/c4c5803cc1f93df3d2448478ef79394a5981cc58", + "reference": "c4c5803cc1f93df3d2448478ef79394a5981cc58", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "myclabs/php-enum": "^1.5", + "php": ">= 7.1", + "psr/http-message": "^1.0", + "symfony/polyfill-mbstring": "^1.0" + }, + "require-dev": { + "ext-zip": "*", + "guzzlehttp/guzzle": ">= 6.3", + "mikey179/vfsstream": "^1.6", + "phpunit/phpunit": ">= 7.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "ZipStream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paul Duncan", + "email": "pabs@pablotron.org" + }, + { + "name": "Jonatan Männchen", + "email": "jonatan@maennchen.ch" + }, + { + "name": "Jesse Donat", + "email": "donatj@gmail.com" + }, + { + "name": "András Kolesár", + "email": "kolesar@kolesar.hu" + } + ], + "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.", + "keywords": [ + "stream", + "zip" + ], + "support": { + "issues": "https://github.com/maennchen/ZipStream-PHP/issues", + "source": "https://github.com/maennchen/ZipStream-PHP/tree/master" + }, + "funding": [ + { + "url": "https://opencollective.com/zipstream", + "type": "open_collective" + } + ], + "time": "2020-05-30T13:11:16+00:00" + }, + { + "name": "markbaker/complex", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPComplex.git", + "reference": "ab8bc271e404909db09ff2d5ffa1e538085c0f22" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/ab8bc271e404909db09ff2d5ffa1e538085c0f22", + "reference": "ab8bc271e404909db09ff2d5ffa1e538085c0f22", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "phpcompatibility/php-compatibility": "^9.0", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.3", + "squizlabs/php_codesniffer": "^3.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Complex\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@lange.demon.co.uk" + } + ], + "description": "PHP Class for working with complex numbers", + "homepage": "https://github.com/MarkBaker/PHPComplex", + "keywords": [ + "complex", + "mathematics" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPComplex/issues", + "source": "https://github.com/MarkBaker/PHPComplex/tree/3.0.1" + }, + "time": "2021-06-29T15:32:53+00:00" + }, + { + "name": "markbaker/matrix", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPMatrix.git", + "reference": "c66aefcafb4f6c269510e9ac46b82619a904c576" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/c66aefcafb4f6c269510e9ac46b82619a904c576", + "reference": "c66aefcafb4f6c269510e9ac46b82619a904c576", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "phpcompatibility/php-compatibility": "^9.0", + "phpdocumentor/phpdocumentor": "2.*", + "phploc/phploc": "^4.0", + "phpmd/phpmd": "2.*", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.3", + "sebastian/phpcpd": "^4.0", + "squizlabs/php_codesniffer": "^3.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Matrix\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@demon-angel.eu" + } + ], + "description": "PHP Class for working with matrices", + "homepage": "https://github.com/MarkBaker/PHPMatrix", + "keywords": [ + "mathematics", + "matrix", + "vector" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPMatrix/issues", + "source": "https://github.com/MarkBaker/PHPMatrix/tree/3.0.0" + }, + "time": "2021-07-01T19:01:15+00:00" + }, + { + "name": "monolog/monolog", + "version": "2.3.5", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "fd4380d6fc37626e2f799f29d91195040137eba9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/fd4380d6fc37626e2f799f29d91195040137eba9", + "reference": "fd4380d6fc37626e2f799f29d91195040137eba9", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "provide": { + "psr/log-implementation": "1.0.0 || 2.0.0 || 3.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7", + "graylog2/gelf-php": "^1.4.2", + "mongodb/mongodb": "^1.8", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "php-console/php-console": "^3.1.3", + "phpspec/prophecy": "^1.6.1", + "phpstan/phpstan": "^0.12.91", + "phpunit/phpunit": "^8.5", + "predis/predis": "^1.1", + "rollbar/rollbar": "^1.3", + "ruflin/elastica": ">=0.90@dev", + "swiftmailer/swiftmailer": "^5.3|^6.0" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "php-console/php-console": "Allow sending log messages to Google Chrome", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "https://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/2.3.5" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2021-10-01T21:08:31+00:00" + }, + { + "name": "myclabs/php-enum", + "version": "1.8.3", + "source": { + "type": "git", + "url": "https://github.com/myclabs/php-enum.git", + "reference": "b942d263c641ddb5190929ff840c68f78713e937" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/php-enum/zipball/b942d263c641ddb5190929ff840c68f78713e937", + "reference": "b942d263c641ddb5190929ff840c68f78713e937", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "1.*", + "vimeo/psalm": "^4.6.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "MyCLabs\\Enum\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP Enum contributors", + "homepage": "https://github.com/myclabs/php-enum/graphs/contributors" + } + ], + "description": "PHP Enum implementation", + "homepage": "http://github.com/myclabs/php-enum", + "keywords": [ + "enum" + ], + "support": { + "issues": "https://github.com/myclabs/php-enum/issues", + "source": "https://github.com/myclabs/php-enum/tree/1.8.3" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/php-enum", + "type": "tidelift" + } + ], + "time": "2021-07-05T08:18:36+00:00" + }, + { + "name": "nesbot/carbon", + "version": "2.55.2", + "source": { + "type": "git", + "url": "https://github.com/briannesbitt/Carbon.git", + "reference": "8c2a18ce3e67c34efc1b29f64fe61304368259a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/8c2a18ce3e67c34efc1b29f64fe61304368259a2", + "reference": "8c2a18ce3e67c34efc1b29f64fe61304368259a2", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "php": "^7.1.8 || ^8.0", + "symfony/polyfill-mbstring": "^1.0", + "symfony/polyfill-php80": "^1.16", + "symfony/translation": "^3.4 || ^4.0 || ^5.0 || ^6.0" + }, + "require-dev": { + "doctrine/dbal": "^2.0 || ^3.0", + "doctrine/orm": "^2.7", + "friendsofphp/php-cs-fixer": "^3.0", + "kylekatarnls/multi-tester": "^2.0", + "phpmd/phpmd": "^2.9", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.54", + "phpunit/phpunit": "^7.5.20 || ^8.5.14", + "squizlabs/php_codesniffer": "^3.4" + }, + "bin": [ + "bin/carbon" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-3.x": "3.x-dev", + "dev-master": "2.x-dev" + }, + "laravel": { + "providers": [ + "Carbon\\Laravel\\ServiceProvider" + ] + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "psr-4": { + "Carbon\\": "src/Carbon/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "https://markido.com" + }, + { + "name": "kylekatarnls", + "homepage": "https://github.com/kylekatarnls" + } + ], + "description": "An API extension for DateTime that supports 281 different languages.", + "homepage": "https://carbon.nesbot.com", + "keywords": [ + "date", + "datetime", + "time" + ], + "support": { + "docs": "https://carbon.nesbot.com/docs", + "issues": "https://github.com/briannesbitt/Carbon/issues", + "source": "https://github.com/briannesbitt/Carbon" + }, + "funding": [ + { + "url": "https://opencollective.com/Carbon", + "type": "open_collective" + }, + { + "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon", + "type": "tidelift" + } + ], + "time": "2021-12-03T14:59:52+00:00" + }, + { + "name": "overtrue/socialite", + "version": "3.5.0", + "source": { + "type": "git", + "url": "https://github.com/overtrue/socialite.git", + "reference": "1607bc8aa95235b1538b3dc4a36324ee2cdf0df5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/overtrue/socialite/zipball/1607bc8aa95235b1538b3dc4a36324ee2cdf0df5", + "reference": "1607bc8aa95235b1538b3dc4a36324ee2cdf0df5", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "ext-openssl": "*", + "guzzlehttp/guzzle": "~6.0|~7.0", + "php": ">=7.4", + "symfony/http-foundation": "^5.0", + "symfony/psr-http-message-bridge": "^2.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.0", + "mockery/mockery": "~1.2", + "phpunit/phpunit": "~9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Overtrue\\Socialite\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "overtrue", + "email": "anzhengchao@gmail.com" + } + ], + "description": "A collection of OAuth 2 packages.", + "keywords": [ + "Feishu", + "login", + "oauth", + "qcloud", + "qq", + "social", + "wechat", + "weibo" + ], + "support": { + "issues": "https://github.com/overtrue/socialite/issues", + "source": "https://github.com/overtrue/socialite/tree/3.5.0" + }, + "funding": [ + { + "url": "https://github.com/overtrue", + "type": "github" + } + ], + "time": "2021-11-24T03:52:33+00:00" + }, + { + "name": "overtrue/wechat", + "version": "5.12.0", + "source": { + "type": "git", + "url": "https://github.com/w7corp/easywechat.git", + "reference": "9fb3e56e1522c46a648d05edec7970b0b3447d5c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/w7corp/easywechat/zipball/9fb3e56e1522c46a648d05edec7970b0b3447d5c", + "reference": "9fb3e56e1522c46a648d05edec7970b0b3447d5c", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "easywechat-composer/easywechat-composer": "^1.1", + "ext-fileinfo": "*", + "ext-libxml": "*", + "ext-openssl": "*", + "ext-simplexml": "*", + "guzzlehttp/guzzle": "^6.2 || ^7.0", + "monolog/monolog": "^1.22 || ^2.0", + "overtrue/socialite": "^3.2", + "php": ">=7.4", + "pimple/pimple": "^3.0", + "psr/simple-cache": "^1.0", + "symfony/cache": "^3.3 || ^4.3 || ^5.0", + "symfony/event-dispatcher": "^4.3 || ^5.0", + "symfony/http-foundation": "^2.7 || ^3.0 || ^4.0 || ^5.0", + "symfony/psr-http-message-bridge": "^0.3 || ^1.0 || ^2.0" + }, + "require-dev": { + "brainmaestro/composer-git-hooks": "^2.7", + "dms/phpunit-arraysubset-asserts": "^0.2.0", + "friendsofphp/php-cs-fixer": "^2.15", + "mikey179/vfsstream": "^1.6", + "mockery/mockery": "^1.2.3", + "phpstan/phpstan": "^0.12.0", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "hooks": { + "pre-commit": [ + "composer test", + "composer fix-style" + ], + "pre-push": [ + "composer test", + "composer fix-style" + ] + } + }, + "autoload": { + "psr-4": { + "EasyWeChat\\": "src/" + }, + "files": [ + "src/Kernel/Support/Helpers.php", + "src/Kernel/Helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "overtrue", + "email": "anzhengchao@gmail.com" + } + ], + "description": "微信SDK", + "keywords": [ + "easywechat", + "sdk", + "wechat", + "weixin", + "weixin-sdk" + ], + "support": { + "issues": "https://github.com/w7corp/easywechat/issues", + "source": "https://github.com/w7corp/easywechat/tree/5.12.0" + }, + "funding": [ + { + "url": "https://github.com/overtrue", + "type": "github" + } + ], + "time": "2021-12-20T08:08:37+00:00" + }, + { + "name": "phpoffice/phpspreadsheet", + "version": "1.20.0", + "source": { + "type": "git", + "url": "https://github.com/PHPOffice/PhpSpreadsheet.git", + "reference": "44436f270bb134b4a94670f3d020a85dfa0a3c02" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/44436f270bb134b4a94670f3d020a85dfa0a3c02", + "reference": "44436f270bb134b4a94670f3d020a85dfa0a3c02", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-ctype": "*", + "ext-dom": "*", + "ext-fileinfo": "*", + "ext-gd": "*", + "ext-iconv": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-xml": "*", + "ext-xmlreader": "*", + "ext-xmlwriter": "*", + "ext-zip": "*", + "ext-zlib": "*", + "ezyang/htmlpurifier": "^4.13", + "maennchen/zipstream-php": "^2.1", + "markbaker/complex": "^3.0", + "markbaker/matrix": "^3.0", + "php": "^7.3 || ^8.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "psr/simple-cache": "^1.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "dompdf/dompdf": "^1.0", + "friendsofphp/php-cs-fixer": "^3.2", + "jpgraph/jpgraph": "^4.0", + "mpdf/mpdf": "^8.0", + "phpcompatibility/php-compatibility": "^9.3", + "phpstan/phpstan": "^1.1", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^8.5 || ^9.0", + "squizlabs/php_codesniffer": "^3.6", + "tecnickcom/tcpdf": "^6.4" + }, + "suggest": { + "dompdf/dompdf": "Option for rendering PDF with PDF Writer (doesn't yet support PHP8)", + "jpgraph/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers", + "mpdf/mpdf": "Option for rendering PDF with PDF Writer", + "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer (doesn't yet support PHP8)" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Maarten Balliauw", + "homepage": "https://blog.maartenballiauw.be" + }, + { + "name": "Mark Baker", + "homepage": "https://markbakeruk.net" + }, + { + "name": "Franck Lefevre", + "homepage": "https://rootslabs.net" + }, + { + "name": "Erik Tilt" + }, + { + "name": "Adrien Crivelli" + } + ], + "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine", + "homepage": "https://github.com/PHPOffice/PhpSpreadsheet", + "keywords": [ + "OpenXML", + "excel", + "gnumeric", + "ods", + "php", + "spreadsheet", + "xls", + "xlsx" + ], + "support": { + "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues", + "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.20.0" + }, + "time": "2021-11-23T15:23:42+00:00" + }, + { + "name": "pimple/pimple", + "version": "v3.5.0", + "source": { + "type": "git", + "url": "https://github.com/silexphp/Pimple.git", + "reference": "a94b3a4db7fb774b3d78dad2315ddc07629e1bed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silexphp/Pimple/zipball/a94b3a4db7fb774b3d78dad2315ddc07629e1bed", + "reference": "a94b3a4db7fb774b3d78dad2315ddc07629e1bed", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1 || ^2.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^5.4@dev" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4.x-dev" + } + }, + "autoload": { + "psr-0": { + "Pimple": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Pimple, a simple Dependency Injection Container", + "homepage": "https://pimple.symfony.com", + "keywords": [ + "container", + "dependency injection" + ], + "support": { + "source": "https://github.com/silexphp/Pimple/tree/v3.5.0" + }, + "time": "2021-10-28T11:13:42+00:00" + }, + { + "name": "psr/cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/master" + }, + "time": "2016-08-06T20:24:11+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client/tree/master" + }, + "time": "2020-06-29T06:28:15+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/log", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.4" + }, + "time": "2021-05-03T11:20:27+00:00" + }, + { + "name": "psr/simple-cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/master" + }, + "time": "2017-10-23T01:57:42+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "s1lentium/iptools", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/S1lentium/IPTools.git", + "reference": "f6f8ab6132ca7443bd7cced1681f5066d725fd5f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/S1lentium/IPTools/zipball/f6f8ab6132ca7443bd7cced1681f5066d725fd5f", + "reference": "f6f8ab6132ca7443bd7cced1681f5066d725fd5f", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-bcmath": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0", + "satooshi/php-coveralls": "~1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "IPTools\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Safarov Alisher", + "email": "alisher.safarov@outlook.com", + "homepage": "https://github.com/S1lentium" + } + ], + "description": "PHP Library for manipulating network addresses (IPv4 and IPv6)", + "keywords": [ + "IP", + "IP-Tools", + "cidr", + "ipv4", + "ipv6", + "network", + "subnet" + ], + "support": { + "issues": "https://github.com/S1lentium/IPTools/issues", + "source": "https://github.com/S1lentium/IPTools/tree/master" + }, + "time": "2018-09-19T06:15:53+00:00" + }, + { + "name": "symfony/cache", + "version": "v5.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "d97d6d7f46cb69968f094e329abd987d5ee17c79" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/d97d6d7f46cb69968f094e329abd987d5ee17c79", + "reference": "d97d6d7f46cb69968f094e329abd987d5ee17c79", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "psr/cache": "^1.0|^2.0", + "psr/log": "^1.1|^2|^3", + "symfony/cache-contracts": "^1.1.7|^2", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php73": "^1.9", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/var-exporter": "^4.4|^5.0|^6.0" + }, + "conflict": { + "doctrine/dbal": "<2.13.1", + "symfony/dependency-injection": "<4.4", + "symfony/http-kernel": "<4.4", + "symfony/var-dumper": "<4.4" + }, + "provide": { + "psr/cache-implementation": "1.0|2.0", + "psr/simple-cache-implementation": "1.0|2.0", + "symfony/cache-implementation": "1.0|2.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/cache": "^1.6|^2.0", + "doctrine/dbal": "^2.13.1|^3.0", + "predis/predis": "^1.1", + "psr/simple-cache": "^1.0|^2.0", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/filesystem": "^4.4|^5.0|^6.0", + "symfony/http-kernel": "^4.4|^5.0|^6.0", + "symfony/messenger": "^4.4|^5.0|^6.0", + "symfony/var-dumper": "^4.4|^5.0|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Cache\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an extended PSR-6, PSR-16 (and tags) implementation", + "homepage": "https://symfony.com", + "keywords": [ + "caching", + "psr6" + ], + "support": { + "source": "https://github.com/symfony/cache/tree/v5.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-23T18:51:45+00:00" + }, + { + "name": "symfony/cache-contracts", + "version": "v2.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "ac2e168102a2e06a2624f0379bde94cd5854ced2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/ac2e168102a2e06a2624f0379bde94cd5854ced2", + "reference": "ac2e168102a2e06a2624f0379bde94cd5854ced2", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "psr/cache": "^1.0|^2.0|^3.0" + }, + "suggest": { + "symfony/cache-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Cache\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to caching", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/cache-contracts/tree/v2.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-08-17T14:20:01+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/6f981ee24cf69ee7ce9736146d1c57c2780598a8", + "reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-07-12T14:48:14+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v5.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "27d39ae126352b9fa3be5e196ccf4617897be3eb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/27d39ae126352b9fa3be5e196ccf4617897be3eb", + "reference": "27d39ae126352b9fa3be5e196ccf4617897be3eb", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/event-dispatcher-contracts": "^2|^3", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "symfony/dependency-injection": "<4.4" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/error-handler": "^4.4|^5.0|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/http-foundation": "^4.4|^5.0|^6.0", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/stopwatch": "^4.4|^5.0|^6.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-23T10:19:22+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v2.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "66bea3b09be61613cd3b4043a65a8ec48cfa6d2a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/66bea3b09be61613cd3b4043a65a8ec48cfa6d2a", + "reference": "66bea3b09be61613cd3b4043a65a8ec48cfa6d2a", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "psr/event-dispatcher": "^1" + }, + "suggest": { + "symfony/event-dispatcher-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-07-12T14:48:14+00:00" + }, + { + "name": "symfony/expression-language", + "version": "v5.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/expression-language.git", + "reference": "aff6ee3cf4ac1f37f5c7dad3f89f439dbe0893f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/expression-language/zipball/aff6ee3cf4ac1f37f5c7dad3f89f439dbe0893f2", + "reference": "aff6ee3cf4ac1f37f5c7dad3f89f439dbe0893f2", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "symfony/cache": "^4.4|^5.0|^6.0", + "symfony/service-contracts": "^1.1|^2|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\ExpressionLanguage\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an engine that can compile and evaluate expressions", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/expression-language/tree/v5.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-23T10:19:22+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v5.4.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "5dad3780023a707f4c24beac7d57aead85c1ce3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/5dad3780023a707f4c24beac7d57aead85c1ce3c", + "reference": "5dad3780023a707f4c24beac7d57aead85c1ce3c", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "predis/predis": "~1.0", + "symfony/cache": "^4.4|^5.0|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/mime": "^4.4|^5.0|^6.0" + }, + "suggest": { + "symfony/mime": "To use the file extension guesser" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Defines an object-oriented layer for the HTTP specification", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-foundation/tree/v5.4.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-12-09T12:46:57+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.23.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9174a3d80210dca8daa7f31fec659150bbeabfc6", + "reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.23.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-05-27T12:26:48+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.23.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fba8933c384d6476ab14fb7b8526e5287ca7e010", + "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.23.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-02-19T12:13:01+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.23.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "1100343ed1a92e3a38f9ae122fc0eb21602547be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/1100343ed1a92e3a38f9ae122fc0eb21602547be", + "reference": "1100343ed1a92e3a38f9ae122fc0eb21602547be", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.23.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-07-28T13:41:28+00:00" + }, + { + "name": "symfony/process", + "version": "v4.4.35", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "c2098705326addae6e6742151dfade47ac71da1b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/c2098705326addae6e6742151dfade47ac71da1b", + "reference": "c2098705326addae6e6742151dfade47ac71da1b", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1.3", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v4.4.35" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-22T22:36:24+00:00" + }, + { + "name": "symfony/psr-http-message-bridge", + "version": "v2.1.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/psr-http-message-bridge.git", + "reference": "22b37c8a3f6b5d94e9cdbd88e1270d96e2f97b34" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/22b37c8a3f6b5d94e9cdbd88e1270d96e2f97b34", + "reference": "22b37c8a3f6b5d94e9cdbd88e1270d96e2f97b34", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0", + "symfony/http-foundation": "^4.4 || ^5.0 || ^6.0" + }, + "require-dev": { + "nyholm/psr7": "^1.1", + "psr/log": "^1.1 || ^2 || ^3", + "symfony/browser-kit": "^4.4 || ^5.0 || ^6.0", + "symfony/config": "^4.4 || ^5.0 || ^6.0", + "symfony/event-dispatcher": "^4.4 || ^5.0 || ^6.0", + "symfony/framework-bundle": "^4.4 || ^5.0 || ^6.0", + "symfony/http-kernel": "^4.4 || ^5.0 || ^6.0", + "symfony/phpunit-bridge": "^5.4@dev || ^6.0" + }, + "suggest": { + "nyholm/psr7": "For a super lightweight PSR-7/17 implementation" + }, + "type": "symfony-bridge", + "extra": { + "branch-alias": { + "dev-main": "2.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Bridge\\PsrHttpMessage\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "PSR HTTP message bridge", + "homepage": "http://symfony.com", + "keywords": [ + "http", + "http-message", + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/symfony/psr-http-message-bridge/issues", + "source": "https://github.com/symfony/psr-http-message-bridge/tree/v2.1.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-05T13:13:39+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc", + "reference": "1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v2.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-04T16:48:04+00:00" + }, + { + "name": "symfony/translation", + "version": "v5.4.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "8c82cd35ed861236138d5ae1c78c0c7ebcd62107" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/8c82cd35ed861236138d5ae1c78c0c7ebcd62107", + "reference": "8c82cd35ed861236138d5ae1c78c0c7ebcd62107", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "^1.16", + "symfony/translation-contracts": "^2.3" + }, + "conflict": { + "symfony/config": "<4.4", + "symfony/console": "<5.3", + "symfony/dependency-injection": "<5.0", + "symfony/http-kernel": "<5.0", + "symfony/twig-bundle": "<5.0", + "symfony/yaml": "<4.4" + }, + "provide": { + "symfony/translation-implementation": "2.3" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/console": "^5.4|^6.0", + "symfony/dependency-injection": "^5.0|^6.0", + "symfony/finder": "^4.4|^5.0|^6.0", + "symfony/http-client-contracts": "^1.1|^2.0|^3.0", + "symfony/http-kernel": "^5.0|^6.0", + "symfony/intl": "^4.4|^5.0|^6.0", + "symfony/polyfill-intl-icu": "^1.21", + "symfony/service-contracts": "^1.1.2|^2|^3", + "symfony/yaml": "^4.4|^5.0|^6.0" + }, + "suggest": { + "psr/log-implementation": "To use logging capability in translator", + "symfony/config": "", + "symfony/yaml": "" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to internationalize your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/translation/tree/v5.4.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-12-05T20:33:52+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v2.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "d28150f0f44ce854e942b671fc2620a98aae1b1e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/d28150f0f44ce854e942b671fc2620a98aae1b1e", + "reference": "d28150f0f44ce854e942b671fc2620a98aae1b1e", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5" + }, + "suggest": { + "symfony/translation-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/translation-contracts/tree/v2.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-08-17T14:20:01+00:00" + }, + { + "name": "symfony/var-exporter", + "version": "v5.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "d59446d6166b1643a8a3c30c2fa8e16e51cdbde7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/d59446d6166b1643a8a3c30c2fa8e16e51cdbde7", + "reference": "d59446d6166b1643a8a3c30c2fa8e16e51cdbde7", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "symfony/var-dumper": "^4.4.9|^5.0.9|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v5.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-22T10:44:13+00:00" + }, + { + "name": "topthink/framework", + "version": "v6.0.9", + "source": { + "type": "git", + "url": "https://github.com/top-think/framework.git", + "reference": "0b5fb453f0e533de3af3a1ab6a202510b61be617" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/framework/zipball/0b5fb453f0e533de3af3a1ab6a202510b61be617", + "reference": "0b5fb453f0e533de3af3a1ab6a202510b61be617", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "ext-mbstring": "*", + "league/flysystem": "^1.1.4", + "league/flysystem-cached-adapter": "^1.0", + "php": ">=7.2.5", + "psr/container": "~1.0", + "psr/log": "~1.0", + "psr/simple-cache": "^1.0", + "topthink/think-helper": "^3.1.1", + "topthink/think-orm": "^2.0" + }, + "require-dev": { + "mikey179/vfsstream": "^1.6", + "mockery/mockery": "^1.2", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "autoload": { + "files": [], + "psr-4": { + "think\\": "src/think/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + }, + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "description": "The ThinkPHP Framework.", + "homepage": "http://thinkphp.cn/", + "keywords": [ + "framework", + "orm", + "thinkphp" + ], + "support": { + "issues": "https://github.com/top-think/framework/issues", + "source": "https://github.com/top-think/framework/tree/v6.0.9" + }, + "time": "2021-07-22T03:24:49+00:00" + }, + { + "name": "topthink/think-captcha", + "version": "v3.0.3", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-captcha.git", + "reference": "1eef3717c1bcf4f5bbe2d1a1c704011d330a8b55" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-captcha/zipball/1eef3717c1bcf4f5bbe2d1a1c704011d330a8b55", + "reference": "1eef3717c1bcf4f5bbe2d1a1c704011d330a8b55", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "topthink/framework": "^6.0.0" + }, + "type": "library", + "extra": { + "think": { + "services": [ + "think\\captcha\\CaptchaService" + ], + "config": { + "captcha": "src/config.php" + } + } + }, + "autoload": { + "psr-4": { + "think\\captcha\\": "src/" + }, + "files": [ + "src/helper.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "description": "captcha package for thinkphp", + "support": { + "issues": "https://github.com/top-think/think-captcha/issues", + "source": "https://github.com/top-think/think-captcha/tree/v3.0.3" + }, + "time": "2020-05-19T10:55:45+00:00" + }, + { + "name": "topthink/think-helper", + "version": "v3.1.5", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-helper.git", + "reference": "f98e3ad44acd27ae85a4d923b1bdfd16c6d8d905" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-helper/zipball/f98e3ad44acd27ae85a4d923b1bdfd16c6d8d905", + "reference": "f98e3ad44acd27ae85a4d923b1bdfd16c6d8d905", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "think\\": "src" + }, + "files": [ + "src/helper.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "description": "The ThinkPHP6 Helper Package", + "support": { + "issues": "https://github.com/top-think/think-helper/issues", + "source": "https://github.com/top-think/think-helper/tree/v3.1.5" + }, + "time": "2021-06-21T06:17:31+00:00" + }, + { + "name": "topthink/think-image", + "version": "v1.0.7", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-image.git", + "reference": "8586cf47f117481c6d415b20f7dedf62e79d5512" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-image/zipball/8586cf47f117481c6d415b20f7dedf62e79d5512", + "reference": "8586cf47f117481c6d415b20f7dedf62e79d5512", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-gd": "*" + }, + "require-dev": { + "phpunit/phpunit": "4.8.*", + "topthink/framework": "^5.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "think\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "description": "The ThinkPHP5 Image Package", + "support": { + "issues": "https://github.com/top-think/think-image/issues", + "source": "https://github.com/top-think/think-image/tree/master" + }, + "time": "2016-09-29T06:05:43+00:00" + }, + { + "name": "topthink/think-migration", + "version": "v3.0.3", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-migration.git", + "reference": "5717d9e5f3ea745f6dbfd1e30b4402aaadff9a79" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-migration/zipball/5717d9e5f3ea745f6dbfd1e30b4402aaadff9a79", + "reference": "5717d9e5f3ea745f6dbfd1e30b4402aaadff9a79", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "topthink/framework": "^6.0.0", + "topthink/think-helper": "^3.0.3" + }, + "require-dev": { + "fzaninotto/faker": "^1.8" + }, + "suggest": { + "fzaninotto/faker": "Required to use the factory builder (^1.8)." + }, + "type": "library", + "extra": { + "think": { + "services": [ + "think\\migration\\Service" + ] + } + }, + "autoload": { + "psr-4": { + "Phinx\\": "phinx/src/Phinx", + "think\\migration\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "support": { + "issues": "https://github.com/top-think/think-migration/issues", + "source": "https://github.com/top-think/think-migration/tree/v3.0.3" + }, + "time": "2020-12-07T05:54:22+00:00" + }, + { + "name": "topthink/think-orm", + "version": "v2.0.45", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-orm.git", + "reference": "3dcf9af447b048103093840833e8c74ab849152f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-orm/zipball/3dcf9af447b048103093840833e8c74ab849152f", + "reference": "3dcf9af447b048103093840833e8c74ab849152f", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "ext-pdo": "*", + "php": ">=7.1.0", + "psr/log": "~1.0", + "psr/simple-cache": "^1.0", + "topthink/think-helper": "^3.1" + }, + "require-dev": { + "phpunit/phpunit": "^7|^8|^9.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "think\\": "src" + }, + "files": [ + "stubs/load_stubs.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "think orm", + "keywords": [ + "database", + "orm" + ], + "support": { + "issues": "https://github.com/top-think/think-orm/issues", + "source": "https://github.com/top-think/think-orm/tree/v2.0.45" + }, + "time": "2021-11-30T14:31:05+00:00" + }, + { + "name": "topthink/think-queue", + "version": "v3.0.6", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-queue.git", + "reference": "a9f81126bdd52d036461e0c6556592dd478c8728" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-queue/zipball/a9f81126bdd52d036461e0c6556592dd478c8728", + "reference": "a9f81126bdd52d036461e0c6556592dd478c8728", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "nesbot/carbon": "^2.16", + "symfony/process": "^4.2", + "topthink/framework": "^6.0" + }, + "require-dev": { + "mockery/mockery": "^1.2", + "phpunit/phpunit": "^6.2", + "topthink/think-migration": "^3.0.0" + }, + "type": "library", + "extra": { + "think": { + "services": [ + "think\\queue\\Service" + ], + "config": { + "queue": "src/config.php" + } + } + }, + "autoload": { + "psr-4": { + "think\\": "src" + }, + "files": [ + "src/common.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "description": "The ThinkPHP6 Queue Package", + "support": { + "issues": "https://github.com/top-think/think-queue/issues", + "source": "https://github.com/top-think/think-queue/tree/v3.0.6" + }, + "time": "2021-06-24T18:03:45+00:00" + }, + { + "name": "topthink/think-template", + "version": "v2.0.8", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-template.git", + "reference": "abfc293f74f9ef5127b5c416310a01fe42e59368" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-template/zipball/abfc293f74f9ef5127b5c416310a01fe42e59368", + "reference": "abfc293f74f9ef5127b5c416310a01fe42e59368", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1.0", + "psr/simple-cache": "^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "think\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "the php template engine", + "support": { + "issues": "https://github.com/top-think/think-template/issues", + "source": "https://github.com/top-think/think-template/tree/v2.0.8" + }, + "time": "2020-12-10T07:52:03+00:00" + }, + { + "name": "topthink/think-view", + "version": "v1.0.14", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-view.git", + "reference": "edce0ae2c9551ab65f9e94a222604b0dead3576d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-view/zipball/edce0ae2c9551ab65f9e94a222604b0dead3576d", + "reference": "edce0ae2c9551ab65f9e94a222604b0dead3576d", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1.0", + "topthink/think-template": "^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "think\\view\\driver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "thinkphp template driver", + "support": { + "issues": "https://github.com/top-think/think-view/issues", + "source": "https://github.com/top-think/think-view/tree/v1.0.14" + }, + "time": "2019-11-06T11:40:13+00:00" + }, + { + "name": "yansongda/pay", + "version": "v2.10.2", + "source": { + "type": "git", + "url": "https://github.com/yansongda/pay.git", + "reference": "8c258853b142c6d7589629b047ca5cb21232b509" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yansongda/pay/zipball/8c258853b142c6d7589629b047ca5cb21232b509", + "reference": "8c258853b142c6d7589629b047ca5cb21232b509", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-bcmath": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-openssl": "*", + "ext-simplexml": "*", + "php": ">=7.1.3", + "symfony/event-dispatcher": "^4.0 || ^5.0", + "symfony/http-foundation": "^4.0 || ^5.0.7", + "yansongda/supports": "^2.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.15", + "mockery/mockery": "^1.2", + "phpunit/phpunit": "^7.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Yansongda\\Pay\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "yansongda", + "email": "me@yansongda.cn" + } + ], + "description": "专注 Alipay 和 WeChat 的支付扩展包", + "keywords": [ + "alipay", + "pay", + "wechat" + ], + "support": { + "issues": "https://github.com/yansongda/pay/issues", + "source": "https://github.com/yansongda/pay" + }, + "time": "2021-01-18T01:48:43+00:00" + }, + { + "name": "yansongda/supports", + "version": "v2.2.0", + "source": { + "type": "git", + "url": "https://github.com/yansongda/supports.git", + "reference": "de9a8d38b0461ddf9c12f27390dad9a40c9b4e3b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yansongda/supports/zipball/de9a8d38b0461ddf9c12f27390dad9a40c9b4e3b", + "reference": "de9a8d38b0461ddf9c12f27390dad9a40c9b4e3b", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "guzzlehttp/guzzle": "^6.2 || ^7.0", + "monolog/monolog": "^1.23 || ^2.0", + "php": ">=7.1.3" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.15", + "phpunit/phpunit": "^7.5", + "predis/predis": "^1.1" + }, + "suggest": { + "predis/predis": "Allows to use throttle feature" + }, + "type": "library", + "autoload": { + "psr-4": { + "Yansongda\\Supports\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "yansongda", + "email": "me@yansongda.cn" + } + ], + "description": "common components", + "keywords": [ + "Guzzle", + "array", + "collection", + "config", + "http", + "support", + "throttle" + ], + "support": { + "issues": "https://github.com/yansongda/supports/issues", + "source": "https://github.com/yansongda/supports" + }, + "time": "2020-10-14T08:17:18+00:00" + } + ], + "packages-dev": [ + { + "name": "symfony/polyfill-php72", + "version": "v1.23.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "9a142215a36a3888e30d0a9eeea9766764e96976" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/9a142215a36a3888e30d0a9eeea9766764e96976", + "reference": "9a142215a36a3888e30d0a9eeea9766764e96976", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php72/tree/v1.23.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-05-27T09:17:38+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v4.4.34", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "2d0c056b2faaa3d785bdbd5adecc593a5be9c16e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/2d0c056b2faaa3d785bdbd5adecc593a5be9c16e", + "reference": "2d0c056b2faaa3d785bdbd5adecc593a5be9c16e", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1.3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php72": "~1.5", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", + "symfony/console": "<3.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^3.4|^4.0|^5.0", + "symfony/process": "^4.4|^5.0", + "twig/twig": "^1.43|^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v4.4.34" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-12T10:50:54+00:00" + }, + { + "name": "topthink/think-trace", + "version": "v1.4", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-trace.git", + "reference": "9a9fa8f767b6c66c5a133ad21ca1bc96ad329444" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-trace/zipball/9a9fa8f767b6c66c5a133ad21ca1bc96ad329444", + "reference": "9a9fa8f767b6c66c5a133ad21ca1bc96ad329444", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1.0", + "topthink/framework": "^6.0.0" + }, + "type": "library", + "extra": { + "think": { + "services": [ + "think\\trace\\Service" + ], + "config": { + "trace": "src/config.php" + } + } + }, + "autoload": { + "psr-4": { + "think\\trace\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "thinkphp debug trace", + "support": { + "issues": "https://github.com/top-think/think-trace/issues", + "source": "https://github.com/top-think/think-trace/tree/v1.4" + }, + "time": "2020-06-29T05:27:28+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=7.4.0", + "ext-json": "*", + "ext-curl": "*", + "ext-bcmath": "*" + }, + "platform-dev": [], + "plugin-api-version": "2.2.0" +} diff --git a/config/app.php b/config/app.php new file mode 100644 index 0000000..2bdef3b --- /dev/null +++ b/config/app.php @@ -0,0 +1,40 @@ + Env::get('app.host', ''), + // 应用的命名空间 + 'app_namespace' => '', + // 是否启用路由 + 'with_route' => true, + // 是否启用事件 + 'with_event' => true, + // 自动多应用模式 + 'auto_multi_app' => true, + // 应用映射(自动多应用模式有效) + 'app_map' => [], + // 域名绑定(自动多应用模式有效) + 'domain_bind' => [], + // 禁止URL访问的应用列表(自动多应用模式有效) + 'deny_app_list' => [], + // 默认应用 + 'default_app' => 'index', + // 默认时区 + 'default_timezone' => 'Asia/Shanghai', + + // 异常页面的模板文件 + 'exception_tmpl' => app()->getThinkPath() . 'tpl/think_exception.tpl', + + /* + 'http_exception_template' => [ + 400 => '', + 500 => app()->getThinkPath() . 'tpl/think_exception.tpl', + ], + */ + + // 错误显示信息,非调试模式有效 + 'error_message' => '页面错误!请稍后再试~', + // 显示错误信息 + 'show_error_msg' => true, +]; diff --git a/config/cache.php b/config/cache.php new file mode 100644 index 0000000..d1c9715 --- /dev/null +++ b/config/cache.php @@ -0,0 +1,37 @@ + Env::get('cache.driver', 'file'), + + // 缓存连接方式配置 + 'stores' => [ + 'file' => [ + // 驱动方式 + 'type' => 'File', + // 缓存保存目录 + 'path' => '', + // 缓存前缀 + 'prefix' => '', + // 缓存有效期 0表示永久缓存 + 'expire' => 0, + // 缓存标签前缀 + 'tag_prefix' => 'tag:', + // 序列化机制 例如 ['serialize', 'unserialize'] + 'serialize' => [], + ], + // redis缓存 + 'redis' => [ + // 驱动方式 + 'type' => 'redis', + // 服务器地址 + 'host' => env('redis.host') ?? '127.0.0.1', + ], + // 更多的缓存连接 + ], +]; diff --git a/config/captcha.php b/config/captcha.php new file mode 100644 index 0000000..3870042 --- /dev/null +++ b/config/captcha.php @@ -0,0 +1,39 @@ + 4, + // 验证码字符集合 + 'codeSet' => '2345678abcdefhijkmnpqrstuvwxyzABCDEFGHJKLMNPQRTUVWXY', + // 验证码过期时间 + 'expire' => 1800, + // 是否使用中文验证码 + 'useZh' => false, + // 是否使用算术验证码 + 'math' => false, + // 是否使用背景图 + 'useImgBg' => false, + //验证码字符大小 + 'fontSize' => 30, + // 是否使用混淆曲线 + 'useCurve' => false, + //是否添加杂点 + 'useNoise' => true, + // 验证码字体 不设置则随机 + 'fontttf' => '', + //背景颜色 + 'bg' => [243, 251, 254], + // 验证码图片高度 + 'imageH' => 0, + // 验证码图片宽度 + 'imageW' => 0, + + // 添加额外的验证码设置 + // verify => [ + // 'length'=>4, + // ... + //], +]; diff --git a/config/console.php b/config/console.php new file mode 100644 index 0000000..a818a98 --- /dev/null +++ b/config/console.php @@ -0,0 +1,9 @@ + [ + ], +]; diff --git a/config/cookie.php b/config/cookie.php new file mode 100644 index 0000000..f728024 --- /dev/null +++ b/config/cookie.php @@ -0,0 +1,18 @@ + 0, + // cookie 保存路径 + 'path' => '/', + // cookie 有效域名 + 'domain' => '', + // cookie 启用安全传输 + 'secure' => false, + // httponly设置 + 'httponly' => false, + // 是否使用 setcookie + 'setcookie' => true, +]; diff --git a/config/database.php b/config/database.php new file mode 100644 index 0000000..abcadc5 --- /dev/null +++ b/config/database.php @@ -0,0 +1,63 @@ + Env::get('database.driver', 'mysql'), + + // 自定义时间查询规则 + 'time_query_rule' => [], + + // 自动写入时间戳字段 + // true为自动识别类型 false关闭 + // 字符串则明确指定时间字段类型 支持 int timestamp datetime date + 'auto_timestamp' => true, + + // 时间字段取出后的默认时间格式 + 'datetime_format' => 'Y-m-d H:i:s', + + // 数据库连接配置信息 + 'connections' => [ + 'mysql' => [ + // 数据库类型 + 'type' => Env::get('database.type', 'mysql'), + // 服务器地址 + 'hostname' => Env::get('database.hostname', '183.221.101.89'), + // 数据库名 + 'database' => Env::get('database.database', 'newest_cms'), + // 用户名 + 'username' => Env::get('database.username', 'newest_cms'), + // 密码 + 'password' => Env::get('database.password', '7pMZSGFP3fGm526w'), + // 端口 + 'hostport' => Env::get('database.hostport', '3306'), + // 数据库连接参数 + 'params' => [], + // 数据库编码默认采用utf8 + 'charset' => Env::get('database.charset', 'utf8'), + // 数据库表前缀 + 'prefix' => Env::get('database.prefix', 'bee_'), + + // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) + 'deploy' => 0, + // 数据库读写是否分离 主从式有效 + 'rw_separate' => false, + // 读写分离后 主服务器数量 + 'master_num' => 1, + // 指定从服务器序号 + 'slave_no' => '', + // 是否严格检查字段是否存在 + 'fields_strict' => true, + // 是否需要断线重连 + 'break_reconnect' => false, + // 监听SQL + 'trigger_sql' => Env::get('database.sql_debug', false), + // 开启字段缓存 + 'fields_cache' => false, + // 字段缓存路径 + 'schema_cache_path' => app()->getRuntimePath() . 'schema' . DIRECTORY_SEPARATOR, + ], + + // 更多的数据库配置信息 + ], +]; diff --git a/config/filesystem.php b/config/filesystem.php new file mode 100644 index 0000000..179b93d --- /dev/null +++ b/config/filesystem.php @@ -0,0 +1,52 @@ + Env::get('filesystem.driver', 'local'), + // 磁盘列表 + 'disks' => [ + 'local' => [ + // 磁盘类型 + 'type' => 'local', + // 磁盘路径 + 'root' => app()->getRootPath() . 'public/storage', + // 磁盘路径对应的外部URL路径 + 'url' => '/storage', + // 可见性 + 'visibility' => 'public', + ], + 'video' => [ + // 磁盘类型 + 'type' => 'local', + // 磁盘路径 + 'root' => app()->getRootPath() . 'public/storage/videos', + // 磁盘路径对应的外部URL路径 + 'url' => '/storage/videos', + // 可见性 + 'visibility' => 'public', + ], + 'backup' => [ + // 磁盘类型 + 'type' => 'local', + // 磁盘路径 + 'root' => app()->getRootPath() . 'public/storage/backup', + // 磁盘路径对应的外部URL路径 + 'url' => '/storage/backup', + // 可见性 + 'visibility' => 'public', + ], + 'oss' => [ + // 磁盘类型 + 'type' => 'local', + // 磁盘路径 + 'root' => app()->getRootPath() . 'public/storage', + // 磁盘路径对应的外部URL路径 + 'url' => '/storage', + // 可见性 + 'visibility' => 'public', + ], + // 更多的磁盘配置信息 + ], +]; diff --git a/config/lang.php b/config/lang.php new file mode 100644 index 0000000..0db66f9 --- /dev/null +++ b/config/lang.php @@ -0,0 +1,27 @@ + Env::get('lang.default_lang', 'zh-cn'), + // 允许的语言列表 + 'allow_lang_list' => ['zh-cn'], + // 多语言自动侦测变量名 + 'detect_var' => 'lang', + // 是否使用Cookie记录 + 'use_cookie' => true, + // 多语言cookie变量 + 'cookie_var' => 'think_lang', + // 扩展语言包 + 'extend_list' => [], + // Accept-Language转义为对应语言包名称 + 'accept_language' => [ + 'zh-hans-cn' => 'zh-cn', + ], + // 是否支持语言分组 + 'allow_group' => false, +]; diff --git a/config/log.php b/config/log.php new file mode 100644 index 0000000..8f00f56 --- /dev/null +++ b/config/log.php @@ -0,0 +1,68 @@ + Env::get('log.channel', 'file'), + // 日志记录级别 + 'level' => [], + // 日志类型记录的通道 ['error'=>'email',...] + 'type_channel' => [], + // 关闭全局日志写入 + 'close' => false, + // 全局日志处理 支持闭包 + 'processor' => null, + + // 日志通道列表 + 'channels' => [ + 'file' => [ + // 日志记录方式 + 'type' => 'File', + // 日志保存目录 + 'path' => '', + // 单文件日志写入 + 'single' => false, + // 独立日志级别 + 'apart_level' => [], + // 最大日志文件数量 + 'max_files' => 0, + // 使用JSON格式记录 + 'json' => false, + // 日志处理 + 'processor' => null, + // 关闭通道日志写入 + 'close' => false, + // 日志输出格式化 + 'format' => '[%s][%s] %s', + // 是否实时写入 + 'realtime_write' => false, + ], + // 其它日志通道配置 + 'order' => [ + // 日志记录方式 + 'type' => 'File', + // 日志保存目录 + 'path' => root_path(). 'order_log', + // 单文件日志写入 + 'single' => false, + // 独立日志级别 + 'apart_level' => [], + // 最大日志文件数量 + 'max_files' => 0, + // 使用JSON格式记录 + 'json' => false, + // 日志处理 + 'processor' => null, + // 关闭通道日志写入 + 'close' => false, + // 日志输出格式化 + 'format' => '[%s][%s] %s', + // 是否实时写入 + 'realtime_write' => false, + ], + ], + +]; diff --git a/config/middleware.php b/config/middleware.php new file mode 100644 index 0000000..02398c9 --- /dev/null +++ b/config/middleware.php @@ -0,0 +1,15 @@ + [ + 'auth' => app\middleware\Auth::class, + 'csrf' => app\middleware\Csrf::class, + 'jwt' => app\middleware\JWT::class, + 'login' => app\middleware\Login::class, + 'apiLogin' => app\middleware\ApiLogin::class, + 'log' => app\middleware\Log::class, + ], + // 优先级设置,此数组中的中间件会按照数组中的顺序优先执行 + 'priority' => [], +]; diff --git a/config/queue.php b/config/queue.php new file mode 100644 index 0000000..d6639e5 --- /dev/null +++ b/config/queue.php @@ -0,0 +1,41 @@ + +// +---------------------------------------------------------------------- + +use think\facade\Env; + +return [ + 'default' => 'redis', + 'connections' => [ + 'sync' => [ + 'type' => 'sync', + ], + 'database' => [ + 'type' => 'database', + 'queue' => 'default', + 'table' => 'jobs', + 'connection' => null, + ], + 'redis' => [ + 'type' => 'redis', + 'queue' => 'default', + 'host' => Env::get('redis.host', '127.0.0.1'), + 'port' => Env::get('redis.port', 6379), + 'password' => Env::get('redis.password', ''), + 'select' => 0, + 'timeout' => 0, + 'persistent' => false, + ], + ], + 'failed' => [ + 'type' => 'none', + 'table' => 'failed_jobs', + ], +]; diff --git a/config/route.php b/config/route.php new file mode 100644 index 0000000..288c8f1 --- /dev/null +++ b/config/route.php @@ -0,0 +1,51 @@ + '/', + // URL伪静态后缀 + 'url_html_suffix' => 'html', + // URL普通方式参数 用于自动生成 + 'url_common_param' => true, + // 是否开启路由延迟解析 + 'url_lazy_route' => false, + // 是否强制使用路由 + 'url_route_must' => false, + // 合并路由规则 + 'route_rule_merge' => false, + // 路由是否完全匹配 + 'route_complete_match' => false, + // 是否开启路由缓存 + 'route_check_cache' => false, + // 路由缓存连接参数 + 'route_cache_option' => [], + // 路由缓存Key + 'route_check_cache_key' => '', + // 访问控制器层名称 + 'controller_layer' => 'controller', + // 空控制器名 + 'empty_controller' => 'Error', + // 是否使用控制器后缀 + 'controller_suffix' => false, + // 默认的路由变量规则 + 'default_route_pattern' => '[\w\.]+', + // 是否开启请求缓存 true自动缓存 支持设置请求缓存规则 + 'request_cache' => false, + // 请求缓存有效期 + 'request_cache_expire' => null, + // 全局请求缓存排除规则 + 'request_cache_except' => [], + // 默认控制器名 + 'default_controller' => 'Index', + // 默认操作名 + 'default_action' => 'index', + // 操作方法后缀 + 'action_suffix' => '', + // 默认JSONP格式返回的处理方法 + 'default_jsonp_handler' => 'jsonpReturn', + // 默认JSONP处理方法 + 'var_jsonp_handler' => 'callback', +]; diff --git a/config/session.php b/config/session.php new file mode 100644 index 0000000..dfdc544 --- /dev/null +++ b/config/session.php @@ -0,0 +1,21 @@ + 'PHPSESSID', + // SESSION_ID的提交变量,解决flash上传跨域 + 'var_session_id' => '', + // 驱动方式 支持file cache + 'type' => 'file', + // 存储连接标识 当type使用cache的时候有效 + 'store' => null, + // 过期时间(秒) + 'expire' => Env::get('app.expire', 3600 * 2), + // 前缀 + 'prefix' => '', +]; diff --git a/config/tauthz-rbac-model.conf b/config/tauthz-rbac-model.conf new file mode 100644 index 0000000..7320e08 --- /dev/null +++ b/config/tauthz-rbac-model.conf @@ -0,0 +1,14 @@ +[request_definition] +r = sub, obj, act + +[policy_definition] +p = sub, obj, act + +[role_definition] +g = _, _ + +[policy_effect] +e = some(where (p.eft == allow)) + +[matchers] +m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act \ No newline at end of file diff --git a/config/tauthz.php b/config/tauthz.php new file mode 100644 index 0000000..3ec750c --- /dev/null +++ b/config/tauthz.php @@ -0,0 +1,70 @@ + 'basic', + + 'log' => [ + // changes whether Lauthz will log messages to the Logger. + 'enabled' => false, + // Casbin Logger, Supported: \Psr\Log\LoggerInterface|string + 'logger' => 'log', + ], + + 'enforcers' => [ + 'basic' => [ + /* + * Model 设置 + */ + 'model' => [ + // 可选值: "file", "text" + 'config_type' => 'file', + 'config_file_path' => config_path().'tauthz-rbac-model.conf', + 'config_text' => '', + ], + + // 适配器 . + 'adapter' => tauthz\adapter\DatabaseAdapter::class, + + /* + * 数据库设置. + */ + 'database' => [ + // 数据库连接名称,不填为默认配置. + 'connection' => '', + // 策略表名(不含表前缀) + 'rules_name' => 'rules', + // 策略表完整名称. + 'rules_table' => null, + ], + ], + 'frontend' => [ + /* + * Model 设置 + */ + 'model' => [ + // 可选值: "file", "text" + 'config_type' => 'file', + 'config_file_path' => config_path().'tauthz-rbac-model.conf', + 'config_text' => '', + ], + + // 适配器 . + 'adapter' => tauthz\adapter\DatabaseAdapter::class, + + /* + * 数据库设置. + */ + 'database' => [ + // 数据库连接名称,不填为默认配置. + 'connection' => '', + // 策略表名(不含表前缀) + 'rules_name' => 'account_rules', + // 策略表完整名称. + 'rules_table' => null, + ], + ], + ], +]; diff --git a/config/trace.php b/config/trace.php new file mode 100644 index 0000000..fad2392 --- /dev/null +++ b/config/trace.php @@ -0,0 +1,10 @@ + 'Html', + // 读取的日志通道名 + 'channel' => '', +]; diff --git a/config/view.php b/config/view.php new file mode 100644 index 0000000..f1da73b --- /dev/null +++ b/config/view.php @@ -0,0 +1,34 @@ + 'Think', + // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 3 保持操作方法 + 'auto_rule' => 1, + // 模板目录名 + 'view_dir_name' => 'view', + // 模板后缀 + 'view_suffix' => 'html', + // 模板文件名分隔符 + 'view_depr' => DIRECTORY_SEPARATOR, + // 模板引擎普通标签开始标记 + 'tpl_begin' => '{', + // 模板引擎普通标签结束标记 + 'tpl_end' => '}', + // 标签库标签开始标记 + 'taglib_begin' => '{', + // 标签库标签结束标记 + 'taglib_end' => '}', + 'tpl_replace_string' => [ + '__STATIC__' => '/static', + '__MANAGER__' => '/static/manager', + '__LAYUIMINI__' => '/static/layuimini', + '__COMMON__' => '/static/common', + '__JS__' => '/static/js', + '__CSS__' => '/static/css', + '__IMG__' => '/static/images', + ], +]; diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000..20232b9 Binary files /dev/null and b/favicon.ico differ diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..978c4ed --- /dev/null +++ b/package-lock.json @@ -0,0 +1,5 @@ +{ + "name": "cms", + "version": "1.0.0", + "lockfileVersion": 1 +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..98a6274 --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "cms", + "version": "1.0.0", + "description": "本CMS基于ThinkPHP 6.0开发", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "https://gitee.com/dxtc/cms.git" + }, + "keywords": [], + "author": "", + "license": "ISC" +} diff --git a/public/.htaccess b/public/.htaccess new file mode 100644 index 0000000..8aa9d23 --- /dev/null +++ b/public/.htaccess @@ -0,0 +1,9 @@ + + Options +FollowSymlinks -Multiviews + RewriteEngine On + + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_URI} !^(.*)\.(gif|jpg|jpeg|png|swf|mp4)$ [NC] + RewriteRule ^(.*)$ index.php?s=/$1 [QSA,PT,L] + \ No newline at end of file diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..20232b9 Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/index.php b/public/index.php new file mode 100644 index 0000000..bbbd56a --- /dev/null +++ b/public/index.php @@ -0,0 +1,21 @@ + +// +---------------------------------------------------------------------- + +// [ 应用入口文件 ] +namespace think; + +require dirname(__DIR__) . '/vendor/autoload.php'; + +// 执行HTTP应用并响应 +$http = (new App())->http; +$response = $http->run(); +$response->send(); +$http->end($response); \ No newline at end of file diff --git a/public/static/api/clear.json b/public/static/api/clear.json new file mode 100644 index 0000000..e0f5ed7 --- /dev/null +++ b/public/static/api/clear.json @@ -0,0 +1,4 @@ +{ + "code": 1, + "msg": "服务端清理缓存成功" +} \ No newline at end of file diff --git a/public/static/api/init.json b/public/static/api/init.json new file mode 100644 index 0000000..73a459d --- /dev/null +++ b/public/static/api/init.json @@ -0,0 +1,221 @@ +{ + "homeInfo": { + "title": "控制台", + "href": "manager/index/dashboard" + }, + "logoInfo": { + "title": "LAYUI MINI", + "image": "/static/layuimini/images/logo.png", + "href": "" + }, + "menuInfo": [ + { + "title": "常规管理", + "icon": "fa fa-address-book", + "href": "", + "target": "_self", + "child": [ + { + "title": "区域", + "href": "manager/index/area.html", + "icon": "fa fa-home", + "target": "_self" + }, + { + "title": "菜单管理", + "href": "manager/menu", + "icon": "fa fa-window-maximize", + "target": "_self" + }, + { + "title": "表单", + "href": "manager/index/form.html", + "icon": "fa fa-gears", + "target": "_self" + }, + { + "title": "菜单2", + "href": "manager/menu/table.html", + "icon": "fa fa-gears", + "target": "_self" + }, + { + "title": "表单示例", + "href": "", + "icon": "fa fa-calendar", + "target": "_self", + "child": [ + { + "title": "普通表单", + "href": "page/form.html", + "icon": "fa fa-list-alt", + "target": "_self" + }, + { + "title": "分步表单", + "href": "page/form-step.html", + "icon": "fa fa-navicon", + "target": "_self" + } + ] + }, + { + "title": "异常页面", + "href": "", + "icon": "fa fa-home", + "target": "_self", + "child": [ + { + "title": "404页面", + "href": "page/404.html", + "icon": "fa fa-hourglass-end", + "target": "_self" + } + ] + }, + { + "title": "其它界面", + "href": "", + "icon": "fa fa-snowflake-o", + "target": "", + "child": [ + { + "title": "按钮示例", + "href": "page/button.html", + "icon": "fa fa-snowflake-o", + "target": "_self" + }, + { + "title": "弹出层", + "href": "page/layer.html", + "icon": "fa fa-shield", + "target": "_self" + } + ] + } + ] + }, + { + "title": "组件管理", + "icon": "fa fa-lemon-o", + "href": "", + "target": "_self", + "child": [ + { + "title": "图标列表", + "href": "page/icon.html", + "icon": "fa fa-dot-circle-o", + "target": "_self" + }, + { + "title": "图标选择", + "href": "page/icon-picker.html", + "icon": "fa fa-adn", + "target": "_self" + }, + { + "title": "颜色选择", + "href": "page/color-select.html", + "icon": "fa fa-dashboard", + "target": "_self" + }, + { + "title": "下拉选择", + "href": "page/table-select.html", + "icon": "fa fa-angle-double-down", + "target": "_self" + }, + { + "title": "文件上传", + "href": "page/upload.html", + "icon": "fa fa-arrow-up", + "target": "_self" + }, + { + "title": "富文本编辑器", + "href": "page/editor.html", + "icon": "fa fa-edit", + "target": "_self" + }, + { + "title": "省市县区选择器", + "href": "page/area.html", + "icon": "fa fa-rocket", + "target": "_self" + } + ] + }, + { + "title": "其它管理", + "icon": "fa fa-slideshare", + "href": "", + "target": "_self", + "child": [ + { + "title": "多级菜单", + "href": "", + "icon": "fa fa-meetup", + "target": "", + "child": [ + { + "title": "按钮1", + "href": "page/button.html?v=1", + "icon": "fa fa-calendar", + "target": "_self", + "child": [ + { + "title": "按钮2", + "href": "page/button.html?v=2", + "icon": "fa fa-snowflake-o", + "target": "_self", + "child": [ + { + "title": "按钮3", + "href": "page/button.html?v=3", + "icon": "fa fa-snowflake-o", + "target": "_self" + }, + { + "title": "表单4", + "href": "page/form.html?v=1", + "icon": "fa fa-calendar", + "target": "_self", + "child": [ + { + "title": "按钮2", + "href": "page/button.html?v=2", + "icon": "fa fa-snowflake-o", + "target": "_self", + "child": [ + { + "title": "按钮3", + "href": "page/button.html?v=3", + "icon": "fa fa-snowflake-o", + "target": "_self" + }, + { + "title": "表单4", + "href": "page/form.html?v=1", + "icon": "fa fa-calendar", + "target": "_self" + } + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "title": "失效菜单", + "href": "page/error.html", + "icon": "fa fa-superpowers", + "target": "_self" + } + ] + } + ] +} \ No newline at end of file diff --git a/public/static/api/menu2.json b/public/static/api/menu2.json new file mode 100644 index 0000000..23d036a --- /dev/null +++ b/public/static/api/menu2.json @@ -0,0 +1,102 @@ +{ + "code": 0, + "msg": "", + "count": 19, + "data": [ + { + "id": 1, + "title": "系统管理", + "sort": 1, + "menuUrl": null, + "icon": "layui-icon-set", + "created_at": "2018/06/29 11:05:41", + "authority": null, + "checked": 0, + "isMenu": 0, + "parentId": -1 + }, + { + "id": 1, + "title": "系统管理", + "sort": 1, + "menuUrl": null, + "icon": "layui-icon-set", + "created_at": "2018/06/29 11:05:41", + "authority": null, + "checked": 0, + "isMenu": 0, + "parentId": -1 + }, + { + "id": 1, + "title": "系统管理", + "sort": 1, + "menuUrl": null, + "icon": "layui-icon-set", + "created_at": "2018/06/29 11:05:41", + "authority": null, + "checked": 0, + "isMenu": 0, + "parentId": -1 + }, + { + "id": 1, + "title": "系统管理", + "sort": 1, + "menuUrl": null, + "icon": "layui-icon-set", + "created_at": "2018/06/29 11:05:41", + "authority": null, + "checked": 0, + "isMenu": 0, + "parentId": -1 + },{ + "id": 1, + "title": "系统管理", + "sort": 1, + "menuUrl": null, + "icon": "layui-icon-set", + "created_at": "2018/06/29 11:05:41", + "authority": null, + "checked": 0, + "isMenu": 0, + "parentId": -1 + }, + { + "id": 1, + "title": "系统管理", + "sort": 1, + "menuUrl": null, + "icon": "layui-icon-set", + "created_at": "2018/06/29 11:05:41", + "authority": null, + "checked": 0, + "isMenu": 0, + "parentId": -1 + }, + { + "id": 1, + "title": "系统管理", + "sort": 1, + "menuUrl": null, + "icon": "layui-icon-set", + "created_at": "2018/06/29 11:05:41", + "authority": null, + "checked": 0, + "isMenu": 0, + "parentId": -1 + }, + { + "id": 1, + "title": "系统管理", + "sort": 1, + "menuUrl": null, + "icon": "layui-icon-set", + "created_at": "2018/06/29 11:05:41", + "authority": null, + "checked": 0, + "isMenu": 0, + "parentId": -1 + } + ] +} \ No newline at end of file diff --git a/public/static/api/menus.json b/public/static/api/menus.json new file mode 100644 index 0000000..e14d00e --- /dev/null +++ b/public/static/api/menus.json @@ -0,0 +1,254 @@ +{ + "code": 0, + "msg": "", + "count": 19, + "data": [ + { + "authorityId": 1, + "authorityName": "系统管理", + "orderNumber": 1, + "menuUrl": null, + "menuIcon": "layui-icon-set", + "createTime": "2018/06/29 11:05:41", + "authority": null, + "checked": 0, + "updateTime": "2018/07/13 09:13:42", + "isMenu": 0, + "parentId": -1 + }, + { + "authorityId": 2, + "authorityName": "用户管理", + "orderNumber": 2, + "menuUrl": "system/user", + "menuIcon": null, + "createTime": "2018/06/29 11:05:41", + "authority": null, + "checked": 0, + "updateTime": "2018/07/13 09:13:42", + "isMenu": 0, + "parentId": 1 + }, + { + "authorityId": 3, + "authorityName": "查询用户", + "orderNumber": 3, + "menuUrl": "", + "menuIcon": "", + "createTime": "2018/07/21 13:54:16", + "authority": "user:view", + "checked": 0, + "updateTime": "2018/07/21 13:54:16", + "isMenu": 1, + "parentId": 2 + }, + { + "authorityId": 4, + "authorityName": "添加用户", + "orderNumber": 4, + "menuUrl": null, + "menuIcon": null, + "createTime": "2018/06/29 11:05:41", + "authority": "user:add", + "checked": 0, + "updateTime": "2018/07/13 09:13:42", + "isMenu": 1, + "parentId": 2 + }, + { + "authorityId": 5, + "authorityName": "修改用户", + "orderNumber": 5, + "menuUrl": null, + "menuIcon": null, + "createTime": "2018/06/29 11:05:41", + "authority": "user:edit", + "checked": 0, + "updateTime": "2018/07/13 09:13:42", + "isMenu": 1, + "parentId": 2 + }, + { + "authorityId": 6, + "authorityName": "删除用户", + "orderNumber": 6, + "menuUrl": null, + "menuIcon": null, + "createTime": "2018/06/29 11:05:41", + "authority": "user:delete", + "checked": 0, + "updateTime": "2018/07/13 09:13:42", + "isMenu": 1, + "parentId": 2 + }, + { + "authorityId": 7, + "authorityName": "角色管理", + "orderNumber": 7, + "menuUrl": "system/role", + "menuIcon": null, + "createTime": "2018/06/29 11:05:41", + "authority": null, + "checked": 0, + "updateTime": "2018/07/13 09:13:42", + "isMenu": 0, + "parentId": 1 + }, + { + "authorityId": 8, + "authorityName": "查询角色", + "orderNumber": 8, + "menuUrl": "", + "menuIcon": "", + "createTime": "2018/07/21 13:54:59", + "authority": "role:view", + "checked": 0, + "updateTime": "2018/07/21 13:54:58", + "isMenu": 1, + "parentId": 7 + }, + { + "authorityId": 9, + "authorityName": "添加角色", + "orderNumber": 9, + "menuUrl": "", + "menuIcon": "", + "createTime": "2018/06/29 11:05:41", + "authority": "role:add", + "checked": 0, + "updateTime": "2018/07/13 09:13:42", + "isMenu": 1, + "parentId": 7 + }, + { + "authorityId": 10, + "authorityName": "修改角色", + "orderNumber": 10, + "menuUrl": "", + "menuIcon": "", + "createTime": "2018/06/29 11:05:41", + "authority": "role:edit", + "checked": 0, + "updateTime": "2018/07/13 09:13:42", + "isMenu": 1, + "parentId": 7 + }, + { + "authorityId": 11, + "authorityName": "删除角色", + "orderNumber": 11, + "menuUrl": "", + "menuIcon": "", + "createTime": "2018/06/29 11:05:41", + "authority": "role:delete", + "checked": 0, + "updateTime": "2018/07/13 09:13:42", + "isMenu": 1, + "parentId": 7 + }, + { + "authorityId": 12, + "authorityName": "角色权限管理", + "orderNumber": 12, + "menuUrl": "", + "menuIcon": "", + "createTime": "2018/06/29 11:05:41", + "authority": "role:auth", + "checked": 0, + "updateTime": "2018/07/13 15:27:18", + "isMenu": 1, + "parentId": 7 + }, + { + "authorityId": 13, + "authorityName": "权限管理", + "orderNumber": 13, + "menuUrl": "system/authorities", + "menuIcon": null, + "createTime": "2018/06/29 11:05:41", + "authority": null, + "checked": 0, + "updateTime": "2018/07/13 15:45:13", + "isMenu": 0, + "parentId": 1 + }, + { + "authorityId": 14, + "authorityName": "查询权限", + "orderNumber": 14, + "menuUrl": "", + "menuIcon": "", + "createTime": "2018/07/21 13:55:57", + "authority": "authorities:view", + "checked": 0, + "updateTime": "2018/07/21 13:55:56", + "isMenu": 1, + "parentId": 13 + }, + { + "authorityId": 15, + "authorityName": "添加权限", + "orderNumber": 15, + "menuUrl": "", + "menuIcon": "", + "createTime": "2018/06/29 11:05:41", + "authority": "authorities:add", + "checked": 0, + "updateTime": "2018/06/29 11:05:41", + "isMenu": 1, + "parentId": 13 + }, + { + "authorityId": 16, + "authorityName": "修改权限", + "orderNumber": 16, + "menuUrl": "", + "menuIcon": "", + "createTime": "2018/07/13 09:13:42", + "authority": "authorities:edit", + "checked": 0, + "updateTime": "2018/07/13 09:13:42", + "isMenu": 1, + "parentId": 13 + }, + { + "authorityId": 17, + "authorityName": "删除权限", + "orderNumber": 17, + "menuUrl": "", + "menuIcon": "", + "createTime": "2018/06/29 11:05:41", + "authority": "authorities:delete", + "checked": 0, + "updateTime": "2018/06/29 11:05:41", + "isMenu": 1, + "parentId": 13 + }, + { + "authorityId": 18, + "authorityName": "登录日志", + "orderNumber": 18, + "menuUrl": "system/loginRecord", + "menuIcon": null, + "createTime": "2018/06/29 11:05:41", + "authority": null, + "checked": 0, + "updateTime": "2018/06/29 11:05:41", + "isMenu": 0, + "parentId": 1 + }, + { + "authorityId": 19, + "authorityName": "查询登录日志", + "orderNumber": 19, + "menuUrl": "", + "menuIcon": "", + "createTime": "2018/07/21 13:56:43", + "authority": "loginRecord:view", + "checked": 0, + "updateTime": "2018/07/21 13:56:43", + "isMenu": 1, + "parentId": 18 + } + ] +} \ No newline at end of file diff --git a/public/static/api/table.json b/public/static/api/table.json new file mode 100644 index 0000000..7bda61b --- /dev/null +++ b/public/static/api/table.json @@ -0,0 +1,127 @@ +{ + "code": 0, + "msg": "", + "count": 1000, + "data": [ + { + "id": 10000, + "username": "user-0", + "sex": "女", + "city": "城市-0", + "sign": "签名-0", + "experience": 255, + "logins": 24, + "wealth": 82830700, + "classify": "作家", + "score": 57 + }, + { + "id": 10001, + "username": "user-1", + "sex": "男", + "city": "城市-1", + "sign": "签名-1", + "experience": 884, + "logins": 58, + "wealth": 64928690, + "classify": "词人", + "score": 27 + }, + { + "id": 10002, + "username": "user-2", + "sex": "女", + "city": "城市-2", + "sign": "签名-2", + "experience": 650, + "logins": 77, + "wealth": 6298078, + "classify": "酱油", + "score": 31 + }, + { + "id": 10003, + "username": "user-3", + "sex": "女", + "city": "城市-3", + "sign": "签名-3", + "experience": 362, + "logins": 157, + "wealth": 37117017, + "classify": "诗人", + "score": 68 + }, + { + "id": 10004, + "username": "user-4", + "sex": "男", + "city": "城市-4", + "sign": "签名-4", + "experience": 807, + "logins": 51, + "wealth": 76263262, + "classify": "作家", + "score": 6 + }, + { + "id": 10005, + "username": "user-5", + "sex": "女", + "city": "城市-5", + "sign": "签名-5", + "experience": 173, + "logins": 68, + "wealth": 60344147, + "classify": "作家", + "score": 87 + }, + { + "id": 10006, + "username": "user-6", + "sex": "女", + "city": "城市-6", + "sign": "签名-6", + "experience": 982, + "logins": 37, + "wealth": 57768166, + "classify": "作家", + "score": 34 + }, + { + "id": 10007, + "username": "user-7", + "sex": "男", + "city": "城市-7", + "sign": "签名-7", + "experience": 727, + "logins": 150, + "wealth": 82030578, + "classify": "作家", + "score": 28 + }, + { + "id": 10008, + "username": "user-8", + "sex": "男", + "city": "城市-8", + "sign": "签名-8", + "experience": 951, + "logins": 133, + "wealth": 16503371, + "classify": "词人", + "score": 14 + }, + { + "id": 10009, + "username": "user-9", + "sex": "女", + "city": "城市-9", + "sign": "签名-9", + "experience": 484, + "logins": 25, + "wealth": 86801934, + "classify": "词人", + "score": 75 + } + ] +} \ No newline at end of file diff --git a/public/static/api/tableSelect.json b/public/static/api/tableSelect.json new file mode 100644 index 0000000..37fb0ed --- /dev/null +++ b/public/static/api/tableSelect.json @@ -0,0 +1,23 @@ +{ + "code": 0, + "msg": "", + "count": 16, + "data": [ + { "id":"001", "username":"张玉林", "sex":"女" }, + { "id":"002", "username":"刘晓军", "sex":"男" }, + { "id":"003", "username":"张恒", "sex":"男" }, + { "id":"004", "username":"朱一", "sex":"男" }, + { "id":"005", "username":"刘佳能", "sex":"女" }, + { "id":"006", "username":"晓梅", "sex":"女" }, + { "id":"007", "username":"马冬梅", "sex":"女" }, + { "id":"008", "username":"刘晓庆", "sex":"女" }, + { "id":"009", "username":"刘晓庆", "sex":"女" }, + { "id":"010", "username":"刘晓庆", "sex":"女" }, + { "id":"011", "username":"刘晓庆", "sex":"女" }, + { "id":"012", "username":"刘晓庆", "sex":"女" }, + { "id":"013", "username":"刘晓庆", "sex":"女" }, + { "id":"014", "username":"刘晓庆", "sex":"女" }, + { "id":"015", "username":"刘晓庆", "sex":"女" }, + { "id":"016", "username":"刘晓庆", "sex":"女" } + ] +} \ No newline at end of file diff --git a/public/static/api/upload.json b/public/static/api/upload.json new file mode 100644 index 0000000..691902d --- /dev/null +++ b/public/static/api/upload.json @@ -0,0 +1,10 @@ +{ + "code": 1, + "msg": "上传成功", + "data": { + "url": [ + "../images/logo.png", + "../images/captcha.jpg" + ] + } +} diff --git a/public/static/images/bg-upload.png b/public/static/images/bg-upload.png new file mode 100644 index 0000000..de827d6 Binary files /dev/null and b/public/static/images/bg-upload.png differ diff --git a/public/static/images/icon-logo.jpg b/public/static/images/icon-logo.jpg new file mode 100644 index 0000000..c979f27 Binary files /dev/null and b/public/static/images/icon-logo.jpg differ diff --git a/public/static/images/poster-bg1.png b/public/static/images/poster-bg1.png new file mode 100644 index 0000000..4e53c82 Binary files /dev/null and b/public/static/images/poster-bg1.png differ diff --git a/public/static/images/scan.jpeg b/public/static/images/scan.jpeg new file mode 100644 index 0000000..9439f3f Binary files /dev/null and b/public/static/images/scan.jpeg differ diff --git a/public/static/js/city-picker.data.js b/public/static/js/city-picker.data.js new file mode 100644 index 0000000..148f45c --- /dev/null +++ b/public/static/js/city-picker.data.js @@ -0,0 +1,4058 @@ +/*! + * Distpicker v1.0.2 + * https://github.com/tshi0912/city-picker + * + * Copyright (c) 2014-2016 Tao Shi + * Released under the MIT license + * + * Date: 2016-02-29T12:11:36.473Z + */ + +function districts() { + + var ChineseDistricts = { + 86: { + 110000: '北京', + 120000: '天津', + 130000: '河北', + 140000: '山西', + 150000: '内蒙古', + 210000: '辽宁', + 220000: '吉林', + 230000: '黑龙江', + 310000: '上海', + 320000: '江苏', + 330000: '浙江', + 340000: '安徽', + 350000: '福建', + 360000: '江西', + 370000: '山东', + 410000: '河南', + 420000: '湖北', + 430000: '湖南', + 440000: '广东', + 450000: '广西', + 460000: '海南', + 500000: '重庆', + 510000: '四川', + 520000: '贵州', + 530000: '云南', + 540000: '西藏', + 610000: '陕西', + 620000: '甘肃', + 630000: '青海', + 640000: '宁夏', + 650000: '新疆', + 710000: '台湾', + 810000: '香港', + 820000: '澳门' + }, + // 86: { + // 'A-G': [ + // {code: '340000', address: '安徽省'}, + // {code: '110000', address: '北京市'}, + // {code: '500000', address: '重庆市'}, + // {code: '350000', address: '福建省'}, + // {code: '620000', address: '甘肃省'}, + // {code: '440000', address: '广东省'}, + // {code: '450000', address: '广西壮族自治区'}, + // {code: '520000', address: '贵州省'}], + // 'H-K': [ + // {code: '460000', address: '海南省'}, + // {code: '130000', address: '河北省'}, + // {code: '230000', address: '黑龙江省'}, + // {code: '410000', address: '河南省'}, + // {code: '420000', address: '湖北省'}, + // {code: '430000', address: '湖南省'}, + // {code: '320000', address: '江苏省'}, + // {code: '360000', address: '江西省'}, + // {code: '220000', address: '吉林省'}], + // 'L-S': [ + // {code: '210000', address: '辽宁省'}, + // {code: '150000', address: '内蒙古自治区'}, + // {code: '640000', address: '宁夏回族自治区'}, + // {code: '630000', address: '青海省'}, + // {code: '370000', address: '山东省'}, + // {code: '310000', address: '上海市'}, + // {code: '140000', address: '山西省'}, + // {code: '610000', address: '陕西省'}, + // {code: '510000', address: '四川省'}], + // 'T-Z': [ + // {code: '120000', address: '天津市'}, + // {code: '650000', address: '新疆维吾尔自治区'}, + // {code: '540000', address: '西藏自治区'}, + // {code: '530000', address: '云南省'}, + // {code: '330000', address: '浙江省'}] + // }, + 110000: { + 110100: '北京市', + }, + 110100: { + 110101: '东城区', + 110102: '西城区', + 110105: '朝阳区', + 110106: '丰台区', + 110107: '石景山区', + 110108: '海淀区', + 110109: '门头沟区', + 110111: '房山区', + 110112: '通州区', + 110113: '顺义区', + 110114: '昌平区', + 110115: '大兴区', + 110116: '怀柔区', + 110117: '平谷区', + 110228: '密云县', + 110229: '延庆县' + }, + 120000: { + 120100: '天津市' + }, + 120100: { + 120101: '和平区', + 120102: '河东区', + 120103: '河西区', + 120104: '南开区', + 120105: '河北区', + 120106: '红桥区', + 120110: '东丽区', + 120111: '西青区', + 120112: '津南区', + 120113: '北辰区', + 120114: '武清区', + 120115: '宝坻区', + 120116: '滨海新区', + 120221: '宁河县', + 120223: '静海县', + 120225: '蓟县' + }, + 130000: { + 130100: '石家庄市', + 130200: '唐山市', + 130300: '秦皇岛市', + 130400: '邯郸市', + 130500: '邢台市', + 130600: '保定市', + 130700: '张家口市', + 130800: '承德市', + 130900: '沧州市', + 131000: '廊坊市', + 131100: '衡水市' + }, + 130100: { + 130102: '长安区', + 130104: '桥西区', + 130105: '新华区', + 130107: '井陉矿区', + 130108: '裕华区', + 130109: '藁城区', + 130110: '鹿泉区', + 130111: '栾城区', + 130121: '井陉县', + 130123: '正定县', + 130125: '行唐县', + 130126: '灵寿县', + 130127: '高邑县', + 130128: '深泽县', + 130129: '赞皇县', + 130130: '无极县', + 130131: '平山县', + 130132: '元氏县', + 130133: '赵县', + 130181: '辛集市', + 130183: '晋州市', + 130184: '新乐市' + }, + 130200: { + 130202: '路南区', + 130203: '路北区', + 130204: '古冶区', + 130205: '开平区', + 130207: '丰南区', + 130208: '丰润区', + 130209: '曹妃甸区', + 130223: '滦县', + 130224: '滦南县', + 130225: '乐亭县', + 130227: '迁西县', + 130229: '玉田县', + 130281: '遵化市', + 130283: '迁安市' + }, + 130300: { + 130302: '海港区', + 130303: '山海关区', + 130304: '北戴河区', + 130321: '青龙满族自治县', + 130322: '昌黎县', + 130323: '抚宁县', + 130324: '卢龙县' + }, + 130400: { + 130402: '邯山区', + 130403: '丛台区', + 130404: '复兴区', + 130406: '峰峰矿区', + 130421: '邯郸县', + 130423: '临漳县', + 130424: '成安县', + 130425: '大名县', + 130426: '涉县', + 130427: '磁县', + 130428: '肥乡县', + 130429: '永年县', + 130430: '邱县', + 130431: '鸡泽县', + 130432: '广平县', + 130433: '馆陶县', + 130434: '魏县', + 130435: '曲周县', + 130481: '武安市' + }, + 130500: { + 130502: '桥东区', + 130503: '桥西区', + 130521: '邢台县', + 130522: '临城县', + 130523: '内丘县', + 130524: '柏乡县', + 130525: '隆尧县', + 130526: '任县', + 130527: '南和县', + 130528: '宁晋县', + 130529: '巨鹿县', + 130530: '新河县', + 130531: '广宗县', + 130532: '平乡县', + 130533: '威县', + 130534: '清河县', + 130535: '临西县', + 130581: '南宫市', + 130582: '沙河市' + }, + 130600: { + 130602: '竞秀区', + 130603: '莲池区', + 130621: '满城区', + 130622: '清苑区', + 130623: '涞水县', + 130624: '阜平县', + 130625: '徐水区', + 130626: '定兴县', + 130627: '唐县', + 130628: '高阳县', + 130629: '容城县', + 130630: '涞源县', + 130631: '望都县', + 130632: '安新县', + 130633: '易县', + 130634: '曲阳县', + 130635: '蠡县', + 130636: '顺平县', + 130637: '博野县', + 130638: '雄县', + 130681: '涿州市', + 130682: '定州市', + 130683: '安国市', + 130684: '高碑店市' + }, + 130700: { + 130702: '桥东区', + 130703: '桥西区', + 130705: '宣化区', + 130706: '下花园区', + 130721: '宣化县', + 130722: '张北县', + 130723: '康保县', + 130724: '沽源县', + 130725: '尚义县', + 130726: '蔚县', + 130727: '阳原县', + 130728: '怀安县', + 130729: '万全县', + 130730: '怀来县', + 130731: '涿鹿县', + 130732: '赤城县', + 130733: '崇礼县' + }, + 130800: { + 130802: '双桥区', + 130803: '双滦区', + 130804: '鹰手营子矿区', + 130821: '承德县', + 130822: '兴隆县', + 130823: '平泉县', + 130824: '滦平县', + 130825: '隆化县', + 130826: '丰宁满族自治县', + 130827: '宽城满族自治县', + 130828: '围场满族蒙古族自治县' + }, + 130900: { + 130902: '新华区', + 130903: '运河区', + 130921: '沧县', + 130922: '青县', + 130923: '东光县', + 130924: '海兴县', + 130925: '盐山县', + 130926: '肃宁县', + 130927: '南皮县', + 130928: '吴桥县', + 130929: '献县', + 130930: '孟村回族自治县', + 130981: '泊头市', + 130982: '任丘市', + 130983: '黄骅市', + 130984: '河间市' + }, + 131000: { + 131002: '安次区', + 131003: '广阳区', + 131022: '固安县', + 131023: '永清县', + 131024: '香河县', + 131025: '大城县', + 131026: '文安县', + 131028: '大厂回族自治县', + 131081: '霸州市', + 131082: '三河市' + }, + 131100: { + 131102: '桃城区', + 131121: '枣强县', + 131122: '武邑县', + 131123: '武强县', + 131124: '饶阳县', + 131125: '安平县', + 131126: '故城县', + 131127: '景县', + 131128: '阜城县', + 131181: '冀州市', + 131182: '深州市' + }, + 140000: { + 140100: '太原市', + 140200: '大同市', + 140300: '阳泉市', + 140400: '长治市', + 140500: '晋城市', + 140600: '朔州市', + 140700: '晋中市', + 140800: '运城市', + 140900: '忻州市', + 141000: '临汾市', + 141100: '吕梁市' + }, + 140100: { + 140105: '小店区', + 140106: '迎泽区', + 140107: '杏花岭区', + 140108: '尖草坪区', + 140109: '万柏林区', + 140110: '晋源区', + 140121: '清徐县', + 140122: '阳曲县', + 140123: '娄烦县', + 140181: '古交市' + }, + 140200: { + 140202: '城区', + 140203: '矿区', + 140211: '南郊区', + 140212: '新荣区', + 140221: '阳高县', + 140222: '天镇县', + 140223: '广灵县', + 140224: '灵丘县', + 140225: '浑源县', + 140226: '左云县', + 140227: '大同县' + }, + 140300: { + 140302: '城区', + 140303: '矿区', + 140311: '郊区', + 140321: '平定县', + 140322: '盂县' + }, + 140400: { + 140402: '城区', + 140411: '郊区', + 140421: '长治县', + 140423: '襄垣县', + 140424: '屯留县', + 140425: '平顺县', + 140426: '黎城县', + 140427: '壶关县', + 140428: '长子县', + 140429: '武乡县', + 140430: '沁县', + 140431: '沁源县', + 140481: '潞城市' + }, + 140500: { + 140502: '城区', + 140521: '沁水县', + 140522: '阳城县', + 140524: '陵川县', + 140525: '泽州县', + 140581: '高平市' + }, + 140600: { + 140602: '朔城区', + 140603: '平鲁区', + 140621: '山阴县', + 140622: '应县', + 140623: '右玉县', + 140624: '怀仁县' + }, + 140700: { + 140702: '榆次区', + 140721: '榆社县', + 140722: '左权县', + 140723: '和顺县', + 140724: '昔阳县', + 140725: '寿阳县', + 140726: '太谷县', + 140727: '祁县', + 140728: '平遥县', + 140729: '灵石县', + 140781: '介休市' + }, + 140800: { + 140802: '盐湖区', + 140821: '临猗县', + 140822: '万荣县', + 140823: '闻喜县', + 140824: '稷山县', + 140825: '新绛县', + 140826: '绛县', + 140827: '垣曲县', + 140828: '夏县', + 140829: '平陆县', + 140830: '芮城县', + 140881: '永济市', + 140882: '河津市' + }, + 140900: { + 140902: '忻府区', + 140921: '定襄县', + 140922: '五台县', + 140923: '代县', + 140924: '繁峙县', + 140925: '宁武县', + 140926: '静乐县', + 140927: '神池县', + 140928: '五寨县', + 140929: '岢岚县', + 140930: '河曲县', + 140931: '保德县', + 140932: '偏关县', + 140981: '原平市' + }, + 141000: { + 141002: '尧都区', + 141021: '曲沃县', + 141022: '翼城县', + 141023: '襄汾县', + 141024: '洪洞县', + 141025: '古县', + 141026: '安泽县', + 141027: '浮山县', + 141028: '吉县', + 141029: '乡宁县', + 141030: '大宁县', + 141031: '隰县', + 141032: '永和县', + 141033: '蒲县', + 141034: '汾西县', + 141081: '侯马市', + 141082: '霍州市' + }, + 141100: { + 141102: '离石区', + 141121: '文水县', + 141122: '交城县', + 141123: '兴县', + 141124: '临县', + 141125: '柳林县', + 141126: '石楼县', + 141127: '岚县', + 141128: '方山县', + 141129: '中阳县', + 141130: '交口县', + 141181: '孝义市', + 141182: '汾阳市' + }, + 150000: { + 150100: '呼和浩特市', + 150200: '包头市', + 150300: '乌海市', + 150400: '赤峰市', + 150500: '通辽市', + 150600: '鄂尔多斯市', + 150700: '呼伦贝尔市', + 150800: '巴彦淖尔市', + 150900: '乌兰察布市', + 152200: '兴安盟', + 152500: '锡林郭勒盟', + 152900: '阿拉善盟' + }, + 150100: { + 150102: '新城区', + 150103: '回民区', + 150104: '玉泉区', + 150105: '赛罕区', + 150121: '土默特左旗', + 150122: '托克托县', + 150123: '和林格尔县', + 150124: '清水河县', + 150125: '武川县' + }, + 150200: { + 150202: '东河区', + 150203: '昆都仑区', + 150204: '青山区', + 150205: '石拐区', + 150206: '白云鄂博矿区', + 150207: '九原区', + 150221: '土默特右旗', + 150222: '固阳县', + 150223: '达尔罕茂明安联合旗' + }, + 150300: { + 150302: '海勃湾区', + 150303: '海南区', + 150304: '乌达区' + }, + 150400: { + 150402: '红山区', + 150403: '元宝山区', + 150404: '松山区', + 150421: '阿鲁科尔沁旗', + 150422: '巴林左旗', + 150423: '巴林右旗', + 150424: '林西县', + 150425: '克什克腾旗', + 150426: '翁牛特旗', + 150428: '喀喇沁旗', + 150429: '宁城县', + 150430: '敖汉旗' + }, + 150500: { + 150502: '科尔沁区', + 150521: '科尔沁左翼中旗', + 150522: '科尔沁左翼后旗', + 150523: '开鲁县', + 150524: '库伦旗', + 150525: '奈曼旗', + 150526: '扎鲁特旗', + 150581: '霍林郭勒市' + }, + 150600: { + 150602: '东胜区', + 150621: '达拉特旗', + 150622: '准格尔旗', + 150623: '鄂托克前旗', + 150624: '鄂托克旗', + 150625: '杭锦旗', + 150626: '乌审旗', + 150627: '伊金霍洛旗' + }, + 150700: { + 150702: '海拉尔区', + 150703: '扎赉诺尔区', + 150721: '阿荣旗', + 150722: '莫力达瓦达斡尔族自治旗', + 150723: '鄂伦春自治旗', + 150724: '鄂温克族自治旗', + 150725: '陈巴尔虎旗', + 150726: '新巴尔虎左旗', + 150727: '新巴尔虎右旗', + 150781: '满洲里市', + 150782: '牙克石市', + 150783: '扎兰屯市', + 150784: '额尔古纳市', + 150785: '根河市' + }, + 150800: { + 150802: '临河区', + 150821: '五原县', + 150822: '磴口县', + 150823: '乌拉特前旗', + 150824: '乌拉特中旗', + 150825: '乌拉特后旗', + 150826: '杭锦后旗' + }, + 150900: { + 150902: '集宁区', + 150921: '卓资县', + 150922: '化德县', + 150923: '商都县', + 150924: '兴和县', + 150925: '凉城县', + 150926: '察哈尔右翼前旗', + 150927: '察哈尔右翼中旗', + 150928: '察哈尔右翼后旗', + 150929: '四子王旗', + 150981: '丰镇市' + }, + 152200: { + 152201: '乌兰浩特市', + 152202: '阿尔山市', + 152221: '科尔沁右翼前旗', + 152222: '科尔沁右翼中旗', + 152223: '扎赉特旗', + 152224: '突泉县' + }, + 152500: { + 152501: '二连浩特市', + 152502: '锡林浩特市', + 152522: '阿巴嘎旗', + 152523: '苏尼特左旗', + 152524: '苏尼特右旗', + 152525: '东乌珠穆沁旗', + 152526: '西乌珠穆沁旗', + 152527: '太仆寺旗', + 152528: '镶黄旗', + 152529: '正镶白旗', + 152530: '正蓝旗', + 152531: '多伦县' + }, + 152900: { + 152921: '阿拉善左旗', + 152922: '阿拉善右旗', + 152923: '额济纳旗' + }, + 210000: { + 210100: '沈阳市', + 210200: '大连市', + 210300: '鞍山市', + 210400: '抚顺市', + 210500: '本溪市', + 210600: '丹东市', + 210700: '锦州市', + 210800: '营口市', + 210900: '阜新市', + 211000: '辽阳市', + 211100: '盘锦市', + 211200: '铁岭市', + 211300: '朝阳市', + 211400: '葫芦岛市' + }, + 210100: { + 210102: '和平区', + 210103: '沈河区', + 210104: '大东区', + 210105: '皇姑区', + 210106: '铁西区', + 210111: '苏家屯区', + 210112: '浑南区', + 210113: '沈北新区', + 210114: '于洪区', + 210122: '辽中县', + 210123: '康平县', + 210124: '法库县', + 210181: '新民市' + }, + 210200: { + 210202: '中山区', + 210203: '西岗区', + 210204: '沙河口区', + 210211: '甘井子区', + 210212: '旅顺口区', + 210213: '金州区', + 210224: '长海县', + 210281: '瓦房店市', + 210282: '普兰店市', + 210283: '庄河市' + }, + 210300: { + 210302: '铁东区', + 210303: '铁西区', + 210304: '立山区', + 210311: '千山区', + 210321: '台安县', + 210323: '岫岩满族自治县', + 210381: '海城市' + }, + 210400: { + 210402: '新抚区', + 210403: '东洲区', + 210404: '望花区', + 210411: '顺城区', + 210421: '抚顺县', + 210422: '新宾满族自治县', + 210423: '清原满族自治县' + }, + 210500: { + 210502: '平山区', + 210503: '溪湖区', + 210504: '明山区', + 210505: '南芬区', + 210521: '本溪满族自治县', + 210522: '桓仁满族自治县' + }, + 210600: { + 210602: '元宝区', + 210603: '振兴区', + 210604: '振安区', + 210624: '宽甸满族自治县', + 210681: '东港市', + 210682: '凤城市' + }, + 210700: { + 210702: '古塔区', + 210703: '凌河区', + 210711: '太和区', + 210726: '黑山县', + 210727: '义县', + 210781: '凌海市', + 210782: '北镇市' + }, + 210800: { + 210802: '站前区', + 210803: '西市区', + 210804: '鲅鱼圈区', + 210811: '老边区', + 210881: '盖州市', + 210882: '大石桥市' + }, + 210900: { + 210902: '海州区', + 210903: '新邱区', + 210904: '太平区', + 210905: '清河门区', + 210911: '细河区', + 210921: '阜新蒙古族自治县', + 210922: '彰武县' + }, + 211000: { + 211002: '白塔区', + 211003: '文圣区', + 211004: '宏伟区', + 211005: '弓长岭区', + 211011: '太子河区', + 211021: '辽阳县', + 211081: '灯塔市' + }, + 211100: { + 211102: '双台子区', + 211103: '兴隆台区', + 211121: '大洼县', + 211122: '盘山县' + }, + 211200: { + 211202: '银州区', + 211204: '清河区', + 211221: '铁岭县', + 211223: '西丰县', + 211224: '昌图县', + 211281: '调兵山市', + 211282: '开原市' + }, + 211300: { + 211302: '双塔区', + 211303: '龙城区', + 211321: '朝阳县', + 211322: '建平县', + 211324: '喀喇沁左翼蒙古族自治县', + 211381: '北票市', + 211382: '凌源市' + }, + 211400: { + 211402: '连山区', + 211403: '龙港区', + 211404: '南票区', + 211421: '绥中县', + 211422: '建昌县', + 211481: '兴城市' + }, + 220000: { + 220100: '长春市', + 220200: '吉林市', + 220300: '四平市', + 220400: '辽源市', + 220500: '通化市', + 220600: '白山市', + 220700: '松原市', + 220800: '白城市', + 222400: '延边朝鲜族自治州' + }, + 220100: { + 220102: '南关区', + 220103: '宽城区', + 220104: '朝阳区', + 220105: '二道区', + 220106: '绿园区', + 220112: '双阳区', + 220113: '九台区', + 220122: '农安县', + 220182: '榆树市', + 220183: '德惠市' + }, + 220200: { + 220202: '昌邑区', + 220203: '龙潭区', + 220204: '船营区', + 220211: '丰满区', + 220221: '永吉县', + 220281: '蛟河市', + 220282: '桦甸市', + 220283: '舒兰市', + 220284: '磐石市' + }, + 220300: { + 220302: '铁西区', + 220303: '铁东区', + 220322: '梨树县', + 220323: '伊通满族自治县', + 220381: '公主岭市', + 220382: '双辽市' + }, + 220400: { + 220402: '龙山区', + 220403: '西安区', + 220421: '东丰县', + 220422: '东辽县' + }, + 220500: { + 220502: '东昌区', + 220503: '二道江区', + 220521: '通化县', + 220523: '辉南县', + 220524: '柳河县', + 220581: '梅河口市', + 220582: '集安市' + }, + 220600: { + 220602: '浑江区', + 220605: '江源区', + 220621: '抚松县', + 220622: '靖宇县', + 220623: '长白朝鲜族自治县', + 220681: '临江市' + }, + 220700: { + 220702: '宁江区', + 220721: '前郭尔罗斯蒙古族自治县', + 220722: '长岭县', + 220723: '乾安县', + 220781: '扶余市' + }, + 220800: { + 220802: '洮北区', + 220821: '镇赉县', + 220822: '通榆县', + 220881: '洮南市', + 220882: '大安市' + }, + 222400: { + 222401: '延吉市', + 222402: '图们市', + 222403: '敦化市', + 222404: '珲春市', + 222405: '龙井市', + 222406: '和龙市', + 222424: '汪清县', + 222426: '安图县' + }, + 230000: { + 230100: '哈尔滨市', + 230200: '齐齐哈尔市', + 230300: '鸡西市', + 230400: '鹤岗市', + 230500: '双鸭山市', + 230600: '大庆市', + 230700: '伊春市', + 230800: '佳木斯市', + 230900: '七台河市', + 231000: '牡丹江市', + 231100: '黑河市', + 231200: '绥化市', + 232700: '大兴安岭地区' + }, + 230100: { + 230102: '道里区', + 230103: '南岗区', + 230104: '道外区', + 230108: '平房区', + 230109: '松北区', + 230110: '香坊区', + 230111: '呼兰区', + 230112: '阿城区', + 230113: '双城区', + 230123: '依兰县', + 230124: '方正县', + 230125: '宾县', + 230126: '巴彦县', + 230127: '木兰县', + 230128: '通河县', + 230129: '延寿县', + 230183: '尚志市', + 230184: '五常市' + }, + 230200: { + 230202: '龙沙区', + 230203: '建华区', + 230204: '铁锋区', + 230205: '昂昂溪区', + 230206: '富拉尔基区', + 230207: '碾子山区', + 230208: '梅里斯达斡尔族区', + 230221: '龙江县', + 230223: '依安县', + 230224: '泰来县', + 230225: '甘南县', + 230227: '富裕县', + 230229: '克山县', + 230230: '克东县', + 230231: '拜泉县', + 230281: '讷河市' + }, + 230300: { + 230302: '鸡冠区', + 230303: '恒山区', + 230304: '滴道区', + 230305: '梨树区', + 230306: '城子河区', + 230307: '麻山区', + 230321: '鸡东县', + 230381: '虎林市', + 230382: '密山市' + }, + 230400: { + 230402: '向阳区', + 230403: '工农区', + 230404: '南山区', + 230405: '兴安区', + 230406: '东山区', + 230407: '兴山区', + 230421: '萝北县', + 230422: '绥滨县' + }, + 230500: { + 230502: '尖山区', + 230503: '岭东区', + 230505: '四方台区', + 230506: '宝山区', + 230521: '集贤县', + 230522: '友谊县', + 230523: '宝清县', + 230524: '饶河县' + }, + 230600: { + 230602: '萨尔图区', + 230603: '龙凤区', + 230604: '让胡路区', + 230605: '红岗区', + 230606: '大同区', + 230621: '肇州县', + 230622: '肇源县', + 230623: '林甸县', + 230624: '杜尔伯特蒙古族自治县' + }, + 230700: { + 230702: '伊春区', + 230703: '南岔区', + 230704: '友好区', + 230705: '西林区', + 230706: '翠峦区', + 230707: '新青区', + 230708: '美溪区', + 230709: '金山屯区', + 230710: '五营区', + 230711: '乌马河区', + 230712: '汤旺河区', + 230713: '带岭区', + 230714: '乌伊岭区', + 230715: '红星区', + 230716: '上甘岭区', + 230722: '嘉荫县', + 230781: '铁力市' + }, + 230800: { + 230803: '向阳区', + 230804: '前进区', + 230805: '东风区', + 230811: '郊区', + 230822: '桦南县', + 230826: '桦川县', + 230828: '汤原县', + 230833: '抚远县', + 230881: '同江市', + 230882: '富锦市' + }, + 230900: { + 230902: '新兴区', + 230903: '桃山区', + 230904: '茄子河区', + 230921: '勃利县' + }, + 231000: { + 231002: '东安区', + 231003: '阳明区', + 231004: '爱民区', + 231005: '西安区', + 231024: '东宁县', + 231025: '林口县', + 231081: '绥芬河市', + 231083: '海林市', + 231084: '宁安市', + 231085: '穆棱市' + }, + 231100: { + 231102: '爱辉区', + 231121: '嫩江县', + 231123: '逊克县', + 231124: '孙吴县', + 231181: '北安市', + 231182: '五大连池市' + }, + 231200: { + 231202: '北林区', + 231221: '望奎县', + 231222: '兰西县', + 231223: '青冈县', + 231224: '庆安县', + 231225: '明水县', + 231226: '绥棱县', + 231281: '安达市', + 231282: '肇东市', + 231283: '海伦市' + }, + 232700: { + 232701: '加格达奇区', + 232721: '呼玛县', + 232722: '塔河县', + 232723: '漠河县' + }, + 310000: { + 310100: '上海市', + }, + 310100: { + 310101: '黄浦区', + 310104: '徐汇区', + 310105: '长宁区', + 310106: '静安区', + 310107: '普陀区', + 310108: '闸北区', + 310109: '虹口区', + 310110: '杨浦区', + 310112: '闵行区', + 310113: '宝山区', + 310114: '嘉定区', + 310115: '浦东新区', + 310116: '金山区', + 310117: '松江区', + 310118: '青浦区', + 310120: '奉贤区', + 310230: '崇明县' + }, + 320000: { + 320100: '南京市', + 320200: '无锡市', + 320300: '徐州市', + 320400: '常州市', + 320500: '苏州市', + 320600: '南通市', + 320700: '连云港市', + 320800: '淮安市', + 320900: '盐城市', + 321000: '扬州市', + 321100: '镇江市', + 321200: '泰州市', + 321300: '宿迁市' + }, + 320100: { + 320102: '玄武区', + 320104: '秦淮区', + 320105: '建邺区', + 320106: '鼓楼区', + 320111: '浦口区', + 320113: '栖霞区', + 320114: '雨花台区', + 320115: '江宁区', + 320116: '六合区', + 320117: '溧水区', + 320118: '高淳区' + }, + 320200: { + 320202: '崇安区', + 320203: '南长区', + 320204: '北塘区', + 320205: '锡山区', + 320206: '惠山区', + 320211: '滨湖区', + 320281: '江阴市', + 320282: '宜兴市' + }, + 320300: { + 320302: '鼓楼区', + 320303: '云龙区', + 320305: '贾汪区', + 320311: '泉山区', + 320312: '铜山区', + 320321: '丰县', + 320322: '沛县', + 320324: '睢宁县', + 320381: '新沂市', + 320382: '邳州市' + }, + 320400: { + 320402: '天宁区', + 320404: '钟楼区', + 320411: '新北区', + 320412: '武进区', + 320481: '溧阳市', + 320482: '金坛区' + }, + 320500: { + 320505: '虎丘区', + 320506: '吴中区', + 320507: '相城区', + 320508: '姑苏区', + 320509: '吴江区', + 320581: '常熟市', + 320582: '张家港市', + 320583: '昆山市', + 320585: '太仓市' + }, + 320600: { + 320602: '崇川区', + 320611: '港闸区', + 320612: '通州区', + 320621: '海安县', + 320623: '如东县', + 320681: '启东市', + 320682: '如皋市', + 320684: '海门市' + }, + 320700: { + 320703: '连云区', + 320706: '海州区', + 320707: '赣榆区', + 320722: '东海县', + 320723: '灌云县', + 320724: '灌南县' + }, + 320800: { + 320802: '清河区', + 320803: '淮安区', + 320804: '淮阴区', + 320811: '清浦区', + 320826: '涟水县', + 320829: '洪泽县', + 320830: '盱眙县', + 320831: '金湖县' + }, + 320900: { + 320902: '亭湖区', + 320903: '盐都区', + 320921: '响水县', + 320922: '滨海县', + 320923: '阜宁县', + 320924: '射阳县', + 320925: '建湖县', + 320981: '东台市', + 320982: '大丰市' + }, + 321000: { + 321002: '广陵区', + 321003: '邗江区', + 321012: '江都区', + 321023: '宝应县', + 321081: '仪征市', + 321084: '高邮市' + }, + 321100: { + 321102: '京口区', + 321111: '润州区', + 321112: '丹徒区', + 321181: '丹阳市', + 321182: '扬中市', + 321183: '句容市' + }, + 321200: { + 321202: '海陵区', + 321203: '高港区', + 321204: '姜堰区', + 321281: '兴化市', + 321282: '靖江市', + 321283: '泰兴市' + }, + 321300: { + 321302: '宿城区', + 321311: '宿豫区', + 321322: '沭阳县', + 321323: '泗阳县', + 321324: '泗洪县' + }, + 330000: { + 330100: '杭州市', + 330200: '宁波市', + 330300: '温州市', + 330400: '嘉兴市', + 330500: '湖州市', + 330600: '绍兴市', + 330700: '金华市', + 330800: '衢州市', + 330900: '舟山市', + 331000: '台州市', + 331100: '丽水市' + }, + 330100: { + 330102: '上城区', + 330103: '下城区', + 330104: '江干区', + 330105: '拱墅区', + 330106: '西湖区', + 330108: '滨江区', + 330109: '萧山区', + 330110: '余杭区', + 330111: '富阳区', + 330122: '桐庐县', + 330127: '淳安县', + 330182: '建德市', + 330185: '临安市' + }, + 330200: { + 330203: '海曙区', + 330204: '江东区', + 330205: '江北区', + 330206: '北仑区', + 330211: '镇海区', + 330212: '鄞州区', + 330225: '象山县', + 330226: '宁海县', + 330281: '余姚市', + 330282: '慈溪市', + 330283: '奉化市' + }, + 330300: { + 330302: '鹿城区', + 330303: '龙湾区', + 330304: '瓯海区', + 330322: '洞头县', + 330324: '永嘉县', + 330326: '平阳县', + 330327: '苍南县', + 330328: '文成县', + 330329: '泰顺县', + 330381: '瑞安市', + 330382: '乐清市' + }, + 330400: { + 330402: '南湖区', + 330411: '秀洲区', + 330421: '嘉善县', + 330424: '海盐县', + 330481: '海宁市', + 330482: '平湖市', + 330483: '桐乡市' + }, + 330500: { + 330502: '吴兴区', + 330503: '南浔区', + 330521: '德清县', + 330522: '长兴县', + 330523: '安吉县' + }, + 330600: { + 330602: '越城区', + 330603: '柯桥区', + 330604: '上虞区', + 330624: '新昌县', + 330681: '诸暨市', + 330683: '嵊州市' + }, + 330700: { + 330702: '婺城区', + 330703: '金东区', + 330723: '武义县', + 330726: '浦江县', + 330727: '磐安县', + 330781: '兰溪市', + 330782: '义乌市', + 330783: '东阳市', + 330784: '永康市' + }, + 330800: { + 330802: '柯城区', + 330803: '衢江区', + 330822: '常山县', + 330824: '开化县', + 330825: '龙游县', + 330881: '江山市' + }, + 330900: { + 330902: '定海区', + 330903: '普陀区', + 330921: '岱山县', + 330922: '嵊泗县' + }, + 331000: { + 331002: '椒江区', + 331003: '黄岩区', + 331004: '路桥区', + 331021: '玉环县', + 331022: '三门县', + 331023: '天台县', + 331024: '仙居县', + 331081: '温岭市', + 331082: '临海市' + }, + 331100: { + 331102: '莲都区', + 331121: '青田县', + 331122: '缙云县', + 331123: '遂昌县', + 331124: '松阳县', + 331125: '云和县', + 331126: '庆元县', + 331127: '景宁畲族自治县', + 331181: '龙泉市' + }, + 340000: { + 340100: '合肥市', + 340200: '芜湖市', + 340300: '蚌埠市', + 340400: '淮南市', + 340500: '马鞍山市', + 340600: '淮北市', + 340700: '铜陵市', + 340800: '安庆市', + 341000: '黄山市', + 341100: '滁州市', + 341200: '阜阳市', + 341300: '宿州市', + 341500: '六安市', + 341600: '亳州市', + 341700: '池州市', + 341800: '宣城市' + }, + 340100: { + 340102: '瑶海区', + 340103: '庐阳区', + 340104: '蜀山区', + 340111: '包河区', + 340121: '长丰县', + 340122: '肥东县', + 340123: '肥西县', + 340124: '庐江县', + 340181: '巢湖市' + }, + 340200: { + 340202: '镜湖区', + 340203: '弋江区', + 340207: '鸠江区', + 340208: '三山区', + 340221: '芜湖县', + 340222: '繁昌县', + 340223: '南陵县', + 340225: '无为县' + }, + 340300: { + 340302: '龙子湖区', + 340303: '蚌山区', + 340304: '禹会区', + 340311: '淮上区', + 340321: '怀远县', + 340322: '五河县', + 340323: '固镇县' + }, + 340400: { + 340402: '大通区', + 340403: '田家庵区', + 340404: '谢家集区', + 340405: '八公山区', + 340406: '潘集区', + 340421: '凤台县' + }, + 340500: { + 340503: '花山区', + 340504: '雨山区', + 340506: '博望区', + 340521: '当涂县', + 340522: '含山县', + 340523: '和县' + }, + 340600: { + 340602: '杜集区', + 340603: '相山区', + 340604: '烈山区', + 340621: '濉溪县' + }, + 340700: { + 340702: '铜官山区', + 340703: '狮子山区', + 340711: '郊区', + 340721: '铜陵县' + }, + 340800: { + 340802: '迎江区', + 340803: '大观区', + 340811: '宜秀区', + 340822: '怀宁县', + 340823: '枞阳县', + 340824: '潜山县', + 340825: '太湖县', + 340826: '宿松县', + 340827: '望江县', + 340828: '岳西县', + 340881: '桐城市' + }, + 341000: { + 341002: '屯溪区', + 341003: '黄山区', + 341004: '徽州区', + 341021: '歙县', + 341022: '休宁县', + 341023: '黟县', + 341024: '祁门县' + }, + 341100: { + 341102: '琅琊区', + 341103: '南谯区', + 341122: '来安县', + 341124: '全椒县', + 341125: '定远县', + 341126: '凤阳县', + 341181: '天长市', + 341182: '明光市' + }, + 341200: { + 341202: '颍州区', + 341203: '颍东区', + 341204: '颍泉区', + 341221: '临泉县', + 341222: '太和县', + 341225: '阜南县', + 341226: '颍上县', + 341282: '界首市' + }, + 341300: { + 341302: '埇桥区', + 341321: '砀山县', + 341322: '萧县', + 341323: '灵璧县', + 341324: '泗县' + }, + 341500: { + 341502: '金安区', + 341503: '裕安区', + 341521: '寿县', + 341522: '霍邱县', + 341523: '舒城县', + 341524: '金寨县', + 341525: '霍山县' + }, + 341600: { + 341602: '谯城区', + 341621: '涡阳县', + 341622: '蒙城县', + 341623: '利辛县' + }, + 341700: { + 341702: '贵池区', + 341721: '东至县', + 341722: '石台县', + 341723: '青阳县' + }, + 341800: { + 341802: '宣州区', + 341821: '郎溪县', + 341822: '广德县', + 341823: '泾县', + 341824: '绩溪县', + 341825: '旌德县', + 341881: '宁国市' + }, + 350000: { + 350100: '福州市', + 350200: '厦门市', + 350300: '莆田市', + 350400: '三明市', + 350500: '泉州市', + 350600: '漳州市', + 350700: '南平市', + 350800: '龙岩市', + 350900: '宁德市' + }, + 350100: { + 350102: '鼓楼区', + 350103: '台江区', + 350104: '仓山区', + 350105: '马尾区', + 350111: '晋安区', + 350121: '闽侯县', + 350122: '连江县', + 350123: '罗源县', + 350124: '闽清县', + 350125: '永泰县', + 350128: '平潭县', + 350181: '福清市', + 350182: '长乐市' + }, + 350200: { + 350203: '思明区', + 350205: '海沧区', + 350206: '湖里区', + 350211: '集美区', + 350212: '同安区', + 350213: '翔安区' + }, + 350300: { + 350302: '城厢区', + 350303: '涵江区', + 350304: '荔城区', + 350305: '秀屿区', + 350322: '仙游县' + }, + 350400: { + 350402: '梅列区', + 350403: '三元区', + 350421: '明溪县', + 350423: '清流县', + 350424: '宁化县', + 350425: '大田县', + 350426: '尤溪县', + 350427: '沙县', + 350428: '将乐县', + 350429: '泰宁县', + 350430: '建宁县', + 350481: '永安市' + }, + 350500: { + 350502: '鲤城区', + 350503: '丰泽区', + 350504: '洛江区', + 350505: '泉港区', + 350521: '惠安县', + 350524: '安溪县', + 350525: '永春县', + 350526: '德化县', + 350527: '金门县', + 350581: '石狮市', + 350582: '晋江市', + 350583: '南安市' + }, + 350600: { + 350602: '芗城区', + 350603: '龙文区', + 350622: '云霄县', + 350623: '漳浦县', + 350624: '诏安县', + 350625: '长泰县', + 350626: '东山县', + 350627: '南靖县', + 350628: '平和县', + 350629: '华安县', + 350681: '龙海市' + }, + 350700: { + 350702: '延平区', + 350703: '建阳区', + 350721: '顺昌县', + 350722: '浦城县', + 350723: '光泽县', + 350724: '松溪县', + 350725: '政和县', + 350781: '邵武市', + 350782: '武夷山市', + 350783: '建瓯市' + }, + 350800: { + 350802: '新罗区', + 350803: '永定区', + 350821: '长汀县', + 350823: '上杭县', + 350824: '武平县', + 350825: '连城县', + 350881: '漳平市' + }, + 350900: { + 350902: '蕉城区', + 350921: '霞浦县', + 350922: '古田县', + 350923: '屏南县', + 350924: '寿宁县', + 350925: '周宁县', + 350926: '柘荣县', + 350981: '福安市', + 350982: '福鼎市' + }, + 360000: { + 360100: '南昌市', + 360200: '景德镇市', + 360300: '萍乡市', + 360400: '九江市', + 360500: '新余市', + 360600: '鹰潭市', + 360700: '赣州市', + 360800: '吉安市', + 360900: '宜春市', + 361000: '抚州市', + 361100: '上饶市' + }, + 360100: { + 360102: '东湖区', + 360103: '西湖区', + 360104: '青云谱区', + 360105: '湾里区', + 360111: '青山湖区', + 360121: '南昌县', + 360122: '新建县', + 360123: '安义县', + 360124: '进贤县' + }, + 360200: { + 360202: '昌江区', + 360203: '珠山区', + 360222: '浮梁县', + 360281: '乐平市' + }, + 360300: { + 360302: '安源区', + 360313: '湘东区', + 360321: '莲花县', + 360322: '上栗县', + 360323: '芦溪县' + }, + 360400: { + 360402: '庐山区', + 360403: '浔阳区', + 360421: '九江县', + 360423: '武宁县', + 360424: '修水县', + 360425: '永修县', + 360426: '德安县', + 360427: '星子县', + 360428: '都昌县', + 360429: '湖口县', + 360430: '彭泽县', + 360481: '瑞昌市', + 360482: '共青城市' + }, + 360500: { + 360502: '渝水区', + 360521: '分宜县' + }, + 360600: { + 360602: '月湖区', + 360622: '余江县', + 360681: '贵溪市' + }, + 360700: { + 360702: '章贡区', + 360703: '南康区', + 360721: '赣县', + 360722: '信丰县', + 360723: '大余县', + 360724: '上犹县', + 360725: '崇义县', + 360726: '安远县', + 360727: '龙南县', + 360728: '定南县', + 360729: '全南县', + 360730: '宁都县', + 360731: '于都县', + 360732: '兴国县', + 360733: '会昌县', + 360734: '寻乌县', + 360735: '石城县', + 360781: '瑞金市' + }, + 360800: { + 360802: '吉州区', + 360803: '青原区', + 360821: '吉安县', + 360822: '吉水县', + 360823: '峡江县', + 360824: '新干县', + 360825: '永丰县', + 360826: '泰和县', + 360827: '遂川县', + 360828: '万安县', + 360829: '安福县', + 360830: '永新县', + 360881: '井冈山市' + }, + 360900: { + 360902: '袁州区', + 360921: '奉新县', + 360922: '万载县', + 360923: '上高县', + 360924: '宜丰县', + 360925: '靖安县', + 360926: '铜鼓县', + 360981: '丰城市', + 360982: '樟树市', + 360983: '高安市' + }, + 361000: { + 361002: '临川区', + 361021: '南城县', + 361022: '黎川县', + 361023: '南丰县', + 361024: '崇仁县', + 361025: '乐安县', + 361026: '宜黄县', + 361027: '金溪县', + 361028: '资溪县', + 361029: '东乡县', + 361030: '广昌县' + }, + 361100: { + 361102: '信州区', + 361103: '广丰区', + 361121: '上饶县', + 361123: '玉山县', + 361124: '铅山县', + 361125: '横峰县', + 361126: '弋阳县', + 361127: '余干县', + 361128: '鄱阳县', + 361129: '万年县', + 361130: '婺源县', + 361181: '德兴市' + }, + 370000: { + 370100: '济南市', + 370200: '青岛市', + 370300: '淄博市', + 370400: '枣庄市', + 370500: '东营市', + 370600: '烟台市', + 370700: '潍坊市', + 370800: '济宁市', + 370900: '泰安市', + 371000: '威海市', + 371100: '日照市', + 371200: '莱芜市', + 371300: '临沂市', + 371400: '德州市', + 371500: '聊城市', + 371600: '滨州市', + 371700: '菏泽市' + }, + 370100: { + 370102: '历下区', + 370103: '市中区', + 370104: '槐荫区', + 370105: '天桥区', + 370112: '历城区', + 370113: '长清区', + 370124: '平阴县', + 370125: '济阳县', + 370126: '商河县', + 370181: '章丘市' + }, + 370200: { + 370202: '市南区', + 370203: '市北区', + 370211: '黄岛区', + 370212: '崂山区', + 370213: '李沧区', + 370214: '城阳区', + 370281: '胶州市', + 370282: '即墨市', + 370283: '平度市', + 370285: '莱西市' + }, + 370300: { + 370302: '淄川区', + 370303: '张店区', + 370304: '博山区', + 370305: '临淄区', + 370306: '周村区', + 370321: '桓台县', + 370322: '高青县', + 370323: '沂源县' + }, + 370400: { + 370402: '市中区', + 370403: '薛城区', + 370404: '峄城区', + 370405: '台儿庄区', + 370406: '山亭区', + 370481: '滕州市' + }, + 370500: { + 370502: '东营区', + 370503: '河口区', + 370521: '垦利县', + 370522: '利津县', + 370523: '广饶县' + }, + 370600: { + 370602: '芝罘区', + 370611: '福山区', + 370612: '牟平区', + 370613: '莱山区', + 370634: '长岛县', + 370681: '龙口市', + 370682: '莱阳市', + 370683: '莱州市', + 370684: '蓬莱市', + 370685: '招远市', + 370686: '栖霞市', + 370687: '海阳市' + }, + 370700: { + 370702: '潍城区', + 370703: '寒亭区', + 370704: '坊子区', + 370705: '奎文区', + 370724: '临朐县', + 370725: '昌乐县', + 370781: '青州市', + 370782: '诸城市', + 370783: '寿光市', + 370784: '安丘市', + 370785: '高密市', + 370786: '昌邑市' + }, + 370800: { + 370811: '任城区', + 370812: '兖州区', + 370826: '微山县', + 370827: '鱼台县', + 370828: '金乡县', + 370829: '嘉祥县', + 370830: '汶上县', + 370831: '泗水县', + 370832: '梁山县', + 370881: '曲阜市', + 370883: '邹城市' + }, + 370900: { + 370902: '泰山区', + 370911: '岱岳区', + 370921: '宁阳县', + 370923: '东平县', + 370982: '新泰市', + 370983: '肥城市' + }, + 371000: { + 371002: '环翠区', + 371003: '文登区', + 371082: '荣成市', + 371083: '乳山市' + }, + 371100: { + 371102: '东港区', + 371103: '岚山区', + 371121: '五莲县', + 371122: '莒县' + }, + 371200: { + 371202: '莱城区', + 371203: '钢城区' + }, + 371300: { + 371302: '兰山区', + 371311: '罗庄区', + 371312: '河东区', + 371321: '沂南县', + 371322: '郯城县', + 371323: '沂水县', + 371324: '兰陵县', + 371325: '费县', + 371326: '平邑县', + 371327: '莒南县', + 371328: '蒙阴县', + 371329: '临沭县' + }, + 371400: { + 371402: '德城区', + 371403: '陵城区', + 371422: '宁津县', + 371423: '庆云县', + 371424: '临邑县', + 371425: '齐河县', + 371426: '平原县', + 371427: '夏津县', + 371428: '武城县', + 371481: '乐陵市', + 371482: '禹城市' + }, + 371500: { + 371502: '东昌府区', + 371521: '阳谷县', + 371522: '莘县', + 371523: '茌平县', + 371524: '东阿县', + 371525: '冠县', + 371526: '高唐县', + 371581: '临清市' + }, + 371600: { + 371602: '滨城区', + 371603: '沾化区', + 371621: '惠民县', + 371622: '阳信县', + 371623: '无棣县', + 371625: '博兴县', + 371626: '邹平县' + }, + 371700: { + 371702: '牡丹区', + 371721: '曹县', + 371722: '单县', + 371723: '成武县', + 371724: '巨野县', + 371725: '郓城县', + 371726: '鄄城县', + 371727: '定陶县', + 371728: '东明县' + }, + 410000: { + 410100: '郑州市', + 410200: '开封市', + 410300: '洛阳市', + 410400: '平顶山市', + 410500: '安阳市', + 410600: '鹤壁市', + 410700: '新乡市', + 410800: '焦作市', + 410900: '濮阳市', + 411000: '许昌市', + 411100: '漯河市', + 411200: '三门峡市', + 411300: '南阳市', + 411400: '商丘市', + 411500: '信阳市', + 411600: '周口市', + 411700: '驻马店市', + 419001: '济源市' + }, + 410100: { + 410102: '中原区', + 410103: '二七区', + 410104: '管城回族区', + 410105: '金水区', + 410106: '上街区', + 410108: '惠济区', + 410122: '中牟县', + 410181: '巩义市', + 410182: '荥阳市', + 410183: '新密市', + 410184: '新郑市', + 410185: '登封市' + }, + 410200: { + 410202: '龙亭区', + 410203: '顺河回族区', + 410204: '鼓楼区', + 410205: '禹王台区', + 410212: '祥符区', + 410221: '杞县', + 410222: '通许县', + 410223: '尉氏县', + 410225: '兰考县' + }, + 410300: { + 410302: '老城区', + 410303: '西工区', + 410304: '瀍河回族区', + 410305: '涧西区', + 410306: '吉利区', + 410311: '洛龙区', + 410322: '孟津县', + 410323: '新安县', + 410324: '栾川县', + 410325: '嵩县', + 410326: '汝阳县', + 410327: '宜阳县', + 410328: '洛宁县', + 410329: '伊川县', + 410381: '偃师市' + }, + 410400: { + 410402: '新华区', + 410403: '卫东区', + 410404: '石龙区', + 410411: '湛河区', + 410421: '宝丰县', + 410422: '叶县', + 410423: '鲁山县', + 410425: '郏县', + 410481: '舞钢市', + 410482: '汝州市' + }, + 410500: { + 410502: '文峰区', + 410503: '北关区', + 410505: '殷都区', + 410506: '龙安区', + 410522: '安阳县', + 410523: '汤阴县', + 410526: '滑县', + 410527: '内黄县', + 410581: '林州市' + }, + 410600: { + 410602: '鹤山区', + 410603: '山城区', + 410611: '淇滨区', + 410621: '浚县', + 410622: '淇县' + }, + 410700: { + 410702: '红旗区', + 410703: '卫滨区', + 410704: '凤泉区', + 410711: '牧野区', + 410721: '新乡县', + 410724: '获嘉县', + 410725: '原阳县', + 410726: '延津县', + 410727: '封丘县', + 410728: '长垣县', + 410781: '卫辉市', + 410782: '辉县市' + }, + 410800: { + 410802: '解放区', + 410803: '中站区', + 410804: '马村区', + 410811: '山阳区', + 410821: '修武县', + 410822: '博爱县', + 410823: '武陟县', + 410825: '温县', + 410882: '沁阳市', + 410883: '孟州市' + }, + 410900: { + 410902: '华龙区', + 410922: '清丰县', + 410923: '南乐县', + 410926: '范县', + 410927: '台前县', + 410928: '濮阳县' + }, + 411000: { + 411002: '魏都区', + 411023: '许昌县', + 411024: '鄢陵县', + 411025: '襄城县', + 411081: '禹州市', + 411082: '长葛市' + }, + 411100: { + 411102: '源汇区', + 411103: '郾城区', + 411104: '召陵区', + 411121: '舞阳县', + 411122: '临颍县' + }, + 411200: { + 411202: '湖滨区', + 411203: '陕州区', + 411221: '渑池县', + 411224: '卢氏县', + 411281: '义马市', + 411282: '灵宝市' + }, + 411300: { + 411302: '宛城区', + 411303: '卧龙区', + 411321: '南召县', + 411322: '方城县', + 411323: '西峡县', + 411324: '镇平县', + 411325: '内乡县', + 411326: '淅川县', + 411327: '社旗县', + 411328: '唐河县', + 411329: '新野县', + 411330: '桐柏县', + 411381: '邓州市' + }, + 411400: { + 411402: '梁园区', + 411403: '睢阳区', + 411421: '民权县', + 411422: '睢县', + 411423: '宁陵县', + 411424: '柘城县', + 411425: '虞城县', + 411426: '夏邑县', + 411481: '永城市' + }, + 411500: { + 411502: '浉河区', + 411503: '平桥区', + 411521: '罗山县', + 411522: '光山县', + 411523: '新县', + 411524: '商城县', + 411525: '固始县', + 411526: '潢川县', + 411527: '淮滨县', + 411528: '息县' + }, + 411600: { + 411602: '川汇区', + 411621: '扶沟县', + 411622: '西华县', + 411623: '商水县', + 411624: '沈丘县', + 411625: '郸城县', + 411626: '淮阳县', + 411627: '太康县', + 411628: '鹿邑县', + 411681: '项城市' + }, + 411700: { + 411702: '驿城区', + 411721: '西平县', + 411722: '上蔡县', + 411723: '平舆县', + 411724: '正阳县', + 411725: '确山县', + 411726: '泌阳县', + 411727: '汝南县', + 411728: '遂平县', + 411729: '新蔡县' + }, + 420000: { + 420100: '武汉市', + 420200: '黄石市', + 420300: '十堰市', + 420500: '宜昌市', + 420600: '襄阳市', + 420700: '鄂州市', + 420800: '荆门市', + 420900: '孝感市', + 421000: '荆州市', + 421100: '黄冈市', + 421200: '咸宁市', + 421300: '随州市', + 422800: '恩施土家族苗族自治州', + 429004: '仙桃市', + 429005: '潜江市', + 429006: '天门市', + 429021: '神农架林区' + }, + 420100: { + 420102: '江岸区', + 420103: '江汉区', + 420104: '硚口区', + 420105: '汉阳区', + 420106: '武昌区', + 420107: '青山区', + 420111: '洪山区', + 420112: '东西湖区', + 420113: '汉南区', + 420114: '蔡甸区', + 420115: '江夏区', + 420116: '黄陂区', + 420117: '新洲区' + }, + 420200: { + 420202: '黄石港区', + 420203: '西塞山区', + 420204: '下陆区', + 420205: '铁山区', + 420222: '阳新县', + 420281: '大冶市' + }, + 420300: { + 420302: '茅箭区', + 420303: '张湾区', + 420304: '郧阳区', + 420322: '郧西县', + 420323: '竹山县', + 420324: '竹溪县', + 420325: '房县', + 420381: '丹江口市' + }, + 420500: { + 420502: '西陵区', + 420503: '伍家岗区', + 420504: '点军区', + 420505: '猇亭区', + 420506: '夷陵区', + 420525: '远安县', + 420526: '兴山县', + 420527: '秭归县', + 420528: '长阳土家族自治县', + 420529: '五峰土家族自治县', + 420581: '宜都市', + 420582: '当阳市', + 420583: '枝江市' + }, + 420600: { + 420602: '襄城区', + 420606: '樊城区', + 420607: '襄州区', + 420624: '南漳县', + 420625: '谷城县', + 420626: '保康县', + 420682: '老河口市', + 420683: '枣阳市', + 420684: '宜城市' + }, + 420700: { + 420702: '梁子湖区', + 420703: '华容区', + 420704: '鄂城区' + }, + 420800: { + 420802: '东宝区', + 420804: '掇刀区', + 420821: '京山县', + 420822: '沙洋县', + 420881: '钟祥市' + }, + 420900: { + 420902: '孝南区', + 420921: '孝昌县', + 420922: '大悟县', + 420923: '云梦县', + 420981: '应城市', + 420982: '安陆市', + 420984: '汉川市' + }, + 421000: { + 421002: '沙市区', + 421003: '荆州区', + 421022: '公安县', + 421023: '监利县', + 421024: '江陵县', + 421081: '石首市', + 421083: '洪湖市', + 421087: '松滋市' + }, + 421100: { + 421102: '黄州区', + 421121: '团风县', + 421122: '红安县', + 421123: '罗田县', + 421124: '英山县', + 421125: '浠水县', + 421126: '蕲春县', + 421127: '黄梅县', + 421181: '麻城市', + 421182: '武穴市' + }, + 421200: { + 421202: '咸安区', + 421221: '嘉鱼县', + 421222: '通城县', + 421223: '崇阳县', + 421224: '通山县', + 421281: '赤壁市' + }, + 421300: { + 421303: '曾都区', + 421321: '随县', + 421381: '广水市' + }, + 422800: { + 422801: '恩施市', + 422802: '利川市', + 422822: '建始县', + 422823: '巴东县', + 422825: '宣恩县', + 422826: '咸丰县', + 422827: '来凤县', + 422828: '鹤峰县' + }, + 430000: { + 430100: '长沙市', + 430200: '株洲市', + 430300: '湘潭市', + 430400: '衡阳市', + 430500: '邵阳市', + 430600: '岳阳市', + 430700: '常德市', + 430800: '张家界市', + 430900: '益阳市', + 431000: '郴州市', + 431100: '永州市', + 431200: '怀化市', + 431300: '娄底市', + 433100: '湘西土家族苗族自治州' + }, + 430100: { + 430102: '芙蓉区', + 430103: '天心区', + 430104: '岳麓区', + 430105: '开福区', + 430111: '雨花区', + 430112: '望城区', + 430121: '长沙县', + 430124: '宁乡县', + 430181: '浏阳市' + }, + 430200: { + 430202: '荷塘区', + 430203: '芦淞区', + 430204: '石峰区', + 430211: '天元区', + 430221: '株洲县', + 430223: '攸县', + 430224: '茶陵县', + 430225: '炎陵县', + 430281: '醴陵市' + }, + 430300: { + 430302: '雨湖区', + 430304: '岳塘区', + 430321: '湘潭县', + 430381: '湘乡市', + 430382: '韶山市' + }, + 430400: { + 430405: '珠晖区', + 430406: '雁峰区', + 430407: '石鼓区', + 430408: '蒸湘区', + 430412: '南岳区', + 430421: '衡阳县', + 430422: '衡南县', + 430423: '衡山县', + 430424: '衡东县', + 430426: '祁东县', + 430481: '耒阳市', + 430482: '常宁市' + }, + 430500: { + 430502: '双清区', + 430503: '大祥区', + 430511: '北塔区', + 430521: '邵东县', + 430522: '新邵县', + 430523: '邵阳县', + 430524: '隆回县', + 430525: '洞口县', + 430527: '绥宁县', + 430528: '新宁县', + 430529: '城步苗族自治县', + 430581: '武冈市' + }, + 430600: { + 430602: '岳阳楼区', + 430603: '云溪区', + 430611: '君山区', + 430621: '岳阳县', + 430623: '华容县', + 430624: '湘阴县', + 430626: '平江县', + 430681: '汨罗市', + 430682: '临湘市' + }, + 430700: { + 430702: '武陵区', + 430703: '鼎城区', + 430721: '安乡县', + 430722: '汉寿县', + 430723: '澧县', + 430724: '临澧县', + 430725: '桃源县', + 430726: '石门县', + 430781: '津市市' + }, + 430800: { + 430802: '永定区', + 430811: '武陵源区', + 430821: '慈利县', + 430822: '桑植县' + }, + 430900: { + 430902: '资阳区', + 430903: '赫山区', + 430921: '南县', + 430922: '桃江县', + 430923: '安化县', + 430981: '沅江市' + }, + 431000: { + 431002: '北湖区', + 431003: '苏仙区', + 431021: '桂阳县', + 431022: '宜章县', + 431023: '永兴县', + 431024: '嘉禾县', + 431025: '临武县', + 431026: '汝城县', + 431027: '桂东县', + 431028: '安仁县', + 431081: '资兴市' + }, + 431100: { + 431102: '零陵区', + 431103: '冷水滩区', + 431121: '祁阳县', + 431122: '东安县', + 431123: '双牌县', + 431124: '道县', + 431125: '江永县', + 431126: '宁远县', + 431127: '蓝山县', + 431128: '新田县', + 431129: '江华瑶族自治县' + }, + 431200: { + 431202: '鹤城区', + 431221: '中方县', + 431222: '沅陵县', + 431223: '辰溪县', + 431224: '溆浦县', + 431225: '会同县', + 431226: '麻阳苗族自治县', + 431227: '新晃侗族自治县', + 431228: '芷江侗族自治县', + 431229: '靖州苗族侗族自治县', + 431230: '通道侗族自治县', + 431281: '洪江市' + }, + 431300: { + 431302: '娄星区', + 431321: '双峰县', + 431322: '新化县', + 431381: '冷水江市', + 431382: '涟源市' + }, + 433100: { + 433101: '吉首市', + 433122: '泸溪县', + 433123: '凤凰县', + 433124: '花垣县', + 433125: '保靖县', + 433126: '古丈县', + 433127: '永顺县', + 433130: '龙山县' + }, + 440000: { + 440100: '广州市', + 440200: '韶关市', + 440300: '深圳市', + 440400: '珠海市', + 440500: '汕头市', + 440600: '佛山市', + 440700: '江门市', + 440800: '湛江市', + 440900: '茂名市', + 441200: '肇庆市', + 441300: '惠州市', + 441400: '梅州市', + 441500: '汕尾市', + 441600: '河源市', + 441700: '阳江市', + 441800: '清远市', + 441900: '东莞市', + 442000: '中山市', + 445100: '潮州市', + 445200: '揭阳市', + 445300: '云浮市' + }, + 440100: { + 440103: '荔湾区', + 440104: '越秀区', + 440105: '海珠区', + 440106: '天河区', + 440111: '白云区', + 440112: '黄埔区', + 440113: '番禺区', + 440114: '花都区', + 440115: '南沙区', + 440117: '从化区', + 440118: '增城区' + }, + 440200: { + 440203: '武江区', + 440204: '浈江区', + 440205: '曲江区', + 440222: '始兴县', + 440224: '仁化县', + 440229: '翁源县', + 440232: '乳源瑶族自治县', + 440233: '新丰县', + 440281: '乐昌市', + 440282: '南雄市' + }, + 440300: { + 440303: '罗湖区', + 440304: '福田区', + 440305: '南山区', + 440306: '宝安区', + 440307: '龙岗区', + 440308: '盐田区' + }, + 440400: { + 440402: '香洲区', + 440403: '斗门区', + 440404: '金湾区' + }, + 440500: { + 440507: '龙湖区', + 440511: '金平区', + 440512: '濠江区', + 440513: '潮阳区', + 440514: '潮南区', + 440515: '澄海区', + 440523: '南澳县' + }, + 440600: { + 440604: '禅城区', + 440605: '南海区', + 440606: '顺德区', + 440607: '三水区', + 440608: '高明区' + }, + 440700: { + 440703: '蓬江区', + 440704: '江海区', + 440705: '新会区', + 440781: '台山市', + 440783: '开平市', + 440784: '鹤山市', + 440785: '恩平市' + }, + 440800: { + 440802: '赤坎区', + 440803: '霞山区', + 440804: '坡头区', + 440811: '麻章区', + 440823: '遂溪县', + 440825: '徐闻县', + 440881: '廉江市', + 440882: '雷州市', + 440883: '吴川市' + }, + 440900: { + 440902: '茂南区', + 440904: '电白区', + 440981: '高州市', + 440982: '化州市', + 440983: '信宜市' + }, + 441200: { + 441202: '端州区', + 441203: '鼎湖区', + 441223: '广宁县', + 441224: '怀集县', + 441225: '封开县', + 441226: '德庆县', + 441283: '高要区', + 441284: '四会市' + }, + 441300: { + 441302: '惠城区', + 441303: '惠阳区', + 441322: '博罗县', + 441323: '惠东县', + 441324: '龙门县' + }, + 441400: { + 441402: '梅江区', + 441403: '梅县区', + 441422: '大埔县', + 441423: '丰顺县', + 441424: '五华县', + 441426: '平远县', + 441427: '蕉岭县', + 441481: '兴宁市' + }, + 441500: { + 441502: '城区', + 441521: '海丰县', + 441523: '陆河县', + 441581: '陆丰市' + }, + 441600: { + 441602: '源城区', + 441621: '紫金县', + 441622: '龙川县', + 441623: '连平县', + 441624: '和平县', + 441625: '东源县' + }, + 441700: { + 441702: '江城区', + 441704: '阳东区', + 441721: '阳西县', + 441781: '阳春市' + }, + 441800: { + 441802: '清城区', + 441803: '清新区', + 441821: '佛冈县', + 441823: '阳山县', + 441825: '连山壮族瑶族自治县', + 441826: '连南瑶族自治县', + 441881: '英德市', + 441882: '连州市' + }, + 441900: { + 441900: '三元里' + }, + 442000: { + 442000: '湖滨北路' + }, + 445100: { + 445102: '湘桥区', + 445103: '潮安区', + 445122: '饶平县' + }, + 445200: { + 445202: '榕城区', + 445203: '揭东区', + 445222: '揭西县', + 445224: '惠来县', + 445281: '普宁市' + }, + 445300: { + 445302: '云城区', + 445303: '云安区', + 445321: '新兴县', + 445322: '郁南县', + 445381: '罗定市' + }, + 450000: { + 450100: '南宁市', + 450200: '柳州市', + 450300: '桂林市', + 450400: '梧州市', + 450500: '北海市', + 450600: '防城港市', + 450700: '钦州市', + 450800: '贵港市', + 450900: '玉林市', + 451000: '百色市', + 451100: '贺州市', + 451200: '河池市', + 451300: '来宾市', + 451400: '崇左市' + }, + 450100: { + 450102: '兴宁区', + 450103: '青秀区', + 450105: '江南区', + 450107: '西乡塘区', + 450108: '良庆区', + 450109: '邕宁区', + 450110: '武鸣区', + 450123: '隆安县', + 450124: '马山县', + 450125: '上林县', + 450126: '宾阳县', + 450127: '横县' + }, + 450200: { + 450202: '城中区', + 450203: '鱼峰区', + 450204: '柳南区', + 450205: '柳北区', + 450221: '柳江县', + 450222: '柳城县', + 450223: '鹿寨县', + 450224: '融安县', + 450225: '融水苗族自治县', + 450226: '三江侗族自治县' + }, + 450300: { + 450302: '秀峰区', + 450303: '叠彩区', + 450304: '象山区', + 450305: '七星区', + 450311: '雁山区', + 450312: '临桂区', + 450321: '阳朔县', + 450323: '灵川县', + 450324: '全州县', + 450325: '兴安县', + 450326: '永福县', + 450327: '灌阳县', + 450328: '龙胜各族自治县', + 450329: '资源县', + 450330: '平乐县', + 450331: '荔浦县', + 450332: '恭城瑶族自治县' + }, + 450400: { + 450403: '万秀区', + 450405: '长洲区', + 450406: '龙圩区', + 450421: '苍梧县', + 450422: '藤县', + 450423: '蒙山县', + 450481: '岑溪市' + }, + 450500: { + 450502: '海城区', + 450503: '银海区', + 450512: '铁山港区', + 450521: '合浦县' + }, + 450600: { + 450602: '港口区', + 450603: '防城区', + 450621: '上思县', + 450681: '东兴市' + }, + 450700: { + 450702: '钦南区', + 450703: '钦北区', + 450721: '灵山县', + 450722: '浦北县' + }, + 450800: { + 450802: '港北区', + 450803: '港南区', + 450804: '覃塘区', + 450821: '平南县', + 450881: '桂平市' + }, + 450900: { + 450902: '玉州区', + 450903: '福绵区', + 450921: '容县', + 450922: '陆川县', + 450923: '博白县', + 450924: '兴业县', + 450981: '北流市' + }, + 451000: { + 451002: '右江区', + 451021: '田阳县', + 451022: '田东县', + 451023: '平果县', + 451024: '德保县', + 451025: '靖西县', + 451026: '那坡县', + 451027: '凌云县', + 451028: '乐业县', + 451029: '田林县', + 451030: '西林县', + 451031: '隆林各族自治县' + }, + 451100: { + 451102: '八步区', + 451121: '昭平县', + 451122: '钟山县', + 451123: '富川瑶族自治县' + }, + 451200: { + 451202: '金城江区', + 451221: '南丹县', + 451222: '天峨县', + 451223: '凤山县', + 451224: '东兰县', + 451225: '罗城仫佬族自治县', + 451226: '环江毛南族自治县', + 451227: '巴马瑶族自治县', + 451228: '都安瑶族自治县', + 451229: '大化瑶族自治县', + 451281: '宜州市' + }, + 451300: { + 451302: '兴宾区', + 451321: '忻城县', + 451322: '象州县', + 451323: '武宣县', + 451324: '金秀瑶族自治县', + 451381: '合山市' + }, + 451400: { + 451402: '江州区', + 451421: '扶绥县', + 451422: '宁明县', + 451423: '龙州县', + 451424: '大新县', + 451425: '天等县', + 451481: '凭祥市' + }, + 460000: { + 460100: '海口市', + 460200: '三亚市', + 460300: '三沙市', + 460400: '儋州市', + 469001: '五指山市', + 469002: '琼海市', + 469005: '文昌市', + 469006: '万宁市', + 469007: '东方市', + 469021: '定安县', + 469022: '屯昌县', + 469023: '澄迈县', + 469024: '临高县', + 469025: '白沙黎族自治县', + 469026: '昌江黎族自治县', + 469027: '乐东黎族自治县', + 469028: '陵水黎族自治县', + 469029: '保亭黎族苗族自治县', + 469030: '琼中黎族苗族自治县' + }, + 460100: { + 460105: '秀英区', + 460106: '龙华区', + 460107: '琼山区', + 460108: '美兰区' + }, + 460200: { + 460200: '三亚湾', + 460202: '海棠区', + 460203: '吉阳区', + 460204: '天涯区', + 460205: '崖州区' + }, + 460300: { + 460321: '西沙群岛', + 460322: '南沙群岛', + 460323: '中沙群岛的岛礁及其海域' + }, + 500000: { + 500100: '重庆市', + }, + 500100: { + 500101: '万州区', + 500102: '涪陵区', + 500103: '渝中区', + 500104: '大渡口区', + 500105: '江北区', + 500106: '沙坪坝区', + 500107: '九龙坡区', + 500108: '南岸区', + 500109: '北碚区', + 500110: '綦江区', + 500111: '大足区', + 500112: '渝北区', + 500113: '巴南区', + 500114: '黔江区', + 500115: '长寿区', + 500116: '江津区', + 500117: '合川区', + 500118: '永川区', + 500119: '南川区', + 500120: '璧山区', + 500151: '铜梁区', + 500223: '潼南区', + 500226: '荣昌区', + 500228: '梁平县', + 500229: '城口县', + 500230: '丰都县', + 500231: '垫江县', + 500232: '武隆县', + 500233: '忠县', + 500234: '开县', + 500235: '云阳县', + 500236: '奉节县', + 500237: '巫山县', + 500238: '巫溪县', + 500240: '石柱土家族自治县', + 500241: '秀山土家族苗族自治县', + 500242: '酉阳土家族苗族自治县', + 500243: '彭水苗族土家族自治县' + }, + 510000: { + 510100: '成都市', + 510300: '自贡市', + 510400: '攀枝花市', + 510500: '泸州市', + 510600: '德阳市', + 510700: '绵阳市', + 510800: '广元市', + 510900: '遂宁市', + 511000: '内江市', + 511100: '乐山市', + 511300: '南充市', + 511400: '眉山市', + 511500: '宜宾市', + 511600: '广安市', + 511700: '达州市', + 511800: '雅安市', + 511900: '巴中市', + 512000: '资阳市', + 513200: '阿坝藏族羌族自治州', + 513300: '甘孜藏族自治州', + 513400: '凉山彝族自治州' + }, + 510100: { + 510104: '锦江区', + 510105: '青羊区', + 510106: '金牛区', + 510107: '武侯区', + 510108: '成华区', + 510112: '龙泉驿区', + 510113: '青白江区', + 510114: '新都区', + 510115: '温江区', + 510121: '金堂县', + 510122: '双流县', + 510124: '郫县', + 510129: '大邑县', + 510131: '蒲江县', + 510132: '新津县', + 510181: '都江堰市', + 510182: '彭州市', + 510183: '邛崃市', + 510184: '崇州市' + }, + 510300: { + 510302: '自流井区', + 510303: '贡井区', + 510304: '大安区', + 510311: '沿滩区', + 510321: '荣县', + 510322: '富顺县' + }, + 510400: { + 510402: '东区', + 510403: '西区', + 510411: '仁和区', + 510421: '米易县', + 510422: '盐边县' + }, + 510500: { + 510502: '江阳区', + 510503: '纳溪区', + 510504: '龙马潭区', + 510521: '泸县', + 510522: '合江县', + 510524: '叙永县', + 510525: '古蔺县' + }, + 510600: { + 510603: '旌阳区', + 510623: '中江县', + 510626: '罗江县', + 510681: '广汉市', + 510682: '什邡市', + 510683: '绵竹市' + }, + 510700: { + 510703: '涪城区', + 510704: '游仙区', + 510722: '三台县', + 510723: '盐亭县', + 510724: '安县', + 510725: '梓潼县', + 510726: '北川羌族自治县', + 510727: '平武县', + 510781: '江油市' + }, + 510800: { + 510802: '利州区', + 510811: '昭化区', + 510812: '朝天区', + 510821: '旺苍县', + 510822: '青川县', + 510823: '剑阁县', + 510824: '苍溪县' + }, + 510900: { + 510903: '船山区', + 510904: '安居区', + 510921: '蓬溪县', + 510922: '射洪县', + 510923: '大英县' + }, + 511000: { + 511002: '市中区', + 511011: '东兴区', + 511024: '威远县', + 511025: '资中县', + 511028: '隆昌县' + }, + 511100: { + 511102: '市中区', + 511111: '沙湾区', + 511112: '五通桥区', + 511113: '金口河区', + 511123: '犍为县', + 511124: '井研县', + 511126: '夹江县', + 511129: '沐川县', + 511132: '峨边彝族自治县', + 511133: '马边彝族自治县', + 511181: '峨眉山市' + }, + 511300: { + 511302: '顺庆区', + 511303: '高坪区', + 511304: '嘉陵区', + 511321: '南部县', + 511322: '营山县', + 511323: '蓬安县', + 511324: '仪陇县', + 511325: '西充县', + 511381: '阆中市' + }, + 511400: { + 511402: '东坡区', + 511403: '彭山区', + 511421: '仁寿县', + 511423: '洪雅县', + 511424: '丹棱县', + 511425: '青神县' + }, + 511500: { + 511502: '翠屏区', + 511503: '南溪区', + 511521: '宜宾县', + 511523: '江安县', + 511524: '长宁县', + 511525: '高县', + 511526: '珙县', + 511527: '筠连县', + 511528: '兴文县', + 511529: '屏山县' + }, + 511600: { + 511602: '广安区', + 511603: '前锋区', + 511621: '岳池县', + 511622: '武胜县', + 511623: '邻水县', + 511681: '华蓥市' + }, + 511700: { + 511702: '通川区', + 511703: '达川区', + 511722: '宣汉县', + 511723: '开江县', + 511724: '大竹县', + 511725: '渠县', + 511781: '万源市' + }, + 511800: { + 511802: '雨城区', + 511803: '名山区', + 511822: '荥经县', + 511823: '汉源县', + 511824: '石棉县', + 511825: '天全县', + 511826: '芦山县', + 511827: '宝兴县' + }, + 511900: { + 511902: '巴州区', + 511903: '恩阳区', + 511921: '通江县', + 511922: '南江县', + 511923: '平昌县' + }, + 512000: { + 512002: '雁江区', + 512021: '安岳县', + 512022: '乐至县', + 512081: '简阳市' + }, + 513200: { + 513221: '汶川县', + 513222: '理县', + 513223: '茂县', + 513224: '松潘县', + 513225: '九寨沟县', + 513226: '金川县', + 513227: '小金县', + 513228: '黑水县', + 513229: '马尔康县', + 513230: '壤塘县', + 513231: '阿坝县', + 513232: '若尔盖县', + 513233: '红原县' + }, + 513300: { + 513301: '康定市', + 513322: '泸定县', + 513323: '丹巴县', + 513324: '九龙县', + 513325: '雅江县', + 513326: '道孚县', + 513327: '炉霍县', + 513328: '甘孜县', + 513329: '新龙县', + 513330: '德格县', + 513331: '白玉县', + 513332: '石渠县', + 513333: '色达县', + 513334: '理塘县', + 513335: '巴塘县', + 513336: '乡城县', + 513337: '稻城县', + 513338: '得荣县' + }, + 513400: { + 513401: '西昌市', + 513422: '木里藏族自治县', + 513423: '盐源县', + 513424: '德昌县', + 513425: '会理县', + 513426: '会东县', + 513427: '宁南县', + 513428: '普格县', + 513429: '布拖县', + 513430: '金阳县', + 513431: '昭觉县', + 513432: '喜德县', + 513433: '冕宁县', + 513434: '越西县', + 513435: '甘洛县', + 513436: '美姑县', + 513437: '雷波县' + }, + 520000: { + 520100: '贵阳市', + 520200: '六盘水市', + 520300: '遵义市', + 520400: '安顺市', + 520500: '毕节市', + 520600: '铜仁市', + 522300: '黔西南布依族苗族自治州', + 522600: '黔东南苗族侗族自治州', + 522700: '黔南布依族苗族自治州' + }, + 520100: { + 520102: '南明区', + 520103: '云岩区', + 520111: '花溪区', + 520112: '乌当区', + 520113: '白云区', + 520115: '观山湖区', + 520121: '开阳县', + 520122: '息烽县', + 520123: '修文县', + 520181: '清镇市' + }, + 520200: { + 520201: '钟山区', + 520203: '六枝特区', + 520221: '水城县', + 520222: '盘县' + }, + 520300: { + 520302: '红花岗区', + 520303: '汇川区', + 520321: '遵义县', + 520322: '桐梓县', + 520323: '绥阳县', + 520324: '正安县', + 520325: '道真仡佬族苗族自治县', + 520326: '务川仡佬族苗族自治县', + 520327: '凤冈县', + 520328: '湄潭县', + 520329: '余庆县', + 520330: '习水县', + 520381: '赤水市', + 520382: '仁怀市' + }, + 520400: { + 520402: '西秀区', + 520403: '平坝区', + 520422: '普定县', + 520423: '镇宁布依族苗族自治县', + 520424: '关岭布依族苗族自治县', + 520425: '紫云苗族布依族自治县' + }, + 520500: { + 520502: '七星关区', + 520521: '大方县', + 520522: '黔西县', + 520523: '金沙县', + 520524: '织金县', + 520525: '纳雍县', + 520526: '威宁彝族回族苗族自治县', + 520527: '赫章县' + }, + 520600: { + 520602: '碧江区', + 520603: '万山区', + 520621: '江口县', + 520622: '玉屏侗族自治县', + 520623: '石阡县', + 520624: '思南县', + 520625: '印江土家族苗族自治县', + 520626: '德江县', + 520627: '沿河土家族自治县', + 520628: '松桃苗族自治县' + }, + 522300: { + 522301: '兴义市', + 522322: '兴仁县', + 522323: '普安县', + 522324: '晴隆县', + 522325: '贞丰县', + 522326: '望谟县', + 522327: '册亨县', + 522328: '安龙县' + }, + 522600: { + 522601: '凯里市', + 522622: '黄平县', + 522623: '施秉县', + 522624: '三穗县', + 522625: '镇远县', + 522626: '岑巩县', + 522627: '天柱县', + 522628: '锦屏县', + 522629: '剑河县', + 522630: '台江县', + 522631: '黎平县', + 522632: '榕江县', + 522633: '从江县', + 522634: '雷山县', + 522635: '麻江县', + 522636: '丹寨县' + }, + 522700: { + 522701: '都匀市', + 522702: '福泉市', + 522722: '荔波县', + 522723: '贵定县', + 522725: '瓮安县', + 522726: '独山县', + 522727: '平塘县', + 522728: '罗甸县', + 522729: '长顺县', + 522730: '龙里县', + 522731: '惠水县', + 522732: '三都水族自治县' + }, + 530000: { + 530100: '昆明市', + 530300: '曲靖市', + 530400: '玉溪市', + 530500: '保山市', + 530600: '昭通市', + 530700: '丽江市', + 530800: '普洱市', + 530900: '临沧市', + 532300: '楚雄彝族自治州', + 532500: '红河哈尼族彝族自治州', + 532600: '文山壮族苗族自治州', + 532800: '西双版纳傣族自治州', + 532900: '大理白族自治州', + 533100: '德宏傣族景颇族自治州', + 533300: '怒江傈僳族自治州', + 533400: '迪庆藏族自治州' + }, + 530100: { + 530102: '五华区', + 530103: '盘龙区', + 530111: '官渡区', + 530112: '西山区', + 530113: '东川区', + 530114: '呈贡区', + 530122: '晋宁县', + 530124: '富民县', + 530125: '宜良县', + 530126: '石林彝族自治县', + 530127: '嵩明县', + 530128: '禄劝彝族苗族自治县', + 530129: '寻甸回族彝族自治县', + 530181: '安宁市' + }, + 530300: { + 530302: '麒麟区', + 530321: '马龙县', + 530322: '陆良县', + 530323: '师宗县', + 530324: '罗平县', + 530325: '富源县', + 530326: '会泽县', + 530328: '沾益县', + 530381: '宣威市' + }, + 530400: { + 530402: '红塔区', + 530421: '江川县', + 530422: '澄江县', + 530423: '通海县', + 530424: '华宁县', + 530425: '易门县', + 530426: '峨山彝族自治县', + 530427: '新平彝族傣族自治县', + 530428: '元江哈尼族彝族傣族自治县' + }, + 530500: { + 530502: '隆阳区', + 530521: '施甸县', + 530522: '腾冲县', + 530523: '龙陵县', + 530524: '昌宁县' + }, + 530600: { + 530602: '昭阳区', + 530621: '鲁甸县', + 530622: '巧家县', + 530623: '盐津县', + 530624: '大关县', + 530625: '永善县', + 530626: '绥江县', + 530627: '镇雄县', + 530628: '彝良县', + 530629: '威信县', + 530630: '水富县' + }, + 530700: { + 530702: '古城区', + 530721: '玉龙纳西族自治县', + 530722: '永胜县', + 530723: '华坪县', + 530724: '宁蒗彝族自治县' + }, + 530800: { + 530802: '思茅区', + 530821: '宁洱哈尼族彝族自治县', + 530822: '墨江哈尼族自治县', + 530823: '景东彝族自治县', + 530824: '景谷傣族彝族自治县', + 530825: '镇沅彝族哈尼族拉祜族自治县', + 530826: '江城哈尼族彝族自治县', + 530827: '孟连傣族拉祜族佤族自治县', + 530828: '澜沧拉祜族自治县', + 530829: '西盟佤族自治县' + }, + 530900: { + 530902: '临翔区', + 530921: '凤庆县', + 530922: '云县', + 530923: '永德县', + 530924: '镇康县', + 530925: '双江拉祜族佤族布朗族傣族自治县', + 530926: '耿马傣族佤族自治县', + 530927: '沧源佤族自治县' + }, + 532300: { + 532301: '楚雄市', + 532322: '双柏县', + 532323: '牟定县', + 532324: '南华县', + 532325: '姚安县', + 532326: '大姚县', + 532327: '永仁县', + 532328: '元谋县', + 532329: '武定县', + 532331: '禄丰县' + }, + 532500: { + 532501: '个旧市', + 532502: '开远市', + 532503: '蒙自市', + 532504: '弥勒市', + 532523: '屏边苗族自治县', + 532524: '建水县', + 532525: '石屏县', + 532527: '泸西县', + 532528: '元阳县', + 532529: '红河县', + 532530: '金平苗族瑶族傣族自治县', + 532531: '绿春县', + 532532: '河口瑶族自治县' + }, + 532600: { + 532601: '文山市', + 532622: '砚山县', + 532623: '西畴县', + 532624: '麻栗坡县', + 532625: '马关县', + 532626: '丘北县', + 532627: '广南县', + 532628: '富宁县' + }, + 532800: { + 532801: '景洪市', + 532822: '勐海县', + 532823: '勐腊县' + }, + 532900: { + 532901: '大理市', + 532922: '漾濞彝族自治县', + 532923: '祥云县', + 532924: '宾川县', + 532925: '弥渡县', + 532926: '南涧彝族自治县', + 532927: '巍山彝族回族自治县', + 532928: '永平县', + 532929: '云龙县', + 532930: '洱源县', + 532931: '剑川县', + 532932: '鹤庆县' + }, + 533100: { + 533102: '瑞丽市', + 533103: '芒市', + 533122: '梁河县', + 533123: '盈江县', + 533124: '陇川县' + }, + 533300: { + 533321: '泸水县', + 533323: '福贡县', + 533324: '贡山独龙族怒族自治县', + 533325: '兰坪白族普米族自治县' + }, + 533400: { + 533401: '香格里拉市', + 533422: '德钦县', + 533423: '维西傈僳族自治县' + }, + 540000: { + 540100: '拉萨市', + 540200: '日喀则市', + 540300: '昌都市', + 542200: '山南地区', + 542400: '那曲地区', + 542500: '阿里地区', + 542600: '林芝市' + }, + 540100: { + 540102: '城关区', + 540121: '林周县', + 540122: '当雄县', + 540123: '尼木县', + 540124: '曲水县', + 540125: '堆龙德庆县', + 540126: '达孜县', + 540127: '墨竹工卡县' + }, + 540200: { + 540202: '桑珠孜区', + 540221: '南木林县', + 540222: '江孜县', + 540223: '定日县', + 540224: '萨迦县', + 540225: '拉孜县', + 540226: '昂仁县', + 540227: '谢通门县', + 540228: '白朗县', + 540229: '仁布县', + 540230: '康马县', + 540231: '定结县', + 540232: '仲巴县', + 540233: '亚东县', + 540234: '吉隆县', + 540235: '聂拉木县', + 540236: '萨嘎县', + 540237: '岗巴县' + }, + 540300: { + 540302: '卡若区', + 540321: '江达县', + 540322: '贡觉县', + 540323: '类乌齐县', + 540324: '丁青县', + 540325: '察雅县', + 540326: '八宿县', + 540327: '左贡县', + 540328: '芒康县', + 540329: '洛隆县', + 540330: '边坝县' + }, + 542200: { + 542221: '乃东县', + 542222: '扎囊县', + 542223: '贡嘎县', + 542224: '桑日县', + 542225: '琼结县', + 542226: '曲松县', + 542227: '措美县', + 542228: '洛扎县', + 542229: '加查县', + 542231: '隆子县', + 542232: '错那县', + 542233: '浪卡子县' + }, + 542400: { + 542421: '那曲县', + 542422: '嘉黎县', + 542423: '比如县', + 542424: '聂荣县', + 542425: '安多县', + 542426: '申扎县', + 542427: '索县', + 542428: '班戈县', + 542429: '巴青县', + 542430: '尼玛县', + 542431: '双湖县' + }, + 542500: { + 542521: '普兰县', + 542522: '札达县', + 542523: '噶尔县', + 542524: '日土县', + 542525: '革吉县', + 542526: '改则县', + 542527: '措勤县' + }, + 542600: { + 542621: '巴宜区', + 542622: '工布江达县', + 542623: '米林县', + 542624: '墨脱县', + 542625: '波密县', + 542626: '察隅县', + 542627: '朗县' + }, + 610000: { + 610100: '西安市', + 610200: '铜川市', + 610300: '宝鸡市', + 610400: '咸阳市', + 610500: '渭南市', + 610600: '延安市', + 610700: '汉中市', + 610800: '榆林市', + 610900: '安康市', + 611000: '商洛市' + }, + 610100: { + 610102: '新城区', + 610103: '碑林区', + 610104: '莲湖区', + 610111: '灞桥区', + 610112: '未央区', + 610113: '雁塔区', + 610114: '阎良区', + 610115: '临潼区', + 610116: '长安区', + 610117: '高陵区', + 610122: '蓝田县', + 610124: '周至县', + 610125: '户县' + }, + 610200: { + 610202: '王益区', + 610203: '印台区', + 610204: '耀州区', + 610222: '宜君县' + }, + 610300: { + 610302: '渭滨区', + 610303: '金台区', + 610304: '陈仓区', + 610322: '凤翔县', + 610323: '岐山县', + 610324: '扶风县', + 610326: '眉县', + 610327: '陇县', + 610328: '千阳县', + 610329: '麟游县', + 610330: '凤县', + 610331: '太白县' + }, + 610400: { + 610402: '秦都区', + 610403: '杨陵区', + 610404: '渭城区', + 610422: '三原县', + 610423: '泾阳县', + 610424: '乾县', + 610425: '礼泉县', + 610426: '永寿县', + 610427: '彬县', + 610428: '长武县', + 610429: '旬邑县', + 610430: '淳化县', + 610431: '武功县', + 610481: '兴平市' + }, + 610500: { + 610502: '临渭区', + 610521: '华县', + 610522: '潼关县', + 610523: '大荔县', + 610524: '合阳县', + 610525: '澄城县', + 610526: '蒲城县', + 610527: '白水县', + 610528: '富平县', + 610581: '韩城市', + 610582: '华阴市' + }, + 610600: { + 610602: '宝塔区', + 610621: '延长县', + 610622: '延川县', + 610623: '子长县', + 610624: '安塞县', + 610625: '志丹县', + 610626: '吴起县', + 610627: '甘泉县', + 610628: '富县', + 610629: '洛川县', + 610630: '宜川县', + 610631: '黄龙县', + 610632: '黄陵县' + }, + 610700: { + 610702: '汉台区', + 610721: '南郑县', + 610722: '城固县', + 610723: '洋县', + 610724: '西乡县', + 610725: '勉县', + 610726: '宁强县', + 610727: '略阳县', + 610728: '镇巴县', + 610729: '留坝县', + 610730: '佛坪县' + }, + 610800: { + 610802: '榆阳区', + 610821: '神木县', + 610822: '府谷县', + 610823: '横山县', + 610824: '靖边县', + 610825: '定边县', + 610826: '绥德县', + 610827: '米脂县', + 610828: '佳县', + 610829: '吴堡县', + 610830: '清涧县', + 610831: '子洲县' + }, + 610900: { + 610902: '汉滨区', + 610921: '汉阴县', + 610922: '石泉县', + 610923: '宁陕县', + 610924: '紫阳县', + 610925: '岚皋县', + 610926: '平利县', + 610927: '镇坪县', + 610928: '旬阳县', + 610929: '白河县' + }, + 611000: { + 611002: '商州区', + 611021: '洛南县', + 611022: '丹凤县', + 611023: '商南县', + 611024: '山阳县', + 611025: '镇安县', + 611026: '柞水县' + }, + 620000: { + 620100: '兰州市', + 620200: '嘉峪关市', + 620300: '金昌市', + 620400: '白银市', + 620500: '天水市', + 620600: '武威市', + 620700: '张掖市', + 620800: '平凉市', + 620900: '酒泉市', + 621000: '庆阳市', + 621100: '定西市', + 621200: '陇南市', + 622900: '临夏回族自治州', + 623000: '甘南藏族自治州' + }, + 620100: { + 620102: '城关区', + 620103: '七里河区', + 620104: '西固区', + 620105: '安宁区', + 620111: '红古区', + 620121: '永登县', + 620122: '皋兰县', + 620123: '榆中县' + }, + 620300: { + 620302: '金川区', + 620321: '永昌县' + }, + 620400: { + 620402: '白银区', + 620403: '平川区', + 620421: '靖远县', + 620422: '会宁县', + 620423: '景泰县' + }, + 620500: { + 620502: '秦州区', + 620503: '麦积区', + 620521: '清水县', + 620522: '秦安县', + 620523: '甘谷县', + 620524: '武山县', + 620525: '张家川回族自治县' + }, + 620600: { + 620602: '凉州区', + 620621: '民勤县', + 620622: '古浪县', + 620623: '天祝藏族自治县' + }, + 620700: { + 620702: '甘州区', + 620721: '肃南裕固族自治县', + 620722: '民乐县', + 620723: '临泽县', + 620724: '高台县', + 620725: '山丹县' + }, + 620800: { + 620802: '崆峒区', + 620821: '泾川县', + 620822: '灵台县', + 620823: '崇信县', + 620824: '华亭县', + 620825: '庄浪县', + 620826: '静宁县' + }, + 620900: { + 620902: '肃州区', + 620921: '金塔县', + 620922: '瓜州县', + 620923: '肃北蒙古族自治县', + 620924: '阿克塞哈萨克族自治县', + 620981: '玉门市', + 620982: '敦煌市' + }, + 621000: { + 621002: '西峰区', + 621021: '庆城县', + 621022: '环县', + 621023: '华池县', + 621024: '合水县', + 621025: '正宁县', + 621026: '宁县', + 621027: '镇原县' + }, + 621100: { + 621102: '安定区', + 621121: '通渭县', + 621122: '陇西县', + 621123: '渭源县', + 621124: '临洮县', + 621125: '漳县', + 621126: '岷县' + }, + 621200: { + 621202: '武都区', + 621221: '成县', + 621222: '文县', + 621223: '宕昌县', + 621224: '康县', + 621225: '西和县', + 621226: '礼县', + 621227: '徽县', + 621228: '两当县' + }, + 622900: { + 622901: '临夏市', + 622921: '临夏县', + 622922: '康乐县', + 622923: '永靖县', + 622924: '广河县', + 622925: '和政县', + 622926: '东乡族自治县', + 622927: '积石山保安族东乡族撒拉族自治县' + }, + 623000: { + 623001: '合作市', + 623021: '临潭县', + 623022: '卓尼县', + 623023: '舟曲县', + 623024: '迭部县', + 623025: '玛曲县', + 623026: '碌曲县', + 623027: '夏河县' + }, + 630000: { + 630100: '西宁市', + 630200: '海东市', + 632200: '海北藏族自治州', + 632300: '黄南藏族自治州', + 632500: '海南藏族自治州', + 632600: '果洛藏族自治州', + 632700: '玉树藏族自治州', + 632800: '海西蒙古族藏族自治州' + }, + 630100: { + 630102: '城东区', + 630103: '城中区', + 630104: '城西区', + 630105: '城北区', + 630121: '大通回族土族自治县', + 630122: '湟中县', + 630123: '湟源县' + }, + 630200: { + 630202: '乐都区', + 630203: '平安区', + 630222: '民和回族土族自治县', + 630223: '互助土族自治县', + 630224: '化隆回族自治县', + 630225: '循化撒拉族自治县' + }, + 632200: { + 632221: '门源回族自治县', + 632222: '祁连县', + 632223: '海晏县', + 632224: '刚察县' + }, + 632300: { + 632321: '同仁县', + 632322: '尖扎县', + 632323: '泽库县', + 632324: '河南蒙古族自治县' + }, + 632500: { + 632521: '共和县', + 632522: '同德县', + 632523: '贵德县', + 632524: '兴海县', + 632525: '贵南县' + }, + 632600: { + 632621: '玛沁县', + 632622: '班玛县', + 632623: '甘德县', + 632624: '达日县', + 632625: '久治县', + 632626: '玛多县' + }, + 632700: { + 632701: '玉树市', + 632722: '杂多县', + 632723: '称多县', + 632724: '治多县', + 632725: '囊谦县', + 632726: '曲麻莱县' + }, + 632800: { + 632801: '格尔木市', + 632802: '德令哈市', + 632821: '乌兰县', + 632822: '都兰县', + 632823: '天峻县', + 632825: '海西蒙古族藏族自治州直辖' + }, + 640000: { + 640100: '银川市', + 640200: '石嘴山市', + 640300: '吴忠市', + 640400: '固原市', + 640500: '中卫市' + }, + 640100: { + 640104: '兴庆区', + 640105: '西夏区', + 640106: '金凤区', + 640121: '永宁县', + 640122: '贺兰县', + 640181: '灵武市' + }, + 640200: { + 640202: '大武口区', + 640205: '惠农区', + 640221: '平罗县' + }, + 640300: { + 640302: '利通区', + 640303: '红寺堡区', + 640323: '盐池县', + 640324: '同心县', + 640381: '青铜峡市' + }, + 640400: { + 640402: '原州区', + 640422: '西吉县', + 640423: '隆德县', + 640424: '泾源县', + 640425: '彭阳县' + }, + 640500: { + 640502: '沙坡头区', + 640521: '中宁县', + 640522: '海原县' + }, + 650000: { + 650100: '乌鲁木齐市', + 650200: '克拉玛依市', + 652100: '吐鲁番市', + 652200: '哈密地区', + 652300: '昌吉回族自治州', + 652700: '博尔塔拉蒙古自治州', + 652800: '巴音郭楞蒙古自治州', + 652900: '阿克苏地区', + 653000: '克孜勒苏柯尔克孜自治州', + 653100: '喀什地区', + 653200: '和田地区', + 654000: '伊犁哈萨克自治州', + 654200: '塔城地区', + 654300: '阿勒泰地区', + 659001: '石河子市', + 659002: '阿拉尔市', + 659003: '图木舒克市', + 659004: '五家渠市', + 659005: '北屯市', + 659006: '铁门关市', + 659007: '双河市', + 659008: '可克达拉市' + }, + 650100: { + 650102: '天山区', + 650103: '沙依巴克区', + 650104: '新市区', + 650105: '水磨沟区', + 650106: '头屯河区', + 650107: '达坂城区', + 650109: '米东区', + 650121: '乌鲁木齐县' + }, + 650200: { + 650202: '独山子区', + 650203: '克拉玛依区', + 650204: '白碱滩区', + 650205: '乌尔禾区' + }, + 652100: { + 652101: '高昌区', + 652122: '鄯善县', + 652123: '托克逊县' + }, + 652200: { + 652201: '哈密市', + 652222: '巴里坤哈萨克自治县', + 652223: '伊吾县' + }, + 652300: { + 652301: '昌吉市', + 652302: '阜康市', + 652323: '呼图壁县', + 652324: '玛纳斯县', + 652325: '奇台县', + 652327: '吉木萨尔县', + 652328: '木垒哈萨克自治县' + }, + 652700: { + 652701: '博乐市', + 652702: '阿拉山口市', + 652722: '精河县', + 652723: '温泉县' + }, + 652800: { + 652801: '库尔勒市', + 652822: '轮台县', + 652823: '尉犁县', + 652824: '若羌县', + 652825: '且末县', + 652826: '焉耆回族自治县', + 652827: '和静县', + 652828: '和硕县', + 652829: '博湖县' + }, + 652900: { + 652901: '阿克苏市', + 652922: '温宿县', + 652923: '库车县', + 652924: '沙雅县', + 652925: '新和县', + 652926: '拜城县', + 652927: '乌什县', + 652928: '阿瓦提县', + 652929: '柯坪县' + }, + 653000: { + 653001: '阿图什市', + 653022: '阿克陶县', + 653023: '阿合奇县', + 653024: '乌恰县' + }, + 653100: { + 653101: '喀什市', + 653121: '疏附县', + 653122: '疏勒县', + 653123: '英吉沙县', + 653124: '泽普县', + 653125: '莎车县', + 653126: '叶城县', + 653127: '麦盖提县', + 653128: '岳普湖县', + 653129: '伽师县', + 653130: '巴楚县', + 653131: '塔什库尔干塔吉克自治县' + }, + 653200: { + 653201: '和田市', + 653221: '和田县', + 653222: '墨玉县', + 653223: '皮山县', + 653224: '洛浦县', + 653225: '策勒县', + 653226: '于田县', + 653227: '民丰县' + }, + 654000: { + 654002: '伊宁市', + 654003: '奎屯市', + 654004: '霍尔果斯市', + 654021: '伊宁县', + 654022: '察布查尔锡伯自治县', + 654023: '霍城县', + 654024: '巩留县', + 654025: '新源县', + 654026: '昭苏县', + 654027: '特克斯县', + 654028: '尼勒克县' + }, + 654200: { + 654201: '塔城市', + 654202: '乌苏市', + 654221: '额敏县', + 654223: '沙湾县', + 654224: '托里县', + 654225: '裕民县', + 654226: '和布克赛尔蒙古自治县' + }, + 654300: { + 654301: '阿勒泰市', + 654321: '布尔津县', + 654322: '富蕴县', + 654323: '福海县', + 654324: '哈巴河县', + 654325: '青河县', + 654326: '吉木乃县' + }, + 810000: { + 810001: '中西區', + 810002: '灣仔區', + 810003: '東區', + 810004: '南區', + 810005: '油尖旺區', + 810006: '深水埗區', + 810007: '九龍城區', + 810008: '黃大仙區', + 810009: '觀塘區', + 810010: '荃灣區', + 810011: '屯門區', + 810012: '元朗區', + 810013: '北區', + 810014: '大埔區', + 810015: '西貢區', + 810016: '沙田區', + 810017: '葵青區', + 810018: '離島區' + }, + 820000: { + 820001: '花地瑪堂區', + 820002: '花王堂區', + 820003: '望德堂區', + 820004: '大堂區', + 820005: '風順堂區', + 820006: '嘉模堂區', + 820007: '路氹填海區', + 820008: '聖方濟各堂區' + } + } + ; + + if (typeof window !== 'undefined') { + window.ChineseDistricts = ChineseDistricts; + } + + return ChineseDistricts; + +} diff --git a/public/static/js/clipboard.min.js b/public/static/js/clipboard.min.js new file mode 100644 index 0000000..95f55d7 --- /dev/null +++ b/public/static/js/clipboard.min.js @@ -0,0 +1,7 @@ +/*! + * clipboard.js v2.0.8 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return n={134:function(t,e,n){"use strict";n.d(e,{default:function(){return r}});var e=n(279),i=n.n(e),e=n(370),a=n.n(e),e=n(817),o=n.n(e);function c(t){return(c="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function u(t,e){for(var n=0;n
  • ", + hourTag: "
  • :
  • ", + minTag: "
  • :
  • ", + secTag: "
  • :
  • ", + dayClass: ".countdownday", + hourClass: ".countdownhour", + minClass: ".countdownmin", + secClass: ".countdownsec", + isDefault: false, + showTemp:0 + }; + var _temp = $.extend(_TempTag, tagTemp); + var TIMER; + createdom = function (dom) { + _DOM = dom; + data = Math.round($(dom).attr("data")); + var htmlstr = (_temp.showTemp == 0 ? _temp.dayTag : "") + _temp.hourTag + _temp.minTag + _temp.secTag; + if (_temp.isDefault) { + htmlstr = (_temp.showTemp == 0 ? _defaultTag.dayTag : "") + _defaultTag.hourTag + _defaultTag.minTag + _defaultTag.secTag; + htmlstr = "
      " + htmlstr + "
    "; + $("head").append(""); + } + $(_DOM).html(htmlstr); + reflash(); + }; + reflash = function () { + var range = data, + secday = 86400, sechour = 3600, + days = parseInt(range / secday), + hours = _temp.showTemp == 0 ? parseInt((range % secday) / sechour) : parseInt(range / sechour), + min = parseInt(((range % secday) % sechour) / 60), + sec = ((range % secday) % sechour) % 60; + data--; + if (range < 0) { + window.clearInterval(TIMER); //清楚定时器 + } else { + $(_DOM).find(_temp.dayClass).html(nol(days)); + $(_DOM).find(_temp.hourClass).html(nol(hours)); + $(_DOM).find(_temp.minClass).html(nol(min)); + $(_DOM).find(_temp.secClass).html(nol(sec)); + } + if ((range - 1) == 0) { + undefined == backfun ? function () { } : backfun(); + } + }; + TIMER = setInterval(reflash, 1000); + nol = function (h) { + return h > 9 ? h : '0' + h; + }; + return this.each(function () { + var $box = $(this); + createdom($box); + }); + }; + +})(jQuery); diff --git a/public/static/js/index.js b/public/static/js/index.js new file mode 100644 index 0000000..f7c63a1 --- /dev/null +++ b/public/static/js/index.js @@ -0,0 +1,13 @@ +$(function(){ + $('html').css('font-size',$(document).width() >= 750 ? '100px' : $(document).width() / 750 * 100 + 'px'); + var index = 1; + function toRun(){ + if (index == 2) index = 0; + $('#ky .steps3-container ul').animate({ + left:-index * $('#ky .steps3-container').width() + 'px' + }) + index +=1; + $("body").css("opacity",1); + } + setInterval(toRun,100) +}) \ No newline at end of file diff --git a/public/static/js/jquery-3.3.1.js b/public/static/js/jquery-3.3.1.js new file mode 100644 index 0000000..00c44f0 --- /dev/null +++ b/public/static/js/jquery-3.3.1.js @@ -0,0 +1,10447 @@ +/*! + * jQuery JavaScript Library v3.3.1 + * https://jquery.com/ + * + * Includes Sizzle.js + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://jquery.org/license + * + * Date: 2018-01-20T17:24Z + */ +(function (global, factory) { + + "use strict"; + + if (typeof module === "object" && typeof module.exports === "object") { + + // For CommonJS and CommonJS-like environments where a proper `window` + // is present, execute the factory and get jQuery. + // For environments that do not have a `window` with a `document` + // (such as Node.js), expose a factory as module.exports. + // This accentuates the need for the creation of a real `window`. + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info. + module.exports = global.document ? + factory(global, true) : + function (w) { + if (!w.document) { + throw new Error("jQuery requires a window with a document"); + } + return factory(w); + }; + } else { + factory(global); + } + + // Pass this if window is not defined yet +})(typeof window !== "undefined" ? window : this, function (window, noGlobal) { + + // Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 + // throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode + // arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common + // enough that all such attempts are guarded in a try block. + "use strict"; + + var arr = []; + + var document = window.document; + + var getProto = Object.getPrototypeOf; + + var slice = arr.slice; + + var concat = arr.concat; + + var push = arr.push; + + var indexOf = arr.indexOf; + + var class2type = {}; + + var toString = class2type.toString; + + var hasOwn = class2type.hasOwnProperty; + + var fnToString = hasOwn.toString; + + var ObjectFunctionString = fnToString.call(Object); + + var support = {}; + + var isFunction = function isFunction(obj) { + + // Support: Chrome <=57, Firefox <=52 + // In some browsers, typeof returns "function" for HTML elements + // (i.e., `typeof document.createElement( "object" ) === "function"`). + // We don't want to classify *any* DOM node as a function. + return typeof obj === "function" && typeof obj.nodeType !== "number"; + }; + + + var isWindow = function isWindow(obj) { + return obj != null && obj === obj.window; + }; + + + + + var preservedScriptAttributes = { + type: true, + src: true, + noModule: true + }; + + function DOMEval(code, doc, node) { + doc = doc || document; + + var i, + script = doc.createElement("script"); + + script.text = code; + if (node) { + for (i in preservedScriptAttributes) { + if (node[i]) { + script[i] = node[i]; + } + } + } + doc.head.appendChild(script).parentNode.removeChild(script); + } + + + function toType(obj) { + if (obj == null) { + return obj + ""; + } + + // Support: Android <=2.3 only (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[toString.call(obj)] || "object" : + typeof obj; + } + /* global Symbol */ + // Defining this global in .eslintrc.json would create a danger of using the global + // unguarded in another place, it seems safer to define global only for this module + + + + var + version = "3.3.1", + + // Define a local copy of jQuery + jQuery = function (selector, context) { + + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init(selector, context); + }, + + // Support: Android <=4.0 only + // Make sure we trim BOM and NBSP + rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g; + + jQuery.fn = jQuery.prototype = { + + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function () { + return slice.call(this); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function (num) { + + // Return all the elements in a clean array + if (num == null) { + return slice.call(this); + } + + // Return just the one element from the set + return num < 0 ? this[num + this.length] : this[num]; + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function (elems) { + + // Build a new jQuery matched element set + var ret = jQuery.merge(this.constructor(), elems); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + each: function (callback) { + return jQuery.each(this, callback); + }, + + map: function (callback) { + return this.pushStack(jQuery.map(this, function (elem, i) { + return callback.call(elem, i, elem); + })); + }, + + slice: function () { + return this.pushStack(slice.apply(this, arguments)); + }, + + first: function () { + return this.eq(0); + }, + + last: function () { + return this.eq(-1); + }, + + eq: function (i) { + var len = this.length, + j = +i + (i < 0 ? len : 0); + return this.pushStack(j >= 0 && j < len ? [this[j]] : []); + }, + + end: function () { + return this.prevObject || this.constructor(); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: arr.sort, + splice: arr.splice + }; + + jQuery.extend = jQuery.fn.extend = function () { + var options, name, src, copy, copyIsArray, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if (typeof target === "boolean") { + deep = target; + + // Skip the boolean and the target + target = arguments[i] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if (typeof target !== "object" && !isFunction(target)) { + target = {}; + } + + // Extend jQuery itself if only one argument is passed + if (i === length) { + target = this; + i--; + } + + for (; i < length; i++) { + + // Only deal with non-null/undefined values + if ((options = arguments[i]) != null) { + + // Extend the base object + for (name in options) { + src = target[name]; + copy = options[name]; + + // Prevent never-ending loop + if (target === copy) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if (deep && copy && (jQuery.isPlainObject(copy) || + (copyIsArray = Array.isArray(copy)))) { + + if (copyIsArray) { + copyIsArray = false; + clone = src && Array.isArray(src) ? src : []; + + } else { + clone = src && jQuery.isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[name] = jQuery.extend(deep, clone, copy); + + // Don't bring in undefined values + } else if (copy !== undefined) { + target[name] = copy; + } + } + } + } + + // Return the modified object + return target; + }; + + jQuery.extend({ + + // Unique for each copy of jQuery on the page + expando: "jQuery" + (version + Math.random()).replace(/\D/g, ""), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function (msg) { + throw new Error(msg); + }, + + noop: function () {}, + + isPlainObject: function (obj) { + var proto, Ctor; + + // Detect obvious negatives + // Use toString instead of jQuery.type to catch host objects + if (!obj || toString.call(obj) !== "[object Object]") { + return false; + } + + proto = getProto(obj); + + // Objects with no prototype (e.g., `Object.create( null )`) are plain + if (!proto) { + return true; + } + + // Objects with prototype are plain iff they were constructed by a global Object function + Ctor = hasOwn.call(proto, "constructor") && proto.constructor; + return typeof Ctor === "function" && fnToString.call(Ctor) === ObjectFunctionString; + }, + + isEmptyObject: function (obj) { + + /* eslint-disable no-unused-vars */ + // See https://github.com/eslint/eslint/issues/6125 + var name; + + for (name in obj) { + return false; + } + return true; + }, + + // Evaluates a script in a global context + globalEval: function (code) { + DOMEval(code); + }, + + each: function (obj, callback) { + var length, i = 0; + + if (isArrayLike(obj)) { + length = obj.length; + for (; i < length; i++) { + if (callback.call(obj[i], i, obj[i]) === false) { + break; + } + } + } else { + for (i in obj) { + if (callback.call(obj[i], i, obj[i]) === false) { + break; + } + } + } + + return obj; + }, + + // Support: Android <=4.0 only + trim: function (text) { + return text == null ? + "" : + (text + "").replace(rtrim, ""); + }, + + // results is for internal usage only + makeArray: function (arr, results) { + var ret = results || []; + + if (arr != null) { + if (isArrayLike(Object(arr))) { + jQuery.merge(ret, + typeof arr === "string" ? [arr] : arr + ); + } else { + push.call(ret, arr); + } + } + + return ret; + }, + + inArray: function (elem, arr, i) { + return arr == null ? -1 : indexOf.call(arr, elem, i); + }, + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + merge: function (first, second) { + var len = +second.length, + j = 0, + i = first.length; + + for (; j < len; j++) { + first[i++] = second[j]; + } + + first.length = i; + + return first; + }, + + grep: function (elems, callback, invert) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for (; i < length; i++) { + callbackInverse = !callback(elems[i], i); + if (callbackInverse !== callbackExpect) { + matches.push(elems[i]); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function (elems, callback, arg) { + var length, value, + i = 0, + ret = []; + + // Go through the array, translating each of the items to their new values + if (isArrayLike(elems)) { + length = elems.length; + for (; i < length; i++) { + value = callback(elems[i], i, arg); + + if (value != null) { + ret.push(value); + } + } + + // Go through every key on the object, + } else { + for (i in elems) { + value = callback(elems[i], i, arg); + + if (value != null) { + ret.push(value); + } + } + } + + // Flatten any nested arrays + return concat.apply([], ret); + }, + + // A global GUID counter for objects + guid: 1, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support + }); + + if (typeof Symbol === "function") { + jQuery.fn[Symbol.iterator] = arr[Symbol.iterator]; + } + + // Populate the class2type map + jQuery.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "), + function (i, name) { + class2type["[object " + name + "]"] = name.toLowerCase(); + }); + + function isArrayLike(obj) { + + // Support: real iOS 8.2 only (not reproducible in simulator) + // `in` check used to prevent JIT error (gh-2145) + // hasOwn isn't used here due to false negatives + // regarding Nodelist length in IE + var length = !!obj && "length" in obj && obj.length, + type = toType(obj); + + if (isFunction(obj) || isWindow(obj)) { + return false; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && (length - 1) in obj; + } + var Sizzle = + /*! + * Sizzle CSS Selector Engine v2.3.3 + * https://sizzlejs.com/ + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2016-08-08 + */ + (function (window) { + + var i, + support, + Expr, + getText, + isXML, + tokenize, + compile, + select, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + 1 * new Date(), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + sortOrder = function (a, b) { + if (a === b) { + hasDuplicate = true; + } + return 0; + }, + + // Instance methods + hasOwn = ({}).hasOwnProperty, + arr = [], + pop = arr.pop, + push_native = arr.push, + push = arr.push, + slice = arr.slice, + // Use a stripped-down indexOf as it's faster than native + // https://jsperf.com/thor-indexof-vs-for/5 + indexOf = function (list, elem) { + var i = 0, + len = list.length; + for (; i < len; i++) { + if (list[i] === elem) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + + // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + identifier = "(?:\\\\.|[\\w-]|[^\0-\\xa0])+", + + // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + + "*\\]", + + pseudos = ":(" + identifier + ")(?:\\((" + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rwhitespace = new RegExp(whitespace + "+", "g"), + rtrim = new RegExp("^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g"), + + rcomma = new RegExp("^" + whitespace + "*," + whitespace + "*"), + rcombinators = new RegExp("^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*"), + + rattributeQuotes = new RegExp("=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g"), + + rpseudo = new RegExp(pseudos), + ridentifier = new RegExp("^" + identifier + "$"), + + matchExpr = { + "ID": new RegExp("^#(" + identifier + ")"), + "CLASS": new RegExp("^\\.(" + identifier + ")"), + "TAG": new RegExp("^(" + identifier + "|[*])"), + "ATTR": new RegExp("^" + attributes), + "PSEUDO": new RegExp("^" + pseudos), + "CHILD": new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + + "*(\\d+)|))" + whitespace + "*\\)|)", "i"), + "bool": new RegExp("^(?:" + booleans + ")$", "i"), + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp("^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i") + }, + + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + + // CSS escapes + // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp("\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig"), + funescape = function (_, escaped, escapedWhitespace) { + var high = "0x" + escaped - 0x10000; + // NaN means non-codepoint + // Support: Firefox<24 + // Workaround erroneous numeric interpretation of +"0x" + return high !== high || escapedWhitespace ? + escaped : + high < 0 ? + // BMP codepoint + String.fromCharCode(high + 0x10000) : + // Supplemental Plane codepoint (surrogate pair) + String.fromCharCode(high >> 10 | 0xD800, high & 0x3FF | 0xDC00); + }, + + // CSS string/identifier serialization + // https://drafts.csswg.org/cssom/#common-serializing-idioms + rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, + fcssescape = function (ch, asCodePoint) { + if (asCodePoint) { + + // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER + if (ch === "\0") { + return "\uFFFD"; + } + + // Control characters and (dependent upon position) numbers get escaped as code points + return ch.slice(0, -1) + "\\" + ch.charCodeAt(ch.length - 1).toString(16) + " "; + } + + // Other potentially-special ASCII characters get backslash-escaped + return "\\" + ch; + }, + + // Used for iframes + // See setDocument() + // Removing the function wrapper causes a "Permission Denied" + // error in IE + unloadHandler = function () { + setDocument(); + }, + + disabledAncestor = addCombinator( + function (elem) { + return elem.disabled === true && ("form" in elem || "label" in elem); + }, { + dir: "parentNode", + next: "legend" + } + ); + + // Optimize for push.apply( _, NodeList ) + try { + push.apply( + (arr = slice.call(preferredDoc.childNodes)), + preferredDoc.childNodes + ); + // Support: Android<4.0 + // Detect silently failing push.apply + arr[preferredDoc.childNodes.length].nodeType; + } catch (e) { + push = { + apply: arr.length ? + + // Leverage slice if possible + function (target, els) { + push_native.apply(target, slice.call(els)); + } : + + // Support: IE<9 + // Otherwise append directly + function (target, els) { + var j = target.length, + i = 0; + // Can't trust NodeList.length + while ((target[j++] = els[i++])) {} + target.length = j - 1; + } + }; + } + + function Sizzle(selector, context, results, seed) { + var m, i, elem, nid, match, groups, newSelector, + newContext = context && context.ownerDocument, + + // nodeType defaults to 9, since context defaults to document + nodeType = context ? context.nodeType : 9; + + results = results || []; + + // Return early from calls with invalid selector or context + if (typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11) { + + return results; + } + + // Try to shortcut find operations (as opposed to filters) in HTML documents + if (!seed) { + + if ((context ? context.ownerDocument || context : preferredDoc) !== document) { + setDocument(context); + } + context = context || document; + + if (documentIsHTML) { + + // If the selector is sufficiently simple, try using a "get*By*" DOM method + // (excepting DocumentFragment context, where the methods don't exist) + if (nodeType !== 11 && (match = rquickExpr.exec(selector))) { + + // ID selector + if ((m = match[1])) { + + // Document context + if (nodeType === 9) { + if ((elem = context.getElementById(m))) { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if (elem.id === m) { + results.push(elem); + return results; + } + } else { + return results; + } + + // Element context + } else { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if (newContext && (elem = newContext.getElementById(m)) && + contains(context, elem) && + elem.id === m) { + + results.push(elem); + return results; + } + } + + // Type selector + } else if (match[2]) { + push.apply(results, context.getElementsByTagName(selector)); + return results; + + // Class selector + } else if ((m = match[3]) && support.getElementsByClassName && + context.getElementsByClassName) { + + push.apply(results, context.getElementsByClassName(m)); + return results; + } + } + + // Take advantage of querySelectorAll + if (support.qsa && + !compilerCache[selector + " "] && + (!rbuggyQSA || !rbuggyQSA.test(selector))) { + + if (nodeType !== 1) { + newContext = context; + newSelector = selector; + + // qSA looks outside Element context, which is not what we want + // Thanks to Andrew Dupont for this workaround technique + // Support: IE <=8 + // Exclude object elements + } else if (context.nodeName.toLowerCase() !== "object") { + + // Capture the context ID, setting it first if necessary + if ((nid = context.getAttribute("id"))) { + nid = nid.replace(rcssescape, fcssescape); + } else { + context.setAttribute("id", (nid = expando)); + } + + // Prefix every selector in the list + groups = tokenize(selector); + i = groups.length; + while (i--) { + groups[i] = "#" + nid + " " + toSelector(groups[i]); + } + newSelector = groups.join(","); + + // Expand context for sibling selectors + newContext = rsibling.test(selector) && testContext(context.parentNode) || + context; + } + + if (newSelector) { + try { + push.apply(results, + newContext.querySelectorAll(newSelector) + ); + return results; + } catch (qsaError) {} finally { + if (nid === expando) { + context.removeAttribute("id"); + } + } + } + } + } + } + + // All others + return select(selector.replace(rtrim, "$1"), context, results, seed); + } + + /** + * Create key-value caches of limited size + * @returns {function(string, object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ + function createCache() { + var keys = []; + + function cache(key, value) { + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if (keys.push(key + " ") > Expr.cacheLength) { + // Only keep the most recent entries + delete cache[keys.shift()]; + } + return (cache[key + " "] = value); + } + return cache; + } + + /** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ + function markFunction(fn) { + fn[expando] = true; + return fn; + } + + /** + * Support testing using an element + * @param {Function} fn Passed the created element and returns a boolean result + */ + function assert(fn) { + var el = document.createElement("fieldset"); + + try { + return !!fn(el); + } catch (e) { + return false; + } finally { + // Remove from its parent by default + if (el.parentNode) { + el.parentNode.removeChild(el); + } + // release memory in IE + el = null; + } + } + + /** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ + function addHandle(attrs, handler) { + var arr = attrs.split("|"), + i = arr.length; + + while (i--) { + Expr.attrHandle[arr[i]] = handler; + } + } + + /** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ + function siblingCheck(a, b) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + a.sourceIndex - b.sourceIndex; + + // Use IE sourceIndex if available on both nodes + if (diff) { + return diff; + } + + // Check if b follows a + if (cur) { + while ((cur = cur.nextSibling)) { + if (cur === b) { + return -1; + } + } + } + + return a ? 1 : -1; + } + + /** + * Returns a function to use in pseudos for input types + * @param {String} type + */ + function createInputPseudo(type) { + return function (elem) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; + } + + /** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ + function createButtonPseudo(type) { + return function (elem) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && elem.type === type; + }; + } + + /** + * Returns a function to use in pseudos for :enabled/:disabled + * @param {Boolean} disabled true for :disabled; false for :enabled + */ + function createDisabledPseudo(disabled) { + + // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable + return function (elem) { + + // Only certain elements can match :enabled or :disabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled + if ("form" in elem) { + + // Check for inherited disabledness on relevant non-disabled elements: + // * listed form-associated elements in a disabled fieldset + // https://html.spec.whatwg.org/multipage/forms.html#category-listed + // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled + // * option elements in a disabled optgroup + // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled + // All such elements have a "form" property. + if (elem.parentNode && elem.disabled === false) { + + // Option elements defer to a parent optgroup if present + if ("label" in elem) { + if ("label" in elem.parentNode) { + return elem.parentNode.disabled === disabled; + } else { + return elem.disabled === disabled; + } + } + + // Support: IE 6 - 11 + // Use the isDisabled shortcut property to check for disabled fieldset ancestors + return elem.isDisabled === disabled || + + // Where there is no isDisabled, check manually + /* jshint -W018 */ + elem.isDisabled !== !disabled && + disabledAncestor(elem) === disabled; + } + + return elem.disabled === disabled; + + // Try to winnow out elements that can't be disabled before trusting the disabled property. + // Some victims get caught in our net (label, legend, menu, track), but it shouldn't + // even exist on them, let alone have a boolean value. + } else if ("label" in elem) { + return elem.disabled === disabled; + } + + // Remaining elements are neither :enabled nor :disabled + return false; + }; + } + + /** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ + function createPositionalPseudo(fn) { + return markFunction(function (argument) { + argument = +argument; + return markFunction(function (seed, matches) { + var j, + matchIndexes = fn([], seed.length, argument), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while (i--) { + if (seed[(j = matchIndexes[i])]) { + seed[j] = !(matches[j] = seed[j]); + } + } + }); + }); + } + + /** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ + function testContext(context) { + return context && typeof context.getElementsByTagName !== "undefined" && context; + } + + // Expose support vars for convenience + support = Sizzle.support = {}; + + /** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node + */ + isXML = Sizzle.isXML = function (elem) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = elem && (elem.ownerDocument || elem).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; + }; + + /** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ + setDocument = Sizzle.setDocument = function (node) { + var hasCompare, subWindow, + doc = node ? node.ownerDocument || node : preferredDoc; + + // Return early if doc is invalid or already selected + if (doc === document || doc.nodeType !== 9 || !doc.documentElement) { + return document; + } + + // Update global variables + document = doc; + docElem = document.documentElement; + documentIsHTML = !isXML(document); + + // Support: IE 9-11, Edge + // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) + if (preferredDoc !== document && + (subWindow = document.defaultView) && subWindow.top !== subWindow) { + + // Support: IE 11, Edge + if (subWindow.addEventListener) { + subWindow.addEventListener("unload", unloadHandler, false); + + // Support: IE 9 - 10 only + } else if (subWindow.attachEvent) { + subWindow.attachEvent("onunload", unloadHandler); + } + } + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties + // (excepting IE8 booleans) + support.attributes = assert(function (el) { + el.className = "i"; + return !el.getAttribute("className"); + }); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert(function (el) { + el.appendChild(document.createComment("")); + return !el.getElementsByTagName("*").length; + }); + + // Support: IE<9 + support.getElementsByClassName = rnative.test(document.getElementsByClassName); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programmatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert(function (el) { + docElem.appendChild(el).id = expando; + return !document.getElementsByName || !document.getElementsByName(expando).length; + }); + + // ID filter and find + if (support.getById) { + Expr.filter["ID"] = function (id) { + var attrId = id.replace(runescape, funescape); + return function (elem) { + return elem.getAttribute("id") === attrId; + }; + }; + Expr.find["ID"] = function (id, context) { + if (typeof context.getElementById !== "undefined" && documentIsHTML) { + var elem = context.getElementById(id); + return elem ? [elem] : []; + } + }; + } else { + Expr.filter["ID"] = function (id) { + var attrId = id.replace(runescape, funescape); + return function (elem) { + var node = typeof elem.getAttributeNode !== "undefined" && + elem.getAttributeNode("id"); + return node && node.value === attrId; + }; + }; + + // Support: IE 6 - 7 only + // getElementById is not reliable as a find shortcut + Expr.find["ID"] = function (id, context) { + if (typeof context.getElementById !== "undefined" && documentIsHTML) { + var node, i, elems, + elem = context.getElementById(id); + + if (elem) { + + // Verify the id attribute + node = elem.getAttributeNode("id"); + if (node && node.value === id) { + return [elem]; + } + + // Fall back on getElementsByName + elems = context.getElementsByName(id); + i = 0; + while ((elem = elems[i++])) { + node = elem.getAttributeNode("id"); + if (node && node.value === id) { + return [elem]; + } + } + } + + return []; + } + }; + } + + // Tag + Expr.find["TAG"] = support.getElementsByTagName ? + function (tag, context) { + if (typeof context.getElementsByTagName !== "undefined") { + return context.getElementsByTagName(tag); + + // DocumentFragment nodes don't have gEBTN + } else if (support.qsa) { + return context.querySelectorAll(tag); + } + } : + + function (tag, context) { + var elem, + tmp = [], + i = 0, + // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too + results = context.getElementsByTagName(tag); + + // Filter out possible comments + if (tag === "*") { + while ((elem = results[i++])) { + if (elem.nodeType === 1) { + tmp.push(elem); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find["CLASS"] = support.getElementsByClassName && function (className, context) { + if (typeof context.getElementsByClassName !== "undefined" && documentIsHTML) { + return context.getElementsByClassName(className); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See https://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ((support.qsa = rnative.test(document.querySelectorAll))) { + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert(function (el) { + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // https://bugs.jquery.com/ticket/12359 + docElem.appendChild(el).innerHTML = "" + + ""; + + // Support: IE8, Opera 11-12.16 + // Nothing should be selected when empty strings follow ^= or $= or *= + // The test attribute must be unknown in Opera but "safe" for WinRT + // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section + if (el.querySelectorAll("[msallowcapture^='']").length) { + rbuggyQSA.push("[*^$]=" + whitespace + "*(?:''|\"\")"); + } + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if (!el.querySelectorAll("[selected]").length) { + rbuggyQSA.push("\\[" + whitespace + "*(?:value|" + booleans + ")"); + } + + // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ + if (!el.querySelectorAll("[id~=" + expando + "-]").length) { + rbuggyQSA.push("~="); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if (!el.querySelectorAll(":checked").length) { + rbuggyQSA.push(":checked"); + } + + // Support: Safari 8+, iOS 8+ + // https://bugs.webkit.org/show_bug.cgi?id=136851 + // In-page `selector#id sibling-combinator selector` fails + if (!el.querySelectorAll("a#" + expando + "+*").length) { + rbuggyQSA.push(".#.+[+~]"); + } + }); + + assert(function (el) { + el.innerHTML = "" + + ""; + + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + var input = document.createElement("input"); + input.setAttribute("type", "hidden"); + el.appendChild(input).setAttribute("name", "D"); + + // Support: IE8 + // Enforce case-sensitivity of name attribute + if (el.querySelectorAll("[name=d]").length) { + rbuggyQSA.push("name" + whitespace + "*[*^$|!~]?="); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if (el.querySelectorAll(":enabled").length !== 2) { + rbuggyQSA.push(":enabled", ":disabled"); + } + + // Support: IE9-11+ + // IE's :disabled selector does not pick up the children of disabled fieldsets + docElem.appendChild(el).disabled = true; + if (el.querySelectorAll(":disabled").length !== 2) { + rbuggyQSA.push(":enabled", ":disabled"); + } + + // Opera 10-11 does not throw on post-comma invalid pseudos + el.querySelectorAll("*,:x"); + rbuggyQSA.push(",.*:"); + }); + } + + if ((support.matchesSelector = rnative.test((matches = docElem.matches || + docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector)))) { + + assert(function (el) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call(el, "*"); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call(el, "[s!='']:x"); + rbuggyMatches.push("!=", pseudos); + }); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp(rbuggyQSA.join("|")); + rbuggyMatches = rbuggyMatches.length && new RegExp(rbuggyMatches.join("|")); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test(docElem.compareDocumentPosition); + + // Element contains another + // Purposefully self-exclusive + // As in, an element does not contain itself + contains = hasCompare || rnative.test(docElem.contains) ? + function (a, b) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!(bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains(bup) : + a.compareDocumentPosition && a.compareDocumentPosition(bup) & 16 + )); + } : + function (a, b) { + if (b) { + while ((b = b.parentNode)) { + if (b === a) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = hasCompare ? + function (a, b) { + + // Flag for duplicate removal + if (a === b) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if (compare) { + return compare; + } + + // Calculate position if both inputs belong to the same document + compare = (a.ownerDocument || a) === (b.ownerDocument || b) ? + a.compareDocumentPosition(b) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if (compare & 1 || + (!support.sortDetached && b.compareDocumentPosition(a) === compare)) { + + // Choose the first element that is related to our preferred document + if (a === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a)) { + return -1; + } + if (b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b)) { + return 1; + } + + // Maintain original order + return sortInput ? + (indexOf(sortInput, a) - indexOf(sortInput, b)) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function (a, b) { + // Exit early if the nodes are identical + if (a === b) { + hasDuplicate = true; + return 0; + } + + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [a], + bp = [b]; + + // Parentless nodes are either documents or disconnected + if (!aup || !bup) { + return a === document ? -1 : + b === document ? 1 : + aup ? -1 : + bup ? 1 : + sortInput ? + (indexOf(sortInput, a) - indexOf(sortInput, b)) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if (aup === bup) { + return siblingCheck(a, b); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ((cur = cur.parentNode)) { + ap.unshift(cur); + } + cur = b; + while ((cur = cur.parentNode)) { + bp.unshift(cur); + } + + // Walk down the tree looking for a discrepancy + while (ap[i] === bp[i]) { + i++; + } + + return i ? + // Do a sibling check if the nodes have a common ancestor + siblingCheck(ap[i], bp[i]) : + + // Otherwise nodes in our document sort first + ap[i] === preferredDoc ? -1 : + bp[i] === preferredDoc ? 1 : + 0; + }; + + return document; + }; + + Sizzle.matches = function (expr, elements) { + return Sizzle(expr, null, null, elements); + }; + + Sizzle.matchesSelector = function (elem, expr) { + // Set document vars if needed + if ((elem.ownerDocument || elem) !== document) { + setDocument(elem); + } + + // Make sure that attribute selectors are quoted + expr = expr.replace(rattributeQuotes, "='$1']"); + + if (support.matchesSelector && documentIsHTML && + !compilerCache[expr + " "] && + (!rbuggyMatches || !rbuggyMatches.test(expr)) && + (!rbuggyQSA || !rbuggyQSA.test(expr))) { + + try { + var ret = matches.call(elem, expr); + + // IE 9's matchesSelector returns false on disconnected nodes + if (ret || support.disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11) { + return ret; + } + } catch (e) {} + } + + return Sizzle(expr, document, null, [elem]).length > 0; + }; + + Sizzle.contains = function (context, elem) { + // Set document vars if needed + if ((context.ownerDocument || context) !== document) { + setDocument(context); + } + return contains(context, elem); + }; + + Sizzle.attr = function (elem, name) { + // Set document vars if needed + if ((elem.ownerDocument || elem) !== document) { + setDocument(elem); + } + + var fn = Expr.attrHandle[name.toLowerCase()], + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call(Expr.attrHandle, name.toLowerCase()) ? + fn(elem, name, !documentIsHTML) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute(name) : + (val = elem.getAttributeNode(name)) && val.specified ? + val.value : + null; + }; + + Sizzle.escape = function (sel) { + return (sel + "").replace(rcssescape, fcssescape); + }; + + Sizzle.error = function (msg) { + throw new Error("Syntax error, unrecognized expression: " + msg); + }; + + /** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ + Sizzle.uniqueSort = function (results) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice(0); + results.sort(sortOrder); + + if (hasDuplicate) { + while ((elem = results[i++])) { + if (elem === results[i]) { + j = duplicates.push(i); + } + } + while (j--) { + results.splice(duplicates[j], 1); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; + }; + + /** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ + getText = Sizzle.getText = function (elem) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if (!nodeType) { + // If no nodeType, this is expected to be an array + while ((node = elem[i++])) { + // Do not traverse comment nodes + ret += getText(node); + } + } else if (nodeType === 1 || nodeType === 9 || nodeType === 11) { + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if (typeof elem.textContent === "string") { + return elem.textContent; + } else { + // Traverse its children + for (elem = elem.firstChild; elem; elem = elem.nextSibling) { + ret += getText(elem); + } + } + } else if (nodeType === 3 || nodeType === 4) { + return elem.nodeValue; + } + // Do not include comment or processing instruction nodes + + return ret; + }; + + Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { + dir: "parentNode", + first: true + }, + " ": { + dir: "parentNode" + }, + "+": { + dir: "previousSibling", + first: true + }, + "~": { + dir: "previousSibling" + } + }, + + preFilter: { + "ATTR": function (match) { + match[1] = match[1].replace(runescape, funescape); + + // Move the given value to match[3] whether quoted or unquoted + match[3] = (match[3] || match[4] || match[5] || "").replace(runescape, funescape); + + if (match[2] === "~=") { + match[3] = " " + match[3] + " "; + } + + return match.slice(0, 4); + }, + + "CHILD": function (match) { + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[1] = match[1].toLowerCase(); + + if (match[1].slice(0, 3) === "nth") { + // nth-* requires argument + if (!match[3]) { + Sizzle.error(match[0]); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[4] = +(match[4] ? match[5] + (match[6] || 1) : 2 * (match[3] === "even" || match[3] === "odd")); + match[5] = +((match[7] + match[8]) || match[3] === "odd"); + + // other types prohibit arguments + } else if (match[3]) { + Sizzle.error(match[0]); + } + + return match; + }, + + "PSEUDO": function (match) { + var excess, + unquoted = !match[6] && match[2]; + + if (matchExpr["CHILD"].test(match[0])) { + return null; + } + + // Accept quoted arguments as-is + if (match[3]) { + match[2] = match[4] || match[5] || ""; + + // Strip excess characters from unquoted arguments + } else if (unquoted && rpseudo.test(unquoted) && + // Get excess from tokenize (recursively) + (excess = tokenize(unquoted, true)) && + // advance to the next closing parenthesis + (excess = unquoted.indexOf(")", unquoted.length - excess) - unquoted.length)) { + + // excess is a negative index + match[0] = match[0].slice(0, excess); + match[2] = unquoted.slice(0, excess); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice(0, 3); + } + }, + + filter: { + + "TAG": function (nodeNameSelector) { + var nodeName = nodeNameSelector.replace(runescape, funescape).toLowerCase(); + return nodeNameSelector === "*" ? + function () { + return true; + } : + function (elem) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function (className) { + var pattern = classCache[className + " "]; + + return pattern || + (pattern = new RegExp("(^|" + whitespace + ")" + className + "(" + whitespace + "|$)")) && + classCache(className, function (elem) { + return pattern.test(typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || ""); + }); + }, + + "ATTR": function (name, operator, check) { + return function (elem) { + var result = Sizzle.attr(elem, name); + + if (result == null) { + return operator === "!="; + } + if (!operator) { + return true; + } + + result += ""; + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf(check) === 0 : + operator === "*=" ? check && result.indexOf(check) > -1 : + operator === "$=" ? check && result.slice(-check.length) === check : + operator === "~=" ? (" " + result.replace(rwhitespace, " ") + " ").indexOf(check) > -1 : + operator === "|=" ? result === check || result.slice(0, check.length + 1) === check + "-" : + false; + }; + }, + + "CHILD": function (type, what, argument, first, last) { + var simple = type.slice(0, 3) !== "nth", + forward = type.slice(-4) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function (elem) { + return !!elem.parentNode; + } : + + function (elem, context, xml) { + var cache, uniqueCache, outerCache, node, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType, + diff = false; + + if (parent) { + + // :(first|last|only)-(child|of-type) + if (simple) { + while (dir) { + node = elem; + while ((node = node[dir])) { + if (ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1) { + + return false; + } + } + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [forward ? parent.firstChild : parent.lastChild]; + + // non-xml :nth-child(...) stores cache data on `parent` + if (forward && useCache) { + + // Seek `elem` from a previously-cached index + + // ...in a gzip-friendly way + node = parent; + outerCache = node[expando] || (node[expando] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[node.uniqueID] || + (outerCache[node.uniqueID] = {}); + + cache = uniqueCache[type] || []; + nodeIndex = cache[0] === dirruns && cache[1]; + diff = nodeIndex && cache[2]; + node = nodeIndex && parent.childNodes[nodeIndex]; + + while ((node = ++nodeIndex && node && node[dir] || + + // Fallback to seeking `elem` from the start + (diff = nodeIndex = 0) || start.pop())) { + + // When found, cache indexes on `parent` and break + if (node.nodeType === 1 && ++diff && node === elem) { + uniqueCache[type] = [dirruns, nodeIndex, diff]; + break; + } + } + + } else { + // Use previously-cached element index if available + if (useCache) { + // ...in a gzip-friendly way + node = elem; + outerCache = node[expando] || (node[expando] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[node.uniqueID] || + (outerCache[node.uniqueID] = {}); + + cache = uniqueCache[type] || []; + nodeIndex = cache[0] === dirruns && cache[1]; + diff = nodeIndex; + } + + // xml :nth-child(...) + // or :nth-last-child(...) or :nth(-last)?-of-type(...) + if (diff === false) { + // Use the same loop as above to seek `elem` from the start + while ((node = ++nodeIndex && node && node[dir] || + (diff = nodeIndex = 0) || start.pop())) { + + if ((ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1) && + ++diff) { + + // Cache the index of each encountered element + if (useCache) { + outerCache = node[expando] || (node[expando] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[node.uniqueID] || + (outerCache[node.uniqueID] = {}); + + uniqueCache[type] = [dirruns, diff]; + } + + if (node === elem) { + break; + } + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || (diff % first === 0 && diff / first >= 0); + } + }; + }, + + "PSEUDO": function (pseudo, argument) { + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[pseudo] || Expr.setFilters[pseudo.toLowerCase()] || + Sizzle.error("unsupported pseudo: " + pseudo); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if (fn[expando]) { + return fn(argument); + } + + // But maintain support for old signatures + if (fn.length > 1) { + args = [pseudo, pseudo, "", argument]; + return Expr.setFilters.hasOwnProperty(pseudo.toLowerCase()) ? + markFunction(function (seed, matches) { + var idx, + matched = fn(seed, argument), + i = matched.length; + while (i--) { + idx = indexOf(seed, matched[i]); + seed[idx] = !(matches[idx] = matched[i]); + } + }) : + function (elem) { + return fn(elem, 0, args); + }; + } + + return fn; + } + }, + + pseudos: { + // Potentially complex pseudos + "not": markFunction(function (selector) { + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile(selector.replace(rtrim, "$1")); + + return matcher[expando] ? + markFunction(function (seed, matches, context, xml) { + var elem, + unmatched = matcher(seed, null, xml, []), + i = seed.length; + + // Match elements unmatched by `matcher` + while (i--) { + if ((elem = unmatched[i])) { + seed[i] = !(matches[i] = elem); + } + } + }) : + function (elem, context, xml) { + input[0] = elem; + matcher(input, null, xml, results); + // Don't keep the element (issue #299) + input[0] = null; + return !results.pop(); + }; + }), + + "has": markFunction(function (selector) { + return function (elem) { + return Sizzle(selector, elem).length > 0; + }; + }), + + "contains": markFunction(function (text) { + text = text.replace(runescape, funescape); + return function (elem) { + return (elem.textContent || elem.innerText || getText(elem)).indexOf(text) > -1; + }; + }), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction(function (lang) { + // lang value must be a valid identifier + if (!ridentifier.test(lang || "")) { + Sizzle.error("unsupported lang: " + lang); + } + lang = lang.replace(runescape, funescape).toLowerCase(); + return function (elem) { + var elemLang; + do { + if ((elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute("xml:lang") || elem.getAttribute("lang"))) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf(lang + "-") === 0; + } + } while ((elem = elem.parentNode) && elem.nodeType === 1); + return false; + }; + }), + + // Miscellaneous + "target": function (elem) { + var hash = window.location && window.location.hash; + return hash && hash.slice(1) === elem.id; + }, + + "root": function (elem) { + return elem === docElem; + }, + + "focus": function (elem) { + return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); + }, + + // Boolean properties + "enabled": createDisabledPseudo(false), + "disabled": createDisabledPseudo(true), + + "checked": function (elem) { + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); + }, + + "selected": function (elem) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if (elem.parentNode) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function (elem) { + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for (elem = elem.firstChild; elem; elem = elem.nextSibling) { + if (elem.nodeType < 6) { + return false; + } + } + return true; + }, + + "parent": function (elem) { + return !Expr.pseudos["empty"](elem); + }, + + // Element/input types + "header": function (elem) { + return rheader.test(elem.nodeName); + }, + + "input": function (elem) { + return rinputs.test(elem.nodeName); + }, + + "button": function (elem) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function (elem) { + var attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ((attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text"); + }, + + // Position-in-collection + "first": createPositionalPseudo(function () { + return [0]; + }), + + "last": createPositionalPseudo(function (matchIndexes, length) { + return [length - 1]; + }), + + "eq": createPositionalPseudo(function (matchIndexes, length, argument) { + return [argument < 0 ? argument + length : argument]; + }), + + "even": createPositionalPseudo(function (matchIndexes, length) { + var i = 0; + for (; i < length; i += 2) { + matchIndexes.push(i); + } + return matchIndexes; + }), + + "odd": createPositionalPseudo(function (matchIndexes, length) { + var i = 1; + for (; i < length; i += 2) { + matchIndexes.push(i); + } + return matchIndexes; + }), + + "lt": createPositionalPseudo(function (matchIndexes, length, argument) { + var i = argument < 0 ? argument + length : argument; + for (; --i >= 0;) { + matchIndexes.push(i); + } + return matchIndexes; + }), + + "gt": createPositionalPseudo(function (matchIndexes, length, argument) { + var i = argument < 0 ? argument + length : argument; + for (; ++i < length;) { + matchIndexes.push(i); + } + return matchIndexes; + }) + } + }; + + Expr.pseudos["nth"] = Expr.pseudos["eq"]; + + // Add button/input type pseudos + for (i in { + radio: true, + checkbox: true, + file: true, + password: true, + image: true + }) { + Expr.pseudos[i] = createInputPseudo(i); + } + for (i in { + submit: true, + reset: true + }) { + Expr.pseudos[i] = createButtonPseudo(i); + } + + // Easy API for creating new setFilters + function setFilters() {} + setFilters.prototype = Expr.filters = Expr.pseudos; + Expr.setFilters = new setFilters(); + + tokenize = Sizzle.tokenize = function (selector, parseOnly) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[selector + " "]; + + if (cached) { + return parseOnly ? 0 : cached.slice(0); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while (soFar) { + + // Comma and first run + if (!matched || (match = rcomma.exec(soFar))) { + if (match) { + // Don't consume trailing commas as valid + soFar = soFar.slice(match[0].length) || soFar; + } + groups.push((tokens = [])); + } + + matched = false; + + // Combinators + if ((match = rcombinators.exec(soFar))) { + matched = match.shift(); + tokens.push({ + value: matched, + // Cast descendant combinators to space + type: match[0].replace(rtrim, " ") + }); + soFar = soFar.slice(matched.length); + } + + // Filters + for (type in Expr.filter) { + if ((match = matchExpr[type].exec(soFar)) && (!preFilters[type] || + (match = preFilters[type](match)))) { + matched = match.shift(); + tokens.push({ + value: matched, + type: type, + matches: match + }); + soFar = soFar.slice(matched.length); + } + } + + if (!matched) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error(selector) : + // Cache the tokens + tokenCache(selector, groups).slice(0); + }; + + function toSelector(tokens) { + var i = 0, + len = tokens.length, + selector = ""; + for (; i < len; i++) { + selector += tokens[i].value; + } + return selector; + } + + function addCombinator(matcher, combinator, base) { + var dir = combinator.dir, + skip = combinator.next, + key = skip || dir, + checkNonElements = base && key === "parentNode", + doneName = done++; + + return combinator.first ? + // Check against closest ancestor/preceding element + function (elem, context, xml) { + while ((elem = elem[dir])) { + if (elem.nodeType === 1 || checkNonElements) { + return matcher(elem, context, xml); + } + } + return false; + } : + + // Check against all ancestor/preceding elements + function (elem, context, xml) { + var oldCache, uniqueCache, outerCache, + newCache = [dirruns, doneName]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching + if (xml) { + while ((elem = elem[dir])) { + if (elem.nodeType === 1 || checkNonElements) { + if (matcher(elem, context, xml)) { + return true; + } + } + } + } else { + while ((elem = elem[dir])) { + if (elem.nodeType === 1 || checkNonElements) { + outerCache = elem[expando] || (elem[expando] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[elem.uniqueID] || (outerCache[elem.uniqueID] = {}); + + if (skip && skip === elem.nodeName.toLowerCase()) { + elem = elem[dir] || elem; + } else if ((oldCache = uniqueCache[key]) && + oldCache[0] === dirruns && oldCache[1] === doneName) { + + // Assign to newCache so results back-propagate to previous elements + return (newCache[2] = oldCache[2]); + } else { + // Reuse newcache so results back-propagate to previous elements + uniqueCache[key] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ((newCache[2] = matcher(elem, context, xml))) { + return true; + } + } + } + } + } + return false; + }; + } + + function elementMatcher(matchers) { + return matchers.length > 1 ? + function (elem, context, xml) { + var i = matchers.length; + while (i--) { + if (!matchers[i](elem, context, xml)) { + return false; + } + } + return true; + } : + matchers[0]; + } + + function multipleContexts(selector, contexts, results) { + var i = 0, + len = contexts.length; + for (; i < len; i++) { + Sizzle(selector, contexts[i], results); + } + return results; + } + + function condense(unmatched, map, filter, context, xml) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for (; i < len; i++) { + if ((elem = unmatched[i])) { + if (!filter || filter(elem, context, xml)) { + newUnmatched.push(elem); + if (mapped) { + map.push(i); + } + } + } + } + + return newUnmatched; + } + + function setMatcher(preFilter, selector, matcher, postFilter, postFinder, postSelector) { + if (postFilter && !postFilter[expando]) { + postFilter = setMatcher(postFilter); + } + if (postFinder && !postFinder[expando]) { + postFinder = setMatcher(postFinder, postSelector); + } + return markFunction(function (seed, results, context, xml) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts(selector || "*", context.nodeType ? [context] : context, []), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && (seed || !selector) ? + condense(elems, preMap, preFilter, context, xml) : + elems, + + matcherOut = matcher ? + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || (seed ? preFilter : preexisting || postFilter) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if (matcher) { + matcher(matcherIn, matcherOut, context, xml); + } + + // Apply postFilter + if (postFilter) { + temp = condense(matcherOut, postMap); + postFilter(temp, [], context, xml); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while (i--) { + if ((elem = temp[i])) { + matcherOut[postMap[i]] = !(matcherIn[postMap[i]] = elem); + } + } + } + + if (seed) { + if (postFinder || preFilter) { + if (postFinder) { + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while (i--) { + if ((elem = matcherOut[i])) { + // Restore matcherIn since elem is not yet a final match + temp.push((matcherIn[i] = elem)); + } + } + postFinder(null, (matcherOut = []), temp, xml); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while (i--) { + if ((elem = matcherOut[i]) && + (temp = postFinder ? indexOf(seed, elem) : preMap[i]) > -1) { + + seed[temp] = !(results[temp] = elem); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice(preexisting, matcherOut.length) : + matcherOut + ); + if (postFinder) { + postFinder(null, results, matcherOut, xml); + } else { + push.apply(results, matcherOut); + } + } + }); + } + + function matcherFromTokens(tokens) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[tokens[0].type], + implicitRelative = leadingRelative || Expr.relative[" "], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator(function (elem) { + return elem === checkContext; + }, implicitRelative, true), + matchAnyContext = addCombinator(function (elem) { + return indexOf(checkContext, elem) > -1; + }, implicitRelative, true), + matchers = [function (elem, context, xml) { + var ret = (!leadingRelative && (xml || context !== outermostContext)) || ( + (checkContext = context).nodeType ? + matchContext(elem, context, xml) : + matchAnyContext(elem, context, xml)); + // Avoid hanging onto element (issue #299) + checkContext = null; + return ret; + }]; + + for (; i < len; i++) { + if ((matcher = Expr.relative[tokens[i].type])) { + matchers = [addCombinator(elementMatcher(matchers), matcher)]; + } else { + matcher = Expr.filter[tokens[i].type].apply(null, tokens[i].matches); + + // Return special upon seeing a positional matcher + if (matcher[expando]) { + // Find the next relative operator (if any) for proper handling + j = ++i; + for (; j < len; j++) { + if (Expr.relative[tokens[j].type]) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher(matchers), + i > 1 && toSelector( + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens.slice(0, i - 1).concat({ + value: tokens[i - 2].type === " " ? "*" : "" + }) + ).replace(rtrim, "$1"), + matcher, + i < j && matcherFromTokens(tokens.slice(i, j)), + j < len && matcherFromTokens((tokens = tokens.slice(j))), + j < len && toSelector(tokens) + ); + } + matchers.push(matcher); + } + } + + return elementMatcher(matchers); + } + + function matcherFromGroupMatchers(elementMatchers, setMatchers) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function (seed, context, xml, results, outermost) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find["TAG"]("*", outermost), + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), + len = elems.length; + + if (outermost) { + outermostContext = context === document || context || outermost; + } + + // Add elements passing elementMatchers directly to results + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id + for (; i !== len && (elem = elems[i]) != null; i++) { + if (byElement && elem) { + j = 0; + if (!context && elem.ownerDocument !== document) { + setDocument(elem); + xml = !documentIsHTML; + } + while ((matcher = elementMatchers[j++])) { + if (matcher(elem, context || document, xml)) { + results.push(elem); + break; + } + } + if (outermost) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if (bySet) { + // They will have gone through all possible matchers + if ((elem = !matcher && elem)) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if (seed) { + unmatched.push(elem); + } + } + } + + // `i` is now the count of elements visited above, and adding it to `matchedCount` + // makes the latter nonnegative. + matchedCount += i; + + // Apply set filters to unmatched elements + // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` + // equals `i`), unless we didn't visit _any_ elements in the above loop because we have + // no element matchers and no seed. + // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that + // case, which will result in a "00" `matchedCount` that differs from `i` but is also + // numerically zero. + if (bySet && i !== matchedCount) { + j = 0; + while ((matcher = setMatchers[j++])) { + matcher(unmatched, setMatched, context, xml); + } + + if (seed) { + // Reintegrate element matches to eliminate the need for sorting + if (matchedCount > 0) { + while (i--) { + if (!(unmatched[i] || setMatched[i])) { + setMatched[i] = pop.call(results); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense(setMatched); + } + + // Add matches to results + push.apply(results, setMatched); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if (outermost && !seed && setMatched.length > 0 && + (matchedCount + setMatchers.length) > 1) { + + Sizzle.uniqueSort(results); + } + } + + // Override manipulation of globals by nested matchers + if (outermost) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction(superMatcher) : + superMatcher; + } + + compile = Sizzle.compile = function (selector, match /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[selector + " "]; + + if (!cached) { + // Generate a function of recursive functions that can be used to check each element + if (!match) { + match = tokenize(selector); + } + i = match.length; + while (i--) { + cached = matcherFromTokens(match[i]); + if (cached[expando]) { + setMatchers.push(cached); + } else { + elementMatchers.push(cached); + } + } + + // Cache the compiled function + cached = compilerCache(selector, matcherFromGroupMatchers(elementMatchers, setMatchers)); + + // Save selector and tokenization + cached.selector = selector; + } + return cached; + }; + + /** + * A low-level selection function that works with Sizzle's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with Sizzle.compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ + select = Sizzle.select = function (selector, context, results, seed) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize((selector = compiled.selector || selector)); + + results = results || []; + + // Try to minimize operations if there is only one selector in the list and no seed + // (the latter of which guarantees us context) + if (match.length === 1) { + + // Reduce context if the leading compound selector is an ID + tokens = match[0] = match[0].slice(0); + if (tokens.length > 2 && (token = tokens[0]).type === "ID" && + context.nodeType === 9 && documentIsHTML && Expr.relative[tokens[1].type]) { + + context = (Expr.find["ID"](token.matches[0].replace(runescape, funescape), context) || [])[0]; + if (!context) { + return results; + + // Precompiled matchers will still verify ancestry, so step up a level + } else if (compiled) { + context = context.parentNode; + } + + selector = selector.slice(tokens.shift().value.length); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr["needsContext"].test(selector) ? 0 : tokens.length; + while (i--) { + token = tokens[i]; + + // Abort if we hit a combinator + if (Expr.relative[(type = token.type)]) { + break; + } + if ((find = Expr.find[type])) { + // Search, expanding context for leading sibling combinators + if ((seed = find( + token.matches[0].replace(runescape, funescape), + rsibling.test(tokens[0].type) && testContext(context.parentNode) || context + ))) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice(i, 1); + selector = seed.length && toSelector(tokens); + if (!selector) { + push.apply(results, seed); + return results; + } + + break; + } + } + } + } + + // Compile and execute a filtering function if one is not provided + // Provide `match` to avoid retokenization if we modified the selector above + (compiled || compile(selector, match))( + seed, + context, + !documentIsHTML, + results, + !context || rsibling.test(selector) && testContext(context.parentNode) || context + ); + return results; + }; + + // One-time assignments + + // Sort stability + support.sortStable = expando.split("").sort(sortOrder).join("") === expando; + + // Support: Chrome 14-35+ + // Always assume duplicates if they aren't passed to the comparison function + support.detectDuplicates = !!hasDuplicate; + + // Initialize against the default document + setDocument(); + + // Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) + // Detached nodes confoundingly follow *each other* + support.sortDetached = assert(function (el) { + // Should return 1, but returns 4 (following) + return el.compareDocumentPosition(document.createElement("fieldset")) & 1; + }); + + // Support: IE<8 + // Prevent attribute/property "interpolation" + // https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx + if (!assert(function (el) { + el.innerHTML = ""; + return el.firstChild.getAttribute("href") === "#"; + })) { + addHandle("type|href|height|width", function (elem, name, isXML) { + if (!isXML) { + return elem.getAttribute(name, name.toLowerCase() === "type" ? 1 : 2); + } + }); + } + + // Support: IE<9 + // Use defaultValue in place of getAttribute("value") + if (!support.attributes || !assert(function (el) { + el.innerHTML = ""; + el.firstChild.setAttribute("value", ""); + return el.firstChild.getAttribute("value") === ""; + })) { + addHandle("value", function (elem, name, isXML) { + if (!isXML && elem.nodeName.toLowerCase() === "input") { + return elem.defaultValue; + } + }); + } + + // Support: IE<9 + // Use getAttributeNode to fetch booleans when getAttribute lies + if (!assert(function (el) { + return el.getAttribute("disabled") == null; + })) { + addHandle(booleans, function (elem, name, isXML) { + var val; + if (!isXML) { + return elem[name] === true ? name.toLowerCase() : + (val = elem.getAttributeNode(name)) && val.specified ? + val.value : + null; + } + }); + } + + return Sizzle; + + })(window); + + + + jQuery.find = Sizzle; + jQuery.expr = Sizzle.selectors; + + // Deprecated + jQuery.expr[":"] = jQuery.expr.pseudos; + jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; + jQuery.text = Sizzle.getText; + jQuery.isXMLDoc = Sizzle.isXML; + jQuery.contains = Sizzle.contains; + jQuery.escapeSelector = Sizzle.escape; + + + + + var dir = function (elem, dir, until) { + var matched = [], + truncate = until !== undefined; + + while ((elem = elem[dir]) && elem.nodeType !== 9) { + if (elem.nodeType === 1) { + if (truncate && jQuery(elem).is(until)) { + break; + } + matched.push(elem); + } + } + return matched; + }; + + + var siblings = function (n, elem) { + var matched = []; + + for (; n; n = n.nextSibling) { + if (n.nodeType === 1 && n !== elem) { + matched.push(n); + } + } + + return matched; + }; + + + var rneedsContext = jQuery.expr.match.needsContext; + + + + function nodeName(elem, name) { + + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + + }; + var rsingleTag = (/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i); + + + + // Implement the identical functionality for filter and not + function winnow(elements, qualifier, not) { + if (isFunction(qualifier)) { + return jQuery.grep(elements, function (elem, i) { + return !!qualifier.call(elem, i, elem) !== not; + }); + } + + // Single element + if (qualifier.nodeType) { + return jQuery.grep(elements, function (elem) { + return (elem === qualifier) !== not; + }); + } + + // Arraylike of elements (jQuery, arguments, Array) + if (typeof qualifier !== "string") { + return jQuery.grep(elements, function (elem) { + return (indexOf.call(qualifier, elem) > -1) !== not; + }); + } + + // Filtered directly for both simple and complex selectors + return jQuery.filter(qualifier, elements, not); + } + + jQuery.filter = function (expr, elems, not) { + var elem = elems[0]; + + if (not) { + expr = ":not(" + expr + ")"; + } + + if (elems.length === 1 && elem.nodeType === 1) { + return jQuery.find.matchesSelector(elem, expr) ? [elem] : []; + } + + return jQuery.find.matches(expr, jQuery.grep(elems, function (elem) { + return elem.nodeType === 1; + })); + }; + + jQuery.fn.extend({ + find: function (selector) { + var i, ret, + len = this.length, + self = this; + + if (typeof selector !== "string") { + return this.pushStack(jQuery(selector).filter(function () { + for (i = 0; i < len; i++) { + if (jQuery.contains(self[i], this)) { + return true; + } + } + })); + } + + ret = this.pushStack([]); + + for (i = 0; i < len; i++) { + jQuery.find(selector, self[i], ret); + } + + return len > 1 ? jQuery.uniqueSort(ret) : ret; + }, + filter: function (selector) { + return this.pushStack(winnow(this, selector || [], false)); + }, + not: function (selector) { + return this.pushStack(winnow(this, selector || [], true)); + }, + is: function (selector) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test(selector) ? + jQuery(selector) : + selector || [], + false + ).length; + } + }); + + + // Initialize a jQuery object + + + // A central reference to the root jQuery(document) + var rootjQuery, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + // Shortcut simple #id case for speed + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, + + init = jQuery.fn.init = function (selector, context, root) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if (!selector) { + return this; + } + + // Method init() accepts an alternate rootjQuery + // so migrate can support jQuery.sub (gh-2101) + root = root || rootjQuery; + + // Handle HTML strings + if (typeof selector === "string") { + if (selector[0] === "<" && + selector[selector.length - 1] === ">" && + selector.length >= 3) { + + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [null, selector, null]; + + } else { + match = rquickExpr.exec(selector); + } + + // Match html or make sure no context is specified for #id + if (match && (match[1] || !context)) { + + // HANDLE: $(html) -> $(array) + if (match[1]) { + context = context instanceof jQuery ? context[0] : context; + + // Option to run scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge(this, jQuery.parseHTML( + match[1], + context && context.nodeType ? context.ownerDocument || context : document, + true + )); + + // HANDLE: $(html, props) + if (rsingleTag.test(match[1]) && jQuery.isPlainObject(context)) { + for (match in context) { + + // Properties of context are called as methods if possible + if (isFunction(this[match])) { + this[match](context[match]); + + // ...and otherwise set as attributes + } else { + this.attr(match, context[match]); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById(match[2]); + + if (elem) { + + // Inject the element directly into the jQuery object + this[0] = elem; + this.length = 1; + } + return this; + } + + // HANDLE: $(expr, $(...)) + } else if (!context || context.jquery) { + return (context || root).find(selector); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor(context).find(selector); + } + + // HANDLE: $(DOMElement) + } else if (selector.nodeType) { + this[0] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if (isFunction(selector)) { + return root.ready !== undefined ? + root.ready(selector) : + + // Execute immediately if ready is not present + selector(jQuery); + } + + return jQuery.makeArray(selector, this); + }; + + // Give the init function the jQuery prototype for later instantiation + init.prototype = jQuery.fn; + + // Initialize central reference + rootjQuery = jQuery(document); + + + var rparentsprev = /^(?:parents|prev(?:Until|All))/, + + // Methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + + jQuery.fn.extend({ + has: function (target) { + var targets = jQuery(target, this), + l = targets.length; + + return this.filter(function () { + var i = 0; + for (; i < l; i++) { + if (jQuery.contains(this, targets[i])) { + return true; + } + } + }); + }, + + closest: function (selectors, context) { + var cur, + i = 0, + l = this.length, + matched = [], + targets = typeof selectors !== "string" && jQuery(selectors); + + // Positional selectors never match, since there's no _selection_ context + if (!rneedsContext.test(selectors)) { + for (; i < l; i++) { + for (cur = this[i]; cur && cur !== context; cur = cur.parentNode) { + + // Always skip document fragments + if (cur.nodeType < 11 && (targets ? + targets.index(cur) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector(cur, selectors))) { + + matched.push(cur); + break; + } + } + } + } + + return this.pushStack(matched.length > 1 ? jQuery.uniqueSort(matched) : matched); + }, + + // Determine the position of an element within the set + index: function (elem) { + + // No argument, return index in parent + if (!elem) { + return (this[0] && this[0].parentNode) ? this.first().prevAll().length : -1; + } + + // Index in selector + if (typeof elem === "string") { + return indexOf.call(jQuery(elem), this[0]); + } + + // Locate the position of the desired element + return indexOf.call(this, + + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem + ); + }, + + add: function (selector, context) { + return this.pushStack( + jQuery.uniqueSort( + jQuery.merge(this.get(), jQuery(selector, context)) + ) + ); + }, + + addBack: function (selector) { + return this.add(selector == null ? + this.prevObject : this.prevObject.filter(selector) + ); + } + }); + + function sibling(cur, dir) { + while ((cur = cur[dir]) && cur.nodeType !== 1) {} + return cur; + } + + jQuery.each({ + parent: function (elem) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function (elem) { + return dir(elem, "parentNode"); + }, + parentsUntil: function (elem, i, until) { + return dir(elem, "parentNode", until); + }, + next: function (elem) { + return sibling(elem, "nextSibling"); + }, + prev: function (elem) { + return sibling(elem, "previousSibling"); + }, + nextAll: function (elem) { + return dir(elem, "nextSibling"); + }, + prevAll: function (elem) { + return dir(elem, "previousSibling"); + }, + nextUntil: function (elem, i, until) { + return dir(elem, "nextSibling", until); + }, + prevUntil: function (elem, i, until) { + return dir(elem, "previousSibling", until); + }, + siblings: function (elem) { + return siblings((elem.parentNode || {}).firstChild, elem); + }, + children: function (elem) { + return siblings(elem.firstChild); + }, + contents: function (elem) { + if (nodeName(elem, "iframe")) { + return elem.contentDocument; + } + + // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only + // Treat the template element as a regular one in browsers that + // don't support it. + if (nodeName(elem, "template")) { + elem = elem.content || elem; + } + + return jQuery.merge([], elem.childNodes); + } + }, function (name, fn) { + jQuery.fn[name] = function (until, selector) { + var matched = jQuery.map(this, fn, until); + + if (name.slice(-5) !== "Until") { + selector = until; + } + + if (selector && typeof selector === "string") { + matched = jQuery.filter(selector, matched); + } + + if (this.length > 1) { + + // Remove duplicates + if (!guaranteedUnique[name]) { + jQuery.uniqueSort(matched); + } + + // Reverse order for parents* and prev-derivatives + if (rparentsprev.test(name)) { + matched.reverse(); + } + } + + return this.pushStack(matched); + }; + }); + var rnothtmlwhite = (/[^\x20\t\r\n\f]+/g); + + + + // Convert String-formatted options into Object-formatted ones + function createOptions(options) { + var object = {}; + jQuery.each(options.match(rnothtmlwhite) || [], function (_, flag) { + object[flag] = true; + }); + return object; + } + + /* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ + jQuery.Callbacks = function (options) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + createOptions(options) : + jQuery.extend({}, options); + + var // Flag to know if list is currently firing + firing, + + // Last fire value for non-forgettable lists + memory, + + // Flag to know if list was already fired + fired, + + // Flag to prevent firing + locked, + + // Actual callback list + list = [], + + // Queue of execution data for repeatable lists + queue = [], + + // Index of currently firing callback (modified by add/remove as needed) + firingIndex = -1, + + // Fire callbacks + fire = function () { + + // Enforce single-firing + locked = locked || options.once; + + // Execute callbacks for all pending executions, + // respecting firingIndex overrides and runtime changes + fired = firing = true; + for (; queue.length; firingIndex = -1) { + memory = queue.shift(); + while (++firingIndex < list.length) { + + // Run callback and check for early termination + if (list[firingIndex].apply(memory[0], memory[1]) === false && + options.stopOnFalse) { + + // Jump to end and forget the data so .add doesn't re-fire + firingIndex = list.length; + memory = false; + } + } + } + + // Forget the data if we're done with it + if (!options.memory) { + memory = false; + } + + firing = false; + + // Clean up if we're done firing for good + if (locked) { + + // Keep an empty list if we have data for future add calls + if (memory) { + list = []; + + // Otherwise, this object is spent + } else { + list = ""; + } + } + }, + + // Actual Callbacks object + self = { + + // Add a callback or a collection of callbacks to the list + add: function () { + if (list) { + + // If we have memory from a past run, we should fire after adding + if (memory && !firing) { + firingIndex = list.length - 1; + queue.push(memory); + } + + (function add(args) { + jQuery.each(args, function (_, arg) { + if (isFunction(arg)) { + if (!options.unique || !self.has(arg)) { + list.push(arg); + } + } else if (arg && arg.length && toType(arg) !== "string") { + + // Inspect recursively + add(arg); + } + }); + })(arguments); + + if (memory && !firing) { + fire(); + } + } + return this; + }, + + // Remove a callback from the list + remove: function () { + jQuery.each(arguments, function (_, arg) { + var index; + while ((index = jQuery.inArray(arg, list, index)) > -1) { + list.splice(index, 1); + + // Handle firing indexes + if (index <= firingIndex) { + firingIndex--; + } + } + }); + return this; + }, + + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function (fn) { + return fn ? + jQuery.inArray(fn, list) > -1 : + list.length > 0; + }, + + // Remove all callbacks from the list + empty: function () { + if (list) { + list = []; + } + return this; + }, + + // Disable .fire and .add + // Abort any current/pending executions + // Clear all callbacks and values + disable: function () { + locked = queue = []; + list = memory = ""; + return this; + }, + disabled: function () { + return !list; + }, + + // Disable .fire + // Also disable .add unless we have memory (since it would have no effect) + // Abort any pending executions + lock: function () { + locked = queue = []; + if (!memory && !firing) { + list = memory = ""; + } + return this; + }, + locked: function () { + return !!locked; + }, + + // Call all callbacks with the given context and arguments + fireWith: function (context, args) { + if (!locked) { + args = args || []; + args = [context, args.slice ? args.slice() : args]; + queue.push(args); + if (!firing) { + fire(); + } + } + return this; + }, + + // Call all the callbacks with the given arguments + fire: function () { + self.fireWith(this, arguments); + return this; + }, + + // To know if the callbacks have already been called at least once + fired: function () { + return !!fired; + } + }; + + return self; + }; + + + function Identity(v) { + return v; + } + + function Thrower(ex) { + throw ex; + } + + function adoptValue(value, resolve, reject, noValue) { + var method; + + try { + + // Check for promise aspect first to privilege synchronous behavior + if (value && isFunction((method = value.promise))) { + method.call(value).done(resolve).fail(reject); + + // Other thenables + } else if (value && isFunction((method = value.then))) { + method.call(value, resolve, reject); + + // Other non-thenables + } else { + + // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: + // * false: [ value ].slice( 0 ) => resolve( value ) + // * true: [ value ].slice( 1 ) => resolve() + resolve.apply(undefined, [value].slice(noValue)); + } + + // For Promises/A+, convert exceptions into rejections + // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in + // Deferred#then to conditionally suppress rejection. + } catch (value) { + + // Support: Android 4.0 only + // Strict mode functions invoked without .call/.apply get global-object context + reject.apply(undefined, [value]); + } + } + + jQuery.extend({ + + Deferred: function (func) { + var tuples = [ + + // action, add listener, callbacks, + // ... .then handlers, argument index, [final state] + ["notify", "progress", jQuery.Callbacks("memory"), + jQuery.Callbacks("memory"), 2 + ], + ["resolve", "done", jQuery.Callbacks("once memory"), + jQuery.Callbacks("once memory"), 0, "resolved" + ], + ["reject", "fail", jQuery.Callbacks("once memory"), + jQuery.Callbacks("once memory"), 1, "rejected" + ] + ], + state = "pending", + promise = { + state: function () { + return state; + }, + always: function () { + deferred.done(arguments).fail(arguments); + return this; + }, + "catch": function (fn) { + return promise.then(null, fn); + }, + + // Keep pipe for back-compat + pipe: function ( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + + return jQuery.Deferred(function (newDefer) { + jQuery.each(tuples, function (i, tuple) { + + // Map tuples (progress, done, fail) to arguments (done, fail, progress) + var fn = isFunction(fns[tuple[4]]) && fns[tuple[4]]; + + // deferred.progress(function() { bind to newDefer or newDefer.notify }) + // deferred.done(function() { bind to newDefer or newDefer.resolve }) + // deferred.fail(function() { bind to newDefer or newDefer.reject }) + deferred[tuple[1]](function () { + var returned = fn && fn.apply(this, arguments); + if (returned && isFunction(returned.promise)) { + returned.promise() + .progress(newDefer.notify) + .done(newDefer.resolve) + .fail(newDefer.reject); + } else { + newDefer[tuple[0] + "With"]( + this, + fn ? [returned] : arguments + ); + } + }); + }); + fns = null; + }).promise(); + }, + then: function (onFulfilled, onRejected, onProgress) { + var maxDepth = 0; + + function resolve(depth, deferred, handler, special) { + return function () { + var that = this, + args = arguments, + mightThrow = function () { + var returned, then; + + // Support: Promises/A+ section 2.3.3.3.3 + // https://promisesaplus.com/#point-59 + // Ignore double-resolution attempts + if (depth < maxDepth) { + return; + } + + returned = handler.apply(that, args); + + // Support: Promises/A+ section 2.3.1 + // https://promisesaplus.com/#point-48 + if (returned === deferred.promise()) { + throw new TypeError("Thenable self-resolution"); + } + + // Support: Promises/A+ sections 2.3.3.1, 3.5 + // https://promisesaplus.com/#point-54 + // https://promisesaplus.com/#point-75 + // Retrieve `then` only once + then = returned && + + // Support: Promises/A+ section 2.3.4 + // https://promisesaplus.com/#point-64 + // Only check objects and functions for thenability + (typeof returned === "object" || + typeof returned === "function") && + returned.then; + + // Handle a returned thenable + if (isFunction(then)) { + + // Special processors (notify) just wait for resolution + if (special) { + then.call( + returned, + resolve(maxDepth, deferred, Identity, special), + resolve(maxDepth, deferred, Thrower, special) + ); + + // Normal processors (resolve) also hook into progress + } else { + + // ...and disregard older resolution values + maxDepth++; + + then.call( + returned, + resolve(maxDepth, deferred, Identity, special), + resolve(maxDepth, deferred, Thrower, special), + resolve(maxDepth, deferred, Identity, + deferred.notifyWith) + ); + } + + // Handle all other returned values + } else { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if (handler !== Identity) { + that = undefined; + args = [returned]; + } + + // Process the value(s) + // Default process is resolve + (special || deferred.resolveWith)(that, args); + } + }, + + // Only normal processors (resolve) catch and reject exceptions + process = special ? + mightThrow : + function () { + try { + mightThrow(); + } catch (e) { + + if (jQuery.Deferred.exceptionHook) { + jQuery.Deferred.exceptionHook(e, + process.stackTrace); + } + + // Support: Promises/A+ section 2.3.3.3.4.1 + // https://promisesaplus.com/#point-61 + // Ignore post-resolution exceptions + if (depth + 1 >= maxDepth) { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if (handler !== Thrower) { + that = undefined; + args = [e]; + } + + deferred.rejectWith(that, args); + } + } + }; + + // Support: Promises/A+ section 2.3.3.3.1 + // https://promisesaplus.com/#point-57 + // Re-resolve promises immediately to dodge false rejection from + // subsequent errors + if (depth) { + process(); + } else { + + // Call an optional hook to record the stack, in case of exception + // since it's otherwise lost when execution goes async + if (jQuery.Deferred.getStackHook) { + process.stackTrace = jQuery.Deferred.getStackHook(); + } + window.setTimeout(process); + } + }; + } + + return jQuery.Deferred(function (newDefer) { + + // progress_handlers.add( ... ) + tuples[0][3].add( + resolve( + 0, + newDefer, + isFunction(onProgress) ? + onProgress : + Identity, + newDefer.notifyWith + ) + ); + + // fulfilled_handlers.add( ... ) + tuples[1][3].add( + resolve( + 0, + newDefer, + isFunction(onFulfilled) ? + onFulfilled : + Identity + ) + ); + + // rejected_handlers.add( ... ) + tuples[2][3].add( + resolve( + 0, + newDefer, + isFunction(onRejected) ? + onRejected : + Thrower + ) + ); + }).promise(); + }, + + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function (obj) { + return obj != null ? jQuery.extend(obj, promise) : promise; + } + }, + deferred = {}; + + // Add list-specific methods + jQuery.each(tuples, function (i, tuple) { + var list = tuple[2], + stateString = tuple[5]; + + // promise.progress = list.add + // promise.done = list.add + // promise.fail = list.add + promise[tuple[1]] = list.add; + + // Handle state + if (stateString) { + list.add( + function () { + + // state = "resolved" (i.e., fulfilled) + // state = "rejected" + state = stateString; + }, + + // rejected_callbacks.disable + // fulfilled_callbacks.disable + tuples[3 - i][2].disable, + + // rejected_handlers.disable + // fulfilled_handlers.disable + tuples[3 - i][3].disable, + + // progress_callbacks.lock + tuples[0][2].lock, + + // progress_handlers.lock + tuples[0][3].lock + ); + } + + // progress_handlers.fire + // fulfilled_handlers.fire + // rejected_handlers.fire + list.add(tuple[3].fire); + + // deferred.notify = function() { deferred.notifyWith(...) } + // deferred.resolve = function() { deferred.resolveWith(...) } + // deferred.reject = function() { deferred.rejectWith(...) } + deferred[tuple[0]] = function () { + deferred[tuple[0] + "With"](this === deferred ? undefined : this, arguments); + return this; + }; + + // deferred.notifyWith = list.fireWith + // deferred.resolveWith = list.fireWith + // deferred.rejectWith = list.fireWith + deferred[tuple[0] + "With"] = list.fireWith; + }); + + // Make the deferred a promise + promise.promise(deferred); + + // Call given func if any + if (func) { + func.call(deferred, deferred); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function (singleValue) { + var + + // count of uncompleted subordinates + remaining = arguments.length, + + // count of unprocessed arguments + i = remaining, + + // subordinate fulfillment data + resolveContexts = Array(i), + resolveValues = slice.call(arguments), + + // the master Deferred + master = jQuery.Deferred(), + + // subordinate callback factory + updateFunc = function (i) { + return function (value) { + resolveContexts[i] = this; + resolveValues[i] = arguments.length > 1 ? slice.call(arguments) : value; + if (!(--remaining)) { + master.resolveWith(resolveContexts, resolveValues); + } + }; + }; + + // Single- and empty arguments are adopted like Promise.resolve + if (remaining <= 1) { + adoptValue(singleValue, master.done(updateFunc(i)).resolve, master.reject, + !remaining); + + // Use .then() to unwrap secondary thenables (cf. gh-3000) + if (master.state() === "pending" || + isFunction(resolveValues[i] && resolveValues[i].then)) { + + return master.then(); + } + } + + // Multiple arguments are aggregated like Promise.all array elements + while (i--) { + adoptValue(resolveValues[i], updateFunc(i), master.reject); + } + + return master.promise(); + } + }); + + + // These usually indicate a programmer mistake during development, + // warn about them ASAP rather than swallowing them by default. + var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; + + jQuery.Deferred.exceptionHook = function (error, stack) { + + // Support: IE 8 - 9 only + // Console exists when dev tools are open, which can happen at any time + if (window.console && window.console.warn && error && rerrorNames.test(error.name)) { + window.console.warn("jQuery.Deferred exception: " + error.message, error.stack, stack); + } + }; + + + + + jQuery.readyException = function (error) { + window.setTimeout(function () { + throw error; + }); + }; + + + + + // The deferred used on DOM ready + var readyList = jQuery.Deferred(); + + jQuery.fn.ready = function (fn) { + + readyList + .then(fn) + + // Wrap jQuery.readyException in a function so that the lookup + // happens at the time of error handling instead of callback + // registration. + .catch(function (error) { + jQuery.readyException(error); + }); + + return this; + }; + + jQuery.extend({ + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Handle when the DOM is ready + ready: function (wait) { + + // Abort if there are pending holds or we're already ready + if (wait === true ? --jQuery.readyWait : jQuery.isReady) { + return; + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if (wait !== true && --jQuery.readyWait > 0) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith(document, [jQuery]); + } + }); + + jQuery.ready.then = readyList.then; + + // The ready event handler and self cleanup method + function completed() { + document.removeEventListener("DOMContentLoaded", completed); + window.removeEventListener("load", completed); + jQuery.ready(); + } + + // Catch cases where $(document).ready() is called + // after the browser event has already occurred. + // Support: IE <=9 - 10 only + // Older IE sometimes signals "interactive" too soon + if (document.readyState === "complete" || + (document.readyState !== "loading" && !document.documentElement.doScroll)) { + + // Handle it asynchronously to allow scripts the opportunity to delay ready + window.setTimeout(jQuery.ready); + + } else { + + // Use the handy event callback + document.addEventListener("DOMContentLoaded", completed); + + // A fallback to window.onload, that will always work + window.addEventListener("load", completed); + } + + + + + // Multifunctional method to get and set values of a collection + // The value/s can optionally be executed if it's a function + var access = function (elems, fn, key, value, chainable, emptyGet, raw) { + var i = 0, + len = elems.length, + bulk = key == null; + + // Sets many values + if (toType(key) === "object") { + chainable = true; + for (i in key) { + access(elems, fn, i, key[i], true, emptyGet, raw); + } + + // Sets one value + } else if (value !== undefined) { + chainable = true; + + if (!isFunction(value)) { + raw = true; + } + + if (bulk) { + + // Bulk operations run against the entire set + if (raw) { + fn.call(elems, value); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function (elem, key, value) { + return bulk.call(jQuery(elem), value); + }; + } + } + + if (fn) { + for (; i < len; i++) { + fn( + elems[i], key, raw ? + value : + value.call(elems[i], i, fn(elems[i], key)) + ); + } + } + } + + if (chainable) { + return elems; + } + + // Gets + if (bulk) { + return fn.call(elems); + } + + return len ? fn(elems[0], key) : emptyGet; + }; + + + // Matches dashed string for camelizing + var rmsPrefix = /^-ms-/, + rdashAlpha = /-([a-z])/g; + + // Used by camelCase as callback to replace() + function fcamelCase(all, letter) { + return letter.toUpperCase(); + } + + // Convert dashed to camelCase; used by the css and data modules + // Support: IE <=9 - 11, Edge 12 - 15 + // Microsoft forgot to hump their vendor prefix (#9572) + function camelCase(string) { + return string.replace(rmsPrefix, "ms-").replace(rdashAlpha, fcamelCase); + } + var acceptData = function (owner) { + + // Accepts only: + // - Node + // - Node.ELEMENT_NODE + // - Node.DOCUMENT_NODE + // - Object + // - Any + return owner.nodeType === 1 || owner.nodeType === 9 || !(+owner.nodeType); + }; + + + + + function Data() { + this.expando = jQuery.expando + Data.uid++; + } + + Data.uid = 1; + + Data.prototype = { + + cache: function (owner) { + + // Check if the owner object already has a cache + var value = owner[this.expando]; + + // If not, create one + if (!value) { + value = {}; + + // We can accept data for non-element nodes in modern browsers, + // but we should not, see #8335. + // Always return an empty object. + if (acceptData(owner)) { + + // If it is a node unlikely to be stringify-ed or looped over + // use plain assignment + if (owner.nodeType) { + owner[this.expando] = value; + + // Otherwise secure it in a non-enumerable property + // configurable must be true to allow the property to be + // deleted when data is removed + } else { + Object.defineProperty(owner, this.expando, { + value: value, + configurable: true + }); + } + } + } + + return value; + }, + set: function (owner, data, value) { + var prop, + cache = this.cache(owner); + + // Handle: [ owner, key, value ] args + // Always use camelCase key (gh-2257) + if (typeof data === "string") { + cache[camelCase(data)] = value; + + // Handle: [ owner, { properties } ] args + } else { + + // Copy the properties one-by-one to the cache object + for (prop in data) { + cache[camelCase(prop)] = data[prop]; + } + } + return cache; + }, + get: function (owner, key) { + return key === undefined ? + this.cache(owner) : + + // Always use camelCase key (gh-2257) + owner[this.expando] && owner[this.expando][camelCase(key)]; + }, + access: function (owner, key, value) { + + // In cases where either: + // + // 1. No key was specified + // 2. A string key was specified, but no value provided + // + // Take the "read" path and allow the get method to determine + // which value to return, respectively either: + // + // 1. The entire cache object + // 2. The data stored at the key + // + if (key === undefined || + ((key && typeof key === "string") && value === undefined)) { + + return this.get(owner, key); + } + + // When the key is not a string, or both a key and value + // are specified, set or extend (existing objects) with either: + // + // 1. An object of properties + // 2. A key and value + // + this.set(owner, key, value); + + // Since the "set" path can have two possible entry points + // return the expected data based on which path was taken[*] + return value !== undefined ? value : key; + }, + remove: function (owner, key) { + var i, + cache = owner[this.expando]; + + if (cache === undefined) { + return; + } + + if (key !== undefined) { + + // Support array or space separated string of keys + if (Array.isArray(key)) { + + // If key is an array of keys... + // We always set camelCase keys, so remove that. + key = key.map(camelCase); + } else { + key = camelCase(key); + + // If a key with the spaces exists, use it. + // Otherwise, create an array by matching non-whitespace + key = key in cache ? [key] : + (key.match(rnothtmlwhite) || []); + } + + i = key.length; + + while (i--) { + delete cache[key[i]]; + } + } + + // Remove the expando if there's no more data + if (key === undefined || jQuery.isEmptyObject(cache)) { + + // Support: Chrome <=35 - 45 + // Webkit & Blink performance suffers when deleting properties + // from DOM nodes, so set to undefined instead + // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) + if (owner.nodeType) { + owner[this.expando] = undefined; + } else { + delete owner[this.expando]; + } + } + }, + hasData: function (owner) { + var cache = owner[this.expando]; + return cache !== undefined && !jQuery.isEmptyObject(cache); + } + }; + var dataPriv = new Data(); + + var dataUser = new Data(); + + + + // Implementation Summary + // + // 1. Enforce API surface and semantic compatibility with 1.9.x branch + // 2. Improve the module's maintainability by reducing the storage + // paths to a single mechanism. + // 3. Use the same single mechanism to support "private" and "user" data. + // 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) + // 5. Avoid exposing implementation details on user objects (eg. expando properties) + // 6. Provide a clear path for implementation upgrade to WeakMap in 2014 + + var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /[A-Z]/g; + + function getData(data) { + if (data === "true") { + return true; + } + + if (data === "false") { + return false; + } + + if (data === "null") { + return null; + } + + // Only convert to a number if it doesn't change the string + if (data === +data + "") { + return +data; + } + + if (rbrace.test(data)) { + return JSON.parse(data); + } + + return data; + } + + function dataAttr(elem, key, data) { + var name; + + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if (data === undefined && elem.nodeType === 1) { + name = "data-" + key.replace(rmultiDash, "-$&").toLowerCase(); + data = elem.getAttribute(name); + + if (typeof data === "string") { + try { + data = getData(data); + } catch (e) {} + + // Make sure we set the data so it isn't changed later + dataUser.set(elem, key, data); + } else { + data = undefined; + } + } + return data; + } + + jQuery.extend({ + hasData: function (elem) { + return dataUser.hasData(elem) || dataPriv.hasData(elem); + }, + + data: function (elem, name, data) { + return dataUser.access(elem, name, data); + }, + + removeData: function (elem, name) { + dataUser.remove(elem, name); + }, + + // TODO: Now that all calls to _data and _removeData have been replaced + // with direct calls to dataPriv methods, these can be deprecated. + _data: function (elem, name, data) { + return dataPriv.access(elem, name, data); + }, + + _removeData: function (elem, name) { + dataPriv.remove(elem, name); + } + }); + + jQuery.fn.extend({ + data: function (key, value) { + var i, name, data, + elem = this[0], + attrs = elem && elem.attributes; + + // Gets all values + if (key === undefined) { + if (this.length) { + data = dataUser.get(elem); + + if (elem.nodeType === 1 && !dataPriv.get(elem, "hasDataAttrs")) { + i = attrs.length; + while (i--) { + + // Support: IE 11 only + // The attrs elements can be null (#14894) + if (attrs[i]) { + name = attrs[i].name; + if (name.indexOf("data-") === 0) { + name = camelCase(name.slice(5)); + dataAttr(elem, name, data[name]); + } + } + } + dataPriv.set(elem, "hasDataAttrs", true); + } + } + + return data; + } + + // Sets multiple values + if (typeof key === "object") { + return this.each(function () { + dataUser.set(this, key); + }); + } + + return access(this, function (value) { + var data; + + // The calling jQuery object (element matches) is not empty + // (and therefore has an element appears at this[ 0 ]) and the + // `value` parameter was not undefined. An empty jQuery object + // will result in `undefined` for elem = this[ 0 ] which will + // throw an exception if an attempt to read a data cache is made. + if (elem && value === undefined) { + + // Attempt to get data from the cache + // The key will always be camelCased in Data + data = dataUser.get(elem, key); + if (data !== undefined) { + return data; + } + + // Attempt to "discover" the data in + // HTML5 custom data-* attrs + data = dataAttr(elem, key); + if (data !== undefined) { + return data; + } + + // We tried really hard, but the data doesn't exist. + return; + } + + // Set the data... + this.each(function () { + + // We always store the camelCased key + dataUser.set(this, key, value); + }); + }, null, value, arguments.length > 1, null, true); + }, + + removeData: function (key) { + return this.each(function () { + dataUser.remove(this, key); + }); + } + }); + + + jQuery.extend({ + queue: function (elem, type, data) { + var queue; + + if (elem) { + type = (type || "fx") + "queue"; + queue = dataPriv.get(elem, type); + + // Speed up dequeue by getting out quickly if this is just a lookup + if (data) { + if (!queue || Array.isArray(data)) { + queue = dataPriv.access(elem, type, jQuery.makeArray(data)); + } else { + queue.push(data); + } + } + return queue || []; + } + }, + + dequeue: function (elem, type) { + type = type || "fx"; + + var queue = jQuery.queue(elem, type), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks(elem, type), + next = function () { + jQuery.dequeue(elem, type); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if (fn === "inprogress") { + fn = queue.shift(); + startLength--; + } + + if (fn) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if (type === "fx") { + queue.unshift("inprogress"); + } + + // Clear up the last queue stop function + delete hooks.stop; + fn.call(elem, next, hooks); + } + + if (!startLength && hooks) { + hooks.empty.fire(); + } + }, + + // Not public - generate a queueHooks object, or return the current one + _queueHooks: function (elem, type) { + var key = type + "queueHooks"; + return dataPriv.get(elem, key) || dataPriv.access(elem, key, { + empty: jQuery.Callbacks("once memory").add(function () { + dataPriv.remove(elem, [type + "queue", key]); + }) + }); + } + }); + + jQuery.fn.extend({ + queue: function (type, data) { + var setter = 2; + + if (typeof type !== "string") { + data = type; + type = "fx"; + setter--; + } + + if (arguments.length < setter) { + return jQuery.queue(this[0], type); + } + + return data === undefined ? + this : + this.each(function () { + var queue = jQuery.queue(this, type, data); + + // Ensure a hooks for this queue + jQuery._queueHooks(this, type); + + if (type === "fx" && queue[0] !== "inprogress") { + jQuery.dequeue(this, type); + } + }); + }, + dequeue: function (type) { + return this.each(function () { + jQuery.dequeue(this, type); + }); + }, + clearQueue: function (type) { + return this.queue(type || "fx", []); + }, + + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function (type, obj) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function () { + if (!(--count)) { + defer.resolveWith(elements, [elements]); + } + }; + + if (typeof type !== "string") { + obj = type; + type = undefined; + } + type = type || "fx"; + + while (i--) { + tmp = dataPriv.get(elements[i], type + "queueHooks"); + if (tmp && tmp.empty) { + count++; + tmp.empty.add(resolve); + } + } + resolve(); + return defer.promise(obj); + } + }); + var pnum = (/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/).source; + + var rcssNum = new RegExp("^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i"); + + + var cssExpand = ["Top", "Right", "Bottom", "Left"]; + + var isHiddenWithinTree = function (elem, el) { + + // isHiddenWithinTree might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + + // Inline style trumps all + return elem.style.display === "none" || + elem.style.display === "" && + + // Otherwise, check computed style + // Support: Firefox <=43 - 45 + // Disconnected elements can have computed display: none, so first confirm that elem is + // in the document. + jQuery.contains(elem.ownerDocument, elem) && + + jQuery.css(elem, "display") === "none"; + }; + + var swap = function (elem, options, callback, args) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for (name in options) { + old[name] = elem.style[name]; + elem.style[name] = options[name]; + } + + ret = callback.apply(elem, args || []); + + // Revert the old values + for (name in options) { + elem.style[name] = old[name]; + } + + return ret; + }; + + + + + function adjustCSS(elem, prop, valueParts, tween) { + var adjusted, scale, + maxIterations = 20, + currentValue = tween ? + function () { + return tween.cur(); + } : + function () { + return jQuery.css(elem, prop, ""); + }, + initial = currentValue(), + unit = valueParts && valueParts[3] || (jQuery.cssNumber[prop] ? "" : "px"), + + // Starting value computation is required for potential unit mismatches + initialInUnit = (jQuery.cssNumber[prop] || unit !== "px" && +initial) && + rcssNum.exec(jQuery.css(elem, prop)); + + if (initialInUnit && initialInUnit[3] !== unit) { + + // Support: Firefox <=54 + // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) + initial = initial / 2; + + // Trust units reported by jQuery.css + unit = unit || initialInUnit[3]; + + // Iteratively approximate from a nonzero starting point + initialInUnit = +initial || 1; + + while (maxIterations--) { + + // Evaluate and update our best guess (doubling guesses that zero out). + // Finish if the scale equals or crosses 1 (making the old*new product non-positive). + jQuery.style(elem, prop, initialInUnit + unit); + if ((1 - scale) * (1 - (scale = currentValue() / initial || 0.5)) <= 0) { + maxIterations = 0; + } + initialInUnit = initialInUnit / scale; + + } + + initialInUnit = initialInUnit * 2; + jQuery.style(elem, prop, initialInUnit + unit); + + // Make sure we update the tween properties later on + valueParts = valueParts || []; + } + + if (valueParts) { + initialInUnit = +initialInUnit || +initial || 0; + + // Apply relative offset (+=/-=) if specified + adjusted = valueParts[1] ? + initialInUnit + (valueParts[1] + 1) * valueParts[2] : + +valueParts[2]; + if (tween) { + tween.unit = unit; + tween.start = initialInUnit; + tween.end = adjusted; + } + } + return adjusted; + } + + + var defaultDisplayMap = {}; + + function getDefaultDisplay(elem) { + var temp, + doc = elem.ownerDocument, + nodeName = elem.nodeName, + display = defaultDisplayMap[nodeName]; + + if (display) { + return display; + } + + temp = doc.body.appendChild(doc.createElement(nodeName)); + display = jQuery.css(temp, "display"); + + temp.parentNode.removeChild(temp); + + if (display === "none") { + display = "block"; + } + defaultDisplayMap[nodeName] = display; + + return display; + } + + function showHide(elements, show) { + var display, elem, + values = [], + index = 0, + length = elements.length; + + // Determine new display value for elements that need to change + for (; index < length; index++) { + elem = elements[index]; + if (!elem.style) { + continue; + } + + display = elem.style.display; + if (show) { + + // Since we force visibility upon cascade-hidden elements, an immediate (and slow) + // check is required in this first loop unless we have a nonempty display value (either + // inline or about-to-be-restored) + if (display === "none") { + values[index] = dataPriv.get(elem, "display") || null; + if (!values[index]) { + elem.style.display = ""; + } + } + if (elem.style.display === "" && isHiddenWithinTree(elem)) { + values[index] = getDefaultDisplay(elem); + } + } else { + if (display !== "none") { + values[index] = "none"; + + // Remember what we're overwriting + dataPriv.set(elem, "display", display); + } + } + } + + // Set the display of the elements in a second loop to avoid constant reflow + for (index = 0; index < length; index++) { + if (values[index] != null) { + elements[index].style.display = values[index]; + } + } + + return elements; + } + + jQuery.fn.extend({ + show: function () { + return showHide(this, true); + }, + hide: function () { + return showHide(this); + }, + toggle: function (state) { + if (typeof state === "boolean") { + return state ? this.show() : this.hide(); + } + + return this.each(function () { + if (isHiddenWithinTree(this)) { + jQuery(this).show(); + } else { + jQuery(this).hide(); + } + }); + } + }); + var rcheckableType = (/^(?:checkbox|radio)$/i); + + var rtagName = (/<([a-z][^\/\0>\x20\t\r\n\f]+)/i); + + var rscriptType = (/^$|^module$|\/(?:java|ecma)script/i); + + + + // We have to close these tags to support XHTML (#13200) + var wrapMap = { + + // Support: IE <=9 only + option: [1, ""], + + // XHTML parsers do not magically insert elements in the + // same way that tag soup parsers do. So we cannot shorten + // this by omitting or other required elements. + thead: [1, "", "
    "], + col: [2, "", "
    "], + tr: [2, "", "
    "], + td: [3, "", "
    "], + + _default: [0, "", ""] + }; + + // Support: IE <=9 only + wrapMap.optgroup = wrapMap.option; + + wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; + wrapMap.th = wrapMap.td; + + + function getAll(context, tag) { + + // Support: IE <=9 - 11 only + // Use typeof to avoid zero-argument method invocation on host objects (#15151) + var ret; + + if (typeof context.getElementsByTagName !== "undefined") { + ret = context.getElementsByTagName(tag || "*"); + + } else if (typeof context.querySelectorAll !== "undefined") { + ret = context.querySelectorAll(tag || "*"); + + } else { + ret = []; + } + + if (tag === undefined || tag && nodeName(context, tag)) { + return jQuery.merge([context], ret); + } + + return ret; + } + + + // Mark scripts as having already been evaluated + function setGlobalEval(elems, refElements) { + var i = 0, + l = elems.length; + + for (; i < l; i++) { + dataPriv.set( + elems[i], + "globalEval", + !refElements || dataPriv.get(refElements[i], "globalEval") + ); + } + } + + + var rhtml = /<|&#?\w+;/; + + function buildFragment(elems, context, scripts, selection, ignored) { + var elem, tmp, tag, wrap, contains, j, + fragment = context.createDocumentFragment(), + nodes = [], + i = 0, + l = elems.length; + + for (; i < l; i++) { + elem = elems[i]; + + if (elem || elem === 0) { + + // Add nodes directly + if (toType(elem) === "object") { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge(nodes, elem.nodeType ? [elem] : elem); + + // Convert non-html into a text node + } else if (!rhtml.test(elem)) { + nodes.push(context.createTextNode(elem)); + + // Convert html into DOM nodes + } else { + tmp = tmp || fragment.appendChild(context.createElement("div")); + + // Deserialize a standard representation + tag = (rtagName.exec(elem) || ["", ""])[1].toLowerCase(); + wrap = wrapMap[tag] || wrapMap._default; + tmp.innerHTML = wrap[1] + jQuery.htmlPrefilter(elem) + wrap[2]; + + // Descend through wrappers to the right content + j = wrap[0]; + while (j--) { + tmp = tmp.lastChild; + } + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge(nodes, tmp.childNodes); + + // Remember the top-level container + tmp = fragment.firstChild; + + // Ensure the created nodes are orphaned (#12392) + tmp.textContent = ""; + } + } + } + + // Remove wrapper from fragment + fragment.textContent = ""; + + i = 0; + while ((elem = nodes[i++])) { + + // Skip elements already in the context collection (trac-4087) + if (selection && jQuery.inArray(elem, selection) > -1) { + if (ignored) { + ignored.push(elem); + } + continue; + } + + contains = jQuery.contains(elem.ownerDocument, elem); + + // Append to fragment + tmp = getAll(fragment.appendChild(elem), "script"); + + // Preserve script evaluation history + if (contains) { + setGlobalEval(tmp); + } + + // Capture executables + if (scripts) { + j = 0; + while ((elem = tmp[j++])) { + if (rscriptType.test(elem.type || "")) { + scripts.push(elem); + } + } + } + } + + return fragment; + } + + + (function () { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild(document.createElement("div")), + input = document.createElement("input"); + + // Support: Android 4.0 - 4.3 only + // Check state lost if the name is set (#11217) + // Support: Windows Web Apps (WWA) + // `name` and `type` must use .setAttribute for WWA (#14901) + input.setAttribute("type", "radio"); + input.setAttribute("checked", "checked"); + input.setAttribute("name", "t"); + + div.appendChild(input); + + // Support: Android <=4.1 only + // Older WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode(true).cloneNode(true).lastChild.checked; + + // Support: IE <=11 only + // Make sure textarea (and checkbox) defaultValue is properly cloned + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode(true).lastChild.defaultValue; + })(); + var documentElement = document.documentElement; + + + + var + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)/; + + function returnTrue() { + return true; + } + + function returnFalse() { + return false; + } + + // Support: IE <=9 only + // See #13393 for more info + function safeActiveElement() { + try { + return document.activeElement; + } catch (err) {} + } + + function on(elem, types, selector, data, fn, one) { + var origFn, type; + + // Types can be a map of types/handlers + if (typeof types === "object") { + + // ( types-Object, selector, data ) + if (typeof selector !== "string") { + + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for (type in types) { + on(elem, type, selector, data, types[type], one); + } + return elem; + } + + if (data == null && fn == null) { + + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if (fn == null) { + if (typeof selector === "string") { + + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if (fn === false) { + fn = returnFalse; + } else if (!fn) { + return elem; + } + + if (one === 1) { + origFn = fn; + fn = function (event) { + + // Can use an empty set, since event contains the info + jQuery().off(event); + return origFn.apply(this, arguments); + }; + + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || (origFn.guid = jQuery.guid++); + } + return elem.each(function () { + jQuery.event.add(this, types, fn, data, selector); + }); + } + + /* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ + jQuery.event = { + + global: {}, + + add: function (elem, types, handler, data, selector) { + + var handleObjIn, eventHandle, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.get(elem); + + // Don't attach events to noData or text/comment nodes (but allow plain objects) + if (!elemData) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if (handler.handler) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Ensure that invalid selectors throw exceptions at attach time + // Evaluate against documentElement in case elem is a non-element node (e.g., document) + if (selector) { + jQuery.find.matchesSelector(documentElement, selector); + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if (!handler.guid) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if (!(events = elemData.events)) { + events = elemData.events = {}; + } + if (!(eventHandle = elemData.handle)) { + eventHandle = elemData.handle = function (e) { + + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? + jQuery.event.dispatch.apply(elem, arguments) : undefined; + }; + } + + // Handle multiple events separated by a space + types = (types || "").match(rnothtmlwhite) || [""]; + t = types.length; + while (t--) { + tmp = rtypenamespace.exec(types[t]) || []; + type = origType = tmp[1]; + namespaces = (tmp[2] || "").split(".").sort(); + + // There *must* be a type, no attaching namespace-only handlers + if (!type) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[type] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = (selector ? special.delegateType : special.bindType) || type; + + // Update special based on newly reset type + special = jQuery.event.special[type] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend({ + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test(selector), + namespace: namespaces.join(".") + }, handleObjIn); + + // Init the event handler queue if we're the first + if (!(handlers = events[type])) { + handlers = events[type] = []; + handlers.delegateCount = 0; + + // Only use addEventListener if the special events handler returns false + if (!special.setup || + special.setup.call(elem, data, namespaces, eventHandle) === false) { + + if (elem.addEventListener) { + elem.addEventListener(type, eventHandle); + } + } + } + + if (special.add) { + special.add.call(elem, handleObj); + + if (!handleObj.handler.guid) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if (selector) { + handlers.splice(handlers.delegateCount++, 0, handleObj); + } else { + handlers.push(handleObj); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[type] = true; + } + + }, + + // Detach an event or set of events from an element + remove: function (elem, types, handler, selector, mappedTypes) { + + var j, origCount, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.hasData(elem) && dataPriv.get(elem); + + if (!elemData || !(events = elemData.events)) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = (types || "").match(rnothtmlwhite) || [""]; + t = types.length; + while (t--) { + tmp = rtypenamespace.exec(types[t]) || []; + type = origType = tmp[1]; + namespaces = (tmp[2] || "").split(".").sort(); + + // Unbind all events (on this namespace, if provided) for the element + if (!type) { + for (type in events) { + jQuery.event.remove(elem, type + types[t], handler, selector, true); + } + continue; + } + + special = jQuery.event.special[type] || {}; + type = (selector ? special.delegateType : special.bindType) || type; + handlers = events[type] || []; + tmp = tmp[2] && + new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)"); + + // Remove matching events + origCount = j = handlers.length; + while (j--) { + handleObj = handlers[j]; + + if ((mappedTypes || origType === handleObj.origType) && + (!handler || handler.guid === handleObj.guid) && + (!tmp || tmp.test(handleObj.namespace)) && + (!selector || selector === handleObj.selector || + selector === "**" && handleObj.selector)) { + handlers.splice(j, 1); + + if (handleObj.selector) { + handlers.delegateCount--; + } + if (special.remove) { + special.remove.call(elem, handleObj); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if (origCount && !handlers.length) { + if (!special.teardown || + special.teardown.call(elem, namespaces, elemData.handle) === false) { + + jQuery.removeEvent(elem, type, elemData.handle); + } + + delete events[type]; + } + } + + // Remove data and the expando if it's no longer used + if (jQuery.isEmptyObject(events)) { + dataPriv.remove(elem, "handle events"); + } + }, + + dispatch: function (nativeEvent) { + + // Make a writable jQuery.Event from the native event object + var event = jQuery.event.fix(nativeEvent); + + var i, j, ret, matched, handleObj, handlerQueue, + args = new Array(arguments.length), + handlers = (dataPriv.get(this, "events") || {})[event.type] || [], + special = jQuery.event.special[event.type] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[0] = event; + + for (i = 1; i < arguments.length; i++) { + args[i] = arguments[i]; + } + + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if (special.preDispatch && special.preDispatch.call(this, event) === false) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call(this, event, handlers); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ((matched = handlerQueue[i++]) && !event.isPropagationStopped()) { + event.currentTarget = matched.elem; + + j = 0; + while ((handleObj = matched.handlers[j++]) && + !event.isImmediatePropagationStopped()) { + + // Triggered event must either 1) have no namespace, or 2) have namespace(s) + // a subset or equal to those in the bound event (both can have no namespace). + if (!event.rnamespace || event.rnamespace.test(handleObj.namespace)) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ((jQuery.event.special[handleObj.origType] || {}).handle || + handleObj.handler).apply(matched.elem, args); + + if (ret !== undefined) { + if ((event.result = ret) === false) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if (special.postDispatch) { + special.postDispatch.call(this, event); + } + + return event.result; + }, + + handlers: function (event, handlers) { + var i, handleObj, sel, matchedHandlers, matchedSelectors, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + if (delegateCount && + + // Support: IE <=9 + // Black-hole SVG instance trees (trac-13180) + cur.nodeType && + + // Support: Firefox <=42 + // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) + // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click + // Support: IE 11 only + // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) + !(event.type === "click" && event.button >= 1)) { + + for (; cur !== this; cur = cur.parentNode || this) { + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if (cur.nodeType === 1 && !(event.type === "click" && cur.disabled === true)) { + matchedHandlers = []; + matchedSelectors = {}; + for (i = 0; i < delegateCount; i++) { + handleObj = handlers[i]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if (matchedSelectors[sel] === undefined) { + matchedSelectors[sel] = handleObj.needsContext ? + jQuery(sel, this).index(cur) > -1 : + jQuery.find(sel, this, null, [cur]).length; + } + if (matchedSelectors[sel]) { + matchedHandlers.push(handleObj); + } + } + if (matchedHandlers.length) { + handlerQueue.push({ + elem: cur, + handlers: matchedHandlers + }); + } + } + } + } + + // Add the remaining (directly-bound) handlers + cur = this; + if (delegateCount < handlers.length) { + handlerQueue.push({ + elem: cur, + handlers: handlers.slice(delegateCount) + }); + } + + return handlerQueue; + }, + + addProp: function (name, hook) { + Object.defineProperty(jQuery.Event.prototype, name, { + enumerable: true, + configurable: true, + + get: isFunction(hook) ? + function () { + if (this.originalEvent) { + return hook(this.originalEvent); + } + } : function () { + if (this.originalEvent) { + return this.originalEvent[name]; + } + }, + + set: function (value) { + Object.defineProperty(this, name, { + enumerable: true, + configurable: true, + writable: true, + value: value + }); + } + }); + }, + + fix: function (originalEvent) { + return originalEvent[jQuery.expando] ? + originalEvent : + new jQuery.Event(originalEvent); + }, + + special: { + load: { + + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + focus: { + + // Fire native event if possible so blur/focus sequence is correct + trigger: function () { + if (this !== safeActiveElement() && this.focus) { + this.focus(); + return false; + } + }, + delegateType: "focusin" + }, + blur: { + trigger: function () { + if (this === safeActiveElement() && this.blur) { + this.blur(); + return false; + } + }, + delegateType: "focusout" + }, + click: { + + // For checkbox, fire native event so checked state will be right + trigger: function () { + if (this.type === "checkbox" && this.click && nodeName(this, "input")) { + this.click(); + return false; + } + }, + + // For cross-browser consistency, don't fire native .click() on links + _default: function (event) { + return nodeName(event.target, "a"); + } + }, + + beforeunload: { + postDispatch: function (event) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if (event.result !== undefined && event.originalEvent) { + event.originalEvent.returnValue = event.result; + } + } + } + } + }; + + jQuery.removeEvent = function (elem, type, handle) { + + // This "if" is needed for plain objects + if (elem.removeEventListener) { + elem.removeEventListener(type, handle); + } + }; + + jQuery.Event = function (src, props) { + + // Allow instantiation without the 'new' keyword + if (!(this instanceof jQuery.Event)) { + return new jQuery.Event(src, props); + } + + // Event object + if (src && src.type) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && + + // Support: Android <=2.3 only + src.returnValue === false ? + returnTrue : + returnFalse; + + // Create target properties + // Support: Safari <=6 - 7 only + // Target should not be a text node (#504, #13143) + this.target = (src.target && src.target.nodeType === 3) ? + src.target.parentNode : + src.target; + + this.currentTarget = src.currentTarget; + this.relatedTarget = src.relatedTarget; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if (props) { + jQuery.extend(this, props); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || Date.now(); + + // Mark it as fixed + this[jQuery.expando] = true; + }; + + // jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding + // https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html + jQuery.Event.prototype = { + constructor: jQuery.Event, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + isSimulated: false, + + preventDefault: function () { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if (e && !this.isSimulated) { + e.preventDefault(); + } + }, + stopPropagation: function () { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if (e && !this.isSimulated) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function () { + var e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if (e && !this.isSimulated) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } + }; + + // Includes all common event props including KeyEvent and MouseEvent specific props + jQuery.each({ + altKey: true, + bubbles: true, + cancelable: true, + changedTouches: true, + ctrlKey: true, + detail: true, + eventPhase: true, + metaKey: true, + pageX: true, + pageY: true, + shiftKey: true, + view: true, + "char": true, + charCode: true, + key: true, + keyCode: true, + button: true, + buttons: true, + clientX: true, + clientY: true, + offsetX: true, + offsetY: true, + pointerId: true, + pointerType: true, + screenX: true, + screenY: true, + targetTouches: true, + toElement: true, + touches: true, + + which: function (event) { + var button = event.button; + + // Add which for key events + if (event.which == null && rkeyEvent.test(event.type)) { + return event.charCode != null ? event.charCode : event.keyCode; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + if (!event.which && button !== undefined && rmouseEvent.test(event.type)) { + if (button & 1) { + return 1; + } + + if (button & 2) { + return 3; + } + + if (button & 4) { + return 2; + } + + return 0; + } + + return event.which; + } + }, jQuery.event.addProp); + + // Create mouseenter/leave events using mouseover/out and event-time checks + // so that event delegation works in jQuery. + // Do the same for pointerenter/pointerleave and pointerover/pointerout + // + // Support: Safari 7 only + // Safari sends mouseenter too often; see: + // https://bugs.chromium.org/p/chromium/issues/detail?id=470258 + // for the description of the bug (it existed in older Chrome versions as well). + jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" + }, function (orig, fix) { + jQuery.event.special[orig] = { + delegateType: fix, + bindType: fix, + + handle: function (event) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mouseenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if (!related || (related !== target && !jQuery.contains(target, related))) { + event.type = handleObj.origType; + ret = handleObj.handler.apply(this, arguments); + event.type = fix; + } + return ret; + } + }; + }); + + jQuery.fn.extend({ + + on: function (types, selector, data, fn) { + return on(this, types, selector, data, fn); + }, + one: function (types, selector, data, fn) { + return on(this, types, selector, data, fn, 1); + }, + off: function (types, selector, fn) { + var handleObj, type; + if (types && types.preventDefault && types.handleObj) { + + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery(types.delegateTarget).off( + handleObj.namespace ? + handleObj.origType + "." + handleObj.namespace : + handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if (typeof types === "object") { + + // ( types-object [, selector] ) + for (type in types) { + this.off(type, selector, types[type]); + } + return this; + } + if (selector === false || typeof selector === "function") { + + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if (fn === false) { + fn = returnFalse; + } + return this.each(function () { + jQuery.event.remove(this, types, fn, selector); + }); + } + }); + + + var + + /* eslint-disable max-len */ + + // See https://github.com/eslint/eslint/issues/3229 + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi, + + /* eslint-enable */ + + // Support: IE <=10 - 11, Edge 12 - 13 only + // In IE/Edge using regex groups here causes severe slowdowns. + // See https://connect.microsoft.com/IE/feedback/details/1736512/ + rnoInnerhtml = /\s*$/g; + + // Prefer a tbody over its parent table for containing new rows + function manipulationTarget(elem, content) { + if (nodeName(elem, "table") && + nodeName(content.nodeType !== 11 ? content : content.firstChild, "tr")) { + + return jQuery(elem).children("tbody")[0] || elem; + } + + return elem; + } + + // Replace/restore the type attribute of script elements for safe DOM manipulation + function disableScript(elem) { + elem.type = (elem.getAttribute("type") !== null) + "/" + elem.type; + return elem; + } + + function restoreScript(elem) { + if ((elem.type || "").slice(0, 5) === "true/") { + elem.type = elem.type.slice(5); + } else { + elem.removeAttribute("type"); + } + + return elem; + } + + function cloneCopyEvent(src, dest) { + var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; + + if (dest.nodeType !== 1) { + return; + } + + // 1. Copy private data: events, handlers, etc. + if (dataPriv.hasData(src)) { + pdataOld = dataPriv.access(src); + pdataCur = dataPriv.set(dest, pdataOld); + events = pdataOld.events; + + if (events) { + delete pdataCur.handle; + pdataCur.events = {}; + + for (type in events) { + for (i = 0, l = events[type].length; i < l; i++) { + jQuery.event.add(dest, type, events[type][i]); + } + } + } + } + + // 2. Copy user data + if (dataUser.hasData(src)) { + udataOld = dataUser.access(src); + udataCur = jQuery.extend({}, udataOld); + + dataUser.set(dest, udataCur); + } + } + + // Fix IE bugs, see support tests + function fixInput(src, dest) { + var nodeName = dest.nodeName.toLowerCase(); + + // Fails to persist the checked state of a cloned checkbox or radio button. + if (nodeName === "input" && rcheckableType.test(src.type)) { + dest.checked = src.checked; + + // Fails to return the selected option to the default selected state when cloning options + } else if (nodeName === "input" || nodeName === "textarea") { + dest.defaultValue = src.defaultValue; + } + } + + function domManip(collection, args, callback, ignored) { + + // Flatten any nested arrays + args = concat.apply([], args); + + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = collection.length, + iNoClone = l - 1, + value = args[0], + valueIsFunction = isFunction(value); + + // We can't cloneNode fragments that contain checked, in WebKit + if (valueIsFunction || + (l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test(value))) { + return collection.each(function (index) { + var self = collection.eq(index); + if (valueIsFunction) { + args[0] = value.call(this, index, self.html()); + } + domManip(self, args, callback, ignored); + }); + } + + if (l) { + fragment = buildFragment(args, collection[0].ownerDocument, false, collection, ignored); + first = fragment.firstChild; + + if (fragment.childNodes.length === 1) { + fragment = first; + } + + // Require either new content or an interest in ignored elements to invoke the callback + if (first || ignored) { + scripts = jQuery.map(getAll(fragment, "script"), disableScript); + hasScripts = scripts.length; + + // Use the original fragment for the last item + // instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for (; i < l; i++) { + node = fragment; + + if (i !== iNoClone) { + node = jQuery.clone(node, true, true); + + // Keep references to cloned scripts for later restoration + if (hasScripts) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge(scripts, getAll(node, "script")); + } + } + + callback.call(collection[i], node, i); + } + + if (hasScripts) { + doc = scripts[scripts.length - 1].ownerDocument; + + // Reenable scripts + jQuery.map(scripts, restoreScript); + + // Evaluate executable scripts on first document insertion + for (i = 0; i < hasScripts; i++) { + node = scripts[i]; + if (rscriptType.test(node.type || "") && + !dataPriv.access(node, "globalEval") && + jQuery.contains(doc, node)) { + + if (node.src && (node.type || "").toLowerCase() !== "module") { + + // Optional AJAX dependency, but won't run scripts if not present + if (jQuery._evalUrl) { + jQuery._evalUrl(node.src); + } + } else { + DOMEval(node.textContent.replace(rcleanScript, ""), doc, node); + } + } + } + } + } + } + + return collection; + } + + function remove(elem, selector, keepData) { + var node, + nodes = selector ? jQuery.filter(selector, elem) : elem, + i = 0; + + for (; + (node = nodes[i]) != null; i++) { + if (!keepData && node.nodeType === 1) { + jQuery.cleanData(getAll(node)); + } + + if (node.parentNode) { + if (keepData && jQuery.contains(node.ownerDocument, node)) { + setGlobalEval(getAll(node, "script")); + } + node.parentNode.removeChild(node); + } + } + + return elem; + } + + jQuery.extend({ + htmlPrefilter: function (html) { + return html.replace(rxhtmlTag, "<$1>"); + }, + + clone: function (elem, dataAndEvents, deepDataAndEvents) { + var i, l, srcElements, destElements, + clone = elem.cloneNode(true), + inPage = jQuery.contains(elem.ownerDocument, elem); + + // Fix IE cloning issues + if (!support.noCloneChecked && (elem.nodeType === 1 || elem.nodeType === 11) && + !jQuery.isXMLDoc(elem)) { + + // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 + destElements = getAll(clone); + srcElements = getAll(elem); + + for (i = 0, l = srcElements.length; i < l; i++) { + fixInput(srcElements[i], destElements[i]); + } + } + + // Copy the events from the original to the clone + if (dataAndEvents) { + if (deepDataAndEvents) { + srcElements = srcElements || getAll(elem); + destElements = destElements || getAll(clone); + + for (i = 0, l = srcElements.length; i < l; i++) { + cloneCopyEvent(srcElements[i], destElements[i]); + } + } else { + cloneCopyEvent(elem, clone); + } + } + + // Preserve script evaluation history + destElements = getAll(clone, "script"); + if (destElements.length > 0) { + setGlobalEval(destElements, !inPage && getAll(elem, "script")); + } + + // Return the cloned set + return clone; + }, + + cleanData: function (elems) { + var data, elem, type, + special = jQuery.event.special, + i = 0; + + for (; + (elem = elems[i]) !== undefined; i++) { + if (acceptData(elem)) { + if ((data = elem[dataPriv.expando])) { + if (data.events) { + for (type in data.events) { + if (special[type]) { + jQuery.event.remove(elem, type); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent(elem, type, data.handle); + } + } + } + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[dataPriv.expando] = undefined; + } + if (elem[dataUser.expando]) { + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[dataUser.expando] = undefined; + } + } + } + } + }); + + jQuery.fn.extend({ + detach: function (selector) { + return remove(this, selector, true); + }, + + remove: function (selector) { + return remove(this, selector); + }, + + text: function (value) { + return access(this, function (value) { + return value === undefined ? + jQuery.text(this) : + this.empty().each(function () { + if (this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9) { + this.textContent = value; + } + }); + }, null, value, arguments.length); + }, + + append: function () { + return domManip(this, arguments, function (elem) { + if (this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9) { + var target = manipulationTarget(this, elem); + target.appendChild(elem); + } + }); + }, + + prepend: function () { + return domManip(this, arguments, function (elem) { + if (this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9) { + var target = manipulationTarget(this, elem); + target.insertBefore(elem, target.firstChild); + } + }); + }, + + before: function () { + return domManip(this, arguments, function (elem) { + if (this.parentNode) { + this.parentNode.insertBefore(elem, this); + } + }); + }, + + after: function () { + return domManip(this, arguments, function (elem) { + if (this.parentNode) { + this.parentNode.insertBefore(elem, this.nextSibling); + } + }); + }, + + empty: function () { + var elem, + i = 0; + + for (; + (elem = this[i]) != null; i++) { + if (elem.nodeType === 1) { + + // Prevent memory leaks + jQuery.cleanData(getAll(elem, false)); + + // Remove any remaining nodes + elem.textContent = ""; + } + } + + return this; + }, + + clone: function (dataAndEvents, deepDataAndEvents) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map(function () { + return jQuery.clone(this, dataAndEvents, deepDataAndEvents); + }); + }, + + html: function (value) { + return access(this, function (value) { + var elem = this[0] || {}, + i = 0, + l = this.length; + + if (value === undefined && elem.nodeType === 1) { + return elem.innerHTML; + } + + // See if we can take a shortcut and just use innerHTML + if (typeof value === "string" && !rnoInnerhtml.test(value) && + !wrapMap[(rtagName.exec(value) || ["", ""])[1].toLowerCase()]) { + + value = jQuery.htmlPrefilter(value); + + try { + for (; i < l; i++) { + elem = this[i] || {}; + + // Remove element nodes and prevent memory leaks + if (elem.nodeType === 1) { + jQuery.cleanData(getAll(elem, false)); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch (e) {} + } + + if (elem) { + this.empty().append(value); + } + }, null, value, arguments.length); + }, + + replaceWith: function () { + var ignored = []; + + // Make the changes, replacing each non-ignored context element with the new content + return domManip(this, arguments, function (elem) { + var parent = this.parentNode; + + if (jQuery.inArray(this, ignored) < 0) { + jQuery.cleanData(getAll(this)); + if (parent) { + parent.replaceChild(elem, this); + } + } + + // Force callback invocation + }, ignored); + } + }); + + jQuery.each({ + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" + }, function (name, original) { + jQuery.fn[name] = function (selector) { + var elems, + ret = [], + insert = jQuery(selector), + last = insert.length - 1, + i = 0; + + for (; i <= last; i++) { + elems = i === last ? this : this.clone(true); + jQuery(insert[i])[original](elems); + + // Support: Android <=4.0 only, PhantomJS 1 only + // .get() because push.apply(_, arraylike) throws on ancient WebKit + push.apply(ret, elems.get()); + } + + return this.pushStack(ret); + }; + }); + var rnumnonpx = new RegExp("^(" + pnum + ")(?!px)[a-z%]+$", "i"); + + var getStyles = function (elem) { + + // Support: IE <=11 only, Firefox <=30 (#15098, #14150) + // IE throws on elements created in popups + // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" + var view = elem.ownerDocument.defaultView; + + if (!view || !view.opener) { + view = window; + } + + return view.getComputedStyle(elem); + }; + + var rboxStyle = new RegExp(cssExpand.join("|"), "i"); + + + + (function () { + + // Executing both pixelPosition & boxSizingReliable tests require only one layout + // so they're executed at the same time to save the second computation. + function computeStyleTests() { + + // This is a singleton, we need to execute it only once + if (!div) { + return; + } + + container.style.cssText = "position:absolute;left:-11111px;width:60px;" + + "margin-top:1px;padding:0;border:0"; + div.style.cssText = + "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + + "margin:auto;border:1px;padding:1px;" + + "width:60%;top:1%"; + documentElement.appendChild(container).appendChild(div); + + var divStyle = window.getComputedStyle(div); + pixelPositionVal = divStyle.top !== "1%"; + + // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 + reliableMarginLeftVal = roundPixelMeasures(divStyle.marginLeft) === 12; + + // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 + // Some styles come back with percentage values, even though they shouldn't + div.style.right = "60%"; + pixelBoxStylesVal = roundPixelMeasures(divStyle.right) === 36; + + // Support: IE 9 - 11 only + // Detect misreporting of content dimensions for box-sizing:border-box elements + boxSizingReliableVal = roundPixelMeasures(divStyle.width) === 36; + + // Support: IE 9 only + // Detect overflow:scroll screwiness (gh-3699) + div.style.position = "absolute"; + scrollboxSizeVal = div.offsetWidth === 36 || "absolute"; + + documentElement.removeChild(container); + + // Nullify the div so it wouldn't be stored in the memory and + // it will also be a sign that checks already performed + div = null; + } + + function roundPixelMeasures(measure) { + return Math.round(parseFloat(measure)); + } + + var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, + reliableMarginLeftVal, + container = document.createElement("div"), + div = document.createElement("div"); + + // Finish early in limited (non-browser) environments + if (!div.style) { + return; + } + + // Support: IE <=9 - 11 only + // Style of cloned element affects source element cloned (#8908) + div.style.backgroundClip = "content-box"; + div.cloneNode(true).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + jQuery.extend(support, { + boxSizingReliable: function () { + computeStyleTests(); + return boxSizingReliableVal; + }, + pixelBoxStyles: function () { + computeStyleTests(); + return pixelBoxStylesVal; + }, + pixelPosition: function () { + computeStyleTests(); + return pixelPositionVal; + }, + reliableMarginLeft: function () { + computeStyleTests(); + return reliableMarginLeftVal; + }, + scrollboxSize: function () { + computeStyleTests(); + return scrollboxSizeVal; + } + }); + })(); + + + function curCSS(elem, name, computed) { + var width, minWidth, maxWidth, ret, + + // Support: Firefox 51+ + // Retrieving style before computed somehow + // fixes an issue with getting wrong values + // on detached elements + style = elem.style; + + computed = computed || getStyles(elem); + + // getPropertyValue is needed for: + // .css('filter') (IE 9 only, #12537) + // .css('--customProperty) (#3144) + if (computed) { + ret = computed.getPropertyValue(name) || computed[name]; + + if (ret === "" && !jQuery.contains(elem.ownerDocument, elem)) { + ret = jQuery.style(elem, name); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Android Browser returns percentage for some values, + // but width seems to be reliably pixels. + // This is against the CSSOM draft spec: + // https://drafts.csswg.org/cssom/#resolved-values + if (!support.pixelBoxStyles() && rnumnonpx.test(ret) && rboxStyle.test(name)) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret !== undefined ? + + // Support: IE <=9 - 11 only + // IE returns zIndex value as an integer. + ret + "" : + ret; + } + + + function addGetHookIf(conditionFn, hookFn) { + + // Define the hook, we'll check on the first run if it's really needed. + return { + get: function () { + if (conditionFn()) { + + // Hook not needed (or it's not possible to use it due + // to missing dependency), remove it. + delete this.get; + return; + } + + // Hook needed; redefine it so that the support test is not executed again. + return (this.get = hookFn).apply(this, arguments); + } + }; + } + + + var + + // Swappable if display is none or starts with table + // except "table", "table-cell", or "table-caption" + // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rcustomProp = /^--/, + cssShow = { + position: "absolute", + visibility: "hidden", + display: "block" + }, + cssNormalTransform = { + letterSpacing: "0", + fontWeight: "400" + }, + + cssPrefixes = ["Webkit", "Moz", "ms"], + emptyStyle = document.createElement("div").style; + + // Return a css property mapped to a potentially vendor prefixed property + function vendorPropName(name) { + + // Shortcut for names that are not vendor prefixed + if (name in emptyStyle) { + return name; + } + + // Check for vendor prefixed names + var capName = name[0].toUpperCase() + name.slice(1), + i = cssPrefixes.length; + + while (i--) { + name = cssPrefixes[i] + capName; + if (name in emptyStyle) { + return name; + } + } + } + + // Return a property mapped along what jQuery.cssProps suggests or to + // a vendor prefixed property. + function finalPropName(name) { + var ret = jQuery.cssProps[name]; + if (!ret) { + ret = jQuery.cssProps[name] = vendorPropName(name) || name; + } + return ret; + } + + function setPositiveNumber(elem, value, subtract) { + + // Any relative (+/-) values have already been + // normalized at this point + var matches = rcssNum.exec(value); + return matches ? + + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max(0, matches[2] - (subtract || 0)) + (matches[3] || "px") : + value; + } + + function boxModelAdjustment(elem, dimension, box, isBorderBox, styles, computedVal) { + var i = dimension === "width" ? 1 : 0, + extra = 0, + delta = 0; + + // Adjustment may not be necessary + if (box === (isBorderBox ? "border" : "content")) { + return 0; + } + + for (; i < 4; i += 2) { + + // Both box models exclude margin + if (box === "margin") { + delta += jQuery.css(elem, box + cssExpand[i], true, styles); + } + + // If we get here with a content-box, we're seeking "padding" or "border" or "margin" + if (!isBorderBox) { + + // Add padding + delta += jQuery.css(elem, "padding" + cssExpand[i], true, styles); + + // For "border" or "margin", add border + if (box !== "padding") { + delta += jQuery.css(elem, "border" + cssExpand[i] + "Width", true, styles); + + // But still keep track of it otherwise + } else { + extra += jQuery.css(elem, "border" + cssExpand[i] + "Width", true, styles); + } + + // If we get here with a border-box (content + padding + border), we're seeking "content" or + // "padding" or "margin" + } else { + + // For "content", subtract padding + if (box === "content") { + delta -= jQuery.css(elem, "padding" + cssExpand[i], true, styles); + } + + // For "content" or "padding", subtract border + if (box !== "margin") { + delta -= jQuery.css(elem, "border" + cssExpand[i] + "Width", true, styles); + } + } + } + + // Account for positive content-box scroll gutter when requested by providing computedVal + if (!isBorderBox && computedVal >= 0) { + + // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border + // Assuming integer scroll gutter, subtract the rest and round down + delta += Math.max(0, Math.ceil( + elem["offset" + dimension[0].toUpperCase() + dimension.slice(1)] - + computedVal - + delta - + extra - + 0.5 + )); + } + + return delta; + } + + function getWidthOrHeight(elem, dimension, extra) { + + // Start with computed style + var styles = getStyles(elem), + val = curCSS(elem, dimension, styles), + isBorderBox = jQuery.css(elem, "boxSizing", false, styles) === "border-box", + valueIsBorderBox = isBorderBox; + + // Support: Firefox <=54 + // Return a confounding non-pixel value or feign ignorance, as appropriate. + if (rnumnonpx.test(val)) { + if (!extra) { + return val; + } + val = "auto"; + } + + // Check for style in case a browser which returns unreliable values + // for getComputedStyle silently falls back to the reliable elem.style + valueIsBorderBox = valueIsBorderBox && + (support.boxSizingReliable() || val === elem.style[dimension]); + + // Fall back to offsetWidth/offsetHeight when value is "auto" + // This happens for inline elements with no explicit setting (gh-3571) + // Support: Android <=4.1 - 4.3 only + // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) + if (val === "auto" || + !parseFloat(val) && jQuery.css(elem, "display", false, styles) === "inline") { + + val = elem["offset" + dimension[0].toUpperCase() + dimension.slice(1)]; + + // offsetWidth/offsetHeight provide border-box values + valueIsBorderBox = true; + } + + // Normalize "" and auto + val = parseFloat(val) || 0; + + // Adjust for the element's box model + return (val + + boxModelAdjustment( + elem, + dimension, + extra || (isBorderBox ? "border" : "content"), + valueIsBorderBox, + styles, + + // Provide the current computed size to request scroll gutter calculation (gh-3589) + val + ) + ) + "px"; + } + + jQuery.extend({ + + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function (elem, computed) { + if (computed) { + + // We should always get a number back from opacity + var ret = curCSS(elem, "opacity"); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Don't automatically add "px" to these possibly-unitless properties + cssNumber: { + "animationIterationCount": true, + "columnCount": true, + "fillOpacity": true, + "flexGrow": true, + "flexShrink": true, + "fontWeight": true, + "lineHeight": true, + "opacity": true, + "order": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: {}, + + // Get and set the style property on a DOM Node + style: function (elem, name, value, extra) { + + // Don't set styles on text and comment nodes + if (!elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = camelCase(name), + isCustomProp = rcustomProp.test(name), + style = elem.style; + + // Make sure that we're working with the right name. We don't + // want to query the value if it is a CSS custom property + // since they are user-defined. + if (!isCustomProp) { + name = finalPropName(origName); + } + + // Gets hook for the prefixed version, then unprefixed version + hooks = jQuery.cssHooks[name] || jQuery.cssHooks[origName]; + + // Check if we're setting a value + if (value !== undefined) { + type = typeof value; + + // Convert "+=" or "-=" to relative numbers (#7345) + if (type === "string" && (ret = rcssNum.exec(value)) && ret[1]) { + value = adjustCSS(elem, name, ret); + + // Fixes bug #9237 + type = "number"; + } + + // Make sure that null and NaN values aren't set (#7116) + if (value == null || value !== value) { + return; + } + + // If a number was passed in, add the unit (except for certain CSS properties) + if (type === "number") { + value += ret && ret[3] || (jQuery.cssNumber[origName] ? "" : "px"); + } + + // background-* props affect original clone's values + if (!support.clearCloneStyle && value === "" && name.indexOf("background") === 0) { + style[name] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if (!hooks || !("set" in hooks) || + (value = hooks.set(elem, value, extra)) !== undefined) { + + if (isCustomProp) { + style.setProperty(name, value); + } else { + style[name] = value; + } + } + + } else { + + // If a hook was provided get the non-computed value from there + if (hooks && "get" in hooks && + (ret = hooks.get(elem, false, extra)) !== undefined) { + + return ret; + } + + // Otherwise just get the value from the style object + return style[name]; + } + }, + + css: function (elem, name, extra, styles) { + var val, num, hooks, + origName = camelCase(name), + isCustomProp = rcustomProp.test(name); + + // Make sure that we're working with the right name. We don't + // want to modify the value if it is a CSS custom property + // since they are user-defined. + if (!isCustomProp) { + name = finalPropName(origName); + } + + // Try prefixed name followed by the unprefixed name + hooks = jQuery.cssHooks[name] || jQuery.cssHooks[origName]; + + // If a hook was provided get the computed value from there + if (hooks && "get" in hooks) { + val = hooks.get(elem, true, extra); + } + + // Otherwise, if a way to get the computed value exists, use that + if (val === undefined) { + val = curCSS(elem, name, styles); + } + + // Convert "normal" to computed value + if (val === "normal" && name in cssNormalTransform) { + val = cssNormalTransform[name]; + } + + // Make numeric if forced or a qualifier was provided and val looks numeric + if (extra === "" || extra) { + num = parseFloat(val); + return extra === true || isFinite(num) ? num || 0 : val; + } + + return val; + } + }); + + jQuery.each(["height", "width"], function (i, dimension) { + jQuery.cssHooks[dimension] = { + get: function (elem, computed, extra) { + if (computed) { + + // Certain elements can have dimension info if we invisibly show them + // but it must have a current display style that would benefit + return rdisplayswap.test(jQuery.css(elem, "display")) && + + // Support: Safari 8+ + // Table columns in Safari have non-zero offsetWidth & zero + // getBoundingClientRect().width unless display is changed. + // Support: IE <=11 only + // Running getBoundingClientRect on a disconnected node + // in IE throws an error. + (!elem.getClientRects().length || !elem.getBoundingClientRect().width) ? + swap(elem, cssShow, function () { + return getWidthOrHeight(elem, dimension, extra); + }) : + getWidthOrHeight(elem, dimension, extra); + } + }, + + set: function (elem, value, extra) { + var matches, + styles = getStyles(elem), + isBorderBox = jQuery.css(elem, "boxSizing", false, styles) === "border-box", + subtract = extra && boxModelAdjustment( + elem, + dimension, + extra, + isBorderBox, + styles + ); + + // Account for unreliable border-box dimensions by comparing offset* to computed and + // faking a content-box to get border and padding (gh-3699) + if (isBorderBox && support.scrollboxSize() === styles.position) { + subtract -= Math.ceil( + elem["offset" + dimension[0].toUpperCase() + dimension.slice(1)] - + parseFloat(styles[dimension]) - + boxModelAdjustment(elem, dimension, "border", false, styles) - + 0.5 + ); + } + + // Convert to pixels if value adjustment is needed + if (subtract && (matches = rcssNum.exec(value)) && + (matches[3] || "px") !== "px") { + + elem.style[dimension] = value; + value = jQuery.css(elem, dimension); + } + + return setPositiveNumber(elem, value, subtract); + } + }; + }); + + jQuery.cssHooks.marginLeft = addGetHookIf(support.reliableMarginLeft, + function (elem, computed) { + if (computed) { + return (parseFloat(curCSS(elem, "marginLeft")) || + elem.getBoundingClientRect().left - + swap(elem, { + marginLeft: 0 + }, function () { + return elem.getBoundingClientRect().left; + }) + ) + "px"; + } + } + ); + + // These hooks are used by animate to expand properties + jQuery.each({ + margin: "", + padding: "", + border: "Width" + }, function (prefix, suffix) { + jQuery.cssHooks[prefix + suffix] = { + expand: function (value) { + var i = 0, + expanded = {}, + + // Assumes a single number if not a string + parts = typeof value === "string" ? value.split(" ") : [value]; + + for (; i < 4; i++) { + expanded[prefix + cssExpand[i] + suffix] = + parts[i] || parts[i - 2] || parts[0]; + } + + return expanded; + } + }; + + if (prefix !== "margin") { + jQuery.cssHooks[prefix + suffix].set = setPositiveNumber; + } + }); + + jQuery.fn.extend({ + css: function (name, value) { + return access(this, function (elem, name, value) { + var styles, len, + map = {}, + i = 0; + + if (Array.isArray(name)) { + styles = getStyles(elem); + len = name.length; + + for (; i < len; i++) { + map[name[i]] = jQuery.css(elem, name[i], false, styles); + } + + return map; + } + + return value !== undefined ? + jQuery.style(elem, name, value) : + jQuery.css(elem, name); + }, name, value, arguments.length > 1); + } + }); + + + function Tween(elem, options, prop, end, easing) { + return new Tween.prototype.init(elem, options, prop, end, easing); + } + jQuery.Tween = Tween; + + Tween.prototype = { + constructor: Tween, + init: function (elem, options, prop, end, easing, unit) { + this.elem = elem; + this.prop = prop; + this.easing = easing || jQuery.easing._default; + this.options = options; + this.start = this.now = this.cur(); + this.end = end; + this.unit = unit || (jQuery.cssNumber[prop] ? "" : "px"); + }, + cur: function () { + var hooks = Tween.propHooks[this.prop]; + + return hooks && hooks.get ? + hooks.get(this) : + Tween.propHooks._default.get(this); + }, + run: function (percent) { + var eased, + hooks = Tween.propHooks[this.prop]; + + if (this.options.duration) { + this.pos = eased = jQuery.easing[this.easing]( + percent, this.options.duration * percent, 0, 1, this.options.duration + ); + } else { + this.pos = eased = percent; + } + this.now = (this.end - this.start) * eased + this.start; + + if (this.options.step) { + this.options.step.call(this.elem, this.now, this); + } + + if (hooks && hooks.set) { + hooks.set(this); + } else { + Tween.propHooks._default.set(this); + } + return this; + } + }; + + Tween.prototype.init.prototype = Tween.prototype; + + Tween.propHooks = { + _default: { + get: function (tween) { + var result; + + // Use a property on the element directly when it is not a DOM element, + // or when there is no matching style property that exists. + if (tween.elem.nodeType !== 1 || + tween.elem[tween.prop] != null && tween.elem.style[tween.prop] == null) { + return tween.elem[tween.prop]; + } + + // Passing an empty string as a 3rd parameter to .css will automatically + // attempt a parseFloat and fallback to a string if the parse fails. + // Simple values such as "10px" are parsed to Float; + // complex values such as "rotate(1rad)" are returned as-is. + result = jQuery.css(tween.elem, tween.prop, ""); + + // Empty strings, null, undefined and "auto" are converted to 0. + return !result || result === "auto" ? 0 : result; + }, + set: function (tween) { + + // Use step hook for back compat. + // Use cssHook if its there. + // Use .style if available and use plain properties where available. + if (jQuery.fx.step[tween.prop]) { + jQuery.fx.step[tween.prop](tween); + } else if (tween.elem.nodeType === 1 && + (tween.elem.style[jQuery.cssProps[tween.prop]] != null || + jQuery.cssHooks[tween.prop])) { + jQuery.style(tween.elem, tween.prop, tween.now + tween.unit); + } else { + tween.elem[tween.prop] = tween.now; + } + } + } + }; + + // Support: IE <=9 only + // Panic based approach to setting things on disconnected nodes + Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { + set: function (tween) { + if (tween.elem.nodeType && tween.elem.parentNode) { + tween.elem[tween.prop] = tween.now; + } + } + }; + + jQuery.easing = { + linear: function (p) { + return p; + }, + swing: function (p) { + return 0.5 - Math.cos(p * Math.PI) / 2; + }, + _default: "swing" + }; + + jQuery.fx = Tween.prototype.init; + + // Back compat <1.8 extension point + jQuery.fx.step = {}; + + + + + var + fxNow, inProgress, + rfxtypes = /^(?:toggle|show|hide)$/, + rrun = /queueHooks$/; + + function schedule() { + if (inProgress) { + if (document.hidden === false && window.requestAnimationFrame) { + window.requestAnimationFrame(schedule); + } else { + window.setTimeout(schedule, jQuery.fx.interval); + } + + jQuery.fx.tick(); + } + } + + // Animations created synchronously will run synchronously + function createFxNow() { + window.setTimeout(function () { + fxNow = undefined; + }); + return (fxNow = Date.now()); + } + + // Generate parameters to create a standard animation + function genFx(type, includeWidth) { + var which, + i = 0, + attrs = { + height: type + }; + + // If we include width, step value is 1 to do all cssExpand values, + // otherwise step value is 2 to skip over Left and Right + includeWidth = includeWidth ? 1 : 0; + for (; i < 4; i += 2 - includeWidth) { + which = cssExpand[i]; + attrs["margin" + which] = attrs["padding" + which] = type; + } + + if (includeWidth) { + attrs.opacity = attrs.width = type; + } + + return attrs; + } + + function createTween(value, prop, animation) { + var tween, + collection = (Animation.tweeners[prop] || []).concat(Animation.tweeners["*"]), + index = 0, + length = collection.length; + for (; index < length; index++) { + if ((tween = collection[index].call(animation, prop, value))) { + + // We're done with this property + return tween; + } + } + } + + function defaultPrefilter(elem, props, opts) { + var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, + isBox = "width" in props || "height" in props, + anim = this, + orig = {}, + style = elem.style, + hidden = elem.nodeType && isHiddenWithinTree(elem), + dataShow = dataPriv.get(elem, "fxshow"); + + // Queue-skipping animations hijack the fx hooks + if (!opts.queue) { + hooks = jQuery._queueHooks(elem, "fx"); + if (hooks.unqueued == null) { + hooks.unqueued = 0; + oldfire = hooks.empty.fire; + hooks.empty.fire = function () { + if (!hooks.unqueued) { + oldfire(); + } + }; + } + hooks.unqueued++; + + anim.always(function () { + + // Ensure the complete handler is called before this completes + anim.always(function () { + hooks.unqueued--; + if (!jQuery.queue(elem, "fx").length) { + hooks.empty.fire(); + } + }); + }); + } + + // Detect show/hide animations + for (prop in props) { + value = props[prop]; + if (rfxtypes.test(value)) { + delete props[prop]; + toggle = toggle || value === "toggle"; + if (value === (hidden ? "hide" : "show")) { + + // Pretend to be hidden if this is a "show" and + // there is still data from a stopped show/hide + if (value === "show" && dataShow && dataShow[prop] !== undefined) { + hidden = true; + + // Ignore all other no-op show/hide data + } else { + continue; + } + } + orig[prop] = dataShow && dataShow[prop] || jQuery.style(elem, prop); + } + } + + // Bail out if this is a no-op like .hide().hide() + propTween = !jQuery.isEmptyObject(props); + if (!propTween && jQuery.isEmptyObject(orig)) { + return; + } + + // Restrict "overflow" and "display" styles during box animations + if (isBox && elem.nodeType === 1) { + + // Support: IE <=9 - 11, Edge 12 - 15 + // Record all 3 overflow attributes because IE does not infer the shorthand + // from identically-valued overflowX and overflowY and Edge just mirrors + // the overflowX value there. + opts.overflow = [style.overflow, style.overflowX, style.overflowY]; + + // Identify a display type, preferring old show/hide data over the CSS cascade + restoreDisplay = dataShow && dataShow.display; + if (restoreDisplay == null) { + restoreDisplay = dataPriv.get(elem, "display"); + } + display = jQuery.css(elem, "display"); + if (display === "none") { + if (restoreDisplay) { + display = restoreDisplay; + } else { + + // Get nonempty value(s) by temporarily forcing visibility + showHide([elem], true); + restoreDisplay = elem.style.display || restoreDisplay; + display = jQuery.css(elem, "display"); + showHide([elem]); + } + } + + // Animate inline elements as inline-block + if (display === "inline" || display === "inline-block" && restoreDisplay != null) { + if (jQuery.css(elem, "float") === "none") { + + // Restore the original display value at the end of pure show/hide animations + if (!propTween) { + anim.done(function () { + style.display = restoreDisplay; + }); + if (restoreDisplay == null) { + display = style.display; + restoreDisplay = display === "none" ? "" : display; + } + } + style.display = "inline-block"; + } + } + } + + if (opts.overflow) { + style.overflow = "hidden"; + anim.always(function () { + style.overflow = opts.overflow[0]; + style.overflowX = opts.overflow[1]; + style.overflowY = opts.overflow[2]; + }); + } + + // Implement show/hide animations + propTween = false; + for (prop in orig) { + + // General show/hide setup for this element animation + if (!propTween) { + if (dataShow) { + if ("hidden" in dataShow) { + hidden = dataShow.hidden; + } + } else { + dataShow = dataPriv.access(elem, "fxshow", { + display: restoreDisplay + }); + } + + // Store hidden/visible for toggle so `.stop().toggle()` "reverses" + if (toggle) { + dataShow.hidden = !hidden; + } + + // Show elements before animating them + if (hidden) { + showHide([elem], true); + } + + /* eslint-disable no-loop-func */ + + anim.done(function () { + + /* eslint-enable no-loop-func */ + + // The final step of a "hide" animation is actually hiding the element + if (!hidden) { + showHide([elem]); + } + dataPriv.remove(elem, "fxshow"); + for (prop in orig) { + jQuery.style(elem, prop, orig[prop]); + } + }); + } + + // Per-property setup + propTween = createTween(hidden ? dataShow[prop] : 0, prop, anim); + if (!(prop in dataShow)) { + dataShow[prop] = propTween.start; + if (hidden) { + propTween.end = propTween.start; + propTween.start = 0; + } + } + } + } + + function propFilter(props, specialEasing) { + var index, name, easing, value, hooks; + + // camelCase, specialEasing and expand cssHook pass + for (index in props) { + name = camelCase(index); + easing = specialEasing[name]; + value = props[index]; + if (Array.isArray(value)) { + easing = value[1]; + value = props[index] = value[0]; + } + + if (index !== name) { + props[name] = value; + delete props[index]; + } + + hooks = jQuery.cssHooks[name]; + if (hooks && "expand" in hooks) { + value = hooks.expand(value); + delete props[name]; + + // Not quite $.extend, this won't overwrite existing keys. + // Reusing 'index' because we have the correct "name" + for (index in value) { + if (!(index in props)) { + props[index] = value[index]; + specialEasing[index] = easing; + } + } + } else { + specialEasing[name] = easing; + } + } + } + + function Animation(elem, properties, options) { + var result, + stopped, + index = 0, + length = Animation.prefilters.length, + deferred = jQuery.Deferred().always(function () { + + // Don't match elem in the :animated selector + delete tick.elem; + }), + tick = function () { + if (stopped) { + return false; + } + var currentTime = fxNow || createFxNow(), + remaining = Math.max(0, animation.startTime + animation.duration - currentTime), + + // Support: Android 2.3 only + // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) + temp = remaining / animation.duration || 0, + percent = 1 - temp, + index = 0, + length = animation.tweens.length; + + for (; index < length; index++) { + animation.tweens[index].run(percent); + } + + deferred.notifyWith(elem, [animation, percent, remaining]); + + // If there's more to do, yield + if (percent < 1 && length) { + return remaining; + } + + // If this was an empty animation, synthesize a final progress notification + if (!length) { + deferred.notifyWith(elem, [animation, 1, 0]); + } + + // Resolve the animation and report its conclusion + deferred.resolveWith(elem, [animation]); + return false; + }, + animation = deferred.promise({ + elem: elem, + props: jQuery.extend({}, properties), + opts: jQuery.extend(true, { + specialEasing: {}, + easing: jQuery.easing._default + }, options), + originalProperties: properties, + originalOptions: options, + startTime: fxNow || createFxNow(), + duration: options.duration, + tweens: [], + createTween: function (prop, end) { + var tween = jQuery.Tween(elem, animation.opts, prop, end, + animation.opts.specialEasing[prop] || animation.opts.easing); + animation.tweens.push(tween); + return tween; + }, + stop: function (gotoEnd) { + var index = 0, + + // If we are going to the end, we want to run all the tweens + // otherwise we skip this part + length = gotoEnd ? animation.tweens.length : 0; + if (stopped) { + return this; + } + stopped = true; + for (; index < length; index++) { + animation.tweens[index].run(1); + } + + // Resolve when we played the last frame; otherwise, reject + if (gotoEnd) { + deferred.notifyWith(elem, [animation, 1, 0]); + deferred.resolveWith(elem, [animation, gotoEnd]); + } else { + deferred.rejectWith(elem, [animation, gotoEnd]); + } + return this; + } + }), + props = animation.props; + + propFilter(props, animation.opts.specialEasing); + + for (; index < length; index++) { + result = Animation.prefilters[index].call(animation, elem, props, animation.opts); + if (result) { + if (isFunction(result.stop)) { + jQuery._queueHooks(animation.elem, animation.opts.queue).stop = + result.stop.bind(result); + } + return result; + } + } + + jQuery.map(props, createTween, animation); + + if (isFunction(animation.opts.start)) { + animation.opts.start.call(elem, animation); + } + + // Attach callbacks from options + animation + .progress(animation.opts.progress) + .done(animation.opts.done, animation.opts.complete) + .fail(animation.opts.fail) + .always(animation.opts.always); + + jQuery.fx.timer( + jQuery.extend(tick, { + elem: elem, + anim: animation, + queue: animation.opts.queue + }) + ); + + return animation; + } + + jQuery.Animation = jQuery.extend(Animation, { + + tweeners: { + "*": [function (prop, value) { + var tween = this.createTween(prop, value); + adjustCSS(tween.elem, prop, rcssNum.exec(value), tween); + return tween; + }] + }, + + tweener: function (props, callback) { + if (isFunction(props)) { + callback = props; + props = ["*"]; + } else { + props = props.match(rnothtmlwhite); + } + + var prop, + index = 0, + length = props.length; + + for (; index < length; index++) { + prop = props[index]; + Animation.tweeners[prop] = Animation.tweeners[prop] || []; + Animation.tweeners[prop].unshift(callback); + } + }, + + prefilters: [defaultPrefilter], + + prefilter: function (callback, prepend) { + if (prepend) { + Animation.prefilters.unshift(callback); + } else { + Animation.prefilters.push(callback); + } + } + }); + + jQuery.speed = function (speed, easing, fn) { + var opt = speed && typeof speed === "object" ? jQuery.extend({}, speed) : { + complete: fn || !fn && easing || + isFunction(speed) && speed, + duration: speed, + easing: fn && easing || easing && !isFunction(easing) && easing + }; + + // Go to the end state if fx are off + if (jQuery.fx.off) { + opt.duration = 0; + + } else { + if (typeof opt.duration !== "number") { + if (opt.duration in jQuery.fx.speeds) { + opt.duration = jQuery.fx.speeds[opt.duration]; + + } else { + opt.duration = jQuery.fx.speeds._default; + } + } + } + + // Normalize opt.queue - true/undefined/null -> "fx" + if (opt.queue == null || opt.queue === true) { + opt.queue = "fx"; + } + + // Queueing + opt.old = opt.complete; + + opt.complete = function () { + if (isFunction(opt.old)) { + opt.old.call(this); + } + + if (opt.queue) { + jQuery.dequeue(this, opt.queue); + } + }; + + return opt; + }; + + jQuery.fn.extend({ + fadeTo: function (speed, to, easing, callback) { + + // Show any hidden elements after setting opacity to 0 + return this.filter(isHiddenWithinTree).css("opacity", 0).show() + + // Animate to the value specified + .end().animate({ + opacity: to + }, speed, easing, callback); + }, + animate: function (prop, speed, easing, callback) { + var empty = jQuery.isEmptyObject(prop), + optall = jQuery.speed(speed, easing, callback), + doAnimation = function () { + + // Operate on a copy of prop so per-property easing won't be lost + var anim = Animation(this, jQuery.extend({}, prop), optall); + + // Empty animations, or finishing resolves immediately + if (empty || dataPriv.get(this, "finish")) { + anim.stop(true); + } + }; + doAnimation.finish = doAnimation; + + return empty || optall.queue === false ? + this.each(doAnimation) : + this.queue(optall.queue, doAnimation); + }, + stop: function (type, clearQueue, gotoEnd) { + var stopQueue = function (hooks) { + var stop = hooks.stop; + delete hooks.stop; + stop(gotoEnd); + }; + + if (typeof type !== "string") { + gotoEnd = clearQueue; + clearQueue = type; + type = undefined; + } + if (clearQueue && type !== false) { + this.queue(type || "fx", []); + } + + return this.each(function () { + var dequeue = true, + index = type != null && type + "queueHooks", + timers = jQuery.timers, + data = dataPriv.get(this); + + if (index) { + if (data[index] && data[index].stop) { + stopQueue(data[index]); + } + } else { + for (index in data) { + if (data[index] && data[index].stop && rrun.test(index)) { + stopQueue(data[index]); + } + } + } + + for (index = timers.length; index--;) { + if (timers[index].elem === this && + (type == null || timers[index].queue === type)) { + + timers[index].anim.stop(gotoEnd); + dequeue = false; + timers.splice(index, 1); + } + } + + // Start the next in the queue if the last step wasn't forced. + // Timers currently will call their complete callbacks, which + // will dequeue but only if they were gotoEnd. + if (dequeue || !gotoEnd) { + jQuery.dequeue(this, type); + } + }); + }, + finish: function (type) { + if (type !== false) { + type = type || "fx"; + } + return this.each(function () { + var index, + data = dataPriv.get(this), + queue = data[type + "queue"], + hooks = data[type + "queueHooks"], + timers = jQuery.timers, + length = queue ? queue.length : 0; + + // Enable finishing flag on private data + data.finish = true; + + // Empty the queue first + jQuery.queue(this, type, []); + + if (hooks && hooks.stop) { + hooks.stop.call(this, true); + } + + // Look for any active animations, and finish them + for (index = timers.length; index--;) { + if (timers[index].elem === this && timers[index].queue === type) { + timers[index].anim.stop(true); + timers.splice(index, 1); + } + } + + // Look for any animations in the old queue and finish them + for (index = 0; index < length; index++) { + if (queue[index] && queue[index].finish) { + queue[index].finish.call(this); + } + } + + // Turn off finishing flag + delete data.finish; + }); + } + }); + + jQuery.each(["toggle", "show", "hide"], function (i, name) { + var cssFn = jQuery.fn[name]; + jQuery.fn[name] = function (speed, easing, callback) { + return speed == null || typeof speed === "boolean" ? + cssFn.apply(this, arguments) : + this.animate(genFx(name, true), speed, easing, callback); + }; + }); + + // Generate shortcuts for custom animations + jQuery.each({ + slideDown: genFx("show"), + slideUp: genFx("hide"), + slideToggle: genFx("toggle"), + fadeIn: { + opacity: "show" + }, + fadeOut: { + opacity: "hide" + }, + fadeToggle: { + opacity: "toggle" + } + }, function (name, props) { + jQuery.fn[name] = function (speed, easing, callback) { + return this.animate(props, speed, easing, callback); + }; + }); + + jQuery.timers = []; + jQuery.fx.tick = function () { + var timer, + i = 0, + timers = jQuery.timers; + + fxNow = Date.now(); + + for (; i < timers.length; i++) { + timer = timers[i]; + + // Run the timer and safely remove it when done (allowing for external removal) + if (!timer() && timers[i] === timer) { + timers.splice(i--, 1); + } + } + + if (!timers.length) { + jQuery.fx.stop(); + } + fxNow = undefined; + }; + + jQuery.fx.timer = function (timer) { + jQuery.timers.push(timer); + jQuery.fx.start(); + }; + + jQuery.fx.interval = 13; + jQuery.fx.start = function () { + if (inProgress) { + return; + } + + inProgress = true; + schedule(); + }; + + jQuery.fx.stop = function () { + inProgress = null; + }; + + jQuery.fx.speeds = { + slow: 600, + fast: 200, + + // Default speed + _default: 400 + }; + + + // Based off of the plugin by Clint Helfers, with permission. + // https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ + jQuery.fn.delay = function (time, type) { + time = jQuery.fx ? jQuery.fx.speeds[time] || time : time; + type = type || "fx"; + + return this.queue(type, function (next, hooks) { + var timeout = window.setTimeout(next, time); + hooks.stop = function () { + window.clearTimeout(timeout); + }; + }); + }; + + + (function () { + var input = document.createElement("input"), + select = document.createElement("select"), + opt = select.appendChild(document.createElement("option")); + + input.type = "checkbox"; + + // Support: Android <=4.3 only + // Default value for a checkbox should be "on" + support.checkOn = input.value !== ""; + + // Support: IE <=11 only + // Must access selectedIndex to make default options select + support.optSelected = opt.selected; + + // Support: IE <=11 only + // An input loses its value after becoming a radio + input = document.createElement("input"); + input.value = "t"; + input.type = "radio"; + support.radioValue = input.value === "t"; + })(); + + + var boolHook, + attrHandle = jQuery.expr.attrHandle; + + jQuery.fn.extend({ + attr: function (name, value) { + return access(this, jQuery.attr, name, value, arguments.length > 1); + }, + + removeAttr: function (name) { + return this.each(function () { + jQuery.removeAttr(this, name); + }); + } + }); + + jQuery.extend({ + attr: function (elem, name, value) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set attributes on text, comment and attribute nodes + if (nType === 3 || nType === 8 || nType === 2) { + return; + } + + // Fallback to prop when attributes are not supported + if (typeof elem.getAttribute === "undefined") { + return jQuery.prop(elem, name, value); + } + + // Attribute hooks are determined by the lowercase version + // Grab necessary hook if one is defined + if (nType !== 1 || !jQuery.isXMLDoc(elem)) { + hooks = jQuery.attrHooks[name.toLowerCase()] || + (jQuery.expr.match.bool.test(name) ? boolHook : undefined); + } + + if (value !== undefined) { + if (value === null) { + jQuery.removeAttr(elem, name); + return; + } + + if (hooks && "set" in hooks && + (ret = hooks.set(elem, value, name)) !== undefined) { + return ret; + } + + elem.setAttribute(name, value + ""); + return value; + } + + if (hooks && "get" in hooks && (ret = hooks.get(elem, name)) !== null) { + return ret; + } + + ret = jQuery.find.attr(elem, name); + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? undefined : ret; + }, + + attrHooks: { + type: { + set: function (elem, value) { + if (!support.radioValue && value === "radio" && + nodeName(elem, "input")) { + var val = elem.value; + elem.setAttribute("type", value); + if (val) { + elem.value = val; + } + return value; + } + } + } + }, + + removeAttr: function (elem, value) { + var name, + i = 0, + + // Attribute names can contain non-HTML whitespace characters + // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 + attrNames = value && value.match(rnothtmlwhite); + + if (attrNames && elem.nodeType === 1) { + while ((name = attrNames[i++])) { + elem.removeAttribute(name); + } + } + } + }); + + // Hooks for boolean attributes + boolHook = { + set: function (elem, value, name) { + if (value === false) { + + // Remove boolean attributes when set to false + jQuery.removeAttr(elem, name); + } else { + elem.setAttribute(name, name); + } + return name; + } + }; + + jQuery.each(jQuery.expr.match.bool.source.match(/\w+/g), function (i, name) { + var getter = attrHandle[name] || jQuery.find.attr; + + attrHandle[name] = function (elem, name, isXML) { + var ret, handle, + lowercaseName = name.toLowerCase(); + + if (!isXML) { + + // Avoid an infinite loop by temporarily removing this function from the getter + handle = attrHandle[lowercaseName]; + attrHandle[lowercaseName] = ret; + ret = getter(elem, name, isXML) != null ? + lowercaseName : + null; + attrHandle[lowercaseName] = handle; + } + return ret; + }; + }); + + + + + var rfocusable = /^(?:input|select|textarea|button)$/i, + rclickable = /^(?:a|area)$/i; + + jQuery.fn.extend({ + prop: function (name, value) { + return access(this, jQuery.prop, name, value, arguments.length > 1); + }, + + removeProp: function (name) { + return this.each(function () { + delete this[jQuery.propFix[name] || name]; + }); + } + }); + + jQuery.extend({ + prop: function (elem, name, value) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set properties on text, comment and attribute nodes + if (nType === 3 || nType === 8 || nType === 2) { + return; + } + + if (nType !== 1 || !jQuery.isXMLDoc(elem)) { + + // Fix name and attach hooks + name = jQuery.propFix[name] || name; + hooks = jQuery.propHooks[name]; + } + + if (value !== undefined) { + if (hooks && "set" in hooks && + (ret = hooks.set(elem, value, name)) !== undefined) { + return ret; + } + + return (elem[name] = value); + } + + if (hooks && "get" in hooks && (ret = hooks.get(elem, name)) !== null) { + return ret; + } + + return elem[name]; + }, + + propHooks: { + tabIndex: { + get: function (elem) { + + // Support: IE <=9 - 11 only + // elem.tabIndex doesn't always return the + // correct value when it hasn't been explicitly set + // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + // Use proper attribute retrieval(#12072) + var tabindex = jQuery.find.attr(elem, "tabindex"); + + if (tabindex) { + return parseInt(tabindex, 10); + } + + if ( + rfocusable.test(elem.nodeName) || + rclickable.test(elem.nodeName) && + elem.href + ) { + return 0; + } + + return -1; + } + } + }, + + propFix: { + "for": "htmlFor", + "class": "className" + } + }); + + // Support: IE <=11 only + // Accessing the selectedIndex property + // forces the browser to respect setting selected + // on the option + // The getter ensures a default option is selected + // when in an optgroup + // eslint rule "no-unused-expressions" is disabled for this code + // since it considers such accessions noop + if (!support.optSelected) { + jQuery.propHooks.selected = { + get: function (elem) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if (parent && parent.parentNode) { + parent.parentNode.selectedIndex; + } + return null; + }, + set: function (elem) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if (parent) { + parent.selectedIndex; + + if (parent.parentNode) { + parent.parentNode.selectedIndex; + } + } + } + }; + } + + jQuery.each([ + "tabIndex", + "readOnly", + "maxLength", + "cellSpacing", + "cellPadding", + "rowSpan", + "colSpan", + "useMap", + "frameBorder", + "contentEditable" + ], function () { + jQuery.propFix[this.toLowerCase()] = this; + }); + + + + + // Strip and collapse whitespace according to HTML spec + // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace + function stripAndCollapse(value) { + var tokens = value.match(rnothtmlwhite) || []; + return tokens.join(" "); + } + + + function getClass(elem) { + return elem.getAttribute && elem.getAttribute("class") || ""; + } + + function classesToArray(value) { + if (Array.isArray(value)) { + return value; + } + if (typeof value === "string") { + return value.match(rnothtmlwhite) || []; + } + return []; + } + + jQuery.fn.extend({ + addClass: function (value) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if (isFunction(value)) { + return this.each(function (j) { + jQuery(this).addClass(value.call(this, j, getClass(this))); + }); + } + + classes = classesToArray(value); + + if (classes.length) { + while ((elem = this[i++])) { + curValue = getClass(elem); + cur = elem.nodeType === 1 && (" " + stripAndCollapse(curValue) + " "); + + if (cur) { + j = 0; + while ((clazz = classes[j++])) { + if (cur.indexOf(" " + clazz + " ") < 0) { + cur += clazz + " "; + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse(cur); + if (curValue !== finalValue) { + elem.setAttribute("class", finalValue); + } + } + } + } + + return this; + }, + + removeClass: function (value) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if (isFunction(value)) { + return this.each(function (j) { + jQuery(this).removeClass(value.call(this, j, getClass(this))); + }); + } + + if (!arguments.length) { + return this.attr("class", ""); + } + + classes = classesToArray(value); + + if (classes.length) { + while ((elem = this[i++])) { + curValue = getClass(elem); + + // This expression is here for better compressibility (see addClass) + cur = elem.nodeType === 1 && (" " + stripAndCollapse(curValue) + " "); + + if (cur) { + j = 0; + while ((clazz = classes[j++])) { + + // Remove *all* instances + while (cur.indexOf(" " + clazz + " ") > -1) { + cur = cur.replace(" " + clazz + " ", " "); + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse(cur); + if (curValue !== finalValue) { + elem.setAttribute("class", finalValue); + } + } + } + } + + return this; + }, + + toggleClass: function (value, stateVal) { + var type = typeof value, + isValidValue = type === "string" || Array.isArray(value); + + if (typeof stateVal === "boolean" && isValidValue) { + return stateVal ? this.addClass(value) : this.removeClass(value); + } + + if (isFunction(value)) { + return this.each(function (i) { + jQuery(this).toggleClass( + value.call(this, i, getClass(this), stateVal), + stateVal + ); + }); + } + + return this.each(function () { + var className, i, self, classNames; + + if (isValidValue) { + + // Toggle individual class names + i = 0; + self = jQuery(this); + classNames = classesToArray(value); + + while ((className = classNames[i++])) { + + // Check each className given, space separated list + if (self.hasClass(className)) { + self.removeClass(className); + } else { + self.addClass(className); + } + } + + // Toggle whole class name + } else if (value === undefined || type === "boolean") { + className = getClass(this); + if (className) { + + // Store className if set + dataPriv.set(this, "__className__", className); + } + + // If the element has a class name or if we're passed `false`, + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + if (this.setAttribute) { + this.setAttribute("class", + className || value === false ? + "" : + dataPriv.get(this, "__className__") || "" + ); + } + } + }); + }, + + hasClass: function (selector) { + var className, elem, + i = 0; + + className = " " + selector + " "; + while ((elem = this[i++])) { + if (elem.nodeType === 1 && + (" " + stripAndCollapse(getClass(elem)) + " ").indexOf(className) > -1) { + return true; + } + } + + return false; + } + }); + + + + + var rreturn = /\r/g; + + jQuery.fn.extend({ + val: function (value) { + var hooks, ret, valueIsFunction, + elem = this[0]; + + if (!arguments.length) { + if (elem) { + hooks = jQuery.valHooks[elem.type] || + jQuery.valHooks[elem.nodeName.toLowerCase()]; + + if (hooks && + "get" in hooks && + (ret = hooks.get(elem, "value")) !== undefined + ) { + return ret; + } + + ret = elem.value; + + // Handle most common string cases + if (typeof ret === "string") { + return ret.replace(rreturn, ""); + } + + // Handle cases where value is null/undef or number + return ret == null ? "" : ret; + } + + return; + } + + valueIsFunction = isFunction(value); + + return this.each(function (i) { + var val; + + if (this.nodeType !== 1) { + return; + } + + if (valueIsFunction) { + val = value.call(this, i, jQuery(this).val()); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if (val == null) { + val = ""; + + } else if (typeof val === "number") { + val += ""; + + } else if (Array.isArray(val)) { + val = jQuery.map(val, function (value) { + return value == null ? "" : value + ""; + }); + } + + hooks = jQuery.valHooks[this.type] || jQuery.valHooks[this.nodeName.toLowerCase()]; + + // If set returns undefined, fall back to normal setting + if (!hooks || !("set" in hooks) || hooks.set(this, val, "value") === undefined) { + this.value = val; + } + }); + } + }); + + jQuery.extend({ + valHooks: { + option: { + get: function (elem) { + + var val = jQuery.find.attr(elem, "value"); + return val != null ? + val : + + // Support: IE <=10 - 11 only + // option.text throws exceptions (#14686, #14858) + // Strip and collapse whitespace + // https://html.spec.whatwg.org/#strip-and-collapse-whitespace + stripAndCollapse(jQuery.text(elem)); + } + }, + select: { + get: function (elem) { + var value, option, i, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one", + values = one ? null : [], + max = one ? index + 1 : options.length; + + if (index < 0) { + i = max; + + } else { + i = one ? index : 0; + } + + // Loop through all the selected options + for (; i < max; i++) { + option = options[i]; + + // Support: IE <=9 only + // IE8-9 doesn't update selected after form reset (#2551) + if ((option.selected || i === index) && + + // Don't return options that are disabled or in a disabled optgroup + !option.disabled && + (!option.parentNode.disabled || + !nodeName(option.parentNode, "optgroup"))) { + + // Get the specific value for the option + value = jQuery(option).val(); + + // We don't need an array for one selects + if (one) { + return value; + } + + // Multi-Selects return an array + values.push(value); + } + } + + return values; + }, + + set: function (elem, value) { + var optionSet, option, + options = elem.options, + values = jQuery.makeArray(value), + i = options.length; + + while (i--) { + option = options[i]; + + /* eslint-disable no-cond-assign */ + + if (option.selected = + jQuery.inArray(jQuery.valHooks.option.get(option), values) > -1 + ) { + optionSet = true; + } + + /* eslint-enable no-cond-assign */ + } + + // Force browsers to behave consistently when non-matching value is set + if (!optionSet) { + elem.selectedIndex = -1; + } + return values; + } + } + } + }); + + // Radios and checkboxes getter/setter + jQuery.each(["radio", "checkbox"], function () { + jQuery.valHooks[this] = { + set: function (elem, value) { + if (Array.isArray(value)) { + return (elem.checked = jQuery.inArray(jQuery(elem).val(), value) > -1); + } + } + }; + if (!support.checkOn) { + jQuery.valHooks[this].get = function (elem) { + return elem.getAttribute("value") === null ? "on" : elem.value; + }; + } + }); + + + + + // Return jQuery for attributes-only inclusion + + + support.focusin = "onfocusin" in window; + + + var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + stopPropagationCallback = function (e) { + e.stopPropagation(); + }; + + jQuery.extend(jQuery.event, { + + trigger: function (event, data, elem, onlyHandlers) { + + var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, + eventPath = [elem || document], + type = hasOwn.call(event, "type") ? event.type : event, + namespaces = hasOwn.call(event, "namespace") ? event.namespace.split(".") : []; + + cur = lastElement = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if (elem.nodeType === 3 || elem.nodeType === 8) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if (rfocusMorph.test(type + jQuery.event.triggered)) { + return; + } + + if (type.indexOf(".") > -1) { + + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split("."); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf(":") < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[jQuery.expando] ? + event : + new jQuery.Event(type, typeof event === "object" && event); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join("."); + event.rnamespace = event.namespace ? + new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)") : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if (!event.target) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? [event] : + jQuery.makeArray(data, [event]); + + // Allow special events to draw outside the lines + special = jQuery.event.special[type] || {}; + if (!onlyHandlers && special.trigger && special.trigger.apply(elem, data) === false) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if (!onlyHandlers && !special.noBubble && !isWindow(elem)) { + + bubbleType = special.delegateType || type; + if (!rfocusMorph.test(bubbleType + type)) { + cur = cur.parentNode; + } + for (; cur; cur = cur.parentNode) { + eventPath.push(cur); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if (tmp === (elem.ownerDocument || document)) { + eventPath.push(tmp.defaultView || tmp.parentWindow || window); + } + } + + // Fire handlers on the event path + i = 0; + while ((cur = eventPath[i++]) && !event.isPropagationStopped()) { + lastElement = cur; + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = (dataPriv.get(cur, "events") || {})[event.type] && + dataPriv.get(cur, "handle"); + if (handle) { + handle.apply(cur, data); + } + + // Native handler + handle = ontype && cur[ontype]; + if (handle && handle.apply && acceptData(cur)) { + event.result = handle.apply(cur, data); + if (event.result === false) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if (!onlyHandlers && !event.isDefaultPrevented()) { + + if ((!special._default || + special._default.apply(eventPath.pop(), data) === false) && + acceptData(elem)) { + + // Call a native DOM method on the target with the same name as the event. + // Don't do default actions on window, that's where global variables be (#6170) + if (ontype && isFunction(elem[type]) && !isWindow(elem)) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ontype]; + + if (tmp) { + elem[ontype] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + + if (event.isPropagationStopped()) { + lastElement.addEventListener(type, stopPropagationCallback); + } + + elem[type](); + + if (event.isPropagationStopped()) { + lastElement.removeEventListener(type, stopPropagationCallback); + } + + jQuery.event.triggered = undefined; + + if (tmp) { + elem[ontype] = tmp; + } + } + } + } + + return event.result; + }, + + // Piggyback on a donor event to simulate a different one + // Used only for `focus(in | out)` events + simulate: function (type, elem, event) { + var e = jQuery.extend( + new jQuery.Event(), + event, { + type: type, + isSimulated: true + } + ); + + jQuery.event.trigger(e, null, elem); + } + + }); + + jQuery.fn.extend({ + + trigger: function (type, data) { + return this.each(function () { + jQuery.event.trigger(type, data, this); + }); + }, + triggerHandler: function (type, data) { + var elem = this[0]; + if (elem) { + return jQuery.event.trigger(type, data, elem, true); + } + } + }); + + + // Support: Firefox <=44 + // Firefox doesn't have focus(in | out) events + // Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 + // + // Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 + // focus(in | out) events fire after focus & blur events, + // which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order + // Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 + if (!support.focusin) { + jQuery.each({ + focus: "focusin", + blur: "focusout" + }, function (orig, fix) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function (event) { + jQuery.event.simulate(fix, event.target, jQuery.event.fix(event)); + }; + + jQuery.event.special[fix] = { + setup: function () { + var doc = this.ownerDocument || this, + attaches = dataPriv.access(doc, fix); + + if (!attaches) { + doc.addEventListener(orig, handler, true); + } + dataPriv.access(doc, fix, (attaches || 0) + 1); + }, + teardown: function () { + var doc = this.ownerDocument || this, + attaches = dataPriv.access(doc, fix) - 1; + + if (!attaches) { + doc.removeEventListener(orig, handler, true); + dataPriv.remove(doc, fix); + + } else { + dataPriv.access(doc, fix, attaches); + } + } + }; + }); + } + var location = window.location; + + var nonce = Date.now(); + + var rquery = (/\?/); + + + + // Cross-browser xml parsing + jQuery.parseXML = function (data) { + var xml; + if (!data || typeof data !== "string") { + return null; + } + + // Support: IE 9 - 11 only + // IE throws on parseFromString with invalid input. + try { + xml = (new window.DOMParser()).parseFromString(data, "text/xml"); + } catch (e) { + xml = undefined; + } + + if (!xml || xml.getElementsByTagName("parsererror").length) { + jQuery.error("Invalid XML: " + data); + } + return xml; + }; + + + var + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, + rsubmittable = /^(?:input|select|textarea|keygen)/i; + + function buildParams(prefix, obj, traditional, add) { + var name; + + if (Array.isArray(obj)) { + + // Serialize array item. + jQuery.each(obj, function (i, v) { + if (traditional || rbracket.test(prefix)) { + + // Treat each array item as a scalar. + add(prefix, v); + + } else { + + // Item is non-scalar (array or object), encode its numeric index. + buildParams( + prefix + "[" + (typeof v === "object" && v != null ? i : "") + "]", + v, + traditional, + add + ); + } + }); + + } else if (!traditional && toType(obj) === "object") { + + // Serialize object item. + for (name in obj) { + buildParams(prefix + "[" + name + "]", obj[name], traditional, add); + } + + } else { + + // Serialize scalar item. + add(prefix, obj); + } + } + + // Serialize an array of form elements or a set of + // key/values into a query string + jQuery.param = function (a, traditional) { + var prefix, + s = [], + add = function (key, valueOrFunction) { + + // If value is a function, invoke it and use its return value + var value = isFunction(valueOrFunction) ? + valueOrFunction() : + valueOrFunction; + + s[s.length] = encodeURIComponent(key) + "=" + + encodeURIComponent(value == null ? "" : value); + }; + + // If an array was passed in, assume that it is an array of form elements. + if (Array.isArray(a) || (a.jquery && !jQuery.isPlainObject(a))) { + + // Serialize the form elements + jQuery.each(a, function () { + add(this.name, this.value); + }); + + } else { + + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for (prefix in a) { + buildParams(prefix, a[prefix], traditional, add); + } + } + + // Return the resulting serialization + return s.join("&"); + }; + + jQuery.fn.extend({ + serialize: function () { + return jQuery.param(this.serializeArray()); + }, + serializeArray: function () { + return this.map(function () { + + // Can add propHook for "elements" to filter or add form elements + var elements = jQuery.prop(this, "elements"); + return elements ? jQuery.makeArray(elements) : this; + }) + .filter(function () { + var type = this.type; + + // Use .is( ":disabled" ) so that fieldset[disabled] works + return this.name && !jQuery(this).is(":disabled") && + rsubmittable.test(this.nodeName) && !rsubmitterTypes.test(type) && + (this.checked || !rcheckableType.test(type)); + }) + .map(function (i, elem) { + var val = jQuery(this).val(); + + if (val == null) { + return null; + } + + if (Array.isArray(val)) { + return jQuery.map(val, function (val) { + return { + name: elem.name, + value: val.replace(rCRLF, "\r\n") + }; + }); + } + + return { + name: elem.name, + value: val.replace(rCRLF, "\r\n") + }; + }).get(); + } + }); + + + var + r20 = /%20/g, + rhash = /#.*$/, + rantiCache = /([?&])_=[^&]*/, + rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, + + // #7653, #8125, #8152: local protocol detection + rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, + rnoContent = /^(?:GET|HEAD)$/, + rprotocol = /^\/\//, + + /* Prefilters + * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) + * 2) These are called: + * - BEFORE asking for a transport + * - AFTER param serialization (s.data is a string if s.processData is true) + * 3) key is the dataType + * 4) the catchall symbol "*" can be used + * 5) execution will start with transport dataType and THEN continue down to "*" if needed + */ + prefilters = {}, + + /* Transports bindings + * 1) key is the dataType + * 2) the catchall symbol "*" can be used + * 3) selection will start with transport dataType and THEN go to "*" if needed + */ + transports = {}, + + // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression + allTypes = "*/".concat("*"), + + // Anchor tag for parsing the document origin + originAnchor = document.createElement("a"); + originAnchor.href = location.href; + + // Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport + function addToPrefiltersOrTransports(structure) { + + // dataTypeExpression is optional and defaults to "*" + return function (dataTypeExpression, func) { + + if (typeof dataTypeExpression !== "string") { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + var dataType, + i = 0, + dataTypes = dataTypeExpression.toLowerCase().match(rnothtmlwhite) || []; + + if (isFunction(func)) { + + // For each dataType in the dataTypeExpression + while ((dataType = dataTypes[i++])) { + + // Prepend if requested + if (dataType[0] === "+") { + dataType = dataType.slice(1) || "*"; + (structure[dataType] = structure[dataType] || []).unshift(func); + + // Otherwise append + } else { + (structure[dataType] = structure[dataType] || []).push(func); + } + } + } + }; + } + + // Base inspection function for prefilters and transports + function inspectPrefiltersOrTransports(structure, options, originalOptions, jqXHR) { + + var inspected = {}, + seekingTransport = (structure === transports); + + function inspect(dataType) { + var selected; + inspected[dataType] = true; + jQuery.each(structure[dataType] || [], function (_, prefilterOrFactory) { + var dataTypeOrTransport = prefilterOrFactory(options, originalOptions, jqXHR); + if (typeof dataTypeOrTransport === "string" && + !seekingTransport && !inspected[dataTypeOrTransport]) { + + options.dataTypes.unshift(dataTypeOrTransport); + inspect(dataTypeOrTransport); + return false; + } else if (seekingTransport) { + return !(selected = dataTypeOrTransport); + } + }); + return selected; + } + + return inspect(options.dataTypes[0]) || !inspected["*"] && inspect("*"); + } + + // A special extend for ajax options + // that takes "flat" options (not to be deep extended) + // Fixes #9887 + function ajaxExtend(target, src) { + var key, deep, + flatOptions = jQuery.ajaxSettings.flatOptions || {}; + + for (key in src) { + if (src[key] !== undefined) { + (flatOptions[key] ? target : (deep || (deep = {})))[key] = src[key]; + } + } + if (deep) { + jQuery.extend(true, target, deep); + } + + return target; + } + + /* Handles responses to an ajax request: + * - finds the right dataType (mediates between content-type and expected dataType) + * - returns the corresponding response + */ + function ajaxHandleResponses(s, jqXHR, responses) { + + var ct, type, finalDataType, firstDataType, + contents = s.contents, + dataTypes = s.dataTypes; + + // Remove auto dataType and get content-type in the process + while (dataTypes[0] === "*") { + dataTypes.shift(); + if (ct === undefined) { + ct = s.mimeType || jqXHR.getResponseHeader("Content-Type"); + } + } + + // Check if we're dealing with a known content-type + if (ct) { + for (type in contents) { + if (contents[type] && contents[type].test(ct)) { + dataTypes.unshift(type); + break; + } + } + } + + // Check to see if we have a response for the expected dataType + if (dataTypes[0] in responses) { + finalDataType = dataTypes[0]; + } else { + + // Try convertible dataTypes + for (type in responses) { + if (!dataTypes[0] || s.converters[type + " " + dataTypes[0]]) { + finalDataType = type; + break; + } + if (!firstDataType) { + firstDataType = type; + } + } + + // Or just use first one + finalDataType = finalDataType || firstDataType; + } + + // If we found a dataType + // We add the dataType to the list if needed + // and return the corresponding response + if (finalDataType) { + if (finalDataType !== dataTypes[0]) { + dataTypes.unshift(finalDataType); + } + return responses[finalDataType]; + } + } + + /* Chain conversions given the request and the original response + * Also sets the responseXXX fields on the jqXHR instance + */ + function ajaxConvert(s, response, jqXHR, isSuccess) { + var conv2, current, conv, tmp, prev, + converters = {}, + + // Work with a copy of dataTypes in case we need to modify it for conversion + dataTypes = s.dataTypes.slice(); + + // Create converters map with lowercased keys + if (dataTypes[1]) { + for (conv in s.converters) { + converters[conv.toLowerCase()] = s.converters[conv]; + } + } + + current = dataTypes.shift(); + + // Convert to each sequential dataType + while (current) { + + if (s.responseFields[current]) { + jqXHR[s.responseFields[current]] = response; + } + + // Apply the dataFilter if provided + if (!prev && isSuccess && s.dataFilter) { + response = s.dataFilter(response, s.dataType); + } + + prev = current; + current = dataTypes.shift(); + + if (current) { + + // There's only work to do if current dataType is non-auto + if (current === "*") { + + current = prev; + + // Convert response if prev dataType is non-auto and differs from current + } else if (prev !== "*" && prev !== current) { + + // Seek a direct converter + conv = converters[prev + " " + current] || converters["* " + current]; + + // If none found, seek a pair + if (!conv) { + for (conv2 in converters) { + + // If conv2 outputs current + tmp = conv2.split(" "); + if (tmp[1] === current) { + + // If prev can be converted to accepted input + conv = converters[prev + " " + tmp[0]] || + converters["* " + tmp[0]]; + if (conv) { + + // Condense equivalence converters + if (conv === true) { + conv = converters[conv2]; + + // Otherwise, insert the intermediate dataType + } else if (converters[conv2] !== true) { + current = tmp[0]; + dataTypes.unshift(tmp[1]); + } + break; + } + } + } + } + + // Apply converter (if not an equivalence) + if (conv !== true) { + + // Unless errors are allowed to bubble, catch and return them + if (conv && s.throws) { + response = conv(response); + } else { + try { + response = conv(response); + } catch (e) { + return { + state: "parsererror", + error: conv ? e : "No conversion from " + prev + " to " + current + }; + } + } + } + } + } + } + + return { + state: "success", + data: response + }; + } + + jQuery.extend({ + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {}, + + ajaxSettings: { + url: location.href, + type: "GET", + isLocal: rlocalProtocol.test(location.protocol), + global: true, + processData: true, + async: true, + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + + /* + timeout: 0, + data: null, + dataType: null, + username: null, + password: null, + cache: null, + throws: false, + traditional: false, + headers: {}, + */ + + accepts: { + "*": allTypes, + text: "text/plain", + html: "text/html", + xml: "application/xml, text/xml", + json: "application/json, text/javascript" + }, + + contents: { + xml: /\bxml\b/, + html: /\bhtml/, + json: /\bjson\b/ + }, + + responseFields: { + xml: "responseXML", + text: "responseText", + json: "responseJSON" + }, + + // Data converters + // Keys separate source (or catchall "*") and destination types with a single space + converters: { + + // Convert anything to text + "* text": String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": JSON.parse, + + // Parse text as xml + "text xml": jQuery.parseXML + }, + + // For options that shouldn't be deep extended: + // you can add your own custom options here if + // and when you create one that shouldn't be + // deep extended (see ajaxExtend) + flatOptions: { + url: true, + context: true + } + }, + + // Creates a full fledged settings object into target + // with both ajaxSettings and settings fields. + // If target is omitted, writes into ajaxSettings. + ajaxSetup: function (target, settings) { + return settings ? + + // Building a settings object + ajaxExtend(ajaxExtend(target, jQuery.ajaxSettings), settings) : + + // Extending ajaxSettings + ajaxExtend(jQuery.ajaxSettings, target); + }, + + ajaxPrefilter: addToPrefiltersOrTransports(prefilters), + ajaxTransport: addToPrefiltersOrTransports(transports), + + // Main method + ajax: function (url, options) { + + // If url is an object, simulate pre-1.5 signature + if (typeof url === "object") { + options = url; + url = undefined; + } + + // Force options to be an object + options = options || {}; + + var transport, + + // URL without anti-cache param + cacheURL, + + // Response headers + responseHeadersString, + responseHeaders, + + // timeout handle + timeoutTimer, + + // Url cleanup var + urlAnchor, + + // Request state (becomes false upon send and true upon completion) + completed, + + // To know if global events are to be dispatched + fireGlobals, + + // Loop variable + i, + + // uncached part of the url + uncached, + + // Create the final options object + s = jQuery.ajaxSetup({}, options), + + // Callbacks context + callbackContext = s.context || s, + + // Context for global events is callbackContext if it is a DOM node or jQuery collection + globalEventContext = s.context && + (callbackContext.nodeType || callbackContext.jquery) ? + jQuery(callbackContext) : + jQuery.event, + + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery.Callbacks("once memory"), + + // Status-dependent callbacks + statusCode = s.statusCode || {}, + + // Headers (they are sent all at once) + requestHeaders = {}, + requestHeadersNames = {}, + + // Default abort message + strAbort = "canceled", + + // Fake xhr + jqXHR = { + readyState: 0, + + // Builds headers hashtable if needed + getResponseHeader: function (key) { + var match; + if (completed) { + if (!responseHeaders) { + responseHeaders = {}; + while ((match = rheaders.exec(responseHeadersString))) { + responseHeaders[match[1].toLowerCase()] = match[2]; + } + } + match = responseHeaders[key.toLowerCase()]; + } + return match == null ? null : match; + }, + + // Raw string + getAllResponseHeaders: function () { + return completed ? responseHeadersString : null; + }, + + // Caches the header + setRequestHeader: function (name, value) { + if (completed == null) { + name = requestHeadersNames[name.toLowerCase()] = + requestHeadersNames[name.toLowerCase()] || name; + requestHeaders[name] = value; + } + return this; + }, + + // Overrides response content-type header + overrideMimeType: function (type) { + if (completed == null) { + s.mimeType = type; + } + return this; + }, + + // Status-dependent callbacks + statusCode: function (map) { + var code; + if (map) { + if (completed) { + + // Execute the appropriate callbacks + jqXHR.always(map[jqXHR.status]); + } else { + + // Lazy-add the new callbacks in a way that preserves old ones + for (code in map) { + statusCode[code] = [statusCode[code], map[code]]; + } + } + } + return this; + }, + + // Cancel the request + abort: function (statusText) { + var finalText = statusText || strAbort; + if (transport) { + transport.abort(finalText); + } + done(0, finalText); + return this; + } + }; + + // Attach deferreds + deferred.promise(jqXHR); + + // Add protocol if not provided (prefilters might expect it) + // Handle falsy url in the settings object (#10093: consistency with old signature) + // We also use the url parameter if available + s.url = ((url || s.url || location.href) + "") + .replace(rprotocol, location.protocol + "//"); + + // Alias method option to type as per ticket #12004 + s.type = options.method || options.type || s.method || s.type; + + // Extract dataTypes list + s.dataTypes = (s.dataType || "*").toLowerCase().match(rnothtmlwhite) || [""]; + + // A cross-domain request is in order when the origin doesn't match the current origin. + if (s.crossDomain == null) { + urlAnchor = document.createElement("a"); + + // Support: IE <=8 - 11, Edge 12 - 15 + // IE throws exception on accessing the href property if url is malformed, + // e.g. http://example.com:80x/ + try { + urlAnchor.href = s.url; + + // Support: IE <=8 - 11 only + // Anchor's host property isn't correctly set when s.url is relative + urlAnchor.href = urlAnchor.href; + s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== + urlAnchor.protocol + "//" + urlAnchor.host; + } catch (e) { + + // If there is an error parsing the URL, assume it is crossDomain, + // it can be rejected by the transport if it is invalid + s.crossDomain = true; + } + } + + // Convert data if not already a string + if (s.data && s.processData && typeof s.data !== "string") { + s.data = jQuery.param(s.data, s.traditional); + } + + // Apply prefilters + inspectPrefiltersOrTransports(prefilters, s, options, jqXHR); + + // If request was aborted inside a prefilter, stop there + if (completed) { + return jqXHR; + } + + // We can fire global events as of now if asked to + // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) + fireGlobals = jQuery.event && s.global; + + // Watch for a new set of requests + if (fireGlobals && jQuery.active++ === 0) { + jQuery.event.trigger("ajaxStart"); + } + + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = !rnoContent.test(s.type); + + // Save the URL in case we're toying with the If-Modified-Since + // and/or If-None-Match header later on + // Remove hash to simplify url manipulation + cacheURL = s.url.replace(rhash, ""); + + // More options handling for requests with no content + if (!s.hasContent) { + + // Remember the hash so we can put it back + uncached = s.url.slice(cacheURL.length); + + // If data is available and should be processed, append data to url + if (s.data && (s.processData || typeof s.data === "string")) { + cacheURL += (rquery.test(cacheURL) ? "&" : "?") + s.data; + + // #9682: remove data so that it's not used in an eventual retry + delete s.data; + } + + // Add or update anti-cache param if needed + if (s.cache === false) { + cacheURL = cacheURL.replace(rantiCache, "$1"); + uncached = (rquery.test(cacheURL) ? "&" : "?") + "_=" + (nonce++) + uncached; + } + + // Put hash and anti-cache on the URL that will be requested (gh-1732) + s.url = cacheURL + uncached; + + // Change '%20' to '+' if this is encoded form body content (gh-2658) + } else if (s.data && s.processData && + (s.contentType || "").indexOf("application/x-www-form-urlencoded") === 0) { + s.data = s.data.replace(r20, "+"); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if (s.ifModified) { + if (jQuery.lastModified[cacheURL]) { + jqXHR.setRequestHeader("If-Modified-Since", jQuery.lastModified[cacheURL]); + } + if (jQuery.etag[cacheURL]) { + jqXHR.setRequestHeader("If-None-Match", jQuery.etag[cacheURL]); + } + } + + // Set the correct header, if data is being sent + if (s.data && s.hasContent && s.contentType !== false || options.contentType) { + jqXHR.setRequestHeader("Content-Type", s.contentType); + } + + // Set the Accepts header for the server, depending on the dataType + jqXHR.setRequestHeader( + "Accept", + s.dataTypes[0] && s.accepts[s.dataTypes[0]] ? + s.accepts[s.dataTypes[0]] + + (s.dataTypes[0] !== "*" ? ", " + allTypes + "; q=0.01" : "") : + s.accepts["*"] + ); + + // Check for headers option + for (i in s.headers) { + jqXHR.setRequestHeader(i, s.headers[i]); + } + + // Allow custom headers/mimetypes and early abort + if (s.beforeSend && + (s.beforeSend.call(callbackContext, jqXHR, s) === false || completed)) { + + // Abort if not done already and return + return jqXHR.abort(); + } + + // Aborting is no longer a cancellation + strAbort = "abort"; + + // Install callbacks on deferreds + completeDeferred.add(s.complete); + jqXHR.done(s.success); + jqXHR.fail(s.error); + + // Get transport + transport = inspectPrefiltersOrTransports(transports, s, options, jqXHR); + + // If no transport, we auto-abort + if (!transport) { + done(-1, "No Transport"); + } else { + jqXHR.readyState = 1; + + // Send global event + if (fireGlobals) { + globalEventContext.trigger("ajaxSend", [jqXHR, s]); + } + + // If request was aborted inside ajaxSend, stop there + if (completed) { + return jqXHR; + } + + // Timeout + if (s.async && s.timeout > 0) { + timeoutTimer = window.setTimeout(function () { + jqXHR.abort("timeout"); + }, s.timeout); + } + + try { + completed = false; + transport.send(requestHeaders, done); + } catch (e) { + + // Rethrow post-completion exceptions + if (completed) { + throw e; + } + + // Propagate others as results + done(-1, e); + } + } + + // Callback for when everything is done + function done(status, nativeStatusText, responses, headers) { + var isSuccess, success, error, response, modified, + statusText = nativeStatusText; + + // Ignore repeat invocations + if (completed) { + return; + } + + completed = true; + + // Clear timeout if it exists + if (timeoutTimer) { + window.clearTimeout(timeoutTimer); + } + + // Dereference transport for early garbage collection + // (no matter how long the jqXHR object will be used) + transport = undefined; + + // Cache response headers + responseHeadersString = headers || ""; + + // Set readyState + jqXHR.readyState = status > 0 ? 4 : 0; + + // Determine if successful + isSuccess = status >= 200 && status < 300 || status === 304; + + // Get response data + if (responses) { + response = ajaxHandleResponses(s, jqXHR, responses); + } + + // Convert no matter what (that way responseXXX fields are always set) + response = ajaxConvert(s, response, jqXHR, isSuccess); + + // If successful, handle type chaining + if (isSuccess) { + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if (s.ifModified) { + modified = jqXHR.getResponseHeader("Last-Modified"); + if (modified) { + jQuery.lastModified[cacheURL] = modified; + } + modified = jqXHR.getResponseHeader("etag"); + if (modified) { + jQuery.etag[cacheURL] = modified; + } + } + + // if no content + if (status === 204 || s.type === "HEAD") { + statusText = "nocontent"; + + // if not modified + } else if (status === 304) { + statusText = "notmodified"; + + // If we have data, let's convert it + } else { + statusText = response.state; + success = response.data; + error = response.error; + isSuccess = !error; + } + } else { + + // Extract error from statusText and normalize for non-aborts + error = statusText; + if (status || !statusText) { + statusText = "error"; + if (status < 0) { + status = 0; + } + } + } + + // Set data for the fake xhr object + jqXHR.status = status; + jqXHR.statusText = (nativeStatusText || statusText) + ""; + + // Success/Error + if (isSuccess) { + deferred.resolveWith(callbackContext, [success, statusText, jqXHR]); + } else { + deferred.rejectWith(callbackContext, [jqXHR, statusText, error]); + } + + // Status-dependent callbacks + jqXHR.statusCode(statusCode); + statusCode = undefined; + + if (fireGlobals) { + globalEventContext.trigger(isSuccess ? "ajaxSuccess" : "ajaxError", + [jqXHR, s, isSuccess ? success : error]); + } + + // Complete + completeDeferred.fireWith(callbackContext, [jqXHR, statusText]); + + if (fireGlobals) { + globalEventContext.trigger("ajaxComplete", [jqXHR, s]); + + // Handle the global AJAX counter + if (!(--jQuery.active)) { + jQuery.event.trigger("ajaxStop"); + } + } + } + + return jqXHR; + }, + + getJSON: function (url, data, callback) { + return jQuery.get(url, data, callback, "json"); + }, + + getScript: function (url, callback) { + return jQuery.get(url, undefined, callback, "script"); + } + }); + + jQuery.each(["get", "post"], function (i, method) { + jQuery[method] = function (url, data, callback, type) { + + // Shift arguments if data argument was omitted + if (isFunction(data)) { + type = type || callback; + callback = data; + data = undefined; + } + + // The url can be an options object (which then must have .url) + return jQuery.ajax(jQuery.extend({ + url: url, + type: method, + dataType: type, + data: data, + success: callback + }, jQuery.isPlainObject(url) && url)); + }; + }); + + + jQuery._evalUrl = function (url) { + return jQuery.ajax({ + url: url, + + // Make this explicit, since user can override this through ajaxSetup (#11264) + type: "GET", + dataType: "script", + cache: true, + async: false, + global: false, + "throws": true + }); + }; + + + jQuery.fn.extend({ + wrapAll: function (html) { + var wrap; + + if (this[0]) { + if (isFunction(html)) { + html = html.call(this[0]); + } + + // The elements to wrap the target around + wrap = jQuery(html, this[0].ownerDocument).eq(0).clone(true); + + if (this[0].parentNode) { + wrap.insertBefore(this[0]); + } + + wrap.map(function () { + var elem = this; + + while (elem.firstElementChild) { + elem = elem.firstElementChild; + } + + return elem; + }).append(this); + } + + return this; + }, + + wrapInner: function (html) { + if (isFunction(html)) { + return this.each(function (i) { + jQuery(this).wrapInner(html.call(this, i)); + }); + } + + return this.each(function () { + var self = jQuery(this), + contents = self.contents(); + + if (contents.length) { + contents.wrapAll(html); + + } else { + self.append(html); + } + }); + }, + + wrap: function (html) { + var htmlIsFunction = isFunction(html); + + return this.each(function (i) { + jQuery(this).wrapAll(htmlIsFunction ? html.call(this, i) : html); + }); + }, + + unwrap: function (selector) { + this.parent(selector).not("body").each(function () { + jQuery(this).replaceWith(this.childNodes); + }); + return this; + } + }); + + + jQuery.expr.pseudos.hidden = function (elem) { + return !jQuery.expr.pseudos.visible(elem); + }; + jQuery.expr.pseudos.visible = function (elem) { + return !!(elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length); + }; + + + + + jQuery.ajaxSettings.xhr = function () { + try { + return new window.XMLHttpRequest(); + } catch (e) {} + }; + + var xhrSuccessStatus = { + + // File protocol always yields status code 0, assume 200 + 0: 200, + + // Support: IE <=9 only + // #1450: sometimes IE returns 1223 when it should be 204 + 1223: 204 + }, + xhrSupported = jQuery.ajaxSettings.xhr(); + + support.cors = !!xhrSupported && ("withCredentials" in xhrSupported); + support.ajax = xhrSupported = !!xhrSupported; + + jQuery.ajaxTransport(function (options) { + var callback, errorCallback; + + // Cross domain only allowed if supported through XMLHttpRequest + if (support.cors || xhrSupported && !options.crossDomain) { + return { + send: function (headers, complete) { + var i, + xhr = options.xhr(); + + xhr.open( + options.type, + options.url, + options.async, + options.username, + options.password + ); + + // Apply custom fields if provided + if (options.xhrFields) { + for (i in options.xhrFields) { + xhr[i] = options.xhrFields[i]; + } + } + + // Override mime type if needed + if (options.mimeType && xhr.overrideMimeType) { + xhr.overrideMimeType(options.mimeType); + } + + // X-Requested-With header + // For cross-domain requests, seeing as conditions for a preflight are + // akin to a jigsaw puzzle, we simply never set it to be sure. + // (it can always be set on a per-request basis or even using ajaxSetup) + // For same-domain requests, won't change header if already provided. + if (!options.crossDomain && !headers["X-Requested-With"]) { + headers["X-Requested-With"] = "XMLHttpRequest"; + } + + // Set headers + for (i in headers) { + xhr.setRequestHeader(i, headers[i]); + } + + // Callback + callback = function (type) { + return function () { + if (callback) { + callback = errorCallback = xhr.onload = + xhr.onerror = xhr.onabort = xhr.ontimeout = + xhr.onreadystatechange = null; + + if (type === "abort") { + xhr.abort(); + } else if (type === "error") { + + // Support: IE <=9 only + // On a manual native abort, IE9 throws + // errors on any property access that is not readyState + if (typeof xhr.status !== "number") { + complete(0, "error"); + } else { + complete( + + // File: protocol always yields status 0; see #8605, #14207 + xhr.status, + xhr.statusText + ); + } + } else { + complete( + xhrSuccessStatus[xhr.status] || xhr.status, + xhr.statusText, + + // Support: IE <=9 only + // IE9 has no XHR2 but throws on binary (trac-11426) + // For XHR2 non-text, let the caller handle it (gh-2498) + (xhr.responseType || "text") !== "text" || + typeof xhr.responseText !== "string" ? { + binary: xhr.response + } : { + text: xhr.responseText + }, + xhr.getAllResponseHeaders() + ); + } + } + }; + }; + + // Listen to events + xhr.onload = callback(); + errorCallback = xhr.onerror = xhr.ontimeout = callback("error"); + + // Support: IE 9 only + // Use onreadystatechange to replace onabort + // to handle uncaught aborts + if (xhr.onabort !== undefined) { + xhr.onabort = errorCallback; + } else { + xhr.onreadystatechange = function () { + + // Check readyState before timeout as it changes + if (xhr.readyState === 4) { + + // Allow onerror to be called first, + // but that will not handle a native abort + // Also, save errorCallback to a variable + // as xhr.onerror cannot be accessed + window.setTimeout(function () { + if (callback) { + errorCallback(); + } + }); + } + }; + } + + // Create the abort callback + callback = callback("abort"); + + try { + + // Do send the request (this may raise an exception) + xhr.send(options.hasContent && options.data || null); + } catch (e) { + + // #14683: Only rethrow if this hasn't been notified as an error yet + if (callback) { + throw e; + } + } + }, + + abort: function () { + if (callback) { + callback(); + } + } + }; + } + }); + + + + + // Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) + jQuery.ajaxPrefilter(function (s) { + if (s.crossDomain) { + s.contents.script = false; + } + }); + + // Install script dataType + jQuery.ajaxSetup({ + accepts: { + script: "text/javascript, application/javascript, " + + "application/ecmascript, application/x-ecmascript" + }, + contents: { + script: /\b(?:java|ecma)script\b/ + }, + converters: { + "text script": function (text) { + jQuery.globalEval(text); + return text; + } + } + }); + + // Handle cache's special case and crossDomain + jQuery.ajaxPrefilter("script", function (s) { + if (s.cache === undefined) { + s.cache = false; + } + if (s.crossDomain) { + s.type = "GET"; + } + }); + + // Bind script tag hack transport + jQuery.ajaxTransport("script", function (s) { + + // This transport only deals with cross domain requests + if (s.crossDomain) { + var script, callback; + return { + send: function (_, complete) { + script = jQuery(" + + + + diff --git a/public/static/layuimini/js/lay-config.js b/public/static/layuimini/js/lay-config.js new file mode 100644 index 0000000..4589a11 --- /dev/null +++ b/public/static/layuimini/js/lay-config.js @@ -0,0 +1,38 @@ +/** + * date:2019/08/16 + * author:Mr.Chung + * description:此处放layui自定义扩展 + * version:2.0.4 + */ + +window.rootPath = (function (src) { + src = document.scripts[document.scripts.length - 1].src; + return src.substring(0, src.lastIndexOf("/") + 1); +})(); + +layui.config({ + base: rootPath + "lay-module/", + version: true +}).extend({ + miniAdmin: "layuimini/miniAdmin", // layuimini后台扩展 + miniMenu: "layuimini/miniMenu", // layuimini菜单扩展 + miniTab: "layuimini/miniTab", // layuimini tab扩展 + miniTheme: "layuimini/miniTheme", // layuimini 主题扩展 + miniTongji: "layuimini/miniTongji", // layuimini 统计扩展 + step: 'step-lay/step', // 分步表单扩展 + treeTable: 'treeTable/treeTable', //table树形扩展 更新为3.x版本 + tableSelect: 'tableSelect/tableSelect', // table选择扩展 + iconPickerFa: 'iconPicker/iconPickerFa', // fa图标选择扩展 + echarts: 'echarts/echarts', // echarts图表扩展 + echartsTheme: 'echarts/echartsTheme', // echarts图表主题扩展 + wangEditor: 'wangEditor/wangEditor', // wangEditor富文本扩展 + layarea: 'layarea/layarea', // 省市县区三级联动下拉选择器 + xmSelect: 'xmSelect/xm-select', // 下拉组件 + + // soul-table 自行引入soulTable.css + soulTable: 'soulTable/soulTable', + tableChild: 'soulTable/tableChild', + tableMerge: 'soulTable/tableMerge', + tableFilter: 'soulTable/tableFilter', + excel: 'soulTable/excel', +}); diff --git a/public/static/layuimini/js/lay-module/echarts/echarts.js b/public/static/layuimini/js/lay-module/echarts/echarts.js new file mode 100644 index 0000000..fcb939c --- /dev/null +++ b/public/static/layuimini/js/lay-module/echarts/echarts.js @@ -0,0 +1,19 @@ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e(t.echarts={})}(this,function(t){"use strict";function e(t){var e={},i={},n=t.match(/Firefox\/([\d.]+)/),r=t.match(/MSIE\s([\d.]+)/)||t.match(/Trident\/.+?rv:(([\d.]+))/),a=t.match(/Edge\/([\d.]+)/),o=/micromessenger/i.test(t);return n&&(i.firefox=!0,i.version=n[1]),r&&(i.ie=!0,i.version=r[1]),a&&(i.edge=!0,i.version=a[1]),o&&(i.weChat=!0),{browser:i,os:e,node:!1,canvasSupported:!!document.createElement("canvas").getContext,svgSupported:"undefined"!=typeof SVGRect,touchEventsSupported:"ontouchstart"in window&&!i.ie&&!i.edge,pointerEventsSupported:"onpointerdown"in window&&(i.edge||i.ie&&i.version>=11),domSupported:"undefined"!=typeof document}}function i(t,e){"createCanvas"===t&&(dg=null),ug[t]=e}function n(t){if(null==t||"object"!=typeof t)return t;var e=t,i=ng.call(t);if("[object Array]"===i){if(!R(t)){e=[];for(var r=0,a=t.length;a>r;r++)e[r]=n(t[r])}}else if(ig[i]){if(!R(t)){var o=t.constructor;if(t.constructor.from)e=o.from(t);else{e=new o(t.length);for(var r=0,a=t.length;a>r;r++)e[r]=n(t[r])}}}else if(!eg[i]&&!R(t)&&!T(t)){e={};for(var s in t)t.hasOwnProperty(s)&&(e[s]=n(t[s]))}return e}function r(t,e,i){if(!S(e)||!S(t))return i?n(e):t;for(var a in e)if(e.hasOwnProperty(a)){var o=t[a],s=e[a];!S(s)||!S(o)||_(s)||_(o)||T(s)||T(o)||M(s)||M(o)||R(s)||R(o)?!i&&a in t||(t[a]=n(e[a],!0)):r(o,s,i)}return t}function a(t,e){for(var i=t[0],n=1,a=t.length;a>n;n++)i=r(i,t[n],e);return i}function o(t,e){for(var i in e)e.hasOwnProperty(i)&&(t[i]=e[i]);return t}function s(t,e,i){for(var n in e)e.hasOwnProperty(n)&&(i?null!=e[n]:null==t[n])&&(t[n]=e[n]);return t}function l(){return dg||(dg=cg().getContext("2d")),dg}function h(t,e){if(t){if(t.indexOf)return t.indexOf(e);for(var i=0,n=t.length;n>i;i++)if(t[i]===e)return i}return-1}function u(t,e){function i(){}var n=t.prototype;i.prototype=e.prototype,t.prototype=new i;for(var r in n)t.prototype[r]=n[r];t.prototype.constructor=t,t.superClass=e}function c(t,e,i){t="prototype"in t?t.prototype:t,e="prototype"in e?e.prototype:e,s(t,e,i)}function d(t){return t?"string"==typeof t?!1:"number"==typeof t.length:void 0}function f(t,e,i){if(t&&e)if(t.forEach&&t.forEach===ag)t.forEach(e,i);else if(t.length===+t.length)for(var n=0,r=t.length;r>n;n++)e.call(i,t[n],n,t);else for(var a in t)t.hasOwnProperty(a)&&e.call(i,t[a],a,t)}function p(t,e,i){if(t&&e){if(t.map&&t.map===lg)return t.map(e,i);for(var n=[],r=0,a=t.length;a>r;r++)n.push(e.call(i,t[r],r,t));return n}}function g(t,e,i,n){if(t&&e){if(t.reduce&&t.reduce===hg)return t.reduce(e,i,n);for(var r=0,a=t.length;a>r;r++)i=e.call(n,i,t[r],r,t);return i}}function v(t,e,i){if(t&&e){if(t.filter&&t.filter===og)return t.filter(e,i);for(var n=[],r=0,a=t.length;a>r;r++)e.call(i,t[r],r,t)&&n.push(t[r]);return n}}function m(t,e,i){if(t&&e)for(var n=0,r=t.length;r>n;n++)if(e.call(i,t[n],n,t))return t[n]}function y(t,e){var i=sg.call(arguments,2);return function(){return t.apply(e,i.concat(sg.call(arguments)))}}function x(t){var e=sg.call(arguments,1);return function(){return t.apply(this,e.concat(sg.call(arguments)))}}function _(t){return"[object Array]"===ng.call(t)}function w(t){return"function"==typeof t}function b(t){return"[object String]"===ng.call(t)}function S(t){var e=typeof t;return"function"===e||!!t&&"object"==e}function M(t){return!!eg[ng.call(t)]}function I(t){return!!ig[ng.call(t)]}function T(t){return"object"==typeof t&&"number"==typeof t.nodeType&&"object"==typeof t.ownerDocument}function C(t){return t!==t}function A(){for(var t=0,e=arguments.length;e>t;t++)if(null!=arguments[t])return arguments[t]}function D(t,e){return null!=t?t:e}function k(t,e,i){return null!=t?t:null!=e?e:i}function P(){return Function.call.apply(sg,arguments)}function L(t){if("number"==typeof t)return[t,t,t,t];var e=t.length;return 2===e?[t[0],t[1],t[0],t[1]]:3===e?[t[0],t[1],t[2],t[1]]:t}function O(t,e){if(!t)throw new Error(e)}function z(t){return null==t?null:"function"==typeof t.trim?t.trim():t.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"")}function E(t){t[fg]=!0}function R(t){return t[fg]}function B(t){function e(t,e){i?n.set(t,e):n.set(e,t)}var i=_(t);this.data={};var n=this;t instanceof B?t.each(e):t&&f(t,e)}function N(t){return new B(t)}function F(t,e){for(var i=new t.constructor(t.length+e.length),n=0;n=0;if(r){var a="touchend"!=n?e.targetTouches[0]:e.changedTouches[0];a&&de(t,a,e,i)}else de(t,e,e,i),e.zrDelta=e.wheelDelta?e.wheelDelta/120:-(e.detail||0)/3;var o=e.button;return null==e.which&&void 0!==o&&Mg.test(e.type)&&(e.which=1&o?1:2&o?3:4&o?2:0),e}function ge(t,e,i){Sg?t.addEventListener(e,i):t.attachEvent("on"+e,i)}function ve(t,e,i){Sg?t.removeEventListener(e,i):t.detachEvent("on"+e,i)}function me(t){return t.which>1}function ye(t,e,i){return{type:t,event:i,target:e.target,topTarget:e.topTarget,cancelBubble:!1,offsetX:i.zrX,offsetY:i.zrY,gestureEvent:i.gestureEvent,pinchX:i.pinchX,pinchY:i.pinchY,pinchScale:i.pinchScale,wheelDelta:i.zrDelta,zrByTouch:i.zrByTouch,which:i.which,stop:xe}}function xe(){Ig(this.event)}function _e(){}function we(t,e,i){if(t[t.rectHover?"rectContain":"contain"](e,i)){for(var n,r=t;r;){if(r.clipPath&&!r.clipPath.contain(e,i))return!1;r.silent&&(n=!0),r=r.parent}return n?Tg:!0}return!1}function be(){var t=new Dg(6);return Se(t),t}function Se(t){return t[0]=1,t[1]=0,t[2]=0,t[3]=1,t[4]=0,t[5]=0,t}function Me(t,e){return t[0]=e[0],t[1]=e[1],t[2]=e[2],t[3]=e[3],t[4]=e[4],t[5]=e[5],t}function Ie(t,e,i){var n=e[0]*i[0]+e[2]*i[1],r=e[1]*i[0]+e[3]*i[1],a=e[0]*i[2]+e[2]*i[3],o=e[1]*i[2]+e[3]*i[3],s=e[0]*i[4]+e[2]*i[5]+e[4],l=e[1]*i[4]+e[3]*i[5]+e[5];return t[0]=n,t[1]=r,t[2]=a,t[3]=o,t[4]=s,t[5]=l,t}function Te(t,e,i){return t[0]=e[0],t[1]=e[1],t[2]=e[2],t[3]=e[3],t[4]=e[4]+i[0],t[5]=e[5]+i[1],t}function Ce(t,e,i){var n=e[0],r=e[2],a=e[4],o=e[1],s=e[3],l=e[5],h=Math.sin(i),u=Math.cos(i);return t[0]=n*u+o*h,t[1]=-n*h+o*u,t[2]=r*u+s*h,t[3]=-r*h+u*s,t[4]=u*a+h*l,t[5]=u*l-h*a,t}function Ae(t,e,i){var n=i[0],r=i[1];return t[0]=e[0]*n,t[1]=e[1]*r,t[2]=e[2]*n,t[3]=e[3]*r,t[4]=e[4]*n,t[5]=e[5]*r,t}function De(t,e){var i=e[0],n=e[2],r=e[4],a=e[1],o=e[3],s=e[5],l=i*o-a*n;return l?(l=1/l,t[0]=o*l,t[1]=-a*l,t[2]=-n*l,t[3]=i*l,t[4]=(n*s-o*r)*l,t[5]=(a*r-i*s)*l,t):null}function ke(t){var e=be();return Me(e,t),e}function Pe(t){return t>Lg||-Lg>t}function Le(t){this._target=t.target,this._life=t.life||1e3,this._delay=t.delay||0,this._initialized=!1,this.loop=null==t.loop?!1:t.loop,this.gap=t.gap||0,this.easing=t.easing||"Linear",this.onframe=t.onframe,this.ondestroy=t.ondestroy,this.onrestart=t.onrestart,this._pausedTime=0,this._paused=!1}function Oe(t){return t=Math.round(t),0>t?0:t>255?255:t}function ze(t){return t=Math.round(t),0>t?0:t>360?360:t}function Ee(t){return 0>t?0:t>1?1:t}function Re(t){return Oe(t.length&&"%"===t.charAt(t.length-1)?parseFloat(t)/100*255:parseInt(t,10))}function Be(t){return Ee(t.length&&"%"===t.charAt(t.length-1)?parseFloat(t)/100:parseFloat(t))}function Ne(t,e,i){return 0>i?i+=1:i>1&&(i-=1),1>6*i?t+(e-t)*i*6:1>2*i?e:2>3*i?t+(e-t)*(2/3-i)*6:t}function Fe(t,e,i){return t+(e-t)*i}function Ve(t,e,i,n,r){return t[0]=e,t[1]=i,t[2]=n,t[3]=r,t}function We(t,e){return t[0]=e[0],t[1]=e[1],t[2]=e[2],t[3]=e[3],t}function Ge(t,e){Yg&&We(Yg,e),Yg=Xg.put(t,Yg||e.slice())}function He(t,e){if(t){e=e||[];var i=Xg.get(t);if(i)return We(e,i);t+="";var n=t.replace(/ /g,"").toLowerCase();if(n in Zg)return We(e,Zg[n]),Ge(t,e),e;if("#"!==n.charAt(0)){var r=n.indexOf("("),a=n.indexOf(")");if(-1!==r&&a+1===n.length){var o=n.substr(0,r),s=n.substr(r+1,a-(r+1)).split(","),l=1;switch(o){case"rgba":if(4!==s.length)return void Ve(e,0,0,0,1);l=Be(s.pop());case"rgb":return 3!==s.length?void Ve(e,0,0,0,1):(Ve(e,Re(s[0]),Re(s[1]),Re(s[2]),l),Ge(t,e),e);case"hsla":return 4!==s.length?void Ve(e,0,0,0,1):(s[3]=Be(s[3]),Ze(s,e),Ge(t,e),e);case"hsl":return 3!==s.length?void Ve(e,0,0,0,1):(Ze(s,e),Ge(t,e),e);default:return}}Ve(e,0,0,0,1)}else{if(4===n.length){var h=parseInt(n.substr(1),16);return h>=0&&4095>=h?(Ve(e,(3840&h)>>4|(3840&h)>>8,240&h|(240&h)>>4,15&h|(15&h)<<4,1),Ge(t,e),e):void Ve(e,0,0,0,1)}if(7===n.length){var h=parseInt(n.substr(1),16);return h>=0&&16777215>=h?(Ve(e,(16711680&h)>>16,(65280&h)>>8,255&h,1),Ge(t,e),e):void Ve(e,0,0,0,1)}}}}function Ze(t,e){var i=(parseFloat(t[0])%360+360)%360/360,n=Be(t[1]),r=Be(t[2]),a=.5>=r?r*(n+1):r+n-r*n,o=2*r-a;return e=e||[],Ve(e,Oe(255*Ne(o,a,i+1/3)),Oe(255*Ne(o,a,i)),Oe(255*Ne(o,a,i-1/3)),1),4===t.length&&(e[3]=t[3]),e}function Xe(t){if(t){var e,i,n=t[0]/255,r=t[1]/255,a=t[2]/255,o=Math.min(n,r,a),s=Math.max(n,r,a),l=s-o,h=(s+o)/2;if(0===l)e=0,i=0;else{i=.5>h?l/(s+o):l/(2-s-o);var u=((s-n)/6+l/2)/l,c=((s-r)/6+l/2)/l,d=((s-a)/6+l/2)/l;n===s?e=d-c:r===s?e=1/3+u-d:a===s&&(e=2/3+c-u),0>e&&(e+=1),e>1&&(e-=1)}var f=[360*e,i,h];return null!=t[3]&&f.push(t[3]),f}}function Ye(t,e){var i=He(t);if(i){for(var n=0;3>n;n++)i[n]=0>e?i[n]*(1-e)|0:(255-i[n])*e+i[n]|0,i[n]>255?i[n]=255:t[n]<0&&(i[n]=0);return Qe(i,4===i.length?"rgba":"rgb")}}function je(t){var e=He(t);return e?((1<<24)+(e[0]<<16)+(e[1]<<8)+ +e[2]).toString(16).slice(1):void 0}function qe(t,e,i){if(e&&e.length&&t>=0&&1>=t){i=i||[];var n=t*(e.length-1),r=Math.floor(n),a=Math.ceil(n),o=e[r],s=e[a],l=n-r;return i[0]=Oe(Fe(o[0],s[0],l)),i[1]=Oe(Fe(o[1],s[1],l)),i[2]=Oe(Fe(o[2],s[2],l)),i[3]=Ee(Fe(o[3],s[3],l)),i}}function Ue(t,e,i){if(e&&e.length&&t>=0&&1>=t){var n=t*(e.length-1),r=Math.floor(n),a=Math.ceil(n),o=He(e[r]),s=He(e[a]),l=n-r,h=Qe([Oe(Fe(o[0],s[0],l)),Oe(Fe(o[1],s[1],l)),Oe(Fe(o[2],s[2],l)),Ee(Fe(o[3],s[3],l))],"rgba");return i?{color:h,leftIndex:r,rightIndex:a,value:n}:h}}function $e(t,e,i,n){return t=He(t),t?(t=Xe(t),null!=e&&(t[0]=ze(e)),null!=i&&(t[1]=Be(i)),null!=n&&(t[2]=Be(n)),Qe(Ze(t),"rgba")):void 0}function Ke(t,e){return t=He(t),t&&null!=e?(t[3]=Ee(e),Qe(t,"rgba")):void 0}function Qe(t,e){if(t&&t.length){var i=t[0]+","+t[1]+","+t[2];return("rgba"===e||"hsva"===e||"hsla"===e)&&(i+=","+t[3]),e+"("+i+")"}}function Je(t,e){return t[e]}function ti(t,e,i){t[e]=i}function ei(t,e,i){return(e-t)*i+t}function ii(t,e,i){return i>.5?e:t}function ni(t,e,i,n,r){var a=t.length;if(1==r)for(var o=0;a>o;o++)n[o]=ei(t[o],e[o],i);else for(var s=a&&t[0].length,o=0;a>o;o++)for(var l=0;s>l;l++)n[o][l]=ei(t[o][l],e[o][l],i)}function ri(t,e,i){var n=t.length,r=e.length;if(n!==r){var a=n>r;if(a)t.length=r;else for(var o=n;r>o;o++)t.push(1===i?e[o]:$g.call(e[o]))}for(var s=t[0]&&t[0].length,o=0;ol;l++)isNaN(t[o][l])&&(t[o][l]=e[o][l])}function ai(t,e,i){if(t===e)return!0;var n=t.length;if(n!==e.length)return!1;if(1===i){for(var r=0;n>r;r++)if(t[r]!==e[r])return!1}else for(var a=t[0].length,r=0;n>r;r++)for(var o=0;a>o;o++)if(t[r][o]!==e[r][o])return!1;return!0}function oi(t,e,i,n,r,a,o,s,l){var h=t.length;if(1==l)for(var u=0;h>u;u++)s[u]=si(t[u],e[u],i[u],n[u],r,a,o);else for(var c=t[0].length,u=0;h>u;u++)for(var d=0;c>d;d++)s[u][d]=si(t[u][d],e[u][d],i[u][d],n[u][d],r,a,o)}function si(t,e,i,n,r,a,o){var s=.5*(i-t),l=.5*(n-e);return(2*(e-i)+s+l)*o+(-3*(e-i)-2*s-l)*a+s*r+e}function li(t){if(d(t)){var e=t.length;if(d(t[0])){for(var i=[],n=0;e>n;n++)i.push($g.call(t[n]));return i}return $g.call(t)}return t}function hi(t){return t[0]=Math.floor(t[0]),t[1]=Math.floor(t[1]),t[2]=Math.floor(t[2]),"rgba("+t.join(",")+")"}function ui(t){var e=t[t.length-1].value;return d(e&&e[0])?2:1}function ci(t,e,i,n,r,a){var o=t._getter,s=t._setter,l="spline"===e,h=n.length;if(h){var u,c=n[0].value,f=d(c),p=!1,g=!1,v=f?ui(n):0;n.sort(function(t,e){return t.time-e.time}),u=n[h-1].time;for(var m=[],y=[],x=n[0].value,_=!0,w=0;h>w;w++){m.push(n[w].time/u);var b=n[w].value;if(f&&ai(b,x,v)||!f&&b===x||(_=!1),x=b,"string"==typeof b){var S=He(b);S?(b=S,p=!0):g=!0}y.push(b)}if(a||!_){for(var M=y[h-1],w=0;h-1>w;w++)f?ri(y[w],M,v):!isNaN(y[w])||isNaN(M)||g||p||(y[w]=M);f&&ri(o(t._target,r),M,v);var I,T,C,A,D,k,P=0,L=0;if(p)var O=[0,0,0,0];var z=function(t,e){var i;if(0>e)i=0;else if(L>e){for(I=Math.min(P+1,h-1),i=I;i>=0&&!(m[i]<=e);i--);i=Math.min(i,h-2)}else{for(i=P;h>i&&!(m[i]>e);i++);i=Math.min(i-1,h-2)}P=i,L=e;var n=m[i+1]-m[i];if(0!==n)if(T=(e-m[i])/n,l)if(A=y[i],C=y[0===i?i:i-1],D=y[i>h-2?h-1:i+1],k=y[i>h-3?h-1:i+2],f)oi(C,A,D,k,T,T*T,T*T*T,o(t,r),v);else{var a;if(p)a=oi(C,A,D,k,T,T*T,T*T*T,O,1),a=hi(O);else{if(g)return ii(A,D,T);a=si(C,A,D,k,T,T*T,T*T*T)}s(t,r,a)}else if(f)ni(y[i],y[i+1],T,o(t,r),v);else{var a;if(p)ni(y[i],y[i+1],T,O,1),a=hi(O);else{if(g)return ii(y[i],y[i+1],T);a=ei(y[i],y[i+1],T)}s(t,r,a)}},E=new Le({target:t._target,life:u,loop:t._loop,delay:t._delay,onframe:z,ondestroy:i});return e&&"spline"!==e&&(E.easing=e),E}}}function di(t,e,i,n,r,a,o,s){function l(){u--,u||a&&a()}b(n)?(a=r,r=n,n=0):w(r)?(a=r,r="linear",n=0):w(n)?(a=n,n=0):w(i)?(a=i,i=500):i||(i=500),t.stopAnimation(),fi(t,"",t,e,i,n,s);var h=t.animators.slice(),u=h.length;u||a&&a();for(var c=0;c0&&t.animate(e,!1).when(null==r?500:r,s).delay(a||0)}function pi(t,e,i,n){if(e){var r={};r[e]={},r[e][i]=n,t.attr(r)}else t.attr(i,n)}function gi(t,e,i,n){0>i&&(t+=i,i=-i),0>n&&(e+=n,n=-n),this.x=t,this.y=e,this.width=i,this.height=n}function vi(t){for(var e=0;t>=hv;)e|=1&t,t>>=1;return t+e}function mi(t,e,i,n){var r=e+1;if(r===i)return 1;if(n(t[r++],t[e])<0){for(;i>r&&n(t[r],t[r-1])<0;)r++;yi(t,e,r)}else for(;i>r&&n(t[r],t[r-1])>=0;)r++;return r-e}function yi(t,e,i){for(i--;i>e;){var n=t[e];t[e++]=t[i],t[i--]=n}}function xi(t,e,i,n,r){for(n===e&&n++;i>n;n++){for(var a,o=t[n],s=e,l=n;l>s;)a=s+l>>>1,r(o,t[a])<0?l=a:s=a+1;var h=n-s;switch(h){case 3:t[s+3]=t[s+2];case 2:t[s+2]=t[s+1];case 1:t[s+1]=t[s];break;default:for(;h>0;)t[s+h]=t[s+h-1],h--}t[s]=o}}function _i(t,e,i,n,r,a){var o=0,s=0,l=1;if(a(t,e[i+r])>0){for(s=n-r;s>l&&a(t,e[i+r+l])>0;)o=l,l=(l<<1)+1,0>=l&&(l=s);l>s&&(l=s),o+=r,l+=r}else{for(s=r+1;s>l&&a(t,e[i+r-l])<=0;)o=l,l=(l<<1)+1,0>=l&&(l=s);l>s&&(l=s);var h=o;o=r-l,l=r-h}for(o++;l>o;){var u=o+(l-o>>>1);a(t,e[i+u])>0?o=u+1:l=u}return l}function wi(t,e,i,n,r,a){var o=0,s=0,l=1;if(a(t,e[i+r])<0){for(s=r+1;s>l&&a(t,e[i+r-l])<0;)o=l,l=(l<<1)+1,0>=l&&(l=s);l>s&&(l=s);var h=o;o=r-l,l=r-h}else{for(s=n-r;s>l&&a(t,e[i+r+l])>=0;)o=l,l=(l<<1)+1,0>=l&&(l=s);l>s&&(l=s),o+=r,l+=r}for(o++;l>o;){var u=o+(l-o>>>1);a(t,e[i+u])<0?l=u:o=u+1}return l}function bi(t,e){function i(t,e){l[c]=t,h[c]=e,c+=1}function n(){for(;c>1;){var t=c-2;if(t>=1&&h[t-1]<=h[t]+h[t+1]||t>=2&&h[t-2]<=h[t]+h[t-1])h[t-1]h[t+1])break;a(t)}}function r(){for(;c>1;){var t=c-2;t>0&&h[t-1]=r?o(n,r,a,u):s(n,r,a,u)))}function o(i,n,r,a){var o=0;for(o=0;n>o;o++)d[o]=t[i+o];var s=0,l=r,h=i;if(t[h++]=t[l++],0!==--a){if(1===n){for(o=0;a>o;o++)t[h+o]=t[l+o];return void(t[h+a]=d[s])}for(var c,f,p,g=u;;){c=0,f=0,p=!1;do if(e(t[l],d[s])<0){if(t[h++]=t[l++],f++,c=0,0===--a){p=!0;break}}else if(t[h++]=d[s++],c++,f=0,1===--n){p=!0;break}while(g>(c|f));if(p)break;do{if(c=wi(t[l],d,s,n,0,e),0!==c){for(o=0;c>o;o++)t[h+o]=d[s+o];if(h+=c,s+=c,n-=c,1>=n){p=!0;break}}if(t[h++]=t[l++],0===--a){p=!0;break}if(f=_i(d[s],t,l,a,0,e),0!==f){for(o=0;f>o;o++)t[h+o]=t[l+o];if(h+=f,l+=f,a-=f,0===a){p=!0;break}}if(t[h++]=d[s++],1===--n){p=!0;break}g--}while(c>=uv||f>=uv);if(p)break;0>g&&(g=0),g+=2}if(u=g,1>u&&(u=1),1===n){for(o=0;a>o;o++)t[h+o]=t[l+o];t[h+a]=d[s]}else{if(0===n)throw new Error;for(o=0;n>o;o++)t[h+o]=d[s+o]}}else for(o=0;n>o;o++)t[h+o]=d[s+o]}function s(i,n,r,a){var o=0;for(o=0;a>o;o++)d[o]=t[r+o];var s=i+n-1,l=a-1,h=r+a-1,c=0,f=0;if(t[h--]=t[s--],0!==--n){if(1===a){for(h-=n,s-=n,f=h+1,c=s+1,o=n-1;o>=0;o--)t[f+o]=t[c+o];return void(t[h]=d[l])}for(var p=u;;){var g=0,v=0,m=!1;do if(e(d[l],t[s])<0){if(t[h--]=t[s--],g++,v=0,0===--n){m=!0;break}}else if(t[h--]=d[l--],v++,g=0,1===--a){m=!0;break}while(p>(g|v));if(m)break;do{if(g=n-wi(d[l],t,i,n,n-1,e),0!==g){for(h-=g,s-=g,n-=g,f=h+1,c=s+1,o=g-1;o>=0;o--)t[f+o]=t[c+o];if(0===n){m=!0;break}}if(t[h--]=d[l--],1===--a){m=!0;break}if(v=a-_i(t[s],d,0,a,a-1,e),0!==v){for(h-=v,l-=v,a-=v,f=h+1,c=l+1,o=0;v>o;o++)t[f+o]=d[c+o];if(1>=a){m=!0;break}}if(t[h--]=t[s--],0===--n){m=!0;break}p--}while(g>=uv||v>=uv);if(m)break;0>p&&(p=0),p+=2}if(u=p,1>u&&(u=1),1===a){for(h-=n,s-=n,f=h+1,c=s+1,o=n-1;o>=0;o--)t[f+o]=t[c+o];t[h]=d[l]}else{if(0===a)throw new Error;for(c=h-(a-1),o=0;a>o;o++)t[c+o]=d[o]}}else for(c=h-(a-1),o=0;a>o;o++)t[c+o]=d[o]}var l,h,u=uv,c=0,d=[];l=[],h=[],this.mergeRuns=n,this.forceMergeRuns=r,this.pushRun=i}function Si(t,e,i,n){i||(i=0),n||(n=t.length);var r=n-i;if(!(2>r)){var a=0;if(hv>r)return a=mi(t,i,n,e),void xi(t,i,n,i+a,e);var o=new bi(t,e),s=vi(r);do{if(a=mi(t,i,n,e),s>a){var l=r;l>s&&(l=s),xi(t,i,i+l,i+a,e),a=l}o.pushRun(i,a),o.mergeRuns(),r-=a,i+=a}while(0!==r);o.forceMergeRuns()}}function Mi(t,e){return t.zlevel===e.zlevel?t.z===e.z?t.z2-e.z2:t.z-e.z:t.zlevel-e.zlevel}function Ii(t,e,i){var n=null==e.x?0:e.x,r=null==e.x2?1:e.x2,a=null==e.y?0:e.y,o=null==e.y2?0:e.y2;e.global||(n=n*i.width+i.x,r=r*i.width+i.x,a=a*i.height+i.y,o=o*i.height+i.y),n=isNaN(n)?0:n,r=isNaN(r)?1:r,a=isNaN(a)?0:a,o=isNaN(o)?0:o;var s=t.createLinearGradient(n,a,r,o);return s}function Ti(t,e,i){var n=i.width,r=i.height,a=Math.min(n,r),o=null==e.x?.5:e.x,s=null==e.y?.5:e.y,l=null==e.r?.5:e.r;e.global||(o=o*n+i.x,s=s*r+i.y,l*=a);var h=t.createRadialGradient(o,s,0,o,s,l);return h}function Ci(){return!1}function Ai(t,e,i){var n=cg(),r=e.getWidth(),a=e.getHeight(),o=n.style;return o&&(o.position="absolute",o.left=0,o.top=0,o.width=r+"px",o.height=a+"px",n.setAttribute("data-zr-dom-id",t)),n.width=r*i,n.height=a*i,n}function Di(t){if("string"==typeof t){var e=bv.get(t);return e&&e.image}return t}function ki(t,e,i,n,r){if(t){if("string"==typeof t){if(e&&e.__zrImageSrc===t||!i)return e;var a=bv.get(t),o={hostEl:i,cb:n,cbPayload:r};return a?(e=a.image,!Li(e)&&a.pending.push(o)):(!e&&(e=new Image),e.onload=e.onerror=Pi,bv.put(t,e.__cachedImgObj={image:e,pending:[o]}),e.src=e.__zrImageSrc=t),e}return t}return e}function Pi(){var t=this.__cachedImgObj;this.onload=this.onerror=this.__cachedImgObj=null;for(var e=0;ea;a++)r=Math.max(Yi(n[a],e).width,r);return Mv>Iv&&(Mv=0,Sv={}),Mv++,Sv[i]=r,r}function Ei(t,e,i,n,r,a,o){return a?Bi(t,e,i,n,r,a,o):Ri(t,e,i,n,r,o)}function Ri(t,e,i,n,r,a){var o=ji(t,e,r,a),s=zi(t,e);r&&(s+=r[1]+r[3]);var l=o.outerHeight,h=Ni(0,s,i),u=Fi(0,l,n),c=new gi(h,u,s,l);return c.lineHeight=o.lineHeight,c}function Bi(t,e,i,n,r,a,o){var s=qi(t,{rich:a,truncate:o,font:e,textAlign:i,textPadding:r}),l=s.outerWidth,h=s.outerHeight,u=Ni(0,l,i),c=Fi(0,h,n);return new gi(u,c,l,h)}function Ni(t,e,i){return"right"===i?t-=e:"center"===i&&(t-=e/2),t}function Fi(t,e,i){return"middle"===i?t-=e/2:"bottom"===i&&(t-=e),t}function Vi(t,e,i){var n=e.x,r=e.y,a=e.height,o=e.width,s=a/2,l="left",h="top";switch(t){case"left":n-=i,r+=s,l="right",h="middle";break;case"right":n+=i+o,r+=s,h="middle";break;case"top":n+=o/2,r-=i,l="center",h="bottom";break;case"bottom":n+=o/2,r+=a+i,l="center";break;case"inside":n+=o/2,r+=s,l="center",h="middle";break;case"insideLeft":n+=i,r+=s,h="middle";break;case"insideRight":n+=o-i,r+=s,l="right",h="middle";break;case"insideTop":n+=o/2,r+=i,l="center";break;case"insideBottom":n+=o/2,r+=a-i,l="center",h="bottom";break;case"insideTopLeft":n+=i,r+=i;break;case"insideTopRight":n+=o-i,r+=i,l="right";break;case"insideBottomLeft":n+=i,r+=a-i,h="bottom";break;case"insideBottomRight":n+=o-i,r+=a-i,l="right",h="bottom"}return{x:n,y:r,textAlign:l,textVerticalAlign:h}}function Wi(t,e,i,n,r){if(!e)return"";var a=(t+"").split("\n");r=Gi(e,i,n,r);for(var o=0,s=a.length;s>o;o++)a[o]=Hi(a[o],r);return a.join("\n")}function Gi(t,e,i,n){n=o({},n),n.font=e;var i=D(i,"...");n.maxIterations=D(n.maxIterations,2);var r=n.minChar=D(n.minChar,0);n.cnCharWidth=zi("国",e);var a=n.ascCharWidth=zi("a",e);n.placeholder=D(n.placeholder,"");for(var s=t=Math.max(0,t-1),l=0;r>l&&s>=a;l++)s-=a;var h=zi(i);return h>s&&(i="",h=0),s=t-h,n.ellipsis=i,n.ellipsisWidth=h,n.contentWidth=s,n.containerWidth=t,n}function Hi(t,e){var i=e.containerWidth,n=e.font,r=e.contentWidth;if(!i)return"";var a=zi(t,n);if(i>=a)return t;for(var o=0;;o++){if(r>=a||o>=e.maxIterations){t+=e.ellipsis;break}var s=0===o?Zi(t,r,e.ascCharWidth,e.cnCharWidth):a>0?Math.floor(t.length*r/a):0;t=t.substr(0,s),a=zi(t,n)}return""===t&&(t=e.placeholder),t}function Zi(t,e,i,n){for(var r=0,a=0,o=t.length;o>a&&e>r;a++){var s=t.charCodeAt(a);r+=s>=0&&127>=s?i:n}return a}function Xi(t){return zi("国",t)}function Yi(t,e){return Av.measureText(t,e)}function ji(t,e,i,n){null!=t&&(t+="");var r=Xi(e),a=t?t.split("\n"):[],o=a.length*r,s=o;if(i&&(s+=i[0]+i[2]),t&&n){var l=n.outerHeight,h=n.outerWidth;if(null!=l&&s>l)t="",a=[];else if(null!=h)for(var u=Gi(h-(i?i[1]+i[3]:0),e,n.ellipsis,{minChar:n.minChar,placeholder:n.placeholder}),c=0,d=a.length;d>c;c++)a[c]=Hi(a[c],u)}return{lines:a,height:o,outerHeight:s,lineHeight:r}}function qi(t,e){var i={lines:[],width:0,height:0};if(null!=t&&(t+=""),!t)return i;for(var n,r=Tv.lastIndex=0;null!=(n=Tv.exec(t));){var a=n.index;a>r&&Ui(i,t.substring(r,a)),Ui(i,n[2],n[1]),r=Tv.lastIndex}rf)return{lines:[],width:0,height:0};x.textWidth=zi(x.text,b);var M=_.textWidth,I=null==M||"auto"===M;if("string"==typeof M&&"%"===M.charAt(M.length-1))x.percentWidth=M,h.push(x),M=0;else{if(I){M=x.textWidth;var T=_.textBackgroundColor,C=T&&T.image;C&&(C=Di(C),Li(C)&&(M=Math.max(M,C.width*S/C.height)))}var A=w?w[1]+w[3]:0;M+=A;var P=null!=d?d-m:null;null!=P&&M>P&&(!I||A>P?(x.text="",x.textWidth=M=0):(x.text=Wi(x.text,P-A,b,c.ellipsis,{minChar:c.minChar}),x.textWidth=zi(x.text,b),M=x.textWidth+A))}m+=x.width=M,_&&(v=Math.max(v,x.lineHeight))}g.width=m,g.lineHeight=v,s+=v,l=Math.max(l,m)}i.outerWidth=i.width=D(e.textWidth,l),i.outerHeight=i.height=D(e.textHeight,s),u&&(i.outerWidth+=u[1]+u[3],i.outerHeight+=u[0]+u[2]);for(var p=0;pl&&(o+=l,l=-l),0>h&&(s+=h,h=-h),"number"==typeof u?i=n=r=a=u:u instanceof Array?1===u.length?i=n=r=a=u[0]:2===u.length?(i=r=u[0],n=a=u[1]):3===u.length?(i=u[0],n=a=u[1],r=u[2]):(i=u[0],n=u[1],r=u[2],a=u[3]):i=n=r=a=0;var c;i+n>l&&(c=i+n,i*=l/c,n*=l/c),r+a>l&&(c=r+a,r*=l/c,a*=l/c),n+r>h&&(c=n+r,n*=h/c,r*=h/c),i+a>h&&(c=i+a,i*=h/c,a*=h/c),t.moveTo(o+i,s),t.lineTo(o+l-n,s),0!==n&&t.arc(o+l-n,s+n,n,-Math.PI/2,0),t.lineTo(o+l,s+h-r),0!==r&&t.arc(o+l-r,s+h-r,r,0,Math.PI/2),t.lineTo(o+a,s+h),0!==a&&t.arc(o+a,s+h-a,a,Math.PI/2,Math.PI),t.lineTo(o,s+i),0!==i&&t.arc(o+i,s+i,i,Math.PI,1.5*Math.PI)}function Qi(t){return Ji(t),f(t.rich,Ji),t}function Ji(t){if(t){t.font=$i(t);var e=t.textAlign;"middle"===e&&(e="center"),t.textAlign=null==e||Dv[e]?e:"left";var i=t.textVerticalAlign||t.textBaseline;"center"===i&&(i="middle"),t.textVerticalAlign=null==i||kv[i]?i:"top";var n=t.textPadding;n&&(t.textPadding=L(t.textPadding))}}function tn(t,e,i,n,r,a){n.rich?nn(t,e,i,n,r):en(t,e,i,n,r,a)}function en(t,e,i,n,r,a){var o=a&&a.style,s=o&&"text"===a.type,l=n.font||Cv;s&&l===(o.font||Cv)||(e.font=l);var h=t.__computedFont;t.__styleFont!==l&&(t.__styleFont=l,h=t.__computedFont=e.font);var u=n.textPadding,c=t.__textCotentBlock;(!c||t.__dirtyText)&&(c=t.__textCotentBlock=ji(i,h,u,n.truncate));var d=c.outerHeight,f=c.lines,p=c.lineHeight,g=un(d,n,r),v=g.baseX,m=g.baseY,y=g.textAlign||"left",x=g.textVerticalAlign;an(e,n,r,v,m);var _=Fi(m,d,x),w=v,b=_,S=sn(n);if(S||u){var M=zi(i,h),I=M;u&&(I+=u[1]+u[3]);var T=Ni(v,I,y);S&&ln(t,e,n,T,_,I,d),u&&(w=gn(v,y,u),b+=u[0])}e.textAlign=y,e.textBaseline="middle";for(var C=0;CT&&(_=b[T],!_.textAlign||"left"===_.textAlign);)on(t,e,_,n,M,m,C,"left"),I-=_.width,C+=_.width,T++;for(;D>=0&&(_=b[D],"right"===_.textAlign);)on(t,e,_,n,M,m,A,"right"),I-=_.width,A-=_.width,D--;for(C+=(a-(C-v)-(y-A)-I)/2;D>=T;)_=b[T],on(t,e,_,n,M,m,C+_.width/2,"center"),C+=_.width,T++;m+=M}}function an(t,e,i,n,r){if(i&&e.textRotation){var a=e.textOrigin;"center"===a?(n=i.width/2+i.x,r=i.height/2+i.y):a&&(n=a[0]+i.x,r=a[1]+i.y),t.translate(n,r),t.rotate(-e.textRotation),t.translate(-n,-r)}}function on(t,e,i,n,r,a,o,s){var l=n.rich[i.styleName]||{};l.text=i.text;var h=i.textVerticalAlign,u=a+r/2;"top"===h?u=a+i.height/2:"bottom"===h&&(u=a+r-i.height/2),!i.isLineHolder&&sn(l)&&ln(t,e,l,"right"===s?o-i.width:"center"===s?o-i.width/2:o,u-i.height/2,i.width,i.height);var c=i.textPadding;c&&(o=gn(o,s,c),u-=i.height/2-c[2]-i.textHeight/2),cn(e,"shadowBlur",k(l.textShadowBlur,n.textShadowBlur,0)),cn(e,"shadowColor",l.textShadowColor||n.textShadowColor||"transparent"),cn(e,"shadowOffsetX",k(l.textShadowOffsetX,n.textShadowOffsetX,0)),cn(e,"shadowOffsetY",k(l.textShadowOffsetY,n.textShadowOffsetY,0)),cn(e,"textAlign",s),cn(e,"textBaseline","middle"),cn(e,"font",i.font||Cv);var d=dn(l.textStroke||n.textStroke,p),f=fn(l.textFill||n.textFill),p=D(l.textStrokeWidth,n.textStrokeWidth);d&&(cn(e,"lineWidth",p),cn(e,"strokeStyle",d),e.strokeText(i.text,o,u)),f&&(cn(e,"fillStyle",f),e.fillText(i.text,o,u))}function sn(t){return t.textBackgroundColor||t.textBorderWidth&&t.textBorderColor}function ln(t,e,i,n,r,a,o){var s=i.textBackgroundColor,l=i.textBorderWidth,h=i.textBorderColor,u=b(s);if(cn(e,"shadowBlur",i.textBoxShadowBlur||0),cn(e,"shadowColor",i.textBoxShadowColor||"transparent"),cn(e,"shadowOffsetX",i.textBoxShadowOffsetX||0),cn(e,"shadowOffsetY",i.textBoxShadowOffsetY||0),u||l&&h){e.beginPath();var c=i.textBorderRadius;c?Ki(e,{x:n,y:r,width:a,height:o,r:c}):e.rect(n,r,a,o),e.closePath()}if(u)if(cn(e,"fillStyle",s),null!=i.fillOpacity){var d=e.globalAlpha;e.globalAlpha=i.fillOpacity*i.opacity,e.fill(),e.globalAlpha=d}else e.fill();else if(w(s))cn(e,"fillStyle",s(i)),e.fill();else if(S(s)){var f=s.image;f=ki(f,null,t,hn,s),f&&Li(f)&&e.drawImage(f,n,r,a,o)}if(l&&h)if(cn(e,"lineWidth",l),cn(e,"strokeStyle",h),null!=i.strokeOpacity){var d=e.globalAlpha;e.globalAlpha=i.strokeOpacity*i.opacity,e.stroke(),e.globalAlpha=d}else e.stroke()}function hn(t,e){e.image=t}function un(t,e,i){var n=e.x||0,r=e.y||0,a=e.textAlign,o=e.textVerticalAlign;if(i){var s=e.textPosition;if(s instanceof Array)n=i.x+pn(s[0],i.width),r=i.y+pn(s[1],i.height);else{var l=Vi(s,i,e.textDistance);n=l.x,r=l.y,a=a||l.textAlign,o=o||l.textVerticalAlign}var h=e.textOffset;h&&(n+=h[0],r+=h[1])}return{baseX:n,baseY:r,textAlign:a,textVerticalAlign:o}}function cn(t,e,i){return t[e]=fv(t,e,i),t[e]}function dn(t,e){return null==t||0>=e||"transparent"===t||"none"===t?null:t.image||t.colorStops?"#000":t}function fn(t){return null==t||"none"===t?null:t.image||t.colorStops?"#000":t}function pn(t,e){return"string"==typeof t?t.lastIndexOf("%")>=0?parseFloat(t)/100*e:parseFloat(t):t}function gn(t,e,i){return"right"===e?t-i[1]:"center"===e?t+i[3]/2-i[1]/2:t+i[3]}function vn(t,e){return null!=t&&(t||e.textBackgroundColor||e.textBorderWidth&&e.textBorderColor||e.textPadding)}function mn(t){t=t||{},rv.call(this,t);for(var e in t)t.hasOwnProperty(e)&&"style"!==e&&(this[e]=t[e]);this.style=new gv(t.style,this),this._rect=null,this.__clipPaths=[] +}function yn(t){mn.call(this,t)}function xn(t){return parseInt(t,10)}function _n(t){return t?t.__builtin__?!0:"function"!=typeof t.resize||"function"!=typeof t.refresh?!1:!0:!1}function wn(t,e,i){return Nv.copy(t.getBoundingRect()),t.transform&&Nv.applyTransform(t.transform),Fv.width=e,Fv.height=i,!Nv.intersect(Fv)}function bn(t,e){if(t==e)return!1;if(!t||!e||t.length!==e.length)return!0;for(var i=0;in;n++){var a=i[n];!t.emphasis[e].hasOwnProperty(a)&&t[e].hasOwnProperty(a)&&(t.emphasis[e][a]=t[e][a])}}}function Vn(t){return!rm(t)||am(t)||t instanceof Date?t:t.value}function Wn(t){return rm(t)&&!(t instanceof Array)}function Gn(t,e){e=(e||[]).slice();var i=p(t||[],function(t){return{exist:t}});return nm(e,function(t,n){if(rm(t)){for(var r=0;r=i.length&&i.push({option:t})}}),i}function Hn(t){var e=N();nm(t,function(t){var i=t.exist;i&&e.set(i.id,t)}),nm(t,function(t){var i=t.option;O(!i||null==i.id||!e.get(i.id)||e.get(i.id)===t,"id duplicates: "+(i&&i.id)),i&&null!=i.id&&e.set(i.id,t),!t.keyInfo&&(t.keyInfo={})}),nm(t,function(t,i){var n=t.exist,r=t.option,a=t.keyInfo;if(rm(r)){if(a.name=null!=r.name?r.name+"":n?n.name:om+i,n)a.id=n.id;else if(null!=r.id)a.id=r.id+"";else{var o=0;do a.id="\x00"+a.name+"\x00"+o++;while(e.get(a.id))}e.set(a.id,t)}})}function Zn(t){var e=t.name;return!(!e||!e.indexOf(om))}function Xn(t){return rm(t)&&t.id&&0===(t.id+"").indexOf("\x00_ec_\x00")}function Yn(t,e){return null!=e.dataIndexInside?e.dataIndexInside:null!=e.dataIndex?_(e.dataIndex)?p(e.dataIndex,function(e){return t.indexOfRawIndex(e)}):t.indexOfRawIndex(e.dataIndex):null!=e.name?_(e.name)?p(e.name,function(e){return t.indexOfName(e)}):t.indexOfName(e.name):void 0}function jn(){var t="__\x00ec_inner_"+lm++ +"_"+Math.random().toFixed(5);return function(e){return e[t]||(e[t]={})}}function qn(t,e,i){if(b(e)){var n={};n[e+"Index"]=0,e=n}var r=i&&i.defaultMainType;!r||Un(e,r+"Index")||Un(e,r+"Id")||Un(e,r+"Name")||(e[r+"Index"]=0);var a={};return nm(e,function(n,r){var n=e[r];if("dataIndex"===r||"dataIndexInside"===r)return void(a[r]=n);var o=r.match(/^(\w+)(Index|Id|Name)$/)||[],s=o[1],l=(o[2]||"").toLowerCase();if(!(!s||!l||null==n||"index"===l&&"none"===n||i&&i.includeMainTypes&&h(i.includeMainTypes,s)<0)){var u={mainType:s};("index"!==l||"all"!==n)&&(u[l]=n);var c=t.queryComponents(u);a[s+"Models"]=c,a[s+"Model"]=c[0]}}),a}function Un(t,e){return t&&t.hasOwnProperty(e)}function $n(t,e,i){t.setAttribute?t.setAttribute(e,i):t[e]=i}function Kn(t,e){return t.getAttribute?t.getAttribute(e):t[e]}function Qn(t){return"auto"===t?tg.domSupported?"html":"richText":t||"html"}function Jn(t){var e={main:"",sub:""};return t&&(t=t.split(hm),e.main=t[0]||"",e.sub=t[1]||""),e}function tr(t){O(/^[a-zA-Z0-9_]+([.][a-zA-Z0-9_]+)?$/.test(t),'componentType "'+t+'" illegal')}function er(t){t.$constructor=t,t.extend=function(t){var e=this,i=function(){t.$constructor?t.$constructor.apply(this,arguments):e.apply(this,arguments)};return o(i.prototype,t),i.extend=this.extend,i.superCall=nr,i.superApply=rr,u(i,this),i.superClass=e,i}}function ir(t){var e=["__\x00is_clz",cm++,Math.random().toFixed(3)].join("_");t.prototype[e]=!0,t.isInstance=function(t){return!(!t||!t[e])}}function nr(t,e){var i=P(arguments,2);return this.superClass.prototype[e].apply(t,i)}function rr(t,e,i){return this.superClass.prototype[e].apply(t,i)}function ar(t,e){function i(t){var e=n[t.main];return e&&e[um]||(e=n[t.main]={},e[um]=!0),e}e=e||{};var n={};if(t.registerClass=function(t,e){if(e)if(tr(e),e=Jn(e),e.sub){if(e.sub!==um){var r=i(e);r[e.sub]=t}}else n[e.main]=t;return t},t.getClass=function(t,e,i){var r=n[t];if(r&&r[um]&&(r=e?r[e]:null),i&&!r)throw new Error(e?"Component "+t+"."+(e||"")+" not exists. Load it first.":t+".type should be specified.");return r},t.getClassesByMainType=function(t){t=Jn(t);var e=[],i=n[t.main];return i&&i[um]?f(i,function(t,i){i!==um&&e.push(t)}):e.push(i),e},t.hasClass=function(t){return t=Jn(t),!!n[t.main]},t.getAllClassMainTypes=function(){var t=[];return f(n,function(e,i){t.push(i)}),t},t.hasSubTypes=function(t){t=Jn(t);var e=n[t.main];return e&&e[um]},t.parseClassType=Jn,e.registerWhenExtend){var r=t.extend;r&&(t.extend=function(e){var i=r.call(this,e);return t.registerClass(i,e.type)})}return t}function or(t){return t>-xm&&xm>t}function sr(t){return t>xm||-xm>t}function lr(t,e,i,n,r){var a=1-r;return a*a*(a*t+3*r*e)+r*r*(r*n+3*a*i)}function hr(t,e,i,n,r){var a=1-r;return 3*(((e-t)*a+2*(i-e)*r)*a+(n-i)*r*r)}function ur(t,e,i,n,r,a){var o=n+3*(e-i)-t,s=3*(i-2*e+t),l=3*(e-t),h=t-r,u=s*s-3*o*l,c=s*l-9*o*h,d=l*l-3*s*h,f=0;if(or(u)&&or(c))if(or(s))a[0]=0;else{var p=-l/s;p>=0&&1>=p&&(a[f++]=p)}else{var g=c*c-4*u*d;if(or(g)){var v=c/u,p=-s/o+v,m=-v/2;p>=0&&1>=p&&(a[f++]=p),m>=0&&1>=m&&(a[f++]=m)}else if(g>0){var y=ym(g),x=u*s+1.5*o*(-c+y),_=u*s+1.5*o*(-c-y);x=0>x?-mm(-x,bm):mm(x,bm),_=0>_?-mm(-_,bm):mm(_,bm);var p=(-s-(x+_))/(3*o);p>=0&&1>=p&&(a[f++]=p)}else{var w=(2*u*s-3*o*c)/(2*ym(u*u*u)),b=Math.acos(w)/3,S=ym(u),M=Math.cos(b),p=(-s-2*S*M)/(3*o),m=(-s+S*(M+wm*Math.sin(b)))/(3*o),I=(-s+S*(M-wm*Math.sin(b)))/(3*o);p>=0&&1>=p&&(a[f++]=p),m>=0&&1>=m&&(a[f++]=m),I>=0&&1>=I&&(a[f++]=I)}}return f}function cr(t,e,i,n,r){var a=6*i-12*e+6*t,o=9*e+3*n-3*t-9*i,s=3*e-3*t,l=0;if(or(o)){if(sr(a)){var h=-s/a;h>=0&&1>=h&&(r[l++]=h)}}else{var u=a*a-4*o*s;if(or(u))r[0]=-a/(2*o);else if(u>0){var c=ym(u),h=(-a+c)/(2*o),d=(-a-c)/(2*o);h>=0&&1>=h&&(r[l++]=h),d>=0&&1>=d&&(r[l++]=d)}}return l}function dr(t,e,i,n,r,a){var o=(e-t)*r+t,s=(i-e)*r+e,l=(n-i)*r+i,h=(s-o)*r+o,u=(l-s)*r+s,c=(u-h)*r+h;a[0]=t,a[1]=o,a[2]=h,a[3]=c,a[4]=c,a[5]=u,a[6]=l,a[7]=n}function fr(t,e,i,n,r,a,o,s,l,h,u){var c,d,f,p,g,v=.005,m=1/0;Sm[0]=l,Sm[1]=h;for(var y=0;1>y;y+=.05)Mm[0]=lr(t,i,r,o,y),Mm[1]=lr(e,n,a,s,y),p=xg(Sm,Mm),m>p&&(c=y,m=p);m=1/0;for(var x=0;32>x&&!(_m>v);x++)d=c-v,f=c+v,Mm[0]=lr(t,i,r,o,d),Mm[1]=lr(e,n,a,s,d),p=xg(Mm,Sm),d>=0&&m>p?(c=d,m=p):(Im[0]=lr(t,i,r,o,f),Im[1]=lr(e,n,a,s,f),g=xg(Im,Sm),1>=f&&m>g?(c=f,m=g):v*=.5);return u&&(u[0]=lr(t,i,r,o,c),u[1]=lr(e,n,a,s,c)),ym(m)}function pr(t,e,i,n){var r=1-n;return r*(r*t+2*n*e)+n*n*i}function gr(t,e,i,n){return 2*((1-n)*(e-t)+n*(i-e))}function vr(t,e,i,n,r){var a=t-2*e+i,o=2*(e-t),s=t-n,l=0;if(or(a)){if(sr(o)){var h=-s/o;h>=0&&1>=h&&(r[l++]=h)}}else{var u=o*o-4*a*s;if(or(u)){var h=-o/(2*a);h>=0&&1>=h&&(r[l++]=h)}else if(u>0){var c=ym(u),h=(-o+c)/(2*a),d=(-o-c)/(2*a);h>=0&&1>=h&&(r[l++]=h),d>=0&&1>=d&&(r[l++]=d)}}return l}function mr(t,e,i){var n=t+i-2*e;return 0===n?.5:(t-e)/n}function yr(t,e,i,n,r){var a=(e-t)*n+t,o=(i-e)*n+e,s=(o-a)*n+a;r[0]=t,r[1]=a,r[2]=s,r[3]=s,r[4]=o,r[5]=i}function xr(t,e,i,n,r,a,o,s,l){var h,u=.005,c=1/0;Sm[0]=o,Sm[1]=s;for(var d=0;1>d;d+=.05){Mm[0]=pr(t,i,r,d),Mm[1]=pr(e,n,a,d);var f=xg(Sm,Mm);c>f&&(h=d,c=f)}c=1/0;for(var p=0;32>p&&!(_m>u);p++){var g=h-u,v=h+u;Mm[0]=pr(t,i,r,g),Mm[1]=pr(e,n,a,g);var f=xg(Mm,Sm);if(g>=0&&c>f)h=g,c=f;else{Im[0]=pr(t,i,r,v),Im[1]=pr(e,n,a,v);var m=xg(Im,Sm);1>=v&&c>m?(h=v,c=m):u*=.5}}return l&&(l[0]=pr(t,i,r,h),l[1]=pr(e,n,a,h)),ym(c)}function _r(t,e,i){if(0!==t.length){var n,r=t[0],a=r[0],o=r[0],s=r[1],l=r[1];for(n=1;nu;u++){var p=d(t,i,r,o,zm[u]);l[0]=Tm(p,l[0]),h[0]=Cm(p,h[0])}for(f=c(e,n,a,s,Em),u=0;f>u;u++){var g=d(e,n,a,s,Em[u]);l[1]=Tm(g,l[1]),h[1]=Cm(g,h[1])}l[0]=Tm(t,l[0]),h[0]=Cm(t,h[0]),l[0]=Tm(o,l[0]),h[0]=Cm(o,h[0]),l[1]=Tm(e,l[1]),h[1]=Cm(e,h[1]),l[1]=Tm(s,l[1]),h[1]=Cm(s,h[1])}function Sr(t,e,i,n,r,a,o,s){var l=mr,h=pr,u=Cm(Tm(l(t,i,r),1),0),c=Cm(Tm(l(e,n,a),1),0),d=h(t,i,r,u),f=h(e,n,a,c);o[0]=Tm(t,r,d),o[1]=Tm(e,a,f),s[0]=Cm(t,r,d),s[1]=Cm(e,a,f)}function Mr(t,e,i,n,r,a,o,s,l){var h=oe,u=se,c=Math.abs(r-a);if(1e-4>c%km&&c>1e-4)return s[0]=t-i,s[1]=e-n,l[0]=t+i,void(l[1]=e+n);if(Pm[0]=Dm(r)*i+t,Pm[1]=Am(r)*n+e,Lm[0]=Dm(a)*i+t,Lm[1]=Am(a)*n+e,h(s,Pm,Lm),u(l,Pm,Lm),r%=km,0>r&&(r+=km),a%=km,0>a&&(a+=km),r>a&&!o?a+=km:a>r&&o&&(r+=km),o){var d=a;a=r,r=d}for(var f=0;a>f;f+=Math.PI/2)f>r&&(Om[0]=Dm(f)*i+t,Om[1]=Am(f)*n+e,h(s,Om,s),u(l,Om,l))}function Ir(t,e,i,n,r,a,o){if(0===r)return!1;var s=r,l=0,h=t;if(o>e+s&&o>n+s||e-s>o&&n-s>o||a>t+s&&a>i+s||t-s>a&&i-s>a)return!1;if(t===i)return Math.abs(a-t)<=s/2;l=(e-n)/(t-i),h=(t*n-i*e)/(t-i);var u=l*a-o+h,c=u*u/(l*l+1);return s/2*s/2>=c}function Tr(t,e,i,n,r,a,o,s,l,h,u){if(0===l)return!1;var c=l;if(u>e+c&&u>n+c&&u>a+c&&u>s+c||e-c>u&&n-c>u&&a-c>u&&s-c>u||h>t+c&&h>i+c&&h>r+c&&h>o+c||t-c>h&&i-c>h&&r-c>h&&o-c>h)return!1;var d=fr(t,e,i,n,r,a,o,s,h,u,null);return c/2>=d}function Cr(t,e,i,n,r,a,o,s,l){if(0===o)return!1;var h=o;if(l>e+h&&l>n+h&&l>a+h||e-h>l&&n-h>l&&a-h>l||s>t+h&&s>i+h&&s>r+h||t-h>s&&i-h>s&&r-h>s)return!1;var u=xr(t,e,i,n,r,a,s,l,null);return h/2>=u}function Ar(t){return t%=Um,0>t&&(t+=Um),t}function Dr(t,e,i,n,r,a,o,s,l){if(0===o)return!1;var h=o;s-=t,l-=e;var u=Math.sqrt(s*s+l*l);if(u-h>i||i>u+h)return!1;if(Math.abs(n-r)%$m<1e-4)return!0;if(a){var c=n;n=Ar(r),r=Ar(c)}else n=Ar(n),r=Ar(r);n>r&&(r+=$m);var d=Math.atan2(l,s);return 0>d&&(d+=$m),d>=n&&r>=d||d+$m>=n&&r>=d+$m}function kr(t,e,i,n,r,a){if(a>e&&a>n||e>a&&n>a)return 0;if(n===e)return 0;var o=e>n?1:-1,s=(a-e)/(n-e);(1===s||0===s)&&(o=e>n?.5:-.5);var l=s*(i-t)+t;return l===r?1/0:l>r?o:0}function Pr(t,e){return Math.abs(t-e)e&&h>n&&h>a&&h>s||e>h&&n>h&&a>h&&s>h)return 0;var u=ur(e,n,a,s,h,ty);if(0===u)return 0;for(var c,d,f=0,p=-1,g=0;u>g;g++){var v=ty[g],m=0===v||1===v?.5:1,y=lr(t,i,r,o,v);l>y||(0>p&&(p=cr(e,n,a,s,ey),ey[1]1&&Lr(),c=lr(e,n,a,s,ey[0]),p>1&&(d=lr(e,n,a,s,ey[1]))),f+=2==p?vc?m:-m:vd?m:-m:d>s?m:-m:vc?m:-m:c>s?m:-m)}return f}function zr(t,e,i,n,r,a,o,s){if(s>e&&s>n&&s>a||e>s&&n>s&&a>s)return 0;var l=vr(e,n,a,s,ty);if(0===l)return 0;var h=mr(e,n,a);if(h>=0&&1>=h){for(var u=0,c=pr(e,n,a,h),d=0;l>d;d++){var f=0===ty[d]||1===ty[d]?.5:1,p=pr(t,i,r,ty[d]);o>p||(u+=ty[d]c?f:-f:c>a?f:-f)}return u}var f=0===ty[0]||1===ty[0]?.5:1,p=pr(t,i,r,ty[0]);return o>p?0:e>a?f:-f}function Er(t,e,i,n,r,a,o,s){if(s-=e,s>i||-i>s)return 0;var l=Math.sqrt(i*i-s*s);ty[0]=-l,ty[1]=l;var h=Math.abs(n-r);if(1e-4>h)return 0;if(1e-4>h%Qm){n=0,r=Qm;var u=a?1:-1;return o>=ty[0]+t&&o<=ty[1]+t?u:0}if(a){var l=n;n=Ar(r),r=Ar(l)}else n=Ar(n),r=Ar(r);n>r&&(r+=Qm);for(var c=0,d=0;2>d;d++){var f=ty[d];if(f+t>o){var p=Math.atan2(s,f),u=a?1:-1;0>p&&(p=Qm+p),(p>=n&&r>=p||p+Qm>=n&&r>=p+Qm)&&(p>Math.PI/2&&p<1.5*Math.PI&&(u=-u),c+=u)}}return c}function Rr(t,e,i,n,r){for(var a=0,o=0,s=0,l=0,h=0,u=0;u1&&(i||(a+=kr(o,s,l,h,n,r))),1==u&&(o=t[u],s=t[u+1],l=o,h=s),c){case Km.M:l=t[u++],h=t[u++],o=l,s=h;break;case Km.L:if(i){if(Ir(o,s,t[u],t[u+1],e,n,r))return!0}else a+=kr(o,s,t[u],t[u+1],n,r)||0;o=t[u++],s=t[u++];break;case Km.C:if(i){if(Tr(o,s,t[u++],t[u++],t[u++],t[u++],t[u],t[u+1],e,n,r))return!0}else a+=Or(o,s,t[u++],t[u++],t[u++],t[u++],t[u],t[u+1],n,r)||0;o=t[u++],s=t[u++];break;case Km.Q:if(i){if(Cr(o,s,t[u++],t[u++],t[u],t[u+1],e,n,r))return!0}else a+=zr(o,s,t[u++],t[u++],t[u],t[u+1],n,r)||0;o=t[u++],s=t[u++];break;case Km.A:var d=t[u++],f=t[u++],p=t[u++],g=t[u++],v=t[u++],m=t[u++],y=(t[u++],1-t[u++]),x=Math.cos(v)*p+d,_=Math.sin(v)*g+f;u>1?a+=kr(o,s,x,_,n,r):(l=x,h=_);var w=(n-d)*g/p+d;if(i){if(Dr(d,f,g,v,v+m,y,e,w,r))return!0}else a+=Er(d,f,g,v,v+m,y,w,r);o=Math.cos(v+m)*p+d,s=Math.sin(v+m)*g+f;break;case Km.R:l=o=t[u++],h=s=t[u++];var b=t[u++],S=t[u++],x=l+b,_=h+S;if(i){if(Ir(l,h,x,h,e,n,r)||Ir(x,h,x,_,e,n,r)||Ir(x,_,l,_,e,n,r)||Ir(l,_,l,h,e,n,r))return!0}else a+=kr(x,h,x,_,n,r),a+=kr(l,_,l,h,n,r);break;case Km.Z:if(i){if(Ir(o,s,l,h,e,n,r))return!0}else a+=kr(o,s,l,h,n,r);o=l,s=h}}return i||Pr(s,h)||(a+=kr(o,s,l,h,n,r)||0),0!==a}function Br(t,e,i){return Rr(t,0,!1,e,i)}function Nr(t,e,i,n){return Rr(t,e,!0,i,n)}function Fr(t){mn.call(this,t),this.path=null}function Vr(t,e,i,n,r,a,o,s,l,h,u){var c=l*(fy/180),d=dy(c)*(t-i)/2+cy(c)*(e-n)/2,f=-1*cy(c)*(t-i)/2+dy(c)*(e-n)/2,p=d*d/(o*o)+f*f/(s*s);p>1&&(o*=uy(p),s*=uy(p));var g=(r===a?-1:1)*uy((o*o*s*s-o*o*f*f-s*s*d*d)/(o*o*f*f+s*s*d*d))||0,v=g*o*f/s,m=g*-s*d/o,y=(t+i)/2+dy(c)*v-cy(c)*m,x=(e+n)/2+cy(c)*v+dy(c)*m,_=vy([1,0],[(d-v)/o,(f-m)/s]),w=[(d-v)/o,(f-m)/s],b=[(-1*d-v)/o,(-1*f-m)/s],S=vy(w,b);gy(w,b)<=-1&&(S=fy),gy(w,b)>=1&&(S=0),0===a&&S>0&&(S-=2*fy),1===a&&0>S&&(S+=2*fy),u.addData(h,y,x,o,s,_,S,c,a)}function Wr(t){if(!t)return new qm;for(var e,i=0,n=0,r=i,a=n,o=new qm,s=qm.CMD,l=t.match(my),h=0;hg;g++)f[g]=parseFloat(f[g]);for(var v=0;p>v;){var m,y,x,_,w,b,S,M=i,I=n;switch(d){case"l":i+=f[v++],n+=f[v++],u=s.L,o.addData(u,i,n);break;case"L":i=f[v++],n=f[v++],u=s.L,o.addData(u,i,n);break;case"m":i+=f[v++],n+=f[v++],u=s.M,o.addData(u,i,n),r=i,a=n,d="l";break;case"M":i=f[v++],n=f[v++],u=s.M,o.addData(u,i,n),r=i,a=n,d="L";break;case"h":i+=f[v++],u=s.L,o.addData(u,i,n);break;case"H":i=f[v++],u=s.L,o.addData(u,i,n);break;case"v":n+=f[v++],u=s.L,o.addData(u,i,n);break;case"V":n=f[v++],u=s.L,o.addData(u,i,n);break;case"C":u=s.C,o.addData(u,f[v++],f[v++],f[v++],f[v++],f[v++],f[v++]),i=f[v-2],n=f[v-1];break;case"c":u=s.C,o.addData(u,f[v++]+i,f[v++]+n,f[v++]+i,f[v++]+n,f[v++]+i,f[v++]+n),i+=f[v-2],n+=f[v-1];break;case"S":m=i,y=n;var T=o.len(),C=o.data;e===s.C&&(m+=i-C[T-4],y+=n-C[T-3]),u=s.C,M=f[v++],I=f[v++],i=f[v++],n=f[v++],o.addData(u,m,y,M,I,i,n);break;case"s":m=i,y=n;var T=o.len(),C=o.data;e===s.C&&(m+=i-C[T-4],y+=n-C[T-3]),u=s.C,M=i+f[v++],I=n+f[v++],i+=f[v++],n+=f[v++],o.addData(u,m,y,M,I,i,n);break;case"Q":M=f[v++],I=f[v++],i=f[v++],n=f[v++],u=s.Q,o.addData(u,M,I,i,n);break;case"q":M=f[v++]+i,I=f[v++]+n,i+=f[v++],n+=f[v++],u=s.Q,o.addData(u,M,I,i,n);break;case"T":m=i,y=n;var T=o.len(),C=o.data;e===s.Q&&(m+=i-C[T-4],y+=n-C[T-3]),i=f[v++],n=f[v++],u=s.Q,o.addData(u,m,y,i,n);break;case"t":m=i,y=n;var T=o.len(),C=o.data;e===s.Q&&(m+=i-C[T-4],y+=n-C[T-3]),i+=f[v++],n+=f[v++],u=s.Q,o.addData(u,m,y,i,n);break;case"A":x=f[v++],_=f[v++],w=f[v++],b=f[v++],S=f[v++],M=i,I=n,i=f[v++],n=f[v++],u=s.A,Vr(M,I,i,n,b,S,x,_,w,u,o);break;case"a":x=f[v++],_=f[v++],w=f[v++],b=f[v++],S=f[v++],M=i,I=n,i+=f[v++],n+=f[v++],u=s.A,Vr(M,I,i,n,b,S,x,_,w,u,o)}}("z"===d||"Z"===d)&&(u=s.Z,o.addData(u),i=r,n=a),e=u}return o.toStatic(),o}function Gr(t,e){var i=Wr(t);return e=e||{},e.buildPath=function(t){if(t.setData){t.setData(i.data);var e=t.getContext();e&&t.rebuildPath(e)}else{var e=t;i.rebuildPath(e)}},e.applyTransform=function(t){hy(i,t),this.dirty(!0)},e}function Hr(t,e){return new Fr(Gr(t,e))}function Zr(t,e){return Fr.extend(Gr(t,e))}function Xr(t,e){for(var i=[],n=t.length,r=0;n>r;r++){var a=t[r];a.path||a.createPathProxy(),a.__dirtyPath&&a.buildPath(a.path,a.shape,!0),i.push(a.path)}var o=new Fr(e);return o.createPathProxy(),o.buildPath=function(t){t.appendPath(i);var e=t.getContext();e&&t.rebuildPath(e)},o}function Yr(t,e,i,n,r,a,o){var s=.5*(i-t),l=.5*(n-e);return(2*(e-i)+s+l)*o+(-3*(e-i)-2*s-l)*a+s*r+e}function jr(t,e,i){var n=e.points,r=e.smooth;if(n&&n.length>=2){if(r&&"spline"!==r){var a=Ty(n,r,i,e.smoothConstraint);t.moveTo(n[0][0],n[0][1]);for(var o=n.length,s=0;(i?o:o-1)>s;s++){var l=a[2*s],h=a[2*s+1],u=n[(s+1)%o];t.bezierCurveTo(l[0],l[1],h[0],h[1],u[0],u[1])}}else{"spline"===r&&(n=Iy(n,i)),t.moveTo(n[0][0],n[0][1]);for(var s=1,c=n.length;c>s;s++)t.lineTo(n[s][0],n[s][1])}i&&t.closePath()}}function qr(t,e,i){var n=t.cpx2,r=t.cpy2;return null===n||null===r?[(i?hr:lr)(t.x1,t.cpx1,t.cpx2,t.x2,e),(i?hr:lr)(t.y1,t.cpy1,t.cpy2,t.y2,e)]:[(i?gr:pr)(t.x1,t.cpx1,t.x2,e),(i?gr:pr)(t.y1,t.cpy1,t.y2,e)]}function Ur(t){mn.call(this,t),this._displayables=[],this._temporaryDisplayables=[],this._cursor=0,this.notClear=!0}function $r(t){return Fr.extend(t)}function Kr(t,e){return Zr(t,e)}function Qr(t,e,i,n){var r=Hr(t,e);return i&&("center"===n&&(i=ta(i,r.getBoundingRect())),ea(r,i)),r}function Jr(t,e,i){var n=new yn({style:{image:t,x:e.x,y:e.y,width:e.width,height:e.height},onload:function(t){if("center"===i){var r={width:t.width,height:t.height};n.setStyle(ta(e,r))}}});return n}function ta(t,e){var i,n=e.width/e.height,r=t.height*n;r<=t.width?i=t.height:(r=t.width,i=r/n);var a=t.x+t.width/2,o=t.y+t.height/2;return{x:a-r/2,y:o-i/2,width:r,height:i}}function ea(t,e){if(t.applyTransform){var i=t.getBoundingRect(),n=i.calculateTransform(e);t.applyTransform(n)}}function ia(t){var e=t.shape,i=t.style.lineWidth;return Fy(2*e.x1)===Fy(2*e.x2)&&(e.x1=e.x2=ra(e.x1,i,!0)),Fy(2*e.y1)===Fy(2*e.y2)&&(e.y1=e.y2=ra(e.y1,i,!0)),t}function na(t){var e=t.shape,i=t.style.lineWidth,n=e.x,r=e.y,a=e.width,o=e.height;return e.x=ra(e.x,i,!0),e.y=ra(e.y,i,!0),e.width=Math.max(ra(n+a,i,!1)-e.x,0===a?0:1),e.height=Math.max(ra(r+o,i,!1)-e.y,0===o?0:1),t}function ra(t,e,i){var n=Fy(2*t);return(n+Fy(e))%2===0?n/2:(n+(i?1:-1))/2}function aa(t){return null!=t&&"none"!==t}function oa(t){if("string"!=typeof t)return t;var e=Zy.get(t);return e||(e=Ye(t,-.1),1e4>Xy&&(Zy.set(t,e),Xy++)),e}function sa(t){if(t.__hoverStlDirty){t.__hoverStlDirty=!1;var e=t.__hoverStl;if(!e)return void(t.__normalStl=null);var i=t.__normalStl={},n=t.style;for(var r in e)null!=e[r]&&(i[r]=n[r]);i.fill=n.fill,i.stroke=n.stroke}}function la(t){var e=t.__hoverStl;if(e&&!t.__highlighted){var i=t.useHoverLayer;t.__highlighted=i?"layer":"plain";var n=t.__zr;if(n||!i){var r=t,a=t.style;i&&(r=n.addHover(t),a=r.style),Da(a),i||sa(r),a.extendFrom(e),ha(a,e,"fill"),ha(a,e,"stroke"),Aa(a),i||(t.dirty(!1),t.z2+=1)}}}function ha(t,e,i){!aa(e[i])&&aa(t[i])&&(t[i]=oa(t[i]))}function ua(t){t.__highlighted&&(ca(t),t.__highlighted=!1)}function ca(t){var e=t.__highlighted;if("layer"===e)t.__zr&&t.__zr.removeHover(t);else if(e){var i=t.style,n=t.__normalStl;n&&(Da(i),t.setStyle(n),Aa(i),t.z2-=1)}}function da(t,e){t.isGroup?t.traverse(function(t){!t.isGroup&&e(t)}):e(t)}function fa(t,e){e=t.__hoverStl=e!==!1&&(e||{}),t.__hoverStlDirty=!0,t.__highlighted&&(ua(t),la(t))}function pa(t){return t&&t.__isEmphasisEntered}function ga(t){this.__hoverSilentOnTouch&&t.zrByTouch||!this.__isEmphasisEntered&&da(this,la)}function va(t){this.__hoverSilentOnTouch&&t.zrByTouch||!this.__isEmphasisEntered&&da(this,ua)}function ma(){this.__isEmphasisEntered=!0,da(this,la)}function ya(){this.__isEmphasisEntered=!1,da(this,ua)}function xa(t,e,i){t.isGroup?t.traverse(function(t){!t.isGroup&&fa(t,t.hoverStyle||e)}):fa(t,t.hoverStyle||e),_a(t,i)}function _a(t,e){var i=e===!1;if(t.__hoverSilentOnTouch=null!=e&&e.hoverSilentOnTouch,!i||t.__hoverStyleTrigger){var n=i?"off":"on";t[n]("mouseover",ga)[n]("mouseout",va),t[n]("emphasis",ma)[n]("normal",ya),t.__hoverStyleTrigger=!i}}function wa(t,e,i,n,r,a,o){r=r||Gy;var s,l=r.labelFetcher,h=r.labelDataIndex,u=r.labelDimIndex,c=i.getShallow("show"),d=n.getShallow("show");(c||d)&&(l&&(s=l.getFormattedLabel(h,"normal",null,u)),null==s&&(s=w(r.defaultText)?r.defaultText(h,r):r.defaultText));var f=c?s:null,p=d?D(l?l.getFormattedLabel(h,"emphasis",null,u):null,s):null;(null!=f||null!=p)&&(ba(t,i,a,r),ba(e,n,o,r,!0)),t.text=f,e.text=p}function ba(t,e,i,n,r){return Ma(t,e,n,r),i&&o(t,i),t}function Sa(t,e,i){var n,r={isRectText:!0};i===!1?n=!0:r.autoColor=i,Ma(t,e,r,n)}function Ma(t,e,i,n){if(i=i||Gy,i.isRectText){var r=e.getShallow("position")||(n?null:"inside");"outside"===r&&(r="top"),t.textPosition=r,t.textOffset=e.getShallow("offset");var a=e.getShallow("rotate");null!=a&&(a*=Math.PI/180),t.textRotation=a,t.textDistance=D(e.getShallow("distance"),n?null:5)}var o,s=e.ecModel,l=s&&s.option.textStyle,h=Ia(e);if(h){o={};for(var u in h)if(h.hasOwnProperty(u)){var c=e.getModel(["rich",u]);Ta(o[u]={},c,l,i,n)}}return t.rich=o,Ta(t,e,l,i,n,!0),i.forceRich&&!i.textStyle&&(i.textStyle={}),t}function Ia(t){for(var e;t&&t!==t.ecModel;){var i=(t.option||Gy).rich;if(i){e=e||{};for(var n in i)i.hasOwnProperty(n)&&(e[n]=1)}t=t.parentModel}return e}function Ta(t,e,i,n,r,a){i=!r&&i||Gy,t.textFill=Ca(e.getShallow("color"),n)||i.color,t.textStroke=Ca(e.getShallow("textBorderColor"),n)||i.textBorderColor,t.textStrokeWidth=D(e.getShallow("textBorderWidth"),i.textBorderWidth),t.insideRawTextPosition=t.textPosition,r||(a&&(t.insideRollbackOpt=n,Aa(t)),null==t.textFill&&(t.textFill=n.autoColor)),t.fontStyle=e.getShallow("fontStyle")||i.fontStyle,t.fontWeight=e.getShallow("fontWeight")||i.fontWeight,t.fontSize=e.getShallow("fontSize")||i.fontSize,t.fontFamily=e.getShallow("fontFamily")||i.fontFamily,t.textAlign=e.getShallow("align"),t.textVerticalAlign=e.getShallow("verticalAlign")||e.getShallow("baseline"),t.textLineHeight=e.getShallow("lineHeight"),t.textWidth=e.getShallow("width"),t.textHeight=e.getShallow("height"),t.textTag=e.getShallow("tag"),a&&n.disableBox||(t.textBackgroundColor=Ca(e.getShallow("backgroundColor"),n),t.textPadding=e.getShallow("padding"),t.textBorderColor=Ca(e.getShallow("borderColor"),n),t.textBorderWidth=e.getShallow("borderWidth"),t.textBorderRadius=e.getShallow("borderRadius"),t.textBoxShadowColor=e.getShallow("shadowColor"),t.textBoxShadowBlur=e.getShallow("shadowBlur"),t.textBoxShadowOffsetX=e.getShallow("shadowOffsetX"),t.textBoxShadowOffsetY=e.getShallow("shadowOffsetY")),t.textShadowColor=e.getShallow("textShadowColor")||i.textShadowColor,t.textShadowBlur=e.getShallow("textShadowBlur")||i.textShadowBlur,t.textShadowOffsetX=e.getShallow("textShadowOffsetX")||i.textShadowOffsetX,t.textShadowOffsetY=e.getShallow("textShadowOffsetY")||i.textShadowOffsetY}function Ca(t,e){return"auto"!==t?t:e&&e.autoColor?e.autoColor:null}function Aa(t){var e=t.insideRollbackOpt;if(e&&null==t.textFill){var i,n=e.useInsideStyle,r=t.insideRawTextPosition,a=e.autoColor;n!==!1&&(n===!0||e.isRectText&&r&&"string"==typeof r&&r.indexOf("inside")>=0)?(i={textFill:null,textStroke:t.textStroke,textStrokeWidth:t.textStrokeWidth},t.textFill="#fff",null==t.textStroke&&(t.textStroke=a,null==t.textStrokeWidth&&(t.textStrokeWidth=2))):null!=a&&(i={textFill:null},t.textFill=a),i&&(t.insideRollback=i)}}function Da(t){var e=t.insideRollback;e&&(t.textFill=e.textFill,t.textStroke=e.textStroke,t.textStrokeWidth=e.textStrokeWidth,t.insideRollback=null)}function ka(t,e){var i=e||e.getModel("textStyle");return z([t.fontStyle||i&&i.getShallow("fontStyle")||"",t.fontWeight||i&&i.getShallow("fontWeight")||"",(t.fontSize||i&&i.getShallow("fontSize")||12)+"px",t.fontFamily||i&&i.getShallow("fontFamily")||"sans-serif"].join(" "))}function Pa(t,e,i,n,r,a){"function"==typeof r&&(a=r,r=null);var o=n&&n.isAnimationEnabled();if(o){var s=t?"Update":"",l=n.getShallow("animationDuration"+s),h=n.getShallow("animationEasing"+s),u=n.getShallow("animationDelay"+s);"function"==typeof u&&(u=u(r,n.getAnimationDelayParams?n.getAnimationDelayParams(e,r):null)),"function"==typeof l&&(l=l(r)),l>0?e.animateTo(i,l,u||0,h,a,!!a):(e.stopAnimation(),e.attr(i),a&&a())}else e.stopAnimation(),e.attr(i),a&&a()}function La(t,e,i,n,r){Pa(!0,t,e,i,n,r)}function Oa(t,e,i,n,r){Pa(!1,t,e,i,n,r)}function za(t,e){for(var i=Se([]);t&&t!==e;)Ie(i,t.getLocalTransform(),i),t=t.parent;return i}function Ea(t,e,i){return e&&!d(e)&&(e=Og.getLocalTransform(e)),i&&(e=De([],e)),ae([],t,e)}function Ra(t,e,i){var n=0===e[4]||0===e[5]||0===e[0]?1:Math.abs(2*e[4]/e[0]),r=0===e[4]||0===e[5]||0===e[2]?1:Math.abs(2*e[4]/e[2]),a=["left"===t?-n:"right"===t?n:0,"top"===t?-r:"bottom"===t?r:0];return a=Ea(a,e,i),Math.abs(a[0])>Math.abs(a[1])?a[0]>0?"right":"left":a[1]>0?"bottom":"top"}function Ba(t,e,i){function n(t){var e={};return t.traverse(function(t){!t.isGroup&&t.anid&&(e[t.anid]=t)}),e}function r(t){var e={position:H(t.position),rotation:t.rotation};return t.shape&&(e.shape=o({},t.shape)),e}if(t&&e){var a=n(t);e.traverse(function(t){if(!t.isGroup&&t.anid){var e=a[t.anid];if(e){var n=r(t);t.attr(r(e)),La(t,n,i,t.dataIndex)}}})}}function Na(t,e){return p(t,function(t){var i=t[0];i=Vy(i,e.x),i=Wy(i,e.x+e.width);var n=t[1];return n=Vy(n,e.y),n=Wy(n,e.y+e.height),[i,n]})}function Fa(t,e){var i=Vy(t.x,e.x),n=Wy(t.x+t.width,e.x+e.width),r=Vy(t.y,e.y),a=Wy(t.y+t.height,e.y+e.height);return n>=i&&a>=r?{x:i,y:r,width:n-i,height:a-r}:void 0}function Va(t,e,i){e=o({rectHover:!0},e);var n=e.style={strokeNoScale:!0};return i=i||{x:-1,y:-1,width:2,height:2},t?0===t.indexOf("image://")?(n.image=t.slice(8),s(n,i),new yn(e)):Qr(t.replace("path://",""),e,i,"center"):void 0}function Wa(t,e,i){this.parentModel=e,this.ecModel=i,this.option=t}function Ga(t,e,i){for(var n=0;n=0&&i.push(t)}),i}t.topologicalTravel=function(t,e,n,r){function a(t){l[t].entryCount--,0===l[t].entryCount&&h.push(t)}function o(t){u[t]=!0,a(t)}if(t.length){var s=i(e),l=s.graph,h=s.noEntryList,u={};for(f(t,function(t){u[t]=!0});h.length;){var c=h.pop(),d=l[c],p=!!u[c];p&&(n.call(r,c,d.originalDeps.slice()),delete u[c]),f(d.successor,p?o:a)}f(u,function(){throw new Error("Circle dependency may exists")})}}}function ja(t){return t.replace(/^\s+/,"").replace(/\s+$/,"")}function qa(t,e,i,n){var r=e[1]-e[0],a=i[1]-i[0];if(0===r)return 0===a?i[0]:(i[0]+i[1])/2;if(n)if(r>0){if(t<=e[0])return i[0];if(t>=e[1])return i[1]}else{if(t>=e[0])return i[0];if(t<=e[1])return i[1]}else{if(t===e[0])return i[0];if(t===e[1])return i[1]}return(t-e[0])/r*a+i[0]}function Ua(t,e){switch(t){case"center":case"middle":t="50%";break;case"left":case"top":t="0%";break;case"right":case"bottom":t="100%"}return"string"==typeof t?ja(t).match(/%$/)?parseFloat(t)/100*e:parseFloat(t):null==t?0/0:+t}function $a(t,e,i){return null==e&&(e=10),e=Math.min(Math.max(0,e),20),t=(+t).toFixed(e),i?t:+t}function Ka(t){return t.sort(function(t,e){return t-e}),t}function Qa(t){if(t=+t,isNaN(t))return 0;for(var e=1,i=0;Math.round(t*e)/e!==t;)e*=10,i++;return i}function Ja(t){var e=t.toString(),i=e.indexOf("e");if(i>0){var n=+e.slice(i+1);return 0>n?-n:0}var r=e.indexOf(".");return 0>r?0:e.length-1-r}function to(t,e){var i=Math.log,n=Math.LN10,r=Math.floor(i(t[1]-t[0])/n),a=Math.round(i(Math.abs(e[1]-e[0]))/n),o=Math.min(Math.max(-r+a,0),20);return isFinite(o)?o:20}function eo(t,e,i){if(!t[e])return 0;var n=g(t,function(t,e){return t+(isNaN(e)?0:e)},0);if(0===n)return 0;for(var r=Math.pow(10,i),a=p(t,function(t){return(isNaN(t)?0:t)/n*r*100}),o=100*r,s=p(a,function(t){return Math.floor(t)}),l=g(s,function(t,e){return t+e},0),h=p(a,function(t,e){return t-s[e]});o>l;){for(var u=Number.NEGATIVE_INFINITY,c=null,d=0,f=h.length;f>d;++d)h[d]>u&&(u=h[d],c=d);++s[c],h[c]=0,++l}return s[e]/r}function io(t){var e=2*Math.PI;return(t%e+e)%e}function no(t){return t>-tx&&tx>t}function ro(t){if(t instanceof Date)return t;if("string"==typeof t){var e=ix.exec(t);if(!e)return new Date(0/0);if(e[8]){var i=+e[4]||0;return"Z"!==e[8].toUpperCase()&&(i-=e[8].slice(0,3)),new Date(Date.UTC(+e[1],+(e[2]||1)-1,+e[3]||1,i,+(e[5]||0),+e[6]||0,+e[7]||0))}return new Date(+e[1],+(e[2]||1)-1,+e[3]||1,+e[4]||0,+(e[5]||0),+e[6]||0,+e[7]||0)}return new Date(null==t?0/0:Math.round(t))}function ao(t){return Math.pow(10,oo(t))}function oo(t){return Math.floor(Math.log(t)/Math.LN10)}function so(t,e){var i,n=oo(t),r=Math.pow(10,n),a=t/r;return i=e?1.5>a?1:2.5>a?2:4>a?3:7>a?5:10:1>a?1:2>a?2:3>a?3:5>a?5:10,t=i*r,n>=-20?+t.toFixed(0>n?-n:0):t}function lo(t,e){var i=(t.length-1)*e+1,n=Math.floor(i),r=+t[n-1],a=i-n;return a?r+a*(t[n]-r):r}function ho(t){function e(t,i,n){return t.interval[n]s;s++)a[s]<=i&&(a[s]=i,o[s]=s?1:1-n),i=a[s],n=o[s];a[0]===a[1]&&o[0]*o[1]!==1?t.splice(r,1):r++}return t}function uo(t){return t-parseFloat(t)>=0}function co(t){return isNaN(t)?"-":(t=(t+"").split("."),t[0].replace(/(\d{1,3})(?=(?:\d{3})+(?!\d))/g,"$1,")+(t.length>1?"."+t[1]:""))}function fo(t,e){return t=(t||"").toLowerCase().replace(/-(.)/g,function(t,e){return e.toUpperCase()}),e&&t&&(t=t.charAt(0).toUpperCase()+t.slice(1)),t}function po(t){return null==t?"":(t+"").replace(ax,function(t,e){return ox[e]})}function go(t,e,i){_(e)||(e=[e]);var n=e.length;if(!n)return"";for(var r=e[0].$vars||[],a=0;as;s++)for(var l=0;l':'':{renderMode:r,content:"{marker"+a+"|} ",style:{color:i}}:"" +}function yo(t,e){return t+="","0000".substr(0,e-t.length)+t}function xo(t,e,i){("week"===t||"month"===t||"quarter"===t||"half-year"===t||"year"===t)&&(t="MM-dd\nyyyy");var n=ro(e),r=i?"UTC":"",a=n["get"+r+"FullYear"](),o=n["get"+r+"Month"]()+1,s=n["get"+r+"Date"](),l=n["get"+r+"Hours"](),h=n["get"+r+"Minutes"](),u=n["get"+r+"Seconds"](),c=n["get"+r+"Milliseconds"]();return t=t.replace("MM",yo(o,2)).replace("M",o).replace("yyyy",a).replace("yy",a%100).replace("dd",yo(s,2)).replace("d",s).replace("hh",yo(l,2)).replace("h",l).replace("mm",yo(h,2)).replace("m",h).replace("ss",yo(u,2)).replace("s",u).replace("SSS",yo(c,3))}function _o(t){return t?t.charAt(0).toUpperCase()+t.substr(1):t}function wo(t,e,i,n,r){var a=0,o=0;null==n&&(n=1/0),null==r&&(r=1/0);var s=0;e.eachChild(function(l,h){var u,c,d=l.position,f=l.getBoundingRect(),p=e.childAt(h+1),g=p&&p.getBoundingRect();if("horizontal"===t){var v=f.width+(g?-g.x+f.x:0);u=a+v,u>n||l.newline?(a=0,u=v,o+=s+i,s=f.height):s=Math.max(s,f.height)}else{var m=f.height+(g?-g.y+f.y:0);c=o+m,c>r||l.newline?(a+=s+i,o=0,c=m,s=f.width):s=Math.max(s,f.width)}l.newline||(d[0]=a,d[1]=o,"horizontal"===t?a=u+i:o=c+i)})}function bo(t,e,i){i=rx(i||0);var n=e.width,r=e.height,a=Ua(t.left,n),o=Ua(t.top,r),s=Ua(t.right,n),l=Ua(t.bottom,r),h=Ua(t.width,n),u=Ua(t.height,r),c=i[2]+i[0],d=i[1]+i[3],f=t.aspect;switch(isNaN(h)&&(h=n-s-d-a),isNaN(u)&&(u=r-l-c-o),null!=f&&(isNaN(h)&&isNaN(u)&&(f>n/r?h=.8*n:u=.8*r),isNaN(h)&&(h=f*u),isNaN(u)&&(u=h/f)),isNaN(a)&&(a=n-s-h-d),isNaN(o)&&(o=r-l-u-c),t.left||t.right){case"center":a=n/2-h/2-i[3];break;case"right":a=n-h-d}switch(t.top||t.bottom){case"middle":case"center":o=r/2-u/2-i[0];break;case"bottom":o=r-u-c}a=a||0,o=o||0,isNaN(h)&&(h=n-d-a-(s||0)),isNaN(u)&&(u=r-c-o-(l||0));var p=new gi(a+i[3],o+i[0],h,u);return p.margin=i,p}function So(t,e,i){function n(i,n){var o={},l=0,h={},u=0,c=2;if(dx(i,function(e){h[e]=t[e]}),dx(i,function(t){r(e,t)&&(o[t]=h[t]=e[t]),a(o,t)&&l++,a(h,t)&&u++}),s[n])return a(e,i[1])?h[i[2]]=null:a(e,i[2])&&(h[i[1]]=null),h;if(u!==c&&l){if(l>=c)return o;for(var d=0;dn;n++)if(t[n].length>e)return t[n];return t[i-1]}function Ao(t){var e=t.get("coordinateSystem"),i={coordSysName:e,coordSysDims:[],axisMap:N(),categoryAxisMap:N()},n=Mx[e];return n?(n(t,i,i.axisMap,i.categoryAxisMap),i):void 0}function Do(t){return"category"===t.get("type")}function ko(t){this.fromDataset=t.fromDataset,this.data=t.data||(t.sourceFormat===Ax?{}:[]),this.sourceFormat=t.sourceFormat||Dx,this.seriesLayoutBy=t.seriesLayoutBy||Px,this.dimensionsDefine=t.dimensionsDefine,this.encodeDefine=t.encodeDefine&&N(t.encodeDefine),this.startIndex=t.startIndex||0,this.dimensionsDetectCount=t.dimensionsDetectCount}function Po(t){var e=t.option.source,i=Dx;if(I(e))i=kx;else if(_(e)){0===e.length&&(i=Tx);for(var n=0,r=e.length;r>n;n++){var a=e[n];if(null!=a){if(_(a)){i=Tx;break}if(S(a)){i=Cx;break}}}}else if(S(e)){for(var o in e)if(e.hasOwnProperty(o)&&d(e[o])){i=Ax;break}}else if(null!=e)throw new Error("Invalid data");Ox(t).sourceFormat=i}function Lo(t){return Ox(t).source}function Oo(t){Ox(t).datasetMap=N()}function zo(t){var e=t.option,i=e.data,n=I(i)?kx:Ix,r=!1,a=e.seriesLayoutBy,o=e.sourceHeader,s=e.dimensions,l=Vo(t);if(l){var h=l.option;i=h.source,n=Ox(l).sourceFormat,r=!0,a=a||h.seriesLayoutBy,null==o&&(o=h.sourceHeader),s=s||h.dimensions}var u=Eo(i,n,a,o,s),c=e.encode;!c&&l&&(c=Fo(t,l,i,n,a,u)),Ox(t).source=new ko({data:i,fromDataset:r,seriesLayoutBy:a,sourceFormat:n,dimensionsDefine:u.dimensionsDefine,startIndex:u.startIndex,dimensionsDetectCount:u.dimensionsDetectCount,encodeDefine:c})}function Eo(t,e,i,n,r){if(!t)return{dimensionsDefine:Ro(r)};var a,o,s;if(e===Tx)"auto"===n||null==n?Bo(function(t){null!=t&&"-"!==t&&(b(t)?null==o&&(o=1):o=0)},i,t,10):o=n?1:0,r||1!==o||(r=[],Bo(function(t,e){r[e]=null!=t?t:""},i,t)),a=r?r.length:i===Lx?t.length:t[0]?t[0].length:null;else if(e===Cx)r||(r=No(t),s=!0);else if(e===Ax)r||(r=[],s=!0,f(t,function(t,e){r.push(e)}));else if(e===Ix){var l=Vn(t[0]);a=_(l)&&l.length||1}var h;return s&&f(r,function(t,e){"name"===(S(t)?t.name:t)&&(h=e)}),{startIndex:o,dimensionsDefine:Ro(r),dimensionsDetectCount:a,potentialNameDimIndex:h}}function Ro(t){if(t){var e=N();return p(t,function(t){if(t=o({},S(t)?t:{name:t}),null==t.name)return t;t.name+="",null==t.displayName&&(t.displayName=t.name);var i=e.get(t.name);return i?t.name+="-"+i.count++:e.set(t.name,{count:1}),t})}}function Bo(t,e,i,n){if(null==n&&(n=1/0),e===Lx)for(var r=0;rr;r++)t(i[r]?i[r][0]:null,r);else for(var a=i[0]||[],r=0;rr;r++)t(a[r],r)}function No(t){for(var e,i=0;ix&&null==y;x++)Go(i,n,r,a.dimensionsDefine,a.startIndex,x)||(y=x);if(null!=y){s.value=y;var _=a.potentialNameDimIndex||Math.max(y-1,0);h.push(_),l.push(_)}}return l.length&&(s.itemName=l),h.length&&(s.seriesName=h),s}function Vo(t){var e=t.option,i=e.data;return i?void 0:t.ecModel.getComponent("dataset",e.datasetIndex||0)}function Wo(t,e){return Go(t.data,t.sourceFormat,t.seriesLayoutBy,t.dimensionsDefine,t.startIndex,e)}function Go(t,e,i,n,r,a){function o(t){return null!=t&&isFinite(t)&&""!==t?!1:b(t)&&"-"!==t?!0:void 0}var s,l=5;if(I(t))return!1;var h;if(n&&(h=n[a],h=S(h)?h.name:h),e===Tx)if(i===Lx){for(var u=t[a],c=0;c<(u||[]).length&&l>c;c++)if(null!=(s=o(u[r+c])))return s}else for(var c=0;cc;c++){var d=t[r+c];if(d&&null!=(s=o(d[a])))return s}else if(e===Cx){if(!h)return;for(var c=0;cc;c++){var f=t[c];if(f&&null!=(s=o(f[h])))return s}}else if(e===Ax){if(!h)return;var u=t[h];if(!u||I(u))return!1;for(var c=0;cc;c++)if(null!=(s=o(u[c])))return s}else if(e===Ix)for(var c=0;cc;c++){var f=t[c],p=Vn(f);if(!_(p))return!1;if(null!=(s=o(p[a])))return s}return!1}function Ho(t,e){if(e){var i=e.seiresIndex,n=e.seriesId,r=e.seriesName;return null!=i&&t.componentIndex!==i||null!=n&&t.id!==n||null!=r&&t.name!==r}}function Zo(t,e){var i=t.color&&!t.colorLayer;f(e,function(e,a){"colorLayer"===a&&i||yx.hasClass(a)||("object"==typeof e?t[a]=t[a]?r(t[a],e,!1):n(e):null==t[a]&&(t[a]=e))})}function Xo(t){t=t,this.option={},this.option[zx]=1,this._componentsMap=N({series:[]}),this._seriesIndices,this._seriesIndicesMap,Zo(t,this._theme.option),r(t,_x,!1),this.mergeOption(t)}function Yo(t,e){_(e)||(e=e?[e]:[]);var i={};return f(e,function(e){i[e]=(t.get(e)||[]).slice()}),i}function jo(t,e,i){var n=e.type?e.type:i?i.subType:yx.determineSubType(t,e);return n}function qo(t,e){t._seriesIndicesMap=N(t._seriesIndices=p(e,function(t){return t.componentIndex})||[])}function Uo(t,e){return e.hasOwnProperty("subType")?v(t,function(t){return t.subType===e.subType}):t}function $o(t){f(Rx,function(e){this[e]=y(t[e],t)},this)}function Ko(){this._coordinateSystems=[]}function Qo(t){this._api=t,this._timelineOptions=[],this._mediaList=[],this._mediaDefault,this._currentMediaIndices=[],this._optionBackup,this._newBaseOption}function Jo(t,e,i){var n,r,a=[],o=[],s=t.timeline;if(t.baseOption&&(r=t.baseOption),(s||t.options)&&(r=r||{},a=(t.options||[]).slice()),t.media){r=r||{};var l=t.media;Nx(l,function(t){t&&t.option&&(t.query?o.push(t):n||(n=t))})}return r||(r=t),r.timeline||(r.timeline=s),Nx([r].concat(a).concat(p(o,function(t){return t.option})),function(t){Nx(e,function(e){e(t,i)})}),{baseOption:r,timelineOptions:a,mediaDefault:n,mediaList:o}}function ts(t,e,i){var n={width:e,height:i,aspectratio:e/i},r=!0;return f(t,function(t,e){var i=e.match(Gx);if(i&&i[1]&&i[2]){var a=i[1],o=i[2].toLowerCase();es(n[o],t,a)||(r=!1)}}),r}function es(t,e,i){return"min"===i?t>=e:"max"===i?e>=t:t===e}function is(t,e){return t.join(",")===e.join(",")}function ns(t,e){e=e||{},Nx(e,function(e,i){if(null!=e){var n=t[i];if(yx.hasClass(i)){e=Nn(e),n=Nn(n);var r=Gn(n,e);t[i]=Vx(r,function(t){return t.option&&t.exist?Wx(t.exist,t.option,!0):t.exist||t.option})}else t[i]=Wx(n,e,!0)}})}function rs(t){var e=t&&t.itemStyle;if(e)for(var i=0,n=Xx.length;n>i;i++){var a=Xx[i],o=e.normal,s=e.emphasis;o&&o[a]&&(t[a]=t[a]||{},t[a].normal?r(t[a].normal,o[a]):t[a].normal=o[a],o[a]=null),s&&s[a]&&(t[a]=t[a]||{},t[a].emphasis?r(t[a].emphasis,s[a]):t[a].emphasis=s[a],s[a]=null)}}function as(t,e,i){if(t&&t[e]&&(t[e].normal||t[e].emphasis)){var n=t[e].normal,r=t[e].emphasis;n&&(i?(t[e].normal=t[e].emphasis=null,s(t[e],n)):t[e]=n),r&&(t.emphasis=t.emphasis||{},t.emphasis[e]=r)}}function os(t){as(t,"itemStyle"),as(t,"lineStyle"),as(t,"areaStyle"),as(t,"label"),as(t,"labelLine"),as(t,"upperLabel"),as(t,"edgeLabel")}function ss(t,e){var i=Zx(t)&&t[e],n=Zx(i)&&i.textStyle;if(n)for(var r=0,a=sm.length;a>r;r++){var e=sm[r];n.hasOwnProperty(e)&&(i[e]=n[e])}}function ls(t){t&&(os(t),ss(t,"label"),t.emphasis&&ss(t.emphasis,"label"))}function hs(t){if(Zx(t)){rs(t),os(t),ss(t,"label"),ss(t,"upperLabel"),ss(t,"edgeLabel"),t.emphasis&&(ss(t.emphasis,"label"),ss(t.emphasis,"upperLabel"),ss(t.emphasis,"edgeLabel"));var e=t.markPoint;e&&(rs(e),ls(e));var i=t.markLine;i&&(rs(i),ls(i));var n=t.markArea;n&&ls(n);var r=t.data;if("graph"===t.type){r=r||t.nodes;var a=t.links||t.edges;if(a&&!I(a))for(var o=0;o=0;p--){var g=t[p];if(s||(d=g.data.rawIndexOf(g.stackedByDimension,c)),d>=0){var v=g.data.getByRawIndex(g.stackResultDimension,d);if(u>=0&&v>0||0>=u&&0>v){u+=v,f=v;break}}}return n[0]=u,n[1]=f,n});o.hostModel.setData(l),e.data=l})}function vs(t,e){ko.isInstance(t)||(t=ko.seriesDataToSource(t)),this._source=t;var i=this._data=t.data,n=t.sourceFormat;n===kx&&(this._offset=0,this._dimSize=e,this._data=i);var r=Qx[n===Tx?n+"_"+t.seriesLayoutBy:n];o(this,r)}function ms(){return this._data.length}function ys(t){return this._data[t]}function xs(t){for(var e=0;ee.outputData.count()&&e.model.getRawData().cloneShallow(e.outputData)}function zs(t,e){f(t.CHANGABLE_METHODS,function(i){t.wrapMethod(i,x(Es,e))})}function Es(t){var e=Rs(t);e&&e.setOutputEnd(this.count())}function Rs(t){var e=(t.ecModel||{}).scheduler,i=e&&e.getPipeline(t.uid);if(i){var n=i.currentTask;if(n){var r=n.agentStubMap;r&&(n=r.get(t.uid))}return n}}function Bs(){this.group=new lv,this.uid=Za("viewChart"),this.renderTask=Is({plan:Vs,reset:Ws}),this.renderTask.context={view:this}}function Ns(t,e){if(t&&(t.trigger(e),"group"===t.type))for(var i=0;i=0?n():c=setTimeout(n,-a),h=r};return d.clear=function(){c&&(clearTimeout(c),c=null)},d.debounceNextCall=function(t){l=t},d}function Hs(t,e,i,n){var r=t[e];if(r){var a=r[p_]||r,o=r[v_],s=r[g_];if(s!==i||o!==n){if(null==i||!n)return t[e]=a;r=t[e]=Gs(a,i,"debounce"===n),r[p_]=a,r[v_]=n,r[g_]=i}return r}}function Zs(t,e){var i=t[e];i&&i[p_]&&(t[e]=i[p_])}function Xs(t,e,i,n){this.ecInstance=t,this.api=e,this.unfinished;var i=this._dataProcessorHandlers=i.slice(),n=this._visualHandlers=n.slice();this._allHandlers=i.concat(n),this._stageTaskMap=N()}function Ys(t,e,i,n,r){function a(t,e){return t.setDirty&&(!t.dirtyMap||t.dirtyMap.get(e.__pipeline.id))}r=r||{};var o;f(e,function(e){if(!r.visualType||r.visualType===e.visualType){var s=t._stageTaskMap.get(e.uid),l=s.seriesTaskMap,h=s.overallTask;if(h){var u,c=h.agentStubMap;c.each(function(t){a(r,t)&&(t.dirty(),u=!0)}),u&&h.dirty(),S_(h,n);var d=t.getPerformArgs(h,r.block);c.each(function(t){t.perform(d)}),o|=h.perform(d)}else l&&l.each(function(s){a(r,s)&&s.dirty();var l=t.getPerformArgs(s,r.block);l.skip=!e.performRawSeries&&i.isSeriesFiltered(s.context.model),S_(s,n),o|=s.perform(l)})}}),t.unfinished|=o}function js(t,e,i,n,r){function a(i){var a=i.uid,s=o.get(a)||o.set(a,Is({plan:Js,reset:tl,count:il}));s.context={model:i,ecModel:n,api:r,useClearVisual:e.isVisual&&!e.isLayout,plan:e.plan,reset:e.reset,scheduler:t},nl(t,i,s)}var o=i.seriesTaskMap||(i.seriesTaskMap=N()),s=e.seriesType,l=e.getTargetSeries;e.createOnAllSeries?n.eachRawSeries(a):s?n.eachRawSeriesByType(s,a):l&&l(n,r).each(a);var h=t._pipelineMap;o.each(function(t,e){h.get(e)||(t.dispose(),o.removeKey(e))})}function qs(t,e,i,n,r){function a(e){var i=e.uid,n=s.get(i);n||(n=s.set(i,Is({reset:$s,onDirty:Qs})),o.dirty()),n.context={model:e,overallProgress:u,modifyOutputEnd:c},n.agent=o,n.__block=u,nl(t,e,n)}var o=i.overallTask=i.overallTask||Is({reset:Us});o.context={ecModel:n,api:r,overallReset:e.overallReset,scheduler:t};var s=o.agentStubMap=o.agentStubMap||N(),l=e.seriesType,h=e.getTargetSeries,u=!0,c=e.modifyOutputEnd;l?n.eachRawSeriesByType(l,a):h?h(n,r).each(a):(u=!1,f(n.getSeries(),a));var d=t._pipelineMap;s.each(function(t,e){d.get(e)||(t.dispose(),o.dirty(),s.removeKey(e))})}function Us(t){t.overallReset(t.ecModel,t.api,t.payload)}function $s(t){return t.overallProgress&&Ks}function Ks(){this.agent.dirty(),this.getDownstream().dirty()}function Qs(){this.agent&&this.agent.dirty()}function Js(t){return t.plan&&t.plan(t.model,t.ecModel,t.api,t.payload)}function tl(t){t.useClearVisual&&t.data.clearAllVisual();var e=t.resetDefines=Nn(t.reset(t.model,t.ecModel,t.api,t.payload));return e.length>1?p(e,function(t,e){return el(e)}):M_}function el(t){return function(e,i){var n=i.data,r=i.resetDefines[t];if(r&&r.dataEach)for(var a=e.start;a0?parseInt(n,10)/100:n?parseFloat(n):0;var r=i.getAttribute("stop-color")||"#000000";e.addColorStop(n,r)}i=i.nextSibling}}function hl(t,e){t&&t.__inheritedStyle&&(e.__inheritedStyle||(e.__inheritedStyle={}),s(e.__inheritedStyle,t.__inheritedStyle))}function ul(t){for(var e=z(t).split(E_),i=[],n=0;n0;a-=2){var o=r[a],s=r[a-1];switch(n=n||be(),s){case"translate":o=z(o).split(E_),Te(n,n,[parseFloat(o[0]),parseFloat(o[1]||0)]);break;case"scale":o=z(o).split(E_),Ae(n,n,[parseFloat(o[0]),parseFloat(o[1]||o[0])]);break;case"rotate":o=z(o).split(E_),Ce(n,n,parseFloat(o[0]));break;case"skew":o=z(o).split(E_),console.warn("Skew transform is not supported yet");break;case"matrix":var o=z(o).split(E_);n[0]=parseFloat(o[0]),n[1]=parseFloat(o[1]),n[2]=parseFloat(o[2]),n[3]=parseFloat(o[3]),n[4]=parseFloat(o[4]),n[5]=parseFloat(o[5])}}}e.setLocalTransform(n)}function pl(t){var e=t.getAttribute("style"),i={};if(!e)return i;var n={};W_.lastIndex=0;for(var r;null!=(r=W_.exec(e));)n[r[1]]=r[2];for(var a in N_)N_.hasOwnProperty(a)&&null!=n[a]&&(i[N_[a]]=n[a]);return i}function gl(t,e,i){var n=e/t.width,r=i/t.height,a=Math.min(n,r),o=[a,a],s=[-(t.x+t.width/2)*a+e/2,-(t.y+t.height/2)*a+i/2];return{scale:o,position:s}}function vl(t){return function(e,i,n){e=e&&e.toLowerCase(),bg.prototype[t].call(this,e,i,n)}}function ml(){bg.call(this)}function yl(t,e,i){function r(t,e){return t.__prio-e.__prio}i=i||{},"string"==typeof e&&(e=xw[e]),this.id,this.group,this._dom=t;var a="canvas",o=this._zr=On(t,{renderer:i.renderer||a,devicePixelRatio:i.devicePixelRatio,width:i.width,height:i.height});this._throttledZrFlush=Gs(y(o.flush,o),17);var e=n(e);e&&Ux(e,!0),this._theme=e,this._chartsViews=[],this._chartsMap={},this._componentsViews=[],this._componentsMap={},this._coordSysMgr=new Ko;var s=this._api=Rl(this);Si(yw,r),Si(gw,r),this._scheduler=new Xs(this,s,gw,yw),bg.call(this,this._ecEventProcessor=new Bl),this._messageCenter=new ml,this._initEvents(),this.resize=y(this.resize,this),this._pendingActions=[],o.animation.on("frame",this._onframe,this),Tl(o,this),E(this)}function xl(t,e,i){var n,r=this._model,a=this._coordSysMgr.getCoordinateSystems();e=qn(r,e);for(var o=0;oe.get("hoverLayerThreshold")&&!tg.node&&i.traverse(function(t){t.isGroup||(t.useHoverLayer=!0)})}function zl(t,e){var i=t.get("blendMode")||null;e.group.traverse(function(t){t.isGroup||t.style.blend!==i&&t.setStyle("blend",i),t.eachPendingDisplayable&&t.eachPendingDisplayable(function(t){t.setStyle("blend",i)})})}function El(t,e){var i=t.get("z"),n=t.get("zlevel");e.group.traverse(function(t){"group"!==t.type&&(null!=i&&(t.z=i),null!=n&&(t.zlevel=n))})}function Rl(t){var e=t._coordSysMgr;return o(new $o(t),{getCoordinateSystems:y(e.getCoordinateSystems,e),getComponentByElement:function(e){for(;e;){var i=e.__ecComponentInfo;if(null!=i)return t._model.getComponent(i.mainType,i.index);e=e.parent}}})}function Bl(){this.eventInfo}function Nl(t){function e(t,e){for(var i=0;i65535?Ow:zw}function vh(t){var e=t.constructor;return e===Array?t.slice():new e(t)}function mh(t,e){f(Ew.concat(e.__wrappedMethods||[]),function(i){e.hasOwnProperty(i)&&(t[i]=e[i])}),t.__wrappedMethods=e.__wrappedMethods,f(Rw,function(i){t[i]=n(e[i])}),t._calculationInfo=o(e._calculationInfo)}function yh(t){var e=t._invertedIndicesMap;f(e,function(i,n){var r=t._dimensionInfos[n],a=r.ordinalMeta;if(a){i=e[n]=new Ow(a.categories.length);for(var o=0;o=0?this._indices[t]:-1}function bh(t,e){var i=t._idList[e];return null==i&&(i=xh(t,t._idDimIdx,e)),null==i&&(i=Pw+e),i}function Sh(t){return _(t)||(t=[t]),t}function Mh(t,e){var i=t.dimensions,n=new Bw(p(i,t.getDimensionInfo,t),t.hostModel);mh(n,t);for(var r=n._storage={},a=t._storage,o=0;o=0?(r[s]=Ih(a[s]),n._rawExtent[s]=Th(),n._extent[s]=null):r[s]=a[s])}return n}function Ih(t){for(var e=new Array(t.length),i=0;ip;p++){var g=a[p]=o({},S(a[p])?a[p]:{name:a[p]}),v=g.name,m=c[p]={otherDims:{}};null!=v&&null==h.get(v)&&(m.name=m.displayName=v,h.set(v,p)),null!=g.type&&(m.type=g.type),null!=g.displayName&&(m.displayName=g.displayName)}l.each(function(t,e){if(t=Nn(t).slice(),1===t.length&&t[0]<0)return void l.set(e,!1);var i=l.set(e,[]);f(t,function(t,n){b(t)&&(t=h.get(t)),null!=t&&d>t&&(i[n]=t,r(c[t],e,n))})});var y=0;f(t,function(t){var e,t,i,a;if(b(t))e=t,t={};else{e=t.name;var o=t.ordinalMeta;t.ordinalMeta=null,t=n(t),t.ordinalMeta=o,i=t.dimsDef,a=t.otherDims,t.name=t.coordDim=t.coordDimIndex=t.dimsDef=t.otherDims=null}var h=l.get(e);if(h!==!1){var h=Nn(h);if(!h.length)for(var u=0;u<(i&&i.length||1);u++){for(;yI;I++){var m=c[I]=c[I]||{},T=m.coordDim;null==T&&(m.coordDim=Dh(M,u,w),m.coordDimIndex=0,(!x||0>=_)&&(m.isExtraCoord=!0),_--),null==m.name&&(m.name=Dh(m.coordDim,h)),null==m.type&&Wo(e,I,m.name)&&(m.type="ordinal")}return c}function Ah(t,e,i,n){var r=Math.max(t.dimensionsDetectCount||1,e.length,i.length,n||0);return f(e,function(t){var e=t.dimsDef;e&&(r=Math.max(r,e.length)) +}),r}function Dh(t,e,i){if(i||null!=e.get(t)){for(var n=0;null!=e.get(t+n);)n++;t+=n}return e.set(t,!0),t}function kh(t,e,i){i=i||{};var n,r,a,o,s=i.byIndex,l=i.stackedCoordDimension,h=!(!t||!t.get("stack"));if(f(e,function(t,i){b(t)&&(e[i]=t={name:t}),h&&!t.isExtraCoord&&(s||n||!t.ordinalMeta||(n=t),r||"ordinal"===t.type||"time"===t.type||l&&l!==t.coordDim||(r=t))}),!r||s||n||(s=!0),r){a="__\x00ecstackresult",o="__\x00ecstackedover",n&&(n.createInvertedIndices=!0);var u=r.coordDim,c=r.type,d=0;f(e,function(t){t.coordDim===u&&d++}),e.push({name:a,coordDim:u,coordDimIndex:d,type:c,isExtraCoord:!0,isCalculationCoord:!0}),d++,e.push({name:o,coordDim:o,coordDimIndex:d,type:c,isExtraCoord:!0,isCalculationCoord:!0})}return{stackedDimension:r&&r.name,stackedByDimension:n&&n.name,isStackedByIndex:s,stackedOverDimension:o,stackResultDimension:a}}function Ph(t,e){return!!e&&e===t.getCalculationInfo("stackedDimension")}function Lh(t,e){return Ph(t,e)?t.getCalculationInfo("stackResultDimension"):e}function Oh(t,e,i){i=i||{},ko.isInstance(t)||(t=ko.seriesDataToSource(t));var n,r=e.get("coordinateSystem"),a=Ko.get(r),o=Ao(e);o&&(n=p(o.coordSysDims,function(t){var e={name:t},i=o.axisMap.get(t);if(i){var n=i.get("type");e.type=fh(n)}return e})),n||(n=a&&(a.getDimensionsInfo?a.getDimensionsInfo():a.dimensions.slice())||["x","y"]);var s,l,h=Vw(t,{coordDimensions:n,generateCoord:i.generateCoord});o&&f(h,function(t,e){var i=t.coordDim,n=o.categoryAxisMap.get(i);n&&(null==s&&(s=e),t.ordinalMeta=n.getOrdinalMeta()),null!=t.otherDims.itemName&&(l=!0)}),l||null==s||(h[s].otherDims.itemName=0);var u=kh(e,h),c=new Bw(h,e);c.setCalculationInfo(u);var d=null!=s&&zh(t)?function(t,e,i,n){return n===s?i:this.defaultDimValueGetter(t,e,i,n)}:null;return c.hasItemOption=!1,c.initData(t,null,d),c}function zh(t){if(t.sourceFormat===Ix){var e=Eh(t.data||[]);return null!=e&&!_(Vn(e))}}function Eh(t){for(var e=0;eo&&(o=r.interval=i),null!=n&&o>n&&(o=r.interval=n);var s=r.intervalPrecision=Wh(o),l=r.niceTickExtent=[Zw(Math.ceil(t[0]/o)*o,s),Zw(Math.floor(t[1]/o)*o,s)];return Hh(l,t),r}function Wh(t){return Ja(t)+2}function Gh(t,e,i){t[e]=Math.max(Math.min(t[e],i[1]),i[0])}function Hh(t,e){!isFinite(t[0])&&(t[0]=e[0]),!isFinite(t[1])&&(t[1]=e[1]),Gh(t,0,e),Gh(t,1,e),t[0]>t[1]&&(t[0]=t[1])}function Zh(t,e,i,n){var r=[];if(!t)return r;var a=1e4;e[0]a)return[];return e[1]>(r.length?r[r.length-1]:i[1])&&r.push(e[1]),r}function Xh(t){return t.get("stack")||jw+t.seriesIndex}function Yh(t){return t.dim+t.index}function jh(t){var e=[],i=t.axis,n="axis0";if("category"===i.type){for(var r=i.getBandWidth(),a=0;ae&&(e=Math.min(e,s),t.width&&(e=Math.min(e,t.width)),s-=e,t.width=e,l--)}),h=(s-a)/(l+(l-1)*o),h=Math.max(h,0);var u,c=0;f(n,function(t){t.width||(t.width=h),u=t,c+=t.width*(1+o)}),u&&(c-=u.width*o);var d=-c/2;f(n,function(t,n){i[e][n]=i[e][n]||{offset:d,width:t.width},d+=t.width*(1+o)})}),i}function Kh(t,e,i){if(t&&e){var n=t[Yh(e)];return null!=n&&null!=i&&(n=n[Xh(i)]),n}}function Qh(t,e){var i=qh(t,e),n=Uh(i),r={};f(i,function(t){var e=t.getData(),i=t.coordinateSystem,a=i.getBaseAxis(),o=Xh(t),s=n[Yh(a)][o],l=s.offset,h=s.width,u=i.getOtherAxis(a),c=t.get("barMinHeight")||0;r[o]=r[o]||[],e.setLayout({offset:l,size:h});for(var d=e.mapDimension(u.dim),f=e.mapDimension(a.dim),p=Ph(e,d),g=u.isHorizontal(),v=eu(a,u,p),m=0,y=e.count();y>m;m++){var x=e.get(d,m),_=e.get(f,m);if(!isNaN(x)){var w=x>=0?"p":"n",b=v;p&&(r[o][_]||(r[o][_]={p:v,n:v}),b=r[o][_][w]);var S,M,I,T;if(g){var C=i.dataToPoint([x,_]);S=b,M=C[1]+l,I=C[0]-v,T=h,Math.abs(I)I?-1:1)*c),p&&(r[o][_][w]+=I)}else{var C=i.dataToPoint([_,x]);S=C[0]+l,M=b,I=h,T=C[1]-v,Math.abs(T)=T?-1:1)*c),p&&(r[o][_][w]+=T)}e.setItemLayout(m,{x:S,y:M,width:I,height:T})}}},this)}function Jh(t){return t.coordinateSystem&&"cartesian2d"===t.coordinateSystem.type}function tu(t){return t.pipelineContext&&t.pipelineContext.large}function eu(t,e){var i,n,r=e.getGlobalExtent();r[0]>r[1]?(i=r[1],n=r[0]):(i=r[0],n=r[1]);var a=e.toGlobalCoord(e.dataToCoord(0));return i>a&&(a=i),a>n&&(a=n),a}function iu(t,e){return ub(t,hb(e))}function nu(t,e){var i,n,r,a=t.type,o=e.getMin(),s=e.getMax(),l=null!=o,h=null!=s,u=t.getExtent();"ordinal"===a?i=e.getCategories().length:(n=e.get("boundaryGap"),_(n)||(n=[n||0,n||0]),"boolean"==typeof n[0]&&(n=[0,0]),n[0]=Ua(n[0],1),n[1]=Ua(n[1],1),r=u[1]-u[0]||Math.abs(u[0])),null==o&&(o="ordinal"===a?i?0:0/0:u[0]-n[0]*r),null==s&&(s="ordinal"===a?i?i-1:0/0:u[1]+n[1]*r),"dataMin"===o?o=u[0]:"function"==typeof o&&(o=o({min:u[0],max:u[1]})),"dataMax"===s?s=u[1]:"function"==typeof s&&(s=s({min:u[0],max:u[1]})),(null==o||!isFinite(o))&&(o=0/0),(null==s||!isFinite(s))&&(s=0/0),t.setBlank(C(o)||C(s)||"ordinal"===a&&!t.getOrdinalMeta().categories.length),e.getNeedCrossZero()&&(o>0&&s>0&&!l&&(o=0),0>o&&0>s&&!h&&(s=0));var c=e.ecModel;if(c&&"time"===a){var d,p=qh("bar",c);if(f(p,function(t){d|=t.getBaseAxis()===e.axis}),d){var g=Uh(p),v=ru(o,s,e,g);o=v.min,s=v.max}}return[o,s]}function ru(t,e,i,n){var r=i.axis.getExtent(),a=r[1]-r[0],o=Kh(n,i.axis);if(void 0===o)return{min:t,max:e};var s=1/0;f(o,function(t){s=Math.min(t.offset,s)});var l=-1/0;f(o,function(t){l=Math.max(t.offset+t.width,l)}),s=Math.abs(s),l=Math.abs(l);var h=s+l,u=e-t,c=1-(s+l)/a,d=u/c-u;return e+=d*(l/h),t-=d*(s/h),{min:t,max:e}}function au(t,e){var i=nu(t,e),n=null!=e.getMin(),r=null!=e.getMax(),a=e.get("splitNumber");"log"===t.type&&(t.base=e.get("logBase"));var o=t.type;t.setExtent(i[0],i[1]),t.niceExtent({splitNumber:a,fixMin:n,fixMax:r,minInterval:"interval"===o||"time"===o?e.get("minInterval"):null,maxInterval:"interval"===o||"time"===o?e.get("maxInterval"):null});var s=e.get("interval");null!=s&&t.setInterval&&t.setInterval(s)}function ou(t,e){if(e=e||t.get("type"))switch(e){case"category":return new Hw(t.getOrdinalMeta?t.getOrdinalMeta():t.getCategories(),[1/0,-1/0]);case"value":return new Yw;default:return(Rh.getClass(e)||Yw).create(t)}}function su(t){var e=t.scale.getExtent(),i=e[0],n=e[1];return!(i>0&&n>0||0>i&&0>n)}function lu(t){var e=t.getLabelModel().get("formatter"),i="category"===t.type?t.scale.getExtent()[0]:null;return"string"==typeof e?e=function(e){return function(i){return i=t.scale.getLabel(i),e.replace("{value}",null!=i?i:"")}}(e):"function"==typeof e?function(n,r){return null!=i&&(r=n-i),e(hu(t,n),r)}:function(e){return t.scale.getLabel(e)}}function hu(t,e){return"category"===t.type?t.scale.getLabel(e):e}function uu(t){var e=t.model,i=t.scale;if(e.get("axisLabel.show")&&!i.isBlank()){var n,r,a="category"===t.type,o=i.getExtent();a?r=i.count():(n=i.getTicks(),r=n.length);var s,l=t.getLabelModel(),h=lu(t),u=1;r>40&&(u=Math.ceil(r/40));for(var c=0;r>c;c+=u){var d=n?n[c]:o[0]+c,f=h(d),p=l.getTextRect(f),g=cu(p,l.get("rotate")||0);s?s.union(g):s=g}return s}}function cu(t,e){var i=e*Math.PI/180,n=t.plain(),r=n.width,a=n.height,o=r*Math.cos(i)+a*Math.sin(i),s=r*Math.sin(i)+a*Math.cos(i),l=new gi(n.x,n.y,o,s);return l}function du(t,e){if("image"!==this.type){var i=this.style,n=this.shape;n&&"line"===n.symbolType?i.stroke=t:this.__isEmptyBrush?(i.stroke=t,i.fill=e||"#fff"):(i.fill&&(i.fill=t),i.stroke&&(i.stroke=t)),this.dirty(!1)}}function fu(t,e,i,n,r,a,o){var s=0===t.indexOf("empty");s&&(t=t.substr(5,1).toLowerCase()+t.substr(6));var l;return l=0===t.indexOf("image://")?Jr(t.slice(8),new gi(e,i,n,r),o?"center":"cover"):0===t.indexOf("path://")?Qr(t.slice(7),{},new gi(e,i,n,r),o?"center":"cover"):new Mb({shape:{symbolType:t,x:e,y:i,width:n,height:r}}),l.__isEmptyBrush=s,l.setColor=du,l.setColor(a),l}function pu(t){return Oh(t.getSource(),t)}function gu(t,e){var i=e;Wa.isInstance(e)||(i=new Wa(e),c(i,vb));var n=ou(i);return n.setExtent(t[0],t[1]),au(n,i),n}function vu(t){c(t,vb)}function mu(t,e){return Math.abs(t-e)>1^-(1&s),l=l>>1^-(1&l),s+=r,l+=a,r=s,a=l,n.push([s/i,l/i])}return n}function bu(t){return"category"===t.type?Mu(t):Cu(t)}function Su(t,e){return"category"===t.type?Tu(t,e):{ticks:t.scale.getTicks()}}function Mu(t){var e=t.getLabelModel(),i=Iu(t,e);return!e.get("show")||t.scale.isBlank()?{labels:[],labelCategoryInterval:i.labelCategoryInterval}:i}function Iu(t,e){var i=Au(t,"labels"),n=Ru(e),r=Du(i,n);if(r)return r;var a,o;return w(n)?a=Eu(t,n):(o="auto"===n?Pu(t):n,a=zu(t,o)),ku(i,n,{labels:a,labelCategoryInterval:o})}function Tu(t,e){var i=Au(t,"ticks"),n=Ru(e),r=Du(i,n);if(r)return r;var a,o;if((!e.get("show")||t.scale.isBlank())&&(a=[]),w(n))a=Eu(t,n,!0);else if("auto"===n){var s=Iu(t,t.getLabelModel());o=s.labelCategoryInterval,a=p(s.labels,function(t){return t.tickValue})}else o=n,a=zu(t,o,!0);return ku(i,n,{ticks:a,tickCategoryInterval:o})}function Cu(t){var e=t.scale.getTicks(),i=lu(t);return{labels:p(e,function(e,n){return{formattedLabel:i(e,n),rawLabel:t.scale.getLabel(e),tickValue:e}})}}function Au(t,e){return Db(t)[e]||(Db(t)[e]=[])}function Du(t,e){for(var i=0;i40&&(s=Math.max(1,Math.floor(o/40)));for(var l=a[0],h=t.dataToCoord(l+1)-t.dataToCoord(l),u=Math.abs(h*Math.cos(n)),c=Math.abs(h*Math.sin(n)),d=0,f=0;l<=a[1];l+=s){var p=0,g=0,v=Ei(i(l),e.font,"center","top");p=1.3*v.width,g=1.3*v.height,d=Math.max(d,p,7),f=Math.max(f,g,7)}var m=d/u,y=f/c;isNaN(m)&&(m=1/0),isNaN(y)&&(y=1/0);var x=Math.max(0,Math.floor(Math.min(m,y))),_=Db(t.model),w=_.lastAutoInterval,b=_.lastTickCount;return null!=w&&null!=b&&Math.abs(w-x)<=1&&Math.abs(b-o)<=1&&w>x?x=w:(_.lastTickCount=o,_.lastAutoInterval=x),x}function Ou(t){var e=t.getLabelModel();return{axisRotate:t.getRotate?t.getRotate():t.isHorizontal&&!t.isHorizontal()?90:0,labelRotate:e.get("rotate")||0,font:e.getFont()}}function zu(t,e,i){function n(t){l.push(i?t:{formattedLabel:r(t),rawLabel:a.getLabel(t),tickValue:t})}var r=lu(t),a=t.scale,o=a.getExtent(),s=t.getLabelModel(),l=[],h=Math.max((e||0)+1,1),u=o[0],c=a.count();0!==u&&h>1&&c/h>2&&(u=Math.round(Math.ceil(u/h)*h));var d={min:s.get("showMinLabel"),max:s.get("showMaxLabel")};d.min&&u!==o[0]&&n(o[0]);for(var f=u;f<=o[1];f+=h)n(f);return d.max&&f!==o[1]&&n(o[1]),l}function Eu(t,e,i){var n=t.scale,r=lu(t),a=[];return f(n.getTicks(),function(t){var o=n.getLabel(t);e(t,o)&&a.push(i?t:{formattedLabel:r(t),rawLabel:o,tickValue:t})}),a}function Ru(t){var e=t.get("interval");return null==e?"auto":e}function Bu(t,e){var i=t[1]-t[0],n=e,r=i/n/2;t[0]+=r,t[1]-=r}function Nu(t,e,i,n,r){function a(t,e){return u?t>e:e>t}var o=e.length;if(t.onBand&&!n&&o){var s,l=t.getExtent();if(1===o)e[0].coord=l[0],s=e[1]={coord:l[0]};else{var h=e[1].coord-e[0].coord;f(e,function(t){t.coord-=h/2;var e=e||0;e%2>0&&(t.coord-=h/(2*(e+1)))}),s={coord:e[o-1].coord+h},e.push(s)}var u=l[0]>l[1];a(e[0].coord,l[0])&&(r?e[0].coord=l[0]:e.shift()),r&&a(l[0],e[0].coord)&&e.unshift({coord:l[0]}),a(l[1],s.coord)&&(r?s.coord=l[1]:e.pop()),r&&a(s.coord,l[1])&&e.push({coord:l[1]})}}function Fu(t){return this._axes[t]}function Vu(t){Eb.call(this,t)}function Wu(t,e){return e.type||(e.data?"category":"value")}function Gu(t,e){return t.getCoordSysModel()===e}function Hu(t,e,i){this._coordsMap={},this._coordsList=[],this._axesMap={},this._axesList=[],this._initCartesian(t,e,i),this.model=t}function Zu(t,e,i,n){function r(t){return t.dim+"_"+t.index}i.getAxesOnZeroOf=function(){return a?[a]:[]};var a,o=t[e],s=i.model,l=s.get("axisLine.onZero"),h=s.get("axisLine.onZeroAxisIndex");if(l){if(null!=h)Xu(o[h])&&(a=o[h]);else for(var u in o)if(o.hasOwnProperty(u)&&Xu(o[u])&&!n[r(o[u])]){a=o[u];break}a&&(n[r(a)]=!0)}}function Xu(t){return t&&"category"!==t.type&&"time"!==t.type&&su(t)}function Yu(t,e){var i=t.getExtent(),n=i[0]+i[1];t.toGlobalCoord="x"===t.dim?function(t){return t+e}:function(t){return n-t+e},t.toLocalCoord="x"===t.dim?function(t){return t-e}:function(t){return n-t+e}}function ju(t){return p(Zb,function(e){var i=t.getReferringComponents(e)[0];return i})}function qu(t){return"cartesian2d"===t.get("coordinateSystem")}function Uu(t,e){var i=t.mapDimension("defaultedLabel",!0),n=i.length;if(1===n)return Ss(t,e,i[0]);if(n){for(var r=[],a=0;a0?"bottom":"top":r.width>0?"left":"right";l||$u(t.style,d,n,h,a,i,p),xa(t,d)}function ec(t,e){var i=t.get(qb)||0;return Math.min(i,Math.abs(e.width),Math.abs(e.height))}function ic(t,e,i){var n=t.getData(),r=[],a=n.getLayout("valueAxisHorizontal")?1:0;r[1-a]=n.getLayout("valueAxisStart");var o=new Kb({shape:{points:n.getLayout("largePoints")},incremental:!!i,__startPoint:r,__valueIdx:a});e.add(o),nc(o,t,n)}function nc(t,e,i){var n=i.getVisual("borderColor")||i.getVisual("color"),r=e.getModel("itemStyle").getItemStyle(["color","borderColor"]);t.useStyle(r),t.style.fill=null,t.style.stroke=n,t.style.lineWidth=i.getLayout("barWidth")}function rc(t){var e={componentType:t.mainType,componentIndex:t.componentIndex};return e[t.mainType+"Index"]=t.componentIndex,e}function ac(t,e,i,n){var r,a,o=io(i-t.rotation),s=n[0]>n[1],l="start"===e&&!s||"start"!==e&&s;return no(o-Qb/2)?(a=l?"bottom":"top",r="center"):no(o-1.5*Qb)?(a=l?"top":"bottom",r="center"):(a="middle",r=1.5*Qb>o&&o>Qb/2?l?"left":"right":l?"right":"left"),{rotation:o,textAlign:r,textVerticalAlign:a}}function oc(t){var e=t.get("tooltip");return t.get("silent")||!(t.get("triggerEvent")||e&&e.show)}function sc(t,e,i){var n=t.get("axisLabel.showMinLabel"),r=t.get("axisLabel.showMaxLabel");e=e||[],i=i||[];var a=e[0],o=e[1],s=e[e.length-1],l=e[e.length-2],h=i[0],u=i[1],c=i[i.length-1],d=i[i.length-2];n===!1?(lc(a),lc(h)):hc(a,o)&&(n?(lc(o),lc(u)):(lc(a),lc(h))),r===!1?(lc(s),lc(c)):hc(l,s)&&(r?(lc(l),lc(d)):(lc(s),lc(c)))}function lc(t){t&&(t.ignore=!0)}function hc(t,e){var i=t&&t.getBoundingRect().clone(),n=e&&e.getBoundingRect().clone();if(i&&n){var r=Se([]);return Ce(r,r,-t.rotation),i.applyTransform(Ie([],r,t.getLocalTransform())),n.applyTransform(Ie([],r,e.getLocalTransform())),i.intersect(n)}}function uc(t){return"middle"===t||"center"===t}function cc(t,e,i){var n=e.axis;if(e.get("axisTick.show")&&!n.scale.isBlank()){for(var r=e.getModel("axisTick"),a=r.getModel("lineStyle"),o=r.get("length"),l=n.getTicksCoords(),h=[],u=[],c=t._transform,d=[],f=0;f=0||t===e}function xc(t){var e=_c(t);if(e){var i=e.axisPointerModel,n=e.axis.scale,r=i.option,a=i.get("status"),o=i.get("value");null!=o&&(o=n.parse(o));var s=bc(i);null==a&&(r.status=s?"show":"hide");var l=n.getExtent().slice();l[0]>l[1]&&l.reverse(),(null==o||o>l[1])&&(o=l[1]),o0?i=n[0]:n[1]<0&&(i=n[1]),i}function Vc(t,e,i,n){var r=0/0;t.stacked&&(r=i.get(i.getCalculationInfo("stackedOverDimension"),n)),isNaN(r)&&(r=t.valueStart);var a=t.baseDataOffset,o=[];return o[a]=i.get(t.baseDim,n),o[1-a]=r,e.dataToPoint(o)}function Wc(t,e){var i=[];return e.diff(t).add(function(t){i.push({cmd:"+",idx:t})}).update(function(t,e){i.push({cmd:"=",idx:e,idx1:t})}).remove(function(t){i.push({cmd:"-",idx:t})}).execute(),i}function Gc(t){return isNaN(t[0])||isNaN(t[1])}function Hc(t,e,i,n,r,a,o,s,l,h){return"none"!==h&&h?Zc.apply(this,arguments):Xc.apply(this,arguments)}function Zc(t,e,i,n,r,a,o,s,l,h,u){for(var c=0,d=i,f=0;n>f;f++){var p=e[d];if(d>=r||0>d)break;if(Gc(p)){if(u){d+=a;continue}break}if(d===i)t[a>0?"moveTo":"lineTo"](p[0],p[1]);else if(l>0){var g=e[c],v="y"===h?1:0,m=(p[v]-g[v])*l;_S(bS,g),bS[v]=g[v]+m,_S(SS,p),SS[v]=p[v]-m,t.bezierCurveTo(bS[0],bS[1],SS[0],SS[1],p[0],p[1])}else t.lineTo(p[0],p[1]);c=d,d+=a}return f}function Xc(t,e,i,n,r,a,o,s,l,h,u){for(var c=0,d=i,f=0;n>f;f++){var p=e[d];if(d>=r||0>d)break;if(Gc(p)){if(u){d+=a;continue}break}if(d===i)t[a>0?"moveTo":"lineTo"](p[0],p[1]),_S(bS,p);else if(l>0){var g=d+a,v=e[g];if(u)for(;v&&Gc(e[g]);)g+=a,v=e[g];var m=.5,y=e[c],v=e[g];if(!v||Gc(v))_S(SS,p);else{Gc(v)&&!u&&(v=p),j(wS,v,y);var x,_;if("x"===h||"y"===h){var w="x"===h?0:1;x=Math.abs(p[w]-y[w]),_=Math.abs(p[w]-v[w])}else x=yg(p,y),_=yg(p,v);m=_/(_+x),xS(SS,p,wS,-l*(1-m))}mS(bS,bS,s),yS(bS,bS,o),mS(SS,SS,s),yS(SS,SS,o),t.bezierCurveTo(bS[0],bS[1],SS[0],SS[1],p[0],p[1]),xS(bS,p,wS,l*m)}else t.lineTo(p[0],p[1]);c=d,d+=a}return f}function Yc(t,e){var i=[1/0,1/0],n=[-1/0,-1/0];if(e)for(var r=0;rn[0]&&(n[0]=a[0]),a[1]>n[1]&&(n[1]=a[1])}return{min:e?i:n,max:e?n:i}}function jc(t,e){if(t.length===e.length){for(var i=0;ie[0]?1:-1;e[0]+=n*i,e[1]-=n*i}return e}function $c(t,e,i){if(!i.valueDim)return[];for(var n=[],r=0,a=e.count();a>r;r++)n.push(Vc(i,t,e,r));return n}function Kc(t,e,i,n){var r=Uc(t.getAxis("x")),a=Uc(t.getAxis("y")),o=t.getBaseAxis().isHorizontal(),s=Math.min(r[0],r[1]),l=Math.min(a[0],a[1]),h=Math.max(r[0],r[1])-s,u=Math.max(a[0],a[1])-l;if(i)s-=.5,h+=.5,l-=.5,u+=.5;else{var c=n.get("lineStyle.width")||2,d=n.get("clipOverflow")?c/2:Math.max(h,u);o?(l-=d,u+=2*d):(s-=d,h+=2*d)}var f=new Dy({shape:{x:s,y:l,width:h,height:u}});return e&&(f.shape[o?"width":"height"]=0,Oa(f,{shape:{width:h,height:u}},n)),f}function Qc(t,e,i,n){var r=t.getAngleAxis(),a=t.getRadiusAxis(),o=a.getExtent().slice();o[0]>o[1]&&o.reverse();var s=r.getExtent(),l=Math.PI/180;i&&(o[0]-=.5,o[1]+=.5);var h=new Sy({shape:{cx:$a(t.cx,1),cy:$a(t.cy,1),r0:$a(o[0],1),r:$a(o[1],1),startAngle:-s[0]*l,endAngle:-s[1]*l,clockwise:r.inverse}});return e&&(h.shape.endAngle=-s[0]*l,Oa(h,{shape:{endAngle:-s[1]*l}},n)),h}function Jc(t,e,i,n){return"polar"===t.type?Qc(t,e,i,n):Kc(t,e,i,n)}function td(t,e,i){for(var n=e.getBaseAxis(),r="x"===n.dim||"radius"===n.dim?0:1,a=[],o=0;o=0;a--){var o=i[a].dimension,s=t.dimensions[o],l=t.getDimensionInfo(s);if(n=l&&l.coordDim,"x"===n||"y"===n){r=i[a];break}}if(r){var h=e.getAxis(n),u=p(r.stops,function(t){return{coord:h.toGlobalCoord(h.dataToCoord(t.value)),color:t.color}}),c=u.length,d=r.outerColors.slice();c&&u[0].coord>u[c-1].coord&&(u.reverse(),d.reverse());var g=10,v=u[0].coord-g,m=u[c-1].coord+g,y=m-v;if(.001>y)return"transparent";f(u,function(t){t.offset=(t.coord-v)/y}),u.push({offset:c?u[c-1].offset:.5,color:d[1]||"transparent"}),u.unshift({offset:c?u[0].offset:.5,color:d[0]||"transparent"});var x=new Ry(0,0,0,0,u,!0);return x[n]=v,x[n+"2"]=m,x}}}function id(t,e,i){var n=t.get("showAllSymbol"),r="auto"===n;if(!n||r){var a=i.getAxesByScale("ordinal")[0];if(a&&(!r||!nd(a,e))){var o=e.mapDimension(a.dim),s={};return f(a.getViewLabels(),function(t){s[t.tickValue]=1}),function(t){return!s.hasOwnProperty(e.get(o,t))}}}}function nd(t,e){var i=t.getExtent(),n=Math.abs(i[1]-i[0])/t.scale.count();isNaN(n)&&(n=0);for(var r=e.count(),a=Math.max(1,Math.round(r/5)),o=0;r>o;o+=a)if(1.5*Cc.getSymbolSize(e,o)[t.isHorizontal()?1:0]>n)return!1;return!0}function rd(t,e,i,n){var r=e.getData(),a=this.dataIndex,o=r.getName(a),s=e.get("selectedOffset");n.dispatchAction({type:"pieToggleSelect",from:t,name:o,seriesId:e.id}),r.each(function(t){ad(r.getItemGraphicEl(t),r.getItemLayout(t),e.isSelected(r.getName(t)),s,i)})}function ad(t,e,i,n,r){var a=(e.startAngle+e.endAngle)/2,o=Math.cos(a),s=Math.sin(a),l=i?n:0,h=[o*l,s*l];r?t.animate().when(200,{position:h}).start("bounceOut"):t.attr("position",h)}function od(t,e){function i(){a.ignore=a.hoverIgnore,o.ignore=o.hoverIgnore}function n(){a.ignore=a.normalIgnore,o.ignore=o.normalIgnore}lv.call(this);var r=new Sy({z2:2}),a=new Ay,o=new xy;this.add(r),this.add(a),this.add(o),this.updateData(t,e,!0),this.on("emphasis",i).on("normal",n).on("mouseover",i).on("mouseout",n)}function sd(t,e,i,n,r,a,o){function s(e,i,n){for(var r=e;i>r;r++)if(t[r].y+=n,r>e&&i>r+1&&t[r+1].y>t[r].y+t[r].height)return void l(r,n/2);l(i-1,n/2)}function l(e,i){for(var n=e;n>=0&&(t[n].y-=i,!(n>0&&t[n].y>t[n-1].y+t[n-1].height));n--);}function h(t,e,i,n,r,a){for(var o=a>0?e?Number.MAX_VALUE:0:e?Number.MAX_VALUE:0,s=0,l=t.length;l>s;s++)if("center"!==t[s].position){var h=Math.abs(t[s].y-n),u=t[s].len,c=t[s].len2,d=r+u>h?Math.sqrt((r+u+c)*(r+u+c)-h*h):Math.abs(t[s].x-i);e&&d>=o&&(d=o-10),!e&&o>=d&&(d=o+10),t[s].x=i+d*a,o=d}}t.sort(function(t,e){return t.y-e.y});for(var u,c=0,d=t.length,f=[],p=[],g=0;d>g;g++)u=t[g].y-c,0>u&&s(g,d,-u,r),c=t[g].y+t[g].height;0>o-c&&l(d-1,c-o);for(var g=0;d>g;g++)t[g].y>=i?p.push(t[g]):f.push(t[g]);h(f,!1,e,i,n,r),h(p,!0,e,i,n,r)}function ld(t,e,i,n,r,a){for(var o=[],s=[],l=0;lu;u++)a[u]&&xd(t.childAt(u),e,a[u],n,t,r)}}function wd(t){new uh(t.oldChildren,t.newChildren,bd,bd,t).add(Sd).update(Sd).remove(Md).execute()}function bd(t,e){var i=t&&t.name;return null!=i?i:KS+e}function Sd(t,e){var i=this.context,n=null!=t?i.newChildren[t]:null,r=null!=e?i.oldChildren[e]:null;xd(r,i.dataIndex,n,i.animatableModel,i.group,i.data)}function Md(t){var e=this.context,i=e.oldChildren[t];i&&e.group.remove(i)}function Id(t){return t&&(t.pathData||t.d)}function Td(t){return t&&(t.hasOwnProperty("pathData")||t.hasOwnProperty("d"))}function Cd(t,e){return t&&t.hasOwnProperty(e)}function Ad(t,e,i){var n,r={},a="toggleSelected"===t;return i.eachComponent("legend",function(i){a&&null!=n?i[n?"select":"unSelect"](e.name):(i[t](e.name),n=i.isSelected(e.name));var o=i.getData();f(o,function(t){var e=t.get("name");if("\n"!==e&&""!==e){var n=i.isSelected(e);r[e]=r.hasOwnProperty(e)?r[e]&&n:n}})}),{name:e.name,selected:r}}function Dd(t,e){var i=rx(e.get("padding")),n=e.getItemStyle(["color","opacity"]);n.fill=e.get("backgroundColor");var t=new Dy({shape:{x:t.x-i[3],y:t.y-i[0],width:t.width+i[1]+i[3],height:t.height+i[0]+i[2],r:e.get("borderRadius")},style:n,silent:!0,z2:-1});return t}function kd(t,e){e.dispatchAction({type:"legendToggleSelect",name:t})}function Pd(t,e,i,n){var r=i.getZr().storage.getDisplayList()[0];r&&r.useHoverLayer||i.dispatchAction({type:"highlight",seriesName:t,name:e,excludeSeriesId:n})}function Ld(t,e,i,n){var r=i.getZr().storage.getDisplayList()[0];r&&r.useHoverLayer||i.dispatchAction({type:"downplay",seriesName:t,name:e,excludeSeriesId:n})}function Od(t,e,i){var n=t.getOrient(),r=[1,1];r[n.index]=0,So(e,i,{type:"box",ignoreSize:r})}function zd(t,e,i,n,r){var a=t.axis;if(!a.scale.isBlank()&&a.containData(e)){if(!t.involveSeries)return void i.showPointer(t,e);var s=Ed(e,t),l=s.payloadBatch,h=s.snapToValue;l[0]&&null==r.seriesIndex&&o(r,l[0]),!n&&t.snap&&a.containData(h)&&null!=h&&(e=h),i.showPointer(t,e,l,r),i.showTooltip(t,s,h)}}function Ed(t,e){var i=e.axis,n=i.dim,r=t,a=[],o=Number.MAX_VALUE,s=-1;return cM(e.seriesModels,function(e){var l,h,u=e.getData().mapDimension(n,!0);if(e.getAxisTooltipData){var c=e.getAxisTooltipData(u,t,i);h=c.dataIndices,l=c.nestestValue}else{if(h=e.getData().indicesOfNearest(u[0],t,"category"===i.type?.5:null),!h.length)return;l=e.getData().get(u[0],h[0])}if(null!=l&&isFinite(l)){var d=t-l,f=Math.abs(d);o>=f&&((o>f||d>=0&&0>s)&&(o=f,s=d,r=l,a.length=0),cM(h,function(t){a.push({seriesIndex:e.seriesIndex,dataIndexInside:t,dataIndex:e.getData().getRawIndex(t)})}))}}),{payloadBatch:a,snapToValue:r}}function Rd(t,e,i,n){t[e.key]={value:i,payloadBatch:n}}function Bd(t,e,i,n){var r=i.payloadBatch,a=e.axis,o=a.model,s=e.axisPointerModel;if(e.triggerTooltip&&r.length){var l=e.coordSys.model,h=Sc(l),u=t.map[h];u||(u=t.map[h]={coordSysId:l.id,coordSysIndex:l.componentIndex,coordSysType:l.type,coordSysMainType:l.mainType,dataByAxis:[]},t.list.push(u)),u.dataByAxis.push({axisDim:a.dim,axisIndex:o.componentIndex,axisType:o.type,axisId:o.id,value:n,valueLabelOpt:{precision:s.get("label.precision"),formatter:s.get("label.formatter")},seriesDataIndices:r.slice()})}}function Nd(t,e,i){var n=i.axesInfo=[];cM(e,function(e,i){var r=e.axisPointerModel.option,a=t[i];a?(!e.useHandle&&(r.status="show"),r.value=a.value,r.seriesDataIndices=(a.payloadBatch||[]).slice()):!e.useHandle&&(r.status="hide"),"show"===r.status&&n.push({axisDim:e.axis.dim,axisIndex:e.axis.model.componentIndex,value:r.value})})}function Fd(t,e,i,n){if(Hd(e)||!t.list.length)return void n({type:"hideTip"});var r=((t.list[0].dataByAxis[0]||{}).seriesDataIndices||[])[0]||{};n({type:"showTip",escapeConnect:!0,x:e[0],y:e[1],tooltipOption:i.tooltipOption,position:i.position,dataIndexInside:r.dataIndexInside,dataIndex:r.dataIndex,seriesIndex:r.seriesIndex,dataByCoordSys:t.list})}function Vd(t,e,i){var n=i.getZr(),r="axisPointerLastHighlights",a=fM(n)[r]||{},o=fM(n)[r]={};cM(t,function(t){var e=t.axisPointerModel.option;"show"===e.status&&cM(e.seriesDataIndices,function(t){var e=t.seriesIndex+" | "+t.dataIndex;o[e]=t})});var s=[],l=[];f(a,function(t,e){!o[e]&&l.push(t)}),f(o,function(t,e){!a[e]&&s.push(t)}),l.length&&i.dispatchAction({type:"downplay",escapeConnect:!0,batch:l}),s.length&&i.dispatchAction({type:"highlight",escapeConnect:!0,batch:s})}function Wd(t,e){for(var i=0;i<(t||[]).length;i++){var n=t[i];if(e.axis.dim===n.axisDim&&e.axis.model.componentIndex===n.axisIndex)return n}}function Gd(t){var e=t.axis.model,i={},n=i.axisDim=t.axis.dim;return i.axisIndex=i[n+"AxisIndex"]=e.componentIndex,i.axisName=i[n+"AxisName"]=e.name,i.axisId=i[n+"AxisId"]=e.id,i}function Hd(t){return!t||null==t[0]||isNaN(t[0])||null==t[1]||isNaN(t[1])}function Zd(t,e,i){if(!tg.node){var n=e.getZr();gM(n).records||(gM(n).records={}),Xd(n,e);var r=gM(n).records[t]||(gM(n).records[t]={});r.handler=i}}function Xd(t,e){function i(i,n){t.on(i,function(i){var r=Ud(e);vM(gM(t).records,function(t){t&&n(t,i,r.dispatchAction)}),Yd(r.pendings,e)})}gM(t).initialized||(gM(t).initialized=!0,i("click",x(qd,"click")),i("mousemove",x(qd,"mousemove")),i("globalout",jd))}function Yd(t,e){var i,n=t.showTip.length,r=t.hideTip.length;n?i=t.showTip[n-1]:r&&(i=t.hideTip[r-1]),i&&(i.dispatchAction=null,e.dispatchAction(i))}function jd(t,e,i){t.handler("leave",null,i)}function qd(t,e,i,n){e.handler(t,i,n)}function Ud(t){var e={showTip:[],hideTip:[]},i=function(n){var r=e[n.type];r?r.push(n):(n.dispatchAction=i,t.dispatchAction(n))};return{dispatchAction:i,pendings:e}}function $d(t,e){if(!tg.node){var i=e.getZr(),n=(gM(i).records||{})[t];n&&(gM(i).records[t]=null)}}function Kd(){}function Qd(t,e,i,n){Jd(yM(i).lastProp,n)||(yM(i).lastProp=n,e?La(i,n,t):(i.stopAnimation(),i.attr(n)))}function Jd(t,e){if(S(t)&&S(e)){var i=!0;return f(e,function(e,n){i=i&&Jd(t[n],e)}),!!i}return t===e}function tf(t,e){t[e.get("label.show")?"show":"hide"]()}function ef(t){return{position:t.position.slice(),rotation:t.rotation||0}}function nf(t,e,i){var n=e.get("z"),r=e.get("zlevel");t&&t.traverse(function(t){"group"!==t.type&&(null!=n&&(t.z=n),null!=r&&(t.zlevel=r),t.silent=i)})}function rf(t){var e,i=t.get("type"),n=t.getModel(i+"Style");return"line"===i?(e=n.getLineStyle(),e.fill=null):"shadow"===i&&(e=n.getAreaStyle(),e.stroke=null),e}function af(t,e,i,n,r){var a=i.get("value"),o=sf(a,e.axis,e.ecModel,i.get("seriesDataIndices"),{precision:i.get("label.precision"),formatter:i.get("label.formatter")}),s=i.getModel("label"),l=rx(s.get("padding")||0),h=s.getFont(),u=Ei(o,h),c=r.position,d=u.width+l[1]+l[3],f=u.height+l[0]+l[2],p=r.align;"right"===p&&(c[0]-=d),"center"===p&&(c[0]-=d/2);var g=r.verticalAlign;"bottom"===g&&(c[1]-=f),"middle"===g&&(c[1]-=f/2),of(c,d,f,n);var v=s.get("backgroundColor");v&&"auto"!==v||(v=e.get("axisLine.lineStyle.color")),t.label={shape:{x:0,y:0,width:d,height:f,r:s.get("borderRadius")},position:c.slice(),style:{text:o,textFont:h,textFill:s.getTextColor(),textPosition:"inside",fill:v,stroke:s.get("borderColor")||"transparent",lineWidth:s.get("borderWidth")||0,shadowBlur:s.get("shadowBlur"),shadowColor:s.get("shadowColor"),shadowOffsetX:s.get("shadowOffsetX"),shadowOffsetY:s.get("shadowOffsetY")},z2:10}}function of(t,e,i,n){var r=n.getWidth(),a=n.getHeight();t[0]=Math.min(t[0]+e,r)-e,t[1]=Math.min(t[1]+i,a)-i,t[0]=Math.max(t[0],0),t[1]=Math.max(t[1],0)}function sf(t,e,i,n,r){t=e.scale.parse(t);var a=e.scale.getLabel(t,{precision:r.precision}),o=r.formatter;if(o){var s={value:hu(e,t),seriesData:[]};f(n,function(t){var e=i.getSeriesByIndex(t.seriesIndex),n=t.dataIndexInside,r=e&&e.getDataParams(n);r&&s.seriesData.push(r)}),b(o)?a=o.replace("{value}",a):w(o)&&(a=o(s))}return a}function lf(t,e,i){var n=be();return Ce(n,n,i.rotation),Te(n,n,i.position),Ea([t.dataToCoord(e),(i.labelOffset||0)+(i.labelDirection||1)*(i.labelMargin||0)],n)}function hf(t,e,i,n,r,a){var o=Jb.innerTextLayout(i.rotation,0,i.labelDirection);i.labelMargin=r.get("label.margin"),af(e,n,r,a,{position:lf(n.axis,t,i),align:o.textAlign,verticalAlign:o.textVerticalAlign})}function uf(t,e,i){return i=i||0,{x1:t[i],y1:t[1-i],x2:e[i],y2:e[1-i]}}function cf(t,e,i){return i=i||0,{x:t[i],y:t[1-i],width:e[i],height:e[1-i]}}function df(t,e){var i={};return i[e.dim+"AxisIndex"]=e.index,t.getCartesian(i)}function ff(t){return"x"===t.dim?0:1}function pf(t){var e="cubic-bezier(0.23, 1, 0.32, 1)",i="left "+t+"s "+e+",top "+t+"s "+e;return p(IM,function(t){return t+"transition:"+i}).join(";")}function gf(t){var e=[],i=t.get("fontSize"),n=t.getTextColor();return n&&e.push("color:"+n),e.push("font:"+t.getFont()),i&&e.push("line-height:"+Math.round(3*i/2)+"px"),SM(["decoration","align"],function(i){var n=t.get(i);n&&e.push("text-"+i+":"+n)}),e.join(";")}function vf(t){var e=[],i=t.get("transitionDuration"),n=t.get("backgroundColor"),r=t.getModel("textStyle"),a=t.get("padding");return i&&e.push(pf(i)),n&&(tg.canvasSupported?e.push("background-Color:"+n):(e.push("background-Color:#"+je(n)),e.push("filter:alpha(opacity=70)"))),SM(["width","color","radius"],function(i){var n="border-"+i,r=MM(n),a=t.get(r);null!=a&&e.push(n+":"+a+("color"===i?"":"px"))}),e.push(gf(r)),null!=a&&e.push("padding:"+rx(a).join("px ")+"px"),e.join(";")+";"}function mf(t,e){if(tg.wxa)return null;var i=document.createElement("div"),n=this._zr=e.getZr();this.el=i,this._x=e.getWidth()/2,this._y=e.getHeight()/2,t.appendChild(i),this._container=t,this._show=!1,this._hideTimeout;var r=this;i.onmouseenter=function(){r._enterable&&(clearTimeout(r._hideTimeout),r._show=!0),r._inContent=!0},i.onmousemove=function(e){if(e=e||window.event,!r._enterable){var i=n.handler;pe(t,e,!0),i.dispatch("mousemove",e)}},i.onmouseleave=function(){r._enterable&&r._show&&r.hideLater(r._hideDelay),r._inContent=!1}}function yf(t){this._zr=t.getZr(),this._show=!1,this._hideTimeout}function xf(t){for(var e=t.pop();t.length;){var i=t.pop();i&&(Wa.isInstance(i)&&(i=i.get("tooltip",!0)),"string"==typeof i&&(i={formatter:i}),e=new Wa(i,e,e.ecModel))}return e}function _f(t,e){return t.dispatchAction||y(e.dispatchAction,e)}function wf(t,e,i,n,r,a,o){var s=i.getOuterSize(),l=s.width,h=s.height;return null!=a&&(t+l+a>n?t-=l+a:t+=a),null!=o&&(e+h+o>r?e-=h+o:e+=o),[t,e]}function bf(t,e,i,n,r){var a=i.getOuterSize(),o=a.width,s=a.height;return t=Math.min(t+o,n)-o,e=Math.min(e+s,r)-s,t=Math.max(t,0),e=Math.max(e,0),[t,e]}function Sf(t,e,i){var n=i[0],r=i[1],a=5,o=0,s=0,l=e.width,h=e.height;switch(t){case"inside":o=e.x+l/2-n/2,s=e.y+h/2-r/2;break;case"top":o=e.x+l/2-n/2,s=e.y-r-a;break;case"bottom":o=e.x+l/2-n/2,s=e.y+h+a;break;case"left":o=e.x-n-a,s=e.y+h/2-r/2;break;case"right":o=e.x+l+a,s=e.y+h/2-r/2}return[o,s]}function Mf(t){return"center"===t||"middle"===t}function If(t){Fn(t,"label",["show"])}function Tf(t){return!(isNaN(parseFloat(t.x))&&isNaN(parseFloat(t.y)))}function Cf(t){return!isNaN(parseFloat(t.x))&&!isNaN(parseFloat(t.y))}function Af(t,e,i,n,r,a){var o=[],s=Ph(e,n),l=s?e.getCalculationInfo("stackResultDimension"):n,h=zf(e,l,t),u=e.indicesOfNearest(l,h)[0];o[r]=e.get(i,u),o[a]=e.get(n,u);var c=Qa(e.get(n,u));return c=Math.min(c,20),c>=0&&(o[a]=+o[a].toFixed(c)),o}function Df(t,e){var i=t.getData(),r=t.coordinateSystem;if(e&&!Cf(e)&&!_(e.coord)&&r){var a=r.dimensions,o=kf(e,i,r,t);if(e=n(e),e.type&&RM[e.type]&&o.baseAxis&&o.valueAxis){var s=zM(a,o.baseAxis.dim),l=zM(a,o.valueAxis.dim);e.coord=RM[e.type](i,o.baseDataDim,o.valueDataDim,s,l),e.value=e.coord[l]}else{for(var h=[null!=e.xAxis?e.xAxis:e.radiusAxis,null!=e.yAxis?e.yAxis:e.angleAxis],u=0;2>u;u++)RM[h[u]]&&(h[u]=zf(i,i.mapDimension(a[u]),h[u]));e.coord=h}}return e}function kf(t,e,i,n){var r={};return null!=t.valueIndex||null!=t.valueDim?(r.valueDataDim=null!=t.valueIndex?e.getDimension(t.valueIndex):t.valueDim,r.valueAxis=i.getAxis(Pf(n,r.valueDataDim)),r.baseAxis=i.getOtherAxis(r.valueAxis),r.baseDataDim=e.mapDimension(r.baseAxis.dim)):(r.baseAxis=n.getBaseAxis(),r.valueAxis=i.getOtherAxis(r.baseAxis),r.baseDataDim=e.mapDimension(r.baseAxis.dim),r.valueDataDim=e.mapDimension(r.valueAxis.dim)),r}function Pf(t,e){var i=t.getData(),n=i.dimensions;e=i.getDimension(e);for(var r=0;rn?t.coord&&t.coord[n]:t.value}function zf(t,e,i){if("average"===i){var n=0,r=0;return t.each(e,function(t){isNaN(t)||(n+=t,r++)}),n/r}return"median"===i?t.getMedian(e):t.getDataExtent(e,!0)["max"===i?1:0]}function Ef(t,e,i){var n=e.coordinateSystem;t.each(function(r){var a,o=t.getItemModel(r),s=Ua(o.get("x"),i.getWidth()),l=Ua(o.get("y"),i.getHeight());if(isNaN(s)||isNaN(l)){if(e.getMarkerPosition)a=e.getMarkerPosition(t.getValues(t.dimensions,r));else if(n){var h=t.get(n.dimensions[0],r),u=t.get(n.dimensions[1],r);a=n.dataToPoint([h,u])}}else a=[s,l];isNaN(s)||(a[0]=s),isNaN(l)||(a[1]=l),t.setItemLayout(r,a)})}function Rf(t,e,i){var n;n=t?p(t&&t.dimensions,function(t){var i=e.getData().getDimensionInfo(e.getData().mapDimension(t))||{};return s({name:t},i)}):[{name:"value",type:"float"}];var r=new Bw(n,i),a=p(i.get("data"),x(Df,e));return t&&(a=v(a,x(Lf,t))),r.initData(a,null,t?Of:function(t){return t.value}),r}function Bf(t){return isNaN(+t.cpx1)||isNaN(+t.cpy1)}function Nf(t){return"_"+t+"Type"}function Ff(t,e,i){var n=e.getItemVisual(i,"color"),r=e.getItemVisual(i,t),a=e.getItemVisual(i,t+"Size");if(r&&"none"!==r){_(a)||(a=[a,a]);var o=fu(r,-a[0]/2,-a[1]/2,a[0],a[1],n);return o.name=t,o}}function Vf(t){var e=new VM({name:"line"});return Wf(e.shape,t),e}function Wf(t,e){var i=e[0],n=e[1],r=e[2];t.x1=i[0],t.y1=i[1],t.x2=n[0],t.y2=n[1],t.percent=1,r?(t.cpx1=r[0],t.cpy1=r[1]):(t.cpx1=0/0,t.cpy1=0/0)}function Gf(){var t=this,e=t.childOfName("fromSymbol"),i=t.childOfName("toSymbol"),n=t.childOfName("label");if(e||i||!n.ignore){for(var r=1,a=this.parent;a;)a.scale&&(r/=a.scale[0]),a=a.parent;var o=t.childOfName("line");if(this.__dirty||o.__dirty){var s=o.shape.percent,l=o.pointAt(0),h=o.pointAt(s),u=j([],h,l);if(te(u,u),e){e.attr("position",l);var c=o.tangentAt(0);e.attr("rotation",Math.PI/2-Math.atan2(c[1],c[0])),e.attr("scale",[r*s,r*s])}if(i){i.attr("position",h);var c=o.tangentAt(1);i.attr("rotation",-Math.PI/2-Math.atan2(c[1],c[0])),i.attr("scale",[r*s,r*s])}if(!n.ignore){n.attr("position",h);var d,f,p,g=5*r;if("end"===n.__position)d=[u[0]*g+h[0],u[1]*g+h[1]],f=u[0]>.8?"left":u[0]<-.8?"right":"center",p=u[1]>.8?"top":u[1]<-.8?"bottom":"middle";else if("middle"===n.__position){var v=s/2,c=o.tangentAt(v),m=[c[1],-c[0]],y=o.pointAt(v);m[1]>0&&(m[0]=-m[0],m[1]=-m[1]),d=[y[0]+m[0]*g,y[1]+m[1]*g],f="center",p="bottom";var x=-Math.atan2(c[1],c[0]);h[0].8?"right":u[0]<-.8?"left":"center",p=u[1]>.8?"bottom":u[1]<-.8?"top":"middle";n.attr({style:{textVerticalAlign:n.__verticalAlign||p,textAlign:n.__textAlign||f},position:d,scale:[r,r]})}}}}function Hf(t,e,i){lv.call(this),this._createLine(t,e,i)}function Zf(t){this._ctor=t||Hf,this.group=new lv}function Xf(t,e,i,n){var r=e.getItemLayout(i);if(Uf(r)){var a=new t._ctor(e,i,n);e.setItemGraphicEl(i,a),t.group.add(a)}}function Yf(t,e,i,n,r,a){var o=e.getItemGraphicEl(n);return Uf(i.getItemLayout(r))?(o?o.updateData(i,r,a):o=new t._ctor(i,r,a),i.setItemGraphicEl(r,o),void t.group.add(o)):void t.group.remove(o)}function jf(t){var e=t.hostModel;return{lineStyle:e.getModel("lineStyle").getLineStyle(),hoverLineStyle:e.getModel("emphasis.lineStyle").getLineStyle(),labelModel:e.getModel("label"),hoverLabelModel:e.getModel("emphasis.label")}}function qf(t){return isNaN(t[0])||isNaN(t[1])}function Uf(t){return!qf(t[0])&&!qf(t[1])}function $f(t){return!isNaN(t)&&!isFinite(t)}function Kf(t,e,i,n){var r=1-t,a=n.dimensions[t];return $f(e[r])&&$f(i[r])&&e[t]===i[t]&&n.getAxis(a).containData(e[t])}function Qf(t,e){if("cartesian2d"===t.type){var i=e[0].coord,n=e[1].coord;if(i&&n&&(Kf(1,i,n,t)||Kf(0,i,n,t)))return!0}return Lf(t,e[0])&&Lf(t,e[1])}function Jf(t,e,i,n,r){var a,o=n.coordinateSystem,s=t.getItemModel(e),l=Ua(s.get("x"),r.getWidth()),h=Ua(s.get("y"),r.getHeight());if(isNaN(l)||isNaN(h)){if(n.getMarkerPosition)a=n.getMarkerPosition(t.getValues(t.dimensions,e));else{var u=o.dimensions,c=t.get(u[0],e),d=t.get(u[1],e);a=o.dataToPoint([c,d])}if("cartesian2d"===o.type){var f=o.getAxis("x"),p=o.getAxis("y"),u=o.dimensions;$f(t.get(u[0],e))?a[0]=f.toGlobalCoord(f.getExtent()[i?0:1]):$f(t.get(u[1],e))&&(a[1]=p.toGlobalCoord(p.getExtent()[i?0:1]))}isNaN(l)||(a[0]=l),isNaN(h)||(a[1]=h)}else a=[l,h];t.setItemLayout(e,a)}function tp(t,e,i){var n;n=t?p(t&&t.dimensions,function(t){var i=e.getData().getDimensionInfo(e.getData().mapDimension(t))||{};return s({name:t},i)}):[{name:"value",type:"float"}];var r=new Bw(n,i),a=new Bw(n,i),o=new Bw([],i),l=p(i.get("data"),x(ZM,e,t,i));t&&(l=v(l,x(Qf,t)));var h=t?Of:function(t){return t.value};return r.initData(p(l,function(t){return t[0]}),null,h),a.initData(p(l,function(t){return t[1]}),null,h),o.initData(p(l,function(t){return t[2]})),o.hasItemOption=!0,{from:r,to:a,line:o}}function ep(t){return!isNaN(t)&&!isFinite(t)}function ip(t,e,i){var n=1-t;return ep(e[n])&&ep(i[n])}function np(t,e){var i=e.coord[0],n=e.coord[1];return"cartesian2d"===t.type&&i&&n&&(ip(1,i,n,t)||ip(0,i,n,t))?!0:Lf(t,{coord:i,x:e.x0,y:e.y0})||Lf(t,{coord:n,x:e.x1,y:e.y1})}function rp(t,e,i,n,r){var a,o=n.coordinateSystem,s=t.getItemModel(e),l=Ua(s.get(i[0]),r.getWidth()),h=Ua(s.get(i[1]),r.getHeight());if(isNaN(l)||isNaN(h)){if(n.getMarkerPosition)a=n.getMarkerPosition(t.getValues(i,e));else{var u=t.get(i[0],e),c=t.get(i[1],e),d=[u,c];o.clampData&&o.clampData(d,d),a=o.dataToPoint(d,!0)}if("cartesian2d"===o.type){var f=o.getAxis("x"),p=o.getAxis("y"),u=t.get(i[0],e),c=t.get(i[1],e);ep(u)?a[0]=f.toGlobalCoord(f.getExtent()["x0"===i[0]?0:1]):ep(c)&&(a[1]=p.toGlobalCoord(p.getExtent()["y0"===i[1]?0:1]))}isNaN(l)||(a[0]=l),isNaN(h)||(a[1]=h)}else a=[l,h];return a}function ap(t,e,i){var n,r,a=["x0","y0","x1","y1"];t?(n=p(t&&t.dimensions,function(t){var i=e.getData(),n=i.getDimensionInfo(i.mapDimension(t))||{};return s({name:t},n)}),r=new Bw(p(a,function(t,e){return{name:t,type:n[e%2].type}}),i)):(n=[{name:"value",type:"float"}],r=new Bw(n,i));var o=p(i.get("data"),x(XM,e,t,i));t&&(o=v(o,x(np,t)));var l=t?function(t,e,i,n){return t.coord[Math.floor(n/2)][n%2]}:function(t){return t.value};return r.initData(o,null,l),r.hasItemOption=!0,r}function op(t){var e=t.type,i={number:"value",time:"time"};if(i[e]&&(t.axisType=i[e],delete t.type),sp(t),lp(t,"controlPosition")){var n=t.controlStyle||(t.controlStyle={});lp(n,"position")||(n.position=t.controlPosition),"none"!==n.position||lp(n,"show")||(n.show=!1,delete n.position),delete t.controlPosition}f(t.data||[],function(t){S(t)&&!_(t)&&(!lp(t,"value")&&lp(t,"name")&&(t.value=t.name),sp(t))})}function sp(t){var e=t.itemStyle||(t.itemStyle={}),i=e.emphasis||(e.emphasis={}),n=t.label||t.label||{},r=n.normal||(n.normal={}),a={normal:1,emphasis:1};f(n,function(t,e){a[e]||lp(r,e)||(r[e]=t)}),i.label&&!lp(n,"emphasis")&&(n.emphasis=i.label,delete i.label)}function lp(t,e){return t.hasOwnProperty(e)}function hp(t,e){return bo(t.getBoxLayoutParams(),{width:e.getWidth(),height:e.getHeight()},t.get("padding"))}function up(t,e,i,r){var a=Qr(t.get(e).replace(/^path:\/\//,""),n(r||{}),new gi(i[0],i[1],i[2],i[3]),"center");return a}function cp(t,e,i,n,a,o){var s=e.get("color");if(a)a.setColor(s),i.add(a),o&&o.onUpdate(a);else{var l=t.get("symbol");a=fu(l,-1,-1,2,2,s),a.setStyle("strokeNoScale",!0),i.add(a),o&&o.onCreate(a)}var h=e.getItemStyle(["color","symbol","symbolSize"]);a.setStyle(h),n=r({rectHover:!0,z2:100},n,!0);var u=t.get("symbolSize");u=u instanceof Array?u.slice():[+u,+u],u[0]/=2,u[1]/=2,n.scale=u;var c=t.get("symbolOffset");if(c){var d=n.position=n.position||[0,0];d[0]+=Ua(c[0],u[0]),d[1]+=Ua(c[1],u[1])}var f=t.get("symbolRotate");return n.rotation=(f||0)*Math.PI/180||0,a.attr(n),a.updateTransform(),a}function dp(t,e,i,n,r){if(!t.dragging){var a=n.getModel("checkpointStyle"),o=i.dataToCoord(n.getData().get(["value"],e));r||!a.get("animation",!0)?t.attr({position:[o,0]}):(t.stopAnimation(!0),t.animateTo({position:[o,0]},a.get("animationDuration",!0),a.get("animationEasing",!0)))}}function fp(t){return h(iI,t)>=0}function pp(t,e){t=t.slice();var i=p(t,_o);e=(e||[]).slice();var n=p(e,_o);return function(r,a){f(t,function(t,o){for(var s={name:t,capital:i[o]},l=0;l=0}function r(t,n){var r=!1;return e(function(e){f(i(t,e)||[],function(t){n.records[e.name][t]&&(r=!0)})}),r}function a(t,n){n.nodes.push(t),e(function(e){f(i(t,e)||[],function(t){n.records[e.name][t]=!0})})}return function(i){function o(t){!n(t,s)&&r(t,s)&&(a(t,s),l=!0)}var s={nodes:[],records:{}};if(e(function(t){s.records[t.name]={}}),!i)return s;a(i,s);var l;do l=!1,t(o);while(l);return s}}function vp(t,e,i){var n=[1/0,-1/0];return rI(i,function(t){var i=t.getData();i&&rI(i.mapDimension(e,!0),function(t){var e=i.getApproximateExtent(t);e[0]n[1]&&(n[1]=e[1])})}),n[1]0?0:0/0);var o=i.getMax(!0);return null!=o&&"dataMax"!==o&&"function"!=typeof o?e[1]=o:r&&(e[1]=a>0?a-1:0/0),i.get("scale",!0)||(e[0]>0&&(e[0]=0),e[1]<0&&(e[1]=0)),e}function yp(t,e){var i=t.getAxisModel(),n=t._percentWindow,r=t._valueWindow;if(n){var a=to(r,[0,500]);a=Math.min(a,20);var o=e||0===n[0]&&100===n[1];i.setRange(o?null:+r[0].toFixed(a),o?null:+r[1].toFixed(a))}}function xp(t){var e=t._minMaxSpan={},i=t._dataZoomModel;rI(["min","max"],function(n){e[n+"Span"]=i.get(n+"Span");var r=i.get(n+"ValueSpan");if(null!=r&&(e[n+"ValueSpan"]=r,r=t.getAxisModel().axis.scale.parse(r),null!=r)){var a=t._dataExtent;e[n+"Span"]=qa(a[0]+r,a,[0,100],!0)}})}function _p(t){var e={};return sI(["start","end","startValue","endValue","throttle"],function(i){t.hasOwnProperty(i)&&(e[i]=t[i])}),e}function wp(t,e){var i=t._rangePropMode,n=t.get("rangeMode");sI([["start","startValue"],["end","endValue"]],function(t,r){var a=null!=e[t[0]],o=null!=e[t[1]];a&&!o?i[r]="percent":!a&&o?i[r]="value":n?i[r]=n[r]:a&&(i[r]="percent")})}function bp(t,e){var i=t[e]-t[1-e];return{span:Math.abs(i),sign:i>0?-1:0>i?1:e?-1:1}}function Sp(t,e){return Math.min(e[1],Math.max(e[0],t))}function Mp(t){var e={x:"y",y:"x",radius:"angle",angle:"radius"};return e[t]}function Ip(t){return"vertical"===t?"ns-resize":"ew-resize"}function Tp(t,e){return!!Cp(t)[e]}function Cp(t){return t[II]||(t[II]={})}function Ap(t){this.pointerChecker,this._zr=t,this._opt={};var e=y,i=e(Dp,this),r=e(kp,this),a=e(Pp,this),o=e(Lp,this),l=e(Op,this);bg.call(this),this.setPointerChecker=function(t){this.pointerChecker=t},this.enable=function(e,h){this.disable(),this._opt=s(n(h)||{},{zoomOnMouseWheel:!0,moveOnMouseMove:!0,moveOnMouseWheel:!1,preventDefaultMouseMove:!0}),null==e&&(e=!0),(e===!0||"move"===e||"pan"===e)&&(t.on("mousedown",i),t.on("mousemove",r),t.on("mouseup",a)),(e===!0||"scale"===e||"zoom"===e)&&(t.on("mousewheel",o),t.on("pinch",l))},this.disable=function(){t.off("mousedown",i),t.off("mousemove",r),t.off("mouseup",a),t.off("mousewheel",o),t.off("pinch",l)},this.dispose=this.disable,this.isDragging=function(){return this._dragging},this.isPinching=function(){return this._pinching}}function Dp(t){if(!(me(t)||t.target&&t.target.draggable)){var e=t.offsetX,i=t.offsetY;this.pointerChecker&&this.pointerChecker(t,e,i)&&(this._x=e,this._y=i,this._dragging=!0)}}function kp(t){if(!me(t)&&Rp("moveOnMouseMove",t,this._opt)&&this._dragging&&"pinch"!==t.gestureEvent&&!Tp(this._zr,"globalPan")){var e=t.offsetX,i=t.offsetY,n=this._x,r=this._y,a=e-n,o=i-r;this._x=e,this._y=i,this._opt.preventDefaultMouseMove&&Ig(t.event),Ep(this,"pan","moveOnMouseMove",t,{dx:a,dy:o,oldX:n,oldY:r,newX:e,newY:i})}}function Pp(t){me(t)||(this._dragging=!1)}function Lp(t){var e=Rp("zoomOnMouseWheel",t,this._opt),i=Rp("moveOnMouseWheel",t,this._opt),n=t.wheelDelta,r=Math.abs(n),a=t.offsetX,o=t.offsetY;if(0!==n&&(e||i)){if(e){var s=r>3?1.4:r>1?1.2:1.1,l=n>0?s:1/s;zp(this,"zoom","zoomOnMouseWheel",t,{scale:l,originX:a,originY:o})}if(i){var h=Math.abs(n),u=(n>0?1:-1)*(h>3?.4:h>1?.15:.05);zp(this,"scrollMove","moveOnMouseWheel",t,{scrollDelta:u,originX:a,originY:o})}}}function Op(t){if(!Tp(this._zr,"globalPan")){var e=t.pinchScale>1?1.1:1/1.1;zp(this,"zoom",null,t,{scale:e,originX:t.pinchX,originY:t.pinchY})}}function zp(t,e,i,n,r){t.pointerChecker&&t.pointerChecker(n,r.originX,r.originY)&&(Ig(n.event),Ep(t,e,i,n,r))}function Ep(t,e,i,n,r){r.isAvailableBehavior=y(Rp,null,i,n),t.trigger(e,r)}function Rp(t,e,i){var n=i[t];return!t||n&&(!b(n)||e.event[n+"Key"])}function Bp(t,e){var i=Vp(t),n=e.dataZoomId,r=e.coordId;f(i,function(t){var i=t.dataZoomInfos;i[n]&&h(e.allCoordIds,r)<0&&(delete i[n],t.count--)}),Gp(i);var a=i[r];a||(a=i[r]={coordId:r,dataZoomInfos:{},count:0},a.controller=Wp(t,a),a.dispatchAction=x(Hp,t)),!a.dataZoomInfos[n]&&a.count++,a.dataZoomInfos[n]=e;var o=Zp(a.dataZoomInfos);a.controller.enable(o.controlType,o.opt),a.controller.setPointerChecker(e.containsPoint),Hs(a,"dispatchAction",e.dataZoomModel.get("throttle",!0),"fixRate")}function Np(t,e){var i=Vp(t);f(i,function(t){t.controller.dispose();var i=t.dataZoomInfos;i[e]&&(delete i[e],t.count--)}),Gp(i)}function Fp(t){return t.type+"\x00_"+t.id}function Vp(t){var e=t.getZr();return e[TI]||(e[TI]={})}function Wp(t,e){var i=new Ap(t.getZr());return f(["pan","zoom","scrollMove"],function(t){i.on(t,function(i){var n=[];f(e.dataZoomInfos,function(r){if(i.isAvailableBehavior(r.dataZoomModel.option)){var a=(r.getRange||{})[t],o=a&&a(e.controller,i);!r.dataZoomModel.get("disabled",!0)&&o&&n.push({dataZoomId:r.dataZoomId,start:o[0],end:o[1]})}}),n.length&&e.dispatchAction(n)})}),i}function Gp(t){f(t,function(e,i){e.count||(e.controller.dispose(),delete t[i])})}function Hp(t,e){t.dispatchAction({type:"dataZoom",batch:e})}function Zp(t){var e,i="type_",n={type_true:2,type_move:1,type_false:0,type_undefined:-1},r=!0;return f(t,function(t){var a=t.dataZoomModel,o=a.get("disabled",!0)?!1:a.get("zoomLock",!0)?"move":!0;n[i+o]>n[i+e]&&(e=o),r&=a.get("preventDefaultMouseMove",!0)}),{controlType:e,opt:{zoomOnMouseWheel:!0,moveOnMouseMove:!0,moveOnMouseWheel:!0,preventDefaultMouseMove:!!r}}}function Xp(t){return function(e,i,n,r){var a=this._range,o=a.slice(),s=e.axisModels[0];if(s){var l=t(o,s,e,i,n,r);return cI(l,o,[0,100],"all"),this._range=o,a[0]!==o[0]||a[1]!==o[1]?o:void 0}}}function Yp(t){return PI(t)}function jp(){if(!zI&&EI){zI=!0;var t=EI.styleSheets;t.length<31?EI.createStyleSheet().addRule(".zrvml","behavior:url(#default#VML)"):t[0].addRule(".zrvml","behavior:url(#default#VML)")}}function qp(t){return parseInt(t,10)}function Up(t,e){jp(),this.root=t,this.storage=e;var i=document.createElement("div"),n=document.createElement("div");i.style.cssText="display:inline-block;overflow:hidden;position:relative;width:300px;height:150px;",n.style.cssText="position:absolute;left:0;top:0;",t.appendChild(i),this._vmlRoot=n,this._vmlViewport=i,this.resize();var r=e.delFromStorage,a=e.addToStorage;e.delFromStorage=function(t){r.call(e,t),t&&t.onRemove&&t.onRemove(n)},e.addToStorage=function(t){t.onAdd&&t.onAdd(n),a.call(e,t) +},this._firstPaint=!0}function $p(t){return function(){iv('In IE8.0 VML mode painter not support method "'+t+'"')}}var Kp=2311,Qp=function(){return Kp++},Jp={};Jp="object"==typeof wx&&"function"==typeof wx.getSystemInfoSync?{browser:{},os:{},node:!1,wxa:!0,canvasSupported:!0,svgSupported:!1,touchEventsSupported:!0,domSupported:!1}:"undefined"==typeof document&&"undefined"!=typeof self?{browser:{},os:{},node:!1,worker:!0,canvasSupported:!0,domSupported:!1}:"undefined"==typeof navigator?{browser:{},os:{},node:!0,worker:!1,canvasSupported:!0,svgSupported:!0,domSupported:!1}:e(navigator.userAgent);var tg=Jp,eg={"[object Function]":1,"[object RegExp]":1,"[object Date]":1,"[object Error]":1,"[object CanvasGradient]":1,"[object CanvasPattern]":1,"[object Image]":1,"[object Canvas]":1},ig={"[object Int8Array]":1,"[object Uint8Array]":1,"[object Uint8ClampedArray]":1,"[object Int16Array]":1,"[object Uint16Array]":1,"[object Int32Array]":1,"[object Uint32Array]":1,"[object Float32Array]":1,"[object Float64Array]":1},ng=Object.prototype.toString,rg=Array.prototype,ag=rg.forEach,og=rg.filter,sg=rg.slice,lg=rg.map,hg=rg.reduce,ug={},cg=function(){return ug.createCanvas()};ug.createCanvas=function(){return document.createElement("canvas")};var dg,fg="__ec_primitive__";B.prototype={constructor:B,get:function(t){return this.data.hasOwnProperty(t)?this.data[t]:null},set:function(t,e){return this.data[t]=e},each:function(t,e){void 0!==e&&(t=y(t,e));for(var i in this.data)this.data.hasOwnProperty(i)&&t(this.data[i],i)},removeKey:function(t){delete this.data[t]}};var pg=(Object.freeze||Object)({$override:i,clone:n,merge:r,mergeAll:a,extend:o,defaults:s,createCanvas:cg,getContext:l,indexOf:h,inherits:u,mixin:c,isArrayLike:d,each:f,map:p,reduce:g,filter:v,find:m,bind:y,curry:x,isArray:_,isFunction:w,isString:b,isObject:S,isBuiltInObject:M,isTypedArray:I,isDom:T,eqNaN:C,retrieve:A,retrieve2:D,retrieve3:k,slice:P,normalizeCssArray:L,assert:O,trim:z,setAsPrimitive:E,isPrimitive:R,createHashMap:N,concatArray:F,noop:V}),gg="undefined"==typeof Float32Array?Array:Float32Array,vg=q,mg=U,yg=ee,xg=ie,_g=(Object.freeze||Object)({create:W,copy:G,clone:H,set:Z,add:X,scaleAndAdd:Y,sub:j,len:q,length:vg,lenSquare:U,lengthSquare:mg,mul:$,div:K,dot:Q,scale:J,normalize:te,distance:ee,dist:yg,distanceSquare:ie,distSquare:xg,negate:ne,lerp:re,applyTransform:ae,min:oe,max:se});le.prototype={constructor:le,_dragStart:function(t){var e=t.target;e&&e.draggable&&(this._draggingTarget=e,e.dragging=!0,this._x=t.offsetX,this._y=t.offsetY,this.dispatchToElement(he(e,t),"dragstart",t.event))},_drag:function(t){var e=this._draggingTarget;if(e){var i=t.offsetX,n=t.offsetY,r=i-this._x,a=n-this._y;this._x=i,this._y=n,e.drift(r,a,t),this.dispatchToElement(he(e,t),"drag",t.event);var o=this.findHover(i,n,e).target,s=this._dropTarget;this._dropTarget=o,e!==o&&(s&&o!==s&&this.dispatchToElement(he(s,t),"dragleave",t.event),o&&o!==s&&this.dispatchToElement(he(o,t),"dragenter",t.event))}},_dragEnd:function(t){var e=this._draggingTarget;e&&(e.dragging=!1),this.dispatchToElement(he(e,t),"dragend",t.event),this._dropTarget&&this.dispatchToElement(he(this._dropTarget,t),"drop",t.event),this._draggingTarget=null,this._dropTarget=null}};var wg=Array.prototype.slice,bg=function(t){this._$handlers={},this._$eventProcessor=t};bg.prototype={constructor:bg,one:function(t,e,i,n){var r=this._$handlers;if("function"==typeof e&&(n=i,i=e,e=null),!i||!t)return this;e=ue(this,e),r[t]||(r[t]=[]);for(var a=0;ar;r++)i[t][r].h!==e&&n.push(i[t][r]);i[t]=n}i[t]&&0===i[t].length&&delete i[t]}else delete i[t];return this},trigger:function(t){var e=this._$handlers[t],i=this._$eventProcessor;if(e){var n=arguments,r=n.length;r>3&&(n=wg.call(n,1));for(var a=e.length,o=0;a>o;){var s=e[o];if(i&&i.filter&&null!=s.query&&!i.filter(t,s.query))o++;else{switch(r){case 1:s.h.call(s.ctx);break;case 2:s.h.call(s.ctx,n[1]);break;case 3:s.h.call(s.ctx,n[1],n[2]);break;default:s.h.apply(s.ctx,n)}s.one?(e.splice(o,1),a--):o++}}}return i&&i.afterTrigger&&i.afterTrigger(t),this},triggerWithContext:function(t){var e=this._$handlers[t],i=this._$eventProcessor;if(e){var n=arguments,r=n.length;r>4&&(n=wg.call(n,1,n.length-1));for(var a=n[n.length-1],o=e.length,s=0;o>s;){var l=e[s];if(i&&i.filter&&null!=l.query&&!i.filter(t,l.query))s++;else{switch(r){case 1:l.h.call(a);break;case 2:l.h.call(a,n[1]);break;case 3:l.h.call(a,n[1],n[2]);break;default:l.h.apply(a,n)}l.one?(e.splice(s,1),o--):s++}}}return i&&i.afterTrigger&&i.afterTrigger(t),this}};var Sg="undefined"!=typeof window&&!!window.addEventListener,Mg=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ig=Sg?function(t){t.preventDefault(),t.stopPropagation(),t.cancelBubble=!0}:function(t){t.returnValue=!1,t.cancelBubble=!0},Tg="silent";_e.prototype.dispose=function(){};var Cg=["click","dblclick","mousewheel","mouseout","mouseup","mousedown","mousemove","contextmenu"],Ag=function(t,e,i,n){bg.call(this),this.storage=t,this.painter=e,this.painterRoot=n,i=i||new _e,this.proxy=null,this._hovered={},this._lastTouchMoment,this._lastX,this._lastY,le.call(this),this.setHandlerProxy(i)};Ag.prototype={constructor:Ag,setHandlerProxy:function(t){this.proxy&&this.proxy.dispose(),t&&(f(Cg,function(e){t.on&&t.on(e,this[e],this)},this),t.handler=this),this.proxy=t},mousemove:function(t){var e=t.zrX,i=t.zrY,n=this._hovered,r=n.target;r&&!r.__zr&&(n=this.findHover(n.x,n.y),r=n.target);var a=this._hovered=this.findHover(e,i),o=a.target,s=this.proxy;s.setCursor&&s.setCursor(o?o.cursor:"default"),r&&o!==r&&this.dispatchToElement(n,"mouseout",t),this.dispatchToElement(a,"mousemove",t),o&&o!==r&&this.dispatchToElement(a,"mouseover",t)},mouseout:function(t){this.dispatchToElement(this._hovered,"mouseout",t);var e,i=t.toElement||t.relatedTarget;do i=i&&i.parentNode;while(i&&9!=i.nodeType&&!(e=i===this.painterRoot));!e&&this.trigger("globalout",{event:t})},resize:function(){this._hovered={}},dispatch:function(t,e){var i=this[t];i&&i.call(this,e)},dispose:function(){this.proxy.dispose(),this.storage=this.proxy=this.painter=null},setCursorStyle:function(t){var e=this.proxy;e.setCursor&&e.setCursor(t)},dispatchToElement:function(t,e,i){t=t||{};var n=t.target;if(!n||!n.silent){for(var r="on"+e,a=ye(e,t,i);n&&(n[r]&&(a.cancelBubble=n[r].call(n,a)),n.trigger(e,a),n=n.parent,!a.cancelBubble););a.cancelBubble||(this.trigger(e,a),this.painter&&this.painter.eachOtherLayer(function(t){"function"==typeof t[r]&&t[r].call(t,a),t.trigger&&t.trigger(e,a)}))}},findHover:function(t,e,i){for(var n=this.storage.getDisplayList(),r={x:t,y:e},a=n.length-1;a>=0;a--){var o;if(n[a]!==i&&!n[a].ignore&&(o=we(n[a],t,e))&&(!r.topTarget&&(r.topTarget=n[a]),o!==Tg)){r.target=n[a];break}}return r}},f(["click","mousedown","mouseup","mousewheel","dblclick","contextmenu"],function(t){Ag.prototype[t]=function(e){var i=this.findHover(e.zrX,e.zrY),n=i.target;if("mousedown"===t)this._downEl=n,this._downPoint=[e.zrX,e.zrY],this._upEl=n;else if("mouseup"===t)this._upEl=n;else if("click"===t){if(this._downEl!==this._upEl||!this._downPoint||yg(this._downPoint,[e.zrX,e.zrY])>4)return;this._downPoint=null}this.dispatchToElement(i,t,e)}}),c(Ag,bg),c(Ag,le);var Dg="undefined"==typeof Float32Array?Array:Float32Array,kg=(Object.freeze||Object)({create:be,identity:Se,copy:Me,mul:Ie,translate:Te,rotate:Ce,scale:Ae,invert:De,clone:ke}),Pg=Se,Lg=5e-5,Og=function(t){t=t||{},t.position||(this.position=[0,0]),null==t.rotation&&(this.rotation=0),t.scale||(this.scale=[1,1]),this.origin=this.origin||null},zg=Og.prototype;zg.transform=null,zg.needLocalTransform=function(){return Pe(this.rotation)||Pe(this.position[0])||Pe(this.position[1])||Pe(this.scale[0]-1)||Pe(this.scale[1]-1)};var Eg=[];zg.updateTransform=function(){var t=this.parent,e=t&&t.transform,i=this.needLocalTransform(),n=this.transform;if(!i&&!e)return void(n&&Pg(n));n=n||be(),i?this.getLocalTransform(n):Pg(n),e&&(i?Ie(n,t.transform,n):Me(n,t.transform)),this.transform=n;var r=this.globalScaleRatio;if(null!=r&&1!==r){this.getGlobalScale(Eg);var a=Eg[0]<0?-1:1,o=Eg[1]<0?-1:1,s=((Eg[0]-a)*r+a)/Eg[0]||0,l=((Eg[1]-o)*r+o)/Eg[1]||0;n[0]*=s,n[1]*=s,n[2]*=l,n[3]*=l}this.invTransform=this.invTransform||be(),De(this.invTransform,n)},zg.getLocalTransform=function(t){return Og.getLocalTransform(this,t)},zg.setTransform=function(t){var e=this.transform,i=t.dpr||1;e?t.setTransform(i*e[0],i*e[1],i*e[2],i*e[3],i*e[4],i*e[5]):t.setTransform(i,0,0,i,0,0)},zg.restoreTransform=function(t){var e=t.dpr||1;t.setTransform(e,0,0,e,0,0)};var Rg=[],Bg=be();zg.setLocalTransform=function(t){if(t){var e=t[0]*t[0]+t[1]*t[1],i=t[2]*t[2]+t[3]*t[3],n=this.position,r=this.scale;Pe(e-1)&&(e=Math.sqrt(e)),Pe(i-1)&&(i=Math.sqrt(i)),t[0]<0&&(e=-e),t[3]<0&&(i=-i),n[0]=t[4],n[1]=t[5],r[0]=e,r[1]=i,this.rotation=Math.atan2(-t[1]/i,t[0]/e)}},zg.decomposeTransform=function(){if(this.transform){var t=this.parent,e=this.transform;t&&t.transform&&(Ie(Rg,t.invTransform,e),e=Rg);var i=this.origin;i&&(i[0]||i[1])&&(Bg[4]=i[0],Bg[5]=i[1],Ie(Rg,e,Bg),Rg[4]-=i[0],Rg[5]-=i[1],e=Rg),this.setLocalTransform(e)}},zg.getGlobalScale=function(t){var e=this.transform;return t=t||[],e?(t[0]=Math.sqrt(e[0]*e[0]+e[1]*e[1]),t[1]=Math.sqrt(e[2]*e[2]+e[3]*e[3]),e[0]<0&&(t[0]=-t[0]),e[3]<0&&(t[1]=-t[1]),t):(t[0]=1,t[1]=1,t)},zg.transformCoordToLocal=function(t,e){var i=[t,e],n=this.invTransform;return n&&ae(i,i,n),i},zg.transformCoordToGlobal=function(t,e){var i=[t,e],n=this.transform;return n&&ae(i,i,n),i},Og.getLocalTransform=function(t,e){e=e||[],Pg(e);var i=t.origin,n=t.scale||[1,1],r=t.rotation||0,a=t.position||[0,0];return i&&(e[4]-=i[0],e[5]-=i[1]),Ae(e,e,n),r&&Ce(e,e,r),i&&(e[4]+=i[0],e[5]+=i[1]),e[4]+=a[0],e[5]+=a[1],e};var Ng={linear:function(t){return t},quadraticIn:function(t){return t*t},quadraticOut:function(t){return t*(2-t)},quadraticInOut:function(t){return(t*=2)<1?.5*t*t:-.5*(--t*(t-2)-1)},cubicIn:function(t){return t*t*t},cubicOut:function(t){return--t*t*t+1},cubicInOut:function(t){return(t*=2)<1?.5*t*t*t:.5*((t-=2)*t*t+2)},quarticIn:function(t){return t*t*t*t},quarticOut:function(t){return 1- --t*t*t*t},quarticInOut:function(t){return(t*=2)<1?.5*t*t*t*t:-.5*((t-=2)*t*t*t-2)},quinticIn:function(t){return t*t*t*t*t},quinticOut:function(t){return--t*t*t*t*t+1},quinticInOut:function(t){return(t*=2)<1?.5*t*t*t*t*t:.5*((t-=2)*t*t*t*t+2)},sinusoidalIn:function(t){return 1-Math.cos(t*Math.PI/2)},sinusoidalOut:function(t){return Math.sin(t*Math.PI/2)},sinusoidalInOut:function(t){return.5*(1-Math.cos(Math.PI*t))},exponentialIn:function(t){return 0===t?0:Math.pow(1024,t-1)},exponentialOut:function(t){return 1===t?1:1-Math.pow(2,-10*t)},exponentialInOut:function(t){return 0===t?0:1===t?1:(t*=2)<1?.5*Math.pow(1024,t-1):.5*(-Math.pow(2,-10*(t-1))+2)},circularIn:function(t){return 1-Math.sqrt(1-t*t)},circularOut:function(t){return Math.sqrt(1- --t*t)},circularInOut:function(t){return(t*=2)<1?-.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1)},elasticIn:function(t){var e,i=.1,n=.4;return 0===t?0:1===t?1:(!i||1>i?(i=1,e=n/4):e=n*Math.asin(1/i)/(2*Math.PI),-(i*Math.pow(2,10*(t-=1))*Math.sin(2*(t-e)*Math.PI/n)))},elasticOut:function(t){var e,i=.1,n=.4;return 0===t?0:1===t?1:(!i||1>i?(i=1,e=n/4):e=n*Math.asin(1/i)/(2*Math.PI),i*Math.pow(2,-10*t)*Math.sin(2*(t-e)*Math.PI/n)+1)},elasticInOut:function(t){var e,i=.1,n=.4;return 0===t?0:1===t?1:(!i||1>i?(i=1,e=n/4):e=n*Math.asin(1/i)/(2*Math.PI),(t*=2)<1?-.5*i*Math.pow(2,10*(t-=1))*Math.sin(2*(t-e)*Math.PI/n):i*Math.pow(2,-10*(t-=1))*Math.sin(2*(t-e)*Math.PI/n)*.5+1)},backIn:function(t){var e=1.70158;return t*t*((e+1)*t-e)},backOut:function(t){var e=1.70158;return--t*t*((e+1)*t+e)+1},backInOut:function(t){var e=2.5949095;return(t*=2)<1?.5*t*t*((e+1)*t-e):.5*((t-=2)*t*((e+1)*t+e)+2)},bounceIn:function(t){return 1-Ng.bounceOut(1-t)},bounceOut:function(t){return 1/2.75>t?7.5625*t*t:2/2.75>t?7.5625*(t-=1.5/2.75)*t+.75:2.5/2.75>t?7.5625*(t-=2.25/2.75)*t+.9375:7.5625*(t-=2.625/2.75)*t+.984375},bounceInOut:function(t){return.5>t?.5*Ng.bounceIn(2*t):.5*Ng.bounceOut(2*t-1)+.5}};Le.prototype={constructor:Le,step:function(t,e){if(this._initialized||(this._startTime=t+this._delay,this._initialized=!0),this._paused)return void(this._pausedTime+=e);var i=(t-this._startTime-this._pausedTime)/this._life;if(!(0>i)){i=Math.min(i,1);var n=this.easing,r="string"==typeof n?Ng[n]:n,a="function"==typeof r?r(i):i;return this.fire("frame",a),1==i?this.loop?(this.restart(t),"restart"):(this._needsRemove=!0,"destroy"):null}},restart:function(t){var e=(t-this._startTime-this._pausedTime)%this._life;this._startTime=t-e+this.gap,this._pausedTime=0,this._needsRemove=!1},fire:function(t,e){t="on"+t,this[t]&&this[t](this._target,e)},pause:function(){this._paused=!0},resume:function(){this._paused=!1}};var Fg=function(){this.head=null,this.tail=null,this._len=0},Vg=Fg.prototype;Vg.insert=function(t){var e=new Wg(t);return this.insertEntry(e),e},Vg.insertEntry=function(t){this.head?(this.tail.next=t,t.prev=this.tail,t.next=null,this.tail=t):this.head=this.tail=t,this._len++},Vg.remove=function(t){var e=t.prev,i=t.next;e?e.next=i:this.head=i,i?i.prev=e:this.tail=e,t.next=t.prev=null,this._len--},Vg.len=function(){return this._len},Vg.clear=function(){this.head=this.tail=null,this._len=0};var Wg=function(t){this.value=t,this.next,this.prev},Gg=function(t){this._list=new Fg,this._map={},this._maxSize=t||10,this._lastRemovedEntry=null},Hg=Gg.prototype;Hg.put=function(t,e){var i=this._list,n=this._map,r=null;if(null==n[t]){var a=i.len(),o=this._lastRemovedEntry;if(a>=this._maxSize&&a>0){var s=i.head;i.remove(s),delete n[s.key],r=s.value,this._lastRemovedEntry=s}o?o.value=e:o=new Wg(e),o.key=t,i.insertEntry(o),n[t]=o}return r},Hg.get=function(t){var e=this._map[t],i=this._list;return null!=e?(e!==i.tail&&(i.remove(e),i.insertEntry(e)),e.value):void 0},Hg.clear=function(){this._list.clear(),this._map={}};var Zg={transparent:[0,0,0,0],aliceblue:[240,248,255,1],antiquewhite:[250,235,215,1],aqua:[0,255,255,1],aquamarine:[127,255,212,1],azure:[240,255,255,1],beige:[245,245,220,1],bisque:[255,228,196,1],black:[0,0,0,1],blanchedalmond:[255,235,205,1],blue:[0,0,255,1],blueviolet:[138,43,226,1],brown:[165,42,42,1],burlywood:[222,184,135,1],cadetblue:[95,158,160,1],chartreuse:[127,255,0,1],chocolate:[210,105,30,1],coral:[255,127,80,1],cornflowerblue:[100,149,237,1],cornsilk:[255,248,220,1],crimson:[220,20,60,1],cyan:[0,255,255,1],darkblue:[0,0,139,1],darkcyan:[0,139,139,1],darkgoldenrod:[184,134,11,1],darkgray:[169,169,169,1],darkgreen:[0,100,0,1],darkgrey:[169,169,169,1],darkkhaki:[189,183,107,1],darkmagenta:[139,0,139,1],darkolivegreen:[85,107,47,1],darkorange:[255,140,0,1],darkorchid:[153,50,204,1],darkred:[139,0,0,1],darksalmon:[233,150,122,1],darkseagreen:[143,188,143,1],darkslateblue:[72,61,139,1],darkslategray:[47,79,79,1],darkslategrey:[47,79,79,1],darkturquoise:[0,206,209,1],darkviolet:[148,0,211,1],deeppink:[255,20,147,1],deepskyblue:[0,191,255,1],dimgray:[105,105,105,1],dimgrey:[105,105,105,1],dodgerblue:[30,144,255,1],firebrick:[178,34,34,1],floralwhite:[255,250,240,1],forestgreen:[34,139,34,1],fuchsia:[255,0,255,1],gainsboro:[220,220,220,1],ghostwhite:[248,248,255,1],gold:[255,215,0,1],goldenrod:[218,165,32,1],gray:[128,128,128,1],green:[0,128,0,1],greenyellow:[173,255,47,1],grey:[128,128,128,1],honeydew:[240,255,240,1],hotpink:[255,105,180,1],indianred:[205,92,92,1],indigo:[75,0,130,1],ivory:[255,255,240,1],khaki:[240,230,140,1],lavender:[230,230,250,1],lavenderblush:[255,240,245,1],lawngreen:[124,252,0,1],lemonchiffon:[255,250,205,1],lightblue:[173,216,230,1],lightcoral:[240,128,128,1],lightcyan:[224,255,255,1],lightgoldenrodyellow:[250,250,210,1],lightgray:[211,211,211,1],lightgreen:[144,238,144,1],lightgrey:[211,211,211,1],lightpink:[255,182,193,1],lightsalmon:[255,160,122,1],lightseagreen:[32,178,170,1],lightskyblue:[135,206,250,1],lightslategray:[119,136,153,1],lightslategrey:[119,136,153,1],lightsteelblue:[176,196,222,1],lightyellow:[255,255,224,1],lime:[0,255,0,1],limegreen:[50,205,50,1],linen:[250,240,230,1],magenta:[255,0,255,1],maroon:[128,0,0,1],mediumaquamarine:[102,205,170,1],mediumblue:[0,0,205,1],mediumorchid:[186,85,211,1],mediumpurple:[147,112,219,1],mediumseagreen:[60,179,113,1],mediumslateblue:[123,104,238,1],mediumspringgreen:[0,250,154,1],mediumturquoise:[72,209,204,1],mediumvioletred:[199,21,133,1],midnightblue:[25,25,112,1],mintcream:[245,255,250,1],mistyrose:[255,228,225,1],moccasin:[255,228,181,1],navajowhite:[255,222,173,1],navy:[0,0,128,1],oldlace:[253,245,230,1],olive:[128,128,0,1],olivedrab:[107,142,35,1],orange:[255,165,0,1],orangered:[255,69,0,1],orchid:[218,112,214,1],palegoldenrod:[238,232,170,1],palegreen:[152,251,152,1],paleturquoise:[175,238,238,1],palevioletred:[219,112,147,1],papayawhip:[255,239,213,1],peachpuff:[255,218,185,1],peru:[205,133,63,1],pink:[255,192,203,1],plum:[221,160,221,1],powderblue:[176,224,230,1],purple:[128,0,128,1],red:[255,0,0,1],rosybrown:[188,143,143,1],royalblue:[65,105,225,1],saddlebrown:[139,69,19,1],salmon:[250,128,114,1],sandybrown:[244,164,96,1],seagreen:[46,139,87,1],seashell:[255,245,238,1],sienna:[160,82,45,1],silver:[192,192,192,1],skyblue:[135,206,235,1],slateblue:[106,90,205,1],slategray:[112,128,144,1],slategrey:[112,128,144,1],snow:[255,250,250,1],springgreen:[0,255,127,1],steelblue:[70,130,180,1],tan:[210,180,140,1],teal:[0,128,128,1],thistle:[216,191,216,1],tomato:[255,99,71,1],turquoise:[64,224,208,1],violet:[238,130,238,1],wheat:[245,222,179,1],white:[255,255,255,1],whitesmoke:[245,245,245,1],yellow:[255,255,0,1],yellowgreen:[154,205,50,1]},Xg=new Gg(20),Yg=null,jg=qe,qg=Ue,Ug=(Object.freeze||Object)({parse:He,lift:Ye,toHex:je,fastLerp:qe,fastMapToColor:jg,lerp:Ue,mapToColor:qg,modifyHSL:$e,modifyAlpha:Ke,stringify:Qe}),$g=Array.prototype.slice,Kg=function(t,e,i,n){this._tracks={},this._target=t,this._loop=e||!1,this._getter=i||Je,this._setter=n||ti,this._clipCount=0,this._delay=0,this._doneList=[],this._onframeList=[],this._clipList=[]};Kg.prototype={when:function(t,e){var i=this._tracks;for(var n in e)if(e.hasOwnProperty(n)){if(!i[n]){i[n]=[];var r=this._getter(this._target,n);if(null==r)continue;0!==t&&i[n].push({time:0,value:li(r)})}i[n].push({time:t,value:e[n]})}return this},during:function(t){return this._onframeList.push(t),this},pause:function(){for(var t=0;ti;i++)t[i].call(this)},start:function(t,e){var i,n=this,r=0,a=function(){r--,r||n._doneCallback()};for(var o in this._tracks)if(this._tracks.hasOwnProperty(o)){var s=ci(this,t,a,this._tracks[o],o,e);s&&(this._clipList.push(s),r++,this.animation&&this.animation.addClip(s),i=s)}if(i){var l=i.onframe;i.onframe=function(t,e){l(t,e);for(var i=0;i1&&(ev=function(){for(var t in arguments)console.log(arguments[t])});var iv=ev,nv=function(){this.animators=[]};nv.prototype={constructor:nv,animate:function(t,e){var i,n=!1,r=this,a=this.__zr;if(t){var o=t.split("."),s=r;n="shape"===o[0];for(var l=0,u=o.length;u>l;l++)s&&(s=s[o[l]]);s&&(i=s)}else i=r;if(!i)return void iv('Property "'+t+'" is not existed in element '+r.id);var c=r.animators,d=new Kg(i,e);return d.during(function(){r.dirty(n)}).done(function(){c.splice(h(c,d),1)}),c.push(d),a&&a.animation.addAnimator(d),d},stopAnimation:function(t){for(var e=this.animators,i=e.length,n=0;i>n;n++)e[n].stop(t);return e.length=0,this},animateTo:function(t,e,i,n,r,a){di(this,t,e,i,n,r,a)},animateFrom:function(t,e,i,n,r,a){di(this,t,e,i,n,r,a,!0)}};var rv=function(t){Og.call(this,t),bg.call(this,t),nv.call(this,t),this.id=t.id||Qp()};rv.prototype={type:"element",name:"",__zr:null,ignore:!1,clipPath:null,isGroup:!1,drift:function(t,e){switch(this.draggable){case"horizontal":e=0;break;case"vertical":t=0}var i=this.transform;i||(i=this.transform=[1,0,0,1,0,0]),i[4]+=t,i[5]+=e,this.decomposeTransform(),this.dirty(!1)},beforeUpdate:function(){},afterUpdate:function(){},update:function(){this.updateTransform()},traverse:function(){},attrKV:function(t,e){if("position"===t||"scale"===t||"origin"===t){if(e){var i=this[t];i||(i=this[t]=[]),i[0]=e[0],i[1]=e[1]}}else this[t]=e},hide:function(){this.ignore=!0,this.__zr&&this.__zr.refresh()},show:function(){this.ignore=!1,this.__zr&&this.__zr.refresh()},attr:function(t,e){if("string"==typeof t)this.attrKV(t,e);else if(S(t))for(var i in t)t.hasOwnProperty(i)&&this.attrKV(i,t[i]);return this.dirty(!1),this},setClipPath:function(t){var e=this.__zr;e&&t.addSelfToZr(e),this.clipPath&&this.clipPath!==t&&this.removeClipPath(),this.clipPath=t,t.__zr=e,t.__clipTarget=this,this.dirty(!1)},removeClipPath:function(){var t=this.clipPath;t&&(t.__zr&&t.removeSelfFromZr(t.__zr),t.__zr=null,t.__clipTarget=null,this.clipPath=null,this.dirty(!1))},addSelfToZr:function(t){this.__zr=t;var e=this.animators;if(e)for(var i=0;in||i>s||l>a||r>h)},contain:function(t,e){var i=this;return t>=i.x&&t<=i.x+i.width&&e>=i.y&&e<=i.y+i.height},clone:function(){return new gi(this.x,this.y,this.width,this.height)},copy:function(t){this.x=t.x,this.y=t.y,this.width=t.width,this.height=t.height},plain:function(){return{x:this.x,y:this.y,width:this.width,height:this.height}}},gi.create=function(t){return new gi(t.x,t.y,t.width,t.height)};var lv=function(t){t=t||{},rv.call(this,t);for(var e in t)t.hasOwnProperty(e)&&(this[e]=t[e]);this._children=[],this.__storage=null,this.__dirty=!0};lv.prototype={constructor:lv,isGroup:!0,type:"group",silent:!1,children:function(){return this._children.slice()},childAt:function(t){return this._children[t]},childOfName:function(t){for(var e=this._children,i=0;i=0&&(i.splice(n,0,t),this._doAdd(t))}return this},_doAdd:function(t){t.parent&&t.parent.remove(t),t.parent=this;var e=this.__storage,i=this.__zr;e&&e!==t.__storage&&(e.addToStorage(t),t instanceof lv&&t.addChildrenToStorage(e)),i&&i.refresh()},remove:function(t){var e=this.__zr,i=this.__storage,n=this._children,r=h(n,t);return 0>r?this:(n.splice(r,1),t.parent=null,i&&(i.delFromStorage(t),t instanceof lv&&t.delChildrenFromStorage(i)),e&&e.refresh(),this)},removeAll:function(){var t,e,i=this._children,n=this.__storage;for(e=0;en;n++)this._updateAndAddDisplayable(e[n],null,t);i.length=this._displayListLen,tg.canvasSupported&&Si(i,Mi)},_updateAndAddDisplayable:function(t,e,i){if(!t.ignore||i){t.beforeUpdate(),t.__dirty&&t.update(),t.afterUpdate();var n=t.clipPath;if(n){e=e?e.slice():[];for(var r=n,a=t;r;)r.parent=a,r.updateTransform(),e.push(r),a=r,r=r.clipPath}if(t.isGroup){for(var o=t._children,s=0;se;e++)this.delRoot(t[e]);else{var r=h(this._roots,t);r>=0&&(this.delFromStorage(t),this._roots.splice(r,1),t instanceof lv&&t.delChildrenFromStorage(this))}},addToStorage:function(t){return t&&(t.__storage=this,t.dirty(!1)),this},delFromStorage:function(t){return t&&(t.__storage=null),this},dispose:function(){this._renderList=this._roots=null},displayableSortFunc:Mi};var dv={shadowBlur:1,shadowOffsetX:1,shadowOffsetY:1,textShadowBlur:1,textShadowOffsetX:1,textShadowOffsetY:1,textBoxShadowBlur:1,textBoxShadowOffsetX:1,textBoxShadowOffsetY:1},fv=function(t,e,i){return dv.hasOwnProperty(e)?i*=t.dpr:i},pv=[["shadowBlur",0],["shadowOffsetX",0],["shadowOffsetY",0],["shadowColor","#000"],["lineCap","butt"],["lineJoin","miter"],["miterLimit",10]],gv=function(t){this.extendFrom(t,!1)};gv.prototype={constructor:gv,fill:"#000",stroke:null,opacity:1,fillOpacity:null,strokeOpacity:null,lineDash:null,lineDashOffset:0,shadowBlur:0,shadowOffsetX:0,shadowOffsetY:0,lineWidth:1,strokeNoScale:!1,text:null,font:null,textFont:null,fontStyle:null,fontWeight:null,fontSize:null,fontFamily:null,textTag:null,textFill:"#000",textStroke:null,textWidth:null,textHeight:null,textStrokeWidth:0,textLineHeight:null,textPosition:"inside",textRect:null,textOffset:null,textAlign:null,textVerticalAlign:null,textDistance:5,textShadowColor:"transparent",textShadowBlur:0,textShadowOffsetX:0,textShadowOffsetY:0,textBoxShadowColor:"transparent",textBoxShadowBlur:0,textBoxShadowOffsetX:0,textBoxShadowOffsetY:0,transformText:!1,textRotation:0,textOrigin:null,textBackgroundColor:null,textBorderColor:null,textBorderWidth:0,textBorderRadius:0,textPadding:null,rich:null,truncate:null,blend:null,bind:function(t,e,i){for(var n=this,r=i&&i.style,a=!r,o=0;o0},extendFrom:function(t,e){if(t)for(var i in t)!t.hasOwnProperty(i)||e!==!0&&(e===!1?this.hasOwnProperty(i):null==t[i])||(this[i]=t[i])},set:function(t,e){"string"==typeof t?this[t]=e:this.extendFrom(t,!0)},clone:function(){var t=new this.constructor;return t.extendFrom(this,!0),t},getGradient:function(t,e,i){for(var n="radial"===e.type?Ti:Ii,r=n(t,e,i),a=e.colorStops,o=0;o=0&&i.splice(n,1),t.__hoverMir=null},clearHover:function(){for(var t=this._hoverElements,e=0;er;){var a=t[r],o=a.__from;o&&o.__zr?(r++,o.invisible||(a.transform=o.transform,a.invTransform=o.invTransform,a.__clipPaths=o.__clipPaths,this._doPaintEl(a,i,!0,n))):(t.splice(r,1),o.__hoverMir=null,e--)}i.ctx.restore()}},getHoverLayer:function(){return this.getLayer(zv)},_paintList:function(t,e,i){if(this._redrawId===i){e=e||!1,this._updateLayerStatus(t);var n=this._doPaintList(t,e);if(this._needsManuallyCompositing&&this._compositeManually(),!n){var r=this;wv(function(){r._paintList(t,e,i)})}}},_compositeManually:function(){var t=this.getLayer(Ev).ctx,e=this._domRoot.width,i=this._domRoot.height;t.clearRect(0,0,e,i),this.eachBuiltinLayer(function(n){n.virtual&&t.drawImage(n.dom,0,0,e,i)})},_doPaintList:function(t,e){for(var i=[],n=0;n15)break}}a.__drawIndex=v,a.__drawIndex0&&t>n[0]){for(o=0;r-1>o&&!(n[o]t);o++);a=i[n[o]]}if(n.splice(o+1,0,t),i[t]=e,!e.virtual)if(a){var l=a.dom;l.nextSibling?s.insertBefore(e.dom,l.nextSibling):s.appendChild(e.dom)}else s.firstChild?s.insertBefore(e.dom,s.firstChild):s.appendChild(e.dom)},eachLayer:function(t,e){var i,n,r=this._zlevelList;for(n=0;n0?Rv:0),this._needsManuallyCompositing),o.__builtin__||iv("ZLevel "+s+" has been used by unkown layer "+o.id),o!==r&&(o.__used=!0,o.__startIndex!==i&&(o.__dirty=!0),o.__startIndex=i,o.__drawIndex=o.incremental?-1:i,e(i),r=o),n.__dirty&&(o.__dirty=!0,o.incremental&&o.__drawIndex<0&&(o.__drawIndex=i))}e(i),this.eachBuiltinLayer(function(t){!t.__used&&t.getElementCount()>0&&(t.__dirty=!0,t.__startIndex=t.__endIndex=t.__drawIndex=0),t.__dirty&&t.__drawIndex<0&&(t.__drawIndex=t.__startIndex)})},clear:function(){return this.eachBuiltinLayer(this._clearLayer),this},_clearLayer:function(t){t.clear()},setBackgroundColor:function(t){this._backgroundColor=t},configLayer:function(t,e){if(e){var i=this._layerConfig;i[t]?r(i[t],e,!0):i[t]=e;for(var n=0;n=0&&this._clips.splice(e,1)},removeAnimator:function(t){for(var e=t.getClips(),i=0;io;o++){var s=i[o],l=s.step(t,e);l&&(r.push(l),a.push(s))}for(var o=0;n>o;)i[o]._needsRemove?(i[o]=i[n-1],i.pop(),n--):o++;n=r.length;for(var o=0;n>o;o++)a[o].fire(r[o]);this._time=t,this.onframe(e),this.trigger("frame",e),this.stage.update&&this.stage.update()},_startLoop:function(){function t(){e._running&&(wv(t),!e._paused&&e._update())}var e=this;this._running=!0,wv(t)},start:function(){this._time=(new Date).getTime(),this._pausedTime=0,this._startLoop()},stop:function(){this._running=!1},pause:function(){this._paused||(this._pauseStart=(new Date).getTime(),this._paused=!0)},resume:function(){this._paused&&(this._pausedTime+=(new Date).getTime()-this._pauseStart,this._paused=!1)},clear:function(){this._clips=[]},isFinished:function(){return!this._clips.length},animate:function(t,e){e=e||{};var i=new Kg(t,e.loop,e.getter,e.setter);return this.addAnimator(i),i}},c(Wv,bg);var Gv=function(){this._track=[]};Gv.prototype={constructor:Gv,recognize:function(t,e,i){return this._doTrack(t,e,i),this._recognize(t)},clear:function(){return this._track.length=0,this},_doTrack:function(t,e,i){var n=t.touches;if(n){for(var r={points:[],touches:[],target:e,event:t},a=0,o=n.length;o>a;a++){var s=n[a],l=de(i,s,{});r.points.push([l.zrX,l.zrY]),r.touches.push(s)}this._track.push(r)}},_recognize:function(t){for(var e in Hv)if(Hv.hasOwnProperty(e)){var i=Hv[e](this._track,t);if(i)return i}}};var Hv={pinch:function(t,e){var i=t.length;if(i){var n=(t[i-1]||{}).points,r=(t[i-2]||{}).points||n;if(r&&r.length>1&&n&&n.length>1){var a=In(n)/In(r);!isFinite(a)&&(a=1),e.pinchScale=a;var o=Tn(n);return e.pinchX=o[0],e.pinchY=o[1],{type:"pinch",target:t[0].target,event:e}}}}},Zv=300,Xv=["click","dblclick","mousewheel","mouseout","mouseup","mousedown","mousemove","contextmenu"],Yv=["touchstart","touchend","touchmove"],jv={pointerdown:1,pointerup:1,pointermove:1,pointerout:1},qv=p(Xv,function(t){var e=t.replace("mouse","pointer");return jv[e]?e:t}),Uv={mousemove:function(t){t=pe(this.dom,t),this.trigger("mousemove",t)},mouseout:function(t){t=pe(this.dom,t);var e=t.toElement||t.relatedTarget;if(e!=this.dom)for(;e&&9!=e.nodeType;){if(e===this.dom)return;e=e.parentNode}this.trigger("mouseout",t)},touchstart:function(t){t=pe(this.dom,t),t.zrByTouch=!0,this._lastTouchMoment=new Date,An(this,t,"start"),Uv.mousemove.call(this,t),Uv.mousedown.call(this,t),Dn(this)},touchmove:function(t){t=pe(this.dom,t),t.zrByTouch=!0,An(this,t,"change"),Uv.mousemove.call(this,t),Dn(this)},touchend:function(t){t=pe(this.dom,t),t.zrByTouch=!0,An(this,t,"end"),Uv.mouseup.call(this,t),+new Date-this._lastTouchMoment=0||n&&h(n,o)<0)){var s=e.getShallow(o);null!=s&&(r[t[a][0]]=s)}}return r}},fm=dm([["lineWidth","width"],["stroke","color"],["opacity"],["shadowBlur"],["shadowOffsetX"],["shadowOffsetY"],["shadowColor"]]),pm={getLineStyle:function(t){var e=fm(this,t),i=this.getLineDash(e.lineWidth);return i&&(e.lineDash=i),e},getLineDash:function(t){null==t&&(t=1);var e=this.get("type"),i=Math.max(t,2),n=4*t;return"solid"===e||null==e?null:"dashed"===e?[n,n]:[i,i]}},gm=dm([["fill","color"],["shadowBlur"],["shadowOffsetX"],["shadowOffsetY"],["opacity"],["shadowColor"]]),vm={getAreaStyle:function(t,e){return gm(this,t,e)}},mm=Math.pow,ym=Math.sqrt,xm=1e-8,_m=1e-4,wm=ym(3),bm=1/3,Sm=W(),Mm=W(),Im=W(),Tm=Math.min,Cm=Math.max,Am=Math.sin,Dm=Math.cos,km=2*Math.PI,Pm=W(),Lm=W(),Om=W(),zm=[],Em=[],Rm={M:1,L:2,C:3,Q:4,A:5,Z:6,R:7},Bm=[],Nm=[],Fm=[],Vm=[],Wm=Math.min,Gm=Math.max,Hm=Math.cos,Zm=Math.sin,Xm=Math.sqrt,Ym=Math.abs,jm="undefined"!=typeof Float32Array,qm=function(t){this._saveData=!t,this._saveData&&(this.data=[]),this._ctx=null};qm.prototype={constructor:qm,_xi:0,_yi:0,_x0:0,_y0:0,_ux:0,_uy:0,_len:0,_lineDash:null,_dashOffset:0,_dashIdx:0,_dashSum:0,setScale:function(t,e){this._ux=Ym(1/tv/t)||0,this._uy=Ym(1/tv/e)||0},getContext:function(){return this._ctx},beginPath:function(t){return this._ctx=t,t&&t.beginPath(),t&&(this.dpr=t.dpr),this._saveData&&(this._len=0),this._lineDash&&(this._lineDash=null,this._dashOffset=0),this},moveTo:function(t,e){return this.addData(Rm.M,t,e),this._ctx&&this._ctx.moveTo(t,e),this._x0=t,this._y0=e,this._xi=t,this._yi=e,this},lineTo:function(t,e){var i=Ym(t-this._xi)>this._ux||Ym(e-this._yi)>this._uy||this._len<5;return this.addData(Rm.L,t,e),this._ctx&&i&&(this._needsDash()?this._dashedLineTo(t,e):this._ctx.lineTo(t,e)),i&&(this._xi=t,this._yi=e),this},bezierCurveTo:function(t,e,i,n,r,a){return this.addData(Rm.C,t,e,i,n,r,a),this._ctx&&(this._needsDash()?this._dashedBezierTo(t,e,i,n,r,a):this._ctx.bezierCurveTo(t,e,i,n,r,a)),this._xi=r,this._yi=a,this},quadraticCurveTo:function(t,e,i,n){return this.addData(Rm.Q,t,e,i,n),this._ctx&&(this._needsDash()?this._dashedQuadraticTo(t,e,i,n):this._ctx.quadraticCurveTo(t,e,i,n)),this._xi=i,this._yi=n,this},arc:function(t,e,i,n,r,a){return this.addData(Rm.A,t,e,i,i,n,r-n,0,a?0:1),this._ctx&&this._ctx.arc(t,e,i,n,r,a),this._xi=Hm(r)*i+t,this._yi=Zm(r)*i+e,this},arcTo:function(t,e,i,n,r){return this._ctx&&this._ctx.arcTo(t,e,i,n,r),this},rect:function(t,e,i,n){return this._ctx&&this._ctx.rect(t,e,i,n),this.addData(Rm.R,t,e,i,n),this},closePath:function(){this.addData(Rm.Z);var t=this._ctx,e=this._x0,i=this._y0;return t&&(this._needsDash()&&this._dashedLineTo(e,i),t.closePath()),this._xi=e,this._yi=i,this},fill:function(t){t&&t.fill(),this.toStatic()},stroke:function(t){t&&t.stroke(),this.toStatic()},setLineDash:function(t){if(t instanceof Array){this._lineDash=t,this._dashIdx=0;for(var e=0,i=0;ii;i++)this.data[i]=t[i];this._len=e},appendPath:function(t){t instanceof Array||(t=[t]);for(var e=t.length,i=0,n=this._len,r=0;e>r;r++)i+=t[r].len();jm&&this.data instanceof Float32Array&&(this.data=new Float32Array(n+i));for(var r=0;e>r;r++)for(var a=t[r].data,o=0;oe.length&&(this._expandData(),e=this.data);for(var i=0;ia&&(a=r+a),a%=r,f-=a*u,p-=a*c;u>0&&t>=f||0>u&&f>=t||0==u&&(c>0&&e>=p||0>c&&p>=e);)n=this._dashIdx,i=o[n],f+=u*i,p+=c*i,this._dashIdx=(n+1)%g,u>0&&l>f||0>u&&f>l||c>0&&h>p||0>c&&p>h||s[n%2?"moveTo":"lineTo"](u>=0?Wm(f,t):Gm(f,t),c>=0?Wm(p,e):Gm(p,e));u=f-t,c=p-e,this._dashOffset=-Xm(u*u+c*c)},_dashedBezierTo:function(t,e,i,n,r,a){var o,s,l,h,u,c=this._dashSum,d=this._dashOffset,f=this._lineDash,p=this._ctx,g=this._xi,v=this._yi,m=lr,y=0,x=this._dashIdx,_=f.length,w=0;for(0>d&&(d=c+d),d%=c,o=0;1>o;o+=.1)s=m(g,t,i,r,o+.1)-m(g,t,i,r,o),l=m(v,e,n,a,o+.1)-m(v,e,n,a,o),y+=Xm(s*s+l*l);for(;_>x&&(w+=f[x],!(w>d));x++);for(o=(w-d)/y;1>=o;)h=m(g,t,i,r,o),u=m(v,e,n,a,o),x%2?p.moveTo(h,u):p.lineTo(h,u),o+=f[x]/y,x=(x+1)%_;x%2!==0&&p.lineTo(r,a),s=r-h,l=a-u,this._dashOffset=-Xm(s*s+l*l)},_dashedQuadraticTo:function(t,e,i,n){var r=i,a=n;i=(i+2*t)/3,n=(n+2*e)/3,t=(this._xi+2*t)/3,e=(this._yi+2*e)/3,this._dashedBezierTo(t,e,i,n,r,a)},toStatic:function(){var t=this.data;t instanceof Array&&(t.length=this._len,jm&&(this.data=new Float32Array(t)))},getBoundingRect:function(){Bm[0]=Bm[1]=Fm[0]=Fm[1]=Number.MAX_VALUE,Nm[0]=Nm[1]=Vm[0]=Vm[1]=-Number.MAX_VALUE;for(var t=this.data,e=0,i=0,n=0,r=0,a=0;ac;){var d=s[c++];switch(1==c&&(n=s[c],r=s[c+1],e=n,i=r),d){case Rm.M:e=n=s[c++],i=r=s[c++],t.moveTo(n,r);break;case Rm.L:a=s[c++],o=s[c++],(Ym(a-n)>l||Ym(o-r)>h||c===u-1)&&(t.lineTo(a,o),n=a,r=o);break;case Rm.C:t.bezierCurveTo(s[c++],s[c++],s[c++],s[c++],s[c++],s[c++]),n=s[c-2],r=s[c-1];break;case Rm.Q:t.quadraticCurveTo(s[c++],s[c++],s[c++],s[c++]),n=s[c-2],r=s[c-1];break;case Rm.A:var f=s[c++],p=s[c++],g=s[c++],v=s[c++],m=s[c++],y=s[c++],x=s[c++],_=s[c++],w=g>v?g:v,b=g>v?1:g/v,S=g>v?v/g:1,M=Math.abs(g-v)>.001,I=m+y;M?(t.translate(f,p),t.rotate(x),t.scale(b,S),t.arc(0,0,w,m,I,1-_),t.scale(1/b,1/S),t.rotate(-x),t.translate(-f,-p)):t.arc(f,p,w,m,I,1-_),1==c&&(e=Hm(m)*g+f,i=Zm(m)*v+p),n=Hm(I)*g+f,r=Zm(I)*v+p;break;case Rm.R:e=n=s[c],i=r=s[c+1],t.rect(s[c++],s[c++],s[c++],s[c++]);break;case Rm.Z:t.closePath(),n=e,r=i}}}},qm.CMD=Rm;var Um=2*Math.PI,$m=2*Math.PI,Km=qm.CMD,Qm=2*Math.PI,Jm=1e-4,ty=[-1,-1,-1],ey=[-1,-1],iy=xv.prototype.getCanvasPattern,ny=Math.abs,ry=new qm(!0);Fr.prototype={constructor:Fr,type:"path",__dirtyPath:!0,strokeContainThreshold:5,brush:function(t,e){var i=this.style,n=this.path||ry,r=i.hasStroke(),a=i.hasFill(),o=i.fill,s=i.stroke,l=a&&!!o.colorStops,h=r&&!!s.colorStops,u=a&&!!o.image,c=r&&!!s.image;if(i.bind(t,this,e),this.setTransform(t),this.__dirty){var d;l&&(d=d||this.getBoundingRect(),this._fillGradient=i.getGradient(t,o,d)),h&&(d=d||this.getBoundingRect(),this._strokeGradient=i.getGradient(t,s,d))}l?t.fillStyle=this._fillGradient:u&&(t.fillStyle=iy.call(o,t)),h?t.strokeStyle=this._strokeGradient:c&&(t.strokeStyle=iy.call(s,t));var f=i.lineDash,p=i.lineDashOffset,g=!!t.setLineDash,v=this.getGlobalScale();if(n.setScale(v[0],v[1]),this.__dirtyPath||f&&!g&&r?(n.beginPath(t),f&&!g&&(n.setLineDash(f),n.setLineDashOffset(p)),this.buildPath(n,this.shape,!1),this.path&&(this.__dirtyPath=!1)):(t.beginPath(),this.path.rebuildPath(t)),a)if(null!=i.fillOpacity){var m=t.globalAlpha;t.globalAlpha=i.fillOpacity*i.opacity,n.fill(t),t.globalAlpha=m}else n.fill(t);if(f&&g&&(t.setLineDash(f),t.lineDashOffset=p),r)if(null!=i.strokeOpacity){var m=t.globalAlpha;t.globalAlpha=i.strokeOpacity*i.opacity,n.stroke(t),t.globalAlpha=m}else n.stroke(t);f&&g&&t.setLineDash([]),null!=i.text&&(this.restoreTransform(t),this.drawRectText(t,this.getBoundingRect()))},buildPath:function(){},createPathProxy:function(){this.path=new qm},getBoundingRect:function(){var t=this._rect,e=this.style,i=!t;if(i){var n=this.path;n||(n=this.path=new qm),this.__dirtyPath&&(n.beginPath(),this.buildPath(n,this.shape,!1)),t=n.getBoundingRect()}if(this._rect=t,e.hasStroke()){var r=this._rectWithStroke||(this._rectWithStroke=t.clone());if(this.__dirty||i){r.copy(t);var a=e.lineWidth,o=e.strokeNoScale?this.getLineScale():1;e.hasFill()||(a=Math.max(a,this.strokeContainThreshold||4)),o>1e-10&&(r.width+=a/o,r.height+=a/o,r.x-=a/o/2,r.y-=a/o/2)}return r}return t},contain:function(t,e){var i=this.transformCoordToLocal(t,e),n=this.getBoundingRect(),r=this.style;if(t=i[0],e=i[1],n.contain(t,e)){var a=this.path.data;if(r.hasStroke()){var o=r.lineWidth,s=r.strokeNoScale?this.getLineScale():1;if(s>1e-10&&(r.hasFill()||(o=Math.max(o,this.strokeContainThreshold)),Nr(a,o/s,t,e)))return!0}if(r.hasFill())return Br(a,t,e)}return!1},dirty:function(t){null==t&&(t=!0),t&&(this.__dirtyPath=t,this._rect=null),this.__dirty=this.__dirtyText=!0,this.__zr&&this.__zr.refresh(),this.__clipTarget&&this.__clipTarget.dirty()},animateShape:function(t){return this.animate("shape",t)},attrKV:function(t,e){"shape"===t?(this.setShape(e),this.__dirtyPath=!0,this._rect=null):mn.prototype.attrKV.call(this,t,e)},setShape:function(t,e){var i=this.shape;if(i){if(S(t))for(var n in t)t.hasOwnProperty(n)&&(i[n]=t[n]);else i[t]=e;this.dirty(!0)}return this},getLineScale:function(){var t=this.transform;return t&&ny(t[0]-1)>1e-10&&ny(t[3]-1)>1e-10?Math.sqrt(ny(t[0]*t[3]-t[2]*t[1])):1}},Fr.extend=function(t){var e=function(e){Fr.call(this,e),t.style&&this.style.extendFrom(t.style,!1);var i=t.shape;if(i){this.shape=this.shape||{};var n=this.shape;for(var r in i)!n.hasOwnProperty(r)&&i.hasOwnProperty(r)&&(n[r]=i[r])}t.init&&t.init.call(this,e)};u(e,Fr);for(var i in t)"style"!==i&&"shape"!==i&&(e.prototype[i]=t[i]);return e},u(Fr,mn);var ay=qm.CMD,oy=[[],[],[]],sy=Math.sqrt,ly=Math.atan2,hy=function(t,e){var i,n,r,a,o,s,l=t.data,h=ay.M,u=ay.C,c=ay.L,d=ay.R,f=ay.A,p=ay.Q;for(r=0,a=0;ro;o++){var s=oy[o];s[0]=l[r++],s[1]=l[r++],ae(s,s,e),l[a++]=s[0],l[a++]=s[1]}}},uy=Math.sqrt,cy=Math.sin,dy=Math.cos,fy=Math.PI,py=function(t){return Math.sqrt(t[0]*t[0]+t[1]*t[1])},gy=function(t,e){return(t[0]*e[0]+t[1]*e[1])/(py(t)*py(e))},vy=function(t,e){return(t[0]*e[1]=11?function(){var e,i=this.__clipPaths,n=this.style;if(i)for(var r=0;ra;a++)r+=ee(t[a-1],t[a]);var o=r/2;o=i>o?i:o;for(var a=0;o>a;a++){var s,l,h,u=a/(o-1)*(e?i:i-1),c=Math.floor(u),d=u-c,f=t[c%i];e?(s=t[(c-1+i)%i],l=t[(c+1)%i],h=t[(c+2)%i]):(s=t[0===c?c:c-1],l=t[c>i-2?i-1:c+1],h=t[c>i-3?i-1:c+2]);var p=d*d,g=d*p;n.push([Yr(s[0],f[0],l[0],h[0],d,p,g),Yr(s[1],f[1],l[1],h[1],d,p,g)])}return n},Ty=function(t,e,i,n){var r,a,o,s,l=[],h=[],u=[],c=[];if(n){o=[1/0,1/0],s=[-1/0,-1/0];for(var d=0,f=t.length;f>d;d++)oe(o,o,t[d]),se(s,s,t[d]);oe(o,o,n[0]),se(s,s,n[1])}for(var d=0,f=t.length;f>d;d++){var p=t[d];if(i)r=t[d?d-1:f-1],a=t[(d+1)%f];else{if(0===d||d===f-1){l.push(H(t[d]));continue}r=t[d-1],a=t[d+1]}j(h,a,r),J(h,h,e);var g=ee(p,r),v=ee(p,a),m=g+v;0!==m&&(g/=m,v/=m),J(u,h,-g),J(c,h,v);var y=X([],p,u),x=X([],p,c);n&&(se(y,y,o),oe(y,y,s),se(x,x,o),oe(x,x,s)),l.push(y),l.push(x)}return i&&l.push(l.shift()),l},Cy=Fr.extend({type:"polygon",shape:{points:null,smooth:!1,smoothConstraint:null},buildPath:function(t,e){jr(t,e,!0)}}),Ay=Fr.extend({type:"polyline",shape:{points:null,smooth:!1,smoothConstraint:null},style:{stroke:"#000",fill:null},buildPath:function(t,e){jr(t,e,!1)}}),Dy=Fr.extend({type:"rect",shape:{r:0,x:0,y:0,width:0,height:0},buildPath:function(t,e){var i=e.x,n=e.y,r=e.width,a=e.height;e.r?Ki(t,e):t.rect(i,n,r,a),t.closePath()}}),ky=Fr.extend({type:"line",shape:{x1:0,y1:0,x2:0,y2:0,percent:1},style:{stroke:"#000",fill:null},buildPath:function(t,e){var i=e.x1,n=e.y1,r=e.x2,a=e.y2,o=e.percent;0!==o&&(t.moveTo(i,n),1>o&&(r=i*(1-o)+r*o,a=n*(1-o)+a*o),t.lineTo(r,a))},pointAt:function(t){var e=this.shape;return[e.x1*(1-t)+e.x2*t,e.y1*(1-t)+e.y2*t]}}),Py=[],Ly=Fr.extend({type:"bezier-curve",shape:{x1:0,y1:0,x2:0,y2:0,cpx1:0,cpy1:0,percent:1},style:{stroke:"#000",fill:null},buildPath:function(t,e){var i=e.x1,n=e.y1,r=e.x2,a=e.y2,o=e.cpx1,s=e.cpy1,l=e.cpx2,h=e.cpy2,u=e.percent;0!==u&&(t.moveTo(i,n),null==l||null==h?(1>u&&(yr(i,o,r,u,Py),o=Py[1],r=Py[2],yr(n,s,a,u,Py),s=Py[1],a=Py[2]),t.quadraticCurveTo(o,s,r,a)):(1>u&&(dr(i,o,l,r,u,Py),o=Py[1],l=Py[2],r=Py[3],dr(n,s,h,a,u,Py),s=Py[1],h=Py[2],a=Py[3]),t.bezierCurveTo(o,s,l,h,r,a)))},pointAt:function(t){return qr(this.shape,t,!1)},tangentAt:function(t){var e=qr(this.shape,t,!0);return te(e,e)}}),Oy=Fr.extend({type:"arc",shape:{cx:0,cy:0,r:0,startAngle:0,endAngle:2*Math.PI,clockwise:!0},style:{stroke:"#000",fill:null},buildPath:function(t,e){var i=e.cx,n=e.cy,r=Math.max(e.r,0),a=e.startAngle,o=e.endAngle,s=e.clockwise,l=Math.cos(a),h=Math.sin(a);t.moveTo(l*r+i,h*r+n),t.arc(i,n,r,a,o,!s)}}),zy=Fr.extend({type:"compound",shape:{paths:null},_updatePathDirty:function(){for(var t=this.__dirtyPath,e=this.shape.paths,i=0;i"'])/g,ox={"&":"&","<":"<",">":">",'"':""","'":"'"},sx=["a","b","c","d","e","f","g"],lx=function(t,e){return"{"+t+(null==e?"":e)+"}"},hx=Wi,ux=Ei,cx=(Object.freeze||Object)({addCommas:co,toCamelCase:fo,normalizeCssArray:rx,encodeHTML:po,formatTpl:go,formatTplSimple:vo,getTooltipMarker:mo,formatTime:xo,capitalFirst:_o,truncateText:hx,getTextRect:ux}),dx=f,fx=["left","right","top","bottom","width","height"],px=[["width","left","right"],["height","top","bottom"]],gx=wo,vx=(x(wo,"vertical"),x(wo,"horizontal"),{getBoxLayoutParams:function(){return{left:this.get("left"),top:this.get("top"),right:this.get("right"),bottom:this.get("bottom"),width:this.get("width"),height:this.get("height")}}}),mx=jn(),yx=Wa.extend({type:"component",id:"",name:"",mainType:"",subType:"",componentIndex:0,defaultOption:null,ecModel:null,dependentModels:[],uid:null,layoutMode:null,$constructor:function(t,e,i,n){Wa.call(this,t,e,i,n),this.uid=Za("ec_cpt_model")},init:function(t,e,i){this.mergeDefaultAndTheme(t,i)},mergeDefaultAndTheme:function(t,e){var i=this.layoutMode,n=i?Mo(t):{},a=e.getTheme();r(t,a.get(this.mainType)),r(t,this.getDefaultOption()),i&&So(t,n,i)},mergeOption:function(t){r(this.option,t,!0);var e=this.layoutMode;e&&So(this.option,t,e)},optionUpdated:function(){},getDefaultOption:function(){var t=mx(this);if(!t.defaultOption){for(var e=[],i=this.constructor;i;){var n=i.prototype.defaultOption;n&&e.push(n),i=i.superClass}for(var a={},o=e.length-1;o>=0;o--)a=r(a,e[o],!0);t.defaultOption=a}return t.defaultOption},getReferringComponents:function(t){return this.ecModel.queryComponents({mainType:t,index:this.get(t+"Index",!0),id:this.get(t+"Id",!0)})}});ar(yx,{registerWhenExtend:!0}),Xa(yx),Ya(yx,To),c(yx,vx);var xx="";"undefined"!=typeof navigator&&(xx=navigator.platform||"");var _x={color:["#c23531","#2f4554","#61a0a8","#d48265","#91c7ae","#749f83","#ca8622","#bda29a","#6e7074","#546570","#c4ccd3"],gradientColor:["#f6efa6","#d88273","#bf444c"],textStyle:{fontFamily:xx.match(/^Win/)?"Microsoft YaHei":"sans-serif",fontSize:12,fontStyle:"normal",fontWeight:"normal"},blendMode:null,animation:"auto",animationDuration:1e3,animationDurationUpdate:300,animationEasing:"exponentialOut",animationEasingUpdate:"cubicOut",animationThreshold:2e3,progressiveThreshold:3e3,progressive:400,hoverLayerThreshold:3e3,useUTC:!1},bx=jn(),Sx={clearColorPalette:function(){bx(this).colorIdx=0,bx(this).colorNameMap={}},getColorFromPalette:function(t,e,i){e=e||this;var n=bx(e),r=n.colorIdx||0,a=n.colorNameMap=n.colorNameMap||{};if(a.hasOwnProperty(t))return a[t];var o=Nn(this.get("color",!0)),s=this.get("colorLayer",!0),l=null!=i&&s?Co(s,i):o;if(l=l||o,l&&l.length){var h=l[r];return t&&(a[t]=h),n.colorIdx=(r+1)%l.length,h}}},Mx={cartesian2d:function(t,e,i,n){var r=t.getReferringComponents("xAxis")[0],a=t.getReferringComponents("yAxis")[0];e.coordSysDims=["x","y"],i.set("x",r),i.set("y",a),Do(r)&&(n.set("x",r),e.firstCategoryDimIndex=0),Do(a)&&(n.set("y",a),e.firstCategoryDimIndex=1)},singleAxis:function(t,e,i,n){var r=t.getReferringComponents("singleAxis")[0];e.coordSysDims=["single"],i.set("single",r),Do(r)&&(n.set("single",r),e.firstCategoryDimIndex=0)},polar:function(t,e,i,n){var r=t.getReferringComponents("polar")[0],a=r.findAxisModel("radiusAxis"),o=r.findAxisModel("angleAxis");e.coordSysDims=["radius","angle"],i.set("radius",a),i.set("angle",o),Do(a)&&(n.set("radius",a),e.firstCategoryDimIndex=0),Do(o)&&(n.set("angle",o),e.firstCategoryDimIndex=1)},geo:function(t,e){e.coordSysDims=["lng","lat"]},parallel:function(t,e,i,n){var r=t.ecModel,a=r.getComponent("parallel",t.get("parallelIndex")),o=e.coordSysDims=a.dimensions.slice();f(a.parallelAxisIndex,function(t,a){var s=r.getComponent("parallelAxis",t),l=o[a];i.set(l,s),Do(s)&&null==e.firstCategoryDimIndex&&(n.set(l,s),e.firstCategoryDimIndex=a)})}},Ix="original",Tx="arrayRows",Cx="objectRows",Ax="keyedColumns",Dx="unknown",kx="typedArray",Px="column",Lx="row";ko.seriesDataToSource=function(t){return new ko({data:t,sourceFormat:I(t)?kx:Ix,fromDataset:!1})},ir(ko);var Ox=jn(),zx="\x00_ec_inner",Ex=Wa.extend({init:function(t,e,i,n){i=i||{},this.option=null,this._theme=new Wa(i),this._optionManager=n},setOption:function(t,e){O(!(zx in t),"please use chart.getOption()"),this._optionManager.setOption(t,e),this.resetOption(null)},resetOption:function(t){var e=!1,i=this._optionManager;if(!t||"recreate"===t){var n=i.mountOption("recreate"===t);this.option&&"recreate"!==t?(this.restoreData(),this.mergeOption(n)):Xo.call(this,n),e=!0}if(("timeline"===t||"media"===t)&&this.restoreData(),!t||"recreate"===t||"timeline"===t){var r=i.getTimelineOption(this);r&&(this.mergeOption(r),e=!0)}if(!t||"recreate"===t||"media"===t){var a=i.getMediaOption(this,this._api);a.length&&f(a,function(t){this.mergeOption(t,e=!0)},this)}return e},mergeOption:function(t){function e(e,n){var r=Nn(t[e]),s=Gn(a.get(e),r);Hn(s),f(s,function(t){var i=t.option;S(i)&&(t.keyInfo.mainType=e,t.keyInfo.subType=jo(e,i,t.exist))});var l=Yo(a,n);i[e]=[],a.set(e,[]),f(s,function(t,n){var r=t.exist,s=t.option;if(O(S(s)||r,"Empty component definition"),s){var h=yx.getClass(e,t.keyInfo.subType,!0);if(r&&r instanceof h)r.name=t.keyInfo.name,r.mergeOption(s,this),r.optionUpdated(s,!1);else{var u=o({dependentModels:l,componentIndex:n},t.keyInfo);r=new h(s,this,this,u),o(r,u),r.init(s,this,this,u),r.optionUpdated(null,!0)}}else r.mergeOption({},this),r.optionUpdated({},!1);a.get(e)[n]=r,i[e][n]=r.option},this),"series"===e&&qo(this,a.get("series"))}var i=this.option,a=this._componentsMap,s=[];Oo(this),f(t,function(t,e){null!=t&&(yx.hasClass(e)?e&&s.push(e):i[e]=null==i[e]?n(t):r(i[e],t,!0))}),yx.topologicalTravel(s,yx.getAllClassMainTypes(),e,this),this._seriesIndicesMap=N(this._seriesIndices=this._seriesIndices||[])},getOption:function(){var t=n(this.option);return f(t,function(e,i){if(yx.hasClass(i)){for(var e=Nn(e),n=e.length-1;n>=0;n--)Xn(e[n])&&e.splice(n,1);t[i]=e}}),delete t[zx],t},getTheme:function(){return this._theme},getComponent:function(t,e){var i=this._componentsMap.get(t);return i?i[e||0]:void 0},queryComponents:function(t){var e=t.mainType;if(!e)return[];var i=t.index,n=t.id,r=t.name,a=this._componentsMap.get(e);if(!a||!a.length)return[];var o;if(null!=i)_(i)||(i=[i]),o=v(p(i,function(t){return a[t]}),function(t){return!!t});else if(null!=n){var s=_(n);o=v(a,function(t){return s&&h(n,t.id)>=0||!s&&t.id===n})}else if(null!=r){var l=_(r);o=v(a,function(t){return l&&h(r,t.name)>=0||!l&&t.name===r})}else o=a.slice();return Uo(o,t)},findComponents:function(t){function e(t){var e=r+"Index",i=r+"Id",n=r+"Name";return!t||null==t[e]&&null==t[i]&&null==t[n]?null:{mainType:r,index:t[e],id:t[i],name:t[n]}}function i(e){return t.filter?v(e,t.filter):e}var n=t.query,r=t.mainType,a=e(n),o=a?this.queryComponents(a):this._componentsMap.get(r);return i(Uo(o,t))},eachComponent:function(t,e,i){var n=this._componentsMap;if("function"==typeof t)i=e,e=t,n.each(function(t,n){f(t,function(t,r){e.call(i,n,t,r)})});else if(b(t))f(n.get(t),e,i);else if(S(t)){var r=this.findComponents(t);f(r,e,i)}},getSeriesByName:function(t){var e=this._componentsMap.get("series");return v(e,function(e){return e.name===t})},getSeriesByIndex:function(t){return this._componentsMap.get("series")[t]},getSeriesByType:function(t){var e=this._componentsMap.get("series");return v(e,function(e){return e.subType===t})},getSeries:function(){return this._componentsMap.get("series").slice()},getSeriesCount:function(){return this._componentsMap.get("series").length},eachSeries:function(t,e){f(this._seriesIndices,function(i){var n=this._componentsMap.get("series")[i];t.call(e,n,i)},this)},eachRawSeries:function(t,e){f(this._componentsMap.get("series"),t,e)},eachSeriesByType:function(t,e,i){f(this._seriesIndices,function(n){var r=this._componentsMap.get("series")[n];r.subType===t&&e.call(i,r,n)},this)},eachRawSeriesByType:function(t,e,i){return f(this.getSeriesByType(t),e,i)},isSeriesFiltered:function(t){return null==this._seriesIndicesMap.get(t.componentIndex)},getCurrentSeriesIndices:function(){return(this._seriesIndices||[]).slice()},filterSeries:function(t,e){var i=v(this._componentsMap.get("series"),t,e);qo(this,i)},restoreData:function(t){var e=this._componentsMap;qo(this,e.get("series"));var i=[];e.each(function(t,e){i.push(e)}),yx.topologicalTravel(i,yx.getAllClassMainTypes(),function(i){f(e.get(i),function(e){("series"!==i||!Ho(e,t))&&e.restoreData()})})}});c(Ex,Sx);var Rx=["getDom","getZr","getWidth","getHeight","getDevicePixelRatio","dispatchAction","isDisposed","on","off","getDataURL","getConnectedDataURL","getModel","getOption","getViewOfComponentModel","getViewOfSeriesModel"],Bx={};Ko.prototype={constructor:Ko,create:function(t,e){var i=[];f(Bx,function(n){var r=n.create(t,e);i=i.concat(r||[])}),this._coordinateSystems=i},update:function(t,e){f(this._coordinateSystems,function(i){i.update&&i.update(t,e)})},getCoordinateSystems:function(){return this._coordinateSystems.slice()}},Ko.register=function(t,e){Bx[t]=e},Ko.get=function(t){return Bx[t]};var Nx=f,Fx=n,Vx=p,Wx=r,Gx=/^(min|max)?(.+)$/;Qo.prototype={constructor:Qo,setOption:function(t,e){t&&f(Nn(t.series),function(t){t&&t.data&&I(t.data)&&E(t.data)}),t=Fx(t,!0);var i=this._optionBackup,n=Jo.call(this,t,e,!i);this._newBaseOption=n.baseOption,i?(ns(i.baseOption,n.baseOption),n.timelineOptions.length&&(i.timelineOptions=n.timelineOptions),n.mediaList.length&&(i.mediaList=n.mediaList),n.mediaDefault&&(i.mediaDefault=n.mediaDefault)):this._optionBackup=n},mountOption:function(t){var e=this._optionBackup;return this._timelineOptions=Vx(e.timelineOptions,Fx),this._mediaList=Vx(e.mediaList,Fx),this._mediaDefault=Fx(e.mediaDefault),this._currentMediaIndices=[],Fx(t?e.baseOption:this._newBaseOption)},getTimelineOption:function(t){var e,i=this._timelineOptions;if(i.length){var n=t.getComponent("timeline");n&&(e=Fx(i[n.getCurrentIndex()],!0))}return e},getMediaOption:function(){var t=this._api.getWidth(),e=this._api.getHeight(),i=this._mediaList,n=this._mediaDefault,r=[],a=[];if(!i.length&&!n)return a;for(var o=0,s=i.length;s>o;o++)ts(i[o].query,t,e)&&r.push(o);return!r.length&&n&&(r=[-1]),r.length&&!is(r,this._currentMediaIndices)&&(a=Vx(r,function(t){return Fx(-1===t?n.option:i[t].option)})),this._currentMediaIndices=r,a}};var Hx=f,Zx=S,Xx=["areaStyle","lineStyle","nodeStyle","linkStyle","chordStyle","label","labelLine"],Yx=function(t,e){Hx(us(t.series),function(t){Zx(t)&&hs(t)});var i=["xAxis","yAxis","radiusAxis","angleAxis","singleAxis","parallelAxis","radar"];e&&i.push("valueAxis","categoryAxis","logAxis","timeAxis"),Hx(i,function(e){Hx(us(t[e]),function(t){t&&(ss(t,"axisLabel"),ss(t.axisPointer,"label"))})}),Hx(us(t.parallel),function(t){var e=t&&t.parallelAxisDefault;ss(e,"axisLabel"),ss(e&&e.axisPointer,"label")}),Hx(us(t.calendar),function(t){as(t,"itemStyle"),ss(t,"dayLabel"),ss(t,"monthLabel"),ss(t,"yearLabel")}),Hx(us(t.radar),function(t){ss(t,"name")}),Hx(us(t.geo),function(t){Zx(t)&&(ls(t),Hx(us(t.regions),function(t){ls(t)}))}),Hx(us(t.timeline),function(t){ls(t),as(t,"label"),as(t,"itemStyle"),as(t,"controlStyle",!0);var e=t.data;_(e)&&f(e,function(t){S(t)&&(as(t,"label"),as(t,"itemStyle"))})}),Hx(us(t.toolbox),function(t){as(t,"iconStyle"),Hx(t.feature,function(t){as(t,"iconStyle")})}),ss(cs(t.axisPointer),"label"),ss(cs(t.tooltip).axisPointer,"label")},jx=[["x","left"],["y","top"],["x2","right"],["y2","bottom"]],qx=["grid","geo","parallel","legend","toolbox","title","visualMap","dataZoom","timeline"],Ux=function(t,e){Yx(t,e),t.series=Nn(t.series),f(t.series,function(t){if(S(t)){var e=t.type;if(("pie"===e||"gauge"===e)&&null!=t.clockWise&&(t.clockwise=t.clockWise),"gauge"===e){var i=ds(t,"pointer.color");null!=i&&fs(t,"itemStyle.normal.color",i)}ps(t)}}),t.dataRange&&(t.visualMap=t.dataRange),f(qx,function(e){var i=t[e];i&&(_(i)||(i=[i]),f(i,function(t){ps(t)}))})},$x=function(t){var e=N();t.eachSeries(function(t){var i=t.get("stack");if(i){var n=e.get(i)||e.set(i,[]),r=t.getData(),a={stackResultDimension:r.getCalculationInfo("stackResultDimension"),stackedOverDimension:r.getCalculationInfo("stackedOverDimension"),stackedDimension:r.getCalculationInfo("stackedDimension"),stackedByDimension:r.getCalculationInfo("stackedByDimension"),isStackedByIndex:r.getCalculationInfo("isStackedByIndex"),data:r,seriesModel:t};if(!a.stackedDimension||!a.isStackedByIndex&&!a.stackedByDimension)return;n.length&&r.setCalculationInfo("stackedOnSeries",n[n.length-1].seriesModel),n.push(a)}}),e.each(gs)},Kx=vs.prototype;Kx.pure=!1,Kx.persistent=!0,Kx.getSource=function(){return this._source};var Qx={arrayRows_column:{pure:!0,count:function(){return Math.max(0,this._data.length-this._source.startIndex)},getItem:function(t){return this._data[t+this._source.startIndex]},appendData:xs},arrayRows_row:{pure:!0,count:function(){var t=this._data[0];return t?Math.max(0,t.length-this._source.startIndex):0},getItem:function(t){t+=this._source.startIndex;for(var e=[],i=this._data,n=0;n=1)&&(t=1),t}var i=this._upstream,n=t&&t.skip;if(this._dirty&&i){var r=this.context;r.data=r.outputData=i.context.outputData}this.__pipeline&&(this.__pipeline.currentTask=this);var a;this._plan&&!n&&(a=this._plan(this.context));var o=e(this._modBy),s=this._modDataCount||0,l=e(t&&t.modBy),h=t&&t.modDataCount||0;(o!==l||s!==h)&&(a="reset");var u;(this._dirty||"reset"===a)&&(this._dirty=!1,u=As(this,n)),this._modBy=l,this._modDataCount=h;var c=t&&t.step;if(this._dueEnd=i?i._outputDueEnd:this._count?this._count(this.context):1/0,this._progress){var d=this._dueIndex,f=Math.min(null!=c?this._dueIndex+c:1/0,this._dueEnd);if(!n&&(u||f>d)){var p=this._progress;if(_(p))for(var g=0;gn?n++:null}function e(){var t=n%o*r+Math.ceil(n/o),e=n>=i?null:a>t?t:n;return n++,e}var i,n,r,a,o,s={reset:function(l,h,u,c){n=l,i=h,r=u,a=c,o=Math.ceil(a/r),s.next=r>1&&a>0?e:t}};return s}();n_.dirty=function(){this._dirty=!0,this._onDirty&&this._onDirty(this.context)},n_.unfinished=function(){return this._progress&&this._dueIndex":"",v=p+s.join(p||", ");return{renderMode:n,content:v,style:h}}function a(t){return{renderMode:n,content:po(co(t)),style:h}}var o=this;n=n||"html";var s="html"===n?"
    ":"\n",l="richText"===n,h={},u=0,c=this.getData(),d=c.mapDimension("defaultedTooltip",!0),p=d.length,v=this.getRawValue(t),m=_(v),y=c.getItemVisual(t,"color");S(y)&&y.colorStops&&(y=(y.colorStops[0]||{}).color),y=y||"transparent";var x=p>1||m&&!p?r(v):a(p?Ss(c,t,d[0]):m?v[0]:v),w=x.content,b=o.seriesIndex+"at"+u,M=mo({color:y,type:"item",renderMode:n,markerId:b});h[b]=y,++u;var I=c.getName(t),T=this.name;Zn(this)||(T=""),T=T?po(T)+(e?": ":s):"";var C="string"==typeof M?M:M.content,A=e?C+T+w:T+C+(I?po(I)+": "+w:w);return{html:A,markers:h}},isAnimationEnabled:function(){if(tg.node)return!1;var t=this.getShallow("animation");return t&&this.getData().count()>this.getShallow("animationThreshold")&&(t=!1),t},restoreData:function(){this.dataTask.dirty()},getColorFromPalette:function(t,e,i){var n=this.ecModel,r=Sx.getColorFromPalette.call(this,t,e,i);return r||(r=n.getColorFromPalette(t,e,i)),r},coordDimToDataDim:function(t){return this.getRawData().mapDimension(t,!0)},getProgressive:function(){return this.get("progressive")},getProgressiveThreshold:function(){return this.get("progressiveThreshold")},getAxisTooltipData:null,getTooltipPosition:null,pipeTask:null,preventIncremental:null,pipelineContext:null});c(o_,i_),c(o_,Sx);var s_=function(){this.group=new lv,this.uid=Za("viewComponent")};s_.prototype={constructor:s_,init:function(){},render:function(){},dispose:function(){},filterForExposedEvent:null};var l_=s_.prototype;l_.updateView=l_.updateLayout=l_.updateVisual=function(){},er(s_),ar(s_,{registerWhenExtend:!0});var h_=function(){var t=jn();return function(e){var i=t(e),n=e.pipelineContext,r=i.large,a=i.progressiveRender,o=i.large=n.large,s=i.progressiveRender=n.progressiveRender;return!!(r^o||a^s)&&"reset"}},u_=jn(),c_=h_();Bs.prototype={type:"chart",init:function(){},render:function(){},highlight:function(t,e,i,n){Fs(t.getData(),n,"emphasis")},downplay:function(t,e,i,n){Fs(t.getData(),n,"normal")},remove:function(){this.group.removeAll()},dispose:function(){},incrementalPrepareRender:null,incrementalRender:null,updateTransform:null,filterForExposedEvent:null};var d_=Bs.prototype;d_.updateView=d_.updateLayout=d_.updateVisual=function(t,e,i,n){this.render(t,e,i,n)},er(Bs,["dispose"]),ar(Bs,{registerWhenExtend:!0}),Bs.markUpdateMethod=function(t,e){u_(t).updateMethod=e +};var f_={incrementalPrepareRender:{progress:function(t,e){e.view.incrementalRender(t,e.model,e.ecModel,e.api,e.payload)}},render:{forceFirstProgress:!0,progress:function(t,e){e.view.render(e.model,e.ecModel,e.api,e.payload)}}},p_="\x00__throttleOriginMethod",g_="\x00__throttleRate",v_="\x00__throttleType",m_={createOnAllSeries:!0,performRawSeries:!0,reset:function(t,e){var i=t.getData(),n=(t.visualColorAccessPath||"itemStyle.color").split("."),r=t.get(n)||t.getColorFromPalette(t.name,null,e.getSeriesCount());if(i.setVisual("color",r),!e.isSeriesFiltered(t)){"function"!=typeof r||r instanceof Ey||i.each(function(e){i.setItemVisual(e,"color",r(t.getDataParams(e)))});var a=function(t,e){var i=t.getItemModel(e),r=i.get(n,!0);null!=r&&t.setItemVisual(e,"color",r)};return{dataEach:i.hasItemOption?a:null}}}},y_={toolbox:{brush:{title:{rect:"矩形选择",polygon:"圈选",lineX:"横向选择",lineY:"纵向选择",keep:"保持选择",clear:"清除选择"}},dataView:{title:"数据视图",lang:["数据视图","关闭","刷新"]},dataZoom:{title:{zoom:"区域缩放",back:"区域缩放还原"}},magicType:{title:{line:"切换为折线图",bar:"切换为柱状图",stack:"切换为堆叠",tiled:"切换为平铺"}},restore:{title:"还原"},saveAsImage:{title:"保存为图片",lang:["右键另存为图片"]}},series:{typeNames:{pie:"饼图",bar:"柱状图",line:"折线图",scatter:"散点图",effectScatter:"涟漪散点图",radar:"雷达图",tree:"树图",treemap:"矩形树图",boxplot:"箱型图",candlestick:"K线图",k:"K线图",heatmap:"热力图",map:"地图",parallel:"平行坐标图",lines:"线图",graph:"关系图",sankey:"桑基图",funnel:"漏斗图",gauge:"仪表盘图",pictorialBar:"象形柱图",themeRiver:"主题河流图",sunburst:"旭日图"}},aria:{general:{withTitle:"这是一个关于“{title}”的图表。",withoutTitle:"这是一个图表,"},series:{single:{prefix:"",withName:"图表类型是{seriesType},表示{seriesName}。",withoutName:"图表类型是{seriesType}。"},multiple:{prefix:"它由{seriesCount}个图表系列组成。",withName:"第{seriesId}个系列是一个表示{seriesName}的{seriesType},",withoutName:"第{seriesId}个系列是一个{seriesType},",separator:{middle:";",end:"。"}}},data:{allData:"其数据是——",partialData:"其中,前{displayCnt}项是——",withName:"{name}的数据是{value}",withoutName:"{value}",separator:{middle:",",end:""}}}},x_=function(t,e){function i(t,e){if("string"!=typeof t)return t;var i=t;return f(e,function(t,e){i=i.replace(new RegExp("\\{\\s*"+e+"\\s*\\}","g"),t)}),i}function n(t){var e=o.get(t);if(null==e){for(var i=t.split("."),n=y_.aria,r=0;rs)){var d=r();l=d?i(n("general.withTitle"),{title:d}):n("general.withoutTitle");var p=[],g=s>1?"series.multiple.prefix":"series.single.prefix";l+=i(n(g),{seriesCount:s}),e.eachSeries(function(t,e){if(c>e){var r,o=t.get("name"),l="series."+(s>1?"multiple":"single")+".";r=n(o?l+"withName":l+"withoutName"),r=i(r,{seriesId:t.seriesIndex,seriesName:t.get("name"),seriesType:a(t.subType)});var u=t.getData();window.data=u,r+=u.count()>h?i(n("data.partialData"),{displayCnt:h}):n("data.allData");for(var d=[],f=0;ff){var g=u.getName(f),v=Ss(u,f);d.push(i(n(g?"data.withName":"data.withoutName"),{name:g,value:v}))}r+=d.join(n("data.separator.middle"))+n("data.separator.end"),p.push(r)}}),l+=p.join(n("series.multiple.separator.middle"))+n("series.multiple.separator.end"),t.setAttribute("aria-label",l)}}},__=Math.PI,w_=function(t,e){e=e||{},s(e,{text:"loading",color:"#c23531",textColor:"#000",maskColor:"rgba(255, 255, 255, 0.8)",zlevel:0});var i=new Dy({style:{fill:e.maskColor},zlevel:e.zlevel,z:1e4}),n=new Oy({shape:{startAngle:-__/2,endAngle:-__/2+.1,r:10},style:{stroke:e.color,lineCap:"round",lineWidth:5},zlevel:e.zlevel,z:10001}),r=new Dy({style:{fill:"none",text:e.text,textPosition:"right",textDistance:10,textFill:e.textColor},zlevel:e.zlevel,z:10001});n.animateShape(!0).when(1e3,{endAngle:3*__/2}).start("circularInOut"),n.animateShape(!0).when(1e3,{startAngle:3*__/2}).delay(300).start("circularInOut");var a=new lv;return a.add(n),a.add(r),a.add(i),a.resize=function(){var e=t.getWidth()/2,a=t.getHeight()/2;n.setShape({cx:e,cy:a});var o=n.shape.r;r.setShape({x:e-o,y:a-o,width:2*o,height:2*o}),i.setShape({x:0,y:0,width:t.getWidth(),height:t.getHeight()})},a.resize(),a},b_=Xs.prototype;b_.restoreData=function(t,e){t.restoreData(e),this._stageTaskMap.each(function(t){var e=t.overallTask;e&&e.dirty()})},b_.getPerformArgs=function(t,e){if(t.__pipeline){var i=this._pipelineMap.get(t.__pipeline.id),n=i.context,r=!e&&i.progressiveEnabled&&(!n||n.progressiveRender)&&t.__idxInPipeline>i.blockIndex,a=r?i.step:null,o=n&&n.modDataCount,s=null!=o?Math.ceil(o/a):null;return{step:a,modBy:s,modDataCount:o}}},b_.getPipeline=function(t){return this._pipelineMap.get(t)},b_.updateStreamModes=function(t,e){var i=this._pipelineMap.get(t.uid),n=t.getData(),r=n.count(),a=i.progressiveEnabled&&e.incrementalPrepareRender&&r>=i.threshold,o=t.get("large")&&r>=t.get("largeThreshold"),s="mod"===t.get("progressiveChunkMode")?r:null;t.pipelineContext=i.context={progressiveRender:a,modDataCount:s,large:o}},b_.restorePipelines=function(t){var e=this,i=e._pipelineMap=N();t.eachSeries(function(t){var n=t.getProgressive(),r=t.uid;i.set(r,{id:r,head:null,tail:null,threshold:t.getProgressiveThreshold(),progressiveEnabled:n&&!(t.preventIncremental&&t.preventIncremental()),blockIndex:-1,step:Math.round(n||700),count:0}),nl(e,t,t.dataTask)})},b_.prepareStageTasks=function(){var t=this._stageTaskMap,e=this.ecInstance.getModel(),i=this.api;f(this._allHandlers,function(n){var r=t.get(n.uid)||t.set(n.uid,[]);n.reset&&js(this,n,r,e,i),n.overallReset&&qs(this,n,r,e,i)},this)},b_.prepareView=function(t,e,i,n){var r=t.renderTask,a=r.context;a.model=e,a.ecModel=i,a.api=n,r.__block=!t.incrementalPrepareRender,nl(this,e,r)},b_.performDataProcessorTasks=function(t,e){Ys(this,this._dataProcessorHandlers,t,e,{block:!0})},b_.performVisualTasks=function(t,e,i){Ys(this,this._visualHandlers,t,e,i)},b_.performSeriesTasks=function(t){var e;t.eachSeries(function(t){e|=t.dataTask.perform()}),this.unfinished|=e},b_.plan=function(){this._pipelineMap.each(function(t){var e=t.tail;do{if(e.__block){t.blockIndex=e.__idxInPipeline;break}e=e.getUpstream()}while(e)})};var S_=b_.updatePayload=function(t,e){"remain"!==e&&(t.context.payload=e)},M_=el(0);Xs.wrapStageHandler=function(t,e){return w(t)&&(t={overallReset:t,seriesType:rl(t)}),t.uid=Za("stageHandler"),e&&(t.visualType=e),t};var I_,T_={},C_={};al(T_,Ex),al(C_,$o),T_.eachSeriesByType=T_.eachRawSeriesByType=function(t){I_=t},T_.eachComponent=function(t){"series"===t.mainType&&t.subType&&(I_=t.subType)};var A_=["#37A2DA","#32C5E9","#67E0E3","#9FE6B8","#FFDB5C","#ff9f7f","#fb7293","#E062AE","#E690D1","#e7bcf3","#9d96f5","#8378EA","#96BFFF"],D_={color:A_,colorLayer:[["#37A2DA","#ffd85c","#fd7b5f"],["#37A2DA","#67E0E3","#FFDB5C","#ff9f7f","#E062AE","#9d96f5"],["#37A2DA","#32C5E9","#9FE6B8","#FFDB5C","#ff9f7f","#fb7293","#e7bcf3","#8378EA","#96BFFF"],A_]},k_="#eee",P_=function(){return{axisLine:{lineStyle:{color:k_}},axisTick:{lineStyle:{color:k_}},axisLabel:{textStyle:{color:k_}},splitLine:{lineStyle:{type:"dashed",color:"#aaa"}},splitArea:{areaStyle:{color:k_}}}},L_=["#dd6b66","#759aa0","#e69d87","#8dc1a9","#ea7e53","#eedd78","#73a373","#73b9bc","#7289ab","#91ca8c","#f49f42"],O_={color:L_,backgroundColor:"#333",tooltip:{axisPointer:{lineStyle:{color:k_},crossStyle:{color:k_}}},legend:{textStyle:{color:k_}},textStyle:{color:k_},title:{textStyle:{color:k_}},toolbox:{iconStyle:{normal:{borderColor:k_}}},dataZoom:{textStyle:{color:k_}},visualMap:{textStyle:{color:k_}},timeline:{lineStyle:{color:k_},itemStyle:{normal:{color:L_[1]}},label:{normal:{textStyle:{color:k_}}},controlStyle:{normal:{color:k_,borderColor:k_}}},timeAxis:P_(),logAxis:P_(),valueAxis:P_(),categoryAxis:P_(),line:{symbol:"circle"},graph:{color:L_},gauge:{title:{textStyle:{color:k_}}},candlestick:{itemStyle:{normal:{color:"#FD1050",color0:"#0CF49B",borderColor:"#FD1050",borderColor0:"#0CF49B"}}}};O_.categoryAxis.splitLine.show=!1,yx.extend({type:"dataset",defaultOption:{seriesLayoutBy:Px,sourceHeader:null,dimensions:null,source:null},optionUpdated:function(){Po(this)}}),s_.extend({type:"dataset"});var z_=Fr.extend({type:"ellipse",shape:{cx:0,cy:0,rx:0,ry:0},buildPath:function(t,e){var i=.5522848,n=e.cx,r=e.cy,a=e.rx,o=e.ry,s=a*i,l=o*i;t.moveTo(n-a,r),t.bezierCurveTo(n-a,r-l,n-s,r-o,n,r-o),t.bezierCurveTo(n+s,r-o,n+a,r-l,n+a,r),t.bezierCurveTo(n+a,r+l,n+s,r+o,n,r+o),t.bezierCurveTo(n-s,r+o,n-a,r+l,n-a,r),t.closePath()}}),E_=/[\s,]+/;sl.prototype.parse=function(t,e){e=e||{};var i=ol(t);if(!i)throw new Error("Illegal svg");var n=new lv;this._root=n;var r=i.getAttribute("viewBox")||"",a=parseFloat(i.getAttribute("width")||e.width),o=parseFloat(i.getAttribute("height")||e.height);isNaN(a)&&(a=null),isNaN(o)&&(o=null),cl(i,n,null,!0);for(var s=i.firstChild;s;)this._parseNode(s,n),s=s.nextSibling;var l,h;if(r){var u=z(r).split(E_);u.length>=4&&(l={x:parseFloat(u[0]||0),y:parseFloat(u[1]||0),width:parseFloat(u[2]),height:parseFloat(u[3])})}if(l&&null!=a&&null!=o&&(h=gl(l,a,o),!e.ignoreViewBox)){var c=n;n=new lv,n.add(c),c.scale=h.scale.slice(),c.position=h.position.slice()}return e.ignoreRootClip||null==a||null==o||n.setClipPath(new Dy({shape:{x:0,y:0,width:a,height:o}})),{root:n,width:a,height:o,viewBoxRect:l,viewBoxTransform:h}},sl.prototype._parseNode=function(t,e){var i=t.nodeName.toLowerCase();"defs"===i?this._isDefine=!0:"text"===i&&(this._isText=!0);var n;if(this._isDefine){var r=B_[i];if(r){var a=r.call(this,t),o=t.getAttribute("id");o&&(this._defs[o]=a)}}else{var r=R_[i];r&&(n=r.call(this,t,e),e.add(n))}for(var s=t.firstChild;s;)1===s.nodeType&&this._parseNode(s,n),3===s.nodeType&&this._isText&&this._parseText(s,n),s=s.nextSibling;"defs"===i?this._isDefine=!1:"text"===i&&(this._isText=!1)},sl.prototype._parseText=function(t,e){if(1===t.nodeType){var i=t.getAttribute("dx")||0,n=t.getAttribute("dy")||0;this._textX+=parseFloat(i),this._textY+=parseFloat(n)}var r=new xy({style:{text:t.textContent,transformText:!0},position:[this._textX||0,this._textY||0]});hl(e,r),cl(t,r,this._defs);var a=r.style.fontSize;a&&9>a&&(r.style.fontSize=9,r.scale=r.scale||[1,1],r.scale[0]*=a/9,r.scale[1]*=a/9);var o=r.getBoundingRect();return this._textX+=o.width,e.add(r),r};var R_={g:function(t,e){var i=new lv;return hl(e,i),cl(t,i,this._defs),i},rect:function(t,e){var i=new Dy;return hl(e,i),cl(t,i,this._defs),i.setShape({x:parseFloat(t.getAttribute("x")||0),y:parseFloat(t.getAttribute("y")||0),width:parseFloat(t.getAttribute("width")||0),height:parseFloat(t.getAttribute("height")||0)}),i},circle:function(t,e){var i=new _y;return hl(e,i),cl(t,i,this._defs),i.setShape({cx:parseFloat(t.getAttribute("cx")||0),cy:parseFloat(t.getAttribute("cy")||0),r:parseFloat(t.getAttribute("r")||0)}),i},line:function(t,e){var i=new ky;return hl(e,i),cl(t,i,this._defs),i.setShape({x1:parseFloat(t.getAttribute("x1")||0),y1:parseFloat(t.getAttribute("y1")||0),x2:parseFloat(t.getAttribute("x2")||0),y2:parseFloat(t.getAttribute("y2")||0)}),i},ellipse:function(t,e){var i=new z_;return hl(e,i),cl(t,i,this._defs),i.setShape({cx:parseFloat(t.getAttribute("cx")||0),cy:parseFloat(t.getAttribute("cy")||0),rx:parseFloat(t.getAttribute("rx")||0),ry:parseFloat(t.getAttribute("ry")||0)}),i},polygon:function(t,e){var i=t.getAttribute("points");i&&(i=ul(i));var n=new Cy({shape:{points:i||[]}});return hl(e,n),cl(t,n,this._defs),n},polyline:function(t,e){var i=new Fr;hl(e,i),cl(t,i,this._defs);var n=t.getAttribute("points");n&&(n=ul(n));var r=new Ay({shape:{points:n||[]}});return r},image:function(t,e){var i=new yn;return hl(e,i),cl(t,i,this._defs),i.setStyle({image:t.getAttribute("xlink:href"),x:t.getAttribute("x"),y:t.getAttribute("y"),width:t.getAttribute("width"),height:t.getAttribute("height")}),i},text:function(t,e){var i=t.getAttribute("x")||0,n=t.getAttribute("y")||0,r=t.getAttribute("dx")||0,a=t.getAttribute("dy")||0;this._textX=parseFloat(i)+parseFloat(r),this._textY=parseFloat(n)+parseFloat(a);var o=new lv;return hl(e,o),cl(t,o,this._defs),o},tspan:function(t,e){var i=t.getAttribute("x"),n=t.getAttribute("y");null!=i&&(this._textX=parseFloat(i)),null!=n&&(this._textY=parseFloat(n));var r=t.getAttribute("dx")||0,a=t.getAttribute("dy")||0,o=new lv;return hl(e,o),cl(t,o,this._defs),this._textX+=r,this._textY+=a,o},path:function(t,e){var i=t.getAttribute("d")||"",n=Hr(i);return hl(e,n),cl(t,n,this._defs),n}},B_={lineargradient:function(t){var e=parseInt(t.getAttribute("x1")||0,10),i=parseInt(t.getAttribute("y1")||0,10),n=parseInt(t.getAttribute("x2")||10,10),r=parseInt(t.getAttribute("y2")||0,10),a=new Ry(e,i,n,r);return ll(t,a),a},radialgradient:function(){}},N_={fill:"fill",stroke:"stroke","stroke-width":"lineWidth",opacity:"opacity","fill-opacity":"fillOpacity","stroke-opacity":"strokeOpacity","stroke-dasharray":"lineDash","stroke-dashoffset":"lineDashOffset","stroke-linecap":"lineCap","stroke-linejoin":"lineJoin","stroke-miterlimit":"miterLimit","font-family":"fontFamily","font-size":"fontSize","font-style":"fontStyle","font-weight":"fontWeight","text-align":"textAlign","alignment-baseline":"textBaseline"},F_=/url\(\s*#(.*?)\)/,V_=/(translate|scale|rotate|skewX|skewY|matrix)\(([\-\s0-9\.e,]*)\)/g,W_=/([^\s:;]+)\s*:\s*([^:;]+)/g,G_=N(),H_={registerMap:function(t,e,i){var n;return _(e)?n=e:e.svg?n=[{type:"svg",source:e.svg,specialAreas:e.specialAreas}]:(e.geoJson&&!e.features&&(i=e.specialAreas,e=e.geoJson),n=[{type:"geoJSON",source:e,specialAreas:i}]),f(n,function(t){var e=t.type;"geoJson"===e&&(e=t.type="geoJSON");var i=Z_[e];i(t)}),G_.set(t,n)},retrieveMap:function(t){return G_.get(t)}},Z_={geoJSON:function(t){var e=t.source;t.geoJSON=b(e)?"undefined"!=typeof JSON&&JSON.parse?JSON.parse(e):new Function("return ("+e+");")():e},svg:function(t){t.svgXML=ol(t.source)}},X_=O,Y_=f,j_=w,q_=S,U_=yx.parseClassType,$_="4.2.0",K_={zrender:"4.0.5"},Q_=1,J_=1e3,tw=5e3,ew=1e3,iw=2e3,nw=3e3,rw=4e3,aw=5e3,ow={PROCESSOR:{FILTER:J_,STATISTIC:tw},VISUAL:{LAYOUT:ew,GLOBAL:iw,CHART:nw,COMPONENT:rw,BRUSH:aw}},sw="__flagInMainProcess",lw="__optionUpdated",hw=/^[a-zA-Z0-9_]+$/;ml.prototype.on=vl("on"),ml.prototype.off=vl("off"),ml.prototype.one=vl("one"),c(ml,bg);var uw=yl.prototype;uw._onframe=function(){if(!this._disposed){var t=this._scheduler;if(this[lw]){var e=this[lw].silent;this[sw]=!0,_l(this),cw.update.call(this),this[sw]=!1,this[lw]=!1,Ml.call(this,e),Il.call(this,e)}else if(t.unfinished){var i=Q_,n=this._model,r=this._api;t.unfinished=!1;do{var a=+new Date;t.performSeriesTasks(n),t.performDataProcessorTasks(n),bl(this,n),t.performVisualTasks(n),Pl(this,this._model,r,"remain"),i-=+new Date-a}while(i>0&&t.unfinished);t.unfinished||this._zr.flush()}}},uw.getDom=function(){return this._dom},uw.getZr=function(){return this._zr},uw.setOption=function(t,e,i){var n;if(q_(e)&&(i=e.lazyUpdate,n=e.silent,e=e.notMerge),this[sw]=!0,!this._model||e){var r=new Qo(this._api),a=this._theme,o=this._model=new Ex(null,null,a,r);o.scheduler=this._scheduler,o.init(null,null,a,r)}this._model.setOption(t,vw),i?(this[lw]={silent:n},this[sw]=!1):(_l(this),cw.update.call(this),this._zr.flush(),this[lw]=!1,this[sw]=!1,Ml.call(this,n),Il.call(this,n))},uw.setTheme=function(){console.error("ECharts#setTheme() is DEPRECATED in ECharts 3.0")},uw.getModel=function(){return this._model},uw.getOption=function(){return this._model&&this._model.getOption()},uw.getWidth=function(){return this._zr.getWidth()},uw.getHeight=function(){return this._zr.getHeight()},uw.getDevicePixelRatio=function(){return this._zr.painter.dpr||window.devicePixelRatio||1},uw.getRenderedCanvas=function(t){if(tg.canvasSupported){t=t||{},t.pixelRatio=t.pixelRatio||1,t.backgroundColor=t.backgroundColor||this._model.get("backgroundColor");var e=this._zr;return e.painter.getRenderedCanvas(t)}},uw.getSvgDataUrl=function(){if(tg.svgSupported){var t=this._zr,e=t.storage.getDisplayList();return f(e,function(t){t.stopAnimation(!0)}),t.painter.pathToDataUrl()}},uw.getDataURL=function(t){t=t||{};var e=t.excludeComponents,i=this._model,n=[],r=this;Y_(e,function(t){i.eachComponent({mainType:t},function(t){var e=r._componentsMap[t.__viewId];e.group.ignore||(n.push(e),e.group.ignore=!0)})});var a="svg"===this._zr.painter.getType()?this.getSvgDataUrl():this.getRenderedCanvas(t).toDataURL("image/"+(t&&t.type||"png"));return Y_(n,function(t){t.group.ignore=!1}),a},uw.getConnectedDataURL=function(t){if(tg.canvasSupported){var e=this.group,i=Math.min,r=Math.max,a=1/0;if(bw[e]){var o=a,s=a,l=-a,h=-a,u=[],c=t&&t.pixelRatio||1;f(ww,function(a){if(a.group===e){var c=a.getRenderedCanvas(n(t)),d=a.getDom().getBoundingClientRect();o=i(d.left,o),s=i(d.top,s),l=r(d.right,l),h=r(d.bottom,h),u.push({dom:c,left:d.left,top:d.top})}}),o*=c,s*=c,l*=c,h*=c;var d=l-o,p=h-s,g=cg();g.width=d,g.height=p;var v=On(g);return Y_(u,function(t){var e=new yn({style:{x:t.left*c-o,y:t.top*c-s,image:t.dom}});v.add(e)}),v.refreshImmediately(),g.toDataURL("image/"+(t&&t.type||"png"))}return this.getDataURL(t)}},uw.convertToPixel=x(xl,"convertToPixel"),uw.convertFromPixel=x(xl,"convertFromPixel"),uw.containPixel=function(t,e){var i,n=this._model;return t=qn(n,t),f(t,function(t,n){n.indexOf("Models")>=0&&f(t,function(t){var r=t.coordinateSystem;if(r&&r.containPoint)i|=!!r.containPoint(e);else if("seriesModels"===n){var a=this._chartsMap[t.__viewId];a&&a.containPoint&&(i|=a.containPoint(e,t))}},this)},this),!!i},uw.getVisual=function(t,e){var i=this._model;t=qn(i,t,{defaultMainType:"series"});var n=t.seriesModel,r=n.getData(),a=t.hasOwnProperty("dataIndexInside")?t.dataIndexInside:t.hasOwnProperty("dataIndex")?r.indexOfRawIndex(t.dataIndex):null;return null!=a?r.getItemVisual(a,e):r.getVisual(e)},uw.getViewOfComponentModel=function(t){return this._componentsMap[t.__viewId]},uw.getViewOfSeriesModel=function(t){return this._chartsMap[t.__viewId]};var cw={prepareAndUpdate:function(t){_l(this),cw.update.call(this,t)},update:function(t){var e=this._model,i=this._api,n=this._zr,r=this._coordSysMgr,a=this._scheduler;if(e){a.restoreData(e,t),a.performSeriesTasks(e),r.create(e,i),a.performDataProcessorTasks(e,t),bl(this,e),r.update(e,i),Al(e),a.performVisualTasks(e,t),Dl(this,e,i,t);var o=e.get("backgroundColor")||"transparent";if(tg.canvasSupported)n.setBackgroundColor(o);else{var s=He(o);o=Qe(s,"rgb"),0===s[3]&&(o="transparent")}Ll(e,i)}},updateTransform:function(t){var e=this._model,i=this,n=this._api;if(e){var r=[];e.eachComponent(function(a,o){var s=i.getViewOfComponentModel(o);if(s&&s.__alive)if(s.updateTransform){var l=s.updateTransform(o,e,n,t);l&&l.update&&r.push(s)}else r.push(s)});var a=N();e.eachSeries(function(r){var o=i._chartsMap[r.__viewId];if(o.updateTransform){var s=o.updateTransform(r,e,n,t);s&&s.update&&a.set(r.uid,1)}else a.set(r.uid,1)}),Al(e),this._scheduler.performVisualTasks(e,t,{setDirty:!0,dirtyMap:a}),Pl(i,e,n,t,a),Ll(e,this._api)}},updateView:function(t){var e=this._model;e&&(Bs.markUpdateMethod(t,"updateView"),Al(e),this._scheduler.performVisualTasks(e,t,{setDirty:!0}),Dl(this,this._model,this._api,t),Ll(e,this._api))},updateVisual:function(t){cw.update.call(this,t)},updateLayout:function(t){cw.update.call(this,t)}};uw.resize=function(t){this._zr.resize(t);var e=this._model;if(this._loadingFX&&this._loadingFX.resize(),e){var i=e.resetOption("media"),n=t&&t.silent;this[sw]=!0,i&&_l(this),cw.update.call(this),this[sw]=!1,Ml.call(this,n),Il.call(this,n)}},uw.showLoading=function(t,e){if(q_(t)&&(e=t,t=""),t=t||"default",this.hideLoading(),_w[t]){var i=_w[t](this._api,e),n=this._zr;this._loadingFX=i,n.add(i)}},uw.hideLoading=function(){this._loadingFX&&this._zr.remove(this._loadingFX),this._loadingFX=null},uw.makeActionFromEvent=function(t){var e=o({},t);return e.type=pw[t.type],e},uw.dispatchAction=function(t,e){if(q_(e)||(e={silent:!!e}),fw[t.type]&&this._model){if(this[sw])return void this._pendingActions.push(t);Sl.call(this,t,e.silent),e.flush?this._zr.flush(!0):e.flush!==!1&&tg.browser.weChat&&this._throttledZrFlush(),Ml.call(this,e.silent),Il.call(this,e.silent)}},uw.appendData=function(t){var e=t.seriesIndex,i=this.getModel(),n=i.getSeriesByIndex(e);n.appendData(t),this._scheduler.unfinished=!0},uw.on=vl("on"),uw.off=vl("off"),uw.one=vl("one");var dw=["click","dblclick","mouseover","mouseout","mousemove","mousedown","mouseup","globalout","contextmenu"];uw._initEvents=function(){Y_(dw,function(t){this._zr.on(t,function(e){var i,n=this.getModel(),r=e.target,a="globalout"===t;if(a)i={};else if(r&&null!=r.dataIndex){var s=r.dataModel||n.getSeriesByIndex(r.seriesIndex);i=s&&s.getDataParams(r.dataIndex,r.dataType,r)||{}}else r&&r.eventData&&(i=o({},r.eventData));if(i){var l=i.componentType,h=i.componentIndex;("markLine"===l||"markPoint"===l||"markArea"===l)&&(l="series",h=i.seriesIndex);var u=l&&null!=h&&n.getComponent(l,h),c=u&&this["series"===u.mainType?"_chartsMap":"_componentsMap"][u.__viewId];i.event=e,i.type=t,this._ecEventProcessor.eventInfo={targetEl:r,packedEvent:i,model:u,view:c},this.trigger(t,i)}},this)},this),Y_(pw,function(t,e){this._messageCenter.on(e,function(t){this.trigger(e,t)},this)},this)},uw.isDisposed=function(){return this._disposed},uw.clear=function(){this.setOption({series:[]},!0)},uw.dispose=function(){if(!this._disposed){this._disposed=!0,$n(this.getDom(),Iw,"");var t=this._api,e=this._model;Y_(this._componentsViews,function(i){i.dispose(e,t)}),Y_(this._chartsViews,function(i){i.dispose(e,t)}),this._zr.dispose(),delete ww[this.id]}},c(yl,bg),Bl.prototype={constructor:Bl,normalizeQuery:function(t){var e={},i={},n={};if(b(t)){var r=U_(t);e.mainType=r.main||null,e.subType=r.sub||null}else{var a=["Index","Name","Id"],o={name:1,dataIndex:1,dataType:1};f(t,function(t,r){for(var s=!1,l=0;l0&&u===r.length-h.length){var c=r.slice(0,u);"data"!==c&&(e.mainType=c,e[h.toLowerCase()]=t,s=!0)}}o.hasOwnProperty(r)&&(i[r]=t,s=!0),s||(n[r]=t)})}return{cptQuery:e,dataQuery:i,otherQuery:n}},filter:function(t,e){function i(t,e,i,n){return null==t[i]||e[n||i]===t[i]}var n=this.eventInfo;if(!n)return!0;var r=n.targetEl,a=n.packedEvent,o=n.model,s=n.view;if(!o||!s)return!0;var l=e.cptQuery,h=e.dataQuery;return i(l,o,"mainType")&&i(l,o,"subType")&&i(l,o,"index","componentIndex")&&i(l,o,"name")&&i(l,o,"id")&&i(h,a,"name")&&i(h,a,"dataIndex")&&i(h,a,"dataType")&&(!s.filterForExposedEvent||s.filterForExposedEvent(t,e.otherQuery,r,a))},afterTrigger:function(){this.eventInfo=null}};var fw={},pw={},gw=[],vw=[],mw=[],yw=[],xw={},_w={},ww={},bw={},Sw=new Date-0,Mw=new Date-0,Iw="_echarts_instance_",Tw=Wl;Jl(iw,m_),Yl(Ux),jl(tw,$x),eh("default",w_),Ul({type:"highlight",event:"highlight",update:"highlight"},V),Ul({type:"downplay",event:"downplay",update:"downplay"},V),Xl("light",D_),Xl("dark",O_);var Cw={};uh.prototype={constructor:uh,add:function(t){return this._add=t,this},update:function(t){return this._update=t,this},remove:function(t){return this._remove=t,this},execute:function(){var t,e=this._old,i=this._new,n={},r={},a=[],o=[];for(ch(e,n,a,"_oldKeyGetter",this),ch(i,r,o,"_newKeyGetter",this),t=0;tu;u++)this._add&&this._add(l[u]);else this._add&&this._add(l)}}}};var Aw=N(["tooltip","label","itemName","itemId","seriesName"]),Dw=S,kw="undefined",Pw="e\x00\x00",Lw={"float":typeof Float64Array===kw?Array:Float64Array,"int":typeof Int32Array===kw?Array:Int32Array,ordinal:Array,number:Array,time:Array},Ow=typeof Uint32Array===kw?Array:Uint32Array,zw=typeof Uint16Array===kw?Array:Uint16Array,Ew=["hasItemOption","_nameList","_idList","_invertedIndicesMap","_rawData","_chunkSize","_chunkCount","_dimValueGetter","_count","_rawCount","_nameDimIdx","_idDimIdx"],Rw=["_extent","_approximateExtent","_rawExtent"],Bw=function(t,e){t=t||["x","y"];for(var i={},n=[],r={},a=0;a=e)){for(var i,n=this._chunkSize,r=this._rawData,a=this._storage,o=this.dimensions,s=o.length,l=this._dimensionInfos,h=this._nameList,u=this._idList,c=this._rawExtent,d=this._nameRepeatCount={},f=this._chunkCount,p=f-1,g=0;s>g;g++){var v=o[g];c[v]||(c[v]=Th());var m=l[v];0===m.otherDims.itemName&&(i=this._nameDimIdx=g),0===m.otherDims.itemId&&(this._idDimIdx=g);var y=Lw[m.type];a[v]||(a[v]=[]);var x=a[v][p];if(x&&x.lengthb;b+=n)a[v].push(new y(Math.min(e-b,n)));this._chunkCount=a[v].length}for(var S=new Array(s),M=t;e>M;M++){S=r.getItem(M,S);for(var I=Math.floor(M/n),T=M%n,b=0;s>b;b++){var v=o[b],C=a[v][I],A=this._dimValueGetter(S,v,M,b);C[T]=A;var D=c[v];AD[1]&&(D[1]=A)}if(!r.pure){var k=h[M];if(S&&null==k)if(null!=S.name)h[M]=k=S.name;else if(null!=i){var P=o[i],L=a[P][I];if(L){k=L[T];var O=l[P].ordinalMeta;O&&O.categories.length&&(k=O.categories[k])}}var z=null==S?null:S.id;null==z&&null!=k&&(d[k]=d[k]||0,z=k,d[k]>0&&(z+="__ec__"+d[k]),d[k]++),null!=z&&(u[M]=z)}}!r.persistent&&r.clean&&r.clean(),this._rawCount=this._count=e,this._extent={},yh(this)}},Nw.count=function(){return this._count},Nw.getIndices=function(){var t,e=this._indices;if(e){var i=e.constructor,n=this._count;if(i===Array){t=new i(n);for(var r=0;n>r;r++)t[r]=e[r]}else t=new i(e.buffer,0,n)}else for(var i=gh(this),t=new i(this.count()),r=0;r=0&&e=0&&en;n++)i.push(this.get(t[n],e));return i},Nw.hasValue=function(t){for(var e=this._dimensionsSummary.dataDimsOnCoord,i=this._dimensionInfos,n=0,r=e.length;r>n;n++)if("ordinal"!==i[e[n]].type&&isNaN(this.get(e[n],t)))return!1;return!0},Nw.getDataExtent=function(t){t=this.getDimension(t);var e=this._storage[t],i=Th();if(!e)return i;var n,r=this.count(),a=!this._indices;if(a)return this._rawExtent[t].slice();if(n=this._extent[t])return n.slice();n=i;for(var o=n[0],s=n[1],l=0;r>l;l++){var h=this._getFast(t,this.getRawIndex(l));o>h&&(o=h),h>s&&(s=h)}return n=[o,s],this._extent[t]=n,n},Nw.getApproximateExtent=function(t){return t=this.getDimension(t),this._approximateExtent[t]||this.getDataExtent(t)},Nw.setApproximateExtent=function(t,e){e=this.getDimension(e),this._approximateExtent[e]=t.slice()},Nw.getCalculationInfo=function(t){return this._calculationInfo[t]},Nw.setCalculationInfo=function(t,e){Dw(t)?o(this._calculationInfo,t):this._calculationInfo[t]=e},Nw.getSum=function(t){var e=this._storage[t],i=0;if(e)for(var n=0,r=this.count();r>n;n++){var a=this.get(t,n);isNaN(a)||(i+=a)}return i},Nw.getMedian=function(t){var e=[];this.each(t,function(t){isNaN(t)||e.push(t)});var i=[].concat(e).sort(function(t,e){return t-e}),n=this.count();return 0===n?0:n%2===1?i[(n-1)/2]:(i[n/2]+i[n/2-1])/2},Nw.rawIndexOf=function(t,e){var i=t&&this._invertedIndicesMap[t],n=i[e];return null==n||isNaN(n)?-1:n},Nw.indexOfName=function(t){for(var e=0,i=this.count();i>e;e++)if(this.getName(e)===t)return e;return-1},Nw.indexOfRawIndex=function(t){if(!this._indices)return t;if(t>=this._rawCount||0>t)return-1;var e=this._indices,i=e[t];if(null!=i&&i=n;){var a=(n+r)/2|0;if(e[a]t))return a;r=a-1}}return-1},Nw.indicesOfNearest=function(t,e,i){var n=this._storage,r=n[t],a=[];if(!r)return a;null==i&&(i=1/0);for(var o=Number.MAX_VALUE,s=-1,l=0,h=this.count();h>l;l++){var u=e-this.get(t,l),c=Math.abs(u);i>=u&&o>=c&&((o>c||u>=0&&0>s)&&(o=c,s=u,a.length=0),a.push(l))}return a},Nw.getRawIndex=_h,Nw.getRawDataItem=function(t){if(this._rawData.persistent)return this._rawData.getItem(this.getRawIndex(t));for(var e=[],i=0;io;o++)s[o]=this.get(t[o],a);s[o]=a,e.apply(i,s)}}},Nw.filterSelf=function(t,e,i,n){if(this._count){"function"==typeof t&&(n=i,i=e,e=t,t=[]),i=i||n||this,t=p(Sh(t),this.getDimension,this);for(var r=this.count(),a=gh(this),o=new a(r),s=[],l=t.length,h=0,u=t[0],c=0;r>c;c++){var d,f=this.getRawIndex(c);if(0===l)d=e.call(i,c);else if(1===l){var g=this._getFast(u,f);d=e.call(i,g,c)}else{for(var v=0;l>v;v++)s[v]=this._getFast(u,f);s[v]=c,d=e.apply(i,s)}d&&(o[h++]=f)}return r>h&&(this._indices=o),this._count=h,this._extent={},this.getRawIndex=this._indices?wh:_h,this}},Nw.selectRange=function(t){if(this._count){var e=[];for(var i in t)t.hasOwnProperty(i)&&e.push(i);var n=e.length;if(n){var r=this.count(),a=gh(this),o=new a(r),s=0,l=e[0],h=t[l][0],u=t[l][1],c=!1;if(!this._indices){var d=0;if(1===n){for(var f=this._storage[e[0]],p=0;pm;m++){var y=g[m];(y>=h&&u>=y||isNaN(y))&&(o[s++]=d),d++}c=!0}else if(2===n){for(var f=this._storage[l],x=this._storage[e[1]],_=t[e[1]][0],w=t[e[1]][1],p=0;pm;m++){var y=g[m],S=b[m]; + (y>=h&&u>=y||isNaN(y))&&(S>=_&&w>=S||isNaN(S))&&(o[s++]=d),d++}c=!0}}if(!c)if(1===n)for(var m=0;r>m;m++){var M=this.getRawIndex(m),y=this._getFast(l,M);(y>=h&&u>=y||isNaN(y))&&(o[s++]=M)}else for(var m=0;r>m;m++){for(var I=!0,M=this.getRawIndex(m),p=0;n>p;p++){var T=e[p],y=this._getFast(i,M);(yt[T][1])&&(I=!1)}I&&(o[s++]=this.getRawIndex(m))}return r>s&&(this._indices=o),this._count=s,this._extent={},this.getRawIndex=this._indices?wh:_h,this}}},Nw.mapArray=function(t,e,i,n){"function"==typeof t&&(n=i,i=e,e=t,t=[]),i=i||n||this;var r=[];return this.each(t,function(){r.push(e&&e.apply(this,arguments))},i),r},Nw.map=function(t,e,i,n){i=i||n||this,t=p(Sh(t),this.getDimension,this);var r=Mh(this,t);r._indices=this._indices,r.getRawIndex=r._indices?wh:_h;for(var a=r._storage,o=[],s=this._chunkSize,l=t.length,h=this.count(),u=[],c=r._rawExtent,d=0;h>d;d++){for(var f=0;l>f;f++)u[f]=this.get(t[f],d);u[l]=d;var g=e&&e.apply(i,u);if(null!=g){"object"!=typeof g&&(o[0]=g,g=o);for(var v=this.getRawIndex(d),m=Math.floor(v/s),y=v%s,x=0;xb[1]&&(b[1]=w)}}}return r},Nw.downSample=function(t,e,i,n){for(var r=Mh(this,[t]),a=r._storage,o=[],s=Math.floor(1/e),l=a[t],h=this.count(),u=this._chunkSize,c=r._rawExtent[t],d=new(gh(this))(h),f=0,p=0;h>p;p+=s){s>h-p&&(s=h-p,o.length=s);for(var g=0;s>g;g++){var v=this.getRawIndex(p+g),m=Math.floor(v/u),y=v%u;o[g]=l[m][y]}var x=i(o),_=this.getRawIndex(Math.min(p+n(o,x)||0,h-1)),w=Math.floor(_/u),b=_%u;l[w][b]=x,xc[1]&&(c[1]=x),d[f++]=_}return r._count=f,r._indices=d,r.getRawIndex=wh,r},Nw.getItemModel=function(t){var e=this.hostModel;return new Wa(this.getRawDataItem(t),e,e&&e.ecModel)},Nw.diff=function(t){var e=this;return new uh(t?t.getIndices():[],this.getIndices(),function(e){return bh(t,e)},function(t){return bh(e,t)})},Nw.getVisual=function(t){var e=this._visual;return e&&e[t]},Nw.setVisual=function(t,e){if(Dw(t))for(var i in t)t.hasOwnProperty(i)&&this.setVisual(i,t[i]);else this._visual=this._visual||{},this._visual[t]=e},Nw.setLayout=function(t,e){if(Dw(t))for(var i in t)t.hasOwnProperty(i)&&this.setLayout(i,t[i]);else this._layout[t]=e},Nw.getLayout=function(t){return this._layout[t]},Nw.getItemLayout=function(t){return this._itemLayouts[t]},Nw.setItemLayout=function(t,e,i){this._itemLayouts[t]=i?o(this._itemLayouts[t]||{},e):e},Nw.clearItemLayouts=function(){this._itemLayouts.length=0},Nw.getItemVisual=function(t,e,i){var n=this._itemVisuals[t],r=n&&n[e];return null!=r||i?r:this.getVisual(e)},Nw.setItemVisual=function(t,e,i){var n=this._itemVisuals[t]||{},r=this.hasItemVisual;if(this._itemVisuals[t]=n,Dw(e))for(var a in e)e.hasOwnProperty(a)&&(n[a]=e[a],r[a]=!0);else n[e]=i,r[e]=!0},Nw.clearAllVisual=function(){this._visual={},this._itemVisuals=[],this.hasItemVisual={}};var Fw=function(t){t.seriesIndex=this.seriesIndex,t.dataIndex=this.dataIndex,t.dataType=this.dataType};Nw.setItemGraphicEl=function(t,e){var i=this.hostModel;e&&(e.dataIndex=t,e.dataType=this.dataType,e.seriesIndex=i&&i.seriesIndex,"group"===e.type&&e.traverse(Fw,e)),this._graphicEls[t]=e},Nw.getItemGraphicEl=function(t){return this._graphicEls[t]},Nw.eachItemGraphicEl=function(t,e){f(this._graphicEls,function(i,n){i&&t&&t.call(e,i,n)})},Nw.cloneShallow=function(t){if(!t){var e=p(this.dimensions,this.getDimensionInfo,this);t=new Bw(e,this.hostModel)}if(t._storage=this._storage,mh(t,this),this._indices){var i=this._indices.constructor;t._indices=new i(this._indices)}else t._indices=null;return t.getRawIndex=t._indices?wh:_h,t},Nw.wrapMethod=function(t,e){var i=this[t];"function"==typeof i&&(this.__wrappedMethods=this.__wrappedMethods||[],this.__wrappedMethods.push(t),this[t]=function(){var t=i.apply(this,arguments);return e.apply(this,[t].concat(P(arguments)))})},Nw.TRANSFERABLE_METHODS=["cloneShallow","downSample","map"],Nw.CHANGABLE_METHODS=["filterSelf","selectRange"];var Vw=function(t,e){return e=e||{},Ch(e.coordDimensions||[],t,{dimsDef:e.dimensionsDefine||t.dimensionsDefine,encodeDef:e.encodeDefine||t.encodeDefine,dimCount:e.dimensionsCount,generateCoord:e.generateCoord,generateCoordCount:e.generateCoordCount})};Rh.prototype.parse=function(t){return t},Rh.prototype.getSetting=function(t){return this._setting[t]},Rh.prototype.contain=function(t){var e=this._extent;return t>=e[0]&&t<=e[1]},Rh.prototype.normalize=function(t){var e=this._extent;return e[1]===e[0]?.5:(t-e[0])/(e[1]-e[0])},Rh.prototype.scale=function(t){var e=this._extent;return t*(e[1]-e[0])+e[0]},Rh.prototype.unionExtent=function(t){var e=this._extent;t[0]e[1]&&(e[1]=t[1])},Rh.prototype.unionExtentFromData=function(t,e){this.unionExtent(t.getApproximateExtent(e))},Rh.prototype.getExtent=function(){return this._extent.slice()},Rh.prototype.setExtent=function(t,e){var i=this._extent;isNaN(t)||(i[0]=t),isNaN(e)||(i[1]=e)},Rh.prototype.isBlank=function(){return this._isBlank},Rh.prototype.setBlank=function(t){this._isBlank=t},Rh.prototype.getLabel=null,er(Rh),ar(Rh,{registerWhenExtend:!0}),Bh.createByAxisModel=function(t){var e=t.option,i=e.data,n=i&&p(i,Fh);return new Bh({categories:n,needCollect:!n,deduplication:e.dedplication!==!1})};var Ww=Bh.prototype;Ww.getOrdinal=function(t){return Nh(this).get(t)},Ww.parseAndCollect=function(t){var e,i=this._needCollect;if("string"!=typeof t&&!i)return t;if(i&&!this._deduplication)return e=this.categories.length,this.categories[e]=t,e;var n=Nh(this);return e=n.get(t),null==e&&(i?(e=this.categories.length,this.categories[e]=t,n.set(t,e)):e=0/0),e};var Gw=Rh.prototype,Hw=Rh.extend({type:"ordinal",init:function(t,e){(!t||_(t))&&(t=new Bh({categories:t})),this._ordinalMeta=t,this._extent=e||[0,t.categories.length-1]},parse:function(t){return"string"==typeof t?this._ordinalMeta.getOrdinal(t):Math.round(t)},contain:function(t){return t=this.parse(t),Gw.contain.call(this,t)&&null!=this._ordinalMeta.categories[t]},normalize:function(t){return Gw.normalize.call(this,this.parse(t))},scale:function(t){return Math.round(Gw.scale.call(this,t))},getTicks:function(){for(var t=[],e=this._extent,i=e[0];i<=e[1];)t.push(i),i++;return t},getLabel:function(t){return this.isBlank()?void 0:this._ordinalMeta.categories[t]},count:function(){return this._extent[1]-this._extent[0]+1},unionExtentFromData:function(t,e){this.unionExtent(t.getApproximateExtent(e))},getOrdinalMeta:function(){return this._ordinalMeta},niceTicks:V,niceExtent:V});Hw.create=function(){return new Hw};var Zw=$a,Xw=$a,Yw=Rh.extend({type:"interval",_interval:0,_intervalPrecision:2,setExtent:function(t,e){var i=this._extent;isNaN(t)||(i[0]=parseFloat(t)),isNaN(e)||(i[1]=parseFloat(e))},unionExtent:function(t){var e=this._extent;t[0]e[1]&&(e[1]=t[1]),Yw.prototype.setExtent.call(this,e[0],e[1])},getInterval:function(){return this._interval},setInterval:function(t){this._interval=t,this._niceExtent=this._extent.slice(),this._intervalPrecision=Wh(t)},getTicks:function(){return Zh(this._interval,this._extent,this._niceExtent,this._intervalPrecision)},getLabel:function(t,e){if(null==t)return"";var i=e&&e.precision;return null==i?i=Ja(t)||0:"auto"===i&&(i=this._intervalPrecision),t=Xw(t,i,!0),co(t)},niceTicks:function(t,e,i){t=t||5;var n=this._extent,r=n[1]-n[0];if(isFinite(r)){0>r&&(r=-r,n.reverse());var a=Vh(n,t,e,i);this._intervalPrecision=a.intervalPrecision,this._interval=a.interval,this._niceExtent=a.niceTickExtent}},niceExtent:function(t){var e=this._extent;if(e[0]===e[1])if(0!==e[0]){var i=e[0];t.fixMax?e[0]-=i/2:(e[1]+=i/2,e[0]-=i/2)}else e[1]=1;var n=e[1]-e[0];isFinite(n)||(e[0]=0,e[1]=1),this.niceTicks(t.splitNumber,t.minInterval,t.maxInterval);var r=this._interval;t.fixMin||(e[0]=Xw(Math.floor(e[0]/r)*r)),t.fixMax||(e[1]=Xw(Math.ceil(e[1]/r)*r))}});Yw.create=function(){return new Yw};var jw="__ec_stack_",qw=.5,Uw="undefined"!=typeof Float32Array?Float32Array:Array,$w={seriesType:"bar",plan:h_(),reset:function(t){function e(t,e){for(var i,c=new Uw(2*t.count),d=[],f=[],p=0;null!=(i=t.next());)f[h]=e.get(o,i),f[1-h]=e.get(s,i),d=n.dataToPoint(f,null,d),c[p++]=d[0],c[p++]=d[1];e.setLayout({largePoints:c,barWidth:u,valueAxisStart:eu(r,a,!1),valueAxisHorizontal:l})}if(Jh(t)&&tu(t)){var i=t.getData(),n=t.coordinateSystem,r=n.getBaseAxis(),a=n.getOtherAxis(r),o=i.mapDimension(a.dim),s=i.mapDimension(r.dim),l=a.isHorizontal(),h=l?0:1,u=Kh(Uh([t]),r,t).width;return u>qw||(u=qw),{progress:e}}}},Kw=Yw.prototype,Qw=Math.ceil,Jw=Math.floor,tb=1e3,eb=60*tb,ib=60*eb,nb=24*ib,rb=function(t,e,i,n){for(;n>i;){var r=i+n>>>1;t[r][1]a&&(a=e),null!=i&&a>i&&(a=i);var o=ob.length,s=rb(ob,a,0,o),l=ob[Math.min(s,o-1)],h=l[1];if("year"===l[0]){var u=r/h,c=so(u/t,!0);h*=c}var d=this.getSetting("useUTC")?0:60*new Date(+n[0]||+n[1]).getTimezoneOffset()*1e3,f=[Math.round(Qw((n[0]-d)/h)*h+d),Math.round(Jw((n[1]-d)/h)*h+d)];Hh(f,n),this._stepLvl=l,this._interval=h,this._niceExtent=f},parse:function(t){return+ro(t)}});f(["contain","normalize"],function(t){ab.prototype[t]=function(e){return Kw[t].call(this,this.parse(e))}});var ob=[["hh:mm:ss",tb],["hh:mm:ss",5*tb],["hh:mm:ss",10*tb],["hh:mm:ss",15*tb],["hh:mm:ss",30*tb],["hh:mm\nMM-dd",eb],["hh:mm\nMM-dd",5*eb],["hh:mm\nMM-dd",10*eb],["hh:mm\nMM-dd",15*eb],["hh:mm\nMM-dd",30*eb],["hh:mm\nMM-dd",ib],["hh:mm\nMM-dd",2*ib],["hh:mm\nMM-dd",6*ib],["hh:mm\nMM-dd",12*ib],["MM-dd\nyyyy",nb],["MM-dd\nyyyy",2*nb],["MM-dd\nyyyy",3*nb],["MM-dd\nyyyy",4*nb],["MM-dd\nyyyy",5*nb],["MM-dd\nyyyy",6*nb],["week",7*nb],["MM-dd\nyyyy",10*nb],["week",14*nb],["week",21*nb],["month",31*nb],["week",42*nb],["month",62*nb],["week",70*nb],["quarter",95*nb],["month",31*nb*4],["month",31*nb*5],["half-year",380*nb/2],["month",31*nb*8],["month",31*nb*10],["year",380*nb]];ab.create=function(t){return new ab({useUTC:t.ecModel.get("useUTC")})};var sb=Rh.prototype,lb=Yw.prototype,hb=Ja,ub=$a,cb=Math.floor,db=Math.ceil,fb=Math.pow,pb=Math.log,gb=Rh.extend({type:"log",base:10,$constructor:function(){Rh.apply(this,arguments),this._originalScale=new Yw},getTicks:function(){var t=this._originalScale,e=this._extent,i=t.getExtent();return p(lb.getTicks.call(this),function(n){var r=$a(fb(this.base,n));return r=n===e[0]&&t.__fixMin?iu(r,i[0]):r,r=n===e[1]&&t.__fixMax?iu(r,i[1]):r},this)},getLabel:lb.getLabel,scale:function(t){return t=sb.scale.call(this,t),fb(this.base,t)},setExtent:function(t,e){var i=this.base;t=pb(t)/pb(i),e=pb(e)/pb(i),lb.setExtent.call(this,t,e)},getExtent:function(){var t=this.base,e=sb.getExtent.call(this);e[0]=fb(t,e[0]),e[1]=fb(t,e[1]);var i=this._originalScale,n=i.getExtent();return i.__fixMin&&(e[0]=iu(e[0],n[0])),i.__fixMax&&(e[1]=iu(e[1],n[1])),e},unionExtent:function(t){this._originalScale.unionExtent(t);var e=this.base;t[0]=pb(t[0])/pb(e),t[1]=pb(t[1])/pb(e),sb.unionExtent.call(this,t)},unionExtentFromData:function(t,e){this.unionExtent(t.getApproximateExtent(e))},niceTicks:function(t){t=t||10;var e=this._extent,i=e[1]-e[0];if(!(1/0===i||0>=i)){var n=ao(i),r=t/i*n;for(.5>=r&&(n*=10);!isNaN(n)&&Math.abs(n)<1&&Math.abs(n)>0;)n*=10;var a=[$a(db(e[0]/n)*n),$a(cb(e[1]/n)*n)];this._interval=n,this._niceExtent=a}},niceExtent:function(t){lb.niceExtent.call(this,t);var e=this._originalScale;e.__fixMin=t.fixMin,e.__fixMax=t.fixMax}});f(["contain","normalize"],function(t){gb.prototype[t]=function(e){return e=pb(e)/pb(this.base),sb[t].call(this,e)}}),gb.create=function(){return new gb};var vb={getMin:function(t){var e=this.option,i=t||null==e.rangeStart?e.min:e.rangeStart;return this.axis&&null!=i&&"dataMin"!==i&&"function"!=typeof i&&!C(i)&&(i=this.axis.scale.parse(i)),i},getMax:function(t){var e=this.option,i=t||null==e.rangeEnd?e.max:e.rangeEnd;return this.axis&&null!=i&&"dataMax"!==i&&"function"!=typeof i&&!C(i)&&(i=this.axis.scale.parse(i)),i},getNeedCrossZero:function(){var t=this.option;return null!=t.rangeStart||null!=t.rangeEnd?!1:!t.scale},getCoordSysModel:V,setRange:function(t,e){this.option.rangeStart=t,this.option.rangeEnd=e},resetRange:function(){this.option.rangeStart=this.option.rangeEnd=null}},mb=$r({type:"triangle",shape:{cx:0,cy:0,width:0,height:0},buildPath:function(t,e){var i=e.cx,n=e.cy,r=e.width/2,a=e.height/2;t.moveTo(i,n-a),t.lineTo(i+r,n+a),t.lineTo(i-r,n+a),t.closePath()}}),yb=$r({type:"diamond",shape:{cx:0,cy:0,width:0,height:0},buildPath:function(t,e){var i=e.cx,n=e.cy,r=e.width/2,a=e.height/2;t.moveTo(i,n-a),t.lineTo(i+r,n),t.lineTo(i,n+a),t.lineTo(i-r,n),t.closePath()}}),xb=$r({type:"pin",shape:{x:0,y:0,width:0,height:0},buildPath:function(t,e){var i=e.x,n=e.y,r=e.width/5*3,a=Math.max(r,e.height),o=r/2,s=o*o/(a-o),l=n-a+o+s,h=Math.asin(s/o),u=Math.cos(h)*o,c=Math.sin(h),d=Math.cos(h),f=.6*o,p=.7*o;t.moveTo(i-u,l+s),t.arc(i,l,o,Math.PI-h,2*Math.PI+h),t.bezierCurveTo(i+u-c*f,l+s+d*f,i,n-p,i,n),t.bezierCurveTo(i,n-p,i-u+c*f,l+s+d*f,i-u,l+s),t.closePath()}}),_b=$r({type:"arrow",shape:{x:0,y:0,width:0,height:0},buildPath:function(t,e){var i=e.height,n=e.width,r=e.x,a=e.y,o=n/3*2;t.moveTo(r,a),t.lineTo(r+o,a+i),t.lineTo(r,a+i/4*3),t.lineTo(r-o,a+i),t.lineTo(r,a),t.closePath()}}),wb={line:ky,rect:Dy,roundRect:Dy,square:Dy,circle:_y,diamond:yb,pin:xb,arrow:_b,triangle:mb},bb={line:function(t,e,i,n,r){r.x1=t,r.y1=e+n/2,r.x2=t+i,r.y2=e+n/2},rect:function(t,e,i,n,r){r.x=t,r.y=e,r.width=i,r.height=n},roundRect:function(t,e,i,n,r){r.x=t,r.y=e,r.width=i,r.height=n,r.r=Math.min(i,n)/4},square:function(t,e,i,n,r){var a=Math.min(i,n);r.x=t,r.y=e,r.width=a,r.height=a},circle:function(t,e,i,n,r){r.cx=t+i/2,r.cy=e+n/2,r.r=Math.min(i,n)/2},diamond:function(t,e,i,n,r){r.cx=t+i/2,r.cy=e+n/2,r.width=i,r.height=n},pin:function(t,e,i,n,r){r.x=t+i/2,r.y=e+n/2,r.width=i,r.height=n},arrow:function(t,e,i,n,r){r.x=t+i/2,r.y=e+n/2,r.width=i,r.height=n},triangle:function(t,e,i,n,r){r.cx=t+i/2,r.cy=e+n/2,r.width=i,r.height=n}},Sb={};f(wb,function(t,e){Sb[e]=new t});var Mb=$r({type:"symbol",shape:{symbolType:"",x:0,y:0,width:0,height:0},beforeBrush:function(){var t=this.style,e=this.shape;"pin"===e.symbolType&&"inside"===t.textPosition&&(t.textPosition=["50%","40%"],t.textAlign="center",t.textVerticalAlign="middle")},buildPath:function(t,e,i){var n=e.symbolType,r=Sb[n];"none"!==e.symbolType&&(r||(n="rect",r=Sb[n]),bb[n](e.x,e.y,e.width,e.height,r.shape),r.buildPath(t,r.shape,i))}}),Ib={isDimensionStacked:Ph,enableDataStack:kh,getStackedDimension:Lh},Tb=(Object.freeze||Object)({createList:pu,getLayoutRect:bo,dataStack:Ib,createScale:gu,mixinAxisModelCommonMethods:vu,completeDimensions:Ch,createDimensions:Vw,createSymbol:fu}),Cb=1e-8;xu.prototype={constructor:xu,properties:null,getBoundingRect:function(){var t=this._rect;if(t)return t;for(var e=Number.MAX_VALUE,i=[e,e],n=[-e,-e],r=[],a=[],o=this.geometries,s=0;sn;n++)if("polygon"===i[n].type){var a=i[n].exterior,o=i[n].interiors;if(yu(a,t[0],t[1])){for(var s=0;s<(o?o.length:0);s++)if(yu(o[s]))continue t;return!0}}return!1},transformTo:function(t,e,i,n){var r=this.getBoundingRect(),a=r.width/r.height;i?n||(n=i/a):i=a*n;for(var o=new gi(t,e,i,n),s=r.calculateTransform(o),l=this.geometries,h=0;h0}),function(t){var e=t.properties,i=t.geometry,n=i.coordinates,r=[];"Polygon"===i.type&&r.push({type:"polygon",exterior:n[0],interiors:n.slice(1)}),"MultiPolygon"===i.type&&f(n,function(t){t[0]&&r.push({type:"polygon",exterior:t[0],interiors:t.slice(1)})});var a=new xu(e.name,r,e.cp);return a.properties=e,a})},Db=jn(),kb=[0,1],Pb=function(t,e,i){this.dim=t,this.scale=e,this._extent=i||[0,0],this.inverse=!1,this.onBand=!1};Pb.prototype={constructor:Pb,contain:function(t){var e=this._extent,i=Math.min(e[0],e[1]),n=Math.max(e[0],e[1]);return t>=i&&n>=t},containData:function(t){return this.contain(this.dataToCoord(t))},getExtent:function(){return this._extent.slice()},getPixelPrecision:function(t){return to(t||this.scale.getExtent(),this._extent)},setExtent:function(t,e){var i=this._extent;i[0]=t,i[1]=e},dataToCoord:function(t,e){var i=this._extent,n=this.scale;return t=n.normalize(t),this.onBand&&"ordinal"===n.type&&(i=i.slice(),Bu(i,n.count())),qa(t,kb,i,e)},coordToData:function(t,e){var i=this._extent,n=this.scale;this.onBand&&"ordinal"===n.type&&(i=i.slice(),Bu(i,n.count()));var r=qa(t,i,kb,e);return this.scale.scale(r)},pointToData:function(){},getTicksCoords:function(t){t=t||{};var e=t.tickModel||this.getTickModel(),i=Su(this,e),n=i.ticks,r=p(n,function(t){return{coord:this.dataToCoord(t),tickValue:t}},this),a=e.get("alignWithLabel");return Nu(this,r,i.tickCategoryInterval,a,t.clamp),r},getViewLabels:function(){return bu(this).labels},getLabelModel:function(){return this.model.getModel("axisLabel")},getTickModel:function(){return this.model.getModel("axisTick")},getBandWidth:function(){var t=this._extent,e=this.scale.getExtent(),i=e[1]-e[0]+(this.onBand?1:0);0===i&&(i=1);var n=Math.abs(t[1]-t[0]);return Math.abs(n)/i},isHorizontal:null,getRotate:null,calculateCategoryInterval:function(){return Lu(this)}};var Lb=Ab,Ob={};f(["map","each","filter","indexOf","inherits","reduce","filter","bind","curry","isArray","isString","isObject","isFunction","extend","defaults","clone","merge"],function(t){Ob[t]=pg[t]});var zb={};f(["extendShape","extendPath","makePath","makeImage","mergePath","resizePath","createIcon","setHoverStyle","setLabelStyle","setTextStyle","setText","getFont","updateProps","initProps","getTransform","clipPointsByRect","clipRectByRect","Group","Image","Text","Circle","Sector","Ring","Polygon","Polyline","Rect","Line","BezierCurve","Arc","IncrementalDisplayable","CompoundPath","LinearGradient","RadialGradient","BoundingRect"],function(t){zb[t]=Yy[t]});var Eb=function(t){this._axes={},this._dimList=[],this.name=t||""};Eb.prototype={constructor:Eb,type:"cartesian",getAxis:function(t){return this._axes[t]},getAxes:function(){return p(this._dimList,Fu,this)},getAxesByScale:function(t){return t=t.toLowerCase(),v(this.getAxes(),function(e){return e.scale.type===t})},addAxis:function(t){var e=t.dim;this._axes[e]=t,this._dimList.push(e)},dataToCoord:function(t){return this._dataCoordConvert(t,"dataToCoord")},coordToData:function(t){return this._dataCoordConvert(t,"coordToData")},_dataCoordConvert:function(t,e){for(var i=this._dimList,n=t instanceof Array?[]:{},r=0;re[1]&&e.reverse(),e},getOtherAxis:function(){this.grid.getOtherAxis()},pointToData:function(t,e){return this.coordToData(this.toLocalCoord(t["x"===this.dim?0:1]),e)},toLocalCoord:null,toGlobalCoord:null},u(Rb,Pb);var Bb={show:!0,zlevel:0,z:0,inverse:!1,name:"",nameLocation:"end",nameRotate:null,nameTruncate:{maxWidth:null,ellipsis:"...",placeholder:"."},nameTextStyle:{},nameGap:15,silent:!1,triggerEvent:!1,tooltip:{show:!1},axisPointer:{},axisLine:{show:!0,onZero:!0,onZeroAxisIndex:null,lineStyle:{color:"#333",width:1,type:"solid"},symbol:["none","none"],symbolSize:[10,15]},axisTick:{show:!0,inside:!1,length:5,lineStyle:{width:1}},axisLabel:{show:!0,inside:!1,rotate:0,showMinLabel:null,showMaxLabel:null,margin:8,fontSize:12},splitLine:{show:!0,lineStyle:{color:["#ccc"],width:1,type:"solid"}},splitArea:{show:!1,areaStyle:{color:["rgba(250,250,250,0.3)","rgba(200,200,200,0.3)"]}}},Nb={};Nb.categoryAxis=r({boundaryGap:!0,deduplication:null,splitLine:{show:!1},axisTick:{alignWithLabel:!1,interval:"auto"},axisLabel:{interval:"auto"}},Bb),Nb.valueAxis=r({boundaryGap:[0,0],splitNumber:5},Bb),Nb.timeAxis=s({scale:!0,min:"dataMin",max:"dataMax"},Nb.valueAxis),Nb.logAxis=s({scale:!0,logBase:10},Nb.valueAxis);var Fb=["value","category","time","log"],Vb=function(t,e,i,n){f(Fb,function(o){e.extend({type:t+"Axis."+o,mergeDefaultAndTheme:function(e,n){var a=this.layoutMode,s=a?Mo(e):{},l=n.getTheme();r(e,l.get(o+"Axis")),r(e,this.getDefaultOption()),e.type=i(t,e),a&&So(e,s,a)},optionUpdated:function(){var t=this.option;"category"===t.type&&(this.__ordinalMeta=Bh.createByAxisModel(this))},getCategories:function(t){var e=this.option;return"category"===e.type?t?e.data:this.__ordinalMeta.categories:void 0},getOrdinalMeta:function(){return this.__ordinalMeta},defaultOption:a([{},Nb[o+"Axis"],n],!0)})}),yx.registerSubTypeDefaulter(t+"Axis",x(i,t))},Wb=yx.extend({type:"cartesian2dAxis",axis:null,init:function(){Wb.superApply(this,"init",arguments),this.resetRange()},mergeOption:function(){Wb.superApply(this,"mergeOption",arguments),this.resetRange()},restoreData:function(){Wb.superApply(this,"restoreData",arguments),this.resetRange()},getCoordSysModel:function(){return this.ecModel.queryComponents({mainType:"grid",index:this.option.gridIndex,id:this.option.gridId})[0]}});r(Wb.prototype,vb);var Gb={offset:0};Vb("x",Wb,Wu,Gb),Vb("y",Wb,Wu,Gb),yx.extend({type:"grid",dependencies:["xAxis","yAxis"],layoutMode:"box",coordinateSystem:null,defaultOption:{show:!1,zlevel:0,z:0,left:"10%",top:60,right:"10%",bottom:60,containLabel:!1,backgroundColor:"rgba(0,0,0,0)",borderWidth:1,borderColor:"#ccc"}});var Hb=Hu.prototype;Hb.type="grid",Hb.axisPointerEnabled=!0,Hb.getRect=function(){return this._rect},Hb.update=function(t,e){var i=this._axesMap;this._updateScale(t,this.model),f(i.x,function(t){au(t.scale,t.model)}),f(i.y,function(t){au(t.scale,t.model)});var n={};f(i.x,function(t){Zu(i,"y",t,n)}),f(i.y,function(t){Zu(i,"x",t,n)}),this.resize(this.model,e)},Hb.resize=function(t,e,i){function n(){f(a,function(t){var e=t.isHorizontal(),i=e?[0,r.width]:[0,r.height],n=t.inverse?1:0;t.setExtent(i[n],i[1-n]),Yu(t,e?r.x:r.y)})}var r=bo(t.getBoxLayoutParams(),{width:e.getWidth(),height:e.getHeight()});this._rect=r;var a=this._axesList;n(),!i&&t.get("containLabel")&&(f(a,function(t){if(!t.model.get("axisLabel.inside")){var e=uu(t);if(e){var i=t.isHorizontal()?"height":"width",n=t.model.get("axisLabel.margin");r[i]-=e[i]+n,"top"===t.position?r.y+=e.height+n:"left"===t.position&&(r.x+=e.width+n)}}}),n())},Hb.getAxis=function(t,e){var i=this._axesMap[t];if(null!=i){if(null==e)for(var n in i)if(i.hasOwnProperty(n))return i[n];return i[e]}},Hb.getAxes=function(){return this._axesList.slice()},Hb.getCartesian=function(t,e){if(null!=t&&null!=e){var i="x"+t+"y"+e;return this._coordsMap[i]}S(t)&&(e=t.yAxisIndex,t=t.xAxisIndex);for(var n=0,r=this._coordsList;nt&&(t=e),t}});var Yb=dm([["fill","color"],["stroke","borderColor"],["lineWidth","borderWidth"],["stroke","barBorderColor"],["lineWidth","barBorderWidth"],["opacity"],["shadowBlur"],["shadowOffsetX"],["shadowOffsetY"],["shadowColor"]]),jb={getBarItemStyle:function(t){var e=Yb(this,t);if(this.getBorderLineDash){var i=this.getBorderLineDash();i&&(e.lineDash=i)}return e}},qb=["itemStyle","barBorderWidth"];o(Wa.prototype,jb),ah({type:"bar",render:function(t,e,i){this._updateDrawMode(t);var n=t.get("coordinateSystem");return("cartesian2d"===n||"polar"===n)&&(this._isLargeDraw?this._renderLarge(t,e,i):this._renderNormal(t,e,i)),this.group},incrementalPrepareRender:function(t){this._clear(),this._updateDrawMode(t)},incrementalRender:function(t,e){this._incrementalRenderLarge(t,e)},_updateDrawMode:function(t){var e=t.pipelineContext.large;(null==this._isLargeDraw||e^this._isLargeDraw)&&(this._isLargeDraw=e,this._clear())},_renderNormal:function(t){var e,i=this.group,n=t.getData(),r=this._data,a=t.coordinateSystem,o=a.getBaseAxis();"cartesian2d"===a.type?e=o.isHorizontal():"polar"===a.type&&(e="angle"===o.dim);var s=t.isAnimationEnabled()?t:null;n.diff(r).add(function(r){if(n.hasValue(r)){var o=n.getItemModel(r),l=$b[a.type](n,r,o),h=Ub[a.type](n,r,o,l,e,s);n.setItemGraphicEl(r,h),i.add(h),tc(h,n,r,o,l,t,e,"polar"===a.type)}}).update(function(o,l){var h=r.getItemGraphicEl(l);if(!n.hasValue(o))return void i.remove(h);var u=n.getItemModel(o),c=$b[a.type](n,o,u);h?La(h,{shape:c},s,o):h=Ub[a.type](n,o,u,c,e,s,!0),n.setItemGraphicEl(o,h),i.add(h),tc(h,n,o,u,c,t,e,"polar"===a.type)}).remove(function(t){var e=r.getItemGraphicEl(t);"cartesian2d"===a.type?e&&Qu(t,s,e):e&&Ju(t,s,e)}).execute(),this._data=n},_renderLarge:function(t){this._clear(),ic(t,this.group)},_incrementalRenderLarge:function(t,e){ic(e,this.group,!0)},dispose:V,remove:function(t){this._clear(t)},_clear:function(t){var e=this.group,i=this._data;t&&t.get("animation")&&i&&!this._isLargeDraw?i.eachItemGraphicEl(function(e){"sector"===e.type?Ju(e.dataIndex,t,e):Qu(e.dataIndex,t,e)}):e.removeAll(),this._data=null}});var Ub={cartesian2d:function(t,e,i,n,r,a,s){var l=new Dy({shape:o({},n)});if(a){var h=l.shape,u=r?"height":"width",c={};h[u]=0,c[u]=n[u],Yy[s?"updateProps":"initProps"](l,{shape:c},a,e)}return l},polar:function(t,e,i,n,r,a,o){var l=n.startAngle0?1:-1,o=n.height>0?1:-1;return{x:n.x+a*r/2,y:n.y+o*r/2,width:n.width-a*r,height:n.height-o*r}},polar:function(t,e){var i=t.getItemLayout(e);return{cx:i.cx,cy:i.cy,r0:i.r0,r:i.r,startAngle:i.startAngle,endAngle:i.endAngle}}},Kb=Fr.extend({type:"largeBar",shape:{points:[]},buildPath:function(t,e){for(var i=e.points,n=this.__startPoint,r=this.__valueIdx,a=0;ah[1]?-1:1,c=["start"===r?h[0]-u*l:"end"===r?h[1]+u*l:(h[0]+h[1])/2,uc(r)?t.labelOffset+a*l:0],d=e.get("nameRotate");null!=d&&(d=d*Qb/180);var f;uc(r)?n=eS(t.rotation,null!=d?d:t.rotation,a):(n=ac(t,r,d||0,h),f=t.axisNameAvailableWidth,null!=f&&(f=Math.abs(f/Math.sin(n.rotation)),!isFinite(f)&&(f=null)));var p=s.getFont(),g=e.get("nameTruncate",!0)||{},v=g.ellipsis,m=A(t.nameTruncateMaxWidth,g.maxWidth,f),y=null!=v&&null!=m?hx(i,m,p,v,{minChar:2,placeholder:g.placeholder}):i,x=e.get("tooltip",!0),_=e.mainType,w={componentType:_,name:i,$vars:["name"]};w[_+"Index"]=e.componentIndex;var b=new xy({anid:"name",__fullText:i,__truncatedText:y,position:c,rotation:n.rotation,silent:oc(e),z2:1,tooltip:x&&x.show?o({content:i,formatter:function(){return i},formatterParams:w},x):null});ba(b.style,s,{text:y,textFont:p,textFill:s.getTextColor()||e.get("axisLine.lineStyle.color"),textAlign:n.textAlign,textVerticalAlign:n.textVerticalAlign}),e.get("triggerEvent")&&(b.eventData=rc(e),b.eventData.targetType="axisName",b.eventData.name=i),this._dumbGroup.add(b),b.updateTransform(),this.group.add(b),b.decomposeTransform()}}},eS=Jb.innerTextLayout=function(t,e,i){var n,r,a=io(e-t);return no(a)?(r=i>0?"top":"bottom",n="center"):no(a-Qb)?(r=i>0?"bottom":"top",n="center"):(r="middle",n=a>0&&Qb>a?i>0?"right":"left":i>0?"left":"right"),{rotation:a,textAlign:n,textVerticalAlign:r}},iS=f,nS=x,rS=nh({type:"axis",_axisPointer:null,axisPointerClass:null,render:function(t,e,i,n){this.axisPointerClass&&xc(t),rS.superApply(this,"render",arguments),Mc(this,t,e,i,n,!0)},updateAxisPointer:function(t,e,i,n){Mc(this,t,e,i,n,!1)},remove:function(t,e){var i=this._axisPointer;i&&i.remove(e),rS.superApply(this,"remove",arguments)},dispose:function(t,e){Ic(this,e),rS.superApply(this,"dispose",arguments)}}),aS=[];rS.registerAxisPointerClass=function(t,e){aS[t]=e},rS.getAxisPointerClass=function(t){return t&&aS[t]};var oS=["axisLine","axisTickLabel","axisName"],sS=["splitArea","splitLine"],lS=rS.extend({type:"cartesianAxis",axisPointerClass:"CartesianAxisPointer",render:function(t,e,i,n){this.group.removeAll();var r=this._axisGroup;if(this._axisGroup=new lv,this.group.add(this._axisGroup),t.get("show")){var a=t.getCoordSysModel(),o=Tc(a,t),s=new Jb(t,o);f(oS,s.add,s),this._axisGroup.add(s.getGroup()),f(sS,function(e){t.get(e+".show")&&this["_"+e](t,a)},this),Ba(r,this._axisGroup,t),lS.superCall(this,"render",t,e,i,n)}},remove:function(){this._splitAreaColors=null},_splitLine:function(t,e){var i=t.axis;if(!i.scale.isBlank()){var n=t.getModel("splitLine"),r=n.getModel("lineStyle"),a=r.get("color");a=_(a)?a:[a];for(var o=e.coordinateSystem.getRect(),l=i.isHorizontal(),h=0,u=i.getTicksCoords({tickModel:n}),c=[],d=[],f=r.getLineStyle(),p=0;p0&&Gc(i[r-1]);r--);for(;r>n&&Gc(i[n]);n++);}for(;r>n;)n+=Hc(t,i,n,r,r,1,a.min,a.max,e.smooth,e.smoothMonotone,e.connectNulls)+1}}),IS=Fr.extend({type:"ec-polygon",shape:{points:[],stackedOnPoints:[],smooth:0,stackedOnSmooth:0,smoothConstraint:!0,smoothMonotone:null,connectNulls:!1},brush:by(Fr.prototype.brush),buildPath:function(t,e){var i=e.points,n=e.stackedOnPoints,r=0,a=i.length,o=e.smoothMonotone,s=Yc(i,e.smoothConstraint),l=Yc(n,e.smoothConstraint);if(e.connectNulls){for(;a>0&&Gc(i[a-1]);a--);for(;a>r&&Gc(i[r]);r++);}for(;a>r;){var h=Hc(t,i,r,a,a,1,s.min,s.max,e.smooth,o,e.connectNulls);Hc(t,n,r+h-1,h,a,-1,l.min,l.max,e.stackedOnSmooth,o,e.connectNulls),r+=h+1,t.closePath()}}});Bs.extend({type:"line",init:function(){var t=new lv,e=new zc;this.group.add(e.group),this._symbolDraw=e,this._lineGroup=t},render:function(t,e,i){var n=t.coordinateSystem,r=this.group,a=t.getData(),o=t.getModel("lineStyle"),l=t.getModel("areaStyle"),h=a.mapArray(a.getItemLayout),u="polar"===n.type,c=this._coordSys,d=this._symbolDraw,f=this._polyline,p=this._polygon,g=this._lineGroup,v=t.get("animation"),m=!l.isEmpty(),y=l.get("origin"),x=Nc(n,a,y),_=$c(n,a,x),w=t.get("showSymbol"),b=w&&!u&&id(t,a,n),S=this._data;S&&S.eachItemGraphicEl(function(t,e){t.__temp&&(r.remove(t),S.setItemGraphicEl(e,null))}),w||d.remove(),r.add(g);var M=!u&&t.get("step");f&&c.type===n.type&&M===this._step?(m&&!p?p=this._newPolygon(h,_,n,v):p&&!m&&(g.remove(p),p=this._polygon=null),g.setClipPath(Jc(n,!1,!1,t)),w&&d.updateData(a,{isIgnore:b,clipShape:Jc(n,!1,!0,t)}),a.eachItemGraphicEl(function(t){t.stopAnimation(!0)}),jc(this._stackedOnPoints,_)&&jc(this._points,h)||(v?this._updateAnimation(a,_,n,i,M,y):(M&&(h=td(h,n,M),_=td(_,n,M)),f.setShape({points:h}),p&&p.setShape({points:h,stackedOnPoints:_})))):(w&&d.updateData(a,{isIgnore:b,clipShape:Jc(n,!1,!0,t)}),M&&(h=td(h,n,M),_=td(_,n,M)),f=this._newPolyline(h,n,v),m&&(p=this._newPolygon(h,_,n,v)),g.setClipPath(Jc(n,!0,!1,t)));var I=ed(a,n)||a.getVisual("color");f.useStyle(s(o.getLineStyle(),{fill:"none",stroke:I,lineJoin:"bevel"}));var T=t.get("smooth");if(T=qc(t.get("smooth")),f.setShape({smooth:T,smoothMonotone:t.get("smoothMonotone"),connectNulls:t.get("connectNulls")}),p){var C=a.getCalculationInfo("stackedOnSeries"),A=0;p.useStyle(s(l.getAreaStyle(),{fill:I,opacity:.7,lineJoin:"bevel"})),C&&(A=qc(C.get("smooth"))),p.setShape({smooth:T,stackedOnSmooth:A,smoothMonotone:t.get("smoothMonotone"),connectNulls:t.get("connectNulls")})}this._data=a,this._coordSys=n,this._stackedOnPoints=_,this._points=h,this._step=M,this._valueOrigin=y},dispose:function(){},highlight:function(t,e,i,n){var r=t.getData(),a=Yn(r,n);if(!(a instanceof Array)&&null!=a&&a>=0){var o=r.getItemGraphicEl(a);if(!o){var s=r.getItemLayout(a);if(!s)return;o=new Cc(r,a),o.position=s,o.setZ(t.get("zlevel"),t.get("z")),o.ignore=isNaN(s[0])||isNaN(s[1]),o.__temp=!0,r.setItemGraphicEl(a,o),o.stopSymbolAnimation(!0),this.group.add(o)}o.highlight()}else Bs.prototype.highlight.call(this,t,e,i,n)},downplay:function(t,e,i,n){var r=t.getData(),a=Yn(r,n);if(null!=a&&a>=0){var o=r.getItemGraphicEl(a);o&&(o.__temp?(r.setItemGraphicEl(a,null),this.group.remove(o)):o.downplay())}else Bs.prototype.downplay.call(this,t,e,i,n)},_newPolyline:function(t){var e=this._polyline;return e&&this._lineGroup.remove(e),e=new MS({shape:{points:t},silent:!0,z2:10}),this._lineGroup.add(e),this._polyline=e,e},_newPolygon:function(t,e){var i=this._polygon;return i&&this._lineGroup.remove(i),i=new IS({shape:{points:t,stackedOnPoints:e},silent:!0}),this._lineGroup.add(i),this._polygon=i,i},_updateAnimation:function(t,e,i,n,r,a){var o=this._polyline,s=this._polygon,l=t.hostModel,h=vS(this._data,t,this._stackedOnPoints,e,this._coordSys,i,this._valueOrigin,a),u=h.current,c=h.stackedOnCurrent,d=h.next,f=h.stackedOnNext;r&&(u=td(h.current,i,r),c=td(h.stackedOnCurrent,i,r),d=td(h.next,i,r),f=td(h.stackedOnNext,i,r)),o.shape.__points=h.current,o.shape.points=u,La(o,{shape:{points:d}},l),s&&(s.setShape({points:u,stackedOnPoints:c}),La(s,{shape:{points:d,stackedOnPoints:f}},l));for(var p=[],g=h.status,v=0;ve&&(e=t[i]);return isFinite(e)?e:0/0},min:function(t){for(var e=1/0,i=0;i1){var h;"string"==typeof i?h=AS[i]:"function"==typeof i&&(h=i),h&&t.setData(e.downSample(e.mapDimension(a.dim),1/l,h,DS))}}}}};Jl(TS("line","circle","line")),Ql(CS("line")),jl(ow.PROCESSOR.STATISTIC,kS("line"));var PS=function(t,e,i){e=_(e)&&{coordDimensions:e}||o({},e);var n=t.getSource(),r=Vw(n,e),a=new Bw(r,t);return a.initData(n,i),a},LS={updateSelectedMap:function(t){this._targetList=_(t)?t.slice():[],this._selectTargetMap=g(t||[],function(t,e){return t.set(e.name,e),t},N())},select:function(t,e){var i=null!=e?this._targetList[e]:this._selectTargetMap.get(t),n=this.get("selectedMode");"single"===n&&this._selectTargetMap.each(function(t){t.selected=!1}),i&&(i.selected=!0)},unSelect:function(t,e){var i=null!=e?this._targetList[e]:this._selectTargetMap.get(t);i&&(i.selected=!1)},toggleSelected:function(t,e){var i=null!=e?this._targetList[e]:this._selectTargetMap.get(t);return null!=i?(this[i.selected?"unSelect":"select"](t,e),i.selected):void 0},isSelected:function(t,e){var i=null!=e?this._targetList[e]:this._selectTargetMap.get(t);return i&&i.selected}},OS=rh({type:"series.pie",init:function(t){OS.superApply(this,"init",arguments),this.legendDataProvider=function(){return this.getRawData()},this.updateSelectedMap(this._createSelectableList()),this._defaultLabelLine(t)},mergeOption:function(t){OS.superCall(this,"mergeOption",t),this.updateSelectedMap(this._createSelectableList())},getInitialData:function(){return PS(this,["value"])},_createSelectableList:function(){for(var t=this.getRawData(),e=t.mapDimension("value"),i=[],n=0,r=t.count();r>n;n++)i.push({name:t.getName(n),value:t.get(e,n),selected:Ms(t,n,"selected")});return i},getDataParams:function(t){var e=this.getData(),i=OS.superCall(this,"getDataParams",t),n=[];return e.each(e.mapDimension("value"),function(t){n.push(t)}),i.percent=eo(n,t,e.hostModel.get("percentPrecision")),i.$vars.push("percent"),i},_defaultLabelLine:function(t){Fn(t,"labelLine",["show"]);var e=t.labelLine,i=t.emphasis.labelLine;e.show=e.show&&t.label.show,i.show=i.show&&t.emphasis.label.show},defaultOption:{zlevel:0,z:2,legendHoverLink:!0,hoverAnimation:!0,center:["50%","50%"],radius:[0,"75%"],clockwise:!0,startAngle:90,minAngle:0,selectedOffset:10,hoverOffset:10,avoidLabelOverlap:!0,percentPrecision:2,stillShowZeroSum:!0,label:{rotate:!1,show:!0,position:"outer"},labelLine:{show:!0,length:15,length2:15,smooth:!1,lineStyle:{width:1,type:"solid"}},itemStyle:{borderWidth:1},animationType:"expansion",animationEasing:"cubicOut"}});c(OS,LS);var zS=od.prototype;zS.updateData=function(t,e,i){function n(){a.stopAnimation(!0),a.animateTo({shape:{r:u.r+l.get("hoverOffset")}},300,"elasticOut")}function r(){a.stopAnimation(!0),a.animateTo({shape:{r:u.r}},300,"elasticOut")}var a=this.childAt(0),l=t.hostModel,h=t.getItemModel(e),u=t.getItemLayout(e),c=o({},u);if(c.label=null,i){a.setShape(c);var d=l.getShallow("animationType");"scale"===d?(a.shape.r=u.r0,Oa(a,{shape:{r:u.r}},l,e)):(a.shape.endAngle=u.startAngle,La(a,{shape:{endAngle:u.endAngle}},l,e))}else La(a,{shape:c},l,e);var f=t.getItemVisual(e,"color");a.useStyle(s({lineJoin:"bevel",fill:f},h.getModel("itemStyle").getItemStyle())),a.hoverStyle=h.getModel("emphasis.itemStyle").getItemStyle();var p=h.getShallow("cursor");p&&a.attr("cursor",p),ad(this,t.getItemLayout(e),l.isSelected(null,e),l.get("selectedOffset"),l.get("animation")),a.off("mouseover").off("mouseout").off("emphasis").off("normal"),h.get("hoverAnimation")&&l.isAnimationEnabled()&&a.on("mouseover",n).on("mouseout",r).on("emphasis",n).on("normal",r),this._updateLabel(t,e),xa(this)},zS._updateLabel=function(t,e){var i=this.childAt(1),n=this.childAt(2),r=t.hostModel,a=t.getItemModel(e),o=t.getItemLayout(e),s=o.label,l=t.getItemVisual(e,"color");La(i,{shape:{points:s.linePoints||[[s.x,s.y],[s.x,s.y],[s.x,s.y]]}},r,e),La(n,{style:{x:s.x,y:s.y}},r,e),n.attr({rotation:s.rotation,origin:[s.x,s.y],z2:10});var h=a.getModel("label"),u=a.getModel("emphasis.label"),c=a.getModel("labelLine"),d=a.getModel("emphasis.labelLine"),l=t.getItemVisual(e,"color");wa(n.style,n.hoverStyle={},h,u,{labelFetcher:t.hostModel,labelDataIndex:e,defaultText:t.getName(e),autoColor:l,useInsideStyle:!!s.inside},{textAlign:s.textAlign,textVerticalAlign:s.verticalAlign,opacity:t.getItemVisual(e,"opacity")}),n.ignore=n.normalIgnore=!h.get("show"),n.hoverIgnore=!u.get("show"),i.ignore=i.normalIgnore=!c.get("show"),i.hoverIgnore=!d.get("show"),i.setStyle({stroke:l,opacity:t.getItemVisual(e,"opacity")}),i.setStyle(c.getModel("lineStyle").getLineStyle()),i.hoverStyle=d.getModel("lineStyle").getLineStyle();var f=c.get("smooth");f&&f===!0&&(f=.4),i.setShape({smooth:f})},u(od,lv);var ES=(Bs.extend({type:"pie",init:function(){var t=new lv;this._sectorGroup=t},render:function(t,e,i,n){if(!n||n.from!==this.uid){var r=t.getData(),a=this._data,o=this.group,s=e.get("animation"),l=!a,h=t.get("animationType"),u=x(rd,this.uid,t,s,i),c=t.get("selectedMode");if(r.diff(a).add(function(t){var e=new od(r,t);l&&"scale"!==h&&e.eachChild(function(t){t.stopAnimation(!0)}),c&&e.on("click",u),r.setItemGraphicEl(t,e),o.add(e)}).update(function(t,e){var i=a.getItemGraphicEl(e);i.updateData(r,t),i.off("click"),c&&i.on("click",u),o.add(i),r.setItemGraphicEl(t,i)}).remove(function(t){var e=a.getItemGraphicEl(t);o.remove(e)}).execute(),s&&l&&r.count()>0&&"scale"!==h){var d=r.getItemLayout(0),f=Math.max(i.getWidth(),i.getHeight())/2,p=y(o.removeClipPath,o);o.setClipPath(this._createClipPath(d.cx,d.cy,f,d.startAngle,d.clockwise,p,t))}else o.removeClipPath();this._data=r}},dispose:function(){},_createClipPath:function(t,e,i,n,r,a,o){var s=new Sy({shape:{cx:t,cy:e,r0:0,r:i,startAngle:n,endAngle:n,clockwise:r}});return Oa(s,{shape:{endAngle:n+(r?1:-1)*Math.PI*2}},o,a),s},containPoint:function(t,e){var i=e.getData(),n=i.getItemLayout(0);if(n){var r=t[0]-n.cx,a=t[1]-n.cy,o=Math.sqrt(r*r+a*a);return o<=n.r&&o>=n.r0}}}),function(t,e){f(e,function(e){e.update="updateView",Ul(e,function(i,n){var r={};return n.eachComponent({mainType:"series",subType:t,query:i},function(t){t[e.method]&&t[e.method](i.name,i.dataIndex);var n=t.getData();n.each(function(e){var i=n.getName(e);r[i]=t.isSelected(i)||!1})}),{name:i.name,selected:r}})})}),RS=function(t){return{getTargetSeries:function(e){var i={},n=N();return e.eachSeriesByType(t,function(t){t.__paletteScope=i,n.set(t.uid,t)}),n},reset:function(t){var e=t.getRawData(),i={},n=t.getData();n.each(function(t){var e=n.getRawIndex(t);i[e]=t}),e.each(function(r){var a=i[r],o=null!=a&&n.getItemVisual(a,"color",!0);if(o)e.setItemVisual(r,"color",o);else{var s=e.getItemModel(r),l=s.get("itemStyle.color")||t.getColorFromPalette(e.getName(r)||r+"",t.__paletteScope,e.count());e.setItemVisual(r,"color",l),null!=a&&n.setItemVisual(a,"color",l)}})}}},BS=function(t,e,i,n){var r,a,o=t.getData(),s=[],l=!1;o.each(function(i){var n,h,u,c,d=o.getItemLayout(i),f=o.getItemModel(i),p=f.getModel("label"),g=p.get("position")||f.get("emphasis.label.position"),v=f.getModel("labelLine"),m=v.get("length"),y=v.get("length2"),x=(d.startAngle+d.endAngle)/2,_=Math.cos(x),w=Math.sin(x);r=d.cx,a=d.cy;var b="inside"===g||"inner"===g;if("center"===g)n=d.cx,h=d.cy,c="center";else{var S=(b?(d.r+d.r0)/2*_:d.r*_)+r,M=(b?(d.r+d.r0)/2*w:d.r*w)+a;if(n=S+3*_,h=M+3*w,!b){var I=S+_*(m+e-d.r),T=M+w*(m+e-d.r),C=I+(0>_?-1:1)*y,A=T;n=C+(0>_?-5:5),h=A,u=[[S,M],[I,T],[C,A]]}c=b?"center":_>0?"left":"right"}var D=p.getFont(),k=p.get("rotate")?0>_?-x+Math.PI:-x:0,P=t.getFormattedLabel(i,"normal")||o.getName(i),L=Ei(P,D,c,"top");l=!!k,d.label={x:n,y:h,position:g,height:L.height,len:m,len2:y,linePoints:u,textAlign:c,verticalAlign:"middle",rotation:k,inside:b},b||s.push(d.label)}),!l&&t.get("avoidLabelOverlap")&&ld(s,r,a,e,i,n)},NS=2*Math.PI,FS=Math.PI/180,VS=function(t,e,i){e.eachSeriesByType(t,function(t){var e=t.getData(),n=e.mapDimension("value"),r=t.get("center"),a=t.get("radius");_(a)||(a=[0,a]),_(r)||(r=[r,r]);var o=i.getWidth(),s=i.getHeight(),l=Math.min(o,s),h=Ua(r[0],o),u=Ua(r[1],s),c=Ua(a[0],l/2),d=Ua(a[1],l/2),f=-t.get("startAngle")*FS,p=t.get("minAngle")*FS,g=0;e.each(n,function(t){!isNaN(t)&&g++});var v=e.getSum(n),m=Math.PI/(v||g)*2,y=t.get("clockwise"),x=t.get("roseType"),w=t.get("stillShowZeroSum"),b=e.getDataExtent(n);b[0]=0;var S=NS,M=0,I=f,T=y?1:-1;if(e.each(n,function(t,i){var n;if(isNaN(t))return void e.setItemLayout(i,{angle:0/0,startAngle:0/0,endAngle:0/0,clockwise:y,cx:h,cy:u,r0:c,r:x?0/0:d});n="area"!==x?0===v&&w?m:t*m:NS/g,p>n?(n=p,S-=p):M+=t;var r=I+T*n;e.setItemLayout(i,{angle:n,startAngle:I,endAngle:r,clockwise:y,cx:h,cy:u,r0:c,r:x?qa(t,b,[c,d]):d}),I=r}),NS>S&&g)if(.001>=S){var C=NS/g;e.each(n,function(t,i){if(!isNaN(t)){var n=e.getItemLayout(i);n.angle=C,n.startAngle=f+T*i*C,n.endAngle=f+T*(i+1)*C}})}else m=S/M,I=f,e.each(n,function(t,i){if(!isNaN(t)){var n=e.getItemLayout(i),r=n.angle===p?p:t*m;n.startAngle=I,n.endAngle=I+T*r,I+=T*r}});BS(t,d,o,s)})},WS=function(t){return{seriesType:t,reset:function(t,e){var i=e.findComponents({mainType:"legend"});if(i&&i.length){var n=t.getData();n.filterSelf(function(t){for(var e=n.getName(t),r=0;rn[1]&&n.reverse(),{coordSys:{type:"polar",cx:t.cx,cy:t.cy,r:n[1],r0:n[0]},api:{coord:y(function(n){var r=e.dataToRadius(n[0]),a=i.dataToAngle(n[1]),o=t.coordToPoint([r,a]);return o.push(r,a*Math.PI/180),o}),size:y(dd,t)}}},YS=function(t){var e=t.getRect(),i=t.getRangeInfo();return{coordSys:{type:"calendar",x:e.x,y:e.y,width:e.width,height:e.height,cellWidth:t.getCellWidth(),cellHeight:t.getCellHeight(),rangeInfo:{start:i.start,end:i.end,weeks:i.weeks,dayCount:i.allDay}},api:{coord:function(e,i){return t.dataToPoint(e,i)}}}},jS=["itemStyle"],qS=["emphasis","itemStyle"],US=["label"],$S=["emphasis","label"],KS="e\x00\x00",QS={cartesian2d:GS,geo:HS,singleAxis:ZS,polar:XS,calendar:YS};o_.extend({type:"series.custom",dependencies:["grid","polar","geo","singleAxis","calendar"],defaultOption:{coordinateSystem:"cartesian2d",zlevel:0,z:2,legendHoverLink:!0,useTransform:!0},getInitialData:function(){return Oh(this.getSource(),this)},getDataParams:function(t,e,i){var n=o_.prototype.getDataParams.apply(this,arguments);return i&&(n.info=i.info),n}}),Bs.extend({type:"custom",_data:null,render:function(t,e,i,n){var r=this._data,a=t.getData(),o=this.group,s=vd(t,a,e,i);a.diff(r).add(function(e){yd(null,e,s(e,n),t,o,a)}).update(function(e,i){var l=r.getItemGraphicEl(i);yd(l,e,s(e,n),t,o,a)}).remove(function(t){var e=r.getItemGraphicEl(t);e&&o.remove(e)}).execute(),this._data=a},incrementalPrepareRender:function(){this.group.removeAll(),this._data=null},incrementalRender:function(t,e,i,n,r){function a(t){t.isGroup||(t.incremental=!0,t.useHoverLayer=!0)}for(var o=e.getData(),s=vd(e,o,i,n),l=t.start;l=0},defaultOption:{zlevel:0,z:4,show:!0,orient:"horizontal",left:"center",top:0,align:"auto",backgroundColor:"rgba(0,0,0,0)",borderColor:"#ccc",borderRadius:0,borderWidth:0,padding:5,itemGap:10,itemWidth:25,itemHeight:14,inactiveColor:"#ccc",textStyle:{color:"#333"},selectedMode:!0,tooltip:{show:!1}}});Ul("legendToggleSelect","legendselectchanged",x(Ad,"toggleSelected")),Ul("legendSelect","legendselected",x(Ad,"select")),Ul("legendUnSelect","legendunselected",x(Ad,"unSelect"));var tM=x,eM=f,iM=lv,nM=nh({type:"legend.plain",newlineDisabled:!1,init:function(){this.group.add(this._contentGroup=new iM),this._backgroundEl},getContentGroup:function(){return this._contentGroup},render:function(t,e,i){if(this.resetInner(),t.get("show",!0)){var n=t.get("align");n&&"auto"!==n||(n="right"===t.get("left")&&"vertical"===t.get("orient")?"right":"left"),this.renderInner(n,t,e,i);var r=t.getBoxLayoutParams(),a={width:i.getWidth(),height:i.getHeight()},o=t.get("padding"),l=bo(r,a,o),h=this.layoutInner(t,n,l),u=bo(s({width:h.width,height:h.height},r),a,o);this.group.attr("position",[u.x-h.x,u.y-h.y]),this.group.add(this._backgroundEl=Dd(h,t))}},resetInner:function(){this.getContentGroup().removeAll(),this._backgroundEl&&this.group.remove(this._backgroundEl)},renderInner:function(t,e,i,n){var r=this.getContentGroup(),a=N(),o=e.get("selectedMode"),s=[];i.eachRawSeries(function(t){!t.get("legendHoverLink")&&s.push(t.id)}),eM(e.getData(),function(l,h){var u=l.get("name");if(!this.newlineDisabled&&(""===u||"\n"===u))return void r.add(new iM({newline:!0}));var c=i.getSeriesByName(u)[0];if(!a.get(u))if(c){var d=c.getData(),f=d.getVisual("color");"function"==typeof f&&(f=f(c.getDataParams(0)));var p=d.getVisual("legendSymbol")||"roundRect",g=d.getVisual("symbol"),v=this._createItem(u,h,l,e,p,g,t,f,o);v.on("click",tM(kd,u,n)).on("mouseover",tM(Pd,c.name,null,n,s)).on("mouseout",tM(Ld,c.name,null,n,s)),a.set(u,!0)}else i.eachRawSeries(function(i){if(!a.get(u)&&i.legendDataProvider){var r=i.legendDataProvider(),c=r.indexOfName(u);if(0>c)return;var d=r.getItemVisual(c,"color"),f="roundRect",p=this._createItem(u,h,l,e,f,null,t,d,o);p.on("click",tM(kd,u,n)).on("mouseover",tM(Pd,null,u,n,s)).on("mouseout",tM(Ld,null,u,n,s)),a.set(u,!0)}},this)},this)},_createItem:function(t,e,i,n,r,a,s,l,h){var u=n.get("itemWidth"),c=n.get("itemHeight"),d=n.get("inactiveColor"),f=n.get("symbolKeepAspect"),p=n.isSelected(t),g=new iM,v=i.getModel("textStyle"),m=i.get("icon"),y=i.getModel("tooltip"),x=y.parentModel;if(r=m||r,g.add(fu(r,0,0,u,c,p?l:d,null==f?!0:f)),!m&&a&&(a!==r||"none"===a)){var _=.8*c;"none"===a&&(a="circle"),g.add(fu(a,(u-_)/2,(c-_)/2,_,_,p?l:d,null==f?!0:f))}var w="left"===s?u+5:-5,b=s,S=n.get("formatter"),M=t;"string"==typeof S&&S?M=S.replace("{name}",null!=t?t:""):"function"==typeof S&&(M=S(t)),g.add(new xy({style:ba({},v,{text:M,x:w,y:c/2,textFill:p?v.getTextColor():d,textAlign:b,textVerticalAlign:"middle"})}));var I=new Dy({shape:g.getBoundingRect(),invisible:!0,tooltip:y.get("show")?o({content:t,formatter:x.get("formatter",!0)||function(){return t},formatterParams:{componentType:"legend",legendIndex:n.componentIndex,name:t,$vars:["name"]}},y.option):null});return g.add(I),g.eachChild(function(t){t.silent=!0}),I.silent=!h,this.getContentGroup().add(g),xa(g),g.__legendDataIndex=e,g},layoutInner:function(t,e,i){var n=this.getContentGroup();gx(t.get("orient"),n,t.get("itemGap"),i.width,i.height);var r=n.getBoundingRect();return n.attr("position",[-r.x,-r.y]),this.group.getBoundingRect()}}),rM=function(t){var e=t.findComponents({mainType:"legend"});e&&e.length&&t.filterSeries(function(t){for(var i=0;ii[s],f=[-u.x,-u.y];f[o]=n.position[o];var p=[0,0],g=[-c.x,-c.y],v=D(t.get("pageButtonGap",!0),t.get("itemGap",!0));if(d){var m=t.get("pageButtonPosition",!0);"end"===m?g[o]+=i[s]-c[s]:p[o]+=c[s]+v}g[1-o]+=u[l]/2-c[l]/2,n.attr("position",f),r.attr("position",p),a.attr("position",g);var y=this.group.getBoundingRect(),y={x:0,y:0};if(y[s]=d?i[s]:u[s],y[l]=Math.max(u[l],c[l]),y[h]=Math.min(0,c[h]+g[1-o]),r.__rectSize=i[s],d){var x={x:0,y:0};x[s]=Math.max(i[s]-c[s]-v,0),x[l]=y[l],r.setClipPath(new Dy({shape:x})),r.__rectSize=x[s]}else a.eachChild(function(t){t.attr({invisible:!0,silent:!0})});var _=this._getPageInfo(t);return null!=_.pageIndex&&La(n,{position:_.contentPosition},d?t:!1),this._updatePageInfoView(t,_),y},_pageGo:function(t,e,i){var n=this._getPageInfo(e)[t];null!=n&&i.dispatchAction({type:"legendScroll",scrollDataIndex:n,legendId:e.id})},_updatePageInfoView:function(t,e){var i=this._controllerGroup;f(["pagePrev","pageNext"],function(n){var r=null!=e[n+"DataIndex"],a=i.childOfName(n);a&&(a.setStyle("fill",r?t.get("pageIconColor",!0):t.get("pageIconInactiveColor",!0)),a.cursor=r?"pointer":"default")});var n=i.childOfName("pageText"),r=t.get("pageFormatter"),a=e.pageIndex,o=null!=a?a+1:0,s=e.pageCount;n&&r&&n.setStyle("text",b(r)?r.replace("{current}",o).replace("{total}",s):r({current:o,total:s}))},_getPageInfo:function(t){function e(t){var e=t.getBoundingRect().clone();return e[f]+=t.position[u],e}var i,n,r,a,o=t.get("scrollDataIndex",!0),s=this.getContentGroup(),l=s.getBoundingRect(),h=this._containerGroup.__rectSize,u=t.getOrient().index,c=sM[u],d=sM[1-u],f=lM[u],p=s.position.slice();this._showController?s.eachChild(function(t){t.__legendDataIndex===o&&(a=t)}):a=s.childAt(0);var g=h?Math.ceil(l[c]/h):0;if(a){var v=a.getBoundingRect(),m=a.position[u]+v[f];p[u]=-m-l[f],i=Math.floor(g*(m+v[f]+h/2)/l[c]),i=l[c]&&g?Math.max(0,Math.min(g-1,i)):-1;var y={x:0,y:0};y[c]=h,y[d]=l[d],y[f]=-p[u]-l[f];var x,_=s.children();if(s.eachChild(function(t,i){var n=e(t);n.intersect(y)&&(null==x&&(x=i),r=t.__legendDataIndex),i===_.length-1&&n[f]+n[c]<=y[f]+y[c]&&(r=null)}),null!=x){var w=_[x],b=e(w);if(y[f]=b[f]+b[c]-y[c],0>=x&&b[f]>=y[f])n=null;else{for(;x>0&&e(_[x-1]).intersect(y);)x--;n=_[x].__legendDataIndex}}}return{contentPosition:p,pageIndex:i,pageCount:g,pagePrevDataIndex:n,pageNextDataIndex:r}}});Ul("legendScroll","legendscroll",function(t,e){var i=t.scrollDataIndex;null!=i&&e.eachComponent({mainType:"legend",subType:"scroll",query:t},function(t){t.setScrollDataIndex(i)})});var uM=function(t,e){var i,n=[],r=t.seriesIndex;if(null==r||!(i=e.getSeriesByIndex(r)))return{point:[]};var a=i.getData(),o=Yn(a,t);if(null==o||0>o||_(o))return{point:[]};var s=a.getItemGraphicEl(o),l=i.coordinateSystem;if(i.getTooltipPosition)n=i.getTooltipPosition(o)||[];else if(l&&l.dataToPoint)n=l.dataToPoint(a.getValues(p(l.dimensions,function(t){return a.mapDimension(t)}),o,!0))||[];else if(s){var h=s.getBoundingRect().clone();h.applyTransform(s.transform),n=[h.x+h.width/2,h.y+h.height/2]}return{point:n,el:s}},cM=f,dM=x,fM=jn(),pM=function(t,e,i){var n=t.currTrigger,r=[t.x,t.y],a=t,o=t.dispatchAction||y(i.dispatchAction,i),s=e.getComponent("axisPointer").coordSysAxesInfo;if(s){Hd(r)&&(r=uM({seriesIndex:a.seriesIndex,dataIndex:a.dataIndex},e).point);var l=Hd(r),h=a.axesInfo,u=s.axesInfo,c="leave"===n||Hd(r),d={},f={},p={list:[],map:{}},g={showPointer:dM(Rd,f),showTooltip:dM(Bd,p)};cM(s.coordSysMap,function(t,e){var i=l||t.containPoint(r);cM(s.coordSysAxesInfo[e],function(t){var e=t.axis,n=Wd(h,t);if(!c&&i&&(!h||n)){var a=n&&n.value;null!=a||l||(a=e.pointToData(r)),null!=a&&zd(t,a,g,!1,d)}})});var v={};return cM(u,function(t,e){var i=t.linkGroup;i&&!f[e]&&cM(i.axesInfo,function(e,n){var r=f[n];if(e!==t&&r){var a=r.value;i.mapper&&(a=t.axis.scale.parse(i.mapper(a,Gd(e),Gd(t)))),v[t.key]=a}})}),cM(v,function(t,e){zd(u[e],t,g,!0,d)}),Nd(f,u,d),Fd(p,r,t,o),Vd(u,o,i),d}},gM=(ih({type:"axisPointer",coordSysAxesInfo:null,defaultOption:{show:"auto",triggerOn:null,zlevel:0,z:50,type:"line",snap:!1,triggerTooltip:!0,value:null,status:null,link:[],animation:null,animationDurationUpdate:200,lineStyle:{color:"#aaa",width:1,type:"solid"},shadowStyle:{color:"rgba(150,150,150,0.3)"},label:{show:!0,formatter:null,precision:"auto",margin:3,color:"#fff",padding:[5,7,5,7],backgroundColor:"auto",borderColor:null,borderWidth:0,shadowBlur:3,shadowColor:"#aaa"},handle:{show:!1,icon:"M10.7,11.9v-1.3H9.3v1.3c-4.9,0.3-8.8,4.4-8.8,9.4c0,5,3.9,9.1,8.8,9.4h1.3c4.9-0.3,8.8-4.4,8.8-9.4C19.5,16.3,15.6,12.2,10.7,11.9z M13.3,24.4H6.7v-1.2h6.6z M13.3,22H6.7v-1.2h6.6z M13.3,19.6H6.7v-1.2h6.6z",size:45,margin:50,color:"#333",shadowBlur:3,shadowColor:"#aaa",shadowOffsetX:0,shadowOffsetY:2,throttle:40}}}),jn()),vM=f,mM=nh({type:"axisPointer",render:function(t,e,i){var n=e.getComponent("tooltip"),r=t.get("triggerOn")||n&&n.get("triggerOn")||"mousemove|click";Zd("axisPointer",i,function(t,e,i){"none"!==r&&("leave"===t||r.indexOf(t)>=0)&&i({type:"updateAxisPointer",currTrigger:t,x:e&&e.offsetX,y:e&&e.offsetY})})},remove:function(t,e){$d(e.getZr(),"axisPointer"),mM.superApply(this._model,"remove",arguments)},dispose:function(t,e){$d("axisPointer",e),mM.superApply(this._model,"dispose",arguments)}}),yM=jn(),xM=n,_M=y;Kd.prototype={_group:null,_lastGraphicKey:null,_handle:null,_dragging:!1,_lastValue:null,_lastStatus:null,_payloadInfo:null,animationThreshold:15,render:function(t,e,i,n){var r=e.get("value"),a=e.get("status");if(this._axisModel=t,this._axisPointerModel=e,this._api=i,n||this._lastValue!==r||this._lastStatus!==a){this._lastValue=r,this._lastStatus=a;var o=this._group,s=this._handle;if(!a||"hide"===a)return o&&o.hide(),void(s&&s.hide());o&&o.show(),s&&s.show();var l={};this.makeElOption(l,r,t,e,i);var h=l.graphicKey;h!==this._lastGraphicKey&&this.clear(i),this._lastGraphicKey=h;var u=this._moveAnimation=this.determineAnimation(t,e);if(o){var c=x(Qd,e,u);this.updatePointerEl(o,l,c,e),this.updateLabelEl(o,l,c,e)}else o=this._group=new lv,this.createPointerEl(o,l,t,e),this.createLabelEl(o,l,t,e),i.getZr().add(o);nf(o,e,!0),this._renderHandle(r)}},remove:function(t){this.clear(t)},dispose:function(t){this.clear(t)},determineAnimation:function(t,e){var i=e.get("animation"),n=t.axis,r="category"===n.type,a=e.get("snap");if(!a&&!r)return!1;if("auto"===i||null==i){var o=this.animationThreshold;if(r&&n.getBandWidth()>o)return!0;if(a){var s=_c(t).seriesDataCount,l=n.getExtent();return Math.abs(l[0]-l[1])/s>o}return!1}return i===!0},makeElOption:function(){},createPointerEl:function(t,e){var i=e.pointer;if(i){var n=yM(t).pointerEl=new Yy[i.type](xM(e.pointer));t.add(n)}},createLabelEl:function(t,e,i,n){if(e.label){var r=yM(t).labelEl=new Dy(xM(e.label));t.add(r),tf(r,n)}},updatePointerEl:function(t,e,i){var n=yM(t).pointerEl;n&&(n.setStyle(e.pointer.style),i(n,{shape:e.pointer.shape}))},updateLabelEl:function(t,e,i,n){var r=yM(t).labelEl;r&&(r.setStyle(e.label.style),i(r,{shape:e.label.shape,position:e.label.position}),tf(r,n))},_renderHandle:function(t){if(!this._dragging&&this.updateHandleTransform){var e=this._axisPointerModel,i=this._api.getZr(),n=this._handle,r=e.getModel("handle"),a=e.get("status");if(!r.get("show")||!a||"hide"===a)return n&&i.remove(n),void(this._handle=null);var o;this._handle||(o=!0,n=this._handle=Va(r.get("icon"),{cursor:"move",draggable:!0,onmousemove:function(t){Ig(t.event)},onmousedown:_M(this._onHandleDragMove,this,0,0),drift:_M(this._onHandleDragMove,this),ondragend:_M(this._onHandleDragEnd,this)}),i.add(n)),nf(n,e,!1);var s=["color","borderColor","borderWidth","opacity","shadowColor","shadowBlur","shadowOffsetX","shadowOffsetY"];n.setStyle(r.getItemStyle(null,s));var l=r.get("size");_(l)||(l=[l,l]),n.attr("scale",[l[0]/2,l[1]/2]),Hs(this,"_doDispatchAxisPointer",r.get("throttle")||0,"fixRate"),this._moveHandleToValue(t,o)}},_moveHandleToValue:function(t,e){Qd(this._axisPointerModel,!e&&this._moveAnimation,this._handle,ef(this.getHandleTransform(t,this._axisModel,this._axisPointerModel)))},_onHandleDragMove:function(t,e){var i=this._handle;if(i){this._dragging=!0;var n=this.updateHandleTransform(ef(i),[t,e],this._axisModel,this._axisPointerModel);this._payloadInfo=n,i.stopAnimation(),i.attr(ef(n)),yM(i).lastProp=null,this._doDispatchAxisPointer()}},_doDispatchAxisPointer:function(){var t=this._handle;if(t){var e=this._payloadInfo,i=this._axisModel;this._api.dispatchAction({type:"updateAxisPointer",x:e.cursorPoint[0],y:e.cursorPoint[1],tooltipOption:e.tooltipOption,axesInfo:[{axisDim:i.axis.dim,axisIndex:i.componentIndex}]})}},_onHandleDragEnd:function(){this._dragging=!1;var t=this._handle;if(t){var e=this._axisPointerModel.get("value");this._moveHandleToValue(e),this._api.dispatchAction({type:"hideTip"})}},getHandleTransform:null,updateHandleTransform:null,clear:function(t){this._lastValue=null,this._lastStatus=null;var e=t.getZr(),i=this._group,n=this._handle;e&&i&&(this._lastGraphicKey=null,i&&e.remove(i),n&&e.remove(n),this._group=null,this._handle=null,this._payloadInfo=null)},doClear:function(){},buildLabel:function(t,e,i){return i=i||0,{x:t[i],y:t[1-i],width:e[i],height:e[1-i]}}},Kd.prototype.constructor=Kd,er(Kd);var wM=Kd.extend({makeElOption:function(t,e,i,n,r){var a=i.axis,o=a.grid,s=n.get("type"),l=df(o,a).getOtherAxis(a).getGlobalExtent(),h=a.toGlobalCoord(a.dataToCoord(e,!0));if(s&&"none"!==s){var u=rf(n),c=bM[s](a,h,l,u);c.style=u,t.graphicKey=c.type,t.pointer=c}var d=Tc(o.model,i);hf(e,t,d,i,n,r)},getHandleTransform:function(t,e,i){var n=Tc(e.axis.grid.model,e,{labelInside:!1});return n.labelMargin=i.get("handle.margin"),{position:lf(e.axis,t,n),rotation:n.rotation+(n.labelDirection<0?Math.PI:0)}},updateHandleTransform:function(t,e,i){var n=i.axis,r=n.grid,a=n.getGlobalExtent(!0),o=df(r,n).getOtherAxis(n).getGlobalExtent(),s="x"===n.dim?0:1,l=t.position;l[s]+=e[s],l[s]=Math.min(a[1],l[s]),l[s]=Math.max(a[0],l[s]);var h=(o[1]+o[0])/2,u=[h,h];u[s]=l[s];var c=[{verticalAlign:"middle"},{align:"center"}];return{position:l,rotation:t.rotation,cursorPoint:u,tooltipOption:c[s]}}}),bM={line:function(t,e,i,n){var r=uf([e,i[0]],[e,i[1]],ff(t));return ia({shape:r,style:n}),{type:"Line",shape:r}},shadow:function(t,e,i){var n=Math.max(1,t.getBandWidth()),r=i[1]-i[0];return{type:"Rect",shape:cf([e-n/2,i[0]],[n,r],ff(t))}}};rS.registerAxisPointerClass("CartesianAxisPointer",wM),Yl(function(t){if(t){(!t.axisPointer||0===t.axisPointer.length)&&(t.axisPointer={});var e=t.axisPointer.link;e&&!_(e)&&(t.axisPointer.link=[e])}}),jl(ow.PROCESSOR.STATISTIC,function(t,e){t.getComponent("axisPointer").coordSysAxesInfo=fc(t,e)}),Ul({type:"updateAxisPointer",event:"updateAxisPointer",update:":updateAxisPointer"},pM),ih({type:"tooltip",dependencies:["axisPointer"],defaultOption:{zlevel:0,z:60,show:!0,showContent:!0,trigger:"item",triggerOn:"mousemove|click",alwaysShowContent:!1,displayMode:"single",renderMode:"auto",confine:!1,showDelay:0,hideDelay:100,transitionDuration:.4,enterable:!1,backgroundColor:"rgba(50,50,50,0.7)",borderColor:"#333",borderRadius:4,borderWidth:0,padding:5,extraCssText:"",axisPointer:{type:"line",axis:"auto",animation:"auto",animationDurationUpdate:200,animationEasingUpdate:"exponentialOut",crossStyle:{color:"#999",width:1,type:"dashed",textStyle:{}}},textStyle:{color:"#fff",fontSize:14}}});var SM=f,MM=fo,IM=["","-webkit-","-moz-","-o-"],TM="position:absolute;display:block;border-style:solid;white-space:nowrap;z-index:9999999;";mf.prototype={constructor:mf,_enterable:!0,update:function(){var t=this._container,e=t.currentStyle||document.defaultView.getComputedStyle(t),i=t.style;"absolute"!==i.position&&"absolute"!==e.position&&(i.position="relative")},show:function(t){clearTimeout(this._hideTimeout);var e=this.el;e.style.cssText=TM+vf(t)+";left:"+this._x+"px;top:"+this._y+"px;"+(t.get("extraCssText")||""),e.style.display=e.innerHTML?"block":"none",e.style.pointerEvents=this._enterable?"auto":"none",this._show=!0},setContent:function(t){this.el.innerHTML=null==t?"":t},setEnterable:function(t){this._enterable=t},getSize:function(){var t=this.el;return[t.clientWidth,t.clientHeight]},moveTo:function(t,e){var i,n=this._zr;n&&n.painter&&(i=n.painter.getViewportRootOffset())&&(t+=i.offsetLeft,e+=i.offsetTop);var r=this.el.style;r.left=t+"px",r.top=e+"px",this._x=t,this._y=e},hide:function(){this.el.style.display="none",this._show=!1},hideLater:function(t){!this._show||this._inContent&&this._enterable||(t?(this._hideDelay=t,this._show=!1,this._hideTimeout=setTimeout(y(this.hide,this),t)):this.hide())},isShow:function(){return this._show},getOuterSize:function(){var t=this.el.clientWidth,e=this.el.clientHeight;if(document.defaultView&&document.defaultView.getComputedStyle){var i=document.defaultView.getComputedStyle(this.el);i&&(t+=parseInt(i.paddingLeft,10)+parseInt(i.paddingRight,10)+parseInt(i.borderLeftWidth,10)+parseInt(i.borderRightWidth,10),e+=parseInt(i.paddingTop,10)+parseInt(i.paddingBottom,10)+parseInt(i.borderTopWidth,10)+parseInt(i.borderBottomWidth,10))}return{width:t,height:e}}},yf.prototype={constructor:yf,_enterable:!0,update:function(){},show:function(){this._hideTimeout&&clearTimeout(this._hideTimeout),this.el.attr("show",!0),this._show=!0},setContent:function(t,e,i){this.el&&this._zr.remove(this.el);for(var n={},r=t,a="{marker",o="|}",s=r.indexOf(a);s>=0;){var l=r.indexOf(o),h=r.substr(s+a.length,l-s-a.length);n["marker"+h]=h.indexOf("sub")>-1?{textWidth:4,textHeight:4,textBorderRadius:2,textBackgroundColor:e[h],textOffset:[3,0]}:{textWidth:10,textHeight:10,textBorderRadius:5,textBackgroundColor:e[h]},r=r.substr(l+1),s=r.indexOf("{marker")}this.el=new xy({style:{rich:n,text:t,textLineHeight:20,textBackgroundColor:i.get("backgroundColor"),textBorderRadius:i.get("borderRadius"),textFill:i.get("textStyle.color"),textPadding:i.get("padding")},z:i.get("z")}),this._zr.add(this.el);var u=this;this.el.on("mouseover",function(){u._enterable&&(clearTimeout(u._hideTimeout),u._show=!0),u._inContent=!0}),this.el.on("mouseout",function(){u._enterable&&u._show&&u.hideLater(u._hideDelay),u._inContent=!1})},setEnterable:function(t){this._enterable=t},getSize:function(){var t=this.el.getBoundingRect();return[t.width,t.height]},moveTo:function(t,e){this.el&&this.el.attr("position",[t,e])},hide:function(){this.el.hide(),this._show=!1},hideLater:function(t){!this._show||this._inContent&&this._enterable||(t?(this._hideDelay=t,this._show=!1,this._hideTimeout=setTimeout(y(this.hide,this),t)):this.hide())},isShow:function(){return this._show},getOuterSize:function(){return this.getSize()}};var CM=y,AM=f,DM=Ua,kM=new Dy({shape:{x:-1,y:-1,width:2,height:2}});nh({type:"tooltip",init:function(t,e){if(!tg.node){var i=t.getComponent("tooltip"),n=i.get("renderMode");this._renderMode=Qn(n);var r;"html"===this._renderMode?(r=new mf(e.getDom(),e),this._newLine="
    "):(r=new yf(e),this._newLine="\n"),this._tooltipContent=r}},render:function(t,e,i){if(!tg.node){this.group.removeAll(),this._tooltipModel=t,this._ecModel=e,this._api=i,this._lastDataByCoordSys=null,this._alwaysShowContent=t.get("alwaysShowContent");var n=this._tooltipContent;n.update(),n.setEnterable(t.get("enterable")),this._initGlobalListener(),this._keepShow()}},_initGlobalListener:function(){var t=this._tooltipModel,e=t.get("triggerOn");Zd("itemTooltip",this._api,CM(function(t,i,n){"none"!==e&&(e.indexOf(t)>=0?this._tryShow(i,n):"leave"===t&&this._hide(n))},this))},_keepShow:function(){var t=this._tooltipModel,e=this._ecModel,i=this._api;if(null!=this._lastX&&null!=this._lastY&&"none"!==t.get("triggerOn")){var n=this;clearTimeout(this._refreshUpdateTimeout),this._refreshUpdateTimeout=setTimeout(function(){n.manuallyShowTip(t,e,i,{x:n._lastX,y:n._lastY})})}},manuallyShowTip:function(t,e,i,n){if(n.from!==this.uid&&!tg.node){var r=_f(n,i);this._ticket="";var a=n.dataByCoordSys;if(n.tooltip&&null!=n.x&&null!=n.y){var o=kM;o.position=[n.x,n.y],o.update(),o.tooltip=n.tooltip,this._tryShow({offsetX:n.x,offsetY:n.y,target:o},r)}else if(a)this._tryShow({offsetX:n.x,offsetY:n.y,position:n.position,event:{},dataByCoordSys:n.dataByCoordSys,tooltipOption:n.tooltipOption},r);else if(null!=n.seriesIndex){if(this._manuallyAxisShowTip(t,e,i,n))return;var s=uM(n,e),l=s.point[0],h=s.point[1];null!=l&&null!=h&&this._tryShow({offsetX:l,offsetY:h,position:n.position,target:s.el,event:{}},r)}else null!=n.x&&null!=n.y&&(i.dispatchAction({type:"updateAxisPointer",x:n.x,y:n.y}),this._tryShow({offsetX:n.x,offsetY:n.y,position:n.position,target:i.getZr().findHover(n.x,n.y).target,event:{}},r))}},manuallyHideTip:function(t,e,i,n){var r=this._tooltipContent;!this._alwaysShowContent&&this._tooltipModel&&r.hideLater(this._tooltipModel.get("hideDelay")),this._lastX=this._lastY=null,n.from!==this.uid&&this._hide(_f(n,i))},_manuallyAxisShowTip:function(t,e,i,n){var r=n.seriesIndex,a=n.dataIndex,o=e.getComponent("axisPointer").coordSysAxesInfo;if(null!=r&&null!=a&&null!=o){var s=e.getSeriesByIndex(r);if(s){var l=s.getData(),t=xf([l.getItemModel(a),s,(s.coordinateSystem||{}).model,t]);if("axis"===t.get("trigger"))return i.dispatchAction({type:"updateAxisPointer",seriesIndex:r,dataIndex:a,position:n.position}),!0}}},_tryShow:function(t,e){var i=t.target,n=this._tooltipModel;if(n){this._lastX=t.offsetX,this._lastY=t.offsetY;var r=t.dataByCoordSys;r&&r.length?this._showAxisTooltip(r,t):i&&null!=i.dataIndex?(this._lastDataByCoordSys=null,this._showSeriesItemTooltip(t,i,e)):i&&i.tooltip?(this._lastDataByCoordSys=null,this._showComponentItemTooltip(t,i,e)):(this._lastDataByCoordSys=null,this._hide(e))}},_showOrMove:function(t,e){var i=t.get("showDelay");e=y(e,this),clearTimeout(this._showTimout),i>0?this._showTimout=setTimeout(e,i):e()},_showAxisTooltip:function(t,e){var i=this._ecModel,n=this._tooltipModel,a=[e.offsetX,e.offsetY],o=[],s=[],l=xf([e.tooltipOption,n]),h=this._renderMode,u=this._newLine,c={};AM(t,function(t){AM(t.dataByAxis,function(t){var e=i.getComponent(t.axisDim+"Axis",t.axisIndex),n=t.value,a=[];if(e&&null!=n){var l=sf(n,e.axis,i,t.seriesDataIndices,t.valueLabelOpt);f(t.seriesDataIndices,function(o){var u=i.getSeriesByIndex(o.seriesIndex),d=o.dataIndexInside,f=u&&u.getDataParams(d);if(f.axisDim=t.axisDim,f.axisIndex=t.axisIndex,f.axisType=t.axisType,f.axisId=t.axisId,f.axisValue=hu(e.axis,n),f.axisValueLabel=l,f){s.push(f);var p,g=u.formatTooltip(d,!0,null,h);if(S(g)){p=g.html;var v=g.markers;r(c,v)}else p=g;a.push(p)}});var d=l;o.push("html"!==h?a.join(u):(d?po(d)+u:"")+a.join(u))}})},this),o.reverse(),o=o.join(this._newLine+this._newLine);var d=e.position;this._showOrMove(l,function(){this._updateContentNotChangedOnAxis(t)?this._updatePosition(l,d,a[0],a[1],this._tooltipContent,s):this._showTooltipContent(l,o,s,Math.random(),a[0],a[1],d,void 0,c)})},_showSeriesItemTooltip:function(t,e,i){var n=this._ecModel,r=e.seriesIndex,a=n.getSeriesByIndex(r),o=e.dataModel||a,s=e.dataIndex,l=e.dataType,h=o.getData(),u=xf([h.getItemModel(s),o,a&&(a.coordinateSystem||{}).model,this._tooltipModel]),c=u.get("trigger");if(null==c||"item"===c){var d,f,p=o.getDataParams(s,l),g=o.formatTooltip(s,!1,l,this._renderMode);S(g)?(d=g.html,f=g.markers):(d=g,f=null);var v="item_"+o.name+"_"+s;this._showOrMove(u,function(){this._showTooltipContent(u,d,p,v,t.offsetX,t.offsetY,t.position,t.target,f)}),i({type:"showTip",dataIndexInside:s,dataIndex:h.getRawIndex(s),seriesIndex:r,from:this.uid})}},_showComponentItemTooltip:function(t,e,i){var n=e.tooltip;if("string"==typeof n){var r=n;n={content:r,formatter:r}}var a=new Wa(n,this._tooltipModel,this._ecModel),o=a.get("content"),s=Math.random();this._showOrMove(a,function(){this._showTooltipContent(a,o,a.get("formatterParams")||{},s,t.offsetX,t.offsetY,t.position,e)}),i({type:"showTip",from:this.uid})},_showTooltipContent:function(t,e,i,n,r,a,o,s,l){if(this._ticket="",t.get("showContent")&&t.get("show")){var h=this._tooltipContent,u=t.get("formatter");o=o||t.get("position");var c=e;if(u&&"string"==typeof u)c=go(u,i,!0);else if("function"==typeof u){var d=CM(function(e,n){e===this._ticket&&(h.setContent(n,l,t),this._updatePosition(t,o,r,a,h,i,s))},this);this._ticket=n,c=u(i,n,d)}h.setContent(c,l,t),h.show(t),this._updatePosition(t,o,r,a,h,i,s)}},_updatePosition:function(t,e,i,n,r,a,o){var s=this._api.getWidth(),l=this._api.getHeight();e=e||t.get("position");var h=r.getSize(),u=t.get("align"),c=t.get("verticalAlign"),d=o&&o.getBoundingRect().clone();if(o&&d.applyTransform(o.transform),"function"==typeof e&&(e=e([i,n],a,r.el,d,{viewSize:[s,l],contentSize:h.slice()})),_(e))i=DM(e[0],s),n=DM(e[1],l);else if(S(e)){e.width=h[0],e.height=h[1];var f=bo(e,{width:s,height:l});i=f.x,n=f.y,u=null,c=null}else if("string"==typeof e&&o){var p=Sf(e,d,h);i=p[0],n=p[1]}else{var p=wf(i,n,r,s,l,u?null:20,c?null:20);i=p[0],n=p[1]}if(u&&(i-=Mf(u)?h[0]/2:"right"===u?h[0]:0),c&&(n-=Mf(c)?h[1]/2:"bottom"===c?h[1]:0),t.get("confine")){var p=bf(i,n,r,s,l);i=p[0],n=p[1]}r.moveTo(i,n)},_updateContentNotChangedOnAxis:function(t){var e=this._lastDataByCoordSys,i=!!e&&e.length===t.length;return i&&AM(e,function(e,n){var r=e.dataByAxis||{},a=t[n]||{},o=a.dataByAxis||[];i&=r.length===o.length,i&&AM(r,function(t,e){var n=o[e]||{},r=t.seriesDataIndices||[],a=n.seriesDataIndices||[];i&=t.value===n.value&&t.axisType===n.axisType&&t.axisId===n.axisId&&r.length===a.length,i&&AM(r,function(t,e){var n=a[e];i&=t.seriesIndex===n.seriesIndex&&t.dataIndex===n.dataIndex})})}),this._lastDataByCoordSys=t,!!i},_hide:function(t){this._lastDataByCoordSys=null,t({type:"hideTip",from:this.uid})},dispose:function(t,e){tg.node||(this._tooltipContent.hide(),$d("itemTooltip",e))}}),Ul({type:"showTip",event:"showTip",update:"tooltip:manuallyShowTip"},function(){}),Ul({type:"hideTip",event:"hideTip",update:"tooltip:manuallyHideTip"},function(){});var PM=co,LM=po,OM=ih({type:"marker",dependencies:["series","grid","polar","geo"],init:function(t,e,i,n){this.mergeDefaultAndTheme(t,i),this.mergeOption(t,i,n.createdBySelf,!0)},isAnimationEnabled:function(){if(tg.node)return!1;var t=this.__hostSeries;return this.getShallow("animation")&&t&&t.isAnimationEnabled()},mergeOption:function(t,e,i,n){var r=this.constructor,a=this.mainType+"Model";i||e.eachSeries(function(t){var i=t.get(this.mainType,!0),s=t[a];return i&&i.data?(s?s.mergeOption(i,e,!0):(n&&If(i),f(i.data,function(t){t instanceof Array?(If(t[0]),If(t[1])):If(t)}),s=new r(i,this,e),o(s,{mainType:this.mainType,seriesIndex:t.seriesIndex,name:t.name,createdBySelf:!0}),s.__hostSeries=t),void(t[a]=s)):void(t[a]=null)},this)},formatTooltip:function(t){var e=this.getData(),i=this.getRawValue(t),n=_(i)?p(i,PM).join(", "):PM(i),r=e.getName(t),a=LM(this.name);return(null!=i||r)&&(a+="
    "),r&&(a+=LM(r),null!=i&&(a+=" : ")),null!=i&&(a+=LM(n)),a},getData:function(){return this._data},setData:function(t){this._data=t}});c(OM,i_),OM.extend({type:"markPoint",defaultOption:{zlevel:0,z:5,symbol:"pin",symbolSize:50,tooltip:{trigger:"item"},label:{show:!0,position:"inside"},itemStyle:{borderWidth:2},emphasis:{label:{show:!0}}}});var zM=h,EM=x,RM={min:EM(Af,"min"),max:EM(Af,"max"),average:EM(Af,"average")},BM=nh({type:"marker",init:function(){this.markerGroupMap=N()},render:function(t,e,i){var n=this.markerGroupMap;n.each(function(t){t.__keep=!1});var r=this.type+"Model";e.eachSeries(function(t){var n=t[r];n&&this.renderSeries(t,n,e,i)},this),n.each(function(t){!t.__keep&&this.group.remove(t.group)},this)},renderSeries:function(){}});BM.extend({type:"markPoint",updateTransform:function(t,e,i){e.eachSeries(function(t){var e=t.markPointModel;e&&(Ef(e.getData(),t,i),this.markerGroupMap.get(t.id).updateLayout(e))},this)},renderSeries:function(t,e,i,n){var r=t.coordinateSystem,a=t.id,o=t.getData(),s=this.markerGroupMap,l=s.get(a)||s.set(a,new zc),h=Rf(r,t,e);e.setData(h),Ef(e.getData(),t,n),h.each(function(t){var i=h.getItemModel(t),n=i.getShallow("symbolSize");"function"==typeof n&&(n=n(e.getRawValue(t),e.getDataParams(t))),h.setItemVisual(t,{symbolSize:n,color:i.get("itemStyle.color")||o.getVisual("color"),symbol:i.getShallow("symbol")})}),l.updateData(h),this.group.add(l.group),h.eachItemGraphicEl(function(t){t.traverse(function(t){t.dataModel=e + })}),l.__keep=!0,l.group.silent=e.get("silent")||t.get("silent")}}),Yl(function(t){t.markPoint=t.markPoint||{}}),OM.extend({type:"markLine",defaultOption:{zlevel:0,z:5,symbol:["circle","arrow"],symbolSize:[8,16],precision:2,tooltip:{trigger:"item"},label:{show:!0,position:"end"},lineStyle:{type:"dashed"},emphasis:{label:{show:!0},lineStyle:{width:3}},animationEasing:"linear"}});var NM=ky.prototype,FM=Ly.prototype,VM=$r({type:"ec-line",style:{stroke:"#000",fill:null},shape:{x1:0,y1:0,x2:0,y2:0,percent:1,cpx1:null,cpy1:null},buildPath:function(t,e){(Bf(e)?NM:FM).buildPath(t,e)},pointAt:function(t){return Bf(this.shape)?NM.pointAt.call(this,t):FM.pointAt.call(this,t)},tangentAt:function(t){var e=this.shape,i=Bf(e)?[e.x2-e.x1,e.y2-e.y1]:FM.tangentAt.call(this,t);return te(i,i)}}),WM=["fromSymbol","toSymbol"],GM=Hf.prototype;GM.beforeUpdate=Gf,GM._createLine=function(t,e,i){var n=t.hostModel,r=t.getItemLayout(e),a=Vf(r);a.shape.percent=0,Oa(a,{shape:{percent:1}},n,e),this.add(a);var o=new xy({name:"label"});this.add(o),f(WM,function(i){var n=Ff(i,t,e);this.add(n),this[Nf(i)]=t.getItemVisual(e,i)},this),this._updateCommonStl(t,e,i)},GM.updateData=function(t,e,i){var n=t.hostModel,r=this.childOfName("line"),a=t.getItemLayout(e),o={shape:{}};Wf(o.shape,a),La(r,o,n,e),f(WM,function(i){var n=t.getItemVisual(e,i),r=Nf(i);if(this[r]!==n){this.remove(this.childOfName(i));var a=Ff(i,t,e);this.add(a)}this[r]=n},this),this._updateCommonStl(t,e,i)},GM._updateCommonStl=function(t,e,i){var n=t.hostModel,r=this.childOfName("line"),a=i&&i.lineStyle,o=i&&i.hoverLineStyle,l=i&&i.labelModel,h=i&&i.hoverLabelModel;if(!i||t.hasItemOption){var u=t.getItemModel(e);a=u.getModel("lineStyle").getLineStyle(),o=u.getModel("emphasis.lineStyle").getLineStyle(),l=u.getModel("label"),h=u.getModel("emphasis.label")}var c=t.getItemVisual(e,"color"),d=k(t.getItemVisual(e,"opacity"),a.opacity,1);r.useStyle(s({strokeNoScale:!0,fill:"none",stroke:c,opacity:d},a)),r.hoverStyle=o,f(WM,function(t){var e=this.childOfName(t);e&&(e.setColor(c),e.setStyle({opacity:d}))},this);var p,g,v=l.getShallow("show"),m=h.getShallow("show"),y=this.childOfName("label");if((v||m)&&(p=c||"#000",g=n.getFormattedLabel(e,"normal",t.dataType),null==g)){var x=n.getRawValue(e);g=null==x?t.getName(e):isFinite(x)?$a(x):x}var _=v?g:null,w=m?D(n.getFormattedLabel(e,"emphasis",t.dataType),g):null,b=y.style;(null!=_||null!=w)&&(ba(y.style,l,{text:_},{autoColor:p}),y.__textAlign=b.textAlign,y.__verticalAlign=b.textVerticalAlign,y.__position=l.get("position")||"middle"),y.hoverStyle=null!=w?{text:w,textFill:h.getTextColor(!0),fontStyle:h.getShallow("fontStyle"),fontWeight:h.getShallow("fontWeight"),fontSize:h.getShallow("fontSize"),fontFamily:h.getShallow("fontFamily")}:{text:null},y.ignore=!v&&!m,xa(this)},GM.highlight=function(){this.trigger("emphasis")},GM.downplay=function(){this.trigger("normal")},GM.updateLayout=function(t,e){this.setLinePoints(t.getItemLayout(e))},GM.setLinePoints=function(t){var e=this.childOfName("line");Wf(e.shape,t),e.dirty()},u(Hf,lv);var HM=Zf.prototype;HM.isPersistent=function(){return!0},HM.updateData=function(t){var e=this,i=e.group,n=e._lineData;e._lineData=t,n||i.removeAll();var r=jf(t);t.diff(n).add(function(i){Xf(e,t,i,r)}).update(function(i,a){Yf(e,n,t,a,i,r)}).remove(function(t){i.remove(n.getItemGraphicEl(t))}).execute()},HM.updateLayout=function(){var t=this._lineData;t&&t.eachItemGraphicEl(function(e,i){e.updateLayout(t,i)},this)},HM.incrementalPrepareUpdate=function(t){this._seriesScope=jf(t),this._lineData=null,this.group.removeAll()},HM.incrementalUpdate=function(t,e){function i(t){t.isGroup||(t.incremental=t.useHoverLayer=!0)}for(var n=t.start;n=0&&"number"==typeof c&&(c=+c.toFixed(Math.min(m,20))),g.coord[f]=v.coord[f]=c,a=[g,v,{type:l,valueIndex:a.valueIndex,value:c}]}return a=[Df(t,a[0]),Df(t,a[1]),o({},a[2])],a[2].type=a[2].type||"",r(a[2],a[0]),r(a[2],a[1]),a};BM.extend({type:"markLine",updateTransform:function(t,e,i){e.eachSeries(function(t){var e=t.markLineModel;if(e){var n=e.getData(),r=e.__from,a=e.__to;r.each(function(e){Jf(r,e,!0,t,i),Jf(a,e,!1,t,i)}),n.each(function(t){n.setItemLayout(t,[r.getItemLayout(t),a.getItemLayout(t)])}),this.markerGroupMap.get(t.id).updateLayout()}},this)},renderSeries:function(t,e,i,n){function r(e,i,r){var a=e.getItemModel(i);Jf(e,i,r,t,n),e.setItemVisual(i,{symbolSize:a.get("symbolSize")||g[r?0:1],symbol:a.get("symbol",!0)||p[r?0:1],color:a.get("itemStyle.color")||s.getVisual("color")})}var a=t.coordinateSystem,o=t.id,s=t.getData(),l=this.markerGroupMap,h=l.get(o)||l.set(o,new Zf);this.group.add(h.group);var u=tp(a,t,e),c=u.from,d=u.to,f=u.line;e.__from=c,e.__to=d,e.setData(f);var p=e.get("symbol"),g=e.get("symbolSize");_(p)||(p=[p,p]),"number"==typeof g&&(g=[g,g]),u.from.each(function(t){r(c,t,!0),r(d,t,!1)}),f.each(function(t){var e=f.getItemModel(t).get("lineStyle.color");f.setItemVisual(t,{color:e||c.getItemVisual(t,"color")}),f.setItemLayout(t,[c.getItemLayout(t),d.getItemLayout(t)]),f.setItemVisual(t,{fromSymbolSize:c.getItemVisual(t,"symbolSize"),fromSymbol:c.getItemVisual(t,"symbol"),toSymbolSize:d.getItemVisual(t,"symbolSize"),toSymbol:d.getItemVisual(t,"symbol")})}),h.updateData(f),u.line.eachItemGraphicEl(function(t){t.traverse(function(t){t.dataModel=e})}),h.__keep=!0,h.group.silent=e.get("silent")||t.get("silent")}}),Yl(function(t){t.markLine=t.markLine||{}}),OM.extend({type:"markArea",defaultOption:{zlevel:0,z:1,tooltip:{trigger:"item"},animation:!1,label:{show:!0,position:"top"},itemStyle:{borderWidth:0},emphasis:{label:{show:!0,position:"top"}}}});var XM=function(t,e,i,n){var r=Df(t,n[0]),o=Df(t,n[1]),s=A,l=r.coord,h=o.coord;l[0]=s(l[0],-1/0),l[1]=s(l[1],-1/0),h[0]=s(h[0],1/0),h[1]=s(h[1],1/0);var u=a([{},r,o]);return u.coord=[r.coord,o.coord],u.x0=r.x,u.y0=r.y,u.x1=o.x,u.y1=o.y,u},YM=[["x0","y0"],["x1","y0"],["x1","y1"],["x0","y1"]];BM.extend({type:"markArea",updateTransform:function(t,e,i){e.eachSeries(function(t){var e=t.markAreaModel;if(e){var n=e.getData();n.each(function(e){var r=p(YM,function(r){return rp(n,e,r,t,i)});n.setItemLayout(e,r);var a=n.getItemGraphicEl(e);a.setShape("points",r)})}},this)},renderSeries:function(t,e,i,n){var r=t.coordinateSystem,a=t.id,o=t.getData(),l=this.markerGroupMap,h=l.get(a)||l.set(a,{group:new lv});this.group.add(h.group),h.__keep=!0;var u=ap(r,t,e);e.setData(u),u.each(function(e){u.setItemLayout(e,p(YM,function(i){return rp(u,e,i,t,n)})),u.setItemVisual(e,{color:o.getVisual("color")})}),u.diff(h.__data).add(function(t){var e=new Cy({shape:{points:u.getItemLayout(t)}});u.setItemGraphicEl(t,e),h.group.add(e)}).update(function(t,i){var n=h.__data.getItemGraphicEl(i);La(n,{shape:{points:u.getItemLayout(t)}},e,t),h.group.add(n),u.setItemGraphicEl(t,n)}).remove(function(t){var e=h.__data.getItemGraphicEl(t);h.group.remove(e)}).execute(),u.eachItemGraphicEl(function(t,i){var n=u.getItemModel(i),r=n.getModel("label"),a=n.getModel("emphasis.label"),o=u.getItemVisual(i,"color");t.useStyle(s(n.getModel("itemStyle").getItemStyle(),{fill:Ke(o,.4),stroke:o})),t.hoverStyle=n.getModel("emphasis.itemStyle").getItemStyle(),wa(t.style,t.hoverStyle,r,a,{labelFetcher:e,labelDataIndex:i,defaultText:u.getName(i)||"",isRectText:!0,autoColor:o}),xa(t,{}),t.dataModel=e}),h.__data=u,h.group.silent=e.get("silent")||t.get("silent")}}),Yl(function(t){t.markArea=t.markArea||{}});var jM=function(t){var e=t&&t.timeline;_(e)||(e=e?[e]:[]),f(e,function(t){t&&op(t)})};yx.registerSubTypeDefaulter("timeline",function(){return"slider"}),Ul({type:"timelineChange",event:"timelineChanged",update:"prepareAndUpdate"},function(t,e){var i=e.getComponent("timeline");return i&&null!=t.currentIndex&&(i.setCurrentIndex(t.currentIndex),!i.get("loop",!0)&&i.isIndexMax()&&i.setPlayState(!1)),e.resetOption("timeline"),s({currentIndex:i.option.currentIndex},t)}),Ul({type:"timelinePlayChange",event:"timelinePlayChanged",update:"update"},function(t,e){var i=e.getComponent("timeline");i&&null!=t.playState&&i.setPlayState(t.playState)});var qM=yx.extend({type:"timeline",layoutMode:"box",defaultOption:{zlevel:0,z:4,show:!0,axisType:"time",realtime:!0,left:"20%",top:null,right:"20%",bottom:0,width:null,height:40,padding:5,controlPosition:"left",autoPlay:!1,rewind:!1,loop:!0,playInterval:2e3,currentIndex:0,itemStyle:{},label:{color:"#000"},data:[]},init:function(t,e,i){this._data,this._names,this.mergeDefaultAndTheme(t,i),this._initData()},mergeOption:function(){qM.superApply(this,"mergeOption",arguments),this._initData()},setCurrentIndex:function(t){null==t&&(t=this.option.currentIndex);var e=this._data.count();this.option.loop?t=(t%e+e)%e:(t>=e&&(t=e-1),0>t&&(t=0)),this.option.currentIndex=t},getCurrentIndex:function(){return this.option.currentIndex},isIndexMax:function(){return this.getCurrentIndex()>=this._data.count()-1},setPlayState:function(t){this.option.autoPlay=!!t},getPlayState:function(){return!!this.option.autoPlay},_initData:function(){var t=this.option,e=t.data||[],i=t.axisType,r=this._names=[];if("category"===i){var a=[];f(e,function(t,e){var i,o=Vn(t);S(t)?(i=n(t),i.value=e):i=e,a.push(i),b(o)||null!=o&&!isNaN(o)||(o=""),r.push(o+"")}),e=a}var o={category:"ordinal",time:"time"}[i]||"number",s=this._data=new Bw([{name:"value",type:o}],this);s.initData(e,r)},getData:function(){return this._data},getCategories:function(){return"category"===this.get("axisType")?this._names.slice():void 0}}),UM=qM.extend({type:"timeline.slider",defaultOption:{backgroundColor:"rgba(0,0,0,0)",borderColor:"#ccc",borderWidth:0,orient:"horizontal",inverse:!1,tooltip:{trigger:"item"},symbol:"emptyCircle",symbolSize:10,lineStyle:{show:!0,width:2,color:"#304654"},label:{position:"auto",show:!0,interval:"auto",rotate:0,color:"#304654"},itemStyle:{color:"#304654",borderWidth:1},checkpointStyle:{symbol:"circle",symbolSize:13,color:"#c23531",borderWidth:5,borderColor:"rgba(194,53,49, 0.5)",animation:!0,animationDuration:300,animationEasing:"quinticInOut"},controlStyle:{show:!0,showPlayBtn:!0,showPrevBtn:!0,showNextBtn:!0,itemSize:22,itemGap:12,position:"left",playIcon:"path://M31.6,53C17.5,53,6,41.5,6,27.4S17.5,1.8,31.6,1.8C45.7,1.8,57.2,13.3,57.2,27.4S45.7,53,31.6,53z M31.6,3.3 C18.4,3.3,7.5,14.1,7.5,27.4c0,13.3,10.8,24.1,24.1,24.1C44.9,51.5,55.7,40.7,55.7,27.4C55.7,14.1,44.9,3.3,31.6,3.3z M24.9,21.3 c0-2.2,1.6-3.1,3.5-2l10.5,6.1c1.899,1.1,1.899,2.9,0,4l-10.5,6.1c-1.9,1.1-3.5,0.2-3.5-2V21.3z",stopIcon:"path://M30.9,53.2C16.8,53.2,5.3,41.7,5.3,27.6S16.8,2,30.9,2C45,2,56.4,13.5,56.4,27.6S45,53.2,30.9,53.2z M30.9,3.5C17.6,3.5,6.8,14.4,6.8,27.6c0,13.3,10.8,24.1,24.101,24.1C44.2,51.7,55,40.9,55,27.6C54.9,14.4,44.1,3.5,30.9,3.5z M36.9,35.8c0,0.601-0.4,1-0.9,1h-1.3c-0.5,0-0.9-0.399-0.9-1V19.5c0-0.6,0.4-1,0.9-1H36c0.5,0,0.9,0.4,0.9,1V35.8z M27.8,35.8 c0,0.601-0.4,1-0.9,1h-1.3c-0.5,0-0.9-0.399-0.9-1V19.5c0-0.6,0.4-1,0.9-1H27c0.5,0,0.9,0.4,0.9,1L27.8,35.8L27.8,35.8z",nextIcon:"path://M18.6,50.8l22.5-22.5c0.2-0.2,0.3-0.4,0.3-0.7c0-0.3-0.1-0.5-0.3-0.7L18.7,4.4c-0.1-0.1-0.2-0.3-0.2-0.5 c0-0.4,0.3-0.8,0.8-0.8c0.2,0,0.5,0.1,0.6,0.3l23.5,23.5l0,0c0.2,0.2,0.3,0.4,0.3,0.7c0,0.3-0.1,0.5-0.3,0.7l-0.1,0.1L19.7,52 c-0.1,0.1-0.3,0.2-0.5,0.2c-0.4,0-0.8-0.3-0.8-0.8C18.4,51.2,18.5,51,18.6,50.8z",prevIcon:"path://M43,52.8L20.4,30.3c-0.2-0.2-0.3-0.4-0.3-0.7c0-0.3,0.1-0.5,0.3-0.7L42.9,6.4c0.1-0.1,0.2-0.3,0.2-0.5 c0-0.4-0.3-0.8-0.8-0.8c-0.2,0-0.5,0.1-0.6,0.3L18.3,28.8l0,0c-0.2,0.2-0.3,0.4-0.3,0.7c0,0.3,0.1,0.5,0.3,0.7l0.1,0.1L41.9,54 c0.1,0.1,0.3,0.2,0.5,0.2c0.4,0,0.8-0.3,0.8-0.8C43.2,53.2,43.1,53,43,52.8z",color:"#304654",borderColor:"#304654",borderWidth:1},emphasis:{label:{show:!0,color:"#c23531"},itemStyle:{color:"#c23531"},controlStyle:{color:"#c23531",borderColor:"#c23531",borderWidth:2}},data:[]}});c(UM,i_);var $M=s_.extend({type:"timeline"}),KM=function(t,e,i,n){Pb.call(this,t,e,i),this.type=n||"value",this.model=null};KM.prototype={constructor:KM,getLabelModel:function(){return this.model.getModel("label")},isHorizontal:function(){return"horizontal"===this.model.get("orient")}},u(KM,Pb);var QM=y,JM=f,tI=Math.PI;$M.extend({type:"timeline.slider",init:function(t,e){this.api=e,this._axis,this._viewRect,this._timer,this._currentPointer,this._mainGroup,this._labelGroup},render:function(t,e,i){if(this.model=t,this.api=i,this.ecModel=e,this.group.removeAll(),t.get("show",!0)){var n=this._layout(t,i),r=this._createGroup("mainGroup"),a=this._createGroup("labelGroup"),o=this._axis=this._createAxis(n,t);t.formatTooltip=function(t){return po(o.scale.getLabel(t))},JM(["AxisLine","AxisTick","Control","CurrentPointer"],function(e){this["_render"+e](n,r,o,t)},this),this._renderAxisLabel(n,a,o,t),this._position(n,t)}this._doPlayStop()},remove:function(){this._clearTimer(),this.group.removeAll()},dispose:function(){this._clearTimer()},_layout:function(t,e){var i=t.get("label.position"),n=t.get("orient"),r=hp(t,e);null==i||"auto"===i?i="horizontal"===n?r.y+r.height/2=0||"+"===i?"left":"right"},o={horizontal:i>=0||"+"===i?"top":"bottom",vertical:"middle"},s={horizontal:0,vertical:tI/2},l="vertical"===n?r.height:r.width,h=t.getModel("controlStyle"),u=h.get("show",!0),c=u?h.get("itemSize"):0,d=u?h.get("itemGap"):0,f=c+d,p=t.get("label.rotate")||0;p=p*tI/180;var g,v,m,y,x=h.get("position",!0),_=u&&h.get("showPlayBtn",!0),w=u&&h.get("showPrevBtn",!0),b=u&&h.get("showNextBtn",!0),S=0,M=l;return"left"===x||"bottom"===x?(_&&(g=[0,0],S+=f),w&&(v=[S,0],S+=f),b&&(m=[M-c,0],M-=f)):(_&&(g=[M-c,0],M-=f),w&&(v=[0,0],S+=f),b&&(m=[M-c,0],M-=f)),y=[S,M],t.get("inverse")&&y.reverse(),{viewRect:r,mainLength:l,orient:n,rotation:s[n],labelRotation:p,labelPosOpt:i,labelAlign:t.get("label.align")||a[n],labelBaseline:t.get("label.verticalAlign")||t.get("label.baseline")||o[n],playPosition:g,prevBtnPosition:v,nextBtnPosition:m,axisExtent:y,controlSize:c,controlGap:d}},_position:function(t){function e(t){var e=t.position;t.origin=[u[0][0]-e[0],u[1][0]-e[1]]}function i(t){return[[t.x,t.x+t.width],[t.y,t.y+t.height]]}function n(t,e,i,n,r){t[n]+=i[n][r]-e[n][r]}var r=this._mainGroup,a=this._labelGroup,o=t.viewRect;if("vertical"===t.orient){var s=be(),l=o.x,h=o.y+o.height;Te(s,s,[-l,-h]),Ce(s,s,-tI/2),Te(s,s,[l,h]),o=o.clone(),o.applyTransform(s)}var u=i(o),c=i(r.getBoundingRect()),d=i(a.getBoundingRect()),f=r.position,p=a.position;p[0]=f[0]=u[0][0];var g=t.labelPosOpt;if(isNaN(g)){var v="+"===g?0:1;n(f,c,u,1,v),n(p,d,u,1,1-v)}else{var v=g>=0?0:1;n(f,c,u,1,v),p[1]=f[1]+g}r.attr("position",f),a.attr("position",p),r.rotation=a.rotation=t.rotation,e(r),e(a)},_createAxis:function(t,e){var i=e.getData(),n=e.get("axisType"),r=ou(e,n);r.getTicks=function(){return i.mapArray(["value"],function(t){return t})};var a=i.getDataExtent("value");r.setExtent(a[0],a[1]),r.niceTicks();var o=new KM("value",r,t.axisExtent,n);return o.model=e,o},_createGroup:function(t){var e=this["_"+t]=new lv;return this.group.add(e),e},_renderAxisLine:function(t,e,i,n){var r=i.getExtent();n.get("lineStyle.show")&&e.add(new ky({shape:{x1:r[0],y1:0,x2:r[1],y2:0},style:o({lineCap:"round"},n.getModel("lineStyle").getLineStyle()),silent:!0,z2:1}))},_renderAxisTick:function(t,e,i,n){var r=n.getData(),a=i.scale.getTicks();JM(a,function(t){var a=i.dataToCoord(t),o=r.getItemModel(t),s=o.getModel("itemStyle"),l=o.getModel("emphasis.itemStyle"),h={position:[a,0],onclick:QM(this._changeTimeline,this,t)},u=cp(o,s,e,h);xa(u,l.getItemStyle()),o.get("tooltip")?(u.dataIndex=t,u.dataModel=n):u.dataIndex=u.dataModel=null},this)},_renderAxisLabel:function(t,e,i,n){var r=i.getLabelModel();if(r.get("show")){var a=n.getData(),o=i.getViewLabels();JM(o,function(n){var r=n.tickValue,o=a.getItemModel(r),s=o.getModel("label"),l=o.getModel("emphasis.label"),h=i.dataToCoord(n.tickValue),u=new xy({position:[h,0],rotation:t.labelRotation-t.rotation,onclick:QM(this._changeTimeline,this,r),silent:!1});ba(u.style,s,{text:n.formattedLabel,textAlign:t.labelAlign,textVerticalAlign:t.labelBaseline}),e.add(u),xa(u,ba({},l))},this)}},_renderControl:function(t,e,i,n){function r(t,i,r,u){if(t){var c={position:t,origin:[a/2,0],rotation:u?-o:0,rectHover:!0,style:s,onclick:r},d=up(n,i,h,c);e.add(d),xa(d,l)}}var a=t.controlSize,o=t.rotation,s=n.getModel("controlStyle").getItemStyle(),l=n.getModel("emphasis.controlStyle").getItemStyle(),h=[0,-a/2,a,a],u=n.getPlayState(),c=n.get("inverse",!0);r(t.nextBtnPosition,"controlStyle.nextIcon",QM(this._changeTimeline,this,c?"-":"+")),r(t.prevBtnPosition,"controlStyle.prevIcon",QM(this._changeTimeline,this,c?"+":"-")),r(t.playPosition,"controlStyle."+(u?"stopIcon":"playIcon"),QM(this._handlePlayClick,this,!u),!0)},_renderCurrentPointer:function(t,e,i,n){var r=n.getData(),a=n.getCurrentIndex(),o=r.getItemModel(a).getModel("checkpointStyle"),s=this,l={onCreate:function(t){t.draggable=!0,t.drift=QM(s._handlePointerDrag,s),t.ondragend=QM(s._handlePointerDragend,s),dp(t,a,i,n,!0)},onUpdate:function(t){dp(t,a,i,n)}};this._currentPointer=cp(o,o,this._mainGroup,{},this._currentPointer,l)},_handlePlayClick:function(t){this._clearTimer(),this.api.dispatchAction({type:"timelinePlayChange",playState:t,from:this.uid})},_handlePointerDrag:function(t,e,i){this._clearTimer(),this._pointerChangeTimeline([i.offsetX,i.offsetY])},_handlePointerDragend:function(t){this._pointerChangeTimeline([t.offsetX,t.offsetY],!0)},_pointerChangeTimeline:function(t,e){var i=this._toAxisCoord(t)[0],n=this._axis,r=Ka(n.getExtent().slice());i>r[1]&&(i=r[1]),is&&(n=s,e=a)}),e},_clearTimer:function(){this._timer&&(clearTimeout(this._timer),this._timer=null)},_changeTimeline:function(t){var e=this.model.getCurrentIndex();"+"===t?t=e+1:"-"===t&&(t=e-1),this.api.dispatchAction({type:"timelineChange",currentIndex:t,from:this.uid})}}),Yl(jM),yx.registerSubTypeDefaulter("dataZoom",function(){return"slider"});var eI=["x","y","z","radius","angle","single"],iI=["cartesian2d","polar","singleAxis"],nI=pp(eI,["axisIndex","axis","index","id"]),rI=f,aI=Ka,oI=function(t,e,i,n){this._dimName=t,this._axisIndex=e,this._valueWindow,this._percentWindow,this._dataExtent,this._minMaxSpan,this.ecModel=n,this._dataZoomModel=i};oI.prototype={constructor:oI,hostedBy:function(t){return this._dataZoomModel===t},getDataValueWindow:function(){return this._valueWindow.slice()},getDataPercentWindow:function(){return this._percentWindow.slice()},getTargetSeriesModels:function(){var t=[],e=this.ecModel;return e.eachSeries(function(i){if(fp(i.get("coordinateSystem"))){var n=this._dimName,r=e.queryComponents({mainType:n+"Axis",index:i.get(n+"AxisIndex"),id:i.get(n+"AxisId")})[0];this._axisIndex===(r&&r.componentIndex)&&t.push(i)}},this),t},getAxisModel:function(){return this.ecModel.getComponent(this._dimName+"Axis",this._axisIndex)},getOtherAxisModel:function(){var t,e,i=this._dimName,n=this.ecModel,r=this.getAxisModel(),a="x"===i||"y"===i;a?(e="gridIndex",t="x"===i?"y":"x"):(e="polarIndex",t="angle"===i?"radius":"angle");var o;return n.eachComponent(t+"Axis",function(t){(t.get(e)||0)===(r.get(e)||0)&&(o=t)}),o},getMinMaxSpan:function(){return n(this._minMaxSpan)},calculateDataWindow:function(t){var e=this._dataExtent,i=this.getAxisModel(),n=i.axis.scale,r=this._dataZoomModel.getRangePropMode(),a=[0,100],o=[t.start,t.end],s=[];return rI(["startValue","endValue"],function(e){s.push(null!=t[e]?n.parse(t[e]):null)}),rI([0,1],function(t){var i=s[t],l=o[t];"percent"===r[t]?(null==l&&(l=a[t]),i=n.parse(qa(l,a,e,!0))):l=qa(i,e,a,!0),s[t]=i,o[t]=l}),{valueWindow:aI(s),percentWindow:aI(o)}},reset:function(t){if(t===this._dataZoomModel){var e=this.getTargetSeriesModels();this._dataExtent=vp(this,this._dimName,e);var i=this.calculateDataWindow(t.option);this._valueWindow=i.valueWindow,this._percentWindow=i.percentWindow,xp(this),yp(this)}},restore:function(t){t===this._dataZoomModel&&(this._valueWindow=this._percentWindow=null,yp(this,!0))},filterData:function(t){function e(t){return t>=a[0]&&t<=a[1]}if(t===this._dataZoomModel){var i=this._dimName,n=this.getTargetSeriesModels(),r=t.get("filterMode"),a=this._valueWindow;"none"!==r&&rI(n,function(t){var n=t.getData(),o=n.mapDimension(i,!0);o.length&&("weakFilter"===r?n.filterSelf(function(t){for(var e,i,r,s=0;sa[1];if(h&&!u&&!c)return!0;h&&(r=!0),u&&(e=!0),c&&(i=!0)}return r&&e&&i}):rI(o,function(i){if("empty"===r)t.setData(n.map(i,function(t){return e(t)?t:0/0}));else{var o={};o[i]=a,n.selectRange(o)}}),rI(o,function(t){n.setApproximateExtent(a,t)}))})}}};var sI=f,lI=nI,hI=ih({type:"dataZoom",dependencies:["xAxis","yAxis","zAxis","radiusAxis","angleAxis","singleAxis","series"],defaultOption:{zlevel:0,z:4,orient:null,xAxisIndex:null,yAxisIndex:null,filterMode:"filter",throttle:null,start:0,end:100,startValue:null,endValue:null,minSpan:null,maxSpan:null,minValueSpan:null,maxValueSpan:null,rangeMode:null},init:function(t,e,i){this._dataIntervalByAxis={},this._dataInfo={},this._axisProxies={},this.textStyleModel,this._autoThrottle=!0,this._rangePropMode=["percent","percent"];var n=_p(t);this.mergeDefaultAndTheme(t,i),this.doInit(n)},mergeOption:function(t){var e=_p(t);r(this.option,t,!0),this.doInit(e)},doInit:function(t){var e=this.option;tg.canvasSupported||(e.realtime=!1),this._setDefaultThrottle(t),wp(this,t),sI([["start","startValue"],["end","endValue"]],function(t,i){"value"===this._rangePropMode[i]&&(e[t[0]]=null)},this),this.textStyleModel=this.getModel("textStyle"),this._resetTarget(),this._giveAxisProxies()},_giveAxisProxies:function(){var t=this._axisProxies;this.eachTargetAxis(function(e,i,n,r){var a=this.dependentModels[e.axis][i],o=a.__dzAxisProxy||(a.__dzAxisProxy=new oI(e.name,i,this,r));t[e.name+"_"+i]=o},this)},_resetTarget:function(){var t=this.option,e=this._judgeAutoMode();lI(function(e){var i=e.axisIndex;t[i]=Nn(t[i])},this),"axisIndex"===e?this._autoSetAxisIndex():"orient"===e&&this._autoSetOrient()},_judgeAutoMode:function(){var t=this.option,e=!1;lI(function(i){null!=t[i.axisIndex]&&(e=!0)},this);var i=t.orient;return null==i&&e?"orient":e?void 0:(null==i&&(t.orient="horizontal"),"axisIndex")},_autoSetAxisIndex:function(){var t=!0,e=this.get("orient",!0),i=this.option,n=this.dependentModels;if(t){var r="vertical"===e?"y":"x";n[r+"Axis"].length?(i[r+"AxisIndex"]=[0],t=!1):sI(n.singleAxis,function(n){t&&n.get("orient",!0)===e&&(i.singleAxisIndex=[n.componentIndex],t=!1)})}t&&lI(function(e){if(t){var n=[],r=this.dependentModels[e.axis];if(r.length&&!n.length)for(var a=0,o=r.length;o>a;a++)"category"===r[a].get("type")&&n.push(a);i[e.axisIndex]=n,n.length&&(t=!1)}},this),t&&this.ecModel.eachSeries(function(t){this._isSeriesHasAllAxesTypeOf(t,"value")&&lI(function(e){var n=i[e.axisIndex],r=t.get(e.axisIndex),a=t.get(e.axisId),o=t.ecModel.queryComponents({mainType:e.axis,index:r,id:a})[0];r=o.componentIndex,h(n,r)<0&&n.push(r)})},this)},_autoSetOrient:function(){var t;this.eachTargetAxis(function(e){!t&&(t=e.name)},this),this.option.orient="y"===t?"vertical":"horizontal"},_isSeriesHasAllAxesTypeOf:function(t,e){var i=!0;return lI(function(n){var r=t.get(n.axisIndex),a=this.dependentModels[n.axis][r];a&&a.get("type")===e||(i=!1)},this),i},_setDefaultThrottle:function(t){if(t.hasOwnProperty("throttle")&&(this._autoThrottle=!1),this._autoThrottle){var e=this.ecModel.option;this.option.throttle=e.animation&&e.animationDurationUpdate>0?100:20}},getFirstTargetAxisModel:function(){var t;return lI(function(e){if(null==t){var i=this.get(e.axisIndex);i.length&&(t=this.dependentModels[e.axis][i[0]])}},this),t},eachTargetAxis:function(t,e){var i=this.ecModel;lI(function(n){sI(this.get(n.axisIndex),function(r){t.call(e,n,r,this,i)},this)},this)},getAxisProxy:function(t,e){return this._axisProxies[t+"_"+e]},getAxisModel:function(t,e){var i=this.getAxisProxy(t,e);return i&&i.getAxisModel()},setRawRange:function(t,e){var i=this.option;sI([["start","startValue"],["end","endValue"]],function(e){(null!=t[e[0]]||null!=t[e[1]])&&(i[e[0]]=t[e[0]],i[e[1]]=t[e[1]])},this),!e&&wp(this,t)},getPercentRange:function(){var t=this.findRepresentativeAxisProxy();return t?t.getDataPercentWindow():void 0},getValueRange:function(t,e){if(null!=t||null!=e)return this.getAxisProxy(t,e).getDataValueWindow();var i=this.findRepresentativeAxisProxy();return i?i.getDataValueWindow():void 0},findRepresentativeAxisProxy:function(t){if(t)return t.__dzAxisProxy;var e=this._axisProxies;for(var i in e)if(e.hasOwnProperty(i)&&e[i].hostedBy(this))return e[i];for(var i in e)if(e.hasOwnProperty(i)&&!e[i].hostedBy(this))return e[i]},getRangePropMode:function(){return this._rangePropMode.slice()}}),uI=s_.extend({type:"dataZoom",render:function(t,e,i){this.dataZoomModel=t,this.ecModel=e,this.api=i},getTargetCoordInfo:function(){function t(t,e,i,n){for(var r,a=0;aa&&(e[1-n]=e[n]+u.sign*a),e}),dI=Dy,fI=qa,pI=Ka,gI=y,vI=f,mI=7,yI=1,xI=30,_I="horizontal",wI="vertical",bI=5,SI=["line","bar","candlestick","scatter"],MI=uI.extend({type:"dataZoom.slider",init:function(t,e){this._displayables={},this._orient,this._range,this._handleEnds,this._size,this._handleWidth,this._handleHeight,this._location,this._dragging,this._dataShadowInfo,this.api=e},render:function(t,e,i,n){return MI.superApply(this,"render",arguments),Hs(this,"_dispatchZoomAction",this.dataZoomModel.get("throttle"),"fixRate"),this._orient=t.get("orient"),this.dataZoomModel.get("show")===!1?void this.group.removeAll():(n&&"dataZoom"===n.type&&n.from===this.uid||this._buildView(),void this._updateView())},remove:function(){MI.superApply(this,"remove",arguments),Zs(this,"_dispatchZoomAction")},dispose:function(){MI.superApply(this,"dispose",arguments),Zs(this,"_dispatchZoomAction")},_buildView:function(){var t=this.group;t.removeAll(),this._resetLocation(),this._resetInterval();var e=this._displayables.barGroup=new lv;this._renderBackground(),this._renderHandle(),this._renderDataShadow(),t.add(e),this._positionGroup()},_resetLocation:function(){var t=this.dataZoomModel,e=this.api,i=this._findCoordRect(),n={width:e.getWidth(),height:e.getHeight()},r=this._orient===_I?{right:n.width-i.x-i.width,top:n.height-xI-mI,width:i.width,height:xI}:{right:mI,top:i.y,width:xI,height:i.height},a=Mo(t.option);f(["right","top","width","height"],function(t){"ph"===a[t]&&(a[t]=r[t])});var o=bo(a,n,t.padding);this._location={x:o.x,y:o.y},this._size=[o.width,o.height],this._orient===wI&&this._size.reverse()},_positionGroup:function(){var t=this.group,e=this._location,i=this._orient,n=this.dataZoomModel.getFirstTargetAxisModel(),r=n&&n.get("inverse"),a=this._displayables.barGroup,o=(this._dataShadowInfo||{}).otherAxisInverse;a.attr(i!==_I||r?i===_I&&r?{scale:o?[-1,1]:[-1,-1]}:i!==wI||r?{scale:o?[-1,-1]:[-1,1],rotation:Math.PI/2}:{scale:o?[1,-1]:[1,1],rotation:Math.PI/2}:{scale:o?[1,1]:[1,-1]});var s=t.getBoundingRect([a]);t.attr("position",[e.x-s.x,e.y-s.y])},_getViewExtent:function(){return[0,this._size[0]]},_renderBackground:function(){var t=this.dataZoomModel,e=this._size,i=this._displayables.barGroup;i.add(new dI({silent:!0,shape:{x:0,y:0,width:e[0],height:e[1]},style:{fill:t.get("backgroundColor")},z2:-40})),i.add(new dI({shape:{x:0,y:0,width:e[0],height:e[1]},style:{fill:"transparent"},z2:0,onclick:y(this._onClickPanelClick,this)}))},_renderDataShadow:function(){var t=this._dataShadowInfo=this._prepareDataShadowInfo();if(t){var e=this._size,i=t.series,n=i.getRawData(),r=i.getShadowDim?i.getShadowDim():t.otherDim;if(null!=r){var a=n.getDataExtent(r),o=.3*(a[1]-a[0]);a=[a[0]-o,a[1]+o];var l,h=[0,e[1]],u=[0,e[0]],c=[[e[0],0],[0,0]],d=[],f=u[1]/(n.count()-1),p=0,g=Math.round(n.count()/e[0]);n.each([r],function(t,e){if(g>0&&e%g)return void(p+=f);var i=null==t||isNaN(t)||""===t,n=i?0:fI(t,a,h,!0);i&&!l&&e?(c.push([c[c.length-1][0],0]),d.push([d[d.length-1][0],0])):!i&&l&&(c.push([p,0]),d.push([p,0])),c.push([p,n]),d.push([p,n]),p+=f,l=i});var v=this.dataZoomModel;this._displayables.barGroup.add(new Cy({shape:{points:c},style:s({fill:v.get("dataBackgroundColor")},v.getModel("dataBackground.areaStyle").getAreaStyle()),silent:!0,z2:-20})),this._displayables.barGroup.add(new Ay({shape:{points:d},style:v.getModel("dataBackground.lineStyle").getLineStyle(),silent:!0,z2:-19}))}}},_prepareDataShadowInfo:function(){var t=this.dataZoomModel,e=t.get("showDataShadow");if(e!==!1){var i,n=this.ecModel;return t.eachTargetAxis(function(r,a){var o=t.getAxisProxy(r.name,a).getTargetSeriesModels();f(o,function(t){if(!(i||e!==!0&&h(SI,t.get("type"))<0)){var o,s=n.getComponent(r.axis,a).axis,l=Mp(r.name),u=t.coordinateSystem;null!=l&&u.getOtherAxis&&(o=u.getOtherAxis(s).inverse),l=t.getData().mapDimension(l),i={thisAxis:s,series:t,thisDim:r.name,otherDim:l,otherAxisInverse:o}}},this)},this),i}},_renderHandle:function(){var t=this._displayables,e=t.handles=[],i=t.handleLabels=[],n=this._displayables.barGroup,r=this._size,a=this.dataZoomModel;n.add(t.filler=new dI({draggable:!0,cursor:Ip(this._orient),drift:gI(this._onDragMove,this,"all"),onmousemove:function(t){Ig(t.event)},ondragstart:gI(this._showDataInfo,this,!0),ondragend:gI(this._onDragEnd,this),onmouseover:gI(this._showDataInfo,this,!0),onmouseout:gI(this._showDataInfo,this,!1),style:{fill:a.get("fillerColor"),textPosition:"inside"}})),n.add(new dI(na({silent:!0,shape:{x:0,y:0,width:r[0],height:r[1]},style:{stroke:a.get("dataBackgroundColor")||a.get("borderColor"),lineWidth:yI,fill:"rgba(0,0,0,0)"}}))),vI([0,1],function(t){var r=Va(a.get("handleIcon"),{cursor:Ip(this._orient),draggable:!0,drift:gI(this._onDragMove,this,t),onmousemove:function(t){Ig(t.event) + },ondragend:gI(this._onDragEnd,this),onmouseover:gI(this._showDataInfo,this,!0),onmouseout:gI(this._showDataInfo,this,!1)},{x:-1,y:0,width:2,height:2}),o=r.getBoundingRect();this._handleHeight=Ua(a.get("handleSize"),this._size[1]),this._handleWidth=o.width/o.height*this._handleHeight,r.setStyle(a.getModel("handleStyle").getItemStyle());var s=a.get("handleColor");null!=s&&(r.style.fill=s),n.add(e[t]=r);var l=a.textStyleModel;this.group.add(i[t]=new xy({silent:!0,invisible:!0,style:{x:0,y:0,text:"",textVerticalAlign:"middle",textAlign:"center",textFill:l.getTextColor(),textFont:l.getFont()},z2:10}))},this)},_resetInterval:function(){var t=this._range=this.dataZoomModel.getPercentRange(),e=this._getViewExtent();this._handleEnds=[fI(t[0],[0,100],e,!0),fI(t[1],[0,100],e,!0)]},_updateInterval:function(t,e){var i=this.dataZoomModel,n=this._handleEnds,r=this._getViewExtent(),a=i.findRepresentativeAxisProxy().getMinMaxSpan(),o=[0,100];cI(e,n,r,i.get("zoomLock")?"all":t,null!=a.minSpan?fI(a.minSpan,o,r,!0):null,null!=a.maxSpan?fI(a.maxSpan,o,r,!0):null);var s=this._range,l=this._range=pI([fI(n[0],r,o,!0),fI(n[1],r,o,!0)]);return!s||s[0]!==l[0]||s[1]!==l[1]},_updateView:function(t){var e=this._displayables,i=this._handleEnds,n=pI(i.slice()),r=this._size;vI([0,1],function(t){var n=e.handles[t],a=this._handleHeight;n.attr({scale:[a/2,a/2],position:[i[t],r[1]/2-a/2]})},this),e.filler.setShape({x:n[0],y:0,width:n[1]-n[0],height:r[1]}),this._updateDataInfo(t)},_updateDataInfo:function(t){function e(t){var e=za(n.handles[t].parent,this.group),i=Ra(0===t?"right":"left",e),s=this._handleWidth/2+bI,l=Ea([c[t]+(0===t?-s:s),this._size[1]/2],e);r[t].setStyle({x:l[0],y:l[1],textVerticalAlign:a===_I?"middle":i,textAlign:a===_I?i:"center",text:o[t]})}var i=this.dataZoomModel,n=this._displayables,r=n.handleLabels,a=this._orient,o=["",""];if(i.get("showDetail")){var s=i.findRepresentativeAxisProxy();if(s){var l=s.getAxisModel().axis,h=this._range,u=t?s.calculateDataWindow({start:h[0],end:h[1]}).valueWindow:s.getDataValueWindow();o=[this._formatLabel(u[0],l),this._formatLabel(u[1],l)]}}var c=pI(this._handleEnds.slice());e.call(this,0),e.call(this,1)},_formatLabel:function(t,e){var i=this.dataZoomModel,n=i.get("labelFormatter"),r=i.get("labelPrecision");(null==r||"auto"===r)&&(r=e.getPixelPrecision());var a=null==t||isNaN(t)?"":"category"===e.type||"time"===e.type?e.scale.getLabel(Math.round(t)):t.toFixed(Math.min(r,20));return w(n)?n(t,a):b(n)?n.replace("{value}",a):a},_showDataInfo:function(t){t=this._dragging||t;var e=this._displayables.handleLabels;e[0].attr("invisible",!t),e[1].attr("invisible",!t)},_onDragMove:function(t,e,i){this._dragging=!0;var n=this._displayables.barGroup.getLocalTransform(),r=Ea([e,i],n,!0),a=this._updateInterval(t,r[0]),o=this.dataZoomModel.get("realtime");this._updateView(!o),a&&o&&this._dispatchZoomAction()},_onDragEnd:function(){this._dragging=!1,this._showDataInfo(!1);var t=this.dataZoomModel.get("realtime");!t&&this._dispatchZoomAction()},_onClickPanelClick:function(t){var e=this._size,i=this._displayables.barGroup.transformCoordToLocal(t.offsetX,t.offsetY);if(!(i[0]<0||i[0]>e[0]||i[1]<0||i[1]>e[1])){var n=this._handleEnds,r=(n[0]+n[1])/2,a=this._updateInterval("all",i[0]-r);this._updateView(),a&&this._dispatchZoomAction()}},_dispatchZoomAction:function(){var t=this._range;this.api.dispatchAction({type:"dataZoom",from:this.uid,dataZoomId:this.dataZoomModel.id,start:t[0],end:t[1]})},_findCoordRect:function(){var t;if(vI(this.getTargetCoordInfo(),function(e){if(!t&&e.length){var i=e[0].model.coordinateSystem;t=i.getRect&&i.getRect()}}),!t){var e=this.api.getWidth(),i=this.api.getHeight();t={x:.2*e,y:.2*i,width:.6*e,height:.6*i}}return t}});hI.extend({type:"dataZoom.inside",defaultOption:{disabled:!1,zoomLock:!1,zoomOnMouseWheel:!0,moveOnMouseMove:!0,moveOnMouseWheel:!1,preventDefaultMouseMove:!0}});var II="\x00_ec_interaction_mutex";Ul({type:"takeGlobalCursor",event:"globalCursorTaken",update:"update"},function(){}),c(Ap,bg);var TI="\x00_ec_dataZoom_roams",CI=y,AI=uI.extend({type:"dataZoom.inside",init:function(){this._range},render:function(t,e,i){AI.superApply(this,"render",arguments),this._range=t.getPercentRange(),f(this.getTargetCoordInfo(),function(e,n){var r=p(e,function(t){return Fp(t.model)});f(e,function(e){var a=e.model,o={};f(["pan","zoom","scrollMove"],function(t){o[t]=CI(DI[t],this,e,n)},this),Bp(i,{coordId:Fp(a),allCoordIds:r,containsPoint:function(t,e,i){return a.coordinateSystem.containPoint([e,i])},dataZoomId:t.id,dataZoomModel:t,getRange:o})},this)},this)},dispose:function(){Np(this.api,this.dataZoomModel.id),AI.superApply(this,"dispose",arguments),this._range=null}}),DI={zoom:function(t,e,i,n){var r=this._range,a=r.slice(),o=t.axisModels[0];if(o){var s=kI[e](null,[n.originX,n.originY],o,i,t),l=(s.signal>0?s.pixelStart+s.pixelLength-s.pixel:s.pixel-s.pixelStart)/s.pixelLength*(a[1]-a[0])+a[0],h=Math.max(1/n.scale,0);a[0]=(a[0]-l)*h+l,a[1]=(a[1]-l)*h+l;var u=this.dataZoomModel.findRepresentativeAxisProxy().getMinMaxSpan();return cI(0,a,[0,100],0,u.minSpan,u.maxSpan),this._range=a,r[0]!==a[0]||r[1]!==a[1]?a:void 0}},pan:Xp(function(t,e,i,n,r,a){var o=kI[n]([a.oldX,a.oldY],[a.newX,a.newY],e,r,i);return o.signal*(t[1]-t[0])*o.pixel/o.pixelLength}),scrollMove:Xp(function(t,e,i,n,r,a){var o=kI[n]([0,0],[a.scrollDelta,a.scrollDelta],e,r,i);return o.signal*(t[1]-t[0])*a.scrollDelta})},kI={grid:function(t,e,i,n,r){var a=i.axis,o={},s=r.model.coordinateSystem.getRect();return t=t||[0,0],"x"===a.dim?(o.pixel=e[0]-t[0],o.pixelLength=s.width,o.pixelStart=s.x,o.signal=a.inverse?1:-1):(o.pixel=e[1]-t[1],o.pixelLength=s.height,o.pixelStart=s.y,o.signal=a.inverse?-1:1),o},polar:function(t,e,i,n,r){var a=i.axis,o={},s=r.model.coordinateSystem,l=s.getRadiusAxis().getExtent(),h=s.getAngleAxis().getExtent();return t=t?s.pointToCoord(t):[0,0],e=s.pointToCoord(e),"radiusAxis"===i.mainType?(o.pixel=e[0]-t[0],o.pixelLength=l[1]-l[0],o.pixelStart=l[0],o.signal=a.inverse?1:-1):(o.pixel=e[1]-t[1],o.pixelLength=h[1]-h[0],o.pixelStart=h[0],o.signal=a.inverse?-1:1),o},singleAxis:function(t,e,i,n,r){var a=i.axis,o=r.model.coordinateSystem.getRect(),s={};return t=t||[0,0],"horizontal"===a.orient?(s.pixel=e[0]-t[0],s.pixelLength=o.width,s.pixelStart=o.x,s.signal=a.inverse?1:-1):(s.pixel=e[1]-t[1],s.pixelLength=o.height,s.pixelStart=o.y,s.signal=a.inverse?-1:1),s}};jl({getTargetSeries:function(t){var e=N();return t.eachComponent("dataZoom",function(t){t.eachTargetAxis(function(t,i,n){var r=n.getAxisProxy(t.name,i);f(r.getTargetSeriesModels(),function(t){e.set(t.uid,t)})})}),e},modifyOutputEnd:!0,overallReset:function(t,e){t.eachComponent("dataZoom",function(t){t.eachTargetAxis(function(t,i,n){n.getAxisProxy(t.name,i).reset(n,e)}),t.eachTargetAxis(function(t,i,n){n.getAxisProxy(t.name,i).filterData(n,e)})}),t.eachComponent("dataZoom",function(t){var e=t.findRepresentativeAxisProxy(),i=e.getDataPercentWindow(),n=e.getDataValueWindow();t.setRawRange({start:i[0],end:i[1],startValue:n[0],endValue:n[1]},!0)})}}),Ul("dataZoom",function(t,e){var i=gp(y(e.eachComponent,e,"dataZoom"),nI,function(t,e){return t.get(e.axisIndex)}),n=[];e.eachComponent({mainType:"dataZoom",query:t},function(t){n.push.apply(n,i(t).nodes)}),f(n,function(e){e.setRawRange({start:t.start,end:t.end,startValue:t.startValue,endValue:t.endValue})})});var PI,LI="urn:schemas-microsoft-com:vml",OI="undefined"==typeof window?null:window,zI=!1,EI=OI&&OI.document;if(EI&&!tg.canvasSupported)try{!EI.namespaces.zrvml&&EI.namespaces.add("zrvml",LI),PI=function(t){return EI.createElement("')}}catch(RI){PI=function(t){return EI.createElement("<"+t+' xmlns="'+LI+'" class="zrvml">')}}var BI=qm.CMD,NI=Math.round,FI=Math.sqrt,VI=Math.abs,WI=Math.cos,GI=Math.sin,HI=Math.max;if(!tg.canvasSupported){var ZI=",",XI="progid:DXImageTransform.Microsoft",YI=21600,jI=YI/2,qI=1e5,UI=1e3,$I=function(t){t.style.cssText="position:absolute;left:0;top:0;width:1px;height:1px;",t.coordsize=YI+","+YI,t.coordorigin="0,0"},KI=function(t){return String(t).replace(/&/g,"&").replace(/"/g,""")},QI=function(t,e,i){return"rgb("+[t,e,i].join(",")+")"},JI=function(t,e){e&&t&&e.parentNode!==t&&t.appendChild(e)},tT=function(t,e){e&&t&&e.parentNode===t&&t.removeChild(e)},eT=function(t,e,i){return(parseFloat(t)||0)*qI+(parseFloat(e)||0)*UI+i},iT=function(t,e){return"string"==typeof t?t.lastIndexOf("%")>=0?parseFloat(t)/100*e:parseFloat(t):t},nT=function(t,e,i){var n=He(e);i=+i,isNaN(i)&&(i=1),n&&(t.color=QI(n[0],n[1],n[2]),t.opacity=i*n[3])},rT=function(t){var e=He(t);return[QI(e[0],e[1],e[2]),e[3]]},aT=function(t,e,i){var n=e.fill;if(null!=n)if(n instanceof Ey){var r,a=0,o=[0,0],s=0,l=1,h=i.getBoundingRect(),u=h.width,c=h.height;if("linear"===n.type){r="gradient";var d=i.transform,f=[n.x*u,n.y*c],p=[n.x2*u,n.y2*c];d&&(ae(f,f,d),ae(p,p,d));var g=p[0]-f[0],v=p[1]-f[1];a=180*Math.atan2(g,v)/Math.PI,0>a&&(a+=360),1e-6>a&&(a=0)}else{r="gradientradial";var f=[n.x*u,n.y*c],d=i.transform,m=i.scale,y=u,x=c;o=[(f[0]-h.x)/y,(f[1]-h.y)/x],d&&ae(f,f,d),y/=m[0]*YI,x/=m[1]*YI;var _=HI(y,x);s=0/_,l=2*n.r/_-s}var w=n.colorStops.slice();w.sort(function(t,e){return t.offset-e.offset});for(var b=w.length,S=[],M=[],I=0;b>I;I++){var T=w[I],C=rT(T.color);M.push(T.offset*l+s+" "+C[0]),(0===I||I===b-1)&&S.push(C)}if(b>=2){var A=S[0][0],D=S[1][0],k=S[0][1]*e.opacity,P=S[1][1]*e.opacity;t.type=r,t.method="none",t.focus="100%",t.angle=a,t.color=A,t.color2=D,t.colors=M.join(","),t.opacity=P,t.opacity2=k}"radial"===r&&(t.focusposition=o.join(","))}else nT(t,n,e.opacity)},oT=function(t,e){null!=e.lineDash&&(t.dashstyle=e.lineDash.join(" ")),null==e.stroke||e.stroke instanceof Ey||nT(t,e.stroke,e.opacity)},sT=function(t,e,i,n){var r="fill"==e,a=t.getElementsByTagName(e)[0];null!=i[e]&&"none"!==i[e]&&(r||!r&&i.lineWidth)?(t[r?"filled":"stroked"]="true",i[e]instanceof Ey&&tT(t,a),a||(a=Yp(e)),r?aT(a,i,n):oT(a,i),JI(t,a)):(t[r?"filled":"stroked"]="false",tT(t,a))},lT=[[],[],[]],hT=function(t,e){var i,n,r,a,o,s,l=BI.M,h=BI.C,u=BI.L,c=BI.A,d=BI.Q,f=[],p=t.data,g=t.len();for(a=0;g>a;){switch(r=p[a++],n="",i=0,r){case l:n=" m ",i=1,o=p[a++],s=p[a++],lT[0][0]=o,lT[0][1]=s;break;case u:n=" l ",i=1,o=p[a++],s=p[a++],lT[0][0]=o,lT[0][1]=s;break;case d:case h:n=" c ",i=3;var v,m,y=p[a++],x=p[a++],_=p[a++],w=p[a++];r===d?(v=_,m=w,_=(_+2*y)/3,w=(w+2*x)/3,y=(o+2*y)/3,x=(s+2*x)/3):(v=p[a++],m=p[a++]),lT[0][0]=y,lT[0][1]=x,lT[1][0]=_,lT[1][1]=w,lT[2][0]=v,lT[2][1]=m,o=v,s=m;break;case c:var b=0,S=0,M=1,I=1,T=0;e&&(b=e[4],S=e[5],M=FI(e[0]*e[0]+e[1]*e[1]),I=FI(e[2]*e[2]+e[3]*e[3]),T=Math.atan2(-e[1]/I,e[0]/M));var C=p[a++],A=p[a++],D=p[a++],k=p[a++],P=p[a++]+T,L=p[a++]+P+T;a++;var O=p[a++],z=C+WI(P)*D,E=A+GI(P)*k,y=C+WI(L)*D,x=A+GI(L)*k,R=O?" wa ":" at ";Math.abs(z-y)<1e-4&&(Math.abs(L-P)>.01?O&&(z+=270/YI):Math.abs(E-A)<1e-4?O&&C>z||!O&&z>C?x-=270/YI:x+=270/YI:O&&A>E||!O&&E>A?y+=270/YI:y-=270/YI),f.push(R,NI(((C-D)*M+b)*YI-jI),ZI,NI(((A-k)*I+S)*YI-jI),ZI,NI(((C+D)*M+b)*YI-jI),ZI,NI(((A+k)*I+S)*YI-jI),ZI,NI((z*M+b)*YI-jI),ZI,NI((E*I+S)*YI-jI),ZI,NI((y*M+b)*YI-jI),ZI,NI((x*I+S)*YI-jI)),o=y,s=x;break;case BI.R:var B=lT[0],N=lT[1];B[0]=p[a++],B[1]=p[a++],N[0]=B[0]+p[a++],N[1]=B[1]+p[a++],e&&(ae(B,B,e),ae(N,N,e)),B[0]=NI(B[0]*YI-jI),N[0]=NI(N[0]*YI-jI),B[1]=NI(B[1]*YI-jI),N[1]=NI(N[1]*YI-jI),f.push(" m ",B[0],ZI,B[1]," l ",N[0],ZI,B[1]," l ",N[0],ZI,N[1]," l ",B[0],ZI,N[1]);break;case BI.Z:f.push(" x ")}if(i>0){f.push(n);for(var F=0;i>F;F++){var V=lT[F];e&&ae(V,V,e),f.push(NI(V[0]*YI-jI),ZI,NI(V[1]*YI-jI),i-1>F?ZI:"")}}}return f.join("")};Fr.prototype.brushVML=function(t){var e=this.style,i=this._vmlEl;i||(i=Yp("shape"),$I(i),this._vmlEl=i),sT(i,"fill",e,this),sT(i,"stroke",e,this);var n=this.transform,r=null!=n,a=i.getElementsByTagName("stroke")[0];if(a){var o=e.lineWidth;if(r&&!e.strokeNoScale){var s=n[0]*n[3]-n[1]*n[2];o*=FI(VI(s))}a.weight=o+"px"}var l=this.path||(this.path=new qm);this.__dirtyPath&&(l.beginPath(),this.buildPath(l,this.shape),l.toStatic(),this.__dirtyPath=!1),i.path=hT(l,this.transform),i.style.zIndex=eT(this.zlevel,this.z,this.z2),JI(t,i),null!=e.text?this.drawRectText(t,this.getBoundingRect()):this.removeRectText(t)},Fr.prototype.onRemove=function(t){tT(t,this._vmlEl),this.removeRectText(t)},Fr.prototype.onAdd=function(t){JI(t,this._vmlEl),this.appendRectText(t)};var uT=function(t){return"object"==typeof t&&t.tagName&&"IMG"===t.tagName.toUpperCase()};yn.prototype.brushVML=function(t){var e,i,n=this.style,r=n.image;if(uT(r)){var a=r.src;if(a===this._imageSrc)e=this._imageWidth,i=this._imageHeight;else{var o=r.runtimeStyle,s=o.width,l=o.height;o.width="auto",o.height="auto",e=r.width,i=r.height,o.width=s,o.height=l,this._imageSrc=a,this._imageWidth=e,this._imageHeight=i}r=a}else r===this._imageSrc&&(e=this._imageWidth,i=this._imageHeight);if(r){var h=n.x||0,u=n.y||0,c=n.width,d=n.height,f=n.sWidth,p=n.sHeight,g=n.sx||0,v=n.sy||0,m=f&&p,y=this._vmlEl;y||(y=EI.createElement("div"),$I(y),this._vmlEl=y);var x,_=y.style,w=!1,b=1,S=1;if(this.transform&&(x=this.transform,b=FI(x[0]*x[0]+x[1]*x[1]),S=FI(x[2]*x[2]+x[3]*x[3]),w=x[1]||x[2]),w){var M=[h,u],I=[h+c,u],T=[h,u+d],C=[h+c,u+d];ae(M,M,x),ae(I,I,x),ae(T,T,x),ae(C,C,x);var A=HI(M[0],I[0],T[0],C[0]),D=HI(M[1],I[1],T[1],C[1]),k=[];k.push("M11=",x[0]/b,ZI,"M12=",x[2]/S,ZI,"M21=",x[1]/b,ZI,"M22=",x[3]/S,ZI,"Dx=",NI(h*b+x[4]),ZI,"Dy=",NI(u*S+x[5])),_.padding="0 "+NI(A)+"px "+NI(D)+"px 0",_.filter=XI+".Matrix("+k.join("")+", SizingMethod=clip)"}else x&&(h=h*b+x[4],u=u*S+x[5]),_.filter="",_.left=NI(h)+"px",_.top=NI(u)+"px";var P=this._imageEl,L=this._cropEl;P||(P=EI.createElement("div"),this._imageEl=P);var O=P.style;if(m){if(e&&i)O.width=NI(b*e*c/f)+"px",O.height=NI(S*i*d/p)+"px";else{var z=new Image,E=this;z.onload=function(){z.onload=null,e=z.width,i=z.height,O.width=NI(b*e*c/f)+"px",O.height=NI(S*i*d/p)+"px",E._imageWidth=e,E._imageHeight=i,E._imageSrc=r},z.src=r}L||(L=EI.createElement("div"),L.style.overflow="hidden",this._cropEl=L);var R=L.style;R.width=NI((c+g*c/f)*b),R.height=NI((d+v*d/p)*S),R.filter=XI+".Matrix(Dx="+-g*c/f*b+",Dy="+-v*d/p*S+")",L.parentNode||y.appendChild(L),P.parentNode!=L&&L.appendChild(P)}else O.width=NI(b*c)+"px",O.height=NI(S*d)+"px",y.appendChild(P),L&&L.parentNode&&(y.removeChild(L),this._cropEl=null);var B="",N=n.opacity;1>N&&(B+=".Alpha(opacity="+NI(100*N)+") "),B+=XI+".AlphaImageLoader(src="+r+", SizingMethod=scale)",O.filter=B,y.style.zIndex=eT(this.zlevel,this.z,this.z2),JI(t,y),null!=n.text&&this.drawRectText(t,this.getBoundingRect())}},yn.prototype.onRemove=function(t){tT(t,this._vmlEl),this._vmlEl=null,this._cropEl=null,this._imageEl=null,this.removeRectText(t)},yn.prototype.onAdd=function(t){JI(t,this._vmlEl),this.appendRectText(t)};var cT,dT="normal",fT={},pT=0,gT=100,vT=document.createElement("div"),mT=function(t){var e=fT[t];if(!e){pT>gT&&(pT=0,fT={});var i,n=vT.style;try{n.font=t,i=n.fontFamily.split(",")[0]}catch(r){}e={style:n.fontStyle||dT,variant:n.fontVariant||dT,weight:n.fontWeight||dT,size:0|parseFloat(n.fontSize||12),family:i||"Microsoft YaHei"},fT[t]=e,pT++}return e};Oi("measureText",function(t,e){var i=EI;cT||(cT=i.createElement("div"),cT.style.cssText="position:absolute;top:-20000px;left:0;padding:0;margin:0;border:none;white-space:pre;",EI.body.appendChild(cT));try{cT.style.font=e}catch(n){}return cT.innerHTML="",cT.appendChild(i.createTextNode(t)),{width:cT.offsetWidth}});for(var yT=new gi,xT=function(t,e,i,n){var r=this.style;this.__dirty&&Qi(r,!0);var a=r.text;if(null!=a&&(a+=""),a){if(r.rich){var o=qi(a,r);a=[];for(var s=0;s'; + + oriIcon += ''; + + var selectHtml = '
    ' + + '
    ' + + '
    ' + + '' + + oriIcon + + '' + + '' + + '
    ' + + '
    ' + + '
    ' + + '123' + + '
    '; + $(elem).after(selectHtml); + return a; + }, + /** + * 展开/折叠下拉框 + */ + toggleSelect: function () { + var item = '#' + TITLE_ID + ' .layui-iconpicker-item,#' + TITLE_ID + ' .layui-iconpicker-item .layui-edge'; + a.event('click', item, function (e) { + var $icon = $('#' + ICON_BODY); + if ($icon.hasClass(selected)) { + $icon.removeClass(selected).addClass(unselect); + } else { + // 隐藏其他picker + $('.layui-form-select').removeClass(selected); + // 显示当前picker + $icon.addClass(selected).removeClass(unselect); + } + e.stopPropagation(); + }); + return a; + }, + /** + * 绘制主体部分 + */ + createBody: function () { + // 获取数据 + var searchHtml = ''; + + if (search) { + searchHtml = ''; + } + + // 组合dom + var bodyHtml = '
    ' + + searchHtml + + '
    ' + + '
    '; + $('#' + ICON_BODY).find('.layui-anim').eq(0).html(bodyHtml); + a.search().createList().check().page(); + + return a; + }, + /** + * 绘制图标列表 + * @param text 模糊查询关键字 + * @returns {string} + */ + createList: function (text) { + var d = data, + l = d.length, + pageHtml = '', + listHtml = $('
    ')//'
    '; + + // 计算分页数据 + var _limit = limit, // 每页显示数量 + _pages = l % _limit === 0 ? l / _limit : parseInt(l / _limit + 1), // 总计多少页 + _id = PAGE_ID; + + // 图标列表 + var icons = []; + + for (var i = 0; i < l; i++) { + var obj = d[i]; + + // 判断是否模糊查询 + if (text && obj.indexOf(text) === -1) { + continue; + } + + // 是否自定义格子宽度 + var style = ''; + if (cellWidth !== null) { + style += ' style="width:' + cellWidth + '"'; + } + + // 每个图标dom + var icon = '
    '; + + icon += ''; + + icon += '
    '; + + icons.push(icon); + } + + // 查询出图标后再分页 + l = icons.length; + _pages = l % _limit === 0 ? l / _limit : parseInt(l / _limit + 1); + for (var i = 0; i < _pages; i++) { + // 按limit分块 + var lm = $('
    '); + + for (var j = i * _limit; j < (i + 1) * _limit && j < l; j++) { + lm.append(icons[j]); + } + + listHtml.append(lm); + } + + // 无数据 + if (l === 0) { + listHtml.append('

    无数据

    '); + } + + // 判断是否分页 + if (page) { + $('#' + PICKER_BODY).addClass('layui-iconpicker-body-page'); + pageHtml = '
    ' + + '
    ' + + '1/' + + '' + _pages + '' + + ' (' + l + ')' + + '
    ' + + '
    ' + + ' ' + + ' ' + + '
    ' + + '
    '; + } + + + $('#' + ICON_BODY).find('.layui-anim').find('.' + LIST_BOX).html('').append(listHtml).append(pageHtml); + return a; + }, + // 阻止Layui的一些默认事件 + preventEvent: function () { + var item = '#' + ICON_BODY + ' .layui-anim'; + a.event('click', item, function (e) { + e.stopPropagation(); + }); + return a; + }, + // 分页 + page: function () { + var icon = '#' + PAGE_ID + ' .layui-iconpicker-page-operate .layui-icon'; + + $(icon).unbind('click'); + a.event('click', icon, function (e) { + var elem = e.currentTarget, + total = parseInt($('#' + PAGE_ID + '-pages').html()), + isPrev = $(elem).attr('prev') !== undefined, + // 按钮上标的页码 + index = parseInt($(elem).attr('data-index')), + $cur = $('#' + PAGE_ID + '-current'), + // 点击时正在显示的页码 + current = parseInt($cur.html()); + + // 分页数据 + if (isPrev && current > 1) { + current = current - 1; + $(icon + '[prev]').attr('data-index', current); + } else if (!isPrev && current < total) { + current = current + 1; + $(icon + '[next]').attr('data-index', current); + } + $cur.html(current); + + // 图标数据 + $('#' + ICON_BODY + ' .layui-iconpicker-icon-limit').hide(); + $('#layui-iconpicker-icon-limit-' + tmp + current).show(); + e.stopPropagation(); + }); + return a; + }, + /** + * 搜索 + */ + search: function () { + var item = '#' + PICKER_BODY + ' .layui-iconpicker-search .layui-input'; + a.event('input propertychange', item, function (e) { + var elem = e.target, + t = $(elem).val(); + a.createList(t); + }); + return a; + }, + /** + * 点击选中图标 + */ + check: function () { + var item = '#' + PICKER_BODY + ' .layui-iconpicker-icon-item'; + a.event('click', item, function (e) { + var el = $(e.currentTarget).find('.fa'), + icon = ''; + + var clsArr = el.attr('class').split(/[\s\n]/), + cls = clsArr[1], + icon = cls; + $('#' + TITLE_ID).find('.layui-iconpicker-item .fa').html('').attr('class', clsArr.join(' ')); + + + $('#' + ICON_BODY).removeClass(selected).addClass(unselect); + $(elem).val(icon).attr('value', icon); + // 回调 + if (click) { + click({ + icon: icon + }); + } + + }); + return a; + }, + // 监听原始input数值改变 + inputListen: function () { + var el = $(elem); + a.event('change', elem, function () { + var value = el.val(); + }) + // el.change(function(){ + + // }); + return a; + }, + event: function (evt, el, fn) { + $(BODY).on(evt, el, fn); + } + }; + + var common = { + /** + * 加载样式表 + */ + loadCss: function () { + var css = '.layui-iconpicker {max-width: 280px;}.layui-iconpicker .layui-anim{display:none;position:absolute;left:0;top:42px;padding:5px 0;z-index:899;min-width:100%;border:1px solid #d2d2d2;max-height:300px;overflow-y:auto;background-color:#fff;border-radius:2px;box-shadow:0 2px 4px rgba(0,0,0,.12);box-sizing:border-box;}.layui-iconpicker-item{border:1px solid #e6e6e6;width:90px;height:38px;border-radius:4px;cursor:pointer;position:relative;}.layui-iconpicker-icon{border-right:1px solid #e6e6e6;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;display:block;width:60px;height:100%;float:left;text-align:center;background:#fff;transition:all .3s;}.layui-iconpicker-icon i{line-height:38px;font-size:18px;}.layui-iconpicker-item > .layui-edge{left:70px;}.layui-iconpicker-item:hover{border-color:#D2D2D2!important;}.layui-iconpicker-item:hover .layui-iconpicker-icon{border-color:#D2D2D2!important;}.layui-iconpicker.layui-form-selected .layui-anim{display:block;}.layui-iconpicker-body{padding:6px;}.layui-iconpicker .layui-iconpicker-list{background-color:#fff;border:1px solid #ccc;border-radius:4px;}.layui-iconpicker .layui-iconpicker-icon-item{display:inline-block;width:21.1%;line-height:36px;text-align:center;cursor:pointer;vertical-align:top;height:36px;margin:4px;border:1px solid #ddd;border-radius:2px;transition:300ms;}.layui-iconpicker .layui-iconpicker-icon-item i.layui-icon{font-size:17px;}.layui-iconpicker .layui-iconpicker-icon-item:hover{background-color:#eee;border-color:#ccc;-webkit-box-shadow:0 0 2px #aaa,0 0 2px #fff inset;-moz-box-shadow:0 0 2px #aaa,0 0 2px #fff inset;box-shadow:0 0 2px #aaa,0 0 2px #fff inset;text-shadow:0 0 1px #fff;}.layui-iconpicker-search{position:relative;margin:0 0 6px 0;border:1px solid #e6e6e6;border-radius:2px;transition:300ms;}.layui-iconpicker-search:hover{border-color:#D2D2D2!important;}.layui-iconpicker-search .layui-input{cursor:text;display:inline-block;width:86%;border:none;padding-right:0;margin-top:1px;}.layui-iconpicker-search .layui-icon{position:absolute;top:11px;right:4%;}.layui-iconpicker-tips{text-align:center;padding:8px 0;cursor:not-allowed;}.layui-iconpicker-page{margin-top:6px;margin-bottom:-6px;font-size:12px;padding:0 2px;}.layui-iconpicker-page-count{display:inline-block;}.layui-iconpicker-page-operate{display:inline-block;float:right;cursor:default;}.layui-iconpicker-page-operate .layui-icon{font-size:12px;cursor:pointer;}.layui-iconpicker-body-page .layui-iconpicker-icon-limit{display:none;}.layui-iconpicker-body-page .layui-iconpicker-icon-limit:first-child{display:block;}'; + var $style = $('head').find('style[iconpicker]'); + if ($style.length === 0) { + $('head').append(''); + } + }, + + /** + * 获取数据 + */ + getData: function (url) { + var iconlist = []; + $.ajax({ + url: url, + type: 'get', + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + async: false, + success: function (ret) { + var exp = /fa-var-(.*):/ig; + var result; + while ((result = exp.exec(ret)) != null) { + iconlist.push('fa-' + result[1]); + } + }, + error: function (xhr, textstatus, thrown) { + layer.msg('fa图标接口有误'); + } + }); + return iconlist; + } + }; + + a.init(); + return new IconPicker(); + }; + + /** + * 选中图标 + * @param filter lay-filter + * @param iconName 图标名称,自动识别fontClass/unicode + */ + IconPicker.prototype.checkIcon = function (filter, iconName) { + var el = $('*[lay-filter=' + filter + ']'), + p = el.next().find('.layui-iconpicker-item .fa'), + c = iconName; + + if (c.indexOf('#xe') > 0) { + p.html(c); + } else { + p.html('').attr('class', 'fa ' + c); + } + el.attr('value', c).val(c); + }; + + var iconPicker = new IconPicker(); + exports(_MOD, iconPicker); +}); \ No newline at end of file diff --git a/public/static/layuimini/js/lay-module/layarea/layarea.js b/public/static/layuimini/js/lay-module/layarea/layarea.js new file mode 100644 index 0000000..d62e384 --- /dev/null +++ b/public/static/layuimini/js/lay-module/layarea/layarea.js @@ -0,0 +1,4040 @@ +layui.define(['layer', 'form', 'laytpl'], function (exports) { + "use strict"; + + let $ = layui.$ + , form = layui.form + , layarea = { + _id: 0 + , config: {} + , set: function (options) { + let that = this; + that.config = $.extend({}, that.config, options); + return that; + } + , on: function (events, callback) { + return layui.onevent.call(this, 'layarea', events, callback); + } + } + , thisArea = function () { + let that = this; + return { + layarea: function (files) { + that.layarea.call(that, files); + } + , config: that.config + } + } + , Class = function (options) { + let that = this; + that.config = $.extend({}, that.config, layarea.config, options); + that.render(); + }; + + let areaList = { + province_list: { + 110000: '北京市', + 120000: '天津市', + 130000: '河北省', + 140000: '山西省', + 150000: '内蒙古自治区', + 210000: '辽宁省', + 220000: '吉林省', + 230000: '黑龙江省', + 310000: '上海市', + 320000: '江苏省', + 330000: '浙江省', + 340000: '安徽省', + 350000: '福建省', + 360000: '江西省', + 370000: '山东省', + 410000: '河南省', + 420000: '湖北省', + 430000: '湖南省', + 440000: '广东省', + 450000: '广西壮族自治区', + 460000: '海南省', + 500000: '重庆市', + 510000: '四川省', + 520000: '贵州省', + 530000: '云南省', + 540000: '西藏自治区', + 610000: '陕西省', + 620000: '甘肃省', + 630000: '青海省', + 640000: '宁夏回族自治区', + 650000: '新疆维吾尔自治区', + 710000: '台湾省', + 810000: '香港特别行政区', + 820000: '澳门特别行政区', + 900000: '海外' + }, + city_list: { + 110100: '北京市', + 120100: '天津市', + 130100: '石家庄市', + 130200: '唐山市', + 130300: '秦皇岛市', + 130400: '邯郸市', + 130500: '邢台市', + 130600: '保定市', + 130700: '张家口市', + 130800: '承德市', + 130900: '沧州市', + 131000: '廊坊市', + 131100: '衡水市', + 139000: '省直辖县', + 140100: '太原市', + 140200: '大同市', + 140300: '阳泉市', + 140400: '长治市', + 140500: '晋城市', + 140600: '朔州市', + 140700: '晋中市', + 140800: '运城市', + 140900: '忻州市', + 141000: '临汾市', + 141100: '吕梁市', + 150100: '呼和浩特市', + 150200: '包头市', + 150300: '乌海市', + 150400: '赤峰市', + 150500: '通辽市', + 150600: '鄂尔多斯市', + 150700: '呼伦贝尔市', + 150800: '巴彦淖尔市', + 150900: '乌兰察布市', + 152200: '兴安盟', + 152500: '锡林郭勒盟', + 152900: '阿拉善盟', + 210100: '沈阳市', + 210200: '大连市', + 210300: '鞍山市', + 210400: '抚顺市', + 210500: '本溪市', + 210600: '丹东市', + 210700: '锦州市', + 210800: '营口市', + 210900: '阜新市', + 211000: '辽阳市', + 211100: '盘锦市', + 211200: '铁岭市', + 211300: '朝阳市', + 211400: '葫芦岛市', + 220100: '长春市', + 220200: '吉林市', + 220300: '四平市', + 220400: '辽源市', + 220500: '通化市', + 220600: '白山市', + 220700: '松原市', + 220800: '白城市', + 222400: '延边朝鲜族自治州', + 230100: '哈尔滨市', + 230200: '齐齐哈尔市', + 230300: '鸡西市', + 230400: '鹤岗市', + 230500: '双鸭山市', + 230600: '大庆市', + 230700: '伊春市', + 230800: '佳木斯市', + 230900: '七台河市', + 231000: '牡丹江市', + 231100: '黑河市', + 231200: '绥化市', + 232700: '大兴安岭地区', + 310100: '上海市', + 320100: '南京市', + 320200: '无锡市', + 320300: '徐州市', + 320400: '常州市', + 320500: '苏州市', + 320600: '南通市', + 320700: '连云港市', + 320800: '淮安市', + 320900: '盐城市', + 321000: '扬州市', + 321100: '镇江市', + 321200: '泰州市', + 321300: '宿迁市', + 330100: '杭州市', + 330200: '宁波市', + 330300: '温州市', + 330400: '嘉兴市', + 330500: '湖州市', + 330600: '绍兴市', + 330700: '金华市', + 330800: '衢州市', + 330900: '舟山市', + 331000: '台州市', + 331100: '丽水市', + 340100: '合肥市', + 340200: '芜湖市', + 340300: '蚌埠市', + 340400: '淮南市', + 340500: '马鞍山市', + 340600: '淮北市', + 340700: '铜陵市', + 340800: '安庆市', + 341000: '黄山市', + 341100: '滁州市', + 341200: '阜阳市', + 341300: '宿州市', + 341500: '六安市', + 341600: '亳州市', + 341700: '池州市', + 341800: '宣城市', + 350100: '福州市', + 350200: '厦门市', + 350300: '莆田市', + 350400: '三明市', + 350500: '泉州市', + 350600: '漳州市', + 350700: '南平市', + 350800: '龙岩市', + 350900: '宁德市', + 360100: '南昌市', + 360200: '景德镇市', + 360300: '萍乡市', + 360400: '九江市', + 360500: '新余市', + 360600: '鹰潭市', + 360700: '赣州市', + 360800: '吉安市', + 360900: '宜春市', + 361000: '抚州市', + 361100: '上饶市', + 370100: '济南市', + 370200: '青岛市', + 370300: '淄博市', + 370400: '枣庄市', + 370500: '东营市', + 370600: '烟台市', + 370700: '潍坊市', + 370800: '济宁市', + 370900: '泰安市', + 371000: '威海市', + 371100: '日照市', + 371200: '莱芜市', + 371300: '临沂市', + 371400: '德州市', + 371500: '聊城市', + 371600: '滨州市', + 371700: '菏泽市', + 410100: '郑州市', + 410200: '开封市', + 410300: '洛阳市', + 410400: '平顶山市', + 410500: '安阳市', + 410600: '鹤壁市', + 410700: '新乡市', + 410800: '焦作市', + 410900: '濮阳市', + 411000: '许昌市', + 411100: '漯河市', + 411200: '三门峡市', + 411300: '南阳市', + 411400: '商丘市', + 411500: '信阳市', + 411600: '周口市', + 411700: '驻马店市', + 419000: '省直辖县', + 420100: '武汉市', + 420200: '黄石市', + 420300: '十堰市', + 420500: '宜昌市', + 420600: '襄阳市', + 420700: '鄂州市', + 420800: '荆门市', + 420900: '孝感市', + 421000: '荆州市', + 421100: '黄冈市', + 421200: '咸宁市', + 421300: '随州市', + 422800: '恩施土家族苗族自治州', + 429000: '省直辖县', + 430100: '长沙市', + 430200: '株洲市', + 430300: '湘潭市', + 430400: '衡阳市', + 430500: '邵阳市', + 430600: '岳阳市', + 430700: '常德市', + 430800: '张家界市', + 430900: '益阳市', + 431000: '郴州市', + 431100: '永州市', + 431200: '怀化市', + 431300: '娄底市', + 433100: '湘西土家族苗族自治州', + 440100: '广州市', + 440200: '韶关市', + 440300: '深圳市', + 440400: '珠海市', + 440500: '汕头市', + 440600: '佛山市', + 440700: '江门市', + 440800: '湛江市', + 440900: '茂名市', + 441200: '肇庆市', + 441300: '惠州市', + 441400: '梅州市', + 441500: '汕尾市', + 441600: '河源市', + 441700: '阳江市', + 441800: '清远市', + 441900: '东莞市', + 442000: '中山市', + 445100: '潮州市', + 445200: '揭阳市', + 445300: '云浮市', + 450100: '南宁市', + 450200: '柳州市', + 450300: '桂林市', + 450400: '梧州市', + 450500: '北海市', + 450600: '防城港市', + 450700: '钦州市', + 450800: '贵港市', + 450900: '玉林市', + 451000: '百色市', + 451100: '贺州市', + 451200: '河池市', + 451300: '来宾市', + 451400: '崇左市', + 460100: '海口市', + 460200: '三亚市', + 460300: '三沙市', + 460400: '儋州市', + 469000: '省直辖县', + 500100: '重庆市', + 500200: '县', + 510100: '成都市', + 510300: '自贡市', + 510400: '攀枝花市', + 510500: '泸州市', + 510600: '德阳市', + 510700: '绵阳市', + 510800: '广元市', + 510900: '遂宁市', + 511000: '内江市', + 511100: '乐山市', + 511300: '南充市', + 511400: '眉山市', + 511500: '宜宾市', + 511600: '广安市', + 511700: '达州市', + 511800: '雅安市', + 511900: '巴中市', + 512000: '资阳市', + 513200: '阿坝藏族羌族自治州', + 513300: '甘孜藏族自治州', + 513400: '凉山彝族自治州', + 520100: '贵阳市', + 520200: '六盘水市', + 520300: '遵义市', + 520400: '安顺市', + 520500: '毕节市', + 520600: '铜仁市', + 522300: '黔西南布依族苗族自治州', + 522600: '黔东南苗族侗族自治州', + 522700: '黔南布依族苗族自治州', + 530100: '昆明市', + 530300: '曲靖市', + 530400: '玉溪市', + 530500: '保山市', + 530600: '昭通市', + 530700: '丽江市', + 530800: '普洱市', + 530900: '临沧市', + 532300: '楚雄彝族自治州', + 532500: '红河哈尼族彝族自治州', + 532600: '文山壮族苗族自治州', + 532800: '西双版纳傣族自治州', + 532900: '大理白族自治州', + 533100: '德宏傣族景颇族自治州', + 533300: '怒江傈僳族自治州', + 533400: '迪庆藏族自治州', + 540100: '拉萨市', + 540200: '日喀则市', + 540300: '昌都市', + 540400: '林芝市', + 540500: '山南市', + 540600: '那曲市', + 542500: '阿里地区', + 610100: '西安市', + 610200: '铜川市', + 610300: '宝鸡市', + 610400: '咸阳市', + 610500: '渭南市', + 610600: '延安市', + 610700: '汉中市', + 610800: '榆林市', + 610900: '安康市', + 611000: '商洛市', + 620100: '兰州市', + 620200: '嘉峪关市', + 620300: '金昌市', + 620400: '白银市', + 620500: '天水市', + 620600: '武威市', + 620700: '张掖市', + 620800: '平凉市', + 620900: '酒泉市', + 621000: '庆阳市', + 621100: '定西市', + 621200: '陇南市', + 622900: '临夏回族自治州', + 623000: '甘南藏族自治州', + 630100: '西宁市', + 630200: '海东市', + 632200: '海北藏族自治州', + 632300: '黄南藏族自治州', + 632500: '海南藏族自治州', + 632600: '果洛藏族自治州', + 632700: '玉树藏族自治州', + 632800: '海西蒙古族藏族自治州', + 640100: '银川市', + 640200: '石嘴山市', + 640300: '吴忠市', + 640400: '固原市', + 640500: '中卫市', + 650100: '乌鲁木齐市', + 650200: '克拉玛依市', + 650400: '吐鲁番市', + 650500: '哈密市', + 652300: '昌吉回族自治州', + 652700: '博尔塔拉蒙古自治州', + 652800: '巴音郭楞蒙古自治州', + 652900: '阿克苏地区', + 653000: '克孜勒苏柯尔克孜自治州', + 653100: '喀什地区', + 653200: '和田地区', + 654000: '伊犁哈萨克自治州', + 654200: '塔城地区', + 654300: '阿勒泰地区', + 659000: '自治区直辖县级行政区划', + 710100: '台北市', + 710200: '高雄市', + 710300: '台南市', + 710400: '台中市', + 710500: '金门县', + 710600: '南投县', + 710700: '基隆市', + 710800: '新竹市', + 710900: '嘉义市', + 711100: '新北市', + 711200: '宜兰县', + 711300: '新竹县', + 711400: '桃园县', + 711500: '苗栗县', + 711700: '彰化县', + 711900: '嘉义县', + 712100: '云林县', + 712400: '屏东县', + 712500: '台东县', + 712600: '花莲县', + 712700: '澎湖县', + 712800: '连江县', + 810100: '香港岛', + 810200: '九龙', + 810300: '新界', + 820100: '澳门半岛', + 820200: '离岛', + 912400: '加拿大', + 941000: '韩国', + 984000: '美国' + }, + county_list: { + 110101: '东城区', + 110102: '西城区', + 110105: '朝阳区', + 110106: '丰台区', + 110107: '石景山区', + 110108: '海淀区', + 110109: '门头沟区', + 110111: '房山区', + 110112: '通州区', + 110113: '顺义区', + 110114: '昌平区', + 110115: '大兴区', + 110116: '怀柔区', + 110117: '平谷区', + 110118: '密云区', + 110119: '延庆区', + 120101: '和平区', + 120102: '河东区', + 120103: '河西区', + 120104: '南开区', + 120105: '河北区', + 120106: '红桥区', + 120110: '东丽区', + 120111: '西青区', + 120112: '津南区', + 120113: '北辰区', + 120114: '武清区', + 120115: '宝坻区', + 120116: '滨海新区', + 120117: '宁河区', + 120118: '静海区', + 120119: '蓟州区', + 130102: '长安区', + 130104: '桥西区', + 130105: '新华区', + 130107: '井陉矿区', + 130108: '裕华区', + 130109: '藁城区', + 130110: '鹿泉区', + 130111: '栾城区', + 130121: '井陉县', + 130123: '正定县', + 130125: '行唐县', + 130126: '灵寿县', + 130127: '高邑县', + 130128: '深泽县', + 130129: '赞皇县', + 130130: '无极县', + 130131: '平山县', + 130132: '元氏县', + 130133: '赵县', + 130181: '辛集市', + 130183: '晋州市', + 130184: '新乐市', + 130202: '路南区', + 130203: '路北区', + 130204: '古冶区', + 130205: '开平区', + 130207: '丰南区', + 130208: '丰润区', + 130209: '曹妃甸区', + 130223: '滦县', + 130224: '滦南县', + 130225: '乐亭县', + 130227: '迁西县', + 130229: '玉田县', + 130281: '遵化市', + 130283: '迁安市', + 130302: '海港区', + 130303: '山海关区', + 130304: '北戴河区', + 130306: '抚宁区', + 130321: '青龙满族自治县', + 130322: '昌黎县', + 130324: '卢龙县', + 130390: '经济技术开发区', + 130402: '邯山区', + 130403: '丛台区', + 130404: '复兴区', + 130406: '峰峰矿区', + 130407: '肥乡区', + 130408: '永年区', + 130423: '临漳县', + 130424: '成安县', + 130425: '大名县', + 130426: '涉县', + 130427: '磁县', + 130430: '邱县', + 130431: '鸡泽县', + 130432: '广平县', + 130433: '馆陶县', + 130434: '魏县', + 130435: '曲周县', + 130481: '武安市', + 130502: '桥东区', + 130503: '桥西区', + 130521: '邢台县', + 130522: '临城县', + 130523: '内丘县', + 130524: '柏乡县', + 130525: '隆尧县', + 130526: '任县', + 130527: '南和县', + 130528: '宁晋县', + 130529: '巨鹿县', + 130530: '新河县', + 130531: '广宗县', + 130532: '平乡县', + 130533: '威县', + 130534: '清河县', + 130535: '临西县', + 130581: '南宫市', + 130582: '沙河市', + 130602: '竞秀区', + 130606: '莲池区', + 130607: '满城区', + 130608: '清苑区', + 130609: '徐水区', + 130623: '涞水县', + 130624: '阜平县', + 130626: '定兴县', + 130627: '唐县', + 130628: '高阳县', + 130629: '容城县', + 130630: '涞源县', + 130631: '望都县', + 130632: '安新县', + 130633: '易县', + 130634: '曲阳县', + 130635: '蠡县', + 130636: '顺平县', + 130637: '博野县', + 130638: '雄县', + 130681: '涿州市', + 130682: '定州市', + 130683: '安国市', + 130684: '高碑店市', + 130702: '桥东区', + 130703: '桥西区', + 130705: '宣化区', + 130706: '下花园区', + 130708: '万全区', + 130709: '崇礼区', + 130722: '张北县', + 130723: '康保县', + 130724: '沽源县', + 130725: '尚义县', + 130726: '蔚县', + 130727: '阳原县', + 130728: '怀安县', + 130730: '怀来县', + 130731: '涿鹿县', + 130732: '赤城县', + 130802: '双桥区', + 130803: '双滦区', + 130804: '鹰手营子矿区', + 130821: '承德县', + 130822: '兴隆县', + 130824: '滦平县', + 130825: '隆化县', + 130826: '丰宁满族自治县', + 130827: '宽城满族自治县', + 130828: '围场满族蒙古族自治县', + 130881: '平泉市', + 130902: '新华区', + 130903: '运河区', + 130921: '沧县', + 130922: '青县', + 130923: '东光县', + 130924: '海兴县', + 130925: '盐山县', + 130926: '肃宁县', + 130927: '南皮县', + 130928: '吴桥县', + 130929: '献县', + 130930: '孟村回族自治县', + 130981: '泊头市', + 130982: '任丘市', + 130983: '黄骅市', + 130984: '河间市', + 131002: '安次区', + 131003: '广阳区', + 131022: '固安县', + 131023: '永清县', + 131024: '香河县', + 131025: '大城县', + 131026: '文安县', + 131028: '大厂回族自治县', + 131081: '霸州市', + 131082: '三河市', + 131090: '开发区', + 131102: '桃城区', + 131103: '冀州区', + 131121: '枣强县', + 131122: '武邑县', + 131123: '武强县', + 131124: '饶阳县', + 131125: '安平县', + 131126: '故城县', + 131127: '景县', + 131128: '阜城县', + 131182: '深州市', + 140105: '小店区', + 140106: '迎泽区', + 140107: '杏花岭区', + 140108: '尖草坪区', + 140109: '万柏林区', + 140110: '晋源区', + 140121: '清徐县', + 140122: '阳曲县', + 140123: '娄烦县', + 140181: '古交市', + 140202: '城区', + 140203: '矿区', + 140211: '南郊区', + 140212: '新荣区', + 140221: '阳高县', + 140222: '天镇县', + 140223: '广灵县', + 140224: '灵丘县', + 140225: '浑源县', + 140226: '左云县', + 140227: '大同县', + 140302: '城区', + 140303: '矿区', + 140311: '郊区', + 140321: '平定县', + 140322: '盂县', + 140402: '城区', + 140411: '郊区', + 140421: '长治县', + 140423: '襄垣县', + 140424: '屯留县', + 140425: '平顺县', + 140426: '黎城县', + 140427: '壶关县', + 140428: '长子县', + 140429: '武乡县', + 140430: '沁县', + 140431: '沁源县', + 140481: '潞城市', + 140502: '城区', + 140521: '沁水县', + 140522: '阳城县', + 140524: '陵川县', + 140525: '泽州县', + 140581: '高平市', + 140602: '朔城区', + 140603: '平鲁区', + 140621: '山阴县', + 140622: '应县', + 140623: '右玉县', + 140624: '怀仁县', + 140702: '榆次区', + 140721: '榆社县', + 140722: '左权县', + 140723: '和顺县', + 140724: '昔阳县', + 140725: '寿阳县', + 140726: '太谷县', + 140727: '祁县', + 140728: '平遥县', + 140729: '灵石县', + 140781: '介休市', + 140802: '盐湖区', + 140821: '临猗县', + 140822: '万荣县', + 140823: '闻喜县', + 140824: '稷山县', + 140825: '新绛县', + 140826: '绛县', + 140827: '垣曲县', + 140828: '夏县', + 140829: '平陆县', + 140830: '芮城县', + 140881: '永济市', + 140882: '河津市', + 140902: '忻府区', + 140921: '定襄县', + 140922: '五台县', + 140923: '代县', + 140924: '繁峙县', + 140925: '宁武县', + 140926: '静乐县', + 140927: '神池县', + 140928: '五寨县', + 140929: '岢岚县', + 140930: '河曲县', + 140931: '保德县', + 140932: '偏关县', + 140981: '原平市', + 141002: '尧都区', + 141021: '曲沃县', + 141022: '翼城县', + 141023: '襄汾县', + 141024: '洪洞县', + 141025: '古县', + 141026: '安泽县', + 141027: '浮山县', + 141028: '吉县', + 141029: '乡宁县', + 141030: '大宁县', + 141031: '隰县', + 141032: '永和县', + 141033: '蒲县', + 141034: '汾西县', + 141081: '侯马市', + 141082: '霍州市', + 141102: '离石区', + 141121: '文水县', + 141122: '交城县', + 141123: '兴县', + 141124: '临县', + 141125: '柳林县', + 141126: '石楼县', + 141127: '岚县', + 141128: '方山县', + 141129: '中阳县', + 141130: '交口县', + 141181: '孝义市', + 141182: '汾阳市', + 150102: '新城区', + 150103: '回民区', + 150104: '玉泉区', + 150105: '赛罕区', + 150121: '土默特左旗', + 150122: '托克托县', + 150123: '和林格尔县', + 150124: '清水河县', + 150125: '武川县', + 150202: '东河区', + 150203: '昆都仑区', + 150204: '青山区', + 150205: '石拐区', + 150206: '白云鄂博矿区', + 150207: '九原区', + 150221: '土默特右旗', + 150222: '固阳县', + 150223: '达尔罕茂明安联合旗', + 150302: '海勃湾区', + 150303: '海南区', + 150304: '乌达区', + 150402: '红山区', + 150403: '元宝山区', + 150404: '松山区', + 150421: '阿鲁科尔沁旗', + 150422: '巴林左旗', + 150423: '巴林右旗', + 150424: '林西县', + 150425: '克什克腾旗', + 150426: '翁牛特旗', + 150428: '喀喇沁旗', + 150429: '宁城县', + 150430: '敖汉旗', + 150502: '科尔沁区', + 150521: '科尔沁左翼中旗', + 150522: '科尔沁左翼后旗', + 150523: '开鲁县', + 150524: '库伦旗', + 150525: '奈曼旗', + 150526: '扎鲁特旗', + 150581: '霍林郭勒市', + 150602: '东胜区', + 150603: '康巴什区', + 150621: '达拉特旗', + 150622: '准格尔旗', + 150623: '鄂托克前旗', + 150624: '鄂托克旗', + 150625: '杭锦旗', + 150626: '乌审旗', + 150627: '伊金霍洛旗', + 150702: '海拉尔区', + 150703: '扎赉诺尔区', + 150721: '阿荣旗', + 150722: '莫力达瓦达斡尔族自治旗', + 150723: '鄂伦春自治旗', + 150724: '鄂温克族自治旗', + 150725: '陈巴尔虎旗', + 150726: '新巴尔虎左旗', + 150727: '新巴尔虎右旗', + 150781: '满洲里市', + 150782: '牙克石市', + 150783: '扎兰屯市', + 150784: '额尔古纳市', + 150785: '根河市', + 150802: '临河区', + 150821: '五原县', + 150822: '磴口县', + 150823: '乌拉特前旗', + 150824: '乌拉特中旗', + 150825: '乌拉特后旗', + 150826: '杭锦后旗', + 150902: '集宁区', + 150921: '卓资县', + 150922: '化德县', + 150923: '商都县', + 150924: '兴和县', + 150925: '凉城县', + 150926: '察哈尔右翼前旗', + 150927: '察哈尔右翼中旗', + 150928: '察哈尔右翼后旗', + 150929: '四子王旗', + 150981: '丰镇市', + 152201: '乌兰浩特市', + 152202: '阿尔山市', + 152221: '科尔沁右翼前旗', + 152222: '科尔沁右翼中旗', + 152223: '扎赉特旗', + 152224: '突泉县', + 152501: '二连浩特市', + 152502: '锡林浩特市', + 152522: '阿巴嘎旗', + 152523: '苏尼特左旗', + 152524: '苏尼特右旗', + 152525: '东乌珠穆沁旗', + 152526: '西乌珠穆沁旗', + 152527: '太仆寺旗', + 152528: '镶黄旗', + 152529: '正镶白旗', + 152530: '正蓝旗', + 152531: '多伦县', + 152921: '阿拉善左旗', + 152922: '阿拉善右旗', + 152923: '额济纳旗', + 210102: '和平区', + 210103: '沈河区', + 210104: '大东区', + 210105: '皇姑区', + 210106: '铁西区', + 210111: '苏家屯区', + 210112: '浑南区', + 210113: '沈北新区', + 210114: '于洪区', + 210115: '辽中区', + 210123: '康平县', + 210124: '法库县', + 210181: '新民市', + 210190: '经济技术开发区', + 210202: '中山区', + 210203: '西岗区', + 210204: '沙河口区', + 210211: '甘井子区', + 210212: '旅顺口区', + 210213: '金州区', + 210214: '普兰店区', + 210224: '长海县', + 210281: '瓦房店市', + 210283: '庄河市', + 210302: '铁东区', + 210303: '铁西区', + 210304: '立山区', + 210311: '千山区', + 210321: '台安县', + 210323: '岫岩满族自治县', + 210381: '海城市', + 210390: '高新区', + 210402: '新抚区', + 210403: '东洲区', + 210404: '望花区', + 210411: '顺城区', + 210421: '抚顺县', + 210422: '新宾满族自治县', + 210423: '清原满族自治县', + 210502: '平山区', + 210503: '溪湖区', + 210504: '明山区', + 210505: '南芬区', + 210521: '本溪满族自治县', + 210522: '桓仁满族自治县', + 210602: '元宝区', + 210603: '振兴区', + 210604: '振安区', + 210624: '宽甸满族自治县', + 210681: '东港市', + 210682: '凤城市', + 210702: '古塔区', + 210703: '凌河区', + 210711: '太和区', + 210726: '黑山县', + 210727: '义县', + 210781: '凌海市', + 210782: '北镇市', + 210793: '经济技术开发区', + 210802: '站前区', + 210803: '西市区', + 210804: '鲅鱼圈区', + 210811: '老边区', + 210881: '盖州市', + 210882: '大石桥市', + 210902: '海州区', + 210903: '新邱区', + 210904: '太平区', + 210905: '清河门区', + 210911: '细河区', + 210921: '阜新蒙古族自治县', + 210922: '彰武县', + 211002: '白塔区', + 211003: '文圣区', + 211004: '宏伟区', + 211005: '弓长岭区', + 211011: '太子河区', + 211021: '辽阳县', + 211081: '灯塔市', + 211102: '双台子区', + 211103: '兴隆台区', + 211104: '大洼区', + 211122: '盘山县', + 211202: '银州区', + 211204: '清河区', + 211221: '铁岭县', + 211223: '西丰县', + 211224: '昌图县', + 211281: '调兵山市', + 211282: '开原市', + 211302: '双塔区', + 211303: '龙城区', + 211321: '朝阳县', + 211322: '建平县', + 211324: '喀喇沁左翼蒙古族自治县', + 211381: '北票市', + 211382: '凌源市', + 211402: '连山区', + 211403: '龙港区', + 211404: '南票区', + 211421: '绥中县', + 211422: '建昌县', + 211481: '兴城市', + 215090: '工业园区', + 220102: '南关区', + 220103: '宽城区', + 220104: '朝阳区', + 220105: '二道区', + 220106: '绿园区', + 220112: '双阳区', + 220113: '九台区', + 220122: '农安县', + 220182: '榆树市', + 220183: '德惠市', + 220192: '经济技术开发区', + 220202: '昌邑区', + 220203: '龙潭区', + 220204: '船营区', + 220211: '丰满区', + 220221: '永吉县', + 220281: '蛟河市', + 220282: '桦甸市', + 220283: '舒兰市', + 220284: '磐石市', + 220302: '铁西区', + 220303: '铁东区', + 220322: '梨树县', + 220323: '伊通满族自治县', + 220381: '公主岭市', + 220382: '双辽市', + 220402: '龙山区', + 220403: '西安区', + 220421: '东丰县', + 220422: '东辽县', + 220502: '东昌区', + 220503: '二道江区', + 220521: '通化县', + 220523: '辉南县', + 220524: '柳河县', + 220581: '梅河口市', + 220582: '集安市', + 220602: '浑江区', + 220605: '江源区', + 220621: '抚松县', + 220622: '靖宇县', + 220623: '长白朝鲜族自治县', + 220681: '临江市', + 220702: '宁江区', + 220721: '前郭尔罗斯蒙古族自治县', + 220722: '长岭县', + 220723: '乾安县', + 220781: '扶余市', + 220802: '洮北区', + 220821: '镇赉县', + 220822: '通榆县', + 220881: '洮南市', + 220882: '大安市', + 221090: '工业园区', + 222401: '延吉市', + 222402: '图们市', + 222403: '敦化市', + 222404: '珲春市', + 222405: '龙井市', + 222406: '和龙市', + 222424: '汪清县', + 222426: '安图县', + 230102: '道里区', + 230103: '南岗区', + 230104: '道外区', + 230108: '平房区', + 230109: '松北区', + 230110: '香坊区', + 230111: '呼兰区', + 230112: '阿城区', + 230113: '双城区', + 230123: '依兰县', + 230124: '方正县', + 230125: '宾县', + 230126: '巴彦县', + 230127: '木兰县', + 230128: '通河县', + 230129: '延寿县', + 230183: '尚志市', + 230184: '五常市', + 230202: '龙沙区', + 230203: '建华区', + 230204: '铁锋区', + 230205: '昂昂溪区', + 230206: '富拉尔基区', + 230207: '碾子山区', + 230208: '梅里斯达斡尔族区', + 230221: '龙江县', + 230223: '依安县', + 230224: '泰来县', + 230225: '甘南县', + 230227: '富裕县', + 230229: '克山县', + 230230: '克东县', + 230231: '拜泉县', + 230281: '讷河市', + 230302: '鸡冠区', + 230303: '恒山区', + 230304: '滴道区', + 230305: '梨树区', + 230306: '城子河区', + 230307: '麻山区', + 230321: '鸡东县', + 230381: '虎林市', + 230382: '密山市', + 230402: '向阳区', + 230403: '工农区', + 230404: '南山区', + 230405: '兴安区', + 230406: '东山区', + 230407: '兴山区', + 230421: '萝北县', + 230422: '绥滨县', + 230502: '尖山区', + 230503: '岭东区', + 230505: '四方台区', + 230506: '宝山区', + 230521: '集贤县', + 230522: '友谊县', + 230523: '宝清县', + 230524: '饶河县', + 230602: '萨尔图区', + 230603: '龙凤区', + 230604: '让胡路区', + 230605: '红岗区', + 230606: '大同区', + 230621: '肇州县', + 230622: '肇源县', + 230623: '林甸县', + 230624: '杜尔伯特蒙古族自治县', + 230702: '伊春区', + 230703: '南岔区', + 230704: '友好区', + 230705: '西林区', + 230706: '翠峦区', + 230707: '新青区', + 230708: '美溪区', + 230709: '金山屯区', + 230710: '五营区', + 230711: '乌马河区', + 230712: '汤旺河区', + 230713: '带岭区', + 230714: '乌伊岭区', + 230715: '红星区', + 230716: '上甘岭区', + 230722: '嘉荫县', + 230781: '铁力市', + 230803: '向阳区', + 230804: '前进区', + 230805: '东风区', + 230811: '郊区', + 230822: '桦南县', + 230826: '桦川县', + 230828: '汤原县', + 230881: '同江市', + 230882: '富锦市', + 230883: '抚远市', + 230902: '新兴区', + 230903: '桃山区', + 230904: '茄子河区', + 230921: '勃利县', + 231002: '东安区', + 231003: '阳明区', + 231004: '爱民区', + 231005: '西安区', + 231025: '林口县', + 231081: '绥芬河市', + 231083: '海林市', + 231084: '宁安市', + 231085: '穆棱市', + 231086: '东宁市', + 231102: '爱辉区', + 231121: '嫩江县', + 231123: '逊克县', + 231124: '孙吴县', + 231181: '北安市', + 231182: '五大连池市', + 231202: '北林区', + 231221: '望奎县', + 231222: '兰西县', + 231223: '青冈县', + 231224: '庆安县', + 231225: '明水县', + 231226: '绥棱县', + 231281: '安达市', + 231282: '肇东市', + 231283: '海伦市', + 232721: '呼玛县', + 232722: '塔河县', + 232723: '漠河县', + 232790: '松岭区', + 232791: '呼中区', + 232792: '加格达奇区', + 232793: '新林区', + 310101: '黄浦区', + 310104: '徐汇区', + 310105: '长宁区', + 310106: '静安区', + 310107: '普陀区', + 310109: '虹口区', + 310110: '杨浦区', + 310112: '闵行区', + 310113: '宝山区', + 310114: '嘉定区', + 310115: '浦东新区', + 310116: '金山区', + 310117: '松江区', + 310118: '青浦区', + 310120: '奉贤区', + 310151: '崇明区', + 320102: '玄武区', + 320104: '秦淮区', + 320105: '建邺区', + 320106: '鼓楼区', + 320111: '浦口区', + 320113: '栖霞区', + 320114: '雨花台区', + 320115: '江宁区', + 320116: '六合区', + 320117: '溧水区', + 320118: '高淳区', + 320205: '锡山区', + 320206: '惠山区', + 320211: '滨湖区', + 320213: '梁溪区', + 320214: '新吴区', + 320281: '江阴市', + 320282: '宜兴市', + 320302: '鼓楼区', + 320303: '云龙区', + 320305: '贾汪区', + 320311: '泉山区', + 320312: '铜山区', + 320321: '丰县', + 320322: '沛县', + 320324: '睢宁县', + 320381: '新沂市', + 320382: '邳州市', + 320391: '工业园区', + 320402: '天宁区', + 320404: '钟楼区', + 320411: '新北区', + 320412: '武进区', + 320413: '金坛区', + 320481: '溧阳市', + 320505: '虎丘区', + 320506: '吴中区', + 320507: '相城区', + 320508: '姑苏区', + 320509: '吴江区', + 320581: '常熟市', + 320582: '张家港市', + 320583: '昆山市', + 320585: '太仓市', + 320590: '工业园区', + 320591: '高新区', + 320602: '崇川区', + 320611: '港闸区', + 320612: '通州区', + 320621: '海安县', + 320623: '如东县', + 320681: '启东市', + 320682: '如皋市', + 320684: '海门市', + 320691: '高新区', + 320703: '连云区', + 320706: '海州区', + 320707: '赣榆区', + 320722: '东海县', + 320723: '灌云县', + 320724: '灌南县', + 320803: '淮安区', + 320804: '淮阴区', + 320812: '清江浦区', + 320813: '洪泽区', + 320826: '涟水县', + 320830: '盱眙县', + 320831: '金湖县', + 320890: '经济开发区', + 320902: '亭湖区', + 320903: '盐都区', + 320904: '大丰区', + 320921: '响水县', + 320922: '滨海县', + 320923: '阜宁县', + 320924: '射阳县', + 320925: '建湖县', + 320981: '东台市', + 321002: '广陵区', + 321003: '邗江区', + 321012: '江都区', + 321023: '宝应县', + 321081: '仪征市', + 321084: '高邮市', + 321090: '经济开发区', + 321102: '京口区', + 321111: '润州区', + 321112: '丹徒区', + 321181: '丹阳市', + 321182: '扬中市', + 321183: '句容市', + 321202: '海陵区', + 321203: '高港区', + 321204: '姜堰区', + 321281: '兴化市', + 321282: '靖江市', + 321283: '泰兴市', + 321302: '宿城区', + 321311: '宿豫区', + 321322: '沭阳县', + 321323: '泗阳县', + 321324: '泗洪县', + 330102: '上城区', + 330103: '下城区', + 330104: '江干区', + 330105: '拱墅区', + 330106: '西湖区', + 330108: '滨江区', + 330109: '萧山区', + 330110: '余杭区', + 330111: '富阳区', + 330112: '临安区', + 330122: '桐庐县', + 330127: '淳安县', + 330182: '建德市', + 330203: '海曙区', + 330205: '江北区', + 330206: '北仑区', + 330211: '镇海区', + 330212: '鄞州区', + 330213: '奉化区', + 330225: '象山县', + 330226: '宁海县', + 330281: '余姚市', + 330282: '慈溪市', + 330302: '鹿城区', + 330303: '龙湾区', + 330304: '瓯海区', + 330305: '洞头区', + 330324: '永嘉县', + 330326: '平阳县', + 330327: '苍南县', + 330328: '文成县', + 330329: '泰顺县', + 330381: '瑞安市', + 330382: '乐清市', + 330402: '南湖区', + 330411: '秀洲区', + 330421: '嘉善县', + 330424: '海盐县', + 330481: '海宁市', + 330482: '平湖市', + 330483: '桐乡市', + 330502: '吴兴区', + 330503: '南浔区', + 330521: '德清县', + 330522: '长兴县', + 330523: '安吉县', + 330602: '越城区', + 330603: '柯桥区', + 330604: '上虞区', + 330624: '新昌县', + 330681: '诸暨市', + 330683: '嵊州市', + 330702: '婺城区', + 330703: '金东区', + 330723: '武义县', + 330726: '浦江县', + 330727: '磐安县', + 330781: '兰溪市', + 330782: '义乌市', + 330783: '东阳市', + 330784: '永康市', + 330802: '柯城区', + 330803: '衢江区', + 330822: '常山县', + 330824: '开化县', + 330825: '龙游县', + 330881: '江山市', + 330902: '定海区', + 330903: '普陀区', + 330921: '岱山县', + 330922: '嵊泗县', + 331002: '椒江区', + 331003: '黄岩区', + 331004: '路桥区', + 331022: '三门县', + 331023: '天台县', + 331024: '仙居县', + 331081: '温岭市', + 331082: '临海市', + 331083: '玉环市', + 331102: '莲都区', + 331121: '青田县', + 331122: '缙云县', + 331123: '遂昌县', + 331124: '松阳县', + 331125: '云和县', + 331126: '庆元县', + 331127: '景宁畲族自治县', + 331181: '龙泉市', + 340102: '瑶海区', + 340103: '庐阳区', + 340104: '蜀山区', + 340111: '包河区', + 340121: '长丰县', + 340122: '肥东县', + 340123: '肥西县', + 340124: '庐江县', + 340181: '巢湖市', + 340190: '高新技术开发区', + 340191: '经济技术开发区', + 340202: '镜湖区', + 340203: '弋江区', + 340207: '鸠江区', + 340208: '三山区', + 340221: '芜湖县', + 340222: '繁昌县', + 340223: '南陵县', + 340225: '无为县', + 340302: '龙子湖区', + 340303: '蚌山区', + 340304: '禹会区', + 340311: '淮上区', + 340321: '怀远县', + 340322: '五河县', + 340323: '固镇县', + 340402: '大通区', + 340403: '田家庵区', + 340404: '谢家集区', + 340405: '八公山区', + 340406: '潘集区', + 340421: '凤台县', + 340422: '寿县', + 340503: '花山区', + 340504: '雨山区', + 340506: '博望区', + 340521: '当涂县', + 340522: '含山县', + 340523: '和县', + 340602: '杜集区', + 340603: '相山区', + 340604: '烈山区', + 340621: '濉溪县', + 340705: '铜官区', + 340706: '义安区', + 340711: '郊区', + 340722: '枞阳县', + 340802: '迎江区', + 340803: '大观区', + 340811: '宜秀区', + 340822: '怀宁县', + 340824: '潜山县', + 340825: '太湖县', + 340826: '宿松县', + 340827: '望江县', + 340828: '岳西县', + 340881: '桐城市', + 341002: '屯溪区', + 341003: '黄山区', + 341004: '徽州区', + 341021: '歙县', + 341022: '休宁县', + 341023: '黟县', + 341024: '祁门县', + 341102: '琅琊区', + 341103: '南谯区', + 341122: '来安县', + 341124: '全椒县', + 341125: '定远县', + 341126: '凤阳县', + 341181: '天长市', + 341182: '明光市', + 341202: '颍州区', + 341203: '颍东区', + 341204: '颍泉区', + 341221: '临泉县', + 341222: '太和县', + 341225: '阜南县', + 341226: '颍上县', + 341282: '界首市', + 341302: '埇桥区', + 341321: '砀山县', + 341322: '萧县', + 341323: '灵璧县', + 341324: '泗县', + 341390: '经济开发区', + 341502: '金安区', + 341503: '裕安区', + 341504: '叶集区', + 341522: '霍邱县', + 341523: '舒城县', + 341524: '金寨县', + 341525: '霍山县', + 341602: '谯城区', + 341621: '涡阳县', + 341622: '蒙城县', + 341623: '利辛县', + 341702: '贵池区', + 341721: '东至县', + 341722: '石台县', + 341723: '青阳县', + 341802: '宣州区', + 341821: '郎溪县', + 341822: '广德县', + 341823: '泾县', + 341824: '绩溪县', + 341825: '旌德县', + 341881: '宁国市', + 350102: '鼓楼区', + 350103: '台江区', + 350104: '仓山区', + 350105: '马尾区', + 350111: '晋安区', + 350112: '长乐区', + 350121: '闽侯县', + 350122: '连江县', + 350123: '罗源县', + 350124: '闽清县', + 350125: '永泰县', + 350128: '平潭县', + 350181: '福清市', + 350203: '思明区', + 350205: '海沧区', + 350206: '湖里区', + 350211: '集美区', + 350212: '同安区', + 350213: '翔安区', + 350302: '城厢区', + 350303: '涵江区', + 350304: '荔城区', + 350305: '秀屿区', + 350322: '仙游县', + 350402: '梅列区', + 350403: '三元区', + 350421: '明溪县', + 350423: '清流县', + 350424: '宁化县', + 350425: '大田县', + 350426: '尤溪县', + 350427: '沙县', + 350428: '将乐县', + 350429: '泰宁县', + 350430: '建宁县', + 350481: '永安市', + 350502: '鲤城区', + 350503: '丰泽区', + 350504: '洛江区', + 350505: '泉港区', + 350521: '惠安县', + 350524: '安溪县', + 350525: '永春县', + 350526: '德化县', + 350527: '金门县', + 350581: '石狮市', + 350582: '晋江市', + 350583: '南安市', + 350602: '芗城区', + 350603: '龙文区', + 350622: '云霄县', + 350623: '漳浦县', + 350624: '诏安县', + 350625: '长泰县', + 350626: '东山县', + 350627: '南靖县', + 350628: '平和县', + 350629: '华安县', + 350681: '龙海市', + 350702: '延平区', + 350703: '建阳区', + 350721: '顺昌县', + 350722: '浦城县', + 350723: '光泽县', + 350724: '松溪县', + 350725: '政和县', + 350781: '邵武市', + 350782: '武夷山市', + 350783: '建瓯市', + 350802: '新罗区', + 350803: '永定区', + 350821: '长汀县', + 350823: '上杭县', + 350824: '武平县', + 350825: '连城县', + 350881: '漳平市', + 350902: '蕉城区', + 350921: '霞浦县', + 350922: '古田县', + 350923: '屏南县', + 350924: '寿宁县', + 350925: '周宁县', + 350926: '柘荣县', + 350981: '福安市', + 350982: '福鼎市', + 360102: '东湖区', + 360103: '西湖区', + 360104: '青云谱区', + 360105: '湾里区', + 360111: '青山湖区', + 360112: '新建区', + 360121: '南昌县', + 360123: '安义县', + 360124: '进贤县', + 360190: '经济技术开发区', + 360192: '高新区', + 360202: '昌江区', + 360203: '珠山区', + 360222: '浮梁县', + 360281: '乐平市', + 360302: '安源区', + 360313: '湘东区', + 360321: '莲花县', + 360322: '上栗县', + 360323: '芦溪县', + 360402: '濂溪区', + 360403: '浔阳区', + 360404: '柴桑区', + 360423: '武宁县', + 360424: '修水县', + 360425: '永修县', + 360426: '德安县', + 360428: '都昌县', + 360429: '湖口县', + 360430: '彭泽县', + 360481: '瑞昌市', + 360482: '共青城市', + 360483: '庐山市', + 360490: '经济技术开发区', + 360502: '渝水区', + 360521: '分宜县', + 360602: '月湖区', + 360622: '余江县', + 360681: '贵溪市', + 360702: '章贡区', + 360703: '南康区', + 360704: '赣县区', + 360722: '信丰县', + 360723: '大余县', + 360724: '上犹县', + 360725: '崇义县', + 360726: '安远县', + 360727: '龙南县', + 360728: '定南县', + 360729: '全南县', + 360730: '宁都县', + 360731: '于都县', + 360732: '兴国县', + 360733: '会昌县', + 360734: '寻乌县', + 360735: '石城县', + 360781: '瑞金市', + 360802: '吉州区', + 360803: '青原区', + 360821: '吉安县', + 360822: '吉水县', + 360823: '峡江县', + 360824: '新干县', + 360825: '永丰县', + 360826: '泰和县', + 360827: '遂川县', + 360828: '万安县', + 360829: '安福县', + 360830: '永新县', + 360881: '井冈山市', + 360902: '袁州区', + 360921: '奉新县', + 360922: '万载县', + 360923: '上高县', + 360924: '宜丰县', + 360925: '靖安县', + 360926: '铜鼓县', + 360981: '丰城市', + 360982: '樟树市', + 360983: '高安市', + 361002: '临川区', + 361003: '东乡区', + 361021: '南城县', + 361022: '黎川县', + 361023: '南丰县', + 361024: '崇仁县', + 361025: '乐安县', + 361026: '宜黄县', + 361027: '金溪县', + 361028: '资溪县', + 361030: '广昌县', + 361102: '信州区', + 361103: '广丰区', + 361121: '上饶县', + 361123: '玉山县', + 361124: '铅山县', + 361125: '横峰县', + 361126: '弋阳县', + 361127: '余干县', + 361128: '鄱阳县', + 361129: '万年县', + 361130: '婺源县', + 361181: '德兴市', + 370102: '历下区', + 370103: '市中区', + 370104: '槐荫区', + 370105: '天桥区', + 370112: '历城区', + 370113: '长清区', + 370114: '章丘区', + 370124: '平阴县', + 370125: '济阳县', + 370126: '商河县', + 370190: '高新区', + 370202: '市南区', + 370203: '市北区', + 370211: '黄岛区', + 370212: '崂山区', + 370213: '李沧区', + 370214: '城阳区', + 370215: '即墨区', + 370281: '胶州市', + 370283: '平度市', + 370285: '莱西市', + 370290: '开发区', + 370302: '淄川区', + 370303: '张店区', + 370304: '博山区', + 370305: '临淄区', + 370306: '周村区', + 370321: '桓台县', + 370322: '高青县', + 370323: '沂源县', + 370402: '市中区', + 370403: '薛城区', + 370404: '峄城区', + 370405: '台儿庄区', + 370406: '山亭区', + 370481: '滕州市', + 370502: '东营区', + 370503: '河口区', + 370505: '垦利区', + 370522: '利津县', + 370523: '广饶县', + 370602: '芝罘区', + 370611: '福山区', + 370612: '牟平区', + 370613: '莱山区', + 370634: '长岛县', + 370681: '龙口市', + 370682: '莱阳市', + 370683: '莱州市', + 370684: '蓬莱市', + 370685: '招远市', + 370686: '栖霞市', + 370687: '海阳市', + 370690: '开发区', + 370702: '潍城区', + 370703: '寒亭区', + 370704: '坊子区', + 370705: '奎文区', + 370724: '临朐县', + 370725: '昌乐县', + 370781: '青州市', + 370782: '诸城市', + 370783: '寿光市', + 370784: '安丘市', + 370785: '高密市', + 370786: '昌邑市', + 370790: '开发区', + 370791: '高新区', + 370811: '任城区', + 370812: '兖州区', + 370826: '微山县', + 370827: '鱼台县', + 370828: '金乡县', + 370829: '嘉祥县', + 370830: '汶上县', + 370831: '泗水县', + 370832: '梁山县', + 370881: '曲阜市', + 370883: '邹城市', + 370890: '高新区', + 370902: '泰山区', + 370911: '岱岳区', + 370921: '宁阳县', + 370923: '东平县', + 370982: '新泰市', + 370983: '肥城市', + 371002: '环翠区', + 371003: '文登区', + 371082: '荣成市', + 371083: '乳山市', + 371091: '经济技术开发区', + 371102: '东港区', + 371103: '岚山区', + 371121: '五莲县', + 371122: '莒县', + 371202: '莱城区', + 371203: '钢城区', + 371302: '兰山区', + 371311: '罗庄区', + 371312: '河东区', + 371321: '沂南县', + 371322: '郯城县', + 371323: '沂水县', + 371324: '兰陵县', + 371325: '费县', + 371326: '平邑县', + 371327: '莒南县', + 371328: '蒙阴县', + 371329: '临沭县', + 371402: '德城区', + 371403: '陵城区', + 371422: '宁津县', + 371423: '庆云县', + 371424: '临邑县', + 371425: '齐河县', + 371426: '平原县', + 371427: '夏津县', + 371428: '武城县', + 371481: '乐陵市', + 371482: '禹城市', + 371502: '东昌府区', + 371521: '阳谷县', + 371522: '莘县', + 371523: '茌平县', + 371524: '东阿县', + 371525: '冠县', + 371526: '高唐县', + 371581: '临清市', + 371602: '滨城区', + 371603: '沾化区', + 371621: '惠民县', + 371622: '阳信县', + 371623: '无棣县', + 371625: '博兴县', + 371626: '邹平县', + 371702: '牡丹区', + 371703: '定陶区', + 371721: '曹县', + 371722: '单县', + 371723: '成武县', + 371724: '巨野县', + 371725: '郓城县', + 371726: '鄄城县', + 371728: '东明县', + 410102: '中原区', + 410103: '二七区', + 410104: '管城回族区', + 410105: '金水区', + 410106: '上街区', + 410108: '惠济区', + 410122: '中牟县', + 410181: '巩义市', + 410182: '荥阳市', + 410183: '新密市', + 410184: '新郑市', + 410185: '登封市', + 410190: '高新技术开发区', + 410191: '经济技术开发区', + 410202: '龙亭区', + 410203: '顺河回族区', + 410204: '鼓楼区', + 410205: '禹王台区', + 410212: '祥符区', + 410221: '杞县', + 410222: '通许县', + 410223: '尉氏县', + 410225: '兰考县', + 410302: '老城区', + 410303: '西工区', + 410304: '瀍河回族区', + 410305: '涧西区', + 410306: '吉利区', + 410311: '洛龙区', + 410322: '孟津县', + 410323: '新安县', + 410324: '栾川县', + 410325: '嵩县', + 410326: '汝阳县', + 410327: '宜阳县', + 410328: '洛宁县', + 410329: '伊川县', + 410381: '偃师市', + 410402: '新华区', + 410403: '卫东区', + 410404: '石龙区', + 410411: '湛河区', + 410421: '宝丰县', + 410422: '叶县', + 410423: '鲁山县', + 410425: '郏县', + 410481: '舞钢市', + 410482: '汝州市', + 410502: '文峰区', + 410503: '北关区', + 410505: '殷都区', + 410506: '龙安区', + 410522: '安阳县', + 410523: '汤阴县', + 410526: '滑县', + 410527: '内黄县', + 410581: '林州市', + 410590: '开发区', + 410602: '鹤山区', + 410603: '山城区', + 410611: '淇滨区', + 410621: '浚县', + 410622: '淇县', + 410702: '红旗区', + 410703: '卫滨区', + 410704: '凤泉区', + 410711: '牧野区', + 410721: '新乡县', + 410724: '获嘉县', + 410725: '原阳县', + 410726: '延津县', + 410727: '封丘县', + 410728: '长垣县', + 410781: '卫辉市', + 410782: '辉县市', + 410802: '解放区', + 410803: '中站区', + 410804: '马村区', + 410811: '山阳区', + 410821: '修武县', + 410822: '博爱县', + 410823: '武陟县', + 410825: '温县', + 410882: '沁阳市', + 410883: '孟州市', + 410902: '华龙区', + 410922: '清丰县', + 410923: '南乐县', + 410926: '范县', + 410927: '台前县', + 410928: '濮阳县', + 411002: '魏都区', + 411003: '建安区', + 411024: '鄢陵县', + 411025: '襄城县', + 411081: '禹州市', + 411082: '长葛市', + 411102: '源汇区', + 411103: '郾城区', + 411104: '召陵区', + 411121: '舞阳县', + 411122: '临颍县', + 411202: '湖滨区', + 411203: '陕州区', + 411221: '渑池县', + 411224: '卢氏县', + 411281: '义马市', + 411282: '灵宝市', + 411302: '宛城区', + 411303: '卧龙区', + 411321: '南召县', + 411322: '方城县', + 411323: '西峡县', + 411324: '镇平县', + 411325: '内乡县', + 411326: '淅川县', + 411327: '社旗县', + 411328: '唐河县', + 411329: '新野县', + 411330: '桐柏县', + 411381: '邓州市', + 411402: '梁园区', + 411403: '睢阳区', + 411421: '民权县', + 411422: '睢县', + 411423: '宁陵县', + 411424: '柘城县', + 411425: '虞城县', + 411426: '夏邑县', + 411481: '永城市', + 411502: '浉河区', + 411503: '平桥区', + 411521: '罗山县', + 411522: '光山县', + 411523: '新县', + 411524: '商城县', + 411525: '固始县', + 411526: '潢川县', + 411527: '淮滨县', + 411528: '息县', + 411602: '川汇区', + 411621: '扶沟县', + 411622: '西华县', + 411623: '商水县', + 411624: '沈丘县', + 411625: '郸城县', + 411626: '淮阳县', + 411627: '太康县', + 411628: '鹿邑县', + 411681: '项城市', + 411690: '经济开发区', + 411702: '驿城区', + 411721: '西平县', + 411722: '上蔡县', + 411723: '平舆县', + 411724: '正阳县', + 411725: '确山县', + 411726: '泌阳县', + 411727: '汝南县', + 411728: '遂平县', + 411729: '新蔡县', + 419001: '济源市', + 420102: '江岸区', + 420103: '江汉区', + 420104: '硚口区', + 420105: '汉阳区', + 420106: '武昌区', + 420107: '青山区', + 420111: '洪山区', + 420112: '东西湖区', + 420113: '汉南区', + 420114: '蔡甸区', + 420115: '江夏区', + 420116: '黄陂区', + 420117: '新洲区', + 420202: '黄石港区', + 420203: '西塞山区', + 420204: '下陆区', + 420205: '铁山区', + 420222: '阳新县', + 420281: '大冶市', + 420302: '茅箭区', + 420303: '张湾区', + 420304: '郧阳区', + 420322: '郧西县', + 420323: '竹山县', + 420324: '竹溪县', + 420325: '房县', + 420381: '丹江口市', + 420502: '西陵区', + 420503: '伍家岗区', + 420504: '点军区', + 420505: '猇亭区', + 420506: '夷陵区', + 420525: '远安县', + 420526: '兴山县', + 420527: '秭归县', + 420528: '长阳土家族自治县', + 420529: '五峰土家族自治县', + 420581: '宜都市', + 420582: '当阳市', + 420583: '枝江市', + 420590: '经济开发区', + 420602: '襄城区', + 420606: '樊城区', + 420607: '襄州区', + 420624: '南漳县', + 420625: '谷城县', + 420626: '保康县', + 420682: '老河口市', + 420683: '枣阳市', + 420684: '宜城市', + 420702: '梁子湖区', + 420703: '华容区', + 420704: '鄂城区', + 420802: '东宝区', + 420804: '掇刀区', + 420821: '京山县', + 420822: '沙洋县', + 420881: '钟祥市', + 420902: '孝南区', + 420921: '孝昌县', + 420922: '大悟县', + 420923: '云梦县', + 420981: '应城市', + 420982: '安陆市', + 420984: '汉川市', + 421002: '沙市区', + 421003: '荆州区', + 421022: '公安县', + 421023: '监利县', + 421024: '江陵县', + 421081: '石首市', + 421083: '洪湖市', + 421087: '松滋市', + 421102: '黄州区', + 421121: '团风县', + 421122: '红安县', + 421123: '罗田县', + 421124: '英山县', + 421125: '浠水县', + 421126: '蕲春县', + 421127: '黄梅县', + 421181: '麻城市', + 421182: '武穴市', + 421202: '咸安区', + 421221: '嘉鱼县', + 421222: '通城县', + 421223: '崇阳县', + 421224: '通山县', + 421281: '赤壁市', + 421303: '曾都区', + 421321: '随县', + 421381: '广水市', + 422801: '恩施市', + 422802: '利川市', + 422822: '建始县', + 422823: '巴东县', + 422825: '宣恩县', + 422826: '咸丰县', + 422827: '来凤县', + 422828: '鹤峰县', + 429004: '仙桃市', + 429005: '潜江市', + 429006: '天门市', + 429021: '神农架林区', + 430102: '芙蓉区', + 430103: '天心区', + 430104: '岳麓区', + 430105: '开福区', + 430111: '雨花区', + 430112: '望城区', + 430121: '长沙县', + 430181: '浏阳市', + 430182: '宁乡市', + 430202: '荷塘区', + 430203: '芦淞区', + 430204: '石峰区', + 430211: '天元区', + 430221: '株洲县', + 430223: '攸县', + 430224: '茶陵县', + 430225: '炎陵县', + 430281: '醴陵市', + 430302: '雨湖区', + 430304: '岳塘区', + 430321: '湘潭县', + 430381: '湘乡市', + 430382: '韶山市', + 430405: '珠晖区', + 430406: '雁峰区', + 430407: '石鼓区', + 430408: '蒸湘区', + 430412: '南岳区', + 430421: '衡阳县', + 430422: '衡南县', + 430423: '衡山县', + 430424: '衡东县', + 430426: '祁东县', + 430481: '耒阳市', + 430482: '常宁市', + 430502: '双清区', + 430503: '大祥区', + 430511: '北塔区', + 430521: '邵东县', + 430522: '新邵县', + 430523: '邵阳县', + 430524: '隆回县', + 430525: '洞口县', + 430527: '绥宁县', + 430528: '新宁县', + 430529: '城步苗族自治县', + 430581: '武冈市', + 430602: '岳阳楼区', + 430603: '云溪区', + 430611: '君山区', + 430621: '岳阳县', + 430623: '华容县', + 430624: '湘阴县', + 430626: '平江县', + 430681: '汨罗市', + 430682: '临湘市', + 430702: '武陵区', + 430703: '鼎城区', + 430721: '安乡县', + 430722: '汉寿县', + 430723: '澧县', + 430724: '临澧县', + 430725: '桃源县', + 430726: '石门县', + 430781: '津市市', + 430802: '永定区', + 430811: '武陵源区', + 430821: '慈利县', + 430822: '桑植县', + 430902: '资阳区', + 430903: '赫山区', + 430921: '南县', + 430922: '桃江县', + 430923: '安化县', + 430981: '沅江市', + 431002: '北湖区', + 431003: '苏仙区', + 431021: '桂阳县', + 431022: '宜章县', + 431023: '永兴县', + 431024: '嘉禾县', + 431025: '临武县', + 431026: '汝城县', + 431027: '桂东县', + 431028: '安仁县', + 431081: '资兴市', + 431102: '零陵区', + 431103: '冷水滩区', + 431121: '祁阳县', + 431122: '东安县', + 431123: '双牌县', + 431124: '道县', + 431125: '江永县', + 431126: '宁远县', + 431127: '蓝山县', + 431128: '新田县', + 431129: '江华瑶族自治县', + 431202: '鹤城区', + 431221: '中方县', + 431222: '沅陵县', + 431223: '辰溪县', + 431224: '溆浦县', + 431225: '会同县', + 431226: '麻阳苗族自治县', + 431227: '新晃侗族自治县', + 431228: '芷江侗族自治县', + 431229: '靖州苗族侗族自治县', + 431230: '通道侗族自治县', + 431281: '洪江市', + 431302: '娄星区', + 431321: '双峰县', + 431322: '新化县', + 431381: '冷水江市', + 431382: '涟源市', + 433101: '吉首市', + 433122: '泸溪县', + 433123: '凤凰县', + 433124: '花垣县', + 433125: '保靖县', + 433126: '古丈县', + 433127: '永顺县', + 433130: '龙山县', + 440103: '荔湾区', + 440104: '越秀区', + 440105: '海珠区', + 440106: '天河区', + 440111: '白云区', + 440112: '黄埔区', + 440113: '番禺区', + 440114: '花都区', + 440115: '南沙区', + 440117: '从化区', + 440118: '增城区', + 440203: '武江区', + 440204: '浈江区', + 440205: '曲江区', + 440222: '始兴县', + 440224: '仁化县', + 440229: '翁源县', + 440232: '乳源瑶族自治县', + 440233: '新丰县', + 440281: '乐昌市', + 440282: '南雄市', + 440303: '罗湖区', + 440304: '福田区', + 440305: '南山区', + 440306: '宝安区', + 440307: '龙岗区', + 440308: '盐田区', + 440309: '龙华区', + 440310: '坪山区', + 440402: '香洲区', + 440403: '斗门区', + 440404: '金湾区', + 440507: '龙湖区', + 440511: '金平区', + 440512: '濠江区', + 440513: '潮阳区', + 440514: '潮南区', + 440515: '澄海区', + 440523: '南澳县', + 440604: '禅城区', + 440605: '南海区', + 440606: '顺德区', + 440607: '三水区', + 440608: '高明区', + 440703: '蓬江区', + 440704: '江海区', + 440705: '新会区', + 440781: '台山市', + 440783: '开平市', + 440784: '鹤山市', + 440785: '恩平市', + 440802: '赤坎区', + 440803: '霞山区', + 440804: '坡头区', + 440811: '麻章区', + 440823: '遂溪县', + 440825: '徐闻县', + 440881: '廉江市', + 440882: '雷州市', + 440883: '吴川市', + 440890: '经济技术开发区', + 440902: '茂南区', + 440904: '电白区', + 440981: '高州市', + 440982: '化州市', + 440983: '信宜市', + 441202: '端州区', + 441203: '鼎湖区', + 441204: '高要区', + 441223: '广宁县', + 441224: '怀集县', + 441225: '封开县', + 441226: '德庆县', + 441284: '四会市', + 441302: '惠城区', + 441303: '惠阳区', + 441322: '博罗县', + 441323: '惠东县', + 441324: '龙门县', + 441402: '梅江区', + 441403: '梅县区', + 441422: '大埔县', + 441423: '丰顺县', + 441424: '五华县', + 441426: '平远县', + 441427: '蕉岭县', + 441481: '兴宁市', + 441502: '城区', + 441521: '海丰县', + 441523: '陆河县', + 441581: '陆丰市', + 441602: '源城区', + 441621: '紫金县', + 441622: '龙川县', + 441623: '连平县', + 441624: '和平县', + 441625: '东源县', + 441702: '江城区', + 441704: '阳东区', + 441721: '阳西县', + 441781: '阳春市', + 441802: '清城区', + 441803: '清新区', + 441821: '佛冈县', + 441823: '阳山县', + 441825: '连山壮族瑶族自治县', + 441826: '连南瑶族自治县', + 441881: '英德市', + 441882: '连州市', + 441901: '中堂镇', + 441903: '南城区', + 441904: '长安镇', + 441905: '东坑镇', + 441906: '樟木头镇', + 441907: '莞城区', + 441908: '石龙镇', + 441909: '桥头镇', + 441910: '万江区', + 441911: '麻涌镇', + 441912: '虎门镇', + 441913: '谢岗镇', + 441914: '石碣镇', + 441915: '茶山镇', + 441916: '东城区', + 441917: '洪梅镇', + 441918: '道滘镇', + 441919: '高埗镇', + 441920: '企石镇', + 441921: '凤岗镇', + 441922: '大岭山镇', + 441923: '松山湖', + 441924: '清溪镇', + 441925: '望牛墩镇', + 441926: '厚街镇', + 441927: '常平镇', + 441928: '寮步镇', + 441929: '石排镇', + 441930: '横沥镇', + 441931: '塘厦镇', + 441932: '黄江镇', + 441933: '大朗镇', + 441990: '沙田镇', + 442001: '南头镇', + 442002: '神湾镇', + 442003: '东凤镇', + 442004: '五桂山镇', + 442005: '黄圃镇', + 442006: '小榄镇', + 442007: '石岐区街道', + 442008: '横栏镇', + 442009: '三角镇', + 442010: '三乡镇', + 442011: '港口镇', + 442012: '沙溪镇', + 442013: '板芙镇', + 442014: '沙朗镇', + 442015: '东升镇', + 442016: '阜沙镇', + 442017: '民众镇', + 442018: '东区街道', + 442019: '火炬开发区', + 442020: '西区街道', + 442021: '南区街道', + 442022: '古镇', + 442023: '坦洲镇', + 442024: '大涌镇', + 442025: '南朗镇', + 445102: '湘桥区', + 445103: '潮安区', + 445122: '饶平县', + 445202: '榕城区', + 445203: '揭东区', + 445222: '揭西县', + 445224: '惠来县', + 445281: '普宁市', + 445302: '云城区', + 445303: '云安区', + 445321: '新兴县', + 445322: '郁南县', + 445381: '罗定市', + 450102: '兴宁区', + 450103: '青秀区', + 450105: '江南区', + 450107: '西乡塘区', + 450108: '良庆区', + 450109: '邕宁区', + 450110: '武鸣区', + 450123: '隆安县', + 450124: '马山县', + 450125: '上林县', + 450126: '宾阳县', + 450127: '横县', + 450202: '城中区', + 450203: '鱼峰区', + 450204: '柳南区', + 450205: '柳北区', + 450206: '柳江区', + 450222: '柳城县', + 450223: '鹿寨县', + 450224: '融安县', + 450225: '融水苗族自治县', + 450226: '三江侗族自治县', + 450302: '秀峰区', + 450303: '叠彩区', + 450304: '象山区', + 450305: '七星区', + 450311: '雁山区', + 450312: '临桂区', + 450321: '阳朔县', + 450323: '灵川县', + 450324: '全州县', + 450325: '兴安县', + 450326: '永福县', + 450327: '灌阳县', + 450328: '龙胜各族自治县', + 450329: '资源县', + 450330: '平乐县', + 450331: '荔浦县', + 450332: '恭城瑶族自治县', + 450403: '万秀区', + 450405: '长洲区', + 450406: '龙圩区', + 450421: '苍梧县', + 450422: '藤县', + 450423: '蒙山县', + 450481: '岑溪市', + 450502: '海城区', + 450503: '银海区', + 450512: '铁山港区', + 450521: '合浦县', + 450602: '港口区', + 450603: '防城区', + 450621: '上思县', + 450681: '东兴市', + 450702: '钦南区', + 450703: '钦北区', + 450721: '灵山县', + 450722: '浦北县', + 450802: '港北区', + 450803: '港南区', + 450804: '覃塘区', + 450821: '平南县', + 450881: '桂平市', + 450902: '玉州区', + 450903: '福绵区', + 450921: '容县', + 450922: '陆川县', + 450923: '博白县', + 450924: '兴业县', + 450981: '北流市', + 451002: '右江区', + 451021: '田阳县', + 451022: '田东县', + 451023: '平果县', + 451024: '德保县', + 451026: '那坡县', + 451027: '凌云县', + 451028: '乐业县', + 451029: '田林县', + 451030: '西林县', + 451031: '隆林各族自治县', + 451081: '靖西市', + 451102: '八步区', + 451103: '平桂区', + 451121: '昭平县', + 451122: '钟山县', + 451123: '富川瑶族自治县', + 451202: '金城江区', + 451203: '宜州区', + 451221: '南丹县', + 451222: '天峨县', + 451223: '凤山县', + 451224: '东兰县', + 451225: '罗城仫佬族自治县', + 451226: '环江毛南族自治县', + 451227: '巴马瑶族自治县', + 451228: '都安瑶族自治县', + 451229: '大化瑶族自治县', + 451302: '兴宾区', + 451321: '忻城县', + 451322: '象州县', + 451323: '武宣县', + 451324: '金秀瑶族自治县', + 451381: '合山市', + 451402: '江州区', + 451421: '扶绥县', + 451422: '宁明县', + 451423: '龙州县', + 451424: '大新县', + 451425: '天等县', + 451481: '凭祥市', + 460105: '秀英区', + 460106: '龙华区', + 460107: '琼山区', + 460108: '美兰区', + 460202: '海棠区', + 460203: '吉阳区', + 460204: '天涯区', + 460205: '崖州区', + 460321: '西沙群岛', + 460322: '南沙群岛', + 460323: '中沙群岛的岛礁及其海域', + 460401: '那大镇', + 460402: '和庆镇', + 460403: '南丰镇', + 460404: '大成镇', + 460405: '雅星镇', + 460406: '兰洋镇', + 460407: '光村镇', + 460408: '木棠镇', + 460409: '海头镇', + 460410: '峨蔓镇', + 460411: '王五镇', + 460412: '白马井镇', + 460413: '中和镇', + 460414: '排浦镇', + 460415: '东成镇', + 460416: '新州镇', + 469001: '五指山市', + 469002: '琼海市', + 469005: '文昌市', + 469006: '万宁市', + 469007: '东方市', + 469021: '定安县', + 469022: '屯昌县', + 469023: '澄迈县', + 469024: '临高县', + 469025: '白沙黎族自治县', + 469026: '昌江黎族自治县', + 469027: '乐东黎族自治县', + 469028: '陵水黎族自治县', + 469029: '保亭黎族苗族自治县', + 469030: '琼中黎族苗族自治县', + 500101: '万州区', + 500102: '涪陵区', + 500103: '渝中区', + 500104: '大渡口区', + 500105: '江北区', + 500106: '沙坪坝区', + 500107: '九龙坡区', + 500108: '南岸区', + 500109: '北碚区', + 500110: '綦江区', + 500111: '大足区', + 500112: '渝北区', + 500113: '巴南区', + 500114: '黔江区', + 500115: '长寿区', + 500116: '江津区', + 500117: '合川区', + 500118: '永川区', + 500119: '南川区', + 500120: '璧山区', + 500151: '铜梁区', + 500152: '潼南区', + 500153: '荣昌区', + 500154: '开州区', + 500155: '梁平区', + 500156: '武隆区', + 500229: '城口县', + 500230: '丰都县', + 500231: '垫江县', + 500233: '忠县', + 500235: '云阳县', + 500236: '奉节县', + 500237: '巫山县', + 500238: '巫溪县', + 500240: '石柱土家族自治县', + 500241: '秀山土家族苗族自治县', + 500242: '酉阳土家族苗族自治县', + 500243: '彭水苗族土家族自治县', + 510104: '锦江区', + 510105: '青羊区', + 510106: '金牛区', + 510107: '武侯区', + 510108: '成华区', + 510112: '龙泉驿区', + 510113: '青白江区', + 510114: '新都区', + 510115: '温江区', + 510116: '双流区', + 510117: '郫都区', + 510121: '金堂县', + 510129: '大邑县', + 510131: '蒲江县', + 510132: '新津县', + 510181: '都江堰市', + 510182: '彭州市', + 510183: '邛崃市', + 510184: '崇州市', + 510185: '简阳市', + 510191: '高新区', + 510302: '自流井区', + 510303: '贡井区', + 510304: '大安区', + 510311: '沿滩区', + 510321: '荣县', + 510322: '富顺县', + 510402: '东区', + 510403: '西区', + 510411: '仁和区', + 510421: '米易县', + 510422: '盐边县', + 510502: '江阳区', + 510503: '纳溪区', + 510504: '龙马潭区', + 510521: '泸县', + 510522: '合江县', + 510524: '叙永县', + 510525: '古蔺县', + 510603: '旌阳区', + 510604: '罗江区', + 510623: '中江县', + 510681: '广汉市', + 510682: '什邡市', + 510683: '绵竹市', + 510703: '涪城区', + 510704: '游仙区', + 510705: '安州区', + 510722: '三台县', + 510723: '盐亭县', + 510725: '梓潼县', + 510726: '北川羌族自治县', + 510727: '平武县', + 510781: '江油市', + 510791: '高新区', + 510802: '利州区', + 510811: '昭化区', + 510812: '朝天区', + 510821: '旺苍县', + 510822: '青川县', + 510823: '剑阁县', + 510824: '苍溪县', + 510903: '船山区', + 510904: '安居区', + 510921: '蓬溪县', + 510922: '射洪县', + 510923: '大英县', + 511002: '市中区', + 511011: '东兴区', + 511024: '威远县', + 511025: '资中县', + 511083: '隆昌市', + 511102: '市中区', + 511111: '沙湾区', + 511112: '五通桥区', + 511113: '金口河区', + 511123: '犍为县', + 511124: '井研县', + 511126: '夹江县', + 511129: '沐川县', + 511132: '峨边彝族自治县', + 511133: '马边彝族自治县', + 511181: '峨眉山市', + 511302: '顺庆区', + 511303: '高坪区', + 511304: '嘉陵区', + 511321: '南部县', + 511322: '营山县', + 511323: '蓬安县', + 511324: '仪陇县', + 511325: '西充县', + 511381: '阆中市', + 511402: '东坡区', + 511403: '彭山区', + 511421: '仁寿县', + 511423: '洪雅县', + 511424: '丹棱县', + 511425: '青神县', + 511502: '翠屏区', + 511503: '南溪区', + 511521: '宜宾县', + 511523: '江安县', + 511524: '长宁县', + 511525: '高县', + 511526: '珙县', + 511527: '筠连县', + 511528: '兴文县', + 511529: '屏山县', + 511602: '广安区', + 511603: '前锋区', + 511621: '岳池县', + 511622: '武胜县', + 511623: '邻水县', + 511681: '华蓥市', + 511702: '通川区', + 511703: '达川区', + 511722: '宣汉县', + 511723: '开江县', + 511724: '大竹县', + 511725: '渠县', + 511781: '万源市', + 511802: '雨城区', + 511803: '名山区', + 511822: '荥经县', + 511823: '汉源县', + 511824: '石棉县', + 511825: '天全县', + 511826: '芦山县', + 511827: '宝兴县', + 511902: '巴州区', + 511903: '恩阳区', + 511921: '通江县', + 511922: '南江县', + 511923: '平昌县', + 512002: '雁江区', + 512021: '安岳县', + 512022: '乐至县', + 513201: '马尔康市', + 513221: '汶川县', + 513222: '理县', + 513223: '茂县', + 513224: '松潘县', + 513225: '九寨沟县', + 513226: '金川县', + 513227: '小金县', + 513228: '黑水县', + 513230: '壤塘县', + 513231: '阿坝县', + 513232: '若尔盖县', + 513233: '红原县', + 513301: '康定市', + 513322: '泸定县', + 513323: '丹巴县', + 513324: '九龙县', + 513325: '雅江县', + 513326: '道孚县', + 513327: '炉霍县', + 513328: '甘孜县', + 513329: '新龙县', + 513330: '德格县', + 513331: '白玉县', + 513332: '石渠县', + 513333: '色达县', + 513334: '理塘县', + 513335: '巴塘县', + 513336: '乡城县', + 513337: '稻城县', + 513338: '得荣县', + 513401: '西昌市', + 513422: '木里藏族自治县', + 513423: '盐源县', + 513424: '德昌县', + 513425: '会理县', + 513426: '会东县', + 513427: '宁南县', + 513428: '普格县', + 513429: '布拖县', + 513430: '金阳县', + 513431: '昭觉县', + 513432: '喜德县', + 513433: '冕宁县', + 513434: '越西县', + 513435: '甘洛县', + 513436: '美姑县', + 513437: '雷波县', + 520102: '南明区', + 520103: '云岩区', + 520111: '花溪区', + 520112: '乌当区', + 520113: '白云区', + 520115: '观山湖区', + 520121: '开阳县', + 520122: '息烽县', + 520123: '修文县', + 520181: '清镇市', + 520201: '钟山区', + 520203: '六枝特区', + 520221: '水城县', + 520281: '盘州市', + 520302: '红花岗区', + 520303: '汇川区', + 520304: '播州区', + 520322: '桐梓县', + 520323: '绥阳县', + 520324: '正安县', + 520325: '道真仡佬族苗族自治县', + 520326: '务川仡佬族苗族自治县', + 520327: '凤冈县', + 520328: '湄潭县', + 520329: '余庆县', + 520330: '习水县', + 520381: '赤水市', + 520382: '仁怀市', + 520402: '西秀区', + 520403: '平坝区', + 520422: '普定县', + 520423: '镇宁布依族苗族自治县', + 520424: '关岭布依族苗族自治县', + 520425: '紫云苗族布依族自治县', + 520502: '七星关区', + 520521: '大方县', + 520522: '黔西县', + 520523: '金沙县', + 520524: '织金县', + 520525: '纳雍县', + 520526: '威宁彝族回族苗族自治县', + 520527: '赫章县', + 520602: '碧江区', + 520603: '万山区', + 520621: '江口县', + 520622: '玉屏侗族自治县', + 520623: '石阡县', + 520624: '思南县', + 520625: '印江土家族苗族自治县', + 520626: '德江县', + 520627: '沿河土家族自治县', + 520628: '松桃苗族自治县', + 522301: '兴义市', + 522322: '兴仁县', + 522323: '普安县', + 522324: '晴隆县', + 522325: '贞丰县', + 522326: '望谟县', + 522327: '册亨县', + 522328: '安龙县', + 522601: '凯里市', + 522622: '黄平县', + 522623: '施秉县', + 522624: '三穗县', + 522625: '镇远县', + 522626: '岑巩县', + 522627: '天柱县', + 522628: '锦屏县', + 522629: '剑河县', + 522630: '台江县', + 522631: '黎平县', + 522632: '榕江县', + 522633: '从江县', + 522634: '雷山县', + 522635: '麻江县', + 522636: '丹寨县', + 522701: '都匀市', + 522702: '福泉市', + 522722: '荔波县', + 522723: '贵定县', + 522725: '瓮安县', + 522726: '独山县', + 522727: '平塘县', + 522728: '罗甸县', + 522729: '长顺县', + 522730: '龙里县', + 522731: '惠水县', + 522732: '三都水族自治县', + 530102: '五华区', + 530103: '盘龙区', + 530111: '官渡区', + 530112: '西山区', + 530113: '东川区', + 530114: '呈贡区', + 530115: '晋宁区', + 530124: '富民县', + 530125: '宜良县', + 530126: '石林彝族自治县', + 530127: '嵩明县', + 530128: '禄劝彝族苗族自治县', + 530129: '寻甸回族彝族自治县', + 530181: '安宁市', + 530302: '麒麟区', + 530303: '沾益区', + 530321: '马龙县', + 530322: '陆良县', + 530323: '师宗县', + 530324: '罗平县', + 530325: '富源县', + 530326: '会泽县', + 530381: '宣威市', + 530402: '红塔区', + 530403: '江川区', + 530422: '澄江县', + 530423: '通海县', + 530424: '华宁县', + 530425: '易门县', + 530426: '峨山彝族自治县', + 530427: '新平彝族傣族自治县', + 530428: '元江哈尼族彝族傣族自治县', + 530502: '隆阳区', + 530521: '施甸县', + 530523: '龙陵县', + 530524: '昌宁县', + 530581: '腾冲市', + 530602: '昭阳区', + 530621: '鲁甸县', + 530622: '巧家县', + 530623: '盐津县', + 530624: '大关县', + 530625: '永善县', + 530626: '绥江县', + 530627: '镇雄县', + 530628: '彝良县', + 530629: '威信县', + 530630: '水富县', + 530702: '古城区', + 530721: '玉龙纳西族自治县', + 530722: '永胜县', + 530723: '华坪县', + 530724: '宁蒗彝族自治县', + 530802: '思茅区', + 530821: '宁洱哈尼族彝族自治县', + 530822: '墨江哈尼族自治县', + 530823: '景东彝族自治县', + 530824: '景谷傣族彝族自治县', + 530825: '镇沅彝族哈尼族拉祜族自治县', + 530826: '江城哈尼族彝族自治县', + 530827: '孟连傣族拉祜族佤族自治县', + 530828: '澜沧拉祜族自治县', + 530829: '西盟佤族自治县', + 530902: '临翔区', + 530921: '凤庆县', + 530922: '云县', + 530923: '永德县', + 530924: '镇康县', + 530925: '双江拉祜族佤族布朗族傣族自治县', + 530926: '耿马傣族佤族自治县', + 530927: '沧源佤族自治县', + 532301: '楚雄市', + 532322: '双柏县', + 532323: '牟定县', + 532324: '南华县', + 532325: '姚安县', + 532326: '大姚县', + 532327: '永仁县', + 532328: '元谋县', + 532329: '武定县', + 532331: '禄丰县', + 532501: '个旧市', + 532502: '开远市', + 532503: '蒙自市', + 532504: '弥勒市', + 532523: '屏边苗族自治县', + 532524: '建水县', + 532525: '石屏县', + 532527: '泸西县', + 532528: '元阳县', + 532529: '红河县', + 532530: '金平苗族瑶族傣族自治县', + 532531: '绿春县', + 532532: '河口瑶族自治县', + 532601: '文山市', + 532622: '砚山县', + 532623: '西畴县', + 532624: '麻栗坡县', + 532625: '马关县', + 532626: '丘北县', + 532627: '广南县', + 532628: '富宁县', + 532801: '景洪市', + 532822: '勐海县', + 532823: '勐腊县', + 532901: '大理市', + 532922: '漾濞彝族自治县', + 532923: '祥云县', + 532924: '宾川县', + 532925: '弥渡县', + 532926: '南涧彝族自治县', + 532927: '巍山彝族回族自治县', + 532928: '永平县', + 532929: '云龙县', + 532930: '洱源县', + 532931: '剑川县', + 532932: '鹤庆县', + 533102: '瑞丽市', + 533103: '芒市', + 533122: '梁河县', + 533123: '盈江县', + 533124: '陇川县', + 533301: '泸水市', + 533323: '福贡县', + 533324: '贡山独龙族怒族自治县', + 533325: '兰坪白族普米族自治县', + 533401: '香格里拉市', + 533422: '德钦县', + 533423: '维西傈僳族自治县', + 540102: '城关区', + 540103: '堆龙德庆区', + 540104: '达孜区', + 540121: '林周县', + 540122: '当雄县', + 540123: '尼木县', + 540124: '曲水县', + 540127: '墨竹工卡县', + 540202: '桑珠孜区', + 540221: '南木林县', + 540222: '江孜县', + 540223: '定日县', + 540224: '萨迦县', + 540225: '拉孜县', + 540226: '昂仁县', + 540227: '谢通门县', + 540228: '白朗县', + 540229: '仁布县', + 540230: '康马县', + 540231: '定结县', + 540232: '仲巴县', + 540233: '亚东县', + 540234: '吉隆县', + 540235: '聂拉木县', + 540236: '萨嘎县', + 540237: '岗巴县', + 540302: '卡若区', + 540321: '江达县', + 540322: '贡觉县', + 540323: '类乌齐县', + 540324: '丁青县', + 540325: '察雅县', + 540326: '八宿县', + 540327: '左贡县', + 540328: '芒康县', + 540329: '洛隆县', + 540330: '边坝县', + 540402: '巴宜区', + 540421: '工布江达县', + 540422: '米林县', + 540423: '墨脱县', + 540424: '波密县', + 540425: '察隅县', + 540426: '朗县', + 540502: '乃东区', + 540521: '扎囊县', + 540522: '贡嘎县', + 540523: '桑日县', + 540524: '琼结县', + 540525: '曲松县', + 540526: '措美县', + 540527: '洛扎县', + 540528: '加查县', + 540529: '隆子县', + 540530: '错那县', + 540531: '浪卡子县', + 540602: '色尼区', + 542421: '那曲县', + 542422: '嘉黎县', + 542423: '比如县', + 542424: '聂荣县', + 542425: '安多县', + 542426: '申扎县', + 542427: '索县', + 542428: '班戈县', + 542429: '巴青县', + 542430: '尼玛县', + 542431: '双湖县', + 542521: '普兰县', + 542522: '札达县', + 542523: '噶尔县', + 542524: '日土县', + 542525: '革吉县', + 542526: '改则县', + 542527: '措勤县', + 610102: '新城区', + 610103: '碑林区', + 610104: '莲湖区', + 610111: '灞桥区', + 610112: '未央区', + 610113: '雁塔区', + 610114: '阎良区', + 610115: '临潼区', + 610116: '长安区', + 610117: '高陵区', + 610118: '鄠邑区', + 610122: '蓝田县', + 610124: '周至县', + 610202: '王益区', + 610203: '印台区', + 610204: '耀州区', + 610222: '宜君县', + 610302: '渭滨区', + 610303: '金台区', + 610304: '陈仓区', + 610322: '凤翔县', + 610323: '岐山县', + 610324: '扶风县', + 610326: '眉县', + 610327: '陇县', + 610328: '千阳县', + 610329: '麟游县', + 610330: '凤县', + 610331: '太白县', + 610402: '秦都区', + 610403: '杨陵区', + 610404: '渭城区', + 610422: '三原县', + 610423: '泾阳县', + 610424: '乾县', + 610425: '礼泉县', + 610426: '永寿县', + 610427: '彬县', + 610428: '长武县', + 610429: '旬邑县', + 610430: '淳化县', + 610431: '武功县', + 610481: '兴平市', + 610502: '临渭区', + 610503: '华州区', + 610522: '潼关县', + 610523: '大荔县', + 610524: '合阳县', + 610525: '澄城县', + 610526: '蒲城县', + 610527: '白水县', + 610528: '富平县', + 610581: '韩城市', + 610582: '华阴市', + 610602: '宝塔区', + 610603: '安塞区', + 610621: '延长县', + 610622: '延川县', + 610623: '子长县', + 610625: '志丹县', + 610626: '吴起县', + 610627: '甘泉县', + 610628: '富县', + 610629: '洛川县', + 610630: '宜川县', + 610631: '黄龙县', + 610632: '黄陵县', + 610702: '汉台区', + 610703: '南郑区', + 610722: '城固县', + 610723: '洋县', + 610724: '西乡县', + 610725: '勉县', + 610726: '宁强县', + 610727: '略阳县', + 610728: '镇巴县', + 610729: '留坝县', + 610730: '佛坪县', + 610802: '榆阳区', + 610803: '横山区', + 610822: '府谷县', + 610824: '靖边县', + 610825: '定边县', + 610826: '绥德县', + 610827: '米脂县', + 610828: '佳县', + 610829: '吴堡县', + 610830: '清涧县', + 610831: '子洲县', + 610881: '神木市', + 610902: '汉滨区', + 610921: '汉阴县', + 610922: '石泉县', + 610923: '宁陕县', + 610924: '紫阳县', + 610925: '岚皋县', + 610926: '平利县', + 610927: '镇坪县', + 610928: '旬阳县', + 610929: '白河县', + 611002: '商州区', + 611021: '洛南县', + 611022: '丹凤县', + 611023: '商南县', + 611024: '山阳县', + 611025: '镇安县', + 611026: '柞水县', + 620102: '城关区', + 620103: '七里河区', + 620104: '西固区', + 620105: '安宁区', + 620111: '红古区', + 620121: '永登县', + 620122: '皋兰县', + 620123: '榆中县', + 620201: '市辖区', + 620290: '雄关区', + 620291: '长城区', + 620292: '镜铁区', + 620293: '新城镇', + 620294: '峪泉镇', + 620295: '文殊镇', + 620302: '金川区', + 620321: '永昌县', + 620402: '白银区', + 620403: '平川区', + 620421: '靖远县', + 620422: '会宁县', + 620423: '景泰县', + 620502: '秦州区', + 620503: '麦积区', + 620521: '清水县', + 620522: '秦安县', + 620523: '甘谷县', + 620524: '武山县', + 620525: '张家川回族自治县', + 620602: '凉州区', + 620621: '民勤县', + 620622: '古浪县', + 620623: '天祝藏族自治县', + 620702: '甘州区', + 620721: '肃南裕固族自治县', + 620722: '民乐县', + 620723: '临泽县', + 620724: '高台县', + 620725: '山丹县', + 620802: '崆峒区', + 620821: '泾川县', + 620822: '灵台县', + 620823: '崇信县', + 620824: '华亭县', + 620825: '庄浪县', + 620826: '静宁县', + 620902: '肃州区', + 620921: '金塔县', + 620922: '瓜州县', + 620923: '肃北蒙古族自治县', + 620924: '阿克塞哈萨克族自治县', + 620981: '玉门市', + 620982: '敦煌市', + 621002: '西峰区', + 621021: '庆城县', + 621022: '环县', + 621023: '华池县', + 621024: '合水县', + 621025: '正宁县', + 621026: '宁县', + 621027: '镇原县', + 621102: '安定区', + 621121: '通渭县', + 621122: '陇西县', + 621123: '渭源县', + 621124: '临洮县', + 621125: '漳县', + 621126: '岷县', + 621202: '武都区', + 621221: '成县', + 621222: '文县', + 621223: '宕昌县', + 621224: '康县', + 621225: '西和县', + 621226: '礼县', + 621227: '徽县', + 621228: '两当县', + 622901: '临夏市', + 622921: '临夏县', + 622922: '康乐县', + 622923: '永靖县', + 622924: '广河县', + 622925: '和政县', + 622926: '东乡族自治县', + 622927: '积石山保安族东乡族撒拉族自治县', + 623001: '合作市', + 623021: '临潭县', + 623022: '卓尼县', + 623023: '舟曲县', + 623024: '迭部县', + 623025: '玛曲县', + 623026: '碌曲县', + 623027: '夏河县', + 630102: '城东区', + 630103: '城中区', + 630104: '城西区', + 630105: '城北区', + 630121: '大通回族土族自治县', + 630122: '湟中县', + 630123: '湟源县', + 630202: '乐都区', + 630203: '平安区', + 630222: '民和回族土族自治县', + 630223: '互助土族自治县', + 630224: '化隆回族自治县', + 630225: '循化撒拉族自治县', + 632221: '门源回族自治县', + 632222: '祁连县', + 632223: '海晏县', + 632224: '刚察县', + 632321: '同仁县', + 632322: '尖扎县', + 632323: '泽库县', + 632324: '河南蒙古族自治县', + 632521: '共和县', + 632522: '同德县', + 632523: '贵德县', + 632524: '兴海县', + 632525: '贵南县', + 632621: '玛沁县', + 632622: '班玛县', + 632623: '甘德县', + 632624: '达日县', + 632625: '久治县', + 632626: '玛多县', + 632701: '玉树市', + 632722: '杂多县', + 632723: '称多县', + 632724: '治多县', + 632725: '囊谦县', + 632726: '曲麻莱县', + 632801: '格尔木市', + 632802: '德令哈市', + 632821: '乌兰县', + 632822: '都兰县', + 632823: '天峻县', + 640104: '兴庆区', + 640105: '西夏区', + 640106: '金凤区', + 640121: '永宁县', + 640122: '贺兰县', + 640181: '灵武市', + 640202: '大武口区', + 640205: '惠农区', + 640221: '平罗县', + 640302: '利通区', + 640303: '红寺堡区', + 640323: '盐池县', + 640324: '同心县', + 640381: '青铜峡市', + 640402: '原州区', + 640422: '西吉县', + 640423: '隆德县', + 640424: '泾源县', + 640425: '彭阳县', + 640502: '沙坡头区', + 640521: '中宁县', + 640522: '海原县', + 650102: '天山区', + 650103: '沙依巴克区', + 650104: '新市区', + 650105: '水磨沟区', + 650106: '头屯河区', + 650107: '达坂城区', + 650109: '米东区', + 650121: '乌鲁木齐县', + 650202: '独山子区', + 650203: '克拉玛依区', + 650204: '白碱滩区', + 650205: '乌尔禾区', + 650402: '高昌区', + 650421: '鄯善县', + 650422: '托克逊县', + 650502: '伊州区', + 650521: '巴里坤哈萨克自治县', + 650522: '伊吾县', + 652301: '昌吉市', + 652302: '阜康市', + 652323: '呼图壁县', + 652324: '玛纳斯县', + 652325: '奇台县', + 652327: '吉木萨尔县', + 652328: '木垒哈萨克自治县', + 652701: '博乐市', + 652702: '阿拉山口市', + 652722: '精河县', + 652723: '温泉县', + 652801: '库尔勒市', + 652822: '轮台县', + 652823: '尉犁县', + 652824: '若羌县', + 652825: '且末县', + 652826: '焉耆回族自治县', + 652827: '和静县', + 652828: '和硕县', + 652829: '博湖县', + 652901: '阿克苏市', + 652922: '温宿县', + 652923: '库车县', + 652924: '沙雅县', + 652925: '新和县', + 652926: '拜城县', + 652927: '乌什县', + 652928: '阿瓦提县', + 652929: '柯坪县', + 653001: '阿图什市', + 653022: '阿克陶县', + 653023: '阿合奇县', + 653024: '乌恰县', + 653101: '喀什市', + 653121: '疏附县', + 653122: '疏勒县', + 653123: '英吉沙县', + 653124: '泽普县', + 653125: '莎车县', + 653126: '叶城县', + 653127: '麦盖提县', + 653128: '岳普湖县', + 653129: '伽师县', + 653130: '巴楚县', + 653131: '塔什库尔干塔吉克自治县', + 653201: '和田市', + 653221: '和田县', + 653222: '墨玉县', + 653223: '皮山县', + 653224: '洛浦县', + 653225: '策勒县', + 653226: '于田县', + 653227: '民丰县', + 654002: '伊宁市', + 654003: '奎屯市', + 654004: '霍尔果斯市', + 654021: '伊宁县', + 654022: '察布查尔锡伯自治县', + 654023: '霍城县', + 654024: '巩留县', + 654025: '新源县', + 654026: '昭苏县', + 654027: '特克斯县', + 654028: '尼勒克县', + 654201: '塔城市', + 654202: '乌苏市', + 654221: '额敏县', + 654223: '沙湾县', + 654224: '托里县', + 654225: '裕民县', + 654226: '和布克赛尔蒙古自治县', + 654301: '阿勒泰市', + 654321: '布尔津县', + 654322: '富蕴县', + 654323: '福海县', + 654324: '哈巴河县', + 654325: '青河县', + 654326: '吉木乃县', + 659001: '石河子市', + 659002: '阿拉尔市', + 659003: '图木舒克市', + 659004: '五家渠市', + 659005: '北屯市', + 659006: '铁门关市', + 659007: '双河市', + 659008: '可克达拉市', + 659009: '昆玉市', + 710101: '中正区', + 710102: '大同区', + 710103: '中山区', + 710104: '松山区', + 710105: '大安区', + 710106: '万华区', + 710107: '信义区', + 710108: '士林区', + 710109: '北投区', + 710110: '内湖区', + 710111: '南港区', + 710112: '文山区', + 710199: '其它区', + 710201: '新兴区', + 710202: '前金区', + 710203: '芩雅区', + 710204: '盐埕区', + 710205: '鼓山区', + 710206: '旗津区', + 710207: '前镇区', + 710208: '三民区', + 710209: '左营区', + 710210: '楠梓区', + 710211: '小港区', + 710241: '苓雅区', + 710242: '仁武区', + 710243: '大社区', + 710244: '冈山区', + 710245: '路竹区', + 710246: '阿莲区', + 710247: '田寮区', + 710248: '燕巢区', + 710249: '桥头区', + 710250: '梓官区', + 710251: '弥陀区', + 710252: '永安区', + 710253: '湖内区', + 710254: '凤山区', + 710255: '大寮区', + 710256: '林园区', + 710257: '鸟松区', + 710258: '大树区', + 710259: '旗山区', + 710260: '美浓区', + 710261: '六龟区', + 710262: '内门区', + 710263: '杉林区', + 710264: '甲仙区', + 710265: '桃源区', + 710266: '那玛夏区', + 710267: '茂林区', + 710268: '茄萣区', + 710299: '其它区', + 710301: '中西区', + 710302: '东区', + 710303: '南区', + 710304: '北区', + 710305: '安平区', + 710306: '安南区', + 710339: '永康区', + 710340: '归仁区', + 710341: '新化区', + 710342: '左镇区', + 710343: '玉井区', + 710344: '楠西区', + 710345: '南化区', + 710346: '仁德区', + 710347: '关庙区', + 710348: '龙崎区', + 710349: '官田区', + 710350: '麻豆区', + 710351: '佳里区', + 710352: '西港区', + 710353: '七股区', + 710354: '将军区', + 710355: '学甲区', + 710356: '北门区', + 710357: '新营区', + 710358: '后壁区', + 710359: '白河区', + 710360: '东山区', + 710361: '六甲区', + 710362: '下营区', + 710363: '柳营区', + 710364: '盐水区', + 710365: '善化区', + 710366: '大内区', + 710367: '山上区', + 710368: '新市区', + 710369: '安定区', + 710399: '其它区', + 710401: '中区', + 710402: '东区', + 710403: '南区', + 710404: '西区', + 710405: '北区', + 710406: '北屯区', + 710407: '西屯区', + 710408: '南屯区', + 710431: '太平区', + 710432: '大里区', + 710433: '雾峰区', + 710434: '乌日区', + 710435: '丰原区', + 710436: '后里区', + 710437: '石冈区', + 710438: '东势区', + 710439: '和平区', + 710440: '新社区', + 710441: '潭子区', + 710442: '大雅区', + 710443: '神冈区', + 710444: '大肚区', + 710445: '沙鹿区', + 710446: '龙井区', + 710447: '梧栖区', + 710448: '清水区', + 710449: '大甲区', + 710450: '外埔区', + 710451: '大安区', + 710499: '其它区', + 710507: '金沙镇', + 710508: '金湖镇', + 710509: '金宁乡', + 710510: '金城镇', + 710511: '烈屿乡', + 710512: '乌坵乡', + 710614: '南投市', + 710615: '中寮乡', + 710616: '草屯镇', + 710617: '国姓乡', + 710618: '埔里镇', + 710619: '仁爱乡', + 710620: '名间乡', + 710621: '集集镇', + 710622: '水里乡', + 710623: '鱼池乡', + 710624: '信义乡', + 710625: '竹山镇', + 710626: '鹿谷乡', + 710701: '仁爱区', + 710702: '信义区', + 710703: '中正区', + 710704: '中山区', + 710705: '安乐区', + 710706: '暖暖区', + 710707: '七堵区', + 710799: '其它区', + 710801: '东区', + 710802: '北区', + 710803: '香山区', + 710899: '其它区', + 710901: '东区', + 710902: '西区', + 710999: '其它区', + 711130: '万里区', + 711132: '板桥区', + 711133: '汐止区', + 711134: '深坑区', + 711136: '瑞芳区', + 711137: '平溪区', + 711138: '双溪区', + 711140: '新店区', + 711141: '坪林区', + 711142: '乌来区', + 711143: '永和区', + 711144: '中和区', + 711145: '土城区', + 711146: '三峡区', + 711147: '树林区', + 711149: '三重区', + 711150: '新庄区', + 711151: '泰山区', + 711152: '林口区', + 711154: '五股区', + 711155: '八里区', + 711156: '淡水区', + 711157: '三芝区', + 711287: '宜兰市', + 711288: '头城镇', + 711289: '礁溪乡', + 711290: '壮围乡', + 711291: '员山乡', + 711292: '罗东镇', + 711293: '三星乡', + 711294: '大同乡', + 711295: '五结乡', + 711296: '冬山乡', + 711297: '苏澳镇', + 711298: '南澳乡', + 711299: '钓鱼台', + 711387: '竹北市', + 711388: '湖口乡', + 711389: '新丰乡', + 711390: '新埔镇', + 711391: '关西镇', + 711392: '芎林乡', + 711393: '宝山乡', + 711394: '竹东镇', + 711395: '五峰乡', + 711396: '横山乡', + 711397: '尖石乡', + 711398: '北埔乡', + 711399: '峨眉乡', + 711487: '中坜市', + 711488: '平镇市', + 711489: '龙潭乡', + 711490: '杨梅市', + 711491: '新屋乡', + 711492: '观音乡', + 711493: '桃园市', + 711494: '龟山乡', + 711495: '八德市', + 711496: '大溪镇', + 711497: '复兴乡', + 711498: '大园乡', + 711499: '芦竹乡', + 711582: '竹南镇', + 711583: '头份镇', + 711584: '三湾乡', + 711585: '南庄乡', + 711586: '狮潭乡', + 711587: '后龙镇', + 711588: '通霄镇', + 711589: '苑里镇', + 711590: '苗栗市', + 711591: '造桥乡', + 711592: '头屋乡', + 711593: '公馆乡', + 711594: '大湖乡', + 711595: '泰安乡', + 711596: '铜锣乡', + 711597: '三义乡', + 711598: '西湖乡', + 711599: '卓兰镇', + 711774: '彰化市', + 711775: '芬园乡', + 711776: '花坛乡', + 711777: '秀水乡', + 711778: '鹿港镇', + 711779: '福兴乡', + 711780: '线西乡', + 711781: '和美镇', + 711782: '伸港乡', + 711783: '员林镇', + 711784: '社头乡', + 711785: '永靖乡', + 711786: '埔心乡', + 711787: '溪湖镇', + 711788: '大村乡', + 711789: '埔盐乡', + 711790: '田中镇', + 711791: '北斗镇', + 711792: '田尾乡', + 711793: '埤头乡', + 711794: '溪州乡', + 711795: '竹塘乡', + 711796: '二林镇', + 711797: '大城乡', + 711798: '芳苑乡', + 711799: '二水乡', + 711982: '番路乡', + 711983: '梅山乡', + 711984: '竹崎乡', + 711985: '阿里山乡', + 711986: '中埔乡', + 711987: '大埔乡', + 711988: '水上乡', + 711989: '鹿草乡', + 711990: '太保市', + 711991: '朴子市', + 711992: '东石乡', + 711993: '六脚乡', + 711994: '新港乡', + 711995: '民雄乡', + 711996: '大林镇', + 711997: '溪口乡', + 711998: '义竹乡', + 711999: '布袋镇', + 712180: '斗南镇', + 712181: '大埤乡', + 712182: '虎尾镇', + 712183: '土库镇', + 712184: '褒忠乡', + 712185: '东势乡', + 712186: '台西乡', + 712187: '仑背乡', + 712188: '麦寮乡', + 712189: '斗六市', + 712190: '林内乡', + 712191: '古坑乡', + 712192: '莿桐乡', + 712193: '西螺镇', + 712194: '二仑乡', + 712195: '北港镇', + 712196: '水林乡', + 712197: '口湖乡', + 712198: '四湖乡', + 712199: '元长乡', + 712467: '屏东市', + 712468: '三地门乡', + 712469: '雾台乡', + 712470: '玛家乡', + 712471: '九如乡', + 712472: '里港乡', + 712473: '高树乡', + 712474: '盐埔乡', + 712475: '长治乡', + 712476: '麟洛乡', + 712477: '竹田乡', + 712478: '内埔乡', + 712479: '万丹乡', + 712480: '潮州镇', + 712481: '泰武乡', + 712482: '来义乡', + 712483: '万峦乡', + 712484: '莰顶乡', + 712485: '新埤乡', + 712486: '南州乡', + 712487: '林边乡', + 712488: '东港镇', + 712489: '琉球乡', + 712490: '佳冬乡', + 712491: '新园乡', + 712492: '枋寮乡', + 712493: '枋山乡', + 712494: '春日乡', + 712495: '狮子乡', + 712496: '车城乡', + 712497: '牡丹乡', + 712498: '恒春镇', + 712499: '满州乡', + 712584: '台东市', + 712585: '绿岛乡', + 712586: '兰屿乡', + 712587: '延平乡', + 712588: '卑南乡', + 712589: '鹿野乡', + 712590: '关山镇', + 712591: '海端乡', + 712592: '池上乡', + 712593: '东河乡', + 712594: '成功镇', + 712595: '长滨乡', + 712596: '金峰乡', + 712597: '大武乡', + 712598: '达仁乡', + 712599: '太麻里乡', + 712686: '花莲市', + 712687: '新城乡', + 712688: '太鲁阁', + 712689: '秀林乡', + 712690: '吉安乡', + 712691: '寿丰乡', + 712692: '凤林镇', + 712693: '光复乡', + 712694: '丰滨乡', + 712695: '瑞穗乡', + 712696: '万荣乡', + 712697: '玉里镇', + 712698: '卓溪乡', + 712699: '富里乡', + 712794: '马公市', + 712795: '西屿乡', + 712796: '望安乡', + 712797: '七美乡', + 712798: '白沙乡', + 712799: '湖西乡', + 712896: '南竿乡', + 712897: '北竿乡', + 712898: '东引乡', + 712899: '莒光乡', + 810101: '中西区', + 810102: '湾仔', + 810103: '东区', + 810104: '南区', + 810201: '九龙城区', + 810202: '油尖旺区', + 810203: '深水埗区', + 810204: '黄大仙区', + 810205: '观塘区', + 810301: '北区', + 810302: '大埔区', + 810303: '沙田区', + 810304: '西贡区', + 810305: '元朗区', + 810306: '屯门区', + 810307: '荃湾区', + 810308: '葵青区', + 810309: '离岛区', + 820101: '澳门半岛', + 820201: '离岛' + } + }; + + + Class.prototype.config = { + elem: '', + data: { + province: '', + city: '', + county: '', + provinceCode: 0, + cityCode: 0, + countyCode: 0, + }, + change: function(result){} + }; + + Class.prototype.index = 0; + + Class.prototype.render = function () { + let that = this, options = that.config; + options.elem = $(options.elem); + options.bindAction = $(options.bindAction); + + that.events(); + }; + + Class.prototype.events = function () { + let that = this, options = that.config, index; + let provinceFilter = 'province-' + layarea._id; + let cityFilter = 'city-' + layarea._id; + let countyFilter = 'county-' + layarea._id; + + let provinceEl = options.elem.find('.province-selector'); + let cityEl = options.elem.find('.city-selector'); + let countyEl = options.elem.find('.county-selector'); + + //filter + if(provinceEl.attr('lay-filter')){ + provinceFilter = provinceEl.attr('lay-filter'); + } + if(cityEl.attr('lay-filter')){ + cityFilter = cityEl.attr('lay-filter'); + } + if(countyEl.attr('lay-filter')){ + countyFilter = countyEl.attr('lay-filter'); + } + provinceEl.attr('lay-filter', provinceFilter); + cityEl.attr('lay-filter', cityFilter); + countyEl.attr('lay-filter', countyFilter); + + //获取默认值 + if(provinceEl.data('value')){ + options.data.province = provinceEl.data('value'); + options.data.provinceCode = getCode('province', options.data.province); + } + if(cityEl.data('value')){ + options.data.city = cityEl.data('value'); + let code = getCode('city', options.data.city, options.data.provinceCode.slice(0, 2)); + options.data.cityCode = code; + } + if(countyEl.data('value')){ + options.data.county = countyEl.data('value'); + options.data.countyCode = getCode('county', options.data.county, options.data.cityCode.slice(0, 4)); + } + provinceEl.attr('lay-filter', provinceFilter); + cityEl.attr('lay-filter', cityFilter); + countyEl.attr('lay-filter', countyFilter); + + //监听结果 + form.on('select('+provinceFilter+')', function(data){ + options.data.province = data.value; + options.data.provinceCode = getCode('province', data.value); + renderCity(options.data.provinceCode); + + options.change(options.data); + }); + form.on('select('+cityFilter+')', function(data){ + options.data.city = data.value; + if(options.data.provinceCode){ + options.data.cityCode = getCode('city', data.value, options.data.provinceCode.slice(0, 2)); + renderCounty(options.data.cityCode); + } + + options.change(options.data); + }); + form.on('select('+countyFilter+')', function(data){ + options.data.county = data.value; + if(options.data.cityCode){ + options.data.countyCode = getCode('county', data.value, options.data.cityCode.slice(0, 4)); + } + options.change(options.data); + }); + + renderProvince(); + + //查找province + function renderProvince(){ + let tpl = ''; + let provinceList = getList("province"); + let currentCode = ''; + let currentName = ''; + provinceList.forEach(function(_item){ + // if (!currentCode){ + // currentCode = _item.code; + // currentName = _item.name; + // } + if(_item.name === options.data.province){ + currentCode = _item.code; + currentName = _item.name; + } + tpl += ''; + }); + provinceEl.html(tpl); + provinceEl.val(options.data.province); + form.render('select'); + renderCity(currentCode); + } + + function renderCity(provinceCode){ + let tpl = ''; + let cityList = getList('city', provinceCode.slice(0, 2)); + let currentCode = ''; + let currentName = ''; + cityList.forEach(function(_item){ + // if (!currentCode){ + // currentCode = _item.code; + // currentName = _item.name; + // } + if(_item.name === options.data.city){ + currentCode = _item.code; + currentName = _item.name; + } + tpl += ''; + }); + options.data.city = currentName; + cityEl.html(tpl); + cityEl.val(options.data.city); + form.render('select'); + renderCounty(currentCode); + } + + function renderCounty(cityCode){ + let tpl = ''; + let countyList = getList('county', cityCode.slice(0, 4)); + let currentCode = ''; + let currentName = ''; + countyList.forEach(function(_item){ + // if (!currentCode){ + // currentCode = _item.code; + // currentName = _item.name; + // } + if(_item.name === options.data.county){ + currentCode = _item.code; + currentName = _item.name; + } + tpl += ''; + }); + options.data.county = currentName; + countyEl.html(tpl); + countyEl.val(options.data.county); + + form.render('select'); + } + + function getList(type, code) { + let result = []; + + if (type !== 'province' && !code) { + return result; + } + + let list = areaList[type + "_list"] || {}; + result = Object.keys(list).map(function (code) { + return { + code: code, + name: list[code] + }; + }); + + if (code) { + // oversea code + if (code[0] === '9' && type === 'city') { + code = '9'; + } + + result = result.filter(function (item) { + return item.code.indexOf(code) === 0; + }); + } + + return result; + } + + function getCode(type, name, parentCode = 0){ + let code = ''; + let list = areaList[type + "_list"] || {}; + let result = {}; + Object.keys(list).map(function (_code) { + if(parentCode){ + if(_code.indexOf(parentCode) === 0){ + result[_code] = list[_code]; + } + }else{ + result[_code] = list[_code]; + } + }); + layui.each(result, function(_code, _name){ + if(_name === name){ + code = _code; + } + }); + + return code; + } + }; + + layarea.render = function (options) { + let inst = new Class(options); + layarea._id++; + return thisArea.call(inst); + }; + + //暴露接口 + exports('layarea', layarea); +}); \ No newline at end of file diff --git a/public/static/layuimini/js/lay-module/layuimini/miniAdmin.js b/public/static/layuimini/js/lay-module/layuimini/miniAdmin.js new file mode 100644 index 0000000..08b4b6b --- /dev/null +++ b/public/static/layuimini/js/lay-module/layuimini/miniAdmin.js @@ -0,0 +1,350 @@ +/** + * date:2020/02/27 + * author:Mr.Chung + * version:2.0 + * description:layuimini 主体框架扩展 + */ +layui.define(["jquery", "miniMenu", "element","miniTab", "miniTheme"], function (exports) { + var $ = layui.$, + layer = layui.layer, + miniMenu = layui.miniMenu, + miniTheme = layui.miniTheme, + element = layui.element , + miniTab = layui.miniTab; + + if (!/http(s*):\/\//.test(location.href)) { + var tips = "请先将项目部署至web容器(Apache/Tomcat/Nginx/IIS/等),否则部分数据将无法显示"; + return layer.alert(tips); + } + + var miniAdmin = { + + /** + * 后台框架初始化 + * @param options.iniUrl 后台初始化接口地址 + * @param options.clearUrl 后台清理缓存接口 + * @param options.urlHashLocation URL地址hash定位 + * @param options.bgColorDefault 默认皮肤 + * @param options.multiModule 是否开启多模块 + * @param options.menuChildOpen 是否展开子菜单 + * @param options.loadingTime 初始化加载时间 + * @param options.pageAnim iframe窗口动画 + * @param options.maxTabNum 最大的tab打开数量 + */ + render: function (options) { + options.iniUrl = options.iniUrl || null; + options.clearUrl = options.clearUrl || null; + options.urlHashLocation = options.urlHashLocation || false; + options.bgColorDefault = options.bgColorDefault || 0; + options.multiModule = options.multiModule || false; + options.menuChildOpen = options.menuChildOpen || false; + options.loadingTime = options.loadingTime || 1; + options.pageAnim = options.pageAnim || false; + options.maxTabNum = options.maxTabNum || 20; + $.getJSON(options.iniUrl, function (data) { + if (data == null) { + miniAdmin.error('暂无菜单信息') + } else { + miniAdmin.renderLogo(data.logoInfo); + miniAdmin.renderClear(options.clearUrl); + miniAdmin.renderHome(data.homeInfo); + miniAdmin.renderAnim(options.pageAnim); + miniAdmin.listen(); + miniMenu.render({ + menuList: data.menuInfo, + multiModule: options.multiModule, + menuChildOpen: options.menuChildOpen + }); + miniTab.render({ + filter: 'layuiminiTab', + urlHashLocation: options.urlHashLocation, + multiModule: options.multiModule, + menuChildOpen: options.menuChildOpen, + maxTabNum: options.maxTabNum, + menuList: data.menuInfo, + homeInfo: data.homeInfo, + listenSwichCallback: function () { + miniAdmin.renderDevice(); + } + }); + miniTheme.render({ + bgColorDefault: options.bgColorDefault, + listen: true, + }); + miniAdmin.deleteLoader(options.loadingTime); + } + }).fail(function () { + miniAdmin.error('菜单接口有误'); + }); + }, + + /** + * 初始化logo + * @param data + */ + renderLogo: function (data) { + var html = 'logo

    ' + data.title + '

    '; + $('.layuimini-logo').html(html); + }, + + /** + * 初始化首页 + * @param data + */ + renderHome: function (data) { + sessionStorage.setItem('layuiminiHomeHref', data.href); + $('#layuiminiHomeTabId').html('' + data.title + ''); + $('#layuiminiHomeTabId').attr('lay-id', data.href); + $('#layuiminiHomeTabIframe').html(''); + }, + + /** + * 初始化缓存地址 + * @param clearUrl + */ + renderClear: function (clearUrl) { + $('.layuimini-clear').attr('data-href',clearUrl); + }, + + /** + * 初始化iframe窗口动画 + * @param anim + */ + renderAnim: function (anim) { + if (anim) { + $('#layuimini-bg-color').after(''); + } + }, + + fullScreen: function () { + var el = document.documentElement; + var rfs = el.requestFullScreen || el.webkitRequestFullScreen; + if (typeof rfs != "undefined" && rfs) { + rfs.call(el); + } else if (typeof window.ActiveXObject != "undefined") { + var wscript = new ActiveXObject("WScript.Shell"); + if (wscript != null) { + wscript.SendKeys("{F11}"); + } + } else if (el.msRequestFullscreen) { + el.msRequestFullscreen(); + } else if (el.oRequestFullscreen) { + el.oRequestFullscreen(); + } else if (el.webkitRequestFullscreen) { + el.webkitRequestFullscreen(); + } else if (el.mozRequestFullScreen) { + el.mozRequestFullScreen(); + } else { + miniAdmin.error('浏览器不支持全屏调用!'); + } + }, + + /** + * 退出全屏 + */ + exitFullScreen: function () { + var el = document; + var cfs = el.cancelFullScreen || el.webkitCancelFullScreen || el.exitFullScreen; + if (typeof cfs != "undefined" && cfs) { + cfs.call(el); + } else if (typeof window.ActiveXObject != "undefined") { + var wscript = new ActiveXObject("WScript.Shell"); + if (wscript != null) { + wscript.SendKeys("{F11}"); + } + } else if (el.msExitFullscreen) { + el.msExitFullscreen(); + } else if (el.oRequestFullscreen) { + el.oCancelFullScreen(); + }else if (el.mozCancelFullScreen) { + el.mozCancelFullScreen(); + } else if (el.webkitCancelFullScreen) { + el.webkitCancelFullScreen(); + } else { + miniAdmin.error('浏览器不支持全屏调用!'); + } + }, + + /** + * 初始化设备端 + */ + renderDevice: function () { + if (miniAdmin.checkMobile()) { + $('.layuimini-tool i').attr('data-side-fold', 1); + $('.layuimini-tool i').attr('class', 'fa fa-outdent'); + $('.layui-layout-body').removeClass('layuimini-mini'); + $('.layui-layout-body').addClass('layuimini-all'); + } + }, + + + /** + * 初始化加载时间 + * @param loadingTime + */ + deleteLoader: function (loadingTime) { + setTimeout(function () { + $('.layuimini-loader').fadeOut(); + }, loadingTime * 1000) + }, + + /** + * 成功 + * @param title + * @returns {*} + */ + success: function (title) { + return layer.msg(title, {icon: 1, shade: this.shade, scrollbar: false, time: 2000, shadeClose: true}); + }, + + /** + * 失败 + * @param title + * @returns {*} + */ + error: function (title) { + return layer.msg(title, {icon: 2, shade: this.shade, scrollbar: false, time: 3000, shadeClose: true}); + }, + + /** + * 判断是否为手机 + * @returns {boolean} + */ + checkMobile: function () { + var ua = navigator.userAgent.toLocaleLowerCase(); + var pf = navigator.platform.toLocaleLowerCase(); + var isAndroid = (/android/i).test(ua) || ((/iPhone|iPod|iPad/i).test(ua) && (/linux/i).test(pf)) + || (/ucweb.*linux/i.test(ua)); + var isIOS = (/iPhone|iPod|iPad/i).test(ua) && !isAndroid; + var isWinPhone = (/Windows Phone|ZuneWP7/i).test(ua); + var clientWidth = document.documentElement.clientWidth; + if (!isAndroid && !isIOS && !isWinPhone && clientWidth > 1024) { + return false; + } else { + return true; + } + }, + + /** + * 监听 + */ + listen: function () { + + /** + * 清理 + */ + $('body').on('click', '[data-clear]', function () { + var loading = layer.load(0, {shade: false, time: 2 * 1000}); + sessionStorage.clear(); + + // 判断是否清理服务端 + var clearUrl = $(this).attr('data-href'); + if (clearUrl != undefined && clearUrl != '' && clearUrl != null) { + $.getJSON(clearUrl, function (data, status) { + layer.close(loading); + if (data.code != 1) { + return miniAdmin.error(data.msg); + } else { + return miniAdmin.success(data.msg); + } + }).fail(function () { + layer.close(loading); + return miniAdmin.error('清理缓存接口有误'); + }); + } else { + layer.close(loading); + return miniAdmin.success('清除缓存成功'); + } + }); + + /** + * 刷新 + */ + $('body').on('click', '[data-refresh]', function () { + $(".layui-tab-item.layui-show").find("iframe")[0].contentWindow.location.reload(); + miniAdmin.success('刷新成功'); + }); + + /** + * 监听提示信息 + */ + $("body").on("mouseenter", ".layui-nav-tree .menu-li", function () { + if (miniAdmin.checkMobile()) { + return false; + } + var classInfo = $(this).attr('class'), + tips = $(this).prop("innerHTML"), + isShow = $('.layuimini-tool i').attr('data-side-fold'); + if (isShow == 0 && tips) { + tips = "
    • "+tips+"
    " ; + window.openTips = layer.tips(tips, $(this), { + tips: [2, '#2f4056'], + time: 300000, + skin:"popup-tips", + success:function (el) { + var left = $(el).position().left - 10 ; + $(el).css({ left:left }); + element.render(); + } + }); + } + }); + + $("body").on("mouseleave", ".popup-tips", function () { + if (miniAdmin.checkMobile()) { + return false; + } + var isShow = $('.layuimini-tool i').attr('data-side-fold'); + if (isShow == 0) { + try { + layer.close(window.openTips); + } catch (e) { + console.log(e.message); + } + } + }); + + + /** + * 全屏 + */ + $('body').on('click', '[data-check-screen]', function () { + var check = $(this).attr('data-check-screen'); + if (check == 'full') { + miniAdmin.fullScreen(); + $(this).attr('data-check-screen', 'exit'); + $(this).html(''); + } else { + miniAdmin.exitFullScreen(); + $(this).attr('data-check-screen', 'full'); + $(this).html(''); + } + }); + + /** + * 点击遮罩层 + */ + $('body').on('click', '.layuimini-make', function () { + miniAdmin.renderDevice(); + }); + + } + }; + + + exports("miniAdmin", miniAdmin); +}); diff --git a/public/static/layuimini/js/lay-module/layuimini/miniMenu.js b/public/static/layuimini/js/lay-module/layuimini/miniMenu.js new file mode 100644 index 0000000..507eae3 --- /dev/null +++ b/public/static/layuimini/js/lay-module/layuimini/miniMenu.js @@ -0,0 +1,250 @@ +/** + * date:2020/02/27 + * author:Mr.Chung + * version:2.0 + * description:layuimini 菜单框架扩展 + */ +layui.define(["element","laytpl" ,"jquery"], function (exports) { + var element = layui.element, + $ = layui.$, + laytpl = layui.laytpl, + layer = layui.layer; + + var miniMenu = { + + /** + * 菜单初始化 + * @param options.menuList 菜单数据信息 + * @param options.multiModule 是否开启多模块 + * @param options.menuChildOpen 是否展开子菜单 + */ + render: function (options) { + options.menuList = options.menuList || []; + options.multiModule = options.multiModule || false; + options.menuChildOpen = options.menuChildOpen || false; + if (options.multiModule) { + miniMenu.renderMultiModule(options.menuList, options.menuChildOpen); + } else { + miniMenu.renderSingleModule(options.menuList, options.menuChildOpen); + } + miniMenu.listen(); + }, + + /** + * 单模块 + * @param menuList 菜单数据 + * @param menuChildOpen 是否默认展开 + */ + renderSingleModule: function (menuList, menuChildOpen) { + menuList = menuList || []; + var leftMenuHtml = '', + childOpenClass = '', + leftMenuCheckDefault = 'layui-this'; + var me = this ; + if (menuChildOpen) childOpenClass = ' layui-nav-itemed'; + leftMenuHtml = this.renderLeftMenu(menuList,{ childOpenClass:childOpenClass }) ; + $('.layui-layout-body').addClass('layuimini-single-module'); //单模块标识 + $('.layuimini-header-menu').remove(); + $('.layuimini-menu-left').html(leftMenuHtml); + + element.init(); + }, + + /** + * 渲染一级菜单 + */ + compileMenu: function(menu,isSub){ + var menuHtml = '' ; + if(isSub){ + menuHtml = '' + } + return laytpl(menuHtml).render(menu); + }, + compileMenuContainer :function(menu,isSub){ + var wrapperHtml = '
      {{d.children}}
    ' ; + if(isSub){ + wrapperHtml = '
    {{d.children}}
    ' ; + } + if(!menu.children){ + return ""; + } + return laytpl(wrapperHtml).render(menu); + }, + + each:function(list,callback){ + var _list = []; + for(var i = 0 ,length = list.length ; i= options.maxTabNum) { + layer.msg('Tab窗口已达到限定数量,请先关闭部分Tab'); + return false; + } + var ele = element; + if (options.isIframe) ele = parent.parent.layui.element; + ele.tabAdd('layuiminiTab', { + title: '' + options.title + '' //用于演示 + , content: '' + , id: options.tabId + }); + $('.layuimini-menu-left').attr('layuimini-tab-tag', 'add'); + sessionStorage.setItem('layuiminimenu_' + options.tabId, options.title); + }, + + + /** + * 切换选项卡 + * @param tabId + */ + change: function (tabId) { + element.tabChange('layuiminiTab', tabId); + }, + + /** + * 删除tab窗口 + * @param tabId + * @param isParent + */ + delete: function (tabId, isParent) { + // todo 未知BUG,不知道是不是layui问题,必须先删除元素 + $(".layuimini-tab .layui-tab-title .layui-unselect.layui-tab-bar").remove(); + + if (isParent === true) { + parent.layui.element.tabDelete('layuiminiTab', tabId); + } else { + element.tabDelete('layuiminiTab', tabId); + } + }, + + /** + * 在iframe层打开新tab方法 + */ + openNewTabByIframe: function (options) { + options.href = options.href || null; + options.title = options.title || null; + var loading = parent.layer.load(0, {shade: false, time: 2 * 1000}); + if (options.href === null || options.href === undefined) options.href = new Date().getTime(); + var checkTab = miniTab.check(options.href, true); + if (!checkTab) { + miniTab.create({ + tabId: options.href, + href: options.href, + title: options.title, + isIframe: true, + }); + } + parent.layui.element.tabChange('layuiminiTab', options.href); + parent.layer.close(loading); + }, + + /** + * 在iframe层关闭当前tab方法 + */ + deleteCurrentByIframe: function () { + var ele = $(".layuimini-tab .layui-tab-title li.layui-this", parent.document); + if (ele.length > 0) { + var layId = $(ele[0]).attr('lay-id'); + miniTab.delete(layId, true); + } + }, + + /** + * 判断tab窗口 + */ + check: function (tabId, isIframe) { + // 判断选项卡上是否有 + var checkTab = false; + if (isIframe === undefined || isIframe === false) { + $(".layui-tab-title li").each(function () { + var checkTabId = $(this).attr('lay-id'); + if (checkTabId != null && checkTabId === tabId) { + checkTab = true; + } + }); + } else { + parent.layui.$(".layui-tab-title li").each(function () { + var checkTabId = $(this).attr('lay-id'); + if (checkTabId != null && checkTabId === tabId) { + checkTab = true; + } + }); + + parent.parent.layui.$(".layui-tab-title li").each(function () { + var checkTabId = $(this).attr('lay-id'); + if (checkTabId != null && checkTabId === tabId) { + checkTab = true; + } + }); + } + return checkTab; + }, + + /** + * 开启tab右键菜单 + * @param tabId + * @param left + */ + openTabRignMenu: function (tabId, left) { + miniTab.closeTabRignMenu(); + var menuHtml = '
    \n' + + '
    \n' + + '
    关 闭 当 前
    \n' + + '
    关 闭 其 他
    \n' + + '
    关 闭 全 部
    \n' + + '
    \n' + + '
    '; + var makeHtml = '
    '; + $('.layuimini-tab .layui-tab-title').after(menuHtml); + $('.layuimini-tab .layui-tab-content').after(makeHtml); + }, + + /** + * 关闭tab右键菜单 + */ + closeTabRignMenu: function () { + $('.layuimini-tab-mousedown').remove(); + $('.layuimini-tab-make').remove(); + }, + + /** + * 查询菜单信息 + * @param href + * @param menuList + */ + searchMenu: function (href, menuList) { + var menu; + for (key in menuList) { + var item = menuList[key]; + if (item.href === href) { + menu = item; + break; + } + if (item.child) { + newMenu = miniTab.searchMenu(href, item.child); + if (newMenu) { + menu = newMenu; + break; + } + } + } + return menu; + }, + + /** + * 监听 + * @param options + */ + listen: function (options) { + options = options || {}; + options.maxTabNum = options.maxTabNum || 20; + + /** + * 打开新窗口 + */ + $('body').on('click', '[layuimini-href]', function () { + var loading = layer.load(0, {shade: false, time: 2 * 1000}); + var tabId = $(this).attr('layuimini-href'), + href = $(this).attr('layuimini-href'), + title = $(this).text(), + target = $(this).attr('target'); + + var el = $("[layuimini-href='" + href + "']", ".layuimini-menu-left"); + layer.close(window.openTips); + if (el.length) { + $(el).closest(".layui-nav-tree").find(".layui-this").removeClass("layui-this"); + $(el).parent().addClass("layui-this"); + } + + if (target === '_blank') { + layer.close(loading); + window.open(href, "_blank"); + return false; + } + + if (tabId === null || tabId === undefined) tabId = new Date().getTime(); + var checkTab = miniTab.check(tabId); + if (!checkTab) { + miniTab.create({ + tabId: tabId, + href: href, + title: title, + isIframe: false, + maxTabNum: options.maxTabNum, + }); + } + element.tabChange('layuiminiTab', tabId); + layer.close(loading); + }); + + /** + * 在iframe子菜单上打开新窗口 + */ + $('body').on('click', '[layuimini-content-href]', function () { + var loading = parent.layer.load(0, {shade: false, time: 2 * 1000}); + var tabId = $(this).attr('layuimini-content-href'), + href = $(this).attr('layuimini-content-href'), + title = $(this).attr('data-title'), + target = $(this).attr('target'); + if (target === '_blank') { + parent.layer.close(loading); + window.open(href, "_blank"); + return false; + } + if (tabId === null || tabId === undefined) tabId = new Date().getTime(); + var checkTab = miniTab.check(tabId, true); + if (!checkTab) { + miniTab.create({ + tabId: tabId, + href: href, + title: title, + isIframe: true, + maxTabNum: options.maxTabNum, + }); + } + parent.parent.layui.element.tabChange('layuiminiTab', tabId); + parent.parent.layer.close(loading); + }); + + /** + * 关闭选项卡 + **/ + $('body').on('click', '.layuimini-tab .layui-tab-title .layui-tab-close', function () { + var loading = layer.load(0, {shade: false, time: 2 * 1000}); + var $parent = $(this).parent(); + var tabId = $parent.attr('lay-id'); + if (tabId !== undefined || tabId !== null) { + miniTab.delete(tabId); + } + layer.close(loading); + }); + + /** + * 选项卡操作 + */ + $('body').on('click', '[layuimini-tab-close]', function () { + var loading = layer.load(0, {shade: false, time: 2 * 1000}); + var closeType = $(this).attr('layuimini-tab-close'); + $(".layuimini-tab .layui-tab-title li").each(function () { + var tabId = $(this).attr('lay-id'); + var id = $(this).attr('id'); + var isCurrent = $(this).hasClass('layui-this'); + if (id !== 'layuiminiHomeTabId') { + if (closeType === 'all') { + miniTab.delete(tabId); + } else { + if (closeType === 'current' && isCurrent) { + miniTab.delete(tabId); + } else if (closeType === 'other' && !isCurrent) { + miniTab.delete(tabId); + } + } + } + }); + layer.close(loading); + }); + + /** + * 禁用网页右键 + */ + $(".layuimini-tab .layui-tab-title").unbind("mousedown").bind("contextmenu", function (e) { + e.preventDefault(); + return false; + }); + + /** + * 注册鼠标右键 + */ + $('body').on('mousedown', '.layuimini-tab .layui-tab-title li', function (e) { + var left = $(this).offset().left - $('.layuimini-tab ').offset().left + ($(this).width() / 2), + tabId = $(this).attr('lay-id'); + if (e.which === 3) { + miniTab.openTabRignMenu(tabId, left); + } + }); + + /** + * 关闭tab右键菜单 + */ + $('body').on('click', '.layui-body,.layui-header,.layuimini-menu-left,.layuimini-tab-make', function () { + miniTab.closeTabRignMenu(); + }); + + /** + * tab右键选项卡操作 + */ + $('body').on('click', '[layuimini-tab-menu-close]', function () { + var loading = layer.load(0, {shade: false, time: 2 * 1000}); + var closeType = $(this).attr('layuimini-tab-menu-close'), + currentTabId = $('.layuimini-tab-mousedown').attr('data-tab-id'); + $(".layuimini-tab .layui-tab-title li").each(function () { + var tabId = $(this).attr('lay-id'); + var id = $(this).attr('id'); + if (id !== 'layuiminiHomeTabId') { + if (closeType === 'all') { + miniTab.delete(tabId); + } else { + if (closeType === 'current' && currentTabId === tabId) { + miniTab.delete(tabId); + } else if (closeType === 'other' && currentTabId !== tabId) { + miniTab.delete(tabId); + } + } + } + }); + miniTab.closeTabRignMenu(); + layer.close(loading); + }); + }, + + /** + * 监听tab切换 + * @param options + */ + listenSwitch: function (options) { + options.filter = options.filter || null; + options.multiModule = options.multiModule || false; + options.urlHashLocation = options.urlHashLocation || false; + options.listenSwichCallback = options.listenSwichCallback || function () { + + }; + element.on('tab(' + options.filter + ')', function (data) { + var tabId = $(this).attr('lay-id'); + if (options.urlHashLocation) { + location.hash = '/' + tabId; + } + if (typeof options.listenSwichCallback === 'function') { + options.listenSwichCallback(); + } + // 判断是否为新增窗口 + if ($('.layuimini-menu-left').attr('layuimini-tab-tag') === 'add') { + $('.layuimini-menu-left').attr('layuimini-tab-tag', 'no') + } else { + $("[layuimini-href]").parent().removeClass('layui-this'); + if (options.multiModule) { + miniTab.listenSwitchMultiModule(tabId); + } else { + miniTab.listenSwitchSingleModule(tabId); + } + } + miniTab.rollPosition(); + }); + }, + + /** + * 监听hash变化 + * @param options + * @returns {boolean} + */ + listenHash: function (options) { + options.urlHashLocation = options.urlHashLocation || false; + options.maxTabNum = options.maxTabNum || 20; + options.homeInfo = options.homeInfo || {}; + options.menuList = options.menuList || []; + if (!options.urlHashLocation) return false; + var tabId = location.hash.replace(/^#\//, ''); + if (tabId === null || tabId === undefined || tabId ==='') return false; + + // 判断是否为首页 + if(tabId ===options.homeInfo.href) return false; + + // 判断是否为右侧菜单 + var menu = miniTab.searchMenu(tabId, options.menuList); + if (menu !== undefined) { + miniTab.create({ + tabId: tabId, + href: tabId, + title: menu.title, + isIframe: false, + maxTabNum: options.maxTabNum, + }); + $('.layuimini-menu-left').attr('layuimini-tab-tag', 'no'); + element.tabChange('layuiminiTab', tabId); + return false; + } + + // 判断是否为快捷菜单 + var isSearchMenu = false; + $("[layuimini-content-href]").each(function () { + if ($(this).attr("layuimini-content-href") === tabId) { + var title = $(this).attr("data-title"); + miniTab.create({ + tabId: tabId, + href: tabId, + title: title, + isIframe: false, + maxTabNum: options.maxTabNum, + }); + $('.layuimini-menu-left').attr('layuimini-tab-tag', 'no'); + element.tabChange('layuiminiTab', tabId); + isSearchMenu = true; + return false; + } + }); + if (isSearchMenu) return false; + + // 既不是右侧菜单、快捷菜单,就直接打开 + var title = sessionStorage.getItem('layuiminimenu_' + tabId) === null ? tabId : sessionStorage.getItem('layuiminimenu_' + tabId); + miniTab.create({ + tabId: tabId, + href: tabId, + title: title, + isIframe: false, + maxTabNum: options.maxTabNum, + }); + element.tabChange('layuiminiTab', tabId); + return false; + }, + + /** + * 监听滚动 + */ + listenRoll: function () { + $(".layuimini-tab-roll-left").click(function () { + miniTab.rollClick("left"); + }); + $(".layuimini-tab-roll-right").click(function () { + miniTab.rollClick("right"); + }); + }, + + /** + * 单模块切换 + * @param tabId + */ + listenSwitchSingleModule: function (tabId) { + $("[layuimini-href]").each(function () { + if ($(this).attr("layuimini-href") === tabId) { + // 自动展开菜单栏 + var addMenuClass = function ($element, type) { + if (type === 1) { + $element.addClass('layui-this'); + if ($element.hasClass('layui-nav-item') && $element.hasClass('layui-this')) { + $(".layuimini-header-menu li").attr('class', 'layui-nav-item'); + } else { + addMenuClass($element.parent().parent(), 2); + } + } else { + $element.addClass('layui-nav-itemed'); + if ($element.hasClass('layui-nav-item') && $element.hasClass('layui-nav-itemed')) { + $(".layuimini-header-menu li").attr('class', 'layui-nav-item'); + } else { + addMenuClass($element.parent().parent(), 2); + } + } + }; + addMenuClass($(this).parent(), 1); + return false; + } + }); + }, + + /** + * 多模块切换 + * @param tabId + */ + listenSwitchMultiModule: function (tabId) { + $("[layuimini-href]").each(function () { + if ($(this).attr("layuimini-href") === tabId) { + + // 自动展开菜单栏 + var addMenuClass = function ($element, type) { + if (type === 1) { + $element.addClass('layui-this'); + if ($element.hasClass('layui-nav-item') && $element.hasClass('layui-this')) { + var moduleId = $element.parent().attr('id'); + $(".layuimini-header-menu li").attr('class', 'layui-nav-item'); + $("#" + moduleId + "HeaderId").addClass("layui-this"); + $(".layuimini-menu-left .layui-nav.layui-nav-tree").attr('class', 'layui-nav layui-nav-tree layui-hide'); + $("#" + moduleId).attr('class', 'layui-nav layui-nav-tree layui-this'); + } else { + addMenuClass($element.parent().parent(), 2); + } + } else { + $element.addClass('layui-nav-itemed'); + if ($element.hasClass('layui-nav-item') && $element.hasClass('layui-nav-itemed')) { + var moduleId = $element.parent().attr('id'); + $(".layuimini-header-menu li").attr('class', 'layui-nav-item'); + $("#" + moduleId + "HeaderId").addClass("layui-this"); + $(".layuimini-menu-left .layui-nav.layui-nav-tree").attr('class', 'layui-nav layui-nav-tree layui-hide'); + $("#" + moduleId).attr('class', 'layui-nav layui-nav-tree layui-this'); + } else { + addMenuClass($element.parent().parent(), 2); + } + } + }; + addMenuClass($(this).parent(), 1); + return false; + } + }); + }, + + /** + * 自动定位 + */ + rollPosition: function () { + var $tabTitle = $('.layuimini-tab .layui-tab-title'); + var autoLeft = 0; + $tabTitle.children("li").each(function () { + if ($(this).hasClass('layui-this')) { + return false; + } else { + autoLeft += $(this).outerWidth(); + } + }); + $tabTitle.animate({ + scrollLeft: autoLeft - $tabTitle.width() / 3 + }, 200); + }, + + /** + * 点击滚动 + * @param direction + */ + rollClick: function (direction) { + var $tabTitle = $('.layuimini-tab .layui-tab-title'); + var left = $tabTitle.scrollLeft(); + if ('left' === direction) { + $tabTitle.animate({ + scrollLeft: left - 450 + }, 200); + } else { + $tabTitle.animate({ + scrollLeft: left + 450 + }, 200); + } + } + + }; + + exports("miniTab", miniTab); +}); diff --git a/public/static/layuimini/js/lay-module/layuimini/miniTheme.js b/public/static/layuimini/js/lay-module/layuimini/miniTheme.js new file mode 100644 index 0000000..1e13b5d --- /dev/null +++ b/public/static/layuimini/js/lay-module/layuimini/miniTheme.js @@ -0,0 +1,471 @@ +/** + * date:2020/02/28 + * author:Mr.Chung + * version:2.0 + * description:layuimini tab框架扩展 + */ +layui.define(["jquery", "layer"], function (exports) { + var $ = layui.$, + layer = layui.layer; + + var miniTheme = { + + /** + * 主题配置项 + * @param bgcolorId + * @returns {{headerLogo, menuLeftHover, headerRight, menuLeft, headerRightThis, menuLeftThis}|*|*[]} + */ + config: function (bgcolorId) { + var bgColorConfig = [ + { + headerRightBg: '#ffffff', //头部右侧背景色 + headerRightBgThis: '#e4e4e4', //头部右侧选中背景色, + headerRightColor: 'rgba(107, 107, 107, 0.7)', //头部右侧字体颜色, + headerRightChildColor: 'rgba(107, 107, 107, 0.7)', //头部右侧下拉字体颜色, + headerRightColorThis: '#565656', //头部右侧鼠标选中, + headerRightNavMore: 'rgba(160, 160, 160, 0.7)', //头部右侧更多下拉颜色, + headerRightNavMoreBg: '#1E9FFF', //头部右侧更多下拉列表选中背景色, + headerRightNavMoreColor: '#ffffff', //头部右侧更多下拉列表字体色, + headerRightToolColor: '#565656', //头部缩放按钮样式, + headerLogoBg: '#192027', //logo背景颜色, + headerLogoColor: 'rgb(191, 187, 187)', //logo字体颜色, + leftMenuNavMore: 'rgb(191, 187, 187)', //左侧菜单更多下拉样式, + leftMenuBg: '#28333E', //左侧菜单背景, + leftMenuBgThis: '#1E9FFF', //左侧菜单选中背景, + leftMenuChildBg: '#0c0f13', //左侧菜单子菜单背景, + leftMenuColor: 'rgb(191, 187, 187)', //左侧菜单字体颜色, + leftMenuColorThis: '#ffffff', //左侧菜单选中字体颜色, + tabActiveColor: '#1e9fff', //tab选项卡选中颜色, + }, + { + headerRightBg: '#23262e', //头部右侧背景色 + headerRightBgThis: '#0c0c0c', //头部右侧选中背景色, + headerRightColor: 'rgba(255,255,255,.7)', //头部右侧字体颜色, + headerRightChildColor: '#676767', //头部右侧下拉字体颜色, + headerRightColorThis: '#ffffff', //头部右侧鼠标选中, + headerRightNavMore: 'rgba(255,255,255,.7)', //头部右侧更多下拉颜色, + headerRightNavMoreBg: '#1aa094', //头部右侧更多下拉列表选中背景色, + headerRightNavMoreColor: '#ffffff', //头部右侧更多下拉列表字体色, + headerRightToolColor: '#bbe3df', //头部缩放按钮样式, + headerLogoBg: '#0c0c0c', //logo背景颜色, + headerLogoColor: '#ffffff', //logo字体颜色, + leftMenuNavMore: 'rgb(191, 187, 187)', //左侧菜单更多下拉样式, + leftMenuBg: '#23262e', //左侧菜单背景, + leftMenuBgThis: '#737373', //左侧菜单选中背景, + leftMenuChildBg: 'rgba(0,0,0,.3)', //左侧菜单子菜单背景, + leftMenuColor: 'rgb(191, 187, 187)', //左侧菜单字体颜色, + leftMenuColorThis: '#ffffff', //左侧菜单选中字体颜色, + tabActiveColor: '#23262e', //tab选项卡选中颜色, + }, + { + headerRightBg: '#ffa4d1', //头部右侧背景色 + headerRightBgThis: '#bf7b9d', //头部右侧选中背景色, + headerRightColor: 'rgba(255,255,255,.7)', //头部右侧字体颜色, + headerRightChildColor: '#676767', //头部右侧下拉字体颜色, + headerRightColorThis: '#ffffff', //头部右侧鼠标选中, + headerRightNavMore: 'rgba(255,255,255,.7)', //头部右侧更多下拉颜色, + headerRightNavMoreBg: '#ffa4d1', //头部右侧更多下拉列表选中背景色, + headerRightNavMoreColor: '#ffffff', //头部右侧更多下拉列表字体色, + headerRightToolColor: '#bbe3df', //头部缩放按钮样式, + headerLogoBg: '#e694bd', //logo背景颜色, + headerLogoColor: '#ffffff', //logo字体颜色, + leftMenuNavMore: 'rgb(191, 187, 187)', //左侧菜单更多下拉样式, + leftMenuBg: '#1f1f1f', //左侧菜单背景, + leftMenuBgThis: '#737373', //左侧菜单选中背景, + leftMenuChildBg: 'rgba(0,0,0,.3)', //左侧菜单子菜单背景, + leftMenuColor: 'rgb(191, 187, 187)', //左侧菜单字体颜色, + leftMenuColorThis: '#ffffff', //左侧菜单选中字体颜色, + tabActiveColor: '#ffa4d1', //tab选项卡选中颜色, + }, + { + headerRightBg: '#1aa094', //头部右侧背景色 + headerRightBgThis: '#197971', //头部右侧选中背景色, + headerRightColor: 'rgba(255,255,255,.7)', //头部右侧字体颜色, + headerRightChildColor: '#676767', //头部右侧下拉字体颜色, + headerRightColorThis: '#ffffff', //头部右侧鼠标选中, + headerRightNavMore: 'rgba(255,255,255,.7)', //头部右侧更多下拉颜色, + headerRightNavMoreBg: '#1aa094', //头部右侧更多下拉列表选中背景色, + headerRightNavMoreColor: '#ffffff', //头部右侧更多下拉列表字体色, + headerRightToolColor: '#bbe3df', //头部缩放按钮样式, + headerLogoBg: '#0c0c0c', //logo背景颜色, + headerLogoColor: '#ffffff', //logo字体颜色, + leftMenuNavMore: 'rgb(191, 187, 187)', //左侧菜单更多下拉样式, + leftMenuBg: '#23262e', //左侧菜单背景, + leftMenuBgThis: '#1aa094', //左侧菜单选中背景, + leftMenuChildBg: 'rgba(0,0,0,.3)', //左侧菜单子菜单背景, + leftMenuColor: 'rgb(191, 187, 187)', //左侧菜单字体颜色, + leftMenuColorThis: '#ffffff', //左侧菜单选中字体颜色, + tabActiveColor: '#1aa094', //tab选项卡选中颜色, + }, + { + headerRightBg: '#1e9fff', //头部右侧背景色 + headerRightBgThis: '#0069b7', //头部右侧选中背景色, + headerRightColor: 'rgba(255,255,255,.7)', //头部右侧字体颜色, + headerRightChildColor: '#676767', //头部右侧下拉字体颜色, + headerRightColorThis: '#ffffff', //头部右侧鼠标选中, + headerRightNavMore: 'rgba(255,255,255,.7)', //头部右侧更多下拉颜色, + headerRightNavMoreBg: '#1e9fff', //头部右侧更多下拉列表选中背景色, + headerRightNavMoreColor: '#ffffff', //头部右侧更多下拉列表字体色, + headerRightToolColor: '#bbe3df', //头部缩放按钮样式, + headerLogoBg: '#0c0c0c', //logo背景颜色, + headerLogoColor: '#ffffff', //logo字体颜色, + leftMenuNavMore: 'rgb(191, 187, 187)', //左侧菜单更多下拉样式, + leftMenuBg: '#1f1f1f', //左侧菜单背景, + leftMenuBgThis: '#1e9fff', //左侧菜单选中背景, + leftMenuChildBg: 'rgba(0,0,0,.3)', //左侧菜单子菜单背景, + leftMenuColor: 'rgb(191, 187, 187)', //左侧菜单字体颜色, + leftMenuColorThis: '#ffffff', //左侧菜单选中字体颜色, + tabActiveColor: '#1e9fff', //tab选项卡选中颜色, + }, + { + headerRightBg: '#ffb800', //头部右侧背景色 + headerRightBgThis: '#d09600', //头部右侧选中背景色, + headerRightColor: 'rgba(255,255,255,.7)', //头部右侧字体颜色, + headerRightChildColor: '#676767', //头部右侧下拉字体颜色, + headerRightColorThis: '#ffffff', //头部右侧鼠标选中, + headerRightNavMore: 'rgba(255,255,255,.7)', //头部右侧更多下拉颜色, + headerRightNavMoreBg: '#d09600', //头部右侧更多下拉列表选中背景色, + headerRightNavMoreColor: '#ffffff', //头部右侧更多下拉列表字体色, + headerRightToolColor: '#bbe3df', //头部缩放按钮样式, + headerLogoBg: '#243346', //logo背景颜色, + headerLogoColor: '#ffffff', //logo字体颜色, + leftMenuNavMore: 'rgb(191, 187, 187)', //左侧菜单更多下拉样式, + leftMenuBg: '#2f4056', //左侧菜单背景, + leftMenuBgThis: '#8593a7', //左侧菜单选中背景, + leftMenuChildBg: 'rgba(0,0,0,.3)', //左侧菜单子菜单背景, + leftMenuColor: 'rgb(191, 187, 187)', //左侧菜单字体颜色, + leftMenuColorThis: '#ffffff', //左侧菜单选中字体颜色, + tabActiveColor: '#ffb800', //tab选项卡选中颜色, + }, + { + headerRightBg: '#e82121', //头部右侧背景色 + headerRightBgThis: '#ae1919', //头部右侧选中背景色, + headerRightColor: 'rgba(255,255,255,.7)', //头部右侧字体颜色, + headerRightChildColor: '#676767', //头部右侧下拉字体颜色, + headerRightColorThis: '#ffffff', //头部右侧鼠标选中, + headerRightNavMore: 'rgba(255,255,255,.7)', //头部右侧更多下拉颜色, + headerRightNavMoreBg: '#ae1919', //头部右侧更多下拉列表选中背景色, + headerRightNavMoreColor: '#ffffff', //头部右侧更多下拉列表字体色, + headerRightToolColor: '#bbe3df', //头部缩放按钮样式, + headerLogoBg: '#0c0c0c', //logo背景颜色, + headerLogoColor: '#ffffff', //logo字体颜色, + leftMenuNavMore: 'rgb(191, 187, 187)', //左侧菜单更多下拉样式, + leftMenuBg: '#1f1f1f', //左侧菜单背景, + leftMenuBgThis: '#3b3f4b', //左侧菜单选中背景, + leftMenuChildBg: 'rgba(0,0,0,.3)', //左侧菜单子菜单背景, + leftMenuColor: 'rgb(191, 187, 187)', //左侧菜单字体颜色, + leftMenuColorThis: '#ffffff', //左侧菜单选中字体颜色, + tabActiveColor: '#e82121', //tab选项卡选中颜色, + }, + { + headerRightBg: '#963885', //头部右侧背景色 + headerRightBgThis: '#772c6a', //头部右侧选中背景色, + headerRightColor: 'rgba(255,255,255,.7)', //头部右侧字体颜色, + headerRightChildColor: '#676767', //头部右侧下拉字体颜色, + headerRightColorThis: '#ffffff', //头部右侧鼠标选中, + headerRightNavMore: 'rgba(255,255,255,.7)', //头部右侧更多下拉颜色, + headerRightNavMoreBg: '#772c6a', //头部右侧更多下拉列表选中背景色, + headerRightNavMoreColor: '#ffffff', //头部右侧更多下拉列表字体色, + headerRightToolColor: '#bbe3df', //头部缩放按钮样式, + headerLogoBg: '#243346', //logo背景颜色, + headerLogoColor: '#ffffff', //logo字体颜色, + leftMenuNavMore: 'rgb(191, 187, 187)', //左侧菜单更多下拉样式, + leftMenuBg: '#2f4056', //左侧菜单背景, + leftMenuBgThis: '#586473', //左侧菜单选中背景, + leftMenuChildBg: 'rgba(0,0,0,.3)', //左侧菜单子菜单背景, + leftMenuColor: 'rgb(191, 187, 187)', //左侧菜单字体颜色, + leftMenuColorThis: '#ffffff', //左侧菜单选中字体颜色, + tabActiveColor: '#963885', //tab选项卡选中颜色, + }, + { + headerRightBg: '#2D8CF0', //头部右侧背景色 + headerRightBgThis: '#0069b7', //头部右侧选中背景色, + headerRightColor: 'rgba(255,255,255,.7)', //头部右侧字体颜色, + headerRightChildColor: '#676767', //头部右侧下拉字体颜色, + headerRightColorThis: '#ffffff', //头部右侧鼠标选中, + headerRightNavMore: 'rgba(255,255,255,.7)', //头部右侧更多下拉颜色, + headerRightNavMoreBg: '#0069b7', //头部右侧更多下拉列表选中背景色, + headerRightNavMoreColor: '#ffffff', //头部右侧更多下拉列表字体色, + headerRightToolColor: '#bbe3df', //头部缩放按钮样式, + headerLogoBg: '#0069b7', //logo背景颜色, + headerLogoColor: '#ffffff', //logo字体颜色, + leftMenuNavMore: 'rgb(191, 187, 187)', //左侧菜单更多下拉样式, + leftMenuBg: '#1f1f1f', //左侧菜单背景, + leftMenuBgThis: '#2D8CF0', //左侧菜单选中背景, + leftMenuChildBg: 'rgba(0,0,0,.3)', //左侧菜单子菜单背景, + leftMenuColor: 'rgb(191, 187, 187)', //左侧菜单字体颜色, + leftMenuColorThis: '#ffffff', //左侧菜单选中字体颜色, + tabActiveColor: '#2d8cf0', //tab选项卡选中颜色, + }, + { + headerRightBg: '#ffb800', //头部右侧背景色 + headerRightBgThis: '#d09600', //头部右侧选中背景色, + headerRightColor: 'rgba(255,255,255,.7)', //头部右侧字体颜色, + headerRightChildColor: '#676767', //头部右侧下拉字体颜色, + headerRightColorThis: '#ffffff', //头部右侧鼠标选中, + headerRightNavMore: 'rgba(255,255,255,.7)', //头部右侧更多下拉颜色, + headerRightNavMoreBg: '#d09600', //头部右侧更多下拉列表选中背景色, + headerRightNavMoreColor: '#ffffff', //头部右侧更多下拉列表字体色, + headerRightToolColor: '#bbe3df', //头部缩放按钮样式, + headerLogoBg: '#d09600', //logo背景颜色, + headerLogoColor: '#ffffff', //logo字体颜色, + leftMenuNavMore: 'rgb(191, 187, 187)', //左侧菜单更多下拉样式, + leftMenuBg: '#2f4056', //左侧菜单背景, + leftMenuBgThis: '#3b3f4b', //左侧菜单选中背景, + leftMenuChildBg: 'rgba(0,0,0,.3)', //左侧菜单子菜单背景, + leftMenuColor: 'rgb(191, 187, 187)', //左侧菜单字体颜色, + leftMenuColorThis: '#ffffff', //左侧菜单选中字体颜色, + tabActiveColor: '#ffb800', //tab选项卡选中颜色, + }, + { + headerRightBg: '#e82121', //头部右侧背景色 + headerRightBgThis: '#ae1919', //头部右侧选中背景色, + headerRightColor: 'rgba(255,255,255,.7)', //头部右侧字体颜色, + headerRightChildColor: '#676767', //头部右侧下拉字体颜色, + headerRightColorThis: '#ffffff', //头部右侧鼠标选中, + headerRightNavMore: 'rgba(255,255,255,.7)', //头部右侧更多下拉颜色, + headerRightNavMoreBg: '#ae1919', //头部右侧更多下拉列表选中背景色, + headerRightNavMoreColor: '#ffffff', //头部右侧更多下拉列表字体色, + headerRightToolColor: '#bbe3df', //头部缩放按钮样式, + headerLogoBg: '#d91f1f', //logo背景颜色, + headerLogoColor: '#ffffff', //logo字体颜色, + leftMenuNavMore: 'rgb(191, 187, 187)', //左侧菜单更多下拉样式, + leftMenuBg: '#1f1f1f', //左侧菜单背景, + leftMenuBgThis: '#3b3f4b', //左侧菜单选中背景, + leftMenuChildBg: 'rgba(0,0,0,.3)', //左侧菜单子菜单背景, + leftMenuColor: 'rgb(191, 187, 187)', //左侧菜单字体颜色, + leftMenuColorThis: '#ffffff', //左侧菜单选中字体颜色, + tabActiveColor: '#e82121', //tab选项卡选中颜色, + }, + { + headerRightBg: '#963885', //头部右侧背景色 + headerRightBgThis: '#772c6a', //头部右侧选中背景色, + headerRightColor: 'rgba(255,255,255,.7)', //头部右侧字体颜色, + headerRightChildColor: '#676767', //头部右侧下拉字体颜色, + headerRightColorThis: '#ffffff', //头部右侧鼠标选中, + headerRightNavMore: 'rgba(255,255,255,.7)', //头部右侧更多下拉颜色, + headerRightNavMoreBg: '#772c6a', //头部右侧更多下拉列表选中背景色, + headerRightNavMoreColor: '#ffffff', //头部右侧更多下拉列表字体色, + headerRightToolColor: '#bbe3df', //头部缩放按钮样式, + headerLogoBg: '#772c6a', //logo背景颜色, + headerLogoColor: '#ffffff', //logo字体颜色, + leftMenuNavMore: 'rgb(191, 187, 187)', //左侧菜单更多下拉样式, + leftMenuBg: '#2f4056', //左侧菜单背景, + leftMenuBgThis: '#626f7f', //左侧菜单选中背景, + leftMenuChildBg: 'rgba(0,0,0,.3)', //左侧菜单子菜单背景, + leftMenuColor: 'rgb(191, 187, 187)', //左侧菜单字体颜色, + leftMenuColorThis: '#ffffff', //左侧菜单选中字体颜色, + tabActiveColor: '#963885', //tab选项卡选中颜色, + } + ]; + if (bgcolorId === undefined) { + return bgColorConfig; + } else { + return bgColorConfig[bgcolorId]; + } + }, + + /** + * 初始化 + * @param options + */ + render: function (options) { + options.bgColorDefault = options.bgColorDefault || false; + options.listen = options.listen || false; + var bgcolorId = sessionStorage.getItem('layuiminiBgcolorId'); + if (bgcolorId === null || bgcolorId === undefined || bgcolorId === '') { + bgcolorId = options.bgColorDefault; + } + miniTheme.buildThemeCss(bgcolorId); + if (options.listen) miniTheme.listen(options); + }, + + /** + * 构建主题样式 + * @param bgcolorId + * @returns {boolean} + */ + buildThemeCss: function (bgcolorId) { + if (!bgcolorId) { + return false; + } + var bgcolorData = miniTheme.config(bgcolorId); + var styleHtml = '/*头部右侧背景色 headerRightBg */\n' + + '.layui-layout-admin .layui-header {\n' + + ' background-color: ' + bgcolorData.headerRightBg + ' !important;\n' + + '}\n' + + '\n' + + '/*头部右侧选中背景色 headerRightBgThis */\n' + + '.layui-layout-admin .layui-header .layuimini-header-content > ul > .layui-nav-item.layui-this, .layuimini-tool i:hover {\n' + + ' background-color: ' + bgcolorData.headerRightBgThis + ' !important;\n' + + '}\n' + + '\n' + + '/*头部右侧字体颜色 headerRightColor */\n' + + '.layui-layout-admin .layui-header .layui-nav .layui-nav-item a {\n' + + ' color: ' + bgcolorData.headerRightColor + ';\n' + + '}\n' + + '/**头部右侧下拉字体颜色 headerRightChildColor */\n' + + '.layui-layout-admin .layui-header .layui-nav .layui-nav-item .layui-nav-child a {\n' + + ' color: ' + bgcolorData.headerRightChildColor + '!important;\n' + + '}\n'+ + '\n' + + '/*头部右侧鼠标选中 headerRightColorThis */\n' + + '.layui-header .layuimini-menu-header-pc.layui-nav .layui-nav-item a:hover, .layui-header .layuimini-header-menu.layuimini-pc-show.layui-nav .layui-this a {\n' + + ' color: ' + bgcolorData.headerRightColorThis + ' !important;\n' + + '}\n' + + '\n' + + '/*头部右侧更多下拉颜色 headerRightNavMore */\n' + + '.layui-header .layui-nav .layui-nav-more {\n' + + ' border-top-color: ' + bgcolorData.headerRightNavMore + ' !important;\n' + + '}\n' + + '\n' + + '/*头部右侧更多下拉颜色 headerRightNavMore */\n' + + '.layui-header .layui-nav .layui-nav-mored, .layui-header .layui-nav-itemed > a .layui-nav-more {\n' + + ' border-color: transparent transparent ' + bgcolorData.headerRightNavMore + ' !important;\n' + + '}\n' + + '\n' + + '/**头部右侧更多下拉配置色 headerRightNavMoreBg headerRightNavMoreColor */\n' + + '.layui-header .layui-nav .layui-nav-child dd.layui-this a, .layui-header .layui-nav-child dd.layui-this, .layui-layout-admin .layui-header .layui-nav .layui-nav-item .layui-nav-child .layui-this a {\n' + + ' background-color: ' + bgcolorData.headerRightNavMoreBg + ' !important;\n' + + ' color:' + bgcolorData.headerRightNavMoreColor + ' !important;\n' + + '}\n' + + '\n' + + '/*头部缩放按钮样式 headerRightToolColor */\n' + + '.layui-layout-admin .layui-header .layuimini-tool i {\n' + + ' color: ' + bgcolorData.headerRightToolColor + ';\n' + + '}\n' + + '\n' + + '/*logo背景颜色 headerLogoBg */\n' + + '.layui-layout-admin .layuimini-logo {\n' + + ' background-color: ' + bgcolorData.headerLogoBg + ' !important;\n' + + '}\n' + + '\n' + + '/*logo字体颜色 headerLogoColor */\n' + + '.layui-layout-admin .layuimini-logo h1 {\n' + + ' color: ' + bgcolorData.headerLogoColor + ';\n' + + '}\n' + + '\n' + + '/*左侧菜单更多下拉样式 leftMenuNavMore */\n' + + '.layuimini-menu-left .layui-nav .layui-nav-more,.layuimini-menu-left-zoom.layui-nav .layui-nav-more {\n' + + ' border-top-color: ' + bgcolorData.leftMenuNavMore + ';\n' + + '}\n' + + '\n' + + '/*左侧菜单更多下拉样式 leftMenuNavMore */\n' + + '.layuimini-menu-left .layui-nav .layui-nav-mored, .layuimini-menu-left .layui-nav-itemed > a .layui-nav-more, .layuimini-menu-left-zoom.layui-nav .layui-nav-mored, .layuimini-menu-left-zoom.layui-nav-itemed > a .layui-nav-more {\n' + + ' border-color: transparent transparent ' + bgcolorData.leftMenuNavMore + ' !important;\n' + + '}\n' + + '\n' + + '/*左侧菜单背景 leftMenuBg */\n' + + '.layui-side.layui-bg-black, .layui-side.layui-bg-black > .layuimini-menu-left > ul, .layuimini-menu-left-zoom > ul {\n' + + ' background-color: ' + bgcolorData.leftMenuBg + ' !important;\n' + + '}\n' + + '\n' + + '/*左侧菜单选中背景 leftMenuBgThis */\n' + + '.layuimini-menu-left .layui-nav-tree .layui-this, .layuimini-menu-left .layui-nav-tree .layui-this > a, .layuimini-menu-left .layui-nav-tree .layui-nav-child dd.layui-this, .layuimini-menu-left .layui-nav-tree .layui-nav-child dd.layui-this a, .layuimini-menu-left-zoom.layui-nav-tree .layui-this, .layuimini-menu-left-zoom.layui-nav-tree .layui-this > a, .layuimini-menu-left-zoom.layui-nav-tree .layui-nav-child dd.layui-this, .layuimini-menu-left-zoom.layui-nav-tree .layui-nav-child dd.layui-this a {\n' + + ' background-color: ' + bgcolorData.leftMenuBgThis + ' !important\n' + + '}\n' + + '\n' + + '/*左侧菜单子菜单背景 leftMenuChildBg */\n' + + '.layuimini-menu-left .layui-nav-itemed > .layui-nav-child{\n' + + ' background-color: ' + bgcolorData.leftMenuChildBg + ' !important;\n' + + '}\n' + + '\n' + + '/*左侧菜单字体颜色 leftMenuColor */\n' + + '.layuimini-menu-left .layui-nav .layui-nav-item a, .layuimini-menu-left-zoom.layui-nav .layui-nav-item a {\n' + + ' color: ' + bgcolorData.leftMenuColor + ' !important;\n' + + '}\n' + + '\n' + + '/*左侧菜单选中字体颜色 leftMenuColorThis */\n' + + '.layuimini-menu-left .layui-nav .layui-nav-item a:hover, .layuimini-menu-left .layui-nav .layui-this a, .layuimini-menu-left-zoom.layui-nav .layui-nav-item a:hover, .layuimini-menu-left-zoom.layui-nav .layui-this a {\n' + + ' color:' + bgcolorData.leftMenuColorThis + ' !important;\n' + + '}\n' + + '\n' + + '/**tab选项卡选中颜色 tabActiveColor */\n' + + '.layuimini-tab .layui-tab-title .layui-this .layuimini-tab-active {\n' + + ' background-color: ' + bgcolorData.tabActiveColor + ';\n' + + '}\n'; + $('#layuimini-bg-color').html(styleHtml); + }, + + /** + * 构建主题选择html + * @param options + * @returns {string} + */ + buildBgColorHtml: function (options) { + options.bgColorDefault = options.bgColorDefault || 0; + var bgcolorId = parseInt(sessionStorage.getItem('layuiminiBgcolorId')); + if (isNaN(bgcolorId)) bgcolorId = options.bgColorDefault; + var bgColorConfig = miniTheme.config(); + var html = ''; + $.each(bgColorConfig, function (key, val) { + if (key === bgcolorId) { + html += '
  • \n'; + } else { + html += '
  • \n'; + } + html += '\n' + + '
    \n' + + '
    \n' + + '
    \n' + + '
  • '; + }); + return html; + }, + + /** + * 监听 + * @param options + */ + listen: function (options) { + $('body').on('click', '[data-bgcolor]', function () { + var loading = layer.load(0, {shade: false, time: 2 * 1000}); + var clientHeight = (document.documentElement.clientHeight) - 60; + var bgColorHtml = miniTheme.buildBgColorHtml(options); + var html = '
    \n' + + '
    \n' + + '配色方案\n' + + '
    \n' + + '
    \n' + + '
      \n' + bgColorHtml + '
    \n' + + '
    \n' + + '
    \n' + + '
    ' + + '
    '; + layer.open({ + type: 1, + title: false, + closeBtn: 0, + shade: 0.2, + anim: 2, + shadeClose: true, + id: 'layuiminiBgColor', + area: ['340px', clientHeight + 'px'], + offset: 'rb', + content: html, + success: function (index, layero) { + }, + end: function () { + $('.layuimini-select-bgcolor').removeClass('layui-this'); + } + }); + layer.close(loading); + }); + + $('body').on('click', '[data-select-bgcolor]', function () { + var bgcolorId = $(this).attr('data-select-bgcolor'); + $('.layuimini-color .color-content ul .layui-this').attr('class', ''); + $(this).attr('class', 'layui-this'); + sessionStorage.setItem('layuiminiBgcolorId', bgcolorId); + miniTheme.render({ + bgColorDefault: bgcolorId, + listen: false, + }); + }); + } + }; + + exports("miniTheme", miniTheme); + +}) +; \ No newline at end of file diff --git a/public/static/layuimini/js/lay-module/layuimini/miniTongji.js b/public/static/layuimini/js/lay-module/layuimini/miniTongji.js new file mode 100644 index 0000000..f0ca101 --- /dev/null +++ b/public/static/layuimini/js/lay-module/layuimini/miniTongji.js @@ -0,0 +1,40 @@ +/** + * date:2020/03/01 + * author:Mr.Chung + * version:2.0 + * description:layuimini 统计框架扩展 + */ +layui.define(["jquery"], function (exports) { + var $ = layui.$; + + var miniTongji = { + + /** + * 初始化 + * @param options + */ + render: function (options) { + options.specific = options.specific || false; + options.domains = options.domains || []; + var domain = window.location.hostname; + if (options.specific === false || (options.specific === true && options.domains.indexOf(domain) >=0)) { + miniTongji.listen(); + } + }, + + /** + * 监听统计代码 + */ + listen: function () { + var _hmt = _hmt || []; + (function () { + var hm = document.createElement("script"); + hm.src = "https://hm.baidu.com/hm.js?d97abf6d61c21d773f97835defbdef4e"; + var s = document.getElementsByTagName("script")[0]; + s.parentNode.insertBefore(hm, s); + })(); + } + }; + + exports("miniTongji", miniTongji); +}); \ No newline at end of file diff --git a/public/static/layuimini/js/lay-module/soulTable/excel.js b/public/static/layuimini/js/lay-module/soulTable/excel.js new file mode 100644 index 0000000..12cd02f --- /dev/null +++ b/public/static/layuimini/js/lay-module/soulTable/excel.js @@ -0,0 +1 @@ +"undefined"==typeof layui&&"undefined"==typeof jQuery&&console.error("非layui调用请先加载jQuery"),"undefined"!=typeof jQuery&&($=jQuery),LAY_EXCEL={downloadExl:function(e,t,r){r=r||"xlsx",this.exportExcel({sheet1:e},t+"."+r,r,null)},exportExcel:function(e,t,r,n){r=r||"xlsx",t=t||"导出数据."+r;var a=XLSX.utils.book_new(),s={Title:t,Subject:"Export From web browser",Author:"excel.wj2015.com",Manager:"",Company:"",Category:"",Keywords:"",Comments:"",LastAuthor:"",CreatedData:new Date};n&&n.Props&&(s=$.extend(s,n.Props)),a.compression=!n||n.compression,!1!==a.compression&&(a.compression=!0),a.Props=s;var i,o,l,c,f,h,u={"!merges":null,"!margins":null,"!cols":null,"!rows":null,"!protect":null,"!autofilter":null};for(i in n&&n.extend&&(u=$.extend(u,n.extend)),u)u.hasOwnProperty(i)&&(u[i]||delete u[i]);for(o in $.isArray(e)&&(e={sheet1:e}),e)e.hasOwnProperty(o)&&(l=e[o],a.SheetNames.push(o),c=!1,l.length&&l[0]&&$.isArray(l[0])&&(c=!0),c?h=XLSX.utils.aoa_to_sheet(l):(c={},l.length&&(c.headers=l.unshift(),c.skipHeader=!0,f=this.splitContent(l)),h=XLSX.utils.json_to_sheet(l,c),u[o]?$.extend(h,u[o]):$.extend(h,u),void 0!==f&&this.mergeCellOpt(h,f.style)),a.Sheets[o]=h);r=XLSX.write(a,{bookType:r,type:"binary",cellStyles:!0,compression:a.compression});saveAs(new Blob([this.s2ab(r)],{type:"application/octet-stream"}),t)},splitContent:function(e){for(var t={},r=0;r tr").each(function(){var e=[];$(this).find("td,th").each(function(){e.push($(this).text())}),t.push(e)});var r=[];return e.find("tbody > tr").each(function(){var e=[];$(this).find("td").each(function(){e.push($(this).text())}),r.push(e)}),{head:t,body:r}},numsTitleCache:{},titleNumsCache:{},numToTitle:function(e){if(this.numsTitleCache[e])return this.numsTitleCache[e];var t="";if(26i.c&&console.error("开始列不得大于结束列"),s.r>i.r&&console.error("开始行不得大于结束行");for(var o=s.r;o<=i.r;o++)for(var l=s.c;l<=i.c;l++){var c=e[o];if(!c){c={};for(var f=0;f>6,128|63&n):n<55296||57344<=n?t.push(224|n>>12,128|n>>6&63,128|63&n):(r++,n=65536+((1023&n)<<10|1023&e.charCodeAt(r)),t.push(240|n>>18,128|n>>12&63,128|n>>6&63,128|63&n))}return t}function t(e){for(var t,r,n,a="",s=e.length,i=0;i>4){case 0:case 1:case 2:case 3:case 4:case 5:case 6:case 7:a+=String.fromCharCode(t);break;case 12:case 13:r=e[i++],a+=String.fromCharCode((31&t)<<6|63&r);break;case 14:r=e[i++],n=e[i++],a+=String.fromCharCode((15&t)<<12|(63&r)<<6|(63&n)<<0)}return a}function o(e){for(var t=new Array(e.byteLength),r=new Uint8Array(e),n=t.length;n--;)t[n]=r[n];return t}function r(e){for(var t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",r=[],n=0;n>2,a=(3&a)<<4|i>>4,i=(15&i)<<2|l>>6,l=63&l;o||(l=64,s||(i=64)),r.push(t[c],t[a],t[i],t[l])}return r.join("")}var n,l,e=Object.create||function(e){function t(){}return t.prototype=e,new t};function c(e,t){for(var r,n=0,a=(e=e||[]).length;n>2,s=(3&o)<<4|(r=e.charCodeAt(c++))>>4,i=(15&r)<<2|(n=e.charCodeAt(c++))>>6,o=63&n,isNaN(r)?i=o=64:isNaN(n)&&(o=64),l=l+f.charAt(a)+f.charAt(s)+f.charAt(i)+f.charAt(o);return l},r.decode=function(e,t){var r,n,a,s,i,o="",l=0;for(e=e.replace(/[^A-Za-z0-9\+\/\=]/g,"");l>4,n=(15&a)<<4|(s=f.indexOf(e.charAt(l++)))>>2,a=(3&s)<<6|(i=f.indexOf(e.charAt(l++))),o+=String.fromCharCode(r),64!=s&&(o+=String.fromCharCode(n)),64!=i&&(o+=String.fromCharCode(a));return o}},{}],2:[function(e,t,r){"use strict";function n(){this.compressedSize=0,this.uncompressedSize=0,this.crc32=0,this.compressionMethod=null,this.compressedContent=null}n.prototype={getContent:function(){return null},getCompressedContent:function(){return null}},t.exports=n},{}],3:[function(e,t,r){"use strict";r.STORE={magic:"\0\0",compress:function(e){return e},uncompress:function(e){return e},compressInputType:null,uncompressInputType:null},r.DEFLATE=e("./flate")},{"./flate":8}],4:[function(e,t,r){"use strict";var i=e("./utils"),o=[0,1996959894,3993919788,2567524794,124634137,1886057615,3915621685,2657392035,249268274,2044508324,3772115230,2547177864,162941995,2125561021,3887607047,2428444049,498536548,1789927666,4089016648,2227061214,450548861,1843258603,4107580753,2211677639,325883990,1684777152,4251122042,2321926636,335633487,1661365465,4195302755,2366115317,997073096,1281953886,3579855332,2724688242,1006888145,1258607687,3524101629,2768942443,901097722,1119000684,3686517206,2898065728,853044451,1172266101,3705015759,2882616665,651767980,1373503546,3369554304,3218104598,565507253,1454621731,3485111705,3099436303,671266974,1594198024,3322730930,2970347812,795835527,1483230225,3244367275,3060149565,1994146192,31158534,2563907772,4023717930,1907459465,112637215,2680153253,3904427059,2013776290,251722036,2517215374,3775830040,2137656763,141376813,2439277719,3865271297,1802195444,476864866,2238001368,4066508878,1812370925,453092731,2181625025,4111451223,1706088902,314042704,2344532202,4240017532,1658658271,366619977,2362670323,4224994405,1303535960,984961486,2747007092,3569037538,1256170817,1037604311,2765210733,3554079995,1131014506,879679996,2909243462,3663771856,1141124467,855842277,2852801631,3708648649,1342533948,654459306,3188396048,3373015174,1466479909,544179635,3110523913,3462522015,1591671054,702138776,2966460450,3352799412,1504918807,783551873,3082640443,3233442989,3988292384,2596254646,62317068,1957810842,3939845945,2647816111,81470997,1943803523,3814918930,2489596804,225274430,2053790376,3826175755,2466906013,167816743,2097651377,4027552580,2265490386,503444072,1762050814,4150417245,2154129355,426522225,1852507879,4275313526,2312317920,282753626,1742555852,4189708143,2394877945,397917763,1622183637,3604390888,2714866558,953729732,1340076626,3518719985,2797360999,1068828381,1219638859,3624741850,2936675148,906185462,1090812512,3747672003,2825379669,829329135,1181335161,3412177804,3160834842,628085408,1382605366,3423369109,3138078467,570562233,1426400815,3317316542,2998733608,733239954,1555261956,3268935591,3050360625,752459403,1541320221,2607071920,3965973030,1969922972,40735498,2617837225,3943577151,1913087877,83908371,2512341634,3803740692,2075208622,213261112,2463272603,3855990285,2094854071,198958881,2262029012,4057260610,1759359992,534414190,2176718541,4139329115,1873836001,414664567,2282248934,4279200368,1711684554,285281116,2405801727,4167216745,1634467795,376229701,2685067896,3608007406,1308918612,956543938,2808555105,3495958263,1231636301,1047427035,2932959818,3654703836,1088359270,936918e3,2847714899,3736837829,1202900863,817233897,3183342108,3401237130,1404277552,615818150,3134207493,3453421203,1423857449,601450431,3009837614,3294710456,1567103746,711928724,3020668471,3272380065,1510334235,755167117];t.exports=function(e,t){if(void 0===e||!e.length)return 0;var r="string"!==i.getTypeOf(e);void 0===t&&(t=0);var n;t^=-1;for(var a=0,s=e.length;a>>8^o[255&(t^n)];return-1^t}},{"./utils":21}],5:[function(e,t,r){"use strict";var n=e("./utils");function a(e){this.data=null,this.length=0,this.index=0}a.prototype={checkOffset:function(e){this.checkIndex(this.index+e)},checkIndex:function(e){if(this.length=this.index;t--)r=(r<<8)+this.byteAt(t);return this.index+=e,r},readString:function(e){return n.transformTo("string",this.readData(e))},readData:function(e){},lastIndexOfSignature:function(e){},readDate:function(){var e=this.readInt(4);return new Date(1980+(e>>25&127),(e>>21&15)-1,e>>16&31,e>>11&31,e>>5&63,(31&e)<<1)}},t.exports=a},{"./utils":21}],6:[function(e,t,r){"use strict";r.base64=!1,r.binary=!1,r.dir=!1,r.createFolders=!1,r.date=null,r.compression=null,r.comment=null},{}],7:[function(e,t,r){"use strict";var n=e("./utils");r.string2binary=function(e){return n.string2binary(e)},r.string2Uint8Array=function(e){return n.transformTo("uint8array",e)},r.uint8Array2String=function(e){return n.transformTo("string",e)},r.string2Blob=function(e){e=n.transformTo("arraybuffer",e);return n.arrayBuffer2Blob(e)},r.arrayBuffer2Blob=function(e){return n.arrayBuffer2Blob(e)},r.transformTo=function(e,t){return n.transformTo(e,t)},r.getTypeOf=function(e){return n.getTypeOf(e)},r.checkSupport=function(e){return n.checkSupport(e)},r.MAX_VALUE_16BITS=n.MAX_VALUE_16BITS,r.MAX_VALUE_32BITS=n.MAX_VALUE_32BITS,r.pretty=function(e){return n.pretty(e)},r.findCompression=function(e){return n.findCompression(e)},r.isRegExp=function(e){return n.isRegExp(e)}},{"./utils":21}],8:[function(e,t,r){"use strict";var n="undefined"!=typeof Uint8Array&&"undefined"!=typeof Uint16Array&&"undefined"!=typeof Uint32Array,a=e("pako");r.uncompressInputType=n?"uint8array":"array",r.compressInputType=n?"uint8array":"array",r.magic="\b\0",r.compress=function(e){return a.deflateRaw(e)},r.uncompress=function(e){return a.inflateRaw(e)}},{pako:24}],9:[function(e,t,r){"use strict";var n=e("./base64");function a(e,t){if(!(this instanceof a))return new a(e,t);this.files={},this.comment=null,this.root="",e&&this.load(e,t),this.clone=function(){var e,t=new a;for(e in this)"function"!=typeof this[e]&&(t[e]=this[e]);return t}}(a.prototype=e("./object")).load=e("./load"),a.support=e("./support"),a.defaults=e("./defaults"),a.utils=e("./deprecatedPublicUtils"),a.base64={encode:function(e){return n.encode(e)},decode:function(e){return n.decode(e)}},a.compressions=e("./compressions"),t.exports=a},{"./base64":1,"./compressions":3,"./defaults":6,"./deprecatedPublicUtils":7,"./load":10,"./object":13,"./support":17}],10:[function(e,t,r){"use strict";var s=e("./base64"),i=e("./zipEntries");t.exports=function(e,t){var r,n,a;for((t=t||{}).base64&&(e=s.decode(e)),r=(e=new i(e,t)).files,n=0;n>>=8;return r}function _(){for(var e,t={},r=0;r>10&1023,a[s++]=56320|1023&t)}return a.length!==s&&(a.subarray?a=a.subarray(0,s):a.length=s),o.applyFromCharCode(a)}r.utf8encode=function(e){return l.nodebuffer?n(e,"utf-8"):function(e){for(var t,r,n,a,s=e.length,i=0,o=0;o>>6:(r<65536?t[a++]=224|r>>>12:(t[a++]=240|r>>>18,t[a++]=128|r>>>12&63),t[a++]=128|r>>>6&63),t[a++]=128|63&r);return t}(e)},r.utf8decode=function(e){if(l.nodebuffer)return o.transformTo("nodebuffer",e).toString("utf-8");for(var t=[],r=0,n=(e=o.transformTo(l.uint8array?"uint8array":"array",e)).length;re.length&&(t=e.length),r=t-1;0<=r&&128==(192&e[r]);)r--;return!(r<0)&&0!==r&&r+c[e[r]]>t?r:t}(e,Math.min(r+65536,n));l.uint8array?t.push(s(e.subarray(r,a))):t.push(s(e.slice(r,a))),r=a}return t.join("")}},{"./nodeBuffer":11,"./support":17,"./utils":21}],21:[function(e,t,c){"use strict";var r=e("./support"),n=e("./compressions"),f=e("./nodeBuffer");function a(e){return e}function s(e,t){for(var r=0;r>>6:(r<65536?t[a++]=224|r>>>12:(t[a++]=240|r>>>18,t[a++]=128|r>>>12&63),t[a++]=128|r>>>6&63),t[a++]=128|63&r);return t},r.buf2binstring=function(e){return f(e,e.length)},r.binstring2buf=function(e){for(var t=new l.Buf8(e.length),r=0,n=t.length;r>10&1023,s[i++]=56320|1023&r)}return f(s,i)},r.utf8border=function(e,t){var r;for((t=t||e.length)>e.length&&(t=e.length),r=t-1;0<=r&&128==(192&e[r]);)r--;return!(r<0)&&0!==r&&r+c[e[r]]>t?r:t}},{"./common":27}],29:[function(e,t,r){"use strict";t.exports=function(e,t,r,n){for(var a=65535&e|0,s=e>>>16&65535|0,i=0;0!==r;){for(i=2e3>>1:e>>>1;t[r]=e}return t}();t.exports=function(e,t,r,n){var a=o,s=n+r;e^=-1;for(var i=n;i>>8^a[255&(e^t[i])];return-1^e}},{}],32:[function(e,t,r){"use strict";var h=e("../utils/common"),o=e("./trees"),u=e("./adler32"),d=e("./crc32"),n=e("./messages"),l=0,c=4,f=0,p=-2,m=-1,g=4,a=2,b=8,v=9,s=286,i=30,E=19,w=2*s+1,S=15,_=3,y=258,C=y+_+1,B=42,T=113,k=1,x=2,A=3,I=4;function R(e,t){return e.msg=n[t],t}function O(e){return(e<<1)-(4e.avail_out&&(r=e.avail_out),0!==r&&(h.arraySet(e.output,t.pending_buf,t.pending_out,r,e.next_out),e.next_out+=r,t.pending_out+=r,e.total_out+=r,e.avail_out-=r,t.pending-=r,0===t.pending&&(t.pending_out=0))}function P(e,t){o._tr_flush_block(e,0<=e.block_start?e.block_start:-1,e.strstart-e.block_start,t),e.block_start=e.strstart,D(e.strm)}function N(e,t){e.pending_buf[e.pending++]=t}function L(e,t){e.pending_buf[e.pending++]=t>>>8&255,e.pending_buf[e.pending++]=255&t}function M(e,t){var r,n,a=e.max_chain_length,s=e.strstart,i=e.prev_length,o=e.nice_match,l=e.strstart>e.w_size-C?e.strstart-(e.w_size-C):0,c=e.window,f=e.w_mask,h=e.prev,u=e.strstart+y,d=c[s+i-1],p=c[s+i];e.prev_length>=e.good_match&&(a>>=2),o>e.lookahead&&(o=e.lookahead);do{if(c[(r=t)+i]===p&&c[r+i-1]===d&&c[r]===c[s]&&c[++r]===c[s+1]){for(s+=2,r++;c[++s]===c[++r]&&c[++s]===c[++r]&&c[++s]===c[++r]&&c[++s]===c[++r]&&c[++s]===c[++r]&&c[++s]===c[++r]&&c[++s]===c[++r]&&c[++s]===c[++r]&&sl&&0!=--a);return i<=e.lookahead?i:e.lookahead}function U(e){var t,r,n,a,s,i,o,l,c,f=e.w_size;do{if(c=e.window_size-e.lookahead-e.strstart,e.strstart>=f+(f-C)){for(h.arraySet(e.window,e.window,f,f,0),e.match_start-=f,e.strstart-=f,e.block_start-=f,r=e.hash_size,t=r;n=e.head[--t],e.head[t]=f<=n?n-f:0,--r;);for(r=f,t=r;n=e.prev[--t],e.prev[t]=f<=n?n-f:0,--r;);c+=f}if(0===e.strm.avail_in)break;if(s=e.strm,i=e.window,o=e.strstart+e.lookahead,l=c,c=void 0,c=s.avail_in,l=_)for(a=e.strstart-e.insert,e.ins_h=e.window[a],e.ins_h=(e.ins_h<=_&&(e.ins_h=(e.ins_h<=_)if(n=o._tr_tally(e,e.strstart-e.match_start,e.match_length-_),e.lookahead-=e.match_length,e.match_length<=e.max_lazy_match&&e.lookahead>=_){for(e.match_length--;e.strstart++,e.ins_h=(e.ins_h<=_&&(e.ins_h=(e.ins_h<=_&&e.match_length<=e.prev_length){for(a=e.strstart+e.lookahead-_,n=o._tr_tally(e,e.strstart-1-e.prev_match,e.prev_length-_),e.lookahead-=e.prev_length-1,e.prev_length-=2;++e.strstart<=a&&(e.ins_h=(e.ins_h<>1,o.l_buf=3*o.lit_bufsize,o.level=t,o.strategy=s,o.method=r,j(e)}V=[new e(0,0,0,0,function(e,t){var r=65535;for(r>e.pending_buf_size-5&&(r=e.pending_buf_size-5);;){if(e.lookahead<=1){if(U(e),0===e.lookahead&&t===l)return k;if(0===e.lookahead)break}e.strstart+=e.lookahead,e.lookahead=0;var n=e.block_start+r;if((0===e.strstart||e.strstart>=n)&&(e.lookahead=e.strstart-n,e.strstart=n,P(e,!1),0===e.strm.avail_out))return k;if(e.strstart-e.block_start>=e.w_size-C&&(P(e,!1),0===e.strm.avail_out))return k}return e.insert=0,t===c?(P(e,!0),0===e.strm.avail_out?A:I):(e.strstart>e.block_start&&(P(e,!1),e.strm.avail_out),k)}),new e(4,4,8,4,H),new e(4,5,16,8,H),new e(4,6,32,32,H),new e(4,4,16,16,z),new e(8,16,32,32,z),new e(8,16,128,128,z),new e(8,32,128,256,z),new e(32,128,258,1024,z),new e(32,258,258,4096,z)],r.deflateInit=function(e,t){return G(e,t,b,15,8,0)},r.deflateInit2=G,r.deflateReset=j,r.deflateResetKeep=X,r.deflateSetHeader=function(e,t){return!e||!e.state||2!==e.state.wrap?p:(e.state.gzhead=t,f)},r.deflate=function(e,t){var r,n,a,s;if(!e||!e.state||5>8&255),N(r,r.gzhead.time>>16&255),N(r,r.gzhead.time>>24&255),N(r,9===r.level?2:2<=r.strategy||r.level<2?4:0),N(r,255&r.gzhead.os),r.gzhead.extra&&r.gzhead.extra.length&&(N(r,255&r.gzhead.extra.length),N(r,r.gzhead.extra.length>>8&255)),r.gzhead.hcrc&&(e.adler=d(e.adler,r.pending_buf,r.pending,0)),r.gzindex=0,r.status=69):(N(r,0),N(r,0),N(r,0),N(r,0),N(r,0),N(r,9===r.level?2:2<=r.strategy||r.level<2?4:0),N(r,3),r.status=T)):(s=b+(r.w_bits-8<<4)<<8,s|=(2<=r.strategy||r.level<2?0:r.level<6?1:6===r.level?2:3)<<6,0!==r.strstart&&(s|=32),s+=31-s%31,r.status=T,L(r,s),0!==r.strstart&&(L(r,e.adler>>>16),L(r,65535&e.adler)),e.adler=1)),69===r.status)if(r.gzhead.extra){for(n=r.pending;r.gzindex<(65535&r.gzhead.extra.length)&&(r.pending!==r.pending_buf_size||(r.gzhead.hcrc&&r.pending>n&&(e.adler=d(e.adler,r.pending_buf,r.pending-n,n)),D(e),n=r.pending,r.pending!==r.pending_buf_size));)N(r,255&r.gzhead.extra[r.gzindex]),r.gzindex++;r.gzhead.hcrc&&r.pending>n&&(e.adler=d(e.adler,r.pending_buf,r.pending-n,n)),r.gzindex===r.gzhead.extra.length&&(r.gzindex=0,r.status=73)}else r.status=73;if(73===r.status)if(r.gzhead.name){n=r.pending;do{if(r.pending===r.pending_buf_size&&(r.gzhead.hcrc&&r.pending>n&&(e.adler=d(e.adler,r.pending_buf,r.pending-n,n)),D(e),n=r.pending,r.pending===r.pending_buf_size)){a=1;break}}while(a=r.gzindexn&&(e.adler=d(e.adler,r.pending_buf,r.pending-n,n)),0===a&&(r.gzindex=0,r.status=91)}else r.status=91;if(91===r.status)if(r.gzhead.comment){n=r.pending;do{if(r.pending===r.pending_buf_size&&(r.gzhead.hcrc&&r.pending>n&&(e.adler=d(e.adler,r.pending_buf,r.pending-n,n)),D(e),n=r.pending,r.pending===r.pending_buf_size)){a=1;break}}while(a=r.gzindexn&&(e.adler=d(e.adler,r.pending_buf,r.pending-n,n)),0===a&&(r.status=103)}else r.status=103;if(103===r.status&&(r.gzhead.hcrc?(r.pending+2>r.pending_buf_size&&D(e),r.pending+2<=r.pending_buf_size&&(N(r,255&e.adler),N(r,e.adler>>8&255),e.adler=0,r.status=T)):r.status=T),0!==r.pending){if(D(e),0===e.avail_out)return r.last_flush=-1,f}else if(0===e.avail_in&&O(t)<=O(i)&&t!==c)return R(e,-5);if(666===r.status&&0!==e.avail_in)return R(e,-5);if(0!==e.avail_in||0!==r.lookahead||t!==l&&666!==r.status){var i=2===r.strategy?function(e,t){for(var r;;){if(0===e.lookahead&&(U(e),0===e.lookahead)){if(t===l)return k;break}if(e.match_length=0,r=o._tr_tally(e,0,e.window[e.strstart]),e.lookahead--,e.strstart++,r&&(P(e,!1),0===e.strm.avail_out))return k}return e.insert=0,t===c?(P(e,!0),0===e.strm.avail_out?A:I):e.last_lit&&(P(e,!1),0===e.strm.avail_out)?k:x}(r,t):3===r.strategy?function(e,t){for(var r,n,a,s,i=e.window;;){if(e.lookahead<=y){if(U(e),e.lookahead<=y&&t===l)return k;if(0===e.lookahead)break}if(e.match_length=0,e.lookahead>=_&&0e.lookahead&&(e.match_length=e.lookahead)}if(e.match_length>=_?(r=o._tr_tally(e,1,e.match_length-_),e.lookahead-=e.match_length,e.strstart+=e.match_length,e.match_length=0):(r=o._tr_tally(e,0,e.window[e.strstart]),e.lookahead--,e.strstart++),r&&(P(e,!1),0===e.strm.avail_out))return k}return e.insert=0,t===c?(P(e,!0),0===e.strm.avail_out?A:I):e.last_lit&&(P(e,!1),0===e.strm.avail_out)?k:x}(r,t):V[r.level].func(r,t);if(i!==A&&i!==I||(r.status=666),i===k||i===A)return 0===e.avail_out&&(r.last_flush=-1),f;if(i===x&&(1===t?o._tr_align(r):5!==t&&(o._tr_stored_block(r,0,0,!1),3===t&&(F(r.head),0===r.lookahead&&(r.strstart=0,r.block_start=0,r.insert=0))),D(e),0===e.avail_out))return r.last_flush=-1,f}return t!==c?f:r.wrap<=0?1:(2===r.wrap?(N(r,255&e.adler),N(r,e.adler>>8&255),N(r,e.adler>>16&255),N(r,e.adler>>24&255),N(r,255&e.total_in),N(r,e.total_in>>8&255),N(r,e.total_in>>16&255),N(r,e.total_in>>24&255)):(L(r,e.adler>>>16),L(r,65535&e.adler)),D(e),0>>=n=r>>>24,_-=n,0===(n=r>>>16&255))d[u++]=65535&r;else{if(!(16&n)){if(0==(64&n)){r=y[(65535&r)+(S&(1<>>=n,_-=n),_<15&&(S+=f[c++]<<_,_+=8,S+=f[c++]<<_,_+=8),r=C[S&T];r:for(;;){if(S>>>=n=r>>>24,_-=n,!(16&(n=r>>>16&255))){if(0==(64&n)){r=C[(65535&r)+(S&(1<>>=n,_-=n,(n=u-p)>3,S&=(1<<(_-=a<<3))-1,e.next_in=c,e.next_out=u,e.avail_in=c>>24&255)+(e>>>8&65280)+((65280&e)<<8)+((255&e)<<24)}function s(){this.mode=0,this.last=!1,this.wrap=0,this.havedict=!1,this.flags=0,this.dmax=0,this.check=0,this.total=0,this.head=null,this.wbits=0,this.wsize=0,this.whave=0,this.wnext=0,this.window=null,this.hold=0,this.bits=0,this.length=0,this.offset=0,this.extra=0,this.lencode=null,this.distcode=null,this.lenbits=0,this.distbits=0,this.ncode=0,this.nlen=0,this.ndist=0,this.have=0,this.next=null,this.lens=new D.Buf16(320),this.work=new D.Buf16(288),this.lendyn=null,this.distdyn=null,this.sane=0,this.back=0,this.was=0}function i(e){var t;return e&&e.state?(t=e.state,e.total_in=e.total_out=t.total=0,e.msg="",t.wrap&&(e.adler=1&t.wrap),t.mode=W,t.last=0,t.havedict=0,t.dmax=32768,t.head=null,t.hold=0,t.bits=0,t.lencode=t.lendyn=new D.Buf32(n),t.distcode=t.distdyn=new D.Buf32(a),t.sane=1,t.back=-1,z):V}function o(e){var t;return e&&e.state?((t=e.state).wsize=0,t.whave=0,t.wnext=0,i(e)):V}function l(e,t){var r,n;return e&&e.state?(n=e.state,t<0?(r=0,t=-t):(r=1+(t>>4),t<48&&(t&=15)),t&&(t<8||15>>8&255,r.check=N(r.check,O,2,0),f=c=0,r.mode=2;break}if(r.flags=0,r.head&&(r.head.done=!1),!(1&r.wrap)||(((255&c)<<8)+(c>>8))%31){e.msg="incorrect header check",r.mode=30;break}if(8!=(15&c)){e.msg="unknown compression method",r.mode=30;break}if(f-=4,_=8+(15&(c>>>=4)),0===r.wbits)r.wbits=_;else if(_>r.wbits){e.msg="invalid window size",r.mode=30;break}r.dmax=1<<_,e.adler=r.check=1,r.mode=512&c?10:12,f=c=0;break;case 2:for(;f<16;){if(0===o)break e;o--,c+=n[s++]<>8&1),512&r.flags&&(O[0]=255&c,O[1]=c>>>8&255,r.check=N(r.check,O,2,0)),f=c=0,r.mode=3;case 3:for(;f<32;){if(0===o)break e;o--,c+=n[s++]<>>8&255,O[2]=c>>>16&255,O[3]=c>>>24&255,r.check=N(r.check,O,4,0)),f=c=0,r.mode=4;case 4:for(;f<16;){if(0===o)break e;o--,c+=n[s++]<>8),512&r.flags&&(O[0]=255&c,O[1]=c>>>8&255,r.check=N(r.check,O,2,0)),f=c=0,r.mode=5;case 5:if(1024&r.flags){for(;f<16;){if(0===o)break e;o--,c+=n[s++]<>>8&255,r.check=N(r.check,O,2,0)),f=c=0}else r.head&&(r.head.extra=null);r.mode=6;case 6:if(1024&r.flags&&(o<(d=r.length)&&(d=o),d&&(r.head&&(_=r.head.extra_len-r.length,r.head.extra||(r.head.extra=new Array(r.head.extra_len)),D.arraySet(r.head.extra,n,s,d,_)),512&r.flags&&(r.check=N(r.check,n,d,s)),o-=d,s+=d,r.length-=d),r.length))break e;r.length=0,r.mode=7;case 7:if(2048&r.flags){if(0===o)break e;for(d=0;_=n[s+d++],r.head&&_&&r.length<65536&&(r.head.name+=String.fromCharCode(_)),_&&d>9&1,r.head.done=!0),e.adler=r.check=0,r.mode=12;break;case 10:for(;f<32;){if(0===o)break e;o--,c+=n[s++]<>>=7&f,f-=7&f,r.mode=27;break}for(;f<3;){if(0===o)break e;o--,c+=n[s++]<>>=1)){case 0:r.mode=14;break;case 1:if(!function(e){if($){var t;for(j=new D.Buf32(512),G=new D.Buf32(32),t=0;t<144;)e.lens[t++]=8;for(;t<256;)e.lens[t++]=9;for(;t<280;)e.lens[t++]=7;for(;t<288;)e.lens[t++]=8;for(M(U,e.lens,0,288,j,0,e.work,{bits:9}),t=0;t<32;)e.lens[t++]=5;M(H,e.lens,0,32,G,0,e.work,{bits:5}),$=!1}e.lencode=j,e.lenbits=9,e.distcode=G,e.distbits=5}(r),r.mode=20,6!==t)break;c>>>=2,f-=2;break e;case 2:r.mode=17;break;case 3:e.msg="invalid block type",r.mode=30}c>>>=2,f-=2;break;case 14:for(c>>>=7&f,f-=7&f;f<32;){if(0===o)break e;o--,c+=n[s++]<>>16^65535)){e.msg="invalid stored block lengths",r.mode=30;break}if(r.length=65535&c,f=c=0,r.mode=15,6===t)break e;case 15:r.mode=16;case 16:if(d=r.length){if(o>>=5,f-=5,r.ndist=1+(31&c),c>>>=5,f-=5,r.ncode=4+(15&c),c>>>=4,f-=4,286>>=3,f-=3}for(;r.have<19;)r.lens[F[r.have++]]=0;if(r.lencode=r.lendyn,r.lenbits=7,C={bits:r.lenbits},y=M(0,r.lens,0,19,r.lencode,0,r.work,C),r.lenbits=C.bits,y){e.msg="invalid code lengths set",r.mode=30;break}r.have=0,r.mode=19;case 19:for(;r.have>>16&255,v=65535&R,!((g=R>>>24)<=f);){if(0===o)break e;o--,c+=n[s++]<>>=g,f-=g,r.lens[r.have++]=v;else{if(16===v){for(B=g+2;f>>=g,f-=g,0===r.have){e.msg="invalid bit length repeat",r.mode=30;break}_=r.lens[r.have-1],d=3+(3&c),c>>>=2,f-=2}else if(17===v){for(B=g+3;f>>=g)),c>>>=3,f-=3}else{for(B=g+7;f>>=g)),c>>>=7,f-=7}if(r.have+d>r.nlen+r.ndist){e.msg="invalid bit length repeat",r.mode=30;break}for(;d--;)r.lens[r.have++]=_}}if(30===r.mode)break;if(0===r.lens[256]){e.msg="invalid code -- missing end-of-block",r.mode=30;break}if(r.lenbits=9,C={bits:r.lenbits},y=M(U,r.lens,0,r.nlen,r.lencode,0,r.work,C),r.lenbits=C.bits,y){e.msg="invalid literal/lengths set",r.mode=30;break}if(r.distbits=6,r.distcode=r.distdyn,C={bits:r.distbits},y=M(H,r.lens,r.nlen,r.ndist,r.distcode,0,r.work,C),r.distbits=C.bits,y){e.msg="invalid distances set",r.mode=30;break}if(r.mode=20,6===t)break e;case 20:r.mode=21;case 21:if(6<=o&&258<=l){e.next_out=i,e.avail_out=l,e.next_in=s,e.avail_in=o,r.hold=c,r.bits=f,L(e,u),i=e.next_out,a=e.output,l=e.avail_out,s=e.next_in,n=e.input,o=e.avail_in,c=r.hold,f=r.bits,12===r.mode&&(r.back=-1);break}for(r.back=0;b=(R=r.lencode[c&(1<>>16&255,v=65535&R,!((g=R>>>24)<=f);){if(0===o)break e;o--,c+=n[s++]<>E)])>>>16&255,v=65535&R,!(E+(g=R>>>24)<=f);){if(0===o)break e;o--,c+=n[s++]<>>=E,f-=E,r.back+=E}if(c>>>=g,f-=g,r.back+=g,r.length=v,0===b){r.mode=26;break}if(32&b){r.back=-1,r.mode=12;break}if(64&b){e.msg="invalid literal/length code",r.mode=30;break}r.extra=15&b,r.mode=22;case 22:if(r.extra){for(B=r.extra;f>>=r.extra,f-=r.extra,r.back+=r.extra}r.was=r.length,r.mode=23;case 23:for(;b=(R=r.distcode[c&(1<>>16&255,v=65535&R,!((g=R>>>24)<=f);){if(0===o)break e;o--,c+=n[s++]<>E)])>>>16&255,v=65535&R,!(E+(g=R>>>24)<=f);){if(0===o)break e;o--,c+=n[s++]<>>=E,f-=E,r.back+=E}if(c>>>=g,f-=g,r.back+=g,64&b){e.msg="invalid distance code",r.mode=30;break}r.offset=v,r.extra=15&b,r.mode=24;case 24:if(r.extra){for(B=r.extra;f>>=r.extra,f-=r.extra,r.back+=r.extra}if(r.offset>r.dmax){e.msg="invalid distance too far back",r.mode=30;break}r.mode=25;case 25:if(0===l)break e;if(d=u-l,r.offset>d){if(d=r.offset-d,d>r.whave&&r.sane){e.msg="invalid distance too far back",r.mode=30;break}p=d>r.wnext?(d-=r.wnext,r.wsize-d):r.wnext-d,d>r.length&&(d=r.length),m=r.window}else m=a,p=i-r.offset,d=r.length;for(l=I.wsize?(D.arraySet(I.window,k,x-I.wsize,I.wsize,0),I.wnext=0,I.whave=I.wsize):(A<(T=I.wsize-I.wnext)&&(T=A),D.arraySet(I.window,k,x-A,T,I.wnext),(A-=T)?(D.arraySet(I.window,k,x-A,A,0),I.wnext=A,I.whave=I.wsize):(I.wnext+=T,I.wnext===I.wsize&&(I.wnext=0),I.whaved?(m=O[F+i[E]],x[A+i[E]]):(m=96,0),l=1<>C)+(c-=l)]=p<<24|m<<16|g|0,0!==c;);for(l=1<>=1;if(0!==l?(k&=l-1,k+=l):k=0,E++,0==--I[v]){if(v===S)break;v=t[r+i[E]]}if(_>>7)]}function L(e,t){e.pending_buf[e.pending++]=255&t,e.pending_buf[e.pending++]=t>>>8&255}function M(e,t,r){e.bi_valid>d-r?(e.bi_buf|=t<>d-e.bi_valid,e.bi_valid+=r-d):(e.bi_buf|=t<>>=1,r<<=1,0<--t;);return r>>>1}function z(e,t,r){for(var n,a=new Array(b+1),s=0,i=1;i<=b;i++)a[i]=s=s+r[i-1]<<1;for(n=0;n<=t;n++){var o=e[2*n+1];0!==o&&(e[2*n]=H(a[o]++,o))}}function V(e){for(var t=0;t>1;1<=t;t--)j(e,a,t);for(n=o;t=e.heap[1],e.heap[1]=e.heap[e.heap_len--],j(e,a,1),r=e.heap[1],e.heap[--e.heap_max]=t,e.heap[--e.heap_max]=r,a[2*n]=a[2*t]+a[2*r],e.depth[n]=(e.depth[t]>=e.depth[r]?e.depth[t]:e.depth[r])+1,a[2*t+1]=a[2*r+1]=n,e.heap[1]=n++,j(e,a,1),2<=e.heap_len;);e.heap[--e.heap_max]=e.heap[1],function(e){for(var t,r,n,a,s,i=m.dyn_tree,o=m.max_code,l=m.stat_desc.static_tree,c=m.stat_desc.has_stree,f=m.stat_desc.extra_bits,h=m.stat_desc.extra_base,u=m.stat_desc.max_length,d=0,p=0;p<=b;p++)e.bl_count[p]=0;for(i[2*e.heap[e.heap_max]+1]=0,t=e.heap_max+1;t>=7;s>>=1)if(1&t&&0!==e.dyn_ltree[2*r])return o;if(0!==e.dyn_ltree[18]||0!==e.dyn_ltree[20]||0!==e.dyn_ltree[26])return l;for(r=32;r>>3,(s=e.static_len+3+7>>>3)<=a&&(a=s)):a=s=r+5,r+4<=a&&-1!==t?Q(e,t,r,n):4===e.strategy||s===a?(M(e,2+(n?1:0),3),G(e,B,T)):(M(e,4+(n?1:0),3),function(e,t,r,n){var a;for(M(e,t-257,5),M(e,r-1,5),M(e,n-4,4),a=0;a>>8&255,e.pending_buf[e.d_buf+2*e.last_lit+1]=255&t,e.pending_buf[e.l_buf+e.last_lit]=255&r,e.last_lit++,0===t?e.dyn_ltree[2*r]++:(e.matches++,t--,e.dyn_ltree[2*(x[r]+c+1)]++,e.dyn_dtree[2*N(t)]++),e.last_lit===e.lit_bufsize-1},r._tr_align=function(e){M(e,2,3),U(e,m,B),16===(e=e).bi_valid?(L(e,e.bi_buf),e.bi_buf=0,e.bi_valid=0):8<=e.bi_valid&&(e.pending_buf[e.pending++]=255&e.bi_buf,e.bi_buf>>=8,e.bi_valid-=8)}},{"../utils/common":27}],39:[function(e,t,r){"use strict";t.exports=function(){this.input=null,this.next_in=0,this.avail_in=0,this.total_in=0,this.output=null,this.next_out=0,this.avail_out=0,this.total_out=0,this.msg="",this.state=null,this.data_type=2,this.adler=0}},{}]},{},[9])(9)});var XLSX={};function make_xlsx_lib(n){n.version="0.14.3";var f=1200,a=1252;"undefined"!=typeof module&&"undefined"!=typeof require&&"undefined"==typeof cptable&&("undefined"!=typeof global?global.cptable=void 0:"undefined"!=typeof window&&(window.cptable=void 0));for(var t=[874,932,936,949,950],e=0;e<=8;++e)t.push(1250+e);var c={0:1252,1:65001,2:65001,77:1e4,128:932,129:949,130:1361,134:936,136:950,161:1253,162:1254,163:1258,177:1255,178:1256,186:1257,204:1251,222:874,238:1250,255:1252,69:6969},l=function(e){-1!=t.indexOf(e)&&(a=c[0]=e)};var ce=function(e){l(f=e)};function h(){ce(1200),l(1252)}function ee(e){for(var t=[],r=0,n=e.length;r>1;++r)t[r]=String.fromCharCode(e.charCodeAt(2*r)+(e.charCodeAt(2*r+1)<<8));return t.join("")}(e.slice(2)):254==t&&255==r?function(e){for(var t=[],r=0;r>1;++r)t[r]=String.fromCharCode(e.charCodeAt(2*r+1)+(e.charCodeAt(2*r)<<8));return t.join("")}(e.slice(2)):65279==t?e.slice(1):e},u=function(e){return String.fromCharCode(e)};"undefined"!=typeof cptable&&(ce=function(e){f=e},te=function(e){return 255===e.charCodeAt(0)&&254===e.charCodeAt(1)?cptable.utils.decode(1200,ee(e.slice(2))):e},u=function(e){return 1200===f?String.fromCharCode(e):cptable.utils.decode(f,[255&e,e>>8])[0]});var d,fe=null,K=(d="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",{encode:function(e){for(var t,r,n,a,s,i="",o=0,l=0,c=0;c>2,s=(3&t)<<4|(r=e.charCodeAt(c++))>>4,o=(15&r)<<2|(n=e.charCodeAt(c++))>>6,l=63&n,isNaN(r)?o=l=64:isNaN(n)&&(l=64),i+=d.charAt(a)+d.charAt(s)+d.charAt(o)+d.charAt(l);return i},decode:function(e){var t,r,n,a,s,i,o="";e=e.replace(/[^\w\+\/\=]/g,"");for(var l=0;l>4,o+=String.fromCharCode(t),r=(15&a)<<4|(s=d.indexOf(e.charAt(l++)))>>2,64!==s&&(o+=String.fromCharCode(r)),n=(3&s)<<6|(i=d.indexOf(e.charAt(l++))),64!==i&&(o+=String.fromCharCode(n));return o}}),Z="undefined"!=typeof Buffer&&"undefined"!=typeof process&&void 0!==process.versions&&!!process.versions.node,s=function(){};if("undefined"!=typeof Buffer){var r=!Buffer.from;if(!r)try{Buffer.from("foo","utf8")}catch(e){r=!0}s=r?function(e,t){return t?new Buffer(e,t):new Buffer(e)}:Buffer.from.bind(Buffer),Buffer.alloc||(Buffer.alloc=function(e){return new Buffer(e)}),Buffer.allocUnsafe||(Buffer.allocUnsafe=function(e){return new Buffer(e)})}function Q(e){return Z?Buffer.alloc(e):new Array(e)}function J(e){return Z?Buffer.allocUnsafe(e):new Array(e)}var q=function(e){return Z?s(e,"binary"):e.split("").map(function(e){return 255&e.charCodeAt(0)})};function i(e){if("undefined"==typeof ArrayBuffer)return q(e);for(var t=new ArrayBuffer(e.length),r=new Uint8Array(t),n=0;n!=e.length;++n)r[n]=255&e.charCodeAt(n);return t}function o(e){if(Array.isArray(e))return e.map(ec).join("");for(var t=[],r=0;r=7+t&&103==(32|e.charCodeAt(t))&&101==(32|e.charCodeAt(t+1))&&110==(32|e.charCodeAt(t+2))&&101==(32|e.charCodeAt(t+3))&&114==(32|e.charCodeAt(t+4))&&97==(32|e.charCodeAt(t+5))&&108==(32|e.charCodeAt(t+6))}var x=[["Sun","Sunday"],["Mon","Monday"],["Tue","Tuesday"],["Wed","Wednesday"],["Thu","Thursday"],["Fri","Friday"],["Sat","Saturday"]],A=[["J","Jan","January"],["F","Feb","February"],["M","Mar","March"],["A","Apr","April"],["M","May","May"],["J","Jun","June"],["J","Jul","July"],["A","Aug","August"],["S","Sep","September"],["O","Oct","October"],["N","Nov","November"],["D","Dec","December"]];function t(e){e[0]="General",e[1]="0",e[2]="0.00",e[3]="#,##0",e[4]="#,##0.00",e[9]="0%",e[10]="0.00%",e[11]="0.00E+00",e[12]="# ?/?",e[13]="# ??/??",e[14]="m/d/yy",e[15]="d-mmm-yy",e[16]="d-mmm",e[17]="mmm-yy",e[18]="h:mm AM/PM",e[19]="h:mm:ss AM/PM",e[20]="h:mm",e[21]="h:mm:ss",e[22]="m/d/yy h:mm",e[37]="#,##0 ;(#,##0)",e[38]="#,##0 ;[Red](#,##0)",e[39]="#,##0.00;(#,##0.00)",e[40]="#,##0.00;[Red](#,##0.00)",e[45]="mm:ss",e[46]="[h]:mm:ss",e[47]="mmss.0",e[48]="##0.0E+0",e[49]="@",e[56]='"上午/下午 "hh"時"mm"分"ss"秒 "',e[65535]="General"}var s={};function y(e,t,r){for(var n=e<0?-1:1,a=e*n,s=0,i=1,o=0,l=1,c=0,f=0,h=Math.floor(a);c(e<0?12:11)&&(s=e.toPrecision(6)),s);return m(function(e){for(var t=0;t!=e.length;++t)if(101==(32|e.charCodeAt(t)))return e.replace(h,".$1").replace(u,"E").replace("e","E").replace(d,"$10$2");return e}(s))});function m(e){return-1t.length?a:L(t.substr(0,t.length-a.length))+a;if(s=t.match(O))return h=s,o=u,l=d,c=parseInt(h[4],10),f=Math.round(o*c),o=Math.floor(f/c),c,l+(0===o?"":""+o)+" "+(0==(f-=o*c)?E(" ",h[1].length+1+h[4].length):w(f,h[1].length)+h[2]+"/"+h[3]+T(c,h[4].length));if(t.match(/^#+0+$/))return d+_(u,t.length-t.indexOf("0"));if(s=t.match(F))return a=M(r,s[1].length).replace(/^([^\.]+)$/,"$1."+L(s[1])).replace(/\.$/,"."+L(s[1])).replace(/\.(\d*)$/,function(e,t){return"."+t+E("0",L(s[1]).length-t.length)}),-1!==t.indexOf("0.")?a:a.replace(/^0\./,".");if(t=t.replace(/^#+([0.])/,"$1"),s=t.match(/^(0*)\.(#*)$/))return d+M(u,s[2].length).replace(/\.(\d*[1-9])0*$/,".$1").replace(/^(-?\d*)$/,"$1.").replace(/^0\./,s[1].length?"0.":".");if(s=t.match(/^#{1,3},##0(\.?)$/))return d+C(_(u,0));if(s=t.match(/^#,##0\.([#0]*0)$/))return r<0?"-"+H(e,t,-r):C(""+(Math.floor(r)+(h=r,(p=s[1].length)<(""+Math.round((h-Math.floor(h))*Math.pow(10,p))).length?1:0)))+"."+T(U(r,s[1].length),s[1].length);if(s=t.match(/^#,#*,#0/))return H(e,t.replace(/^#,#*,/,""),r);if(s=t.match(/^([0#]+)(\\?-([0#]+))+$/))return a=v(H(e,t.replace(/[\\-]/g,""),r)),i=0,v(v(t.replace(/\\/g,"")).replace(/[0#]/g,function(e){return it.length?o:L(t.substr(0,t.length-o.length))+o;if(l=t.match(O))return h+(0===(u=f)?"":""+u)+E(" ",(u=l)[1].length+2+u[4].length);if(t.match(/^#+0+$/))return h+T(f,t.length-t.indexOf("0"));if(l=t.match(F))return o=(o=(""+r).replace(/^([^\.]+)$/,"$1."+L(l[1])).replace(/\.$/,"."+L(l[1]))).replace(/\.(\d*)$/,function(e,t){return"."+t+E("0",L(l[1]).length-t.length)}),-1!==t.indexOf("0.")?o:o.replace(/^0\./,".");if(t=t.replace(/^#+([0.])/,"$1"),l=t.match(/^(0*)\.(#*)$/))return h+(""+f).replace(/\.(\d*[1-9])0*$/,".$1").replace(/^(-?\d*)$/,"$1.").replace(/^0\./,l[1].length?"0.":".");if(l=t.match(/^#{1,3},##0(\.?)$/))return h+C(""+f);if(l=t.match(/^#,##0\.([#0]*0)$/))return r<0?"-"+g(e,t,-r):C(""+r)+"."+E("0",l[1].length);if(l=t.match(/^#,#*,#0/))return g(e,t.replace(/^#,#*,/,""),r);if(l=t.match(/^([0#]+)(\\?-([0#]+))+$/))return o=v(g(e,t.replace(/[\\-]/g,""),r)),c=0,v(v(t.replace(/\\/g,"")).replace(/[0#]/g,function(e){return c=€acfijklopqrtuvwxzP".indexOf(f))throw new Error("unrecognized character "+f+" in "+e);o[o.length]={t:"t",v:f},++c}var m,g=0,b=0;for(c=o.length-1,h="t";0<=c;--c)switch(o[c].t){case"h":case"H":o[c].t=u,h="h",g<1&&(g=1);break;case"s":(m=o[c].v.match(/\.0+$/))&&(b=Math.max(b,m[0].length-1)),g<3&&(g=3);case"d":case"y":case"M":case"e":h=o[c].t;break;case"m":"s"===h&&(o[c].t="M",g<2&&(g=2));break;case"X":break;case"Z":g<1&&o[c].v.match(/[Hh]/)&&(g=1),g<2&&o[c].v.match(/[Mm]/)&&(g=2),g<3&&o[c].v.match(/[Ss]/)&&(g=3)}switch(g){case 0:break;case 1:.5<=a.u&&(a.u=0,++a.S),60<=a.S&&(a.S=0,++a.M),60<=a.M&&(a.M=0,++a.H);break;case 2:.5<=a.u&&(a.u=0,++a.S),60<=a.S&&(a.S=0,++a.M)}var v,E="";for(c=0;c=o[c].v.length-1?(v-=o[c].v.length,o[c].v=S.substr(v+1,o[c].v.length)):v<0?o[c].v="":(o[c].v=S.substr(0,v+1),v=-1),o[c].t="t",C=c);0<=v&&C]/,j=/\[(=|>[=]?|<[>=]?)(-?\d+(?:\.\d*)?)\]/;function G(e,t){if(null!=t){var r=parseFloat(t[2]);switch(t[1]){case"=":if(e==r)return 1;break;case">":if(r":if(e!=r)return 1;break;case">=":if(r<=e)return 1;break;case"<=":if(e<=r)return 1}}}function $(e,t,r){null==r&&(r={});var n="";switch(typeof e){case"string":n="m/d/yy"==e&&r.dateNF?r.dateNF:e;break;case"number":n=14==e&&r.dateNF?r.dateNF:(null!=r.table?r.table:s)[e]}if(k(n,0))return R(t,r);t instanceof Date&&(t=l(t,r.date1904));var a=function(e,t){var r=b(e),n=r.length,a=r[n-1].indexOf("@");if(n<4&&-1>>1:e>>>1)?-306674912^e>>>1:e>>>1)?-306674912^e>>>1:e>>>1)?-306674912^e>>>1:e>>>1)?-306674912^e>>>1:e>>>1)?-306674912^e>>>1:e>>>1)?-306674912^e>>>1:e>>>1)?-306674912^e>>>1:e>>>1,t[r]=e;return"undefined"!=typeof Int32Array?new Int32Array(t):t}();e.table=o,e.bstr=function(e,t){for(var r=-1^t,n=e.length-1,a=0;a>>8^o[255&(r^e.charCodeAt(a++))])>>>8^o[255&(r^e.charCodeAt(a++))];return a===n&&(r=r>>>8^o[255&(r^e.charCodeAt(a))]),-1^r},e.buf=function(e,t){if(1e4>>8^o[255&(r^e[a++])])>>>8^o[255&(r^e[a++])])>>>8^o[255&(r^e[a++])])>>>8^o[255&(r^e[a++])])>>>8^o[255&(r^e[a++])])>>>8^o[255&(r^e[a++])])>>>8^o[255&(r^e[a++])])>>>8^o[255&(r^e[a++])];for(;a<7+n;)r=r>>>8^o[255&(r^e[a++])];return-1^r}(e,t);for(var r=-1^t,n=e.length-3,a=0;a>>8^o[255&(r^e[a++])])>>>8^o[255&(r^e[a++])])>>>8^o[255&(r^e[a++])])>>>8^o[255&(r^e[a++])];for(;a<3+n;)r=r>>>8^o[255&(r^e[a++])];return-1^r},e.str=function(e,t){for(var r,n,a=-1^t,s=0,i=e.length;s>>8^o[255&(a^r)]:r<2048?(a=a>>>8^o[255&(a^(192|r>>6&31))])>>>8^o[255&(a^(128|63&r))]:55296<=r&&r<57344?(r=64+(1023&r),n=1023&e.charCodeAt(s++),(a=(a=(a=a>>>8^o[255&(a^(240|r>>8&7))])>>>8^o[255&(a^(128|r>>2&63))])>>>8^o[255&(a^(128|n>>6&15|(3&r)<<4))])>>>8^o[255&(a^(128|63&n))]):(a=(a=a>>>8^o[255&(a^(224|r>>12&15))])>>>8^o[255&(a^(128|r>>6&63))])>>>8^o[255&(a^(128|63&r))];return-1^a}}(ie={});var b,oe=function(){var a,e={};function u(e){if("/"==e.charAt(e.length-1))return-1===e.slice(0,-1).indexOf("/")?e:u(e.slice(0,-1));var t=e.lastIndexOf("/");return-1===t?e:e.slice(0,t+1)}function d(e){if("/"==e.charAt(e.length-1))return d(e.slice(0,-1));var t=e.lastIndexOf("/");return-1===t?e:e.slice(t+1)}function b(e){At(e,0);for(var t,r={};e.l<=e.length-4;){var n=e.read_shift(2),a=e.read_shift(2),s=e.l+a,i={};21589===n&&(1&(t=e.read_shift(1))&&(i.mtime=e.read_shift(4)),5>>2)-1;if(o){for(var c=0;c=l.length?-1:o,o=a+1;o=l.length?-1:o,f.type=1}else u(e.FullPaths[a+1]||"")==u(h)&&(f.R=a+1),f.type=2}}}function n(e,t){var r=t||{};if(m(e),"zip"==r.fileType)return function(e,t){var t=t||{},r=[],n=[],a=Rt(1),s=t.compression?8:0,i=0;0;var o=0,l=0,c=0,f=0,h=e.FullPaths[0],u=h,d=e.FileIndex[0],p=[],m=0;for(o=1;o>>1,e.write_shift(2,r),r=(r=(r=t.getFullYear()-1980)<<4|t.getMonth()+1)<<5|t.getDate(),e.write_shift(2,r)}(a,d.mt):a.write_shift(4,0),a.write_shift(-4,8&i?0:p[f]),a.write_shift(4,8&i?0:v.length),a.write_shift(4,8&i?0:d.content.length),a.write_shift(2,b.length),a.write_shift(2,0),c+=a.length,r.push(a),c+=b.length,r.push(b),c+=v.length,r.push(v),8&i&&((a=Rt(12)).write_shift(-4,p[f]),a.write_shift(4,v.length),a.write_shift(4,d.content.length),c+=a.l,r.push(a)),(a=Rt(46)).write_shift(4,33639248),a.write_shift(2,0),a.write_shift(2,20),a.write_shift(2,i),a.write_shift(2,s),a.write_shift(4,0),a.write_shift(-4,p[f]),a.write_shift(4,v.length),a.write_shift(4,d.content.length),a.write_shift(2,b.length),a.write_shift(2,0),a.write_shift(2,0),a.write_shift(2,0),a.write_shift(2,0),a.write_shift(4,0),a.write_shift(4,g),m+=a.l,n.push(a),m+=b.length,n.push(b),++f}return(a=Rt(22)).write_shift(4,101010256),a.write_shift(2,0),a.write_shift(2,0),a.write_shift(2,f),a.write_shift(2,f),a.write_shift(4,m),a.write_shift(4,c),a.write_shift(2,0),he([he(r),he(n),a])}(e,r);for(var n=function(e){for(var t=0,r=0,n=0;n>6:r+=a+511>>9))}for(var s=e.FullPaths.length+3>>2,i=t+127>>7,o=(t+7>>3)+r+s+i,l=o+127>>7,c=l<=109?0:Math.ceil((l-109)/127);l>7;)c=++l<=109?0:Math.ceil((l-109)/127);s=[1,c,l,i,s,r,t,0];return e.FileIndex[0].size=t<<6,s[7]=(e.FileIndex[0].start=s[0]+s[1]+s[2]+s[3]+s[4]+s[5])+(s[6]+7>>3),s}(e),a=Rt(n[7]<<9),s=0,i=0,s=0;s<8;++s)a.write_shift(1,g[s]);for(s=0;s<8;++s)a.write_shift(2,0);for(a.write_shift(2,62),a.write_shift(2,3),a.write_shift(2,65534),a.write_shift(2,9),a.write_shift(2,6),s=0;s<3;++s)a.write_shift(2,0);for(a.write_shift(4,0),a.write_shift(4,n[2]),a.write_shift(4,n[0]+n[1]+n[2]+n[3]-1),a.write_shift(4,0),a.write_shift(4,4096),a.write_shift(4,n[3]?n[0]+n[1]+n[2]-1:S),a.write_shift(4,n[3]),a.write_shift(-4,n[1]?n[0]-1:S),a.write_shift(4,n[1]),s=0;s<109;++s)a.write_shift(-4,s>9)));for(o(n[6]+7>>3);511&a.l;)a.write_shift(-4,C.ENDOFCHAIN);for(l=i=s=0;l>6)));for(;511&a.l;)a.write_shift(-4,C.ENDOFCHAIN);for(s=0;s>16|t>>8|t);function I(e,t){var r=7&t,t=t>>>3;return(e[t]|(r<=5?0:e[1+t]<<8))>>>r&7}function R(e,t){var r=7&t,t=t>>>3;return(e[t]|(r<=3?0:e[1+t]<<8))>>>r&31}function O(e,t){var r=7&t,t=t>>>3;return(e[t]|(r<=1?0:e[1+t]<<8))>>>r&127}function F(e,t,r){var n=7&t,a=t>>>3,s=(1<>>n;return r<8-n?t&s:(t|=e[1+a]<<8-n,r<16-n?t&s:(t|=e[2+a]<<16-n,r<24-n?t&s:(t|=e[3+a]<<24-n)&s))}function D(e,t){var r=e.length,n=t<2*r?2*r:t+5,a=0;if(t<=r)return e;if(Z){var s=J(n);if(e.copy)e.copy(s);else for(;a>>8-d:(p=p<<8|A[u>>8&255],d<=16?p>>>16-d:(p=p<<8|A[u>>16&255])>>>24-d))>>a-h,i=(1<>>0,o=0,l=0;0==(1&n);)if(n=I(e,r),r+=3,n>>>1!=0)for(l=n>>>1==1?(o=9,5):(r=function(e,t){var r,n,a,s=R(e,t)+257,i=R(e,t+=5)+1,o=(a=7&(n=t+=5),4+(((r=e)[n=n>>>3]|(a<=4?0:r[1+n]<<8))>>>a&15));t+=4;for(var l=0,c=x?new Uint8Array(19):P(19),f=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],h=1,u=x?new Uint8Array(8):P(8),d=x?new Uint8Array(8):P(8),p=c.length,m=0;m>8-b;for(var v=(1<<7-b)-1;0<=v;--v)V[g|v<>>=3){case 16:for(l=3+(S=void 0,S=7&(w=t),((E=e)[w=w>>>3]|(S<=6?0:E[1+w]<<8))>>>S&3),t+=2,g=_[_.length-1];0>>1==1?M:H)[c];if(r+=15&f,0==((f>>>=4)>>>8&255))a[s++]=f;else{if(256==f)break;var h=(f-=257)<8?0:f-4>>2;5>>1==1?U:z)[c]);var c=(f>>>=4)<4?0:f-2>>1,d=k[f];for(0>>3]|e[1+(r>>>3)]<<8;if(r+=32,!t&&i>>3,(r>>>3)+p),s+=p,r+=8*p;else for(;0>>3],r+=8}return[t?a:a.slice(0,s),r+7>>>3]}function j(e,t){t=f(e.slice(e.l||0),t);return e.l+=t[1],t[0]}function G(e,t){if(!e)throw new Error(t);"undefined"!=typeof console&&console.error(t)}function $(e,t){var r=e;At(r,0);var n={FileIndex:[],FullPaths:[]};p(n,{root:t.root});for(var a=r.length-4;(80!=r[a]||75!=r[a+1]||5!=r[a+2]||6!=r[a+3])&&0<=a;)--a;r.l=a+4,r.l+=4;var s=r.read_shift(2);r.l+=6;t=r.read_shift(4);for(r.l=t,a=0;a>>=5);return r>>>=4,n.setMilliseconds(0),n.setFullYear(1980+r),n.setMonth(e-1),n.setDate(a),e=31&t,a=63&(t>>>=5),t>>>=6,n.setHours(t),n.setMinutes(a),n.setSeconds(e<<1),n}(e);if(8257&s)throw new Error("Unsupported ZIP encryption");for(var l,c=e.read_shift(4),f=e.read_shift(4),h=e.read_shift(4),u=e.read_shift(2),d=e.read_shift(2),p="",m=0;m\/]+)\s*=\s*((?:")([^"]*)(?:")|(?:')([^']*)(?:')|([^'">\s]+))/g,W=/<[\/\?]?[a-zA-Z0-9:]+(?:\s+[^"\s?>\/]+\s*=\s*(?:"[^"]*"|'[^']*'|[^'">\s=]+))*\s?[\/\?]?>/g;z.match(W)||(W=/<[^>]*>/g);var j=/<\w*:/,G=/<(\/?)\w+:/;function me(e,t){for(var r={},n=0,a=0;n!==e.length&&(32!==(a=e.charCodeAt(n))&&10!==a&&13!==a);++n);if(t||(r[0]=e.slice(0,n)),n===e.length)return r;var s,i,o,l=e.match(V),c=0,f=0,h="",u="";if(l)for(f=0;f!=l.length;++f){for(u=l[f],a=0;a!=u.length&&61!==u.charCodeAt(a);++a);for(h=u.slice(0,a).trim();32==u.charCodeAt(a+1);)++a;for(i=34==(n=u.charCodeAt(a+1))||39==n?1:0,s=u.slice(a+1+i,u.length-i),c=0;c!=h.length&&58!==h.charCodeAt(c);++c);c===h.length?(0","<":"<","&":"&"},Ee=S(ve),we=(ge=/&(?:quot|apos|gt|lt|amp|#x?([\da-fA-F]+));/g,be=/_x([\da-fA-F]{4})_/g,function e(t){var r=t+"",n=r.indexOf("");return e(r.slice(0,n))+r.slice(n+9,t)+e(r.slice(t+3))}),Se=/[&<>'"]/g,_e=/[\u0000-\u0008\u000b-\u001f]/g;function ye(e){return(e+"").replace(Se,function(e){return Ee[e]}).replace(_e,function(e){return"_x"+("000"+e.charCodeAt(0).toString(16)).slice(-4)+"_"})}function Ce(e){return ye(e).replace(/ /g,"_x0020_")}var Be=/[\u0000-\u001f]/g;function Te(e){return(e+"").replace(Se,function(e){return Ee[e]}).replace(/\n/g,"
    ").replace(Be,function(e){return"&#x"+("000"+e.charCodeAt(0).toString(16)).slice(-4)+";"})}var ke,xe=(ke=/&#(\d+);/g,function(e){return e.replace(ke,Ae)});function Ae(e,t){return String.fromCharCode(parseInt(t,10))}var Ie=function(e){return e.replace(/(\r\n|[\r\n])/g," ")};function Re(e){switch(e){case 1:case!0:case"1":case"true":case"TRUE":return!0;default:return!1}}var Oe=function(e){for(var t,r,n,a,s="",i=0,o=0;i>>10&1023)),s+=String.fromCharCode(56320+(1023&a)))));return s},Fe=function(e){for(var t,r=[],n=0,a=0;n>6))),r.push(String.fromCharCode(128+(63&a)));break;case 55296<=a&&a<57344:a-=55296,t=e.charCodeAt(n++)-56320+(a<<10),r.push(String.fromCharCode(240+(t>>18&7))),r.push(String.fromCharCode(144+(t>>12&63))),r.push(String.fromCharCode(128+(t>>6&63))),r.push(String.fromCharCode(128+(63&t)));break;default:r.push(String.fromCharCode(224+(a>>12))),r.push(String.fromCharCode(128+(a>>6&63))),r.push(String.fromCharCode(128+(63&a)))}return r.join("")};Z&&(fo=function(e){for(var t,r,n=Buffer.alloc(2*e.length),a=1,s=0,i=0,o=0;o>>10&1023),t=56320+(1023&t)),0!==i&&(n[s++]=255&i,n[s++]=i>>>8,i=0),n[s++]=t%256,n[s++]=t>>>8;return n.slice(0,s).toString("ucs2")},Oe(ho="foo bar baz☃🍣")==fo(ho)&&(Oe=fo),uo=function(e){return s(e,"binary").toString("utf8")},Oe(ho)==uo(ho)&&(Oe=uo),Fe=function(e){return s(e,"utf8").toString("binary")});var De,Pe,Ne,Le=(De={},function(e,t){var r=e+"|"+(t||"");return De[r]||(De[r]=new RegExp("<(?:\\w+:)?"+e+'(?: xml:space="preserve")?(?:[^>]*)>([\\s\\S]*?)",t||""))}),Me=(Pe=[["nbsp"," "],["middot","·"],["quot",'"'],["apos","'"],["gt",">"],["lt","<"],["amp","&"]].map(function(e){return[new RegExp("&"+e[0]+";","g"),e[1]]}),function(e){for(var t=e.replace(/^[\t\n\r ]+/,"").replace(/[\t\n\r ]+$/,"").replace(/[\t\n\r ]+/g," ").replace(/<\s*[bB][rR]\s*\/?>/g,"\n").replace(/<[^>]*>/g,""),r=0;r([\\s\\S]*?)","g")}),He=/<\/?(?:vt:)?variant>/g,ze=/<(?:vt:)([^>]*)>([\s\S]*)"+t+""}function je(t){return de(t).map(function(e){return" "+e+'="'+t[e]+'"'}).join("")}function Ge(e,t,r){return"<"+e+(null!=r?je(r):"")+(null!=t?(t.match(We)?' xml:space="preserve"':"")+">"+t+""}function $e(e,t){try{return e.toISOString().replace(/\.\d*/,"")}catch(e){if(t)throw e}return""}var Ye={dc:"http://purl.org/dc/elements/1.1/",dcterms:"http://purl.org/dc/terms/",dcmitype:"http://purl.org/dc/dcmitype/",mx:"http://schemas.microsoft.com/office/mac/excel/2008/main",r:"http://schemas.openxmlformats.org/officeDocument/2006/relationships",sjs:"http://schemas.openxmlformats.org/package/2006/sheetjs/core-properties",vt:"http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes",xsi:"http://www.w3.org/2001/XMLSchema-instance",xsd:"http://www.w3.org/2001/XMLSchema",main:["http://schemas.openxmlformats.org/spreadsheetml/2006/main","http://purl.oclc.org/ooxml/spreadsheetml/main","http://schemas.microsoft.com/office/excel/2006/main","http://schemas.microsoft.com/office/excel/2006/2"]},Ke={o:"urn:schemas-microsoft-com:office:office",x:"urn:schemas-microsoft-com:office:excel",ss:"urn:schemas-microsoft-com:office:spreadsheet",dt:"uuid:C2F41010-65B3-11d1-A29F-00AA00C14882",mv:"http://macVmlSchemaUri",v:"urn:schemas-microsoft-com:vml",html:"http://www.w3.org/TR/REC-html40"};var Ze,Qe,Je=function(e){for(var t=[],r=0;r>>7),n=((127&e[t+7])<<4)+(e[t+6]>>>4&15),a=15&e[t+6],s=5;0<=s;--s)a=256*a+e[t+s];return 2047==n?0==a?1/0*r:NaN:(0==n?n=-1022:(n-=1023,a+=Math.pow(2,52)),r*Math.pow(2,n-52)*a)},gt=function(e){return Array.isArray(e)};Z&&(et=function(e,t,r){return Buffer.isBuffer(e)?e.toString("utf16le",t,r).replace(re,""):tt(e,t,r)},rt=function(e,t,r){return Buffer.isBuffer(e)?e.toString("hex",t,t+r):nt(e,t,r)},it=function(e,t){if(!Buffer.isBuffer(e))return ot(e,t);var r=e.readUInt32LE(t);return 0>>8&255,e[r+2]=t>>>16&255,e[r+3]=t>>>24&255},Bt=function(e,t,r){e[r]=255&t,e[r+1]=t>>8&255,e[r+2]=t>>16&255,e[r+3]=t>>24&255},Tt=function(e,t,r){e[r]=255&t,e[r+1]=t>>>8&255};function kt(e,t,r){var n=0,a=0;if("dbcs"===r){for(a=0;a!=t.length;++a)Tt(this,t.charCodeAt(a),this.l+2*a);n=2*t.length}else if("sbcs"===r){for(t=t.replace(/[^\x00-\x7F]/g,"_"),a=0;a!=t.length;++a)this[this.l+a]=255&t.charCodeAt(a);n=t.length}else{if("hex"===r){for(;a>8}for(;this.l>>=8,this[this.l+1]=255&t;break;case 3:n=3,this[this.l]=255&t,t>>>=8,this[this.l+1]=255&t,t>>>=8,this[this.l+2]=255&t;break;case 4:n=4,Ct(this,t,this.l);break;case 8:if(n=8,"f"===r){!function(e,t,r){var n=(t<0||1/t==-1/0?1:0)<<7,a=0,s=0,i=n?-t:t;isFinite(i)?0==i?a=s=0:(a=Math.floor(Math.log(i)/Math.LN2),s=i*Math.pow(2,52-a),a<=-1023&&(!isFinite(s)||s>4|n}(this,t,this.l);break}case 16:break;case-4:n=4,Bt(this,t,this.l)}}return this.l+=n,this}function xt(e,t){var r=rt(this,this.l,e.length>>1);if(r!==e)throw new Error(t+"Expected "+e+" saw "+r);this.l+=e.length>>1}function At(e,t){e.l=t,e.read_shift=yt,e.chk=xt,e.write_shift=kt}function It(e,t){e.l+=t}function Rt(e){e=Q(e);return At(e,0),e}function Ot(e,t,r){if(e){At(e,e.l||0);for(var n,a=e.length,s=0;e.ls.l&&((s=s.slice(0,s.l)).l=s.length),0>7));for(var i=0;4!=i;++i){if(!(128<=n)){s.write_shift(1,n);break}s.write_shift(1,128+(127&n)),n>>=7}0d&&(c.s.r=d),c.s.c>p&&(c.s.c=p),c.e.r>2;return r?t/100:t}function gr(e){var t={s:{},e:{}};return t.s.r=e.read_shift(4),t.e.r=e.read_shift(4),t.s.c=e.read_shift(4),t.e.c=e.read_shift(4),t}var br=gr,vr=function(e,t){return(t=t||Rt(16)).write_shift(4,e.s.r),t.write_shift(4,e.e.r),t.write_shift(4,e.s.c),t.write_shift(4,e.e.c),t};function Er(e){return e.read_shift(8,"f")}function wr(e,t){return(t||Rt(8)).write_shift(8,e,"f")}var Sr={0:"#NULL!",7:"#DIV/0!",15:"#VALUE!",23:"#REF!",29:"#NAME?",36:"#NUM!",42:"#N/A",43:"#GETTING_DATA",255:"#WTF?"},_r=_(Sr);function yr(e,t){if(t=t||Rt(8),!e||e.auto)return t.write_shift(4,0),t.write_shift(4,0),t;e.index?(t.write_shift(1,2),t.write_shift(1,e.index)):e.theme?(t.write_shift(1,6),t.write_shift(1,e.theme)):(t.write_shift(1,5),t.write_shift(1,0));var r=e.tint||0;return 0>16&255,e>>8&255,255&e]}),Ur={"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml":"workbooks","application/vnd.ms-excel.binIndexWs":"TODO","application/vnd.ms-excel.intlmacrosheet":"TODO","application/vnd.ms-excel.binIndexMs":"TODO","application/vnd.openxmlformats-package.core-properties+xml":"coreprops","application/vnd.openxmlformats-officedocument.custom-properties+xml":"custprops","application/vnd.openxmlformats-officedocument.extended-properties+xml":"extprops","application/vnd.openxmlformats-officedocument.customXmlProperties+xml":"TODO","application/vnd.openxmlformats-officedocument.spreadsheetml.customProperty":"TODO","application/vnd.ms-excel.pivotTable":"TODO","application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml":"TODO","application/vnd.ms-office.chartcolorstyle+xml":"TODO","application/vnd.ms-office.chartstyle+xml":"TODO","application/vnd.ms-excel.calcChain":"calcchains","application/vnd.openxmlformats-officedocument.spreadsheetml.calcChain+xml":"calcchains","application/vnd.openxmlformats-officedocument.spreadsheetml.printerSettings":"TODO","application/vnd.ms-office.activeX":"TODO","application/vnd.ms-office.activeX+xml":"TODO","application/vnd.ms-excel.attachedToolbars":"TODO","application/vnd.ms-excel.connections":"TODO","application/vnd.openxmlformats-officedocument.spreadsheetml.connections+xml":"TODO","application/vnd.ms-excel.externalLink":"links","application/vnd.openxmlformats-officedocument.spreadsheetml.externalLink+xml":"links","application/vnd.ms-excel.sheetMetadata":"TODO","application/vnd.openxmlformats-officedocument.spreadsheetml.sheetMetadata+xml":"TODO","application/vnd.ms-excel.pivotCacheDefinition":"TODO","application/vnd.ms-excel.pivotCacheRecords":"TODO","application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml":"TODO","application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheRecords+xml":"TODO","application/vnd.ms-excel.queryTable":"TODO","application/vnd.openxmlformats-officedocument.spreadsheetml.queryTable+xml":"TODO","application/vnd.ms-excel.userNames":"TODO","application/vnd.ms-excel.revisionHeaders":"TODO","application/vnd.ms-excel.revisionLog":"TODO","application/vnd.openxmlformats-officedocument.spreadsheetml.revisionHeaders+xml":"TODO","application/vnd.openxmlformats-officedocument.spreadsheetml.revisionLog+xml":"TODO","application/vnd.openxmlformats-officedocument.spreadsheetml.userNames+xml":"TODO","application/vnd.ms-excel.tableSingleCells":"TODO","application/vnd.openxmlformats-officedocument.spreadsheetml.tableSingleCells+xml":"TODO","application/vnd.ms-excel.slicer":"TODO","application/vnd.ms-excel.slicerCache":"TODO","application/vnd.ms-excel.slicer+xml":"TODO","application/vnd.ms-excel.slicerCache+xml":"TODO","application/vnd.ms-excel.wsSortMap":"TODO","application/vnd.ms-excel.table":"TODO","application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml":"TODO","application/vnd.openxmlformats-officedocument.theme+xml":"themes","application/vnd.openxmlformats-officedocument.themeOverride+xml":"TODO","application/vnd.ms-excel.Timeline+xml":"TODO","application/vnd.ms-excel.TimelineCache+xml":"TODO","application/vnd.ms-office.vbaProject":"vba","application/vnd.ms-office.vbaProjectSignature":"vba","application/vnd.ms-office.volatileDependencies":"TODO","application/vnd.openxmlformats-officedocument.spreadsheetml.volatileDependencies+xml":"TODO","application/vnd.ms-excel.controlproperties+xml":"TODO","application/vnd.openxmlformats-officedocument.model+data":"TODO","application/vnd.ms-excel.Survey+xml":"TODO","application/vnd.openxmlformats-officedocument.drawing+xml":"drawings","application/vnd.openxmlformats-officedocument.drawingml.chart+xml":"TODO","application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml":"TODO","application/vnd.openxmlformats-officedocument.drawingml.diagramColors+xml":"TODO","application/vnd.openxmlformats-officedocument.drawingml.diagramData+xml":"TODO","application/vnd.openxmlformats-officedocument.drawingml.diagramLayout+xml":"TODO","application/vnd.openxmlformats-officedocument.drawingml.diagramStyle+xml":"TODO","application/vnd.openxmlformats-officedocument.vmlDrawing":"TODO","application/vnd.openxmlformats-package.relationships+xml":"rels","application/vnd.openxmlformats-officedocument.oleObject":"TODO","image/png":"TODO",sheet:"js"},Hr=(de(Lr={workbooks:{xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml",xlsm:"application/vnd.ms-excel.sheet.macroEnabled.main+xml",xlsb:"application/vnd.ms-excel.sheet.binary.macroEnabled.main",xlam:"application/vnd.ms-excel.addin.macroEnabled.main+xml",xltx:"application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml"},strs:{xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml",xlsb:"application/vnd.ms-excel.sharedStrings"},comments:{xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml",xlsb:"application/vnd.ms-excel.comments"},sheets:{xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml",xlsb:"application/vnd.ms-excel.worksheet"},charts:{xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml",xlsb:"application/vnd.ms-excel.chartsheet"},dialogs:{xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.dialogsheet+xml",xlsb:"application/vnd.ms-excel.dialogsheet"},macros:{xlsx:"application/vnd.ms-excel.macrosheet+xml",xlsb:"application/vnd.ms-excel.macrosheet"},styles:{xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml",xlsb:"application/vnd.ms-excel.styles"}}).forEach(function(t){["xlsm","xlam"].forEach(function(e){Lr[t][e]||(Lr[t][e]=Lr[t].xlsx)})}),de(Lr).forEach(function(t){de(Lr[t]).forEach(function(e){Ur[Lr[t][e]]=t})}),Lr),zr=function(e){for(var t=[],r=de(e),n=0;n!==r.length;++n)null==t[e[r[n]]]&&(t[e[r[n]]]=[]),t[e[r[n]]].push(r[n]);return t}(Ur);function Vr(){return{workbooks:[],sheets:[],charts:[],dialogs:[],macros:[],rels:[],strs:[],comments:[],links:[],coreprops:[],extprops:[],custprops:[],themes:[],styles:[],calcchains:[],vba:[],drawings:[],TODO:[],xmlns:""}}Ye.CT="http://schemas.openxmlformats.org/package/2006/content-types";var Wr=Ge("Types",null,{xmlns:Ye.CT,"xmlns:xsd":Ye.xsd,"xmlns:xsi":Ye.xsi}),Xr=[["xml","application/xml"],["bin","application/vnd.ms-excel.sheet.binary.macroEnabled.main"],["vml","application/vnd.openxmlformats-officedocument.vmlDrawing"],["bmp","image/bmp"],["png","image/png"],["gif","image/gif"],["emf","image/x-emf"],["wmf","image/x-wmf"],["jpg","image/jpeg"],["jpeg","image/jpeg"],["tif","image/tiff"],["tiff","image/tiff"],["pdf","application/pdf"],["rels",zr.rels[0]]].map(function(e){return Ge("Default",null,{Extension:e[0],ContentType:e[1]})});function jr(r,n){var t,a=[];a[a.length]=z,a[a.length]=Wr,a=a.concat(Xr);function e(e){r[e]&&0",">")),a.join("")}var Gr={WB:"http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument",SHEET:"http://sheetjs.openxmlformats.org/officeDocument/2006/relationships/officeDocument",HLINK:"http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",VML:"http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing",VBA:"http://schemas.microsoft.com/office/2006/relationships/vbaProject"};function $r(e){var t=e.lastIndexOf("/");return e.slice(0,t+1)+"_rels/"+e.slice(t+1)+".rels"}function Yr(e,n){if(!e)return e;"/"!==n.charAt(0)&&(n="/"+n);var a={},s={};return(e.match(W)||[]).forEach(function(e){var t,r=me(e);"",r[1]=r[1].replace("/>",">")),r.join("")}function Qr(e,t,r,n,a){if(a=a||{},e["!id"]||(e["!id"]={}),t<0)for(t=1;e["!id"]["rId"+t];++t);if(a.Id="rId"+t,a.Type=n,a.Target=r,a.Type==Gr.HLINK&&(a.TargetMode="External"),e["!id"][a.Id])throw new Error("Cannot rewrite rId "+t);return e["!id"][a.Id]=a,e[("/"+a.Target).replace("//","/")]=a,t}var Jr="application/vnd.oasis.opendocument.spreadsheet";function qr(e,t,r){return[' \n',' \n'," \n"].join("")}var en,tn=(en='SheetJS '+n.version+"",function(){return en}),rn=[["cp:category","Category"],["cp:contentStatus","ContentStatus"],["cp:keywords","Keywords"],["cp:lastModifiedBy","LastAuthor"],["cp:lastPrinted","LastPrinted"],["cp:revision","RevNumber"],["cp:version","Version"],["dc:creator","Author"],["dc:description","Comments"],["dc:identifier","Identifier"],["dc:language","Language"],["dc:subject","Subject"],["dc:title","Title"],["dcterms:created","CreatedDate","date"],["dcterms:modified","ModifiedDate","date"]];Ye.CORE_PROPS="http://schemas.openxmlformats.org/package/2006/metadata/core-properties",Gr.CORE_PROPS="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties";var nn=function(){for(var e=new Array(rn.length),t=0;t]*>([\\s\\S]*?)")}return e}();function an(e){var t={};e=Oe(e);for(var r=0;r]+>[^<]*/g;var un=Ge("Properties",null,{xmlns:Ye.CUST_PROPS,"xmlns:vt":Ye.vt});function dn(t){var r=[z,un];if(!t)return r.join("");var n=1;return de(t).forEach(function(e){++n,r[r.length]=Ge("property",function(e){switch(typeof e){case"string":return Ge("vt:lpwstr",e);case"number":return Ge((0|e)==e?"vt:i4":"vt:r8",String(e));case"boolean":return Ge("vt:bool",e?"true":"false")}if(e instanceof Date)return Ge("vt:filetime",$e(e));throw new Error("Unable to serialize "+e)}(t[e]),{fmtid:"{D5CDD505-2E9C-101B-9397-08002B2CF9AE}",pid:n,name:e})}),2",r[1]=r[1].replace("/>",">")),r.join("")}var pn={Title:"Title",Subject:"Subject",Author:"Author",Keywords:"Keywords",Comments:"Description",LastAuthor:"LastAuthor",RevNumber:"Revision",Application:"AppName",LastPrinted:"LastPrinted",CreatedDate:"Created",ModifiedDate:"LastSaved",Category:"Category",Manager:"Manager",Company:"Company",AppVersion:"Version",ContentStatus:"ContentStatus",Identifier:"Identifier",Language:"Language"},mn=S(pn);function gn(e){var t=e.read_shift(4),e=e.read_shift(4);return new Date(1e3*(e/1e7*Math.pow(2,32)+t/1e7-11644473600)).toISOString().replace(/\.000/,"")}function bn(e,t,r){var n=e.l,a=e.read_shift(0,"lpstr-cp");if(r)for(;e.l-n&3;)++e.l;return a}function vn(e,t,r){var n=e.read_shift(0,"lpwstr");return r&&(e.l+=4-(n.length+1&3)&3),n}function En(e,t,r){return 31===t?vn(e):bn(e,0,r)}function wn(e,t,r){return En(e,t,!1===r?0:4)}function Sn(e){return function(e){for(var t=e.read_shift(4),r=[],n=0;n!=t;++n)r[n]=e.read_shift(0,"lpstr-cp").replace(re,"");return r}(e)}function _n(e){for(var t,r=e.read_shift(4),n=[],a=0;a!=r/2;++a)n.push([Bn(t=e,xr),Bn(t,Tr)]);return n}function yn(e,t){for(var r=e.read_shift(4),n={},a=0;a!=r;++a){var s=e.read_shift(4),i=e.read_shift(4);n[s]=e.read_shift(i,1200===t?"utf16le":"utf8").replace(re,"").replace(ne,"!"),1200===t&&i%2&&(e.l+=2)}return 3&e.l&&(e.l=e.l>>3<<2),n}function Cn(e){var t=e.read_shift(4),r=e.slice(e.l,e.l+t);return e.l+=t,0<(3&t)&&(e.l+=4-(3&t)&3),r}function Bn(e,t,r){var n,a,s=e.read_shift(2),i=r||{};if(e.l+=2,t!==kr&&s!==t&&-1===Ar.indexOf(t))throw new Error("Expected type "+t+" saw "+s);switch(t===kr?s:t){case 2:return n=e.read_shift(2,"i"),i.raw||(e.l+=2),n;case 3:return n=e.read_shift(4,"i");case 11:return 0!==e.read_shift(4);case 19:return n=e.read_shift(4);case 30:return bn(e,0,4).replace(re,"");case 31:return vn(e);case 64:return gn(e);case 65:return Cn(e);case 71:return(a={}).Size=(n=e).read_shift(4),n.l+=a.Size+3-(a.Size-1)%4,a;case 80:return wn(e,s,!i.raw).replace(re,"");case 81:return function(e,t){if(!t)throw new Error("VtUnalignedString must have positive length");return En(e,t,0)}(e,s).replace(re,"");case 4108:return _n(e);case 4126:return Sn(e);default:throw new Error("TypedPropertyValue unrecognized type "+t+" "+s)}}function Tn(e,t){var r,n,a,s=Rt(4),i=Rt(4);switch(s.write_shift(4,80==e?31:e),e){case 3:i.write_shift(-4,t);break;case 5:(i=Rt(8)).write_shift(8,t,"f");break;case 11:i.write_shift(4,t?1:0);break;case 64:n=("string"==typeof(r=t)?new Date(Date.parse(r)):r).getTime()/1e3+11644473600,a=n%Math.pow(2,32),r=(n-a)/Math.pow(2,32),r*=1e7,0<(n=(a*=1e7)/Math.pow(2,32)|0)&&(a%=Math.pow(2,32),r+=n),(n=Rt(8)).write_shift(4,a),n.write_shift(4,r),i=n;break;case 31:case 80:for((i=Rt(4+2*(t.length+1)+(t.length%2?0:2))).write_shift(4,t.length+1),i.write_shift(0,t,"dbcs");i.l!=i.length;)i.write_shift(1,0);break;default:throw new Error("TypedPropertyValue unrecognized type "+e+" "+t)}return he([s,i])}function kn(e,t){for(var r=e.l,n=e.read_shift(4),a=e.read_shift(4),s=[],i=0,o=0,l=-1,c={},i=0;i!=a;++i){var f=e.read_shift(4),h=e.read_shift(4);s[i]=[f,h+r]}s.sort(function(e,t){return e[1]-t[1]});var u={};for(i=0;i!=a;++i){if(e.l!==s[i][1]){var d=!0;if(0>16)+"."+("0000"+String(65535&u[p.n])).slice(-4)),"CodePage"==p.n)switch(u[p.n]){case 0:u[p.n]=1252;case 874:case 932:case 936:case 949:case 950:case 1250:case 1251:case 1253:case 1254:case 1255:case 1256:case 1257:case 1258:case 1e4:case 1200:case 1201:case 1252:case 65e3:case-536:case 65001:case-535:ce(o=u[p.n]>>>0&65535);break;default:throw new Error("Unsupported CodePage: "+u[p.n])}}else if(1===s[i][0]){o=u.CodePage=Bn(e,Br);ce(o),-1!==l&&(g=e.l,e.l=s[l][1],c=yn(e,o),e.l=g)}else if(0===s[i][0])0!==o?c=yn(e,o):(l=i,e.l=s[i+1][1]);else{var m,g=c[s[i][0]];switch(e[e.l]){case 65:e.l+=4,m=Cn(e);break;case 30:case 31:e.l+=4,m=wn(e,e[e.l-4]).replace(/\u0000+$/,"");break;case 3:e.l+=4,m=e.read_shift(4,"i");break;case 19:e.l+=4,m=e.read_shift(4);break;case 5:e.l+=4,m=e.read_shift(8,"f");break;case 11:e.l+=4,m=Fn(e,4);break;case 64:e.l+=4,m=le(gn(e));break;default:throw new Error("unparsed value: "+e[e.l])}u[g]=m}}return e.l=r+n,u}var xn=["CodePage","Thumbnail","_PID_LINKBASE","_PID_HLINKS","SystemIdentifier","FMTID"].concat(["Worksheets","SheetNames","NamedRanges","DefinedNames","Chartsheets","ChartNames"]);function An(e,t,r){var n,a,s,i,o=Rt(8),l=[],c=[],f=8,h=0,u=Rt(8),d=Rt(8);if(u.write_shift(4,2),u.write_shift(4,1200),d.write_shift(4,1),c.push(u),l.push(d),f+=8+u.length,!t){(d=Rt(8)).write_shift(4,0),l.unshift(d);var p=[Rt(4)];for(p[0].write_shift(4,e.length),h=0;h>1,"utf16le").replace(re,""),a&&(t.l+=24),r;case"0303000000000000c000000000000046":return function(e){e.l+=2;var t=e.read_shift(0,"lpstr-ansi");if(e.l+=2,57005!=e.read_shift(2))throw new Error("Bad FileMoniker");if(0===e.read_shift(4))return t.replace(/\\/g,"/");if(t=e.read_shift(4),3!=e.read_shift(2))throw new Error("Bad FileMoniker");return e.read_shift(t>>1,"utf16le").replace(re,"")}(e);default:throw new Error("Unsupported Moniker "+s)}}function Xn(e){var t=e.read_shift(4);return 0>15),e&=32767),[{Unsynced:1&n,DyZero:(2&n)>>1,ExAsc:(4&n)>>2,ExDsc:(8&n)>>3},e]}var ia=zn;function oa(e,t,r){var n=e.l+t,a=8!=r.biff&&r.biff?2:4,s=e.read_shift(a),t=e.read_shift(a),r=e.read_shift(2),a=e.read_shift(2);return e.l=n,{s:{r:s,c:r},e:{r:t,c:a}}}function la(e,t,r,n){r=r&&5==r.biff;return(n=n||Rt(r?16:20)).write_shift(2,0),e.style?(n.write_shift(2,e.numFmtId||0),n.write_shift(2,65524)):(n.write_shift(2,e.numFmtId||0),n.write_shift(2,t<<4)),n.write_shift(4,0),n.write_shift(4,0),r||n.write_shift(4,0),n.write_shift(2,0),n}function ca(e,t,r){var n=$n(e);2==r.biff&&++e.l;e=(r=e).read_shift(1),e=1===r.read_shift(1)?e:1===e;return n.val=e,n.t=!0===e||!1===e?"b":"e",n}function fa(e,t,r,n,a,s){var i=Rt(8);return Yn(e,t,n,i),n=r,r=s,(s=(s=i)||Rt(2)).write_shift(1,+n),s.write_shift(1,"e"==r?1:0),i}function ha(e,t,r){return 0===t?"":zn(e,0,r)}function ua(e,t,r){var n,a=e.read_shift(2),a={fBuiltIn:1&a,fWantAdvise:a>>>1&1,fWantPict:a>>>2&1,fOle:a>>>3&1,fOleLink:a>>>4&1,cf:a>>>5&1023,fIcon:a>>>15&1};return 14849===r.sbcch&&(n=function(e,t,r){e.l+=4,t-=4;var n=e.l+t,t=Mn(e,0,r);if((r=e.read_shift(2))!==(n-=e.l))throw new Error("Malformed AddinUdf: padding = "+n+" != "+r);return e.l+=r,t}(e,t-2,r)),a.body=n||e.read_shift(t-2),"string"==typeof n&&(a.Name=n),a}var da=["_xlnm.Consolidate_Area","_xlnm.Auto_Open","_xlnm.Auto_Close","_xlnm.Extract","_xlnm.Database","_xlnm.Criteria","_xlnm.Print_Area","_xlnm.Print_Titles","_xlnm.Recorder","_xlnm.Data_Form","_xlnm.Auto_Activate","_xlnm.Auto_Deactivate","_xlnm.Sheet_Title","_xlnm._FilterDatabase"];function pa(e,t,r){var n=e.l+t,a=e.read_shift(2),s=e.read_shift(1),i=e.read_shift(1),o=e.read_shift(r&&2==r.biff?1:2),t=0;(!r||5<=r.biff)&&(5!=r.biff&&(e.l+=2),t=e.read_shift(2),5==r.biff&&(e.l+=2),e.l+=4);i=Un(e,i,r);32&a&&(i=da[i.charCodeAt(0)]);a=n-e.l;return r&&2==r.biff&&--a,{chKey:s,Name:i,itab:t,rgce:n==e.l||0===o?[]:function(e,t,r,n){var a,t=e.l+t,n=wo(e,n,r);t!==e.l&&(a=Eo(e,t-e.l,n,r));return[n,a]}(e,a,r,o)}}function ma(e,t,r){if(r.biff<8)return function(e,t){3==e[e.l+1]&&e[e.l]++;t=Mn(e,0,t);return 3==t.charCodeAt(0)?t.slice(1):t}(e,r);for(var n,a,s=[],t=e.l+t,i=e.read_shift(8>1;if(1&r[1].v)switch(7&n){case 1:n=500*(n>>3);break;case 2:n=(n>>3)/20;break;case 4:n=(n>>3)/2e3;break;case 6:n=(n>>3)/16;break;case 7:n=(n>>3)/64;break;default:throw"unknown NUMBER_18 encoding "+(7&n)}return r[1].v=n,r}},25:{n:"FORMULA19",f:function(e,t){var r=rs(e);return e.l+=t-14,r}},26:{n:"??"},27:{n:"??"},28:{n:"??"},29:{n:"??"},30:{n:"??"},31:{n:"??"},33:{n:"??"},37:{n:"NUMBER25",f:function(e,t){var r=ts(e),e=e.read_shift(4);return r[1].v=e>>6,r}},39:{n:"NUMBER27",f:ns},40:{n:"FORMULA28",f:function(e,t){var r=ns(e);return e.l+=t-10,r}},255:{n:"",f:It}},{to_workbook:function(e,t){switch(t.type){case"base64":return Ja(q(K.decode(e)),t);case"binary":return Ja(q(e),t);case"buffer":case"array":return Ja(e,t)}throw"Unsupported type "+t.type}});function Ja(n,e){if(!n)return n;var a=e||{};null!=fe&&null==a.dense&&(a.dense=fe);var s=a.dense?[]:{},i="Sheet1",o=0,l={},c=[i],f={s:{r:0,c:0},e:{r:0,c:0}},h=a.sheetRows||0;if(2==n[2])a.Enum=Ka;else if(26==n[2])a.Enum=Za;else{if(14!=n[2])throw new Error("Unrecognized LOTUS BOF "+n[2]);a.Enum=Za,a.qpro=!0,n.l=0}return function(e,t,r){if(e){At(e,e.l||0);for(var n=r.Enum||Ka;e.lo&&(s["!ref"]=$t(f),l[i]=s,s=a.dense?[]:{},f={s:{r:0,c:0},e:{r:0,c:0}},o=e[3],i="Sheet"+(o+1),c.push(i)),0=h)break;a.dense?(s[e[0].r]||(s[e[0].r]=[]),s[e[0].r][e[0].c]=e[1]):s[jt(e[0])]=e[1],f.e.c>>-s)+(-32>>-(32+s))),r}function ns(e,t){var r=ts(e),e=e.read_shift(8,"f");return r[1].v=e,r}var as,ss,is,os,ls,cs=(as=Le("t"),ss=Le("rPr"),is=/<(?:\w+:)?r>/g,os=/<\/(?:\w+:)?r>/,ls=/\r\n/g,function(e){return e.replace(is,"").split(os).map(fs).join("")});function fs(e){var t=[[],"",[]],r=e.match(as);if(!r)return"";t[1]=r[1];e=e.match(ss);return e&&function(e,t,r){var n={},a="",s=!1,i=e.match(W),o=0;if(i)for(;o!=i.length;++o){var l=me(i[o]);switch(l[0].replace(/\w*:/g,"")){case"":case"":n.shadow=1;break;case"":break;case"":case"":n.outline=1;break;case"":break;case"":case"":n.strike=1;break;case"":break;case"":case"":n.u=1;break;case"":break;case"":case"":n.b=1;break;case"":break;case"":case"":n.i=1;break;case"":break;case"":case"":break;case"":s=!1;break;default:if(47!==l[0].charCodeAt(1)&&!s)throw new Error("Unrecognized rich format "+l[0])}}e=[],n.u&&e.push("text-decoration: underline;"),n.uval&&e.push("text-underline-style:"+n.uval+";"),n.sz&&e.push("font-size:"+n.sz+"pt;"),n.outline&&e.push("text-effect: outline;"),n.shadow&&e.push("text-shadow: auto;"),t.push(''),n.b&&(t.push(""),r.push("")),n.i&&(t.push(""),r.push("")),n.strike&&(t.push(""),r.push("")),"superscript"==a?a="sup":"subscript"==a&&(a="sub"),""!=a&&(t.push("<"+a+">"),r.push("")),r.push("")}(e[1],t[0],t[2]),t[0].join("")+t[1].replace(ls,"
    ")+t[2].join("")}var hs=/<(?:\w+:)?t[^>]*>([^<]*)<\/(?:\w+:)?t>/g,us=/<(?:\w+:)?r>/,ds=/<(?:\w+:)?rPh.*?>([\s\S]*?)<\/(?:\w+:)?rPh>/g;function ps(e,t){var r=!t||t.cellHTML,t={};return e?(e.match(/^\s*<(?:\w+:)?t[^>]*>/)?(t.t=we(Oe(e.slice(e.indexOf(">")+1).split(/<\/(?:\w+:)?t>/)[0]||"")),t.r=Oe(e),r&&(t.h=Te(t.t))):e.match(us)&&(t.r=Oe(e),t.t=we(Oe((e.replace(ds,"").match(hs)||[]).join("").replace(W,""))),r&&(t.h=cs(t.r))),t):null}var ms=/<(?:\w+:)?sst([^>]*)>([\s\S]*)<\/(?:\w+:)?sst>/,gs=/<(?:\w+:)?(?:si|sstItem)>/g,bs=/<\/(?:\w+:)?(?:si|sstItem)>/;Gr.SST="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings";var vs=/^\s|\s$|[\t\n\r]/;var Es=function(e,t){var r=!1;return null==t&&(r=!0,t=Rt(15+4*e.t.length)),t.write_shift(1,0),rr(e.t,t),r?t.slice(0,t.l):t};function ws(e){var t,r,n=Ft();Dt(n,"BrtBeginSst",(t=e,(r=r||Rt(8)).write_shift(4,t.Count),r.write_shift(4,t.Unique),r));for(var a=0;a>1,"utf16le"),e.l=t,r}function Ts(e,t){var r={},t=e.l+t;return e.l+=4,r.Salt=e.slice(e.l,e.l+16),e.l+=16,r.Verifier=e.slice(e.l,e.l+16),e.l+=16,e.read_shift(4),r.VerifierHash=e.slice(e.l,t),e.l=t,r}function ks(e){var t=_s(e);switch(t.Minor){case 2:return[t.Minor,function(e){if(36!=(63&e.read_shift(4)))throw new Error("EncryptionInfo mismatch");var t=e.read_shift(4),t=Bs(e,t),e=Ts(e,e.length-e.l);return{t:"Std",h:t,v:e}}(e)];case 3:return[t.Minor,function(){throw new Error("File is password-protected: ECMA-376 Extensible")}()];case 4:return[t.Minor,function(e){var r=["saltSize","blockSize","keyBits","hashSize","cipherAlgorithm","cipherChaining","hashAlgorithm","saltValue"];e.l+=4;var e=e.read_shift(e.length-e.l,"utf8"),n={};return e.replace(W,function(e){var t=me(e);switch(Y(t[0])){case"":break;case"":case"":break;case"":break;case">8,i[s]=Fs(As[0],t),--s,t=255&a,e=n[n.length-1],i[s]=Fs(e,t));0>8,i[--s]=Fs(n[s],t),t=255&a,i[--s]=Fs(n[s],t);for(r=(s=15)-n.length;0>8,i[s]=Fs(As[r],t),--r,t=255&a,i[--s]=Fs(n[s],t),--s,--r;return i});function Fs(e,t){return 255&((t=e^t)/2|128*t)}var Ds=function(e){var t=0,r=Os(e);return function(e){e=function(e,t,r,n,a){var s,i;for(a=a||t,n=n||Os(e),s=0;s!=t.length;++s)i=t[s],i=255&((i^=n[r])>>5|i<<3),a[s]=i,++r;return[a,r,n]}("",e,t,r);return t=e[1],e[0]}};function Ps(e,t,r){r=r||{};return r.Info=e.read_shift(2),e.l-=2,1===r.Info?r.Data=function(e){var t={},r=t.EncryptionVersionInfo=_s(e,4);if(1!=r.Major||1!=r.Minor)throw"unrecognized version code "+r.Major+" : "+r.Minor;return t.Salt=e.read_shift(16),t.EncryptedVerifier=e.read_shift(16),t.EncryptedVerifierHash=e.read_shift(16),t}(e):r.Data=function(e,t){var r={},n=r.EncryptionVersionInfo=_s(e,4);if(t-=4,2!=n.Minor)throw new Error("unrecognized minor version code: "+n.Minor);if(4]*)>[\S\s]*?<\/(?:\w+:)?numFmts>/,ai=/<(?:\w+:)?cellXfs([^>]*)>[\S\s]*?<\/(?:\w+:)?cellXfs>/,si=/<(?:\w+:)?fills([^>]*)>[\S\s]*?<\/(?:\w+:)?fills>/,ii=/<(?:\w+:)?fonts([^>]*)>[\S\s]*?<\/(?:\w+:)?fonts>/,oi=/<(?:\w+:)?borders([^>]*)>[\S\s]*?<\/(?:\w+:)?borders>/,function(e,t,r){var n,a,s,i,o,l={};return e&&((n=(e=e.replace(//gm,"").replace(//gm,"")).match(ni))&&function(e,t,r){t.NumberFmt=[];for(var n=de(ue._table),a=0;a":case"":case"":break;case"":break;default:if(r.WTF)throw new Error("unrecognized "+i[0]+" in numFmts")}}}(n,l,r),(n=e.match(ii))&&function(e,n,a,s){n.Fonts=[];var i={},o=!1;e[0].match(W).forEach(function(e){var t=me(e);switch(Y(t[0])){case"":case"":break;case"":break;case"":case"":n.Fonts.push(i),i={};break;case"":case"":break;case"":i.bold=1;break;case"":i.italic=1;break;case"":i.underline=1;break;case"":i.strike=1;break;case"":i.outline=1;break;case"":i.shadow=1;break;case"":i.condense=1;break;case"":i.extend=1;break;case"":case"":break;case"":case"":break;case"":case"":break;case"":case"":break;case"":case"":break;case"":case"":break;case"":o=!1;break;default:if(s&&s.WTF&&!o)throw new Error("unrecognized "+t[0]+" in fonts")}})}(n,l,t,r),(n=e.match(si))&&function(e,r,n){r.Fills=[];var a={},s=!1;e[0].match(W).forEach(function(e){var t=me(e);switch(Y(t[0])){case"":case"":break;case"":case"":a={},r.Fills.push(a);break;case"":case"":break;case"":r.Fills.push(a),a={};break;case"":t.patternType&&(a.patternType=t.patternType);break;case"":case"":break;case"":case"":break;case"":case"":break;case"":case"":break;case"":case"":break;case"":case"":break;case"":s=!1;break;default:if(n&&n.WTF&&!s)throw new Error("unrecognized "+t[0]+" in fills")}})}(n,l,r),(n=e.match(oi))&&function(e,r,n){r.Borders=[];var a={},s=!1;e[0].match(W).forEach(function(e){var t=me(e);switch(Y(t[0])){case"":case"":break;case"":case"":a={},t.diagonalUp&&(a.diagonalUp=t.diagonalUp),t.diagonalDown&&(a.diagonalDown=t.diagonalDown),r.Borders.push(a);break;case"":case"":break;case"":case"":case"":break;case"":case"":case"":break;case"":case"":case"":break;case"":case"":break;case"":case"":case"":break;case"":case"":case"":break;case"":case"":case"":break;case"":case"":case"":break;case"":case"":case"":break;case"":break;case"":case"":break;case"":case"":break;case"":s=!1;break;default:if(n&&n.WTF&&!s)throw new Error("unrecognized "+t[0]+" in borders")}})}(n,l,r),(n=e.match(ai))&&(n=n,s=r,o=!((a=l).CellXf=[]),n[0].match(W).forEach(function(e){var t=me(e),r=0;switch(Y(t[0])){case"":case"":case"":break;case"":for(delete(i=t)[0],r=0;r":break;case"":var n={};t.vertical&&(n.vertical=t.vertical),t.horizontal&&(n.horizontal=t.horizontal),null!=t.textRotation&&(n.textRotation=t.textRotation),t.indent&&(n.indent=t.indent),t.wrapText&&(n.wrapText=t.wrapText),i.alignment=n;break;case"":break;case"":case"":break;case"":case"":break;case"":o=!1;break;default:if(s&&s.WTF&&!o)throw new Error("unrecognized "+t[0]+" in cellXfs")}}))),l}),ci=Ge("styleSheet",null,{xmlns:Ye.main[0],"xmlns:vt":Ye.vt});function fi(e,t){if("undefined"!=typeof style_builder)return style_builder.toXml();var r,n,a,s,i=[z,ci];return e.SSF&&null!=(n=e.SSF,a=[""],[[5,8],[23,26],[41,44],[50,392]].forEach(function(e){for(var t=e[0];t<=e[1];++t)null!=n[t]&&(a[a.length]=Ge("numFmt",null,{numFmtId:t,formatCode:ye(n[t])}))}),r=1===a.length?"":(a[a.length]="",a[0]=Ge("numFmts",null,{count:a.length-2}).replace("/>",">"),a.join("")))&&(i[i.length]=r),i[i.length]='',i[i.length]='',i[i.length]='',i[i.length]='',t=t.cellXfs,(s=[])[s.length]="",t.forEach(function(e){s[s.length]=Ge("xf",null,e)}),s[s.length]="",(r=2===s.length?"":(s[0]=Ge("cellXfs",null,{count:s.length-2}).replace("/>",">"),s.join("")))&&(i[i.length]=r),i[i.length]='',i[i.length]='',i[i.length]='',2",i[1]=i[1].replace("/>",">")),i.join("")}function hi(e,t){var r;(t=t||Rt(153)).write_shift(2,20*e.sz),r=e,n=(n=t)||Rt(2),r=(r.italic?2:0)|(r.strike?8:0)|(r.outline?16:0)|(r.shadow?32:0)|(r.condense?64:0)|(r.extend?128:0),n.write_shift(1,r),n.write_shift(1,0),t.write_shift(2,e.bold?700:400);var n=0;"superscript"==e.vertAlign?n=1:"subscript"==e.vertAlign&&(n=2),t.write_shift(2,n),t.write_shift(1,e.underline||0),t.write_shift(1,e.family||0),t.write_shift(1,e.charset||0),t.write_shift(1,0),yr(e.color,t);n=0;return"major"==e.scheme&&(n=1),"minor"==e.scheme&&(n=2),t.write_shift(1,n),rr(e.name,t),t.length>t.l?t.slice(0,t.l):t}Gr.STY="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles";var ui=S(["none","solid","mediumGray","darkGray","lightGray","darkHorizontal","darkVertical","darkDown","darkUp","darkGrid","darkTrellis","lightHorizontal","lightVertical","lightDown","lightUp","lightGrid","lightTrellis","gray125","gray0625"]),di=It;function pi(e,t){t=t||Rt(84);e=ui[e.patternType];null==e&&(e=40),t.write_shift(4,e);var r=0;if(40!=e)for(yr({auto:1},t),yr({auto:1},t);r<12;++r)t.write_shift(4,0);else{for(;r<4;++r)t.write_shift(4,0);for(;r<12;++r)t.write_shift(4,0)}return t.length>t.l?t.slice(0,t.l):t}function mi(e,t,r){return(r=r||Rt(16)).write_shift(2,t||0),r.write_shift(2,e.numFmtId||0),r.write_shift(2,0),r.write_shift(2,0),r.write_shift(2,0),r.write_shift(1,0),r.write_shift(1,0),r.write_shift(1,0),r.write_shift(1,0),r.write_shift(1,0),r.write_shift(1,0),r}function gi(e,t){return(t=t||Rt(10)).write_shift(1,0),t.write_shift(1,0),t.write_shift(4,0),t.write_shift(4,0),t}var bi=It;function vi(s,i){var r;i&&(r=0,[[5,8],[23,26],[41,44],[50,392]].forEach(function(e){for(var t=e[0];t<=e[1];++t)null!=i[t]&&++r}),0!=r&&(Dt(s,"BrtBeginFmts",er(r)),[[5,8],[23,26],[41,44],[50,392]].forEach(function(e){for(var t,r,n,a=e[0];a<=e[1];++a)null!=i[a]&&Dt(s,"BrtFmt",(r=i[t=a],(n=(n=void 0)||Rt(6+4*r.length)).write_shift(2,t),rr(r,n),r=n.length>n.l?n.slice(0,n.l):n,null==n.l&&(n.l=n.length),r))}),Dt(s,"BrtEndFmts")))}function Ei(e){var t;Dt(e,"BrtBeginBorders",er(1)),Dt(e,"BrtBorder",((t=t||Rt(51)).write_shift(1,0),gi(0,t),gi(0,t),gi(0,t),gi(0,t),gi(0,t),t.length>t.l?t.slice(0,t.l):t)),Dt(e,"BrtEndBorders")}function wi(e){var t,r;Dt(e,"BrtBeginStyles",er(1)),Dt(e,"BrtStyle",(t={xfId:0,builtinId:0,name:"Normal"},(r=r||Rt(52)).write_shift(4,t.xfId),r.write_shift(2,1),r.write_shift(1,+t.builtinId),r.write_shift(1,0),hr(t.name||"",r),r.length>r.l?r.slice(0,r.l):r)),Dt(e,"BrtEndStyles")}function Si(e){var t,r,n,a;Dt(e,"BrtBeginTableStyles",(t=0,r="TableStyleMedium9",n="PivotStyleMedium4",(a=Rt(2052)).write_shift(4,t),hr(r,a),hr(n,a),a.length>a.l?a.slice(0,a.l):a)),Dt(e,"BrtEndTableStyles")}function _i(e,t){var r,n=Ft();return Dt(n,"BrtBeginStyleSheet"),vi(n,e.SSF),Dt(e=n,"BrtBeginFonts",er(1)),Dt(e,"BrtFont",hi({sz:12,color:{theme:1},name:"Calibri",family:2,scheme:"minor"})),Dt(e,"BrtEndFonts"),Dt(e=n,"BrtBeginFills",er(2)),Dt(e,"BrtFill",pi({patternType:"none"})),Dt(e,"BrtFill",pi({patternType:"gray125"})),Dt(e,"BrtEndFills"),Ei(n),Dt(e=n,"BrtBeginCellStyleXFs",er(1)),Dt(e,"BrtXF",mi({numFmtId:0,fontId:0,fillId:0,borderId:0},65535)),Dt(e,"BrtEndCellStyleXFs"),r=n,t=t.cellXfs,Dt(r,"BrtBeginCellXFs",er(t.length)),t.forEach(function(e){Dt(r,"BrtXF",mi(e,0))}),Dt(r,"BrtEndCellXFs"),wi(n),Dt(t=n,"BrtBeginDXFs",er(0)),Dt(t,"BrtEndDXFs"),Si(n),Dt(n,"BrtEndStyleSheet"),n.end()}function yi(e,r,n){r.themeElements.clrScheme=[];var a={};(e[0].match(W)||[]).forEach(function(e){var t=me(e);switch(t[0]){case"":break;case"":case"":case"":case"":case"":case"":case"":case"":case"":case"":case"":case"":case"":case"":case"":case"":case"":case"":case"":case"":case"":case"":case"":case"":"/"===t[0].charAt(1)?(r.themeElements.clrScheme.push(a),a={}):a.name=t[0].slice(3,t[0].length-1);break;default:if(n&&n.WTF)throw new Error("Unrecognized "+t[0]+" in clrScheme")}})}function Ci(){}function Bi(){}Gr.THEME="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme";var Ti=/]*)>[\s\S]*<\/a:clrScheme>/,ki=/]*)>[\s\S]*<\/a:fontScheme>/,xi=/]*)>[\s\S]*<\/a:fmtScheme>/;var Ai=/]*)>[\s\S]*<\/a:themeElements>/;function Ii(e,t){if(!e||0===e.length)return Ii(Ri());var r,n,a,s,i={};if(!(e=e.match(Ai)))throw new Error("themeElements not found in theme");return r=e[0],a=t,(n=i).themeElements={},[["clrScheme",Ti,yi],["fontScheme",ki,Ci],["fmtScheme",xi,Bi]].forEach(function(e){if(!(s=r.match(e[1])))throw new Error(e[0]+" not found in themeElements");e[2](s,n,a)}),i}function Ri(e,t){if(t&&t.themeXLSX)return t.themeXLSX;t=[z];return t[t.length]='',t[t.length]="",t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]="",t[t.length]='',t[t.length]="",t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]="",t[t.length]="",t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]="",t[t.length]="",t[t.length]='',t[t.length]="",t[t.length]='',t[t.length]='',t[t.length]="",t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]="",t[t.length]='',t[t.length]="",t[t.length]='',t[t.length]="",t[t.length]='',t[t.length]='',t[t.length]="",t[t.length]='',t[t.length]="",t[t.length]="",t[t.length]="",t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]="",t[t.length]="",t[t.length]="",t[t.length]="",t[t.length]='',t[t.length]="",t[t.length]="",t[t.length]="",t[t.length]="",t[t.length]='',t[t.length]="",t[t.length]="",t[t.length]="",t[t.length]="",t[t.length]='',t[t.length]="",t[t.length]='',t[t.length]='',t[t.length]="",t[t.length]="",t[t.length]="",t[t.length]='',t[t.length]='',t[t.length]="",t[t.length]='',t[t.length]='',t[t.length]='',t[t.length]="",t[t.length]='',t[t.length]="",t[t.length]='',t[t.length]="",t[t.length]='',t[t.length]='',t[t.length]="",t[t.length]='',t[t.length]="",t[t.length]="",t[t.length]="",t[t.length]="",t[t.length]="",t[t.length]="",t[t.length]='',t[t.length]="",t[t.length]="",t[t.length]='',t[t.length]="",t[t.length]="",t[t.length]="",t[t.length]="",t.join("")}function Oi(e){var t={};switch(t.xclrType=e.read_shift(2),t.nTintShade=e.read_shift(2),t.xclrType){case 0:e.l+=4;break;case 1:t.xclrValue=It(e,4);break;case 2:t.xclrValue=jn(e);break;case 3:t.xclrValue=e.read_shift(4);break;case 4:e.l+=4}return e.l+=8,t}function Fi(e){var t=e.read_shift(2),r=e.read_shift(2)-4,n=[t];switch(t){case 4:case 5:case 7:case 8:case 9:case 10:case 11:case 13:n[1]=Oi(e);break;case 6:n[1]=It(e,r);break;case 14:case 15:n[1]=e.read_shift(1==r?1:2);break;default:throw new Error("Unrecognized ExtProp type: "+t+" "+r)}return n}Gr.IMG="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",Gr.DRAW="http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing";var Di=1024;function Pi(e,t,r,n,a){for(var s,i,o=0;o!=t.length;++o){var l=t[o],c=(s=M(e,l.replace(/^\//,""),!0),i=a,(".bin"===l.slice(-4)?function(e,n){var a=[],s=[],i={},o=!1;return Ot(e,function(e,t,r){switch(r){case 632:s.push(e);break;case 635:i=e;break;case 637:i.t=e.t,i.h=e.h,i.r=e.r;break;case 636:if(i.author=s[i.iauthor],delete i.iauthor,n.sheetRows&&n.sheetRows<=i.rfx.r)break;i.t||(i.t=""),delete i.rfx,a.push(i);break;case 3072:break;case 35:o=!0;break;case 36:o=!1;break;case 37:case 38:break;default:if(!(0<(t||"").indexOf("Begin"))&&!(0<(t||"").indexOf("End"))&&(!o||n.WTF))throw new Error("Unexpected record "+r+" "+t)}}),a}:function(e,n){if(e.match(/<(?:\w+:)?comments *\/>/))return[];var a=[],s=[],t=e.match(/<(?:\w+:)?authors>([\s\S]*)<\/(?:\w+:)?authors>/);t&&t[1]&&t[1].split(/<\/\w*:?author>/).forEach(function(e){""===e||""===e.trim()||(e=e.match(/<(?:\w+:)?author[^>]*>(.*)/))&&a.push(e[1])});e=e.match(/<(?:\w+:)?commentList>([\s\S]*)<\/(?:\w+:)?commentList>/);e&&e[1]&&e[1].split(/<\/\w*:?comment>/).forEach(function(e){var t,r;""===e||""===e.trim()||(t=e.match(/<(?:\w+:)?comment[^>]*>/))&&(t={author:(r=me(t[0])).authorId&&a[r.authorId]||"sheetjsghost",ref:r.ref,guid:r.guid},r=Xt(r.ref),n.sheetRows&&n.sheetRows<=r.r||(e=!!(e=e.match(/<(?:\w+:)?text>([\s\S]*)<\/(?:\w+:)?text>/))&&!!e[1]&&ps(e[1])||{r:"",t:"",h:""},t.r=e.r,""==e.r&&(e.t=e.h=""),t.t=e.t.replace(/\r\n/g,"\n").replace(/\r/g,"\n"),n.cellHTML&&(t.h=e.h),s.push(t)))});return s})(s,i));if(c&&c.length)for(var f=de(r),h=0;h!=f.length;++h){var u=f[h],d=n[u];d&&d[l]&&function(n,e){var a,s=Array.isArray(n);e.forEach(function(e){var t=Xt(e.ref);(a=s?(n[t.r]||(n[t.r]=[]),n[t.r][t.c]):n[e.ref])||(a={},s?n[t.r][t.c]=a:n[e.ref]=a,(r=Yt(n["!ref"]||"BDWGO1000001:A1")).s.r>t.r&&(r.s.r=t.r),r.e.rt.c&&(r.s.c=t.c),r.e.c>14&1,t>>15&1]}function Qi(e,t,r){var n=2;if(r){if(2<=r.biff&&r.biff<=5)return Ji(e);12==r.biff&&(n=4)}var a=e.read_shift(n),r=e.read_shift(n),n=Zi(e,2),e=Zi(e,2);return{s:{r:a,c:n[0],cRel:n[1],rRel:n[2]},e:{r:r,c:e[0],cRel:e[1],rRel:e[2]}}}function Ji(e){var t=Zi(e,2),r=Zi(e,2),n=e.read_shift(1),e=e.read_shift(1);return{s:{r:t[0],c:n,cRel:t[1],rRel:t[2]},e:{r:r[0],c:e,cRel:r[1],rRel:r[2]}}}function qi(e,t,r){if(r&&2<=r.biff&&r.biff<=5)return a=Zi(n=e,2),n=n.read_shift(1),{r:a[0],c:n,cRel:a[1],rRel:a[2]};var n,a,r=e.read_shift(r&&12==r.biff?4:2),e=Zi(e,2);return{r:r,c:e[0],cRel:e[1],rRel:e[2]}}function eo(e,t,r){r=r&&r.biff?r.biff:8;if(2<=r&&r<=5)return function(e){var t=e.read_shift(2),r=e.read_shift(1),n=(32768&t)>>15,e=(16384&t)>>14;t&=16383,1==n&&8192<=t&&(t-=16384);1==e&&128<=r&&(r-=256);return{r:t,c:r,cRel:e,rRel:n}}(e);var n=e.read_shift(12<=r?4:2),a=e.read_shift(2),r=(16384&a)>>14,e=(32768&a)>>15;if(a&=16383,1==e)for(;524287>15,rRel:n>>15})];var n}function ao(e){return e.l+=6,[]}var so=no,io=ao,oo=ao,lo=no;function co(e){return e.l+=2,[Pn(e),1&e.read_shift(2)]}var r=no,y=co,fo=ao,ho=no,uo=no,po=["Data","All","Headers","??","?Data2","??","?DataHeaders","??","Totals","??","??","??","?DataTotals","??","??","??","?Current"];var mo={1:{n:"PtgExp",f:function(e,t,r){return e.l++,r&&12==r.biff?[e.read_shift(4,"i"),0]:[e.read_shift(2),e.read_shift(r&&2==r.biff?1:2)]}},2:{n:"PtgTbl",f:It},3:{n:"PtgAdd",f:Ki},4:{n:"PtgSub",f:Ki},5:{n:"PtgMul",f:Ki},6:{n:"PtgDiv",f:Ki},7:{n:"PtgPower",f:Ki},8:{n:"PtgConcat",f:Ki},9:{n:"PtgLt",f:Ki},10:{n:"PtgLe",f:Ki},11:{n:"PtgEq",f:Ki},12:{n:"PtgGe",f:Ki},13:{n:"PtgGt",f:Ki},14:{n:"PtgNe",f:Ki},15:{n:"PtgIsect",f:Ki},16:{n:"PtgUnion",f:Ki},17:{n:"PtgRange",f:Ki},18:{n:"PtgUplus",f:Ki},19:{n:"PtgUminus",f:Ki},20:{n:"PtgPercent",f:Ki},21:{n:"PtgParen",f:Ki},22:{n:"PtgMissArg",f:Ki},23:{n:"PtgStr",f:function(e,t,r){return e.l++,Mn(e,0,r)}},26:{n:"PtgSheet",f:function(e,t,r){return e.l+=5,e.l+=2,e.l+=2==r.biff?1:4,["PTGSHEET"]}},27:{n:"PtgEndSheet",f:function(e,t,r){return e.l+=2==r.biff?4:5,["PTGENDSHEET"]}},28:{n:"PtgErr",f:function(e){return e.l++,Sr[e.read_shift(1)]}},29:{n:"PtgBool",f:function(e){return e.l++,0!==e.read_shift(1)}},30:{n:"PtgInt",f:function(e){return e.l++,e.read_shift(2)}},31:{n:"PtgNum",f:function(e){return e.l++,Er(e)}},32:{n:"PtgArray",f:function(e,t,r){var n=(96&e[e.l++])>>5;return e.l+=2==r.biff?6:12==r.biff?14:7,[n]}},33:{n:"PtgFunc",f:function(e,t,r){var n=(96&e[e.l])>>5;return e.l+=1,r=e.read_shift(r&&r.biff<=3?1:2),[Oo[r],Ro[r],n]}},34:{n:"PtgFuncVar",f:function(e,t,r){var n=e[e.l++],a=e.read_shift(1),s=r&&r.biff<=3?[88==n?-1:0,e.read_shift(1)]:[(s=e)[s.l+1]>>7,32767&s.read_shift(2)];return[a,(0===s[0]?Ro:Io)[s[1]]]}},35:{n:"PtgName",f:function(e,t,r){var n=e.read_shift(1)>>>5&3,a=!r||8<=r.biff?4:2,a=e.read_shift(a);switch(r.biff){case 2:e.l+=5;break;case 3:case 4:e.l+=8;break;case 5:e.l+=12}return[n,0,a]}},36:{n:"PtgRef",f:function(e,t,r){var n=(96&e[e.l])>>5;return e.l+=1,[n,qi(e,0,r)]}},37:{n:"PtgArea",f:function(e,t,r){return[(96&e[e.l++])>>5,Qi(e,2<=r.biff&&r.biff,r)]}},38:{n:"PtgMemArea",f:function(e,t,r){var n=e.read_shift(1)>>>5&3;return e.l+=r&&2==r.biff?3:4,[n,e.read_shift(r&&2==r.biff?1:2)]}},39:{n:"PtgMemErr",f:It},40:{n:"PtgMemNoMem",f:It},41:{n:"PtgMemFunc",f:function(e,t,r){return[e.read_shift(1)>>>5&3,e.read_shift(r&&2==r.biff?1:2)]}},42:{n:"PtgRefErr",f:function(e,t,r){var n=e.read_shift(1)>>>5&3;return e.l+=4,r.biff<8&&e.l--,12==r.biff&&(e.l+=2),[n]}},43:{n:"PtgAreaErr",f:function(e,t,r){var n=(96&e[e.l++])>>5;return e.l+=r&&8>5;return e.l+=1,[n,eo(e,0,r)]}},45:{n:"PtgAreaN",f:function(e,t,r){return[(96&e[e.l++])>>5,function(e,t){if(t.biff<8)return Ji(e);var r=e.read_shift(12==t.biff?4:2),n=e.read_shift(12==t.biff?4:2),t=Zi(e,2),e=Zi(e,2);return{s:{r:r,c:t[0],cRel:t[1],rRel:t[2]},e:{r:n,c:e[0],cRel:e[1],rRel:e[2]}}}(e,r)]}},46:{n:"PtgMemAreaN",f:function(e){return[e.read_shift(1)>>>5&3,e.read_shift(2)]}},47:{n:"PtgMemNoMemN",f:function(e){return[e.read_shift(1)>>>5&3,e.read_shift(2)]}},57:{n:"PtgNameX",f:function(e,t,r){return 5==r.biff?function(e){var t=e.read_shift(1)>>>5&3,r=e.read_shift(2,"i");e.l+=8;var n=e.read_shift(2);return e.l+=12,[t,r,n]}(e):[e.read_shift(1)>>>5&3,e.read_shift(2),e.read_shift(4)]}},58:{n:"PtgRef3d",f:function(e,t,r){var n=(96&e[e.l])>>5;e.l+=1;var a=e.read_shift(2);return r&&5==r.biff&&(e.l+=12),[n,a,qi(e,0,r)]}},59:{n:"PtgArea3d",f:function(e,t,r){var n=(96&e[e.l++])>>5,a=e.read_shift(2,"i");if(r)switch(r.biff){case 5:e.l+=12,0;break;case 12:0}return[n,a,Qi(e,0,r)]}},60:{n:"PtgRefErr3d",f:function(e,t,r){var n=(96&e[e.l++])>>5,a=e.read_shift(2),s=4;if(r)switch(r.biff){case 5:s=15;break;case 12:s=6}return e.l+=s,[n,a]}},61:{n:"PtgAreaErr3d",f:function(e,t,r){var n=(96&e[e.l++])>>5,a=e.read_shift(2),s=8;if(r)switch(r.biff){case 5:e.l+=12,s=6;break;case 12:s=12}return e.l+=s,[n,a]}},255:{}},go={64:32,96:32,65:33,97:33,66:34,98:34,67:35,99:35,68:36,100:36,69:37,101:37,70:38,102:38,71:39,103:39,72:40,104:40,73:41,105:41,74:42,106:42,75:43,107:43,76:44,108:44,77:45,109:45,78:46,110:46,79:47,111:47,88:34,120:34,89:57,121:57,90:58,122:58,91:59,123:59,92:60,124:60,93:61,125:61};!function(){for(var e in go)mo[e]=mo[go[e]]}();var bo={1:{n:"PtgElfLel",f:co},2:{n:"PtgElfRw",f:ho},3:{n:"PtgElfCol",f:so},6:{n:"PtgElfRwV",f:uo},7:{n:"PtgElfColV",f:lo},10:{n:"PtgElfRadical",f:r},11:{n:"PtgElfRadicalS",f:fo},13:{n:"PtgElfColS",f:io},15:{n:"PtgElfColSV",f:oo},16:{n:"PtgElfRadicalLel",f:y},25:{n:"PtgList",f:function(e){e.l+=2;var t=e.read_shift(2),r=e.read_shift(2),n=e.read_shift(4),a=e.read_shift(2),e=e.read_shift(2);return{ixti:t,coltype:3&r,rt:po[r>>2&31],idx:n,c:a,C:e}}},29:{n:"PtgSxName",f:function(e){return e.l+=2,[e.read_shift(4)]}},255:{}},vo={0:{n:"PtgAttrNoop",f:function(e){return e.l+=4,[0,0]}},1:{n:"PtgAttrSemi",f:function(e,t,r){var n=255&e[e.l+1]?1:0;return e.l+=r&&2==r.biff?3:4,[n]}},2:{n:"PtgAttrIf",f:function(e,t,r){var n=255&e[e.l+1]?1:0;return e.l+=2,[n,e.read_shift(r&&2==r.biff?1:2)]}},4:{n:"PtgAttrChoose",f:function(e,t,r){e.l+=2;for(var n=e.read_shift(r&&2==r.biff?1:2),a=[],s=0;s<=n;++s)a.push(e.read_shift(r&&2==r.biff?1:2));return a}},8:{n:"PtgAttrGoto",f:function(e,t,r){var n=255&e[e.l+1]?1:0;return e.l+=2,[n,e.read_shift(r&&2==r.biff?1:2)]}},16:{n:"PtgAttrSum",f:function(e,t,r){e.l+=r&&2==r.biff?3:4}},32:{n:"PtgAttrBaxcel",f:function(e){var t=1&e[e.l+1];return e.l+=4,[t,1]}},64:{n:"PtgAttrSpace",f:function(e){return e.read_shift(2),to(e)}},65:{n:"PtgAttrSpaceSemi",f:function(e){return e.read_shift(2),to(e)}},128:{n:"PtgAttrIfError",f:function(e){var t=255&e[e.l+1]?1:0;return e.l+=2,[t,e.read_shift(2)]}},255:{}};function Eo(e,t,r,n){if(n.biff<8)return It(e,t);for(var a=e.l+t,s=[],i=0;i!==r.length;++i)switch(r[i][0]){case"PtgArray":r[i][1]=ro(e,0,n),s.push(r[i][1]);break;case"PtgMemArea":r[i][2]=function(e,t){for(var r=e.read_shift(12==t.biff?4:2),n=[],a=0;a!=r;++a)n.push((12==t.biff?br:Zn)(e,8));return n}(e,(r[i][1],n)),s.push(r[i][2]);break;case"PtgExp":n&&12==n.biff&&(r[i][1][1]=e.read_shift(4),s.push(r[i][1]));break;case"PtgList":case"PtgElfRadicalS":case"PtgElfColS":case"PtgElfColSV":throw"Unsupported "+r[i][0]}return 0!==(t=a-e.l)&&s.push(It(e,t)),s}function wo(e,t,r){for(var n,a,s=e.l+t,i=[];s!=e.l;)t=s-e.l,a=e[e.l],n=mo[a],24!==a&&25!==a||(n=(24===a?bo:vo)[e[e.l+1]]),n&&n.f?i.push([n.n,n.f(e,t,r)]):It(e,t);return i}vo[33]=vo[32];var So={PtgAdd:"+",PtgConcat:"&",PtgDiv:"/",PtgEq:"=",PtgGe:">=",PtgGt:">",PtgLe:"<=",PtgLt:"<",PtgMul:"*",PtgNe:"<>",PtgPower:"^",PtgSub:"-"};function _o(e,t,r){return function(e,t){if(!(e||t&&t.biff<=5&&2<=t.biff))throw new Error("empty sheet name");return-1s[0].e.c||i.rs[0].e.r)){f.push(yo(s[1],0,k,n,a)),x=!0;break}x||f.push(v[1])}break;case"PtgArray":f.push("{"+function(e){for(var t=[],r=0;r>3&1,tt:s[1]}}function Bo(e,t,r){var n=e.read_shift(4),a=wo(e,n,r),n=e.read_shift(4);return[a,0/g,jo=/<(?:\w+:)?sheetData>([\s\S]*)<\/(?:\w+:)?sheetData>/,Go=/<(?:\w:)?hyperlink [^>]*>/gm,$o=/"(\w*:\w*)"/,Yo=/<(?:\w:)?col\b[^>]*[\/]?>/g,Ko=/<(?:\w:)?autoFilter[^>]*([\/]|>([\s\S]*)<\/(?:\w:)?autoFilter)>/g,Zo=/<(?:\w:)?pageMargins[^>]*\/>/g,Qo=/<(?:\w:)?sheetPr\b(?:[^>a-z][^>]*)?\/>/,Jo=/<(?:\w:)?sheetViews[^>]*(?:[\/]|>([\s\S]*)<\/(?:\w:)?sheetViews)>/;function qo(e,t,r,n,a,s,i){if(!e)return e;null!=fe&&null==t.dense&&(t.dense=fe);var o=t.dense?[]:{},l={s:{r:2e6,c:2e6},e:{r:0,c:0}},c="",f="",h=e.match(jo);h?(c=e.slice(0,h.index),f=e.slice(h.index+h[0].length)):c=f=e;e=c.match(Qo);e&&el(e[0],0,a,r);var u=(c.match(/<(?:\w*:)?dimension/)||{index:-1}).index;0=l.s.c&&l.e.r>=l.s.r&&(o["!ref"]=$t(l)),0l.e.r&&(f.e.r=l.e.r),f.e.rl.e.c&&(f.e.c=l.e.c),f.e.ca-z][^>]*)?\/>/;var rl,nl,al,sl,il,ol,ll,cl=(rl=/<(?:\w+:)?c[ >]/,nl=/<\/(?:\w+:)?row>/,al=/r=["']([^"']*)["']/,sl=/<(?:\w+:)?is>([\S\s]*?)<\/(?:\w+:)?is>/,il=/ref=["']([^"']*)["']/,ol=Le("v"),ll=Le("f"),function(e,t,r,n,a,s){for(var i,o,l,c,f,h,u=0,d="",p=[],m=[],g=0,b=0,v="",E=0,w=0,S=0,_=0,y=Array.isArray(s.CellXf),C=[],B=[],T=Array.isArray(t),k=[],x={},A=!1,I=e.split(nl),R=0,O=I.length;R!=O;++R){var F=(d=I[R].trim()).length;if(0!==F){for(u=0;uE-1&&(n.s.r=E-1),n.e.r":"")+d,null!=m&&2===m.length){for(g=0,v=m[1],b=0;b!=v.length&&!((i=v.charCodeAt(b)-64)<1||26]*\/>/))&&B[(f=me(m[0])).si]&&(o.f=$i(B[f.si][1],B[f.si][2],l.r));for(var D=Xt(l.r),b=0;b=C[b][0].s.r&&D.r<=C[b][0].e.r&&D.c>=C[b][0].s.c&&D.c<=C[b][0].e.c&&(o.F=C[b][1])}if(null==l.t&&void 0===o.v)if(o.f||o.F)o.v=0,o.t="n";else{if(!r.sheetStubs)continue;o.t="z"}else o.t=l.t||"n";switch(n.s.c>w&&(n.s.c=w),n.e.c"],n=0;n!=e.length;++n)(t=e[n])&&(r[r.length]=Ge("col",null,Ho(n,t)));return r[r.length]="",r.join("")}(l["!cols"])),s[o=s.length]="",l["!links"]=[],null!=l["!ref"]&&0<(a=fl(l,t)).length&&(s[s.length]=a),s.length>o+1&&(s[s.length]="",s[o]=s[o].replace("/>",">")),null!=l["!protect"]&&(s[s.length]=(c=l["!protect"],f={sheet:1},["objects","scenarios","selectLockedCells","selectUnlockedCells"].forEach(function(e){null!=c[e]&&c[e]&&(f[e]="1")}),["formatColumns","formatRows","formatCells","insertColumns","insertRows","insertHyperlinks","deleteColumns","deleteRows","sort","autoFilter","pivotTables"].forEach(function(e){null==c[e]||c[e]||(f[e]="0")}),c.password&&(f.password=xs(c.password).toString(16).toUpperCase()),Ge("sheetProtection",null,f))),null!=l["!autofilter"]&&(s[s.length]=function(e,t,r,n){var a="string"==typeof e.ref?e.ref:$t(e.ref);r.Workbook||(r.Workbook={}),r.Workbook.Names||(r.Workbook.Names=[]);var s=r.Workbook.Names;(e=Gt(a)).s.r==e.e.r&&(e.e.r=Gt(t["!ref"]).e.r,a=$t(e));for(var i=0;i',r=0;r!=e.length;++r)t+='';return t+""}(l["!merges"]));var p,m,g=-1;return 0",l["!links"].forEach(function(e){e[1].Target&&(m={ref:e[0]},"#"!=e[1].Target.charAt(0)&&(g=Qr(n,-1,ye(e[1].Target).replace(/#.*$/,""),Gr.HLINK),m["r:id"]="rId"+g),-1<(p=e[1].Target.indexOf("#"))&&(m.location=ye(e[1].Target.slice(p+1))),e[1].Tooltip&&(m.tooltip=ye(e[1].Tooltip)),s[s.length]=Ge("hyperlink",null,m))}),s[s.length]=""),delete l["!links"],null!=l["!margins"]&&(s[s.length]=(zo(o=l["!margins"]),Ge("pageMargins",null,o))),s[s.length]="",t&&!t.ignoreEC&&null!=t.ignoreEC||(s[s.length]=Xe("ignoredErrors",Ge("ignoredError",null,{numberStoredAsText:1,sqref:h}))),0",s[1]=s[1].replace("/>",">")),s.join("")}function dl(e,t,r,n){r=function(e,t,r){var n=Rt(145),a=(r["!rows"]||[])[e]||{};n.write_shift(4,e),n.write_shift(4,0);var s=320;a.hpx?s=20*Js(a.hpx):a.hpt&&(s=20*a.hpt),n.write_shift(2,s),n.write_shift(1,0),s=0,a.level&&(s|=a.level),a.hidden&&(s|=16),(a.hpx||a.hpt)&&(s|=32),n.write_shift(1,s),n.write_shift(1,0);var i=0,s=n.l;n.l+=4;for(var o={r:e,c:0},l=0;l<16;++l)if(!(t.s.c>l+1<<10||t.e.cn.l?n.slice(0,n.l):n}(n,r,t);(17u.l?u.slice(0,u.l):u))));case"n":return void(t.v==(0|t.v)&&-1e3[\s\S]*?<\/c:numCache>/gm)||[]).forEach(function(e){var t,r=(t=[],((e=e).match(/(.*?)<\/c:pt>/gm)||[]).forEach(function(e){e=e.match(/(.*)<\/c:v><\/c:pt>/);e&&(t[+e[1]]=+e[2])}),e=we((e.match(/([\s\S]*?)<\/c:formatCode>/)||["","General"])[1]),[t,e]);f.s.r=f.s.c=0,f.e.c=l,o=Vt(l),r[0].forEach(function(e,t){i[o+Ht(t)]={t:"n",v:e,z:r[1]},c=t}),f.e.ra.l?a.slice(0,a.l):a))}Dt(e,"BrtEndBundleShs")}function Hl(e,t){if(t.Workbook&&t.Workbook.Sheets){for(var r,n=t.Workbook.Sheets,a=0,s=-1,i=-1;ar.l?r.slice(0,r.l):r)),Dt(e,"BrtEndBookViews"))}}function zl(e,t){var r=Ft();return Dt(r,"BrtBeginBook"),Dt(r,"BrtFileVersion",function(e){e=e||Rt(127);for(var t=0;4!=t;++t)e.write_shift(4,0);return rr("SheetJS",e),rr(n.version,e),rr(n.version,e),rr("7262",e),e.length=e.l,e.length>e.l?e.slice(0,e.l):e}()),Dt(r,"BrtWbProp",function(e,t){t=t||Rt(72);var r=0;return e&&e.filterPrivacy&&(r|=8),t.write_shift(4,r),t.write_shift(4,0),cr(e&&e.CodeName||"ThisWorkbook",t),t.slice(0,t.l)}(e.Workbook&&e.Workbook.WBProps||null)),Hl(r,e),Ul(r,e),Dt(r,"BrtEndBook"),r.end()}function Vl(e,t,r){return(".bin"===t.slice(-4)?function(e,n){var a={AppVersion:{},WBProps:{},WBView:[],Sheets:[],CalcPr:{},xmlns:""},s=[],i=!1;(n=n||{}).biff=12;var o=[],l=[[]];return l.SheetNames=[],l.XTI=[],Ot(e,function(e,t,r){switch(r){case 156:l.SheetNames.push(e.name),a.Sheets.push(e);break;case 153:a.WBProps=e;break;case 39:null!=e.Sheet&&(n.SID=e.Sheet),e.Ref=yo(e.Ptg,0,null,l,n),delete n.SID,delete e.Ptg,o.push(e);break;case 1036:break;case 357:case 358:case 355:case 667:l[0].length?l.push([r,e]):l[0]=[r,e],l[l.length-1].XTI=[];break;case 362:0===l.length&&(l[0]=[],l[0].XTI=[]),l[l.length-1].XTI=l[l.length-1].XTI.concat(e),l.XTI=l.XTI.concat(e);break;case 361:break;case 3072:case 3073:case 2071:case 534:case 677:case 158:case 157:case 610:case 2050:case 155:case 548:case 676:case 128:case 665:case 2128:case 2125:case 549:case 2053:case 596:case 2076:case 2075:case 2082:case 397:case 154:case 1117:case 553:case 2091:break;case 35:s.push(t),i=!0;break;case 36:s.pop(),i=!1;break;case 37:s.push(t),i=!0;break;case 38:s.pop(),i=!1;break;case 16:break;default:if(!(0<(t||"").indexOf("Begin"))&&!(0<(t||"").indexOf("End"))&&(!i||n.WTF&&"BrtACBegin"!=s[s.length-1]&&"BrtFRTBegin"!=s[s.length-1]))throw new Error("Unexpected record "+r+" "+t)}},n),Fl(a),a.Names=o,a.supbooks=l,a}:function(n,a){if(!n)throw new Error("Could not find file");var s={AppVersion:{},WBProps:{},WBView:[],Sheets:[],CalcPr:{},Names:[],xmlns:""},i=!1,o="xmlns",l={},c=0;if(n.replace(W,function(e,t){var r=me(e);switch(Y(r[0])){case"":break;case"":case"":break;case"":break;case"":kl.forEach(function(e){if(null!=r[e[0]])switch(e[2]){case"bool":s.WBProps[e[0]]=Re(r[e[0]]);break;case"int":s.WBProps[e[0]]=parseInt(r[e[0]],10);break;default:s.WBProps[e[0]]=r[e[0]]}}),r.codeName&&(s.WBProps.CodeName=r.codeName);break;case"":case"":break;case"":case"":break;case"":delete r[0],s.WBView.push(r);break;case"":break;case"":case"":break;case"":break;case"":case"":case"":case"":break;case"":case"":i=!1;break;case"":l.Ref=we(Oe(n.slice(c,t))),s.Names.push(l);break;case"":break;case"":delete r[0],s.CalcPr=r;break;case"":case"":case"":case"":break;case"":case"":case"":break;case"":case"":case"":break;case"":break;case"":case"":case"":case"":case"":break;case"":i=!1;break;case"":i=!0;break;case"":i=!1;break;case"=n[0].s.r&&u.r<=n[0].e.r&&g>=n[0].s.c&&g<=n[0].e.c&&(d.F=$t(n[0]),k=!0)}!k&&3u.r&&(_.s.r=u.r),_.s.c>g&&(_.s.c=g),_.e.ru.r&&(_.s.r=u.r),_.s.c>g&&(_.s.c=g),_.e.r=e.s;)R[e.e--]={width:e.w/256,hidden:!!(1&e.flags)},F||(F=!0,Ks(e.w/256)),Zs(R[e.e+1]);break;case 161:S["!autofilter"]={ref:$t(e)};break;case 476:S["!margins"]=e;break;case 147:o.Sheets[s]||(o.Sheets[s]={}),e.name&&(o.Sheets[s].CodeName=e.name);break;case 137:o.Views||(o.Views=[{}]),o.Views[0]||(o.Views[0]={}),e.RTL&&(o.Views[0].RTL=!0);break;case 485:break;case 175:case 644:case 625:case 562:case 396:case 1112:case 1146:case 471:case 1050:case 649:case 1105:case 49:case 589:case 607:case 564:case 1055:case 168:case 174:case 1180:case 499:case 64:case 1053:case 550:case 171:case 167:case 1177:case 169:case 1181:case 551:case 552:case 661:case 639:case 478:case 151:case 537:case 477:case 536:case 1103:case 680:case 1104:case 1024:case 152:case 663:case 535:case 678:case 504:case 1043:case 428:case 170:case 3072:case 50:case 2070:case 1045:break;case 35:y=!0;break;case 36:y=!1;break;case 37:case 38:break;default:if(!(0<(t||"").indexOf("Begin"))&&!(0<(t||"").indexOf("End"))&&(!y||f.WTF))throw new Error("Unexpected record "+r+" "+t)}},f),delete f.supbooks,delete f["!row"],!S["!ref"]&&(_.s.r<2e6||h&&(0_.e.r&&(e.e.r=_.e.r),e.e.r_.e.c&&(e.e.c=_.e.c),e.e.c":case"":break;case"",s=0;s!=t.SheetNames.length;++s){var i={name:ye(t.SheetNames[s].slice(0,31))};if(i.sheetId=""+(s+1),i["r:id"]="rId"+(s+1),a[s])switch(a[s].Hidden){case 1:i.state="hidden";break;case 2:i.state="veryHidden"}r[r.length]=Ge("sheet",null,i)}return r[r.length]="",e&&(r[r.length]="",t.Workbook&&t.Workbook.Names&&t.Workbook.Names.forEach(function(e){var t={name:e.Name};e.Comment&&(t.comment=e.Comment),null!=e.Sheet&&(t.localSheetId=""+e.Sheet),e.Hidden&&(t.hidden="1"),e.Ref&&(r[r.length]=Ge("definedName",String(e.Ref).replace(//g,">"),t))}),r[r.length]=""),2",r[1]=r[1].replace("/>",">")),r.join("")})(e,r)}function Zl(e,t,r){return(".bin"===t.slice(-4)?ws:function(e,t){if(!t.bookSST)return"";var r=[z];r[r.length]=Ge("sst",null,{xmlns:Ye.main[0],count:e.Count,uniqueCount:e.Unique});for(var n,a,s=0;s!=e.length;++s)null!=e[s]&&(a="",(n=e[s]).r?a+=n.r:(a+=""),a+="",r[r.length]=a);return 2",r[1]=r[1].replace("/>",">")),r.join("")})(e,r)}function Ql(e,t,r){return(".bin"===t.slice(-4)?Mi:function(e){var r=[z,Ni],n=[];return r.push(""),e.forEach(function(e){e[1].forEach(function(e){e=ye(e.a);-1"+e+""))})}),r.push(""),r.push(""),e.forEach(function(t){t[1].forEach(function(e){r.push(''),r.push(Xe("t",null==e.t?"":ye(e.t))),r.push("")})}),r.push(""),2",r[1]=r[1].replace("/>",">")),r.join("")})(e,r)}var Jl=/([\w:]+)=((?:")([^"]*)(?:")|(?:')([^']*)(?:'))/g,ql=/([\w:]+)=((?:")(?:[^"]*)(?:")|(?:')(?:[^']*)(?:'))/,ec=function(e){return String.fromCharCode(e)};function tc(e,t){var r=e.split(/\s+/),n=[];if(t||(n[0]=r[0]),1===r.length)return n;var a,s,i,o=e.match(Jl);if(o)for(i=0;i!=o.length;++i)-1===(s=(a=o[i].match(ql))[1].indexOf(":"))?n[a[1]]=a[2].slice(1,a[2].length-1):n["xmlns:"===a[1].slice(0,6)?"xmlns"+a[1].slice(6):a[1].slice(s+1)]=a[2].slice(1,a[2].length-1);return n}function rc(e,t,r){if("z"!==e.t){if(!r||!1!==r.cellText)try{"e"===e.t?e.w=e.w||Sr[e.v]:"General"===t?"n"===e.t?(0|e.v)===e.v?e.w=ue._general_int(e.v):e.w=ue._general_num(e.v):e.w=ue._general(e.v):e.w=(n=t||"General",a=e.v,"General"===(n=se[n]||we(n))?ue._general(a):ue.format(n,a))}catch(e){if(r.WTF)throw e}var n,a;try{var s=se[t]||t||"General";r.cellNF&&(e.z=s),r.cellDates&&"n"==e.t&&ue.is_date(s)&&((s=ue.parse_date_code(e.v))&&(e.t="d",e.v=new Date(s.y,s.m-1,s.d,s.H,s.M,s.S,s.u)))}catch(e){if(r.WTF)throw e}}}function nc(e){if(Z&&Buffer.isBuffer(e))return e.toString("utf8");if("string"==typeof e)return e;if("undefined"!=typeof Uint8Array&&e instanceof Uint8Array)return Oe(o(p(e)));throw new Error("Bad input format: expected Buffer or string")}var ac=/<(\/?)([^\s?>!\/:]*:|)([^\s?>:\/]+)[^>]*>/gm;function sc(e,t){var r=t||{};ae(ue);var n=te(nc(e));"binary"!=r.type&&"array"!=r.type&&"base64"!=r.type||(n="undefined"!=typeof cptable?cptable.utils.decode(65001,ee(n)):Oe(n));var a,s=n.slice(0,1024).toLowerCase(),i=!1;if(-1==s.indexOf("'),S=0,_=0,y=0,C={s:{r:2e6,c:2e6},e:{r:0,c:0}},B={},T={},k="",x=0,A=[],I={},R={},O=0,F=[],D=[],P={},N=[],L=!1,M=[],U=[],H={},z=0,V=0,W={Sheets:[],WBProps:{date1904:!1}},X={};for(ac.lastIndex=0,n=n.replace(//gm,"");a=ac.exec(n);)switch(a[3]){case"Data":if(l[l.length-1][1])break;"/"===a[1]?function(e,t,r,n,a,s,i,o,l,c){var f="General",h=n.StyleID,u={};c=c||{};var d=[],p=0;for(void 0===h&&o&&(h=o.StyleID),void 0===h&&i&&(h=i.StyleID);void 0!==s[h]&&(s[h].nf&&(f=s[h].nf),s[h].Interior&&d.push(s[h].Interior),s[h].Parent);)h=s[h].Parent;switch(r.Type){case"Boolean":n.t="b",n.v=Re(e);break;case"String":n.t="s",n.r=xe(we(e)),n.v=-1=l[p][0].s.r&&a.r<=l[p][0].e.r&&a.c>=l[p][0].s.c&&a.c<=l[p][0].e.c&&(n.F=l[p][1]);c.cellStyles&&(d.forEach(function(e){!u.patternType&&e.patternType&&(u.patternType=e.patternType)}),n.s=u),void 0!==n.StyleID&&(n.ixfe=n.StyleID)}(n.slice(S,a.index),k,w,"Comment"==l[l.length-1][0]?P:v,{c:_,r:y},B,N[_],E,M,r):(k="",w=tc(a[0]),S=a.index+a[0].length);break;case"Cell":if("/"===a[1])if(0y)&&void 0!==v.v&&(r.dense?(g[y]||(g[y]=[]),g[y][_]=v):g[Vt(_)+Ht(y)]=v),v.HRef&&(v.l={Target:v.HRef},v.HRefScreenTip&&(v.l.Tooltip=v.HRefScreenTip),delete v.HRef,delete v.HRefScreenTip),(v.MergeAcross||v.MergeDown)&&(z=_+(0|parseInt(v.MergeAcross,10)),V=y+(0|parseInt(v.MergeDown,10)),A.push({s:{c:_,r:y},e:{c:z,r:V}})),r.sheetStubs)if(v.MergeAcross||v.MergeDown){for(var j=_;j<=z;++j)for(var G=y;G<=V;++G)(_C.e.c&&(C.e.c=_),"/>"===a[0].slice(-2)&&++_,D=[];break;case"Row":"/"===a[1]||"/>"===a[0].slice(-2)?(yC.e.r&&(C.e.r=y),"/>"===a[0].slice(-2)&&(E=tc(a[0])).Index&&(y=+E.Index-1),_=0,++y):((E=tc(a[0])).Index&&(y=+E.Index-1),H={},"0"!=E.AutoFitHeight&&!E.Height||(H.hpx=parseInt(E.Height,10),H.hpt=Js(H.hpx),U[y]=H),"1"==E.Hidden&&(H.hidden=!0,U[y]=H));break;case"Worksheet":if("/"===a[1]){if((o=l.pop())[0]!==a[3])throw new Error("Bad state: "+o.join("|"));m.push(b),C.s.r<=C.e.r&&C.s.c<=C.e.c&&(g["!ref"]=$t(C),r.sheetRows&&r.sheetRows<=C.e.r&&(g["!fullref"]=g["!ref"],C.e.r=r.sheetRows-1,g["!ref"]=$t(C))),A.length&&(g["!merges"]=A),0"==a[0].slice(-2))break;tc(a[0]),l.push([a[3],!1]),L=!(N=[])}break;case"Style":"/"===a[1]?(h=B,u=T,(d=r).cellStyles&&(!u.Interior||(d=u.Interior).Pattern&&(d.patternType=ei[d.Pattern]||d.Pattern)),h[u.ID]=u):T=tc(a[0]);break;case"NumberFormat":T.nf=we(tc(a[0]).Format||"General"),se[T.nf]&&(T.nf=se[T.nf]);for(var $=0;392!=$&&ue._table[$]!=T.nf;++$);if(392==$)for($=57;392!=$;++$)if(null==ue._table[$]){ue.load(T.nf,$);break}break;case"Column":if("Table"!==l[l.length-1][0])break;if((c=tc(a[0])).Hidden&&(c.hidden=!0,delete c.Hidden),c.Width&&(c.wpx=parseInt(c.Width,10)),!L&&10"===a[0].slice(-2))break;"/"===a[1]?k+=n.slice(x,a.index):x=a.index+a[0].length;break;case"Interior":if(!r.cellStyles)break;T.Interior=tc(a[0]);break;case"Protection":break;case"Author":case"Title":case"Description":case"Created":case"Keywords":case"Subject":case"Category":case"Company":case"LastAuthor":case"LastSaved":case"LastPrinted":case"Version":case"Revision":case"TotalTime":case"HyperlinkBase":case"Manager":case"ContentStatus":case"Identifier":case"Language":case"AppName":if("/>"===a[0].slice(-2))break;"/"===a[1]?(u=I,Z=a[3],Q=n.slice(O,a.index),u[Z=mn[Z]||Z]=Q):O=a.index+a[0].length;break;case"Paragraphs":break;case"Styles":case"Workbook":if("/"===a[1]){if((o=l.pop())[0]!==a[3])throw new Error("Bad state: "+o.join("|"))}else l.push([a[3],!1]);break;case"Comment":if("/"===a[1]){if((o=l.pop())[0]!==a[3])throw new Error("Bad state: "+o.join("|"));(f=P).t=f.v||"",f.t=f.t.replace(/\r\n/g,"\n").replace(/\r/g,"\n"),f.v=f.w=f.ixfe=void 0,D.push(P)}else l.push([a[3],!1]),P={a:(o=tc(a[0])).Author};break;case"AutoFilter":if("/"===a[1]){if((o=l.pop())[0]!==a[3])throw new Error("Bad state: "+o.join("|"))}else"/"!==a[0].charAt(a[0].length-2)&&(f=tc(a[0]),g["!autofilter"]={ref:Wi(f.Range).replace(/\$/g,"")},l.push([a[3],!0]));break;case"Name":break;case"ComponentOptions":case"DocumentProperties":case"CustomDocumentProperties":case"OfficeDocumentSettings":case"PivotTable":case"PivotCache":case"Names":case"MapInfo":case"PageBreaks":case"QueryTable":case"DataValidation":case"Sorting":case"Schema":case"data":case"ConditionalFormatting":case"SmartTagType":case"SmartTags":case"ExcelWorkbook":case"WorkbookOptions":case"WorksheetOptions":if("/"===a[1]){if((o=l.pop())[0]!==a[3])throw new Error("Bad state: "+o.join("|"))}else"/"!==a[0].charAt(a[0].length-2)&&l.push([a[3],!0]);break;default:if(0==l.length&&"document"==a[3])return Gc(n,r);if(0==l.length&&"UOF"==a[3])return Gc(n,r);var J=!0;switch(l[l.length-1][0]){case"OfficeDocumentSettings":switch(a[3]){case"AllowPNG":case"RemovePersonalInformation":case"DownloadComponents":case"LocationOfComponents":case"Colors":case"Color":case"Index":case"RGB":case"PixelsPerInch":case"TargetScreenSize":case"ReadOnlyRecommended":break;default:J=!1}break;case"ComponentOptions":switch(a[3]){case"Toolbar":case"HideOfficeLogo":case"SpreadsheetAutoFit":case"Label":case"Caption":case"MaxHeight":case"MaxWidth":case"NextSheetNumber":break;default:J=!1}break;case"ExcelWorkbook":switch(a[3]){case"Date1904":W.WBProps.date1904=!0;break;case"WindowHeight":case"WindowWidth":case"WindowTopX":case"WindowTopY":case"TabRatio":case"ProtectStructure":case"ProtectWindows":case"ActiveSheet":case"DisplayInkNotes":case"FirstVisibleSheet":case"SupBook":case"SheetName":case"SheetIndex":case"SheetIndexFirst":case"SheetIndexLast":case"Dll":case"AcceptLabelsInFormulas":case"DoNotSaveLinkValues":case"Iteration":case"MaxIterations":case"MaxChange":case"Path":case"Xct":case"Count":case"SelectedSheets":case"Calculation":case"Uncalced":case"StartupPrompt":case"Crn":case"ExternName":case"Formula":case"ColFirst":case"ColLast":case"WantAdvise":case"Boolean":case"Error":case"Text":case"OLE":case"NoAutoRecover":case"PublishObjects":case"DoNotCalculateBeforeSave":case"Number":case"RefModeR1C1":case"EmbedSaveSmartTags":break;default:J=!1}break;case"WorkbookOptions":switch(a[3]){case"OWCVersion":case"Height":case"Width":break;default:J=!1}break;case"WorksheetOptions":switch(a[3]){case"Visible":if("/>"!==a[0].slice(-2))if("/"===a[1])switch(n.slice(O,a.index)){case"SheetHidden":X.Hidden=1;break;case"SheetVeryHidden":X.Hidden=2}else O=a.index+a[0].length;break;case"Header":g["!margins"]||zo(g["!margins"]={},"xlml"),g["!margins"].header=me(a[0]).Margin;break;case"Footer":g["!margins"]||zo(g["!margins"]={},"xlml"),g["!margins"].footer=me(a[0]).Margin;break;case"PageMargins":var q=me(a[0]);g["!margins"]||zo(g["!margins"]={},"xlml"),q.Top&&(g["!margins"].top=q.Top),q.Left&&(g["!margins"].left=q.Left),q.Right&&(g["!margins"].right=q.Right),q.Bottom&&(g["!margins"].bottom=q.Bottom);break;case"DisplayRightToLeft":W.Views||(W.Views=[]),W.Views[0]||(W.Views[0]={}),W.Views[0].RTL=!0;break;case"Unsynced":case"Print":case"Panes":case"Scale":case"Pane":case"Number":case"Layout":case"PageSetup":case"Selected":case"ProtectObjects":case"EnableSelection":case"ProtectScenarios":case"ValidPrinterInfo":case"HorizontalResolution":case"VerticalResolution":case"NumberofCopies":case"ActiveRow":case"ActiveCol":case"ActivePane":case"TopRowVisible":case"TopRowBottomPane":case"LeftColumnVisible":case"LeftColumnRightPane":case"FitToPage":case"RangeSelection":case"PaperSizeIndex":case"PageLayoutZoom":case"PageBreakZoom":case"FilterOn":case"DoNotDisplayGridlines":case"SplitHorizontal":case"SplitVertical":case"FreezePanes":case"FrozenNoSplit":case"FitWidth":case"FitHeight":case"CommentsLayout":case"Zoom":case"LeftToRight":case"Gridlines":case"AllowSort":case"AllowFilter":case"AllowInsertRows":case"AllowDeleteRows":case"AllowInsertCols":case"AllowDeleteCols":case"AllowInsertHyperlinks":case"AllowFormatCells":case"AllowSizeCols":case"AllowSizeRows":case"NoSummaryRowsBelowDetail":case"TabColorIndex":case"DoNotDisplayHeadings":case"ShowPageLayoutZoom":case"NoSummaryColumnsRightDetail":case"BlackAndWhite":case"DoNotDisplayZeros":case"DisplayPageBreak":case"RowColHeadings":case"DoNotDisplayOutline":case"NoOrientation":case"AllowUsePivotTables":case"ZeroHeight":case"ViewableRange":case"Selection":case"ProtectContents":break;default:J=!1}break;case"PivotTable":case"PivotCache":switch(a[3]){case"ImmediateItemsOnDrop":case"ShowPageMultipleItemLabel":case"CompactRowIndent":case"Location":case"PivotField":case"Orientation":case"LayoutForm":case"LayoutSubtotalLocation":case"LayoutCompactRow":case"Position":case"PivotItem":case"DataType":case"DataField":case"SourceName":case"ParentField":case"PTLineItems":case"PTLineItem":case"CountOfSameItems":case"Item":case"ItemType":case"PTSource":case"CacheIndex":case"ConsolidationReference":case"FileName":case"Reference":case"NoColumnGrand":case"NoRowGrand":case"BlankLineAfterItems":case"Hidden":case"Subtotal":case"BaseField":case"MapChildItems":case"Function":case"RefreshOnFileOpen":case"PrintSetTitles":case"MergeLabels":case"DefaultVersion":case"RefreshName":case"RefreshDate":case"RefreshDateCopy":case"VersionLastRefresh":case"VersionLastUpdate":case"VersionUpdateableMin":case"VersionRefreshableMin":case"Calculation":break;default:J=!1}break;case"PageBreaks":switch(a[3]){case"ColBreaks":case"ColBreak":case"RowBreaks":case"RowBreak":case"ColStart":case"ColEnd":case"RowEnd":break;default:J=!1}break;case"AutoFilter":switch(a[3]){case"AutoFilterColumn":case"AutoFilterCondition":case"AutoFilterAnd":case"AutoFilterOr":break;default:J=!1}break;case"QueryTable":switch(a[3]){case"Id":case"AutoFormatFont":case"AutoFormatPattern":case"QuerySource":case"QueryType":case"EnableRedirections":case"RefreshedInXl9":case"URLString":case"HTMLTables":case"Connection":case"CommandText":case"RefreshInfo":case"NoTitles":case"NextId":case"ColumnInfo":case"OverwriteCells":case"DoNotPromptForFile":case"TextWizardSettings":case"Source":case"Number":case"Decimal":case"ThousandSeparator":case"TrailingMinusNumbers":case"FormatSettings":case"FieldType":case"Delimiters":case"Tab":case"Comma":case"AutoFormatName":case"VersionLastEdit":case"VersionLastRefresh":break;default:J=!1}break;case"Sorting":case"ConditionalFormatting":case"DataValidation":switch(a[3]){case"Range":case"Type":case"Min":case"Max":case"Sort":case"Descending":case"Order":case"CaseSensitive":case"Value":case"ErrorStyle":case"ErrorMessage":case"ErrorTitle":case"CellRangeList":case"InputMessage":case"InputTitle":case"ComboHide":case"InputHide":case"Condition":case"Qualifier":case"UseBlank":case"Value1":case"Value2":case"Format":break;default:J=!1}break;case"MapInfo":case"Schema":case"data":switch(a[3]){case"Map":case"Entry":case"Range":case"XPath":case"Field":case"XSDType":case"FilterOn":case"Aggregate":case"ElementType":case"AttributeType":break;case"schema":case"element":case"complexType":case"datatype":case"all":case"attribute":case"extends":case"row":break;default:J=!1}break;case"SmartTags":break;default:J=!1}if(J)break;if(!l[l.length-1][1])throw"Unrecognized tag: "+a[3]+"|"+l.join("|");if("CustomDocumentProperties"===l[l.length-1][0]){if("/>"===a[0].slice(-2))break;"/"===a[1]?function(e,t,r,n){var a=n;switch((r[0].match(/dt:dt="([\w.]+)"/)||["",""])[1]){case"boolean":a=Re(n);break;case"i2":case"int":a=parseInt(n,10);break;case"r4":case"float":a=parseFloat(n);break;case"date":case"dateTime.tz":a=le(n);break;case"i8":case"string":case"fixed":case"uuid":case"bin.base64":break;default:throw new Error("bad custprop:"+r[0])}e[we(t)]=a}(R,a[3],F,n.slice(O,a.index)):O=(F=a).index+a[0].length;break}if(r.WTF)throw"Unrecognized tag: "+a[3]+"|"+l.join("|")}e={};return r.bookSheets||r.bookProps||(e.Sheets=p),e.SheetNames=m,e.Workbook=W,e.SSF=ue.get_table(),e.Props=I,e.Custprops=R,e}function ic(e,t){switch(hf(t=t||{}),t.type||"base64"){case"base64":return sc(K.decode(e),t);case"binary":case"buffer":case"file":return sc(e,t);case"array":return sc(o(e),t)}}function oc(e,t){var r,n,a,s,i,o,l,c=[];return e.Props&&c.push((r=e.Props,n=t,a=[],de(pn).map(function(e){for(var t=0;t'+l.join("")+"")),c.join("")}function lc(e){return Ge("NamedRange",null,{"ss:Name":e.Name,"ss:RefersTo":"="+Gi(e.Ref,{r:0,c:0})})}function cc(e,t,r,n,a,s,i){if(!e||null==e.v&&null==e.f)return"";var o={};if(e.f&&(o["ss:Formula"]="="+ye(Gi(e.f,i))),e.F&&e.F.slice(0,t.length)==t&&(t=Xt(e.F.slice(t.length+1)),o["ss:ArrayRange"]="RC:R"+(t.r==i.r?"":"["+(t.r-i.r)+"]")+"C"+(t.c==i.c?"":"["+(t.c-i.c)+"]")),e.l&&e.l.Target&&(o["ss:HRef"]=ye(e.l.Target),e.l.Tooltip&&(o["x:HRefScreenTip"]=ye(e.l.Tooltip))),r["!merges"])for(var l=r["!merges"],c=0;c!=l.length;++c)l[c].s.c==i.c&&l[c].s.r==i.r&&(l[c].e.c>l[c].s.c&&(o["ss:MergeAcross"]=l[c].e.c-l[c].s.c),l[c].e.r>l[c].s.r&&(o["ss:MergeDown"]=l[c].e.r-l[c].s.r));var f="",h="";switch(e.t){case"z":return"";case"n":f="Number",h=String(e.v);break;case"b":f="Boolean",h=e.v?"1":"0";break;case"e":f="Error",h=Sr[e.v];break;case"d":f="DateTime",h=new Date(e.v).toISOString(),null==e.z&&(e.z=e.z||ue._table[14]);break;case"s":f="String",h=((e.v||"")+"").replace(Se,function(e){return Ee[e]}).replace(Be,function(e){return"&#x"+e.charCodeAt(0).toString(16).toUpperCase()+";"})}n=Vo(n.cellXfs,e,n);o["ss:StyleID"]="s"+(21+n),o["ss:Index"]=i.c+1;n=''+(null!=e.v?h:"")+"";return 0<(e.c||[]).length&&(n+=e.c.map(function(e){var t=Ge("ss:Data",Ie(e.t||""),{xmlns:"http://www.w3.org/TR/REC-html40"});return Ge("Comment",t,{"ss:Author":e.a})}).join("")),Ge("Cell",n,o)}function fc(e,t){if(!e["!ref"])return"";var r=Yt(e["!ref"]),n=e["!merges"]||[],a=0,s=[];e["!cols"]&&e["!cols"].forEach(function(e,t){Zs(e);var r=!!e.width,n=Ho(t,e),t={"ss:Index":t+1};r&&(t["ss:Width"]=js(n.width)),e.hidden&&(t["ss:Hidden"]="1"),s.push(Ge("Column",null,t))});for(var i,o,l=Array.isArray(e),c=r.s.r;c<=r.e.r;++c){for(var f=[(i=c,o=(e["!rows"]||[])[c],i='")],h=r.s.c;h<=r.e.c;++h){for(var u,d,p,m=!1,a=0;a!=n.length;++a)if(!(n[a].s.c>h||n[a].s.r>c||n[a].e.c"),2"+a+""),0<(a=s?fc(s,t):"").length&&n.push(""+a+"
    "),n.push(function(t,e,r){if(!t)return"";var n=[];if(t["!margins"]&&(n.push(""),t["!margins"].header&&n.push(Ge("Header",null,{"x:Margin":t["!margins"].header})),t["!margins"].footer&&n.push(Ge("Footer",null,{"x:Margin":t["!margins"].footer})),n.push(Ge("PageMargins",null,{"x:Bottom":t["!margins"].bottom||"0.75","x:Left":t["!margins"].left||"0.7","x:Right":t["!margins"].right||"0.7","x:Top":t["!margins"].top||"0.75"})),n.push("")),r&&r.Workbook&&r.Workbook.Sheets&&r.Workbook.Sheets[e])if(r.Workbook.Sheets[e].Hidden)n.push(Ge("Visible",1==r.Workbook.Sheets[e].Hidden?"SheetHidden":"SheetVeryHidden",{}));else{for(var a=0;a")}return((((r||{}).Workbook||{}).Views||[])[0]||{}).RTL&&n.push(""),t["!protect"]&&(n.push(Xe("ProtectContents","True")),t["!protect"].objects&&n.push(Xe("ProtectObjects","True")),t["!protect"].scenarios&&n.push(Xe("ProtectScenarios","True")),null==t["!protect"].selectLockedCells||t["!protect"].selectLockedCells?null==t["!protect"].selectUnlockedCells||t["!protect"].selectUnlockedCells||n.push(Xe("EnableSelection","UnlockedCells")):n.push(Xe("EnableSelection","NoSelection")),[["formatCells","AllowFormatCells"],["formatColumns","AllowSizeCols"],["formatRows","AllowSizeRows"],["insertColumns","AllowInsertCols"],["insertRows","AllowInsertRows"],["insertHyperlinks","AllowInsertHyperlinks"],["deleteColumns","AllowDeleteCols"],["deleteRows","AllowDeleteRows"],["sort","AllowSort"],["autoFilter","AllowFilter"],["pivotTables","AllowUsePivotTables"]].forEach(function(e){t["!protect"][e[0]]&&n.push("<"+e[1]+"/>")})),0==n.length?"":Ge("WorksheetOptions",n.join(""),{xmlns:Ke.x})}(s,e,r)),n.join("")}function uc(e,t){t=t||{},e.SSF||(e.SSF=ue.get_table()),e.SSF&&(ae(ue),ue.load_table(e.SSF),t.revssf=_(e.SSF),t.revssf[e.SSF[65535]]=0,t.ssf=e.SSF,t.cellXfs=[],Vo(t.cellXfs,{},{revssf:{General:0}}));var r=[];r.push(oc(e,t)),r.push(""),r.push(""),r.push("");for(var n,a=0;a'],t.cellXfs.forEach(function(e,t){var r=[];r.push(Ge("NumberFormat",null,{"ss:Format":ye(ue._table[e.numFmtId])})),n.push(Ge("Style",r.join(""),{"ss:ID":"s"+(21+t)}))}),Ge("Styles",n.join(""))),r[3]=function(e){if(!((e||{}).Workbook||{}).Names)return"";for(var t=e.Workbook.Names,r=[],n=0;n=r.sheetRows&&(S=!1),S)){var n,a,s;if(r.cellStyles&&t.XF&&t.XF.data&&(a=r,(s=(n=t).XF.data)&&s.patternType&&a&&a.cellStyles&&(n.s={},n.s.patternType=s.patternType,(a=Us(o(s.icvFore)))&&(n.s.fgColor={rgb:a}),(a=Us(o(s.icvBack)))&&(n.s.bgColor={rgb:a}))),delete t.ixfe,delete t.XF,v=jt(l=e),d&&d.s&&d.e||(d={s:{r:0,c:0},e:{r:0,c:0}}),e.rd.e.r&&(d.e.r=e.r+1),e.c+1>d.e.c&&(d.e.c=e.c+1),r.cellFormula&&t.f)for(var i=0;ie.c||w[i][0].s.r>e.r||w[i][0].e.c>8)!==z)throw new Error("rt mismatch: "+X+"!="+z);12==W.r&&(e.l+=10,V-=10)}var j,G,$,Y,K="EOF"===W.n?W.f(e,V,T):function(e,t,r,n){var a=r,s=[],i=t.slice(t.l,t.l+a);if(n&&n.enc&&n.enc.insitu)switch(e.n){case"BOF":case"FilePass":case"FileLock":case"InterfaceHdr":case"RRDInfo":case"RRDHead":case"UsrExcl":break;default:if(0===i.length)break;n.enc.insitu(i)}s.push(i),t.l+=a;for(var o=_c[vt(t,t.l)],l=0;null!=o&&"Continue"===o.n.slice(0,8);)a=vt(t,t.l+2),l=t.l+4,"ContinueFrt"==o.n?l+=4:"ContinueFrt"==o.n.slice(0,11)&&(l+=12),s.push(t.slice(l,t.l+4+a)),t.l+=4+a,o=_c[vt(t,t.l)];var c=he(s);At(c,0);var f=0;c.lens=[];for(var h=0;h>8&31]),pc(ee,t,r.opts.Date1904),a({c:K.c,r:K.r},ee,t);break;case"BoolErr":ee={ixfe:K.ixfe,XF:_[K.ixfe],v:K.val,t:K.t},0>8&31]),pc(ee,t,r.opts.Date1904),a({c:K.c,r:K.r},ee,t);break;case"RK":ee={ixfe:K.ixfe,XF:_[K.ixfe],v:K.rknum,t:"n"},0>8&31]),pc(ee,t,r.opts.Date1904),a({c:K.c,r:K.r},ee,t);break;case"MulRk":for(var J=K.c;J<=K.C;++J){var q=K.rkrec[J-K.c][0],ee={ixfe:q,XF:_[q],v:K.rkrec[J-K.c][1],t:"n"};0>8&31]),pc(ee,t,r.opts.Date1904),a({c:J,r:K.r},ee,t)}break;case"Formula":if("String"==K.val){p=K;break}(ee=mc(K.val,K.cell.ixfe,K.tt)).XF=_[ee.ixfe],t.cellFormula&&(!((Q=K.formula)&&Q[0]&&Q[0][0]&&"PtgExp"==Q[0][0][0])||E[Y=jt({r:G=Q[0][0][1][0],c:$=Q[0][0][1][1]})]?ee.f=""+yo(K.formula,0,K.cell,O,T):ee.F=((t.dense?(h[G]||[])[$]:h[Y])||{}).F),0>8&31]),pc(ee,t,r.opts.Date1904),a(K.cell,ee,t),p=K;break;case"String":if(!p)throw new Error("String record expects Formula");(ee=mc(p.val=K,p.cell.ixfe,"s")).XF=_[ee.ixfe],t.cellFormula&&(ee.f=""+yo(p.formula,0,p.cell,O,T)),0>8&31]),pc(ee,t,r.opts.Date1904),a(p.cell,ee,t),p=null;break;case"Array":w.push(K);var te=jt(K[0].s),re=t.dense?(h[K[0].s.r]||[])[K[0].s.c]:h[te];if(t.cellFormula&&re){if(!p)break;if(!te||!re)break;re.f=""+yo(K[1],0,K[0],O,T),re.F=$t(K[0])}break;case"ShrFmla":if(!S)break;if(!t.cellFormula)break;if(v){if(!p)break;E[jt(p.cell)]=K[0],((re=t.dense?(h[p.cell.r]||[])[p.cell.c]:h[jt(p.cell)])||{}).f=""+yo(K[0],0,l,O,T)}break;case"LabelSst":(ee=mc(m[K.isst].t,K.ixfe,"s")).XF=_[ee.ixfe],0>8&31]),pc(ee,t,r.opts.Date1904),a({c:K.c,r:K.r},ee,t);break;case"Blank":t.sheetStubs&&(ee={ixfe:K.ixfe,XF:_[K.ixfe],t:"z"},0>8&31]),pc(ee,t,r.opts.Date1904),a({c:K.c,r:K.r},ee,t));break;case"MulBlank":if(t.sheetStubs)for(var ne=K.c;ne<=K.C;++ne){var ae=K.ixfe[ne-K.c];ee={ixfe:ae,XF:_[ae],t:"z"},0>8&31]),pc(ee,t,r.opts.Date1904),a({c:ne,r:K.r},ee,t)}break;case"RString":case"Label":case"BIFF2STR":(ee=mc(K.val,K.ixfe,"s")).XF=_[ee.ixfe],0>8&31]),pc(ee,t,r.opts.Date1904),a({c:K.c,r:K.r},ee,t);break;case"Dimensions":1===P&&(d=K);break;case"SST":m=K;break;case"Format":if(4==T.biff){L[N++]=K[1];for(var se=0;se=K.s;)A[K.e--]={width:K.w/256},R||(R=!0,Ks(K.w/256)),Zs(A[K.e+1]);break;case"Row":var le={};null!=K.level&&((I[K.r]=le).level=K.level),K.hidden&&((I[K.r]=le).hidden=!0),K.hpt&&((I[K.r]=le).hpt=K.hpt,le.hpx=qs(K.hpt));break;case"LeftMargin":case"RightMargin":case"TopMargin":case"BottomMargin":h["!margins"]||zo(h["!margins"]={}),h["!margins"][Z.slice(0,-6).toLowerCase()]=K;break;case"Setup":h["!margins"]||zo(h["!margins"]={}),h["!margins"].header=K.header,h["!margins"].footer=K.footer;break;case"Window2":K.RTL&&(C.Views[0].RTL=!0);break;case"Header":case"Footer":case"HCenter":case"VCenter":case"Pls":case"GCW":case"LHRecord":case"DBCell":case"EntExU2":case"SxView":case"Sxvd":case"SXVI":case"SXVDEx":case"SxIvd":case"SXString":case"Sync":case"Addin":case"SXDI":case"SXLI":case"SXEx":case"QsiSXTag":case"Selection":case"Feat":break;case"FeatHdr":case"FeatHdr11":break;case"Feature11":case"Feature12":case"List12":break;case"Country":c=K;break;case"RecalcId":case"DxGCol":break;case"Fbi":case"Fbi2":case"GelFrame":case"Font":case"XFCRC":case"Style":case"StyleExt":break;case"Palette":y=K;break;case"Theme":f=K;break;case"ScenarioProtect":case"ObjProtect":case"CondFmt12":case"Table":case"TableStyles":case"TableStyle":case"TableStyleElement":case"SXStreamID":case"SXVS":case"DConRef":case"SXAddl":case"DConBin":case"DConName":case"SXPI":case"SxFormat":case"SxSelect":case"SxRule":case"SxFilt":case"SxItm":case"SxDXF":case"ScenMan":case"DCon":case"CellWatch":case"PrintRowCol":case"PrintGrid":case"PrintSize":case"XCT":case"CRN":case"Scl":case"SheetExt":case"SheetExtOptional":case"ObNoMacros":case"ObProj":break;case"CodeName":g?B.CodeName=K||B.name:C.WBProps.CodeName=K||"ThisWorkbook";break;case"GUIDTypeLib":case"WOpt":case"PhoneticInfo":case"OleObjectSize":break;case"DXF":case"DXFN":case"DXFN12":case"DXFN12List":case"DXFN12NoCB":break;case"Dv":case"DVal":break;case"BRAI":case"Series":case"SeriesText":case"DConn":case"DbOrParamQry":case"DBQueryExt":case"OleDbConn":case"ExtString":case"IFmtRecord":break;case"CondFmt":case"CF":case"CF12":case"CFEx":case"Excel9File":case"Units":break;case"InterfaceHdr":case"Mms":case"InterfaceEnd":case"DSF":case"BuiltInFnGroupCount":break;case"Window1":case"HideObj":case"GridSet":case"Guts":case"UserBView":case"UserSViewBegin":case"UserSViewEnd":case"Pane":break;default:switch(W.n){case"Dat":case"Begin":case"End":case"StartBlock":case"EndBlock":case"Frame":case"Area":case"Axis":case"AxisLine":case"Tick":break;case"AxesUsed":case"CrtLayout12":case"CrtLayout12A":case"CrtLink":case"CrtLine":case"CrtMlFrt":case"CrtMlFrtContinue":break;case"LineFormat":case"AreaFormat":case"Chart":case"Chart3d":case"Chart3DBarShape":case"ChartFormat":case"ChartFrtInfo":break;case"PlotArea":case"PlotGrowth":break;case"SeriesList":case"SerParent":case"SerAuxTrend":break;case"DataFormat":case"SerToCrt":case"FontX":break;case"CatSerRange":case"AxcExt":case"SerFmt":case"ShtProps":break;case"DefaultText":case"Text":case"CatLab":case"DataLabExtContents":break;case"Legend":case"LegendException":break;case"Pie":case"Scatter":break;case"PieFormat":case"MarkerFormat":break;case"StartObject":case"EndObject":break;case"AlRuns":case"ObjectLink":case"SIIndex":break;case"AttachedLabel":case"YMult":break;case"Line":case"Bar":case"Surf":case"AxisParent":case"Pos":case"ValueRange":case"SXViewEx9":case"SXViewLink":case"PivotChartBits":case"SBaseRef":case"TextPropsStream":case"LnExt":case"MkrExt":case"CrtCoopt":break;case"Qsi":case"Qsif":case"Qsir":case"QsiSXTag":case"TxtQry":case"FilterMode":break;case"AutoFilter":case"AutoFilterInfo":case"AutoFilter12":case"DropDownObjIds":case"Sort":case"SortData":case"ShapePropsStream":break;case"MsoDrawing":case"MsoDrawingGroup":case"MsoDrawingSelection":break;case"WebPub":case"AutoWebPub":break;case"HeaderFooter":case"HFPicture":case"PLV":case"HorizontalPageBreaks":case"VerticalPageBreaks":break;case"Backup":case"CompressPictures":case"Compat12":break;case"Continue":case"ContinueFrt12":break;case"FrtFontList":case"FrtWrapper":break;default:switch(W.n){case"TabIdConf":case"Radar":case"RadarArea":case"DropBar":case"Intl":case"CoordList":case"SerAuxErrBar":break;case"BIFF2FONTCLR":case"BIFF2FMTCNT":case"BIFF2FONTXTRA":break;case"BIFF2XF":case"BIFF3XF":case"BIFF4XF":break;case"BIFF4FMTCNT":case"BIFF2ROW":case"BIFF2WINDOW2":break;case"SCENARIO":case"DConBin":case"PicF":case"DataLabExt":case"Lel":case"BopPop":case"BopPopCustom":case"RealTimeData":case"Name":break;case"LHNGraph":case"FnGroupName":case"AddMenu":case"LPr":break;case"ListObj":case"ListField":case"RRSort":case"BigName":break;case"ToolbarHdr":case"ToolbarEnd":case"DDEObjName":case"FRTArchId$":break;default:if(t.WTF)throw"Unrecognized Record "+W.n}}}}}else e.l+=V}return r.SheetNames=de(u).sort(function(e,t){return Number(e)-Number(t)}).map(function(e){return u[e].name}),t.bookSheets||(r.Sheets=n),r.Sheets&&M.forEach(function(e,t){r.Sheets[r.SheetNames[t]]["!autofilter"]=e}),r.Preamble=b,r.Strings=m,r.SSF=ue.get_table(),T.enc&&(r.Encryption=T.enc),f&&(r.Themes=f),r.Metadata={},void 0!==c&&(r.Metadata.Country=c),0>>1,n=e.read_shift(1),a=e.read_shift(2,"i"),s=e.read_shift(1),i=e.read_shift(1),o=e.read_shift(1);switch(e.l++,r){case 0:t.auto=1;break;case 1:t.index=n;var l=Mr[n];l&&(t.rgb=Us(l));break;case 2:t.rgb=Us([s,i,o]);break;case 3:t.theme=n}return 0!=a&&(t.tint=0>13&3],r.showPivotChartFilter=!!(32768&n),r.updateLinks=["userSet","never","always"][n>>8&3],r}},154:{n:"BrtWbFactoid"},155:{n:"BrtFileRecover"},156:{n:"BrtBundleSh",f:function(e,t){var r={};return r.Hidden=e.read_shift(4),r.iTabID=e.read_shift(4),r.strRelID=dr(e,t-8),r.name=tr(e),r}},157:{n:"BrtCalcProp"},158:{n:"BrtBookView"},159:{n:"BrtBeginSst",f:function(e){return[e.read_shift(4),e.read_shift(4)]}},160:{n:"BrtEndSst"},161:{n:"BrtBeginAFilter",f:br},162:{n:"BrtEndAFilter"},163:{n:"BrtBeginFilterColumn"},164:{n:"BrtEndFilterColumn"},165:{n:"BrtBeginFilters"},166:{n:"BrtEndFilters"},167:{n:"BrtFilter"},168:{n:"BrtColorFilter"},169:{n:"BrtIconFilter"},170:{n:"BrtTop10Filter"},171:{n:"BrtDynamicFilter"},172:{n:"BrtBeginCustomFilters"},173:{n:"BrtEndCustomFilters"},174:{n:"BrtCustomFilter"},175:{n:"BrtAFilterDateGroupItem"},176:{n:"BrtMergeCell",f:y},177:{n:"BrtBeginMergeCells"},178:{n:"BrtEndMergeCells"},179:{n:"BrtBeginPivotCacheDef"},180:{n:"BrtEndPivotCacheDef"},181:{n:"BrtBeginPCDFields"},182:{n:"BrtEndPCDFields"},183:{n:"BrtBeginPCDField"},184:{n:"BrtEndPCDField"},185:{n:"BrtBeginPCDSource"},186:{n:"BrtEndPCDSource"},187:{n:"BrtBeginPCDSRange"},188:{n:"BrtEndPCDSRange"},189:{n:"BrtBeginPCDFAtbl"},190:{n:"BrtEndPCDFAtbl"},191:{n:"BrtBeginPCDIRun"},192:{n:"BrtEndPCDIRun"},193:{n:"BrtBeginPivotCacheRecords"},194:{n:"BrtEndPivotCacheRecords"},195:{n:"BrtBeginPCDHierarchies"},196:{n:"BrtEndPCDHierarchies"},197:{n:"BrtBeginPCDHierarchy"},198:{n:"BrtEndPCDHierarchy"},199:{n:"BrtBeginPCDHFieldsUsage"},200:{n:"BrtEndPCDHFieldsUsage"},201:{n:"BrtBeginExtConnection"},202:{n:"BrtEndExtConnection"},203:{n:"BrtBeginECDbProps"},204:{n:"BrtEndECDbProps"},205:{n:"BrtBeginECOlapProps"},206:{n:"BrtEndECOlapProps"},207:{n:"BrtBeginPCDSConsol"},208:{n:"BrtEndPCDSConsol"},209:{n:"BrtBeginPCDSCPages"},210:{n:"BrtEndPCDSCPages"},211:{n:"BrtBeginPCDSCPage"},212:{n:"BrtEndPCDSCPage"},213:{n:"BrtBeginPCDSCPItem"},214:{n:"BrtEndPCDSCPItem"},215:{n:"BrtBeginPCDSCSets"},216:{n:"BrtEndPCDSCSets"},217:{n:"BrtBeginPCDSCSet"},218:{n:"BrtEndPCDSCSet"},219:{n:"BrtBeginPCDFGroup"},220:{n:"BrtEndPCDFGroup"},221:{n:"BrtBeginPCDFGItems"},222:{n:"BrtEndPCDFGItems"},223:{n:"BrtBeginPCDFGRange"},224:{n:"BrtEndPCDFGRange"},225:{n:"BrtBeginPCDFGDiscrete"},226:{n:"BrtEndPCDFGDiscrete"},227:{n:"BrtBeginPCDSDTupleCache"},228:{n:"BrtEndPCDSDTupleCache"},229:{n:"BrtBeginPCDSDTCEntries"},230:{n:"BrtEndPCDSDTCEntries"},231:{n:"BrtBeginPCDSDTCEMembers"},232:{n:"BrtEndPCDSDTCEMembers"},233:{n:"BrtBeginPCDSDTCEMember"},234:{n:"BrtEndPCDSDTCEMember"},235:{n:"BrtBeginPCDSDTCQueries"},236:{n:"BrtEndPCDSDTCQueries"},237:{n:"BrtBeginPCDSDTCQuery"},238:{n:"BrtEndPCDSDTCQuery"},239:{n:"BrtBeginPCDSDTCSets"},240:{n:"BrtEndPCDSDTCSets"},241:{n:"BrtBeginPCDSDTCSet"},242:{n:"BrtEndPCDSDTCSet"},243:{n:"BrtBeginPCDCalcItems"},244:{n:"BrtEndPCDCalcItems"},245:{n:"BrtBeginPCDCalcItem"},246:{n:"BrtEndPCDCalcItem"},247:{n:"BrtBeginPRule"},248:{n:"BrtEndPRule"},249:{n:"BrtBeginPRFilters"},250:{n:"BrtEndPRFilters"},251:{n:"BrtBeginPRFilter"},252:{n:"BrtEndPRFilter"},253:{n:"BrtBeginPNames"},254:{n:"BrtEndPNames"},255:{n:"BrtBeginPName"},256:{n:"BrtEndPName"},257:{n:"BrtBeginPNPairs"},258:{n:"BrtEndPNPairs"},259:{n:"BrtBeginPNPair"},260:{n:"BrtEndPNPair"},261:{n:"BrtBeginECWebProps"},262:{n:"BrtEndECWebProps"},263:{n:"BrtBeginEcWpTables"},264:{n:"BrtEndECWPTables"},265:{n:"BrtBeginECParams"},266:{n:"BrtEndECParams"},267:{n:"BrtBeginECParam"},268:{n:"BrtEndECParam"},269:{n:"BrtBeginPCDKPIs"},270:{n:"BrtEndPCDKPIs"},271:{n:"BrtBeginPCDKPI"},272:{n:"BrtEndPCDKPI"},273:{n:"BrtBeginDims"},274:{n:"BrtEndDims"},275:{n:"BrtBeginDim"},276:{n:"BrtEndDim"},277:{n:"BrtIndexPartEnd"},278:{n:"BrtBeginStyleSheet"},279:{n:"BrtEndStyleSheet"},280:{n:"BrtBeginSXView"},281:{n:"BrtEndSXVI"},282:{n:"BrtBeginSXVI"},283:{n:"BrtBeginSXVIs"},284:{n:"BrtEndSXVIs"},285:{n:"BrtBeginSXVD"},286:{n:"BrtEndSXVD"},287:{n:"BrtBeginSXVDs"},288:{n:"BrtEndSXVDs"},289:{n:"BrtBeginSXPI"},290:{n:"BrtEndSXPI"},291:{n:"BrtBeginSXPIs"},292:{n:"BrtEndSXPIs"},293:{n:"BrtBeginSXDI"},294:{n:"BrtEndSXDI"},295:{n:"BrtBeginSXDIs"},296:{n:"BrtEndSXDIs"},297:{n:"BrtBeginSXLI"},298:{n:"BrtEndSXLI"},299:{n:"BrtBeginSXLIRws"},300:{n:"BrtEndSXLIRws"},301:{n:"BrtBeginSXLICols"},302:{n:"BrtEndSXLICols"},303:{n:"BrtBeginSXFormat"},304:{n:"BrtEndSXFormat"},305:{n:"BrtBeginSXFormats"},306:{n:"BrtEndSxFormats"},307:{n:"BrtBeginSxSelect"},308:{n:"BrtEndSxSelect"},309:{n:"BrtBeginISXVDRws"},310:{n:"BrtEndISXVDRws"},311:{n:"BrtBeginISXVDCols"},312:{n:"BrtEndISXVDCols"},313:{n:"BrtEndSXLocation"},314:{n:"BrtBeginSXLocation"},315:{n:"BrtEndSXView"},316:{n:"BrtBeginSXTHs"},317:{n:"BrtEndSXTHs"},318:{n:"BrtBeginSXTH"},319:{n:"BrtEndSXTH"},320:{n:"BrtBeginISXTHRws"},321:{n:"BrtEndISXTHRws"},322:{n:"BrtBeginISXTHCols"},323:{n:"BrtEndISXTHCols"},324:{n:"BrtBeginSXTDMPS"},325:{n:"BrtEndSXTDMPs"},326:{n:"BrtBeginSXTDMP"},327:{n:"BrtEndSXTDMP"},328:{n:"BrtBeginSXTHItems"},329:{n:"BrtEndSXTHItems"},330:{n:"BrtBeginSXTHItem"},331:{n:"BrtEndSXTHItem"},332:{n:"BrtBeginMetadata"},333:{n:"BrtEndMetadata"},334:{n:"BrtBeginEsmdtinfo"},335:{n:"BrtMdtinfo"},336:{n:"BrtEndEsmdtinfo"},337:{n:"BrtBeginEsmdb"},338:{n:"BrtEndEsmdb"},339:{n:"BrtBeginEsfmd"},340:{n:"BrtEndEsfmd"},341:{n:"BrtBeginSingleCells"},342:{n:"BrtEndSingleCells"},343:{n:"BrtBeginList"},344:{n:"BrtEndList"},345:{n:"BrtBeginListCols"},346:{n:"BrtEndListCols"},347:{n:"BrtBeginListCol"},348:{n:"BrtEndListCol"},349:{n:"BrtBeginListXmlCPr"},350:{n:"BrtEndListXmlCPr"},351:{n:"BrtListCCFmla"},352:{n:"BrtListTrFmla"},353:{n:"BrtBeginExternals"},354:{n:"BrtEndExternals"},355:{n:"BrtSupBookSrc",f:dr},357:{n:"BrtSupSelf"},358:{n:"BrtSupSame"},359:{n:"BrtSupTabs"},360:{n:"BrtBeginSupBook"},361:{n:"BrtPlaceholderName"},362:{n:"BrtExternSheet",f:ma},363:{n:"BrtExternTableStart"},364:{n:"BrtExternTableEnd"},366:{n:"BrtExternRowHdr"},367:{n:"BrtExternCellBlank"},368:{n:"BrtExternCellReal"},369:{n:"BrtExternCellBool"},370:{n:"BrtExternCellError"},371:{n:"BrtExternCellString"},372:{n:"BrtBeginEsmdx"},373:{n:"BrtEndEsmdx"},374:{n:"BrtBeginMdxSet"},375:{n:"BrtEndMdxSet"},376:{n:"BrtBeginMdxMbrProp"},377:{n:"BrtEndMdxMbrProp"},378:{n:"BrtBeginMdxKPI"},379:{n:"BrtEndMdxKPI"},380:{n:"BrtBeginEsstr"},381:{n:"BrtEndEsstr"},382:{n:"BrtBeginPRFItem"},383:{n:"BrtEndPRFItem"},384:{n:"BrtBeginPivotCacheIDs"},385:{n:"BrtEndPivotCacheIDs"},386:{n:"BrtBeginPivotCacheID"},387:{n:"BrtEndPivotCacheID"},388:{n:"BrtBeginISXVIs"},389:{n:"BrtEndISXVIs"},390:{n:"BrtBeginColInfos"},391:{n:"BrtEndColInfos"},392:{n:"BrtBeginRwBrk"},393:{n:"BrtEndRwBrk"},394:{n:"BrtBeginColBrk"},395:{n:"BrtEndColBrk"},396:{n:"BrtBrk"},397:{n:"BrtUserBookView"},398:{n:"BrtInfo"},399:{n:"BrtCUsr"},400:{n:"BrtUsr"},401:{n:"BrtBeginUsers"},403:{n:"BrtEOF"},404:{n:"BrtUCR"},405:{n:"BrtRRInsDel"},406:{n:"BrtRREndInsDel"},407:{n:"BrtRRMove"},408:{n:"BrtRREndMove"},409:{n:"BrtRRChgCell"},410:{n:"BrtRREndChgCell"},411:{n:"BrtRRHeader"},412:{n:"BrtRRUserView"},413:{n:"BrtRRRenSheet"},414:{n:"BrtRRInsertSh"},415:{n:"BrtRRDefName"},416:{n:"BrtRRNote"},417:{n:"BrtRRConflict"},418:{n:"BrtRRTQSIF"},419:{n:"BrtRRFormat"},420:{n:"BrtRREndFormat"},421:{n:"BrtRRAutoFmt"},422:{n:"BrtBeginUserShViews"},423:{n:"BrtBeginUserShView"},424:{n:"BrtEndUserShView"},425:{n:"BrtEndUserShViews"},426:{n:"BrtArrFmla",f:function(e,t,r){var n=e.l+t,a=gr(e),t=e.read_shift(1);return(a=[a])[2]=t,r.cellFormula?(r=To(e,n-e.l,r),a[1]=r):e.l=n,a}},427:{n:"BrtShrFmla",f:function(e,t,r){var n=e.l+t,t=[br(e,16)];return r.cellFormula&&(r=Ao(e,n-e.l,r),t[1]=r),e.l=n,t}},428:{n:"BrtTable"},429:{n:"BrtBeginExtConnections"},430:{n:"BrtEndExtConnections"},431:{n:"BrtBeginPCDCalcMems"},432:{n:"BrtEndPCDCalcMems"},433:{n:"BrtBeginPCDCalcMem"},434:{n:"BrtEndPCDCalcMem"},435:{n:"BrtBeginPCDHGLevels"},436:{n:"BrtEndPCDHGLevels"},437:{n:"BrtBeginPCDHGLevel"},438:{n:"BrtEndPCDHGLevel"},439:{n:"BrtBeginPCDHGLGroups"},440:{n:"BrtEndPCDHGLGroups"},441:{n:"BrtBeginPCDHGLGroup"},442:{n:"BrtEndPCDHGLGroup"},443:{n:"BrtBeginPCDHGLGMembers"},444:{n:"BrtEndPCDHGLGMembers"},445:{n:"BrtBeginPCDHGLGMember"},446:{n:"BrtEndPCDHGLGMember"},447:{n:"BrtBeginQSI"},448:{n:"BrtEndQSI"},449:{n:"BrtBeginQSIR"},450:{n:"BrtEndQSIR"},451:{n:"BrtBeginDeletedNames"},452:{n:"BrtEndDeletedNames"},453:{n:"BrtBeginDeletedName"},454:{n:"BrtEndDeletedName"},455:{n:"BrtBeginQSIFs"},456:{n:"BrtEndQSIFs"},457:{n:"BrtBeginQSIF"},458:{n:"BrtEndQSIF"},459:{n:"BrtBeginAutoSortScope"},460:{n:"BrtEndAutoSortScope"},461:{n:"BrtBeginConditionalFormatting"},462:{n:"BrtEndConditionalFormatting"},463:{n:"BrtBeginCFRule"},464:{n:"BrtEndCFRule"},465:{n:"BrtBeginIconSet"},466:{n:"BrtEndIconSet"},467:{n:"BrtBeginDatabar"},468:{n:"BrtEndDatabar"},469:{n:"BrtBeginColorScale"},470:{n:"BrtEndColorScale"},471:{n:"BrtCFVO"},472:{n:"BrtExternValueMeta"},473:{n:"BrtBeginColorPalette"},474:{n:"BrtEndColorPalette"},475:{n:"BrtIndexedColor"},476:{n:"BrtMargins",f:function(t){var r={};return bl.forEach(function(e){r[e]=Er(t)}),r}},477:{n:"BrtPrintOptions"},478:{n:"BrtPageSetup"},479:{n:"BrtBeginHeaderFooter"},480:{n:"BrtEndHeaderFooter"},481:{n:"BrtBeginSXCrtFormat"},482:{n:"BrtEndSXCrtFormat"},483:{n:"BrtBeginSXCrtFormats"},484:{n:"BrtEndSXCrtFormats"},485:{n:"BrtWsFmtInfo",f:function(){}},486:{n:"BrtBeginMgs"},487:{n:"BrtEndMGs"},488:{n:"BrtBeginMGMaps"},489:{n:"BrtEndMGMaps"},490:{n:"BrtBeginMG"},491:{n:"BrtEndMG"},492:{n:"BrtBeginMap"},493:{n:"BrtEndMap"},494:{n:"BrtHLink",f:function(e,t){var r=e.l+t,n=br(e,16),a=fr(e),s=tr(e),i=tr(e),t=tr(e);return e.l=r,t={rfx:n,relId:a,loc:s,display:t},i&&(t.Tooltip=i),t}},495:{n:"BrtBeginDCon"},496:{n:"BrtEndDCon"},497:{n:"BrtBeginDRefs"},498:{n:"BrtEndDRefs"},499:{n:"BrtDRef"},500:{n:"BrtBeginScenMan"},501:{n:"BrtEndScenMan"},502:{n:"BrtBeginSct"},503:{n:"BrtEndSct"},504:{n:"BrtSlc"},505:{n:"BrtBeginDXFs"},506:{n:"BrtEndDXFs"},507:{n:"BrtDXF"},508:{n:"BrtBeginTableStyles"},509:{n:"BrtEndTableStyles"},510:{n:"BrtBeginTableStyle"},511:{n:"BrtEndTableStyle"},512:{n:"BrtTableStyleElement"},513:{n:"BrtTableStyleClient"},514:{n:"BrtBeginVolDeps"},515:{n:"BrtEndVolDeps"},516:{n:"BrtBeginVolType"},517:{n:"BrtEndVolType"},518:{n:"BrtBeginVolMain"},519:{n:"BrtEndVolMain"},520:{n:"BrtBeginVolTopic"},521:{n:"BrtEndVolTopic"},522:{n:"BrtVolSubtopic"},523:{n:"BrtVolRef"},524:{n:"BrtVolNum"},525:{n:"BrtVolErr"},526:{n:"BrtVolStr"},527:{n:"BrtVolBool"},528:{n:"BrtBeginCalcChain$"},529:{n:"BrtEndCalcChain$"},530:{n:"BrtBeginSortState"},531:{n:"BrtEndSortState"},532:{n:"BrtBeginSortCond"},533:{n:"BrtEndSortCond"},534:{n:"BrtBookProtection"},535:{n:"BrtSheetProtection"},536:{n:"BrtRangeProtection"},537:{n:"BrtPhoneticInfo"},538:{n:"BrtBeginECTxtWiz"},539:{n:"BrtEndECTxtWiz"},540:{n:"BrtBeginECTWFldInfoLst"},541:{n:"BrtEndECTWFldInfoLst"},542:{n:"BrtBeginECTwFldInfo"},548:{n:"BrtFileSharing"},549:{n:"BrtOleSize"},550:{n:"BrtDrawing",f:dr},551:{n:"BrtLegacyDrawing"},552:{n:"BrtLegacyDrawingHF"},553:{n:"BrtWebOpt"},554:{n:"BrtBeginWebPubItems"},555:{n:"BrtEndWebPubItems"},556:{n:"BrtBeginWebPubItem"},557:{n:"BrtEndWebPubItem"},558:{n:"BrtBeginSXCondFmt"},559:{n:"BrtEndSXCondFmt"},560:{n:"BrtBeginSXCondFmts"},561:{n:"BrtEndSXCondFmts"},562:{n:"BrtBkHim"},564:{n:"BrtColor"},565:{n:"BrtBeginIndexedColors"},566:{n:"BrtEndIndexedColors"},569:{n:"BrtBeginMRUColors"},570:{n:"BrtEndMRUColors"},572:{n:"BrtMRUColor"},573:{n:"BrtBeginDVals"},574:{n:"BrtEndDVals"},577:{n:"BrtSupNameStart"},578:{n:"BrtSupNameValueStart"},579:{n:"BrtSupNameValueEnd"},580:{n:"BrtSupNameNum"},581:{n:"BrtSupNameErr"},582:{n:"BrtSupNameSt"},583:{n:"BrtSupNameNil"},584:{n:"BrtSupNameBool"},585:{n:"BrtSupNameFmla"},586:{n:"BrtSupNameBits"},587:{n:"BrtSupNameEnd"},588:{n:"BrtEndSupBook"},589:{n:"BrtCellSmartTagProperty"},590:{n:"BrtBeginCellSmartTag"},591:{n:"BrtEndCellSmartTag"},592:{n:"BrtBeginCellSmartTags"},593:{n:"BrtEndCellSmartTags"},594:{n:"BrtBeginSmartTags"},595:{n:"BrtEndSmartTags"},596:{n:"BrtSmartTagType"},597:{n:"BrtBeginSmartTagTypes"},598:{n:"BrtEndSmartTagTypes"},599:{n:"BrtBeginSXFilters"},600:{n:"BrtEndSXFilters"},601:{n:"BrtBeginSXFILTER"},602:{n:"BrtEndSXFilter"},603:{n:"BrtBeginFills"},604:{n:"BrtEndFills"},605:{n:"BrtBeginCellWatches"},606:{n:"BrtEndCellWatches"},607:{n:"BrtCellWatch"},608:{n:"BrtBeginCRErrs"},609:{n:"BrtEndCRErrs"},610:{n:"BrtCrashRecErr"},611:{n:"BrtBeginFonts"},612:{n:"BrtEndFonts"},613:{n:"BrtBeginBorders"},614:{n:"BrtEndBorders"},615:{n:"BrtBeginFmts"},616:{n:"BrtEndFmts"},617:{n:"BrtBeginCellXFs"},618:{n:"BrtEndCellXFs"},619:{n:"BrtBeginStyles"},620:{n:"BrtEndStyles"},625:{n:"BrtBigName"},626:{n:"BrtBeginCellStyleXFs"},627:{n:"BrtEndCellStyleXFs"},628:{n:"BrtBeginComments"},629:{n:"BrtEndComments"},630:{n:"BrtBeginCommentAuthors"},631:{n:"BrtEndCommentAuthors"},632:{n:"BrtCommentAuthor",f:Li},633:{n:"BrtBeginCommentList"},634:{n:"BrtEndCommentList"},635:{n:"BrtBeginComment",f:function(e){var t={};t.iauthor=e.read_shift(4);var r=br(e,16);return t.rfx=r.s,t.ref=jt(r.s),e.l+=16,t}},636:{n:"BrtEndComment"},637:{n:"BrtCommentText",f:ar},638:{n:"BrtBeginOleObjects"},639:{n:"BrtOleObject"},640:{n:"BrtEndOleObjects"},641:{n:"BrtBeginSxrules"},642:{n:"BrtEndSxRules"},643:{n:"BrtBeginActiveXControls"},644:{n:"BrtActiveX"},645:{n:"BrtEndActiveXControls"},646:{n:"BrtBeginPCDSDTCEMembersSortBy"},648:{n:"BrtBeginCellIgnoreECs"},649:{n:"BrtCellIgnoreEC"},650:{n:"BrtEndCellIgnoreECs"},651:{n:"BrtCsProp",f:function(e,t){return e.l+=10,{name:tr(e)}}},652:{n:"BrtCsPageSetup"},653:{n:"BrtBeginUserCsViews"},654:{n:"BrtEndUserCsViews"},655:{n:"BrtBeginUserCsView"},656:{n:"BrtEndUserCsView"},657:{n:"BrtBeginPcdSFCIEntries"},658:{n:"BrtEndPCDSFCIEntries"},659:{n:"BrtPCDSFCIEntry"},660:{n:"BrtBeginListParts"},661:{n:"BrtListPart"},662:{n:"BrtEndListParts"},663:{n:"BrtSheetCalcProp"},664:{n:"BrtBeginFnGroup"},665:{n:"BrtFnGroup"},666:{n:"BrtEndFnGroup"},667:{n:"BrtSupAddin"},668:{n:"BrtSXTDMPOrder"},669:{n:"BrtCsProtection"},671:{n:"BrtBeginWsSortMap"},672:{n:"BrtEndWsSortMap"},673:{n:"BrtBeginRRSort"},674:{n:"BrtEndRRSort"},675:{n:"BrtRRSortItem"},676:{n:"BrtFileSharingIso"},677:{n:"BrtBookProtectionIso"},678:{n:"BrtSheetProtectionIso"},679:{n:"BrtCsProtectionIso"},680:{n:"BrtRangeProtectionIso"},1024:{n:"BrtRwDescent"},1025:{n:"BrtKnownFonts"},1026:{n:"BrtBeginSXTupleSet"},1027:{n:"BrtEndSXTupleSet"},1028:{n:"BrtBeginSXTupleSetHeader"},1029:{n:"BrtEndSXTupleSetHeader"},1030:{n:"BrtSXTupleSetHeaderItem"},1031:{n:"BrtBeginSXTupleSetData"},1032:{n:"BrtEndSXTupleSetData"},1033:{n:"BrtBeginSXTupleSetRow"},1034:{n:"BrtEndSXTupleSetRow"},1035:{n:"BrtSXTupleSetRowItem"},1036:{n:"BrtNameExt"},1037:{n:"BrtPCDH14"},1038:{n:"BrtBeginPCDCalcMem14"},1039:{n:"BrtEndPCDCalcMem14"},1040:{n:"BrtSXTH14"},1041:{n:"BrtBeginSparklineGroup"},1042:{n:"BrtEndSparklineGroup"},1043:{n:"BrtSparkline"},1044:{n:"BrtSXDI14"},1045:{n:"BrtWsFmtInfoEx14"},1046:{n:"BrtBeginConditionalFormatting14"},1047:{n:"BrtEndConditionalFormatting14"},1048:{n:"BrtBeginCFRule14"},1049:{n:"BrtEndCFRule14"},1050:{n:"BrtCFVO14"},1051:{n:"BrtBeginDatabar14"},1052:{n:"BrtBeginIconSet14"},1053:{n:"BrtDVal14"},1054:{n:"BrtBeginDVals14"},1055:{n:"BrtColor14"},1056:{n:"BrtBeginSparklines"},1057:{n:"BrtEndSparklines"},1058:{n:"BrtBeginSparklineGroups"},1059:{n:"BrtEndSparklineGroups"},1061:{n:"BrtSXVD14"},1062:{n:"BrtBeginSXView14"},1063:{n:"BrtEndSXView14"},1064:{n:"BrtBeginSXView16"},1065:{n:"BrtEndSXView16"},1066:{n:"BrtBeginPCD14"},1067:{n:"BrtEndPCD14"},1068:{n:"BrtBeginExtConn14"},1069:{n:"BrtEndExtConn14"},1070:{n:"BrtBeginSlicerCacheIDs"},1071:{n:"BrtEndSlicerCacheIDs"},1072:{n:"BrtBeginSlicerCacheID"},1073:{n:"BrtEndSlicerCacheID"},1075:{n:"BrtBeginSlicerCache"},1076:{n:"BrtEndSlicerCache"},1077:{n:"BrtBeginSlicerCacheDef"},1078:{n:"BrtEndSlicerCacheDef"},1079:{n:"BrtBeginSlicersEx"},1080:{n:"BrtEndSlicersEx"},1081:{n:"BrtBeginSlicerEx"},1082:{n:"BrtEndSlicerEx"},1083:{n:"BrtBeginSlicer"},1084:{n:"BrtEndSlicer"},1085:{n:"BrtSlicerCachePivotTables"},1086:{n:"BrtBeginSlicerCacheOlapImpl"},1087:{n:"BrtEndSlicerCacheOlapImpl"},1088:{n:"BrtBeginSlicerCacheLevelsData"},1089:{n:"BrtEndSlicerCacheLevelsData"},1090:{n:"BrtBeginSlicerCacheLevelData"},1091:{n:"BrtEndSlicerCacheLevelData"},1092:{n:"BrtBeginSlicerCacheSiRanges"},1093:{n:"BrtEndSlicerCacheSiRanges"},1094:{n:"BrtBeginSlicerCacheSiRange"},1095:{n:"BrtEndSlicerCacheSiRange"},1096:{n:"BrtSlicerCacheOlapItem"},1097:{n:"BrtBeginSlicerCacheSelections"},1098:{n:"BrtSlicerCacheSelection"},1099:{n:"BrtEndSlicerCacheSelections"},1100:{n:"BrtBeginSlicerCacheNative"},1101:{n:"BrtEndSlicerCacheNative"},1102:{n:"BrtSlicerCacheNativeItem"},1103:{n:"BrtRangeProtection14"},1104:{n:"BrtRangeProtectionIso14"},1105:{n:"BrtCellIgnoreEC14"},1111:{n:"BrtList14"},1112:{n:"BrtCFIcon"},1113:{n:"BrtBeginSlicerCachesPivotCacheIDs"},1114:{n:"BrtEndSlicerCachesPivotCacheIDs"},1115:{n:"BrtBeginSlicers"},1116:{n:"BrtEndSlicers"},1117:{n:"BrtWbProp14"},1118:{n:"BrtBeginSXEdit"},1119:{n:"BrtEndSXEdit"},1120:{n:"BrtBeginSXEdits"},1121:{n:"BrtEndSXEdits"},1122:{n:"BrtBeginSXChange"},1123:{n:"BrtEndSXChange"},1124:{n:"BrtBeginSXChanges"},1125:{n:"BrtEndSXChanges"},1126:{n:"BrtSXTupleItems"},1128:{n:"BrtBeginSlicerStyle"},1129:{n:"BrtEndSlicerStyle"},1130:{n:"BrtSlicerStyleElement"},1131:{n:"BrtBeginStyleSheetExt14"},1132:{n:"BrtEndStyleSheetExt14"},1133:{n:"BrtBeginSlicerCachesPivotCacheID"},1134:{n:"BrtEndSlicerCachesPivotCacheID"},1135:{n:"BrtBeginConditionalFormattings"},1136:{n:"BrtEndConditionalFormattings"},1137:{n:"BrtBeginPCDCalcMemExt"},1138:{n:"BrtEndPCDCalcMemExt"},1139:{n:"BrtBeginPCDCalcMemsExt"},1140:{n:"BrtEndPCDCalcMemsExt"},1141:{n:"BrtPCDField14"},1142:{n:"BrtBeginSlicerStyles"},1143:{n:"BrtEndSlicerStyles"},1144:{n:"BrtBeginSlicerStyleElements"},1145:{n:"BrtEndSlicerStyleElements"},1146:{n:"BrtCFRuleExt"},1147:{n:"BrtBeginSXCondFmt14"},1148:{n:"BrtEndSXCondFmt14"},1149:{n:"BrtBeginSXCondFmts14"},1150:{n:"BrtEndSXCondFmts14"},1152:{n:"BrtBeginSortCond14"},1153:{n:"BrtEndSortCond14"},1154:{n:"BrtEndDVals14"},1155:{n:"BrtEndIconSet14"},1156:{n:"BrtEndDatabar14"},1157:{n:"BrtBeginColorScale14"},1158:{n:"BrtEndColorScale14"},1159:{n:"BrtBeginSxrules14"},1160:{n:"BrtEndSxrules14"},1161:{n:"BrtBeginPRule14"},1162:{n:"BrtEndPRule14"},1163:{n:"BrtBeginPRFilters14"},1164:{n:"BrtEndPRFilters14"},1165:{n:"BrtBeginPRFilter14"},1166:{n:"BrtEndPRFilter14"},1167:{n:"BrtBeginPRFItem14"},1168:{n:"BrtEndPRFItem14"},1169:{n:"BrtBeginCellIgnoreECs14"},1170:{n:"BrtEndCellIgnoreECs14"},1171:{n:"BrtDxf14"},1172:{n:"BrtBeginDxF14s"},1173:{n:"BrtEndDxf14s"},1177:{n:"BrtFilter14"},1178:{n:"BrtBeginCustomFilters14"},1180:{n:"BrtCustomFilter14"},1181:{n:"BrtIconFilter14"},1182:{n:"BrtPivotCacheConnectionName"},2048:{n:"BrtBeginDecoupledPivotCacheIDs"},2049:{n:"BrtEndDecoupledPivotCacheIDs"},2050:{n:"BrtDecoupledPivotCacheID"},2051:{n:"BrtBeginPivotTableRefs"},2052:{n:"BrtEndPivotTableRefs"},2053:{n:"BrtPivotTableRef"},2054:{n:"BrtSlicerCacheBookPivotTables"},2055:{n:"BrtBeginSxvcells"},2056:{n:"BrtEndSxvcells"},2057:{n:"BrtBeginSxRow"},2058:{n:"BrtEndSxRow"},2060:{n:"BrtPcdCalcMem15"},2067:{n:"BrtQsi15"},2068:{n:"BrtBeginWebExtensions"},2069:{n:"BrtEndWebExtensions"},2070:{n:"BrtWebExtension"},2071:{n:"BrtAbsPath15"},2072:{n:"BrtBeginPivotTableUISettings"},2073:{n:"BrtEndPivotTableUISettings"},2075:{n:"BrtTableSlicerCacheIDs"},2076:{n:"BrtTableSlicerCacheID"},2077:{n:"BrtBeginTableSlicerCache"},2078:{n:"BrtEndTableSlicerCache"},2079:{n:"BrtSxFilter15"},2080:{n:"BrtBeginTimelineCachePivotCacheIDs"},2081:{n:"BrtEndTimelineCachePivotCacheIDs"},2082:{n:"BrtTimelineCachePivotCacheID"},2083:{n:"BrtBeginTimelineCacheIDs"},2084:{n:"BrtEndTimelineCacheIDs"},2085:{n:"BrtBeginTimelineCacheID"},2086:{n:"BrtEndTimelineCacheID"},2087:{n:"BrtBeginTimelinesEx"},2088:{n:"BrtEndTimelinesEx"},2089:{n:"BrtBeginTimelineEx"},2090:{n:"BrtEndTimelineEx"},2091:{n:"BrtWorkBookPr15"},2092:{n:"BrtPCDH15"},2093:{n:"BrtBeginTimelineStyle"},2094:{n:"BrtEndTimelineStyle"},2095:{n:"BrtTimelineStyleElement"},2096:{n:"BrtBeginTimelineStylesheetExt15"},2097:{n:"BrtEndTimelineStylesheetExt15"},2098:{n:"BrtBeginTimelineStyles"},2099:{n:"BrtEndTimelineStyles"},2100:{n:"BrtBeginTimelineStyleElements"},2101:{n:"BrtEndTimelineStyleElements"},2102:{n:"BrtDxf15"},2103:{n:"BrtBeginDxfs15"},2104:{n:"brtEndDxfs15"},2105:{n:"BrtSlicerCacheHideItemsWithNoData"},2106:{n:"BrtBeginItemUniqueNames"},2107:{n:"BrtEndItemUniqueNames"},2108:{n:"BrtItemUniqueName"},2109:{n:"BrtBeginExtConn15"},2110:{n:"BrtEndExtConn15"},2111:{n:"BrtBeginOledbPr15"},2112:{n:"BrtEndOledbPr15"},2113:{n:"BrtBeginDataFeedPr15"},2114:{n:"BrtEndDataFeedPr15"},2115:{n:"BrtTextPr15"},2116:{n:"BrtRangePr15"},2117:{n:"BrtDbCommand15"},2118:{n:"BrtBeginDbTables15"},2119:{n:"BrtEndDbTables15"},2120:{n:"BrtDbTable15"},2121:{n:"BrtBeginDataModel"},2122:{n:"BrtEndDataModel"},2123:{n:"BrtBeginModelTables"},2124:{n:"BrtEndModelTables"},2125:{n:"BrtModelTable"},2126:{n:"BrtBeginModelRelationships"},2127:{n:"BrtEndModelRelationships"},2128:{n:"BrtModelRelationship"},2129:{n:"BrtBeginECTxtWiz15"},2130:{n:"BrtEndECTxtWiz15"},2131:{n:"BrtBeginECTWFldInfoLst15"},2132:{n:"BrtEndECTWFldInfoLst15"},2133:{n:"BrtBeginECTWFldInfo15"},2134:{n:"BrtFieldListActiveItem"},2135:{n:"BrtPivotCacheIdVersion"},2136:{n:"BrtSXDI15"},2137:{n:"BrtBeginModelTimeGroupings"},2138:{n:"BrtEndModelTimeGroupings"},2139:{n:"BrtBeginModelTimeGrouping"},2140:{n:"BrtEndModelTimeGrouping"},2141:{n:"BrtModelTimeGroupingCalcCol"},3072:{n:"BrtUid"},3073:{n:"BrtRevisionPtr"},5095:{n:"BrtBeginCalcFeatures"},5096:{n:"BrtEndCalcFeatures"},5097:{n:"BrtCalcFeature"},65535:{n:""}},Sc=w(wc,"n"),_c={3:{n:"BIFF2NUM",f:function(e){var t=$n(e);return++e.l,e=Er(e),t.t="n",t.val=e,t}},4:{n:"BIFF2STR",f:function(e,t,r){var n=$n(e);return++e.l,r=zn(e,0,r),n.t="str",n.val=r,n}},6:{n:"Formula",f:Co},9:{n:"BOF",f:na},10:{n:"EOF",f:On},12:{n:"CalcCount",f:Pn},13:{n:"CalcMode",f:Pn},14:{n:"CalcPrecision",f:Fn},15:{n:"CalcRefMode",f:Fn},16:{n:"CalcDelta",f:Er},17:{n:"CalcIter",f:Fn},18:{n:"Protect",f:Fn},19:{n:"Password",f:Pn},20:{n:"Header",f:ha},21:{n:"Footer",f:ha},23:{n:"ExternSheet",f:ma},24:{n:"Lbl",f:pa},25:{n:"WinProtect",f:Fn},26:{n:"VerticalPageBreaks"},27:{n:"HorizontalPageBreaks"},28:{n:"Note",f:function(e,t,r){return function(e,t){if(!(t.biff<8)){var r=e.read_shift(2),n=e.read_shift(2),a=e.read_shift(2),s=e.read_shift(2),i=zn(e,0,t);return t.biff<8&&e.read_shift(1),[{r:r,c:n},i,s,a]}}(e,r)}},29:{n:"Selection"},34:{n:"Date1904",f:Fn},35:{n:"ExternName",f:ua},38:{n:"LeftMargin",f:Er},39:{n:"RightMargin",f:Er},40:{n:"TopMargin",f:Er},41:{n:"BottomMargin",f:Er},42:{n:"PrintRowCol",f:Fn},43:{n:"PrintGrid",f:Fn},47:{n:"FilePass",f:function(e,t,r){var n={Type:8<=r.biff?e.read_shift(2):0};return n.Type?Ps(e,t-2,n):(t=e,r.biff,e=r,r=n,t={key:Pn(t),verificationBytes:Pn(t)},e.password&&(t.verifier=xs(e.password)),r.valid=t.verificationBytes===t.verifier,r.valid&&(r.insitu=Ds(e.password))),n}},49:{n:"Font",f:function(e,t,r){var n={dyHeight:e.read_shift(2),fl:e.read_shift(2)};switch(r&&r.biff||8){case 2:break;case 3:case 4:e.l+=2;break;default:e.l+=10}return n.name=Mn(e,0,r),n}},51:{n:"PrintSize",f:Pn},60:{n:"Continue"},61:{n:"Window1",f:function(e){return{Pos:[e.read_shift(2),e.read_shift(2)],Dim:[e.read_shift(2),e.read_shift(2)],Flags:e.read_shift(2),CurTab:e.read_shift(2),FirstTab:e.read_shift(2),Selected:e.read_shift(2),TabRatio:e.read_shift(2)}}},64:{n:"Backup",f:Fn},65:{n:"Pane"},66:{n:"CodePage",f:Pn},77:{n:"Pls"},80:{n:"DCon"},81:{n:"DConRef"},82:{n:"DConName"},85:{n:"DefColWidth",f:Pn},89:{n:"XCT"},90:{n:"CRN"},91:{n:"FileSharing"},92:{n:"WriteAccess",f:function(e,t,r){if(r.enc)return e.l+=t,"";var n=e.l,r=zn(e,0,r);return e.read_shift(t+n-e.l),r}},93:{n:"Obj",f:function(e,t,r){return r&&r.biff<8?function(e,t,r){e.l+=4;var n=e.read_shift(2),a=e.read_shift(2),s=e.read_shift(2);e.l+=2,e.l+=2,e.l+=2,e.l+=2,e.l+=2,e.l+=2,e.l+=2,e.l+=2,e.l+=2,e.l+=6,t-=36;var i=[];return i.push((ba[n]||It)(e,t,r)),{cmo:[a,n,s],ft:i}}(e,t,r):{cmo:r=ea(e),ft:function(t,e){for(var r=t.l+e,n=[];t.l>2&1,t-=6,i.data=(n=e,i.fStyle,a=r,s={},t=n.read_shift(4),e=n.read_shift(4),r=n.read_shift(4),n=n.read_shift(2),s.patternType=Nr[r>>26],a.cellStyles&&(s.alc=7&t,s.fWrap=t>>3&1,s.alcV=t>>4&7,s.fJustLast=t>>7&1,s.trot=t>>8&255,s.cIndent=t>>16&15,s.fShrinkToFit=t>>20&1,s.iReadOrder=t>>22&2,s.fAtrNum=t>>26&1,s.fAtrFnt=t>>27&1,s.fAtrAlc=t>>28&1,s.fAtrBdr=t>>29&1,s.fAtrPat=t>>30&1,s.fAtrProt=t>>31&1,s.dgLeft=15&e,s.dgRight=e>>4&15,s.dgTop=e>>8&15,s.dgBottom=e>>12&15,s.icvLeft=e>>16&127,s.icvRight=e>>23&127,s.grbitDiag=e>>30&3,s.icvTop=127&r,s.icvBottom=r>>7&127,s.icvDiag=r>>14&127,s.dgDiag=r>>21&15,s.icvFore=127&n,s.icvBack=n>>7&127,s.fsxButton=n>>14&1),s),i}},225:{n:"InterfaceHdr",f:function(e,t){return 0===t||e.read_shift(2),1200}},226:{n:"InterfaceEnd",f:On},227:{n:"SXVS"},229:{n:"MergeCells",f:function(e,t){for(var r=[],n=e.read_shift(2);n--;)r.push(Zn(e));return r}},233:{n:"BkHim"},235:{n:"MsoDrawingGroup"},236:{n:"MsoDrawing"},237:{n:"MsoDrawingSelection"},239:{n:"PhoneticInfo"},240:{n:"SxRule"},241:{n:"SXEx"},242:{n:"SxFilt"},244:{n:"SxDXF"},245:{n:"SxItm"},246:{n:"SxName"},247:{n:"SxSelect"},248:{n:"SXPair"},249:{n:"SxFmla"},251:{n:"SxFormat"},252:{n:"SST",f:function(e,t){for(var r=e.l+t,t=e.read_shift(4),n=e.read_shift(4),a=[],s=0;s!=n&&e.l"+l.t+"",l.r=l.t),f=t,l}(e));return a.Count=t,a.Unique=n,a}},253:{n:"LabelSst",f:function(e){var t=$n(e);return t.isst=e.read_shift(4),t}},255:{n:"ExtSST",f:function(e,t){var r={};return r.dsst=e.read_shift(2),e.l+=t-2,r}},256:{n:"SXVDEx"},259:{n:"SXFormula"},290:{n:"SXDBEx"},311:{n:"RRDInsDel"},312:{n:"RRDHead"},315:{n:"RRDChgCell"},317:{n:"RRTabId",f:Ln},318:{n:"RRDRenSheet"},319:{n:"RRSort"},320:{n:"RRDMove"},330:{n:"RRFormat"},331:{n:"RRAutoFmt"},333:{n:"RRInsertSh"},334:{n:"RRDMoveBegin"},335:{n:"RRDMoveEnd"},336:{n:"RRDInsDelBegin"},337:{n:"RRDInsDelEnd"},338:{n:"RRDConflict"},339:{n:"RRDDefName"},340:{n:"RRDRstEtxp"},351:{n:"LRng"},352:{n:"UsesELFs",f:Fn},353:{n:"DSF",f:On},401:{n:"CUsr"},402:{n:"CbUsr"},403:{n:"UsrInfo"},404:{n:"UsrExcl"},405:{n:"FileLock"},406:{n:"RRDInfo"},407:{n:"BCUsrs"},408:{n:"UsrChk"},425:{n:"UserBView"},426:{n:"UserSViewBegin"},427:{n:"UserSViewEnd"},428:{n:"RRDUserView"},429:{n:"Qsi"},430:{n:"SupBook",f:function(e,t,r){var n=e.l+t,a=e.read_shift(2),t=e.read_shift(2);if(1025==(r.sbcch=t)||14849==t)return[t,a];if(t<1||255e.l;)s.push(Hn(e));return[t,a,r,s]}},431:{n:"Prot4Rev",f:Fn},432:{n:"CondFmt"},433:{n:"CF"},434:{n:"DVal"},437:{n:"DConBin"},438:{n:"TxO",f:function(t,r,e){var n=t.l,a="";try{t.l+=4;var s=(e.lastobj||{cmo:[0,0]}).cmo[1];-1==[0,5,7,11,12,14].indexOf(s)?t.l+=6:function(e){var t=e.read_shift(1);e.l++;var r=e.read_shift(2);e.l+=2}(t);var i=t.read_shift(2);t.read_shift(2),Pn(t);s=t.read_shift(2);t.l+=s;for(var o=1;o=(l?i:2*i))break}if(a.length!==i&&a.length!==2*i)throw new Error("cchText: "+i+" != "+a.length);return t.l=n+r,{t:a}}catch(e){return t.l=n+r,{t:a}}}},439:{n:"RefreshAll",f:Fn},440:{n:"HLink",f:function(e,t){var r=Zn(e);return e.l+=16,[r,function(e,t){var r=e.l+t;if(2!==(l=e.read_shift(4)))throw new Error("Unrecognized streamVersion: "+l);t=e.read_shift(2),e.l+=2;var n,a,s,i,o,l="";16&t&&(n=Xn(e,e.l)),128&t&&(a=Xn(e,e.l)),257==(257&t)&&(s=Xn(e,e.l)),1==(257&t)&&(c=Wn(e,e.l)),8&t&&(l=Xn(e,e.l)),32&t&&(i=e.read_shift(16)),64&t&&(o=gn(e)),e.l=r;var c=a||s||c||"";return c&&l&&(c+="#"+l),c={Target:c=c||"#"+l},i&&(c.guid=i),o&&(c.time=o),n&&(c.Tooltip=n),c}(e,t-24)]}},441:{n:"Lel"},442:{n:"CodeName",f:Hn},443:{n:"SXFDBType"},444:{n:"Prot4RevPass",f:Pn},445:{n:"ObNoMacros"},446:{n:"Dv"},448:{n:"Excel9File",f:On},449:{n:"RecalcId",f:function(e){return e.read_shift(2),e.read_shift(4)},r:2},450:{n:"EntExU2",f:On},512:{n:"Dimensions",f:oa},513:{n:"Blank",f:wa},515:{n:"Number",f:function(e){var t=$n(e),e=Er(e);return t.val=e,t}},516:{n:"Label",f:function(e,t,r){return e.l,t=$n(e),2==r.biff&&e.l++,r=Hn(e,e.l,r),t.val=r,t}},517:{n:"BoolErr",f:ca},518:{n:"Formula",f:Co},519:{n:"String",f:_a},520:{n:"Row",f:function(e){var t={};t.r=e.read_shift(2),t.c=e.read_shift(2),t.cnt=e.read_shift(2)-t.c;var r=e.read_shift(2);e.l+=4;var n=e.read_shift(1);return e.l+=3,7&n&&(t.level=7&n),32&n&&(t.hidden=!0),64&n&&(t.hpt=r/20),t}},523:{n:"Index"},545:{n:"Array",f:ga},549:{n:"DefaultRowHeight",f:sa},566:{n:"Table"},574:{n:"Window2",f:function(e,t,r){return r&&2<=r.biff&&r.biff<8?{}:{RTL:64&e.read_shift(2)}}},638:{n:"RK",f:function(e){var t=e.read_shift(2),r=e.read_shift(2),e=Kn(e);return{r:t,c:r,ixfe:e[0],rknum:e[1]}}},659:{n:"Style"},1030:{n:"Formula",f:Co},1048:{n:"BigName"},1054:{n:"Format",f:function(e,t,r){return[e.read_shift(2),zn(e,0,r)]}},1084:{n:"ContinueBigName"},1212:{n:"ShrFmla",f:function(e,t,r){var n=Jn(e);e.l++;var a=e.read_shift(1);return[function(e,t,r){var n,a=e.l+t,s=e.read_shift(2),i=wo(e,s,r);if(65535==s)return[[],It(e,t-2)];t!==s+2&&(n=Eo(e,a-s-2,i,r));return[i,n]}(e,t-=8,r),a,n]}},2048:{n:"HLinkTooltip",f:function(e,t){return e.read_shift(2),[Zn(e),e.read_shift((t-10)/2,"dbcs-cont").replace(re,"")]}},2049:{n:"WebPub"},2050:{n:"QsiSXTag"},2051:{n:"DBQueryExt"},2052:{n:"ExtString"},2053:{n:"TxtQry"},2054:{n:"Qsir"},2055:{n:"Qsif"},2056:{n:"RRDTQSIF"},2057:{n:"BOF",f:na},2058:{n:"OleDbConn"},2059:{n:"WOpt"},2060:{n:"SXViewEx"},2061:{n:"SXTH"},2062:{n:"SXPIEx"},2063:{n:"SXVDTEx"},2064:{n:"SXViewEx9"},2066:{n:"ContinueFrt"},2067:{n:"RealTimeData"},2128:{n:"ChartFrtInfo"},2129:{n:"FrtWrapper"},2130:{n:"StartBlock"},2131:{n:"EndBlock"},2132:{n:"StartObject"},2133:{n:"EndObject"},2134:{n:"CatLab"},2135:{n:"YMult"},2136:{n:"SXViewLink"},2137:{n:"PivotChartBits"},2138:{n:"FrtFontList"},2146:{n:"SheetExt"},2147:{n:"BookExt",r:12},2148:{n:"SXAddl"},2149:{n:"CrErr"},2150:{n:"HFPicture"},2151:{n:"FeatHdr",f:On},2152:{n:"Feat"},2154:{n:"DataLabExt"},2155:{n:"DataLabExtContents"},2156:{n:"CellWatch"},2161:{n:"FeatHdr11"},2162:{n:"Feature11"},2164:{n:"DropDownObjIds"},2165:{n:"ContinueFrt11"},2166:{n:"DConn"},2167:{n:"List12"},2168:{n:"Feature12"},2169:{n:"CondFmt12"},2170:{n:"CF12"},2171:{n:"CFEx"},2172:{n:"XFCRC",f:function(e){e.l+=2;var t={cxfs:0,crc:0};return t.cxfs=e.read_shift(2),t.crc=e.read_shift(4),t},r:12},2173:{n:"XFExt",f:function(e,t){e.l,e.l+=2,t=e.read_shift(2),e.l+=2;for(var r=e.read_shift(2),n=[];0a.l?a.slice(0,a.l):a).l&&(a.l=a.length),a))})}function Ic(e,t){for(var r=0;r=p){if(t.WTF)throw new Error("Range "+(o["!ref"]||"A1")+" exceeds format limit A1:IV16384");d.e.c=Math.min(d.e.c,255),d.e.r=Math.min(d.e.c,p-1)}Cc(s,2057,aa(0,16,t)),Cc(s,"CalcMode",Nn(1)),Cc(s,"CalcCount",Nn(100)),Cc(s,"CalcRefMode",Dn(!0)),Cc(s,"CalcIter",Dn(!1)),Cc(s,"CalcDelta",wr(.001)),Cc(s,"CalcSaveRecalc",Dn(!0)),Cc(s,"PrintRowCol",Dn(!1)),Cc(s,"PrintGrid",Dn(!1)),Cc(s,"GridSet",Nn(1)),Cc(s,"Guts",(r=[0,0],(e=Rt(8)).write_shift(4,0),e.write_shift(2,r[0]?r[0]+1:0),e.write_shift(2,r[1]?r[1]+1:0),e)),Cc(s,"HCenter",Dn(!1)),Cc(s,"VCenter",Dn(!1)),Cc(s,512,(p=d,(r=Rt(2*(e=8!=(r=t).biff&&r.biff?2:4)+6)).write_shift(e,p.s.r),r.write_shift(e,p.e.r+1),r.write_shift(2,p.s.c),r.write_shift(2,p.e.c+1),r.write_shift(2,0),r)),h&&(o["!links"]=[]);for(var m=d.s.r;m<=d.e.r;++m){a=Ht(m);for(var g=d.s.c;g<=d.e.c;++g){m===d.s.r&&(u[g]=Vt(g)),n=u[g]+a;var b=f?(o[m]||[])[g]:o[n];b&&(Rc(s,b,m,g,t),h&&b.l&&o["!links"].push([n,b.l]))}}var v,E,i=c.CodeName||c.name||i;return h&&l.Views&&Cc(s,"Window2",(l=l.Views[0],E=Rt(18),v=1718,l&&l.RTL&&(v|=64),E.write_shift(2,v),E.write_shift(4,0),E.write_shift(4,64),E.write_shift(4,0),E.write_shift(4,0),E)),h&&(o["!merges"]||[]).length&&Cc(s,"MergeCells",function(e){var t=Rt(2+8*e.length);t.write_shift(2,e.length);for(var r=0;r"+t),a.join("")}};function Mc(e,t){var r=t||{};null!=fe&&null==r.dense&&(r.dense=fe);var n=r.dense?[]:{},a=e.match(/");for(var s,t=e.match(/<\/table/i),i=a.index,o=t&&t.index||e.length,l=function(e,t,r){if(O||"string"==typeof t)return e.split(t);for(var n=e.split(t),a=[n[0]],s=1;s]*>)/i,""),c=-1,f=0,h={s:{r:1e7,c:1e7},e:{r:0,c:0}},u=[],i=0;i/i),o=0;o"));)b=b.slice(v+1);var E=me(g.slice(0,g.indexOf(">"))),w=E.colspan?+E.colspan:1;(1<(s=+E.rowspan)||1c&&(h.s.r=c),h.e.rf&&(h.s.c=f),h.e.cr||a[d].s.c>i||a[d].e.r'+f+""),c.id="sjs-"+o,s.push(Ge("td",f,c)))}return""+s.join("")+""}function Hc(e,t,r){return[].join("")+""}function zc(e,t){var r=t||{};null!=fe&&(r.dense=fe);for(var n,a,s=r.dense?[]:{},i=e.getElementsByTagName("tr"),o=r.sheetRows||1e7,l={s:{r:0,c:0},e:{r:0,c:0}},c=[],f=0,h=[],u=0,d=0;u/gm,"").replace(//gm,"");i=ac.exec(l);)switch(i[3]=i[3].replace(/_.*$/,"")){case"table":case"工作表":"/"===i[1]?(_.e.c>=_.s.c&&_.e.r>=_.s.r&&(m["!ref"]=$t(_)),0_.e.c&&(_.e.c=S),S<_.s.c&&(_.s.c=S),w<_.s.r&&(_.s.r=w),z>_.e.r&&(_.e.r=z),D=[],P={},o={t:(g=me(i[0],!1))["数据类型"]||g["value-type"],v:null},r.cellFormula)if(g.formula&&(g.formula=we(g.formula)),g["number-matrix-columns-spanned"]&&g["number-matrix-rows-spanned"]&&(T={s:{r:w,c:S},e:{r:w+(parseInt(g["number-matrix-rows-spanned"],10)||0)-1,c:S+(parseInt(g["number-matrix-columns-spanned"],10)||0)-1}},o.F=$t(T),I.push([T,o.F])),g.formula)o.f=Do(g.formula);else for(H=0;H=I[H][0].s.r&&w<=I[H][0].e.r&&S>=I[H][0].s.c&&S<=I[H][0].e.c&&(o.F=I[H][1]);switch((g["number-columns-spanned"]||g["number-rows-spanned"])&&(T={s:{r:w,c:S},e:{r:w+(parseInt(g["number-rows-spanned"],10)||0)-1,c:S+(parseInt(g["number-columns-spanned"],10)||0)-1}},B.push(T)),g["number-columns-repeated"]&&(A=parseInt(g["number-columns-repeated"],10)),o.t){case"boolean":o.t="b",o.v=Re(g["boolean-value"]);break;case"float":case"percentage":case"currency":o.t="n",o.v=parseFloat(g.value);break;case"date":o.t="d",o.v=le(g["date-value"]),r.cellDates||(o.t="n",o.v=X(o.v)),o.z="m/d/yy";break;case"time":o.t="n",o.v=function(e){var t=0,r=0,n=!1,a=e.match(/P([0-9\.]+Y)?([0-9\.]+M)?([0-9\.]+D)?T([0-9\.]+H)?([0-9\.]+M)?([0-9\.]+S)?/);if(!a)throw new Error("|"+e+"| is not an ISO8601 Duration");for(var s=1;s!=a.length;++s)if(a[s]){switch(r=1,3"===i[0].slice(-2))break;if("/"===i[1])switch(c[c.length-1][0]){case"number-style":case"date-style":case"time-style":h+=l.slice(u,i.index)}else u=i.index+i[0].length;break;case"named-range":F=Po((a=me(i[0],!1))["cell-range-address"]);var W={Name:a.name,Ref:F[0]+"!"+F[1]};U&&(W.Sheet=p.length),R.Names.push(W);break;case"text-content":case"text-properties":case"embedded-text":break;case"body":case"电子表格":case"forms":case"table-column":case"table-header-rows":case"table-rows":case"table-column-group":case"table-header-columns":case"table-columns":case"null-date":case"graphic-properties":case"calculation-settings":case"named-expressions":case"label-range":case"label-ranges":case"named-expression":case"sort":case"sort-by":case"sort-groups":case"tab":case"line-break":case"span":break;case"p":case"文本串":"/"!==i[1]||g&&g["string-value"]?(me(i[0],!1),v=i.index+i[0].length):(W=function(e){e=e.replace(/[\t\r\n]/g," ").trim().replace(/ +/g," ").replace(//g," ").replace(//g,function(e,t){return Array(parseInt(t,10)+1).join(" ")}).replace(/]*\/>/g,"\t").replace(//g,"\n");return[we(e.replace(/<[^>]*>/g,""))]}(l.slice(v,i.index)),b=(0",function(){return z+$c}),Zc=function(e,t){var r=[z],n=je({"xmlns:office":"urn:oasis:names:tc:opendocument:xmlns:office:1.0","xmlns:table":"urn:oasis:names:tc:opendocument:xmlns:table:1.0","xmlns:style":"urn:oasis:names:tc:opendocument:xmlns:style:1.0","xmlns:text":"urn:oasis:names:tc:opendocument:xmlns:text:1.0","xmlns:draw":"urn:oasis:names:tc:opendocument:xmlns:drawing:1.0","xmlns:fo":"urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0","xmlns:xlink":"http://www.w3.org/1999/xlink","xmlns:dc":"http://purl.org/dc/elements/1.1/","xmlns:meta":"urn:oasis:names:tc:opendocument:xmlns:meta:1.0","xmlns:number":"urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0","xmlns:presentation":"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0","xmlns:svg":"urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0","xmlns:chart":"urn:oasis:names:tc:opendocument:xmlns:chart:1.0","xmlns:dr3d":"urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0","xmlns:math":"http://www.w3.org/1998/Math/MathML","xmlns:form":"urn:oasis:names:tc:opendocument:xmlns:form:1.0","xmlns:script":"urn:oasis:names:tc:opendocument:xmlns:script:1.0","xmlns:ooo":"http://openoffice.org/2004/office","xmlns:ooow":"http://openoffice.org/2004/writer","xmlns:oooc":"http://openoffice.org/2004/calc","xmlns:dom":"http://www.w3.org/2001/xml-events","xmlns:xforms":"http://www.w3.org/2002/xforms","xmlns:xsd":"http://www.w3.org/2001/XMLSchema","xmlns:xsi":"http://www.w3.org/2001/XMLSchema-instance","xmlns:sheet":"urn:oasis:names:tc:opendocument:sh33tjs:1.0","xmlns:rpt":"http://openoffice.org/2005/report","xmlns:of":"urn:oasis:names:tc:opendocument:xmlns:of:1.2","xmlns:xhtml":"http://www.w3.org/1999/xhtml","xmlns:grddl":"http://www.w3.org/2003/g/data-view#","xmlns:tableooo":"http://openoffice.org/2009/table","xmlns:drawooo":"http://openoffice.org/2010/draw","xmlns:calcext":"urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0","xmlns:loext":"urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0","xmlns:field":"urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0","xmlns:formx":"urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0","xmlns:css3t":"http://www.w3.org/TR/css3-text/","office:version":"1.2"}),a=je({"xmlns:config":"urn:oasis:names:tc:opendocument:xmlns:config:1.0","office:mimetype":"application/vnd.oasis.opendocument.spreadsheet"});"fods"==t.bookType?r.push("\n"):r.push("\n"),(n=r).push(" \n"),n.push(' \n'),n.push(' \n'),n.push(" /\n"),n.push(' \n'),n.push(" /\n"),n.push(" \n"),n.push(" \n"),n.push(' \n'),n.push(" \n"),r.push(" \n"),r.push(" \n");for(var s=0;s!=e.SheetNames.length;++s)r.push(Qc(e.Sheets[e.SheetNames[s]],e,s));return r.push(" \n"),r.push(" \n"),"fods"==t.bookType?r.push(""):r.push(""),r.join("")};function Qc(e,t,r){var n=[];n.push(' \n');for(var a=0,s=0,i=Gt(e["!ref"]),o=e["!merges"]||[],l=0,c=Array.isArray(e),a=0;a\n");for(;a<=i.e.r;++a){for(n.push(" \n"),s=0;ss||o[l].s.r>a||o[l].e.c\n");else{var d=jt({r:a,c:s}),p=c?(e[a]||[])[s]:e[d];if(p&&p.f&&(h["table:formula"]=ye(("of:="+p.f.replace(ji,"$1[.$2$3$4$5]").replace(/\]:\[/g,":")).replace(/;/g,"|").replace(/,/g,";")),p.F&&p.F.slice(0,d.length)==d&&(m=Gt(p.F),h["table:number-matrix-columns-spanned"]=m.e.c-m.s.c+1,h["table:number-matrix-rows-spanned"]=m.e.r-m.s.r+1)),p){switch(p.t){case"b":u=p.v?"TRUE":"FALSE",h["office:value-type"]="boolean",h["office:boolean-value"]=p.v?"true":"false";break;case"n":u=p.w||String(p.v||0),h["office:value-type"]="float",h["office:value"]=p.v||0;break;case"s":case"str":u=p.v,h["office:value-type"]="string";break;case"d":u=p.w||le(p.v).toISOString(),h["office:value-type"]="date",h["office:date-value"]=le(p.v).toISOString(),h["table:style-name"]="ce1";break;default:n.push(Yc);continue}var m,d=ye(u).replace(/ +/g,function(e){return''}).replace(/\t/g,"").replace(/\n/g,"").replace(/^ /,"").replace(/ $/,"");p.l&&p.l.Target&&(d=Ge("text:a",d,{"xlink:href":m="#"==(m=p.l.Target).charAt(0)?"#"+m.slice(1).replace(/\./,"!"):m})),n.push(" "+Ge("table:table-cell",Ge("text:p",d,{}),h)+"\n")}else n.push(Yc)}}n.push(" \n")}return n.push(" \n"),n.join("")}function Jc(e,t){if("fods"==t.bookType)return Zc(e,t);var r=new I,n=[],a=[];return r.file("mimetype","application/vnd.oasis.opendocument.spreadsheet"),r.file("content.xml",Zc(e,t)),n.push(["content.xml","text/xml"]),a.push(["content.xml","ContentFile"]),r.file("styles.xml",Kc(e,t)),n.push(["styles.xml","text/xml"]),a.push(["styles.xml","StylesFile"]),r.file("meta.xml",tn()),n.push(["meta.xml","text/xml"]),a.push(["meta.xml","MetadataFile"]),r.file("manifest.rdf",function(e){var t=[z];t.push('\n');for(var r=0;r!=e.length;++r)t.push(qr(e[r][0],e[r][1])),t.push([' \n',' \n'," \n"].join(""));return t.push(qr("","Document","pkg")),t.push(""),t.join("")}(a)),n.push(["manifest.rdf","application/rdf+xml"]),r.file("META-INF/manifest.xml",function(e){var t=[z];t.push('\n'),t.push(' \n');for(var r=0;r\n');return t.push(""),t.join("")}(n)),r}function qc(n){return function(e,t){var r=function(e,t){if(!t)return 0;if(-1==(e=e.SheetNames.indexOf(t)))throw new Error("Sheet not found: "+t);return e}(e,t.sheet);return n.from_sheet(e.Sheets[e.SheetNames[r]],t,e)}}var ef=qc(Lc),tf=qc({from_sheet:Ff}),rf=qc(ka),nf=qc(Ia),af=qc(Xa),sf=qc(Ns),of=qc({from_sheet:Df}),lf=qc(Ba),cf=qc(Ua);function ff(n){return function(e){for(var t=0;t!=n.length;++t){var r=n[t];void 0===e[r[0]]&&(e[r[0]]=r[1]),"n"===r[2]&&(e[r[0]]=Number(e[r[0]]))}}}var hf=ff([["cellNF",!(Yc=" \n")],["cellHTML",!0],["cellFormula",!0],["cellStyles",!1],["cellText",!0],["cellDates",!1],["sheetStubs",!1],["sheetRows",0,"n"],["bookDeps",!1],["bookSheets",!1],["bookProps",!1],["bookFiles",!1],["bookVBA",!1],["password",""],["WTF",!1]]),uf=ff([["cellDates",!1],["bookSST",!1],["bookType","xlsx"],["compression",!1],["WTF",!1]]);function df(t,e){if(!t)return 0;try{t=e.map(function(e){return e.id||(e.id=e.strRelID),[e.name,t["!id"][e.id].Target,(e=t["!id"][e.id].Type,-1]*>([\\s\\S]*?)"));t&&0":n=null;break;default:if(0===i.indexOf(""),c=l[0].slice(4),f=l[1];switch(c){case"lpstr":case"bstr":case"lpwstr":r[n]=we(f);break;case"bool":r[n]=Re(f);break;case"i1":case"i2":case"i4":case"i8":case"int":case"uint":r[n]=parseInt(f,10);break;case"r4":case"r8":case"decimal":r[n]=parseFloat(f);break;case"filetime":case"date":r[n]=le(f);break;case"cy":case"error":r[n]=we(f);break;default:if("/"==c.slice(-1))break;t.WTF&&"undefined"!=typeof console&&console.warn("Unexpected",i,c,l)}}else if("]*r:id="([^"]*)"/)||["",""])[1],p["!id"][m].Target):"??"),d),d=$r(m),g=Tl(U(e,m,!0),0,0,Yr(U(e,d,!0),m),0,g);break;case"macro":b=t,s[n],b.slice(-4),g={"!type":"macro"};break;case"dialog":b=t,s[n],b.slice(-4),g={"!type":"dialog"}}i[n]=g}catch(e){if(l.WTF)throw e}var b,m}(t,g,b,u.SheetNames[v],v,E,e,T,r,h,i,o)}return a.comments&&Pi(t,a.comments,e,E,r),m={Directory:a,Workbook:h,Props:u,Custprops:p,Deps:d,Sheets:e,SheetNames:u.SheetNames,Strings:No,Styles:o,Themes:i,SSF:ue.get_table()},r.bookFiles&&(m.keys=n,m.files=t.files),r.bookVBA&&(0",">")),n.join("")}(e.Props,t)),a.coreprops.push(u),Qr(t.rels,2,u,Gr.CORE_PROPS),u="docProps/app.xml",!e.Props||!e.Props.SheetNames)if(e.Workbook&&e.Workbook.Sheets){for(var p=[],m=0;mWorksheets")+o("vt:variant",o("vt:i4",String(s.Worksheets))),{size:2,baseType:"variant"})),i[i.length]=o("TitlesOfParts",o("vt:vector",s.SheetNames.map(function(e){return""+ye(e)+""}).join(""),{size:s.Worksheets,baseType:"lpstr"})),2",i[1]=i[1].replace("/>",">")),i.join(""))),a.extprops.push(u),Qr(t.rels,3,u,Gr.EXT_PROPS),e.Custprops!==e.Props&&0/,">"),Ge("o:shapelayout",Ge("o:idmap",null,{"v:ext":"edit",data:e}),{"v:ext":"edit"}),Ge("v:shapetype",[Ge("v:stroke",null,{joinstyle:"miter"}),Ge("v:path",null,{gradientshapeok:"t","o:connecttype":"rect"})].join(""),{id:"_x0000_t202","o:spt":202,coordsize:r.join(","),path:n})];Di<1e3*e;)Di+=1e3;return t.forEach(function(e){var t=Xt(e[0]);a=a.concat(["",Ge("v:fill",Ge("o:fill",null,{type:"gradientUnscaled","v:ext":"view"}),{color2:"#BEFF82",angle:"-180",type:"gradient"}),Ge("v:shadow",null,{on:"t",obscured:"t"}),Ge("v:path",null,{"o:connecttype":"none"}),'
    ','',"","",Xe("x:Anchor",[t.c,0,t.r,0,t.c+3,100,t.r+5,100].join(",")),Xe("x:AutoFill","False"),Xe("x:Row",String(t.r)),Xe("x:Column",String(t.c)),e[1].hidden?"":"","",""])}),a.push(""),a.join("")}(d,w["!comments"])),delete w["!comments"],delete w["!legacy"]),E["!id"].rId1&&h.file($r(u),Zr(E))}return null!=t.Strings&&0t)return t;throw new Error("Cannot find sheet # "+t)}if("string"!=typeof t)throw new Error("Cannot find sheet |"+t+"|");if(-1<(e=e.SheetNames.indexOf(t)))return e;throw new Error("Cannot find sheet name |"+t+"|")}(e,t);switch(Uf(e.Workbook.Sheets,t,{}),r){case 0:case 1:case 2:break;default:throw new Error("Bad sheet visibility setting "+r)}e.Workbook.Sheets[t].Hidden=r},[["SHEET_VISIBLE",0],["SHEET_HIDDEN",1],["SHEET_VERY_HIDDEN",2]].forEach(function(e){Lf.consts[e[0]]=e[1]}),Lf.cell_set_number_format=function(e,t){return e.z=t,e},Lf.cell_set_hyperlink=function(e,t,r){return t?(e.l={Target:t},r&&(e.l.Tooltip=r)):delete e.l,e},Lf.cell_set_internal_link=function(e,t,r){return Lf.cell_set_hyperlink(e,"#"+t,r)},Lf.cell_add_comment=function(e,t,r){e.c||(e.c=[]),e.c.push({t:t,a:r||"SheetJS"})},Lf.sheet_set_array_formula=function(e,t,r){for(var n="string"!=typeof t?t:Yt(t),a="string"==typeof t?t:$t(t),s=n.s.r;s<=n.e.r;++s)for(var i=n.s.c;i<=n.e.c;++i){var o=function e(t,r,n){return"string"==typeof r?t[r]||(t[r]={t:"z"}):e(t,jt("number"!=typeof r?r:{r:r,c:n||0}))}(e,s,i);o.t="n",o.F=a,delete o.v,s==n.s.r&&i==n.s.c&&(o.f=r)}return e},Z&&"undefined"!=typeof require&&(Mf={}.Readable,n.stream={to_json:function(t,e){var r=Mf({objectMode:!0});if(null==t||null==t["!ref"])return r.push(null),r;var n,a={t:"n",v:0},s=0,i=1,o=[],l="",c={s:{r:0,c:0},e:{r:0,c:0}},f=e||{},h=null!=f.range?f.range:t["!ref"];switch(1===f.header?s=1:"A"===f.header?s=2:Array.isArray(f.header)&&(s=3),typeof h){case"string":c=Yt(h);break;case"number":(c=Yt(t["!ref"])).s.r=h;break;default:c=h}0c.e.r)return r.push(null);for(;g<=c.e.r;){var e=Af(t,c,g,d,s,o,m,f);if(++g,!1===e.isempty||(1===s?!1!==f.blankrows:f.blankrows)){r.push(e.row);break}}},r},to_html:function(e,t){var r=Mf(),n=t||{},t=null!=n.header?n.header:Lc.BEGIN,a=null!=n.footer?n.footer:Lc.END;r.push(t);var s=Gt(e["!ref"]);n.dense=Array.isArray(e),r.push(Lc._preamble(e,s,n));var i=s.s.r,o=!1;return r._read=function(){if(i>s.e.r)return o||(o=!0,r.push("
    "+a)),r.push(null);for(;i<=s.e.r;){r.push(Lc._row(e,s,i,n)),++i;break}},r},to_csv:function(e,t){var r=Mf(),n=null==t?{}:t;if(null==e||null==e["!ref"])return r.push(null),r;var a=Yt(e["!ref"]),s=void 0!==n.FS?n.FS:",",i=s.charCodeAt(0),o=void 0!==n.RS?n.RS:"\n",l=o.charCodeAt(0),c=new RegExp(("|"==s?"\\|":s)+"+$"),f="",h=[];n.dense=Array.isArray(e);for(var u=n.skipHidden&&e["!cols"]||[],d=n.skipHidden&&e["!rows"]||[],p=a.s.c;p<=a.e.c;++p)(u[p]||{}).hidden||(h[p]=Vt(p));var m=a.s.r,g=!1;return r._read=function(){if(!g)return g=!0,r.push("\ufeff");if(m>a.e.r)return r.push(null);for(;m<=a.e.r;)if(++m,!(d[m-1]||{}).hidden&&null!=(f=Of(e,a,m-1,h,i,l,s,n))){n.strip&&(f=f.replace(c,"")),r.push(f+o);break}},r}});var Hf=function(){function n(e,t,r){return this instanceof n?(this.tagName=e,this._attributes=t||{},this._children=r||[],this._prefix="",this):new n(e,t,r)}n.prototype.createElement=function(){return new n(arguments)},n.prototype.children=function(){return this._children},n.prototype.append=function(e){return this._children.push(e),this},n.prototype.prefix=function(e){return 0==arguments.length?this._prefix:(this._prefix=e,this)},n.prototype.attr=function(e,t){if(null==t)return delete this._attributes[e],this;if(0==arguments.length)return this._attributes;if("string"==typeof e&&1==arguments.length)return this._attributes.attr[e];if("object"==typeof e&&1==arguments.length)for(var r in e)this._attributes[r]=e[r];else 2==arguments.length&&"string"==typeof e&&(this._attributes[e]=t);return this};QUOTE='"';var e={};return e[QUOTE]=""",e["'"]="'",n.prototype.escapeAttributeValue=function(e){return'"'+e.replace(/\"/g,""")+'"'},n.prototype.toXml=function(e){var t=(e=e||this)._prefix;if(t+="<"+e.tagName,e._attributes)for(var r in e._attributes)t+=" "+r+"="+this.escapeAttributeValue(""+e._attributes[r]);if(e._children&&0";for(var n=0;n"}else t+="/>";return t},n}(),zf=function(e){var t,r=164,n={0:"General",1:"0",2:"0.00",3:"#,##0",4:"#,##0.00",9:"0%",10:"0.00%",11:"0.00E+00",12:"# ?/?",13:"# ??/??",14:"m/d/yy",15:"d-mmm-yy",16:"d-mmm",17:"mmm-yy",18:"h:mm AM/PM",19:"h:mm:ss AM/PM",20:"h:mm",21:"h:mm:ss",22:"m/d/yy h:mm",37:"#,##0 ;(#,##0)",38:"#,##0 ;[Red](#,##0)",39:"#,##0.00;(#,##0.00)",40:"#,##0.00;[Red](#,##0.00)",45:"mm:ss",46:"[h]:mm:ss",47:"mmss.0",48:"##0.0E+0",49:"@",56:'"上午/下午 "hh"時"mm"分"ss"秒 "'},a={};for(t in n)a[n[t]]=t;return _hashIndex={},_listIndex=[],{initialize:function(e){this.$fonts=Hf("fonts").attr("count",0).attr("x14ac:knownFonts","1"),this.$fills=Hf("fills").attr("count",0),this.$borders=Hf("borders").attr("count",0),this.$numFmts=Hf("numFmts").attr("count",0),this.$cellStyleXfs=Hf("cellStyleXfs"),this.$xf=Hf("xf").attr("numFmtId",0).attr("fontId",0).attr("fillId",0).attr("borderId",0),this.$cellXfs=Hf("cellXfs").attr("count",0),this.$cellStyles=Hf("cellStyles").append(Hf("cellStyle").attr("name","Normal").attr("xfId",0).attr("builtinId",0)),this.$dxfs=Hf("dxfs").attr("count","0"),this.$tableStyles=Hf("tableStyles").attr("count","0").attr("defaultTableStyle","TableStyleMedium9").attr("defaultPivotStyle","PivotStyleMedium4"),this.$styles=Hf("styleSheet").attr("xmlns:mc","http://schemas.openxmlformats.org/markup-compatibility/2006").attr("xmlns:x14ac","http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac").attr("xmlns","http://schemas.openxmlformats.org/spreadsheetml/2006/main").attr("mc:Ignorable","x14ac").prefix('').append(this.$numFmts).append(this.$fonts).append(this.$fills).append(this.$borders).append(this.$cellStyleXfs.append(this.$xf)).append(this.$cellXfs).append(this.$cellStyles).append(this.$dxfs).append(this.$tableStyles);var t=e.defaultCellStyle||{};t.font||(t.font={name:"Calibri",sz:"12"}),t.font.name||(t.font.name="Calibri"),t.font.sz||(t.font.sz=11),t.fill||(t.fill={patternType:"none",fgColor:{}}),t.border||(t.border={}),t.numFmt||(t.numFmt=0),this.defaultStyle=t;e=JSON.parse(JSON.stringify(t));return e.fill={patternType:"gray125",fgColor:{}},this.addStyles([t,e]),this},addStyle:function(e){var t=JSON.stringify(e),r=_hashIndex[t];return null==r?(r=this._addXf(e),_hashIndex[t]=r):r=_hashIndex[t],r},addStyles:function(e){var t=this;return e.map(function(e){return t.addStyle(e)})},_duckTypeStyle:function(e){return"object"==typeof e&&(e.patternFill||e.fgColor)?{fill:e}:e.font||e.numFmt||e.border||e.fill?e:this._getStyleCSS(e)},_getStyleCSS:function(e){return e},_addXf:function(e){var t=this._addFont(e.font),r=this._addFill(e.fill),n=this._addBorder(e.border),a=this._addNumFmt(e.numFmt),s=Hf("xf").attr("numFmtId",a).attr("fontId",t).attr("fillId",r).attr("borderId",n).attr("xfId","0");0/g,">").replace(/"/g,""").replace(/'/g,"'");e=Hf("numFmt").attr("numFmtId",++r).attr("formatCode",e);this.$numFmts.append(e);e=this.$numFmts.children().length;return this.$numFmts.attr("count",e),r},_addFill:function(e){if(!e)return 0;var t,r=Hf("patternFill").attr("patternType",e.patternType||"solid");e.fgColor&&(t=Hf("fgColor"),e.fgColor.rgb?(6==e.fgColor.rgb.length&&(e.fgColor.rgb="FF"+e.fgColor.rgb),t.attr("rgb",e.fgColor.rgb),r.append(t)):e.fgColor.theme&&(t.attr("theme",e.fgColor.theme),e.fgColor.tint&&t.attr("tint",e.fgColor.tint),r.append(t)),e.bgColor||(e.bgColor={indexed:"64"})),e.bgColor&&(e=Hf("bgColor").attr(e.bgColor),r.append(e));r=Hf("fill").append(r);this.$fills.append(r);r=this.$fills.children().length;return this.$fills.attr("count",r),r-1},_getSubBorder:function(e,t){var r=Hf(e);return t&&(t.style&&r.attr("style",t.style),t.color&&(e=Hf("color"),t.color.auto?e.attr("auto",t.color.auto):t.color.rgb?e.attr("rgb",t.color.rgb):(t.color.theme||t.color.tint)&&(e.attr("theme",t.color.theme||"1"),e.attr("tint",t.color.tint||"0")),r.append(e))),r},_addBorder:function(t){if(!t)return 0;var r=this,n=Hf("border").attr("diagonalUp",t.diagonalUp).attr("diagonalDown",t.diagonalDown);["left","right","top","bottom","diagonal"].forEach(function(e){n.append(r._getSubBorder(e,t[e]))}),this.$borders.append(n);var e=this.$borders.children().length;return this.$borders.attr("count",e),e-1},toXml:function(){return this.$styles.toXml()}}.initialize(e||{})};n.parse_xlscfb=vc,n.parse_ods=jc,n.parse_fods=Gc,n.write_ods=Jc,n.parse_zip=mf,n.read=Sf,n.readFile=_f,n.readFileSync=_f,n.write=Tf,n.writeFile=xf,n.writeFileSync=xf,n.writeFileAsync=function(e,t,r,n){var a=r||{};return a.type="file",a.file=e,kf(a),a.type="buffer",n instanceof Function||(n=r),b.writeFile(e,Tf(t,a),n)},n.utils=ia,n.SSF=ue,n.CFB=oe}"undefined"!=typeof exports?make_xlsx_lib(exports):"undefined"!=typeof module&&module.exports?make_xlsx_lib(module.exports):"function"==typeof define&&define.amd?define("xlsx",function(){return XLSX.version||make_xlsx_lib(XLSX),XLSX}):make_xlsx_lib(XLSX);var XLS=XLSX,ODS=XLSX;!function(d){"use strict";var p=void 0,t=1e5;function m(e){switch(typeof e){case"undefined":return"undefined";case"boolean":return"boolean";case"number":return"number";case"string":return"string";default:return null===e?"null":"object"}}function g(e){return Object.prototype.toString.call(e).replace(/^\[object *|\]$/g,"")}function b(e){return"function"==typeof e}function v(e){if(null===e||e===p)throw TypeError();return Object(e)}var n,e,o,E=Math.LN2,w=Math.abs,S=Math.floor,_=Math.log,y=Math.max,C=Math.min,B=Math.pow,r=Math.round;function T(r){if(!("TYPED_ARRAY_POLYFILL_NO_ARRAY_ACCESSORS"in d)){if(r.length>t)throw RangeError("Array too large for polyfill");for(var e=0;e>t}function s(e,t){t=32-t;return e<>>t}function k(e){return[255&e]}function x(e){return a(e[0],8)}function A(e){return[255&e]}function I(e){return s(e[0],8)}function R(e){return[(e=r(Number(e)))<0?0:255>8&255]}function F(e){return a(e[1]<<8|e[0],16)}function D(e){return[255&e,e>>8&255]}function P(e){return s(e[1]<<8|e[0],16)}function N(e){return[255&e,e>>8&255,e>>16&255,e>>24&255]}function L(e){return a(e[3]<<24|e[2]<<16|e[1]<<8|e[0],32)}function M(e){return[255&e,e>>8&255,e>>16&255,e>>24&255]}function U(e){return s(e[3]<<24|e[2]<<16|e[1]<<8|e[0],32)}function i(e,t,r){var n,a,s,i,o=(1<=B(2,1-o)?(n=C(S(_(e)/E),1023),(a=e/B(2,n))<1&&(--n,a*=2),2<=a&&(n+=1,a/=2),n+=o,1<=(s=l(a*(a=B(2,r)))-a)/a&&(n+=1,s=0),2*o>=1;return c.reverse(),l=c.join(""),s=(1<>>=0)>e.byteLength)throw RangeError("byteOffset out of range");if(r===p?r=e.byteLength-t:r>>>=0,t+r>e.byteLength)throw RangeError("byteOffset and length reference an area beyond the end of the buffer");Object.defineProperty(this,"buffer",{value:e}),Object.defineProperty(this,"byteLength",{value:r}),Object.defineProperty(this,"byteOffset",{value:t})}function h(s){return function(e,t){if((e>>>=0)+s.BYTES_PER_ELEMENT>this.byteLength)throw RangeError("Array index out of range");e+=this.byteOffset;for(var r=new Uint8Array(this.buffer,e,s.BYTES_PER_ELEMENT),n=[],a=0;a>>=0)+i.BYTES_PER_ELEMENT>this.byteLength)throw RangeError("Array index out of range");for(var t=new i([t]),n=new Uint8Array(t.buffer),a=[],s=0;s>=0)<0)throw RangeError("ArrayBuffer size is not a small enough positive integer.");Object.defineProperty(this,"byteLength",{value:e}),Object.defineProperty(this,"_bytes",{value:Array(e)});for(var t=0;t>=0)<0)throw RangeError("length is not a small enough positive integer.");Object.defineProperty(this,"length",{value:e}),Object.defineProperty(this,"byteLength",{value:e*this.BYTES_PER_ELEMENT}),Object.defineProperty(this,"buffer",{value:new a(this.byteLength)}),Object.defineProperty(this,"byteOffset",{value:0})}.apply(this,arguments);if(1<=arguments.length&&"object"===m(arguments[0])&&arguments[0]instanceof s)return function(e){if(this.constructor!==e.constructor)throw TypeError();var t=e.length*this.BYTES_PER_ELEMENT;Object.defineProperty(this,"buffer",{value:new a(t)}),Object.defineProperty(this,"byteLength",{value:t}),Object.defineProperty(this,"byteOffset",{value:0}),Object.defineProperty(this,"length",{value:e.length});for(var r=0;r>>=0)>e.byteLength)throw RangeError("byteOffset out of range");if(t%this.BYTES_PER_ELEMENT)throw RangeError("buffer length minus the byteOffset is not a multiple of the element size.");if(r===p){var n=e.byteLength-t;if(n%this.BYTES_PER_ELEMENT)throw RangeError("length of buffer minus byteOffset not a multiple of the element size");r=n/this.BYTES_PER_ELEMENT}else n=(r>>>=0)*this.BYTES_PER_ELEMENT;if(t+n>e.byteLength)throw RangeError("byteOffset and length reference an area beyond the end of the buffer");Object.defineProperty(this,"buffer",{value:e}),Object.defineProperty(this,"byteLength",{value:n}),Object.defineProperty(this,"byteOffset",{value:t}),Object.defineProperty(this,"length",{value:r})}.apply(this,arguments);throw TypeError()}d.ArrayBuffer=d.ArrayBuffer||a,Object.defineProperty(s,"from",{value:function(e){return new this(e)}}),Object.defineProperty(s,"of",{value:function(){return new this(arguments)}});var i={};function e(e,t,r){var n=function(){Object.defineProperty(this,"constructor",{value:n}),s.apply(this,arguments),T(this)};"__proto__"in n?n.__proto__=s:(n.from=s.from,n.of=s.of),n.BYTES_PER_ELEMENT=e;function a(){}return a.prototype=i,n.prototype=new a,Object.defineProperty(n.prototype,"BYTES_PER_ELEMENT",{value:e}),Object.defineProperty(n.prototype,"_pack",{value:t}),Object.defineProperty(n.prototype,"_unpack",{value:r}),n}s.prototype=i,Object.defineProperty(s.prototype,"_getter",{value:function(e){if(arguments.length<1)throw SyntaxError("Not enough arguments");if((e>>>=0)>=this.length)return p;for(var t=[],r=0,n=this.byteOffset+e*this.BYTES_PER_ELEMENT;r>>=0)>=this.length))for(var r=this._pack(t),n=0,a=this.byteOffset+e*this.BYTES_PER_ELEMENT;n>>0,0),e=e>>0,i=e<0?y(s+e,0):C(e,s),t=t>>0,o=t<0?y(s+t,0):C(t,s),n=n===p?s:n>>0,n=n<0?y(s+n,0):C(n,s),l=C(n-o,s-i);for(o>>0;if(!b(e))throw TypeError();for(var n=arguments[1],a=0;a>>0,0),t=t>>0,s=t<0?y(a+t,0):C(t,a),r=r===p?a:r>>0,i=r<0?y(a+r,0):C(r,a);s>>0;if(!b(e))throw TypeError();for(var n=[],a=arguments[1],s=0;s>>0;if(!b(e))throw TypeError();for(var n=1>>0;if(!b(e))throw TypeError();for(var n=1>>0;if(!b(e))throw TypeError();for(var n=arguments[1],a=0;a>>0;if(0==r)return-1;var n=0;if(0>>0,n=Array(r),a=0;a>>0;if(0==r)return-1;var n=r;1>>0;if(!b(e))throw TypeError();var n=[];n.length=r;for(var a=arguments[1],s=0;s>>0;if(!b(e))throw TypeError();if(0==r&&1===arguments.length)throw TypeError();for(var n=0,a=2<=arguments.length?arguments[1]:t._getter(n++);n>>0;if(!b(e))throw TypeError();if(0==r&&1===arguments.length)throw TypeError();for(var n=r-1,a=2<=arguments.length?arguments[1]:t._getter(n--);0<=n;)a=e.call(p,a,t._getter(n),n,t),n--;return a}}),Object.defineProperty(s.prototype,"reverse",{value:function(){if(this===p||null===this)throw TypeError();for(var e=Object(this),t=e.length>>>0,r=S(t/2),n=0,a=t-1;n>>0)+(r=e).length>this.length)throw RangeError("Offset plus length of array is out of range");if(c=this.byteOffset+a*this.BYTES_PER_ELEMENT,f=r.length*this.BYTES_PER_ELEMENT,r.buffer===this.buffer){for(h=[],i=0,o=r.byteOffset;i>>0)+(s=(n=e).length>>>0)>this.length)throw RangeError("Offset plus length of array is out of range");for(i=0;i>>0,e=e>>0,a=e<0?y(n+e,0):C(e,n),t=t===p?n:t>>0,s=t<0?y(n+t,0):C(t,n),n=s-a,i=new r.constructor(n),o=0;a>>0;if(!b(e))throw TypeError();for(var n=arguments[1],a=0;a>>0,n=Array(t),a=0;a>=0,t>>=0,arguments.length<1&&(e=0),arguments.length<2&&(t=this.length),e<0&&(e=this.length+e),t<0&&(t=this.length+t),e=r(e,0,this.length);t=(t=r(t,0,this.length))-e;return t<0&&(t=0),new this.constructor(this.buffer,this.byteOffset+e*this.BYTES_PER_ELEMENT,t)}});var t=e(1,k,x),r=e(1,A,I),n=e(1,R,I),o=e(2,O,F),l=e(2,D,P),c=e(4,N,L),f=e(4,M,U),h=e(4,W,V),u=e(8,z,H);d.Int8Array=d.Int8Array||t,d.Uint8Array=d.Uint8Array||r,d.Uint8ClampedArray=d.Uint8ClampedArray||n,d.Int16Array=d.Int16Array||o,d.Uint16Array=d.Uint16Array||l,d.Int32Array=d.Int32Array||c,d.Uint32Array=d.Uint32Array||f,d.Float32Array=d.Float32Array||h,d.Float64Array=d.Float64Array||u}(),e=new Uint16Array([4660]),o=18===c(new Uint8Array(e.buffer),0),Object.defineProperty(f.prototype,"getUint8",{value:h(Uint8Array)}),Object.defineProperty(f.prototype,"getInt8",{value:h(Int8Array)}),Object.defineProperty(f.prototype,"getUint16",{value:h(Uint16Array)}),Object.defineProperty(f.prototype,"getInt16",{value:h(Int16Array)}),Object.defineProperty(f.prototype,"getUint32",{value:h(Uint32Array)}),Object.defineProperty(f.prototype,"getInt32",{value:h(Int32Array)}),Object.defineProperty(f.prototype,"getFloat32",{value:h(Float32Array)}),Object.defineProperty(f.prototype,"getFloat64",{value:h(Float64Array)}),Object.defineProperty(f.prototype,"setUint8",{value:u(Uint8Array)}),Object.defineProperty(f.prototype,"setInt8",{value:u(Int8Array)}),Object.defineProperty(f.prototype,"setUint16",{value:u(Uint16Array)}),Object.defineProperty(f.prototype,"setInt16",{value:u(Int16Array)}),Object.defineProperty(f.prototype,"setUint32",{value:u(Uint32Array)}),Object.defineProperty(f.prototype,"setInt32",{value:u(Int32Array)}),Object.defineProperty(f.prototype,"setFloat32",{value:u(Float32Array)}),Object.defineProperty(f.prototype,"setFloat64",{value:u(Float64Array)}),d.DataView=d.DataView||f}(self); \ No newline at end of file diff --git a/public/static/layuimini/js/lay-module/soulTable/soulTable.js b/public/static/layuimini/js/lay-module/soulTable/soulTable.js new file mode 100644 index 0000000..f53feb4 --- /dev/null +++ b/public/static/layuimini/js/lay-module/soulTable/soulTable.js @@ -0,0 +1,9 @@ +/** + * + * @name: 表格增强插件 + * @author: yelog + * @link: https://github.com/yelog/layui-soul-table + * @license: MIT + * @version: v1.6.2 + */ +layui.define(["table","tableFilter","tableChild","tableMerge"],function(exports){var tableFilter=layui.tableFilter,tableChild=layui.tableChild,tableMerge=layui.tableMerge,$=layui.$,table=layui.table,HIDE="layui-hide",tables={},originCols={},defaultConfig={fixTotal:!1,drag:!0,rowDrag:!1,autoColumnWidth:!0,contextmenu:!1,fixResize:!0,overflow:!1,fixFixedScroll:!0,filter:!1},_BODY=$("body"),_DOC=$(document),mod={render:function(e){tables[e.id]=e;var t,i,l,a=$.extend({},defaultConfig,e);a.filter&&a.filter.cache?(t=location.pathname+location.hash+e.id,i=this.deepStringify(e.cols),originCols[e.id]||(originCols[e.id]=this.deepClone(e.cols),(l=localStorage.getItem(t))&&i===localStorage.getItem("origin"+t)?this.updateCols(e,this.deepParse(l)):(localStorage.setItem("origin"+t,i),localStorage.removeItem(t)))):this.clearCache(e),tableFilter.render(e),tableChild.render(e),tableMerge.render(e),this.suspendConfig[e.id]={drag:!1,rowDrag:!1},a.fixTotal&&this.fixTotal(e),a.drag&&this.drag(e,a.drag),a.rowDrag&&this.rowDrag(e,a.rowDrag),a.autoColumnWidth&&this.autoColumnWidth(e,a.autoColumnWidth),this.contextmenu(e,a.contextmenu),a.fixResize&&this.fixResizeRightFixed(e),a.overflow&&this.overflow(e,a.overflow),a.fixFixedScroll&&this.fixFixedScroll(e)},config:function(e){"object"==typeof e&&$.extend(!0,defaultConfig,e)},updateCols:function(a,n){for(var o,r,e={},t=[],i=[],l=$(a.elem).next().children(".layui-table-box"),d=l.children(".layui-table-fixed").children(".layui-table-header").children("table"),s=$.merge(l.children(".layui-table-header").children("table"),d),c=l.children(".layui-table-header").children("table"),h=l.children(".layui-table-fixed").children(".layui-table-body").children("table"),u=l.children(".layui-table-body").children("table"),f=$.merge(l.children(".layui-table-body").children("table"),h),b=0;b"+this+"
    ").css({position:"absolute",float:"left","white-space":"nowrap",visibility:"hidden",font:t}).appendTo(_BODY),t=e.width();return e.remove(),t},void 0!==n&&void 0!==n.dblclick&&!n.dblclick||t.add(l).on("dblclick",function(e){var t=$(this),e=e.clientX-t.offset().left;a(i,t,0
    左固定
    不固定
    右固定
    '),(N=F.children(".soul-drag-bar")).children("div").on("mouseenter",function(){$(this).addClass("active")}).on("mouseleave",function(){$(this).removeClass("active")})),s.find("th").each(function(){var e,d,k,C,w=$(this),S=w.data("field"),R=w.data("key");R&&(e=R.split("-"),d=I.cols[e[1]][e[2]],k=e[1]+"-"+e[2],C=0r.width()/2,b=h&&o-p.position().left>c.width()/2;if(Math.abs(p.position().left-o),0li[data-value="'+S+'"]').after($("#soul-columns"+z+'>li[data-value="'+S+'"]').prev()),l=0;lli[data-value="'+S+'"]').before($("#soul-columns"+z+'>li[data-value="'+S+'"]').next()),l=0;l'),$("#column-remove").css({top:e.clientY-$("#column-remove").height()/2,left:e.clientX-$("#column-remove").width()/2,"font-size":y-e.clientY+"px"}),$("#column-remove").show()):$("#column-remove").hide()}}).on("mouseup",function(){if(_DOC.unbind("selectstart"),_BODY.off("mousemove").off("mouseup"),B&&p){if(B=!1,Y){"checkbox"!==d.type&&r.on("click",function(e){e.stopPropagation()}),Y=!1,F.removeClass("no-left-border"),w.removeClass("isDrag").css({position:"relative","z-index":"inherit",left:"inherit","border-left":"inherit",width:"inherit","background-color":"inherit"}),w.next().remove();var e,t=w.prev().data("key");if(g&&(e=F.children(".layui-table-header").children("table").find('th[data-key="'+R+'"]'),t?e.parent().children('th[data-key="'+t+'"]').after(e):"right"===g?0'),e.parent().children("th:first").after(e),e.parent().children("th:first").remove())),X?(O.find('td[data-key="'+R+'"]').each(function(){var e;t?$(this).parent().children('td[data-key="'+t+'"]').after($(this)):"right"===g?0'),$(this).parent().children("td:first").after($(this)),$(this).parent().children("td:first").remove())):($(this).parent().prepend(''),$(this).parent().children("td:first").after($(this)),$(this).parent().children("td:first").remove())}),0'),$(this).parent().children("td:first").after($(this)),$(this).parent().children("td:first").remove())})):C?(c.find('td[data-key="'+R+'"]').each(function(){var e;t?$(this).parent().children('td[data-key="'+t+'"]').after($(this)):"right"===g?0'),$(this).parent().children("td:first").after($(this)),$(this).parent().children("td:first").remove())):($(this).parent().prepend(''),$(this).parent().children("td:first").after($(this)),$(this).parent().children("td:first").remove())}),_.find('td[data-key="'+R+'"][data-clone]').each(function(){$(this).prev().removeClass("isDrag").css({position:"relative","z-index":"inherit",left:"inherit","border-left":"inherit",width:"inherit","background-color":"inherit"}),$(this).remove()}),0'),$(this).parent().children("td:first").after($(this)),$(this).parent().children("td:first").remove())}),T.find('td[data-key="'+R+'"][data-clone]').each(function(){$(this).prev().removeClass("isDrag").css({position:"relative","z-index":"inherit",left:"inherit",width:"inherit","background-color":"inherit"}),$(this).remove()}))):(O.find('td[data-key="'+R+'"][data-clone]').each(function(){$(this).prev().removeClass("isDrag").css({position:"relative","z-index":"inherit",left:"inherit",width:"inherit","background-color":"inherit"}),$(this).remove()}),0tr>th[data-key="+R+"]").addClass(HIDE),O.find('tbody>tr>td[data-key="'+R+'"]').addClass(HIDE),W.find('tbody>tr>td[data-key="'+R+'"]').addClass(HIDE),d.hide=!0,D.fixTableRemember(I),$("#soul-columns"+z).find('li[data-value="'+S+'"]>input').prop("checked",!1),tableFilter.resize(I)),$("#column-remove").hide()}}))}))})))},rowDrag:function(d,b){var p,e,y=this,v=$(d.elem),x=v.next().children(".layui-table-box"),m=x.children(".layui-table-fixed").children(".layui-table-body").children("table"),g=x.children(".layui-table-body").children("table"),t=$.merge(x.children(".layui-table-body").children("table"),m),k=d.id,C=!1,i=b.trigger||"row",w=!1!==b.numbers,S=null,R=0,l="row"===i?t.children("tbody").children("tr"):t.find(i);for("row"!==i&&t.find(i).css("cursor","move"),p=0;pl.height()/2,e=o&&t-i>n.height()/2;if(0l&&(n=layer.tips(''+$(this).text()+"",this,{tips:[1,d],maxWidth:a,time:0})))}"tips"===i.type?(e.off("mouseenter","td").off("mouseleave","td").on("mouseenter","td",function(){var e=this;o=setTimeout(function(){h.call(e)},a)}).on("mouseleave","td",function(){h.call(this,"hide")}),i.header&&t.off("mouseenter","th").off("mouseleave","th").on("mouseenter","th",function(){var e=this;o=setTimeout(function(){h.call(e)},a)}).on("mouseleave","th",function(){h.call(this,"hide")}),i.total&&l.off("mouseenter","td").off("mouseleave","td").on("mouseenter","td",function(){var e=this;o=setTimeout(function(){h.call(e)},a)}).on("mouseleave","td",function(){h.call(this,"hide")})):"title"===i.type&&(e.off("mouseenter","td").on("mouseenter","td",function(){var e=$(this),t=e.children(".layui-table-cell");e.data("off")||t.prop("scrollWidth")>t.outerWidth()&&t.attr("title",$(this).text())}),i.header&&t.off("mouseenter","th").on("mouseenter","th",function(){var e=$(this),t=e.children(".layui-table-cell");e.data("off")||t.prop("scrollWidth")>t.outerWidth()&&t.attr("title",$(this).text())}),i.total&&l.off("mouseenter","td").on("mouseenter","td",function(){var e=$(this),t=e.children(".layui-table-cell");e.data("off")||t.prop("scrollWidth")>t.outerWidth()&&t.attr("title",$(this).text())}))},contextmenu:function(h,e){for(var t=$(h.elem),i=t.next().children(".layui-table-box"),l=$.merge(i.children(".layui-table-header").children("table"),i.children(".layui-table-fixed").children(".layui-table-header").children("table")),a=i.children(".layui-table-fixed").children(".layui-table-body").children("table"),a=$.merge(i.children(".layui-table-body").children("table"),a),t=t.next().children(".layui-table-total").children("table"),u=h.id,n={header:{box:l,tag:"th",opts:e?e.header:"",cols:{}},body:{box:a,tag:"td",opts:e?e.body:"",cols:{},isBody:!0},total:{box:t,tag:"td",opts:e?e.total:"",cols:{}}},o=!1,r=0;r
    '),$("#soul-table-contextmenu-wrapper").on("click",function(e){e.stopPropagation()});var t=n[i].cols[$(this).data("key").substr($(this).data("key").indexOf("-")+1)];return!1!==t&&(t&&0'),a=0;a'),l[a].icon?n.push(''):n.push(''),n.push(l[a].name),l[a].children&&0'),n.push("");n.push(""),e.append(n.join(""));var c=e.children().last();for(i+c.outerHeight()>_BODY.prop("scrollHeight")&&(i-=c.outerHeight())<0&&(i=0),"left"===e.parent().data("direction")&&0_BODY.prop("scrollWidth")&&((t=t-c.outerWidth()-e.outerWidth())+e.offset().left<0&&(t=-e.offset().left),c.data("direction","left")),c.css({top:i+"px",left:t+"px"}),a=0;a'),a.each(function(){$(this).data("key")&&t.push(l.children("table:eq(0)").find('[data-key="'+$(this).data("key")+'"]').prop("outerHTML"))}),t.push(""),l.append(t.join(""))),0'),e.each(function(){t.push(l.children("table:eq(0)").find('[data-key="'+$(this).data("key")+'"]').prop("outerHTML"))}),t.push(""),l.append(t.join(""))))},fixResizeRightFixed:function(l){var t,a=this,e=$(l.elem).next().children(".layui-table-box").children(".layui-table-fixed-r").children(".layui-table-header").children("table"),n={},o="layui-table-sort",r="layui-table-sort-invalid";0
    ')).find("div").css({width:d}),e.find("tr").append(t)):e.find(".layui-table-patch").remove()};t(i),t(l);a=a.height()-s;n.find(".layui-table-body").css("height",r.height()>=a?a:"auto"),o[0"+this+"
    ").css({position:"absolute",float:"left","white-space":"nowrap",visibility:"hidden",font:t}).appendTo(_BODY),t=e.width();return e.remove(),t},void 0!==n&&void 0!==n.dblclick&&!n.dblclick||t.add(l).on("dblclick",function(e){var t=$(this),e=e.clientX-t.offset().left;a(i,t,0
    左固定
    不固定
    右固定
    '),(N=_.children(".soul-drag-bar")).children("div").on("mouseenter",function(){$(this).addClass("active")}).on("mouseleave",function(){$(this).removeClass("active")})),s.find("th").each(function(){var e,d,k,C,w=$(this),S=w.data("field"),R=w.data("key");R&&(e=R.split("-"),d=I.cols[e[1]][e[2]],k=e[1]+"-"+e[2],C=0r.width()/2,p=h&&o-y.position().left>c.width()/2;if(Math.abs(y.position().left-o),0li[data-value="'+S+'"]').after($("#soul-columns"+z+'>li[data-value="'+S+'"]').prev()),l=0;lli[data-value="'+S+'"]').before($("#soul-columns"+z+'>li[data-value="'+S+'"]').next()),l=0;l
    '),$("#column-remove").css({top:e.clientY-$("#column-remove").height()/2,left:e.clientX-$("#column-remove").width()/2,"font-size":b-e.clientY+"px"}),$("#column-remove").show()):$("#column-remove").hide()}}).on("mouseup",function(){if(_DOC.unbind("selectstart"),_BODY.off("mousemove").off("mouseup"),B&&y){if(B=!1,Y){"checkbox"!==d.type&&r.on("click",function(e){e.stopPropagation()}),Y=!1,_.removeClass("no-left-border"),w.removeClass("isDrag").css({position:"relative","z-index":"inherit",left:"inherit","border-left":"inherit",width:"inherit","background-color":"inherit"}),w.next().remove();var e,t=w.prev().data("key");if(g&&(e=_.children(".layui-table-header").children("table").find('th[data-key="'+R+'"]'),t?e.parent().children('th[data-key="'+t+'"]').after(e):"right"===g?0'),e.parent().children("th:first").after(e),e.parent().children("th:first").remove())),X?(W.find('td[data-key="'+R+'"]').each(function(){var e;t?$(this).parent().children('td[data-key="'+t+'"]').after($(this)):"right"===g?0'),$(this).parent().children("td:first").after($(this)),$(this).parent().children("td:first").remove())):($(this).parent().prepend(''),$(this).parent().children("td:first").after($(this)),$(this).parent().children("td:first").remove())}),0'),$(this).parent().children("td:first").after($(this)),$(this).parent().children("td:first").remove())})):C?(c.find('td[data-key="'+R+'"]').each(function(){var e;t?$(this).parent().children('td[data-key="'+t+'"]').after($(this)):"right"===g?0'),$(this).parent().children("td:first").after($(this)),$(this).parent().children("td:first").remove())):($(this).parent().prepend(''),$(this).parent().children("td:first").after($(this)),$(this).parent().children("td:first").remove())}),O.find('td[data-key="'+R+'"][data-clone]').each(function(){$(this).prev().removeClass("isDrag").css({position:"relative","z-index":"inherit",left:"inherit","border-left":"inherit",width:"inherit","background-color":"inherit"}),$(this).remove()}),0'),$(this).parent().children("td:first").after($(this)),$(this).parent().children("td:first").remove())}),T.find('td[data-key="'+R+'"][data-clone]').each(function(){$(this).prev().removeClass("isDrag").css({position:"relative","z-index":"inherit",left:"inherit",width:"inherit","background-color":"inherit"}),$(this).remove()}))):(W.find('td[data-key="'+R+'"][data-clone]').each(function(){$(this).prev().removeClass("isDrag").css({position:"relative","z-index":"inherit",left:"inherit",width:"inherit","background-color":"inherit"}),$(this).remove()}),0tr>th[data-key="+R+"]").addClass(HIDE),W.find('tbody>tr>td[data-key="'+R+'"]').addClass(HIDE),F.find('tbody>tr>td[data-key="'+R+'"]').addClass(HIDE),d.hide=!0,D.fixTableRemember(I),$("#soul-columns"+z).find('li[data-value="'+S+'"]>input').prop("checked",!1)),$("#column-remove").hide()}}))}))})))},rowDrag:function(d,p){var y,e,b=this,v=$(d.elem),x=v.next().children(".layui-table-box"),m=x.children(".layui-table-fixed").children(".layui-table-body").children("table"),g=x.children(".layui-table-body").children("table"),t=$.merge(x.children(".layui-table-body").children("table"),m),k=d.id,C=!1,i=p.trigger||"row",w=!1!==p.numbers,S=null,R=0,l="row"===i?t.children("tbody").children("tr"):t.find(i);for("row"!==i&&t.find(i).css("cursor","move"),y=0;yl.height()/2,e=o&&t-i>n.height()/2;if(0l&&(n=layer.tips(''+$(this).text()+"",this,{tips:[1,d],maxWidth:a,time:0})))}"tips"===i.type?(e.off("mouseenter","td").off("mouseleave","td").on("mouseenter","td",function(){var e=this;o=setTimeout(function(){h.call(e)},a)}).on("mouseleave","td",function(){h.call(this,"hide")}),i.header&&t.off("mouseenter","th").off("mouseleave","th").on("mouseenter","th",function(){var e=this;o=setTimeout(function(){h.call(e)},a)}).on("mouseleave","th",function(){h.call(this,"hide")}),i.total&&l.off("mouseenter","td").off("mouseleave","td").on("mouseenter","td",function(){var e=this;o=setTimeout(function(){h.call(e)},a)}).on("mouseleave","td",function(){h.call(this,"hide")})):"title"===i.type&&(e.off("mouseenter","td").on("mouseenter","td",function(){var e=$(this),t=e.children(".layui-table-cell");e.data("off")||t.prop("scrollWidth")>t.outerWidth()&&t.attr("title",$(this).text())}),i.header&&t.off("mouseenter","th").on("mouseenter","th",function(){var e=$(this),t=e.children(".layui-table-cell");e.data("off")||t.prop("scrollWidth")>t.outerWidth()&&t.attr("title",$(this).text())}),i.total&&l.off("mouseenter","td").on("mouseenter","td",function(){var e=$(this),t=e.children(".layui-table-cell");e.data("off")||t.prop("scrollWidth")>t.outerWidth()&&t.attr("title",$(this).text())}))},contextmenu:function(h,e){for(var t=$(h.elem),i=t.next().children(".layui-table-box"),l=$.merge(i.children(".layui-table-header").children("table"),i.children(".layui-table-fixed").children(".layui-table-header").children("table")),a=i.children(".layui-table-fixed").children(".layui-table-body").children("table"),a=$.merge(i.children(".layui-table-body").children("table"),a),t=t.next().children(".layui-table-total").children("table"),u=h.id,n={header:{box:l,tag:"th",opts:e?e.header:"",cols:{}},body:{box:a,tag:"td",opts:e?e.body:"",cols:{},isBody:!0},total:{box:t,tag:"td",opts:e?e.total:"",cols:{}}},o=!1,r=0;r'),$("#soul-table-contextmenu-wrapper").on("click",function(e){e.stopPropagation()});var t=n[i].cols[$(this).data("key").substr($(this).data("key").indexOf("-")+1)];return!1!==t&&(t&&0'),a=0;a'),l[a].icon?n.push(''):n.push(''),n.push(l[a].name),l[a].children&&0'),n.push("");n.push(""),e.append(n.join(""));var c=e.children().last();for(i+c.outerHeight()>_BODY.prop("scrollHeight")&&(i-=c.outerHeight())<0&&(i=0),"left"===e.parent().data("direction")&&0_BODY.prop("scrollWidth")&&((t=t-c.outerWidth()-e.outerWidth())+e.offset().left<0&&(t=-e.offset().left),c.data("direction","left")),c.css({top:i+"px",left:t+"px"}),a=0;a'),a.each(function(){$(this).data("key")&&t.push(l.children("table:eq(0)").find('[data-key="'+$(this).data("key")+'"]').prop("outerHTML"))}),t.push(""),l.append(t.join(""))),0'),e.each(function(){t.push(l.children("table:eq(0)").find('[data-key="'+$(this).data("key")+'"]').prop("outerHTML"))}),t.push(""),l.append(t.join(""))))},fixResizeRightFixed:function(l){var t,a=this,e=$(l.elem).next().children(".layui-table-box").children(".layui-table-fixed-r").children(".layui-table-header").children("table"),n={},o="layui-table-sort",r="layui-table-sort-invalid";0
    ')).find("div").css({width:d}),e.find("tr").append(t)):e.find(".layui-table-patch").remove()};t(i),t(l);a=a.height()-s;n.find(".layui-table-body").css("height",r.height()>=a?a:"auto"),o[0div').each(function(){u.isChild(layui.table.cache[i][$(this).parents("tr:eq(0)").data("index")])&&(u.field?$(this).prepend(''):$(this).html(''))}):u.field?x.find("tr").find('td[data-key$="'+u.key+'"]>div').prepend(''):x.find("tr").find('td[data-key$="'+u.key+'"]>div').html(''),x.children("tbody").children("tr").each(function(){$(this).children("td:eq("+e+")").find(".childTable").on("click",function(e){layui.stope(e);var t,l=$(this).parents("tr:eq(0)").data("index"),a=f.id,i=$(this).parents("td:eq(0)").data("key"),n=v.children("tbody").children("tr[data-index="+l+"]").children('td[data-key="'+i+'"]').find(".childTable:eq(0)"),d=m.find("tr[data-index="+l+"]").children('td[data-key="'+i+'"]').find(".childTable:eq(0)"),r=table.cache[a][l],o=u.children,c=!1,e="",h=x.children("tbody").children('tr[data-index="'+l+'"]'),s={data:r,tr:h,del:function(){table.cache[a][l]=[],y.destroyChildren(l,f,b),h.remove(),table.resize(a)},update:function(e){e=e||{},layui.each(e,function(l,e){var i,t;l in r&&(t=h.children('td[data-field="'+l+'"]'),r[l]=e,table.eachCols(a,function(e,t){t.field==l&&t.templet&&(i=t.templet)}),t.children(".layui-table-cell").html(i?"function"==typeof i?i(r):laytpl($(i).html()||e).render(r):e),t.data("content",e))})},close:function(){y.destroyChildren(l,f,b),table.resize(a)}};if(n.hasClass(b[1])){if("function"==typeof u.childClose&&!1===u.childClose(s))return}else if("function"==typeof u.childOpen&&!1===u.childOpen(s))return;"function"==typeof o&&(o=o(r)),"string"==typeof o&&(c=!0,e=y.parseTempData(u,u.field?r[u.field]:null,r)),2===u.show?(!u.layerOption||"function"==typeof u.layerOption.title&&(u.layerOption.title=u.layerOption.title(r)),layer.open($.extend({type:1,title:"子表",maxmin:!0,content:y.getTables(this,r,u,f,o,c,e),area:"1000px",offset:"100px",cancel:function(){"function"==typeof u.childClose&&u.childClose(s)}},u.layerOption||{})),c||y.renderTable(this,r,u,f,o,b)):(!n.hasClass(b[1])&&u.collapse&&x.children("tbody").children("tr").children("td").find(".childTable").each(function(){$(this).hasClass(b[1])&&y.destroyChildren($(this).parents("tr:eq(0)").data("index"),f,b)}),n.hasClass(b[1])||n.parents("tr:eq(0)").children("td").find(".childTable").each(function(){$(this).hasClass(b[1])&&($(this).removeClass(b[1]).addClass(b[0]),y.destroyChildren($(this).parents("tr:eq(0)").data("index"),f,b))}),n.hasClass(b[1])?(n.removeClass(b[1]).addClass(b[0]),d.removeClass(b[1]).addClass(b[0])):(n.removeClass(b[0]).addClass(b[1]),d.removeClass(b[0]).addClass(b[1])),i=n.parents("td:eq(0)").attr("rowspan"),n.hasClass(b[1])?((d=[]).push(''),d.push(y.getTables(this,r,u,f,o,c,e)),d.push(""),i?(i=parseInt(n.parents("tr:eq(0)").data("index"))+parseInt(i)-1,n.parents("table:eq(0)").children().children("[data-index='"+i+"']").after(d.join(""))):n.parents("tr:eq(0)").after(d.join("")),layui.element.init("tab"),c||(y.renderTable(this,r,u,f,o,b),n.parents("tr:eq(0)").next().children("td").children(".layui-tab").children(".layui-tab-content").on("click",function(e){$(e.target.parentElement).hasClass("layui-tab-title")||e.stopPropagation()}).off("dblclick").on("dblclick",function(e){e.stopPropagation()}).on("mouseenter","td",function(e){e.stopPropagation()}).on("change",function(e){layui.stope(e)})),0',o.children("td").children(".soul-table-child-wrapper").css({position:"absolute",top:0,width:"100%",background:"white","z-index":200}),o.children("td").append(t),m.find('tr[data-index="'+l+'"]').each(function(){$(this).after(''+t+"")}),table.resize(a)),3===u.show&&(n.parents("tr:eq(0)").next().find(".layui-table-view").css({margin:0,"border-width":0}),n.parents("tr:eq(0)").next().find(".layui-table-header").css("display","none"))):(y.destroyChildren(l,f,b),table.resize(a)))})}),u.spread&&2!==u.show&&x.children("tbody").children("tr").children("td").find(".childTable").trigger("click")}()},getTables:function(e,t,l,i,a,n,d){var r,o=[],c=$(i.elem),h=c.next().children(".layui-table-box"),s=i.id+$(e).parents("tr:eq(0)").data("index"),i=h.children(".layui-table-header").children("table"),e=c.next().children(".layui-table-box").children(".layui-table-body"),h=e.children("table"),c=0;if(n?o.push('
    '):3===l.show||"full"===l.childWidth?o.push('">'):(e.prop("scrollHeight")+(0e.height()&&(c=this.getScrollWidth()),c=e.width()-1-c,o.push("max-width: "+(c>i.width()?i.width():c)+'px">')),n)o.push(d);else{if(3!==l.show&&(void 0===l.childTitle||l.childTitle)){for(o.push('
      '),r=0;r'+("function"==typeof a[r].title?a[r].title(t):a[r].title)+"");o.push("
    ")}for(3===l.show?o.push('
    '):o.push('
    '),r=0;r
    ')}o.push("
    ")}return o.push("
    "),o.join("")},renderTable:function(a,n,d,r,o,e){var t=[],c=this,h=r.id,s=h+$(a).parents("tr:eq(0)").data("index");if(d.lazy)t.push(b(c,a,n,d,r,0,o,e));else for(var u=0;u"+t+"").text():t},commonMember:function(a,e,t){var l=$(this),n=e.id,i=$(e.elem).next().children(".layui-table-box"),d=i.children(".layui-table-fixed").children(".layui-table-body").children("table"),d=$.merge(i.children(".layui-table-body").children("table"),d),r=("TR"===l[0].tagName?$(this):l.parents("tr:eq(0)")).data("index"),o=d.children("tbody").children('tr[data-index="'+r+'"]'),c=(c=table.cache[n]||[])[r]||{};return $.extend(t,{tr:o,oldValue:l.prev()?l.prev().text():null,del:function(){table.cache[n][r]=[],o.remove(),a.scrollPatch(e)},update:function(e){e=e||{},layui.each(e,function(l,e){var i,t;l in c&&(t=o.children('td[data-field="'+l+'"]'),c[l]=e,table.eachCols(n,function(e,t){t.field==l&&t.templet&&(i=t.templet)}),t.children(".layui-table-cell").html(a.parseTempData({templet:i},e,c)),t.data("content",e))})}})},scrollPatch:function(e){var t=$(e.elem),l=t.next().children(".layui-table-box").children(".layui-table-header"),i=t.next().children(".layui-table-total"),a=t.next().children(".layui-table-box").children(".layui-table-main"),n=t.next().children(".layui-table-box").children(".layui-table-fixed"),d=t.next().children(".layui-table-box").children(".layui-table-fixed-r"),r=a.children("table"),o=a.width()-a.prop("clientWidth"),c=a.height()-a.prop("clientHeight"),e=r.outerWidth()-a.width(),t=function(e){var t;o&&c?(e=e.eq(0)).find(".layui-table-patch")[0]||((t=$('
    ')).find("div").css({width:o}),e.find("tr").append(t)):e.find(".layui-table-patch").remove()};t(l),t(i);a=a.height()-c;n.find(".layui-table-body").css("height",r.height()>=a?a:"auto"),d[0 大于",ge:"≥ 大于等于",lt:"< 小于",le:"≤ 小于等于",contain:"包含",notContain:"不包含",start:"以...开头",end:"以...结尾",null:"为空",notNull:"不为空"},b={all:"全部",yesterday:"昨天",thisWeek:"本周",lastWeek:"上周",thisMonth:"本月",thisYear:"今年"},z=["column","data","condition","editCondition","excel"],p={column:"soul-column",data:"soul-dropList",condition:"soul-condition",editCondition:"soul-edit-condition",excel:"soul-export",clearCache:"soul-clear-cache"},u={in:"data",condition:"condition",date:"condition"},c={data:{mode:"condition",type:"eq",value:""},condition:{mode:"in",values:[]}};e("tableFilter",{destroy:function(e){if(e)if(Array.isArray(e))for(var i=0;i')):i.find('th[data-field="'+g[x].field+'"]').children().append(''),0')):l.find('th[data-field="'+g[x].field+'"]').children().append(''),0')):a.find('th[data-field="'+g[x].field+'"]').children().append('')));if(Q[m.id]=m,K[m.id]=d){if(m.filter&&void 0!==m.filter.bottom&&!m.filter.bottom||0!==t.next().children(".soul-bottom-contion").length||(t.next().children(".layui-table-box").after(''),y=t.next().children(".layui-table-box").children(".layui-table-body").outerHeight()-t.next().children(".soul-bottom-contion").outerHeight(),m.page&&t.next().children(".layui-table-page").hasClass("layui-hide")&&(y+=t.next().children(".layui-table-page").outerHeight()),t.next().children(".layui-table-box").children(".layui-table-body").css("height",y),y=y-v.getScrollWidth(e[0]),e=e.children("table").height(),t.next().children(".layui-table-box").children(".layui-table-fixed").children(".layui-table-body").css("height",y<=e?y:"auto"),t.next().children(".soul-bottom-contion").children(".condition-items").css("width",t.next().children(".soul-bottom-contion").width()-t.next().children(".soul-bottom-contion").children(".editCondtion").width()+"px"),t.next().children(".soul-bottom-contion").children(".editCondtion").children("a").on("click",function(){v.showConditionBoard(m)})),!n||J[m.id]||m.isSoulFrontFilter)return J[m.id]=!1,m.isSoulFrontFilter=!1,!m.url&&m.page&&m.data&&m.data.forEach(function(e){for(x=0;xm.limit&&layui.each(m.data,function(e,i){i[m.indexName]=e}),m.url&&!m.page?X[m.id]=layui.table.cache[m.id]:X[m.id]=m.data||layui.table.cache[m.id],X[m.id].forEach(function(e,i){e[A]=i}),m.filter&&m.filter.clearFilter){if(m.where&&m.where.filterSos&&0 表格列 ',data:'
  • 筛选数据
  • ',condition:'
  • 筛选条件
  • ',editCondition:'
  • 编辑筛选条件
  • ',excel:'
  • 导出excel
  • ',clearCache:'
  • 清除缓存
  • '};for(r.push('
    '),E("body").append(r.join(""));var h=!0;j.on("checkbox(changeColumns"+b+")",function(e){h=!1;var i=e.value;for(e.elem.checked?t.next().find("[data-key="+i+"]").removeClass(H):t.next().find("[data-key="+i+"]").addClass(H),x=0;xtr>th:visible").length),Y.resize(b)}),E("#soul-columns"+b+">li[data-value]").on("click",function(){E(this).find(":checkbox").is(":disabled")||(h&&E(this).find("div.layui-form-checkbox").trigger("click"),h=!0)}),E("#soul-dropList"+b+" .check [data-type]").on("click",function(){switch(E(this).data("type")){case"all":E(this).parents("#soul-dropList"+b).find("input[type=checkbox]:not(:checked)").prop("checked",!0);break;case"reverse":E(this).parents("#soul-dropList"+b).find("input[type=checkbox]").each(function(){E(this).prop("checked",!E(this).prop("checked"))});break;case"none":E(this).parents("#soul-dropList"+b).find("input[type=checkbox]:checked").prop("checked",!1)}return j.render("checkbox","orm"),v.updateDropList(m,E("#main-list"+b).data("field")),!1}),E("#soul-dropList"+b+" .filter-search input").on("input",function(){var e=E(this).val();""===e?E("#soul-dropList"+b+">ul>li").show():(E("#soul-dropList"+b+">ul>li").hide(),E("#soul-dropList"+b+'>ul>li[data-value*="'+e.toLowerCase()+'"]').show())}),E("#main-list"+b+" .soul-column").on("mouseover",function(e){for(v.hideDropList(m),v.hideCondition(m),e.stopPropagation(),R&&clearTimeout(R),g=v.getCompleteCols(m.cols),x=0;xinput').prop("checked",!g[x].hide);var i;j.render("checkbox","orm"),E("#soul-columns"+b).show(),e=E(this).parent().offset().left+E(this).parent().width()+E("#soul-columns"+b).width().filter-search>input").val(""),E("#soul-dropList"+b).show();var i,t=E("#main-list"+b).data("field"),l=E("#main-list"+b).offset().left+E("#soul-dropList"+b).width()+E("#soul-dropList"+b).width()ul").data({head:!0,id:o,prefix:d,refresh:!0,split:E("#main-list"+b).data("split")}).html(E("#soulDropList"+b).find("."+t+"DropList li").clone()),E("#soul-dropList"+b).css({top:E(this).offset().top,left:i}).show().removeClass().addClass(l+" animated"),setTimeout(function(){E("#soul-dropList"+b+">.filter-search>input").focus(),j.render("checkbox","orm")},1);var n=!0;j.on("checkbox(soulDropList"+b+")",function(e){n=!1,v.updateDropList(m,t)}),E("#soul-dropList"+b+">ul>li[data-value]").on("click",function(){n&&E(this).find("div.layui-form-checkbox").trigger("click"),n=!0})}),E("#main-list"+b+" .soul-condition").on("mouseover",function(e){if(E("#soul-condition"+b).is(":visible")&&!E("#soul-condition"+b).hasClass("fadeOutLeft"))return!1;v.hideColumns(m),v.hideDropList(m),e.stopPropagation(),q&&clearTimeout(q);var i=document.body.clientWidth;E("#soul-condition"+b).show();var t,n,s=E(this).parent().data("field"),e=E(this).parent().offset().left+E(this).parent().width()+E("#soul-condition"+b).width()'+_[r]+"";if(d+="",l.push(''),n&&n.children&&0'),0===x?l.push('"):l.push('"),l.push('"),l.push(''),l.push(''),l.push("")}else l.push('');function f(e){var i=e.data("id"),t=E("#soul-condition"+b).data("id"),l=e.find('input[lay-filter="soul-coondition-switch"]:checked').prop("checked")?"and":"or",a=e.find("select").val(),o=e.find(".value").val(),d=E("#soul-condition"+b).data("head");n=t?{id:i,prefix:l,mode:"condition",field:s,type:a,value:o,groupId:t}:{head:d,prefix:"and",mode:"group",field:s,children:[{id:v.getDifId(),prefix:l,mode:"condition",field:s,type:a,value:o,groupId:t}]},v.updateWhere(m,n),t?i||e.data("id",n.id):(E("#soul-condition"+b).data("id",n.id),e.data("id",n.children[0].id))}function y(e){var i;1===E(e).parents("table:eq(0)").find("tr").length?(i=E("#soul-condition"+b).data("id"),E("#soul-condition"+b).data("id",""),E(e).parents("tr:eq(0)").find("select").val("eq"),E(e).parents("tr:eq(0)").find(".value").val("").show(),j.render("select","orm")):(i=E(e).parents("tr:eq(0)").data("id"),0===E(e).parents("tr:eq(0)").index()&&E(e).parents("table:eq(0)").find("tr:eq(1)>td:eq(0)").html(o[s].title).addClass("soul-condition-title"),E(e).parents("tr:eq(0)").remove()),i&&v.updateWhere(m,{id:i,delete:!0})}l.push('
    '+o[s].title+"
    '+o[s].title+'
    '+d+'
    '),E("#soul-condition"+b).data({head:!0,id:n&&n.id||""}).html(l.join("")).css({top:E(this).offset().top,left:t}).show().removeClass().addClass(e+" animated"),E(".condition-table").on("click",function(){return!1}),E("#soul-condition"+b+" button[data-type]").on("click",function(){var e,i,t;return"add"===E(this).data("type")?(e=E("#soul-condition"+b).data("id"),t=E("#soul-condition"+b).data("head"),i=E("#soul-condition"+b).find("tr:eq(0)"),t=e?{head:t,prefix:"and",field:s,mode:"condition",type:"eq",value:"",groupId:e}:{head:t,prefix:"and",mode:"group",field:s,children:[{id:v.getDifId(),prefix:"and",field:s,mode:"condition",type:i.find("select").val(),value:i.find(".value").val()},{id:v.getDifId(),prefix:"and",field:s,mode:"condition",type:"eq",value:""}]},v.updateWhere(m,t),e||(E("#soul-condition"+b).data("id",t.id),i.data("id",t.children[0].id)),t='
    '+d+'
    ',E("#soul-condition"+b+">table>tbody").append(t),E("#soul-condition"+b).find(".del:last").on("click",function(){y(this)}),E("#soul-condition"+b+" input.value:last").on("input",function(){f(E(this).parents("tr:eq(0)"))})):"search"===E(this).data("type")&&v.soulReload(m),j.render("select","orm"),j.render("checkbox","orm"),!1}),E("#soul-condition"+b+" input.value").on("input",function(){f(E(this).parents("tr:eq(0)"))}),j.on("select(conditionChange)",function(e){"null"===e.value||"notNull"===e.value?E(this).parents("tr").find("input.value").hide():E(this).parents("tr").find("input.value").show(),f(E(e.elem).parents("tr:eq(0)"))}),j.on("switch(soul-coondition-switch)",function(e){f(E(this).parents("tr:eq(0)"))}),E("#soul-condition"+b+" .del").on("click",function(){y(this)})}j.render("select","orm"),j.render("checkbox","orm")}),E("#soul-columns"+b+", #soul-dropList"+b).on("mouseover",function(e){e.stopPropagation()}),E("#main-list"+b+" .soul-edit-condition").on("mouseover",function(e){v.hideColumns(m),v.hideDropList(m),v.hideCondition(m),e.stopPropagation()}).on("click",function(){E("#main-list"+b).hide(),v.showConditionBoard(m)}),E("#main-list"+b+" .soul-export").on("mouseover",function(e){v.hideColumns(m),v.hideDropList(m),v.hideCondition(m),e.stopPropagation()}).on("click",function(){E("#main-list"+b).hide(),v.export(Q[m.id])}),E("#main-list"+b+" .soul-clear-cache").on("mouseover",function(e){v.hideColumns(m),v.hideDropList(m),v.hideCondition(m),e.stopPropagation()}).on("click",function(){E("#main-list"+b).hide(),layui.soulTable&&layui.soulTable.clearCache(m),layer.msg("已还原!",{icon:1,time:1e3})}),E("#main-list"+b).on("mouseover",function(e){var i=e.pageX,t=e.pageY,l=E(this),a=l.offset().top,o=a+l.height(),e=l.offset().left,l=e+l.width();i<=e||l<=i||t<=a||o<=t||(v.hideColumns(m),v.hideDropList(m),v.hideCondition(m))})}else{for(c={},x=0;x
    '),e.push("")}},renderBottomCondition:function(h){for(var e,p=this,i=$[h.id]||{},f=i.filterSos?JSON.parse(i.filterSos):[],o=i.tableFilterType?JSON.parse(i.tableFilterType):{},i=E(h.elem),y=h.id,i=i.next().children(".soul-bottom-contion"),d={},t=[],l=h.filter&&h.filter.items||z,a=p.getCompleteCols(h.cols),n=0;n
    #s'; + $pre_replace = '\1'; + return preg_replace($pre_regex, $pre_replace, $html); + } + + /** + * @param string $html + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return string + */ + public function postFilter($html, $config, $context) + { + $post_regex = '#((?:v|cp)/[A-Za-z0-9\-_=]+)#'; + return preg_replace_callback($post_regex, array($this, 'postFilterCallback'), $html); + } + + /** + * @param $url + * @return string + */ + protected function armorUrl($url) + { + return str_replace('--', '--', $url); + } + + /** + * @param array $matches + * @return string + */ + protected function postFilterCallback($matches) + { + $url = $this->armorUrl($matches[1]); + return '' . + '' . + '' . + ''; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Generator.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Generator.php new file mode 100644 index 0000000..eb56e2d --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Generator.php @@ -0,0 +1,286 @@ + tags. + * @type bool + */ + private $_scriptFix = false; + + /** + * Cache of HTMLDefinition during HTML output to determine whether or + * not attributes should be minimized. + * @type HTMLPurifier_HTMLDefinition + */ + private $_def; + + /** + * Cache of %Output.SortAttr. + * @type bool + */ + private $_sortAttr; + + /** + * Cache of %Output.FlashCompat. + * @type bool + */ + private $_flashCompat; + + /** + * Cache of %Output.FixInnerHTML. + * @type bool + */ + private $_innerHTMLFix; + + /** + * Stack for keeping track of object information when outputting IE + * compatibility code. + * @type array + */ + private $_flashStack = array(); + + /** + * Configuration for the generator + * @type HTMLPurifier_Config + */ + protected $config; + + /** + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + */ + public function __construct($config, $context) + { + $this->config = $config; + $this->_scriptFix = $config->get('Output.CommentScriptContents'); + $this->_innerHTMLFix = $config->get('Output.FixInnerHTML'); + $this->_sortAttr = $config->get('Output.SortAttr'); + $this->_flashCompat = $config->get('Output.FlashCompat'); + $this->_def = $config->getHTMLDefinition(); + $this->_xhtml = $this->_def->doctype->xml; + } + + /** + * Generates HTML from an array of tokens. + * @param HTMLPurifier_Token[] $tokens Array of HTMLPurifier_Token + * @return string Generated HTML + */ + public function generateFromTokens($tokens) + { + if (!$tokens) { + return ''; + } + + // Basic algorithm + $html = ''; + for ($i = 0, $size = count($tokens); $i < $size; $i++) { + if ($this->_scriptFix && $tokens[$i]->name === 'script' + && $i + 2 < $size && $tokens[$i+2] instanceof HTMLPurifier_Token_End) { + // script special case + // the contents of the script block must be ONE token + // for this to work. + $html .= $this->generateFromToken($tokens[$i++]); + $html .= $this->generateScriptFromToken($tokens[$i++]); + } + $html .= $this->generateFromToken($tokens[$i]); + } + + // Tidy cleanup + if (extension_loaded('tidy') && $this->config->get('Output.TidyFormat')) { + $tidy = new Tidy; + $tidy->parseString( + $html, + array( + 'indent'=> true, + 'output-xhtml' => $this->_xhtml, + 'show-body-only' => true, + 'indent-spaces' => 2, + 'wrap' => 68, + ), + 'utf8' + ); + $tidy->cleanRepair(); + $html = (string) $tidy; // explicit cast necessary + } + + // Normalize newlines to system defined value + if ($this->config->get('Core.NormalizeNewlines')) { + $nl = $this->config->get('Output.Newline'); + if ($nl === null) { + $nl = PHP_EOL; + } + if ($nl !== "\n") { + $html = str_replace("\n", $nl, $html); + } + } + return $html; + } + + /** + * Generates HTML from a single token. + * @param HTMLPurifier_Token $token HTMLPurifier_Token object. + * @return string Generated HTML + */ + public function generateFromToken($token) + { + if (!$token instanceof HTMLPurifier_Token) { + trigger_error('Cannot generate HTML from non-HTMLPurifier_Token object', E_USER_WARNING); + return ''; + + } elseif ($token instanceof HTMLPurifier_Token_Start) { + $attr = $this->generateAttributes($token->attr, $token->name); + if ($this->_flashCompat) { + if ($token->name == "object") { + $flash = new stdClass(); + $flash->attr = $token->attr; + $flash->param = array(); + $this->_flashStack[] = $flash; + } + } + return '<' . $token->name . ($attr ? ' ' : '') . $attr . '>'; + + } elseif ($token instanceof HTMLPurifier_Token_End) { + $_extra = ''; + if ($this->_flashCompat) { + if ($token->name == "object" && !empty($this->_flashStack)) { + // doesn't do anything for now + } + } + return $_extra . 'name . '>'; + + } elseif ($token instanceof HTMLPurifier_Token_Empty) { + if ($this->_flashCompat && $token->name == "param" && !empty($this->_flashStack)) { + $this->_flashStack[count($this->_flashStack)-1]->param[$token->attr['name']] = $token->attr['value']; + } + $attr = $this->generateAttributes($token->attr, $token->name); + return '<' . $token->name . ($attr ? ' ' : '') . $attr . + ( $this->_xhtml ? ' /': '' ) //
    v.
    + . '>'; + + } elseif ($token instanceof HTMLPurifier_Token_Text) { + return $this->escape($token->data, ENT_NOQUOTES); + + } elseif ($token instanceof HTMLPurifier_Token_Comment) { + return ''; + } else { + return ''; + + } + } + + /** + * Special case processor for the contents of script tags + * @param HTMLPurifier_Token $token HTMLPurifier_Token object. + * @return string + * @warning This runs into problems if there's already a literal + * --> somewhere inside the script contents. + */ + public function generateScriptFromToken($token) + { + if (!$token instanceof HTMLPurifier_Token_Text) { + return $this->generateFromToken($token); + } + // Thanks + $data = preg_replace('#//\s*$#', '', $token->data); + return ''; + } + + /** + * Generates attribute declarations from attribute array. + * @note This does not include the leading or trailing space. + * @param array $assoc_array_of_attributes Attribute array + * @param string $element Name of element attributes are for, used to check + * attribute minimization. + * @return string Generated HTML fragment for insertion. + */ + public function generateAttributes($assoc_array_of_attributes, $element = '') + { + $html = ''; + if ($this->_sortAttr) { + ksort($assoc_array_of_attributes); + } + foreach ($assoc_array_of_attributes as $key => $value) { + if (!$this->_xhtml) { + // Remove namespaced attributes + if (strpos($key, ':') !== false) { + continue; + } + // Check if we should minimize the attribute: val="val" -> val + if ($element && !empty($this->_def->info[$element]->attr[$key]->minimized)) { + $html .= $key . ' '; + continue; + } + } + // Workaround for Internet Explorer innerHTML bug. + // Essentially, Internet Explorer, when calculating + // innerHTML, omits quotes if there are no instances of + // angled brackets, quotes or spaces. However, when parsing + // HTML (for example, when you assign to innerHTML), it + // treats backticks as quotes. Thus, + // `` + // becomes + // `` + // becomes + // + // Fortunately, all we need to do is trigger an appropriate + // quoting style, which we do by adding an extra space. + // This also is consistent with the W3C spec, which states + // that user agents may ignore leading or trailing + // whitespace (in fact, most don't, at least for attributes + // like alt, but an extra space at the end is barely + // noticeable). Still, we have a configuration knob for + // this, since this transformation is not necesary if you + // don't process user input with innerHTML or you don't plan + // on supporting Internet Explorer. + if ($this->_innerHTMLFix) { + if (strpos($value, '`') !== false) { + // check if correct quoting style would not already be + // triggered + if (strcspn($value, '"\' <>') === strlen($value)) { + // protect! + $value .= ' '; + } + } + } + $html .= $key.'="'.$this->escape($value).'" '; + } + return rtrim($html); + } + + /** + * Escapes raw text data. + * @todo This really ought to be protected, but until we have a facility + * for properly generating HTML here w/o using tokens, it stays + * public. + * @param string $string String data to escape for HTML. + * @param int $quote Quoting style, like htmlspecialchars. ENT_NOQUOTES is + * permissible for non-attribute output. + * @return string escaped data. + */ + public function escape($string, $quote = null) + { + // Workaround for APC bug on Mac Leopard reported by sidepodcast + // http://htmlpurifier.org/phorum/read.php?3,4823,4846 + if ($quote === null) { + $quote = ENT_COMPAT; + } + return htmlspecialchars($string, $quote, 'UTF-8'); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLDefinition.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLDefinition.php new file mode 100644 index 0000000..9b7b334 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLDefinition.php @@ -0,0 +1,493 @@ +getAnonymousModule(); + if (!isset($module->info[$element_name])) { + $element = $module->addBlankElement($element_name); + } else { + $element = $module->info[$element_name]; + } + $element->attr[$attr_name] = $def; + } + + /** + * Adds a custom element to your HTML definition + * @see HTMLPurifier_HTMLModule::addElement() for detailed + * parameter and return value descriptions. + */ + public function addElement($element_name, $type, $contents, $attr_collections, $attributes = array()) + { + $module = $this->getAnonymousModule(); + // assume that if the user is calling this, the element + // is safe. This may not be a good idea + $element = $module->addElement($element_name, $type, $contents, $attr_collections, $attributes); + return $element; + } + + /** + * Adds a blank element to your HTML definition, for overriding + * existing behavior + * @param string $element_name + * @return HTMLPurifier_ElementDef + * @see HTMLPurifier_HTMLModule::addBlankElement() for detailed + * parameter and return value descriptions. + */ + public function addBlankElement($element_name) + { + $module = $this->getAnonymousModule(); + $element = $module->addBlankElement($element_name); + return $element; + } + + /** + * Retrieves a reference to the anonymous module, so you can + * bust out advanced features without having to make your own + * module. + * @return HTMLPurifier_HTMLModule + */ + public function getAnonymousModule() + { + if (!$this->_anonModule) { + $this->_anonModule = new HTMLPurifier_HTMLModule(); + $this->_anonModule->name = 'Anonymous'; + } + return $this->_anonModule; + } + + private $_anonModule = null; + + // PUBLIC BUT INTERNAL VARIABLES -------------------------------------- + + /** + * @type string + */ + public $type = 'HTML'; + + /** + * @type HTMLPurifier_HTMLModuleManager + */ + public $manager; + + /** + * Performs low-cost, preliminary initialization. + */ + public function __construct() + { + $this->manager = new HTMLPurifier_HTMLModuleManager(); + } + + /** + * @param HTMLPurifier_Config $config + */ + protected function doSetup($config) + { + $this->processModules($config); + $this->setupConfigStuff($config); + unset($this->manager); + + // cleanup some of the element definitions + foreach ($this->info as $k => $v) { + unset($this->info[$k]->content_model); + unset($this->info[$k]->content_model_type); + } + } + + /** + * Extract out the information from the manager + * @param HTMLPurifier_Config $config + */ + protected function processModules($config) + { + if ($this->_anonModule) { + // for user specific changes + // this is late-loaded so we don't have to deal with PHP4 + // reference wonky-ness + $this->manager->addModule($this->_anonModule); + unset($this->_anonModule); + } + + $this->manager->setup($config); + $this->doctype = $this->manager->doctype; + + foreach ($this->manager->modules as $module) { + foreach ($module->info_tag_transform as $k => $v) { + if ($v === false) { + unset($this->info_tag_transform[$k]); + } else { + $this->info_tag_transform[$k] = $v; + } + } + foreach ($module->info_attr_transform_pre as $k => $v) { + if ($v === false) { + unset($this->info_attr_transform_pre[$k]); + } else { + $this->info_attr_transform_pre[$k] = $v; + } + } + foreach ($module->info_attr_transform_post as $k => $v) { + if ($v === false) { + unset($this->info_attr_transform_post[$k]); + } else { + $this->info_attr_transform_post[$k] = $v; + } + } + foreach ($module->info_injector as $k => $v) { + if ($v === false) { + unset($this->info_injector[$k]); + } else { + $this->info_injector[$k] = $v; + } + } + } + $this->info = $this->manager->getElements(); + $this->info_content_sets = $this->manager->contentSets->lookup; + } + + /** + * Sets up stuff based on config. We need a better way of doing this. + * @param HTMLPurifier_Config $config + */ + protected function setupConfigStuff($config) + { + $block_wrapper = $config->get('HTML.BlockWrapper'); + if (isset($this->info_content_sets['Block'][$block_wrapper])) { + $this->info_block_wrapper = $block_wrapper; + } else { + trigger_error( + 'Cannot use non-block element as block wrapper', + E_USER_ERROR + ); + } + + $parent = $config->get('HTML.Parent'); + $def = $this->manager->getElement($parent, true); + if ($def) { + $this->info_parent = $parent; + $this->info_parent_def = $def; + } else { + trigger_error( + 'Cannot use unrecognized element as parent', + E_USER_ERROR + ); + $this->info_parent_def = $this->manager->getElement($this->info_parent, true); + } + + // support template text + $support = "(for information on implementing this, see the support forums) "; + + // setup allowed elements ----------------------------------------- + + $allowed_elements = $config->get('HTML.AllowedElements'); + $allowed_attributes = $config->get('HTML.AllowedAttributes'); // retrieve early + + if (!is_array($allowed_elements) && !is_array($allowed_attributes)) { + $allowed = $config->get('HTML.Allowed'); + if (is_string($allowed)) { + list($allowed_elements, $allowed_attributes) = $this->parseTinyMCEAllowedList($allowed); + } + } + + if (is_array($allowed_elements)) { + foreach ($this->info as $name => $d) { + if (!isset($allowed_elements[$name])) { + unset($this->info[$name]); + } + unset($allowed_elements[$name]); + } + // emit errors + foreach ($allowed_elements as $element => $d) { + $element = htmlspecialchars($element); // PHP doesn't escape errors, be careful! + trigger_error("Element '$element' is not supported $support", E_USER_WARNING); + } + } + + // setup allowed attributes --------------------------------------- + + $allowed_attributes_mutable = $allowed_attributes; // by copy! + if (is_array($allowed_attributes)) { + // This actually doesn't do anything, since we went away from + // global attributes. It's possible that userland code uses + // it, but HTMLModuleManager doesn't! + foreach ($this->info_global_attr as $attr => $x) { + $keys = array($attr, "*@$attr", "*.$attr"); + $delete = true; + foreach ($keys as $key) { + if ($delete && isset($allowed_attributes[$key])) { + $delete = false; + } + if (isset($allowed_attributes_mutable[$key])) { + unset($allowed_attributes_mutable[$key]); + } + } + if ($delete) { + unset($this->info_global_attr[$attr]); + } + } + + foreach ($this->info as $tag => $info) { + foreach ($info->attr as $attr => $x) { + $keys = array("$tag@$attr", $attr, "*@$attr", "$tag.$attr", "*.$attr"); + $delete = true; + foreach ($keys as $key) { + if ($delete && isset($allowed_attributes[$key])) { + $delete = false; + } + if (isset($allowed_attributes_mutable[$key])) { + unset($allowed_attributes_mutable[$key]); + } + } + if ($delete) { + if ($this->info[$tag]->attr[$attr]->required) { + trigger_error( + "Required attribute '$attr' in element '$tag' " . + "was not allowed, which means '$tag' will not be allowed either", + E_USER_WARNING + ); + } + unset($this->info[$tag]->attr[$attr]); + } + } + } + // emit errors + foreach ($allowed_attributes_mutable as $elattr => $d) { + $bits = preg_split('/[.@]/', $elattr, 2); + $c = count($bits); + switch ($c) { + case 2: + if ($bits[0] !== '*') { + $element = htmlspecialchars($bits[0]); + $attribute = htmlspecialchars($bits[1]); + if (!isset($this->info[$element])) { + trigger_error( + "Cannot allow attribute '$attribute' if element " . + "'$element' is not allowed/supported $support" + ); + } else { + trigger_error( + "Attribute '$attribute' in element '$element' not supported $support", + E_USER_WARNING + ); + } + break; + } + // otherwise fall through + case 1: + $attribute = htmlspecialchars($bits[0]); + trigger_error( + "Global attribute '$attribute' is not ". + "supported in any elements $support", + E_USER_WARNING + ); + break; + } + } + } + + // setup forbidden elements --------------------------------------- + + $forbidden_elements = $config->get('HTML.ForbiddenElements'); + $forbidden_attributes = $config->get('HTML.ForbiddenAttributes'); + + foreach ($this->info as $tag => $info) { + if (isset($forbidden_elements[$tag])) { + unset($this->info[$tag]); + continue; + } + foreach ($info->attr as $attr => $x) { + if (isset($forbidden_attributes["$tag@$attr"]) || + isset($forbidden_attributes["*@$attr"]) || + isset($forbidden_attributes[$attr]) + ) { + unset($this->info[$tag]->attr[$attr]); + continue; + } elseif (isset($forbidden_attributes["$tag.$attr"])) { // this segment might get removed eventually + // $tag.$attr are not user supplied, so no worries! + trigger_error( + "Error with $tag.$attr: tag.attr syntax not supported for " . + "HTML.ForbiddenAttributes; use tag@attr instead", + E_USER_WARNING + ); + } + } + } + foreach ($forbidden_attributes as $key => $v) { + if (strlen($key) < 2) { + continue; + } + if ($key[0] != '*') { + continue; + } + if ($key[1] == '.') { + trigger_error( + "Error with $key: *.attr syntax not supported for HTML.ForbiddenAttributes; use attr instead", + E_USER_WARNING + ); + } + } + + // setup injectors ----------------------------------------------------- + foreach ($this->info_injector as $i => $injector) { + if ($injector->checkNeeded($config) !== false) { + // remove injector that does not have it's required + // elements/attributes present, and is thus not needed. + unset($this->info_injector[$i]); + } + } + } + + /** + * Parses a TinyMCE-flavored Allowed Elements and Attributes list into + * separate lists for processing. Format is element[attr1|attr2],element2... + * @warning Although it's largely drawn from TinyMCE's implementation, + * it is different, and you'll probably have to modify your lists + * @param array $list String list to parse + * @return array + * @todo Give this its own class, probably static interface + */ + public function parseTinyMCEAllowedList($list) + { + $list = str_replace(array(' ', "\t"), '', $list); + + $elements = array(); + $attributes = array(); + + $chunks = preg_split('/(,|[\n\r]+)/', $list); + foreach ($chunks as $chunk) { + if (empty($chunk)) { + continue; + } + // remove TinyMCE element control characters + if (!strpos($chunk, '[')) { + $element = $chunk; + $attr = false; + } else { + list($element, $attr) = explode('[', $chunk); + } + if ($element !== '*') { + $elements[$element] = true; + } + if (!$attr) { + continue; + } + $attr = substr($attr, 0, strlen($attr) - 1); // remove trailing ] + $attr = explode('|', $attr); + foreach ($attr as $key) { + $attributes["$element.$key"] = true; + } + } + return array($elements, $attributes); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule.php new file mode 100644 index 0000000..9dbb987 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule.php @@ -0,0 +1,285 @@ +info, since the object's data is only info, + * with extra behavior associated with it. + * @type array + */ + public $attr_collections = array(); + + /** + * Associative array of deprecated tag name to HTMLPurifier_TagTransform. + * @type array + */ + public $info_tag_transform = array(); + + /** + * List of HTMLPurifier_AttrTransform to be performed before validation. + * @type array + */ + public $info_attr_transform_pre = array(); + + /** + * List of HTMLPurifier_AttrTransform to be performed after validation. + * @type array + */ + public $info_attr_transform_post = array(); + + /** + * List of HTMLPurifier_Injector to be performed during well-formedness fixing. + * An injector will only be invoked if all of it's pre-requisites are met; + * if an injector fails setup, there will be no error; it will simply be + * silently disabled. + * @type array + */ + public $info_injector = array(); + + /** + * Boolean flag that indicates whether or not getChildDef is implemented. + * For optimization reasons: may save a call to a function. Be sure + * to set it if you do implement getChildDef(), otherwise it will have + * no effect! + * @type bool + */ + public $defines_child_def = false; + + /** + * Boolean flag whether or not this module is safe. If it is not safe, all + * of its members are unsafe. Modules are safe by default (this might be + * slightly dangerous, but it doesn't make much sense to force HTML Purifier, + * which is based off of safe HTML, to explicitly say, "This is safe," even + * though there are modules which are "unsafe") + * + * @type bool + * @note Previously, safety could be applied at an element level granularity. + * We've removed this ability, so in order to add "unsafe" elements + * or attributes, a dedicated module with this property set to false + * must be used. + */ + public $safe = true; + + /** + * Retrieves a proper HTMLPurifier_ChildDef subclass based on + * content_model and content_model_type member variables of + * the HTMLPurifier_ElementDef class. There is a similar function + * in HTMLPurifier_HTMLDefinition. + * @param HTMLPurifier_ElementDef $def + * @return HTMLPurifier_ChildDef subclass + */ + public function getChildDef($def) + { + return false; + } + + // -- Convenience ----------------------------------------------------- + + /** + * Convenience function that sets up a new element + * @param string $element Name of element to add + * @param string|bool $type What content set should element be registered to? + * Set as false to skip this step. + * @param string|HTMLPurifier_ChildDef $contents Allowed children in form of: + * "$content_model_type: $content_model" + * @param array|string $attr_includes What attribute collections to register to + * element? + * @param array $attr What unique attributes does the element define? + * @see HTMLPurifier_ElementDef:: for in-depth descriptions of these parameters. + * @return HTMLPurifier_ElementDef Created element definition object, so you + * can set advanced parameters + */ + public function addElement($element, $type, $contents, $attr_includes = array(), $attr = array()) + { + $this->elements[] = $element; + // parse content_model + list($content_model_type, $content_model) = $this->parseContents($contents); + // merge in attribute inclusions + $this->mergeInAttrIncludes($attr, $attr_includes); + // add element to content sets + if ($type) { + $this->addElementToContentSet($element, $type); + } + // create element + $this->info[$element] = HTMLPurifier_ElementDef::create( + $content_model, + $content_model_type, + $attr + ); + // literal object $contents means direct child manipulation + if (!is_string($contents)) { + $this->info[$element]->child = $contents; + } + return $this->info[$element]; + } + + /** + * Convenience function that creates a totally blank, non-standalone + * element. + * @param string $element Name of element to create + * @return HTMLPurifier_ElementDef Created element + */ + public function addBlankElement($element) + { + if (!isset($this->info[$element])) { + $this->elements[] = $element; + $this->info[$element] = new HTMLPurifier_ElementDef(); + $this->info[$element]->standalone = false; + } else { + trigger_error("Definition for $element already exists in module, cannot redefine"); + } + return $this->info[$element]; + } + + /** + * Convenience function that registers an element to a content set + * @param string $element Element to register + * @param string $type Name content set (warning: case sensitive, usually upper-case + * first letter) + */ + public function addElementToContentSet($element, $type) + { + if (!isset($this->content_sets[$type])) { + $this->content_sets[$type] = ''; + } else { + $this->content_sets[$type] .= ' | '; + } + $this->content_sets[$type] .= $element; + } + + /** + * Convenience function that transforms single-string contents + * into separate content model and content model type + * @param string $contents Allowed children in form of: + * "$content_model_type: $content_model" + * @return array + * @note If contents is an object, an array of two nulls will be + * returned, and the callee needs to take the original $contents + * and use it directly. + */ + public function parseContents($contents) + { + if (!is_string($contents)) { + return array(null, null); + } // defer + switch ($contents) { + // check for shorthand content model forms + case 'Empty': + return array('empty', ''); + case 'Inline': + return array('optional', 'Inline | #PCDATA'); + case 'Flow': + return array('optional', 'Flow | #PCDATA'); + } + list($content_model_type, $content_model) = explode(':', $contents); + $content_model_type = strtolower(trim($content_model_type)); + $content_model = trim($content_model); + return array($content_model_type, $content_model); + } + + /** + * Convenience function that merges a list of attribute includes into + * an attribute array. + * @param array $attr Reference to attr array to modify + * @param array $attr_includes Array of includes / string include to merge in + */ + public function mergeInAttrIncludes(&$attr, $attr_includes) + { + if (!is_array($attr_includes)) { + if (empty($attr_includes)) { + $attr_includes = array(); + } else { + $attr_includes = array($attr_includes); + } + } + $attr[0] = $attr_includes; + } + + /** + * Convenience function that generates a lookup table with boolean + * true as value. + * @param string $list List of values to turn into a lookup + * @note You can also pass an arbitrary number of arguments in + * place of the regular argument + * @return array array equivalent of list + */ + public function makeLookup($list) + { + $args = func_get_args(); + if (is_string($list)) { + $list = $args; + } + $ret = array(); + foreach ($list as $value) { + if (is_null($value)) { + continue; + } + $ret[$value] = true; + } + return $ret; + } + + /** + * Lazy load construction of the module after determining whether + * or not it's needed, and also when a finalized configuration object + * is available. + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Bdo.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Bdo.php new file mode 100644 index 0000000..1e67c79 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Bdo.php @@ -0,0 +1,44 @@ + array('dir' => false) + ); + + /** + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { + $bdo = $this->addElement( + 'bdo', + 'Inline', + 'Inline', + array('Core', 'Lang'), + array( + 'dir' => 'Enum#ltr,rtl', // required + // The Abstract Module specification has the attribute + // inclusions wrong for bdo: bdo allows Lang + ) + ); + $bdo->attr_transform_post[] = new HTMLPurifier_AttrTransform_BdoDir(); + + $this->attr_collections['I18N']['dir'] = 'Enum#ltr,rtl'; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/CommonAttributes.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/CommonAttributes.php new file mode 100644 index 0000000..a96ab1b --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/CommonAttributes.php @@ -0,0 +1,31 @@ + array( + 0 => array('Style'), + // 'xml:space' => false, + 'class' => 'Class', + 'id' => 'ID', + 'title' => 'CDATA', + ), + 'Lang' => array(), + 'I18N' => array( + 0 => array('Lang'), // proprietary, for xml:lang/lang + ), + 'Common' => array( + 0 => array('Core', 'I18N') + ) + ); +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Edit.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Edit.php new file mode 100644 index 0000000..a9042a3 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Edit.php @@ -0,0 +1,55 @@ + 'URI', + // 'datetime' => 'Datetime', // not implemented + ); + $this->addElement('del', 'Inline', $contents, 'Common', $attr); + $this->addElement('ins', 'Inline', $contents, 'Common', $attr); + } + + // HTML 4.01 specifies that ins/del must not contain block + // elements when used in an inline context, chameleon is + // a complicated workaround to acheive this effect + + // Inline context ! Block context (exclamation mark is + // separator, see getChildDef for parsing) + + /** + * @type bool + */ + public $defines_child_def = true; + + /** + * @param HTMLPurifier_ElementDef $def + * @return HTMLPurifier_ChildDef_Chameleon + */ + public function getChildDef($def) + { + if ($def->content_model_type != 'chameleon') { + return false; + } + $value = explode('!', $def->content_model); + return new HTMLPurifier_ChildDef_Chameleon($value[0], $value[1]); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Forms.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Forms.php new file mode 100644 index 0000000..eb0edcf --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Forms.php @@ -0,0 +1,194 @@ + 'Form', + 'Inline' => 'Formctrl', + ); + + /** + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { + if ($config->get('HTML.Forms')) { + $this->safe = true; + } + + $form = $this->addElement( + 'form', + 'Form', + 'Required: Heading | List | Block | fieldset', + 'Common', + array( + 'accept' => 'ContentTypes', + 'accept-charset' => 'Charsets', + 'action*' => 'URI', + 'method' => 'Enum#get,post', + // really ContentType, but these two are the only ones used today + 'enctype' => 'Enum#application/x-www-form-urlencoded,multipart/form-data', + ) + ); + $form->excludes = array('form' => true); + + $input = $this->addElement( + 'input', + 'Formctrl', + 'Empty', + 'Common', + array( + 'accept' => 'ContentTypes', + 'accesskey' => 'Character', + 'alt' => 'Text', + 'checked' => 'Bool#checked', + 'disabled' => 'Bool#disabled', + 'maxlength' => 'Number', + 'name' => 'CDATA', + 'readonly' => 'Bool#readonly', + 'size' => 'Number', + 'src' => 'URI#embedded', + 'tabindex' => 'Number', + 'type' => 'Enum#text,password,checkbox,button,radio,submit,reset,file,hidden,image', + 'value' => 'CDATA', + ) + ); + $input->attr_transform_post[] = new HTMLPurifier_AttrTransform_Input(); + + $this->addElement( + 'select', + 'Formctrl', + 'Required: optgroup | option', + 'Common', + array( + 'disabled' => 'Bool#disabled', + 'multiple' => 'Bool#multiple', + 'name' => 'CDATA', + 'size' => 'Number', + 'tabindex' => 'Number', + ) + ); + + $this->addElement( + 'option', + false, + 'Optional: #PCDATA', + 'Common', + array( + 'disabled' => 'Bool#disabled', + 'label' => 'Text', + 'selected' => 'Bool#selected', + 'value' => 'CDATA', + ) + ); + // It's illegal for there to be more than one selected, but not + // be multiple. Also, no selected means undefined behavior. This might + // be difficult to implement; perhaps an injector, or a context variable. + + $textarea = $this->addElement( + 'textarea', + 'Formctrl', + 'Optional: #PCDATA', + 'Common', + array( + 'accesskey' => 'Character', + 'cols*' => 'Number', + 'disabled' => 'Bool#disabled', + 'name' => 'CDATA', + 'readonly' => 'Bool#readonly', + 'rows*' => 'Number', + 'tabindex' => 'Number', + ) + ); + $textarea->attr_transform_pre[] = new HTMLPurifier_AttrTransform_Textarea(); + + $button = $this->addElement( + 'button', + 'Formctrl', + 'Optional: #PCDATA | Heading | List | Block | Inline', + 'Common', + array( + 'accesskey' => 'Character', + 'disabled' => 'Bool#disabled', + 'name' => 'CDATA', + 'tabindex' => 'Number', + 'type' => 'Enum#button,submit,reset', + 'value' => 'CDATA', + ) + ); + + // For exclusions, ideally we'd specify content sets, not literal elements + $button->excludes = $this->makeLookup( + 'form', + 'fieldset', // Form + 'input', + 'select', + 'textarea', + 'label', + 'button', // Formctrl + 'a', // as per HTML 4.01 spec, this is omitted by modularization + 'isindex', + 'iframe' // legacy items + ); + + // Extra exclusion: img usemap="" is not permitted within this element. + // We'll omit this for now, since we don't have any good way of + // indicating it yet. + + // This is HIGHLY user-unfriendly; we need a custom child-def for this + $this->addElement('fieldset', 'Form', 'Custom: (#WS?,legend,(Flow|#PCDATA)*)', 'Common'); + + $label = $this->addElement( + 'label', + 'Formctrl', + 'Optional: #PCDATA | Inline', + 'Common', + array( + 'accesskey' => 'Character', + // 'for' => 'IDREF', // IDREF not implemented, cannot allow + ) + ); + $label->excludes = array('label' => true); + + $this->addElement( + 'legend', + false, + 'Optional: #PCDATA | Inline', + 'Common', + array( + 'accesskey' => 'Character', + ) + ); + + $this->addElement( + 'optgroup', + false, + 'Required: option', + 'Common', + array( + 'disabled' => 'Bool#disabled', + 'label*' => 'Text', + ) + ); + // Don't forget an injector for . This one's a little complex + // because it maps to multiple elements. + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Hypertext.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Hypertext.php new file mode 100644 index 0000000..72d7a31 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Hypertext.php @@ -0,0 +1,40 @@ +addElement( + 'a', + 'Inline', + 'Inline', + 'Common', + array( + // 'accesskey' => 'Character', + // 'charset' => 'Charset', + 'href' => 'URI', + // 'hreflang' => 'LanguageCode', + 'rel' => new HTMLPurifier_AttrDef_HTML_LinkTypes('rel'), + 'rev' => new HTMLPurifier_AttrDef_HTML_LinkTypes('rev'), + // 'tabindex' => 'Number', + // 'type' => 'ContentType', + ) + ); + $a->formatting = true; + $a->excludes = array('a' => true); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Iframe.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Iframe.php new file mode 100644 index 0000000..f7e7c91 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Iframe.php @@ -0,0 +1,51 @@ +get('HTML.SafeIframe')) { + $this->safe = true; + } + $this->addElement( + 'iframe', + 'Inline', + 'Flow', + 'Common', + array( + 'src' => 'URI#embedded', + 'width' => 'Length', + 'height' => 'Length', + 'name' => 'ID', + 'scrolling' => 'Enum#yes,no,auto', + 'frameborder' => 'Enum#0,1', + 'longdesc' => 'URI', + 'marginheight' => 'Pixels', + 'marginwidth' => 'Pixels', + ) + ); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Image.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Image.php new file mode 100644 index 0000000..0f5fdb3 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Image.php @@ -0,0 +1,49 @@ +get('HTML.MaxImgLength'); + $img = $this->addElement( + 'img', + 'Inline', + 'Empty', + 'Common', + array( + 'alt*' => 'Text', + // According to the spec, it's Length, but percents can + // be abused, so we allow only Pixels. + 'height' => 'Pixels#' . $max, + 'width' => 'Pixels#' . $max, + 'longdesc' => 'URI', + 'src*' => new HTMLPurifier_AttrDef_URI(true), // embedded + ) + ); + if ($max === null || $config->get('HTML.Trusted')) { + $img->attr['height'] = + $img->attr['width'] = 'Length'; + } + + // kind of strange, but splitting things up would be inefficient + $img->attr_transform_pre[] = + $img->attr_transform_post[] = + new HTMLPurifier_AttrTransform_ImgRequired(); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Legacy.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Legacy.php new file mode 100644 index 0000000..86b5299 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Legacy.php @@ -0,0 +1,186 @@ +addElement( + 'basefont', + 'Inline', + 'Empty', + null, + array( + 'color' => 'Color', + 'face' => 'Text', // extremely broad, we should + 'size' => 'Text', // tighten it + 'id' => 'ID' + ) + ); + $this->addElement('center', 'Block', 'Flow', 'Common'); + $this->addElement( + 'dir', + 'Block', + 'Required: li', + 'Common', + array( + 'compact' => 'Bool#compact' + ) + ); + $this->addElement( + 'font', + 'Inline', + 'Inline', + array('Core', 'I18N'), + array( + 'color' => 'Color', + 'face' => 'Text', // extremely broad, we should + 'size' => 'Text', // tighten it + ) + ); + $this->addElement( + 'menu', + 'Block', + 'Required: li', + 'Common', + array( + 'compact' => 'Bool#compact' + ) + ); + + $s = $this->addElement('s', 'Inline', 'Inline', 'Common'); + $s->formatting = true; + + $strike = $this->addElement('strike', 'Inline', 'Inline', 'Common'); + $strike->formatting = true; + + $u = $this->addElement('u', 'Inline', 'Inline', 'Common'); + $u->formatting = true; + + // setup modifications to old elements + + $align = 'Enum#left,right,center,justify'; + + $address = $this->addBlankElement('address'); + $address->content_model = 'Inline | #PCDATA | p'; + $address->content_model_type = 'optional'; + $address->child = false; + + $blockquote = $this->addBlankElement('blockquote'); + $blockquote->content_model = 'Flow | #PCDATA'; + $blockquote->content_model_type = 'optional'; + $blockquote->child = false; + + $br = $this->addBlankElement('br'); + $br->attr['clear'] = 'Enum#left,all,right,none'; + + $caption = $this->addBlankElement('caption'); + $caption->attr['align'] = 'Enum#top,bottom,left,right'; + + $div = $this->addBlankElement('div'); + $div->attr['align'] = $align; + + $dl = $this->addBlankElement('dl'); + $dl->attr['compact'] = 'Bool#compact'; + + for ($i = 1; $i <= 6; $i++) { + $h = $this->addBlankElement("h$i"); + $h->attr['align'] = $align; + } + + $hr = $this->addBlankElement('hr'); + $hr->attr['align'] = $align; + $hr->attr['noshade'] = 'Bool#noshade'; + $hr->attr['size'] = 'Pixels'; + $hr->attr['width'] = 'Length'; + + $img = $this->addBlankElement('img'); + $img->attr['align'] = 'IAlign'; + $img->attr['border'] = 'Pixels'; + $img->attr['hspace'] = 'Pixels'; + $img->attr['vspace'] = 'Pixels'; + + // figure out this integer business + + $li = $this->addBlankElement('li'); + $li->attr['value'] = new HTMLPurifier_AttrDef_Integer(); + $li->attr['type'] = 'Enum#s:1,i,I,a,A,disc,square,circle'; + + $ol = $this->addBlankElement('ol'); + $ol->attr['compact'] = 'Bool#compact'; + $ol->attr['start'] = new HTMLPurifier_AttrDef_Integer(); + $ol->attr['type'] = 'Enum#s:1,i,I,a,A'; + + $p = $this->addBlankElement('p'); + $p->attr['align'] = $align; + + $pre = $this->addBlankElement('pre'); + $pre->attr['width'] = 'Number'; + + // script omitted + + $table = $this->addBlankElement('table'); + $table->attr['align'] = 'Enum#left,center,right'; + $table->attr['bgcolor'] = 'Color'; + + $tr = $this->addBlankElement('tr'); + $tr->attr['bgcolor'] = 'Color'; + + $th = $this->addBlankElement('th'); + $th->attr['bgcolor'] = 'Color'; + $th->attr['height'] = 'Length'; + $th->attr['nowrap'] = 'Bool#nowrap'; + $th->attr['width'] = 'Length'; + + $td = $this->addBlankElement('td'); + $td->attr['bgcolor'] = 'Color'; + $td->attr['height'] = 'Length'; + $td->attr['nowrap'] = 'Bool#nowrap'; + $td->attr['width'] = 'Length'; + + $ul = $this->addBlankElement('ul'); + $ul->attr['compact'] = 'Bool#compact'; + $ul->attr['type'] = 'Enum#square,disc,circle'; + + // "safe" modifications to "unsafe" elements + // WARNING: If you want to add support for an unsafe, legacy + // attribute, make a new TrustedLegacy module with the trusted + // bit set appropriately + + $form = $this->addBlankElement('form'); + $form->content_model = 'Flow | #PCDATA'; + $form->content_model_type = 'optional'; + $form->attr['target'] = 'FrameTarget'; + + $input = $this->addBlankElement('input'); + $input->attr['align'] = 'IAlign'; + + $legend = $this->addBlankElement('legend'); + $legend->attr['align'] = 'LAlign'; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/List.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/List.php new file mode 100644 index 0000000..7a20ff7 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/List.php @@ -0,0 +1,51 @@ + 'List'); + + /** + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { + $ol = $this->addElement('ol', 'List', new HTMLPurifier_ChildDef_List(), 'Common'); + $ul = $this->addElement('ul', 'List', new HTMLPurifier_ChildDef_List(), 'Common'); + // XXX The wrap attribute is handled by MakeWellFormed. This is all + // quite unsatisfactory, because we generated this + // *specifically* for lists, and now a big chunk of the handling + // is done properly by the List ChildDef. So actually, we just + // want enough information to make autoclosing work properly, + // and then hand off the tricky stuff to the ChildDef. + $ol->wrap = 'li'; + $ul->wrap = 'li'; + $this->addElement('dl', 'List', 'Required: dt | dd', 'Common'); + + $this->addElement('li', false, 'Flow', 'Common'); + + $this->addElement('dd', false, 'Flow', 'Common'); + $this->addElement('dt', false, 'Inline', 'Common'); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Name.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Name.php new file mode 100644 index 0000000..60c0545 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Name.php @@ -0,0 +1,26 @@ +addBlankElement($name); + $element->attr['name'] = 'CDATA'; + if (!$config->get('HTML.Attr.Name.UseCDATA')) { + $element->attr_transform_post[] = new HTMLPurifier_AttrTransform_NameSync(); + } + } + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Nofollow.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Nofollow.php new file mode 100644 index 0000000..dc9410a --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Nofollow.php @@ -0,0 +1,25 @@ +addBlankElement('a'); + $a->attr_transform_post[] = new HTMLPurifier_AttrTransform_Nofollow(); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php new file mode 100644 index 0000000..da72225 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php @@ -0,0 +1,20 @@ + array( + 'lang' => 'LanguageCode', + ) + ); +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Object.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Object.php new file mode 100644 index 0000000..2f9efc5 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Object.php @@ -0,0 +1,62 @@ + to cater to legacy browsers: this + * module does not allow this sort of behavior + */ +class HTMLPurifier_HTMLModule_Object extends HTMLPurifier_HTMLModule +{ + /** + * @type string + */ + public $name = 'Object'; + + /** + * @type bool + */ + public $safe = false; + + /** + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { + $this->addElement( + 'object', + 'Inline', + 'Optional: #PCDATA | Flow | param', + 'Common', + array( + 'archive' => 'URI', + 'classid' => 'URI', + 'codebase' => 'URI', + 'codetype' => 'Text', + 'data' => 'URI', + 'declare' => 'Bool#declare', + 'height' => 'Length', + 'name' => 'CDATA', + 'standby' => 'Text', + 'tabindex' => 'Number', + 'type' => 'ContentType', + 'width' => 'Length' + ) + ); + + $this->addElement( + 'param', + false, + 'Empty', + null, + array( + 'id' => 'ID', + 'name*' => 'Text', + 'type' => 'Text', + 'value' => 'Text', + 'valuetype' => 'Enum#data,ref,object' + ) + ); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Presentation.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Presentation.php new file mode 100644 index 0000000..6458ce9 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Presentation.php @@ -0,0 +1,42 @@ +addElement('hr', 'Block', 'Empty', 'Common'); + $this->addElement('sub', 'Inline', 'Inline', 'Common'); + $this->addElement('sup', 'Inline', 'Inline', 'Common'); + $b = $this->addElement('b', 'Inline', 'Inline', 'Common'); + $b->formatting = true; + $big = $this->addElement('big', 'Inline', 'Inline', 'Common'); + $big->formatting = true; + $i = $this->addElement('i', 'Inline', 'Inline', 'Common'); + $i->formatting = true; + $small = $this->addElement('small', 'Inline', 'Inline', 'Common'); + $small->formatting = true; + $tt = $this->addElement('tt', 'Inline', 'Inline', 'Common'); + $tt->formatting = true; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Proprietary.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Proprietary.php new file mode 100644 index 0000000..5ee3c8e --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Proprietary.php @@ -0,0 +1,40 @@ +addElement( + 'marquee', + 'Inline', + 'Flow', + 'Common', + array( + 'direction' => 'Enum#left,right,up,down', + 'behavior' => 'Enum#alternate', + 'width' => 'Length', + 'height' => 'Length', + 'scrolldelay' => 'Number', + 'scrollamount' => 'Number', + 'loop' => 'Number', + 'bgcolor' => 'Color', + 'hspace' => 'Pixels', + 'vspace' => 'Pixels', + ) + ); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Ruby.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Ruby.php new file mode 100644 index 0000000..a0d4892 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Ruby.php @@ -0,0 +1,36 @@ +addElement( + 'ruby', + 'Inline', + 'Custom: ((rb, (rt | (rp, rt, rp))) | (rbc, rtc, rtc?))', + 'Common' + ); + $this->addElement('rbc', false, 'Required: rb', 'Common'); + $this->addElement('rtc', false, 'Required: rt', 'Common'); + $rb = $this->addElement('rb', false, 'Inline', 'Common'); + $rb->excludes = array('ruby' => true); + $rt = $this->addElement('rt', false, 'Inline', 'Common', array('rbspan' => 'Number')); + $rt->excludes = array('ruby' => true); + $this->addElement('rp', false, 'Optional: #PCDATA', 'Common'); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeEmbed.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeEmbed.php new file mode 100644 index 0000000..04e6689 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeEmbed.php @@ -0,0 +1,40 @@ +get('HTML.MaxImgLength'); + $embed = $this->addElement( + 'embed', + 'Inline', + 'Empty', + 'Common', + array( + 'src*' => 'URI#embedded', + 'type' => 'Enum#application/x-shockwave-flash', + 'width' => 'Pixels#' . $max, + 'height' => 'Pixels#' . $max, + 'allowscriptaccess' => 'Enum#never', + 'allownetworking' => 'Enum#internal', + 'flashvars' => 'Text', + 'wmode' => 'Enum#window,transparent,opaque', + 'name' => 'ID', + ) + ); + $embed->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeEmbed(); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeObject.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeObject.php new file mode 100644 index 0000000..1297f80 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeObject.php @@ -0,0 +1,62 @@ +get('HTML.MaxImgLength'); + $object = $this->addElement( + 'object', + 'Inline', + 'Optional: param | Flow | #PCDATA', + 'Common', + array( + // While technically not required by the spec, we're forcing + // it to this value. + 'type' => 'Enum#application/x-shockwave-flash', + 'width' => 'Pixels#' . $max, + 'height' => 'Pixels#' . $max, + 'data' => 'URI#embedded', + 'codebase' => new HTMLPurifier_AttrDef_Enum( + array( + 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0' + ) + ), + ) + ); + $object->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeObject(); + + $param = $this->addElement( + 'param', + false, + 'Empty', + false, + array( + 'id' => 'ID', + 'name*' => 'Text', + 'value' => 'Text' + ) + ); + $param->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeParam(); + $this->info_injector[] = 'SafeObject'; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeScripting.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeScripting.php new file mode 100644 index 0000000..aea7584 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeScripting.php @@ -0,0 +1,40 @@ +get('HTML.SafeScripting'); + $script = $this->addElement( + 'script', + 'Inline', + 'Optional:', // Not `Empty` to not allow to autoclose the #i', '', $html); + } + + return $html; + } + + /** + * Takes a string of HTML (fragment or document) and returns the content + * @todo Consider making protected + */ + public function extractBody($html) + { + $matches = array(); + $result = preg_match('|(.*?)]*>(.*)|is', $html, $matches); + if ($result) { + // Make sure it's not in a comment + $comment_start = strrpos($matches[1], ''); + if ($comment_start === false || + ($comment_end !== false && $comment_end > $comment_start)) { + return $matches[2]; + } + } + return $html; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/DOMLex.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/DOMLex.php new file mode 100644 index 0000000..ca5f25b --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/DOMLex.php @@ -0,0 +1,338 @@ +factory = new HTMLPurifier_TokenFactory(); + } + + /** + * @param string $html + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return HTMLPurifier_Token[] + */ + public function tokenizeHTML($html, $config, $context) + { + $html = $this->normalize($html, $config, $context); + + // attempt to armor stray angled brackets that cannot possibly + // form tags and thus are probably being used as emoticons + if ($config->get('Core.AggressivelyFixLt')) { + $char = '[^a-z!\/]'; + $comment = "/|\z)/is"; + $html = preg_replace_callback($comment, array($this, 'callbackArmorCommentEntities'), $html); + do { + $old = $html; + $html = preg_replace("/<($char)/i", '<\\1', $html); + } while ($html !== $old); + $html = preg_replace_callback($comment, array($this, 'callbackUndoCommentSubst'), $html); // fix comments + } + + // preprocess html, essential for UTF-8 + $html = $this->wrapHTML($html, $config, $context); + + $doc = new DOMDocument(); + $doc->encoding = 'UTF-8'; // theoretically, the above has this covered + + $options = 0; + if ($config->get('Core.AllowParseManyTags') && defined('LIBXML_PARSEHUGE')) { + $options |= LIBXML_PARSEHUGE; + } + + set_error_handler(array($this, 'muteErrorHandler')); + // loadHTML() fails on PHP 5.3 when second parameter is given + if ($options) { + $doc->loadHTML($html, $options); + } else { + $doc->loadHTML($html); + } + restore_error_handler(); + + $body = $doc->getElementsByTagName('html')->item(0)-> // + getElementsByTagName('body')->item(0); // + + $div = $body->getElementsByTagName('div')->item(0); //
    + $tokens = array(); + $this->tokenizeDOM($div, $tokens, $config); + // If the div has a sibling, that means we tripped across + // a premature
    tag. So remove the div we parsed, + // and then tokenize the rest of body. We can't tokenize + // the sibling directly as we'll lose the tags in that case. + if ($div->nextSibling) { + $body->removeChild($div); + $this->tokenizeDOM($body, $tokens, $config); + } + return $tokens; + } + + /** + * Iterative function that tokenizes a node, putting it into an accumulator. + * To iterate is human, to recurse divine - L. Peter Deutsch + * @param DOMNode $node DOMNode to be tokenized. + * @param HTMLPurifier_Token[] $tokens Array-list of already tokenized tokens. + * @return HTMLPurifier_Token of node appended to previously passed tokens. + */ + protected function tokenizeDOM($node, &$tokens, $config) + { + $level = 0; + $nodes = array($level => new HTMLPurifier_Queue(array($node))); + $closingNodes = array(); + do { + while (!$nodes[$level]->isEmpty()) { + $node = $nodes[$level]->shift(); // FIFO + $collect = $level > 0 ? true : false; + $needEndingTag = $this->createStartNode($node, $tokens, $collect, $config); + if ($needEndingTag) { + $closingNodes[$level][] = $node; + } + if ($node->childNodes && $node->childNodes->length) { + $level++; + $nodes[$level] = new HTMLPurifier_Queue(); + foreach ($node->childNodes as $childNode) { + $nodes[$level]->push($childNode); + } + } + } + $level--; + if ($level && isset($closingNodes[$level])) { + while ($node = array_pop($closingNodes[$level])) { + $this->createEndNode($node, $tokens); + } + } + } while ($level > 0); + } + + /** + * Portably retrieve the tag name of a node; deals with older versions + * of libxml like 2.7.6 + * @param DOMNode $node + */ + protected function getTagName($node) + { + if (isset($node->tagName)) { + return $node->tagName; + } else if (isset($node->nodeName)) { + return $node->nodeName; + } else if (isset($node->localName)) { + return $node->localName; + } + return null; + } + + /** + * Portably retrieve the data of a node; deals with older versions + * of libxml like 2.7.6 + * @param DOMNode $node + */ + protected function getData($node) + { + if (isset($node->data)) { + return $node->data; + } else if (isset($node->nodeValue)) { + return $node->nodeValue; + } else if (isset($node->textContent)) { + return $node->textContent; + } + return null; + } + + + /** + * @param DOMNode $node DOMNode to be tokenized. + * @param HTMLPurifier_Token[] $tokens Array-list of already tokenized tokens. + * @param bool $collect Says whether or start and close are collected, set to + * false at first recursion because it's the implicit DIV + * tag you're dealing with. + * @return bool if the token needs an endtoken + * @todo data and tagName properties don't seem to exist in DOMNode? + */ + protected function createStartNode($node, &$tokens, $collect, $config) + { + // intercept non element nodes. WE MUST catch all of them, + // but we're not getting the character reference nodes because + // those should have been preprocessed + if ($node->nodeType === XML_TEXT_NODE) { + $data = $this->getData($node); // Handle variable data property + if ($data !== null) { + $tokens[] = $this->factory->createText($data); + } + return false; + } elseif ($node->nodeType === XML_CDATA_SECTION_NODE) { + // undo libxml's special treatment of )#si', + array($this, 'scriptCallback'), + $html + ); + } + + $html = $this->normalize($html, $config, $context); + + $cursor = 0; // our location in the text + $inside_tag = false; // whether or not we're parsing the inside of a tag + $array = array(); // result array + + // This is also treated to mean maintain *column* numbers too + $maintain_line_numbers = $config->get('Core.MaintainLineNumbers'); + + if ($maintain_line_numbers === null) { + // automatically determine line numbering by checking + // if error collection is on + $maintain_line_numbers = $config->get('Core.CollectErrors'); + } + + if ($maintain_line_numbers) { + $current_line = 1; + $current_col = 0; + $length = strlen($html); + } else { + $current_line = false; + $current_col = false; + $length = false; + } + $context->register('CurrentLine', $current_line); + $context->register('CurrentCol', $current_col); + $nl = "\n"; + // how often to manually recalculate. This will ALWAYS be right, + // but it's pretty wasteful. Set to 0 to turn off + $synchronize_interval = $config->get('Core.DirectLexLineNumberSyncInterval'); + + $e = false; + if ($config->get('Core.CollectErrors')) { + $e =& $context->get('ErrorCollector'); + } + + // for testing synchronization + $loops = 0; + + while (++$loops) { + // $cursor is either at the start of a token, or inside of + // a tag (i.e. there was a < immediately before it), as indicated + // by $inside_tag + + if ($maintain_line_numbers) { + // $rcursor, however, is always at the start of a token. + $rcursor = $cursor - (int)$inside_tag; + + // Column number is cheap, so we calculate it every round. + // We're interested at the *end* of the newline string, so + // we need to add strlen($nl) == 1 to $nl_pos before subtracting it + // from our "rcursor" position. + $nl_pos = strrpos($html, $nl, $rcursor - $length); + $current_col = $rcursor - (is_bool($nl_pos) ? 0 : $nl_pos + 1); + + // recalculate lines + if ($synchronize_interval && // synchronization is on + $cursor > 0 && // cursor is further than zero + $loops % $synchronize_interval === 0) { // time to synchronize! + $current_line = 1 + $this->substrCount($html, $nl, 0, $cursor); + } + } + + $position_next_lt = strpos($html, '<', $cursor); + $position_next_gt = strpos($html, '>', $cursor); + + // triggers on "asdf" but not "asdf " + // special case to set up context + if ($position_next_lt === $cursor) { + $inside_tag = true; + $cursor++; + } + + if (!$inside_tag && $position_next_lt !== false) { + // We are not inside tag and there still is another tag to parse + $token = new + HTMLPurifier_Token_Text( + $this->parseText( + substr( + $html, + $cursor, + $position_next_lt - $cursor + ), $config + ) + ); + if ($maintain_line_numbers) { + $token->rawPosition($current_line, $current_col); + $current_line += $this->substrCount($html, $nl, $cursor, $position_next_lt - $cursor); + } + $array[] = $token; + $cursor = $position_next_lt + 1; + $inside_tag = true; + continue; + } elseif (!$inside_tag) { + // We are not inside tag but there are no more tags + // If we're already at the end, break + if ($cursor === strlen($html)) { + break; + } + // Create Text of rest of string + $token = new + HTMLPurifier_Token_Text( + $this->parseText( + substr( + $html, + $cursor + ), $config + ) + ); + if ($maintain_line_numbers) { + $token->rawPosition($current_line, $current_col); + } + $array[] = $token; + break; + } elseif ($inside_tag && $position_next_gt !== false) { + // We are in tag and it is well formed + // Grab the internals of the tag + $strlen_segment = $position_next_gt - $cursor; + + if ($strlen_segment < 1) { + // there's nothing to process! + $token = new HTMLPurifier_Token_Text('<'); + $cursor++; + continue; + } + + $segment = substr($html, $cursor, $strlen_segment); + + if ($segment === false) { + // somehow, we attempted to access beyond the end of + // the string, defense-in-depth, reported by Nate Abele + break; + } + + // Check if it's a comment + if (substr($segment, 0, 3) === '!--') { + // re-determine segment length, looking for --> + $position_comment_end = strpos($html, '-->', $cursor); + if ($position_comment_end === false) { + // uh oh, we have a comment that extends to + // infinity. Can't be helped: set comment + // end position to end of string + if ($e) { + $e->send(E_WARNING, 'Lexer: Unclosed comment'); + } + $position_comment_end = strlen($html); + $end = true; + } else { + $end = false; + } + $strlen_segment = $position_comment_end - $cursor; + $segment = substr($html, $cursor, $strlen_segment); + $token = new + HTMLPurifier_Token_Comment( + substr( + $segment, + 3, + $strlen_segment - 3 + ) + ); + if ($maintain_line_numbers) { + $token->rawPosition($current_line, $current_col); + $current_line += $this->substrCount($html, $nl, $cursor, $strlen_segment); + } + $array[] = $token; + $cursor = $end ? $position_comment_end : $position_comment_end + 3; + $inside_tag = false; + continue; + } + + // Check if it's an end tag + $is_end_tag = (strpos($segment, '/') === 0); + if ($is_end_tag) { + $type = substr($segment, 1); + $token = new HTMLPurifier_Token_End($type); + if ($maintain_line_numbers) { + $token->rawPosition($current_line, $current_col); + $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor); + } + $array[] = $token; + $inside_tag = false; + $cursor = $position_next_gt + 1; + continue; + } + + // Check leading character is alnum, if not, we may + // have accidently grabbed an emoticon. Translate into + // text and go our merry way + if (!ctype_alpha($segment[0])) { + // XML: $segment[0] !== '_' && $segment[0] !== ':' + if ($e) { + $e->send(E_NOTICE, 'Lexer: Unescaped lt'); + } + $token = new HTMLPurifier_Token_Text('<'); + if ($maintain_line_numbers) { + $token->rawPosition($current_line, $current_col); + $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor); + } + $array[] = $token; + $inside_tag = false; + continue; + } + + // Check if it is explicitly self closing, if so, remove + // trailing slash. Remember, we could have a tag like
    , so + // any later token processing scripts must convert improperly + // classified EmptyTags from StartTags. + $is_self_closing = (strrpos($segment, '/') === $strlen_segment - 1); + if ($is_self_closing) { + $strlen_segment--; + $segment = substr($segment, 0, $strlen_segment); + } + + // Check if there are any attributes + $position_first_space = strcspn($segment, $this->_whitespace); + + if ($position_first_space >= $strlen_segment) { + if ($is_self_closing) { + $token = new HTMLPurifier_Token_Empty($segment); + } else { + $token = new HTMLPurifier_Token_Start($segment); + } + if ($maintain_line_numbers) { + $token->rawPosition($current_line, $current_col); + $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor); + } + $array[] = $token; + $inside_tag = false; + $cursor = $position_next_gt + 1; + continue; + } + + // Grab out all the data + $type = substr($segment, 0, $position_first_space); + $attribute_string = + trim( + substr( + $segment, + $position_first_space + ) + ); + if ($attribute_string) { + $attr = $this->parseAttributeString( + $attribute_string, + $config, + $context + ); + } else { + $attr = array(); + } + + if ($is_self_closing) { + $token = new HTMLPurifier_Token_Empty($type, $attr); + } else { + $token = new HTMLPurifier_Token_Start($type, $attr); + } + if ($maintain_line_numbers) { + $token->rawPosition($current_line, $current_col); + $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor); + } + $array[] = $token; + $cursor = $position_next_gt + 1; + $inside_tag = false; + continue; + } else { + // inside tag, but there's no ending > sign + if ($e) { + $e->send(E_WARNING, 'Lexer: Missing gt'); + } + $token = new + HTMLPurifier_Token_Text( + '<' . + $this->parseText( + substr($html, $cursor), $config + ) + ); + if ($maintain_line_numbers) { + $token->rawPosition($current_line, $current_col); + } + // no cursor scroll? Hmm... + $array[] = $token; + break; + } + break; + } + + $context->destroy('CurrentLine'); + $context->destroy('CurrentCol'); + return $array; + } + + /** + * PHP 5.0.x compatible substr_count that implements offset and length + * @param string $haystack + * @param string $needle + * @param int $offset + * @param int $length + * @return int + */ + protected function substrCount($haystack, $needle, $offset, $length) + { + static $oldVersion; + if ($oldVersion === null) { + $oldVersion = version_compare(PHP_VERSION, '5.1', '<'); + } + if ($oldVersion) { + $haystack = substr($haystack, $offset, $length); + return substr_count($haystack, $needle); + } else { + return substr_count($haystack, $needle, $offset, $length); + } + } + + /** + * Takes the inside of an HTML tag and makes an assoc array of attributes. + * + * @param string $string Inside of tag excluding name. + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array Assoc array of attributes. + */ + public function parseAttributeString($string, $config, $context) + { + $string = (string)$string; // quick typecast + + if ($string == '') { + return array(); + } // no attributes + + $e = false; + if ($config->get('Core.CollectErrors')) { + $e =& $context->get('ErrorCollector'); + } + + // let's see if we can abort as quickly as possible + // one equal sign, no spaces => one attribute + $num_equal = substr_count($string, '='); + $has_space = strpos($string, ' '); + if ($num_equal === 0 && !$has_space) { + // bool attribute + return array($string => $string); + } elseif ($num_equal === 1 && !$has_space) { + // only one attribute + list($key, $quoted_value) = explode('=', $string); + $quoted_value = trim($quoted_value); + if (!$key) { + if ($e) { + $e->send(E_ERROR, 'Lexer: Missing attribute key'); + } + return array(); + } + if (!$quoted_value) { + return array($key => ''); + } + $first_char = @$quoted_value[0]; + $last_char = @$quoted_value[strlen($quoted_value) - 1]; + + $same_quote = ($first_char == $last_char); + $open_quote = ($first_char == '"' || $first_char == "'"); + + if ($same_quote && $open_quote) { + // well behaved + $value = substr($quoted_value, 1, strlen($quoted_value) - 2); + } else { + // not well behaved + if ($open_quote) { + if ($e) { + $e->send(E_ERROR, 'Lexer: Missing end quote'); + } + $value = substr($quoted_value, 1); + } else { + $value = $quoted_value; + } + } + if ($value === false) { + $value = ''; + } + return array($key => $this->parseAttr($value, $config)); + } + + // setup loop environment + $array = array(); // return assoc array of attributes + $cursor = 0; // current position in string (moves forward) + $size = strlen($string); // size of the string (stays the same) + + // if we have unquoted attributes, the parser expects a terminating + // space, so let's guarantee that there's always a terminating space. + $string .= ' '; + + $old_cursor = -1; + while ($cursor < $size) { + if ($old_cursor >= $cursor) { + throw new Exception("Infinite loop detected"); + } + $old_cursor = $cursor; + + $cursor += ($value = strspn($string, $this->_whitespace, $cursor)); + // grab the key + + $key_begin = $cursor; //we're currently at the start of the key + + // scroll past all characters that are the key (not whitespace or =) + $cursor += strcspn($string, $this->_whitespace . '=', $cursor); + + $key_end = $cursor; // now at the end of the key + + $key = substr($string, $key_begin, $key_end - $key_begin); + + if (!$key) { + if ($e) { + $e->send(E_ERROR, 'Lexer: Missing attribute key'); + } + $cursor += 1 + strcspn($string, $this->_whitespace, $cursor + 1); // prevent infinite loop + continue; // empty key + } + + // scroll past all whitespace + $cursor += strspn($string, $this->_whitespace, $cursor); + + if ($cursor >= $size) { + $array[$key] = $key; + break; + } + + // if the next character is an equal sign, we've got a regular + // pair, otherwise, it's a bool attribute + $first_char = @$string[$cursor]; + + if ($first_char == '=') { + // key="value" + + $cursor++; + $cursor += strspn($string, $this->_whitespace, $cursor); + + if ($cursor === false) { + $array[$key] = ''; + break; + } + + // we might be in front of a quote right now + + $char = @$string[$cursor]; + + if ($char == '"' || $char == "'") { + // it's quoted, end bound is $char + $cursor++; + $value_begin = $cursor; + $cursor = strpos($string, $char, $cursor); + $value_end = $cursor; + } else { + // it's not quoted, end bound is whitespace + $value_begin = $cursor; + $cursor += strcspn($string, $this->_whitespace, $cursor); + $value_end = $cursor; + } + + // we reached a premature end + if ($cursor === false) { + $cursor = $size; + $value_end = $cursor; + } + + $value = substr($string, $value_begin, $value_end - $value_begin); + if ($value === false) { + $value = ''; + } + $array[$key] = $this->parseAttr($value, $config); + $cursor++; + } else { + // boolattr + if ($key !== '') { + $array[$key] = $key; + } else { + // purely theoretical + if ($e) { + $e->send(E_ERROR, 'Lexer: Missing attribute key'); + } + } + } + } + return $array; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/PH5P.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/PH5P.php new file mode 100644 index 0000000..72476dd --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/PH5P.php @@ -0,0 +1,4788 @@ +normalize($html, $config, $context); + $new_html = $this->wrapHTML($new_html, $config, $context, false /* no div */); + try { + $parser = new HTML5($new_html); + $doc = $parser->save(); + } catch (DOMException $e) { + // Uh oh, it failed. Punt to DirectLex. + $lexer = new HTMLPurifier_Lexer_DirectLex(); + $context->register('PH5PError', $e); // save the error, so we can detect it + return $lexer->tokenizeHTML($html, $config, $context); // use original HTML + } + $tokens = array(); + $this->tokenizeDOM( + $doc->getElementsByTagName('html')->item(0)-> // + getElementsByTagName('body')->item(0) // + , + $tokens, $config + ); + return $tokens; + } +} + +/* + +Copyright 2007 Jeroen van der Meer + +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. + +*/ + +class HTML5 +{ + private $data; + private $char; + private $EOF; + private $state; + private $tree; + private $token; + private $content_model; + private $escape = false; + private $entities = array( + 'AElig;', + 'AElig', + 'AMP;', + 'AMP', + 'Aacute;', + 'Aacute', + 'Acirc;', + 'Acirc', + 'Agrave;', + 'Agrave', + 'Alpha;', + 'Aring;', + 'Aring', + 'Atilde;', + 'Atilde', + 'Auml;', + 'Auml', + 'Beta;', + 'COPY;', + 'COPY', + 'Ccedil;', + 'Ccedil', + 'Chi;', + 'Dagger;', + 'Delta;', + 'ETH;', + 'ETH', + 'Eacute;', + 'Eacute', + 'Ecirc;', + 'Ecirc', + 'Egrave;', + 'Egrave', + 'Epsilon;', + 'Eta;', + 'Euml;', + 'Euml', + 'GT;', + 'GT', + 'Gamma;', + 'Iacute;', + 'Iacute', + 'Icirc;', + 'Icirc', + 'Igrave;', + 'Igrave', + 'Iota;', + 'Iuml;', + 'Iuml', + 'Kappa;', + 'LT;', + 'LT', + 'Lambda;', + 'Mu;', + 'Ntilde;', + 'Ntilde', + 'Nu;', + 'OElig;', + 'Oacute;', + 'Oacute', + 'Ocirc;', + 'Ocirc', + 'Ograve;', + 'Ograve', + 'Omega;', + 'Omicron;', + 'Oslash;', + 'Oslash', + 'Otilde;', + 'Otilde', + 'Ouml;', + 'Ouml', + 'Phi;', + 'Pi;', + 'Prime;', + 'Psi;', + 'QUOT;', + 'QUOT', + 'REG;', + 'REG', + 'Rho;', + 'Scaron;', + 'Sigma;', + 'THORN;', + 'THORN', + 'TRADE;', + 'Tau;', + 'Theta;', + 'Uacute;', + 'Uacute', + 'Ucirc;', + 'Ucirc', + 'Ugrave;', + 'Ugrave', + 'Upsilon;', + 'Uuml;', + 'Uuml', + 'Xi;', + 'Yacute;', + 'Yacute', + 'Yuml;', + 'Zeta;', + 'aacute;', + 'aacute', + 'acirc;', + 'acirc', + 'acute;', + 'acute', + 'aelig;', + 'aelig', + 'agrave;', + 'agrave', + 'alefsym;', + 'alpha;', + 'amp;', + 'amp', + 'and;', + 'ang;', + 'apos;', + 'aring;', + 'aring', + 'asymp;', + 'atilde;', + 'atilde', + 'auml;', + 'auml', + 'bdquo;', + 'beta;', + 'brvbar;', + 'brvbar', + 'bull;', + 'cap;', + 'ccedil;', + 'ccedil', + 'cedil;', + 'cedil', + 'cent;', + 'cent', + 'chi;', + 'circ;', + 'clubs;', + 'cong;', + 'copy;', + 'copy', + 'crarr;', + 'cup;', + 'curren;', + 'curren', + 'dArr;', + 'dagger;', + 'darr;', + 'deg;', + 'deg', + 'delta;', + 'diams;', + 'divide;', + 'divide', + 'eacute;', + 'eacute', + 'ecirc;', + 'ecirc', + 'egrave;', + 'egrave', + 'empty;', + 'emsp;', + 'ensp;', + 'epsilon;', + 'equiv;', + 'eta;', + 'eth;', + 'eth', + 'euml;', + 'euml', + 'euro;', + 'exist;', + 'fnof;', + 'forall;', + 'frac12;', + 'frac12', + 'frac14;', + 'frac14', + 'frac34;', + 'frac34', + 'frasl;', + 'gamma;', + 'ge;', + 'gt;', + 'gt', + 'hArr;', + 'harr;', + 'hearts;', + 'hellip;', + 'iacute;', + 'iacute', + 'icirc;', + 'icirc', + 'iexcl;', + 'iexcl', + 'igrave;', + 'igrave', + 'image;', + 'infin;', + 'int;', + 'iota;', + 'iquest;', + 'iquest', + 'isin;', + 'iuml;', + 'iuml', + 'kappa;', + 'lArr;', + 'lambda;', + 'lang;', + 'laquo;', + 'laquo', + 'larr;', + 'lceil;', + 'ldquo;', + 'le;', + 'lfloor;', + 'lowast;', + 'loz;', + 'lrm;', + 'lsaquo;', + 'lsquo;', + 'lt;', + 'lt', + 'macr;', + 'macr', + 'mdash;', + 'micro;', + 'micro', + 'middot;', + 'middot', + 'minus;', + 'mu;', + 'nabla;', + 'nbsp;', + 'nbsp', + 'ndash;', + 'ne;', + 'ni;', + 'not;', + 'not', + 'notin;', + 'nsub;', + 'ntilde;', + 'ntilde', + 'nu;', + 'oacute;', + 'oacute', + 'ocirc;', + 'ocirc', + 'oelig;', + 'ograve;', + 'ograve', + 'oline;', + 'omega;', + 'omicron;', + 'oplus;', + 'or;', + 'ordf;', + 'ordf', + 'ordm;', + 'ordm', + 'oslash;', + 'oslash', + 'otilde;', + 'otilde', + 'otimes;', + 'ouml;', + 'ouml', + 'para;', + 'para', + 'part;', + 'permil;', + 'perp;', + 'phi;', + 'pi;', + 'piv;', + 'plusmn;', + 'plusmn', + 'pound;', + 'pound', + 'prime;', + 'prod;', + 'prop;', + 'psi;', + 'quot;', + 'quot', + 'rArr;', + 'radic;', + 'rang;', + 'raquo;', + 'raquo', + 'rarr;', + 'rceil;', + 'rdquo;', + 'real;', + 'reg;', + 'reg', + 'rfloor;', + 'rho;', + 'rlm;', + 'rsaquo;', + 'rsquo;', + 'sbquo;', + 'scaron;', + 'sdot;', + 'sect;', + 'sect', + 'shy;', + 'shy', + 'sigma;', + 'sigmaf;', + 'sim;', + 'spades;', + 'sub;', + 'sube;', + 'sum;', + 'sup1;', + 'sup1', + 'sup2;', + 'sup2', + 'sup3;', + 'sup3', + 'sup;', + 'supe;', + 'szlig;', + 'szlig', + 'tau;', + 'there4;', + 'theta;', + 'thetasym;', + 'thinsp;', + 'thorn;', + 'thorn', + 'tilde;', + 'times;', + 'times', + 'trade;', + 'uArr;', + 'uacute;', + 'uacute', + 'uarr;', + 'ucirc;', + 'ucirc', + 'ugrave;', + 'ugrave', + 'uml;', + 'uml', + 'upsih;', + 'upsilon;', + 'uuml;', + 'uuml', + 'weierp;', + 'xi;', + 'yacute;', + 'yacute', + 'yen;', + 'yen', + 'yuml;', + 'yuml', + 'zeta;', + 'zwj;', + 'zwnj;' + ); + + const PCDATA = 0; + const RCDATA = 1; + const CDATA = 2; + const PLAINTEXT = 3; + + const DOCTYPE = 0; + const STARTTAG = 1; + const ENDTAG = 2; + const COMMENT = 3; + const CHARACTR = 4; + const EOF = 5; + + public function __construct($data) + { + $this->data = $data; + $this->char = -1; + $this->EOF = strlen($data); + $this->tree = new HTML5TreeConstructer; + $this->content_model = self::PCDATA; + + $this->state = 'data'; + + while ($this->state !== null) { + $this->{$this->state . 'State'}(); + } + } + + public function save() + { + return $this->tree->save(); + } + + private function char() + { + return ($this->char < $this->EOF) + ? $this->data[$this->char] + : false; + } + + private function character($s, $l = 0) + { + if ($s + $l < $this->EOF) { + if ($l === 0) { + return $this->data[$s]; + } else { + return substr($this->data, $s, $l); + } + } + } + + private function characters($char_class, $start) + { + return preg_replace('#^([' . $char_class . ']+).*#s', '\\1', substr($this->data, $start)); + } + + private function dataState() + { + // Consume the next input character + $this->char++; + $char = $this->char(); + + if ($char === '&' && ($this->content_model === self::PCDATA || $this->content_model === self::RCDATA)) { + /* U+0026 AMPERSAND (&) + When the content model flag is set to one of the PCDATA or RCDATA + states: switch to the entity data state. Otherwise: treat it as per + the "anything else" entry below. */ + $this->state = 'entityData'; + + } elseif ($char === '-') { + /* If the content model flag is set to either the RCDATA state or + the CDATA state, and the escape flag is false, and there are at + least three characters before this one in the input stream, and the + last four characters in the input stream, including this one, are + U+003C LESS-THAN SIGN, U+0021 EXCLAMATION MARK, U+002D HYPHEN-MINUS, + and U+002D HYPHEN-MINUS (""), + set the escape flag to false. */ + if (($this->content_model === self::RCDATA || + $this->content_model === self::CDATA) && $this->escape === true && + $this->character($this->char, 3) === '-->' + ) { + $this->escape = false; + } + + /* In any case, emit the input character as a character token. + Stay in the data state. */ + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => $char + ) + ); + + } elseif ($this->char === $this->EOF) { + /* EOF + Emit an end-of-file token. */ + $this->EOF(); + + } elseif ($this->content_model === self::PLAINTEXT) { + /* When the content model flag is set to the PLAINTEXT state + THIS DIFFERS GREATLY FROM THE SPEC: Get the remaining characters of + the text and emit it as a character token. */ + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => substr($this->data, $this->char) + ) + ); + + $this->EOF(); + + } else { + /* Anything else + THIS DIFFERS GREATLY FROM THE SPEC: Get as many character that + otherwise would also be treated as a character token and emit it + as a single character token. Stay in the data state. */ + $len = strcspn($this->data, '<&', $this->char); + $char = substr($this->data, $this->char, $len); + $this->char += $len - 1; + + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => $char + ) + ); + + $this->state = 'data'; + } + } + + private function entityDataState() + { + // Attempt to consume an entity. + $entity = $this->entity(); + + // If nothing is returned, emit a U+0026 AMPERSAND character token. + // Otherwise, emit the character token that was returned. + $char = (!$entity) ? '&' : $entity; + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => $char + ) + ); + + // Finally, switch to the data state. + $this->state = 'data'; + } + + private function tagOpenState() + { + switch ($this->content_model) { + case self::RCDATA: + case self::CDATA: + /* If the next input character is a U+002F SOLIDUS (/) character, + consume it and switch to the close tag open state. If the next + input character is not a U+002F SOLIDUS (/) character, emit a + U+003C LESS-THAN SIGN character token and switch to the data + state to process the next input character. */ + if ($this->character($this->char + 1) === '/') { + $this->char++; + $this->state = 'closeTagOpen'; + + } else { + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => '<' + ) + ); + + $this->state = 'data'; + } + break; + + case self::PCDATA: + // If the content model flag is set to the PCDATA state + // Consume the next input character: + $this->char++; + $char = $this->char(); + + if ($char === '!') { + /* U+0021 EXCLAMATION MARK (!) + Switch to the markup declaration open state. */ + $this->state = 'markupDeclarationOpen'; + + } elseif ($char === '/') { + /* U+002F SOLIDUS (/) + Switch to the close tag open state. */ + $this->state = 'closeTagOpen'; + + } elseif (preg_match('/^[A-Za-z]$/', $char)) { + /* U+0041 LATIN LETTER A through to U+005A LATIN LETTER Z + Create a new start tag token, set its tag name to the lowercase + version of the input character (add 0x0020 to the character's code + point), then switch to the tag name state. (Don't emit the token + yet; further details will be filled in before it is emitted.) */ + $this->token = array( + 'name' => strtolower($char), + 'type' => self::STARTTAG, + 'attr' => array() + ); + + $this->state = 'tagName'; + + } elseif ($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Parse error. Emit a U+003C LESS-THAN SIGN character token and a + U+003E GREATER-THAN SIGN character token. Switch to the data state. */ + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => '<>' + ) + ); + + $this->state = 'data'; + + } elseif ($char === '?') { + /* U+003F QUESTION MARK (?) + Parse error. Switch to the bogus comment state. */ + $this->state = 'bogusComment'; + + } else { + /* Anything else + Parse error. Emit a U+003C LESS-THAN SIGN character token and + reconsume the current input character in the data state. */ + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => '<' + ) + ); + + $this->char--; + $this->state = 'data'; + } + break; + } + } + + private function closeTagOpenState() + { + $next_node = strtolower($this->characters('A-Za-z', $this->char + 1)); + $the_same = count($this->tree->stack) > 0 && $next_node === end($this->tree->stack)->nodeName; + + if (($this->content_model === self::RCDATA || $this->content_model === self::CDATA) && + (!$the_same || ($the_same && (!preg_match( + '/[\t\n\x0b\x0c >\/]/', + $this->character($this->char + 1 + strlen($next_node)) + ) || $this->EOF === $this->char))) + ) { + /* If the content model flag is set to the RCDATA or CDATA states then + examine the next few characters. If they do not match the tag name of + the last start tag token emitted (case insensitively), or if they do but + they are not immediately followed by one of the following characters: + * U+0009 CHARACTER TABULATION + * U+000A LINE FEED (LF) + * U+000B LINE TABULATION + * U+000C FORM FEED (FF) + * U+0020 SPACE + * U+003E GREATER-THAN SIGN (>) + * U+002F SOLIDUS (/) + * EOF + ...then there is a parse error. Emit a U+003C LESS-THAN SIGN character + token, a U+002F SOLIDUS character token, and switch to the data state + to process the next input character. */ + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => 'state = 'data'; + + } else { + /* Otherwise, if the content model flag is set to the PCDATA state, + or if the next few characters do match that tag name, consume the + next input character: */ + $this->char++; + $char = $this->char(); + + if (preg_match('/^[A-Za-z]$/', $char)) { + /* U+0041 LATIN LETTER A through to U+005A LATIN LETTER Z + Create a new end tag token, set its tag name to the lowercase version + of the input character (add 0x0020 to the character's code point), then + switch to the tag name state. (Don't emit the token yet; further details + will be filled in before it is emitted.) */ + $this->token = array( + 'name' => strtolower($char), + 'type' => self::ENDTAG + ); + + $this->state = 'tagName'; + + } elseif ($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Parse error. Switch to the data state. */ + $this->state = 'data'; + + } elseif ($this->char === $this->EOF) { + /* EOF + Parse error. Emit a U+003C LESS-THAN SIGN character token and a U+002F + SOLIDUS character token. Reconsume the EOF character in the data state. */ + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => 'char--; + $this->state = 'data'; + + } else { + /* Parse error. Switch to the bogus comment state. */ + $this->state = 'bogusComment'; + } + } + } + + private function tagNameState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + /* U+0009 CHARACTER TABULATION + U+000A LINE FEED (LF) + U+000B LINE TABULATION + U+000C FORM FEED (FF) + U+0020 SPACE + Switch to the before attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif ($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Emit the current tag token. Switch to the data state. */ + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif ($this->char === $this->EOF) { + /* EOF + Parse error. Emit the current tag token. Reconsume the EOF + character in the data state. */ + $this->emitToken($this->token); + + $this->char--; + $this->state = 'data'; + + } elseif ($char === '/') { + /* U+002F SOLIDUS (/) + Parse error unless this is a permitted slash. Switch to the before + attribute name state. */ + $this->state = 'beforeAttributeName'; + + } else { + /* Anything else + Append the current input character to the current tag token's tag name. + Stay in the tag name state. */ + $this->token['name'] .= strtolower($char); + $this->state = 'tagName'; + } + } + + private function beforeAttributeNameState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + /* U+0009 CHARACTER TABULATION + U+000A LINE FEED (LF) + U+000B LINE TABULATION + U+000C FORM FEED (FF) + U+0020 SPACE + Stay in the before attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif ($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Emit the current tag token. Switch to the data state. */ + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif ($char === '/') { + /* U+002F SOLIDUS (/) + Parse error unless this is a permitted slash. Stay in the before + attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif ($this->char === $this->EOF) { + /* EOF + Parse error. Emit the current tag token. Reconsume the EOF + character in the data state. */ + $this->emitToken($this->token); + + $this->char--; + $this->state = 'data'; + + } else { + /* Anything else + Start a new attribute in the current tag token. Set that attribute's + name to the current input character, and its value to the empty string. + Switch to the attribute name state. */ + $this->token['attr'][] = array( + 'name' => strtolower($char), + 'value' => null + ); + + $this->state = 'attributeName'; + } + } + + private function attributeNameState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + /* U+0009 CHARACTER TABULATION + U+000A LINE FEED (LF) + U+000B LINE TABULATION + U+000C FORM FEED (FF) + U+0020 SPACE + Stay in the before attribute name state. */ + $this->state = 'afterAttributeName'; + + } elseif ($char === '=') { + /* U+003D EQUALS SIGN (=) + Switch to the before attribute value state. */ + $this->state = 'beforeAttributeValue'; + + } elseif ($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Emit the current tag token. Switch to the data state. */ + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif ($char === '/' && $this->character($this->char + 1) !== '>') { + /* U+002F SOLIDUS (/) + Parse error unless this is a permitted slash. Switch to the before + attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif ($this->char === $this->EOF) { + /* EOF + Parse error. Emit the current tag token. Reconsume the EOF + character in the data state. */ + $this->emitToken($this->token); + + $this->char--; + $this->state = 'data'; + + } else { + /* Anything else + Append the current input character to the current attribute's name. + Stay in the attribute name state. */ + $last = count($this->token['attr']) - 1; + $this->token['attr'][$last]['name'] .= strtolower($char); + + $this->state = 'attributeName'; + } + } + + private function afterAttributeNameState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + /* U+0009 CHARACTER TABULATION + U+000A LINE FEED (LF) + U+000B LINE TABULATION + U+000C FORM FEED (FF) + U+0020 SPACE + Stay in the after attribute name state. */ + $this->state = 'afterAttributeName'; + + } elseif ($char === '=') { + /* U+003D EQUALS SIGN (=) + Switch to the before attribute value state. */ + $this->state = 'beforeAttributeValue'; + + } elseif ($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Emit the current tag token. Switch to the data state. */ + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif ($char === '/' && $this->character($this->char + 1) !== '>') { + /* U+002F SOLIDUS (/) + Parse error unless this is a permitted slash. Switch to the + before attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif ($this->char === $this->EOF) { + /* EOF + Parse error. Emit the current tag token. Reconsume the EOF + character in the data state. */ + $this->emitToken($this->token); + + $this->char--; + $this->state = 'data'; + + } else { + /* Anything else + Start a new attribute in the current tag token. Set that attribute's + name to the current input character, and its value to the empty string. + Switch to the attribute name state. */ + $this->token['attr'][] = array( + 'name' => strtolower($char), + 'value' => null + ); + + $this->state = 'attributeName'; + } + } + + private function beforeAttributeValueState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + /* U+0009 CHARACTER TABULATION + U+000A LINE FEED (LF) + U+000B LINE TABULATION + U+000C FORM FEED (FF) + U+0020 SPACE + Stay in the before attribute value state. */ + $this->state = 'beforeAttributeValue'; + + } elseif ($char === '"') { + /* U+0022 QUOTATION MARK (") + Switch to the attribute value (double-quoted) state. */ + $this->state = 'attributeValueDoubleQuoted'; + + } elseif ($char === '&') { + /* U+0026 AMPERSAND (&) + Switch to the attribute value (unquoted) state and reconsume + this input character. */ + $this->char--; + $this->state = 'attributeValueUnquoted'; + + } elseif ($char === '\'') { + /* U+0027 APOSTROPHE (') + Switch to the attribute value (single-quoted) state. */ + $this->state = 'attributeValueSingleQuoted'; + + } elseif ($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Emit the current tag token. Switch to the data state. */ + $this->emitToken($this->token); + $this->state = 'data'; + + } else { + /* Anything else + Append the current input character to the current attribute's value. + Switch to the attribute value (unquoted) state. */ + $last = count($this->token['attr']) - 1; + $this->token['attr'][$last]['value'] .= $char; + + $this->state = 'attributeValueUnquoted'; + } + } + + private function attributeValueDoubleQuotedState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if ($char === '"') { + /* U+0022 QUOTATION MARK (") + Switch to the before attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif ($char === '&') { + /* U+0026 AMPERSAND (&) + Switch to the entity in attribute value state. */ + $this->entityInAttributeValueState('double'); + + } elseif ($this->char === $this->EOF) { + /* EOF + Parse error. Emit the current tag token. Reconsume the character + in the data state. */ + $this->emitToken($this->token); + + $this->char--; + $this->state = 'data'; + + } else { + /* Anything else + Append the current input character to the current attribute's value. + Stay in the attribute value (double-quoted) state. */ + $last = count($this->token['attr']) - 1; + $this->token['attr'][$last]['value'] .= $char; + + $this->state = 'attributeValueDoubleQuoted'; + } + } + + private function attributeValueSingleQuotedState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if ($char === '\'') { + /* U+0022 QUOTATION MARK (') + Switch to the before attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif ($char === '&') { + /* U+0026 AMPERSAND (&) + Switch to the entity in attribute value state. */ + $this->entityInAttributeValueState('single'); + + } elseif ($this->char === $this->EOF) { + /* EOF + Parse error. Emit the current tag token. Reconsume the character + in the data state. */ + $this->emitToken($this->token); + + $this->char--; + $this->state = 'data'; + + } else { + /* Anything else + Append the current input character to the current attribute's value. + Stay in the attribute value (single-quoted) state. */ + $last = count($this->token['attr']) - 1; + $this->token['attr'][$last]['value'] .= $char; + + $this->state = 'attributeValueSingleQuoted'; + } + } + + private function attributeValueUnquotedState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + /* U+0009 CHARACTER TABULATION + U+000A LINE FEED (LF) + U+000B LINE TABULATION + U+000C FORM FEED (FF) + U+0020 SPACE + Switch to the before attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif ($char === '&') { + /* U+0026 AMPERSAND (&) + Switch to the entity in attribute value state. */ + $this->entityInAttributeValueState(); + + } elseif ($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Emit the current tag token. Switch to the data state. */ + $this->emitToken($this->token); + $this->state = 'data'; + + } else { + /* Anything else + Append the current input character to the current attribute's value. + Stay in the attribute value (unquoted) state. */ + $last = count($this->token['attr']) - 1; + $this->token['attr'][$last]['value'] .= $char; + + $this->state = 'attributeValueUnquoted'; + } + } + + private function entityInAttributeValueState() + { + // Attempt to consume an entity. + $entity = $this->entity(); + + // If nothing is returned, append a U+0026 AMPERSAND character to the + // current attribute's value. Otherwise, emit the character token that + // was returned. + $char = (!$entity) + ? '&' + : $entity; + + $last = count($this->token['attr']) - 1; + $this->token['attr'][$last]['value'] .= $char; + } + + private function bogusCommentState() + { + /* Consume every character up to the first U+003E GREATER-THAN SIGN + character (>) or the end of the file (EOF), whichever comes first. Emit + a comment token whose data is the concatenation of all the characters + starting from and including the character that caused the state machine + to switch into the bogus comment state, up to and including the last + consumed character before the U+003E character, if any, or up to the + end of the file otherwise. (If the comment was started by the end of + the file (EOF), the token is empty.) */ + $data = $this->characters('^>', $this->char); + $this->emitToken( + array( + 'data' => $data, + 'type' => self::COMMENT + ) + ); + + $this->char += strlen($data); + + /* Switch to the data state. */ + $this->state = 'data'; + + /* If the end of the file was reached, reconsume the EOF character. */ + if ($this->char === $this->EOF) { + $this->char = $this->EOF - 1; + } + } + + private function markupDeclarationOpenState() + { + /* If the next two characters are both U+002D HYPHEN-MINUS (-) + characters, consume those two characters, create a comment token whose + data is the empty string, and switch to the comment state. */ + if ($this->character($this->char + 1, 2) === '--') { + $this->char += 2; + $this->state = 'comment'; + $this->token = array( + 'data' => null, + 'type' => self::COMMENT + ); + + /* Otherwise if the next seven chacacters are a case-insensitive match + for the word "DOCTYPE", then consume those characters and switch to the + DOCTYPE state. */ + } elseif (strtolower($this->character($this->char + 1, 7)) === 'doctype') { + $this->char += 7; + $this->state = 'doctype'; + + /* Otherwise, is is a parse error. Switch to the bogus comment state. + The next character that is consumed, if any, is the first character + that will be in the comment. */ + } else { + $this->char++; + $this->state = 'bogusComment'; + } + } + + private function commentState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + /* U+002D HYPHEN-MINUS (-) */ + if ($char === '-') { + /* Switch to the comment dash state */ + $this->state = 'commentDash'; + + /* EOF */ + } elseif ($this->char === $this->EOF) { + /* Parse error. Emit the comment token. Reconsume the EOF character + in the data state. */ + $this->emitToken($this->token); + $this->char--; + $this->state = 'data'; + + /* Anything else */ + } else { + /* Append the input character to the comment token's data. Stay in + the comment state. */ + $this->token['data'] .= $char; + } + } + + private function commentDashState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + /* U+002D HYPHEN-MINUS (-) */ + if ($char === '-') { + /* Switch to the comment end state */ + $this->state = 'commentEnd'; + + /* EOF */ + } elseif ($this->char === $this->EOF) { + /* Parse error. Emit the comment token. Reconsume the EOF character + in the data state. */ + $this->emitToken($this->token); + $this->char--; + $this->state = 'data'; + + /* Anything else */ + } else { + /* Append a U+002D HYPHEN-MINUS (-) character and the input + character to the comment token's data. Switch to the comment state. */ + $this->token['data'] .= '-' . $char; + $this->state = 'comment'; + } + } + + private function commentEndState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + if ($char === '>') { + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif ($char === '-') { + $this->token['data'] .= '-'; + + } elseif ($this->char === $this->EOF) { + $this->emitToken($this->token); + $this->char--; + $this->state = 'data'; + + } else { + $this->token['data'] .= '--' . $char; + $this->state = 'comment'; + } + } + + private function doctypeState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + $this->state = 'beforeDoctypeName'; + + } else { + $this->char--; + $this->state = 'beforeDoctypeName'; + } + } + + private function beforeDoctypeNameState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + // Stay in the before DOCTYPE name state. + + } elseif (preg_match('/^[a-z]$/', $char)) { + $this->token = array( + 'name' => strtoupper($char), + 'type' => self::DOCTYPE, + 'error' => true + ); + + $this->state = 'doctypeName'; + + } elseif ($char === '>') { + $this->emitToken( + array( + 'name' => null, + 'type' => self::DOCTYPE, + 'error' => true + ) + ); + + $this->state = 'data'; + + } elseif ($this->char === $this->EOF) { + $this->emitToken( + array( + 'name' => null, + 'type' => self::DOCTYPE, + 'error' => true + ) + ); + + $this->char--; + $this->state = 'data'; + + } else { + $this->token = array( + 'name' => $char, + 'type' => self::DOCTYPE, + 'error' => true + ); + + $this->state = 'doctypeName'; + } + } + + private function doctypeNameState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + $this->state = 'AfterDoctypeName'; + + } elseif ($char === '>') { + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif (preg_match('/^[a-z]$/', $char)) { + $this->token['name'] .= strtoupper($char); + + } elseif ($this->char === $this->EOF) { + $this->emitToken($this->token); + $this->char--; + $this->state = 'data'; + + } else { + $this->token['name'] .= $char; + } + + $this->token['error'] = ($this->token['name'] === 'HTML') + ? false + : true; + } + + private function afterDoctypeNameState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + // Stay in the DOCTYPE name state. + + } elseif ($char === '>') { + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif ($this->char === $this->EOF) { + $this->emitToken($this->token); + $this->char--; + $this->state = 'data'; + + } else { + $this->token['error'] = true; + $this->state = 'bogusDoctype'; + } + } + + private function bogusDoctypeState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + if ($char === '>') { + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif ($this->char === $this->EOF) { + $this->emitToken($this->token); + $this->char--; + $this->state = 'data'; + + } else { + // Stay in the bogus DOCTYPE state. + } + } + + private function entity() + { + $start = $this->char; + + // This section defines how to consume an entity. This definition is + // used when parsing entities in text and in attributes. + + // The behaviour depends on the identity of the next character (the + // one immediately after the U+0026 AMPERSAND character): + + switch ($this->character($this->char + 1)) { + // U+0023 NUMBER SIGN (#) + case '#': + + // The behaviour further depends on the character after the + // U+0023 NUMBER SIGN: + switch ($this->character($this->char + 1)) { + // U+0078 LATIN SMALL LETTER X + // U+0058 LATIN CAPITAL LETTER X + case 'x': + case 'X': + // Follow the steps below, but using the range of + // characters U+0030 DIGIT ZERO through to U+0039 DIGIT + // NINE, U+0061 LATIN SMALL LETTER A through to U+0066 + // LATIN SMALL LETTER F, and U+0041 LATIN CAPITAL LETTER + // A, through to U+0046 LATIN CAPITAL LETTER F (in other + // words, 0-9, A-F, a-f). + $char = 1; + $char_class = '0-9A-Fa-f'; + break; + + // Anything else + default: + // Follow the steps below, but using the range of + // characters U+0030 DIGIT ZERO through to U+0039 DIGIT + // NINE (i.e. just 0-9). + $char = 0; + $char_class = '0-9'; + break; + } + + // Consume as many characters as match the range of characters + // given above. + $this->char++; + $e_name = $this->characters($char_class, $this->char + $char + 1); + $entity = $this->character($start, $this->char); + $cond = strlen($e_name) > 0; + + // The rest of the parsing happens below. + break; + + // Anything else + default: + // Consume the maximum number of characters possible, with the + // consumed characters case-sensitively matching one of the + // identifiers in the first column of the entities table. + + $e_name = $this->characters('0-9A-Za-z;', $this->char + 1); + $len = strlen($e_name); + + for ($c = 1; $c <= $len; $c++) { + $id = substr($e_name, 0, $c); + $this->char++; + + if (in_array($id, $this->entities)) { + if ($e_name[$c - 1] !== ';') { + if ($c < $len && $e_name[$c] == ';') { + $this->char++; // consume extra semicolon + } + } + $entity = $id; + break; + } + } + + $cond = isset($entity); + // The rest of the parsing happens below. + break; + } + + if (!$cond) { + // If no match can be made, then this is a parse error. No + // characters are consumed, and nothing is returned. + $this->char = $start; + return false; + } + + // Return a character token for the character corresponding to the + // entity name (as given by the second column of the entities table). + return html_entity_decode('&' . rtrim($entity, ';') . ';', ENT_QUOTES, 'UTF-8'); + } + + private function emitToken($token) + { + $emit = $this->tree->emitToken($token); + + if (is_int($emit)) { + $this->content_model = $emit; + + } elseif ($token['type'] === self::ENDTAG) { + $this->content_model = self::PCDATA; + } + } + + private function EOF() + { + $this->state = null; + $this->tree->emitToken( + array( + 'type' => self::EOF + ) + ); + } +} + +class HTML5TreeConstructer +{ + public $stack = array(); + + private $phase; + private $mode; + private $dom; + private $foster_parent = null; + private $a_formatting = array(); + + private $head_pointer = null; + private $form_pointer = null; + + private $scoping = array('button', 'caption', 'html', 'marquee', 'object', 'table', 'td', 'th'); + private $formatting = array( + 'a', + 'b', + 'big', + 'em', + 'font', + 'i', + 'nobr', + 's', + 'small', + 'strike', + 'strong', + 'tt', + 'u' + ); + private $special = array( + 'address', + 'area', + 'base', + 'basefont', + 'bgsound', + 'blockquote', + 'body', + 'br', + 'center', + 'col', + 'colgroup', + 'dd', + 'dir', + 'div', + 'dl', + 'dt', + 'embed', + 'fieldset', + 'form', + 'frame', + 'frameset', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'head', + 'hr', + 'iframe', + 'image', + 'img', + 'input', + 'isindex', + 'li', + 'link', + 'listing', + 'menu', + 'meta', + 'noembed', + 'noframes', + 'noscript', + 'ol', + 'optgroup', + 'option', + 'p', + 'param', + 'plaintext', + 'pre', + 'script', + 'select', + 'spacer', + 'style', + 'tbody', + 'textarea', + 'tfoot', + 'thead', + 'title', + 'tr', + 'ul', + 'wbr' + ); + + // The different phases. + const INIT_PHASE = 0; + const ROOT_PHASE = 1; + const MAIN_PHASE = 2; + const END_PHASE = 3; + + // The different insertion modes for the main phase. + const BEFOR_HEAD = 0; + const IN_HEAD = 1; + const AFTER_HEAD = 2; + const IN_BODY = 3; + const IN_TABLE = 4; + const IN_CAPTION = 5; + const IN_CGROUP = 6; + const IN_TBODY = 7; + const IN_ROW = 8; + const IN_CELL = 9; + const IN_SELECT = 10; + const AFTER_BODY = 11; + const IN_FRAME = 12; + const AFTR_FRAME = 13; + + // The different types of elements. + const SPECIAL = 0; + const SCOPING = 1; + const FORMATTING = 2; + const PHRASING = 3; + + const MARKER = 0; + + public function __construct() + { + $this->phase = self::INIT_PHASE; + $this->mode = self::BEFOR_HEAD; + $this->dom = new DOMDocument; + + $this->dom->encoding = 'UTF-8'; + $this->dom->preserveWhiteSpace = true; + $this->dom->substituteEntities = true; + $this->dom->strictErrorChecking = false; + } + + // Process tag tokens + public function emitToken($token) + { + switch ($this->phase) { + case self::INIT_PHASE: + return $this->initPhase($token); + break; + case self::ROOT_PHASE: + return $this->rootElementPhase($token); + break; + case self::MAIN_PHASE: + return $this->mainPhase($token); + break; + case self::END_PHASE : + return $this->trailingEndPhase($token); + break; + } + } + + private function initPhase($token) + { + /* Initially, the tree construction stage must handle each token + emitted from the tokenisation stage as follows: */ + + /* A DOCTYPE token that is marked as being in error + A comment token + A start tag token + An end tag token + A character token that is not one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE + An end-of-file token */ + if ((isset($token['error']) && $token['error']) || + $token['type'] === HTML5::COMMENT || + $token['type'] === HTML5::STARTTAG || + $token['type'] === HTML5::ENDTAG || + $token['type'] === HTML5::EOF || + ($token['type'] === HTML5::CHARACTR && isset($token['data']) && + !preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) + ) { + /* This specification does not define how to handle this case. In + particular, user agents may ignore the entirety of this specification + altogether for such documents, and instead invoke special parse modes + with a greater emphasis on backwards compatibility. */ + + $this->phase = self::ROOT_PHASE; + return $this->rootElementPhase($token); + + /* A DOCTYPE token marked as being correct */ + } elseif (isset($token['error']) && !$token['error']) { + /* Append a DocumentType node to the Document node, with the name + attribute set to the name given in the DOCTYPE token (which will be + "HTML"), and the other attributes specific to DocumentType objects + set to null, empty lists, or the empty string as appropriate. */ + $doctype = new DOMDocumentType(null, null, 'HTML'); + + /* Then, switch to the root element phase of the tree construction + stage. */ + $this->phase = self::ROOT_PHASE; + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + } elseif (isset($token['data']) && preg_match( + '/^[\t\n\x0b\x0c ]+$/', + $token['data'] + ) + ) { + /* Append that character to the Document node. */ + $text = $this->dom->createTextNode($token['data']); + $this->dom->appendChild($text); + } + } + + private function rootElementPhase($token) + { + /* After the initial phase, as each token is emitted from the tokenisation + stage, it must be processed as described in this section. */ + + /* A DOCTYPE token */ + if ($token['type'] === HTML5::DOCTYPE) { + // Parse error. Ignore the token. + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the Document object with the data + attribute set to the data given in the comment token. */ + $comment = $this->dom->createComment($token['data']); + $this->dom->appendChild($comment); + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + } elseif ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Append that character to the Document node. */ + $text = $this->dom->createTextNode($token['data']); + $this->dom->appendChild($text); + + /* A character token that is not one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED + (FF), or U+0020 SPACE + A start tag token + An end tag token + An end-of-file token */ + } elseif (($token['type'] === HTML5::CHARACTR && + !preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) || + $token['type'] === HTML5::STARTTAG || + $token['type'] === HTML5::ENDTAG || + $token['type'] === HTML5::EOF + ) { + /* Create an HTMLElement node with the tag name html, in the HTML + namespace. Append it to the Document object. Switch to the main + phase and reprocess the current token. */ + $html = $this->dom->createElement('html'); + $this->dom->appendChild($html); + $this->stack[] = $html; + + $this->phase = self::MAIN_PHASE; + return $this->mainPhase($token); + } + } + + private function mainPhase($token) + { + /* Tokens in the main phase must be handled as follows: */ + + /* A DOCTYPE token */ + if ($token['type'] === HTML5::DOCTYPE) { + // Parse error. Ignore the token. + + /* A start tag token with the tag name "html" */ + } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'html') { + /* If this start tag token was not the first start tag token, then + it is a parse error. */ + + /* For each attribute on the token, check to see if the attribute + is already present on the top element of the stack of open elements. + If it is not, add the attribute and its corresponding value to that + element. */ + foreach ($token['attr'] as $attr) { + if (!$this->stack[0]->hasAttribute($attr['name'])) { + $this->stack[0]->setAttribute($attr['name'], $attr['value']); + } + } + + /* An end-of-file token */ + } elseif ($token['type'] === HTML5::EOF) { + /* Generate implied end tags. */ + $this->generateImpliedEndTags(); + + /* Anything else. */ + } else { + /* Depends on the insertion mode: */ + switch ($this->mode) { + case self::BEFOR_HEAD: + return $this->beforeHead($token); + break; + case self::IN_HEAD: + return $this->inHead($token); + break; + case self::AFTER_HEAD: + return $this->afterHead($token); + break; + case self::IN_BODY: + return $this->inBody($token); + break; + case self::IN_TABLE: + return $this->inTable($token); + break; + case self::IN_CAPTION: + return $this->inCaption($token); + break; + case self::IN_CGROUP: + return $this->inColumnGroup($token); + break; + case self::IN_TBODY: + return $this->inTableBody($token); + break; + case self::IN_ROW: + return $this->inRow($token); + break; + case self::IN_CELL: + return $this->inCell($token); + break; + case self::IN_SELECT: + return $this->inSelect($token); + break; + case self::AFTER_BODY: + return $this->afterBody($token); + break; + case self::IN_FRAME: + return $this->inFrameset($token); + break; + case self::AFTR_FRAME: + return $this->afterFrameset($token); + break; + case self::END_PHASE: + return $this->trailingEndPhase($token); + break; + } + } + } + + private function beforeHead($token) + { + /* Handle the token as follows: */ + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + if ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Append the character to the current node. */ + $this->insertText($token['data']); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data attribute + set to the data given in the comment token. */ + $this->insertComment($token['data']); + + /* A start tag token with the tag name "head" */ + } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'head') { + /* Create an element for the token, append the new element to the + current node and push it onto the stack of open elements. */ + $element = $this->insertElement($token); + + /* Set the head element pointer to this new element node. */ + $this->head_pointer = $element; + + /* Change the insertion mode to "in head". */ + $this->mode = self::IN_HEAD; + + /* A start tag token whose tag name is one of: "base", "link", "meta", + "script", "style", "title". Or an end tag with the tag name "html". + Or a character token that is not one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE. Or any other start tag token */ + } elseif ($token['type'] === HTML5::STARTTAG || + ($token['type'] === HTML5::ENDTAG && $token['name'] === 'html') || + ($token['type'] === HTML5::CHARACTR && !preg_match( + '/^[\t\n\x0b\x0c ]$/', + $token['data'] + )) + ) { + /* Act as if a start tag token with the tag name "head" and no + attributes had been seen, then reprocess the current token. */ + $this->beforeHead( + array( + 'name' => 'head', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + return $this->inHead($token); + + /* Any other end tag */ + } elseif ($token['type'] === HTML5::ENDTAG) { + /* Parse error. Ignore the token. */ + } + } + + private function inHead($token) + { + /* Handle the token as follows: */ + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE. + + THIS DIFFERS FROM THE SPEC: If the current node is either a title, style + or script element, append the character to the current node regardless + of its content. */ + if (($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) || ( + $token['type'] === HTML5::CHARACTR && in_array( + end($this->stack)->nodeName, + array('title', 'style', 'script') + )) + ) { + /* Append the character to the current node. */ + $this->insertText($token['data']); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data attribute + set to the data given in the comment token. */ + $this->insertComment($token['data']); + + } elseif ($token['type'] === HTML5::ENDTAG && + in_array($token['name'], array('title', 'style', 'script')) + ) { + array_pop($this->stack); + return HTML5::PCDATA; + + /* A start tag with the tag name "title" */ + } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'title') { + /* Create an element for the token and append the new element to the + node pointed to by the head element pointer, or, if that is null + (innerHTML case), to the current node. */ + if ($this->head_pointer !== null) { + $element = $this->insertElement($token, false); + $this->head_pointer->appendChild($element); + + } else { + $element = $this->insertElement($token); + } + + /* Switch the tokeniser's content model flag to the RCDATA state. */ + return HTML5::RCDATA; + + /* A start tag with the tag name "style" */ + } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'style') { + /* Create an element for the token and append the new element to the + node pointed to by the head element pointer, or, if that is null + (innerHTML case), to the current node. */ + if ($this->head_pointer !== null) { + $element = $this->insertElement($token, false); + $this->head_pointer->appendChild($element); + + } else { + $this->insertElement($token); + } + + /* Switch the tokeniser's content model flag to the CDATA state. */ + return HTML5::CDATA; + + /* A start tag with the tag name "script" */ + } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'script') { + /* Create an element for the token. */ + $element = $this->insertElement($token, false); + $this->head_pointer->appendChild($element); + + /* Switch the tokeniser's content model flag to the CDATA state. */ + return HTML5::CDATA; + + /* A start tag with the tag name "base", "link", or "meta" */ + } elseif ($token['type'] === HTML5::STARTTAG && in_array( + $token['name'], + array('base', 'link', 'meta') + ) + ) { + /* Create an element for the token and append the new element to the + node pointed to by the head element pointer, or, if that is null + (innerHTML case), to the current node. */ + if ($this->head_pointer !== null) { + $element = $this->insertElement($token, false); + $this->head_pointer->appendChild($element); + array_pop($this->stack); + + } else { + $this->insertElement($token); + } + + /* An end tag with the tag name "head" */ + } elseif ($token['type'] === HTML5::ENDTAG && $token['name'] === 'head') { + /* If the current node is a head element, pop the current node off + the stack of open elements. */ + if ($this->head_pointer->isSameNode(end($this->stack))) { + array_pop($this->stack); + + /* Otherwise, this is a parse error. */ + } else { + // k + } + + /* Change the insertion mode to "after head". */ + $this->mode = self::AFTER_HEAD; + + /* A start tag with the tag name "head" or an end tag except "html". */ + } elseif (($token['type'] === HTML5::STARTTAG && $token['name'] === 'head') || + ($token['type'] === HTML5::ENDTAG && $token['name'] !== 'html') + ) { + // Parse error. Ignore the token. + + /* Anything else */ + } else { + /* If the current node is a head element, act as if an end tag + token with the tag name "head" had been seen. */ + if ($this->head_pointer->isSameNode(end($this->stack))) { + $this->inHead( + array( + 'name' => 'head', + 'type' => HTML5::ENDTAG + ) + ); + + /* Otherwise, change the insertion mode to "after head". */ + } else { + $this->mode = self::AFTER_HEAD; + } + + /* Then, reprocess the current token. */ + return $this->afterHead($token); + } + } + + private function afterHead($token) + { + /* Handle the token as follows: */ + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + if ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Append the character to the current node. */ + $this->insertText($token['data']); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data attribute + set to the data given in the comment token. */ + $this->insertComment($token['data']); + + /* A start tag token with the tag name "body" */ + } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'body') { + /* Insert a body element for the token. */ + $this->insertElement($token); + + /* Change the insertion mode to "in body". */ + $this->mode = self::IN_BODY; + + /* A start tag token with the tag name "frameset" */ + } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'frameset') { + /* Insert a frameset element for the token. */ + $this->insertElement($token); + + /* Change the insertion mode to "in frameset". */ + $this->mode = self::IN_FRAME; + + /* A start tag token whose tag name is one of: "base", "link", "meta", + "script", "style", "title" */ + } elseif ($token['type'] === HTML5::STARTTAG && in_array( + $token['name'], + array('base', 'link', 'meta', 'script', 'style', 'title') + ) + ) { + /* Parse error. Switch the insertion mode back to "in head" and + reprocess the token. */ + $this->mode = self::IN_HEAD; + return $this->inHead($token); + + /* Anything else */ + } else { + /* Act as if a start tag token with the tag name "body" and no + attributes had been seen, and then reprocess the current token. */ + $this->afterHead( + array( + 'name' => 'body', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + return $this->inBody($token); + } + } + + private function inBody($token) + { + /* Handle the token as follows: */ + + switch ($token['type']) { + /* A character token */ + case HTML5::CHARACTR: + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Append the token's character to the current node. */ + $this->insertText($token['data']); + break; + + /* A comment token */ + case HTML5::COMMENT: + /* Append a Comment node to the current node with the data + attribute set to the data given in the comment token. */ + $this->insertComment($token['data']); + break; + + case HTML5::STARTTAG: + switch ($token['name']) { + /* A start tag token whose tag name is one of: "script", + "style" */ + case 'script': + case 'style': + /* Process the token as if the insertion mode had been "in + head". */ + return $this->inHead($token); + break; + + /* A start tag token whose tag name is one of: "base", "link", + "meta", "title" */ + case 'base': + case 'link': + case 'meta': + case 'title': + /* Parse error. Process the token as if the insertion mode + had been "in head". */ + return $this->inHead($token); + break; + + /* A start tag token with the tag name "body" */ + case 'body': + /* Parse error. If the second element on the stack of open + elements is not a body element, or, if the stack of open + elements has only one node on it, then ignore the token. + (innerHTML case) */ + if (count($this->stack) === 1 || $this->stack[1]->nodeName !== 'body') { + // Ignore + + /* Otherwise, for each attribute on the token, check to see + if the attribute is already present on the body element (the + second element) on the stack of open elements. If it is not, + add the attribute and its corresponding value to that + element. */ + } else { + foreach ($token['attr'] as $attr) { + if (!$this->stack[1]->hasAttribute($attr['name'])) { + $this->stack[1]->setAttribute($attr['name'], $attr['value']); + } + } + } + break; + + /* A start tag whose tag name is one of: "address", + "blockquote", "center", "dir", "div", "dl", "fieldset", + "listing", "menu", "ol", "p", "ul" */ + case 'address': + case 'blockquote': + case 'center': + case 'dir': + case 'div': + case 'dl': + case 'fieldset': + case 'listing': + case 'menu': + case 'ol': + case 'p': + case 'ul': + /* If the stack of open elements has a p element in scope, + then act as if an end tag with the tag name p had been + seen. */ + if ($this->elementInScope('p')) { + $this->emitToken( + array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + break; + + /* A start tag whose tag name is "form" */ + case 'form': + /* If the form element pointer is not null, ignore the + token with a parse error. */ + if ($this->form_pointer !== null) { + // Ignore. + + /* Otherwise: */ + } else { + /* If the stack of open elements has a p element in + scope, then act as if an end tag with the tag name p + had been seen. */ + if ($this->elementInScope('p')) { + $this->emitToken( + array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* Insert an HTML element for the token, and set the + form element pointer to point to the element created. */ + $element = $this->insertElement($token); + $this->form_pointer = $element; + } + break; + + /* A start tag whose tag name is "li", "dd" or "dt" */ + case 'li': + case 'dd': + case 'dt': + /* If the stack of open elements has a p element in scope, + then act as if an end tag with the tag name p had been + seen. */ + if ($this->elementInScope('p')) { + $this->emitToken( + array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + ) + ); + } + + $stack_length = count($this->stack) - 1; + + for ($n = $stack_length; 0 <= $n; $n--) { + /* 1. Initialise node to be the current node (the + bottommost node of the stack). */ + $stop = false; + $node = $this->stack[$n]; + $cat = $this->getElementCategory($node->tagName); + + /* 2. If node is an li, dd or dt element, then pop all + the nodes from the current node up to node, including + node, then stop this algorithm. */ + if ($token['name'] === $node->tagName || ($token['name'] !== 'li' + && ($node->tagName === 'dd' || $node->tagName === 'dt')) + ) { + for ($x = $stack_length; $x >= $n; $x--) { + array_pop($this->stack); + } + + break; + } + + /* 3. If node is not in the formatting category, and is + not in the phrasing category, and is not an address or + div element, then stop this algorithm. */ + if ($cat !== self::FORMATTING && $cat !== self::PHRASING && + $node->tagName !== 'address' && $node->tagName !== 'div' + ) { + break; + } + } + + /* Finally, insert an HTML element with the same tag + name as the token's. */ + $this->insertElement($token); + break; + + /* A start tag token whose tag name is "plaintext" */ + case 'plaintext': + /* If the stack of open elements has a p element in scope, + then act as if an end tag with the tag name p had been + seen. */ + if ($this->elementInScope('p')) { + $this->emitToken( + array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + return HTML5::PLAINTEXT; + break; + + /* A start tag whose tag name is one of: "h1", "h2", "h3", "h4", + "h5", "h6" */ + case 'h1': + case 'h2': + case 'h3': + case 'h4': + case 'h5': + case 'h6': + /* If the stack of open elements has a p element in scope, + then act as if an end tag with the tag name p had been seen. */ + if ($this->elementInScope('p')) { + $this->emitToken( + array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* If the stack of open elements has in scope an element whose + tag name is one of "h1", "h2", "h3", "h4", "h5", or "h6", then + this is a parse error; pop elements from the stack until an + element with one of those tag names has been popped from the + stack. */ + while ($this->elementInScope(array('h1', 'h2', 'h3', 'h4', 'h5', 'h6'))) { + array_pop($this->stack); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + break; + + /* A start tag whose tag name is "a" */ + case 'a': + /* If the list of active formatting elements contains + an element whose tag name is "a" between the end of the + list and the last marker on the list (or the start of + the list if there is no marker on the list), then this + is a parse error; act as if an end tag with the tag name + "a" had been seen, then remove that element from the list + of active formatting elements and the stack of open + elements if the end tag didn't already remove it (it + might not have if the element is not in table scope). */ + $leng = count($this->a_formatting); + + for ($n = $leng - 1; $n >= 0; $n--) { + if ($this->a_formatting[$n] === self::MARKER) { + break; + + } elseif ($this->a_formatting[$n]->nodeName === 'a') { + $this->emitToken( + array( + 'name' => 'a', + 'type' => HTML5::ENDTAG + ) + ); + break; + } + } + + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $el = $this->insertElement($token); + + /* Add that element to the list of active formatting + elements. */ + $this->a_formatting[] = $el; + break; + + /* A start tag whose tag name is one of: "b", "big", "em", "font", + "i", "nobr", "s", "small", "strike", "strong", "tt", "u" */ + case 'b': + case 'big': + case 'em': + case 'font': + case 'i': + case 'nobr': + case 's': + case 'small': + case 'strike': + case 'strong': + case 'tt': + case 'u': + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $el = $this->insertElement($token); + + /* Add that element to the list of active formatting + elements. */ + $this->a_formatting[] = $el; + break; + + /* A start tag token whose tag name is "button" */ + case 'button': + /* If the stack of open elements has a button element in scope, + then this is a parse error; act as if an end tag with the tag + name "button" had been seen, then reprocess the token. (We don't + do that. Unnecessary.) */ + if ($this->elementInScope('button')) { + $this->inBody( + array( + 'name' => 'button', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Insert a marker at the end of the list of active + formatting elements. */ + $this->a_formatting[] = self::MARKER; + break; + + /* A start tag token whose tag name is one of: "marquee", "object" */ + case 'marquee': + case 'object': + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Insert a marker at the end of the list of active + formatting elements. */ + $this->a_formatting[] = self::MARKER; + break; + + /* A start tag token whose tag name is "xmp" */ + case 'xmp': + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Switch the content model flag to the CDATA state. */ + return HTML5::CDATA; + break; + + /* A start tag whose tag name is "table" */ + case 'table': + /* If the stack of open elements has a p element in scope, + then act as if an end tag with the tag name p had been seen. */ + if ($this->elementInScope('p')) { + $this->emitToken( + array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Change the insertion mode to "in table". */ + $this->mode = self::IN_TABLE; + break; + + /* A start tag whose tag name is one of: "area", "basefont", + "bgsound", "br", "embed", "img", "param", "spacer", "wbr" */ + case 'area': + case 'basefont': + case 'bgsound': + case 'br': + case 'embed': + case 'img': + case 'param': + case 'spacer': + case 'wbr': + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Immediately pop the current node off the stack of open elements. */ + array_pop($this->stack); + break; + + /* A start tag whose tag name is "hr" */ + case 'hr': + /* If the stack of open elements has a p element in scope, + then act as if an end tag with the tag name p had been seen. */ + if ($this->elementInScope('p')) { + $this->emitToken( + array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Immediately pop the current node off the stack of open elements. */ + array_pop($this->stack); + break; + + /* A start tag whose tag name is "image" */ + case 'image': + /* Parse error. Change the token's tag name to "img" and + reprocess it. (Don't ask.) */ + $token['name'] = 'img'; + return $this->inBody($token); + break; + + /* A start tag whose tag name is "input" */ + case 'input': + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an input element for the token. */ + $element = $this->insertElement($token, false); + + /* If the form element pointer is not null, then associate the + input element with the form element pointed to by the form + element pointer. */ + $this->form_pointer !== null + ? $this->form_pointer->appendChild($element) + : end($this->stack)->appendChild($element); + + /* Pop that input element off the stack of open elements. */ + array_pop($this->stack); + break; + + /* A start tag whose tag name is "isindex" */ + case 'isindex': + /* Parse error. */ + // w/e + + /* If the form element pointer is not null, + then ignore the token. */ + if ($this->form_pointer === null) { + /* Act as if a start tag token with the tag name "form" had + been seen. */ + $this->inBody( + array( + 'name' => 'body', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + /* Act as if a start tag token with the tag name "hr" had + been seen. */ + $this->inBody( + array( + 'name' => 'hr', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + /* Act as if a start tag token with the tag name "p" had + been seen. */ + $this->inBody( + array( + 'name' => 'p', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + /* Act as if a start tag token with the tag name "label" + had been seen. */ + $this->inBody( + array( + 'name' => 'label', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + /* Act as if a stream of character tokens had been seen. */ + $this->insertText( + 'This is a searchable index. ' . + 'Insert your search keywords here: ' + ); + + /* Act as if a start tag token with the tag name "input" + had been seen, with all the attributes from the "isindex" + token, except with the "name" attribute set to the value + "isindex" (ignoring any explicit "name" attribute). */ + $attr = $token['attr']; + $attr[] = array('name' => 'name', 'value' => 'isindex'); + + $this->inBody( + array( + 'name' => 'input', + 'type' => HTML5::STARTTAG, + 'attr' => $attr + ) + ); + + /* Act as if a stream of character tokens had been seen + (see below for what they should say). */ + $this->insertText( + 'This is a searchable index. ' . + 'Insert your search keywords here: ' + ); + + /* Act as if an end tag token with the tag name "label" + had been seen. */ + $this->inBody( + array( + 'name' => 'label', + 'type' => HTML5::ENDTAG + ) + ); + + /* Act as if an end tag token with the tag name "p" had + been seen. */ + $this->inBody( + array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + ) + ); + + /* Act as if a start tag token with the tag name "hr" had + been seen. */ + $this->inBody( + array( + 'name' => 'hr', + 'type' => HTML5::ENDTAG + ) + ); + + /* Act as if an end tag token with the tag name "form" had + been seen. */ + $this->inBody( + array( + 'name' => 'form', + 'type' => HTML5::ENDTAG + ) + ); + } + break; + + /* A start tag whose tag name is "textarea" */ + case 'textarea': + $this->insertElement($token); + + /* Switch the tokeniser's content model flag to the + RCDATA state. */ + return HTML5::RCDATA; + break; + + /* A start tag whose tag name is one of: "iframe", "noembed", + "noframes" */ + case 'iframe': + case 'noembed': + case 'noframes': + $this->insertElement($token); + + /* Switch the tokeniser's content model flag to the CDATA state. */ + return HTML5::CDATA; + break; + + /* A start tag whose tag name is "select" */ + case 'select': + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Change the insertion mode to "in select". */ + $this->mode = self::IN_SELECT; + break; + + /* A start or end tag whose tag name is one of: "caption", "col", + "colgroup", "frame", "frameset", "head", "option", "optgroup", + "tbody", "td", "tfoot", "th", "thead", "tr". */ + case 'caption': + case 'col': + case 'colgroup': + case 'frame': + case 'frameset': + case 'head': + case 'option': + case 'optgroup': + case 'tbody': + case 'td': + case 'tfoot': + case 'th': + case 'thead': + case 'tr': + // Parse error. Ignore the token. + break; + + /* A start or end tag whose tag name is one of: "event-source", + "section", "nav", "article", "aside", "header", "footer", + "datagrid", "command" */ + case 'event-source': + case 'section': + case 'nav': + case 'article': + case 'aside': + case 'header': + case 'footer': + case 'datagrid': + case 'command': + // Work in progress! + break; + + /* A start tag token not covered by the previous entries */ + default: + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + $this->insertElement($token, true, true); + break; + } + break; + + case HTML5::ENDTAG: + switch ($token['name']) { + /* An end tag with the tag name "body" */ + case 'body': + /* If the second element in the stack of open elements is + not a body element, this is a parse error. Ignore the token. + (innerHTML case) */ + if (count($this->stack) < 2 || $this->stack[1]->nodeName !== 'body') { + // Ignore. + + /* If the current node is not the body element, then this + is a parse error. */ + } elseif (end($this->stack)->nodeName !== 'body') { + // Parse error. + } + + /* Change the insertion mode to "after body". */ + $this->mode = self::AFTER_BODY; + break; + + /* An end tag with the tag name "html" */ + case 'html': + /* Act as if an end tag with tag name "body" had been seen, + then, if that token wasn't ignored, reprocess the current + token. */ + $this->inBody( + array( + 'name' => 'body', + 'type' => HTML5::ENDTAG + ) + ); + + return $this->afterBody($token); + break; + + /* An end tag whose tag name is one of: "address", "blockquote", + "center", "dir", "div", "dl", "fieldset", "listing", "menu", + "ol", "pre", "ul" */ + case 'address': + case 'blockquote': + case 'center': + case 'dir': + case 'div': + case 'dl': + case 'fieldset': + case 'listing': + case 'menu': + case 'ol': + case 'pre': + case 'ul': + /* If the stack of open elements has an element in scope + with the same tag name as that of the token, then generate + implied end tags. */ + if ($this->elementInScope($token['name'])) { + $this->generateImpliedEndTags(); + + /* Now, if the current node is not an element with + the same tag name as that of the token, then this + is a parse error. */ + // w/e + + /* If the stack of open elements has an element in + scope with the same tag name as that of the token, + then pop elements from this stack until an element + with that tag name has been popped from the stack. */ + for ($n = count($this->stack) - 1; $n >= 0; $n--) { + if ($this->stack[$n]->nodeName === $token['name']) { + $n = -1; + } + + array_pop($this->stack); + } + } + break; + + /* An end tag whose tag name is "form" */ + case 'form': + /* If the stack of open elements has an element in scope + with the same tag name as that of the token, then generate + implied end tags. */ + if ($this->elementInScope($token['name'])) { + $this->generateImpliedEndTags(); + + } + + if (end($this->stack)->nodeName !== $token['name']) { + /* Now, if the current node is not an element with the + same tag name as that of the token, then this is a parse + error. */ + // w/e + + } else { + /* Otherwise, if the current node is an element with + the same tag name as that of the token pop that element + from the stack. */ + array_pop($this->stack); + } + + /* In any case, set the form element pointer to null. */ + $this->form_pointer = null; + break; + + /* An end tag whose tag name is "p" */ + case 'p': + /* If the stack of open elements has a p element in scope, + then generate implied end tags, except for p elements. */ + if ($this->elementInScope('p')) { + $this->generateImpliedEndTags(array('p')); + + /* If the current node is not a p element, then this is + a parse error. */ + // k + + /* If the stack of open elements has a p element in + scope, then pop elements from this stack until the stack + no longer has a p element in scope. */ + for ($n = count($this->stack) - 1; $n >= 0; $n--) { + if ($this->elementInScope('p')) { + array_pop($this->stack); + + } else { + break; + } + } + } + break; + + /* An end tag whose tag name is "dd", "dt", or "li" */ + case 'dd': + case 'dt': + case 'li': + /* If the stack of open elements has an element in scope + whose tag name matches the tag name of the token, then + generate implied end tags, except for elements with the + same tag name as the token. */ + if ($this->elementInScope($token['name'])) { + $this->generateImpliedEndTags(array($token['name'])); + + /* If the current node is not an element with the same + tag name as the token, then this is a parse error. */ + // w/e + + /* If the stack of open elements has an element in scope + whose tag name matches the tag name of the token, then + pop elements from this stack until an element with that + tag name has been popped from the stack. */ + for ($n = count($this->stack) - 1; $n >= 0; $n--) { + if ($this->stack[$n]->nodeName === $token['name']) { + $n = -1; + } + + array_pop($this->stack); + } + } + break; + + /* An end tag whose tag name is one of: "h1", "h2", "h3", "h4", + "h5", "h6" */ + case 'h1': + case 'h2': + case 'h3': + case 'h4': + case 'h5': + case 'h6': + $elements = array('h1', 'h2', 'h3', 'h4', 'h5', 'h6'); + + /* If the stack of open elements has in scope an element whose + tag name is one of "h1", "h2", "h3", "h4", "h5", or "h6", then + generate implied end tags. */ + if ($this->elementInScope($elements)) { + $this->generateImpliedEndTags(); + + /* Now, if the current node is not an element with the same + tag name as that of the token, then this is a parse error. */ + // w/e + + /* If the stack of open elements has in scope an element + whose tag name is one of "h1", "h2", "h3", "h4", "h5", or + "h6", then pop elements from the stack until an element + with one of those tag names has been popped from the stack. */ + while ($this->elementInScope($elements)) { + array_pop($this->stack); + } + } + break; + + /* An end tag whose tag name is one of: "a", "b", "big", "em", + "font", "i", "nobr", "s", "small", "strike", "strong", "tt", "u" */ + case 'a': + case 'b': + case 'big': + case 'em': + case 'font': + case 'i': + case 'nobr': + case 's': + case 'small': + case 'strike': + case 'strong': + case 'tt': + case 'u': + /* 1. Let the formatting element be the last element in + the list of active formatting elements that: + * is between the end of the list and the last scope + marker in the list, if any, or the start of the list + otherwise, and + * has the same tag name as the token. + */ + while (true) { + for ($a = count($this->a_formatting) - 1; $a >= 0; $a--) { + if ($this->a_formatting[$a] === self::MARKER) { + break; + + } elseif ($this->a_formatting[$a]->tagName === $token['name']) { + $formatting_element = $this->a_formatting[$a]; + $in_stack = in_array($formatting_element, $this->stack, true); + $fe_af_pos = $a; + break; + } + } + + /* If there is no such node, or, if that node is + also in the stack of open elements but the element + is not in scope, then this is a parse error. Abort + these steps. The token is ignored. */ + if (!isset($formatting_element) || ($in_stack && + !$this->elementInScope($token['name'])) + ) { + break; + + /* Otherwise, if there is such a node, but that node + is not in the stack of open elements, then this is a + parse error; remove the element from the list, and + abort these steps. */ + } elseif (isset($formatting_element) && !$in_stack) { + unset($this->a_formatting[$fe_af_pos]); + $this->a_formatting = array_merge($this->a_formatting); + break; + } + + /* 2. Let the furthest block be the topmost node in the + stack of open elements that is lower in the stack + than the formatting element, and is not an element in + the phrasing or formatting categories. There might + not be one. */ + $fe_s_pos = array_search($formatting_element, $this->stack, true); + $length = count($this->stack); + + for ($s = $fe_s_pos + 1; $s < $length; $s++) { + $category = $this->getElementCategory($this->stack[$s]->nodeName); + + if ($category !== self::PHRASING && $category !== self::FORMATTING) { + $furthest_block = $this->stack[$s]; + } + } + + /* 3. If there is no furthest block, then the UA must + skip the subsequent steps and instead just pop all + the nodes from the bottom of the stack of open + elements, from the current node up to the formatting + element, and remove the formatting element from the + list of active formatting elements. */ + if (!isset($furthest_block)) { + for ($n = $length - 1; $n >= $fe_s_pos; $n--) { + array_pop($this->stack); + } + + unset($this->a_formatting[$fe_af_pos]); + $this->a_formatting = array_merge($this->a_formatting); + break; + } + + /* 4. Let the common ancestor be the element + immediately above the formatting element in the stack + of open elements. */ + $common_ancestor = $this->stack[$fe_s_pos - 1]; + + /* 5. If the furthest block has a parent node, then + remove the furthest block from its parent node. */ + if ($furthest_block->parentNode !== null) { + $furthest_block->parentNode->removeChild($furthest_block); + } + + /* 6. Let a bookmark note the position of the + formatting element in the list of active formatting + elements relative to the elements on either side + of it in the list. */ + $bookmark = $fe_af_pos; + + /* 7. Let node and last node be the furthest block. + Follow these steps: */ + $node = $furthest_block; + $last_node = $furthest_block; + + while (true) { + for ($n = array_search($node, $this->stack, true) - 1; $n >= 0; $n--) { + /* 7.1 Let node be the element immediately + prior to node in the stack of open elements. */ + $node = $this->stack[$n]; + + /* 7.2 If node is not in the list of active + formatting elements, then remove node from + the stack of open elements and then go back + to step 1. */ + if (!in_array($node, $this->a_formatting, true)) { + unset($this->stack[$n]); + $this->stack = array_merge($this->stack); + + } else { + break; + } + } + + /* 7.3 Otherwise, if node is the formatting + element, then go to the next step in the overall + algorithm. */ + if ($node === $formatting_element) { + break; + + /* 7.4 Otherwise, if last node is the furthest + block, then move the aforementioned bookmark to + be immediately after the node in the list of + active formatting elements. */ + } elseif ($last_node === $furthest_block) { + $bookmark = array_search($node, $this->a_formatting, true) + 1; + } + + /* 7.5 If node has any children, perform a + shallow clone of node, replace the entry for + node in the list of active formatting elements + with an entry for the clone, replace the entry + for node in the stack of open elements with an + entry for the clone, and let node be the clone. */ + if ($node->hasChildNodes()) { + $clone = $node->cloneNode(); + $s_pos = array_search($node, $this->stack, true); + $a_pos = array_search($node, $this->a_formatting, true); + + $this->stack[$s_pos] = $clone; + $this->a_formatting[$a_pos] = $clone; + $node = $clone; + } + + /* 7.6 Insert last node into node, first removing + it from its previous parent node if any. */ + if ($last_node->parentNode !== null) { + $last_node->parentNode->removeChild($last_node); + } + + $node->appendChild($last_node); + + /* 7.7 Let last node be node. */ + $last_node = $node; + } + + /* 8. Insert whatever last node ended up being in + the previous step into the common ancestor node, + first removing it from its previous parent node if + any. */ + if ($last_node->parentNode !== null) { + $last_node->parentNode->removeChild($last_node); + } + + $common_ancestor->appendChild($last_node); + + /* 9. Perform a shallow clone of the formatting + element. */ + $clone = $formatting_element->cloneNode(); + + /* 10. Take all of the child nodes of the furthest + block and append them to the clone created in the + last step. */ + while ($furthest_block->hasChildNodes()) { + $child = $furthest_block->firstChild; + $furthest_block->removeChild($child); + $clone->appendChild($child); + } + + /* 11. Append that clone to the furthest block. */ + $furthest_block->appendChild($clone); + + /* 12. Remove the formatting element from the list + of active formatting elements, and insert the clone + into the list of active formatting elements at the + position of the aforementioned bookmark. */ + $fe_af_pos = array_search($formatting_element, $this->a_formatting, true); + unset($this->a_formatting[$fe_af_pos]); + $this->a_formatting = array_merge($this->a_formatting); + + $af_part1 = array_slice($this->a_formatting, 0, $bookmark - 1); + $af_part2 = array_slice($this->a_formatting, $bookmark, count($this->a_formatting)); + $this->a_formatting = array_merge($af_part1, array($clone), $af_part2); + + /* 13. Remove the formatting element from the stack + of open elements, and insert the clone into the stack + of open elements immediately after (i.e. in a more + deeply nested position than) the position of the + furthest block in that stack. */ + $fe_s_pos = array_search($formatting_element, $this->stack, true); + $fb_s_pos = array_search($furthest_block, $this->stack, true); + unset($this->stack[$fe_s_pos]); + + $s_part1 = array_slice($this->stack, 0, $fb_s_pos); + $s_part2 = array_slice($this->stack, $fb_s_pos + 1, count($this->stack)); + $this->stack = array_merge($s_part1, array($clone), $s_part2); + + /* 14. Jump back to step 1 in this series of steps. */ + unset($formatting_element, $fe_af_pos, $fe_s_pos, $furthest_block); + } + break; + + /* An end tag token whose tag name is one of: "button", + "marquee", "object" */ + case 'button': + case 'marquee': + case 'object': + /* If the stack of open elements has an element in scope whose + tag name matches the tag name of the token, then generate implied + tags. */ + if ($this->elementInScope($token['name'])) { + $this->generateImpliedEndTags(); + + /* Now, if the current node is not an element with the same + tag name as the token, then this is a parse error. */ + // k + + /* Now, if the stack of open elements has an element in scope + whose tag name matches the tag name of the token, then pop + elements from the stack until that element has been popped from + the stack, and clear the list of active formatting elements up + to the last marker. */ + for ($n = count($this->stack) - 1; $n >= 0; $n--) { + if ($this->stack[$n]->nodeName === $token['name']) { + $n = -1; + } + + array_pop($this->stack); + } + + $marker = end(array_keys($this->a_formatting, self::MARKER, true)); + + for ($n = count($this->a_formatting) - 1; $n > $marker; $n--) { + array_pop($this->a_formatting); + } + } + break; + + /* Or an end tag whose tag name is one of: "area", "basefont", + "bgsound", "br", "embed", "hr", "iframe", "image", "img", + "input", "isindex", "noembed", "noframes", "param", "select", + "spacer", "table", "textarea", "wbr" */ + case 'area': + case 'basefont': + case 'bgsound': + case 'br': + case 'embed': + case 'hr': + case 'iframe': + case 'image': + case 'img': + case 'input': + case 'isindex': + case 'noembed': + case 'noframes': + case 'param': + case 'select': + case 'spacer': + case 'table': + case 'textarea': + case 'wbr': + // Parse error. Ignore the token. + break; + + /* An end tag token not covered by the previous entries */ + default: + for ($n = count($this->stack) - 1; $n >= 0; $n--) { + /* Initialise node to be the current node (the bottommost + node of the stack). */ + $node = end($this->stack); + + /* If node has the same tag name as the end tag token, + then: */ + if ($token['name'] === $node->nodeName) { + /* Generate implied end tags. */ + $this->generateImpliedEndTags(); + + /* If the tag name of the end tag token does not + match the tag name of the current node, this is a + parse error. */ + // k + + /* Pop all the nodes from the current node up to + node, including node, then stop this algorithm. */ + for ($x = count($this->stack) - $n; $x >= $n; $x--) { + array_pop($this->stack); + } + + } else { + $category = $this->getElementCategory($node); + + if ($category !== self::SPECIAL && $category !== self::SCOPING) { + /* Otherwise, if node is in neither the formatting + category nor the phrasing category, then this is a + parse error. Stop this algorithm. The end tag token + is ignored. */ + return false; + } + } + } + break; + } + break; + } + } + + private function inTable($token) + { + $clear = array('html', 'table'); + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + if ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Append the character to the current node. */ + $text = $this->dom->createTextNode($token['data']); + end($this->stack)->appendChild($text); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data + attribute set to the data given in the comment token. */ + $comment = $this->dom->createComment($token['data']); + end($this->stack)->appendChild($comment); + + /* A start tag whose tag name is "caption" */ + } elseif ($token['type'] === HTML5::STARTTAG && + $token['name'] === 'caption' + ) { + /* Clear the stack back to a table context. */ + $this->clearStackToTableContext($clear); + + /* Insert a marker at the end of the list of active + formatting elements. */ + $this->a_formatting[] = self::MARKER; + + /* Insert an HTML element for the token, then switch the + insertion mode to "in caption". */ + $this->insertElement($token); + $this->mode = self::IN_CAPTION; + + /* A start tag whose tag name is "colgroup" */ + } elseif ($token['type'] === HTML5::STARTTAG && + $token['name'] === 'colgroup' + ) { + /* Clear the stack back to a table context. */ + $this->clearStackToTableContext($clear); + + /* Insert an HTML element for the token, then switch the + insertion mode to "in column group". */ + $this->insertElement($token); + $this->mode = self::IN_CGROUP; + + /* A start tag whose tag name is "col" */ + } elseif ($token['type'] === HTML5::STARTTAG && + $token['name'] === 'col' + ) { + $this->inTable( + array( + 'name' => 'colgroup', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + $this->inColumnGroup($token); + + /* A start tag whose tag name is one of: "tbody", "tfoot", "thead" */ + } elseif ($token['type'] === HTML5::STARTTAG && in_array( + $token['name'], + array('tbody', 'tfoot', 'thead') + ) + ) { + /* Clear the stack back to a table context. */ + $this->clearStackToTableContext($clear); + + /* Insert an HTML element for the token, then switch the insertion + mode to "in table body". */ + $this->insertElement($token); + $this->mode = self::IN_TBODY; + + /* A start tag whose tag name is one of: "td", "th", "tr" */ + } elseif ($token['type'] === HTML5::STARTTAG && + in_array($token['name'], array('td', 'th', 'tr')) + ) { + /* Act as if a start tag token with the tag name "tbody" had been + seen, then reprocess the current token. */ + $this->inTable( + array( + 'name' => 'tbody', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + return $this->inTableBody($token); + + /* A start tag whose tag name is "table" */ + } elseif ($token['type'] === HTML5::STARTTAG && + $token['name'] === 'table' + ) { + /* Parse error. Act as if an end tag token with the tag name "table" + had been seen, then, if that token wasn't ignored, reprocess the + current token. */ + $this->inTable( + array( + 'name' => 'table', + 'type' => HTML5::ENDTAG + ) + ); + + return $this->mainPhase($token); + + /* An end tag whose tag name is "table" */ + } elseif ($token['type'] === HTML5::ENDTAG && + $token['name'] === 'table' + ) { + /* If the stack of open elements does not have an element in table + scope with the same tag name as the token, this is a parse error. + Ignore the token. (innerHTML case) */ + if (!$this->elementInScope($token['name'], true)) { + return false; + + /* Otherwise: */ + } else { + /* Generate implied end tags. */ + $this->generateImpliedEndTags(); + + /* Now, if the current node is not a table element, then this + is a parse error. */ + // w/e + + /* Pop elements from this stack until a table element has been + popped from the stack. */ + while (true) { + $current = end($this->stack)->nodeName; + array_pop($this->stack); + + if ($current === 'table') { + break; + } + } + + /* Reset the insertion mode appropriately. */ + $this->resetInsertionMode(); + } + + /* An end tag whose tag name is one of: "body", "caption", "col", + "colgroup", "html", "tbody", "td", "tfoot", "th", "thead", "tr" */ + } elseif ($token['type'] === HTML5::ENDTAG && in_array( + $token['name'], + array( + 'body', + 'caption', + 'col', + 'colgroup', + 'html', + 'tbody', + 'td', + 'tfoot', + 'th', + 'thead', + 'tr' + ) + ) + ) { + // Parse error. Ignore the token. + + /* Anything else */ + } else { + /* Parse error. Process the token as if the insertion mode was "in + body", with the following exception: */ + + /* If the current node is a table, tbody, tfoot, thead, or tr + element, then, whenever a node would be inserted into the current + node, it must instead be inserted into the foster parent element. */ + if (in_array( + end($this->stack)->nodeName, + array('table', 'tbody', 'tfoot', 'thead', 'tr') + ) + ) { + /* The foster parent element is the parent element of the last + table element in the stack of open elements, if there is a + table element and it has such a parent element. If there is no + table element in the stack of open elements (innerHTML case), + then the foster parent element is the first element in the + stack of open elements (the html element). Otherwise, if there + is a table element in the stack of open elements, but the last + table element in the stack of open elements has no parent, or + its parent node is not an element, then the foster parent + element is the element before the last table element in the + stack of open elements. */ + for ($n = count($this->stack) - 1; $n >= 0; $n--) { + if ($this->stack[$n]->nodeName === 'table') { + $table = $this->stack[$n]; + break; + } + } + + if (isset($table) && $table->parentNode !== null) { + $this->foster_parent = $table->parentNode; + + } elseif (!isset($table)) { + $this->foster_parent = $this->stack[0]; + + } elseif (isset($table) && ($table->parentNode === null || + $table->parentNode->nodeType !== XML_ELEMENT_NODE) + ) { + $this->foster_parent = $this->stack[$n - 1]; + } + } + + $this->inBody($token); + } + } + + private function inCaption($token) + { + /* An end tag whose tag name is "caption" */ + if ($token['type'] === HTML5::ENDTAG && $token['name'] === 'caption') { + /* If the stack of open elements does not have an element in table + scope with the same tag name as the token, this is a parse error. + Ignore the token. (innerHTML case) */ + if (!$this->elementInScope($token['name'], true)) { + // Ignore + + /* Otherwise: */ + } else { + /* Generate implied end tags. */ + $this->generateImpliedEndTags(); + + /* Now, if the current node is not a caption element, then this + is a parse error. */ + // w/e + + /* Pop elements from this stack until a caption element has + been popped from the stack. */ + while (true) { + $node = end($this->stack)->nodeName; + array_pop($this->stack); + + if ($node === 'caption') { + break; + } + } + + /* Clear the list of active formatting elements up to the last + marker. */ + $this->clearTheActiveFormattingElementsUpToTheLastMarker(); + + /* Switch the insertion mode to "in table". */ + $this->mode = self::IN_TABLE; + } + + /* A start tag whose tag name is one of: "caption", "col", "colgroup", + "tbody", "td", "tfoot", "th", "thead", "tr", or an end tag whose tag + name is "table" */ + } elseif (($token['type'] === HTML5::STARTTAG && in_array( + $token['name'], + array( + 'caption', + 'col', + 'colgroup', + 'tbody', + 'td', + 'tfoot', + 'th', + 'thead', + 'tr' + ) + )) || ($token['type'] === HTML5::ENDTAG && + $token['name'] === 'table') + ) { + /* Parse error. Act as if an end tag with the tag name "caption" + had been seen, then, if that token wasn't ignored, reprocess the + current token. */ + $this->inCaption( + array( + 'name' => 'caption', + 'type' => HTML5::ENDTAG + ) + ); + + return $this->inTable($token); + + /* An end tag whose tag name is one of: "body", "col", "colgroup", + "html", "tbody", "td", "tfoot", "th", "thead", "tr" */ + } elseif ($token['type'] === HTML5::ENDTAG && in_array( + $token['name'], + array( + 'body', + 'col', + 'colgroup', + 'html', + 'tbody', + 'tfoot', + 'th', + 'thead', + 'tr' + ) + ) + ) { + // Parse error. Ignore the token. + + /* Anything else */ + } else { + /* Process the token as if the insertion mode was "in body". */ + $this->inBody($token); + } + } + + private function inColumnGroup($token) + { + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + if ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Append the character to the current node. */ + $text = $this->dom->createTextNode($token['data']); + end($this->stack)->appendChild($text); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data + attribute set to the data given in the comment token. */ + $comment = $this->dom->createComment($token['data']); + end($this->stack)->appendChild($comment); + + /* A start tag whose tag name is "col" */ + } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'col') { + /* Insert a col element for the token. Immediately pop the current + node off the stack of open elements. */ + $this->insertElement($token); + array_pop($this->stack); + + /* An end tag whose tag name is "colgroup" */ + } elseif ($token['type'] === HTML5::ENDTAG && + $token['name'] === 'colgroup' + ) { + /* If the current node is the root html element, then this is a + parse error, ignore the token. (innerHTML case) */ + if (end($this->stack)->nodeName === 'html') { + // Ignore + + /* Otherwise, pop the current node (which will be a colgroup + element) from the stack of open elements. Switch the insertion + mode to "in table". */ + } else { + array_pop($this->stack); + $this->mode = self::IN_TABLE; + } + + /* An end tag whose tag name is "col" */ + } elseif ($token['type'] === HTML5::ENDTAG && $token['name'] === 'col') { + /* Parse error. Ignore the token. */ + + /* Anything else */ + } else { + /* Act as if an end tag with the tag name "colgroup" had been seen, + and then, if that token wasn't ignored, reprocess the current token. */ + $this->inColumnGroup( + array( + 'name' => 'colgroup', + 'type' => HTML5::ENDTAG + ) + ); + + return $this->inTable($token); + } + } + + private function inTableBody($token) + { + $clear = array('tbody', 'tfoot', 'thead', 'html'); + + /* A start tag whose tag name is "tr" */ + if ($token['type'] === HTML5::STARTTAG && $token['name'] === 'tr') { + /* Clear the stack back to a table body context. */ + $this->clearStackToTableContext($clear); + + /* Insert a tr element for the token, then switch the insertion + mode to "in row". */ + $this->insertElement($token); + $this->mode = self::IN_ROW; + + /* A start tag whose tag name is one of: "th", "td" */ + } elseif ($token['type'] === HTML5::STARTTAG && + ($token['name'] === 'th' || $token['name'] === 'td') + ) { + /* Parse error. Act as if a start tag with the tag name "tr" had + been seen, then reprocess the current token. */ + $this->inTableBody( + array( + 'name' => 'tr', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + return $this->inRow($token); + + /* An end tag whose tag name is one of: "tbody", "tfoot", "thead" */ + } elseif ($token['type'] === HTML5::ENDTAG && + in_array($token['name'], array('tbody', 'tfoot', 'thead')) + ) { + /* If the stack of open elements does not have an element in table + scope with the same tag name as the token, this is a parse error. + Ignore the token. */ + if (!$this->elementInScope($token['name'], true)) { + // Ignore + + /* Otherwise: */ + } else { + /* Clear the stack back to a table body context. */ + $this->clearStackToTableContext($clear); + + /* Pop the current node from the stack of open elements. Switch + the insertion mode to "in table". */ + array_pop($this->stack); + $this->mode = self::IN_TABLE; + } + + /* A start tag whose tag name is one of: "caption", "col", "colgroup", + "tbody", "tfoot", "thead", or an end tag whose tag name is "table" */ + } elseif (($token['type'] === HTML5::STARTTAG && in_array( + $token['name'], + array('caption', 'col', 'colgroup', 'tbody', 'tfoor', 'thead') + )) || + ($token['type'] === HTML5::STARTTAG && $token['name'] === 'table') + ) { + /* If the stack of open elements does not have a tbody, thead, or + tfoot element in table scope, this is a parse error. Ignore the + token. (innerHTML case) */ + if (!$this->elementInScope(array('tbody', 'thead', 'tfoot'), true)) { + // Ignore. + + /* Otherwise: */ + } else { + /* Clear the stack back to a table body context. */ + $this->clearStackToTableContext($clear); + + /* Act as if an end tag with the same tag name as the current + node ("tbody", "tfoot", or "thead") had been seen, then + reprocess the current token. */ + $this->inTableBody( + array( + 'name' => end($this->stack)->nodeName, + 'type' => HTML5::ENDTAG + ) + ); + + return $this->mainPhase($token); + } + + /* An end tag whose tag name is one of: "body", "caption", "col", + "colgroup", "html", "td", "th", "tr" */ + } elseif ($token['type'] === HTML5::ENDTAG && in_array( + $token['name'], + array('body', 'caption', 'col', 'colgroup', 'html', 'td', 'th', 'tr') + ) + ) { + /* Parse error. Ignore the token. */ + + /* Anything else */ + } else { + /* Process the token as if the insertion mode was "in table". */ + $this->inTable($token); + } + } + + private function inRow($token) + { + $clear = array('tr', 'html'); + + /* A start tag whose tag name is one of: "th", "td" */ + if ($token['type'] === HTML5::STARTTAG && + ($token['name'] === 'th' || $token['name'] === 'td') + ) { + /* Clear the stack back to a table row context. */ + $this->clearStackToTableContext($clear); + + /* Insert an HTML element for the token, then switch the insertion + mode to "in cell". */ + $this->insertElement($token); + $this->mode = self::IN_CELL; + + /* Insert a marker at the end of the list of active formatting + elements. */ + $this->a_formatting[] = self::MARKER; + + /* An end tag whose tag name is "tr" */ + } elseif ($token['type'] === HTML5::ENDTAG && $token['name'] === 'tr') { + /* If the stack of open elements does not have an element in table + scope with the same tag name as the token, this is a parse error. + Ignore the token. (innerHTML case) */ + if (!$this->elementInScope($token['name'], true)) { + // Ignore. + + /* Otherwise: */ + } else { + /* Clear the stack back to a table row context. */ + $this->clearStackToTableContext($clear); + + /* Pop the current node (which will be a tr element) from the + stack of open elements. Switch the insertion mode to "in table + body". */ + array_pop($this->stack); + $this->mode = self::IN_TBODY; + } + + /* A start tag whose tag name is one of: "caption", "col", "colgroup", + "tbody", "tfoot", "thead", "tr" or an end tag whose tag name is "table" */ + } elseif ($token['type'] === HTML5::STARTTAG && in_array( + $token['name'], + array('caption', 'col', 'colgroup', 'tbody', 'tfoot', 'thead', 'tr') + ) + ) { + /* Act as if an end tag with the tag name "tr" had been seen, then, + if that token wasn't ignored, reprocess the current token. */ + $this->inRow( + array( + 'name' => 'tr', + 'type' => HTML5::ENDTAG + ) + ); + + return $this->inCell($token); + + /* An end tag whose tag name is one of: "tbody", "tfoot", "thead" */ + } elseif ($token['type'] === HTML5::ENDTAG && + in_array($token['name'], array('tbody', 'tfoot', 'thead')) + ) { + /* If the stack of open elements does not have an element in table + scope with the same tag name as the token, this is a parse error. + Ignore the token. */ + if (!$this->elementInScope($token['name'], true)) { + // Ignore. + + /* Otherwise: */ + } else { + /* Otherwise, act as if an end tag with the tag name "tr" had + been seen, then reprocess the current token. */ + $this->inRow( + array( + 'name' => 'tr', + 'type' => HTML5::ENDTAG + ) + ); + + return $this->inCell($token); + } + + /* An end tag whose tag name is one of: "body", "caption", "col", + "colgroup", "html", "td", "th" */ + } elseif ($token['type'] === HTML5::ENDTAG && in_array( + $token['name'], + array('body', 'caption', 'col', 'colgroup', 'html', 'td', 'th', 'tr') + ) + ) { + /* Parse error. Ignore the token. */ + + /* Anything else */ + } else { + /* Process the token as if the insertion mode was "in table". */ + $this->inTable($token); + } + } + + private function inCell($token) + { + /* An end tag whose tag name is one of: "td", "th" */ + if ($token['type'] === HTML5::ENDTAG && + ($token['name'] === 'td' || $token['name'] === 'th') + ) { + /* If the stack of open elements does not have an element in table + scope with the same tag name as that of the token, then this is a + parse error and the token must be ignored. */ + if (!$this->elementInScope($token['name'], true)) { + // Ignore. + + /* Otherwise: */ + } else { + /* Generate implied end tags, except for elements with the same + tag name as the token. */ + $this->generateImpliedEndTags(array($token['name'])); + + /* Now, if the current node is not an element with the same tag + name as the token, then this is a parse error. */ + // k + + /* Pop elements from this stack until an element with the same + tag name as the token has been popped from the stack. */ + while (true) { + $node = end($this->stack)->nodeName; + array_pop($this->stack); + + if ($node === $token['name']) { + break; + } + } + + /* Clear the list of active formatting elements up to the last + marker. */ + $this->clearTheActiveFormattingElementsUpToTheLastMarker(); + + /* Switch the insertion mode to "in row". (The current node + will be a tr element at this point.) */ + $this->mode = self::IN_ROW; + } + + /* A start tag whose tag name is one of: "caption", "col", "colgroup", + "tbody", "td", "tfoot", "th", "thead", "tr" */ + } elseif ($token['type'] === HTML5::STARTTAG && in_array( + $token['name'], + array( + 'caption', + 'col', + 'colgroup', + 'tbody', + 'td', + 'tfoot', + 'th', + 'thead', + 'tr' + ) + ) + ) { + /* If the stack of open elements does not have a td or th element + in table scope, then this is a parse error; ignore the token. + (innerHTML case) */ + if (!$this->elementInScope(array('td', 'th'), true)) { + // Ignore. + + /* Otherwise, close the cell (see below) and reprocess the current + token. */ + } else { + $this->closeCell(); + return $this->inRow($token); + } + + /* A start tag whose tag name is one of: "caption", "col", "colgroup", + "tbody", "td", "tfoot", "th", "thead", "tr" */ + } elseif ($token['type'] === HTML5::STARTTAG && in_array( + $token['name'], + array( + 'caption', + 'col', + 'colgroup', + 'tbody', + 'td', + 'tfoot', + 'th', + 'thead', + 'tr' + ) + ) + ) { + /* If the stack of open elements does not have a td or th element + in table scope, then this is a parse error; ignore the token. + (innerHTML case) */ + if (!$this->elementInScope(array('td', 'th'), true)) { + // Ignore. + + /* Otherwise, close the cell (see below) and reprocess the current + token. */ + } else { + $this->closeCell(); + return $this->inRow($token); + } + + /* An end tag whose tag name is one of: "body", "caption", "col", + "colgroup", "html" */ + } elseif ($token['type'] === HTML5::ENDTAG && in_array( + $token['name'], + array('body', 'caption', 'col', 'colgroup', 'html') + ) + ) { + /* Parse error. Ignore the token. */ + + /* An end tag whose tag name is one of: "table", "tbody", "tfoot", + "thead", "tr" */ + } elseif ($token['type'] === HTML5::ENDTAG && in_array( + $token['name'], + array('table', 'tbody', 'tfoot', 'thead', 'tr') + ) + ) { + /* If the stack of open elements does not have an element in table + scope with the same tag name as that of the token (which can only + happen for "tbody", "tfoot" and "thead", or, in the innerHTML case), + then this is a parse error and the token must be ignored. */ + if (!$this->elementInScope($token['name'], true)) { + // Ignore. + + /* Otherwise, close the cell (see below) and reprocess the current + token. */ + } else { + $this->closeCell(); + return $this->inRow($token); + } + + /* Anything else */ + } else { + /* Process the token as if the insertion mode was "in body". */ + $this->inBody($token); + } + } + + private function inSelect($token) + { + /* Handle the token as follows: */ + + /* A character token */ + if ($token['type'] === HTML5::CHARACTR) { + /* Append the token's character to the current node. */ + $this->insertText($token['data']); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data + attribute set to the data given in the comment token. */ + $this->insertComment($token['data']); + + /* A start tag token whose tag name is "option" */ + } elseif ($token['type'] === HTML5::STARTTAG && + $token['name'] === 'option' + ) { + /* If the current node is an option element, act as if an end tag + with the tag name "option" had been seen. */ + if (end($this->stack)->nodeName === 'option') { + $this->inSelect( + array( + 'name' => 'option', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* A start tag token whose tag name is "optgroup" */ + } elseif ($token['type'] === HTML5::STARTTAG && + $token['name'] === 'optgroup' + ) { + /* If the current node is an option element, act as if an end tag + with the tag name "option" had been seen. */ + if (end($this->stack)->nodeName === 'option') { + $this->inSelect( + array( + 'name' => 'option', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* If the current node is an optgroup element, act as if an end tag + with the tag name "optgroup" had been seen. */ + if (end($this->stack)->nodeName === 'optgroup') { + $this->inSelect( + array( + 'name' => 'optgroup', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* An end tag token whose tag name is "optgroup" */ + } elseif ($token['type'] === HTML5::ENDTAG && + $token['name'] === 'optgroup' + ) { + /* First, if the current node is an option element, and the node + immediately before it in the stack of open elements is an optgroup + element, then act as if an end tag with the tag name "option" had + been seen. */ + $elements_in_stack = count($this->stack); + + if ($this->stack[$elements_in_stack - 1]->nodeName === 'option' && + $this->stack[$elements_in_stack - 2]->nodeName === 'optgroup' + ) { + $this->inSelect( + array( + 'name' => 'option', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* If the current node is an optgroup element, then pop that node + from the stack of open elements. Otherwise, this is a parse error, + ignore the token. */ + if ($this->stack[$elements_in_stack - 1] === 'optgroup') { + array_pop($this->stack); + } + + /* An end tag token whose tag name is "option" */ + } elseif ($token['type'] === HTML5::ENDTAG && + $token['name'] === 'option' + ) { + /* If the current node is an option element, then pop that node + from the stack of open elements. Otherwise, this is a parse error, + ignore the token. */ + if (end($this->stack)->nodeName === 'option') { + array_pop($this->stack); + } + + /* An end tag whose tag name is "select" */ + } elseif ($token['type'] === HTML5::ENDTAG && + $token['name'] === 'select' + ) { + /* If the stack of open elements does not have an element in table + scope with the same tag name as the token, this is a parse error. + Ignore the token. (innerHTML case) */ + if (!$this->elementInScope($token['name'], true)) { + // w/e + + /* Otherwise: */ + } else { + /* Pop elements from the stack of open elements until a select + element has been popped from the stack. */ + while (true) { + $current = end($this->stack)->nodeName; + array_pop($this->stack); + + if ($current === 'select') { + break; + } + } + + /* Reset the insertion mode appropriately. */ + $this->resetInsertionMode(); + } + + /* A start tag whose tag name is "select" */ + } elseif ($token['name'] === 'select' && + $token['type'] === HTML5::STARTTAG + ) { + /* Parse error. Act as if the token had been an end tag with the + tag name "select" instead. */ + $this->inSelect( + array( + 'name' => 'select', + 'type' => HTML5::ENDTAG + ) + ); + + /* An end tag whose tag name is one of: "caption", "table", "tbody", + "tfoot", "thead", "tr", "td", "th" */ + } elseif (in_array( + $token['name'], + array( + 'caption', + 'table', + 'tbody', + 'tfoot', + 'thead', + 'tr', + 'td', + 'th' + ) + ) && $token['type'] === HTML5::ENDTAG + ) { + /* Parse error. */ + // w/e + + /* If the stack of open elements has an element in table scope with + the same tag name as that of the token, then act as if an end tag + with the tag name "select" had been seen, and reprocess the token. + Otherwise, ignore the token. */ + if ($this->elementInScope($token['name'], true)) { + $this->inSelect( + array( + 'name' => 'select', + 'type' => HTML5::ENDTAG + ) + ); + + $this->mainPhase($token); + } + + /* Anything else */ + } else { + /* Parse error. Ignore the token. */ + } + } + + private function afterBody($token) + { + /* Handle the token as follows: */ + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + if ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Process the token as it would be processed if the insertion mode + was "in body". */ + $this->inBody($token); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the first element in the stack of open + elements (the html element), with the data attribute set to the + data given in the comment token. */ + $comment = $this->dom->createComment($token['data']); + $this->stack[0]->appendChild($comment); + + /* An end tag with the tag name "html" */ + } elseif ($token['type'] === HTML5::ENDTAG && $token['name'] === 'html') { + /* If the parser was originally created in order to handle the + setting of an element's innerHTML attribute, this is a parse error; + ignore the token. (The element will be an html element in this + case.) (innerHTML case) */ + + /* Otherwise, switch to the trailing end phase. */ + $this->phase = self::END_PHASE; + + /* Anything else */ + } else { + /* Parse error. Set the insertion mode to "in body" and reprocess + the token. */ + $this->mode = self::IN_BODY; + return $this->inBody($token); + } + } + + private function inFrameset($token) + { + /* Handle the token as follows: */ + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + U+000D CARRIAGE RETURN (CR), or U+0020 SPACE */ + if ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Append the character to the current node. */ + $this->insertText($token['data']); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data + attribute set to the data given in the comment token. */ + $this->insertComment($token['data']); + + /* A start tag with the tag name "frameset" */ + } elseif ($token['name'] === 'frameset' && + $token['type'] === HTML5::STARTTAG + ) { + $this->insertElement($token); + + /* An end tag with the tag name "frameset" */ + } elseif ($token['name'] === 'frameset' && + $token['type'] === HTML5::ENDTAG + ) { + /* If the current node is the root html element, then this is a + parse error; ignore the token. (innerHTML case) */ + if (end($this->stack)->nodeName === 'html') { + // Ignore + + } else { + /* Otherwise, pop the current node from the stack of open + elements. */ + array_pop($this->stack); + + /* If the parser was not originally created in order to handle + the setting of an element's innerHTML attribute (innerHTML case), + and the current node is no longer a frameset element, then change + the insertion mode to "after frameset". */ + $this->mode = self::AFTR_FRAME; + } + + /* A start tag with the tag name "frame" */ + } elseif ($token['name'] === 'frame' && + $token['type'] === HTML5::STARTTAG + ) { + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Immediately pop the current node off the stack of open elements. */ + array_pop($this->stack); + + /* A start tag with the tag name "noframes" */ + } elseif ($token['name'] === 'noframes' && + $token['type'] === HTML5::STARTTAG + ) { + /* Process the token as if the insertion mode had been "in body". */ + $this->inBody($token); + + /* Anything else */ + } else { + /* Parse error. Ignore the token. */ + } + } + + private function afterFrameset($token) + { + /* Handle the token as follows: */ + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + U+000D CARRIAGE RETURN (CR), or U+0020 SPACE */ + if ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Append the character to the current node. */ + $this->insertText($token['data']); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data + attribute set to the data given in the comment token. */ + $this->insertComment($token['data']); + + /* An end tag with the tag name "html" */ + } elseif ($token['name'] === 'html' && + $token['type'] === HTML5::ENDTAG + ) { + /* Switch to the trailing end phase. */ + $this->phase = self::END_PHASE; + + /* A start tag with the tag name "noframes" */ + } elseif ($token['name'] === 'noframes' && + $token['type'] === HTML5::STARTTAG + ) { + /* Process the token as if the insertion mode had been "in body". */ + $this->inBody($token); + + /* Anything else */ + } else { + /* Parse error. Ignore the token. */ + } + } + + private function trailingEndPhase($token) + { + /* After the main phase, as each token is emitted from the tokenisation + stage, it must be processed as described in this section. */ + + /* A DOCTYPE token */ + if ($token['type'] === HTML5::DOCTYPE) { + // Parse error. Ignore the token. + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the Document object with the data + attribute set to the data given in the comment token. */ + $comment = $this->dom->createComment($token['data']); + $this->dom->appendChild($comment); + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + } elseif ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Process the token as it would be processed in the main phase. */ + $this->mainPhase($token); + + /* A character token that is not one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE. Or a start tag token. Or an end tag token. */ + } elseif (($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) || + $token['type'] === HTML5::STARTTAG || $token['type'] === HTML5::ENDTAG + ) { + /* Parse error. Switch back to the main phase and reprocess the + token. */ + $this->phase = self::MAIN_PHASE; + return $this->mainPhase($token); + + /* An end-of-file token */ + } elseif ($token['type'] === HTML5::EOF) { + /* OMG DONE!! */ + } + } + + private function insertElement($token, $append = true, $check = false) + { + // Proprietary workaround for libxml2's limitations with tag names + if ($check) { + // Slightly modified HTML5 tag-name modification, + // removing anything that's not an ASCII letter, digit, or hyphen + $token['name'] = preg_replace('/[^a-z0-9-]/i', '', $token['name']); + // Remove leading hyphens and numbers + $token['name'] = ltrim($token['name'], '-0..9'); + // In theory, this should ever be needed, but just in case + if ($token['name'] === '') { + $token['name'] = 'span'; + } // arbitrary generic choice + } + + $el = $this->dom->createElement($token['name']); + + foreach ($token['attr'] as $attr) { + if (!$el->hasAttribute($attr['name'])) { + $el->setAttribute($attr['name'], $attr['value']); + } + } + + $this->appendToRealParent($el); + $this->stack[] = $el; + + return $el; + } + + private function insertText($data) + { + $text = $this->dom->createTextNode($data); + $this->appendToRealParent($text); + } + + private function insertComment($data) + { + $comment = $this->dom->createComment($data); + $this->appendToRealParent($comment); + } + + private function appendToRealParent($node) + { + if ($this->foster_parent === null) { + end($this->stack)->appendChild($node); + + } elseif ($this->foster_parent !== null) { + /* If the foster parent element is the parent element of the + last table element in the stack of open elements, then the new + node must be inserted immediately before the last table element + in the stack of open elements in the foster parent element; + otherwise, the new node must be appended to the foster parent + element. */ + for ($n = count($this->stack) - 1; $n >= 0; $n--) { + if ($this->stack[$n]->nodeName === 'table' && + $this->stack[$n]->parentNode !== null + ) { + $table = $this->stack[$n]; + break; + } + } + + if (isset($table) && $this->foster_parent->isSameNode($table->parentNode)) { + $this->foster_parent->insertBefore($node, $table); + } else { + $this->foster_parent->appendChild($node); + } + + $this->foster_parent = null; + } + } + + private function elementInScope($el, $table = false) + { + if (is_array($el)) { + foreach ($el as $element) { + if ($this->elementInScope($element, $table)) { + return true; + } + } + + return false; + } + + $leng = count($this->stack); + + for ($n = 0; $n < $leng; $n++) { + /* 1. Initialise node to be the current node (the bottommost node of + the stack). */ + $node = $this->stack[$leng - 1 - $n]; + + if ($node->tagName === $el) { + /* 2. If node is the target node, terminate in a match state. */ + return true; + + } elseif ($node->tagName === 'table') { + /* 3. Otherwise, if node is a table element, terminate in a failure + state. */ + return false; + + } elseif ($table === true && in_array( + $node->tagName, + array( + 'caption', + 'td', + 'th', + 'button', + 'marquee', + 'object' + ) + ) + ) { + /* 4. Otherwise, if the algorithm is the "has an element in scope" + variant (rather than the "has an element in table scope" variant), + and node is one of the following, terminate in a failure state. */ + return false; + + } elseif ($node === $node->ownerDocument->documentElement) { + /* 5. Otherwise, if node is an html element (root element), terminate + in a failure state. (This can only happen if the node is the topmost + node of the stack of open elements, and prevents the next step from + being invoked if there are no more elements in the stack.) */ + return false; + } + + /* Otherwise, set node to the previous entry in the stack of open + elements and return to step 2. (This will never fail, since the loop + will always terminate in the previous step if the top of the stack + is reached.) */ + } + } + + private function reconstructActiveFormattingElements() + { + /* 1. If there are no entries in the list of active formatting elements, + then there is nothing to reconstruct; stop this algorithm. */ + $formatting_elements = count($this->a_formatting); + + if ($formatting_elements === 0) { + return false; + } + + /* 3. Let entry be the last (most recently added) element in the list + of active formatting elements. */ + $entry = end($this->a_formatting); + + /* 2. If the last (most recently added) entry in the list of active + formatting elements is a marker, or if it is an element that is in the + stack of open elements, then there is nothing to reconstruct; stop this + algorithm. */ + if ($entry === self::MARKER || in_array($entry, $this->stack, true)) { + return false; + } + + for ($a = $formatting_elements - 1; $a >= 0; true) { + /* 4. If there are no entries before entry in the list of active + formatting elements, then jump to step 8. */ + if ($a === 0) { + $step_seven = false; + break; + } + + /* 5. Let entry be the entry one earlier than entry in the list of + active formatting elements. */ + $a--; + $entry = $this->a_formatting[$a]; + + /* 6. If entry is neither a marker nor an element that is also in + thetack of open elements, go to step 4. */ + if ($entry === self::MARKER || in_array($entry, $this->stack, true)) { + break; + } + } + + while (true) { + /* 7. Let entry be the element one later than entry in the list of + active formatting elements. */ + if (isset($step_seven) && $step_seven === true) { + $a++; + $entry = $this->a_formatting[$a]; + } + + /* 8. Perform a shallow clone of the element entry to obtain clone. */ + $clone = $entry->cloneNode(); + + /* 9. Append clone to the current node and push it onto the stack + of open elements so that it is the new current node. */ + end($this->stack)->appendChild($clone); + $this->stack[] = $clone; + + /* 10. Replace the entry for entry in the list with an entry for + clone. */ + $this->a_formatting[$a] = $clone; + + /* 11. If the entry for clone in the list of active formatting + elements is not the last entry in the list, return to step 7. */ + if (end($this->a_formatting) !== $clone) { + $step_seven = true; + } else { + break; + } + } + } + + private function clearTheActiveFormattingElementsUpToTheLastMarker() + { + /* When the steps below require the UA to clear the list of active + formatting elements up to the last marker, the UA must perform the + following steps: */ + + while (true) { + /* 1. Let entry be the last (most recently added) entry in the list + of active formatting elements. */ + $entry = end($this->a_formatting); + + /* 2. Remove entry from the list of active formatting elements. */ + array_pop($this->a_formatting); + + /* 3. If entry was a marker, then stop the algorithm at this point. + The list has been cleared up to the last marker. */ + if ($entry === self::MARKER) { + break; + } + } + } + + private function generateImpliedEndTags($exclude = array()) + { + /* When the steps below require the UA to generate implied end tags, + then, if the current node is a dd element, a dt element, an li element, + a p element, a td element, a th element, or a tr element, the UA must + act as if an end tag with the respective tag name had been seen and + then generate implied end tags again. */ + $node = end($this->stack); + $elements = array_diff(array('dd', 'dt', 'li', 'p', 'td', 'th', 'tr'), $exclude); + + while (in_array(end($this->stack)->nodeName, $elements)) { + array_pop($this->stack); + } + } + + private function getElementCategory($node) + { + $name = $node->tagName; + if (in_array($name, $this->special)) { + return self::SPECIAL; + } elseif (in_array($name, $this->scoping)) { + return self::SCOPING; + } elseif (in_array($name, $this->formatting)) { + return self::FORMATTING; + } else { + return self::PHRASING; + } + } + + private function clearStackToTableContext($elements) + { + /* When the steps above require the UA to clear the stack back to a + table context, it means that the UA must, while the current node is not + a table element or an html element, pop elements from the stack of open + elements. If this causes any elements to be popped from the stack, then + this is a parse error. */ + while (true) { + $node = end($this->stack)->nodeName; + + if (in_array($node, $elements)) { + break; + } else { + array_pop($this->stack); + } + } + } + + private function resetInsertionMode() + { + /* 1. Let last be false. */ + $last = false; + $leng = count($this->stack); + + for ($n = $leng - 1; $n >= 0; $n--) { + /* 2. Let node be the last node in the stack of open elements. */ + $node = $this->stack[$n]; + + /* 3. If node is the first node in the stack of open elements, then + set last to true. If the element whose innerHTML attribute is being + set is neither a td element nor a th element, then set node to the + element whose innerHTML attribute is being set. (innerHTML case) */ + if ($this->stack[0]->isSameNode($node)) { + $last = true; + } + + /* 4. If node is a select element, then switch the insertion mode to + "in select" and abort these steps. (innerHTML case) */ + if ($node->nodeName === 'select') { + $this->mode = self::IN_SELECT; + break; + + /* 5. If node is a td or th element, then switch the insertion mode + to "in cell" and abort these steps. */ + } elseif ($node->nodeName === 'td' || $node->nodeName === 'th') { + $this->mode = self::IN_CELL; + break; + + /* 6. If node is a tr element, then switch the insertion mode to + "in row" and abort these steps. */ + } elseif ($node->nodeName === 'tr') { + $this->mode = self::IN_ROW; + break; + + /* 7. If node is a tbody, thead, or tfoot element, then switch the + insertion mode to "in table body" and abort these steps. */ + } elseif (in_array($node->nodeName, array('tbody', 'thead', 'tfoot'))) { + $this->mode = self::IN_TBODY; + break; + + /* 8. If node is a caption element, then switch the insertion mode + to "in caption" and abort these steps. */ + } elseif ($node->nodeName === 'caption') { + $this->mode = self::IN_CAPTION; + break; + + /* 9. If node is a colgroup element, then switch the insertion mode + to "in column group" and abort these steps. (innerHTML case) */ + } elseif ($node->nodeName === 'colgroup') { + $this->mode = self::IN_CGROUP; + break; + + /* 10. If node is a table element, then switch the insertion mode + to "in table" and abort these steps. */ + } elseif ($node->nodeName === 'table') { + $this->mode = self::IN_TABLE; + break; + + /* 11. If node is a head element, then switch the insertion mode + to "in body" ("in body"! not "in head"!) and abort these steps. + (innerHTML case) */ + } elseif ($node->nodeName === 'head') { + $this->mode = self::IN_BODY; + break; + + /* 12. If node is a body element, then switch the insertion mode to + "in body" and abort these steps. */ + } elseif ($node->nodeName === 'body') { + $this->mode = self::IN_BODY; + break; + + /* 13. If node is a frameset element, then switch the insertion + mode to "in frameset" and abort these steps. (innerHTML case) */ + } elseif ($node->nodeName === 'frameset') { + $this->mode = self::IN_FRAME; + break; + + /* 14. If node is an html element, then: if the head element + pointer is null, switch the insertion mode to "before head", + otherwise, switch the insertion mode to "after head". In either + case, abort these steps. (innerHTML case) */ + } elseif ($node->nodeName === 'html') { + $this->mode = ($this->head_pointer === null) + ? self::BEFOR_HEAD + : self::AFTER_HEAD; + + break; + + /* 15. If last is true, then set the insertion mode to "in body" + and abort these steps. (innerHTML case) */ + } elseif ($last) { + $this->mode = self::IN_BODY; + break; + } + } + } + + private function closeCell() + { + /* If the stack of open elements has a td or th element in table scope, + then act as if an end tag token with that tag name had been seen. */ + foreach (array('td', 'th') as $cell) { + if ($this->elementInScope($cell, true)) { + $this->inCell( + array( + 'name' => $cell, + 'type' => HTML5::ENDTAG + ) + ); + + break; + } + } + } + + public function save() + { + return $this->dom; + } +} diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node.php new file mode 100644 index 0000000..3995fec --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node.php @@ -0,0 +1,49 @@ +data = $data; + $this->line = $line; + $this->col = $col; + } + + public function toTokenPair() { + return array(new HTMLPurifier_Token_Comment($this->data, $this->line, $this->col), null); + } +} diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node/Element.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node/Element.php new file mode 100644 index 0000000..6cbf56d --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node/Element.php @@ -0,0 +1,59 @@ + form or the form, i.e. + * is it a pair of start/end tokens or an empty token. + * @bool + */ + public $empty = false; + + public $endCol = null, $endLine = null, $endArmor = array(); + + public function __construct($name, $attr = array(), $line = null, $col = null, $armor = array()) { + $this->name = $name; + $this->attr = $attr; + $this->line = $line; + $this->col = $col; + $this->armor = $armor; + } + + public function toTokenPair() { + // XXX inefficiency here, normalization is not necessary + if ($this->empty) { + return array(new HTMLPurifier_Token_Empty($this->name, $this->attr, $this->line, $this->col, $this->armor), null); + } else { + $start = new HTMLPurifier_Token_Start($this->name, $this->attr, $this->line, $this->col, $this->armor); + $end = new HTMLPurifier_Token_End($this->name, array(), $this->endLine, $this->endCol, $this->endArmor); + //$end->start = $start; + return array($start, $end); + } + } +} + diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node/Text.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node/Text.php new file mode 100644 index 0000000..aec9166 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node/Text.php @@ -0,0 +1,54 @@ +data = $data; + $this->is_whitespace = $is_whitespace; + $this->line = $line; + $this->col = $col; + } + + public function toTokenPair() { + return array(new HTMLPurifier_Token_Text($this->data, $this->line, $this->col), null); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/PercentEncoder.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/PercentEncoder.php new file mode 100644 index 0000000..18c8bbb --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/PercentEncoder.php @@ -0,0 +1,111 @@ +preserve[$i] = true; + } + for ($i = 65; $i <= 90; $i++) { // upper-case + $this->preserve[$i] = true; + } + for ($i = 97; $i <= 122; $i++) { // lower-case + $this->preserve[$i] = true; + } + $this->preserve[45] = true; // Dash - + $this->preserve[46] = true; // Period . + $this->preserve[95] = true; // Underscore _ + $this->preserve[126]= true; // Tilde ~ + + // extra letters not to escape + if ($preserve !== false) { + for ($i = 0, $c = strlen($preserve); $i < $c; $i++) { + $this->preserve[ord($preserve[$i])] = true; + } + } + } + + /** + * Our replacement for urlencode, it encodes all non-reserved characters, + * as well as any extra characters that were instructed to be preserved. + * @note + * Assumes that the string has already been normalized, making any + * and all percent escape sequences valid. Percents will not be + * re-escaped, regardless of their status in $preserve + * @param string $string String to be encoded + * @return string Encoded string. + */ + public function encode($string) + { + $ret = ''; + for ($i = 0, $c = strlen($string); $i < $c; $i++) { + if ($string[$i] !== '%' && !isset($this->preserve[$int = ord($string[$i])])) { + $ret .= '%' . sprintf('%02X', $int); + } else { + $ret .= $string[$i]; + } + } + return $ret; + } + + /** + * Fix up percent-encoding by decoding unreserved characters and normalizing. + * @warning This function is affected by $preserve, even though the + * usual desired behavior is for this not to preserve those + * characters. Be careful when reusing instances of PercentEncoder! + * @param string $string String to normalize + * @return string + */ + public function normalize($string) + { + if ($string == '') { + return ''; + } + $parts = explode('%', $string); + $ret = array_shift($parts); + foreach ($parts as $part) { + $length = strlen($part); + if ($length < 2) { + $ret .= '%25' . $part; + continue; + } + $encoding = substr($part, 0, 2); + $text = substr($part, 2); + if (!ctype_xdigit($encoding)) { + $ret .= '%25' . $part; + continue; + } + $int = hexdec($encoding); + if (isset($this->preserve[$int])) { + $ret .= chr($int) . $text; + continue; + } + $encoding = strtoupper($encoding); + $ret .= '%' . $encoding . $text; + } + return $ret; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer.php new file mode 100644 index 0000000..549e4ce --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer.php @@ -0,0 +1,218 @@ +getAll(); + $context = new HTMLPurifier_Context(); + $this->generator = new HTMLPurifier_Generator($config, $context); + } + + /** + * Main function that renders object or aspect of that object + * @note Parameters vary depending on printer + */ + // function render() {} + + /** + * Returns a start tag + * @param string $tag Tag name + * @param array $attr Attribute array + * @return string + */ + protected function start($tag, $attr = array()) + { + return $this->generator->generateFromToken( + new HTMLPurifier_Token_Start($tag, $attr ? $attr : array()) + ); + } + + /** + * Returns an end tag + * @param string $tag Tag name + * @return string + */ + protected function end($tag) + { + return $this->generator->generateFromToken( + new HTMLPurifier_Token_End($tag) + ); + } + + /** + * Prints a complete element with content inside + * @param string $tag Tag name + * @param string $contents Element contents + * @param array $attr Tag attributes + * @param bool $escape whether or not to escape contents + * @return string + */ + protected function element($tag, $contents, $attr = array(), $escape = true) + { + return $this->start($tag, $attr) . + ($escape ? $this->escape($contents) : $contents) . + $this->end($tag); + } + + /** + * @param string $tag + * @param array $attr + * @return string + */ + protected function elementEmpty($tag, $attr = array()) + { + return $this->generator->generateFromToken( + new HTMLPurifier_Token_Empty($tag, $attr) + ); + } + + /** + * @param string $text + * @return string + */ + protected function text($text) + { + return $this->generator->generateFromToken( + new HTMLPurifier_Token_Text($text) + ); + } + + /** + * Prints a simple key/value row in a table. + * @param string $name Key + * @param mixed $value Value + * @return string + */ + protected function row($name, $value) + { + if (is_bool($value)) { + $value = $value ? 'On' : 'Off'; + } + return + $this->start('tr') . "\n" . + $this->element('th', $name) . "\n" . + $this->element('td', $value) . "\n" . + $this->end('tr'); + } + + /** + * Escapes a string for HTML output. + * @param string $string String to escape + * @return string + */ + protected function escape($string) + { + $string = HTMLPurifier_Encoder::cleanUTF8($string); + $string = htmlspecialchars($string, ENT_COMPAT, 'UTF-8'); + return $string; + } + + /** + * Takes a list of strings and turns them into a single list + * @param string[] $array List of strings + * @param bool $polite Bool whether or not to add an end before the last + * @return string + */ + protected function listify($array, $polite = false) + { + if (empty($array)) { + return 'None'; + } + $ret = ''; + $i = count($array); + foreach ($array as $value) { + $i--; + $ret .= $value; + if ($i > 0 && !($polite && $i == 1)) { + $ret .= ', '; + } + if ($polite && $i == 1) { + $ret .= 'and '; + } + } + return $ret; + } + + /** + * Retrieves the class of an object without prefixes, as well as metadata + * @param object $obj Object to determine class of + * @param string $sec_prefix Further prefix to remove + * @return string + */ + protected function getClass($obj, $sec_prefix = '') + { + static $five = null; + if ($five === null) { + $five = version_compare(PHP_VERSION, '5', '>='); + } + $prefix = 'HTMLPurifier_' . $sec_prefix; + if (!$five) { + $prefix = strtolower($prefix); + } + $class = str_replace($prefix, '', get_class($obj)); + $lclass = strtolower($class); + $class .= '('; + switch ($lclass) { + case 'enum': + $values = array(); + foreach ($obj->valid_values as $value => $bool) { + $values[] = $value; + } + $class .= implode(', ', $values); + break; + case 'css_composite': + $values = array(); + foreach ($obj->defs as $def) { + $values[] = $this->getClass($def, $sec_prefix); + } + $class .= implode(', ', $values); + break; + case 'css_multiple': + $class .= $this->getClass($obj->single, $sec_prefix) . ', '; + $class .= $obj->max; + break; + case 'css_denyelementdecorator': + $class .= $this->getClass($obj->def, $sec_prefix) . ', '; + $class .= $obj->element; + break; + case 'css_importantdecorator': + $class .= $this->getClass($obj->def, $sec_prefix); + if ($obj->allow) { + $class .= ', !important'; + } + break; + } + $class .= ')'; + return $class; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/CSSDefinition.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/CSSDefinition.php new file mode 100644 index 0000000..29505fe --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/CSSDefinition.php @@ -0,0 +1,44 @@ +def = $config->getCSSDefinition(); + $ret = ''; + + $ret .= $this->start('div', array('class' => 'HTMLPurifier_Printer')); + $ret .= $this->start('table'); + + $ret .= $this->element('caption', 'Properties ($info)'); + + $ret .= $this->start('thead'); + $ret .= $this->start('tr'); + $ret .= $this->element('th', 'Property', array('class' => 'heavy')); + $ret .= $this->element('th', 'Definition', array('class' => 'heavy', 'style' => 'width:auto;')); + $ret .= $this->end('tr'); + $ret .= $this->end('thead'); + + ksort($this->def->info); + foreach ($this->def->info as $property => $obj) { + $name = $this->getClass($obj, 'AttrDef_'); + $ret .= $this->row($property, $name); + } + + $ret .= $this->end('table'); + $ret .= $this->end('div'); + + return $ret; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.css b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.css new file mode 100644 index 0000000..3ff1a88 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.css @@ -0,0 +1,10 @@ + +.hp-config {} + +.hp-config tbody th {text-align:right; padding-right:0.5em;} +.hp-config thead, .hp-config .namespace {background:#3C578C; color:#FFF;} +.hp-config .namespace th {text-align:center;} +.hp-config .verbose {display:none;} +.hp-config .controls {text-align:center;} + +/* vim: et sw=4 sts=4 */ diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.js b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.js new file mode 100644 index 0000000..cba00c9 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.js @@ -0,0 +1,5 @@ +function toggleWriteability(id_of_patient, checked) { + document.getElementById(id_of_patient).disabled = checked; +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.php new file mode 100644 index 0000000..33ae113 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.php @@ -0,0 +1,451 @@ +docURL = $doc_url; + $this->name = $name; + $this->compress = $compress; + // initialize sub-printers + $this->fields[0] = new HTMLPurifier_Printer_ConfigForm_default(); + $this->fields[HTMLPurifier_VarParser::C_BOOL] = new HTMLPurifier_Printer_ConfigForm_bool(); + } + + /** + * Sets default column and row size for textareas in sub-printers + * @param $cols Integer columns of textarea, null to use default + * @param $rows Integer rows of textarea, null to use default + */ + public function setTextareaDimensions($cols = null, $rows = null) + { + if ($cols) { + $this->fields['default']->cols = $cols; + } + if ($rows) { + $this->fields['default']->rows = $rows; + } + } + + /** + * Retrieves styling, in case it is not accessible by webserver + */ + public static function getCSS() + { + return file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/Printer/ConfigForm.css'); + } + + /** + * Retrieves JavaScript, in case it is not accessible by webserver + */ + public static function getJavaScript() + { + return file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/Printer/ConfigForm.js'); + } + + /** + * Returns HTML output for a configuration form + * @param HTMLPurifier_Config|array $config Configuration object of current form state, or an array + * where [0] has an HTML namespace and [1] is being rendered. + * @param array|bool $allowed Optional namespace(s) and directives to restrict form to. + * @param bool $render_controls + * @return string + */ + public function render($config, $allowed = true, $render_controls = true) + { + if (is_array($config) && isset($config[0])) { + $gen_config = $config[0]; + $config = $config[1]; + } else { + $gen_config = $config; + } + + $this->config = $config; + $this->genConfig = $gen_config; + $this->prepareGenerator($gen_config); + + $allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed, $config->def); + $all = array(); + foreach ($allowed as $key) { + list($ns, $directive) = $key; + $all[$ns][$directive] = $config->get($ns . '.' . $directive); + } + + $ret = ''; + $ret .= $this->start('table', array('class' => 'hp-config')); + $ret .= $this->start('thead'); + $ret .= $this->start('tr'); + $ret .= $this->element('th', 'Directive', array('class' => 'hp-directive')); + $ret .= $this->element('th', 'Value', array('class' => 'hp-value')); + $ret .= $this->end('tr'); + $ret .= $this->end('thead'); + foreach ($all as $ns => $directives) { + $ret .= $this->renderNamespace($ns, $directives); + } + if ($render_controls) { + $ret .= $this->start('tbody'); + $ret .= $this->start('tr'); + $ret .= $this->start('td', array('colspan' => 2, 'class' => 'controls')); + $ret .= $this->elementEmpty('input', array('type' => 'submit', 'value' => 'Submit')); + $ret .= '[Reset]'; + $ret .= $this->end('td'); + $ret .= $this->end('tr'); + $ret .= $this->end('tbody'); + } + $ret .= $this->end('table'); + return $ret; + } + + /** + * Renders a single namespace + * @param $ns String namespace name + * @param array $directives array of directives to values + * @return string + */ + protected function renderNamespace($ns, $directives) + { + $ret = ''; + $ret .= $this->start('tbody', array('class' => 'namespace')); + $ret .= $this->start('tr'); + $ret .= $this->element('th', $ns, array('colspan' => 2)); + $ret .= $this->end('tr'); + $ret .= $this->end('tbody'); + $ret .= $this->start('tbody'); + foreach ($directives as $directive => $value) { + $ret .= $this->start('tr'); + $ret .= $this->start('th'); + if ($this->docURL) { + $url = str_replace('%s', urlencode("$ns.$directive"), $this->docURL); + $ret .= $this->start('a', array('href' => $url)); + } + $attr = array('for' => "{$this->name}:$ns.$directive"); + + // crop directive name if it's too long + if (!$this->compress || (strlen($directive) < $this->compress)) { + $directive_disp = $directive; + } else { + $directive_disp = substr($directive, 0, $this->compress - 2) . '...'; + $attr['title'] = $directive; + } + + $ret .= $this->element( + 'label', + $directive_disp, + // component printers must create an element with this id + $attr + ); + if ($this->docURL) { + $ret .= $this->end('a'); + } + $ret .= $this->end('th'); + + $ret .= $this->start('td'); + $def = $this->config->def->info["$ns.$directive"]; + if (is_int($def)) { + $allow_null = $def < 0; + $type = abs($def); + } else { + $type = $def->type; + $allow_null = isset($def->allow_null); + } + if (!isset($this->fields[$type])) { + $type = 0; + } // default + $type_obj = $this->fields[$type]; + if ($allow_null) { + $type_obj = new HTMLPurifier_Printer_ConfigForm_NullDecorator($type_obj); + } + $ret .= $type_obj->render($ns, $directive, $value, $this->name, array($this->genConfig, $this->config)); + $ret .= $this->end('td'); + $ret .= $this->end('tr'); + } + $ret .= $this->end('tbody'); + return $ret; + } + +} + +/** + * Printer decorator for directives that accept null + */ +class HTMLPurifier_Printer_ConfigForm_NullDecorator extends HTMLPurifier_Printer +{ + /** + * Printer being decorated + * @type HTMLPurifier_Printer + */ + protected $obj; + + /** + * @param HTMLPurifier_Printer $obj Printer to decorate + */ + public function __construct($obj) + { + parent::__construct(); + $this->obj = $obj; + } + + /** + * @param string $ns + * @param string $directive + * @param string $value + * @param string $name + * @param HTMLPurifier_Config|array $config + * @return string + */ + public function render($ns, $directive, $value, $name, $config) + { + if (is_array($config) && isset($config[0])) { + $gen_config = $config[0]; + $config = $config[1]; + } else { + $gen_config = $config; + } + $this->prepareGenerator($gen_config); + + $ret = ''; + $ret .= $this->start('label', array('for' => "$name:Null_$ns.$directive")); + $ret .= $this->element('span', "$ns.$directive:", array('class' => 'verbose')); + $ret .= $this->text(' Null/Disabled'); + $ret .= $this->end('label'); + $attr = array( + 'type' => 'checkbox', + 'value' => '1', + 'class' => 'null-toggle', + 'name' => "$name" . "[Null_$ns.$directive]", + 'id' => "$name:Null_$ns.$directive", + 'onclick' => "toggleWriteability('$name:$ns.$directive',checked)" // INLINE JAVASCRIPT!!!! + ); + if ($this->obj instanceof HTMLPurifier_Printer_ConfigForm_bool) { + // modify inline javascript slightly + $attr['onclick'] = + "toggleWriteability('$name:Yes_$ns.$directive',checked);" . + "toggleWriteability('$name:No_$ns.$directive',checked)"; + } + if ($value === null) { + $attr['checked'] = 'checked'; + } + $ret .= $this->elementEmpty('input', $attr); + $ret .= $this->text(' or '); + $ret .= $this->elementEmpty('br'); + $ret .= $this->obj->render($ns, $directive, $value, $name, array($gen_config, $config)); + return $ret; + } +} + +/** + * Swiss-army knife configuration form field printer + */ +class HTMLPurifier_Printer_ConfigForm_default extends HTMLPurifier_Printer +{ + /** + * @type int + */ + public $cols = 18; + + /** + * @type int + */ + public $rows = 5; + + /** + * @param string $ns + * @param string $directive + * @param string $value + * @param string $name + * @param HTMLPurifier_Config|array $config + * @return string + */ + public function render($ns, $directive, $value, $name, $config) + { + if (is_array($config) && isset($config[0])) { + $gen_config = $config[0]; + $config = $config[1]; + } else { + $gen_config = $config; + } + $this->prepareGenerator($gen_config); + // this should probably be split up a little + $ret = ''; + $def = $config->def->info["$ns.$directive"]; + if (is_int($def)) { + $type = abs($def); + } else { + $type = $def->type; + } + if (is_array($value)) { + switch ($type) { + case HTMLPurifier_VarParser::LOOKUP: + $array = $value; + $value = array(); + foreach ($array as $val => $b) { + $value[] = $val; + } + //TODO does this need a break? + case HTMLPurifier_VarParser::ALIST: + $value = implode(PHP_EOL, $value); + break; + case HTMLPurifier_VarParser::HASH: + $nvalue = ''; + foreach ($value as $i => $v) { + if (is_array($v)) { + // HACK + $v = implode(";", $v); + } + $nvalue .= "$i:$v" . PHP_EOL; + } + $value = $nvalue; + break; + default: + $value = ''; + } + } + if ($type === HTMLPurifier_VarParser::C_MIXED) { + return 'Not supported'; + $value = serialize($value); + } + $attr = array( + 'name' => "$name" . "[$ns.$directive]", + 'id' => "$name:$ns.$directive" + ); + if ($value === null) { + $attr['disabled'] = 'disabled'; + } + if (isset($def->allowed)) { + $ret .= $this->start('select', $attr); + foreach ($def->allowed as $val => $b) { + $attr = array(); + if ($value == $val) { + $attr['selected'] = 'selected'; + } + $ret .= $this->element('option', $val, $attr); + } + $ret .= $this->end('select'); + } elseif ($type === HTMLPurifier_VarParser::TEXT || + $type === HTMLPurifier_VarParser::ITEXT || + $type === HTMLPurifier_VarParser::ALIST || + $type === HTMLPurifier_VarParser::HASH || + $type === HTMLPurifier_VarParser::LOOKUP) { + $attr['cols'] = $this->cols; + $attr['rows'] = $this->rows; + $ret .= $this->start('textarea', $attr); + $ret .= $this->text($value); + $ret .= $this->end('textarea'); + } else { + $attr['value'] = $value; + $attr['type'] = 'text'; + $ret .= $this->elementEmpty('input', $attr); + } + return $ret; + } +} + +/** + * Bool form field printer + */ +class HTMLPurifier_Printer_ConfigForm_bool extends HTMLPurifier_Printer +{ + /** + * @param string $ns + * @param string $directive + * @param string $value + * @param string $name + * @param HTMLPurifier_Config|array $config + * @return string + */ + public function render($ns, $directive, $value, $name, $config) + { + if (is_array($config) && isset($config[0])) { + $gen_config = $config[0]; + $config = $config[1]; + } else { + $gen_config = $config; + } + $this->prepareGenerator($gen_config); + $ret = ''; + $ret .= $this->start('div', array('id' => "$name:$ns.$directive")); + + $ret .= $this->start('label', array('for' => "$name:Yes_$ns.$directive")); + $ret .= $this->element('span', "$ns.$directive:", array('class' => 'verbose')); + $ret .= $this->text(' Yes'); + $ret .= $this->end('label'); + + $attr = array( + 'type' => 'radio', + 'name' => "$name" . "[$ns.$directive]", + 'id' => "$name:Yes_$ns.$directive", + 'value' => '1' + ); + if ($value === true) { + $attr['checked'] = 'checked'; + } + if ($value === null) { + $attr['disabled'] = 'disabled'; + } + $ret .= $this->elementEmpty('input', $attr); + + $ret .= $this->start('label', array('for' => "$name:No_$ns.$directive")); + $ret .= $this->element('span', "$ns.$directive:", array('class' => 'verbose')); + $ret .= $this->text(' No'); + $ret .= $this->end('label'); + + $attr = array( + 'type' => 'radio', + 'name' => "$name" . "[$ns.$directive]", + 'id' => "$name:No_$ns.$directive", + 'value' => '0' + ); + if ($value === false) { + $attr['checked'] = 'checked'; + } + if ($value === null) { + $attr['disabled'] = 'disabled'; + } + $ret .= $this->elementEmpty('input', $attr); + + $ret .= $this->end('div'); + + return $ret; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/HTMLDefinition.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/HTMLDefinition.php new file mode 100644 index 0000000..ae86391 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/HTMLDefinition.php @@ -0,0 +1,324 @@ +config =& $config; + + $this->def = $config->getHTMLDefinition(); + + $ret .= $this->start('div', array('class' => 'HTMLPurifier_Printer')); + + $ret .= $this->renderDoctype(); + $ret .= $this->renderEnvironment(); + $ret .= $this->renderContentSets(); + $ret .= $this->renderInfo(); + + $ret .= $this->end('div'); + + return $ret; + } + + /** + * Renders the Doctype table + * @return string + */ + protected function renderDoctype() + { + $doctype = $this->def->doctype; + $ret = ''; + $ret .= $this->start('table'); + $ret .= $this->element('caption', 'Doctype'); + $ret .= $this->row('Name', $doctype->name); + $ret .= $this->row('XML', $doctype->xml ? 'Yes' : 'No'); + $ret .= $this->row('Default Modules', implode(', ', $doctype->modules)); + $ret .= $this->row('Default Tidy Modules', implode(', ', $doctype->tidyModules)); + $ret .= $this->end('table'); + return $ret; + } + + + /** + * Renders environment table, which is miscellaneous info + * @return string + */ + protected function renderEnvironment() + { + $def = $this->def; + + $ret = ''; + + $ret .= $this->start('table'); + $ret .= $this->element('caption', 'Environment'); + + $ret .= $this->row('Parent of fragment', $def->info_parent); + $ret .= $this->renderChildren($def->info_parent_def->child); + $ret .= $this->row('Block wrap name', $def->info_block_wrapper); + + $ret .= $this->start('tr'); + $ret .= $this->element('th', 'Global attributes'); + $ret .= $this->element('td', $this->listifyAttr($def->info_global_attr), null, 0); + $ret .= $this->end('tr'); + + $ret .= $this->start('tr'); + $ret .= $this->element('th', 'Tag transforms'); + $list = array(); + foreach ($def->info_tag_transform as $old => $new) { + $new = $this->getClass($new, 'TagTransform_'); + $list[] = "<$old> with $new"; + } + $ret .= $this->element('td', $this->listify($list)); + $ret .= $this->end('tr'); + + $ret .= $this->start('tr'); + $ret .= $this->element('th', 'Pre-AttrTransform'); + $ret .= $this->element('td', $this->listifyObjectList($def->info_attr_transform_pre)); + $ret .= $this->end('tr'); + + $ret .= $this->start('tr'); + $ret .= $this->element('th', 'Post-AttrTransform'); + $ret .= $this->element('td', $this->listifyObjectList($def->info_attr_transform_post)); + $ret .= $this->end('tr'); + + $ret .= $this->end('table'); + return $ret; + } + + /** + * Renders the Content Sets table + * @return string + */ + protected function renderContentSets() + { + $ret = ''; + $ret .= $this->start('table'); + $ret .= $this->element('caption', 'Content Sets'); + foreach ($this->def->info_content_sets as $name => $lookup) { + $ret .= $this->heavyHeader($name); + $ret .= $this->start('tr'); + $ret .= $this->element('td', $this->listifyTagLookup($lookup)); + $ret .= $this->end('tr'); + } + $ret .= $this->end('table'); + return $ret; + } + + /** + * Renders the Elements ($info) table + * @return string + */ + protected function renderInfo() + { + $ret = ''; + $ret .= $this->start('table'); + $ret .= $this->element('caption', 'Elements ($info)'); + ksort($this->def->info); + $ret .= $this->heavyHeader('Allowed tags', 2); + $ret .= $this->start('tr'); + $ret .= $this->element('td', $this->listifyTagLookup($this->def->info), array('colspan' => 2)); + $ret .= $this->end('tr'); + foreach ($this->def->info as $name => $def) { + $ret .= $this->start('tr'); + $ret .= $this->element('th', "<$name>", array('class' => 'heavy', 'colspan' => 2)); + $ret .= $this->end('tr'); + $ret .= $this->start('tr'); + $ret .= $this->element('th', 'Inline content'); + $ret .= $this->element('td', $def->descendants_are_inline ? 'Yes' : 'No'); + $ret .= $this->end('tr'); + if (!empty($def->excludes)) { + $ret .= $this->start('tr'); + $ret .= $this->element('th', 'Excludes'); + $ret .= $this->element('td', $this->listifyTagLookup($def->excludes)); + $ret .= $this->end('tr'); + } + if (!empty($def->attr_transform_pre)) { + $ret .= $this->start('tr'); + $ret .= $this->element('th', 'Pre-AttrTransform'); + $ret .= $this->element('td', $this->listifyObjectList($def->attr_transform_pre)); + $ret .= $this->end('tr'); + } + if (!empty($def->attr_transform_post)) { + $ret .= $this->start('tr'); + $ret .= $this->element('th', 'Post-AttrTransform'); + $ret .= $this->element('td', $this->listifyObjectList($def->attr_transform_post)); + $ret .= $this->end('tr'); + } + if (!empty($def->auto_close)) { + $ret .= $this->start('tr'); + $ret .= $this->element('th', 'Auto closed by'); + $ret .= $this->element('td', $this->listifyTagLookup($def->auto_close)); + $ret .= $this->end('tr'); + } + $ret .= $this->start('tr'); + $ret .= $this->element('th', 'Allowed attributes'); + $ret .= $this->element('td', $this->listifyAttr($def->attr), array(), 0); + $ret .= $this->end('tr'); + + if (!empty($def->required_attr)) { + $ret .= $this->row('Required attributes', $this->listify($def->required_attr)); + } + + $ret .= $this->renderChildren($def->child); + } + $ret .= $this->end('table'); + return $ret; + } + + /** + * Renders a row describing the allowed children of an element + * @param HTMLPurifier_ChildDef $def HTMLPurifier_ChildDef of pertinent element + * @return string + */ + protected function renderChildren($def) + { + $context = new HTMLPurifier_Context(); + $ret = ''; + $ret .= $this->start('tr'); + $elements = array(); + $attr = array(); + if (isset($def->elements)) { + if ($def->type == 'strictblockquote') { + $def->validateChildren(array(), $this->config, $context); + } + $elements = $def->elements; + } + if ($def->type == 'chameleon') { + $attr['rowspan'] = 2; + } elseif ($def->type == 'empty') { + $elements = array(); + } elseif ($def->type == 'table') { + $elements = array_flip( + array( + 'col', + 'caption', + 'colgroup', + 'thead', + 'tfoot', + 'tbody', + 'tr' + ) + ); + } + $ret .= $this->element('th', 'Allowed children', $attr); + + if ($def->type == 'chameleon') { + + $ret .= $this->element( + 'td', + 'Block: ' . + $this->escape($this->listifyTagLookup($def->block->elements)), + null, + 0 + ); + $ret .= $this->end('tr'); + $ret .= $this->start('tr'); + $ret .= $this->element( + 'td', + 'Inline: ' . + $this->escape($this->listifyTagLookup($def->inline->elements)), + null, + 0 + ); + + } elseif ($def->type == 'custom') { + + $ret .= $this->element( + 'td', + '' . ucfirst($def->type) . ': ' . + $def->dtd_regex + ); + + } else { + $ret .= $this->element( + 'td', + '' . ucfirst($def->type) . ': ' . + $this->escape($this->listifyTagLookup($elements)), + null, + 0 + ); + } + $ret .= $this->end('tr'); + return $ret; + } + + /** + * Listifies a tag lookup table. + * @param array $array Tag lookup array in form of array('tagname' => true) + * @return string + */ + protected function listifyTagLookup($array) + { + ksort($array); + $list = array(); + foreach ($array as $name => $discard) { + if ($name !== '#PCDATA' && !isset($this->def->info[$name])) { + continue; + } + $list[] = $name; + } + return $this->listify($list); + } + + /** + * Listifies a list of objects by retrieving class names and internal state + * @param array $array List of objects + * @return string + * @todo Also add information about internal state + */ + protected function listifyObjectList($array) + { + ksort($array); + $list = array(); + foreach ($array as $obj) { + $list[] = $this->getClass($obj, 'AttrTransform_'); + } + return $this->listify($list); + } + + /** + * Listifies a hash of attributes to AttrDef classes + * @param array $array Array hash in form of array('attrname' => HTMLPurifier_AttrDef) + * @return string + */ + protected function listifyAttr($array) + { + ksort($array); + $list = array(); + foreach ($array as $name => $obj) { + if ($obj === false) { + continue; + } + $list[] = "$name = " . $this->getClass($obj, 'AttrDef_') . ''; + } + return $this->listify($list); + } + + /** + * Creates a heavy header row + * @param string $text + * @param int $num + * @return string + */ + protected function heavyHeader($text, $num = 1) + { + $ret = ''; + $ret .= $this->start('tr'); + $ret .= $this->element('th', $text, array('colspan' => $num, 'class' => 'heavy')); + $ret .= $this->end('tr'); + return $ret; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/PropertyList.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/PropertyList.php new file mode 100644 index 0000000..189348f --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/PropertyList.php @@ -0,0 +1,122 @@ +parent = $parent; + } + + /** + * Recursively retrieves the value for a key + * @param string $name + * @throws HTMLPurifier_Exception + */ + public function get($name) + { + if ($this->has($name)) { + return $this->data[$name]; + } + // possible performance bottleneck, convert to iterative if necessary + if ($this->parent) { + return $this->parent->get($name); + } + throw new HTMLPurifier_Exception("Key '$name' not found"); + } + + /** + * Sets the value of a key, for this plist + * @param string $name + * @param mixed $value + */ + public function set($name, $value) + { + $this->data[$name] = $value; + } + + /** + * Returns true if a given key exists + * @param string $name + * @return bool + */ + public function has($name) + { + return array_key_exists($name, $this->data); + } + + /** + * Resets a value to the value of it's parent, usually the default. If + * no value is specified, the entire plist is reset. + * @param string $name + */ + public function reset($name = null) + { + if ($name == null) { + $this->data = array(); + } else { + unset($this->data[$name]); + } + } + + /** + * Squashes this property list and all of its property lists into a single + * array, and returns the array. This value is cached by default. + * @param bool $force If true, ignores the cache and regenerates the array. + * @return array + */ + public function squash($force = false) + { + if ($this->cache !== null && !$force) { + return $this->cache; + } + if ($this->parent) { + return $this->cache = array_merge($this->parent->squash($force), $this->data); + } else { + return $this->cache = $this->data; + } + } + + /** + * Returns the parent plist. + * @return HTMLPurifier_PropertyList + */ + public function getParent() + { + return $this->parent; + } + + /** + * Sets the parent plist. + * @param HTMLPurifier_PropertyList $plist Parent plist + */ + public function setParent($plist) + { + $this->parent = $plist; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/PropertyListIterator.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/PropertyListIterator.php new file mode 100644 index 0000000..15b330e --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/PropertyListIterator.php @@ -0,0 +1,42 @@ +l = strlen($filter); + $this->filter = $filter; + } + + /** + * @return bool + */ + public function accept() + { + $key = $this->getInnerIterator()->key(); + if (strncmp($key, $this->filter, $this->l) !== 0) { + return false; + } + return true; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Queue.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Queue.php new file mode 100644 index 0000000..f58db90 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Queue.php @@ -0,0 +1,56 @@ +input = $input; + $this->output = array(); + } + + /** + * Shifts an element off the front of the queue. + */ + public function shift() { + if (empty($this->output)) { + $this->output = array_reverse($this->input); + $this->input = array(); + } + if (empty($this->output)) { + return NULL; + } + return array_pop($this->output); + } + + /** + * Pushes an element onto the front of the queue. + */ + public function push($x) { + array_push($this->input, $x); + } + + /** + * Checks if it's empty. + */ + public function isEmpty() { + return empty($this->input) && empty($this->output); + } +} diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy.php new file mode 100644 index 0000000..e1ff3b7 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy.php @@ -0,0 +1,26 @@ +strategies as $strategy) { + $tokens = $strategy->execute($tokens, $config, $context); + } + return $tokens; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/Core.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/Core.php new file mode 100644 index 0000000..4414c17 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/Core.php @@ -0,0 +1,17 @@ +strategies[] = new HTMLPurifier_Strategy_RemoveForeignElements(); + $this->strategies[] = new HTMLPurifier_Strategy_MakeWellFormed(); + $this->strategies[] = new HTMLPurifier_Strategy_FixNesting(); + $this->strategies[] = new HTMLPurifier_Strategy_ValidateAttributes(); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/FixNesting.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/FixNesting.php new file mode 100644 index 0000000..6fa673d --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/FixNesting.php @@ -0,0 +1,181 @@ +getHTMLDefinition(); + + $excludes_enabled = !$config->get('Core.DisableExcludes'); + + // setup the context variable 'IsInline', for chameleon processing + // is 'false' when we are not inline, 'true' when it must always + // be inline, and an integer when it is inline for a certain + // branch of the document tree + $is_inline = $definition->info_parent_def->descendants_are_inline; + $context->register('IsInline', $is_inline); + + // setup error collector + $e =& $context->get('ErrorCollector', true); + + //####################################################################// + // Loop initialization + + // stack that contains all elements that are excluded + // it is organized by parent elements, similar to $stack, + // but it is only populated when an element with exclusions is + // processed, i.e. there won't be empty exclusions. + $exclude_stack = array($definition->info_parent_def->excludes); + + // variable that contains the start token while we are processing + // nodes. This enables error reporting to do its job + $node = $top_node; + // dummy token + list($token, $d) = $node->toTokenPair(); + $context->register('CurrentNode', $node); + $context->register('CurrentToken', $token); + + //####################################################################// + // Loop + + // We need to implement a post-order traversal iteratively, to + // avoid running into stack space limits. This is pretty tricky + // to reason about, so we just manually stack-ify the recursive + // variant: + // + // function f($node) { + // foreach ($node->children as $child) { + // f($child); + // } + // validate($node); + // } + // + // Thus, we will represent a stack frame as array($node, + // $is_inline, stack of children) + // e.g. array_reverse($node->children) - already processed + // children. + + $parent_def = $definition->info_parent_def; + $stack = array( + array($top_node, + $parent_def->descendants_are_inline, + $parent_def->excludes, // exclusions + 0) + ); + + while (!empty($stack)) { + list($node, $is_inline, $excludes, $ix) = array_pop($stack); + // recursive call + $go = false; + $def = empty($stack) ? $definition->info_parent_def : $definition->info[$node->name]; + while (isset($node->children[$ix])) { + $child = $node->children[$ix++]; + if ($child instanceof HTMLPurifier_Node_Element) { + $go = true; + $stack[] = array($node, $is_inline, $excludes, $ix); + $stack[] = array($child, + // ToDo: I don't think it matters if it's def or + // child_def, but double check this... + $is_inline || $def->descendants_are_inline, + empty($def->excludes) ? $excludes + : array_merge($excludes, $def->excludes), + 0); + break; + } + }; + if ($go) continue; + list($token, $d) = $node->toTokenPair(); + // base case + if ($excludes_enabled && isset($excludes[$node->name])) { + $node->dead = true; + if ($e) $e->send(E_ERROR, 'Strategy_FixNesting: Node excluded'); + } else { + // XXX I suppose it would be slightly more efficient to + // avoid the allocation here and have children + // strategies handle it + $children = array(); + foreach ($node->children as $child) { + if (!$child->dead) $children[] = $child; + } + $result = $def->child->validateChildren($children, $config, $context); + if ($result === true) { + // nop + $node->children = $children; + } elseif ($result === false) { + $node->dead = true; + if ($e) $e->send(E_ERROR, 'Strategy_FixNesting: Node removed'); + } else { + $node->children = $result; + if ($e) { + // XXX This will miss mutations of internal nodes. Perhaps defer to the child validators + if (empty($result) && !empty($children)) { + $e->send(E_ERROR, 'Strategy_FixNesting: Node contents removed'); + } else if ($result != $children) { + $e->send(E_WARNING, 'Strategy_FixNesting: Node reorganized'); + } + } + } + } + } + + //####################################################################// + // Post-processing + + // remove context variables + $context->destroy('IsInline'); + $context->destroy('CurrentNode'); + $context->destroy('CurrentToken'); + + //####################################################################// + // Return + + return HTMLPurifier_Arborize::flatten($node, $config, $context); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/MakeWellFormed.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/MakeWellFormed.php new file mode 100644 index 0000000..a6eb09e --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/MakeWellFormed.php @@ -0,0 +1,659 @@ +getHTMLDefinition(); + + // local variables + $generator = new HTMLPurifier_Generator($config, $context); + $escape_invalid_tags = $config->get('Core.EscapeInvalidTags'); + // used for autoclose early abortion + $global_parent_allowed_elements = $definition->info_parent_def->child->getAllowedElements($config); + $e = $context->get('ErrorCollector', true); + $i = false; // injector index + list($zipper, $token) = HTMLPurifier_Zipper::fromArray($tokens); + if ($token === NULL) { + return array(); + } + $reprocess = false; // whether or not to reprocess the same token + $stack = array(); + + // member variables + $this->stack =& $stack; + $this->tokens =& $tokens; + $this->token =& $token; + $this->zipper =& $zipper; + $this->config = $config; + $this->context = $context; + + // context variables + $context->register('CurrentNesting', $stack); + $context->register('InputZipper', $zipper); + $context->register('CurrentToken', $token); + + // -- begin INJECTOR -- + + $this->injectors = array(); + + $injectors = $config->getBatch('AutoFormat'); + $def_injectors = $definition->info_injector; + $custom_injectors = $injectors['Custom']; + unset($injectors['Custom']); // special case + foreach ($injectors as $injector => $b) { + // XXX: Fix with a legitimate lookup table of enabled filters + if (strpos($injector, '.') !== false) { + continue; + } + $injector = "HTMLPurifier_Injector_$injector"; + if (!$b) { + continue; + } + $this->injectors[] = new $injector; + } + foreach ($def_injectors as $injector) { + // assumed to be objects + $this->injectors[] = $injector; + } + foreach ($custom_injectors as $injector) { + if (!$injector) { + continue; + } + if (is_string($injector)) { + $injector = "HTMLPurifier_Injector_$injector"; + $injector = new $injector; + } + $this->injectors[] = $injector; + } + + // give the injectors references to the definition and context + // variables for performance reasons + foreach ($this->injectors as $ix => $injector) { + $error = $injector->prepare($config, $context); + if (!$error) { + continue; + } + array_splice($this->injectors, $ix, 1); // rm the injector + trigger_error("Cannot enable {$injector->name} injector because $error is not allowed", E_USER_WARNING); + } + + // -- end INJECTOR -- + + // a note on reprocessing: + // In order to reduce code duplication, whenever some code needs + // to make HTML changes in order to make things "correct", the + // new HTML gets sent through the purifier, regardless of its + // status. This means that if we add a start token, because it + // was totally necessary, we don't have to update nesting; we just + // punt ($reprocess = true; continue;) and it does that for us. + + // isset is in loop because $tokens size changes during loop exec + for (;; + // only increment if we don't need to reprocess + $reprocess ? $reprocess = false : $token = $zipper->next($token)) { + + // check for a rewind + if (is_int($i)) { + // possibility: disable rewinding if the current token has a + // rewind set on it already. This would offer protection from + // infinite loop, but might hinder some advanced rewinding. + $rewind_offset = $this->injectors[$i]->getRewindOffset(); + if (is_int($rewind_offset)) { + for ($j = 0; $j < $rewind_offset; $j++) { + if (empty($zipper->front)) break; + $token = $zipper->prev($token); + // indicate that other injectors should not process this token, + // but we need to reprocess it. See Note [Injector skips] + unset($token->skip[$i]); + $token->rewind = $i; + if ($token instanceof HTMLPurifier_Token_Start) { + array_pop($this->stack); + } elseif ($token instanceof HTMLPurifier_Token_End) { + $this->stack[] = $token->start; + } + } + } + $i = false; + } + + // handle case of document end + if ($token === NULL) { + // kill processing if stack is empty + if (empty($this->stack)) { + break; + } + + // peek + $top_nesting = array_pop($this->stack); + $this->stack[] = $top_nesting; + + // send error [TagClosedSuppress] + if ($e && !isset($top_nesting->armor['MakeWellFormed_TagClosedError'])) { + $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by document end', $top_nesting); + } + + // append, don't splice, since this is the end + $token = new HTMLPurifier_Token_End($top_nesting->name); + + // punt! + $reprocess = true; + continue; + } + + //echo '
    '; printZipper($zipper, $token);//printTokens($this->stack); + //flush(); + + // quick-check: if it's not a tag, no need to process + if (empty($token->is_tag)) { + if ($token instanceof HTMLPurifier_Token_Text) { + foreach ($this->injectors as $i => $injector) { + if (isset($token->skip[$i])) { + // See Note [Injector skips] + continue; + } + if ($token->rewind !== null && $token->rewind !== $i) { + continue; + } + // XXX fuckup + $r = $token; + $injector->handleText($r); + $token = $this->processToken($r, $i); + $reprocess = true; + break; + } + } + // another possibility is a comment + continue; + } + + if (isset($definition->info[$token->name])) { + $type = $definition->info[$token->name]->child->type; + } else { + $type = false; // Type is unknown, treat accordingly + } + + // quick tag checks: anything that's *not* an end tag + $ok = false; + if ($type === 'empty' && $token instanceof HTMLPurifier_Token_Start) { + // claims to be a start tag but is empty + $token = new HTMLPurifier_Token_Empty( + $token->name, + $token->attr, + $token->line, + $token->col, + $token->armor + ); + $ok = true; + } elseif ($type && $type !== 'empty' && $token instanceof HTMLPurifier_Token_Empty) { + // claims to be empty but really is a start tag + // NB: this assignment is required + $old_token = $token; + $token = new HTMLPurifier_Token_End($token->name); + $token = $this->insertBefore( + new HTMLPurifier_Token_Start($old_token->name, $old_token->attr, $old_token->line, $old_token->col, $old_token->armor) + ); + // punt (since we had to modify the input stream in a non-trivial way) + $reprocess = true; + continue; + } elseif ($token instanceof HTMLPurifier_Token_Empty) { + // real empty token + $ok = true; + } elseif ($token instanceof HTMLPurifier_Token_Start) { + // start tag + + // ...unless they also have to close their parent + if (!empty($this->stack)) { + + // Performance note: you might think that it's rather + // inefficient, recalculating the autoclose information + // for every tag that a token closes (since when we + // do an autoclose, we push a new token into the + // stream and then /process/ that, before + // re-processing this token.) But this is + // necessary, because an injector can make an + // arbitrary transformations to the autoclosing + // tokens we introduce, so things may have changed + // in the meantime. Also, doing the inefficient thing is + // "easy" to reason about (for certain perverse definitions + // of "easy") + + $parent = array_pop($this->stack); + $this->stack[] = $parent; + + $parent_def = null; + $parent_elements = null; + $autoclose = false; + if (isset($definition->info[$parent->name])) { + $parent_def = $definition->info[$parent->name]; + $parent_elements = $parent_def->child->getAllowedElements($config); + $autoclose = !isset($parent_elements[$token->name]); + } + + if ($autoclose && $definition->info[$token->name]->wrap) { + // Check if an element can be wrapped by another + // element to make it valid in a context (for + // example,
        needs a
      • in between) + $wrapname = $definition->info[$token->name]->wrap; + $wrapdef = $definition->info[$wrapname]; + $elements = $wrapdef->child->getAllowedElements($config); + if (isset($elements[$token->name]) && isset($parent_elements[$wrapname])) { + $newtoken = new HTMLPurifier_Token_Start($wrapname); + $token = $this->insertBefore($newtoken); + $reprocess = true; + continue; + } + } + + $carryover = false; + if ($autoclose && $parent_def->formatting) { + $carryover = true; + } + + if ($autoclose) { + // check if this autoclose is doomed to fail + // (this rechecks $parent, which his harmless) + $autoclose_ok = isset($global_parent_allowed_elements[$token->name]); + if (!$autoclose_ok) { + foreach ($this->stack as $ancestor) { + $elements = $definition->info[$ancestor->name]->child->getAllowedElements($config); + if (isset($elements[$token->name])) { + $autoclose_ok = true; + break; + } + if ($definition->info[$token->name]->wrap) { + $wrapname = $definition->info[$token->name]->wrap; + $wrapdef = $definition->info[$wrapname]; + $wrap_elements = $wrapdef->child->getAllowedElements($config); + if (isset($wrap_elements[$token->name]) && isset($elements[$wrapname])) { + $autoclose_ok = true; + break; + } + } + } + } + if ($autoclose_ok) { + // errors need to be updated + $new_token = new HTMLPurifier_Token_End($parent->name); + $new_token->start = $parent; + // [TagClosedSuppress] + if ($e && !isset($parent->armor['MakeWellFormed_TagClosedError'])) { + if (!$carryover) { + $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag auto closed', $parent); + } else { + $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag carryover', $parent); + } + } + if ($carryover) { + $element = clone $parent; + // [TagClosedAuto] + $element->armor['MakeWellFormed_TagClosedError'] = true; + $element->carryover = true; + $token = $this->processToken(array($new_token, $token, $element)); + } else { + $token = $this->insertBefore($new_token); + } + } else { + $token = $this->remove(); + } + $reprocess = true; + continue; + } + + } + $ok = true; + } + + if ($ok) { + foreach ($this->injectors as $i => $injector) { + if (isset($token->skip[$i])) { + // See Note [Injector skips] + continue; + } + if ($token->rewind !== null && $token->rewind !== $i) { + continue; + } + $r = $token; + $injector->handleElement($r); + $token = $this->processToken($r, $i); + $reprocess = true; + break; + } + if (!$reprocess) { + // ah, nothing interesting happened; do normal processing + if ($token instanceof HTMLPurifier_Token_Start) { + $this->stack[] = $token; + } elseif ($token instanceof HTMLPurifier_Token_End) { + throw new HTMLPurifier_Exception( + 'Improper handling of end tag in start code; possible error in MakeWellFormed' + ); + } + } + continue; + } + + // sanity check: we should be dealing with a closing tag + if (!$token instanceof HTMLPurifier_Token_End) { + throw new HTMLPurifier_Exception('Unaccounted for tag token in input stream, bug in HTML Purifier'); + } + + // make sure that we have something open + if (empty($this->stack)) { + if ($escape_invalid_tags) { + if ($e) { + $e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag to text'); + } + $token = new HTMLPurifier_Token_Text($generator->generateFromToken($token)); + } else { + if ($e) { + $e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag removed'); + } + $token = $this->remove(); + } + $reprocess = true; + continue; + } + + // first, check for the simplest case: everything closes neatly. + // Eventually, everything passes through here; if there are problems + // we modify the input stream accordingly and then punt, so that + // the tokens get processed again. + $current_parent = array_pop($this->stack); + if ($current_parent->name == $token->name) { + $token->start = $current_parent; + foreach ($this->injectors as $i => $injector) { + if (isset($token->skip[$i])) { + // See Note [Injector skips] + continue; + } + if ($token->rewind !== null && $token->rewind !== $i) { + continue; + } + $r = $token; + $injector->handleEnd($r); + $token = $this->processToken($r, $i); + $this->stack[] = $current_parent; + $reprocess = true; + break; + } + continue; + } + + // okay, so we're trying to close the wrong tag + + // undo the pop previous pop + $this->stack[] = $current_parent; + + // scroll back the entire nest, trying to find our tag. + // (feature could be to specify how far you'd like to go) + $size = count($this->stack); + // -2 because -1 is the last element, but we already checked that + $skipped_tags = false; + for ($j = $size - 2; $j >= 0; $j--) { + if ($this->stack[$j]->name == $token->name) { + $skipped_tags = array_slice($this->stack, $j); + break; + } + } + + // we didn't find the tag, so remove + if ($skipped_tags === false) { + if ($escape_invalid_tags) { + if ($e) { + $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag to text'); + } + $token = new HTMLPurifier_Token_Text($generator->generateFromToken($token)); + } else { + if ($e) { + $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag removed'); + } + $token = $this->remove(); + } + $reprocess = true; + continue; + } + + // do errors, in REVERSE $j order: a,b,c with + $c = count($skipped_tags); + if ($e) { + for ($j = $c - 1; $j > 0; $j--) { + // notice we exclude $j == 0, i.e. the current ending tag, from + // the errors... [TagClosedSuppress] + if (!isset($skipped_tags[$j]->armor['MakeWellFormed_TagClosedError'])) { + $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by element end', $skipped_tags[$j]); + } + } + } + + // insert tags, in FORWARD $j order: c,b,a with + $replace = array($token); + for ($j = 1; $j < $c; $j++) { + // ...as well as from the insertions + $new_token = new HTMLPurifier_Token_End($skipped_tags[$j]->name); + $new_token->start = $skipped_tags[$j]; + array_unshift($replace, $new_token); + if (isset($definition->info[$new_token->name]) && $definition->info[$new_token->name]->formatting) { + // [TagClosedAuto] + $element = clone $skipped_tags[$j]; + $element->carryover = true; + $element->armor['MakeWellFormed_TagClosedError'] = true; + $replace[] = $element; + } + } + $token = $this->processToken($replace); + $reprocess = true; + continue; + } + + $context->destroy('CurrentToken'); + $context->destroy('CurrentNesting'); + $context->destroy('InputZipper'); + + unset($this->injectors, $this->stack, $this->tokens); + return $zipper->toArray($token); + } + + /** + * Processes arbitrary token values for complicated substitution patterns. + * In general: + * + * If $token is an array, it is a list of tokens to substitute for the + * current token. These tokens then get individually processed. If there + * is a leading integer in the list, that integer determines how many + * tokens from the stream should be removed. + * + * If $token is a regular token, it is swapped with the current token. + * + * If $token is false, the current token is deleted. + * + * If $token is an integer, that number of tokens (with the first token + * being the current one) will be deleted. + * + * @param HTMLPurifier_Token|array|int|bool $token Token substitution value + * @param HTMLPurifier_Injector|int $injector Injector that performed the substitution; default is if + * this is not an injector related operation. + * @throws HTMLPurifier_Exception + */ + protected function processToken($token, $injector = -1) + { + // Zend OpCache miscompiles $token = array($token), so + // avoid this pattern. See: https://github.com/ezyang/htmlpurifier/issues/108 + + // normalize forms of token + if (is_object($token)) { + $tmp = $token; + $token = array(1, $tmp); + } + if (is_int($token)) { + $tmp = $token; + $token = array($tmp); + } + if ($token === false) { + $token = array(1); + } + if (!is_array($token)) { + throw new HTMLPurifier_Exception('Invalid token type from injector'); + } + if (!is_int($token[0])) { + array_unshift($token, 1); + } + if ($token[0] === 0) { + throw new HTMLPurifier_Exception('Deleting zero tokens is not valid'); + } + + // $token is now an array with the following form: + // array(number nodes to delete, new node 1, new node 2, ...) + + $delete = array_shift($token); + list($old, $r) = $this->zipper->splice($this->token, $delete, $token); + + if ($injector > -1) { + // See Note [Injector skips] + // Determine appropriate skips. Here's what the code does: + // *If* we deleted one or more tokens, copy the skips + // of those tokens into the skips of the new tokens (in $token). + // Also, mark the newly inserted tokens as having come from + // $injector. + $oldskip = isset($old[0]) ? $old[0]->skip : array(); + foreach ($token as $object) { + $object->skip = $oldskip; + $object->skip[$injector] = true; + } + } + + return $r; + + } + + /** + * Inserts a token before the current token. Cursor now points to + * this token. You must reprocess after this. + * @param HTMLPurifier_Token $token + */ + private function insertBefore($token) + { + // NB not $this->zipper->insertBefore(), due to positioning + // differences + $splice = $this->zipper->splice($this->token, 0, array($token)); + + return $splice[1]; + } + + /** + * Removes current token. Cursor now points to new token occupying previously + * occupied space. You must reprocess after this. + */ + private function remove() + { + return $this->zipper->delete(); + } +} + +// Note [Injector skips] +// ~~~~~~~~~~~~~~~~~~~~~ +// When I originally designed this class, the idea behind the 'skip' +// property of HTMLPurifier_Token was to help avoid infinite loops +// in injector processing. For example, suppose you wrote an injector +// that bolded swear words. Naively, you might write it so that +// whenever you saw ****, you replaced it with ****. +// +// When this happens, we will reprocess all of the tokens with the +// other injectors. Now there is an opportunity for infinite loop: +// if we rerun the swear-word injector on these tokens, we might +// see **** and then reprocess again to get +// **** ad infinitum. +// +// Thus, the idea of a skip is that once we process a token with +// an injector, we mark all of those tokens as having "come from" +// the injector, and we never run the injector again on these +// tokens. +// +// There were two more complications, however: +// +// - With HTMLPurifier_Injector_RemoveEmpty, we noticed that if +// you had , after you removed the , you +// really would like this injector to go back and reprocess +// the tag, discovering that it is now empty and can be +// removed. So we reintroduced the possibility of infinite looping +// by adding a "rewind" function, which let you go back to an +// earlier point in the token stream and reprocess it with injectors. +// Needless to say, we need to UN-skip the token so it gets +// reprocessed. +// +// - Suppose that you successfuly process a token, replace it with +// one with your skip mark, but now another injector wants to +// process the skipped token with another token. Should you continue +// to skip that new token, or reprocess it? If you reprocess, +// you can end up with an infinite loop where one injector converts +// to , and then another injector converts it back. So +// we inherit the skips, but for some reason, I thought that we +// should inherit the skip from the first token of the token +// that we deleted. Why? Well, it seems to work OK. +// +// If I were to redesign this functionality, I would absolutely not +// go about doing it this way: the semantics are just not very well +// defined, and in any case you probably wanted to operate on trees, +// not token streams. + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/RemoveForeignElements.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/RemoveForeignElements.php new file mode 100644 index 0000000..1a8149e --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/RemoveForeignElements.php @@ -0,0 +1,207 @@ +getHTMLDefinition(); + $generator = new HTMLPurifier_Generator($config, $context); + $result = array(); + + $escape_invalid_tags = $config->get('Core.EscapeInvalidTags'); + $remove_invalid_img = $config->get('Core.RemoveInvalidImg'); + + // currently only used to determine if comments should be kept + $trusted = $config->get('HTML.Trusted'); + $comment_lookup = $config->get('HTML.AllowedComments'); + $comment_regexp = $config->get('HTML.AllowedCommentsRegexp'); + $check_comments = $comment_lookup !== array() || $comment_regexp !== null; + + $remove_script_contents = $config->get('Core.RemoveScriptContents'); + $hidden_elements = $config->get('Core.HiddenElements'); + + // remove script contents compatibility + if ($remove_script_contents === true) { + $hidden_elements['script'] = true; + } elseif ($remove_script_contents === false && isset($hidden_elements['script'])) { + unset($hidden_elements['script']); + } + + $attr_validator = new HTMLPurifier_AttrValidator(); + + // removes tokens until it reaches a closing tag with its value + $remove_until = false; + + // converts comments into text tokens when this is equal to a tag name + $textify_comments = false; + + $token = false; + $context->register('CurrentToken', $token); + + $e = false; + if ($config->get('Core.CollectErrors')) { + $e =& $context->get('ErrorCollector'); + } + + foreach ($tokens as $token) { + if ($remove_until) { + if (empty($token->is_tag) || $token->name !== $remove_until) { + continue; + } + } + if (!empty($token->is_tag)) { + // DEFINITION CALL + + // before any processing, try to transform the element + if (isset($definition->info_tag_transform[$token->name])) { + $original_name = $token->name; + // there is a transformation for this tag + // DEFINITION CALL + $token = $definition-> + info_tag_transform[$token->name]->transform($token, $config, $context); + if ($e) { + $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Tag transform', $original_name); + } + } + + if (isset($definition->info[$token->name])) { + // mostly everything's good, but + // we need to make sure required attributes are in order + if (($token instanceof HTMLPurifier_Token_Start || $token instanceof HTMLPurifier_Token_Empty) && + $definition->info[$token->name]->required_attr && + ($token->name != 'img' || $remove_invalid_img) // ensure config option still works + ) { + $attr_validator->validateToken($token, $config, $context); + $ok = true; + foreach ($definition->info[$token->name]->required_attr as $name) { + if (!isset($token->attr[$name])) { + $ok = false; + break; + } + } + if (!$ok) { + if ($e) { + $e->send( + E_ERROR, + 'Strategy_RemoveForeignElements: Missing required attribute', + $name + ); + } + continue; + } + $token->armor['ValidateAttributes'] = true; + } + + if (isset($hidden_elements[$token->name]) && $token instanceof HTMLPurifier_Token_Start) { + $textify_comments = $token->name; + } elseif ($token->name === $textify_comments && $token instanceof HTMLPurifier_Token_End) { + $textify_comments = false; + } + + } elseif ($escape_invalid_tags) { + // invalid tag, generate HTML representation and insert in + if ($e) { + $e->send(E_WARNING, 'Strategy_RemoveForeignElements: Foreign element to text'); + } + $token = new HTMLPurifier_Token_Text( + $generator->generateFromToken($token) + ); + } else { + // check if we need to destroy all of the tag's children + // CAN BE GENERICIZED + if (isset($hidden_elements[$token->name])) { + if ($token instanceof HTMLPurifier_Token_Start) { + $remove_until = $token->name; + } elseif ($token instanceof HTMLPurifier_Token_Empty) { + // do nothing: we're still looking + } else { + $remove_until = false; + } + if ($e) { + $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign meta element removed'); + } + } else { + if ($e) { + $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign element removed'); + } + } + continue; + } + } elseif ($token instanceof HTMLPurifier_Token_Comment) { + // textify comments in script tags when they are allowed + if ($textify_comments !== false) { + $data = $token->data; + $token = new HTMLPurifier_Token_Text($data); + } elseif ($trusted || $check_comments) { + // always cleanup comments + $trailing_hyphen = false; + if ($e) { + // perform check whether or not there's a trailing hyphen + if (substr($token->data, -1) == '-') { + $trailing_hyphen = true; + } + } + $token->data = rtrim($token->data, '-'); + $found_double_hyphen = false; + while (strpos($token->data, '--') !== false) { + $found_double_hyphen = true; + $token->data = str_replace('--', '-', $token->data); + } + if ($trusted || !empty($comment_lookup[trim($token->data)]) || + ($comment_regexp !== null && preg_match($comment_regexp, trim($token->data)))) { + // OK good + if ($e) { + if ($trailing_hyphen) { + $e->send( + E_NOTICE, + 'Strategy_RemoveForeignElements: Trailing hyphen in comment removed' + ); + } + if ($found_double_hyphen) { + $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Hyphens in comment collapsed'); + } + } + } else { + if ($e) { + $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Comment removed'); + } + continue; + } + } else { + // strip comments + if ($e) { + $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Comment removed'); + } + continue; + } + } elseif ($token instanceof HTMLPurifier_Token_Text) { + } else { + continue; + } + $result[] = $token; + } + if ($remove_until && $e) { + // we removed tokens until the end, throw error + $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Token removed to end', $remove_until); + } + $context->destroy('CurrentToken'); + return $result; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/ValidateAttributes.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/ValidateAttributes.php new file mode 100644 index 0000000..fbb3d27 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/ValidateAttributes.php @@ -0,0 +1,45 @@ +register('CurrentToken', $token); + + foreach ($tokens as $key => $token) { + + // only process tokens that have attributes, + // namely start and empty tags + if (!$token instanceof HTMLPurifier_Token_Start && !$token instanceof HTMLPurifier_Token_Empty) { + continue; + } + + // skip tokens that are armored + if (!empty($token->armor['ValidateAttributes'])) { + continue; + } + + // note that we have no facilities here for removing tokens + $validator->validateToken($token, $config, $context); + } + $context->destroy('CurrentToken'); + return $tokens; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/StringHash.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/StringHash.php new file mode 100644 index 0000000..c073701 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/StringHash.php @@ -0,0 +1,47 @@ +accessed[$index] = true; + return parent::offsetGet($index); + } + + /** + * Returns a lookup array of all array indexes that have been accessed. + * @return array in form array($index => true). + */ + public function getAccessed() + { + return $this->accessed; + } + + /** + * Resets the access array. + */ + public function resetAccessed() + { + $this->accessed = array(); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/StringHashParser.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/StringHashParser.php new file mode 100644 index 0000000..7c73f80 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/StringHashParser.php @@ -0,0 +1,136 @@ + 'DefaultKeyValue', + * 'KEY' => 'Value', + * 'KEY2' => 'Value2', + * 'MULTILINE-KEY' => "Multiline\nvalue.\n", + * ) + * + * We use this as an easy to use file-format for configuration schema + * files, but the class itself is usage agnostic. + * + * You can use ---- to forcibly terminate parsing of a single string-hash; + * this marker is used in multi string-hashes to delimit boundaries. + */ +class HTMLPurifier_StringHashParser +{ + + /** + * @type string + */ + public $default = 'ID'; + + /** + * Parses a file that contains a single string-hash. + * @param string $file + * @return array + */ + public function parseFile($file) + { + if (!file_exists($file)) { + return false; + } + $fh = fopen($file, 'r'); + if (!$fh) { + return false; + } + $ret = $this->parseHandle($fh); + fclose($fh); + return $ret; + } + + /** + * Parses a file that contains multiple string-hashes delimited by '----' + * @param string $file + * @return array + */ + public function parseMultiFile($file) + { + if (!file_exists($file)) { + return false; + } + $ret = array(); + $fh = fopen($file, 'r'); + if (!$fh) { + return false; + } + while (!feof($fh)) { + $ret[] = $this->parseHandle($fh); + } + fclose($fh); + return $ret; + } + + /** + * Internal parser that acepts a file handle. + * @note While it's possible to simulate in-memory parsing by using + * custom stream wrappers, if such a use-case arises we should + * factor out the file handle into its own class. + * @param resource $fh File handle with pointer at start of valid string-hash + * block. + * @return array + */ + protected function parseHandle($fh) + { + $state = false; + $single = false; + $ret = array(); + do { + $line = fgets($fh); + if ($line === false) { + break; + } + $line = rtrim($line, "\n\r"); + if (!$state && $line === '') { + continue; + } + if ($line === '----') { + break; + } + if (strncmp('--#', $line, 3) === 0) { + // Comment + continue; + } elseif (strncmp('--', $line, 2) === 0) { + // Multiline declaration + $state = trim($line, '- '); + if (!isset($ret[$state])) { + $ret[$state] = ''; + } + continue; + } elseif (!$state) { + $single = true; + if (strpos($line, ':') !== false) { + // Single-line declaration + list($state, $line) = explode(':', $line, 2); + $line = trim($line); + } else { + // Use default declaration + $state = $this->default; + } + } + if ($single) { + $ret[$state] = $line; + $single = false; + $state = false; + } else { + $ret[$state] .= "$line\n"; + } + } while (!feof($fh)); + return $ret; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform.php new file mode 100644 index 0000000..7b8d833 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform.php @@ -0,0 +1,37 @@ + 'xx-small', + '1' => 'xx-small', + '2' => 'small', + '3' => 'medium', + '4' => 'large', + '5' => 'x-large', + '6' => 'xx-large', + '7' => '300%', + '-1' => 'smaller', + '-2' => '60%', + '+1' => 'larger', + '+2' => '150%', + '+3' => '200%', + '+4' => '300%' + ); + + /** + * @param HTMLPurifier_Token_Tag $tag + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return HTMLPurifier_Token_End|string + */ + public function transform($tag, $config, $context) + { + if ($tag instanceof HTMLPurifier_Token_End) { + $new_tag = clone $tag; + $new_tag->name = $this->transform_to; + return $new_tag; + } + + $attr = $tag->attr; + $prepend_style = ''; + + // handle color transform + if (isset($attr['color'])) { + $prepend_style .= 'color:' . $attr['color'] . ';'; + unset($attr['color']); + } + + // handle face transform + if (isset($attr['face'])) { + $prepend_style .= 'font-family:' . $attr['face'] . ';'; + unset($attr['face']); + } + + // handle size transform + if (isset($attr['size'])) { + // normalize large numbers + if ($attr['size'] !== '') { + if ($attr['size'][0] == '+' || $attr['size'][0] == '-') { + $size = (int)$attr['size']; + if ($size < -2) { + $attr['size'] = '-2'; + } + if ($size > 4) { + $attr['size'] = '+4'; + } + } else { + $size = (int)$attr['size']; + if ($size > 7) { + $attr['size'] = '7'; + } + } + } + if (isset($this->_size_lookup[$attr['size']])) { + $prepend_style .= 'font-size:' . + $this->_size_lookup[$attr['size']] . ';'; + } + unset($attr['size']); + } + + if ($prepend_style) { + $attr['style'] = isset($attr['style']) ? + $prepend_style . $attr['style'] : + $prepend_style; + } + + $new_tag = clone $tag; + $new_tag->name = $this->transform_to; + $new_tag->attr = $attr; + + return $new_tag; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform/Simple.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform/Simple.php new file mode 100644 index 0000000..71bf10b --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform/Simple.php @@ -0,0 +1,44 @@ +transform_to = $transform_to; + $this->style = $style; + } + + /** + * @param HTMLPurifier_Token_Tag $tag + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return string + */ + public function transform($tag, $config, $context) + { + $new_tag = clone $tag; + $new_tag->name = $this->transform_to; + if (!is_null($this->style) && + ($new_tag instanceof HTMLPurifier_Token_Start || $new_tag instanceof HTMLPurifier_Token_Empty) + ) { + $this->prependCSS($new_tag->attr, $this->style); + } + return $new_tag; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token.php new file mode 100644 index 0000000..84d3619 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token.php @@ -0,0 +1,100 @@ +line = $l; + $this->col = $c; + } + + /** + * Convenience function for DirectLex settings line/col position. + * @param int $l + * @param int $c + */ + public function rawPosition($l, $c) + { + if ($c === -1) { + $l++; + } + $this->line = $l; + $this->col = $c; + } + + /** + * Converts a token into its corresponding node. + */ + abstract public function toNode(); +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Comment.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Comment.php new file mode 100644 index 0000000..23453c7 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Comment.php @@ -0,0 +1,38 @@ +data = $data; + $this->line = $line; + $this->col = $col; + } + + public function toNode() { + return new HTMLPurifier_Node_Comment($this->data, $this->line, $this->col); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Empty.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Empty.php new file mode 100644 index 0000000..78a95f5 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Empty.php @@ -0,0 +1,15 @@ +empty = true; + return $n; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/End.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/End.php new file mode 100644 index 0000000..59b38fd --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/End.php @@ -0,0 +1,24 @@ +toNode not supported!"); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Start.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Start.php new file mode 100644 index 0000000..019f317 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Start.php @@ -0,0 +1,10 @@ +!empty($obj->is_tag) + * without having to use a function call is_a(). + * @type bool + */ + public $is_tag = true; + + /** + * The lower-case name of the tag, like 'a', 'b' or 'blockquote'. + * + * @note Strictly speaking, XML tags are case sensitive, so we shouldn't + * be lower-casing them, but these tokens cater to HTML tags, which are + * insensitive. + * @type string + */ + public $name; + + /** + * Associative array of the tag's attributes. + * @type array + */ + public $attr = array(); + + /** + * Non-overloaded constructor, which lower-cases passed tag name. + * + * @param string $name String name. + * @param array $attr Associative array of attributes. + * @param int $line + * @param int $col + * @param array $armor + */ + public function __construct($name, $attr = array(), $line = null, $col = null, $armor = array()) + { + $this->name = ctype_lower($name) ? $name : strtolower($name); + foreach ($attr as $key => $value) { + // normalization only necessary when key is not lowercase + if (!ctype_lower($key)) { + $new_key = strtolower($key); + if (!isset($attr[$new_key])) { + $attr[$new_key] = $attr[$key]; + } + if ($new_key !== $key) { + unset($attr[$key]); + } + } + } + $this->attr = $attr; + $this->line = $line; + $this->col = $col; + $this->armor = $armor; + } + + public function toNode() { + return new HTMLPurifier_Node_Element($this->name, $this->attr, $this->line, $this->col, $this->armor); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Text.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Text.php new file mode 100644 index 0000000..f26a1c2 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Text.php @@ -0,0 +1,53 @@ +data = $data; + $this->is_whitespace = ctype_space($data); + $this->line = $line; + $this->col = $col; + } + + public function toNode() { + return new HTMLPurifier_Node_Text($this->data, $this->is_whitespace, $this->line, $this->col); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TokenFactory.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TokenFactory.php new file mode 100644 index 0000000..dea2446 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TokenFactory.php @@ -0,0 +1,118 @@ +p_start = new HTMLPurifier_Token_Start('', array()); + $this->p_end = new HTMLPurifier_Token_End(''); + $this->p_empty = new HTMLPurifier_Token_Empty('', array()); + $this->p_text = new HTMLPurifier_Token_Text(''); + $this->p_comment = new HTMLPurifier_Token_Comment(''); + } + + /** + * Creates a HTMLPurifier_Token_Start. + * @param string $name Tag name + * @param array $attr Associative array of attributes + * @return HTMLPurifier_Token_Start Generated HTMLPurifier_Token_Start + */ + public function createStart($name, $attr = array()) + { + $p = clone $this->p_start; + $p->__construct($name, $attr); + return $p; + } + + /** + * Creates a HTMLPurifier_Token_End. + * @param string $name Tag name + * @return HTMLPurifier_Token_End Generated HTMLPurifier_Token_End + */ + public function createEnd($name) + { + $p = clone $this->p_end; + $p->__construct($name); + return $p; + } + + /** + * Creates a HTMLPurifier_Token_Empty. + * @param string $name Tag name + * @param array $attr Associative array of attributes + * @return HTMLPurifier_Token_Empty Generated HTMLPurifier_Token_Empty + */ + public function createEmpty($name, $attr = array()) + { + $p = clone $this->p_empty; + $p->__construct($name, $attr); + return $p; + } + + /** + * Creates a HTMLPurifier_Token_Text. + * @param string $data Data of text token + * @return HTMLPurifier_Token_Text Generated HTMLPurifier_Token_Text + */ + public function createText($data) + { + $p = clone $this->p_text; + $p->__construct($data); + return $p; + } + + /** + * Creates a HTMLPurifier_Token_Comment. + * @param string $data Data of comment token + * @return HTMLPurifier_Token_Comment Generated HTMLPurifier_Token_Comment + */ + public function createComment($data) + { + $p = clone $this->p_comment; + $p->__construct($data); + return $p; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URI.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URI.php new file mode 100644 index 0000000..9c5be39 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URI.php @@ -0,0 +1,316 @@ +scheme = is_null($scheme) || ctype_lower($scheme) ? $scheme : strtolower($scheme); + $this->userinfo = $userinfo; + $this->host = $host; + $this->port = is_null($port) ? $port : (int)$port; + $this->path = $path; + $this->query = $query; + $this->fragment = $fragment; + } + + /** + * Retrieves a scheme object corresponding to the URI's scheme/default + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return HTMLPurifier_URIScheme Scheme object appropriate for validating this URI + */ + public function getSchemeObj($config, $context) + { + $registry = HTMLPurifier_URISchemeRegistry::instance(); + if ($this->scheme !== null) { + $scheme_obj = $registry->getScheme($this->scheme, $config, $context); + if (!$scheme_obj) { + return false; + } // invalid scheme, clean it out + } else { + // no scheme: retrieve the default one + $def = $config->getDefinition('URI'); + $scheme_obj = $def->getDefaultScheme($config, $context); + if (!$scheme_obj) { + if ($def->defaultScheme !== null) { + // something funky happened to the default scheme object + trigger_error( + 'Default scheme object "' . $def->defaultScheme . '" was not readable', + E_USER_WARNING + ); + } // suppress error if it's null + return false; + } + } + return $scheme_obj; + } + + /** + * Generic validation method applicable for all schemes. May modify + * this URI in order to get it into a compliant form. + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool True if validation/filtering succeeds, false if failure + */ + public function validate($config, $context) + { + // ABNF definitions from RFC 3986 + $chars_sub_delims = '!$&\'()*+,;='; + $chars_gen_delims = ':/?#[]@'; + $chars_pchar = $chars_sub_delims . ':@'; + + // validate host + if (!is_null($this->host)) { + $host_def = new HTMLPurifier_AttrDef_URI_Host(); + $this->host = $host_def->validate($this->host, $config, $context); + if ($this->host === false) { + $this->host = null; + } + } + + // validate scheme + // NOTE: It's not appropriate to check whether or not this + // scheme is in our registry, since a URIFilter may convert a + // URI that we don't allow into one we do. So instead, we just + // check if the scheme can be dropped because there is no host + // and it is our default scheme. + if (!is_null($this->scheme) && is_null($this->host) || $this->host === '') { + // support for relative paths is pretty abysmal when the + // scheme is present, so axe it when possible + $def = $config->getDefinition('URI'); + if ($def->defaultScheme === $this->scheme) { + $this->scheme = null; + } + } + + // validate username + if (!is_null($this->userinfo)) { + $encoder = new HTMLPurifier_PercentEncoder($chars_sub_delims . ':'); + $this->userinfo = $encoder->encode($this->userinfo); + } + + // validate port + if (!is_null($this->port)) { + if ($this->port < 1 || $this->port > 65535) { + $this->port = null; + } + } + + // validate path + $segments_encoder = new HTMLPurifier_PercentEncoder($chars_pchar . '/'); + if (!is_null($this->host)) { // this catches $this->host === '' + // path-abempty (hier and relative) + // http://www.example.com/my/path + // //www.example.com/my/path (looks odd, but works, and + // recognized by most browsers) + // (this set is valid or invalid on a scheme by scheme + // basis, so we'll deal with it later) + // file:///my/path + // ///my/path + $this->path = $segments_encoder->encode($this->path); + } elseif ($this->path !== '') { + if ($this->path[0] === '/') { + // path-absolute (hier and relative) + // http:/my/path + // /my/path + if (strlen($this->path) >= 2 && $this->path[1] === '/') { + // This could happen if both the host gets stripped + // out + // http://my/path + // //my/path + $this->path = ''; + } else { + $this->path = $segments_encoder->encode($this->path); + } + } elseif (!is_null($this->scheme)) { + // path-rootless (hier) + // http:my/path + // Short circuit evaluation means we don't need to check nz + $this->path = $segments_encoder->encode($this->path); + } else { + // path-noscheme (relative) + // my/path + // (once again, not checking nz) + $segment_nc_encoder = new HTMLPurifier_PercentEncoder($chars_sub_delims . '@'); + $c = strpos($this->path, '/'); + if ($c !== false) { + $this->path = + $segment_nc_encoder->encode(substr($this->path, 0, $c)) . + $segments_encoder->encode(substr($this->path, $c)); + } else { + $this->path = $segment_nc_encoder->encode($this->path); + } + } + } else { + // path-empty (hier and relative) + $this->path = ''; // just to be safe + } + + // qf = query and fragment + $qf_encoder = new HTMLPurifier_PercentEncoder($chars_pchar . '/?'); + + if (!is_null($this->query)) { + $this->query = $qf_encoder->encode($this->query); + } + + if (!is_null($this->fragment)) { + $this->fragment = $qf_encoder->encode($this->fragment); + } + return true; + } + + /** + * Convert URI back to string + * @return string URI appropriate for output + */ + public function toString() + { + // reconstruct authority + $authority = null; + // there is a rendering difference between a null authority + // (http:foo-bar) and an empty string authority + // (http:///foo-bar). + if (!is_null($this->host)) { + $authority = ''; + if (!is_null($this->userinfo)) { + $authority .= $this->userinfo . '@'; + } + $authority .= $this->host; + if (!is_null($this->port)) { + $authority .= ':' . $this->port; + } + } + + // Reconstruct the result + // One might wonder about parsing quirks from browsers after + // this reconstruction. Unfortunately, parsing behavior depends + // on what *scheme* was employed (file:///foo is handled *very* + // differently than http:///foo), so unfortunately we have to + // defer to the schemes to do the right thing. + $result = ''; + if (!is_null($this->scheme)) { + $result .= $this->scheme . ':'; + } + if (!is_null($authority)) { + $result .= '//' . $authority; + } + $result .= $this->path; + if (!is_null($this->query)) { + $result .= '?' . $this->query; + } + if (!is_null($this->fragment)) { + $result .= '#' . $this->fragment; + } + + return $result; + } + + /** + * Returns true if this URL might be considered a 'local' URL given + * the current context. This is true when the host is null, or + * when it matches the host supplied to the configuration. + * + * Note that this does not do any scheme checking, so it is mostly + * only appropriate for metadata that doesn't care about protocol + * security. isBenign is probably what you actually want. + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function isLocal($config, $context) + { + if ($this->host === null) { + return true; + } + $uri_def = $config->getDefinition('URI'); + if ($uri_def->host === $this->host) { + return true; + } + return false; + } + + /** + * Returns true if this URL should be considered a 'benign' URL, + * that is: + * + * - It is a local URL (isLocal), and + * - It has a equal or better level of security + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function isBenign($config, $context) + { + if (!$this->isLocal($config, $context)) { + return false; + } + + $scheme_obj = $this->getSchemeObj($config, $context); + if (!$scheme_obj) { + return false; + } // conservative approach + + $current_scheme_obj = $config->getDefinition('URI')->getDefaultScheme($config, $context); + if ($current_scheme_obj->secure) { + if (!$scheme_obj->secure) { + return false; + } + } + return true; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIDefinition.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIDefinition.php new file mode 100644 index 0000000..e0bd8bc --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIDefinition.php @@ -0,0 +1,112 @@ +registerFilter(new HTMLPurifier_URIFilter_DisableExternal()); + $this->registerFilter(new HTMLPurifier_URIFilter_DisableExternalResources()); + $this->registerFilter(new HTMLPurifier_URIFilter_DisableResources()); + $this->registerFilter(new HTMLPurifier_URIFilter_HostBlacklist()); + $this->registerFilter(new HTMLPurifier_URIFilter_SafeIframe()); + $this->registerFilter(new HTMLPurifier_URIFilter_MakeAbsolute()); + $this->registerFilter(new HTMLPurifier_URIFilter_Munge()); + } + + public function registerFilter($filter) + { + $this->registeredFilters[$filter->name] = $filter; + } + + public function addFilter($filter, $config) + { + $r = $filter->prepare($config); + if ($r === false) return; // null is ok, for backwards compat + if ($filter->post) { + $this->postFilters[$filter->name] = $filter; + } else { + $this->filters[$filter->name] = $filter; + } + } + + protected function doSetup($config) + { + $this->setupMemberVariables($config); + $this->setupFilters($config); + } + + protected function setupFilters($config) + { + foreach ($this->registeredFilters as $name => $filter) { + if ($filter->always_load) { + $this->addFilter($filter, $config); + } else { + $conf = $config->get('URI.' . $name); + if ($conf !== false && $conf !== null) { + $this->addFilter($filter, $config); + } + } + } + unset($this->registeredFilters); + } + + protected function setupMemberVariables($config) + { + $this->host = $config->get('URI.Host'); + $base_uri = $config->get('URI.Base'); + if (!is_null($base_uri)) { + $parser = new HTMLPurifier_URIParser(); + $this->base = $parser->parse($base_uri); + $this->defaultScheme = $this->base->scheme; + if (is_null($this->host)) $this->host = $this->base->host; + } + if (is_null($this->defaultScheme)) $this->defaultScheme = $config->get('URI.DefaultScheme'); + } + + public function getDefaultScheme($config, $context) + { + return HTMLPurifier_URISchemeRegistry::instance()->getScheme($this->defaultScheme, $config, $context); + } + + public function filter(&$uri, $config, $context) + { + foreach ($this->filters as $name => $f) { + $result = $f->filter($uri, $config, $context); + if (!$result) return false; + } + return true; + } + + public function postFilter(&$uri, $config, $context) + { + foreach ($this->postFilters as $name => $f) { + $result = $f->filter($uri, $config, $context); + if (!$result) return false; + } + return true; + } + +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter.php new file mode 100644 index 0000000..09724e9 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter.php @@ -0,0 +1,74 @@ +getDefinition('URI')->host; + if ($our_host !== null) { + $this->ourHostParts = array_reverse(explode('.', $our_host)); + } + } + + /** + * @param HTMLPurifier_URI $uri Reference + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function filter(&$uri, $config, $context) + { + if (is_null($uri->host)) { + return true; + } + if ($this->ourHostParts === false) { + return false; + } + $host_parts = array_reverse(explode('.', $uri->host)); + foreach ($this->ourHostParts as $i => $x) { + if (!isset($host_parts[$i])) { + return false; + } + if ($host_parts[$i] != $this->ourHostParts[$i]) { + return false; + } + } + return true; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableExternalResources.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableExternalResources.php new file mode 100644 index 0000000..c656216 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableExternalResources.php @@ -0,0 +1,25 @@ +get('EmbeddedURI', true)) { + return true; + } + return parent::filter($uri, $config, $context); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableResources.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableResources.php new file mode 100644 index 0000000..d5c412c --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableResources.php @@ -0,0 +1,22 @@ +get('EmbeddedURI', true); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/HostBlacklist.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/HostBlacklist.php new file mode 100644 index 0000000..a6645c1 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/HostBlacklist.php @@ -0,0 +1,46 @@ +blacklist = $config->get('URI.HostBlacklist'); + return true; + } + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function filter(&$uri, $config, $context) + { + foreach ($this->blacklist as $blacklisted_host_fragment) { + if (strpos($uri->host, $blacklisted_host_fragment) !== false) { + return false; + } + } + return true; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/MakeAbsolute.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/MakeAbsolute.php new file mode 100644 index 0000000..c507bbf --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/MakeAbsolute.php @@ -0,0 +1,158 @@ +getDefinition('URI'); + $this->base = $def->base; + if (is_null($this->base)) { + trigger_error( + 'URI.MakeAbsolute is being ignored due to lack of ' . + 'value for URI.Base configuration', + E_USER_WARNING + ); + return false; + } + $this->base->fragment = null; // fragment is invalid for base URI + $stack = explode('/', $this->base->path); + array_pop($stack); // discard last segment + $stack = $this->_collapseStack($stack); // do pre-parsing + $this->basePathStack = $stack; + return true; + } + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function filter(&$uri, $config, $context) + { + if (is_null($this->base)) { + return true; + } // abort early + if ($uri->path === '' && is_null($uri->scheme) && + is_null($uri->host) && is_null($uri->query) && is_null($uri->fragment)) { + // reference to current document + $uri = clone $this->base; + return true; + } + if (!is_null($uri->scheme)) { + // absolute URI already: don't change + if (!is_null($uri->host)) { + return true; + } + $scheme_obj = $uri->getSchemeObj($config, $context); + if (!$scheme_obj) { + // scheme not recognized + return false; + } + if (!$scheme_obj->hierarchical) { + // non-hierarchal URI with explicit scheme, don't change + return true; + } + // special case: had a scheme but always is hierarchical and had no authority + } + if (!is_null($uri->host)) { + // network path, don't bother + return true; + } + if ($uri->path === '') { + $uri->path = $this->base->path; + } elseif ($uri->path[0] !== '/') { + // relative path, needs more complicated processing + $stack = explode('/', $uri->path); + $new_stack = array_merge($this->basePathStack, $stack); + if ($new_stack[0] !== '' && !is_null($this->base->host)) { + array_unshift($new_stack, ''); + } + $new_stack = $this->_collapseStack($new_stack); + $uri->path = implode('/', $new_stack); + } else { + // absolute path, but still we should collapse + $uri->path = implode('/', $this->_collapseStack(explode('/', $uri->path))); + } + // re-combine + $uri->scheme = $this->base->scheme; + if (is_null($uri->userinfo)) { + $uri->userinfo = $this->base->userinfo; + } + if (is_null($uri->host)) { + $uri->host = $this->base->host; + } + if (is_null($uri->port)) { + $uri->port = $this->base->port; + } + return true; + } + + /** + * Resolve dots and double-dots in a path stack + * @param array $stack + * @return array + */ + private function _collapseStack($stack) + { + $result = array(); + $is_folder = false; + for ($i = 0; isset($stack[$i]); $i++) { + $is_folder = false; + // absorb an internally duplicated slash + if ($stack[$i] == '' && $i && isset($stack[$i + 1])) { + continue; + } + if ($stack[$i] == '..') { + if (!empty($result)) { + $segment = array_pop($result); + if ($segment === '' && empty($result)) { + // error case: attempted to back out too far: + // restore the leading slash + $result[] = ''; + } elseif ($segment === '..') { + $result[] = '..'; // cannot remove .. with .. + } + } else { + // relative path, preserve the double-dots + $result[] = '..'; + } + $is_folder = true; + continue; + } + if ($stack[$i] == '.') { + // silently absorb + $is_folder = true; + continue; + } + $result[] = $stack[$i]; + } + if ($is_folder) { + $result[] = ''; + } + return $result; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/Munge.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/Munge.php new file mode 100644 index 0000000..6e03315 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/Munge.php @@ -0,0 +1,115 @@ +target = $config->get('URI.' . $this->name); + $this->parser = new HTMLPurifier_URIParser(); + $this->doEmbed = $config->get('URI.MungeResources'); + $this->secretKey = $config->get('URI.MungeSecretKey'); + if ($this->secretKey && !function_exists('hash_hmac')) { + throw new Exception("Cannot use %URI.MungeSecretKey without hash_hmac support."); + } + return true; + } + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function filter(&$uri, $config, $context) + { + if ($context->get('EmbeddedURI', true) && !$this->doEmbed) { + return true; + } + + $scheme_obj = $uri->getSchemeObj($config, $context); + if (!$scheme_obj) { + return true; + } // ignore unknown schemes, maybe another postfilter did it + if (!$scheme_obj->browsable) { + return true; + } // ignore non-browseable schemes, since we can't munge those in a reasonable way + if ($uri->isBenign($config, $context)) { + return true; + } // don't redirect if a benign URL + + $this->makeReplace($uri, $config, $context); + $this->replace = array_map('rawurlencode', $this->replace); + + $new_uri = strtr($this->target, $this->replace); + $new_uri = $this->parser->parse($new_uri); + // don't redirect if the target host is the same as the + // starting host + if ($uri->host === $new_uri->host) { + return true; + } + $uri = $new_uri; // overwrite + return true; + } + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + */ + protected function makeReplace($uri, $config, $context) + { + $string = $uri->toString(); + // always available + $this->replace['%s'] = $string; + $this->replace['%r'] = $context->get('EmbeddedURI', true); + $token = $context->get('CurrentToken', true); + $this->replace['%n'] = $token ? $token->name : null; + $this->replace['%m'] = $context->get('CurrentAttr', true); + $this->replace['%p'] = $context->get('CurrentCSSProperty', true); + // not always available + if ($this->secretKey) { + $this->replace['%t'] = hash_hmac("sha256", $string, $this->secretKey); + } + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/SafeIframe.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/SafeIframe.php new file mode 100644 index 0000000..f609c47 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/SafeIframe.php @@ -0,0 +1,68 @@ +regexp = $config->get('URI.SafeIframeRegexp'); + return true; + } + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function filter(&$uri, $config, $context) + { + // check if filter not applicable + if (!$config->get('HTML.SafeIframe')) { + return true; + } + // check if the filter should actually trigger + if (!$context->get('EmbeddedURI', true)) { + return true; + } + $token = $context->get('CurrentToken', true); + if (!($token && $token->name == 'iframe')) { + return true; + } + // check if we actually have some whitelists enabled + if ($this->regexp === null) { + return false; + } + // actually check the whitelists + return preg_match($this->regexp, $uri->toString()); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIParser.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIParser.php new file mode 100644 index 0000000..0e7381a --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIParser.php @@ -0,0 +1,71 @@ +percentEncoder = new HTMLPurifier_PercentEncoder(); + } + + /** + * Parses a URI. + * @param $uri string URI to parse + * @return HTMLPurifier_URI representation of URI. This representation has + * not been validated yet and may not conform to RFC. + */ + public function parse($uri) + { + $uri = $this->percentEncoder->normalize($uri); + + // Regexp is as per Appendix B. + // Note that ["<>] are an addition to the RFC's recommended + // characters, because they represent external delimeters. + $r_URI = '!'. + '(([a-zA-Z0-9\.\+\-]+):)?'. // 2. Scheme + '(//([^/?#"<>]*))?'. // 4. Authority + '([^?#"<>]*)'. // 5. Path + '(\?([^#"<>]*))?'. // 7. Query + '(#([^"<>]*))?'. // 8. Fragment + '!'; + + $matches = array(); + $result = preg_match($r_URI, $uri, $matches); + + if (!$result) return false; // *really* invalid URI + + // seperate out parts + $scheme = !empty($matches[1]) ? $matches[2] : null; + $authority = !empty($matches[3]) ? $matches[4] : null; + $path = $matches[5]; // always present, can be empty + $query = !empty($matches[6]) ? $matches[7] : null; + $fragment = !empty($matches[8]) ? $matches[9] : null; + + // further parse authority + if ($authority !== null) { + $r_authority = "/^((.+?)@)?(\[[^\]]+\]|[^:]*)(:(\d*))?/"; + $matches = array(); + preg_match($r_authority, $authority, $matches); + $userinfo = !empty($matches[1]) ? $matches[2] : null; + $host = !empty($matches[3]) ? $matches[3] : ''; + $port = !empty($matches[4]) ? (int) $matches[5] : null; + } else { + $port = $host = $userinfo = null; + } + + return new HTMLPurifier_URI( + $scheme, $userinfo, $host, $port, $path, $query, $fragment); + } + +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme.php new file mode 100644 index 0000000..fe9e82c --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme.php @@ -0,0 +1,102 @@ +, resolves edge cases + * with making relative URIs absolute + * @type bool + */ + public $hierarchical = false; + + /** + * Whether or not the URI may omit a hostname when the scheme is + * explicitly specified, ala file:///path/to/file. As of writing, + * 'file' is the only scheme that browsers support his properly. + * @type bool + */ + public $may_omit_host = false; + + /** + * Validates the components of a URI for a specific scheme. + * @param HTMLPurifier_URI $uri Reference to a HTMLPurifier_URI object + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool success or failure + */ + abstract public function doValidate(&$uri, $config, $context); + + /** + * Public interface for validating components of a URI. Performs a + * bunch of default actions. Don't overload this method. + * @param HTMLPurifier_URI $uri Reference to a HTMLPurifier_URI object + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool success or failure + */ + public function validate(&$uri, $config, $context) + { + if ($this->default_port == $uri->port) { + $uri->port = null; + } + // kludge: browsers do funny things when the scheme but not the + // authority is set + if (!$this->may_omit_host && + // if the scheme is present, a missing host is always in error + (!is_null($uri->scheme) && ($uri->host === '' || is_null($uri->host))) || + // if the scheme is not present, a *blank* host is in error, + // since this translates into '///path' which most browsers + // interpret as being 'http://path'. + (is_null($uri->scheme) && $uri->host === '') + ) { + do { + if (is_null($uri->scheme)) { + if (substr($uri->path, 0, 2) != '//') { + $uri->host = null; + break; + } + // URI is '////path', so we cannot nullify the + // host to preserve semantics. Try expanding the + // hostname instead (fall through) + } + // first see if we can manually insert a hostname + $host = $config->get('URI.Host'); + if (!is_null($host)) { + $uri->host = $host; + } else { + // we can't do anything sensible, reject the URL. + return false; + } + } while (false); + } + return $this->doValidate($uri, $config, $context); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/data.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/data.php new file mode 100644 index 0000000..41c49d5 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/data.php @@ -0,0 +1,136 @@ + true, + 'image/gif' => true, + 'image/png' => true, + ); + // this is actually irrelevant since we only write out the path + // component + /** + * @type bool + */ + public $may_omit_host = true; + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function doValidate(&$uri, $config, $context) + { + $result = explode(',', $uri->path, 2); + $is_base64 = false; + $charset = null; + $content_type = null; + if (count($result) == 2) { + list($metadata, $data) = $result; + // do some legwork on the metadata + $metas = explode(';', $metadata); + while (!empty($metas)) { + $cur = array_shift($metas); + if ($cur == 'base64') { + $is_base64 = true; + break; + } + if (substr($cur, 0, 8) == 'charset=') { + // doesn't match if there are arbitrary spaces, but + // whatever dude + if ($charset !== null) { + continue; + } // garbage + $charset = substr($cur, 8); // not used + } else { + if ($content_type !== null) { + continue; + } // garbage + $content_type = $cur; + } + } + } else { + $data = $result[0]; + } + if ($content_type !== null && empty($this->allowed_types[$content_type])) { + return false; + } + if ($charset !== null) { + // error; we don't allow plaintext stuff + $charset = null; + } + $data = rawurldecode($data); + if ($is_base64) { + $raw_data = base64_decode($data); + } else { + $raw_data = $data; + } + if ( strlen($raw_data) < 12 ) { + // error; exif_imagetype throws exception with small files, + // and this likely indicates a corrupt URI/failed parse anyway + return false; + } + // XXX probably want to refactor this into a general mechanism + // for filtering arbitrary content types + if (function_exists('sys_get_temp_dir')) { + $file = tempnam(sys_get_temp_dir(), ""); + } else { + $file = tempnam("/tmp", ""); + } + file_put_contents($file, $raw_data); + if (function_exists('exif_imagetype')) { + $image_code = exif_imagetype($file); + unlink($file); + } elseif (function_exists('getimagesize')) { + set_error_handler(array($this, 'muteErrorHandler')); + $info = getimagesize($file); + restore_error_handler(); + unlink($file); + if ($info == false) { + return false; + } + $image_code = $info[2]; + } else { + trigger_error("could not find exif_imagetype or getimagesize functions", E_USER_ERROR); + } + $real_content_type = image_type_to_mime_type($image_code); + if ($real_content_type != $content_type) { + // we're nice guys; if the content type is something else we + // support, change it over + if (empty($this->allowed_types[$real_content_type])) { + return false; + } + $content_type = $real_content_type; + } + // ok, it's kosher, rewrite what we need + $uri->userinfo = null; + $uri->host = null; + $uri->port = null; + $uri->fragment = null; + $uri->query = null; + $uri->path = "$content_type;base64," . base64_encode($raw_data); + return true; + } + + /** + * @param int $errno + * @param string $errstr + */ + public function muteErrorHandler($errno, $errstr) + { + } +} diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/file.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/file.php new file mode 100644 index 0000000..215be4b --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/file.php @@ -0,0 +1,44 @@ +userinfo = null; + // file:// makes no provisions for accessing the resource + $uri->port = null; + // While it seems to work on Firefox, the querystring has + // no possible effect and is thus stripped. + $uri->query = null; + return true; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/ftp.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/ftp.php new file mode 100644 index 0000000..1eb43ee --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/ftp.php @@ -0,0 +1,58 @@ +query = null; + + // typecode check + $semicolon_pos = strrpos($uri->path, ';'); // reverse + if ($semicolon_pos !== false) { + $type = substr($uri->path, $semicolon_pos + 1); // no semicolon + $uri->path = substr($uri->path, 0, $semicolon_pos); + $type_ret = ''; + if (strpos($type, '=') !== false) { + // figure out whether or not the declaration is correct + list($key, $typecode) = explode('=', $type, 2); + if ($key !== 'type') { + // invalid key, tack it back on encoded + $uri->path .= '%3B' . $type; + } elseif ($typecode === 'a' || $typecode === 'i' || $typecode === 'd') { + $type_ret = ";type=$typecode"; + } + } else { + $uri->path .= '%3B' . $type; + } + $uri->path = str_replace(';', '%3B', $uri->path); + $uri->path .= $type_ret; + } + return true; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/http.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/http.php new file mode 100644 index 0000000..ce69ec4 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/http.php @@ -0,0 +1,36 @@ +userinfo = null; + return true; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/https.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/https.php new file mode 100644 index 0000000..0e96882 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/https.php @@ -0,0 +1,18 @@ +userinfo = null; + $uri->host = null; + $uri->port = null; + // we need to validate path against RFC 2368's addr-spec + return true; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/news.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/news.php new file mode 100644 index 0000000..7490927 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/news.php @@ -0,0 +1,35 @@ +userinfo = null; + $uri->host = null; + $uri->port = null; + $uri->query = null; + // typecode check needed on path + return true; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/nntp.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/nntp.php new file mode 100644 index 0000000..f211d71 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/nntp.php @@ -0,0 +1,32 @@ +userinfo = null; + $uri->query = null; + return true; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/tel.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/tel.php new file mode 100644 index 0000000..8cd1933 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/tel.php @@ -0,0 +1,46 @@ +userinfo = null; + $uri->host = null; + $uri->port = null; + + // Delete all non-numeric characters, non-x characters + // from phone number, EXCEPT for a leading plus sign. + $uri->path = preg_replace('/(?!^\+)[^\dx]/', '', + // Normalize e(x)tension to lower-case + str_replace('X', 'x', $uri->path)); + + return true; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URISchemeRegistry.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URISchemeRegistry.php new file mode 100644 index 0000000..4ac8a0b --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URISchemeRegistry.php @@ -0,0 +1,81 @@ +get('URI.AllowedSchemes'); + if (!$config->get('URI.OverrideAllowedSchemes') && + !isset($allowed_schemes[$scheme]) + ) { + return; + } + + if (isset($this->schemes[$scheme])) { + return $this->schemes[$scheme]; + } + if (!isset($allowed_schemes[$scheme])) { + return; + } + + $class = 'HTMLPurifier_URIScheme_' . $scheme; + if (!class_exists($class)) { + return; + } + $this->schemes[$scheme] = new $class(); + return $this->schemes[$scheme]; + } + + /** + * Registers a custom scheme to the cache, bypassing reflection. + * @param string $scheme Scheme name + * @param HTMLPurifier_URIScheme $scheme_obj + */ + public function register($scheme, $scheme_obj) + { + $this->schemes[$scheme] = $scheme_obj; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/UnitConverter.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/UnitConverter.php new file mode 100644 index 0000000..166f3bf --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/UnitConverter.php @@ -0,0 +1,307 @@ + array( + 'px' => 3, // This is as per CSS 2.1 and Firefox. Your mileage may vary + 'pt' => 4, + 'pc' => 48, + 'in' => 288, + self::METRIC => array('pt', '0.352777778', 'mm'), + ), + self::METRIC => array( + 'mm' => 1, + 'cm' => 10, + self::ENGLISH => array('mm', '2.83464567', 'pt'), + ), + ); + + /** + * Minimum bcmath precision for output. + * @type int + */ + protected $outputPrecision; + + /** + * Bcmath precision for internal calculations. + * @type int + */ + protected $internalPrecision; + + /** + * Whether or not BCMath is available. + * @type bool + */ + private $bcmath; + + public function __construct($output_precision = 4, $internal_precision = 10, $force_no_bcmath = false) + { + $this->outputPrecision = $output_precision; + $this->internalPrecision = $internal_precision; + $this->bcmath = !$force_no_bcmath && function_exists('bcmul'); + } + + /** + * Converts a length object of one unit into another unit. + * @param HTMLPurifier_Length $length + * Instance of HTMLPurifier_Length to convert. You must validate() + * it before passing it here! + * @param string $to_unit + * Unit to convert to. + * @return HTMLPurifier_Length|bool + * @note + * About precision: This conversion function pays very special + * attention to the incoming precision of values and attempts + * to maintain a number of significant figure. Results are + * fairly accurate up to nine digits. Some caveats: + * - If a number is zero-padded as a result of this significant + * figure tracking, the zeroes will be eliminated. + * - If a number contains less than four sigfigs ($outputPrecision) + * and this causes some decimals to be excluded, those + * decimals will be added on. + */ + public function convert($length, $to_unit) + { + if (!$length->isValid()) { + return false; + } + + $n = $length->getN(); + $unit = $length->getUnit(); + + if ($n === '0' || $unit === false) { + return new HTMLPurifier_Length('0', false); + } + + $state = $dest_state = false; + foreach (self::$units as $k => $x) { + if (isset($x[$unit])) { + $state = $k; + } + if (isset($x[$to_unit])) { + $dest_state = $k; + } + } + if (!$state || !$dest_state) { + return false; + } + + // Some calculations about the initial precision of the number; + // this will be useful when we need to do final rounding. + $sigfigs = $this->getSigFigs($n); + if ($sigfigs < $this->outputPrecision) { + $sigfigs = $this->outputPrecision; + } + + // BCMath's internal precision deals only with decimals. Use + // our default if the initial number has no decimals, or increase + // it by how ever many decimals, thus, the number of guard digits + // will always be greater than or equal to internalPrecision. + $log = (int)floor(log(abs($n), 10)); + $cp = ($log < 0) ? $this->internalPrecision - $log : $this->internalPrecision; // internal precision + + for ($i = 0; $i < 2; $i++) { + + // Determine what unit IN THIS SYSTEM we need to convert to + if ($dest_state === $state) { + // Simple conversion + $dest_unit = $to_unit; + } else { + // Convert to the smallest unit, pending a system shift + $dest_unit = self::$units[$state][$dest_state][0]; + } + + // Do the conversion if necessary + if ($dest_unit !== $unit) { + $factor = $this->div(self::$units[$state][$unit], self::$units[$state][$dest_unit], $cp); + $n = $this->mul($n, $factor, $cp); + $unit = $dest_unit; + } + + // Output was zero, so bail out early. Shouldn't ever happen. + if ($n === '') { + $n = '0'; + $unit = $to_unit; + break; + } + + // It was a simple conversion, so bail out + if ($dest_state === $state) { + break; + } + + if ($i !== 0) { + // Conversion failed! Apparently, the system we forwarded + // to didn't have this unit. This should never happen! + return false; + } + + // Pre-condition: $i == 0 + + // Perform conversion to next system of units + $n = $this->mul($n, self::$units[$state][$dest_state][1], $cp); + $unit = self::$units[$state][$dest_state][2]; + $state = $dest_state; + + // One more loop around to convert the unit in the new system. + + } + + // Post-condition: $unit == $to_unit + if ($unit !== $to_unit) { + return false; + } + + // Useful for debugging: + //echo "
        n";
        +        //echo "$n\nsigfigs = $sigfigs\nnew_log = $new_log\nlog = $log\nrp = $rp\n
        \n"; + + $n = $this->round($n, $sigfigs); + if (strpos($n, '.') !== false) { + $n = rtrim($n, '0'); + } + $n = rtrim($n, '.'); + + return new HTMLPurifier_Length($n, $unit); + } + + /** + * Returns the number of significant figures in a string number. + * @param string $n Decimal number + * @return int number of sigfigs + */ + public function getSigFigs($n) + { + $n = ltrim($n, '0+-'); + $dp = strpos($n, '.'); // decimal position + if ($dp === false) { + $sigfigs = strlen(rtrim($n, '0')); + } else { + $sigfigs = strlen(ltrim($n, '0.')); // eliminate extra decimal character + if ($dp !== 0) { + $sigfigs--; + } + } + return $sigfigs; + } + + /** + * Adds two numbers, using arbitrary precision when available. + * @param string $s1 + * @param string $s2 + * @param int $scale + * @return string + */ + private function add($s1, $s2, $scale) + { + if ($this->bcmath) { + return bcadd($s1, $s2, $scale); + } else { + return $this->scale((float)$s1 + (float)$s2, $scale); + } + } + + /** + * Multiples two numbers, using arbitrary precision when available. + * @param string $s1 + * @param string $s2 + * @param int $scale + * @return string + */ + private function mul($s1, $s2, $scale) + { + if ($this->bcmath) { + return bcmul($s1, $s2, $scale); + } else { + return $this->scale((float)$s1 * (float)$s2, $scale); + } + } + + /** + * Divides two numbers, using arbitrary precision when available. + * @param string $s1 + * @param string $s2 + * @param int $scale + * @return string + */ + private function div($s1, $s2, $scale) + { + if ($this->bcmath) { + return bcdiv($s1, $s2, $scale); + } else { + return $this->scale((float)$s1 / (float)$s2, $scale); + } + } + + /** + * Rounds a number according to the number of sigfigs it should have, + * using arbitrary precision when available. + * @param float $n + * @param int $sigfigs + * @return string + */ + private function round($n, $sigfigs) + { + $new_log = (int)floor(log(abs($n), 10)); // Number of digits left of decimal - 1 + $rp = $sigfigs - $new_log - 1; // Number of decimal places needed + $neg = $n < 0 ? '-' : ''; // Negative sign + if ($this->bcmath) { + if ($rp >= 0) { + $n = bcadd($n, $neg . '0.' . str_repeat('0', $rp) . '5', $rp + 1); + $n = bcdiv($n, '1', $rp); + } else { + // This algorithm partially depends on the standardized + // form of numbers that comes out of bcmath. + $n = bcadd($n, $neg . '5' . str_repeat('0', $new_log - $sigfigs), 0); + $n = substr($n, 0, $sigfigs + strlen($neg)) . str_repeat('0', $new_log - $sigfigs + 1); + } + return $n; + } else { + return $this->scale(round($n, $sigfigs - $new_log - 1), $rp + 1); + } + } + + /** + * Scales a float to $scale digits right of decimal point, like BCMath. + * @param float $r + * @param int $scale + * @return string + */ + private function scale($r, $scale) + { + if ($scale < 0) { + // The f sprintf type doesn't support negative numbers, so we + // need to cludge things manually. First get the string. + $r = sprintf('%.0f', (float)$r); + // Due to floating point precision loss, $r will more than likely + // look something like 4652999999999.9234. We grab one more digit + // than we need to precise from $r and then use that to round + // appropriately. + $precise = (string)round(substr($r, 0, strlen($r) + $scale), -1); + // Now we return it, truncating the zero that was rounded off. + return substr($precise, 0, -1) . str_repeat('0', -$scale + 1); + } + return sprintf('%.' . $scale . 'f', (float)$r); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser.php new file mode 100644 index 0000000..0c97c82 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser.php @@ -0,0 +1,198 @@ + self::C_STRING, + 'istring' => self::ISTRING, + 'text' => self::TEXT, + 'itext' => self::ITEXT, + 'int' => self::C_INT, + 'float' => self::C_FLOAT, + 'bool' => self::C_BOOL, + 'lookup' => self::LOOKUP, + 'list' => self::ALIST, + 'hash' => self::HASH, + 'mixed' => self::C_MIXED + ); + + /** + * Lookup table of types that are string, and can have aliases or + * allowed value lists. + */ + public static $stringTypes = array( + self::C_STRING => true, + self::ISTRING => true, + self::TEXT => true, + self::ITEXT => true, + ); + + /** + * Validate a variable according to type. + * It may return NULL as a valid type if $allow_null is true. + * + * @param mixed $var Variable to validate + * @param int $type Type of variable, see HTMLPurifier_VarParser->types + * @param bool $allow_null Whether or not to permit null as a value + * @return string Validated and type-coerced variable + * @throws HTMLPurifier_VarParserException + */ + final public function parse($var, $type, $allow_null = false) + { + if (is_string($type)) { + if (!isset(HTMLPurifier_VarParser::$types[$type])) { + throw new HTMLPurifier_VarParserException("Invalid type '$type'"); + } else { + $type = HTMLPurifier_VarParser::$types[$type]; + } + } + $var = $this->parseImplementation($var, $type, $allow_null); + if ($allow_null && $var === null) { + return null; + } + // These are basic checks, to make sure nothing horribly wrong + // happened in our implementations. + switch ($type) { + case (self::C_STRING): + case (self::ISTRING): + case (self::TEXT): + case (self::ITEXT): + if (!is_string($var)) { + break; + } + if ($type == self::ISTRING || $type == self::ITEXT) { + $var = strtolower($var); + } + return $var; + case (self::C_INT): + if (!is_int($var)) { + break; + } + return $var; + case (self::C_FLOAT): + if (!is_float($var)) { + break; + } + return $var; + case (self::C_BOOL): + if (!is_bool($var)) { + break; + } + return $var; + case (self::LOOKUP): + case (self::ALIST): + case (self::HASH): + if (!is_array($var)) { + break; + } + if ($type === self::LOOKUP) { + foreach ($var as $k) { + if ($k !== true) { + $this->error('Lookup table contains value other than true'); + } + } + } elseif ($type === self::ALIST) { + $keys = array_keys($var); + if (array_keys($keys) !== $keys) { + $this->error('Indices for list are not uniform'); + } + } + return $var; + case (self::C_MIXED): + return $var; + default: + $this->errorInconsistent(get_class($this), $type); + } + $this->errorGeneric($var, $type); + } + + /** + * Actually implements the parsing. Base implementation does not + * do anything to $var. Subclasses should overload this! + * @param mixed $var + * @param int $type + * @param bool $allow_null + * @return string + */ + protected function parseImplementation($var, $type, $allow_null) + { + return $var; + } + + /** + * Throws an exception. + * @throws HTMLPurifier_VarParserException + */ + protected function error($msg) + { + throw new HTMLPurifier_VarParserException($msg); + } + + /** + * Throws an inconsistency exception. + * @note This should not ever be called. It would be called if we + * extend the allowed values of HTMLPurifier_VarParser without + * updating subclasses. + * @param string $class + * @param int $type + * @throws HTMLPurifier_Exception + */ + protected function errorInconsistent($class, $type) + { + throw new HTMLPurifier_Exception( + "Inconsistency in $class: " . HTMLPurifier_VarParser::getTypeName($type) . + " not implemented" + ); + } + + /** + * Generic error for if a type didn't work. + * @param mixed $var + * @param int $type + */ + protected function errorGeneric($var, $type) + { + $vtype = gettype($var); + $this->error("Expected type " . HTMLPurifier_VarParser::getTypeName($type) . ", got $vtype"); + } + + /** + * @param int $type + * @return string + */ + public static function getTypeName($type) + { + static $lookup; + if (!$lookup) { + // Lazy load the alternative lookup table + $lookup = array_flip(HTMLPurifier_VarParser::$types); + } + if (!isset($lookup[$type])) { + return 'unknown'; + } + return $lookup[$type]; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Flexible.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Flexible.php new file mode 100644 index 0000000..3bfbe83 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Flexible.php @@ -0,0 +1,130 @@ + $j) { + $var[$i] = trim($j); + } + if ($type === self::HASH) { + // key:value,key2:value2 + $nvar = array(); + foreach ($var as $keypair) { + $c = explode(':', $keypair, 2); + if (!isset($c[1])) { + continue; + } + $nvar[trim($c[0])] = trim($c[1]); + } + $var = $nvar; + } + } + if (!is_array($var)) { + break; + } + $keys = array_keys($var); + if ($keys === array_keys($keys)) { + if ($type == self::ALIST) { + return $var; + } elseif ($type == self::LOOKUP) { + $new = array(); + foreach ($var as $key) { + $new[$key] = true; + } + return $new; + } else { + break; + } + } + if ($type === self::ALIST) { + trigger_error("Array list did not have consecutive integer indexes", E_USER_WARNING); + return array_values($var); + } + if ($type === self::LOOKUP) { + foreach ($var as $key => $value) { + if ($value !== true) { + trigger_error( + "Lookup array has non-true value at key '$key'; " . + "maybe your input array was not indexed numerically", + E_USER_WARNING + ); + } + $var[$key] = true; + } + } + return $var; + default: + $this->errorInconsistent(__CLASS__, $type); + } + $this->errorGeneric($var, $type); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Native.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Native.php new file mode 100644 index 0000000..f11c318 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Native.php @@ -0,0 +1,38 @@ +evalExpression($var); + } + + /** + * @param string $expr + * @return mixed + * @throws HTMLPurifier_VarParserException + */ + protected function evalExpression($expr) + { + $var = null; + $result = eval("\$var = $expr;"); + if ($result === false) { + throw new HTMLPurifier_VarParserException("Fatal error in evaluated code"); + } + return $var; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParserException.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParserException.php new file mode 100644 index 0000000..5df3414 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParserException.php @@ -0,0 +1,11 @@ +front = $front; + $this->back = $back; + } + + /** + * Creates a zipper from an array, with a hole in the + * 0-index position. + * @param Array to zipper-ify. + * @return Tuple of zipper and element of first position. + */ + static public function fromArray($array) { + $z = new self(array(), array_reverse($array)); + $t = $z->delete(); // delete the "dummy hole" + return array($z, $t); + } + + /** + * Convert zipper back into a normal array, optionally filling in + * the hole with a value. (Usually you should supply a $t, unless you + * are at the end of the array.) + */ + public function toArray($t = NULL) { + $a = $this->front; + if ($t !== NULL) $a[] = $t; + for ($i = count($this->back)-1; $i >= 0; $i--) { + $a[] = $this->back[$i]; + } + return $a; + } + + /** + * Move hole to the next element. + * @param $t Element to fill hole with + * @return Original contents of new hole. + */ + public function next($t) { + if ($t !== NULL) array_push($this->front, $t); + return empty($this->back) ? NULL : array_pop($this->back); + } + + /** + * Iterated hole advancement. + * @param $t Element to fill hole with + * @param $i How many forward to advance hole + * @return Original contents of new hole, i away + */ + public function advance($t, $n) { + for ($i = 0; $i < $n; $i++) { + $t = $this->next($t); + } + return $t; + } + + /** + * Move hole to the previous element + * @param $t Element to fill hole with + * @return Original contents of new hole. + */ + public function prev($t) { + if ($t !== NULL) array_push($this->back, $t); + return empty($this->front) ? NULL : array_pop($this->front); + } + + /** + * Delete contents of current hole, shifting hole to + * next element. + * @return Original contents of new hole. + */ + public function delete() { + return empty($this->back) ? NULL : array_pop($this->back); + } + + /** + * Returns true if we are at the end of the list. + * @return bool + */ + public function done() { + return empty($this->back); + } + + /** + * Insert element before hole. + * @param Element to insert + */ + public function insertBefore($t) { + if ($t !== NULL) array_push($this->front, $t); + } + + /** + * Insert element after hole. + * @param Element to insert + */ + public function insertAfter($t) { + if ($t !== NULL) array_push($this->back, $t); + } + + /** + * Splice in multiple elements at hole. Functional specification + * in terms of array_splice: + * + * $arr1 = $arr; + * $old1 = array_splice($arr1, $i, $delete, $replacement); + * + * list($z, $t) = HTMLPurifier_Zipper::fromArray($arr); + * $t = $z->advance($t, $i); + * list($old2, $t) = $z->splice($t, $delete, $replacement); + * $arr2 = $z->toArray($t); + * + * assert($old1 === $old2); + * assert($arr1 === $arr2); + * + * NB: the absolute index location after this operation is + * *unchanged!* + * + * @param Current contents of hole. + */ + public function splice($t, $delete, $replacement) { + // delete + $old = array(); + $r = $t; + for ($i = $delete; $i > 0; $i--) { + $old[] = $r; + $r = $this->delete(); + } + // insert + for ($i = count($replacement)-1; $i >= 0; $i--) { + $this->insertAfter($r); + $r = $replacement[$i]; + } + return array($old, $r); + } +} diff --git a/vendor/guzzlehttp/guzzle/CHANGELOG.md b/vendor/guzzlehttp/guzzle/CHANGELOG.md new file mode 100644 index 0000000..b278efc --- /dev/null +++ b/vendor/guzzlehttp/guzzle/CHANGELOG.md @@ -0,0 +1,1490 @@ +# Change Log + +Please refer to [UPGRADING](UPGRADING.md) guide for upgrading to a major version. + +## 7.4.1 - 2021-12-06 + +### Changed + +- Replaced implicit URI to string coercion [#2946](https://github.com/guzzle/guzzle/pull/2946) +- Allow `symfony/deprecation-contracts` version 3 [#2961](https://github.com/guzzle/guzzle/pull/2961) + +### Fixed + +- Only close curl handle if it's done [#2950](https://github.com/guzzle/guzzle/pull/2950) + +## 7.4.0 - 2021-10-18 + +### Added + +- Support PHP 8.1 [#2929](https://github.com/guzzle/guzzle/pull/2929), [#2939](https://github.com/guzzle/guzzle/pull/2939) +- Support `psr/log` version 2 and 3 [#2943](https://github.com/guzzle/guzzle/pull/2943) + +### Fixed + +- Make sure we always call `restore_error_handler()` [#2915](https://github.com/guzzle/guzzle/pull/2915) +- Fix progress parameter type compatibility between the cURL and stream handlers [#2936](https://github.com/guzzle/guzzle/pull/2936) +- Throw `InvalidArgumentException` when an incorrect `headers` array is provided [#2916](https://github.com/guzzle/guzzle/pull/2916), [#2942](https://github.com/guzzle/guzzle/pull/2942) + +### Changed + +- Be more strict with types [#2914](https://github.com/guzzle/guzzle/pull/2914), [#2917](https://github.com/guzzle/guzzle/pull/2917), [#2919](https://github.com/guzzle/guzzle/pull/2919), [#2945](https://github.com/guzzle/guzzle/pull/2945) + +## 7.3.0 - 2021-03-23 + +### Added + +- Support for DER and P12 certificates [#2413](https://github.com/guzzle/guzzle/pull/2413) +- Support the cURL (http://) scheme for StreamHandler proxies [#2850](https://github.com/guzzle/guzzle/pull/2850) +- Support for `guzzlehttp/psr7:^2.0` [#2878](https://github.com/guzzle/guzzle/pull/2878) + +### Fixed + +- Handle exceptions on invalid header consistently between PHP versions and handlers [#2872](https://github.com/guzzle/guzzle/pull/2872) + +## 7.2.0 - 2020-10-10 + +### Added + +- Support for PHP 8 [#2712](https://github.com/guzzle/guzzle/pull/2712), [#2715](https://github.com/guzzle/guzzle/pull/2715), [#2789](https://github.com/guzzle/guzzle/pull/2789) +- Support passing a body summarizer to the http errors middleware [#2795](https://github.com/guzzle/guzzle/pull/2795) + +### Fixed + +- Handle exceptions during response creation [#2591](https://github.com/guzzle/guzzle/pull/2591) +- Fix CURLOPT_ENCODING not to be overwritten [#2595](https://github.com/guzzle/guzzle/pull/2595) +- Make sure the Request always has a body object [#2804](https://github.com/guzzle/guzzle/pull/2804) + +### Changed + +- The `TooManyRedirectsException` has a response [#2660](https://github.com/guzzle/guzzle/pull/2660) +- Avoid "functions" from dependencies [#2712](https://github.com/guzzle/guzzle/pull/2712) + +### Deprecated + +- Using environment variable GUZZLE_CURL_SELECT_TIMEOUT [#2786](https://github.com/guzzle/guzzle/pull/2786) + +## 7.1.1 - 2020-09-30 + +### Fixed + +- Incorrect EOF detection for response body streams on Windows. + +### Changed + +- We dont connect curl `sink` on HEAD requests. +- Removed some PHP 5 workarounds + +## 7.1.0 - 2020-09-22 + +### Added + +- `GuzzleHttp\MessageFormatterInterface` + +### Fixed + +- Fixed issue that caused cookies with no value not to be stored. +- On redirects, we allow all safe methods like GET, HEAD and OPTIONS. +- Fixed logging on empty responses. +- Make sure MessageFormatter::format returns string + +### Deprecated + +- All functions in `GuzzleHttp` has been deprecated. Use static methods on `Utils` instead. +- `ClientInterface::getConfig()` +- `Client::getConfig()` +- `Client::__call()` +- `Utils::defaultCaBundle()` +- `CurlFactory::LOW_CURL_VERSION_NUMBER` + +## 7.0.1 - 2020-06-27 + +* Fix multiply defined functions fatal error [#2699](https://github.com/guzzle/guzzle/pull/2699) + +## 7.0.0 - 2020-06-27 + +No changes since 7.0.0-rc1. + +## 7.0.0-rc1 - 2020-06-15 + +### Changed + +* Use error level for logging errors in Middleware [#2629](https://github.com/guzzle/guzzle/pull/2629) +* Disabled IDN support by default and require ext-intl to use it [#2675](https://github.com/guzzle/guzzle/pull/2675) + +## 7.0.0-beta2 - 2020-05-25 + +### Added + +* Using `Utils` class instead of functions in the `GuzzleHttp` namespace. [#2546](https://github.com/guzzle/guzzle/pull/2546) +* `ClientInterface::MAJOR_VERSION` [#2583](https://github.com/guzzle/guzzle/pull/2583) + +### Changed + +* Avoid the `getenv` function when unsafe [#2531](https://github.com/guzzle/guzzle/pull/2531) +* Added real client methods [#2529](https://github.com/guzzle/guzzle/pull/2529) +* Avoid functions due to global install conflicts [#2546](https://github.com/guzzle/guzzle/pull/2546) +* Use Symfony intl-idn polyfill [#2550](https://github.com/guzzle/guzzle/pull/2550) +* Adding methods for HTTP verbs like `Client::get()`, `Client::head()`, `Client::patch()` etc [#2529](https://github.com/guzzle/guzzle/pull/2529) +* `ConnectException` extends `TransferException` [#2541](https://github.com/guzzle/guzzle/pull/2541) +* Updated the default User Agent to "GuzzleHttp/7" [#2654](https://github.com/guzzle/guzzle/pull/2654) + +### Fixed + +* Various intl icu issues [#2626](https://github.com/guzzle/guzzle/pull/2626) + +### Removed + +* Pool option `pool_size` [#2528](https://github.com/guzzle/guzzle/pull/2528) + +## 7.0.0-beta1 - 2019-12-30 + +The diff might look very big but 95% of Guzzle users will be able to upgrade without modification. +Please see [the upgrade document](UPGRADING.md) that describes all BC breaking changes. + +### Added + +* Implement PSR-18 and dropped PHP 5 support [#2421](https://github.com/guzzle/guzzle/pull/2421) [#2474](https://github.com/guzzle/guzzle/pull/2474) +* PHP 7 types [#2442](https://github.com/guzzle/guzzle/pull/2442) [#2449](https://github.com/guzzle/guzzle/pull/2449) [#2466](https://github.com/guzzle/guzzle/pull/2466) [#2497](https://github.com/guzzle/guzzle/pull/2497) [#2499](https://github.com/guzzle/guzzle/pull/2499) +* IDN support for redirects [2424](https://github.com/guzzle/guzzle/pull/2424) + +### Changed + +* Dont allow passing null as third argument to `BadResponseException::__construct()` [#2427](https://github.com/guzzle/guzzle/pull/2427) +* Use SAPI constant instead of method call [#2450](https://github.com/guzzle/guzzle/pull/2450) +* Use native function invocation [#2444](https://github.com/guzzle/guzzle/pull/2444) +* Better defaults for PHP installations with old ICU lib [2454](https://github.com/guzzle/guzzle/pull/2454) +* Added visibility to all constants [#2462](https://github.com/guzzle/guzzle/pull/2462) +* Dont allow passing `null` as URI to `Client::request()` and `Client::requestAsync()` [#2461](https://github.com/guzzle/guzzle/pull/2461) +* Widen the exception argument to throwable [#2495](https://github.com/guzzle/guzzle/pull/2495) + +### Fixed + +* Logging when Promise rejected with a string [#2311](https://github.com/guzzle/guzzle/pull/2311) + +### Removed + +* Class `SeekException` [#2162](https://github.com/guzzle/guzzle/pull/2162) +* `RequestException::getResponseBodySummary()` [#2425](https://github.com/guzzle/guzzle/pull/2425) +* `CookieJar::getCookieValue()` [#2433](https://github.com/guzzle/guzzle/pull/2433) +* `uri_template()` and `UriTemplate` [#2440](https://github.com/guzzle/guzzle/pull/2440) +* Request options `save_to` and `exceptions` [#2464](https://github.com/guzzle/guzzle/pull/2464) + +## 6.5.2 - 2019-12-23 + +* idn_to_ascii() fix for old PHP versions [#2489](https://github.com/guzzle/guzzle/pull/2489) + +## 6.5.1 - 2019-12-21 + +* Better defaults for PHP installations with old ICU lib [#2454](https://github.com/guzzle/guzzle/pull/2454) +* IDN support for redirects [#2424](https://github.com/guzzle/guzzle/pull/2424) + +## 6.5.0 - 2019-12-07 + +* Improvement: Added support for reset internal queue in MockHandler. [#2143](https://github.com/guzzle/guzzle/pull/2143) +* Improvement: Added support to pass arbitrary options to `curl_multi_init`. [#2287](https://github.com/guzzle/guzzle/pull/2287) +* Fix: Gracefully handle passing `null` to the `header` option. [#2132](https://github.com/guzzle/guzzle/pull/2132) +* Fix: `RetryMiddleware` did not do exponential delay between retires due unit mismatch. [#2132](https://github.com/guzzle/guzzle/pull/2132) +* Fix: Prevent undefined offset when using array for ssl_key options. [#2348](https://github.com/guzzle/guzzle/pull/2348) +* Deprecated `ClientInterface::VERSION` + +## 6.4.1 - 2019-10-23 + +* No `guzzle.phar` was created in 6.4.0 due expired API token. This release will fix that +* Added `parent::__construct()` to `FileCookieJar` and `SessionCookieJar` + +## 6.4.0 - 2019-10-23 + +* Improvement: Improved error messages when using curl < 7.21.2 [#2108](https://github.com/guzzle/guzzle/pull/2108) +* Fix: Test if response is readable before returning a summary in `RequestException::getResponseBodySummary()` [#2081](https://github.com/guzzle/guzzle/pull/2081) +* Fix: Add support for GUZZLE_CURL_SELECT_TIMEOUT environment variable [#2161](https://github.com/guzzle/guzzle/pull/2161) +* Improvement: Added `GuzzleHttp\Exception\InvalidArgumentException` [#2163](https://github.com/guzzle/guzzle/pull/2163) +* Improvement: Added `GuzzleHttp\_current_time()` to use `hrtime()` if that function exists. [#2242](https://github.com/guzzle/guzzle/pull/2242) +* Improvement: Added curl's `appconnect_time` in `TransferStats` [#2284](https://github.com/guzzle/guzzle/pull/2284) +* Improvement: Make GuzzleException extend Throwable wherever it's available [#2273](https://github.com/guzzle/guzzle/pull/2273) +* Fix: Prevent concurrent writes to file when saving `CookieJar` [#2335](https://github.com/guzzle/guzzle/pull/2335) +* Improvement: Update `MockHandler` so we can test transfer time [#2362](https://github.com/guzzle/guzzle/pull/2362) + +## 6.3.3 - 2018-04-22 + +* Fix: Default headers when decode_content is specified + + +## 6.3.2 - 2018-03-26 + +* Fix: Release process + + +## 6.3.1 - 2018-03-26 + +* Bug fix: Parsing 0 epoch expiry times in cookies [#2014](https://github.com/guzzle/guzzle/pull/2014) +* Improvement: Better ConnectException detection [#2012](https://github.com/guzzle/guzzle/pull/2012) +* Bug fix: Malformed domain that contains a "/" [#1999](https://github.com/guzzle/guzzle/pull/1999) +* Bug fix: Undefined offset when a cookie has no first key-value pair [#1998](https://github.com/guzzle/guzzle/pull/1998) +* Improvement: Support PHPUnit 6 [#1953](https://github.com/guzzle/guzzle/pull/1953) +* Bug fix: Support empty headers [#1915](https://github.com/guzzle/guzzle/pull/1915) +* Bug fix: Ignore case during header modifications [#1916](https://github.com/guzzle/guzzle/pull/1916) + ++ Minor code cleanups, documentation fixes and clarifications. + + +## 6.3.0 - 2017-06-22 + +* Feature: force IP resolution (ipv4 or ipv6) [#1608](https://github.com/guzzle/guzzle/pull/1608), [#1659](https://github.com/guzzle/guzzle/pull/1659) +* Improvement: Don't include summary in exception message when body is empty [#1621](https://github.com/guzzle/guzzle/pull/1621) +* Improvement: Handle `on_headers` option in MockHandler [#1580](https://github.com/guzzle/guzzle/pull/1580) +* Improvement: Added SUSE Linux CA path [#1609](https://github.com/guzzle/guzzle/issues/1609) +* Improvement: Use class reference for getting the name of the class instead of using hardcoded strings [#1641](https://github.com/guzzle/guzzle/pull/1641) +* Feature: Added `read_timeout` option [#1611](https://github.com/guzzle/guzzle/pull/1611) +* Bug fix: PHP 7.x fixes [#1685](https://github.com/guzzle/guzzle/pull/1685), [#1686](https://github.com/guzzle/guzzle/pull/1686), [#1811](https://github.com/guzzle/guzzle/pull/1811) +* Deprecation: BadResponseException instantiation without a response [#1642](https://github.com/guzzle/guzzle/pull/1642) +* Feature: Added NTLM auth [#1569](https://github.com/guzzle/guzzle/pull/1569) +* Feature: Track redirect HTTP status codes [#1711](https://github.com/guzzle/guzzle/pull/1711) +* Improvement: Check handler type during construction [#1745](https://github.com/guzzle/guzzle/pull/1745) +* Improvement: Always include the Content-Length if there's a body [#1721](https://github.com/guzzle/guzzle/pull/1721) +* Feature: Added convenience method to access a cookie by name [#1318](https://github.com/guzzle/guzzle/pull/1318) +* Bug fix: Fill `CURLOPT_CAPATH` and `CURLOPT_CAINFO` properly [#1684](https://github.com/guzzle/guzzle/pull/1684) +* Improvement: Use `\GuzzleHttp\Promise\rejection_for` function instead of object init [#1827](https://github.com/guzzle/guzzle/pull/1827) + + ++ Minor code cleanups, documentation fixes and clarifications. + +## 6.2.3 - 2017-02-28 + +* Fix deprecations with guzzle/psr7 version 1.4 + +## 6.2.2 - 2016-10-08 + +* Allow to pass nullable Response to delay callable +* Only add scheme when host is present +* Fix drain case where content-length is the literal string zero +* Obfuscate in-URL credentials in exceptions + +## 6.2.1 - 2016-07-18 + +* Address HTTP_PROXY security vulnerability, CVE-2016-5385: + https://httpoxy.org/ +* Fixing timeout bug with StreamHandler: + https://github.com/guzzle/guzzle/pull/1488 +* Only read up to `Content-Length` in PHP StreamHandler to avoid timeouts when + a server does not honor `Connection: close`. +* Ignore URI fragment when sending requests. + +## 6.2.0 - 2016-03-21 + +* Feature: added `GuzzleHttp\json_encode` and `GuzzleHttp\json_decode`. + https://github.com/guzzle/guzzle/pull/1389 +* Bug fix: Fix sleep calculation when waiting for delayed requests. + https://github.com/guzzle/guzzle/pull/1324 +* Feature: More flexible history containers. + https://github.com/guzzle/guzzle/pull/1373 +* Bug fix: defer sink stream opening in StreamHandler. + https://github.com/guzzle/guzzle/pull/1377 +* Bug fix: do not attempt to escape cookie values. + https://github.com/guzzle/guzzle/pull/1406 +* Feature: report original content encoding and length on decoded responses. + https://github.com/guzzle/guzzle/pull/1409 +* Bug fix: rewind seekable request bodies before dispatching to cURL. + https://github.com/guzzle/guzzle/pull/1422 +* Bug fix: provide an empty string to `http_build_query` for HHVM workaround. + https://github.com/guzzle/guzzle/pull/1367 + +## 6.1.1 - 2015-11-22 + +* Bug fix: Proxy::wrapSync() now correctly proxies to the appropriate handler + https://github.com/guzzle/guzzle/commit/911bcbc8b434adce64e223a6d1d14e9a8f63e4e4 +* Feature: HandlerStack is now more generic. + https://github.com/guzzle/guzzle/commit/f2102941331cda544745eedd97fc8fd46e1ee33e +* Bug fix: setting verify to false in the StreamHandler now disables peer + verification. https://github.com/guzzle/guzzle/issues/1256 +* Feature: Middleware now uses an exception factory, including more error + context. https://github.com/guzzle/guzzle/pull/1282 +* Feature: better support for disabled functions. + https://github.com/guzzle/guzzle/pull/1287 +* Bug fix: fixed regression where MockHandler was not using `sink`. + https://github.com/guzzle/guzzle/pull/1292 + +## 6.1.0 - 2015-09-08 + +* Feature: Added the `on_stats` request option to provide access to transfer + statistics for requests. https://github.com/guzzle/guzzle/pull/1202 +* Feature: Added the ability to persist session cookies in CookieJars. + https://github.com/guzzle/guzzle/pull/1195 +* Feature: Some compatibility updates for Google APP Engine + https://github.com/guzzle/guzzle/pull/1216 +* Feature: Added support for NO_PROXY to prevent the use of a proxy based on + a simple set of rules. https://github.com/guzzle/guzzle/pull/1197 +* Feature: Cookies can now contain square brackets. + https://github.com/guzzle/guzzle/pull/1237 +* Bug fix: Now correctly parsing `=` inside of quotes in Cookies. + https://github.com/guzzle/guzzle/pull/1232 +* Bug fix: Cusotm cURL options now correctly override curl options of the + same name. https://github.com/guzzle/guzzle/pull/1221 +* Bug fix: Content-Type header is now added when using an explicitly provided + multipart body. https://github.com/guzzle/guzzle/pull/1218 +* Bug fix: Now ignoring Set-Cookie headers that have no name. +* Bug fix: Reason phrase is no longer cast to an int in some cases in the + cURL handler. https://github.com/guzzle/guzzle/pull/1187 +* Bug fix: Remove the Authorization header when redirecting if the Host + header changes. https://github.com/guzzle/guzzle/pull/1207 +* Bug fix: Cookie path matching fixes + https://github.com/guzzle/guzzle/issues/1129 +* Bug fix: Fixing the cURL `body_as_string` setting + https://github.com/guzzle/guzzle/pull/1201 +* Bug fix: quotes are no longer stripped when parsing cookies. + https://github.com/guzzle/guzzle/issues/1172 +* Bug fix: `form_params` and `query` now always uses the `&` separator. + https://github.com/guzzle/guzzle/pull/1163 +* Bug fix: Adding a Content-Length to PHP stream wrapper requests if not set. + https://github.com/guzzle/guzzle/pull/1189 + +## 6.0.2 - 2015-07-04 + +* Fixed a memory leak in the curl handlers in which references to callbacks + were not being removed by `curl_reset`. +* Cookies are now extracted properly before redirects. +* Cookies now allow more character ranges. +* Decoded Content-Encoding responses are now modified to correctly reflect + their state if the encoding was automatically removed by a handler. This + means that the `Content-Encoding` header may be removed an the + `Content-Length` modified to reflect the message size after removing the + encoding. +* Added a more explicit error message when trying to use `form_params` and + `multipart` in the same request. +* Several fixes for HHVM support. +* Functions are now conditionally required using an additional level of + indirection to help with global Composer installations. + +## 6.0.1 - 2015-05-27 + +* Fixed a bug with serializing the `query` request option where the `&` + separator was missing. +* Added a better error message for when `body` is provided as an array. Please + use `form_params` or `multipart` instead. +* Various doc fixes. + +## 6.0.0 - 2015-05-26 + +* See the UPGRADING.md document for more information. +* Added `multipart` and `form_params` request options. +* Added `synchronous` request option. +* Added the `on_headers` request option. +* Fixed `expect` handling. +* No longer adding default middlewares in the client ctor. These need to be + present on the provided handler in order to work. +* Requests are no longer initiated when sending async requests with the + CurlMultiHandler. This prevents unexpected recursion from requests completing + while ticking the cURL loop. +* Removed the semantics of setting `default` to `true`. This is no longer + required now that the cURL loop is not ticked for async requests. +* Added request and response logging middleware. +* No longer allowing self signed certificates when using the StreamHandler. +* Ensuring that `sink` is valid if saving to a file. +* Request exceptions now include a "handler context" which provides handler + specific contextual information. +* Added `GuzzleHttp\RequestOptions` to allow request options to be applied + using constants. +* `$maxHandles` has been removed from CurlMultiHandler. +* `MultipartPostBody` is now part of the `guzzlehttp/psr7` package. + +## 5.3.0 - 2015-05-19 + +* Mock now supports `save_to` +* Marked `AbstractRequestEvent::getTransaction()` as public. +* Fixed a bug in which multiple headers using different casing would overwrite + previous headers in the associative array. +* Added `Utils::getDefaultHandler()` +* Marked `GuzzleHttp\Client::getDefaultUserAgent` as deprecated. +* URL scheme is now always lowercased. + +## 6.0.0-beta.1 + +* Requires PHP >= 5.5 +* Updated to use PSR-7 + * Requires immutable messages, which basically means an event based system + owned by a request instance is no longer possible. + * Utilizing the [Guzzle PSR-7 package](https://github.com/guzzle/psr7). + * Removed the dependency on `guzzlehttp/streams`. These stream abstractions + are available in the `guzzlehttp/psr7` package under the `GuzzleHttp\Psr7` + namespace. +* Added middleware and handler system + * Replaced the Guzzle event and subscriber system with a middleware system. + * No longer depends on RingPHP, but rather places the HTTP handlers directly + in Guzzle, operating on PSR-7 messages. + * Retry logic is now encapsulated in `GuzzleHttp\Middleware::retry`, which + means the `guzzlehttp/retry-subscriber` is now obsolete. + * Mocking responses is now handled using `GuzzleHttp\Handler\MockHandler`. +* Asynchronous responses + * No longer supports the `future` request option to send an async request. + Instead, use one of the `*Async` methods of a client (e.g., `requestAsync`, + `getAsync`, etc.). + * Utilizing `GuzzleHttp\Promise` instead of React's promise library to avoid + recursion required by chaining and forwarding react promises. See + https://github.com/guzzle/promises + * Added `requestAsync` and `sendAsync` to send request asynchronously. + * Added magic methods for `getAsync()`, `postAsync()`, etc. to send requests + asynchronously. +* Request options + * POST and form updates + * Added the `form_fields` and `form_files` request options. + * Removed the `GuzzleHttp\Post` namespace. + * The `body` request option no longer accepts an array for POST requests. + * The `exceptions` request option has been deprecated in favor of the + `http_errors` request options. + * The `save_to` request option has been deprecated in favor of `sink` request + option. +* Clients no longer accept an array of URI template string and variables for + URI variables. You will need to expand URI templates before passing them + into a client constructor or request method. +* Client methods `get()`, `post()`, `put()`, `patch()`, `options()`, etc. are + now magic methods that will send synchronous requests. +* Replaced `Utils.php` with plain functions in `functions.php`. +* Removed `GuzzleHttp\Collection`. +* Removed `GuzzleHttp\BatchResults`. Batched pool results are now returned as + an array. +* Removed `GuzzleHttp\Query`. Query string handling is now handled using an + associative array passed into the `query` request option. The query string + is serialized using PHP's `http_build_query`. If you need more control, you + can pass the query string in as a string. +* `GuzzleHttp\QueryParser` has been replaced with the + `GuzzleHttp\Psr7\parse_query`. + +## 5.2.0 - 2015-01-27 + +* Added `AppliesHeadersInterface` to make applying headers to a request based + on the body more generic and not specific to `PostBodyInterface`. +* Reduced the number of stack frames needed to send requests. +* Nested futures are now resolved in the client rather than the RequestFsm +* Finishing state transitions is now handled in the RequestFsm rather than the + RingBridge. +* Added a guard in the Pool class to not use recursion for request retries. + +## 5.1.0 - 2014-12-19 + +* Pool class no longer uses recursion when a request is intercepted. +* The size of a Pool can now be dynamically adjusted using a callback. + See https://github.com/guzzle/guzzle/pull/943. +* Setting a request option to `null` when creating a request with a client will + ensure that the option is not set. This allows you to overwrite default + request options on a per-request basis. + See https://github.com/guzzle/guzzle/pull/937. +* Added the ability to limit which protocols are allowed for redirects by + specifying a `protocols` array in the `allow_redirects` request option. +* Nested futures due to retries are now resolved when waiting for synchronous + responses. See https://github.com/guzzle/guzzle/pull/947. +* `"0"` is now an allowed URI path. See + https://github.com/guzzle/guzzle/pull/935. +* `Query` no longer typehints on the `$query` argument in the constructor, + allowing for strings and arrays. +* Exceptions thrown in the `end` event are now correctly wrapped with Guzzle + specific exceptions if necessary. + +## 5.0.3 - 2014-11-03 + +This change updates query strings so that they are treated as un-encoded values +by default where the value represents an un-encoded value to send over the +wire. A Query object then encodes the value before sending over the wire. This +means that even value query string values (e.g., ":") are url encoded. This +makes the Query class match PHP's http_build_query function. However, if you +want to send requests over the wire using valid query string characters that do +not need to be encoded, then you can provide a string to Url::setQuery() and +pass true as the second argument to specify that the query string is a raw +string that should not be parsed or encoded (unless a call to getQuery() is +subsequently made, forcing the query-string to be converted into a Query +object). + +## 5.0.2 - 2014-10-30 + +* Added a trailing `\r\n` to multipart/form-data payloads. See + https://github.com/guzzle/guzzle/pull/871 +* Added a `GuzzleHttp\Pool::send()` convenience method to match the docs. +* Status codes are now returned as integers. See + https://github.com/guzzle/guzzle/issues/881 +* No longer overwriting an existing `application/x-www-form-urlencoded` header + when sending POST requests, allowing for customized headers. See + https://github.com/guzzle/guzzle/issues/877 +* Improved path URL serialization. + + * No longer double percent-encoding characters in the path or query string if + they are already encoded. + * Now properly encoding the supplied path to a URL object, instead of only + encoding ' ' and '?'. + * Note: This has been changed in 5.0.3 to now encode query string values by + default unless the `rawString` argument is provided when setting the query + string on a URL: Now allowing many more characters to be present in the + query string without being percent encoded. See https://tools.ietf.org/html/rfc3986#appendix-A + +## 5.0.1 - 2014-10-16 + +Bugfix release. + +* Fixed an issue where connection errors still returned response object in + error and end events event though the response is unusable. This has been + corrected so that a response is not returned in the `getResponse` method of + these events if the response did not complete. https://github.com/guzzle/guzzle/issues/867 +* Fixed an issue where transfer statistics were not being populated in the + RingBridge. https://github.com/guzzle/guzzle/issues/866 + +## 5.0.0 - 2014-10-12 + +Adding support for non-blocking responses and some minor API cleanup. + +### New Features + +* Added support for non-blocking responses based on `guzzlehttp/guzzle-ring`. +* Added a public API for creating a default HTTP adapter. +* Updated the redirect plugin to be non-blocking so that redirects are sent + concurrently. Other plugins like this can now be updated to be non-blocking. +* Added a "progress" event so that you can get upload and download progress + events. +* Added `GuzzleHttp\Pool` which implements FutureInterface and transfers + requests concurrently using a capped pool size as efficiently as possible. +* Added `hasListeners()` to EmitterInterface. +* Removed `GuzzleHttp\ClientInterface::sendAll` and marked + `GuzzleHttp\Client::sendAll` as deprecated (it's still there, just not the + recommended way). + +### Breaking changes + +The breaking changes in this release are relatively minor. The biggest thing to +look out for is that request and response objects no longer implement fluent +interfaces. + +* Removed the fluent interfaces (i.e., `return $this`) from requests, + responses, `GuzzleHttp\Collection`, `GuzzleHttp\Url`, + `GuzzleHttp\Query`, `GuzzleHttp\Post\PostBody`, and + `GuzzleHttp\Cookie\SetCookie`. This blog post provides a good outline of + why I did this: https://ocramius.github.io/blog/fluent-interfaces-are-evil/. + This also makes the Guzzle message interfaces compatible with the current + PSR-7 message proposal. +* Removed "functions.php", so that Guzzle is truly PSR-4 compliant. Except + for the HTTP request functions from function.php, these functions are now + implemented in `GuzzleHttp\Utils` using camelCase. `GuzzleHttp\json_decode` + moved to `GuzzleHttp\Utils::jsonDecode`. `GuzzleHttp\get_path` moved to + `GuzzleHttp\Utils::getPath`. `GuzzleHttp\set_path` moved to + `GuzzleHttp\Utils::setPath`. `GuzzleHttp\batch` should now be + `GuzzleHttp\Pool::batch`, which returns an `objectStorage`. Using functions.php + caused problems for many users: they aren't PSR-4 compliant, require an + explicit include, and needed an if-guard to ensure that the functions are not + declared multiple times. +* Rewrote adapter layer. + * Removing all classes from `GuzzleHttp\Adapter`, these are now + implemented as callables that are stored in `GuzzleHttp\Ring\Client`. + * Removed the concept of "parallel adapters". Sending requests serially or + concurrently is now handled using a single adapter. + * Moved `GuzzleHttp\Adapter\Transaction` to `GuzzleHttp\Transaction`. The + Transaction object now exposes the request, response, and client as public + properties. The getters and setters have been removed. +* Removed the "headers" event. This event was only useful for changing the + body a response once the headers of the response were known. You can implement + a similar behavior in a number of ways. One example might be to use a + FnStream that has access to the transaction being sent. For example, when the + first byte is written, you could check if the response headers match your + expectations, and if so, change the actual stream body that is being + written to. +* Removed the `asArray` parameter from + `GuzzleHttp\Message\MessageInterface::getHeader`. If you want to get a header + value as an array, then use the newly added `getHeaderAsArray()` method of + `MessageInterface`. This change makes the Guzzle interfaces compatible with + the PSR-7 interfaces. +* `GuzzleHttp\Message\MessageFactory` no longer allows subclasses to add + custom request options using double-dispatch (this was an implementation + detail). Instead, you should now provide an associative array to the + constructor which is a mapping of the request option name mapping to a + function that applies the option value to a request. +* Removed the concept of "throwImmediately" from exceptions and error events. + This control mechanism was used to stop a transfer of concurrent requests + from completing. This can now be handled by throwing the exception or by + cancelling a pool of requests or each outstanding future request individually. +* Updated to "GuzzleHttp\Streams" 3.0. + * `GuzzleHttp\Stream\StreamInterface::getContents()` no longer accepts a + `maxLen` parameter. This update makes the Guzzle streams project + compatible with the current PSR-7 proposal. + * `GuzzleHttp\Stream\Stream::__construct`, + `GuzzleHttp\Stream\Stream::factory`, and + `GuzzleHttp\Stream\Utils::create` no longer accept a size in the second + argument. They now accept an associative array of options, including the + "size" key and "metadata" key which can be used to provide custom metadata. + +## 4.2.2 - 2014-09-08 + +* Fixed a memory leak in the CurlAdapter when reusing cURL handles. +* No longer using `request_fulluri` in stream adapter proxies. +* Relative redirects are now based on the last response, not the first response. + +## 4.2.1 - 2014-08-19 + +* Ensuring that the StreamAdapter does not always add a Content-Type header +* Adding automated github releases with a phar and zip + +## 4.2.0 - 2014-08-17 + +* Now merging in default options using a case-insensitive comparison. + Closes https://github.com/guzzle/guzzle/issues/767 +* Added the ability to automatically decode `Content-Encoding` response bodies + using the `decode_content` request option. This is set to `true` by default + to decode the response body if it comes over the wire with a + `Content-Encoding`. Set this value to `false` to disable decoding the + response content, and pass a string to provide a request `Accept-Encoding` + header and turn on automatic response decoding. This feature now allows you + to pass an `Accept-Encoding` header in the headers of a request but still + disable automatic response decoding. + Closes https://github.com/guzzle/guzzle/issues/764 +* Added the ability to throw an exception immediately when transferring + requests in parallel. Closes https://github.com/guzzle/guzzle/issues/760 +* Updating guzzlehttp/streams dependency to ~2.1 +* No longer utilizing the now deprecated namespaced methods from the stream + package. + +## 4.1.8 - 2014-08-14 + +* Fixed an issue in the CurlFactory that caused setting the `stream=false` + request option to throw an exception. + See: https://github.com/guzzle/guzzle/issues/769 +* TransactionIterator now calls rewind on the inner iterator. + See: https://github.com/guzzle/guzzle/pull/765 +* You can now set the `Content-Type` header to `multipart/form-data` + when creating POST requests to force multipart bodies. + See https://github.com/guzzle/guzzle/issues/768 + +## 4.1.7 - 2014-08-07 + +* Fixed an error in the HistoryPlugin that caused the same request and response + to be logged multiple times when an HTTP protocol error occurs. +* Ensuring that cURL does not add a default Content-Type when no Content-Type + has been supplied by the user. This prevents the adapter layer from modifying + the request that is sent over the wire after any listeners may have already + put the request in a desired state (e.g., signed the request). +* Throwing an exception when you attempt to send requests that have the + "stream" set to true in parallel using the MultiAdapter. +* Only calling curl_multi_select when there are active cURL handles. This was + previously changed and caused performance problems on some systems due to PHP + always selecting until the maximum select timeout. +* Fixed a bug where multipart/form-data POST fields were not correctly + aggregated (e.g., values with "&"). + +## 4.1.6 - 2014-08-03 + +* Added helper methods to make it easier to represent messages as strings, + including getting the start line and getting headers as a string. + +## 4.1.5 - 2014-08-02 + +* Automatically retrying cURL "Connection died, retrying a fresh connect" + errors when possible. +* cURL implementation cleanup +* Allowing multiple event subscriber listeners to be registered per event by + passing an array of arrays of listener configuration. + +## 4.1.4 - 2014-07-22 + +* Fixed a bug that caused multi-part POST requests with more than one field to + serialize incorrectly. +* Paths can now be set to "0" +* `ResponseInterface::xml` now accepts a `libxml_options` option and added a + missing default argument that was required when parsing XML response bodies. +* A `save_to` stream is now created lazily, which means that files are not + created on disk unless a request succeeds. + +## 4.1.3 - 2014-07-15 + +* Various fixes to multipart/form-data POST uploads +* Wrapping function.php in an if-statement to ensure Guzzle can be used + globally and in a Composer install +* Fixed an issue with generating and merging in events to an event array +* POST headers are only applied before sending a request to allow you to change + the query aggregator used before uploading +* Added much more robust query string parsing +* Fixed various parsing and normalization issues with URLs +* Fixing an issue where multi-valued headers were not being utilized correctly + in the StreamAdapter + +## 4.1.2 - 2014-06-18 + +* Added support for sending payloads with GET requests + +## 4.1.1 - 2014-06-08 + +* Fixed an issue related to using custom message factory options in subclasses +* Fixed an issue with nested form fields in a multi-part POST +* Fixed an issue with using the `json` request option for POST requests +* Added `ToArrayInterface` to `GuzzleHttp\Cookie\CookieJar` + +## 4.1.0 - 2014-05-27 + +* Added a `json` request option to easily serialize JSON payloads. +* Added a `GuzzleHttp\json_decode()` wrapper to safely parse JSON. +* Added `setPort()` and `getPort()` to `GuzzleHttp\Message\RequestInterface`. +* Added the ability to provide an emitter to a client in the client constructor. +* Added the ability to persist a cookie session using $_SESSION. +* Added a trait that can be used to add event listeners to an iterator. +* Removed request method constants from RequestInterface. +* Fixed warning when invalid request start-lines are received. +* Updated MessageFactory to work with custom request option methods. +* Updated cacert bundle to latest build. + +4.0.2 (2014-04-16) +------------------ + +* Proxy requests using the StreamAdapter now properly use request_fulluri (#632) +* Added the ability to set scalars as POST fields (#628) + +## 4.0.1 - 2014-04-04 + +* The HTTP status code of a response is now set as the exception code of + RequestException objects. +* 303 redirects will now correctly switch from POST to GET requests. +* The default parallel adapter of a client now correctly uses the MultiAdapter. +* HasDataTrait now initializes the internal data array as an empty array so + that the toArray() method always returns an array. + +## 4.0.0 - 2014-03-29 + +* For information on changes and upgrading, see: + https://github.com/guzzle/guzzle/blob/master/UPGRADING.md#3x-to-40 +* Added `GuzzleHttp\batch()` as a convenience function for sending requests in + parallel without needing to write asynchronous code. +* Restructured how events are added to `GuzzleHttp\ClientInterface::sendAll()`. + You can now pass a callable or an array of associative arrays where each + associative array contains the "fn", "priority", and "once" keys. + +## 4.0.0.rc-2 - 2014-03-25 + +* Removed `getConfig()` and `setConfig()` from clients to avoid confusion + around whether things like base_url, message_factory, etc. should be able to + be retrieved or modified. +* Added `getDefaultOption()` and `setDefaultOption()` to ClientInterface +* functions.php functions were renamed using snake_case to match PHP idioms +* Added support for `HTTP_PROXY`, `HTTPS_PROXY`, and + `GUZZLE_CURL_SELECT_TIMEOUT` environment variables +* Added the ability to specify custom `sendAll()` event priorities +* Added the ability to specify custom stream context options to the stream + adapter. +* Added a functions.php function for `get_path()` and `set_path()` +* CurlAdapter and MultiAdapter now use a callable to generate curl resources +* MockAdapter now properly reads a body and emits a `headers` event +* Updated Url class to check if a scheme and host are set before adding ":" + and "//". This allows empty Url (e.g., "") to be serialized as "". +* Parsing invalid XML no longer emits warnings +* Curl classes now properly throw AdapterExceptions +* Various performance optimizations +* Streams are created with the faster `Stream\create()` function +* Marked deprecation_proxy() as internal +* Test server is now a collection of static methods on a class + +## 4.0.0-rc.1 - 2014-03-15 + +* See https://github.com/guzzle/guzzle/blob/master/UPGRADING.md#3x-to-40 + +## 3.8.1 - 2014-01-28 + +* Bug: Always using GET requests when redirecting from a 303 response +* Bug: CURLOPT_SSL_VERIFYHOST is now correctly set to false when setting `$certificateAuthority` to false in + `Guzzle\Http\ClientInterface::setSslVerification()` +* Bug: RedirectPlugin now uses strict RFC 3986 compliance when combining a base URL with a relative URL +* Bug: The body of a request can now be set to `"0"` +* Sending PHP stream requests no longer forces `HTTP/1.0` +* Adding more information to ExceptionCollection exceptions so that users have more context, including a stack trace of + each sub-exception +* Updated the `$ref` attribute in service descriptions to merge over any existing parameters of a schema (rather than + clobbering everything). +* Merging URLs will now use the query string object from the relative URL (thus allowing custom query aggregators) +* Query strings are now parsed in a way that they do no convert empty keys with no value to have a dangling `=`. + For example `foo&bar=baz` is now correctly parsed and recognized as `foo&bar=baz` rather than `foo=&bar=baz`. +* Now properly escaping the regular expression delimiter when matching Cookie domains. +* Network access is now disabled when loading XML documents + +## 3.8.0 - 2013-12-05 + +* Added the ability to define a POST name for a file +* JSON response parsing now properly walks additionalProperties +* cURL error code 18 is now retried automatically in the BackoffPlugin +* Fixed a cURL error when URLs contain fragments +* Fixed an issue in the BackoffPlugin retry event where it was trying to access all exceptions as if they were + CurlExceptions +* CURLOPT_PROGRESS function fix for PHP 5.5 (69fcc1e) +* Added the ability for Guzzle to work with older versions of cURL that do not support `CURLOPT_TIMEOUT_MS` +* Fixed a bug that was encountered when parsing empty header parameters +* UriTemplate now has a `setRegex()` method to match the docs +* The `debug` request parameter now checks if it is truthy rather than if it exists +* Setting the `debug` request parameter to true shows verbose cURL output instead of using the LogPlugin +* Added the ability to combine URLs using strict RFC 3986 compliance +* Command objects can now return the validation errors encountered by the command +* Various fixes to cache revalidation (#437 and 29797e5) +* Various fixes to the AsyncPlugin +* Cleaned up build scripts + +## 3.7.4 - 2013-10-02 + +* Bug fix: 0 is now an allowed value in a description parameter that has a default value (#430) +* Bug fix: SchemaFormatter now returns an integer when formatting to a Unix timestamp + (see https://github.com/aws/aws-sdk-php/issues/147) +* Bug fix: Cleaned up and fixed URL dot segment removal to properly resolve internal dots +* Minimum PHP version is now properly specified as 5.3.3 (up from 5.3.2) (#420) +* Updated the bundled cacert.pem (#419) +* OauthPlugin now supports adding authentication to headers or query string (#425) + +## 3.7.3 - 2013-09-08 + +* Added the ability to get the exception associated with a request/command when using `MultiTransferException` and + `CommandTransferException`. +* Setting `additionalParameters` of a response to false is now honored when parsing responses with a service description +* Schemas are only injected into response models when explicitly configured. +* No longer guessing Content-Type based on the path of a request. Content-Type is now only guessed based on the path of + an EntityBody. +* Bug fix: ChunkedIterator can now properly chunk a \Traversable as well as an \Iterator. +* Bug fix: FilterIterator now relies on `\Iterator` instead of `\Traversable`. +* Bug fix: Gracefully handling malformed responses in RequestMediator::writeResponseBody() +* Bug fix: Replaced call to canCache with canCacheRequest in the CallbackCanCacheStrategy of the CachePlugin +* Bug fix: Visiting XML attributes first before visiting XML children when serializing requests +* Bug fix: Properly parsing headers that contain commas contained in quotes +* Bug fix: mimetype guessing based on a filename is now case-insensitive + +## 3.7.2 - 2013-08-02 + +* Bug fix: Properly URL encoding paths when using the PHP-only version of the UriTemplate expander + See https://github.com/guzzle/guzzle/issues/371 +* Bug fix: Cookie domains are now matched correctly according to RFC 6265 + See https://github.com/guzzle/guzzle/issues/377 +* Bug fix: GET parameters are now used when calculating an OAuth signature +* Bug fix: Fixed an issue with cache revalidation where the If-None-Match header was being double quoted +* `Guzzle\Common\AbstractHasDispatcher::dispatch()` now returns the event that was dispatched +* `Guzzle\Http\QueryString::factory()` now guesses the most appropriate query aggregator to used based on the input. + See https://github.com/guzzle/guzzle/issues/379 +* Added a way to add custom domain objects to service description parsing using the `operation.parse_class` event. See + https://github.com/guzzle/guzzle/pull/380 +* cURL multi cleanup and optimizations + +## 3.7.1 - 2013-07-05 + +* Bug fix: Setting default options on a client now works +* Bug fix: Setting options on HEAD requests now works. See #352 +* Bug fix: Moving stream factory before send event to before building the stream. See #353 +* Bug fix: Cookies no longer match on IP addresses per RFC 6265 +* Bug fix: Correctly parsing header parameters that are in `<>` and quotes +* Added `cert` and `ssl_key` as request options +* `Host` header can now diverge from the host part of a URL if the header is set manually +* `Guzzle\Service\Command\LocationVisitor\Request\XmlVisitor` was rewritten to change from using SimpleXML to XMLWriter +* OAuth parameters are only added via the plugin if they aren't already set +* Exceptions are now thrown when a URL cannot be parsed +* Returning `false` if `Guzzle\Http\EntityBody::getContentMd5()` fails +* Not setting a `Content-MD5` on a command if calculating the Content-MD5 fails via the CommandContentMd5Plugin + +## 3.7.0 - 2013-06-10 + +* See UPGRADING.md for more information on how to upgrade. +* Requests now support the ability to specify an array of $options when creating a request to more easily modify a + request. You can pass a 'request.options' configuration setting to a client to apply default request options to + every request created by a client (e.g. default query string variables, headers, curl options, etc.). +* Added a static facade class that allows you to use Guzzle with static methods and mount the class to `\Guzzle`. + See `Guzzle\Http\StaticClient::mount`. +* Added `command.request_options` to `Guzzle\Service\Command\AbstractCommand` to pass request options to requests + created by a command (e.g. custom headers, query string variables, timeout settings, etc.). +* Stream size in `Guzzle\Stream\PhpStreamRequestFactory` will now be set if Content-Length is returned in the + headers of a response +* Added `Guzzle\Common\Collection::setPath($path, $value)` to set a value into an array using a nested key + (e.g. `$collection->setPath('foo/baz/bar', 'test'); echo $collection['foo']['bar']['bar'];`) +* ServiceBuilders now support storing and retrieving arbitrary data +* CachePlugin can now purge all resources for a given URI +* CachePlugin can automatically purge matching cached items when a non-idempotent request is sent to a resource +* CachePlugin now uses the Vary header to determine if a resource is a cache hit +* `Guzzle\Http\Message\Response` now implements `\Serializable` +* Added `Guzzle\Cache\CacheAdapterFactory::fromCache()` to more easily create cache adapters +* `Guzzle\Service\ClientInterface::execute()` now accepts an array, single command, or Traversable +* Fixed a bug in `Guzzle\Http\Message\Header\Link::addLink()` +* Better handling of calculating the size of a stream in `Guzzle\Stream\Stream` using fstat() and caching the size +* `Guzzle\Common\Exception\ExceptionCollection` now creates a more readable exception message +* Fixing BC break: Added back the MonologLogAdapter implementation rather than extending from PsrLog so that older + Symfony users can still use the old version of Monolog. +* Fixing BC break: Added the implementation back in for `Guzzle\Http\Message\AbstractMessage::getTokenizedHeader()`. + Now triggering an E_USER_DEPRECATED warning when used. Use `$message->getHeader()->parseParams()`. +* Several performance improvements to `Guzzle\Common\Collection` +* Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`: + createRequest, head, delete, put, patch, post, options, prepareRequest +* Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()` +* Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface` +* Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to + `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a + resource, string, or EntityBody into the $options parameter to specify the download location of the response. +* Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a + default `array()` +* Added `Guzzle\Stream\StreamInterface::isRepeatable` +* Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use + $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or + $client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))`. +* Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use $client->getConfig()->getPath('request.options/headers')`. +* Removed `Guzzle\Http\ClientInterface::expandTemplate()` +* Removed `Guzzle\Http\ClientInterface::setRequestFactory()` +* Removed `Guzzle\Http\ClientInterface::getCurlMulti()` +* Removed `Guzzle\Http\Message\RequestInterface::canCache` +* Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect` +* Removed `Guzzle\Http\Message\RequestInterface::isRedirect` +* Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods. +* You can now enable E_USER_DEPRECATED warnings to see if you are using a deprecated method by setting + `Guzzle\Common\Version::$emitWarnings` to true. +* Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use + `$request->getResponseBody()->isRepeatable()` instead. +* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use + `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. +* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use + `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. +* Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead. +* Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead. +* Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated +* Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand. + These will work through Guzzle 4.0 +* Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use [request.options][params]. +* Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client. +* Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use $client->getConfig()->getPath('request.options/headers')`. +* Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. +* Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8. +* Marked `Guzzle\Common\Collection::inject()` as deprecated. +* Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest');` +* CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a + CacheStorageInterface. These two objects and interface will be removed in a future version. +* Always setting X-cache headers on cached responses +* Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin +* `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface + $request, Response $response);` +* `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);` +* `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);` +* Added `CacheStorageInterface::purge($url)` +* `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin + $plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache, + CanCacheStrategyInterface $canCache = null)` +* Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)` + +## 3.6.0 - 2013-05-29 + +* ServiceDescription now implements ToArrayInterface +* Added command.hidden_params to blacklist certain headers from being treated as additionalParameters +* Guzzle can now correctly parse incomplete URLs +* Mixed casing of headers are now forced to be a single consistent casing across all values for that header. +* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution +* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader(). +* Specific header implementations can be created for complex headers. When a message creates a header, it uses a + HeaderFactory which can map specific headers to specific header classes. There is now a Link header and + CacheControl header implementation. +* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate +* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti() +* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in + Guzzle\Http\Curl\RequestMediator +* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string. +* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface +* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders() +* Removed Guzzle\Parser\ParserRegister::get(). Use getParser() +* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser(). +* All response header helper functions return a string rather than mixing Header objects and strings inconsistently +* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc. are managed by Guzzle + directly via interfaces +* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist + but are a no-op until removed. +* Most classes that used to require a `Guzzle\Service\Command\CommandInterface` typehint now request a + `Guzzle\Service\Command\ArrayCommandInterface`. +* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response + on a request while the request is still being transferred +* The ability to case-insensitively search for header values +* Guzzle\Http\Message\Header::hasExactHeader +* Guzzle\Http\Message\Header::raw. Use getAll() +* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object + instead. +* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess +* Added the ability to cast Model objects to a string to view debug information. + +## 3.5.0 - 2013-05-13 + +* Bug: Fixed a regression so that request responses are parsed only once per oncomplete event rather than multiple times +* Bug: Better cleanup of one-time events across the board (when an event is meant to fire once, it will now remove + itself from the EventDispatcher) +* Bug: `Guzzle\Log\MessageFormatter` now properly writes "total_time" and "connect_time" values +* Bug: Cloning an EntityEnclosingRequest now clones the EntityBody too +* Bug: Fixed an undefined index error when parsing nested JSON responses with a sentAs parameter that reference a + non-existent key +* Bug: All __call() method arguments are now required (helps with mocking frameworks) +* Deprecating Response::getRequest() and now using a shallow clone of a request object to remove a circular reference + to help with refcount based garbage collection of resources created by sending a request +* Deprecating ZF1 cache and log adapters. These will be removed in the next major version. +* Deprecating `Response::getPreviousResponse()` (method signature still exists, but it's deprecated). Use the + HistoryPlugin for a history. +* Added a `responseBody` alias for the `response_body` location +* Refactored internals to no longer rely on Response::getRequest() +* HistoryPlugin can now be cast to a string +* HistoryPlugin now logs transactions rather than requests and responses to more accurately keep track of the requests + and responses that are sent over the wire +* Added `getEffectiveUrl()` and `getRedirectCount()` to Response objects + +## 3.4.3 - 2013-04-30 + +* Bug fix: Fixing bug introduced in 3.4.2 where redirect responses are duplicated on the final redirected response +* Added a check to re-extract the temp cacert bundle from the phar before sending each request + +## 3.4.2 - 2013-04-29 + +* Bug fix: Stream objects now work correctly with "a" and "a+" modes +* Bug fix: Removing `Transfer-Encoding: chunked` header when a Content-Length is present +* Bug fix: AsyncPlugin no longer forces HEAD requests +* Bug fix: DateTime timezones are now properly handled when using the service description schema formatter +* Bug fix: CachePlugin now properly handles stale-if-error directives when a request to the origin server fails +* Setting a response on a request will write to the custom request body from the response body if one is specified +* LogPlugin now writes to php://output when STDERR is undefined +* Added the ability to set multiple POST files for the same key in a single call +* application/x-www-form-urlencoded POSTs now use the utf-8 charset by default +* Added the ability to queue CurlExceptions to the MockPlugin +* Cleaned up how manual responses are queued on requests (removed "queued_response" and now using request.before_send) +* Configuration loading now allows remote files + +## 3.4.1 - 2013-04-16 + +* Large refactoring to how CurlMulti handles work. There is now a proxy that sits in front of a pool of CurlMulti + handles. This greatly simplifies the implementation, fixes a couple bugs, and provides a small performance boost. +* Exceptions are now properly grouped when sending requests in parallel +* Redirects are now properly aggregated when a multi transaction fails +* Redirects now set the response on the original object even in the event of a failure +* Bug fix: Model names are now properly set even when using $refs +* Added support for PHP 5.5's CurlFile to prevent warnings with the deprecated @ syntax +* Added support for oauth_callback in OAuth signatures +* Added support for oauth_verifier in OAuth signatures +* Added support to attempt to retrieve a command first literally, then ucfirst, the with inflection + +## 3.4.0 - 2013-04-11 + +* Bug fix: URLs are now resolved correctly based on https://tools.ietf.org/html/rfc3986#section-5.2. #289 +* Bug fix: Absolute URLs with a path in a service description will now properly override the base URL. #289 +* Bug fix: Parsing a query string with a single PHP array value will now result in an array. #263 +* Bug fix: Better normalization of the User-Agent header to prevent duplicate headers. #264. +* Bug fix: Added `number` type to service descriptions. +* Bug fix: empty parameters are removed from an OAuth signature +* Bug fix: Revalidating a cache entry prefers the Last-Modified over the Date header +* Bug fix: Fixed "array to string" error when validating a union of types in a service description +* Bug fix: Removed code that attempted to determine the size of a stream when data is written to the stream +* Bug fix: Not including an `oauth_token` if the value is null in the OauthPlugin. +* Bug fix: Now correctly aggregating successful requests and failed requests in CurlMulti when a redirect occurs. +* The new default CURLOPT_TIMEOUT setting has been increased to 150 seconds so that Guzzle works on poor connections. +* Added a feature to EntityEnclosingRequest::setBody() that will automatically set the Content-Type of the request if + the Content-Type can be determined based on the entity body or the path of the request. +* Added the ability to overwrite configuration settings in a client when grabbing a throwaway client from a builder. +* Added support for a PSR-3 LogAdapter. +* Added a `command.after_prepare` event +* Added `oauth_callback` parameter to the OauthPlugin +* Added the ability to create a custom stream class when using a stream factory +* Added a CachingEntityBody decorator +* Added support for `additionalParameters` in service descriptions to define how custom parameters are serialized. +* The bundled SSL certificate is now provided in the phar file and extracted when running Guzzle from a phar. +* You can now send any EntityEnclosingRequest with POST fields or POST files and cURL will handle creating bodies +* POST requests using a custom entity body are now treated exactly like PUT requests but with a custom cURL method. This + means that the redirect behavior of POST requests with custom bodies will not be the same as POST requests that use + POST fields or files (the latter is only used when emulating a form POST in the browser). +* Lots of cleanup to CurlHandle::factory and RequestFactory::createRequest + +## 3.3.1 - 2013-03-10 + +* Added the ability to create PHP streaming responses from HTTP requests +* Bug fix: Running any filters when parsing response headers with service descriptions +* Bug fix: OauthPlugin fixes to allow for multi-dimensional array signing, and sorting parameters before signing +* Bug fix: Removed the adding of default empty arrays and false Booleans to responses in order to be consistent across + response location visitors. +* Bug fix: Removed the possibility of creating configuration files with circular dependencies +* RequestFactory::create() now uses the key of a POST file when setting the POST file name +* Added xmlAllowEmpty to serialize an XML body even if no XML specific parameters are set + +## 3.3.0 - 2013-03-03 + +* A large number of performance optimizations have been made +* Bug fix: Added 'wb' as a valid write mode for streams +* Bug fix: `Guzzle\Http\Message\Response::json()` now allows scalar values to be returned +* Bug fix: Fixed bug in `Guzzle\Http\Message\Response` where wrapping quotes were stripped from `getEtag()` +* BC: Removed `Guzzle\Http\Utils` class +* BC: Setting a service description on a client will no longer modify the client's command factories. +* BC: Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using + the 'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io' +* BC: `Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getSteamType()` are no longer converted to + lowercase +* Operation parameter objects are now lazy loaded internally +* Added ErrorResponsePlugin that can throw errors for responses defined in service description operations' errorResponses +* Added support for instantiating responseType=class responseClass classes. Classes must implement + `Guzzle\Service\Command\ResponseClassInterface` +* Added support for additionalProperties for top-level parameters in responseType=model responseClasses. These + additional properties also support locations and can be used to parse JSON responses where the outermost part of the + JSON is an array +* Added support for nested renaming of JSON models (rename sentAs to name) +* CachePlugin + * Added support for stale-if-error so that the CachePlugin can now serve stale content from the cache on error + * Debug headers can now added to cached response in the CachePlugin + +## 3.2.0 - 2013-02-14 + +* CurlMulti is no longer reused globally. A new multi object is created per-client. This helps to isolate clients. +* URLs with no path no longer contain a "/" by default +* Guzzle\Http\QueryString does no longer manages the leading "?". This is now handled in Guzzle\Http\Url. +* BadResponseException no longer includes the full request and response message +* Adding setData() to Guzzle\Service\Description\ServiceDescriptionInterface +* Adding getResponseBody() to Guzzle\Http\Message\RequestInterface +* Various updates to classes to use ServiceDescriptionInterface type hints rather than ServiceDescription +* Header values can now be normalized into distinct values when multiple headers are combined with a comma separated list +* xmlEncoding can now be customized for the XML declaration of a XML service description operation +* Guzzle\Http\QueryString now uses Guzzle\Http\QueryAggregator\QueryAggregatorInterface objects to add custom value + aggregation and no longer uses callbacks +* The URL encoding implementation of Guzzle\Http\QueryString can now be customized +* Bug fix: Filters were not always invoked for array service description parameters +* Bug fix: Redirects now use a target response body rather than a temporary response body +* Bug fix: The default exponential backoff BackoffPlugin was not giving when the request threshold was exceeded +* Bug fix: Guzzle now takes the first found value when grabbing Cache-Control directives + +## 3.1.2 - 2013-01-27 + +* Refactored how operation responses are parsed. Visitors now include a before() method responsible for parsing the + response body. For example, the XmlVisitor now parses the XML response into an array in the before() method. +* Fixed an issue where cURL would not automatically decompress responses when the Accept-Encoding header was sent +* CURLOPT_SSL_VERIFYHOST is never set to 1 because it is deprecated (see 5e0ff2ef20f839e19d1eeb298f90ba3598784444) +* Fixed a bug where redirect responses were not chained correctly using getPreviousResponse() +* Setting default headers on a client after setting the user-agent will not erase the user-agent setting + +## 3.1.1 - 2013-01-20 + +* Adding wildcard support to Guzzle\Common\Collection::getPath() +* Adding alias support to ServiceBuilder configs +* Adding Guzzle\Service\Resource\CompositeResourceIteratorFactory and cleaning up factory interface + +## 3.1.0 - 2013-01-12 + +* BC: CurlException now extends from RequestException rather than BadResponseException +* BC: Renamed Guzzle\Plugin\Cache\CanCacheStrategyInterface::canCache() to canCacheRequest() and added CanCacheResponse() +* Added getData to ServiceDescriptionInterface +* Added context array to RequestInterface::setState() +* Bug: Removing hard dependency on the BackoffPlugin from Guzzle\Http +* Bug: Adding required content-type when JSON request visitor adds JSON to a command +* Bug: Fixing the serialization of a service description with custom data +* Made it easier to deal with exceptions thrown when transferring commands or requests in parallel by providing + an array of successful and failed responses +* Moved getPath from Guzzle\Service\Resource\Model to Guzzle\Common\Collection +* Added Guzzle\Http\IoEmittingEntityBody +* Moved command filtration from validators to location visitors +* Added `extends` attributes to service description parameters +* Added getModels to ServiceDescriptionInterface + +## 3.0.7 - 2012-12-19 + +* Fixing phar detection when forcing a cacert to system if null or true +* Allowing filename to be passed to `Guzzle\Http\Message\Request::setResponseBody()` +* Cleaning up `Guzzle\Common\Collection::inject` method +* Adding a response_body location to service descriptions + +## 3.0.6 - 2012-12-09 + +* CurlMulti performance improvements +* Adding setErrorResponses() to Operation +* composer.json tweaks + +## 3.0.5 - 2012-11-18 + +* Bug: Fixing an infinite recursion bug caused from revalidating with the CachePlugin +* Bug: Response body can now be a string containing "0" +* Bug: Using Guzzle inside of a phar uses system by default but now allows for a custom cacert +* Bug: QueryString::fromString now properly parses query string parameters that contain equal signs +* Added support for XML attributes in service description responses +* DefaultRequestSerializer now supports array URI parameter values for URI template expansion +* Added better mimetype guessing to requests and post files + +## 3.0.4 - 2012-11-11 + +* Bug: Fixed a bug when adding multiple cookies to a request to use the correct glue value +* Bug: Cookies can now be added that have a name, domain, or value set to "0" +* Bug: Using the system cacert bundle when using the Phar +* Added json and xml methods to Response to make it easier to parse JSON and XML response data into data structures +* Enhanced cookie jar de-duplication +* Added the ability to enable strict cookie jars that throw exceptions when invalid cookies are added +* Added setStream to StreamInterface to actually make it possible to implement custom rewind behavior for entity bodies +* Added the ability to create any sort of hash for a stream rather than just an MD5 hash + +## 3.0.3 - 2012-11-04 + +* Implementing redirects in PHP rather than cURL +* Added PECL URI template extension and using as default parser if available +* Bug: Fixed Content-Length parsing of Response factory +* Adding rewind() method to entity bodies and streams. Allows for custom rewinding of non-repeatable streams. +* Adding ToArrayInterface throughout library +* Fixing OauthPlugin to create unique nonce values per request + +## 3.0.2 - 2012-10-25 + +* Magic methods are enabled by default on clients +* Magic methods return the result of a command +* Service clients no longer require a base_url option in the factory +* Bug: Fixed an issue with URI templates where null template variables were being expanded + +## 3.0.1 - 2012-10-22 + +* Models can now be used like regular collection objects by calling filter, map, etc. +* Models no longer require a Parameter structure or initial data in the constructor +* Added a custom AppendIterator to get around a PHP bug with the `\AppendIterator` + +## 3.0.0 - 2012-10-15 + +* Rewrote service description format to be based on Swagger + * Now based on JSON schema + * Added nested input structures and nested response models + * Support for JSON and XML input and output models + * Renamed `commands` to `operations` + * Removed dot class notation + * Removed custom types +* Broke the project into smaller top-level namespaces to be more component friendly +* Removed support for XML configs and descriptions. Use arrays or JSON files. +* Removed the Validation component and Inspector +* Moved all cookie code to Guzzle\Plugin\Cookie +* Magic methods on a Guzzle\Service\Client now return the command un-executed. +* Calling getResult() or getResponse() on a command will lazily execute the command if needed. +* Now shipping with cURL's CA certs and using it by default +* Added previousResponse() method to response objects +* No longer sending Accept and Accept-Encoding headers on every request +* Only sending an Expect header by default when a payload is greater than 1MB +* Added/moved client options: + * curl.blacklist to curl.option.blacklist + * Added ssl.certificate_authority +* Added a Guzzle\Iterator component +* Moved plugins from Guzzle\Http\Plugin to Guzzle\Plugin +* Added a more robust backoff retry strategy (replaced the ExponentialBackoffPlugin) +* Added a more robust caching plugin +* Added setBody to response objects +* Updating LogPlugin to use a more flexible MessageFormatter +* Added a completely revamped build process +* Cleaning up Collection class and removing default values from the get method +* Fixed ZF2 cache adapters + +## 2.8.8 - 2012-10-15 + +* Bug: Fixed a cookie issue that caused dot prefixed domains to not match where popular browsers did + +## 2.8.7 - 2012-09-30 + +* Bug: Fixed config file aliases for JSON includes +* Bug: Fixed cookie bug on a request object by using CookieParser to parse cookies on requests +* Bug: Removing the path to a file when sending a Content-Disposition header on a POST upload +* Bug: Hardening request and response parsing to account for missing parts +* Bug: Fixed PEAR packaging +* Bug: Fixed Request::getInfo +* Bug: Fixed cases where CURLM_CALL_MULTI_PERFORM return codes were causing curl transactions to fail +* Adding the ability for the namespace Iterator factory to look in multiple directories +* Added more getters/setters/removers from service descriptions +* Added the ability to remove POST fields from OAuth signatures +* OAuth plugin now supports 2-legged OAuth + +## 2.8.6 - 2012-09-05 + +* Added the ability to modify and build service descriptions +* Added the use of visitors to apply parameters to locations in service descriptions using the dynamic command +* Added a `json` parameter location +* Now allowing dot notation for classes in the CacheAdapterFactory +* Using the union of two arrays rather than an array_merge when extending service builder services and service params +* Ensuring that a service is a string before doing strpos() checks on it when substituting services for references + in service builder config files. +* Services defined in two different config files that include one another will by default replace the previously + defined service, but you can now create services that extend themselves and merge their settings over the previous +* The JsonLoader now supports aliasing filenames with different filenames. This allows you to alias something like + '_default' with a default JSON configuration file. + +## 2.8.5 - 2012-08-29 + +* Bug: Suppressed empty arrays from URI templates +* Bug: Added the missing $options argument from ServiceDescription::factory to enable caching +* Added support for HTTP responses that do not contain a reason phrase in the start-line +* AbstractCommand commands are now invokable +* Added a way to get the data used when signing an Oauth request before a request is sent + +## 2.8.4 - 2012-08-15 + +* Bug: Custom delay time calculations are no longer ignored in the ExponentialBackoffPlugin +* Added the ability to transfer entity bodies as a string rather than streamed. This gets around curl error 65. Set `body_as_string` in a request's curl options to enable. +* Added a StreamInterface, EntityBodyInterface, and added ftell() to Guzzle\Common\Stream +* Added an AbstractEntityBodyDecorator and a ReadLimitEntityBody decorator to transfer only a subset of a decorated stream +* Stream and EntityBody objects will now return the file position to the previous position after a read required operation (e.g. getContentMd5()) +* Added additional response status codes +* Removed SSL information from the default User-Agent header +* DELETE requests can now send an entity body +* Added an EventDispatcher to the ExponentialBackoffPlugin and added an ExponentialBackoffLogger to log backoff retries +* Added the ability of the MockPlugin to consume mocked request bodies +* LogPlugin now exposes request and response objects in the extras array + +## 2.8.3 - 2012-07-30 + +* Bug: Fixed a case where empty POST requests were sent as GET requests +* Bug: Fixed a bug in ExponentialBackoffPlugin that caused fatal errors when retrying an EntityEnclosingRequest that does not have a body +* Bug: Setting the response body of a request to null after completing a request, not when setting the state of a request to new +* Added multiple inheritance to service description commands +* Added an ApiCommandInterface and added `getParamNames()` and `hasParam()` +* Removed the default 2mb size cutoff from the Md5ValidatorPlugin so that it now defaults to validating everything +* Changed CurlMulti::perform to pass a smaller timeout to CurlMulti::executeHandles + +## 2.8.2 - 2012-07-24 + +* Bug: Query string values set to 0 are no longer dropped from the query string +* Bug: A Collection object is no longer created each time a call is made to `Guzzle\Service\Command\AbstractCommand::getRequestHeaders()` +* Bug: `+` is now treated as an encoded space when parsing query strings +* QueryString and Collection performance improvements +* Allowing dot notation for class paths in filters attribute of a service descriptions + +## 2.8.1 - 2012-07-16 + +* Loosening Event Dispatcher dependency +* POST redirects can now be customized using CURLOPT_POSTREDIR + +## 2.8.0 - 2012-07-15 + +* BC: Guzzle\Http\Query + * Query strings with empty variables will always show an equal sign unless the variable is set to QueryString::BLANK (e.g. ?acl= vs ?acl) + * Changed isEncodingValues() and isEncodingFields() to isUrlEncoding() + * Changed setEncodeValues(bool) and setEncodeFields(bool) to useUrlEncoding(bool) + * Changed the aggregation functions of QueryString to be static methods + * Can now use fromString() with querystrings that have a leading ? +* cURL configuration values can be specified in service descriptions using `curl.` prefixed parameters +* Content-Length is set to 0 before emitting the request.before_send event when sending an empty request body +* Cookies are no longer URL decoded by default +* Bug: URI template variables set to null are no longer expanded + +## 2.7.2 - 2012-07-02 + +* BC: Moving things to get ready for subtree splits. Moving Inflection into Common. Moving Guzzle\Http\Parser to Guzzle\Parser. +* BC: Removing Guzzle\Common\Batch\Batch::count() and replacing it with isEmpty() +* CachePlugin now allows for a custom request parameter function to check if a request can be cached +* Bug fix: CachePlugin now only caches GET and HEAD requests by default +* Bug fix: Using header glue when transferring headers over the wire +* Allowing deeply nested arrays for composite variables in URI templates +* Batch divisors can now return iterators or arrays + +## 2.7.1 - 2012-06-26 + +* Minor patch to update version number in UA string +* Updating build process + +## 2.7.0 - 2012-06-25 + +* BC: Inflection classes moved to Guzzle\Inflection. No longer static methods. Can now inject custom inflectors into classes. +* BC: Removed magic setX methods from commands +* BC: Magic methods mapped to service description commands are now inflected in the command factory rather than the client __call() method +* Verbose cURL options are no longer enabled by default. Set curl.debug to true on a client to enable. +* Bug: Now allowing colons in a response start-line (e.g. HTTP/1.1 503 Service Unavailable: Back-end server is at capacity) +* Guzzle\Service\Resource\ResourceIteratorApplyBatched now internally uses the Guzzle\Common\Batch namespace +* Added Guzzle\Service\Plugin namespace and a PluginCollectionPlugin +* Added the ability to set POST fields and files in a service description +* Guzzle\Http\EntityBody::factory() now accepts objects with a __toString() method +* Adding a command.before_prepare event to clients +* Added BatchClosureTransfer and BatchClosureDivisor +* BatchTransferException now includes references to the batch divisor and transfer strategies +* Fixed some tests so that they pass more reliably +* Added Guzzle\Common\Log\ArrayLogAdapter + +## 2.6.6 - 2012-06-10 + +* BC: Removing Guzzle\Http\Plugin\BatchQueuePlugin +* BC: Removing Guzzle\Service\Command\CommandSet +* Adding generic batching system (replaces the batch queue plugin and command set) +* Updating ZF cache and log adapters and now using ZF's composer repository +* Bug: Setting the name of each ApiParam when creating through an ApiCommand +* Adding result_type, result_doc, deprecated, and doc_url to service descriptions +* Bug: Changed the default cookie header casing back to 'Cookie' + +## 2.6.5 - 2012-06-03 + +* BC: Renaming Guzzle\Http\Message\RequestInterface::getResourceUri() to getResource() +* BC: Removing unused AUTH_BASIC and AUTH_DIGEST constants from +* BC: Guzzle\Http\Cookie is now used to manage Set-Cookie data, not Cookie data +* BC: Renaming methods in the CookieJarInterface +* Moving almost all cookie logic out of the CookiePlugin and into the Cookie or CookieJar implementations +* Making the default glue for HTTP headers ';' instead of ',' +* Adding a removeValue to Guzzle\Http\Message\Header +* Adding getCookies() to request interface. +* Making it easier to add event subscribers to HasDispatcherInterface classes. Can now directly call addSubscriber() + +## 2.6.4 - 2012-05-30 + +* BC: Cleaning up how POST files are stored in EntityEnclosingRequest objects. Adding PostFile class. +* BC: Moving ApiCommand specific functionality from the Inspector and on to the ApiCommand +* Bug: Fixing magic method command calls on clients +* Bug: Email constraint only validates strings +* Bug: Aggregate POST fields when POST files are present in curl handle +* Bug: Fixing default User-Agent header +* Bug: Only appending or prepending parameters in commands if they are specified +* Bug: Not requiring response reason phrases or status codes to match a predefined list of codes +* Allowing the use of dot notation for class namespaces when using instance_of constraint +* Added any_match validation constraint +* Added an AsyncPlugin +* Passing request object to the calculateWait method of the ExponentialBackoffPlugin +* Allowing the result of a command object to be changed +* Parsing location and type sub values when instantiating a service description rather than over and over at runtime + +## 2.6.3 - 2012-05-23 + +* [BC] Guzzle\Common\FromConfigInterface no longer requires any config options. +* [BC] Refactoring how POST files are stored on an EntityEnclosingRequest. They are now separate from POST fields. +* You can now use an array of data when creating PUT request bodies in the request factory. +* Removing the requirement that HTTPS requests needed a Cache-Control: public directive to be cacheable. +* [Http] Adding support for Content-Type in multipart POST uploads per upload +* [Http] Added support for uploading multiple files using the same name (foo[0], foo[1]) +* Adding more POST data operations for easier manipulation of POST data. +* You can now set empty POST fields. +* The body of a request is only shown on EntityEnclosingRequest objects that do not use POST files. +* Split the Guzzle\Service\Inspector::validateConfig method into two methods. One to initialize when a command is created, and one to validate. +* CS updates + +## 2.6.2 - 2012-05-19 + +* [Http] Better handling of nested scope requests in CurlMulti. Requests are now always prepares in the send() method rather than the addRequest() method. + +## 2.6.1 - 2012-05-19 + +* [BC] Removing 'path' support in service descriptions. Use 'uri'. +* [BC] Guzzle\Service\Inspector::parseDocBlock is now protected. Adding getApiParamsForClass() with cache. +* [BC] Removing Guzzle\Common\NullObject. Use https://github.com/mtdowling/NullObject if you need it. +* [BC] Removing Guzzle\Common\XmlElement. +* All commands, both dynamic and concrete, have ApiCommand objects. +* Adding a fix for CurlMulti so that if all of the connections encounter some sort of curl error, then the loop exits. +* Adding checks to EntityEnclosingRequest so that empty POST files and fields are ignored. +* Making the method signature of Guzzle\Service\Builder\ServiceBuilder::factory more flexible. + +## 2.6.0 - 2012-05-15 + +* [BC] Moving Guzzle\Service\Builder to Guzzle\Service\Builder\ServiceBuilder +* [BC] Executing a Command returns the result of the command rather than the command +* [BC] Moving all HTTP parsing logic to Guzzle\Http\Parsers. Allows for faster C implementations if needed. +* [BC] Changing the Guzzle\Http\Message\Response::setProtocol() method to accept a protocol and version in separate args. +* [BC] Moving ResourceIterator* to Guzzle\Service\Resource +* [BC] Completely refactored ResourceIterators to iterate over a cloned command object +* [BC] Moved Guzzle\Http\UriTemplate to Guzzle\Http\Parser\UriTemplate\UriTemplate +* [BC] Guzzle\Guzzle is now deprecated +* Moving Guzzle\Common\Guzzle::inject to Guzzle\Common\Collection::inject +* Adding Guzzle\Version class to give version information about Guzzle +* Adding Guzzle\Http\Utils class to provide getDefaultUserAgent() and getHttpDate() +* Adding Guzzle\Curl\CurlVersion to manage caching curl_version() data +* ServiceDescription and ServiceBuilder are now cacheable using similar configs +* Changing the format of XML and JSON service builder configs. Backwards compatible. +* Cleaned up Cookie parsing +* Trimming the default Guzzle User-Agent header +* Adding a setOnComplete() method to Commands that is called when a command completes +* Keeping track of requests that were mocked in the MockPlugin +* Fixed a caching bug in the CacheAdapterFactory +* Inspector objects can be injected into a Command object +* Refactoring a lot of code and tests to be case insensitive when dealing with headers +* Adding Guzzle\Http\Message\HeaderComparison for easy comparison of HTTP headers using a DSL +* Adding the ability to set global option overrides to service builder configs +* Adding the ability to include other service builder config files from within XML and JSON files +* Moving the parseQuery method out of Url and on to QueryString::fromString() as a static factory method. + +## 2.5.0 - 2012-05-08 + +* Major performance improvements +* [BC] Simplifying Guzzle\Common\Collection. Please check to see if you are using features that are now deprecated. +* [BC] Using a custom validation system that allows a flyweight implementation for much faster validation. No longer using Symfony2 Validation component. +* [BC] No longer supporting "{{ }}" for injecting into command or UriTemplates. Use "{}" +* Added the ability to passed parameters to all requests created by a client +* Added callback functionality to the ExponentialBackoffPlugin +* Using microtime in ExponentialBackoffPlugin to allow more granular backoff strategies. +* Rewinding request stream bodies when retrying requests +* Exception is thrown when JSON response body cannot be decoded +* Added configurable magic method calls to clients and commands. This is off by default. +* Fixed a defect that added a hash to every parsed URL part +* Fixed duplicate none generation for OauthPlugin. +* Emitting an event each time a client is generated by a ServiceBuilder +* Using an ApiParams object instead of a Collection for parameters of an ApiCommand +* cache.* request parameters should be renamed to params.cache.* +* Added the ability to set arbitrary curl options on requests (disable_wire, progress, etc.). See CurlHandle. +* Added the ability to disable type validation of service descriptions +* ServiceDescriptions and ServiceBuilders are now Serializable diff --git a/vendor/guzzlehttp/guzzle/LICENSE b/vendor/guzzlehttp/guzzle/LICENSE new file mode 100644 index 0000000..fd2375d --- /dev/null +++ b/vendor/guzzlehttp/guzzle/LICENSE @@ -0,0 +1,27 @@ +The MIT License (MIT) + +Copyright (c) 2011 Michael Dowling +Copyright (c) 2012 Jeremy Lindblom +Copyright (c) 2014 Graham Campbell +Copyright (c) 2015 Márk Sági-Kazár +Copyright (c) 2015 Tobias Schultze +Copyright (c) 2016 Tobias Nyholm +Copyright (c) 2016 George Mponos + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/guzzlehttp/guzzle/README.md b/vendor/guzzlehttp/guzzle/README.md new file mode 100644 index 0000000..0025aa7 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/README.md @@ -0,0 +1,94 @@ +![Guzzle](.github/logo.png?raw=true) + +# Guzzle, PHP HTTP client + +[![Latest Version](https://img.shields.io/github/release/guzzle/guzzle.svg?style=flat-square)](https://github.com/guzzle/guzzle/releases) +[![Build Status](https://img.shields.io/github/workflow/status/guzzle/guzzle/CI?label=ci%20build&style=flat-square)](https://github.com/guzzle/guzzle/actions?query=workflow%3ACI) +[![Total Downloads](https://img.shields.io/packagist/dt/guzzlehttp/guzzle.svg?style=flat-square)](https://packagist.org/packages/guzzlehttp/guzzle) + +Guzzle is a PHP HTTP client that makes it easy to send HTTP requests and +trivial to integrate with web services. + +- Simple interface for building query strings, POST requests, streaming large + uploads, streaming large downloads, using HTTP cookies, uploading JSON data, + etc... +- Can send both synchronous and asynchronous requests using the same interface. +- Uses PSR-7 interfaces for requests, responses, and streams. This allows you + to utilize other PSR-7 compatible libraries with Guzzle. +- Supports PSR-18 allowing interoperability between other PSR-18 HTTP Clients. +- Abstracts away the underlying HTTP transport, allowing you to write + environment and transport agnostic code; i.e., no hard dependency on cURL, + PHP streams, sockets, or non-blocking event loops. +- Middleware system allows you to augment and compose client behavior. + +```php +$client = new \GuzzleHttp\Client(); +$response = $client->request('GET', 'https://api.github.com/repos/guzzle/guzzle'); + +echo $response->getStatusCode(); // 200 +echo $response->getHeaderLine('content-type'); // 'application/json; charset=utf8' +echo $response->getBody(); // '{"id": 1420053, "name": "guzzle", ...}' + +// Send an asynchronous request. +$request = new \GuzzleHttp\Psr7\Request('GET', 'http://httpbin.org'); +$promise = $client->sendAsync($request)->then(function ($response) { + echo 'I completed! ' . $response->getBody(); +}); + +$promise->wait(); +``` + +## Help and docs + +We use GitHub issues only to discuss bugs and new features. For support please refer to: + +- [Documentation](http://guzzlephp.org/) +- [Stack Overflow](http://stackoverflow.com/questions/tagged/guzzle) +- [#guzzle](https://app.slack.com/client/T0D2S9JCT/CE6UAAKL4) channel on [PHP-HTTP Slack](http://slack.httplug.io/) +- [Gitter](https://gitter.im/guzzle/guzzle) + + +## Installing Guzzle + +The recommended way to install Guzzle is through +[Composer](https://getcomposer.org/). + +```bash +composer require guzzlehttp/guzzle +``` + + +## Version Guidance + +| Version | Status | Packagist | Namespace | Repo | Docs | PSR-7 | PHP Version | +|---------|------------|---------------------|--------------|---------------------|---------------------|-------|-------------| +| 3.x | EOL | `guzzle/guzzle` | `Guzzle` | [v3][guzzle-3-repo] | [v3][guzzle-3-docs] | No | >= 5.3.3 | +| 4.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v4][guzzle-4-repo] | N/A | No | >= 5.4 | +| 5.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v5][guzzle-5-repo] | [v5][guzzle-5-docs] | No | >= 5.4 | +| 6.x | Security fixes | `guzzlehttp/guzzle` | `GuzzleHttp` | [v6][guzzle-6-repo] | [v6][guzzle-6-docs] | Yes | >= 5.5 | +| 7.x | Latest | `guzzlehttp/guzzle` | `GuzzleHttp` | [v7][guzzle-7-repo] | [v7][guzzle-7-docs] | Yes | >= 7.2 | + +[guzzle-3-repo]: https://github.com/guzzle/guzzle3 +[guzzle-4-repo]: https://github.com/guzzle/guzzle/tree/4.x +[guzzle-5-repo]: https://github.com/guzzle/guzzle/tree/5.3 +[guzzle-6-repo]: https://github.com/guzzle/guzzle/tree/6.5 +[guzzle-7-repo]: https://github.com/guzzle/guzzle +[guzzle-3-docs]: http://guzzle3.readthedocs.org +[guzzle-5-docs]: http://docs.guzzlephp.org/en/5.3/ +[guzzle-6-docs]: http://docs.guzzlephp.org/en/6.5/ +[guzzle-7-docs]: http://docs.guzzlephp.org/en/latest/ + + +## Security + +If you discover a security vulnerability within this package, please send an email to security@tidelift.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced. Please see [Security Policy](https://github.com/guzzle/guzzle/security/policy) for more information. + +## License + +Guzzle is made available under the MIT License (MIT). Please see [License File](LICENSE) for more information. + +## For Enterprise + +Available as part of the Tidelift Subscription + +The maintainers of Guzzle and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-guzzlehttp-guzzle?utm_source=packagist-guzzlehttp-guzzle&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) diff --git a/vendor/guzzlehttp/guzzle/UPGRADING.md b/vendor/guzzlehttp/guzzle/UPGRADING.md new file mode 100644 index 0000000..45417a7 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/UPGRADING.md @@ -0,0 +1,1253 @@ +Guzzle Upgrade Guide +==================== + +6.0 to 7.0 +---------- + +In order to take advantage of the new features of PHP, Guzzle dropped the support +of PHP 5. The minimum supported PHP version is now PHP 7.2. Type hints and return +types for functions and methods have been added wherever possible. + +Please make sure: +- You are calling a function or a method with the correct type. +- If you extend a class of Guzzle; update all signatures on methods you override. + +#### Other backwards compatibility breaking changes + +- Class `GuzzleHttp\UriTemplate` is removed. +- Class `GuzzleHttp\Exception\SeekException` is removed. +- Classes `GuzzleHttp\Exception\BadResponseException`, `GuzzleHttp\Exception\ClientException`, + `GuzzleHttp\Exception\ServerException` can no longer be initialized with an empty + Response as argument. +- Class `GuzzleHttp\Exception\ConnectException` now extends `GuzzleHttp\Exception\TransferException` + instead of `GuzzleHttp\Exception\RequestException`. +- Function `GuzzleHttp\Exception\ConnectException::getResponse()` is removed. +- Function `GuzzleHttp\Exception\ConnectException::hasResponse()` is removed. +- Constant `GuzzleHttp\ClientInterface::VERSION` is removed. Added `GuzzleHttp\ClientInterface::MAJOR_VERSION` instead. +- Function `GuzzleHttp\Exception\RequestException::getResponseBodySummary` is removed. + Use `\GuzzleHttp\Psr7\get_message_body_summary` as an alternative. +- Function `GuzzleHttp\Cookie\CookieJar::getCookieValue` is removed. +- Request option `exception` is removed. Please use `http_errors`. +- Request option `save_to` is removed. Please use `sink`. +- Pool option `pool_size` is removed. Please use `concurrency`. +- We now look for environment variables in the `$_SERVER` super global, due to thread safety issues with `getenv`. We continue to fallback to `getenv` in CLI environments, for maximum compatibility. +- The `get`, `head`, `put`, `post`, `patch`, `delete`, `getAsync`, `headAsync`, `putAsync`, `postAsync`, `patchAsync`, and `deleteAsync` methods are now implemented as genuine methods on `GuzzleHttp\Client`, with strong typing. The original `__call` implementation remains unchanged for now, for maximum backwards compatibility, but won't be invoked under normal operation. +- The `log` middleware will log the errors with level `error` instead of `notice` +- Support for international domain names (IDN) is now disabled by default, and enabling it requires installing ext-intl, linked against a modern version of the C library (ICU 4.6 or higher). + +#### Native functions calls + +All internal native functions calls of Guzzle are now prefixed with a slash. This +change makes it impossible for method overloading by other libraries or applications. +Example: + +```php +// Before: +curl_version(); + +// After: +\curl_version(); +``` + +For the full diff you can check [here](https://github.com/guzzle/guzzle/compare/6.5.4..master). + +5.0 to 6.0 +---------- + +Guzzle now uses [PSR-7](https://www.php-fig.org/psr/psr-7/) for HTTP messages. +Due to the fact that these messages are immutable, this prompted a refactoring +of Guzzle to use a middleware based system rather than an event system. Any +HTTP message interaction (e.g., `GuzzleHttp\Message\Request`) need to be +updated to work with the new immutable PSR-7 request and response objects. Any +event listeners or subscribers need to be updated to become middleware +functions that wrap handlers (or are injected into a +`GuzzleHttp\HandlerStack`). + +- Removed `GuzzleHttp\BatchResults` +- Removed `GuzzleHttp\Collection` +- Removed `GuzzleHttp\HasDataTrait` +- Removed `GuzzleHttp\ToArrayInterface` +- The `guzzlehttp/streams` dependency has been removed. Stream functionality + is now present in the `GuzzleHttp\Psr7` namespace provided by the + `guzzlehttp/psr7` package. +- Guzzle no longer uses ReactPHP promises and now uses the + `guzzlehttp/promises` library. We use a custom promise library for three + significant reasons: + 1. React promises (at the time of writing this) are recursive. Promise + chaining and promise resolution will eventually blow the stack. Guzzle + promises are not recursive as they use a sort of trampolining technique. + Note: there has been movement in the React project to modify promises to + no longer utilize recursion. + 2. Guzzle needs to have the ability to synchronously block on a promise to + wait for a result. Guzzle promises allows this functionality (and does + not require the use of recursion). + 3. Because we need to be able to wait on a result, doing so using React + promises requires wrapping react promises with RingPHP futures. This + overhead is no longer needed, reducing stack sizes, reducing complexity, + and improving performance. +- `GuzzleHttp\Mimetypes` has been moved to a function in + `GuzzleHttp\Psr7\mimetype_from_extension` and + `GuzzleHttp\Psr7\mimetype_from_filename`. +- `GuzzleHttp\Query` and `GuzzleHttp\QueryParser` have been removed. Query + strings must now be passed into request objects as strings, or provided to + the `query` request option when creating requests with clients. The `query` + option uses PHP's `http_build_query` to convert an array to a string. If you + need a different serialization technique, you will need to pass the query + string in as a string. There are a couple helper functions that will make + working with query strings easier: `GuzzleHttp\Psr7\parse_query` and + `GuzzleHttp\Psr7\build_query`. +- Guzzle no longer has a dependency on RingPHP. Due to the use of a middleware + system based on PSR-7, using RingPHP and it's middleware system as well adds + more complexity than the benefits it provides. All HTTP handlers that were + present in RingPHP have been modified to work directly with PSR-7 messages + and placed in the `GuzzleHttp\Handler` namespace. This significantly reduces + complexity in Guzzle, removes a dependency, and improves performance. RingPHP + will be maintained for Guzzle 5 support, but will no longer be a part of + Guzzle 6. +- As Guzzle now uses a middleware based systems the event system and RingPHP + integration has been removed. Note: while the event system has been removed, + it is possible to add your own type of event system that is powered by the + middleware system. + - Removed the `Event` namespace. + - Removed the `Subscriber` namespace. + - Removed `Transaction` class + - Removed `RequestFsm` + - Removed `RingBridge` + - `GuzzleHttp\Subscriber\Cookie` is now provided by + `GuzzleHttp\Middleware::cookies` + - `GuzzleHttp\Subscriber\HttpError` is now provided by + `GuzzleHttp\Middleware::httpError` + - `GuzzleHttp\Subscriber\History` is now provided by + `GuzzleHttp\Middleware::history` + - `GuzzleHttp\Subscriber\Mock` is now provided by + `GuzzleHttp\Handler\MockHandler` + - `GuzzleHttp\Subscriber\Prepare` is now provided by + `GuzzleHttp\PrepareBodyMiddleware` + - `GuzzleHttp\Subscriber\Redirect` is now provided by + `GuzzleHttp\RedirectMiddleware` +- Guzzle now uses `Psr\Http\Message\UriInterface` (implements in + `GuzzleHttp\Psr7\Uri`) for URI support. `GuzzleHttp\Url` is now gone. +- Static functions in `GuzzleHttp\Utils` have been moved to namespaced + functions under the `GuzzleHttp` namespace. This requires either a Composer + based autoloader or you to include functions.php. +- `GuzzleHttp\ClientInterface::getDefaultOption` has been renamed to + `GuzzleHttp\ClientInterface::getConfig`. +- `GuzzleHttp\ClientInterface::setDefaultOption` has been removed. +- The `json` and `xml` methods of response objects has been removed. With the + migration to strictly adhering to PSR-7 as the interface for Guzzle messages, + adding methods to message interfaces would actually require Guzzle messages + to extend from PSR-7 messages rather then work with them directly. + +## Migrating to middleware + +The change to PSR-7 unfortunately required significant refactoring to Guzzle +due to the fact that PSR-7 messages are immutable. Guzzle 5 relied on an event +system from plugins. The event system relied on mutability of HTTP messages and +side effects in order to work. With immutable messages, you have to change your +workflow to become more about either returning a value (e.g., functional +middlewares) or setting a value on an object. Guzzle v6 has chosen the +functional middleware approach. + +Instead of using the event system to listen for things like the `before` event, +you now create a stack based middleware function that intercepts a request on +the way in and the promise of the response on the way out. This is a much +simpler and more predictable approach than the event system and works nicely +with PSR-7 middleware. Due to the use of promises, the middleware system is +also asynchronous. + +v5: + +```php +use GuzzleHttp\Event\BeforeEvent; +$client = new GuzzleHttp\Client(); +// Get the emitter and listen to the before event. +$client->getEmitter()->on('before', function (BeforeEvent $e) { + // Guzzle v5 events relied on mutation + $e->getRequest()->setHeader('X-Foo', 'Bar'); +}); +``` + +v6: + +In v6, you can modify the request before it is sent using the `mapRequest` +middleware. The idiomatic way in v6 to modify the request/response lifecycle is +to setup a handler middleware stack up front and inject the handler into a +client. + +```php +use GuzzleHttp\Middleware; +// Create a handler stack that has all of the default middlewares attached +$handler = GuzzleHttp\HandlerStack::create(); +// Push the handler onto the handler stack +$handler->push(Middleware::mapRequest(function (RequestInterface $request) { + // Notice that we have to return a request object + return $request->withHeader('X-Foo', 'Bar'); +})); +// Inject the handler into the client +$client = new GuzzleHttp\Client(['handler' => $handler]); +``` + +## POST Requests + +This version added the [`form_params`](http://guzzle.readthedocs.org/en/latest/request-options.html#form_params) +and `multipart` request options. `form_params` is an associative array of +strings or array of strings and is used to serialize an +`application/x-www-form-urlencoded` POST request. The +[`multipart`](http://guzzle.readthedocs.org/en/latest/request-options.html#multipart) +option is now used to send a multipart/form-data POST request. + +`GuzzleHttp\Post\PostFile` has been removed. Use the `multipart` option to add +POST files to a multipart/form-data request. + +The `body` option no longer accepts an array to send POST requests. Please use +`multipart` or `form_params` instead. + +The `base_url` option has been renamed to `base_uri`. + +4.x to 5.0 +---------- + +## Rewritten Adapter Layer + +Guzzle now uses [RingPHP](http://ringphp.readthedocs.org/en/latest) to send +HTTP requests. The `adapter` option in a `GuzzleHttp\Client` constructor +is still supported, but it has now been renamed to `handler`. Instead of +passing a `GuzzleHttp\Adapter\AdapterInterface`, you must now pass a PHP +`callable` that follows the RingPHP specification. + +## Removed Fluent Interfaces + +[Fluent interfaces were removed](https://ocramius.github.io/blog/fluent-interfaces-are-evil/) +from the following classes: + +- `GuzzleHttp\Collection` +- `GuzzleHttp\Url` +- `GuzzleHttp\Query` +- `GuzzleHttp\Post\PostBody` +- `GuzzleHttp\Cookie\SetCookie` + +## Removed functions.php + +Removed "functions.php", so that Guzzle is truly PSR-4 compliant. The following +functions can be used as replacements. + +- `GuzzleHttp\json_decode` -> `GuzzleHttp\Utils::jsonDecode` +- `GuzzleHttp\get_path` -> `GuzzleHttp\Utils::getPath` +- `GuzzleHttp\Utils::setPath` -> `GuzzleHttp\set_path` +- `GuzzleHttp\Pool::batch` -> `GuzzleHttp\batch`. This function is, however, + deprecated in favor of using `GuzzleHttp\Pool::batch()`. + +The "procedural" global client has been removed with no replacement (e.g., +`GuzzleHttp\get()`, `GuzzleHttp\post()`, etc.). Use a `GuzzleHttp\Client` +object as a replacement. + +## `throwImmediately` has been removed + +The concept of "throwImmediately" has been removed from exceptions and error +events. This control mechanism was used to stop a transfer of concurrent +requests from completing. This can now be handled by throwing the exception or +by cancelling a pool of requests or each outstanding future request +individually. + +## headers event has been removed + +Removed the "headers" event. This event was only useful for changing the +body a response once the headers of the response were known. You can implement +a similar behavior in a number of ways. One example might be to use a +FnStream that has access to the transaction being sent. For example, when the +first byte is written, you could check if the response headers match your +expectations, and if so, change the actual stream body that is being +written to. + +## Updates to HTTP Messages + +Removed the `asArray` parameter from +`GuzzleHttp\Message\MessageInterface::getHeader`. If you want to get a header +value as an array, then use the newly added `getHeaderAsArray()` method of +`MessageInterface`. This change makes the Guzzle interfaces compatible with +the PSR-7 interfaces. + +3.x to 4.0 +---------- + +## Overarching changes: + +- Now requires PHP 5.4 or greater. +- No longer requires cURL to send requests. +- Guzzle no longer wraps every exception it throws. Only exceptions that are + recoverable are now wrapped by Guzzle. +- Various namespaces have been removed or renamed. +- No longer requiring the Symfony EventDispatcher. A custom event dispatcher + based on the Symfony EventDispatcher is + now utilized in `GuzzleHttp\Event\EmitterInterface` (resulting in significant + speed and functionality improvements). + +Changes per Guzzle 3.x namespace are described below. + +## Batch + +The `Guzzle\Batch` namespace has been removed. This is best left to +third-parties to implement on top of Guzzle's core HTTP library. + +## Cache + +The `Guzzle\Cache` namespace has been removed. (Todo: No suitable replacement +has been implemented yet, but hoping to utilize a PSR cache interface). + +## Common + +- Removed all of the wrapped exceptions. It's better to use the standard PHP + library for unrecoverable exceptions. +- `FromConfigInterface` has been removed. +- `Guzzle\Common\Version` has been removed. The VERSION constant can be found + at `GuzzleHttp\ClientInterface::VERSION`. + +### Collection + +- `getAll` has been removed. Use `toArray` to convert a collection to an array. +- `inject` has been removed. +- `keySearch` has been removed. +- `getPath` no longer supports wildcard expressions. Use something better like + JMESPath for this. +- `setPath` now supports appending to an existing array via the `[]` notation. + +### Events + +Guzzle no longer requires Symfony's EventDispatcher component. Guzzle now uses +`GuzzleHttp\Event\Emitter`. + +- `Symfony\Component\EventDispatcher\EventDispatcherInterface` is replaced by + `GuzzleHttp\Event\EmitterInterface`. +- `Symfony\Component\EventDispatcher\EventDispatcher` is replaced by + `GuzzleHttp\Event\Emitter`. +- `Symfony\Component\EventDispatcher\Event` is replaced by + `GuzzleHttp\Event\Event`, and Guzzle now has an EventInterface in + `GuzzleHttp\Event\EventInterface`. +- `AbstractHasDispatcher` has moved to a trait, `HasEmitterTrait`, and + `HasDispatcherInterface` has moved to `HasEmitterInterface`. Retrieving the + event emitter of a request, client, etc. now uses the `getEmitter` method + rather than the `getDispatcher` method. + +#### Emitter + +- Use the `once()` method to add a listener that automatically removes itself + the first time it is invoked. +- Use the `listeners()` method to retrieve a list of event listeners rather than + the `getListeners()` method. +- Use `emit()` instead of `dispatch()` to emit an event from an emitter. +- Use `attach()` instead of `addSubscriber()` and `detach()` instead of + `removeSubscriber()`. + +```php +$mock = new Mock(); +// 3.x +$request->getEventDispatcher()->addSubscriber($mock); +$request->getEventDispatcher()->removeSubscriber($mock); +// 4.x +$request->getEmitter()->attach($mock); +$request->getEmitter()->detach($mock); +``` + +Use the `on()` method to add a listener rather than the `addListener()` method. + +```php +// 3.x +$request->getEventDispatcher()->addListener('foo', function (Event $event) { /* ... */ } ); +// 4.x +$request->getEmitter()->on('foo', function (Event $event, $name) { /* ... */ } ); +``` + +## Http + +### General changes + +- The cacert.pem certificate has been moved to `src/cacert.pem`. +- Added the concept of adapters that are used to transfer requests over the + wire. +- Simplified the event system. +- Sending requests in parallel is still possible, but batching is no longer a + concept of the HTTP layer. Instead, you must use the `complete` and `error` + events to asynchronously manage parallel request transfers. +- `Guzzle\Http\Url` has moved to `GuzzleHttp\Url`. +- `Guzzle\Http\QueryString` has moved to `GuzzleHttp\Query`. +- QueryAggregators have been rewritten so that they are simply callable + functions. +- `GuzzleHttp\StaticClient` has been removed. Use the functions provided in + `functions.php` for an easy to use static client instance. +- Exceptions in `GuzzleHttp\Exception` have been updated to all extend from + `GuzzleHttp\Exception\TransferException`. + +### Client + +Calling methods like `get()`, `post()`, `head()`, etc. no longer create and +return a request, but rather creates a request, sends the request, and returns +the response. + +```php +// 3.0 +$request = $client->get('/'); +$response = $request->send(); + +// 4.0 +$response = $client->get('/'); + +// or, to mirror the previous behavior +$request = $client->createRequest('GET', '/'); +$response = $client->send($request); +``` + +`GuzzleHttp\ClientInterface` has changed. + +- The `send` method no longer accepts more than one request. Use `sendAll` to + send multiple requests in parallel. +- `setUserAgent()` has been removed. Use a default request option instead. You + could, for example, do something like: + `$client->setConfig('defaults/headers/User-Agent', 'Foo/Bar ' . $client::getDefaultUserAgent())`. +- `setSslVerification()` has been removed. Use default request options instead, + like `$client->setConfig('defaults/verify', true)`. + +`GuzzleHttp\Client` has changed. + +- The constructor now accepts only an associative array. You can include a + `base_url` string or array to use a URI template as the base URL of a client. + You can also specify a `defaults` key that is an associative array of default + request options. You can pass an `adapter` to use a custom adapter, + `batch_adapter` to use a custom adapter for sending requests in parallel, or + a `message_factory` to change the factory used to create HTTP requests and + responses. +- The client no longer emits a `client.create_request` event. +- Creating requests with a client no longer automatically utilize a URI + template. You must pass an array into a creational method (e.g., + `createRequest`, `get`, `put`, etc.) in order to expand a URI template. + +### Messages + +Messages no longer have references to their counterparts (i.e., a request no +longer has a reference to it's response, and a response no loger has a +reference to its request). This association is now managed through a +`GuzzleHttp\Adapter\TransactionInterface` object. You can get references to +these transaction objects using request events that are emitted over the +lifecycle of a request. + +#### Requests with a body + +- `GuzzleHttp\Message\EntityEnclosingRequest` and + `GuzzleHttp\Message\EntityEnclosingRequestInterface` have been removed. The + separation between requests that contain a body and requests that do not + contain a body has been removed, and now `GuzzleHttp\Message\RequestInterface` + handles both use cases. +- Any method that previously accepts a `GuzzleHttp\Response` object now accept a + `GuzzleHttp\Message\ResponseInterface`. +- `GuzzleHttp\Message\RequestFactoryInterface` has been renamed to + `GuzzleHttp\Message\MessageFactoryInterface`. This interface is used to create + both requests and responses and is implemented in + `GuzzleHttp\Message\MessageFactory`. +- POST field and file methods have been removed from the request object. You + must now use the methods made available to `GuzzleHttp\Post\PostBodyInterface` + to control the format of a POST body. Requests that are created using a + standard `GuzzleHttp\Message\MessageFactoryInterface` will automatically use + a `GuzzleHttp\Post\PostBody` body if the body was passed as an array or if + the method is POST and no body is provided. + +```php +$request = $client->createRequest('POST', '/'); +$request->getBody()->setField('foo', 'bar'); +$request->getBody()->addFile(new PostFile('file_key', fopen('/path/to/content', 'r'))); +``` + +#### Headers + +- `GuzzleHttp\Message\Header` has been removed. Header values are now simply + represented by an array of values or as a string. Header values are returned + as a string by default when retrieving a header value from a message. You can + pass an optional argument of `true` to retrieve a header value as an array + of strings instead of a single concatenated string. +- `GuzzleHttp\PostFile` and `GuzzleHttp\PostFileInterface` have been moved to + `GuzzleHttp\Post`. This interface has been simplified and now allows the + addition of arbitrary headers. +- Custom headers like `GuzzleHttp\Message\Header\Link` have been removed. Most + of the custom headers are now handled separately in specific + subscribers/plugins, and `GuzzleHttp\Message\HeaderValues::parseParams()` has + been updated to properly handle headers that contain parameters (like the + `Link` header). + +#### Responses + +- `GuzzleHttp\Message\Response::getInfo()` and + `GuzzleHttp\Message\Response::setInfo()` have been removed. Use the event + system to retrieve this type of information. +- `GuzzleHttp\Message\Response::getRawHeaders()` has been removed. +- `GuzzleHttp\Message\Response::getMessage()` has been removed. +- `GuzzleHttp\Message\Response::calculateAge()` and other cache specific + methods have moved to the CacheSubscriber. +- Header specific helper functions like `getContentMd5()` have been removed. + Just use `getHeader('Content-MD5')` instead. +- `GuzzleHttp\Message\Response::setRequest()` and + `GuzzleHttp\Message\Response::getRequest()` have been removed. Use the event + system to work with request and response objects as a transaction. +- `GuzzleHttp\Message\Response::getRedirectCount()` has been removed. Use the + Redirect subscriber instead. +- `GuzzleHttp\Message\Response::isSuccessful()` and other related methods have + been removed. Use `getStatusCode()` instead. + +#### Streaming responses + +Streaming requests can now be created by a client directly, returning a +`GuzzleHttp\Message\ResponseInterface` object that contains a body stream +referencing an open PHP HTTP stream. + +```php +// 3.0 +use Guzzle\Stream\PhpStreamRequestFactory; +$request = $client->get('/'); +$factory = new PhpStreamRequestFactory(); +$stream = $factory->fromRequest($request); +$data = $stream->read(1024); + +// 4.0 +$response = $client->get('/', ['stream' => true]); +// Read some data off of the stream in the response body +$data = $response->getBody()->read(1024); +``` + +#### Redirects + +The `configureRedirects()` method has been removed in favor of a +`allow_redirects` request option. + +```php +// Standard redirects with a default of a max of 5 redirects +$request = $client->createRequest('GET', '/', ['allow_redirects' => true]); + +// Strict redirects with a custom number of redirects +$request = $client->createRequest('GET', '/', [ + 'allow_redirects' => ['max' => 5, 'strict' => true] +]); +``` + +#### EntityBody + +EntityBody interfaces and classes have been removed or moved to +`GuzzleHttp\Stream`. All classes and interfaces that once required +`GuzzleHttp\EntityBodyInterface` now require +`GuzzleHttp\Stream\StreamInterface`. Creating a new body for a request no +longer uses `GuzzleHttp\EntityBody::factory` but now uses +`GuzzleHttp\Stream\Stream::factory` or even better: +`GuzzleHttp\Stream\create()`. + +- `Guzzle\Http\EntityBodyInterface` is now `GuzzleHttp\Stream\StreamInterface` +- `Guzzle\Http\EntityBody` is now `GuzzleHttp\Stream\Stream` +- `Guzzle\Http\CachingEntityBody` is now `GuzzleHttp\Stream\CachingStream` +- `Guzzle\Http\ReadLimitEntityBody` is now `GuzzleHttp\Stream\LimitStream` +- `Guzzle\Http\IoEmittyinEntityBody` has been removed. + +#### Request lifecycle events + +Requests previously submitted a large number of requests. The number of events +emitted over the lifecycle of a request has been significantly reduced to make +it easier to understand how to extend the behavior of a request. All events +emitted during the lifecycle of a request now emit a custom +`GuzzleHttp\Event\EventInterface` object that contains context providing +methods and a way in which to modify the transaction at that specific point in +time (e.g., intercept the request and set a response on the transaction). + +- `request.before_send` has been renamed to `before` and now emits a + `GuzzleHttp\Event\BeforeEvent` +- `request.complete` has been renamed to `complete` and now emits a + `GuzzleHttp\Event\CompleteEvent`. +- `request.sent` has been removed. Use `complete`. +- `request.success` has been removed. Use `complete`. +- `error` is now an event that emits a `GuzzleHttp\Event\ErrorEvent`. +- `request.exception` has been removed. Use `error`. +- `request.receive.status_line` has been removed. +- `curl.callback.progress` has been removed. Use a custom `StreamInterface` to + maintain a status update. +- `curl.callback.write` has been removed. Use a custom `StreamInterface` to + intercept writes. +- `curl.callback.read` has been removed. Use a custom `StreamInterface` to + intercept reads. + +`headers` is a new event that is emitted after the response headers of a +request have been received before the body of the response is downloaded. This +event emits a `GuzzleHttp\Event\HeadersEvent`. + +You can intercept a request and inject a response using the `intercept()` event +of a `GuzzleHttp\Event\BeforeEvent`, `GuzzleHttp\Event\CompleteEvent`, and +`GuzzleHttp\Event\ErrorEvent` event. + +See: http://docs.guzzlephp.org/en/latest/events.html + +## Inflection + +The `Guzzle\Inflection` namespace has been removed. This is not a core concern +of Guzzle. + +## Iterator + +The `Guzzle\Iterator` namespace has been removed. + +- `Guzzle\Iterator\AppendIterator`, `Guzzle\Iterator\ChunkedIterator`, and + `Guzzle\Iterator\MethodProxyIterator` are nice, but not a core requirement of + Guzzle itself. +- `Guzzle\Iterator\FilterIterator` is no longer needed because an equivalent + class is shipped with PHP 5.4. +- `Guzzle\Iterator\MapIterator` is not really needed when using PHP 5.5 because + it's easier to just wrap an iterator in a generator that maps values. + +For a replacement of these iterators, see https://github.com/nikic/iter + +## Log + +The LogPlugin has moved to https://github.com/guzzle/log-subscriber. The +`Guzzle\Log` namespace has been removed. Guzzle now relies on +`Psr\Log\LoggerInterface` for all logging. The MessageFormatter class has been +moved to `GuzzleHttp\Subscriber\Log\Formatter`. + +## Parser + +The `Guzzle\Parser` namespace has been removed. This was previously used to +make it possible to plug in custom parsers for cookies, messages, URI +templates, and URLs; however, this level of complexity is not needed in Guzzle +so it has been removed. + +- Cookie: Cookie parsing logic has been moved to + `GuzzleHttp\Cookie\SetCookie::fromString`. +- Message: Message parsing logic for both requests and responses has been moved + to `GuzzleHttp\Message\MessageFactory::fromMessage`. Message parsing is only + used in debugging or deserializing messages, so it doesn't make sense for + Guzzle as a library to add this level of complexity to parsing messages. +- UriTemplate: URI template parsing has been moved to + `GuzzleHttp\UriTemplate`. The Guzzle library will automatically use the PECL + URI template library if it is installed. +- Url: URL parsing is now performed in `GuzzleHttp\Url::fromString` (previously + it was `Guzzle\Http\Url::factory()`). If custom URL parsing is necessary, + then developers are free to subclass `GuzzleHttp\Url`. + +## Plugin + +The `Guzzle\Plugin` namespace has been renamed to `GuzzleHttp\Subscriber`. +Several plugins are shipping with the core Guzzle library under this namespace. + +- `GuzzleHttp\Subscriber\Cookie`: Replaces the old CookiePlugin. Cookie jar + code has moved to `GuzzleHttp\Cookie`. +- `GuzzleHttp\Subscriber\History`: Replaces the old HistoryPlugin. +- `GuzzleHttp\Subscriber\HttpError`: Throws errors when a bad HTTP response is + received. +- `GuzzleHttp\Subscriber\Mock`: Replaces the old MockPlugin. +- `GuzzleHttp\Subscriber\Prepare`: Prepares the body of a request just before + sending. This subscriber is attached to all requests by default. +- `GuzzleHttp\Subscriber\Redirect`: Replaces the RedirectPlugin. + +The following plugins have been removed (third-parties are free to re-implement +these if needed): + +- `GuzzleHttp\Plugin\Async` has been removed. +- `GuzzleHttp\Plugin\CurlAuth` has been removed. +- `GuzzleHttp\Plugin\ErrorResponse\ErrorResponsePlugin` has been removed. This + functionality should instead be implemented with event listeners that occur + after normal response parsing occurs in the guzzle/command package. + +The following plugins are not part of the core Guzzle package, but are provided +in separate repositories: + +- `Guzzle\Http\Plugin\BackoffPlugin` has been rewritten to be much simpler + to build custom retry policies using simple functions rather than various + chained classes. See: https://github.com/guzzle/retry-subscriber +- `Guzzle\Http\Plugin\Cache\CachePlugin` has moved to + https://github.com/guzzle/cache-subscriber +- `Guzzle\Http\Plugin\Log\LogPlugin` has moved to + https://github.com/guzzle/log-subscriber +- `Guzzle\Http\Plugin\Md5\Md5Plugin` has moved to + https://github.com/guzzle/message-integrity-subscriber +- `Guzzle\Http\Plugin\Mock\MockPlugin` has moved to + `GuzzleHttp\Subscriber\MockSubscriber`. +- `Guzzle\Http\Plugin\Oauth\OauthPlugin` has moved to + https://github.com/guzzle/oauth-subscriber + +## Service + +The service description layer of Guzzle has moved into two separate packages: + +- http://github.com/guzzle/command Provides a high level abstraction over web + services by representing web service operations using commands. +- http://github.com/guzzle/guzzle-services Provides an implementation of + guzzle/command that provides request serialization and response parsing using + Guzzle service descriptions. + +## Stream + +Stream have moved to a separate package available at +https://github.com/guzzle/streams. + +`Guzzle\Stream\StreamInterface` has been given a large update to cleanly take +on the responsibilities of `Guzzle\Http\EntityBody` and +`Guzzle\Http\EntityBodyInterface` now that they have been removed. The number +of methods implemented by the `StreamInterface` has been drastically reduced to +allow developers to more easily extend and decorate stream behavior. + +## Removed methods from StreamInterface + +- `getStream` and `setStream` have been removed to better encapsulate streams. +- `getMetadata` and `setMetadata` have been removed in favor of + `GuzzleHttp\Stream\MetadataStreamInterface`. +- `getWrapper`, `getWrapperData`, `getStreamType`, and `getUri` have all been + removed. This data is accessible when + using streams that implement `GuzzleHttp\Stream\MetadataStreamInterface`. +- `rewind` has been removed. Use `seek(0)` for a similar behavior. + +## Renamed methods + +- `detachStream` has been renamed to `detach`. +- `feof` has been renamed to `eof`. +- `ftell` has been renamed to `tell`. +- `readLine` has moved from an instance method to a static class method of + `GuzzleHttp\Stream\Stream`. + +## Metadata streams + +`GuzzleHttp\Stream\MetadataStreamInterface` has been added to denote streams +that contain additional metadata accessible via `getMetadata()`. +`GuzzleHttp\Stream\StreamInterface::getMetadata` and +`GuzzleHttp\Stream\StreamInterface::setMetadata` have been removed. + +## StreamRequestFactory + +The entire concept of the StreamRequestFactory has been removed. The way this +was used in Guzzle 3 broke the actual interface of sending streaming requests +(instead of getting back a Response, you got a StreamInterface). Streaming +PHP requests are now implemented through the `GuzzleHttp\Adapter\StreamAdapter`. + +3.6 to 3.7 +---------- + +### Deprecations + +- You can now enable E_USER_DEPRECATED warnings to see if you are using any deprecated methods.: + +```php +\Guzzle\Common\Version::$emitWarnings = true; +``` + +The following APIs and options have been marked as deprecated: + +- Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use `$request->getResponseBody()->isRepeatable()` instead. +- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. +- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. +- Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead. +- Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead. +- Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated +- Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client. +- Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8. +- Marked `Guzzle\Common\Collection::inject()` as deprecated. +- Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use + `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));` or + `$client->setDefaultOption('auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));` + +3.7 introduces `request.options` as a parameter for a client configuration and as an optional argument to all creational +request methods. When paired with a client's configuration settings, these options allow you to specify default settings +for various aspects of a request. Because these options make other previous configuration options redundant, several +configuration options and methods of a client and AbstractCommand have been deprecated. + +- Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use `$client->getDefaultOption('headers')`. +- Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use `$client->setDefaultOption('headers/{header_name}', 'value')`. +- Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use `$client->setDefaultOption('params/{param_name}', 'value')` +- Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand. These will work through Guzzle 4.0 + + $command = $client->getCommand('foo', array( + 'command.headers' => array('Test' => '123'), + 'command.response_body' => '/path/to/file' + )); + + // Should be changed to: + + $command = $client->getCommand('foo', array( + 'command.request_options' => array( + 'headers' => array('Test' => '123'), + 'save_as' => '/path/to/file' + ) + )); + +### Interface changes + +Additions and changes (you will need to update any implementations or subclasses you may have created): + +- Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`: + createRequest, head, delete, put, patch, post, options, prepareRequest +- Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()` +- Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface` +- Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to + `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a + resource, string, or EntityBody into the $options parameter to specify the download location of the response. +- Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a + default `array()` +- Added `Guzzle\Stream\StreamInterface::isRepeatable` +- Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods. + +The following methods were removed from interfaces. All of these methods are still available in the concrete classes +that implement them, but you should update your code to use alternative methods: + +- Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use + `$client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or + `$client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))` or + `$client->setDefaultOption('headers/{header_name}', 'value')`. or + `$client->setDefaultOption('headers', array('header_name' => 'value'))`. +- Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use `$client->getConfig()->getPath('request.options/headers')`. +- Removed `Guzzle\Http\ClientInterface::expandTemplate()`. This is an implementation detail. +- Removed `Guzzle\Http\ClientInterface::setRequestFactory()`. This is an implementation detail. +- Removed `Guzzle\Http\ClientInterface::getCurlMulti()`. This is a very specific implementation detail. +- Removed `Guzzle\Http\Message\RequestInterface::canCache`. Use the CachePlugin. +- Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect`. Use the HistoryPlugin. +- Removed `Guzzle\Http\Message\RequestInterface::isRedirect`. Use the HistoryPlugin. + +### Cache plugin breaking changes + +- CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a + CacheStorageInterface. These two objects and interface will be removed in a future version. +- Always setting X-cache headers on cached responses +- Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin +- `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface + $request, Response $response);` +- `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);` +- `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);` +- Added `CacheStorageInterface::purge($url)` +- `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin + $plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache, + CanCacheStrategyInterface $canCache = null)` +- Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)` + +3.5 to 3.6 +---------- + +* Mixed casing of headers are now forced to be a single consistent casing across all values for that header. +* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution +* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader(). + For example, setHeader() first removes the header using unset on a HeaderCollection and then calls addHeader(). + Keeping the Host header and URL host in sync is now handled by overriding the addHeader method in Request. +* Specific header implementations can be created for complex headers. When a message creates a header, it uses a + HeaderFactory which can map specific headers to specific header classes. There is now a Link header and + CacheControl header implementation. +* Moved getLinks() from Response to just be used on a Link header object. + +If you previously relied on Guzzle\Http\Message\Header::raw(), then you will need to update your code to use the +HeaderInterface (e.g. toArray(), getAll(), etc.). + +### Interface changes + +* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate +* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti() +* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in + Guzzle\Http\Curl\RequestMediator +* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string. +* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface +* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders() + +### Removed deprecated functions + +* Removed Guzzle\Parser\ParserRegister::get(). Use getParser() +* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser(). + +### Deprecations + +* The ability to case-insensitively search for header values +* Guzzle\Http\Message\Header::hasExactHeader +* Guzzle\Http\Message\Header::raw. Use getAll() +* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object + instead. + +### Other changes + +* All response header helper functions return a string rather than mixing Header objects and strings inconsistently +* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc. are managed by Guzzle + directly via interfaces +* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist + but are a no-op until removed. +* Most classes that used to require a `Guzzle\Service\Command\CommandInterface` typehint now request a + `Guzzle\Service\Command\ArrayCommandInterface`. +* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response + on a request while the request is still being transferred +* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess + +3.3 to 3.4 +---------- + +Base URLs of a client now follow the rules of https://tools.ietf.org/html/rfc3986#section-5.2.2 when merging URLs. + +3.2 to 3.3 +---------- + +### Response::getEtag() quote stripping removed + +`Guzzle\Http\Message\Response::getEtag()` no longer strips quotes around the ETag response header + +### Removed `Guzzle\Http\Utils` + +The `Guzzle\Http\Utils` class was removed. This class was only used for testing. + +### Stream wrapper and type + +`Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getStreamType()` are no longer converted to lowercase. + +### curl.emit_io became emit_io + +Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using the +'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io' + +3.1 to 3.2 +---------- + +### CurlMulti is no longer reused globally + +Before 3.2, the same CurlMulti object was reused globally for each client. This can cause issue where plugins added +to a single client can pollute requests dispatched from other clients. + +If you still wish to reuse the same CurlMulti object with each client, then you can add a listener to the +ServiceBuilder's `service_builder.create_client` event to inject a custom CurlMulti object into each client as it is +created. + +```php +$multi = new Guzzle\Http\Curl\CurlMulti(); +$builder = Guzzle\Service\Builder\ServiceBuilder::factory('/path/to/config.json'); +$builder->addListener('service_builder.create_client', function ($event) use ($multi) { + $event['client']->setCurlMulti($multi); +} +}); +``` + +### No default path + +URLs no longer have a default path value of '/' if no path was specified. + +Before: + +```php +$request = $client->get('http://www.foo.com'); +echo $request->getUrl(); +// >> http://www.foo.com/ +``` + +After: + +```php +$request = $client->get('http://www.foo.com'); +echo $request->getUrl(); +// >> http://www.foo.com +``` + +### Less verbose BadResponseException + +The exception message for `Guzzle\Http\Exception\BadResponseException` no longer contains the full HTTP request and +response information. You can, however, get access to the request and response object by calling `getRequest()` or +`getResponse()` on the exception object. + +### Query parameter aggregation + +Multi-valued query parameters are no longer aggregated using a callback function. `Guzzle\Http\Query` now has a +setAggregator() method that accepts a `Guzzle\Http\QueryAggregator\QueryAggregatorInterface` object. This object is +responsible for handling the aggregation of multi-valued query string variables into a flattened hash. + +2.8 to 3.x +---------- + +### Guzzle\Service\Inspector + +Change `\Guzzle\Service\Inspector::fromConfig` to `\Guzzle\Common\Collection::fromConfig` + +**Before** + +```php +use Guzzle\Service\Inspector; + +class YourClient extends \Guzzle\Service\Client +{ + public static function factory($config = array()) + { + $default = array(); + $required = array('base_url', 'username', 'api_key'); + $config = Inspector::fromConfig($config, $default, $required); + + $client = new self( + $config->get('base_url'), + $config->get('username'), + $config->get('api_key') + ); + $client->setConfig($config); + + $client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json')); + + return $client; + } +``` + +**After** + +```php +use Guzzle\Common\Collection; + +class YourClient extends \Guzzle\Service\Client +{ + public static function factory($config = array()) + { + $default = array(); + $required = array('base_url', 'username', 'api_key'); + $config = Collection::fromConfig($config, $default, $required); + + $client = new self( + $config->get('base_url'), + $config->get('username'), + $config->get('api_key') + ); + $client->setConfig($config); + + $client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json')); + + return $client; + } +``` + +### Convert XML Service Descriptions to JSON + +**Before** + +```xml + + + + + + Get a list of groups + + + Uses a search query to get a list of groups + + + + Create a group + + + + + Delete a group by ID + + + + + + + Update a group + + + + + + +``` + +**After** + +```json +{ + "name": "Zendesk REST API v2", + "apiVersion": "2012-12-31", + "description":"Provides access to Zendesk views, groups, tickets, ticket fields, and users", + "operations": { + "list_groups": { + "httpMethod":"GET", + "uri": "groups.json", + "summary": "Get a list of groups" + }, + "search_groups":{ + "httpMethod":"GET", + "uri": "search.json?query=\"{query} type:group\"", + "summary": "Uses a search query to get a list of groups", + "parameters":{ + "query":{ + "location": "uri", + "description":"Zendesk Search Query", + "type": "string", + "required": true + } + } + }, + "create_group": { + "httpMethod":"POST", + "uri": "groups.json", + "summary": "Create a group", + "parameters":{ + "data": { + "type": "array", + "location": "body", + "description":"Group JSON", + "filters": "json_encode", + "required": true + }, + "Content-Type":{ + "type": "string", + "location":"header", + "static": "application/json" + } + } + }, + "delete_group": { + "httpMethod":"DELETE", + "uri": "groups/{id}.json", + "summary": "Delete a group", + "parameters":{ + "id":{ + "location": "uri", + "description":"Group to delete by ID", + "type": "integer", + "required": true + } + } + }, + "get_group": { + "httpMethod":"GET", + "uri": "groups/{id}.json", + "summary": "Get a ticket", + "parameters":{ + "id":{ + "location": "uri", + "description":"Group to get by ID", + "type": "integer", + "required": true + } + } + }, + "update_group": { + "httpMethod":"PUT", + "uri": "groups/{id}.json", + "summary": "Update a group", + "parameters":{ + "id": { + "location": "uri", + "description":"Group to update by ID", + "type": "integer", + "required": true + }, + "data": { + "type": "array", + "location": "body", + "description":"Group JSON", + "filters": "json_encode", + "required": true + }, + "Content-Type":{ + "type": "string", + "location":"header", + "static": "application/json" + } + } + } +} +``` + +### Guzzle\Service\Description\ServiceDescription + +Commands are now called Operations + +**Before** + +```php +use Guzzle\Service\Description\ServiceDescription; + +$sd = new ServiceDescription(); +$sd->getCommands(); // @returns ApiCommandInterface[] +$sd->hasCommand($name); +$sd->getCommand($name); // @returns ApiCommandInterface|null +$sd->addCommand($command); // @param ApiCommandInterface $command +``` + +**After** + +```php +use Guzzle\Service\Description\ServiceDescription; + +$sd = new ServiceDescription(); +$sd->getOperations(); // @returns OperationInterface[] +$sd->hasOperation($name); +$sd->getOperation($name); // @returns OperationInterface|null +$sd->addOperation($operation); // @param OperationInterface $operation +``` + +### Guzzle\Common\Inflection\Inflector + +Namespace is now `Guzzle\Inflection\Inflector` + +### Guzzle\Http\Plugin + +Namespace is now `Guzzle\Plugin`. Many other changes occur within this namespace and are detailed in their own sections below. + +### Guzzle\Http\Plugin\LogPlugin and Guzzle\Common\Log + +Now `Guzzle\Plugin\Log\LogPlugin` and `Guzzle\Log` respectively. + +**Before** + +```php +use Guzzle\Common\Log\ClosureLogAdapter; +use Guzzle\Http\Plugin\LogPlugin; + +/** @var \Guzzle\Http\Client */ +$client; + +// $verbosity is an integer indicating desired message verbosity level +$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $verbosity = LogPlugin::LOG_VERBOSE); +``` + +**After** + +```php +use Guzzle\Log\ClosureLogAdapter; +use Guzzle\Log\MessageFormatter; +use Guzzle\Plugin\Log\LogPlugin; + +/** @var \Guzzle\Http\Client */ +$client; + +// $format is a string indicating desired message format -- @see MessageFormatter +$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $format = MessageFormatter::DEBUG_FORMAT); +``` + +### Guzzle\Http\Plugin\CurlAuthPlugin + +Now `Guzzle\Plugin\CurlAuth\CurlAuthPlugin`. + +### Guzzle\Http\Plugin\ExponentialBackoffPlugin + +Now `Guzzle\Plugin\Backoff\BackoffPlugin`, and other changes. + +**Before** + +```php +use Guzzle\Http\Plugin\ExponentialBackoffPlugin; + +$backoffPlugin = new ExponentialBackoffPlugin($maxRetries, array_merge( + ExponentialBackoffPlugin::getDefaultFailureCodes(), array(429) + )); + +$client->addSubscriber($backoffPlugin); +``` + +**After** + +```php +use Guzzle\Plugin\Backoff\BackoffPlugin; +use Guzzle\Plugin\Backoff\HttpBackoffStrategy; + +// Use convenient factory method instead -- see implementation for ideas of what +// you can do with chaining backoff strategies +$backoffPlugin = BackoffPlugin::getExponentialBackoff($maxRetries, array_merge( + HttpBackoffStrategy::getDefaultFailureCodes(), array(429) + )); +$client->addSubscriber($backoffPlugin); +``` + +### Known Issues + +#### [BUG] Accept-Encoding header behavior changed unintentionally. + +(See #217) (Fixed in 09daeb8c666fb44499a0646d655a8ae36456575e) + +In version 2.8 setting the `Accept-Encoding` header would set the CURLOPT_ENCODING option, which permitted cURL to +properly handle gzip/deflate compressed responses from the server. In versions affected by this bug this does not happen. +See issue #217 for a workaround, or use a version containing the fix. diff --git a/vendor/guzzlehttp/guzzle/composer.json b/vendor/guzzlehttp/guzzle/composer.json new file mode 100644 index 0000000..2549f78 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/composer.json @@ -0,0 +1,98 @@ +{ + "name": "guzzlehttp/guzzle", + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "framework", + "http", + "rest", + "web service", + "curl", + "client", + "HTTP client", + "PSR-7", + "PSR-18" + ], + "license": "MIT", + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "require": { + "php": "^7.2.5 || ^8.0", + "ext-json": "*", + "guzzlehttp/promises": "^1.5", + "guzzlehttp/psr7": "^1.8.3 || ^2.1", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "bamarni/composer-bin-plugin": "^1.4.1", + "php-http/client-integration-tests": "^3.0", + "phpunit/phpunit": "^8.5.5 || ^9.3.5", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "config": { + "preferred-install": "dist", + "sort-packages": true + }, + "extra": { + "branch-alias": { + "dev-master": "7.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "autoload-dev": { + "psr-4": { + "GuzzleHttp\\Tests\\": "tests/" + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/BodySummarizer.php b/vendor/guzzlehttp/guzzle/src/BodySummarizer.php new file mode 100644 index 0000000..6eca94e --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/BodySummarizer.php @@ -0,0 +1,28 @@ +truncateAt = $truncateAt; + } + + /** + * Returns a summarized message body. + */ + public function summarize(MessageInterface $message): ?string + { + return $this->truncateAt === null + ? \GuzzleHttp\Psr7\Message::bodySummary($message) + : \GuzzleHttp\Psr7\Message::bodySummary($message, $this->truncateAt); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/BodySummarizerInterface.php b/vendor/guzzlehttp/guzzle/src/BodySummarizerInterface.php new file mode 100644 index 0000000..3e02e03 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/BodySummarizerInterface.php @@ -0,0 +1,13 @@ + 'http://www.foo.com/1.0/', + * 'timeout' => 0, + * 'allow_redirects' => false, + * 'proxy' => '192.168.16.1:10' + * ]); + * + * Client configuration settings include the following options: + * + * - handler: (callable) Function that transfers HTTP requests over the + * wire. The function is called with a Psr7\Http\Message\RequestInterface + * and array of transfer options, and must return a + * GuzzleHttp\Promise\PromiseInterface that is fulfilled with a + * Psr7\Http\Message\ResponseInterface on success. + * If no handler is provided, a default handler will be created + * that enables all of the request options below by attaching all of the + * default middleware to the handler. + * - base_uri: (string|UriInterface) Base URI of the client that is merged + * into relative URIs. Can be a string or instance of UriInterface. + * - **: any request option + * + * @param array $config Client configuration settings. + * + * @see \GuzzleHttp\RequestOptions for a list of available request options. + */ + public function __construct(array $config = []) + { + if (!isset($config['handler'])) { + $config['handler'] = HandlerStack::create(); + } elseif (!\is_callable($config['handler'])) { + throw new InvalidArgumentException('handler must be a callable'); + } + + // Convert the base_uri to a UriInterface + if (isset($config['base_uri'])) { + $config['base_uri'] = Psr7\Utils::uriFor($config['base_uri']); + } + + $this->configureDefaults($config); + } + + /** + * @param string $method + * @param array $args + * + * @return PromiseInterface|ResponseInterface + * + * @deprecated Client::__call will be removed in guzzlehttp/guzzle:8.0. + */ + public function __call($method, $args) + { + if (\count($args) < 1) { + throw new InvalidArgumentException('Magic request methods require a URI and optional options array'); + } + + $uri = $args[0]; + $opts = $args[1] ?? []; + + return \substr($method, -5) === 'Async' + ? $this->requestAsync(\substr($method, 0, -5), $uri, $opts) + : $this->request($method, $uri, $opts); + } + + /** + * Asynchronously send an HTTP request. + * + * @param array $options Request options to apply to the given + * request and to the transfer. See \GuzzleHttp\RequestOptions. + */ + public function sendAsync(RequestInterface $request, array $options = []): PromiseInterface + { + // Merge the base URI into the request URI if needed. + $options = $this->prepareDefaults($options); + + return $this->transfer( + $request->withUri($this->buildUri($request->getUri(), $options), $request->hasHeader('Host')), + $options + ); + } + + /** + * Send an HTTP request. + * + * @param array $options Request options to apply to the given + * request and to the transfer. See \GuzzleHttp\RequestOptions. + * + * @throws GuzzleException + */ + public function send(RequestInterface $request, array $options = []): ResponseInterface + { + $options[RequestOptions::SYNCHRONOUS] = true; + return $this->sendAsync($request, $options)->wait(); + } + + /** + * The HttpClient PSR (PSR-18) specify this method. + * + * @inheritDoc + */ + public function sendRequest(RequestInterface $request): ResponseInterface + { + $options[RequestOptions::SYNCHRONOUS] = true; + $options[RequestOptions::ALLOW_REDIRECTS] = false; + $options[RequestOptions::HTTP_ERRORS] = false; + + return $this->sendAsync($request, $options)->wait(); + } + + /** + * Create and send an asynchronous HTTP request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. Use an array to provide a URL + * template and additional variables to use in the URL template expansion. + * + * @param string $method HTTP method + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. See \GuzzleHttp\RequestOptions. + */ + public function requestAsync(string $method, $uri = '', array $options = []): PromiseInterface + { + $options = $this->prepareDefaults($options); + // Remove request modifying parameter because it can be done up-front. + $headers = $options['headers'] ?? []; + $body = $options['body'] ?? null; + $version = $options['version'] ?? '1.1'; + // Merge the URI into the base URI. + $uri = $this->buildUri(Psr7\Utils::uriFor($uri), $options); + if (\is_array($body)) { + throw $this->invalidBody(); + } + $request = new Psr7\Request($method, $uri, $headers, $body, $version); + // Remove the option so that they are not doubly-applied. + unset($options['headers'], $options['body'], $options['version']); + + return $this->transfer($request, $options); + } + + /** + * Create and send an HTTP request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. + * + * @param string $method HTTP method. + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. See \GuzzleHttp\RequestOptions. + * + * @throws GuzzleException + */ + public function request(string $method, $uri = '', array $options = []): ResponseInterface + { + $options[RequestOptions::SYNCHRONOUS] = true; + return $this->requestAsync($method, $uri, $options)->wait(); + } + + /** + * Get a client configuration option. + * + * These options include default request options of the client, a "handler" + * (if utilized by the concrete client), and a "base_uri" if utilized by + * the concrete client. + * + * @param string|null $option The config option to retrieve. + * + * @return mixed + * + * @deprecated Client::getConfig will be removed in guzzlehttp/guzzle:8.0. + */ + public function getConfig(?string $option = null) + { + return $option === null + ? $this->config + : ($this->config[$option] ?? null); + } + + private function buildUri(UriInterface $uri, array $config): UriInterface + { + if (isset($config['base_uri'])) { + $uri = Psr7\UriResolver::resolve(Psr7\Utils::uriFor($config['base_uri']), $uri); + } + + if (isset($config['idn_conversion']) && ($config['idn_conversion'] !== false)) { + $idnOptions = ($config['idn_conversion'] === true) ? \IDNA_DEFAULT : $config['idn_conversion']; + $uri = Utils::idnUriConvert($uri, $idnOptions); + } + + return $uri->getScheme() === '' && $uri->getHost() !== '' ? $uri->withScheme('http') : $uri; + } + + /** + * Configures the default options for a client. + */ + private function configureDefaults(array $config): void + { + $defaults = [ + 'allow_redirects' => RedirectMiddleware::$defaultSettings, + 'http_errors' => true, + 'decode_content' => true, + 'verify' => true, + 'cookies' => false, + 'idn_conversion' => false, + ]; + + // Use the standard Linux HTTP_PROXY and HTTPS_PROXY if set. + + // We can only trust the HTTP_PROXY environment variable in a CLI + // process due to the fact that PHP has no reliable mechanism to + // get environment variables that start with "HTTP_". + if (\PHP_SAPI === 'cli' && ($proxy = Utils::getenv('HTTP_PROXY'))) { + $defaults['proxy']['http'] = $proxy; + } + + if ($proxy = Utils::getenv('HTTPS_PROXY')) { + $defaults['proxy']['https'] = $proxy; + } + + if ($noProxy = Utils::getenv('NO_PROXY')) { + $cleanedNoProxy = \str_replace(' ', '', $noProxy); + $defaults['proxy']['no'] = \explode(',', $cleanedNoProxy); + } + + $this->config = $config + $defaults; + + if (!empty($config['cookies']) && $config['cookies'] === true) { + $this->config['cookies'] = new CookieJar(); + } + + // Add the default user-agent header. + if (!isset($this->config['headers'])) { + $this->config['headers'] = ['User-Agent' => Utils::defaultUserAgent()]; + } else { + // Add the User-Agent header if one was not already set. + foreach (\array_keys($this->config['headers']) as $name) { + if (\strtolower($name) === 'user-agent') { + return; + } + } + $this->config['headers']['User-Agent'] = Utils::defaultUserAgent(); + } + } + + /** + * Merges default options into the array. + * + * @param array $options Options to modify by reference + */ + private function prepareDefaults(array $options): array + { + $defaults = $this->config; + + if (!empty($defaults['headers'])) { + // Default headers are only added if they are not present. + $defaults['_conditional'] = $defaults['headers']; + unset($defaults['headers']); + } + + // Special handling for headers is required as they are added as + // conditional headers and as headers passed to a request ctor. + if (\array_key_exists('headers', $options)) { + // Allows default headers to be unset. + if ($options['headers'] === null) { + $defaults['_conditional'] = []; + unset($options['headers']); + } elseif (!\is_array($options['headers'])) { + throw new InvalidArgumentException('headers must be an array'); + } + } + + // Shallow merge defaults underneath options. + $result = $options + $defaults; + + // Remove null values. + foreach ($result as $k => $v) { + if ($v === null) { + unset($result[$k]); + } + } + + return $result; + } + + /** + * Transfers the given request and applies request options. + * + * The URI of the request is not modified and the request options are used + * as-is without merging in default options. + * + * @param array $options See \GuzzleHttp\RequestOptions. + */ + private function transfer(RequestInterface $request, array $options): PromiseInterface + { + $request = $this->applyOptions($request, $options); + /** @var HandlerStack $handler */ + $handler = $options['handler']; + + try { + return P\Create::promiseFor($handler($request, $options)); + } catch (\Exception $e) { + return P\Create::rejectionFor($e); + } + } + + /** + * Applies the array of request options to a request. + */ + private function applyOptions(RequestInterface $request, array &$options): RequestInterface + { + $modify = [ + 'set_headers' => [], + ]; + + if (isset($options['headers'])) { + if (array_keys($options['headers']) === range(0, count($options['headers']) - 1)) { + throw new InvalidArgumentException('The headers array must have header name as keys.'); + } + $modify['set_headers'] = $options['headers']; + unset($options['headers']); + } + + if (isset($options['form_params'])) { + if (isset($options['multipart'])) { + throw new InvalidArgumentException('You cannot use ' + . 'form_params and multipart at the same time. Use the ' + . 'form_params option if you want to send application/' + . 'x-www-form-urlencoded requests, and the multipart ' + . 'option to send multipart/form-data requests.'); + } + $options['body'] = \http_build_query($options['form_params'], '', '&'); + unset($options['form_params']); + // Ensure that we don't have the header in different case and set the new value. + $options['_conditional'] = Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']); + $options['_conditional']['Content-Type'] = 'application/x-www-form-urlencoded'; + } + + if (isset($options['multipart'])) { + $options['body'] = new Psr7\MultipartStream($options['multipart']); + unset($options['multipart']); + } + + if (isset($options['json'])) { + $options['body'] = Utils::jsonEncode($options['json']); + unset($options['json']); + // Ensure that we don't have the header in different case and set the new value. + $options['_conditional'] = Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']); + $options['_conditional']['Content-Type'] = 'application/json'; + } + + if (!empty($options['decode_content']) + && $options['decode_content'] !== true + ) { + // Ensure that we don't have the header in different case and set the new value. + $options['_conditional'] = Psr7\Utils::caselessRemove(['Accept-Encoding'], $options['_conditional']); + $modify['set_headers']['Accept-Encoding'] = $options['decode_content']; + } + + if (isset($options['body'])) { + if (\is_array($options['body'])) { + throw $this->invalidBody(); + } + $modify['body'] = Psr7\Utils::streamFor($options['body']); + unset($options['body']); + } + + if (!empty($options['auth']) && \is_array($options['auth'])) { + $value = $options['auth']; + $type = isset($value[2]) ? \strtolower($value[2]) : 'basic'; + switch ($type) { + case 'basic': + // Ensure that we don't have the header in different case and set the new value. + $modify['set_headers'] = Psr7\Utils::caselessRemove(['Authorization'], $modify['set_headers']); + $modify['set_headers']['Authorization'] = 'Basic ' + . \base64_encode("$value[0]:$value[1]"); + break; + case 'digest': + // @todo: Do not rely on curl + $options['curl'][\CURLOPT_HTTPAUTH] = \CURLAUTH_DIGEST; + $options['curl'][\CURLOPT_USERPWD] = "$value[0]:$value[1]"; + break; + case 'ntlm': + $options['curl'][\CURLOPT_HTTPAUTH] = \CURLAUTH_NTLM; + $options['curl'][\CURLOPT_USERPWD] = "$value[0]:$value[1]"; + break; + } + } + + if (isset($options['query'])) { + $value = $options['query']; + if (\is_array($value)) { + $value = \http_build_query($value, '', '&', \PHP_QUERY_RFC3986); + } + if (!\is_string($value)) { + throw new InvalidArgumentException('query must be a string or array'); + } + $modify['query'] = $value; + unset($options['query']); + } + + // Ensure that sink is not an invalid value. + if (isset($options['sink'])) { + // TODO: Add more sink validation? + if (\is_bool($options['sink'])) { + throw new InvalidArgumentException('sink must not be a boolean'); + } + } + + $request = Psr7\Utils::modifyRequest($request, $modify); + if ($request->getBody() instanceof Psr7\MultipartStream) { + // Use a multipart/form-data POST if a Content-Type is not set. + // Ensure that we don't have the header in different case and set the new value. + $options['_conditional'] = Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']); + $options['_conditional']['Content-Type'] = 'multipart/form-data; boundary=' + . $request->getBody()->getBoundary(); + } + + // Merge in conditional headers if they are not present. + if (isset($options['_conditional'])) { + // Build up the changes so it's in a single clone of the message. + $modify = []; + foreach ($options['_conditional'] as $k => $v) { + if (!$request->hasHeader($k)) { + $modify['set_headers'][$k] = $v; + } + } + $request = Psr7\Utils::modifyRequest($request, $modify); + // Don't pass this internal value along to middleware/handlers. + unset($options['_conditional']); + } + + return $request; + } + + /** + * Return an InvalidArgumentException with pre-set message. + */ + private function invalidBody(): InvalidArgumentException + { + return new InvalidArgumentException('Passing in the "body" request ' + . 'option as an array to send a request is not supported. ' + . 'Please use the "form_params" request option to send a ' + . 'application/x-www-form-urlencoded request, or the "multipart" ' + . 'request option to send a multipart/form-data request.'); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/ClientInterface.php b/vendor/guzzlehttp/guzzle/src/ClientInterface.php new file mode 100644 index 0000000..6aaee61 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/ClientInterface.php @@ -0,0 +1,84 @@ +request('GET', $uri, $options); + } + + /** + * Create and send an HTTP HEAD request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + * + * @throws GuzzleException + */ + public function head($uri, array $options = []): ResponseInterface + { + return $this->request('HEAD', $uri, $options); + } + + /** + * Create and send an HTTP PUT request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + * + * @throws GuzzleException + */ + public function put($uri, array $options = []): ResponseInterface + { + return $this->request('PUT', $uri, $options); + } + + /** + * Create and send an HTTP POST request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + * + * @throws GuzzleException + */ + public function post($uri, array $options = []): ResponseInterface + { + return $this->request('POST', $uri, $options); + } + + /** + * Create and send an HTTP PATCH request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + * + * @throws GuzzleException + */ + public function patch($uri, array $options = []): ResponseInterface + { + return $this->request('PATCH', $uri, $options); + } + + /** + * Create and send an HTTP DELETE request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + * + * @throws GuzzleException + */ + public function delete($uri, array $options = []): ResponseInterface + { + return $this->request('DELETE', $uri, $options); + } + + /** + * Create and send an asynchronous HTTP request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. Use an array to provide a URL + * template and additional variables to use in the URL template expansion. + * + * @param string $method HTTP method + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + abstract public function requestAsync(string $method, $uri, array $options = []): PromiseInterface; + + /** + * Create and send an asynchronous HTTP GET request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. Use an array to provide a URL + * template and additional variables to use in the URL template expansion. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + public function getAsync($uri, array $options = []): PromiseInterface + { + return $this->requestAsync('GET', $uri, $options); + } + + /** + * Create and send an asynchronous HTTP HEAD request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. Use an array to provide a URL + * template and additional variables to use in the URL template expansion. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + public function headAsync($uri, array $options = []): PromiseInterface + { + return $this->requestAsync('HEAD', $uri, $options); + } + + /** + * Create and send an asynchronous HTTP PUT request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. Use an array to provide a URL + * template and additional variables to use in the URL template expansion. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + public function putAsync($uri, array $options = []): PromiseInterface + { + return $this->requestAsync('PUT', $uri, $options); + } + + /** + * Create and send an asynchronous HTTP POST request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. Use an array to provide a URL + * template and additional variables to use in the URL template expansion. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + public function postAsync($uri, array $options = []): PromiseInterface + { + return $this->requestAsync('POST', $uri, $options); + } + + /** + * Create and send an asynchronous HTTP PATCH request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. Use an array to provide a URL + * template and additional variables to use in the URL template expansion. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + public function patchAsync($uri, array $options = []): PromiseInterface + { + return $this->requestAsync('PATCH', $uri, $options); + } + + /** + * Create and send an asynchronous HTTP DELETE request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. Use an array to provide a URL + * template and additional variables to use in the URL template expansion. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + public function deleteAsync($uri, array $options = []): PromiseInterface + { + return $this->requestAsync('DELETE', $uri, $options); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Cookie/CookieJar.php b/vendor/guzzlehttp/guzzle/src/Cookie/CookieJar.php new file mode 100644 index 0000000..d6757c6 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Cookie/CookieJar.php @@ -0,0 +1,313 @@ +strictMode = $strictMode; + + foreach ($cookieArray as $cookie) { + if (!($cookie instanceof SetCookie)) { + $cookie = new SetCookie($cookie); + } + $this->setCookie($cookie); + } + } + + /** + * Create a new Cookie jar from an associative array and domain. + * + * @param array $cookies Cookies to create the jar from + * @param string $domain Domain to set the cookies to + */ + public static function fromArray(array $cookies, string $domain): self + { + $cookieJar = new self(); + foreach ($cookies as $name => $value) { + $cookieJar->setCookie(new SetCookie([ + 'Domain' => $domain, + 'Name' => $name, + 'Value' => $value, + 'Discard' => true + ])); + } + + return $cookieJar; + } + + /** + * Evaluate if this cookie should be persisted to storage + * that survives between requests. + * + * @param SetCookie $cookie Being evaluated. + * @param bool $allowSessionCookies If we should persist session cookies + */ + public static function shouldPersist(SetCookie $cookie, bool $allowSessionCookies = false): bool + { + if ($cookie->getExpires() || $allowSessionCookies) { + if (!$cookie->getDiscard()) { + return true; + } + } + + return false; + } + + /** + * Finds and returns the cookie based on the name + * + * @param string $name cookie name to search for + * + * @return SetCookie|null cookie that was found or null if not found + */ + public function getCookieByName(string $name): ?SetCookie + { + foreach ($this->cookies as $cookie) { + if ($cookie->getName() !== null && \strcasecmp($cookie->getName(), $name) === 0) { + return $cookie; + } + } + + return null; + } + + /** + * @inheritDoc + */ + public function toArray(): array + { + return \array_map(static function (SetCookie $cookie): array { + return $cookie->toArray(); + }, $this->getIterator()->getArrayCopy()); + } + + /** + * @inheritDoc + */ + public function clear(?string $domain = null, ?string $path = null, ?string $name = null): void + { + if (!$domain) { + $this->cookies = []; + return; + } elseif (!$path) { + $this->cookies = \array_filter( + $this->cookies, + static function (SetCookie $cookie) use ($domain): bool { + return !$cookie->matchesDomain($domain); + } + ); + } elseif (!$name) { + $this->cookies = \array_filter( + $this->cookies, + static function (SetCookie $cookie) use ($path, $domain): bool { + return !($cookie->matchesPath($path) && + $cookie->matchesDomain($domain)); + } + ); + } else { + $this->cookies = \array_filter( + $this->cookies, + static function (SetCookie $cookie) use ($path, $domain, $name) { + return !($cookie->getName() == $name && + $cookie->matchesPath($path) && + $cookie->matchesDomain($domain)); + } + ); + } + } + + /** + * @inheritDoc + */ + public function clearSessionCookies(): void + { + $this->cookies = \array_filter( + $this->cookies, + static function (SetCookie $cookie): bool { + return !$cookie->getDiscard() && $cookie->getExpires(); + } + ); + } + + /** + * @inheritDoc + */ + public function setCookie(SetCookie $cookie): bool + { + // If the name string is empty (but not 0), ignore the set-cookie + // string entirely. + $name = $cookie->getName(); + if (!$name && $name !== '0') { + return false; + } + + // Only allow cookies with set and valid domain, name, value + $result = $cookie->validate(); + if ($result !== true) { + if ($this->strictMode) { + throw new \RuntimeException('Invalid cookie: ' . $result); + } + $this->removeCookieIfEmpty($cookie); + return false; + } + + // Resolve conflicts with previously set cookies + foreach ($this->cookies as $i => $c) { + + // Two cookies are identical, when their path, and domain are + // identical. + if ($c->getPath() != $cookie->getPath() || + $c->getDomain() != $cookie->getDomain() || + $c->getName() != $cookie->getName() + ) { + continue; + } + + // The previously set cookie is a discard cookie and this one is + // not so allow the new cookie to be set + if (!$cookie->getDiscard() && $c->getDiscard()) { + unset($this->cookies[$i]); + continue; + } + + // If the new cookie's expiration is further into the future, then + // replace the old cookie + if ($cookie->getExpires() > $c->getExpires()) { + unset($this->cookies[$i]); + continue; + } + + // If the value has changed, we better change it + if ($cookie->getValue() !== $c->getValue()) { + unset($this->cookies[$i]); + continue; + } + + // The cookie exists, so no need to continue + return false; + } + + $this->cookies[] = $cookie; + + return true; + } + + public function count(): int + { + return \count($this->cookies); + } + + /** + * @return \ArrayIterator + */ + public function getIterator(): \ArrayIterator + { + return new \ArrayIterator(\array_values($this->cookies)); + } + + public function extractCookies(RequestInterface $request, ResponseInterface $response): void + { + if ($cookieHeader = $response->getHeader('Set-Cookie')) { + foreach ($cookieHeader as $cookie) { + $sc = SetCookie::fromString($cookie); + if (!$sc->getDomain()) { + $sc->setDomain($request->getUri()->getHost()); + } + if (0 !== \strpos($sc->getPath(), '/')) { + $sc->setPath($this->getCookiePathFromRequest($request)); + } + $this->setCookie($sc); + } + } + } + + /** + * Computes cookie path following RFC 6265 section 5.1.4 + * + * @link https://tools.ietf.org/html/rfc6265#section-5.1.4 + */ + private function getCookiePathFromRequest(RequestInterface $request): string + { + $uriPath = $request->getUri()->getPath(); + if ('' === $uriPath) { + return '/'; + } + if (0 !== \strpos($uriPath, '/')) { + return '/'; + } + if ('/' === $uriPath) { + return '/'; + } + $lastSlashPos = \strrpos($uriPath, '/'); + if (0 === $lastSlashPos || false === $lastSlashPos) { + return '/'; + } + + return \substr($uriPath, 0, $lastSlashPos); + } + + public function withCookieHeader(RequestInterface $request): RequestInterface + { + $values = []; + $uri = $request->getUri(); + $scheme = $uri->getScheme(); + $host = $uri->getHost(); + $path = $uri->getPath() ?: '/'; + + foreach ($this->cookies as $cookie) { + if ($cookie->matchesPath($path) && + $cookie->matchesDomain($host) && + !$cookie->isExpired() && + (!$cookie->getSecure() || $scheme === 'https') + ) { + $values[] = $cookie->getName() . '=' + . $cookie->getValue(); + } + } + + return $values + ? $request->withHeader('Cookie', \implode('; ', $values)) + : $request; + } + + /** + * If a cookie already exists and the server asks to set it again with a + * null value, the cookie must be deleted. + */ + private function removeCookieIfEmpty(SetCookie $cookie): void + { + $cookieValue = $cookie->getValue(); + if ($cookieValue === null || $cookieValue === '') { + $this->clear( + $cookie->getDomain(), + $cookie->getPath(), + $cookie->getName() + ); + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php b/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php new file mode 100644 index 0000000..7df374b --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php @@ -0,0 +1,79 @@ + + */ +interface CookieJarInterface extends \Countable, \IteratorAggregate +{ + /** + * Create a request with added cookie headers. + * + * If no matching cookies are found in the cookie jar, then no Cookie + * header is added to the request and the same request is returned. + * + * @param RequestInterface $request Request object to modify. + * + * @return RequestInterface returns the modified request. + */ + public function withCookieHeader(RequestInterface $request): RequestInterface; + + /** + * Extract cookies from an HTTP response and store them in the CookieJar. + * + * @param RequestInterface $request Request that was sent + * @param ResponseInterface $response Response that was received + */ + public function extractCookies(RequestInterface $request, ResponseInterface $response): void; + + /** + * Sets a cookie in the cookie jar. + * + * @param SetCookie $cookie Cookie to set. + * + * @return bool Returns true on success or false on failure + */ + public function setCookie(SetCookie $cookie): bool; + + /** + * Remove cookies currently held in the cookie jar. + * + * Invoking this method without arguments will empty the whole cookie jar. + * If given a $domain argument only cookies belonging to that domain will + * be removed. If given a $domain and $path argument, cookies belonging to + * the specified path within that domain are removed. If given all three + * arguments, then the cookie with the specified name, path and domain is + * removed. + * + * @param string|null $domain Clears cookies matching a domain + * @param string|null $path Clears cookies matching a domain and path + * @param string|null $name Clears cookies matching a domain, path, and name + */ + public function clear(?string $domain = null, ?string $path = null, ?string $name = null): void; + + /** + * Discard all sessions cookies. + * + * Removes cookies that don't have an expire field or a have a discard + * field set to true. To be called when the user agent shuts down according + * to RFC 2965. + */ + public function clearSessionCookies(): void; + + /** + * Converts the cookie jar to an array. + */ + public function toArray(): array; +} diff --git a/vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php b/vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php new file mode 100644 index 0000000..290236d --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php @@ -0,0 +1,101 @@ +filename = $cookieFile; + $this->storeSessionCookies = $storeSessionCookies; + + if (\file_exists($cookieFile)) { + $this->load($cookieFile); + } + } + + /** + * Saves the file when shutting down + */ + public function __destruct() + { + $this->save($this->filename); + } + + /** + * Saves the cookies to a file. + * + * @param string $filename File to save + * + * @throws \RuntimeException if the file cannot be found or created + */ + public function save(string $filename): void + { + $json = []; + /** @var SetCookie $cookie */ + foreach ($this as $cookie) { + if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) { + $json[] = $cookie->toArray(); + } + } + + $jsonStr = Utils::jsonEncode($json); + if (false === \file_put_contents($filename, $jsonStr, \LOCK_EX)) { + throw new \RuntimeException("Unable to save file {$filename}"); + } + } + + /** + * Load cookies from a JSON formatted file. + * + * Old cookies are kept unless overwritten by newly loaded ones. + * + * @param string $filename Cookie file to load. + * + * @throws \RuntimeException if the file cannot be loaded. + */ + public function load(string $filename): void + { + $json = \file_get_contents($filename); + if (false === $json) { + throw new \RuntimeException("Unable to load file {$filename}"); + } + if ($json === '') { + return; + } + + $data = Utils::jsonDecode($json, true); + if (\is_array($data)) { + foreach ($data as $cookie) { + $this->setCookie(new SetCookie($cookie)); + } + } elseif (\is_scalar($data) && !empty($data)) { + throw new \RuntimeException("Invalid cookie file: {$filename}"); + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php b/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php new file mode 100644 index 0000000..5d51ca9 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php @@ -0,0 +1,77 @@ +sessionKey = $sessionKey; + $this->storeSessionCookies = $storeSessionCookies; + $this->load(); + } + + /** + * Saves cookies to session when shutting down + */ + public function __destruct() + { + $this->save(); + } + + /** + * Save cookies to the client session + */ + public function save(): void + { + $json = []; + /** @var SetCookie $cookie */ + foreach ($this as $cookie) { + if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) { + $json[] = $cookie->toArray(); + } + } + + $_SESSION[$this->sessionKey] = \json_encode($json); + } + + /** + * Load the contents of the client session into the data array + */ + protected function load(): void + { + if (!isset($_SESSION[$this->sessionKey])) { + return; + } + $data = \json_decode($_SESSION[$this->sessionKey], true); + if (\is_array($data)) { + foreach ($data as $cookie) { + $this->setCookie(new SetCookie($cookie)); + } + } elseif (\strlen($data)) { + throw new \RuntimeException("Invalid cookie data"); + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php b/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php new file mode 100644 index 0000000..7c04034 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php @@ -0,0 +1,444 @@ + null, + 'Value' => null, + 'Domain' => null, + 'Path' => '/', + 'Max-Age' => null, + 'Expires' => null, + 'Secure' => false, + 'Discard' => false, + 'HttpOnly' => false + ]; + + /** + * @var array Cookie data + */ + private $data; + + /** + * Create a new SetCookie object from a string. + * + * @param string $cookie Set-Cookie header string + */ + public static function fromString(string $cookie): self + { + // Create the default return array + $data = self::$defaults; + // Explode the cookie string using a series of semicolons + $pieces = \array_filter(\array_map('trim', \explode(';', $cookie))); + // The name of the cookie (first kvp) must exist and include an equal sign. + if (!isset($pieces[0]) || \strpos($pieces[0], '=') === false) { + return new self($data); + } + + // Add the cookie pieces into the parsed data array + foreach ($pieces as $part) { + $cookieParts = \explode('=', $part, 2); + $key = \trim($cookieParts[0]); + $value = isset($cookieParts[1]) + ? \trim($cookieParts[1], " \n\r\t\0\x0B") + : true; + + // Only check for non-cookies when cookies have been found + if (!isset($data['Name'])) { + $data['Name'] = $key; + $data['Value'] = $value; + } else { + foreach (\array_keys(self::$defaults) as $search) { + if (!\strcasecmp($search, $key)) { + $data[$search] = $value; + continue 2; + } + } + $data[$key] = $value; + } + } + + return new self($data); + } + + /** + * @param array $data Array of cookie data provided by a Cookie parser + */ + public function __construct(array $data = []) + { + /** @var array|null $replaced will be null in case of replace error */ + $replaced = \array_replace(self::$defaults, $data); + if ($replaced === null) { + throw new \InvalidArgumentException('Unable to replace the default values for the Cookie.'); + } + + $this->data = $replaced; + // Extract the Expires value and turn it into a UNIX timestamp if needed + if (!$this->getExpires() && $this->getMaxAge()) { + // Calculate the Expires date + $this->setExpires(\time() + $this->getMaxAge()); + } elseif (null !== ($expires = $this->getExpires()) && !\is_numeric($expires)) { + $this->setExpires($expires); + } + } + + public function __toString() + { + $str = $this->data['Name'] . '=' . ($this->data['Value'] ?? '') . '; '; + foreach ($this->data as $k => $v) { + if ($k !== 'Name' && $k !== 'Value' && $v !== null && $v !== false) { + if ($k === 'Expires') { + $str .= 'Expires=' . \gmdate('D, d M Y H:i:s \G\M\T', $v) . '; '; + } else { + $str .= ($v === true ? $k : "{$k}={$v}") . '; '; + } + } + } + + return \rtrim($str, '; '); + } + + public function toArray(): array + { + return $this->data; + } + + /** + * Get the cookie name. + * + * @return string + */ + public function getName() + { + return $this->data['Name']; + } + + /** + * Set the cookie name. + * + * @param string $name Cookie name + */ + public function setName($name): void + { + if (!is_string($name)) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['Name'] = (string) $name; + } + + /** + * Get the cookie value. + * + * @return string|null + */ + public function getValue() + { + return $this->data['Value']; + } + + /** + * Set the cookie value. + * + * @param string $value Cookie value + */ + public function setValue($value): void + { + if (!is_string($value)) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['Value'] = (string) $value; + } + + /** + * Get the domain. + * + * @return string|null + */ + public function getDomain() + { + return $this->data['Domain']; + } + + /** + * Set the domain of the cookie. + * + * @param string|null $domain + */ + public function setDomain($domain): void + { + if (!is_string($domain) && null !== $domain) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string or null to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['Domain'] = null === $domain ? null : (string) $domain; + } + + /** + * Get the path. + * + * @return string + */ + public function getPath() + { + return $this->data['Path']; + } + + /** + * Set the path of the cookie. + * + * @param string $path Path of the cookie + */ + public function setPath($path): void + { + if (!is_string($path)) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['Path'] = (string) $path; + } + + /** + * Maximum lifetime of the cookie in seconds. + * + * @return int|null + */ + public function getMaxAge() + { + return null === $this->data['Max-Age'] ? null : (int) $this->data['Max-Age']; + } + + /** + * Set the max-age of the cookie. + * + * @param int|null $maxAge Max age of the cookie in seconds + */ + public function setMaxAge($maxAge): void + { + if (!is_int($maxAge) && null !== $maxAge) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing an int or null to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['Max-Age'] = $maxAge === null ? null : (int) $maxAge; + } + + /** + * The UNIX timestamp when the cookie Expires. + * + * @return string|int|null + */ + public function getExpires() + { + return $this->data['Expires']; + } + + /** + * Set the unix timestamp for which the cookie will expire. + * + * @param int|string|null $timestamp Unix timestamp or any English textual datetime description. + */ + public function setExpires($timestamp): void + { + if (!is_int($timestamp) && !is_string($timestamp) && null !== $timestamp) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing an int, string or null to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['Expires'] = null === $timestamp ? null : (\is_numeric($timestamp) ? (int) $timestamp : \strtotime((string) $timestamp)); + } + + /** + * Get whether or not this is a secure cookie. + * + * @return bool + */ + public function getSecure() + { + return $this->data['Secure']; + } + + /** + * Set whether or not the cookie is secure. + * + * @param bool $secure Set to true or false if secure + */ + public function setSecure($secure): void + { + if (!is_bool($secure)) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a bool to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['Secure'] = (bool) $secure; + } + + /** + * Get whether or not this is a session cookie. + * + * @return bool|null + */ + public function getDiscard() + { + return $this->data['Discard']; + } + + /** + * Set whether or not this is a session cookie. + * + * @param bool $discard Set to true or false if this is a session cookie + */ + public function setDiscard($discard): void + { + if (!is_bool($discard)) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a bool to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['Discard'] = (bool) $discard; + } + + /** + * Get whether or not this is an HTTP only cookie. + * + * @return bool + */ + public function getHttpOnly() + { + return $this->data['HttpOnly']; + } + + /** + * Set whether or not this is an HTTP only cookie. + * + * @param bool $httpOnly Set to true or false if this is HTTP only + */ + public function setHttpOnly($httpOnly): void + { + if (!is_bool($httpOnly)) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a bool to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['HttpOnly'] = (bool) $httpOnly; + } + + /** + * Check if the cookie matches a path value. + * + * A request-path path-matches a given cookie-path if at least one of + * the following conditions holds: + * + * - The cookie-path and the request-path are identical. + * - The cookie-path is a prefix of the request-path, and the last + * character of the cookie-path is %x2F ("/"). + * - The cookie-path is a prefix of the request-path, and the first + * character of the request-path that is not included in the cookie- + * path is a %x2F ("/") character. + * + * @param string $requestPath Path to check against + */ + public function matchesPath(string $requestPath): bool + { + $cookiePath = $this->getPath(); + + // Match on exact matches or when path is the default empty "/" + if ($cookiePath === '/' || $cookiePath == $requestPath) { + return true; + } + + // Ensure that the cookie-path is a prefix of the request path. + if (0 !== \strpos($requestPath, $cookiePath)) { + return false; + } + + // Match if the last character of the cookie-path is "/" + if (\substr($cookiePath, -1, 1) === '/') { + return true; + } + + // Match if the first character not included in cookie path is "/" + return \substr($requestPath, \strlen($cookiePath), 1) === '/'; + } + + /** + * Check if the cookie matches a domain value. + * + * @param string $domain Domain to check against + */ + public function matchesDomain(string $domain): bool + { + $cookieDomain = $this->getDomain(); + if (null === $cookieDomain) { + return true; + } + + // Remove the leading '.' as per spec in RFC 6265. + // https://tools.ietf.org/html/rfc6265#section-5.2.3 + $cookieDomain = \ltrim($cookieDomain, '.'); + + // Domain not set or exact match. + if (!$cookieDomain || !\strcasecmp($domain, $cookieDomain)) { + return true; + } + + // Matching the subdomain according to RFC 6265. + // https://tools.ietf.org/html/rfc6265#section-5.1.3 + if (\filter_var($domain, \FILTER_VALIDATE_IP)) { + return false; + } + + return (bool) \preg_match('/\.' . \preg_quote($cookieDomain, '/') . '$/', $domain); + } + + /** + * Check if the cookie is expired. + */ + public function isExpired(): bool + { + return $this->getExpires() !== null && \time() > $this->getExpires(); + } + + /** + * Check if the cookie is valid according to RFC 6265. + * + * @return bool|string Returns true if valid or an error message if invalid + */ + public function validate() + { + $name = $this->getName(); + if ($name === '') { + return 'The cookie name must not be empty'; + } + + // Check if any of the invalid characters are present in the cookie name + if (\preg_match( + '/[\x00-\x20\x22\x28-\x29\x2c\x2f\x3a-\x40\x5c\x7b\x7d\x7f]/', + $name + )) { + return 'Cookie name must not contain invalid characters: ASCII ' + . 'Control characters (0-31;127), space, tab and the ' + . 'following characters: ()<>@,;:\"/?={}'; + } + + // Value must not be null. 0 and empty string are valid. Empty strings + // are technically against RFC 6265, but known to happen in the wild. + $value = $this->getValue(); + if ($value === null) { + return 'The cookie value must not be empty'; + } + + // Domains must not be empty, but can be 0. "0" is not a valid internet + // domain, but may be used as server name in a private network. + $domain = $this->getDomain(); + if ($domain === null || $domain === '') { + return 'The cookie domain must not be empty'; + } + + return true; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php b/vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php new file mode 100644 index 0000000..a80956c --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php @@ -0,0 +1,39 @@ +request = $request; + $this->handlerContext = $handlerContext; + } + + /** + * Get the request that caused the exception + */ + public function getRequest(): RequestInterface + { + return $this->request; + } + + /** + * Get contextual information about the error from the underlying handler. + * + * The contents of this array will vary depending on which handler you are + * using. It may also be just an empty array. Relying on this data will + * couple you to a specific handler, but can give more debug information + * when needed. + */ + public function getHandlerContext(): array + { + return $this->handlerContext; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Exception/GuzzleException.php b/vendor/guzzlehttp/guzzle/src/Exception/GuzzleException.php new file mode 100644 index 0000000..fa3ed69 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Exception/GuzzleException.php @@ -0,0 +1,9 @@ +getStatusCode() : 0; + parent::__construct($message, $code, $previous); + $this->request = $request; + $this->response = $response; + $this->handlerContext = $handlerContext; + } + + /** + * Wrap non-RequestExceptions with a RequestException + */ + public static function wrapException(RequestInterface $request, \Throwable $e): RequestException + { + return $e instanceof RequestException ? $e : new RequestException($e->getMessage(), $request, null, $e); + } + + /** + * Factory method to create a new exception with a normalized error message + * + * @param RequestInterface $request Request sent + * @param ResponseInterface $response Response received + * @param \Throwable|null $previous Previous exception + * @param array $handlerContext Optional handler context + * @param BodySummarizerInterface|null $bodySummarizer Optional body summarizer + */ + public static function create( + RequestInterface $request, + ResponseInterface $response = null, + \Throwable $previous = null, + array $handlerContext = [], + BodySummarizerInterface $bodySummarizer = null + ): self { + if (!$response) { + return new self( + 'Error completing request', + $request, + null, + $previous, + $handlerContext + ); + } + + $level = (int) \floor($response->getStatusCode() / 100); + if ($level === 4) { + $label = 'Client error'; + $className = ClientException::class; + } elseif ($level === 5) { + $label = 'Server error'; + $className = ServerException::class; + } else { + $label = 'Unsuccessful request'; + $className = __CLASS__; + } + + $uri = $request->getUri(); + $uri = static::obfuscateUri($uri); + + // Client Error: `GET /` resulted in a `404 Not Found` response: + // ... (truncated) + $message = \sprintf( + '%s: `%s %s` resulted in a `%s %s` response', + $label, + $request->getMethod(), + $uri->__toString(), + $response->getStatusCode(), + $response->getReasonPhrase() + ); + + $summary = ($bodySummarizer ?? new BodySummarizer())->summarize($response); + + if ($summary !== null) { + $message .= ":\n{$summary}\n"; + } + + return new $className($message, $request, $response, $previous, $handlerContext); + } + + /** + * Obfuscates URI if there is a username and a password present + */ + private static function obfuscateUri(UriInterface $uri): UriInterface + { + $userInfo = $uri->getUserInfo(); + + if (false !== ($pos = \strpos($userInfo, ':'))) { + return $uri->withUserInfo(\substr($userInfo, 0, $pos), '***'); + } + + return $uri; + } + + /** + * Get the request that caused the exception + */ + public function getRequest(): RequestInterface + { + return $this->request; + } + + /** + * Get the associated response + */ + public function getResponse(): ?ResponseInterface + { + return $this->response; + } + + /** + * Check if a response was received + */ + public function hasResponse(): bool + { + return $this->response !== null; + } + + /** + * Get contextual information about the error from the underlying handler. + * + * The contents of this array will vary depending on which handler you are + * using. It may also be just an empty array. Relying on this data will + * couple you to a specific handler, but can give more debug information + * when needed. + */ + public function getHandlerContext(): array + { + return $this->handlerContext; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php b/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php new file mode 100644 index 0000000..8055e06 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php @@ -0,0 +1,10 @@ +maxHandles = $maxHandles; + } + + public function create(RequestInterface $request, array $options): EasyHandle + { + if (isset($options['curl']['body_as_string'])) { + $options['_body_as_string'] = $options['curl']['body_as_string']; + unset($options['curl']['body_as_string']); + } + + $easy = new EasyHandle; + $easy->request = $request; + $easy->options = $options; + $conf = $this->getDefaultConf($easy); + $this->applyMethod($easy, $conf); + $this->applyHandlerOptions($easy, $conf); + $this->applyHeaders($easy, $conf); + unset($conf['_headers']); + + // Add handler options from the request configuration options + if (isset($options['curl'])) { + $conf = \array_replace($conf, $options['curl']); + } + + $conf[\CURLOPT_HEADERFUNCTION] = $this->createHeaderFn($easy); + $easy->handle = $this->handles ? \array_pop($this->handles) : \curl_init(); + curl_setopt_array($easy->handle, $conf); + + return $easy; + } + + public function release(EasyHandle $easy): void + { + $resource = $easy->handle; + unset($easy->handle); + + if (\count($this->handles) >= $this->maxHandles) { + \curl_close($resource); + } else { + // Remove all callback functions as they can hold onto references + // and are not cleaned up by curl_reset. Using curl_setopt_array + // does not work for some reason, so removing each one + // individually. + \curl_setopt($resource, \CURLOPT_HEADERFUNCTION, null); + \curl_setopt($resource, \CURLOPT_READFUNCTION, null); + \curl_setopt($resource, \CURLOPT_WRITEFUNCTION, null); + \curl_setopt($resource, \CURLOPT_PROGRESSFUNCTION, null); + \curl_reset($resource); + $this->handles[] = $resource; + } + } + + /** + * Completes a cURL transaction, either returning a response promise or a + * rejected promise. + * + * @param callable(RequestInterface, array): PromiseInterface $handler + * @param CurlFactoryInterface $factory Dictates how the handle is released + */ + public static function finish(callable $handler, EasyHandle $easy, CurlFactoryInterface $factory): PromiseInterface + { + if (isset($easy->options['on_stats'])) { + self::invokeStats($easy); + } + + if (!$easy->response || $easy->errno) { + return self::finishError($handler, $easy, $factory); + } + + // Return the response if it is present and there is no error. + $factory->release($easy); + + // Rewind the body of the response if possible. + $body = $easy->response->getBody(); + if ($body->isSeekable()) { + $body->rewind(); + } + + return new FulfilledPromise($easy->response); + } + + private static function invokeStats(EasyHandle $easy): void + { + $curlStats = \curl_getinfo($easy->handle); + $curlStats['appconnect_time'] = \curl_getinfo($easy->handle, \CURLINFO_APPCONNECT_TIME); + $stats = new TransferStats( + $easy->request, + $easy->response, + $curlStats['total_time'], + $easy->errno, + $curlStats + ); + ($easy->options['on_stats'])($stats); + } + + /** + * @param callable(RequestInterface, array): PromiseInterface $handler + */ + private static function finishError(callable $handler, EasyHandle $easy, CurlFactoryInterface $factory): PromiseInterface + { + // Get error information and release the handle to the factory. + $ctx = [ + 'errno' => $easy->errno, + 'error' => \curl_error($easy->handle), + 'appconnect_time' => \curl_getinfo($easy->handle, \CURLINFO_APPCONNECT_TIME), + ] + \curl_getinfo($easy->handle); + $ctx[self::CURL_VERSION_STR] = \curl_version()['version']; + $factory->release($easy); + + // Retry when nothing is present or when curl failed to rewind. + if (empty($easy->options['_err_message']) && (!$easy->errno || $easy->errno == 65)) { + return self::retryFailedRewind($handler, $easy, $ctx); + } + + return self::createRejection($easy, $ctx); + } + + private static function createRejection(EasyHandle $easy, array $ctx): PromiseInterface + { + static $connectionErrors = [ + \CURLE_OPERATION_TIMEOUTED => true, + \CURLE_COULDNT_RESOLVE_HOST => true, + \CURLE_COULDNT_CONNECT => true, + \CURLE_SSL_CONNECT_ERROR => true, + \CURLE_GOT_NOTHING => true, + ]; + + if ($easy->createResponseException) { + return P\Create::rejectionFor( + new RequestException( + 'An error was encountered while creating the response', + $easy->request, + $easy->response, + $easy->createResponseException, + $ctx + ) + ); + } + + // If an exception was encountered during the onHeaders event, then + // return a rejected promise that wraps that exception. + if ($easy->onHeadersException) { + return P\Create::rejectionFor( + new RequestException( + 'An error was encountered during the on_headers event', + $easy->request, + $easy->response, + $easy->onHeadersException, + $ctx + ) + ); + } + + $message = \sprintf( + 'cURL error %s: %s (%s)', + $ctx['errno'], + $ctx['error'], + 'see https://curl.haxx.se/libcurl/c/libcurl-errors.html' + ); + $uriString = (string) $easy->request->getUri(); + if ($uriString !== '' && false === \strpos($ctx['error'], $uriString)) { + $message .= \sprintf(' for %s', $uriString); + } + + // Create a connection exception if it was a specific error code. + $error = isset($connectionErrors[$easy->errno]) + ? new ConnectException($message, $easy->request, null, $ctx) + : new RequestException($message, $easy->request, $easy->response, null, $ctx); + + return P\Create::rejectionFor($error); + } + + /** + * @return array + */ + private function getDefaultConf(EasyHandle $easy): array + { + $conf = [ + '_headers' => $easy->request->getHeaders(), + \CURLOPT_CUSTOMREQUEST => $easy->request->getMethod(), + \CURLOPT_URL => (string) $easy->request->getUri()->withFragment(''), + \CURLOPT_RETURNTRANSFER => false, + \CURLOPT_HEADER => false, + \CURLOPT_CONNECTTIMEOUT => 150, + ]; + + if (\defined('CURLOPT_PROTOCOLS')) { + $conf[\CURLOPT_PROTOCOLS] = \CURLPROTO_HTTP | \CURLPROTO_HTTPS; + } + + $version = $easy->request->getProtocolVersion(); + if ($version == 1.1) { + $conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_1; + } elseif ($version == 2.0) { + $conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_2_0; + } else { + $conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_0; + } + + return $conf; + } + + private function applyMethod(EasyHandle $easy, array &$conf): void + { + $body = $easy->request->getBody(); + $size = $body->getSize(); + + if ($size === null || $size > 0) { + $this->applyBody($easy->request, $easy->options, $conf); + return; + } + + $method = $easy->request->getMethod(); + if ($method === 'PUT' || $method === 'POST') { + // See https://tools.ietf.org/html/rfc7230#section-3.3.2 + if (!$easy->request->hasHeader('Content-Length')) { + $conf[\CURLOPT_HTTPHEADER][] = 'Content-Length: 0'; + } + } elseif ($method === 'HEAD') { + $conf[\CURLOPT_NOBODY] = true; + unset( + $conf[\CURLOPT_WRITEFUNCTION], + $conf[\CURLOPT_READFUNCTION], + $conf[\CURLOPT_FILE], + $conf[\CURLOPT_INFILE] + ); + } + } + + private function applyBody(RequestInterface $request, array $options, array &$conf): void + { + $size = $request->hasHeader('Content-Length') + ? (int) $request->getHeaderLine('Content-Length') + : null; + + // Send the body as a string if the size is less than 1MB OR if the + // [curl][body_as_string] request value is set. + if (($size !== null && $size < 1000000) || !empty($options['_body_as_string'])) { + $conf[\CURLOPT_POSTFIELDS] = (string) $request->getBody(); + // Don't duplicate the Content-Length header + $this->removeHeader('Content-Length', $conf); + $this->removeHeader('Transfer-Encoding', $conf); + } else { + $conf[\CURLOPT_UPLOAD] = true; + if ($size !== null) { + $conf[\CURLOPT_INFILESIZE] = $size; + $this->removeHeader('Content-Length', $conf); + } + $body = $request->getBody(); + if ($body->isSeekable()) { + $body->rewind(); + } + $conf[\CURLOPT_READFUNCTION] = static function ($ch, $fd, $length) use ($body) { + return $body->read($length); + }; + } + + // If the Expect header is not present, prevent curl from adding it + if (!$request->hasHeader('Expect')) { + $conf[\CURLOPT_HTTPHEADER][] = 'Expect:'; + } + + // cURL sometimes adds a content-type by default. Prevent this. + if (!$request->hasHeader('Content-Type')) { + $conf[\CURLOPT_HTTPHEADER][] = 'Content-Type:'; + } + } + + private function applyHeaders(EasyHandle $easy, array &$conf): void + { + foreach ($conf['_headers'] as $name => $values) { + foreach ($values as $value) { + $value = (string) $value; + if ($value === '') { + // cURL requires a special format for empty headers. + // See https://github.com/guzzle/guzzle/issues/1882 for more details. + $conf[\CURLOPT_HTTPHEADER][] = "$name;"; + } else { + $conf[\CURLOPT_HTTPHEADER][] = "$name: $value"; + } + } + } + + // Remove the Accept header if one was not set + if (!$easy->request->hasHeader('Accept')) { + $conf[\CURLOPT_HTTPHEADER][] = 'Accept:'; + } + } + + /** + * Remove a header from the options array. + * + * @param string $name Case-insensitive header to remove + * @param array $options Array of options to modify + */ + private function removeHeader(string $name, array &$options): void + { + foreach (\array_keys($options['_headers']) as $key) { + if (!\strcasecmp($key, $name)) { + unset($options['_headers'][$key]); + return; + } + } + } + + private function applyHandlerOptions(EasyHandle $easy, array &$conf): void + { + $options = $easy->options; + if (isset($options['verify'])) { + if ($options['verify'] === false) { + unset($conf[\CURLOPT_CAINFO]); + $conf[\CURLOPT_SSL_VERIFYHOST] = 0; + $conf[\CURLOPT_SSL_VERIFYPEER] = false; + } else { + $conf[\CURLOPT_SSL_VERIFYHOST] = 2; + $conf[\CURLOPT_SSL_VERIFYPEER] = true; + if (\is_string($options['verify'])) { + // Throw an error if the file/folder/link path is not valid or doesn't exist. + if (!\file_exists($options['verify'])) { + throw new \InvalidArgumentException("SSL CA bundle not found: {$options['verify']}"); + } + // If it's a directory or a link to a directory use CURLOPT_CAPATH. + // If not, it's probably a file, or a link to a file, so use CURLOPT_CAINFO. + if ( + \is_dir($options['verify']) || + ( + \is_link($options['verify']) === true && + ($verifyLink = \readlink($options['verify'])) !== false && + \is_dir($verifyLink) + ) + ) { + $conf[\CURLOPT_CAPATH] = $options['verify']; + } else { + $conf[\CURLOPT_CAINFO] = $options['verify']; + } + } + } + } + + if (!isset($options['curl'][\CURLOPT_ENCODING]) && !empty($options['decode_content'])) { + $accept = $easy->request->getHeaderLine('Accept-Encoding'); + if ($accept) { + $conf[\CURLOPT_ENCODING] = $accept; + } else { + // The empty string enables all available decoders and implicitly + // sets a matching 'Accept-Encoding' header. + $conf[\CURLOPT_ENCODING] = ''; + // But as the user did not specify any acceptable encodings we need + // to overwrite this implicit header with an empty one. + $conf[\CURLOPT_HTTPHEADER][] = 'Accept-Encoding:'; + } + } + + if (!isset($options['sink'])) { + // Use a default temp stream if no sink was set. + $options['sink'] = \GuzzleHttp\Psr7\Utils::tryFopen('php://temp', 'w+'); + } + $sink = $options['sink']; + if (!\is_string($sink)) { + $sink = \GuzzleHttp\Psr7\Utils::streamFor($sink); + } elseif (!\is_dir(\dirname($sink))) { + // Ensure that the directory exists before failing in curl. + throw new \RuntimeException(\sprintf('Directory %s does not exist for sink value of %s', \dirname($sink), $sink)); + } else { + $sink = new LazyOpenStream($sink, 'w+'); + } + $easy->sink = $sink; + $conf[\CURLOPT_WRITEFUNCTION] = static function ($ch, $write) use ($sink): int { + return $sink->write($write); + }; + + $timeoutRequiresNoSignal = false; + if (isset($options['timeout'])) { + $timeoutRequiresNoSignal |= $options['timeout'] < 1; + $conf[\CURLOPT_TIMEOUT_MS] = $options['timeout'] * 1000; + } + + // CURL default value is CURL_IPRESOLVE_WHATEVER + if (isset($options['force_ip_resolve'])) { + if ('v4' === $options['force_ip_resolve']) { + $conf[\CURLOPT_IPRESOLVE] = \CURL_IPRESOLVE_V4; + } elseif ('v6' === $options['force_ip_resolve']) { + $conf[\CURLOPT_IPRESOLVE] = \CURL_IPRESOLVE_V6; + } + } + + if (isset($options['connect_timeout'])) { + $timeoutRequiresNoSignal |= $options['connect_timeout'] < 1; + $conf[\CURLOPT_CONNECTTIMEOUT_MS] = $options['connect_timeout'] * 1000; + } + + if ($timeoutRequiresNoSignal && \strtoupper(\substr(\PHP_OS, 0, 3)) !== 'WIN') { + $conf[\CURLOPT_NOSIGNAL] = true; + } + + if (isset($options['proxy'])) { + if (!\is_array($options['proxy'])) { + $conf[\CURLOPT_PROXY] = $options['proxy']; + } else { + $scheme = $easy->request->getUri()->getScheme(); + if (isset($options['proxy'][$scheme])) { + $host = $easy->request->getUri()->getHost(); + if (!isset($options['proxy']['no']) || !Utils::isHostInNoProxy($host, $options['proxy']['no'])) { + $conf[\CURLOPT_PROXY] = $options['proxy'][$scheme]; + } + } + } + } + + if (isset($options['cert'])) { + $cert = $options['cert']; + if (\is_array($cert)) { + $conf[\CURLOPT_SSLCERTPASSWD] = $cert[1]; + $cert = $cert[0]; + } + if (!\file_exists($cert)) { + throw new \InvalidArgumentException("SSL certificate not found: {$cert}"); + } + # OpenSSL (versions 0.9.3 and later) also support "P12" for PKCS#12-encoded files. + # see https://curl.se/libcurl/c/CURLOPT_SSLCERTTYPE.html + $ext = pathinfo($cert, \PATHINFO_EXTENSION); + if (preg_match('#^(der|p12)$#i', $ext)) { + $conf[\CURLOPT_SSLCERTTYPE] = strtoupper($ext); + } + $conf[\CURLOPT_SSLCERT] = $cert; + } + + if (isset($options['ssl_key'])) { + if (\is_array($options['ssl_key'])) { + if (\count($options['ssl_key']) === 2) { + [$sslKey, $conf[\CURLOPT_SSLKEYPASSWD]] = $options['ssl_key']; + } else { + [$sslKey] = $options['ssl_key']; + } + } + + $sslKey = $sslKey ?? $options['ssl_key']; + + if (!\file_exists($sslKey)) { + throw new \InvalidArgumentException("SSL private key not found: {$sslKey}"); + } + $conf[\CURLOPT_SSLKEY] = $sslKey; + } + + if (isset($options['progress'])) { + $progress = $options['progress']; + if (!\is_callable($progress)) { + throw new \InvalidArgumentException('progress client option must be callable'); + } + $conf[\CURLOPT_NOPROGRESS] = false; + $conf[\CURLOPT_PROGRESSFUNCTION] = static function ($resource, int $downloadSize, int $downloaded, int $uploadSize, int $uploaded) use ($progress) { + $progress($downloadSize, $downloaded, $uploadSize, $uploaded); + }; + } + + if (!empty($options['debug'])) { + $conf[\CURLOPT_STDERR] = Utils::debugResource($options['debug']); + $conf[\CURLOPT_VERBOSE] = true; + } + } + + /** + * This function ensures that a response was set on a transaction. If one + * was not set, then the request is retried if possible. This error + * typically means you are sending a payload, curl encountered a + * "Connection died, retrying a fresh connect" error, tried to rewind the + * stream, and then encountered a "necessary data rewind wasn't possible" + * error, causing the request to be sent through curl_multi_info_read() + * without an error status. + * + * @param callable(RequestInterface, array): PromiseInterface $handler + */ + private static function retryFailedRewind(callable $handler, EasyHandle $easy, array $ctx): PromiseInterface + { + try { + // Only rewind if the body has been read from. + $body = $easy->request->getBody(); + if ($body->tell() > 0) { + $body->rewind(); + } + } catch (\RuntimeException $e) { + $ctx['error'] = 'The connection unexpectedly failed without ' + . 'providing an error. The request would have been retried, ' + . 'but attempting to rewind the request body failed. ' + . 'Exception: ' . $e; + return self::createRejection($easy, $ctx); + } + + // Retry no more than 3 times before giving up. + if (!isset($easy->options['_curl_retries'])) { + $easy->options['_curl_retries'] = 1; + } elseif ($easy->options['_curl_retries'] == 2) { + $ctx['error'] = 'The cURL request was retried 3 times ' + . 'and did not succeed. The most likely reason for the failure ' + . 'is that cURL was unable to rewind the body of the request ' + . 'and subsequent retries resulted in the same error. Turn on ' + . 'the debug option to see what went wrong. See ' + . 'https://bugs.php.net/bug.php?id=47204 for more information.'; + return self::createRejection($easy, $ctx); + } else { + $easy->options['_curl_retries']++; + } + + return $handler($easy->request, $easy->options); + } + + private function createHeaderFn(EasyHandle $easy): callable + { + if (isset($easy->options['on_headers'])) { + $onHeaders = $easy->options['on_headers']; + + if (!\is_callable($onHeaders)) { + throw new \InvalidArgumentException('on_headers must be callable'); + } + } else { + $onHeaders = null; + } + + return static function ($ch, $h) use ( + $onHeaders, + $easy, + &$startingResponse + ) { + $value = \trim($h); + if ($value === '') { + $startingResponse = true; + try { + $easy->createResponse(); + } catch (\Exception $e) { + $easy->createResponseException = $e; + return -1; + } + if ($onHeaders !== null) { + try { + $onHeaders($easy->response); + } catch (\Exception $e) { + // Associate the exception with the handle and trigger + // a curl header write error by returning 0. + $easy->onHeadersException = $e; + return -1; + } + } + } elseif ($startingResponse) { + $startingResponse = false; + $easy->headers = [$value]; + } else { + $easy->headers[] = $value; + } + return \strlen($h); + }; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php b/vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php new file mode 100644 index 0000000..fe57ed5 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php @@ -0,0 +1,25 @@ +factory = $options['handle_factory'] + ?? new CurlFactory(3); + } + + public function __invoke(RequestInterface $request, array $options): PromiseInterface + { + if (isset($options['delay'])) { + \usleep($options['delay'] * 1000); + } + + $easy = $this->factory->create($request, $options); + \curl_exec($easy->handle); + $easy->errno = \curl_errno($easy->handle); + + return CurlFactory::finish($this, $easy, $this->factory); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php b/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php new file mode 100644 index 0000000..9e2e470 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php @@ -0,0 +1,261 @@ + An array of delay times, indexed by handle id in `addRequest`. + * + * @see CurlMultiHandler::addRequest + */ + private $delays = []; + + /** + * @var array An associative array of CURLMOPT_* options and corresponding values for curl_multi_setopt() + */ + private $options = []; + + /** + * This handler accepts the following options: + * + * - handle_factory: An optional factory used to create curl handles + * - select_timeout: Optional timeout (in seconds) to block before timing + * out while selecting curl handles. Defaults to 1 second. + * - options: An associative array of CURLMOPT_* options and + * corresponding values for curl_multi_setopt() + */ + public function __construct(array $options = []) + { + $this->factory = $options['handle_factory'] ?? new CurlFactory(50); + + if (isset($options['select_timeout'])) { + $this->selectTimeout = $options['select_timeout']; + } elseif ($selectTimeout = Utils::getenv('GUZZLE_CURL_SELECT_TIMEOUT')) { + @trigger_error('Since guzzlehttp/guzzle 7.2.0: Using environment variable GUZZLE_CURL_SELECT_TIMEOUT is deprecated. Use option "select_timeout" instead.', \E_USER_DEPRECATED); + $this->selectTimeout = (int) $selectTimeout; + } else { + $this->selectTimeout = 1; + } + + $this->options = $options['options'] ?? []; + } + + /** + * @param string $name + * + * @return resource|\CurlMultiHandle + * + * @throws \BadMethodCallException when another field as `_mh` will be gotten + * @throws \RuntimeException when curl can not initialize a multi handle + */ + public function __get($name) + { + if ($name !== '_mh') { + throw new \BadMethodCallException("Can not get other property as '_mh'."); + } + + $multiHandle = \curl_multi_init(); + + if (false === $multiHandle) { + throw new \RuntimeException('Can not initialize curl multi handle.'); + } + + $this->_mh = $multiHandle; + + foreach ($this->options as $option => $value) { + // A warning is raised in case of a wrong option. + curl_multi_setopt($this->_mh, $option, $value); + } + + return $this->_mh; + } + + public function __destruct() + { + if (isset($this->_mh)) { + \curl_multi_close($this->_mh); + unset($this->_mh); + } + } + + public function __invoke(RequestInterface $request, array $options): PromiseInterface + { + $easy = $this->factory->create($request, $options); + $id = (int) $easy->handle; + + $promise = new Promise( + [$this, 'execute'], + function () use ($id) { + return $this->cancel($id); + } + ); + + $this->addRequest(['easy' => $easy, 'deferred' => $promise]); + + return $promise; + } + + /** + * Ticks the curl event loop. + */ + public function tick(): void + { + // Add any delayed handles if needed. + if ($this->delays) { + $currentTime = Utils::currentTime(); + foreach ($this->delays as $id => $delay) { + if ($currentTime >= $delay) { + unset($this->delays[$id]); + \curl_multi_add_handle( + $this->_mh, + $this->handles[$id]['easy']->handle + ); + } + } + } + + // Step through the task queue which may add additional requests. + P\Utils::queue()->run(); + + if ($this->active && \curl_multi_select($this->_mh, $this->selectTimeout) === -1) { + // Perform a usleep if a select returns -1. + // See: https://bugs.php.net/bug.php?id=61141 + \usleep(250); + } + + while (\curl_multi_exec($this->_mh, $this->active) === \CURLM_CALL_MULTI_PERFORM); + + $this->processMessages(); + } + + /** + * Runs until all outstanding connections have completed. + */ + public function execute(): void + { + $queue = P\Utils::queue(); + + while ($this->handles || !$queue->isEmpty()) { + // If there are no transfers, then sleep for the next delay + if (!$this->active && $this->delays) { + \usleep($this->timeToNext()); + } + $this->tick(); + } + } + + private function addRequest(array $entry): void + { + $easy = $entry['easy']; + $id = (int) $easy->handle; + $this->handles[$id] = $entry; + if (empty($easy->options['delay'])) { + \curl_multi_add_handle($this->_mh, $easy->handle); + } else { + $this->delays[$id] = Utils::currentTime() + ($easy->options['delay'] / 1000); + } + } + + /** + * Cancels a handle from sending and removes references to it. + * + * @param int $id Handle ID to cancel and remove. + * + * @return bool True on success, false on failure. + */ + private function cancel($id): bool + { + if (!is_int($id)) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing an integer to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + // Cannot cancel if it has been processed. + if (!isset($this->handles[$id])) { + return false; + } + + $handle = $this->handles[$id]['easy']->handle; + unset($this->delays[$id], $this->handles[$id]); + \curl_multi_remove_handle($this->_mh, $handle); + \curl_close($handle); + + return true; + } + + private function processMessages(): void + { + while ($done = \curl_multi_info_read($this->_mh)) { + if ($done['msg'] !== \CURLMSG_DONE) { + // if it's not done, then it would be premature to remove the handle. ref https://github.com/guzzle/guzzle/pull/2892#issuecomment-945150216 + continue; + } + $id = (int) $done['handle']; + \curl_multi_remove_handle($this->_mh, $done['handle']); + + if (!isset($this->handles[$id])) { + // Probably was cancelled. + continue; + } + + $entry = $this->handles[$id]; + unset($this->handles[$id], $this->delays[$id]); + $entry['easy']->errno = $done['result']; + $entry['deferred']->resolve( + CurlFactory::finish($this, $entry['easy'], $this->factory) + ); + } + } + + private function timeToNext(): int + { + $currentTime = Utils::currentTime(); + $nextTime = \PHP_INT_MAX; + foreach ($this->delays as $time) { + if ($time < $nextTime) { + $nextTime = $time; + } + } + + return ((int) \max(0, $nextTime - $currentTime)) * 1000000; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php b/vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php new file mode 100644 index 0000000..224344d --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php @@ -0,0 +1,112 @@ +headers); + + $normalizedKeys = Utils::normalizeHeaderKeys($headers); + + if (!empty($this->options['decode_content']) && isset($normalizedKeys['content-encoding'])) { + $headers['x-encoded-content-encoding'] = $headers[$normalizedKeys['content-encoding']]; + unset($headers[$normalizedKeys['content-encoding']]); + if (isset($normalizedKeys['content-length'])) { + $headers['x-encoded-content-length'] = $headers[$normalizedKeys['content-length']]; + + $bodyLength = (int) $this->sink->getSize(); + if ($bodyLength) { + $headers[$normalizedKeys['content-length']] = $bodyLength; + } else { + unset($headers[$normalizedKeys['content-length']]); + } + } + } + + // Attach a response to the easy handle with the parsed headers. + $this->response = new Response( + $status, + $headers, + $this->sink, + $ver, + $reason + ); + } + + /** + * @param string $name + * + * @return void + * + * @throws \BadMethodCallException + */ + public function __get($name) + { + $msg = $name === 'handle' ? 'The EasyHandle has been released' : 'Invalid property: ' . $name; + throw new \BadMethodCallException($msg); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Handler/HeaderProcessor.php b/vendor/guzzlehttp/guzzle/src/Handler/HeaderProcessor.php new file mode 100644 index 0000000..a098884 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Handler/HeaderProcessor.php @@ -0,0 +1,42 @@ +|null $queue The parameters to be passed to the append function, as an indexed array. + * @param callable|null $onFulfilled Callback to invoke when the return value is fulfilled. + * @param callable|null $onRejected Callback to invoke when the return value is rejected. + */ + public function __construct(array $queue = null, callable $onFulfilled = null, callable $onRejected = null) + { + $this->onFulfilled = $onFulfilled; + $this->onRejected = $onRejected; + + if ($queue) { + // array_values included for BC + $this->append(...array_values($queue)); + } + } + + public function __invoke(RequestInterface $request, array $options): PromiseInterface + { + if (!$this->queue) { + throw new \OutOfBoundsException('Mock queue is empty'); + } + + if (isset($options['delay']) && \is_numeric($options['delay'])) { + \usleep((int) $options['delay'] * 1000); + } + + $this->lastRequest = $request; + $this->lastOptions = $options; + $response = \array_shift($this->queue); + + if (isset($options['on_headers'])) { + if (!\is_callable($options['on_headers'])) { + throw new \InvalidArgumentException('on_headers must be callable'); + } + try { + $options['on_headers']($response); + } catch (\Exception $e) { + $msg = 'An error was encountered during the on_headers event'; + $response = new RequestException($msg, $request, $response, $e); + } + } + + if (\is_callable($response)) { + $response = $response($request, $options); + } + + $response = $response instanceof \Throwable + ? P\Create::rejectionFor($response) + : P\Create::promiseFor($response); + + return $response->then( + function (?ResponseInterface $value) use ($request, $options) { + $this->invokeStats($request, $options, $value); + if ($this->onFulfilled) { + ($this->onFulfilled)($value); + } + + if ($value !== null && isset($options['sink'])) { + $contents = (string) $value->getBody(); + $sink = $options['sink']; + + if (\is_resource($sink)) { + \fwrite($sink, $contents); + } elseif (\is_string($sink)) { + \file_put_contents($sink, $contents); + } elseif ($sink instanceof StreamInterface) { + $sink->write($contents); + } + } + + return $value; + }, + function ($reason) use ($request, $options) { + $this->invokeStats($request, $options, null, $reason); + if ($this->onRejected) { + ($this->onRejected)($reason); + } + return P\Create::rejectionFor($reason); + } + ); + } + + /** + * Adds one or more variadic requests, exceptions, callables, or promises + * to the queue. + * + * @param mixed ...$values + */ + public function append(...$values): void + { + foreach ($values as $value) { + if ($value instanceof ResponseInterface + || $value instanceof \Throwable + || $value instanceof PromiseInterface + || \is_callable($value) + ) { + $this->queue[] = $value; + } else { + throw new \TypeError('Expected a Response, Promise, Throwable or callable. Found ' . Utils::describeType($value)); + } + } + } + + /** + * Get the last received request. + */ + public function getLastRequest(): ?RequestInterface + { + return $this->lastRequest; + } + + /** + * Get the last received request options. + */ + public function getLastOptions(): array + { + return $this->lastOptions; + } + + /** + * Returns the number of remaining items in the queue. + */ + public function count(): int + { + return \count($this->queue); + } + + public function reset(): void + { + $this->queue = []; + } + + /** + * @param mixed $reason Promise or reason. + */ + private function invokeStats( + RequestInterface $request, + array $options, + ResponseInterface $response = null, + $reason = null + ): void { + if (isset($options['on_stats'])) { + $transferTime = $options['transfer_time'] ?? 0; + $stats = new TransferStats($request, $response, $transferTime, $reason); + ($options['on_stats'])($stats); + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php b/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php new file mode 100644 index 0000000..f045b52 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php @@ -0,0 +1,51 @@ +withoutHeader('Expect'); + + // Append a content-length header if body size is zero to match + // cURL's behavior. + if (0 === $request->getBody()->getSize()) { + $request = $request->withHeader('Content-Length', '0'); + } + + return $this->createResponse( + $request, + $options, + $this->createStream($request, $options), + $startTime + ); + } catch (\InvalidArgumentException $e) { + throw $e; + } catch (\Exception $e) { + // Determine if the error was a networking error. + $message = $e->getMessage(); + // This list can probably get more comprehensive. + if (false !== \strpos($message, 'getaddrinfo') // DNS lookup failed + || false !== \strpos($message, 'Connection refused') + || false !== \strpos($message, "couldn't connect to host") // error on HHVM + || false !== \strpos($message, "connection attempt failed") + ) { + $e = new ConnectException($e->getMessage(), $request, $e); + } else { + $e = RequestException::wrapException($request, $e); + } + $this->invokeStats($options, $request, $startTime, null, $e); + + return P\Create::rejectionFor($e); + } + } + + private function invokeStats( + array $options, + RequestInterface $request, + ?float $startTime, + ResponseInterface $response = null, + \Throwable $error = null + ): void { + if (isset($options['on_stats'])) { + $stats = new TransferStats($request, $response, Utils::currentTime() - $startTime, $error, []); + ($options['on_stats'])($stats); + } + } + + /** + * @param resource $stream + */ + private function createResponse(RequestInterface $request, array $options, $stream, ?float $startTime): PromiseInterface + { + $hdrs = $this->lastHeaders; + $this->lastHeaders = []; + + try { + [$ver, $status, $reason, $headers] = HeaderProcessor::parseHeaders($hdrs); + } catch (\Exception $e) { + return P\Create::rejectionFor( + new RequestException('An error was encountered while creating the response', $request, null, $e) + ); + } + + [$stream, $headers] = $this->checkDecode($options, $headers, $stream); + $stream = Psr7\Utils::streamFor($stream); + $sink = $stream; + + if (\strcasecmp('HEAD', $request->getMethod())) { + $sink = $this->createSink($stream, $options); + } + + try { + $response = new Psr7\Response($status, $headers, $sink, $ver, $reason); + } catch (\Exception $e) { + return P\Create::rejectionFor( + new RequestException('An error was encountered while creating the response', $request, null, $e) + ); + } + + if (isset($options['on_headers'])) { + try { + $options['on_headers']($response); + } catch (\Exception $e) { + return P\Create::rejectionFor( + new RequestException('An error was encountered during the on_headers event', $request, $response, $e) + ); + } + } + + // Do not drain when the request is a HEAD request because they have + // no body. + if ($sink !== $stream) { + $this->drain($stream, $sink, $response->getHeaderLine('Content-Length')); + } + + $this->invokeStats($options, $request, $startTime, $response, null); + + return new FulfilledPromise($response); + } + + private function createSink(StreamInterface $stream, array $options): StreamInterface + { + if (!empty($options['stream'])) { + return $stream; + } + + $sink = $options['sink'] ?? Psr7\Utils::tryFopen('php://temp', 'r+'); + + return \is_string($sink) ? new Psr7\LazyOpenStream($sink, 'w+') : Psr7\Utils::streamFor($sink); + } + + /** + * @param resource $stream + */ + private function checkDecode(array $options, array $headers, $stream): array + { + // Automatically decode responses when instructed. + if (!empty($options['decode_content'])) { + $normalizedKeys = Utils::normalizeHeaderKeys($headers); + if (isset($normalizedKeys['content-encoding'])) { + $encoding = $headers[$normalizedKeys['content-encoding']]; + if ($encoding[0] === 'gzip' || $encoding[0] === 'deflate') { + $stream = new Psr7\InflateStream(Psr7\Utils::streamFor($stream)); + $headers['x-encoded-content-encoding'] = $headers[$normalizedKeys['content-encoding']]; + + // Remove content-encoding header + unset($headers[$normalizedKeys['content-encoding']]); + + // Fix content-length header + if (isset($normalizedKeys['content-length'])) { + $headers['x-encoded-content-length'] = $headers[$normalizedKeys['content-length']]; + $length = (int) $stream->getSize(); + if ($length === 0) { + unset($headers[$normalizedKeys['content-length']]); + } else { + $headers[$normalizedKeys['content-length']] = [$length]; + } + } + } + } + } + + return [$stream, $headers]; + } + + /** + * Drains the source stream into the "sink" client option. + * + * @param string $contentLength Header specifying the amount of + * data to read. + * + * @throws \RuntimeException when the sink option is invalid. + */ + private function drain(StreamInterface $source, StreamInterface $sink, string $contentLength): StreamInterface + { + // If a content-length header is provided, then stop reading once + // that number of bytes has been read. This can prevent infinitely + // reading from a stream when dealing with servers that do not honor + // Connection: Close headers. + Psr7\Utils::copyToStream( + $source, + $sink, + (\strlen($contentLength) > 0 && (int) $contentLength > 0) ? (int) $contentLength : -1 + ); + + $sink->seek(0); + $source->close(); + + return $sink; + } + + /** + * Create a resource and check to ensure it was created successfully + * + * @param callable $callback Callable that returns stream resource + * + * @return resource + * + * @throws \RuntimeException on error + */ + private function createResource(callable $callback) + { + $errors = []; + \set_error_handler(static function ($_, $msg, $file, $line) use (&$errors): bool { + $errors[] = [ + 'message' => $msg, + 'file' => $file, + 'line' => $line + ]; + return true; + }); + + try { + $resource = $callback(); + } finally { + \restore_error_handler(); + } + + if (!$resource) { + $message = 'Error creating resource: '; + foreach ($errors as $err) { + foreach ($err as $key => $value) { + $message .= "[$key] $value" . \PHP_EOL; + } + } + throw new \RuntimeException(\trim($message)); + } + + return $resource; + } + + /** + * @return resource + */ + private function createStream(RequestInterface $request, array $options) + { + static $methods; + if (!$methods) { + $methods = \array_flip(\get_class_methods(__CLASS__)); + } + + // HTTP/1.1 streams using the PHP stream wrapper require a + // Connection: close header + if ($request->getProtocolVersion() == '1.1' + && !$request->hasHeader('Connection') + ) { + $request = $request->withHeader('Connection', 'close'); + } + + // Ensure SSL is verified by default + if (!isset($options['verify'])) { + $options['verify'] = true; + } + + $params = []; + $context = $this->getDefaultContext($request); + + if (isset($options['on_headers']) && !\is_callable($options['on_headers'])) { + throw new \InvalidArgumentException('on_headers must be callable'); + } + + if (!empty($options)) { + foreach ($options as $key => $value) { + $method = "add_{$key}"; + if (isset($methods[$method])) { + $this->{$method}($request, $context, $value, $params); + } + } + } + + if (isset($options['stream_context'])) { + if (!\is_array($options['stream_context'])) { + throw new \InvalidArgumentException('stream_context must be an array'); + } + $context = \array_replace_recursive($context, $options['stream_context']); + } + + // Microsoft NTLM authentication only supported with curl handler + if (isset($options['auth'][2]) && 'ntlm' === $options['auth'][2]) { + throw new \InvalidArgumentException('Microsoft NTLM authentication only supported with curl handler'); + } + + $uri = $this->resolveHost($request, $options); + + $contextResource = $this->createResource( + static function () use ($context, $params) { + return \stream_context_create($context, $params); + } + ); + + return $this->createResource( + function () use ($uri, &$http_response_header, $contextResource, $context, $options, $request) { + $resource = @\fopen((string) $uri, 'r', false, $contextResource); + $this->lastHeaders = $http_response_header; + + if (false === $resource) { + throw new ConnectException(sprintf('Connection refused for URI %s', $uri), $request, null, $context); + } + + if (isset($options['read_timeout'])) { + $readTimeout = $options['read_timeout']; + $sec = (int) $readTimeout; + $usec = ($readTimeout - $sec) * 100000; + \stream_set_timeout($resource, $sec, $usec); + } + + return $resource; + } + ); + } + + private function resolveHost(RequestInterface $request, array $options): UriInterface + { + $uri = $request->getUri(); + + if (isset($options['force_ip_resolve']) && !\filter_var($uri->getHost(), \FILTER_VALIDATE_IP)) { + if ('v4' === $options['force_ip_resolve']) { + $records = \dns_get_record($uri->getHost(), \DNS_A); + if (false === $records || !isset($records[0]['ip'])) { + throw new ConnectException(\sprintf("Could not resolve IPv4 address for host '%s'", $uri->getHost()), $request); + } + return $uri->withHost($records[0]['ip']); + } + if ('v6' === $options['force_ip_resolve']) { + $records = \dns_get_record($uri->getHost(), \DNS_AAAA); + if (false === $records || !isset($records[0]['ipv6'])) { + throw new ConnectException(\sprintf("Could not resolve IPv6 address for host '%s'", $uri->getHost()), $request); + } + return $uri->withHost('[' . $records[0]['ipv6'] . ']'); + } + } + + return $uri; + } + + private function getDefaultContext(RequestInterface $request): array + { + $headers = ''; + foreach ($request->getHeaders() as $name => $value) { + foreach ($value as $val) { + $headers .= "$name: $val\r\n"; + } + } + + $context = [ + 'http' => [ + 'method' => $request->getMethod(), + 'header' => $headers, + 'protocol_version' => $request->getProtocolVersion(), + 'ignore_errors' => true, + 'follow_location' => 0, + ], + ]; + + $body = (string) $request->getBody(); + + if (!empty($body)) { + $context['http']['content'] = $body; + // Prevent the HTTP handler from adding a Content-Type header. + if (!$request->hasHeader('Content-Type')) { + $context['http']['header'] .= "Content-Type:\r\n"; + } + } + + $context['http']['header'] = \rtrim($context['http']['header']); + + return $context; + } + + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_proxy(RequestInterface $request, array &$options, $value, array &$params): void + { + $uri = null; + + if (!\is_array($value)) { + $uri = $value; + } else { + $scheme = $request->getUri()->getScheme(); + if (isset($value[$scheme])) { + if (!isset($value['no']) || !Utils::isHostInNoProxy($request->getUri()->getHost(), $value['no'])) { + $uri = $value[$scheme]; + } + } + } + + if (!$uri) { + return; + } + + $parsed = $this->parse_proxy($uri); + $options['http']['proxy'] = $parsed['proxy']; + + if ($parsed['auth']) { + if (!isset($options['http']['header'])) { + $options['http']['header'] = []; + } + $options['http']['header'] .= "\r\nProxy-Authorization: {$parsed['auth']}"; + } + } + + /** + * Parses the given proxy URL to make it compatible with the format PHP's stream context expects. + */ + private function parse_proxy(string $url): array + { + $parsed = \parse_url($url); + + if ($parsed !== false && isset($parsed['scheme']) && $parsed['scheme'] === 'http') { + if (isset($parsed['host']) && isset($parsed['port'])) { + $auth = null; + if (isset($parsed['user']) && isset($parsed['pass'])) { + $auth = \base64_encode("{$parsed['user']}:{$parsed['pass']}"); + } + + return [ + 'proxy' => "tcp://{$parsed['host']}:{$parsed['port']}", + 'auth' => $auth ? "Basic {$auth}" : null, + ]; + } + } + + // Return proxy as-is. + return [ + 'proxy' => $url, + 'auth' => null, + ]; + } + + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_timeout(RequestInterface $request, array &$options, $value, array &$params): void + { + if ($value > 0) { + $options['http']['timeout'] = $value; + } + } + + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_verify(RequestInterface $request, array &$options, $value, array &$params): void + { + if ($value === false) { + $options['ssl']['verify_peer'] = false; + $options['ssl']['verify_peer_name'] = false; + + return; + } + + if (\is_string($value)) { + $options['ssl']['cafile'] = $value; + if (!\file_exists($value)) { + throw new \RuntimeException("SSL CA bundle not found: $value"); + } + } elseif ($value !== true) { + throw new \InvalidArgumentException('Invalid verify request option'); + } + + $options['ssl']['verify_peer'] = true; + $options['ssl']['verify_peer_name'] = true; + $options['ssl']['allow_self_signed'] = false; + } + + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_cert(RequestInterface $request, array &$options, $value, array &$params): void + { + if (\is_array($value)) { + $options['ssl']['passphrase'] = $value[1]; + $value = $value[0]; + } + + if (!\file_exists($value)) { + throw new \RuntimeException("SSL certificate not found: {$value}"); + } + + $options['ssl']['local_cert'] = $value; + } + + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_progress(RequestInterface $request, array &$options, $value, array &$params): void + { + self::addNotification( + $params, + static function ($code, $a, $b, $c, $transferred, $total) use ($value) { + if ($code == \STREAM_NOTIFY_PROGRESS) { + // The upload progress cannot be determined. Use 0 for cURL compatibility: + // https://curl.se/libcurl/c/CURLOPT_PROGRESSFUNCTION.html + $value($total, $transferred, 0, 0); + } + } + ); + } + + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_debug(RequestInterface $request, array &$options, $value, array &$params): void + { + if ($value === false) { + return; + } + + static $map = [ + \STREAM_NOTIFY_CONNECT => 'CONNECT', + \STREAM_NOTIFY_AUTH_REQUIRED => 'AUTH_REQUIRED', + \STREAM_NOTIFY_AUTH_RESULT => 'AUTH_RESULT', + \STREAM_NOTIFY_MIME_TYPE_IS => 'MIME_TYPE_IS', + \STREAM_NOTIFY_FILE_SIZE_IS => 'FILE_SIZE_IS', + \STREAM_NOTIFY_REDIRECTED => 'REDIRECTED', + \STREAM_NOTIFY_PROGRESS => 'PROGRESS', + \STREAM_NOTIFY_FAILURE => 'FAILURE', + \STREAM_NOTIFY_COMPLETED => 'COMPLETED', + \STREAM_NOTIFY_RESOLVE => 'RESOLVE', + ]; + static $args = ['severity', 'message', 'message_code', 'bytes_transferred', 'bytes_max']; + + $value = Utils::debugResource($value); + $ident = $request->getMethod() . ' ' . $request->getUri()->withFragment(''); + self::addNotification( + $params, + static function (int $code, ...$passed) use ($ident, $value, $map, $args): void { + \fprintf($value, '<%s> [%s] ', $ident, $map[$code]); + foreach (\array_filter($passed) as $i => $v) { + \fwrite($value, $args[$i] . ': "' . $v . '" '); + } + \fwrite($value, "\n"); + } + ); + } + + private static function addNotification(array &$params, callable $notify): void + { + // Wrap the existing function if needed. + if (!isset($params['notification'])) { + $params['notification'] = $notify; + } else { + $params['notification'] = self::callArray([ + $params['notification'], + $notify + ]); + } + } + + private static function callArray(array $functions): callable + { + return static function (...$args) use ($functions) { + foreach ($functions as $fn) { + $fn(...$args); + } + }; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/HandlerStack.php b/vendor/guzzlehttp/guzzle/src/HandlerStack.php new file mode 100644 index 0000000..e0a1d11 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/HandlerStack.php @@ -0,0 +1,275 @@ +push(Middleware::httpErrors(), 'http_errors'); + $stack->push(Middleware::redirect(), 'allow_redirects'); + $stack->push(Middleware::cookies(), 'cookies'); + $stack->push(Middleware::prepareBody(), 'prepare_body'); + + return $stack; + } + + /** + * @param (callable(RequestInterface, array): PromiseInterface)|null $handler Underlying HTTP handler. + */ + public function __construct(callable $handler = null) + { + $this->handler = $handler; + } + + /** + * Invokes the handler stack as a composed handler + * + * @return ResponseInterface|PromiseInterface + */ + public function __invoke(RequestInterface $request, array $options) + { + $handler = $this->resolve(); + + return $handler($request, $options); + } + + /** + * Dumps a string representation of the stack. + * + * @return string + */ + public function __toString() + { + $depth = 0; + $stack = []; + + if ($this->handler !== null) { + $stack[] = "0) Handler: " . $this->debugCallable($this->handler); + } + + $result = ''; + foreach (\array_reverse($this->stack) as $tuple) { + $depth++; + $str = "{$depth}) Name: '{$tuple[1]}', "; + $str .= "Function: " . $this->debugCallable($tuple[0]); + $result = "> {$str}\n{$result}"; + $stack[] = $str; + } + + foreach (\array_keys($stack) as $k) { + $result .= "< {$stack[$k]}\n"; + } + + return $result; + } + + /** + * Set the HTTP handler that actually returns a promise. + * + * @param callable(RequestInterface, array): PromiseInterface $handler Accepts a request and array of options and + * returns a Promise. + */ + public function setHandler(callable $handler): void + { + $this->handler = $handler; + $this->cached = null; + } + + /** + * Returns true if the builder has a handler. + */ + public function hasHandler(): bool + { + return $this->handler !== null ; + } + + /** + * Unshift a middleware to the bottom of the stack. + * + * @param callable(callable): callable $middleware Middleware function + * @param string $name Name to register for this middleware. + */ + public function unshift(callable $middleware, ?string $name = null): void + { + \array_unshift($this->stack, [$middleware, $name]); + $this->cached = null; + } + + /** + * Push a middleware to the top of the stack. + * + * @param callable(callable): callable $middleware Middleware function + * @param string $name Name to register for this middleware. + */ + public function push(callable $middleware, string $name = ''): void + { + $this->stack[] = [$middleware, $name]; + $this->cached = null; + } + + /** + * Add a middleware before another middleware by name. + * + * @param string $findName Middleware to find + * @param callable(callable): callable $middleware Middleware function + * @param string $withName Name to register for this middleware. + */ + public function before(string $findName, callable $middleware, string $withName = ''): void + { + $this->splice($findName, $withName, $middleware, true); + } + + /** + * Add a middleware after another middleware by name. + * + * @param string $findName Middleware to find + * @param callable(callable): callable $middleware Middleware function + * @param string $withName Name to register for this middleware. + */ + public function after(string $findName, callable $middleware, string $withName = ''): void + { + $this->splice($findName, $withName, $middleware, false); + } + + /** + * Remove a middleware by instance or name from the stack. + * + * @param callable|string $remove Middleware to remove by instance or name. + */ + public function remove($remove): void + { + if (!is_string($remove) && !is_callable($remove)) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a callable or string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->cached = null; + $idx = \is_callable($remove) ? 0 : 1; + $this->stack = \array_values(\array_filter( + $this->stack, + static function ($tuple) use ($idx, $remove) { + return $tuple[$idx] !== $remove; + } + )); + } + + /** + * Compose the middleware and handler into a single callable function. + * + * @return callable(RequestInterface, array): PromiseInterface + */ + public function resolve(): callable + { + if ($this->cached === null) { + if (($prev = $this->handler) === null) { + throw new \LogicException('No handler has been specified'); + } + + foreach (\array_reverse($this->stack) as $fn) { + /** @var callable(RequestInterface, array): PromiseInterface $prev */ + $prev = $fn[0]($prev); + } + + $this->cached = $prev; + } + + return $this->cached; + } + + private function findByName(string $name): int + { + foreach ($this->stack as $k => $v) { + if ($v[1] === $name) { + return $k; + } + } + + throw new \InvalidArgumentException("Middleware not found: $name"); + } + + /** + * Splices a function into the middleware list at a specific position. + */ + private function splice(string $findName, string $withName, callable $middleware, bool $before): void + { + $this->cached = null; + $idx = $this->findByName($findName); + $tuple = [$middleware, $withName]; + + if ($before) { + if ($idx === 0) { + \array_unshift($this->stack, $tuple); + } else { + $replacement = [$tuple, $this->stack[$idx]]; + \array_splice($this->stack, $idx, 1, $replacement); + } + } elseif ($idx === \count($this->stack) - 1) { + $this->stack[] = $tuple; + } else { + $replacement = [$this->stack[$idx], $tuple]; + \array_splice($this->stack, $idx, 1, $replacement); + } + } + + /** + * Provides a debug string for a given callable. + * + * @param callable|string $fn Function to write as a string. + */ + private function debugCallable($fn): string + { + if (\is_string($fn)) { + return "callable({$fn})"; + } + + if (\is_array($fn)) { + return \is_string($fn[0]) + ? "callable({$fn[0]}::{$fn[1]})" + : "callable(['" . \get_class($fn[0]) . "', '{$fn[1]}'])"; + } + + /** @var object $fn */ + return 'callable(' . \spl_object_hash($fn) . ')'; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/MessageFormatter.php b/vendor/guzzlehttp/guzzle/src/MessageFormatter.php new file mode 100644 index 0000000..da49954 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/MessageFormatter.php @@ -0,0 +1,198 @@ +>>>>>>>\n{request}\n<<<<<<<<\n{response}\n--------\n{error}"; + public const SHORT = '[{ts}] "{method} {target} HTTP/{version}" {code}'; + + /** + * @var string Template used to format log messages + */ + private $template; + + /** + * @param string $template Log message template + */ + public function __construct(?string $template = self::CLF) + { + $this->template = $template ?: self::CLF; + } + + /** + * Returns a formatted message string. + * + * @param RequestInterface $request Request that was sent + * @param ResponseInterface|null $response Response that was received + * @param \Throwable|null $error Exception that was received + */ + public function format(RequestInterface $request, ?ResponseInterface $response = null, ?\Throwable $error = null): string + { + $cache = []; + + /** @var string */ + return \preg_replace_callback( + '/{\s*([A-Za-z_\-\.0-9]+)\s*}/', + function (array $matches) use ($request, $response, $error, &$cache) { + if (isset($cache[$matches[1]])) { + return $cache[$matches[1]]; + } + + $result = ''; + switch ($matches[1]) { + case 'request': + $result = Psr7\Message::toString($request); + break; + case 'response': + $result = $response ? Psr7\Message::toString($response) : ''; + break; + case 'req_headers': + $result = \trim($request->getMethod() + . ' ' . $request->getRequestTarget()) + . ' HTTP/' . $request->getProtocolVersion() . "\r\n" + . $this->headers($request); + break; + case 'res_headers': + $result = $response ? + \sprintf( + 'HTTP/%s %d %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() + ) . "\r\n" . $this->headers($response) + : 'NULL'; + break; + case 'req_body': + $result = $request->getBody()->__toString(); + break; + case 'res_body': + if (!$response instanceof ResponseInterface) { + $result = 'NULL'; + break; + } + + $body = $response->getBody(); + + if (!$body->isSeekable()) { + $result = 'RESPONSE_NOT_LOGGEABLE'; + break; + } + + $result = $response->getBody()->__toString(); + break; + case 'ts': + case 'date_iso_8601': + $result = \gmdate('c'); + break; + case 'date_common_log': + $result = \date('d/M/Y:H:i:s O'); + break; + case 'method': + $result = $request->getMethod(); + break; + case 'version': + $result = $request->getProtocolVersion(); + break; + case 'uri': + case 'url': + $result = $request->getUri()->__toString(); + break; + case 'target': + $result = $request->getRequestTarget(); + break; + case 'req_version': + $result = $request->getProtocolVersion(); + break; + case 'res_version': + $result = $response + ? $response->getProtocolVersion() + : 'NULL'; + break; + case 'host': + $result = $request->getHeaderLine('Host'); + break; + case 'hostname': + $result = \gethostname(); + break; + case 'code': + $result = $response ? $response->getStatusCode() : 'NULL'; + break; + case 'phrase': + $result = $response ? $response->getReasonPhrase() : 'NULL'; + break; + case 'error': + $result = $error ? $error->getMessage() : 'NULL'; + break; + default: + // handle prefixed dynamic headers + if (\strpos($matches[1], 'req_header_') === 0) { + $result = $request->getHeaderLine(\substr($matches[1], 11)); + } elseif (\strpos($matches[1], 'res_header_') === 0) { + $result = $response + ? $response->getHeaderLine(\substr($matches[1], 11)) + : 'NULL'; + } + } + + $cache[$matches[1]] = $result; + return $result; + }, + $this->template + ); + } + + /** + * Get headers from message as string + */ + private function headers(MessageInterface $message): string + { + $result = ''; + foreach ($message->getHeaders() as $name => $values) { + $result .= $name . ': ' . \implode(', ', $values) . "\r\n"; + } + + return \trim($result); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/MessageFormatterInterface.php b/vendor/guzzlehttp/guzzle/src/MessageFormatterInterface.php new file mode 100644 index 0000000..a39ac24 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/MessageFormatterInterface.php @@ -0,0 +1,18 @@ +withCookieHeader($request); + return $handler($request, $options) + ->then( + static function (ResponseInterface $response) use ($cookieJar, $request): ResponseInterface { + $cookieJar->extractCookies($request, $response); + return $response; + } + ); + }; + }; + } + + /** + * Middleware that throws exceptions for 4xx or 5xx responses when the + * "http_errors" request option is set to true. + * + * @param BodySummarizerInterface|null $bodySummarizer The body summarizer to use in exception messages. + * + * @return callable(callable): callable Returns a function that accepts the next handler. + */ + public static function httpErrors(BodySummarizerInterface $bodySummarizer = null): callable + { + return static function (callable $handler) use ($bodySummarizer): callable { + return static function ($request, array $options) use ($handler, $bodySummarizer) { + if (empty($options['http_errors'])) { + return $handler($request, $options); + } + return $handler($request, $options)->then( + static function (ResponseInterface $response) use ($request, $bodySummarizer) { + $code = $response->getStatusCode(); + if ($code < 400) { + return $response; + } + throw RequestException::create($request, $response, null, [], $bodySummarizer); + } + ); + }; + }; + } + + /** + * Middleware that pushes history data to an ArrayAccess container. + * + * @param array|\ArrayAccess $container Container to hold the history (by reference). + * + * @return callable(callable): callable Returns a function that accepts the next handler. + * + * @throws \InvalidArgumentException if container is not an array or ArrayAccess. + */ + public static function history(&$container): callable + { + if (!\is_array($container) && !$container instanceof \ArrayAccess) { + throw new \InvalidArgumentException('history container must be an array or object implementing ArrayAccess'); + } + + return static function (callable $handler) use (&$container): callable { + return static function (RequestInterface $request, array $options) use ($handler, &$container) { + return $handler($request, $options)->then( + static function ($value) use ($request, &$container, $options) { + $container[] = [ + 'request' => $request, + 'response' => $value, + 'error' => null, + 'options' => $options + ]; + return $value; + }, + static function ($reason) use ($request, &$container, $options) { + $container[] = [ + 'request' => $request, + 'response' => null, + 'error' => $reason, + 'options' => $options + ]; + return P\Create::rejectionFor($reason); + } + ); + }; + }; + } + + /** + * Middleware that invokes a callback before and after sending a request. + * + * The provided listener cannot modify or alter the response. It simply + * "taps" into the chain to be notified before returning the promise. The + * before listener accepts a request and options array, and the after + * listener accepts a request, options array, and response promise. + * + * @param callable $before Function to invoke before forwarding the request. + * @param callable $after Function invoked after forwarding. + * + * @return callable Returns a function that accepts the next handler. + */ + public static function tap(callable $before = null, callable $after = null): callable + { + return static function (callable $handler) use ($before, $after): callable { + return static function (RequestInterface $request, array $options) use ($handler, $before, $after) { + if ($before) { + $before($request, $options); + } + $response = $handler($request, $options); + if ($after) { + $after($request, $options, $response); + } + return $response; + }; + }; + } + + /** + * Middleware that handles request redirects. + * + * @return callable Returns a function that accepts the next handler. + */ + public static function redirect(): callable + { + return static function (callable $handler): RedirectMiddleware { + return new RedirectMiddleware($handler); + }; + } + + /** + * Middleware that retries requests based on the boolean result of + * invoking the provided "decider" function. + * + * If no delay function is provided, a simple implementation of exponential + * backoff will be utilized. + * + * @param callable $decider Function that accepts the number of retries, + * a request, [response], and [exception] and + * returns true if the request is to be retried. + * @param callable $delay Function that accepts the number of retries and + * returns the number of milliseconds to delay. + * + * @return callable Returns a function that accepts the next handler. + */ + public static function retry(callable $decider, callable $delay = null): callable + { + return static function (callable $handler) use ($decider, $delay): RetryMiddleware { + return new RetryMiddleware($decider, $handler, $delay); + }; + } + + /** + * Middleware that logs requests, responses, and errors using a message + * formatter. + * + * @phpstan-param \Psr\Log\LogLevel::* $logLevel Level at which to log requests. + * + * @param LoggerInterface $logger Logs messages. + * @param MessageFormatterInterface|MessageFormatter $formatter Formatter used to create message strings. + * @param string $logLevel Level at which to log requests. + * + * @return callable Returns a function that accepts the next handler. + */ + public static function log(LoggerInterface $logger, $formatter, string $logLevel = 'info'): callable + { + // To be compatible with Guzzle 7.1.x we need to allow users to pass a MessageFormatter + if (!$formatter instanceof MessageFormatter && !$formatter instanceof MessageFormatterInterface) { + throw new \LogicException(sprintf('Argument 2 to %s::log() must be of type %s', self::class, MessageFormatterInterface::class)); + } + + return static function (callable $handler) use ($logger, $formatter, $logLevel): callable { + return static function (RequestInterface $request, array $options = []) use ($handler, $logger, $formatter, $logLevel) { + return $handler($request, $options)->then( + static function ($response) use ($logger, $request, $formatter, $logLevel): ResponseInterface { + $message = $formatter->format($request, $response); + $logger->log($logLevel, $message); + return $response; + }, + static function ($reason) use ($logger, $request, $formatter): PromiseInterface { + $response = $reason instanceof RequestException ? $reason->getResponse() : null; + $message = $formatter->format($request, $response, P\Create::exceptionFor($reason)); + $logger->error($message); + return P\Create::rejectionFor($reason); + } + ); + }; + }; + } + + /** + * This middleware adds a default content-type if possible, a default + * content-length or transfer-encoding header, and the expect header. + */ + public static function prepareBody(): callable + { + return static function (callable $handler): PrepareBodyMiddleware { + return new PrepareBodyMiddleware($handler); + }; + } + + /** + * Middleware that applies a map function to the request before passing to + * the next handler. + * + * @param callable $fn Function that accepts a RequestInterface and returns + * a RequestInterface. + */ + public static function mapRequest(callable $fn): callable + { + return static function (callable $handler) use ($fn): callable { + return static function (RequestInterface $request, array $options) use ($handler, $fn) { + return $handler($fn($request), $options); + }; + }; + } + + /** + * Middleware that applies a map function to the resolved promise's + * response. + * + * @param callable $fn Function that accepts a ResponseInterface and + * returns a ResponseInterface. + */ + public static function mapResponse(callable $fn): callable + { + return static function (callable $handler) use ($fn): callable { + return static function (RequestInterface $request, array $options) use ($handler, $fn) { + return $handler($request, $options)->then($fn); + }; + }; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Pool.php b/vendor/guzzlehttp/guzzle/src/Pool.php new file mode 100644 index 0000000..6277c61 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Pool.php @@ -0,0 +1,125 @@ + $rfn) { + if ($rfn instanceof RequestInterface) { + yield $key => $client->sendAsync($rfn, $opts); + } elseif (\is_callable($rfn)) { + yield $key => $rfn($opts); + } else { + throw new \InvalidArgumentException('Each value yielded by the iterator must be a Psr7\Http\Message\RequestInterface or a callable that returns a promise that fulfills with a Psr7\Message\Http\ResponseInterface object.'); + } + } + }; + + $this->each = new EachPromise($requests(), $config); + } + + /** + * Get promise + */ + public function promise(): PromiseInterface + { + return $this->each->promise(); + } + + /** + * Sends multiple requests concurrently and returns an array of responses + * and exceptions that uses the same ordering as the provided requests. + * + * IMPORTANT: This method keeps every request and response in memory, and + * as such, is NOT recommended when sending a large number or an + * indeterminate number of requests concurrently. + * + * @param ClientInterface $client Client used to send the requests + * @param array|\Iterator $requests Requests to send concurrently. + * @param array $options Passes through the options available in + * {@see \GuzzleHttp\Pool::__construct} + * + * @return array Returns an array containing the response or an exception + * in the same order that the requests were sent. + * + * @throws \InvalidArgumentException if the event format is incorrect. + */ + public static function batch(ClientInterface $client, $requests, array $options = []): array + { + $res = []; + self::cmpCallback($options, 'fulfilled', $res); + self::cmpCallback($options, 'rejected', $res); + $pool = new static($client, $requests, $options); + $pool->promise()->wait(); + \ksort($res); + + return $res; + } + + /** + * Execute callback(s) + */ + private static function cmpCallback(array &$options, string $name, array &$results): void + { + if (!isset($options[$name])) { + $options[$name] = static function ($v, $k) use (&$results) { + $results[$k] = $v; + }; + } else { + $currentFn = $options[$name]; + $options[$name] = static function ($v, $k) use (&$results, $currentFn) { + $currentFn($v, $k); + $results[$k] = $v; + }; + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php b/vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php new file mode 100644 index 0000000..7ca6283 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php @@ -0,0 +1,104 @@ +nextHandler = $nextHandler; + } + + public function __invoke(RequestInterface $request, array $options): PromiseInterface + { + $fn = $this->nextHandler; + + // Don't do anything if the request has no body. + if ($request->getBody()->getSize() === 0) { + return $fn($request, $options); + } + + $modify = []; + + // Add a default content-type if possible. + if (!$request->hasHeader('Content-Type')) { + if ($uri = $request->getBody()->getMetadata('uri')) { + if (is_string($uri) && $type = Psr7\MimeType::fromFilename($uri)) { + $modify['set_headers']['Content-Type'] = $type; + } + } + } + + // Add a default content-length or transfer-encoding header. + if (!$request->hasHeader('Content-Length') + && !$request->hasHeader('Transfer-Encoding') + ) { + $size = $request->getBody()->getSize(); + if ($size !== null) { + $modify['set_headers']['Content-Length'] = $size; + } else { + $modify['set_headers']['Transfer-Encoding'] = 'chunked'; + } + } + + // Add the expect header if needed. + $this->addExpectHeader($request, $options, $modify); + + return $fn(Psr7\Utils::modifyRequest($request, $modify), $options); + } + + /** + * Add expect header + */ + private function addExpectHeader(RequestInterface $request, array $options, array &$modify): void + { + // Determine if the Expect header should be used + if ($request->hasHeader('Expect')) { + return; + } + + $expect = $options['expect'] ?? null; + + // Return if disabled or if you're not using HTTP/1.1 or HTTP/2.0 + if ($expect === false || $request->getProtocolVersion() < 1.1) { + return; + } + + // The expect header is unconditionally enabled + if ($expect === true) { + $modify['set_headers']['Expect'] = '100-Continue'; + return; + } + + // By default, send the expect header when the payload is > 1mb + if ($expect === null) { + $expect = 1048576; + } + + // Always add if the body cannot be rewound, the size cannot be + // determined, or the size is greater than the cutoff threshold + $body = $request->getBody(); + $size = $body->getSize(); + + if ($size === null || $size >= (int) $expect || !$body->isSeekable()) { + $modify['set_headers']['Expect'] = '100-Continue'; + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php b/vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php new file mode 100644 index 0000000..1dd3861 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php @@ -0,0 +1,216 @@ + 5, + 'protocols' => ['http', 'https'], + 'strict' => false, + 'referer' => false, + 'track_redirects' => false, + ]; + + /** + * @var callable(RequestInterface, array): PromiseInterface + */ + private $nextHandler; + + /** + * @param callable(RequestInterface, array): PromiseInterface $nextHandler Next handler to invoke. + */ + public function __construct(callable $nextHandler) + { + $this->nextHandler = $nextHandler; + } + + public function __invoke(RequestInterface $request, array $options): PromiseInterface + { + $fn = $this->nextHandler; + + if (empty($options['allow_redirects'])) { + return $fn($request, $options); + } + + if ($options['allow_redirects'] === true) { + $options['allow_redirects'] = self::$defaultSettings; + } elseif (!\is_array($options['allow_redirects'])) { + throw new \InvalidArgumentException('allow_redirects must be true, false, or array'); + } else { + // Merge the default settings with the provided settings + $options['allow_redirects'] += self::$defaultSettings; + } + + if (empty($options['allow_redirects']['max'])) { + return $fn($request, $options); + } + + return $fn($request, $options) + ->then(function (ResponseInterface $response) use ($request, $options) { + return $this->checkRedirect($request, $options, $response); + }); + } + + /** + * @return ResponseInterface|PromiseInterface + */ + public function checkRedirect(RequestInterface $request, array $options, ResponseInterface $response) + { + if (\strpos((string) $response->getStatusCode(), '3') !== 0 + || !$response->hasHeader('Location') + ) { + return $response; + } + + $this->guardMax($request, $response, $options); + $nextRequest = $this->modifyRequest($request, $options, $response); + + if (isset($options['allow_redirects']['on_redirect'])) { + ($options['allow_redirects']['on_redirect'])( + $request, + $response, + $nextRequest->getUri() + ); + } + + $promise = $this($nextRequest, $options); + + // Add headers to be able to track history of redirects. + if (!empty($options['allow_redirects']['track_redirects'])) { + return $this->withTracking( + $promise, + (string) $nextRequest->getUri(), + $response->getStatusCode() + ); + } + + return $promise; + } + + /** + * Enable tracking on promise. + */ + private function withTracking(PromiseInterface $promise, string $uri, int $statusCode): PromiseInterface + { + return $promise->then( + static function (ResponseInterface $response) use ($uri, $statusCode) { + // Note that we are pushing to the front of the list as this + // would be an earlier response than what is currently present + // in the history header. + $historyHeader = $response->getHeader(self::HISTORY_HEADER); + $statusHeader = $response->getHeader(self::STATUS_HISTORY_HEADER); + \array_unshift($historyHeader, $uri); + \array_unshift($statusHeader, (string) $statusCode); + + return $response->withHeader(self::HISTORY_HEADER, $historyHeader) + ->withHeader(self::STATUS_HISTORY_HEADER, $statusHeader); + } + ); + } + + /** + * Check for too many redirects + * + * @throws TooManyRedirectsException Too many redirects. + */ + private function guardMax(RequestInterface $request, ResponseInterface $response, array &$options): void + { + $current = $options['__redirect_count'] + ?? 0; + $options['__redirect_count'] = $current + 1; + $max = $options['allow_redirects']['max']; + + if ($options['__redirect_count'] > $max) { + throw new TooManyRedirectsException("Will not follow more than {$max} redirects", $request, $response); + } + } + + public function modifyRequest(RequestInterface $request, array $options, ResponseInterface $response): RequestInterface + { + // Request modifications to apply. + $modify = []; + $protocols = $options['allow_redirects']['protocols']; + + // Use a GET request if this is an entity enclosing request and we are + // not forcing RFC compliance, but rather emulating what all browsers + // would do. + $statusCode = $response->getStatusCode(); + if ($statusCode == 303 || + ($statusCode <= 302 && !$options['allow_redirects']['strict']) + ) { + $safeMethods = ['GET', 'HEAD', 'OPTIONS']; + $requestMethod = $request->getMethod(); + + $modify['method'] = in_array($requestMethod, $safeMethods) ? $requestMethod : 'GET'; + $modify['body'] = ''; + } + + $uri = $this->redirectUri($request, $response, $protocols); + if (isset($options['idn_conversion']) && ($options['idn_conversion'] !== false)) { + $idnOptions = ($options['idn_conversion'] === true) ? \IDNA_DEFAULT : $options['idn_conversion']; + $uri = Utils::idnUriConvert($uri, $idnOptions); + } + + $modify['uri'] = $uri; + Psr7\Message::rewindBody($request); + + // Add the Referer header if it is told to do so and only + // add the header if we are not redirecting from https to http. + if ($options['allow_redirects']['referer'] + && $modify['uri']->getScheme() === $request->getUri()->getScheme() + ) { + $uri = $request->getUri()->withUserInfo(''); + $modify['set_headers']['Referer'] = (string) $uri; + } else { + $modify['remove_headers'][] = 'Referer'; + } + + // Remove Authorization header if host is different. + if ($request->getUri()->getHost() !== $modify['uri']->getHost()) { + $modify['remove_headers'][] = 'Authorization'; + } + + return Psr7\Utils::modifyRequest($request, $modify); + } + + /** + * Set the appropriate URL on the request based on the location header + */ + private function redirectUri(RequestInterface $request, ResponseInterface $response, array $protocols): UriInterface + { + $location = Psr7\UriResolver::resolve( + $request->getUri(), + new Psr7\Uri($response->getHeaderLine('Location')) + ); + + // Ensure that the redirect URI is allowed based on the protocols. + if (!\in_array($location->getScheme(), $protocols)) { + throw new BadResponseException(\sprintf('Redirect URI, %s, does not use one of the allowed redirect protocols: %s', $location, \implode(', ', $protocols)), $request, $response); + } + + return $location; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/RequestOptions.php b/vendor/guzzlehttp/guzzle/src/RequestOptions.php new file mode 100644 index 0000000..20b31bc --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/RequestOptions.php @@ -0,0 +1,264 @@ +decider = $decider; + $this->nextHandler = $nextHandler; + $this->delay = $delay ?: __CLASS__ . '::exponentialDelay'; + } + + /** + * Default exponential backoff delay function. + * + * @return int milliseconds. + */ + public static function exponentialDelay(int $retries): int + { + return (int) \pow(2, $retries - 1) * 1000; + } + + public function __invoke(RequestInterface $request, array $options): PromiseInterface + { + if (!isset($options['retries'])) { + $options['retries'] = 0; + } + + $fn = $this->nextHandler; + return $fn($request, $options) + ->then( + $this->onFulfilled($request, $options), + $this->onRejected($request, $options) + ); + } + + /** + * Execute fulfilled closure + */ + private function onFulfilled(RequestInterface $request, array $options): callable + { + return function ($value) use ($request, $options) { + if (!($this->decider)( + $options['retries'], + $request, + $value, + null + )) { + return $value; + } + return $this->doRetry($request, $options, $value); + }; + } + + /** + * Execute rejected closure + */ + private function onRejected(RequestInterface $req, array $options): callable + { + return function ($reason) use ($req, $options) { + if (!($this->decider)( + $options['retries'], + $req, + null, + $reason + )) { + return P\Create::rejectionFor($reason); + } + return $this->doRetry($req, $options); + }; + } + + private function doRetry(RequestInterface $request, array $options, ResponseInterface $response = null): PromiseInterface + { + $options['delay'] = ($this->delay)(++$options['retries'], $response); + + return $this($request, $options); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/TransferStats.php b/vendor/guzzlehttp/guzzle/src/TransferStats.php new file mode 100644 index 0000000..93fa334 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/TransferStats.php @@ -0,0 +1,133 @@ +request = $request; + $this->response = $response; + $this->transferTime = $transferTime; + $this->handlerErrorData = $handlerErrorData; + $this->handlerStats = $handlerStats; + } + + public function getRequest(): RequestInterface + { + return $this->request; + } + + /** + * Returns the response that was received (if any). + */ + public function getResponse(): ?ResponseInterface + { + return $this->response; + } + + /** + * Returns true if a response was received. + */ + public function hasResponse(): bool + { + return $this->response !== null; + } + + /** + * Gets handler specific error data. + * + * This might be an exception, a integer representing an error code, or + * anything else. Relying on this value assumes that you know what handler + * you are using. + * + * @return mixed + */ + public function getHandlerErrorData() + { + return $this->handlerErrorData; + } + + /** + * Get the effective URI the request was sent to. + */ + public function getEffectiveUri(): UriInterface + { + return $this->request->getUri(); + } + + /** + * Get the estimated time the request was being transferred by the handler. + * + * @return float|null Time in seconds. + */ + public function getTransferTime(): ?float + { + return $this->transferTime; + } + + /** + * Gets an array of all of the handler specific transfer data. + */ + public function getHandlerStats(): array + { + return $this->handlerStats; + } + + /** + * Get a specific handler statistic from the handler by name. + * + * @param string $stat Handler specific transfer stat to retrieve. + * + * @return mixed|null + */ + public function getHandlerStat(string $stat) + { + return $this->handlerStats[$stat] ?? null; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Utils.php b/vendor/guzzlehttp/guzzle/src/Utils.php new file mode 100644 index 0000000..91591da --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Utils.php @@ -0,0 +1,382 @@ +getHost()) { + $asciiHost = self::idnToAsci($uri->getHost(), $options, $info); + if ($asciiHost === false) { + $errorBitSet = $info['errors'] ?? 0; + + $errorConstants = array_filter(array_keys(get_defined_constants()), static function (string $name): bool { + return substr($name, 0, 11) === 'IDNA_ERROR_'; + }); + + $errors = []; + foreach ($errorConstants as $errorConstant) { + if ($errorBitSet & constant($errorConstant)) { + $errors[] = $errorConstant; + } + } + + $errorMessage = 'IDN conversion failed'; + if ($errors) { + $errorMessage .= ' (errors: ' . implode(', ', $errors) . ')'; + } + + throw new InvalidArgumentException($errorMessage); + } + if ($uri->getHost() !== $asciiHost) { + // Replace URI only if the ASCII version is different + $uri = $uri->withHost($asciiHost); + } + } + + return $uri; + } + + /** + * @internal + */ + public static function getenv(string $name): ?string + { + if (isset($_SERVER[$name])) { + return (string) $_SERVER[$name]; + } + + if (\PHP_SAPI === 'cli' && ($value = \getenv($name)) !== false && $value !== null) { + return (string) $value; + } + + return null; + } + + /** + * @return string|false + */ + private static function idnToAsci(string $domain, int $options, ?array &$info = []) + { + if (\function_exists('idn_to_ascii') && \defined('INTL_IDNA_VARIANT_UTS46')) { + return \idn_to_ascii($domain, $options, \INTL_IDNA_VARIANT_UTS46, $info); + } + + throw new \Error('ext-idn or symfony/polyfill-intl-idn not loaded or too old'); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/functions.php b/vendor/guzzlehttp/guzzle/src/functions.php new file mode 100644 index 0000000..a70d2cb --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/functions.php @@ -0,0 +1,167 @@ + +Copyright (c) 2015 Graham Campbell +Copyright (c) 2017 Tobias Schultze +Copyright (c) 2020 Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/guzzlehttp/promises/Makefile b/vendor/guzzlehttp/promises/Makefile new file mode 100644 index 0000000..8d5b3ef --- /dev/null +++ b/vendor/guzzlehttp/promises/Makefile @@ -0,0 +1,13 @@ +all: clean test + +test: + vendor/bin/phpunit + +coverage: + vendor/bin/phpunit --coverage-html=artifacts/coverage + +view-coverage: + open artifacts/coverage/index.html + +clean: + rm -rf artifacts/* diff --git a/vendor/guzzlehttp/promises/README.md b/vendor/guzzlehttp/promises/README.md new file mode 100644 index 0000000..c175fec --- /dev/null +++ b/vendor/guzzlehttp/promises/README.md @@ -0,0 +1,547 @@ +# Guzzle Promises + +[Promises/A+](https://promisesaplus.com/) implementation that handles promise +chaining and resolution iteratively, allowing for "infinite" promise chaining +while keeping the stack size constant. Read [this blog post](https://blog.domenic.me/youre-missing-the-point-of-promises/) +for a general introduction to promises. + +- [Features](#features) +- [Quick start](#quick-start) +- [Synchronous wait](#synchronous-wait) +- [Cancellation](#cancellation) +- [API](#api) + - [Promise](#promise) + - [FulfilledPromise](#fulfilledpromise) + - [RejectedPromise](#rejectedpromise) +- [Promise interop](#promise-interop) +- [Implementation notes](#implementation-notes) + + +# Features + +- [Promises/A+](https://promisesaplus.com/) implementation. +- Promise resolution and chaining is handled iteratively, allowing for + "infinite" promise chaining. +- Promises have a synchronous `wait` method. +- Promises can be cancelled. +- Works with any object that has a `then` function. +- C# style async/await coroutine promises using + `GuzzleHttp\Promise\Coroutine::of()`. + + +# Quick start + +A *promise* represents the eventual result of an asynchronous operation. The +primary way of interacting with a promise is through its `then` method, which +registers callbacks to receive either a promise's eventual value or the reason +why the promise cannot be fulfilled. + + +## Callbacks + +Callbacks are registered with the `then` method by providing an optional +`$onFulfilled` followed by an optional `$onRejected` function. + + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$promise->then( + // $onFulfilled + function ($value) { + echo 'The promise was fulfilled.'; + }, + // $onRejected + function ($reason) { + echo 'The promise was rejected.'; + } +); +``` + +*Resolving* a promise means that you either fulfill a promise with a *value* or +reject a promise with a *reason*. Resolving a promises triggers callbacks +registered with the promises's `then` method. These callbacks are triggered +only once and in the order in which they were added. + + +## Resolving a promise + +Promises are fulfilled using the `resolve($value)` method. Resolving a promise +with any value other than a `GuzzleHttp\Promise\RejectedPromise` will trigger +all of the onFulfilled callbacks (resolving a promise with a rejected promise +will reject the promise and trigger the `$onRejected` callbacks). + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$promise + ->then(function ($value) { + // Return a value and don't break the chain + return "Hello, " . $value; + }) + // This then is executed after the first then and receives the value + // returned from the first then. + ->then(function ($value) { + echo $value; + }); + +// Resolving the promise triggers the $onFulfilled callbacks and outputs +// "Hello, reader." +$promise->resolve('reader.'); +``` + + +## Promise forwarding + +Promises can be chained one after the other. Each then in the chain is a new +promise. The return value of a promise is what's forwarded to the next +promise in the chain. Returning a promise in a `then` callback will cause the +subsequent promises in the chain to only be fulfilled when the returned promise +has been fulfilled. The next promise in the chain will be invoked with the +resolved value of the promise. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$nextPromise = new Promise(); + +$promise + ->then(function ($value) use ($nextPromise) { + echo $value; + return $nextPromise; + }) + ->then(function ($value) { + echo $value; + }); + +// Triggers the first callback and outputs "A" +$promise->resolve('A'); +// Triggers the second callback and outputs "B" +$nextPromise->resolve('B'); +``` + +## Promise rejection + +When a promise is rejected, the `$onRejected` callbacks are invoked with the +rejection reason. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$promise->then(null, function ($reason) { + echo $reason; +}); + +$promise->reject('Error!'); +// Outputs "Error!" +``` + +## Rejection forwarding + +If an exception is thrown in an `$onRejected` callback, subsequent +`$onRejected` callbacks are invoked with the thrown exception as the reason. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$promise->then(null, function ($reason) { + throw new Exception($reason); +})->then(null, function ($reason) { + assert($reason->getMessage() === 'Error!'); +}); + +$promise->reject('Error!'); +``` + +You can also forward a rejection down the promise chain by returning a +`GuzzleHttp\Promise\RejectedPromise` in either an `$onFulfilled` or +`$onRejected` callback. + +```php +use GuzzleHttp\Promise\Promise; +use GuzzleHttp\Promise\RejectedPromise; + +$promise = new Promise(); +$promise->then(null, function ($reason) { + return new RejectedPromise($reason); +})->then(null, function ($reason) { + assert($reason === 'Error!'); +}); + +$promise->reject('Error!'); +``` + +If an exception is not thrown in a `$onRejected` callback and the callback +does not return a rejected promise, downstream `$onFulfilled` callbacks are +invoked using the value returned from the `$onRejected` callback. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$promise + ->then(null, function ($reason) { + return "It's ok"; + }) + ->then(function ($value) { + assert($value === "It's ok"); + }); + +$promise->reject('Error!'); +``` + +# Synchronous wait + +You can synchronously force promises to complete using a promise's `wait` +method. When creating a promise, you can provide a wait function that is used +to synchronously force a promise to complete. When a wait function is invoked +it is expected to deliver a value to the promise or reject the promise. If the +wait function does not deliver a value, then an exception is thrown. The wait +function provided to a promise constructor is invoked when the `wait` function +of the promise is called. + +```php +$promise = new Promise(function () use (&$promise) { + $promise->resolve('foo'); +}); + +// Calling wait will return the value of the promise. +echo $promise->wait(); // outputs "foo" +``` + +If an exception is encountered while invoking the wait function of a promise, +the promise is rejected with the exception and the exception is thrown. + +```php +$promise = new Promise(function () use (&$promise) { + throw new Exception('foo'); +}); + +$promise->wait(); // throws the exception. +``` + +Calling `wait` on a promise that has been fulfilled will not trigger the wait +function. It will simply return the previously resolved value. + +```php +$promise = new Promise(function () { die('this is not called!'); }); +$promise->resolve('foo'); +echo $promise->wait(); // outputs "foo" +``` + +Calling `wait` on a promise that has been rejected will throw an exception. If +the rejection reason is an instance of `\Exception` the reason is thrown. +Otherwise, a `GuzzleHttp\Promise\RejectionException` is thrown and the reason +can be obtained by calling the `getReason` method of the exception. + +```php +$promise = new Promise(); +$promise->reject('foo'); +$promise->wait(); +``` + +> PHP Fatal error: Uncaught exception 'GuzzleHttp\Promise\RejectionException' with message 'The promise was rejected with value: foo' + + +## Unwrapping a promise + +When synchronously waiting on a promise, you are joining the state of the +promise into the current state of execution (i.e., return the value of the +promise if it was fulfilled or throw an exception if it was rejected). This is +called "unwrapping" the promise. Waiting on a promise will by default unwrap +the promise state. + +You can force a promise to resolve and *not* unwrap the state of the promise +by passing `false` to the first argument of the `wait` function: + +```php +$promise = new Promise(); +$promise->reject('foo'); +// This will not throw an exception. It simply ensures the promise has +// been resolved. +$promise->wait(false); +``` + +When unwrapping a promise, the resolved value of the promise will be waited +upon until the unwrapped value is not a promise. This means that if you resolve +promise A with a promise B and unwrap promise A, the value returned by the +wait function will be the value delivered to promise B. + +**Note**: when you do not unwrap the promise, no value is returned. + + +# Cancellation + +You can cancel a promise that has not yet been fulfilled using the `cancel()` +method of a promise. When creating a promise you can provide an optional +cancel function that when invoked cancels the action of computing a resolution +of the promise. + + +# API + + +## Promise + +When creating a promise object, you can provide an optional `$waitFn` and +`$cancelFn`. `$waitFn` is a function that is invoked with no arguments and is +expected to resolve the promise. `$cancelFn` is a function with no arguments +that is expected to cancel the computation of a promise. It is invoked when the +`cancel()` method of a promise is called. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise( + function () use (&$promise) { + $promise->resolve('waited'); + }, + function () { + // do something that will cancel the promise computation (e.g., close + // a socket, cancel a database query, etc...) + } +); + +assert('waited' === $promise->wait()); +``` + +A promise has the following methods: + +- `then(callable $onFulfilled, callable $onRejected) : PromiseInterface` + + Appends fulfillment and rejection handlers to the promise, and returns a new promise resolving to the return value of the called handler. + +- `otherwise(callable $onRejected) : PromiseInterface` + + Appends a rejection handler callback to the promise, and returns a new promise resolving to the return value of the callback if it is called, or to its original fulfillment value if the promise is instead fulfilled. + +- `wait($unwrap = true) : mixed` + + Synchronously waits on the promise to complete. + + `$unwrap` controls whether or not the value of the promise is returned for a + fulfilled promise or if an exception is thrown if the promise is rejected. + This is set to `true` by default. + +- `cancel()` + + Attempts to cancel the promise if possible. The promise being cancelled and + the parent most ancestor that has not yet been resolved will also be + cancelled. Any promises waiting on the cancelled promise to resolve will also + be cancelled. + +- `getState() : string` + + Returns the state of the promise. One of `pending`, `fulfilled`, or + `rejected`. + +- `resolve($value)` + + Fulfills the promise with the given `$value`. + +- `reject($reason)` + + Rejects the promise with the given `$reason`. + + +## FulfilledPromise + +A fulfilled promise can be created to represent a promise that has been +fulfilled. + +```php +use GuzzleHttp\Promise\FulfilledPromise; + +$promise = new FulfilledPromise('value'); + +// Fulfilled callbacks are immediately invoked. +$promise->then(function ($value) { + echo $value; +}); +``` + + +## RejectedPromise + +A rejected promise can be created to represent a promise that has been +rejected. + +```php +use GuzzleHttp\Promise\RejectedPromise; + +$promise = new RejectedPromise('Error'); + +// Rejected callbacks are immediately invoked. +$promise->then(null, function ($reason) { + echo $reason; +}); +``` + + +# Promise interop + +This library works with foreign promises that have a `then` method. This means +you can use Guzzle promises with [React promises](https://github.com/reactphp/promise) +for example. When a foreign promise is returned inside of a then method +callback, promise resolution will occur recursively. + +```php +// Create a React promise +$deferred = new React\Promise\Deferred(); +$reactPromise = $deferred->promise(); + +// Create a Guzzle promise that is fulfilled with a React promise. +$guzzlePromise = new GuzzleHttp\Promise\Promise(); +$guzzlePromise->then(function ($value) use ($reactPromise) { + // Do something something with the value... + // Return the React promise + return $reactPromise; +}); +``` + +Please note that wait and cancel chaining is no longer possible when forwarding +a foreign promise. You will need to wrap a third-party promise with a Guzzle +promise in order to utilize wait and cancel functions with foreign promises. + + +## Event Loop Integration + +In order to keep the stack size constant, Guzzle promises are resolved +asynchronously using a task queue. When waiting on promises synchronously, the +task queue will be automatically run to ensure that the blocking promise and +any forwarded promises are resolved. When using promises asynchronously in an +event loop, you will need to run the task queue on each tick of the loop. If +you do not run the task queue, then promises will not be resolved. + +You can run the task queue using the `run()` method of the global task queue +instance. + +```php +// Get the global task queue +$queue = GuzzleHttp\Promise\Utils::queue(); +$queue->run(); +``` + +For example, you could use Guzzle promises with React using a periodic timer: + +```php +$loop = React\EventLoop\Factory::create(); +$loop->addPeriodicTimer(0, [$queue, 'run']); +``` + +*TODO*: Perhaps adding a `futureTick()` on each tick would be faster? + + +# Implementation notes + + +## Promise resolution and chaining is handled iteratively + +By shuffling pending handlers from one owner to another, promises are +resolved iteratively, allowing for "infinite" then chaining. + +```php +then(function ($v) { + // The stack size remains constant (a good thing) + echo xdebug_get_stack_depth() . ', '; + return $v + 1; + }); +} + +$parent->resolve(0); +var_dump($p->wait()); // int(1000) + +``` + +When a promise is fulfilled or rejected with a non-promise value, the promise +then takes ownership of the handlers of each child promise and delivers values +down the chain without using recursion. + +When a promise is resolved with another promise, the original promise transfers +all of its pending handlers to the new promise. When the new promise is +eventually resolved, all of the pending handlers are delivered the forwarded +value. + + +## A promise is the deferred. + +Some promise libraries implement promises using a deferred object to represent +a computation and a promise object to represent the delivery of the result of +the computation. This is a nice separation of computation and delivery because +consumers of the promise cannot modify the value that will be eventually +delivered. + +One side effect of being able to implement promise resolution and chaining +iteratively is that you need to be able for one promise to reach into the state +of another promise to shuffle around ownership of handlers. In order to achieve +this without making the handlers of a promise publicly mutable, a promise is +also the deferred value, allowing promises of the same parent class to reach +into and modify the private properties of promises of the same type. While this +does allow consumers of the value to modify the resolution or rejection of the +deferred, it is a small price to pay for keeping the stack size constant. + +```php +$promise = new Promise(); +$promise->then(function ($value) { echo $value; }); +// The promise is the deferred value, so you can deliver a value to it. +$promise->resolve('foo'); +// prints "foo" +``` + + +## Upgrading from Function API + +A static API was first introduced in 1.4.0, in order to mitigate problems with functions conflicting between global and local copies of the package. The function API will be removed in 2.0.0. A migration table has been provided here for your convenience: + +| Original Function | Replacement Method | +|----------------|----------------| +| `queue` | `Utils::queue` | +| `task` | `Utils::task` | +| `promise_for` | `Create::promiseFor` | +| `rejection_for` | `Create::rejectionFor` | +| `exception_for` | `Create::exceptionFor` | +| `iter_for` | `Create::iterFor` | +| `inspect` | `Utils::inspect` | +| `inspect_all` | `Utils::inspectAll` | +| `unwrap` | `Utils::unwrap` | +| `all` | `Utils::all` | +| `some` | `Utils::some` | +| `any` | `Utils::any` | +| `settle` | `Utils::settle` | +| `each` | `Each::of` | +| `each_limit` | `Each::ofLimit` | +| `each_limit_all` | `Each::ofLimitAll` | +| `!is_fulfilled` | `Is::pending` | +| `is_fulfilled` | `Is::fulfilled` | +| `is_rejected` | `Is::rejected` | +| `is_settled` | `Is::settled` | +| `coroutine` | `Coroutine::of` | + + +## Security + +If you discover a security vulnerability within this package, please send an email to security@tidelift.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced. Please see [Security Policy](https://github.com/guzzle/promises/security/policy) for more information. + +## License + +Guzzle is made available under the MIT License (MIT). Please see [License File](LICENSE) for more information. + +## For Enterprise + +Available as part of the Tidelift Subscription + +The maintainers of Guzzle and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-guzzlehttp-promises?utm_source=packagist-guzzlehttp-promises&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) diff --git a/vendor/guzzlehttp/promises/composer.json b/vendor/guzzlehttp/promises/composer.json new file mode 100644 index 0000000..c959fb3 --- /dev/null +++ b/vendor/guzzlehttp/promises/composer.json @@ -0,0 +1,58 @@ +{ + "name": "guzzlehttp/promises", + "description": "Guzzle promises library", + "keywords": ["promise"], + "license": "MIT", + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "require": { + "php": ">=5.5" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.4 || ^5.1" + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": ["src/functions_include.php"] + }, + "autoload-dev": { + "psr-4": { + "GuzzleHttp\\Promise\\Tests\\": "tests/" + } + }, + "scripts": { + "test": "vendor/bin/simple-phpunit", + "test-ci": "vendor/bin/simple-phpunit --coverage-text" + }, + "extra": { + "branch-alias": { + "dev-master": "1.5-dev" + } + }, + "config": { + "preferred-install": "dist", + "sort-packages": true + } +} diff --git a/vendor/guzzlehttp/promises/src/AggregateException.php b/vendor/guzzlehttp/promises/src/AggregateException.php new file mode 100644 index 0000000..d2b5712 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/AggregateException.php @@ -0,0 +1,17 @@ +then(function ($v) { echo $v; }); + * + * @param callable $generatorFn Generator function to wrap into a promise. + * + * @return Promise + * + * @link https://github.com/petkaantonov/bluebird/blob/master/API.md#generators inspiration + */ +final class Coroutine implements PromiseInterface +{ + /** + * @var PromiseInterface|null + */ + private $currentPromise; + + /** + * @var Generator + */ + private $generator; + + /** + * @var Promise + */ + private $result; + + public function __construct(callable $generatorFn) + { + $this->generator = $generatorFn(); + $this->result = new Promise(function () { + while (isset($this->currentPromise)) { + $this->currentPromise->wait(); + } + }); + try { + $this->nextCoroutine($this->generator->current()); + } catch (\Exception $exception) { + $this->result->reject($exception); + } catch (Throwable $throwable) { + $this->result->reject($throwable); + } + } + + /** + * Create a new coroutine. + * + * @return self + */ + public static function of(callable $generatorFn) + { + return new self($generatorFn); + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ) { + return $this->result->then($onFulfilled, $onRejected); + } + + public function otherwise(callable $onRejected) + { + return $this->result->otherwise($onRejected); + } + + public function wait($unwrap = true) + { + return $this->result->wait($unwrap); + } + + public function getState() + { + return $this->result->getState(); + } + + public function resolve($value) + { + $this->result->resolve($value); + } + + public function reject($reason) + { + $this->result->reject($reason); + } + + public function cancel() + { + $this->currentPromise->cancel(); + $this->result->cancel(); + } + + private function nextCoroutine($yielded) + { + $this->currentPromise = Create::promiseFor($yielded) + ->then([$this, '_handleSuccess'], [$this, '_handleFailure']); + } + + /** + * @internal + */ + public function _handleSuccess($value) + { + unset($this->currentPromise); + try { + $next = $this->generator->send($value); + if ($this->generator->valid()) { + $this->nextCoroutine($next); + } else { + $this->result->resolve($value); + } + } catch (Exception $exception) { + $this->result->reject($exception); + } catch (Throwable $throwable) { + $this->result->reject($throwable); + } + } + + /** + * @internal + */ + public function _handleFailure($reason) + { + unset($this->currentPromise); + try { + $nextYield = $this->generator->throw(Create::exceptionFor($reason)); + // The throw was caught, so keep iterating on the coroutine + $this->nextCoroutine($nextYield); + } catch (Exception $exception) { + $this->result->reject($exception); + } catch (Throwable $throwable) { + $this->result->reject($throwable); + } + } +} diff --git a/vendor/guzzlehttp/promises/src/Create.php b/vendor/guzzlehttp/promises/src/Create.php new file mode 100644 index 0000000..8d038e9 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/Create.php @@ -0,0 +1,84 @@ +then([$promise, 'resolve'], [$promise, 'reject']); + return $promise; + } + + return new FulfilledPromise($value); + } + + /** + * Creates a rejected promise for a reason if the reason is not a promise. + * If the provided reason is a promise, then it is returned as-is. + * + * @param mixed $reason Promise or reason. + * + * @return PromiseInterface + */ + public static function rejectionFor($reason) + { + if ($reason instanceof PromiseInterface) { + return $reason; + } + + return new RejectedPromise($reason); + } + + /** + * Create an exception for a rejected promise value. + * + * @param mixed $reason + * + * @return \Exception|\Throwable + */ + public static function exceptionFor($reason) + { + if ($reason instanceof \Exception || $reason instanceof \Throwable) { + return $reason; + } + + return new RejectionException($reason); + } + + /** + * Returns an iterator for the given value. + * + * @param mixed $value + * + * @return \Iterator + */ + public static function iterFor($value) + { + if ($value instanceof \Iterator) { + return $value; + } + + if (is_array($value)) { + return new \ArrayIterator($value); + } + + return new \ArrayIterator([$value]); + } +} diff --git a/vendor/guzzlehttp/promises/src/Each.php b/vendor/guzzlehttp/promises/src/Each.php new file mode 100644 index 0000000..1dda354 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/Each.php @@ -0,0 +1,90 @@ + $onFulfilled, + 'rejected' => $onRejected + ]))->promise(); + } + + /** + * Like of, but only allows a certain number of outstanding promises at any + * given time. + * + * $concurrency may be an integer or a function that accepts the number of + * pending promises and returns a numeric concurrency limit value to allow + * for dynamic a concurrency size. + * + * @param mixed $iterable + * @param int|callable $concurrency + * @param callable $onFulfilled + * @param callable $onRejected + * + * @return PromiseInterface + */ + public static function ofLimit( + $iterable, + $concurrency, + callable $onFulfilled = null, + callable $onRejected = null + ) { + return (new EachPromise($iterable, [ + 'fulfilled' => $onFulfilled, + 'rejected' => $onRejected, + 'concurrency' => $concurrency + ]))->promise(); + } + + /** + * Like limit, but ensures that no promise in the given $iterable argument + * is rejected. If any promise is rejected, then the aggregate promise is + * rejected with the encountered rejection. + * + * @param mixed $iterable + * @param int|callable $concurrency + * @param callable $onFulfilled + * + * @return PromiseInterface + */ + public static function ofLimitAll( + $iterable, + $concurrency, + callable $onFulfilled = null + ) { + return each_limit( + $iterable, + $concurrency, + $onFulfilled, + function ($reason, $idx, PromiseInterface $aggregate) { + $aggregate->reject($reason); + } + ); + } +} diff --git a/vendor/guzzlehttp/promises/src/EachPromise.php b/vendor/guzzlehttp/promises/src/EachPromise.php new file mode 100644 index 0000000..38ecb59 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/EachPromise.php @@ -0,0 +1,255 @@ +iterable = Create::iterFor($iterable); + + if (isset($config['concurrency'])) { + $this->concurrency = $config['concurrency']; + } + + if (isset($config['fulfilled'])) { + $this->onFulfilled = $config['fulfilled']; + } + + if (isset($config['rejected'])) { + $this->onRejected = $config['rejected']; + } + } + + /** @psalm-suppress InvalidNullableReturnType */ + public function promise() + { + if ($this->aggregate) { + return $this->aggregate; + } + + try { + $this->createPromise(); + /** @psalm-assert Promise $this->aggregate */ + $this->iterable->rewind(); + $this->refillPending(); + } catch (\Throwable $e) { + /** + * @psalm-suppress NullReference + * @phpstan-ignore-next-line + */ + $this->aggregate->reject($e); + } catch (\Exception $e) { + /** + * @psalm-suppress NullReference + * @phpstan-ignore-next-line + */ + $this->aggregate->reject($e); + } + + /** + * @psalm-suppress NullableReturnStatement + * @phpstan-ignore-next-line + */ + return $this->aggregate; + } + + private function createPromise() + { + $this->mutex = false; + $this->aggregate = new Promise(function () { + if ($this->checkIfFinished()) { + return; + } + reset($this->pending); + // Consume a potentially fluctuating list of promises while + // ensuring that indexes are maintained (precluding array_shift). + while ($promise = current($this->pending)) { + next($this->pending); + $promise->wait(); + if (Is::settled($this->aggregate)) { + return; + } + } + }); + + // Clear the references when the promise is resolved. + $clearFn = function () { + $this->iterable = $this->concurrency = $this->pending = null; + $this->onFulfilled = $this->onRejected = null; + $this->nextPendingIndex = 0; + }; + + $this->aggregate->then($clearFn, $clearFn); + } + + private function refillPending() + { + if (!$this->concurrency) { + // Add all pending promises. + while ($this->addPending() && $this->advanceIterator()); + return; + } + + // Add only up to N pending promises. + $concurrency = is_callable($this->concurrency) + ? call_user_func($this->concurrency, count($this->pending)) + : $this->concurrency; + $concurrency = max($concurrency - count($this->pending), 0); + // Concurrency may be set to 0 to disallow new promises. + if (!$concurrency) { + return; + } + // Add the first pending promise. + $this->addPending(); + // Note this is special handling for concurrency=1 so that we do + // not advance the iterator after adding the first promise. This + // helps work around issues with generators that might not have the + // next value to yield until promise callbacks are called. + while (--$concurrency + && $this->advanceIterator() + && $this->addPending()); + } + + private function addPending() + { + if (!$this->iterable || !$this->iterable->valid()) { + return false; + } + + $promise = Create::promiseFor($this->iterable->current()); + $key = $this->iterable->key(); + + // Iterable keys may not be unique, so we use a counter to + // guarantee uniqueness + $idx = $this->nextPendingIndex++; + + $this->pending[$idx] = $promise->then( + function ($value) use ($idx, $key) { + if ($this->onFulfilled) { + call_user_func( + $this->onFulfilled, + $value, + $key, + $this->aggregate + ); + } + $this->step($idx); + }, + function ($reason) use ($idx, $key) { + if ($this->onRejected) { + call_user_func( + $this->onRejected, + $reason, + $key, + $this->aggregate + ); + } + $this->step($idx); + } + ); + + return true; + } + + private function advanceIterator() + { + // Place a lock on the iterator so that we ensure to not recurse, + // preventing fatal generator errors. + if ($this->mutex) { + return false; + } + + $this->mutex = true; + + try { + $this->iterable->next(); + $this->mutex = false; + return true; + } catch (\Throwable $e) { + $this->aggregate->reject($e); + $this->mutex = false; + return false; + } catch (\Exception $e) { + $this->aggregate->reject($e); + $this->mutex = false; + return false; + } + } + + private function step($idx) + { + // If the promise was already resolved, then ignore this step. + if (Is::settled($this->aggregate)) { + return; + } + + unset($this->pending[$idx]); + + // Only refill pending promises if we are not locked, preventing the + // EachPromise to recursively invoke the provided iterator, which + // cause a fatal error: "Cannot resume an already running generator" + if ($this->advanceIterator() && !$this->checkIfFinished()) { + // Add more pending promises if possible. + $this->refillPending(); + } + } + + private function checkIfFinished() + { + if (!$this->pending && !$this->iterable->valid()) { + // Resolve the promise if there's nothing left to do. + $this->aggregate->resolve(null); + return true; + } + + return false; + } +} diff --git a/vendor/guzzlehttp/promises/src/FulfilledPromise.php b/vendor/guzzlehttp/promises/src/FulfilledPromise.php new file mode 100644 index 0000000..98f72a6 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/FulfilledPromise.php @@ -0,0 +1,84 @@ +value = $value; + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ) { + // Return itself if there is no onFulfilled function. + if (!$onFulfilled) { + return $this; + } + + $queue = Utils::queue(); + $p = new Promise([$queue, 'run']); + $value = $this->value; + $queue->add(static function () use ($p, $value, $onFulfilled) { + if (Is::pending($p)) { + try { + $p->resolve($onFulfilled($value)); + } catch (\Throwable $e) { + $p->reject($e); + } catch (\Exception $e) { + $p->reject($e); + } + } + }); + + return $p; + } + + public function otherwise(callable $onRejected) + { + return $this->then(null, $onRejected); + } + + public function wait($unwrap = true, $defaultDelivery = null) + { + return $unwrap ? $this->value : null; + } + + public function getState() + { + return self::FULFILLED; + } + + public function resolve($value) + { + if ($value !== $this->value) { + throw new \LogicException("Cannot resolve a fulfilled promise"); + } + } + + public function reject($reason) + { + throw new \LogicException("Cannot reject a fulfilled promise"); + } + + public function cancel() + { + // pass + } +} diff --git a/vendor/guzzlehttp/promises/src/Is.php b/vendor/guzzlehttp/promises/src/Is.php new file mode 100644 index 0000000..c3ed8d0 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/Is.php @@ -0,0 +1,46 @@ +getState() === PromiseInterface::PENDING; + } + + /** + * Returns true if a promise is fulfilled or rejected. + * + * @return bool + */ + public static function settled(PromiseInterface $promise) + { + return $promise->getState() !== PromiseInterface::PENDING; + } + + /** + * Returns true if a promise is fulfilled. + * + * @return bool + */ + public static function fulfilled(PromiseInterface $promise) + { + return $promise->getState() === PromiseInterface::FULFILLED; + } + + /** + * Returns true if a promise is rejected. + * + * @return bool + */ + public static function rejected(PromiseInterface $promise) + { + return $promise->getState() === PromiseInterface::REJECTED; + } +} diff --git a/vendor/guzzlehttp/promises/src/Promise.php b/vendor/guzzlehttp/promises/src/Promise.php new file mode 100644 index 0000000..7593905 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/Promise.php @@ -0,0 +1,278 @@ +waitFn = $waitFn; + $this->cancelFn = $cancelFn; + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ) { + if ($this->state === self::PENDING) { + $p = new Promise(null, [$this, 'cancel']); + $this->handlers[] = [$p, $onFulfilled, $onRejected]; + $p->waitList = $this->waitList; + $p->waitList[] = $this; + return $p; + } + + // Return a fulfilled promise and immediately invoke any callbacks. + if ($this->state === self::FULFILLED) { + $promise = Create::promiseFor($this->result); + return $onFulfilled ? $promise->then($onFulfilled) : $promise; + } + + // It's either cancelled or rejected, so return a rejected promise + // and immediately invoke any callbacks. + $rejection = Create::rejectionFor($this->result); + return $onRejected ? $rejection->then(null, $onRejected) : $rejection; + } + + public function otherwise(callable $onRejected) + { + return $this->then(null, $onRejected); + } + + public function wait($unwrap = true) + { + $this->waitIfPending(); + + if ($this->result instanceof PromiseInterface) { + return $this->result->wait($unwrap); + } + if ($unwrap) { + if ($this->state === self::FULFILLED) { + return $this->result; + } + // It's rejected so "unwrap" and throw an exception. + throw Create::exceptionFor($this->result); + } + } + + public function getState() + { + return $this->state; + } + + public function cancel() + { + if ($this->state !== self::PENDING) { + return; + } + + $this->waitFn = $this->waitList = null; + + if ($this->cancelFn) { + $fn = $this->cancelFn; + $this->cancelFn = null; + try { + $fn(); + } catch (\Throwable $e) { + $this->reject($e); + } catch (\Exception $e) { + $this->reject($e); + } + } + + // Reject the promise only if it wasn't rejected in a then callback. + /** @psalm-suppress RedundantCondition */ + if ($this->state === self::PENDING) { + $this->reject(new CancellationException('Promise has been cancelled')); + } + } + + public function resolve($value) + { + $this->settle(self::FULFILLED, $value); + } + + public function reject($reason) + { + $this->settle(self::REJECTED, $reason); + } + + private function settle($state, $value) + { + if ($this->state !== self::PENDING) { + // Ignore calls with the same resolution. + if ($state === $this->state && $value === $this->result) { + return; + } + throw $this->state === $state + ? new \LogicException("The promise is already {$state}.") + : new \LogicException("Cannot change a {$this->state} promise to {$state}"); + } + + if ($value === $this) { + throw new \LogicException('Cannot fulfill or reject a promise with itself'); + } + + // Clear out the state of the promise but stash the handlers. + $this->state = $state; + $this->result = $value; + $handlers = $this->handlers; + $this->handlers = null; + $this->waitList = $this->waitFn = null; + $this->cancelFn = null; + + if (!$handlers) { + return; + } + + // If the value was not a settled promise or a thenable, then resolve + // it in the task queue using the correct ID. + if (!is_object($value) || !method_exists($value, 'then')) { + $id = $state === self::FULFILLED ? 1 : 2; + // It's a success, so resolve the handlers in the queue. + Utils::queue()->add(static function () use ($id, $value, $handlers) { + foreach ($handlers as $handler) { + self::callHandler($id, $value, $handler); + } + }); + } elseif ($value instanceof Promise && Is::pending($value)) { + // We can just merge our handlers onto the next promise. + $value->handlers = array_merge($value->handlers, $handlers); + } else { + // Resolve the handlers when the forwarded promise is resolved. + $value->then( + static function ($value) use ($handlers) { + foreach ($handlers as $handler) { + self::callHandler(1, $value, $handler); + } + }, + static function ($reason) use ($handlers) { + foreach ($handlers as $handler) { + self::callHandler(2, $reason, $handler); + } + } + ); + } + } + + /** + * Call a stack of handlers using a specific callback index and value. + * + * @param int $index 1 (resolve) or 2 (reject). + * @param mixed $value Value to pass to the callback. + * @param array $handler Array of handler data (promise and callbacks). + */ + private static function callHandler($index, $value, array $handler) + { + /** @var PromiseInterface $promise */ + $promise = $handler[0]; + + // The promise may have been cancelled or resolved before placing + // this thunk in the queue. + if (Is::settled($promise)) { + return; + } + + try { + if (isset($handler[$index])) { + /* + * If $f throws an exception, then $handler will be in the exception + * stack trace. Since $handler contains a reference to the callable + * itself we get a circular reference. We clear the $handler + * here to avoid that memory leak. + */ + $f = $handler[$index]; + unset($handler); + $promise->resolve($f($value)); + } elseif ($index === 1) { + // Forward resolution values as-is. + $promise->resolve($value); + } else { + // Forward rejections down the chain. + $promise->reject($value); + } + } catch (\Throwable $reason) { + $promise->reject($reason); + } catch (\Exception $reason) { + $promise->reject($reason); + } + } + + private function waitIfPending() + { + if ($this->state !== self::PENDING) { + return; + } elseif ($this->waitFn) { + $this->invokeWaitFn(); + } elseif ($this->waitList) { + $this->invokeWaitList(); + } else { + // If there's no wait function, then reject the promise. + $this->reject('Cannot wait on a promise that has ' + . 'no internal wait function. You must provide a wait ' + . 'function when constructing the promise to be able to ' + . 'wait on a promise.'); + } + + Utils::queue()->run(); + + /** @psalm-suppress RedundantCondition */ + if ($this->state === self::PENDING) { + $this->reject('Invoking the wait callback did not resolve the promise'); + } + } + + private function invokeWaitFn() + { + try { + $wfn = $this->waitFn; + $this->waitFn = null; + $wfn(true); + } catch (\Exception $reason) { + if ($this->state === self::PENDING) { + // The promise has not been resolved yet, so reject the promise + // with the exception. + $this->reject($reason); + } else { + // The promise was already resolved, so there's a problem in + // the application. + throw $reason; + } + } + } + + private function invokeWaitList() + { + $waitList = $this->waitList; + $this->waitList = null; + + foreach ($waitList as $result) { + do { + $result->waitIfPending(); + $result = $result->result; + } while ($result instanceof Promise); + + if ($result instanceof PromiseInterface) { + $result->wait(false); + } + } + } +} diff --git a/vendor/guzzlehttp/promises/src/PromiseInterface.php b/vendor/guzzlehttp/promises/src/PromiseInterface.php new file mode 100644 index 0000000..e598331 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/PromiseInterface.php @@ -0,0 +1,97 @@ +reason = $reason; + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ) { + // If there's no onRejected callback then just return self. + if (!$onRejected) { + return $this; + } + + $queue = Utils::queue(); + $reason = $this->reason; + $p = new Promise([$queue, 'run']); + $queue->add(static function () use ($p, $reason, $onRejected) { + if (Is::pending($p)) { + try { + // Return a resolved promise if onRejected does not throw. + $p->resolve($onRejected($reason)); + } catch (\Throwable $e) { + // onRejected threw, so return a rejected promise. + $p->reject($e); + } catch (\Exception $e) { + // onRejected threw, so return a rejected promise. + $p->reject($e); + } + } + }); + + return $p; + } + + public function otherwise(callable $onRejected) + { + return $this->then(null, $onRejected); + } + + public function wait($unwrap = true, $defaultDelivery = null) + { + if ($unwrap) { + throw Create::exceptionFor($this->reason); + } + + return null; + } + + public function getState() + { + return self::REJECTED; + } + + public function resolve($value) + { + throw new \LogicException("Cannot resolve a rejected promise"); + } + + public function reject($reason) + { + if ($reason !== $this->reason) { + throw new \LogicException("Cannot reject a rejected promise"); + } + } + + public function cancel() + { + // pass + } +} diff --git a/vendor/guzzlehttp/promises/src/RejectionException.php b/vendor/guzzlehttp/promises/src/RejectionException.php new file mode 100644 index 0000000..e2f1377 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/RejectionException.php @@ -0,0 +1,48 @@ +reason = $reason; + + $message = 'The promise was rejected'; + + if ($description) { + $message .= ' with reason: ' . $description; + } elseif (is_string($reason) + || (is_object($reason) && method_exists($reason, '__toString')) + ) { + $message .= ' with reason: ' . $this->reason; + } elseif ($reason instanceof \JsonSerializable) { + $message .= ' with reason: ' + . json_encode($this->reason, JSON_PRETTY_PRINT); + } + + parent::__construct($message); + } + + /** + * Returns the rejection reason. + * + * @return mixed + */ + public function getReason() + { + return $this->reason; + } +} diff --git a/vendor/guzzlehttp/promises/src/TaskQueue.php b/vendor/guzzlehttp/promises/src/TaskQueue.php new file mode 100644 index 0000000..f0fba2c --- /dev/null +++ b/vendor/guzzlehttp/promises/src/TaskQueue.php @@ -0,0 +1,67 @@ +run(); + */ +class TaskQueue implements TaskQueueInterface +{ + private $enableShutdown = true; + private $queue = []; + + public function __construct($withShutdown = true) + { + if ($withShutdown) { + register_shutdown_function(function () { + if ($this->enableShutdown) { + // Only run the tasks if an E_ERROR didn't occur. + $err = error_get_last(); + if (!$err || ($err['type'] ^ E_ERROR)) { + $this->run(); + } + } + }); + } + } + + public function isEmpty() + { + return !$this->queue; + } + + public function add(callable $task) + { + $this->queue[] = $task; + } + + public function run() + { + while ($task = array_shift($this->queue)) { + /** @var callable $task */ + $task(); + } + } + + /** + * The task queue will be run and exhausted by default when the process + * exits IFF the exit is not the result of a PHP E_ERROR error. + * + * You can disable running the automatic shutdown of the queue by calling + * this function. If you disable the task queue shutdown process, then you + * MUST either run the task queue (as a result of running your event loop + * or manually using the run() method) or wait on each outstanding promise. + * + * Note: This shutdown will occur before any destructors are triggered. + */ + public function disableShutdown() + { + $this->enableShutdown = false; + } +} diff --git a/vendor/guzzlehttp/promises/src/TaskQueueInterface.php b/vendor/guzzlehttp/promises/src/TaskQueueInterface.php new file mode 100644 index 0000000..723d4d5 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/TaskQueueInterface.php @@ -0,0 +1,24 @@ + + * while ($eventLoop->isRunning()) { + * GuzzleHttp\Promise\Utils::queue()->run(); + * } + * + * + * @param TaskQueueInterface $assign Optionally specify a new queue instance. + * + * @return TaskQueueInterface + */ + public static function queue(TaskQueueInterface $assign = null) + { + static $queue; + + if ($assign) { + $queue = $assign; + } elseif (!$queue) { + $queue = new TaskQueue(); + } + + return $queue; + } + + /** + * Adds a function to run in the task queue when it is next `run()` and + * returns a promise that is fulfilled or rejected with the result. + * + * @param callable $task Task function to run. + * + * @return PromiseInterface + */ + public static function task(callable $task) + { + $queue = self::queue(); + $promise = new Promise([$queue, 'run']); + $queue->add(function () use ($task, $promise) { + try { + if (Is::pending($promise)) { + $promise->resolve($task()); + } + } catch (\Throwable $e) { + $promise->reject($e); + } catch (\Exception $e) { + $promise->reject($e); + } + }); + + return $promise; + } + + /** + * Synchronously waits on a promise to resolve and returns an inspection + * state array. + * + * Returns a state associative array containing a "state" key mapping to a + * valid promise state. If the state of the promise is "fulfilled", the + * array will contain a "value" key mapping to the fulfilled value of the + * promise. If the promise is rejected, the array will contain a "reason" + * key mapping to the rejection reason of the promise. + * + * @param PromiseInterface $promise Promise or value. + * + * @return array + */ + public static function inspect(PromiseInterface $promise) + { + try { + return [ + 'state' => PromiseInterface::FULFILLED, + 'value' => $promise->wait() + ]; + } catch (RejectionException $e) { + return ['state' => PromiseInterface::REJECTED, 'reason' => $e->getReason()]; + } catch (\Throwable $e) { + return ['state' => PromiseInterface::REJECTED, 'reason' => $e]; + } catch (\Exception $e) { + return ['state' => PromiseInterface::REJECTED, 'reason' => $e]; + } + } + + /** + * Waits on all of the provided promises, but does not unwrap rejected + * promises as thrown exception. + * + * Returns an array of inspection state arrays. + * + * @see inspect for the inspection state array format. + * + * @param PromiseInterface[] $promises Traversable of promises to wait upon. + * + * @return array + */ + public static function inspectAll($promises) + { + $results = []; + foreach ($promises as $key => $promise) { + $results[$key] = inspect($promise); + } + + return $results; + } + + /** + * Waits on all of the provided promises and returns the fulfilled values. + * + * Returns an array that contains the value of each promise (in the same + * order the promises were provided). An exception is thrown if any of the + * promises are rejected. + * + * @param iterable $promises Iterable of PromiseInterface objects to wait on. + * + * @return array + * + * @throws \Exception on error + * @throws \Throwable on error in PHP >=7 + */ + public static function unwrap($promises) + { + $results = []; + foreach ($promises as $key => $promise) { + $results[$key] = $promise->wait(); + } + + return $results; + } + + /** + * Given an array of promises, return a promise that is fulfilled when all + * the items in the array are fulfilled. + * + * The promise's fulfillment value is an array with fulfillment values at + * respective positions to the original array. If any promise in the array + * rejects, the returned promise is rejected with the rejection reason. + * + * @param mixed $promises Promises or values. + * @param bool $recursive If true, resolves new promises that might have been added to the stack during its own resolution. + * + * @return PromiseInterface + */ + public static function all($promises, $recursive = false) + { + $results = []; + $promise = Each::of( + $promises, + function ($value, $idx) use (&$results) { + $results[$idx] = $value; + }, + function ($reason, $idx, Promise $aggregate) { + $aggregate->reject($reason); + } + )->then(function () use (&$results) { + ksort($results); + return $results; + }); + + if (true === $recursive) { + $promise = $promise->then(function ($results) use ($recursive, &$promises) { + foreach ($promises as $promise) { + if (Is::pending($promise)) { + return self::all($promises, $recursive); + } + } + return $results; + }); + } + + return $promise; + } + + /** + * Initiate a competitive race between multiple promises or values (values + * will become immediately fulfilled promises). + * + * When count amount of promises have been fulfilled, the returned promise + * is fulfilled with an array that contains the fulfillment values of the + * winners in order of resolution. + * + * This promise is rejected with a {@see AggregateException} if the number + * of fulfilled promises is less than the desired $count. + * + * @param int $count Total number of promises. + * @param mixed $promises Promises or values. + * + * @return PromiseInterface + */ + public static function some($count, $promises) + { + $results = []; + $rejections = []; + + return Each::of( + $promises, + function ($value, $idx, PromiseInterface $p) use (&$results, $count) { + if (Is::settled($p)) { + return; + } + $results[$idx] = $value; + if (count($results) >= $count) { + $p->resolve(null); + } + }, + function ($reason) use (&$rejections) { + $rejections[] = $reason; + } + )->then( + function () use (&$results, &$rejections, $count) { + if (count($results) !== $count) { + throw new AggregateException( + 'Not enough promises to fulfill count', + $rejections + ); + } + ksort($results); + return array_values($results); + } + ); + } + + /** + * Like some(), with 1 as count. However, if the promise fulfills, the + * fulfillment value is not an array of 1 but the value directly. + * + * @param mixed $promises Promises or values. + * + * @return PromiseInterface + */ + public static function any($promises) + { + return self::some(1, $promises)->then(function ($values) { + return $values[0]; + }); + } + + /** + * Returns a promise that is fulfilled when all of the provided promises have + * been fulfilled or rejected. + * + * The returned promise is fulfilled with an array of inspection state arrays. + * + * @see inspect for the inspection state array format. + * + * @param mixed $promises Promises or values. + * + * @return PromiseInterface + */ + public static function settle($promises) + { + $results = []; + + return Each::of( + $promises, + function ($value, $idx) use (&$results) { + $results[$idx] = ['state' => PromiseInterface::FULFILLED, 'value' => $value]; + }, + function ($reason, $idx) use (&$results) { + $results[$idx] = ['state' => PromiseInterface::REJECTED, 'reason' => $reason]; + } + )->then(function () use (&$results) { + ksort($results); + return $results; + }); + } +} diff --git a/vendor/guzzlehttp/promises/src/functions.php b/vendor/guzzlehttp/promises/src/functions.php new file mode 100644 index 0000000..c03d39d --- /dev/null +++ b/vendor/guzzlehttp/promises/src/functions.php @@ -0,0 +1,363 @@ + + * while ($eventLoop->isRunning()) { + * GuzzleHttp\Promise\queue()->run(); + * } + * + * + * @param TaskQueueInterface $assign Optionally specify a new queue instance. + * + * @return TaskQueueInterface + * + * @deprecated queue will be removed in guzzlehttp/promises:2.0. Use Utils::queue instead. + */ +function queue(TaskQueueInterface $assign = null) +{ + return Utils::queue($assign); +} + +/** + * Adds a function to run in the task queue when it is next `run()` and returns + * a promise that is fulfilled or rejected with the result. + * + * @param callable $task Task function to run. + * + * @return PromiseInterface + * + * @deprecated task will be removed in guzzlehttp/promises:2.0. Use Utils::task instead. + */ +function task(callable $task) +{ + return Utils::task($task); +} + +/** + * Creates a promise for a value if the value is not a promise. + * + * @param mixed $value Promise or value. + * + * @return PromiseInterface + * + * @deprecated promise_for will be removed in guzzlehttp/promises:2.0. Use Create::promiseFor instead. + */ +function promise_for($value) +{ + return Create::promiseFor($value); +} + +/** + * Creates a rejected promise for a reason if the reason is not a promise. If + * the provided reason is a promise, then it is returned as-is. + * + * @param mixed $reason Promise or reason. + * + * @return PromiseInterface + * + * @deprecated rejection_for will be removed in guzzlehttp/promises:2.0. Use Create::rejectionFor instead. + */ +function rejection_for($reason) +{ + return Create::rejectionFor($reason); +} + +/** + * Create an exception for a rejected promise value. + * + * @param mixed $reason + * + * @return \Exception|\Throwable + * + * @deprecated exception_for will be removed in guzzlehttp/promises:2.0. Use Create::exceptionFor instead. + */ +function exception_for($reason) +{ + return Create::exceptionFor($reason); +} + +/** + * Returns an iterator for the given value. + * + * @param mixed $value + * + * @return \Iterator + * + * @deprecated iter_for will be removed in guzzlehttp/promises:2.0. Use Create::iterFor instead. + */ +function iter_for($value) +{ + return Create::iterFor($value); +} + +/** + * Synchronously waits on a promise to resolve and returns an inspection state + * array. + * + * Returns a state associative array containing a "state" key mapping to a + * valid promise state. If the state of the promise is "fulfilled", the array + * will contain a "value" key mapping to the fulfilled value of the promise. If + * the promise is rejected, the array will contain a "reason" key mapping to + * the rejection reason of the promise. + * + * @param PromiseInterface $promise Promise or value. + * + * @return array + * + * @deprecated inspect will be removed in guzzlehttp/promises:2.0. Use Utils::inspect instead. + */ +function inspect(PromiseInterface $promise) +{ + return Utils::inspect($promise); +} + +/** + * Waits on all of the provided promises, but does not unwrap rejected promises + * as thrown exception. + * + * Returns an array of inspection state arrays. + * + * @see inspect for the inspection state array format. + * + * @param PromiseInterface[] $promises Traversable of promises to wait upon. + * + * @return array + * + * @deprecated inspect will be removed in guzzlehttp/promises:2.0. Use Utils::inspectAll instead. + */ +function inspect_all($promises) +{ + return Utils::inspectAll($promises); +} + +/** + * Waits on all of the provided promises and returns the fulfilled values. + * + * Returns an array that contains the value of each promise (in the same order + * the promises were provided). An exception is thrown if any of the promises + * are rejected. + * + * @param iterable $promises Iterable of PromiseInterface objects to wait on. + * + * @return array + * + * @throws \Exception on error + * @throws \Throwable on error in PHP >=7 + * + * @deprecated unwrap will be removed in guzzlehttp/promises:2.0. Use Utils::unwrap instead. + */ +function unwrap($promises) +{ + return Utils::unwrap($promises); +} + +/** + * Given an array of promises, return a promise that is fulfilled when all the + * items in the array are fulfilled. + * + * The promise's fulfillment value is an array with fulfillment values at + * respective positions to the original array. If any promise in the array + * rejects, the returned promise is rejected with the rejection reason. + * + * @param mixed $promises Promises or values. + * @param bool $recursive If true, resolves new promises that might have been added to the stack during its own resolution. + * + * @return PromiseInterface + * + * @deprecated all will be removed in guzzlehttp/promises:2.0. Use Utils::all instead. + */ +function all($promises, $recursive = false) +{ + return Utils::all($promises, $recursive); +} + +/** + * Initiate a competitive race between multiple promises or values (values will + * become immediately fulfilled promises). + * + * When count amount of promises have been fulfilled, the returned promise is + * fulfilled with an array that contains the fulfillment values of the winners + * in order of resolution. + * + * This promise is rejected with a {@see AggregateException} if the number of + * fulfilled promises is less than the desired $count. + * + * @param int $count Total number of promises. + * @param mixed $promises Promises or values. + * + * @return PromiseInterface + * + * @deprecated some will be removed in guzzlehttp/promises:2.0. Use Utils::some instead. + */ +function some($count, $promises) +{ + return Utils::some($count, $promises); +} + +/** + * Like some(), with 1 as count. However, if the promise fulfills, the + * fulfillment value is not an array of 1 but the value directly. + * + * @param mixed $promises Promises or values. + * + * @return PromiseInterface + * + * @deprecated any will be removed in guzzlehttp/promises:2.0. Use Utils::any instead. + */ +function any($promises) +{ + return Utils::any($promises); +} + +/** + * Returns a promise that is fulfilled when all of the provided promises have + * been fulfilled or rejected. + * + * The returned promise is fulfilled with an array of inspection state arrays. + * + * @see inspect for the inspection state array format. + * + * @param mixed $promises Promises or values. + * + * @return PromiseInterface + * + * @deprecated settle will be removed in guzzlehttp/promises:2.0. Use Utils::settle instead. + */ +function settle($promises) +{ + return Utils::settle($promises); +} + +/** + * Given an iterator that yields promises or values, returns a promise that is + * fulfilled with a null value when the iterator has been consumed or the + * aggregate promise has been fulfilled or rejected. + * + * $onFulfilled is a function that accepts the fulfilled value, iterator index, + * and the aggregate promise. The callback can invoke any necessary side + * effects and choose to resolve or reject the aggregate if needed. + * + * $onRejected is a function that accepts the rejection reason, iterator index, + * and the aggregate promise. The callback can invoke any necessary side + * effects and choose to resolve or reject the aggregate if needed. + * + * @param mixed $iterable Iterator or array to iterate over. + * @param callable $onFulfilled + * @param callable $onRejected + * + * @return PromiseInterface + * + * @deprecated each will be removed in guzzlehttp/promises:2.0. Use Each::of instead. + */ +function each( + $iterable, + callable $onFulfilled = null, + callable $onRejected = null +) { + return Each::of($iterable, $onFulfilled, $onRejected); +} + +/** + * Like each, but only allows a certain number of outstanding promises at any + * given time. + * + * $concurrency may be an integer or a function that accepts the number of + * pending promises and returns a numeric concurrency limit value to allow for + * dynamic a concurrency size. + * + * @param mixed $iterable + * @param int|callable $concurrency + * @param callable $onFulfilled + * @param callable $onRejected + * + * @return PromiseInterface + * + * @deprecated each_limit will be removed in guzzlehttp/promises:2.0. Use Each::ofLimit instead. + */ +function each_limit( + $iterable, + $concurrency, + callable $onFulfilled = null, + callable $onRejected = null +) { + return Each::ofLimit($iterable, $concurrency, $onFulfilled, $onRejected); +} + +/** + * Like each_limit, but ensures that no promise in the given $iterable argument + * is rejected. If any promise is rejected, then the aggregate promise is + * rejected with the encountered rejection. + * + * @param mixed $iterable + * @param int|callable $concurrency + * @param callable $onFulfilled + * + * @return PromiseInterface + * + * @deprecated each_limit_all will be removed in guzzlehttp/promises:2.0. Use Each::ofLimitAll instead. + */ +function each_limit_all( + $iterable, + $concurrency, + callable $onFulfilled = null +) { + return Each::ofLimitAll($iterable, $concurrency, $onFulfilled); +} + +/** + * Returns true if a promise is fulfilled. + * + * @return bool + * + * @deprecated is_fulfilled will be removed in guzzlehttp/promises:2.0. Use Is::fulfilled instead. + */ +function is_fulfilled(PromiseInterface $promise) +{ + return Is::fulfilled($promise); +} + +/** + * Returns true if a promise is rejected. + * + * @return bool + * + * @deprecated is_rejected will be removed in guzzlehttp/promises:2.0. Use Is::rejected instead. + */ +function is_rejected(PromiseInterface $promise) +{ + return Is::rejected($promise); +} + +/** + * Returns true if a promise is fulfilled or rejected. + * + * @return bool + * + * @deprecated is_settled will be removed in guzzlehttp/promises:2.0. Use Is::settled instead. + */ +function is_settled(PromiseInterface $promise) +{ + return Is::settled($promise); +} + +/** + * Create a new coroutine. + * + * @see Coroutine + * + * @return PromiseInterface + * + * @deprecated coroutine will be removed in guzzlehttp/promises:2.0. Use Coroutine::of instead. + */ +function coroutine(callable $generatorFn) +{ + return Coroutine::of($generatorFn); +} diff --git a/vendor/guzzlehttp/promises/src/functions_include.php b/vendor/guzzlehttp/promises/src/functions_include.php new file mode 100644 index 0000000..34cd171 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/functions_include.php @@ -0,0 +1,6 @@ +withPath('foo')->withHost('example.com')` will throw an exception + because the path of a URI with an authority must start with a slash "/" or be empty + - `(new Uri())->withScheme('http')` will return `'http://localhost'` + +### Deprecated + +- `Uri::resolve` in favor of `UriResolver::resolve` +- `Uri::removeDotSegments` in favor of `UriResolver::removeDotSegments` + +### Fixed + +- `Stream::read` when length parameter <= 0. +- `copy_to_stream` reads bytes in chunks instead of `maxLen` into memory. +- `ServerRequest::getUriFromGlobals` when `Host` header contains port. +- Compatibility of URIs with `file` scheme and empty host. + + +## [1.3.1] - 2016-06-25 + +### Fixed + +- `Uri::__toString` for network path references, e.g. `//example.org`. +- Missing lowercase normalization for host. +- Handling of URI components in case they are `'0'` in a lot of places, + e.g. as a user info password. +- `Uri::withAddedHeader` to correctly merge headers with different case. +- Trimming of header values in `Uri::withAddedHeader`. Header values may + be surrounded by whitespace which should be ignored according to RFC 7230 + Section 3.2.4. This does not apply to header names. +- `Uri::withAddedHeader` with an array of header values. +- `Uri::resolve` when base path has no slash and handling of fragment. +- Handling of encoding in `Uri::with(out)QueryValue` so one can pass the + key/value both in encoded as well as decoded form to those methods. This is + consistent with withPath, withQuery etc. +- `ServerRequest::withoutAttribute` when attribute value is null. + + +## [1.3.0] - 2016-04-13 + +### Added + +- Remaining interfaces needed for full PSR7 compatibility + (ServerRequestInterface, UploadedFileInterface, etc.). +- Support for stream_for from scalars. + +### Changed + +- Can now extend Uri. + +### Fixed +- A bug in validating request methods by making it more permissive. + + +## [1.2.3] - 2016-02-18 + +### Fixed + +- Support in `GuzzleHttp\Psr7\CachingStream` for seeking forward on remote + streams, which can sometimes return fewer bytes than requested with `fread`. +- Handling of gzipped responses with FNAME headers. + + +## [1.2.2] - 2016-01-22 + +### Added + +- Support for URIs without any authority. +- Support for HTTP 451 'Unavailable For Legal Reasons.' +- Support for using '0' as a filename. +- Support for including non-standard ports in Host headers. + + +## [1.2.1] - 2015-11-02 + +### Changes + +- Now supporting negative offsets when seeking to SEEK_END. + + +## [1.2.0] - 2015-08-15 + +### Changed + +- Body as `"0"` is now properly added to a response. +- Now allowing forward seeking in CachingStream. +- Now properly parsing HTTP requests that contain proxy targets in + `parse_request`. +- functions.php is now conditionally required. +- user-info is no longer dropped when resolving URIs. + + +## [1.1.0] - 2015-06-24 + +### Changed + +- URIs can now be relative. +- `multipart/form-data` headers are now overridden case-insensitively. +- URI paths no longer encode the following characters because they are allowed + in URIs: "(", ")", "*", "!", "'" +- A port is no longer added to a URI when the scheme is missing and no port is + present. + + +## 1.0.0 - 2015-05-19 + +Initial release. + +Currently unsupported: + +- `Psr\Http\Message\ServerRequestInterface` +- `Psr\Http\Message\UploadedFileInterface` + + + +[1.6.0]: https://github.com/guzzle/psr7/compare/1.5.2...1.6.0 +[1.5.2]: https://github.com/guzzle/psr7/compare/1.5.1...1.5.2 +[1.5.1]: https://github.com/guzzle/psr7/compare/1.5.0...1.5.1 +[1.5.0]: https://github.com/guzzle/psr7/compare/1.4.2...1.5.0 +[1.4.2]: https://github.com/guzzle/psr7/compare/1.4.1...1.4.2 +[1.4.1]: https://github.com/guzzle/psr7/compare/1.4.0...1.4.1 +[1.4.0]: https://github.com/guzzle/psr7/compare/1.3.1...1.4.0 +[1.3.1]: https://github.com/guzzle/psr7/compare/1.3.0...1.3.1 +[1.3.0]: https://github.com/guzzle/psr7/compare/1.2.3...1.3.0 +[1.2.3]: https://github.com/guzzle/psr7/compare/1.2.2...1.2.3 +[1.2.2]: https://github.com/guzzle/psr7/compare/1.2.1...1.2.2 +[1.2.1]: https://github.com/guzzle/psr7/compare/1.2.0...1.2.1 +[1.2.0]: https://github.com/guzzle/psr7/compare/1.1.0...1.2.0 +[1.1.0]: https://github.com/guzzle/psr7/compare/1.0.0...1.1.0 diff --git a/vendor/guzzlehttp/psr7/LICENSE b/vendor/guzzlehttp/psr7/LICENSE new file mode 100644 index 0000000..51c7ec8 --- /dev/null +++ b/vendor/guzzlehttp/psr7/LICENSE @@ -0,0 +1,26 @@ +The MIT License (MIT) + +Copyright (c) 2015 Michael Dowling +Copyright (c) 2015 Márk Sági-Kazár +Copyright (c) 2015 Graham Campbell +Copyright (c) 2016 Tobias Schultze +Copyright (c) 2016 George Mponos +Copyright (c) 2018 Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/guzzlehttp/psr7/README.md b/vendor/guzzlehttp/psr7/README.md new file mode 100644 index 0000000..ed81c92 --- /dev/null +++ b/vendor/guzzlehttp/psr7/README.md @@ -0,0 +1,823 @@ +# PSR-7 Message Implementation + +This repository contains a full [PSR-7](http://www.php-fig.org/psr/psr-7/) +message implementation, several stream decorators, and some helpful +functionality like query string parsing. + +![CI](https://github.com/guzzle/psr7/workflows/CI/badge.svg) +![Static analysis](https://github.com/guzzle/psr7/workflows/Static%20analysis/badge.svg) + + +# Stream implementation + +This package comes with a number of stream implementations and stream +decorators. + + +## AppendStream + +`GuzzleHttp\Psr7\AppendStream` + +Reads from multiple streams, one after the other. + +```php +use GuzzleHttp\Psr7; + +$a = Psr7\Utils::streamFor('abc, '); +$b = Psr7\Utils::streamFor('123.'); +$composed = new Psr7\AppendStream([$a, $b]); + +$composed->addStream(Psr7\Utils::streamFor(' Above all listen to me')); + +echo $composed; // abc, 123. Above all listen to me. +``` + + +## BufferStream + +`GuzzleHttp\Psr7\BufferStream` + +Provides a buffer stream that can be written to fill a buffer, and read +from to remove bytes from the buffer. + +This stream returns a "hwm" metadata value that tells upstream consumers +what the configured high water mark of the stream is, or the maximum +preferred size of the buffer. + +```php +use GuzzleHttp\Psr7; + +// When more than 1024 bytes are in the buffer, it will begin returning +// false to writes. This is an indication that writers should slow down. +$buffer = new Psr7\BufferStream(1024); +``` + + +## CachingStream + +The CachingStream is used to allow seeking over previously read bytes on +non-seekable streams. This can be useful when transferring a non-seekable +entity body fails due to needing to rewind the stream (for example, resulting +from a redirect). Data that is read from the remote stream will be buffered in +a PHP temp stream so that previously read bytes are cached first in memory, +then on disk. + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\Utils::streamFor(fopen('http://www.google.com', 'r')); +$stream = new Psr7\CachingStream($original); + +$stream->read(1024); +echo $stream->tell(); +// 1024 + +$stream->seek(0); +echo $stream->tell(); +// 0 +``` + + +## DroppingStream + +`GuzzleHttp\Psr7\DroppingStream` + +Stream decorator that begins dropping data once the size of the underlying +stream becomes too full. + +```php +use GuzzleHttp\Psr7; + +// Create an empty stream +$stream = Psr7\Utils::streamFor(); + +// Start dropping data when the stream has more than 10 bytes +$dropping = new Psr7\DroppingStream($stream, 10); + +$dropping->write('01234567890123456789'); +echo $stream; // 0123456789 +``` + + +## FnStream + +`GuzzleHttp\Psr7\FnStream` + +Compose stream implementations based on a hash of functions. + +Allows for easy testing and extension of a provided stream without needing +to create a concrete class for a simple extension point. + +```php + +use GuzzleHttp\Psr7; + +$stream = Psr7\Utils::streamFor('hi'); +$fnStream = Psr7\FnStream::decorate($stream, [ + 'rewind' => function () use ($stream) { + echo 'About to rewind - '; + $stream->rewind(); + echo 'rewound!'; + } +]); + +$fnStream->rewind(); +// Outputs: About to rewind - rewound! +``` + + +## InflateStream + +`GuzzleHttp\Psr7\InflateStream` + +Uses PHP's zlib.inflate filter to inflate zlib (HTTP deflate, RFC1950) or gzipped (RFC1952) content. + +This stream decorator converts the provided stream to a PHP stream resource, +then appends the zlib.inflate filter. The stream is then converted back +to a Guzzle stream resource to be used as a Guzzle stream. + + +## LazyOpenStream + +`GuzzleHttp\Psr7\LazyOpenStream` + +Lazily reads or writes to a file that is opened only after an IO operation +take place on the stream. + +```php +use GuzzleHttp\Psr7; + +$stream = new Psr7\LazyOpenStream('/path/to/file', 'r'); +// The file has not yet been opened... + +echo $stream->read(10); +// The file is opened and read from only when needed. +``` + + +## LimitStream + +`GuzzleHttp\Psr7\LimitStream` + +LimitStream can be used to read a subset or slice of an existing stream object. +This can be useful for breaking a large file into smaller pieces to be sent in +chunks (e.g. Amazon S3's multipart upload API). + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\Utils::streamFor(fopen('/tmp/test.txt', 'r+')); +echo $original->getSize(); +// >>> 1048576 + +// Limit the size of the body to 1024 bytes and start reading from byte 2048 +$stream = new Psr7\LimitStream($original, 1024, 2048); +echo $stream->getSize(); +// >>> 1024 +echo $stream->tell(); +// >>> 0 +``` + + +## MultipartStream + +`GuzzleHttp\Psr7\MultipartStream` + +Stream that when read returns bytes for a streaming multipart or +multipart/form-data stream. + + +## NoSeekStream + +`GuzzleHttp\Psr7\NoSeekStream` + +NoSeekStream wraps a stream and does not allow seeking. + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\Utils::streamFor('foo'); +$noSeek = new Psr7\NoSeekStream($original); + +echo $noSeek->read(3); +// foo +var_export($noSeek->isSeekable()); +// false +$noSeek->seek(0); +var_export($noSeek->read(3)); +// NULL +``` + + +## PumpStream + +`GuzzleHttp\Psr7\PumpStream` + +Provides a read only stream that pumps data from a PHP callable. + +When invoking the provided callable, the PumpStream will pass the amount of +data requested to read to the callable. The callable can choose to ignore +this value and return fewer or more bytes than requested. Any extra data +returned by the provided callable is buffered internally until drained using +the read() function of the PumpStream. The provided callable MUST return +false when there is no more data to read. + + +## Implementing stream decorators + +Creating a stream decorator is very easy thanks to the +`GuzzleHttp\Psr7\StreamDecoratorTrait`. This trait provides methods that +implement `Psr\Http\Message\StreamInterface` by proxying to an underlying +stream. Just `use` the `StreamDecoratorTrait` and implement your custom +methods. + +For example, let's say we wanted to call a specific function each time the last +byte is read from a stream. This could be implemented by overriding the +`read()` method. + +```php +use Psr\Http\Message\StreamInterface; +use GuzzleHttp\Psr7\StreamDecoratorTrait; + +class EofCallbackStream implements StreamInterface +{ + use StreamDecoratorTrait; + + private $callback; + + public function __construct(StreamInterface $stream, callable $cb) + { + $this->stream = $stream; + $this->callback = $cb; + } + + public function read($length) + { + $result = $this->stream->read($length); + + // Invoke the callback when EOF is hit. + if ($this->eof()) { + call_user_func($this->callback); + } + + return $result; + } +} +``` + +This decorator could be added to any existing stream and used like so: + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\Utils::streamFor('foo'); + +$eofStream = new EofCallbackStream($original, function () { + echo 'EOF!'; +}); + +$eofStream->read(2); +$eofStream->read(1); +// echoes "EOF!" +$eofStream->seek(0); +$eofStream->read(3); +// echoes "EOF!" +``` + + +## PHP StreamWrapper + +You can use the `GuzzleHttp\Psr7\StreamWrapper` class if you need to use a +PSR-7 stream as a PHP stream resource. + +Use the `GuzzleHttp\Psr7\StreamWrapper::getResource()` method to create a PHP +stream from a PSR-7 stream. + +```php +use GuzzleHttp\Psr7\StreamWrapper; + +$stream = GuzzleHttp\Psr7\Utils::streamFor('hello!'); +$resource = StreamWrapper::getResource($stream); +echo fread($resource, 6); // outputs hello! +``` + + +# Static API + +There are various static methods available under the `GuzzleHttp\Psr7` namespace. + + +## `GuzzleHttp\Psr7\Message::toString` + +`public static function toString(MessageInterface $message): string` + +Returns the string representation of an HTTP message. + +```php +$request = new GuzzleHttp\Psr7\Request('GET', 'http://example.com'); +echo GuzzleHttp\Psr7\Message::toString($request); +``` + + +## `GuzzleHttp\Psr7\Message::bodySummary` + +`public static function bodySummary(MessageInterface $message, int $truncateAt = 120): string|null` + +Get a short summary of the message body. + +Will return `null` if the response is not printable. + + +## `GuzzleHttp\Psr7\Message::rewindBody` + +`public static function rewindBody(MessageInterface $message): void` + +Attempts to rewind a message body and throws an exception on failure. + +The body of the message will only be rewound if a call to `tell()` +returns a value other than `0`. + + +## `GuzzleHttp\Psr7\Message::parseMessage` + +`public static function parseMessage(string $message): array` + +Parses an HTTP message into an associative array. + +The array contains the "start-line" key containing the start line of +the message, "headers" key containing an associative array of header +array values, and a "body" key containing the body of the message. + + +## `GuzzleHttp\Psr7\Message::parseRequestUri` + +`public static function parseRequestUri(string $path, array $headers): string` + +Constructs a URI for an HTTP request message. + + +## `GuzzleHttp\Psr7\Message::parseRequest` + +`public static function parseRequest(string $message): Request` + +Parses a request message string into a request object. + + +## `GuzzleHttp\Psr7\Message::parseResponse` + +`public static function parseResponse(string $message): Response` + +Parses a response message string into a response object. + + +## `GuzzleHttp\Psr7\Header::parse` + +`public static function parse(string|array $header): array` + +Parse an array of header values containing ";" separated data into an +array of associative arrays representing the header key value pair data +of the header. When a parameter does not contain a value, but just +contains a key, this function will inject a key with a '' string value. + + +## `GuzzleHttp\Psr7\Header::normalize` + +`public static function normalize(string|array $header): array` + +Converts an array of header values that may contain comma separated +headers into an array of headers with no comma separated values. + + +## `GuzzleHttp\Psr7\Query::parse` + +`public static function parse(string $str, int|bool $urlEncoding = true): array` + +Parse a query string into an associative array. + +If multiple values are found for the same key, the value of that key +value pair will become an array. This function does not parse nested +PHP style arrays into an associative array (e.g., `foo[a]=1&foo[b]=2` +will be parsed into `['foo[a]' => '1', 'foo[b]' => '2'])`. + + +## `GuzzleHttp\Psr7\Query::build` + +`public static function build(array $params, int|false $encoding = PHP_QUERY_RFC3986): string` + +Build a query string from an array of key value pairs. + +This function can use the return value of `parse()` to build a query +string. This function does not modify the provided keys when an array is +encountered (like `http_build_query()` would). + + +## `GuzzleHttp\Psr7\Utils::caselessRemove` + +`public static function caselessRemove(iterable $keys, $keys, array $data): array` + +Remove the items given by the keys, case insensitively from the data. + + +## `GuzzleHttp\Psr7\Utils::copyToStream` + +`public static function copyToStream(StreamInterface $source, StreamInterface $dest, int $maxLen = -1): void` + +Copy the contents of a stream into another stream until the given number +of bytes have been read. + + +## `GuzzleHttp\Psr7\Utils::copyToString` + +`public static function copyToString(StreamInterface $stream, int $maxLen = -1): string` + +Copy the contents of a stream into a string until the given number of +bytes have been read. + + +## `GuzzleHttp\Psr7\Utils::hash` + +`public static function hash(StreamInterface $stream, string $algo, bool $rawOutput = false): string` + +Calculate a hash of a stream. + +This method reads the entire stream to calculate a rolling hash, based on +PHP's `hash_init` functions. + + +## `GuzzleHttp\Psr7\Utils::modifyRequest` + +`public static function modifyRequest(RequestInterface $request, array $changes): RequestInterface` + +Clone and modify a request with the given changes. + +This method is useful for reducing the number of clones needed to mutate +a message. + +- method: (string) Changes the HTTP method. +- set_headers: (array) Sets the given headers. +- remove_headers: (array) Remove the given headers. +- body: (mixed) Sets the given body. +- uri: (UriInterface) Set the URI. +- query: (string) Set the query string value of the URI. +- version: (string) Set the protocol version. + + +## `GuzzleHttp\Psr7\Utils::readLine` + +`public static function readLine(StreamInterface $stream, int $maxLength = null): string` + +Read a line from the stream up to the maximum allowed buffer length. + + +## `GuzzleHttp\Psr7\Utils::streamFor` + +`public static function streamFor(resource|string|null|int|float|bool|StreamInterface|callable|\Iterator $resource = '', array $options = []): StreamInterface` + +Create a new stream based on the input type. + +Options is an associative array that can contain the following keys: + +- metadata: Array of custom metadata. +- size: Size of the stream. + +This method accepts the following `$resource` types: + +- `Psr\Http\Message\StreamInterface`: Returns the value as-is. +- `string`: Creates a stream object that uses the given string as the contents. +- `resource`: Creates a stream object that wraps the given PHP stream resource. +- `Iterator`: If the provided value implements `Iterator`, then a read-only + stream object will be created that wraps the given iterable. Each time the + stream is read from, data from the iterator will fill a buffer and will be + continuously called until the buffer is equal to the requested read size. + Subsequent read calls will first read from the buffer and then call `next` + on the underlying iterator until it is exhausted. +- `object` with `__toString()`: If the object has the `__toString()` method, + the object will be cast to a string and then a stream will be returned that + uses the string value. +- `NULL`: When `null` is passed, an empty stream object is returned. +- `callable` When a callable is passed, a read-only stream object will be + created that invokes the given callable. The callable is invoked with the + number of suggested bytes to read. The callable can return any number of + bytes, but MUST return `false` when there is no more data to return. The + stream object that wraps the callable will invoke the callable until the + number of requested bytes are available. Any additional bytes will be + buffered and used in subsequent reads. + +```php +$stream = GuzzleHttp\Psr7\Utils::streamFor('foo'); +$stream = GuzzleHttp\Psr7\Utils::streamFor(fopen('/path/to/file', 'r')); + +$generator = function ($bytes) { + for ($i = 0; $i < $bytes; $i++) { + yield ' '; + } +} + +$stream = GuzzleHttp\Psr7\Utils::streamFor($generator(100)); +``` + + +## `GuzzleHttp\Psr7\Utils::tryFopen` + +`public static function tryFopen(string $filename, string $mode): resource` + +Safely opens a PHP stream resource using a filename. + +When fopen fails, PHP normally raises a warning. This function adds an +error handler that checks for errors and throws an exception instead. + + +## `GuzzleHttp\Psr7\Utils::uriFor` + +`public static function uriFor(string|UriInterface $uri): UriInterface` + +Returns a UriInterface for the given value. + +This function accepts a string or UriInterface and returns a +UriInterface for the given value. If the value is already a +UriInterface, it is returned as-is. + + +## `GuzzleHttp\Psr7\MimeType::fromFilename` + +`public static function fromFilename(string $filename): string|null` + +Determines the mimetype of a file by looking at its extension. + + +## `GuzzleHttp\Psr7\MimeType::fromExtension` + +`public static function fromExtension(string $extension): string|null` + +Maps a file extensions to a mimetype. + + +## Upgrading from Function API + +The static API was first introduced in 1.7.0, in order to mitigate problems with functions conflicting between global and local copies of the package. The function API was removed in 2.0.0. A migration table has been provided here for your convenience: + +| Original Function | Replacement Method | +|----------------|----------------| +| `str` | `Message::toString` | +| `uri_for` | `Utils::uriFor` | +| `stream_for` | `Utils::streamFor` | +| `parse_header` | `Header::parse` | +| `normalize_header` | `Header::normalize` | +| `modify_request` | `Utils::modifyRequest` | +| `rewind_body` | `Message::rewindBody` | +| `try_fopen` | `Utils::tryFopen` | +| `copy_to_string` | `Utils::copyToString` | +| `copy_to_stream` | `Utils::copyToStream` | +| `hash` | `Utils::hash` | +| `readline` | `Utils::readLine` | +| `parse_request` | `Message::parseRequest` | +| `parse_response` | `Message::parseResponse` | +| `parse_query` | `Query::parse` | +| `build_query` | `Query::build` | +| `mimetype_from_filename` | `MimeType::fromFilename` | +| `mimetype_from_extension` | `MimeType::fromExtension` | +| `_parse_message` | `Message::parseMessage` | +| `_parse_request_uri` | `Message::parseRequestUri` | +| `get_message_body_summary` | `Message::bodySummary` | +| `_caseless_remove` | `Utils::caselessRemove` | + + +# Additional URI Methods + +Aside from the standard `Psr\Http\Message\UriInterface` implementation in form of the `GuzzleHttp\Psr7\Uri` class, +this library also provides additional functionality when working with URIs as static methods. + +## URI Types + +An instance of `Psr\Http\Message\UriInterface` can either be an absolute URI or a relative reference. +An absolute URI has a scheme. A relative reference is used to express a URI relative to another URI, +the base URI. Relative references can be divided into several forms according to +[RFC 3986 Section 4.2](https://tools.ietf.org/html/rfc3986#section-4.2): + +- network-path references, e.g. `//example.com/path` +- absolute-path references, e.g. `/path` +- relative-path references, e.g. `subpath` + +The following methods can be used to identify the type of the URI. + +### `GuzzleHttp\Psr7\Uri::isAbsolute` + +`public static function isAbsolute(UriInterface $uri): bool` + +Whether the URI is absolute, i.e. it has a scheme. + +### `GuzzleHttp\Psr7\Uri::isNetworkPathReference` + +`public static function isNetworkPathReference(UriInterface $uri): bool` + +Whether the URI is a network-path reference. A relative reference that begins with two slash characters is +termed an network-path reference. + +### `GuzzleHttp\Psr7\Uri::isAbsolutePathReference` + +`public static function isAbsolutePathReference(UriInterface $uri): bool` + +Whether the URI is a absolute-path reference. A relative reference that begins with a single slash character is +termed an absolute-path reference. + +### `GuzzleHttp\Psr7\Uri::isRelativePathReference` + +`public static function isRelativePathReference(UriInterface $uri): bool` + +Whether the URI is a relative-path reference. A relative reference that does not begin with a slash character is +termed a relative-path reference. + +### `GuzzleHttp\Psr7\Uri::isSameDocumentReference` + +`public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null): bool` + +Whether the URI is a same-document reference. A same-document reference refers to a URI that is, aside from its +fragment component, identical to the base URI. When no base URI is given, only an empty URI reference +(apart from its fragment) is considered a same-document reference. + +## URI Components + +Additional methods to work with URI components. + +### `GuzzleHttp\Psr7\Uri::isDefaultPort` + +`public static function isDefaultPort(UriInterface $uri): bool` + +Whether the URI has the default port of the current scheme. `Psr\Http\Message\UriInterface::getPort` may return null +or the standard port. This method can be used independently of the implementation. + +### `GuzzleHttp\Psr7\Uri::composeComponents` + +`public static function composeComponents($scheme, $authority, $path, $query, $fragment): string` + +Composes a URI reference string from its various components according to +[RFC 3986 Section 5.3](https://tools.ietf.org/html/rfc3986#section-5.3). Usually this method does not need to be called +manually but instead is used indirectly via `Psr\Http\Message\UriInterface::__toString`. + +### `GuzzleHttp\Psr7\Uri::fromParts` + +`public static function fromParts(array $parts): UriInterface` + +Creates a URI from a hash of [`parse_url`](http://php.net/manual/en/function.parse-url.php) components. + + +### `GuzzleHttp\Psr7\Uri::withQueryValue` + +`public static function withQueryValue(UriInterface $uri, $key, $value): UriInterface` + +Creates a new URI with a specific query string value. Any existing query string values that exactly match the +provided key are removed and replaced with the given key value pair. A value of null will set the query string +key without a value, e.g. "key" instead of "key=value". + +### `GuzzleHttp\Psr7\Uri::withQueryValues` + +`public static function withQueryValues(UriInterface $uri, array $keyValueArray): UriInterface` + +Creates a new URI with multiple query string values. It has the same behavior as `withQueryValue()` but for an +associative array of key => value. + +### `GuzzleHttp\Psr7\Uri::withoutQueryValue` + +`public static function withoutQueryValue(UriInterface $uri, $key): UriInterface` + +Creates a new URI with a specific query string value removed. Any existing query string values that exactly match the +provided key are removed. + +## Reference Resolution + +`GuzzleHttp\Psr7\UriResolver` provides methods to resolve a URI reference in the context of a base URI according +to [RFC 3986 Section 5](https://tools.ietf.org/html/rfc3986#section-5). This is for example also what web browsers +do when resolving a link in a website based on the current request URI. + +### `GuzzleHttp\Psr7\UriResolver::resolve` + +`public static function resolve(UriInterface $base, UriInterface $rel): UriInterface` + +Converts the relative URI into a new URI that is resolved against the base URI. + +### `GuzzleHttp\Psr7\UriResolver::removeDotSegments` + +`public static function removeDotSegments(string $path): string` + +Removes dot segments from a path and returns the new path according to +[RFC 3986 Section 5.2.4](https://tools.ietf.org/html/rfc3986#section-5.2.4). + +### `GuzzleHttp\Psr7\UriResolver::relativize` + +`public static function relativize(UriInterface $base, UriInterface $target): UriInterface` + +Returns the target URI as a relative reference from the base URI. This method is the counterpart to resolve(): + +```php +(string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target)) +``` + +One use-case is to use the current request URI as base URI and then generate relative links in your documents +to reduce the document size or offer self-contained downloadable document archives. + +```php +$base = new Uri('http://example.com/a/b/'); +echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'. +echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'. +echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'. +echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'. +``` + +## Normalization and Comparison + +`GuzzleHttp\Psr7\UriNormalizer` provides methods to normalize and compare URIs according to +[RFC 3986 Section 6](https://tools.ietf.org/html/rfc3986#section-6). + +### `GuzzleHttp\Psr7\UriNormalizer::normalize` + +`public static function normalize(UriInterface $uri, $flags = self::PRESERVING_NORMALIZATIONS): UriInterface` + +Returns a normalized URI. The scheme and host component are already normalized to lowercase per PSR-7 UriInterface. +This methods adds additional normalizations that can be configured with the `$flags` parameter which is a bitmask +of normalizations to apply. The following normalizations are available: + +- `UriNormalizer::PRESERVING_NORMALIZATIONS` + + Default normalizations which only include the ones that preserve semantics. + +- `UriNormalizer::CAPITALIZE_PERCENT_ENCODING` + + All letters within a percent-encoding triplet (e.g., "%3A") are case-insensitive, and should be capitalized. + + Example: `http://example.org/a%c2%b1b` → `http://example.org/a%C2%B1b` + +- `UriNormalizer::DECODE_UNRESERVED_CHARACTERS` + + Decodes percent-encoded octets of unreserved characters. For consistency, percent-encoded octets in the ranges of + ALPHA (%41–%5A and %61–%7A), DIGIT (%30–%39), hyphen (%2D), period (%2E), underscore (%5F), or tilde (%7E) should + not be created by URI producers and, when found in a URI, should be decoded to their corresponding unreserved + characters by URI normalizers. + + Example: `http://example.org/%7Eusern%61me/` → `http://example.org/~username/` + +- `UriNormalizer::CONVERT_EMPTY_PATH` + + Converts the empty path to "/" for http and https URIs. + + Example: `http://example.org` → `http://example.org/` + +- `UriNormalizer::REMOVE_DEFAULT_HOST` + + Removes the default host of the given URI scheme from the URI. Only the "file" scheme defines the default host + "localhost". All of `file:/myfile`, `file:///myfile`, and `file://localhost/myfile` are equivalent according to + RFC 3986. + + Example: `file://localhost/myfile` → `file:///myfile` + +- `UriNormalizer::REMOVE_DEFAULT_PORT` + + Removes the default port of the given URI scheme from the URI. + + Example: `http://example.org:80/` → `http://example.org/` + +- `UriNormalizer::REMOVE_DOT_SEGMENTS` + + Removes unnecessary dot-segments. Dot-segments in relative-path references are not removed as it would + change the semantics of the URI reference. + + Example: `http://example.org/../a/b/../c/./d.html` → `http://example.org/a/c/d.html` + +- `UriNormalizer::REMOVE_DUPLICATE_SLASHES` + + Paths which include two or more adjacent slashes are converted to one. Webservers usually ignore duplicate slashes + and treat those URIs equivalent. But in theory those URIs do not need to be equivalent. So this normalization + may change the semantics. Encoded slashes (%2F) are not removed. + + Example: `http://example.org//foo///bar.html` → `http://example.org/foo/bar.html` + +- `UriNormalizer::SORT_QUERY_PARAMETERS` + + Sort query parameters with their values in alphabetical order. However, the order of parameters in a URI may be + significant (this is not defined by the standard). So this normalization is not safe and may change the semantics + of the URI. + + Example: `?lang=en&article=fred` → `?article=fred&lang=en` + +### `GuzzleHttp\Psr7\UriNormalizer::isEquivalent` + +`public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, $normalizations = self::PRESERVING_NORMALIZATIONS): bool` + +Whether two URIs can be considered equivalent. Both URIs are normalized automatically before comparison with the given +`$normalizations` bitmask. The method also accepts relative URI references and returns true when they are equivalent. +This of course assumes they will be resolved against the same base URI. If this is not the case, determination of +equivalence or difference of relative references does not mean anything. + + +## Security + +If you discover a security vulnerability within this package, please send an email to security@tidelift.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced. Please see [Security Policy](https://github.com/guzzle/psr7/security/policy) for more information. + +## License + +Guzzle is made available under the MIT License (MIT). Please see [License File](LICENSE) for more information. + +## For Enterprise + +Available as part of the Tidelift Subscription + +The maintainers of Guzzle and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-guzzlehttp-psr7?utm_source=packagist-guzzlehttp-psr7&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) diff --git a/vendor/guzzlehttp/psr7/composer.json b/vendor/guzzlehttp/psr7/composer.json new file mode 100644 index 0000000..885aa1d --- /dev/null +++ b/vendor/guzzlehttp/psr7/composer.json @@ -0,0 +1,89 @@ +{ + "name": "guzzlehttp/psr7", + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "request", + "response", + "message", + "stream", + "http", + "uri", + "url", + "psr-7" + ], + "license": "MIT", + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "http-interop/http-factory-tests": "^0.9", + "phpunit/phpunit": "^8.5.8 || ^9.3.10" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "GuzzleHttp\\Tests\\Psr7\\": "tests/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "config": { + "preferred-install": "dist", + "sort-packages": true + } +} diff --git a/vendor/guzzlehttp/psr7/src/AppendStream.php b/vendor/guzzlehttp/psr7/src/AppendStream.php new file mode 100644 index 0000000..967925f --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/AppendStream.php @@ -0,0 +1,249 @@ +addStream($stream); + } + } + + public function __toString(): string + { + try { + $this->rewind(); + return $this->getContents(); + } catch (\Throwable $e) { + if (\PHP_VERSION_ID >= 70400) { + throw $e; + } + trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR); + return ''; + } + } + + /** + * Add a stream to the AppendStream + * + * @param StreamInterface $stream Stream to append. Must be readable. + * + * @throws \InvalidArgumentException if the stream is not readable + */ + public function addStream(StreamInterface $stream): void + { + if (!$stream->isReadable()) { + throw new \InvalidArgumentException('Each stream must be readable'); + } + + // The stream is only seekable if all streams are seekable + if (!$stream->isSeekable()) { + $this->seekable = false; + } + + $this->streams[] = $stream; + } + + public function getContents(): string + { + return Utils::copyToString($this); + } + + /** + * Closes each attached stream. + */ + public function close(): void + { + $this->pos = $this->current = 0; + $this->seekable = true; + + foreach ($this->streams as $stream) { + $stream->close(); + } + + $this->streams = []; + } + + /** + * Detaches each attached stream. + * + * Returns null as it's not clear which underlying stream resource to return. + */ + public function detach() + { + $this->pos = $this->current = 0; + $this->seekable = true; + + foreach ($this->streams as $stream) { + $stream->detach(); + } + + $this->streams = []; + + return null; + } + + public function tell(): int + { + return $this->pos; + } + + /** + * Tries to calculate the size by adding the size of each stream. + * + * If any of the streams do not return a valid number, then the size of the + * append stream cannot be determined and null is returned. + */ + public function getSize(): ?int + { + $size = 0; + + foreach ($this->streams as $stream) { + $s = $stream->getSize(); + if ($s === null) { + return null; + } + $size += $s; + } + + return $size; + } + + public function eof(): bool + { + return !$this->streams || + ($this->current >= count($this->streams) - 1 && + $this->streams[$this->current]->eof()); + } + + public function rewind(): void + { + $this->seek(0); + } + + /** + * Attempts to seek to the given position. Only supports SEEK_SET. + */ + public function seek($offset, $whence = SEEK_SET): void + { + if (!$this->seekable) { + throw new \RuntimeException('This AppendStream is not seekable'); + } elseif ($whence !== SEEK_SET) { + throw new \RuntimeException('The AppendStream can only seek with SEEK_SET'); + } + + $this->pos = $this->current = 0; + + // Rewind each stream + foreach ($this->streams as $i => $stream) { + try { + $stream->rewind(); + } catch (\Exception $e) { + throw new \RuntimeException('Unable to seek stream ' + . $i . ' of the AppendStream', 0, $e); + } + } + + // Seek to the actual position by reading from each stream + while ($this->pos < $offset && !$this->eof()) { + $result = $this->read(min(8096, $offset - $this->pos)); + if ($result === '') { + break; + } + } + } + + /** + * Reads from all of the appended streams until the length is met or EOF. + */ + public function read($length): string + { + $buffer = ''; + $total = count($this->streams) - 1; + $remaining = $length; + $progressToNext = false; + + while ($remaining > 0) { + + // Progress to the next stream if needed. + if ($progressToNext || $this->streams[$this->current]->eof()) { + $progressToNext = false; + if ($this->current === $total) { + break; + } + $this->current++; + } + + $result = $this->streams[$this->current]->read($remaining); + + if ($result === '') { + $progressToNext = true; + continue; + } + + $buffer .= $result; + $remaining = $length - strlen($buffer); + } + + $this->pos += strlen($buffer); + + return $buffer; + } + + public function isReadable(): bool + { + return true; + } + + public function isWritable(): bool + { + return false; + } + + public function isSeekable(): bool + { + return $this->seekable; + } + + public function write($string): int + { + throw new \RuntimeException('Cannot write to an AppendStream'); + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function getMetadata($key = null) + { + return $key ? null : []; + } +} diff --git a/vendor/guzzlehttp/psr7/src/BufferStream.php b/vendor/guzzlehttp/psr7/src/BufferStream.php new file mode 100644 index 0000000..21be8c0 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/BufferStream.php @@ -0,0 +1,149 @@ +hwm = $hwm; + } + + public function __toString(): string + { + return $this->getContents(); + } + + public function getContents(): string + { + $buffer = $this->buffer; + $this->buffer = ''; + + return $buffer; + } + + public function close(): void + { + $this->buffer = ''; + } + + public function detach() + { + $this->close(); + + return null; + } + + public function getSize(): ?int + { + return strlen($this->buffer); + } + + public function isReadable(): bool + { + return true; + } + + public function isWritable(): bool + { + return true; + } + + public function isSeekable(): bool + { + return false; + } + + public function rewind(): void + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET): void + { + throw new \RuntimeException('Cannot seek a BufferStream'); + } + + public function eof(): bool + { + return strlen($this->buffer) === 0; + } + + public function tell(): int + { + throw new \RuntimeException('Cannot determine the position of a BufferStream'); + } + + /** + * Reads data from the buffer. + */ + public function read($length): string + { + $currentLength = strlen($this->buffer); + + if ($length >= $currentLength) { + // No need to slice the buffer because we don't have enough data. + $result = $this->buffer; + $this->buffer = ''; + } else { + // Slice up the result to provide a subset of the buffer. + $result = substr($this->buffer, 0, $length); + $this->buffer = substr($this->buffer, $length); + } + + return $result; + } + + /** + * Writes data to the buffer. + */ + public function write($string): int + { + $this->buffer .= $string; + + if (strlen($this->buffer) >= $this->hwm) { + return 0; + } + + return strlen($string); + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function getMetadata($key = null) + { + if ($key === 'hwm') { + return $this->hwm; + } + + return $key ? null : []; + } +} diff --git a/vendor/guzzlehttp/psr7/src/CachingStream.php b/vendor/guzzlehttp/psr7/src/CachingStream.php new file mode 100644 index 0000000..7a70ee9 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/CachingStream.php @@ -0,0 +1,148 @@ +remoteStream = $stream; + $this->stream = $target ?: new Stream(Utils::tryFopen('php://temp', 'r+')); + } + + public function getSize(): ?int + { + $remoteSize = $this->remoteStream->getSize(); + + if (null === $remoteSize) { + return null; + } + + return max($this->stream->getSize(), $remoteSize); + } + + public function rewind(): void + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET): void + { + if ($whence === SEEK_SET) { + $byte = $offset; + } elseif ($whence === SEEK_CUR) { + $byte = $offset + $this->tell(); + } elseif ($whence === SEEK_END) { + $size = $this->remoteStream->getSize(); + if ($size === null) { + $size = $this->cacheEntireStream(); + } + $byte = $size + $offset; + } else { + throw new \InvalidArgumentException('Invalid whence'); + } + + $diff = $byte - $this->stream->getSize(); + + if ($diff > 0) { + // Read the remoteStream until we have read in at least the amount + // of bytes requested, or we reach the end of the file. + while ($diff > 0 && !$this->remoteStream->eof()) { + $this->read($diff); + $diff = $byte - $this->stream->getSize(); + } + } else { + // We can just do a normal seek since we've already seen this byte. + $this->stream->seek($byte); + } + } + + public function read($length): string + { + // Perform a regular read on any previously read data from the buffer + $data = $this->stream->read($length); + $remaining = $length - strlen($data); + + // More data was requested so read from the remote stream + if ($remaining) { + // If data was written to the buffer in a position that would have + // been filled from the remote stream, then we must skip bytes on + // the remote stream to emulate overwriting bytes from that + // position. This mimics the behavior of other PHP stream wrappers. + $remoteData = $this->remoteStream->read( + $remaining + $this->skipReadBytes + ); + + if ($this->skipReadBytes) { + $len = strlen($remoteData); + $remoteData = substr($remoteData, $this->skipReadBytes); + $this->skipReadBytes = max(0, $this->skipReadBytes - $len); + } + + $data .= $remoteData; + $this->stream->write($remoteData); + } + + return $data; + } + + public function write($string): int + { + // When appending to the end of the currently read stream, you'll want + // to skip bytes from being read from the remote stream to emulate + // other stream wrappers. Basically replacing bytes of data of a fixed + // length. + $overflow = (strlen($string) + $this->tell()) - $this->remoteStream->tell(); + if ($overflow > 0) { + $this->skipReadBytes += $overflow; + } + + return $this->stream->write($string); + } + + public function eof(): bool + { + return $this->stream->eof() && $this->remoteStream->eof(); + } + + /** + * Close both the remote stream and buffer stream + */ + public function close(): void + { + $this->remoteStream->close(); + $this->stream->close(); + } + + private function cacheEntireStream(): int + { + $target = new FnStream(['write' => 'strlen']); + Utils::copyToStream($this, $target); + + return $this->tell(); + } +} diff --git a/vendor/guzzlehttp/psr7/src/DroppingStream.php b/vendor/guzzlehttp/psr7/src/DroppingStream.php new file mode 100644 index 0000000..d78070a --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/DroppingStream.php @@ -0,0 +1,46 @@ +stream = $stream; + $this->maxLength = $maxLength; + } + + public function write($string): int + { + $diff = $this->maxLength - $this->stream->getSize(); + + // Begin returning 0 when the underlying stream is too large. + if ($diff <= 0) { + return 0; + } + + // Write the stream or a subset of the stream if needed. + if (strlen($string) < $diff) { + return $this->stream->write($string); + } + + return $this->stream->write(substr($string, 0, $diff)); + } +} diff --git a/vendor/guzzlehttp/psr7/src/Exception/MalformedUriException.php b/vendor/guzzlehttp/psr7/src/Exception/MalformedUriException.php new file mode 100644 index 0000000..3a08477 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Exception/MalformedUriException.php @@ -0,0 +1,14 @@ + */ + private $methods; + + /** + * @param array $methods Hash of method name to a callable. + */ + public function __construct(array $methods) + { + $this->methods = $methods; + + // Create the functions on the class + foreach ($methods as $name => $fn) { + $this->{'_fn_' . $name} = $fn; + } + } + + /** + * Lazily determine which methods are not implemented. + * + * @throws \BadMethodCallException + */ + public function __get(string $name): void + { + throw new \BadMethodCallException(str_replace('_fn_', '', $name) + . '() is not implemented in the FnStream'); + } + + /** + * The close method is called on the underlying stream only if possible. + */ + public function __destruct() + { + if (isset($this->_fn_close)) { + call_user_func($this->_fn_close); + } + } + + /** + * An unserialize would allow the __destruct to run when the unserialized value goes out of scope. + * + * @throws \LogicException + */ + public function __wakeup(): void + { + throw new \LogicException('FnStream should never be unserialized'); + } + + /** + * Adds custom functionality to an underlying stream by intercepting + * specific method calls. + * + * @param StreamInterface $stream Stream to decorate + * @param array $methods Hash of method name to a closure + * + * @return FnStream + */ + public static function decorate(StreamInterface $stream, array $methods) + { + // If any of the required methods were not provided, then simply + // proxy to the decorated stream. + foreach (array_diff(self::SLOTS, array_keys($methods)) as $diff) { + /** @var callable $callable */ + $callable = [$stream, $diff]; + $methods[$diff] = $callable; + } + + return new self($methods); + } + + public function __toString(): string + { + try { + return call_user_func($this->_fn___toString); + } catch (\Throwable $e) { + if (\PHP_VERSION_ID >= 70400) { + throw $e; + } + trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR); + return ''; + } + } + + public function close(): void + { + call_user_func($this->_fn_close); + } + + public function detach() + { + return call_user_func($this->_fn_detach); + } + + public function getSize(): ?int + { + return call_user_func($this->_fn_getSize); + } + + public function tell(): int + { + return call_user_func($this->_fn_tell); + } + + public function eof(): bool + { + return call_user_func($this->_fn_eof); + } + + public function isSeekable(): bool + { + return call_user_func($this->_fn_isSeekable); + } + + public function rewind(): void + { + call_user_func($this->_fn_rewind); + } + + public function seek($offset, $whence = SEEK_SET): void + { + call_user_func($this->_fn_seek, $offset, $whence); + } + + public function isWritable(): bool + { + return call_user_func($this->_fn_isWritable); + } + + public function write($string): int + { + return call_user_func($this->_fn_write, $string); + } + + public function isReadable(): bool + { + return call_user_func($this->_fn_isReadable); + } + + public function read($length): string + { + return call_user_func($this->_fn_read, $length); + } + + public function getContents(): string + { + return call_user_func($this->_fn_getContents); + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function getMetadata($key = null) + { + return call_user_func($this->_fn_getMetadata, $key); + } +} diff --git a/vendor/guzzlehttp/psr7/src/Header.php b/vendor/guzzlehttp/psr7/src/Header.php new file mode 100644 index 0000000..0e79a71 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Header.php @@ -0,0 +1,69 @@ +]+>|[^=]+/', $kvp, $matches)) { + $m = $matches[0]; + if (isset($m[1])) { + $part[trim($m[0], $trimmed)] = trim($m[1], $trimmed); + } else { + $part[] = trim($m[0], $trimmed); + } + } + } + if ($part) { + $params[] = $part; + } + } + + return $params; + } + + /** + * Converts an array of header values that may contain comma separated + * headers into an array of headers with no comma separated values. + * + * @param string|array $header Header to normalize. + */ + public static function normalize($header): array + { + if (!is_array($header)) { + return array_map('trim', explode(',', $header)); + } + + $result = []; + foreach ($header as $value) { + foreach ((array) $value as $v) { + if (strpos($v, ',') === false) { + $result[] = $v; + continue; + } + foreach (preg_split('/,(?=([^"]*"[^"]*")*[^"]*$)/', $v) as $vv) { + $result[] = trim($vv); + } + } + } + + return $result; + } +} diff --git a/vendor/guzzlehttp/psr7/src/HttpFactory.php b/vendor/guzzlehttp/psr7/src/HttpFactory.php new file mode 100644 index 0000000..30be222 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/HttpFactory.php @@ -0,0 +1,100 @@ +getSize(); + } + + return new UploadedFile($stream, $size, $error, $clientFilename, $clientMediaType); + } + + public function createStream(string $content = ''): StreamInterface + { + return Utils::streamFor($content); + } + + public function createStreamFromFile(string $file, string $mode = 'r'): StreamInterface + { + try { + $resource = Utils::tryFopen($file, $mode); + } catch (\RuntimeException $e) { + if ('' === $mode || false === \in_array($mode[0], ['r', 'w', 'a', 'x', 'c'], true)) { + throw new \InvalidArgumentException(sprintf('Invalid file opening mode "%s"', $mode), 0, $e); + } + + throw $e; + } + + return Utils::streamFor($resource); + } + + public function createStreamFromResource($resource): StreamInterface + { + return Utils::streamFor($resource); + } + + public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface + { + if (empty($method)) { + if (!empty($serverParams['REQUEST_METHOD'])) { + $method = $serverParams['REQUEST_METHOD']; + } else { + throw new \InvalidArgumentException('Cannot determine HTTP method'); + } + } + + return new ServerRequest($method, $uri, [], null, '1.1', $serverParams); + } + + public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface + { + return new Response($code, [], null, '1.1', $reasonPhrase); + } + + public function createRequest(string $method, $uri): RequestInterface + { + return new Request($method, $uri); + } + + public function createUri(string $uri = ''): UriInterface + { + return new Uri($uri); + } +} diff --git a/vendor/guzzlehttp/psr7/src/InflateStream.php b/vendor/guzzlehttp/psr7/src/InflateStream.php new file mode 100644 index 0000000..8e3cf17 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/InflateStream.php @@ -0,0 +1,34 @@ + 15 + 32]); + $this->stream = $stream->isSeekable() ? new Stream($resource) : new NoSeekStream(new Stream($resource)); + } +} diff --git a/vendor/guzzlehttp/psr7/src/LazyOpenStream.php b/vendor/guzzlehttp/psr7/src/LazyOpenStream.php new file mode 100644 index 0000000..6b60429 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/LazyOpenStream.php @@ -0,0 +1,40 @@ +filename = $filename; + $this->mode = $mode; + } + + /** + * Creates the underlying stream lazily when required. + */ + protected function createStream(): StreamInterface + { + return Utils::streamFor(Utils::tryFopen($this->filename, $this->mode)); + } +} diff --git a/vendor/guzzlehttp/psr7/src/LimitStream.php b/vendor/guzzlehttp/psr7/src/LimitStream.php new file mode 100644 index 0000000..9762d38 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/LimitStream.php @@ -0,0 +1,154 @@ +stream = $stream; + $this->setLimit($limit); + $this->setOffset($offset); + } + + public function eof(): bool + { + // Always return true if the underlying stream is EOF + if ($this->stream->eof()) { + return true; + } + + // No limit and the underlying stream is not at EOF + if ($this->limit === -1) { + return false; + } + + return $this->stream->tell() >= $this->offset + $this->limit; + } + + /** + * Returns the size of the limited subset of data + */ + public function getSize(): ?int + { + if (null === ($length = $this->stream->getSize())) { + return null; + } elseif ($this->limit === -1) { + return $length - $this->offset; + } else { + return min($this->limit, $length - $this->offset); + } + } + + /** + * Allow for a bounded seek on the read limited stream + */ + public function seek($offset, $whence = SEEK_SET): void + { + if ($whence !== SEEK_SET || $offset < 0) { + throw new \RuntimeException(sprintf( + 'Cannot seek to offset %s with whence %s', + $offset, + $whence + )); + } + + $offset += $this->offset; + + if ($this->limit !== -1) { + if ($offset > $this->offset + $this->limit) { + $offset = $this->offset + $this->limit; + } + } + + $this->stream->seek($offset); + } + + /** + * Give a relative tell() + */ + public function tell(): int + { + return $this->stream->tell() - $this->offset; + } + + /** + * Set the offset to start limiting from + * + * @param int $offset Offset to seek to and begin byte limiting from + * + * @throws \RuntimeException if the stream cannot be seeked. + */ + public function setOffset(int $offset): void + { + $current = $this->stream->tell(); + + if ($current !== $offset) { + // If the stream cannot seek to the offset position, then read to it + if ($this->stream->isSeekable()) { + $this->stream->seek($offset); + } elseif ($current > $offset) { + throw new \RuntimeException("Could not seek to stream offset $offset"); + } else { + $this->stream->read($offset - $current); + } + } + + $this->offset = $offset; + } + + /** + * Set the limit of bytes that the decorator allows to be read from the + * stream. + * + * @param int $limit Number of bytes to allow to be read from the stream. + * Use -1 for no limit. + */ + public function setLimit(int $limit): void + { + $this->limit = $limit; + } + + public function read($length): string + { + if ($this->limit === -1) { + return $this->stream->read($length); + } + + // Check if the current position is less than the total allowed + // bytes + original offset + $remaining = ($this->offset + $this->limit) - $this->stream->tell(); + if ($remaining > 0) { + // Only return the amount of requested data, ensuring that the byte + // limit is not exceeded + return $this->stream->read(min($remaining, $length)); + } + + return ''; + } +} diff --git a/vendor/guzzlehttp/psr7/src/Message.php b/vendor/guzzlehttp/psr7/src/Message.php new file mode 100644 index 0000000..9b825b3 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Message.php @@ -0,0 +1,242 @@ +getMethod() . ' ' + . $message->getRequestTarget()) + . ' HTTP/' . $message->getProtocolVersion(); + if (!$message->hasHeader('host')) { + $msg .= "\r\nHost: " . $message->getUri()->getHost(); + } + } elseif ($message instanceof ResponseInterface) { + $msg = 'HTTP/' . $message->getProtocolVersion() . ' ' + . $message->getStatusCode() . ' ' + . $message->getReasonPhrase(); + } else { + throw new \InvalidArgumentException('Unknown message type'); + } + + foreach ($message->getHeaders() as $name => $values) { + if (strtolower($name) === 'set-cookie') { + foreach ($values as $value) { + $msg .= "\r\n{$name}: " . $value; + } + } else { + $msg .= "\r\n{$name}: " . implode(', ', $values); + } + } + + return "{$msg}\r\n\r\n" . $message->getBody(); + } + + /** + * Get a short summary of the message body. + * + * Will return `null` if the response is not printable. + * + * @param MessageInterface $message The message to get the body summary + * @param int $truncateAt The maximum allowed size of the summary + */ + public static function bodySummary(MessageInterface $message, int $truncateAt = 120): ?string + { + $body = $message->getBody(); + + if (!$body->isSeekable() || !$body->isReadable()) { + return null; + } + + $size = $body->getSize(); + + if ($size === 0) { + return null; + } + + $summary = $body->read($truncateAt); + $body->rewind(); + + if ($size > $truncateAt) { + $summary .= ' (truncated...)'; + } + + // Matches any printable character, including unicode characters: + // letters, marks, numbers, punctuation, spacing, and separators. + if (preg_match('/[^\pL\pM\pN\pP\pS\pZ\n\r\t]/u', $summary)) { + return null; + } + + return $summary; + } + + /** + * Attempts to rewind a message body and throws an exception on failure. + * + * The body of the message will only be rewound if a call to `tell()` + * returns a value other than `0`. + * + * @param MessageInterface $message Message to rewind + * + * @throws \RuntimeException + */ + public static function rewindBody(MessageInterface $message): void + { + $body = $message->getBody(); + + if ($body->tell()) { + $body->rewind(); + } + } + + /** + * Parses an HTTP message into an associative array. + * + * The array contains the "start-line" key containing the start line of + * the message, "headers" key containing an associative array of header + * array values, and a "body" key containing the body of the message. + * + * @param string $message HTTP request or response to parse. + */ + public static function parseMessage(string $message): array + { + if (!$message) { + throw new \InvalidArgumentException('Invalid message'); + } + + $message = ltrim($message, "\r\n"); + + $messageParts = preg_split("/\r?\n\r?\n/", $message, 2); + + if ($messageParts === false || count($messageParts) !== 2) { + throw new \InvalidArgumentException('Invalid message: Missing header delimiter'); + } + + [$rawHeaders, $body] = $messageParts; + $rawHeaders .= "\r\n"; // Put back the delimiter we split previously + $headerParts = preg_split("/\r?\n/", $rawHeaders, 2); + + if ($headerParts === false || count($headerParts) !== 2) { + throw new \InvalidArgumentException('Invalid message: Missing status line'); + } + + [$startLine, $rawHeaders] = $headerParts; + + if (preg_match("/(?:^HTTP\/|^[A-Z]+ \S+ HTTP\/)(\d+(?:\.\d+)?)/i", $startLine, $matches) && $matches[1] === '1.0') { + // Header folding is deprecated for HTTP/1.1, but allowed in HTTP/1.0 + $rawHeaders = preg_replace(Rfc7230::HEADER_FOLD_REGEX, ' ', $rawHeaders); + } + + /** @var array[] $headerLines */ + $count = preg_match_all(Rfc7230::HEADER_REGEX, $rawHeaders, $headerLines, PREG_SET_ORDER); + + // If these aren't the same, then one line didn't match and there's an invalid header. + if ($count !== substr_count($rawHeaders, "\n")) { + // Folding is deprecated, see https://tools.ietf.org/html/rfc7230#section-3.2.4 + if (preg_match(Rfc7230::HEADER_FOLD_REGEX, $rawHeaders)) { + throw new \InvalidArgumentException('Invalid header syntax: Obsolete line folding'); + } + + throw new \InvalidArgumentException('Invalid header syntax'); + } + + $headers = []; + + foreach ($headerLines as $headerLine) { + $headers[$headerLine[1]][] = $headerLine[2]; + } + + return [ + 'start-line' => $startLine, + 'headers' => $headers, + 'body' => $body, + ]; + } + + /** + * Constructs a URI for an HTTP request message. + * + * @param string $path Path from the start-line + * @param array $headers Array of headers (each value an array). + */ + public static function parseRequestUri(string $path, array $headers): string + { + $hostKey = array_filter(array_keys($headers), function ($k) { + return strtolower($k) === 'host'; + }); + + // If no host is found, then a full URI cannot be constructed. + if (!$hostKey) { + return $path; + } + + $host = $headers[reset($hostKey)][0]; + $scheme = substr($host, -4) === ':443' ? 'https' : 'http'; + + return $scheme . '://' . $host . '/' . ltrim($path, '/'); + } + + /** + * Parses a request message string into a request object. + * + * @param string $message Request message string. + */ + public static function parseRequest(string $message): RequestInterface + { + $data = self::parseMessage($message); + $matches = []; + if (!preg_match('/^[\S]+\s+([a-zA-Z]+:\/\/|\/).*/', $data['start-line'], $matches)) { + throw new \InvalidArgumentException('Invalid request string'); + } + $parts = explode(' ', $data['start-line'], 3); + $version = isset($parts[2]) ? explode('/', $parts[2])[1] : '1.1'; + + $request = new Request( + $parts[0], + $matches[1] === '/' ? self::parseRequestUri($parts[1], $data['headers']) : $parts[1], + $data['headers'], + $data['body'], + $version + ); + + return $matches[1] === '/' ? $request : $request->withRequestTarget($parts[1]); + } + + /** + * Parses a response message string into a response object. + * + * @param string $message Response message string. + */ + public static function parseResponse(string $message): ResponseInterface + { + $data = self::parseMessage($message); + // According to https://tools.ietf.org/html/rfc7230#section-3.1.2 the space + // between status-code and reason-phrase is required. But browsers accept + // responses without space and reason as well. + if (!preg_match('/^HTTP\/.* [0-9]{3}( .*|$)/', $data['start-line'])) { + throw new \InvalidArgumentException('Invalid response string: ' . $data['start-line']); + } + $parts = explode(' ', $data['start-line'], 3); + + return new Response( + (int) $parts[1], + $data['headers'], + $data['body'], + explode('/', $parts[0])[1], + $parts[2] ?? null + ); + } +} diff --git a/vendor/guzzlehttp/psr7/src/MessageTrait.php b/vendor/guzzlehttp/psr7/src/MessageTrait.php new file mode 100644 index 0000000..503c280 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/MessageTrait.php @@ -0,0 +1,235 @@ + Map of all registered headers, as original name => array of values */ + private $headers = []; + + /** @var array Map of lowercase header name => original name at registration */ + private $headerNames = []; + + /** @var string */ + private $protocol = '1.1'; + + /** @var StreamInterface|null */ + private $stream; + + public function getProtocolVersion(): string + { + return $this->protocol; + } + + public function withProtocolVersion($version): MessageInterface + { + if ($this->protocol === $version) { + return $this; + } + + $new = clone $this; + $new->protocol = $version; + return $new; + } + + public function getHeaders(): array + { + return $this->headers; + } + + public function hasHeader($header): bool + { + return isset($this->headerNames[strtolower($header)]); + } + + public function getHeader($header): array + { + $header = strtolower($header); + + if (!isset($this->headerNames[$header])) { + return []; + } + + $header = $this->headerNames[$header]; + + return $this->headers[$header]; + } + + public function getHeaderLine($header): string + { + return implode(', ', $this->getHeader($header)); + } + + public function withHeader($header, $value): MessageInterface + { + $this->assertHeader($header); + $value = $this->normalizeHeaderValue($value); + $normalized = strtolower($header); + + $new = clone $this; + if (isset($new->headerNames[$normalized])) { + unset($new->headers[$new->headerNames[$normalized]]); + } + $new->headerNames[$normalized] = $header; + $new->headers[$header] = $value; + + return $new; + } + + public function withAddedHeader($header, $value): MessageInterface + { + $this->assertHeader($header); + $value = $this->normalizeHeaderValue($value); + $normalized = strtolower($header); + + $new = clone $this; + if (isset($new->headerNames[$normalized])) { + $header = $this->headerNames[$normalized]; + $new->headers[$header] = array_merge($this->headers[$header], $value); + } else { + $new->headerNames[$normalized] = $header; + $new->headers[$header] = $value; + } + + return $new; + } + + public function withoutHeader($header): MessageInterface + { + $normalized = strtolower($header); + + if (!isset($this->headerNames[$normalized])) { + return $this; + } + + $header = $this->headerNames[$normalized]; + + $new = clone $this; + unset($new->headers[$header], $new->headerNames[$normalized]); + + return $new; + } + + public function getBody(): StreamInterface + { + if (!$this->stream) { + $this->stream = Utils::streamFor(''); + } + + return $this->stream; + } + + public function withBody(StreamInterface $body): MessageInterface + { + if ($body === $this->stream) { + return $this; + } + + $new = clone $this; + $new->stream = $body; + return $new; + } + + /** + * @param array $headers + */ + private function setHeaders(array $headers): void + { + $this->headerNames = $this->headers = []; + foreach ($headers as $header => $value) { + if (is_int($header)) { + // Numeric array keys are converted to int by PHP but having a header name '123' is not forbidden by the spec + // and also allowed in withHeader(). So we need to cast it to string again for the following assertion to pass. + $header = (string) $header; + } + $this->assertHeader($header); + $value = $this->normalizeHeaderValue($value); + $normalized = strtolower($header); + if (isset($this->headerNames[$normalized])) { + $header = $this->headerNames[$normalized]; + $this->headers[$header] = array_merge($this->headers[$header], $value); + } else { + $this->headerNames[$normalized] = $header; + $this->headers[$header] = $value; + } + } + } + + /** + * @param mixed $value + * + * @return string[] + */ + private function normalizeHeaderValue($value): array + { + if (!is_array($value)) { + return $this->trimHeaderValues([$value]); + } + + if (count($value) === 0) { + throw new \InvalidArgumentException('Header value can not be an empty array.'); + } + + return $this->trimHeaderValues($value); + } + + /** + * Trims whitespace from the header values. + * + * Spaces and tabs ought to be excluded by parsers when extracting the field value from a header field. + * + * header-field = field-name ":" OWS field-value OWS + * OWS = *( SP / HTAB ) + * + * @param mixed[] $values Header values + * + * @return string[] Trimmed header values + * + * @see https://tools.ietf.org/html/rfc7230#section-3.2.4 + */ + private function trimHeaderValues(array $values): array + { + return array_map(function ($value) { + if (!is_scalar($value) && null !== $value) { + throw new \InvalidArgumentException(sprintf( + 'Header value must be scalar or null but %s provided.', + is_object($value) ? get_class($value) : gettype($value) + )); + } + + return trim((string) $value, " \t"); + }, array_values($values)); + } + + /** + * @see https://tools.ietf.org/html/rfc7230#section-3.2 + * + * @param mixed $header + */ + private function assertHeader($header): void + { + if (!is_string($header)) { + throw new \InvalidArgumentException(sprintf( + 'Header name must be a string but %s provided.', + is_object($header) ? get_class($header) : gettype($header) + )); + } + + if (! preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/', $header)) { + throw new \InvalidArgumentException( + sprintf( + '"%s" is not valid header name', + $header + ) + ); + } + } +} diff --git a/vendor/guzzlehttp/psr7/src/MimeType.php b/vendor/guzzlehttp/psr7/src/MimeType.php new file mode 100644 index 0000000..dfa9425 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/MimeType.php @@ -0,0 +1,130 @@ + 'video/3gpp', + '7z' => 'application/x-7z-compressed', + 'aac' => 'audio/x-aac', + 'ai' => 'application/postscript', + 'aif' => 'audio/x-aiff', + 'asc' => 'text/plain', + 'asf' => 'video/x-ms-asf', + 'atom' => 'application/atom+xml', + 'avi' => 'video/x-msvideo', + 'bmp' => 'image/bmp', + 'bz2' => 'application/x-bzip2', + 'cer' => 'application/pkix-cert', + 'crl' => 'application/pkix-crl', + 'crt' => 'application/x-x509-ca-cert', + 'css' => 'text/css', + 'csv' => 'text/csv', + 'cu' => 'application/cu-seeme', + 'deb' => 'application/x-debian-package', + 'doc' => 'application/msword', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'dvi' => 'application/x-dvi', + 'eot' => 'application/vnd.ms-fontobject', + 'eps' => 'application/postscript', + 'epub' => 'application/epub+zip', + 'etx' => 'text/x-setext', + 'flac' => 'audio/flac', + 'flv' => 'video/x-flv', + 'gif' => 'image/gif', + 'gz' => 'application/gzip', + 'htm' => 'text/html', + 'html' => 'text/html', + 'ico' => 'image/x-icon', + 'ics' => 'text/calendar', + 'ini' => 'text/plain', + 'iso' => 'application/x-iso9660-image', + 'jar' => 'application/java-archive', + 'jpe' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'js' => 'text/javascript', + 'json' => 'application/json', + 'latex' => 'application/x-latex', + 'log' => 'text/plain', + 'm4a' => 'audio/mp4', + 'm4v' => 'video/mp4', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mov' => 'video/quicktime', + 'mkv' => 'video/x-matroska', + 'mp3' => 'audio/mpeg', + 'mp4' => 'video/mp4', + 'mp4a' => 'audio/mp4', + 'mp4v' => 'video/mp4', + 'mpe' => 'video/mpeg', + 'mpeg' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mpg4' => 'video/mp4', + 'oga' => 'audio/ogg', + 'ogg' => 'audio/ogg', + 'ogv' => 'video/ogg', + 'ogx' => 'application/ogg', + 'pbm' => 'image/x-portable-bitmap', + 'pdf' => 'application/pdf', + 'pgm' => 'image/x-portable-graymap', + 'png' => 'image/png', + 'pnm' => 'image/x-portable-anymap', + 'ppm' => 'image/x-portable-pixmap', + 'ppt' => 'application/vnd.ms-powerpoint', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'ps' => 'application/postscript', + 'qt' => 'video/quicktime', + 'rar' => 'application/x-rar-compressed', + 'ras' => 'image/x-cmu-raster', + 'rss' => 'application/rss+xml', + 'rtf' => 'application/rtf', + 'sgm' => 'text/sgml', + 'sgml' => 'text/sgml', + 'svg' => 'image/svg+xml', + 'swf' => 'application/x-shockwave-flash', + 'tar' => 'application/x-tar', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'torrent' => 'application/x-bittorrent', + 'ttf' => 'application/x-font-ttf', + 'txt' => 'text/plain', + 'wav' => 'audio/x-wav', + 'webm' => 'video/webm', + 'webp' => 'image/webp', + 'wma' => 'audio/x-ms-wma', + 'wmv' => 'video/x-ms-wmv', + 'woff' => 'application/x-font-woff', + 'wsdl' => 'application/wsdl+xml', + 'xbm' => 'image/x-xbitmap', + 'xls' => 'application/vnd.ms-excel', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xml' => 'application/xml', + 'xpm' => 'image/x-xpixmap', + 'xwd' => 'image/x-xwindowdump', + 'yaml' => 'text/yaml', + 'yml' => 'text/yaml', + 'zip' => 'application/zip', + ]; + + /** + * Determines the mimetype of a file by looking at its extension. + */ + public static function fromFilename(string $filename): ?string + { + return self::fromExtension(pathinfo($filename, PATHINFO_EXTENSION)); + } + + /** + * Maps a file extensions to a mimetype. + * + * @link http://svn.apache.org/repos/asf/httpd/httpd/branches/1.3.x/conf/mime.types + */ + public static function fromExtension(string $extension): ?string + { + return self::MIME_TYPES[strtolower($extension)] ?? null; + } +} diff --git a/vendor/guzzlehttp/psr7/src/MultipartStream.php b/vendor/guzzlehttp/psr7/src/MultipartStream.php new file mode 100644 index 0000000..f76d7c6 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/MultipartStream.php @@ -0,0 +1,153 @@ +boundary = $boundary ?: sha1(uniqid('', true)); + $this->stream = $this->createStream($elements); + } + + public function getBoundary(): string + { + return $this->boundary; + } + + public function isWritable(): bool + { + return false; + } + + /** + * Get the headers needed before transferring the content of a POST file + * + * @param array $headers + */ + private function getHeaders(array $headers): string + { + $str = ''; + foreach ($headers as $key => $value) { + $str .= "{$key}: {$value}\r\n"; + } + + return "--{$this->boundary}\r\n" . trim($str) . "\r\n\r\n"; + } + + /** + * Create the aggregate stream that will be used to upload the POST data + */ + protected function createStream(array $elements = []): StreamInterface + { + $stream = new AppendStream(); + + foreach ($elements as $element) { + $this->addElement($stream, $element); + } + + // Add the trailing boundary with CRLF + $stream->addStream(Utils::streamFor("--{$this->boundary}--\r\n")); + + return $stream; + } + + private function addElement(AppendStream $stream, array $element): void + { + foreach (['contents', 'name'] as $key) { + if (!array_key_exists($key, $element)) { + throw new \InvalidArgumentException("A '{$key}' key is required"); + } + } + + $element['contents'] = Utils::streamFor($element['contents']); + + if (empty($element['filename'])) { + $uri = $element['contents']->getMetadata('uri'); + if (substr($uri, 0, 6) !== 'php://') { + $element['filename'] = $uri; + } + } + + [$body, $headers] = $this->createElement( + $element['name'], + $element['contents'], + $element['filename'] ?? null, + $element['headers'] ?? [] + ); + + $stream->addStream(Utils::streamFor($this->getHeaders($headers))); + $stream->addStream($body); + $stream->addStream(Utils::streamFor("\r\n")); + } + + private function createElement(string $name, StreamInterface $stream, ?string $filename, array $headers): array + { + // Set a default content-disposition header if one was no provided + $disposition = $this->getHeader($headers, 'content-disposition'); + if (!$disposition) { + $headers['Content-Disposition'] = ($filename === '0' || $filename) + ? sprintf( + 'form-data; name="%s"; filename="%s"', + $name, + basename($filename) + ) + : "form-data; name=\"{$name}\""; + } + + // Set a default content-length header if one was no provided + $length = $this->getHeader($headers, 'content-length'); + if (!$length) { + if ($length = $stream->getSize()) { + $headers['Content-Length'] = (string) $length; + } + } + + // Set a default Content-Type if one was not supplied + $type = $this->getHeader($headers, 'content-type'); + if (!$type && ($filename === '0' || $filename)) { + if ($type = MimeType::fromFilename($filename)) { + $headers['Content-Type'] = $type; + } + } + + return [$stream, $headers]; + } + + private function getHeader(array $headers, string $key) + { + $lowercaseHeader = strtolower($key); + foreach ($headers as $k => $v) { + if (strtolower($k) === $lowercaseHeader) { + return $v; + } + } + + return null; + } +} diff --git a/vendor/guzzlehttp/psr7/src/NoSeekStream.php b/vendor/guzzlehttp/psr7/src/NoSeekStream.php new file mode 100644 index 0000000..99e25b9 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/NoSeekStream.php @@ -0,0 +1,25 @@ +source = $source; + $this->size = $options['size'] ?? null; + $this->metadata = $options['metadata'] ?? []; + $this->buffer = new BufferStream(); + } + + public function __toString(): string + { + try { + return Utils::copyToString($this); + } catch (\Throwable $e) { + if (\PHP_VERSION_ID >= 70400) { + throw $e; + } + trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR); + return ''; + } + } + + public function close(): void + { + $this->detach(); + } + + public function detach() + { + $this->tellPos = 0; + $this->source = null; + + return null; + } + + public function getSize(): ?int + { + return $this->size; + } + + public function tell(): int + { + return $this->tellPos; + } + + public function eof(): bool + { + return $this->source === null; + } + + public function isSeekable(): bool + { + return false; + } + + public function rewind(): void + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET): void + { + throw new \RuntimeException('Cannot seek a PumpStream'); + } + + public function isWritable(): bool + { + return false; + } + + public function write($string): int + { + throw new \RuntimeException('Cannot write to a PumpStream'); + } + + public function isReadable(): bool + { + return true; + } + + public function read($length): string + { + $data = $this->buffer->read($length); + $readLen = strlen($data); + $this->tellPos += $readLen; + $remaining = $length - $readLen; + + if ($remaining) { + $this->pump($remaining); + $data .= $this->buffer->read($remaining); + $this->tellPos += strlen($data) - $readLen; + } + + return $data; + } + + public function getContents(): string + { + $result = ''; + while (!$this->eof()) { + $result .= $this->read(1000000); + } + + return $result; + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function getMetadata($key = null) + { + if (!$key) { + return $this->metadata; + } + + return $this->metadata[$key] ?? null; + } + + private function pump(int $length): void + { + if ($this->source) { + do { + $data = call_user_func($this->source, $length); + if ($data === false || $data === null) { + $this->source = null; + return; + } + $this->buffer->write($data); + $length -= strlen($data); + } while ($length > 0); + } + } +} diff --git a/vendor/guzzlehttp/psr7/src/Query.php b/vendor/guzzlehttp/psr7/src/Query.php new file mode 100644 index 0000000..4fd0ca9 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Query.php @@ -0,0 +1,113 @@ + '1', 'foo[b]' => '2'])`. + * + * @param string $str Query string to parse + * @param int|bool $urlEncoding How the query string is encoded + */ + public static function parse(string $str, $urlEncoding = true): array + { + $result = []; + + if ($str === '') { + return $result; + } + + if ($urlEncoding === true) { + $decoder = function ($value) { + return rawurldecode(str_replace('+', ' ', (string) $value)); + }; + } elseif ($urlEncoding === PHP_QUERY_RFC3986) { + $decoder = 'rawurldecode'; + } elseif ($urlEncoding === PHP_QUERY_RFC1738) { + $decoder = 'urldecode'; + } else { + $decoder = function ($str) { + return $str; + }; + } + + foreach (explode('&', $str) as $kvp) { + $parts = explode('=', $kvp, 2); + $key = $decoder($parts[0]); + $value = isset($parts[1]) ? $decoder($parts[1]) : null; + if (!isset($result[$key])) { + $result[$key] = $value; + } else { + if (!is_array($result[$key])) { + $result[$key] = [$result[$key]]; + } + $result[$key][] = $value; + } + } + + return $result; + } + + /** + * Build a query string from an array of key value pairs. + * + * This function can use the return value of `parse()` to build a query + * string. This function does not modify the provided keys when an array is + * encountered (like `http_build_query()` would). + * + * @param array $params Query string parameters. + * @param int|false $encoding Set to false to not encode, PHP_QUERY_RFC3986 + * to encode using RFC3986, or PHP_QUERY_RFC1738 + * to encode using RFC1738. + */ + public static function build(array $params, $encoding = PHP_QUERY_RFC3986): string + { + if (!$params) { + return ''; + } + + if ($encoding === false) { + $encoder = function (string $str): string { + return $str; + }; + } elseif ($encoding === PHP_QUERY_RFC3986) { + $encoder = 'rawurlencode'; + } elseif ($encoding === PHP_QUERY_RFC1738) { + $encoder = 'urlencode'; + } else { + throw new \InvalidArgumentException('Invalid type'); + } + + $qs = ''; + foreach ($params as $k => $v) { + $k = $encoder((string) $k); + if (!is_array($v)) { + $qs .= $k; + $v = is_bool($v) ? (int) $v : $v; + if ($v !== null) { + $qs .= '=' . $encoder((string) $v); + } + $qs .= '&'; + } else { + foreach ($v as $vv) { + $qs .= $k; + $vv = is_bool($vv) ? (int) $vv : $vv; + if ($vv !== null) { + $qs .= '=' . $encoder((string) $vv); + } + $qs .= '&'; + } + } + } + + return $qs ? (string) substr($qs, 0, -1) : ''; + } +} diff --git a/vendor/guzzlehttp/psr7/src/Request.php b/vendor/guzzlehttp/psr7/src/Request.php new file mode 100644 index 0000000..b17af66 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Request.php @@ -0,0 +1,157 @@ + $headers Request headers + * @param string|resource|StreamInterface|null $body Request body + * @param string $version Protocol version + */ + public function __construct( + string $method, + $uri, + array $headers = [], + $body = null, + string $version = '1.1' + ) { + $this->assertMethod($method); + if (!($uri instanceof UriInterface)) { + $uri = new Uri($uri); + } + + $this->method = strtoupper($method); + $this->uri = $uri; + $this->setHeaders($headers); + $this->protocol = $version; + + if (!isset($this->headerNames['host'])) { + $this->updateHostFromUri(); + } + + if ($body !== '' && $body !== null) { + $this->stream = Utils::streamFor($body); + } + } + + public function getRequestTarget(): string + { + if ($this->requestTarget !== null) { + return $this->requestTarget; + } + + $target = $this->uri->getPath(); + if ($target === '') { + $target = '/'; + } + if ($this->uri->getQuery() != '') { + $target .= '?' . $this->uri->getQuery(); + } + + return $target; + } + + public function withRequestTarget($requestTarget): RequestInterface + { + if (preg_match('#\s#', $requestTarget)) { + throw new InvalidArgumentException( + 'Invalid request target provided; cannot contain whitespace' + ); + } + + $new = clone $this; + $new->requestTarget = $requestTarget; + return $new; + } + + public function getMethod(): string + { + return $this->method; + } + + public function withMethod($method): RequestInterface + { + $this->assertMethod($method); + $new = clone $this; + $new->method = strtoupper($method); + return $new; + } + + public function getUri(): UriInterface + { + return $this->uri; + } + + public function withUri(UriInterface $uri, $preserveHost = false): RequestInterface + { + if ($uri === $this->uri) { + return $this; + } + + $new = clone $this; + $new->uri = $uri; + + if (!$preserveHost || !isset($this->headerNames['host'])) { + $new->updateHostFromUri(); + } + + return $new; + } + + private function updateHostFromUri(): void + { + $host = $this->uri->getHost(); + + if ($host == '') { + return; + } + + if (($port = $this->uri->getPort()) !== null) { + $host .= ':' . $port; + } + + if (isset($this->headerNames['host'])) { + $header = $this->headerNames['host']; + } else { + $header = 'Host'; + $this->headerNames['host'] = 'Host'; + } + // Ensure Host is the first header. + // See: http://tools.ietf.org/html/rfc7230#section-5.4 + $this->headers = [$header => [$host]] + $this->headers; + } + + /** + * @param mixed $method + */ + private function assertMethod($method): void + { + if (!is_string($method) || $method === '') { + throw new InvalidArgumentException('Method must be a non-empty string.'); + } + } +} diff --git a/vendor/guzzlehttp/psr7/src/Response.php b/vendor/guzzlehttp/psr7/src/Response.php new file mode 100644 index 0000000..4c6ee6f --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Response.php @@ -0,0 +1,160 @@ + 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-status', + 208 => 'Already Reported', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 306 => 'Switch Proxy', + 307 => 'Temporary Redirect', + 308 => 'Permanent Redirect', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Time-out', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Large', + 415 => 'Unsupported Media Type', + 416 => 'Requested range not satisfiable', + 417 => 'Expectation Failed', + 418 => 'I\'m a teapot', + 422 => 'Unprocessable Entity', + 423 => 'Locked', + 424 => 'Failed Dependency', + 425 => 'Unordered Collection', + 426 => 'Upgrade Required', + 428 => 'Precondition Required', + 429 => 'Too Many Requests', + 431 => 'Request Header Fields Too Large', + 451 => 'Unavailable For Legal Reasons', + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Time-out', + 505 => 'HTTP Version not supported', + 506 => 'Variant Also Negotiates', + 507 => 'Insufficient Storage', + 508 => 'Loop Detected', + 510 => 'Not Extended', + 511 => 'Network Authentication Required', + ]; + + /** @var string */ + private $reasonPhrase; + + /** @var int */ + private $statusCode; + + /** + * @param int $status Status code + * @param array $headers Response headers + * @param string|resource|StreamInterface|null $body Response body + * @param string $version Protocol version + * @param string|null $reason Reason phrase (when empty a default will be used based on the status code) + */ + public function __construct( + int $status = 200, + array $headers = [], + $body = null, + string $version = '1.1', + string $reason = null + ) { + $this->assertStatusCodeRange($status); + + $this->statusCode = $status; + + if ($body !== '' && $body !== null) { + $this->stream = Utils::streamFor($body); + } + + $this->setHeaders($headers); + if ($reason == '' && isset(self::PHRASES[$this->statusCode])) { + $this->reasonPhrase = self::PHRASES[$this->statusCode]; + } else { + $this->reasonPhrase = (string) $reason; + } + + $this->protocol = $version; + } + + public function getStatusCode(): int + { + return $this->statusCode; + } + + public function getReasonPhrase(): string + { + return $this->reasonPhrase; + } + + public function withStatus($code, $reasonPhrase = ''): ResponseInterface + { + $this->assertStatusCodeIsInteger($code); + $code = (int) $code; + $this->assertStatusCodeRange($code); + + $new = clone $this; + $new->statusCode = $code; + if ($reasonPhrase == '' && isset(self::PHRASES[$new->statusCode])) { + $reasonPhrase = self::PHRASES[$new->statusCode]; + } + $new->reasonPhrase = (string) $reasonPhrase; + return $new; + } + + /** + * @param mixed $statusCode + */ + private function assertStatusCodeIsInteger($statusCode): void + { + if (filter_var($statusCode, FILTER_VALIDATE_INT) === false) { + throw new \InvalidArgumentException('Status code must be an integer value.'); + } + } + + private function assertStatusCodeRange(int $statusCode): void + { + if ($statusCode < 100 || $statusCode >= 600) { + throw new \InvalidArgumentException('Status code must be an integer value between 1xx and 5xx.'); + } + } +} diff --git a/vendor/guzzlehttp/psr7/src/Rfc7230.php b/vendor/guzzlehttp/psr7/src/Rfc7230.php new file mode 100644 index 0000000..3022401 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Rfc7230.php @@ -0,0 +1,23 @@ +@,;:\\\"/[\]?={}\x01-\x20\x7F]++):[ \t]*+((?:[ \t]*+[\x21-\x7E\x80-\xFF]++)*+)[ \t]*+\r?\n)m"; + public const HEADER_FOLD_REGEX = "(\r?\n[ \t]++)"; +} diff --git a/vendor/guzzlehttp/psr7/src/ServerRequest.php b/vendor/guzzlehttp/psr7/src/ServerRequest.php new file mode 100644 index 0000000..6ae3f9b --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/ServerRequest.php @@ -0,0 +1,344 @@ + $headers Request headers + * @param string|resource|StreamInterface|null $body Request body + * @param string $version Protocol version + * @param array $serverParams Typically the $_SERVER superglobal + */ + public function __construct( + string $method, + $uri, + array $headers = [], + $body = null, + string $version = '1.1', + array $serverParams = [] + ) { + $this->serverParams = $serverParams; + + parent::__construct($method, $uri, $headers, $body, $version); + } + + /** + * Return an UploadedFile instance array. + * + * @param array $files A array which respect $_FILES structure + * + * @throws InvalidArgumentException for unrecognized values + */ + public static function normalizeFiles(array $files): array + { + $normalized = []; + + foreach ($files as $key => $value) { + if ($value instanceof UploadedFileInterface) { + $normalized[$key] = $value; + } elseif (is_array($value) && isset($value['tmp_name'])) { + $normalized[$key] = self::createUploadedFileFromSpec($value); + } elseif (is_array($value)) { + $normalized[$key] = self::normalizeFiles($value); + continue; + } else { + throw new InvalidArgumentException('Invalid value in files specification'); + } + } + + return $normalized; + } + + /** + * Create and return an UploadedFile instance from a $_FILES specification. + * + * If the specification represents an array of values, this method will + * delegate to normalizeNestedFileSpec() and return that return value. + * + * @param array $value $_FILES struct + * + * @return UploadedFileInterface|UploadedFileInterface[] + */ + private static function createUploadedFileFromSpec(array $value) + { + if (is_array($value['tmp_name'])) { + return self::normalizeNestedFileSpec($value); + } + + return new UploadedFile( + $value['tmp_name'], + (int) $value['size'], + (int) $value['error'], + $value['name'], + $value['type'] + ); + } + + /** + * Normalize an array of file specifications. + * + * Loops through all nested files and returns a normalized array of + * UploadedFileInterface instances. + * + * @return UploadedFileInterface[] + */ + private static function normalizeNestedFileSpec(array $files = []): array + { + $normalizedFiles = []; + + foreach (array_keys($files['tmp_name']) as $key) { + $spec = [ + 'tmp_name' => $files['tmp_name'][$key], + 'size' => $files['size'][$key], + 'error' => $files['error'][$key], + 'name' => $files['name'][$key], + 'type' => $files['type'][$key], + ]; + $normalizedFiles[$key] = self::createUploadedFileFromSpec($spec); + } + + return $normalizedFiles; + } + + /** + * Return a ServerRequest populated with superglobals: + * $_GET + * $_POST + * $_COOKIE + * $_FILES + * $_SERVER + */ + public static function fromGlobals(): ServerRequestInterface + { + $method = $_SERVER['REQUEST_METHOD'] ?? 'GET'; + $headers = getallheaders(); + $uri = self::getUriFromGlobals(); + $body = new CachingStream(new LazyOpenStream('php://input', 'r+')); + $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? str_replace('HTTP/', '', $_SERVER['SERVER_PROTOCOL']) : '1.1'; + + $serverRequest = new ServerRequest($method, $uri, $headers, $body, $protocol, $_SERVER); + + return $serverRequest + ->withCookieParams($_COOKIE) + ->withQueryParams($_GET) + ->withParsedBody($_POST) + ->withUploadedFiles(self::normalizeFiles($_FILES)); + } + + private static function extractHostAndPortFromAuthority(string $authority): array + { + $uri = 'http://' . $authority; + $parts = parse_url($uri); + if (false === $parts) { + return [null, null]; + } + + $host = $parts['host'] ?? null; + $port = $parts['port'] ?? null; + + return [$host, $port]; + } + + /** + * Get a Uri populated with values from $_SERVER. + */ + public static function getUriFromGlobals(): UriInterface + { + $uri = new Uri(''); + + $uri = $uri->withScheme(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http'); + + $hasPort = false; + if (isset($_SERVER['HTTP_HOST'])) { + [$host, $port] = self::extractHostAndPortFromAuthority($_SERVER['HTTP_HOST']); + if ($host !== null) { + $uri = $uri->withHost($host); + } + + if ($port !== null) { + $hasPort = true; + $uri = $uri->withPort($port); + } + } elseif (isset($_SERVER['SERVER_NAME'])) { + $uri = $uri->withHost($_SERVER['SERVER_NAME']); + } elseif (isset($_SERVER['SERVER_ADDR'])) { + $uri = $uri->withHost($_SERVER['SERVER_ADDR']); + } + + if (!$hasPort && isset($_SERVER['SERVER_PORT'])) { + $uri = $uri->withPort($_SERVER['SERVER_PORT']); + } + + $hasQuery = false; + if (isset($_SERVER['REQUEST_URI'])) { + $requestUriParts = explode('?', $_SERVER['REQUEST_URI'], 2); + $uri = $uri->withPath($requestUriParts[0]); + if (isset($requestUriParts[1])) { + $hasQuery = true; + $uri = $uri->withQuery($requestUriParts[1]); + } + } + + if (!$hasQuery && isset($_SERVER['QUERY_STRING'])) { + $uri = $uri->withQuery($_SERVER['QUERY_STRING']); + } + + return $uri; + } + + public function getServerParams(): array + { + return $this->serverParams; + } + + public function getUploadedFiles(): array + { + return $this->uploadedFiles; + } + + public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface + { + $new = clone $this; + $new->uploadedFiles = $uploadedFiles; + + return $new; + } + + public function getCookieParams(): array + { + return $this->cookieParams; + } + + public function withCookieParams(array $cookies): ServerRequestInterface + { + $new = clone $this; + $new->cookieParams = $cookies; + + return $new; + } + + public function getQueryParams(): array + { + return $this->queryParams; + } + + public function withQueryParams(array $query): ServerRequestInterface + { + $new = clone $this; + $new->queryParams = $query; + + return $new; + } + + /** + * {@inheritdoc} + * + * @return array|object|null + */ + public function getParsedBody() + { + return $this->parsedBody; + } + + public function withParsedBody($data): ServerRequestInterface + { + $new = clone $this; + $new->parsedBody = $data; + + return $new; + } + + public function getAttributes(): array + { + return $this->attributes; + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function getAttribute($attribute, $default = null) + { + if (false === array_key_exists($attribute, $this->attributes)) { + return $default; + } + + return $this->attributes[$attribute]; + } + + public function withAttribute($attribute, $value): ServerRequestInterface + { + $new = clone $this; + $new->attributes[$attribute] = $value; + + return $new; + } + + public function withoutAttribute($attribute): ServerRequestInterface + { + if (false === array_key_exists($attribute, $this->attributes)) { + return $this; + } + + $new = clone $this; + unset($new->attributes[$attribute]); + + return $new; + } +} diff --git a/vendor/guzzlehttp/psr7/src/Stream.php b/vendor/guzzlehttp/psr7/src/Stream.php new file mode 100644 index 0000000..d389427 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Stream.php @@ -0,0 +1,279 @@ +size = $options['size']; + } + + $this->customMetadata = $options['metadata'] ?? []; + $this->stream = $stream; + $meta = stream_get_meta_data($this->stream); + $this->seekable = $meta['seekable']; + $this->readable = (bool)preg_match(self::READABLE_MODES, $meta['mode']); + $this->writable = (bool)preg_match(self::WRITABLE_MODES, $meta['mode']); + $this->uri = $this->getMetadata('uri'); + } + + /** + * Closes the stream when the destructed + */ + public function __destruct() + { + $this->close(); + } + + public function __toString(): string + { + try { + if ($this->isSeekable()) { + $this->seek(0); + } + return $this->getContents(); + } catch (\Throwable $e) { + if (\PHP_VERSION_ID >= 70400) { + throw $e; + } + trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR); + return ''; + } + } + + public function getContents(): string + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + + $contents = stream_get_contents($this->stream); + + if ($contents === false) { + throw new \RuntimeException('Unable to read stream contents'); + } + + return $contents; + } + + public function close(): void + { + if (isset($this->stream)) { + if (is_resource($this->stream)) { + fclose($this->stream); + } + $this->detach(); + } + } + + public function detach() + { + if (!isset($this->stream)) { + return null; + } + + $result = $this->stream; + unset($this->stream); + $this->size = $this->uri = null; + $this->readable = $this->writable = $this->seekable = false; + + return $result; + } + + public function getSize(): ?int + { + if ($this->size !== null) { + return $this->size; + } + + if (!isset($this->stream)) { + return null; + } + + // Clear the stat cache if the stream has a URI + if ($this->uri) { + clearstatcache(true, $this->uri); + } + + $stats = fstat($this->stream); + if (is_array($stats) && isset($stats['size'])) { + $this->size = $stats['size']; + return $this->size; + } + + return null; + } + + public function isReadable(): bool + { + return $this->readable; + } + + public function isWritable(): bool + { + return $this->writable; + } + + public function isSeekable(): bool + { + return $this->seekable; + } + + public function eof(): bool + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + + return feof($this->stream); + } + + public function tell(): int + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + + $result = ftell($this->stream); + + if ($result === false) { + throw new \RuntimeException('Unable to determine stream position'); + } + + return $result; + } + + public function rewind(): void + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET): void + { + $whence = (int) $whence; + + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + if (!$this->seekable) { + throw new \RuntimeException('Stream is not seekable'); + } + if (fseek($this->stream, $offset, $whence) === -1) { + throw new \RuntimeException('Unable to seek to stream position ' + . $offset . ' with whence ' . var_export($whence, true)); + } + } + + public function read($length): string + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + if (!$this->readable) { + throw new \RuntimeException('Cannot read from non-readable stream'); + } + if ($length < 0) { + throw new \RuntimeException('Length parameter cannot be negative'); + } + + if (0 === $length) { + return ''; + } + + $string = fread($this->stream, $length); + if (false === $string) { + throw new \RuntimeException('Unable to read from stream'); + } + + return $string; + } + + public function write($string): int + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + if (!$this->writable) { + throw new \RuntimeException('Cannot write to a non-writable stream'); + } + + // We can't know the size after writing anything + $this->size = null; + $result = fwrite($this->stream, $string); + + if ($result === false) { + throw new \RuntimeException('Unable to write to stream'); + } + + return $result; + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function getMetadata($key = null) + { + if (!isset($this->stream)) { + return $key ? null : []; + } elseif (!$key) { + return $this->customMetadata + stream_get_meta_data($this->stream); + } elseif (isset($this->customMetadata[$key])) { + return $this->customMetadata[$key]; + } + + $meta = stream_get_meta_data($this->stream); + + return $meta[$key] ?? null; + } +} diff --git a/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php b/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php new file mode 100644 index 0000000..56d4104 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php @@ -0,0 +1,155 @@ +stream = $stream; + } + + /** + * Magic method used to create a new stream if streams are not added in + * the constructor of a decorator (e.g., LazyOpenStream). + * + * @return StreamInterface + */ + public function __get(string $name) + { + if ($name === 'stream') { + $this->stream = $this->createStream(); + return $this->stream; + } + + throw new \UnexpectedValueException("$name not found on class"); + } + + public function __toString(): string + { + try { + if ($this->isSeekable()) { + $this->seek(0); + } + return $this->getContents(); + } catch (\Throwable $e) { + if (\PHP_VERSION_ID >= 70400) { + throw $e; + } + trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR); + return ''; + } + } + + public function getContents(): string + { + return Utils::copyToString($this); + } + + /** + * Allow decorators to implement custom methods + * + * @return mixed + */ + public function __call(string $method, array $args) + { + /** @var callable $callable */ + $callable = [$this->stream, $method]; + $result = call_user_func_array($callable, $args); + + // Always return the wrapped object if the result is a return $this + return $result === $this->stream ? $this : $result; + } + + public function close(): void + { + $this->stream->close(); + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function getMetadata($key = null) + { + return $this->stream->getMetadata($key); + } + + public function detach() + { + return $this->stream->detach(); + } + + public function getSize(): ?int + { + return $this->stream->getSize(); + } + + public function eof(): bool + { + return $this->stream->eof(); + } + + public function tell(): int + { + return $this->stream->tell(); + } + + public function isReadable(): bool + { + return $this->stream->isReadable(); + } + + public function isWritable(): bool + { + return $this->stream->isWritable(); + } + + public function isSeekable(): bool + { + return $this->stream->isSeekable(); + } + + public function rewind(): void + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET): void + { + $this->stream->seek($offset, $whence); + } + + public function read($length): string + { + return $this->stream->read($length); + } + + public function write($string): int + { + return $this->stream->write($string); + } + + /** + * Implement in subclasses to dynamically create streams when requested. + * + * @throws \BadMethodCallException + */ + protected function createStream(): StreamInterface + { + throw new \BadMethodCallException('Not implemented'); + } +} diff --git a/vendor/guzzlehttp/psr7/src/StreamWrapper.php b/vendor/guzzlehttp/psr7/src/StreamWrapper.php new file mode 100644 index 0000000..2a93464 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/StreamWrapper.php @@ -0,0 +1,175 @@ +isReadable()) { + $mode = $stream->isWritable() ? 'r+' : 'r'; + } elseif ($stream->isWritable()) { + $mode = 'w'; + } else { + throw new \InvalidArgumentException('The stream must be readable, ' + . 'writable, or both.'); + } + + return fopen('guzzle://stream', $mode, false, self::createStreamContext($stream)); + } + + /** + * Creates a stream context that can be used to open a stream as a php stream resource. + * + * @return resource + */ + public static function createStreamContext(StreamInterface $stream) + { + return stream_context_create([ + 'guzzle' => ['stream' => $stream] + ]); + } + + /** + * Registers the stream wrapper if needed + */ + public static function register(): void + { + if (!in_array('guzzle', stream_get_wrappers())) { + stream_wrapper_register('guzzle', __CLASS__); + } + } + + public function stream_open(string $path, string $mode, int $options, string &$opened_path = null): bool + { + $options = stream_context_get_options($this->context); + + if (!isset($options['guzzle']['stream'])) { + return false; + } + + $this->mode = $mode; + $this->stream = $options['guzzle']['stream']; + + return true; + } + + public function stream_read(int $count): string + { + return $this->stream->read($count); + } + + public function stream_write(string $data): int + { + return $this->stream->write($data); + } + + public function stream_tell(): int + { + return $this->stream->tell(); + } + + public function stream_eof(): bool + { + return $this->stream->eof(); + } + + public function stream_seek(int $offset, int $whence): bool + { + $this->stream->seek($offset, $whence); + + return true; + } + + /** + * @return resource|false + */ + public function stream_cast(int $cast_as) + { + $stream = clone($this->stream); + $resource = $stream->detach(); + + return $resource ?? false; + } + + /** + * @return array + */ + public function stream_stat(): array + { + static $modeMap = [ + 'r' => 33060, + 'rb' => 33060, + 'r+' => 33206, + 'w' => 33188, + 'wb' => 33188 + ]; + + return [ + 'dev' => 0, + 'ino' => 0, + 'mode' => $modeMap[$this->mode], + 'nlink' => 0, + 'uid' => 0, + 'gid' => 0, + 'rdev' => 0, + 'size' => $this->stream->getSize() ?: 0, + 'atime' => 0, + 'mtime' => 0, + 'ctime' => 0, + 'blksize' => 0, + 'blocks' => 0 + ]; + } + + /** + * @return array + */ + public function url_stat(string $path, int $flags): array + { + return [ + 'dev' => 0, + 'ino' => 0, + 'mode' => 0, + 'nlink' => 0, + 'uid' => 0, + 'gid' => 0, + 'rdev' => 0, + 'size' => 0, + 'atime' => 0, + 'mtime' => 0, + 'ctime' => 0, + 'blksize' => 0, + 'blocks' => 0 + ]; + } +} diff --git a/vendor/guzzlehttp/psr7/src/UploadedFile.php b/vendor/guzzlehttp/psr7/src/UploadedFile.php new file mode 100644 index 0000000..b1521bc --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/UploadedFile.php @@ -0,0 +1,211 @@ +setError($errorStatus); + $this->size = $size; + $this->clientFilename = $clientFilename; + $this->clientMediaType = $clientMediaType; + + if ($this->isOk()) { + $this->setStreamOrFile($streamOrFile); + } + } + + /** + * Depending on the value set file or stream variable + * + * @param StreamInterface|string|resource $streamOrFile + * + * @throws InvalidArgumentException + */ + private function setStreamOrFile($streamOrFile): void + { + if (is_string($streamOrFile)) { + $this->file = $streamOrFile; + } elseif (is_resource($streamOrFile)) { + $this->stream = new Stream($streamOrFile); + } elseif ($streamOrFile instanceof StreamInterface) { + $this->stream = $streamOrFile; + } else { + throw new InvalidArgumentException( + 'Invalid stream or file provided for UploadedFile' + ); + } + } + + /** + * @throws InvalidArgumentException + */ + private function setError(int $error): void + { + if (false === in_array($error, UploadedFile::ERRORS, true)) { + throw new InvalidArgumentException( + 'Invalid error status for UploadedFile' + ); + } + + $this->error = $error; + } + + private function isStringNotEmpty($param): bool + { + return is_string($param) && false === empty($param); + } + + /** + * Return true if there is no upload error + */ + private function isOk(): bool + { + return $this->error === UPLOAD_ERR_OK; + } + + public function isMoved(): bool + { + return $this->moved; + } + + /** + * @throws RuntimeException if is moved or not ok + */ + private function validateActive(): void + { + if (false === $this->isOk()) { + throw new RuntimeException('Cannot retrieve stream due to upload error'); + } + + if ($this->isMoved()) { + throw new RuntimeException('Cannot retrieve stream after it has already been moved'); + } + } + + public function getStream(): StreamInterface + { + $this->validateActive(); + + if ($this->stream instanceof StreamInterface) { + return $this->stream; + } + + /** @var string $file */ + $file = $this->file; + + return new LazyOpenStream($file, 'r+'); + } + + public function moveTo($targetPath): void + { + $this->validateActive(); + + if (false === $this->isStringNotEmpty($targetPath)) { + throw new InvalidArgumentException( + 'Invalid path provided for move operation; must be a non-empty string' + ); + } + + if ($this->file) { + $this->moved = PHP_SAPI === 'cli' + ? rename($this->file, $targetPath) + : move_uploaded_file($this->file, $targetPath); + } else { + Utils::copyToStream( + $this->getStream(), + new LazyOpenStream($targetPath, 'w') + ); + + $this->moved = true; + } + + if (false === $this->moved) { + throw new RuntimeException( + sprintf('Uploaded file could not be moved to %s', $targetPath) + ); + } + } + + public function getSize(): ?int + { + return $this->size; + } + + public function getError(): int + { + return $this->error; + } + + public function getClientFilename(): ?string + { + return $this->clientFilename; + } + + public function getClientMediaType(): ?string + { + return $this->clientMediaType; + } +} diff --git a/vendor/guzzlehttp/psr7/src/Uri.php b/vendor/guzzlehttp/psr7/src/Uri.php new file mode 100644 index 0000000..3898f78 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Uri.php @@ -0,0 +1,733 @@ + 80, + 'https' => 443, + 'ftp' => 21, + 'gopher' => 70, + 'nntp' => 119, + 'news' => 119, + 'telnet' => 23, + 'tn3270' => 23, + 'imap' => 143, + 'pop' => 110, + 'ldap' => 389, + ]; + + /** + * Unreserved characters for use in a regex. + * + * @link https://tools.ietf.org/html/rfc3986#section-2.3 + */ + private const CHAR_UNRESERVED = 'a-zA-Z0-9_\-\.~'; + + /** + * Sub-delims for use in a regex. + * + * @link https://tools.ietf.org/html/rfc3986#section-2.2 + */ + private const CHAR_SUB_DELIMS = '!\$&\'\(\)\*\+,;='; + private const QUERY_SEPARATORS_REPLACEMENT = ['=' => '%3D', '&' => '%26']; + + /** @var string Uri scheme. */ + private $scheme = ''; + + /** @var string Uri user info. */ + private $userInfo = ''; + + /** @var string Uri host. */ + private $host = ''; + + /** @var int|null Uri port. */ + private $port; + + /** @var string Uri path. */ + private $path = ''; + + /** @var string Uri query string. */ + private $query = ''; + + /** @var string Uri fragment. */ + private $fragment = ''; + + /** @var string|null String representation */ + private $composedComponents; + + public function __construct(string $uri = '') + { + if ($uri !== '') { + $parts = self::parse($uri); + if ($parts === false) { + throw new MalformedUriException("Unable to parse URI: $uri"); + } + $this->applyParts($parts); + } + } + /** + * UTF-8 aware \parse_url() replacement. + * + * The internal function produces broken output for non ASCII domain names + * (IDN) when used with locales other than "C". + * + * On the other hand, cURL understands IDN correctly only when UTF-8 locale + * is configured ("C.UTF-8", "en_US.UTF-8", etc.). + * + * @see https://bugs.php.net/bug.php?id=52923 + * @see https://www.php.net/manual/en/function.parse-url.php#114817 + * @see https://curl.haxx.se/libcurl/c/CURLOPT_URL.html#ENCODING + * + * @return array|false + */ + private static function parse(string $url) + { + // If IPv6 + $prefix = ''; + if (preg_match('%^(.*://\[[0-9:a-f]+\])(.*?)$%', $url, $matches)) { + /** @var array{0:string, 1:string, 2:string} $matches */ + $prefix = $matches[1]; + $url = $matches[2]; + } + + /** @var string */ + $encodedUrl = preg_replace_callback( + '%[^:/@?&=#]+%usD', + static function ($matches) { + return urlencode($matches[0]); + }, + $url + ); + + $result = parse_url($prefix . $encodedUrl); + + if ($result === false) { + return false; + } + + return array_map('urldecode', $result); + } + + public function __toString(): string + { + if ($this->composedComponents === null) { + $this->composedComponents = self::composeComponents( + $this->scheme, + $this->getAuthority(), + $this->path, + $this->query, + $this->fragment + ); + } + + return $this->composedComponents; + } + + /** + * Composes a URI reference string from its various components. + * + * Usually this method does not need to be called manually but instead is used indirectly via + * `Psr\Http\Message\UriInterface::__toString`. + * + * PSR-7 UriInterface treats an empty component the same as a missing component as + * getQuery(), getFragment() etc. always return a string. This explains the slight + * difference to RFC 3986 Section 5.3. + * + * Another adjustment is that the authority separator is added even when the authority is missing/empty + * for the "file" scheme. This is because PHP stream functions like `file_get_contents` only work with + * `file:///myfile` but not with `file:/myfile` although they are equivalent according to RFC 3986. But + * `file:///` is the more common syntax for the file scheme anyway (Chrome for example redirects to + * that format). + * + * @link https://tools.ietf.org/html/rfc3986#section-5.3 + */ + public static function composeComponents(?string $scheme, ?string $authority, string $path, ?string $query, ?string $fragment): string + { + $uri = ''; + + // weak type checks to also accept null until we can add scalar type hints + if ($scheme != '') { + $uri .= $scheme . ':'; + } + + if ($authority != ''|| $scheme === 'file') { + $uri .= '//' . $authority; + } + + $uri .= $path; + + if ($query != '') { + $uri .= '?' . $query; + } + + if ($fragment != '') { + $uri .= '#' . $fragment; + } + + return $uri; + } + + /** + * Whether the URI has the default port of the current scheme. + * + * `Psr\Http\Message\UriInterface::getPort` may return null or the standard port. This method can be used + * independently of the implementation. + */ + public static function isDefaultPort(UriInterface $uri): bool + { + return $uri->getPort() === null + || (isset(self::DEFAULT_PORTS[$uri->getScheme()]) && $uri->getPort() === self::DEFAULT_PORTS[$uri->getScheme()]); + } + + /** + * Whether the URI is absolute, i.e. it has a scheme. + * + * An instance of UriInterface can either be an absolute URI or a relative reference. This method returns true + * if it is the former. An absolute URI has a scheme. A relative reference is used to express a URI relative + * to another URI, the base URI. Relative references can be divided into several forms: + * - network-path references, e.g. '//example.com/path' + * - absolute-path references, e.g. '/path' + * - relative-path references, e.g. 'subpath' + * + * @see Uri::isNetworkPathReference + * @see Uri::isAbsolutePathReference + * @see Uri::isRelativePathReference + * @link https://tools.ietf.org/html/rfc3986#section-4 + */ + public static function isAbsolute(UriInterface $uri): bool + { + return $uri->getScheme() !== ''; + } + + /** + * Whether the URI is a network-path reference. + * + * A relative reference that begins with two slash characters is termed an network-path reference. + * + * @link https://tools.ietf.org/html/rfc3986#section-4.2 + */ + public static function isNetworkPathReference(UriInterface $uri): bool + { + return $uri->getScheme() === '' && $uri->getAuthority() !== ''; + } + + /** + * Whether the URI is a absolute-path reference. + * + * A relative reference that begins with a single slash character is termed an absolute-path reference. + * + * @link https://tools.ietf.org/html/rfc3986#section-4.2 + */ + public static function isAbsolutePathReference(UriInterface $uri): bool + { + return $uri->getScheme() === '' + && $uri->getAuthority() === '' + && isset($uri->getPath()[0]) + && $uri->getPath()[0] === '/'; + } + + /** + * Whether the URI is a relative-path reference. + * + * A relative reference that does not begin with a slash character is termed a relative-path reference. + * + * @link https://tools.ietf.org/html/rfc3986#section-4.2 + */ + public static function isRelativePathReference(UriInterface $uri): bool + { + return $uri->getScheme() === '' + && $uri->getAuthority() === '' + && (!isset($uri->getPath()[0]) || $uri->getPath()[0] !== '/'); + } + + /** + * Whether the URI is a same-document reference. + * + * A same-document reference refers to a URI that is, aside from its fragment + * component, identical to the base URI. When no base URI is given, only an empty + * URI reference (apart from its fragment) is considered a same-document reference. + * + * @param UriInterface $uri The URI to check + * @param UriInterface|null $base An optional base URI to compare against + * + * @link https://tools.ietf.org/html/rfc3986#section-4.4 + */ + public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null): bool + { + if ($base !== null) { + $uri = UriResolver::resolve($base, $uri); + + return ($uri->getScheme() === $base->getScheme()) + && ($uri->getAuthority() === $base->getAuthority()) + && ($uri->getPath() === $base->getPath()) + && ($uri->getQuery() === $base->getQuery()); + } + + return $uri->getScheme() === '' && $uri->getAuthority() === '' && $uri->getPath() === '' && $uri->getQuery() === ''; + } + + /** + * Creates a new URI with a specific query string value removed. + * + * Any existing query string values that exactly match the provided key are + * removed. + * + * @param UriInterface $uri URI to use as a base. + * @param string $key Query string key to remove. + */ + public static function withoutQueryValue(UriInterface $uri, string $key): UriInterface + { + $result = self::getFilteredQueryString($uri, [$key]); + + return $uri->withQuery(implode('&', $result)); + } + + /** + * Creates a new URI with a specific query string value. + * + * Any existing query string values that exactly match the provided key are + * removed and replaced with the given key value pair. + * + * A value of null will set the query string key without a value, e.g. "key" + * instead of "key=value". + * + * @param UriInterface $uri URI to use as a base. + * @param string $key Key to set. + * @param string|null $value Value to set + */ + public static function withQueryValue(UriInterface $uri, string $key, ?string $value): UriInterface + { + $result = self::getFilteredQueryString($uri, [$key]); + + $result[] = self::generateQueryString($key, $value); + + return $uri->withQuery(implode('&', $result)); + } + + /** + * Creates a new URI with multiple specific query string values. + * + * It has the same behavior as withQueryValue() but for an associative array of key => value. + * + * @param UriInterface $uri URI to use as a base. + * @param array $keyValueArray Associative array of key and values + */ + public static function withQueryValues(UriInterface $uri, array $keyValueArray): UriInterface + { + $result = self::getFilteredQueryString($uri, array_keys($keyValueArray)); + + foreach ($keyValueArray as $key => $value) { + $result[] = self::generateQueryString((string) $key, $value !== null ? (string) $value : null); + } + + return $uri->withQuery(implode('&', $result)); + } + + /** + * Creates a URI from a hash of `parse_url` components. + * + * @link http://php.net/manual/en/function.parse-url.php + * + * @throws MalformedUriException If the components do not form a valid URI. + */ + public static function fromParts(array $parts): UriInterface + { + $uri = new self(); + $uri->applyParts($parts); + $uri->validateState(); + + return $uri; + } + + public function getScheme(): string + { + return $this->scheme; + } + + public function getAuthority(): string + { + $authority = $this->host; + if ($this->userInfo !== '') { + $authority = $this->userInfo . '@' . $authority; + } + + if ($this->port !== null) { + $authority .= ':' . $this->port; + } + + return $authority; + } + + public function getUserInfo(): string + { + return $this->userInfo; + } + + public function getHost(): string + { + return $this->host; + } + + public function getPort(): ?int + { + return $this->port; + } + + public function getPath(): string + { + return $this->path; + } + + public function getQuery(): string + { + return $this->query; + } + + public function getFragment(): string + { + return $this->fragment; + } + + public function withScheme($scheme): UriInterface + { + $scheme = $this->filterScheme($scheme); + + if ($this->scheme === $scheme) { + return $this; + } + + $new = clone $this; + $new->scheme = $scheme; + $new->composedComponents = null; + $new->removeDefaultPort(); + $new->validateState(); + + return $new; + } + + public function withUserInfo($user, $password = null): UriInterface + { + $info = $this->filterUserInfoComponent($user); + if ($password !== null) { + $info .= ':' . $this->filterUserInfoComponent($password); + } + + if ($this->userInfo === $info) { + return $this; + } + + $new = clone $this; + $new->userInfo = $info; + $new->composedComponents = null; + $new->validateState(); + + return $new; + } + + public function withHost($host): UriInterface + { + $host = $this->filterHost($host); + + if ($this->host === $host) { + return $this; + } + + $new = clone $this; + $new->host = $host; + $new->composedComponents = null; + $new->validateState(); + + return $new; + } + + public function withPort($port): UriInterface + { + $port = $this->filterPort($port); + + if ($this->port === $port) { + return $this; + } + + $new = clone $this; + $new->port = $port; + $new->composedComponents = null; + $new->removeDefaultPort(); + $new->validateState(); + + return $new; + } + + public function withPath($path): UriInterface + { + $path = $this->filterPath($path); + + if ($this->path === $path) { + return $this; + } + + $new = clone $this; + $new->path = $path; + $new->composedComponents = null; + $new->validateState(); + + return $new; + } + + public function withQuery($query): UriInterface + { + $query = $this->filterQueryAndFragment($query); + + if ($this->query === $query) { + return $this; + } + + $new = clone $this; + $new->query = $query; + $new->composedComponents = null; + + return $new; + } + + public function withFragment($fragment): UriInterface + { + $fragment = $this->filterQueryAndFragment($fragment); + + if ($this->fragment === $fragment) { + return $this; + } + + $new = clone $this; + $new->fragment = $fragment; + $new->composedComponents = null; + + return $new; + } + + /** + * Apply parse_url parts to a URI. + * + * @param array $parts Array of parse_url parts to apply. + */ + private function applyParts(array $parts): void + { + $this->scheme = isset($parts['scheme']) + ? $this->filterScheme($parts['scheme']) + : ''; + $this->userInfo = isset($parts['user']) + ? $this->filterUserInfoComponent($parts['user']) + : ''; + $this->host = isset($parts['host']) + ? $this->filterHost($parts['host']) + : ''; + $this->port = isset($parts['port']) + ? $this->filterPort($parts['port']) + : null; + $this->path = isset($parts['path']) + ? $this->filterPath($parts['path']) + : ''; + $this->query = isset($parts['query']) + ? $this->filterQueryAndFragment($parts['query']) + : ''; + $this->fragment = isset($parts['fragment']) + ? $this->filterQueryAndFragment($parts['fragment']) + : ''; + if (isset($parts['pass'])) { + $this->userInfo .= ':' . $this->filterUserInfoComponent($parts['pass']); + } + + $this->removeDefaultPort(); + } + + /** + * @param mixed $scheme + * + * @throws \InvalidArgumentException If the scheme is invalid. + */ + private function filterScheme($scheme): string + { + if (!is_string($scheme)) { + throw new \InvalidArgumentException('Scheme must be a string'); + } + + return \strtr($scheme, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + } + + /** + * @param mixed $component + * + * @throws \InvalidArgumentException If the user info is invalid. + */ + private function filterUserInfoComponent($component): string + { + if (!is_string($component)) { + throw new \InvalidArgumentException('User info must be a string'); + } + + return preg_replace_callback( + '/(?:[^%' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . ']+|%(?![A-Fa-f0-9]{2}))/', + [$this, 'rawurlencodeMatchZero'], + $component + ); + } + + /** + * @param mixed $host + * + * @throws \InvalidArgumentException If the host is invalid. + */ + private function filterHost($host): string + { + if (!is_string($host)) { + throw new \InvalidArgumentException('Host must be a string'); + } + + return \strtr($host, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + } + + /** + * @param mixed $port + * + * @throws \InvalidArgumentException If the port is invalid. + */ + private function filterPort($port): ?int + { + if ($port === null) { + return null; + } + + $port = (int) $port; + if (0 > $port || 0xffff < $port) { + throw new \InvalidArgumentException( + sprintf('Invalid port: %d. Must be between 0 and 65535', $port) + ); + } + + return $port; + } + + /** + * @param string[] $keys + * + * @return string[] + */ + private static function getFilteredQueryString(UriInterface $uri, array $keys): array + { + $current = $uri->getQuery(); + + if ($current === '') { + return []; + } + + $decodedKeys = array_map('rawurldecode', $keys); + + return array_filter(explode('&', $current), function ($part) use ($decodedKeys) { + return !in_array(rawurldecode(explode('=', $part)[0]), $decodedKeys, true); + }); + } + + private static function generateQueryString(string $key, ?string $value): string + { + // Query string separators ("=", "&") within the key or value need to be encoded + // (while preventing double-encoding) before setting the query string. All other + // chars that need percent-encoding will be encoded by withQuery(). + $queryString = strtr($key, self::QUERY_SEPARATORS_REPLACEMENT); + + if ($value !== null) { + $queryString .= '=' . strtr($value, self::QUERY_SEPARATORS_REPLACEMENT); + } + + return $queryString; + } + + private function removeDefaultPort(): void + { + if ($this->port !== null && self::isDefaultPort($this)) { + $this->port = null; + } + } + + /** + * Filters the path of a URI + * + * @param mixed $path + * + * @throws \InvalidArgumentException If the path is invalid. + */ + private function filterPath($path): string + { + if (!is_string($path)) { + throw new \InvalidArgumentException('Path must be a string'); + } + + return preg_replace_callback( + '/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\/]++|%(?![A-Fa-f0-9]{2}))/', + [$this, 'rawurlencodeMatchZero'], + $path + ); + } + + /** + * Filters the query string or fragment of a URI. + * + * @param mixed $str + * + * @throws \InvalidArgumentException If the query or fragment is invalid. + */ + private function filterQueryAndFragment($str): string + { + if (!is_string($str)) { + throw new \InvalidArgumentException('Query and fragment must be a string'); + } + + return preg_replace_callback( + '/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/', + [$this, 'rawurlencodeMatchZero'], + $str + ); + } + + private function rawurlencodeMatchZero(array $match): string + { + return rawurlencode($match[0]); + } + + private function validateState(): void + { + if ($this->host === '' && ($this->scheme === 'http' || $this->scheme === 'https')) { + $this->host = self::HTTP_DEFAULT_HOST; + } + + if ($this->getAuthority() === '') { + if (0 === strpos($this->path, '//')) { + throw new MalformedUriException('The path of a URI without an authority must not start with two slashes "//"'); + } + if ($this->scheme === '' && false !== strpos(explode('/', $this->path, 2)[0], ':')) { + throw new MalformedUriException('A relative URI must not have a path beginning with a segment containing a colon'); + } + } elseif (isset($this->path[0]) && $this->path[0] !== '/') { + throw new MalformedUriException('The path of a URI with an authority must start with a slash "/" or be empty'); + } + } +} diff --git a/vendor/guzzlehttp/psr7/src/UriNormalizer.php b/vendor/guzzlehttp/psr7/src/UriNormalizer.php new file mode 100644 index 0000000..e12971e --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/UriNormalizer.php @@ -0,0 +1,220 @@ +getPath() === '' && + ($uri->getScheme() === 'http' || $uri->getScheme() === 'https') + ) { + $uri = $uri->withPath('/'); + } + + if ($flags & self::REMOVE_DEFAULT_HOST && $uri->getScheme() === 'file' && $uri->getHost() === 'localhost') { + $uri = $uri->withHost(''); + } + + if ($flags & self::REMOVE_DEFAULT_PORT && $uri->getPort() !== null && Uri::isDefaultPort($uri)) { + $uri = $uri->withPort(null); + } + + if ($flags & self::REMOVE_DOT_SEGMENTS && !Uri::isRelativePathReference($uri)) { + $uri = $uri->withPath(UriResolver::removeDotSegments($uri->getPath())); + } + + if ($flags & self::REMOVE_DUPLICATE_SLASHES) { + $uri = $uri->withPath(preg_replace('#//++#', '/', $uri->getPath())); + } + + if ($flags & self::SORT_QUERY_PARAMETERS && $uri->getQuery() !== '') { + $queryKeyValues = explode('&', $uri->getQuery()); + sort($queryKeyValues); + $uri = $uri->withQuery(implode('&', $queryKeyValues)); + } + + return $uri; + } + + /** + * Whether two URIs can be considered equivalent. + * + * Both URIs are normalized automatically before comparison with the given $normalizations bitmask. The method also + * accepts relative URI references and returns true when they are equivalent. This of course assumes they will be + * resolved against the same base URI. If this is not the case, determination of equivalence or difference of + * relative references does not mean anything. + * + * @param UriInterface $uri1 An URI to compare + * @param UriInterface $uri2 An URI to compare + * @param int $normalizations A bitmask of normalizations to apply, see constants + * + * @link https://tools.ietf.org/html/rfc3986#section-6.1 + */ + public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, int $normalizations = self::PRESERVING_NORMALIZATIONS): bool + { + return (string) self::normalize($uri1, $normalizations) === (string) self::normalize($uri2, $normalizations); + } + + private static function capitalizePercentEncoding(UriInterface $uri): UriInterface + { + $regex = '/(?:%[A-Fa-f0-9]{2})++/'; + + $callback = function (array $match) { + return strtoupper($match[0]); + }; + + return + $uri->withPath( + preg_replace_callback($regex, $callback, $uri->getPath()) + )->withQuery( + preg_replace_callback($regex, $callback, $uri->getQuery()) + ); + } + + private static function decodeUnreservedCharacters(UriInterface $uri): UriInterface + { + $regex = '/%(?:2D|2E|5F|7E|3[0-9]|[46][1-9A-F]|[57][0-9A])/i'; + + $callback = function (array $match) { + return rawurldecode($match[0]); + }; + + return + $uri->withPath( + preg_replace_callback($regex, $callback, $uri->getPath()) + )->withQuery( + preg_replace_callback($regex, $callback, $uri->getQuery()) + ); + } + + private function __construct() + { + // cannot be instantiated + } +} diff --git a/vendor/guzzlehttp/psr7/src/UriResolver.php b/vendor/guzzlehttp/psr7/src/UriResolver.php new file mode 100644 index 0000000..426e5c9 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/UriResolver.php @@ -0,0 +1,211 @@ +getScheme() != '') { + return $rel->withPath(self::removeDotSegments($rel->getPath())); + } + + if ($rel->getAuthority() != '') { + $targetAuthority = $rel->getAuthority(); + $targetPath = self::removeDotSegments($rel->getPath()); + $targetQuery = $rel->getQuery(); + } else { + $targetAuthority = $base->getAuthority(); + if ($rel->getPath() === '') { + $targetPath = $base->getPath(); + $targetQuery = $rel->getQuery() != '' ? $rel->getQuery() : $base->getQuery(); + } else { + if ($rel->getPath()[0] === '/') { + $targetPath = $rel->getPath(); + } else { + if ($targetAuthority != '' && $base->getPath() === '') { + $targetPath = '/' . $rel->getPath(); + } else { + $lastSlashPos = strrpos($base->getPath(), '/'); + if ($lastSlashPos === false) { + $targetPath = $rel->getPath(); + } else { + $targetPath = substr($base->getPath(), 0, $lastSlashPos + 1) . $rel->getPath(); + } + } + } + $targetPath = self::removeDotSegments($targetPath); + $targetQuery = $rel->getQuery(); + } + } + + return new Uri(Uri::composeComponents( + $base->getScheme(), + $targetAuthority, + $targetPath, + $targetQuery, + $rel->getFragment() + )); + } + + /** + * Returns the target URI as a relative reference from the base URI. + * + * This method is the counterpart to resolve(): + * + * (string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target)) + * + * One use-case is to use the current request URI as base URI and then generate relative links in your documents + * to reduce the document size or offer self-contained downloadable document archives. + * + * $base = new Uri('http://example.com/a/b/'); + * echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'. + * echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'. + * echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'. + * echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'. + * + * This method also accepts a target that is already relative and will try to relativize it further. Only a + * relative-path reference will be returned as-is. + * + * echo UriResolver::relativize($base, new Uri('/a/b/c')); // prints 'c' as well + */ + public static function relativize(UriInterface $base, UriInterface $target): UriInterface + { + if ($target->getScheme() !== '' && + ($base->getScheme() !== $target->getScheme() || $target->getAuthority() === '' && $base->getAuthority() !== '') + ) { + return $target; + } + + if (Uri::isRelativePathReference($target)) { + // As the target is already highly relative we return it as-is. It would be possible to resolve + // the target with `$target = self::resolve($base, $target);` and then try make it more relative + // by removing a duplicate query. But let's not do that automatically. + return $target; + } + + if ($target->getAuthority() !== '' && $base->getAuthority() !== $target->getAuthority()) { + return $target->withScheme(''); + } + + // We must remove the path before removing the authority because if the path starts with two slashes, the URI + // would turn invalid. And we also cannot set a relative path before removing the authority, as that is also + // invalid. + $emptyPathUri = $target->withScheme('')->withPath('')->withUserInfo('')->withPort(null)->withHost(''); + + if ($base->getPath() !== $target->getPath()) { + return $emptyPathUri->withPath(self::getRelativePath($base, $target)); + } + + if ($base->getQuery() === $target->getQuery()) { + // Only the target fragment is left. And it must be returned even if base and target fragment are the same. + return $emptyPathUri->withQuery(''); + } + + // If the base URI has a query but the target has none, we cannot return an empty path reference as it would + // inherit the base query component when resolving. + if ($target->getQuery() === '') { + $segments = explode('/', $target->getPath()); + /** @var string $lastSegment */ + $lastSegment = end($segments); + + return $emptyPathUri->withPath($lastSegment === '' ? './' : $lastSegment); + } + + return $emptyPathUri; + } + + private static function getRelativePath(UriInterface $base, UriInterface $target): string + { + $sourceSegments = explode('/', $base->getPath()); + $targetSegments = explode('/', $target->getPath()); + array_pop($sourceSegments); + $targetLastSegment = array_pop($targetSegments); + foreach ($sourceSegments as $i => $segment) { + if (isset($targetSegments[$i]) && $segment === $targetSegments[$i]) { + unset($sourceSegments[$i], $targetSegments[$i]); + } else { + break; + } + } + $targetSegments[] = $targetLastSegment; + $relativePath = str_repeat('../', count($sourceSegments)) . implode('/', $targetSegments); + + // A reference to am empty last segment or an empty first sub-segment must be prefixed with "./". + // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used + // as the first segment of a relative-path reference, as it would be mistaken for a scheme name. + if ('' === $relativePath || false !== strpos(explode('/', $relativePath, 2)[0], ':')) { + $relativePath = "./$relativePath"; + } elseif ('/' === $relativePath[0]) { + if ($base->getAuthority() != '' && $base->getPath() === '') { + // In this case an extra slash is added by resolve() automatically. So we must not add one here. + $relativePath = ".$relativePath"; + } else { + $relativePath = "./$relativePath"; + } + } + + return $relativePath; + } + + private function __construct() + { + // cannot be instantiated + } +} diff --git a/vendor/guzzlehttp/psr7/src/Utils.php b/vendor/guzzlehttp/psr7/src/Utils.php new file mode 100644 index 0000000..6cc04ee --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Utils.php @@ -0,0 +1,412 @@ + $v) { + if (!is_string($k) || !in_array(strtolower($k), $keys)) { + $result[$k] = $v; + } + } + + return $result; + } + + /** + * Copy the contents of a stream into another stream until the given number + * of bytes have been read. + * + * @param StreamInterface $source Stream to read from + * @param StreamInterface $dest Stream to write to + * @param int $maxLen Maximum number of bytes to read. Pass -1 + * to read the entire stream. + * + * @throws \RuntimeException on error. + */ + public static function copyToStream(StreamInterface $source, StreamInterface $dest, int $maxLen = -1): void + { + $bufferSize = 8192; + + if ($maxLen === -1) { + while (!$source->eof()) { + if (!$dest->write($source->read($bufferSize))) { + break; + } + } + } else { + $remaining = $maxLen; + while ($remaining > 0 && !$source->eof()) { + $buf = $source->read(min($bufferSize, $remaining)); + $len = strlen($buf); + if (!$len) { + break; + } + $remaining -= $len; + $dest->write($buf); + } + } + } + + /** + * Copy the contents of a stream into a string until the given number of + * bytes have been read. + * + * @param StreamInterface $stream Stream to read + * @param int $maxLen Maximum number of bytes to read. Pass -1 + * to read the entire stream. + * + * @throws \RuntimeException on error. + */ + public static function copyToString(StreamInterface $stream, int $maxLen = -1): string + { + $buffer = ''; + + if ($maxLen === -1) { + while (!$stream->eof()) { + $buf = $stream->read(1048576); + if ($buf === '') { + break; + } + $buffer .= $buf; + } + return $buffer; + } + + $len = 0; + while (!$stream->eof() && $len < $maxLen) { + $buf = $stream->read($maxLen - $len); + if ($buf === '') { + break; + } + $buffer .= $buf; + $len = strlen($buffer); + } + + return $buffer; + } + + /** + * Calculate a hash of a stream. + * + * This method reads the entire stream to calculate a rolling hash, based + * on PHP's `hash_init` functions. + * + * @param StreamInterface $stream Stream to calculate the hash for + * @param string $algo Hash algorithm (e.g. md5, crc32, etc) + * @param bool $rawOutput Whether or not to use raw output + * + * @throws \RuntimeException on error. + */ + public static function hash(StreamInterface $stream, string $algo, bool $rawOutput = false): string + { + $pos = $stream->tell(); + + if ($pos > 0) { + $stream->rewind(); + } + + $ctx = hash_init($algo); + while (!$stream->eof()) { + hash_update($ctx, $stream->read(1048576)); + } + + $out = hash_final($ctx, (bool) $rawOutput); + $stream->seek($pos); + + return $out; + } + + /** + * Clone and modify a request with the given changes. + * + * This method is useful for reducing the number of clones needed to mutate + * a message. + * + * The changes can be one of: + * - method: (string) Changes the HTTP method. + * - set_headers: (array) Sets the given headers. + * - remove_headers: (array) Remove the given headers. + * - body: (mixed) Sets the given body. + * - uri: (UriInterface) Set the URI. + * - query: (string) Set the query string value of the URI. + * - version: (string) Set the protocol version. + * + * @param RequestInterface $request Request to clone and modify. + * @param array $changes Changes to apply. + */ + public static function modifyRequest(RequestInterface $request, array $changes): RequestInterface + { + if (!$changes) { + return $request; + } + + $headers = $request->getHeaders(); + + if (!isset($changes['uri'])) { + $uri = $request->getUri(); + } else { + // Remove the host header if one is on the URI + if ($host = $changes['uri']->getHost()) { + $changes['set_headers']['Host'] = $host; + + if ($port = $changes['uri']->getPort()) { + $standardPorts = ['http' => 80, 'https' => 443]; + $scheme = $changes['uri']->getScheme(); + if (isset($standardPorts[$scheme]) && $port != $standardPorts[$scheme]) { + $changes['set_headers']['Host'] .= ':' . $port; + } + } + } + $uri = $changes['uri']; + } + + if (!empty($changes['remove_headers'])) { + $headers = self::caselessRemove($changes['remove_headers'], $headers); + } + + if (!empty($changes['set_headers'])) { + $headers = self::caselessRemove(array_keys($changes['set_headers']), $headers); + $headers = $changes['set_headers'] + $headers; + } + + if (isset($changes['query'])) { + $uri = $uri->withQuery($changes['query']); + } + + if ($request instanceof ServerRequestInterface) { + $new = (new ServerRequest( + $changes['method'] ?? $request->getMethod(), + $uri, + $headers, + $changes['body'] ?? $request->getBody(), + $changes['version'] ?? $request->getProtocolVersion(), + $request->getServerParams() + )) + ->withParsedBody($request->getParsedBody()) + ->withQueryParams($request->getQueryParams()) + ->withCookieParams($request->getCookieParams()) + ->withUploadedFiles($request->getUploadedFiles()); + + foreach ($request->getAttributes() as $key => $value) { + $new = $new->withAttribute($key, $value); + } + + return $new; + } + + return new Request( + $changes['method'] ?? $request->getMethod(), + $uri, + $headers, + $changes['body'] ?? $request->getBody(), + $changes['version'] ?? $request->getProtocolVersion() + ); + } + + /** + * Read a line from the stream up to the maximum allowed buffer length. + * + * @param StreamInterface $stream Stream to read from + * @param int|null $maxLength Maximum buffer length + */ + public static function readLine(StreamInterface $stream, ?int $maxLength = null): string + { + $buffer = ''; + $size = 0; + + while (!$stream->eof()) { + if ('' === ($byte = $stream->read(1))) { + return $buffer; + } + $buffer .= $byte; + // Break when a new line is found or the max length - 1 is reached + if ($byte === "\n" || ++$size === $maxLength - 1) { + break; + } + } + + return $buffer; + } + + /** + * Create a new stream based on the input type. + * + * Options is an associative array that can contain the following keys: + * - metadata: Array of custom metadata. + * - size: Size of the stream. + * + * This method accepts the following `$resource` types: + * - `Psr\Http\Message\StreamInterface`: Returns the value as-is. + * - `string`: Creates a stream object that uses the given string as the contents. + * - `resource`: Creates a stream object that wraps the given PHP stream resource. + * - `Iterator`: If the provided value implements `Iterator`, then a read-only + * stream object will be created that wraps the given iterable. Each time the + * stream is read from, data from the iterator will fill a buffer and will be + * continuously called until the buffer is equal to the requested read size. + * Subsequent read calls will first read from the buffer and then call `next` + * on the underlying iterator until it is exhausted. + * - `object` with `__toString()`: If the object has the `__toString()` method, + * the object will be cast to a string and then a stream will be returned that + * uses the string value. + * - `NULL`: When `null` is passed, an empty stream object is returned. + * - `callable` When a callable is passed, a read-only stream object will be + * created that invokes the given callable. The callable is invoked with the + * number of suggested bytes to read. The callable can return any number of + * bytes, but MUST return `false` when there is no more data to return. The + * stream object that wraps the callable will invoke the callable until the + * number of requested bytes are available. Any additional bytes will be + * buffered and used in subsequent reads. + * + * @param resource|string|int|float|bool|StreamInterface|callable|\Iterator|null $resource Entity body data + * @param array{size?: int, metadata?: array} $options Additional options + * + * @throws \InvalidArgumentException if the $resource arg is not valid. + */ + public static function streamFor($resource = '', array $options = []): StreamInterface + { + if (is_scalar($resource)) { + $stream = self::tryFopen('php://temp', 'r+'); + if ($resource !== '') { + fwrite($stream, (string) $resource); + fseek($stream, 0); + } + return new Stream($stream, $options); + } + + switch (gettype($resource)) { + case 'resource': + /* + * The 'php://input' is a special stream with quirks and inconsistencies. + * We avoid using that stream by reading it into php://temp + */ + + /** @var resource $resource */ + if ((\stream_get_meta_data($resource)['uri'] ?? '') === 'php://input') { + $stream = self::tryFopen('php://temp', 'w+'); + fwrite($stream, stream_get_contents($resource)); + fseek($stream, 0); + $resource = $stream; + } + return new Stream($resource, $options); + case 'object': + /** @var object $resource */ + if ($resource instanceof StreamInterface) { + return $resource; + } elseif ($resource instanceof \Iterator) { + return new PumpStream(function () use ($resource) { + if (!$resource->valid()) { + return false; + } + $result = $resource->current(); + $resource->next(); + return $result; + }, $options); + } elseif (method_exists($resource, '__toString')) { + return self::streamFor((string) $resource, $options); + } + break; + case 'NULL': + return new Stream(self::tryFopen('php://temp', 'r+'), $options); + } + + if (is_callable($resource)) { + return new PumpStream($resource, $options); + } + + throw new \InvalidArgumentException('Invalid resource type: ' . gettype($resource)); + } + + /** + * Safely opens a PHP stream resource using a filename. + * + * When fopen fails, PHP normally raises a warning. This function adds an + * error handler that checks for errors and throws an exception instead. + * + * @param string $filename File to open + * @param string $mode Mode used to open the file + * + * @return resource + * + * @throws \RuntimeException if the file cannot be opened + */ + public static function tryFopen(string $filename, string $mode) + { + $ex = null; + set_error_handler(static function (int $errno, string $errstr) use ($filename, $mode, &$ex): bool { + $ex = new \RuntimeException(sprintf( + 'Unable to open "%s" using mode "%s": %s', + $filename, + $mode, + $errstr + )); + + return true; + }); + + try { + /** @var resource $handle */ + $handle = fopen($filename, $mode); + } catch (\Throwable $e) { + $ex = new \RuntimeException(sprintf( + 'Unable to open "%s" using mode "%s": %s', + $filename, + $mode, + $e->getMessage() + ), 0, $e); + } + + restore_error_handler(); + + if ($ex) { + /** @var $ex \RuntimeException */ + throw $ex; + } + + return $handle; + } + + /** + * Returns a UriInterface for the given value. + * + * This function accepts a string or UriInterface and returns a + * UriInterface for the given value. If the value is already a + * UriInterface, it is returned as-is. + * + * @param string|UriInterface $uri + * + * @throws \InvalidArgumentException + */ + public static function uriFor($uri): UriInterface + { + if ($uri instanceof UriInterface) { + return $uri; + } + + if (is_string($uri)) { + return new Uri($uri); + } + + throw new \InvalidArgumentException('URI must be a string or UriInterface'); + } +} diff --git a/vendor/guzzlehttp/psr7/vendor-bin/php-cs-fixer/composer.json b/vendor/guzzlehttp/psr7/vendor-bin/php-cs-fixer/composer.json new file mode 100644 index 0000000..d69a683 --- /dev/null +++ b/vendor/guzzlehttp/psr7/vendor-bin/php-cs-fixer/composer.json @@ -0,0 +1,9 @@ +{ + "require": { + "php": "^7.2.5 || ^8.0", + "friendsofphp/php-cs-fixer": "3.2.1" + }, + "config": { + "preferred-install": "dist" + } +} diff --git a/vendor/guzzlehttp/psr7/vendor-bin/phpstan/composer.json b/vendor/guzzlehttp/psr7/vendor-bin/phpstan/composer.json new file mode 100644 index 0000000..bfbc727 --- /dev/null +++ b/vendor/guzzlehttp/psr7/vendor-bin/phpstan/composer.json @@ -0,0 +1,10 @@ +{ + "require": { + "php": "^7.2.5 || ^8.0", + "phpstan/phpstan": "0.12.81", + "phpstan/phpstan-deprecation-rules": "0.12.6" + }, + "config": { + "preferred-install": "dist" + } +} diff --git a/vendor/guzzlehttp/psr7/vendor-bin/psalm/composer.json b/vendor/guzzlehttp/psr7/vendor-bin/psalm/composer.json new file mode 100644 index 0000000..535a079 --- /dev/null +++ b/vendor/guzzlehttp/psr7/vendor-bin/psalm/composer.json @@ -0,0 +1,9 @@ +{ + "require": { + "php": "^7.2.5 || ^8.0", + "psalm/phar": "4.6.2" + }, + "config": { + "preferred-install": "dist" + } +} diff --git a/vendor/intervention/image/.github/FUNDING.yml b/vendor/intervention/image/.github/FUNDING.yml new file mode 100644 index 0000000..612406c --- /dev/null +++ b/vendor/intervention/image/.github/FUNDING.yml @@ -0,0 +1,2 @@ +github: [Intervention] +custom: https://www.paypal.me/interventionphp diff --git a/vendor/intervention/image/LICENSE b/vendor/intervention/image/LICENSE new file mode 100644 index 0000000..bc444ba --- /dev/null +++ b/vendor/intervention/image/LICENSE @@ -0,0 +1,9 @@ +The MIT License (MIT) + +Copyright (c) 2014 Oliver Vogel + +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. \ No newline at end of file diff --git a/vendor/intervention/image/composer.json b/vendor/intervention/image/composer.json new file mode 100644 index 0000000..e750b40 --- /dev/null +++ b/vendor/intervention/image/composer.json @@ -0,0 +1,47 @@ +{ + "name": "intervention/image", + "description": "Image handling and manipulation library with support for Laravel integration", + "homepage": "http://image.intervention.io/", + "keywords": ["image", "gd", "imagick", "laravel", "watermark", "thumbnail"], + "license": "MIT", + "authors": [ + { + "name": "Oliver Vogel", + "email": "oliver@olivervogel.com", + "homepage": "http://olivervogel.com/" + } + ], + "require": { + "php": ">=5.4.0", + "ext-fileinfo": "*", + "guzzlehttp/psr7": "~1.1 || ^2.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8 || ^5.7 || ^7.5.15", + "mockery/mockery": "~0.9.2" + }, + "suggest": { + "ext-gd": "to use GD library based image processing.", + "ext-imagick": "to use Imagick based image processing.", + "intervention/imagecache": "Caching extension for the Intervention Image library" + }, + "autoload": { + "psr-4": { + "Intervention\\Image\\": "src/Intervention/Image" + } + }, + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + }, + "laravel": { + "providers": [ + "Intervention\\Image\\ImageServiceProvider" + ], + "aliases": { + "Image": "Intervention\\Image\\Facades\\Image" + } + } + }, + "minimum-stability": "stable" +} diff --git a/vendor/intervention/image/provides.json b/vendor/intervention/image/provides.json new file mode 100644 index 0000000..a8cd1b6 --- /dev/null +++ b/vendor/intervention/image/provides.json @@ -0,0 +1,11 @@ +{ + "providers": [ + "Intervention\\Image\\ImageServiceProvider" + ], + "aliases": [ + { + "alias": "Image", + "facade": "Intervention\\Image\\Facades\\Image" + } + ] +} diff --git a/vendor/intervention/image/src/Intervention/Image/AbstractColor.php b/vendor/intervention/image/src/Intervention/Image/AbstractColor.php new file mode 100644 index 0000000..c9846c6 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/AbstractColor.php @@ -0,0 +1,229 @@ +parse($value); + } + + /** + * Parses given value as color + * + * @param mixed $value + * @return \Intervention\Image\AbstractColor + */ + public function parse($value) + { + switch (true) { + + case is_string($value): + $this->initFromString($value); + break; + + case is_int($value): + $this->initFromInteger($value); + break; + + case is_array($value): + $this->initFromArray($value); + break; + + case is_object($value): + $this->initFromObject($value); + break; + + case is_null($value): + $this->initFromArray([255, 255, 255, 0]); + break; + + default: + throw new NotReadableException( + "Color format ({$value}) cannot be read." + ); + } + + return $this; + } + + /** + * Formats current color instance into given format + * + * @param string $type + * @return mixed + */ + public function format($type) + { + switch (strtolower($type)) { + + case 'rgba': + return $this->getRgba(); + + case 'hex': + return $this->getHex('#'); + + case 'int': + case 'integer': + return $this->getInt(); + + case 'array': + return $this->getArray(); + + case 'obj': + case 'object': + return $this; + + default: + throw new NotSupportedException( + "Color format ({$type}) is not supported." + ); + } + } + + /** + * Reads RGBA values from string into array + * + * @param string $value + * @return array + */ + protected function rgbaFromString($value) + { + $result = false; + + // parse color string in hexidecimal format like #cccccc or cccccc or ccc + $hexPattern = '/^#?([a-f0-9]{1,2})([a-f0-9]{1,2})([a-f0-9]{1,2})$/i'; + + // parse color string in format rgb(140, 140, 140) + $rgbPattern = '/^rgb ?\(([0-9]{1,3}), ?([0-9]{1,3}), ?([0-9]{1,3})\)$/i'; + + // parse color string in format rgba(255, 0, 0, 0.5) + $rgbaPattern = '/^rgba ?\(([0-9]{1,3}), ?([0-9]{1,3}), ?([0-9]{1,3}), ?([0-9.]{1,4})\)$/i'; + + if (preg_match($hexPattern, $value, $matches)) { + $result = []; + $result[0] = strlen($matches[1]) == '1' ? hexdec($matches[1].$matches[1]) : hexdec($matches[1]); + $result[1] = strlen($matches[2]) == '1' ? hexdec($matches[2].$matches[2]) : hexdec($matches[2]); + $result[2] = strlen($matches[3]) == '1' ? hexdec($matches[3].$matches[3]) : hexdec($matches[3]); + $result[3] = 1; + } elseif (preg_match($rgbPattern, $value, $matches)) { + $result = []; + $result[0] = ($matches[1] >= 0 && $matches[1] <= 255) ? intval($matches[1]) : 0; + $result[1] = ($matches[2] >= 0 && $matches[2] <= 255) ? intval($matches[2]) : 0; + $result[2] = ($matches[3] >= 0 && $matches[3] <= 255) ? intval($matches[3]) : 0; + $result[3] = 1; + } elseif (preg_match($rgbaPattern, $value, $matches)) { + $result = []; + $result[0] = ($matches[1] >= 0 && $matches[1] <= 255) ? intval($matches[1]) : 0; + $result[1] = ($matches[2] >= 0 && $matches[2] <= 255) ? intval($matches[2]) : 0; + $result[2] = ($matches[3] >= 0 && $matches[3] <= 255) ? intval($matches[3]) : 0; + $result[3] = ($matches[4] >= 0 && $matches[4] <= 1) ? $matches[4] : 0; + } else { + throw new NotReadableException( + "Unable to read color ({$value})." + ); + } + + return $result; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/AbstractDecoder.php b/vendor/intervention/image/src/Intervention/Image/AbstractDecoder.php new file mode 100644 index 0000000..743cc5c --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/AbstractDecoder.php @@ -0,0 +1,364 @@ +data = $data; + } + + /** + * Init from given URL + * + * @param string $url + * @return \Intervention\Image\Image + */ + public function initFromUrl($url) + { + + $options = [ + 'http' => [ + 'method'=>"GET", + 'protocol_version'=>1.1, // force use HTTP 1.1 for service mesh environment with envoy + 'header'=>"Accept-language: en\r\n". + "User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.2 (KHTML, like Gecko) Chrome/22.0.1216.0 Safari/537.2\r\n" + ] + ]; + + $context = stream_context_create($options); + + + if ($data = @file_get_contents($url, false, $context)) { + return $this->initFromBinary($data); + } + + throw new NotReadableException( + "Unable to init from given url (".$url.")." + ); + } + + /** + * Init from given stream + * + * @param StreamInterface|resource $stream + * @return \Intervention\Image\Image + */ + public function initFromStream($stream) + { + if (!$stream instanceof StreamInterface) { + $stream = new Stream($stream); + } + + try { + $offset = $stream->tell(); + } catch (\RuntimeException $e) { + $offset = 0; + } + + $shouldAndCanSeek = $offset !== 0 && $stream->isSeekable(); + + if ($shouldAndCanSeek) { + $stream->rewind(); + } + + try { + $data = $stream->getContents(); + } catch (\RuntimeException $e) { + $data = null; + } + + if ($shouldAndCanSeek) { + $stream->seek($offset); + } + + if ($data) { + return $this->initFromBinary($data); + } + + throw new NotReadableException( + "Unable to init from given stream" + ); + } + + /** + * Determines if current source data is GD resource + * + * @return boolean + */ + public function isGdResource() + { + if (is_resource($this->data)) { + return (get_resource_type($this->data) == 'gd'); + } + + if ($this->data instanceof \GdImage) { + return true; + } + + return false; + } + + /** + * Determines if current source data is Imagick object + * + * @return boolean + */ + public function isImagick() + { + return is_a($this->data, 'Imagick'); + } + + /** + * Determines if current source data is Intervention\Image\Image object + * + * @return boolean + */ + public function isInterventionImage() + { + return is_a($this->data, '\Intervention\Image\Image'); + } + + /** + * Determines if current data is SplFileInfo object + * + * @return boolean + */ + public function isSplFileInfo() + { + return is_a($this->data, 'SplFileInfo'); + } + + /** + * Determines if current data is Symfony UploadedFile component + * + * @return boolean + */ + public function isSymfonyUpload() + { + return is_a($this->data, 'Symfony\Component\HttpFoundation\File\UploadedFile'); + } + + /** + * Determines if current source data is file path + * + * @return boolean + */ + public function isFilePath() + { + if (is_string($this->data)) { + try { + return is_file($this->data); + } catch (\Exception $e) { + return false; + } + } + + return false; + } + + /** + * Determines if current source data is url + * + * @return boolean + */ + public function isUrl() + { + return (bool) filter_var($this->data, FILTER_VALIDATE_URL); + } + + /** + * Determines if current source data is a stream resource + * + * @return boolean + */ + public function isStream() + { + if ($this->data instanceof StreamInterface) return true; + if (!is_resource($this->data)) return false; + if (get_resource_type($this->data) !== 'stream') return false; + + return true; + } + + /** + * Determines if current source data is binary data + * + * @return boolean + */ + public function isBinary() + { + if (is_string($this->data)) { + $mime = finfo_buffer(finfo_open(FILEINFO_MIME_TYPE), $this->data); + return (substr($mime, 0, 4) != 'text' && $mime != 'application/x-empty'); + } + + return false; + } + + /** + * Determines if current source data is data-url + * + * @return boolean + */ + public function isDataUrl() + { + $data = $this->decodeDataUrl($this->data); + + return is_null($data) ? false : true; + } + + /** + * Determines if current source data is base64 encoded + * + * @return boolean + */ + public function isBase64() + { + if (!is_string($this->data)) { + return false; + } + + return base64_encode(base64_decode($this->data)) === str_replace(["\n", "\r"], '', $this->data); + } + + /** + * Initiates new Image from Intervention\Image\Image + * + * @param Image $object + * @return \Intervention\Image\Image + */ + public function initFromInterventionImage($object) + { + return $object; + } + + /** + * Parses and decodes binary image data from data-url + * + * @param string $data_url + * @return string + */ + private function decodeDataUrl($data_url) + { + if (!is_string($data_url)) { + return null; + } + + $pattern = "/^data:(?:image\/[a-zA-Z\-\.]+)(?:charset=\".+\")?;base64,(?P.+)$/"; + preg_match($pattern, str_replace(["\n", "\r"], '', $data_url), $matches); + + if (is_array($matches) && array_key_exists('data', $matches)) { + return base64_decode($matches['data']); + } + + return null; + } + + /** + * Initiates new image from mixed data + * + * @param mixed $data + * @return \Intervention\Image\Image + */ + public function init($data) + { + $this->data = $data; + + switch (true) { + + case $this->isGdResource(): + return $this->initFromGdResource($this->data); + + case $this->isImagick(): + return $this->initFromImagick($this->data); + + case $this->isInterventionImage(): + return $this->initFromInterventionImage($this->data); + + case $this->isSplFileInfo(): + return $this->initFromPath($this->data->getRealPath()); + + case $this->isBinary(): + return $this->initFromBinary($this->data); + + case $this->isUrl(): + return $this->initFromUrl($this->data); + + case $this->isStream(): + return $this->initFromStream($this->data); + + case $this->isDataUrl(): + return $this->initFromBinary($this->decodeDataUrl($this->data)); + + case $this->isFilePath(): + return $this->initFromPath($this->data); + + // isBase64 has to be after isFilePath to prevent false positives + case $this->isBase64(): + return $this->initFromBinary(base64_decode($this->data)); + + default: + throw new NotReadableException("Image source not readable"); + } + } + + /** + * Decoder object transforms to string source data + * + * @return string + */ + public function __toString() + { + return (string) $this->data; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/AbstractDriver.php b/vendor/intervention/image/src/Intervention/Image/AbstractDriver.php new file mode 100644 index 0000000..6c591a7 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/AbstractDriver.php @@ -0,0 +1,140 @@ +decoder->init($data); + } + + /** + * Encodes given image + * + * @param Image $image + * @param string $format + * @param int $quality + * @return \Intervention\Image\Image + */ + public function encode($image, $format, $quality) + { + return $this->encoder->process($image, $format, $quality); + } + + /** + * Executes named command on given image + * + * @param Image $image + * @param string $name + * @param array $arguments + * @return \Intervention\Image\Commands\AbstractCommand + */ + public function executeCommand($image, $name, $arguments) + { + $commandName = $this->getCommandClassName($name); + $command = new $commandName($arguments); + $command->execute($image); + + return $command; + } + + /** + * Returns classname of given command name + * + * @param string $name + * @return string + */ + private function getCommandClassName($name) + { + if (extension_loaded('mbstring')) { + $name = mb_strtoupper(mb_substr($name, 0, 1)) . mb_substr($name, 1); + } else { + $name = strtoupper(substr($name, 0, 1)) . substr($name, 1); + } + + $drivername = $this->getDriverName(); + $classnameLocal = sprintf('\Intervention\Image\%s\Commands\%sCommand', $drivername, ucfirst($name)); + $classnameGlobal = sprintf('\Intervention\Image\Commands\%sCommand', ucfirst($name)); + + if (class_exists($classnameLocal)) { + return $classnameLocal; + } elseif (class_exists($classnameGlobal)) { + return $classnameGlobal; + } + + throw new NotSupportedException( + "Command ({$name}) is not available for driver ({$drivername})." + ); + } + + /** + * Returns name of current driver instance + * + * @return string + */ + public function getDriverName() + { + $reflect = new \ReflectionClass($this); + $namespace = $reflect->getNamespaceName(); + + return substr(strrchr($namespace, "\\"), 1); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/AbstractEncoder.php b/vendor/intervention/image/src/Intervention/Image/AbstractEncoder.php new file mode 100644 index 0000000..c25f8bf --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/AbstractEncoder.php @@ -0,0 +1,271 @@ +setImage($image); + $this->setFormat($format); + $this->setQuality($quality); + + switch (strtolower($this->format)) { + + case 'data-url': + $this->result = $this->processDataUrl(); + break; + + case 'gif': + case 'image/gif': + $this->result = $this->processGif(); + break; + + case 'png': + case 'image/png': + case 'image/x-png': + $this->result = $this->processPng(); + break; + + case 'jpg': + case 'jpeg': + case 'jfif': + case 'image/jp2': + case 'image/jpg': + case 'image/jpeg': + case 'image/pjpeg': + case 'image/jfif': + $this->result = $this->processJpeg(); + break; + + case 'tif': + case 'tiff': + case 'image/tiff': + case 'image/tif': + case 'image/x-tif': + case 'image/x-tiff': + $this->result = $this->processTiff(); + break; + + case 'bmp': + case 'ms-bmp': + case 'x-bitmap': + case 'x-bmp': + case 'x-ms-bmp': + case 'x-win-bitmap': + case 'x-windows-bmp': + case 'x-xbitmap': + case 'image/ms-bmp': + case 'image/x-bitmap': + case 'image/x-bmp': + case 'image/x-ms-bmp': + case 'image/x-win-bitmap': + case 'image/x-windows-bmp': + case 'image/x-xbitmap': + $this->result = $this->processBmp(); + break; + + case 'ico': + case 'image/x-ico': + case 'image/x-icon': + case 'image/vnd.microsoft.icon': + $this->result = $this->processIco(); + break; + + case 'psd': + case 'image/vnd.adobe.photoshop': + $this->result = $this->processPsd(); + break; + + case 'webp': + case 'image/webp': + case 'image/x-webp': + $this->result = $this->processWebp(); + break; + + case 'avif': + case 'image/avif': + $this->result = $this->processAvif(); + break; + + case 'heic': + case 'image/heic': + case 'image/heif': + $this->result = $this->processHeic(); + break; + + default: + throw new NotSupportedException( + "Encoding format ({$this->format}) is not supported." + ); + } + + $this->setImage(null); + + return $image->setEncoded($this->result); + } + + /** + * Processes and returns encoded image as data-url string + * + * @return string + */ + protected function processDataUrl() + { + $mime = $this->image->mime ? $this->image->mime : 'image/png'; + + return sprintf('data:%s;base64,%s', + $mime, + base64_encode($this->process($this->image, $mime, $this->quality)) + ); + } + + /** + * Sets image to process + * + * @param Image $image + */ + protected function setImage($image) + { + $this->image = $image; + } + + /** + * Determines output format + * + * @param string $format + */ + protected function setFormat($format = null) + { + if ($format == '' && $this->image instanceof Image) { + $format = $this->image->mime; + } + + $this->format = $format ? $format : 'jpg'; + + return $this; + } + + /** + * Determines output quality + * + * @param int $quality + */ + protected function setQuality($quality) + { + $quality = is_null($quality) ? 90 : $quality; + $quality = $quality === 0 ? 1 : $quality; + + if ($quality < 0 || $quality > 100) { + throw new InvalidArgumentException( + 'Quality must range from 0 to 100.' + ); + } + + $this->quality = intval($quality); + + return $this; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/AbstractFont.php b/vendor/intervention/image/src/Intervention/Image/AbstractFont.php new file mode 100644 index 0000000..35c1825 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/AbstractFont.php @@ -0,0 +1,295 @@ +text = $text; + } + + /** + * Set text to be written + * + * @param String $text + * @return self + */ + public function text($text) + { + $this->text = $text; + + return $this; + } + + /** + * Get text to be written + * + * @return String + */ + public function getText() + { + return $this->text; + } + + /** + * Set font size in pixels + * + * @param int $size + * @return self + */ + public function size($size) + { + $this->size = $size; + + return $this; + } + + /** + * Get font size in pixels + * + * @return int + */ + public function getSize() + { + return $this->size; + } + + /** + * Set color of text to be written + * + * @param mixed $color + * @return self + */ + public function color($color) + { + $this->color = $color; + + return $this; + } + + /** + * Get color of text + * + * @return mixed + */ + public function getColor() + { + return $this->color; + } + + /** + * Set rotation angle of text + * + * @param int $angle + * @return self + */ + public function angle($angle) + { + $this->angle = $angle; + + return $this; + } + + /** + * Get rotation angle of text + * + * @return int + */ + public function getAngle() + { + return $this->angle; + } + + /** + * Set horizontal text alignment + * + * @param string $align + * @return self + */ + public function align($align) + { + $this->align = $align; + + return $this; + } + + /** + * Get horizontal text alignment + * + * @return string + */ + public function getAlign() + { + return $this->align; + } + + /** + * Set vertical text alignment + * + * @param string $valign + * @return self + */ + public function valign($valign) + { + $this->valign = $valign; + + return $this; + } + + /** + * Get vertical text alignment + * + * @return string + */ + public function getValign() + { + return $this->valign; + } + + /** + * Set text kerning + * + * @param string $kerning + * @return void + */ + public function kerning($kerning) + { + $this->kerning = $kerning; + } + + /** + * Get kerning + * + * @return float + */ + public function getKerning() + { + return $this->kerning; + } + + /** + * Set path to font file + * + * @param string $file + * @return self + */ + public function file($file) + { + $this->file = $file; + + return $this; + } + + /** + * Get path to font file + * + * @return string + */ + public function getFile() + { + return $this->file; + } + + /** + * Checks if current font has access to an applicable font file + * + * @return boolean + */ + protected function hasApplicableFontFile() + { + if (is_string($this->file)) { + return file_exists($this->file); + } + + return false; + } + + /** + * Counts lines of text to be written + * + * @return int + */ + public function countLines() + { + return count(explode(PHP_EOL, $this->text)); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/AbstractShape.php b/vendor/intervention/image/src/Intervention/Image/AbstractShape.php new file mode 100644 index 0000000..cd4a9f1 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/AbstractShape.php @@ -0,0 +1,71 @@ +background = $color; + } + + /** + * Set border width and color of current shape + * + * @param int $width + * @param string $color + * @return void + */ + public function border($width, $color = null) + { + $this->border_width = is_numeric($width) ? intval($width) : 0; + $this->border_color = is_null($color) ? '#000000' : $color; + } + + /** + * Determines if current shape has border + * + * @return boolean + */ + public function hasBorder() + { + return ($this->border_width >= 1); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/AbstractCommand.php b/vendor/intervention/image/src/Intervention/Image/Commands/AbstractCommand.php new file mode 100644 index 0000000..e31078c --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/AbstractCommand.php @@ -0,0 +1,81 @@ +arguments = $arguments; + } + + /** + * Creates new argument instance from given argument key + * + * @param int $key + * @return \Intervention\Image\Commands\Argument + */ + public function argument($key) + { + return new Argument($this, $key); + } + + /** + * Returns output data of current command + * + * @return mixed + */ + public function getOutput() + { + return $this->output ? $this->output : null; + } + + /** + * Determines if current instance has output data + * + * @return boolean + */ + public function hasOutput() + { + return ! is_null($this->output); + } + + /** + * Sets output data of current command + * + * @param mixed $value + */ + public function setOutput($value) + { + $this->output = $value; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/Argument.php b/vendor/intervention/image/src/Intervention/Image/Commands/Argument.php new file mode 100644 index 0000000..9538199 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/Argument.php @@ -0,0 +1,225 @@ +command = $command; + $this->key = $key; + } + + /** + * Returns name of current arguments command + * + * @return string + */ + public function getCommandName() + { + preg_match("/\\\\([\w]+)Command$/", get_class($this->command), $matches); + return isset($matches[1]) ? lcfirst($matches[1]).'()' : 'Method'; + } + + /** + * Returns value of current argument + * + * @param mixed $default + * @return mixed + */ + public function value($default = null) + { + $arguments = $this->command->arguments; + + if (is_array($arguments)) { + return isset($arguments[$this->key]) ? $arguments[$this->key] : $default; + } + + return $default; + } + + /** + * Defines current argument as required + * + * @return \Intervention\Image\Commands\Argument + */ + public function required() + { + if ( ! array_key_exists($this->key, $this->command->arguments)) { + throw new InvalidArgumentException( + sprintf("Missing argument %d for %s", $this->key + 1, $this->getCommandName()) + ); + } + + return $this; + } + + /** + * Determines that current argument must be of given type + * + * @return \Intervention\Image\Commands\Argument + */ + public function type($type) + { + $valid = true; + $value = $this->value(); + + if ($value === null) { + return $this; + } + + switch (strtolower($type)) { + case 'bool': + case 'boolean': + $valid = \is_bool($value); + $message = '%s accepts only boolean values as argument %d.'; + break; + case 'int': + case 'integer': + $valid = \is_int($value); + $message = '%s accepts only integer values as argument %d.'; + break; + case 'num': + case 'numeric': + $valid = is_numeric($value); + $message = '%s accepts only numeric values as argument %d.'; + break; + case 'str': + case 'string': + $valid = \is_string($value); + $message = '%s accepts only string values as argument %d.'; + break; + case 'array': + $valid = \is_array($value); + $message = '%s accepts only array as argument %d.'; + break; + case 'closure': + $valid = is_a($value, '\Closure'); + $message = '%s accepts only Closure as argument %d.'; + break; + case 'digit': + $valid = $this->isDigit($value); + $message = '%s accepts only integer values as argument %d.'; + break; + } + + if (! $valid) { + $commandName = $this->getCommandName(); + $argument = $this->key + 1; + + if (isset($message)) { + $message = sprintf($message, $commandName, $argument); + } else { + $message = sprintf('Missing argument for %d.', $argument); + } + + throw new InvalidArgumentException( + $message + ); + } + + return $this; + } + + /** + * Determines that current argument value must be numeric between given values + * + * @return \Intervention\Image\Commands\Argument + */ + public function between($x, $y) + { + $value = $this->type('numeric')->value(); + + if (is_null($value)) { + return $this; + } + + $alpha = min($x, $y); + $omega = max($x, $y); + + if ($value < $alpha || $value > $omega) { + throw new InvalidArgumentException( + sprintf('Argument %d must be between %s and %s.', $this->key, $x, $y) + ); + } + + return $this; + } + + /** + * Determines that current argument must be over a minimum value + * + * @return \Intervention\Image\Commands\Argument + */ + public function min($value) + { + $v = $this->type('numeric')->value(); + + if (is_null($v)) { + return $this; + } + + if ($v < $value) { + throw new InvalidArgumentException( + sprintf('Argument %d must be at least %s.', $this->key, $value) + ); + } + + return $this; + } + + /** + * Determines that current argument must be under a maxiumum value + * + * @return \Intervention\Image\Commands\Argument + */ + public function max($value) + { + $v = $this->type('numeric')->value(); + + if (is_null($v)) { + return $this; + } + + if ($v > $value) { + throw new InvalidArgumentException( + sprintf('Argument %d may not be greater than %s.', $this->key, $value) + ); + } + + return $this; + } + + /** + * Checks if value is "PHP" integer (120 but also 120.0) + * + * @param mixed $value + * @return boolean + */ + private function isDigit($value) + { + return is_numeric($value) ? intval($value) == $value : false; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/ChecksumCommand.php b/vendor/intervention/image/src/Intervention/Image/Commands/ChecksumCommand.php new file mode 100644 index 0000000..9acc403 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/ChecksumCommand.php @@ -0,0 +1,29 @@ +getSize(); + + for ($x=0; $x <= ($size->width-1); $x++) { + for ($y=0; $y <= ($size->height-1); $y++) { + $colors[] = $image->pickColor($x, $y, 'array'); + } + } + + $this->setOutput(md5(serialize($colors))); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/CircleCommand.php b/vendor/intervention/image/src/Intervention/Image/Commands/CircleCommand.php new file mode 100644 index 0000000..c627818 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/CircleCommand.php @@ -0,0 +1,35 @@ +argument(0)->type('numeric')->required()->value(); + $x = $this->argument(1)->type('numeric')->required()->value(); + $y = $this->argument(2)->type('numeric')->required()->value(); + $callback = $this->argument(3)->type('closure')->value(); + + $circle_classname = sprintf('\Intervention\Image\%s\Shapes\CircleShape', + $image->getDriver()->getDriverName()); + + $circle = new $circle_classname($diameter); + + if ($callback instanceof Closure) { + $callback($circle); + } + + $circle->applyToImage($image, $x, $y); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/EllipseCommand.php b/vendor/intervention/image/src/Intervention/Image/Commands/EllipseCommand.php new file mode 100644 index 0000000..4637e02 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/EllipseCommand.php @@ -0,0 +1,36 @@ +argument(0)->type('numeric')->required()->value(); + $height = $this->argument(1)->type('numeric')->required()->value(); + $x = $this->argument(2)->type('numeric')->required()->value(); + $y = $this->argument(3)->type('numeric')->required()->value(); + $callback = $this->argument(4)->type('closure')->value(); + + $ellipse_classname = sprintf('\Intervention\Image\%s\Shapes\EllipseShape', + $image->getDriver()->getDriverName()); + + $ellipse = new $ellipse_classname($width, $height); + + if ($callback instanceof Closure) { + $callback($ellipse); + } + + $ellipse->applyToImage($image, $x, $y); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/ExifCommand.php b/vendor/intervention/image/src/Intervention/Image/Commands/ExifCommand.php new file mode 100644 index 0000000..063df6e --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/ExifCommand.php @@ -0,0 +1,61 @@ +argument(0)->value(); + + // try to read exif data from image file + try { + if ($image->dirname && $image->basename) { + $stream = $image->dirname . '/' . $image->basename; + } elseif (version_compare(PHP_VERSION, '7.2.0', '>=')) { + // https://www.php.net/manual/en/function.exif-read-data.php#refsect1-function.exif-read-data-changelog + $stream = $image->stream()->detach(); + } else { + // https://bugs.php.net/bug.php?id=65187 + $stream = $image->encode('data-url')->encoded; + } + + $data = @exif_read_data($stream); + + if (!is_null($key) && is_array($data)) { + $data = array_key_exists($key, $data) ? $data[$key] : false; + } + + } catch (\Exception $e) { + throw new NotReadableException( + sprintf( + "Cannot read the Exif data from the filename (%s) provided ", + $image->dirname . '/' . $image->basename + ), + $e->getCode(), + $e + ); + } + + $this->setOutput($data); + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/IptcCommand.php b/vendor/intervention/image/src/Intervention/Image/Commands/IptcCommand.php new file mode 100644 index 0000000..b78a2a8 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/IptcCommand.php @@ -0,0 +1,68 @@ +argument(0)->value(); + + $info = []; + @getimagesize($image->dirname .'/'. $image->basename, $info); + + $data = []; + + if (array_key_exists('APP13', $info)) { + $iptc = iptcparse($info['APP13']); + + if (is_array($iptc)) { + $data['DocumentTitle'] = isset($iptc["2#005"][0]) ? $iptc["2#005"][0] : null; + $data['Urgency'] = isset($iptc["2#010"][0]) ? $iptc["2#010"][0] : null; + $data['Category'] = isset($iptc["2#015"][0]) ? $iptc["2#015"][0] : null; + $data['Subcategories'] = isset($iptc["2#020"][0]) ? $iptc["2#020"][0] : null; + $data['Keywords'] = isset($iptc["2#025"][0]) ? $iptc["2#025"] : null; + $data['ReleaseDate'] = isset($iptc["2#030"][0]) ? $iptc["2#030"][0] : null; + $data['ReleaseTime'] = isset($iptc["2#035"][0]) ? $iptc["2#035"][0] : null; + $data['SpecialInstructions'] = isset($iptc["2#040"][0]) ? $iptc["2#040"][0] : null; + $data['CreationDate'] = isset($iptc["2#055"][0]) ? $iptc["2#055"][0] : null; + $data['CreationTime'] = isset($iptc["2#060"][0]) ? $iptc["2#060"][0] : null; + $data['AuthorByline'] = isset($iptc["2#080"][0]) ? $iptc["2#080"][0] : null; + $data['AuthorTitle'] = isset($iptc["2#085"][0]) ? $iptc["2#085"][0] : null; + $data['City'] = isset($iptc["2#090"][0]) ? $iptc["2#090"][0] : null; + $data['SubLocation'] = isset($iptc["2#092"][0]) ? $iptc["2#092"][0] : null; + $data['State'] = isset($iptc["2#095"][0]) ? $iptc["2#095"][0] : null; + $data['Country'] = isset($iptc["2#101"][0]) ? $iptc["2#101"][0] : null; + $data['OTR'] = isset($iptc["2#103"][0]) ? $iptc["2#103"][0] : null; + $data['Headline'] = isset($iptc["2#105"][0]) ? $iptc["2#105"][0] : null; + $data['Source'] = isset($iptc["2#110"][0]) ? $iptc["2#110"][0] : null; + $data['PhotoSource'] = isset($iptc["2#115"][0]) ? $iptc["2#115"][0] : null; + $data['Copyright'] = isset($iptc["2#116"][0]) ? $iptc["2#116"][0] : null; + $data['Caption'] = isset($iptc["2#120"][0]) ? $iptc["2#120"][0] : null; + $data['CaptionWriter'] = isset($iptc["2#122"][0]) ? $iptc["2#122"][0] : null; + } + } + + if (! is_null($key) && is_array($data)) { + $data = array_key_exists($key, $data) ? $data[$key] : false; + } + + $this->setOutput($data); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/LineCommand.php b/vendor/intervention/image/src/Intervention/Image/Commands/LineCommand.php new file mode 100644 index 0000000..a068c66 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/LineCommand.php @@ -0,0 +1,36 @@ +argument(0)->type('numeric')->required()->value(); + $y1 = $this->argument(1)->type('numeric')->required()->value(); + $x2 = $this->argument(2)->type('numeric')->required()->value(); + $y2 = $this->argument(3)->type('numeric')->required()->value(); + $callback = $this->argument(4)->type('closure')->value(); + + $line_classname = sprintf('\Intervention\Image\%s\Shapes\LineShape', + $image->getDriver()->getDriverName()); + + $line = new $line_classname($x2, $y2); + + if ($callback instanceof Closure) { + $callback($line); + } + + $line->applyToImage($image, $x1, $y1); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/OrientateCommand.php b/vendor/intervention/image/src/Intervention/Image/Commands/OrientateCommand.php new file mode 100644 index 0000000..552482c --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/OrientateCommand.php @@ -0,0 +1,48 @@ +exif('Orientation')) { + + case 2: + $image->flip(); + break; + + case 3: + $image->rotate(180); + break; + + case 4: + $image->rotate(180)->flip(); + break; + + case 5: + $image->rotate(270)->flip(); + break; + + case 6: + $image->rotate(270); + break; + + case 7: + $image->rotate(90)->flip(); + break; + + case 8: + $image->rotate(90); + break; + } + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/PolygonCommand.php b/vendor/intervention/image/src/Intervention/Image/Commands/PolygonCommand.php new file mode 100644 index 0000000..a2fa997 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/PolygonCommand.php @@ -0,0 +1,49 @@ +argument(0)->type('array')->required()->value(); + $callback = $this->argument(1)->type('closure')->value(); + + $vertices_count = count($points); + + // check if number if coordinates is even + if ($vertices_count % 2 !== 0) { + throw new InvalidArgumentException( + "The number of given polygon vertices must be even." + ); + } + + if ($vertices_count < 6) { + throw new InvalidArgumentException( + "You must have at least 3 points in your array." + ); + } + + $polygon_classname = sprintf('\Intervention\Image\%s\Shapes\PolygonShape', + $image->getDriver()->getDriverName()); + + $polygon = new $polygon_classname($points); + + if ($callback instanceof Closure) { + $callback($polygon); + } + + $polygon->applyToImage($image); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/PsrResponseCommand.php b/vendor/intervention/image/src/Intervention/Image/Commands/PsrResponseCommand.php new file mode 100644 index 0000000..d75cd90 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/PsrResponseCommand.php @@ -0,0 +1,45 @@ +argument(0)->value(); + $quality = $this->argument(1)->between(0, 100)->value(); + + //Encoded property will be populated at this moment + $stream = $image->stream($format, $quality); + + $mimetype = finfo_buffer( + finfo_open(FILEINFO_MIME_TYPE), + $image->getEncoded() + ); + + $this->setOutput(new Response( + 200, + [ + 'Content-Type' => $mimetype, + 'Content-Length' => strlen($image->getEncoded()) + ], + $stream + )); + + return true; + } +} \ No newline at end of file diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/RectangleCommand.php b/vendor/intervention/image/src/Intervention/Image/Commands/RectangleCommand.php new file mode 100644 index 0000000..24378b3 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/RectangleCommand.php @@ -0,0 +1,36 @@ +argument(0)->type('numeric')->required()->value(); + $y1 = $this->argument(1)->type('numeric')->required()->value(); + $x2 = $this->argument(2)->type('numeric')->required()->value(); + $y2 = $this->argument(3)->type('numeric')->required()->value(); + $callback = $this->argument(4)->type('closure')->value(); + + $rectangle_classname = sprintf('\Intervention\Image\%s\Shapes\RectangleShape', + $image->getDriver()->getDriverName()); + + $rectangle = new $rectangle_classname($x1, $y1, $x2, $y2); + + if ($callback instanceof Closure) { + $callback($rectangle); + } + + $rectangle->applyToImage($image, $x1, $y1); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/ResponseCommand.php b/vendor/intervention/image/src/Intervention/Image/Commands/ResponseCommand.php new file mode 100644 index 0000000..7903b5a --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/ResponseCommand.php @@ -0,0 +1,26 @@ +argument(0)->value(); + $quality = $this->argument(1)->between(0, 100)->value(); + + $response = new Response($image, $format, $quality); + + $this->setOutput($response->make()); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/StreamCommand.php b/vendor/intervention/image/src/Intervention/Image/Commands/StreamCommand.php new file mode 100644 index 0000000..bc2ff9e --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/StreamCommand.php @@ -0,0 +1,39 @@ +argument(0)->value(); + $quality = $this->argument(1)->between(0, 100)->value(); + $data = $image->encode($format, $quality)->getEncoded(); + + $this->setOutput($this->getStream($data)); + + return true; + } + + /** + * Create stream from given data + * + * @param string $data + * @return \Psr\Http\Message\StreamInterface + */ + protected function getStream($data) + { + if (class_exists(\GuzzleHttp\Psr7\Utils::class)) { + return \GuzzleHttp\Psr7\Utils::streamFor($data); // guzzlehttp/psr7 >= 2.0 + } + + return \GuzzleHttp\Psr7\stream_for($data); // guzzlehttp/psr7 < 2.0 + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/TextCommand.php b/vendor/intervention/image/src/Intervention/Image/Commands/TextCommand.php new file mode 100644 index 0000000..3c60b4e --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/TextCommand.php @@ -0,0 +1,34 @@ +argument(0)->required()->value(); + $x = $this->argument(1)->type('numeric')->value(0); + $y = $this->argument(2)->type('numeric')->value(0); + $callback = $this->argument(3)->type('closure')->value(); + + $fontclassname = sprintf('\Intervention\Image\%s\Font', + $image->getDriver()->getDriverName()); + + $font = new $fontclassname($text); + + if ($callback instanceof Closure) { + $callback($font); + } + + $font->applyToImage($image, $x, $y); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Constraint.php b/vendor/intervention/image/src/Intervention/Image/Constraint.php new file mode 100644 index 0000000..44bdd67 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Constraint.php @@ -0,0 +1,92 @@ +size = $size; + } + + /** + * Returns current size of constraint + * + * @return \Intervention\Image\Size + */ + public function getSize() + { + return $this->size; + } + + /** + * Fix the given argument in current constraint + * + * @param int $type + * @return void + */ + public function fix($type) + { + $this->fixed = ($this->fixed & ~(1 << $type)) | (1 << $type); + } + + /** + * Checks if given argument is fixed in current constraint + * + * @param int $type + * @return boolean + */ + public function isFixed($type) + { + return (bool) ($this->fixed & (1 << $type)); + } + + /** + * Fixes aspect ratio in current constraint + * + * @return void + */ + public function aspectRatio() + { + $this->fix(self::ASPECTRATIO); + } + + /** + * Fixes possibility to size up in current constraint + * + * @return void + */ + public function upsize() + { + $this->fix(self::UPSIZE); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Exception/ImageException.php b/vendor/intervention/image/src/Intervention/Image/Exception/ImageException.php new file mode 100644 index 0000000..83e6b91 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Exception/ImageException.php @@ -0,0 +1,8 @@ +dirname = array_key_exists('dirname', $info) ? $info['dirname'] : null; + $this->basename = array_key_exists('basename', $info) ? $info['basename'] : null; + $this->extension = array_key_exists('extension', $info) ? $info['extension'] : null; + $this->filename = array_key_exists('filename', $info) ? $info['filename'] : null; + + if (file_exists($path) && is_file($path)) { + $this->mime = finfo_file(finfo_open(FILEINFO_MIME_TYPE), $path); + } + + return $this; + } + + /** + * Get file size + * + * @return mixed + */ + public function filesize() + { + $path = $this->basePath(); + + if (file_exists($path) && is_file($path)) { + return filesize($path); + } + + return false; + } + + /** + * Get fully qualified path + * + * @return string + */ + public function basePath() + { + if ($this->dirname && $this->basename) { + return ($this->dirname .'/'. $this->basename); + } + + return null; + } + +} diff --git a/vendor/intervention/image/src/Intervention/Image/Filters/DemoFilter.php b/vendor/intervention/image/src/Intervention/Image/Filters/DemoFilter.php new file mode 100644 index 0000000..4e8f92b --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Filters/DemoFilter.php @@ -0,0 +1,44 @@ +size = is_numeric($size) ? intval($size) : self::DEFAULT_SIZE; + } + + /** + * Applies filter effects to given image + * + * @param \Intervention\Image\Image $image + * @return \Intervention\Image\Image + */ + public function applyFilter(Image $image) + { + $image->pixelate($this->size); + $image->greyscale(); + + return $image; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Filters/FilterInterface.php b/vendor/intervention/image/src/Intervention/Image/Filters/FilterInterface.php new file mode 100644 index 0000000..88f6cbf --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Filters/FilterInterface.php @@ -0,0 +1,16 @@ +a = ($value >> 24) & 0xFF; + $this->r = ($value >> 16) & 0xFF; + $this->g = ($value >> 8) & 0xFF; + $this->b = $value & 0xFF; + } + + /** + * Initiates color object from given array + * + * @param array $value + * @return \Intervention\Image\AbstractColor + */ + public function initFromArray($array) + { + $array = array_values($array); + + if (count($array) == 4) { + + // color array with alpha value + list($r, $g, $b, $a) = $array; + $this->a = $this->alpha2gd($a); + + } elseif (count($array) == 3) { + + // color array without alpha value + list($r, $g, $b) = $array; + $this->a = 0; + + } + + $this->r = $r; + $this->g = $g; + $this->b = $b; + } + + /** + * Initiates color object from given string + * + * @param string $value + * @return \Intervention\Image\AbstractColor + */ + public function initFromString($value) + { + if ($color = $this->rgbaFromString($value)) { + $this->r = $color[0]; + $this->g = $color[1]; + $this->b = $color[2]; + $this->a = $this->alpha2gd($color[3]); + } + } + + /** + * Initiates color object from given R, G and B values + * + * @param int $r + * @param int $g + * @param int $b + * @return \Intervention\Image\AbstractColor + */ + public function initFromRgb($r, $g, $b) + { + $this->r = intval($r); + $this->g = intval($g); + $this->b = intval($b); + $this->a = 0; + } + + /** + * Initiates color object from given R, G, B and A values + * + * @param int $r + * @param int $g + * @param int $b + * @param float $a + * @return \Intervention\Image\AbstractColor + */ + public function initFromRgba($r, $g, $b, $a = 1) + { + $this->r = intval($r); + $this->g = intval($g); + $this->b = intval($b); + $this->a = $this->alpha2gd($a); + } + + /** + * Initiates color object from given ImagickPixel object + * + * @param ImagickPixel $value + * @return \Intervention\Image\AbstractColor + */ + public function initFromObject($value) + { + throw new NotSupportedException( + "GD colors cannot init from ImagickPixel objects." + ); + } + + /** + * Calculates integer value of current color instance + * + * @return int + */ + public function getInt() + { + return ($this->a << 24) + ($this->r << 16) + ($this->g << 8) + $this->b; + } + + /** + * Calculates hexadecimal value of current color instance + * + * @param string $prefix + * @return string + */ + public function getHex($prefix = '') + { + return sprintf('%s%02x%02x%02x', $prefix, $this->r, $this->g, $this->b); + } + + /** + * Calculates RGB(A) in array format of current color instance + * + * @return array + */ + public function getArray() + { + return [$this->r, $this->g, $this->b, round(1 - $this->a / 127, 2)]; + } + + /** + * Calculates RGBA in string format of current color instance + * + * @return string + */ + public function getRgba() + { + return sprintf('rgba(%d, %d, %d, %.2F)', $this->r, $this->g, $this->b, round(1 - $this->a / 127, 2)); + } + + /** + * Determines if current color is different from given color + * + * @param AbstractColor $color + * @param int $tolerance + * @return boolean + */ + public function differs(AbstractColor $color, $tolerance = 0) + { + $color_tolerance = round($tolerance * 2.55); + $alpha_tolerance = round($tolerance * 1.27); + + $delta = [ + 'r' => abs($color->r - $this->r), + 'g' => abs($color->g - $this->g), + 'b' => abs($color->b - $this->b), + 'a' => abs($color->a - $this->a) + ]; + + return ( + $delta['r'] > $color_tolerance or + $delta['g'] > $color_tolerance or + $delta['b'] > $color_tolerance or + $delta['a'] > $alpha_tolerance + ); + } + + /** + * Convert rgba alpha (0-1) value to gd value (0-127) + * + * @param float $input + * @return int + */ + private function alpha2gd($input) + { + $oldMin = 0; + $oldMax = 1; + + $newMin = 127; + $newMax = 0; + + return ceil(((($input- $oldMin) * ($newMax - $newMin)) / ($oldMax - $oldMin)) + $newMin); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/BackupCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/BackupCommand.php new file mode 100644 index 0000000..310dc03 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/BackupCommand.php @@ -0,0 +1,25 @@ +argument(0)->value(); + + // clone current image resource + $clone = clone $image; + $image->setBackup($clone->getCore(), $backupName); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/BlurCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/BlurCommand.php new file mode 100644 index 0000000..c4fca0e --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/BlurCommand.php @@ -0,0 +1,25 @@ +argument(0)->between(0, 100)->value(1); + + for ($i=0; $i < intval($amount); $i++) { + imagefilter($image->getCore(), IMG_FILTER_GAUSSIAN_BLUR); + } + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/BrightnessCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/BrightnessCommand.php new file mode 100644 index 0000000..4e48464 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/BrightnessCommand.php @@ -0,0 +1,21 @@ +argument(0)->between(-100, 100)->required()->value(); + + return imagefilter($image->getCore(), IMG_FILTER_BRIGHTNESS, ($level * 2.55)); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ColorizeCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ColorizeCommand.php new file mode 100644 index 0000000..410917b --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ColorizeCommand.php @@ -0,0 +1,29 @@ +argument(0)->between(-100, 100)->required()->value(); + $green = $this->argument(1)->between(-100, 100)->required()->value(); + $blue = $this->argument(2)->between(-100, 100)->required()->value(); + + // normalize colorize levels + $red = round($red * 2.55); + $green = round($green * 2.55); + $blue = round($blue * 2.55); + + // apply filter + return imagefilter($image->getCore(), IMG_FILTER_COLORIZE, $red, $green, $blue); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ContrastCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ContrastCommand.php new file mode 100644 index 0000000..916c41f --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ContrastCommand.php @@ -0,0 +1,21 @@ +argument(0)->between(-100, 100)->required()->value(); + + return imagefilter($image->getCore(), IMG_FILTER_CONTRAST, ($level * -1)); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/CropCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/CropCommand.php new file mode 100644 index 0000000..b7f5954 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/CropCommand.php @@ -0,0 +1,40 @@ +argument(0)->type('digit')->required()->value(); + $height = $this->argument(1)->type('digit')->required()->value(); + $x = $this->argument(2)->type('digit')->value(); + $y = $this->argument(3)->type('digit')->value(); + + if (is_null($width) || is_null($height)) { + throw new \Intervention\Image\Exception\InvalidArgumentException( + "Width and height of cutout needs to be defined." + ); + } + + $cropped = new Size($width, $height); + $position = new Point($x, $y); + + // align boxes + if (is_null($x) && is_null($y)) { + $position = $image->getSize()->align('center')->relativePosition($cropped->align('center')); + } + + // crop image core + return $this->modify($image, 0, 0, $position->x, $position->y, $cropped->width, $cropped->height, $cropped->width, $cropped->height); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/DestroyCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/DestroyCommand.php new file mode 100644 index 0000000..4503d10 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/DestroyCommand.php @@ -0,0 +1,27 @@ +getCore()); + + // destroy backups + foreach ($image->getBackups() as $backup) { + imagedestroy($backup); + } + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/FillCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/FillCommand.php new file mode 100644 index 0000000..cf19082 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/FillCommand.php @@ -0,0 +1,69 @@ +argument(0)->value(); + $x = $this->argument(1)->type('digit')->value(); + $y = $this->argument(2)->type('digit')->value(); + + $width = $image->getWidth(); + $height = $image->getHeight(); + $resource = $image->getCore(); + + try { + + // set image tile filling + $source = new Decoder; + $tile = $source->init($filling); + imagesettile($image->getCore(), $tile->getCore()); + $filling = IMG_COLOR_TILED; + + } catch (\Intervention\Image\Exception\NotReadableException $e) { + + // set solid color filling + $color = new Color($filling); + $filling = $color->getInt(); + } + + imagealphablending($resource, true); + + if (is_int($x) && is_int($y)) { + + // resource should be visible through transparency + $base = $image->getDriver()->newImage($width, $height)->getCore(); + imagecopy($base, $resource, 0, 0, 0, 0, $width, $height); + + // floodfill if exact position is defined + imagefill($resource, $x, $y, $filling); + + // copy filled original over base + imagecopy($base, $resource, 0, 0, 0, 0, $width, $height); + + // set base as new resource-core + $image->setCore($base); + imagedestroy($resource); + + } else { + // fill whole image otherwise + imagefilledrectangle($resource, 0, 0, $width - 1, $height - 1, $filling); + } + + isset($tile) ? imagedestroy($tile->getCore()) : null; + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/FitCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/FitCommand.php new file mode 100644 index 0000000..d861ad9 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/FitCommand.php @@ -0,0 +1,32 @@ +argument(0)->type('digit')->required()->value(); + $height = $this->argument(1)->type('digit')->value($width); + $constraints = $this->argument(2)->type('closure')->value(); + $position = $this->argument(3)->type('string')->value('center'); + + // calculate size + $cropped = $image->getSize()->fit(new Size($width, $height), $position); + $resized = clone $cropped; + $resized = $resized->resize($width, $height, $constraints); + + // modify image + $this->modify($image, 0, 0, $cropped->pivot->x, $cropped->pivot->y, $resized->getWidth(), $resized->getHeight(), $cropped->getWidth(), $cropped->getHeight()); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/FlipCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/FlipCommand.php new file mode 100644 index 0000000..aa8f230 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/FlipCommand.php @@ -0,0 +1,37 @@ +argument(0)->value('h'); + + $size = $image->getSize(); + $dst = clone $size; + + switch (strtolower($mode)) { + case 2: + case 'v': + case 'vert': + case 'vertical': + $size->pivot->y = $size->height - 1; + $size->height = $size->height * (-1); + break; + + default: + $size->pivot->x = $size->width - 1; + $size->width = $size->width * (-1); + break; + } + + return $this->modify($image, 0, 0, $size->pivot->x, $size->pivot->y, $dst->width, $dst->height, $size->width, $size->height); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/GammaCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/GammaCommand.php new file mode 100644 index 0000000..7de0fb8 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/GammaCommand.php @@ -0,0 +1,21 @@ +argument(0)->type('numeric')->required()->value(); + + return imagegammacorrect($image->getCore(), 1, $gamma); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/GetSizeCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/GetSizeCommand.php new file mode 100644 index 0000000..9eb7e20 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/GetSizeCommand.php @@ -0,0 +1,25 @@ +setOutput(new Size( + imagesx($image->getCore()), + imagesy($image->getCore()) + )); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/GreyscaleCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/GreyscaleCommand.php new file mode 100644 index 0000000..12921e7 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/GreyscaleCommand.php @@ -0,0 +1,19 @@ +getCore(), IMG_FILTER_GRAYSCALE); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/HeightenCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/HeightenCommand.php new file mode 100644 index 0000000..d31e9cd --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/HeightenCommand.php @@ -0,0 +1,28 @@ +argument(0)->type('digit')->required()->value(); + $additionalConstraints = $this->argument(1)->type('closure')->value(); + + $this->arguments[0] = null; + $this->arguments[1] = $height; + $this->arguments[2] = function ($constraint) use ($additionalConstraints) { + $constraint->aspectRatio(); + if(is_callable($additionalConstraints)) + $additionalConstraints($constraint); + }; + + return parent::execute($image); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/InsertCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/InsertCommand.php new file mode 100644 index 0000000..47c7703 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/InsertCommand.php @@ -0,0 +1,34 @@ +argument(0)->required()->value(); + $position = $this->argument(1)->type('string')->value(); + $x = $this->argument(2)->type('digit')->value(0); + $y = $this->argument(3)->type('digit')->value(0); + + // build watermark + $watermark = $image->getDriver()->init($source); + + // define insertion point + $image_size = $image->getSize()->align($position, $x, $y); + $watermark_size = $watermark->getSize()->align($position); + $target = $image_size->relativePosition($watermark_size); + + // insert image at position + imagealphablending($image->getCore(), true); + return imagecopy($image->getCore(), $watermark->getCore(), $target->x, $target->y, 0, 0, $watermark_size->width, $watermark_size->height); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/InterlaceCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/InterlaceCommand.php new file mode 100644 index 0000000..e3461cb --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/InterlaceCommand.php @@ -0,0 +1,23 @@ +argument(0)->type('bool')->value(true); + + imageinterlace($image->getCore(), $mode); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/InvertCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/InvertCommand.php new file mode 100644 index 0000000..1a514f1 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/InvertCommand.php @@ -0,0 +1,19 @@ +getCore(), IMG_FILTER_NEGATE); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/LimitColorsCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/LimitColorsCommand.php new file mode 100644 index 0000000..0baed7e --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/LimitColorsCommand.php @@ -0,0 +1,53 @@ +argument(0)->value(); + $matte = $this->argument(1)->value(); + + // get current image size + $size = $image->getSize(); + + // create empty canvas + $resource = imagecreatetruecolor($size->width, $size->height); + + // define matte + if (is_null($matte)) { + $matte = imagecolorallocatealpha($resource, 255, 255, 255, 127); + } else { + $matte = $image->getDriver()->parseColor($matte)->getInt(); + } + + // fill with matte and copy original image + imagefill($resource, 0, 0, $matte); + + // set transparency + imagecolortransparent($resource, $matte); + + // copy original image + imagecopy($resource, $image->getCore(), 0, 0, 0, 0, $size->width, $size->height); + + if (is_numeric($count) && $count <= 256) { + // decrease colors + imagetruecolortopalette($resource, true, $count); + } + + // set new resource + $image->setCore($resource); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/MaskCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/MaskCommand.php new file mode 100644 index 0000000..4c61429 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/MaskCommand.php @@ -0,0 +1,83 @@ +argument(0)->value(); + $mask_w_alpha = $this->argument(1)->type('bool')->value(false); + + $image_size = $image->getSize(); + + // create empty canvas + $canvas = $image->getDriver()->newImage($image_size->width, $image_size->height, [0,0,0,0]); + + // build mask image from source + $mask = $image->getDriver()->init($mask_source); + $mask_size = $mask->getSize(); + + // resize mask to size of current image (if necessary) + if ($mask_size != $image_size) { + $mask->resize($image_size->width, $image_size->height); + } + + imagealphablending($canvas->getCore(), false); + + if ( ! $mask_w_alpha) { + // mask from greyscale image + imagefilter($mask->getCore(), IMG_FILTER_GRAYSCALE); + } + + // redraw old image pixel by pixel considering alpha map + for ($x=0; $x < $image_size->width; $x++) { + for ($y=0; $y < $image_size->height; $y++) { + + $color = $image->pickColor($x, $y, 'array'); + $alpha = $mask->pickColor($x, $y, 'array'); + + if ($mask_w_alpha) { + $alpha = $alpha[3]; // use alpha channel as mask + } else { + + if ($alpha[3] == 0) { // transparent as black + $alpha = 0; + } else { + + // $alpha = floatval(round((($alpha[0] + $alpha[1] + $alpha[3]) / 3) / 255, 2)); + + // image is greyscale, so channel doesn't matter (use red channel) + $alpha = floatval(round($alpha[0] / 255, 2)); + } + + } + + // preserve alpha of original image... + if ($color[3] < $alpha) { + $alpha = $color[3]; + } + + // replace alpha value + $color[3] = $alpha; + + // redraw pixel + $canvas->pixel($color, $x, $y); + } + } + + + // replace current image with masked instance + $image->setCore($canvas->getCore()); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/OpacityCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/OpacityCommand.php new file mode 100644 index 0000000..48492d2 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/OpacityCommand.php @@ -0,0 +1,31 @@ +argument(0)->between(0, 100)->required()->value(); + + // get size of image + $size = $image->getSize(); + + // build temp alpha mask + $mask_color = sprintf('rgba(0, 0, 0, %.1F)', $transparency / 100); + $mask = $image->getDriver()->newImage($size->width, $size->height, $mask_color); + + // mask image + $image->mask($mask->getCore(), true); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/PickColorCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/PickColorCommand.php new file mode 100644 index 0000000..bad96f4 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/PickColorCommand.php @@ -0,0 +1,37 @@ +argument(0)->type('digit')->required()->value(); + $y = $this->argument(1)->type('digit')->required()->value(); + $format = $this->argument(2)->type('string')->value('array'); + + // pick color + $color = imagecolorat($image->getCore(), $x, $y); + + if ( ! imageistruecolor($image->getCore())) { + $color = imagecolorsforindex($image->getCore(), $color); + $color['alpha'] = round(1 - $color['alpha'] / 127, 2); + } + + $color = new Color($color); + + // format to output + $this->setOutput($color->format($format)); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/PixelCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/PixelCommand.php new file mode 100644 index 0000000..2a90ce3 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/PixelCommand.php @@ -0,0 +1,25 @@ +argument(0)->required()->value(); + $color = new Color($color); + $x = $this->argument(1)->type('digit')->required()->value(); + $y = $this->argument(2)->type('digit')->required()->value(); + + return imagesetpixel($image->getCore(), $x, $y, $color->getInt()); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/PixelateCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/PixelateCommand.php new file mode 100644 index 0000000..0934797 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/PixelateCommand.php @@ -0,0 +1,21 @@ +argument(0)->type('digit')->value(10); + + return imagefilter($image->getCore(), IMG_FILTER_PIXELATE, $size, true); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ResetCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ResetCommand.php new file mode 100644 index 0000000..a68b75e --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ResetCommand.php @@ -0,0 +1,39 @@ +argument(0)->value(); + $backup = $image->getBackup($backupName); + + if (is_resource($backup) || $backup instanceof \GdImage) { + + // destroy current resource + imagedestroy($image->getCore()); + + // clone backup + $backup = $image->getDriver()->cloneCore($backup); + + // reset to new resource + $image->setCore($backup); + + return true; + } + + throw new RuntimeException( + "Backup not available. Call backup() before reset()." + ); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ResizeCanvasCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ResizeCanvasCommand.php new file mode 100644 index 0000000..73f3df3 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ResizeCanvasCommand.php @@ -0,0 +1,83 @@ +argument(0)->type('digit')->required()->value(); + $height = $this->argument(1)->type('digit')->required()->value(); + $anchor = $this->argument(2)->value('center'); + $relative = $this->argument(3)->type('boolean')->value(false); + $bgcolor = $this->argument(4)->value(); + + $original_width = $image->getWidth(); + $original_height = $image->getHeight(); + + // check of only width or height is set + $width = is_null($width) ? $original_width : intval($width); + $height = is_null($height) ? $original_height : intval($height); + + // check on relative width/height + if ($relative) { + $width = $original_width + $width; + $height = $original_height + $height; + } + + // check for negative width/height + $width = ($width <= 0) ? $width + $original_width : $width; + $height = ($height <= 0) ? $height + $original_height : $height; + + // create new canvas + $canvas = $image->getDriver()->newImage($width, $height, $bgcolor); + + // set copy position + $canvas_size = $canvas->getSize()->align($anchor); + $image_size = $image->getSize()->align($anchor); + $canvas_pos = $image_size->relativePosition($canvas_size); + $image_pos = $canvas_size->relativePosition($image_size); + + if ($width <= $original_width) { + $dst_x = 0; + $src_x = $canvas_pos->x; + $src_w = $canvas_size->width; + } else { + $dst_x = $image_pos->x; + $src_x = 0; + $src_w = $original_width; + } + + if ($height <= $original_height) { + $dst_y = 0; + $src_y = $canvas_pos->y; + $src_h = $canvas_size->height; + } else { + $dst_y = $image_pos->y; + $src_y = 0; + $src_h = $original_height; + } + + // make image area transparent to keep transparency + // even if background-color is set + $transparent = imagecolorallocatealpha($canvas->getCore(), 255, 255, 255, 127); + imagealphablending($canvas->getCore(), false); // do not blend / just overwrite + imagefilledrectangle($canvas->getCore(), $dst_x, $dst_y, $dst_x + $src_w - 1, $dst_y + $src_h - 1, $transparent); + + // copy image into new canvas + imagecopy($canvas->getCore(), $image->getCore(), $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h); + + // set new core to canvas + $image->setCore($canvas->getCore()); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ResizeCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ResizeCommand.php new file mode 100644 index 0000000..382d970 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ResizeCommand.php @@ -0,0 +1,84 @@ +argument(0)->value(); + $height = $this->argument(1)->value(); + $constraints = $this->argument(2)->type('closure')->value(); + + // resize box + $resized = $image->getSize()->resize($width, $height, $constraints); + + // modify image + $this->modify($image, 0, 0, 0, 0, $resized->getWidth(), $resized->getHeight(), $image->getWidth(), $image->getHeight()); + + return true; + } + + /** + * Wrapper function for 'imagecopyresampled' + * + * @param Image $image + * @param int $dst_x + * @param int $dst_y + * @param int $src_x + * @param int $src_y + * @param int $dst_w + * @param int $dst_h + * @param int $src_w + * @param int $src_h + * @return boolean + */ + protected function modify($image, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h) + { + // create new image + $modified = imagecreatetruecolor($dst_w, $dst_h); + + // get current image + $resource = $image->getCore(); + + // preserve transparency + $transIndex = imagecolortransparent($resource); + + if ($transIndex != -1) { + $rgba = imagecolorsforindex($modified, $transIndex); + $transColor = imagecolorallocatealpha($modified, $rgba['red'], $rgba['green'], $rgba['blue'], 127); + imagefill($modified, 0, 0, $transColor); + imagecolortransparent($modified, $transColor); + } else { + imagealphablending($modified, false); + imagesavealpha($modified, true); + } + + // copy content from resource + $result = imagecopyresampled( + $modified, + $resource, + $dst_x, + $dst_y, + $src_x, + $src_y, + $dst_w, + $dst_h, + $src_w, + $src_h + ); + + // set new content as recource + $image->setCore($modified); + + return $result; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/RotateCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/RotateCommand.php new file mode 100644 index 0000000..682ec0d --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/RotateCommand.php @@ -0,0 +1,30 @@ +argument(0)->type('numeric')->required()->value(); + $color = $this->argument(1)->value(); + $color = new Color($color); + + // restrict rotations beyond 360 degrees, since the end result is the same + $angle = fmod($angle, 360); + + // rotate image + $image->setCore(imagerotate($image->getCore(), $angle, $color->getInt())); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/SharpenCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/SharpenCommand.php new file mode 100644 index 0000000..782e565 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/SharpenCommand.php @@ -0,0 +1,34 @@ +argument(0)->between(0, 100)->value(10); + + // build matrix + $min = $amount >= 10 ? $amount * -0.01 : 0; + $max = $amount * -0.025; + $abs = ((4 * $min + 4 * $max) * -1) + 1; + $div = 1; + + $matrix = [ + [$min, $max, $min], + [$max, $abs, $max], + [$min, $max, $min] + ]; + + // apply the matrix + return imageconvolution($image->getCore(), $matrix, $div, 0); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/TrimCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/TrimCommand.php new file mode 100644 index 0000000..2e36975 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/TrimCommand.php @@ -0,0 +1,176 @@ +argument(0)->type('string')->value(); + $away = $this->argument(1)->value(); + $tolerance = $this->argument(2)->type('numeric')->value(0); + $feather = $this->argument(3)->type('numeric')->value(0); + + $width = $image->getWidth(); + $height = $image->getHeight(); + + // default values + $checkTransparency = false; + + // define borders to trim away + if (is_null($away)) { + $away = ['top', 'right', 'bottom', 'left']; + } elseif (is_string($away)) { + $away = [$away]; + } + + // lower border names + foreach ($away as $key => $value) { + $away[$key] = strtolower($value); + } + + // define base color position + switch (strtolower($base)) { + case 'transparent': + case 'trans': + $checkTransparency = true; + $base_x = 0; + $base_y = 0; + break; + + case 'bottom-right': + case 'right-bottom': + $base_x = $width - 1; + $base_y = $height - 1; + break; + + default: + case 'top-left': + case 'left-top': + $base_x = 0; + $base_y = 0; + break; + } + + // pick base color + if ($checkTransparency) { + $color = new Color; // color will only be used to compare alpha channel + } else { + $color = $image->pickColor($base_x, $base_y, 'object'); + } + + $top_x = 0; + $top_y = 0; + $bottom_x = $width; + $bottom_y = $height; + + // search upper part of image for colors to trim away + if (in_array('top', $away)) { + + for ($y=0; $y < ceil($height/2); $y++) { + for ($x=0; $x < $width; $x++) { + + $checkColor = $image->pickColor($x, $y, 'object'); + + if ($checkTransparency) { + $checkColor->r = $color->r; + $checkColor->g = $color->g; + $checkColor->b = $color->b; + } + + if ($color->differs($checkColor, $tolerance)) { + $top_y = max(0, $y - $feather); + break 2; + } + + } + } + + } + + // search left part of image for colors to trim away + if (in_array('left', $away)) { + + for ($x=0; $x < ceil($width/2); $x++) { + for ($y=$top_y; $y < $height; $y++) { + + $checkColor = $image->pickColor($x, $y, 'object'); + + if ($checkTransparency) { + $checkColor->r = $color->r; + $checkColor->g = $color->g; + $checkColor->b = $color->b; + } + + if ($color->differs($checkColor, $tolerance)) { + $top_x = max(0, $x - $feather); + break 2; + } + + } + } + + } + + // search lower part of image for colors to trim away + if (in_array('bottom', $away)) { + + for ($y=($height-1); $y >= floor($height/2)-1; $y--) { + for ($x=$top_x; $x < $width; $x++) { + + $checkColor = $image->pickColor($x, $y, 'object'); + + if ($checkTransparency) { + $checkColor->r = $color->r; + $checkColor->g = $color->g; + $checkColor->b = $color->b; + } + + if ($color->differs($checkColor, $tolerance)) { + $bottom_y = min($height, $y+1 + $feather); + break 2; + } + + } + } + + } + + // search right part of image for colors to trim away + if (in_array('right', $away)) { + + for ($x=($width-1); $x >= floor($width/2)-1; $x--) { + for ($y=$top_y; $y < $bottom_y; $y++) { + + $checkColor = $image->pickColor($x, $y, 'object'); + + if ($checkTransparency) { + $checkColor->r = $color->r; + $checkColor->g = $color->g; + $checkColor->b = $color->b; + } + + if ($color->differs($checkColor, $tolerance)) { + $bottom_x = min($width, $x+1 + $feather); + break 2; + } + + } + } + + } + + + // trim parts of image + return $this->modify($image, 0, 0, $top_x, $top_y, ($bottom_x-$top_x), ($bottom_y-$top_y), ($bottom_x-$top_x), ($bottom_y-$top_y)); + + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/WidenCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/WidenCommand.php new file mode 100644 index 0000000..43000d5 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/WidenCommand.php @@ -0,0 +1,28 @@ +argument(0)->type('digit')->required()->value(); + $additionalConstraints = $this->argument(1)->type('closure')->value(); + + $this->arguments[0] = $width; + $this->arguments[1] = null; + $this->arguments[2] = function ($constraint) use ($additionalConstraints) { + $constraint->aspectRatio(); + if(is_callable($additionalConstraints)) + $additionalConstraints($constraint); + }; + + return parent::execute($image); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Decoder.php b/vendor/intervention/image/src/Intervention/Image/Gd/Decoder.php new file mode 100644 index 0000000..f5c34aa --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Decoder.php @@ -0,0 +1,171 @@ +gdResourceToTruecolor($core); + + // build image + $image = $this->initFromGdResource($core); + $image->mime = $mime; + $image->setFileInfoFromPath($path); + + return $image; + } + + /** + * Initiates new image from GD resource + * + * @param Resource $resource + * @return \Intervention\Image\Image + */ + public function initFromGdResource($resource) + { + return new Image(new Driver, $resource); + } + + /** + * Initiates new image from Imagick object + * + * @param Imagick $object + * @return \Intervention\Image\Image + */ + public function initFromImagick(\Imagick $object) + { + throw new NotSupportedException( + "Gd driver is unable to init from Imagick object." + ); + } + + /** + * Initiates new image from binary data + * + * @param string $data + * @return \Intervention\Image\Image + */ + public function initFromBinary($binary) + { + $resource = @imagecreatefromstring($binary); + + if ($resource === false) { + throw new NotReadableException( + "Unable to init from given binary data." + ); + } + + $image = $this->initFromGdResource($resource); + $image->mime = finfo_buffer(finfo_open(FILEINFO_MIME_TYPE), $binary); + + return $image; + } + + /** + * Transform GD resource into Truecolor version + * + * @param resource $resource + * @return bool + */ + public function gdResourceToTruecolor(&$resource) + { + $width = imagesx($resource); + $height = imagesy($resource); + + // new canvas + $canvas = imagecreatetruecolor($width, $height); + + // fill with transparent color + imagealphablending($canvas, false); + $transparent = imagecolorallocatealpha($canvas, 255, 255, 255, 127); + imagefilledrectangle($canvas, 0, 0, $width, $height, $transparent); + imagecolortransparent($canvas, $transparent); + imagealphablending($canvas, true); + + // copy original + imagecopy($canvas, $resource, 0, 0, 0, 0, $width, $height); + imagedestroy($resource); + + $resource = $canvas; + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Driver.php b/vendor/intervention/image/src/Intervention/Image/Gd/Driver.php new file mode 100644 index 0000000..5f2f23e --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Driver.php @@ -0,0 +1,89 @@ +coreAvailable()) { + throw new NotSupportedException( + "GD Library extension not available with this PHP installation." + ); + } + + $this->decoder = $decoder ? $decoder : new Decoder; + $this->encoder = $encoder ? $encoder : new Encoder; + } + + /** + * Creates new image instance + * + * @param int $width + * @param int $height + * @param mixed $background + * @return \Intervention\Image\Image + */ + public function newImage($width, $height, $background = null) + { + // create empty resource + $core = imagecreatetruecolor($width, $height); + $image = new Image(new static, $core); + + // set background color + $background = new Color($background); + imagefill($image->getCore(), 0, 0, $background->getInt()); + + return $image; + } + + /** + * Reads given string into color object + * + * @param string $value + * @return AbstractColor + */ + public function parseColor($value) + { + return new Color($value); + } + + /** + * Checks if core module installation is available + * + * @return boolean + */ + protected function coreAvailable() + { + return (extension_loaded('gd') && function_exists('gd_info')); + } + + /** + * Returns clone of given core + * + * @return mixed + */ + public function cloneCore($core) + { + $width = imagesx($core); + $height = imagesy($core); + $clone = imagecreatetruecolor($width, $height); + imagealphablending($clone, false); + imagesavealpha($clone, true); + $transparency = imagecolorallocatealpha($clone, 0, 0, 0, 127); + imagefill($clone, 0, 0, $transparency); + + imagecopy($clone, $core, 0, 0, 0, 0, $width, $height); + + return $clone; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Encoder.php b/vendor/intervention/image/src/Intervention/Image/Gd/Encoder.php new file mode 100644 index 0000000..559d60d --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Encoder.php @@ -0,0 +1,180 @@ +image->getCore(), null, $this->quality); + $this->image->mime = image_type_to_mime_type(IMAGETYPE_JPEG); + $buffer = ob_get_contents(); + ob_end_clean(); + + return $buffer; + } + + /** + * Processes and returns encoded image as PNG string + * + * @return string + */ + protected function processPng() + { + ob_start(); + $resource = $this->image->getCore(); + imagealphablending($resource, false); + imagesavealpha($resource, true); + imagepng($resource, null, -1); + $this->image->mime = image_type_to_mime_type(IMAGETYPE_PNG); + $buffer = ob_get_contents(); + ob_end_clean(); + + return $buffer; + } + + /** + * Processes and returns encoded image as GIF string + * + * @return string + */ + protected function processGif() + { + ob_start(); + imagegif($this->image->getCore()); + $this->image->mime = image_type_to_mime_type(IMAGETYPE_GIF); + $buffer = ob_get_contents(); + ob_end_clean(); + + return $buffer; + } + + /** + * Processes and returns encoded image as WEBP string + * + * @return string + */ + protected function processWebp() + { + if ( ! function_exists('imagewebp')) { + throw new NotSupportedException( + "Webp format is not supported by PHP installation." + ); + } + + ob_start(); + imagepalettetotruecolor($this->image->getCore()); + imagealphablending($this->image->getCore(), true); + imagesavealpha($this->image->getCore(), true); + imagewebp($this->image->getCore(), null, $this->quality); + $this->image->mime = defined('IMAGETYPE_WEBP') ? image_type_to_mime_type(IMAGETYPE_WEBP) : 'image/webp'; + $buffer = ob_get_contents(); + ob_end_clean(); + + return $buffer; + } + + /** + * Processes and returns encoded image as TIFF string + * + * @return string + */ + protected function processTiff() + { + throw new NotSupportedException( + "TIFF format is not supported by Gd Driver." + ); + } + + /** + * Processes and returns encoded image as BMP string + * + * @return string + */ + protected function processBmp() + { + if ( ! function_exists('imagebmp')) { + throw new NotSupportedException( + "BMP format is not supported by PHP installation." + ); + } + + ob_start(); + imagebmp($this->image->getCore()); + $this->image->mime = defined('IMAGETYPE_BMP') ? image_type_to_mime_type(IMAGETYPE_BMP) : 'image/bmp'; + $buffer = ob_get_contents(); + ob_end_clean(); + + return $buffer; + } + + /** + * Processes and returns encoded image as ICO string + * + * @return string + */ + protected function processIco() + { + throw new NotSupportedException( + "ICO format is not supported by Gd Driver." + ); + } + + /** + * Processes and returns encoded image as PSD string + * + * @return string + */ + protected function processPsd() + { + throw new NotSupportedException( + "PSD format is not supported by Gd Driver." + ); + } + + /** + * Processes and returns encoded image as AVIF string + * + * @return string + */ + protected function processAvif() + { + if ( ! function_exists('imageavif')) { + throw new NotSupportedException( + "AVIF format is not supported by PHP installation." + ); + } + + ob_start(); + $resource = $this->image->getCore(); + imagepalettetotruecolor($resource); + imagealphablending($resource, true); + imagesavealpha($resource, true); + imageavif($resource, null, $this->quality); + $this->image->mime = defined('IMAGETYPE_AVIF') ? image_type_to_mime_type(IMAGETYPE_AVIF) : 'image/avif'; + $buffer = ob_get_contents(); + ob_end_clean(); + + return $buffer; + } + + /** + * Processes and returns encoded image as HEIC string + * + * @return string + */ + protected function processHeic() + { + throw new NotSupportedException( + "HEIC format is not supported by Gd Driver." + ); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Font.php b/vendor/intervention/image/src/Intervention/Image/Gd/Font.php new file mode 100644 index 0000000..3f58887 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Font.php @@ -0,0 +1,277 @@ +size * 0.75)); + } + + /** + * Filter function to access internal integer font values + * + * @return int + */ + private function getInternalFont() + { + $internalfont = is_null($this->file) ? 1 : $this->file; + $internalfont = is_numeric($internalfont) ? $internalfont : false; + + if ( ! in_array($internalfont, [1, 2, 3, 4, 5])) { + throw new NotSupportedException( + sprintf('Internal GD font (%s) not available. Use only 1-5.', $internalfont) + ); + } + + return intval($internalfont); + } + + /** + * Get width of an internal font character + * + * @return int + */ + private function getInternalFontWidth() + { + return $this->getInternalFont() + 4; + } + + /** + * Get height of an internal font character + * + * @return int + */ + private function getInternalFontHeight() + { + switch ($this->getInternalFont()) { + case 1: + return 8; + + case 2: + return 14; + + case 3: + return 14; + + case 4: + return 16; + + case 5: + return 16; + } + } + + /** + * Calculates bounding box of current font setting + * + * @return Array + */ + public function getBoxSize() + { + $box = []; + + if ($this->hasApplicableFontFile()) { + + // imagettfbbox() converts numeric entities to their respective + // character. Preserve any originally double encoded entities to be + // represented as is. + // eg: &#160; will render   rather than its character. + $this->text = preg_replace('/&(#(?:x[a-fA-F0-9]+|[0-9]+);)/', '&\1', $this->text); + $this->text = mb_encode_numericentity($this->text, array(0x0080, 0xffff, 0, 0xffff), 'UTF-8'); + + // get bounding box with angle 0 + $box = imagettfbbox($this->getPointSize(), 0, $this->file, $this->text); + + // rotate points manually + if ($this->angle != 0) { + + $angle = pi() * 2 - $this->angle * pi() * 2 / 360; + + for ($i=0; $i<4; $i++) { + $x = $box[$i * 2]; + $y = $box[$i * 2 + 1]; + $box[$i * 2] = cos($angle) * $x - sin($angle) * $y; + $box[$i * 2 + 1] = sin($angle) * $x + cos($angle) * $y; + } + } + + $box['width'] = intval(abs($box[4] - $box[0])); + $box['height'] = intval(abs($box[5] - $box[1])); + + } else { + + // get current internal font size + $width = $this->getInternalFontWidth(); + $height = $this->getInternalFontHeight(); + + if (strlen($this->text) == 0) { + // no text -> no boxsize + $box['width'] = 0; + $box['height'] = 0; + } else { + // calculate boxsize + $box['width'] = strlen($this->text) * $width; + $box['height'] = $height; + } + } + + return $box; + } + + /** + * Draws font to given image at given position + * + * @param Image $image + * @param int $posx + * @param int $posy + * @return void + */ + public function applyToImage(Image $image, $posx = 0, $posy = 0) + { + // parse text color + $color = new Color($this->color); + + if ($this->hasApplicableFontFile()) { + + if ($this->angle != 0 || is_string($this->align) || is_string($this->valign)) { + + $box = $this->getBoxSize(); + + $align = is_null($this->align) ? 'left' : strtolower($this->align); + $valign = is_null($this->valign) ? 'bottom' : strtolower($this->valign); + + // correction on position depending on v/h alignment + switch ($align.'-'.$valign) { + + case 'center-top': + $posx = $posx - round(($box[6]+$box[4])/2); + $posy = $posy - round(($box[7]+$box[5])/2); + break; + + case 'right-top': + $posx = $posx - $box[4]; + $posy = $posy - $box[5]; + break; + + case 'left-top': + $posx = $posx - $box[6]; + $posy = $posy - $box[7]; + break; + + case 'center-center': + case 'center-middle': + $posx = $posx - round(($box[0]+$box[4])/2); + $posy = $posy - round(($box[1]+$box[5])/2); + break; + + case 'right-center': + case 'right-middle': + $posx = $posx - round(($box[2]+$box[4])/2); + $posy = $posy - round(($box[3]+$box[5])/2); + break; + + case 'left-center': + case 'left-middle': + $posx = $posx - round(($box[0]+$box[6])/2); + $posy = $posy - round(($box[1]+$box[7])/2); + break; + + case 'center-bottom': + $posx = $posx - round(($box[0]+$box[2])/2); + $posy = $posy - round(($box[1]+$box[3])/2); + break; + + case 'right-bottom': + $posx = $posx - $box[2]; + $posy = $posy - $box[3]; + break; + + case 'left-bottom': + $posx = $posx - $box[0]; + $posy = $posy - $box[1]; + break; + } + } + + // enable alphablending for imagettftext + imagealphablending($image->getCore(), true); + + // draw ttf text + imagettftext($image->getCore(), $this->getPointSize(), $this->angle, $posx, $posy, $color->getInt(), $this->file, $this->text); + + } else { + + // get box size + $box = $this->getBoxSize(); + $width = $box['width']; + $height = $box['height']; + + // internal font specific position corrections + if ($this->getInternalFont() == 1) { + $top_correction = 1; + $bottom_correction = 2; + } elseif ($this->getInternalFont() == 3) { + $top_correction = 2; + $bottom_correction = 4; + } else { + $top_correction = 3; + $bottom_correction = 4; + } + + // x-position corrections for horizontal alignment + switch (strtolower($this->align)) { + case 'center': + $posx = ceil($posx - ($width / 2)); + break; + + case 'right': + $posx = ceil($posx - $width) + 1; + break; + } + + // y-position corrections for vertical alignment + switch (strtolower($this->valign)) { + case 'center': + case 'middle': + $posy = ceil($posy - ($height / 2)); + break; + + case 'top': + $posy = ceil($posy - $top_correction); + break; + + default: + case 'bottom': + $posy = round($posy - $height + $bottom_correction); + break; + } + + // draw text + imagestring($image->getCore(), $this->getInternalFont(), $posx, $posy, $this->text, $color->getInt()); + } + } + + /** + * Set text kerning + * + * @param string $kerning + * @return void + */ + public function kerning($kerning) + { + throw new \Intervention\Image\Exception\NotSupportedException( + "Kerning is not supported by GD driver." + ); + } + +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/CircleShape.php b/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/CircleShape.php new file mode 100644 index 0000000..c3c4214 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/CircleShape.php @@ -0,0 +1,40 @@ +width = is_numeric($diameter) ? intval($diameter) : $this->diameter; + $this->height = is_numeric($diameter) ? intval($diameter) : $this->diameter; + $this->diameter = is_numeric($diameter) ? intval($diameter) : $this->diameter; + } + + /** + * Draw current circle on given image + * + * @param Image $image + * @param int $x + * @param int $y + * @return boolean + */ + public function applyToImage(Image $image, $x = 0, $y = 0) + { + return parent::applyToImage($image, $x, $y); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/EllipseShape.php b/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/EllipseShape.php new file mode 100644 index 0000000..78e5e4a --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/EllipseShape.php @@ -0,0 +1,65 @@ +width = is_numeric($width) ? intval($width) : $this->width; + $this->height = is_numeric($height) ? intval($height) : $this->height; + } + + /** + * Draw ellipse instance on given image + * + * @param Image $image + * @param int $x + * @param int $y + * @return boolean + */ + public function applyToImage(Image $image, $x = 0, $y = 0) + { + // parse background color + $background = new Color($this->background); + + if ($this->hasBorder()) { + // slightly smaller ellipse to keep 1px bordered edges clean + imagefilledellipse($image->getCore(), $x, $y, $this->width-1, $this->height-1, $background->getInt()); + + $border_color = new Color($this->border_color); + imagesetthickness($image->getCore(), $this->border_width); + + // gd's imageellipse doesn't respect imagesetthickness so i use imagearc with 359.9 degrees here + imagearc($image->getCore(), $x, $y, $this->width, $this->height, 0, 359.99, $border_color->getInt()); + } else { + imagefilledellipse($image->getCore(), $x, $y, $this->width, $this->height, $background->getInt()); + } + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/LineShape.php b/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/LineShape.php new file mode 100644 index 0000000..ea38b51 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/LineShape.php @@ -0,0 +1,90 @@ +x = is_numeric($x) ? intval($x) : $this->x; + $this->y = is_numeric($y) ? intval($y) : $this->y; + } + + /** + * Set current line color + * + * @param string $color + * @return void + */ + public function color($color) + { + $this->color = $color; + } + + /** + * Set current line width in pixels + * + * @param int $width + * @return void + */ + public function width($width) + { + throw new \Intervention\Image\Exception\NotSupportedException( + "Line width is not supported by GD driver." + ); + } + + /** + * Draw current instance of line to given endpoint on given image + * + * @param Image $image + * @param int $x + * @param int $y + * @return boolean + */ + public function applyToImage(Image $image, $x = 0, $y = 0) + { + $color = new Color($this->color); + imageline($image->getCore(), $x, $y, $this->x, $this->y, $color->getInt()); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/PolygonShape.php b/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/PolygonShape.php new file mode 100644 index 0000000..5e14df4 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/PolygonShape.php @@ -0,0 +1,49 @@ +points = $points; + } + + /** + * Draw polygon on given image + * + * @param Image $image + * @param int $x + * @param int $y + * @return boolean + */ + public function applyToImage(Image $image, $x = 0, $y = 0) + { + $background = new Color($this->background); + imagefilledpolygon($image->getCore(), $this->points, intval(count($this->points) / 2), $background->getInt()); + + if ($this->hasBorder()) { + $border_color = new Color($this->border_color); + imagesetthickness($image->getCore(), $this->border_width); + imagepolygon($image->getCore(), $this->points, intval(count($this->points) / 2), $border_color->getInt()); + } + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/RectangleShape.php b/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/RectangleShape.php new file mode 100644 index 0000000..5f69a7f --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/RectangleShape.php @@ -0,0 +1,76 @@ +x1 = is_numeric($x1) ? intval($x1) : $this->x1; + $this->y1 = is_numeric($y1) ? intval($y1) : $this->y1; + $this->x2 = is_numeric($x2) ? intval($x2) : $this->x2; + $this->y2 = is_numeric($y2) ? intval($y2) : $this->y2; + } + + /** + * Draw rectangle to given image at certain position + * + * @param Image $image + * @param int $x + * @param int $y + * @return boolean + */ + public function applyToImage(Image $image, $x = 0, $y = 0) + { + $background = new Color($this->background); + imagefilledrectangle($image->getCore(), $this->x1, $this->y1, $this->x2, $this->y2, $background->getInt()); + + if ($this->hasBorder()) { + $border_color = new Color($this->border_color); + imagesetthickness($image->getCore(), $this->border_width); + imagerectangle($image->getCore(), $this->x1, $this->y1, $this->x2, $this->y2, $border_color->getInt()); + } + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Image.php b/vendor/intervention/image/src/Intervention/Image/Image.php new file mode 100644 index 0000000..7d5a59b --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Image.php @@ -0,0 +1,370 @@ +driver = $driver; + $this->core = $core; + } + + /** + * Magic method to catch all image calls + * usually any AbstractCommand + * + * @param string $name + * @param Array $arguments + * @return mixed + */ + public function __call($name, $arguments) + { + $command = $this->driver->executeCommand($this, $name, $arguments); + return $command->hasOutput() ? $command->getOutput() : $this; + } + + /** + * Starts encoding of current image + * + * @param string $format + * @param int $quality + * @return \Intervention\Image\Image + */ + public function encode($format = null, $quality = 90) + { + return $this->driver->encode($this, $format, $quality); + } + + /** + * Saves encoded image in filesystem + * + * @param string $path + * @param int $quality + * @param string $format + * @return \Intervention\Image\Image + */ + public function save($path = null, $quality = null, $format = null) + { + $path = is_null($path) ? $this->basePath() : $path; + + if (is_null($path)) { + throw new NotWritableException( + "Can't write to undefined path." + ); + } + + if ($format === null) { + $format = pathinfo($path, PATHINFO_EXTENSION); + } + + $data = $this->encode($format, $quality); + $saved = @file_put_contents($path, $data); + + if ($saved === false) { + throw new NotWritableException( + "Can't write image data to path ({$path})" + ); + } + + // set new file info + $this->setFileInfoFromPath($path); + + return $this; + } + + /** + * Runs a given filter on current image + * + * @param FiltersFilterInterface $filter + * @return \Intervention\Image\Image + */ + public function filter(Filters\FilterInterface $filter) + { + return $filter->applyFilter($this); + } + + /** + * Returns current image driver + * + * @return \Intervention\Image\AbstractDriver + */ + public function getDriver() + { + return $this->driver; + } + + /** + * Sets current image driver + * @param AbstractDriver $driver + */ + public function setDriver(AbstractDriver $driver) + { + $this->driver = $driver; + + return $this; + } + + /** + * Returns current image resource/obj + * + * @return mixed + */ + public function getCore() + { + return $this->core; + } + + /** + * Sets current image resource + * + * @param mixed $core + */ + public function setCore($core) + { + $this->core = $core; + + return $this; + } + + /** + * Returns current image backup + * + * @param string $name + * @return mixed + */ + public function getBackup($name = null) + { + $name = is_null($name) ? 'default' : $name; + + if ( ! $this->backupExists($name)) { + throw new RuntimeException( + "Backup with name ({$name}) not available. Call backup() before reset()." + ); + } + + return $this->backups[$name]; + } + + /** + * Returns all backups attached to image + * + * @return array + */ + public function getBackups() + { + return $this->backups; + } + + /** + * Sets current image backup + * + * @param mixed $resource + * @param string $name + * @return self + */ + public function setBackup($resource, $name = null) + { + $name = is_null($name) ? 'default' : $name; + + $this->backups[$name] = $resource; + + return $this; + } + + /** + * Checks if named backup exists + * + * @param string $name + * @return bool + */ + private function backupExists($name) + { + return array_key_exists($name, $this->backups); + } + + /** + * Checks if current image is already encoded + * + * @return boolean + */ + public function isEncoded() + { + return ! empty($this->encoded); + } + + /** + * Returns encoded image data of current image + * + * @return string + */ + public function getEncoded() + { + return $this->encoded; + } + + /** + * Sets encoded image buffer + * + * @param string $value + */ + public function setEncoded($value) + { + $this->encoded = $value; + + return $this; + } + + /** + * Calculates current image width + * + * @return int + */ + public function getWidth() + { + return $this->getSize()->width; + } + + /** + * Alias of getWidth() + * + * @return int + */ + public function width() + { + return $this->getWidth(); + } + + /** + * Calculates current image height + * + * @return int + */ + public function getHeight() + { + return $this->getSize()->height; + } + + /** + * Alias of getHeight + * + * @return int + */ + public function height() + { + return $this->getHeight(); + } + + /** + * Reads mime type + * + * @return string + */ + public function mime() + { + return $this->mime; + } + + /** + * Returns encoded image data in string conversion + * + * @return string + */ + public function __toString() + { + return $this->encoded; + } + + /** + * Cloning an image + */ + public function __clone() + { + $this->core = $this->driver->cloneCore($this->core); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/ImageManager.php b/vendor/intervention/image/src/Intervention/Image/ImageManager.php new file mode 100644 index 0000000..324fe7f --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/ImageManager.php @@ -0,0 +1,142 @@ + 'gd' + ]; + + /** + * Creates new instance of Image Manager + * + * @param array $config + */ + public function __construct(array $config = []) + { + $this->checkRequirements(); + $this->configure($config); + } + + /** + * Overrides configuration settings + * + * @param array $config + * + * @return self + */ + public function configure(array $config = []) + { + $this->config = array_replace($this->config, $config); + + return $this; + } + + /** + * Initiates an Image instance from different input types + * + * @param mixed $data + * + * @return \Intervention\Image\Image + */ + public function make($data) + { + return $this->createDriver()->init($data); + } + + /** + * Creates an empty image canvas + * + * @param int $width + * @param int $height + * @param mixed $background + * + * @return \Intervention\Image\Image + */ + public function canvas($width, $height, $background = null) + { + return $this->createDriver()->newImage($width, $height, $background); + } + + /** + * Create new cached image and run callback + * (requires additional package intervention/imagecache) + * + * @param Closure $callback + * @param int $lifetime + * @param boolean $returnObj + * + * @return Image + */ + public function cache(Closure $callback, $lifetime = null, $returnObj = false) + { + if (class_exists('Intervention\\Image\\ImageCache')) { + // create imagecache + $imagecache = new ImageCache($this); + + // run callback + if (is_callable($callback)) { + $callback($imagecache); + } + + return $imagecache->get($lifetime, $returnObj); + } + + throw new MissingDependencyException( + "Please install package intervention/imagecache before running this function." + ); + } + + /** + * Creates a driver instance according to config settings + * + * @return \Intervention\Image\AbstractDriver + */ + private function createDriver() + { + if (is_string($this->config['driver'])) { + $drivername = ucfirst($this->config['driver']); + $driverclass = sprintf('Intervention\\Image\\%s\\Driver', $drivername); + + if (class_exists($driverclass)) { + return new $driverclass; + } + + throw new NotSupportedException( + "Driver ({$drivername}) could not be instantiated." + ); + } + + if ($this->config['driver'] instanceof AbstractDriver) { + return $this->config['driver']; + } + + throw new NotSupportedException( + "Unknown driver type." + ); + } + + /** + * Check if all requirements are available + * + * @return void + */ + private function checkRequirements() + { + if ( ! function_exists('finfo_buffer')) { + throw new MissingDependencyException( + "PHP Fileinfo extension must be installed/enabled to use Intervention Image." + ); + } + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/ImageManagerStatic.php b/vendor/intervention/image/src/Intervention/Image/ImageManagerStatic.php new file mode 100644 index 0000000..a1b5642 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/ImageManagerStatic.php @@ -0,0 +1,88 @@ +configure($config); + } + + /** + * Statically initiates an Image instance from different input types + * + * @param mixed $data + * + * @return \Intervention\Image\Image + * @throws \Intervention\Image\Exception\NotReadableException + */ + public static function make($data) + { + return self::getManager()->make($data); + } + + /** + * Statically creates an empty image canvas + * + * @param int $width + * @param int $height + * @param mixed $background + * + * @return \Intervention\Image\Image + */ + public static function canvas($width, $height, $background = null) + { + return self::getManager()->canvas($width, $height, $background); + } + + /** + * Create new cached image and run callback statically + * + * @param Closure $callback + * @param int $lifetime + * @param boolean $returnObj + * + * @return mixed + */ + public static function cache(Closure $callback, $lifetime = null, $returnObj = false) + { + return self::getManager()->cache($callback, $lifetime, $returnObj); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/ImageServiceProvider.php b/vendor/intervention/image/src/Intervention/Image/ImageServiceProvider.php new file mode 100644 index 0000000..f99fe4a --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/ImageServiceProvider.php @@ -0,0 +1,87 @@ +provider = $this->getProvider(); + } + + /** + * Bootstrap the application events. + * + * @return void + */ + public function boot() + { + if (method_exists($this->provider, 'boot')) { + return $this->provider->boot(); + } + } + + /** + * Register the service provider. + * + * @return void + */ + public function register() + { + return $this->provider->register(); + } + + /** + * Return ServiceProvider according to Laravel version + * + * @return \Intervention\Image\Provider\ProviderInterface + */ + private function getProvider() + { + if ($this->app instanceof LumenApplication) { + $provider = '\Intervention\Image\ImageServiceProviderLumen'; + } elseif (version_compare(IlluminateApplication::VERSION, '5.0', '<')) { + $provider = '\Intervention\Image\ImageServiceProviderLaravel4'; + } else { + $provider = '\Intervention\Image\ImageServiceProviderLaravelRecent'; + } + + return new $provider($this->app); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return ['image']; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/ImageServiceProviderLaravel4.php b/vendor/intervention/image/src/Intervention/Image/ImageServiceProviderLaravel4.php new file mode 100644 index 0000000..3b1388f --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/ImageServiceProviderLaravel4.php @@ -0,0 +1,112 @@ +package('intervention/image'); + + // try to create imagecache route only if imagecache is present + if (class_exists('Intervention\\Image\\ImageCache')) { + + $app = $this->app; + + // load imagecache config + $app['config']->package('intervention/imagecache', __DIR__.'/../../../../imagecache/src/config', 'imagecache'); + $config = $app['config']; + + // create dynamic manipulation route + if (is_string($config->get('imagecache::route'))) { + + // add original to route templates + $config->set('imagecache::templates.original', null); + + // setup image manipulator route + $app['router']->get($config->get('imagecache::route').'/{template}/{filename}', ['as' => 'imagecache', function ($template, $filename) use ($app, $config) { + + // disable session cookies for image route + $app['config']->set('session.driver', 'array'); + + // find file + foreach ($config->get('imagecache::paths') as $path) { + // don't allow '..' in filenames + $image_path = $path.'/'.str_replace('..', '', $filename); + if (file_exists($image_path) && is_file($image_path)) { + break; + } else { + $image_path = false; + } + } + + // abort if file not found + if ($image_path === false) { + $app->abort(404); + } + + // define template callback + $callback = $config->get("imagecache::templates.{$template}"); + + if (is_callable($callback) || class_exists($callback)) { + + // image manipulation based on callback + $content = $app['image']->cache(function ($image) use ($image_path, $callback) { + + switch (true) { + case is_callable($callback): + return $callback($image->make($image_path)); + break; + + case class_exists($callback): + return $image->make($image_path)->filter(new $callback); + break; + } + + }, $config->get('imagecache::lifetime')); + + } else { + + // get original image file contents + $content = file_get_contents($image_path); + } + + // define mime type + $mime = finfo_buffer(finfo_open(FILEINFO_MIME_TYPE), $content); + + // return http response + return new IlluminateResponse($content, 200, [ + 'Content-Type' => $mime, + 'Cache-Control' => 'max-age='.($config->get('imagecache::lifetime')*60).', public', + 'Etag' => md5($content) + ]); + + }])->where(['template' => join('|', array_keys($config->get('imagecache::templates'))), 'filename' => '[ \w\\.\\/\\-]+']); + } + } + } + + /** + * Register the service provider. + * + * @return void + */ + public function register() + { + $app = $this->app; + + $app['image'] = $app->share(function ($app) { + return new ImageManager($app['config']->get('image::config')); + }); + + $app->alias('image', 'Intervention\Image\ImageManager'); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/ImageServiceProviderLaravelRecent.php b/vendor/intervention/image/src/Intervention/Image/ImageServiceProviderLaravelRecent.php new file mode 100644 index 0000000..17a84d0 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/ImageServiceProviderLaravelRecent.php @@ -0,0 +1,106 @@ +publishes([ + __DIR__.'/../../config/config.php' => config_path('image.php') + ]); + + // setup intervention/imagecache if package is installed + $this->cacheIsInstalled() ? $this->bootstrapImageCache() : null; + } + + /** + * Register the service provider. + * + * @return void + */ + public function register() + { + $app = $this->app; + + // merge default config + $this->mergeConfigFrom( + __DIR__.'/../../config/config.php', + 'image' + ); + + // create image + $app->singleton('image', function ($app) { + return new ImageManager($this->getImageConfig($app)); + }); + + $app->alias('image', 'Intervention\Image\ImageManager'); + } + + /** + * Bootstrap imagecache + * + * @return void + */ + protected function bootstrapImageCache() + { + $app = $this->app; + $config = __DIR__.'/../../../../imagecache/src/config/config.php'; + + $this->publishes([ + $config => config_path('imagecache.php') + ]); + + // merge default config + $this->mergeConfigFrom( + $config, + 'imagecache' + ); + + // imagecache route + if (is_string(config('imagecache.route'))) { + + $filename_pattern = '[ \w\\.\\/\\-\\@\(\)\=]+'; + + // route to access template applied image file + $app['router']->get(config('imagecache.route').'/{template}/{filename}', [ + 'uses' => 'Intervention\Image\ImageCacheController@getResponse', + 'as' => 'imagecache' + ])->where(['filename' => $filename_pattern]); + } + } + + /** + * Return image configuration as array + * + * @param Application $app + * @return array + */ + private function getImageConfig($app) + { + $config = $app['config']->get('image'); + + if (is_null($config)) { + return []; + } + + return $config; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/ImageServiceProviderLeague.php b/vendor/intervention/image/src/Intervention/Image/ImageServiceProviderLeague.php new file mode 100644 index 0000000..b756a61 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/ImageServiceProviderLeague.php @@ -0,0 +1,42 @@ +config = $config; + } + + /** + * Register the server provider. + * + * @return void + */ + public function register() + { + $this->getContainer()->share('Intervention\Image\ImageManager', function () { + return new ImageManager($this->config); + }); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/ImageServiceProviderLumen.php b/vendor/intervention/image/src/Intervention/Image/ImageServiceProviderLumen.php new file mode 100644 index 0000000..4a381cc --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/ImageServiceProviderLumen.php @@ -0,0 +1,34 @@ +app; + + // merge default config + $this->mergeConfigFrom( + __DIR__.'/../../config/config.php', + 'image' + ); + + // set configuration + $app->configure('image'); + + // create image + $app->singleton('image',function ($app) { + return new ImageManager($app['config']->get('image')); + }); + + $app->alias('image', 'Intervention\Image\ImageManager'); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Color.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Color.php new file mode 100644 index 0000000..a41ff3a --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Color.php @@ -0,0 +1,279 @@ +> 24) & 0xFF; + $r = ($value >> 16) & 0xFF; + $g = ($value >> 8) & 0xFF; + $b = $value & 0xFF; + $a = $this->rgb2alpha($a); + + $this->setPixel($r, $g, $b, $a); + } + + /** + * Initiates color object from given array + * + * @param array $value + * @return \Intervention\Image\AbstractColor + */ + public function initFromArray($array) + { + $array = array_values($array); + + if (count($array) == 4) { + + // color array with alpha value + list($r, $g, $b, $a) = $array; + + } elseif (count($array) == 3) { + + // color array without alpha value + list($r, $g, $b) = $array; + $a = 1; + } + + $this->setPixel($r, $g, $b, $a); + } + + /** + * Initiates color object from given string + * + * @param string $value + * + * @return \Intervention\Image\AbstractColor + */ + public function initFromString($value) + { + if ($color = $this->rgbaFromString($value)) { + $this->setPixel($color[0], $color[1], $color[2], $color[3]); + } + } + + /** + * Initiates color object from given ImagickPixel object + * + * @param ImagickPixel $value + * + * @return \Intervention\Image\AbstractColor + */ + public function initFromObject($value) + { + if (is_a($value, '\ImagickPixel')) { + $this->pixel = $value; + } + } + + /** + * Initiates color object from given R, G and B values + * + * @param int $r + * @param int $g + * @param int $b + * + * @return \Intervention\Image\AbstractColor + */ + public function initFromRgb($r, $g, $b) + { + $this->setPixel($r, $g, $b); + } + + /** + * Initiates color object from given R, G, B and A values + * + * @param int $r + * @param int $g + * @param int $b + * @param float $a + * + * @return \Intervention\Image\AbstractColor + */ + public function initFromRgba($r, $g, $b, $a) + { + $this->setPixel($r, $g, $b, $a); + } + + /** + * Calculates integer value of current color instance + * + * @return int + */ + public function getInt() + { + $r = $this->getRedValue(); + $g = $this->getGreenValue(); + $b = $this->getBlueValue(); + $a = intval(round($this->getAlphaValue() * 255)); + + return intval(($a << 24) + ($r << 16) + ($g << 8) + $b); + } + + /** + * Calculates hexadecimal value of current color instance + * + * @param string $prefix + * + * @return string + */ + public function getHex($prefix = '') + { + return sprintf('%s%02x%02x%02x', $prefix, + $this->getRedValue(), + $this->getGreenValue(), + $this->getBlueValue() + ); + } + + /** + * Calculates RGB(A) in array format of current color instance + * + * @return array + */ + public function getArray() + { + return [ + $this->getRedValue(), + $this->getGreenValue(), + $this->getBlueValue(), + $this->getAlphaValue() + ]; + } + + /** + * Calculates RGBA in string format of current color instance + * + * @return string + */ + public function getRgba() + { + return sprintf('rgba(%d, %d, %d, %.2F)', + $this->getRedValue(), + $this->getGreenValue(), + $this->getBlueValue(), + $this->getAlphaValue() + ); + } + + /** + * Determines if current color is different from given color + * + * @param AbstractColor $color + * @param int $tolerance + * @return boolean + */ + public function differs(AbstractColor $color, $tolerance = 0) + { + $color_tolerance = round($tolerance * 2.55); + $alpha_tolerance = round($tolerance); + + $delta = [ + 'r' => abs($color->getRedValue() - $this->getRedValue()), + 'g' => abs($color->getGreenValue() - $this->getGreenValue()), + 'b' => abs($color->getBlueValue() - $this->getBlueValue()), + 'a' => abs($color->getAlphaValue() - $this->getAlphaValue()) + ]; + + return ( + $delta['r'] > $color_tolerance or + $delta['g'] > $color_tolerance or + $delta['b'] > $color_tolerance or + $delta['a'] > $alpha_tolerance + ); + } + + /** + * Returns RGB red value of current color + * + * @return int + */ + public function getRedValue() + { + return intval(round($this->pixel->getColorValue(\Imagick::COLOR_RED) * 255)); + } + + /** + * Returns RGB green value of current color + * + * @return int + */ + public function getGreenValue() + { + return intval(round($this->pixel->getColorValue(\Imagick::COLOR_GREEN) * 255)); + } + + /** + * Returns RGB blue value of current color + * + * @return int + */ + public function getBlueValue() + { + return intval(round($this->pixel->getColorValue(\Imagick::COLOR_BLUE) * 255)); + } + + /** + * Returns RGB alpha value of current color + * + * @return float + */ + public function getAlphaValue() + { + return round($this->pixel->getColorValue(\Imagick::COLOR_ALPHA), 2); + } + + /** + * Initiates ImagickPixel from given RGBA values + * + * @return \ImagickPixel + */ + private function setPixel($r, $g, $b, $a = null) + { + $a = is_null($a) ? 1 : $a; + + return $this->pixel = new \ImagickPixel( + sprintf('rgba(%d, %d, %d, %.2F)', $r, $g, $b, $a) + ); + } + + /** + * Returns current color as ImagickPixel + * + * @return \ImagickPixel + */ + public function getPixel() + { + return $this->pixel; + } + + /** + * Calculates RGA integer alpha value into float value + * + * @param int $value + * @return float + */ + private function rgb2alpha($value) + { + // (255 -> 1.0) / (0 -> 0.0) + return (float) round($value/255, 2); + } + +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/BackupCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/BackupCommand.php new file mode 100644 index 0000000..76b4f72 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/BackupCommand.php @@ -0,0 +1,25 @@ +argument(0)->value(); + + // clone current image resource + $clone = clone $image; + $image->setBackup($clone->getCore(), $backupName); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/BlurCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/BlurCommand.php new file mode 100644 index 0000000..d2533e0 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/BlurCommand.php @@ -0,0 +1,21 @@ +argument(0)->between(0, 100)->value(1); + + return $image->getCore()->blurImage(1 * $amount, 0.5 * $amount); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/BrightnessCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/BrightnessCommand.php new file mode 100644 index 0000000..03ac847 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/BrightnessCommand.php @@ -0,0 +1,21 @@ +argument(0)->between(-100, 100)->required()->value(); + + return $image->getCore()->modulateImage(100 + $level, 100, 100); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ColorizeCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ColorizeCommand.php new file mode 100644 index 0000000..3a6506f --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ColorizeCommand.php @@ -0,0 +1,44 @@ +argument(0)->between(-100, 100)->required()->value(); + $green = $this->argument(1)->between(-100, 100)->required()->value(); + $blue = $this->argument(2)->between(-100, 100)->required()->value(); + + // normalize colorize levels + $red = $this->normalizeLevel($red); + $green = $this->normalizeLevel($green); + $blue = $this->normalizeLevel($blue); + + $qrange = $image->getCore()->getQuantumRange(); + + // apply + $image->getCore()->levelImage(0, $red, $qrange['quantumRangeLong'], \Imagick::CHANNEL_RED); + $image->getCore()->levelImage(0, $green, $qrange['quantumRangeLong'], \Imagick::CHANNEL_GREEN); + $image->getCore()->levelImage(0, $blue, $qrange['quantumRangeLong'], \Imagick::CHANNEL_BLUE); + + return true; + } + + private function normalizeLevel($level) + { + if ($level > 0) { + return $level/5; + } else { + return ($level+100)/100; + } + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ContrastCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ContrastCommand.php new file mode 100644 index 0000000..c4847c6 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ContrastCommand.php @@ -0,0 +1,21 @@ +argument(0)->between(-100, 100)->required()->value(); + + return $image->getCore()->sigmoidalContrastImage($level > 0, $level / 4, 0); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/CropCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/CropCommand.php new file mode 100644 index 0000000..618edea --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/CropCommand.php @@ -0,0 +1,45 @@ +argument(0)->type('digit')->required()->value(); + $height = $this->argument(1)->type('digit')->required()->value(); + $x = $this->argument(2)->type('digit')->value(); + $y = $this->argument(3)->type('digit')->value(); + + if (is_null($width) || is_null($height)) { + throw new InvalidArgumentException( + "Width and height of cutout needs to be defined." + ); + } + + $cropped = new Size($width, $height); + $position = new Point($x, $y); + + // align boxes + if (is_null($x) && is_null($y)) { + $position = $image->getSize()->align('center')->relativePosition($cropped->align('center')); + } + + // crop image core + $image->getCore()->cropImage($cropped->width, $cropped->height, $position->x, $position->y); + $image->getCore()->setImagePage(0,0,0,0); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/DestroyCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/DestroyCommand.php new file mode 100644 index 0000000..58c9556 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/DestroyCommand.php @@ -0,0 +1,27 @@ +getCore()->clear(); + + // destroy backups + foreach ($image->getBackups() as $backup) { + $backup->clear(); + } + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ExifCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ExifCommand.php new file mode 100644 index 0000000..521b38b --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ExifCommand.php @@ -0,0 +1,63 @@ +preferExtension = false; + } + + /** + * Read Exif data from the given image + * + * @param \Intervention\Image\Image $image + * @return boolean + */ + public function execute($image) + { + if ($this->preferExtension && function_exists('exif_read_data')) { + return parent::execute($image); + } + + $core = $image->getCore(); + + if ( ! method_exists($core, 'getImageProperties')) { + throw new NotSupportedException( + "Reading Exif data is not supported by this PHP installation." + ); + } + + $requestedKey = $this->argument(0)->value(); + if ($requestedKey !== null) { + $this->setOutput($core->getImageProperty('exif:' . $requestedKey)); + return true; + } + + $exif = []; + $properties = $core->getImageProperties(); + foreach ($properties as $key => $value) { + if (substr($key, 0, 5) !== 'exif:') { + continue; + } + + $exif[substr($key, 5)] = $value; + } + + $this->setOutput($exif); + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/FillCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/FillCommand.php new file mode 100644 index 0000000..82baac5 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/FillCommand.php @@ -0,0 +1,105 @@ +argument(0)->value(); + $x = $this->argument(1)->type('digit')->value(); + $y = $this->argument(2)->type('digit')->value(); + + $imagick = $image->getCore(); + + try { + // set image filling + $source = new Decoder; + $filling = $source->init($filling); + + } catch (NotReadableException $e) { + + // set solid color filling + $filling = new Color($filling); + } + + // flood fill if coordinates are set + if (is_int($x) && is_int($y)) { + + // flood fill with texture + if ($filling instanceof Image) { + + // create tile + $tile = clone $image->getCore(); + + // mask away color at position + $tile->transparentPaintImage($tile->getImagePixelColor($x, $y), 0, 0, false); + + // create canvas + $canvas = clone $image->getCore(); + + // fill canvas with texture + $canvas = $canvas->textureImage($filling->getCore()); + + // merge canvas and tile + $canvas->compositeImage($tile, \Imagick::COMPOSITE_DEFAULT, 0, 0); + + // replace image core + $image->setCore($canvas); + + // flood fill with color + } elseif ($filling instanceof Color) { + + // create canvas with filling + $canvas = new \Imagick; + $canvas->newImage($image->getWidth(), $image->getHeight(), $filling->getPixel(), 'png'); + + // create tile to put on top + $tile = clone $image->getCore(); + + // mask away color at pos. + $tile->transparentPaintImage($tile->getImagePixelColor($x, $y), 0, 0, false); + + // save alpha channel of original image + $alpha = clone $image->getCore(); + + // merge original with canvas and tile + $image->getCore()->compositeImage($canvas, \Imagick::COMPOSITE_DEFAULT, 0, 0); + $image->getCore()->compositeImage($tile, \Imagick::COMPOSITE_DEFAULT, 0, 0); + + // restore alpha channel of original image + $image->getCore()->compositeImage($alpha, \Imagick::COMPOSITE_COPYOPACITY, 0, 0); + } + + } else { + + if ($filling instanceof Image) { + + // fill whole image with texture + $image->setCore($image->getCore()->textureImage($filling->getCore())); + + } elseif ($filling instanceof Color) { + + // fill whole image with color + $draw = new \ImagickDraw(); + $draw->setFillColor($filling->getPixel()); + $draw->rectangle(0, 0, $image->getWidth(), $image->getHeight()); + $image->getCore()->drawImage($draw); + } + } + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/FitCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/FitCommand.php new file mode 100644 index 0000000..6d62ba6 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/FitCommand.php @@ -0,0 +1,42 @@ +argument(0)->type('digit')->required()->value(); + $height = $this->argument(1)->type('digit')->value($width); + $constraints = $this->argument(2)->type('closure')->value(); + $position = $this->argument(3)->type('string')->value('center'); + + // calculate size + $cropped = $image->getSize()->fit(new Size($width, $height), $position); + $resized = clone $cropped; + $resized = $resized->resize($width, $height, $constraints); + + // crop image + $image->getCore()->cropImage( + $cropped->width, + $cropped->height, + $cropped->pivot->x, + $cropped->pivot->y + ); + + // resize image + $image->getCore()->scaleImage($resized->getWidth(), $resized->getHeight()); + $image->getCore()->setImagePage(0,0,0,0); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/FlipCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/FlipCommand.php new file mode 100644 index 0000000..abae16a --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/FlipCommand.php @@ -0,0 +1,27 @@ +argument(0)->value('h'); + + if (in_array(strtolower($mode), [2, 'v', 'vert', 'vertical'])) { + // flip vertical + return $image->getCore()->flipImage(); + } else { + // flip horizontal + return $image->getCore()->flopImage(); + } + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/GammaCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/GammaCommand.php new file mode 100644 index 0000000..200515f --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/GammaCommand.php @@ -0,0 +1,21 @@ +argument(0)->type('numeric')->required()->value(); + + return $image->getCore()->gammaImage($gamma); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/GetSizeCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/GetSizeCommand.php new file mode 100644 index 0000000..ccccedb --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/GetSizeCommand.php @@ -0,0 +1,28 @@ +getCore(); + + $this->setOutput(new Size( + $core->getImageWidth(), + $core->getImageHeight() + )); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/GreyscaleCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/GreyscaleCommand.php new file mode 100644 index 0000000..df0ff5b --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/GreyscaleCommand.php @@ -0,0 +1,19 @@ +getCore()->modulateImage(100, 0, 100); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/HeightenCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/HeightenCommand.php new file mode 100644 index 0000000..0b61e50 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/HeightenCommand.php @@ -0,0 +1,28 @@ +argument(0)->type('digit')->required()->value(); + $additionalConstraints = $this->argument(1)->type('closure')->value(); + + $this->arguments[0] = null; + $this->arguments[1] = $height; + $this->arguments[2] = function ($constraint) use ($additionalConstraints) { + $constraint->aspectRatio(); + if(is_callable($additionalConstraints)) + $additionalConstraints($constraint); + }; + + return parent::execute($image); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/InsertCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/InsertCommand.php new file mode 100644 index 0000000..2a99743 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/InsertCommand.php @@ -0,0 +1,33 @@ +argument(0)->required()->value(); + $position = $this->argument(1)->type('string')->value(); + $x = $this->argument(2)->type('digit')->value(0); + $y = $this->argument(3)->type('digit')->value(0); + + // build watermark + $watermark = $image->getDriver()->init($source); + + // define insertion point + $image_size = $image->getSize()->align($position, $x, $y); + $watermark_size = $watermark->getSize()->align($position); + $target = $image_size->relativePosition($watermark_size); + + // insert image at position + return $image->getCore()->compositeImage($watermark->getCore(), \Imagick::COMPOSITE_DEFAULT, $target->x, $target->y); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/InterlaceCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/InterlaceCommand.php new file mode 100644 index 0000000..913cab7 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/InterlaceCommand.php @@ -0,0 +1,29 @@ +argument(0)->type('bool')->value(true); + + if ($mode) { + $mode = \Imagick::INTERLACE_LINE; + } else { + $mode = \Imagick::INTERLACE_NO; + } + + $image->getCore()->setInterlaceScheme($mode); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/InvertCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/InvertCommand.php new file mode 100644 index 0000000..1d13430 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/InvertCommand.php @@ -0,0 +1,19 @@ +getCore()->negateImage(false); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/LimitColorsCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/LimitColorsCommand.php new file mode 100644 index 0000000..16f9b82 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/LimitColorsCommand.php @@ -0,0 +1,59 @@ +argument(0)->value(); + $matte = $this->argument(1)->value(); + + // get current image size + $size = $image->getSize(); + + // build 2 color alpha mask from original alpha + $alpha = clone $image->getCore(); + $alpha->separateImageChannel(\Imagick::CHANNEL_ALPHA); + $alpha->transparentPaintImage('#ffffff', 0, 0, false); + $alpha->separateImageChannel(\Imagick::CHANNEL_ALPHA); + $alpha->negateImage(false); + + if ($matte) { + + // get matte color + $mattecolor = $image->getDriver()->parseColor($matte)->getPixel(); + + // create matte image + $canvas = new \Imagick; + $canvas->newImage($size->width, $size->height, $mattecolor, 'png'); + + // lower colors of original and copy to matte + $image->getCore()->quantizeImage($count, \Imagick::COLORSPACE_RGB, 0, false, false); + $canvas->compositeImage($image->getCore(), \Imagick::COMPOSITE_DEFAULT, 0, 0); + + // copy new alpha to canvas + $canvas->compositeImage($alpha, \Imagick::COMPOSITE_COPYOPACITY, 0, 0); + + // replace core + $image->setCore($canvas); + + } else { + + $image->getCore()->quantizeImage($count, \Imagick::COLORSPACE_RGB, 0, false, false); + $image->getCore()->compositeImage($alpha, \Imagick::COMPOSITE_COPYOPACITY, 0, 0); + + } + + return true; + + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/MaskCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/MaskCommand.php new file mode 100644 index 0000000..af9d6b2 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/MaskCommand.php @@ -0,0 +1,60 @@ +argument(0)->value(); + $mask_w_alpha = $this->argument(1)->type('bool')->value(false); + + // get imagick + $imagick = $image->getCore(); + + // build mask image from source + $mask = $image->getDriver()->init($mask_source); + + // resize mask to size of current image (if necessary) + $image_size = $image->getSize(); + if ($mask->getSize() != $image_size) { + $mask->resize($image_size->width, $image_size->height); + } + + $imagick->setImageMatte(true); + + if ($mask_w_alpha) { + + // just mask with alpha map + $imagick->compositeImage($mask->getCore(), \Imagick::COMPOSITE_DSTIN, 0, 0); + + } else { + + // get alpha channel of original as greyscale image + $original_alpha = clone $imagick; + $original_alpha->separateImageChannel(\Imagick::CHANNEL_ALPHA); + + // use red channel from mask ask alpha + $mask_alpha = clone $mask->getCore(); + $mask_alpha->compositeImage($mask->getCore(), \Imagick::COMPOSITE_DEFAULT, 0, 0); + // $mask_alpha->setImageAlphaChannel(\Imagick::ALPHACHANNEL_DEACTIVATE); + $mask_alpha->separateImageChannel(\Imagick::CHANNEL_ALL); + + // combine both alphas from original and mask + $original_alpha->compositeImage($mask_alpha, \Imagick::COMPOSITE_COPYOPACITY, 0, 0); + + // mask the image with the alpha combination + $imagick->compositeImage($original_alpha, \Imagick::COMPOSITE_DSTIN, 0, 0); + } + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/OpacityCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/OpacityCommand.php new file mode 100644 index 0000000..b4708d8 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/OpacityCommand.php @@ -0,0 +1,23 @@ +argument(0)->between(0, 100)->required()->value(); + + $transparency = $transparency > 0 ? (100 / $transparency) : 1000; + + return $image->getCore()->evaluateImage(\Imagick::EVALUATE_DIVIDE, $transparency, \Imagick::CHANNEL_ALPHA); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/PickColorCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/PickColorCommand.php new file mode 100644 index 0000000..978a128 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/PickColorCommand.php @@ -0,0 +1,30 @@ +argument(0)->type('digit')->required()->value(); + $y = $this->argument(1)->type('digit')->required()->value(); + $format = $this->argument(2)->type('string')->value('array'); + + // pick color + $color = new Color($image->getCore()->getImagePixelColor($x, $y)); + + // format to output + $this->setOutput($color->format($format)); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/PixelCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/PixelCommand.php new file mode 100644 index 0000000..6eb6ae7 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/PixelCommand.php @@ -0,0 +1,31 @@ +argument(0)->required()->value(); + $color = new Color($color); + $x = $this->argument(1)->type('digit')->required()->value(); + $y = $this->argument(2)->type('digit')->required()->value(); + + // prepare pixel + $draw = new \ImagickDraw; + $draw->setFillColor($color->getPixel()); + $draw->point($x, $y); + + // apply pixel + return $image->getCore()->drawImage($draw); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/PixelateCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/PixelateCommand.php new file mode 100644 index 0000000..6fe7949 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/PixelateCommand.php @@ -0,0 +1,27 @@ +argument(0)->type('digit')->value(10); + + $width = $image->getWidth(); + $height = $image->getHeight(); + + $image->getCore()->scaleImage(max(1, ($width / $size)), max(1, ($height / $size))); + $image->getCore()->scaleImage($width, $height); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ResetCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ResetCommand.php new file mode 100644 index 0000000..77b7f33 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ResetCommand.php @@ -0,0 +1,40 @@ +argument(0)->value(); + + $backup = $image->getBackup($backupName); + + if ($backup instanceof \Imagick) { + + // destroy current core + $image->getCore()->clear(); + + // clone backup + $backup = clone $backup; + + // reset to new resource + $image->setCore($backup); + + return true; + } + + throw new RuntimeException( + "Backup not available. Call backup({$backupName}) before reset()." + ); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ResizeCanvasCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ResizeCanvasCommand.php new file mode 100644 index 0000000..ecf0761 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ResizeCanvasCommand.php @@ -0,0 +1,91 @@ +argument(0)->type('digit')->required()->value(); + $height = $this->argument(1)->type('digit')->required()->value(); + $anchor = $this->argument(2)->value('center'); + $relative = $this->argument(3)->type('boolean')->value(false); + $bgcolor = $this->argument(4)->value(); + + $original_width = $image->getWidth(); + $original_height = $image->getHeight(); + + // check of only width or height is set + $width = is_null($width) ? $original_width : intval($width); + $height = is_null($height) ? $original_height : intval($height); + + // check on relative width/height + if ($relative) { + $width = $original_width + $width; + $height = $original_height + $height; + } + + // check for negative width/height + $width = ($width <= 0) ? $width + $original_width : $width; + $height = ($height <= 0) ? $height + $original_height : $height; + + // create new canvas + $canvas = $image->getDriver()->newImage($width, $height, $bgcolor); + + // set copy position + $canvas_size = $canvas->getSize()->align($anchor); + $image_size = $image->getSize()->align($anchor); + $canvas_pos = $image_size->relativePosition($canvas_size); + $image_pos = $canvas_size->relativePosition($image_size); + + if ($width <= $original_width) { + $dst_x = 0; + $src_x = $canvas_pos->x; + $src_w = $canvas_size->width; + } else { + $dst_x = $image_pos->x; + $src_x = 0; + $src_w = $original_width; + } + + if ($height <= $original_height) { + $dst_y = 0; + $src_y = $canvas_pos->y; + $src_h = $canvas_size->height; + } else { + $dst_y = $image_pos->y; + $src_y = 0; + $src_h = $original_height; + } + + // make image area transparent to keep transparency + // even if background-color is set + $rect = new \ImagickDraw; + $fill = $canvas->pickColor(0, 0, 'hex'); + $fill = $fill == '#ff0000' ? '#00ff00' : '#ff0000'; + $rect->setFillColor($fill); + $rect->rectangle($dst_x, $dst_y, $dst_x + $src_w - 1, $dst_y + $src_h - 1); + $canvas->getCore()->drawImage($rect); + $canvas->getCore()->transparentPaintImage($fill, 0, 0, false); + + $canvas->getCore()->setImageColorspace($image->getCore()->getImageColorspace()); + + // copy image into new canvas + $image->getCore()->cropImage($src_w, $src_h, $src_x, $src_y); + $canvas->getCore()->compositeImage($image->getCore(), \Imagick::COMPOSITE_DEFAULT, $dst_x, $dst_y); + $canvas->getCore()->setImagePage(0,0,0,0); + + // set new core to canvas + $image->setCore($canvas->getCore()); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ResizeCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ResizeCommand.php new file mode 100644 index 0000000..3d4dc5b --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ResizeCommand.php @@ -0,0 +1,29 @@ +argument(0)->value(); + $height = $this->argument(1)->value(); + $constraints = $this->argument(2)->type('closure')->value(); + + // resize box + $resized = $image->getSize()->resize($width, $height, $constraints); + + // modify image + $image->getCore()->scaleImage($resized->getWidth(), $resized->getHeight()); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/RotateCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/RotateCommand.php new file mode 100644 index 0000000..b3e12a5 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/RotateCommand.php @@ -0,0 +1,30 @@ +argument(0)->type('numeric')->required()->value(); + $color = $this->argument(1)->value(); + $color = new Color($color); + + // restrict rotations beyond 360 degrees, since the end result is the same + $angle = fmod($angle, 360); + + // rotate image + $image->getCore()->rotateImage($color->getPixel(), ($angle * -1)); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/SharpenCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/SharpenCommand.php new file mode 100644 index 0000000..bc5e393 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/SharpenCommand.php @@ -0,0 +1,21 @@ +argument(0)->between(0, 100)->value(10); + + return $image->getCore()->unsharpMaskImage(1, 1, $amount / 6.25, 0); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/TrimCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/TrimCommand.php new file mode 100644 index 0000000..bdc897b --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/TrimCommand.php @@ -0,0 +1,121 @@ +argument(0)->type('string')->value(); + $away = $this->argument(1)->value(); + $tolerance = $this->argument(2)->type('numeric')->value(0); + $feather = $this->argument(3)->type('numeric')->value(0); + + $width = $image->getWidth(); + $height = $image->getHeight(); + + $checkTransparency = false; + + // define borders to trim away + if (is_null($away)) { + $away = ['top', 'right', 'bottom', 'left']; + } elseif (is_string($away)) { + $away = [$away]; + } + + // lower border names + foreach ($away as $key => $value) { + $away[$key] = strtolower($value); + } + + // define base color position + switch (strtolower($base)) { + case 'transparent': + case 'trans': + $checkTransparency = true; + $base_x = 0; + $base_y = 0; + break; + + case 'bottom-right': + case 'right-bottom': + $base_x = $width - 1; + $base_y = $height - 1; + break; + + default: + case 'top-left': + case 'left-top': + $base_x = 0; + $base_y = 0; + break; + } + + // pick base color + if ($checkTransparency) { + $base_color = new Color; // color will only be used to compare alpha channel + } else { + $base_color = $image->pickColor($base_x, $base_y, 'object'); + } + + // trim on clone to get only coordinates + $trimed = clone $image->getCore(); + + // add border to trim specific color + $trimed->borderImage($base_color->getPixel(), 1, 1); + + // trim image + $trimed->trimImage(65850 / 100 * $tolerance); + + // get coordinates of trim + $imagePage = $trimed->getImagePage(); + list($crop_x, $crop_y) = [$imagePage['x']-1, $imagePage['y']-1]; + // $trimed->setImagePage(0, 0, 0, 0); + list($crop_width, $crop_height) = [$trimed->width, $trimed->height]; + + // adjust settings if right should not be trimed + if ( ! in_array('right', $away)) { + $crop_width = $crop_width + ($width - ($width - $crop_x)); + } + + // adjust settings if bottom should not be trimed + if ( ! in_array('bottom', $away)) { + $crop_height = $crop_height + ($height - ($height - $crop_y)); + } + + // adjust settings if left should not be trimed + if ( ! in_array('left', $away)) { + $crop_width = $crop_width + $crop_x; + $crop_x = 0; + } + + // adjust settings if top should not be trimed + if ( ! in_array('top', $away)) { + $crop_height = $crop_height + $crop_y; + $crop_y = 0; + } + + // add feather + $crop_width = min($width, ($crop_width + $feather * 2)); + $crop_height = min($height, ($crop_height + $feather * 2)); + $crop_x = max(0, ($crop_x - $feather)); + $crop_y = max(0, ($crop_y - $feather)); + + // finally crop based on page + $image->getCore()->cropImage($crop_width, $crop_height, $crop_x, $crop_y); + $image->getCore()->setImagePage(0,0,0,0); + + $trimed->destroy(); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/WidenCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/WidenCommand.php new file mode 100644 index 0000000..a196753 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/WidenCommand.php @@ -0,0 +1,28 @@ +argument(0)->type('digit')->required()->value(); + $additionalConstraints = $this->argument(1)->type('closure')->value(); + + $this->arguments[0] = $width; + $this->arguments[1] = null; + $this->arguments[2] = function ($constraint) use ($additionalConstraints) { + $constraint->aspectRatio(); + if(is_callable($additionalConstraints)) + $additionalConstraints($constraint); + }; + + return parent::execute($image); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Decoder.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Decoder.php new file mode 100644 index 0000000..f4dde9a --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Decoder.php @@ -0,0 +1,124 @@ +setBackgroundColor(new \ImagickPixel('transparent')); + $core->readImage($path); + $core->setImageType(defined('\Imagick::IMGTYPE_TRUECOLORALPHA') ? \Imagick::IMGTYPE_TRUECOLORALPHA : \Imagick::IMGTYPE_TRUECOLORMATTE); + + } catch (\ImagickException $e) { + throw new \Intervention\Image\Exception\NotReadableException( + "Unable to read image from path ({$path}).", + 0, + $e + ); + } + + // build image + $image = $this->initFromImagick($core); + $image->setFileInfoFromPath($path); + + return $image; + } + + /** + * Initiates new image from GD resource + * + * @param Resource $resource + * @return \Intervention\Image\Image + */ + public function initFromGdResource($resource) + { + throw new NotSupportedException( + 'Imagick driver is unable to init from GD resource.' + ); + } + + /** + * Initiates new image from Imagick object + * + * @param Imagick $object + * @return \Intervention\Image\Image + */ + public function initFromImagick(\Imagick $object) + { + // currently animations are not supported + // so all images are turned into static + $object = $this->removeAnimation($object); + + // reset image orientation + $object->setImageOrientation(\Imagick::ORIENTATION_UNDEFINED); + + return new Image(new Driver, $object); + } + + /** + * Initiates new image from binary data + * + * @param string $data + * @return \Intervention\Image\Image + */ + public function initFromBinary($binary) + { + $core = new \Imagick; + + try { + $core->setBackgroundColor(new \ImagickPixel('transparent')); + + $core->readImageBlob($binary); + + } catch (\ImagickException $e) { + throw new NotReadableException( + "Unable to read image from binary data.", + 0, + $e + ); + } + + // build image + $image = $this->initFromImagick($core); + $image->mime = finfo_buffer(finfo_open(FILEINFO_MIME_TYPE), $binary); + + return $image; + } + + /** + * Turns object into one frame Imagick object + * by removing all frames except first + * + * @param Imagick $object + * @return Imagick + */ + private function removeAnimation(\Imagick $object) + { + $imagick = new \Imagick; + + foreach ($object as $frame) { + $imagick->addImage($frame->getImage()); + break; + } + + $object->destroy(); + + return $imagick; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Driver.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Driver.php new file mode 100644 index 0000000..bb26ca0 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Driver.php @@ -0,0 +1,74 @@ +coreAvailable()) { + throw new NotSupportedException( + "ImageMagick module not available with this PHP installation." + ); + } + + $this->decoder = $decoder ? $decoder : new Decoder; + $this->encoder = $encoder ? $encoder : new Encoder; + } + + /** + * Creates new image instance + * + * @param int $width + * @param int $height + * @param mixed $background + * @return \Intervention\Image\Image + */ + public function newImage($width, $height, $background = null) + { + $background = new Color($background); + + // create empty core + $core = new \Imagick; + $core->newImage($width, $height, $background->getPixel(), 'png'); + $core->setType(\Imagick::IMGTYPE_UNDEFINED); + $core->setImageType(\Imagick::IMGTYPE_UNDEFINED); + $core->setColorspace(\Imagick::COLORSPACE_UNDEFINED); + + // build image + $image = new Image(new static, $core); + + return $image; + } + + /** + * Reads given string into color object + * + * @param string $value + * @return AbstractColor + */ + public function parseColor($value) + { + return new Color($value); + } + + /** + * Checks if core module installation is available + * + * @return boolean + */ + protected function coreAvailable() + { + return (extension_loaded('imagick') && class_exists('Imagick')); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Encoder.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Encoder.php new file mode 100644 index 0000000..feb8a9d --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Encoder.php @@ -0,0 +1,239 @@ +image->getCore(); + $imagick->setImageBackgroundColor('white'); + $imagick->setBackgroundColor('white'); + $imagick = $imagick->mergeImageLayers(\Imagick::LAYERMETHOD_MERGE); + $imagick->setFormat($format); + $imagick->setImageFormat($format); + $imagick->setCompression($compression); + $imagick->setImageCompression($compression); + $imagick->setCompressionQuality($this->quality); + $imagick->setImageCompressionQuality($this->quality); + + return $imagick->getImagesBlob(); + } + + /** + * Processes and returns encoded image as PNG string + * + * @return string + */ + protected function processPng() + { + $format = 'png'; + $compression = \Imagick::COMPRESSION_ZIP; + + $imagick = $this->image->getCore(); + $imagick->setFormat($format); + $imagick->setImageFormat($format); + $imagick->setCompression($compression); + $imagick->setImageCompression($compression); + + $this->image->mime = image_type_to_mime_type(IMAGETYPE_PNG); + + return $imagick->getImagesBlob(); + } + + /** + * Processes and returns encoded image as GIF string + * + * @return string + */ + protected function processGif() + { + $format = 'gif'; + $compression = \Imagick::COMPRESSION_LZW; + + $imagick = $this->image->getCore(); + $imagick->setFormat($format); + $imagick->setImageFormat($format); + $imagick->setCompression($compression); + $imagick->setImageCompression($compression); + + $this->image->mime = image_type_to_mime_type(IMAGETYPE_GIF); + + return $imagick->getImagesBlob(); + } + + protected function processWebp() + { + if ( ! \Imagick::queryFormats('WEBP')) { + throw new NotSupportedException( + "Webp format is not supported by Imagick installation." + ); + } + + $format = 'webp'; + $compression = \Imagick::COMPRESSION_JPEG; + + $imagick = $this->image->getCore(); + $imagick->setImageBackgroundColor(new \ImagickPixel('transparent')); + + $imagick = $imagick->mergeImageLayers(\Imagick::LAYERMETHOD_MERGE); + $imagick->setFormat($format); + $imagick->setImageFormat($format); + $imagick->setCompression($compression); + $imagick->setImageCompression($compression); + $imagick->setImageCompressionQuality($this->quality); + + return $imagick->getImagesBlob(); + } + + /** + * Processes and returns encoded image as TIFF string + * + * @return string + */ + protected function processTiff() + { + $format = 'tiff'; + $compression = \Imagick::COMPRESSION_UNDEFINED; + + $imagick = $this->image->getCore(); + $imagick->setFormat($format); + $imagick->setImageFormat($format); + $imagick->setCompression($compression); + $imagick->setImageCompression($compression); + $imagick->setCompressionQuality($this->quality); + $imagick->setImageCompressionQuality($this->quality); + + $this->image->mime = image_type_to_mime_type(IMAGETYPE_TIFF_II); + + return $imagick->getImagesBlob(); + } + + /** + * Processes and returns encoded image as BMP string + * + * @return string + */ + protected function processBmp() + { + $format = 'bmp'; + $compression = \Imagick::COMPRESSION_UNDEFINED; + + $imagick = $this->image->getCore(); + $imagick->setFormat($format); + $imagick->setImageFormat($format); + $imagick->setCompression($compression); + $imagick->setImageCompression($compression); + + $this->image->mime = image_type_to_mime_type(IMAGETYPE_BMP); + + return $imagick->getImagesBlob(); + } + + /** + * Processes and returns encoded image as ICO string + * + * @return string + */ + protected function processIco() + { + $format = 'ico'; + $compression = \Imagick::COMPRESSION_UNDEFINED; + + $imagick = $this->image->getCore(); + $imagick->setFormat($format); + $imagick->setImageFormat($format); + $imagick->setCompression($compression); + $imagick->setImageCompression($compression); + + $this->image->mime = image_type_to_mime_type(IMAGETYPE_ICO); + + return $imagick->getImagesBlob(); + } + + /** + * Processes and returns encoded image as PSD string + * + * @return string + */ + protected function processPsd() + { + $format = 'psd'; + $compression = \Imagick::COMPRESSION_UNDEFINED; + + $imagick = $this->image->getCore(); + $imagick->setFormat($format); + $imagick->setImageFormat($format); + $imagick->setCompression($compression); + $imagick->setImageCompression($compression); + + $this->image->mime = image_type_to_mime_type(IMAGETYPE_PSD); + + return $imagick->getImagesBlob(); + } + + /** + * Processes and returns encoded image as AVIF string + * + * @return string + */ + protected function processAvif() + { + if ( ! \Imagick::queryFormats('AVIF')) { + throw new NotSupportedException( + "AVIF format is not supported by Imagick installation." + ); + } + + $format = 'avif'; + $compression = \Imagick::COMPRESSION_UNDEFINED; + + $imagick = $this->image->getCore(); + $imagick->setFormat($format); + $imagick->setImageFormat($format); + $imagick->setCompression($compression); + $imagick->setImageCompression($compression); + $imagick->setCompressionQuality($this->quality); + $imagick->setImageCompressionQuality($this->quality); + + return $imagick->getImagesBlob(); + } + + /** + * Processes and returns encoded image as HEIC string + * + * @return string + */ + protected function processHeic() + { + if ( ! \Imagick::queryFormats('HEIC')) { + throw new NotSupportedException( + "HEIC format is not supported by Imagick installation." + ); + } + + $format = 'heic'; + $compression = \Imagick::COMPRESSION_UNDEFINED; + + $imagick = $this->image->getCore(); + $imagick->setFormat($format); + $imagick->setImageFormat($format); + $imagick->setCompression($compression); + $imagick->setImageCompression($compression); + $imagick->setCompressionQuality($this->quality); + $imagick->setImageCompressionQuality($this->quality); + + return $imagick->getImagesBlob(); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Font.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Font.php new file mode 100644 index 0000000..9869d06 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Font.php @@ -0,0 +1,122 @@ +setStrokeAntialias(true); + $draw->setTextAntialias(true); + + // set font file + if ($this->hasApplicableFontFile()) { + $draw->setFont($this->file); + } else { + throw new RuntimeException( + "Font file must be provided to apply text to image." + ); + } + + // parse text color + $color = new Color($this->color); + + $draw->setFontSize($this->size); + $draw->setFillColor($color->getPixel()); + $draw->setTextKerning($this->kerning); + + // align horizontal + switch (strtolower($this->align)) { + case 'center': + $align = \Imagick::ALIGN_CENTER; + break; + + case 'right': + $align = \Imagick::ALIGN_RIGHT; + break; + + default: + $align = \Imagick::ALIGN_LEFT; + break; + } + + $draw->setTextAlignment($align); + + // align vertical + if (strtolower($this->valign) != 'bottom') { + + // corrections on y-position + switch (strtolower($this->valign)) { + case 'center': + case 'middle': + // calculate box size + $dimensions = $image->getCore()->queryFontMetrics($draw, $this->text); + $posy = $posy + $dimensions['textHeight'] * 0.65 / 2; + break; + + case 'top': + // calculate box size + $dimensions = $image->getCore()->queryFontMetrics($draw, $this->text, false); + $posy = $posy + $dimensions['characterHeight']; + break; + } + } + + // apply to image + $image->getCore()->annotateImage($draw, $posx, $posy, $this->angle * (-1), $this->text); + } + + /** + * Calculates bounding box of current font setting + * + * @return array + */ + public function getBoxSize() + { + $box = []; + + // build draw object + $draw = new \ImagickDraw(); + $draw->setStrokeAntialias(true); + $draw->setTextAntialias(true); + + // set font file + if ($this->hasApplicableFontFile()) { + $draw->setFont($this->file); + } else { + throw new RuntimeException( + "Font file must be provided to apply text to image." + ); + } + + $draw->setFontSize($this->size); + + $dimensions = (new \Imagick())->queryFontMetrics($draw, $this->text); + + if (strlen($this->text) == 0) { + // no text -> no boxsize + $box['width'] = 0; + $box['height'] = 0; + } else { + // get boxsize + $box['width'] = intval(abs($dimensions['textWidth'])); + $box['height'] = intval(abs($dimensions['textHeight'])); + } + + return $box; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/CircleShape.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/CircleShape.php new file mode 100644 index 0000000..24172ea --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/CircleShape.php @@ -0,0 +1,40 @@ +width = is_numeric($diameter) ? intval($diameter) : $this->diameter; + $this->height = is_numeric($diameter) ? intval($diameter) : $this->diameter; + $this->diameter = is_numeric($diameter) ? intval($diameter) : $this->diameter; + } + + /** + * Draw current circle on given image + * + * @param Image $image + * @param int $x + * @param int $y + * @return boolean + */ + public function applyToImage(Image $image, $x = 0, $y = 0) + { + return parent::applyToImage($image, $x, $y); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/EllipseShape.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/EllipseShape.php new file mode 100644 index 0000000..99694b9 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/EllipseShape.php @@ -0,0 +1,66 @@ +width = is_numeric($width) ? intval($width) : $this->width; + $this->height = is_numeric($height) ? intval($height) : $this->height; + } + + /** + * Draw ellipse instance on given image + * + * @param Image $image + * @param int $x + * @param int $y + * @return boolean + */ + public function applyToImage(Image $image, $x = 0, $y = 0) + { + $circle = new \ImagickDraw; + + // set background + $bgcolor = new Color($this->background); + $circle->setFillColor($bgcolor->getPixel()); + + // set border + if ($this->hasBorder()) { + $border_color = new Color($this->border_color); + $circle->setStrokeWidth($this->border_width); + $circle->setStrokeColor($border_color->getPixel()); + } + + $circle->ellipse($x, $y, $this->width / 2, $this->height / 2, 0, 360); + + $image->getCore()->drawImage($circle); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/LineShape.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/LineShape.php new file mode 100644 index 0000000..0d6b329 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/LineShape.php @@ -0,0 +1,94 @@ +x = is_numeric($x) ? intval($x) : $this->x; + $this->y = is_numeric($y) ? intval($y) : $this->y; + } + + /** + * Set current line color + * + * @param string $color + * @return void + */ + public function color($color) + { + $this->color = $color; + } + + /** + * Set current line width in pixels + * + * @param int $width + * @return void + */ + public function width($width) + { + $this->width = $width; + } + + /** + * Draw current instance of line to given endpoint on given image + * + * @param Image $image + * @param int $x + * @param int $y + * @return boolean + */ + public function applyToImage(Image $image, $x = 0, $y = 0) + { + $line = new \ImagickDraw; + + $color = new Color($this->color); + $line->setStrokeColor($color->getPixel()); + $line->setStrokeWidth($this->width); + + $line->line($this->x, $this->y, $x, $y); + $image->getCore()->drawImage($line); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/PolygonShape.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/PolygonShape.php new file mode 100644 index 0000000..45f44ad --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/PolygonShape.php @@ -0,0 +1,81 @@ +points = $this->formatPoints($points); + } + + /** + * Draw polygon on given image + * + * @param Image $image + * @param int $x + * @param int $y + * @return boolean + */ + public function applyToImage(Image $image, $x = 0, $y = 0) + { + $polygon = new \ImagickDraw; + + // set background + $bgcolor = new Color($this->background); + $polygon->setFillColor($bgcolor->getPixel()); + + // set border + if ($this->hasBorder()) { + $border_color = new Color($this->border_color); + $polygon->setStrokeWidth($this->border_width); + $polygon->setStrokeColor($border_color->getPixel()); + } + + $polygon->polygon($this->points); + + $image->getCore()->drawImage($polygon); + + return true; + } + + /** + * Format polygon points to Imagick format + * + * @param Array $points + * @return Array + */ + private function formatPoints($points) + { + $ipoints = []; + $count = 1; + + foreach ($points as $key => $value) { + if ($count%2 === 0) { + $y = $value; + $ipoints[] = ['x' => $x, 'y' => $y]; + } else { + $x = $value; + } + $count++; + } + + return $ipoints; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/RectangleShape.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/RectangleShape.php new file mode 100644 index 0000000..1166d77 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/RectangleShape.php @@ -0,0 +1,84 @@ +x1 = is_numeric($x1) ? intval($x1) : $this->x1; + $this->y1 = is_numeric($y1) ? intval($y1) : $this->y1; + $this->x2 = is_numeric($x2) ? intval($x2) : $this->x2; + $this->y2 = is_numeric($y2) ? intval($y2) : $this->y2; + } + + /** + * Draw rectangle to given image at certain position + * + * @param Image $image + * @param int $x + * @param int $y + * @return boolean + */ + public function applyToImage(Image $image, $x = 0, $y = 0) + { + $rectangle = new \ImagickDraw; + + // set background + $bgcolor = new Color($this->background); + $rectangle->setFillColor($bgcolor->getPixel()); + + // set border + if ($this->hasBorder()) { + $border_color = new Color($this->border_color); + $rectangle->setStrokeWidth($this->border_width); + $rectangle->setStrokeColor($border_color->getPixel()); + } + + $rectangle->rectangle($this->x1, $this->y1, $this->x2, $this->y2); + + $image->getCore()->drawImage($rectangle); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Point.php b/vendor/intervention/image/src/Intervention/Image/Point.php new file mode 100644 index 0000000..d51452e --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Point.php @@ -0,0 +1,64 @@ +x = is_numeric($x) ? intval($x) : 0; + $this->y = is_numeric($y) ? intval($y) : 0; + } + + /** + * Sets X coordinate + * + * @param int $x + */ + public function setX($x) + { + $this->x = intval($x); + } + + /** + * Sets Y coordinate + * + * @param int $y + */ + public function setY($y) + { + $this->y = intval($y); + } + + /** + * Sets both X and Y coordinate + * + * @param int $x + * @param int $y + */ + public function setPosition($x, $y) + { + $this->setX($x); + $this->setY($y); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Response.php b/vendor/intervention/image/src/Intervention/Image/Response.php new file mode 100644 index 0000000..59a51e9 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Response.php @@ -0,0 +1,78 @@ +image = $image; + $this->format = $format ? $format : $image->mime; + $this->quality = $quality ? $quality : 90; + } + + /** + * Builds response according to settings + * + * @return mixed + */ + public function make() + { + $this->image->encode($this->format, $this->quality); + $data = $this->image->getEncoded(); + $mime = finfo_buffer(finfo_open(FILEINFO_MIME_TYPE), $data); + $length = strlen($data); + + if (function_exists('app') && is_a($app = app(), 'Illuminate\Foundation\Application')) { + + $response = IlluminateResponse::make($data); + $response->header('Content-Type', $mime); + $response->header('Content-Length', $length); + + } elseif (class_exists('\Symfony\Component\HttpFoundation\Response')) { + + $response = new SymfonyResponse($data); + $response->headers->set('Content-Type', $mime); + $response->headers->set('Content-Length', $length); + + } else { + + header('Content-Type: ' . $mime); + header('Content-Length: ' . $length); + $response = $data; + } + + return $response; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Size.php b/vendor/intervention/image/src/Intervention/Image/Size.php new file mode 100644 index 0000000..f8b6dc8 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Size.php @@ -0,0 +1,374 @@ +width = is_numeric($width) ? intval($width) : 1; + $this->height = is_numeric($height) ? intval($height) : 1; + $this->pivot = $pivot ? $pivot : new Point; + } + + /** + * Set the width and height absolutely + * + * @param int $width + * @param int $height + */ + public function set($width, $height) + { + $this->width = $width; + $this->height = $height; + } + + /** + * Set current pivot point + * + * @param Point $point + */ + public function setPivot(Point $point) + { + $this->pivot = $point; + } + + /** + * Get the current width + * + * @return int + */ + public function getWidth() + { + return $this->width; + } + + /** + * Get the current height + * + * @return int + */ + public function getHeight() + { + return $this->height; + } + + /** + * Calculate the current aspect ratio + * + * @return float + */ + public function getRatio() + { + return $this->width / $this->height; + } + + /** + * Resize to desired width and/or height + * + * @param int $width + * @param int $height + * @param Closure $callback + * @return Size + */ + public function resize($width, $height, Closure $callback = null) + { + if (is_null($width) && is_null($height)) { + throw new InvalidArgumentException( + "Width or height needs to be defined." + ); + } + + // new size with dominant width + $dominant_w_size = clone $this; + $dominant_w_size->resizeHeight($height, $callback); + $dominant_w_size->resizeWidth($width, $callback); + + // new size with dominant height + $dominant_h_size = clone $this; + $dominant_h_size->resizeWidth($width, $callback); + $dominant_h_size->resizeHeight($height, $callback); + + // decide which size to use + if ($dominant_h_size->fitsInto(new self($width, $height))) { + $this->set($dominant_h_size->width, $dominant_h_size->height); + } else { + $this->set($dominant_w_size->width, $dominant_w_size->height); + } + + return $this; + } + + /** + * Scale size according to given constraints + * + * @param int $width + * @param Closure $callback + * @return Size + */ + private function resizeWidth($width, Closure $callback = null) + { + $constraint = $this->getConstraint($callback); + + if ($constraint->isFixed(Constraint::UPSIZE)) { + $max_width = $constraint->getSize()->getWidth(); + $max_height = $constraint->getSize()->getHeight(); + } + + if (is_numeric($width)) { + + if ($constraint->isFixed(Constraint::UPSIZE)) { + $this->width = ($width > $max_width) ? $max_width : $width; + } else { + $this->width = $width; + } + + if ($constraint->isFixed(Constraint::ASPECTRATIO)) { + $h = max(1, intval(round($this->width / $constraint->getSize()->getRatio()))); + + if ($constraint->isFixed(Constraint::UPSIZE)) { + $this->height = ($h > $max_height) ? $max_height : $h; + } else { + $this->height = $h; + } + } + } + } + + /** + * Scale size according to given constraints + * + * @param int $height + * @param Closure $callback + * @return Size + */ + private function resizeHeight($height, Closure $callback = null) + { + $constraint = $this->getConstraint($callback); + + if ($constraint->isFixed(Constraint::UPSIZE)) { + $max_width = $constraint->getSize()->getWidth(); + $max_height = $constraint->getSize()->getHeight(); + } + + if (is_numeric($height)) { + + if ($constraint->isFixed(Constraint::UPSIZE)) { + $this->height = ($height > $max_height) ? $max_height : $height; + } else { + $this->height = $height; + } + + if ($constraint->isFixed(Constraint::ASPECTRATIO)) { + $w = max(1, intval(round($this->height * $constraint->getSize()->getRatio()))); + + if ($constraint->isFixed(Constraint::UPSIZE)) { + $this->width = ($w > $max_width) ? $max_width : $w; + } else { + $this->width = $w; + } + } + } + } + + /** + * Calculate the relative position to another Size + * based on the pivot point settings of both sizes. + * + * @param Size $size + * @return \Intervention\Image\Point + */ + public function relativePosition(Size $size) + { + $x = $this->pivot->x - $size->pivot->x; + $y = $this->pivot->y - $size->pivot->y; + + return new Point($x, $y); + } + + /** + * Resize given Size to best fitting size of current size. + * + * @param Size $size + * @return \Intervention\Image\Size + */ + public function fit(Size $size, $position = 'center') + { + // create size with auto height + $auto_height = clone $size; + + $auto_height->resize($this->width, null, function ($constraint) { + $constraint->aspectRatio(); + }); + + // decide which version to use + if ($auto_height->fitsInto($this)) { + + $size = $auto_height; + + } else { + + // create size with auto width + $auto_width = clone $size; + + $auto_width->resize(null, $this->height, function ($constraint) { + $constraint->aspectRatio(); + }); + + $size = $auto_width; + } + + $this->align($position); + $size->align($position); + $size->setPivot($this->relativePosition($size)); + + return $size; + } + + /** + * Checks if given size fits into current size + * + * @param Size $size + * @return boolean + */ + public function fitsInto(Size $size) + { + return ($this->width <= $size->width) && ($this->height <= $size->height); + } + + /** + * Aligns current size's pivot point to given position + * and moves point automatically by offset. + * + * @param string $position + * @param int $offset_x + * @param int $offset_y + * @return \Intervention\Image\Size + */ + public function align($position, $offset_x = 0, $offset_y = 0) + { + switch (strtolower($position)) { + + case 'top': + case 'top-center': + case 'top-middle': + case 'center-top': + case 'middle-top': + $x = intval($this->width / 2); + $y = 0 + $offset_y; + break; + + case 'top-right': + case 'right-top': + $x = $this->width - $offset_x; + $y = 0 + $offset_y; + break; + + case 'left': + case 'left-center': + case 'left-middle': + case 'center-left': + case 'middle-left': + $x = 0 + $offset_x; + $y = intval($this->height / 2); + break; + + case 'right': + case 'right-center': + case 'right-middle': + case 'center-right': + case 'middle-right': + $x = $this->width - $offset_x; + $y = intval($this->height / 2); + break; + + case 'bottom-left': + case 'left-bottom': + $x = 0 + $offset_x; + $y = $this->height - $offset_y; + break; + + case 'bottom': + case 'bottom-center': + case 'bottom-middle': + case 'center-bottom': + case 'middle-bottom': + $x = intval($this->width / 2); + $y = $this->height - $offset_y; + break; + + case 'bottom-right': + case 'right-bottom': + $x = $this->width - $offset_x; + $y = $this->height - $offset_y; + break; + + case 'center': + case 'middle': + case 'center-center': + case 'middle-middle': + $x = intval($this->width / 2) + $offset_x; + $y = intval($this->height / 2) + $offset_y; + break; + + default: + case 'top-left': + case 'left-top': + $x = 0 + $offset_x; + $y = 0 + $offset_y; + break; + } + + $this->pivot->setPosition($x, $y); + + return $this; + } + + /** + * Runs constraints on current size + * + * @param Closure $callback + * @return \Intervention\Image\Constraint + */ + private function getConstraint(Closure $callback = null) + { + $constraint = new Constraint(clone $this); + + if (is_callable($callback)) { + $callback($constraint); + } + + return $constraint; + } +} diff --git a/vendor/intervention/image/src/config/config.php b/vendor/intervention/image/src/config/config.php new file mode 100644 index 0000000..2b1d2c3 --- /dev/null +++ b/vendor/intervention/image/src/config/config.php @@ -0,0 +1,20 @@ + 'gd' + +]; diff --git a/vendor/lcobucci/clock/LICENSE b/vendor/lcobucci/clock/LICENSE new file mode 100644 index 0000000..58ea944 --- /dev/null +++ b/vendor/lcobucci/clock/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Luís Cobucci + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/lcobucci/clock/composer.json b/vendor/lcobucci/clock/composer.json new file mode 100644 index 0000000..e0d2f78 --- /dev/null +++ b/vendor/lcobucci/clock/composer.json @@ -0,0 +1,40 @@ +{ + "name": "lcobucci/clock", + "description": "Yet another clock abstraction", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Luís Cobucci", + "email": "lcobucci@gmail.com" + } + ], + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "infection/infection": "^0.17", + "lcobucci/coding-standard": "^6.0", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-deprecation-rules": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/php-code-coverage": "9.1.4", + "phpunit/phpunit": "9.3.7" + }, + "autoload": { + "psr-4": { + "Lcobucci\\Clock\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Lcobucci\\Clock\\": "test" + } + }, + "config": { + "preferred-install": "dist", + "sort-packages": true + } +} diff --git a/vendor/lcobucci/clock/src/Clock.php b/vendor/lcobucci/clock/src/Clock.php new file mode 100644 index 0000000..f356776 --- /dev/null +++ b/vendor/lcobucci/clock/src/Clock.php @@ -0,0 +1,11 @@ +now = $now; + } + + public static function fromUTC(): self + { + return new self(new DateTimeImmutable('now', new DateTimeZone('UTC'))); + } + + public function setTo(DateTimeImmutable $now): void + { + $this->now = $now; + } + + public function now(): DateTimeImmutable + { + return $this->now; + } +} diff --git a/vendor/lcobucci/clock/src/SystemClock.php b/vendor/lcobucci/clock/src/SystemClock.php new file mode 100644 index 0000000..f40559c --- /dev/null +++ b/vendor/lcobucci/clock/src/SystemClock.php @@ -0,0 +1,34 @@ +timezone = $timezone; + } + + public static function fromUTC(): self + { + return new self(new DateTimeZone('UTC')); + } + + public static function fromSystemTimezone(): self + { + return new self(new DateTimeZone(date_default_timezone_get())); + } + + public function now(): DateTimeImmutable + { + return new DateTimeImmutable('now', $this->timezone); + } +} diff --git a/vendor/lcobucci/jwt/LICENSE b/vendor/lcobucci/jwt/LICENSE new file mode 100644 index 0000000..cc7e28f --- /dev/null +++ b/vendor/lcobucci/jwt/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2014, Luís Cobucci +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the {organization} nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/lcobucci/jwt/composer.json b/vendor/lcobucci/jwt/composer.json new file mode 100644 index 0000000..a936976 --- /dev/null +++ b/vendor/lcobucci/jwt/composer.json @@ -0,0 +1,60 @@ +{ + "name": "lcobucci/jwt", + "type": "library", + "description": "A simple library to work with JSON Web Token and JSON Web Signature", + "keywords": [ + "JWT", + "JWS" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Luís Cobucci", + "email": "lcobucci@gmail.com", + "role": "Developer" + } + ], + "require": { + "php": "^7.4 || ^8.0", + "ext-hash": "*", + "ext-json": "*", + "ext-mbstring": "*", + "ext-openssl": "*", + "ext-sodium": "*", + "lcobucci/clock": "^2.0" + }, + "require-dev": { + "infection/infection": "^0.21", + "lcobucci/coding-standard": "^6.0", + "mikey179/vfsstream": "^1.6.7", + "phpbench/phpbench": "^1.0", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-deprecation-rules": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/php-invoker": "^3.1", + "phpunit/phpunit": "^9.5" + }, + "config": { + "preferred-install": "dist", + "sort-packages": true + }, + "autoload": { + "psr-4": { + "Lcobucci\\JWT\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Lcobucci\\JWT\\": [ + "test/_keys", + "test/unit", + "test/performance" + ], + "Lcobucci\\JWT\\FunctionalTests\\": "test/functional" + } + } +} diff --git a/vendor/lcobucci/jwt/src/Builder.php b/vendor/lcobucci/jwt/src/Builder.php new file mode 100644 index 0000000..531e9d3 --- /dev/null +++ b/vendor/lcobucci/jwt/src/Builder.php @@ -0,0 +1,77 @@ + $claims + * + * @return array + */ + public function formatClaims(array $claims): array; +} diff --git a/vendor/lcobucci/jwt/src/Configuration.php b/vendor/lcobucci/jwt/src/Configuration.php new file mode 100644 index 0000000..8da1c8a --- /dev/null +++ b/vendor/lcobucci/jwt/src/Configuration.php @@ -0,0 +1,154 @@ +signer = $signer; + $this->signingKey = $signingKey; + $this->verificationKey = $verificationKey; + $this->parser = new Token\Parser($decoder ?? new JoseEncoder()); + $this->validator = new Validation\Validator(); + + $this->builderFactory = static function (ClaimsFormatter $claimFormatter) use ($encoder): Builder { + return new Token\Builder($encoder ?? new JoseEncoder(), $claimFormatter); + }; + } + + public static function forAsymmetricSigner( + Signer $signer, + Key $signingKey, + Key $verificationKey, + ?Encoder $encoder = null, + ?Decoder $decoder = null + ): self { + return new self( + $signer, + $signingKey, + $verificationKey, + $encoder, + $decoder + ); + } + + public static function forSymmetricSigner( + Signer $signer, + Key $key, + ?Encoder $encoder = null, + ?Decoder $decoder = null + ): self { + return new self( + $signer, + $key, + $key, + $encoder, + $decoder + ); + } + + public static function forUnsecuredSigner( + ?Encoder $encoder = null, + ?Decoder $decoder = null + ): self { + $key = InMemory::empty(); + + return new self( + new None(), + $key, + $key, + $encoder, + $decoder + ); + } + + /** @param callable(ClaimsFormatter): Builder $builderFactory */ + public function setBuilderFactory(callable $builderFactory): void + { + $this->builderFactory = Closure::fromCallable($builderFactory); + } + + public function builder(?ClaimsFormatter $claimFormatter = null): Builder + { + return ($this->builderFactory)($claimFormatter ?? ChainedFormatter::default()); + } + + public function parser(): Parser + { + return $this->parser; + } + + public function setParser(Parser $parser): void + { + $this->parser = $parser; + } + + public function signer(): Signer + { + return $this->signer; + } + + public function signingKey(): Key + { + return $this->signingKey; + } + + public function verificationKey(): Key + { + return $this->verificationKey; + } + + public function validator(): Validator + { + return $this->validator; + } + + public function setValidator(Validator $validator): void + { + $this->validator = $validator; + } + + /** @return Constraint[] */ + public function validationConstraints(): array + { + return $this->validationConstraints; + } + + public function setValidationConstraints(Constraint ...$validationConstraints): void + { + $this->validationConstraints = $validationConstraints; + } +} diff --git a/vendor/lcobucci/jwt/src/Decoder.php b/vendor/lcobucci/jwt/src/Decoder.php new file mode 100644 index 0000000..10fc991 --- /dev/null +++ b/vendor/lcobucci/jwt/src/Decoder.php @@ -0,0 +1,27 @@ + */ + private array $formatters; + + public function __construct(ClaimsFormatter ...$formatters) + { + $this->formatters = $formatters; + } + + public static function default(): self + { + return new self(new UnifyAudience(), new MicrosecondBasedDateConversion()); + } + + public static function withUnixTimestampDates(): self + { + return new self(new UnifyAudience(), new UnixTimestampDates()); + } + + /** @inheritdoc */ + public function formatClaims(array $claims): array + { + foreach ($this->formatters as $formatter) { + $claims = $formatter->formatClaims($claims); + } + + return $claims; + } +} diff --git a/vendor/lcobucci/jwt/src/Encoding/JoseEncoder.php b/vendor/lcobucci/jwt/src/Encoding/JoseEncoder.php new file mode 100644 index 0000000..597d15f --- /dev/null +++ b/vendor/lcobucci/jwt/src/Encoding/JoseEncoder.php @@ -0,0 +1,60 @@ +convertDate($claims[$claim]); + } + + return $claims; + } + + /** @return int|float */ + private function convertDate(DateTimeImmutable $date) + { + if ($date->format('u') === '000000') { + return (int) $date->format('U'); + } + + return (float) $date->format('U.u'); + } +} diff --git a/vendor/lcobucci/jwt/src/Encoding/UnifyAudience.php b/vendor/lcobucci/jwt/src/Encoding/UnifyAudience.php new file mode 100644 index 0000000..cf57252 --- /dev/null +++ b/vendor/lcobucci/jwt/src/Encoding/UnifyAudience.php @@ -0,0 +1,29 @@ +convertDate($claims[$claim]); + } + + return $claims; + } + + private function convertDate(DateTimeImmutable $date): int + { + return $date->getTimestamp(); + } +} diff --git a/vendor/lcobucci/jwt/src/Exception.php b/vendor/lcobucci/jwt/src/Exception.php new file mode 100644 index 0000000..4b3916e --- /dev/null +++ b/vendor/lcobucci/jwt/src/Exception.php @@ -0,0 +1,10 @@ +converter = $converter; + } + + public static function create(): Ecdsa + { + return new static(new MultibyteStringConverter()); // @phpstan-ignore-line + } + + final public function sign(string $payload, Key $key): string + { + return $this->converter->fromAsn1( + $this->createSignature($key->contents(), $key->passphrase(), $payload), + $this->keyLength() + ); + } + + final public function verify(string $expected, string $payload, Key $key): bool + { + return $this->verifySignature( + $this->converter->toAsn1($expected, $this->keyLength()), + $payload, + $key->contents() + ); + } + + final public function keyType(): int + { + return OPENSSL_KEYTYPE_EC; + } + + /** + * Returns the length of each point in the signature, so that we can calculate and verify R and S points properly + * + * @internal + */ + abstract public function keyLength(): int; +} diff --git a/vendor/lcobucci/jwt/src/Signer/Ecdsa/ConversionFailed.php b/vendor/lcobucci/jwt/src/Signer/Ecdsa/ConversionFailed.php new file mode 100644 index 0000000..d9ca751 --- /dev/null +++ b/vendor/lcobucci/jwt/src/Signer/Ecdsa/ConversionFailed.php @@ -0,0 +1,25 @@ + self::ASN1_MAX_SINGLE_BYTE ? self::ASN1_LENGTH_2BYTES : ''; + + $asn1 = hex2bin( + self::ASN1_SEQUENCE + . $lengthPrefix . dechex($totalLength) + . self::ASN1_INTEGER . dechex($lengthR) . $pointR + . self::ASN1_INTEGER . dechex($lengthS) . $pointS + ); + assert(is_string($asn1)); + + return $asn1; + } + + private static function octetLength(string $data): int + { + return (int) (mb_strlen($data, '8bit') / self::BYTE_SIZE); + } + + private static function preparePositiveInteger(string $data): string + { + if (mb_substr($data, 0, self::BYTE_SIZE, '8bit') > self::ASN1_BIG_INTEGER_LIMIT) { + return self::ASN1_NEGATIVE_INTEGER . $data; + } + + while ( + mb_substr($data, 0, self::BYTE_SIZE, '8bit') === self::ASN1_NEGATIVE_INTEGER + && mb_substr($data, 2, self::BYTE_SIZE, '8bit') <= self::ASN1_BIG_INTEGER_LIMIT + ) { + $data = mb_substr($data, 2, null, '8bit'); + } + + return $data; + } + + public function fromAsn1(string $signature, int $length): string + { + $message = bin2hex($signature); + $position = 0; + + if (self::readAsn1Content($message, $position, self::BYTE_SIZE) !== self::ASN1_SEQUENCE) { + throw ConversionFailed::incorrectStartSequence(); + } + + // @phpstan-ignore-next-line + if (self::readAsn1Content($message, $position, self::BYTE_SIZE) === self::ASN1_LENGTH_2BYTES) { + $position += self::BYTE_SIZE; + } + + $pointR = self::retrievePositiveInteger(self::readAsn1Integer($message, $position)); + $pointS = self::retrievePositiveInteger(self::readAsn1Integer($message, $position)); + + $points = hex2bin(str_pad($pointR, $length, '0', STR_PAD_LEFT) . str_pad($pointS, $length, '0', STR_PAD_LEFT)); + assert(is_string($points)); + + return $points; + } + + private static function readAsn1Content(string $message, int &$position, int $length): string + { + $content = mb_substr($message, $position, $length, '8bit'); + $position += $length; + + return $content; + } + + private static function readAsn1Integer(string $message, int &$position): string + { + if (self::readAsn1Content($message, $position, self::BYTE_SIZE) !== self::ASN1_INTEGER) { + throw ConversionFailed::integerExpected(); + } + + $length = (int) hexdec(self::readAsn1Content($message, $position, self::BYTE_SIZE)); + + return self::readAsn1Content($message, $position, $length * self::BYTE_SIZE); + } + + private static function retrievePositiveInteger(string $data): string + { + while ( + mb_substr($data, 0, self::BYTE_SIZE, '8bit') === self::ASN1_NEGATIVE_INTEGER + && mb_substr($data, 2, self::BYTE_SIZE, '8bit') > self::ASN1_BIG_INTEGER_LIMIT + ) { + $data = mb_substr($data, 2, null, '8bit'); + } + + return $data; + } +} diff --git a/vendor/lcobucci/jwt/src/Signer/Ecdsa/Sha256.php b/vendor/lcobucci/jwt/src/Signer/Ecdsa/Sha256.php new file mode 100644 index 0000000..36f6ec1 --- /dev/null +++ b/vendor/lcobucci/jwt/src/Signer/Ecdsa/Sha256.php @@ -0,0 +1,26 @@ +contents()); + } catch (SodiumException $sodiumException) { + throw new InvalidKeyProvided($sodiumException->getMessage(), 0, $sodiumException); + } + } + + public function verify(string $expected, string $payload, Key $key): bool + { + try { + return sodium_crypto_sign_verify_detached($expected, $payload, $key->contents()); + } catch (SodiumException $sodiumException) { + throw new InvalidKeyProvided($sodiumException->getMessage(), 0, $sodiumException); + } + } +} diff --git a/vendor/lcobucci/jwt/src/Signer/Hmac.php b/vendor/lcobucci/jwt/src/Signer/Hmac.php new file mode 100644 index 0000000..05b46bc --- /dev/null +++ b/vendor/lcobucci/jwt/src/Signer/Hmac.php @@ -0,0 +1,24 @@ +algorithm(), $payload, $key->contents(), true); + } + + final public function verify(string $expected, string $payload, Key $key): bool + { + return hash_equals($expected, $this->sign($payload, $key)); + } + + abstract public function algorithm(): string; +} diff --git a/vendor/lcobucci/jwt/src/Signer/Hmac/Sha256.php b/vendor/lcobucci/jwt/src/Signer/Hmac/Sha256.php new file mode 100644 index 0000000..4be692e --- /dev/null +++ b/vendor/lcobucci/jwt/src/Signer/Hmac/Sha256.php @@ -0,0 +1,19 @@ +contents = $contents; + $this->passphrase = $passphrase; + } + + public static function empty(): self + { + return new self('', ''); + } + + public static function plainText(string $contents, string $passphrase = ''): self + { + return new self($contents, $passphrase); + } + + public static function base64Encoded(string $contents, string $passphrase = ''): self + { + $decoded = SodiumBase64Polyfill::base642bin( + $contents, + SodiumBase64Polyfill::SODIUM_BASE64_VARIANT_ORIGINAL + ); + + return new self($decoded, $passphrase); + } + + /** @throws FileCouldNotBeRead */ + public static function file(string $path, string $passphrase = ''): self + { + try { + $file = new SplFileObject($path); + } catch (Throwable $exception) { + throw FileCouldNotBeRead::onPath($path, $exception); + } + + $contents = $file->fread($file->getSize()); + assert(is_string($contents)); + + return new self($contents, $passphrase); + } + + public function contents(): string + { + return $this->contents; + } + + public function passphrase(): string + { + return $this->passphrase; + } +} diff --git a/vendor/lcobucci/jwt/src/Signer/Key/LocalFileReference.php b/vendor/lcobucci/jwt/src/Signer/Key/LocalFileReference.php new file mode 100644 index 0000000..b6cb3f1 --- /dev/null +++ b/vendor/lcobucci/jwt/src/Signer/Key/LocalFileReference.php @@ -0,0 +1,54 @@ +path = $path; + $this->passphrase = $passphrase; + } + + /** @throws FileCouldNotBeRead */ + public static function file(string $path, string $passphrase = ''): self + { + if (strpos($path, self::PATH_PREFIX) === 0) { + $path = substr($path, 7); + } + + if (! file_exists($path)) { + throw FileCouldNotBeRead::onPath($path); + } + + return new self($path, $passphrase); + } + + public function contents(): string + { + if (! isset($this->contents)) { + $this->contents = InMemory::file($this->path)->contents(); + } + + return $this->contents; + } + + public function passphrase(): string + { + return $this->passphrase; + } +} diff --git a/vendor/lcobucci/jwt/src/Signer/None.php b/vendor/lcobucci/jwt/src/Signer/None.php new file mode 100644 index 0000000..4e6ecb1 --- /dev/null +++ b/vendor/lcobucci/jwt/src/Signer/None.php @@ -0,0 +1,26 @@ +getPrivateKey($pem, $passphrase); + + try { + $signature = ''; + + if (! openssl_sign($payload, $signature, $key, $this->algorithm())) { + $error = openssl_error_string(); + assert(is_string($error)); + + throw CannotSignPayload::errorHappened($error); + } + + return $signature; + } finally { + $this->freeKey($key); + } + } + + /** + * @return resource|OpenSSLAsymmetricKey + * + * @throws CannotSignPayload + */ + private function getPrivateKey(string $pem, string $passphrase) + { + $privateKey = openssl_pkey_get_private($pem, $passphrase); + $this->validateKey($privateKey); + + return $privateKey; + } + + /** @throws InvalidKeyProvided */ + final protected function verifySignature( + string $expected, + string $payload, + string $pem + ): bool { + $key = $this->getPublicKey($pem); + $result = openssl_verify($payload, $expected, $key, $this->algorithm()); + $this->freeKey($key); + + return $result === 1; + } + + /** + * @return resource|OpenSSLAsymmetricKey + * + * @throws InvalidKeyProvided + */ + private function getPublicKey(string $pem) + { + $publicKey = openssl_pkey_get_public($pem); + $this->validateKey($publicKey); + + return $publicKey; + } + + /** + * Raises an exception when the key type is not the expected type + * + * @param resource|OpenSSLAsymmetricKey|bool $key + * + * @throws InvalidKeyProvided + */ + private function validateKey($key): void + { + if (is_bool($key)) { + $error = openssl_error_string(); + assert(is_string($error)); + + throw InvalidKeyProvided::cannotBeParsed($error); + } + + $details = openssl_pkey_get_details($key); + assert(is_array($details)); + + if (! array_key_exists('key', $details) || $details['type'] !== $this->keyType()) { + throw InvalidKeyProvided::incompatibleKey(); + } + } + + /** @param resource|OpenSSLAsymmetricKey $key */ + private function freeKey($key): void + { + if ($key instanceof OpenSSLAsymmetricKey) { + return; + } + + openssl_free_key($key); // Deprecated and no longer necessary as of PHP >= 8.0 + } + + /** + * Returns the type of key to be used to create/verify the signature (using OpenSSL constants) + * + * @internal + */ + abstract public function keyType(): int; + + /** + * Returns which algorithm to be used to create/verify the signature (using OpenSSL constants) + * + * @internal + */ + abstract public function algorithm(): int; +} diff --git a/vendor/lcobucci/jwt/src/Signer/Rsa.php b/vendor/lcobucci/jwt/src/Signer/Rsa.php new file mode 100644 index 0000000..ff54dd3 --- /dev/null +++ b/vendor/lcobucci/jwt/src/Signer/Rsa.php @@ -0,0 +1,24 @@ +createSignature($key->contents(), $key->passphrase(), $payload); + } + + final public function verify(string $expected, string $payload, Key $key): bool + { + return $this->verifySignature($expected, $payload, $key->contents()); + } + + final public function keyType(): int + { + return OPENSSL_KEYTYPE_RSA; + } +} diff --git a/vendor/lcobucci/jwt/src/Signer/Rsa/Sha256.php b/vendor/lcobucci/jwt/src/Signer/Rsa/Sha256.php new file mode 100644 index 0000000..9e56c70 --- /dev/null +++ b/vendor/lcobucci/jwt/src/Signer/Rsa/Sha256.php @@ -0,0 +1,21 @@ + */ + private array $headers = ['typ' => 'JWT', 'alg' => null]; + + /** @var array */ + private array $claims = []; + + private Encoder $encoder; + private ClaimsFormatter $claimFormatter; + + public function __construct(Encoder $encoder, ClaimsFormatter $claimFormatter) + { + $this->encoder = $encoder; + $this->claimFormatter = $claimFormatter; + } + + public function permittedFor(string ...$audiences): BuilderInterface + { + $configured = $this->claims[RegisteredClaims::AUDIENCE] ?? []; + $toAppend = array_diff($audiences, $configured); + + return $this->setClaim(RegisteredClaims::AUDIENCE, array_merge($configured, $toAppend)); + } + + public function expiresAt(DateTimeImmutable $expiration): BuilderInterface + { + return $this->setClaim(RegisteredClaims::EXPIRATION_TIME, $expiration); + } + + public function identifiedBy(string $id): BuilderInterface + { + return $this->setClaim(RegisteredClaims::ID, $id); + } + + public function issuedAt(DateTimeImmutable $issuedAt): BuilderInterface + { + return $this->setClaim(RegisteredClaims::ISSUED_AT, $issuedAt); + } + + public function issuedBy(string $issuer): BuilderInterface + { + return $this->setClaim(RegisteredClaims::ISSUER, $issuer); + } + + public function canOnlyBeUsedAfter(DateTimeImmutable $notBefore): BuilderInterface + { + return $this->setClaim(RegisteredClaims::NOT_BEFORE, $notBefore); + } + + public function relatedTo(string $subject): BuilderInterface + { + return $this->setClaim(RegisteredClaims::SUBJECT, $subject); + } + + /** @inheritdoc */ + public function withHeader(string $name, $value): BuilderInterface + { + $this->headers[$name] = $value; + + return $this; + } + + /** @inheritdoc */ + public function withClaim(string $name, $value): BuilderInterface + { + if (in_array($name, RegisteredClaims::ALL, true)) { + throw RegisteredClaimGiven::forClaim($name); + } + + return $this->setClaim($name, $value); + } + + /** @param mixed $value */ + private function setClaim(string $name, $value): BuilderInterface + { + $this->claims[$name] = $value; + + return $this; + } + + /** + * @param array $items + * + * @throws CannotEncodeContent When data cannot be converted to JSON. + */ + private function encode(array $items): string + { + return $this->encoder->base64UrlEncode( + $this->encoder->jsonEncode($items) + ); + } + + public function getToken(Signer $signer, Key $key): Plain + { + $headers = $this->headers; + $headers['alg'] = $signer->algorithmId(); + + $encodedHeaders = $this->encode($headers); + $encodedClaims = $this->encode($this->claimFormatter->formatClaims($this->claims)); + + $signature = $signer->sign($encodedHeaders . '.' . $encodedClaims, $key); + $encodedSignature = $this->encoder->base64UrlEncode($signature); + + return new Plain( + new DataSet($headers, $encodedHeaders), + new DataSet($this->claims, $encodedClaims), + new Signature($signature, $encodedSignature) + ); + } +} diff --git a/vendor/lcobucci/jwt/src/Token/DataSet.php b/vendor/lcobucci/jwt/src/Token/DataSet.php new file mode 100644 index 0000000..f9256d6 --- /dev/null +++ b/vendor/lcobucci/jwt/src/Token/DataSet.php @@ -0,0 +1,46 @@ + */ + private array $data; + private string $encoded; + + /** @param mixed[] $data */ + public function __construct(array $data, string $encoded) + { + $this->data = $data; + $this->encoded = $encoded; + } + + /** + * @param mixed|null $default + * + * @return mixed|null + */ + public function get(string $name, $default = null) + { + return $this->data[$name] ?? $default; + } + + public function has(string $name): bool + { + return array_key_exists($name, $this->data); + } + + /** @return mixed[] */ + public function all(): array + { + return $this->data; + } + + public function toString(): string + { + return $this->encoded; + } +} diff --git a/vendor/lcobucci/jwt/src/Token/InvalidTokenStructure.php b/vendor/lcobucci/jwt/src/Token/InvalidTokenStructure.php new file mode 100644 index 0000000..49c2840 --- /dev/null +++ b/vendor/lcobucci/jwt/src/Token/InvalidTokenStructure.php @@ -0,0 +1,25 @@ +decoder = $decoder; + } + + public function parse(string $jwt): TokenInterface + { + [$encodedHeaders, $encodedClaims, $encodedSignature] = $this->splitJwt($jwt); + + $header = $this->parseHeader($encodedHeaders); + + return new Plain( + new DataSet($header, $encodedHeaders), + new DataSet($this->parseClaims($encodedClaims), $encodedClaims), + $this->parseSignature($header, $encodedSignature) + ); + } + + /** + * Splits the JWT string into an array + * + * @return string[] + * + * @throws InvalidTokenStructure When JWT doesn't have all parts. + */ + private function splitJwt(string $jwt): array + { + $data = explode('.', $jwt); + + if (count($data) !== 3) { + throw InvalidTokenStructure::missingOrNotEnoughSeparators(); + } + + return $data; + } + + /** + * Parses the header from a string + * + * @return mixed[] + * + * @throws UnsupportedHeaderFound When an invalid header is informed. + * @throws InvalidTokenStructure When parsed content isn't an array. + */ + private function parseHeader(string $data): array + { + $header = $this->decoder->jsonDecode($this->decoder->base64UrlDecode($data)); + + if (! is_array($header)) { + throw InvalidTokenStructure::arrayExpected('headers'); + } + + if (array_key_exists('enc', $header)) { + throw UnsupportedHeaderFound::encryption(); + } + + if (! array_key_exists('typ', $header)) { + $header['typ'] = 'JWT'; + } + + return $header; + } + + /** + * Parses the claim set from a string + * + * @return mixed[] + * + * @throws InvalidTokenStructure When parsed content isn't an array or contains non-parseable dates. + */ + private function parseClaims(string $data): array + { + $claims = $this->decoder->jsonDecode($this->decoder->base64UrlDecode($data)); + + if (! is_array($claims)) { + throw InvalidTokenStructure::arrayExpected('claims'); + } + + if (array_key_exists(RegisteredClaims::AUDIENCE, $claims)) { + $claims[RegisteredClaims::AUDIENCE] = (array) $claims[RegisteredClaims::AUDIENCE]; + } + + foreach (RegisteredClaims::DATE_CLAIMS as $claim) { + if (! array_key_exists($claim, $claims)) { + continue; + } + + $claims[$claim] = $this->convertDate($claims[$claim]); + } + + return $claims; + } + + /** + * @param int|float|string $timestamp + * + * @throws InvalidTokenStructure + */ + private function convertDate($timestamp): DateTimeImmutable + { + if (! is_numeric($timestamp)) { + throw InvalidTokenStructure::dateIsNotParseable($timestamp); + } + + $normalizedTimestamp = number_format((float) $timestamp, self::MICROSECOND_PRECISION, '.', ''); + + $date = DateTimeImmutable::createFromFormat('U.u', $normalizedTimestamp); + + if ($date === false) { + throw InvalidTokenStructure::dateIsNotParseable($normalizedTimestamp); + } + + return $date; + } + + /** + * Returns the signature from given data + * + * @param mixed[] $header + */ + private function parseSignature(array $header, string $data): Signature + { + if ($data === '' || ! array_key_exists('alg', $header) || $header['alg'] === 'none') { + return Signature::fromEmptyData(); + } + + $hash = $this->decoder->base64UrlDecode($data); + + return new Signature($hash, $data); + } +} diff --git a/vendor/lcobucci/jwt/src/Token/Plain.php b/vendor/lcobucci/jwt/src/Token/Plain.php new file mode 100644 index 0000000..8903e6a --- /dev/null +++ b/vendor/lcobucci/jwt/src/Token/Plain.php @@ -0,0 +1,92 @@ +headers = $headers; + $this->claims = $claims; + $this->signature = $signature; + } + + public function headers(): DataSet + { + return $this->headers; + } + + public function claims(): DataSet + { + return $this->claims; + } + + public function signature(): Signature + { + return $this->signature; + } + + public function payload(): string + { + return $this->headers->toString() . '.' . $this->claims->toString(); + } + + public function isPermittedFor(string $audience): bool + { + return in_array($audience, $this->claims->get(RegisteredClaims::AUDIENCE, []), true); + } + + public function isIdentifiedBy(string $id): bool + { + return $this->claims->get(RegisteredClaims::ID) === $id; + } + + public function isRelatedTo(string $subject): bool + { + return $this->claims->get(RegisteredClaims::SUBJECT) === $subject; + } + + public function hasBeenIssuedBy(string ...$issuers): bool + { + return in_array($this->claims->get(RegisteredClaims::ISSUER), $issuers, true); + } + + public function hasBeenIssuedBefore(DateTimeInterface $now): bool + { + return $now >= $this->claims->get(RegisteredClaims::ISSUED_AT); + } + + public function isMinimumTimeBefore(DateTimeInterface $now): bool + { + return $now >= $this->claims->get(RegisteredClaims::NOT_BEFORE); + } + + public function isExpired(DateTimeInterface $now): bool + { + if (! $this->claims->has(RegisteredClaims::EXPIRATION_TIME)) { + return false; + } + + return $now >= $this->claims->get(RegisteredClaims::EXPIRATION_TIME); + } + + public function toString(): string + { + return $this->headers->toString() . '.' + . $this->claims->toString() . '.' + . $this->signature->toString(); + } +} diff --git a/vendor/lcobucci/jwt/src/Token/RegisteredClaimGiven.php b/vendor/lcobucci/jwt/src/Token/RegisteredClaimGiven.php new file mode 100644 index 0000000..e70996e --- /dev/null +++ b/vendor/lcobucci/jwt/src/Token/RegisteredClaimGiven.php @@ -0,0 +1,20 @@ +hash = $hash; + $this->encoded = $encoded; + } + + public static function fromEmptyData(): self + { + return new self('', ''); + } + + public function hash(): string + { + return $this->hash; + } + + /** + * Returns the encoded version of the signature + */ + public function toString(): string + { + return $this->encoded; + } +} diff --git a/vendor/lcobucci/jwt/src/Token/UnsupportedHeaderFound.php b/vendor/lcobucci/jwt/src/Token/UnsupportedHeaderFound.php new file mode 100644 index 0000000..1824078 --- /dev/null +++ b/vendor/lcobucci/jwt/src/Token/UnsupportedHeaderFound.php @@ -0,0 +1,15 @@ +id = $id; + } + + public function assert(Token $token): void + { + if (! $token->isIdentifiedBy($this->id)) { + throw new ConstraintViolation( + 'The token is not identified with the expected ID' + ); + } + } +} diff --git a/vendor/lcobucci/jwt/src/Validation/Constraint/IssuedBy.php b/vendor/lcobucci/jwt/src/Validation/Constraint/IssuedBy.php new file mode 100644 index 0000000..5f5346e --- /dev/null +++ b/vendor/lcobucci/jwt/src/Validation/Constraint/IssuedBy.php @@ -0,0 +1,28 @@ +issuers = $issuers; + } + + public function assert(Token $token): void + { + if (! $token->hasBeenIssuedBy(...$this->issuers)) { + throw new ConstraintViolation( + 'The token was not issued by the given issuers' + ); + } + } +} diff --git a/vendor/lcobucci/jwt/src/Validation/Constraint/LeewayCannotBeNegative.php b/vendor/lcobucci/jwt/src/Validation/Constraint/LeewayCannotBeNegative.php new file mode 100644 index 0000000..53abc0d --- /dev/null +++ b/vendor/lcobucci/jwt/src/Validation/Constraint/LeewayCannotBeNegative.php @@ -0,0 +1,15 @@ +clock = $clock; + $this->leeway = $this->guardLeeway($leeway); + } + + private function guardLeeway(?DateInterval $leeway): DateInterval + { + if ($leeway === null) { + return new DateInterval('PT0S'); + } + + if ($leeway->invert === 1) { + throw LeewayCannotBeNegative::create(); + } + + return $leeway; + } + + public function assert(Token $token): void + { + $now = $this->clock->now(); + + $this->assertIssueTime($token, $now->add($this->leeway)); + $this->assertMinimumTime($token, $now->add($this->leeway)); + $this->assertExpiration($token, $now->sub($this->leeway)); + } + + /** @throws ConstraintViolation */ + private function assertExpiration(Token $token, DateTimeInterface $now): void + { + if ($token->isExpired($now)) { + throw new ConstraintViolation('The token is expired'); + } + } + + /** @throws ConstraintViolation */ + private function assertMinimumTime(Token $token, DateTimeInterface $now): void + { + if (! $token->isMinimumTimeBefore($now)) { + throw new ConstraintViolation('The token cannot be used yet'); + } + } + + /** @throws ConstraintViolation */ + private function assertIssueTime(Token $token, DateTimeInterface $now): void + { + if (! $token->hasBeenIssuedBefore($now)) { + throw new ConstraintViolation('The token was issued in the future'); + } + } +} diff --git a/vendor/lcobucci/jwt/src/Validation/Constraint/PermittedFor.php b/vendor/lcobucci/jwt/src/Validation/Constraint/PermittedFor.php new file mode 100644 index 0000000..1155697 --- /dev/null +++ b/vendor/lcobucci/jwt/src/Validation/Constraint/PermittedFor.php @@ -0,0 +1,27 @@ +audience = $audience; + } + + public function assert(Token $token): void + { + if (! $token->isPermittedFor($this->audience)) { + throw new ConstraintViolation( + 'The token is not allowed to be used by this audience' + ); + } + } +} diff --git a/vendor/lcobucci/jwt/src/Validation/Constraint/RelatedTo.php b/vendor/lcobucci/jwt/src/Validation/Constraint/RelatedTo.php new file mode 100644 index 0000000..8beecd7 --- /dev/null +++ b/vendor/lcobucci/jwt/src/Validation/Constraint/RelatedTo.php @@ -0,0 +1,27 @@ +subject = $subject; + } + + public function assert(Token $token): void + { + if (! $token->isRelatedTo($this->subject)) { + throw new ConstraintViolation( + 'The token is not related to the expected subject' + ); + } + } +} diff --git a/vendor/lcobucci/jwt/src/Validation/Constraint/SignedWith.php b/vendor/lcobucci/jwt/src/Validation/Constraint/SignedWith.php new file mode 100644 index 0000000..1bc1e90 --- /dev/null +++ b/vendor/lcobucci/jwt/src/Validation/Constraint/SignedWith.php @@ -0,0 +1,37 @@ +signer = $signer; + $this->key = $key; + } + + public function assert(Token $token): void + { + if (! $token instanceof UnencryptedToken) { + throw new ConstraintViolation('You should pass a plain token'); + } + + if ($token->headers()->get('alg') !== $this->signer->algorithmId()) { + throw new ConstraintViolation('Token signer mismatch'); + } + + if (! $this->signer->verify($token->signature()->hash(), $token->payload(), $this->key)) { + throw new ConstraintViolation('Token signature mismatch'); + } + } +} diff --git a/vendor/lcobucci/jwt/src/Validation/Constraint/StrictValidAt.php b/vendor/lcobucci/jwt/src/Validation/Constraint/StrictValidAt.php new file mode 100644 index 0000000..bb22109 --- /dev/null +++ b/vendor/lcobucci/jwt/src/Validation/Constraint/StrictValidAt.php @@ -0,0 +1,86 @@ +clock = $clock; + $this->leeway = $this->guardLeeway($leeway); + } + + private function guardLeeway(?DateInterval $leeway): DateInterval + { + if ($leeway === null) { + return new DateInterval('PT0S'); + } + + if ($leeway->invert === 1) { + throw LeewayCannotBeNegative::create(); + } + + return $leeway; + } + + public function assert(Token $token): void + { + if (! $token instanceof UnencryptedToken) { + throw new ConstraintViolation('You should pass a plain token'); + } + + $now = $this->clock->now(); + + $this->assertIssueTime($token, $now->add($this->leeway)); + $this->assertMinimumTime($token, $now->add($this->leeway)); + $this->assertExpiration($token, $now->sub($this->leeway)); + } + + /** @throws ConstraintViolation */ + private function assertExpiration(UnencryptedToken $token, DateTimeInterface $now): void + { + if (! $token->claims()->has(Token\RegisteredClaims::EXPIRATION_TIME)) { + throw new ConstraintViolation('"Expiration Time" claim missing'); + } + + if ($token->isExpired($now)) { + throw new ConstraintViolation('The token is expired'); + } + } + + /** @throws ConstraintViolation */ + private function assertMinimumTime(UnencryptedToken $token, DateTimeInterface $now): void + { + if (! $token->claims()->has(Token\RegisteredClaims::NOT_BEFORE)) { + throw new ConstraintViolation('"Not Before" claim missing'); + } + + if (! $token->isMinimumTimeBefore($now)) { + throw new ConstraintViolation('The token cannot be used yet'); + } + } + + /** @throws ConstraintViolation */ + private function assertIssueTime(UnencryptedToken $token, DateTimeInterface $now): void + { + if (! $token->claims()->has(Token\RegisteredClaims::ISSUED_AT)) { + throw new ConstraintViolation('"Issued At" claim missing'); + } + + if (! $token->hasBeenIssuedBefore($now)) { + throw new ConstraintViolation('The token was issued in the future'); + } + } +} diff --git a/vendor/lcobucci/jwt/src/Validation/Constraint/ValidAt.php b/vendor/lcobucci/jwt/src/Validation/Constraint/ValidAt.php new file mode 100644 index 0000000..db7f6ba --- /dev/null +++ b/vendor/lcobucci/jwt/src/Validation/Constraint/ValidAt.php @@ -0,0 +1,25 @@ +constraint = new LooseValidAt($clock, $leeway); + } + + public function assert(Token $token): void + { + $this->constraint->assert($token); + } +} diff --git a/vendor/lcobucci/jwt/src/Validation/ConstraintViolation.php b/vendor/lcobucci/jwt/src/Validation/ConstraintViolation.php new file mode 100644 index 0000000..39a56ed --- /dev/null +++ b/vendor/lcobucci/jwt/src/Validation/ConstraintViolation.php @@ -0,0 +1,11 @@ +violations = $violations; + + return $exception; + } + + /** @param ConstraintViolation[] $violations */ + private static function buildMessage(array $violations): string + { + $violations = array_map( + static function (ConstraintViolation $violation): string { + return '- ' . $violation->getMessage(); + }, + $violations + ); + + $message = "The token violates some mandatory constraints, details:\n"; + $message .= implode("\n", $violations); + + return $message; + } + + /** @return ConstraintViolation[] */ + public function violations(): array + { + return $this->violations; + } +} diff --git a/vendor/lcobucci/jwt/src/Validation/Validator.php b/vendor/lcobucci/jwt/src/Validation/Validator.php new file mode 100644 index 0000000..62d0618 --- /dev/null +++ b/vendor/lcobucci/jwt/src/Validation/Validator.php @@ -0,0 +1,56 @@ +checkConstraint($constraint, $token, $violations); + } + + if ($violations) { + throw RequiredConstraintsViolated::fromViolations(...$violations); + } + } + + /** @param ConstraintViolation[] $violations */ + private function checkConstraint( + Constraint $constraint, + Token $token, + array &$violations + ): void { + try { + $constraint->assert($token); + } catch (ConstraintViolation $e) { + $violations[] = $e; + } + } + + public function validate(Token $token, Constraint ...$constraints): bool + { + if ($constraints === []) { + throw new NoConstraintsGiven('No constraint given.'); + } + + try { + foreach ($constraints as $constraint) { + $constraint->assert($token); + } + + return true; + } catch (ConstraintViolation $e) { + return false; + } + } +} diff --git a/vendor/lcobucci/jwt/src/Validator.php b/vendor/lcobucci/jwt/src/Validator.php new file mode 100644 index 0000000..d0ce4b8 --- /dev/null +++ b/vendor/lcobucci/jwt/src/Validator.php @@ -0,0 +1,20 @@ +level(Symfony\CS\FixerInterface::PSR2_LEVEL) + ->fixers(['-yoda_conditions', 'ordered_use', 'short_array_syntax']) + ->finder(Symfony\CS\Finder\DefaultFinder::create() + ->in(__DIR__.'/src/')); \ No newline at end of file diff --git a/vendor/league/flysystem-cached-adapter/.scrutinizer.yml b/vendor/league/flysystem-cached-adapter/.scrutinizer.yml new file mode 100644 index 0000000..fa39b52 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/.scrutinizer.yml @@ -0,0 +1,34 @@ +filter: + paths: [src/*] +checks: + php: + code_rating: true + remove_extra_empty_lines: true + remove_php_closing_tag: true + remove_trailing_whitespace: true + fix_use_statements: + remove_unused: true + preserve_multiple: false + preserve_blanklines: true + order_alphabetically: true + fix_php_opening_tag: true + fix_linefeed: true + fix_line_ending: true + fix_identation_4spaces: true + fix_doc_comments: true +tools: + external_code_coverage: + timeout: 900 + runs: 6 + php_code_coverage: false + php_code_sniffer: + config: + standard: PSR2 + filter: + paths: ['src'] + php_loc: + enabled: true + excluded_dirs: [vendor, spec, stubs] + php_cpd: + enabled: true + excluded_dirs: [vendor, spec, stubs] \ No newline at end of file diff --git a/vendor/league/flysystem-cached-adapter/.travis.yml b/vendor/league/flysystem-cached-adapter/.travis.yml new file mode 100644 index 0000000..6706449 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/.travis.yml @@ -0,0 +1,29 @@ +language: php + +php: + - 5.5 + - 5.6 + - 7.0 + - 7.1 + - 7.2 + +matrix: + allow_failures: + - php: 5.5 + +env: + - COMPOSER_OPTS="" + - COMPOSER_OPTS="--prefer-lowest" + +install: + - if [[ "${TRAVIS_PHP_VERSION}" == "5.5" ]]; then composer require phpunit/phpunit:^4.8.36 phpspec/phpspec:^2 --prefer-dist --update-with-dependencies; fi + - if [[ "${TRAVIS_PHP_VERSION}" == "7.2" ]]; then composer require phpunit/phpunit:^6.0 --prefer-dist --update-with-dependencies; fi + - travis_retry composer update --prefer-dist $COMPOSER_OPTS + +script: + - vendor/bin/phpspec run + - vendor/bin/phpunit + +after_script: + - wget https://scrutinizer-ci.com/ocular.phar' + - php ocular.phar code-coverage:upload --format=php-clover ./clover/phpunit.xml' diff --git a/vendor/league/flysystem-cached-adapter/LICENSE b/vendor/league/flysystem-cached-adapter/LICENSE new file mode 100644 index 0000000..666f6c8 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015 Frank de Jonge + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/league/flysystem-cached-adapter/clover/.gitignore b/vendor/league/flysystem-cached-adapter/clover/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/clover/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/vendor/league/flysystem-cached-adapter/composer.json b/vendor/league/flysystem-cached-adapter/composer.json new file mode 100644 index 0000000..df7fb7f --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/composer.json @@ -0,0 +1,30 @@ +{ + "name": "league/flysystem-cached-adapter", + "description": "An adapter decorator to enable meta-data caching.", + "autoload": { + "psr-4": { + "League\\Flysystem\\Cached\\": "src/" + } + }, + "require": { + "league/flysystem": "~1.0", + "psr/cache": "^1.0.0" + }, + "require-dev": { + "phpspec/phpspec": "^3.4", + "phpunit/phpunit": "^5.7", + "mockery/mockery": "~0.9", + "predis/predis": "~1.0", + "tedivm/stash": "~0.12" + }, + "suggest": { + "ext-phpredis": "Pure C implemented extension for PHP" + }, + "license": "MIT", + "authors": [ + { + "name": "frankdejonge", + "email": "info@frenky.net" + } + ] +} diff --git a/vendor/league/flysystem-cached-adapter/phpspec.yml b/vendor/league/flysystem-cached-adapter/phpspec.yml new file mode 100644 index 0000000..5eabcb2 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/phpspec.yml @@ -0,0 +1,6 @@ +--- +suites: + cached_adapter_suite: + namespace: League\Flysystem\Cached + psr4_prefix: League\Flysystem\Cached +formatter.name: pretty diff --git a/vendor/league/flysystem-cached-adapter/phpunit.php b/vendor/league/flysystem-cached-adapter/phpunit.php new file mode 100644 index 0000000..d109587 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/phpunit.php @@ -0,0 +1,3 @@ + + + + + ./tests/ + + + + + ./src/ + + + + + + + + diff --git a/vendor/league/flysystem-cached-adapter/readme.md b/vendor/league/flysystem-cached-adapter/readme.md new file mode 100644 index 0000000..dd1433d --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/readme.md @@ -0,0 +1,20 @@ +# Flysystem Cached CachedAdapter + +[![Author](http://img.shields.io/badge/author-@frankdejonge-blue.svg?style=flat-square)](https://twitter.com/frankdejonge) +[![Build Status](https://img.shields.io/travis/thephpleague/flysystem-cached-adapter/master.svg?style=flat-square)](https://travis-ci.org/thephpleague/flysystem-cached-adapter) +[![Coverage Status](https://img.shields.io/scrutinizer/coverage/g/thephpleague/flysystem-cached-adapter.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/flysystem-cached-adapter/code-structure) +[![Quality Score](https://img.shields.io/scrutinizer/g/thephpleague/flysystem-cached-adapter.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/flysystem-cached-adapter) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) +[![Packagist Version](https://img.shields.io/packagist/v/league/flysystem-cached-adapter.svg?style=flat-square)](https://packagist.org/packages/league/flysystem-cached-adapter) +[![Total Downloads](https://img.shields.io/packagist/dt/league/flysystem-cached-adapter.svg?style=flat-square)](https://packagist.org/packages/league/flysystem-cached-adapter) + + +The adapter decorator caches metadata and directory listings. + +```bash +composer require league/flysystem-cached-adapter +``` + +## Usage + +[Check out the docs.](https://flysystem.thephpleague.com/docs/advanced/caching/) diff --git a/vendor/league/flysystem-cached-adapter/spec/CachedAdapterSpec.php b/vendor/league/flysystem-cached-adapter/spec/CachedAdapterSpec.php new file mode 100644 index 0000000..69428d9 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/spec/CachedAdapterSpec.php @@ -0,0 +1,435 @@ +adapter = $adapter; + $this->cache = $cache; + $this->cache->load()->shouldBeCalled(); + $this->beConstructedWith($adapter, $cache); + } + + public function it_is_initializable() + { + $this->shouldHaveType('League\Flysystem\Cached\CachedAdapter'); + $this->shouldHaveType('League\Flysystem\AdapterInterface'); + } + + public function it_should_forward_read_streams() + { + $path = 'path.txt'; + $response = ['path' => $path]; + $this->adapter->readStream($path)->willReturn($response); + $this->readStream($path)->shouldbe($response); + } + + public function it_should_cache_writes() + { + $type = 'file'; + $path = 'path.txt'; + $contents = 'contents'; + $config = new Config(); + $response = compact('path', 'contents', 'type'); + $this->adapter->write($path, $contents, $config)->willReturn($response); + $this->cache->updateObject($path, $response, true)->shouldBeCalled(); + $this->write($path, $contents, $config)->shouldBe($response); + } + + public function it_should_cache_streamed_writes() + { + $type = 'file'; + $path = 'path.txt'; + $stream = tmpfile(); + $config = new Config(); + $response = compact('path', 'stream', 'type'); + $this->adapter->writeStream($path, $stream, $config)->willReturn($response); + $this->cache->updateObject($path, ['contents' => false] + $response, true)->shouldBeCalled(); + $this->writeStream($path, $stream, $config)->shouldBe($response); + fclose($stream); + } + + public function it_should_cache_streamed_updates() + { + $type = 'file'; + $path = 'path.txt'; + $stream = tmpfile(); + $config = new Config(); + $response = compact('path', 'stream', 'type'); + $this->adapter->updateStream($path, $stream, $config)->willReturn($response); + $this->cache->updateObject($path, ['contents' => false] + $response, true)->shouldBeCalled(); + $this->updateStream($path, $stream, $config)->shouldBe($response); + fclose($stream); + } + + public function it_should_ignore_failed_writes() + { + $path = 'path.txt'; + $contents = 'contents'; + $config = new Config(); + $this->adapter->write($path, $contents, $config)->willReturn(false); + $this->write($path, $contents, $config)->shouldBe(false); + } + + public function it_should_ignore_failed_streamed_writes() + { + $path = 'path.txt'; + $contents = tmpfile(); + $config = new Config(); + $this->adapter->writeStream($path, $contents, $config)->willReturn(false); + $this->writeStream($path, $contents, $config)->shouldBe(false); + fclose($contents); + } + + public function it_should_cache_updated() + { + $type = 'file'; + $path = 'path.txt'; + $contents = 'contents'; + $config = new Config(); + $response = compact('path', 'contents', 'type'); + $this->adapter->update($path, $contents, $config)->willReturn($response); + $this->cache->updateObject($path, $response, true)->shouldBeCalled(); + $this->update($path, $contents, $config)->shouldBe($response); + } + + public function it_should_ignore_failed_updates() + { + $path = 'path.txt'; + $contents = 'contents'; + $config = new Config(); + $this->adapter->update($path, $contents, $config)->willReturn(false); + $this->update($path, $contents, $config)->shouldBe(false); + } + + public function it_should_ignore_failed_streamed_updates() + { + $path = 'path.txt'; + $contents = tmpfile(); + $config = new Config(); + $this->adapter->updateStream($path, $contents, $config)->willReturn(false); + $this->updateStream($path, $contents, $config)->shouldBe(false); + fclose($contents); + } + + public function it_should_cache_renames() + { + $old = 'old.txt'; + $new = 'new.txt'; + $this->adapter->rename($old, $new)->willReturn(true); + $this->cache->rename($old, $new)->shouldBeCalled(); + $this->rename($old, $new)->shouldBe(true); + } + + public function it_should_ignore_rename_fails() + { + $old = 'old.txt'; + $new = 'new.txt'; + $this->adapter->rename($old, $new)->willReturn(false); + $this->rename($old, $new)->shouldBe(false); + } + + public function it_should_cache_copies() + { + $old = 'old.txt'; + $new = 'new.txt'; + $this->adapter->copy($old, $new)->willReturn(true); + $this->cache->copy($old, $new)->shouldBeCalled(); + $this->copy($old, $new)->shouldBe(true); + } + + public function it_should_ignore_copy_fails() + { + $old = 'old.txt'; + $new = 'new.txt'; + $this->adapter->copy($old, $new)->willReturn(false); + $this->copy($old, $new)->shouldBe(false); + } + + public function it_should_cache_deletes() + { + $delete = 'delete.txt'; + $this->adapter->delete($delete)->willReturn(true); + $this->cache->delete($delete)->shouldBeCalled(); + $this->delete($delete)->shouldBe(true); + } + + public function it_should_ignore_delete_fails() + { + $delete = 'delete.txt'; + $this->adapter->delete($delete)->willReturn(false); + $this->delete($delete)->shouldBe(false); + } + + public function it_should_cache_dir_deletes() + { + $delete = 'delete'; + $this->adapter->deleteDir($delete)->willReturn(true); + $this->cache->deleteDir($delete)->shouldBeCalled(); + $this->deleteDir($delete)->shouldBe(true); + } + + public function it_should_ignore_delete_dir_fails() + { + $delete = 'delete'; + $this->adapter->deleteDir($delete)->willReturn(false); + $this->deleteDir($delete)->shouldBe(false); + } + + public function it_should_cache_dir_creates() + { + $dirname = 'dirname'; + $config = new Config(); + $response = ['path' => $dirname, 'type' => 'dir']; + $this->adapter->createDir($dirname, $config)->willReturn($response); + $this->cache->updateObject($dirname, $response, true)->shouldBeCalled(); + $this->createDir($dirname, $config)->shouldBe($response); + } + + public function it_should_ignore_create_dir_fails() + { + $dirname = 'dirname'; + $config = new Config(); + $this->adapter->createDir($dirname, $config)->willReturn(false); + $this->createDir($dirname, $config)->shouldBe(false); + } + + public function it_should_cache_set_visibility() + { + $path = 'path.txt'; + $visibility = AdapterInterface::VISIBILITY_PUBLIC; + $this->adapter->setVisibility($path, $visibility)->willReturn(true); + $this->cache->updateObject($path, ['path' => $path, 'visibility' => $visibility], true)->shouldBeCalled(); + $this->setVisibility($path, $visibility)->shouldBe(true); + } + + public function it_should_ignore_set_visibility_fails() + { + $dirname = 'delete'; + $visibility = AdapterInterface::VISIBILITY_PUBLIC; + $this->adapter->setVisibility($dirname, $visibility)->willReturn(false); + $this->setVisibility($dirname, $visibility)->shouldBe(false); + } + + public function it_should_indicate_missing_files() + { + $this->cache->has($path = 'path.txt')->willReturn(false); + $this->has($path)->shouldBe(false); + } + + public function it_should_indicate_file_existance() + { + $this->cache->has($path = 'path.txt')->willReturn(true); + $this->has($path)->shouldBe(true); + } + + public function it_should_cache_missing_files() + { + $this->cache->has($path = 'path.txt')->willReturn(null); + $this->adapter->has($path)->willReturn(false); + $this->cache->storeMiss($path)->shouldBeCalled(); + $this->has($path)->shouldBe(false); + } + + public function it_should_delete_when_metadata_is_missing() + { + $path = 'path.txt'; + $this->cache->has($path)->willReturn(true); + $this->cache->getSize($path)->willReturn(['path' => $path]); + $this->adapter->getSize($path)->willReturn($response = ['path' => $path, 'size' => 1024]); + $this->cache->updateObject($path, $response, true)->shouldBeCalled(); + $this->getSize($path)->shouldBe($response); + } + + public function it_should_cache_has() + { + $this->cache->has($path = 'path.txt')->willReturn(null); + $this->adapter->has($path)->willReturn(true); + $this->cache->updateObject($path, compact('path'), true)->shouldBeCalled(); + $this->has($path)->shouldBe(true); + } + + public function it_should_list_cached_contents() + { + $this->cache->isComplete($dirname = 'dirname', $recursive = true)->willReturn(true); + $response = [['path' => 'path.txt']]; + $this->cache->listContents($dirname, $recursive)->willReturn($response); + $this->listContents($dirname, $recursive)->shouldBe($response); + } + + public function it_should_ignore_failed_list_contents() + { + $this->cache->isComplete($dirname = 'dirname', $recursive = true)->willReturn(false); + $this->adapter->listContents($dirname, $recursive)->willReturn(false); + $this->listContents($dirname, $recursive)->shouldBe(false); + } + + public function it_should_cache_contents_listings() + { + $this->cache->isComplete($dirname = 'dirname', $recursive = true)->willReturn(false); + $response = [['path' => 'path.txt']]; + $this->adapter->listContents($dirname, $recursive)->willReturn($response); + $this->cache->storeContents($dirname, $response, $recursive)->shouldBeCalled(); + $this->listContents($dirname, $recursive)->shouldBe($response); + } + + public function it_should_use_cached_visibility() + { + $this->make_it_use_getter_cache('getVisibility', 'path.txt', [ + 'path' => 'path.txt', + 'visibility' => AdapterInterface::VISIBILITY_PUBLIC, + ]); + } + + public function it_should_cache_get_visibility() + { + $path = 'path.txt'; + $response = ['visibility' => AdapterInterface::VISIBILITY_PUBLIC, 'path' => $path]; + $this->make_it_cache_getter('getVisibility', $path, $response); + } + + public function it_should_ignore_failed_get_visibility() + { + $path = 'path.txt'; + $this->make_it_ignore_failed_getter('getVisibility', $path); + } + + public function it_should_use_cached_timestamp() + { + $this->make_it_use_getter_cache('getTimestamp', 'path.txt', [ + 'path' => 'path.txt', + 'timestamp' => 1234, + ]); + } + + public function it_should_cache_timestamps() + { + $this->make_it_cache_getter('getTimestamp', 'path.txt', [ + 'path' => 'path.txt', + 'timestamp' => 1234, + ]); + } + + public function it_should_ignore_failed_get_timestamps() + { + $this->make_it_ignore_failed_getter('getTimestamp', 'path.txt'); + } + + public function it_should_cache_get_metadata() + { + $path = 'path.txt'; + $response = ['visibility' => AdapterInterface::VISIBILITY_PUBLIC, 'path' => $path]; + $this->make_it_cache_getter('getMetadata', $path, $response); + } + + public function it_should_use_cached_metadata() + { + $this->make_it_use_getter_cache('getMetadata', 'path.txt', [ + 'path' => 'path.txt', + 'timestamp' => 1234, + ]); + } + + public function it_should_ignore_failed_get_metadata() + { + $this->make_it_ignore_failed_getter('getMetadata', 'path.txt'); + } + + public function it_should_cache_get_size() + { + $path = 'path.txt'; + $response = ['size' => 1234, 'path' => $path]; + $this->make_it_cache_getter('getSize', $path, $response); + } + + public function it_should_use_cached_size() + { + $this->make_it_use_getter_cache('getSize', 'path.txt', [ + 'path' => 'path.txt', + 'size' => 1234, + ]); + } + + public function it_should_ignore_failed_get_size() + { + $this->make_it_ignore_failed_getter('getSize', 'path.txt'); + } + + public function it_should_cache_get_mimetype() + { + $path = 'path.txt'; + $response = ['mimetype' => 'text/plain', 'path' => $path]; + $this->make_it_cache_getter('getMimetype', $path, $response); + } + + public function it_should_use_cached_mimetype() + { + $this->make_it_use_getter_cache('getMimetype', 'path.txt', [ + 'path' => 'path.txt', + 'mimetype' => 'text/plain', + ]); + } + + public function it_should_ignore_failed_get_mimetype() + { + $this->make_it_ignore_failed_getter('getMimetype', 'path.txt'); + } + + public function it_should_cache_reads() + { + $path = 'path.txt'; + $response = ['path' => $path, 'contents' => 'contents']; + $this->make_it_cache_getter('read', $path, $response); + } + + public function it_should_use_cached_file_contents() + { + $this->make_it_use_getter_cache('read', 'path.txt', [ + 'path' => 'path.txt', + 'contents' => 'contents' + ]); + } + + public function it_should_ignore_failed_reads() + { + $this->make_it_ignore_failed_getter('read', 'path.txt'); + } + + protected function make_it_use_getter_cache($method, $path, $response) + { + $this->cache->{$method}($path)->willReturn($response); + $this->{$method}($path)->shouldBe($response); + } + + protected function make_it_cache_getter($method, $path, $response) + { + $this->cache->{$method}($path)->willReturn(false); + $this->adapter->{$method}($path)->willReturn($response); + $this->cache->updateObject($path, $response, true)->shouldBeCalled(); + $this->{$method}($path)->shouldBe($response); + } + + protected function make_it_ignore_failed_getter($method, $path) + { + $this->cache->{$method}($path)->willReturn(false); + $this->adapter->{$method}($path)->willReturn(false); + $this->{$method}($path)->shouldBe(false); + } +} diff --git a/vendor/league/flysystem-cached-adapter/src/CacheInterface.php b/vendor/league/flysystem-cached-adapter/src/CacheInterface.php new file mode 100644 index 0000000..de3ab3d --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/src/CacheInterface.php @@ -0,0 +1,101 @@ +adapter = $adapter; + $this->cache = $cache; + $this->cache->load(); + } + + /** + * Get the underlying Adapter implementation. + * + * @return AdapterInterface + */ + public function getAdapter() + { + return $this->adapter; + } + + /** + * Get the used Cache implementation. + * + * @return CacheInterface + */ + public function getCache() + { + return $this->cache; + } + + /** + * {@inheritdoc} + */ + public function write($path, $contents, Config $config) + { + $result = $this->adapter->write($path, $contents, $config); + + if ($result !== false) { + $result['type'] = 'file'; + $this->cache->updateObject($path, $result + compact('path', 'contents'), true); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function writeStream($path, $resource, Config $config) + { + $result = $this->adapter->writeStream($path, $resource, $config); + + if ($result !== false) { + $result['type'] = 'file'; + $contents = false; + $this->cache->updateObject($path, $result + compact('path', 'contents'), true); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function update($path, $contents, Config $config) + { + $result = $this->adapter->update($path, $contents, $config); + + if ($result !== false) { + $result['type'] = 'file'; + $this->cache->updateObject($path, $result + compact('path', 'contents'), true); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function updateStream($path, $resource, Config $config) + { + $result = $this->adapter->updateStream($path, $resource, $config); + + if ($result !== false) { + $result['type'] = 'file'; + $contents = false; + $this->cache->updateObject($path, $result + compact('path', 'contents'), true); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function rename($path, $newPath) + { + $result = $this->adapter->rename($path, $newPath); + + if ($result !== false) { + $this->cache->rename($path, $newPath); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function copy($path, $newpath) + { + $result = $this->adapter->copy($path, $newpath); + + if ($result !== false) { + $this->cache->copy($path, $newpath); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function delete($path) + { + $result = $this->adapter->delete($path); + + if ($result !== false) { + $this->cache->delete($path); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function deleteDir($dirname) + { + $result = $this->adapter->deleteDir($dirname); + + if ($result !== false) { + $this->cache->deleteDir($dirname); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function createDir($dirname, Config $config) + { + $result = $this->adapter->createDir($dirname, $config); + + if ($result !== false) { + $type = 'dir'; + $path = $dirname; + $this->cache->updateObject($dirname, compact('path', 'type'), true); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function setVisibility($path, $visibility) + { + $result = $this->adapter->setVisibility($path, $visibility); + + if ($result !== false) { + $this->cache->updateObject($path, compact('path', 'visibility'), true); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function has($path) + { + $cacheHas = $this->cache->has($path); + + if ($cacheHas !== null) { + return $cacheHas; + } + + $adapterResponse = $this->adapter->has($path); + + if (! $adapterResponse) { + $this->cache->storeMiss($path); + } else { + $cacheEntry = is_array($adapterResponse) ? $adapterResponse : compact('path'); + $this->cache->updateObject($path, $cacheEntry, true); + } + + return $adapterResponse; + } + + /** + * {@inheritdoc} + */ + public function read($path) + { + return $this->callWithFallback('contents', $path, 'read'); + } + + /** + * {@inheritdoc} + */ + public function readStream($path) + { + return $this->adapter->readStream($path); + } + + /** + * Get the path prefix. + * + * @return string|null path prefix or null if pathPrefix is empty + */ + public function getPathPrefix() + { + return $this->adapter->getPathPrefix(); + } + + /** + * Prefix a path. + * + * @param string $path + * + * @return string prefixed path + */ + public function applyPathPrefix($path) + { + return $this->adapter->applyPathPrefix($path); + } + + /** + * {@inheritdoc} + */ + public function listContents($directory = '', $recursive = false) + { + if ($this->cache->isComplete($directory, $recursive)) { + return $this->cache->listContents($directory, $recursive); + } + + $result = $this->adapter->listContents($directory, $recursive); + + if ($result !== false) { + $this->cache->storeContents($directory, $result, $recursive); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function getMetadata($path) + { + return $this->callWithFallback(null, $path, 'getMetadata'); + } + + /** + * {@inheritdoc} + */ + public function getSize($path) + { + return $this->callWithFallback('size', $path, 'getSize'); + } + + /** + * {@inheritdoc} + */ + public function getMimetype($path) + { + return $this->callWithFallback('mimetype', $path, 'getMimetype'); + } + + /** + * {@inheritdoc} + */ + public function getTimestamp($path) + { + return $this->callWithFallback('timestamp', $path, 'getTimestamp'); + } + + /** + * {@inheritdoc} + */ + public function getVisibility($path) + { + return $this->callWithFallback('visibility', $path, 'getVisibility'); + } + + /** + * Call a method and cache the response. + * + * @param string $property + * @param string $path + * @param string $method + * + * @return mixed + */ + protected function callWithFallback($property, $path, $method) + { + $result = $this->cache->{$method}($path); + + if ($result !== false && ($property === null || array_key_exists($property, $result))) { + return $result; + } + + $result = $this->adapter->{$method}($path); + + if ($result) { + $object = $result + compact('path'); + $this->cache->updateObject($path, $object, true); + } + + return $result; + } +} diff --git a/vendor/league/flysystem-cached-adapter/src/Storage/AbstractCache.php b/vendor/league/flysystem-cached-adapter/src/Storage/AbstractCache.php new file mode 100644 index 0000000..141b468 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/src/Storage/AbstractCache.php @@ -0,0 +1,418 @@ +autosave) { + $this->save(); + } + } + + /** + * Get the autosave setting. + * + * @return bool autosave + */ + public function getAutosave() + { + return $this->autosave; + } + + /** + * Get the autosave setting. + * + * @param bool $autosave + */ + public function setAutosave($autosave) + { + $this->autosave = $autosave; + } + + /** + * Store the contents listing. + * + * @param string $directory + * @param array $contents + * @param bool $recursive + * + * @return array contents listing + */ + public function storeContents($directory, array $contents, $recursive = false) + { + $directories = [$directory]; + + foreach ($contents as $object) { + $this->updateObject($object['path'], $object); + $object = $this->cache[$object['path']]; + + if ($recursive && $this->pathIsInDirectory($directory, $object['path'])) { + $directories[] = $object['dirname']; + } + } + + foreach (array_unique($directories) as $directory) { + $this->setComplete($directory, $recursive); + } + + $this->autosave(); + } + + /** + * Update the metadata for an object. + * + * @param string $path object path + * @param array $object object metadata + * @param bool $autosave whether to trigger the autosave routine + */ + public function updateObject($path, array $object, $autosave = false) + { + if (! $this->has($path)) { + $this->cache[$path] = Util::pathinfo($path); + } + + $this->cache[$path] = array_merge($this->cache[$path], $object); + + if ($autosave) { + $this->autosave(); + } + + $this->ensureParentDirectories($path); + } + + /** + * Store object hit miss. + * + * @param string $path + */ + public function storeMiss($path) + { + $this->cache[$path] = false; + $this->autosave(); + } + + /** + * Get the contents listing. + * + * @param string $dirname + * @param bool $recursive + * + * @return array contents listing + */ + public function listContents($dirname = '', $recursive = false) + { + $result = []; + + foreach ($this->cache as $object) { + if ($object === false) { + continue; + } + if ($object['dirname'] === $dirname) { + $result[] = $object; + } elseif ($recursive && $this->pathIsInDirectory($dirname, $object['path'])) { + $result[] = $object; + } + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function has($path) + { + if ($path !== false && array_key_exists($path, $this->cache)) { + return $this->cache[$path] !== false; + } + + if ($this->isComplete(Util::dirname($path), false)) { + return false; + } + } + + /** + * {@inheritdoc} + */ + public function read($path) + { + if (isset($this->cache[$path]['contents']) && $this->cache[$path]['contents'] !== false) { + return $this->cache[$path]; + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function readStream($path) + { + return false; + } + + /** + * {@inheritdoc} + */ + public function rename($path, $newpath) + { + if ($this->has($path)) { + $object = $this->cache[$path]; + unset($this->cache[$path]); + $object['path'] = $newpath; + $object = array_merge($object, Util::pathinfo($newpath)); + $this->cache[$newpath] = $object; + $this->autosave(); + } + } + + /** + * {@inheritdoc} + */ + public function copy($path, $newpath) + { + if ($this->has($path)) { + $object = $this->cache[$path]; + $object = array_merge($object, Util::pathinfo($newpath)); + $this->updateObject($newpath, $object, true); + } + } + + /** + * {@inheritdoc} + */ + public function delete($path) + { + $this->storeMiss($path); + } + + /** + * {@inheritdoc} + */ + public function deleteDir($dirname) + { + foreach ($this->cache as $path => $object) { + if ($this->pathIsInDirectory($dirname, $path) || $path === $dirname) { + unset($this->cache[$path]); + } + } + + unset($this->complete[$dirname]); + + $this->autosave(); + } + + /** + * {@inheritdoc} + */ + public function getMimetype($path) + { + if (isset($this->cache[$path]['mimetype'])) { + return $this->cache[$path]; + } + + if (! $result = $this->read($path)) { + return false; + } + + $mimetype = Util::guessMimeType($path, $result['contents']); + $this->cache[$path]['mimetype'] = $mimetype; + + return $this->cache[$path]; + } + + /** + * {@inheritdoc} + */ + public function getSize($path) + { + if (isset($this->cache[$path]['size'])) { + return $this->cache[$path]; + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function getTimestamp($path) + { + if (isset($this->cache[$path]['timestamp'])) { + return $this->cache[$path]; + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function getVisibility($path) + { + if (isset($this->cache[$path]['visibility'])) { + return $this->cache[$path]; + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function getMetadata($path) + { + if (isset($this->cache[$path]['type'])) { + return $this->cache[$path]; + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function isComplete($dirname, $recursive) + { + if (! array_key_exists($dirname, $this->complete)) { + return false; + } + + if ($recursive && $this->complete[$dirname] !== 'recursive') { + return false; + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function setComplete($dirname, $recursive) + { + $this->complete[$dirname] = $recursive ? 'recursive' : true; + } + + /** + * Filter the contents from a listing. + * + * @param array $contents object listing + * + * @return array filtered contents + */ + public function cleanContents(array $contents) + { + $cachedProperties = array_flip([ + 'path', 'dirname', 'basename', 'extension', 'filename', + 'size', 'mimetype', 'visibility', 'timestamp', 'type', + 'md5', + ]); + + foreach ($contents as $path => $object) { + if (is_array($object)) { + $contents[$path] = array_intersect_key($object, $cachedProperties); + } + } + + return $contents; + } + + /** + * {@inheritdoc} + */ + public function flush() + { + $this->cache = []; + $this->complete = []; + $this->autosave(); + } + + /** + * {@inheritdoc} + */ + public function autosave() + { + if ($this->autosave) { + $this->save(); + } + } + + /** + * Retrieve serialized cache data. + * + * @return string serialized data + */ + public function getForStorage() + { + $cleaned = $this->cleanContents($this->cache); + + return json_encode([$cleaned, $this->complete]); + } + + /** + * Load from serialized cache data. + * + * @param string $json + */ + public function setFromStorage($json) + { + list($cache, $complete) = json_decode($json, true); + + if (json_last_error() === JSON_ERROR_NONE && is_array($cache) && is_array($complete)) { + $this->cache = $cache; + $this->complete = $complete; + } + } + + /** + * Ensure parent directories of an object. + * + * @param string $path object path + */ + public function ensureParentDirectories($path) + { + $object = $this->cache[$path]; + + while ($object['dirname'] !== '' && ! isset($this->cache[$object['dirname']])) { + $object = Util::pathinfo($object['dirname']); + $object['type'] = 'dir'; + $this->cache[$object['path']] = $object; + } + } + + /** + * Determines if the path is inside the directory. + * + * @param string $directory + * @param string $path + * + * @return bool + */ + protected function pathIsInDirectory($directory, $path) + { + return $directory === '' || strpos($path, $directory . '/') === 0; + } +} diff --git a/vendor/league/flysystem-cached-adapter/src/Storage/Adapter.php b/vendor/league/flysystem-cached-adapter/src/Storage/Adapter.php new file mode 100644 index 0000000..649a60e --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/src/Storage/Adapter.php @@ -0,0 +1,115 @@ +adapter = $adapter; + $this->file = $file; + $this->setExpire($expire); + } + + /** + * Set the expiration time in seconds. + * + * @param int $expire relative expiration time + */ + protected function setExpire($expire) + { + if ($expire) { + $this->expire = $this->getTime($expire); + } + } + + /** + * Get expiration time in seconds. + * + * @param int $time relative expiration time + * + * @return int actual expiration time + */ + protected function getTime($time = 0) + { + return intval(microtime(true)) + $time; + } + + /** + * {@inheritdoc} + */ + public function setFromStorage($json) + { + list($cache, $complete, $expire) = json_decode($json, true); + + if (! $expire || $expire > $this->getTime()) { + $this->cache = is_array($cache) ? $cache : []; + $this->complete = is_array($complete) ? $complete : []; + } else { + $this->adapter->delete($this->file); + } + } + + /** + * {@inheritdoc} + */ + public function load() + { + if ($this->adapter->has($this->file)) { + $file = $this->adapter->read($this->file); + if ($file && !empty($file['contents'])) { + $this->setFromStorage($file['contents']); + } + } + } + + /** + * {@inheritdoc} + */ + public function getForStorage() + { + $cleaned = $this->cleanContents($this->cache); + + return json_encode([$cleaned, $this->complete, $this->expire]); + } + + /** + * {@inheritdoc} + */ + public function save() + { + $config = new Config(); + $contents = $this->getForStorage(); + + if ($this->adapter->has($this->file)) { + $this->adapter->update($this->file, $contents, $config); + } else { + $this->adapter->write($this->file, $contents, $config); + } + } +} diff --git a/vendor/league/flysystem-cached-adapter/src/Storage/Memcached.php b/vendor/league/flysystem-cached-adapter/src/Storage/Memcached.php new file mode 100644 index 0000000..f67d271 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/src/Storage/Memcached.php @@ -0,0 +1,59 @@ +key = $key; + $this->expire = $expire; + $this->memcached = $memcached; + } + + /** + * {@inheritdoc} + */ + public function load() + { + $contents = $this->memcached->get($this->key); + + if ($contents !== false) { + $this->setFromStorage($contents); + } + } + + /** + * {@inheritdoc} + */ + public function save() + { + $contents = $this->getForStorage(); + $expiration = $this->expire === null ? 0 : time() + $this->expire; + $this->memcached->set($this->key, $contents, $expiration); + } +} diff --git a/vendor/league/flysystem-cached-adapter/src/Storage/Memory.php b/vendor/league/flysystem-cached-adapter/src/Storage/Memory.php new file mode 100644 index 0000000..d0914fa --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/src/Storage/Memory.php @@ -0,0 +1,22 @@ +client = $client ?: new Redis(); + $this->key = $key; + $this->expire = $expire; + } + + /** + * {@inheritdoc} + */ + public function load() + { + $contents = $this->client->get($this->key); + + if ($contents !== false) { + $this->setFromStorage($contents); + } + } + + /** + * {@inheritdoc} + */ + public function save() + { + $contents = $this->getForStorage(); + $this->client->set($this->key, $contents); + + if ($this->expire !== null) { + $this->client->expire($this->key, $this->expire); + } + } +} diff --git a/vendor/league/flysystem-cached-adapter/src/Storage/Predis.php b/vendor/league/flysystem-cached-adapter/src/Storage/Predis.php new file mode 100644 index 0000000..8a29574 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/src/Storage/Predis.php @@ -0,0 +1,75 @@ +client = $client ?: new Client(); + $this->key = $key; + $this->expire = $expire; + } + + /** + * {@inheritdoc} + */ + public function load() + { + if (($contents = $this->executeCommand('get', [$this->key])) !== null) { + $this->setFromStorage($contents); + } + } + + /** + * {@inheritdoc} + */ + public function save() + { + $contents = $this->getForStorage(); + $this->executeCommand('set', [$this->key, $contents]); + + if ($this->expire !== null) { + $this->executeCommand('expire', [$this->key, $this->expire]); + } + } + + /** + * Execute a Predis command. + * + * @param string $name + * @param array $arguments + * + * @return string + */ + protected function executeCommand($name, array $arguments) + { + $command = $this->client->createCommand($name, $arguments); + + return $this->client->executeCommand($command); + } +} diff --git a/vendor/league/flysystem-cached-adapter/src/Storage/Psr6Cache.php b/vendor/league/flysystem-cached-adapter/src/Storage/Psr6Cache.php new file mode 100644 index 0000000..43be87e --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/src/Storage/Psr6Cache.php @@ -0,0 +1,59 @@ +pool = $pool; + $this->key = $key; + $this->expire = $expire; + } + + /** + * {@inheritdoc} + */ + public function save() + { + $item = $this->pool->getItem($this->key); + $item->set($this->getForStorage()); + $item->expiresAfter($this->expire); + $this->pool->save($item); + } + + /** + * {@inheritdoc} + */ + public function load() + { + $item = $this->pool->getItem($this->key); + if ($item->isHit()) { + $this->setFromStorage($item->get()); + } + } +} \ No newline at end of file diff --git a/vendor/league/flysystem-cached-adapter/src/Storage/Stash.php b/vendor/league/flysystem-cached-adapter/src/Storage/Stash.php new file mode 100644 index 0000000..e05b832 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/src/Storage/Stash.php @@ -0,0 +1,60 @@ +key = $key; + $this->expire = $expire; + $this->pool = $pool; + } + + /** + * {@inheritdoc} + */ + public function load() + { + $item = $this->pool->getItem($this->key); + $contents = $item->get(); + + if ($item->isMiss() === false) { + $this->setFromStorage($contents); + } + } + + /** + * {@inheritdoc} + */ + public function save() + { + $contents = $this->getForStorage(); + $item = $this->pool->getItem($this->key); + $item->set($contents, $this->expire); + } +} diff --git a/vendor/league/flysystem-cached-adapter/tests/AdapterCacheTests.php b/vendor/league/flysystem-cached-adapter/tests/AdapterCacheTests.php new file mode 100644 index 0000000..b63cba7 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/tests/AdapterCacheTests.php @@ -0,0 +1,104 @@ +shouldReceive('has')->once()->with('file.json')->andReturn(false); + $cache = new Adapter($adapter, 'file.json', 10); + $cache->load(); + $this->assertFalse($cache->isComplete('', false)); + } + + public function testLoadExpired() + { + $response = ['contents' => json_encode([[], ['' => true], 1234567890]), 'path' => 'file.json']; + $adapter = Mockery::mock('League\Flysystem\AdapterInterface'); + $adapter->shouldReceive('has')->once()->with('file.json')->andReturn(true); + $adapter->shouldReceive('read')->once()->with('file.json')->andReturn($response); + $adapter->shouldReceive('delete')->once()->with('file.json'); + $cache = new Adapter($adapter, 'file.json', 10); + $cache->load(); + $this->assertFalse($cache->isComplete('', false)); + } + + public function testLoadSuccess() + { + $response = ['contents' => json_encode([[], ['' => true], 9876543210]), 'path' => 'file.json']; + $adapter = Mockery::mock('League\Flysystem\AdapterInterface'); + $adapter->shouldReceive('has')->once()->with('file.json')->andReturn(true); + $adapter->shouldReceive('read')->once()->with('file.json')->andReturn($response); + $cache = new Adapter($adapter, 'file.json', 10); + $cache->load(); + $this->assertTrue($cache->isComplete('', false)); + } + + public function testSaveExists() + { + $response = json_encode([[], [], null]); + $adapter = Mockery::mock('League\Flysystem\AdapterInterface'); + $adapter->shouldReceive('has')->once()->with('file.json')->andReturn(true); + $adapter->shouldReceive('update')->once()->with('file.json', $response, Mockery::any()); + $cache = new Adapter($adapter, 'file.json', null); + $cache->save(); + } + + public function testSaveNew() + { + $response = json_encode([[], [], null]); + $adapter = Mockery::mock('League\Flysystem\AdapterInterface'); + $adapter->shouldReceive('has')->once()->with('file.json')->andReturn(false); + $adapter->shouldReceive('write')->once()->with('file.json', $response, Mockery::any()); + $cache = new Adapter($adapter, 'file.json', null); + $cache->save(); + } + + public function testStoreContentsRecursive() + { + $adapter = Mockery::mock('League\Flysystem\AdapterInterface'); + $adapter->shouldReceive('has')->once()->with('file.json')->andReturn(false); + $adapter->shouldReceive('write')->once()->with('file.json', Mockery::any(), Mockery::any()); + + $cache = new Adapter($adapter, 'file.json', null); + + $contents = [ + ['path' => 'foo/bar', 'dirname' => 'foo'], + ['path' => 'afoo/bang', 'dirname' => 'afoo'], + ]; + + $cache->storeContents('foo', $contents, true); + + $this->assertTrue($cache->isComplete('foo', true)); + $this->assertFalse($cache->isComplete('afoo', true)); + } + + public function testDeleteDir() + { + $cache_data = [ + 'foo' => ['path' => 'foo', 'type' => 'dir', 'dirname' => ''], + 'foo/bar' => ['path' => 'foo/bar', 'type' => 'file', 'dirname' => 'foo'], + 'foobaz' => ['path' => 'foobaz', 'type' => 'file', 'dirname' => ''], + ]; + + $response = [ + 'contents' => json_encode([$cache_data, [], null]), + 'path' => 'file.json', + ]; + + $adapter = Mockery::mock('League\Flysystem\AdapterInterface'); + $adapter->shouldReceive('has')->zeroOrMoreTimes()->with('file.json')->andReturn(true); + $adapter->shouldReceive('read')->once()->with('file.json')->andReturn($response); + $adapter->shouldReceive('update')->once()->with('file.json', Mockery::any(), Mockery::any())->andReturn(true); + + $cache = new Adapter($adapter, 'file.json', null); + $cache->load(); + + $cache->deleteDir('foo', true); + + $this->assertSame(1, count($cache->listContents('', true))); + } +} diff --git a/vendor/league/flysystem-cached-adapter/tests/InspectionTests.php b/vendor/league/flysystem-cached-adapter/tests/InspectionTests.php new file mode 100644 index 0000000..40d4c91 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/tests/InspectionTests.php @@ -0,0 +1,16 @@ +shouldReceive('load')->once(); + $cached_adapter = new CachedAdapter($adapter, $cache); + $this->assertInstanceOf('League\Flysystem\AdapterInterface', $cached_adapter->getAdapter()); + } +} diff --git a/vendor/league/flysystem-cached-adapter/tests/MemcachedTests.php b/vendor/league/flysystem-cached-adapter/tests/MemcachedTests.php new file mode 100644 index 0000000..e3d9ad9 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/tests/MemcachedTests.php @@ -0,0 +1,35 @@ +shouldReceive('get')->once()->andReturn(false); + $cache = new Memcached($client); + $cache->load(); + $this->assertFalse($cache->isComplete('', false)); + } + + public function testLoadSuccess() + { + $response = json_encode([[], ['' => true]]); + $client = Mockery::mock('Memcached'); + $client->shouldReceive('get')->once()->andReturn($response); + $cache = new Memcached($client); + $cache->load(); + $this->assertTrue($cache->isComplete('', false)); + } + + public function testSave() + { + $response = json_encode([[], []]); + $client = Mockery::mock('Memcached'); + $client->shouldReceive('set')->once()->andReturn($response); + $cache = new Memcached($client); + $cache->save(); + } +} diff --git a/vendor/league/flysystem-cached-adapter/tests/MemoryCacheTests.php b/vendor/league/flysystem-cached-adapter/tests/MemoryCacheTests.php new file mode 100644 index 0000000..3ac58fd --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/tests/MemoryCacheTests.php @@ -0,0 +1,255 @@ +setAutosave(true); + $this->assertTrue($cache->getAutosave()); + $cache->setAutosave(false); + $this->assertFalse($cache->getAutosave()); + } + + public function testCacheMiss() + { + $cache = new Memory(); + $cache->storeMiss('path.txt'); + $this->assertFalse($cache->has('path.txt')); + } + + public function testIsComplete() + { + $cache = new Memory(); + $this->assertFalse($cache->isComplete('dirname', false)); + $cache->setComplete('dirname', false); + $this->assertFalse($cache->isComplete('dirname', true)); + $cache->setComplete('dirname', true); + $this->assertTrue($cache->isComplete('dirname', true)); + } + + public function testCleanContents() + { + $cache = new Memory(); + $input = [[ + 'path' => 'path.txt', + 'visibility' => 'public', + 'invalid' => 'thing', + ]]; + + $expected = [[ + 'path' => 'path.txt', + 'visibility' => 'public', + ]]; + + $output = $cache->cleanContents($input); + $this->assertEquals($expected, $output); + } + + public function testGetForStorage() + { + $cache = new Memory(); + $input = [[ + 'path' => 'path.txt', + 'visibility' => 'public', + 'type' => 'file', + ]]; + + $cache->storeContents('', $input, true); + $contents = $cache->listContents('', true); + $cached = []; + foreach ($contents as $item) { + $cached[$item['path']] = $item; + } + + $this->assertEquals(json_encode([$cached, ['' => 'recursive']]), $cache->getForStorage()); + } + + public function testParentCompleteIsUsedDuringHas() + { + $cache = new Memory(); + $cache->setComplete('dirname', false); + $this->assertFalse($cache->has('dirname/path.txt')); + } + + public function testFlush() + { + $cache = new Memory(); + $cache->setComplete('dirname', true); + $cache->updateObject('path.txt', [ + 'path' => 'path.txt', + 'visibility' => 'public', + ]); + $cache->flush(); + $this->assertFalse($cache->isComplete('dirname', true)); + $this->assertNull($cache->has('path.txt')); + } + + public function testSetFromStorage() + { + $cache = new Memory(); + $json = [[ + 'path.txt' => ['path' => 'path.txt', 'type' => 'file'], + ], ['dirname' => 'recursive']]; + $jsonString = json_encode($json); + $cache->setFromStorage($jsonString); + $this->assertTrue($cache->has('path.txt')); + $this->assertTrue($cache->isComplete('dirname', true)); + } + + public function testGetMetadataFail() + { + $cache = new Memory(); + $this->assertFalse($cache->getMetadata('path.txt')); + } + + public function metaGetterProvider() + { + return [ + ['getTimestamp', 'timestamp', 12344], + ['getMimetype', 'mimetype', 'text/plain'], + ['getSize', 'size', 12], + ['getVisibility', 'visibility', 'private'], + ['read', 'contents', '__contents__'], + ]; + } + + /** + * @dataProvider metaGetterProvider + * + * @param $method + * @param $key + * @param $value + */ + public function testMetaGetters($method, $key, $value) + { + $cache = new Memory(); + $this->assertFalse($cache->{$method}('path.txt')); + $cache->updateObject('path.txt', $object = [ + 'path' => 'path.txt', + 'type' => 'file', + $key => $value, + ] + Util::pathinfo('path.txt'), true); + $this->assertEquals($object, $cache->{$method}('path.txt')); + $this->assertEquals($object, $cache->getMetadata('path.txt')); + } + + public function testGetDerivedMimetype() + { + $cache = new Memory(); + $cache->updateObject('path.txt', [ + 'contents' => 'something', + ]); + $response = $cache->getMimetype('path.txt'); + $this->assertEquals('text/plain', $response['mimetype']); + } + + public function testCopyFail() + { + $cache = new Memory(); + $cache->copy('one', 'two'); + $this->assertNull($cache->has('two')); + $this->assertNull($cache->load()); + } + + public function testStoreContents() + { + $cache = new Memory(); + $cache->storeContents('dirname', [ + ['path' => 'dirname', 'type' => 'dir'], + ['path' => 'dirname/nested', 'type' => 'dir'], + ['path' => 'dirname/nested/deep', 'type' => 'dir'], + ['path' => 'other/nested/deep', 'type' => 'dir'], + ], true); + + $this->isTrue($cache->isComplete('other/nested', true)); + } + + public function testDelete() + { + $cache = new Memory(); + $cache->updateObject('path.txt', ['type' => 'file']); + $this->assertTrue($cache->has('path.txt')); + $cache->delete('path.txt'); + $this->assertFalse($cache->has('path.txt')); + } + + public function testDeleteDir() + { + $cache = new Memory(); + $cache->storeContents('dirname', [ + ['path' => 'dirname/path.txt', 'type' => 'file'], + ]); + $this->assertTrue($cache->isComplete('dirname', false)); + $this->assertTrue($cache->has('dirname/path.txt')); + $cache->deleteDir('dirname'); + $this->assertFalse($cache->isComplete('dirname', false)); + $this->assertNull($cache->has('dirname/path.txt')); + } + + public function testReadStream() + { + $cache = new Memory(); + $this->assertFalse($cache->readStream('path.txt')); + } + + public function testRename() + { + $cache = new Memory(); + $cache->updateObject('path.txt', ['type' => 'file']); + $cache->rename('path.txt', 'newpath.txt'); + $this->assertTrue($cache->has('newpath.txt')); + } + + public function testCopy() + { + $cache = new Memory(); + $cache->updateObject('path.txt', ['type' => 'file']); + $cache->copy('path.txt', 'newpath.txt'); + $this->assertTrue($cache->has('newpath.txt')); + } + + public function testComplextListContents() + { + $cache = new Memory(); + $cache->storeContents('', [ + ['path' => 'dirname', 'type' => 'dir'], + ['path' => 'dirname/file.txt', 'type' => 'file'], + ['path' => 'other', 'type' => 'dir'], + ['path' => 'other/file.txt', 'type' => 'file'], + ['path' => 'other/nested/file.txt', 'type' => 'file'], + ]); + + $this->assertCount(3, $cache->listContents('other', true)); + } + + public function testComplextListContentsWithDeletedFile() + { + $cache = new Memory(); + $cache->storeContents('', [ + ['path' => 'dirname', 'type' => 'dir'], + ['path' => 'dirname/file.txt', 'type' => 'file'], + ['path' => 'other', 'type' => 'dir'], + ['path' => 'other/file.txt', 'type' => 'file'], + ['path' => 'other/another_file.txt', 'type' => 'file'], + ]); + + $cache->delete('other/another_file.txt'); + $this->assertCount(4, $cache->listContents('', true)); + } + + public function testCacheMissIfContentsIsFalse() + { + $cache = new Memory(); + $cache->updateObject('path.txt', [ + 'path' => 'path.txt', + 'contents' => false, + ], true); + + $this->assertFalse($cache->read('path.txt')); + } +} diff --git a/vendor/league/flysystem-cached-adapter/tests/NoopCacheTests.php b/vendor/league/flysystem-cached-adapter/tests/NoopCacheTests.php new file mode 100644 index 0000000..148616f --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/tests/NoopCacheTests.php @@ -0,0 +1,35 @@ +assertEquals($cache, $cache->storeMiss('file.txt')); + $this->assertNull($cache->setComplete('', false)); + $this->assertNull($cache->load()); + $this->assertNull($cache->flush()); + $this->assertNull($cache->has('path.txt')); + $this->assertNull($cache->autosave()); + $this->assertFalse($cache->isComplete('', false)); + $this->assertFalse($cache->read('something')); + $this->assertFalse($cache->readStream('something')); + $this->assertFalse($cache->getMetadata('something')); + $this->assertFalse($cache->getMimetype('something')); + $this->assertFalse($cache->getSize('something')); + $this->assertFalse($cache->getTimestamp('something')); + $this->assertFalse($cache->getVisibility('something')); + $this->assertEmpty($cache->listContents('', false)); + $this->assertFalse($cache->rename('', '')); + $this->assertFalse($cache->copy('', '')); + $this->assertNull($cache->save()); + $object = ['path' => 'path.ext']; + $this->assertEquals($object, $cache->updateObject('path.txt', $object)); + $this->assertEquals([['path' => 'some/file.txt']], $cache->storeContents('unknwon', [ + ['path' => 'some/file.txt'], + ], false)); + } +} diff --git a/vendor/league/flysystem-cached-adapter/tests/PhpRedisTests.php b/vendor/league/flysystem-cached-adapter/tests/PhpRedisTests.php new file mode 100644 index 0000000..d1ccb65 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/tests/PhpRedisTests.php @@ -0,0 +1,45 @@ +shouldReceive('get')->with('flysystem')->once()->andReturn(false); + $cache = new PhpRedis($client); + $cache->load(); + $this->assertFalse($cache->isComplete('', false)); + } + + public function testLoadSuccess() + { + $response = json_encode([[], ['' => true]]); + $client = Mockery::mock('Redis'); + $client->shouldReceive('get')->with('flysystem')->once()->andReturn($response); + $cache = new PhpRedis($client); + $cache->load(); + $this->assertTrue($cache->isComplete('', false)); + } + + public function testSave() + { + $data = json_encode([[], []]); + $client = Mockery::mock('Redis'); + $client->shouldReceive('set')->with('flysystem', $data)->once(); + $cache = new PhpRedis($client); + $cache->save(); + } + + public function testSaveWithExpire() + { + $data = json_encode([[], []]); + $client = Mockery::mock('Redis'); + $client->shouldReceive('set')->with('flysystem', $data)->once(); + $client->shouldReceive('expire')->with('flysystem', 20)->once(); + $cache = new PhpRedis($client, 'flysystem', 20); + $cache->save(); + } +} diff --git a/vendor/league/flysystem-cached-adapter/tests/PredisTests.php b/vendor/league/flysystem-cached-adapter/tests/PredisTests.php new file mode 100644 index 0000000..e33e104 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/tests/PredisTests.php @@ -0,0 +1,55 @@ +shouldReceive('createCommand')->with('get', ['flysystem'])->once()->andReturn($command); + $client->shouldReceive('executeCommand')->with($command)->andReturn(null); + $cache = new Predis($client); + $cache->load(); + $this->assertFalse($cache->isComplete('', false)); + } + + public function testLoadSuccess() + { + $response = json_encode([[], ['' => true]]); + $client = Mockery::mock('Predis\Client'); + $command = Mockery::mock('Predis\Command\CommandInterface'); + $client->shouldReceive('createCommand')->with('get', ['flysystem'])->once()->andReturn($command); + $client->shouldReceive('executeCommand')->with($command)->andReturn($response); + $cache = new Predis($client); + $cache->load(); + $this->assertTrue($cache->isComplete('', false)); + } + + public function testSave() + { + $data = json_encode([[], []]); + $client = Mockery::mock('Predis\Client'); + $command = Mockery::mock('Predis\Command\CommandInterface'); + $client->shouldReceive('createCommand')->with('set', ['flysystem', $data])->once()->andReturn($command); + $client->shouldReceive('executeCommand')->with($command)->once(); + $cache = new Predis($client); + $cache->save(); + } + + public function testSaveWithExpire() + { + $data = json_encode([[], []]); + $client = Mockery::mock('Predis\Client'); + $command = Mockery::mock('Predis\Command\CommandInterface'); + $client->shouldReceive('createCommand')->with('set', ['flysystem', $data])->once()->andReturn($command); + $client->shouldReceive('executeCommand')->with($command)->once(); + $expireCommand = Mockery::mock('Predis\Command\CommandInterface'); + $client->shouldReceive('createCommand')->with('expire', ['flysystem', 20])->once()->andReturn($expireCommand); + $client->shouldReceive('executeCommand')->with($expireCommand)->once(); + $cache = new Predis($client, 'flysystem', 20); + $cache->save(); + } +} diff --git a/vendor/league/flysystem-cached-adapter/tests/Psr6CacheTest.php b/vendor/league/flysystem-cached-adapter/tests/Psr6CacheTest.php new file mode 100644 index 0000000..d5e5700 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/tests/Psr6CacheTest.php @@ -0,0 +1,45 @@ +shouldReceive('isHit')->once()->andReturn(false); + $pool->shouldReceive('getItem')->once()->andReturn($item); + $cache = new Psr6Cache($pool); + $cache->load(); + $this->assertFalse($cache->isComplete('', false)); + } + + public function testLoadSuccess() + { + $response = json_encode([[], ['' => true]]); + $pool = Mockery::mock('Psr\Cache\CacheItemPoolInterface'); + $item = Mockery::mock('Psr\Cache\CacheItemInterface'); + $item->shouldReceive('get')->once()->andReturn($response); + $item->shouldReceive('isHit')->once()->andReturn(true); + $pool->shouldReceive('getItem')->once()->andReturn($item); + $cache = new Psr6Cache($pool); + $cache->load(); + $this->assertTrue($cache->isComplete('', false)); + } + + public function testSave() + { + $response = json_encode([[], []]); + $ttl = 4711; + $pool = Mockery::mock('Psr\Cache\CacheItemPoolInterface'); + $item = Mockery::mock('Psr\Cache\CacheItemInterface'); + $item->shouldReceive('expiresAfter')->once()->with($ttl); + $item->shouldReceive('set')->once()->andReturn($response); + $pool->shouldReceive('getItem')->once()->andReturn($item); + $pool->shouldReceive('save')->once()->with($item); + $cache = new Psr6Cache($pool, 'foo', $ttl); + $cache->save(); + } +} diff --git a/vendor/league/flysystem-cached-adapter/tests/StashTest.php b/vendor/league/flysystem-cached-adapter/tests/StashTest.php new file mode 100644 index 0000000..29e142d --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/tests/StashTest.php @@ -0,0 +1,43 @@ +shouldReceive('get')->once()->andReturn(null); + $item->shouldReceive('isMiss')->once()->andReturn(true); + $pool->shouldReceive('getItem')->once()->andReturn($item); + $cache = new Stash($pool); + $cache->load(); + $this->assertFalse($cache->isComplete('', false)); + } + + public function testLoadSuccess() + { + $response = json_encode([[], ['' => true]]); + $pool = Mockery::mock('Stash\Pool'); + $item = Mockery::mock('Stash\Item'); + $item->shouldReceive('get')->once()->andReturn($response); + $item->shouldReceive('isMiss')->once()->andReturn(false); + $pool->shouldReceive('getItem')->once()->andReturn($item); + $cache = new Stash($pool); + $cache->load(); + $this->assertTrue($cache->isComplete('', false)); + } + + public function testSave() + { + $response = json_encode([[], []]); + $pool = Mockery::mock('Stash\Pool'); + $item = Mockery::mock('Stash\Item'); + $item->shouldReceive('set')->once()->andReturn($response); + $pool->shouldReceive('getItem')->once()->andReturn($item); + $cache = new Stash($pool); + $cache->save(); + } +} diff --git a/vendor/league/flysystem/CODE_OF_CONDUCT.md b/vendor/league/flysystem/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..89569c0 --- /dev/null +++ b/vendor/league/flysystem/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at info+flysystem@frankdejonge.nl. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/vendor/league/flysystem/LICENSE b/vendor/league/flysystem/LICENSE new file mode 100644 index 0000000..f2684c8 --- /dev/null +++ b/vendor/league/flysystem/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2013-2019 Frank de Jonge + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/league/flysystem/SECURITY.md b/vendor/league/flysystem/SECURITY.md new file mode 100644 index 0000000..f5b205e --- /dev/null +++ b/vendor/league/flysystem/SECURITY.md @@ -0,0 +1,16 @@ +# Security Policy + +## Supported Versions + +| Version | Supported | +| ------- | ------------------ | +| 1.0.x | :white_check_mark: | +| 2.0.x | :x: | + +## Reporting a Vulnerability + +When you've encountered a security vulnerability, please disclose it securely. + +The security process is described at: +[https://flysystem.thephpleague.com/docs/security/](https://flysystem.thephpleague.com/docs/security/) + diff --git a/vendor/league/flysystem/composer.json b/vendor/league/flysystem/composer.json new file mode 100644 index 0000000..32ec81d --- /dev/null +++ b/vendor/league/flysystem/composer.json @@ -0,0 +1,68 @@ +{ + "name": "league/flysystem", + "type": "library", + "description": "Filesystem abstraction: Many filesystems, one API.", + "keywords": [ + "filesystem", "filesystems", "files", "storage", "dropbox", "aws", + "abstraction", "s3", "ftp", "sftp", "remote", "webdav", + "file systems", "cloud", "cloud files", "rackspace", "copy.com" + ], + "funding": [ + { + "type": "other", + "url": "https://offset.earth/frankdejonge" + } + ], + "license": "MIT", + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "require": { + "php": "^7.2.5 || ^8.0", + "ext-fileinfo": "*", + "league/mime-type-detection": "^1.3" + }, + "require-dev": { + "phpspec/prophecy": "^1.11.1", + "phpunit/phpunit": "^8.5.8" + }, + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "League\\Flysystem\\Stub\\": "stub/" + } + }, + "suggest": { + "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem", + "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files", + "league/flysystem-azure": "Allows you to use Windows Azure Blob storage", + "league/flysystem-webdav": "Allows you to use WebDAV storage", + "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2", + "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3", + "spatie/flysystem-dropbox": "Allows you to use Dropbox storage", + "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications", + "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching", + "ext-ftp": "Allows you to use FTP server storage", + "ext-openssl": "Allows you to use FTPS server storage", + "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib", + "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter" + }, + "conflict": { + "league/flysystem-sftp": "<1.0.6" + }, + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "scripts": { + "phpstan": "php phpstan.php" + } +} diff --git a/vendor/league/flysystem/deprecations.md b/vendor/league/flysystem/deprecations.md new file mode 100644 index 0000000..c336a42 --- /dev/null +++ b/vendor/league/flysystem/deprecations.md @@ -0,0 +1,19 @@ +# Deprecations + +This document lists all the planned deprecations. + +## Handlers will be removed in 2.0 + +The `Handler` type and associated calls will be removed in version 2.0. + +### Upgrade path + +You should create your own implementation for handling OOP usage, +but it's recommended to move away from using an OOP-style wrapper entirely. + +The reason for this is that it's too easy for implementation details (for +your application this is Flysystem) to leak into the application. The most +important part for Flysystem is that it improves portability and creates a +solid boundary between your application core and the infrastructure you use. +The OOP-style handling breaks this principle, therefore I want to stop +promoting it. diff --git a/vendor/league/flysystem/src/Adapter/AbstractAdapter.php b/vendor/league/flysystem/src/Adapter/AbstractAdapter.php new file mode 100644 index 0000000..a6a8ed0 --- /dev/null +++ b/vendor/league/flysystem/src/Adapter/AbstractAdapter.php @@ -0,0 +1,72 @@ +pathPrefix = null; + + return; + } + + $this->pathPrefix = rtrim($prefix, '\\/') . $this->pathSeparator; + } + + /** + * Get the path prefix. + * + * @return string|null path prefix or null if pathPrefix is empty + */ + public function getPathPrefix() + { + return $this->pathPrefix; + } + + /** + * Prefix a path. + * + * @param string $path + * + * @return string prefixed path + */ + public function applyPathPrefix($path) + { + return $this->getPathPrefix() . ltrim($path, '\\/'); + } + + /** + * Remove a path prefix. + * + * @param string $path + * + * @return string path without the prefix + */ + public function removePathPrefix($path) + { + return substr($path, strlen((string) $this->getPathPrefix())); + } +} diff --git a/vendor/league/flysystem/src/Adapter/AbstractFtpAdapter.php b/vendor/league/flysystem/src/Adapter/AbstractFtpAdapter.php new file mode 100644 index 0000000..25d949e --- /dev/null +++ b/vendor/league/flysystem/src/Adapter/AbstractFtpAdapter.php @@ -0,0 +1,705 @@ +safeStorage = new SafeStorage(); + $this->setConfig($config); + } + + /** + * Set the config. + * + * @param array $config + * + * @return $this + */ + public function setConfig(array $config) + { + foreach ($this->configurable as $setting) { + if ( ! isset($config[$setting])) { + continue; + } + + $method = 'set' . ucfirst($setting); + + if (method_exists($this, $method)) { + $this->$method($config[$setting]); + } + } + + return $this; + } + + /** + * Returns the host. + * + * @return string + */ + public function getHost() + { + return $this->host; + } + + /** + * Set the host. + * + * @param string $host + * + * @return $this + */ + public function setHost($host) + { + $this->host = $host; + + return $this; + } + + /** + * Set the public permission value. + * + * @param int $permPublic + * + * @return $this + */ + public function setPermPublic($permPublic) + { + $this->permPublic = $permPublic; + + return $this; + } + + /** + * Set the private permission value. + * + * @param int $permPrivate + * + * @return $this + */ + public function setPermPrivate($permPrivate) + { + $this->permPrivate = $permPrivate; + + return $this; + } + + /** + * Returns the ftp port. + * + * @return int + */ + public function getPort() + { + return $this->port; + } + + /** + * Returns the root folder to work from. + * + * @return string + */ + public function getRoot() + { + return $this->root; + } + + /** + * Set the ftp port. + * + * @param int|string $port + * + * @return $this + */ + public function setPort($port) + { + $this->port = (int) $port; + + return $this; + } + + /** + * Set the root folder to work from. + * + * @param string $root + * + * @return $this + */ + public function setRoot($root) + { + $this->root = rtrim($root, '\\/') . $this->separator; + + return $this; + } + + /** + * Returns the ftp username. + * + * @return string username + */ + public function getUsername() + { + $username = $this->safeStorage->retrieveSafely('username'); + + return $username !== null ? $username : 'anonymous'; + } + + /** + * Set ftp username. + * + * @param string $username + * + * @return $this + */ + public function setUsername($username) + { + $this->safeStorage->storeSafely('username', $username); + + return $this; + } + + /** + * Returns the password. + * + * @return string password + */ + public function getPassword() + { + return $this->safeStorage->retrieveSafely('password'); + } + + /** + * Set the ftp password. + * + * @param string $password + * + * @return $this + */ + public function setPassword($password) + { + $this->safeStorage->storeSafely('password', $password); + + return $this; + } + + /** + * Returns the amount of seconds before the connection will timeout. + * + * @return int + */ + public function getTimeout() + { + return $this->timeout; + } + + /** + * Set the amount of seconds before the connection should timeout. + * + * @param int $timeout + * + * @return $this + */ + public function setTimeout($timeout) + { + $this->timeout = (int) $timeout; + + return $this; + } + + /** + * Return the FTP system type. + * + * @return string + */ + public function getSystemType() + { + return $this->systemType; + } + + /** + * Set the FTP system type (windows or unix). + * + * @param string $systemType + * + * @return $this + */ + public function setSystemType($systemType) + { + $this->systemType = strtolower($systemType); + + return $this; + } + + /** + * True to enable timestamps for FTP servers that return unix-style listings. + * + * @param bool $bool + * + * @return $this + */ + public function setEnableTimestampsOnUnixListings($bool = false) + { + $this->enableTimestampsOnUnixListings = $bool; + + return $this; + } + + /** + * @inheritdoc + */ + public function listContents($directory = '', $recursive = false) + { + return $this->listDirectoryContents($directory, $recursive); + } + + abstract protected function listDirectoryContents($directory, $recursive = false); + + /** + * Normalize a directory listing. + * + * @param array $listing + * @param string $prefix + * + * @return array directory listing + */ + protected function normalizeListing(array $listing, $prefix = '') + { + $base = $prefix; + $result = []; + $listing = $this->removeDotDirectories($listing); + + while ($item = array_shift($listing)) { + if (preg_match('#^.*:$#', $item)) { + $base = preg_replace('~^\./*|:$~', '', $item); + continue; + } + + $result[] = $this->normalizeObject($item, $base); + } + + return $this->sortListing($result); + } + + /** + * Sort a directory listing. + * + * @param array $result + * + * @return array sorted listing + */ + protected function sortListing(array $result) + { + $compare = function ($one, $two) { + return strnatcmp($one['path'], $two['path']); + }; + + usort($result, $compare); + + return $result; + } + + /** + * Normalize a file entry. + * + * @param string $item + * @param string $base + * + * @return array normalized file array + * + * @throws NotSupportedException + */ + protected function normalizeObject($item, $base) + { + $systemType = $this->systemType ?: $this->detectSystemType($item); + + if ($systemType === 'unix') { + return $this->normalizeUnixObject($item, $base); + } elseif ($systemType === 'windows') { + return $this->normalizeWindowsObject($item, $base); + } + + throw NotSupportedException::forFtpSystemType($systemType); + } + + /** + * Normalize a Unix file entry. + * + * Given $item contains: + * '-rw-r--r-- 1 ftp ftp 409 Aug 19 09:01 file1.txt' + * + * This function will return: + * [ + * 'type' => 'file', + * 'path' => 'file1.txt', + * 'visibility' => 'public', + * 'size' => 409, + * 'timestamp' => 1566205260 + * ] + * + * @param string $item + * @param string $base + * + * @return array normalized file array + */ + protected function normalizeUnixObject($item, $base) + { + $item = preg_replace('#\s+#', ' ', trim($item), 7); + + if (count(explode(' ', $item, 9)) !== 9) { + throw new RuntimeException("Metadata can't be parsed from item '$item' , not enough parts."); + } + + list($permissions, /* $number */, /* $owner */, /* $group */, $size, $month, $day, $timeOrYear, $name) = explode(' ', $item, 9); + $type = $this->detectType($permissions); + $path = $base === '' ? $name : $base . $this->separator . $name; + + if ($type === 'dir') { + $result = compact('type', 'path'); + if ($this->enableTimestampsOnUnixListings) { + $timestamp = $this->normalizeUnixTimestamp($month, $day, $timeOrYear); + $result += compact('timestamp'); + } + + return $result; + } + + $permissions = $this->normalizePermissions($permissions); + $visibility = $permissions & 0044 ? AdapterInterface::VISIBILITY_PUBLIC : AdapterInterface::VISIBILITY_PRIVATE; + $size = (int) $size; + + $result = compact('type', 'path', 'visibility', 'size'); + if ($this->enableTimestampsOnUnixListings) { + $timestamp = $this->normalizeUnixTimestamp($month, $day, $timeOrYear); + $result += compact('timestamp'); + } + + return $result; + } + + /** + * Only accurate to the minute (current year), or to the day. + * + * Inadequacies in timestamp accuracy are due to limitations of the FTP 'LIST' command + * + * Note: The 'MLSD' command is a machine-readable replacement for 'LIST' + * but many FTP servers do not support it :( + * + * @param string $month e.g. 'Aug' + * @param string $day e.g. '19' + * @param string $timeOrYear e.g. '09:01' OR '2015' + * + * @return int + */ + protected function normalizeUnixTimestamp($month, $day, $timeOrYear) + { + if (is_numeric($timeOrYear)) { + $year = $timeOrYear; + $hour = '00'; + $minute = '00'; + $seconds = '00'; + } else { + $year = date('Y'); + list($hour, $minute) = explode(':', $timeOrYear); + $seconds = '00'; + } + $dateTime = DateTime::createFromFormat('Y-M-j-G:i:s', "{$year}-{$month}-{$day}-{$hour}:{$minute}:{$seconds}"); + + return $dateTime->getTimestamp(); + } + + /** + * Normalize a Windows/DOS file entry. + * + * @param string $item + * @param string $base + * + * @return array normalized file array + */ + protected function normalizeWindowsObject($item, $base) + { + $item = preg_replace('#\s+#', ' ', trim($item), 3); + + if (count(explode(' ', $item, 4)) !== 4) { + throw new RuntimeException("Metadata can't be parsed from item '$item' , not enough parts."); + } + + list($date, $time, $size, $name) = explode(' ', $item, 4); + $path = $base === '' ? $name : $base . $this->separator . $name; + + // Check for the correct date/time format + $format = strlen($date) === 8 ? 'm-d-yH:iA' : 'Y-m-dH:i'; + $dt = DateTime::createFromFormat($format, $date . $time); + $timestamp = $dt ? $dt->getTimestamp() : (int) strtotime("$date $time"); + + if ($size === '
        ') { + $type = 'dir'; + + return compact('type', 'path', 'timestamp'); + } + + $type = 'file'; + $visibility = AdapterInterface::VISIBILITY_PUBLIC; + $size = (int) $size; + + return compact('type', 'path', 'visibility', 'size', 'timestamp'); + } + + /** + * Get the system type from a listing item. + * + * @param string $item + * + * @return string the system type + */ + protected function detectSystemType($item) + { + return preg_match('/^[0-9]{2,4}-[0-9]{2}-[0-9]{2}/', trim($item)) ? 'windows' : 'unix'; + } + + /** + * Get the file type from the permissions. + * + * @param string $permissions + * + * @return string file type + */ + protected function detectType($permissions) + { + return substr($permissions, 0, 1) === 'd' ? 'dir' : 'file'; + } + + /** + * Normalize a permissions string. + * + * @param string $permissions + * + * @return int + */ + protected function normalizePermissions($permissions) + { + if (is_numeric($permissions)) { + return ((int) $permissions) & 0777; + } + + // remove the type identifier + $permissions = substr($permissions, 1); + + // map the string rights to the numeric counterparts + $map = ['-' => '0', 'r' => '4', 'w' => '2', 'x' => '1']; + $permissions = strtr($permissions, $map); + + // split up the permission groups + $parts = str_split($permissions, 3); + + // convert the groups + $mapper = function ($part) { + return array_sum(str_split($part)); + }; + + // converts to decimal number + return octdec(implode('', array_map($mapper, $parts))); + } + + /** + * Filter out dot-directories. + * + * @param array $list + * + * @return array + */ + public function removeDotDirectories(array $list) + { + $filter = function ($line) { + return $line !== '' && ! preg_match('#.* \.(\.)?$|^total#', $line); + }; + + return array_filter($list, $filter); + } + + /** + * @inheritdoc + */ + public function has($path) + { + return $this->getMetadata($path); + } + + /** + * @inheritdoc + */ + public function getSize($path) + { + return $this->getMetadata($path); + } + + /** + * @inheritdoc + */ + public function getVisibility($path) + { + return $this->getMetadata($path); + } + + /** + * Ensure a directory exists. + * + * @param string $dirname + */ + public function ensureDirectory($dirname) + { + $dirname = (string) $dirname; + + if ($dirname !== '' && ! $this->has($dirname)) { + $this->createDir($dirname, new Config()); + } + } + + /** + * @return mixed + */ + public function getConnection() + { + if ( ! $this->isConnected()) { + $this->disconnect(); + $this->connect(); + } + + return $this->connection; + } + + /** + * Get the public permission value. + * + * @return int + */ + public function getPermPublic() + { + return $this->permPublic; + } + + /** + * Get the private permission value. + * + * @return int + */ + public function getPermPrivate() + { + return $this->permPrivate; + } + + /** + * Disconnect on destruction. + */ + public function __destruct() + { + $this->disconnect(); + } + + /** + * Establish a connection. + */ + abstract public function connect(); + + /** + * Close the connection. + */ + abstract public function disconnect(); + + /** + * Check if a connection is active. + * + * @return bool + */ + abstract public function isConnected(); + + protected function escapePath($path) + { + return str_replace(['*', '[', ']'], ['\\*', '\\[', '\\]'], $path); + } +} diff --git a/vendor/league/flysystem/src/Adapter/CanOverwriteFiles.php b/vendor/league/flysystem/src/Adapter/CanOverwriteFiles.php new file mode 100644 index 0000000..fd8d216 --- /dev/null +++ b/vendor/league/flysystem/src/Adapter/CanOverwriteFiles.php @@ -0,0 +1,12 @@ +transferMode = $mode; + + return $this; + } + + /** + * Set if Ssl is enabled. + * + * @param bool $ssl + * + * @return $this + */ + public function setSsl($ssl) + { + $this->ssl = (bool) $ssl; + + return $this; + } + + /** + * Set if passive mode should be used. + * + * @param bool $passive + */ + public function setPassive($passive = true) + { + $this->passive = $passive; + } + + /** + * @param bool $ignorePassiveAddress + */ + public function setIgnorePassiveAddress($ignorePassiveAddress) + { + $this->ignorePassiveAddress = $ignorePassiveAddress; + } + + /** + * @param bool $recurseManually + */ + public function setRecurseManually($recurseManually) + { + $this->recurseManually = $recurseManually; + } + + /** + * @param bool $utf8 + */ + public function setUtf8($utf8) + { + $this->utf8 = (bool) $utf8; + } + + /** + * Connect to the FTP server. + */ + public function connect() + { + $tries = 3; + start_connecting: + + if ($this->ssl) { + $this->connection = @ftp_ssl_connect($this->getHost(), $this->getPort(), $this->getTimeout()); + } else { + $this->connection = @ftp_connect($this->getHost(), $this->getPort(), $this->getTimeout()); + } + + if ( ! $this->connection) { + $tries--; + + if ($tries > 0) goto start_connecting; + + throw new ConnectionRuntimeException('Could not connect to host: ' . $this->getHost() . ', port:' . $this->getPort()); + } + + $this->login(); + $this->setUtf8Mode(); + $this->setConnectionPassiveMode(); + $this->setConnectionRoot(); + $this->isPureFtpd = $this->isPureFtpdServer(); + } + + /** + * Set the connection to UTF-8 mode. + */ + protected function setUtf8Mode() + { + if ($this->utf8) { + $response = ftp_raw($this->connection, "OPTS UTF8 ON"); + if (!in_array(substr($response[0], 0, 3), ['200', '202'])) { + throw new ConnectionRuntimeException( + 'Could not set UTF-8 mode for connection: ' . $this->getHost() . '::' . $this->getPort() + ); + } + } + } + + /** + * Set the connections to passive mode. + * + * @throws ConnectionRuntimeException + */ + protected function setConnectionPassiveMode() + { + if (is_bool($this->ignorePassiveAddress) && defined('FTP_USEPASVADDRESS')) { + ftp_set_option($this->connection, FTP_USEPASVADDRESS, ! $this->ignorePassiveAddress); + } + + if ( ! ftp_pasv($this->connection, $this->passive)) { + throw new ConnectionRuntimeException( + 'Could not set passive mode for connection: ' . $this->getHost() . '::' . $this->getPort() + ); + } + } + + /** + * Set the connection root. + */ + protected function setConnectionRoot() + { + $root = $this->getRoot(); + $connection = $this->connection; + + if ($root && ! ftp_chdir($connection, $root)) { + throw new InvalidRootException('Root is invalid or does not exist: ' . $this->getRoot()); + } + + // Store absolute path for further reference. + // This is needed when creating directories and + // initial root was a relative path, else the root + // would be relative to the chdir'd path. + $this->root = ftp_pwd($connection); + } + + /** + * Login. + * + * @throws ConnectionRuntimeException + */ + protected function login() + { + set_error_handler(function () { + }); + $isLoggedIn = ftp_login( + $this->connection, + $this->getUsername(), + $this->getPassword() + ); + restore_error_handler(); + + if ( ! $isLoggedIn) { + $this->disconnect(); + throw new ConnectionRuntimeException( + 'Could not login with connection: ' . $this->getHost() . '::' . $this->getPort( + ) . ', username: ' . $this->getUsername() + ); + } + } + + /** + * Disconnect from the FTP server. + */ + public function disconnect() + { + if ($this->hasFtpConnection()) { + @ftp_close($this->connection); + } + + $this->connection = null; + } + + /** + * @inheritdoc + */ + public function write($path, $contents, Config $config) + { + $stream = fopen('php://temp', 'w+b'); + fwrite($stream, $contents); + rewind($stream); + $result = $this->writeStream($path, $stream, $config); + fclose($stream); + + if ($result === false) { + return false; + } + + $result['contents'] = $contents; + $result['mimetype'] = $config->get('mimetype') ?: Util::guessMimeType($path, $contents); + + return $result; + } + + /** + * @inheritdoc + */ + public function writeStream($path, $resource, Config $config) + { + $this->ensureDirectory(Util::dirname($path)); + + if ( ! ftp_fput($this->getConnection(), $path, $resource, $this->transferMode)) { + return false; + } + + if ($visibility = $config->get('visibility')) { + $this->setVisibility($path, $visibility); + } + + $type = 'file'; + + return compact('type', 'path', 'visibility'); + } + + /** + * @inheritdoc + */ + public function update($path, $contents, Config $config) + { + return $this->write($path, $contents, $config); + } + + /** + * @inheritdoc + */ + public function updateStream($path, $resource, Config $config) + { + return $this->writeStream($path, $resource, $config); + } + + /** + * @inheritdoc + */ + public function rename($path, $newpath) + { + return ftp_rename($this->getConnection(), $path, $newpath); + } + + /** + * @inheritdoc + */ + public function delete($path) + { + return ftp_delete($this->getConnection(), $path); + } + + /** + * @inheritdoc + */ + public function deleteDir($dirname) + { + $connection = $this->getConnection(); + $contents = array_reverse($this->listDirectoryContents($dirname, false)); + + foreach ($contents as $object) { + if ($object['type'] === 'file') { + if ( ! ftp_delete($connection, $object['path'])) { + return false; + } + } elseif ( ! $this->deleteDir($object['path'])) { + return false; + } + } + + return ftp_rmdir($connection, $dirname); + } + + /** + * @inheritdoc + */ + public function createDir($dirname, Config $config) + { + $connection = $this->getConnection(); + $directories = explode('/', $dirname); + + foreach ($directories as $directory) { + if (false === $this->createActualDirectory($directory, $connection)) { + $this->setConnectionRoot(); + + return false; + } + + ftp_chdir($connection, $directory); + } + + $this->setConnectionRoot(); + + return ['type' => 'dir', 'path' => $dirname]; + } + + /** + * Create a directory. + * + * @param string $directory + * @param resource $connection + * + * @return bool + */ + protected function createActualDirectory($directory, $connection) + { + // List the current directory + $listing = ftp_nlist($connection, '.') ?: []; + + foreach ($listing as $key => $item) { + if (preg_match('~^\./.*~', $item)) { + $listing[$key] = substr($item, 2); + } + } + + if (in_array($directory, $listing, true)) { + return true; + } + + return (boolean) ftp_mkdir($connection, $directory); + } + + /** + * @inheritdoc + */ + public function getMetadata($path) + { + if ($path === '') { + return ['type' => 'dir', 'path' => '']; + } + + if (@ftp_chdir($this->getConnection(), $path) === true) { + $this->setConnectionRoot(); + + return ['type' => 'dir', 'path' => $path]; + } + + $listing = $this->ftpRawlist('-A', $path); + + if (empty($listing) || in_array('total 0', $listing, true)) { + return false; + } + + if (preg_match('/.* not found/', $listing[0])) { + return false; + } + + if (preg_match('/^total [0-9]*$/', $listing[0])) { + array_shift($listing); + } + + return $this->normalizeObject($listing[0], ''); + } + + /** + * @inheritdoc + */ + public function getMimetype($path) + { + if ( ! $metadata = $this->getMetadata($path)) { + return false; + } + + $metadata['mimetype'] = MimeType::detectByFilename($path); + + return $metadata; + } + + /** + * @inheritdoc + */ + public function getTimestamp($path) + { + $timestamp = ftp_mdtm($this->getConnection(), $path); + + return ($timestamp !== -1) ? ['path' => $path, 'timestamp' => $timestamp] : false; + } + + /** + * @inheritdoc + */ + public function read($path) + { + if ( ! $object = $this->readStream($path)) { + return false; + } + + $object['contents'] = stream_get_contents($object['stream']); + fclose($object['stream']); + unset($object['stream']); + + return $object; + } + + /** + * @inheritdoc + */ + public function readStream($path) + { + $stream = fopen('php://temp', 'w+b'); + $result = ftp_fget($this->getConnection(), $stream, $path, $this->transferMode); + rewind($stream); + + if ( ! $result) { + fclose($stream); + + return false; + } + + return ['type' => 'file', 'path' => $path, 'stream' => $stream]; + } + + /** + * @inheritdoc + */ + public function setVisibility($path, $visibility) + { + $mode = $visibility === AdapterInterface::VISIBILITY_PUBLIC ? $this->getPermPublic() : $this->getPermPrivate(); + + if ( ! ftp_chmod($this->getConnection(), $mode, $path)) { + return false; + } + + return compact('path', 'visibility'); + } + + /** + * @inheritdoc + * + * @param string $directory + */ + protected function listDirectoryContents($directory, $recursive = true) + { + if ($recursive && $this->recurseManually) { + return $this->listDirectoryContentsRecursive($directory); + } + + $options = $recursive ? '-alnR' : '-aln'; + $listing = $this->ftpRawlist($options, $directory); + + return $listing ? $this->normalizeListing($listing, $directory) : []; + } + + /** + * @inheritdoc + * + * @param string $directory + */ + protected function listDirectoryContentsRecursive($directory) + { + $listing = $this->normalizeListing($this->ftpRawlist('-aln', $directory) ?: [], $directory); + $output = []; + + foreach ($listing as $item) { + $output[] = $item; + if ($item['type'] !== 'dir') { + continue; + } + $output = array_merge($output, $this->listDirectoryContentsRecursive($item['path'])); + } + + return $output; + } + + /** + * Check if the connection is open. + * + * @return bool + * + * @throws ConnectionErrorException + */ + public function isConnected() + { + return $this->hasFtpConnection() && $this->getRawExecResponseCode('NOOP') === 200; + } + + /** + * @return bool + */ + protected function isPureFtpdServer() + { + $response = ftp_raw($this->connection, 'HELP'); + + return stripos(implode(' ', $response), 'Pure-FTPd') !== false; + } + + /** + * The ftp_rawlist function with optional escaping. + * + * @param string $options + * @param string $path + * + * @return array + */ + protected function ftpRawlist($options, $path) + { + $connection = $this->getConnection(); + + if ($this->isPureFtpd) { + $path = str_replace([' ', '[', ']'], ['\ ', '\\[', '\\]'], $path); + } + + return ftp_rawlist($connection, $options . ' ' . $this->escapePath($path)); + } + + private function getRawExecResponseCode($command) + { + $response = @ftp_raw($this->connection, trim($command)); + + return (int) preg_replace('/\D/', '', implode(' ', $response)); + } + + private function hasFtpConnection(): bool + { + return is_resource($this->connection) || $this->connection instanceof \FTP\Connection; + } +} diff --git a/vendor/league/flysystem/src/Adapter/Ftpd.php b/vendor/league/flysystem/src/Adapter/Ftpd.php new file mode 100644 index 0000000..7e71d19 --- /dev/null +++ b/vendor/league/flysystem/src/Adapter/Ftpd.php @@ -0,0 +1,48 @@ + 'dir', 'path' => '']; + } + + if (@ftp_chdir($this->getConnection(), $path) === true) { + $this->setConnectionRoot(); + + return ['type' => 'dir', 'path' => $path]; + } + + $object = ftp_raw($this->getConnection(), 'STAT ' . $this->escapePath($path)); + + if ( ! $object || count($object) < 3) { + return false; + } + + if (substr($object[1], 0, 5) === "ftpd:") { + return false; + } + + return $this->normalizeObject($object[1], ''); + } + + /** + * @inheritdoc + */ + protected function listDirectoryContents($directory, $recursive = true) + { + $listing = ftp_rawlist($this->getConnection(), $this->escapePath($directory), $recursive); + + if ($listing === false || ( ! empty($listing) && substr($listing[0], 0, 5) === "ftpd:")) { + return []; + } + + return $this->normalizeListing($listing, $directory); + } +} diff --git a/vendor/league/flysystem/src/Adapter/Local.php b/vendor/league/flysystem/src/Adapter/Local.php new file mode 100644 index 0000000..747c463 --- /dev/null +++ b/vendor/league/flysystem/src/Adapter/Local.php @@ -0,0 +1,533 @@ + [ + 'public' => 0644, + 'private' => 0600, + ], + 'dir' => [ + 'public' => 0755, + 'private' => 0700, + ], + ]; + + /** + * @var string + */ + protected $pathSeparator = DIRECTORY_SEPARATOR; + + /** + * @var array + */ + protected $permissionMap; + + /** + * @var int + */ + protected $writeFlags; + + /** + * @var int + */ + private $linkHandling; + + /** + * Constructor. + * + * @param string $root + * @param int $writeFlags + * @param int $linkHandling + * @param array $permissions + * + * @throws LogicException + */ + public function __construct($root, $writeFlags = LOCK_EX, $linkHandling = self::DISALLOW_LINKS, array $permissions = []) + { + $root = is_link($root) ? realpath($root) : $root; + $this->permissionMap = array_replace_recursive(static::$permissions, $permissions); + $this->ensureDirectory($root); + + if ( ! is_dir($root) || ! is_readable($root)) { + throw new LogicException('The root path ' . $root . ' is not readable.'); + } + + $this->setPathPrefix($root); + $this->writeFlags = $writeFlags; + $this->linkHandling = $linkHandling; + } + + /** + * Ensure the root directory exists. + * + * @param string $root root directory path + * + * @return void + * + * @throws Exception in case the root directory can not be created + */ + protected function ensureDirectory($root) + { + if ( ! is_dir($root)) { + $umask = umask(0); + + if ( ! @mkdir($root, $this->permissionMap['dir']['public'], true)) { + $mkdirError = error_get_last(); + } + + umask($umask); + clearstatcache(false, $root); + + if ( ! is_dir($root)) { + $errorMessage = isset($mkdirError['message']) ? $mkdirError['message'] : ''; + throw new Exception(sprintf('Impossible to create the root directory "%s". %s', $root, $errorMessage)); + } + } + } + + /** + * @inheritdoc + */ + public function has($path) + { + $location = $this->applyPathPrefix($path); + + return file_exists($location); + } + + /** + * @inheritdoc + */ + public function write($path, $contents, Config $config) + { + $location = $this->applyPathPrefix($path); + $this->ensureDirectory(dirname($location)); + + if (($size = file_put_contents($location, $contents, $this->writeFlags)) === false) { + return false; + } + + $type = 'file'; + $result = compact('contents', 'type', 'size', 'path'); + + if ($visibility = $config->get('visibility')) { + $result['visibility'] = $visibility; + $this->setVisibility($path, $visibility); + } + + return $result; + } + + /** + * @inheritdoc + */ + public function writeStream($path, $resource, Config $config) + { + $location = $this->applyPathPrefix($path); + $this->ensureDirectory(dirname($location)); + $stream = fopen($location, 'w+b'); + + if ( ! $stream || stream_copy_to_stream($resource, $stream) === false || ! fclose($stream)) { + return false; + } + + $type = 'file'; + $result = compact('type', 'path'); + + if ($visibility = $config->get('visibility')) { + $this->setVisibility($path, $visibility); + $result['visibility'] = $visibility; + } + + return $result; + } + + /** + * @inheritdoc + */ + public function readStream($path) + { + $location = $this->applyPathPrefix($path); + $stream = fopen($location, 'rb'); + + return ['type' => 'file', 'path' => $path, 'stream' => $stream]; + } + + /** + * @inheritdoc + */ + public function updateStream($path, $resource, Config $config) + { + return $this->writeStream($path, $resource, $config); + } + + /** + * @inheritdoc + */ + public function update($path, $contents, Config $config) + { + $location = $this->applyPathPrefix($path); + $size = file_put_contents($location, $contents, $this->writeFlags); + + if ($size === false) { + return false; + } + + $type = 'file'; + + $result = compact('type', 'path', 'size', 'contents'); + + if ($visibility = $config->get('visibility')) { + $this->setVisibility($path, $visibility); + $result['visibility'] = $visibility; + } + + return $result; + } + + /** + * @inheritdoc + */ + public function read($path) + { + $location = $this->applyPathPrefix($path); + $contents = @file_get_contents($location); + + if ($contents === false) { + return false; + } + + return ['type' => 'file', 'path' => $path, 'contents' => $contents]; + } + + /** + * @inheritdoc + */ + public function rename($path, $newpath) + { + $location = $this->applyPathPrefix($path); + $destination = $this->applyPathPrefix($newpath); + $parentDirectory = $this->applyPathPrefix(Util::dirname($newpath)); + $this->ensureDirectory($parentDirectory); + + return rename($location, $destination); + } + + /** + * @inheritdoc + */ + public function copy($path, $newpath) + { + $location = $this->applyPathPrefix($path); + $destination = $this->applyPathPrefix($newpath); + $this->ensureDirectory(dirname($destination)); + + return copy($location, $destination); + } + + /** + * @inheritdoc + */ + public function delete($path) + { + $location = $this->applyPathPrefix($path); + + return @unlink($location); + } + + /** + * @inheritdoc + */ + public function listContents($directory = '', $recursive = false) + { + $result = []; + $location = $this->applyPathPrefix($directory); + + if ( ! is_dir($location)) { + return []; + } + + $iterator = $recursive ? $this->getRecursiveDirectoryIterator($location) : $this->getDirectoryIterator($location); + + foreach ($iterator as $file) { + $path = $this->getFilePath($file); + + if (preg_match('#(^|/|\\\\)\.{1,2}$#', $path)) { + continue; + } + + $result[] = $this->normalizeFileInfo($file); + } + + unset($iterator); + + return array_filter($result); + } + + /** + * @inheritdoc + */ + public function getMetadata($path) + { + $location = $this->applyPathPrefix($path); + clearstatcache(false, $location); + $info = new SplFileInfo($location); + + return $this->normalizeFileInfo($info); + } + + /** + * @inheritdoc + */ + public function getSize($path) + { + return $this->getMetadata($path); + } + + /** + * @inheritdoc + */ + public function getMimetype($path) + { + $location = $this->applyPathPrefix($path); + $finfo = new Finfo(FILEINFO_MIME_TYPE); + $mimetype = $finfo->file($location); + + if (in_array($mimetype, ['application/octet-stream', 'inode/x-empty', 'application/x-empty'])) { + $mimetype = Util\MimeType::detectByFilename($location); + } + + return ['path' => $path, 'type' => 'file', 'mimetype' => $mimetype]; + } + + /** + * @inheritdoc + */ + public function getTimestamp($path) + { + return $this->getMetadata($path); + } + + /** + * @inheritdoc + */ + public function getVisibility($path) + { + $location = $this->applyPathPrefix($path); + clearstatcache(false, $location); + $permissions = octdec(substr(sprintf('%o', fileperms($location)), -4)); + $type = is_dir($location) ? 'dir' : 'file'; + + foreach ($this->permissionMap[$type] as $visibility => $visibilityPermissions) { + if ($visibilityPermissions == $permissions) { + return compact('path', 'visibility'); + } + } + + $visibility = substr(sprintf('%o', fileperms($location)), -4); + + return compact('path', 'visibility'); + } + + /** + * @inheritdoc + */ + public function setVisibility($path, $visibility) + { + $location = $this->applyPathPrefix($path); + $type = is_dir($location) ? 'dir' : 'file'; + $success = chmod($location, $this->permissionMap[$type][$visibility]); + + if ($success === false) { + return false; + } + + return compact('path', 'visibility'); + } + + /** + * @inheritdoc + */ + public function createDir($dirname, Config $config) + { + $location = $this->applyPathPrefix($dirname); + $umask = umask(0); + $visibility = $config->get('visibility', 'public'); + $return = ['path' => $dirname, 'type' => 'dir']; + + if ( ! is_dir($location)) { + if (false === @mkdir($location, $this->permissionMap['dir'][$visibility], true) + || false === is_dir($location)) { + $return = false; + } + } + + umask($umask); + + return $return; + } + + /** + * @inheritdoc + */ + public function deleteDir($dirname) + { + $location = $this->applyPathPrefix($dirname); + + if ( ! is_dir($location)) { + return false; + } + + $contents = $this->getRecursiveDirectoryIterator($location, RecursiveIteratorIterator::CHILD_FIRST); + + /** @var SplFileInfo $file */ + foreach ($contents as $file) { + $this->guardAgainstUnreadableFileInfo($file); + $this->deleteFileInfoObject($file); + } + + unset($contents); + + return rmdir($location); + } + + /** + * @param SplFileInfo $file + */ + protected function deleteFileInfoObject(SplFileInfo $file) + { + switch ($file->getType()) { + case 'dir': + rmdir($file->getRealPath()); + break; + case 'link': + unlink($file->getPathname()); + break; + default: + unlink($file->getRealPath()); + } + } + + /** + * Normalize the file info. + * + * @param SplFileInfo $file + * + * @return array|void + * + * @throws NotSupportedException + */ + protected function normalizeFileInfo(SplFileInfo $file) + { + if ( ! $file->isLink()) { + return $this->mapFileInfo($file); + } + + if ($this->linkHandling & self::DISALLOW_LINKS) { + throw NotSupportedException::forLink($file); + } + } + + /** + * Get the normalized path from a SplFileInfo object. + * + * @param SplFileInfo $file + * + * @return string + */ + protected function getFilePath(SplFileInfo $file) + { + $location = $file->getPathname(); + $path = $this->removePathPrefix($location); + + return trim(str_replace('\\', '/', $path), '/'); + } + + /** + * @param string $path + * @param int $mode + * + * @return RecursiveIteratorIterator + */ + protected function getRecursiveDirectoryIterator($path, $mode = RecursiveIteratorIterator::SELF_FIRST) + { + return new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS), + $mode + ); + } + + /** + * @param string $path + * + * @return DirectoryIterator + */ + protected function getDirectoryIterator($path) + { + $iterator = new DirectoryIterator($path); + + return $iterator; + } + + /** + * @param SplFileInfo $file + * + * @return array + */ + protected function mapFileInfo(SplFileInfo $file) + { + $normalized = [ + 'type' => $file->getType(), + 'path' => $this->getFilePath($file), + ]; + + $normalized['timestamp'] = $file->getMTime(); + + if ($normalized['type'] === 'file') { + $normalized['size'] = $file->getSize(); + } + + return $normalized; + } + + /** + * @param SplFileInfo $file + * + * @throws UnreadableFileException + */ + protected function guardAgainstUnreadableFileInfo(SplFileInfo $file) + { + if ( ! $file->isReadable()) { + throw UnreadableFileException::forFileInfo($file); + } + } +} diff --git a/vendor/league/flysystem/src/Adapter/NullAdapter.php b/vendor/league/flysystem/src/Adapter/NullAdapter.php new file mode 100644 index 0000000..2527087 --- /dev/null +++ b/vendor/league/flysystem/src/Adapter/NullAdapter.php @@ -0,0 +1,144 @@ +get('visibility')) { + $result['visibility'] = $visibility; + } + + return $result; + } + + /** + * @inheritdoc + */ + public function update($path, $contents, Config $config) + { + return false; + } + + /** + * @inheritdoc + */ + public function read($path) + { + return false; + } + + /** + * @inheritdoc + */ + public function rename($path, $newpath) + { + return false; + } + + /** + * @inheritdoc + */ + public function delete($path) + { + return false; + } + + /** + * @inheritdoc + */ + public function listContents($directory = '', $recursive = false) + { + return []; + } + + /** + * @inheritdoc + */ + public function getMetadata($path) + { + return false; + } + + /** + * @inheritdoc + */ + public function getSize($path) + { + return false; + } + + /** + * @inheritdoc + */ + public function getMimetype($path) + { + return false; + } + + /** + * @inheritdoc + */ + public function getTimestamp($path) + { + return false; + } + + /** + * @inheritdoc + */ + public function getVisibility($path) + { + return false; + } + + /** + * @inheritdoc + */ + public function setVisibility($path, $visibility) + { + return compact('visibility'); + } + + /** + * @inheritdoc + */ + public function createDir($dirname, Config $config) + { + return ['path' => $dirname, 'type' => 'dir']; + } + + /** + * @inheritdoc + */ + public function deleteDir($dirname) + { + return false; + } +} diff --git a/vendor/league/flysystem/src/Adapter/Polyfill/NotSupportingVisibilityTrait.php b/vendor/league/flysystem/src/Adapter/Polyfill/NotSupportingVisibilityTrait.php new file mode 100644 index 0000000..fc0a747 --- /dev/null +++ b/vendor/league/flysystem/src/Adapter/Polyfill/NotSupportingVisibilityTrait.php @@ -0,0 +1,33 @@ +readStream($path); + + if ($response === false || ! is_resource($response['stream'])) { + return false; + } + + $result = $this->writeStream($newpath, $response['stream'], new Config()); + + if ($result !== false && is_resource($response['stream'])) { + fclose($response['stream']); + } + + return $result !== false; + } + + // Required abstract method + + /** + * @param string $path + * + * @return resource + */ + abstract public function readStream($path); + + /** + * @param string $path + * @param resource $resource + * @param Config $config + * + * @return resource + */ + abstract public function writeStream($path, $resource, Config $config); +} diff --git a/vendor/league/flysystem/src/Adapter/Polyfill/StreamedReadingTrait.php b/vendor/league/flysystem/src/Adapter/Polyfill/StreamedReadingTrait.php new file mode 100644 index 0000000..2b31c01 --- /dev/null +++ b/vendor/league/flysystem/src/Adapter/Polyfill/StreamedReadingTrait.php @@ -0,0 +1,44 @@ +read($path)) { + return false; + } + + $stream = fopen('php://temp', 'w+b'); + fwrite($stream, $data['contents']); + rewind($stream); + $data['stream'] = $stream; + unset($data['contents']); + + return $data; + } + + /** + * Reads a file. + * + * @param string $path + * + * @return array|false + * + * @see League\Flysystem\ReadInterface::read() + */ + abstract public function read($path); +} diff --git a/vendor/league/flysystem/src/Adapter/Polyfill/StreamedTrait.php b/vendor/league/flysystem/src/Adapter/Polyfill/StreamedTrait.php new file mode 100644 index 0000000..8042496 --- /dev/null +++ b/vendor/league/flysystem/src/Adapter/Polyfill/StreamedTrait.php @@ -0,0 +1,9 @@ +stream($path, $resource, $config, 'write'); + } + + /** + * Update a file using a stream. + * + * @param string $path + * @param resource $resource + * @param Config $config Config object or visibility setting + * + * @return mixed false of file metadata + */ + public function updateStream($path, $resource, Config $config) + { + return $this->stream($path, $resource, $config, 'update'); + } + + // Required abstract methods + abstract public function write($pash, $contents, Config $config); + abstract public function update($pash, $contents, Config $config); +} diff --git a/vendor/league/flysystem/src/Adapter/SynologyFtp.php b/vendor/league/flysystem/src/Adapter/SynologyFtp.php new file mode 100644 index 0000000..fe0d344 --- /dev/null +++ b/vendor/league/flysystem/src/Adapter/SynologyFtp.php @@ -0,0 +1,8 @@ +settings = $settings; + } + + /** + * Get a setting. + * + * @param string $key + * @param mixed $default + * + * @return mixed config setting or default when not found + */ + public function get($key, $default = null) + { + if ( ! array_key_exists($key, $this->settings)) { + return $this->getDefault($key, $default); + } + + return $this->settings[$key]; + } + + /** + * Check if an item exists by key. + * + * @param string $key + * + * @return bool + */ + public function has($key) + { + if (array_key_exists($key, $this->settings)) { + return true; + } + + return $this->fallback instanceof Config + ? $this->fallback->has($key) + : false; + } + + /** + * Try to retrieve a default setting from a config fallback. + * + * @param string $key + * @param mixed $default + * + * @return mixed config setting or default when not found + */ + protected function getDefault($key, $default) + { + if ( ! $this->fallback) { + return $default; + } + + return $this->fallback->get($key, $default); + } + + /** + * Set a setting. + * + * @param string $key + * @param mixed $value + * + * @return $this + */ + public function set($key, $value) + { + $this->settings[$key] = $value; + + return $this; + } + + /** + * Set the fallback. + * + * @param Config $fallback + * + * @return $this + */ + public function setFallback(Config $fallback) + { + $this->fallback = $fallback; + + return $this; + } +} diff --git a/vendor/league/flysystem/src/ConfigAwareTrait.php b/vendor/league/flysystem/src/ConfigAwareTrait.php new file mode 100644 index 0000000..202d605 --- /dev/null +++ b/vendor/league/flysystem/src/ConfigAwareTrait.php @@ -0,0 +1,49 @@ +config = $config ? Util::ensureConfig($config) : new Config; + } + + /** + * Get the Config. + * + * @return Config config object + */ + public function getConfig() + { + return $this->config; + } + + /** + * Convert a config array to a Config object with the correct fallback. + * + * @param array $config + * + * @return Config + */ + protected function prepareConfig(array $config) + { + $config = new Config($config); + $config->setFallback($this->getConfig()); + + return $config; + } +} diff --git a/vendor/league/flysystem/src/ConnectionErrorException.php b/vendor/league/flysystem/src/ConnectionErrorException.php new file mode 100644 index 0000000..adb651d --- /dev/null +++ b/vendor/league/flysystem/src/ConnectionErrorException.php @@ -0,0 +1,9 @@ +filesystem->deleteDir($this->path); + } + + /** + * List the directory contents. + * + * @param bool $recursive + * + * @return array|bool directory contents or false + */ + public function getContents($recursive = false) + { + return $this->filesystem->listContents($this->path, $recursive); + } +} diff --git a/vendor/league/flysystem/src/Exception.php b/vendor/league/flysystem/src/Exception.php new file mode 100644 index 0000000..4596c0a --- /dev/null +++ b/vendor/league/flysystem/src/Exception.php @@ -0,0 +1,8 @@ +filesystem->has($this->path); + } + + /** + * Read the file. + * + * @return string|false file contents + */ + public function read() + { + return $this->filesystem->read($this->path); + } + + /** + * Read the file as a stream. + * + * @return resource|false file stream + */ + public function readStream() + { + return $this->filesystem->readStream($this->path); + } + + /** + * Write the new file. + * + * @param string $content + * + * @return bool success boolean + */ + public function write($content) + { + return $this->filesystem->write($this->path, $content); + } + + /** + * Write the new file using a stream. + * + * @param resource $resource + * + * @return bool success boolean + */ + public function writeStream($resource) + { + return $this->filesystem->writeStream($this->path, $resource); + } + + /** + * Update the file contents. + * + * @param string $content + * + * @return bool success boolean + */ + public function update($content) + { + return $this->filesystem->update($this->path, $content); + } + + /** + * Update the file contents with a stream. + * + * @param resource $resource + * + * @return bool success boolean + */ + public function updateStream($resource) + { + return $this->filesystem->updateStream($this->path, $resource); + } + + /** + * Create the file or update if exists. + * + * @param string $content + * + * @return bool success boolean + */ + public function put($content) + { + return $this->filesystem->put($this->path, $content); + } + + /** + * Create the file or update if exists using a stream. + * + * @param resource $resource + * + * @return bool success boolean + */ + public function putStream($resource) + { + return $this->filesystem->putStream($this->path, $resource); + } + + /** + * Rename the file. + * + * @param string $newpath + * + * @return bool success boolean + */ + public function rename($newpath) + { + if ($this->filesystem->rename($this->path, $newpath)) { + $this->path = $newpath; + + return true; + } + + return false; + } + + /** + * Copy the file. + * + * @param string $newpath + * + * @return File|false new file or false + */ + public function copy($newpath) + { + if ($this->filesystem->copy($this->path, $newpath)) { + return new File($this->filesystem, $newpath); + } + + return false; + } + + /** + * Get the file's timestamp. + * + * @return string|false The timestamp or false on failure. + */ + public function getTimestamp() + { + return $this->filesystem->getTimestamp($this->path); + } + + /** + * Get the file's mimetype. + * + * @return string|false The file mime-type or false on failure. + */ + public function getMimetype() + { + return $this->filesystem->getMimetype($this->path); + } + + /** + * Get the file's visibility. + * + * @return string|false The visibility (public|private) or false on failure. + */ + public function getVisibility() + { + return $this->filesystem->getVisibility($this->path); + } + + /** + * Get the file's metadata. + * + * @return array|false The file metadata or false on failure. + */ + public function getMetadata() + { + return $this->filesystem->getMetadata($this->path); + } + + /** + * Get the file size. + * + * @return int|false The file size or false on failure. + */ + public function getSize() + { + return $this->filesystem->getSize($this->path); + } + + /** + * Delete the file. + * + * @return bool success boolean + */ + public function delete() + { + return $this->filesystem->delete($this->path); + } +} diff --git a/vendor/league/flysystem/src/FileExistsException.php b/vendor/league/flysystem/src/FileExistsException.php new file mode 100644 index 0000000..c82e20c --- /dev/null +++ b/vendor/league/flysystem/src/FileExistsException.php @@ -0,0 +1,37 @@ +path = $path; + + parent::__construct('File already exists at path: ' . $this->getPath(), $code, $previous); + } + + /** + * Get the path which was found. + * + * @return string + */ + public function getPath() + { + return $this->path; + } +} diff --git a/vendor/league/flysystem/src/FileNotFoundException.php b/vendor/league/flysystem/src/FileNotFoundException.php new file mode 100644 index 0000000..989df69 --- /dev/null +++ b/vendor/league/flysystem/src/FileNotFoundException.php @@ -0,0 +1,37 @@ +path = $path; + + parent::__construct('File not found at path: ' . $this->getPath(), $code, $previous); + } + + /** + * Get the path which was not found. + * + * @return string + */ + public function getPath() + { + return $this->path; + } +} diff --git a/vendor/league/flysystem/src/Filesystem.php b/vendor/league/flysystem/src/Filesystem.php new file mode 100644 index 0000000..c4eaf27 --- /dev/null +++ b/vendor/league/flysystem/src/Filesystem.php @@ -0,0 +1,409 @@ +adapter = $adapter; + $this->setConfig($config); + } + + /** + * Get the Adapter. + * + * @return AdapterInterface adapter + */ + public function getAdapter() + { + return $this->adapter; + } + + /** + * @inheritdoc + */ + public function has($path) + { + $path = Util::normalizePath($path); + + return strlen($path) === 0 ? false : (bool) $this->getAdapter()->has($path); + } + + /** + * @inheritdoc + */ + public function write($path, $contents, array $config = []) + { + $path = Util::normalizePath($path); + $this->assertAbsent($path); + $config = $this->prepareConfig($config); + + return (bool) $this->getAdapter()->write($path, $contents, $config); + } + + /** + * @inheritdoc + */ + public function writeStream($path, $resource, array $config = []) + { + if ( ! is_resource($resource) || get_resource_type($resource) !== 'stream') { + throw new InvalidArgumentException(__METHOD__ . ' expects argument #2 to be a valid resource.'); + } + + $path = Util::normalizePath($path); + $this->assertAbsent($path); + $config = $this->prepareConfig($config); + + Util::rewindStream($resource); + + return (bool) $this->getAdapter()->writeStream($path, $resource, $config); + } + + /** + * @inheritdoc + */ + public function put($path, $contents, array $config = []) + { + $path = Util::normalizePath($path); + $config = $this->prepareConfig($config); + + if ( ! $this->getAdapter() instanceof CanOverwriteFiles && $this->has($path)) { + return (bool) $this->getAdapter()->update($path, $contents, $config); + } + + return (bool) $this->getAdapter()->write($path, $contents, $config); + } + + /** + * @inheritdoc + */ + public function putStream($path, $resource, array $config = []) + { + if ( ! is_resource($resource) || get_resource_type($resource) !== 'stream') { + throw new InvalidArgumentException(__METHOD__ . ' expects argument #2 to be a valid resource.'); + } + + $path = Util::normalizePath($path); + $config = $this->prepareConfig($config); + Util::rewindStream($resource); + + if ( ! $this->getAdapter() instanceof CanOverwriteFiles && $this->has($path)) { + return (bool) $this->getAdapter()->updateStream($path, $resource, $config); + } + + return (bool) $this->getAdapter()->writeStream($path, $resource, $config); + } + + /** + * @inheritdoc + */ + public function readAndDelete($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + $contents = $this->read($path); + + if ($contents === false) { + return false; + } + + $this->delete($path); + + return $contents; + } + + /** + * @inheritdoc + */ + public function update($path, $contents, array $config = []) + { + $path = Util::normalizePath($path); + $config = $this->prepareConfig($config); + + $this->assertPresent($path); + + return (bool) $this->getAdapter()->update($path, $contents, $config); + } + + /** + * @inheritdoc + */ + public function updateStream($path, $resource, array $config = []) + { + if ( ! is_resource($resource) || get_resource_type($resource) !== 'stream') { + throw new InvalidArgumentException(__METHOD__ . ' expects argument #2 to be a valid resource.'); + } + + $path = Util::normalizePath($path); + $config = $this->prepareConfig($config); + $this->assertPresent($path); + Util::rewindStream($resource); + + return (bool) $this->getAdapter()->updateStream($path, $resource, $config); + } + + /** + * @inheritdoc + */ + public function read($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + if ( ! ($object = $this->getAdapter()->read($path))) { + return false; + } + + return $object['contents']; + } + + /** + * @inheritdoc + */ + public function readStream($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + if ( ! $object = $this->getAdapter()->readStream($path)) { + return false; + } + + return $object['stream']; + } + + /** + * @inheritdoc + */ + public function rename($path, $newpath) + { + $path = Util::normalizePath($path); + $newpath = Util::normalizePath($newpath); + $this->assertPresent($path); + $this->assertAbsent($newpath); + + return (bool) $this->getAdapter()->rename($path, $newpath); + } + + /** + * @inheritdoc + */ + public function copy($path, $newpath) + { + $path = Util::normalizePath($path); + $newpath = Util::normalizePath($newpath); + $this->assertPresent($path); + $this->assertAbsent($newpath); + + return $this->getAdapter()->copy($path, $newpath); + } + + /** + * @inheritdoc + */ + public function delete($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + return $this->getAdapter()->delete($path); + } + + /** + * @inheritdoc + */ + public function deleteDir($dirname) + { + $dirname = Util::normalizePath($dirname); + + if ($dirname === '') { + throw new RootViolationException('Root directories can not be deleted.'); + } + + return (bool) $this->getAdapter()->deleteDir($dirname); + } + + /** + * @inheritdoc + */ + public function createDir($dirname, array $config = []) + { + $dirname = Util::normalizePath($dirname); + $config = $this->prepareConfig($config); + + return (bool) $this->getAdapter()->createDir($dirname, $config); + } + + /** + * @inheritdoc + */ + public function listContents($directory = '', $recursive = false) + { + $directory = Util::normalizePath($directory); + $contents = $this->getAdapter()->listContents($directory, $recursive); + + return (new ContentListingFormatter($directory, $recursive, $this->config->get('case_sensitive', true))) + ->formatListing($contents); + } + + /** + * @inheritdoc + */ + public function getMimetype($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + if (( ! $object = $this->getAdapter()->getMimetype($path)) || ! array_key_exists('mimetype', $object)) { + return false; + } + + return $object['mimetype']; + } + + /** + * @inheritdoc + */ + public function getTimestamp($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + if (( ! $object = $this->getAdapter()->getTimestamp($path)) || ! array_key_exists('timestamp', $object)) { + return false; + } + + return (int) $object['timestamp']; + } + + /** + * @inheritdoc + */ + public function getVisibility($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + if (( ! $object = $this->getAdapter()->getVisibility($path)) || ! array_key_exists('visibility', $object)) { + return false; + } + + return $object['visibility']; + } + + /** + * @inheritdoc + */ + public function getSize($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + if (( ! $object = $this->getAdapter()->getSize($path)) || ! array_key_exists('size', $object)) { + return false; + } + + return (int) $object['size']; + } + + /** + * @inheritdoc + */ + public function setVisibility($path, $visibility) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + return (bool) $this->getAdapter()->setVisibility($path, $visibility); + } + + /** + * @inheritdoc + */ + public function getMetadata($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + return $this->getAdapter()->getMetadata($path); + } + + /** + * @inheritdoc + */ + public function get($path, Handler $handler = null) + { + $path = Util::normalizePath($path); + + if ( ! $handler) { + $metadata = $this->getMetadata($path); + $handler = ($metadata && $metadata['type'] === 'file') ? new File($this, $path) : new Directory($this, $path); + } + + $handler->setPath($path); + $handler->setFilesystem($this); + + return $handler; + } + + /** + * Assert a file is present. + * + * @param string $path path to file + * + * @throws FileNotFoundException + * + * @return void + */ + public function assertPresent($path) + { + if ($this->config->get('disable_asserts', false) === false && ! $this->has($path)) { + throw new FileNotFoundException($path); + } + } + + /** + * Assert a file is absent. + * + * @param string $path path to file + * + * @throws FileExistsException + * + * @return void + */ + public function assertAbsent($path) + { + if ($this->config->get('disable_asserts', false) === false && $this->has($path)) { + throw new FileExistsException($path); + } + } +} diff --git a/vendor/league/flysystem/src/FilesystemException.php b/vendor/league/flysystem/src/FilesystemException.php new file mode 100644 index 0000000..3121e53 --- /dev/null +++ b/vendor/league/flysystem/src/FilesystemException.php @@ -0,0 +1,7 @@ +path = $path; + $this->filesystem = $filesystem; + } + + /** + * Check whether the entree is a directory. + * + * @return bool + */ + public function isDir() + { + return $this->getType() === 'dir'; + } + + /** + * Check whether the entree is a file. + * + * @return bool + */ + public function isFile() + { + return $this->getType() === 'file'; + } + + /** + * Retrieve the entree type (file|dir). + * + * @return string file or dir + */ + public function getType() + { + $metadata = $this->filesystem->getMetadata($this->path); + + return $metadata ? $metadata['type'] : 'dir'; + } + + /** + * Set the Filesystem object. + * + * @param FilesystemInterface $filesystem + * + * @return $this + */ + public function setFilesystem(FilesystemInterface $filesystem) + { + $this->filesystem = $filesystem; + + return $this; + } + + /** + * Retrieve the Filesystem object. + * + * @return FilesystemInterface + */ + public function getFilesystem() + { + return $this->filesystem; + } + + /** + * Set the entree path. + * + * @param string $path + * + * @return $this + */ + public function setPath($path) + { + $this->path = $path; + + return $this; + } + + /** + * Retrieve the entree path. + * + * @return string path + */ + public function getPath() + { + return $this->path; + } + + /** + * Plugins pass-through. + * + * @param string $method + * @param array $arguments + * + * @return mixed + */ + public function __call($method, array $arguments) + { + array_unshift($arguments, $this->path); + $callback = [$this->filesystem, $method]; + + try { + return call_user_func_array($callback, $arguments); + } catch (BadMethodCallException $e) { + throw new BadMethodCallException( + 'Call to undefined method ' + . get_called_class() + . '::' . $method + ); + } + } +} diff --git a/vendor/league/flysystem/src/InvalidRootException.php b/vendor/league/flysystem/src/InvalidRootException.php new file mode 100644 index 0000000..468d1d5 --- /dev/null +++ b/vendor/league/flysystem/src/InvalidRootException.php @@ -0,0 +1,9 @@ + Filesystem,] + * + * @throws InvalidArgumentException + */ + public function __construct(array $filesystems = []) + { + $this->mountFilesystems($filesystems); + } + + /** + * Mount filesystems. + * + * @param FilesystemInterface[] $filesystems [:prefix => Filesystem,] + * + * @throws InvalidArgumentException + * + * @return $this + */ + public function mountFilesystems(array $filesystems) + { + foreach ($filesystems as $prefix => $filesystem) { + $this->mountFilesystem($prefix, $filesystem); + } + + return $this; + } + + /** + * Mount filesystems. + * + * @param string $prefix + * @param FilesystemInterface $filesystem + * + * @throws InvalidArgumentException + * + * @return $this + */ + public function mountFilesystem($prefix, FilesystemInterface $filesystem) + { + if ( ! is_string($prefix)) { + throw new InvalidArgumentException(__METHOD__ . ' expects argument #1 to be a string.'); + } + + $this->filesystems[$prefix] = $filesystem; + + return $this; + } + + /** + * Get the filesystem with the corresponding prefix. + * + * @param string $prefix + * + * @throws FilesystemNotFoundException + * + * @return FilesystemInterface + */ + public function getFilesystem($prefix) + { + if ( ! isset($this->filesystems[$prefix])) { + throw new FilesystemNotFoundException('No filesystem mounted with prefix ' . $prefix); + } + + return $this->filesystems[$prefix]; + } + + /** + * Retrieve the prefix from an arguments array. + * + * @param array $arguments + * + * @throws InvalidArgumentException + * + * @return array [:prefix, :arguments] + */ + public function filterPrefix(array $arguments) + { + if (empty($arguments)) { + throw new InvalidArgumentException('At least one argument needed'); + } + + $path = array_shift($arguments); + + if ( ! is_string($path)) { + throw new InvalidArgumentException('First argument should be a string'); + } + + list($prefix, $path) = $this->getPrefixAndPath($path); + array_unshift($arguments, $path); + + return [$prefix, $arguments]; + } + + /** + * @param string $directory + * @param bool $recursive + * + * @throws InvalidArgumentException + * @throws FilesystemNotFoundException + * + * @return array + */ + public function listContents($directory = '', $recursive = false) + { + list($prefix, $directory) = $this->getPrefixAndPath($directory); + $filesystem = $this->getFilesystem($prefix); + $result = $filesystem->listContents($directory, $recursive); + + foreach ($result as &$file) { + $file['filesystem'] = $prefix; + } + + return $result; + } + + /** + * Call forwarder. + * + * @param string $method + * @param array $arguments + * + * @throws InvalidArgumentException + * @throws FilesystemNotFoundException + * + * @return mixed + */ + public function __call($method, $arguments) + { + list($prefix, $arguments) = $this->filterPrefix($arguments); + + return $this->invokePluginOnFilesystem($method, $arguments, $prefix); + } + + /** + * @param string $from + * @param string $to + * @param array $config + * + * @throws InvalidArgumentException + * @throws FilesystemNotFoundException + * @throws FileExistsException + * + * @return bool + */ + public function copy($from, $to, array $config = []) + { + list($prefixFrom, $from) = $this->getPrefixAndPath($from); + + $buffer = $this->getFilesystem($prefixFrom)->readStream($from); + + if ($buffer === false) { + return false; + } + + list($prefixTo, $to) = $this->getPrefixAndPath($to); + + $result = $this->getFilesystem($prefixTo)->writeStream($to, $buffer, $config); + + if (is_resource($buffer)) { + fclose($buffer); + } + + return $result; + } + + /** + * List with plugin adapter. + * + * @param array $keys + * @param string $directory + * @param bool $recursive + * + * @throws InvalidArgumentException + * @throws FilesystemNotFoundException + * + * @return array + */ + public function listWith(array $keys = [], $directory = '', $recursive = false) + { + list($prefix, $directory) = $this->getPrefixAndPath($directory); + $arguments = [$keys, $directory, $recursive]; + + return $this->invokePluginOnFilesystem('listWith', $arguments, $prefix); + } + + /** + * Move a file. + * + * @param string $from + * @param string $to + * @param array $config + * + * @throws InvalidArgumentException + * @throws FilesystemNotFoundException + * + * @return bool + */ + public function move($from, $to, array $config = []) + { + list($prefixFrom, $pathFrom) = $this->getPrefixAndPath($from); + list($prefixTo, $pathTo) = $this->getPrefixAndPath($to); + + if ($prefixFrom === $prefixTo) { + $filesystem = $this->getFilesystem($prefixFrom); + $renamed = $filesystem->rename($pathFrom, $pathTo); + + if ($renamed && isset($config['visibility'])) { + return $filesystem->setVisibility($pathTo, $config['visibility']); + } + + return $renamed; + } + + $copied = $this->copy($from, $to, $config); + + if ($copied) { + return $this->delete($from); + } + + return false; + } + + /** + * Invoke a plugin on a filesystem mounted on a given prefix. + * + * @param string $method + * @param array $arguments + * @param string $prefix + * + * @throws FilesystemNotFoundException + * + * @return mixed + */ + public function invokePluginOnFilesystem($method, $arguments, $prefix) + { + $filesystem = $this->getFilesystem($prefix); + + try { + return $this->invokePlugin($method, $arguments, $filesystem); + } catch (PluginNotFoundException $e) { + // Let it pass, it's ok, don't panic. + } + + $callback = [$filesystem, $method]; + + return call_user_func_array($callback, $arguments); + } + + /** + * @param string $path + * + * @throws InvalidArgumentException + * + * @return string[] [:prefix, :path] + */ + protected function getPrefixAndPath($path) + { + if (strpos($path, '://') < 1) { + throw new InvalidArgumentException('No prefix detected in path: ' . $path); + } + + return explode('://', $path, 2); + } + + /** + * Check whether a file exists. + * + * @param string $path + * + * @return bool + */ + public function has($path) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->has($path); + } + + /** + * Read a file. + * + * @param string $path The path to the file. + * + * @throws FileNotFoundException + * + * @return string|false The file contents or false on failure. + */ + public function read($path) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->read($path); + } + + /** + * Retrieves a read-stream for a path. + * + * @param string $path The path to the file. + * + * @throws FileNotFoundException + * + * @return resource|false The path resource or false on failure. + */ + public function readStream($path) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->readStream($path); + } + + /** + * Get a file's metadata. + * + * @param string $path The path to the file. + * + * @throws FileNotFoundException + * + * @return array|false The file metadata or false on failure. + */ + public function getMetadata($path) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->getMetadata($path); + } + + /** + * Get a file's size. + * + * @param string $path The path to the file. + * + * @throws FileNotFoundException + * + * @return int|false The file size or false on failure. + */ + public function getSize($path) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->getSize($path); + } + + /** + * Get a file's mime-type. + * + * @param string $path The path to the file. + * + * @throws FileNotFoundException + * + * @return string|false The file mime-type or false on failure. + */ + public function getMimetype($path) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->getMimetype($path); + } + + /** + * Get a file's timestamp. + * + * @param string $path The path to the file. + * + * @throws FileNotFoundException + * + * @return string|false The timestamp or false on failure. + */ + public function getTimestamp($path) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->getTimestamp($path); + } + + /** + * Get a file's visibility. + * + * @param string $path The path to the file. + * + * @throws FileNotFoundException + * + * @return string|false The visibility (public|private) or false on failure. + */ + public function getVisibility($path) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->getVisibility($path); + } + + /** + * Write a new file. + * + * @param string $path The path of the new file. + * @param string $contents The file contents. + * @param array $config An optional configuration array. + * + * @throws FileExistsException + * + * @return bool True on success, false on failure. + */ + public function write($path, $contents, array $config = []) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->write($path, $contents, $config); + } + + /** + * Write a new file using a stream. + * + * @param string $path The path of the new file. + * @param resource $resource The file handle. + * @param array $config An optional configuration array. + * + * @throws InvalidArgumentException If $resource is not a file handle. + * @throws FileExistsException + * + * @return bool True on success, false on failure. + */ + public function writeStream($path, $resource, array $config = []) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->writeStream($path, $resource, $config); + } + + /** + * Update an existing file. + * + * @param string $path The path of the existing file. + * @param string $contents The file contents. + * @param array $config An optional configuration array. + * + * @throws FileNotFoundException + * + * @return bool True on success, false on failure. + */ + public function update($path, $contents, array $config = []) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->update($path, $contents, $config); + } + + /** + * Update an existing file using a stream. + * + * @param string $path The path of the existing file. + * @param resource $resource The file handle. + * @param array $config An optional configuration array. + * + * @throws InvalidArgumentException If $resource is not a file handle. + * @throws FileNotFoundException + * + * @return bool True on success, false on failure. + */ + public function updateStream($path, $resource, array $config = []) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->updateStream($path, $resource, $config); + } + + /** + * Rename a file. + * + * @param string $path Path to the existing file. + * @param string $newpath The new path of the file. + * + * @throws FileExistsException Thrown if $newpath exists. + * @throws FileNotFoundException Thrown if $path does not exist. + * + * @return bool True on success, false on failure. + */ + public function rename($path, $newpath) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->rename($path, $newpath); + } + + /** + * Delete a file. + * + * @param string $path + * + * @throws FileNotFoundException + * + * @return bool True on success, false on failure. + */ + public function delete($path) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->delete($path); + } + + /** + * Delete a directory. + * + * @param string $dirname + * + * @throws RootViolationException Thrown if $dirname is empty. + * + * @return bool True on success, false on failure. + */ + public function deleteDir($dirname) + { + list($prefix, $dirname) = $this->getPrefixAndPath($dirname); + + return $this->getFilesystem($prefix)->deleteDir($dirname); + } + + /** + * Create a directory. + * + * @param string $dirname The name of the new directory. + * @param array $config An optional configuration array. + * + * @return bool True on success, false on failure. + */ + public function createDir($dirname, array $config = []) + { + list($prefix, $dirname) = $this->getPrefixAndPath($dirname); + + return $this->getFilesystem($prefix)->createDir($dirname); + } + + /** + * Set the visibility for a file. + * + * @param string $path The path to the file. + * @param string $visibility One of 'public' or 'private'. + * + * @throws FileNotFoundException + * + * @return bool True on success, false on failure. + */ + public function setVisibility($path, $visibility) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->setVisibility($path, $visibility); + } + + /** + * Create a file or update if exists. + * + * @param string $path The path to the file. + * @param string $contents The file contents. + * @param array $config An optional configuration array. + * + * @return bool True on success, false on failure. + */ + public function put($path, $contents, array $config = []) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->put($path, $contents, $config); + } + + /** + * Create a file or update if exists. + * + * @param string $path The path to the file. + * @param resource $resource The file handle. + * @param array $config An optional configuration array. + * + * @throws InvalidArgumentException Thrown if $resource is not a resource. + * + * @return bool True on success, false on failure. + */ + public function putStream($path, $resource, array $config = []) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->putStream($path, $resource, $config); + } + + /** + * Read and delete a file. + * + * @param string $path The path to the file. + * + * @throws FileNotFoundException + * + * @return string|false The file contents, or false on failure. + */ + public function readAndDelete($path) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->readAndDelete($path); + } + + /** + * Get a file/directory handler. + * + * @deprecated + * + * @param string $path The path to the file. + * @param Handler $handler An optional existing handler to populate. + * + * @return Handler Either a file or directory handler. + */ + public function get($path, Handler $handler = null) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->get($path); + } +} diff --git a/vendor/league/flysystem/src/NotSupportedException.php b/vendor/league/flysystem/src/NotSupportedException.php new file mode 100644 index 0000000..e0a989b --- /dev/null +++ b/vendor/league/flysystem/src/NotSupportedException.php @@ -0,0 +1,37 @@ +getPathname()); + } + + /** + * Create a new exception for a link. + * + * @param string $systemType + * + * @return static + */ + public static function forFtpSystemType($systemType) + { + $message = "The FTP system type '$systemType' is currently not supported."; + + return new static($message); + } +} diff --git a/vendor/league/flysystem/src/Plugin/AbstractPlugin.php b/vendor/league/flysystem/src/Plugin/AbstractPlugin.php new file mode 100644 index 0000000..0d56789 --- /dev/null +++ b/vendor/league/flysystem/src/Plugin/AbstractPlugin.php @@ -0,0 +1,24 @@ +filesystem = $filesystem; + } +} diff --git a/vendor/league/flysystem/src/Plugin/EmptyDir.php b/vendor/league/flysystem/src/Plugin/EmptyDir.php new file mode 100644 index 0000000..b5ae7f5 --- /dev/null +++ b/vendor/league/flysystem/src/Plugin/EmptyDir.php @@ -0,0 +1,34 @@ +filesystem->listContents($dirname, false); + + foreach ($listing as $item) { + if ($item['type'] === 'dir') { + $this->filesystem->deleteDir($item['path']); + } else { + $this->filesystem->delete($item['path']); + } + } + } +} diff --git a/vendor/league/flysystem/src/Plugin/ForcedCopy.php b/vendor/league/flysystem/src/Plugin/ForcedCopy.php new file mode 100644 index 0000000..a41e9f3 --- /dev/null +++ b/vendor/league/flysystem/src/Plugin/ForcedCopy.php @@ -0,0 +1,44 @@ +filesystem->delete($newpath); + } catch (FileNotFoundException $e) { + // The destination path does not exist. That's ok. + $deleted = true; + } + + if ($deleted) { + return $this->filesystem->copy($path, $newpath); + } + + return false; + } +} diff --git a/vendor/league/flysystem/src/Plugin/ForcedRename.php b/vendor/league/flysystem/src/Plugin/ForcedRename.php new file mode 100644 index 0000000..3f51cd6 --- /dev/null +++ b/vendor/league/flysystem/src/Plugin/ForcedRename.php @@ -0,0 +1,44 @@ +filesystem->delete($newpath); + } catch (FileNotFoundException $e) { + // The destination path does not exist. That's ok. + $deleted = true; + } + + if ($deleted) { + return $this->filesystem->rename($path, $newpath); + } + + return false; + } +} diff --git a/vendor/league/flysystem/src/Plugin/GetWithMetadata.php b/vendor/league/flysystem/src/Plugin/GetWithMetadata.php new file mode 100644 index 0000000..2f13d2f --- /dev/null +++ b/vendor/league/flysystem/src/Plugin/GetWithMetadata.php @@ -0,0 +1,51 @@ +filesystem->getMetadata($path); + + if ( ! $object) { + return false; + } + + $keys = array_diff($metadata, array_keys($object)); + + foreach ($keys as $key) { + if ( ! method_exists($this->filesystem, $method = 'get' . ucfirst($key))) { + throw new InvalidArgumentException('Could not fetch metadata: ' . $key); + } + + $object[$key] = $this->filesystem->{$method}($path); + } + + return $object; + } +} diff --git a/vendor/league/flysystem/src/Plugin/ListFiles.php b/vendor/league/flysystem/src/Plugin/ListFiles.php new file mode 100644 index 0000000..9669fe7 --- /dev/null +++ b/vendor/league/flysystem/src/Plugin/ListFiles.php @@ -0,0 +1,35 @@ +filesystem->listContents($directory, $recursive); + + $filter = function ($object) { + return $object['type'] === 'file'; + }; + + return array_values(array_filter($contents, $filter)); + } +} diff --git a/vendor/league/flysystem/src/Plugin/ListPaths.php b/vendor/league/flysystem/src/Plugin/ListPaths.php new file mode 100644 index 0000000..0889d1f --- /dev/null +++ b/vendor/league/flysystem/src/Plugin/ListPaths.php @@ -0,0 +1,36 @@ +filesystem->listContents($directory, $recursive); + + foreach ($contents as $object) { + $result[] = $object['path']; + } + + return $result; + } +} diff --git a/vendor/league/flysystem/src/Plugin/ListWith.php b/vendor/league/flysystem/src/Plugin/ListWith.php new file mode 100644 index 0000000..d64debe --- /dev/null +++ b/vendor/league/flysystem/src/Plugin/ListWith.php @@ -0,0 +1,60 @@ +filesystem->listContents($directory, $recursive); + + foreach ($contents as $index => $object) { + if ($object['type'] === 'file') { + $missingKeys = array_diff($keys, array_keys($object)); + $contents[$index] = array_reduce($missingKeys, [$this, 'getMetadataByName'], $object); + } + } + + return $contents; + } + + /** + * Get a meta-data value by key name. + * + * @param array $object + * @param string $key + * + * @return array + */ + protected function getMetadataByName(array $object, $key) + { + $method = 'get' . ucfirst($key); + + if ( ! method_exists($this->filesystem, $method)) { + throw new \InvalidArgumentException('Could not get meta-data for key: ' . $key); + } + + $object[$key] = $this->filesystem->{$method}($object['path']); + + return $object; + } +} diff --git a/vendor/league/flysystem/src/Plugin/PluggableTrait.php b/vendor/league/flysystem/src/Plugin/PluggableTrait.php new file mode 100644 index 0000000..922edfe --- /dev/null +++ b/vendor/league/flysystem/src/Plugin/PluggableTrait.php @@ -0,0 +1,97 @@ +plugins[$plugin->getMethod()] = $plugin; + + return $this; + } + + /** + * Find a specific plugin. + * + * @param string $method + * + * @throws PluginNotFoundException + * + * @return PluginInterface + */ + protected function findPlugin($method) + { + if ( ! isset($this->plugins[$method])) { + throw new PluginNotFoundException('Plugin not found for method: ' . $method); + } + + return $this->plugins[$method]; + } + + /** + * Invoke a plugin by method name. + * + * @param string $method + * @param array $arguments + * @param FilesystemInterface $filesystem + * + * @throws PluginNotFoundException + * + * @return mixed + */ + protected function invokePlugin($method, array $arguments, FilesystemInterface $filesystem) + { + $plugin = $this->findPlugin($method); + $plugin->setFilesystem($filesystem); + $callback = [$plugin, 'handle']; + + return call_user_func_array($callback, $arguments); + } + + /** + * Plugins pass-through. + * + * @param string $method + * @param array $arguments + * + * @throws BadMethodCallException + * + * @return mixed + */ + public function __call($method, array $arguments) + { + try { + return $this->invokePlugin($method, $arguments, $this); + } catch (PluginNotFoundException $e) { + throw new BadMethodCallException( + 'Call to undefined method ' + . get_class($this) + . '::' . $method + ); + } + } +} diff --git a/vendor/league/flysystem/src/Plugin/PluginNotFoundException.php b/vendor/league/flysystem/src/Plugin/PluginNotFoundException.php new file mode 100644 index 0000000..fd1d7e7 --- /dev/null +++ b/vendor/league/flysystem/src/Plugin/PluginNotFoundException.php @@ -0,0 +1,10 @@ +hash = spl_object_hash($this); + static::$safeStorage[$this->hash] = []; + } + + public function storeSafely($key, $value) + { + static::$safeStorage[$this->hash][$key] = $value; + } + + public function retrieveSafely($key) + { + if (array_key_exists($key, static::$safeStorage[$this->hash])) { + return static::$safeStorage[$this->hash][$key]; + } + } + + public function __destruct() + { + unset(static::$safeStorage[$this->hash]); + } +} diff --git a/vendor/league/flysystem/src/UnreadableFileException.php b/vendor/league/flysystem/src/UnreadableFileException.php new file mode 100644 index 0000000..e668033 --- /dev/null +++ b/vendor/league/flysystem/src/UnreadableFileException.php @@ -0,0 +1,18 @@ +getRealPath() + ) + ); + } +} diff --git a/vendor/league/flysystem/src/Util.php b/vendor/league/flysystem/src/Util.php new file mode 100644 index 0000000..1a2db71 --- /dev/null +++ b/vendor/league/flysystem/src/Util.php @@ -0,0 +1,354 @@ + '']; + } + + /** + * Normalize a dirname return value. + * + * @param string $dirname + * + * @return string normalized dirname + */ + public static function normalizeDirname($dirname) + { + return $dirname === '.' ? '' : $dirname; + } + + /** + * Get a normalized dirname from a path. + * + * @param string $path + * + * @return string dirname + */ + public static function dirname($path) + { + return static::normalizeDirname(dirname($path)); + } + + /** + * Map result arrays. + * + * @param array $object + * @param array $map + * + * @return array mapped result + */ + public static function map(array $object, array $map) + { + $result = []; + + foreach ($map as $from => $to) { + if ( ! isset($object[$from])) { + continue; + } + + $result[$to] = $object[$from]; + } + + return $result; + } + + /** + * Normalize path. + * + * @param string $path + * + * @throws LogicException + * + * @return string + */ + public static function normalizePath($path) + { + return static::normalizeRelativePath($path); + } + + /** + * Normalize relative directories in a path. + * + * @param string $path + * + * @throws LogicException + * + * @return string + */ + public static function normalizeRelativePath($path) + { + $path = str_replace('\\', '/', $path); + $path = static::removeFunkyWhiteSpace($path); + $parts = []; + + foreach (explode('/', $path) as $part) { + switch ($part) { + case '': + case '.': + break; + + case '..': + if (empty($parts)) { + throw new LogicException( + 'Path is outside of the defined root, path: [' . $path . ']' + ); + } + array_pop($parts); + break; + + default: + $parts[] = $part; + break; + } + } + + $path = implode('/', $parts); + + return $path; + } + + /** + * Rejects unprintable characters and invalid unicode characters. + * + * @param string $path + * + * @return string $path + */ + protected static function removeFunkyWhiteSpace($path) + { + if (preg_match('#\p{C}+#u', $path)) { + throw CorruptedPathDetected::forPath($path); + } + + return $path; + } + + /** + * Normalize prefix. + * + * @param string $prefix + * @param string $separator + * + * @return string normalized path + */ + public static function normalizePrefix($prefix, $separator) + { + return rtrim($prefix, $separator) . $separator; + } + + /** + * Get content size. + * + * @param string $contents + * + * @return int content size + */ + public static function contentSize($contents) + { + return defined('MB_OVERLOAD_STRING') ? mb_strlen($contents, '8bit') : strlen($contents); + } + + /** + * Guess MIME Type based on the path of the file and it's content. + * + * @param string $path + * @param string|resource $content + * + * @return string|null MIME Type or NULL if no extension detected + */ + public static function guessMimeType($path, $content) + { + $mimeType = MimeType::detectByContent($content); + + if ( ! (empty($mimeType) || in_array($mimeType, ['application/x-empty', 'text/plain', 'text/x-asm']))) { + return $mimeType; + } + + return MimeType::detectByFilename($path); + } + + /** + * Emulate directories. + * + * @param array $listing + * + * @return array listing with emulated directories + */ + public static function emulateDirectories(array $listing) + { + $directories = []; + $listedDirectories = []; + + foreach ($listing as $object) { + [$directories, $listedDirectories] = static::emulateObjectDirectories($object, $directories, $listedDirectories); + } + + $directories = array_diff(array_unique($directories), array_unique($listedDirectories)); + + foreach ($directories as $directory) { + $listing[] = static::pathinfo($directory) + ['type' => 'dir']; + } + + return $listing; + } + + /** + * Ensure a Config instance. + * + * @param null|array|Config $config + * + * @return Config config instance + * + * @throw LogicException + */ + public static function ensureConfig($config) + { + if ($config === null) { + return new Config(); + } + + if ($config instanceof Config) { + return $config; + } + + if (is_array($config)) { + return new Config($config); + } + + throw new LogicException('A config should either be an array or a Flysystem\Config object.'); + } + + /** + * Rewind a stream. + * + * @param resource $resource + */ + public static function rewindStream($resource) + { + if (ftell($resource) !== 0 && static::isSeekableStream($resource)) { + rewind($resource); + } + } + + public static function isSeekableStream($resource) + { + $metadata = stream_get_meta_data($resource); + + return $metadata['seekable']; + } + + /** + * Get the size of a stream. + * + * @param resource $resource + * + * @return int|null stream size + */ + public static function getStreamSize($resource) + { + $stat = fstat($resource); + + if ( ! is_array($stat) || ! isset($stat['size'])) { + return null; + } + + return $stat['size']; + } + + /** + * Emulate the directories of a single object. + * + * @param array $object + * @param array $directories + * @param array $listedDirectories + * + * @return array + */ + protected static function emulateObjectDirectories(array $object, array $directories, array $listedDirectories) + { + if ($object['type'] === 'dir') { + $listedDirectories[] = $object['path']; + } + + if ( ! isset($object['dirname']) || trim($object['dirname']) === '') { + return [$directories, $listedDirectories]; + } + + $parent = $object['dirname']; + + while (isset($parent) && trim($parent) !== '' && ! in_array($parent, $directories)) { + $directories[] = $parent; + $parent = static::dirname($parent); + } + + if (isset($object['type']) && $object['type'] === 'dir') { + $listedDirectories[] = $object['path']; + + return [$directories, $listedDirectories]; + } + + return [$directories, $listedDirectories]; + } + + /** + * Returns the trailing name component of the path. + * + * @param string $path + * + * @return string + */ + private static function basename($path) + { + $separators = DIRECTORY_SEPARATOR === '/' ? '/' : '\/'; + + $path = rtrim($path, $separators); + + $basename = preg_replace('#.*?([^' . preg_quote($separators, '#') . ']+$)#', '$1', $path); + + if (DIRECTORY_SEPARATOR === '/') { + return $basename; + } + // @codeCoverageIgnoreStart + // Extra Windows path munging. This is tested via AppVeyor, but code + // coverage is not reported. + + // Handle relative paths with drive letters. c:file.txt. + while (preg_match('#^[a-zA-Z]{1}:[^\\\/]#', $basename)) { + $basename = substr($basename, 2); + } + + // Remove colon for standalone drive letter names. + if (preg_match('#^[a-zA-Z]{1}:$#', $basename)) { + $basename = rtrim($basename, ':'); + } + + return $basename; + // @codeCoverageIgnoreEnd + } +} diff --git a/vendor/league/flysystem/src/Util/ContentListingFormatter.php b/vendor/league/flysystem/src/Util/ContentListingFormatter.php new file mode 100644 index 0000000..ae0d3b9 --- /dev/null +++ b/vendor/league/flysystem/src/Util/ContentListingFormatter.php @@ -0,0 +1,122 @@ +directory = rtrim($directory, '/'); + $this->recursive = $recursive; + $this->caseSensitive = $caseSensitive; + } + + /** + * Format contents listing. + * + * @param array $listing + * + * @return array + */ + public function formatListing(array $listing) + { + $listing = array_filter(array_map([$this, 'addPathInfo'], $listing), [$this, 'isEntryOutOfScope']); + + return $this->sortListing(array_values($listing)); + } + + private function addPathInfo(array $entry) + { + return $entry + Util::pathinfo($entry['path']); + } + + /** + * Determine if the entry is out of scope. + * + * @param array $entry + * + * @return bool + */ + private function isEntryOutOfScope(array $entry) + { + if (empty($entry['path']) && $entry['path'] !== '0') { + return false; + } + + if ($this->recursive) { + return $this->residesInDirectory($entry); + } + + return $this->isDirectChild($entry); + } + + /** + * Check if the entry resides within the parent directory. + * + * @param array $entry + * + * @return bool + */ + private function residesInDirectory(array $entry) + { + if ($this->directory === '') { + return true; + } + + return $this->caseSensitive + ? strpos($entry['path'], $this->directory . '/') === 0 + : stripos($entry['path'], $this->directory . '/') === 0; + } + + /** + * Check if the entry is a direct child of the directory. + * + * @param array $entry + * + * @return bool + */ + private function isDirectChild(array $entry) + { + return $this->caseSensitive + ? $entry['dirname'] === $this->directory + : strcasecmp($this->directory, $entry['dirname']) === 0; + } + + /** + * @param array $listing + * + * @return array + */ + private function sortListing(array $listing) + { + usort($listing, function ($a, $b) { + return strcasecmp($a['path'], $b['path']); + }); + + return $listing; + } +} diff --git a/vendor/league/flysystem/src/Util/MimeType.php b/vendor/league/flysystem/src/Util/MimeType.php new file mode 100644 index 0000000..35cba3f --- /dev/null +++ b/vendor/league/flysystem/src/Util/MimeType.php @@ -0,0 +1,80 @@ +detectMimeTypeFromBuffer($content); + } + + return 'text/plain'; + } + + /** + * Detects MIME Type based on file extension. + * + * @param string $extension + * + * @return string MIME Type + */ + public static function detectByFileExtension($extension) + { + return static::detector()->detectMimeTypeFromPath('artificial.' . $extension) ?: 'text/plain'; + } + + /** + * @param string $filename + * + * @return string MIME Type + */ + public static function detectByFilename($filename) + { + return static::detector()->detectMimeTypeFromPath($filename) ?: 'text/plain'; + } + + /** + * @return array Map of file extension to MIME Type + */ + public static function getExtensionToMimeTypeMap() + { + return static::$extensionToMimeTypeMap; + } +} diff --git a/vendor/league/flysystem/src/Util/StreamHasher.php b/vendor/league/flysystem/src/Util/StreamHasher.php new file mode 100644 index 0000000..938ec5d --- /dev/null +++ b/vendor/league/flysystem/src/Util/StreamHasher.php @@ -0,0 +1,36 @@ +algo = $algo; + } + + /** + * @param resource $resource + * + * @return string + */ + public function hash($resource) + { + rewind($resource); + $context = hash_init($this->algo); + hash_update_stream($context, $resource); + fclose($resource); + + return hash_final($context); + } +} diff --git a/vendor/league/mime-type-detection/CHANGELOG.md b/vendor/league/mime-type-detection/CHANGELOG.md new file mode 100644 index 0000000..9f296f2 --- /dev/null +++ b/vendor/league/mime-type-detection/CHANGELOG.md @@ -0,0 +1,25 @@ +# Changelog + +## 1.9.0 - 2021-11-21 + +### Updated + +- Updated lookup + +- ## 1.8.0 - 2021-09-25 + +### Added + +- Added the decorator `OverridingExtensionToMimeTypeMap` which allows you to override values. + +## 1.7.0 - 2021-01-18 + +### Added + +- Added a `bufferSampleSize` parameter to the `FinfoMimeTypeDetector` class that allows you to send a reduced content sample which costs less memory. + +## 1.6.0 - 2021-01-18 + +### Changes + +- Updated generated mime-type map diff --git a/vendor/league/mime-type-detection/LICENSE b/vendor/league/mime-type-detection/LICENSE new file mode 100644 index 0000000..7c1027d --- /dev/null +++ b/vendor/league/mime-type-detection/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2013-2020 Frank de Jonge + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/league/mime-type-detection/composer.json b/vendor/league/mime-type-detection/composer.json new file mode 100644 index 0000000..80ca1af --- /dev/null +++ b/vendor/league/mime-type-detection/composer.json @@ -0,0 +1,34 @@ +{ + "name": "league/mime-type-detection", + "description": "Mime-type detection for Flysystem", + "license": "MIT", + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "scripts": { + "test": "vendor/bin/phpunit", + "phpstan": "vendor/bin/phpstan analyse -l 6 src" + }, + "require": { + "php": "^7.2 || ^8.0", + "ext-fileinfo": "*" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.8 || ^9.3", + "phpstan/phpstan": "^0.12.68", + "friendsofphp/php-cs-fixer": "^3.2" + }, + "autoload": { + "psr-4": { + "League\\MimeTypeDetection\\": "src" + } + }, + "config": { + "platform": { + "php": "7.2.0" + } + } +} diff --git a/vendor/league/mime-type-detection/src/EmptyExtensionToMimeTypeMap.php b/vendor/league/mime-type-detection/src/EmptyExtensionToMimeTypeMap.php new file mode 100644 index 0000000..fc04241 --- /dev/null +++ b/vendor/league/mime-type-detection/src/EmptyExtensionToMimeTypeMap.php @@ -0,0 +1,13 @@ +extensions = $extensions ?: new GeneratedExtensionToMimeTypeMap(); + } + + public function detectMimeType(string $path, $contents): ?string + { + return $this->detectMimeTypeFromPath($path); + } + + public function detectMimeTypeFromPath(string $path): ?string + { + $extension = strtolower(pathinfo($path, PATHINFO_EXTENSION)); + + return $this->extensions->lookupMimeType($extension); + } + + public function detectMimeTypeFromFile(string $path): ?string + { + return $this->detectMimeTypeFromPath($path); + } + + public function detectMimeTypeFromBuffer(string $contents): ?string + { + return null; + } +} diff --git a/vendor/league/mime-type-detection/src/ExtensionToMimeTypeMap.php b/vendor/league/mime-type-detection/src/ExtensionToMimeTypeMap.php new file mode 100644 index 0000000..1dad7bc --- /dev/null +++ b/vendor/league/mime-type-detection/src/ExtensionToMimeTypeMap.php @@ -0,0 +1,10 @@ +finfo = new finfo(FILEINFO_MIME_TYPE, $magicFile); + $this->extensionMap = $extensionMap ?: new GeneratedExtensionToMimeTypeMap(); + $this->bufferSampleSize = $bufferSampleSize; + } + + public function detectMimeType(string $path, $contents): ?string + { + $mimeType = is_string($contents) + ? (@$this->finfo->buffer($this->takeSample($contents)) ?: null) + : null; + + if ($mimeType !== null && ! in_array($mimeType, self::INCONCLUSIVE_MIME_TYPES)) { + return $mimeType; + } + + return $this->detectMimeTypeFromPath($path); + } + + public function detectMimeTypeFromPath(string $path): ?string + { + $extension = strtolower(pathinfo($path, PATHINFO_EXTENSION)); + + return $this->extensionMap->lookupMimeType($extension); + } + + public function detectMimeTypeFromFile(string $path): ?string + { + return @$this->finfo->file($path) ?: null; + } + + public function detectMimeTypeFromBuffer(string $contents): ?string + { + return @$this->finfo->buffer($this->takeSample($contents)) ?: null; + } + + private function takeSample(string $contents): string + { + if ($this->bufferSampleSize === null) { + return $contents; + } + + return (string) substr($contents, 0, $this->bufferSampleSize); + } +} diff --git a/vendor/league/mime-type-detection/src/GeneratedExtensionToMimeTypeMap.php b/vendor/league/mime-type-detection/src/GeneratedExtensionToMimeTypeMap.php new file mode 100644 index 0000000..d1f714a --- /dev/null +++ b/vendor/league/mime-type-detection/src/GeneratedExtensionToMimeTypeMap.php @@ -0,0 +1,1220 @@ + 'application/vnd.1000minds.decision-model+xml', + '3dml' => 'text/vnd.in3d.3dml', + '3ds' => 'image/x-3ds', + '3g2' => 'video/3gpp2', + '3gp' => 'video/3gp', + '3gpp' => 'video/3gpp', + '3mf' => 'model/3mf', + '7z' => 'application/x-7z-compressed', + '7zip' => 'application/x-7z-compressed', + '123' => 'application/vnd.lotus-1-2-3', + 'aab' => 'application/x-authorware-bin', + 'aac' => 'audio/x-acc', + 'aam' => 'application/x-authorware-map', + 'aas' => 'application/x-authorware-seg', + 'abw' => 'application/x-abiword', + 'ac' => 'application/vnd.nokia.n-gage.ac+xml', + 'ac3' => 'audio/ac3', + 'acc' => 'application/vnd.americandynamics.acc', + 'ace' => 'application/x-ace-compressed', + 'acu' => 'application/vnd.acucobol', + 'acutc' => 'application/vnd.acucorp', + 'adp' => 'audio/adpcm', + 'aep' => 'application/vnd.audiograph', + 'afm' => 'application/x-font-type1', + 'afp' => 'application/vnd.ibm.modcap', + 'age' => 'application/vnd.age', + 'ahead' => 'application/vnd.ahead.space', + 'ai' => 'application/pdf', + 'aif' => 'audio/x-aiff', + 'aifc' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'air' => 'application/vnd.adobe.air-application-installer-package+zip', + 'ait' => 'application/vnd.dvb.ait', + 'ami' => 'application/vnd.amiga.ami', + 'amr' => 'audio/amr', + 'apk' => 'application/vnd.android.package-archive', + 'apng' => 'image/apng', + 'appcache' => 'text/cache-manifest', + 'application' => 'application/x-ms-application', + 'apr' => 'application/vnd.lotus-approach', + 'arc' => 'application/x-freearc', + 'arj' => 'application/x-arj', + 'asc' => 'application/pgp-signature', + 'asf' => 'video/x-ms-asf', + 'asm' => 'text/x-asm', + 'aso' => 'application/vnd.accpac.simply.aso', + 'asx' => 'video/x-ms-asf', + 'atc' => 'application/vnd.acucorp', + 'atom' => 'application/atom+xml', + 'atomcat' => 'application/atomcat+xml', + 'atomdeleted' => 'application/atomdeleted+xml', + 'atomsvc' => 'application/atomsvc+xml', + 'atx' => 'application/vnd.antix.game-component', + 'au' => 'audio/x-au', + 'avi' => 'video/x-msvideo', + 'avif' => 'image/avif', + 'aw' => 'application/applixware', + 'azf' => 'application/vnd.airzip.filesecure.azf', + 'azs' => 'application/vnd.airzip.filesecure.azs', + 'azv' => 'image/vnd.airzip.accelerator.azv', + 'azw' => 'application/vnd.amazon.ebook', + 'b16' => 'image/vnd.pco.b16', + 'bat' => 'application/x-msdownload', + 'bcpio' => 'application/x-bcpio', + 'bdf' => 'application/x-font-bdf', + 'bdm' => 'application/vnd.syncml.dm+wbxml', + 'bdoc' => 'application/x-bdoc', + 'bed' => 'application/vnd.realvnc.bed', + 'bh2' => 'application/vnd.fujitsu.oasysprs', + 'bin' => 'application/octet-stream', + 'blb' => 'application/x-blorb', + 'blorb' => 'application/x-blorb', + 'bmi' => 'application/vnd.bmi', + 'bmml' => 'application/vnd.balsamiq.bmml+xml', + 'bmp' => 'image/bmp', + 'book' => 'application/vnd.framemaker', + 'box' => 'application/vnd.previewsystems.box', + 'boz' => 'application/x-bzip2', + 'bpk' => 'application/octet-stream', + 'bpmn' => 'application/octet-stream', + 'bsp' => 'model/vnd.valve.source.compiled-map', + 'btif' => 'image/prs.btif', + 'buffer' => 'application/octet-stream', + 'bz' => 'application/x-bzip', + 'bz2' => 'application/x-bzip2', + 'c' => 'text/x-c', + 'c4d' => 'application/vnd.clonk.c4group', + 'c4f' => 'application/vnd.clonk.c4group', + 'c4g' => 'application/vnd.clonk.c4group', + 'c4p' => 'application/vnd.clonk.c4group', + 'c4u' => 'application/vnd.clonk.c4group', + 'c11amc' => 'application/vnd.cluetrust.cartomobile-config', + 'c11amz' => 'application/vnd.cluetrust.cartomobile-config-pkg', + 'cab' => 'application/vnd.ms-cab-compressed', + 'caf' => 'audio/x-caf', + 'cap' => 'application/vnd.tcpdump.pcap', + 'car' => 'application/vnd.curl.car', + 'cat' => 'application/vnd.ms-pki.seccat', + 'cb7' => 'application/x-cbr', + 'cba' => 'application/x-cbr', + 'cbr' => 'application/x-cbr', + 'cbt' => 'application/x-cbr', + 'cbz' => 'application/x-cbr', + 'cc' => 'text/x-c', + 'cco' => 'application/x-cocoa', + 'cct' => 'application/x-director', + 'ccxml' => 'application/ccxml+xml', + 'cdbcmsg' => 'application/vnd.contact.cmsg', + 'cdf' => 'application/x-netcdf', + 'cdfx' => 'application/cdfx+xml', + 'cdkey' => 'application/vnd.mediastation.cdkey', + 'cdmia' => 'application/cdmi-capability', + 'cdmic' => 'application/cdmi-container', + 'cdmid' => 'application/cdmi-domain', + 'cdmio' => 'application/cdmi-object', + 'cdmiq' => 'application/cdmi-queue', + 'cdr' => 'application/cdr', + 'cdx' => 'chemical/x-cdx', + 'cdxml' => 'application/vnd.chemdraw+xml', + 'cdy' => 'application/vnd.cinderella', + 'cer' => 'application/pkix-cert', + 'cfs' => 'application/x-cfs-compressed', + 'cgm' => 'image/cgm', + 'chat' => 'application/x-chat', + 'chm' => 'application/vnd.ms-htmlhelp', + 'chrt' => 'application/vnd.kde.kchart', + 'cif' => 'chemical/x-cif', + 'cii' => 'application/vnd.anser-web-certificate-issue-initiation', + 'cil' => 'application/vnd.ms-artgalry', + 'cjs' => 'application/node', + 'cla' => 'application/vnd.claymore', + 'class' => 'application/octet-stream', + 'clkk' => 'application/vnd.crick.clicker.keyboard', + 'clkp' => 'application/vnd.crick.clicker.palette', + 'clkt' => 'application/vnd.crick.clicker.template', + 'clkw' => 'application/vnd.crick.clicker.wordbank', + 'clkx' => 'application/vnd.crick.clicker', + 'clp' => 'application/x-msclip', + 'cmc' => 'application/vnd.cosmocaller', + 'cmdf' => 'chemical/x-cmdf', + 'cml' => 'chemical/x-cml', + 'cmp' => 'application/vnd.yellowriver-custom-menu', + 'cmx' => 'image/x-cmx', + 'cod' => 'application/vnd.rim.cod', + 'coffee' => 'text/coffeescript', + 'com' => 'application/x-msdownload', + 'conf' => 'text/plain', + 'cpio' => 'application/x-cpio', + 'cpp' => 'text/x-c', + 'cpt' => 'application/mac-compactpro', + 'crd' => 'application/x-mscardfile', + 'crl' => 'application/pkix-crl', + 'crt' => 'application/x-x509-ca-cert', + 'crx' => 'application/x-chrome-extension', + 'cryptonote' => 'application/vnd.rig.cryptonote', + 'csh' => 'application/x-csh', + 'csl' => 'application/vnd.citationstyles.style+xml', + 'csml' => 'chemical/x-csml', + 'csp' => 'application/vnd.commonspace', + 'csr' => 'application/octet-stream', + 'css' => 'text/css', + 'cst' => 'application/x-director', + 'csv' => 'text/csv', + 'cu' => 'application/cu-seeme', + 'curl' => 'text/vnd.curl', + 'cww' => 'application/prs.cww', + 'cxt' => 'application/x-director', + 'cxx' => 'text/x-c', + 'dae' => 'model/vnd.collada+xml', + 'daf' => 'application/vnd.mobius.daf', + 'dart' => 'application/vnd.dart', + 'dataless' => 'application/vnd.fdsn.seed', + 'davmount' => 'application/davmount+xml', + 'dbf' => 'application/vnd.dbf', + 'dbk' => 'application/docbook+xml', + 'dcr' => 'application/x-director', + 'dcurl' => 'text/vnd.curl.dcurl', + 'dd2' => 'application/vnd.oma.dd2+xml', + 'ddd' => 'application/vnd.fujixerox.ddd', + 'ddf' => 'application/vnd.syncml.dmddf+xml', + 'dds' => 'image/vnd.ms-dds', + 'deb' => 'application/x-debian-package', + 'def' => 'text/plain', + 'deploy' => 'application/octet-stream', + 'der' => 'application/x-x509-ca-cert', + 'dfac' => 'application/vnd.dreamfactory', + 'dgc' => 'application/x-dgc-compressed', + 'dic' => 'text/x-c', + 'dir' => 'application/x-director', + 'dis' => 'application/vnd.mobius.dis', + 'disposition-notification' => 'message/disposition-notification', + 'dist' => 'application/octet-stream', + 'distz' => 'application/octet-stream', + 'djv' => 'image/vnd.djvu', + 'djvu' => 'image/vnd.djvu', + 'dll' => 'application/octet-stream', + 'dmg' => 'application/x-apple-diskimage', + 'dmn' => 'application/octet-stream', + 'dmp' => 'application/vnd.tcpdump.pcap', + 'dms' => 'application/octet-stream', + 'dna' => 'application/vnd.dna', + 'doc' => 'application/msword', + 'docm' => 'application/vnd.ms-word.template.macroEnabled.12', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'dot' => 'application/msword', + 'dotm' => 'application/vnd.ms-word.template.macroEnabled.12', + 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', + 'dp' => 'application/vnd.osgi.dp', + 'dpg' => 'application/vnd.dpgraph', + 'dra' => 'audio/vnd.dra', + 'drle' => 'image/dicom-rle', + 'dsc' => 'text/prs.lines.tag', + 'dssc' => 'application/dssc+der', + 'dtb' => 'application/x-dtbook+xml', + 'dtd' => 'application/xml-dtd', + 'dts' => 'audio/vnd.dts', + 'dtshd' => 'audio/vnd.dts.hd', + 'dump' => 'application/octet-stream', + 'dvb' => 'video/vnd.dvb.file', + 'dvi' => 'application/x-dvi', + 'dwd' => 'application/atsc-dwd+xml', + 'dwf' => 'model/vnd.dwf', + 'dwg' => 'image/vnd.dwg', + 'dxf' => 'image/vnd.dxf', + 'dxp' => 'application/vnd.spotfire.dxp', + 'dxr' => 'application/x-director', + 'ear' => 'application/java-archive', + 'ecelp4800' => 'audio/vnd.nuera.ecelp4800', + 'ecelp7470' => 'audio/vnd.nuera.ecelp7470', + 'ecelp9600' => 'audio/vnd.nuera.ecelp9600', + 'ecma' => 'application/ecmascript', + 'edm' => 'application/vnd.novadigm.edm', + 'edx' => 'application/vnd.novadigm.edx', + 'efif' => 'application/vnd.picsel', + 'ei6' => 'application/vnd.pg.osasli', + 'elc' => 'application/octet-stream', + 'emf' => 'image/emf', + 'eml' => 'message/rfc822', + 'emma' => 'application/emma+xml', + 'emotionml' => 'application/emotionml+xml', + 'emz' => 'application/x-msmetafile', + 'eol' => 'audio/vnd.digital-winds', + 'eot' => 'application/vnd.ms-fontobject', + 'eps' => 'application/postscript', + 'epub' => 'application/epub+zip', + 'es' => 'application/ecmascript', + 'es3' => 'application/vnd.eszigno3+xml', + 'esa' => 'application/vnd.osgi.subsystem', + 'esf' => 'application/vnd.epson.esf', + 'et3' => 'application/vnd.eszigno3+xml', + 'etx' => 'text/x-setext', + 'eva' => 'application/x-eva', + 'evy' => 'application/x-envoy', + 'exe' => 'application/octet-stream', + 'exi' => 'application/exi', + 'exp' => 'application/express', + 'exr' => 'image/aces', + 'ext' => 'application/vnd.novadigm.ext', + 'ez' => 'application/andrew-inset', + 'ez2' => 'application/vnd.ezpix-album', + 'ez3' => 'application/vnd.ezpix-package', + 'f' => 'text/x-fortran', + 'f4v' => 'video/mp4', + 'f77' => 'text/x-fortran', + 'f90' => 'text/x-fortran', + 'fbs' => 'image/vnd.fastbidsheet', + 'fcdt' => 'application/vnd.adobe.formscentral.fcdt', + 'fcs' => 'application/vnd.isac.fcs', + 'fdf' => 'application/vnd.fdf', + 'fdt' => 'application/fdt+xml', + 'fe_launch' => 'application/vnd.denovo.fcselayout-link', + 'fg5' => 'application/vnd.fujitsu.oasysgp', + 'fgd' => 'application/x-director', + 'fh' => 'image/x-freehand', + 'fh4' => 'image/x-freehand', + 'fh5' => 'image/x-freehand', + 'fh7' => 'image/x-freehand', + 'fhc' => 'image/x-freehand', + 'fig' => 'application/x-xfig', + 'fits' => 'image/fits', + 'flac' => 'audio/x-flac', + 'fli' => 'video/x-fli', + 'flo' => 'application/vnd.micrografx.flo', + 'flv' => 'video/x-flv', + 'flw' => 'application/vnd.kde.kivio', + 'flx' => 'text/vnd.fmi.flexstor', + 'fly' => 'text/vnd.fly', + 'fm' => 'application/vnd.framemaker', + 'fnc' => 'application/vnd.frogans.fnc', + 'fo' => 'application/vnd.software602.filler.form+xml', + 'for' => 'text/x-fortran', + 'fpx' => 'image/vnd.fpx', + 'frame' => 'application/vnd.framemaker', + 'fsc' => 'application/vnd.fsc.weblaunch', + 'fst' => 'image/vnd.fst', + 'ftc' => 'application/vnd.fluxtime.clip', + 'fti' => 'application/vnd.anser-web-funds-transfer-initiation', + 'fvt' => 'video/vnd.fvt', + 'fxp' => 'application/vnd.adobe.fxp', + 'fxpl' => 'application/vnd.adobe.fxp', + 'fzs' => 'application/vnd.fuzzysheet', + 'g2w' => 'application/vnd.geoplan', + 'g3' => 'image/g3fax', + 'g3w' => 'application/vnd.geospace', + 'gac' => 'application/vnd.groove-account', + 'gam' => 'application/x-tads', + 'gbr' => 'application/rpki-ghostbusters', + 'gca' => 'application/x-gca-compressed', + 'gdl' => 'model/vnd.gdl', + 'gdoc' => 'application/vnd.google-apps.document', + 'ged' => 'text/vnd.familysearch.gedcom', + 'geo' => 'application/vnd.dynageo', + 'geojson' => 'application/geo+json', + 'gex' => 'application/vnd.geometry-explorer', + 'ggb' => 'application/vnd.geogebra.file', + 'ggt' => 'application/vnd.geogebra.tool', + 'ghf' => 'application/vnd.groove-help', + 'gif' => 'image/gif', + 'gim' => 'application/vnd.groove-identity-message', + 'glb' => 'model/gltf-binary', + 'gltf' => 'model/gltf+json', + 'gml' => 'application/gml+xml', + 'gmx' => 'application/vnd.gmx', + 'gnumeric' => 'application/x-gnumeric', + 'gpg' => 'application/gpg-keys', + 'gph' => 'application/vnd.flographit', + 'gpx' => 'application/gpx+xml', + 'gqf' => 'application/vnd.grafeq', + 'gqs' => 'application/vnd.grafeq', + 'gram' => 'application/srgs', + 'gramps' => 'application/x-gramps-xml', + 'gre' => 'application/vnd.geometry-explorer', + 'grv' => 'application/vnd.groove-injector', + 'grxml' => 'application/srgs+xml', + 'gsf' => 'application/x-font-ghostscript', + 'gsheet' => 'application/vnd.google-apps.spreadsheet', + 'gslides' => 'application/vnd.google-apps.presentation', + 'gtar' => 'application/x-gtar', + 'gtm' => 'application/vnd.groove-tool-message', + 'gtw' => 'model/vnd.gtw', + 'gv' => 'text/vnd.graphviz', + 'gxf' => 'application/gxf', + 'gxt' => 'application/vnd.geonext', + 'gz' => 'application/gzip', + 'gzip' => 'application/gzip', + 'h' => 'text/x-c', + 'h261' => 'video/h261', + 'h263' => 'video/h263', + 'h264' => 'video/h264', + 'hal' => 'application/vnd.hal+xml', + 'hbci' => 'application/vnd.hbci', + 'hbs' => 'text/x-handlebars-template', + 'hdd' => 'application/x-virtualbox-hdd', + 'hdf' => 'application/x-hdf', + 'heic' => 'image/heic', + 'heics' => 'image/heic-sequence', + 'heif' => 'image/heif', + 'heifs' => 'image/heif-sequence', + 'hej2' => 'image/hej2k', + 'held' => 'application/atsc-held+xml', + 'hh' => 'text/x-c', + 'hjson' => 'application/hjson', + 'hlp' => 'application/winhlp', + 'hpgl' => 'application/vnd.hp-hpgl', + 'hpid' => 'application/vnd.hp-hpid', + 'hps' => 'application/vnd.hp-hps', + 'hqx' => 'application/mac-binhex40', + 'hsj2' => 'image/hsj2', + 'htc' => 'text/x-component', + 'htke' => 'application/vnd.kenameaapp', + 'htm' => 'text/html', + 'html' => 'text/html', + 'hvd' => 'application/vnd.yamaha.hv-dic', + 'hvp' => 'application/vnd.yamaha.hv-voice', + 'hvs' => 'application/vnd.yamaha.hv-script', + 'i2g' => 'application/vnd.intergeo', + 'icc' => 'application/vnd.iccprofile', + 'ice' => 'x-conference/x-cooltalk', + 'icm' => 'application/vnd.iccprofile', + 'ico' => 'image/x-icon', + 'ics' => 'text/calendar', + 'ief' => 'image/ief', + 'ifb' => 'text/calendar', + 'ifm' => 'application/vnd.shana.informed.formdata', + 'iges' => 'model/iges', + 'igl' => 'application/vnd.igloader', + 'igm' => 'application/vnd.insors.igm', + 'igs' => 'model/iges', + 'igx' => 'application/vnd.micrografx.igx', + 'iif' => 'application/vnd.shana.informed.interchange', + 'img' => 'application/octet-stream', + 'imp' => 'application/vnd.accpac.simply.imp', + 'ims' => 'application/vnd.ms-ims', + 'in' => 'text/plain', + 'ini' => 'text/plain', + 'ink' => 'application/inkml+xml', + 'inkml' => 'application/inkml+xml', + 'install' => 'application/x-install-instructions', + 'iota' => 'application/vnd.astraea-software.iota', + 'ipfix' => 'application/ipfix', + 'ipk' => 'application/vnd.shana.informed.package', + 'irm' => 'application/vnd.ibm.rights-management', + 'irp' => 'application/vnd.irepository.package+xml', + 'iso' => 'application/x-iso9660-image', + 'itp' => 'application/vnd.shana.informed.formtemplate', + 'its' => 'application/its+xml', + 'ivp' => 'application/vnd.immervision-ivp', + 'ivu' => 'application/vnd.immervision-ivu', + 'jad' => 'text/vnd.sun.j2me.app-descriptor', + 'jade' => 'text/jade', + 'jam' => 'application/vnd.jam', + 'jar' => 'application/java-archive', + 'jardiff' => 'application/x-java-archive-diff', + 'java' => 'text/x-java-source', + 'jhc' => 'image/jphc', + 'jisp' => 'application/vnd.jisp', + 'jls' => 'image/jls', + 'jlt' => 'application/vnd.hp-jlyt', + 'jng' => 'image/x-jng', + 'jnlp' => 'application/x-java-jnlp-file', + 'joda' => 'application/vnd.joost.joda-archive', + 'jp2' => 'image/jp2', + 'jpe' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'jpf' => 'image/jpx', + 'jpg' => 'image/jpeg', + 'jpg2' => 'image/jp2', + 'jpgm' => 'video/jpm', + 'jpgv' => 'video/jpeg', + 'jph' => 'image/jph', + 'jpm' => 'video/jpm', + 'jpx' => 'image/jpx', + 'js' => 'application/javascript', + 'json' => 'application/json', + 'json5' => 'application/json5', + 'jsonld' => 'application/ld+json', + 'jsonml' => 'application/jsonml+json', + 'jsx' => 'text/jsx', + 'jxr' => 'image/jxr', + 'jxra' => 'image/jxra', + 'jxrs' => 'image/jxrs', + 'jxs' => 'image/jxs', + 'jxsc' => 'image/jxsc', + 'jxsi' => 'image/jxsi', + 'jxss' => 'image/jxss', + 'kar' => 'audio/midi', + 'karbon' => 'application/vnd.kde.karbon', + 'kdb' => 'application/octet-stream', + 'kdbx' => 'application/x-keepass2', + 'key' => 'application/x-iwork-keynote-sffkey', + 'kfo' => 'application/vnd.kde.kformula', + 'kia' => 'application/vnd.kidspiration', + 'kml' => 'application/vnd.google-earth.kml+xml', + 'kmz' => 'application/vnd.google-earth.kmz', + 'kne' => 'application/vnd.kinar', + 'knp' => 'application/vnd.kinar', + 'kon' => 'application/vnd.kde.kontour', + 'kpr' => 'application/vnd.kde.kpresenter', + 'kpt' => 'application/vnd.kde.kpresenter', + 'kpxx' => 'application/vnd.ds-keypoint', + 'ksp' => 'application/vnd.kde.kspread', + 'ktr' => 'application/vnd.kahootz', + 'ktx' => 'image/ktx', + 'ktx2' => 'image/ktx2', + 'ktz' => 'application/vnd.kahootz', + 'kwd' => 'application/vnd.kde.kword', + 'kwt' => 'application/vnd.kde.kword', + 'lasxml' => 'application/vnd.las.las+xml', + 'latex' => 'application/x-latex', + 'lbd' => 'application/vnd.llamagraphics.life-balance.desktop', + 'lbe' => 'application/vnd.llamagraphics.life-balance.exchange+xml', + 'les' => 'application/vnd.hhe.lesson-player', + 'less' => 'text/less', + 'lgr' => 'application/lgr+xml', + 'lha' => 'application/octet-stream', + 'link66' => 'application/vnd.route66.link66+xml', + 'list' => 'text/plain', + 'list3820' => 'application/vnd.ibm.modcap', + 'listafp' => 'application/vnd.ibm.modcap', + 'litcoffee' => 'text/coffeescript', + 'lnk' => 'application/x-ms-shortcut', + 'log' => 'text/plain', + 'lostxml' => 'application/lost+xml', + 'lrf' => 'application/octet-stream', + 'lrm' => 'application/vnd.ms-lrm', + 'ltf' => 'application/vnd.frogans.ltf', + 'lua' => 'text/x-lua', + 'luac' => 'application/x-lua-bytecode', + 'lvp' => 'audio/vnd.lucent.voice', + 'lwp' => 'application/vnd.lotus-wordpro', + 'lzh' => 'application/octet-stream', + 'm1v' => 'video/mpeg', + 'm2a' => 'audio/mpeg', + 'm2v' => 'video/mpeg', + 'm3a' => 'audio/mpeg', + 'm3u' => 'text/plain', + 'm3u8' => 'application/vnd.apple.mpegurl', + 'm4a' => 'audio/x-m4a', + 'm4p' => 'application/mp4', + 'm4s' => 'video/iso.segment', + 'm4u' => 'application/vnd.mpegurl', + 'm4v' => 'video/x-m4v', + 'm13' => 'application/x-msmediaview', + 'm14' => 'application/x-msmediaview', + 'm21' => 'application/mp21', + 'ma' => 'application/mathematica', + 'mads' => 'application/mads+xml', + 'maei' => 'application/mmt-aei+xml', + 'mag' => 'application/vnd.ecowin.chart', + 'maker' => 'application/vnd.framemaker', + 'man' => 'text/troff', + 'manifest' => 'text/cache-manifest', + 'map' => 'application/json', + 'mar' => 'application/octet-stream', + 'markdown' => 'text/markdown', + 'mathml' => 'application/mathml+xml', + 'mb' => 'application/mathematica', + 'mbk' => 'application/vnd.mobius.mbk', + 'mbox' => 'application/mbox', + 'mc1' => 'application/vnd.medcalcdata', + 'mcd' => 'application/vnd.mcd', + 'mcurl' => 'text/vnd.curl.mcurl', + 'md' => 'text/markdown', + 'mdb' => 'application/x-msaccess', + 'mdi' => 'image/vnd.ms-modi', + 'mdx' => 'text/mdx', + 'me' => 'text/troff', + 'mesh' => 'model/mesh', + 'meta4' => 'application/metalink4+xml', + 'metalink' => 'application/metalink+xml', + 'mets' => 'application/mets+xml', + 'mfm' => 'application/vnd.mfmp', + 'mft' => 'application/rpki-manifest', + 'mgp' => 'application/vnd.osgeo.mapguide.package', + 'mgz' => 'application/vnd.proteus.magazine', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mie' => 'application/x-mie', + 'mif' => 'application/vnd.mif', + 'mime' => 'message/rfc822', + 'mj2' => 'video/mj2', + 'mjp2' => 'video/mj2', + 'mjs' => 'application/javascript', + 'mk3d' => 'video/x-matroska', + 'mka' => 'audio/x-matroska', + 'mkd' => 'text/x-markdown', + 'mks' => 'video/x-matroska', + 'mkv' => 'video/x-matroska', + 'mlp' => 'application/vnd.dolby.mlp', + 'mmd' => 'application/vnd.chipnuts.karaoke-mmd', + 'mmf' => 'application/vnd.smaf', + 'mml' => 'text/mathml', + 'mmr' => 'image/vnd.fujixerox.edmics-mmr', + 'mng' => 'video/x-mng', + 'mny' => 'application/x-msmoney', + 'mobi' => 'application/x-mobipocket-ebook', + 'mods' => 'application/mods+xml', + 'mov' => 'video/quicktime', + 'movie' => 'video/x-sgi-movie', + 'mp2' => 'audio/mpeg', + 'mp2a' => 'audio/mpeg', + 'mp3' => 'audio/mpeg', + 'mp4' => 'video/mp4', + 'mp4a' => 'audio/mp4', + 'mp4s' => 'application/mp4', + 'mp4v' => 'video/mp4', + 'mp21' => 'application/mp21', + 'mpc' => 'application/vnd.mophun.certificate', + 'mpd' => 'application/dash+xml', + 'mpe' => 'video/mpeg', + 'mpeg' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mpg4' => 'video/mp4', + 'mpga' => 'audio/mpeg', + 'mpkg' => 'application/vnd.apple.installer+xml', + 'mpm' => 'application/vnd.blueice.multipass', + 'mpn' => 'application/vnd.mophun.application', + 'mpp' => 'application/vnd.ms-project', + 'mpt' => 'application/vnd.ms-project', + 'mpy' => 'application/vnd.ibm.minipay', + 'mqy' => 'application/vnd.mobius.mqy', + 'mrc' => 'application/marc', + 'mrcx' => 'application/marcxml+xml', + 'ms' => 'text/troff', + 'mscml' => 'application/mediaservercontrol+xml', + 'mseed' => 'application/vnd.fdsn.mseed', + 'mseq' => 'application/vnd.mseq', + 'msf' => 'application/vnd.epson.msf', + 'msg' => 'application/vnd.ms-outlook', + 'msh' => 'model/mesh', + 'msi' => 'application/x-msdownload', + 'msl' => 'application/vnd.mobius.msl', + 'msm' => 'application/octet-stream', + 'msp' => 'application/octet-stream', + 'msty' => 'application/vnd.muvee.style', + 'mtl' => 'model/mtl', + 'mts' => 'model/vnd.mts', + 'mus' => 'application/vnd.musician', + 'musd' => 'application/mmt-usd+xml', + 'musicxml' => 'application/vnd.recordare.musicxml+xml', + 'mvb' => 'application/x-msmediaview', + 'mvt' => 'application/vnd.mapbox-vector-tile', + 'mwf' => 'application/vnd.mfer', + 'mxf' => 'application/mxf', + 'mxl' => 'application/vnd.recordare.musicxml', + 'mxmf' => 'audio/mobile-xmf', + 'mxml' => 'application/xv+xml', + 'mxs' => 'application/vnd.triscape.mxs', + 'mxu' => 'video/vnd.mpegurl', + 'n-gage' => 'application/vnd.nokia.n-gage.symbian.install', + 'n3' => 'text/n3', + 'nb' => 'application/mathematica', + 'nbp' => 'application/vnd.wolfram.player', + 'nc' => 'application/x-netcdf', + 'ncx' => 'application/x-dtbncx+xml', + 'nfo' => 'text/x-nfo', + 'ngdat' => 'application/vnd.nokia.n-gage.data', + 'nitf' => 'application/vnd.nitf', + 'nlu' => 'application/vnd.neurolanguage.nlu', + 'nml' => 'application/vnd.enliven', + 'nnd' => 'application/vnd.noblenet-directory', + 'nns' => 'application/vnd.noblenet-sealer', + 'nnw' => 'application/vnd.noblenet-web', + 'npx' => 'image/vnd.net-fpx', + 'nq' => 'application/n-quads', + 'nsc' => 'application/x-conference', + 'nsf' => 'application/vnd.lotus-notes', + 'nt' => 'application/n-triples', + 'ntf' => 'application/vnd.nitf', + 'numbers' => 'application/x-iwork-numbers-sffnumbers', + 'nzb' => 'application/x-nzb', + 'oa2' => 'application/vnd.fujitsu.oasys2', + 'oa3' => 'application/vnd.fujitsu.oasys3', + 'oas' => 'application/vnd.fujitsu.oasys', + 'obd' => 'application/x-msbinder', + 'obgx' => 'application/vnd.openblox.game+xml', + 'obj' => 'model/obj', + 'oda' => 'application/oda', + 'odb' => 'application/vnd.oasis.opendocument.database', + 'odc' => 'application/vnd.oasis.opendocument.chart', + 'odf' => 'application/vnd.oasis.opendocument.formula', + 'odft' => 'application/vnd.oasis.opendocument.formula-template', + 'odg' => 'application/vnd.oasis.opendocument.graphics', + 'odi' => 'application/vnd.oasis.opendocument.image', + 'odm' => 'application/vnd.oasis.opendocument.text-master', + 'odp' => 'application/vnd.oasis.opendocument.presentation', + 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', + 'odt' => 'application/vnd.oasis.opendocument.text', + 'oga' => 'audio/ogg', + 'ogex' => 'model/vnd.opengex', + 'ogg' => 'audio/ogg', + 'ogv' => 'video/ogg', + 'ogx' => 'application/ogg', + 'omdoc' => 'application/omdoc+xml', + 'onepkg' => 'application/onenote', + 'onetmp' => 'application/onenote', + 'onetoc' => 'application/onenote', + 'onetoc2' => 'application/onenote', + 'opf' => 'application/oebps-package+xml', + 'opml' => 'text/x-opml', + 'oprc' => 'application/vnd.palm', + 'opus' => 'audio/ogg', + 'org' => 'text/x-org', + 'osf' => 'application/vnd.yamaha.openscoreformat', + 'osfpvg' => 'application/vnd.yamaha.openscoreformat.osfpvg+xml', + 'osm' => 'application/vnd.openstreetmap.data+xml', + 'otc' => 'application/vnd.oasis.opendocument.chart-template', + 'otf' => 'font/otf', + 'otg' => 'application/vnd.oasis.opendocument.graphics-template', + 'oth' => 'application/vnd.oasis.opendocument.text-web', + 'oti' => 'application/vnd.oasis.opendocument.image-template', + 'otp' => 'application/vnd.oasis.opendocument.presentation-template', + 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template', + 'ott' => 'application/vnd.oasis.opendocument.text-template', + 'ova' => 'application/x-virtualbox-ova', + 'ovf' => 'application/x-virtualbox-ovf', + 'owl' => 'application/rdf+xml', + 'oxps' => 'application/oxps', + 'oxt' => 'application/vnd.openofficeorg.extension', + 'p' => 'text/x-pascal', + 'p7a' => 'application/x-pkcs7-signature', + 'p7b' => 'application/x-pkcs7-certificates', + 'p7c' => 'application/pkcs7-mime', + 'p7m' => 'application/pkcs7-mime', + 'p7r' => 'application/x-pkcs7-certreqresp', + 'p7s' => 'application/pkcs7-signature', + 'p8' => 'application/pkcs8', + 'p10' => 'application/x-pkcs10', + 'p12' => 'application/x-pkcs12', + 'pac' => 'application/x-ns-proxy-autoconfig', + 'pages' => 'application/x-iwork-pages-sffpages', + 'pas' => 'text/x-pascal', + 'paw' => 'application/vnd.pawaafile', + 'pbd' => 'application/vnd.powerbuilder6', + 'pbm' => 'image/x-portable-bitmap', + 'pcap' => 'application/vnd.tcpdump.pcap', + 'pcf' => 'application/x-font-pcf', + 'pcl' => 'application/vnd.hp-pcl', + 'pclxl' => 'application/vnd.hp-pclxl', + 'pct' => 'image/x-pict', + 'pcurl' => 'application/vnd.curl.pcurl', + 'pcx' => 'image/x-pcx', + 'pdb' => 'application/x-pilot', + 'pde' => 'text/x-processing', + 'pdf' => 'application/pdf', + 'pem' => 'application/x-x509-user-cert', + 'pfa' => 'application/x-font-type1', + 'pfb' => 'application/x-font-type1', + 'pfm' => 'application/x-font-type1', + 'pfr' => 'application/font-tdpfr', + 'pfx' => 'application/x-pkcs12', + 'pgm' => 'image/x-portable-graymap', + 'pgn' => 'application/x-chess-pgn', + 'pgp' => 'application/pgp', + 'php' => 'application/x-httpd-php', + 'php3' => 'application/x-httpd-php', + 'php4' => 'application/x-httpd-php', + 'phps' => 'application/x-httpd-php-source', + 'phtml' => 'application/x-httpd-php', + 'pic' => 'image/x-pict', + 'pkg' => 'application/octet-stream', + 'pki' => 'application/pkixcmp', + 'pkipath' => 'application/pkix-pkipath', + 'pkpass' => 'application/vnd.apple.pkpass', + 'pl' => 'application/x-perl', + 'plb' => 'application/vnd.3gpp.pic-bw-large', + 'plc' => 'application/vnd.mobius.plc', + 'plf' => 'application/vnd.pocketlearn', + 'pls' => 'application/pls+xml', + 'pm' => 'application/x-perl', + 'pml' => 'application/vnd.ctc-posml', + 'png' => 'image/png', + 'pnm' => 'image/x-portable-anymap', + 'portpkg' => 'application/vnd.macports.portpkg', + 'pot' => 'application/vnd.ms-powerpoint', + 'potm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', + 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', + 'ppa' => 'application/vnd.ms-powerpoint', + 'ppam' => 'application/vnd.ms-powerpoint.addin.macroEnabled.12', + 'ppd' => 'application/vnd.cups-ppd', + 'ppm' => 'image/x-portable-pixmap', + 'pps' => 'application/vnd.ms-powerpoint', + 'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12', + 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', + 'ppt' => 'application/powerpoint', + 'pptm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'pqa' => 'application/vnd.palm', + 'prc' => 'application/x-pilot', + 'pre' => 'application/vnd.lotus-freelance', + 'prf' => 'application/pics-rules', + 'provx' => 'application/provenance+xml', + 'ps' => 'application/postscript', + 'psb' => 'application/vnd.3gpp.pic-bw-small', + 'psd' => 'application/x-photoshop', + 'psf' => 'application/x-font-linux-psf', + 'pskcxml' => 'application/pskc+xml', + 'pti' => 'image/prs.pti', + 'ptid' => 'application/vnd.pvi.ptid1', + 'pub' => 'application/x-mspublisher', + 'pvb' => 'application/vnd.3gpp.pic-bw-var', + 'pwn' => 'application/vnd.3m.post-it-notes', + 'pya' => 'audio/vnd.ms-playready.media.pya', + 'pyv' => 'video/vnd.ms-playready.media.pyv', + 'qam' => 'application/vnd.epson.quickanime', + 'qbo' => 'application/vnd.intu.qbo', + 'qfx' => 'application/vnd.intu.qfx', + 'qps' => 'application/vnd.publishare-delta-tree', + 'qt' => 'video/quicktime', + 'qwd' => 'application/vnd.quark.quarkxpress', + 'qwt' => 'application/vnd.quark.quarkxpress', + 'qxb' => 'application/vnd.quark.quarkxpress', + 'qxd' => 'application/vnd.quark.quarkxpress', + 'qxl' => 'application/vnd.quark.quarkxpress', + 'qxt' => 'application/vnd.quark.quarkxpress', + 'ra' => 'audio/x-realaudio', + 'ram' => 'audio/x-pn-realaudio', + 'raml' => 'application/raml+yaml', + 'rapd' => 'application/route-apd+xml', + 'rar' => 'application/x-rar', + 'ras' => 'image/x-cmu-raster', + 'rcprofile' => 'application/vnd.ipunplugged.rcprofile', + 'rdf' => 'application/rdf+xml', + 'rdz' => 'application/vnd.data-vision.rdz', + 'relo' => 'application/p2p-overlay+xml', + 'rep' => 'application/vnd.businessobjects', + 'res' => 'application/x-dtbresource+xml', + 'rgb' => 'image/x-rgb', + 'rif' => 'application/reginfo+xml', + 'rip' => 'audio/vnd.rip', + 'ris' => 'application/x-research-info-systems', + 'rl' => 'application/resource-lists+xml', + 'rlc' => 'image/vnd.fujixerox.edmics-rlc', + 'rld' => 'application/resource-lists-diff+xml', + 'rm' => 'audio/x-pn-realaudio', + 'rmi' => 'audio/midi', + 'rmp' => 'audio/x-pn-realaudio-plugin', + 'rms' => 'application/vnd.jcp.javame.midlet-rms', + 'rmvb' => 'application/vnd.rn-realmedia-vbr', + 'rnc' => 'application/relax-ng-compact-syntax', + 'rng' => 'application/xml', + 'roa' => 'application/rpki-roa', + 'roff' => 'text/troff', + 'rp9' => 'application/vnd.cloanto.rp9', + 'rpm' => 'audio/x-pn-realaudio-plugin', + 'rpss' => 'application/vnd.nokia.radio-presets', + 'rpst' => 'application/vnd.nokia.radio-preset', + 'rq' => 'application/sparql-query', + 'rs' => 'application/rls-services+xml', + 'rsa' => 'application/x-pkcs7', + 'rsat' => 'application/atsc-rsat+xml', + 'rsd' => 'application/rsd+xml', + 'rsheet' => 'application/urc-ressheet+xml', + 'rss' => 'application/rss+xml', + 'rtf' => 'text/rtf', + 'rtx' => 'text/richtext', + 'run' => 'application/x-makeself', + 'rusd' => 'application/route-usd+xml', + 'rv' => 'video/vnd.rn-realvideo', + 's' => 'text/x-asm', + 's3m' => 'audio/s3m', + 'saf' => 'application/vnd.yamaha.smaf-audio', + 'sass' => 'text/x-sass', + 'sbml' => 'application/sbml+xml', + 'sc' => 'application/vnd.ibm.secure-container', + 'scd' => 'application/x-msschedule', + 'scm' => 'application/vnd.lotus-screencam', + 'scq' => 'application/scvp-cv-request', + 'scs' => 'application/scvp-cv-response', + 'scss' => 'text/x-scss', + 'scurl' => 'text/vnd.curl.scurl', + 'sda' => 'application/vnd.stardivision.draw', + 'sdc' => 'application/vnd.stardivision.calc', + 'sdd' => 'application/vnd.stardivision.impress', + 'sdkd' => 'application/vnd.solent.sdkm+xml', + 'sdkm' => 'application/vnd.solent.sdkm+xml', + 'sdp' => 'application/sdp', + 'sdw' => 'application/vnd.stardivision.writer', + 'sea' => 'application/octet-stream', + 'see' => 'application/vnd.seemail', + 'seed' => 'application/vnd.fdsn.seed', + 'sema' => 'application/vnd.sema', + 'semd' => 'application/vnd.semd', + 'semf' => 'application/vnd.semf', + 'senmlx' => 'application/senml+xml', + 'sensmlx' => 'application/sensml+xml', + 'ser' => 'application/java-serialized-object', + 'setpay' => 'application/set-payment-initiation', + 'setreg' => 'application/set-registration-initiation', + 'sfd-hdstx' => 'application/vnd.hydrostatix.sof-data', + 'sfs' => 'application/vnd.spotfire.sfs', + 'sfv' => 'text/x-sfv', + 'sgi' => 'image/sgi', + 'sgl' => 'application/vnd.stardivision.writer-global', + 'sgm' => 'text/sgml', + 'sgml' => 'text/sgml', + 'sh' => 'application/x-sh', + 'shar' => 'application/x-shar', + 'shex' => 'text/shex', + 'shf' => 'application/shf+xml', + 'shtml' => 'text/html', + 'sid' => 'image/x-mrsid-image', + 'sieve' => 'application/sieve', + 'sig' => 'application/pgp-signature', + 'sil' => 'audio/silk', + 'silo' => 'model/mesh', + 'sis' => 'application/vnd.symbian.install', + 'sisx' => 'application/vnd.symbian.install', + 'sit' => 'application/x-stuffit', + 'sitx' => 'application/x-stuffitx', + 'siv' => 'application/sieve', + 'skd' => 'application/vnd.koan', + 'skm' => 'application/vnd.koan', + 'skp' => 'application/vnd.koan', + 'skt' => 'application/vnd.koan', + 'sldm' => 'application/vnd.ms-powerpoint.slide.macroenabled.12', + 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide', + 'slim' => 'text/slim', + 'slm' => 'text/slim', + 'sls' => 'application/route-s-tsid+xml', + 'slt' => 'application/vnd.epson.salt', + 'sm' => 'application/vnd.stepmania.stepchart', + 'smf' => 'application/vnd.stardivision.math', + 'smi' => 'application/smil', + 'smil' => 'application/smil', + 'smv' => 'video/x-smv', + 'smzip' => 'application/vnd.stepmania.package', + 'snd' => 'audio/basic', + 'snf' => 'application/x-font-snf', + 'so' => 'application/octet-stream', + 'spc' => 'application/x-pkcs7-certificates', + 'spdx' => 'text/spdx', + 'spf' => 'application/vnd.yamaha.smaf-phrase', + 'spl' => 'application/x-futuresplash', + 'spot' => 'text/vnd.in3d.spot', + 'spp' => 'application/scvp-vp-response', + 'spq' => 'application/scvp-vp-request', + 'spx' => 'audio/ogg', + 'sql' => 'application/x-sql', + 'src' => 'application/x-wais-source', + 'srt' => 'application/x-subrip', + 'sru' => 'application/sru+xml', + 'srx' => 'application/sparql-results+xml', + 'ssdl' => 'application/ssdl+xml', + 'sse' => 'application/vnd.kodak-descriptor', + 'ssf' => 'application/vnd.epson.ssf', + 'ssml' => 'application/ssml+xml', + 'sst' => 'application/octet-stream', + 'st' => 'application/vnd.sailingtracker.track', + 'stc' => 'application/vnd.sun.xml.calc.template', + 'std' => 'application/vnd.sun.xml.draw.template', + 'stf' => 'application/vnd.wt.stf', + 'sti' => 'application/vnd.sun.xml.impress.template', + 'stk' => 'application/hyperstudio', + 'stl' => 'model/stl', + 'stpx' => 'model/step+xml', + 'stpxz' => 'model/step-xml+zip', + 'stpz' => 'model/step+zip', + 'str' => 'application/vnd.pg.format', + 'stw' => 'application/vnd.sun.xml.writer.template', + 'styl' => 'text/stylus', + 'stylus' => 'text/stylus', + 'sub' => 'text/vnd.dvb.subtitle', + 'sus' => 'application/vnd.sus-calendar', + 'susp' => 'application/vnd.sus-calendar', + 'sv4cpio' => 'application/x-sv4cpio', + 'sv4crc' => 'application/x-sv4crc', + 'svc' => 'application/vnd.dvb.service', + 'svd' => 'application/vnd.svd', + 'svg' => 'image/svg+xml', + 'svgz' => 'image/svg+xml', + 'swa' => 'application/x-director', + 'swf' => 'application/x-shockwave-flash', + 'swi' => 'application/vnd.aristanetworks.swi', + 'swidtag' => 'application/swid+xml', + 'sxc' => 'application/vnd.sun.xml.calc', + 'sxd' => 'application/vnd.sun.xml.draw', + 'sxg' => 'application/vnd.sun.xml.writer.global', + 'sxi' => 'application/vnd.sun.xml.impress', + 'sxm' => 'application/vnd.sun.xml.math', + 'sxw' => 'application/vnd.sun.xml.writer', + 't' => 'text/troff', + 't3' => 'application/x-t3vm-image', + 't38' => 'image/t38', + 'taglet' => 'application/vnd.mynfc', + 'tao' => 'application/vnd.tao.intent-module-archive', + 'tap' => 'image/vnd.tencent.tap', + 'tar' => 'application/x-tar', + 'tcap' => 'application/vnd.3gpp2.tcap', + 'tcl' => 'application/x-tcl', + 'td' => 'application/urc-targetdesc+xml', + 'teacher' => 'application/vnd.smart.teacher', + 'tei' => 'application/tei+xml', + 'teicorpus' => 'application/tei+xml', + 'tex' => 'application/x-tex', + 'texi' => 'application/x-texinfo', + 'texinfo' => 'application/x-texinfo', + 'text' => 'text/plain', + 'tfi' => 'application/thraud+xml', + 'tfm' => 'application/x-tex-tfm', + 'tfx' => 'image/tiff-fx', + 'tga' => 'image/x-tga', + 'tgz' => 'application/x-tar', + 'thmx' => 'application/vnd.ms-officetheme', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'tk' => 'application/x-tcl', + 'tmo' => 'application/vnd.tmobile-livetv', + 'toml' => 'application/toml', + 'torrent' => 'application/x-bittorrent', + 'tpl' => 'application/vnd.groove-tool-template', + 'tpt' => 'application/vnd.trid.tpt', + 'tr' => 'text/troff', + 'tra' => 'application/vnd.trueapp', + 'trig' => 'application/trig', + 'trm' => 'application/x-msterminal', + 'ts' => 'video/mp2t', + 'tsd' => 'application/timestamped-data', + 'tsv' => 'text/tab-separated-values', + 'ttc' => 'font/collection', + 'ttf' => 'font/ttf', + 'ttl' => 'text/turtle', + 'ttml' => 'application/ttml+xml', + 'twd' => 'application/vnd.simtech-mindmapper', + 'twds' => 'application/vnd.simtech-mindmapper', + 'txd' => 'application/vnd.genomatix.tuxedo', + 'txf' => 'application/vnd.mobius.txf', + 'txt' => 'text/plain', + 'u8dsn' => 'message/global-delivery-status', + 'u8hdr' => 'message/global-headers', + 'u8mdn' => 'message/global-disposition-notification', + 'u8msg' => 'message/global', + 'u32' => 'application/x-authorware-bin', + 'ubj' => 'application/ubjson', + 'udeb' => 'application/x-debian-package', + 'ufd' => 'application/vnd.ufdl', + 'ufdl' => 'application/vnd.ufdl', + 'ulx' => 'application/x-glulx', + 'umj' => 'application/vnd.umajin', + 'unityweb' => 'application/vnd.unity', + 'uoml' => 'application/vnd.uoml+xml', + 'uri' => 'text/uri-list', + 'uris' => 'text/uri-list', + 'urls' => 'text/uri-list', + 'usdz' => 'model/vnd.usdz+zip', + 'ustar' => 'application/x-ustar', + 'utz' => 'application/vnd.uiq.theme', + 'uu' => 'text/x-uuencode', + 'uva' => 'audio/vnd.dece.audio', + 'uvd' => 'application/vnd.dece.data', + 'uvf' => 'application/vnd.dece.data', + 'uvg' => 'image/vnd.dece.graphic', + 'uvh' => 'video/vnd.dece.hd', + 'uvi' => 'image/vnd.dece.graphic', + 'uvm' => 'video/vnd.dece.mobile', + 'uvp' => 'video/vnd.dece.pd', + 'uvs' => 'video/vnd.dece.sd', + 'uvt' => 'application/vnd.dece.ttml+xml', + 'uvu' => 'video/vnd.uvvu.mp4', + 'uvv' => 'video/vnd.dece.video', + 'uvva' => 'audio/vnd.dece.audio', + 'uvvd' => 'application/vnd.dece.data', + 'uvvf' => 'application/vnd.dece.data', + 'uvvg' => 'image/vnd.dece.graphic', + 'uvvh' => 'video/vnd.dece.hd', + 'uvvi' => 'image/vnd.dece.graphic', + 'uvvm' => 'video/vnd.dece.mobile', + 'uvvp' => 'video/vnd.dece.pd', + 'uvvs' => 'video/vnd.dece.sd', + 'uvvt' => 'application/vnd.dece.ttml+xml', + 'uvvu' => 'video/vnd.uvvu.mp4', + 'uvvv' => 'video/vnd.dece.video', + 'uvvx' => 'application/vnd.dece.unspecified', + 'uvvz' => 'application/vnd.dece.zip', + 'uvx' => 'application/vnd.dece.unspecified', + 'uvz' => 'application/vnd.dece.zip', + 'vbox' => 'application/x-virtualbox-vbox', + 'vbox-extpack' => 'application/x-virtualbox-vbox-extpack', + 'vcard' => 'text/vcard', + 'vcd' => 'application/x-cdlink', + 'vcf' => 'text/x-vcard', + 'vcg' => 'application/vnd.groove-vcard', + 'vcs' => 'text/x-vcalendar', + 'vcx' => 'application/vnd.vcx', + 'vdi' => 'application/x-virtualbox-vdi', + 'vds' => 'model/vnd.sap.vds', + 'vhd' => 'application/x-virtualbox-vhd', + 'vis' => 'application/vnd.visionary', + 'viv' => 'video/vnd.vivo', + 'vlc' => 'application/videolan', + 'vmdk' => 'application/x-virtualbox-vmdk', + 'vob' => 'video/x-ms-vob', + 'vor' => 'application/vnd.stardivision.writer', + 'vox' => 'application/x-authorware-bin', + 'vrml' => 'model/vrml', + 'vsd' => 'application/vnd.visio', + 'vsf' => 'application/vnd.vsf', + 'vss' => 'application/vnd.visio', + 'vst' => 'application/vnd.visio', + 'vsw' => 'application/vnd.visio', + 'vtf' => 'image/vnd.valve.source.texture', + 'vtt' => 'text/vtt', + 'vtu' => 'model/vnd.vtu', + 'vxml' => 'application/voicexml+xml', + 'w3d' => 'application/x-director', + 'wad' => 'application/x-doom', + 'wadl' => 'application/vnd.sun.wadl+xml', + 'war' => 'application/java-archive', + 'wasm' => 'application/wasm', + 'wav' => 'audio/x-wav', + 'wax' => 'audio/x-ms-wax', + 'wbmp' => 'image/vnd.wap.wbmp', + 'wbs' => 'application/vnd.criticaltools.wbs+xml', + 'wbxml' => 'application/wbxml', + 'wcm' => 'application/vnd.ms-works', + 'wdb' => 'application/vnd.ms-works', + 'wdp' => 'image/vnd.ms-photo', + 'weba' => 'audio/webm', + 'webapp' => 'application/x-web-app-manifest+json', + 'webm' => 'video/webm', + 'webmanifest' => 'application/manifest+json', + 'webp' => 'image/webp', + 'wg' => 'application/vnd.pmi.widget', + 'wgt' => 'application/widget', + 'wks' => 'application/vnd.ms-works', + 'wm' => 'video/x-ms-wm', + 'wma' => 'audio/x-ms-wma', + 'wmd' => 'application/x-ms-wmd', + 'wmf' => 'image/wmf', + 'wml' => 'text/vnd.wap.wml', + 'wmlc' => 'application/wmlc', + 'wmls' => 'text/vnd.wap.wmlscript', + 'wmlsc' => 'application/vnd.wap.wmlscriptc', + 'wmv' => 'video/x-ms-wmv', + 'wmx' => 'video/x-ms-wmx', + 'wmz' => 'application/x-msmetafile', + 'woff' => 'font/woff', + 'woff2' => 'font/woff2', + 'word' => 'application/msword', + 'wpd' => 'application/vnd.wordperfect', + 'wpl' => 'application/vnd.ms-wpl', + 'wps' => 'application/vnd.ms-works', + 'wqd' => 'application/vnd.wqd', + 'wri' => 'application/x-mswrite', + 'wrl' => 'model/vrml', + 'wsc' => 'message/vnd.wfa.wsc', + 'wsdl' => 'application/wsdl+xml', + 'wspolicy' => 'application/wspolicy+xml', + 'wtb' => 'application/vnd.webturbo', + 'wvx' => 'video/x-ms-wvx', + 'x3d' => 'model/x3d+xml', + 'x3db' => 'model/x3d+fastinfoset', + 'x3dbz' => 'model/x3d+binary', + 'x3dv' => 'model/x3d-vrml', + 'x3dvz' => 'model/x3d+vrml', + 'x3dz' => 'model/x3d+xml', + 'x32' => 'application/x-authorware-bin', + 'x_b' => 'model/vnd.parasolid.transmit.binary', + 'x_t' => 'model/vnd.parasolid.transmit.text', + 'xaml' => 'application/xaml+xml', + 'xap' => 'application/x-silverlight-app', + 'xar' => 'application/vnd.xara', + 'xav' => 'application/xcap-att+xml', + 'xbap' => 'application/x-ms-xbap', + 'xbd' => 'application/vnd.fujixerox.docuworks.binder', + 'xbm' => 'image/x-xbitmap', + 'xca' => 'application/xcap-caps+xml', + 'xcs' => 'application/calendar+xml', + 'xdf' => 'application/xcap-diff+xml', + 'xdm' => 'application/vnd.syncml.dm+xml', + 'xdp' => 'application/vnd.adobe.xdp+xml', + 'xdssc' => 'application/dssc+xml', + 'xdw' => 'application/vnd.fujixerox.docuworks', + 'xel' => 'application/xcap-el+xml', + 'xenc' => 'application/xenc+xml', + 'xer' => 'application/patch-ops-error+xml', + 'xfdf' => 'application/vnd.adobe.xfdf', + 'xfdl' => 'application/vnd.xfdl', + 'xht' => 'application/xhtml+xml', + 'xhtml' => 'application/xhtml+xml', + 'xhvml' => 'application/xv+xml', + 'xif' => 'image/vnd.xiff', + 'xl' => 'application/excel', + 'xla' => 'application/vnd.ms-excel', + 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12', + 'xlc' => 'application/vnd.ms-excel', + 'xlf' => 'application/xliff+xml', + 'xlm' => 'application/vnd.ms-excel', + 'xls' => 'application/vnd.ms-excel', + 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', + 'xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xlt' => 'application/vnd.ms-excel', + 'xltm' => 'application/vnd.ms-excel.template.macroEnabled.12', + 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', + 'xlw' => 'application/vnd.ms-excel', + 'xm' => 'audio/xm', + 'xml' => 'application/xml', + 'xns' => 'application/xcap-ns+xml', + 'xo' => 'application/vnd.olpc-sugar', + 'xop' => 'application/xop+xml', + 'xpi' => 'application/x-xpinstall', + 'xpl' => 'application/xproc+xml', + 'xpm' => 'image/x-xpixmap', + 'xpr' => 'application/vnd.is-xpr', + 'xps' => 'application/vnd.ms-xpsdocument', + 'xpw' => 'application/vnd.intercon.formnet', + 'xpx' => 'application/vnd.intercon.formnet', + 'xsd' => 'application/xml', + 'xsl' => 'application/xml', + 'xslt' => 'application/xslt+xml', + 'xsm' => 'application/vnd.syncml+xml', + 'xspf' => 'application/xspf+xml', + 'xul' => 'application/vnd.mozilla.xul+xml', + 'xvm' => 'application/xv+xml', + 'xvml' => 'application/xv+xml', + 'xwd' => 'image/x-xwindowdump', + 'xyz' => 'chemical/x-xyz', + 'xz' => 'application/x-xz', + 'yaml' => 'text/yaml', + 'yang' => 'application/yang', + 'yin' => 'application/yin+xml', + 'yml' => 'text/yaml', + 'ymp' => 'text/x-suse-ymp', + 'z' => 'application/x-compress', + 'z1' => 'application/x-zmachine', + 'z2' => 'application/x-zmachine', + 'z3' => 'application/x-zmachine', + 'z4' => 'application/x-zmachine', + 'z5' => 'application/x-zmachine', + 'z6' => 'application/x-zmachine', + 'z7' => 'application/x-zmachine', + 'z8' => 'application/x-zmachine', + 'zaz' => 'application/vnd.zzazz.deck+xml', + 'zip' => 'application/zip', + 'zir' => 'application/vnd.zul', + 'zirz' => 'application/vnd.zul', + 'zmm' => 'application/vnd.handheld-entertainment+xml', + 'zsh' => 'text/x-scriptzsh', + ]; + + public function lookupMimeType(string $extension): ?string + { + return self::MIME_TYPES_FOR_EXTENSIONS[$extension] ?? null; + } +} diff --git a/vendor/league/mime-type-detection/src/MimeTypeDetector.php b/vendor/league/mime-type-detection/src/MimeTypeDetector.php new file mode 100644 index 0000000..5d799d2 --- /dev/null +++ b/vendor/league/mime-type-detection/src/MimeTypeDetector.php @@ -0,0 +1,19 @@ + $overrides + */ + public function __construct(ExtensionToMimeTypeMap $innerMap, array $overrides) + { + $this->innerMap = $innerMap; + $this->overrides = $overrides; + } + + public function lookupMimeType(string $extension): ?string + { + return $this->overrides[$extension] ?? $this->innerMap->lookupMimeType($extension); + } +} diff --git a/vendor/maennchen/zipstream-php/.github/FUNDING.yml b/vendor/maennchen/zipstream-php/.github/FUNDING.yml new file mode 100644 index 0000000..d807a3f --- /dev/null +++ b/vendor/maennchen/zipstream-php/.github/FUNDING.yml @@ -0,0 +1 @@ +open_collective: zipstream diff --git a/vendor/maennchen/zipstream-php/.github/ISSUE_TEMPLATE.md b/vendor/maennchen/zipstream-php/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..3c2e66d --- /dev/null +++ b/vendor/maennchen/zipstream-php/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,12 @@ +# Description of the problem + +Please be very descriptive and include as much details as possible. + +# Example code + +# Informations + +* ZipStream-PHP version: +* PHP version: + +Please include any supplemental information you deem relevant to this issue. diff --git a/vendor/maennchen/zipstream-php/.gitignore b/vendor/maennchen/zipstream-php/.gitignore new file mode 100644 index 0000000..5210738 --- /dev/null +++ b/vendor/maennchen/zipstream-php/.gitignore @@ -0,0 +1,6 @@ +clover.xml +composer.lock +coverage.clover +.idea +phpunit.xml +vendor diff --git a/vendor/maennchen/zipstream-php/.travis.yml b/vendor/maennchen/zipstream-php/.travis.yml new file mode 100644 index 0000000..12c8a64 --- /dev/null +++ b/vendor/maennchen/zipstream-php/.travis.yml @@ -0,0 +1,12 @@ +language: php +dist: trusty +sudo: false +php: + - 7.1 + - 7.2 + - 7.3 +install: composer install +script: ./vendor/bin/phpunit --coverage-clover=coverage.clover +after_script: + - wget https://scrutinizer-ci.com/ocular.phar + - php ocular.phar code-coverage:upload --format=php-clover coverage.clover diff --git a/vendor/maennchen/zipstream-php/CHANGELOG.md b/vendor/maennchen/zipstream-php/CHANGELOG.md new file mode 100644 index 0000000..b1979ab --- /dev/null +++ b/vendor/maennchen/zipstream-php/CHANGELOG.md @@ -0,0 +1,51 @@ +# CHANGELOG for ZipStream-PHP + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + +## [2.1.0] - 2020-06-01 +### Changed +- Don't execute ob_flush() when output buffering is not enabled (#152) +- Fix inconsistent return type on 32-bit systems (#149) Fix #144 +- Use mbstring polyfill (#151) +- Promote 7zip usage over unzip to avoid UTF-8 issues (#147) + +## [2.0.0] - 2020-02-22 +### Breaking change +- Only the self opened streams will be closed (#139) +If you were relying on ZipStream to close streams that the library didn't open, +you'll need to close them yourself now. + +### Changed +- Minor change to data descriptor (#136) + +## [1.2.0] - 2019-07-11 + +### Added +- Option to flush output buffer after every write (#122) + +## [1.1.0] - 2019-04-30 + +### Fixed +- Honor last-modified timestamps set via `ZipStream\Option\File::setTime()` (#106) +- Documentation regarding output of HTTP headers +- Test warnings with PHPUnit (#109) + +### Added +- Test for FileNotReadableException (#114) +- Size attribute to File options (#113) +- Tests on PHP 7.3 (#108) + +## [1.0.0] - 2019-04-17 + +### Breaking changes +- Mininum PHP version is now 7.1 +- Options are now passed to the ZipStream object via the Option\Archive object. See the wiki for available options and code examples + +### Added +- Add large file support with Zip64 headers + +### Changed +- Major refactoring and code cleanup diff --git a/vendor/maennchen/zipstream-php/CONTRIBUTING.md b/vendor/maennchen/zipstream-php/CONTRIBUTING.md new file mode 100644 index 0000000..f8ef5a5 --- /dev/null +++ b/vendor/maennchen/zipstream-php/CONTRIBUTING.md @@ -0,0 +1,25 @@ +# ZipStream Readme for Contributors +## Code styling +### Indention +For spaces are used to indent code. The convention is [K&R](http://en.wikipedia.org/wiki/Indent_style#K&R) + +### Comments +Double Slashes are used for an one line comment. + +Classes, Variables, Methods etc: + +```php +/** + * My comment + * + * @myanotation like @param etc. + */ +``` + +## Pull requests +Feel free to submit pull requests. + +## Testing +For every new feature please write a new PHPUnit test. + +Before every commit execute `./vendor/bin/phpunit` to check if your changes wrecked something: diff --git a/vendor/maennchen/zipstream-php/LICENSE b/vendor/maennchen/zipstream-php/LICENSE new file mode 100644 index 0000000..ebe7fe2 --- /dev/null +++ b/vendor/maennchen/zipstream-php/LICENSE @@ -0,0 +1,24 @@ +MIT License + +Copyright (C) 2007-2009 Paul Duncan +Copyright (C) 2014 Jonatan Männchen +Copyright (C) 2014 Jesse G. Donat +Copyright (C) 2018 Nicolas CARPi + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/maennchen/zipstream-php/README.md b/vendor/maennchen/zipstream-php/README.md new file mode 100644 index 0000000..c2e832b --- /dev/null +++ b/vendor/maennchen/zipstream-php/README.md @@ -0,0 +1,123 @@ +# ZipStream-PHP + +[![Build Status](https://travis-ci.org/maennchen/ZipStream-PHP.svg?branch=master)](https://travis-ci.org/maennchen/ZipStream-PHP) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/maennchen/ZipStream-PHP/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/maennchen/ZipStream-PHP/) +[![Code Coverage](https://scrutinizer-ci.com/g/maennchen/ZipStream-PHP/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/maennchen/ZipStream-PHP/) +[![Latest Stable Version](https://poser.pugx.org/maennchen/zipstream-php/v/stable)](https://packagist.org/packages/maennchen/zipstream-php) +[![Total Downloads](https://poser.pugx.org/maennchen/zipstream-php/downloads)](https://packagist.org/packages/maennchen/zipstream-php) +[![Financial Contributors on Open Collective](https://opencollective.com/zipstream/all/badge.svg?label=financial+contributors)](https://opencollective.com/zipstream) [![License](https://img.shields.io/github/license/maennchen/zipstream-php.svg)](LICENSE) + +## Overview + +A fast and simple streaming zip file downloader for PHP. Using this library will save you from having to write the Zip to disk. You can directly send it to the user, which is much faster. It can work with S3 buckets or any PSR7 Stream. + +Please see the [LICENSE](LICENSE) file for licensing and warranty information. + +## Installation + +Simply add a dependency on maennchen/zipstream-php to your project's composer.json file if you use Composer to manage the dependencies of your project. Use following command to add the package to your project's dependencies: + +```bash +composer require maennchen/zipstream-php +``` + +## Usage and options + +Here's a simple example: + +```php +// Autoload the dependencies +require 'vendor/autoload.php'; + +// enable output of HTTP headers +$options = new ZipStream\Option\Archive(); +$options->setSendHttpHeaders(true); + +// create a new zipstream object +$zip = new ZipStream\ZipStream('example.zip', $options); + +// create a file named 'hello.txt' +$zip->addFile('hello.txt', 'This is the contents of hello.txt'); + +// add a file named 'some_image.jpg' from a local file 'path/to/image.jpg' +$zip->addFileFromPath('some_image.jpg', 'path/to/image.jpg'); + +// add a file named 'goodbye.txt' from an open stream resource +$fp = tmpfile(); +fwrite($fp, 'The quick brown fox jumped over the lazy dog.'); +rewind($fp); +$zip->addFileFromStream('goodbye.txt', $fp); +fclose($fp); + +// finish the zip stream +$zip->finish(); +``` + +You can also add comments, modify file timestamps, and customize (or +disable) the HTTP headers. It is also possible to specify the storage method when adding files, +the current default storage method is 'deflate' i.e files are stored with Compression mode 0x08. + +See the [Wiki](https://github.com/maennchen/ZipStream-PHP/wiki) for details. + +## Known issue + +The native Mac OS archive extraction tool might not open archives in some conditions. A workaround is to disable the Zip64 feature with the option `$opt->setEnableZip64(false)`. This limits the archive to 4 Gb and 64k files but will allow Mac OS users to open them without issue. See #116. + +The linux `unzip` utility might not handle properly unicode characters. It is recommended to extract with another tool like [7-zip](https://www.7-zip.org/). See #146. + +## Upgrade to version 2.0.0 + +* Only the self opened streams will be closed (#139) +If you were relying on ZipStream to close streams that the library didn't open, +you'll need to close them yourself now. + +## Upgrade to version 1.0.0 + +* All options parameters to all function have been moved from an `array` to structured option objects. See [the wiki](https://github.com/maennchen/ZipStream-PHP/wiki/Available-options) for examples. +* The whole library has been refactored. The minimal PHP requirement has been raised to PHP 7.1. + +## Usage with Symfony and S3 + +You can find example code on [the wiki](https://github.com/maennchen/ZipStream-PHP/wiki/Symfony-example). + +## Contributing + +ZipStream-PHP is a collaborative project. Please take a look at the [CONTRIBUTING.md](CONTRIBUTING.md) file. + +## About the Authors + +* Paul Duncan - https://pablotron.org/ +* Jonatan Männchen - https://maennchen.dev +* Jesse G. Donat - https://donatstudios.com +* Nicolas CARPi - https://www.deltablot.com +* Nik Barham - https://www.brokencube.co.uk + +## Contributors + +### Code Contributors + +This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)]. + + +### Financial Contributors + +Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/zipstream/contribute)] + +#### Individuals + + + +#### Organizations + +Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/zipstream/contribute)] + + + + + + + + + + + diff --git a/vendor/maennchen/zipstream-php/composer.json b/vendor/maennchen/zipstream-php/composer.json new file mode 100644 index 0000000..103c78c --- /dev/null +++ b/vendor/maennchen/zipstream-php/composer.json @@ -0,0 +1,41 @@ +{ + "name": "maennchen/zipstream-php", + "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.", + "keywords": ["zip", "stream"], + "type": "library", + "license": "MIT", + "authors": [{ + "name": "Paul Duncan", + "email": "pabs@pablotron.org" + }, + { + "name": "Jonatan Männchen", + "email": "jonatan@maennchen.ch" + }, + { + "name": "Jesse Donat", + "email": "donatj@gmail.com" + }, + { + "name": "András Kolesár", + "email": "kolesar@kolesar.hu" + } + ], + "require": { + "php": ">= 7.1", + "symfony/polyfill-mbstring": "^1.0", + "psr/http-message": "^1.0", + "myclabs/php-enum": "^1.5" + }, + "require-dev": { + "phpunit/phpunit": ">= 7.5", + "guzzlehttp/guzzle": ">= 6.3", + "ext-zip": "*", + "mikey179/vfsstream": "^1.6" + }, + "autoload": { + "psr-4": { + "ZipStream\\": "src/" + } + } +} diff --git a/vendor/maennchen/zipstream-php/phpunit.xml.dist b/vendor/maennchen/zipstream-php/phpunit.xml.dist new file mode 100644 index 0000000..f6e7227 --- /dev/null +++ b/vendor/maennchen/zipstream-php/phpunit.xml.dist @@ -0,0 +1,17 @@ + + + + test + + + + + + + + + + src + + + diff --git a/vendor/maennchen/zipstream-php/psalm.xml b/vendor/maennchen/zipstream-php/psalm.xml new file mode 100644 index 0000000..42f355b --- /dev/null +++ b/vendor/maennchen/zipstream-php/psalm.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/maennchen/zipstream-php/src/Bigint.php b/vendor/maennchen/zipstream-php/src/Bigint.php new file mode 100644 index 0000000..52ccfd2 --- /dev/null +++ b/vendor/maennchen/zipstream-php/src/Bigint.php @@ -0,0 +1,172 @@ +fillBytes($value, 0, 8); + } + + /** + * Fill the bytes field with int + * + * @param int $value + * @param int $start + * @param int $count + * @return void + */ + protected function fillBytes(int $value, int $start, int $count): void + { + for ($i = 0; $i < $count; $i++) { + $this->bytes[$start + $i] = $i >= PHP_INT_SIZE ? 0 : $value & 0xFF; + $value >>= 8; + } + } + + /** + * Get an instance + * + * @param int $value + * @return Bigint + */ + public static function init(int $value = 0): self + { + return new self($value); + } + + /** + * Fill bytes from low to high + * + * @param int $low + * @param int $high + * @return Bigint + */ + public static function fromLowHigh(int $low, int $high): self + { + $bigint = new Bigint(); + $bigint->fillBytes($low, 0, 4); + $bigint->fillBytes($high, 4, 4); + return $bigint; + } + + /** + * Get high 32 + * + * @return int + */ + public function getHigh32(): int + { + return $this->getValue(4, 4); + } + + /** + * Get value from bytes array + * + * @param int $end + * @param int $length + * @return int + */ + public function getValue(int $end = 0, int $length = 8): int + { + $result = 0; + for ($i = $end + $length - 1; $i >= $end; $i--) { + $result <<= 8; + $result |= $this->bytes[$i]; + } + return $result; + } + + /** + * Get low FF + * + * @param bool $force + * @return float + */ + public function getLowFF(bool $force = false): float + { + if ($force || $this->isOver32()) { + return (float)0xFFFFFFFF; + } + return (float)$this->getLow32(); + } + + /** + * Check if is over 32 + * + * @param bool $force + * @return bool + */ + public function isOver32(bool $force = false): bool + { + // value 0xFFFFFFFF already needs a Zip64 header + return $force || + max(array_slice($this->bytes, 4, 4)) > 0 || + min(array_slice($this->bytes, 0, 4)) === 0xFF; + } + + /** + * Get low 32 + * + * @return int + */ + public function getLow32(): int + { + return $this->getValue(0, 4); + } + + /** + * Get hexadecimal + * + * @return string + */ + public function getHex64(): string + { + $result = '0x'; + for ($i = 7; $i >= 0; $i--) { + $result .= sprintf('%02X', $this->bytes[$i]); + } + return $result; + } + + /** + * Add + * + * @param Bigint $other + * @return Bigint + */ + public function add(Bigint $other): Bigint + { + $result = clone $this; + $overflow = false; + for ($i = 0; $i < 8; $i++) { + $result->bytes[$i] += $other->bytes[$i]; + if ($overflow) { + $result->bytes[$i]++; + $overflow = false; + } + if ($result->bytes[$i] & 0x100) { + $overflow = true; + $result->bytes[$i] &= 0xFF; + } + } + if ($overflow) { + throw new OverflowException; + } + return $result; + } +} diff --git a/vendor/maennchen/zipstream-php/src/DeflateStream.php b/vendor/maennchen/zipstream-php/src/DeflateStream.php new file mode 100644 index 0000000..d6c2728 --- /dev/null +++ b/vendor/maennchen/zipstream-php/src/DeflateStream.php @@ -0,0 +1,70 @@ +filter) { + $this->removeDeflateFilter(); + $this->seek(0); + $this->addDeflateFilter($this->options); + } else { + rewind($this->stream); + } + } + + /** + * Remove the deflate filter + * + * @return void + */ + public function removeDeflateFilter(): void + { + if (!$this->filter) { + return; + } + stream_filter_remove($this->filter); + $this->filter = null; + } + + /** + * Add a deflate filter + * + * @param Option\File $options + * @return void + */ + public function addDeflateFilter(Option\File $options): void + { + $this->options = $options; + // parameter 4 for stream_filter_append expects array + // so we convert the option object in an array + $optionsArr = [ + 'comment' => $options->getComment(), + 'method' => $options->getMethod(), + 'deflateLevel' => $options->getDeflateLevel(), + 'time' => $options->getTime() + ]; + $this->filter = stream_filter_append( + $this->stream, + 'zlib.deflate', + STREAM_FILTER_READ, + $optionsArr + ); + } +} diff --git a/vendor/maennchen/zipstream-php/src/Exception.php b/vendor/maennchen/zipstream-php/src/Exception.php new file mode 100644 index 0000000..18ccfbb --- /dev/null +++ b/vendor/maennchen/zipstream-php/src/Exception.php @@ -0,0 +1,11 @@ +zip = $zip; + + $this->name = $name; + $this->opt = $opt ?: new FileOptions(); + $this->method = $this->opt->getMethod(); + $this->version = Version::STORE(); + $this->ofs = new Bigint(); + } + + public function processPath(string $path): void + { + if (!is_readable($path)) { + if (!file_exists($path)) { + throw new FileNotFoundException($path); + } + throw new FileNotReadableException($path); + } + if ($this->zip->isLargeFile($path) === false) { + $data = file_get_contents($path); + $this->processData($data); + } else { + $this->method = $this->zip->opt->getLargeFileMethod(); + + $stream = new DeflateStream(fopen($path, 'rb')); + $this->processStream($stream); + $stream->close(); + } + } + + public function processData(string $data): void + { + $this->len = new Bigint(strlen($data)); + $this->crc = crc32($data); + + // compress data if needed + if ($this->method->equals(Method::DEFLATE())) { + $data = gzdeflate($data); + } + + $this->zlen = new Bigint(strlen($data)); + $this->addFileHeader(); + $this->zip->send($data); + $this->addFileFooter(); + } + + /** + * Create and send zip header for this file. + * + * @return void + * @throws \ZipStream\Exception\EncodingException + */ + public function addFileHeader(): void + { + $name = static::filterFilename($this->name); + + // calculate name length + $nameLength = strlen($name); + + // create dos timestamp + $time = static::dosTime($this->opt->getTime()->getTimestamp()); + + $comment = $this->opt->getComment(); + + if (!mb_check_encoding($name, 'ASCII') || + !mb_check_encoding($comment, 'ASCII')) { + // Sets Bit 11: Language encoding flag (EFS). If this bit is set, + // the filename and comment fields for this file + // MUST be encoded using UTF-8. (see APPENDIX D) + if (!mb_check_encoding($name, 'UTF-8') || + !mb_check_encoding($comment, 'UTF-8')) { + throw new EncodingException( + 'File name and comment should use UTF-8 ' . + 'if one of them does not fit into ASCII range.' + ); + } + $this->bits |= self::BIT_EFS_UTF8; + } + + if ($this->method->equals(Method::DEFLATE())) { + $this->version = Version::DEFLATE(); + } + + $force = (boolean)($this->bits & self::BIT_ZERO_HEADER) && + $this->zip->opt->isEnableZip64(); + + $footer = $this->buildZip64ExtraBlock($force); + + // If this file will start over 4GB limit in ZIP file, + // CDR record will have to use Zip64 extension to describe offset + // to keep consistency we use the same value here + if ($this->zip->ofs->isOver32()) { + $this->version = Version::ZIP64(); + } + + $fields = [ + ['V', ZipStream::FILE_HEADER_SIGNATURE], + ['v', $this->version->getValue()], // Version needed to Extract + ['v', $this->bits], // General purpose bit flags - data descriptor flag set + ['v', $this->method->getValue()], // Compression method + ['V', $time], // Timestamp (DOS Format) + ['V', $this->crc], // CRC32 of data (0 -> moved to data descriptor footer) + ['V', $this->zlen->getLowFF($force)], // Length of compressed data (forced to 0xFFFFFFFF for zero header) + ['V', $this->len->getLowFF($force)], // Length of original data (forced to 0xFFFFFFFF for zero header) + ['v', $nameLength], // Length of filename + ['v', strlen($footer)], // Extra data (see above) + ]; + + // pack fields and calculate "total" length + $header = ZipStream::packFields($fields); + + // print header and filename + $data = $header . $name . $footer; + $this->zip->send($data); + + // save header length + $this->hlen = Bigint::init(strlen($data)); + } + + /** + * Strip characters that are not legal in Windows filenames + * to prevent compatibility issues + * + * @param string $filename Unprocessed filename + * @return string + */ + public static function filterFilename(string $filename): string + { + // strip leading slashes from file name + // (fixes bug in windows archive viewer) + $filename = preg_replace('/^\\/+/', '', $filename); + + return str_replace(['\\', ':', '*', '?', '"', '<', '>', '|'], '_', $filename); + } + + /** + * Convert a UNIX timestamp to a DOS timestamp. + * + * @param int $when + * @return int DOS Timestamp + */ + final protected static function dosTime(int $when): int + { + // get date array for timestamp + $d = getdate($when); + + // set lower-bound on dates + if ($d['year'] < 1980) { + $d = array( + 'year' => 1980, + 'mon' => 1, + 'mday' => 1, + 'hours' => 0, + 'minutes' => 0, + 'seconds' => 0 + ); + } + + // remove extra years from 1980 + $d['year'] -= 1980; + + // return date string + return + ($d['year'] << 25) | + ($d['mon'] << 21) | + ($d['mday'] << 16) | + ($d['hours'] << 11) | + ($d['minutes'] << 5) | + ($d['seconds'] >> 1); + } + + protected function buildZip64ExtraBlock(bool $force = false): string + { + + $fields = []; + if ($this->len->isOver32($force)) { + $fields[] = ['P', $this->len]; // Length of original data + } + + if ($this->len->isOver32($force)) { + $fields[] = ['P', $this->zlen]; // Length of compressed data + } + + if ($this->ofs->isOver32()) { + $fields[] = ['P', $this->ofs]; // Offset of local header record + } + + if (!empty($fields)) { + if (!$this->zip->opt->isEnableZip64()) { + throw new OverflowException(); + } + + array_unshift( + $fields, + ['v', 0x0001], // 64 bit extension + ['v', count($fields) * 8] // Length of data block + ); + $this->version = Version::ZIP64(); + } + + return ZipStream::packFields($fields); + } + + /** + * Create and send data descriptor footer for this file. + * + * @return void + */ + + public function addFileFooter(): void + { + + if ($this->bits & self::BIT_ZERO_HEADER) { + // compressed and uncompressed size + $sizeFormat = 'V'; + if ($this->zip->opt->isEnableZip64()) { + $sizeFormat = 'P'; + } + $fields = [ + ['V', ZipStream::DATA_DESCRIPTOR_SIGNATURE], + ['V', $this->crc], // CRC32 + [$sizeFormat, $this->zlen], // Length of compressed data + [$sizeFormat, $this->len], // Length of original data + ]; + + $footer = ZipStream::packFields($fields); + $this->zip->send($footer); + } else { + $footer = ''; + } + $this->totalLength = $this->hlen->add($this->zlen)->add(Bigint::init(strlen($footer))); + $this->zip->addToCdr($this); + } + + public function processStream(StreamInterface $stream): void + { + $this->zlen = new Bigint(); + $this->len = new Bigint(); + + if ($this->zip->opt->isZeroHeader()) { + $this->processStreamWithZeroHeader($stream); + } else { + $this->processStreamWithComputedHeader($stream); + } + } + + protected function processStreamWithZeroHeader(StreamInterface $stream): void + { + $this->bits |= self::BIT_ZERO_HEADER; + $this->addFileHeader(); + $this->readStream($stream, self::COMPUTE | self::SEND); + $this->addFileFooter(); + } + + protected function readStream(StreamInterface $stream, ?int $options = null): void + { + $this->deflateInit(); + $total = 0; + $size = $this->opt->getSize(); + while (!$stream->eof() && ($size === 0 || $total < $size)) { + $data = $stream->read(self::CHUNKED_READ_BLOCK_SIZE); + $total += strlen($data); + if ($size > 0 && $total > $size) { + $data = substr($data, 0 , strlen($data)-($total - $size)); + } + $this->deflateData($stream, $data, $options); + if ($options & self::SEND) { + $this->zip->send($data); + } + } + $this->deflateFinish($options); + } + + protected function deflateInit(): void + { + $this->hash = hash_init(self::HASH_ALGORITHM); + if ($this->method->equals(Method::DEFLATE())) { + $this->deflate = deflate_init( + ZLIB_ENCODING_RAW, + ['level' => $this->opt->getDeflateLevel()] + ); + } + } + + protected function deflateData(StreamInterface $stream, string &$data, ?int $options = null): void + { + if ($options & self::COMPUTE) { + $this->len = $this->len->add(Bigint::init(strlen($data))); + hash_update($this->hash, $data); + } + if ($this->deflate) { + $data = deflate_add( + $this->deflate, + $data, + $stream->eof() + ? ZLIB_FINISH + : ZLIB_NO_FLUSH + ); + } + if ($options & self::COMPUTE) { + $this->zlen = $this->zlen->add(Bigint::init(strlen($data))); + } + } + + protected function deflateFinish(?int $options = null): void + { + if ($options & self::COMPUTE) { + $this->crc = hexdec(hash_final($this->hash)); + } + } + + protected function processStreamWithComputedHeader(StreamInterface $stream): void + { + $this->readStream($stream, self::COMPUTE); + $stream->rewind(); + + // incremental compression with deflate_add + // makes this second read unnecessary + // but it is only available from PHP 7.0 + if (!$this->deflate && $stream instanceof DeflateStream && $this->method->equals(Method::DEFLATE())) { + $stream->addDeflateFilter($this->opt); + $this->zlen = new Bigint(); + while (!$stream->eof()) { + $data = $stream->read(self::CHUNKED_READ_BLOCK_SIZE); + $this->zlen = $this->zlen->add(Bigint::init(strlen($data))); + } + $stream->rewind(); + } + + $this->addFileHeader(); + $this->readStream($stream, self::SEND); + $this->addFileFooter(); + } + + /** + * Send CDR record for specified file. + * + * @return string + */ + public function getCdrFile(): string + { + $name = static::filterFilename($this->name); + + // get attributes + $comment = $this->opt->getComment(); + + // get dos timestamp + $time = static::dosTime($this->opt->getTime()->getTimestamp()); + + $footer = $this->buildZip64ExtraBlock(); + + $fields = [ + ['V', ZipStream::CDR_FILE_SIGNATURE], // Central file header signature + ['v', ZipStream::ZIP_VERSION_MADE_BY], // Made by version + ['v', $this->version->getValue()], // Extract by version + ['v', $this->bits], // General purpose bit flags - data descriptor flag set + ['v', $this->method->getValue()], // Compression method + ['V', $time], // Timestamp (DOS Format) + ['V', $this->crc], // CRC32 + ['V', $this->zlen->getLowFF()], // Compressed Data Length + ['V', $this->len->getLowFF()], // Original Data Length + ['v', strlen($name)], // Length of filename + ['v', strlen($footer)], // Extra data len (see above) + ['v', strlen($comment)], // Length of comment + ['v', 0], // Disk number + ['v', 0], // Internal File Attributes + ['V', 32], // External File Attributes + ['V', $this->ofs->getLowFF()] // Relative offset of local header + ]; + + // pack fields, then append name and comment + $header = ZipStream::packFields($fields); + + return $header . $name . $footer . $comment; + } + + /** + * @return Bigint + */ + public function getTotalLength(): Bigint + { + return $this->totalLength; + } +} diff --git a/vendor/maennchen/zipstream-php/src/Option/Archive.php b/vendor/maennchen/zipstream-php/src/Option/Archive.php new file mode 100644 index 0000000..b6b95cc --- /dev/null +++ b/vendor/maennchen/zipstream-php/src/Option/Archive.php @@ -0,0 +1,261 @@ + 4 GB or file count > 64k) + * + * @var bool + */ + private $enableZip64 = true; + /** + * Enable streaming files with single read where + * general purpose bit 3 indicates local file header + * contain zero values in crc and size fields, + * these appear only after file contents + * in data descriptor block. + * + * @var bool + */ + private $zeroHeader = false; + /** + * Enable reading file stat for determining file size. + * When a 32-bit system reads file size that is + * over 2 GB, invalid value appears in file size + * due to integer overflow. Should be disabled on + * 32-bit systems with method addFileFromPath + * if any file may exceed 2 GB. In this case file + * will be read in blocks and correct size will be + * determined from content. + * + * @var bool + */ + private $statFiles = true; + /** + * Enable flush after every write to output stream. + * @var bool + */ + private $flushOutput = false; + /** + * HTTP Content-Disposition. Defaults to + * 'attachment', where + * FILENAME is the specified filename. + * + * Note that this does nothing if you are + * not sending HTTP headers. + * + * @var string + */ + private $contentDisposition = 'attachment'; + /** + * Note that this does nothing if you are + * not sending HTTP headers. + * + * @var string + */ + private $contentType = 'application/x-zip'; + /** + * @var int + */ + private $deflateLevel = 6; + + /** + * @var resource + */ + private $outputStream; + + /** + * Options constructor. + */ + public function __construct() + { + $this->largeFileMethod = Method::STORE(); + $this->outputStream = fopen('php://output', 'wb'); + } + + public function getComment(): string + { + return $this->comment; + } + + public function setComment(string $comment): void + { + $this->comment = $comment; + } + + public function getLargeFileSize(): int + { + return $this->largeFileSize; + } + + public function setLargeFileSize(int $largeFileSize): void + { + $this->largeFileSize = $largeFileSize; + } + + public function getLargeFileMethod(): Method + { + return $this->largeFileMethod; + } + + public function setLargeFileMethod(Method $largeFileMethod): void + { + $this->largeFileMethod = $largeFileMethod; + } + + public function isSendHttpHeaders(): bool + { + return $this->sendHttpHeaders; + } + + public function setSendHttpHeaders(bool $sendHttpHeaders): void + { + $this->sendHttpHeaders = $sendHttpHeaders; + } + + public function getHttpHeaderCallback(): Callable + { + return $this->httpHeaderCallback; + } + + public function setHttpHeaderCallback(Callable $httpHeaderCallback): void + { + $this->httpHeaderCallback = $httpHeaderCallback; + } + + public function isEnableZip64(): bool + { + return $this->enableZip64; + } + + public function setEnableZip64(bool $enableZip64): void + { + $this->enableZip64 = $enableZip64; + } + + public function isZeroHeader(): bool + { + return $this->zeroHeader; + } + + public function setZeroHeader(bool $zeroHeader): void + { + $this->zeroHeader = $zeroHeader; + } + + public function isFlushOutput(): bool + { + return $this->flushOutput; + } + + public function setFlushOutput(bool $flushOutput): void + { + $this->flushOutput = $flushOutput; + } + + public function isStatFiles(): bool + { + return $this->statFiles; + } + + public function setStatFiles(bool $statFiles): void + { + $this->statFiles = $statFiles; + } + + public function getContentDisposition(): string + { + return $this->contentDisposition; + } + + public function setContentDisposition(string $contentDisposition): void + { + $this->contentDisposition = $contentDisposition; + } + + public function getContentType(): string + { + return $this->contentType; + } + + public function setContentType(string $contentType): void + { + $this->contentType = $contentType; + } + + /** + * @return resource + */ + public function getOutputStream() + { + return $this->outputStream; + } + + /** + * @param resource $outputStream + */ + public function setOutputStream($outputStream): void + { + $this->outputStream = $outputStream; + } + + /** + * @return int + */ + public function getDeflateLevel(): int + { + return $this->deflateLevel; + } + + /** + * @param int $deflateLevel + */ + public function setDeflateLevel(int $deflateLevel): void + { + $this->deflateLevel = $deflateLevel; + } +} diff --git a/vendor/maennchen/zipstream-php/src/Option/File.php b/vendor/maennchen/zipstream-php/src/Option/File.php new file mode 100644 index 0000000..7fd29ea --- /dev/null +++ b/vendor/maennchen/zipstream-php/src/Option/File.php @@ -0,0 +1,116 @@ +deflateLevel = $this->deflateLevel ?: $archiveOptions->getDeflateLevel(); + $this->time = $this->time ?: new DateTime(); + } + + /** + * @return string + */ + public function getComment(): string + { + return $this->comment; + } + + /** + * @param string $comment + */ + public function setComment(string $comment): void + { + $this->comment = $comment; + } + + /** + * @return Method + */ + public function getMethod(): Method + { + return $this->method ?: Method::DEFLATE(); + } + + /** + * @param Method $method + */ + public function setMethod(Method $method): void + { + $this->method = $method; + } + + /** + * @return int + */ + public function getDeflateLevel(): int + { + return $this->deflateLevel ?: Archive::DEFAULT_DEFLATE_LEVEL; + } + + /** + * @param int $deflateLevel + */ + public function setDeflateLevel(int $deflateLevel): void + { + $this->deflateLevel = $deflateLevel; + } + + /** + * @return DateTime + */ + public function getTime(): DateTime + { + return $this->time; + } + + /** + * @param DateTime $time + */ + public function setTime(DateTime $time): void + { + $this->time = $time; + } + + /** + * @return int + */ + public function getSize(): int + { + return $this->size; + } + + /** + * @param int $size + */ + public function setSize(int $size): void + { + $this->size = $size; + } +} diff --git a/vendor/maennchen/zipstream-php/src/Option/Method.php b/vendor/maennchen/zipstream-php/src/Option/Method.php new file mode 100644 index 0000000..bbec84c --- /dev/null +++ b/vendor/maennchen/zipstream-php/src/Option/Method.php @@ -0,0 +1,19 @@ +stream = $stream; + } + + /** + * Closes the stream and any underlying resources. + * + * @return void + */ + public function close(): void + { + if (is_resource($this->stream)) { + fclose($this->stream); + } + $this->detach(); + } + + /** + * Separates any underlying resources from the stream. + * + * After the stream has been detached, the stream is in an unusable state. + * + * @return resource|null Underlying PHP stream, if any + */ + public function detach() + { + $result = $this->stream; + $this->stream = null; + return $result; + } + + /** + * Reads all data from the stream into a string, from the beginning to end. + * + * This method MUST attempt to seek to the beginning of the stream before + * reading data and read the stream until the end is reached. + * + * Warning: This could attempt to load a large amount of data into memory. + * + * This method MUST NOT raise an exception in order to conform with PHP's + * string casting operations. + * + * @see http://php.net/manual/en/language.oop5.magic.php#object.tostring + * @return string + */ + public function __toString(): string + { + try { + $this->seek(0); + } catch (\RuntimeException $e) {} + return (string) stream_get_contents($this->stream); + } + + /** + * Seek to a position in the stream. + * + * @link http://www.php.net/manual/en/function.fseek.php + * @param int $offset Stream offset + * @param int $whence Specifies how the cursor position will be calculated + * based on the seek offset. Valid values are identical to the built-in + * PHP $whence values for `fseek()`. SEEK_SET: Set position equal to + * offset bytes SEEK_CUR: Set position to current location plus offset + * SEEK_END: Set position to end-of-stream plus offset. + * @throws \RuntimeException on failure. + */ + public function seek($offset, $whence = SEEK_SET): void + { + if (!$this->isSeekable()) { + throw new RuntimeException; + } + if (fseek($this->stream, $offset, $whence) !== 0) { + throw new RuntimeException; + } + } + + /** + * Returns whether or not the stream is seekable. + * + * @return bool + */ + public function isSeekable(): bool + { + return (bool)$this->getMetadata('seekable'); + } + + /** + * Get stream metadata as an associative array or retrieve a specific key. + * + * The keys returned are identical to the keys returned from PHP's + * stream_get_meta_data() function. + * + * @link http://php.net/manual/en/function.stream-get-meta-data.php + * @param string $key Specific metadata to retrieve. + * @return array|mixed|null Returns an associative array if no key is + * provided. Returns a specific key value if a key is provided and the + * value is found, or null if the key is not found. + */ + public function getMetadata($key = null) + { + $metadata = stream_get_meta_data($this->stream); + return $key !== null ? @$metadata[$key] : $metadata; + } + + /** + * Get the size of the stream if known. + * + * @return int|null Returns the size in bytes if known, or null if unknown. + */ + public function getSize(): ?int + { + $stats = fstat($this->stream); + return $stats['size']; + } + + /** + * Returns the current position of the file read/write pointer + * + * @return int Position of the file pointer + * @throws \RuntimeException on error. + */ + public function tell(): int + { + $position = ftell($this->stream); + if ($position === false) { + throw new RuntimeException; + } + return $position; + } + + /** + * Returns true if the stream is at the end of the stream. + * + * @return bool + */ + public function eof(): bool + { + return feof($this->stream); + } + + /** + * Seek to the beginning of the stream. + * + * If the stream is not seekable, this method will raise an exception; + * otherwise, it will perform a seek(0). + * + * @see seek() + * @link http://www.php.net/manual/en/function.fseek.php + * @throws \RuntimeException on failure. + */ + public function rewind(): void + { + $this->seek(0); + } + + /** + * Write data to the stream. + * + * @param string $string The string that is to be written. + * @return int Returns the number of bytes written to the stream. + * @throws \RuntimeException on failure. + */ + public function write($string): int + { + if (!$this->isWritable()) { + throw new RuntimeException; + } + if (fwrite($this->stream, $string) === false) { + throw new RuntimeException; + } + return \mb_strlen($string); + } + + /** + * Returns whether or not the stream is writable. + * + * @return bool + */ + public function isWritable(): bool + { + return preg_match('/[waxc+]/', $this->getMetadata('mode')) === 1; + } + + /** + * Read data from the stream. + * + * @param int $length Read up to $length bytes from the object and return + * them. Fewer than $length bytes may be returned if underlying stream + * call returns fewer bytes. + * @return string Returns the data read from the stream, or an empty string + * if no bytes are available. + * @throws \RuntimeException if an error occurs. + */ + public function read($length): string + { + if (!$this->isReadable()) { + throw new RuntimeException; + } + $result = fread($this->stream, $length); + if ($result === false) { + throw new RuntimeException; + } + return $result; + } + + /** + * Returns whether or not the stream is readable. + * + * @return bool + */ + public function isReadable(): bool + { + return preg_match('/[r+]/', $this->getMetadata('mode')) === 1; + } + + /** + * Returns the remaining contents in a string + * + * @return string + * @throws \RuntimeException if unable to read or an error occurs while + * reading. + */ + public function getContents(): string + { + if (!$this->isReadable()) { + throw new RuntimeException; + } + $result = stream_get_contents($this->stream); + if ($result === false) { + throw new RuntimeException; + } + return $result; + } +} diff --git a/vendor/maennchen/zipstream-php/src/ZipStream.php b/vendor/maennchen/zipstream-php/src/ZipStream.php new file mode 100644 index 0000000..e83038c --- /dev/null +++ b/vendor/maennchen/zipstream-php/src/ZipStream.php @@ -0,0 +1,599 @@ +addFile('some_file.gif', $data); + * + * * add second file + * $data = file_get_contents('some_file.gif'); + * $zip->addFile('another_file.png', $data); + * + * 3. Finish the zip stream: + * + * $zip->finish(); + * + * You can also add an archive comment, add comments to individual files, + * and adjust the timestamp of files. See the API documentation for each + * method below for additional information. + * + * Example: + * + * // create a new zip stream object + * $zip = new ZipStream('some_files.zip'); + * + * // list of local files + * $files = array('foo.txt', 'bar.jpg'); + * + * // read and add each file to the archive + * foreach ($files as $path) + * $zip->addFile($path, file_get_contents($path)); + * + * // write archive footer to stream + * $zip->finish(); + */ +class ZipStream +{ + /** + * This number corresponds to the ZIP version/OS used (2 bytes) + * From: https://www.iana.org/assignments/media-types/application/zip + * The upper byte (leftmost one) indicates the host system (OS) for the + * file. Software can use this information to determine + * the line record format for text files etc. The current + * mappings are: + * + * 0 - MS-DOS and OS/2 (F.A.T. file systems) + * 1 - Amiga 2 - VAX/VMS + * 3 - *nix 4 - VM/CMS + * 5 - Atari ST 6 - OS/2 H.P.F.S. + * 7 - Macintosh 8 - Z-System + * 9 - CP/M 10 thru 255 - unused + * + * The lower byte (rightmost one) indicates the version number of the + * software used to encode the file. The value/10 + * indicates the major version number, and the value + * mod 10 is the minor version number. + * Here we are using 6 for the OS, indicating OS/2 H.P.F.S. + * to prevent file permissions issues upon extract (see #84) + * 0x603 is 00000110 00000011 in binary, so 6 and 3 + */ + const ZIP_VERSION_MADE_BY = 0x603; + + /** + * The following signatures end with 0x4b50, which in ASCII is PK, + * the initials of the inventor Phil Katz. + * See https://en.wikipedia.org/wiki/Zip_(file_format)#File_headers + */ + const FILE_HEADER_SIGNATURE = 0x04034b50; + const CDR_FILE_SIGNATURE = 0x02014b50; + const CDR_EOF_SIGNATURE = 0x06054b50; + const DATA_DESCRIPTOR_SIGNATURE = 0x08074b50; + const ZIP64_CDR_EOF_SIGNATURE = 0x06064b50; + const ZIP64_CDR_LOCATOR_SIGNATURE = 0x07064b50; + + /** + * Global Options + * + * @var ArchiveOptions + */ + public $opt; + + /** + * @var array + */ + public $files = []; + + /** + * @var Bigint + */ + public $cdr_ofs; + + /** + * @var Bigint + */ + public $ofs; + + /** + * @var bool + */ + protected $need_headers; + + /** + * @var null|String + */ + protected $output_name; + + /** + * Create a new ZipStream object. + * + * Parameters: + * + * @param String $name - Name of output file (optional). + * @param ArchiveOptions $opt - Archive Options + * + * Large File Support: + * + * By default, the method addFileFromPath() will send send files + * larger than 20 megabytes along raw rather than attempting to + * compress them. You can change both the maximum size and the + * compression behavior using the largeFile* options above, with the + * following caveats: + * + * * For "small" files (e.g. files smaller than largeFileSize), the + * memory use can be up to twice that of the actual file. In other + * words, adding a 10 megabyte file to the archive could potentially + * occupy 20 megabytes of memory. + * + * * Enabling compression on large files (e.g. files larger than + * large_file_size) is extremely slow, because ZipStream has to pass + * over the large file once to calculate header information, and then + * again to compress and send the actual data. + * + * Examples: + * + * // create a new zip file named 'foo.zip' + * $zip = new ZipStream('foo.zip'); + * + * // create a new zip file named 'bar.zip' with a comment + * $opt->setComment = 'this is a comment for the zip file.'; + * $zip = new ZipStream('bar.zip', $opt); + * + * Notes: + * + * In order to let this library send HTTP headers, a filename must be given + * _and_ the option `sendHttpHeaders` must be `true`. This behavior is to + * allow software to send its own headers (including the filename), and + * still use this library. + */ + public function __construct(?string $name = null, ?ArchiveOptions $opt = null) + { + $this->opt = $opt ?: new ArchiveOptions(); + + $this->output_name = $name; + $this->need_headers = $name && $this->opt->isSendHttpHeaders(); + + $this->cdr_ofs = new Bigint(); + $this->ofs = new Bigint(); + } + + /** + * addFile + * + * Add a file to the archive. + * + * @param String $name - path of file in archive (including directory). + * @param String $data - contents of file + * @param FileOptions $options + * + * File Options: + * time - Last-modified timestamp (seconds since the epoch) of + * this file. Defaults to the current time. + * comment - Comment related to this file. + * method - Storage method for file ("store" or "deflate") + * + * Examples: + * + * // add a file named 'foo.txt' + * $data = file_get_contents('foo.txt'); + * $zip->addFile('foo.txt', $data); + * + * // add a file named 'bar.jpg' with a comment and a last-modified + * // time of two hours ago + * $data = file_get_contents('bar.jpg'); + * $opt->setTime = time() - 2 * 3600; + * $opt->setComment = 'this is a comment about bar.jpg'; + * $zip->addFile('bar.jpg', $data, $opt); + */ + public function addFile(string $name, string $data, ?FileOptions $options = null): void + { + $options = $options ?: new FileOptions(); + $options->defaultTo($this->opt); + + $file = new File($this, $name, $options); + $file->processData($data); + } + + /** + * addFileFromPath + * + * Add a file at path to the archive. + * + * Note that large files may be compressed differently than smaller + * files; see the "Large File Support" section above for more + * information. + * + * @param String $name - name of file in archive (including directory path). + * @param String $path - path to file on disk (note: paths should be encoded using + * UNIX-style forward slashes -- e.g '/path/to/some/file'). + * @param FileOptions $options + * + * File Options: + * time - Last-modified timestamp (seconds since the epoch) of + * this file. Defaults to the current time. + * comment - Comment related to this file. + * method - Storage method for file ("store" or "deflate") + * + * Examples: + * + * // add a file named 'foo.txt' from the local file '/tmp/foo.txt' + * $zip->addFileFromPath('foo.txt', '/tmp/foo.txt'); + * + * // add a file named 'bigfile.rar' from the local file + * // '/usr/share/bigfile.rar' with a comment and a last-modified + * // time of two hours ago + * $path = '/usr/share/bigfile.rar'; + * $opt->setTime = time() - 2 * 3600; + * $opt->setComment = 'this is a comment about bar.jpg'; + * $zip->addFileFromPath('bigfile.rar', $path, $opt); + * + * @return void + * @throws \ZipStream\Exception\FileNotFoundException + * @throws \ZipStream\Exception\FileNotReadableException + */ + public function addFileFromPath(string $name, string $path, ?FileOptions $options = null): void + { + $options = $options ?: new FileOptions(); + $options->defaultTo($this->opt); + + $file = new File($this, $name, $options); + $file->processPath($path); + } + + /** + * addFileFromStream + * + * Add an open stream to the archive. + * + * @param String $name - path of file in archive (including directory). + * @param resource $stream - contents of file as a stream resource + * @param FileOptions $options + * + * File Options: + * time - Last-modified timestamp (seconds since the epoch) of + * this file. Defaults to the current time. + * comment - Comment related to this file. + * + * Examples: + * + * // create a temporary file stream and write text to it + * $fp = tmpfile(); + * fwrite($fp, 'The quick brown fox jumped over the lazy dog.'); + * + * // add a file named 'streamfile.txt' from the content of the stream + * $x->addFileFromStream('streamfile.txt', $fp); + * + * @return void + */ + public function addFileFromStream(string $name, $stream, ?FileOptions $options = null): void + { + $options = $options ?: new FileOptions(); + $options->defaultTo($this->opt); + + $file = new File($this, $name, $options); + $file->processStream(new DeflateStream($stream)); + } + + /** + * addFileFromPsr7Stream + * + * Add an open stream to the archive. + * + * @param String $name - path of file in archive (including directory). + * @param StreamInterface $stream - contents of file as a stream resource + * @param FileOptions $options + * + * File Options: + * time - Last-modified timestamp (seconds since the epoch) of + * this file. Defaults to the current time. + * comment - Comment related to this file. + * + * Examples: + * + * // create a temporary file stream and write text to it + * $fp = tmpfile(); + * fwrite($fp, 'The quick brown fox jumped over the lazy dog.'); + * + * // add a file named 'streamfile.txt' from the content of the stream + * $x->addFileFromPsr7Stream('streamfile.txt', $fp); + * + * @return void + */ + public function addFileFromPsr7Stream( + string $name, + StreamInterface $stream, + ?FileOptions $options = null + ): void { + $options = $options ?: new FileOptions(); + $options->defaultTo($this->opt); + + $file = new File($this, $name, $options); + $file->processStream($stream); + } + + /** + * finish + * + * Write zip footer to stream. + * + * Example: + * + * // add a list of files to the archive + * $files = array('foo.txt', 'bar.jpg'); + * foreach ($files as $path) + * $zip->addFile($path, file_get_contents($path)); + * + * // write footer to stream + * $zip->finish(); + * @return void + * + * @throws OverflowException + */ + public function finish(): void + { + // add trailing cdr file records + foreach ($this->files as $cdrFile) { + $this->send($cdrFile); + $this->cdr_ofs = $this->cdr_ofs->add(Bigint::init(strlen($cdrFile))); + } + + // Add 64bit headers (if applicable) + if (count($this->files) >= 0xFFFF || + $this->cdr_ofs->isOver32() || + $this->ofs->isOver32()) { + if (!$this->opt->isEnableZip64()) { + throw new OverflowException(); + } + + $this->addCdr64Eof(); + $this->addCdr64Locator(); + } + + // add trailing cdr eof record + $this->addCdrEof(); + + // The End + $this->clear(); + } + + /** + * Send ZIP64 CDR EOF (Central Directory Record End-of-File) record. + * + * @return void + */ + protected function addCdr64Eof(): void + { + $num_files = count($this->files); + $cdr_length = $this->cdr_ofs; + $cdr_offset = $this->ofs; + + $fields = [ + ['V', static::ZIP64_CDR_EOF_SIGNATURE], // ZIP64 end of central file header signature + ['P', 44], // Length of data below this header (length of block - 12) = 44 + ['v', static::ZIP_VERSION_MADE_BY], // Made by version + ['v', Version::ZIP64], // Extract by version + ['V', 0x00], // disk number + ['V', 0x00], // no of disks + ['P', $num_files], // no of entries on disk + ['P', $num_files], // no of entries in cdr + ['P', $cdr_length], // CDR size + ['P', $cdr_offset], // CDR offset + ]; + + $ret = static::packFields($fields); + $this->send($ret); + } + + /** + * Create a format string and argument list for pack(), then call + * pack() and return the result. + * + * @param array $fields + * @return string + */ + public static function packFields(array $fields): string + { + $fmt = ''; + $args = []; + + // populate format string and argument list + foreach ($fields as [$format, $value]) { + if ($format === 'P') { + $fmt .= 'VV'; + if ($value instanceof Bigint) { + $args[] = $value->getLow32(); + $args[] = $value->getHigh32(); + } else { + $args[] = $value; + $args[] = 0; + } + } else { + if ($value instanceof Bigint) { + $value = $value->getLow32(); + } + $fmt .= $format; + $args[] = $value; + } + } + + // prepend format string to argument list + array_unshift($args, $fmt); + + // build output string from header and compressed data + return pack(...$args); + } + + /** + * Send string, sending HTTP headers if necessary. + * Flush output after write if configure option is set. + * + * @param String $str + * @return void + */ + public function send(string $str): void + { + if ($this->need_headers) { + $this->sendHttpHeaders(); + } + $this->need_headers = false; + + fwrite($this->opt->getOutputStream(), $str); + + if ($this->opt->isFlushOutput()) { + // flush output buffer if it is on and flushable + $status = ob_get_status(); + if (isset($status['flags']) && ($status['flags'] & PHP_OUTPUT_HANDLER_FLUSHABLE)) { + ob_flush(); + } + + // Flush system buffers after flushing userspace output buffer + flush(); + } + } + + /** + * Send HTTP headers for this stream. + * + * @return void + */ + protected function sendHttpHeaders(): void + { + // grab content disposition + $disposition = $this->opt->getContentDisposition(); + + if ($this->output_name) { + // Various different browsers dislike various characters here. Strip them all for safety. + $safe_output = trim(str_replace(['"', "'", '\\', ';', "\n", "\r"], '', $this->output_name)); + + // Check if we need to UTF-8 encode the filename + $urlencoded = rawurlencode($safe_output); + $disposition .= "; filename*=UTF-8''{$urlencoded}"; + } + + $headers = array( + 'Content-Type' => $this->opt->getContentType(), + 'Content-Disposition' => $disposition, + 'Pragma' => 'public', + 'Cache-Control' => 'public, must-revalidate', + 'Content-Transfer-Encoding' => 'binary' + ); + + $call = $this->opt->getHttpHeaderCallback(); + foreach ($headers as $key => $val) { + $call("$key: $val"); + } + } + + /** + * Send ZIP64 CDR Locator (Central Directory Record Locator) record. + * + * @return void + */ + protected function addCdr64Locator(): void + { + $cdr_offset = $this->ofs->add($this->cdr_ofs); + + $fields = [ + ['V', static::ZIP64_CDR_LOCATOR_SIGNATURE], // ZIP64 end of central file header signature + ['V', 0x00], // Disc number containing CDR64EOF + ['P', $cdr_offset], // CDR offset + ['V', 1], // Total number of disks + ]; + + $ret = static::packFields($fields); + $this->send($ret); + } + + /** + * Send CDR EOF (Central Directory Record End-of-File) record. + * + * @return void + */ + protected function addCdrEof(): void + { + $num_files = count($this->files); + $cdr_length = $this->cdr_ofs; + $cdr_offset = $this->ofs; + + // grab comment (if specified) + $comment = $this->opt->getComment(); + + $fields = [ + ['V', static::CDR_EOF_SIGNATURE], // end of central file header signature + ['v', 0x00], // disk number + ['v', 0x00], // no of disks + ['v', min($num_files, 0xFFFF)], // no of entries on disk + ['v', min($num_files, 0xFFFF)], // no of entries in cdr + ['V', $cdr_length->getLowFF()], // CDR size + ['V', $cdr_offset->getLowFF()], // CDR offset + ['v', strlen($comment)], // Zip Comment size + ]; + + $ret = static::packFields($fields) . $comment; + $this->send($ret); + } + + /** + * Clear all internal variables. Note that the stream object is not + * usable after this. + * + * @return void + */ + protected function clear(): void + { + $this->files = []; + $this->ofs = new Bigint(); + $this->cdr_ofs = new Bigint(); + $this->opt = new ArchiveOptions(); + } + + /** + * Is this file larger than large_file_size? + * + * @param string $path + * @return bool + */ + public function isLargeFile(string $path): bool + { + if (!$this->opt->isStatFiles()) { + return false; + } + $stat = stat($path); + return $stat['size'] > $this->opt->getLargeFileSize(); + } + + /** + * Save file attributes for trailing CDR record. + * + * @param File $file + * @return void + */ + public function addToCdr(File $file): void + { + $file->ofs = $this->ofs; + $this->ofs = $this->ofs->add($file->getTotalLength()); + $this->files[] = $file->getCdrFile(); + } +} diff --git a/vendor/maennchen/zipstream-php/test/BigintTest.php b/vendor/maennchen/zipstream-php/test/BigintTest.php new file mode 100644 index 0000000..ac9c7c2 --- /dev/null +++ b/vendor/maennchen/zipstream-php/test/BigintTest.php @@ -0,0 +1,65 @@ +assertSame('0x0000000012345678', $bigint->getHex64()); + $this->assertSame(0x12345678, $bigint->getLow32()); + $this->assertSame(0, $bigint->getHigh32()); + } + + public function testConstructLarge(): void + { + $bigint = new Bigint(0x87654321); + $this->assertSame('0x0000000087654321', $bigint->getHex64()); + $this->assertSame('87654321', bin2hex(pack('N', $bigint->getLow32()))); + $this->assertSame(0, $bigint->getHigh32()); + } + + public function testAddSmallValue(): void + { + $bigint = new Bigint(1); + $bigint = $bigint->add(Bigint::init(2)); + $this->assertSame(3, $bigint->getLow32()); + $this->assertFalse($bigint->isOver32()); + $this->assertTrue($bigint->isOver32(true)); + $this->assertSame($bigint->getLowFF(), (float)$bigint->getLow32()); + $this->assertSame($bigint->getLowFF(true), (float)0xFFFFFFFF); + } + + public function testAddWithOverflowAtLowestByte(): void + { + $bigint = new Bigint(0xFF); + $bigint = $bigint->add(Bigint::init(0x01)); + $this->assertSame(0x100, $bigint->getLow32()); + } + + public function testAddWithOverflowAtInteger32(): void + { + $bigint = new Bigint(0xFFFFFFFE); + $this->assertFalse($bigint->isOver32()); + $bigint = $bigint->add(Bigint::init(0x01)); + $this->assertTrue($bigint->isOver32()); + $bigint = $bigint->add(Bigint::init(0x01)); + $this->assertSame('0x0000000100000000', $bigint->getHex64()); + $this->assertTrue($bigint->isOver32()); + $this->assertSame((float)0xFFFFFFFF, $bigint->getLowFF()); + } + + public function testAddWithOverflowAtInteger64(): void + { + $bigint = Bigint::fromLowHigh(0xFFFFFFFF, 0xFFFFFFFF); + $this->assertSame('0xFFFFFFFFFFFFFFFF', $bigint->getHex64()); + $this->expectException(OverflowException::class); + $bigint->add(Bigint::init(1)); + } +} diff --git a/vendor/maennchen/zipstream-php/test/ZipStreamTest.php b/vendor/maennchen/zipstream-php/test/ZipStreamTest.php new file mode 100644 index 0000000..6549b0b --- /dev/null +++ b/vendor/maennchen/zipstream-php/test/ZipStreamTest.php @@ -0,0 +1,586 @@ +expectException(\ZipStream\Exception\FileNotFoundException::class); + // Get ZipStream Object + $zip = new ZipStream(); + + // Trigger error by adding a file which doesn't exist + $zip->addFileFromPath('foobar.php', '/foo/bar/foobar.php'); + } + + public function testFileNotReadableException(): void + { + // create new virtual filesystem + $root = vfsStream::setup('vfs'); + // create a virtual file with no permissions + $file = vfsStream::newFile('foo.txt', 0000)->at($root)->setContent('bar'); + $zip = new ZipStream(); + $this->expectException(\ZipStream\Exception\FileNotReadableException::class); + $zip->addFileFromPath('foo.txt', $file->url()); + } + + public function testDostime(): void + { + // Allows testing of protected method + $class = new \ReflectionClass(File::class); + $method = $class->getMethod('dostime'); + $method->setAccessible(true); + + $this->assertSame($method->invoke(null, 1416246368), 1165069764); + + // January 1 1980 - DOS Epoch. + $this->assertSame($method->invoke(null, 315532800), 2162688); + + // January 1 1970 -> January 1 1980 due to minimum DOS Epoch. @todo Throw Exception? + $this->assertSame($method->invoke(null, 0), 2162688); + } + + public function testAddFile(): void + { + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); + + $zip->addFile('sample.txt', 'Sample String Data'); + $zip->addFile('test/sample.txt', 'More Simple Sample Data'); + + $zip->finish(); + fclose($stream); + + $tmpDir = $this->validateAndExtractZip($tmp); + + $files = $this->getRecursiveFileList($tmpDir); + $this->assertEquals(['sample.txt', 'test/sample.txt'], $files); + + $this->assertStringEqualsFile($tmpDir . '/sample.txt', 'Sample String Data'); + $this->assertStringEqualsFile($tmpDir . '/test/sample.txt', 'More Simple Sample Data'); + } + + /** + * @return array + */ + protected function getTmpFileStream(): array + { + $tmp = tempnam(sys_get_temp_dir(), 'zipstreamtest'); + $stream = fopen($tmp, 'wb+'); + + return array($tmp, $stream); + } + + /** + * @param string $tmp + * @return string + */ + protected function validateAndExtractZip($tmp): string + { + $tmpDir = $this->getTmpDir(); + + $zipArch = new \ZipArchive; + $res = $zipArch->open($tmp); + + if ($res !== true) { + $this->fail("Failed to open {$tmp}. Code: $res"); + + return $tmpDir; + } + + $this->assertEquals(0, $zipArch->status); + $this->assertEquals(0, $zipArch->statusSys); + + $zipArch->extractTo($tmpDir); + $zipArch->close(); + + return $tmpDir; + } + + protected function getTmpDir(): string + { + $tmp = tempnam(sys_get_temp_dir(), 'zipstreamtest'); + unlink($tmp); + mkdir($tmp) or $this->fail('Failed to make directory'); + + return $tmp; + } + + /** + * @param string $path + * @return string[] + */ + protected function getRecursiveFileList(string $path): array + { + $data = array(); + $path = (string)realpath($path); + $files = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path)); + + $pathLen = strlen($path); + foreach ($files as $file) { + $filePath = $file->getRealPath(); + if (!is_dir($filePath)) { + $data[] = substr($filePath, $pathLen + 1); + } + } + + sort($data); + + return $data; + } + + public function testAddFileUtf8NameComment(): void + { + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); + + $name = 'árvíztűrő tükörfúrógép.txt'; + $content = 'Sample String Data'; + $comment = + 'Filename has every special characters ' . + 'from Hungarian language in lowercase. ' . + 'In uppercase: ÁÍŰŐÜÖÚÓÉ'; + + $fileOptions = new FileOptions(); + $fileOptions->setComment($comment); + + $zip->addFile($name, $content, $fileOptions); + $zip->finish(); + fclose($stream); + + $tmpDir = $this->validateAndExtractZip($tmp); + + $files = $this->getRecursiveFileList($tmpDir); + $this->assertEquals(array($name), $files); + $this->assertStringEqualsFile($tmpDir . '/' . $name, $content); + + $zipArch = new \ZipArchive(); + $zipArch->open($tmp); + $this->assertEquals($comment, $zipArch->getCommentName($name)); + } + + public function testAddFileUtf8NameNonUtfComment(): void + { + $this->expectException(\ZipStream\Exception\EncodingException::class); + + $stream = $this->getTmpFileStream()[1]; + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); + + $name = 'á.txt'; + $content = 'any'; + $comment = 'á'; + + $fileOptions = new FileOptions(); + $fileOptions->setComment(mb_convert_encoding($comment, 'ISO-8859-2', 'UTF-8')); + + $zip->addFile($name, $content, $fileOptions); + } + + public function testAddFileNonUtf8NameUtfComment(): void + { + $this->expectException(\ZipStream\Exception\EncodingException::class); + + $stream = $this->getTmpFileStream()[1]; + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); + + $name = 'á.txt'; + $content = 'any'; + $comment = 'á'; + + $fileOptions = new FileOptions(); + $fileOptions->setComment($comment); + + $zip->addFile(mb_convert_encoding($name, 'ISO-8859-2', 'UTF-8'), $content, $fileOptions); + } + + public function testAddFileWithStorageMethod(): void + { + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); + + $fileOptions = new FileOptions(); + $fileOptions->setMethod(Method::STORE()); + + $zip->addFile('sample.txt', 'Sample String Data', $fileOptions); + $zip->addFile('test/sample.txt', 'More Simple Sample Data'); + $zip->finish(); + fclose($stream); + + $zipArch = new \ZipArchive(); + $zipArch->open($tmp); + + $sample1 = $zipArch->statName('sample.txt'); + $sample12 = $zipArch->statName('test/sample.txt'); + $this->assertEquals($sample1['comp_method'], Method::STORE); + $this->assertEquals($sample12['comp_method'], Method::DEFLATE); + + $zipArch->close(); + } + + public function testDecompressFileWithMacUnarchiver(): void + { + if (!file_exists(self::OSX_ARCHIVE_UTILITY)) { + $this->markTestSkipped('The Mac OSX Archive Utility is not available.'); + } + + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); + + $folder = uniqid('', true); + + $zip->addFile($folder . '/sample.txt', 'Sample Data'); + $zip->finish(); + fclose($stream); + + exec(escapeshellarg(self::OSX_ARCHIVE_UTILITY) . ' ' . escapeshellarg($tmp), $output, $returnStatus); + + $this->assertEquals(0, $returnStatus); + $this->assertCount(0, $output); + + $this->assertFileExists(dirname($tmp) . '/' . $folder . '/sample.txt'); + $this->assertStringEqualsFile(dirname($tmp) . '/' . $folder . '/sample.txt', 'Sample Data'); + } + + public function testAddFileFromPath(): void + { + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); + + [$tmpExample, $streamExample] = $this->getTmpFileStream(); + fwrite($streamExample, 'Sample String Data'); + fclose($streamExample); + $zip->addFileFromPath('sample.txt', $tmpExample); + + [$tmpExample, $streamExample] = $this->getTmpFileStream(); + fwrite($streamExample, 'More Simple Sample Data'); + fclose($streamExample); + $zip->addFileFromPath('test/sample.txt', $tmpExample); + + $zip->finish(); + fclose($stream); + + $tmpDir = $this->validateAndExtractZip($tmp); + + $files = $this->getRecursiveFileList($tmpDir); + $this->assertEquals(array('sample.txt', 'test/sample.txt'), $files); + + $this->assertStringEqualsFile($tmpDir . '/sample.txt', 'Sample String Data'); + $this->assertStringEqualsFile($tmpDir . '/test/sample.txt', 'More Simple Sample Data'); + } + + public function testAddFileFromPathWithStorageMethod(): void + { + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); + + $fileOptions = new FileOptions(); + $fileOptions->setMethod(Method::STORE()); + + [$tmpExample, $streamExample] = $this->getTmpFileStream(); + fwrite($streamExample, 'Sample String Data'); + fclose($streamExample); + $zip->addFileFromPath('sample.txt', $tmpExample, $fileOptions); + + [$tmpExample, $streamExample] = $this->getTmpFileStream(); + fwrite($streamExample, 'More Simple Sample Data'); + fclose($streamExample); + $zip->addFileFromPath('test/sample.txt', $tmpExample); + + $zip->finish(); + fclose($stream); + + $zipArch = new \ZipArchive(); + $zipArch->open($tmp); + + $sample1 = $zipArch->statName('sample.txt'); + $this->assertEquals(Method::STORE, $sample1['comp_method']); + + $sample2 = $zipArch->statName('test/sample.txt'); + $this->assertEquals(Method::DEFLATE, $sample2['comp_method']); + + $zipArch->close(); + } + + public function testAddLargeFileFromPath(): void + { + $methods = [Method::DEFLATE(), Method::STORE()]; + $falseTrue = [false, true]; + foreach ($methods as $method) { + foreach ($falseTrue as $zeroHeader) { + foreach ($falseTrue as $zip64) { + if ($zeroHeader && $method->equals(Method::DEFLATE())) { + continue; + } + $this->addLargeFileFileFromPath($method, $zeroHeader, $zip64); + } + } + } + } + + protected function addLargeFileFileFromPath($method, $zeroHeader, $zip64): void + { + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + $options->setLargeFileMethod($method); + $options->setLargeFileSize(5); + $options->setZeroHeader($zeroHeader); + $options->setEnableZip64($zip64); + + $zip = new ZipStream(null, $options); + + [$tmpExample, $streamExample] = $this->getTmpFileStream(); + for ($i = 0; $i <= 10000; $i++) { + fwrite($streamExample, sha1((string)$i)); + if ($i % 100 === 0) { + fwrite($streamExample, "\n"); + } + } + fclose($streamExample); + $shaExample = sha1_file($tmpExample); + $zip->addFileFromPath('sample.txt', $tmpExample); + unlink($tmpExample); + + $zip->finish(); + fclose($stream); + + $tmpDir = $this->validateAndExtractZip($tmp); + + $files = $this->getRecursiveFileList($tmpDir); + $this->assertEquals(array('sample.txt'), $files); + + $this->assertEquals(sha1_file($tmpDir . '/sample.txt'), $shaExample, "SHA-1 Mismatch Method: {$method}"); + } + + public function testAddFileFromStream(): void + { + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); + + // In this test we can't use temporary stream to feed data + // because zlib.deflate filter gives empty string before PHP 7 + // it works fine with file stream + $streamExample = fopen(__FILE__, 'rb'); + $zip->addFileFromStream('sample.txt', $streamExample); +// fclose($streamExample); + + $fileOptions = new FileOptions(); + $fileOptions->setMethod(Method::STORE()); + + $streamExample2 = fopen('php://temp', 'wb+'); + fwrite($streamExample2, 'More Simple Sample Data'); + rewind($streamExample2); // move the pointer back to the beginning of file. + $zip->addFileFromStream('test/sample.txt', $streamExample2, $fileOptions); +// fclose($streamExample2); + + $zip->finish(); + fclose($stream); + + $tmpDir = $this->validateAndExtractZip($tmp); + + $files = $this->getRecursiveFileList($tmpDir); + $this->assertEquals(array('sample.txt', 'test/sample.txt'), $files); + + $this->assertStringEqualsFile(__FILE__, file_get_contents($tmpDir . '/sample.txt')); + $this->assertStringEqualsFile($tmpDir . '/test/sample.txt', 'More Simple Sample Data'); + } + + public function testAddFileFromStreamWithStorageMethod(): void + { + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); + + $fileOptions = new FileOptions(); + $fileOptions->setMethod(Method::STORE()); + + $streamExample = fopen('php://temp', 'wb+'); + fwrite($streamExample, 'Sample String Data'); + rewind($streamExample); // move the pointer back to the beginning of file. + $zip->addFileFromStream('sample.txt', $streamExample, $fileOptions); +// fclose($streamExample); + + $streamExample2 = fopen('php://temp', 'bw+'); + fwrite($streamExample2, 'More Simple Sample Data'); + rewind($streamExample2); // move the pointer back to the beginning of file. + $zip->addFileFromStream('test/sample.txt', $streamExample2); +// fclose($streamExample2); + + $zip->finish(); + fclose($stream); + + $zipArch = new \ZipArchive(); + $zipArch->open($tmp); + + $sample1 = $zipArch->statName('sample.txt'); + $this->assertEquals(Method::STORE, $sample1['comp_method']); + + $sample2 = $zipArch->statName('test/sample.txt'); + $this->assertEquals(Method::DEFLATE, $sample2['comp_method']); + + $zipArch->close(); + } + + public function testAddFileFromPsr7Stream(): void + { + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); + + $body = 'Sample String Data'; + $response = new Response(200, [], $body); + + $fileOptions = new FileOptions(); + $fileOptions->setMethod(Method::STORE()); + + $zip->addFileFromPsr7Stream('sample.json', $response->getBody(), $fileOptions); + $zip->finish(); + fclose($stream); + + $tmpDir = $this->validateAndExtractZip($tmp); + + $files = $this->getRecursiveFileList($tmpDir); + $this->assertEquals(array('sample.json'), $files); + $this->assertStringEqualsFile($tmpDir . '/sample.json', $body); + } + + public function testAddFileFromPsr7StreamWithFileSizeSet(): void + { + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); + + $body = 'Sample String Data'; + $fileSize = strlen($body); + // Add fake padding + $fakePadding = "\0\0\0\0\0\0"; + $response = new Response(200, [], $body . $fakePadding); + + $fileOptions = new FileOptions(); + $fileOptions->setMethod(Method::STORE()); + $fileOptions->setSize($fileSize); + $zip->addFileFromPsr7Stream('sample.json', $response->getBody(), $fileOptions); + $zip->finish(); + fclose($stream); + + $tmpDir = $this->validateAndExtractZip($tmp); + + $files = $this->getRecursiveFileList($tmpDir); + $this->assertEquals(array('sample.json'), $files); + $this->assertStringEqualsFile($tmpDir . '/sample.json', $body); + } + + public function testCreateArchiveWithFlushOptionSet(): void + { + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + $options->setFlushOutput(true); + + $zip = new ZipStream(null, $options); + + $zip->addFile('sample.txt', 'Sample String Data'); + $zip->addFile('test/sample.txt', 'More Simple Sample Data'); + + $zip->finish(); + fclose($stream); + + $tmpDir = $this->validateAndExtractZip($tmp); + + $files = $this->getRecursiveFileList($tmpDir); + $this->assertEquals(['sample.txt', 'test/sample.txt'], $files); + + $this->assertStringEqualsFile($tmpDir . '/sample.txt', 'Sample String Data'); + $this->assertStringEqualsFile($tmpDir . '/test/sample.txt', 'More Simple Sample Data'); + } + + public function testCreateArchiveWithOutputBufferingOffAndFlushOptionSet(): void + { + // WORKAROUND (1/2): remove phpunit's output buffer in order to run test without any buffering + ob_end_flush(); + $this->assertEquals(0, ob_get_level()); + + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + $options->setFlushOutput(true); + + $zip = new ZipStream(null, $options); + + $zip->addFile('sample.txt', 'Sample String Data'); + + $zip->finish(); + fclose($stream); + + $tmpDir = $this->validateAndExtractZip($tmp); + $this->assertStringEqualsFile($tmpDir . '/sample.txt', 'Sample String Data'); + + // WORKAROUND (2/2): add back output buffering so that PHPUnit doesn't complain that it is missing + ob_start(); + } +} diff --git a/vendor/maennchen/zipstream-php/test/bootstrap.php b/vendor/maennchen/zipstream-php/test/bootstrap.php new file mode 100644 index 0000000..b43c9bd --- /dev/null +++ b/vendor/maennchen/zipstream-php/test/bootstrap.php @@ -0,0 +1,6 @@ +setOutputStream(fopen('php://memory', 'wb')); + $fileOpt->setTime(clone $expectedTime); + + $zip = new ZipStream(null, $archiveOpt); + + $zip->addFile('sample.txt', 'Sample', $fileOpt); + + $zip->finish(); + + $this->assertEquals($expectedTime, $fileOpt->getTime()); + } +} diff --git a/vendor/markbaker/complex/.github/workflows/main.yml b/vendor/markbaker/complex/.github/workflows/main.yml new file mode 100644 index 0000000..43d5bf0 --- /dev/null +++ b/vendor/markbaker/complex/.github/workflows/main.yml @@ -0,0 +1,150 @@ +name: main +on: [ push, pull_request ] +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + experimental: + - false + php-version: + - '7.2' + - '7.3' + - '7.4' + - '8.0' + + include: + - php-version: '8.1' + experimental: true + + name: PHP ${{ matrix.php-version }} + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP, with composer and extensions + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + coverage: none + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache composer dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Delete composer lock file + id: composer-lock + if: ${{ matrix.php-version == '8.1' }} + run: | + echo "::set-output name=flags::--ignore-platform-reqs" + + - name: Install dependencies + run: composer update --no-progress --prefer-dist --optimize-autoloader ${{ steps.composer-lock.outputs.flags }} + + - name: Setup problem matchers for PHP + run: echo "::add-matcher::${{ runner.tool_cache }}/php.json" + + - name: Setup problem matchers for PHPUnit + run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + + - name: "Run PHPUnit tests (Experimental: ${{ matrix.experimental }})" + env: + FAILURE_ACTION: "${{ matrix.experimental == true }}" + run: vendor/bin/phpunit --verbose || $FAILURE_ACTION + + phpcs: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP, with composer and extensions + uses: shivammathur/setup-php@v2 + with: + php-version: 7.4 + coverage: none + tools: cs2pr + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache composer dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: composer install --no-progress --prefer-dist --optimize-autoloader + + - name: Code style with PHP_CodeSniffer + run: ./vendor/bin/phpcs -q --report=checkstyle classes/src/ | cs2pr + + versions: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP, with composer and extensions + uses: shivammathur/setup-php@v2 + with: + php-version: 7.4 + coverage: none + tools: cs2pr + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache composer dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: composer install --no-progress --prefer-dist --optimize-autoloader + + - name: Code Version Compatibility check with PHP_CodeSniffer + run: ./vendor/bin/phpcs -q --report-width=200 --report=summary,full classes/src/ --standard=PHPCompatibility --runtime-set testVersion 7.2- + + coverage: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP, with composer and extensions + uses: shivammathur/setup-php@v2 + with: + php-version: 7.4 + coverage: pcov + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache composer dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: composer install --no-progress --prefer-dist --optimize-autoloader + + - name: Test Coverage + run: ./vendor/bin/phpunit --verbose --coverage-text diff --git a/vendor/markbaker/complex/README.md b/vendor/markbaker/complex/README.md new file mode 100644 index 0000000..27afc59 --- /dev/null +++ b/vendor/markbaker/complex/README.md @@ -0,0 +1,173 @@ +PHPComplex +========== + +--- + +PHP Class Library for working with Complex numbers + +[![Build Status](https://github.com/MarkBaker/PHPComplex/workflows/main/badge.svg)](https://github.com/MarkBaker/PHPComplex/actions) +[![Total Downloads](https://img.shields.io/packagist/dt/markbaker/complex)](https://packagist.org/packages/markbaker/complex) +[![Latest Stable Version](https://img.shields.io/github/v/release/MarkBaker/PHPComplex)](https://packagist.org/packages/markbaker/complex) +[![License](https://img.shields.io/github/license/MarkBaker/PHPComplex)](https://packagist.org/packages/markbaker/complex) + + +[![Complex Numbers](https://imgs.xkcd.com/comics/complex_numbers_2x.png)](https://xkcd.com/2028/) + +--- + +The library currently provides the following operations: + + - addition + - subtraction + - multiplication + - division + - division by + - division into + +together with functions for + + - theta (polar theta angle) + - rho (polar distance/radius) + - conjugate + * negative + - inverse (1 / complex) + - cos (cosine) + - acos (inverse cosine) + - cosh (hyperbolic cosine) + - acosh (inverse hyperbolic cosine) + - sin (sine) + - asin (inverse sine) + - sinh (hyperbolic sine) + - asinh (inverse hyperbolic sine) + - sec (secant) + - asec (inverse secant) + - sech (hyperbolic secant) + - asech (inverse hyperbolic secant) + - csc (cosecant) + - acsc (inverse cosecant) + - csch (hyperbolic secant) + - acsch (inverse hyperbolic secant) + - tan (tangent) + - atan (inverse tangent) + - tanh (hyperbolic tangent) + - atanh (inverse hyperbolic tangent) + - cot (cotangent) + - acot (inverse cotangent) + - coth (hyperbolic cotangent) + - acoth (inverse hyperbolic cotangent) + - sqrt (square root) + - exp (exponential) + - ln (natural log) + - log10 (base-10 log) + - log2 (base-2 log) + - pow (raised to the power of a real number) + + +--- + +# Installation + +```shell +composer require markbaker/complex:^3.0 +``` + +# Important BC Note + +If you've previously been using procedural calls to functions and operations using this library, then from version 3.0 you should use [MarkBaker/PHPComplexFunctions](https://github.com/MarkBaker/PHPComplexFunctions) instead (available on packagist as [markbaker/complex-functions](https://packagist.org/packages/markbaker/complex-functions)). + +You'll need to replace `markbaker/complex`in your `composer.json` file with the new library, but otherwise there should be no difference in the namespacing, or in the way that you have called the Complex functions in the past, so no actual code changes are required. + +```shell +composer require markbaker/complex-functions:^1.0 +``` + +You should not reference this library (`markbaker/complex`) in your `composer.json`, composer wil take care of that for you. + +# Usage + +To create a new complex object, you can provide either the real, imaginary and suffix parts as individual values, or as an array of values passed passed to the constructor; or a string representing the value. e.g + +```php +$real = 1.23; +$imaginary = -4.56; +$suffix = 'i'; + +$complexObject = new Complex\Complex($real, $imaginary, $suffix); +``` +or as an array +```php +$real = 1.23; +$imaginary = -4.56; +$suffix = 'i'; + +$arguments = [$real, $imaginary, $suffix]; + +$complexObject = new Complex\Complex($arguments); +``` +or as a string +```php +$complexString = '1.23-4.56i'; + +$complexObject = new Complex\Complex($complexString); +``` + +Complex objects are immutable: whenever you call a method or pass a complex value to a function that returns a complex value, a new Complex object will be returned, and the original will remain unchanged. +This also allows you to chain multiple methods as you would for a fluent interface (as long as they are methods that will return a Complex result). + +## Performing Mathematical Operations + +To perform mathematical operations with Complex values, you can call the appropriate method against a complex value, passing other values as arguments + +```php +$complexString1 = '1.23-4.56i'; +$complexString2 = '2.34+5.67i'; + +$complexObject = new Complex\Complex($complexString1); +echo $complexObject->add($complexString2); +``` + +or use the static Operation methods +```php +$complexString1 = '1.23-4.56i'; +$complexString2 = '2.34+5.67i'; + +echo Complex\Operations::add($complexString1, $complexString2); +``` +If you want to perform the same operation against multiple values (e.g. to add three or more complex numbers), then you can pass multiple arguments to any of the operations. + +You can pass these arguments as Complex objects, or as an array, or string that will parse to a complex object. + +## Using functions + +When calling any of the available functions for a complex value, you can either call the relevant method for the Complex object +```php +$complexString = '1.23-4.56i'; + +$complexObject = new Complex\Complex($complexString); +echo $complexObject->sinh(); +``` + +or use the static Functions methods +```php +$complexString = '1.23-4.56i'; + +echo Complex\Functions::sinh($complexString); +``` +As with operations, you can pass these arguments as Complex objects, or as an array or string that will parse to a complex object. + + +In the case of the `pow()` function (the only implemented function that requires an additional argument) you need to pass both arguments when calling the function + +```php +$complexString = '1.23-4.56i'; + +$complexObject = new Complex\Complex($complexString); +echo Complex\Functions::pow($complexObject, 2); +``` +or pass the additional argument when calling the method +```php +$complexString = '1.23-4.56i'; + +$complexObject = new Complex\Complex($complexString); +echo $complexObject->pow(2); +``` diff --git a/vendor/markbaker/complex/classes/src/Complex.php b/vendor/markbaker/complex/classes/src/Complex.php new file mode 100644 index 0000000..25414ee --- /dev/null +++ b/vendor/markbaker/complex/classes/src/Complex.php @@ -0,0 +1,388 @@ +realPart = (float) $realPart; + $this->imaginaryPart = (float) $imaginaryPart; + $this->suffix = strtolower($suffix ?? ''); + } + + /** + * Gets the real part of this complex number + * + * @return Float + */ + public function getReal(): float + { + return $this->realPart; + } + + /** + * Gets the imaginary part of this complex number + * + * @return Float + */ + public function getImaginary(): float + { + return $this->imaginaryPart; + } + + /** + * Gets the suffix of this complex number + * + * @return String + */ + public function getSuffix(): string + { + return $this->suffix; + } + + /** + * Returns true if this is a real value, false if a complex value + * + * @return Bool + */ + public function isReal(): bool + { + return $this->imaginaryPart == 0.0; + } + + /** + * Returns true if this is a complex value, false if a real value + * + * @return Bool + */ + public function isComplex(): bool + { + return !$this->isReal(); + } + + public function format(): string + { + $str = ""; + if ($this->imaginaryPart != 0.0) { + if (\abs($this->imaginaryPart) != 1.0) { + $str .= $this->imaginaryPart . $this->suffix; + } else { + $str .= (($this->imaginaryPart < 0.0) ? '-' : '') . $this->suffix; + } + } + if ($this->realPart != 0.0) { + if (($str) && ($this->imaginaryPart > 0.0)) { + $str = "+" . $str; + } + $str = $this->realPart . $str; + } + if (!$str) { + $str = "0.0"; + } + + return $str; + } + + public function __toString(): string + { + return $this->format(); + } + + /** + * Validates whether the argument is a valid complex number, converting scalar or array values if possible + * + * @param mixed $complex The value to validate + * @return Complex + * @throws Exception If the argument isn't a Complex number or cannot be converted to one + */ + public static function validateComplexArgument($complex): Complex + { + if (is_scalar($complex) || is_array($complex)) { + $complex = new Complex($complex); + } elseif (!is_object($complex) || !($complex instanceof Complex)) { + throw new Exception('Value is not a valid complex number'); + } + + return $complex; + } + + /** + * Returns the reverse of this complex number + * + * @return Complex + */ + public function reverse(): Complex + { + return new Complex( + $this->imaginaryPart, + $this->realPart, + ($this->realPart == 0.0) ? null : $this->suffix + ); + } + + public function invertImaginary(): Complex + { + return new Complex( + $this->realPart, + $this->imaginaryPart * -1, + ($this->imaginaryPart == 0.0) ? null : $this->suffix + ); + } + + public function invertReal(): Complex + { + return new Complex( + $this->realPart * -1, + $this->imaginaryPart, + ($this->imaginaryPart == 0.0) ? null : $this->suffix + ); + } + + protected static $functions = [ + 'abs', + 'acos', + 'acosh', + 'acot', + 'acoth', + 'acsc', + 'acsch', + 'argument', + 'asec', + 'asech', + 'asin', + 'asinh', + 'atan', + 'atanh', + 'conjugate', + 'cos', + 'cosh', + 'cot', + 'coth', + 'csc', + 'csch', + 'exp', + 'inverse', + 'ln', + 'log2', + 'log10', + 'negative', + 'pow', + 'rho', + 'sec', + 'sech', + 'sin', + 'sinh', + 'sqrt', + 'tan', + 'tanh', + 'theta', + ]; + + protected static $operations = [ + 'add', + 'subtract', + 'multiply', + 'divideby', + 'divideinto', + ]; + + /** + * Returns the result of the function call or operation + * + * @return Complex|float + * @throws Exception|\InvalidArgumentException + */ + public function __call($functionName, $arguments) + { + $functionName = strtolower(str_replace('_', '', $functionName)); + + // Test for function calls + if (in_array($functionName, self::$functions, true)) { + return Functions::$functionName($this, ...$arguments); + } + // Test for operation calls + if (in_array($functionName, self::$operations, true)) { + return Operations::$functionName($this, ...$arguments); + } + throw new Exception('Complex Function or Operation does not exist'); + } +} diff --git a/vendor/markbaker/complex/classes/src/Exception.php b/vendor/markbaker/complex/classes/src/Exception.php new file mode 100644 index 0000000..a2beb73 --- /dev/null +++ b/vendor/markbaker/complex/classes/src/Exception.php @@ -0,0 +1,13 @@ +getReal() - $invsqrt->getImaginary(), + $complex->getImaginary() + $invsqrt->getReal() + ); + $log = self::ln($adjust); + + return new Complex( + $log->getImaginary(), + -1 * $log->getReal() + ); + } + + /** + * Returns the inverse hyperbolic cosine of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The inverse hyperbolic cosine of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + */ + public static function acosh($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + if ($complex->isReal() && ($complex->getReal() > 1)) { + return new Complex(\acosh($complex->getReal())); + } + + $acosh = self::acos($complex) + ->reverse(); + if ($acosh->getReal() < 0.0) { + $acosh = $acosh->invertReal(); + } + + return $acosh; + } + + /** + * Returns the inverse cotangent of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The inverse cotangent of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + * @throws \InvalidArgumentException If function would result in a division by zero + */ + public static function acot($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + return self::atan(self::inverse($complex)); + } + + /** + * Returns the inverse hyperbolic cotangent of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The inverse hyperbolic cotangent of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + * @throws \InvalidArgumentException If function would result in a division by zero + */ + public static function acoth($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + return self::atanh(self::inverse($complex)); + } + + /** + * Returns the inverse cosecant of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The inverse cosecant of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + * @throws \InvalidArgumentException If function would result in a division by zero + */ + public static function acsc($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { + return new Complex(INF); + } + + return self::asin(self::inverse($complex)); + } + + /** + * Returns the inverse hyperbolic cosecant of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The inverse hyperbolic cosecant of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + * @throws \InvalidArgumentException If function would result in a division by zero + */ + public static function acsch($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { + return new Complex(INF); + } + + return self::asinh(self::inverse($complex)); + } + + /** + * Returns the argument of a complex number. + * Also known as the theta of the complex number, i.e. the angle in radians + * from the real axis to the representation of the number in polar coordinates. + * + * This function is a synonym for theta() + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return float The argument (or theta) value of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + * + * @see theta + */ + public static function argument($complex): float + { + return self::theta($complex); + } + + /** + * Returns the inverse secant of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The inverse secant of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + * @throws \InvalidArgumentException If function would result in a division by zero + */ + public static function asec($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { + return new Complex(INF); + } + + return self::acos(self::inverse($complex)); + } + + /** + * Returns the inverse hyperbolic secant of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The inverse hyperbolic secant of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + * @throws \InvalidArgumentException If function would result in a division by zero + */ + public static function asech($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { + return new Complex(INF); + } + + return self::acosh(self::inverse($complex)); + } + + /** + * Returns the inverse sine of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The inverse sine of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + */ + public static function asin($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + $square = Operations::multiply($complex, $complex); + $invsqrt = new Complex(1.0); + $invsqrt = Operations::subtract($invsqrt, $square); + $invsqrt = self::sqrt($invsqrt); + $adjust = new Complex( + $invsqrt->getReal() - $complex->getImaginary(), + $invsqrt->getImaginary() + $complex->getReal() + ); + $log = self::ln($adjust); + + return new Complex( + $log->getImaginary(), + -1 * $log->getReal() + ); + } + + /** + * Returns the inverse hyperbolic sine of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The inverse hyperbolic sine of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + */ + public static function asinh($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + if ($complex->isReal() && ($complex->getReal() > 1)) { + return new Complex(\asinh($complex->getReal())); + } + + $asinh = clone $complex; + $asinh = $asinh->reverse() + ->invertReal(); + $asinh = self::asin($asinh); + + return $asinh->reverse() + ->invertImaginary(); + } + + /** + * Returns the inverse tangent of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The inverse tangent of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + * @throws \InvalidArgumentException If function would result in a division by zero + */ + public static function atan($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + if ($complex->isReal()) { + return new Complex(\atan($complex->getReal())); + } + + $t1Value = new Complex(-1 * $complex->getImaginary(), $complex->getReal()); + $uValue = new Complex(1, 0); + + $d1Value = clone $uValue; + $d1Value = Operations::subtract($d1Value, $t1Value); + $d2Value = Operations::add($t1Value, $uValue); + $uResult = $d1Value->divideBy($d2Value); + $uResult = self::ln($uResult); + + return new Complex( + (($uResult->getImaginary() == M_PI) ? -M_PI : $uResult->getImaginary()) * -0.5, + $uResult->getReal() * 0.5, + $complex->getSuffix() + ); + } + + /** + * Returns the inverse hyperbolic tangent of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The inverse hyperbolic tangent of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + */ + public static function atanh($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + if ($complex->isReal()) { + $real = $complex->getReal(); + if ($real >= -1.0 && $real <= 1.0) { + return new Complex(\atanh($real)); + } else { + return new Complex(\atanh(1 / $real), (($real < 0.0) ? M_PI_2 : -1 * M_PI_2)); + } + } + + $iComplex = clone $complex; + $iComplex = $iComplex->invertImaginary() + ->reverse(); + return self::atan($iComplex) + ->invertReal() + ->reverse(); + } + + /** + * Returns the complex conjugate of a complex number + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The conjugate of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + */ + public static function conjugate($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + return new Complex( + $complex->getReal(), + -1 * $complex->getImaginary(), + $complex->getSuffix() + ); + } + + /** + * Returns the cosine of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The cosine of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + */ + public static function cos($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + if ($complex->isReal()) { + return new Complex(\cos($complex->getReal())); + } + + return self::conjugate( + new Complex( + \cos($complex->getReal()) * \cosh($complex->getImaginary()), + \sin($complex->getReal()) * \sinh($complex->getImaginary()), + $complex->getSuffix() + ) + ); + } + + /** + * Returns the hyperbolic cosine of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The hyperbolic cosine of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + */ + public static function cosh($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + if ($complex->isReal()) { + return new Complex(\cosh($complex->getReal())); + } + + return new Complex( + \cosh($complex->getReal()) * \cos($complex->getImaginary()), + \sinh($complex->getReal()) * \sin($complex->getImaginary()), + $complex->getSuffix() + ); + } + + /** + * Returns the cotangent of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The cotangent of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + * @throws \InvalidArgumentException If function would result in a division by zero + */ + public static function cot($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { + return new Complex(INF); + } + + return self::inverse(self::tan($complex)); + } + + /** + * Returns the hyperbolic cotangent of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The hyperbolic cotangent of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + * @throws \InvalidArgumentException If function would result in a division by zero + */ + public static function coth($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + return self::inverse(self::tanh($complex)); + } + + /** + * Returns the cosecant of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The cosecant of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + * @throws \InvalidArgumentException If function would result in a division by zero + */ + public static function csc($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { + return new Complex(INF); + } + + return self::inverse(self::sin($complex)); + } + + /** + * Returns the hyperbolic cosecant of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The hyperbolic cosecant of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + * @throws \InvalidArgumentException If function would result in a division by zero + */ + public static function csch($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { + return new Complex(INF); + } + + return self::inverse(self::sinh($complex)); + } + + /** + * Returns the exponential of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The exponential of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + */ + public static function exp($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + if (($complex->getReal() == 0.0) && (\abs($complex->getImaginary()) == M_PI)) { + return new Complex(-1.0, 0.0); + } + + $rho = \exp($complex->getReal()); + + return new Complex( + $rho * \cos($complex->getImaginary()), + $rho * \sin($complex->getImaginary()), + $complex->getSuffix() + ); + } + + /** + * Returns the inverse of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The inverse of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + * @throws InvalidArgumentException If function would result in a division by zero + */ + public static function inverse($complex): Complex + { + $complex = clone Complex::validateComplexArgument($complex); + + if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { + throw new InvalidArgumentException('Division by zero'); + } + + return $complex->divideInto(1.0); + } + + /** + * Returns the natural logarithm of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The natural logarithm of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + * @throws InvalidArgumentException If the real and the imaginary parts are both zero + */ + public static function ln($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + if (($complex->getReal() == 0.0) && ($complex->getImaginary() == 0.0)) { + throw new InvalidArgumentException(); + } + + return new Complex( + \log(self::rho($complex)), + self::theta($complex), + $complex->getSuffix() + ); + } + + /** + * Returns the base-2 logarithm of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The base-2 logarithm of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + * @throws InvalidArgumentException If the real and the imaginary parts are both zero + */ + public static function log2($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + if (($complex->getReal() == 0.0) && ($complex->getImaginary() == 0.0)) { + throw new InvalidArgumentException(); + } elseif (($complex->getReal() > 0.0) && ($complex->getImaginary() == 0.0)) { + return new Complex(\log($complex->getReal(), 2), 0.0, $complex->getSuffix()); + } + + return self::ln($complex) + ->multiply(\log(Complex::EULER, 2)); + } + + /** + * Returns the common logarithm (base 10) of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The common logarithm (base 10) of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + * @throws InvalidArgumentException If the real and the imaginary parts are both zero + */ + public static function log10($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + if (($complex->getReal() == 0.0) && ($complex->getImaginary() == 0.0)) { + throw new InvalidArgumentException(); + } elseif (($complex->getReal() > 0.0) && ($complex->getImaginary() == 0.0)) { + return new Complex(\log10($complex->getReal()), 0.0, $complex->getSuffix()); + } + + return self::ln($complex) + ->multiply(\log10(Complex::EULER)); + } + + /** + * Returns the negative of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The negative value of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + * + * @see rho + * + */ + public static function negative($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + return new Complex( + -1 * $complex->getReal(), + -1 * $complex->getImaginary(), + $complex->getSuffix() + ); + } + + /** + * Returns a complex number raised to a power. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @param float|integer $power The power to raise this value to + * @return Complex The complex argument raised to the real power. + * @throws Exception If the power argument isn't a valid real + */ + public static function pow($complex, $power): Complex + { + $complex = Complex::validateComplexArgument($complex); + + if (!is_numeric($power)) { + throw new Exception('Power argument must be a real number'); + } + + if ($complex->getImaginary() == 0.0 && $complex->getReal() >= 0.0) { + return new Complex(\pow($complex->getReal(), $power)); + } + + $rValue = \sqrt(($complex->getReal() * $complex->getReal()) + ($complex->getImaginary() * $complex->getImaginary())); + $rPower = \pow($rValue, $power); + $theta = $complex->argument() * $power; + if ($theta == 0) { + return new Complex(1); + } + + return new Complex($rPower * \cos($theta), $rPower * \sin($theta), $complex->getSuffix()); + } + + /** + * Returns the rho of a complex number. + * This is the distance/radius from the centrepoint to the representation of the number in polar coordinates. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return float The rho value of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + */ + public static function rho($complex): float + { + $complex = Complex::validateComplexArgument($complex); + + return \sqrt( + ($complex->getReal() * $complex->getReal()) + + ($complex->getImaginary() * $complex->getImaginary()) + ); + } + + /** + * Returns the secant of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The secant of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + * @throws \InvalidArgumentException If function would result in a division by zero + */ + public static function sec($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + return self::inverse(self::cos($complex)); + } + + /** + * Returns the hyperbolic secant of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The hyperbolic secant of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + * @throws \InvalidArgumentException If function would result in a division by zero + */ + public static function sech($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + return self::inverse(self::cosh($complex)); + } + + /** + * Returns the sine of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The sine of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + */ + public static function sin($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + if ($complex->isReal()) { + return new Complex(\sin($complex->getReal())); + } + + return new Complex( + \sin($complex->getReal()) * \cosh($complex->getImaginary()), + \cos($complex->getReal()) * \sinh($complex->getImaginary()), + $complex->getSuffix() + ); + } + + /** + * Returns the hyperbolic sine of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The hyperbolic sine of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + */ + public static function sinh($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + if ($complex->isReal()) { + return new Complex(\sinh($complex->getReal())); + } + + return new Complex( + \sinh($complex->getReal()) * \cos($complex->getImaginary()), + \cosh($complex->getReal()) * \sin($complex->getImaginary()), + $complex->getSuffix() + ); + } + + /** + * Returns the square root of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The Square root of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + */ + public static function sqrt($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + $theta = self::theta($complex); + $delta1 = \cos($theta / 2); + $delta2 = \sin($theta / 2); + $rho = \sqrt(self::rho($complex)); + + return new Complex($delta1 * $rho, $delta2 * $rho, $complex->getSuffix()); + } + + /** + * Returns the tangent of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The tangent of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + * @throws InvalidArgumentException If function would result in a division by zero + */ + public static function tan($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + if ($complex->isReal()) { + return new Complex(\tan($complex->getReal())); + } + + $real = $complex->getReal(); + $imaginary = $complex->getImaginary(); + $divisor = 1 + \pow(\tan($real), 2) * \pow(\tanh($imaginary), 2); + if ($divisor == 0.0) { + throw new InvalidArgumentException('Division by zero'); + } + + return new Complex( + \pow(self::sech($imaginary)->getReal(), 2) * \tan($real) / $divisor, + \pow(self::sec($real)->getReal(), 2) * \tanh($imaginary) / $divisor, + $complex->getSuffix() + ); + } + + /** + * Returns the hyperbolic tangent of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The hyperbolic tangent of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + * @throws \InvalidArgumentException If function would result in a division by zero + */ + public static function tanh($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + $real = $complex->getReal(); + $imaginary = $complex->getImaginary(); + $divisor = \cos($imaginary) * \cos($imaginary) + \sinh($real) * \sinh($real); + if ($divisor == 0.0) { + throw new InvalidArgumentException('Division by zero'); + } + + return new Complex( + \sinh($real) * \cosh($real) / $divisor, + 0.5 * \sin(2 * $imaginary) / $divisor, + $complex->getSuffix() + ); + } + + /** + * Returns the theta of a complex number. + * This is the angle in radians from the real axis to the representation of the number in polar coordinates. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return float The theta value of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + */ + public static function theta($complex): float + { + $complex = Complex::validateComplexArgument($complex); + + if ($complex->getReal() == 0.0) { + if ($complex->isReal()) { + return 0.0; + } elseif ($complex->getImaginary() < 0.0) { + return M_PI / -2; + } + return M_PI / 2; + } elseif ($complex->getReal() > 0.0) { + return \atan($complex->getImaginary() / $complex->getReal()); + } elseif ($complex->getImaginary() < 0.0) { + return -(M_PI - \atan(\abs($complex->getImaginary()) / \abs($complex->getReal()))); + } + + return M_PI - \atan($complex->getImaginary() / \abs($complex->getReal())); + } +} diff --git a/vendor/markbaker/complex/classes/src/Operations.php b/vendor/markbaker/complex/classes/src/Operations.php new file mode 100644 index 0000000..b13a873 --- /dev/null +++ b/vendor/markbaker/complex/classes/src/Operations.php @@ -0,0 +1,210 @@ +isComplex() && $complex->isComplex() && + $result->getSuffix() !== $complex->getSuffix()) { + throw new Exception('Suffix Mismatch'); + } + + $real = $result->getReal() + $complex->getReal(); + $imaginary = $result->getImaginary() + $complex->getImaginary(); + + $result = new Complex( + $real, + $imaginary, + ($imaginary == 0.0) ? null : max($result->getSuffix(), $complex->getSuffix()) + ); + } + + return $result; + } + + /** + * Divides two or more complex numbers + * + * @param array of string|integer|float|Complex $complexValues The numbers to divide + * @return Complex + */ + public static function divideby(...$complexValues): Complex + { + if (count($complexValues) < 2) { + throw new \Exception('This function requires at least 2 arguments'); + } + + $base = array_shift($complexValues); + $result = clone Complex::validateComplexArgument($base); + + foreach ($complexValues as $complex) { + $complex = Complex::validateComplexArgument($complex); + + if ($result->isComplex() && $complex->isComplex() && + $result->getSuffix() !== $complex->getSuffix()) { + throw new Exception('Suffix Mismatch'); + } + if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { + throw new InvalidArgumentException('Division by zero'); + } + + $delta1 = ($result->getReal() * $complex->getReal()) + + ($result->getImaginary() * $complex->getImaginary()); + $delta2 = ($result->getImaginary() * $complex->getReal()) - + ($result->getReal() * $complex->getImaginary()); + $delta3 = ($complex->getReal() * $complex->getReal()) + + ($complex->getImaginary() * $complex->getImaginary()); + + $real = $delta1 / $delta3; + $imaginary = $delta2 / $delta3; + + $result = new Complex( + $real, + $imaginary, + ($imaginary == 0.0) ? null : max($result->getSuffix(), $complex->getSuffix()) + ); + } + + return $result; + } + + /** + * Divides two or more complex numbers + * + * @param array of string|integer|float|Complex $complexValues The numbers to divide + * @return Complex + */ + public static function divideinto(...$complexValues): Complex + { + if (count($complexValues) < 2) { + throw new \Exception('This function requires at least 2 arguments'); + } + + $base = array_shift($complexValues); + $result = clone Complex::validateComplexArgument($base); + + foreach ($complexValues as $complex) { + $complex = Complex::validateComplexArgument($complex); + + if ($result->isComplex() && $complex->isComplex() && + $result->getSuffix() !== $complex->getSuffix()) { + throw new Exception('Suffix Mismatch'); + } + if ($result->getReal() == 0.0 && $result->getImaginary() == 0.0) { + throw new InvalidArgumentException('Division by zero'); + } + + $delta1 = ($complex->getReal() * $result->getReal()) + + ($complex->getImaginary() * $result->getImaginary()); + $delta2 = ($complex->getImaginary() * $result->getReal()) - + ($complex->getReal() * $result->getImaginary()); + $delta3 = ($result->getReal() * $result->getReal()) + + ($result->getImaginary() * $result->getImaginary()); + + $real = $delta1 / $delta3; + $imaginary = $delta2 / $delta3; + + $result = new Complex( + $real, + $imaginary, + ($imaginary == 0.0) ? null : max($result->getSuffix(), $complex->getSuffix()) + ); + } + + return $result; + } + + /** + * Multiplies two or more complex numbers + * + * @param array of string|integer|float|Complex $complexValues The numbers to multiply + * @return Complex + */ + public static function multiply(...$complexValues): Complex + { + if (count($complexValues) < 2) { + throw new \Exception('This function requires at least 2 arguments'); + } + + $base = array_shift($complexValues); + $result = clone Complex::validateComplexArgument($base); + + foreach ($complexValues as $complex) { + $complex = Complex::validateComplexArgument($complex); + + if ($result->isComplex() && $complex->isComplex() && + $result->getSuffix() !== $complex->getSuffix()) { + throw new Exception('Suffix Mismatch'); + } + + $real = ($result->getReal() * $complex->getReal()) - + ($result->getImaginary() * $complex->getImaginary()); + $imaginary = ($result->getReal() * $complex->getImaginary()) + + ($result->getImaginary() * $complex->getReal()); + + $result = new Complex( + $real, + $imaginary, + ($imaginary == 0.0) ? null : max($result->getSuffix(), $complex->getSuffix()) + ); + } + + return $result; + } + + /** + * Subtracts two or more complex numbers + * + * @param array of string|integer|float|Complex $complexValues The numbers to subtract + * @return Complex + */ + public static function subtract(...$complexValues): Complex + { + if (count($complexValues) < 2) { + throw new \Exception('This function requires at least 2 arguments'); + } + + $base = array_shift($complexValues); + $result = clone Complex::validateComplexArgument($base); + + foreach ($complexValues as $complex) { + $complex = Complex::validateComplexArgument($complex); + + if ($result->isComplex() && $complex->isComplex() && + $result->getSuffix() !== $complex->getSuffix()) { + throw new Exception('Suffix Mismatch'); + } + + $real = $result->getReal() - $complex->getReal(); + $imaginary = $result->getImaginary() - $complex->getImaginary(); + + $result = new Complex( + $real, + $imaginary, + ($imaginary == 0.0) ? null : max($result->getSuffix(), $complex->getSuffix()) + ); + } + + return $result; + } +} diff --git a/vendor/markbaker/complex/composer.json b/vendor/markbaker/complex/composer.json new file mode 100644 index 0000000..ea1d06f --- /dev/null +++ b/vendor/markbaker/complex/composer.json @@ -0,0 +1,33 @@ +{ + "name": "markbaker/complex", + "type": "library", + "description": "PHP Class for working with complex numbers", + "keywords": ["complex", "mathematics"], + "homepage": "https://github.com/MarkBaker/PHPComplex", + "license": "MIT", + "authors": [ + { + "name": "Mark Baker", + "email": "mark@lange.demon.co.uk" + } + ], + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.3", + "squizlabs/php_codesniffer": "^3.4", + "phpcompatibility/php-compatibility": "^9.0", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0" + }, + "autoload": { + "psr-4": { + "Complex\\": "classes/src/" + } + }, + "scripts": { + "style": "phpcs --report-width=200 --standard=PSR2 --report=summary,full classes/src/ unitTests/classes/src -n", + "versions": "phpcs --report-width=200 --standard=PHPCompatibility --report=summary,full classes/src/ --runtime-set testVersion 7.2- -n" + }, + "minimum-stability": "dev" +} diff --git a/vendor/markbaker/complex/examples/complexTest.php b/vendor/markbaker/complex/examples/complexTest.php new file mode 100644 index 0000000..9a5e123 --- /dev/null +++ b/vendor/markbaker/complex/examples/complexTest.php @@ -0,0 +1,154 @@ +add(456); +echo $x, PHP_EOL; + +$x = new Complex(123.456); +$x->add(789.012); +echo $x, PHP_EOL; + +$x = new Complex(123.456, 78.90); +$x->add(new Complex(-987.654, -32.1)); +echo $x, PHP_EOL; + +$x = new Complex(123.456, 78.90); +$x->add(-987.654); +echo $x, PHP_EOL; + +$x = new Complex(-987.654, -32.1); +$x->add(new Complex(0, 1)); +echo $x, PHP_EOL; + +$x = new Complex(-987.654, -32.1); +$x->add(new Complex(0, -1)); +echo $x, PHP_EOL; + + +echo PHP_EOL, 'Subtract', PHP_EOL; + +$x = new Complex(123); +$x->subtract(456); +echo $x, PHP_EOL; + +$x = new Complex(123.456); +$x->subtract(789.012); +echo $x, PHP_EOL; + +$x = new Complex(123.456, 78.90); +$x->subtract(new Complex(-987.654, -32.1)); +echo $x, PHP_EOL; + +$x = new Complex(123.456, 78.90); +$x->subtract(-987.654); +echo $x, PHP_EOL; + +$x = new Complex(-987.654, -32.1); +$x->subtract(new Complex(0, 1)); +echo $x, PHP_EOL; + +$x = new Complex(-987.654, -32.1); +$x->subtract(new Complex(0, -1)); +echo $x, PHP_EOL; + + +echo PHP_EOL, 'Multiply', PHP_EOL; + +$x = new Complex(123); +$x->multiply(456); +echo $x, PHP_EOL; + +$x = new Complex(123.456); +$x->multiply(789.012); +echo $x, PHP_EOL; + +$x = new Complex(123.456, 78.90); +$x->multiply(new Complex(-987.654, -32.1)); +echo $x, PHP_EOL; + +$x = new Complex(123.456, 78.90); +$x->multiply(-987.654); +echo $x, PHP_EOL; + +$x = new Complex(-987.654, -32.1); +$x->multiply(new Complex(0, 1)); +echo $x, PHP_EOL; + +$x = new Complex(-987.654, -32.1); +$x->multiply(new Complex(0, -1)); +echo $x, PHP_EOL; + + +echo PHP_EOL, 'Divide By', PHP_EOL; + +$x = new Complex(123); +$x->divideBy(456); +echo $x, PHP_EOL; + +$x = new Complex(123.456); +$x->divideBy(789.012); +echo $x, PHP_EOL; + +$x = new Complex(123.456, 78.90); +$x->divideBy(new Complex(-987.654, -32.1)); +echo $x, PHP_EOL; + +$x = new Complex(123.456, 78.90); +$x->divideBy(-987.654); +echo $x, PHP_EOL; + +$x = new Complex(-987.654, -32.1); +$x->divideBy(new Complex(0, 1)); +echo $x, PHP_EOL; + +$x = new Complex(-987.654, -32.1); +$x->divideBy(new Complex(0, -1)); +echo $x, PHP_EOL; + + +echo PHP_EOL, 'Divide Into', PHP_EOL; + +$x = new Complex(123); +$x->divideInto(456); +echo $x, PHP_EOL; + +$x = new Complex(123.456); +$x->divideInto(789.012); +echo $x, PHP_EOL; + +$x = new Complex(123.456, 78.90); +$x->divideInto(new Complex(-987.654, -32.1)); +echo $x, PHP_EOL; + +$x = new Complex(123.456, 78.90); +$x->divideInto(-987.654); +echo $x, PHP_EOL; + +$x = new Complex(-987.654, -32.1); +$x->divideInto(new Complex(0, 1)); +echo $x, PHP_EOL; + +$x = new Complex(-987.654, -32.1); +$x->divideInto(new Complex(0, -1)); +echo $x, PHP_EOL; diff --git a/vendor/markbaker/complex/examples/testFunctions.php b/vendor/markbaker/complex/examples/testFunctions.php new file mode 100644 index 0000000..bad1c03 --- /dev/null +++ b/vendor/markbaker/complex/examples/testFunctions.php @@ -0,0 +1,52 @@ +getMessage(), PHP_EOL; + } + } + echo PHP_EOL; + } +} diff --git a/vendor/markbaker/complex/examples/testOperations.php b/vendor/markbaker/complex/examples/testOperations.php new file mode 100644 index 0000000..2b7e0ba --- /dev/null +++ b/vendor/markbaker/complex/examples/testOperations.php @@ -0,0 +1,35 @@ + ', $result, PHP_EOL; + +echo PHP_EOL; + +echo 'Subtraction', PHP_EOL; + +$result = Operations::subtract(...$values); +echo '=> ', $result, PHP_EOL; + +echo PHP_EOL; + +echo 'Multiplication', PHP_EOL; + +$result = Operations::multiply(...$values); +echo '=> ', $result, PHP_EOL; diff --git a/vendor/markbaker/complex/license.md b/vendor/markbaker/complex/license.md new file mode 100644 index 0000000..5b4b156 --- /dev/null +++ b/vendor/markbaker/complex/license.md @@ -0,0 +1,25 @@ +The MIT License (MIT) +===================== + +Copyright © `2017` `Mark Baker` + +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. \ No newline at end of file diff --git a/vendor/markbaker/matrix/.github/workflows/main.yaml b/vendor/markbaker/matrix/.github/workflows/main.yaml new file mode 100644 index 0000000..158fcdf --- /dev/null +++ b/vendor/markbaker/matrix/.github/workflows/main.yaml @@ -0,0 +1,118 @@ +name: main +on: [ push, pull_request ] +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + php-version: + - '7.1' + - '7.2' + - '7.3' + - '7.4' + - '8.0' + - '8.1' + + name: PHP ${{ matrix.php-version }} + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP, with composer and extensions + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, simplexml, xml, xmlreader, xmlwriter, zip, zlib + coverage: none + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache composer dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Set composer flags + id: composer-lock + if: ${{ matrix.php-version == '8.0' || matrix.php-version == '8.1' }} + run: | + echo "::set-output name=flags::--ignore-platform-reqs" + + - name: Install dependencies + run: composer install --no-progress --prefer-dist --optimize-autoloader ${{ steps.composer-lock.outputs.flags }} + + - name: Setup problem matchers for PHP + run: echo "::add-matcher::${{ runner.tool_cache }}/php.json" + + - name: Setup problem matchers for PHPUnit + run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + + - name: Test with PHPUnit + run: ./vendor/bin/phpunit + + phpcs: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP, with composer and extensions + uses: shivammathur/setup-php@v2 + with: + php-version: 7.4 + extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, simplexml, xml, xmlreader, xmlwriter, zip, zlib + coverage: none + tools: cs2pr + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache composer dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: composer install --no-progress --prefer-dist --optimize-autoloader + + - name: Code style with PHP_CodeSniffer + run: ./vendor/bin/phpcs -q --report=checkstyle | cs2pr --graceful-warnings --colorize + + coverage: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP, with composer and extensions + uses: shivammathur/setup-php@v2 + with: + php-version: 7.4 + extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, simplexml, xml, xmlreader, xmlwriter, zip, zlib + coverage: pcov + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache composer dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: composer install --no-progress --prefer-dist --optimize-autoloader + + - name: Coverage + run: | + ./vendor/bin/phpunit --coverage-text diff --git a/vendor/markbaker/matrix/README.md b/vendor/markbaker/matrix/README.md new file mode 100644 index 0000000..d0dc914 --- /dev/null +++ b/vendor/markbaker/matrix/README.md @@ -0,0 +1,215 @@ +PHPMatrix +========== + +--- + +PHP Class for handling Matrices + +[![Build Status](https://github.com/MarkBaker/PHPMatrix/workflows/main/badge.svg)](https://github.com/MarkBaker/PHPMatrix/actions) +[![Total Downloads](https://img.shields.io/packagist/dt/markbaker/matrix)](https://packagist.org/packages/markbaker/matrix) +[![Latest Stable Version](https://img.shields.io/github/v/release/MarkBaker/PHPMatrix)](https://packagist.org/packages/markbaker/matrix) +[![License](https://img.shields.io/github/license/MarkBaker/PHPMatrix)](https://packagist.org/packages/markbaker/matrix) + + +[![Matrix Transform](https://imgs.xkcd.com/comics/matrix_transform.png)](https://xkcd.com/184/) + +Matrix Transform + +--- + +This library currently provides the following operations: + + - addition + - direct sum + - subtraction + - multiplication + - division (using [A].[B]-1) + - division by + - division into + +together with functions for + + - adjoint + - antidiagonal + - cofactors + - determinant + - diagonal + - identity + - inverse + - minors + - trace + - transpose + - solve + + Given Matrices A and B, calculate X for A.X = B + +and classes for + + - Decomposition + - LU Decomposition with partial row pivoting, + + such that [P].[A] = [L].[U] and [A] = [P]|.[L].[U] + - QR Decomposition + + such that [A] = [Q].[R] + +## TO DO + + - power() function + - Decomposition + - Cholesky Decomposition + - EigenValue Decomposition + - EigenValues + - EigenVectors + +--- + +# Installation + +```shell +composer require markbaker/matrix:^3.0 +``` + +# Important BC Note + +If you've previously been using procedural calls to functions and operations using this library, then from version 3.0 you should use [MarkBaker/PHPMatrixFunctions](https://github.com/MarkBaker/PHPMatrixFunctions) instead (available on packagist as [markbaker/matrix-functions](https://packagist.org/packages/markbaker/matrix-functions)). + +You'll need to replace `markbaker/matrix`in your `composer.json` file with the new library, but otherwise there should be no difference in the namespacing, or in the way that you have called the Matrix functions in the past, so no actual code changes are required. + +```shell +composer require markbaker/matrix-functions:^1.0 +``` + +You should not reference this library (`markbaker/matrix`) in your `composer.json`, composer wil take care of that for you. + +# Usage + +To create a new Matrix object, provide an array as the constructor argument + +```php +$grid = [ + [16, 3, 2, 13], + [ 5, 10, 11, 8], + [ 9, 6, 7, 12], + [ 4, 15, 14, 1], +]; + +$matrix = new Matrix\Matrix($grid); +``` +The `Builder` class provides helper methods for creating specific matrices, specifically an identity matrix of a specified size; or a matrix of a specified dimensions, with every cell containing a set value. +```php +$matrix = Matrix\Builder::createFilledMatrix(1, 5, 3); +``` +Will create a matrix of 5 rows and 3 columns, filled with a `1` in every cell; while +```php +$matrix = Matrix\Builder::createIdentityMatrix(3); +``` +will create a 3x3 identity matrix. + + +Matrix objects are immutable: whenever you call a method or pass a grid to a function that returns a matrix value, a new Matrix object will be returned, and the original will remain unchanged. This also allows you to chain multiple methods as you would for a fluent interface (as long as they are methods that will return a Matrix result). + +## Performing Mathematical Operations + +To perform mathematical operations with Matrices, you can call the appropriate method against a matrix value, passing other values as arguments + +```php +$matrix1 = new Matrix\Matrix([ + [2, 7, 6], + [9, 5, 1], + [4, 3, 8], +]); +$matrix2 = new Matrix\Matrix([ + [1, 2, 3], + [4, 5, 6], + [7, 8, 9], +]); + +var_dump($matrix1->multiply($matrix2)->toArray()); +``` +or pass all values to the appropriate static method +```php +$matrix1 = new Matrix\Matrix([ + [2, 7, 6], + [9, 5, 1], + [4, 3, 8], +]); +$matrix2 = new Matrix\Matrix([ + [1, 2, 3], + [4, 5, 6], + [7, 8, 9], +]); + +var_dump(Matrix\Operations::multiply($matrix1, $matrix2)->toArray()); +``` +You can pass in the arguments as Matrix objects, or as arrays. + +If you want to perform the same operation against multiple values (e.g. to add three or more matrices), then you can pass multiple arguments to any of the operations. + +## Using functions + +When calling any of the available functions for a matrix value, you can either call the relevant method for the Matrix object +```php +$grid = [ + [16, 3, 2, 13], + [ 5, 10, 11, 8], + [ 9, 6, 7, 12], + [ 4, 15, 14, 1], +]; + +$matrix = new Matrix\Matrix($grid); + +echo $matrix->trace(); +``` +or you can call the static method, passing the Matrix object or array as an argument +```php +$grid = [ + [16, 3, 2, 13], + [ 5, 10, 11, 8], + [ 9, 6, 7, 12], + [ 4, 15, 14, 1], +]; + +$matrix = new Matrix\Matrix($grid); +echo Matrix\Functions::trace($matrix); +``` +```php +$grid = [ + [16, 3, 2, 13], + [ 5, 10, 11, 8], + [ 9, 6, 7, 12], + [ 4, 15, 14, 1], +]; + +echo Matrix\Functions::trace($grid); +``` + +## Decomposition + +The library also provides classes for matrix decomposition. You can access these using +```php +$grid = [ + [1, 2], + [3, 4], +]; + +$matrix = new Matrix\Matrix($grid); + +$decomposition = new Matrix\Decomposition\QR($matrix); +$Q = $decomposition->getQ(); +$R = $decomposition->getR(); +``` + +or alternatively us the `Decomposition` factory, identifying which form of decomposition you want to use +```php +$grid = [ + [1, 2], + [3, 4], +]; + +$matrix = new Matrix\Matrix($grid); + +$decomposition = Matrix\Decomposition\Decomposition::decomposition(Matrix\Decomposition\Decomposition::QR, $matrix); +$Q = $decomposition->getQ(); +$R = $decomposition->getR(); +``` diff --git a/vendor/markbaker/matrix/buildPhar.php b/vendor/markbaker/matrix/buildPhar.php new file mode 100644 index 0000000..e1b8f96 --- /dev/null +++ b/vendor/markbaker/matrix/buildPhar.php @@ -0,0 +1,62 @@ + 'Mark Baker ', + 'Description' => 'PHP Class for working with Matrix numbers', + 'Copyright' => 'Mark Baker (c) 2013-' . date('Y'), + 'Timestamp' => time(), + 'Version' => '0.1.0', + 'Date' => date('Y-m-d') +); + +// cleanup +if (file_exists($pharName)) { + echo "Removed: {$pharName}\n"; + unlink($pharName); +} + +echo "Building phar file...\n"; + +// the phar object +$phar = new Phar($pharName, null, 'Matrix'); +$phar->buildFromDirectory($sourceDir); +$phar->setStub( +<<<'EOT' +getMessage()); + exit(1); + } + + include 'phar://functions/sqrt.php'; + + __HALT_COMPILER(); +EOT +); +$phar->setMetadata($metaData); +$phar->compressFiles(Phar::GZ); + +echo "Complete.\n"; + +exit(); diff --git a/vendor/markbaker/matrix/classes/src/Builder.php b/vendor/markbaker/matrix/classes/src/Builder.php new file mode 100644 index 0000000..161bb68 --- /dev/null +++ b/vendor/markbaker/matrix/classes/src/Builder.php @@ -0,0 +1,70 @@ +toArray(); + + for ($x = 0; $x < $dimensions; ++$x) { + $grid[$x][$x] = 1; + } + + return new Matrix($grid); + } +} diff --git a/vendor/markbaker/matrix/classes/src/Decomposition/Decomposition.php b/vendor/markbaker/matrix/classes/src/Decomposition/Decomposition.php new file mode 100644 index 0000000..f014448 --- /dev/null +++ b/vendor/markbaker/matrix/classes/src/Decomposition/Decomposition.php @@ -0,0 +1,27 @@ +luMatrix = $matrix->toArray(); + $this->rows = $matrix->rows; + $this->columns = $matrix->columns; + + $this->buildPivot(); + } + + /** + * Get lower triangular factor. + * + * @return Matrix Lower triangular factor + */ + public function getL(): Matrix + { + $lower = []; + + $columns = min($this->rows, $this->columns); + for ($row = 0; $row < $this->rows; ++$row) { + for ($column = 0; $column < $columns; ++$column) { + if ($row > $column) { + $lower[$row][$column] = $this->luMatrix[$row][$column]; + } elseif ($row === $column) { + $lower[$row][$column] = 1.0; + } else { + $lower[$row][$column] = 0.0; + } + } + } + + return new Matrix($lower); + } + + /** + * Get upper triangular factor. + * + * @return Matrix Upper triangular factor + */ + public function getU(): Matrix + { + $upper = []; + + $rows = min($this->rows, $this->columns); + for ($row = 0; $row < $rows; ++$row) { + for ($column = 0; $column < $this->columns; ++$column) { + if ($row <= $column) { + $upper[$row][$column] = $this->luMatrix[$row][$column]; + } else { + $upper[$row][$column] = 0.0; + } + } + } + + return new Matrix($upper); + } + + /** + * Return pivot permutation vector. + * + * @return Matrix Pivot matrix + */ + public function getP(): Matrix + { + $pMatrix = []; + + $pivots = $this->pivot; + $pivotCount = count($pivots); + foreach ($pivots as $row => $pivot) { + $pMatrix[$row] = array_fill(0, $pivotCount, 0); + $pMatrix[$row][$pivot] = 1; + } + + return new Matrix($pMatrix); + } + + /** + * Return pivot permutation vector. + * + * @return array Pivot vector + */ + public function getPivot(): array + { + return $this->pivot; + } + + /** + * Is the matrix nonsingular? + * + * @return bool true if U, and hence A, is nonsingular + */ + public function isNonsingular(): bool + { + for ($diagonal = 0; $diagonal < $this->columns; ++$diagonal) { + if ($this->luMatrix[$diagonal][$diagonal] === 0.0) { + return false; + } + } + + return true; + } + + private function buildPivot(): void + { + for ($row = 0; $row < $this->rows; ++$row) { + $this->pivot[$row] = $row; + } + + for ($column = 0; $column < $this->columns; ++$column) { + $luColumn = $this->localisedReferenceColumn($column); + + $this->applyTransformations($column, $luColumn); + + $pivot = $this->findPivot($column, $luColumn); + if ($pivot !== $column) { + $this->pivotExchange($pivot, $column); + } + + $this->computeMultipliers($column); + + unset($luColumn); + } + } + + private function localisedReferenceColumn($column): array + { + $luColumn = []; + + for ($row = 0; $row < $this->rows; ++$row) { + $luColumn[$row] = &$this->luMatrix[$row][$column]; + } + + return $luColumn; + } + + private function applyTransformations($column, array $luColumn): void + { + for ($row = 0; $row < $this->rows; ++$row) { + $luRow = $this->luMatrix[$row]; + // Most of the time is spent in the following dot product. + $kmax = min($row, $column); + $sValue = 0.0; + for ($kValue = 0; $kValue < $kmax; ++$kValue) { + $sValue += $luRow[$kValue] * $luColumn[$kValue]; + } + $luRow[$column] = $luColumn[$row] -= $sValue; + } + } + + private function findPivot($column, array $luColumn): int + { + $pivot = $column; + for ($row = $column + 1; $row < $this->rows; ++$row) { + if (abs($luColumn[$row]) > abs($luColumn[$pivot])) { + $pivot = $row; + } + } + + return $pivot; + } + + private function pivotExchange($pivot, $column): void + { + for ($kValue = 0; $kValue < $this->columns; ++$kValue) { + $tValue = $this->luMatrix[$pivot][$kValue]; + $this->luMatrix[$pivot][$kValue] = $this->luMatrix[$column][$kValue]; + $this->luMatrix[$column][$kValue] = $tValue; + } + + $lValue = $this->pivot[$pivot]; + $this->pivot[$pivot] = $this->pivot[$column]; + $this->pivot[$column] = $lValue; + } + + private function computeMultipliers($diagonal): void + { + if (($diagonal < $this->rows) && ($this->luMatrix[$diagonal][$diagonal] != 0.0)) { + for ($row = $diagonal + 1; $row < $this->rows; ++$row) { + $this->luMatrix[$row][$diagonal] /= $this->luMatrix[$diagonal][$diagonal]; + } + } + } + + private function pivotB(Matrix $B): array + { + $X = []; + foreach ($this->pivot as $rowId) { + $row = $B->getRows($rowId + 1)->toArray(); + $X[] = array_pop($row); + } + + return $X; + } + + /** + * Solve A*X = B. + * + * @param Matrix $B a Matrix with as many rows as A and any number of columns + * + * @throws Exception + * + * @return Matrix X so that L*U*X = B(piv,:) + */ + public function solve(Matrix $B): Matrix + { + if ($B->rows !== $this->rows) { + throw new Exception('Matrix row dimensions are not equal'); + } + + if ($this->rows !== $this->columns) { + throw new Exception('LU solve() only works on square matrices'); + } + + if (!$this->isNonsingular()) { + throw new Exception('Can only perform operation on singular matrix'); + } + + // Copy right hand side with pivoting + $nx = $B->columns; + $X = $this->pivotB($B); + + // Solve L*Y = B(piv,:) + for ($k = 0; $k < $this->columns; ++$k) { + for ($i = $k + 1; $i < $this->columns; ++$i) { + for ($j = 0; $j < $nx; ++$j) { + $X[$i][$j] -= $X[$k][$j] * $this->luMatrix[$i][$k]; + } + } + } + + // Solve U*X = Y; + for ($k = $this->columns - 1; $k >= 0; --$k) { + for ($j = 0; $j < $nx; ++$j) { + $X[$k][$j] /= $this->luMatrix[$k][$k]; + } + for ($i = 0; $i < $k; ++$i) { + for ($j = 0; $j < $nx; ++$j) { + $X[$i][$j] -= $X[$k][$j] * $this->luMatrix[$i][$k]; + } + } + } + + return new Matrix($X); + } +} diff --git a/vendor/markbaker/matrix/classes/src/Decomposition/QR.php b/vendor/markbaker/matrix/classes/src/Decomposition/QR.php new file mode 100644 index 0000000..4b6106f --- /dev/null +++ b/vendor/markbaker/matrix/classes/src/Decomposition/QR.php @@ -0,0 +1,191 @@ +qrMatrix = $matrix->toArray(); + $this->rows = $matrix->rows; + $this->columns = $matrix->columns; + + $this->decompose(); + } + + public function getHouseholdVectors(): Matrix + { + $householdVectors = []; + for ($row = 0; $row < $this->rows; ++$row) { + for ($column = 0; $column < $this->columns; ++$column) { + if ($row >= $column) { + $householdVectors[$row][$column] = $this->qrMatrix[$row][$column]; + } else { + $householdVectors[$row][$column] = 0.0; + } + } + } + + return new Matrix($householdVectors); + } + + public function getQ(): Matrix + { + $qGrid = []; + + $rowCount = $this->rows; + for ($k = $this->columns - 1; $k >= 0; --$k) { + for ($i = 0; $i < $this->rows; ++$i) { + $qGrid[$i][$k] = 0.0; + } + $qGrid[$k][$k] = 1.0; + if ($this->columns > $this->rows) { + $qGrid = array_slice($qGrid, 0, $this->rows); + } + + for ($j = $k; $j < $this->columns; ++$j) { + if (isset($this->qrMatrix[$k], $this->qrMatrix[$k][$k]) && $this->qrMatrix[$k][$k] != 0.0) { + $s = 0.0; + for ($i = $k; $i < $this->rows; ++$i) { + $s += $this->qrMatrix[$i][$k] * $qGrid[$i][$j]; + } + $s = -$s / $this->qrMatrix[$k][$k]; + for ($i = $k; $i < $this->rows; ++$i) { + $qGrid[$i][$j] += $s * $this->qrMatrix[$i][$k]; + } + } + } + } + + array_walk( + $qGrid, + function (&$row) use ($rowCount) { + $row = array_reverse($row); + $row = array_slice($row, 0, $rowCount); + } + ); + + return new Matrix($qGrid); + } + + public function getR(): Matrix + { + $rGrid = []; + + for ($row = 0; $row < $this->columns; ++$row) { + for ($column = 0; $column < $this->columns; ++$column) { + if ($row < $column) { + $rGrid[$row][$column] = $this->qrMatrix[$row][$column] ?? 0.0; + } elseif ($row === $column) { + $rGrid[$row][$column] = $this->rDiagonal[$row] ?? 0.0; + } else { + $rGrid[$row][$column] = 0.0; + } + } + } + + if ($this->columns > $this->rows) { + $rGrid = array_slice($rGrid, 0, $this->rows); + } + + return new Matrix($rGrid); + } + + private function hypo($a, $b): float + { + if (abs($a) > abs($b)) { + $r = $b / $a; + $r = abs($a) * sqrt(1 + $r * $r); + } elseif ($b != 0.0) { + $r = $a / $b; + $r = abs($b) * sqrt(1 + $r * $r); + } else { + $r = 0.0; + } + + return $r; + } + + /** + * QR Decomposition computed by Householder reflections. + */ + private function decompose(): void + { + for ($k = 0; $k < $this->columns; ++$k) { + // Compute 2-norm of k-th column without under/overflow. + $norm = 0.0; + for ($i = $k; $i < $this->rows; ++$i) { + $norm = $this->hypo($norm, $this->qrMatrix[$i][$k]); + } + if ($norm != 0.0) { + // Form k-th Householder vector. + if ($this->qrMatrix[$k][$k] < 0.0) { + $norm = -$norm; + } + for ($i = $k; $i < $this->rows; ++$i) { + $this->qrMatrix[$i][$k] /= $norm; + } + $this->qrMatrix[$k][$k] += 1.0; + // Apply transformation to remaining columns. + for ($j = $k + 1; $j < $this->columns; ++$j) { + $s = 0.0; + for ($i = $k; $i < $this->rows; ++$i) { + $s += $this->qrMatrix[$i][$k] * $this->qrMatrix[$i][$j]; + } + $s = -$s / $this->qrMatrix[$k][$k]; + for ($i = $k; $i < $this->rows; ++$i) { + $this->qrMatrix[$i][$j] += $s * $this->qrMatrix[$i][$k]; + } + } + } + $this->rDiagonal[$k] = -$norm; + } + } + + public function isFullRank(): bool + { + for ($j = 0; $j < $this->columns; ++$j) { + if ($this->rDiagonal[$j] == 0.0) { + return false; + } + } + + return true; + } + + /** + * Least squares solution of A*X = B. + * + * @param Matrix $B a Matrix with as many rows as A and any number of columns + * + * @throws Exception + * + * @return Matrix matrix that minimizes the two norm of Q*R*X-B + */ + public function solve(Matrix $B): Matrix + { + if ($B->rows !== $this->rows) { + throw new Exception('Matrix row dimensions are not equal'); + } + + if (!$this->isFullRank()) { + throw new Exception('Can only perform this operation on a full-rank matrix'); + } + + // Compute Y = transpose(Q)*B + $Y = $this->getQ()->transpose() + ->multiply($B); + // Solve R*X = Y; + return $this->getR()->inverse() + ->multiply($Y); + } +} diff --git a/vendor/markbaker/matrix/classes/src/Div0Exception.php b/vendor/markbaker/matrix/classes/src/Div0Exception.php new file mode 100644 index 0000000..eba28f8 --- /dev/null +++ b/vendor/markbaker/matrix/classes/src/Div0Exception.php @@ -0,0 +1,13 @@ +isSquare()) { + throw new Exception('Adjoint can only be calculated for a square matrix'); + } + + return self::getAdjoint($matrix); + } + + /** + * Calculate the cofactors of the matrix + * + * @param Matrix $matrix The matrix whose cofactors we wish to calculate + * @return Matrix + * + * @throws Exception + */ + private static function getCofactors(Matrix $matrix) + { + $cofactors = self::getMinors($matrix); + $dimensions = $matrix->rows; + + $cof = 1; + for ($i = 0; $i < $dimensions; ++$i) { + $cofs = $cof; + for ($j = 0; $j < $dimensions; ++$j) { + $cofactors[$i][$j] *= $cofs; + $cofs = -$cofs; + } + $cof = -$cof; + } + + return new Matrix($cofactors); + } + + /** + * Return the cofactors of this matrix + * + * @param Matrix|array $matrix The matrix whose cofactors we wish to calculate + * @return Matrix + * + * @throws Exception + */ + public static function cofactors($matrix) + { + $matrix = self::validateMatrix($matrix); + + if (!$matrix->isSquare()) { + throw new Exception('Cofactors can only be calculated for a square matrix'); + } + + return self::getCofactors($matrix); + } + + /** + * @param Matrix $matrix + * @param int $row + * @param int $column + * @return float + * @throws Exception + */ + private static function getDeterminantSegment(Matrix $matrix, $row, $column) + { + $tmpMatrix = $matrix->toArray(); + unset($tmpMatrix[$row]); + array_walk( + $tmpMatrix, + function (&$row) use ($column) { + unset($row[$column]); + } + ); + + return self::getDeterminant(new Matrix($tmpMatrix)); + } + + /** + * Calculate the determinant of the matrix + * + * @param Matrix $matrix The matrix whose determinant we wish to calculate + * @return float + * + * @throws Exception + */ + private static function getDeterminant(Matrix $matrix) + { + $dimensions = $matrix->rows; + $determinant = 0; + + switch ($dimensions) { + case 1: + $determinant = $matrix->getValue(1, 1); + break; + case 2: + $determinant = $matrix->getValue(1, 1) * $matrix->getValue(2, 2) - + $matrix->getValue(1, 2) * $matrix->getValue(2, 1); + break; + default: + for ($i = 1; $i <= $dimensions; ++$i) { + $det = $matrix->getValue(1, $i) * self::getDeterminantSegment($matrix, 0, $i - 1); + if (($i % 2) == 0) { + $determinant -= $det; + } else { + $determinant += $det; + } + } + break; + } + + return $determinant; + } + + /** + * Return the determinant of this matrix + * + * @param Matrix|array $matrix The matrix whose determinant we wish to calculate + * @return float + * @throws Exception + **/ + public static function determinant($matrix) + { + $matrix = self::validateMatrix($matrix); + + if (!$matrix->isSquare()) { + throw new Exception('Determinant can only be calculated for a square matrix'); + } + + return self::getDeterminant($matrix); + } + + /** + * Return the diagonal of this matrix + * + * @param Matrix|array $matrix The matrix whose diagonal we wish to calculate + * @return Matrix + * @throws Exception + **/ + public static function diagonal($matrix) + { + $matrix = self::validateMatrix($matrix); + + if (!$matrix->isSquare()) { + throw new Exception('Diagonal can only be extracted from a square matrix'); + } + + $dimensions = $matrix->rows; + $grid = Builder::createFilledMatrix(0, $dimensions, $dimensions) + ->toArray(); + + for ($i = 0; $i < $dimensions; ++$i) { + $grid[$i][$i] = $matrix->getValue($i + 1, $i + 1); + } + + return new Matrix($grid); + } + + /** + * Return the antidiagonal of this matrix + * + * @param Matrix|array $matrix The matrix whose antidiagonal we wish to calculate + * @return Matrix + * @throws Exception + **/ + public static function antidiagonal($matrix) + { + $matrix = self::validateMatrix($matrix); + + if (!$matrix->isSquare()) { + throw new Exception('Anti-Diagonal can only be extracted from a square matrix'); + } + + $dimensions = $matrix->rows; + $grid = Builder::createFilledMatrix(0, $dimensions, $dimensions) + ->toArray(); + + for ($i = 0; $i < $dimensions; ++$i) { + $grid[$i][$dimensions - $i - 1] = $matrix->getValue($i + 1, $dimensions - $i); + } + + return new Matrix($grid); + } + + /** + * Return the identity matrix + * The identity matrix, or sometimes ambiguously called a unit matrix, of size n is the n × n square matrix + * with ones on the main diagonal and zeros elsewhere + * + * @param Matrix|array $matrix The matrix whose identity we wish to calculate + * @return Matrix + * @throws Exception + **/ + public static function identity($matrix) + { + $matrix = self::validateMatrix($matrix); + + if (!$matrix->isSquare()) { + throw new Exception('Identity can only be created for a square matrix'); + } + + $dimensions = $matrix->rows; + + return Builder::createIdentityMatrix($dimensions); + } + + /** + * Return the inverse of this matrix + * + * @param Matrix|array $matrix The matrix whose inverse we wish to calculate + * @return Matrix + * @throws Exception + **/ + public static function inverse($matrix, string $type = 'inverse') + { + $matrix = self::validateMatrix($matrix); + + if (!$matrix->isSquare()) { + throw new Exception(ucfirst($type) . ' can only be calculated for a square matrix'); + } + + $determinant = self::getDeterminant($matrix); + if ($determinant == 0.0) { + throw new Div0Exception(ucfirst($type) . ' can only be calculated for a matrix with a non-zero determinant'); + } + + if ($matrix->rows == 1) { + return new Matrix([[1 / $matrix->getValue(1, 1)]]); + } + + return self::getAdjoint($matrix) + ->multiply(1 / $determinant); + } + + /** + * Calculate the minors of the matrix + * + * @param Matrix $matrix The matrix whose minors we wish to calculate + * @return array[] + * + * @throws Exception + */ + protected static function getMinors(Matrix $matrix) + { + $minors = $matrix->toArray(); + $dimensions = $matrix->rows; + if ($dimensions == 1) { + return $minors; + } + + for ($i = 0; $i < $dimensions; ++$i) { + for ($j = 0; $j < $dimensions; ++$j) { + $minors[$i][$j] = self::getDeterminantSegment($matrix, $i, $j); + } + } + + return $minors; + } + + /** + * Return the minors of the matrix + * The minor of a matrix A is the determinant of some smaller square matrix, cut down from A by removing one or + * more of its rows or columns. + * Minors obtained by removing just one row and one column from square matrices (first minors) are required for + * calculating matrix cofactors, which in turn are useful for computing both the determinant and inverse of + * square matrices. + * + * @param Matrix|array $matrix The matrix whose minors we wish to calculate + * @return Matrix + * @throws Exception + **/ + public static function minors($matrix) + { + $matrix = self::validateMatrix($matrix); + + if (!$matrix->isSquare()) { + throw new Exception('Minors can only be calculated for a square matrix'); + } + + return new Matrix(self::getMinors($matrix)); + } + + /** + * Return the trace of this matrix + * The trace is defined as the sum of the elements on the main diagonal (the diagonal from the upper left to the lower right) + * of the matrix + * + * @param Matrix|array $matrix The matrix whose trace we wish to calculate + * @return float + * @throws Exception + **/ + public static function trace($matrix) + { + $matrix = self::validateMatrix($matrix); + + if (!$matrix->isSquare()) { + throw new Exception('Trace can only be extracted from a square matrix'); + } + + $dimensions = $matrix->rows; + $result = 0; + for ($i = 1; $i <= $dimensions; ++$i) { + $result += $matrix->getValue($i, $i); + } + + return $result; + } + + /** + * Return the transpose of this matrix + * + * @param Matrix|\a $matrix The matrix whose transpose we wish to calculate + * @return Matrix + **/ + public static function transpose($matrix) + { + $matrix = self::validateMatrix($matrix); + + $array = array_values(array_merge([null], $matrix->toArray())); + $grid = call_user_func_array( + 'array_map', + $array + ); + + return new Matrix($grid); + } +} diff --git a/vendor/markbaker/matrix/classes/src/Matrix.php b/vendor/markbaker/matrix/classes/src/Matrix.php new file mode 100644 index 0000000..95f55e7 --- /dev/null +++ b/vendor/markbaker/matrix/classes/src/Matrix.php @@ -0,0 +1,423 @@ +buildFromArray(array_values($grid)); + } + + /* + * Create a new Matrix object from an array of values + * + * @param array $grid + */ + protected function buildFromArray(array $grid): void + { + $this->rows = count($grid); + $columns = array_reduce( + $grid, + function ($carry, $value) { + return max($carry, is_array($value) ? count($value) : 1); + } + ); + $this->columns = $columns; + + array_walk( + $grid, + function (&$value) use ($columns) { + if (!is_array($value)) { + $value = [$value]; + } + $value = array_pad(array_values($value), $columns, null); + } + ); + + $this->grid = $grid; + } + + /** + * Validate that a row number is a positive integer + * + * @param int $row + * @return int + * @throws Exception + */ + public static function validateRow(int $row): int + { + if ((!is_numeric($row)) || (intval($row) < 1)) { + throw new Exception('Invalid Row'); + } + + return (int)$row; + } + + /** + * Validate that a column number is a positive integer + * + * @param int $column + * @return int + * @throws Exception + */ + public static function validateColumn(int $column): int + { + if ((!is_numeric($column)) || (intval($column) < 1)) { + throw new Exception('Invalid Column'); + } + + return (int)$column; + } + + /** + * Validate that a row number falls within the set of rows for this matrix + * + * @param int $row + * @return int + * @throws Exception + */ + protected function validateRowInRange(int $row): int + { + $row = static::validateRow($row); + if ($row > $this->rows) { + throw new Exception('Requested Row exceeds matrix size'); + } + + return $row; + } + + /** + * Validate that a column number falls within the set of columns for this matrix + * + * @param int $column + * @return int + * @throws Exception + */ + protected function validateColumnInRange(int $column): int + { + $column = static::validateColumn($column); + if ($column > $this->columns) { + throw new Exception('Requested Column exceeds matrix size'); + } + + return $column; + } + + /** + * Return a new matrix as a subset of rows from this matrix, starting at row number $row, and $rowCount rows + * A $rowCount value of 0 will return all rows of the matrix from $row + * A negative $rowCount value will return rows until that many rows from the end of the matrix + * + * Note that row numbers start from 1, not from 0 + * + * @param int $row + * @param int $rowCount + * @return static + * @throws Exception + */ + public function getRows(int $row, int $rowCount = 1): Matrix + { + $row = $this->validateRowInRange($row); + if ($rowCount === 0) { + $rowCount = $this->rows - $row + 1; + } + + return new static(array_slice($this->grid, $row - 1, (int)$rowCount)); + } + + /** + * Return a new matrix as a subset of columns from this matrix, starting at column number $column, and $columnCount columns + * A $columnCount value of 0 will return all columns of the matrix from $column + * A negative $columnCount value will return columns until that many columns from the end of the matrix + * + * Note that column numbers start from 1, not from 0 + * + * @param int $column + * @param int $columnCount + * @return Matrix + * @throws Exception + */ + public function getColumns(int $column, int $columnCount = 1): Matrix + { + $column = $this->validateColumnInRange($column); + if ($columnCount < 1) { + $columnCount = $this->columns + $columnCount - $column + 1; + } + + $grid = []; + for ($i = $column - 1; $i < $column + $columnCount - 1; ++$i) { + $grid[] = array_column($this->grid, $i); + } + + return (new static($grid))->transpose(); + } + + /** + * Return a new matrix as a subset of rows from this matrix, dropping rows starting at row number $row, + * and $rowCount rows + * A negative $rowCount value will drop rows until that many rows from the end of the matrix + * A $rowCount value of 0 will remove all rows of the matrix from $row + * + * Note that row numbers start from 1, not from 0 + * + * @param int $row + * @param int $rowCount + * @return static + * @throws Exception + */ + public function dropRows(int $row, int $rowCount = 1): Matrix + { + $this->validateRowInRange($row); + if ($rowCount === 0) { + $rowCount = $this->rows - $row + 1; + } + + $grid = $this->grid; + array_splice($grid, $row - 1, (int)$rowCount); + + return new static($grid); + } + + /** + * Return a new matrix as a subset of columns from this matrix, dropping columns starting at column number $column, + * and $columnCount columns + * A negative $columnCount value will drop columns until that many columns from the end of the matrix + * A $columnCount value of 0 will remove all columns of the matrix from $column + * + * Note that column numbers start from 1, not from 0 + * + * @param int $column + * @param int $columnCount + * @return static + * @throws Exception + */ + public function dropColumns(int $column, int $columnCount = 1): Matrix + { + $this->validateColumnInRange($column); + if ($columnCount < 1) { + $columnCount = $this->columns + $columnCount - $column + 1; + } + + $grid = $this->grid; + array_walk( + $grid, + function (&$row) use ($column, $columnCount) { + array_splice($row, $column - 1, (int)$columnCount); + } + ); + + return new static($grid); + } + + /** + * Return a value from this matrix, from the "cell" identified by the row and column numbers + * Note that row and column numbers start from 1, not from 0 + * + * @param int $row + * @param int $column + * @return mixed + * @throws Exception + */ + public function getValue(int $row, int $column) + { + $row = $this->validateRowInRange($row); + $column = $this->validateColumnInRange($column); + + return $this->grid[$row - 1][$column - 1]; + } + + /** + * Returns a Generator that will yield each row of the matrix in turn as a vector matrix + * or the value of each cell if the matrix is a column vector + * + * @return Generator|Matrix[]|mixed[] + */ + public function rows(): Generator + { + foreach ($this->grid as $i => $row) { + yield $i + 1 => ($this->columns == 1) + ? $row[0] + : new static([$row]); + } + } + + /** + * Returns a Generator that will yield each column of the matrix in turn as a vector matrix + * or the value of each cell if the matrix is a row vector + * + * @return Generator|Matrix[]|mixed[] + */ + public function columns(): Generator + { + for ($i = 0; $i < $this->columns; ++$i) { + yield $i + 1 => ($this->rows == 1) + ? $this->grid[0][$i] + : new static(array_column($this->grid, $i)); + } + } + + /** + * Identify if the row and column dimensions of this matrix are equal, + * i.e. if it is a "square" matrix + * + * @return bool + */ + public function isSquare(): bool + { + return $this->rows === $this->columns; + } + + /** + * Identify if this matrix is a vector + * i.e. if it comprises only a single row or a single column + * + * @return bool + */ + public function isVector(): bool + { + return $this->rows === 1 || $this->columns === 1; + } + + /** + * Return the matrix as a 2-dimensional array + * + * @return array + */ + public function toArray(): array + { + return $this->grid; + } + + /** + * Solve A*X = B. + * + * @param Matrix $B Right hand side + * + * @throws Exception + * + * @return Matrix ... Solution if A is square, least squares solution otherwise + */ + public function solve(Matrix $B): Matrix + { + if ($this->columns === $this->rows) { + return (new LU($this))->solve($B); + } + + return (new QR($this))->solve($B); + } + + protected static $getters = [ + 'rows', + 'columns', + ]; + + /** + * Access specific properties as read-only (no setters) + * + * @param string $propertyName + * @return mixed + * @throws Exception + */ + public function __get(string $propertyName) + { + $propertyName = strtolower($propertyName); + + // Test for function calls + if (in_array($propertyName, self::$getters)) { + return $this->$propertyName; + } + + throw new Exception('Property does not exist'); + } + + protected static $functions = [ + 'adjoint', + 'antidiagonal', + 'cofactors', + 'determinant', + 'diagonal', + 'identity', + 'inverse', + 'minors', + 'trace', + 'transpose', + ]; + + protected static $operations = [ + 'add', + 'subtract', + 'multiply', + 'divideby', + 'divideinto', + 'directsum', + ]; + + /** + * Returns the result of the function call or operation + * + * @param string $functionName + * @param mixed[] $arguments + * @return Matrix|float + * @throws Exception + */ + public function __call(string $functionName, $arguments) + { + $functionName = strtolower(str_replace('_', '', $functionName)); + + // Test for function calls + if (in_array($functionName, self::$functions, true)) { + return Functions::$functionName($this, ...$arguments); + } + // Test for operation calls + if (in_array($functionName, self::$operations, true)) { + return Operations::$functionName($this, ...$arguments); + } + throw new Exception('Function or Operation does not exist'); + } +} diff --git a/vendor/markbaker/matrix/classes/src/Operations.php b/vendor/markbaker/matrix/classes/src/Operations.php new file mode 100644 index 0000000..e3d88d6 --- /dev/null +++ b/vendor/markbaker/matrix/classes/src/Operations.php @@ -0,0 +1,157 @@ +execute($matrix); + } + + return $result->result(); + } + + public static function directsum(...$matrixValues): Matrix + { + if (count($matrixValues) < 2) { + throw new Exception('DirectSum operation requires at least 2 arguments'); + } + + $matrix = array_shift($matrixValues); + + if (is_array($matrix)) { + $matrix = new Matrix($matrix); + } + if (!$matrix instanceof Matrix) { + throw new Exception('DirectSum arguments must be Matrix or array'); + } + + $result = new DirectSum($matrix); + + foreach ($matrixValues as $matrix) { + $result->execute($matrix); + } + + return $result->result(); + } + + public static function divideby(...$matrixValues): Matrix + { + if (count($matrixValues) < 2) { + throw new Exception('Division operation requires at least 2 arguments'); + } + + $matrix = array_shift($matrixValues); + + if (is_array($matrix)) { + $matrix = new Matrix($matrix); + } + if (!$matrix instanceof Matrix) { + throw new Exception('Division arguments must be Matrix or array'); + } + + $result = new Division($matrix); + + foreach ($matrixValues as $matrix) { + $result->execute($matrix); + } + + return $result->result(); + } + + public static function divideinto(...$matrixValues): Matrix + { + if (count($matrixValues) < 2) { + throw new Exception('Division operation requires at least 2 arguments'); + } + + $matrix = array_pop($matrixValues); + $matrixValues = array_reverse($matrixValues); + + if (is_array($matrix)) { + $matrix = new Matrix($matrix); + } + if (!$matrix instanceof Matrix) { + throw new Exception('Division arguments must be Matrix or array'); + } + + $result = new Division($matrix); + + foreach ($matrixValues as $matrix) { + $result->execute($matrix); + } + + return $result->result(); + } + + public static function multiply(...$matrixValues): Matrix + { + if (count($matrixValues) < 2) { + throw new Exception('Multiplication operation requires at least 2 arguments'); + } + + $matrix = array_shift($matrixValues); + + if (is_array($matrix)) { + $matrix = new Matrix($matrix); + } + if (!$matrix instanceof Matrix) { + throw new Exception('Multiplication arguments must be Matrix or array'); + } + + $result = new Multiplication($matrix); + + foreach ($matrixValues as $matrix) { + $result->execute($matrix); + } + + return $result->result(); + } + + public static function subtract(...$matrixValues): Matrix + { + if (count($matrixValues) < 2) { + throw new Exception('Subtraction operation requires at least 2 arguments'); + } + + $matrix = array_shift($matrixValues); + + if (is_array($matrix)) { + $matrix = new Matrix($matrix); + } + if (!$matrix instanceof Matrix) { + throw new Exception('Subtraction arguments must be Matrix or array'); + } + + $result = new Subtraction($matrix); + + foreach ($matrixValues as $matrix) { + $result->execute($matrix); + } + + return $result->result(); + } +} diff --git a/vendor/markbaker/matrix/classes/src/Operators/Addition.php b/vendor/markbaker/matrix/classes/src/Operators/Addition.php new file mode 100644 index 0000000..543f56e --- /dev/null +++ b/vendor/markbaker/matrix/classes/src/Operators/Addition.php @@ -0,0 +1,68 @@ +addMatrix($value); + } elseif (is_numeric($value)) { + return $this->addScalar($value); + } + + throw new Exception('Invalid argument for addition'); + } + + /** + * Execute the addition for a scalar + * + * @param mixed $value The numeric value to add to the current base value + * @return $this The operation object, allowing multiple additions to be chained + **/ + protected function addScalar($value): Operator + { + for ($row = 0; $row < $this->rows; ++$row) { + for ($column = 0; $column < $this->columns; ++$column) { + $this->matrix[$row][$column] += $value; + } + } + + return $this; + } + + /** + * Execute the addition for a matrix + * + * @param Matrix $value The numeric value to add to the current base value + * @return $this The operation object, allowing multiple additions to be chained + * @throws Exception If the provided argument is not appropriate for the operation + **/ + protected function addMatrix(Matrix $value): Operator + { + $this->validateMatchingDimensions($value); + + for ($row = 0; $row < $this->rows; ++$row) { + for ($column = 0; $column < $this->columns; ++$column) { + $this->matrix[$row][$column] += $value->getValue($row + 1, $column + 1); + } + } + + return $this; + } +} diff --git a/vendor/markbaker/matrix/classes/src/Operators/DirectSum.php b/vendor/markbaker/matrix/classes/src/Operators/DirectSum.php new file mode 100644 index 0000000..cc51ef9 --- /dev/null +++ b/vendor/markbaker/matrix/classes/src/Operators/DirectSum.php @@ -0,0 +1,64 @@ +directSumMatrix($value); + } + + throw new Exception('Invalid argument for addition'); + } + + /** + * Execute the direct sum for a matrix + * + * @param Matrix $value The numeric value to concatenate/direct sum with the current base value + * @return $this The operation object, allowing multiple additions to be chained + **/ + private function directSumMatrix($value): Operator + { + $originalColumnCount = count($this->matrix[0]); + $originalRowCount = count($this->matrix); + $valColumnCount = $value->columns; + $valRowCount = $value->rows; + $value = $value->toArray(); + + for ($row = 0; $row < $this->rows; ++$row) { + $this->matrix[$row] = array_merge($this->matrix[$row], array_fill(0, $valColumnCount, 0)); + } + + $this->matrix = array_merge( + $this->matrix, + array_fill(0, $valRowCount, array_fill(0, $originalColumnCount, 0)) + ); + + for ($row = $originalRowCount; $row < $originalRowCount + $valRowCount; ++$row) { + array_splice( + $this->matrix[$row], + $originalColumnCount, + $valColumnCount, + $value[$row - $originalRowCount] + ); + } + + return $this; + } +} diff --git a/vendor/markbaker/matrix/classes/src/Operators/Division.php b/vendor/markbaker/matrix/classes/src/Operators/Division.php new file mode 100644 index 0000000..dbfec9d --- /dev/null +++ b/vendor/markbaker/matrix/classes/src/Operators/Division.php @@ -0,0 +1,35 @@ +multiplyMatrix($value, $type); + } elseif (is_numeric($value)) { + return $this->multiplyScalar(1 / $value, $type); + } + + throw new Exception('Invalid argument for division'); + } +} diff --git a/vendor/markbaker/matrix/classes/src/Operators/Multiplication.php b/vendor/markbaker/matrix/classes/src/Operators/Multiplication.php new file mode 100644 index 0000000..0761e46 --- /dev/null +++ b/vendor/markbaker/matrix/classes/src/Operators/Multiplication.php @@ -0,0 +1,86 @@ +multiplyMatrix($value, $type); + } elseif (is_numeric($value)) { + return $this->multiplyScalar($value, $type); + } + + throw new Exception("Invalid argument for $type"); + } + + /** + * Execute the multiplication for a scalar + * + * @param mixed $value The numeric value to multiply with the current base value + * @return $this The operation object, allowing multiple mutiplications to be chained + **/ + protected function multiplyScalar($value, string $type = 'multiplication'): Operator + { + try { + for ($row = 0; $row < $this->rows; ++$row) { + for ($column = 0; $column < $this->columns; ++$column) { + $this->matrix[$row][$column] *= $value; + } + } + } catch (Throwable $e) { + throw new Exception("Invalid argument for $type"); + } + + return $this; + } + + /** + * Execute the multiplication for a matrix + * + * @param Matrix $value The numeric value to multiply with the current base value + * @return $this The operation object, allowing multiple mutiplications to be chained + * @throws Exception If the provided argument is not appropriate for the operation + **/ + protected function multiplyMatrix(Matrix $value, string $type = 'multiplication'): Operator + { + $this->validateReflectingDimensions($value); + + $newRows = $this->rows; + $newColumns = $value->columns; + $matrix = Builder::createFilledMatrix(0, $newRows, $newColumns) + ->toArray(); + try { + for ($row = 0; $row < $newRows; ++$row) { + for ($column = 0; $column < $newColumns; ++$column) { + $columnData = $value->getColumns($column + 1)->toArray(); + foreach ($this->matrix[$row] as $key => $valueData) { + $matrix[$row][$column] += $valueData * $columnData[$key][0]; + } + } + } + } catch (Throwable $e) { + throw new Exception("Invalid argument for $type"); + } + $this->matrix = $matrix; + + return $this; + } +} diff --git a/vendor/markbaker/matrix/classes/src/Operators/Operator.php b/vendor/markbaker/matrix/classes/src/Operators/Operator.php new file mode 100644 index 0000000..39e36c6 --- /dev/null +++ b/vendor/markbaker/matrix/classes/src/Operators/Operator.php @@ -0,0 +1,78 @@ +rows = $matrix->rows; + $this->columns = $matrix->columns; + $this->matrix = $matrix->toArray(); + } + + /** + * Compare the dimensions of the matrices being operated on to see if they are valid for addition/subtraction + * + * @param Matrix $matrix The second Matrix object on which the operation will be performed + * @throws Exception + */ + protected function validateMatchingDimensions(Matrix $matrix): void + { + if (($this->rows != $matrix->rows) || ($this->columns != $matrix->columns)) { + throw new Exception('Matrices have mismatched dimensions'); + } + } + + /** + * Compare the dimensions of the matrices being operated on to see if they are valid for multiplication/division + * + * @param Matrix $matrix The second Matrix object on which the operation will be performed + * @throws Exception + */ + protected function validateReflectingDimensions(Matrix $matrix): void + { + if ($this->columns != $matrix->rows) { + throw new Exception('Matrices have mismatched dimensions'); + } + } + + /** + * Return the result of the operation + * + * @return Matrix + */ + public function result(): Matrix + { + return new Matrix($this->matrix); + } +} diff --git a/vendor/markbaker/matrix/classes/src/Operators/Subtraction.php b/vendor/markbaker/matrix/classes/src/Operators/Subtraction.php new file mode 100644 index 0000000..b7e14fa --- /dev/null +++ b/vendor/markbaker/matrix/classes/src/Operators/Subtraction.php @@ -0,0 +1,68 @@ +subtractMatrix($value); + } elseif (is_numeric($value)) { + return $this->subtractScalar($value); + } + + throw new Exception('Invalid argument for subtraction'); + } + + /** + * Execute the subtraction for a scalar + * + * @param mixed $value The numeric value to subtracted from the current base value + * @return $this The operation object, allowing multiple additions to be chained + **/ + protected function subtractScalar($value): Operator + { + for ($row = 0; $row < $this->rows; ++$row) { + for ($column = 0; $column < $this->columns; ++$column) { + $this->matrix[$row][$column] -= $value; + } + } + + return $this; + } + + /** + * Execute the subtraction for a matrix + * + * @param Matrix $value The numeric value to subtract from the current base value + * @return $this The operation object, allowing multiple subtractions to be chained + * @throws Exception If the provided argument is not appropriate for the operation + **/ + protected function subtractMatrix(Matrix $value): Operator + { + $this->validateMatchingDimensions($value); + + for ($row = 0; $row < $this->rows; ++$row) { + for ($column = 0; $column < $this->columns; ++$column) { + $this->matrix[$row][$column] -= $value->getValue($row + 1, $column + 1); + } + } + + return $this; + } +} diff --git a/vendor/markbaker/matrix/composer.json b/vendor/markbaker/matrix/composer.json new file mode 100644 index 0000000..fa90f56 --- /dev/null +++ b/vendor/markbaker/matrix/composer.json @@ -0,0 +1,47 @@ +{ + "name": "markbaker/matrix", + "type": "library", + "description": "PHP Class for working with matrices", + "keywords": ["matrix", "vector", "mathematics"], + "homepage": "https://github.com/MarkBaker/PHPMatrix", + "license": "MIT", + "authors": [ + { + "name": "Mark Baker", + "email": "mark@demon-angel.eu" + } + ], + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.3", + "phpdocumentor/phpdocumentor": "2.*", + "phpmd/phpmd": "2.*", + "sebastian/phpcpd": "^4.0", + "phploc/phploc": "^4.0", + "squizlabs/php_codesniffer": "^3.4", + "phpcompatibility/php-compatibility": "^9.0", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0" + }, + "autoload": { + "psr-4": { + "Matrix\\": "classes/src/" + } + }, + "autoload-dev": { + "psr-4": { + "MatrixTest\\": "unitTests/classes/src/" + } + }, + "scripts": { + "style": "phpcs --report-width=200 --standard=PSR2 --report=summary,full classes/src/ unitTests/classes/src -n", + "test": "phpunit -c phpunit.xml.dist", + "mess": "phpmd classes/src/ xml codesize,unusedcode,design,naming -n", + "lines": "phploc classes/src/ -n", + "cpd": "phpcpd classes/src/ -n", + "versions": "phpcs --report-width=200 --standard=PHPCompatibility --report=summary,full classes/src/ --runtime-set testVersion 7.2- -n", + "coverage": "phpunit -c phpunit.xml.dist --coverage-text --coverage-html ./build/coverage" + }, + "minimum-stability": "dev" +} \ No newline at end of file diff --git a/vendor/markbaker/matrix/examples/test.php b/vendor/markbaker/matrix/examples/test.php new file mode 100644 index 0000000..071dae9 --- /dev/null +++ b/vendor/markbaker/matrix/examples/test.php @@ -0,0 +1,33 @@ +solve($target); + +echo 'X', PHP_EOL; +var_export($X->toArray()); +echo PHP_EOL; + +$resolve = $matrix->multiply($X); + +echo 'Resolve', PHP_EOL; +var_export($resolve->toArray()); +echo PHP_EOL; diff --git a/vendor/markbaker/matrix/infection.json.dist b/vendor/markbaker/matrix/infection.json.dist new file mode 100644 index 0000000..eddaa70 --- /dev/null +++ b/vendor/markbaker/matrix/infection.json.dist @@ -0,0 +1,17 @@ +{ + "timeout": 1, + "source": { + "directories": [ + "classes\/src" + ] + }, + "logs": { + "text": "build/infection/text.log", + "summary": "build/infection/summary.log", + "debug": "build/infection/debug.log", + "perMutator": "build/infection/perMutator.md" + }, + "mutators": { + "@default": true + } +} diff --git a/vendor/markbaker/matrix/license.md b/vendor/markbaker/matrix/license.md new file mode 100644 index 0000000..7329058 --- /dev/null +++ b/vendor/markbaker/matrix/license.md @@ -0,0 +1,25 @@ +The MIT License (MIT) +===================== + +Copyright © `2018` `Mark Baker` + +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. \ No newline at end of file diff --git a/vendor/markbaker/matrix/phpstan.neon b/vendor/markbaker/matrix/phpstan.neon new file mode 100644 index 0000000..3d90d49 --- /dev/null +++ b/vendor/markbaker/matrix/phpstan.neon @@ -0,0 +1,6 @@ +parameters: + ignoreErrors: + - '#Property [A-Za-z\\]+::\$[A-Za-z]+ has no typehint specified#' + - '#Method [A-Za-z\\]+::[A-Za-z]+\(\) has no return typehint specified#' + - '#Method [A-Za-z\\]+::[A-Za-z]+\(\) has parameter \$[A-Za-z0-9]+ with no typehint specified#' + checkMissingIterableValueType: false diff --git a/vendor/monolog/monolog/CHANGELOG.md b/vendor/monolog/monolog/CHANGELOG.md new file mode 100644 index 0000000..121380f --- /dev/null +++ b/vendor/monolog/monolog/CHANGELOG.md @@ -0,0 +1,557 @@ +### 2.3.5 (2021-10-01) + + * Fixed regression in StreamHandler since 2.3.3 on systems with the memory_limit set to >=20GB (#1592) + +### 2.3.4 (2021-09-15) + + * Fixed support for psr/log 3.x (#1589) + +### 2.3.3 (2021-09-14) + + * Fixed memory usage when using StreamHandler and calling stream_get_contents on the resource you passed to it (#1578, #1577) + * Fixed support for psr/log 2.x (#1587) + * Fixed some type annotations + +### 2.3.2 (2021-07-23) + + * Fixed compatibility with PHP 7.2 - 7.4 when experiencing PCRE errors (#1568) + +### 2.3.1 (2021-07-14) + + * Fixed Utils::getClass handling of anonymous classes not being fully compatible with PHP 8 (#1563) + * Fixed some `@inheritDoc` annotations having the wrong case + +### 2.3.0 (2021-07-05) + + * Added a ton of PHPStan type annotations as well as type aliases on Monolog\Logger for Record, Level and LevelName that you can import (#1557) + * Added ability to customize date format when using JsonFormatter (#1561) + * Fixed FilterHandler not calling reset on its internal handler when reset() is called on it (#1531) + * Fixed SyslogUdpHandler not setting the timezone correctly on DateTimeImmutable instances (#1540) + * Fixed StreamHandler thread safety - chunk size set to 2GB now to avoid interlacing when doing concurrent writes (#1553) + +### 2.2.0 (2020-12-14) + + * Added JSON_PARTIAL_OUTPUT_ON_ERROR to default json encoding flags, to avoid dropping entire context data or even records due to an invalid subset of it somewhere + * Added setDateFormat to NormalizerFormatter (and Line/Json formatters by extension) to allow changing this after object creation + * Added RedisPubSubHandler to log records to a Redis channel using PUBLISH + * Added support for Elastica 7, and deprecated the $type argument of ElasticaFormatter which is not in use anymore as of Elastica 7 + * Added support for millisecond write timeouts in SocketHandler, you can now pass floats to setWritingTimeout, e.g. 0.2 is 200ms + * Added support for unix sockets in SyslogUdpHandler (set $port to 0 to make the $host a unix socket) + * Added handleBatch support for TelegramBotHandler + * Added RFC5424e extended date format including milliseconds to SyslogUdpHandler + * Added support for configuring handlers with numeric level values in strings (coming from e.g. env vars) + * Fixed Wildfire/FirePHP/ChromePHP handling of unicode characters + * Fixed PHP 8 issues in SyslogUdpHandler + * Fixed internal type error when mbstring is missing + +### 2.1.1 (2020-07-23) + + * Fixed removing of json encoding options + * Fixed type hint of $level not accepting strings in SendGridHandler and OverflowHandler + * Fixed SwiftMailerHandler not accepting email templates with an empty subject + * Fixed array access on null in RavenHandler + * Fixed unique_id in WebProcessor not being disableable + +### 2.1.0 (2020-05-22) + + * Added `JSON_INVALID_UTF8_SUBSTITUTE` to default json flags, so that invalid UTF8 characters now get converted to [�](https://en.wikipedia.org/wiki/Specials_(Unicode_block)#Replacement_character) instead of being converted from ISO-8859-15 to UTF8 as it was before, which was hardly a comprehensive solution + * Added `$ignoreEmptyContextAndExtra` option to JsonFormatter to skip empty context/extra entirely from the output + * Added `$parseMode`, `$disableWebPagePreview` and `$disableNotification` options to TelegramBotHandler + * Added tentative support for PHP 8 + * NormalizerFormatter::addJsonEncodeOption and removeJsonEncodeOption are now public to allow modifying default json flags + * Fixed GitProcessor type error when there is no git repo present + * Fixed normalization of SoapFault objects containing deeply nested objects as "detail" + * Fixed support for relative paths in RotatingFileHandler + +### 2.0.2 (2019-12-20) + + * Fixed ElasticsearchHandler swallowing exceptions details when failing to index log records + * Fixed normalization of SoapFault objects containing non-strings as "detail" in LineFormatter + * Fixed formatting of resources in JsonFormatter + * Fixed RedisHandler failing to use MULTI properly when passed a proxied Redis instance (e.g. in Symfony with lazy services) + * Fixed FilterHandler triggering a notice when handleBatch was filtering all records passed to it + * Fixed Turkish locale messing up the conversion of level names to their constant values + +### 2.0.1 (2019-11-13) + + * Fixed normalization of Traversables to avoid traversing them as not all of them are rewindable + * Fixed setFormatter/getFormatter to forward to the nested handler in FilterHandler, FingersCrossedHandler, BufferHandler, OverflowHandler and SamplingHandler + * Fixed BrowserConsoleHandler formatting when using multiple styles + * Fixed normalization of exception codes to be always integers even for PDOException which have them as numeric strings + * Fixed normalization of SoapFault objects containing non-strings as "detail" + * Fixed json encoding across all handlers to always attempt recovery of non-UTF-8 strings instead of failing the whole encoding + * Fixed ChromePHPHandler to avoid sending more data than latest Chrome versions allow in headers (4KB down from 256KB). + * Fixed type error in BrowserConsoleHandler when the context array of log records was not associative. + +### 2.0.0 (2019-08-30) + + * BC Break: This is a major release, see [UPGRADE.md](UPGRADE.md) for details if you are coming from a 1.x release + * BC Break: Logger methods log/debug/info/notice/warning/error/critical/alert/emergency now have explicit void return types + * Added FallbackGroupHandler which works like the WhatFailureGroupHandler but stops dispatching log records as soon as one handler accepted it + * Fixed support for UTF-8 when cutting strings to avoid cutting a multibyte-character in half + * Fixed normalizers handling of exception backtraces to avoid serializing arguments in some cases + * Fixed date timezone handling in SyslogUdpHandler + +### 2.0.0-beta2 (2019-07-06) + + * BC Break: This is a major release, see [UPGRADE.md](UPGRADE.md) for details if you are coming from a 1.x release + * BC Break: PHP 7.2 is now the minimum required PHP version. + * BC Break: Removed SlackbotHandler, RavenHandler and HipChatHandler, see [UPGRADE.md](UPGRADE.md) for details + * Added OverflowHandler which will only flush log records to its nested handler when reaching a certain amount of logs (i.e. only pass through when things go really bad) + * Added TelegramBotHandler to log records to a [Telegram](https://core.telegram.org/bots/api) bot account + * Added support for JsonSerializable when normalizing exceptions + * Added support for RFC3164 (outdated BSD syslog protocol) to SyslogUdpHandler + * Added SoapFault details to formatted exceptions + * Fixed DeduplicationHandler silently failing to start when file could not be opened + * Fixed issue in GroupHandler and WhatFailureGroupHandler where setting multiple processors would duplicate records + * Fixed GelfFormatter losing some data when one attachment was too long + * Fixed issue in SignalHandler restarting syscalls functionality + * Improved performance of LogglyHandler when sending multiple logs in a single request + +### 2.0.0-beta1 (2018-12-08) + + * BC Break: This is a major release, see [UPGRADE.md](UPGRADE.md) for details if you are coming from a 1.x release + * BC Break: PHP 7.1 is now the minimum required PHP version. + * BC Break: Quite a few interface changes, only relevant if you implemented your own handlers/processors/formatters + * BC Break: Removed non-PSR-3 methods to add records, all the `add*` (e.g. `addWarning`) methods as well as `emerg`, `crit`, `err` and `warn` + * BC Break: The record timezone is now set per Logger instance and not statically anymore + * BC Break: There is no more default handler configured on empty Logger instances + * BC Break: ElasticSearchHandler renamed to ElasticaHandler + * BC Break: Various handler-specific breaks, see [UPGRADE.md](UPGRADE.md) for details + * Added scalar type hints and return hints in all the places it was possible. Switched strict_types on for more reliability. + * Added DateTimeImmutable support, all record datetime are now immutable, and will toString/json serialize with the correct date format, including microseconds (unless disabled) + * Added timezone and microseconds to the default date format + * Added SendGridHandler to use the SendGrid API to send emails + * Added LogmaticHandler to use the Logmatic.io API to store log records + * Added SqsHandler to send log records to an AWS SQS queue + * Added ElasticsearchHandler to send records via the official ES library. Elastica users should now use ElasticaHandler instead of ElasticSearchHandler + * Added NoopHandler which is similar to the NullHandle but does not prevent the bubbling of log records to handlers further down the configuration, useful for temporarily disabling a handler in configuration files + * Added ProcessHandler to write log output to the STDIN of a given process + * Added HostnameProcessor that adds the machine's hostname to log records + * Added a `$dateFormat` option to the PsrLogMessageProcessor which lets you format DateTime instances nicely + * Added support for the PHP 7.x `mongodb` extension in the MongoDBHandler + * Fixed many minor issues in various handlers, and probably added a few regressions too + +### 1.26.1 (2021-05-28) + + * Fixed PHP 8.1 deprecation warning + +### 1.26.0 (2020-12-14) + + * Added $dateFormat and $removeUsedContextFields arguments to PsrLogMessageProcessor (backport from 2.x) + +### 1.25.5 (2020-07-23) + + * Fixed array access on null in RavenHandler + * Fixed unique_id in WebProcessor not being disableable + +### 1.25.4 (2020-05-22) + + * Fixed GitProcessor type error when there is no git repo present + * Fixed normalization of SoapFault objects containing deeply nested objects as "detail" + * Fixed support for relative paths in RotatingFileHandler + +### 1.25.3 (2019-12-20) + + * Fixed formatting of resources in JsonFormatter + * Fixed RedisHandler failing to use MULTI properly when passed a proxied Redis instance (e.g. in Symfony with lazy services) + * Fixed FilterHandler triggering a notice when handleBatch was filtering all records passed to it + * Fixed Turkish locale messing up the conversion of level names to their constant values + +### 1.25.2 (2019-11-13) + + * Fixed normalization of Traversables to avoid traversing them as not all of them are rewindable + * Fixed setFormatter/getFormatter to forward to the nested handler in FilterHandler, FingersCrossedHandler, BufferHandler and SamplingHandler + * Fixed BrowserConsoleHandler formatting when using multiple styles + * Fixed normalization of exception codes to be always integers even for PDOException which have them as numeric strings + * Fixed normalization of SoapFault objects containing non-strings as "detail" + * Fixed json encoding across all handlers to always attempt recovery of non-UTF-8 strings instead of failing the whole encoding + +### 1.25.1 (2019-09-06) + + * Fixed forward-compatible interfaces to be compatible with Monolog 1.x too. + +### 1.25.0 (2019-09-06) + + * Deprecated SlackbotHandler, use SlackWebhookHandler or SlackHandler instead + * Deprecated RavenHandler, use sentry/sentry 2.x and their Sentry\Monolog\Handler instead + * Deprecated HipChatHandler, migrate to Slack and use SlackWebhookHandler or SlackHandler instead + * Added forward-compatible interfaces and traits FormattableHandlerInterface, FormattableHandlerTrait, ProcessableHandlerInterface, ProcessableHandlerTrait. If you use modern PHP and want to make code compatible with Monolog 1 and 2 this can help. You will have to require at least Monolog 1.25 though. + * Added support for RFC3164 (outdated BSD syslog protocol) to SyslogUdpHandler + * Fixed issue in GroupHandler and WhatFailureGroupHandler where setting multiple processors would duplicate records + * Fixed issue in SignalHandler restarting syscalls functionality + * Fixed normalizers handling of exception backtraces to avoid serializing arguments in some cases + * Fixed ZendMonitorHandler to work with the latest Zend Server versions + * Fixed ChromePHPHandler to avoid sending more data than latest Chrome versions allow in headers (4KB down from 256KB). + +### 1.24.0 (2018-11-05) + + * BC Notice: If you are extending any of the Monolog's Formatters' `normalize` method, make sure you add the new `$depth = 0` argument to your function signature to avoid strict PHP warnings. + * Added a `ResettableInterface` in order to reset/reset/clear/flush handlers and processors + * Added a `ProcessorInterface` as an optional way to label a class as being a processor (mostly useful for autowiring dependency containers) + * Added a way to log signals being received using Monolog\SignalHandler + * Added ability to customize error handling at the Logger level using Logger::setExceptionHandler + * Added InsightOpsHandler to migrate users of the LogEntriesHandler + * Added protection to NormalizerFormatter against circular and very deep structures, it now stops normalizing at a depth of 9 + * Added capture of stack traces to ErrorHandler when logging PHP errors + * Added RavenHandler support for a `contexts` context or extra key to forward that to Sentry's contexts + * Added forwarding of context info to FluentdFormatter + * Added SocketHandler::setChunkSize to override the default chunk size in case you must send large log lines to rsyslog for example + * Added ability to extend/override BrowserConsoleHandler + * Added SlackWebhookHandler::getWebhookUrl and SlackHandler::getToken to enable class extensibility + * Added SwiftMailerHandler::getSubjectFormatter to enable class extensibility + * Dropped official support for HHVM in test builds + * Fixed normalization of exception traces when call_user_func is used to avoid serializing objects and the data they contain + * Fixed naming of fields in Slack handler, all field names are now capitalized in all cases + * Fixed HipChatHandler bug where slack dropped messages randomly + * Fixed normalization of objects in Slack handlers + * Fixed support for PHP7's Throwable in NewRelicHandler + * Fixed race bug when StreamHandler sometimes incorrectly reported it failed to create a directory + * Fixed table row styling issues in HtmlFormatter + * Fixed RavenHandler dropping the message when logging exception + * Fixed WhatFailureGroupHandler skipping processors when using handleBatch + and implement it where possible + * Fixed display of anonymous class names + +### 1.23.0 (2017-06-19) + + * Improved SyslogUdpHandler's support for RFC5424 and added optional `$ident` argument + * Fixed GelfHandler truncation to be per field and not per message + * Fixed compatibility issue with PHP <5.3.6 + * Fixed support for headless Chrome in ChromePHPHandler + * Fixed support for latest Aws SDK in DynamoDbHandler + * Fixed support for SwiftMailer 6.0+ in SwiftMailerHandler + +### 1.22.1 (2017-03-13) + + * Fixed lots of minor issues in the new Slack integrations + * Fixed support for allowInlineLineBreaks in LineFormatter when formatting exception backtraces + +### 1.22.0 (2016-11-26) + + * Added SlackbotHandler and SlackWebhookHandler to set up Slack integration more easily + * Added MercurialProcessor to add mercurial revision and branch names to log records + * Added support for AWS SDK v3 in DynamoDbHandler + * Fixed fatal errors occurring when normalizing generators that have been fully consumed + * Fixed RollbarHandler to include a level (rollbar level), monolog_level (original name), channel and datetime (unix) + * Fixed RollbarHandler not flushing records automatically, calling close() explicitly is not necessary anymore + * Fixed SyslogUdpHandler to avoid sending empty frames + * Fixed a few PHP 7.0 and 7.1 compatibility issues + +### 1.21.0 (2016-07-29) + + * Break: Reverted the addition of $context when the ErrorHandler handles regular php errors from 1.20.0 as it was causing issues + * Added support for more formats in RotatingFileHandler::setFilenameFormat as long as they have Y, m and d in order + * Added ability to format the main line of text the SlackHandler sends by explicitly setting a formatter on the handler + * Added information about SoapFault instances in NormalizerFormatter + * Added $handleOnlyReportedErrors option on ErrorHandler::registerErrorHandler (default true) to allow logging of all errors no matter the error_reporting level + +### 1.20.0 (2016-07-02) + + * Added FingersCrossedHandler::activate() to manually trigger the handler regardless of the activation policy + * Added StreamHandler::getUrl to retrieve the stream's URL + * Added ability to override addRow/addTitle in HtmlFormatter + * Added the $context to context information when the ErrorHandler handles a regular php error + * Deprecated RotatingFileHandler::setFilenameFormat to only support 3 formats: Y, Y-m and Y-m-d + * Fixed WhatFailureGroupHandler to work with PHP7 throwables + * Fixed a few minor bugs + +### 1.19.0 (2016-04-12) + + * Break: StreamHandler will not close streams automatically that it does not own. If you pass in a stream (not a path/url), then it will not close it for you. You can retrieve those using getStream() if needed + * Added DeduplicationHandler to remove duplicate records from notifications across multiple requests, useful for email or other notifications on errors + * Added ability to use `%message%` and other LineFormatter replacements in the subject line of emails sent with NativeMailHandler and SwiftMailerHandler + * Fixed HipChatHandler handling of long messages + +### 1.18.2 (2016-04-02) + + * Fixed ElasticaFormatter to use more precise dates + * Fixed GelfMessageFormatter sending too long messages + +### 1.18.1 (2016-03-13) + + * Fixed SlackHandler bug where slack dropped messages randomly + * Fixed RedisHandler issue when using with the PHPRedis extension + * Fixed AmqpHandler content-type being incorrectly set when using with the AMQP extension + * Fixed BrowserConsoleHandler regression + +### 1.18.0 (2016-03-01) + + * Added optional reduction of timestamp precision via `Logger->useMicrosecondTimestamps(false)`, disabling it gets you a bit of performance boost but reduces the precision to the second instead of microsecond + * Added possibility to skip some extra stack frames in IntrospectionProcessor if you have some library wrapping Monolog that is always adding frames + * Added `Logger->withName` to clone a logger (keeping all handlers) with a new name + * Added FluentdFormatter for the Fluentd unix socket protocol + * Added HandlerWrapper base class to ease the creation of handler wrappers, just extend it and override as needed + * Added support for replacing context sub-keys using `%context.*%` in LineFormatter + * Added support for `payload` context value in RollbarHandler + * Added setRelease to RavenHandler to describe the application version, sent with every log + * Added support for `fingerprint` context value in RavenHandler + * Fixed JSON encoding errors that would gobble up the whole log record, we now handle those more gracefully by dropping chars as needed + * Fixed write timeouts in SocketHandler and derivatives, set to 10sec by default, lower it with `setWritingTimeout()` + * Fixed PHP7 compatibility with regard to Exception/Throwable handling in a few places + +### 1.17.2 (2015-10-14) + + * Fixed ErrorHandler compatibility with non-Monolog PSR-3 loggers + * Fixed SlackHandler handling to use slack functionalities better + * Fixed SwiftMailerHandler bug when sending multiple emails they all had the same id + * Fixed 5.3 compatibility regression + +### 1.17.1 (2015-08-31) + + * Fixed RollbarHandler triggering PHP notices + +### 1.17.0 (2015-08-30) + + * Added support for `checksum` and `release` context/extra values in RavenHandler + * Added better support for exceptions in RollbarHandler + * Added UidProcessor::getUid + * Added support for showing the resource type in NormalizedFormatter + * Fixed IntrospectionProcessor triggering PHP notices + +### 1.16.0 (2015-08-09) + + * Added IFTTTHandler to notify ifttt.com triggers + * Added Logger::setHandlers() to allow setting/replacing all handlers + * Added $capSize in RedisHandler to cap the log size + * Fixed StreamHandler creation of directory to only trigger when the first log write happens + * Fixed bug in the handling of curl failures + * Fixed duplicate logging of fatal errors when both error and fatal error handlers are registered in monolog's ErrorHandler + * Fixed missing fatal errors records with handlers that need to be closed to flush log records + * Fixed TagProcessor::addTags support for associative arrays + +### 1.15.0 (2015-07-12) + + * Added addTags and setTags methods to change a TagProcessor + * Added automatic creation of directories if they are missing for a StreamHandler to open a log file + * Added retry functionality to Loggly, Cube and Mandrill handlers so they retry up to 5 times in case of network failure + * Fixed process exit code being incorrectly reset to 0 if ErrorHandler::registerExceptionHandler was used + * Fixed HTML/JS escaping in BrowserConsoleHandler + * Fixed JSON encoding errors being silently suppressed (PHP 5.5+ only) + +### 1.14.0 (2015-06-19) + + * Added PHPConsoleHandler to send record to Chrome's PHP Console extension and library + * Added support for objects implementing __toString in the NormalizerFormatter + * Added support for HipChat's v2 API in HipChatHandler + * Added Logger::setTimezone() to initialize the timezone monolog should use in case date.timezone isn't correct for your app + * Added an option to send formatted message instead of the raw record on PushoverHandler via ->useFormattedMessage(true) + * Fixed curl errors being silently suppressed + +### 1.13.1 (2015-03-09) + + * Fixed regression in HipChat requiring a new token to be created + +### 1.13.0 (2015-03-05) + + * Added Registry::hasLogger to check for the presence of a logger instance + * Added context.user support to RavenHandler + * Added HipChat API v2 support in the HipChatHandler + * Added NativeMailerHandler::addParameter to pass params to the mail() process + * Added context data to SlackHandler when $includeContextAndExtra is true + * Added ability to customize the Swift_Message per-email in SwiftMailerHandler + * Fixed SwiftMailerHandler to lazily create message instances if a callback is provided + * Fixed serialization of INF and NaN values in Normalizer and LineFormatter + +### 1.12.0 (2014-12-29) + + * Break: HandlerInterface::isHandling now receives a partial record containing only a level key. This was always the intent and does not break any Monolog handler but is strictly speaking a BC break and you should check if you relied on any other field in your own handlers. + * Added PsrHandler to forward records to another PSR-3 logger + * Added SamplingHandler to wrap around a handler and include only every Nth record + * Added MongoDBFormatter to support better storage with MongoDBHandler (it must be enabled manually for now) + * Added exception codes in the output of most formatters + * Added LineFormatter::includeStacktraces to enable exception stack traces in logs (uses more than one line) + * Added $useShortAttachment to SlackHandler to minify attachment size and $includeExtra to append extra data + * Added $host to HipChatHandler for users of private instances + * Added $transactionName to NewRelicHandler and support for a transaction_name context value + * Fixed MandrillHandler to avoid outputting API call responses + * Fixed some non-standard behaviors in SyslogUdpHandler + +### 1.11.0 (2014-09-30) + + * Break: The NewRelicHandler extra and context data are now prefixed with extra_ and context_ to avoid clashes. Watch out if you have scripts reading those from the API and rely on names + * Added WhatFailureGroupHandler to suppress any exception coming from the wrapped handlers and avoid chain failures if a logging service fails + * Added MandrillHandler to send emails via the Mandrillapp.com API + * Added SlackHandler to log records to a Slack.com account + * Added FleepHookHandler to log records to a Fleep.io account + * Added LogglyHandler::addTag to allow adding tags to an existing handler + * Added $ignoreEmptyContextAndExtra to LineFormatter to avoid empty [] at the end + * Added $useLocking to StreamHandler and RotatingFileHandler to enable flock() while writing + * Added support for PhpAmqpLib in the AmqpHandler + * Added FingersCrossedHandler::clear and BufferHandler::clear to reset them between batches in long running jobs + * Added support for adding extra fields from $_SERVER in the WebProcessor + * Fixed support for non-string values in PrsLogMessageProcessor + * Fixed SwiftMailer messages being sent with the wrong date in long running scripts + * Fixed minor PHP 5.6 compatibility issues + * Fixed BufferHandler::close being called twice + +### 1.10.0 (2014-06-04) + + * Added Logger::getHandlers() and Logger::getProcessors() methods + * Added $passthruLevel argument to FingersCrossedHandler to let it always pass some records through even if the trigger level is not reached + * Added support for extra data in NewRelicHandler + * Added $expandNewlines flag to the ErrorLogHandler to create multiple log entries when a message has multiple lines + +### 1.9.1 (2014-04-24) + + * Fixed regression in RotatingFileHandler file permissions + * Fixed initialization of the BufferHandler to make sure it gets flushed after receiving records + * Fixed ChromePHPHandler and FirePHPHandler's activation strategies to be more conservative + +### 1.9.0 (2014-04-20) + + * Added LogEntriesHandler to send logs to a LogEntries account + * Added $filePermissions to tweak file mode on StreamHandler and RotatingFileHandler + * Added $useFormatting flag to MemoryProcessor to make it send raw data in bytes + * Added support for table formatting in FirePHPHandler via the table context key + * Added a TagProcessor to add tags to records, and support for tags in RavenHandler + * Added $appendNewline flag to the JsonFormatter to enable using it when logging to files + * Added sound support to the PushoverHandler + * Fixed multi-threading support in StreamHandler + * Fixed empty headers issue when ChromePHPHandler received no records + * Fixed default format of the ErrorLogHandler + +### 1.8.0 (2014-03-23) + + * Break: the LineFormatter now strips newlines by default because this was a bug, set $allowInlineLineBreaks to true if you need them + * Added BrowserConsoleHandler to send logs to any browser's console via console.log() injection in the output + * Added FilterHandler to filter records and only allow those of a given list of levels through to the wrapped handler + * Added FlowdockHandler to send logs to a Flowdock account + * Added RollbarHandler to send logs to a Rollbar account + * Added HtmlFormatter to send prettier log emails with colors for each log level + * Added GitProcessor to add the current branch/commit to extra record data + * Added a Monolog\Registry class to allow easier global access to pre-configured loggers + * Added support for the new official graylog2/gelf-php lib for GelfHandler, upgrade if you can by replacing the mlehner/gelf-php requirement + * Added support for HHVM + * Added support for Loggly batch uploads + * Added support for tweaking the content type and encoding in NativeMailerHandler + * Added $skipClassesPartials to tweak the ignored classes in the IntrospectionProcessor + * Fixed batch request support in GelfHandler + +### 1.7.0 (2013-11-14) + + * Added ElasticSearchHandler to send logs to an Elastic Search server + * Added DynamoDbHandler and ScalarFormatter to send logs to Amazon's Dynamo DB + * Added SyslogUdpHandler to send logs to a remote syslogd server + * Added LogglyHandler to send logs to a Loggly account + * Added $level to IntrospectionProcessor so it only adds backtraces when needed + * Added $version to LogstashFormatter to allow using the new v1 Logstash format + * Added $appName to NewRelicHandler + * Added configuration of Pushover notification retries/expiry + * Added $maxColumnWidth to NativeMailerHandler to change the 70 chars default + * Added chainability to most setters for all handlers + * Fixed RavenHandler batch processing so it takes the message from the record with highest priority + * Fixed HipChatHandler batch processing so it sends all messages at once + * Fixed issues with eAccelerator + * Fixed and improved many small things + +### 1.6.0 (2013-07-29) + + * Added HipChatHandler to send logs to a HipChat chat room + * Added ErrorLogHandler to send logs to PHP's error_log function + * Added NewRelicHandler to send logs to NewRelic's service + * Added Monolog\ErrorHandler helper class to register a Logger as exception/error/fatal handler + * Added ChannelLevelActivationStrategy for the FingersCrossedHandler to customize levels by channel + * Added stack traces output when normalizing exceptions (json output & co) + * Added Monolog\Logger::API constant (currently 1) + * Added support for ChromePHP's v4.0 extension + * Added support for message priorities in PushoverHandler, see $highPriorityLevel and $emergencyLevel + * Added support for sending messages to multiple users at once with the PushoverHandler + * Fixed RavenHandler's support for batch sending of messages (when behind a Buffer or FingersCrossedHandler) + * Fixed normalization of Traversables with very large data sets, only the first 1000 items are shown now + * Fixed issue in RotatingFileHandler when an open_basedir restriction is active + * Fixed minor issues in RavenHandler and bumped the API to Raven 0.5.0 + * Fixed SyslogHandler issue when many were used concurrently with different facilities + +### 1.5.0 (2013-04-23) + + * Added ProcessIdProcessor to inject the PID in log records + * Added UidProcessor to inject a unique identifier to all log records of one request/run + * Added support for previous exceptions in the LineFormatter exception serialization + * Added Monolog\Logger::getLevels() to get all available levels + * Fixed ChromePHPHandler so it avoids sending headers larger than Chrome can handle + +### 1.4.1 (2013-04-01) + + * Fixed exception formatting in the LineFormatter to be more minimalistic + * Fixed RavenHandler's handling of context/extra data, requires Raven client >0.1.0 + * Fixed log rotation in RotatingFileHandler to work with long running scripts spanning multiple days + * Fixed WebProcessor array access so it checks for data presence + * Fixed Buffer, Group and FingersCrossed handlers to make use of their processors + +### 1.4.0 (2013-02-13) + + * Added RedisHandler to log to Redis via the Predis library or the phpredis extension + * Added ZendMonitorHandler to log to the Zend Server monitor + * Added the possibility to pass arrays of handlers and processors directly in the Logger constructor + * Added `$useSSL` option to the PushoverHandler which is enabled by default + * Fixed ChromePHPHandler and FirePHPHandler issue when multiple instances are used simultaneously + * Fixed header injection capability in the NativeMailHandler + +### 1.3.1 (2013-01-11) + + * Fixed LogstashFormatter to be usable with stream handlers + * Fixed GelfMessageFormatter levels on Windows + +### 1.3.0 (2013-01-08) + + * Added PSR-3 compliance, the `Monolog\Logger` class is now an instance of `Psr\Log\LoggerInterface` + * Added PsrLogMessageProcessor that you can selectively enable for full PSR-3 compliance + * Added LogstashFormatter (combine with SocketHandler or StreamHandler to send logs to Logstash) + * Added PushoverHandler to send mobile notifications + * Added CouchDBHandler and DoctrineCouchDBHandler + * Added RavenHandler to send data to Sentry servers + * Added support for the new MongoClient class in MongoDBHandler + * Added microsecond precision to log records' timestamps + * Added `$flushOnOverflow` param to BufferHandler to flush by batches instead of losing + the oldest entries + * Fixed normalization of objects with cyclic references + +### 1.2.1 (2012-08-29) + + * Added new $logopts arg to SyslogHandler to provide custom openlog options + * Fixed fatal error in SyslogHandler + +### 1.2.0 (2012-08-18) + + * Added AmqpHandler (for use with AMQP servers) + * Added CubeHandler + * Added NativeMailerHandler::addHeader() to send custom headers in mails + * Added the possibility to specify more than one recipient in NativeMailerHandler + * Added the possibility to specify float timeouts in SocketHandler + * Added NOTICE and EMERGENCY levels to conform with RFC 5424 + * Fixed the log records to use the php default timezone instead of UTC + * Fixed BufferHandler not being flushed properly on PHP fatal errors + * Fixed normalization of exotic resource types + * Fixed the default format of the SyslogHandler to avoid duplicating datetimes in syslog + +### 1.1.0 (2012-04-23) + + * Added Monolog\Logger::isHandling() to check if a handler will + handle the given log level + * Added ChromePHPHandler + * Added MongoDBHandler + * Added GelfHandler (for use with Graylog2 servers) + * Added SocketHandler (for use with syslog-ng for example) + * Added NormalizerFormatter + * Added the possibility to change the activation strategy of the FingersCrossedHandler + * Added possibility to show microseconds in logs + * Added `server` and `referer` to WebProcessor output + +### 1.0.2 (2011-10-24) + + * Fixed bug in IE with large response headers and FirePHPHandler + +### 1.0.1 (2011-08-25) + + * Added MemoryPeakUsageProcessor and MemoryUsageProcessor + * Added Monolog\Logger::getName() to get a logger's channel name + +### 1.0.0 (2011-07-06) + + * Added IntrospectionProcessor to get info from where the logger was called + * Fixed WebProcessor in CLI + +### 1.0.0-RC1 (2011-07-01) + + * Initial release diff --git a/vendor/monolog/monolog/LICENSE b/vendor/monolog/monolog/LICENSE new file mode 100644 index 0000000..aa2a042 --- /dev/null +++ b/vendor/monolog/monolog/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2011-2020 Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/monolog/monolog/README.md b/vendor/monolog/monolog/README.md new file mode 100644 index 0000000..e7104af --- /dev/null +++ b/vendor/monolog/monolog/README.md @@ -0,0 +1,109 @@ +# Monolog - Logging for PHP [![Continuous Integration](https://github.com/Seldaek/monolog/workflows/Continuous%20Integration/badge.svg?branch=main)](https://github.com/Seldaek/monolog/actions) + +[![Total Downloads](https://img.shields.io/packagist/dt/monolog/monolog.svg)](https://packagist.org/packages/monolog/monolog) +[![Latest Stable Version](https://img.shields.io/packagist/v/monolog/monolog.svg)](https://packagist.org/packages/monolog/monolog) + + +Monolog sends your logs to files, sockets, inboxes, databases and various +web services. See the complete list of handlers below. Special handlers +allow you to build advanced logging strategies. + +This library implements the [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) +interface that you can type-hint against in your own libraries to keep +a maximum of interoperability. You can also use it in your applications to +make sure you can always use another compatible logger at a later time. +As of 1.11.0 Monolog public APIs will also accept PSR-3 log levels. +Internally Monolog still uses its own level scheme since it predates PSR-3. + +## Installation + +Install the latest version with + +```bash +$ composer require monolog/monolog +``` + +## Basic Usage + +```php +pushHandler(new StreamHandler('path/to/your.log', Logger::WARNING)); + +// add records to the log +$log->warning('Foo'); +$log->error('Bar'); +``` + +## Documentation + +- [Usage Instructions](doc/01-usage.md) +- [Handlers, Formatters and Processors](doc/02-handlers-formatters-processors.md) +- [Utility Classes](doc/03-utilities.md) +- [Extending Monolog](doc/04-extending.md) +- [Log Record Structure](doc/message-structure.md) + +## Support Monolog Financially + +Get supported Monolog and help fund the project with the [Tidelift Subscription](https://tidelift.com/subscription/pkg/packagist-monolog-monolog?utm_source=packagist-monolog-monolog&utm_medium=referral&utm_campaign=enterprise) or via [GitHub sponsorship](https://github.com/sponsors/Seldaek). + +Tidelift delivers commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. + +## Third Party Packages + +Third party handlers, formatters and processors are +[listed in the wiki](https://github.com/Seldaek/monolog/wiki/Third-Party-Packages). You +can also add your own there if you publish one. + +## About + +### Requirements + +- Monolog `^2.0` works with PHP 7.2 or above, use Monolog `^1.25` for PHP 5.3+ support. + +### Support + +Monolog 1.x support is somewhat limited at this point and only important fixes will be done. You should migrate to Monolog 2 where possible to benefit from all the latest features and fixes. + +### Submitting bugs and feature requests + +Bugs and feature request are tracked on [GitHub](https://github.com/Seldaek/monolog/issues) + +### Framework Integrations + +- Frameworks and libraries using [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) + can be used very easily with Monolog since it implements the interface. +- [Symfony](http://symfony.com) comes out of the box with Monolog. +- [Laravel](http://laravel.com/) comes out of the box with Monolog. +- [Lumen](http://lumen.laravel.com/) comes out of the box with Monolog. +- [PPI](https://github.com/ppi/framework) comes out of the box with Monolog. +- [CakePHP](http://cakephp.org/) is usable with Monolog via the [cakephp-monolog](https://github.com/jadb/cakephp-monolog) plugin. +- [Slim](http://www.slimframework.com/) is usable with Monolog via the [Slim-Monolog](https://github.com/Flynsarmy/Slim-Monolog) log writer. +- [XOOPS 2.6](http://xoops.org/) comes out of the box with Monolog. +- [Aura.Web_Project](https://github.com/auraphp/Aura.Web_Project) comes out of the box with Monolog. +- [Nette Framework](http://nette.org/en/) is usable with Monolog via the [contributte/monolog](https://github.com/contributte/monolog) or [orisai/nette-monolog](https://github.com/orisai/nette-monolog) extensions. +- [Proton Micro Framework](https://github.com/alexbilbie/Proton) comes out of the box with Monolog. +- [FuelPHP](http://fuelphp.com/) comes out of the box with Monolog. +- [Equip Framework](https://github.com/equip/framework) comes out of the box with Monolog. +- [Yii 2](http://www.yiiframework.com/) is usable with Monolog via the [yii2-monolog](https://github.com/merorafael/yii2-monolog) or [yii2-psr-log-target](https://github.com/samdark/yii2-psr-log-target) plugins. +- [Hawkbit Micro Framework](https://github.com/HawkBitPhp/hawkbit) comes out of the box with Monolog. +- [SilverStripe 4](https://www.silverstripe.org/) comes out of the box with Monolog. + +### Author + +Jordi Boggiano - -
        +See also the list of [contributors](https://github.com/Seldaek/monolog/contributors) who participated in this project. + +### License + +Monolog is licensed under the MIT License - see the [LICENSE](LICENSE) file for details + +### Acknowledgements + +This library is heavily inspired by Python's [Logbook](https://logbook.readthedocs.io/en/stable/) +library, although most concepts have been adjusted to fit to the PHP world. diff --git a/vendor/monolog/monolog/UPGRADE.md b/vendor/monolog/monolog/UPGRADE.md new file mode 100644 index 0000000..84e15e6 --- /dev/null +++ b/vendor/monolog/monolog/UPGRADE.md @@ -0,0 +1,72 @@ +### 2.0.0 + +- `Monolog\Logger::API` can be used to distinguish between a Monolog `1` and `2` + install of Monolog when writing integration code. + +- Removed non-PSR-3 methods to add records, all the `add*` (e.g. `addWarning`) + methods as well as `emerg`, `crit`, `err` and `warn`. + +- DateTime are now formatted with a timezone and microseconds (unless disabled). + Various formatters and log output might be affected, which may mess with log parsing + in some cases. + +- The `datetime` in every record array is now a DateTimeImmutable, not that you + should have been modifying these anyway. + +- The timezone is now set per Logger instance and not statically, either + via ->setTimezone or passed in the constructor. Calls to Logger::setTimezone + should be converted. + +- `HandlerInterface` has been split off and two new interfaces now exist for + more granular controls: `ProcessableHandlerInterface` and + `FormattableHandlerInterface`. Handlers not extending `AbstractHandler` + should make sure to implement the relevant interfaces. + +- `HandlerInterface` now requires the `close` method to be implemented. This + only impacts you if you implement the interface yourself, but you can extend + the new `Monolog\Handler\Handler` base class too. + +- There is no more default handler configured on empty Logger instances, if + you were relying on that you will not get any output anymore, make sure to + configure the handler you need. + +#### LogglyFormatter + +- The records' `datetime` is not sent anymore. Only `timestamp` is sent to Loggly. + +#### AmqpHandler + +- Log levels are not shortened to 4 characters anymore. e.g. a warning record + will be sent using the `warning.channel` routing key instead of `warn.channel` + as in 1.x. +- The exchange name does not default to 'log' anymore, and it is completely ignored + now for the AMQP extension users. Only PHPAmqpLib uses it if provided. + +#### RotatingFileHandler + +- The file name format must now contain `{date}` and the date format must be set + to one of the predefined FILE_PER_* constants to avoid issues with file rotation. + See `setFilenameFormat`. + +#### LogstashFormatter + +- Removed Logstash V0 support +- Context/extra prefix has been removed in favor of letting users configure the exact key being sent +- Context/extra data are now sent as an object instead of single keys + +#### HipChatHandler + +- Removed deprecated HipChat handler, migrate to Slack and use SlackWebhookHandler or SlackHandler instead + +#### SlackbotHandler + +- Removed deprecated SlackbotHandler handler, use SlackWebhookHandler or SlackHandler instead + +#### RavenHandler + +- Removed deprecated RavenHandler handler, use sentry/sentry 2.x and their Sentry\Monolog\Handler instead + +#### ElasticSearchHandler + +- As support for the official Elasticsearch library was added, the former ElasticSearchHandler has been + renamed to ElasticaHandler and the new one added as ElasticsearchHandler. diff --git a/vendor/monolog/monolog/composer.json b/vendor/monolog/monolog/composer.json new file mode 100644 index 0000000..020d278 --- /dev/null +++ b/vendor/monolog/monolog/composer.json @@ -0,0 +1,75 @@ +{ + "name": "monolog/monolog", + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "keywords": ["log", "logging", "psr-3"], + "homepage": "https://github.com/Seldaek/monolog", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "require": { + "php": ">=7.2", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7", + "mongodb/mongodb": "^1.8", + "graylog2/gelf-php": "^1.4.2", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "php-console/php-console": "^3.1.3", + "phpspec/prophecy": "^1.6.1", + "phpunit/phpunit": "^8.5", + "predis/predis": "^1.1", + "rollbar/rollbar": "^1.3", + "ruflin/elastica": ">=0.90@dev", + "swiftmailer/swiftmailer": "^5.3|^6.0", + "phpstan/phpstan": "^0.12.91" + }, + "suggest": { + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "php-console/php-console": "Allow sending log messages to Google Chrome", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-openssl": "Required to send log messages using SSL" + }, + "autoload": { + "psr-4": {"Monolog\\": "src/Monolog"} + }, + "autoload-dev": { + "psr-4": {"Monolog\\": "tests/Monolog"} + }, + "provide": { + "psr/log-implementation": "1.0.0 || 2.0.0 || 3.0.0" + }, + "extra": { + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "scripts": { + "test": "vendor/bin/phpunit", + "phpstan": "vendor/bin/phpstan analyse" + }, + "config": { + "lock": false, + "sort-packages": true, + "platform-check": false + } +} diff --git a/vendor/monolog/monolog/src/Monolog/DateTimeImmutable.php b/vendor/monolog/monolog/src/Monolog/DateTimeImmutable.php new file mode 100644 index 0000000..6a1ba9b --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/DateTimeImmutable.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use DateTimeZone; + +/** + * Overrides default json encoding of date time objects + * + * @author Menno Holtkamp + * @author Jordi Boggiano + */ +class DateTimeImmutable extends \DateTimeImmutable implements \JsonSerializable +{ + /** + * @var bool + */ + private $useMicroseconds; + + public function __construct(bool $useMicroseconds, ?DateTimeZone $timezone = null) + { + $this->useMicroseconds = $useMicroseconds; + + parent::__construct('now', $timezone); + } + + public function jsonSerialize(): string + { + if ($this->useMicroseconds) { + return $this->format('Y-m-d\TH:i:s.uP'); + } + + return $this->format('Y-m-d\TH:i:sP'); + } + + public function __toString(): string + { + return $this->jsonSerialize(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/ErrorHandler.php b/vendor/monolog/monolog/src/Monolog/ErrorHandler.php new file mode 100644 index 0000000..e7d244b --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/ErrorHandler.php @@ -0,0 +1,301 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; + +/** + * Monolog error handler + * + * A facility to enable logging of runtime errors, exceptions and fatal errors. + * + * Quick setup: ErrorHandler::register($logger); + * + * @author Jordi Boggiano + */ +class ErrorHandler +{ + /** @var LoggerInterface */ + private $logger; + + /** @var ?callable */ + private $previousExceptionHandler = null; + /** @var array an array of class name to LogLevel::* constant mapping */ + private $uncaughtExceptionLevelMap = []; + + /** @var callable|true|null */ + private $previousErrorHandler = null; + /** @var array an array of E_* constant to LogLevel::* constant mapping */ + private $errorLevelMap = []; + /** @var bool */ + private $handleOnlyReportedErrors = true; + + /** @var bool */ + private $hasFatalErrorHandler = false; + /** @var LogLevel::* */ + private $fatalLevel = LogLevel::ALERT; + /** @var ?string */ + private $reservedMemory = null; + /** @var ?mixed */ + private $lastFatalTrace; + /** @var int[] */ + private static $fatalErrors = [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR]; + + public function __construct(LoggerInterface $logger) + { + $this->logger = $logger; + } + + /** + * Registers a new ErrorHandler for a given Logger + * + * By default it will handle errors, exceptions and fatal errors + * + * @param LoggerInterface $logger + * @param array|false $errorLevelMap an array of E_* constant to LogLevel::* constant mapping, or false to disable error handling + * @param array|false $exceptionLevelMap an array of class name to LogLevel::* constant mapping, or false to disable exception handling + * @param LogLevel::*|null|false $fatalLevel a LogLevel::* constant, null to use the default LogLevel::ALERT or false to disable fatal error handling + * @return ErrorHandler + */ + public static function register(LoggerInterface $logger, $errorLevelMap = [], $exceptionLevelMap = [], $fatalLevel = null): self + { + /** @phpstan-ignore-next-line */ + $handler = new static($logger); + if ($errorLevelMap !== false) { + $handler->registerErrorHandler($errorLevelMap); + } + if ($exceptionLevelMap !== false) { + $handler->registerExceptionHandler($exceptionLevelMap); + } + if ($fatalLevel !== false) { + $handler->registerFatalHandler($fatalLevel); + } + + return $handler; + } + + /** + * @param array $levelMap an array of class name to LogLevel::* constant mapping + * @return $this + */ + public function registerExceptionHandler(array $levelMap = [], bool $callPrevious = true): self + { + $prev = set_exception_handler(function (\Throwable $e): void { + $this->handleException($e); + }); + $this->uncaughtExceptionLevelMap = $levelMap; + foreach ($this->defaultExceptionLevelMap() as $class => $level) { + if (!isset($this->uncaughtExceptionLevelMap[$class])) { + $this->uncaughtExceptionLevelMap[$class] = $level; + } + } + if ($callPrevious && $prev) { + $this->previousExceptionHandler = $prev; + } + + return $this; + } + + /** + * @param array $levelMap an array of E_* constant to LogLevel::* constant mapping + * @return $this + */ + public function registerErrorHandler(array $levelMap = [], bool $callPrevious = true, int $errorTypes = -1, bool $handleOnlyReportedErrors = true): self + { + $prev = set_error_handler([$this, 'handleError'], $errorTypes); + $this->errorLevelMap = array_replace($this->defaultErrorLevelMap(), $levelMap); + if ($callPrevious) { + $this->previousErrorHandler = $prev ?: true; + } else { + $this->previousErrorHandler = null; + } + + $this->handleOnlyReportedErrors = $handleOnlyReportedErrors; + + return $this; + } + + /** + * @param LogLevel::*|null $level a LogLevel::* constant, null to use the default LogLevel::ALERT + * @param int $reservedMemorySize Amount of KBs to reserve in memory so that it can be freed when handling fatal errors giving Monolog some room in memory to get its job done + */ + public function registerFatalHandler($level = null, int $reservedMemorySize = 20): self + { + register_shutdown_function([$this, 'handleFatalError']); + + $this->reservedMemory = str_repeat(' ', 1024 * $reservedMemorySize); + $this->fatalLevel = null === $level ? LogLevel::ALERT : $level; + $this->hasFatalErrorHandler = true; + + return $this; + } + + /** + * @return array + */ + protected function defaultExceptionLevelMap(): array + { + return [ + 'ParseError' => LogLevel::CRITICAL, + 'Throwable' => LogLevel::ERROR, + ]; + } + + /** + * @return array + */ + protected function defaultErrorLevelMap(): array + { + return [ + E_ERROR => LogLevel::CRITICAL, + E_WARNING => LogLevel::WARNING, + E_PARSE => LogLevel::ALERT, + E_NOTICE => LogLevel::NOTICE, + E_CORE_ERROR => LogLevel::CRITICAL, + E_CORE_WARNING => LogLevel::WARNING, + E_COMPILE_ERROR => LogLevel::ALERT, + E_COMPILE_WARNING => LogLevel::WARNING, + E_USER_ERROR => LogLevel::ERROR, + E_USER_WARNING => LogLevel::WARNING, + E_USER_NOTICE => LogLevel::NOTICE, + E_STRICT => LogLevel::NOTICE, + E_RECOVERABLE_ERROR => LogLevel::ERROR, + E_DEPRECATED => LogLevel::NOTICE, + E_USER_DEPRECATED => LogLevel::NOTICE, + ]; + } + + /** + * @phpstan-return never + */ + private function handleException(\Throwable $e): void + { + $level = LogLevel::ERROR; + foreach ($this->uncaughtExceptionLevelMap as $class => $candidate) { + if ($e instanceof $class) { + $level = $candidate; + break; + } + } + + $this->logger->log( + $level, + sprintf('Uncaught Exception %s: "%s" at %s line %s', Utils::getClass($e), $e->getMessage(), $e->getFile(), $e->getLine()), + ['exception' => $e] + ); + + if ($this->previousExceptionHandler) { + ($this->previousExceptionHandler)($e); + } + + if (!headers_sent() && !ini_get('display_errors')) { + http_response_code(500); + } + + exit(255); + } + + /** + * @private + * + * @param mixed[] $context + */ + public function handleError(int $code, string $message, string $file = '', int $line = 0, array $context = []): bool + { + if ($this->handleOnlyReportedErrors && !(error_reporting() & $code)) { + return false; + } + + // fatal error codes are ignored if a fatal error handler is present as well to avoid duplicate log entries + if (!$this->hasFatalErrorHandler || !in_array($code, self::$fatalErrors, true)) { + $level = $this->errorLevelMap[$code] ?? LogLevel::CRITICAL; + $this->logger->log($level, self::codeToString($code).': '.$message, ['code' => $code, 'message' => $message, 'file' => $file, 'line' => $line]); + } else { + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + array_shift($trace); // Exclude handleError from trace + $this->lastFatalTrace = $trace; + } + + if ($this->previousErrorHandler === true) { + return false; + } elseif ($this->previousErrorHandler) { + return (bool) ($this->previousErrorHandler)($code, $message, $file, $line, $context); + } + + return true; + } + + /** + * @private + */ + public function handleFatalError(): void + { + $this->reservedMemory = ''; + + $lastError = error_get_last(); + if ($lastError && in_array($lastError['type'], self::$fatalErrors, true)) { + $this->logger->log( + $this->fatalLevel, + 'Fatal Error ('.self::codeToString($lastError['type']).'): '.$lastError['message'], + ['code' => $lastError['type'], 'message' => $lastError['message'], 'file' => $lastError['file'], 'line' => $lastError['line'], 'trace' => $this->lastFatalTrace] + ); + + if ($this->logger instanceof Logger) { + foreach ($this->logger->getHandlers() as $handler) { + $handler->close(); + } + } + } + } + + /** + * @param int $code + */ + private static function codeToString($code): string + { + switch ($code) { + case E_ERROR: + return 'E_ERROR'; + case E_WARNING: + return 'E_WARNING'; + case E_PARSE: + return 'E_PARSE'; + case E_NOTICE: + return 'E_NOTICE'; + case E_CORE_ERROR: + return 'E_CORE_ERROR'; + case E_CORE_WARNING: + return 'E_CORE_WARNING'; + case E_COMPILE_ERROR: + return 'E_COMPILE_ERROR'; + case E_COMPILE_WARNING: + return 'E_COMPILE_WARNING'; + case E_USER_ERROR: + return 'E_USER_ERROR'; + case E_USER_WARNING: + return 'E_USER_WARNING'; + case E_USER_NOTICE: + return 'E_USER_NOTICE'; + case E_STRICT: + return 'E_STRICT'; + case E_RECOVERABLE_ERROR: + return 'E_RECOVERABLE_ERROR'; + case E_DEPRECATED: + return 'E_DEPRECATED'; + case E_USER_DEPRECATED: + return 'E_USER_DEPRECATED'; + } + + return 'Unknown PHP error'; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php new file mode 100644 index 0000000..aa1884b --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +/** + * Formats a log message according to the ChromePHP array format + * + * @author Christophe Coevoet + */ +class ChromePHPFormatter implements FormatterInterface +{ + /** + * Translates Monolog log levels to Wildfire levels. + * + * @var array + */ + private $logLevels = [ + Logger::DEBUG => 'log', + Logger::INFO => 'info', + Logger::NOTICE => 'info', + Logger::WARNING => 'warn', + Logger::ERROR => 'error', + Logger::CRITICAL => 'error', + Logger::ALERT => 'error', + Logger::EMERGENCY => 'error', + ]; + + /** + * {@inheritDoc} + */ + public function format(array $record) + { + // Retrieve the line and file if set and remove them from the formatted extra + $backtrace = 'unknown'; + if (isset($record['extra']['file'], $record['extra']['line'])) { + $backtrace = $record['extra']['file'].' : '.$record['extra']['line']; + unset($record['extra']['file'], $record['extra']['line']); + } + + $message = ['message' => $record['message']]; + if ($record['context']) { + $message['context'] = $record['context']; + } + if ($record['extra']) { + $message['extra'] = $record['extra']; + } + if (count($message) === 1) { + $message = reset($message); + } + + return [ + $record['channel'], + $message, + $backtrace, + $this->logLevels[$record['level']], + ]; + } + + /** + * {@inheritDoc} + */ + public function formatBatch(array $records) + { + $formatted = []; + + foreach ($records as $record) { + $formatted[] = $this->format($record); + } + + return $formatted; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php new file mode 100644 index 0000000..6c8a9ab --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Elastica\Document; + +/** + * Format a log message into an Elastica Document + * + * @author Jelle Vink + * + * @phpstan-import-type Record from \Monolog\Logger + */ +class ElasticaFormatter extends NormalizerFormatter +{ + /** + * @var string Elastic search index name + */ + protected $index; + + /** + * @var ?string Elastic search document type + */ + protected $type; + + /** + * @param string $index Elastic Search index name + * @param ?string $type Elastic Search document type, deprecated as of Elastica 7 + */ + public function __construct(string $index, ?string $type) + { + // elasticsearch requires a ISO 8601 format date with optional millisecond precision. + parent::__construct('Y-m-d\TH:i:s.uP'); + + $this->index = $index; + $this->type = $type; + } + + /** + * {@inheritDoc} + */ + public function format(array $record) + { + $record = parent::format($record); + + return $this->getDocument($record); + } + + public function getIndex(): string + { + return $this->index; + } + + /** + * @deprecated since Elastica 7 type has no effect + */ + public function getType(): string + { + /** @phpstan-ignore-next-line */ + return $this->type; + } + + /** + * Convert a log message into an Elastica Document + * + * @phpstan-param Record $record + */ + protected function getDocument(array $record): Document + { + $document = new Document(); + $document->setData($record); + if (method_exists($document, 'setType')) { + /** @phpstan-ignore-next-line */ + $document->setType($this->type); + } + $document->setIndex($this->index); + + return $document; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/ElasticsearchFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/ElasticsearchFormatter.php new file mode 100644 index 0000000..b792b81 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/ElasticsearchFormatter.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use DateTimeInterface; + +/** + * Format a log message into an Elasticsearch record + * + * @author Avtandil Kikabidze + */ +class ElasticsearchFormatter extends NormalizerFormatter +{ + /** + * @var string Elasticsearch index name + */ + protected $index; + + /** + * @var string Elasticsearch record type + */ + protected $type; + + /** + * @param string $index Elasticsearch index name + * @param string $type Elasticsearch record type + */ + public function __construct(string $index, string $type) + { + // Elasticsearch requires an ISO 8601 format date with optional millisecond precision. + parent::__construct(DateTimeInterface::ISO8601); + + $this->index = $index; + $this->type = $type; + } + + /** + * {@inheritDoc} + */ + public function format(array $record) + { + $record = parent::format($record); + + return $this->getDocument($record); + } + + /** + * Getter index + * + * @return string + */ + public function getIndex(): string + { + return $this->index; + } + + /** + * Getter type + * + * @return string + */ + public function getType(): string + { + return $this->type; + } + + /** + * Convert a log message into an Elasticsearch record + * + * @param mixed[] $record Log message + * @return mixed[] + */ + protected function getDocument(array $record): array + { + $record['_index'] = $this->index; + $record['_type'] = $this->type; + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php new file mode 100644 index 0000000..41b56b3 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * formats the record to be used in the FlowdockHandler + * + * @author Dominik Liebler + */ +class FlowdockFormatter implements FormatterInterface +{ + /** + * @var string + */ + private $source; + + /** + * @var string + */ + private $sourceEmail; + + public function __construct(string $source, string $sourceEmail) + { + $this->source = $source; + $this->sourceEmail = $sourceEmail; + } + + /** + * {@inheritDoc} + * + * @return mixed[] + */ + public function format(array $record): array + { + $tags = [ + '#logs', + '#' . strtolower($record['level_name']), + '#' . $record['channel'], + ]; + + foreach ($record['extra'] as $value) { + $tags[] = '#' . $value; + } + + $subject = sprintf( + 'in %s: %s - %s', + $this->source, + $record['level_name'], + $this->getShortMessage($record['message']) + ); + + $record['flowdock'] = [ + 'source' => $this->source, + 'from_address' => $this->sourceEmail, + 'subject' => $subject, + 'content' => $record['message'], + 'tags' => $tags, + 'project' => $this->source, + ]; + + return $record; + } + + /** + * {@inheritDoc} + * + * @return mixed[][] + */ + public function formatBatch(array $records): array + { + $formatted = []; + + foreach ($records as $record) { + $formatted[] = $this->format($record); + } + + return $formatted; + } + + public function getShortMessage(string $message): string + { + static $hasMbString; + + if (null === $hasMbString) { + $hasMbString = function_exists('mb_strlen'); + } + + $maxLength = 45; + + if ($hasMbString) { + if (mb_strlen($message, 'UTF-8') > $maxLength) { + $message = mb_substr($message, 0, $maxLength - 4, 'UTF-8') . ' ...'; + } + } else { + if (strlen($message) > $maxLength) { + $message = substr($message, 0, $maxLength - 4) . ' ...'; + } + } + + return $message; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php new file mode 100644 index 0000000..29b14d3 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Utils; + +/** + * Class FluentdFormatter + * + * Serializes a log message to Fluentd unix socket protocol + * + * Fluentd config: + * + * + * type unix + * path /var/run/td-agent/td-agent.sock + * + * + * Monolog setup: + * + * $logger = new Monolog\Logger('fluent.tag'); + * $fluentHandler = new Monolog\Handler\SocketHandler('unix:///var/run/td-agent/td-agent.sock'); + * $fluentHandler->setFormatter(new Monolog\Formatter\FluentdFormatter()); + * $logger->pushHandler($fluentHandler); + * + * @author Andrius Putna + */ +class FluentdFormatter implements FormatterInterface +{ + /** + * @var bool $levelTag should message level be a part of the fluentd tag + */ + protected $levelTag = false; + + public function __construct(bool $levelTag = false) + { + if (!function_exists('json_encode')) { + throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s FluentdUnixFormatter'); + } + + $this->levelTag = $levelTag; + } + + public function isUsingLevelsInTag(): bool + { + return $this->levelTag; + } + + public function format(array $record): string + { + $tag = $record['channel']; + if ($this->levelTag) { + $tag .= '.' . strtolower($record['level_name']); + } + + $message = [ + 'message' => $record['message'], + 'context' => $record['context'], + 'extra' => $record['extra'], + ]; + + if (!$this->levelTag) { + $message['level'] = $record['level']; + $message['level_name'] = $record['level_name']; + } + + return Utils::jsonEncode([$tag, $record['datetime']->getTimestamp(), $message]); + } + + public function formatBatch(array $records): string + { + $message = ''; + foreach ($records as $record) { + $message .= $this->format($record); + } + + return $message; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php b/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php new file mode 100644 index 0000000..19617ec --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * Interface for formatters + * + * @author Jordi Boggiano + * + * @phpstan-import-type Record from \Monolog\Logger + */ +interface FormatterInterface +{ + /** + * Formats a log record. + * + * @param array $record A record to format + * @return mixed The formatted record + * + * @phpstan-param Record $record + */ + public function format(array $record); + + /** + * Formats a set of log records. + * + * @param array $records A set of records to format + * @return mixed The formatted set of records + * + * @phpstan-param Record[] $records + */ + public function formatBatch(array $records); +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php new file mode 100644 index 0000000..a563a1e --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php @@ -0,0 +1,156 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; +use Gelf\Message; +use Monolog\Utils; + +/** + * Serializes a log message to GELF + * @see http://docs.graylog.org/en/latest/pages/gelf.html + * + * @author Matt Lehner + * + * @phpstan-import-type Level from \Monolog\Logger + */ +class GelfMessageFormatter extends NormalizerFormatter +{ + protected const DEFAULT_MAX_LENGTH = 32766; + + /** + * @var string the name of the system for the Gelf log message + */ + protected $systemName; + + /** + * @var string a prefix for 'extra' fields from the Monolog record (optional) + */ + protected $extraPrefix; + + /** + * @var string a prefix for 'context' fields from the Monolog record (optional) + */ + protected $contextPrefix; + + /** + * @var int max length per field + */ + protected $maxLength; + + /** + * Translates Monolog log levels to Graylog2 log priorities. + * + * @var array + * + * @phpstan-var array + */ + private $logLevels = [ + Logger::DEBUG => 7, + Logger::INFO => 6, + Logger::NOTICE => 5, + Logger::WARNING => 4, + Logger::ERROR => 3, + Logger::CRITICAL => 2, + Logger::ALERT => 1, + Logger::EMERGENCY => 0, + ]; + + public function __construct(?string $systemName = null, ?string $extraPrefix = null, string $contextPrefix = 'ctxt_', ?int $maxLength = null) + { + parent::__construct('U.u'); + + $this->systemName = (is_null($systemName) || $systemName === '') ? (string) gethostname() : $systemName; + + $this->extraPrefix = is_null($extraPrefix) ? '' : $extraPrefix; + $this->contextPrefix = $contextPrefix; + $this->maxLength = is_null($maxLength) ? self::DEFAULT_MAX_LENGTH : $maxLength; + } + + /** + * {@inheritDoc} + */ + public function format(array $record): Message + { + $context = $extra = []; + if (isset($record['context'])) { + /** @var mixed[] $context */ + $context = parent::normalize($record['context']); + } + if (isset($record['extra'])) { + /** @var mixed[] $extra */ + $extra = parent::normalize($record['extra']); + } + + if (!isset($record['datetime'], $record['message'], $record['level'])) { + throw new \InvalidArgumentException('The record should at least contain datetime, message and level keys, '.var_export($record, true).' given'); + } + + $message = new Message(); + $message + ->setTimestamp($record['datetime']) + ->setShortMessage((string) $record['message']) + ->setHost($this->systemName) + ->setLevel($this->logLevels[$record['level']]); + + // message length + system name length + 200 for padding / metadata + $len = 200 + strlen((string) $record['message']) + strlen($this->systemName); + + if ($len > $this->maxLength) { + $message->setShortMessage(Utils::substr($record['message'], 0, $this->maxLength)); + } + + if (isset($record['channel'])) { + $message->setFacility($record['channel']); + } + if (isset($extra['line'])) { + $message->setLine($extra['line']); + unset($extra['line']); + } + if (isset($extra['file'])) { + $message->setFile($extra['file']); + unset($extra['file']); + } + + foreach ($extra as $key => $val) { + $val = is_scalar($val) || null === $val ? $val : $this->toJson($val); + $len = strlen($this->extraPrefix . $key . $val); + if ($len > $this->maxLength) { + $message->setAdditional($this->extraPrefix . $key, Utils::substr((string) $val, 0, $this->maxLength)); + + continue; + } + $message->setAdditional($this->extraPrefix . $key, $val); + } + + foreach ($context as $key => $val) { + $val = is_scalar($val) || null === $val ? $val : $this->toJson($val); + $len = strlen($this->contextPrefix . $key . $val); + if ($len > $this->maxLength) { + $message->setAdditional($this->contextPrefix . $key, Utils::substr((string) $val, 0, $this->maxLength)); + + continue; + } + $message->setAdditional($this->contextPrefix . $key, $val); + } + + /** @phpstan-ignore-next-line */ + if (null === $message->getFile() && isset($context['exception']['file'])) { + if (preg_match("/^(.+):([0-9]+)$/", $context['exception']['file'], $matches)) { + $message->setFile($matches[1]); + $message->setLine($matches[2]); + } + } + + return $message; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php new file mode 100644 index 0000000..10a4311 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php @@ -0,0 +1,142 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; +use Monolog\Utils; + +/** + * Formats incoming records into an HTML table + * + * This is especially useful for html email logging + * + * @author Tiago Brito + */ +class HtmlFormatter extends NormalizerFormatter +{ + /** + * Translates Monolog log levels to html color priorities. + * + * @var array + */ + protected $logLevels = [ + Logger::DEBUG => '#CCCCCC', + Logger::INFO => '#28A745', + Logger::NOTICE => '#17A2B8', + Logger::WARNING => '#FFC107', + Logger::ERROR => '#FD7E14', + Logger::CRITICAL => '#DC3545', + Logger::ALERT => '#821722', + Logger::EMERGENCY => '#000000', + ]; + + /** + * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format + */ + public function __construct(?string $dateFormat = null) + { + parent::__construct($dateFormat); + } + + /** + * Creates an HTML table row + * + * @param string $th Row header content + * @param string $td Row standard cell content + * @param bool $escapeTd false if td content must not be html escaped + */ + protected function addRow(string $th, string $td = ' ', bool $escapeTd = true): string + { + $th = htmlspecialchars($th, ENT_NOQUOTES, 'UTF-8'); + if ($escapeTd) { + $td = '
        '.htmlspecialchars($td, ENT_NOQUOTES, 'UTF-8').'
        '; + } + + return "\n$th:\n".$td."\n"; + } + + /** + * Create a HTML h1 tag + * + * @param string $title Text to be in the h1 + * @param int $level Error level + * @return string + */ + protected function addTitle(string $title, int $level): string + { + $title = htmlspecialchars($title, ENT_NOQUOTES, 'UTF-8'); + + return '

        '.$title.'

        '; + } + + /** + * Formats a log record. + * + * @return string The formatted record + */ + public function format(array $record): string + { + $output = $this->addTitle($record['level_name'], $record['level']); + $output .= ''; + + $output .= $this->addRow('Message', (string) $record['message']); + $output .= $this->addRow('Time', $this->formatDate($record['datetime'])); + $output .= $this->addRow('Channel', $record['channel']); + if ($record['context']) { + $embeddedTable = '
        '; + foreach ($record['context'] as $key => $value) { + $embeddedTable .= $this->addRow((string) $key, $this->convertToString($value)); + } + $embeddedTable .= '
        '; + $output .= $this->addRow('Context', $embeddedTable, false); + } + if ($record['extra']) { + $embeddedTable = ''; + foreach ($record['extra'] as $key => $value) { + $embeddedTable .= $this->addRow((string) $key, $this->convertToString($value)); + } + $embeddedTable .= '
        '; + $output .= $this->addRow('Extra', $embeddedTable, false); + } + + return $output.''; + } + + /** + * Formats a set of log records. + * + * @return string The formatted set of records + */ + public function formatBatch(array $records): string + { + $message = ''; + foreach ($records as $record) { + $message .= $this->format($record); + } + + return $message; + } + + /** + * @param mixed $data + */ + protected function convertToString($data): string + { + if (null === $data || is_scalar($data)) { + return (string) $data; + } + + $data = $this->normalize($data); + + return Utils::jsonEncode($data, JSON_PRETTY_PRINT | Utils::DEFAULT_JSON_FLAGS, true); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php new file mode 100644 index 0000000..46592ba --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php @@ -0,0 +1,208 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Throwable; + +/** + * Encodes whatever record data is passed to it as json + * + * This can be useful to log to databases or remote APIs + * + * @author Jordi Boggiano + * + * @phpstan-import-type Record from \Monolog\Logger + */ +class JsonFormatter extends NormalizerFormatter +{ + public const BATCH_MODE_JSON = 1; + public const BATCH_MODE_NEWLINES = 2; + + /** @var self::BATCH_MODE_* */ + protected $batchMode; + /** @var bool */ + protected $appendNewline; + /** @var bool */ + protected $ignoreEmptyContextAndExtra; + /** @var bool */ + protected $includeStacktraces = false; + + /** + * @param self::BATCH_MODE_* $batchMode + */ + public function __construct(int $batchMode = self::BATCH_MODE_JSON, bool $appendNewline = true, bool $ignoreEmptyContextAndExtra = false) + { + $this->batchMode = $batchMode; + $this->appendNewline = $appendNewline; + $this->ignoreEmptyContextAndExtra = $ignoreEmptyContextAndExtra; + + parent::__construct(); + } + + /** + * The batch mode option configures the formatting style for + * multiple records. By default, multiple records will be + * formatted as a JSON-encoded array. However, for + * compatibility with some API endpoints, alternative styles + * are available. + */ + public function getBatchMode(): int + { + return $this->batchMode; + } + + /** + * True if newlines are appended to every formatted record + */ + public function isAppendingNewlines(): bool + { + return $this->appendNewline; + } + + /** + * {@inheritDoc} + */ + public function format(array $record): string + { + $normalized = $this->normalize($record); + + if (isset($normalized['context']) && $normalized['context'] === []) { + if ($this->ignoreEmptyContextAndExtra) { + unset($normalized['context']); + } else { + $normalized['context'] = new \stdClass; + } + } + if (isset($normalized['extra']) && $normalized['extra'] === []) { + if ($this->ignoreEmptyContextAndExtra) { + unset($normalized['extra']); + } else { + $normalized['extra'] = new \stdClass; + } + } + + return $this->toJson($normalized, true) . ($this->appendNewline ? "\n" : ''); + } + + /** + * {@inheritDoc} + */ + public function formatBatch(array $records): string + { + switch ($this->batchMode) { + case static::BATCH_MODE_NEWLINES: + return $this->formatBatchNewlines($records); + + case static::BATCH_MODE_JSON: + default: + return $this->formatBatchJson($records); + } + } + + /** + * @return void + */ + public function includeStacktraces(bool $include = true) + { + $this->includeStacktraces = $include; + } + + /** + * Return a JSON-encoded array of records. + * + * @phpstan-param Record[] $records + */ + protected function formatBatchJson(array $records): string + { + return $this->toJson($this->normalize($records), true); + } + + /** + * Use new lines to separate records instead of a + * JSON-encoded array. + * + * @phpstan-param Record[] $records + */ + protected function formatBatchNewlines(array $records): string + { + $instance = $this; + + $oldNewline = $this->appendNewline; + $this->appendNewline = false; + array_walk($records, function (&$value, $key) use ($instance) { + $value = $instance->format($value); + }); + $this->appendNewline = $oldNewline; + + return implode("\n", $records); + } + + /** + * Normalizes given $data. + * + * @param mixed $data + * + * @return mixed + */ + protected function normalize($data, int $depth = 0) + { + if ($depth > $this->maxNormalizeDepth) { + return 'Over '.$this->maxNormalizeDepth.' levels deep, aborting normalization'; + } + + if (is_array($data)) { + $normalized = []; + + $count = 1; + foreach ($data as $key => $value) { + if ($count++ > $this->maxNormalizeItemCount) { + $normalized['...'] = 'Over '.$this->maxNormalizeItemCount.' items ('.count($data).' total), aborting normalization'; + break; + } + + $normalized[$key] = $this->normalize($value, $depth + 1); + } + + return $normalized; + } + + if ($data instanceof \DateTimeInterface) { + return $this->formatDate($data); + } + + if ($data instanceof Throwable) { + return $this->normalizeException($data, $depth); + } + + if (is_resource($data)) { + return parent::normalize($data); + } + + return $data; + } + + /** + * Normalizes given exception with or without its own stack trace based on + * `includeStacktraces` property. + * + * {@inheritDoc} + */ + protected function normalizeException(Throwable $e, int $depth = 0): array + { + $data = parent::normalizeException($e, $depth); + if (!$this->includeStacktraces) { + unset($data['trace']); + } + + return $data; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php new file mode 100644 index 0000000..6ed817a --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php @@ -0,0 +1,210 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Utils; + +/** + * Formats incoming records into a one-line string + * + * This is especially useful for logging to files + * + * @author Jordi Boggiano + * @author Christophe Coevoet + */ +class LineFormatter extends NormalizerFormatter +{ + public const SIMPLE_FORMAT = "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n"; + + /** @var string */ + protected $format; + /** @var bool */ + protected $allowInlineLineBreaks; + /** @var bool */ + protected $ignoreEmptyContextAndExtra; + /** @var bool */ + protected $includeStacktraces; + + /** + * @param string|null $format The format of the message + * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format + * @param bool $allowInlineLineBreaks Whether to allow inline line breaks in log entries + * @param bool $ignoreEmptyContextAndExtra + */ + public function __construct(?string $format = null, ?string $dateFormat = null, bool $allowInlineLineBreaks = false, bool $ignoreEmptyContextAndExtra = false) + { + $this->format = $format === null ? static::SIMPLE_FORMAT : $format; + $this->allowInlineLineBreaks = $allowInlineLineBreaks; + $this->ignoreEmptyContextAndExtra = $ignoreEmptyContextAndExtra; + parent::__construct($dateFormat); + } + + public function includeStacktraces(bool $include = true): void + { + $this->includeStacktraces = $include; + if ($this->includeStacktraces) { + $this->allowInlineLineBreaks = true; + } + } + + public function allowInlineLineBreaks(bool $allow = true): void + { + $this->allowInlineLineBreaks = $allow; + } + + public function ignoreEmptyContextAndExtra(bool $ignore = true): void + { + $this->ignoreEmptyContextAndExtra = $ignore; + } + + /** + * {@inheritDoc} + */ + public function format(array $record): string + { + $vars = parent::format($record); + + $output = $this->format; + + foreach ($vars['extra'] as $var => $val) { + if (false !== strpos($output, '%extra.'.$var.'%')) { + $output = str_replace('%extra.'.$var.'%', $this->stringify($val), $output); + unset($vars['extra'][$var]); + } + } + + foreach ($vars['context'] as $var => $val) { + if (false !== strpos($output, '%context.'.$var.'%')) { + $output = str_replace('%context.'.$var.'%', $this->stringify($val), $output); + unset($vars['context'][$var]); + } + } + + if ($this->ignoreEmptyContextAndExtra) { + if (empty($vars['context'])) { + unset($vars['context']); + $output = str_replace('%context%', '', $output); + } + + if (empty($vars['extra'])) { + unset($vars['extra']); + $output = str_replace('%extra%', '', $output); + } + } + + foreach ($vars as $var => $val) { + if (false !== strpos($output, '%'.$var.'%')) { + $output = str_replace('%'.$var.'%', $this->stringify($val), $output); + } + } + + // remove leftover %extra.xxx% and %context.xxx% if any + if (false !== strpos($output, '%')) { + $output = preg_replace('/%(?:extra|context)\..+?%/', '', $output); + if (null === $output) { + $pcreErrorCode = preg_last_error(); + throw new \RuntimeException('Failed to run preg_replace: ' . $pcreErrorCode . ' / ' . Utils::pcreLastErrorMessage($pcreErrorCode)); + } + } + + return $output; + } + + public function formatBatch(array $records): string + { + $message = ''; + foreach ($records as $record) { + $message .= $this->format($record); + } + + return $message; + } + + /** + * @param mixed $value + */ + public function stringify($value): string + { + return $this->replaceNewlines($this->convertToString($value)); + } + + protected function normalizeException(\Throwable $e, int $depth = 0): string + { + $str = $this->formatException($e); + + if ($previous = $e->getPrevious()) { + do { + $str .= "\n[previous exception] " . $this->formatException($previous); + } while ($previous = $previous->getPrevious()); + } + + return $str; + } + + /** + * @param mixed $data + */ + protected function convertToString($data): string + { + if (null === $data || is_bool($data)) { + return var_export($data, true); + } + + if (is_scalar($data)) { + return (string) $data; + } + + return $this->toJson($data, true); + } + + protected function replaceNewlines(string $str): string + { + if ($this->allowInlineLineBreaks) { + if (0 === strpos($str, '{')) { + return str_replace(array('\r', '\n'), array("\r", "\n"), $str); + } + + return $str; + } + + return str_replace(["\r\n", "\r", "\n"], ' ', $str); + } + + private function formatException(\Throwable $e): string + { + $str = '[object] (' . Utils::getClass($e) . '(code: ' . $e->getCode(); + if ($e instanceof \SoapFault) { + if (isset($e->faultcode)) { + $str .= ' faultcode: ' . $e->faultcode; + } + + if (isset($e->faultactor)) { + $str .= ' faultactor: ' . $e->faultactor; + } + + if (isset($e->detail)) { + if (is_string($e->detail)) { + $str .= ' detail: ' . $e->detail; + } elseif (is_object($e->detail) || is_array($e->detail)) { + $str .= ' detail: ' . $this->toJson($e->detail, true); + } + } + } + $str .= '): ' . $e->getMessage() . ' at ' . $e->getFile() . ':' . $e->getLine() . ')'; + + if ($this->includeStacktraces) { + $str .= "\n[stacktrace]\n" . $e->getTraceAsString() . "\n"; + } + + return $str; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php new file mode 100644 index 0000000..29841aa --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * Encodes message information into JSON in a format compatible with Loggly. + * + * @author Adam Pancutt + */ +class LogglyFormatter extends JsonFormatter +{ + /** + * Overrides the default batch mode to new lines for compatibility with the + * Loggly bulk API. + */ + public function __construct(int $batchMode = self::BATCH_MODE_NEWLINES, bool $appendNewline = false) + { + parent::__construct($batchMode, $appendNewline); + } + + /** + * Appends the 'timestamp' parameter for indexing by Loggly. + * + * @see https://www.loggly.com/docs/automated-parsing/#json + * @see \Monolog\Formatter\JsonFormatter::format() + */ + public function format(array $record): string + { + if (isset($record["datetime"]) && ($record["datetime"] instanceof \DateTimeInterface)) { + $record["timestamp"] = $record["datetime"]->format("Y-m-d\TH:i:s.uO"); + unset($record["datetime"]); + } + + return parent::format($record); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/LogmaticFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/LogmaticFormatter.php new file mode 100644 index 0000000..b0451ab --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/LogmaticFormatter.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * Encodes message information into JSON in a format compatible with Logmatic. + * + * @author Julien Breux + */ +class LogmaticFormatter extends JsonFormatter +{ + protected const MARKERS = ["sourcecode", "php"]; + + /** + * @var string + */ + protected $hostname = ''; + + /** + * @var string + */ + protected $appname = ''; + + public function setHostname(string $hostname): self + { + $this->hostname = $hostname; + + return $this; + } + + public function setAppname(string $appname): self + { + $this->appname = $appname; + + return $this; + } + + /** + * Appends the 'hostname' and 'appname' parameter for indexing by Logmatic. + * + * @see http://doc.logmatic.io/docs/basics-to-send-data + * @see \Monolog\Formatter\JsonFormatter::format() + */ + public function format(array $record): string + { + if (!empty($this->hostname)) { + $record["hostname"] = $this->hostname; + } + if (!empty($this->appname)) { + $record["appname"] = $this->appname; + } + + $record["@marker"] = static::MARKERS; + + return parent::format($record); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php new file mode 100644 index 0000000..f8de0d3 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * Serializes a log message to Logstash Event Format + * + * @see https://www.elastic.co/products/logstash + * @see https://github.com/elastic/logstash/blob/master/logstash-core/src/main/java/org/logstash/Event.java + * + * @author Tim Mower + */ +class LogstashFormatter extends NormalizerFormatter +{ + /** + * @var string the name of the system for the Logstash log message, used to fill the @source field + */ + protected $systemName; + + /** + * @var string an application name for the Logstash log message, used to fill the @type field + */ + protected $applicationName; + + /** + * @var string the key for 'extra' fields from the Monolog record + */ + protected $extraKey; + + /** + * @var string the key for 'context' fields from the Monolog record + */ + protected $contextKey; + + /** + * @param string $applicationName The application that sends the data, used as the "type" field of logstash + * @param string|null $systemName The system/machine name, used as the "source" field of logstash, defaults to the hostname of the machine + * @param string $extraKey The key for extra keys inside logstash "fields", defaults to extra + * @param string $contextKey The key for context keys inside logstash "fields", defaults to context + */ + public function __construct(string $applicationName, ?string $systemName = null, string $extraKey = 'extra', string $contextKey = 'context') + { + // logstash requires a ISO 8601 format date with optional millisecond precision. + parent::__construct('Y-m-d\TH:i:s.uP'); + + $this->systemName = $systemName === null ? (string) gethostname() : $systemName; + $this->applicationName = $applicationName; + $this->extraKey = $extraKey; + $this->contextKey = $contextKey; + } + + /** + * {@inheritDoc} + */ + public function format(array $record): string + { + $record = parent::format($record); + + if (empty($record['datetime'])) { + $record['datetime'] = gmdate('c'); + } + $message = [ + '@timestamp' => $record['datetime'], + '@version' => 1, + 'host' => $this->systemName, + ]; + if (isset($record['message'])) { + $message['message'] = $record['message']; + } + if (isset($record['channel'])) { + $message['type'] = $record['channel']; + $message['channel'] = $record['channel']; + } + if (isset($record['level_name'])) { + $message['level'] = $record['level_name']; + } + if (isset($record['level'])) { + $message['monolog_level'] = $record['level']; + } + if ($this->applicationName) { + $message['type'] = $this->applicationName; + } + if (!empty($record['extra'])) { + $message[$this->extraKey] = $record['extra']; + } + if (!empty($record['context'])) { + $message[$this->contextKey] = $record['context']; + } + + return $this->toJson($message) . "\n"; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php new file mode 100644 index 0000000..202d30e --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php @@ -0,0 +1,161 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use MongoDB\BSON\UTCDateTime; +use Monolog\Utils; + +/** + * Formats a record for use with the MongoDBHandler. + * + * @author Florian Plattner + */ +class MongoDBFormatter implements FormatterInterface +{ + /** @var bool */ + private $exceptionTraceAsString; + /** @var int */ + private $maxNestingLevel; + /** @var bool */ + private $isLegacyMongoExt; + + /** + * @param int $maxNestingLevel 0 means infinite nesting, the $record itself is level 1, $record['context'] is 2 + * @param bool $exceptionTraceAsString set to false to log exception traces as a sub documents instead of strings + */ + public function __construct(int $maxNestingLevel = 3, bool $exceptionTraceAsString = true) + { + $this->maxNestingLevel = max($maxNestingLevel, 0); + $this->exceptionTraceAsString = $exceptionTraceAsString; + + $this->isLegacyMongoExt = extension_loaded('mongodb') && version_compare((string) phpversion('mongodb'), '1.1.9', '<='); + } + + /** + * {@inheritDoc} + * + * @return mixed[] + */ + public function format(array $record): array + { + /** @var mixed[] $res */ + $res = $this->formatArray($record); + + return $res; + } + + /** + * {@inheritDoc} + * + * @return array + */ + public function formatBatch(array $records): array + { + $formatted = []; + foreach ($records as $key => $record) { + $formatted[$key] = $this->format($record); + } + + return $formatted; + } + + /** + * @param mixed[] $array + * @return mixed[]|string Array except when max nesting level is reached then a string "[...]" + */ + protected function formatArray(array $array, int $nestingLevel = 0) + { + if ($this->maxNestingLevel > 0 && $nestingLevel > $this->maxNestingLevel) { + return '[...]'; + } + + foreach ($array as $name => $value) { + if ($value instanceof \DateTimeInterface) { + $array[$name] = $this->formatDate($value, $nestingLevel + 1); + } elseif ($value instanceof \Throwable) { + $array[$name] = $this->formatException($value, $nestingLevel + 1); + } elseif (is_array($value)) { + $array[$name] = $this->formatArray($value, $nestingLevel + 1); + } elseif (is_object($value)) { + $array[$name] = $this->formatObject($value, $nestingLevel + 1); + } + } + + return $array; + } + + /** + * @param mixed $value + * @return mixed[]|string + */ + protected function formatObject($value, int $nestingLevel) + { + $objectVars = get_object_vars($value); + $objectVars['class'] = Utils::getClass($value); + + return $this->formatArray($objectVars, $nestingLevel); + } + + /** + * @return mixed[]|string + */ + protected function formatException(\Throwable $exception, int $nestingLevel) + { + $formattedException = [ + 'class' => Utils::getClass($exception), + 'message' => $exception->getMessage(), + 'code' => (int) $exception->getCode(), + 'file' => $exception->getFile() . ':' . $exception->getLine(), + ]; + + if ($this->exceptionTraceAsString === true) { + $formattedException['trace'] = $exception->getTraceAsString(); + } else { + $formattedException['trace'] = $exception->getTrace(); + } + + return $this->formatArray($formattedException, $nestingLevel); + } + + protected function formatDate(\DateTimeInterface $value, int $nestingLevel): UTCDateTime + { + if ($this->isLegacyMongoExt) { + return $this->legacyGetMongoDbDateTime($value); + } + + return $this->getMongoDbDateTime($value); + } + + private function getMongoDbDateTime(\DateTimeInterface $value): UTCDateTime + { + return new UTCDateTime((int) floor(((float) $value->format('U.u')) * 1000)); + } + + /** + * This is needed to support MongoDB Driver v1.19 and below + * + * See https://github.com/mongodb/mongo-php-driver/issues/426 + * + * It can probably be removed in 2.1 or later once MongoDB's 1.2 is released and widely adopted + */ + private function legacyGetMongoDbDateTime(\DateTimeInterface $value): UTCDateTime + { + $milliseconds = floor(((float) $value->format('U.u')) * 1000); + + $milliseconds = (PHP_INT_SIZE == 8) //64-bit OS? + ? (int) $milliseconds + : (string) $milliseconds; + + // @phpstan-ignore-next-line + return new UTCDateTime($milliseconds); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php new file mode 100644 index 0000000..01f75a4 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php @@ -0,0 +1,279 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\DateTimeImmutable; +use Monolog\Utils; +use Throwable; + +/** + * Normalizes incoming records to remove objects/resources so it's easier to dump to various targets + * + * @author Jordi Boggiano + */ +class NormalizerFormatter implements FormatterInterface +{ + public const SIMPLE_DATE = "Y-m-d\TH:i:sP"; + + /** @var string */ + protected $dateFormat; + /** @var int */ + protected $maxNormalizeDepth = 9; + /** @var int */ + protected $maxNormalizeItemCount = 1000; + + /** @var int */ + private $jsonEncodeOptions = Utils::DEFAULT_JSON_FLAGS; + + /** + * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format + */ + public function __construct(?string $dateFormat = null) + { + $this->dateFormat = null === $dateFormat ? static::SIMPLE_DATE : $dateFormat; + if (!function_exists('json_encode')) { + throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s NormalizerFormatter'); + } + } + + /** + * {@inheritDoc} + * + * @param mixed[] $record + */ + public function format(array $record) + { + return $this->normalize($record); + } + + /** + * {@inheritDoc} + */ + public function formatBatch(array $records) + { + foreach ($records as $key => $record) { + $records[$key] = $this->format($record); + } + + return $records; + } + + public function getDateFormat(): string + { + return $this->dateFormat; + } + + public function setDateFormat(string $dateFormat): self + { + $this->dateFormat = $dateFormat; + + return $this; + } + + /** + * The maximum number of normalization levels to go through + */ + public function getMaxNormalizeDepth(): int + { + return $this->maxNormalizeDepth; + } + + public function setMaxNormalizeDepth(int $maxNormalizeDepth): self + { + $this->maxNormalizeDepth = $maxNormalizeDepth; + + return $this; + } + + /** + * The maximum number of items to normalize per level + */ + public function getMaxNormalizeItemCount(): int + { + return $this->maxNormalizeItemCount; + } + + public function setMaxNormalizeItemCount(int $maxNormalizeItemCount): self + { + $this->maxNormalizeItemCount = $maxNormalizeItemCount; + + return $this; + } + + /** + * Enables `json_encode` pretty print. + */ + public function setJsonPrettyPrint(bool $enable): self + { + if ($enable) { + $this->jsonEncodeOptions |= JSON_PRETTY_PRINT; + } else { + $this->jsonEncodeOptions &= ~JSON_PRETTY_PRINT; + } + + return $this; + } + + /** + * @param mixed $data + * @return null|scalar|array + */ + protected function normalize($data, int $depth = 0) + { + if ($depth > $this->maxNormalizeDepth) { + return 'Over ' . $this->maxNormalizeDepth . ' levels deep, aborting normalization'; + } + + if (null === $data || is_scalar($data)) { + if (is_float($data)) { + if (is_infinite($data)) { + return ($data > 0 ? '' : '-') . 'INF'; + } + if (is_nan($data)) { + return 'NaN'; + } + } + + return $data; + } + + if (is_array($data)) { + $normalized = []; + + $count = 1; + foreach ($data as $key => $value) { + if ($count++ > $this->maxNormalizeItemCount) { + $normalized['...'] = 'Over ' . $this->maxNormalizeItemCount . ' items ('.count($data).' total), aborting normalization'; + break; + } + + $normalized[$key] = $this->normalize($value, $depth + 1); + } + + return $normalized; + } + + if ($data instanceof \DateTimeInterface) { + return $this->formatDate($data); + } + + if (is_object($data)) { + if ($data instanceof Throwable) { + return $this->normalizeException($data, $depth); + } + + if ($data instanceof \JsonSerializable) { + /** @var null|scalar|array $value */ + $value = $data->jsonSerialize(); + } elseif (method_exists($data, '__toString')) { + /** @var string $value */ + $value = $data->__toString(); + } else { + // the rest is normalized by json encoding and decoding it + /** @var null|scalar|array $value */ + $value = json_decode($this->toJson($data, true), true); + } + + return [Utils::getClass($data) => $value]; + } + + if (is_resource($data)) { + return sprintf('[resource(%s)]', get_resource_type($data)); + } + + return '[unknown('.gettype($data).')]'; + } + + /** + * @return mixed[] + */ + protected function normalizeException(Throwable $e, int $depth = 0) + { + if ($e instanceof \JsonSerializable) { + return (array) $e->jsonSerialize(); + } + + $data = [ + 'class' => Utils::getClass($e), + 'message' => $e->getMessage(), + 'code' => (int) $e->getCode(), + 'file' => $e->getFile().':'.$e->getLine(), + ]; + + if ($e instanceof \SoapFault) { + if (isset($e->faultcode)) { + $data['faultcode'] = $e->faultcode; + } + + if (isset($e->faultactor)) { + $data['faultactor'] = $e->faultactor; + } + + if (isset($e->detail)) { + if (is_string($e->detail)) { + $data['detail'] = $e->detail; + } elseif (is_object($e->detail) || is_array($e->detail)) { + $data['detail'] = $this->toJson($e->detail, true); + } + } + } + + $trace = $e->getTrace(); + foreach ($trace as $frame) { + if (isset($frame['file'])) { + $data['trace'][] = $frame['file'].':'.$frame['line']; + } + } + + if ($previous = $e->getPrevious()) { + $data['previous'] = $this->normalizeException($previous, $depth + 1); + } + + return $data; + } + + /** + * Return the JSON representation of a value + * + * @param mixed $data + * @throws \RuntimeException if encoding fails and errors are not ignored + * @return string if encoding fails and ignoreErrors is true 'null' is returned + */ + protected function toJson($data, bool $ignoreErrors = false): string + { + return Utils::jsonEncode($data, $this->jsonEncodeOptions, $ignoreErrors); + } + + /** + * @return string + */ + protected function formatDate(\DateTimeInterface $date) + { + // in case the date format isn't custom then we defer to the custom DateTimeImmutable + // formatting logic, which will pick the right format based on whether useMicroseconds is on + if ($this->dateFormat === self::SIMPLE_DATE && $date instanceof DateTimeImmutable) { + return (string) $date; + } + + return $date->format($this->dateFormat); + } + + public function addJsonEncodeOption(int $option): void + { + $this->jsonEncodeOptions |= $option; + } + + public function removeJsonEncodeOption(int $option): void + { + $this->jsonEncodeOptions &= ~$option; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php new file mode 100644 index 0000000..187bc55 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * Formats data into an associative array of scalar values. + * Objects and arrays will be JSON encoded. + * + * @author Andrew Lawson + */ +class ScalarFormatter extends NormalizerFormatter +{ + /** + * {@inheritDoc} + * + * @phpstan-return array $record + */ + public function format(array $record): array + { + $result = []; + foreach ($record as $key => $value) { + $result[$key] = $this->normalizeValue($value); + } + + return $result; + } + + /** + * @param mixed $value + * @return scalar|null + */ + protected function normalizeValue($value) + { + $normalized = $this->normalize($value); + + if (is_array($normalized)) { + return $this->toJson($normalized, true); + } + + return $normalized; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php new file mode 100644 index 0000000..6539b34 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php @@ -0,0 +1,139 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +/** + * Serializes a log message according to Wildfire's header requirements + * + * @author Eric Clemmons (@ericclemmons) + * @author Christophe Coevoet + * @author Kirill chEbba Chebunin + * + * @phpstan-import-type Level from \Monolog\Logger + */ +class WildfireFormatter extends NormalizerFormatter +{ + /** + * Translates Monolog log levels to Wildfire levels. + * + * @var array + */ + private $logLevels = [ + Logger::DEBUG => 'LOG', + Logger::INFO => 'INFO', + Logger::NOTICE => 'INFO', + Logger::WARNING => 'WARN', + Logger::ERROR => 'ERROR', + Logger::CRITICAL => 'ERROR', + Logger::ALERT => 'ERROR', + Logger::EMERGENCY => 'ERROR', + ]; + + /** + * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format + */ + public function __construct(?string $dateFormat = null) + { + parent::__construct($dateFormat); + + // http headers do not like non-ISO-8559-1 characters + $this->removeJsonEncodeOption(JSON_UNESCAPED_UNICODE); + } + + /** + * {@inheritDoc} + * + * @return string + */ + public function format(array $record): string + { + // Retrieve the line and file if set and remove them from the formatted extra + $file = $line = ''; + if (isset($record['extra']['file'])) { + $file = $record['extra']['file']; + unset($record['extra']['file']); + } + if (isset($record['extra']['line'])) { + $line = $record['extra']['line']; + unset($record['extra']['line']); + } + + /** @var mixed[] $record */ + $record = $this->normalize($record); + $message = ['message' => $record['message']]; + $handleError = false; + if ($record['context']) { + $message['context'] = $record['context']; + $handleError = true; + } + if ($record['extra']) { + $message['extra'] = $record['extra']; + $handleError = true; + } + if (count($message) === 1) { + $message = reset($message); + } + + if (isset($record['context']['table'])) { + $type = 'TABLE'; + $label = $record['channel'] .': '. $record['message']; + $message = $record['context']['table']; + } else { + $type = $this->logLevels[$record['level']]; + $label = $record['channel']; + } + + // Create JSON object describing the appearance of the message in the console + $json = $this->toJson([ + [ + 'Type' => $type, + 'File' => $file, + 'Line' => $line, + 'Label' => $label, + ], + $message, + ], $handleError); + + // The message itself is a serialization of the above JSON object + it's length + return sprintf( + '%d|%s|', + strlen($json), + $json + ); + } + + /** + * {@inheritDoc} + * + * @phpstan-return never + */ + public function formatBatch(array $records) + { + throw new \BadMethodCallException('Batch formatting does not make sense for the WildfireFormatter'); + } + + /** + * {@inheritDoc} + * + * @return null|scalar|array|object + */ + protected function normalize($data, int $depth = 0) + { + if (is_object($data) && !$data instanceof \DateTimeInterface) { + return $data; + } + + return parent::normalize($data, $depth); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php new file mode 100644 index 0000000..a5cdaa7 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\ResettableInterface; +use Psr\Log\LogLevel; + +/** + * Base Handler class providing basic level/bubble support + * + * @author Jordi Boggiano + * + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger + */ +abstract class AbstractHandler extends Handler implements ResettableInterface +{ + /** + * @var int + * @phpstan-var Level + */ + protected $level = Logger::DEBUG; + /** @var bool */ + protected $bubble = true; + + /** + * @param int|string $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * + * @phpstan-param Level|LevelName|LogLevel::* $level + */ + public function __construct($level = Logger::DEBUG, bool $bubble = true) + { + $this->setLevel($level); + $this->bubble = $bubble; + } + + /** + * {@inheritDoc} + */ + public function isHandling(array $record): bool + { + return $record['level'] >= $this->level; + } + + /** + * Sets minimum logging level at which this handler will be triggered. + * + * @param Level|LevelName|LogLevel::* $level Level or level name + * @return self + */ + public function setLevel($level): self + { + $this->level = Logger::toMonologLevel($level); + + return $this; + } + + /** + * Gets minimum logging level at which this handler will be triggered. + * + * @return int + * + * @phpstan-return Level + */ + public function getLevel(): int + { + return $this->level; + } + + /** + * Sets the bubbling behavior. + * + * @param bool $bubble true means that this handler allows bubbling. + * false means that bubbling is not permitted. + * @return self + */ + public function setBubble(bool $bubble): self + { + $this->bubble = $bubble; + + return $this; + } + + /** + * Gets the bubbling behavior. + * + * @return bool true means that this handler allows bubbling. + * false means that bubbling is not permitted. + */ + public function getBubble(): bool + { + return $this->bubble; + } + + /** + * {@inheritDoc} + */ + public function reset() + { + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php new file mode 100644 index 0000000..77e533f --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Base Handler class providing the Handler structure, including processors and formatters + * + * Classes extending it should (in most cases) only implement write($record) + * + * @author Jordi Boggiano + * @author Christophe Coevoet + * + * @phpstan-import-type LevelName from \Monolog\Logger + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type Record from \Monolog\Logger + * @phpstan-type FormattedRecord array{message: string, context: mixed[], level: Level, level_name: LevelName, channel: string, datetime: \DateTimeImmutable, extra: mixed[], formatted: mixed} + */ +abstract class AbstractProcessingHandler extends AbstractHandler implements ProcessableHandlerInterface, FormattableHandlerInterface +{ + use ProcessableHandlerTrait; + use FormattableHandlerTrait; + + /** + * {@inheritDoc} + */ + public function handle(array $record): bool + { + if (!$this->isHandling($record)) { + return false; + } + + if ($this->processors) { + /** @var Record $record */ + $record = $this->processRecord($record); + } + + $record['formatted'] = $this->getFormatter()->format($record); + + $this->write($record); + + return false === $this->bubble; + } + + /** + * Writes the record down to the log of the implementing handler + * + * @phpstan-param FormattedRecord $record + */ + abstract protected function write(array $record): void; + + /** + * @return void + */ + public function reset() + { + parent::reset(); + + $this->resetProcessors(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php new file mode 100644 index 0000000..5e5ad1c --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LineFormatter; + +/** + * Common syslog functionality + * + * @phpstan-import-type Level from \Monolog\Logger + */ +abstract class AbstractSyslogHandler extends AbstractProcessingHandler +{ + /** @var int */ + protected $facility; + + /** + * Translates Monolog log levels to syslog log priorities. + * @var array + * @phpstan-var array + */ + protected $logLevels = [ + Logger::DEBUG => LOG_DEBUG, + Logger::INFO => LOG_INFO, + Logger::NOTICE => LOG_NOTICE, + Logger::WARNING => LOG_WARNING, + Logger::ERROR => LOG_ERR, + Logger::CRITICAL => LOG_CRIT, + Logger::ALERT => LOG_ALERT, + Logger::EMERGENCY => LOG_EMERG, + ]; + + /** + * List of valid log facility names. + * @var array + */ + protected $facilities = [ + 'auth' => LOG_AUTH, + 'authpriv' => LOG_AUTHPRIV, + 'cron' => LOG_CRON, + 'daemon' => LOG_DAEMON, + 'kern' => LOG_KERN, + 'lpr' => LOG_LPR, + 'mail' => LOG_MAIL, + 'news' => LOG_NEWS, + 'syslog' => LOG_SYSLOG, + 'user' => LOG_USER, + 'uucp' => LOG_UUCP, + ]; + + /** + * @param string|int $facility Either one of the names of the keys in $this->facilities, or a LOG_* facility constant + */ + public function __construct($facility = LOG_USER, $level = Logger::DEBUG, bool $bubble = true) + { + parent::__construct($level, $bubble); + + if (!defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->facilities['local0'] = LOG_LOCAL0; + $this->facilities['local1'] = LOG_LOCAL1; + $this->facilities['local2'] = LOG_LOCAL2; + $this->facilities['local3'] = LOG_LOCAL3; + $this->facilities['local4'] = LOG_LOCAL4; + $this->facilities['local5'] = LOG_LOCAL5; + $this->facilities['local6'] = LOG_LOCAL6; + $this->facilities['local7'] = LOG_LOCAL7; + } else { + $this->facilities['local0'] = 128; // LOG_LOCAL0 + $this->facilities['local1'] = 136; // LOG_LOCAL1 + $this->facilities['local2'] = 144; // LOG_LOCAL2 + $this->facilities['local3'] = 152; // LOG_LOCAL3 + $this->facilities['local4'] = 160; // LOG_LOCAL4 + $this->facilities['local5'] = 168; // LOG_LOCAL5 + $this->facilities['local6'] = 176; // LOG_LOCAL6 + $this->facilities['local7'] = 184; // LOG_LOCAL7 + } + + // convert textual description of facility to syslog constant + if (is_string($facility) && array_key_exists(strtolower($facility), $this->facilities)) { + $facility = $this->facilities[strtolower($facility)]; + } elseif (!in_array($facility, array_values($this->facilities), true)) { + throw new \UnexpectedValueException('Unknown facility value "'.$facility.'" given'); + } + + $this->facility = $facility; + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new LineFormatter('%channel%.%level_name%: %message% %context% %extra%'); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php new file mode 100644 index 0000000..e30d784 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php @@ -0,0 +1,141 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\JsonFormatter; +use PhpAmqpLib\Message\AMQPMessage; +use PhpAmqpLib\Channel\AMQPChannel; +use AMQPExchange; + +/** + * @phpstan-import-type Record from \Monolog\Logger + */ +class AmqpHandler extends AbstractProcessingHandler +{ + /** + * @var AMQPExchange|AMQPChannel $exchange + */ + protected $exchange; + + /** + * @var string + */ + protected $exchangeName; + + /** + * @param AMQPExchange|AMQPChannel $exchange AMQPExchange (php AMQP ext) or PHP AMQP lib channel, ready for use + * @param string|null $exchangeName Optional exchange name, for AMQPChannel (PhpAmqpLib) only + */ + public function __construct($exchange, ?string $exchangeName = null, $level = Logger::DEBUG, bool $bubble = true) + { + if ($exchange instanceof AMQPChannel) { + $this->exchangeName = (string) $exchangeName; + } elseif (!$exchange instanceof AMQPExchange) { + throw new \InvalidArgumentException('PhpAmqpLib\Channel\AMQPChannel or AMQPExchange instance required'); + } elseif ($exchangeName) { + @trigger_error('The $exchangeName parameter can only be passed when using PhpAmqpLib, if using an AMQPExchange instance configure it beforehand', E_USER_DEPRECATED); + } + $this->exchange = $exchange; + + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + $data = $record["formatted"]; + $routingKey = $this->getRoutingKey($record); + + if ($this->exchange instanceof AMQPExchange) { + $this->exchange->publish( + $data, + $routingKey, + 0, + [ + 'delivery_mode' => 2, + 'content_type' => 'application/json', + ] + ); + } else { + $this->exchange->basic_publish( + $this->createAmqpMessage($data), + $this->exchangeName, + $routingKey + ); + } + } + + /** + * {@inheritDoc} + */ + public function handleBatch(array $records): void + { + if ($this->exchange instanceof AMQPExchange) { + parent::handleBatch($records); + + return; + } + + foreach ($records as $record) { + if (!$this->isHandling($record)) { + continue; + } + + /** @var Record $record */ + $record = $this->processRecord($record); + $data = $this->getFormatter()->format($record); + + $this->exchange->batch_basic_publish( + $this->createAmqpMessage($data), + $this->exchangeName, + $this->getRoutingKey($record) + ); + } + + $this->exchange->publish_batch(); + } + + /** + * Gets the routing key for the AMQP exchange + * + * @phpstan-param Record $record + */ + protected function getRoutingKey(array $record): string + { + $routingKey = sprintf('%s.%s', $record['level_name'], $record['channel']); + + return strtolower($routingKey); + } + + private function createAmqpMessage(string $data): AMQPMessage + { + return new AMQPMessage( + $data, + [ + 'delivery_mode' => 2, + 'content_type' => 'application/json', + ] + ); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php new file mode 100644 index 0000000..8d908b2 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php @@ -0,0 +1,270 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Formatter\FormatterInterface; +use Monolog\Utils; + +/** + * Handler sending logs to browser's javascript console with no browser extension required + * + * @author Olivier Poitrey + * + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler + */ +class BrowserConsoleHandler extends AbstractProcessingHandler +{ + /** @var bool */ + protected static $initialized = false; + /** @var FormattedRecord[] */ + protected static $records = []; + + /** + * {@inheritDoc} + * + * Formatted output may contain some formatting markers to be transferred to `console.log` using the %c format. + * + * Example of formatted string: + * + * You can do [[blue text]]{color: blue} or [[green background]]{background-color: green; color: white} + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new LineFormatter('[[%channel%]]{macro: autolabel} [[%level_name%]]{font-weight: bold} %message%'); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + // Accumulate records + static::$records[] = $record; + + // Register shutdown handler if not already done + if (!static::$initialized) { + static::$initialized = true; + $this->registerShutdownFunction(); + } + } + + /** + * Convert records to javascript console commands and send it to the browser. + * This method is automatically called on PHP shutdown if output is HTML or Javascript. + */ + public static function send(): void + { + $format = static::getResponseFormat(); + if ($format === 'unknown') { + return; + } + + if (count(static::$records)) { + if ($format === 'html') { + static::writeOutput(''); + } elseif ($format === 'js') { + static::writeOutput(static::generateScript()); + } + static::resetStatic(); + } + } + + public function close(): void + { + self::resetStatic(); + } + + public function reset() + { + parent::reset(); + + self::resetStatic(); + } + + /** + * Forget all logged records + */ + public static function resetStatic(): void + { + static::$records = []; + } + + /** + * Wrapper for register_shutdown_function to allow overriding + */ + protected function registerShutdownFunction(): void + { + if (PHP_SAPI !== 'cli') { + register_shutdown_function(['Monolog\Handler\BrowserConsoleHandler', 'send']); + } + } + + /** + * Wrapper for echo to allow overriding + */ + protected static function writeOutput(string $str): void + { + echo $str; + } + + /** + * Checks the format of the response + * + * If Content-Type is set to application/javascript or text/javascript -> js + * If Content-Type is set to text/html, or is unset -> html + * If Content-Type is anything else -> unknown + * + * @return string One of 'js', 'html' or 'unknown' + */ + protected static function getResponseFormat(): string + { + // Check content type + foreach (headers_list() as $header) { + if (stripos($header, 'content-type:') === 0) { + // This handler only works with HTML and javascript outputs + // text/javascript is obsolete in favour of application/javascript, but still used + if (stripos($header, 'application/javascript') !== false || stripos($header, 'text/javascript') !== false) { + return 'js'; + } + if (stripos($header, 'text/html') === false) { + return 'unknown'; + } + break; + } + } + + return 'html'; + } + + private static function generateScript(): string + { + $script = []; + foreach (static::$records as $record) { + $context = static::dump('Context', $record['context']); + $extra = static::dump('Extra', $record['extra']); + + if (empty($context) && empty($extra)) { + $script[] = static::call_array('log', static::handleStyles($record['formatted'])); + } else { + $script = array_merge( + $script, + [static::call_array('groupCollapsed', static::handleStyles($record['formatted']))], + $context, + $extra, + [static::call('groupEnd')] + ); + } + } + + return "(function (c) {if (c && c.groupCollapsed) {\n" . implode("\n", $script) . "\n}})(console);"; + } + + /** + * @return string[] + */ + private static function handleStyles(string $formatted): array + { + $args = []; + $format = '%c' . $formatted; + preg_match_all('/\[\[(.*?)\]\]\{([^}]*)\}/s', $format, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER); + + foreach (array_reverse($matches) as $match) { + $args[] = '"font-weight: normal"'; + $args[] = static::quote(static::handleCustomStyles($match[2][0], $match[1][0])); + + $pos = $match[0][1]; + $format = Utils::substr($format, 0, $pos) . '%c' . $match[1][0] . '%c' . Utils::substr($format, $pos + strlen($match[0][0])); + } + + $args[] = static::quote('font-weight: normal'); + $args[] = static::quote($format); + + return array_reverse($args); + } + + private static function handleCustomStyles(string $style, string $string): string + { + static $colors = ['blue', 'green', 'red', 'magenta', 'orange', 'black', 'grey']; + static $labels = []; + + $style = preg_replace_callback('/macro\s*:(.*?)(?:;|$)/', function (array $m) use ($string, &$colors, &$labels) { + if (trim($m[1]) === 'autolabel') { + // Format the string as a label with consistent auto assigned background color + if (!isset($labels[$string])) { + $labels[$string] = $colors[count($labels) % count($colors)]; + } + $color = $labels[$string]; + + return "background-color: $color; color: white; border-radius: 3px; padding: 0 2px 0 2px"; + } + + return $m[1]; + }, $style); + + if (null === $style) { + $pcreErrorCode = preg_last_error(); + throw new \RuntimeException('Failed to run preg_replace_callback: ' . $pcreErrorCode . ' / ' . Utils::pcreLastErrorMessage($pcreErrorCode)); + } + + return $style; + } + + /** + * @param mixed[] $dict + * @return mixed[] + */ + private static function dump(string $title, array $dict): array + { + $script = []; + $dict = array_filter($dict); + if (empty($dict)) { + return $script; + } + $script[] = static::call('log', static::quote('%c%s'), static::quote('font-weight: bold'), static::quote($title)); + foreach ($dict as $key => $value) { + $value = json_encode($value); + if (empty($value)) { + $value = static::quote(''); + } + $script[] = static::call('log', static::quote('%s: %o'), static::quote((string) $key), $value); + } + + return $script; + } + + private static function quote(string $arg): string + { + return '"' . addcslashes($arg, "\"\n\\") . '"'; + } + + /** + * @param mixed $args + */ + private static function call(...$args): string + { + $method = array_shift($args); + if (!is_string($method)) { + throw new \UnexpectedValueException('Expected the first arg to be a string, got: '.var_export($method, true)); + } + + return static::call_array($method, $args); + } + + /** + * @param mixed[] $args + */ + private static function call_array(string $method, array $args): string + { + return 'c.' . $method . '(' . implode(', ', $args) . ');'; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php new file mode 100644 index 0000000..fcce5d6 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php @@ -0,0 +1,167 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\ResettableInterface; +use Monolog\Formatter\FormatterInterface; + +/** + * Buffers all records until closing the handler and then pass them as batch. + * + * This is useful for a MailHandler to send only one mail per request instead of + * sending one per log message. + * + * @author Christophe Coevoet + * + * @phpstan-import-type Record from \Monolog\Logger + */ +class BufferHandler extends AbstractHandler implements ProcessableHandlerInterface, FormattableHandlerInterface +{ + use ProcessableHandlerTrait; + + /** @var HandlerInterface */ + protected $handler; + /** @var int */ + protected $bufferSize = 0; + /** @var int */ + protected $bufferLimit; + /** @var bool */ + protected $flushOnOverflow; + /** @var Record[] */ + protected $buffer = []; + /** @var bool */ + protected $initialized = false; + + /** + * @param HandlerInterface $handler Handler. + * @param int $bufferLimit How many entries should be buffered at most, beyond that the oldest items are removed from the buffer. + * @param bool $flushOnOverflow If true, the buffer is flushed when the max size has been reached, by default oldest entries are discarded + */ + public function __construct(HandlerInterface $handler, int $bufferLimit = 0, $level = Logger::DEBUG, bool $bubble = true, bool $flushOnOverflow = false) + { + parent::__construct($level, $bubble); + $this->handler = $handler; + $this->bufferLimit = $bufferLimit; + $this->flushOnOverflow = $flushOnOverflow; + } + + /** + * {@inheritDoc} + */ + public function handle(array $record): bool + { + if ($record['level'] < $this->level) { + return false; + } + + if (!$this->initialized) { + // __destructor() doesn't get called on Fatal errors + register_shutdown_function([$this, 'close']); + $this->initialized = true; + } + + if ($this->bufferLimit > 0 && $this->bufferSize === $this->bufferLimit) { + if ($this->flushOnOverflow) { + $this->flush(); + } else { + array_shift($this->buffer); + $this->bufferSize--; + } + } + + if ($this->processors) { + /** @var Record $record */ + $record = $this->processRecord($record); + } + + $this->buffer[] = $record; + $this->bufferSize++; + + return false === $this->bubble; + } + + public function flush(): void + { + if ($this->bufferSize === 0) { + return; + } + + $this->handler->handleBatch($this->buffer); + $this->clear(); + } + + public function __destruct() + { + // suppress the parent behavior since we already have register_shutdown_function() + // to call close(), and the reference contained there will prevent this from being + // GC'd until the end of the request + } + + /** + * {@inheritDoc} + */ + public function close(): void + { + $this->flush(); + + $this->handler->close(); + } + + /** + * Clears the buffer without flushing any messages down to the wrapped handler. + */ + public function clear(): void + { + $this->bufferSize = 0; + $this->buffer = []; + } + + public function reset() + { + $this->flush(); + + parent::reset(); + + $this->resetProcessors(); + + if ($this->handler instanceof ResettableInterface) { + $this->handler->reset(); + } + } + + /** + * {@inheritDoc} + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + if ($this->handler instanceof FormattableHandlerInterface) { + $this->handler->setFormatter($formatter); + + return $this; + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($this->handler).' does not support formatters.'); + } + + /** + * {@inheritDoc} + */ + public function getFormatter(): FormatterInterface + { + if ($this->handler instanceof FormattableHandlerInterface) { + return $this->handler->getFormatter(); + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($this->handler).' does not support formatters.'); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php new file mode 100644 index 0000000..d1a98b8 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php @@ -0,0 +1,196 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\ChromePHPFormatter; +use Monolog\Formatter\FormatterInterface; +use Monolog\Logger; +use Monolog\Utils; + +/** + * Handler sending logs to the ChromePHP extension (http://www.chromephp.com/) + * + * This also works out of the box with Firefox 43+ + * + * @author Christophe Coevoet + * + * @phpstan-import-type Record from \Monolog\Logger + */ +class ChromePHPHandler extends AbstractProcessingHandler +{ + use WebRequestRecognizerTrait; + + /** + * Version of the extension + */ + protected const VERSION = '4.0'; + + /** + * Header name + */ + protected const HEADER_NAME = 'X-ChromeLogger-Data'; + + /** + * Regular expression to detect supported browsers (matches any Chrome, or Firefox 43+) + */ + protected const USER_AGENT_REGEX = '{\b(?:Chrome/\d+(?:\.\d+)*|HeadlessChrome|Firefox/(?:4[3-9]|[5-9]\d|\d{3,})(?:\.\d)*)\b}'; + + /** @var bool */ + protected static $initialized = false; + + /** + * Tracks whether we sent too much data + * + * Chrome limits the headers to 4KB, so when we sent 3KB we stop sending + * + * @var bool + */ + protected static $overflowed = false; + + /** @var mixed[] */ + protected static $json = [ + 'version' => self::VERSION, + 'columns' => ['label', 'log', 'backtrace', 'type'], + 'rows' => [], + ]; + + /** @var bool */ + protected static $sendHeaders = true; + + public function __construct($level = Logger::DEBUG, bool $bubble = true) + { + parent::__construct($level, $bubble); + if (!function_exists('json_encode')) { + throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s ChromePHPHandler'); + } + } + + /** + * {@inheritDoc} + */ + public function handleBatch(array $records): void + { + if (!$this->isWebRequest()) { + return; + } + + $messages = []; + + foreach ($records as $record) { + if ($record['level'] < $this->level) { + continue; + } + /** @var Record $message */ + $message = $this->processRecord($record); + $messages[] = $message; + } + + if (!empty($messages)) { + $messages = $this->getFormatter()->formatBatch($messages); + self::$json['rows'] = array_merge(self::$json['rows'], $messages); + $this->send(); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new ChromePHPFormatter(); + } + + /** + * Creates & sends header for a record + * + * @see sendHeader() + * @see send() + */ + protected function write(array $record): void + { + if (!$this->isWebRequest()) { + return; + } + + self::$json['rows'][] = $record['formatted']; + + $this->send(); + } + + /** + * Sends the log header + * + * @see sendHeader() + */ + protected function send(): void + { + if (self::$overflowed || !self::$sendHeaders) { + return; + } + + if (!self::$initialized) { + self::$initialized = true; + + self::$sendHeaders = $this->headersAccepted(); + if (!self::$sendHeaders) { + return; + } + + self::$json['request_uri'] = $_SERVER['REQUEST_URI'] ?? ''; + } + + $json = Utils::jsonEncode(self::$json, Utils::DEFAULT_JSON_FLAGS & ~JSON_UNESCAPED_UNICODE, true); + $data = base64_encode(utf8_encode($json)); + if (strlen($data) > 3 * 1024) { + self::$overflowed = true; + + $record = [ + 'message' => 'Incomplete logs, chrome header size limit reached', + 'context' => [], + 'level' => Logger::WARNING, + 'level_name' => Logger::getLevelName(Logger::WARNING), + 'channel' => 'monolog', + 'datetime' => new \DateTimeImmutable(), + 'extra' => [], + ]; + self::$json['rows'][count(self::$json['rows']) - 1] = $this->getFormatter()->format($record); + $json = Utils::jsonEncode(self::$json, null, true); + $data = base64_encode(utf8_encode($json)); + } + + if (trim($data) !== '') { + $this->sendHeader(static::HEADER_NAME, $data); + } + } + + /** + * Send header string to the client + */ + protected function sendHeader(string $header, string $content): void + { + if (!headers_sent() && self::$sendHeaders) { + header(sprintf('%s: %s', $header, $content)); + } + } + + /** + * Verifies if the headers are accepted by the current user agent + */ + protected function headersAccepted(): bool + { + if (empty($_SERVER['HTTP_USER_AGENT'])) { + return false; + } + + return preg_match(static::USER_AGENT_REGEX, $_SERVER['HTTP_USER_AGENT']) === 1; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php new file mode 100644 index 0000000..5265761 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\JsonFormatter; +use Monolog\Logger; + +/** + * CouchDB handler + * + * @author Markus Bachmann + */ +class CouchDBHandler extends AbstractProcessingHandler +{ + /** @var mixed[] */ + private $options; + + /** + * @param mixed[] $options + */ + public function __construct(array $options = [], $level = Logger::DEBUG, bool $bubble = true) + { + $this->options = array_merge([ + 'host' => 'localhost', + 'port' => 5984, + 'dbname' => 'logger', + 'username' => null, + 'password' => null, + ], $options); + + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + $basicAuth = null; + if ($this->options['username']) { + $basicAuth = sprintf('%s:%s@', $this->options['username'], $this->options['password']); + } + + $url = 'http://'.$basicAuth.$this->options['host'].':'.$this->options['port'].'/'.$this->options['dbname']; + $context = stream_context_create([ + 'http' => [ + 'method' => 'POST', + 'content' => $record['formatted'], + 'ignore_errors' => true, + 'max_redirects' => 0, + 'header' => 'Content-type: application/json', + ], + ]); + + if (false === @file_get_contents($url, false, $context)) { + throw new \RuntimeException(sprintf('Could not connect to %s', $url)); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php new file mode 100644 index 0000000..fc8f58f --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php @@ -0,0 +1,166 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Utils; + +/** + * Logs to Cube. + * + * @link http://square.github.com/cube/ + * @author Wan Chen + */ +class CubeHandler extends AbstractProcessingHandler +{ + /** @var resource|\Socket|null */ + private $udpConnection = null; + /** @var resource|\CurlHandle|null */ + private $httpConnection = null; + /** @var string */ + private $scheme; + /** @var string */ + private $host; + /** @var int */ + private $port; + /** @var string[] */ + private $acceptedSchemes = ['http', 'udp']; + + /** + * Create a Cube handler + * + * @throws \UnexpectedValueException when given url is not a valid url. + * A valid url must consist of three parts : protocol://host:port + * Only valid protocols used by Cube are http and udp + */ + public function __construct(string $url, $level = Logger::DEBUG, bool $bubble = true) + { + $urlInfo = parse_url($url); + + if ($urlInfo === false || !isset($urlInfo['scheme'], $urlInfo['host'], $urlInfo['port'])) { + throw new \UnexpectedValueException('URL "'.$url.'" is not valid'); + } + + if (!in_array($urlInfo['scheme'], $this->acceptedSchemes)) { + throw new \UnexpectedValueException( + 'Invalid protocol (' . $urlInfo['scheme'] . ').' + . ' Valid options are ' . implode(', ', $this->acceptedSchemes) + ); + } + + $this->scheme = $urlInfo['scheme']; + $this->host = $urlInfo['host']; + $this->port = (int) $urlInfo['port']; + + parent::__construct($level, $bubble); + } + + /** + * Establish a connection to an UDP socket + * + * @throws \LogicException when unable to connect to the socket + * @throws MissingExtensionException when there is no socket extension + */ + protected function connectUdp(): void + { + if (!extension_loaded('sockets')) { + throw new MissingExtensionException('The sockets extension is required to use udp URLs with the CubeHandler'); + } + + $udpConnection = socket_create(AF_INET, SOCK_DGRAM, 0); + if (false === $udpConnection) { + throw new \LogicException('Unable to create a socket'); + } + + $this->udpConnection = $udpConnection; + if (!socket_connect($this->udpConnection, $this->host, $this->port)) { + throw new \LogicException('Unable to connect to the socket at ' . $this->host . ':' . $this->port); + } + } + + /** + * Establish a connection to an http server + * + * @throws \LogicException when unable to connect to the socket + * @throws MissingExtensionException when no curl extension + */ + protected function connectHttp(): void + { + if (!extension_loaded('curl')) { + throw new MissingExtensionException('The curl extension is required to use http URLs with the CubeHandler'); + } + + $httpConnection = curl_init('http://'.$this->host.':'.$this->port.'/1.0/event/put'); + if (false === $httpConnection) { + throw new \LogicException('Unable to connect to ' . $this->host . ':' . $this->port); + } + + $this->httpConnection = $httpConnection; + curl_setopt($this->httpConnection, CURLOPT_CUSTOMREQUEST, "POST"); + curl_setopt($this->httpConnection, CURLOPT_RETURNTRANSFER, true); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + $date = $record['datetime']; + + $data = ['time' => $date->format('Y-m-d\TH:i:s.uO')]; + unset($record['datetime']); + + if (isset($record['context']['type'])) { + $data['type'] = $record['context']['type']; + unset($record['context']['type']); + } else { + $data['type'] = $record['channel']; + } + + $data['data'] = $record['context']; + $data['data']['level'] = $record['level']; + + if ($this->scheme === 'http') { + $this->writeHttp(Utils::jsonEncode($data)); + } else { + $this->writeUdp(Utils::jsonEncode($data)); + } + } + + private function writeUdp(string $data): void + { + if (!$this->udpConnection) { + $this->connectUdp(); + } + + socket_send($this->udpConnection, $data, strlen($data), 0); + } + + private function writeHttp(string $data): void + { + if (!$this->httpConnection) { + $this->connectHttp(); + } + + if (null === $this->httpConnection) { + throw new \LogicException('No connection could be established'); + } + + curl_setopt($this->httpConnection, CURLOPT_POSTFIELDS, '['.$data.']'); + curl_setopt($this->httpConnection, CURLOPT_HTTPHEADER, [ + 'Content-Type: application/json', + 'Content-Length: ' . strlen('['.$data.']'), + ]); + + Curl\Util::execute($this->httpConnection, 5, false); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php b/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php new file mode 100644 index 0000000..7213e8e --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\Curl; + +use CurlHandle; + +/** + * This class is marked as internal and it is not under the BC promise of the package. + * + * @internal + */ +final class Util +{ + /** @var array */ + private static $retriableErrorCodes = [ + CURLE_COULDNT_RESOLVE_HOST, + CURLE_COULDNT_CONNECT, + CURLE_HTTP_NOT_FOUND, + CURLE_READ_ERROR, + CURLE_OPERATION_TIMEOUTED, + CURLE_HTTP_POST_ERROR, + CURLE_SSL_CONNECT_ERROR, + ]; + + /** + * Executes a CURL request with optional retries and exception on failure + * + * @param resource|CurlHandle $ch curl handler + * @param int $retries + * @param bool $closeAfterDone + * @return bool|string @see curl_exec + */ + public static function execute($ch, int $retries = 5, bool $closeAfterDone = true) + { + while ($retries--) { + $curlResponse = curl_exec($ch); + if ($curlResponse === false) { + $curlErrno = curl_errno($ch); + + if (false === in_array($curlErrno, self::$retriableErrorCodes, true) || !$retries) { + $curlError = curl_error($ch); + + if ($closeAfterDone) { + curl_close($ch); + } + + throw new \RuntimeException(sprintf('Curl error (code %d): %s', $curlErrno, $curlError)); + } + + continue; + } + + if ($closeAfterDone) { + curl_close($ch); + } + + return $curlResponse; + } + + return false; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php new file mode 100644 index 0000000..9b85ae7 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php @@ -0,0 +1,186 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Psr\Log\LogLevel; + +/** + * Simple handler wrapper that deduplicates log records across multiple requests + * + * It also includes the BufferHandler functionality and will buffer + * all messages until the end of the request or flush() is called. + * + * This works by storing all log records' messages above $deduplicationLevel + * to the file specified by $deduplicationStore. When further logs come in at the end of the + * request (or when flush() is called), all those above $deduplicationLevel are checked + * against the existing stored logs. If they match and the timestamps in the stored log is + * not older than $time seconds, the new log record is discarded. If no log record is new, the + * whole data set is discarded. + * + * This is mainly useful in combination with Mail handlers or things like Slack or HipChat handlers + * that send messages to people, to avoid spamming with the same message over and over in case of + * a major component failure like a database server being down which makes all requests fail in the + * same way. + * + * @author Jordi Boggiano + * + * @phpstan-import-type Record from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger + * @phpstan-import-type Level from \Monolog\Logger + */ +class DeduplicationHandler extends BufferHandler +{ + /** + * @var string + */ + protected $deduplicationStore; + + /** + * @var Level + */ + protected $deduplicationLevel; + + /** + * @var int + */ + protected $time; + + /** + * @var bool + */ + private $gc = false; + + /** + * @param HandlerInterface $handler Handler. + * @param string $deduplicationStore The file/path where the deduplication log should be kept + * @param string|int $deduplicationLevel The minimum logging level for log records to be looked at for deduplication purposes + * @param int $time The period (in seconds) during which duplicate entries should be suppressed after a given log is sent through + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * + * @phpstan-param Level|LevelName|LogLevel::* $deduplicationLevel + */ + public function __construct(HandlerInterface $handler, ?string $deduplicationStore = null, $deduplicationLevel = Logger::ERROR, int $time = 60, bool $bubble = true) + { + parent::__construct($handler, 0, Logger::DEBUG, $bubble, false); + + $this->deduplicationStore = $deduplicationStore === null ? sys_get_temp_dir() . '/monolog-dedup-' . substr(md5(__FILE__), 0, 20) .'.log' : $deduplicationStore; + $this->deduplicationLevel = Logger::toMonologLevel($deduplicationLevel); + $this->time = $time; + } + + public function flush(): void + { + if ($this->bufferSize === 0) { + return; + } + + $passthru = null; + + foreach ($this->buffer as $record) { + if ($record['level'] >= $this->deduplicationLevel) { + $passthru = $passthru || !$this->isDuplicate($record); + if ($passthru) { + $this->appendRecord($record); + } + } + } + + // default of null is valid as well as if no record matches duplicationLevel we just pass through + if ($passthru === true || $passthru === null) { + $this->handler->handleBatch($this->buffer); + } + + $this->clear(); + + if ($this->gc) { + $this->collectLogs(); + } + } + + /** + * @phpstan-param Record $record + */ + private function isDuplicate(array $record): bool + { + if (!file_exists($this->deduplicationStore)) { + return false; + } + + $store = file($this->deduplicationStore, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + if (!is_array($store)) { + return false; + } + + $yesterday = time() - 86400; + $timestampValidity = $record['datetime']->getTimestamp() - $this->time; + $expectedMessage = preg_replace('{[\r\n].*}', '', $record['message']); + + for ($i = count($store) - 1; $i >= 0; $i--) { + list($timestamp, $level, $message) = explode(':', $store[$i], 3); + + if ($level === $record['level_name'] && $message === $expectedMessage && $timestamp > $timestampValidity) { + return true; + } + + if ($timestamp < $yesterday) { + $this->gc = true; + } + } + + return false; + } + + private function collectLogs(): void + { + if (!file_exists($this->deduplicationStore)) { + return; + } + + $handle = fopen($this->deduplicationStore, 'rw+'); + + if (!$handle) { + throw new \RuntimeException('Failed to open file for reading and writing: ' . $this->deduplicationStore); + } + + flock($handle, LOCK_EX); + $validLogs = []; + + $timestampValidity = time() - $this->time; + + while (!feof($handle)) { + $log = fgets($handle); + if ($log && substr($log, 0, 10) >= $timestampValidity) { + $validLogs[] = $log; + } + } + + ftruncate($handle, 0); + rewind($handle); + foreach ($validLogs as $log) { + fwrite($handle, $log); + } + + flock($handle, LOCK_UN); + fclose($handle); + + $this->gc = false; + } + + /** + * @phpstan-param Record $record + */ + private function appendRecord(array $record): void + { + file_put_contents($this->deduplicationStore, $record['datetime']->getTimestamp() . ':' . $record['level_name'] . ':' . preg_replace('{[\r\n].*}', '', $record['message']) . "\n", FILE_APPEND); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php new file mode 100644 index 0000000..ebd52c3 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\NormalizerFormatter; +use Monolog\Formatter\FormatterInterface; +use Doctrine\CouchDB\CouchDBClient; + +/** + * CouchDB handler for Doctrine CouchDB ODM + * + * @author Markus Bachmann + */ +class DoctrineCouchDBHandler extends AbstractProcessingHandler +{ + /** @var CouchDBClient */ + private $client; + + public function __construct(CouchDBClient $client, $level = Logger::DEBUG, bool $bubble = true) + { + $this->client = $client; + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + $this->client->postDocument($record['formatted']); + } + + protected function getDefaultFormatter(): FormatterInterface + { + return new NormalizerFormatter; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php new file mode 100644 index 0000000..21840bf --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Aws\Sdk; +use Aws\DynamoDb\DynamoDbClient; +use Monolog\Formatter\FormatterInterface; +use Aws\DynamoDb\Marshaler; +use Monolog\Formatter\ScalarFormatter; +use Monolog\Logger; + +/** + * Amazon DynamoDB handler (http://aws.amazon.com/dynamodb/) + * + * @link https://github.com/aws/aws-sdk-php/ + * @author Andrew Lawson + */ +class DynamoDbHandler extends AbstractProcessingHandler +{ + public const DATE_FORMAT = 'Y-m-d\TH:i:s.uO'; + + /** + * @var DynamoDbClient + */ + protected $client; + + /** + * @var string + */ + protected $table; + + /** + * @var int + */ + protected $version; + + /** + * @var Marshaler + */ + protected $marshaler; + + public function __construct(DynamoDbClient $client, string $table, $level = Logger::DEBUG, bool $bubble = true) + { + /** @phpstan-ignore-next-line */ + if (defined('Aws\Sdk::VERSION') && version_compare(Sdk::VERSION, '3.0', '>=')) { + $this->version = 3; + $this->marshaler = new Marshaler; + } else { + $this->version = 2; + } + + $this->client = $client; + $this->table = $table; + + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + $filtered = $this->filterEmptyFields($record['formatted']); + if ($this->version === 3) { + $formatted = $this->marshaler->marshalItem($filtered); + } else { + /** @phpstan-ignore-next-line */ + $formatted = $this->client->formatAttributes($filtered); + } + + $this->client->putItem([ + 'TableName' => $this->table, + 'Item' => $formatted, + ]); + } + + /** + * @param mixed[] $record + * @return mixed[] + */ + protected function filterEmptyFields(array $record): array + { + return array_filter($record, function ($value) { + return !empty($value) || false === $value || 0 === $value; + }); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new ScalarFormatter(self::DATE_FORMAT); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ElasticaHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ElasticaHandler.php new file mode 100644 index 0000000..fc92ca4 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ElasticaHandler.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Elastica\Document; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\ElasticaFormatter; +use Monolog\Logger; +use Elastica\Client; +use Elastica\Exception\ExceptionInterface; + +/** + * Elastic Search handler + * + * Usage example: + * + * $client = new \Elastica\Client(); + * $options = array( + * 'index' => 'elastic_index_name', + * 'type' => 'elastic_doc_type', Types have been removed in Elastica 7 + * ); + * $handler = new ElasticaHandler($client, $options); + * $log = new Logger('application'); + * $log->pushHandler($handler); + * + * @author Jelle Vink + */ +class ElasticaHandler extends AbstractProcessingHandler +{ + /** + * @var Client + */ + protected $client; + + /** + * @var mixed[] Handler config options + */ + protected $options = []; + + /** + * @param Client $client Elastica Client object + * @param mixed[] $options Handler configuration + */ + public function __construct(Client $client, array $options = [], $level = Logger::DEBUG, bool $bubble = true) + { + parent::__construct($level, $bubble); + $this->client = $client; + $this->options = array_merge( + [ + 'index' => 'monolog', // Elastic index name + 'type' => 'record', // Elastic document type + 'ignore_error' => false, // Suppress Elastica exceptions + ], + $options + ); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + $this->bulkSend([$record['formatted']]); + } + + /** + * {@inheritDoc} + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + if ($formatter instanceof ElasticaFormatter) { + return parent::setFormatter($formatter); + } + + throw new \InvalidArgumentException('ElasticaHandler is only compatible with ElasticaFormatter'); + } + + /** + * @return mixed[] + */ + public function getOptions(): array + { + return $this->options; + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new ElasticaFormatter($this->options['index'], $this->options['type']); + } + + /** + * {@inheritDoc} + */ + public function handleBatch(array $records): void + { + $documents = $this->getFormatter()->formatBatch($records); + $this->bulkSend($documents); + } + + /** + * Use Elasticsearch bulk API to send list of documents + * + * @param Document[] $documents + * + * @throws \RuntimeException + */ + protected function bulkSend(array $documents): void + { + try { + $this->client->addDocuments($documents); + } catch (ExceptionInterface $e) { + if (!$this->options['ignore_error']) { + throw new \RuntimeException("Error sending messages to Elasticsearch", 0, $e); + } + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ElasticsearchHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ElasticsearchHandler.php new file mode 100644 index 0000000..b9d323d --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ElasticsearchHandler.php @@ -0,0 +1,187 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Throwable; +use RuntimeException; +use Monolog\Logger; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\ElasticsearchFormatter; +use InvalidArgumentException; +use Elasticsearch\Common\Exceptions\RuntimeException as ElasticsearchRuntimeException; +use Elasticsearch\Client; + +/** + * Elasticsearch handler + * + * @link https://www.elastic.co/guide/en/elasticsearch/client/php-api/current/index.html + * + * Simple usage example: + * + * $client = \Elasticsearch\ClientBuilder::create() + * ->setHosts($hosts) + * ->build(); + * + * $options = array( + * 'index' => 'elastic_index_name', + * 'type' => 'elastic_doc_type', + * ); + * $handler = new ElasticsearchHandler($client, $options); + * $log = new Logger('application'); + * $log->pushHandler($handler); + * + * @author Avtandil Kikabidze + */ +class ElasticsearchHandler extends AbstractProcessingHandler +{ + /** + * @var Client + */ + protected $client; + + /** + * @var mixed[] Handler config options + */ + protected $options = []; + + /** + * @param Client $client Elasticsearch Client object + * @param mixed[] $options Handler configuration + */ + public function __construct(Client $client, array $options = [], $level = Logger::DEBUG, bool $bubble = true) + { + parent::__construct($level, $bubble); + $this->client = $client; + $this->options = array_merge( + [ + 'index' => 'monolog', // Elastic index name + 'type' => '_doc', // Elastic document type + 'ignore_error' => false, // Suppress Elasticsearch exceptions + ], + $options + ); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + $this->bulkSend([$record['formatted']]); + } + + /** + * {@inheritDoc} + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + if ($formatter instanceof ElasticsearchFormatter) { + return parent::setFormatter($formatter); + } + + throw new InvalidArgumentException('ElasticsearchHandler is only compatible with ElasticsearchFormatter'); + } + + /** + * Getter options + * + * @return mixed[] + */ + public function getOptions(): array + { + return $this->options; + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new ElasticsearchFormatter($this->options['index'], $this->options['type']); + } + + /** + * {@inheritDoc} + */ + public function handleBatch(array $records): void + { + $documents = $this->getFormatter()->formatBatch($records); + $this->bulkSend($documents); + } + + /** + * Use Elasticsearch bulk API to send list of documents + * + * @param array[] $records Records + _index/_type keys + * @throws \RuntimeException + */ + protected function bulkSend(array $records): void + { + try { + $params = [ + 'body' => [], + ]; + + foreach ($records as $record) { + $params['body'][] = [ + 'index' => [ + '_index' => $record['_index'], + '_type' => $record['_type'], + ], + ]; + unset($record['_index'], $record['_type']); + + $params['body'][] = $record; + } + + $responses = $this->client->bulk($params); + + if ($responses['errors'] === true) { + throw $this->createExceptionFromResponses($responses); + } + } catch (Throwable $e) { + if (! $this->options['ignore_error']) { + throw new RuntimeException('Error sending messages to Elasticsearch', 0, $e); + } + } + } + + /** + * Creates elasticsearch exception from responses array + * + * Only the first error is converted into an exception. + * + * @param mixed[] $responses returned by $this->client->bulk() + */ + protected function createExceptionFromResponses(array $responses): ElasticsearchRuntimeException + { + foreach ($responses['items'] ?? [] as $item) { + if (isset($item['index']['error'])) { + return $this->createExceptionFromError($item['index']['error']); + } + } + + return new ElasticsearchRuntimeException('Elasticsearch failed to index one or more records.'); + } + + /** + * Creates elasticsearch exception from error array + * + * @param mixed[] $error + */ + protected function createExceptionFromError(array $error): ElasticsearchRuntimeException + { + $previous = isset($error['caused_by']) ? $this->createExceptionFromError($error['caused_by']) : null; + + return new ElasticsearchRuntimeException($error['type'] . ': ' . $error['reason'], 0, $previous); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php new file mode 100644 index 0000000..f2e2203 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Formatter\FormatterInterface; +use Monolog\Logger; +use Monolog\Utils; + +/** + * Stores to PHP error_log() handler. + * + * @author Elan Ruusamäe + */ +class ErrorLogHandler extends AbstractProcessingHandler +{ + public const OPERATING_SYSTEM = 0; + public const SAPI = 4; + + /** @var int */ + protected $messageType; + /** @var bool */ + protected $expandNewlines; + + /** + * @param int $messageType Says where the error should go. + * @param bool $expandNewlines If set to true, newlines in the message will be expanded to be take multiple log entries + */ + public function __construct(int $messageType = self::OPERATING_SYSTEM, $level = Logger::DEBUG, bool $bubble = true, bool $expandNewlines = false) + { + parent::__construct($level, $bubble); + + if (false === in_array($messageType, self::getAvailableTypes(), true)) { + $message = sprintf('The given message type "%s" is not supported', print_r($messageType, true)); + + throw new \InvalidArgumentException($message); + } + + $this->messageType = $messageType; + $this->expandNewlines = $expandNewlines; + } + + /** + * @return int[] With all available types + */ + public static function getAvailableTypes(): array + { + return [ + self::OPERATING_SYSTEM, + self::SAPI, + ]; + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new LineFormatter('[%datetime%] %channel%.%level_name%: %message% %context% %extra%'); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + if (!$this->expandNewlines) { + error_log((string) $record['formatted'], $this->messageType); + + return; + } + + $lines = preg_split('{[\r\n]+}', (string) $record['formatted']); + if ($lines === false) { + $pcreErrorCode = preg_last_error(); + throw new \RuntimeException('Failed to preg_split formatted string: ' . $pcreErrorCode . ' / '. Utils::pcreLastErrorMessage($pcreErrorCode)); + } + foreach ($lines as $line) { + error_log($line, $this->messageType); + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FallbackGroupHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FallbackGroupHandler.php new file mode 100644 index 0000000..d4e234c --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FallbackGroupHandler.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Throwable; + +/** + * Forwards records to at most one handler + * + * If a handler fails, the exception is suppressed and the record is forwarded to the next handler. + * + * As soon as one handler handles a record successfully, the handling stops there. + * + * @phpstan-import-type Record from \Monolog\Logger + */ +class FallbackGroupHandler extends GroupHandler +{ + /** + * {@inheritDoc} + */ + public function handle(array $record): bool + { + if ($this->processors) { + /** @var Record $record */ + $record = $this->processRecord($record); + } + foreach ($this->handlers as $handler) { + try { + $handler->handle($record); + break; + } catch (Throwable $e) { + // What throwable? + } + } + + return false === $this->bubble; + } + + /** + * {@inheritDoc} + */ + public function handleBatch(array $records): void + { + if ($this->processors) { + $processed = []; + foreach ($records as $record) { + $processed[] = $this->processRecord($record); + } + /** @var Record[] $records */ + $records = $processed; + } + + foreach ($this->handlers as $handler) { + try { + $handler->handleBatch($records); + break; + } catch (Throwable $e) { + // What throwable? + } + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php new file mode 100644 index 0000000..718f17e --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php @@ -0,0 +1,212 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\ResettableInterface; +use Monolog\Formatter\FormatterInterface; +use Psr\Log\LogLevel; + +/** + * Simple handler wrapper that filters records based on a list of levels + * + * It can be configured with an exact list of levels to allow, or a min/max level. + * + * @author Hennadiy Verkh + * @author Jordi Boggiano + * + * @phpstan-import-type Record from \Monolog\Logger + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger + */ +class FilterHandler extends Handler implements ProcessableHandlerInterface, ResettableInterface, FormattableHandlerInterface +{ + use ProcessableHandlerTrait; + + /** + * Handler or factory callable($record, $this) + * + * @var callable|HandlerInterface + * @phpstan-var callable(?Record, HandlerInterface): HandlerInterface|HandlerInterface + */ + protected $handler; + + /** + * Minimum level for logs that are passed to handler + * + * @var int[] + * @phpstan-var array + */ + protected $acceptedLevels; + + /** + * Whether the messages that are handled can bubble up the stack or not + * + * @var bool + */ + protected $bubble; + + /** + * @psalm-param HandlerInterface|callable(?Record, HandlerInterface): HandlerInterface $handler + * + * @param callable|HandlerInterface $handler Handler or factory callable($record|null, $filterHandler). + * @param int|array $minLevelOrList A list of levels to accept or a minimum level if maxLevel is provided + * @param int|string $maxLevel Maximum level to accept, only used if $minLevelOrList is not an array + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * + * @phpstan-param Level|LevelName|LogLevel::*|array $minLevelOrList + * @phpstan-param Level|LevelName|LogLevel::* $maxLevel + */ + public function __construct($handler, $minLevelOrList = Logger::DEBUG, $maxLevel = Logger::EMERGENCY, bool $bubble = true) + { + $this->handler = $handler; + $this->bubble = $bubble; + $this->setAcceptedLevels($minLevelOrList, $maxLevel); + + if (!$this->handler instanceof HandlerInterface && !is_callable($this->handler)) { + throw new \RuntimeException("The given handler (".json_encode($this->handler).") is not a callable nor a Monolog\Handler\HandlerInterface object"); + } + } + + /** + * @phpstan-return array + */ + public function getAcceptedLevels(): array + { + return array_flip($this->acceptedLevels); + } + + /** + * @param int|string|array $minLevelOrList A list of levels to accept or a minimum level or level name if maxLevel is provided + * @param int|string $maxLevel Maximum level or level name to accept, only used if $minLevelOrList is not an array + * + * @phpstan-param Level|LevelName|LogLevel::*|array $minLevelOrList + * @phpstan-param Level|LevelName|LogLevel::* $maxLevel + */ + public function setAcceptedLevels($minLevelOrList = Logger::DEBUG, $maxLevel = Logger::EMERGENCY): self + { + if (is_array($minLevelOrList)) { + $acceptedLevels = array_map('Monolog\Logger::toMonologLevel', $minLevelOrList); + } else { + $minLevelOrList = Logger::toMonologLevel($minLevelOrList); + $maxLevel = Logger::toMonologLevel($maxLevel); + $acceptedLevels = array_values(array_filter(Logger::getLevels(), function ($level) use ($minLevelOrList, $maxLevel) { + return $level >= $minLevelOrList && $level <= $maxLevel; + })); + } + $this->acceptedLevels = array_flip($acceptedLevels); + + return $this; + } + + /** + * {@inheritDoc} + */ + public function isHandling(array $record): bool + { + return isset($this->acceptedLevels[$record['level']]); + } + + /** + * {@inheritDoc} + */ + public function handle(array $record): bool + { + if (!$this->isHandling($record)) { + return false; + } + + if ($this->processors) { + /** @var Record $record */ + $record = $this->processRecord($record); + } + + $this->getHandler($record)->handle($record); + + return false === $this->bubble; + } + + /** + * {@inheritDoc} + */ + public function handleBatch(array $records): void + { + $filtered = []; + foreach ($records as $record) { + if ($this->isHandling($record)) { + $filtered[] = $record; + } + } + + if (count($filtered) > 0) { + $this->getHandler($filtered[count($filtered) - 1])->handleBatch($filtered); + } + } + + /** + * Return the nested handler + * + * If the handler was provided as a factory callable, this will trigger the handler's instantiation. + * + * @return HandlerInterface + * + * @phpstan-param Record $record + */ + public function getHandler(array $record = null) + { + if (!$this->handler instanceof HandlerInterface) { + $this->handler = ($this->handler)($record, $this); + if (!$this->handler instanceof HandlerInterface) { + throw new \RuntimeException("The factory callable should return a HandlerInterface"); + } + } + + return $this->handler; + } + + /** + * {@inheritDoc} + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + $handler = $this->getHandler(); + if ($handler instanceof FormattableHandlerInterface) { + $handler->setFormatter($formatter); + + return $this; + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($handler).' does not support formatters.'); + } + + /** + * {@inheritDoc} + */ + public function getFormatter(): FormatterInterface + { + $handler = $this->getHandler(); + if ($handler instanceof FormattableHandlerInterface) { + return $handler->getFormatter(); + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($handler).' does not support formatters.'); + } + + public function reset() + { + $this->resetProcessors(); + + if ($this->getHandler() instanceof ResettableInterface) { + $this->getHandler()->reset(); + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php new file mode 100644 index 0000000..0aa5607 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\FingersCrossed; + +/** + * Interface for activation strategies for the FingersCrossedHandler. + * + * @author Johannes M. Schmitt + * + * @phpstan-import-type Record from \Monolog\Logger + */ +interface ActivationStrategyInterface +{ + /** + * Returns whether the given record activates the handler. + * + * @phpstan-param Record $record + */ + public function isHandlerActivated(array $record): bool; +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php new file mode 100644 index 0000000..7b9abb5 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\FingersCrossed; + +use Monolog\Logger; +use Psr\Log\LogLevel; + +/** + * Channel and Error level based monolog activation strategy. Allows to trigger activation + * based on level per channel. e.g. trigger activation on level 'ERROR' by default, except + * for records of the 'sql' channel; those should trigger activation on level 'WARN'. + * + * Example: + * + * + * $activationStrategy = new ChannelLevelActivationStrategy( + * Logger::CRITICAL, + * array( + * 'request' => Logger::ALERT, + * 'sensitive' => Logger::ERROR, + * ) + * ); + * $handler = new FingersCrossedHandler(new StreamHandler('php://stderr'), $activationStrategy); + * + * + * @author Mike Meessen + * + * @phpstan-import-type Record from \Monolog\Logger + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger + */ +class ChannelLevelActivationStrategy implements ActivationStrategyInterface +{ + /** + * @var Level + */ + private $defaultActionLevel; + + /** + * @var array + */ + private $channelToActionLevel; + + /** + * @param int|string $defaultActionLevel The default action level to be used if the record's category doesn't match any + * @param array $channelToActionLevel An array that maps channel names to action levels. + * + * @phpstan-param array $channelToActionLevel + * @phpstan-param Level|LevelName|LogLevel::* $defaultActionLevel + */ + public function __construct($defaultActionLevel, array $channelToActionLevel = []) + { + $this->defaultActionLevel = Logger::toMonologLevel($defaultActionLevel); + $this->channelToActionLevel = array_map('Monolog\Logger::toMonologLevel', $channelToActionLevel); + } + + /** + * @phpstan-param Record $record + */ + public function isHandlerActivated(array $record): bool + { + if (isset($this->channelToActionLevel[$record['channel']])) { + return $record['level'] >= $this->channelToActionLevel[$record['channel']]; + } + + return $record['level'] >= $this->defaultActionLevel; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php new file mode 100644 index 0000000..5ec88ea --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\FingersCrossed; + +use Monolog\Logger; +use Psr\Log\LogLevel; + +/** + * Error level based activation strategy. + * + * @author Johannes M. Schmitt + * + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger + */ +class ErrorLevelActivationStrategy implements ActivationStrategyInterface +{ + /** + * @var Level + */ + private $actionLevel; + + /** + * @param int|string $actionLevel Level or name or value + * + * @phpstan-param Level|LevelName|LogLevel::* $actionLevel + */ + public function __construct($actionLevel) + { + $this->actionLevel = Logger::toMonologLevel($actionLevel); + } + + public function isHandlerActivated(array $record): bool + { + return $record['level'] >= $this->actionLevel; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php new file mode 100644 index 0000000..0627b44 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php @@ -0,0 +1,252 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy; +use Monolog\Handler\FingersCrossed\ActivationStrategyInterface; +use Monolog\Logger; +use Monolog\ResettableInterface; +use Monolog\Formatter\FormatterInterface; +use Psr\Log\LogLevel; + +/** + * Buffers all records until a certain level is reached + * + * The advantage of this approach is that you don't get any clutter in your log files. + * Only requests which actually trigger an error (or whatever your actionLevel is) will be + * in the logs, but they will contain all records, not only those above the level threshold. + * + * You can then have a passthruLevel as well which means that at the end of the request, + * even if it did not get activated, it will still send through log records of e.g. at least a + * warning level. + * + * You can find the various activation strategies in the + * Monolog\Handler\FingersCrossed\ namespace. + * + * @author Jordi Boggiano + * + * @phpstan-import-type Record from \Monolog\Logger + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger + */ +class FingersCrossedHandler extends Handler implements ProcessableHandlerInterface, ResettableInterface, FormattableHandlerInterface +{ + use ProcessableHandlerTrait; + + /** + * @var callable|HandlerInterface + * @phpstan-var callable(?Record, HandlerInterface): HandlerInterface|HandlerInterface + */ + protected $handler; + /** @var ActivationStrategyInterface */ + protected $activationStrategy; + /** @var bool */ + protected $buffering = true; + /** @var int */ + protected $bufferSize; + /** @var Record[] */ + protected $buffer = []; + /** @var bool */ + protected $stopBuffering; + /** + * @var ?int + * @phpstan-var ?Level + */ + protected $passthruLevel; + /** @var bool */ + protected $bubble; + + /** + * @psalm-param HandlerInterface|callable(?Record, HandlerInterface): HandlerInterface $handler + * + * @param callable|HandlerInterface $handler Handler or factory callable($record|null, $fingersCrossedHandler). + * @param int|string|ActivationStrategyInterface $activationStrategy Strategy which determines when this handler takes action, or a level name/value at which the handler is activated + * @param int $bufferSize How many entries should be buffered at most, beyond that the oldest items are removed from the buffer. + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param bool $stopBuffering Whether the handler should stop buffering after being triggered (default true) + * @param int|string $passthruLevel Minimum level to always flush to handler on close, even if strategy not triggered + * + * @phpstan-param Level|LevelName|LogLevel::* $passthruLevel + * @phpstan-param Level|LevelName|LogLevel::*|ActivationStrategyInterface $activationStrategy + */ + public function __construct($handler, $activationStrategy = null, int $bufferSize = 0, bool $bubble = true, bool $stopBuffering = true, $passthruLevel = null) + { + if (null === $activationStrategy) { + $activationStrategy = new ErrorLevelActivationStrategy(Logger::WARNING); + } + + // convert simple int activationStrategy to an object + if (!$activationStrategy instanceof ActivationStrategyInterface) { + $activationStrategy = new ErrorLevelActivationStrategy($activationStrategy); + } + + $this->handler = $handler; + $this->activationStrategy = $activationStrategy; + $this->bufferSize = $bufferSize; + $this->bubble = $bubble; + $this->stopBuffering = $stopBuffering; + + if ($passthruLevel !== null) { + $this->passthruLevel = Logger::toMonologLevel($passthruLevel); + } + + if (!$this->handler instanceof HandlerInterface && !is_callable($this->handler)) { + throw new \RuntimeException("The given handler (".json_encode($this->handler).") is not a callable nor a Monolog\Handler\HandlerInterface object"); + } + } + + /** + * {@inheritDoc} + */ + public function isHandling(array $record): bool + { + return true; + } + + /** + * Manually activate this logger regardless of the activation strategy + */ + public function activate(): void + { + if ($this->stopBuffering) { + $this->buffering = false; + } + + $this->getHandler(end($this->buffer) ?: null)->handleBatch($this->buffer); + $this->buffer = []; + } + + /** + * {@inheritDoc} + */ + public function handle(array $record): bool + { + if ($this->processors) { + /** @var Record $record */ + $record = $this->processRecord($record); + } + + if ($this->buffering) { + $this->buffer[] = $record; + if ($this->bufferSize > 0 && count($this->buffer) > $this->bufferSize) { + array_shift($this->buffer); + } + if ($this->activationStrategy->isHandlerActivated($record)) { + $this->activate(); + } + } else { + $this->getHandler($record)->handle($record); + } + + return false === $this->bubble; + } + + /** + * {@inheritDoc} + */ + public function close(): void + { + $this->flushBuffer(); + + $this->getHandler()->close(); + } + + public function reset() + { + $this->flushBuffer(); + + $this->resetProcessors(); + + if ($this->getHandler() instanceof ResettableInterface) { + $this->getHandler()->reset(); + } + } + + /** + * Clears the buffer without flushing any messages down to the wrapped handler. + * + * It also resets the handler to its initial buffering state. + */ + public function clear(): void + { + $this->buffer = []; + $this->reset(); + } + + /** + * Resets the state of the handler. Stops forwarding records to the wrapped handler. + */ + private function flushBuffer(): void + { + if (null !== $this->passthruLevel) { + $level = $this->passthruLevel; + $this->buffer = array_filter($this->buffer, function ($record) use ($level) { + return $record['level'] >= $level; + }); + if (count($this->buffer) > 0) { + $this->getHandler(end($this->buffer))->handleBatch($this->buffer); + } + } + + $this->buffer = []; + $this->buffering = true; + } + + /** + * Return the nested handler + * + * If the handler was provided as a factory callable, this will trigger the handler's instantiation. + * + * @return HandlerInterface + * + * @phpstan-param Record $record + */ + public function getHandler(array $record = null) + { + if (!$this->handler instanceof HandlerInterface) { + $this->handler = ($this->handler)($record, $this); + if (!$this->handler instanceof HandlerInterface) { + throw new \RuntimeException("The factory callable should return a HandlerInterface"); + } + } + + return $this->handler; + } + + /** + * {@inheritDoc} + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + $handler = $this->getHandler(); + if ($handler instanceof FormattableHandlerInterface) { + $handler->setFormatter($formatter); + + return $this; + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($handler).' does not support formatters.'); + } + + /** + * {@inheritDoc} + */ + public function getFormatter(): FormatterInterface + { + $handler = $this->getHandler(); + if ($handler instanceof FormattableHandlerInterface) { + return $handler->getFormatter(); + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($handler).' does not support formatters.'); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php new file mode 100644 index 0000000..72718de --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php @@ -0,0 +1,180 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\WildfireFormatter; +use Monolog\Formatter\FormatterInterface; + +/** + * Simple FirePHP Handler (http://www.firephp.org/), which uses the Wildfire protocol. + * + * @author Eric Clemmons (@ericclemmons) + * + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler + */ +class FirePHPHandler extends AbstractProcessingHandler +{ + use WebRequestRecognizerTrait; + + /** + * WildFire JSON header message format + */ + protected const PROTOCOL_URI = 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2'; + + /** + * FirePHP structure for parsing messages & their presentation + */ + protected const STRUCTURE_URI = 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1'; + + /** + * Must reference a "known" plugin, otherwise headers won't display in FirePHP + */ + protected const PLUGIN_URI = 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3'; + + /** + * Header prefix for Wildfire to recognize & parse headers + */ + protected const HEADER_PREFIX = 'X-Wf'; + + /** + * Whether or not Wildfire vendor-specific headers have been generated & sent yet + * @var bool + */ + protected static $initialized = false; + + /** + * Shared static message index between potentially multiple handlers + * @var int + */ + protected static $messageIndex = 1; + + /** @var bool */ + protected static $sendHeaders = true; + + /** + * Base header creation function used by init headers & record headers + * + * @param array $meta Wildfire Plugin, Protocol & Structure Indexes + * @param string $message Log message + * + * @return array Complete header string ready for the client as key and message as value + * + * @phpstan-return non-empty-array + */ + protected function createHeader(array $meta, string $message): array + { + $header = sprintf('%s-%s', static::HEADER_PREFIX, join('-', $meta)); + + return [$header => $message]; + } + + /** + * Creates message header from record + * + * @return array + * + * @phpstan-return non-empty-array + * + * @see createHeader() + * + * @phpstan-param FormattedRecord $record + */ + protected function createRecordHeader(array $record): array + { + // Wildfire is extensible to support multiple protocols & plugins in a single request, + // but we're not taking advantage of that (yet), so we're using "1" for simplicity's sake. + return $this->createHeader( + [1, 1, 1, self::$messageIndex++], + $record['formatted'] + ); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new WildfireFormatter(); + } + + /** + * Wildfire initialization headers to enable message parsing + * + * @see createHeader() + * @see sendHeader() + * + * @return array + */ + protected function getInitHeaders(): array + { + // Initial payload consists of required headers for Wildfire + return array_merge( + $this->createHeader(['Protocol', 1], static::PROTOCOL_URI), + $this->createHeader([1, 'Structure', 1], static::STRUCTURE_URI), + $this->createHeader([1, 'Plugin', 1], static::PLUGIN_URI) + ); + } + + /** + * Send header string to the client + */ + protected function sendHeader(string $header, string $content): void + { + if (!headers_sent() && self::$sendHeaders) { + header(sprintf('%s: %s', $header, $content)); + } + } + + /** + * Creates & sends header for a record, ensuring init headers have been sent prior + * + * @see sendHeader() + * @see sendInitHeaders() + */ + protected function write(array $record): void + { + if (!self::$sendHeaders || !$this->isWebRequest()) { + return; + } + + // WildFire-specific headers must be sent prior to any messages + if (!self::$initialized) { + self::$initialized = true; + + self::$sendHeaders = $this->headersAccepted(); + if (!self::$sendHeaders) { + return; + } + + foreach ($this->getInitHeaders() as $header => $content) { + $this->sendHeader($header, $content); + } + } + + $header = $this->createRecordHeader($record); + if (trim(current($header)) !== '') { + $this->sendHeader(key($header), current($header)); + } + } + + /** + * Verifies if the headers are accepted by the current user agent + */ + protected function headersAccepted(): bool + { + if (!empty($_SERVER['HTTP_USER_AGENT']) && preg_match('{\bFirePHP/\d+\.\d+\b}', $_SERVER['HTTP_USER_AGENT'])) { + return true; + } + + return isset($_SERVER['HTTP_X_FIREPHP_VERSION']); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php new file mode 100644 index 0000000..6194283 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LineFormatter; +use Monolog\Logger; + +/** + * Sends logs to Fleep.io using Webhook integrations + * + * You'll need a Fleep.io account to use this handler. + * + * @see https://fleep.io/integrations/webhooks/ Fleep Webhooks Documentation + * @author Ando Roots + * + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler + */ +class FleepHookHandler extends SocketHandler +{ + protected const FLEEP_HOST = 'fleep.io'; + + protected const FLEEP_HOOK_URI = '/hook/'; + + /** + * @var string Webhook token (specifies the conversation where logs are sent) + */ + protected $token; + + /** + * Construct a new Fleep.io Handler. + * + * For instructions on how to create a new web hook in your conversations + * see https://fleep.io/integrations/webhooks/ + * + * @param string $token Webhook token + * @throws MissingExtensionException + */ + public function __construct(string $token, $level = Logger::DEBUG, bool $bubble = true) + { + if (!extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP extension is required to use the FleepHookHandler'); + } + + $this->token = $token; + + $connectionString = 'ssl://' . static::FLEEP_HOST . ':443'; + parent::__construct($connectionString, $level, $bubble); + } + + /** + * Returns the default formatter to use with this handler + * + * Overloaded to remove empty context and extra arrays from the end of the log message. + * + * @return LineFormatter + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new LineFormatter(null, null, true, true); + } + + /** + * Handles a log record + */ + public function write(array $record): void + { + parent::write($record); + $this->closeSocket(); + } + + /** + * {@inheritDoc} + */ + protected function generateDataStream(array $record): string + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + /** + * Builds the header of the API Call + */ + private function buildHeader(string $content): string + { + $header = "POST " . static::FLEEP_HOOK_URI . $this->token . " HTTP/1.1\r\n"; + $header .= "Host: " . static::FLEEP_HOST . "\r\n"; + $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; + $header .= "Content-Length: " . strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } + + /** + * Builds the body of API call + * + * @phpstan-param FormattedRecord $record + */ + private function buildContent(array $record): string + { + $dataArray = [ + 'message' => $record['formatted'], + ]; + + return http_build_query($dataArray); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php new file mode 100644 index 0000000..a632c86 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Utils; +use Monolog\Formatter\FlowdockFormatter; +use Monolog\Formatter\FormatterInterface; + +/** + * Sends notifications through the Flowdock push API + * + * This must be configured with a FlowdockFormatter instance via setFormatter() + * + * Notes: + * API token - Flowdock API token + * + * @author Dominik Liebler + * @see https://www.flowdock.com/api/push + * + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler + */ +class FlowdockHandler extends SocketHandler +{ + /** + * @var string + */ + protected $apiToken; + + /** + * @throws MissingExtensionException if OpenSSL is missing + */ + public function __construct(string $apiToken, $level = Logger::DEBUG, bool $bubble = true) + { + if (!extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP extension is required to use the FlowdockHandler'); + } + + parent::__construct('ssl://api.flowdock.com:443', $level, $bubble); + $this->apiToken = $apiToken; + } + + /** + * {@inheritDoc} + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + if (!$formatter instanceof FlowdockFormatter) { + throw new \InvalidArgumentException('The FlowdockHandler requires an instance of Monolog\Formatter\FlowdockFormatter to function correctly'); + } + + return parent::setFormatter($formatter); + } + + /** + * Gets the default formatter. + */ + protected function getDefaultFormatter(): FormatterInterface + { + throw new \InvalidArgumentException('The FlowdockHandler must be configured (via setFormatter) with an instance of Monolog\Formatter\FlowdockFormatter to function correctly'); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + parent::write($record); + + $this->closeSocket(); + } + + /** + * {@inheritDoc} + */ + protected function generateDataStream(array $record): string + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + /** + * Builds the body of API call + * + * @phpstan-param FormattedRecord $record + */ + private function buildContent(array $record): string + { + return Utils::jsonEncode($record['formatted']['flowdock']); + } + + /** + * Builds the header of the API Call + */ + private function buildHeader(string $content): string + { + $header = "POST /v1/messages/team_inbox/" . $this->apiToken . " HTTP/1.1\r\n"; + $header .= "Host: api.flowdock.com\r\n"; + $header .= "Content-Type: application/json\r\n"; + $header .= "Content-Length: " . strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php new file mode 100644 index 0000000..fc1693c --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; + +/** + * Interface to describe loggers that have a formatter + * + * @author Jordi Boggiano + */ +interface FormattableHandlerInterface +{ + /** + * Sets the formatter. + * + * @param FormatterInterface $formatter + * @return HandlerInterface self + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface; + + /** + * Gets the formatter. + * + * @return FormatterInterface + */ + public function getFormatter(): FormatterInterface; +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php b/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php new file mode 100644 index 0000000..b60bdce --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LineFormatter; + +/** + * Helper trait for implementing FormattableInterface + * + * @author Jordi Boggiano + */ +trait FormattableHandlerTrait +{ + /** + * @var ?FormatterInterface + */ + protected $formatter; + + /** + * {@inheritDoc} + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + $this->formatter = $formatter; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function getFormatter(): FormatterInterface + { + if (!$this->formatter) { + $this->formatter = $this->getDefaultFormatter(); + } + + return $this->formatter; + } + + /** + * Gets the default formatter. + * + * Overwrite this if the LineFormatter is not a good default for your handler. + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new LineFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php new file mode 100644 index 0000000..4ff26c4 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Gelf\PublisherInterface; +use Monolog\Logger; +use Monolog\Formatter\GelfMessageFormatter; +use Monolog\Formatter\FormatterInterface; + +/** + * Handler to send messages to a Graylog2 (http://www.graylog2.org) server + * + * @author Matt Lehner + * @author Benjamin Zikarsky + */ +class GelfHandler extends AbstractProcessingHandler +{ + /** + * @var PublisherInterface the publisher object that sends the message to the server + */ + protected $publisher; + + /** + * @param PublisherInterface $publisher a gelf publisher object + */ + public function __construct(PublisherInterface $publisher, $level = Logger::DEBUG, bool $bubble = true) + { + parent::__construct($level, $bubble); + + $this->publisher = $publisher; + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + $this->publisher->publish($record['formatted']); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new GelfMessageFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php new file mode 100644 index 0000000..3c9dc4b --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php @@ -0,0 +1,132 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\ResettableInterface; + +/** + * Forwards records to multiple handlers + * + * @author Lenar Lõhmus + * + * @phpstan-import-type Record from \Monolog\Logger + */ +class GroupHandler extends Handler implements ProcessableHandlerInterface, ResettableInterface +{ + use ProcessableHandlerTrait; + + /** @var HandlerInterface[] */ + protected $handlers; + /** @var bool */ + protected $bubble; + + /** + * @param HandlerInterface[] $handlers Array of Handlers. + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(array $handlers, bool $bubble = true) + { + foreach ($handlers as $handler) { + if (!$handler instanceof HandlerInterface) { + throw new \InvalidArgumentException('The first argument of the GroupHandler must be an array of HandlerInterface instances.'); + } + } + + $this->handlers = $handlers; + $this->bubble = $bubble; + } + + /** + * {@inheritDoc} + */ + public function isHandling(array $record): bool + { + foreach ($this->handlers as $handler) { + if ($handler->isHandling($record)) { + return true; + } + } + + return false; + } + + /** + * {@inheritDoc} + */ + public function handle(array $record): bool + { + if ($this->processors) { + /** @var Record $record */ + $record = $this->processRecord($record); + } + + foreach ($this->handlers as $handler) { + $handler->handle($record); + } + + return false === $this->bubble; + } + + /** + * {@inheritDoc} + */ + public function handleBatch(array $records): void + { + if ($this->processors) { + $processed = []; + foreach ($records as $record) { + $processed[] = $this->processRecord($record); + } + /** @var Record[] $records */ + $records = $processed; + } + + foreach ($this->handlers as $handler) { + $handler->handleBatch($records); + } + } + + public function reset() + { + $this->resetProcessors(); + + foreach ($this->handlers as $handler) { + if ($handler instanceof ResettableInterface) { + $handler->reset(); + } + } + } + + public function close(): void + { + parent::close(); + + foreach ($this->handlers as $handler) { + $handler->close(); + } + } + + /** + * {@inheritDoc} + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + foreach ($this->handlers as $handler) { + if ($handler instanceof FormattableHandlerInterface) { + $handler->setFormatter($formatter); + } + } + + return $this; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/Handler.php b/vendor/monolog/monolog/src/Monolog/Handler/Handler.php new file mode 100644 index 0000000..afef2fd --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/Handler.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Base Handler class providing basic close() support as well as handleBatch + * + * @author Jordi Boggiano + */ +abstract class Handler implements HandlerInterface +{ + /** + * {@inheritDoc} + */ + public function handleBatch(array $records): void + { + foreach ($records as $record) { + $this->handle($record); + } + } + + /** + * {@inheritDoc} + */ + public function close(): void + { + } + + public function __destruct() + { + try { + $this->close(); + } catch (\Throwable $e) { + // do nothing + } + } + + public function __sleep() + { + $this->close(); + + return array_keys(get_object_vars($this)); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php new file mode 100644 index 0000000..affcc51 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Interface that all Monolog Handlers must implement + * + * @author Jordi Boggiano + * + * @phpstan-import-type Record from \Monolog\Logger + * @phpstan-import-type Level from \Monolog\Logger + */ +interface HandlerInterface +{ + /** + * Checks whether the given record will be handled by this handler. + * + * This is mostly done for performance reasons, to avoid calling processors for nothing. + * + * Handlers should still check the record levels within handle(), returning false in isHandling() + * is no guarantee that handle() will not be called, and isHandling() might not be called + * for a given record. + * + * @param array $record Partial log record containing only a level key + * + * @return bool + * + * @phpstan-param array{level: Level} $record + */ + public function isHandling(array $record): bool; + + /** + * Handles a record. + * + * All records may be passed to this method, and the handler should discard + * those that it does not want to handle. + * + * The return value of this function controls the bubbling process of the handler stack. + * Unless the bubbling is interrupted (by returning true), the Logger class will keep on + * calling further handlers in the stack with a given log record. + * + * @param array $record The record to handle + * @return bool true means that this handler handled the record, and that bubbling is not permitted. + * false means the record was either not processed or that this handler allows bubbling. + * + * @phpstan-param Record $record + */ + public function handle(array $record): bool; + + /** + * Handles a set of records at once. + * + * @param array $records The records to handle (an array of record arrays) + * + * @phpstan-param Record[] $records + */ + public function handleBatch(array $records): void; + + /** + * Closes the handler. + * + * Ends a log cycle and frees all resources used by the handler. + * + * Closing a Handler means flushing all buffers and freeing any open resources/handles. + * + * Implementations have to be idempotent (i.e. it should be possible to call close several times without breakage) + * and ideally handlers should be able to reopen themselves on handle() after they have been closed. + * + * This is useful at the end of a request and will be called automatically when the object + * is destroyed if you extend Monolog\Handler\Handler. + * + * If you are thinking of calling this method yourself, most likely you should be + * calling ResettableInterface::reset instead. Have a look. + */ + public function close(): void; +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php b/vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php new file mode 100644 index 0000000..d4351b9 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php @@ -0,0 +1,136 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\ResettableInterface; +use Monolog\Formatter\FormatterInterface; + +/** + * This simple wrapper class can be used to extend handlers functionality. + * + * Example: A custom filtering that can be applied to any handler. + * + * Inherit from this class and override handle() like this: + * + * public function handle(array $record) + * { + * if ($record meets certain conditions) { + * return false; + * } + * return $this->handler->handle($record); + * } + * + * @author Alexey Karapetov + */ +class HandlerWrapper implements HandlerInterface, ProcessableHandlerInterface, FormattableHandlerInterface, ResettableInterface +{ + /** + * @var HandlerInterface + */ + protected $handler; + + public function __construct(HandlerInterface $handler) + { + $this->handler = $handler; + } + + /** + * {@inheritDoc} + */ + public function isHandling(array $record): bool + { + return $this->handler->isHandling($record); + } + + /** + * {@inheritDoc} + */ + public function handle(array $record): bool + { + return $this->handler->handle($record); + } + + /** + * {@inheritDoc} + */ + public function handleBatch(array $records): void + { + $this->handler->handleBatch($records); + } + + /** + * {@inheritDoc} + */ + public function close(): void + { + $this->handler->close(); + } + + /** + * {@inheritDoc} + */ + public function pushProcessor(callable $callback): HandlerInterface + { + if ($this->handler instanceof ProcessableHandlerInterface) { + $this->handler->pushProcessor($callback); + + return $this; + } + + throw new \LogicException('The wrapped handler does not implement ' . ProcessableHandlerInterface::class); + } + + /** + * {@inheritDoc} + */ + public function popProcessor(): callable + { + if ($this->handler instanceof ProcessableHandlerInterface) { + return $this->handler->popProcessor(); + } + + throw new \LogicException('The wrapped handler does not implement ' . ProcessableHandlerInterface::class); + } + + /** + * {@inheritDoc} + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + if ($this->handler instanceof FormattableHandlerInterface) { + $this->handler->setFormatter($formatter); + + return $this; + } + + throw new \LogicException('The wrapped handler does not implement ' . FormattableHandlerInterface::class); + } + + /** + * {@inheritDoc} + */ + public function getFormatter(): FormatterInterface + { + if ($this->handler instanceof FormattableHandlerInterface) { + return $this->handler->getFormatter(); + } + + throw new \LogicException('The wrapped handler does not implement ' . FormattableHandlerInterface::class); + } + + public function reset() + { + if ($this->handler instanceof ResettableInterface) { + $this->handler->reset(); + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php new file mode 100644 index 0000000..000ccea --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Utils; + +/** + * IFTTTHandler uses cURL to trigger IFTTT Maker actions + * + * Register a secret key and trigger/event name at https://ifttt.com/maker + * + * value1 will be the channel from monolog's Logger constructor, + * value2 will be the level name (ERROR, WARNING, ..) + * value3 will be the log record's message + * + * @author Nehal Patel + */ +class IFTTTHandler extends AbstractProcessingHandler +{ + /** @var string */ + private $eventName; + /** @var string */ + private $secretKey; + + /** + * @param string $eventName The name of the IFTTT Maker event that should be triggered + * @param string $secretKey A valid IFTTT secret key + */ + public function __construct(string $eventName, string $secretKey, $level = Logger::ERROR, bool $bubble = true) + { + if (!extension_loaded('curl')) { + throw new MissingExtensionException('The curl extension is needed to use the IFTTTHandler'); + } + + $this->eventName = $eventName; + $this->secretKey = $secretKey; + + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + public function write(array $record): void + { + $postData = [ + "value1" => $record["channel"], + "value2" => $record["level_name"], + "value3" => $record["message"], + ]; + $postString = Utils::jsonEncode($postData); + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, "https://maker.ifttt.com/trigger/" . $this->eventName . "/with/key/" . $this->secretKey); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $postString); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + "Content-Type: application/json", + ]); + + Curl\Util::execute($ch); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php new file mode 100644 index 0000000..c62dc24 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Inspired on LogEntriesHandler. + * + * @author Robert Kaufmann III + * @author Gabriel Machado + */ +class InsightOpsHandler extends SocketHandler +{ + /** + * @var string + */ + protected $logToken; + + /** + * @param string $token Log token supplied by InsightOps + * @param string $region Region where InsightOps account is hosted. Could be 'us' or 'eu'. + * @param bool $useSSL Whether or not SSL encryption should be used + * + * @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing + */ + public function __construct(string $token, string $region = 'us', bool $useSSL = true, $level = Logger::DEBUG, bool $bubble = true) + { + if ($useSSL && !extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP plugin is required to use SSL encrypted connection for InsightOpsHandler'); + } + + $endpoint = $useSSL + ? 'ssl://' . $region . '.data.logs.insight.rapid7.com:443' + : $region . '.data.logs.insight.rapid7.com:80'; + + parent::__construct($endpoint, $level, $bubble); + $this->logToken = $token; + } + + /** + * {@inheritDoc} + */ + protected function generateDataStream(array $record): string + { + return $this->logToken . ' ' . $record['formatted']; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php new file mode 100644 index 0000000..a0739cf --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * @author Robert Kaufmann III + */ +class LogEntriesHandler extends SocketHandler +{ + /** + * @var string + */ + protected $logToken; + + /** + * @param string $token Log token supplied by LogEntries + * @param bool $useSSL Whether or not SSL encryption should be used. + * @param string $host Custom hostname to send the data to if needed + * + * @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing + */ + public function __construct(string $token, bool $useSSL = true, $level = Logger::DEBUG, bool $bubble = true, string $host = 'data.logentries.com') + { + if ($useSSL && !extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP plugin is required to use SSL encrypted connection for LogEntriesHandler'); + } + + $endpoint = $useSSL ? 'ssl://' . $host . ':443' : $host . ':80'; + parent::__construct($endpoint, $level, $bubble); + $this->logToken = $token; + } + + /** + * {@inheritDoc} + */ + protected function generateDataStream(array $record): string + { + return $this->logToken . ' ' . $record['formatted']; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php new file mode 100644 index 0000000..6d13db3 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php @@ -0,0 +1,160 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LogglyFormatter; +use function array_key_exists; +use CurlHandle; + +/** + * Sends errors to Loggly. + * + * @author Przemek Sobstel + * @author Adam Pancutt + * @author Gregory Barchard + */ +class LogglyHandler extends AbstractProcessingHandler +{ + protected const HOST = 'logs-01.loggly.com'; + protected const ENDPOINT_SINGLE = 'inputs'; + protected const ENDPOINT_BATCH = 'bulk'; + + /** + * Caches the curl handlers for every given endpoint. + * + * @var resource[]|CurlHandle[] + */ + protected $curlHandlers = []; + + /** @var string */ + protected $token; + + /** @var string[] */ + protected $tag = []; + + /** + * @param string $token API token supplied by Loggly + * + * @throws MissingExtensionException If the curl extension is missing + */ + public function __construct(string $token, $level = Logger::DEBUG, bool $bubble = true) + { + if (!extension_loaded('curl')) { + throw new MissingExtensionException('The curl extension is needed to use the LogglyHandler'); + } + + $this->token = $token; + + parent::__construct($level, $bubble); + } + + /** + * Loads and returns the shared curl handler for the given endpoint. + * + * @param string $endpoint + * + * @return resource|CurlHandle + */ + protected function getCurlHandler(string $endpoint) + { + if (!array_key_exists($endpoint, $this->curlHandlers)) { + $this->curlHandlers[$endpoint] = $this->loadCurlHandle($endpoint); + } + + return $this->curlHandlers[$endpoint]; + } + + /** + * Starts a fresh curl session for the given endpoint and returns its handler. + * + * @param string $endpoint + * + * @return resource|CurlHandle + */ + private function loadCurlHandle(string $endpoint) + { + $url = sprintf("https://%s/%s/%s/", static::HOST, $endpoint, $this->token); + + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + + return $ch; + } + + /** + * @param string[]|string $tag + */ + public function setTag($tag): self + { + $tag = !empty($tag) ? $tag : []; + $this->tag = is_array($tag) ? $tag : [$tag]; + + return $this; + } + + /** + * @param string[]|string $tag + */ + public function addTag($tag): self + { + if (!empty($tag)) { + $tag = is_array($tag) ? $tag : [$tag]; + $this->tag = array_unique(array_merge($this->tag, $tag)); + } + + return $this; + } + + protected function write(array $record): void + { + $this->send($record["formatted"], static::ENDPOINT_SINGLE); + } + + public function handleBatch(array $records): void + { + $level = $this->level; + + $records = array_filter($records, function ($record) use ($level) { + return ($record['level'] >= $level); + }); + + if ($records) { + $this->send($this->getFormatter()->formatBatch($records), static::ENDPOINT_BATCH); + } + } + + protected function send(string $data, string $endpoint): void + { + $ch = $this->getCurlHandler($endpoint); + + $headers = ['Content-Type: application/json']; + + if (!empty($this->tag)) { + $headers[] = 'X-LOGGLY-TAG: '.implode(',', $this->tag); + } + + curl_setopt($ch, CURLOPT_POSTFIELDS, $data); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + + Curl\Util::execute($ch, 5, false); + } + + protected function getDefaultFormatter(): FormatterInterface + { + return new LogglyFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/LogmaticHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/LogmaticHandler.php new file mode 100644 index 0000000..e7666ec --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/LogmaticHandler.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LogmaticFormatter; + +/** + * @author Julien Breux + */ +class LogmaticHandler extends SocketHandler +{ + /** + * @var string + */ + private $logToken; + + /** + * @var string + */ + private $hostname; + + /** + * @var string + */ + private $appname; + + /** + * @param string $token Log token supplied by Logmatic. + * @param string $hostname Host name supplied by Logmatic. + * @param string $appname Application name supplied by Logmatic. + * @param bool $useSSL Whether or not SSL encryption should be used. + * + * @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing + */ + public function __construct(string $token, string $hostname = '', string $appname = '', bool $useSSL = true, $level = Logger::DEBUG, bool $bubble = true) + { + if ($useSSL && !extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP extension is required to use SSL encrypted connection for LogmaticHandler'); + } + + $endpoint = $useSSL ? 'ssl://api.logmatic.io:10515' : 'api.logmatic.io:10514'; + $endpoint .= '/v1/'; + + parent::__construct($endpoint, $level, $bubble); + + $this->logToken = $token; + $this->hostname = $hostname; + $this->appname = $appname; + } + + /** + * {@inheritDoc} + */ + protected function generateDataStream(array $record): string + { + return $this->logToken . ' ' . $record['formatted']; + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + $formatter = new LogmaticFormatter(); + + if (!empty($this->hostname)) { + $formatter->setHostname($this->hostname); + } + if (!empty($this->appname)) { + $formatter->setAppname($this->appname); + } + + return $formatter; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php new file mode 100644 index 0000000..97f3432 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\HtmlFormatter; + +/** + * Base class for all mail handlers + * + * @author Gyula Sallai + * + * @phpstan-import-type Record from \Monolog\Logger + */ +abstract class MailHandler extends AbstractProcessingHandler +{ + /** + * {@inheritDoc} + */ + public function handleBatch(array $records): void + { + $messages = []; + + foreach ($records as $record) { + if ($record['level'] < $this->level) { + continue; + } + /** @var Record $message */ + $message = $this->processRecord($record); + $messages[] = $message; + } + + if (!empty($messages)) { + $this->send((string) $this->getFormatter()->formatBatch($messages), $messages); + } + } + + /** + * Send a mail with the given content + * + * @param string $content formatted email body to be sent + * @param array $records the array of log records that formed this content + * + * @phpstan-param Record[] $records + */ + abstract protected function send(string $content, array $records): void; + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + $this->send((string) $record['formatted'], [$record]); + } + + /** + * @phpstan-param non-empty-array $records + * @phpstan-return Record + */ + protected function getHighestRecord(array $records): array + { + $highestRecord = null; + foreach ($records as $record) { + if ($highestRecord === null || $highestRecord['level'] < $record['level']) { + $highestRecord = $record; + } + } + + return $highestRecord; + } + + protected function isHtmlBody(string $body): bool + { + return ($body[0] ?? null) === '<'; + } + + /** + * Gets the default formatter. + * + * @return FormatterInterface + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new HtmlFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php new file mode 100644 index 0000000..3003500 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Swift; +use Swift_Message; + +/** + * MandrillHandler uses cURL to send the emails to the Mandrill API + * + * @author Adam Nicholson + */ +class MandrillHandler extends MailHandler +{ + /** @var Swift_Message */ + protected $message; + /** @var string */ + protected $apiKey; + + /** + * @psalm-param Swift_Message|callable(): Swift_Message $message + * + * @param string $apiKey A valid Mandrill API key + * @param callable|Swift_Message $message An example message for real messages, only the body will be replaced + */ + public function __construct(string $apiKey, $message, $level = Logger::ERROR, bool $bubble = true) + { + parent::__construct($level, $bubble); + + if (!$message instanceof Swift_Message && is_callable($message)) { + $message = $message(); + } + if (!$message instanceof Swift_Message) { + throw new \InvalidArgumentException('You must provide either a Swift_Message instance or a callable returning it'); + } + $this->message = $message; + $this->apiKey = $apiKey; + } + + /** + * {@inheritDoc} + */ + protected function send(string $content, array $records): void + { + $mime = 'text/plain'; + if ($this->isHtmlBody($content)) { + $mime = 'text/html'; + } + + $message = clone $this->message; + $message->setBody($content, $mime); + /** @phpstan-ignore-next-line */ + if (version_compare(Swift::VERSION, '6.0.0', '>=')) { + $message->setDate(new \DateTimeImmutable()); + } else { + /** @phpstan-ignore-next-line */ + $message->setDate(time()); + } + + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, 'https://mandrillapp.com/api/1.0/messages/send-raw.json'); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([ + 'key' => $this->apiKey, + 'raw_message' => (string) $message, + 'async' => false, + ])); + + Curl\Util::execute($ch); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php b/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php new file mode 100644 index 0000000..3965aee --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Exception can be thrown if an extension for a handler is missing + * + * @author Christian Bergau + */ +class MissingExtensionException extends \Exception +{ +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php new file mode 100644 index 0000000..3063091 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use MongoDB\Driver\BulkWrite; +use MongoDB\Driver\Manager; +use MongoDB\Client; +use Monolog\Logger; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\MongoDBFormatter; + +/** + * Logs to a MongoDB database. + * + * Usage example: + * + * $log = new \Monolog\Logger('application'); + * $client = new \MongoDB\Client('mongodb://localhost:27017'); + * $mongodb = new \Monolog\Handler\MongoDBHandler($client, 'logs', 'prod'); + * $log->pushHandler($mongodb); + * + * The above examples uses the MongoDB PHP library's client class; however, the + * MongoDB\Driver\Manager class from ext-mongodb is also supported. + */ +class MongoDBHandler extends AbstractProcessingHandler +{ + /** @var \MongoDB\Collection */ + private $collection; + /** @var Client|Manager */ + private $manager; + /** @var string */ + private $namespace; + + /** + * Constructor. + * + * @param Client|Manager $mongodb MongoDB library or driver client + * @param string $database Database name + * @param string $collection Collection name + */ + public function __construct($mongodb, string $database, string $collection, $level = Logger::DEBUG, bool $bubble = true) + { + if (!($mongodb instanceof Client || $mongodb instanceof Manager)) { + throw new \InvalidArgumentException('MongoDB\Client or MongoDB\Driver\Manager instance required'); + } + + if ($mongodb instanceof Client) { + $this->collection = $mongodb->selectCollection($database, $collection); + } else { + $this->manager = $mongodb; + $this->namespace = $database . '.' . $collection; + } + + parent::__construct($level, $bubble); + } + + protected function write(array $record): void + { + if (isset($this->collection)) { + $this->collection->insertOne($record['formatted']); + } + + if (isset($this->manager, $this->namespace)) { + $bulk = new BulkWrite; + $bulk->insert($record["formatted"]); + $this->manager->executeBulkWrite($this->namespace, $bulk); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new MongoDBFormatter; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php new file mode 100644 index 0000000..0c0a3bd --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php @@ -0,0 +1,174 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; + +/** + * NativeMailerHandler uses the mail() function to send the emails + * + * @author Christophe Coevoet + * @author Mark Garrett + */ +class NativeMailerHandler extends MailHandler +{ + /** + * The email addresses to which the message will be sent + * @var string[] + */ + protected $to; + + /** + * The subject of the email + * @var string + */ + protected $subject; + + /** + * Optional headers for the message + * @var string[] + */ + protected $headers = []; + + /** + * Optional parameters for the message + * @var string[] + */ + protected $parameters = []; + + /** + * The wordwrap length for the message + * @var int + */ + protected $maxColumnWidth; + + /** + * The Content-type for the message + * @var string|null + */ + protected $contentType; + + /** + * The encoding for the message + * @var string + */ + protected $encoding = 'utf-8'; + + /** + * @param string|string[] $to The receiver of the mail + * @param string $subject The subject of the mail + * @param string $from The sender of the mail + * @param int $maxColumnWidth The maximum column width that the message lines will have + */ + public function __construct($to, string $subject, string $from, $level = Logger::ERROR, bool $bubble = true, int $maxColumnWidth = 70) + { + parent::__construct($level, $bubble); + $this->to = (array) $to; + $this->subject = $subject; + $this->addHeader(sprintf('From: %s', $from)); + $this->maxColumnWidth = $maxColumnWidth; + } + + /** + * Add headers to the message + * + * @param string|string[] $headers Custom added headers + */ + public function addHeader($headers): self + { + foreach ((array) $headers as $header) { + if (strpos($header, "\n") !== false || strpos($header, "\r") !== false) { + throw new \InvalidArgumentException('Headers can not contain newline characters for security reasons'); + } + $this->headers[] = $header; + } + + return $this; + } + + /** + * Add parameters to the message + * + * @param string|string[] $parameters Custom added parameters + */ + public function addParameter($parameters): self + { + $this->parameters = array_merge($this->parameters, (array) $parameters); + + return $this; + } + + /** + * {@inheritDoc} + */ + protected function send(string $content, array $records): void + { + $contentType = $this->getContentType() ?: ($this->isHtmlBody($content) ? 'text/html' : 'text/plain'); + + if ($contentType !== 'text/html') { + $content = wordwrap($content, $this->maxColumnWidth); + } + + $headers = ltrim(implode("\r\n", $this->headers) . "\r\n", "\r\n"); + $headers .= 'Content-type: ' . $contentType . '; charset=' . $this->getEncoding() . "\r\n"; + if ($contentType === 'text/html' && false === strpos($headers, 'MIME-Version:')) { + $headers .= 'MIME-Version: 1.0' . "\r\n"; + } + + $subject = $this->subject; + if ($records) { + $subjectFormatter = new LineFormatter($this->subject); + $subject = $subjectFormatter->format($this->getHighestRecord($records)); + } + + $parameters = implode(' ', $this->parameters); + foreach ($this->to as $to) { + mail($to, $subject, $content, $headers, $parameters); + } + } + + public function getContentType(): ?string + { + return $this->contentType; + } + + public function getEncoding(): string + { + return $this->encoding; + } + + /** + * @param string $contentType The content type of the email - Defaults to text/plain. Use text/html for HTML messages. + */ + public function setContentType(string $contentType): self + { + if (strpos($contentType, "\n") !== false || strpos($contentType, "\r") !== false) { + throw new \InvalidArgumentException('The content type can not contain newline characters to prevent email header injection'); + } + + $this->contentType = $contentType; + + return $this; + } + + public function setEncoding(string $encoding): self + { + if (strpos($encoding, "\n") !== false || strpos($encoding, "\r") !== false) { + throw new \InvalidArgumentException('The encoding can not contain newline characters to prevent email header injection'); + } + + $this->encoding = $encoding; + + return $this; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php new file mode 100644 index 0000000..114d749 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php @@ -0,0 +1,199 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Utils; +use Monolog\Formatter\NormalizerFormatter; +use Monolog\Formatter\FormatterInterface; + +/** + * Class to record a log on a NewRelic application. + * Enabling New Relic High Security mode may prevent capture of useful information. + * + * This handler requires a NormalizerFormatter to function and expects an array in $record['formatted'] + * + * @see https://docs.newrelic.com/docs/agents/php-agent + * @see https://docs.newrelic.com/docs/accounts-partnerships/accounts/security/high-security + */ +class NewRelicHandler extends AbstractProcessingHandler +{ + /** + * Name of the New Relic application that will receive logs from this handler. + * + * @var ?string + */ + protected $appName; + + /** + * Name of the current transaction + * + * @var ?string + */ + protected $transactionName; + + /** + * Some context and extra data is passed into the handler as arrays of values. Do we send them as is + * (useful if we are using the API), or explode them for display on the NewRelic RPM website? + * + * @var bool + */ + protected $explodeArrays; + + /** + * {@inheritDoc} + * + * @param string|null $appName + * @param bool $explodeArrays + * @param string|null $transactionName + */ + public function __construct( + $level = Logger::ERROR, + bool $bubble = true, + ?string $appName = null, + bool $explodeArrays = false, + ?string $transactionName = null + ) { + parent::__construct($level, $bubble); + + $this->appName = $appName; + $this->explodeArrays = $explodeArrays; + $this->transactionName = $transactionName; + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + if (!$this->isNewRelicEnabled()) { + throw new MissingExtensionException('The newrelic PHP extension is required to use the NewRelicHandler'); + } + + if ($appName = $this->getAppName($record['context'])) { + $this->setNewRelicAppName($appName); + } + + if ($transactionName = $this->getTransactionName($record['context'])) { + $this->setNewRelicTransactionName($transactionName); + unset($record['formatted']['context']['transaction_name']); + } + + if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Throwable) { + newrelic_notice_error($record['message'], $record['context']['exception']); + unset($record['formatted']['context']['exception']); + } else { + newrelic_notice_error($record['message']); + } + + if (isset($record['formatted']['context']) && is_array($record['formatted']['context'])) { + foreach ($record['formatted']['context'] as $key => $parameter) { + if (is_array($parameter) && $this->explodeArrays) { + foreach ($parameter as $paramKey => $paramValue) { + $this->setNewRelicParameter('context_' . $key . '_' . $paramKey, $paramValue); + } + } else { + $this->setNewRelicParameter('context_' . $key, $parameter); + } + } + } + + if (isset($record['formatted']['extra']) && is_array($record['formatted']['extra'])) { + foreach ($record['formatted']['extra'] as $key => $parameter) { + if (is_array($parameter) && $this->explodeArrays) { + foreach ($parameter as $paramKey => $paramValue) { + $this->setNewRelicParameter('extra_' . $key . '_' . $paramKey, $paramValue); + } + } else { + $this->setNewRelicParameter('extra_' . $key, $parameter); + } + } + } + } + + /** + * Checks whether the NewRelic extension is enabled in the system. + * + * @return bool + */ + protected function isNewRelicEnabled(): bool + { + return extension_loaded('newrelic'); + } + + /** + * Returns the appname where this log should be sent. Each log can override the default appname, set in this + * handler's constructor, by providing the appname in it's context. + * + * @param mixed[] $context + */ + protected function getAppName(array $context): ?string + { + if (isset($context['appname'])) { + return $context['appname']; + } + + return $this->appName; + } + + /** + * Returns the name of the current transaction. Each log can override the default transaction name, set in this + * handler's constructor, by providing the transaction_name in it's context + * + * @param mixed[] $context + */ + protected function getTransactionName(array $context): ?string + { + if (isset($context['transaction_name'])) { + return $context['transaction_name']; + } + + return $this->transactionName; + } + + /** + * Sets the NewRelic application that should receive this log. + */ + protected function setNewRelicAppName(string $appName): void + { + newrelic_set_appname($appName); + } + + /** + * Overwrites the name of the current transaction + */ + protected function setNewRelicTransactionName(string $transactionName): void + { + newrelic_name_transaction($transactionName); + } + + /** + * @param string $key + * @param mixed $value + */ + protected function setNewRelicParameter(string $key, $value): void + { + if (null === $value || is_scalar($value)) { + newrelic_add_custom_parameter($key, $value); + } else { + newrelic_add_custom_parameter($key, Utils::jsonEncode($value, null, true)); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new NormalizerFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NoopHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NoopHandler.php new file mode 100644 index 0000000..1ddf0be --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/NoopHandler.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * No-op + * + * This handler handles anything, but does nothing, and does not stop bubbling to the rest of the stack. + * This can be used for testing, or to disable a handler when overriding a configuration without + * influencing the rest of the stack. + * + * @author Roel Harbers + */ +class NoopHandler extends Handler +{ + /** + * {@inheritDoc} + */ + public function isHandling(array $record): bool + { + return true; + } + + /** + * {@inheritDoc} + */ + public function handle(array $record): bool + { + return false; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php new file mode 100644 index 0000000..e75ee0c --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Psr\Log\LogLevel; + +/** + * Blackhole + * + * Any record it can handle will be thrown away. This can be used + * to put on top of an existing stack to override it temporarily. + * + * @author Jordi Boggiano + * + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger + */ +class NullHandler extends Handler +{ + /** + * @var int + */ + private $level; + + /** + * @param string|int $level The minimum logging level at which this handler will be triggered + * + * @phpstan-param Level|LevelName|LogLevel::* $level + */ + public function __construct($level = Logger::DEBUG) + { + $this->level = Logger::toMonologLevel($level); + } + + /** + * {@inheritDoc} + */ + public function isHandling(array $record): bool + { + return $record['level'] >= $this->level; + } + + /** + * {@inheritDoc} + */ + public function handle(array $record): bool + { + return $record['level'] >= $this->level; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/OverflowHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/OverflowHandler.php new file mode 100644 index 0000000..22068c9 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/OverflowHandler.php @@ -0,0 +1,149 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\FormatterInterface; + +/** + * Handler to only pass log messages when a certain threshold of number of messages is reached. + * + * This can be useful in cases of processing a batch of data, but you're for example only interested + * in case it fails catastrophically instead of a warning for 1 or 2 events. Worse things can happen, right? + * + * Usage example: + * + * ``` + * $log = new Logger('application'); + * $handler = new SomeHandler(...) + * + * // Pass all warnings to the handler when more than 10 & all error messages when more then 5 + * $overflow = new OverflowHandler($handler, [Logger::WARNING => 10, Logger::ERROR => 5]); + * + * $log->pushHandler($overflow); + *``` + * + * @author Kris Buist + */ +class OverflowHandler extends AbstractHandler implements FormattableHandlerInterface +{ + /** @var HandlerInterface */ + private $handler; + + /** @var int[] */ + private $thresholdMap = [ + Logger::DEBUG => 0, + Logger::INFO => 0, + Logger::NOTICE => 0, + Logger::WARNING => 0, + Logger::ERROR => 0, + Logger::CRITICAL => 0, + Logger::ALERT => 0, + Logger::EMERGENCY => 0, + ]; + + /** + * Buffer of all messages passed to the handler before the threshold was reached + * + * @var mixed[][] + */ + private $buffer = []; + + /** + * @param HandlerInterface $handler + * @param int[] $thresholdMap Dictionary of logger level => threshold + */ + public function __construct( + HandlerInterface $handler, + array $thresholdMap = [], + $level = Logger::DEBUG, + bool $bubble = true + ) { + $this->handler = $handler; + foreach ($thresholdMap as $thresholdLevel => $threshold) { + $this->thresholdMap[$thresholdLevel] = $threshold; + } + parent::__construct($level, $bubble); + } + + /** + * Handles a record. + * + * All records may be passed to this method, and the handler should discard + * those that it does not want to handle. + * + * The return value of this function controls the bubbling process of the handler stack. + * Unless the bubbling is interrupted (by returning true), the Logger class will keep on + * calling further handlers in the stack with a given log record. + * + * {@inheritDoc} + */ + public function handle(array $record): bool + { + if ($record['level'] < $this->level) { + return false; + } + + $level = $record['level']; + + if (!isset($this->thresholdMap[$level])) { + $this->thresholdMap[$level] = 0; + } + + if ($this->thresholdMap[$level] > 0) { + // The overflow threshold is not yet reached, so we're buffering the record and lowering the threshold by 1 + $this->thresholdMap[$level]--; + $this->buffer[$level][] = $record; + + return false === $this->bubble; + } + + if ($this->thresholdMap[$level] == 0) { + // This current message is breaking the threshold. Flush the buffer and continue handling the current record + foreach ($this->buffer[$level] ?? [] as $buffered) { + $this->handler->handle($buffered); + } + $this->thresholdMap[$level]--; + unset($this->buffer[$level]); + } + + $this->handler->handle($record); + + return false === $this->bubble; + } + + /** + * {@inheritDoc} + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + if ($this->handler instanceof FormattableHandlerInterface) { + $this->handler->setFormatter($formatter); + + return $this; + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($this->handler).' does not support formatters.'); + } + + /** + * {@inheritDoc} + */ + public function getFormatter(): FormatterInterface + { + if ($this->handler instanceof FormattableHandlerInterface) { + return $this->handler->getFormatter(); + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($this->handler).' does not support formatters.'); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php new file mode 100644 index 0000000..6e209b1 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php @@ -0,0 +1,262 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Formatter\FormatterInterface; +use Monolog\Logger; +use Monolog\Utils; +use PhpConsole\Connector; +use PhpConsole\Handler as VendorPhpConsoleHandler; +use PhpConsole\Helper; + +/** + * Monolog handler for Google Chrome extension "PHP Console" + * + * Display PHP error/debug log messages in Google Chrome console and notification popups, executes PHP code remotely + * + * Usage: + * 1. Install Google Chrome extension https://chrome.google.com/webstore/detail/php-console/nfhmhhlpfleoednkpnnnkolmclajemef + * 2. See overview https://github.com/barbushin/php-console#overview + * 3. Install PHP Console library https://github.com/barbushin/php-console#installation + * 4. Example (result will looks like http://i.hizliresim.com/vg3Pz4.png) + * + * $logger = new \Monolog\Logger('all', array(new \Monolog\Handler\PHPConsoleHandler())); + * \Monolog\ErrorHandler::register($logger); + * echo $undefinedVar; + * $logger->debug('SELECT * FROM users', array('db', 'time' => 0.012)); + * PC::debug($_SERVER); // PHP Console debugger for any type of vars + * + * @author Sergey Barbushin https://www.linkedin.com/in/barbushin + * + * @phpstan-import-type Record from \Monolog\Logger + */ +class PHPConsoleHandler extends AbstractProcessingHandler +{ + /** @var array */ + private $options = [ + 'enabled' => true, // bool Is PHP Console server enabled + 'classesPartialsTraceIgnore' => ['Monolog\\'], // array Hide calls of classes started with... + 'debugTagsKeysInContext' => [0, 'tag'], // bool Is PHP Console server enabled + 'useOwnErrorsHandler' => false, // bool Enable errors handling + 'useOwnExceptionsHandler' => false, // bool Enable exceptions handling + 'sourcesBasePath' => null, // string Base path of all project sources to strip in errors source paths + 'registerHelper' => true, // bool Register PhpConsole\Helper that allows short debug calls like PC::debug($var, 'ta.g.s') + 'serverEncoding' => null, // string|null Server internal encoding + 'headersLimit' => null, // int|null Set headers size limit for your web-server + 'password' => null, // string|null Protect PHP Console connection by password + 'enableSslOnlyMode' => false, // bool Force connection by SSL for clients with PHP Console installed + 'ipMasks' => [], // array Set IP masks of clients that will be allowed to connect to PHP Console: array('192.168.*.*', '127.0.0.1') + 'enableEvalListener' => false, // bool Enable eval request to be handled by eval dispatcher(if enabled, 'password' option is also required) + 'dumperDetectCallbacks' => false, // bool Convert callback items in dumper vars to (callback SomeClass::someMethod) strings + 'dumperLevelLimit' => 5, // int Maximum dumped vars array or object nested dump level + 'dumperItemsCountLimit' => 100, // int Maximum dumped var same level array items or object properties number + 'dumperItemSizeLimit' => 5000, // int Maximum length of any string or dumped array item + 'dumperDumpSizeLimit' => 500000, // int Maximum approximate size of dumped vars result formatted in JSON + 'detectDumpTraceAndSource' => false, // bool Autodetect and append trace data to debug + 'dataStorage' => null, // \PhpConsole\Storage|null Fixes problem with custom $_SESSION handler(see http://goo.gl/Ne8juJ) + ]; + + /** @var Connector */ + private $connector; + + /** + * @param array $options See \Monolog\Handler\PHPConsoleHandler::$options for more details + * @param Connector|null $connector Instance of \PhpConsole\Connector class (optional) + * @throws \RuntimeException + */ + public function __construct(array $options = [], ?Connector $connector = null, $level = Logger::DEBUG, bool $bubble = true) + { + if (!class_exists('PhpConsole\Connector')) { + throw new \RuntimeException('PHP Console library not found. See https://github.com/barbushin/php-console#installation'); + } + parent::__construct($level, $bubble); + $this->options = $this->initOptions($options); + $this->connector = $this->initConnector($connector); + } + + /** + * @param array $options + * + * @return array + */ + private function initOptions(array $options): array + { + $wrongOptions = array_diff(array_keys($options), array_keys($this->options)); + if ($wrongOptions) { + throw new \RuntimeException('Unknown options: ' . implode(', ', $wrongOptions)); + } + + return array_replace($this->options, $options); + } + + private function initConnector(?Connector $connector = null): Connector + { + if (!$connector) { + if ($this->options['dataStorage']) { + Connector::setPostponeStorage($this->options['dataStorage']); + } + $connector = Connector::getInstance(); + } + + if ($this->options['registerHelper'] && !Helper::isRegistered()) { + Helper::register(); + } + + if ($this->options['enabled'] && $connector->isActiveClient()) { + if ($this->options['useOwnErrorsHandler'] || $this->options['useOwnExceptionsHandler']) { + $handler = VendorPhpConsoleHandler::getInstance(); + $handler->setHandleErrors($this->options['useOwnErrorsHandler']); + $handler->setHandleExceptions($this->options['useOwnExceptionsHandler']); + $handler->start(); + } + if ($this->options['sourcesBasePath']) { + $connector->setSourcesBasePath($this->options['sourcesBasePath']); + } + if ($this->options['serverEncoding']) { + $connector->setServerEncoding($this->options['serverEncoding']); + } + if ($this->options['password']) { + $connector->setPassword($this->options['password']); + } + if ($this->options['enableSslOnlyMode']) { + $connector->enableSslOnlyMode(); + } + if ($this->options['ipMasks']) { + $connector->setAllowedIpMasks($this->options['ipMasks']); + } + if ($this->options['headersLimit']) { + $connector->setHeadersLimit($this->options['headersLimit']); + } + if ($this->options['detectDumpTraceAndSource']) { + $connector->getDebugDispatcher()->detectTraceAndSource = true; + } + $dumper = $connector->getDumper(); + $dumper->levelLimit = $this->options['dumperLevelLimit']; + $dumper->itemsCountLimit = $this->options['dumperItemsCountLimit']; + $dumper->itemSizeLimit = $this->options['dumperItemSizeLimit']; + $dumper->dumpSizeLimit = $this->options['dumperDumpSizeLimit']; + $dumper->detectCallbacks = $this->options['dumperDetectCallbacks']; + if ($this->options['enableEvalListener']) { + $connector->startEvalRequestsListener(); + } + } + + return $connector; + } + + public function getConnector(): Connector + { + return $this->connector; + } + + /** + * @return array + */ + public function getOptions(): array + { + return $this->options; + } + + public function handle(array $record): bool + { + if ($this->options['enabled'] && $this->connector->isActiveClient()) { + return parent::handle($record); + } + + return !$this->bubble; + } + + /** + * Writes the record down to the log of the implementing handler + */ + protected function write(array $record): void + { + if ($record['level'] < Logger::NOTICE) { + $this->handleDebugRecord($record); + } elseif (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Throwable) { + $this->handleExceptionRecord($record); + } else { + $this->handleErrorRecord($record); + } + } + + /** + * @phpstan-param Record $record + */ + private function handleDebugRecord(array $record): void + { + $tags = $this->getRecordTags($record); + $message = $record['message']; + if ($record['context']) { + $message .= ' ' . Utils::jsonEncode($this->connector->getDumper()->dump(array_filter($record['context'])), null, true); + } + $this->connector->getDebugDispatcher()->dispatchDebug($message, $tags, $this->options['classesPartialsTraceIgnore']); + } + + /** + * @phpstan-param Record $record + */ + private function handleExceptionRecord(array $record): void + { + $this->connector->getErrorsDispatcher()->dispatchException($record['context']['exception']); + } + + /** + * @phpstan-param Record $record + */ + private function handleErrorRecord(array $record): void + { + $context = $record['context']; + + $this->connector->getErrorsDispatcher()->dispatchError( + $context['code'] ?? null, + $context['message'] ?? $record['message'], + $context['file'] ?? null, + $context['line'] ?? null, + $this->options['classesPartialsTraceIgnore'] + ); + } + + /** + * @phpstan-param Record $record + * @return string + */ + private function getRecordTags(array &$record) + { + $tags = null; + if (!empty($record['context'])) { + $context = & $record['context']; + foreach ($this->options['debugTagsKeysInContext'] as $key) { + if (!empty($context[$key])) { + $tags = $context[$key]; + if ($key === 0) { + array_shift($context); + } else { + unset($context[$key]); + } + break; + } + } + } + + return $tags ?: strtolower($record['level_name']); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new LineFormatter('%message%'); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ProcessHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ProcessHandler.php new file mode 100644 index 0000000..8a8cf1b --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ProcessHandler.php @@ -0,0 +1,191 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Stores to STDIN of any process, specified by a command. + * + * Usage example: + *
        + * $log = new Logger('myLogger');
        + * $log->pushHandler(new ProcessHandler('/usr/bin/php /var/www/monolog/someScript.php'));
        + * 
        + * + * @author Kolja Zuelsdorf + */ +class ProcessHandler extends AbstractProcessingHandler +{ + /** + * Holds the process to receive data on its STDIN. + * + * @var resource|bool|null + */ + private $process; + + /** + * @var string + */ + private $command; + + /** + * @var string|null + */ + private $cwd; + + /** + * @var resource[] + */ + private $pipes = []; + + /** + * @var array + */ + protected const DESCRIPTOR_SPEC = [ + 0 => ['pipe', 'r'], // STDIN is a pipe that the child will read from + 1 => ['pipe', 'w'], // STDOUT is a pipe that the child will write to + 2 => ['pipe', 'w'], // STDERR is a pipe to catch the any errors + ]; + + /** + * @param string $command Command for the process to start. Absolute paths are recommended, + * especially if you do not use the $cwd parameter. + * @param string|null $cwd "Current working directory" (CWD) for the process to be executed in. + * @throws \InvalidArgumentException + */ + public function __construct(string $command, $level = Logger::DEBUG, bool $bubble = true, ?string $cwd = null) + { + if ($command === '') { + throw new \InvalidArgumentException('The command argument must be a non-empty string.'); + } + if ($cwd === '') { + throw new \InvalidArgumentException('The optional CWD argument must be a non-empty string or null.'); + } + + parent::__construct($level, $bubble); + + $this->command = $command; + $this->cwd = $cwd; + } + + /** + * Writes the record down to the log of the implementing handler + * + * @throws \UnexpectedValueException + */ + protected function write(array $record): void + { + $this->ensureProcessIsStarted(); + + $this->writeProcessInput($record['formatted']); + + $errors = $this->readProcessErrors(); + if (empty($errors) === false) { + throw new \UnexpectedValueException(sprintf('Errors while writing to process: %s', $errors)); + } + } + + /** + * Makes sure that the process is actually started, and if not, starts it, + * assigns the stream pipes, and handles startup errors, if any. + */ + private function ensureProcessIsStarted(): void + { + if (is_resource($this->process) === false) { + $this->startProcess(); + + $this->handleStartupErrors(); + } + } + + /** + * Starts the actual process and sets all streams to non-blocking. + */ + private function startProcess(): void + { + $this->process = proc_open($this->command, static::DESCRIPTOR_SPEC, $this->pipes, $this->cwd); + + foreach ($this->pipes as $pipe) { + stream_set_blocking($pipe, false); + } + } + + /** + * Selects the STDERR stream, handles upcoming startup errors, and throws an exception, if any. + * + * @throws \UnexpectedValueException + */ + private function handleStartupErrors(): void + { + $selected = $this->selectErrorStream(); + if (false === $selected) { + throw new \UnexpectedValueException('Something went wrong while selecting a stream.'); + } + + $errors = $this->readProcessErrors(); + + if (is_resource($this->process) === false || empty($errors) === false) { + throw new \UnexpectedValueException( + sprintf('The process "%s" could not be opened: ' . $errors, $this->command) + ); + } + } + + /** + * Selects the STDERR stream. + * + * @return int|bool + */ + protected function selectErrorStream() + { + $empty = []; + $errorPipes = [$this->pipes[2]]; + + return stream_select($errorPipes, $empty, $empty, 1); + } + + /** + * Reads the errors of the process, if there are any. + * + * @codeCoverageIgnore + * @return string Empty string if there are no errors. + */ + protected function readProcessErrors(): string + { + return (string) stream_get_contents($this->pipes[2]); + } + + /** + * Writes to the input stream of the opened process. + * + * @codeCoverageIgnore + */ + protected function writeProcessInput(string $string): void + { + fwrite($this->pipes[0], $string); + } + + /** + * {@inheritDoc} + */ + public function close(): void + { + if (is_resource($this->process)) { + foreach ($this->pipes as $pipe) { + fclose($pipe); + } + proc_close($this->process); + $this->process = null; + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php new file mode 100644 index 0000000..3adec7a --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Processor\ProcessorInterface; + +/** + * Interface to describe loggers that have processors + * + * @author Jordi Boggiano + * + * @phpstan-import-type Record from \Monolog\Logger + */ +interface ProcessableHandlerInterface +{ + /** + * Adds a processor in the stack. + * + * @psalm-param ProcessorInterface|callable(Record): Record $callback + * + * @param ProcessorInterface|callable $callback + * @return HandlerInterface self + */ + public function pushProcessor(callable $callback): HandlerInterface; + + /** + * Removes the processor on top of the stack and returns it. + * + * @psalm-return ProcessorInterface|callable(Record): Record $callback + * + * @throws \LogicException In case the processor stack is empty + * @return callable|ProcessorInterface + */ + public function popProcessor(): callable; +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php b/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php new file mode 100644 index 0000000..9ef6e30 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\ResettableInterface; +use Monolog\Processor\ProcessorInterface; + +/** + * Helper trait for implementing ProcessableInterface + * + * @author Jordi Boggiano + * + * @phpstan-import-type Record from \Monolog\Logger + */ +trait ProcessableHandlerTrait +{ + /** + * @var callable[] + * @phpstan-var array + */ + protected $processors = []; + + /** + * {@inheritDoc} + */ + public function pushProcessor(callable $callback): HandlerInterface + { + array_unshift($this->processors, $callback); + + return $this; + } + + /** + * {@inheritDoc} + */ + public function popProcessor(): callable + { + if (!$this->processors) { + throw new \LogicException('You tried to pop from an empty processor stack.'); + } + + return array_shift($this->processors); + } + + /** + * Processes a record. + * + * @phpstan-param Record $record + * @phpstan-return Record + */ + protected function processRecord(array $record): array + { + foreach ($this->processors as $processor) { + $record = $processor($record); + } + + return $record; + } + + protected function resetProcessors(): void + { + foreach ($this->processors as $processor) { + if ($processor instanceof ResettableInterface) { + $processor->reset(); + } + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php new file mode 100644 index 0000000..36e19cc --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Psr\Log\LoggerInterface; +use Monolog\Formatter\FormatterInterface; + +/** + * Proxies log messages to an existing PSR-3 compliant logger. + * + * If a formatter is configured, the formatter's output MUST be a string and the + * formatted message will be fed to the wrapped PSR logger instead of the original + * log record's message. + * + * @author Michael Moussa + */ +class PsrHandler extends AbstractHandler implements FormattableHandlerInterface +{ + /** + * PSR-3 compliant logger + * + * @var LoggerInterface + */ + protected $logger; + + /** + * @var FormatterInterface|null + */ + protected $formatter; + + /** + * @param LoggerInterface $logger The underlying PSR-3 compliant logger to which messages will be proxied + */ + public function __construct(LoggerInterface $logger, $level = Logger::DEBUG, bool $bubble = true) + { + parent::__construct($level, $bubble); + + $this->logger = $logger; + } + + /** + * {@inheritDoc} + */ + public function handle(array $record): bool + { + if (!$this->isHandling($record)) { + return false; + } + + if ($this->formatter) { + $formatted = $this->formatter->format($record); + $this->logger->log(strtolower($record['level_name']), (string) $formatted, $record['context']); + } else { + $this->logger->log(strtolower($record['level_name']), $record['message'], $record['context']); + } + + return false === $this->bubble; + } + + /** + * Sets the formatter. + * + * @param FormatterInterface $formatter + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + $this->formatter = $formatter; + + return $this; + } + + /** + * Gets the formatter. + * + * @return FormatterInterface + */ + public function getFormatter(): FormatterInterface + { + if (!$this->formatter) { + throw new \LogicException('No formatter has been set and this handler does not have a default formatter'); + } + + return $this->formatter; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php new file mode 100644 index 0000000..255af5c --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php @@ -0,0 +1,232 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Utils; +use Psr\Log\LogLevel; + +/** + * Sends notifications through the pushover api to mobile phones + * + * @author Sebastian Göttschkes + * @see https://www.pushover.net/api + * + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger + */ +class PushoverHandler extends SocketHandler +{ + /** @var string */ + private $token; + /** @var array */ + private $users; + /** @var string */ + private $title; + /** @var string|int|null */ + private $user = null; + /** @var int */ + private $retry; + /** @var int */ + private $expire; + + /** @var int */ + private $highPriorityLevel; + /** @var int */ + private $emergencyLevel; + /** @var bool */ + private $useFormattedMessage = false; + + /** + * All parameters that can be sent to Pushover + * @see https://pushover.net/api + * @var array + */ + private $parameterNames = [ + 'token' => true, + 'user' => true, + 'message' => true, + 'device' => true, + 'title' => true, + 'url' => true, + 'url_title' => true, + 'priority' => true, + 'timestamp' => true, + 'sound' => true, + 'retry' => true, + 'expire' => true, + 'callback' => true, + ]; + + /** + * Sounds the api supports by default + * @see https://pushover.net/api#sounds + * @var string[] + */ + private $sounds = [ + 'pushover', 'bike', 'bugle', 'cashregister', 'classical', 'cosmic', 'falling', 'gamelan', 'incoming', + 'intermission', 'magic', 'mechanical', 'pianobar', 'siren', 'spacealarm', 'tugboat', 'alien', 'climb', + 'persistent', 'echo', 'updown', 'none', + ]; + + /** + * @param string $token Pushover api token + * @param string|array $users Pushover user id or array of ids the message will be sent to + * @param string|null $title Title sent to the Pushover API + * @param bool $useSSL Whether to connect via SSL. Required when pushing messages to users that are not + * the pushover.net app owner. OpenSSL is required for this option. + * @param string|int $highPriorityLevel The minimum logging level at which this handler will start + * sending "high priority" requests to the Pushover API + * @param string|int $emergencyLevel The minimum logging level at which this handler will start + * sending "emergency" requests to the Pushover API + * @param int $retry The retry parameter specifies how often (in seconds) the Pushover servers will + * send the same notification to the user. + * @param int $expire The expire parameter specifies how many seconds your notification will continue + * to be retried for (every retry seconds). + * + * @phpstan-param string|array $users + * @phpstan-param Level|LevelName|LogLevel::* $highPriorityLevel + * @phpstan-param Level|LevelName|LogLevel::* $emergencyLevel + */ + public function __construct( + string $token, + $users, + ?string $title = null, + $level = Logger::CRITICAL, + bool $bubble = true, + bool $useSSL = true, + $highPriorityLevel = Logger::CRITICAL, + $emergencyLevel = Logger::EMERGENCY, + int $retry = 30, + int $expire = 25200 + ) { + $connectionString = $useSSL ? 'ssl://api.pushover.net:443' : 'api.pushover.net:80'; + parent::__construct($connectionString, $level, $bubble); + + $this->token = $token; + $this->users = (array) $users; + $this->title = $title ?: (string) gethostname(); + $this->highPriorityLevel = Logger::toMonologLevel($highPriorityLevel); + $this->emergencyLevel = Logger::toMonologLevel($emergencyLevel); + $this->retry = $retry; + $this->expire = $expire; + } + + protected function generateDataStream(array $record): string + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + /** + * @phpstan-param FormattedRecord $record + */ + private function buildContent(array $record): string + { + // Pushover has a limit of 512 characters on title and message combined. + $maxMessageLength = 512 - strlen($this->title); + + $message = ($this->useFormattedMessage) ? $record['formatted'] : $record['message']; + $message = Utils::substr($message, 0, $maxMessageLength); + + $timestamp = $record['datetime']->getTimestamp(); + + $dataArray = [ + 'token' => $this->token, + 'user' => $this->user, + 'message' => $message, + 'title' => $this->title, + 'timestamp' => $timestamp, + ]; + + if (isset($record['level']) && $record['level'] >= $this->emergencyLevel) { + $dataArray['priority'] = 2; + $dataArray['retry'] = $this->retry; + $dataArray['expire'] = $this->expire; + } elseif (isset($record['level']) && $record['level'] >= $this->highPriorityLevel) { + $dataArray['priority'] = 1; + } + + // First determine the available parameters + $context = array_intersect_key($record['context'], $this->parameterNames); + $extra = array_intersect_key($record['extra'], $this->parameterNames); + + // Least important info should be merged with subsequent info + $dataArray = array_merge($extra, $context, $dataArray); + + // Only pass sounds that are supported by the API + if (isset($dataArray['sound']) && !in_array($dataArray['sound'], $this->sounds)) { + unset($dataArray['sound']); + } + + return http_build_query($dataArray); + } + + private function buildHeader(string $content): string + { + $header = "POST /1/messages.json HTTP/1.1\r\n"; + $header .= "Host: api.pushover.net\r\n"; + $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; + $header .= "Content-Length: " . strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } + + protected function write(array $record): void + { + foreach ($this->users as $user) { + $this->user = $user; + + parent::write($record); + $this->closeSocket(); + } + + $this->user = null; + } + + /** + * @param int|string $value + * + * @phpstan-param Level|LevelName|LogLevel::* $value + */ + public function setHighPriorityLevel($value): self + { + $this->highPriorityLevel = Logger::toMonologLevel($value); + + return $this; + } + + /** + * @param int|string $value + * + * @phpstan-param Level|LevelName|LogLevel::* $value + */ + public function setEmergencyLevel($value): self + { + $this->emergencyLevel = Logger::toMonologLevel($value); + + return $this; + } + + /** + * Use the formatted message? + */ + public function useFormattedMessage(bool $value): self + { + $this->useFormattedMessage = $value; + + return $this; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php new file mode 100644 index 0000000..938eee6 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Formatter\FormatterInterface; +use Monolog\Logger; + +/** + * Logs to a Redis key using rpush + * + * usage example: + * + * $log = new Logger('application'); + * $redis = new RedisHandler(new Predis\Client("tcp://localhost:6379"), "logs", "prod"); + * $log->pushHandler($redis); + * + * @author Thomas Tourlourat + * + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler + */ +class RedisHandler extends AbstractProcessingHandler +{ + /** @var \Predis\Client|\Redis */ + private $redisClient; + /** @var string */ + private $redisKey; + /** @var int */ + protected $capSize; + + /** + * @param \Predis\Client|\Redis $redis The redis instance + * @param string $key The key name to push records to + * @param int $capSize Number of entries to limit list size to, 0 = unlimited + */ + public function __construct($redis, string $key, $level = Logger::DEBUG, bool $bubble = true, int $capSize = 0) + { + if (!(($redis instanceof \Predis\Client) || ($redis instanceof \Redis))) { + throw new \InvalidArgumentException('Predis\Client or Redis instance required'); + } + + $this->redisClient = $redis; + $this->redisKey = $key; + $this->capSize = $capSize; + + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + if ($this->capSize) { + $this->writeCapped($record); + } else { + $this->redisClient->rpush($this->redisKey, $record["formatted"]); + } + } + + /** + * Write and cap the collection + * Writes the record to the redis list and caps its + * + * @phpstan-param FormattedRecord $record + */ + protected function writeCapped(array $record): void + { + if ($this->redisClient instanceof \Redis) { + $mode = defined('\Redis::MULTI') ? \Redis::MULTI : 1; + $this->redisClient->multi($mode) + ->rpush($this->redisKey, $record["formatted"]) + ->ltrim($this->redisKey, -$this->capSize, -1) + ->exec(); + } else { + $redisKey = $this->redisKey; + $capSize = $this->capSize; + $this->redisClient->transaction(function ($tx) use ($record, $redisKey, $capSize) { + $tx->rpush($redisKey, $record["formatted"]); + $tx->ltrim($redisKey, -$capSize, -1); + }); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new LineFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RedisPubSubHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RedisPubSubHandler.php new file mode 100644 index 0000000..f9fede8 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/RedisPubSubHandler.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Formatter\FormatterInterface; +use Monolog\Logger; + +/** + * Sends the message to a Redis Pub/Sub channel using PUBLISH + * + * usage example: + * + * $log = new Logger('application'); + * $redis = new RedisPubSubHandler(new Predis\Client("tcp://localhost:6379"), "logs", Logger::WARNING); + * $log->pushHandler($redis); + * + * @author Gaëtan Faugère + */ +class RedisPubSubHandler extends AbstractProcessingHandler +{ + /** @var \Predis\Client|\Redis */ + private $redisClient; + /** @var string */ + private $channelKey; + + /** + * @param \Predis\Client|\Redis $redis The redis instance + * @param string $key The channel key to publish records to + */ + public function __construct($redis, string $key, $level = Logger::DEBUG, bool $bubble = true) + { + if (!(($redis instanceof \Predis\Client) || ($redis instanceof \Redis))) { + throw new \InvalidArgumentException('Predis\Client or Redis instance required'); + } + + $this->redisClient = $redis; + $this->channelKey = $key; + + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + $this->redisClient->publish($this->channelKey, $record["formatted"]); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new LineFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php new file mode 100644 index 0000000..adcc939 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php @@ -0,0 +1,131 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Rollbar\RollbarLogger; +use Throwable; +use Monolog\Logger; + +/** + * Sends errors to Rollbar + * + * If the context data contains a `payload` key, that is used as an array + * of payload options to RollbarLogger's log method. + * + * Rollbar's context info will contain the context + extra keys from the log record + * merged, and then on top of that a few keys: + * + * - level (rollbar level name) + * - monolog_level (monolog level name, raw level, as rollbar only has 5 but monolog 8) + * - channel + * - datetime (unix timestamp) + * + * @author Paul Statezny + */ +class RollbarHandler extends AbstractProcessingHandler +{ + /** + * @var RollbarLogger + */ + protected $rollbarLogger; + + /** @var string[] */ + protected $levelMap = [ + Logger::DEBUG => 'debug', + Logger::INFO => 'info', + Logger::NOTICE => 'info', + Logger::WARNING => 'warning', + Logger::ERROR => 'error', + Logger::CRITICAL => 'critical', + Logger::ALERT => 'critical', + Logger::EMERGENCY => 'critical', + ]; + + /** + * Records whether any log records have been added since the last flush of the rollbar notifier + * + * @var bool + */ + private $hasRecords = false; + + /** @var bool */ + protected $initialized = false; + + /** + * @param RollbarLogger $rollbarLogger RollbarLogger object constructed with valid token + */ + public function __construct(RollbarLogger $rollbarLogger, $level = Logger::ERROR, bool $bubble = true) + { + $this->rollbarLogger = $rollbarLogger; + + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + if (!$this->initialized) { + // __destructor() doesn't get called on Fatal errors + register_shutdown_function(array($this, 'close')); + $this->initialized = true; + } + + $context = $record['context']; + $context = array_merge($context, $record['extra'], [ + 'level' => $this->levelMap[$record['level']], + 'monolog_level' => $record['level_name'], + 'channel' => $record['channel'], + 'datetime' => $record['datetime']->format('U'), + ]); + + if (isset($context['exception']) && $context['exception'] instanceof Throwable) { + $exception = $context['exception']; + unset($context['exception']); + $toLog = $exception; + } else { + $toLog = $record['message']; + } + + // @phpstan-ignore-next-line + $this->rollbarLogger->log($context['level'], $toLog, $context); + + $this->hasRecords = true; + } + + public function flush(): void + { + if ($this->hasRecords) { + $this->rollbarLogger->flush(); + $this->hasRecords = false; + } + } + + /** + * {@inheritDoc} + */ + public function close(): void + { + $this->flush(); + } + + /** + * {@inheritDoc} + */ + public function reset() + { + $this->flush(); + + parent::reset(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php new file mode 100644 index 0000000..2b7c480 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php @@ -0,0 +1,203 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use InvalidArgumentException; +use Monolog\Logger; +use Monolog\Utils; + +/** + * Stores logs to files that are rotated every day and a limited number of files are kept. + * + * This rotation is only intended to be used as a workaround. Using logrotate to + * handle the rotation is strongly encouraged when you can use it. + * + * @author Christophe Coevoet + * @author Jordi Boggiano + */ +class RotatingFileHandler extends StreamHandler +{ + public const FILE_PER_DAY = 'Y-m-d'; + public const FILE_PER_MONTH = 'Y-m'; + public const FILE_PER_YEAR = 'Y'; + + /** @var string */ + protected $filename; + /** @var int */ + protected $maxFiles; + /** @var bool */ + protected $mustRotate; + /** @var \DateTimeImmutable */ + protected $nextRotation; + /** @var string */ + protected $filenameFormat; + /** @var string */ + protected $dateFormat; + + /** + * @param string $filename + * @param int $maxFiles The maximal amount of files to keep (0 means unlimited) + * @param int|null $filePermission Optional file permissions (default (0644) are only for owner read/write) + * @param bool $useLocking Try to lock log file before doing any writes + */ + public function __construct(string $filename, int $maxFiles = 0, $level = Logger::DEBUG, bool $bubble = true, ?int $filePermission = null, bool $useLocking = false) + { + $this->filename = Utils::canonicalizePath($filename); + $this->maxFiles = $maxFiles; + $this->nextRotation = new \DateTimeImmutable('tomorrow'); + $this->filenameFormat = '{filename}-{date}'; + $this->dateFormat = static::FILE_PER_DAY; + + parent::__construct($this->getTimedFilename(), $level, $bubble, $filePermission, $useLocking); + } + + /** + * {@inheritDoc} + */ + public function close(): void + { + parent::close(); + + if (true === $this->mustRotate) { + $this->rotate(); + } + } + + /** + * {@inheritDoc} + */ + public function reset() + { + parent::reset(); + + if (true === $this->mustRotate) { + $this->rotate(); + } + } + + public function setFilenameFormat(string $filenameFormat, string $dateFormat): self + { + if (!preg_match('{^[Yy](([/_.-]?m)([/_.-]?d)?)?$}', $dateFormat)) { + throw new InvalidArgumentException( + 'Invalid date format - format must be one of '. + 'RotatingFileHandler::FILE_PER_DAY ("Y-m-d"), RotatingFileHandler::FILE_PER_MONTH ("Y-m") '. + 'or RotatingFileHandler::FILE_PER_YEAR ("Y"), or you can set one of the '. + 'date formats using slashes, underscores and/or dots instead of dashes.' + ); + } + if (substr_count($filenameFormat, '{date}') === 0) { + throw new InvalidArgumentException( + 'Invalid filename format - format must contain at least `{date}`, because otherwise rotating is impossible.' + ); + } + $this->filenameFormat = $filenameFormat; + $this->dateFormat = $dateFormat; + $this->url = $this->getTimedFilename(); + $this->close(); + + return $this; + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + // on the first record written, if the log is new, we should rotate (once per day) + if (null === $this->mustRotate) { + $this->mustRotate = null === $this->url || !file_exists($this->url); + } + + if ($this->nextRotation <= $record['datetime']) { + $this->mustRotate = true; + $this->close(); + } + + parent::write($record); + } + + /** + * Rotates the files. + */ + protected function rotate(): void + { + // update filename + $this->url = $this->getTimedFilename(); + $this->nextRotation = new \DateTimeImmutable('tomorrow'); + + // skip GC of old logs if files are unlimited + if (0 === $this->maxFiles) { + return; + } + + $logFiles = glob($this->getGlobPattern()); + if (false === $logFiles) { + // failed to glob + return; + } + + if ($this->maxFiles >= count($logFiles)) { + // no files to remove + return; + } + + // Sorting the files by name to remove the older ones + usort($logFiles, function ($a, $b) { + return strcmp($b, $a); + }); + + foreach (array_slice($logFiles, $this->maxFiles) as $file) { + if (is_writable($file)) { + // suppress errors here as unlink() might fail if two processes + // are cleaning up/rotating at the same time + set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline): bool { + return false; + }); + unlink($file); + restore_error_handler(); + } + } + + $this->mustRotate = false; + } + + protected function getTimedFilename(): string + { + $fileInfo = pathinfo($this->filename); + $timedFilename = str_replace( + ['{filename}', '{date}'], + [$fileInfo['filename'], date($this->dateFormat)], + $fileInfo['dirname'] . '/' . $this->filenameFormat + ); + + if (isset($fileInfo['extension'])) { + $timedFilename .= '.'.$fileInfo['extension']; + } + + return $timedFilename; + } + + protected function getGlobPattern(): string + { + $fileInfo = pathinfo($this->filename); + $glob = str_replace( + ['{filename}', '{date}'], + [$fileInfo['filename'], '[0-9][0-9][0-9][0-9]*'], + $fileInfo['dirname'] . '/' . $this->filenameFormat + ); + if (isset($fileInfo['extension'])) { + $glob .= '.'.$fileInfo['extension']; + } + + return $glob; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.php new file mode 100644 index 0000000..c128a32 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.php @@ -0,0 +1,132 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; + +/** + * Sampling handler + * + * A sampled event stream can be useful for logging high frequency events in + * a production environment where you only need an idea of what is happening + * and are not concerned with capturing every occurrence. Since the decision to + * handle or not handle a particular event is determined randomly, the + * resulting sampled log is not guaranteed to contain 1/N of the events that + * occurred in the application, but based on the Law of large numbers, it will + * tend to be close to this ratio with a large number of attempts. + * + * @author Bryan Davis + * @author Kunal Mehta + * + * @phpstan-import-type Record from \Monolog\Logger + * @phpstan-import-type Level from \Monolog\Logger + */ +class SamplingHandler extends AbstractHandler implements ProcessableHandlerInterface, FormattableHandlerInterface +{ + use ProcessableHandlerTrait; + + /** + * @var HandlerInterface|callable + * @phpstan-var HandlerInterface|callable(Record|array{level: Level}|null, HandlerInterface): HandlerInterface + */ + protected $handler; + + /** + * @var int $factor + */ + protected $factor; + + /** + * @psalm-param HandlerInterface|callable(Record|array{level: Level}|null, HandlerInterface): HandlerInterface $handler + * + * @param callable|HandlerInterface $handler Handler or factory callable($record|null, $samplingHandler). + * @param int $factor Sample factor (e.g. 10 means every ~10th record is sampled) + */ + public function __construct($handler, int $factor) + { + parent::__construct(); + $this->handler = $handler; + $this->factor = $factor; + + if (!$this->handler instanceof HandlerInterface && !is_callable($this->handler)) { + throw new \RuntimeException("The given handler (".json_encode($this->handler).") is not a callable nor a Monolog\Handler\HandlerInterface object"); + } + } + + public function isHandling(array $record): bool + { + return $this->getHandler($record)->isHandling($record); + } + + public function handle(array $record): bool + { + if ($this->isHandling($record) && mt_rand(1, $this->factor) === 1) { + if ($this->processors) { + /** @var Record $record */ + $record = $this->processRecord($record); + } + + $this->getHandler($record)->handle($record); + } + + return false === $this->bubble; + } + + /** + * Return the nested handler + * + * If the handler was provided as a factory callable, this will trigger the handler's instantiation. + * + * @phpstan-param Record|array{level: Level}|null $record + * + * @return HandlerInterface + */ + public function getHandler(array $record = null) + { + if (!$this->handler instanceof HandlerInterface) { + $this->handler = ($this->handler)($record, $this); + if (!$this->handler instanceof HandlerInterface) { + throw new \RuntimeException("The factory callable should return a HandlerInterface"); + } + } + + return $this->handler; + } + + /** + * {@inheritDoc} + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + $handler = $this->getHandler(); + if ($handler instanceof FormattableHandlerInterface) { + $handler->setFormatter($formatter); + + return $this; + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($handler).' does not support formatters.'); + } + + /** + * {@inheritDoc} + */ + public function getFormatter(): FormatterInterface + { + $handler = $this->getHandler(); + if ($handler instanceof FormattableHandlerInterface) { + return $handler->getFormatter(); + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($handler).' does not support formatters.'); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SendGridHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SendGridHandler.php new file mode 100644 index 0000000..1280ee7 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SendGridHandler.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * SendGridrHandler uses the SendGrid API v2 function to send Log emails, more information in https://sendgrid.com/docs/API_Reference/Web_API/mail.html + * + * @author Ricardo Fontanelli + */ +class SendGridHandler extends MailHandler +{ + /** + * The SendGrid API User + * @var string + */ + protected $apiUser; + + /** + * The SendGrid API Key + * @var string + */ + protected $apiKey; + + /** + * The email addresses to which the message will be sent + * @var string + */ + protected $from; + + /** + * The email addresses to which the message will be sent + * @var string[] + */ + protected $to; + + /** + * The subject of the email + * @var string + */ + protected $subject; + + /** + * @param string $apiUser The SendGrid API User + * @param string $apiKey The SendGrid API Key + * @param string $from The sender of the email + * @param string|string[] $to The recipients of the email + * @param string $subject The subject of the mail + */ + public function __construct(string $apiUser, string $apiKey, string $from, $to, string $subject, $level = Logger::ERROR, bool $bubble = true) + { + if (!extension_loaded('curl')) { + throw new MissingExtensionException('The curl extension is needed to use the SendGridHandler'); + } + + parent::__construct($level, $bubble); + $this->apiUser = $apiUser; + $this->apiKey = $apiKey; + $this->from = $from; + $this->to = (array) $to; + $this->subject = $subject; + } + + /** + * {@inheritDoc} + */ + protected function send(string $content, array $records): void + { + $message = []; + $message['api_user'] = $this->apiUser; + $message['api_key'] = $this->apiKey; + $message['from'] = $this->from; + foreach ($this->to as $recipient) { + $message['to[]'] = $recipient; + } + $message['subject'] = $this->subject; + $message['date'] = date('r'); + + if ($this->isHtmlBody($content)) { + $message['html'] = $content; + } else { + $message['text'] = $content; + } + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, 'https://api.sendgrid.com/api/mail.send.json'); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($message)); + Curl\Util::execute($ch, 2); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php b/vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php new file mode 100644 index 0000000..13c3a10 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php @@ -0,0 +1,385 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\Slack; + +use Monolog\Logger; +use Monolog\Utils; +use Monolog\Formatter\NormalizerFormatter; +use Monolog\Formatter\FormatterInterface; + +/** + * Slack record utility helping to log to Slack webhooks or API. + * + * @author Greg Kedzierski + * @author Haralan Dobrev + * @see https://api.slack.com/incoming-webhooks + * @see https://api.slack.com/docs/message-attachments + * + * @phpstan-import-type FormattedRecord from \Monolog\Handler\AbstractProcessingHandler + * @phpstan-import-type Record from \Monolog\Logger + */ +class SlackRecord +{ + public const COLOR_DANGER = 'danger'; + + public const COLOR_WARNING = 'warning'; + + public const COLOR_GOOD = 'good'; + + public const COLOR_DEFAULT = '#e3e4e6'; + + /** + * Slack channel (encoded ID or name) + * @var string|null + */ + private $channel; + + /** + * Name of a bot + * @var string|null + */ + private $username; + + /** + * User icon e.g. 'ghost', 'http://example.com/user.png' + * @var string|null + */ + private $userIcon; + + /** + * Whether the message should be added to Slack as attachment (plain text otherwise) + * @var bool + */ + private $useAttachment; + + /** + * Whether the the context/extra messages added to Slack as attachments are in a short style + * @var bool + */ + private $useShortAttachment; + + /** + * Whether the attachment should include context and extra data + * @var bool + */ + private $includeContextAndExtra; + + /** + * Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] + * @var string[] + */ + private $excludeFields; + + /** + * @var ?FormatterInterface + */ + private $formatter; + + /** + * @var NormalizerFormatter + */ + private $normalizerFormatter; + + /** + * @param string[] $excludeFields + */ + public function __construct( + ?string $channel = null, + ?string $username = null, + bool $useAttachment = true, + ?string $userIcon = null, + bool $useShortAttachment = false, + bool $includeContextAndExtra = false, + array $excludeFields = array(), + FormatterInterface $formatter = null + ) { + $this + ->setChannel($channel) + ->setUsername($username) + ->useAttachment($useAttachment) + ->setUserIcon($userIcon) + ->useShortAttachment($useShortAttachment) + ->includeContextAndExtra($includeContextAndExtra) + ->excludeFields($excludeFields) + ->setFormatter($formatter); + + if ($this->includeContextAndExtra) { + $this->normalizerFormatter = new NormalizerFormatter(); + } + } + + /** + * Returns required data in format that Slack + * is expecting. + * + * @phpstan-param FormattedRecord $record + * @phpstan-return mixed[] + */ + public function getSlackData(array $record): array + { + $dataArray = array(); + $record = $this->removeExcludedFields($record); + + if ($this->username) { + $dataArray['username'] = $this->username; + } + + if ($this->channel) { + $dataArray['channel'] = $this->channel; + } + + if ($this->formatter && !$this->useAttachment) { + /** @phpstan-ignore-next-line */ + $message = $this->formatter->format($record); + } else { + $message = $record['message']; + } + + if ($this->useAttachment) { + $attachment = array( + 'fallback' => $message, + 'text' => $message, + 'color' => $this->getAttachmentColor($record['level']), + 'fields' => array(), + 'mrkdwn_in' => array('fields'), + 'ts' => $record['datetime']->getTimestamp(), + ); + + if ($this->useShortAttachment) { + $attachment['title'] = $record['level_name']; + } else { + $attachment['title'] = 'Message'; + $attachment['fields'][] = $this->generateAttachmentField('Level', $record['level_name']); + } + + if ($this->includeContextAndExtra) { + foreach (array('extra', 'context') as $key) { + if (empty($record[$key])) { + continue; + } + + if ($this->useShortAttachment) { + $attachment['fields'][] = $this->generateAttachmentField( + (string) $key, + $record[$key] + ); + } else { + // Add all extra fields as individual fields in attachment + $attachment['fields'] = array_merge( + $attachment['fields'], + $this->generateAttachmentFields($record[$key]) + ); + } + } + } + + $dataArray['attachments'] = array($attachment); + } else { + $dataArray['text'] = $message; + } + + if ($this->userIcon) { + if (filter_var($this->userIcon, FILTER_VALIDATE_URL)) { + $dataArray['icon_url'] = $this->userIcon; + } else { + $dataArray['icon_emoji'] = ":{$this->userIcon}:"; + } + } + + return $dataArray; + } + + /** + * Returns a Slack message attachment color associated with + * provided level. + */ + public function getAttachmentColor(int $level): string + { + switch (true) { + case $level >= Logger::ERROR: + return static::COLOR_DANGER; + case $level >= Logger::WARNING: + return static::COLOR_WARNING; + case $level >= Logger::INFO: + return static::COLOR_GOOD; + default: + return static::COLOR_DEFAULT; + } + } + + /** + * Stringifies an array of key/value pairs to be used in attachment fields + * + * @param mixed[] $fields + */ + public function stringify(array $fields): string + { + /** @var Record $fields */ + $normalized = $this->normalizerFormatter->format($fields); + + $hasSecondDimension = count(array_filter($normalized, 'is_array')); + $hasNonNumericKeys = !count(array_filter(array_keys($normalized), 'is_numeric')); + + return $hasSecondDimension || $hasNonNumericKeys + ? Utils::jsonEncode($normalized, JSON_PRETTY_PRINT|Utils::DEFAULT_JSON_FLAGS) + : Utils::jsonEncode($normalized, Utils::DEFAULT_JSON_FLAGS); + } + + /** + * Channel used by the bot when posting + * + * @param ?string $channel + * + * @return static + */ + public function setChannel(?string $channel = null): self + { + $this->channel = $channel; + + return $this; + } + + /** + * Username used by the bot when posting + * + * @param ?string $username + * + * @return static + */ + public function setUsername(?string $username = null): self + { + $this->username = $username; + + return $this; + } + + public function useAttachment(bool $useAttachment = true): self + { + $this->useAttachment = $useAttachment; + + return $this; + } + + public function setUserIcon(?string $userIcon = null): self + { + $this->userIcon = $userIcon; + + if (\is_string($userIcon)) { + $this->userIcon = trim($userIcon, ':'); + } + + return $this; + } + + public function useShortAttachment(bool $useShortAttachment = false): self + { + $this->useShortAttachment = $useShortAttachment; + + return $this; + } + + public function includeContextAndExtra(bool $includeContextAndExtra = false): self + { + $this->includeContextAndExtra = $includeContextAndExtra; + + if ($this->includeContextAndExtra) { + $this->normalizerFormatter = new NormalizerFormatter(); + } + + return $this; + } + + /** + * @param string[] $excludeFields + */ + public function excludeFields(array $excludeFields = []): self + { + $this->excludeFields = $excludeFields; + + return $this; + } + + public function setFormatter(?FormatterInterface $formatter = null): self + { + $this->formatter = $formatter; + + return $this; + } + + /** + * Generates attachment field + * + * @param string|mixed[] $value + * + * @return array{title: string, value: string, short: false} + */ + private function generateAttachmentField(string $title, $value): array + { + $value = is_array($value) + ? sprintf('```%s```', substr($this->stringify($value), 0, 1990)) + : $value; + + return array( + 'title' => ucfirst($title), + 'value' => $value, + 'short' => false, + ); + } + + /** + * Generates a collection of attachment fields from array + * + * @param mixed[] $data + * + * @return array + */ + private function generateAttachmentFields(array $data): array + { + /** @var Record $data */ + $normalized = $this->normalizerFormatter->format($data); + + $fields = array(); + foreach ($normalized as $key => $value) { + $fields[] = $this->generateAttachmentField((string) $key, $value); + } + + return $fields; + } + + /** + * Get a copy of record with fields excluded according to $this->excludeFields + * + * @phpstan-param FormattedRecord $record + * + * @return mixed[] + */ + private function removeExcludedFields(array $record): array + { + foreach ($this->excludeFields as $field) { + $keys = explode('.', $field); + $node = &$record; + $lastKey = end($keys); + foreach ($keys as $key) { + if (!isset($node[$key])) { + break; + } + if ($lastKey === $key) { + unset($node[$key]); + break; + } + $node = &$node[$key]; + } + } + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php new file mode 100644 index 0000000..46d69a6 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php @@ -0,0 +1,242 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Logger; +use Monolog\Utils; +use Monolog\Handler\Slack\SlackRecord; + +/** + * Sends notifications through Slack API + * + * @author Greg Kedzierski + * @see https://api.slack.com/ + * + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler + */ +class SlackHandler extends SocketHandler +{ + /** + * Slack API token + * @var string + */ + private $token; + + /** + * Instance of the SlackRecord util class preparing data for Slack API. + * @var SlackRecord + */ + private $slackRecord; + + /** + * @param string $token Slack API token + * @param string $channel Slack channel (encoded ID or name) + * @param string|null $username Name of a bot + * @param bool $useAttachment Whether the message should be added to Slack as attachment (plain text otherwise) + * @param string|null $iconEmoji The emoji name to use (or null) + * @param bool $useShortAttachment Whether the context/extra messages added to Slack as attachments are in a short style + * @param bool $includeContextAndExtra Whether the attachment should include context and extra data + * @param string[] $excludeFields Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] + * @throws MissingExtensionException If no OpenSSL PHP extension configured + */ + public function __construct( + string $token, + string $channel, + ?string $username = null, + bool $useAttachment = true, + ?string $iconEmoji = null, + $level = Logger::CRITICAL, + bool $bubble = true, + bool $useShortAttachment = false, + bool $includeContextAndExtra = false, + array $excludeFields = array() + ) { + if (!extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP extension is required to use the SlackHandler'); + } + + parent::__construct('ssl://slack.com:443', $level, $bubble); + + $this->slackRecord = new SlackRecord( + $channel, + $username, + $useAttachment, + $iconEmoji, + $useShortAttachment, + $includeContextAndExtra, + $excludeFields + ); + + $this->token = $token; + } + + public function getSlackRecord(): SlackRecord + { + return $this->slackRecord; + } + + public function getToken(): string + { + return $this->token; + } + + /** + * {@inheritDoc} + */ + protected function generateDataStream(array $record): string + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + /** + * Builds the body of API call + * + * @phpstan-param FormattedRecord $record + */ + private function buildContent(array $record): string + { + $dataArray = $this->prepareContentData($record); + + return http_build_query($dataArray); + } + + /** + * @phpstan-param FormattedRecord $record + * @return string[] + */ + protected function prepareContentData(array $record): array + { + $dataArray = $this->slackRecord->getSlackData($record); + $dataArray['token'] = $this->token; + + if (!empty($dataArray['attachments'])) { + $dataArray['attachments'] = Utils::jsonEncode($dataArray['attachments']); + } + + return $dataArray; + } + + /** + * Builds the header of the API Call + */ + private function buildHeader(string $content): string + { + $header = "POST /api/chat.postMessage HTTP/1.1\r\n"; + $header .= "Host: slack.com\r\n"; + $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; + $header .= "Content-Length: " . strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + parent::write($record); + $this->finalizeWrite(); + } + + /** + * Finalizes the request by reading some bytes and then closing the socket + * + * If we do not read some but close the socket too early, slack sometimes + * drops the request entirely. + */ + protected function finalizeWrite(): void + { + $res = $this->getResource(); + if (is_resource($res)) { + @fread($res, 2048); + } + $this->closeSocket(); + } + + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + parent::setFormatter($formatter); + $this->slackRecord->setFormatter($formatter); + + return $this; + } + + public function getFormatter(): FormatterInterface + { + $formatter = parent::getFormatter(); + $this->slackRecord->setFormatter($formatter); + + return $formatter; + } + + /** + * Channel used by the bot when posting + */ + public function setChannel(string $channel): self + { + $this->slackRecord->setChannel($channel); + + return $this; + } + + /** + * Username used by the bot when posting + */ + public function setUsername(string $username): self + { + $this->slackRecord->setUsername($username); + + return $this; + } + + public function useAttachment(bool $useAttachment): self + { + $this->slackRecord->useAttachment($useAttachment); + + return $this; + } + + public function setIconEmoji(string $iconEmoji): self + { + $this->slackRecord->setUserIcon($iconEmoji); + + return $this; + } + + public function useShortAttachment(bool $useShortAttachment): self + { + $this->slackRecord->useShortAttachment($useShortAttachment); + + return $this; + } + + public function includeContextAndExtra(bool $includeContextAndExtra): self + { + $this->slackRecord->includeContextAndExtra($includeContextAndExtra); + + return $this; + } + + /** + * @param string[] $excludeFields + */ + public function excludeFields(array $excludeFields): self + { + $this->slackRecord->excludeFields($excludeFields); + + return $this; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php new file mode 100644 index 0000000..8ae3c78 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php @@ -0,0 +1,130 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Logger; +use Monolog\Utils; +use Monolog\Handler\Slack\SlackRecord; + +/** + * Sends notifications through Slack Webhooks + * + * @author Haralan Dobrev + * @see https://api.slack.com/incoming-webhooks + */ +class SlackWebhookHandler extends AbstractProcessingHandler +{ + /** + * Slack Webhook token + * @var string + */ + private $webhookUrl; + + /** + * Instance of the SlackRecord util class preparing data for Slack API. + * @var SlackRecord + */ + private $slackRecord; + + /** + * @param string $webhookUrl Slack Webhook URL + * @param string|null $channel Slack channel (encoded ID or name) + * @param string|null $username Name of a bot + * @param bool $useAttachment Whether the message should be added to Slack as attachment (plain text otherwise) + * @param string|null $iconEmoji The emoji name to use (or null) + * @param bool $useShortAttachment Whether the the context/extra messages added to Slack as attachments are in a short style + * @param bool $includeContextAndExtra Whether the attachment should include context and extra data + * @param string[] $excludeFields Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] + */ + public function __construct( + string $webhookUrl, + ?string $channel = null, + ?string $username = null, + bool $useAttachment = true, + ?string $iconEmoji = null, + bool $useShortAttachment = false, + bool $includeContextAndExtra = false, + $level = Logger::CRITICAL, + bool $bubble = true, + array $excludeFields = array() + ) { + if (!extension_loaded('curl')) { + throw new MissingExtensionException('The curl extension is needed to use the SlackWebhookHandler'); + } + + parent::__construct($level, $bubble); + + $this->webhookUrl = $webhookUrl; + + $this->slackRecord = new SlackRecord( + $channel, + $username, + $useAttachment, + $iconEmoji, + $useShortAttachment, + $includeContextAndExtra, + $excludeFields + ); + } + + public function getSlackRecord(): SlackRecord + { + return $this->slackRecord; + } + + public function getWebhookUrl(): string + { + return $this->webhookUrl; + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + $postData = $this->slackRecord->getSlackData($record); + $postString = Utils::jsonEncode($postData); + + $ch = curl_init(); + $options = array( + CURLOPT_URL => $this->webhookUrl, + CURLOPT_POST => true, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => array('Content-type: application/json'), + CURLOPT_POSTFIELDS => $postString, + ); + if (defined('CURLOPT_SAFE_UPLOAD')) { + $options[CURLOPT_SAFE_UPLOAD] = true; + } + + curl_setopt_array($ch, $options); + + Curl\Util::execute($ch); + } + + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + parent::setFormatter($formatter); + $this->slackRecord->setFormatter($formatter); + + return $this; + } + + public function getFormatter(): FormatterInterface + { + $formatter = parent::getFormatter(); + $this->slackRecord->setFormatter($formatter); + + return $formatter; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php new file mode 100644 index 0000000..4e1410a --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php @@ -0,0 +1,421 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Stores to any socket - uses fsockopen() or pfsockopen(). + * + * @author Pablo de Leon Belloc + * @see http://php.net/manual/en/function.fsockopen.php + * + * @phpstan-import-type Record from \Monolog\Logger + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler + */ +class SocketHandler extends AbstractProcessingHandler +{ + /** @var string */ + private $connectionString; + /** @var float */ + private $connectionTimeout; + /** @var resource|null */ + private $resource; + /** @var float */ + private $timeout = 0.0; + /** @var float */ + private $writingTimeout = 10.0; + /** @var ?int */ + private $lastSentBytes = null; + /** @var ?int */ + private $chunkSize = null; + /** @var bool */ + private $persistent = false; + /** @var ?int */ + private $errno = null; + /** @var ?string */ + private $errstr = null; + /** @var ?float */ + private $lastWritingAt = null; + + /** + * @param string $connectionString Socket connection string + */ + public function __construct(string $connectionString, $level = Logger::DEBUG, bool $bubble = true) + { + parent::__construct($level, $bubble); + $this->connectionString = $connectionString; + $this->connectionTimeout = (float) ini_get('default_socket_timeout'); + } + + /** + * Connect (if necessary) and write to the socket + * + * {@inheritDoc} + * + * @throws \UnexpectedValueException + * @throws \RuntimeException + */ + protected function write(array $record): void + { + $this->connectIfNotConnected(); + $data = $this->generateDataStream($record); + $this->writeToSocket($data); + } + + /** + * We will not close a PersistentSocket instance so it can be reused in other requests. + */ + public function close(): void + { + if (!$this->isPersistent()) { + $this->closeSocket(); + } + } + + /** + * Close socket, if open + */ + public function closeSocket(): void + { + if (is_resource($this->resource)) { + fclose($this->resource); + $this->resource = null; + } + } + + /** + * Set socket connection to be persistent. It only has effect before the connection is initiated. + */ + public function setPersistent(bool $persistent): self + { + $this->persistent = $persistent; + + return $this; + } + + /** + * Set connection timeout. Only has effect before we connect. + * + * @see http://php.net/manual/en/function.fsockopen.php + */ + public function setConnectionTimeout(float $seconds): self + { + $this->validateTimeout($seconds); + $this->connectionTimeout = $seconds; + + return $this; + } + + /** + * Set write timeout. Only has effect before we connect. + * + * @see http://php.net/manual/en/function.stream-set-timeout.php + */ + public function setTimeout(float $seconds): self + { + $this->validateTimeout($seconds); + $this->timeout = $seconds; + + return $this; + } + + /** + * Set writing timeout. Only has effect during connection in the writing cycle. + * + * @param float $seconds 0 for no timeout + */ + public function setWritingTimeout(float $seconds): self + { + $this->validateTimeout($seconds); + $this->writingTimeout = $seconds; + + return $this; + } + + /** + * Set chunk size. Only has effect during connection in the writing cycle. + */ + public function setChunkSize(int $bytes): self + { + $this->chunkSize = $bytes; + + return $this; + } + + /** + * Get current connection string + */ + public function getConnectionString(): string + { + return $this->connectionString; + } + + /** + * Get persistent setting + */ + public function isPersistent(): bool + { + return $this->persistent; + } + + /** + * Get current connection timeout setting + */ + public function getConnectionTimeout(): float + { + return $this->connectionTimeout; + } + + /** + * Get current in-transfer timeout + */ + public function getTimeout(): float + { + return $this->timeout; + } + + /** + * Get current local writing timeout + * + * @return float + */ + public function getWritingTimeout(): float + { + return $this->writingTimeout; + } + + /** + * Get current chunk size + */ + public function getChunkSize(): ?int + { + return $this->chunkSize; + } + + /** + * Check to see if the socket is currently available. + * + * UDP might appear to be connected but might fail when writing. See http://php.net/fsockopen for details. + */ + public function isConnected(): bool + { + return is_resource($this->resource) + && !feof($this->resource); // on TCP - other party can close connection. + } + + /** + * Wrapper to allow mocking + * + * @return resource|false + */ + protected function pfsockopen() + { + return @pfsockopen($this->connectionString, -1, $this->errno, $this->errstr, $this->connectionTimeout); + } + + /** + * Wrapper to allow mocking + * + * @return resource|false + */ + protected function fsockopen() + { + return @fsockopen($this->connectionString, -1, $this->errno, $this->errstr, $this->connectionTimeout); + } + + /** + * Wrapper to allow mocking + * + * @see http://php.net/manual/en/function.stream-set-timeout.php + * + * @return bool + */ + protected function streamSetTimeout() + { + $seconds = floor($this->timeout); + $microseconds = round(($this->timeout - $seconds) * 1e6); + + if (!is_resource($this->resource)) { + throw new \LogicException('streamSetTimeout called but $this->resource is not a resource'); + } + + return stream_set_timeout($this->resource, (int) $seconds, (int) $microseconds); + } + + /** + * Wrapper to allow mocking + * + * @see http://php.net/manual/en/function.stream-set-chunk-size.php + * + * @return int|bool + */ + protected function streamSetChunkSize() + { + if (!is_resource($this->resource)) { + throw new \LogicException('streamSetChunkSize called but $this->resource is not a resource'); + } + + if (null === $this->chunkSize) { + throw new \LogicException('streamSetChunkSize called but $this->chunkSize is not set'); + } + + return stream_set_chunk_size($this->resource, $this->chunkSize); + } + + /** + * Wrapper to allow mocking + * + * @return int|bool + */ + protected function fwrite(string $data) + { + if (!is_resource($this->resource)) { + throw new \LogicException('fwrite called but $this->resource is not a resource'); + } + + return @fwrite($this->resource, $data); + } + + /** + * Wrapper to allow mocking + * + * @return mixed[]|bool + */ + protected function streamGetMetadata() + { + if (!is_resource($this->resource)) { + throw new \LogicException('streamGetMetadata called but $this->resource is not a resource'); + } + + return stream_get_meta_data($this->resource); + } + + private function validateTimeout(float $value): void + { + if ($value < 0) { + throw new \InvalidArgumentException("Timeout must be 0 or a positive float (got $value)"); + } + } + + private function connectIfNotConnected(): void + { + if ($this->isConnected()) { + return; + } + $this->connect(); + } + + /** + * @phpstan-param FormattedRecord $record + */ + protected function generateDataStream(array $record): string + { + return (string) $record['formatted']; + } + + /** + * @return resource|null + */ + protected function getResource() + { + return $this->resource; + } + + private function connect(): void + { + $this->createSocketResource(); + $this->setSocketTimeout(); + $this->setStreamChunkSize(); + } + + private function createSocketResource(): void + { + if ($this->isPersistent()) { + $resource = $this->pfsockopen(); + } else { + $resource = $this->fsockopen(); + } + if (is_bool($resource)) { + throw new \UnexpectedValueException("Failed connecting to $this->connectionString ($this->errno: $this->errstr)"); + } + $this->resource = $resource; + } + + private function setSocketTimeout(): void + { + if (!$this->streamSetTimeout()) { + throw new \UnexpectedValueException("Failed setting timeout with stream_set_timeout()"); + } + } + + private function setStreamChunkSize(): void + { + if ($this->chunkSize && !$this->streamSetChunkSize()) { + throw new \UnexpectedValueException("Failed setting chunk size with stream_set_chunk_size()"); + } + } + + private function writeToSocket(string $data): void + { + $length = strlen($data); + $sent = 0; + $this->lastSentBytes = $sent; + while ($this->isConnected() && $sent < $length) { + if (0 == $sent) { + $chunk = $this->fwrite($data); + } else { + $chunk = $this->fwrite(substr($data, $sent)); + } + if ($chunk === false) { + throw new \RuntimeException("Could not write to socket"); + } + $sent += $chunk; + $socketInfo = $this->streamGetMetadata(); + if (is_array($socketInfo) && $socketInfo['timed_out']) { + throw new \RuntimeException("Write timed-out"); + } + + if ($this->writingIsTimedOut($sent)) { + throw new \RuntimeException("Write timed-out, no data sent for `{$this->writingTimeout}` seconds, probably we got disconnected (sent $sent of $length)"); + } + } + if (!$this->isConnected() && $sent < $length) { + throw new \RuntimeException("End-of-file reached, probably we got disconnected (sent $sent of $length)"); + } + } + + private function writingIsTimedOut(int $sent): bool + { + // convert to ms + if (0.0 == $this->writingTimeout) { + return false; + } + + if ($sent !== $this->lastSentBytes) { + $this->lastWritingAt = microtime(true); + $this->lastSentBytes = $sent; + + return false; + } else { + usleep(100); + } + + if ((microtime(true) - $this->lastWritingAt) >= $this->writingTimeout) { + $this->closeSocket(); + + return true; + } + + return false; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SqsHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SqsHandler.php new file mode 100644 index 0000000..dc1dcb4 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SqsHandler.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Aws\Sqs\SqsClient; +use Monolog\Logger; +use Monolog\Utils; + +/** + * Writes to any sqs queue. + * + * @author Martijn van Calker + */ +class SqsHandler extends AbstractProcessingHandler +{ + /** 256 KB in bytes - maximum message size in SQS */ + protected const MAX_MESSAGE_SIZE = 262144; + /** 100 KB in bytes - head message size for new error log */ + protected const HEAD_MESSAGE_SIZE = 102400; + + /** @var SqsClient */ + private $client; + /** @var string */ + private $queueUrl; + + public function __construct(SqsClient $sqsClient, string $queueUrl, $level = Logger::DEBUG, bool $bubble = true) + { + parent::__construct($level, $bubble); + + $this->client = $sqsClient; + $this->queueUrl = $queueUrl; + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + if (!isset($record['formatted']) || 'string' !== gettype($record['formatted'])) { + throw new \InvalidArgumentException('SqsHandler accepts only formatted records as a string'); + } + + $messageBody = $record['formatted']; + if (strlen($messageBody) >= static::MAX_MESSAGE_SIZE) { + $messageBody = Utils::substr($messageBody, 0, static::HEAD_MESSAGE_SIZE); + } + + $this->client->sendMessage([ + 'QueueUrl' => $this->queueUrl, + 'MessageBody' => $messageBody, + ]); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php new file mode 100644 index 0000000..2531d41 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php @@ -0,0 +1,221 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Utils; + +/** + * Stores to any stream resource + * + * Can be used to store into php://stderr, remote and local files, etc. + * + * @author Jordi Boggiano + * + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler + */ +class StreamHandler extends AbstractProcessingHandler +{ + /** @const int */ + protected const MAX_CHUNK_SIZE = 2147483647; + /** @const int 10MB */ + protected const DEFAULT_CHUNK_SIZE = 10 * 1024 * 1024; + /** @var int */ + protected $streamChunkSize; + /** @var resource|null */ + protected $stream; + /** @var ?string */ + protected $url = null; + /** @var ?string */ + private $errorMessage = null; + /** @var ?int */ + protected $filePermission; + /** @var bool */ + protected $useLocking; + /** @var true|null */ + private $dirCreated = null; + + /** + * @param resource|string $stream If a missing path can't be created, an UnexpectedValueException will be thrown on first write + * @param int|null $filePermission Optional file permissions (default (0644) are only for owner read/write) + * @param bool $useLocking Try to lock log file before doing any writes + * + * @throws \InvalidArgumentException If stream is not a resource or string + */ + public function __construct($stream, $level = Logger::DEBUG, bool $bubble = true, ?int $filePermission = null, bool $useLocking = false) + { + parent::__construct($level, $bubble); + + if (($phpMemoryLimit = Utils::expandIniShorthandBytes(ini_get('memory_limit'))) !== false) { + if ($phpMemoryLimit > 0) { + // use max 10% of allowed memory for the chunk size, and at least 100KB + $this->streamChunkSize = min(static::MAX_CHUNK_SIZE, max((int) ($phpMemoryLimit / 10), 100 * 1024)); + } else { + // memory is unlimited, set to the default 10MB + $this->streamChunkSize = static::DEFAULT_CHUNK_SIZE; + } + } else { + // no memory limit information, set to the default 10MB + $this->streamChunkSize = static::DEFAULT_CHUNK_SIZE; + } + + if (is_resource($stream)) { + $this->stream = $stream; + + stream_set_chunk_size($this->stream, $this->streamChunkSize); + } elseif (is_string($stream)) { + $this->url = Utils::canonicalizePath($stream); + } else { + throw new \InvalidArgumentException('A stream must either be a resource or a string.'); + } + + $this->filePermission = $filePermission; + $this->useLocking = $useLocking; + } + + /** + * {@inheritDoc} + */ + public function close(): void + { + if ($this->url && is_resource($this->stream)) { + fclose($this->stream); + } + $this->stream = null; + $this->dirCreated = null; + } + + /** + * Return the currently active stream if it is open + * + * @return resource|null + */ + public function getStream() + { + return $this->stream; + } + + /** + * Return the stream URL if it was configured with a URL and not an active resource + * + * @return string|null + */ + public function getUrl(): ?string + { + return $this->url; + } + + /** + * @return int + */ + public function getStreamChunkSize(): int + { + return $this->streamChunkSize; + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + if (!is_resource($this->stream)) { + $url = $this->url; + if (null === $url || '' === $url) { + throw new \LogicException('Missing stream url, the stream can not be opened. This may be caused by a premature call to close().'); + } + $this->createDir($url); + $this->errorMessage = null; + set_error_handler([$this, 'customErrorHandler']); + $stream = fopen($url, 'a'); + if ($this->filePermission !== null) { + @chmod($url, $this->filePermission); + } + restore_error_handler(); + if (!is_resource($stream)) { + $this->stream = null; + + throw new \UnexpectedValueException(sprintf('The stream or file "%s" could not be opened in append mode: '.$this->errorMessage, $url)); + } + stream_set_chunk_size($stream, $this->streamChunkSize); + $this->stream = $stream; + } + + $stream = $this->stream; + if (!is_resource($stream)) { + throw new \LogicException('No stream was opened yet'); + } + + if ($this->useLocking) { + // ignoring errors here, there's not much we can do about them + flock($stream, LOCK_EX); + } + + $this->streamWrite($stream, $record); + + if ($this->useLocking) { + flock($stream, LOCK_UN); + } + } + + /** + * Write to stream + * @param resource $stream + * @param array $record + * + * @phpstan-param FormattedRecord $record + */ + protected function streamWrite($stream, array $record): void + { + fwrite($stream, (string) $record['formatted']); + } + + private function customErrorHandler(int $code, string $msg): bool + { + $this->errorMessage = preg_replace('{^(fopen|mkdir)\(.*?\): }', '', $msg); + + return true; + } + + private function getDirFromStream(string $stream): ?string + { + $pos = strpos($stream, '://'); + if ($pos === false) { + return dirname($stream); + } + + if ('file://' === substr($stream, 0, 7)) { + return dirname(substr($stream, 7)); + } + + return null; + } + + private function createDir(string $url): void + { + // Do not try to create dir if it has already been tried. + if ($this->dirCreated) { + return; + } + + $dir = $this->getDirFromStream($url); + if (null !== $dir && !is_dir($dir)) { + $this->errorMessage = null; + set_error_handler([$this, 'customErrorHandler']); + $status = mkdir($dir, 0777, true); + restore_error_handler(); + if (false === $status && !is_dir($dir)) { + throw new \UnexpectedValueException(sprintf('There is no existing directory at "%s" and it could not be created: '.$this->errorMessage, $dir)); + } + } + $this->dirCreated = true; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php new file mode 100644 index 0000000..b3fedea --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LineFormatter; +use Swift_Message; +use Swift; + +/** + * SwiftMailerHandler uses Swift_Mailer to send the emails + * + * @author Gyula Sallai + * + * @phpstan-import-type Record from \Monolog\Logger + */ +class SwiftMailerHandler extends MailHandler +{ + /** @var \Swift_Mailer */ + protected $mailer; + /** @var Swift_Message|callable(string, Record[]): Swift_Message */ + private $messageTemplate; + + /** + * @psalm-param Swift_Message|callable(string, Record[]): Swift_Message $message + * + * @param \Swift_Mailer $mailer The mailer to use + * @param callable|Swift_Message $message An example message for real messages, only the body will be replaced + */ + public function __construct(\Swift_Mailer $mailer, $message, $level = Logger::ERROR, bool $bubble = true) + { + parent::__construct($level, $bubble); + + $this->mailer = $mailer; + $this->messageTemplate = $message; + } + + /** + * {@inheritDoc} + */ + protected function send(string $content, array $records): void + { + $this->mailer->send($this->buildMessage($content, $records)); + } + + /** + * Gets the formatter for the Swift_Message subject. + * + * @param string|null $format The format of the subject + */ + protected function getSubjectFormatter(?string $format): FormatterInterface + { + return new LineFormatter($format); + } + + /** + * Creates instance of Swift_Message to be sent + * + * @param string $content formatted email body to be sent + * @param array $records Log records that formed the content + * @return Swift_Message + * + * @phpstan-param Record[] $records + */ + protected function buildMessage(string $content, array $records): Swift_Message + { + $message = null; + if ($this->messageTemplate instanceof Swift_Message) { + $message = clone $this->messageTemplate; + $message->generateId(); + } elseif (is_callable($this->messageTemplate)) { + $message = ($this->messageTemplate)($content, $records); + } + + if (!$message instanceof Swift_Message) { + throw new \InvalidArgumentException('Could not resolve message as instance of Swift_Message or a callable returning it'); + } + + if ($records) { + $subjectFormatter = $this->getSubjectFormatter($message->getSubject()); + $message->setSubject($subjectFormatter->format($this->getHighestRecord($records))); + } + + $mime = 'text/plain'; + if ($this->isHtmlBody($content)) { + $mime = 'text/html'; + } + + $message->setBody($content, $mime); + /** @phpstan-ignore-next-line */ + if (version_compare(Swift::VERSION, '6.0.0', '>=')) { + $message->setDate(new \DateTimeImmutable()); + } else { + /** @phpstan-ignore-next-line */ + $message->setDate(time()); + } + + return $message; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php new file mode 100644 index 0000000..4951f66 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Logs to syslog service. + * + * usage example: + * + * $log = new Logger('application'); + * $syslog = new SyslogHandler('myfacility', 'local6'); + * $formatter = new LineFormatter("%channel%.%level_name%: %message% %extra%"); + * $syslog->setFormatter($formatter); + * $log->pushHandler($syslog); + * + * @author Sven Paulus + */ +class SyslogHandler extends AbstractSyslogHandler +{ + /** @var string */ + protected $ident; + /** @var int */ + protected $logopts; + + /** + * @param string $ident + * @param string|int $facility Either one of the names of the keys in $this->facilities, or a LOG_* facility constant + * @param int $logopts Option flags for the openlog() call, defaults to LOG_PID + */ + public function __construct(string $ident, $facility = LOG_USER, $level = Logger::DEBUG, bool $bubble = true, int $logopts = LOG_PID) + { + parent::__construct($facility, $level, $bubble); + + $this->ident = $ident; + $this->logopts = $logopts; + } + + /** + * {@inheritDoc} + */ + public function close(): void + { + closelog(); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + if (!openlog($this->ident, $this->logopts, $this->facility)) { + throw new \LogicException('Can\'t open syslog for ident "'.$this->ident.'" and facility "'.$this->facility.'"'); + } + syslog($this->logLevels[$record['level']], (string) $record['formatted']); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php b/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php new file mode 100644 index 0000000..30b5186 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\SyslogUdp; + +use Monolog\Utils; +use Socket; + +class UdpSocket +{ + protected const DATAGRAM_MAX_LENGTH = 65023; + + /** @var string */ + protected $ip; + /** @var int */ + protected $port; + /** @var resource|Socket|null */ + protected $socket; + + public function __construct(string $ip, int $port = 514) + { + $this->ip = $ip; + $this->port = $port; + $domain = AF_INET; + $protocol = SOL_UDP; + // Check if we are using unix sockets. + if ($port === 0) { + $domain = AF_UNIX; + $protocol = IPPROTO_IP; + } + $this->socket = socket_create($domain, SOCK_DGRAM, $protocol) ?: null; + } + + /** + * @param string $line + * @param string $header + * @return void + */ + public function write($line, $header = "") + { + $this->send($this->assembleMessage($line, $header)); + } + + public function close(): void + { + if (is_resource($this->socket) || $this->socket instanceof Socket) { + socket_close($this->socket); + $this->socket = null; + } + } + + protected function send(string $chunk): void + { + if (!is_resource($this->socket) && !$this->socket instanceof Socket) { + throw new \RuntimeException('The UdpSocket to '.$this->ip.':'.$this->port.' has been closed and can not be written to anymore'); + } + socket_sendto($this->socket, $chunk, strlen($chunk), $flags = 0, $this->ip, $this->port); + } + + protected function assembleMessage(string $line, string $header): string + { + $chunkSize = static::DATAGRAM_MAX_LENGTH - strlen($header); + + return $header . Utils::substr($line, 0, $chunkSize); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php new file mode 100644 index 0000000..deaa19f --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php @@ -0,0 +1,150 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use DateTimeInterface; +use Monolog\Logger; +use Monolog\Handler\SyslogUdp\UdpSocket; +use Monolog\Utils; + +/** + * A Handler for logging to a remote syslogd server. + * + * @author Jesper Skovgaard Nielsen + * @author Dominik Kukacka + */ +class SyslogUdpHandler extends AbstractSyslogHandler +{ + const RFC3164 = 0; + const RFC5424 = 1; + const RFC5424e = 2; + + /** @var array */ + private $dateFormats = array( + self::RFC3164 => 'M d H:i:s', + self::RFC5424 => \DateTime::RFC3339, + self::RFC5424e => \DateTime::RFC3339_EXTENDED, + ); + + /** @var UdpSocket */ + protected $socket; + /** @var string */ + protected $ident; + /** @var self::RFC* */ + protected $rfc; + + /** + * @param string $host Either IP/hostname or a path to a unix socket (port must be 0 then) + * @param int $port Port number, or 0 if $host is a unix socket + * @param string|int $facility Either one of the names of the keys in $this->facilities, or a LOG_* facility constant + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param string $ident Program name or tag for each log message. + * @param int $rfc RFC to format the message for. + * @throws MissingExtensionException + * + * @phpstan-param self::RFC* $rfc + */ + public function __construct(string $host, int $port = 514, $facility = LOG_USER, $level = Logger::DEBUG, bool $bubble = true, string $ident = 'php', int $rfc = self::RFC5424) + { + if (!extension_loaded('sockets')) { + throw new MissingExtensionException('The sockets extension is required to use the SyslogUdpHandler'); + } + + parent::__construct($facility, $level, $bubble); + + $this->ident = $ident; + $this->rfc = $rfc; + + $this->socket = new UdpSocket($host, $port); + } + + protected function write(array $record): void + { + $lines = $this->splitMessageIntoLines($record['formatted']); + + $header = $this->makeCommonSyslogHeader($this->logLevels[$record['level']], $record['datetime']); + + foreach ($lines as $line) { + $this->socket->write($line, $header); + } + } + + public function close(): void + { + $this->socket->close(); + } + + /** + * @param string|string[] $message + * @return string[] + */ + private function splitMessageIntoLines($message): array + { + if (is_array($message)) { + $message = implode("\n", $message); + } + + $lines = preg_split('/$\R?^/m', (string) $message, -1, PREG_SPLIT_NO_EMPTY); + if (false === $lines) { + $pcreErrorCode = preg_last_error(); + throw new \RuntimeException('Could not preg_split: ' . $pcreErrorCode . ' / ' . Utils::pcreLastErrorMessage($pcreErrorCode)); + } + + return $lines; + } + + /** + * Make common syslog header (see rfc5424 or rfc3164) + */ + protected function makeCommonSyslogHeader(int $severity, DateTimeInterface $datetime): string + { + $priority = $severity + $this->facility; + + if (!$pid = getmypid()) { + $pid = '-'; + } + + if (!$hostname = gethostname()) { + $hostname = '-'; + } + + if ($this->rfc === self::RFC3164) { + // see https://github.com/phpstan/phpstan/issues/5348 + // @phpstan-ignore-next-line + $dateNew = $datetime->setTimezone(new \DateTimeZone('UTC')); + $date = $dateNew->format($this->dateFormats[$this->rfc]); + + return "<$priority>" . + $date . " " . + $hostname . " " . + $this->ident . "[" . $pid . "]: "; + } + + $date = $datetime->format($this->dateFormats[$this->rfc]); + + return "<$priority>1 " . + $date . " " . + $hostname . " " . + $this->ident . " " . + $pid . " - - "; + } + + /** + * Inject your own socket, mainly used for testing + */ + public function setSocket(UdpSocket $socket): self + { + $this->socket = $socket; + + return $this; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/TelegramBotHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/TelegramBotHandler.php new file mode 100644 index 0000000..9ea0573 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/TelegramBotHandler.php @@ -0,0 +1,194 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use RuntimeException; +use Monolog\Logger; + +/** + * Handler send logs to Telegram using Telegram Bot API. + * + * How to use: + * 1) Create telegram bot with https://telegram.me/BotFather + * 2) Create a telegram channel where logs will be recorded. + * 3) Add created bot from step 1 to the created channel from step 2. + * + * Use telegram bot API key from step 1 and channel name with '@' prefix from step 2 to create instance of TelegramBotHandler + * + * @link https://core.telegram.org/bots/api + * + * @author Mazur Alexandr + * + * @phpstan-import-type Record from \Monolog\Logger + */ +class TelegramBotHandler extends AbstractProcessingHandler +{ + private const BOT_API = 'https://api.telegram.org/bot'; + + /** + * The available values of parseMode according to the Telegram api documentation + */ + private const AVAILABLE_PARSE_MODES = [ + 'HTML', + 'MarkdownV2', + 'Markdown', // legacy mode without underline and strikethrough, use MarkdownV2 instead + ]; + + /** + * Telegram bot access token provided by BotFather. + * Create telegram bot with https://telegram.me/BotFather and use access token from it. + * @var string + */ + private $apiKey; + + /** + * Telegram channel name. + * Since to start with '@' symbol as prefix. + * @var string + */ + private $channel; + + /** + * The kind of formatting that is used for the message. + * See available options at https://core.telegram.org/bots/api#formatting-options + * or in AVAILABLE_PARSE_MODES + * @var ?string + */ + private $parseMode; + + /** + * Disables link previews for links in the message. + * @var ?bool + */ + private $disableWebPagePreview; + + /** + * Sends the message silently. Users will receive a notification with no sound. + * @var ?bool + */ + private $disableNotification; + + /** + * @param string $apiKey Telegram bot access token provided by BotFather + * @param string $channel Telegram channel name + */ + public function __construct( + string $apiKey, + string $channel, + $level = Logger::DEBUG, + bool $bubble = true, + string $parseMode = null, + bool $disableWebPagePreview = null, + bool $disableNotification = null + ) { + if (!extension_loaded('curl')) { + throw new MissingExtensionException('The curl extension is needed to use the TelegramBotHandler'); + } + + parent::__construct($level, $bubble); + + $this->apiKey = $apiKey; + $this->channel = $channel; + $this->setParseMode($parseMode); + $this->disableWebPagePreview($disableWebPagePreview); + $this->disableNotification($disableNotification); + } + + public function setParseMode(string $parseMode = null): self + { + if ($parseMode !== null && !in_array($parseMode, self::AVAILABLE_PARSE_MODES)) { + throw new \InvalidArgumentException('Unknown parseMode, use one of these: ' . implode(', ', self::AVAILABLE_PARSE_MODES) . '.'); + } + + $this->parseMode = $parseMode; + + return $this; + } + + public function disableWebPagePreview(bool $disableWebPagePreview = null): self + { + $this->disableWebPagePreview = $disableWebPagePreview; + + return $this; + } + + public function disableNotification(bool $disableNotification = null): self + { + $this->disableNotification = $disableNotification; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function handleBatch(array $records): void + { + /** @var Record[] $messages */ + $messages = []; + + foreach ($records as $record) { + if (!$this->isHandling($record)) { + continue; + } + + if ($this->processors) { + /** @var Record $record */ + $record = $this->processRecord($record); + } + + $messages[] = $record; + } + + if (!empty($messages)) { + $this->send((string) $this->getFormatter()->formatBatch($messages)); + } + } + + /** + * @inheritDoc + */ + protected function write(array $record): void + { + $this->send($record['formatted']); + } + + /** + * Send request to @link https://api.telegram.org/bot on SendMessage action. + * @param string $message + */ + protected function send(string $message): void + { + $ch = curl_init(); + $url = self::BOT_API . $this->apiKey . '/SendMessage'; + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([ + 'text' => $message, + 'chat_id' => $this->channel, + 'parse_mode' => $this->parseMode, + 'disable_web_page_preview' => $this->disableWebPagePreview, + 'disable_notification' => $this->disableNotification, + ])); + + $result = Curl\Util::execute($ch); + if (!is_string($result)) { + throw new RuntimeException('Telegram API error. Description: No response'); + } + $result = json_decode($result, true); + + if ($result['ok'] === false) { + throw new RuntimeException('Telegram API error. Description: ' . $result['description']); + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php new file mode 100644 index 0000000..0986da2 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php @@ -0,0 +1,231 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Psr\Log\LogLevel; + +/** + * Used for testing purposes. + * + * It records all records and gives you access to them for verification. + * + * @author Jordi Boggiano + * + * @method bool hasEmergency($record) + * @method bool hasAlert($record) + * @method bool hasCritical($record) + * @method bool hasError($record) + * @method bool hasWarning($record) + * @method bool hasNotice($record) + * @method bool hasInfo($record) + * @method bool hasDebug($record) + * + * @method bool hasEmergencyRecords() + * @method bool hasAlertRecords() + * @method bool hasCriticalRecords() + * @method bool hasErrorRecords() + * @method bool hasWarningRecords() + * @method bool hasNoticeRecords() + * @method bool hasInfoRecords() + * @method bool hasDebugRecords() + * + * @method bool hasEmergencyThatContains($message) + * @method bool hasAlertThatContains($message) + * @method bool hasCriticalThatContains($message) + * @method bool hasErrorThatContains($message) + * @method bool hasWarningThatContains($message) + * @method bool hasNoticeThatContains($message) + * @method bool hasInfoThatContains($message) + * @method bool hasDebugThatContains($message) + * + * @method bool hasEmergencyThatMatches($message) + * @method bool hasAlertThatMatches($message) + * @method bool hasCriticalThatMatches($message) + * @method bool hasErrorThatMatches($message) + * @method bool hasWarningThatMatches($message) + * @method bool hasNoticeThatMatches($message) + * @method bool hasInfoThatMatches($message) + * @method bool hasDebugThatMatches($message) + * + * @method bool hasEmergencyThatPasses($message) + * @method bool hasAlertThatPasses($message) + * @method bool hasCriticalThatPasses($message) + * @method bool hasErrorThatPasses($message) + * @method bool hasWarningThatPasses($message) + * @method bool hasNoticeThatPasses($message) + * @method bool hasInfoThatPasses($message) + * @method bool hasDebugThatPasses($message) + * + * @phpstan-import-type Record from \Monolog\Logger + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger + */ +class TestHandler extends AbstractProcessingHandler +{ + /** @var Record[] */ + protected $records = []; + /** @var array */ + protected $recordsByLevel = []; + /** @var bool */ + private $skipReset = false; + + /** + * @return array + * + * @phpstan-return Record[] + */ + public function getRecords() + { + return $this->records; + } + + /** + * @return void + */ + public function clear() + { + $this->records = []; + $this->recordsByLevel = []; + } + + /** + * @return void + */ + public function reset() + { + if (!$this->skipReset) { + $this->clear(); + } + } + + /** + * @return void + */ + public function setSkipReset(bool $skipReset) + { + $this->skipReset = $skipReset; + } + + /** + * @param string|int $level Logging level value or name + * + * @phpstan-param Level|LevelName|LogLevel::* $level + */ + public function hasRecords($level): bool + { + return isset($this->recordsByLevel[Logger::toMonologLevel($level)]); + } + + /** + * @param string|array $record Either a message string or an array containing message and optionally context keys that will be checked against all records + * @param string|int $level Logging level value or name + * + * @phpstan-param array{message: string, context?: mixed[]}|string $record + * @phpstan-param Level|LevelName|LogLevel::* $level + */ + public function hasRecord($record, $level): bool + { + if (is_string($record)) { + $record = array('message' => $record); + } + + return $this->hasRecordThatPasses(function ($rec) use ($record) { + if ($rec['message'] !== $record['message']) { + return false; + } + if (isset($record['context']) && $rec['context'] !== $record['context']) { + return false; + } + + return true; + }, $level); + } + + /** + * @param string|int $level Logging level value or name + * + * @phpstan-param Level|LevelName|LogLevel::* $level + */ + public function hasRecordThatContains(string $message, $level): bool + { + return $this->hasRecordThatPasses(function ($rec) use ($message) { + return strpos($rec['message'], $message) !== false; + }, $level); + } + + /** + * @param string|int $level Logging level value or name + * + * @phpstan-param Level|LevelName|LogLevel::* $level + */ + public function hasRecordThatMatches(string $regex, $level): bool + { + return $this->hasRecordThatPasses(function (array $rec) use ($regex): bool { + return preg_match($regex, $rec['message']) > 0; + }, $level); + } + + /** + * @param string|int $level Logging level value or name + * @return bool + * + * @psalm-param callable(Record, int): mixed $predicate + * @phpstan-param Level|LevelName|LogLevel::* $level + */ + public function hasRecordThatPasses(callable $predicate, $level) + { + $level = Logger::toMonologLevel($level); + + if (!isset($this->recordsByLevel[$level])) { + return false; + } + + foreach ($this->recordsByLevel[$level] as $i => $rec) { + if ($predicate($rec, $i)) { + return true; + } + } + + return false; + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + $this->recordsByLevel[$record['level']][] = $record; + $this->records[] = $record; + } + + /** + * @param string $method + * @param mixed[] $args + * @return bool + */ + public function __call($method, $args) + { + if (preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) { + $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3]; + $level = constant('Monolog\Logger::' . strtoupper($matches[2])); + $callback = [$this, $genericMethod]; + if (is_callable($callback)) { + $args[] = $level; + + return call_user_func_array($callback, $args); + } + } + + throw new \BadMethodCallException('Call to undefined method ' . get_class($this) . '::' . $method . '()'); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/WebRequestRecognizerTrait.php b/vendor/monolog/monolog/src/Monolog/Handler/WebRequestRecognizerTrait.php new file mode 100644 index 0000000..c818352 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/WebRequestRecognizerTrait.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +trait WebRequestRecognizerTrait +{ + /** + * Checks if PHP's serving a web request + * @return bool + */ + protected function isWebRequest(): bool + { + return 'cli' !== \PHP_SAPI && 'phpdbg' !== \PHP_SAPI; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php new file mode 100644 index 0000000..2dd1367 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Forwards records to multiple handlers suppressing failures of each handler + * and continuing through to give every handler a chance to succeed. + * + * @author Craig D'Amelio + * + * @phpstan-import-type Record from \Monolog\Logger + */ +class WhatFailureGroupHandler extends GroupHandler +{ + /** + * {@inheritDoc} + */ + public function handle(array $record): bool + { + if ($this->processors) { + /** @var Record $record */ + $record = $this->processRecord($record); + } + + foreach ($this->handlers as $handler) { + try { + $handler->handle($record); + } catch (\Throwable $e) { + // What failure? + } + } + + return false === $this->bubble; + } + + /** + * {@inheritDoc} + */ + public function handleBatch(array $records): void + { + if ($this->processors) { + $processed = array(); + foreach ($records as $record) { + $processed[] = $this->processRecord($record); + } + /** @var Record[] $records */ + $records = $processed; + } + + foreach ($this->handlers as $handler) { + try { + $handler->handleBatch($records); + } catch (\Throwable $e) { + // What failure? + } + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php new file mode 100644 index 0000000..ddd46d8 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\NormalizerFormatter; +use Monolog\Logger; + +/** + * Handler sending logs to Zend Monitor + * + * @author Christian Bergau + * @author Jason Davis + * + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler + */ +class ZendMonitorHandler extends AbstractProcessingHandler +{ + /** + * Monolog level / ZendMonitor Custom Event priority map + * + * @var array + */ + protected $levelMap = []; + + /** + * @throws MissingExtensionException + */ + public function __construct($level = Logger::DEBUG, bool $bubble = true) + { + if (!function_exists('zend_monitor_custom_event')) { + throw new MissingExtensionException( + 'You must have Zend Server installed with Zend Monitor enabled in order to use this handler' + ); + } + //zend monitor constants are not defined if zend monitor is not enabled. + $this->levelMap = [ + Logger::DEBUG => \ZEND_MONITOR_EVENT_SEVERITY_INFO, + Logger::INFO => \ZEND_MONITOR_EVENT_SEVERITY_INFO, + Logger::NOTICE => \ZEND_MONITOR_EVENT_SEVERITY_INFO, + Logger::WARNING => \ZEND_MONITOR_EVENT_SEVERITY_WARNING, + Logger::ERROR => \ZEND_MONITOR_EVENT_SEVERITY_ERROR, + Logger::CRITICAL => \ZEND_MONITOR_EVENT_SEVERITY_ERROR, + Logger::ALERT => \ZEND_MONITOR_EVENT_SEVERITY_ERROR, + Logger::EMERGENCY => \ZEND_MONITOR_EVENT_SEVERITY_ERROR, + ]; + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + $this->writeZendMonitorCustomEvent( + Logger::getLevelName($record['level']), + $record['message'], + $record['formatted'], + $this->levelMap[$record['level']] + ); + } + + /** + * Write to Zend Monitor Events + * @param string $type Text displayed in "Class Name (custom)" field + * @param string $message Text displayed in "Error String" + * @param array $formatted Displayed in Custom Variables tab + * @param int $severity Set the event severity level (-1,0,1) + * + * @phpstan-param FormattedRecord $formatted + */ + protected function writeZendMonitorCustomEvent(string $type, string $message, array $formatted, int $severity): void + { + zend_monitor_custom_event($type, $message, $formatted, $severity); + } + + /** + * {@inheritDoc} + */ + public function getDefaultFormatter(): FormatterInterface + { + return new NormalizerFormatter(); + } + + /** + * @return array + */ + public function getLevelMap(): array + { + return $this->levelMap; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Logger.php b/vendor/monolog/monolog/src/Monolog/Logger.php new file mode 100644 index 0000000..9fbf205 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Logger.php @@ -0,0 +1,640 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use DateTimeZone; +use Monolog\Handler\HandlerInterface; +use Psr\Log\LoggerInterface; +use Psr\Log\InvalidArgumentException; +use Psr\Log\LogLevel; +use Throwable; +use Stringable; + +/** + * Monolog log channel + * + * It contains a stack of Handlers and a stack of Processors, + * and uses them to store records that are added to it. + * + * @author Jordi Boggiano + * + * @phpstan-type Level Logger::DEBUG|Logger::INFO|Logger::NOTICE|Logger::WARNING|Logger::ERROR|Logger::CRITICAL|Logger::ALERT|Logger::EMERGENCY + * @phpstan-type LevelName 'DEBUG'|'INFO'|'NOTICE'|'WARNING'|'ERROR'|'CRITICAL'|'ALERT'|'EMERGENCY' + * @phpstan-type Record array{message: string, context: mixed[], level: Level, level_name: LevelName, channel: string, datetime: \DateTimeImmutable, extra: mixed[]} + */ +class Logger implements LoggerInterface, ResettableInterface +{ + /** + * Detailed debug information + */ + public const DEBUG = 100; + + /** + * Interesting events + * + * Examples: User logs in, SQL logs. + */ + public const INFO = 200; + + /** + * Uncommon events + */ + public const NOTICE = 250; + + /** + * Exceptional occurrences that are not errors + * + * Examples: Use of deprecated APIs, poor use of an API, + * undesirable things that are not necessarily wrong. + */ + public const WARNING = 300; + + /** + * Runtime errors + */ + public const ERROR = 400; + + /** + * Critical conditions + * + * Example: Application component unavailable, unexpected exception. + */ + public const CRITICAL = 500; + + /** + * Action must be taken immediately + * + * Example: Entire website down, database unavailable, etc. + * This should trigger the SMS alerts and wake you up. + */ + public const ALERT = 550; + + /** + * Urgent alert. + */ + public const EMERGENCY = 600; + + /** + * Monolog API version + * + * This is only bumped when API breaks are done and should + * follow the major version of the library + * + * @var int + */ + public const API = 2; + + /** + * This is a static variable and not a constant to serve as an extension point for custom levels + * + * @var array $levels Logging levels with the levels as key + * + * @phpstan-var array $levels Logging levels with the levels as key + */ + protected static $levels = [ + self::DEBUG => 'DEBUG', + self::INFO => 'INFO', + self::NOTICE => 'NOTICE', + self::WARNING => 'WARNING', + self::ERROR => 'ERROR', + self::CRITICAL => 'CRITICAL', + self::ALERT => 'ALERT', + self::EMERGENCY => 'EMERGENCY', + ]; + + /** + * @var string + */ + protected $name; + + /** + * The handler stack + * + * @var HandlerInterface[] + */ + protected $handlers; + + /** + * Processors that will process all log records + * + * To process records of a single handler instead, add the processor on that specific handler + * + * @var callable[] + */ + protected $processors; + + /** + * @var bool + */ + protected $microsecondTimestamps = true; + + /** + * @var DateTimeZone + */ + protected $timezone; + + /** + * @var callable|null + */ + protected $exceptionHandler; + + /** + * @psalm-param array $processors + * + * @param string $name The logging channel, a simple descriptive name that is attached to all log records + * @param HandlerInterface[] $handlers Optional stack of handlers, the first one in the array is called first, etc. + * @param callable[] $processors Optional array of processors + * @param DateTimeZone|null $timezone Optional timezone, if not provided date_default_timezone_get() will be used + */ + public function __construct(string $name, array $handlers = [], array $processors = [], ?DateTimeZone $timezone = null) + { + $this->name = $name; + $this->setHandlers($handlers); + $this->processors = $processors; + $this->timezone = $timezone ?: new DateTimeZone(date_default_timezone_get() ?: 'UTC'); + } + + public function getName(): string + { + return $this->name; + } + + /** + * Return a new cloned instance with the name changed + */ + public function withName(string $name): self + { + $new = clone $this; + $new->name = $name; + + return $new; + } + + /** + * Pushes a handler on to the stack. + */ + public function pushHandler(HandlerInterface $handler): self + { + array_unshift($this->handlers, $handler); + + return $this; + } + + /** + * Pops a handler from the stack + * + * @throws \LogicException If empty handler stack + */ + public function popHandler(): HandlerInterface + { + if (!$this->handlers) { + throw new \LogicException('You tried to pop from an empty handler stack.'); + } + + return array_shift($this->handlers); + } + + /** + * Set handlers, replacing all existing ones. + * + * If a map is passed, keys will be ignored. + * + * @param HandlerInterface[] $handlers + */ + public function setHandlers(array $handlers): self + { + $this->handlers = []; + foreach (array_reverse($handlers) as $handler) { + $this->pushHandler($handler); + } + + return $this; + } + + /** + * @return HandlerInterface[] + */ + public function getHandlers(): array + { + return $this->handlers; + } + + /** + * Adds a processor on to the stack. + */ + public function pushProcessor(callable $callback): self + { + array_unshift($this->processors, $callback); + + return $this; + } + + /** + * Removes the processor on top of the stack and returns it. + * + * @throws \LogicException If empty processor stack + * @return callable + */ + public function popProcessor(): callable + { + if (!$this->processors) { + throw new \LogicException('You tried to pop from an empty processor stack.'); + } + + return array_shift($this->processors); + } + + /** + * @return callable[] + */ + public function getProcessors(): array + { + return $this->processors; + } + + /** + * Control the use of microsecond resolution timestamps in the 'datetime' + * member of new records. + * + * As of PHP7.1 microseconds are always included by the engine, so + * there is no performance penalty and Monolog 2 enabled microseconds + * by default. This function lets you disable them though in case you want + * to suppress microseconds from the output. + * + * @param bool $micro True to use microtime() to create timestamps + */ + public function useMicrosecondTimestamps(bool $micro): void + { + $this->microsecondTimestamps = $micro; + } + + /** + * Adds a log record. + * + * @param int $level The logging level + * @param string $message The log message + * @param mixed[] $context The log context + * @return bool Whether the record has been processed + * + * @phpstan-param Level $level + */ + public function addRecord(int $level, string $message, array $context = []): bool + { + $offset = 0; + $record = null; + + foreach ($this->handlers as $handler) { + if (null === $record) { + // skip creating the record as long as no handler is going to handle it + if (!$handler->isHandling(['level' => $level])) { + continue; + } + + $levelName = static::getLevelName($level); + + $record = [ + 'message' => $message, + 'context' => $context, + 'level' => $level, + 'level_name' => $levelName, + 'channel' => $this->name, + 'datetime' => new DateTimeImmutable($this->microsecondTimestamps, $this->timezone), + 'extra' => [], + ]; + + try { + foreach ($this->processors as $processor) { + $record = $processor($record); + } + } catch (Throwable $e) { + $this->handleException($e, $record); + + return true; + } + } + + // once the record exists, send it to all handlers as long as the bubbling chain is not interrupted + try { + if (true === $handler->handle($record)) { + break; + } + } catch (Throwable $e) { + $this->handleException($e, $record); + + return true; + } + } + + return null !== $record; + } + + /** + * Ends a log cycle and frees all resources used by handlers. + * + * Closing a Handler means flushing all buffers and freeing any open resources/handles. + * Handlers that have been closed should be able to accept log records again and re-open + * themselves on demand, but this may not always be possible depending on implementation. + * + * This is useful at the end of a request and will be called automatically on every handler + * when they get destructed. + */ + public function close(): void + { + foreach ($this->handlers as $handler) { + $handler->close(); + } + } + + /** + * Ends a log cycle and resets all handlers and processors to their initial state. + * + * Resetting a Handler or a Processor means flushing/cleaning all buffers, resetting internal + * state, and getting it back to a state in which it can receive log records again. + * + * This is useful in case you want to avoid logs leaking between two requests or jobs when you + * have a long running process like a worker or an application server serving multiple requests + * in one process. + */ + public function reset(): void + { + foreach ($this->handlers as $handler) { + if ($handler instanceof ResettableInterface) { + $handler->reset(); + } + } + + foreach ($this->processors as $processor) { + if ($processor instanceof ResettableInterface) { + $processor->reset(); + } + } + } + + /** + * Gets all supported logging levels. + * + * @return array Assoc array with human-readable level names => level codes. + * @phpstan-return array + */ + public static function getLevels(): array + { + return array_flip(static::$levels); + } + + /** + * Gets the name of the logging level. + * + * @throws \Psr\Log\InvalidArgumentException If level is not defined + * + * @phpstan-param Level $level + * @phpstan-return LevelName + */ + public static function getLevelName(int $level): string + { + if (!isset(static::$levels[$level])) { + throw new InvalidArgumentException('Level "'.$level.'" is not defined, use one of: '.implode(', ', array_keys(static::$levels))); + } + + return static::$levels[$level]; + } + + /** + * Converts PSR-3 levels to Monolog ones if necessary + * + * @param string|int $level Level number (monolog) or name (PSR-3) + * @throws \Psr\Log\InvalidArgumentException If level is not defined + * + * @phpstan-param Level|LevelName|LogLevel::* $level + * @phpstan-return Level + */ + public static function toMonologLevel($level): int + { + if (is_string($level)) { + if (is_numeric($level)) { + /** @phpstan-ignore-next-line */ + return intval($level); + } + + // Contains chars of all log levels and avoids using strtoupper() which may have + // strange results depending on locale (for example, "i" will become "İ" in Turkish locale) + $upper = strtr($level, 'abcdefgilmnortuwy', 'ABCDEFGILMNORTUWY'); + if (defined(__CLASS__.'::'.$upper)) { + return constant(__CLASS__ . '::' . $upper); + } + + throw new InvalidArgumentException('Level "'.$level.'" is not defined, use one of: '.implode(', ', array_keys(static::$levels) + static::$levels)); + } + + if (!is_int($level)) { + throw new InvalidArgumentException('Level "'.var_export($level, true).'" is not defined, use one of: '.implode(', ', array_keys(static::$levels) + static::$levels)); + } + + return $level; + } + + /** + * Checks whether the Logger has a handler that listens on the given level + * + * @phpstan-param Level $level + */ + public function isHandling(int $level): bool + { + $record = [ + 'level' => $level, + ]; + + foreach ($this->handlers as $handler) { + if ($handler->isHandling($record)) { + return true; + } + } + + return false; + } + + /** + * Set a custom exception handler that will be called if adding a new record fails + * + * The callable will receive an exception object and the record that failed to be logged + */ + public function setExceptionHandler(?callable $callback): self + { + $this->exceptionHandler = $callback; + + return $this; + } + + public function getExceptionHandler(): ?callable + { + return $this->exceptionHandler; + } + + /** + * Adds a log record at an arbitrary level. + * + * This method allows for compatibility with common interfaces. + * + * @param mixed $level The log level + * @param string|Stringable $message The log message + * @param mixed[] $context The log context + * + * @phpstan-param Level|LevelName|LogLevel::* $level + */ + public function log($level, $message, array $context = []): void + { + if (!is_int($level) && !is_string($level)) { + throw new \InvalidArgumentException('$level is expected to be a string or int'); + } + + $level = static::toMonologLevel($level); + + $this->addRecord($level, (string) $message, $context); + } + + /** + * Adds a log record at the DEBUG level. + * + * This method allows for compatibility with common interfaces. + * + * @param string|Stringable $message The log message + * @param mixed[] $context The log context + */ + public function debug($message, array $context = []): void + { + $this->addRecord(static::DEBUG, (string) $message, $context); + } + + /** + * Adds a log record at the INFO level. + * + * This method allows for compatibility with common interfaces. + * + * @param string|Stringable $message The log message + * @param mixed[] $context The log context + */ + public function info($message, array $context = []): void + { + $this->addRecord(static::INFO, (string) $message, $context); + } + + /** + * Adds a log record at the NOTICE level. + * + * This method allows for compatibility with common interfaces. + * + * @param string|Stringable $message The log message + * @param mixed[] $context The log context + */ + public function notice($message, array $context = []): void + { + $this->addRecord(static::NOTICE, (string) $message, $context); + } + + /** + * Adds a log record at the WARNING level. + * + * This method allows for compatibility with common interfaces. + * + * @param string|Stringable $message The log message + * @param mixed[] $context The log context + */ + public function warning($message, array $context = []): void + { + $this->addRecord(static::WARNING, (string) $message, $context); + } + + /** + * Adds a log record at the ERROR level. + * + * This method allows for compatibility with common interfaces. + * + * @param string|Stringable $message The log message + * @param mixed[] $context The log context + */ + public function error($message, array $context = []): void + { + $this->addRecord(static::ERROR, (string) $message, $context); + } + + /** + * Adds a log record at the CRITICAL level. + * + * This method allows for compatibility with common interfaces. + * + * @param string|Stringable $message The log message + * @param mixed[] $context The log context + */ + public function critical($message, array $context = []): void + { + $this->addRecord(static::CRITICAL, (string) $message, $context); + } + + /** + * Adds a log record at the ALERT level. + * + * This method allows for compatibility with common interfaces. + * + * @param string|Stringable $message The log message + * @param mixed[] $context The log context + */ + public function alert($message, array $context = []): void + { + $this->addRecord(static::ALERT, (string) $message, $context); + } + + /** + * Adds a log record at the EMERGENCY level. + * + * This method allows for compatibility with common interfaces. + * + * @param string|Stringable $message The log message + * @param mixed[] $context The log context + */ + public function emergency($message, array $context = []): void + { + $this->addRecord(static::EMERGENCY, (string) $message, $context); + } + + /** + * Sets the timezone to be used for the timestamp of log records. + */ + public function setTimezone(DateTimeZone $tz): self + { + $this->timezone = $tz; + + return $this; + } + + /** + * Returns the timezone to be used for the timestamp of log records. + */ + public function getTimezone(): DateTimeZone + { + return $this->timezone; + } + + /** + * Delegates exception management to the custom exception handler, + * or throws the exception if no custom handler is set. + * + * @param array $record + * @phpstan-param Record $record + */ + protected function handleException(Throwable $e, array $record): void + { + if (!$this->exceptionHandler) { + throw $e; + } + + ($this->exceptionHandler)($e, $record); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.php new file mode 100644 index 0000000..8166bdc --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\Logger; +use Psr\Log\LogLevel; + +/** + * Injects Git branch and Git commit SHA in all records + * + * @author Nick Otter + * @author Jordi Boggiano + * + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger + */ +class GitProcessor implements ProcessorInterface +{ + /** @var int */ + private $level; + /** @var array{branch: string, commit: string}|array|null */ + private static $cache = null; + + /** + * @param string|int $level The minimum logging level at which this Processor will be triggered + * + * @phpstan-param Level|LevelName|LogLevel::* $level + */ + public function __construct($level = Logger::DEBUG) + { + $this->level = Logger::toMonologLevel($level); + } + + /** + * {@inheritDoc} + */ + public function __invoke(array $record): array + { + // return if the level is not high enough + if ($record['level'] < $this->level) { + return $record; + } + + $record['extra']['git'] = self::getGitInfo(); + + return $record; + } + + /** + * @return array{branch: string, commit: string}|array + */ + private static function getGitInfo(): array + { + if (self::$cache) { + return self::$cache; + } + + $branches = `git branch -v --no-abbrev`; + if ($branches && preg_match('{^\* (.+?)\s+([a-f0-9]{40})(?:\s|$)}m', $branches, $matches)) { + return self::$cache = [ + 'branch' => $matches[1], + 'commit' => $matches[2], + ]; + } + + return self::$cache = []; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/HostnameProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/HostnameProcessor.php new file mode 100644 index 0000000..91fda7d --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/HostnameProcessor.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Injects value of gethostname in all records + */ +class HostnameProcessor implements ProcessorInterface +{ + /** @var string */ + private static $host; + + public function __construct() + { + self::$host = (string) gethostname(); + } + + /** + * {@inheritDoc} + */ + public function __invoke(array $record): array + { + $record['extra']['hostname'] = self::$host; + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php new file mode 100644 index 0000000..0823501 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\Logger; +use Psr\Log\LogLevel; + +/** + * Injects line/file:class/function where the log message came from + * + * Warning: This only works if the handler processes the logs directly. + * If you put the processor on a handler that is behind a FingersCrossedHandler + * for example, the processor will only be called once the trigger level is reached, + * and all the log records will have the same file/line/.. data from the call that + * triggered the FingersCrossedHandler. + * + * @author Jordi Boggiano + * + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger + */ +class IntrospectionProcessor implements ProcessorInterface +{ + /** @var int */ + private $level; + /** @var string[] */ + private $skipClassesPartials; + /** @var int */ + private $skipStackFramesCount; + /** @var string[] */ + private $skipFunctions = [ + 'call_user_func', + 'call_user_func_array', + ]; + + /** + * @param string|int $level The minimum logging level at which this Processor will be triggered + * @param string[] $skipClassesPartials + * + * @phpstan-param Level|LevelName|LogLevel::* $level + */ + public function __construct($level = Logger::DEBUG, array $skipClassesPartials = [], int $skipStackFramesCount = 0) + { + $this->level = Logger::toMonologLevel($level); + $this->skipClassesPartials = array_merge(['Monolog\\'], $skipClassesPartials); + $this->skipStackFramesCount = $skipStackFramesCount; + } + + /** + * {@inheritDoc} + */ + public function __invoke(array $record): array + { + // return if the level is not high enough + if ($record['level'] < $this->level) { + return $record; + } + + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + + // skip first since it's always the current method + array_shift($trace); + // the call_user_func call is also skipped + array_shift($trace); + + $i = 0; + + while ($this->isTraceClassOrSkippedFunction($trace, $i)) { + if (isset($trace[$i]['class'])) { + foreach ($this->skipClassesPartials as $part) { + if (strpos($trace[$i]['class'], $part) !== false) { + $i++; + + continue 2; + } + } + } elseif (in_array($trace[$i]['function'], $this->skipFunctions)) { + $i++; + + continue; + } + + break; + } + + $i += $this->skipStackFramesCount; + + // we should have the call source now + $record['extra'] = array_merge( + $record['extra'], + [ + 'file' => isset($trace[$i - 1]['file']) ? $trace[$i - 1]['file'] : null, + 'line' => isset($trace[$i - 1]['line']) ? $trace[$i - 1]['line'] : null, + 'class' => isset($trace[$i]['class']) ? $trace[$i]['class'] : null, + 'function' => isset($trace[$i]['function']) ? $trace[$i]['function'] : null, + ] + ); + + return $record; + } + + /** + * @param array[] $trace + */ + private function isTraceClassOrSkippedFunction(array $trace, int $index): bool + { + if (!isset($trace[$index])) { + return false; + } + + return isset($trace[$index]['class']) || in_array($trace[$index]['function'], $this->skipFunctions); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php new file mode 100644 index 0000000..37c756f --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Injects memory_get_peak_usage in all records + * + * @see Monolog\Processor\MemoryProcessor::__construct() for options + * @author Rob Jensen + */ +class MemoryPeakUsageProcessor extends MemoryProcessor +{ + /** + * {@inheritDoc} + */ + public function __invoke(array $record): array + { + $usage = memory_get_peak_usage($this->realUsage); + + if ($this->useFormatting) { + $usage = $this->formatBytes($usage); + } + + $record['extra']['memory_peak_usage'] = $usage; + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php new file mode 100644 index 0000000..227deb7 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Some methods that are common for all memory processors + * + * @author Rob Jensen + */ +abstract class MemoryProcessor implements ProcessorInterface +{ + /** + * @var bool If true, get the real size of memory allocated from system. Else, only the memory used by emalloc() is reported. + */ + protected $realUsage; + + /** + * @var bool If true, then format memory size to human readable string (MB, KB, B depending on size) + */ + protected $useFormatting; + + /** + * @param bool $realUsage Set this to true to get the real size of memory allocated from system. + * @param bool $useFormatting If true, then format memory size to human readable string (MB, KB, B depending on size) + */ + public function __construct(bool $realUsage = true, bool $useFormatting = true) + { + $this->realUsage = $realUsage; + $this->useFormatting = $useFormatting; + } + + /** + * Formats bytes into a human readable string if $this->useFormatting is true, otherwise return $bytes as is + * + * @param int $bytes + * @return string|int Formatted string if $this->useFormatting is true, otherwise return $bytes as int + */ + protected function formatBytes(int $bytes) + { + if (!$this->useFormatting) { + return $bytes; + } + + if ($bytes > 1024 * 1024) { + return round($bytes / 1024 / 1024, 2).' MB'; + } elseif ($bytes > 1024) { + return round($bytes / 1024, 2).' KB'; + } + + return $bytes . ' B'; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php new file mode 100644 index 0000000..e141921 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Injects memory_get_usage in all records + * + * @see Monolog\Processor\MemoryProcessor::__construct() for options + * @author Rob Jensen + */ +class MemoryUsageProcessor extends MemoryProcessor +{ + /** + * {@inheritDoc} + */ + public function __invoke(array $record): array + { + $usage = memory_get_usage($this->realUsage); + + if ($this->useFormatting) { + $usage = $this->formatBytes($usage); + } + + $record['extra']['memory_usage'] = $usage; + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php new file mode 100644 index 0000000..d4a628f --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\Logger; +use Psr\Log\LogLevel; + +/** + * Injects Hg branch and Hg revision number in all records + * + * @author Jonathan A. Schweder + * + * @phpstan-import-type LevelName from \Monolog\Logger + * @phpstan-import-type Level from \Monolog\Logger + */ +class MercurialProcessor implements ProcessorInterface +{ + /** @var Level */ + private $level; + /** @var array{branch: string, revision: string}|array|null */ + private static $cache = null; + + /** + * @param int|string $level The minimum logging level at which this Processor will be triggered + * + * @phpstan-param Level|LevelName|LogLevel::* $level + */ + public function __construct($level = Logger::DEBUG) + { + $this->level = Logger::toMonologLevel($level); + } + + /** + * {@inheritDoc} + */ + public function __invoke(array $record): array + { + // return if the level is not high enough + if ($record['level'] < $this->level) { + return $record; + } + + $record['extra']['hg'] = self::getMercurialInfo(); + + return $record; + } + + /** + * @return array{branch: string, revision: string}|array + */ + private static function getMercurialInfo(): array + { + if (self::$cache) { + return self::$cache; + } + + $result = explode(' ', trim(`hg id -nb`)); + + if (count($result) >= 3) { + return self::$cache = [ + 'branch' => $result[1], + 'revision' => $result[2], + ]; + } + + return self::$cache = []; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php new file mode 100644 index 0000000..3b939a9 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Adds value of getmypid into records + * + * @author Andreas Hörnicke + */ +class ProcessIdProcessor implements ProcessorInterface +{ + /** + * {@inheritDoc} + */ + public function __invoke(array $record): array + { + $record['extra']['process_id'] = getmypid(); + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/ProcessorInterface.php b/vendor/monolog/monolog/src/Monolog/Processor/ProcessorInterface.php new file mode 100644 index 0000000..5defb7e --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/ProcessorInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * An optional interface to allow labelling Monolog processors. + * + * @author Nicolas Grekas + * + * @phpstan-import-type Record from \Monolog\Logger + */ +interface ProcessorInterface +{ + /** + * @return array The processed record + * + * @phpstan-param Record $record + * @phpstan-return Record + */ + public function __invoke(array $record); +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php new file mode 100644 index 0000000..2c2a00e --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\Utils; + +/** + * Processes a record's message according to PSR-3 rules + * + * It replaces {foo} with the value from $context['foo'] + * + * @author Jordi Boggiano + */ +class PsrLogMessageProcessor implements ProcessorInterface +{ + public const SIMPLE_DATE = "Y-m-d\TH:i:s.uP"; + + /** @var string|null */ + private $dateFormat; + + /** @var bool */ + private $removeUsedContextFields; + + /** + * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format + * @param bool $removeUsedContextFields If set to true the fields interpolated into message gets unset + */ + public function __construct(?string $dateFormat = null, bool $removeUsedContextFields = false) + { + $this->dateFormat = $dateFormat; + $this->removeUsedContextFields = $removeUsedContextFields; + } + + /** + * {@inheritDoc} + */ + public function __invoke(array $record): array + { + if (false === strpos($record['message'], '{')) { + return $record; + } + + $replacements = []; + foreach ($record['context'] as $key => $val) { + $placeholder = '{' . $key . '}'; + if (strpos($record['message'], $placeholder) === false) { + continue; + } + + if (is_null($val) || is_scalar($val) || (is_object($val) && method_exists($val, "__toString"))) { + $replacements[$placeholder] = $val; + } elseif ($val instanceof \DateTimeInterface) { + if (!$this->dateFormat && $val instanceof \Monolog\DateTimeImmutable) { + // handle monolog dates using __toString if no specific dateFormat was asked for + // so that it follows the useMicroseconds flag + $replacements[$placeholder] = (string) $val; + } else { + $replacements[$placeholder] = $val->format($this->dateFormat ?: static::SIMPLE_DATE); + } + } elseif (is_object($val)) { + $replacements[$placeholder] = '[object '.Utils::getClass($val).']'; + } elseif (is_array($val)) { + $replacements[$placeholder] = 'array'.Utils::jsonEncode($val, null, true); + } else { + $replacements[$placeholder] = '['.gettype($val).']'; + } + + if ($this->removeUsedContextFields) { + unset($record['context'][$key]); + } + } + + $record['message'] = strtr($record['message'], $replacements); + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php new file mode 100644 index 0000000..80f1874 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Adds a tags array into record + * + * @author Martijn Riemers + */ +class TagProcessor implements ProcessorInterface +{ + /** @var string[] */ + private $tags; + + /** + * @param string[] $tags + */ + public function __construct(array $tags = []) + { + $this->setTags($tags); + } + + /** + * @param string[] $tags + */ + public function addTags(array $tags = []): self + { + $this->tags = array_merge($this->tags, $tags); + + return $this; + } + + /** + * @param string[] $tags + */ + public function setTags(array $tags = []): self + { + $this->tags = $tags; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function __invoke(array $record): array + { + $record['extra']['tags'] = $this->tags; + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php new file mode 100644 index 0000000..a27b74d --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\ResettableInterface; + +/** + * Adds a unique identifier into records + * + * @author Simon Mönch + */ +class UidProcessor implements ProcessorInterface, ResettableInterface +{ + /** @var string */ + private $uid; + + public function __construct(int $length = 7) + { + if ($length > 32 || $length < 1) { + throw new \InvalidArgumentException('The uid length must be an integer between 1 and 32'); + } + + $this->uid = $this->generateUid($length); + } + + /** + * {@inheritDoc} + */ + public function __invoke(array $record): array + { + $record['extra']['uid'] = $this->uid; + + return $record; + } + + public function getUid(): string + { + return $this->uid; + } + + public function reset() + { + $this->uid = $this->generateUid(strlen($this->uid)); + } + + private function generateUid(int $length): string + { + return substr(bin2hex(random_bytes((int) ceil($length / 2))), 0, $length); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php new file mode 100644 index 0000000..64d251d --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Injects url/method and remote IP of the current web request in all records + * + * @author Jordi Boggiano + */ +class WebProcessor implements ProcessorInterface +{ + /** + * @var array|\ArrayAccess + */ + protected $serverData; + + /** + * Default fields + * + * Array is structured as [key in record.extra => key in $serverData] + * + * @var array + */ + protected $extraFields = [ + 'url' => 'REQUEST_URI', + 'ip' => 'REMOTE_ADDR', + 'http_method' => 'REQUEST_METHOD', + 'server' => 'SERVER_NAME', + 'referrer' => 'HTTP_REFERER', + ]; + + /** + * @param array|\ArrayAccess|null $serverData Array or object w/ ArrayAccess that provides access to the $_SERVER data + * @param array|null $extraFields Field names and the related key inside $serverData to be added. If not provided it defaults to: url, ip, http_method, server, referrer + */ + public function __construct($serverData = null, array $extraFields = null) + { + if (null === $serverData) { + $this->serverData = &$_SERVER; + } elseif (is_array($serverData) || $serverData instanceof \ArrayAccess) { + $this->serverData = $serverData; + } else { + throw new \UnexpectedValueException('$serverData must be an array or object implementing ArrayAccess.'); + } + + if (isset($this->serverData['UNIQUE_ID'])) { + $this->extraFields['unique_id'] = 'UNIQUE_ID'; + } + + if (null !== $extraFields) { + if (isset($extraFields[0])) { + foreach (array_keys($this->extraFields) as $fieldName) { + if (!in_array($fieldName, $extraFields)) { + unset($this->extraFields[$fieldName]); + } + } + } else { + $this->extraFields = $extraFields; + } + } + } + + /** + * {@inheritDoc} + */ + public function __invoke(array $record): array + { + // skip processing if for some reason request data + // is not present (CLI or wonky SAPIs) + if (!isset($this->serverData['REQUEST_URI'])) { + return $record; + } + + $record['extra'] = $this->appendExtraFields($record['extra']); + + return $record; + } + + public function addExtraField(string $extraName, string $serverName): self + { + $this->extraFields[$extraName] = $serverName; + + return $this; + } + + /** + * @param mixed[] $extra + * @return mixed[] + */ + private function appendExtraFields(array $extra): array + { + foreach ($this->extraFields as $extraName => $serverName) { + $extra[$extraName] = $this->serverData[$serverName] ?? null; + } + + return $extra; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Registry.php b/vendor/monolog/monolog/src/Monolog/Registry.php new file mode 100644 index 0000000..ae94ae6 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Registry.php @@ -0,0 +1,134 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use InvalidArgumentException; + +/** + * Monolog log registry + * + * Allows to get `Logger` instances in the global scope + * via static method calls on this class. + * + * + * $application = new Monolog\Logger('application'); + * $api = new Monolog\Logger('api'); + * + * Monolog\Registry::addLogger($application); + * Monolog\Registry::addLogger($api); + * + * function testLogger() + * { + * Monolog\Registry::api()->error('Sent to $api Logger instance'); + * Monolog\Registry::application()->error('Sent to $application Logger instance'); + * } + * + * + * @author Tomas Tatarko + */ +class Registry +{ + /** + * List of all loggers in the registry (by named indexes) + * + * @var Logger[] + */ + private static $loggers = []; + + /** + * Adds new logging channel to the registry + * + * @param Logger $logger Instance of the logging channel + * @param string|null $name Name of the logging channel ($logger->getName() by default) + * @param bool $overwrite Overwrite instance in the registry if the given name already exists? + * @throws \InvalidArgumentException If $overwrite set to false and named Logger instance already exists + * @return void + */ + public static function addLogger(Logger $logger, ?string $name = null, bool $overwrite = false) + { + $name = $name ?: $logger->getName(); + + if (isset(self::$loggers[$name]) && !$overwrite) { + throw new InvalidArgumentException('Logger with the given name already exists'); + } + + self::$loggers[$name] = $logger; + } + + /** + * Checks if such logging channel exists by name or instance + * + * @param string|Logger $logger Name or logger instance + */ + public static function hasLogger($logger): bool + { + if ($logger instanceof Logger) { + $index = array_search($logger, self::$loggers, true); + + return false !== $index; + } + + return isset(self::$loggers[$logger]); + } + + /** + * Removes instance from registry by name or instance + * + * @param string|Logger $logger Name or logger instance + */ + public static function removeLogger($logger): void + { + if ($logger instanceof Logger) { + if (false !== ($idx = array_search($logger, self::$loggers, true))) { + unset(self::$loggers[$idx]); + } + } else { + unset(self::$loggers[$logger]); + } + } + + /** + * Clears the registry + */ + public static function clear(): void + { + self::$loggers = []; + } + + /** + * Gets Logger instance from the registry + * + * @param string $name Name of the requested Logger instance + * @throws \InvalidArgumentException If named Logger instance is not in the registry + */ + public static function getInstance($name): Logger + { + if (!isset(self::$loggers[$name])) { + throw new InvalidArgumentException(sprintf('Requested "%s" logger instance is not in the registry', $name)); + } + + return self::$loggers[$name]; + } + + /** + * Gets Logger instance from the registry via static method call + * + * @param string $name Name of the requested Logger instance + * @param mixed[] $arguments Arguments passed to static method call + * @throws \InvalidArgumentException If named Logger instance is not in the registry + * @return Logger Requested instance of Logger + */ + public static function __callStatic($name, $arguments) + { + return self::getInstance($name); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/ResettableInterface.php b/vendor/monolog/monolog/src/Monolog/ResettableInterface.php new file mode 100644 index 0000000..2c5fd78 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/ResettableInterface.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +/** + * Handler or Processor implementing this interface will be reset when Logger::reset() is called. + * + * Resetting ends a log cycle gets them back to their initial state. + * + * Resetting a Handler or a Processor means flushing/cleaning all buffers, resetting internal + * state, and getting it back to a state in which it can receive log records again. + * + * This is useful in case you want to avoid logs leaking between two requests or jobs when you + * have a long running process like a worker or an application server serving multiple requests + * in one process. + * + * @author Grégoire Pineau + */ +interface ResettableInterface +{ + /** + * @return void + */ + public function reset(); +} diff --git a/vendor/monolog/monolog/src/Monolog/SignalHandler.php b/vendor/monolog/monolog/src/Monolog/SignalHandler.php new file mode 100644 index 0000000..d730eea --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/SignalHandler.php @@ -0,0 +1,120 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; +use ReflectionExtension; + +/** + * Monolog POSIX signal handler + * + * @author Robert Gust-Bardon + * + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger + */ +class SignalHandler +{ + /** @var LoggerInterface */ + private $logger; + + /** @var array SIG_DFL, SIG_IGN or previous callable */ + private $previousSignalHandler = []; + /** @var array */ + private $signalLevelMap = []; + /** @var array */ + private $signalRestartSyscalls = []; + + public function __construct(LoggerInterface $logger) + { + $this->logger = $logger; + } + + /** + * @param int|string $level Level or level name + * @param bool $callPrevious + * @param bool $restartSyscalls + * @param bool|null $async + * @return $this + * + * @phpstan-param Level|LevelName|LogLevel::* $level + */ + public function registerSignalHandler(int $signo, $level = LogLevel::CRITICAL, bool $callPrevious = true, bool $restartSyscalls = true, ?bool $async = true): self + { + if (!extension_loaded('pcntl') || !function_exists('pcntl_signal')) { + return $this; + } + + $level = Logger::toMonologLevel($level); + + if ($callPrevious) { + $handler = pcntl_signal_get_handler($signo); + $this->previousSignalHandler[$signo] = $handler; + } else { + unset($this->previousSignalHandler[$signo]); + } + $this->signalLevelMap[$signo] = $level; + $this->signalRestartSyscalls[$signo] = $restartSyscalls; + + if ($async !== null) { + pcntl_async_signals($async); + } + + pcntl_signal($signo, [$this, 'handleSignal'], $restartSyscalls); + + return $this; + } + + /** + * @param mixed $siginfo + */ + public function handleSignal(int $signo, $siginfo = null): void + { + static $signals = []; + + if (!$signals && extension_loaded('pcntl')) { + $pcntl = new ReflectionExtension('pcntl'); + // HHVM 3.24.2 returns an empty array. + foreach ($pcntl->getConstants() ?: get_defined_constants(true)['Core'] as $name => $value) { + if (substr($name, 0, 3) === 'SIG' && $name[3] !== '_' && is_int($value)) { + $signals[$value] = $name; + } + } + } + + $level = $this->signalLevelMap[$signo] ?? LogLevel::CRITICAL; + $signal = $signals[$signo] ?? $signo; + $context = $siginfo ?? []; + $this->logger->log($level, sprintf('Program received signal %s', $signal), $context); + + if (!isset($this->previousSignalHandler[$signo])) { + return; + } + + if ($this->previousSignalHandler[$signo] === SIG_DFL) { + if (extension_loaded('pcntl') && function_exists('pcntl_signal') && function_exists('pcntl_sigprocmask') && function_exists('pcntl_signal_dispatch') + && extension_loaded('posix') && function_exists('posix_getpid') && function_exists('posix_kill') + ) { + $restartSyscalls = $this->signalRestartSyscalls[$signo] ?? true; + pcntl_signal($signo, SIG_DFL, $restartSyscalls); + pcntl_sigprocmask(SIG_UNBLOCK, [$signo], $oldset); + posix_kill(posix_getpid(), $signo); + pcntl_signal_dispatch(); + pcntl_sigprocmask(SIG_SETMASK, $oldset); + pcntl_signal($signo, [$this, 'handleSignal'], $restartSyscalls); + } + } elseif (is_callable($this->previousSignalHandler[$signo])) { + $this->previousSignalHandler[$signo]($signo, $siginfo); + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Test/TestCase.php b/vendor/monolog/monolog/src/Monolog/Test/TestCase.php new file mode 100644 index 0000000..1824fde --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Test/TestCase.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Test; + +use Monolog\Logger; +use Monolog\DateTimeImmutable; +use Monolog\Formatter\FormatterInterface; + +/** + * Lets you easily generate log records and a dummy formatter for testing purposes + * + * @author Jordi Boggiano + * + * @phpstan-import-type Record from \Monolog\Logger + * @phpstan-import-type Level from \Monolog\Logger + */ +class TestCase extends \PHPUnit\Framework\TestCase +{ + /** + * @param mixed[] $context + * + * @return array Record + * + * @phpstan-param Level $level + * @phpstan-return Record + */ + protected function getRecord(int $level = Logger::WARNING, string $message = 'test', array $context = []): array + { + return [ + 'message' => (string) $message, + 'context' => $context, + 'level' => $level, + 'level_name' => Logger::getLevelName($level), + 'channel' => 'test', + 'datetime' => new DateTimeImmutable(true), + 'extra' => [], + ]; + } + + /** + * @phpstan-return Record[] + */ + protected function getMultipleRecords(): array + { + return [ + $this->getRecord(Logger::DEBUG, 'debug message 1'), + $this->getRecord(Logger::DEBUG, 'debug message 2'), + $this->getRecord(Logger::INFO, 'information'), + $this->getRecord(Logger::WARNING, 'warning'), + $this->getRecord(Logger::ERROR, 'error'), + ]; + } + + protected function getIdentityFormatter(): FormatterInterface + { + $formatter = $this->createMock(FormatterInterface::class); + $formatter->expects($this->any()) + ->method('format') + ->will($this->returnCallback(function ($record) { + return $record['message']; + })); + + return $formatter; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Utils.php b/vendor/monolog/monolog/src/Monolog/Utils.php new file mode 100644 index 0000000..d3e7ad0 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Utils.php @@ -0,0 +1,263 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +final class Utils +{ + const DEFAULT_JSON_FLAGS = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRESERVE_ZERO_FRACTION | JSON_INVALID_UTF8_SUBSTITUTE | JSON_PARTIAL_OUTPUT_ON_ERROR; + + public static function getClass(object $object): string + { + $class = \get_class($object); + + if (false === ($pos = \strpos($class, "@anonymous\0"))) { + return $class; + } + + if (false === ($parent = \get_parent_class($class))) { + return \substr($class, 0, $pos + 10); + } + + return $parent . '@anonymous'; + } + + public static function substr(string $string, int $start, ?int $length = null): string + { + if (extension_loaded('mbstring')) { + return mb_strcut($string, $start, $length); + } + + return substr($string, $start, (null === $length) ? strlen($string) : $length); + } + + /** + * Makes sure if a relative path is passed in it is turned into an absolute path + * + * @param string $streamUrl stream URL or path without protocol + */ + public static function canonicalizePath(string $streamUrl): string + { + $prefix = ''; + if ('file://' === substr($streamUrl, 0, 7)) { + $streamUrl = substr($streamUrl, 7); + $prefix = 'file://'; + } + + // other type of stream, not supported + if (false !== strpos($streamUrl, '://')) { + return $streamUrl; + } + + // already absolute + if (substr($streamUrl, 0, 1) === '/' || substr($streamUrl, 1, 1) === ':' || substr($streamUrl, 0, 2) === '\\\\') { + return $prefix.$streamUrl; + } + + $streamUrl = getcwd() . '/' . $streamUrl; + + return $prefix.$streamUrl; + } + + /** + * Return the JSON representation of a value + * + * @param mixed $data + * @param int $encodeFlags flags to pass to json encode, defaults to DEFAULT_JSON_FLAGS + * @param bool $ignoreErrors whether to ignore encoding errors or to throw on error, when ignored and the encoding fails, "null" is returned which is valid json for null + * @throws \RuntimeException if encoding fails and errors are not ignored + * @return string when errors are ignored and the encoding fails, "null" is returned which is valid json for null + */ + public static function jsonEncode($data, ?int $encodeFlags = null, bool $ignoreErrors = false): string + { + if (null === $encodeFlags) { + $encodeFlags = self::DEFAULT_JSON_FLAGS; + } + + if ($ignoreErrors) { + $json = @json_encode($data, $encodeFlags); + if (false === $json) { + return 'null'; + } + + return $json; + } + + $json = json_encode($data, $encodeFlags); + if (false === $json) { + $json = self::handleJsonError(json_last_error(), $data); + } + + return $json; + } + + /** + * Handle a json_encode failure. + * + * If the failure is due to invalid string encoding, try to clean the + * input and encode again. If the second encoding attempt fails, the + * initial error is not encoding related or the input can't be cleaned then + * raise a descriptive exception. + * + * @param int $code return code of json_last_error function + * @param mixed $data data that was meant to be encoded + * @param int $encodeFlags flags to pass to json encode, defaults to JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRESERVE_ZERO_FRACTION + * @throws \RuntimeException if failure can't be corrected + * @return string JSON encoded data after error correction + */ + public static function handleJsonError(int $code, $data, ?int $encodeFlags = null): string + { + if ($code !== JSON_ERROR_UTF8) { + self::throwEncodeError($code, $data); + } + + if (is_string($data)) { + self::detectAndCleanUtf8($data); + } elseif (is_array($data)) { + array_walk_recursive($data, array('Monolog\Utils', 'detectAndCleanUtf8')); + } else { + self::throwEncodeError($code, $data); + } + + if (null === $encodeFlags) { + $encodeFlags = self::DEFAULT_JSON_FLAGS; + } + + $json = json_encode($data, $encodeFlags); + + if ($json === false) { + self::throwEncodeError(json_last_error(), $data); + } + + return $json; + } + + /** + * @internal + */ + public static function pcreLastErrorMessage(int $code): string + { + if (PHP_VERSION_ID >= 80000) { + return preg_last_error_msg(); + } + + $constants = (get_defined_constants(true))['pcre']; + $constants = array_filter($constants, function ($key) { + return substr($key, -6) == '_ERROR'; + }, ARRAY_FILTER_USE_KEY); + + $constants = array_flip($constants); + + return $constants[$code] ?? 'UNDEFINED_ERROR'; + } + + /** + * Throws an exception according to a given code with a customized message + * + * @param int $code return code of json_last_error function + * @param mixed $data data that was meant to be encoded + * @throws \RuntimeException + * + * @return never + */ + private static function throwEncodeError(int $code, $data): void + { + switch ($code) { + case JSON_ERROR_DEPTH: + $msg = 'Maximum stack depth exceeded'; + break; + case JSON_ERROR_STATE_MISMATCH: + $msg = 'Underflow or the modes mismatch'; + break; + case JSON_ERROR_CTRL_CHAR: + $msg = 'Unexpected control character found'; + break; + case JSON_ERROR_UTF8: + $msg = 'Malformed UTF-8 characters, possibly incorrectly encoded'; + break; + default: + $msg = 'Unknown error'; + } + + throw new \RuntimeException('JSON encoding failed: '.$msg.'. Encoding: '.var_export($data, true)); + } + + /** + * Detect invalid UTF-8 string characters and convert to valid UTF-8. + * + * Valid UTF-8 input will be left unmodified, but strings containing + * invalid UTF-8 codepoints will be reencoded as UTF-8 with an assumed + * original encoding of ISO-8859-15. This conversion may result in + * incorrect output if the actual encoding was not ISO-8859-15, but it + * will be clean UTF-8 output and will not rely on expensive and fragile + * detection algorithms. + * + * Function converts the input in place in the passed variable so that it + * can be used as a callback for array_walk_recursive. + * + * @param mixed $data Input to check and convert if needed, passed by ref + */ + private static function detectAndCleanUtf8(&$data): void + { + if (is_string($data) && !preg_match('//u', $data)) { + $data = preg_replace_callback( + '/[\x80-\xFF]+/', + function ($m) { + return utf8_encode($m[0]); + }, + $data + ); + if (!is_string($data)) { + $pcreErrorCode = preg_last_error(); + throw new \RuntimeException('Failed to preg_replace_callback: ' . $pcreErrorCode . ' / ' . self::pcreLastErrorMessage($pcreErrorCode)); + } + $data = str_replace( + ['¤', '¦', '¨', '´', '¸', '¼', '½', '¾'], + ['€', 'Š', 'š', 'Ž', 'ž', 'Œ', 'œ', 'Ÿ'], + $data + ); + } + } + + /** + * Converts a string with a valid 'memory_limit' format, to bytes. + * + * @param string|false $val + * @return int|false Returns an integer representing bytes. Returns FALSE in case of error. + */ + public static function expandIniShorthandBytes($val) + { + if (!is_string($val)) { + return false; + } + + // support -1 + if ((int) $val < 0) { + return (int) $val; + } + + if (!preg_match('/^\s*(?\d+)(?:\.\d+)?\s*(?[gmk]?)\s*$/i', $val, $match)) { + return false; + } + + $val = (int) $match['val']; + switch (strtolower($match['unit'] ?? '')) { + case 'g': + $val *= 1024; + case 'm': + $val *= 1024; + case 'k': + $val *= 1024; + } + + return $val; + } +} diff --git a/vendor/myclabs/php-enum/LICENSE b/vendor/myclabs/php-enum/LICENSE new file mode 100644 index 0000000..2a8cf22 --- /dev/null +++ b/vendor/myclabs/php-enum/LICENSE @@ -0,0 +1,18 @@ +The MIT License (MIT) + +Copyright (c) 2015 My C-Labs + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/myclabs/php-enum/README.md b/vendor/myclabs/php-enum/README.md new file mode 100644 index 0000000..1e4d1ff --- /dev/null +++ b/vendor/myclabs/php-enum/README.md @@ -0,0 +1,138 @@ +# PHP Enum implementation inspired from SplEnum + +[![Build Status](https://travis-ci.org/myclabs/php-enum.png?branch=master)](https://travis-ci.org/myclabs/php-enum) +[![Latest Stable Version](https://poser.pugx.org/myclabs/php-enum/version.png)](https://packagist.org/packages/myclabs/php-enum) +[![Total Downloads](https://poser.pugx.org/myclabs/php-enum/downloads.png)](https://packagist.org/packages/myclabs/php-enum) +[![psalm](https://shepherd.dev/github/myclabs/php-enum/coverage.svg)](https://shepherd.dev/github/myclabs/php-enum) + +Maintenance for this project is [supported via Tidelift](https://tidelift.com/subscription/pkg/packagist-myclabs-php-enum?utm_source=packagist-myclabs-php-enum&utm_medium=referral&utm_campaign=readme). + +## Why? + +First, and mainly, `SplEnum` is not integrated to PHP, you have to install the extension separately. + +Using an enum instead of class constants provides the following advantages: + +- You can use an enum as a parameter type: `function setAction(Action $action) {` +- You can use an enum as a return type: `function getAction() : Action {` +- You can enrich the enum with methods (e.g. `format`, `parse`, …) +- You can extend the enum to add new values (make your enum `final` to prevent it) +- You can get a list of all the possible values (see below) + +This Enum class is not intended to replace class constants, but only to be used when it makes sense. + +## Installation + +``` +composer require myclabs/php-enum +``` + +## Declaration + +```php +use MyCLabs\Enum\Enum; + +/** + * Action enum + */ +final class Action extends Enum +{ + private const VIEW = 'view'; + private const EDIT = 'edit'; +} +``` + +## Usage + +```php +$action = Action::VIEW(); + +// or with a dynamic key: +$action = Action::$key(); +// or with a dynamic value: +$action = Action::from($value); +// or +$action = new Action($value); +``` + +As you can see, static methods are automatically implemented to provide quick access to an enum value. + +One advantage over using class constants is to be able to use an enum as a parameter type: + +```php +function setAction(Action $action) { + // ... +} +``` + +## Documentation + +- `__construct()` The constructor checks that the value exist in the enum +- `__toString()` You can `echo $myValue`, it will display the enum value (value of the constant) +- `getValue()` Returns the current value of the enum +- `getKey()` Returns the key of the current value on Enum +- `equals()` Tests whether enum instances are equal (returns `true` if enum values are equal, `false` otherwise) + +Static methods: + +- `from()` Creates an Enum instance, checking that the value exist in the enum +- `toArray()` method Returns all possible values as an array (constant name in key, constant value in value) +- `keys()` Returns the names (keys) of all constants in the Enum class +- `values()` Returns instances of the Enum class of all Enum constants (constant name in key, Enum instance in value) +- `isValid()` Check if tested value is valid on enum set +- `isValidKey()` Check if tested key is valid on enum set +- `assertValidValue()` Assert the value is valid on enum set, throwing exception otherwise +- `search()` Return key for searched value + +### Static methods + +```php +final class Action extends Enum +{ + private const VIEW = 'view'; + private const EDIT = 'edit'; +} + +// Static method: +$action = Action::VIEW(); +$action = Action::EDIT(); +``` + +Static method helpers are implemented using [`__callStatic()`](http://www.php.net/manual/en/language.oop5.overloading.php#object.callstatic). + +If you care about IDE autocompletion, you can either implement the static methods yourself: + +```php +final class Action extends Enum +{ + private const VIEW = 'view'; + + /** + * @return Action + */ + public static function VIEW() { + return new Action(self::VIEW); + } +} +``` + +or you can use phpdoc (this is supported in PhpStorm for example): + +```php +/** + * @method static Action VIEW() + * @method static Action EDIT() + */ +final class Action extends Enum +{ + private const VIEW = 'view'; + private const EDIT = 'edit'; +} +``` + +## Related projects + +- [Doctrine enum mapping](https://github.com/acelaya/doctrine-enum-type) +- [Symfony ParamConverter integration](https://github.com/Ex3v/MyCLabsEnumParamConverter) +- [PHPStan integration](https://github.com/timeweb/phpstan-enum) +- [Yii2 enum mapping](https://github.com/KartaviK/yii2-enum) diff --git a/vendor/myclabs/php-enum/SECURITY.md b/vendor/myclabs/php-enum/SECURITY.md new file mode 100644 index 0000000..84fd4e3 --- /dev/null +++ b/vendor/myclabs/php-enum/SECURITY.md @@ -0,0 +1,11 @@ +# Security Policy + +## Supported Versions + +Only the latest stable release is supported. + +## Reporting a Vulnerability + +To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). + +Tidelift will coordinate the fix and disclosure. diff --git a/vendor/myclabs/php-enum/composer.json b/vendor/myclabs/php-enum/composer.json new file mode 100644 index 0000000..924f924 --- /dev/null +++ b/vendor/myclabs/php-enum/composer.json @@ -0,0 +1,33 @@ +{ + "name": "myclabs/php-enum", + "type": "library", + "description": "PHP Enum implementation", + "keywords": ["enum"], + "homepage": "http://github.com/myclabs/php-enum", + "license": "MIT", + "authors": [ + { + "name": "PHP Enum contributors", + "homepage": "https://github.com/myclabs/php-enum/graphs/contributors" + } + ], + "autoload": { + "psr-4": { + "MyCLabs\\Enum\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "MyCLabs\\Tests\\Enum\\": "tests/" + } + }, + "require": { + "php": "^7.3 || ^8.0", + "ext-json": "*" + }, + "require-dev": { + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "1.*", + "vimeo/psalm": "^4.6.2" + } +} diff --git a/vendor/myclabs/php-enum/psalm.xml b/vendor/myclabs/php-enum/psalm.xml new file mode 100644 index 0000000..ff06b66 --- /dev/null +++ b/vendor/myclabs/php-enum/psalm.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/myclabs/php-enum/src/Enum.php b/vendor/myclabs/php-enum/src/Enum.php new file mode 100644 index 0000000..89064eb --- /dev/null +++ b/vendor/myclabs/php-enum/src/Enum.php @@ -0,0 +1,318 @@ + + * @author Daniel Costa + * @author Mirosław Filip + * + * @psalm-template T + * @psalm-immutable + * @psalm-consistent-constructor + */ +abstract class Enum implements \JsonSerializable +{ + /** + * Enum value + * + * @var mixed + * @psalm-var T + */ + protected $value; + + /** + * Enum key, the constant name + * + * @var string + */ + private $key; + + /** + * Store existing constants in a static cache per object. + * + * + * @var array + * @psalm-var array> + */ + protected static $cache = []; + + /** + * Cache of instances of the Enum class + * + * @var array + * @psalm-var array> + */ + protected static $instances = []; + + /** + * Creates a new value of some type + * + * @psalm-pure + * @param mixed $value + * + * @psalm-param T $value + * @throws \UnexpectedValueException if incompatible type is given. + */ + public function __construct($value) + { + if ($value instanceof static) { + /** @psalm-var T */ + $value = $value->getValue(); + } + + /** @psalm-suppress ImplicitToStringCast assertValidValueReturningKey returns always a string but psalm has currently an issue here */ + $this->key = static::assertValidValueReturningKey($value); + + /** @psalm-var T */ + $this->value = $value; + } + + /** + * This method exists only for the compatibility reason when deserializing a previously serialized version + * that didn't had the key property + */ + public function __wakeup() + { + /** @psalm-suppress DocblockTypeContradiction key can be null when deserializing an enum without the key */ + if ($this->key === null) { + /** + * @psalm-suppress InaccessibleProperty key is not readonly as marked by psalm + * @psalm-suppress PossiblyFalsePropertyAssignmentValue deserializing a case that was removed + */ + $this->key = static::search($this->value); + } + } + + /** + * @param mixed $value + * @return static + */ + public static function from($value): self + { + $key = static::assertValidValueReturningKey($value); + + return self::__callStatic($key, []); + } + + /** + * @psalm-pure + * @return mixed + * @psalm-return T + */ + public function getValue() + { + return $this->value; + } + + /** + * Returns the enum key (i.e. the constant name). + * + * @psalm-pure + * @return string + */ + public function getKey() + { + return $this->key; + } + + /** + * @psalm-pure + * @psalm-suppress InvalidCast + * @return string + */ + public function __toString() + { + return (string)$this->value; + } + + /** + * Determines if Enum should be considered equal with the variable passed as a parameter. + * Returns false if an argument is an object of different class or not an object. + * + * This method is final, for more information read https://github.com/myclabs/php-enum/issues/4 + * + * @psalm-pure + * @psalm-param mixed $variable + * @return bool + */ + final public function equals($variable = null): bool + { + return $variable instanceof self + && $this->getValue() === $variable->getValue() + && static::class === \get_class($variable); + } + + /** + * Returns the names (keys) of all constants in the Enum class + * + * @psalm-pure + * @psalm-return list + * @return array + */ + public static function keys() + { + return \array_keys(static::toArray()); + } + + /** + * Returns instances of the Enum class of all Enum constants + * + * @psalm-pure + * @psalm-return array + * @return static[] Constant name in key, Enum instance in value + */ + public static function values() + { + $values = array(); + + /** @psalm-var T $value */ + foreach (static::toArray() as $key => $value) { + $values[$key] = new static($value); + } + + return $values; + } + + /** + * Returns all possible values as an array + * + * @psalm-pure + * @psalm-suppress ImpureStaticProperty + * + * @psalm-return array + * @return array Constant name in key, constant value in value + */ + public static function toArray() + { + $class = static::class; + + if (!isset(static::$cache[$class])) { + /** @psalm-suppress ImpureMethodCall this reflection API usage has no side-effects here */ + $reflection = new \ReflectionClass($class); + /** @psalm-suppress ImpureMethodCall this reflection API usage has no side-effects here */ + static::$cache[$class] = $reflection->getConstants(); + } + + return static::$cache[$class]; + } + + /** + * Check if is valid enum value + * + * @param $value + * @psalm-param mixed $value + * @psalm-pure + * @psalm-assert-if-true T $value + * @return bool + */ + public static function isValid($value) + { + return \in_array($value, static::toArray(), true); + } + + /** + * Asserts valid enum value + * + * @psalm-pure + * @psalm-assert T $value + * @param mixed $value + */ + public static function assertValidValue($value): void + { + self::assertValidValueReturningKey($value); + } + + /** + * Asserts valid enum value + * + * @psalm-pure + * @psalm-assert T $value + * @param mixed $value + * @return string + */ + private static function assertValidValueReturningKey($value): string + { + if (false === ($key = static::search($value))) { + throw new \UnexpectedValueException("Value '$value' is not part of the enum " . static::class); + } + + return $key; + } + + /** + * Check if is valid enum key + * + * @param $key + * @psalm-param string $key + * @psalm-pure + * @return bool + */ + public static function isValidKey($key) + { + $array = static::toArray(); + + return isset($array[$key]) || \array_key_exists($key, $array); + } + + /** + * Return key for value + * + * @param mixed $value + * + * @psalm-param mixed $value + * @psalm-pure + * @return string|false + */ + public static function search($value) + { + return \array_search($value, static::toArray(), true); + } + + /** + * Returns a value when called statically like so: MyEnum::SOME_VALUE() given SOME_VALUE is a class constant + * + * @param string $name + * @param array $arguments + * + * @return static + * @throws \BadMethodCallException + * + * @psalm-pure + */ + public static function __callStatic($name, $arguments) + { + $class = static::class; + if (!isset(self::$instances[$class][$name])) { + $array = static::toArray(); + if (!isset($array[$name]) && !\array_key_exists($name, $array)) { + $message = "No static method or enum constant '$name' in class " . static::class; + throw new \BadMethodCallException($message); + } + return self::$instances[$class][$name] = new static($array[$name]); + } + return clone self::$instances[$class][$name]; + } + + /** + * Specify data which should be serialized to JSON. This method returns data that can be serialized by json_encode() + * natively. + * + * @return mixed + * @link http://php.net/manual/en/jsonserializable.jsonserialize.php + * @psalm-pure + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->getValue(); + } +} diff --git a/vendor/myclabs/php-enum/src/PHPUnit/Comparator.php b/vendor/myclabs/php-enum/src/PHPUnit/Comparator.php new file mode 100644 index 0000000..302bf80 --- /dev/null +++ b/vendor/myclabs/php-enum/src/PHPUnit/Comparator.php @@ -0,0 +1,54 @@ +register(new \MyCLabs\Enum\PHPUnit\Comparator()); + */ +final class Comparator extends \SebastianBergmann\Comparator\Comparator +{ + public function accepts($expected, $actual) + { + return $expected instanceof Enum && ( + $actual instanceof Enum || $actual === null + ); + } + + /** + * @param Enum $expected + * @param Enum|null $actual + * + * @return void + */ + public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false) + { + if ($expected->equals($actual)) { + return; + } + + throw new ComparisonFailure( + $expected, + $actual, + $this->formatEnum($expected), + $this->formatEnum($actual), + false, + 'Failed asserting that two Enums are equal.' + ); + } + + private function formatEnum(Enum $enum = null) + { + if ($enum === null) { + return "null"; + } + + return get_class($enum)."::{$enum->getKey()}()"; + } +} diff --git a/vendor/nesbot/carbon/LICENSE b/vendor/nesbot/carbon/LICENSE new file mode 100644 index 0000000..6de45eb --- /dev/null +++ b/vendor/nesbot/carbon/LICENSE @@ -0,0 +1,19 @@ +Copyright (C) Brian Nesbitt + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/nesbot/carbon/bin/carbon b/vendor/nesbot/carbon/bin/carbon new file mode 100644 index 0000000..b53ab73 --- /dev/null +++ b/vendor/nesbot/carbon/bin/carbon @@ -0,0 +1,23 @@ +#!/usr/bin/env php + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use Symfony\Component\Translation\MessageCatalogueInterface; + +if (!class_exists(LazyTranslator::class, false)) { + class LazyTranslator extends AbstractTranslator implements TranslatorStrongTypeInterface + { + public function trans(?string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string + { + return $this->translate($id, $parameters, $domain, $locale); + } + + public function getFromCatalogue(MessageCatalogueInterface $catalogue, string $id, string $domain = 'messages') + { + $messages = $this->getPrivateProperty($catalogue, 'messages'); + + if (isset($messages[$domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX][$id])) { + return $messages[$domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX][$id]; + } + + if (isset($messages[$domain][$id])) { + return $messages[$domain][$id]; + } + + $fallbackCatalogue = $this->getPrivateProperty($catalogue, 'fallbackCatalogue'); + + if ($fallbackCatalogue !== null) { + return $this->getFromCatalogue($fallbackCatalogue, $id, $domain); + } + + return $id; + } + + private function getPrivateProperty($instance, string $field) + { + return (function (string $field) { + return $this->$field; + })->call($instance, $field); + } + } +} diff --git a/vendor/nesbot/carbon/lazy/Carbon/TranslatorWeakType.php b/vendor/nesbot/carbon/lazy/Carbon/TranslatorWeakType.php new file mode 100644 index 0000000..94dbdc3 --- /dev/null +++ b/vendor/nesbot/carbon/lazy/Carbon/TranslatorWeakType.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +if (!class_exists(LazyTranslator::class, false)) { + class LazyTranslator extends AbstractTranslator + { + /** + * Returns the translation. + * + * @param string|null $id + * @param array $parameters + * @param string|null $domain + * @param string|null $locale + * + * @return string + */ + public function trans($id, array $parameters = [], $domain = null, $locale = null) + { + return $this->translate($id, $parameters, $domain, $locale); + } + } +} diff --git a/vendor/nesbot/carbon/readme.md b/vendor/nesbot/carbon/readme.md new file mode 100644 index 0000000..5d82721 --- /dev/null +++ b/vendor/nesbot/carbon/readme.md @@ -0,0 +1,144 @@ +# Carbon + +[![Latest Stable Version](https://img.shields.io/packagist/v/nesbot/carbon.svg?style=flat-square)](https://packagist.org/packages/nesbot/carbon) +[![Total Downloads](https://img.shields.io/packagist/dt/nesbot/carbon.svg?style=flat-square)](https://packagist.org/packages/nesbot/carbon) +[![GitHub Actions](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Fbriannesbitt%2FCarbon%2Fbadge&style=flat-square&label=Build&logo=none)](https://actions-badge.atrox.dev/briannesbitt/Carbon/goto) +[![codecov.io](https://img.shields.io/codecov/c/github/briannesbitt/Carbon.svg?style=flat-square)](https://codecov.io/github/briannesbitt/Carbon?branch=master) +[![Tidelift](https://tidelift.com/badges/github/briannesbitt/Carbon)](https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme) + +An international PHP extension for DateTime. [https://carbon.nesbot.com](https://carbon.nesbot.com) + +```php +toDateTimeString()); +printf("Right now in Vancouver is %s", Carbon::now('America/Vancouver')); //implicit __toString() +$tomorrow = Carbon::now()->addDay(); +$lastWeek = Carbon::now()->subWeek(); +$nextSummerOlympics = Carbon::createFromDate(2016)->addYears(4); + +$officialDate = Carbon::now()->toRfc2822String(); + +$howOldAmI = Carbon::createFromDate(1975, 5, 21)->age; + +$noonTodayLondonTime = Carbon::createFromTime(12, 0, 0, 'Europe/London'); + +$internetWillBlowUpOn = Carbon::create(2038, 01, 19, 3, 14, 7, 'GMT'); + +// Don't really want this to happen so mock now +Carbon::setTestNow(Carbon::createFromDate(2000, 1, 1)); + +// comparisons are always done in UTC +if (Carbon::now()->gte($internetWillBlowUpOn)) { + die(); +} + +// Phew! Return to normal behaviour +Carbon::setTestNow(); + +if (Carbon::now()->isWeekend()) { + echo 'Party!'; +} +// Over 200 languages (and over 500 regional variants) supported: +echo Carbon::now()->subMinutes(2)->diffForHumans(); // '2 minutes ago' +echo Carbon::now()->subMinutes(2)->locale('zh_CN')->diffForHumans(); // '2分钟前' +echo Carbon::parse('2019-07-23 14:51')->isoFormat('LLLL'); // 'Tuesday, July 23, 2019 2:51 PM' +echo Carbon::parse('2019-07-23 14:51')->locale('fr_FR')->isoFormat('LLLL'); // 'mardi 23 juillet 2019 14:51' + +// ... but also does 'from now', 'after' and 'before' +// rolling up to seconds, minutes, hours, days, months, years + +$daysSinceEpoch = Carbon::createFromTimestamp(0)->diffInDays(); +``` + +[Get supported nesbot/carbon with the Tidelift Subscription](https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme) + +## Installation + +### With Composer + +``` +$ composer require nesbot/carbon +``` + +```json +{ + "require": { + "nesbot/carbon": "^2.16" + } +} +``` + +```php + + +### Translators + +[Thanks to people helping us to translate Carbon in so many languages](https://carbon.nesbot.com/contribute/translators/) + +### Sponsors + +Support this project by becoming a sponsor. Your logo will show up here with a link to your website. + + + + + + + + + +[[Become a sponsor](https://opencollective.com/Carbon#sponsor)] + +### Backers + +Thank you to all our backers! 🙏 + + + +[[Become a backer](https://opencollective.com/Carbon#backer)] + +## Carbon for enterprise + +Available as part of the Tidelift Subscription. + +The maintainers of ``Carbon`` and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) diff --git a/vendor/nesbot/carbon/src/Carbon/AbstractTranslator.php b/vendor/nesbot/carbon/src/Carbon/AbstractTranslator.php new file mode 100644 index 0000000..555e07a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/AbstractTranslator.php @@ -0,0 +1,400 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use Closure; +use ReflectionException; +use ReflectionFunction; +use Symfony\Component\Translation; +use Symfony\Component\Translation\Formatter\MessageFormatterInterface; +use Symfony\Component\Translation\Loader\ArrayLoader; + +abstract class AbstractTranslator extends Translation\Translator +{ + /** + * Translator singletons for each language. + * + * @var array + */ + protected static $singletons = []; + + /** + * List of custom localized messages. + * + * @var array + */ + protected $messages = []; + + /** + * List of custom directories that contain translation files. + * + * @var string[] + */ + protected $directories = []; + + /** + * Set to true while constructing. + * + * @var bool + */ + protected $initializing = false; + + /** + * List of locales aliases. + * + * @var string[] + */ + protected $aliases = [ + 'me' => 'sr_Latn_ME', + 'scr' => 'sh', + ]; + + /** + * Return a singleton instance of Translator. + * + * @param string|null $locale optional initial locale ("en" - english by default) + * + * @return static + */ + public static function get($locale = null) + { + $locale = $locale ?: 'en'; + + if (!isset(static::$singletons[$locale])) { + static::$singletons[$locale] = new static($locale); + } + + return static::$singletons[$locale]; + } + + public function __construct($locale, MessageFormatterInterface $formatter = null, $cacheDir = null, $debug = false) + { + parent::setLocale($locale); + $this->initializing = true; + $this->directories = [__DIR__.'/Lang']; + $this->addLoader('array', new ArrayLoader()); + parent::__construct($locale, $formatter, $cacheDir, $debug); + $this->initializing = false; + } + + /** + * Returns the list of directories translation files are searched in. + * + * @return array + */ + public function getDirectories(): array + { + return $this->directories; + } + + /** + * Set list of directories translation files are searched in. + * + * @param array $directories new directories list + * + * @return $this + */ + public function setDirectories(array $directories) + { + $this->directories = $directories; + + return $this; + } + + /** + * Add a directory to the list translation files are searched in. + * + * @param string $directory new directory + * + * @return $this + */ + public function addDirectory(string $directory) + { + $this->directories[] = $directory; + + return $this; + } + + /** + * Remove a directory from the list translation files are searched in. + * + * @param string $directory directory path + * + * @return $this + */ + public function removeDirectory(string $directory) + { + $search = rtrim(strtr($directory, '\\', '/'), '/'); + + return $this->setDirectories(array_filter($this->getDirectories(), function ($item) use ($search) { + return rtrim(strtr($item, '\\', '/'), '/') !== $search; + })); + } + + /** + * Reset messages of a locale (all locale if no locale passed). + * Remove custom messages and reload initial messages from matching + * file in Lang directory. + * + * @param string|null $locale + * + * @return bool + */ + public function resetMessages($locale = null) + { + if ($locale === null) { + $this->messages = []; + + return true; + } + + foreach ($this->getDirectories() as $directory) { + $data = @include sprintf('%s/%s.php', rtrim($directory, '\\/'), $locale); + + if ($data !== false) { + $this->messages[$locale] = $data; + $this->addResource('array', $this->messages[$locale], $locale); + + return true; + } + } + + return false; + } + + /** + * Returns the list of files matching a given locale prefix (or all if empty). + * + * @param string $prefix prefix required to filter result + * + * @return array + */ + public function getLocalesFiles($prefix = '') + { + $files = []; + + foreach ($this->getDirectories() as $directory) { + $directory = rtrim($directory, '\\/'); + + foreach (glob("$directory/$prefix*.php") as $file) { + $files[] = $file; + } + } + + return array_unique($files); + } + + /** + * Returns the list of internally available locales and already loaded custom locales. + * (It will ignore custom translator dynamic loading.) + * + * @param string $prefix prefix required to filter result + * + * @return array + */ + public function getAvailableLocales($prefix = '') + { + $locales = []; + foreach ($this->getLocalesFiles($prefix) as $file) { + $locales[] = substr($file, strrpos($file, '/') + 1, -4); + } + + return array_unique(array_merge($locales, array_keys($this->messages))); + } + + protected function translate(?string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string + { + if ($domain === null) { + $domain = 'messages'; + } + + $catalogue = $this->getCatalogue($locale); + $format = $this instanceof TranslatorStrongTypeInterface + ? $this->getFromCatalogue($catalogue, (string) $id, $domain) // @codeCoverageIgnore + : $this->getCatalogue($locale)->get((string) $id, $domain); + + if ($format instanceof Closure) { + // @codeCoverageIgnoreStart + try { + $count = (new ReflectionFunction($format))->getNumberOfRequiredParameters(); + } catch (ReflectionException $exception) { + $count = 0; + } + // @codeCoverageIgnoreEnd + + return $format( + ...array_values($parameters), + ...array_fill(0, max(0, $count - \count($parameters)), null) + ); + } + + return parent::trans($id, $parameters, $domain, $locale); + } + + /** + * Init messages language from matching file in Lang directory. + * + * @param string $locale + * + * @return bool + */ + protected function loadMessagesFromFile($locale) + { + if (isset($this->messages[$locale])) { + return true; + } + + return $this->resetMessages($locale); + } + + /** + * Set messages of a locale and take file first if present. + * + * @param string $locale + * @param array $messages + * + * @return $this + */ + public function setMessages($locale, $messages) + { + $this->loadMessagesFromFile($locale); + $this->addResource('array', $messages, $locale); + $this->messages[$locale] = array_merge( + $this->messages[$locale] ?? [], + $messages + ); + + return $this; + } + + /** + * Set messages of the current locale and take file first if present. + * + * @param array $messages + * + * @return $this + */ + public function setTranslations($messages) + { + return $this->setMessages($this->getLocale(), $messages); + } + + /** + * Get messages of a locale, if none given, return all the + * languages. + * + * @param string|null $locale + * + * @return array + */ + public function getMessages($locale = null) + { + return $locale === null ? $this->messages : $this->messages[$locale]; + } + + /** + * Set the current translator locale and indicate if the source locale file exists + * + * @param string $locale locale ex. en + * + * @return bool + */ + public function setLocale($locale) + { + $locale = preg_replace_callback('/[-_]([a-z]{2,}|[0-9]{2,})/', function ($matches) { + // _2-letters or YUE is a region, _3+-letters is a variant + $upper = strtoupper($matches[1]); + + if ($upper === 'YUE' || $upper === 'ISO' || \strlen($upper) < 3) { + return "_$upper"; + } + + return '_'.ucfirst($matches[1]); + }, strtolower($locale)); + + $previousLocale = $this->getLocale(); + + if ($previousLocale === $locale && isset($this->messages[$locale])) { + return true; + } + + unset(static::$singletons[$previousLocale]); + + if ($locale === 'auto') { + $completeLocale = setlocale(LC_TIME, '0'); + $locale = preg_replace('/^([^_.-]+).*$/', '$1', $completeLocale); + $locales = $this->getAvailableLocales($locale); + + $completeLocaleChunks = preg_split('/[_.-]+/', $completeLocale); + + $getScore = function ($language) use ($completeLocaleChunks) { + return static::compareChunkLists($completeLocaleChunks, preg_split('/[_.-]+/', $language)); + }; + + usort($locales, function ($first, $second) use ($getScore) { + return $getScore($second) <=> $getScore($first); + }); + + $locale = $locales[0]; + } + + if (isset($this->aliases[$locale])) { + $locale = $this->aliases[$locale]; + } + + // If subtag (ex: en_CA) first load the macro (ex: en) to have a fallback + if (str_contains($locale, '_') && + $this->loadMessagesFromFile($macroLocale = preg_replace('/^([^_]+).*$/', '$1', $locale)) + ) { + parent::setLocale($macroLocale); + } + + if ($this->loadMessagesFromFile($locale) || $this->initializing) { + parent::setLocale($locale); + + return true; + } + + return false; + } + + /** + * Show locale on var_dump(). + * + * @return array + */ + public function __debugInfo() + { + return [ + 'locale' => $this->getLocale(), + ]; + } + + private static function compareChunkLists($referenceChunks, $chunks) + { + $score = 0; + + foreach ($referenceChunks as $index => $chunk) { + if (!isset($chunks[$index])) { + $score++; + + continue; + } + + if (strtolower($chunks[$index]) === strtolower($chunk)) { + $score += 10; + } + } + + return $score; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Carbon.php b/vendor/nesbot/carbon/src/Carbon/Carbon.php new file mode 100644 index 0000000..e327590 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Carbon.php @@ -0,0 +1,523 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use Carbon\Traits\Date; +use Carbon\Traits\DeprecatedProperties; +use DateTime; +use DateTimeInterface; +use DateTimeZone; + +/** + * A simple API extension for DateTime. + * + * @mixin DeprecatedProperties + * + * + * + * @property int $year + * @property int $yearIso + * @property int $month + * @property int $day + * @property int $hour + * @property int $minute + * @property int $second + * @property int $micro + * @property int $microsecond + * @property int|float|string $timestamp seconds since the Unix Epoch + * @property string $englishDayOfWeek the day of week in English + * @property string $shortEnglishDayOfWeek the abbreviated day of week in English + * @property string $englishMonth the month in English + * @property string $shortEnglishMonth the abbreviated month in English + * @property int $milliseconds + * @property int $millisecond + * @property int $milli + * @property int $week 1 through 53 + * @property int $isoWeek 1 through 53 + * @property int $weekYear year according to week format + * @property int $isoWeekYear year according to ISO week format + * @property int $dayOfYear 1 through 366 + * @property int $age does a diffInYears() with default parameters + * @property int $offset the timezone offset in seconds from UTC + * @property int $offsetMinutes the timezone offset in minutes from UTC + * @property int $offsetHours the timezone offset in hours from UTC + * @property CarbonTimeZone $timezone the current timezone + * @property CarbonTimeZone $tz alias of $timezone + * @property-read int $dayOfWeek 0 (for Sunday) through 6 (for Saturday) + * @property-read int $dayOfWeekIso 1 (for Monday) through 7 (for Sunday) + * @property-read int $weekOfYear ISO-8601 week number of year, weeks starting on Monday + * @property-read int $daysInMonth number of days in the given month + * @property-read string $latinMeridiem "am"/"pm" (Ante meridiem or Post meridiem latin lowercase mark) + * @property-read string $latinUpperMeridiem "AM"/"PM" (Ante meridiem or Post meridiem latin uppercase mark) + * @property-read string $timezoneAbbreviatedName the current timezone abbreviated name + * @property-read string $tzAbbrName alias of $timezoneAbbreviatedName + * @property-read string $dayName long name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $shortDayName short name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $minDayName very short name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $monthName long name of month translated according to Carbon locale, in english if no translation available for current language + * @property-read string $shortMonthName short name of month translated according to Carbon locale, in english if no translation available for current language + * @property-read string $meridiem lowercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language + * @property-read string $upperMeridiem uppercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language + * @property-read int $noZeroHour current hour from 1 to 24 + * @property-read int $weeksInYear 51 through 53 + * @property-read int $isoWeeksInYear 51 through 53 + * @property-read int $weekOfMonth 1 through 5 + * @property-read int $weekNumberInMonth 1 through 5 + * @property-read int $firstWeekDay 0 through 6 + * @property-read int $lastWeekDay 0 through 6 + * @property-read int $daysInYear 365 or 366 + * @property-read int $quarter the quarter of this instance, 1 - 4 + * @property-read int $decade the decade of this instance + * @property-read int $century the century of this instance + * @property-read int $millennium the millennium of this instance + * @property-read bool $dst daylight savings time indicator, true if DST, false otherwise + * @property-read bool $local checks if the timezone is local, true if local, false otherwise + * @property-read bool $utc checks if the timezone is UTC, true if UTC, false otherwise + * @property-read string $timezoneName the current timezone name + * @property-read string $tzName alias of $timezoneName + * @property-read string $locale locale of the current instance + * + * @method bool isUtc() Check if the current instance has UTC timezone. (Both isUtc and isUTC cases are valid.) + * @method bool isLocal() Check if the current instance has non-UTC timezone. + * @method bool isValid() Check if the current instance is a valid date. + * @method bool isDST() Check if the current instance is in a daylight saving time. + * @method bool isSunday() Checks if the instance day is sunday. + * @method bool isMonday() Checks if the instance day is monday. + * @method bool isTuesday() Checks if the instance day is tuesday. + * @method bool isWednesday() Checks if the instance day is wednesday. + * @method bool isThursday() Checks if the instance day is thursday. + * @method bool isFriday() Checks if the instance day is friday. + * @method bool isSaturday() Checks if the instance day is saturday. + * @method bool isSameYear(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same year as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentYear() Checks if the instance is in the same year as the current moment. + * @method bool isNextYear() Checks if the instance is in the same year as the current moment next year. + * @method bool isLastYear() Checks if the instance is in the same year as the current moment last year. + * @method bool isSameWeek(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same week as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentWeek() Checks if the instance is in the same week as the current moment. + * @method bool isNextWeek() Checks if the instance is in the same week as the current moment next week. + * @method bool isLastWeek() Checks if the instance is in the same week as the current moment last week. + * @method bool isSameDay(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same day as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentDay() Checks if the instance is in the same day as the current moment. + * @method bool isNextDay() Checks if the instance is in the same day as the current moment next day. + * @method bool isLastDay() Checks if the instance is in the same day as the current moment last day. + * @method bool isSameHour(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same hour as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentHour() Checks if the instance is in the same hour as the current moment. + * @method bool isNextHour() Checks if the instance is in the same hour as the current moment next hour. + * @method bool isLastHour() Checks if the instance is in the same hour as the current moment last hour. + * @method bool isSameMinute(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same minute as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMinute() Checks if the instance is in the same minute as the current moment. + * @method bool isNextMinute() Checks if the instance is in the same minute as the current moment next minute. + * @method bool isLastMinute() Checks if the instance is in the same minute as the current moment last minute. + * @method bool isSameSecond(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same second as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentSecond() Checks if the instance is in the same second as the current moment. + * @method bool isNextSecond() Checks if the instance is in the same second as the current moment next second. + * @method bool isLastSecond() Checks if the instance is in the same second as the current moment last second. + * @method bool isSameMicro(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMicro() Checks if the instance is in the same microsecond as the current moment. + * @method bool isNextMicro() Checks if the instance is in the same microsecond as the current moment next microsecond. + * @method bool isLastMicro() Checks if the instance is in the same microsecond as the current moment last microsecond. + * @method bool isSameMicrosecond(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMicrosecond() Checks if the instance is in the same microsecond as the current moment. + * @method bool isNextMicrosecond() Checks if the instance is in the same microsecond as the current moment next microsecond. + * @method bool isLastMicrosecond() Checks if the instance is in the same microsecond as the current moment last microsecond. + * @method bool isCurrentMonth() Checks if the instance is in the same month as the current moment. + * @method bool isNextMonth() Checks if the instance is in the same month as the current moment next month. + * @method bool isLastMonth() Checks if the instance is in the same month as the current moment last month. + * @method bool isCurrentQuarter() Checks if the instance is in the same quarter as the current moment. + * @method bool isNextQuarter() Checks if the instance is in the same quarter as the current moment next quarter. + * @method bool isLastQuarter() Checks if the instance is in the same quarter as the current moment last quarter. + * @method bool isSameDecade(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same decade as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentDecade() Checks if the instance is in the same decade as the current moment. + * @method bool isNextDecade() Checks if the instance is in the same decade as the current moment next decade. + * @method bool isLastDecade() Checks if the instance is in the same decade as the current moment last decade. + * @method bool isSameCentury(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same century as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentCentury() Checks if the instance is in the same century as the current moment. + * @method bool isNextCentury() Checks if the instance is in the same century as the current moment next century. + * @method bool isLastCentury() Checks if the instance is in the same century as the current moment last century. + * @method bool isSameMillennium(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same millennium as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMillennium() Checks if the instance is in the same millennium as the current moment. + * @method bool isNextMillennium() Checks if the instance is in the same millennium as the current moment next millennium. + * @method bool isLastMillennium() Checks if the instance is in the same millennium as the current moment last millennium. + * @method $this years(int $value) Set current instance year to the given value. + * @method $this year(int $value) Set current instance year to the given value. + * @method $this setYears(int $value) Set current instance year to the given value. + * @method $this setYear(int $value) Set current instance year to the given value. + * @method $this months(int $value) Set current instance month to the given value. + * @method $this month(int $value) Set current instance month to the given value. + * @method $this setMonths(int $value) Set current instance month to the given value. + * @method $this setMonth(int $value) Set current instance month to the given value. + * @method $this days(int $value) Set current instance day to the given value. + * @method $this day(int $value) Set current instance day to the given value. + * @method $this setDays(int $value) Set current instance day to the given value. + * @method $this setDay(int $value) Set current instance day to the given value. + * @method $this hours(int $value) Set current instance hour to the given value. + * @method $this hour(int $value) Set current instance hour to the given value. + * @method $this setHours(int $value) Set current instance hour to the given value. + * @method $this setHour(int $value) Set current instance hour to the given value. + * @method $this minutes(int $value) Set current instance minute to the given value. + * @method $this minute(int $value) Set current instance minute to the given value. + * @method $this setMinutes(int $value) Set current instance minute to the given value. + * @method $this setMinute(int $value) Set current instance minute to the given value. + * @method $this seconds(int $value) Set current instance second to the given value. + * @method $this second(int $value) Set current instance second to the given value. + * @method $this setSeconds(int $value) Set current instance second to the given value. + * @method $this setSecond(int $value) Set current instance second to the given value. + * @method $this millis(int $value) Set current instance millisecond to the given value. + * @method $this milli(int $value) Set current instance millisecond to the given value. + * @method $this setMillis(int $value) Set current instance millisecond to the given value. + * @method $this setMilli(int $value) Set current instance millisecond to the given value. + * @method $this milliseconds(int $value) Set current instance millisecond to the given value. + * @method $this millisecond(int $value) Set current instance millisecond to the given value. + * @method $this setMilliseconds(int $value) Set current instance millisecond to the given value. + * @method $this setMillisecond(int $value) Set current instance millisecond to the given value. + * @method $this micros(int $value) Set current instance microsecond to the given value. + * @method $this micro(int $value) Set current instance microsecond to the given value. + * @method $this setMicros(int $value) Set current instance microsecond to the given value. + * @method $this setMicro(int $value) Set current instance microsecond to the given value. + * @method $this microseconds(int $value) Set current instance microsecond to the given value. + * @method $this microsecond(int $value) Set current instance microsecond to the given value. + * @method $this setMicroseconds(int $value) Set current instance microsecond to the given value. + * @method $this setMicrosecond(int $value) Set current instance microsecond to the given value. + * @method $this addYears(int $value = 1) Add years (the $value count passed in) to the instance (using date interval). + * @method $this addYear() Add one year to the instance (using date interval). + * @method $this subYears(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval). + * @method $this subYear() Sub one year to the instance (using date interval). + * @method $this addYearsWithOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this addYearWithOverflow() Add one year to the instance (using date interval) with overflow explicitly allowed. + * @method $this subYearsWithOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this subYearWithOverflow() Sub one year to the instance (using date interval) with overflow explicitly allowed. + * @method $this addYearsWithoutOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addYearWithoutOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subYearsWithoutOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subYearWithoutOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addYearsWithNoOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addYearWithNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subYearsWithNoOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subYearWithNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addYearsNoOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addYearNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subYearsNoOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subYearNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMonths(int $value = 1) Add months (the $value count passed in) to the instance (using date interval). + * @method $this addMonth() Add one month to the instance (using date interval). + * @method $this subMonths(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval). + * @method $this subMonth() Sub one month to the instance (using date interval). + * @method $this addMonthsWithOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this addMonthWithOverflow() Add one month to the instance (using date interval) with overflow explicitly allowed. + * @method $this subMonthsWithOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this subMonthWithOverflow() Sub one month to the instance (using date interval) with overflow explicitly allowed. + * @method $this addMonthsWithoutOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMonthWithoutOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMonthsWithoutOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMonthWithoutOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMonthsWithNoOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMonthWithNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMonthsWithNoOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMonthWithNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMonthsNoOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMonthNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMonthsNoOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMonthNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addDays(int $value = 1) Add days (the $value count passed in) to the instance (using date interval). + * @method $this addDay() Add one day to the instance (using date interval). + * @method $this subDays(int $value = 1) Sub days (the $value count passed in) to the instance (using date interval). + * @method $this subDay() Sub one day to the instance (using date interval). + * @method $this addHours(int $value = 1) Add hours (the $value count passed in) to the instance (using date interval). + * @method $this addHour() Add one hour to the instance (using date interval). + * @method $this subHours(int $value = 1) Sub hours (the $value count passed in) to the instance (using date interval). + * @method $this subHour() Sub one hour to the instance (using date interval). + * @method $this addMinutes(int $value = 1) Add minutes (the $value count passed in) to the instance (using date interval). + * @method $this addMinute() Add one minute to the instance (using date interval). + * @method $this subMinutes(int $value = 1) Sub minutes (the $value count passed in) to the instance (using date interval). + * @method $this subMinute() Sub one minute to the instance (using date interval). + * @method $this addSeconds(int $value = 1) Add seconds (the $value count passed in) to the instance (using date interval). + * @method $this addSecond() Add one second to the instance (using date interval). + * @method $this subSeconds(int $value = 1) Sub seconds (the $value count passed in) to the instance (using date interval). + * @method $this subSecond() Sub one second to the instance (using date interval). + * @method $this addMillis(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). + * @method $this addMilli() Add one millisecond to the instance (using date interval). + * @method $this subMillis(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). + * @method $this subMilli() Sub one millisecond to the instance (using date interval). + * @method $this addMilliseconds(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). + * @method $this addMillisecond() Add one millisecond to the instance (using date interval). + * @method $this subMilliseconds(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). + * @method $this subMillisecond() Sub one millisecond to the instance (using date interval). + * @method $this addMicros(int $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). + * @method $this addMicro() Add one microsecond to the instance (using date interval). + * @method $this subMicros(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). + * @method $this subMicro() Sub one microsecond to the instance (using date interval). + * @method $this addMicroseconds(int $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). + * @method $this addMicrosecond() Add one microsecond to the instance (using date interval). + * @method $this subMicroseconds(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). + * @method $this subMicrosecond() Sub one microsecond to the instance (using date interval). + * @method $this addMillennia(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval). + * @method $this addMillennium() Add one millennium to the instance (using date interval). + * @method $this subMillennia(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval). + * @method $this subMillennium() Sub one millennium to the instance (using date interval). + * @method $this addMillenniaWithOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this addMillenniumWithOverflow() Add one millennium to the instance (using date interval) with overflow explicitly allowed. + * @method $this subMillenniaWithOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this subMillenniumWithOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly allowed. + * @method $this addMillenniaWithoutOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMillenniumWithoutOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMillenniaWithoutOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMillenniumWithoutOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMillenniaWithNoOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMillenniumWithNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMillenniaWithNoOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMillenniumWithNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMillenniaNoOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMillenniumNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMillenniaNoOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMillenniumNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addCenturies(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval). + * @method $this addCentury() Add one century to the instance (using date interval). + * @method $this subCenturies(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval). + * @method $this subCentury() Sub one century to the instance (using date interval). + * @method $this addCenturiesWithOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this addCenturyWithOverflow() Add one century to the instance (using date interval) with overflow explicitly allowed. + * @method $this subCenturiesWithOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this subCenturyWithOverflow() Sub one century to the instance (using date interval) with overflow explicitly allowed. + * @method $this addCenturiesWithoutOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addCenturyWithoutOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subCenturiesWithoutOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subCenturyWithoutOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addCenturiesWithNoOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addCenturyWithNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subCenturiesWithNoOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subCenturyWithNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addCenturiesNoOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addCenturyNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subCenturiesNoOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subCenturyNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addDecades(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval). + * @method $this addDecade() Add one decade to the instance (using date interval). + * @method $this subDecades(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval). + * @method $this subDecade() Sub one decade to the instance (using date interval). + * @method $this addDecadesWithOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this addDecadeWithOverflow() Add one decade to the instance (using date interval) with overflow explicitly allowed. + * @method $this subDecadesWithOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this subDecadeWithOverflow() Sub one decade to the instance (using date interval) with overflow explicitly allowed. + * @method $this addDecadesWithoutOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addDecadeWithoutOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subDecadesWithoutOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subDecadeWithoutOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addDecadesWithNoOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addDecadeWithNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subDecadesWithNoOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subDecadeWithNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addDecadesNoOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addDecadeNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subDecadesNoOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subDecadeNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addQuarters(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval). + * @method $this addQuarter() Add one quarter to the instance (using date interval). + * @method $this subQuarters(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval). + * @method $this subQuarter() Sub one quarter to the instance (using date interval). + * @method $this addQuartersWithOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this addQuarterWithOverflow() Add one quarter to the instance (using date interval) with overflow explicitly allowed. + * @method $this subQuartersWithOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this subQuarterWithOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly allowed. + * @method $this addQuartersWithoutOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addQuarterWithoutOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subQuartersWithoutOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subQuarterWithoutOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addQuartersWithNoOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addQuarterWithNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subQuartersWithNoOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subQuarterWithNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addQuartersNoOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addQuarterNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subQuartersNoOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subQuarterNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addWeeks(int $value = 1) Add weeks (the $value count passed in) to the instance (using date interval). + * @method $this addWeek() Add one week to the instance (using date interval). + * @method $this subWeeks(int $value = 1) Sub weeks (the $value count passed in) to the instance (using date interval). + * @method $this subWeek() Sub one week to the instance (using date interval). + * @method $this addWeekdays(int $value = 1) Add weekdays (the $value count passed in) to the instance (using date interval). + * @method $this addWeekday() Add one weekday to the instance (using date interval). + * @method $this subWeekdays(int $value = 1) Sub weekdays (the $value count passed in) to the instance (using date interval). + * @method $this subWeekday() Sub one weekday to the instance (using date interval). + * @method $this addRealMicros(int $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). + * @method $this addRealMicro() Add one microsecond to the instance (using timestamp). + * @method $this subRealMicros(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). + * @method $this subRealMicro() Sub one microsecond to the instance (using timestamp). + * @method CarbonPeriod microsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each microsecond or every X microseconds if a factor is given. + * @method $this addRealMicroseconds(int $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). + * @method $this addRealMicrosecond() Add one microsecond to the instance (using timestamp). + * @method $this subRealMicroseconds(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). + * @method $this subRealMicrosecond() Sub one microsecond to the instance (using timestamp). + * @method CarbonPeriod microsecondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each microsecond or every X microseconds if a factor is given. + * @method $this addRealMillis(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). + * @method $this addRealMilli() Add one millisecond to the instance (using timestamp). + * @method $this subRealMillis(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). + * @method $this subRealMilli() Sub one millisecond to the instance (using timestamp). + * @method CarbonPeriod millisUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millisecond or every X milliseconds if a factor is given. + * @method $this addRealMilliseconds(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). + * @method $this addRealMillisecond() Add one millisecond to the instance (using timestamp). + * @method $this subRealMilliseconds(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). + * @method $this subRealMillisecond() Sub one millisecond to the instance (using timestamp). + * @method CarbonPeriod millisecondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millisecond or every X milliseconds if a factor is given. + * @method $this addRealSeconds(int $value = 1) Add seconds (the $value count passed in) to the instance (using timestamp). + * @method $this addRealSecond() Add one second to the instance (using timestamp). + * @method $this subRealSeconds(int $value = 1) Sub seconds (the $value count passed in) to the instance (using timestamp). + * @method $this subRealSecond() Sub one second to the instance (using timestamp). + * @method CarbonPeriod secondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each second or every X seconds if a factor is given. + * @method $this addRealMinutes(int $value = 1) Add minutes (the $value count passed in) to the instance (using timestamp). + * @method $this addRealMinute() Add one minute to the instance (using timestamp). + * @method $this subRealMinutes(int $value = 1) Sub minutes (the $value count passed in) to the instance (using timestamp). + * @method $this subRealMinute() Sub one minute to the instance (using timestamp). + * @method CarbonPeriod minutesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each minute or every X minutes if a factor is given. + * @method $this addRealHours(int $value = 1) Add hours (the $value count passed in) to the instance (using timestamp). + * @method $this addRealHour() Add one hour to the instance (using timestamp). + * @method $this subRealHours(int $value = 1) Sub hours (the $value count passed in) to the instance (using timestamp). + * @method $this subRealHour() Sub one hour to the instance (using timestamp). + * @method CarbonPeriod hoursUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each hour or every X hours if a factor is given. + * @method $this addRealDays(int $value = 1) Add days (the $value count passed in) to the instance (using timestamp). + * @method $this addRealDay() Add one day to the instance (using timestamp). + * @method $this subRealDays(int $value = 1) Sub days (the $value count passed in) to the instance (using timestamp). + * @method $this subRealDay() Sub one day to the instance (using timestamp). + * @method CarbonPeriod daysUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each day or every X days if a factor is given. + * @method $this addRealWeeks(int $value = 1) Add weeks (the $value count passed in) to the instance (using timestamp). + * @method $this addRealWeek() Add one week to the instance (using timestamp). + * @method $this subRealWeeks(int $value = 1) Sub weeks (the $value count passed in) to the instance (using timestamp). + * @method $this subRealWeek() Sub one week to the instance (using timestamp). + * @method CarbonPeriod weeksUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each week or every X weeks if a factor is given. + * @method $this addRealMonths(int $value = 1) Add months (the $value count passed in) to the instance (using timestamp). + * @method $this addRealMonth() Add one month to the instance (using timestamp). + * @method $this subRealMonths(int $value = 1) Sub months (the $value count passed in) to the instance (using timestamp). + * @method $this subRealMonth() Sub one month to the instance (using timestamp). + * @method CarbonPeriod monthsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each month or every X months if a factor is given. + * @method $this addRealQuarters(int $value = 1) Add quarters (the $value count passed in) to the instance (using timestamp). + * @method $this addRealQuarter() Add one quarter to the instance (using timestamp). + * @method $this subRealQuarters(int $value = 1) Sub quarters (the $value count passed in) to the instance (using timestamp). + * @method $this subRealQuarter() Sub one quarter to the instance (using timestamp). + * @method CarbonPeriod quartersUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each quarter or every X quarters if a factor is given. + * @method $this addRealYears(int $value = 1) Add years (the $value count passed in) to the instance (using timestamp). + * @method $this addRealYear() Add one year to the instance (using timestamp). + * @method $this subRealYears(int $value = 1) Sub years (the $value count passed in) to the instance (using timestamp). + * @method $this subRealYear() Sub one year to the instance (using timestamp). + * @method CarbonPeriod yearsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each year or every X years if a factor is given. + * @method $this addRealDecades(int $value = 1) Add decades (the $value count passed in) to the instance (using timestamp). + * @method $this addRealDecade() Add one decade to the instance (using timestamp). + * @method $this subRealDecades(int $value = 1) Sub decades (the $value count passed in) to the instance (using timestamp). + * @method $this subRealDecade() Sub one decade to the instance (using timestamp). + * @method CarbonPeriod decadesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each decade or every X decades if a factor is given. + * @method $this addRealCenturies(int $value = 1) Add centuries (the $value count passed in) to the instance (using timestamp). + * @method $this addRealCentury() Add one century to the instance (using timestamp). + * @method $this subRealCenturies(int $value = 1) Sub centuries (the $value count passed in) to the instance (using timestamp). + * @method $this subRealCentury() Sub one century to the instance (using timestamp). + * @method CarbonPeriod centuriesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each century or every X centuries if a factor is given. + * @method $this addRealMillennia(int $value = 1) Add millennia (the $value count passed in) to the instance (using timestamp). + * @method $this addRealMillennium() Add one millennium to the instance (using timestamp). + * @method $this subRealMillennia(int $value = 1) Sub millennia (the $value count passed in) to the instance (using timestamp). + * @method $this subRealMillennium() Sub one millennium to the instance (using timestamp). + * @method CarbonPeriod millenniaUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millennium or every X millennia if a factor is given. + * @method $this roundYear(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method $this roundYears(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method $this floorYear(float $precision = 1) Truncate the current instance year with given precision. + * @method $this floorYears(float $precision = 1) Truncate the current instance year with given precision. + * @method $this ceilYear(float $precision = 1) Ceil the current instance year with given precision. + * @method $this ceilYears(float $precision = 1) Ceil the current instance year with given precision. + * @method $this roundMonth(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method $this roundMonths(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method $this floorMonth(float $precision = 1) Truncate the current instance month with given precision. + * @method $this floorMonths(float $precision = 1) Truncate the current instance month with given precision. + * @method $this ceilMonth(float $precision = 1) Ceil the current instance month with given precision. + * @method $this ceilMonths(float $precision = 1) Ceil the current instance month with given precision. + * @method $this roundDay(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method $this roundDays(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method $this floorDay(float $precision = 1) Truncate the current instance day with given precision. + * @method $this floorDays(float $precision = 1) Truncate the current instance day with given precision. + * @method $this ceilDay(float $precision = 1) Ceil the current instance day with given precision. + * @method $this ceilDays(float $precision = 1) Ceil the current instance day with given precision. + * @method $this roundHour(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method $this roundHours(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method $this floorHour(float $precision = 1) Truncate the current instance hour with given precision. + * @method $this floorHours(float $precision = 1) Truncate the current instance hour with given precision. + * @method $this ceilHour(float $precision = 1) Ceil the current instance hour with given precision. + * @method $this ceilHours(float $precision = 1) Ceil the current instance hour with given precision. + * @method $this roundMinute(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method $this roundMinutes(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method $this floorMinute(float $precision = 1) Truncate the current instance minute with given precision. + * @method $this floorMinutes(float $precision = 1) Truncate the current instance minute with given precision. + * @method $this ceilMinute(float $precision = 1) Ceil the current instance minute with given precision. + * @method $this ceilMinutes(float $precision = 1) Ceil the current instance minute with given precision. + * @method $this roundSecond(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method $this roundSeconds(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method $this floorSecond(float $precision = 1) Truncate the current instance second with given precision. + * @method $this floorSeconds(float $precision = 1) Truncate the current instance second with given precision. + * @method $this ceilSecond(float $precision = 1) Ceil the current instance second with given precision. + * @method $this ceilSeconds(float $precision = 1) Ceil the current instance second with given precision. + * @method $this roundMillennium(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method $this roundMillennia(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method $this floorMillennium(float $precision = 1) Truncate the current instance millennium with given precision. + * @method $this floorMillennia(float $precision = 1) Truncate the current instance millennium with given precision. + * @method $this ceilMillennium(float $precision = 1) Ceil the current instance millennium with given precision. + * @method $this ceilMillennia(float $precision = 1) Ceil the current instance millennium with given precision. + * @method $this roundCentury(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method $this roundCenturies(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method $this floorCentury(float $precision = 1) Truncate the current instance century with given precision. + * @method $this floorCenturies(float $precision = 1) Truncate the current instance century with given precision. + * @method $this ceilCentury(float $precision = 1) Ceil the current instance century with given precision. + * @method $this ceilCenturies(float $precision = 1) Ceil the current instance century with given precision. + * @method $this roundDecade(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method $this roundDecades(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method $this floorDecade(float $precision = 1) Truncate the current instance decade with given precision. + * @method $this floorDecades(float $precision = 1) Truncate the current instance decade with given precision. + * @method $this ceilDecade(float $precision = 1) Ceil the current instance decade with given precision. + * @method $this ceilDecades(float $precision = 1) Ceil the current instance decade with given precision. + * @method $this roundQuarter(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method $this roundQuarters(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method $this floorQuarter(float $precision = 1) Truncate the current instance quarter with given precision. + * @method $this floorQuarters(float $precision = 1) Truncate the current instance quarter with given precision. + * @method $this ceilQuarter(float $precision = 1) Ceil the current instance quarter with given precision. + * @method $this ceilQuarters(float $precision = 1) Ceil the current instance quarter with given precision. + * @method $this roundMillisecond(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method $this roundMilliseconds(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method $this floorMillisecond(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method $this floorMilliseconds(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method $this ceilMillisecond(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method $this ceilMilliseconds(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method $this roundMicrosecond(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method $this roundMicroseconds(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method $this floorMicrosecond(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method $this floorMicroseconds(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method $this ceilMicrosecond(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method $this ceilMicroseconds(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method string shortAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method static Carbon|false createFromFormat(string $format, string $time, string|DateTimeZone $timezone = null) Parse a string into a new Carbon object according to the specified format. + * @method static Carbon __set_state(array $array) https://php.net/manual/en/datetime.set-state.php + * + * + */ +class Carbon extends DateTime implements CarbonInterface +{ + use Date; + + /** + * Returns true if the current class/instance is mutable. + * + * @return bool + */ + public static function isMutable() + { + return true; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/CarbonConverterInterface.php b/vendor/nesbot/carbon/src/Carbon/CarbonConverterInterface.php new file mode 100644 index 0000000..1ce967b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/CarbonConverterInterface.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use DateTimeInterface; + +interface CarbonConverterInterface +{ + public function convertDate(DateTimeInterface $dateTime, bool $negated = false): CarbonInterface; +} diff --git a/vendor/nesbot/carbon/src/Carbon/CarbonImmutable.php b/vendor/nesbot/carbon/src/Carbon/CarbonImmutable.php new file mode 100644 index 0000000..6d1194e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/CarbonImmutable.php @@ -0,0 +1,582 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use Carbon\Traits\Date; +use Carbon\Traits\DeprecatedProperties; +use DateTimeImmutable; +use DateTimeInterface; +use DateTimeZone; + +/** + * A simple API extension for DateTimeImmutable. + * + * @mixin DeprecatedProperties + * + * + * + * @property int $year + * @property int $yearIso + * @property int $month + * @property int $day + * @property int $hour + * @property int $minute + * @property int $second + * @property int $micro + * @property int $microsecond + * @property int|float|string $timestamp seconds since the Unix Epoch + * @property string $englishDayOfWeek the day of week in English + * @property string $shortEnglishDayOfWeek the abbreviated day of week in English + * @property string $englishMonth the month in English + * @property string $shortEnglishMonth the abbreviated month in English + * @property int $milliseconds + * @property int $millisecond + * @property int $milli + * @property int $week 1 through 53 + * @property int $isoWeek 1 through 53 + * @property int $weekYear year according to week format + * @property int $isoWeekYear year according to ISO week format + * @property int $dayOfYear 1 through 366 + * @property int $age does a diffInYears() with default parameters + * @property int $offset the timezone offset in seconds from UTC + * @property int $offsetMinutes the timezone offset in minutes from UTC + * @property int $offsetHours the timezone offset in hours from UTC + * @property CarbonTimeZone $timezone the current timezone + * @property CarbonTimeZone $tz alias of $timezone + * @property-read int $dayOfWeek 0 (for Sunday) through 6 (for Saturday) + * @property-read int $dayOfWeekIso 1 (for Monday) through 7 (for Sunday) + * @property-read int $weekOfYear ISO-8601 week number of year, weeks starting on Monday + * @property-read int $daysInMonth number of days in the given month + * @property-read string $latinMeridiem "am"/"pm" (Ante meridiem or Post meridiem latin lowercase mark) + * @property-read string $latinUpperMeridiem "AM"/"PM" (Ante meridiem or Post meridiem latin uppercase mark) + * @property-read string $timezoneAbbreviatedName the current timezone abbreviated name + * @property-read string $tzAbbrName alias of $timezoneAbbreviatedName + * @property-read string $dayName long name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $shortDayName short name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $minDayName very short name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $monthName long name of month translated according to Carbon locale, in english if no translation available for current language + * @property-read string $shortMonthName short name of month translated according to Carbon locale, in english if no translation available for current language + * @property-read string $meridiem lowercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language + * @property-read string $upperMeridiem uppercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language + * @property-read int $noZeroHour current hour from 1 to 24 + * @property-read int $weeksInYear 51 through 53 + * @property-read int $isoWeeksInYear 51 through 53 + * @property-read int $weekOfMonth 1 through 5 + * @property-read int $weekNumberInMonth 1 through 5 + * @property-read int $firstWeekDay 0 through 6 + * @property-read int $lastWeekDay 0 through 6 + * @property-read int $daysInYear 365 or 366 + * @property-read int $quarter the quarter of this instance, 1 - 4 + * @property-read int $decade the decade of this instance + * @property-read int $century the century of this instance + * @property-read int $millennium the millennium of this instance + * @property-read bool $dst daylight savings time indicator, true if DST, false otherwise + * @property-read bool $local checks if the timezone is local, true if local, false otherwise + * @property-read bool $utc checks if the timezone is UTC, true if UTC, false otherwise + * @property-read string $timezoneName the current timezone name + * @property-read string $tzName alias of $timezoneName + * @property-read string $locale locale of the current instance + * + * @method bool isUtc() Check if the current instance has UTC timezone. (Both isUtc and isUTC cases are valid.) + * @method bool isLocal() Check if the current instance has non-UTC timezone. + * @method bool isValid() Check if the current instance is a valid date. + * @method bool isDST() Check if the current instance is in a daylight saving time. + * @method bool isSunday() Checks if the instance day is sunday. + * @method bool isMonday() Checks if the instance day is monday. + * @method bool isTuesday() Checks if the instance day is tuesday. + * @method bool isWednesday() Checks if the instance day is wednesday. + * @method bool isThursday() Checks if the instance day is thursday. + * @method bool isFriday() Checks if the instance day is friday. + * @method bool isSaturday() Checks if the instance day is saturday. + * @method bool isSameYear(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same year as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentYear() Checks if the instance is in the same year as the current moment. + * @method bool isNextYear() Checks if the instance is in the same year as the current moment next year. + * @method bool isLastYear() Checks if the instance is in the same year as the current moment last year. + * @method bool isSameWeek(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same week as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentWeek() Checks if the instance is in the same week as the current moment. + * @method bool isNextWeek() Checks if the instance is in the same week as the current moment next week. + * @method bool isLastWeek() Checks if the instance is in the same week as the current moment last week. + * @method bool isSameDay(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same day as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentDay() Checks if the instance is in the same day as the current moment. + * @method bool isNextDay() Checks if the instance is in the same day as the current moment next day. + * @method bool isLastDay() Checks if the instance is in the same day as the current moment last day. + * @method bool isSameHour(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same hour as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentHour() Checks if the instance is in the same hour as the current moment. + * @method bool isNextHour() Checks if the instance is in the same hour as the current moment next hour. + * @method bool isLastHour() Checks if the instance is in the same hour as the current moment last hour. + * @method bool isSameMinute(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same minute as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMinute() Checks if the instance is in the same minute as the current moment. + * @method bool isNextMinute() Checks if the instance is in the same minute as the current moment next minute. + * @method bool isLastMinute() Checks if the instance is in the same minute as the current moment last minute. + * @method bool isSameSecond(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same second as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentSecond() Checks if the instance is in the same second as the current moment. + * @method bool isNextSecond() Checks if the instance is in the same second as the current moment next second. + * @method bool isLastSecond() Checks if the instance is in the same second as the current moment last second. + * @method bool isSameMicro(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMicro() Checks if the instance is in the same microsecond as the current moment. + * @method bool isNextMicro() Checks if the instance is in the same microsecond as the current moment next microsecond. + * @method bool isLastMicro() Checks if the instance is in the same microsecond as the current moment last microsecond. + * @method bool isSameMicrosecond(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMicrosecond() Checks if the instance is in the same microsecond as the current moment. + * @method bool isNextMicrosecond() Checks if the instance is in the same microsecond as the current moment next microsecond. + * @method bool isLastMicrosecond() Checks if the instance is in the same microsecond as the current moment last microsecond. + * @method bool isCurrentMonth() Checks if the instance is in the same month as the current moment. + * @method bool isNextMonth() Checks if the instance is in the same month as the current moment next month. + * @method bool isLastMonth() Checks if the instance is in the same month as the current moment last month. + * @method bool isCurrentQuarter() Checks if the instance is in the same quarter as the current moment. + * @method bool isNextQuarter() Checks if the instance is in the same quarter as the current moment next quarter. + * @method bool isLastQuarter() Checks if the instance is in the same quarter as the current moment last quarter. + * @method bool isSameDecade(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same decade as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentDecade() Checks if the instance is in the same decade as the current moment. + * @method bool isNextDecade() Checks if the instance is in the same decade as the current moment next decade. + * @method bool isLastDecade() Checks if the instance is in the same decade as the current moment last decade. + * @method bool isSameCentury(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same century as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentCentury() Checks if the instance is in the same century as the current moment. + * @method bool isNextCentury() Checks if the instance is in the same century as the current moment next century. + * @method bool isLastCentury() Checks if the instance is in the same century as the current moment last century. + * @method bool isSameMillennium(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same millennium as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMillennium() Checks if the instance is in the same millennium as the current moment. + * @method bool isNextMillennium() Checks if the instance is in the same millennium as the current moment next millennium. + * @method bool isLastMillennium() Checks if the instance is in the same millennium as the current moment last millennium. + * @method CarbonImmutable years(int $value) Set current instance year to the given value. + * @method CarbonImmutable year(int $value) Set current instance year to the given value. + * @method CarbonImmutable setYears(int $value) Set current instance year to the given value. + * @method CarbonImmutable setYear(int $value) Set current instance year to the given value. + * @method CarbonImmutable months(int $value) Set current instance month to the given value. + * @method CarbonImmutable month(int $value) Set current instance month to the given value. + * @method CarbonImmutable setMonths(int $value) Set current instance month to the given value. + * @method CarbonImmutable setMonth(int $value) Set current instance month to the given value. + * @method CarbonImmutable days(int $value) Set current instance day to the given value. + * @method CarbonImmutable day(int $value) Set current instance day to the given value. + * @method CarbonImmutable setDays(int $value) Set current instance day to the given value. + * @method CarbonImmutable setDay(int $value) Set current instance day to the given value. + * @method CarbonImmutable hours(int $value) Set current instance hour to the given value. + * @method CarbonImmutable hour(int $value) Set current instance hour to the given value. + * @method CarbonImmutable setHours(int $value) Set current instance hour to the given value. + * @method CarbonImmutable setHour(int $value) Set current instance hour to the given value. + * @method CarbonImmutable minutes(int $value) Set current instance minute to the given value. + * @method CarbonImmutable minute(int $value) Set current instance minute to the given value. + * @method CarbonImmutable setMinutes(int $value) Set current instance minute to the given value. + * @method CarbonImmutable setMinute(int $value) Set current instance minute to the given value. + * @method CarbonImmutable seconds(int $value) Set current instance second to the given value. + * @method CarbonImmutable second(int $value) Set current instance second to the given value. + * @method CarbonImmutable setSeconds(int $value) Set current instance second to the given value. + * @method CarbonImmutable setSecond(int $value) Set current instance second to the given value. + * @method CarbonImmutable millis(int $value) Set current instance millisecond to the given value. + * @method CarbonImmutable milli(int $value) Set current instance millisecond to the given value. + * @method CarbonImmutable setMillis(int $value) Set current instance millisecond to the given value. + * @method CarbonImmutable setMilli(int $value) Set current instance millisecond to the given value. + * @method CarbonImmutable milliseconds(int $value) Set current instance millisecond to the given value. + * @method CarbonImmutable millisecond(int $value) Set current instance millisecond to the given value. + * @method CarbonImmutable setMilliseconds(int $value) Set current instance millisecond to the given value. + * @method CarbonImmutable setMillisecond(int $value) Set current instance millisecond to the given value. + * @method CarbonImmutable micros(int $value) Set current instance microsecond to the given value. + * @method CarbonImmutable micro(int $value) Set current instance microsecond to the given value. + * @method CarbonImmutable setMicros(int $value) Set current instance microsecond to the given value. + * @method CarbonImmutable setMicro(int $value) Set current instance microsecond to the given value. + * @method CarbonImmutable microseconds(int $value) Set current instance microsecond to the given value. + * @method CarbonImmutable microsecond(int $value) Set current instance microsecond to the given value. + * @method CarbonImmutable setMicroseconds(int $value) Set current instance microsecond to the given value. + * @method CarbonImmutable setMicrosecond(int $value) Set current instance microsecond to the given value. + * @method CarbonImmutable addYears(int $value = 1) Add years (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addYear() Add one year to the instance (using date interval). + * @method CarbonImmutable subYears(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subYear() Sub one year to the instance (using date interval). + * @method CarbonImmutable addYearsWithOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addYearWithOverflow() Add one year to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subYearsWithOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subYearWithOverflow() Sub one year to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addYearsWithoutOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addYearWithoutOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subYearsWithoutOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subYearWithoutOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addYearsWithNoOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addYearWithNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subYearsWithNoOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subYearWithNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addYearsNoOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addYearNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subYearsNoOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subYearNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMonths(int $value = 1) Add months (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addMonth() Add one month to the instance (using date interval). + * @method CarbonImmutable subMonths(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subMonth() Sub one month to the instance (using date interval). + * @method CarbonImmutable addMonthsWithOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addMonthWithOverflow() Add one month to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subMonthsWithOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subMonthWithOverflow() Sub one month to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addMonthsWithoutOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMonthWithoutOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMonthsWithoutOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMonthWithoutOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMonthsWithNoOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMonthWithNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMonthsWithNoOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMonthWithNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMonthsNoOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMonthNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMonthsNoOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMonthNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addDays(int $value = 1) Add days (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addDay() Add one day to the instance (using date interval). + * @method CarbonImmutable subDays(int $value = 1) Sub days (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subDay() Sub one day to the instance (using date interval). + * @method CarbonImmutable addHours(int $value = 1) Add hours (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addHour() Add one hour to the instance (using date interval). + * @method CarbonImmutable subHours(int $value = 1) Sub hours (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subHour() Sub one hour to the instance (using date interval). + * @method CarbonImmutable addMinutes(int $value = 1) Add minutes (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addMinute() Add one minute to the instance (using date interval). + * @method CarbonImmutable subMinutes(int $value = 1) Sub minutes (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subMinute() Sub one minute to the instance (using date interval). + * @method CarbonImmutable addSeconds(int $value = 1) Add seconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addSecond() Add one second to the instance (using date interval). + * @method CarbonImmutable subSeconds(int $value = 1) Sub seconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subSecond() Sub one second to the instance (using date interval). + * @method CarbonImmutable addMillis(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addMilli() Add one millisecond to the instance (using date interval). + * @method CarbonImmutable subMillis(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subMilli() Sub one millisecond to the instance (using date interval). + * @method CarbonImmutable addMilliseconds(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addMillisecond() Add one millisecond to the instance (using date interval). + * @method CarbonImmutable subMilliseconds(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subMillisecond() Sub one millisecond to the instance (using date interval). + * @method CarbonImmutable addMicros(int $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addMicro() Add one microsecond to the instance (using date interval). + * @method CarbonImmutable subMicros(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subMicro() Sub one microsecond to the instance (using date interval). + * @method CarbonImmutable addMicroseconds(int $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addMicrosecond() Add one microsecond to the instance (using date interval). + * @method CarbonImmutable subMicroseconds(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subMicrosecond() Sub one microsecond to the instance (using date interval). + * @method CarbonImmutable addMillennia(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addMillennium() Add one millennium to the instance (using date interval). + * @method CarbonImmutable subMillennia(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subMillennium() Sub one millennium to the instance (using date interval). + * @method CarbonImmutable addMillenniaWithOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addMillenniumWithOverflow() Add one millennium to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subMillenniaWithOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subMillenniumWithOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addMillenniaWithoutOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMillenniumWithoutOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMillenniaWithoutOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMillenniumWithoutOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMillenniaWithNoOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMillenniumWithNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMillenniaWithNoOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMillenniumWithNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMillenniaNoOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMillenniumNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMillenniaNoOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMillenniumNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addCenturies(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addCentury() Add one century to the instance (using date interval). + * @method CarbonImmutable subCenturies(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subCentury() Sub one century to the instance (using date interval). + * @method CarbonImmutable addCenturiesWithOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addCenturyWithOverflow() Add one century to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subCenturiesWithOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subCenturyWithOverflow() Sub one century to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addCenturiesWithoutOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addCenturyWithoutOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subCenturiesWithoutOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subCenturyWithoutOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addCenturiesWithNoOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addCenturyWithNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subCenturiesWithNoOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subCenturyWithNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addCenturiesNoOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addCenturyNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subCenturiesNoOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subCenturyNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addDecades(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addDecade() Add one decade to the instance (using date interval). + * @method CarbonImmutable subDecades(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subDecade() Sub one decade to the instance (using date interval). + * @method CarbonImmutable addDecadesWithOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addDecadeWithOverflow() Add one decade to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subDecadesWithOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subDecadeWithOverflow() Sub one decade to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addDecadesWithoutOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addDecadeWithoutOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subDecadesWithoutOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subDecadeWithoutOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addDecadesWithNoOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addDecadeWithNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subDecadesWithNoOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subDecadeWithNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addDecadesNoOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addDecadeNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subDecadesNoOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subDecadeNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addQuarters(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addQuarter() Add one quarter to the instance (using date interval). + * @method CarbonImmutable subQuarters(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subQuarter() Sub one quarter to the instance (using date interval). + * @method CarbonImmutable addQuartersWithOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addQuarterWithOverflow() Add one quarter to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subQuartersWithOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subQuarterWithOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addQuartersWithoutOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addQuarterWithoutOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subQuartersWithoutOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subQuarterWithoutOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addQuartersWithNoOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addQuarterWithNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subQuartersWithNoOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subQuarterWithNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addQuartersNoOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addQuarterNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subQuartersNoOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subQuarterNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addWeeks(int $value = 1) Add weeks (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addWeek() Add one week to the instance (using date interval). + * @method CarbonImmutable subWeeks(int $value = 1) Sub weeks (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subWeek() Sub one week to the instance (using date interval). + * @method CarbonImmutable addWeekdays(int $value = 1) Add weekdays (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addWeekday() Add one weekday to the instance (using date interval). + * @method CarbonImmutable subWeekdays(int $value = 1) Sub weekdays (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subWeekday() Sub one weekday to the instance (using date interval). + * @method CarbonImmutable addRealMicros(int $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealMicro() Add one microsecond to the instance (using timestamp). + * @method CarbonImmutable subRealMicros(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealMicro() Sub one microsecond to the instance (using timestamp). + * @method CarbonPeriod microsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each microsecond or every X microseconds if a factor is given. + * @method CarbonImmutable addRealMicroseconds(int $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealMicrosecond() Add one microsecond to the instance (using timestamp). + * @method CarbonImmutable subRealMicroseconds(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealMicrosecond() Sub one microsecond to the instance (using timestamp). + * @method CarbonPeriod microsecondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each microsecond or every X microseconds if a factor is given. + * @method CarbonImmutable addRealMillis(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealMilli() Add one millisecond to the instance (using timestamp). + * @method CarbonImmutable subRealMillis(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealMilli() Sub one millisecond to the instance (using timestamp). + * @method CarbonPeriod millisUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millisecond or every X milliseconds if a factor is given. + * @method CarbonImmutable addRealMilliseconds(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealMillisecond() Add one millisecond to the instance (using timestamp). + * @method CarbonImmutable subRealMilliseconds(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealMillisecond() Sub one millisecond to the instance (using timestamp). + * @method CarbonPeriod millisecondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millisecond or every X milliseconds if a factor is given. + * @method CarbonImmutable addRealSeconds(int $value = 1) Add seconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealSecond() Add one second to the instance (using timestamp). + * @method CarbonImmutable subRealSeconds(int $value = 1) Sub seconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealSecond() Sub one second to the instance (using timestamp). + * @method CarbonPeriod secondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each second or every X seconds if a factor is given. + * @method CarbonImmutable addRealMinutes(int $value = 1) Add minutes (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealMinute() Add one minute to the instance (using timestamp). + * @method CarbonImmutable subRealMinutes(int $value = 1) Sub minutes (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealMinute() Sub one minute to the instance (using timestamp). + * @method CarbonPeriod minutesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each minute or every X minutes if a factor is given. + * @method CarbonImmutable addRealHours(int $value = 1) Add hours (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealHour() Add one hour to the instance (using timestamp). + * @method CarbonImmutable subRealHours(int $value = 1) Sub hours (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealHour() Sub one hour to the instance (using timestamp). + * @method CarbonPeriod hoursUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each hour or every X hours if a factor is given. + * @method CarbonImmutable addRealDays(int $value = 1) Add days (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealDay() Add one day to the instance (using timestamp). + * @method CarbonImmutable subRealDays(int $value = 1) Sub days (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealDay() Sub one day to the instance (using timestamp). + * @method CarbonPeriod daysUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each day or every X days if a factor is given. + * @method CarbonImmutable addRealWeeks(int $value = 1) Add weeks (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealWeek() Add one week to the instance (using timestamp). + * @method CarbonImmutable subRealWeeks(int $value = 1) Sub weeks (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealWeek() Sub one week to the instance (using timestamp). + * @method CarbonPeriod weeksUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each week or every X weeks if a factor is given. + * @method CarbonImmutable addRealMonths(int $value = 1) Add months (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealMonth() Add one month to the instance (using timestamp). + * @method CarbonImmutable subRealMonths(int $value = 1) Sub months (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealMonth() Sub one month to the instance (using timestamp). + * @method CarbonPeriod monthsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each month or every X months if a factor is given. + * @method CarbonImmutable addRealQuarters(int $value = 1) Add quarters (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealQuarter() Add one quarter to the instance (using timestamp). + * @method CarbonImmutable subRealQuarters(int $value = 1) Sub quarters (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealQuarter() Sub one quarter to the instance (using timestamp). + * @method CarbonPeriod quartersUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each quarter or every X quarters if a factor is given. + * @method CarbonImmutable addRealYears(int $value = 1) Add years (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealYear() Add one year to the instance (using timestamp). + * @method CarbonImmutable subRealYears(int $value = 1) Sub years (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealYear() Sub one year to the instance (using timestamp). + * @method CarbonPeriod yearsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each year or every X years if a factor is given. + * @method CarbonImmutable addRealDecades(int $value = 1) Add decades (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealDecade() Add one decade to the instance (using timestamp). + * @method CarbonImmutable subRealDecades(int $value = 1) Sub decades (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealDecade() Sub one decade to the instance (using timestamp). + * @method CarbonPeriod decadesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each decade or every X decades if a factor is given. + * @method CarbonImmutable addRealCenturies(int $value = 1) Add centuries (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealCentury() Add one century to the instance (using timestamp). + * @method CarbonImmutable subRealCenturies(int $value = 1) Sub centuries (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealCentury() Sub one century to the instance (using timestamp). + * @method CarbonPeriod centuriesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each century or every X centuries if a factor is given. + * @method CarbonImmutable addRealMillennia(int $value = 1) Add millennia (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addRealMillennium() Add one millennium to the instance (using timestamp). + * @method CarbonImmutable subRealMillennia(int $value = 1) Sub millennia (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subRealMillennium() Sub one millennium to the instance (using timestamp). + * @method CarbonPeriod millenniaUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millennium or every X millennia if a factor is given. + * @method CarbonImmutable roundYear(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method CarbonImmutable roundYears(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method CarbonImmutable floorYear(float $precision = 1) Truncate the current instance year with given precision. + * @method CarbonImmutable floorYears(float $precision = 1) Truncate the current instance year with given precision. + * @method CarbonImmutable ceilYear(float $precision = 1) Ceil the current instance year with given precision. + * @method CarbonImmutable ceilYears(float $precision = 1) Ceil the current instance year with given precision. + * @method CarbonImmutable roundMonth(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method CarbonImmutable roundMonths(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method CarbonImmutable floorMonth(float $precision = 1) Truncate the current instance month with given precision. + * @method CarbonImmutable floorMonths(float $precision = 1) Truncate the current instance month with given precision. + * @method CarbonImmutable ceilMonth(float $precision = 1) Ceil the current instance month with given precision. + * @method CarbonImmutable ceilMonths(float $precision = 1) Ceil the current instance month with given precision. + * @method CarbonImmutable roundDay(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method CarbonImmutable roundDays(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method CarbonImmutable floorDay(float $precision = 1) Truncate the current instance day with given precision. + * @method CarbonImmutable floorDays(float $precision = 1) Truncate the current instance day with given precision. + * @method CarbonImmutable ceilDay(float $precision = 1) Ceil the current instance day with given precision. + * @method CarbonImmutable ceilDays(float $precision = 1) Ceil the current instance day with given precision. + * @method CarbonImmutable roundHour(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method CarbonImmutable roundHours(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method CarbonImmutable floorHour(float $precision = 1) Truncate the current instance hour with given precision. + * @method CarbonImmutable floorHours(float $precision = 1) Truncate the current instance hour with given precision. + * @method CarbonImmutable ceilHour(float $precision = 1) Ceil the current instance hour with given precision. + * @method CarbonImmutable ceilHours(float $precision = 1) Ceil the current instance hour with given precision. + * @method CarbonImmutable roundMinute(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method CarbonImmutable roundMinutes(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method CarbonImmutable floorMinute(float $precision = 1) Truncate the current instance minute with given precision. + * @method CarbonImmutable floorMinutes(float $precision = 1) Truncate the current instance minute with given precision. + * @method CarbonImmutable ceilMinute(float $precision = 1) Ceil the current instance minute with given precision. + * @method CarbonImmutable ceilMinutes(float $precision = 1) Ceil the current instance minute with given precision. + * @method CarbonImmutable roundSecond(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method CarbonImmutable roundSeconds(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method CarbonImmutable floorSecond(float $precision = 1) Truncate the current instance second with given precision. + * @method CarbonImmutable floorSeconds(float $precision = 1) Truncate the current instance second with given precision. + * @method CarbonImmutable ceilSecond(float $precision = 1) Ceil the current instance second with given precision. + * @method CarbonImmutable ceilSeconds(float $precision = 1) Ceil the current instance second with given precision. + * @method CarbonImmutable roundMillennium(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method CarbonImmutable roundMillennia(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method CarbonImmutable floorMillennium(float $precision = 1) Truncate the current instance millennium with given precision. + * @method CarbonImmutable floorMillennia(float $precision = 1) Truncate the current instance millennium with given precision. + * @method CarbonImmutable ceilMillennium(float $precision = 1) Ceil the current instance millennium with given precision. + * @method CarbonImmutable ceilMillennia(float $precision = 1) Ceil the current instance millennium with given precision. + * @method CarbonImmutable roundCentury(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method CarbonImmutable roundCenturies(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method CarbonImmutable floorCentury(float $precision = 1) Truncate the current instance century with given precision. + * @method CarbonImmutable floorCenturies(float $precision = 1) Truncate the current instance century with given precision. + * @method CarbonImmutable ceilCentury(float $precision = 1) Ceil the current instance century with given precision. + * @method CarbonImmutable ceilCenturies(float $precision = 1) Ceil the current instance century with given precision. + * @method CarbonImmutable roundDecade(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method CarbonImmutable roundDecades(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method CarbonImmutable floorDecade(float $precision = 1) Truncate the current instance decade with given precision. + * @method CarbonImmutable floorDecades(float $precision = 1) Truncate the current instance decade with given precision. + * @method CarbonImmutable ceilDecade(float $precision = 1) Ceil the current instance decade with given precision. + * @method CarbonImmutable ceilDecades(float $precision = 1) Ceil the current instance decade with given precision. + * @method CarbonImmutable roundQuarter(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method CarbonImmutable roundQuarters(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method CarbonImmutable floorQuarter(float $precision = 1) Truncate the current instance quarter with given precision. + * @method CarbonImmutable floorQuarters(float $precision = 1) Truncate the current instance quarter with given precision. + * @method CarbonImmutable ceilQuarter(float $precision = 1) Ceil the current instance quarter with given precision. + * @method CarbonImmutable ceilQuarters(float $precision = 1) Ceil the current instance quarter with given precision. + * @method CarbonImmutable roundMillisecond(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method CarbonImmutable roundMilliseconds(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method CarbonImmutable floorMillisecond(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method CarbonImmutable floorMilliseconds(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method CarbonImmutable ceilMillisecond(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method CarbonImmutable ceilMilliseconds(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method CarbonImmutable roundMicrosecond(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method CarbonImmutable roundMicroseconds(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method CarbonImmutable floorMicrosecond(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method CarbonImmutable floorMicroseconds(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method CarbonImmutable ceilMicrosecond(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method CarbonImmutable ceilMicroseconds(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method string shortAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method static CarbonImmutable|false createFromFormat(string $format, string $time, string|DateTimeZone $timezone = null) Parse a string into a new CarbonImmutable object according to the specified format. + * @method static CarbonImmutable __set_state(array $array) https://php.net/manual/en/datetime.set-state.php + * + * + */ +class CarbonImmutable extends DateTimeImmutable implements CarbonInterface +{ + use Date { + __clone as dateTraitClone; + } + + public function __clone() + { + $this->dateTraitClone(); + $this->endOfTime = false; + $this->startOfTime = false; + } + + /** + * Create a very old date representing start of time. + * + * @return static + */ + public static function startOfTime(): self + { + $date = static::parse('0001-01-01')->years(self::getStartOfTimeYear()); + $date->startOfTime = true; + + return $date; + } + + /** + * Create a very far date representing end of time. + * + * @return static + */ + public static function endOfTime(): self + { + $date = static::parse('9999-12-31 23:59:59.999999')->years(self::getEndOfTimeYear()); + $date->endOfTime = true; + + return $date; + } + + /** + * @codeCoverageIgnore + */ + private static function getEndOfTimeYear(): int + { + if (version_compare(PHP_VERSION, '7.3.0-dev', '<')) { + return 145261681241552; + } + + // Remove if https://bugs.php.net/bug.php?id=81107 is fixed + if (version_compare(PHP_VERSION, '8.1.0-dev', '>=')) { + return 1118290769066902787; + } + + return PHP_INT_MAX; + } + + /** + * @codeCoverageIgnore + */ + private static function getStartOfTimeYear(): int + { + if (version_compare(PHP_VERSION, '7.3.0-dev', '<')) { + return -135908816449551; + } + + // Remove if https://bugs.php.net/bug.php?id=81107 is fixed + if (version_compare(PHP_VERSION, '8.1.0-dev', '>=')) { + return -1118290769066898816; + } + + return max(PHP_INT_MIN, -9223372036854773760); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/CarbonInterface.php b/vendor/nesbot/carbon/src/Carbon/CarbonInterface.php new file mode 100644 index 0000000..15e2061 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/CarbonInterface.php @@ -0,0 +1,5077 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use BadMethodCallException; +use Carbon\Exceptions\BadComparisonUnitException; +use Carbon\Exceptions\ImmutableException; +use Carbon\Exceptions\InvalidDateException; +use Carbon\Exceptions\InvalidFormatException; +use Carbon\Exceptions\UnknownGetterException; +use Carbon\Exceptions\UnknownMethodException; +use Carbon\Exceptions\UnknownSetterException; +use Closure; +use DateInterval; +use DateTime; +use DateTimeImmutable; +use DateTimeInterface; +use DateTimeZone; +use JsonSerializable; +use ReflectionException; +use ReturnTypeWillChange; +use Symfony\Component\Translation\TranslatorInterface; +use Throwable; + +/** + * Common interface for Carbon and CarbonImmutable. + * + * + * + * @property int $year + * @property int $yearIso + * @property int $month + * @property int $day + * @property int $hour + * @property int $minute + * @property int $second + * @property int $micro + * @property int $microsecond + * @property int|float|string $timestamp seconds since the Unix Epoch + * @property string $englishDayOfWeek the day of week in English + * @property string $shortEnglishDayOfWeek the abbreviated day of week in English + * @property string $englishMonth the month in English + * @property string $shortEnglishMonth the abbreviated month in English + * @property int $milliseconds + * @property int $millisecond + * @property int $milli + * @property int $week 1 through 53 + * @property int $isoWeek 1 through 53 + * @property int $weekYear year according to week format + * @property int $isoWeekYear year according to ISO week format + * @property int $dayOfYear 1 through 366 + * @property int $age does a diffInYears() with default parameters + * @property int $offset the timezone offset in seconds from UTC + * @property int $offsetMinutes the timezone offset in minutes from UTC + * @property int $offsetHours the timezone offset in hours from UTC + * @property CarbonTimeZone $timezone the current timezone + * @property CarbonTimeZone $tz alias of $timezone + * @property-read int $dayOfWeek 0 (for Sunday) through 6 (for Saturday) + * @property-read int $dayOfWeekIso 1 (for Monday) through 7 (for Sunday) + * @property-read int $weekOfYear ISO-8601 week number of year, weeks starting on Monday + * @property-read int $daysInMonth number of days in the given month + * @property-read string $latinMeridiem "am"/"pm" (Ante meridiem or Post meridiem latin lowercase mark) + * @property-read string $latinUpperMeridiem "AM"/"PM" (Ante meridiem or Post meridiem latin uppercase mark) + * @property-read string $timezoneAbbreviatedName the current timezone abbreviated name + * @property-read string $tzAbbrName alias of $timezoneAbbreviatedName + * @property-read string $dayName long name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $shortDayName short name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $minDayName very short name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $monthName long name of month translated according to Carbon locale, in english if no translation available for current language + * @property-read string $shortMonthName short name of month translated according to Carbon locale, in english if no translation available for current language + * @property-read string $meridiem lowercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language + * @property-read string $upperMeridiem uppercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language + * @property-read int $noZeroHour current hour from 1 to 24 + * @property-read int $weeksInYear 51 through 53 + * @property-read int $isoWeeksInYear 51 through 53 + * @property-read int $weekOfMonth 1 through 5 + * @property-read int $weekNumberInMonth 1 through 5 + * @property-read int $firstWeekDay 0 through 6 + * @property-read int $lastWeekDay 0 through 6 + * @property-read int $daysInYear 365 or 366 + * @property-read int $quarter the quarter of this instance, 1 - 4 + * @property-read int $decade the decade of this instance + * @property-read int $century the century of this instance + * @property-read int $millennium the millennium of this instance + * @property-read bool $dst daylight savings time indicator, true if DST, false otherwise + * @property-read bool $local checks if the timezone is local, true if local, false otherwise + * @property-read bool $utc checks if the timezone is UTC, true if UTC, false otherwise + * @property-read string $timezoneName the current timezone name + * @property-read string $tzName alias of $timezoneName + * @property-read string $locale locale of the current instance + * + * @method bool isUtc() Check if the current instance has UTC timezone. (Both isUtc and isUTC cases are valid.) + * @method bool isLocal() Check if the current instance has non-UTC timezone. + * @method bool isValid() Check if the current instance is a valid date. + * @method bool isDST() Check if the current instance is in a daylight saving time. + * @method bool isSunday() Checks if the instance day is sunday. + * @method bool isMonday() Checks if the instance day is monday. + * @method bool isTuesday() Checks if the instance day is tuesday. + * @method bool isWednesday() Checks if the instance day is wednesday. + * @method bool isThursday() Checks if the instance day is thursday. + * @method bool isFriday() Checks if the instance day is friday. + * @method bool isSaturday() Checks if the instance day is saturday. + * @method bool isSameYear(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same year as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentYear() Checks if the instance is in the same year as the current moment. + * @method bool isNextYear() Checks if the instance is in the same year as the current moment next year. + * @method bool isLastYear() Checks if the instance is in the same year as the current moment last year. + * @method bool isSameWeek(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same week as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentWeek() Checks if the instance is in the same week as the current moment. + * @method bool isNextWeek() Checks if the instance is in the same week as the current moment next week. + * @method bool isLastWeek() Checks if the instance is in the same week as the current moment last week. + * @method bool isSameDay(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same day as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentDay() Checks if the instance is in the same day as the current moment. + * @method bool isNextDay() Checks if the instance is in the same day as the current moment next day. + * @method bool isLastDay() Checks if the instance is in the same day as the current moment last day. + * @method bool isSameHour(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same hour as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentHour() Checks if the instance is in the same hour as the current moment. + * @method bool isNextHour() Checks if the instance is in the same hour as the current moment next hour. + * @method bool isLastHour() Checks if the instance is in the same hour as the current moment last hour. + * @method bool isSameMinute(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same minute as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMinute() Checks if the instance is in the same minute as the current moment. + * @method bool isNextMinute() Checks if the instance is in the same minute as the current moment next minute. + * @method bool isLastMinute() Checks if the instance is in the same minute as the current moment last minute. + * @method bool isSameSecond(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same second as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentSecond() Checks if the instance is in the same second as the current moment. + * @method bool isNextSecond() Checks if the instance is in the same second as the current moment next second. + * @method bool isLastSecond() Checks if the instance is in the same second as the current moment last second. + * @method bool isSameMicro(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMicro() Checks if the instance is in the same microsecond as the current moment. + * @method bool isNextMicro() Checks if the instance is in the same microsecond as the current moment next microsecond. + * @method bool isLastMicro() Checks if the instance is in the same microsecond as the current moment last microsecond. + * @method bool isSameMicrosecond(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMicrosecond() Checks if the instance is in the same microsecond as the current moment. + * @method bool isNextMicrosecond() Checks if the instance is in the same microsecond as the current moment next microsecond. + * @method bool isLastMicrosecond() Checks if the instance is in the same microsecond as the current moment last microsecond. + * @method bool isCurrentMonth() Checks if the instance is in the same month as the current moment. + * @method bool isNextMonth() Checks if the instance is in the same month as the current moment next month. + * @method bool isLastMonth() Checks if the instance is in the same month as the current moment last month. + * @method bool isCurrentQuarter() Checks if the instance is in the same quarter as the current moment. + * @method bool isNextQuarter() Checks if the instance is in the same quarter as the current moment next quarter. + * @method bool isLastQuarter() Checks if the instance is in the same quarter as the current moment last quarter. + * @method bool isSameDecade(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same decade as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentDecade() Checks if the instance is in the same decade as the current moment. + * @method bool isNextDecade() Checks if the instance is in the same decade as the current moment next decade. + * @method bool isLastDecade() Checks if the instance is in the same decade as the current moment last decade. + * @method bool isSameCentury(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same century as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentCentury() Checks if the instance is in the same century as the current moment. + * @method bool isNextCentury() Checks if the instance is in the same century as the current moment next century. + * @method bool isLastCentury() Checks if the instance is in the same century as the current moment last century. + * @method bool isSameMillennium(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same millennium as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMillennium() Checks if the instance is in the same millennium as the current moment. + * @method bool isNextMillennium() Checks if the instance is in the same millennium as the current moment next millennium. + * @method bool isLastMillennium() Checks if the instance is in the same millennium as the current moment last millennium. + * @method CarbonInterface years(int $value) Set current instance year to the given value. + * @method CarbonInterface year(int $value) Set current instance year to the given value. + * @method CarbonInterface setYears(int $value) Set current instance year to the given value. + * @method CarbonInterface setYear(int $value) Set current instance year to the given value. + * @method CarbonInterface months(int $value) Set current instance month to the given value. + * @method CarbonInterface month(int $value) Set current instance month to the given value. + * @method CarbonInterface setMonths(int $value) Set current instance month to the given value. + * @method CarbonInterface setMonth(int $value) Set current instance month to the given value. + * @method CarbonInterface days(int $value) Set current instance day to the given value. + * @method CarbonInterface day(int $value) Set current instance day to the given value. + * @method CarbonInterface setDays(int $value) Set current instance day to the given value. + * @method CarbonInterface setDay(int $value) Set current instance day to the given value. + * @method CarbonInterface hours(int $value) Set current instance hour to the given value. + * @method CarbonInterface hour(int $value) Set current instance hour to the given value. + * @method CarbonInterface setHours(int $value) Set current instance hour to the given value. + * @method CarbonInterface setHour(int $value) Set current instance hour to the given value. + * @method CarbonInterface minutes(int $value) Set current instance minute to the given value. + * @method CarbonInterface minute(int $value) Set current instance minute to the given value. + * @method CarbonInterface setMinutes(int $value) Set current instance minute to the given value. + * @method CarbonInterface setMinute(int $value) Set current instance minute to the given value. + * @method CarbonInterface seconds(int $value) Set current instance second to the given value. + * @method CarbonInterface second(int $value) Set current instance second to the given value. + * @method CarbonInterface setSeconds(int $value) Set current instance second to the given value. + * @method CarbonInterface setSecond(int $value) Set current instance second to the given value. + * @method CarbonInterface millis(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface milli(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface setMillis(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface setMilli(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface milliseconds(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface millisecond(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface setMilliseconds(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface setMillisecond(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface micros(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface micro(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface setMicros(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface setMicro(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface microseconds(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface microsecond(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface setMicroseconds(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface setMicrosecond(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface addYears(int $value = 1) Add years (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addYear() Add one year to the instance (using date interval). + * @method CarbonInterface subYears(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subYear() Sub one year to the instance (using date interval). + * @method CarbonInterface addYearsWithOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addYearWithOverflow() Add one year to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subYearsWithOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subYearWithOverflow() Sub one year to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addYearsWithoutOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearWithoutOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearsWithoutOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearWithoutOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearsWithNoOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearWithNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearsWithNoOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearWithNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearsNoOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearsNoOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonths(int $value = 1) Add months (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMonth() Add one month to the instance (using date interval). + * @method CarbonInterface subMonths(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMonth() Sub one month to the instance (using date interval). + * @method CarbonInterface addMonthsWithOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addMonthWithOverflow() Add one month to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subMonthsWithOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subMonthWithOverflow() Sub one month to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addMonthsWithoutOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthWithoutOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthsWithoutOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthWithoutOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthsWithNoOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthWithNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthsWithNoOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthWithNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthsNoOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthsNoOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDays(int $value = 1) Add days (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addDay() Add one day to the instance (using date interval). + * @method CarbonInterface subDays(int $value = 1) Sub days (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subDay() Sub one day to the instance (using date interval). + * @method CarbonInterface addHours(int $value = 1) Add hours (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addHour() Add one hour to the instance (using date interval). + * @method CarbonInterface subHours(int $value = 1) Sub hours (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subHour() Sub one hour to the instance (using date interval). + * @method CarbonInterface addMinutes(int $value = 1) Add minutes (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMinute() Add one minute to the instance (using date interval). + * @method CarbonInterface subMinutes(int $value = 1) Sub minutes (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMinute() Sub one minute to the instance (using date interval). + * @method CarbonInterface addSeconds(int $value = 1) Add seconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addSecond() Add one second to the instance (using date interval). + * @method CarbonInterface subSeconds(int $value = 1) Sub seconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subSecond() Sub one second to the instance (using date interval). + * @method CarbonInterface addMillis(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMilli() Add one millisecond to the instance (using date interval). + * @method CarbonInterface subMillis(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMilli() Sub one millisecond to the instance (using date interval). + * @method CarbonInterface addMilliseconds(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMillisecond() Add one millisecond to the instance (using date interval). + * @method CarbonInterface subMilliseconds(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMillisecond() Sub one millisecond to the instance (using date interval). + * @method CarbonInterface addMicros(int $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMicro() Add one microsecond to the instance (using date interval). + * @method CarbonInterface subMicros(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMicro() Sub one microsecond to the instance (using date interval). + * @method CarbonInterface addMicroseconds(int $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMicrosecond() Add one microsecond to the instance (using date interval). + * @method CarbonInterface subMicroseconds(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMicrosecond() Sub one microsecond to the instance (using date interval). + * @method CarbonInterface addMillennia(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMillennium() Add one millennium to the instance (using date interval). + * @method CarbonInterface subMillennia(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMillennium() Sub one millennium to the instance (using date interval). + * @method CarbonInterface addMillenniaWithOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addMillenniumWithOverflow() Add one millennium to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subMillenniaWithOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subMillenniumWithOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addMillenniaWithoutOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniumWithoutOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniaWithoutOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniumWithoutOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniaWithNoOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniumWithNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniaWithNoOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniumWithNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniaNoOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniumNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniaNoOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniumNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturies(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addCentury() Add one century to the instance (using date interval). + * @method CarbonInterface subCenturies(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subCentury() Sub one century to the instance (using date interval). + * @method CarbonInterface addCenturiesWithOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addCenturyWithOverflow() Add one century to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subCenturiesWithOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subCenturyWithOverflow() Sub one century to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addCenturiesWithoutOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturyWithoutOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturiesWithoutOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturyWithoutOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturiesWithNoOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturyWithNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturiesWithNoOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturyWithNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturiesNoOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturyNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturiesNoOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturyNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecades(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addDecade() Add one decade to the instance (using date interval). + * @method CarbonInterface subDecades(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subDecade() Sub one decade to the instance (using date interval). + * @method CarbonInterface addDecadesWithOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addDecadeWithOverflow() Add one decade to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subDecadesWithOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subDecadeWithOverflow() Sub one decade to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addDecadesWithoutOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadeWithoutOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadesWithoutOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadeWithoutOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadesWithNoOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadeWithNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadesWithNoOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadeWithNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadesNoOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadeNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadesNoOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadeNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuarters(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addQuarter() Add one quarter to the instance (using date interval). + * @method CarbonInterface subQuarters(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subQuarter() Sub one quarter to the instance (using date interval). + * @method CarbonInterface addQuartersWithOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addQuarterWithOverflow() Add one quarter to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subQuartersWithOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subQuarterWithOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addQuartersWithoutOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuarterWithoutOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuartersWithoutOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuarterWithoutOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuartersWithNoOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuarterWithNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuartersWithNoOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuarterWithNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuartersNoOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuarterNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuartersNoOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuarterNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addWeeks(int $value = 1) Add weeks (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addWeek() Add one week to the instance (using date interval). + * @method CarbonInterface subWeeks(int $value = 1) Sub weeks (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subWeek() Sub one week to the instance (using date interval). + * @method CarbonInterface addWeekdays(int $value = 1) Add weekdays (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addWeekday() Add one weekday to the instance (using date interval). + * @method CarbonInterface subWeekdays(int $value = 1) Sub weekdays (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subWeekday() Sub one weekday to the instance (using date interval). + * @method CarbonInterface addRealMicros(int $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMicro() Add one microsecond to the instance (using timestamp). + * @method CarbonInterface subRealMicros(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMicro() Sub one microsecond to the instance (using timestamp). + * @method CarbonPeriod microsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each microsecond or every X microseconds if a factor is given. + * @method CarbonInterface addRealMicroseconds(int $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMicrosecond() Add one microsecond to the instance (using timestamp). + * @method CarbonInterface subRealMicroseconds(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMicrosecond() Sub one microsecond to the instance (using timestamp). + * @method CarbonPeriod microsecondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each microsecond or every X microseconds if a factor is given. + * @method CarbonInterface addRealMillis(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMilli() Add one millisecond to the instance (using timestamp). + * @method CarbonInterface subRealMillis(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMilli() Sub one millisecond to the instance (using timestamp). + * @method CarbonPeriod millisUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millisecond or every X milliseconds if a factor is given. + * @method CarbonInterface addRealMilliseconds(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMillisecond() Add one millisecond to the instance (using timestamp). + * @method CarbonInterface subRealMilliseconds(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMillisecond() Sub one millisecond to the instance (using timestamp). + * @method CarbonPeriod millisecondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millisecond or every X milliseconds if a factor is given. + * @method CarbonInterface addRealSeconds(int $value = 1) Add seconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealSecond() Add one second to the instance (using timestamp). + * @method CarbonInterface subRealSeconds(int $value = 1) Sub seconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealSecond() Sub one second to the instance (using timestamp). + * @method CarbonPeriod secondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each second or every X seconds if a factor is given. + * @method CarbonInterface addRealMinutes(int $value = 1) Add minutes (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMinute() Add one minute to the instance (using timestamp). + * @method CarbonInterface subRealMinutes(int $value = 1) Sub minutes (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMinute() Sub one minute to the instance (using timestamp). + * @method CarbonPeriod minutesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each minute or every X minutes if a factor is given. + * @method CarbonInterface addRealHours(int $value = 1) Add hours (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealHour() Add one hour to the instance (using timestamp). + * @method CarbonInterface subRealHours(int $value = 1) Sub hours (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealHour() Sub one hour to the instance (using timestamp). + * @method CarbonPeriod hoursUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each hour or every X hours if a factor is given. + * @method CarbonInterface addRealDays(int $value = 1) Add days (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealDay() Add one day to the instance (using timestamp). + * @method CarbonInterface subRealDays(int $value = 1) Sub days (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealDay() Sub one day to the instance (using timestamp). + * @method CarbonPeriod daysUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each day or every X days if a factor is given. + * @method CarbonInterface addRealWeeks(int $value = 1) Add weeks (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealWeek() Add one week to the instance (using timestamp). + * @method CarbonInterface subRealWeeks(int $value = 1) Sub weeks (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealWeek() Sub one week to the instance (using timestamp). + * @method CarbonPeriod weeksUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each week or every X weeks if a factor is given. + * @method CarbonInterface addRealMonths(int $value = 1) Add months (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMonth() Add one month to the instance (using timestamp). + * @method CarbonInterface subRealMonths(int $value = 1) Sub months (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMonth() Sub one month to the instance (using timestamp). + * @method CarbonPeriod monthsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each month or every X months if a factor is given. + * @method CarbonInterface addRealQuarters(int $value = 1) Add quarters (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealQuarter() Add one quarter to the instance (using timestamp). + * @method CarbonInterface subRealQuarters(int $value = 1) Sub quarters (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealQuarter() Sub one quarter to the instance (using timestamp). + * @method CarbonPeriod quartersUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each quarter or every X quarters if a factor is given. + * @method CarbonInterface addRealYears(int $value = 1) Add years (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealYear() Add one year to the instance (using timestamp). + * @method CarbonInterface subRealYears(int $value = 1) Sub years (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealYear() Sub one year to the instance (using timestamp). + * @method CarbonPeriod yearsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each year or every X years if a factor is given. + * @method CarbonInterface addRealDecades(int $value = 1) Add decades (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealDecade() Add one decade to the instance (using timestamp). + * @method CarbonInterface subRealDecades(int $value = 1) Sub decades (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealDecade() Sub one decade to the instance (using timestamp). + * @method CarbonPeriod decadesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each decade or every X decades if a factor is given. + * @method CarbonInterface addRealCenturies(int $value = 1) Add centuries (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealCentury() Add one century to the instance (using timestamp). + * @method CarbonInterface subRealCenturies(int $value = 1) Sub centuries (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealCentury() Sub one century to the instance (using timestamp). + * @method CarbonPeriod centuriesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each century or every X centuries if a factor is given. + * @method CarbonInterface addRealMillennia(int $value = 1) Add millennia (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMillennium() Add one millennium to the instance (using timestamp). + * @method CarbonInterface subRealMillennia(int $value = 1) Sub millennia (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMillennium() Sub one millennium to the instance (using timestamp). + * @method CarbonPeriod millenniaUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millennium or every X millennia if a factor is given. + * @method CarbonInterface roundYear(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method CarbonInterface roundYears(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method CarbonInterface floorYear(float $precision = 1) Truncate the current instance year with given precision. + * @method CarbonInterface floorYears(float $precision = 1) Truncate the current instance year with given precision. + * @method CarbonInterface ceilYear(float $precision = 1) Ceil the current instance year with given precision. + * @method CarbonInterface ceilYears(float $precision = 1) Ceil the current instance year with given precision. + * @method CarbonInterface roundMonth(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method CarbonInterface roundMonths(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method CarbonInterface floorMonth(float $precision = 1) Truncate the current instance month with given precision. + * @method CarbonInterface floorMonths(float $precision = 1) Truncate the current instance month with given precision. + * @method CarbonInterface ceilMonth(float $precision = 1) Ceil the current instance month with given precision. + * @method CarbonInterface ceilMonths(float $precision = 1) Ceil the current instance month with given precision. + * @method CarbonInterface roundDay(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method CarbonInterface roundDays(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method CarbonInterface floorDay(float $precision = 1) Truncate the current instance day with given precision. + * @method CarbonInterface floorDays(float $precision = 1) Truncate the current instance day with given precision. + * @method CarbonInterface ceilDay(float $precision = 1) Ceil the current instance day with given precision. + * @method CarbonInterface ceilDays(float $precision = 1) Ceil the current instance day with given precision. + * @method CarbonInterface roundHour(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method CarbonInterface roundHours(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method CarbonInterface floorHour(float $precision = 1) Truncate the current instance hour with given precision. + * @method CarbonInterface floorHours(float $precision = 1) Truncate the current instance hour with given precision. + * @method CarbonInterface ceilHour(float $precision = 1) Ceil the current instance hour with given precision. + * @method CarbonInterface ceilHours(float $precision = 1) Ceil the current instance hour with given precision. + * @method CarbonInterface roundMinute(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method CarbonInterface roundMinutes(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method CarbonInterface floorMinute(float $precision = 1) Truncate the current instance minute with given precision. + * @method CarbonInterface floorMinutes(float $precision = 1) Truncate the current instance minute with given precision. + * @method CarbonInterface ceilMinute(float $precision = 1) Ceil the current instance minute with given precision. + * @method CarbonInterface ceilMinutes(float $precision = 1) Ceil the current instance minute with given precision. + * @method CarbonInterface roundSecond(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method CarbonInterface roundSeconds(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method CarbonInterface floorSecond(float $precision = 1) Truncate the current instance second with given precision. + * @method CarbonInterface floorSeconds(float $precision = 1) Truncate the current instance second with given precision. + * @method CarbonInterface ceilSecond(float $precision = 1) Ceil the current instance second with given precision. + * @method CarbonInterface ceilSeconds(float $precision = 1) Ceil the current instance second with given precision. + * @method CarbonInterface roundMillennium(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method CarbonInterface roundMillennia(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method CarbonInterface floorMillennium(float $precision = 1) Truncate the current instance millennium with given precision. + * @method CarbonInterface floorMillennia(float $precision = 1) Truncate the current instance millennium with given precision. + * @method CarbonInterface ceilMillennium(float $precision = 1) Ceil the current instance millennium with given precision. + * @method CarbonInterface ceilMillennia(float $precision = 1) Ceil the current instance millennium with given precision. + * @method CarbonInterface roundCentury(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method CarbonInterface roundCenturies(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method CarbonInterface floorCentury(float $precision = 1) Truncate the current instance century with given precision. + * @method CarbonInterface floorCenturies(float $precision = 1) Truncate the current instance century with given precision. + * @method CarbonInterface ceilCentury(float $precision = 1) Ceil the current instance century with given precision. + * @method CarbonInterface ceilCenturies(float $precision = 1) Ceil the current instance century with given precision. + * @method CarbonInterface roundDecade(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method CarbonInterface roundDecades(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method CarbonInterface floorDecade(float $precision = 1) Truncate the current instance decade with given precision. + * @method CarbonInterface floorDecades(float $precision = 1) Truncate the current instance decade with given precision. + * @method CarbonInterface ceilDecade(float $precision = 1) Ceil the current instance decade with given precision. + * @method CarbonInterface ceilDecades(float $precision = 1) Ceil the current instance decade with given precision. + * @method CarbonInterface roundQuarter(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method CarbonInterface roundQuarters(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method CarbonInterface floorQuarter(float $precision = 1) Truncate the current instance quarter with given precision. + * @method CarbonInterface floorQuarters(float $precision = 1) Truncate the current instance quarter with given precision. + * @method CarbonInterface ceilQuarter(float $precision = 1) Ceil the current instance quarter with given precision. + * @method CarbonInterface ceilQuarters(float $precision = 1) Ceil the current instance quarter with given precision. + * @method CarbonInterface roundMillisecond(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method CarbonInterface roundMilliseconds(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method CarbonInterface floorMillisecond(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method CarbonInterface floorMilliseconds(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method CarbonInterface ceilMillisecond(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method CarbonInterface ceilMilliseconds(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method CarbonInterface roundMicrosecond(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method CarbonInterface roundMicroseconds(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method CarbonInterface floorMicrosecond(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method CarbonInterface floorMicroseconds(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method CarbonInterface ceilMicrosecond(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method CarbonInterface ceilMicroseconds(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method string shortAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * + * + */ +interface CarbonInterface extends DateTimeInterface, JsonSerializable +{ + /** + * Diff wording options(expressed in octal). + */ + public const NO_ZERO_DIFF = 01; + public const JUST_NOW = 02; + public const ONE_DAY_WORDS = 04; + public const TWO_DAY_WORDS = 010; + public const SEQUENTIAL_PARTS_ONLY = 020; + public const ROUND = 040; + public const FLOOR = 0100; + public const CEIL = 0200; + + /** + * Diff syntax options. + */ + public const DIFF_ABSOLUTE = 1; // backward compatibility with true + public const DIFF_RELATIVE_AUTO = 0; // backward compatibility with false + public const DIFF_RELATIVE_TO_NOW = 2; + public const DIFF_RELATIVE_TO_OTHER = 3; + + /** + * Translate string options. + */ + public const TRANSLATE_MONTHS = 1; + public const TRANSLATE_DAYS = 2; + public const TRANSLATE_UNITS = 4; + public const TRANSLATE_MERIDIEM = 8; + public const TRANSLATE_DIFF = 0x10; + public const TRANSLATE_ALL = self::TRANSLATE_MONTHS | self::TRANSLATE_DAYS | self::TRANSLATE_UNITS | self::TRANSLATE_MERIDIEM | self::TRANSLATE_DIFF; + + /** + * The day constants. + */ + public const SUNDAY = 0; + public const MONDAY = 1; + public const TUESDAY = 2; + public const WEDNESDAY = 3; + public const THURSDAY = 4; + public const FRIDAY = 5; + public const SATURDAY = 6; + + /** + * The month constants. + * These aren't used by Carbon itself but exist for + * convenience sake alone. + */ + public const JANUARY = 1; + public const FEBRUARY = 2; + public const MARCH = 3; + public const APRIL = 4; + public const MAY = 5; + public const JUNE = 6; + public const JULY = 7; + public const AUGUST = 8; + public const SEPTEMBER = 9; + public const OCTOBER = 10; + public const NOVEMBER = 11; + public const DECEMBER = 12; + + /** + * Number of X in Y. + */ + public const YEARS_PER_MILLENNIUM = 1000; + public const YEARS_PER_CENTURY = 100; + public const YEARS_PER_DECADE = 10; + public const MONTHS_PER_YEAR = 12; + public const MONTHS_PER_QUARTER = 3; + public const WEEKS_PER_YEAR = 52; + public const WEEKS_PER_MONTH = 4; + public const DAYS_PER_YEAR = 365; + public const DAYS_PER_WEEK = 7; + public const HOURS_PER_DAY = 24; + public const MINUTES_PER_HOUR = 60; + public const SECONDS_PER_MINUTE = 60; + public const MILLISECONDS_PER_SECOND = 1000; + public const MICROSECONDS_PER_MILLISECOND = 1000; + public const MICROSECONDS_PER_SECOND = 1000000; + + /** + * Special settings to get the start of week from current locale culture. + */ + public const WEEK_DAY_AUTO = 'auto'; + + /** + * RFC7231 DateTime format. + * + * @var string + */ + public const RFC7231_FORMAT = 'D, d M Y H:i:s \G\M\T'; + + /** + * Default format to use for __toString method when type juggling occurs. + * + * @var string + */ + public const DEFAULT_TO_STRING_FORMAT = 'Y-m-d H:i:s'; + + /** + * Format for converting mocked time, includes microseconds. + * + * @var string + */ + public const MOCK_DATETIME_FORMAT = 'Y-m-d H:i:s.u'; + + /** + * Pattern detection for ->isoFormat and ::createFromIsoFormat. + * + * @var string + */ + public const ISO_FORMAT_REGEXP = '(O[YMDHhms]|[Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY?|g{1,5}|G{1,5}|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?)'; + + // + + /** + * Dynamically handle calls to the class. + * + * @param string $method magic method name called + * @param array $parameters parameters list + * + * @throws UnknownMethodException|BadMethodCallException|ReflectionException|Throwable + * + * @return mixed + */ + public function __call($method, $parameters); + + /** + * Dynamically handle calls to the class. + * + * @param string $method magic method name called + * @param array $parameters parameters list + * + * @throws BadMethodCallException + * + * @return mixed + */ + public static function __callStatic($method, $parameters); + + /** + * Update constructedObjectId on cloned. + */ + public function __clone(); + + /** + * Create a new Carbon instance. + * + * Please see the testing aids section (specifically static::setTestNow()) + * for more on the possibility of this constructor returning a test instance. + * + * @param DateTimeInterface|string|null $time + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + */ + public function __construct($time = null, $tz = null); + + /** + * Show truthy properties on var_dump(). + * + * @return array + */ + public function __debugInfo(); + + /** + * Get a part of the Carbon object + * + * @param string $name + * + * @throws UnknownGetterException + * + * @return string|int|bool|DateTimeZone|null + */ + public function __get($name); + + /** + * Check if an attribute exists on the object + * + * @param string $name + * + * @return bool + */ + public function __isset($name); + + /** + * Set a part of the Carbon object + * + * @param string $name + * @param string|int|DateTimeZone $value + * + * @throws UnknownSetterException|ReflectionException + * + * @return void + */ + public function __set($name, $value); + + /** + * The __set_state handler. + * + * @param string|array $dump + * + * @return static + */ + #[ReturnTypeWillChange] + public static function __set_state($dump); + + /** + * Returns the list of properties to dump on serialize() called on. + * + * @return array + */ + public function __sleep(); + + /** + * Format the instance as a string using the set format + * + * @example + * ``` + * echo Carbon::now(); // Carbon instances can be casted to string + * ``` + * + * @return string + */ + public function __toString(); + + /** + * Add given units or interval to the current instance. + * + * @example $date->add('hour', 3) + * @example $date->add(15, 'days') + * @example $date->add(CarbonInterval::days(4)) + * + * @param string|DateInterval|Closure|CarbonConverterInterface $unit + * @param int $value + * @param bool|null $overflow + * + * @return static + */ + #[ReturnTypeWillChange] + public function add($unit, $value = 1, $overflow = null); + + /** + * Add seconds to the instance using timestamp. Positive $value travels + * forward while negative $value travels into the past. + * + * @param string $unit + * @param int $value + * + * @return static + */ + public function addRealUnit($unit, $value = 1); + + /** + * Add given units to the current instance. + * + * @param string $unit + * @param int $value + * @param bool|null $overflow + * + * @return static + */ + public function addUnit($unit, $value = 1, $overflow = null); + + /** + * Add any unit to a new value without overflowing current other unit given. + * + * @param string $valueUnit unit name to modify + * @param int $value amount to add to the input unit + * @param string $overflowUnit unit name to not overflow + * + * @return static + */ + public function addUnitNoOverflow($valueUnit, $value, $overflowUnit); + + /** + * Get the difference in a human readable format in the current locale from an other + * instance given to now + * + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single part) + * @param int $options human diff options + * + * @return string + */ + public function ago($syntax = null, $short = false, $parts = 1, $options = null); + + /** + * Modify the current instance to the average of a given instance (default now) and the current instance + * (second-precision). + * + * @param \Carbon\Carbon|\DateTimeInterface|null $date + * + * @return static + */ + public function average($date = null); + + /** + * Clone the current instance if it's mutable. + * + * This method is convenient to ensure you don't mutate the initial object + * but avoid to make a useless copy of it if it's already immutable. + * + * @return static + */ + public function avoidMutation(); + + /** + * Determines if the instance is between two others. + * + * The third argument allow you to specify if bounds are included or not (true by default) + * but for when you including/excluding bounds may produce different results in your application, + * we recommend to use the explicit methods ->betweenIncluded() or ->betweenExcluded() instead. + * + * @example + * ``` + * Carbon::parse('2018-07-25')->between('2018-07-14', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->between('2018-08-01', '2018-08-20'); // false + * Carbon::parse('2018-07-25')->between('2018-07-25', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->between('2018-07-25', '2018-08-01', false); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 + * @param bool $equal Indicates if an equal to comparison should be done + * + * @return bool + */ + public function between($date1, $date2, $equal = true): bool; + + /** + * Determines if the instance is between two others, bounds excluded. + * + * @example + * ``` + * Carbon::parse('2018-07-25')->betweenExcluded('2018-07-14', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->betweenExcluded('2018-08-01', '2018-08-20'); // false + * Carbon::parse('2018-07-25')->betweenExcluded('2018-07-25', '2018-08-01'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 + * + * @return bool + */ + public function betweenExcluded($date1, $date2): bool; + + /** + * Determines if the instance is between two others, bounds included. + * + * @example + * ``` + * Carbon::parse('2018-07-25')->betweenIncluded('2018-07-14', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->betweenIncluded('2018-08-01', '2018-08-20'); // false + * Carbon::parse('2018-07-25')->betweenIncluded('2018-07-25', '2018-08-01'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 + * + * @return bool + */ + public function betweenIncluded($date1, $date2): bool; + + /** + * Returns either day of week + time (e.g. "Last Friday at 3:30 PM") if reference time is within 7 days, + * or a calendar date (e.g. "10/29/2017") otherwise. + * + * Language, date and time formats will change according to the current locale. + * + * @param Carbon|\DateTimeInterface|string|null $referenceTime + * @param array $formats + * + * @return string + */ + public function calendar($referenceTime = null, array $formats = []); + + /** + * Checks if the (date)time string is in a given format and valid to create a + * new instance. + * + * @example + * ``` + * Carbon::canBeCreatedFromFormat('11:12:45', 'h:i:s'); // true + * Carbon::canBeCreatedFromFormat('13:12:45', 'h:i:s'); // false + * ``` + * + * @param string $date + * @param string $format + * + * @return bool + */ + public static function canBeCreatedFromFormat($date, $format); + + /** + * Return the Carbon instance passed through, a now instance in the same timezone + * if null given or parse the input if string given. + * + * @param Carbon|\Carbon\CarbonPeriod|\Carbon\CarbonInterval|\DateInterval|\DatePeriod|DateTimeInterface|string|null $date + * + * @return static + */ + public function carbonize($date = null); + + /** + * Cast the current instance into the given class. + * + * @param string $className The $className::instance() method will be called to cast the current object. + * + * @return DateTimeInterface + */ + public function cast(string $className); + + /** + * Ceil the current instance second with given precision if specified. + * + * @param float|int|string|\DateInterval|null $precision + * + * @return CarbonInterface + */ + public function ceil($precision = 1); + + /** + * Ceil the current instance at the given unit with given precision if specified. + * + * @param string $unit + * @param float|int $precision + * + * @return CarbonInterface + */ + public function ceilUnit($unit, $precision = 1); + + /** + * Ceil the current instance week. + * + * @param int $weekStartsAt optional start allow you to specify the day of week to use to start the week + * + * @return CarbonInterface + */ + public function ceilWeek($weekStartsAt = null); + + /** + * Similar to native modify() method of DateTime but can handle more grammars. + * + * @example + * ``` + * echo Carbon::now()->change('next 2pm'); + * ``` + * + * @link https://php.net/manual/en/datetime.modify.php + * + * @param string $modifier + * + * @return static + */ + public function change($modifier); + + /** + * Cleanup properties attached to the public scope of DateTime when a dump of the date is requested. + * foreach ($date as $_) {} + * serializer($date) + * var_export($date) + * get_object_vars($date) + */ + public function cleanupDumpProperties(); + + /** + * @alias copy + * + * Get a copy of the instance. + * + * @return static + */ + public function clone(); + + /** + * Get the closest date from the instance (second-precision). + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 + * + * @return static + */ + public function closest($date1, $date2); + + /** + * Get a copy of the instance. + * + * @return static + */ + public function copy(); + + /** + * Create a new Carbon instance from a specific date and time. + * + * If any of $year, $month or $day are set to null their now() values will + * be used. + * + * If $hour is null it will be set to its now() value and the default + * values for $minute and $second will be their now() values. + * + * If $hour is not null then the default values for $minute and $second + * will be 0. + * + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param int|null $hour + * @param int|null $minute + * @param int|null $second + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static|false + */ + public static function create($year = 0, $month = 1, $day = 1, $hour = 0, $minute = 0, $second = 0, $tz = null); + + /** + * Create a Carbon instance from just a date. The time portion is set to now. + * + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function createFromDate($year = null, $month = null, $day = null, $tz = null); + + /** + * Create a Carbon instance from a specific format. + * + * @param string $format Datetime format + * @param string $time + * @param DateTimeZone|string|false|null $tz + * + * @throws InvalidFormatException + * + * @return static|false + */ + #[ReturnTypeWillChange] + public static function createFromFormat($format, $time, $tz = null); + + /** + * Create a Carbon instance from a specific ISO format (same replacements as ->isoFormat()). + * + * @param string $format Datetime format + * @param string $time + * @param DateTimeZone|string|false|null $tz optional timezone + * @param string|null $locale locale to be used for LTS, LT, LL, LLL, etc. macro-formats (en by fault, unneeded if no such macro-format in use) + * @param \Symfony\Component\Translation\TranslatorInterface $translator optional custom translator to use for macro-formats + * + * @throws InvalidFormatException + * + * @return static|false + */ + public static function createFromIsoFormat($format, $time, $tz = null, $locale = 'en', $translator = null); + + /** + * Create a Carbon instance from a specific format and a string in a given language. + * + * @param string $format Datetime format + * @param string $locale + * @param string $time + * @param DateTimeZone|string|false|null $tz + * + * @throws InvalidFormatException + * + * @return static|false + */ + public static function createFromLocaleFormat($format, $locale, $time, $tz = null); + + /** + * Create a Carbon instance from a specific ISO format and a string in a given language. + * + * @param string $format Datetime ISO format + * @param string $locale + * @param string $time + * @param DateTimeZone|string|false|null $tz + * + * @throws InvalidFormatException + * + * @return static|false + */ + public static function createFromLocaleIsoFormat($format, $locale, $time, $tz = null); + + /** + * Create a Carbon instance from just a time. The date portion is set to today. + * + * @param int|null $hour + * @param int|null $minute + * @param int|null $second + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function createFromTime($hour = 0, $minute = 0, $second = 0, $tz = null); + + /** + * Create a Carbon instance from a time string. The date portion is set to today. + * + * @param string $time + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function createFromTimeString($time, $tz = null); + + /** + * Create a Carbon instance from a timestamp and set the timezone (use default one if not specified). + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $timestamp + * @param \DateTimeZone|string|null $tz + * + * @return static + */ + public static function createFromTimestamp($timestamp, $tz = null); + + /** + * Create a Carbon instance from a timestamp in milliseconds. + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $timestamp + * @param \DateTimeZone|string|null $tz + * + * @return static + */ + public static function createFromTimestampMs($timestamp, $tz = null); + + /** + * Create a Carbon instance from a timestamp in milliseconds. + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $timestamp + * + * @return static + */ + public static function createFromTimestampMsUTC($timestamp); + + /** + * Create a Carbon instance from an timestamp keeping the timezone to UTC. + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $timestamp + * + * @return static + */ + public static function createFromTimestampUTC($timestamp); + + /** + * Create a Carbon instance from just a date. The time portion is set to midnight. + * + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function createMidnightDate($year = null, $month = null, $day = null, $tz = null); + + /** + * Create a new safe Carbon instance from a specific date and time. + * + * If any of $year, $month or $day are set to null their now() values will + * be used. + * + * If $hour is null it will be set to its now() value and the default + * values for $minute and $second will be their now() values. + * + * If $hour is not null then the default values for $minute and $second + * will be 0. + * + * If one of the set values is not valid, an InvalidDateException + * will be thrown. + * + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param int|null $hour + * @param int|null $minute + * @param int|null $second + * @param DateTimeZone|string|null $tz + * + * @throws InvalidDateException + * + * @return static|false + */ + public static function createSafe($year = null, $month = null, $day = null, $hour = null, $minute = null, $second = null, $tz = null); + + /** + * Create a new Carbon instance from a specific date and time using strict validation. + * + * @see create() + * + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param int|null $hour + * @param int|null $minute + * @param int|null $second + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function createStrict(?int $year = 0, ?int $month = 1, ?int $day = 1, ?int $hour = 0, ?int $minute = 0, ?int $second = 0, $tz = null); + + /** + * Get/set the day of year. + * + * @param int|null $value new value for day of year if using as setter. + * + * @return static|int + */ + public function dayOfYear($value = null); + + /** + * Get the difference as a CarbonInterval instance. + * Return relative interval (negative if $absolute flag is not set to true and the given date is before + * current one). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return CarbonInterval + */ + public function diffAsCarbonInterval($date = null, $absolute = true); + + /** + * Get the difference by the given interval using a filter closure. + * + * @param CarbonInterval $ci An interval to traverse by + * @param Closure $callback + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffFiltered(CarbonInterval $ci, Closure $callback, $date = null, $absolute = true); + + /** + * Get the difference in a human readable format in the current locale from current instance to an other + * instance given (or now if null given). + * + * @example + * ``` + * echo Carbon::tomorrow()->diffForHumans() . "\n"; + * echo Carbon::tomorrow()->diffForHumans(['parts' => 2]) . "\n"; + * echo Carbon::tomorrow()->diffForHumans(['parts' => 3, 'join' => true]) . "\n"; + * echo Carbon::tomorrow()->diffForHumans(Carbon::yesterday()) . "\n"; + * echo Carbon::tomorrow()->diffForHumans(Carbon::yesterday(), ['short' => true]) . "\n"; + * ``` + * + * @param Carbon|\DateTimeInterface|string|array|null $other if array passed, will be used as parameters array, see $syntax below; + * if null passed, now will be used as comparison reference; + * if any other type, it will be converted to date and used as reference. + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'skip' entry, list of units to skip (array of strings or a single string, + * ` it can be the unit name (singular or plural) or its shortcut + * ` (y, m, w, d, h, min, s, ms, µs). + * - 'aUnit' entry, prefer "an hour" over "1 hour" if true + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * - 'other' entry (see above) + * - 'minimumUnit' entry determines the smallest unit of time to display can be long or + * ` short form of the units, e.g. 'hour' or 'h' (default value: s) + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single unit) + * @param int $options human diff options + * + * @return string + */ + public function diffForHumans($other = null, $syntax = null, $short = false, $parts = 1, $options = null); + + /** + * Get the difference in days rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInDays($date = null, $absolute = true); + + /** + * Get the difference in days using a filter closure rounded down. + * + * @param Closure $callback + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInDaysFiltered(Closure $callback, $date = null, $absolute = true); + + /** + * Get the difference in hours rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInHours($date = null, $absolute = true); + + /** + * Get the difference in hours using a filter closure rounded down. + * + * @param Closure $callback + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInHoursFiltered(Closure $callback, $date = null, $absolute = true); + + /** + * Get the difference in microseconds. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInMicroseconds($date = null, $absolute = true); + + /** + * Get the difference in milliseconds rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInMilliseconds($date = null, $absolute = true); + + /** + * Get the difference in minutes rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInMinutes($date = null, $absolute = true); + + /** + * Get the difference in months rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInMonths($date = null, $absolute = true); + + /** + * Get the difference in quarters rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInQuarters($date = null, $absolute = true); + + /** + * Get the difference in hours rounded down using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealHours($date = null, $absolute = true); + + /** + * Get the difference in microseconds using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealMicroseconds($date = null, $absolute = true); + + /** + * Get the difference in milliseconds rounded down using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealMilliseconds($date = null, $absolute = true); + + /** + * Get the difference in minutes rounded down using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealMinutes($date = null, $absolute = true); + + /** + * Get the difference in seconds using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealSeconds($date = null, $absolute = true); + + /** + * Get the difference in seconds rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInSeconds($date = null, $absolute = true); + + /** + * Get the difference in weekdays rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInWeekdays($date = null, $absolute = true); + + /** + * Get the difference in weekend days using a filter rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInWeekendDays($date = null, $absolute = true); + + /** + * Get the difference in weeks rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInWeeks($date = null, $absolute = true); + + /** + * Get the difference in years + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInYears($date = null, $absolute = true); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @see settings + * + * @param int $humanDiffOption + */ + public static function disableHumanDiffOption($humanDiffOption); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @see settings + * + * @param int $humanDiffOption + */ + public static function enableHumanDiffOption($humanDiffOption); + + /** + * Modify to end of current given unit. + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16.334455') + * ->startOf('month') + * ->endOf('week', Carbon::FRIDAY); + * ``` + * + * @param string $unit + * @param array $params + * + * @return static + */ + public function endOf($unit, ...$params); + + /** + * Resets the date to end of the century and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfCentury(); + * ``` + * + * @return static + */ + public function endOfCentury(); + + /** + * Resets the time to 23:59:59.999999 end of day + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfDay(); + * ``` + * + * @return static + */ + public function endOfDay(); + + /** + * Resets the date to end of the decade and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfDecade(); + * ``` + * + * @return static + */ + public function endOfDecade(); + + /** + * Modify to end of current hour, minutes and seconds become 59 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfHour(); + * ``` + * + * @return static + */ + public function endOfHour(); + + /** + * Resets the date to end of the millennium and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfMillennium(); + * ``` + * + * @return static + */ + public function endOfMillennium(); + + /** + * Modify to end of current minute, seconds become 59 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfMinute(); + * ``` + * + * @return static + */ + public function endOfMinute(); + + /** + * Resets the date to end of the month and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfMonth(); + * ``` + * + * @return static + */ + public function endOfMonth(); + + /** + * Resets the date to end of the quarter and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfQuarter(); + * ``` + * + * @return static + */ + public function endOfQuarter(); + + /** + * Modify to end of current second, microseconds become 999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16.334455') + * ->endOfSecond() + * ->format('H:i:s.u'); + * ``` + * + * @return static + */ + public function endOfSecond(); + + /** + * Resets the date to end of week (defined in $weekEndsAt) and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfWeek() . "\n"; + * echo Carbon::parse('2018-07-25 12:45:16')->locale('ar')->endOfWeek() . "\n"; + * echo Carbon::parse('2018-07-25 12:45:16')->endOfWeek(Carbon::SATURDAY) . "\n"; + * ``` + * + * @param int $weekEndsAt optional start allow you to specify the day of week to use to end the week + * + * @return static + */ + public function endOfWeek($weekEndsAt = null); + + /** + * Resets the date to end of the year and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfYear(); + * ``` + * + * @return static + */ + public function endOfYear(); + + /** + * Determines if the instance is equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->eq('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->eq(Carbon::parse('2018-07-25 12:45:16')); // true + * Carbon::parse('2018-07-25 12:45:16')->eq('2018-07-25 12:45:17'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see equalTo() + * + * @return bool + */ + public function eq($date): bool; + + /** + * Determines if the instance is equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->equalTo('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->equalTo(Carbon::parse('2018-07-25 12:45:16')); // true + * Carbon::parse('2018-07-25 12:45:16')->equalTo('2018-07-25 12:45:17'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function equalTo($date): bool; + + /** + * Set the current locale to the given, execute the passed function, reset the locale to previous one, + * then return the result of the closure (or null if the closure was void). + * + * @param string $locale locale ex. en + * @param callable $func + * + * @return mixed + */ + public static function executeWithLocale($locale, $func); + + /** + * Get the farthest date from the instance (second-precision). + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 + * + * @return static + */ + public function farthest($date1, $date2); + + /** + * Modify to the first occurrence of a given day of the week + * in the current month. If no dayOfWeek is provided, modify to the + * first day of the current month. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek + * + * @return static + */ + public function firstOfMonth($dayOfWeek = null); + + /** + * Modify to the first occurrence of a given day of the week + * in the current quarter. If no dayOfWeek is provided, modify to the + * first day of the current quarter. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek day of the week default null + * + * @return static + */ + public function firstOfQuarter($dayOfWeek = null); + + /** + * Modify to the first occurrence of a given day of the week + * in the current year. If no dayOfWeek is provided, modify to the + * first day of the current year. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek day of the week default null + * + * @return static + */ + public function firstOfYear($dayOfWeek = null); + + /** + * Get the difference in days as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInDays($date = null, $absolute = true); + + /** + * Get the difference in hours as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInHours($date = null, $absolute = true); + + /** + * Get the difference in minutes as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInMinutes($date = null, $absolute = true); + + /** + * Get the difference in months as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInMonths($date = null, $absolute = true); + + /** + * Get the difference in days as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealDays($date = null, $absolute = true); + + /** + * Get the difference in hours as float (microsecond-precision) using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealHours($date = null, $absolute = true); + + /** + * Get the difference in minutes as float (microsecond-precision) using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealMinutes($date = null, $absolute = true); + + /** + * Get the difference in months as float (microsecond-precision) using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealMonths($date = null, $absolute = true); + + /** + * Get the difference in seconds as float (microsecond-precision) using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealSeconds($date = null, $absolute = true); + + /** + * Get the difference in weeks as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealWeeks($date = null, $absolute = true); + + /** + * Get the difference in year as float (microsecond-precision) using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealYears($date = null, $absolute = true); + + /** + * Get the difference in seconds as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInSeconds($date = null, $absolute = true); + + /** + * Get the difference in weeks as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInWeeks($date = null, $absolute = true); + + /** + * Get the difference in year as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInYears($date = null, $absolute = true); + + /** + * Round the current instance second with given precision if specified. + * + * @param float|int|string|\DateInterval|null $precision + * + * @return CarbonInterface + */ + public function floor($precision = 1); + + /** + * Truncate the current instance at the given unit with given precision if specified. + * + * @param string $unit + * @param float|int $precision + * + * @return CarbonInterface + */ + public function floorUnit($unit, $precision = 1); + + /** + * Truncate the current instance week. + * + * @param int $weekStartsAt optional start allow you to specify the day of week to use to start the week + * + * @return CarbonInterface + */ + public function floorWeek($weekStartsAt = null); + + /** + * Format the instance with the current locale. You can set the current + * locale using setlocale() https://php.net/setlocale. + * + * @deprecated It uses OS language package and strftime() which is deprecated since PHP 8.1. + * Use ->isoFormat() instead. + * Deprecated since 2.55.0 + * + * @param string $format + * + * @return string + */ + public function formatLocalized($format); + + /** + * @alias diffForHumans + * + * Get the difference in a human readable format in the current locale from current instance to an other + * instance given (or now if null given). + * + * @param Carbon|\DateTimeInterface|string|array|null $other if array passed, will be used as parameters array, see $syntax below; + * if null passed, now will be used as comparison reference; + * if any other type, it will be converted to date and used as reference. + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * - 'other' entry (see above) + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single unit) + * @param int $options human diff options + * + * @return string + */ + public function from($other = null, $syntax = null, $short = false, $parts = 1, $options = null); + + /** + * Get the difference in a human readable format in the current locale from current + * instance to now. + * + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single unit) + * @param int $options human diff options + * + * @return string + */ + public function fromNow($syntax = null, $short = false, $parts = 1, $options = null); + + /** + * Create an instance from a serialized string. + * + * @param string $value + * + * @throws InvalidFormatException + * + * @return static + */ + public static function fromSerialized($value); + + /** + * Register a custom macro. + * + * @param object|callable $macro + * @param int $priority marco with higher priority is tried first + * + * @return void + */ + public static function genericMacro($macro, $priority = 0); + + /** + * Get a part of the Carbon object + * + * @param string $name + * + * @throws UnknownGetterException + * + * @return string|int|bool|DateTimeZone|null + */ + public function get($name); + + /** + * Returns the alternative number for a given date property if available in the current locale. + * + * @param string $key date property + * + * @return string + */ + public function getAltNumber(string $key): string; + + /** + * Returns the list of internally available locales and already loaded custom locales. + * (It will ignore custom translator dynamic loading.) + * + * @return array + */ + public static function getAvailableLocales(); + + /** + * Returns list of Language object for each available locale. This object allow you to get the ISO name, native + * name, region and variant of the locale. + * + * @return Language[] + */ + public static function getAvailableLocalesInfo(); + + /** + * Returns list of calendar formats for ISO formatting. + * + * @param string|null $locale current locale used if null + * + * @return array + */ + public function getCalendarFormats($locale = null); + + /** + * Get the days of the week + * + * @return array + */ + public static function getDays(); + + /** + * Get the fallback locale. + * + * @see https://symfony.com/doc/current/components/translation.html#fallback-locales + * + * @return string|null + */ + public static function getFallbackLocale(); + + /** + * List of replacements from date() format to isoFormat(). + * + * @return array + */ + public static function getFormatsToIsoReplacements(); + + /** + * Return default humanDiff() options (merged flags as integer). + * + * @return int + */ + public static function getHumanDiffOptions(); + + /** + * Returns list of locale formats for ISO formatting. + * + * @param string|null $locale current locale used if null + * + * @return array + */ + public function getIsoFormats($locale = null); + + /** + * Returns list of locale units for ISO formatting. + * + * @return array + */ + public static function getIsoUnits(); + + /** + * {@inheritdoc} + * + * @return array + */ + #[ReturnTypeWillChange] + public static function getLastErrors(); + + /** + * Get the raw callable macro registered globally or locally for a given name. + * + * @param string $name + * + * @return callable|null + */ + public function getLocalMacro($name); + + /** + * Get the translator of the current instance or the default if none set. + * + * @return \Symfony\Component\Translation\TranslatorInterface + */ + public function getLocalTranslator(); + + /** + * Get the current translator locale. + * + * @return string + */ + public static function getLocale(); + + /** + * Get the raw callable macro registered globally for a given name. + * + * @param string $name + * + * @return callable|null + */ + public static function getMacro($name); + + /** + * get midday/noon hour + * + * @return int + */ + public static function getMidDayAt(); + + /** + * Returns the offset hour and minute formatted with +/- and a given separator (":" by default). + * For example, if the time zone is 9 hours 30 minutes, you'll get "+09:30", with "@@" as first + * argument, "+09@@30", with "" as first argument, "+0930". Negative offset will return something + * like "-12:00". + * + * @param string $separator string to place between hours and minutes (":" by default) + * + * @return string + */ + public function getOffsetString($separator = ':'); + + /** + * Returns a unit of the instance padded with 0 by default or any other string if specified. + * + * @param string $unit Carbon unit name + * @param int $length Length of the output (2 by default) + * @param string $padString String to use for padding ("0" by default) + * @param int $padType Side(s) to pad (STR_PAD_LEFT by default) + * + * @return string + */ + public function getPaddedUnit($unit, $length = 2, $padString = '0', $padType = 0); + + /** + * Returns a timestamp rounded with the given precision (6 by default). + * + * @example getPreciseTimestamp() 1532087464437474 (microsecond maximum precision) + * @example getPreciseTimestamp(6) 1532087464437474 + * @example getPreciseTimestamp(5) 153208746443747 (1/100000 second precision) + * @example getPreciseTimestamp(4) 15320874644375 (1/10000 second precision) + * @example getPreciseTimestamp(3) 1532087464437 (millisecond precision) + * @example getPreciseTimestamp(2) 153208746444 (1/100 second precision) + * @example getPreciseTimestamp(1) 15320874644 (1/10 second precision) + * @example getPreciseTimestamp(0) 1532087464 (second precision) + * @example getPreciseTimestamp(-1) 153208746 (10 second precision) + * @example getPreciseTimestamp(-2) 15320875 (100 second precision) + * + * @param int $precision + * + * @return float + */ + public function getPreciseTimestamp($precision = 6); + + /** + * Returns current local settings. + * + * @return array + */ + public function getSettings(); + + /** + * Get the Carbon instance (real or mock) to be returned when a "now" + * instance is created. + * + * @return Closure|static the current instance used for testing + */ + public static function getTestNow(); + + /** + * Return a format from H:i to H:i:s.u according to given unit precision. + * + * @param string $unitPrecision "minute", "second", "millisecond" or "microsecond" + * + * @return string + */ + public static function getTimeFormatByPrecision($unitPrecision); + + /** + * Returns the timestamp with millisecond precision. + * + * @return int + */ + public function getTimestampMs(); + + /** + * Get the translation of the current week day name (with context for languages with multiple forms). + * + * @param string|null $context whole format string + * @param string $keySuffix "", "_short" or "_min" + * @param string|null $defaultValue default value if translation missing + * + * @return string + */ + public function getTranslatedDayName($context = null, $keySuffix = '', $defaultValue = null); + + /** + * Get the translation of the current abbreviated week day name (with context for languages with multiple forms). + * + * @param string|null $context whole format string + * + * @return string + */ + public function getTranslatedMinDayName($context = null); + + /** + * Get the translation of the current month day name (with context for languages with multiple forms). + * + * @param string|null $context whole format string + * @param string $keySuffix "" or "_short" + * @param string|null $defaultValue default value if translation missing + * + * @return string + */ + public function getTranslatedMonthName($context = null, $keySuffix = '', $defaultValue = null); + + /** + * Get the translation of the current short week day name (with context for languages with multiple forms). + * + * @param string|null $context whole format string + * + * @return string + */ + public function getTranslatedShortDayName($context = null); + + /** + * Get the translation of the current short month day name (with context for languages with multiple forms). + * + * @param string|null $context whole format string + * + * @return string + */ + public function getTranslatedShortMonthName($context = null); + + /** + * Returns raw translation message for a given key. + * + * @param string $key key to find + * @param string|null $locale current locale used if null + * @param string|null $default default value if translation returns the key + * @param \Symfony\Component\Translation\TranslatorInterface $translator an optional translator to use + * + * @return string + */ + public function getTranslationMessage(string $key, ?string $locale = null, ?string $default = null, $translator = null); + + /** + * Returns raw translation message for a given key. + * + * @param \Symfony\Component\Translation\TranslatorInterface $translator the translator to use + * @param string $key key to find + * @param string|null $locale current locale used if null + * @param string|null $default default value if translation returns the key + * + * @return string + */ + public static function getTranslationMessageWith($translator, string $key, ?string $locale = null, ?string $default = null); + + /** + * Get the default translator instance in use. + * + * @return \Symfony\Component\Translation\TranslatorInterface + */ + public static function getTranslator(); + + /** + * Get the last day of week + * + * @return int + */ + public static function getWeekEndsAt(); + + /** + * Get the first day of week + * + * @return int + */ + public static function getWeekStartsAt(); + + /** + * Get weekend days + * + * @return array + */ + public static function getWeekendDays(); + + /** + * Determines if the instance is greater (after) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->greaterThan('2018-07-25 12:45:15'); // true + * Carbon::parse('2018-07-25 12:45:16')->greaterThan('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->greaterThan('2018-07-25 12:45:17'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function greaterThan($date): bool; + + /** + * Determines if the instance is greater (after) than or equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->greaterThanOrEqualTo('2018-07-25 12:45:15'); // true + * Carbon::parse('2018-07-25 12:45:16')->greaterThanOrEqualTo('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->greaterThanOrEqualTo('2018-07-25 12:45:17'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function greaterThanOrEqualTo($date): bool; + + /** + * Determines if the instance is greater (after) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->gt('2018-07-25 12:45:15'); // true + * Carbon::parse('2018-07-25 12:45:16')->gt('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->gt('2018-07-25 12:45:17'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see greaterThan() + * + * @return bool + */ + public function gt($date): bool; + + /** + * Determines if the instance is greater (after) than or equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->gte('2018-07-25 12:45:15'); // true + * Carbon::parse('2018-07-25 12:45:16')->gte('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->gte('2018-07-25 12:45:17'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see greaterThanOrEqualTo() + * + * @return bool + */ + public function gte($date): bool; + + /** + * Checks if the (date)time string is in a given format. + * + * @example + * ``` + * Carbon::hasFormat('11:12:45', 'h:i:s'); // true + * Carbon::hasFormat('13:12:45', 'h:i:s'); // false + * ``` + * + * @param string $date + * @param string $format + * + * @return bool + */ + public static function hasFormat($date, $format); + + /** + * Checks if the (date)time string is in a given format. + * + * @example + * ``` + * Carbon::hasFormatWithModifiers('31/08/2015', 'd#m#Y'); // true + * Carbon::hasFormatWithModifiers('31/08/2015', 'm#d#Y'); // false + * ``` + * + * @param string $date + * @param string $format + * + * @return bool + */ + public static function hasFormatWithModifiers($date, $format): bool; + + /** + * Checks if macro is registered globally or locally. + * + * @param string $name + * + * @return bool + */ + public function hasLocalMacro($name); + + /** + * Return true if the current instance has its own translator. + * + * @return bool + */ + public function hasLocalTranslator(); + + /** + * Checks if macro is registered globally. + * + * @param string $name + * + * @return bool + */ + public static function hasMacro($name); + + /** + * Determine if a time string will produce a relative date. + * + * @param string $time + * + * @return bool true if time match a relative date, false if absolute or invalid time string + */ + public static function hasRelativeKeywords($time); + + /** + * Determine if there is a valid test instance set. A valid test instance + * is anything that is not null. + * + * @return bool true if there is a test instance, otherwise false + */ + public static function hasTestNow(); + + /** + * Create a Carbon instance from a DateTime one. + * + * @param DateTimeInterface $date + * + * @return static + */ + public static function instance($date); + + /** + * Returns true if the current date matches the given string. + * + * @example + * ``` + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('2019')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('2018')); // false + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('2019-06')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('06-02')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('2019-06-02')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('Sunday')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('June')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('12:23')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('12:23:45')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('12:23:00')); // false + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('12h')); // true + * var_dump(Carbon::parse('2019-06-02 15:23:45')->is('3pm')); // true + * var_dump(Carbon::parse('2019-06-02 15:23:45')->is('3am')); // false + * ``` + * + * @param string $tester day name, month name, hour, date, etc. as string + * + * @return bool + */ + public function is(string $tester); + + /** + * Determines if the instance is greater (after) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->isAfter('2018-07-25 12:45:15'); // true + * Carbon::parse('2018-07-25 12:45:16')->isAfter('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->isAfter('2018-07-25 12:45:17'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see greaterThan() + * + * @return bool + */ + public function isAfter($date): bool; + + /** + * Determines if the instance is less (before) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->isBefore('2018-07-25 12:45:15'); // false + * Carbon::parse('2018-07-25 12:45:16')->isBefore('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->isBefore('2018-07-25 12:45:17'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see lessThan() + * + * @return bool + */ + public function isBefore($date): bool; + + /** + * Determines if the instance is between two others + * + * @example + * ``` + * Carbon::parse('2018-07-25')->isBetween('2018-07-14', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->isBetween('2018-08-01', '2018-08-20'); // false + * Carbon::parse('2018-07-25')->isBetween('2018-07-25', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->isBetween('2018-07-25', '2018-08-01', false); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 + * @param bool $equal Indicates if an equal to comparison should be done + * + * @return bool + */ + public function isBetween($date1, $date2, $equal = true): bool; + + /** + * Check if its the birthday. Compares the date/month values of the two dates. + * + * @example + * ``` + * Carbon::now()->subYears(5)->isBirthday(); // true + * Carbon::now()->subYears(5)->subDay()->isBirthday(); // false + * Carbon::parse('2019-06-05')->isBirthday(Carbon::parse('2001-06-05')); // true + * Carbon::parse('2019-06-05')->isBirthday(Carbon::parse('2001-06-06')); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use current day. + * + * @return bool + */ + public function isBirthday($date = null); + + /** + * Determines if the instance is in the current unit given. + * + * @example + * ``` + * Carbon::now()->isCurrentUnit('hour'); // true + * Carbon::now()->subHours(2)->isCurrentUnit('hour'); // false + * ``` + * + * @param string $unit The unit to test. + * + * @throws BadMethodCallException + * + * @return bool + */ + public function isCurrentUnit($unit); + + /** + * Checks if this day is a specific day of the week. + * + * @example + * ``` + * Carbon::parse('2019-07-17')->isDayOfWeek(Carbon::WEDNESDAY); // true + * Carbon::parse('2019-07-17')->isDayOfWeek(Carbon::FRIDAY); // false + * Carbon::parse('2019-07-17')->isDayOfWeek('Wednesday'); // true + * Carbon::parse('2019-07-17')->isDayOfWeek('Friday'); // false + * ``` + * + * @param int $dayOfWeek + * + * @return bool + */ + public function isDayOfWeek($dayOfWeek); + + /** + * Check if the instance is end of day. + * + * @example + * ``` + * Carbon::parse('2019-02-28 23:59:59.999999')->isEndOfDay(); // true + * Carbon::parse('2019-02-28 23:59:59.123456')->isEndOfDay(); // true + * Carbon::parse('2019-02-28 23:59:59')->isEndOfDay(); // true + * Carbon::parse('2019-02-28 23:59:58.999999')->isEndOfDay(); // false + * Carbon::parse('2019-02-28 23:59:59.999999')->isEndOfDay(true); // true + * Carbon::parse('2019-02-28 23:59:59.123456')->isEndOfDay(true); // false + * Carbon::parse('2019-02-28 23:59:59')->isEndOfDay(true); // false + * ``` + * + * @param bool $checkMicroseconds check time at microseconds precision + * + * @return bool + */ + public function isEndOfDay($checkMicroseconds = false); + + /** + * Returns true if the date was created using CarbonImmutable::endOfTime() + * + * @return bool + */ + public function isEndOfTime(): bool; + + /** + * Determines if the instance is in the future, ie. greater (after) than now. + * + * @example + * ``` + * Carbon::now()->addHours(5)->isFuture(); // true + * Carbon::now()->subHours(5)->isFuture(); // false + * ``` + * + * @return bool + */ + public function isFuture(); + + /** + * Returns true if the current class/instance is immutable. + * + * @return bool + */ + public static function isImmutable(); + + /** + * Check if today is the last day of the Month + * + * @example + * ``` + * Carbon::parse('2019-02-28')->isLastOfMonth(); // true + * Carbon::parse('2019-03-28')->isLastOfMonth(); // false + * Carbon::parse('2019-03-30')->isLastOfMonth(); // false + * Carbon::parse('2019-03-31')->isLastOfMonth(); // true + * Carbon::parse('2019-04-30')->isLastOfMonth(); // true + * ``` + * + * @return bool + */ + public function isLastOfMonth(); + + /** + * Determines if the instance is a leap year. + * + * @example + * ``` + * Carbon::parse('2020-01-01')->isLeapYear(); // true + * Carbon::parse('2019-01-01')->isLeapYear(); // false + * ``` + * + * @return bool + */ + public function isLeapYear(); + + /** + * Determines if the instance is a long year + * + * @example + * ``` + * Carbon::parse('2015-01-01')->isLongYear(); // true + * Carbon::parse('2016-01-01')->isLongYear(); // false + * ``` + * + * @see https://en.wikipedia.org/wiki/ISO_8601#Week_dates + * + * @return bool + */ + public function isLongYear(); + + /** + * Check if the instance is midday. + * + * @example + * ``` + * Carbon::parse('2019-02-28 11:59:59.999999')->isMidday(); // false + * Carbon::parse('2019-02-28 12:00:00')->isMidday(); // true + * Carbon::parse('2019-02-28 12:00:00.999999')->isMidday(); // true + * Carbon::parse('2019-02-28 12:00:01')->isMidday(); // false + * ``` + * + * @return bool + */ + public function isMidday(); + + /** + * Check if the instance is start of day / midnight. + * + * @example + * ``` + * Carbon::parse('2019-02-28 00:00:00')->isMidnight(); // true + * Carbon::parse('2019-02-28 00:00:00.999999')->isMidnight(); // true + * Carbon::parse('2019-02-28 00:00:01')->isMidnight(); // false + * ``` + * + * @return bool + */ + public function isMidnight(); + + /** + * Returns true if a property can be changed via setter. + * + * @param string $unit + * + * @return bool + */ + public static function isModifiableUnit($unit); + + /** + * Returns true if the current class/instance is mutable. + * + * @return bool + */ + public static function isMutable(); + + /** + * Determines if the instance is in the past, ie. less (before) than now. + * + * @example + * ``` + * Carbon::now()->subHours(5)->isPast(); // true + * Carbon::now()->addHours(5)->isPast(); // false + * ``` + * + * @return bool + */ + public function isPast(); + + /** + * Compares the formatted values of the two dates. + * + * @example + * ``` + * Carbon::parse('2019-06-13')->isSameAs('Y-d', Carbon::parse('2019-12-13')); // true + * Carbon::parse('2019-06-13')->isSameAs('Y-d', Carbon::parse('2019-06-14')); // false + * ``` + * + * @param string $format date formats to compare. + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date instance to compare with or null to use current day. + * + * @return bool + */ + public function isSameAs($format, $date = null); + + /** + * Checks if the passed in date is in the same month as the instance´s month. + * + * @example + * ``` + * Carbon::parse('2019-01-12')->isSameMonth(Carbon::parse('2019-01-01')); // true + * Carbon::parse('2019-01-12')->isSameMonth(Carbon::parse('2019-02-01')); // false + * Carbon::parse('2019-01-12')->isSameMonth(Carbon::parse('2018-01-01')); // false + * Carbon::parse('2019-01-12')->isSameMonth(Carbon::parse('2018-01-01'), false); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use the current date. + * @param bool $ofSameYear Check if it is the same month in the same year. + * + * @return bool + */ + public function isSameMonth($date = null, $ofSameYear = true); + + /** + * Checks if the passed in date is in the same quarter as the instance quarter (and year if needed). + * + * @example + * ``` + * Carbon::parse('2019-01-12')->isSameQuarter(Carbon::parse('2019-03-01')); // true + * Carbon::parse('2019-01-12')->isSameQuarter(Carbon::parse('2019-04-01')); // false + * Carbon::parse('2019-01-12')->isSameQuarter(Carbon::parse('2018-03-01')); // false + * Carbon::parse('2019-01-12')->isSameQuarter(Carbon::parse('2018-03-01'), false); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date The instance to compare with or null to use current day. + * @param bool $ofSameYear Check if it is the same month in the same year. + * + * @return bool + */ + public function isSameQuarter($date = null, $ofSameYear = true); + + /** + * Determines if the instance is in the current unit given. + * + * @example + * ``` + * Carbon::parse('2019-01-13')->isSameUnit('year', Carbon::parse('2019-12-25')); // true + * Carbon::parse('2018-12-13')->isSameUnit('year', Carbon::parse('2019-12-25')); // false + * ``` + * + * @param string $unit singular unit string + * @param \Carbon\Carbon|\DateTimeInterface|null $date instance to compare with or null to use current day. + * + * @throws BadComparisonUnitException + * + * @return bool + */ + public function isSameUnit($unit, $date = null); + + /** + * Check if the instance is start of day / midnight. + * + * @example + * ``` + * Carbon::parse('2019-02-28 00:00:00')->isStartOfDay(); // true + * Carbon::parse('2019-02-28 00:00:00.999999')->isStartOfDay(); // true + * Carbon::parse('2019-02-28 00:00:01')->isStartOfDay(); // false + * Carbon::parse('2019-02-28 00:00:00.000000')->isStartOfDay(true); // true + * Carbon::parse('2019-02-28 00:00:00.000012')->isStartOfDay(true); // false + * ``` + * + * @param bool $checkMicroseconds check time at microseconds precision + * + * @return bool + */ + public function isStartOfDay($checkMicroseconds = false); + + /** + * Returns true if the date was created using CarbonImmutable::startOfTime() + * + * @return bool + */ + public function isStartOfTime(): bool; + + /** + * Returns true if the strict mode is globally in use, false else. + * (It can be overridden in specific instances.) + * + * @return bool + */ + public static function isStrictModeEnabled(); + + /** + * Determines if the instance is today. + * + * @example + * ``` + * Carbon::today()->isToday(); // true + * Carbon::tomorrow()->isToday(); // false + * ``` + * + * @return bool + */ + public function isToday(); + + /** + * Determines if the instance is tomorrow. + * + * @example + * ``` + * Carbon::tomorrow()->isTomorrow(); // true + * Carbon::yesterday()->isTomorrow(); // false + * ``` + * + * @return bool + */ + public function isTomorrow(); + + /** + * Determines if the instance is a weekday. + * + * @example + * ``` + * Carbon::parse('2019-07-14')->isWeekday(); // false + * Carbon::parse('2019-07-15')->isWeekday(); // true + * ``` + * + * @return bool + */ + public function isWeekday(); + + /** + * Determines if the instance is a weekend day. + * + * @example + * ``` + * Carbon::parse('2019-07-14')->isWeekend(); // true + * Carbon::parse('2019-07-15')->isWeekend(); // false + * ``` + * + * @return bool + */ + public function isWeekend(); + + /** + * Determines if the instance is yesterday. + * + * @example + * ``` + * Carbon::yesterday()->isYesterday(); // true + * Carbon::tomorrow()->isYesterday(); // false + * ``` + * + * @return bool + */ + public function isYesterday(); + + /** + * Format in the current language using ISO replacement patterns. + * + * @param string $format + * @param string|null $originalFormat provide context if a chunk has been passed alone + * + * @return string + */ + public function isoFormat(string $format, ?string $originalFormat = null): string; + + /** + * Get/set the week number using given first day of week and first + * day of year included in the first week. Or use ISO format if no settings + * given. + * + * @param int|null $week + * @param int|null $dayOfWeek + * @param int|null $dayOfYear + * + * @return int|static + */ + public function isoWeek($week = null, $dayOfWeek = null, $dayOfYear = null); + + /** + * Set/get the week number of year using given first day of week and first + * day of year included in the first week. Or use ISO format if no settings + * given. + * + * @param int|null $year if null, act as a getter, if not null, set the year and return current instance. + * @param int|null $dayOfWeek first date of week from 0 (Sunday) to 6 (Saturday) + * @param int|null $dayOfYear first day of year included in the week #1 + * + * @return int|static + */ + public function isoWeekYear($year = null, $dayOfWeek = null, $dayOfYear = null); + + /** + * Get/set the ISO weekday from 1 (Monday) to 7 (Sunday). + * + * @param int|null $value new value for weekday if using as setter. + * + * @return static|int + */ + public function isoWeekday($value = null); + + /** + * Get the number of weeks of the current week-year using given first day of week and first + * day of year included in the first week. Or use ISO format if no settings + * given. + * + * @param int|null $dayOfWeek first date of week from 0 (Sunday) to 6 (Saturday) + * @param int|null $dayOfYear first day of year included in the week #1 + * + * @return int + */ + public function isoWeeksInYear($dayOfWeek = null, $dayOfYear = null); + + /** + * Prepare the object for JSON serialization. + * + * @return array|string + */ + #[ReturnTypeWillChange] + public function jsonSerialize(); + + /** + * Modify to the last occurrence of a given day of the week + * in the current month. If no dayOfWeek is provided, modify to the + * last day of the current month. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek + * + * @return static + */ + public function lastOfMonth($dayOfWeek = null); + + /** + * Modify to the last occurrence of a given day of the week + * in the current quarter. If no dayOfWeek is provided, modify to the + * last day of the current quarter. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek day of the week default null + * + * @return static + */ + public function lastOfQuarter($dayOfWeek = null); + + /** + * Modify to the last occurrence of a given day of the week + * in the current year. If no dayOfWeek is provided, modify to the + * last day of the current year. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek day of the week default null + * + * @return static + */ + public function lastOfYear($dayOfWeek = null); + + /** + * Determines if the instance is less (before) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->lessThan('2018-07-25 12:45:15'); // false + * Carbon::parse('2018-07-25 12:45:16')->lessThan('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->lessThan('2018-07-25 12:45:17'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function lessThan($date): bool; + + /** + * Determines if the instance is less (before) or equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->lessThanOrEqualTo('2018-07-25 12:45:15'); // false + * Carbon::parse('2018-07-25 12:45:16')->lessThanOrEqualTo('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->lessThanOrEqualTo('2018-07-25 12:45:17'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function lessThanOrEqualTo($date): bool; + + /** + * Get/set the locale for the current instance. + * + * @param string|null $locale + * @param string ...$fallbackLocales + * + * @return $this|string + */ + public function locale(?string $locale = null, ...$fallbackLocales); + + /** + * Returns true if the given locale is internally supported and has words for 1-day diff (just now, yesterday, tomorrow). + * Support is considered enabled if the 3 words are translated in the given locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasDiffOneDayWords($locale); + + /** + * Returns true if the given locale is internally supported and has diff syntax support (ago, from now, before, after). + * Support is considered enabled if the 4 sentences are translated in the given locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasDiffSyntax($locale); + + /** + * Returns true if the given locale is internally supported and has words for 2-days diff (before yesterday, after tomorrow). + * Support is considered enabled if the 2 words are translated in the given locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasDiffTwoDayWords($locale); + + /** + * Returns true if the given locale is internally supported and has period syntax support (X times, every X, from X, to X). + * Support is considered enabled if the 4 sentences are translated in the given locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasPeriodSyntax($locale); + + /** + * Returns true if the given locale is internally supported and has short-units support. + * Support is considered enabled if either year, day or hour has a short variant translated. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasShortUnits($locale); + + /** + * Determines if the instance is less (before) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->lt('2018-07-25 12:45:15'); // false + * Carbon::parse('2018-07-25 12:45:16')->lt('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->lt('2018-07-25 12:45:17'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see lessThan() + * + * @return bool + */ + public function lt($date): bool; + + /** + * Determines if the instance is less (before) or equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->lte('2018-07-25 12:45:15'); // false + * Carbon::parse('2018-07-25 12:45:16')->lte('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->lte('2018-07-25 12:45:17'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see lessThanOrEqualTo() + * + * @return bool + */ + public function lte($date): bool; + + /** + * Register a custom macro. + * + * @example + * ``` + * $userSettings = [ + * 'locale' => 'pt', + * 'timezone' => 'America/Sao_Paulo', + * ]; + * Carbon::macro('userFormat', function () use ($userSettings) { + * return $this->copy()->locale($userSettings['locale'])->tz($userSettings['timezone'])->calendar(); + * }); + * echo Carbon::yesterday()->hours(11)->userFormat(); + * ``` + * + * @param string $name + * @param object|callable $macro + * + * @return void + */ + public static function macro($name, $macro); + + /** + * Make a Carbon instance from given variable if possible. + * + * Always return a new instance. Parse only strings and only these likely to be dates (skip intervals + * and recurrences). Throw an exception for invalid format, but otherwise return null. + * + * @param mixed $var + * + * @throws InvalidFormatException + * + * @return static|null + */ + public static function make($var); + + /** + * Get the maximum instance between a given instance (default now) and the current instance. + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return static + */ + public function max($date = null); + + /** + * Create a Carbon instance for the greatest supported date. + * + * @return static + */ + public static function maxValue(); + + /** + * Get the maximum instance between a given instance (default now) and the current instance. + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see max() + * + * @return static + */ + public function maximum($date = null); + + /** + * Return the meridiem of the current time in the current locale. + * + * @param bool $isLower if true, returns lowercase variant if available in the current locale. + * + * @return string + */ + public function meridiem(bool $isLower = false): string; + + /** + * Modify to midday, default to self::$midDayAt + * + * @return static + */ + public function midDay(); + + /** + * Get the minimum instance between a given instance (default now) and the current instance. + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return static + */ + public function min($date = null); + + /** + * Create a Carbon instance for the lowest supported date. + * + * @return static + */ + public static function minValue(); + + /** + * Get the minimum instance between a given instance (default now) and the current instance. + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see min() + * + * @return static + */ + public function minimum($date = null); + + /** + * Mix another object into the class. + * + * @example + * ``` + * Carbon::mixin(new class { + * public function addMoon() { + * return function () { + * return $this->addDays(30); + * }; + * } + * public function subMoon() { + * return function () { + * return $this->subDays(30); + * }; + * } + * }); + * $fullMoon = Carbon::create('2018-12-22'); + * $nextFullMoon = $fullMoon->addMoon(); + * $blackMoon = Carbon::create('2019-01-06'); + * $previousBlackMoon = $blackMoon->subMoon(); + * echo "$nextFullMoon\n"; + * echo "$previousBlackMoon\n"; + * ``` + * + * @param object|string $mixin + * + * @throws ReflectionException + * + * @return void + */ + public static function mixin($mixin); + + /** + * Calls \DateTime::modify if mutable or \DateTimeImmutable::modify else. + * + * @see https://php.net/manual/en/datetime.modify.php + * + * @return static|false + */ + #[ReturnTypeWillChange] + public function modify($modify); + + /** + * Determines if the instance is not equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->ne('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->ne(Carbon::parse('2018-07-25 12:45:16')); // false + * Carbon::parse('2018-07-25 12:45:16')->ne('2018-07-25 12:45:17'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see notEqualTo() + * + * @return bool + */ + public function ne($date): bool; + + /** + * Modify to the next occurrence of a given modifier such as a day of + * the week. If no modifier is provided, modify to the next occurrence + * of the current day of the week. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param string|int|null $modifier + * + * @return static + */ + public function next($modifier = null); + + /** + * Go forward to the next weekday. + * + * @return static + */ + public function nextWeekday(); + + /** + * Go forward to the next weekend day. + * + * @return static + */ + public function nextWeekendDay(); + + /** + * Determines if the instance is not equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->notEqualTo('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->notEqualTo(Carbon::parse('2018-07-25 12:45:16')); // false + * Carbon::parse('2018-07-25 12:45:16')->notEqualTo('2018-07-25 12:45:17'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function notEqualTo($date): bool; + + /** + * Get a Carbon instance for the current date and time. + * + * @param DateTimeZone|string|null $tz + * + * @return static + */ + public static function now($tz = null); + + /** + * Returns a present instance in the same timezone. + * + * @return static + */ + public function nowWithSameTz(); + + /** + * Modify to the given occurrence of a given day of the week + * in the current month. If the calculated occurrence is outside the scope + * of the current month, then return false and no modifications are made. + * Use the supplied constants to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int $nth + * @param int $dayOfWeek + * + * @return mixed + */ + public function nthOfMonth($nth, $dayOfWeek); + + /** + * Modify to the given occurrence of a given day of the week + * in the current quarter. If the calculated occurrence is outside the scope + * of the current quarter, then return false and no modifications are made. + * Use the supplied constants to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int $nth + * @param int $dayOfWeek + * + * @return mixed + */ + public function nthOfQuarter($nth, $dayOfWeek); + + /** + * Modify to the given occurrence of a given day of the week + * in the current year. If the calculated occurrence is outside the scope + * of the current year, then return false and no modifications are made. + * Use the supplied constants to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int $nth + * @param int $dayOfWeek + * + * @return mixed + */ + public function nthOfYear($nth, $dayOfWeek); + + /** + * Return a property with its ordinal. + * + * @param string $key + * @param string|null $period + * + * @return string + */ + public function ordinal(string $key, ?string $period = null): string; + + /** + * Create a carbon instance from a string. + * + * This is an alias for the constructor that allows better fluent syntax + * as it allows you to do Carbon::parse('Monday next week')->fn() rather + * than (new Carbon('Monday next week'))->fn(). + * + * @param string|DateTimeInterface|null $time + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function parse($time = null, $tz = null); + + /** + * Create a carbon instance from a localized string (in French, Japanese, Arabic, etc.). + * + * @param string $time date/time string in the given language (may also contain English). + * @param string|null $locale if locale is null or not specified, current global locale will be + * used instead. + * @param DateTimeZone|string|null $tz optional timezone for the new instance. + * + * @throws InvalidFormatException + * + * @return static + */ + public static function parseFromLocale($time, $locale = null, $tz = null); + + /** + * Returns standardized plural of a given singular/plural unit name (in English). + * + * @param string $unit + * + * @return string + */ + public static function pluralUnit(string $unit): string; + + /** + * Modify to the previous occurrence of a given modifier such as a day of + * the week. If no dayOfWeek is provided, modify to the previous occurrence + * of the current day of the week. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param string|int|null $modifier + * + * @return static + */ + public function previous($modifier = null); + + /** + * Go backward to the previous weekday. + * + * @return static + */ + public function previousWeekday(); + + /** + * Go backward to the previous weekend day. + * + * @return static + */ + public function previousWeekendDay(); + + /** + * Create a iterable CarbonPeriod object from current date to a given end date (and optional interval). + * + * @param \DateTimeInterface|Carbon|CarbonImmutable|null $end period end date + * @param int|\DateInterval|string|null $interval period default interval or number of the given $unit + * @param string|null $unit if specified, $interval must be an integer + * + * @return CarbonPeriod + */ + public function range($end = null, $interval = null, $unit = null); + + /** + * Call native PHP DateTime/DateTimeImmutable add() method. + * + * @param DateInterval $interval + * + * @return static + */ + public function rawAdd(DateInterval $interval); + + /** + * Create a Carbon instance from a specific format. + * + * @param string $format Datetime format + * @param string $time + * @param DateTimeZone|string|false|null $tz + * + * @throws InvalidFormatException + * + * @return static|false + */ + public static function rawCreateFromFormat($format, $time, $tz = null); + + /** + * @see https://php.net/manual/en/datetime.format.php + * + * @param string $format + * + * @return string + */ + public function rawFormat($format); + + /** + * Create a carbon instance from a string. + * + * This is an alias for the constructor that allows better fluent syntax + * as it allows you to do Carbon::parse('Monday next week')->fn() rather + * than (new Carbon('Monday next week'))->fn(). + * + * @param string|DateTimeInterface|null $time + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function rawParse($time = null, $tz = null); + + /** + * Call native PHP DateTime/DateTimeImmutable sub() method. + * + * @param DateInterval $interval + * + * @return static + */ + public function rawSub(DateInterval $interval); + + /** + * Remove all macros and generic macros. + */ + public static function resetMacros(); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addMonthsWithOverflow/addMonthsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @see settings + * + * Reset the month overflow behavior. + * + * @return void + */ + public static function resetMonthsOverflow(); + + /** + * Reset the format used to the default when type juggling a Carbon instance to a string + * + * @return void + */ + public static function resetToStringFormat(); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addYearsWithOverflow/addYearsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @see settings + * + * Reset the month overflow behavior. + * + * @return void + */ + public static function resetYearsOverflow(); + + /** + * Round the current instance second with given precision if specified. + * + * @param float|int|string|\DateInterval|null $precision + * @param string $function + * + * @return CarbonInterface + */ + public function round($precision = 1, $function = 'round'); + + /** + * Round the current instance at the given unit with given precision if specified and the given function. + * + * @param string $unit + * @param float|int $precision + * @param string $function + * + * @return CarbonInterface + */ + public function roundUnit($unit, $precision = 1, $function = 'round'); + + /** + * Round the current instance week. + * + * @param int $weekStartsAt optional start allow you to specify the day of week to use to start the week + * + * @return CarbonInterface + */ + public function roundWeek($weekStartsAt = null); + + /** + * The number of seconds since midnight. + * + * @return int + */ + public function secondsSinceMidnight(); + + /** + * The number of seconds until 23:59:59. + * + * @return int + */ + public function secondsUntilEndOfDay(); + + /** + * Return a serialized string of the instance. + * + * @return string + */ + public function serialize(); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather transform Carbon object before the serialization. + * + * JSON serialize all Carbon instances using the given callback. + * + * @param callable $callback + * + * @return void + */ + public static function serializeUsing($callback); + + /** + * Set a part of the Carbon object + * + * @param string|array $name + * @param string|int|DateTimeZone $value + * + * @throws ImmutableException|UnknownSetterException + * + * @return $this + */ + public function set($name, $value = null); + + /** + * Set the date with gregorian year, month and day numbers. + * + * @see https://php.net/manual/en/datetime.setdate.php + * + * @param int $year + * @param int $month + * @param int $day + * + * @return static + */ + #[ReturnTypeWillChange] + public function setDate($year, $month, $day); + + /** + * Set the year, month, and date for this instance to that of the passed instance. + * + * @param Carbon|DateTimeInterface $date now if null + * + * @return static + */ + public function setDateFrom($date = null); + + /** + * Set the date and time all together. + * + * @param int $year + * @param int $month + * @param int $day + * @param int $hour + * @param int $minute + * @param int $second + * @param int $microseconds + * + * @return static + */ + public function setDateTime($year, $month, $day, $hour, $minute, $second = 0, $microseconds = 0); + + /** + * Set the date and time for this instance to that of the passed instance. + * + * @param Carbon|DateTimeInterface $date + * + * @return static + */ + public function setDateTimeFrom($date = null); + + /** + * Set the fallback locale. + * + * @see https://symfony.com/doc/current/components/translation.html#fallback-locales + * + * @param string $locale + */ + public static function setFallbackLocale($locale); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @see settings + * + * @param int $humanDiffOptions + */ + public static function setHumanDiffOptions($humanDiffOptions); + + /** + * Set a date according to the ISO 8601 standard - using weeks and day offsets rather than specific dates. + * + * @see https://php.net/manual/en/datetime.setisodate.php + * + * @param int $year + * @param int $week + * @param int $day + * + * @return static + */ + #[ReturnTypeWillChange] + public function setISODate($year, $week, $day = 1); + + /** + * Set the translator for the current instance. + * + * @param \Symfony\Component\Translation\TranslatorInterface $translator + * + * @return $this + */ + public function setLocalTranslator(TranslatorInterface $translator); + + /** + * Set the current translator locale and indicate if the source locale file exists. + * Pass 'auto' as locale to use closest language from the current LC_TIME locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function setLocale($locale); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather consider mid-day is always 12pm, then if you need to test if it's an other + * hour, test it explicitly: + * $date->format('G') == 13 + * or to set explicitly to a given hour: + * $date->setTime(13, 0, 0, 0) + * + * Set midday/noon hour + * + * @param int $hour midday hour + * + * @return void + */ + public static function setMidDayAt($hour); + + /** + * Set a Carbon instance (real or mock) to be returned when a "now" + * instance is created. The provided instance will be returned + * specifically under the following conditions: + * - A call to the static now() method, ex. Carbon::now() + * - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null) + * - When the string "now" is passed to the constructor or parse(), ex. new Carbon('now') + * - When a string containing the desired time is passed to Carbon::parse(). + * + * Note the timezone parameter was left out of the examples above and + * has no affect as the mock value will be returned regardless of its value. + * + * Only the moment is mocked with setTestNow(), the timezone will still be the one passed + * as parameter of date_default_timezone_get() as a fallback (see setTestNowAndTimezone()). + * + * To clear the test instance call this method using the default + * parameter of null. + * + * /!\ Use this method for unit tests only. + * + * @param Closure|static|string|false|null $testNow real or mock Carbon instance + */ + public static function setTestNow($testNow = null); + + /** + * Set a Carbon instance (real or mock) to be returned when a "now" + * instance is created. The provided instance will be returned + * specifically under the following conditions: + * - A call to the static now() method, ex. Carbon::now() + * - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null) + * - When the string "now" is passed to the constructor or parse(), ex. new Carbon('now') + * - When a string containing the desired time is passed to Carbon::parse(). + * + * It will also align default timezone (e.g. call date_default_timezone_set()) with + * the second argument or if null, with the timezone of the given date object. + * + * To clear the test instance call this method using the default + * parameter of null. + * + * /!\ Use this method for unit tests only. + * + * @param Closure|static|string|false|null $testNow real or mock Carbon instance + */ + public static function setTestNowAndTimezone($testNow = null, $tz = null); + + /** + * Resets the current time of the DateTime object to a different time. + * + * @see https://php.net/manual/en/datetime.settime.php + * + * @param int $hour + * @param int $minute + * @param int $second + * @param int $microseconds + * + * @return static + */ + #[ReturnTypeWillChange] + public function setTime($hour, $minute, $second = 0, $microseconds = 0); + + /** + * Set the hour, minute, second and microseconds for this instance to that of the passed instance. + * + * @param Carbon|DateTimeInterface $date now if null + * + * @return static + */ + public function setTimeFrom($date = null); + + /** + * Set the time by time string. + * + * @param string $time + * + * @return static + */ + public function setTimeFromTimeString($time); + + /** + * Set the instance's timestamp. + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $unixTimestamp + * + * @return static + */ + #[ReturnTypeWillChange] + public function setTimestamp($unixTimestamp); + + /** + * Set the instance's timezone from a string or object. + * + * @param DateTimeZone|string $value + * + * @return static + */ + #[ReturnTypeWillChange] + public function setTimezone($value); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather let Carbon object being casted to string with DEFAULT_TO_STRING_FORMAT, and + * use other method or custom format passed to format() method if you need to dump an other string + * format. + * + * Set the default format used when type juggling a Carbon instance to a string + * + * @param string|Closure|null $format + * + * @return void + */ + public static function setToStringFormat($format); + + /** + * Set the default translator instance to use. + * + * @param \Symfony\Component\Translation\TranslatorInterface $translator + * + * @return void + */ + public static function setTranslator(TranslatorInterface $translator); + + /** + * Set specified unit to new given value. + * + * @param string $unit year, month, day, hour, minute, second or microsecond + * @param int $value new value for given unit + * + * @return static + */ + public function setUnit($unit, $value = null); + + /** + * Set any unit to a new value without overflowing current other unit given. + * + * @param string $valueUnit unit name to modify + * @param int $value new value for the input unit + * @param string $overflowUnit unit name to not overflow + * + * @return static + */ + public function setUnitNoOverflow($valueUnit, $value, $overflowUnit); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use UTF-8 language packages on every machine. + * + * Set if UTF8 will be used for localized date/time. + * + * @param bool $utf8 + */ + public static function setUtf8($utf8); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * Use $weekStartsAt optional parameter instead when using startOfWeek, floorWeek, ceilWeek + * or roundWeek method. You can also use the 'first_day_of_week' locale setting to change the + * start of week according to current locale selected and implicitly the end of week. + * + * Set the last day of week + * + * @param int|string $day week end day (or 'auto' to get the day before the first day of week + * from Carbon::getLocale() culture). + * + * @return void + */ + public static function setWeekEndsAt($day); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * Use $weekEndsAt optional parameter instead when using endOfWeek method. You can also use the + * 'first_day_of_week' locale setting to change the start of week according to current locale + * selected and implicitly the end of week. + * + * Set the first day of week + * + * @param int|string $day week start day (or 'auto' to get the first day of week from Carbon::getLocale() culture). + * + * @return void + */ + public static function setWeekStartsAt($day); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather consider week-end is always saturday and sunday, and if you have some custom + * week-end days to handle, give to those days an other name and create a macro for them: + * + * ``` + * Carbon::macro('isDayOff', function ($date) { + * return $date->isSunday() || $date->isMonday(); + * }); + * Carbon::macro('isNotDayOff', function ($date) { + * return !$date->isDayOff(); + * }); + * if ($someDate->isDayOff()) ... + * if ($someDate->isNotDayOff()) ... + * // Add 5 not-off days + * $count = 5; + * while ($someDate->isDayOff() || ($count-- > 0)) { + * $someDate->addDay(); + * } + * ``` + * + * Set weekend days + * + * @param array $days + * + * @return void + */ + public static function setWeekendDays($days); + + /** + * Set specific options. + * - strictMode: true|false|null + * - monthOverflow: true|false|null + * - yearOverflow: true|false|null + * - humanDiffOptions: int|null + * - toStringFormat: string|Closure|null + * - toJsonFormat: string|Closure|null + * - locale: string|null + * - timezone: \DateTimeZone|string|int|null + * - macros: array|null + * - genericMacros: array|null + * + * @param array $settings + * + * @return $this|static + */ + public function settings(array $settings); + + /** + * Set the instance's timezone from a string or object and add/subtract the offset difference. + * + * @param DateTimeZone|string $value + * + * @return static + */ + public function shiftTimezone($value); + + /** + * Get the month overflow global behavior (can be overridden in specific instances). + * + * @return bool + */ + public static function shouldOverflowMonths(); + + /** + * Get the month overflow global behavior (can be overridden in specific instances). + * + * @return bool + */ + public static function shouldOverflowYears(); + + /** + * @alias diffForHumans + * + * Get the difference in a human readable format in the current locale from current instance to an other + * instance given (or now if null given). + */ + public function since($other = null, $syntax = null, $short = false, $parts = 1, $options = null); + + /** + * Returns standardized singular of a given singular/plural unit name (in English). + * + * @param string $unit + * + * @return string + */ + public static function singularUnit(string $unit): string; + + /** + * Modify to start of current given unit. + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16.334455') + * ->startOf('month') + * ->endOf('week', Carbon::FRIDAY); + * ``` + * + * @param string $unit + * @param array $params + * + * @return static + */ + public function startOf($unit, ...$params); + + /** + * Resets the date to the first day of the century and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfCentury(); + * ``` + * + * @return static + */ + public function startOfCentury(); + + /** + * Resets the time to 00:00:00 start of day + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfDay(); + * ``` + * + * @return static + */ + public function startOfDay(); + + /** + * Resets the date to the first day of the decade and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfDecade(); + * ``` + * + * @return static + */ + public function startOfDecade(); + + /** + * Modify to start of current hour, minutes and seconds become 0 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfHour(); + * ``` + * + * @return static + */ + public function startOfHour(); + + /** + * Resets the date to the first day of the millennium and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfMillennium(); + * ``` + * + * @return static + */ + public function startOfMillennium(); + + /** + * Modify to start of current minute, seconds become 0 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfMinute(); + * ``` + * + * @return static + */ + public function startOfMinute(); + + /** + * Resets the date to the first day of the month and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfMonth(); + * ``` + * + * @return static + */ + public function startOfMonth(); + + /** + * Resets the date to the first day of the quarter and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfQuarter(); + * ``` + * + * @return static + */ + public function startOfQuarter(); + + /** + * Modify to start of current second, microseconds become 0 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16.334455') + * ->startOfSecond() + * ->format('H:i:s.u'); + * ``` + * + * @return static + */ + public function startOfSecond(); + + /** + * Resets the date to the first day of week (defined in $weekStartsAt) and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfWeek() . "\n"; + * echo Carbon::parse('2018-07-25 12:45:16')->locale('ar')->startOfWeek() . "\n"; + * echo Carbon::parse('2018-07-25 12:45:16')->startOfWeek(Carbon::SUNDAY) . "\n"; + * ``` + * + * @param int $weekStartsAt optional start allow you to specify the day of week to use to start the week + * + * @return static + */ + public function startOfWeek($weekStartsAt = null); + + /** + * Resets the date to the first day of the year and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfYear(); + * ``` + * + * @return static + */ + public function startOfYear(); + + /** + * Subtract given units or interval to the current instance. + * + * @example $date->sub('hour', 3) + * @example $date->sub(15, 'days') + * @example $date->sub(CarbonInterval::days(4)) + * + * @param string|DateInterval|Closure|CarbonConverterInterface $unit + * @param int $value + * @param bool|null $overflow + * + * @return static + */ + #[ReturnTypeWillChange] + public function sub($unit, $value = 1, $overflow = null); + + public function subRealUnit($unit, $value = 1); + + /** + * Subtract given units to the current instance. + * + * @param string $unit + * @param int $value + * @param bool|null $overflow + * + * @return static + */ + public function subUnit($unit, $value = 1, $overflow = null); + + /** + * Subtract any unit to a new value without overflowing current other unit given. + * + * @param string $valueUnit unit name to modify + * @param int $value amount to subtract to the input unit + * @param string $overflowUnit unit name to not overflow + * + * @return static + */ + public function subUnitNoOverflow($valueUnit, $value, $overflowUnit); + + /** + * Subtract given units or interval to the current instance. + * + * @see sub() + * + * @param string|DateInterval $unit + * @param int $value + * @param bool|null $overflow + * + * @return static + */ + public function subtract($unit, $value = 1, $overflow = null); + + /** + * Get the difference in a human readable format in the current locale from current instance to an other + * instance given (or now if null given). + * + * @return string + */ + public function timespan($other = null, $timezone = null); + + /** + * Set the instance's timestamp. + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $unixTimestamp + * + * @return static + */ + public function timestamp($unixTimestamp); + + /** + * @alias setTimezone + * + * @param DateTimeZone|string $value + * + * @return static + */ + public function timezone($value); + + /** + * Get the difference in a human readable format in the current locale from an other + * instance given (or now if null given) to current instance. + * + * When comparing a value in the past to default now: + * 1 hour from now + * 5 months from now + * + * When comparing a value in the future to default now: + * 1 hour ago + * 5 months ago + * + * When comparing a value in the past to another value: + * 1 hour after + * 5 months after + * + * When comparing a value in the future to another value: + * 1 hour before + * 5 months before + * + * @param Carbon|\DateTimeInterface|string|array|null $other if array passed, will be used as parameters array, see $syntax below; + * if null passed, now will be used as comparison reference; + * if any other type, it will be converted to date and used as reference. + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * - 'other' entry (see above) + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single unit) + * @param int $options human diff options + * + * @return string + */ + public function to($other = null, $syntax = null, $short = false, $parts = 1, $options = null); + + /** + * Get default array representation. + * + * @example + * ``` + * var_dump(Carbon::now()->toArray()); + * ``` + * + * @return array + */ + public function toArray(); + + /** + * Format the instance as ATOM + * + * @example + * ``` + * echo Carbon::now()->toAtomString(); + * ``` + * + * @return string + */ + public function toAtomString(); + + /** + * Format the instance as COOKIE + * + * @example + * ``` + * echo Carbon::now()->toCookieString(); + * ``` + * + * @return string + */ + public function toCookieString(); + + /** + * @alias toDateTime + * + * Return native DateTime PHP object matching the current instance. + * + * @example + * ``` + * var_dump(Carbon::now()->toDate()); + * ``` + * + * @return DateTime + */ + public function toDate(); + + /** + * Format the instance as date + * + * @example + * ``` + * echo Carbon::now()->toDateString(); + * ``` + * + * @return string + */ + public function toDateString(); + + /** + * Return native DateTime PHP object matching the current instance. + * + * @example + * ``` + * var_dump(Carbon::now()->toDateTime()); + * ``` + * + * @return DateTime + */ + public function toDateTime(); + + /** + * Return native toDateTimeImmutable PHP object matching the current instance. + * + * @example + * ``` + * var_dump(Carbon::now()->toDateTimeImmutable()); + * ``` + * + * @return DateTimeImmutable + */ + public function toDateTimeImmutable(); + + /** + * Format the instance as date and time T-separated with no timezone + * + * @example + * ``` + * echo Carbon::now()->toDateTimeLocalString(); + * echo "\n"; + * echo Carbon::now()->toDateTimeLocalString('minute'); // You can specify precision among: minute, second, millisecond and microsecond + * ``` + * + * @param string $unitPrecision + * + * @return string + */ + public function toDateTimeLocalString($unitPrecision = 'second'); + + /** + * Format the instance as date and time + * + * @example + * ``` + * echo Carbon::now()->toDateTimeString(); + * ``` + * + * @param string $unitPrecision + * + * @return string + */ + public function toDateTimeString($unitPrecision = 'second'); + + /** + * Format the instance with day, date and time + * + * @example + * ``` + * echo Carbon::now()->toDayDateTimeString(); + * ``` + * + * @return string + */ + public function toDayDateTimeString(); + + /** + * Format the instance as a readable date + * + * @example + * ``` + * echo Carbon::now()->toFormattedDateString(); + * ``` + * + * @return string + */ + public function toFormattedDateString(); + + /** + * Return the ISO-8601 string (ex: 1977-04-22T06:00:00Z, if $keepOffset truthy, offset will be kept: + * 1977-04-22T01:00:00-05:00). + * + * @example + * ``` + * echo Carbon::now('America/Toronto')->toISOString() . "\n"; + * echo Carbon::now('America/Toronto')->toISOString(true) . "\n"; + * ``` + * + * @param bool $keepOffset Pass true to keep the date offset. Else forced to UTC. + * + * @return null|string + */ + public function toISOString($keepOffset = false); + + /** + * Return a immutable copy of the instance. + * + * @return CarbonImmutable + */ + public function toImmutable(); + + /** + * Format the instance as ISO8601 + * + * @example + * ``` + * echo Carbon::now()->toIso8601String(); + * ``` + * + * @return string + */ + public function toIso8601String(); + + /** + * Convert the instance to UTC and return as Zulu ISO8601 + * + * @example + * ``` + * echo Carbon::now()->toIso8601ZuluString(); + * ``` + * + * @param string $unitPrecision + * + * @return string + */ + public function toIso8601ZuluString($unitPrecision = 'second'); + + /** + * Return the ISO-8601 string (ex: 1977-04-22T06:00:00Z) with UTC timezone. + * + * @example + * ``` + * echo Carbon::now('America/Toronto')->toJSON(); + * ``` + * + * @return null|string + */ + public function toJSON(); + + /** + * Return a mutable copy of the instance. + * + * @return Carbon + */ + public function toMutable(); + + /** + * Get the difference in a human readable format in the current locale from an other + * instance given to now + * + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single part) + * @param int $options human diff options + * + * @return string + */ + public function toNow($syntax = null, $short = false, $parts = 1, $options = null); + + /** + * Get default object representation. + * + * @example + * ``` + * var_dump(Carbon::now()->toObject()); + * ``` + * + * @return object + */ + public function toObject(); + + /** + * Create a iterable CarbonPeriod object from current date to a given end date (and optional interval). + * + * @param \DateTimeInterface|Carbon|CarbonImmutable|int|null $end period end date or recurrences count if int + * @param int|\DateInterval|string|null $interval period default interval or number of the given $unit + * @param string|null $unit if specified, $interval must be an integer + * + * @return CarbonPeriod + */ + public function toPeriod($end = null, $interval = null, $unit = null); + + /** + * Format the instance as RFC1036 + * + * @example + * ``` + * echo Carbon::now()->toRfc1036String(); + * ``` + * + * @return string + */ + public function toRfc1036String(); + + /** + * Format the instance as RFC1123 + * + * @example + * ``` + * echo Carbon::now()->toRfc1123String(); + * ``` + * + * @return string + */ + public function toRfc1123String(); + + /** + * Format the instance as RFC2822 + * + * @example + * ``` + * echo Carbon::now()->toRfc2822String(); + * ``` + * + * @return string + */ + public function toRfc2822String(); + + /** + * Format the instance as RFC3339 + * + * @param bool $extended + * + * @example + * ``` + * echo Carbon::now()->toRfc3339String() . "\n"; + * echo Carbon::now()->toRfc3339String(true) . "\n"; + * ``` + * + * @return string + */ + public function toRfc3339String($extended = false); + + /** + * Format the instance as RFC7231 + * + * @example + * ``` + * echo Carbon::now()->toRfc7231String(); + * ``` + * + * @return string + */ + public function toRfc7231String(); + + /** + * Format the instance as RFC822 + * + * @example + * ``` + * echo Carbon::now()->toRfc822String(); + * ``` + * + * @return string + */ + public function toRfc822String(); + + /** + * Format the instance as RFC850 + * + * @example + * ``` + * echo Carbon::now()->toRfc850String(); + * ``` + * + * @return string + */ + public function toRfc850String(); + + /** + * Format the instance as RSS + * + * @example + * ``` + * echo Carbon::now()->toRssString(); + * ``` + * + * @return string + */ + public function toRssString(); + + /** + * Returns english human readable complete date string. + * + * @example + * ``` + * echo Carbon::now()->toString(); + * ``` + * + * @return string + */ + public function toString(); + + /** + * Format the instance as time + * + * @example + * ``` + * echo Carbon::now()->toTimeString(); + * ``` + * + * @param string $unitPrecision + * + * @return string + */ + public function toTimeString($unitPrecision = 'second'); + + /** + * Format the instance as W3C + * + * @example + * ``` + * echo Carbon::now()->toW3cString(); + * ``` + * + * @return string + */ + public function toW3cString(); + + /** + * Create a Carbon instance for today. + * + * @param DateTimeZone|string|null $tz + * + * @return static + */ + public static function today($tz = null); + + /** + * Create a Carbon instance for tomorrow. + * + * @param DateTimeZone|string|null $tz + * + * @return static + */ + public static function tomorrow($tz = null); + + /** + * Translate using translation string or callback available. + * + * @param string $key + * @param array $parameters + * @param string|int|float|null $number + * @param \Symfony\Component\Translation\TranslatorInterface|null $translator + * @param bool $altNumbers + * + * @return string + */ + public function translate(string $key, array $parameters = [], $number = null, ?TranslatorInterface $translator = null, bool $altNumbers = false): string; + + /** + * Returns the alternative number for a given integer if available in the current locale. + * + * @param int $number + * + * @return string + */ + public function translateNumber(int $number): string; + + /** + * Translate a time string from a locale to an other. + * + * @param string $timeString date/time/duration string to translate (may also contain English) + * @param string|null $from input locale of the $timeString parameter (`Carbon::getLocale()` by default) + * @param string|null $to output locale of the result returned (`"en"` by default) + * @param int $mode specify what to translate with options: + * - self::TRANSLATE_ALL (default) + * - CarbonInterface::TRANSLATE_MONTHS + * - CarbonInterface::TRANSLATE_DAYS + * - CarbonInterface::TRANSLATE_UNITS + * - CarbonInterface::TRANSLATE_MERIDIEM + * You can use pipe to group: CarbonInterface::TRANSLATE_MONTHS | CarbonInterface::TRANSLATE_DAYS + * + * @return string + */ + public static function translateTimeString($timeString, $from = null, $to = null, $mode = self::TRANSLATE_ALL); + + /** + * Translate a time string from the current locale (`$date->locale()`) to an other. + * + * @param string $timeString time string to translate + * @param string|null $to output locale of the result returned ("en" by default) + * + * @return string + */ + public function translateTimeStringTo($timeString, $to = null); + + /** + * Translate using translation string or callback available. + * + * @param \Symfony\Component\Translation\TranslatorInterface $translator + * @param string $key + * @param array $parameters + * @param null $number + * + * @return string + */ + public static function translateWith(TranslatorInterface $translator, string $key, array $parameters = [], $number = null): string; + + /** + * Format as ->format() do (using date replacements patterns from https://php.net/manual/en/function.date.php) + * but translate words whenever possible (months, day names, etc.) using the current locale. + * + * @param string $format + * + * @return string + */ + public function translatedFormat(string $format): string; + + /** + * Set the timezone or returns the timezone name if no arguments passed. + * + * @param DateTimeZone|string $value + * + * @return static|string + */ + public function tz($value = null); + + /** + * @alias getTimestamp + * + * Returns the UNIX timestamp for the current date. + * + * @return int + */ + public function unix(); + + /** + * @alias to + * + * Get the difference in a human readable format in the current locale from an other + * instance given (or now if null given) to current instance. + * + * @param Carbon|\DateTimeInterface|string|array|null $other if array passed, will be used as parameters array, see $syntax below; + * if null passed, now will be used as comparison reference; + * if any other type, it will be converted to date and used as reference. + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * - 'other' entry (see above) + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single unit) + * @param int $options human diff options + * + * @return string + */ + public function until($other = null, $syntax = null, $short = false, $parts = 1, $options = null); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addMonthsWithOverflow/addMonthsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @see settings + * + * Indicates if months should be calculated with overflow. + * + * @param bool $monthsOverflow + * + * @return void + */ + public static function useMonthsOverflow($monthsOverflow = true); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @see settings + * + * Enable the strict mode (or disable with passing false). + * + * @param bool $strictModeEnabled + */ + public static function useStrictMode($strictModeEnabled = true); + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addYearsWithOverflow/addYearsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @see settings + * + * Indicates if years should be calculated with overflow. + * + * @param bool $yearsOverflow + * + * @return void + */ + public static function useYearsOverflow($yearsOverflow = true); + + /** + * Set the instance's timezone to UTC. + * + * @return static + */ + public function utc(); + + /** + * Returns the minutes offset to UTC if no arguments passed, else set the timezone with given minutes shift passed. + * + * @param int|null $minuteOffset + * + * @return int|static + */ + public function utcOffset(?int $minuteOffset = null); + + /** + * Returns the milliseconds timestamps used amongst other by Date javascript objects. + * + * @return float + */ + public function valueOf(); + + /** + * Get/set the week number using given first day of week and first + * day of year included in the first week. Or use US format if no settings + * given (Sunday / Jan 6). + * + * @param int|null $week + * @param int|null $dayOfWeek + * @param int|null $dayOfYear + * + * @return int|static + */ + public function week($week = null, $dayOfWeek = null, $dayOfYear = null); + + /** + * Set/get the week number of year using given first day of week and first + * day of year included in the first week. Or use US format if no settings + * given (Sunday / Jan 6). + * + * @param int|null $year if null, act as a getter, if not null, set the year and return current instance. + * @param int|null $dayOfWeek first date of week from 0 (Sunday) to 6 (Saturday) + * @param int|null $dayOfYear first day of year included in the week #1 + * + * @return int|static + */ + public function weekYear($year = null, $dayOfWeek = null, $dayOfYear = null); + + /** + * Get/set the weekday from 0 (Sunday) to 6 (Saturday). + * + * @param int|null $value new value for weekday if using as setter. + * + * @return static|int + */ + public function weekday($value = null); + + /** + * Get the number of weeks of the current week-year using given first day of week and first + * day of year included in the first week. Or use US format if no settings + * given (Sunday / Jan 6). + * + * @param int|null $dayOfWeek first date of week from 0 (Sunday) to 6 (Saturday) + * @param int|null $dayOfYear first day of year included in the week #1 + * + * @return int + */ + public function weeksInYear($dayOfWeek = null, $dayOfYear = null); + + /** + * Temporarily sets a static date to be used within the callback. + * Using setTestNow to set the date, executing the callback, then + * clearing the test instance. + * + * /!\ Use this method for unit tests only. + * + * @param Closure|static|string|false|null $testNow real or mock Carbon instance + * @param Closure|null $callback + * + * @return mixed + */ + public static function withTestNow($testNow = null, $callback = null); + + /** + * Create a Carbon instance for yesterday. + * + * @param DateTimeZone|string|null $tz + * + * @return static + */ + public static function yesterday($tz = null); + + // +} diff --git a/vendor/nesbot/carbon/src/Carbon/CarbonInterval.php b/vendor/nesbot/carbon/src/Carbon/CarbonInterval.php new file mode 100644 index 0000000..a2b99a7 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/CarbonInterval.php @@ -0,0 +1,2749 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use Carbon\Exceptions\BadFluentConstructorException; +use Carbon\Exceptions\BadFluentSetterException; +use Carbon\Exceptions\InvalidCastException; +use Carbon\Exceptions\InvalidIntervalException; +use Carbon\Exceptions\ParseErrorException; +use Carbon\Exceptions\UnitNotConfiguredException; +use Carbon\Exceptions\UnknownGetterException; +use Carbon\Exceptions\UnknownSetterException; +use Carbon\Exceptions\UnknownUnitException; +use Carbon\Traits\IntervalRounding; +use Carbon\Traits\IntervalStep; +use Carbon\Traits\Mixin; +use Carbon\Traits\Options; +use Closure; +use DateInterval; +use DateTimeInterface; +use DateTimeZone; +use Exception; +use ReflectionException; +use ReturnTypeWillChange; +use Throwable; + +/** + * A simple API extension for DateInterval. + * The implementation provides helpers to handle weeks but only days are saved. + * Weeks are calculated based on the total days of the current instance. + * + * @property int $years Total years of the current interval. + * @property int $months Total months of the current interval. + * @property int $weeks Total weeks of the current interval calculated from the days. + * @property int $dayz Total days of the current interval (weeks * 7 + days). + * @property int $hours Total hours of the current interval. + * @property int $minutes Total minutes of the current interval. + * @property int $seconds Total seconds of the current interval. + * @property int $microseconds Total microseconds of the current interval. + * @property int $milliseconds Total microseconds of the current interval. + * @property int $microExcludeMilli Remaining microseconds without the milliseconds. + * @property int $dayzExcludeWeeks Total days remaining in the final week of the current instance (days % 7). + * @property int $daysExcludeWeeks alias of dayzExcludeWeeks + * @property-read float $totalYears Number of years equivalent to the interval. + * @property-read float $totalMonths Number of months equivalent to the interval. + * @property-read float $totalWeeks Number of weeks equivalent to the interval. + * @property-read float $totalDays Number of days equivalent to the interval. + * @property-read float $totalDayz Alias for totalDays. + * @property-read float $totalHours Number of hours equivalent to the interval. + * @property-read float $totalMinutes Number of minutes equivalent to the interval. + * @property-read float $totalSeconds Number of seconds equivalent to the interval. + * @property-read float $totalMilliseconds Number of milliseconds equivalent to the interval. + * @property-read float $totalMicroseconds Number of microseconds equivalent to the interval. + * @property-read string $locale locale of the current instance + * + * @method static CarbonInterval years($years = 1) Create instance specifying a number of years or modify the number of years if called on an instance. + * @method static CarbonInterval year($years = 1) Alias for years() + * @method static CarbonInterval months($months = 1) Create instance specifying a number of months or modify the number of months if called on an instance. + * @method static CarbonInterval month($months = 1) Alias for months() + * @method static CarbonInterval weeks($weeks = 1) Create instance specifying a number of weeks or modify the number of weeks if called on an instance. + * @method static CarbonInterval week($weeks = 1) Alias for weeks() + * @method static CarbonInterval days($days = 1) Create instance specifying a number of days or modify the number of days if called on an instance. + * @method static CarbonInterval dayz($days = 1) Alias for days() + * @method static CarbonInterval daysExcludeWeeks($days = 1) Create instance specifying a number of days or modify the number of days (keeping the current number of weeks) if called on an instance. + * @method static CarbonInterval dayzExcludeWeeks($days = 1) Alias for daysExcludeWeeks() + * @method static CarbonInterval day($days = 1) Alias for days() + * @method static CarbonInterval hours($hours = 1) Create instance specifying a number of hours or modify the number of hours if called on an instance. + * @method static CarbonInterval hour($hours = 1) Alias for hours() + * @method static CarbonInterval minutes($minutes = 1) Create instance specifying a number of minutes or modify the number of minutes if called on an instance. + * @method static CarbonInterval minute($minutes = 1) Alias for minutes() + * @method static CarbonInterval seconds($seconds = 1) Create instance specifying a number of seconds or modify the number of seconds if called on an instance. + * @method static CarbonInterval second($seconds = 1) Alias for seconds() + * @method static CarbonInterval milliseconds($milliseconds = 1) Create instance specifying a number of milliseconds or modify the number of milliseconds if called on an instance. + * @method static CarbonInterval millisecond($milliseconds = 1) Alias for milliseconds() + * @method static CarbonInterval microseconds($microseconds = 1) Create instance specifying a number of microseconds or modify the number of microseconds if called on an instance. + * @method static CarbonInterval microsecond($microseconds = 1) Alias for microseconds() + * @method $this addYears(int $years) Add given number of years to the current interval + * @method $this subYears(int $years) Subtract given number of years to the current interval + * @method $this addMonths(int $months) Add given number of months to the current interval + * @method $this subMonths(int $months) Subtract given number of months to the current interval + * @method $this addWeeks(int|float $weeks) Add given number of weeks to the current interval + * @method $this subWeeks(int|float $weeks) Subtract given number of weeks to the current interval + * @method $this addDays(int|float $days) Add given number of days to the current interval + * @method $this subDays(int|float $days) Subtract given number of days to the current interval + * @method $this addHours(int|float $hours) Add given number of hours to the current interval + * @method $this subHours(int|float $hours) Subtract given number of hours to the current interval + * @method $this addMinutes(int|float $minutes) Add given number of minutes to the current interval + * @method $this subMinutes(int|float $minutes) Subtract given number of minutes to the current interval + * @method $this addSeconds(int|float $seconds) Add given number of seconds to the current interval + * @method $this subSeconds(int|float $seconds) Subtract given number of seconds to the current interval + * @method $this addMilliseconds(int|float $milliseconds) Add given number of milliseconds to the current interval + * @method $this subMilliseconds(int|float $milliseconds) Subtract given number of milliseconds to the current interval + * @method $this addMicroseconds(int|float $microseconds) Add given number of microseconds to the current interval + * @method $this subMicroseconds(int|float $microseconds) Subtract given number of microseconds to the current interval + * @method $this roundYear(int|float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method $this roundYears(int|float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method $this floorYear(int|float $precision = 1) Truncate the current instance year with given precision. + * @method $this floorYears(int|float $precision = 1) Truncate the current instance year with given precision. + * @method $this ceilYear(int|float $precision = 1) Ceil the current instance year with given precision. + * @method $this ceilYears(int|float $precision = 1) Ceil the current instance year with given precision. + * @method $this roundMonth(int|float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method $this roundMonths(int|float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method $this floorMonth(int|float $precision = 1) Truncate the current instance month with given precision. + * @method $this floorMonths(int|float $precision = 1) Truncate the current instance month with given precision. + * @method $this ceilMonth(int|float $precision = 1) Ceil the current instance month with given precision. + * @method $this ceilMonths(int|float $precision = 1) Ceil the current instance month with given precision. + * @method $this roundWeek(int|float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method $this roundWeeks(int|float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method $this floorWeek(int|float $precision = 1) Truncate the current instance day with given precision. + * @method $this floorWeeks(int|float $precision = 1) Truncate the current instance day with given precision. + * @method $this ceilWeek(int|float $precision = 1) Ceil the current instance day with given precision. + * @method $this ceilWeeks(int|float $precision = 1) Ceil the current instance day with given precision. + * @method $this roundDay(int|float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method $this roundDays(int|float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method $this floorDay(int|float $precision = 1) Truncate the current instance day with given precision. + * @method $this floorDays(int|float $precision = 1) Truncate the current instance day with given precision. + * @method $this ceilDay(int|float $precision = 1) Ceil the current instance day with given precision. + * @method $this ceilDays(int|float $precision = 1) Ceil the current instance day with given precision. + * @method $this roundHour(int|float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method $this roundHours(int|float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method $this floorHour(int|float $precision = 1) Truncate the current instance hour with given precision. + * @method $this floorHours(int|float $precision = 1) Truncate the current instance hour with given precision. + * @method $this ceilHour(int|float $precision = 1) Ceil the current instance hour with given precision. + * @method $this ceilHours(int|float $precision = 1) Ceil the current instance hour with given precision. + * @method $this roundMinute(int|float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method $this roundMinutes(int|float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method $this floorMinute(int|float $precision = 1) Truncate the current instance minute with given precision. + * @method $this floorMinutes(int|float $precision = 1) Truncate the current instance minute with given precision. + * @method $this ceilMinute(int|float $precision = 1) Ceil the current instance minute with given precision. + * @method $this ceilMinutes(int|float $precision = 1) Ceil the current instance minute with given precision. + * @method $this roundSecond(int|float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method $this roundSeconds(int|float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method $this floorSecond(int|float $precision = 1) Truncate the current instance second with given precision. + * @method $this floorSeconds(int|float $precision = 1) Truncate the current instance second with given precision. + * @method $this ceilSecond(int|float $precision = 1) Ceil the current instance second with given precision. + * @method $this ceilSeconds(int|float $precision = 1) Ceil the current instance second with given precision. + * @method $this roundMillennium(int|float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method $this roundMillennia(int|float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method $this floorMillennium(int|float $precision = 1) Truncate the current instance millennium with given precision. + * @method $this floorMillennia(int|float $precision = 1) Truncate the current instance millennium with given precision. + * @method $this ceilMillennium(int|float $precision = 1) Ceil the current instance millennium with given precision. + * @method $this ceilMillennia(int|float $precision = 1) Ceil the current instance millennium with given precision. + * @method $this roundCentury(int|float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method $this roundCenturies(int|float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method $this floorCentury(int|float $precision = 1) Truncate the current instance century with given precision. + * @method $this floorCenturies(int|float $precision = 1) Truncate the current instance century with given precision. + * @method $this ceilCentury(int|float $precision = 1) Ceil the current instance century with given precision. + * @method $this ceilCenturies(int|float $precision = 1) Ceil the current instance century with given precision. + * @method $this roundDecade(int|float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method $this roundDecades(int|float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method $this floorDecade(int|float $precision = 1) Truncate the current instance decade with given precision. + * @method $this floorDecades(int|float $precision = 1) Truncate the current instance decade with given precision. + * @method $this ceilDecade(int|float $precision = 1) Ceil the current instance decade with given precision. + * @method $this ceilDecades(int|float $precision = 1) Ceil the current instance decade with given precision. + * @method $this roundQuarter(int|float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method $this roundQuarters(int|float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method $this floorQuarter(int|float $precision = 1) Truncate the current instance quarter with given precision. + * @method $this floorQuarters(int|float $precision = 1) Truncate the current instance quarter with given precision. + * @method $this ceilQuarter(int|float $precision = 1) Ceil the current instance quarter with given precision. + * @method $this ceilQuarters(int|float $precision = 1) Ceil the current instance quarter with given precision. + * @method $this roundMillisecond(int|float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method $this roundMilliseconds(int|float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method $this floorMillisecond(int|float $precision = 1) Truncate the current instance millisecond with given precision. + * @method $this floorMilliseconds(int|float $precision = 1) Truncate the current instance millisecond with given precision. + * @method $this ceilMillisecond(int|float $precision = 1) Ceil the current instance millisecond with given precision. + * @method $this ceilMilliseconds(int|float $precision = 1) Ceil the current instance millisecond with given precision. + * @method $this roundMicrosecond(int|float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method $this roundMicroseconds(int|float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method $this floorMicrosecond(int|float $precision = 1) Truncate the current instance microsecond with given precision. + * @method $this floorMicroseconds(int|float $precision = 1) Truncate the current instance microsecond with given precision. + * @method $this ceilMicrosecond(int|float $precision = 1) Ceil the current instance microsecond with given precision. + * @method $this ceilMicroseconds(int|float $precision = 1) Ceil the current instance microsecond with given precision. + */ +class CarbonInterval extends DateInterval implements CarbonConverterInterface +{ + use IntervalRounding; + use IntervalStep; + use Mixin { + Mixin::mixin as baseMixin; + } + use Options; + + /** + * Interval spec period designators + */ + public const PERIOD_PREFIX = 'P'; + public const PERIOD_YEARS = 'Y'; + public const PERIOD_MONTHS = 'M'; + public const PERIOD_DAYS = 'D'; + public const PERIOD_TIME_PREFIX = 'T'; + public const PERIOD_HOURS = 'H'; + public const PERIOD_MINUTES = 'M'; + public const PERIOD_SECONDS = 'S'; + + /** + * A translator to ... er ... translate stuff + * + * @var \Symfony\Component\Translation\TranslatorInterface + */ + protected static $translator; + + /** + * @var array|null + */ + protected static $cascadeFactors; + + /** + * @var array + */ + protected static $formats = [ + 'y' => 'y', + 'Y' => 'y', + 'o' => 'y', + 'm' => 'm', + 'n' => 'm', + 'W' => 'weeks', + 'd' => 'd', + 'j' => 'd', + 'z' => 'd', + 'h' => 'h', + 'g' => 'h', + 'H' => 'h', + 'G' => 'h', + 'i' => 'i', + 's' => 's', + 'u' => 'micro', + 'v' => 'milli', + ]; + + /** + * @var array|null + */ + private static $flipCascadeFactors; + + /** + * The registered macros. + * + * @var array + */ + protected static $macros = []; + + /** + * Timezone handler for settings() method. + * + * @var mixed + */ + protected $tzName; + + /** + * Set the instance's timezone from a string or object. + * + * @param \DateTimeZone|string $tzName + * + * @return static + */ + public function setTimezone($tzName) + { + $this->tzName = $tzName; + + return $this; + } + + /** + * @internal + * + * Set the instance's timezone from a string or object and add/subtract the offset difference. + * + * @param \DateTimeZone|string $tzName + * + * @return static + */ + public function shiftTimezone($tzName) + { + $this->tzName = $tzName; + + return $this; + } + + /** + * Mapping of units and factors for cascading. + * + * Should only be modified by changing the factors or referenced constants. + * + * @return array + */ + public static function getCascadeFactors() + { + return static::$cascadeFactors ?: [ + 'milliseconds' => [Carbon::MICROSECONDS_PER_MILLISECOND, 'microseconds'], + 'seconds' => [Carbon::MILLISECONDS_PER_SECOND, 'milliseconds'], + 'minutes' => [Carbon::SECONDS_PER_MINUTE, 'seconds'], + 'hours' => [Carbon::MINUTES_PER_HOUR, 'minutes'], + 'dayz' => [Carbon::HOURS_PER_DAY, 'hours'], + 'weeks' => [Carbon::DAYS_PER_WEEK, 'dayz'], + 'months' => [Carbon::WEEKS_PER_MONTH, 'weeks'], + 'years' => [Carbon::MONTHS_PER_YEAR, 'months'], + ]; + } + + private static function standardizeUnit($unit) + { + $unit = rtrim($unit, 'sz').'s'; + + return $unit === 'days' ? 'dayz' : $unit; + } + + private static function getFlipCascadeFactors() + { + if (!self::$flipCascadeFactors) { + self::$flipCascadeFactors = []; + + foreach (static::getCascadeFactors() as $to => [$factor, $from]) { + self::$flipCascadeFactors[self::standardizeUnit($from)] = [self::standardizeUnit($to), $factor]; + } + } + + return self::$flipCascadeFactors; + } + + /** + * Set default cascading factors for ->cascade() method. + * + * @param array $cascadeFactors + */ + public static function setCascadeFactors(array $cascadeFactors) + { + self::$flipCascadeFactors = null; + static::$cascadeFactors = $cascadeFactors; + } + + /////////////////////////////////////////////////////////////////// + //////////////////////////// CONSTRUCTORS ///////////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Create a new CarbonInterval instance. + * + * @param int|null $years + * @param int|null $months + * @param int|null $weeks + * @param int|null $days + * @param int|null $hours + * @param int|null $minutes + * @param int|null $seconds + * @param int|null $microseconds + * + * @throws Exception when the interval_spec (passed as $years) cannot be parsed as an interval. + */ + public function __construct($years = 1, $months = null, $weeks = null, $days = null, $hours = null, $minutes = null, $seconds = null, $microseconds = null) + { + if ($years instanceof Closure) { + $this->step = $years; + $years = null; + } + + if ($years instanceof DateInterval) { + parent::__construct(static::getDateIntervalSpec($years)); + $this->f = $years->f; + static::copyNegativeUnits($years, $this); + + return; + } + + $spec = $years; + + if (!\is_string($spec) || (float) $years || preg_match('/^[0-9.]/', $years)) { + $spec = static::PERIOD_PREFIX; + + $spec .= $years > 0 ? $years.static::PERIOD_YEARS : ''; + $spec .= $months > 0 ? $months.static::PERIOD_MONTHS : ''; + + $specDays = 0; + $specDays += $weeks > 0 ? $weeks * static::getDaysPerWeek() : 0; + $specDays += $days > 0 ? $days : 0; + + $spec .= $specDays > 0 ? $specDays.static::PERIOD_DAYS : ''; + + if ($hours > 0 || $minutes > 0 || $seconds > 0) { + $spec .= static::PERIOD_TIME_PREFIX; + $spec .= $hours > 0 ? $hours.static::PERIOD_HOURS : ''; + $spec .= $minutes > 0 ? $minutes.static::PERIOD_MINUTES : ''; + $spec .= $seconds > 0 ? $seconds.static::PERIOD_SECONDS : ''; + } + + if ($spec === static::PERIOD_PREFIX) { + // Allow the zero interval. + $spec .= '0'.static::PERIOD_YEARS; + } + } + + parent::__construct($spec); + + if ($microseconds !== null) { + $this->f = $microseconds / Carbon::MICROSECONDS_PER_SECOND; + } + } + + /** + * Returns the factor for a given source-to-target couple. + * + * @param string $source + * @param string $target + * + * @return int|null + */ + public static function getFactor($source, $target) + { + $source = self::standardizeUnit($source); + $target = self::standardizeUnit($target); + $factors = static::getFlipCascadeFactors(); + + if (isset($factors[$source])) { + [$to, $factor] = $factors[$source]; + + if ($to === $target) { + return $factor; + } + + return $factor * static::getFactor($to, $target); + } + + return null; + } + + /** + * Returns the factor for a given source-to-target couple if set, + * else try to find the appropriate constant as the factor, such as Carbon::DAYS_PER_WEEK. + * + * @param string $source + * @param string $target + * + * @return int|null + */ + public function getFactorWithDefault($source, $target) + { + $factor = self::getFactor($source, $target); + + if ($factor) { + return $factor; + } + + static $defaults = [ + 'month' => ['year' => Carbon::MONTHS_PER_YEAR], + 'week' => ['month' => Carbon::WEEKS_PER_MONTH], + 'day' => ['week' => Carbon::DAYS_PER_WEEK], + 'hour' => ['day' => Carbon::HOURS_PER_DAY], + 'minute' => ['hour' => Carbon::MINUTES_PER_HOUR], + 'second' => ['minute' => Carbon::SECONDS_PER_MINUTE], + 'millisecond' => ['second' => Carbon::MILLISECONDS_PER_SECOND], + 'microsecond' => ['millisecond' => Carbon::MICROSECONDS_PER_MILLISECOND], + ]; + + return $defaults[$source][$target] ?? null; + } + + /** + * Returns current config for days per week. + * + * @return int + */ + public static function getDaysPerWeek() + { + return static::getFactor('dayz', 'weeks') ?: Carbon::DAYS_PER_WEEK; + } + + /** + * Returns current config for hours per day. + * + * @return int + */ + public static function getHoursPerDay() + { + return static::getFactor('hours', 'dayz') ?: Carbon::HOURS_PER_DAY; + } + + /** + * Returns current config for minutes per hour. + * + * @return int + */ + public static function getMinutesPerHour() + { + return static::getFactor('minutes', 'hours') ?: Carbon::MINUTES_PER_HOUR; + } + + /** + * Returns current config for seconds per minute. + * + * @return int + */ + public static function getSecondsPerMinute() + { + return static::getFactor('seconds', 'minutes') ?: Carbon::SECONDS_PER_MINUTE; + } + + /** + * Returns current config for microseconds per second. + * + * @return int + */ + public static function getMillisecondsPerSecond() + { + return static::getFactor('milliseconds', 'seconds') ?: Carbon::MILLISECONDS_PER_SECOND; + } + + /** + * Returns current config for microseconds per second. + * + * @return int + */ + public static function getMicrosecondsPerMillisecond() + { + return static::getFactor('microseconds', 'milliseconds') ?: Carbon::MICROSECONDS_PER_MILLISECOND; + } + + /** + * Create a new CarbonInterval instance from specific values. + * This is an alias for the constructor that allows better fluent + * syntax as it allows you to do CarbonInterval::create(1)->fn() rather than + * (new CarbonInterval(1))->fn(). + * + * @param int $years + * @param int $months + * @param int $weeks + * @param int $days + * @param int $hours + * @param int $minutes + * @param int $seconds + * @param int $microseconds + * + * @throws Exception when the interval_spec (passed as $years) cannot be parsed as an interval. + * + * @return static + */ + public static function create($years = 1, $months = null, $weeks = null, $days = null, $hours = null, $minutes = null, $seconds = null, $microseconds = null) + { + return new static($years, $months, $weeks, $days, $hours, $minutes, $seconds, $microseconds); + } + + /** + * Parse a string into a new CarbonInterval object according to the specified format. + * + * @example + * ``` + * echo Carboninterval::createFromFormat('H:i', '1:30'); + * ``` + * + * @param string $format Format of the $interval input string + * @param string|null $interval Input string to convert into an interval + * + * @throws \Carbon\Exceptions\ParseErrorException when the $interval cannot be parsed as an interval. + * + * @return static + */ + public static function createFromFormat(string $format, ?string $interval) + { + $instance = new static(0); + $length = mb_strlen($format); + + if (preg_match('/s([,.])([uv])$/', $format, $match)) { + $interval = explode($match[1], $interval); + $index = \count($interval) - 1; + $interval[$index] = str_pad($interval[$index], $match[2] === 'v' ? 3 : 6, '0'); + $interval = implode($match[1], $interval); + } + + $interval = $interval ?? ''; + + for ($index = 0; $index < $length; $index++) { + $expected = mb_substr($format, $index, 1); + $nextCharacter = mb_substr($interval, 0, 1); + $unit = static::$formats[$expected] ?? null; + + if ($unit) { + if (!preg_match('/^-?\d+/', $interval, $match)) { + throw new ParseErrorException('number', $nextCharacter); + } + + $interval = mb_substr($interval, mb_strlen($match[0])); + $instance->$unit += (int) ($match[0]); + + continue; + } + + if ($nextCharacter !== $expected) { + throw new ParseErrorException( + "'$expected'", + $nextCharacter, + 'Allowed substitutes for interval formats are '.implode(', ', array_keys(static::$formats))."\n". + 'See https://php.net/manual/en/function.date.php for their meaning' + ); + } + + $interval = mb_substr($interval, 1); + } + + if ($interval !== '') { + throw new ParseErrorException( + 'end of string', + $interval + ); + } + + return $instance; + } + + /** + * Get a copy of the instance. + * + * @return static + */ + public function copy() + { + $date = new static(0); + $date->copyProperties($this); + $date->step = $this->step; + + return $date; + } + + /** + * Get a copy of the instance. + * + * @return static + */ + public function clone() + { + return $this->copy(); + } + + /** + * Provide static helpers to create instances. Allows CarbonInterval::years(3). + * + * Note: This is done using the magic method to allow static and instance methods to + * have the same names. + * + * @param string $method magic method name called + * @param array $parameters parameters list + * + * @return static|null + */ + public static function __callStatic($method, $parameters) + { + try { + $interval = new static(0); + $localStrictModeEnabled = $interval->localStrictModeEnabled; + $interval->localStrictModeEnabled = true; + + $result = static::hasMacro($method) + ? static::bindMacroContext(null, function () use (&$method, &$parameters, &$interval) { + return $interval->callMacro($method, $parameters); + }) + : $interval->$method(...$parameters); + + $interval->localStrictModeEnabled = $localStrictModeEnabled; + + return $result; + } catch (BadFluentSetterException $exception) { + if (Carbon::isStrictModeEnabled()) { + throw new BadFluentConstructorException($method, 0, $exception); + } + + return null; + } + } + + /** + * Return the current context from inside a macro callee or a new one if static. + * + * @return static + */ + protected static function this() + { + return end(static::$macroContextStack) ?: new static(0); + } + + /** + * Creates a CarbonInterval from string. + * + * Format: + * + * Suffix | Unit | Example | DateInterval expression + * -------|---------|---------|------------------------ + * y | years | 1y | P1Y + * mo | months | 3mo | P3M + * w | weeks | 2w | P2W + * d | days | 28d | P28D + * h | hours | 4h | PT4H + * m | minutes | 12m | PT12M + * s | seconds | 59s | PT59S + * + * e. g. `1w 3d 4h 32m 23s` is converted to 10 days 4 hours 32 minutes and 23 seconds. + * + * Special cases: + * - An empty string will return a zero interval + * - Fractions are allowed for weeks, days, hours and minutes and will be converted + * and rounded to the next smaller value (caution: 0.5w = 4d) + * + * @param string $intervalDefinition + * + * @return static + */ + public static function fromString($intervalDefinition) + { + if (empty($intervalDefinition)) { + return new static(0); + } + + $years = 0; + $months = 0; + $weeks = 0; + $days = 0; + $hours = 0; + $minutes = 0; + $seconds = 0; + $milliseconds = 0; + $microseconds = 0; + + $pattern = '/(\d+(?:\.\d+)?)\h*([^\d\h]*)/i'; + preg_match_all($pattern, $intervalDefinition, $parts, PREG_SET_ORDER); + + while ([$part, $value, $unit] = array_shift($parts)) { + $intValue = (int) $value; + $fraction = (float) $value - $intValue; + + // Fix calculation precision + switch (round($fraction, 6)) { + case 1: + $fraction = 0; + $intValue++; + + break; + case 0: + $fraction = 0; + + break; + } + + switch ($unit === 'µs' ? 'µs' : strtolower($unit)) { + case 'millennia': + case 'millennium': + $years += $intValue * CarbonInterface::YEARS_PER_MILLENNIUM; + + break; + + case 'century': + case 'centuries': + $years += $intValue * CarbonInterface::YEARS_PER_CENTURY; + + break; + + case 'decade': + case 'decades': + $years += $intValue * CarbonInterface::YEARS_PER_DECADE; + + break; + + case 'year': + case 'years': + case 'y': + $years += $intValue; + + break; + + case 'quarter': + case 'quarters': + $months += $intValue * CarbonInterface::MONTHS_PER_QUARTER; + + break; + + case 'month': + case 'months': + case 'mo': + $months += $intValue; + + break; + + case 'week': + case 'weeks': + case 'w': + $weeks += $intValue; + + if ($fraction) { + $parts[] = [null, $fraction * static::getDaysPerWeek(), 'd']; + } + + break; + + case 'day': + case 'days': + case 'd': + $days += $intValue; + + if ($fraction) { + $parts[] = [null, $fraction * static::getHoursPerDay(), 'h']; + } + + break; + + case 'hour': + case 'hours': + case 'h': + $hours += $intValue; + + if ($fraction) { + $parts[] = [null, $fraction * static::getMinutesPerHour(), 'm']; + } + + break; + + case 'minute': + case 'minutes': + case 'm': + $minutes += $intValue; + + if ($fraction) { + $parts[] = [null, $fraction * static::getSecondsPerMinute(), 's']; + } + + break; + + case 'second': + case 'seconds': + case 's': + $seconds += $intValue; + + if ($fraction) { + $parts[] = [null, $fraction * static::getMillisecondsPerSecond(), 'ms']; + } + + break; + + case 'millisecond': + case 'milliseconds': + case 'milli': + case 'ms': + $milliseconds += $intValue; + + if ($fraction) { + $microseconds += round($fraction * static::getMicrosecondsPerMillisecond()); + } + + break; + + case 'microsecond': + case 'microseconds': + case 'micro': + case 'µs': + $microseconds += $intValue; + + break; + + default: + throw new InvalidIntervalException( + sprintf('Invalid part %s in definition %s', $part, $intervalDefinition) + ); + } + } + + return new static($years, $months, $weeks, $days, $hours, $minutes, $seconds, $milliseconds * Carbon::MICROSECONDS_PER_MILLISECOND + $microseconds); + } + + /** + * Creates a CarbonInterval from string using a different locale. + * + * @param string $interval interval string in the given language (may also contain English). + * @param string|null $locale if locale is null or not specified, current global locale will be used instead. + * + * @return static + */ + public static function parseFromLocale($interval, $locale = null) + { + return static::fromString(Carbon::translateTimeString($interval, $locale ?: static::getLocale(), 'en')); + } + + private static function castIntervalToClass(DateInterval $interval, string $className) + { + $mainClass = DateInterval::class; + + if (!is_a($className, $mainClass, true)) { + throw new InvalidCastException("$className is not a sub-class of $mainClass."); + } + + $microseconds = $interval->f; + $instance = new $className(static::getDateIntervalSpec($interval)); + + if ($microseconds) { + $instance->f = $microseconds; + } + + if ($interval instanceof self && is_a($className, self::class, true)) { + static::copyStep($interval, $instance); + } + + static::copyNegativeUnits($interval, $instance); + + return $instance; + } + + private static function copyNegativeUnits(DateInterval $from, DateInterval $to): void + { + $to->invert = $from->invert; + + foreach (['y', 'm', 'd', 'h', 'i', 's'] as $unit) { + if ($from->$unit < 0) { + $to->$unit *= -1; + } + } + } + + private static function copyStep(self $from, self $to): void + { + $to->setStep($from->getStep()); + } + + /** + * Cast the current instance into the given class. + * + * @param string $className The $className::instance() method will be called to cast the current object. + * + * @return DateInterval + */ + public function cast(string $className) + { + return self::castIntervalToClass($this, $className); + } + + /** + * Create a CarbonInterval instance from a DateInterval one. Can not instance + * DateInterval objects created from DateTime::diff() as you can't externally + * set the $days field. + * + * @param DateInterval $interval + * + * @return static + */ + public static function instance(DateInterval $interval) + { + return self::castIntervalToClass($interval, static::class); + } + + /** + * Make a CarbonInterval instance from given variable if possible. + * + * Always return a new instance. Parse only strings and only these likely to be intervals (skip dates + * and recurrences). Throw an exception for invalid format, but otherwise return null. + * + * @param mixed|int|DateInterval|string|Closure|null $interval interval or number of the given $unit + * @param string|null $unit if specified, $interval must be an integer + * + * @return static|null + */ + public static function make($interval, $unit = null) + { + if ($unit) { + $interval = "$interval ".Carbon::pluralUnit($unit); + } + + if ($interval instanceof DateInterval) { + return static::instance($interval); + } + + if ($interval instanceof Closure) { + return new static($interval); + } + + if (!\is_string($interval)) { + return null; + } + + return static::makeFromString($interval); + } + + protected static function makeFromString(string $interval) + { + $interval = preg_replace('/\s+/', ' ', trim($interval)); + + if (preg_match('/^P[T0-9]/', $interval)) { + return new static($interval); + } + + if (preg_match('/^(?:\h*\d+(?:\.\d+)?\h*[a-z]+)+$/i', $interval)) { + return static::fromString($interval); + } + + /** @var static $interval */ + $interval = static::createFromDateString($interval); + + return !$interval || $interval->isEmpty() ? null : $interval; + } + + protected function resolveInterval($interval) + { + if (!($interval instanceof self)) { + return self::make($interval); + } + + return $interval; + } + + /** + * Sets up a DateInterval from the relative parts of the string. + * + * @param string $time + * + * @return static + * + * @link https://php.net/manual/en/dateinterval.createfromdatestring.php + */ + #[ReturnTypeWillChange] + public static function createFromDateString($time) + { + $interval = @parent::createFromDateString(strtr($time, [ + ',' => ' ', + ' and ' => ' ', + ])); + + if ($interval instanceof DateInterval) { + $interval = static::instance($interval); + } + + return $interval; + } + + /////////////////////////////////////////////////////////////////// + ///////////////////////// GETTERS AND SETTERS ///////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Get a part of the CarbonInterval object. + * + * @param string $name + * + * @throws UnknownGetterException + * + * @return int|float|string + */ + public function get($name) + { + if (str_starts_with($name, 'total')) { + return $this->total(substr($name, 5)); + } + + switch ($name) { + case 'years': + return $this->y; + + case 'months': + return $this->m; + + case 'dayz': + return $this->d; + + case 'hours': + return $this->h; + + case 'minutes': + return $this->i; + + case 'seconds': + return $this->s; + + case 'milli': + case 'milliseconds': + return (int) (round($this->f * Carbon::MICROSECONDS_PER_SECOND) / Carbon::MICROSECONDS_PER_MILLISECOND); + + case 'micro': + case 'microseconds': + return (int) round($this->f * Carbon::MICROSECONDS_PER_SECOND); + + case 'microExcludeMilli': + return (int) round($this->f * Carbon::MICROSECONDS_PER_SECOND) % Carbon::MICROSECONDS_PER_MILLISECOND; + + case 'weeks': + return (int) ($this->d / static::getDaysPerWeek()); + + case 'daysExcludeWeeks': + case 'dayzExcludeWeeks': + return $this->d % static::getDaysPerWeek(); + + case 'locale': + return $this->getTranslatorLocale(); + + default: + throw new UnknownGetterException($name); + } + } + + /** + * Get a part of the CarbonInterval object. + * + * @param string $name + * + * @throws UnknownGetterException + * + * @return int|float|string + */ + public function __get($name) + { + return $this->get($name); + } + + /** + * Set a part of the CarbonInterval object. + * + * @param string|array $name + * @param int $value + * + * @throws UnknownSetterException + * + * @return $this + */ + public function set($name, $value = null) + { + $properties = \is_array($name) ? $name : [$name => $value]; + + foreach ($properties as $key => $value) { + switch (Carbon::singularUnit(rtrim($key, 'z'))) { + case 'year': + $this->y = $value; + + break; + + case 'month': + $this->m = $value; + + break; + + case 'week': + $this->d = $value * static::getDaysPerWeek(); + + break; + + case 'day': + $this->d = $value; + + break; + + case 'daysexcludeweek': + case 'dayzexcludeweek': + $this->d = $this->weeks * static::getDaysPerWeek() + $value; + + break; + + case 'hour': + $this->h = $value; + + break; + + case 'minute': + $this->i = $value; + + break; + + case 'second': + $this->s = $value; + + break; + + case 'milli': + case 'millisecond': + $this->microseconds = $value * Carbon::MICROSECONDS_PER_MILLISECOND + $this->microseconds % Carbon::MICROSECONDS_PER_MILLISECOND; + + break; + + case 'micro': + case 'microsecond': + $this->f = $value / Carbon::MICROSECONDS_PER_SECOND; + + break; + + default: + if ($this->localStrictModeEnabled ?? Carbon::isStrictModeEnabled()) { + throw new UnknownSetterException($key); + } + + $this->$key = $value; + } + } + + return $this; + } + + /** + * Set a part of the CarbonInterval object. + * + * @param string $name + * @param int $value + * + * @throws UnknownSetterException + */ + public function __set($name, $value) + { + $this->set($name, $value); + } + + /** + * Allow setting of weeks and days to be cumulative. + * + * @param int $weeks Number of weeks to set + * @param int $days Number of days to set + * + * @return static + */ + public function weeksAndDays($weeks, $days) + { + $this->dayz = ($weeks * static::getDaysPerWeek()) + $days; + + return $this; + } + + /** + * Returns true if the interval is empty for each unit. + * + * @return bool + */ + public function isEmpty() + { + return $this->years === 0 && + $this->months === 0 && + $this->dayz === 0 && + !$this->days && + $this->hours === 0 && + $this->minutes === 0 && + $this->seconds === 0 && + $this->microseconds === 0; + } + + /** + * Register a custom macro. + * + * @example + * ``` + * CarbonInterval::macro('twice', function () { + * return $this->times(2); + * }); + * echo CarbonInterval::hours(2)->twice(); + * ``` + * + * @param string $name + * @param object|callable $macro + * + * @return void + */ + public static function macro($name, $macro) + { + static::$macros[$name] = $macro; + } + + /** + * Register macros from a mixin object. + * + * @example + * ``` + * CarbonInterval::mixin(new class { + * public function daysToHours() { + * return function () { + * $this->hours += $this->days; + * $this->days = 0; + * + * return $this; + * }; + * } + * public function hoursToDays() { + * return function () { + * $this->days += $this->hours; + * $this->hours = 0; + * + * return $this; + * }; + * } + * }); + * echo CarbonInterval::hours(5)->hoursToDays() . "\n"; + * echo CarbonInterval::days(5)->daysToHours() . "\n"; + * ``` + * + * @param object|string $mixin + * + * @throws ReflectionException + * + * @return void + */ + public static function mixin($mixin) + { + static::baseMixin($mixin); + } + + /** + * Check if macro is registered. + * + * @param string $name + * + * @return bool + */ + public static function hasMacro($name) + { + return isset(static::$macros[$name]); + } + + /** + * Call given macro. + * + * @param string $name + * @param array $parameters + * + * @return mixed + */ + protected function callMacro($name, $parameters) + { + $macro = static::$macros[$name]; + + if ($macro instanceof Closure) { + $boundMacro = @$macro->bindTo($this, static::class) ?: @$macro->bindTo(null, static::class); + + return ($boundMacro ?: $macro)(...$parameters); + } + + return $macro(...$parameters); + } + + /** + * Allow fluent calls on the setters... CarbonInterval::years(3)->months(5)->day(). + * + * Note: This is done using the magic method to allow static and instance methods to + * have the same names. + * + * @param string $method magic method name called + * @param array $parameters parameters list + * + * @throws BadFluentSetterException|Throwable + * + * @return static + */ + public function __call($method, $parameters) + { + if (static::hasMacro($method)) { + return static::bindMacroContext($this, function () use (&$method, &$parameters) { + return $this->callMacro($method, $parameters); + }); + } + + $roundedValue = $this->callRoundMethod($method, $parameters); + + if ($roundedValue !== null) { + return $roundedValue; + } + + if (preg_match('/^(?add|sub)(?[A-Z].*)$/', $method, $match)) { + return $this->{$match['method']}($parameters[0], $match['unit']); + } + + try { + $this->set($method, \count($parameters) === 0 ? 1 : $parameters[0]); + } catch (UnknownSetterException $exception) { + if ($this->localStrictModeEnabled ?? Carbon::isStrictModeEnabled()) { + throw new BadFluentSetterException($method, 0, $exception); + } + } + + return $this; + } + + protected function getForHumansInitialVariables($syntax, $short) + { + if (\is_array($syntax)) { + return $syntax; + } + + if (\is_int($short)) { + return [ + 'parts' => $short, + 'short' => false, + ]; + } + + if (\is_bool($syntax)) { + return [ + 'short' => $syntax, + 'syntax' => CarbonInterface::DIFF_ABSOLUTE, + ]; + } + + return []; + } + + /** + * @param mixed $syntax + * @param mixed $short + * @param mixed $parts + * @param mixed $options + * + * @return array + */ + protected function getForHumansParameters($syntax = null, $short = false, $parts = -1, $options = null) + { + $optionalSpace = ' '; + $default = $this->getTranslationMessage('list.0') ?? $this->getTranslationMessage('list') ?? ' '; + $join = $default === '' ? '' : ' '; + $altNumbers = false; + $aUnit = false; + $minimumUnit = 's'; + $skip = []; + extract($this->getForHumansInitialVariables($syntax, $short)); + $skip = array_filter((array) $skip, static function ($value) { + return \is_string($value) && $value !== ''; + }); + + if ($syntax === null) { + $syntax = CarbonInterface::DIFF_ABSOLUTE; + } + + if ($parts === -1) { + $parts = INF; + } + + if ($options === null) { + $options = static::getHumanDiffOptions(); + } + + if ($join === false) { + $join = ' '; + } elseif ($join === true) { + $join = [ + $default, + $this->getTranslationMessage('list.1') ?? $default, + ]; + } + + if ($altNumbers) { + if ($altNumbers !== true) { + $language = new Language($this->locale); + $altNumbers = \in_array($language->getCode(), (array) $altNumbers); + } + } + + if (\is_array($join)) { + [$default, $last] = $join; + + if ($default !== ' ') { + $optionalSpace = ''; + } + + $join = function ($list) use ($default, $last) { + if (\count($list) < 2) { + return implode('', $list); + } + + $end = array_pop($list); + + return implode($default, $list).$last.$end; + }; + } + + if (\is_string($join)) { + if ($join !== ' ') { + $optionalSpace = ''; + } + + $glue = $join; + $join = function ($list) use ($glue) { + return implode($glue, $list); + }; + } + + $interpolations = [ + ':optional-space' => $optionalSpace, + ]; + + return [$syntax, $short, $parts, $options, $join, $aUnit, $altNumbers, $interpolations, $minimumUnit, $skip]; + } + + protected static function getRoundingMethodFromOptions(int $options): ?string + { + if ($options & CarbonInterface::ROUND) { + return 'round'; + } + + if ($options & CarbonInterface::CEIL) { + return 'ceil'; + } + + if ($options & CarbonInterface::FLOOR) { + return 'floor'; + } + + return null; + } + + /** + * Returns interval values as an array where key are the unit names and values the counts. + * + * @return int[] + */ + public function toArray() + { + return [ + 'years' => $this->years, + 'months' => $this->months, + 'weeks' => $this->weeks, + 'days' => $this->daysExcludeWeeks, + 'hours' => $this->hours, + 'minutes' => $this->minutes, + 'seconds' => $this->seconds, + 'microseconds' => $this->microseconds, + ]; + } + + /** + * Returns interval non-zero values as an array where key are the unit names and values the counts. + * + * @return int[] + */ + public function getNonZeroValues() + { + return array_filter($this->toArray(), 'intval'); + } + + /** + * Returns interval values as an array where key are the unit names and values the counts + * from the biggest non-zero one the the smallest non-zero one. + * + * @return int[] + */ + public function getValuesSequence() + { + $nonZeroValues = $this->getNonZeroValues(); + + if ($nonZeroValues === []) { + return []; + } + + $keys = array_keys($nonZeroValues); + $firstKey = $keys[0]; + $lastKey = $keys[\count($keys) - 1]; + $values = []; + $record = false; + + foreach ($this->toArray() as $unit => $count) { + if ($unit === $firstKey) { + $record = true; + } + + if ($record) { + $values[$unit] = $count; + } + + if ($unit === $lastKey) { + $record = false; + } + } + + return $values; + } + + /** + * Get the current interval in a human readable format in the current locale. + * + * @example + * ``` + * echo CarbonInterval::fromString('4d 3h 40m')->forHumans() . "\n"; + * echo CarbonInterval::fromString('4d 3h 40m')->forHumans(['parts' => 2]) . "\n"; + * echo CarbonInterval::fromString('4d 3h 40m')->forHumans(['parts' => 3, 'join' => true]) . "\n"; + * echo CarbonInterval::fromString('4d 3h 40m')->forHumans(['short' => true]) . "\n"; + * echo CarbonInterval::fromString('1d 24h')->forHumans(['join' => ' or ']) . "\n"; + * echo CarbonInterval::fromString('1d 24h')->forHumans(['minimumUnit' => 'hour']) . "\n"; + * ``` + * + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'skip' entry, list of units to skip (array of strings or a single string, + * ` it can be the unit name (singular or plural) or its shortcut + * ` (y, m, w, d, h, min, s, ms, µs). + * - 'aUnit' entry, prefer "an hour" over "1 hour" if true + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * - 'minimumUnit' entry determines the smallest unit of time to display can be long or + * ` short form of the units, e.g. 'hour' or 'h' (default value: s) + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: -1: no limits) + * @param int $options human diff options + * + * @throws Exception + * + * @return string + */ + public function forHumans($syntax = null, $short = false, $parts = -1, $options = null) + { + [$syntax, $short, $parts, $options, $join, $aUnit, $altNumbers, $interpolations, $minimumUnit, $skip] = $this + ->getForHumansParameters($syntax, $short, $parts, $options); + + $interval = []; + + $syntax = (int) ($syntax ?? CarbonInterface::DIFF_ABSOLUTE); + $absolute = $syntax === CarbonInterface::DIFF_ABSOLUTE; + $relativeToNow = $syntax === CarbonInterface::DIFF_RELATIVE_TO_NOW; + $count = 1; + $unit = $short ? 's' : 'second'; + $isFuture = $this->invert === 1; + $transId = $relativeToNow ? ($isFuture ? 'from_now' : 'ago') : ($isFuture ? 'after' : 'before'); + + /** @var \Symfony\Component\Translation\Translator $translator */ + $translator = $this->getLocalTranslator(); + + $handleDeclensions = function ($unit, $count) use ($interpolations, $transId, $translator, $altNumbers, $absolute) { + if (!$absolute) { + // Some languages have special pluralization for past and future tense. + $key = $unit.'_'.$transId; + $result = $this->translate($key, $interpolations, $count, $translator, $altNumbers); + + if ($result !== $key) { + return $result; + } + } + + $result = $this->translate($unit, $interpolations, $count, $translator, $altNumbers); + + if ($result !== $unit) { + return $result; + } + + return null; + }; + + $intervalValues = $this; + $method = static::getRoundingMethodFromOptions($options); + + if ($method) { + $previousCount = INF; + + while ( + \count($intervalValues->getNonZeroValues()) > $parts && + ($count = \count($keys = array_keys($intervalValues->getValuesSequence()))) > 1 + ) { + $intervalValues = $this->copy()->roundUnit( + $keys[min($count, $previousCount - 1) - 2], + 1, + $method + ); + $previousCount = $count; + } + } + + $diffIntervalArray = [ + ['value' => $intervalValues->years, 'unit' => 'year', 'unitShort' => 'y'], + ['value' => $intervalValues->months, 'unit' => 'month', 'unitShort' => 'm'], + ['value' => $intervalValues->weeks, 'unit' => 'week', 'unitShort' => 'w'], + ['value' => $intervalValues->daysExcludeWeeks, 'unit' => 'day', 'unitShort' => 'd'], + ['value' => $intervalValues->hours, 'unit' => 'hour', 'unitShort' => 'h'], + ['value' => $intervalValues->minutes, 'unit' => 'minute', 'unitShort' => 'min'], + ['value' => $intervalValues->seconds, 'unit' => 'second', 'unitShort' => 's'], + ['value' => $intervalValues->milliseconds, 'unit' => 'millisecond', 'unitShort' => 'ms'], + ['value' => $intervalValues->microExcludeMilli, 'unit' => 'microsecond', 'unitShort' => 'µs'], + ]; + + if (!empty($skip)) { + foreach ($diffIntervalArray as $index => &$unitData) { + $nextIndex = $index + 1; + + if ($unitData['value'] && + isset($diffIntervalArray[$nextIndex]) && + \count(array_intersect([$unitData['unit'], $unitData['unit'].'s', $unitData['unitShort']], $skip)) + ) { + $diffIntervalArray[$nextIndex]['value'] += $unitData['value'] * + self::getFactorWithDefault($diffIntervalArray[$nextIndex]['unit'], $unitData['unit']); + $unitData['value'] = 0; + } + } + } + + $transChoice = function ($short, $unitData) use ($absolute, $handleDeclensions, $translator, $aUnit, $altNumbers, $interpolations) { + $count = $unitData['value']; + + if ($short) { + $result = $handleDeclensions($unitData['unitShort'], $count); + + if ($result !== null) { + return $result; + } + } elseif ($aUnit) { + $result = $handleDeclensions('a_'.$unitData['unit'], $count); + + if ($result !== null) { + return $result; + } + } + + if (!$absolute) { + return $handleDeclensions($unitData['unit'], $count); + } + + return $this->translate($unitData['unit'], $interpolations, $count, $translator, $altNumbers); + }; + + $fallbackUnit = ['second', 's']; + + foreach ($diffIntervalArray as $diffIntervalData) { + if ($diffIntervalData['value'] > 0) { + $unit = $short ? $diffIntervalData['unitShort'] : $diffIntervalData['unit']; + $count = $diffIntervalData['value']; + $interval[] = $transChoice($short, $diffIntervalData); + } elseif ($options & CarbonInterface::SEQUENTIAL_PARTS_ONLY && \count($interval) > 0) { + break; + } + + // break the loop after we get the required number of parts in array + if (\count($interval) >= $parts) { + break; + } + + // break the loop after we have reached the minimum unit + if (\in_array($minimumUnit, [$diffIntervalData['unit'], $diffIntervalData['unitShort']])) { + $fallbackUnit = [$diffIntervalData['unit'], $diffIntervalData['unitShort']]; + + break; + } + } + + if (\count($interval) === 0) { + if ($relativeToNow && $options & CarbonInterface::JUST_NOW) { + $key = 'diff_now'; + $translation = $this->translate($key, $interpolations, null, $translator); + + if ($translation !== $key) { + return $translation; + } + } + + $count = $options & CarbonInterface::NO_ZERO_DIFF ? 1 : 0; + $unit = $fallbackUnit[$short ? 1 : 0]; + $interval[] = $this->translate($unit, $interpolations, $count, $translator, $altNumbers); + } + + // join the interval parts by a space + $time = $join($interval); + + unset($diffIntervalArray, $interval); + + if ($absolute) { + return $time; + } + + $isFuture = $this->invert === 1; + + $transId = $relativeToNow ? ($isFuture ? 'from_now' : 'ago') : ($isFuture ? 'after' : 'before'); + + if ($parts === 1) { + if ($relativeToNow && $unit === 'day') { + if ($count === 1 && $options & CarbonInterface::ONE_DAY_WORDS) { + $key = $isFuture ? 'diff_tomorrow' : 'diff_yesterday'; + $translation = $this->translate($key, $interpolations, null, $translator); + + if ($translation !== $key) { + return $translation; + } + } + + if ($count === 2 && $options & CarbonInterface::TWO_DAY_WORDS) { + $key = $isFuture ? 'diff_after_tomorrow' : 'diff_before_yesterday'; + $translation = $this->translate($key, $interpolations, null, $translator); + + if ($translation !== $key) { + return $translation; + } + } + } + + $aTime = $aUnit ? $handleDeclensions('a_'.$unit, $count) : null; + + $time = $aTime ?: $handleDeclensions($unit, $count) ?: $time; + } + + $time = [':time' => $time]; + + return $this->translate($transId, array_merge($time, $interpolations, $time), null, $translator); + } + + /** + * Format the instance as a string using the forHumans() function. + * + * @throws Exception + * + * @return string + */ + public function __toString() + { + $format = $this->localToStringFormat; + + if ($format) { + if ($format instanceof Closure) { + return $format($this); + } + + return $this->format($format); + } + + return $this->forHumans(); + } + + /** + * Return native DateInterval PHP object matching the current instance. + * + * @example + * ``` + * var_dump(CarbonInterval::hours(2)->toDateInterval()); + * ``` + * + * @return DateInterval + */ + public function toDateInterval() + { + return self::castIntervalToClass($this, DateInterval::class); + } + + /** + * Convert the interval to a CarbonPeriod. + * + * @param DateTimeInterface|string|int ...$params Start date, [end date or recurrences] and optional settings. + * + * @return CarbonPeriod + */ + public function toPeriod(...$params) + { + if ($this->tzName) { + $tz = \is_string($this->tzName) ? new DateTimeZone($this->tzName) : $this->tzName; + + if ($tz instanceof DateTimeZone) { + array_unshift($params, $tz); + } + } + + return CarbonPeriod::create($this, ...$params); + } + + /** + * Invert the interval. + * + * @param bool|int $inverted if a parameter is passed, the passed value casted as 1 or 0 is used + * as the new value of the ->invert property. + * + * @return $this + */ + public function invert($inverted = null) + { + $this->invert = (\func_num_args() === 0 ? !$this->invert : $inverted) ? 1 : 0; + + return $this; + } + + protected function solveNegativeInterval() + { + if (!$this->isEmpty() && $this->years <= 0 && $this->months <= 0 && $this->dayz <= 0 && $this->hours <= 0 && $this->minutes <= 0 && $this->seconds <= 0 && $this->microseconds <= 0) { + $this->years *= -1; + $this->months *= -1; + $this->dayz *= -1; + $this->hours *= -1; + $this->minutes *= -1; + $this->seconds *= -1; + $this->microseconds *= -1; + $this->invert(); + } + + return $this; + } + + /** + * Add the passed interval to the current instance. + * + * @param string|DateInterval $unit + * @param int|float $value + * + * @return $this + */ + public function add($unit, $value = 1) + { + if (is_numeric($unit)) { + [$value, $unit] = [$unit, $value]; + } + + if (\is_string($unit) && !preg_match('/^\s*\d/', $unit)) { + $unit = "$value $unit"; + $value = 1; + } + + $interval = static::make($unit); + + if (!$interval) { + throw new InvalidIntervalException('This type of data cannot be added/subtracted.'); + } + + if ($value !== 1) { + $interval->times($value); + } + + $sign = ($this->invert === 1) !== ($interval->invert === 1) ? -1 : 1; + $this->years += $interval->y * $sign; + $this->months += $interval->m * $sign; + $this->dayz += ($interval->days === false ? $interval->d : $interval->days) * $sign; + $this->hours += $interval->h * $sign; + $this->minutes += $interval->i * $sign; + $this->seconds += $interval->s * $sign; + $this->microseconds += $interval->microseconds * $sign; + + $this->solveNegativeInterval(); + + return $this; + } + + /** + * Subtract the passed interval to the current instance. + * + * @param string|DateInterval $unit + * @param int|float $value + * + * @return $this + */ + public function sub($unit, $value = 1) + { + if (is_numeric($unit)) { + [$value, $unit] = [$unit, $value]; + } + + return $this->add($unit, -(float) $value); + } + + /** + * Subtract the passed interval to the current instance. + * + * @param string|DateInterval $unit + * @param int|float $value + * + * @return $this + */ + public function subtract($unit, $value = 1) + { + return $this->sub($unit, $value); + } + + /** + * Add given parameters to the current interval. + * + * @param int $years + * @param int $months + * @param int|float $weeks + * @param int|float $days + * @param int|float $hours + * @param int|float $minutes + * @param int|float $seconds + * @param int|float $microseconds + * + * @return $this + */ + public function plus( + $years = 0, + $months = 0, + $weeks = 0, + $days = 0, + $hours = 0, + $minutes = 0, + $seconds = 0, + $microseconds = 0 + ): self { + return $this->add(" + $years years $months months $weeks weeks $days days + $hours hours $minutes minutes $seconds seconds $microseconds microseconds + "); + } + + /** + * Add given parameters to the current interval. + * + * @param int $years + * @param int $months + * @param int|float $weeks + * @param int|float $days + * @param int|float $hours + * @param int|float $minutes + * @param int|float $seconds + * @param int|float $microseconds + * + * @return $this + */ + public function minus( + $years = 0, + $months = 0, + $weeks = 0, + $days = 0, + $hours = 0, + $minutes = 0, + $seconds = 0, + $microseconds = 0 + ): self { + return $this->sub(" + $years years $months months $weeks weeks $days days + $hours hours $minutes minutes $seconds seconds $microseconds microseconds + "); + } + + /** + * Multiply current instance given number of times. times() is naive, it multiplies each unit + * (so day can be greater than 31, hour can be greater than 23, etc.) and the result is rounded + * separately for each unit. + * + * Use times() when you want a fast and approximated calculation that does not cascade units. + * + * For a precise and cascaded calculation, + * + * @see multiply() + * + * @param float|int $factor + * + * @return $this + */ + public function times($factor) + { + if ($factor < 0) { + $this->invert = $this->invert ? 0 : 1; + $factor = -$factor; + } + + $this->years = (int) round($this->years * $factor); + $this->months = (int) round($this->months * $factor); + $this->dayz = (int) round($this->dayz * $factor); + $this->hours = (int) round($this->hours * $factor); + $this->minutes = (int) round($this->minutes * $factor); + $this->seconds = (int) round($this->seconds * $factor); + $this->microseconds = (int) round($this->microseconds * $factor); + + return $this; + } + + /** + * Divide current instance by a given divider. shares() is naive, it divides each unit separately + * and the result is rounded for each unit. So 5 hours and 20 minutes shared by 3 becomes 2 hours + * and 7 minutes. + * + * Use shares() when you want a fast and approximated calculation that does not cascade units. + * + * For a precise and cascaded calculation, + * + * @see divide() + * + * @param float|int $divider + * + * @return $this + */ + public function shares($divider) + { + return $this->times(1 / $divider); + } + + protected function copyProperties(self $interval, $ignoreSign = false) + { + $this->years = $interval->years; + $this->months = $interval->months; + $this->dayz = $interval->dayz; + $this->hours = $interval->hours; + $this->minutes = $interval->minutes; + $this->seconds = $interval->seconds; + $this->microseconds = $interval->microseconds; + + if (!$ignoreSign) { + $this->invert = $interval->invert; + } + + return $this; + } + + /** + * Multiply and cascade current instance by a given factor. + * + * @param float|int $factor + * + * @return $this + */ + public function multiply($factor) + { + if ($factor < 0) { + $this->invert = $this->invert ? 0 : 1; + $factor = -$factor; + } + + $yearPart = (int) floor($this->years * $factor); // Split calculation to prevent imprecision + + if ($yearPart) { + $this->years -= $yearPart / $factor; + } + + return $this->copyProperties( + static::create($yearPart) + ->microseconds(abs($this->totalMicroseconds) * $factor) + ->cascade(), + true + ); + } + + /** + * Divide and cascade current instance by a given divider. + * + * @param float|int $divider + * + * @return $this + */ + public function divide($divider) + { + return $this->multiply(1 / $divider); + } + + /** + * Get the interval_spec string of a date interval. + * + * @param DateInterval $interval + * + * @return string + */ + public static function getDateIntervalSpec(DateInterval $interval) + { + $date = array_filter([ + static::PERIOD_YEARS => abs($interval->y), + static::PERIOD_MONTHS => abs($interval->m), + static::PERIOD_DAYS => abs($interval->d), + ]); + + $time = array_filter([ + static::PERIOD_HOURS => abs($interval->h), + static::PERIOD_MINUTES => abs($interval->i), + static::PERIOD_SECONDS => abs($interval->s), + ]); + + $specString = static::PERIOD_PREFIX; + + foreach ($date as $key => $value) { + $specString .= $value.$key; + } + + if (\count($time) > 0) { + $specString .= static::PERIOD_TIME_PREFIX; + foreach ($time as $key => $value) { + $specString .= $value.$key; + } + } + + return $specString === static::PERIOD_PREFIX ? 'PT0S' : $specString; + } + + /** + * Get the interval_spec string. + * + * @return string + */ + public function spec() + { + return static::getDateIntervalSpec($this); + } + + /** + * Comparing 2 date intervals. + * + * @param DateInterval $first + * @param DateInterval $second + * + * @return int + */ + public static function compareDateIntervals(DateInterval $first, DateInterval $second) + { + $current = Carbon::now(); + $passed = $current->avoidMutation()->add($second); + $current->add($first); + + if ($current < $passed) { + return -1; + } + if ($current > $passed) { + return 1; + } + + return 0; + } + + /** + * Comparing with passed interval. + * + * @param DateInterval $interval + * + * @return int + */ + public function compare(DateInterval $interval) + { + return static::compareDateIntervals($this, $interval); + } + + private function invertCascade(array $values) + { + return $this->set(array_map(function ($value) { + return -$value; + }, $values))->doCascade(true)->invert(); + } + + private function doCascade(bool $deep) + { + $originalData = $this->toArray(); + $originalData['milliseconds'] = (int) ($originalData['microseconds'] / static::getMicrosecondsPerMillisecond()); + $originalData['microseconds'] = $originalData['microseconds'] % static::getMicrosecondsPerMillisecond(); + $originalData['daysExcludeWeeks'] = $originalData['days']; + unset($originalData['days']); + $newData = $originalData; + + foreach (static::getFlipCascadeFactors() as $source => [$target, $factor]) { + foreach (['source', 'target'] as $key) { + if ($$key === 'dayz') { + $$key = 'daysExcludeWeeks'; + } + } + + $value = $newData[$source]; + $modulo = ($factor + ($value % $factor)) % $factor; + $newData[$source] = $modulo; + $newData[$target] += ($value - $modulo) / $factor; + } + + $positive = null; + + if (!$deep) { + foreach ($newData as $value) { + if ($value) { + if ($positive === null) { + $positive = ($value > 0); + + continue; + } + + if (($value > 0) !== $positive) { + return $this->invertCascade($originalData) + ->solveNegativeInterval(); + } + } + } + } + + return $this->set($newData) + ->solveNegativeInterval(); + } + + /** + * Convert overflowed values into bigger units. + * + * @return $this + */ + public function cascade() + { + return $this->doCascade(false); + } + + public function hasNegativeValues(): bool + { + foreach ($this->toArray() as $value) { + if ($value < 0) { + return true; + } + } + + return false; + } + + public function hasPositiveValues(): bool + { + foreach ($this->toArray() as $value) { + if ($value > 0) { + return true; + } + } + + return false; + } + + /** + * Get amount of given unit equivalent to the interval. + * + * @param string $unit + * + * @throws UnknownUnitException|UnitNotConfiguredException + * + * @return float + */ + public function total($unit) + { + $realUnit = $unit = strtolower($unit); + + if (\in_array($unit, ['days', 'weeks'])) { + $realUnit = 'dayz'; + } elseif (!\in_array($unit, ['microseconds', 'milliseconds', 'seconds', 'minutes', 'hours', 'dayz', 'months', 'years'])) { + throw new UnknownUnitException($unit); + } + + $result = 0; + $cumulativeFactor = 0; + $unitFound = false; + $factors = static::getFlipCascadeFactors(); + $daysPerWeek = static::getDaysPerWeek(); + + $values = [ + 'years' => $this->years, + 'months' => $this->months, + 'weeks' => (int) ($this->d / $daysPerWeek), + 'dayz' => $this->d % $daysPerWeek, + 'hours' => $this->hours, + 'minutes' => $this->minutes, + 'seconds' => $this->seconds, + 'milliseconds' => (int) ($this->microseconds / Carbon::MICROSECONDS_PER_MILLISECOND), + 'microseconds' => $this->microseconds % Carbon::MICROSECONDS_PER_MILLISECOND, + ]; + + if (isset($factors['dayz']) && $factors['dayz'][0] !== 'weeks') { + $values['dayz'] += $values['weeks'] * $daysPerWeek; + $values['weeks'] = 0; + } + + foreach ($factors as $source => [$target, $factor]) { + if ($source === $realUnit) { + $unitFound = true; + $value = $values[$source]; + $result += $value; + $cumulativeFactor = 1; + } + + if ($factor === false) { + if ($unitFound) { + break; + } + + $result = 0; + $cumulativeFactor = 0; + + continue; + } + + if ($target === $realUnit) { + $unitFound = true; + } + + if ($cumulativeFactor) { + $cumulativeFactor *= $factor; + $result += $values[$target] * $cumulativeFactor; + + continue; + } + + $value = $values[$source]; + + $result = ($result + $value) / $factor; + } + + if (isset($target) && !$cumulativeFactor) { + $result += $values[$target]; + } + + if (!$unitFound) { + throw new UnitNotConfiguredException($unit); + } + + if ($this->invert) { + $result *= -1; + } + + if ($unit === 'weeks') { + return $result / $daysPerWeek; + } + + return $result; + } + + /** + * Determines if the instance is equal to another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @see equalTo() + * + * @return bool + */ + public function eq($interval): bool + { + return $this->equalTo($interval); + } + + /** + * Determines if the instance is equal to another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @return bool + */ + public function equalTo($interval): bool + { + $interval = $this->resolveInterval($interval); + + return $interval !== null && $this->totalMicroseconds === $interval->totalMicroseconds; + } + + /** + * Determines if the instance is not equal to another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @see notEqualTo() + * + * @return bool + */ + public function ne($interval): bool + { + return $this->notEqualTo($interval); + } + + /** + * Determines if the instance is not equal to another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @return bool + */ + public function notEqualTo($interval): bool + { + return !$this->eq($interval); + } + + /** + * Determines if the instance is greater (longer) than another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @see greaterThan() + * + * @return bool + */ + public function gt($interval): bool + { + return $this->greaterThan($interval); + } + + /** + * Determines if the instance is greater (longer) than another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @return bool + */ + public function greaterThan($interval): bool + { + $interval = $this->resolveInterval($interval); + + return $interval === null || $this->totalMicroseconds > $interval->totalMicroseconds; + } + + /** + * Determines if the instance is greater (longer) than or equal to another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @see greaterThanOrEqualTo() + * + * @return bool + */ + public function gte($interval): bool + { + return $this->greaterThanOrEqualTo($interval); + } + + /** + * Determines if the instance is greater (longer) than or equal to another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @return bool + */ + public function greaterThanOrEqualTo($interval): bool + { + return $this->greaterThan($interval) || $this->equalTo($interval); + } + + /** + * Determines if the instance is less (shorter) than another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @see lessThan() + * + * @return bool + */ + public function lt($interval): bool + { + return $this->lessThan($interval); + } + + /** + * Determines if the instance is less (shorter) than another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @return bool + */ + public function lessThan($interval): bool + { + $interval = $this->resolveInterval($interval); + + return $interval !== null && $this->totalMicroseconds < $interval->totalMicroseconds; + } + + /** + * Determines if the instance is less (shorter) than or equal to another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @see lessThanOrEqualTo() + * + * @return bool + */ + public function lte($interval): bool + { + return $this->lessThanOrEqualTo($interval); + } + + /** + * Determines if the instance is less (shorter) than or equal to another + * + * @param CarbonInterval|DateInterval|mixed $interval + * + * @return bool + */ + public function lessThanOrEqualTo($interval): bool + { + return $this->lessThan($interval) || $this->equalTo($interval); + } + + /** + * Determines if the instance is between two others. + * + * The third argument allow you to specify if bounds are included or not (true by default) + * but for when you including/excluding bounds may produce different results in your application, + * we recommend to use the explicit methods ->betweenIncluded() or ->betweenExcluded() instead. + * + * @example + * ``` + * CarbonInterval::hours(48)->between(CarbonInterval::day(), CarbonInterval::days(3)); // true + * CarbonInterval::hours(48)->between(CarbonInterval::day(), CarbonInterval::hours(36)); // false + * CarbonInterval::hours(48)->between(CarbonInterval::day(), CarbonInterval::days(2)); // true + * CarbonInterval::hours(48)->between(CarbonInterval::day(), CarbonInterval::days(2), false); // false + * ``` + * + * @param CarbonInterval|DateInterval|mixed $interval1 + * @param CarbonInterval|DateInterval|mixed $interval2 + * @param bool $equal Indicates if an equal to comparison should be done + * + * @return bool + */ + public function between($interval1, $interval2, $equal = true): bool + { + return $equal + ? $this->greaterThanOrEqualTo($interval1) && $this->lessThanOrEqualTo($interval2) + : $this->greaterThan($interval1) && $this->lessThan($interval2); + } + + /** + * Determines if the instance is between two others, bounds excluded. + * + * @example + * ``` + * CarbonInterval::hours(48)->betweenExcluded(CarbonInterval::day(), CarbonInterval::days(3)); // true + * CarbonInterval::hours(48)->betweenExcluded(CarbonInterval::day(), CarbonInterval::hours(36)); // false + * CarbonInterval::hours(48)->betweenExcluded(CarbonInterval::day(), CarbonInterval::days(2)); // true + * ``` + * + * @param CarbonInterval|DateInterval|mixed $interval1 + * @param CarbonInterval|DateInterval|mixed $interval2 + * + * @return bool + */ + public function betweenIncluded($interval1, $interval2): bool + { + return $this->between($interval1, $interval2, true); + } + + /** + * Determines if the instance is between two others, bounds excluded. + * + * @example + * ``` + * CarbonInterval::hours(48)->betweenExcluded(CarbonInterval::day(), CarbonInterval::days(3)); // true + * CarbonInterval::hours(48)->betweenExcluded(CarbonInterval::day(), CarbonInterval::hours(36)); // false + * CarbonInterval::hours(48)->betweenExcluded(CarbonInterval::day(), CarbonInterval::days(2)); // false + * ``` + * + * @param CarbonInterval|DateInterval|mixed $interval1 + * @param CarbonInterval|DateInterval|mixed $interval2 + * + * @return bool + */ + public function betweenExcluded($interval1, $interval2): bool + { + return $this->between($interval1, $interval2, false); + } + + /** + * Determines if the instance is between two others + * + * @example + * ``` + * CarbonInterval::hours(48)->isBetween(CarbonInterval::day(), CarbonInterval::days(3)); // true + * CarbonInterval::hours(48)->isBetween(CarbonInterval::day(), CarbonInterval::hours(36)); // false + * CarbonInterval::hours(48)->isBetween(CarbonInterval::day(), CarbonInterval::days(2)); // true + * CarbonInterval::hours(48)->isBetween(CarbonInterval::day(), CarbonInterval::days(2), false); // false + * ``` + * + * @param CarbonInterval|DateInterval|mixed $interval1 + * @param CarbonInterval|DateInterval|mixed $interval2 + * @param bool $equal Indicates if an equal to comparison should be done + * + * @return bool + */ + public function isBetween($interval1, $interval2, $equal = true): bool + { + return $this->between($interval1, $interval2, $equal); + } + + /** + * Round the current instance at the given unit with given precision if specified and the given function. + * + * @param string $unit + * @param float|int|string|DateInterval|null $precision + * @param string $function + * + * @throws Exception + * + * @return $this + */ + public function roundUnit($unit, $precision = 1, $function = 'round') + { + $base = CarbonImmutable::parse('2000-01-01 00:00:00', 'UTC') + ->roundUnit($unit, $precision, $function); + $next = $base->add($this); + $inverted = $next < $base; + + if ($inverted) { + $next = $base->sub($this); + } + + $this->copyProperties( + $next + ->roundUnit($unit, $precision, $function) + ->diffAsCarbonInterval($base) + ); + + return $this->invert($inverted); + } + + /** + * Truncate the current instance at the given unit with given precision if specified. + * + * @param string $unit + * @param float|int|string|DateInterval|null $precision + * + * @throws Exception + * + * @return $this + */ + public function floorUnit($unit, $precision = 1) + { + return $this->roundUnit($unit, $precision, 'floor'); + } + + /** + * Ceil the current instance at the given unit with given precision if specified. + * + * @param string $unit + * @param float|int|string|DateInterval|null $precision + * + * @throws Exception + * + * @return $this + */ + public function ceilUnit($unit, $precision = 1) + { + return $this->roundUnit($unit, $precision, 'ceil'); + } + + /** + * Round the current instance second with given precision if specified. + * + * @param float|int|string|DateInterval|null $precision + * @param string $function + * + * @throws Exception + * + * @return $this + */ + public function round($precision = 1, $function = 'round') + { + return $this->roundWith($precision, $function); + } + + /** + * Round the current instance second with given precision if specified. + * + * @param float|int|string|DateInterval|null $precision + * + * @throws Exception + * + * @return $this + */ + public function floor($precision = 1) + { + return $this->round($precision, 'floor'); + } + + /** + * Ceil the current instance second with given precision if specified. + * + * @param float|int|string|DateInterval|null $precision + * + * @throws Exception + * + * @return $this + */ + public function ceil($precision = 1) + { + return $this->round($precision, 'ceil'); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/CarbonPeriod.php b/vendor/nesbot/carbon/src/Carbon/CarbonPeriod.php new file mode 100644 index 0000000..197f370 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/CarbonPeriod.php @@ -0,0 +1,2557 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use Carbon\Exceptions\InvalidCastException; +use Carbon\Exceptions\InvalidIntervalException; +use Carbon\Exceptions\InvalidPeriodDateException; +use Carbon\Exceptions\InvalidPeriodParameterException; +use Carbon\Exceptions\NotACarbonClassException; +use Carbon\Exceptions\NotAPeriodException; +use Carbon\Exceptions\UnknownGetterException; +use Carbon\Exceptions\UnknownMethodException; +use Carbon\Exceptions\UnreachableException; +use Carbon\Traits\IntervalRounding; +use Carbon\Traits\Mixin; +use Carbon\Traits\Options; +use Closure; +use Countable; +use DateInterval; +use DatePeriod; +use DateTime; +use DateTimeInterface; +use DateTimeZone; +use InvalidArgumentException; +use Iterator; +use JsonSerializable; +use ReflectionException; +use ReturnTypeWillChange; +use RuntimeException; + +/** + * Substitution of DatePeriod with some modifications and many more features. + * + * @property-read int|float $recurrences number of recurrences (if end not set). + * @property-read bool $include_start_date rather the start date is included in the iteration. + * @property-read bool $include_end_date rather the end date is included in the iteration (if recurrences not set). + * @property-read CarbonInterface $start Period start date. + * @property-read CarbonInterface $current Current date from the iteration. + * @property-read CarbonInterface $end Period end date. + * @property-read CarbonInterval $interval Underlying date interval instance. Always present, one day by default. + * + * @method static CarbonPeriod start($date, $inclusive = null) Create instance specifying start date or modify the start date if called on an instance. + * @method static CarbonPeriod since($date, $inclusive = null) Alias for start(). + * @method static CarbonPeriod sinceNow($inclusive = null) Create instance with start date set to now or set the start date to now if called on an instance. + * @method static CarbonPeriod end($date = null, $inclusive = null) Create instance specifying end date or modify the end date if called on an instance. + * @method static CarbonPeriod until($date = null, $inclusive = null) Alias for end(). + * @method static CarbonPeriod untilNow($inclusive = null) Create instance with end date set to now or set the end date to now if called on an instance. + * @method static CarbonPeriod dates($start, $end = null) Create instance with start and end dates or modify the start and end dates if called on an instance. + * @method static CarbonPeriod between($start, $end = null) Create instance with start and end dates or modify the start and end dates if called on an instance. + * @method static CarbonPeriod recurrences($recurrences = null) Create instance with maximum number of recurrences or modify the number of recurrences if called on an instance. + * @method static CarbonPeriod times($recurrences = null) Alias for recurrences(). + * @method static CarbonPeriod options($options = null) Create instance with options or modify the options if called on an instance. + * @method static CarbonPeriod toggle($options, $state = null) Create instance with options toggled on or off, or toggle options if called on an instance. + * @method static CarbonPeriod filter($callback, $name = null) Create instance with filter added to the stack or append a filter if called on an instance. + * @method static CarbonPeriod push($callback, $name = null) Alias for filter(). + * @method static CarbonPeriod prepend($callback, $name = null) Create instance with filter prepended to the stack or prepend a filter if called on an instance. + * @method static CarbonPeriod filters(array $filters = []) Create instance with filters stack or replace the whole filters stack if called on an instance. + * @method static CarbonPeriod interval($interval) Create instance with given date interval or modify the interval if called on an instance. + * @method static CarbonPeriod each($interval) Create instance with given date interval or modify the interval if called on an instance. + * @method static CarbonPeriod every($interval) Create instance with given date interval or modify the interval if called on an instance. + * @method static CarbonPeriod step($interval) Create instance with given date interval or modify the interval if called on an instance. + * @method static CarbonPeriod stepBy($interval) Create instance with given date interval or modify the interval if called on an instance. + * @method static CarbonPeriod invert() Create instance with inverted date interval or invert the interval if called on an instance. + * @method static CarbonPeriod years($years = 1) Create instance specifying a number of years for date interval or replace the interval by the given a number of years if called on an instance. + * @method static CarbonPeriod year($years = 1) Alias for years(). + * @method static CarbonPeriod months($months = 1) Create instance specifying a number of months for date interval or replace the interval by the given a number of months if called on an instance. + * @method static CarbonPeriod month($months = 1) Alias for months(). + * @method static CarbonPeriod weeks($weeks = 1) Create instance specifying a number of weeks for date interval or replace the interval by the given a number of weeks if called on an instance. + * @method static CarbonPeriod week($weeks = 1) Alias for weeks(). + * @method static CarbonPeriod days($days = 1) Create instance specifying a number of days for date interval or replace the interval by the given a number of days if called on an instance. + * @method static CarbonPeriod dayz($days = 1) Alias for days(). + * @method static CarbonPeriod day($days = 1) Alias for days(). + * @method static CarbonPeriod hours($hours = 1) Create instance specifying a number of hours for date interval or replace the interval by the given a number of hours if called on an instance. + * @method static CarbonPeriod hour($hours = 1) Alias for hours(). + * @method static CarbonPeriod minutes($minutes = 1) Create instance specifying a number of minutes for date interval or replace the interval by the given a number of minutes if called on an instance. + * @method static CarbonPeriod minute($minutes = 1) Alias for minutes(). + * @method static CarbonPeriod seconds($seconds = 1) Create instance specifying a number of seconds for date interval or replace the interval by the given a number of seconds if called on an instance. + * @method static CarbonPeriod second($seconds = 1) Alias for seconds(). + * @method $this roundYear(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method $this roundYears(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method $this floorYear(float $precision = 1) Truncate the current instance year with given precision. + * @method $this floorYears(float $precision = 1) Truncate the current instance year with given precision. + * @method $this ceilYear(float $precision = 1) Ceil the current instance year with given precision. + * @method $this ceilYears(float $precision = 1) Ceil the current instance year with given precision. + * @method $this roundMonth(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method $this roundMonths(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method $this floorMonth(float $precision = 1) Truncate the current instance month with given precision. + * @method $this floorMonths(float $precision = 1) Truncate the current instance month with given precision. + * @method $this ceilMonth(float $precision = 1) Ceil the current instance month with given precision. + * @method $this ceilMonths(float $precision = 1) Ceil the current instance month with given precision. + * @method $this roundWeek(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method $this roundWeeks(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method $this floorWeek(float $precision = 1) Truncate the current instance day with given precision. + * @method $this floorWeeks(float $precision = 1) Truncate the current instance day with given precision. + * @method $this ceilWeek(float $precision = 1) Ceil the current instance day with given precision. + * @method $this ceilWeeks(float $precision = 1) Ceil the current instance day with given precision. + * @method $this roundDay(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method $this roundDays(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method $this floorDay(float $precision = 1) Truncate the current instance day with given precision. + * @method $this floorDays(float $precision = 1) Truncate the current instance day with given precision. + * @method $this ceilDay(float $precision = 1) Ceil the current instance day with given precision. + * @method $this ceilDays(float $precision = 1) Ceil the current instance day with given precision. + * @method $this roundHour(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method $this roundHours(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method $this floorHour(float $precision = 1) Truncate the current instance hour with given precision. + * @method $this floorHours(float $precision = 1) Truncate the current instance hour with given precision. + * @method $this ceilHour(float $precision = 1) Ceil the current instance hour with given precision. + * @method $this ceilHours(float $precision = 1) Ceil the current instance hour with given precision. + * @method $this roundMinute(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method $this roundMinutes(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method $this floorMinute(float $precision = 1) Truncate the current instance minute with given precision. + * @method $this floorMinutes(float $precision = 1) Truncate the current instance minute with given precision. + * @method $this ceilMinute(float $precision = 1) Ceil the current instance minute with given precision. + * @method $this ceilMinutes(float $precision = 1) Ceil the current instance minute with given precision. + * @method $this roundSecond(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method $this roundSeconds(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method $this floorSecond(float $precision = 1) Truncate the current instance second with given precision. + * @method $this floorSeconds(float $precision = 1) Truncate the current instance second with given precision. + * @method $this ceilSecond(float $precision = 1) Ceil the current instance second with given precision. + * @method $this ceilSeconds(float $precision = 1) Ceil the current instance second with given precision. + * @method $this roundMillennium(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method $this roundMillennia(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method $this floorMillennium(float $precision = 1) Truncate the current instance millennium with given precision. + * @method $this floorMillennia(float $precision = 1) Truncate the current instance millennium with given precision. + * @method $this ceilMillennium(float $precision = 1) Ceil the current instance millennium with given precision. + * @method $this ceilMillennia(float $precision = 1) Ceil the current instance millennium with given precision. + * @method $this roundCentury(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method $this roundCenturies(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method $this floorCentury(float $precision = 1) Truncate the current instance century with given precision. + * @method $this floorCenturies(float $precision = 1) Truncate the current instance century with given precision. + * @method $this ceilCentury(float $precision = 1) Ceil the current instance century with given precision. + * @method $this ceilCenturies(float $precision = 1) Ceil the current instance century with given precision. + * @method $this roundDecade(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method $this roundDecades(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method $this floorDecade(float $precision = 1) Truncate the current instance decade with given precision. + * @method $this floorDecades(float $precision = 1) Truncate the current instance decade with given precision. + * @method $this ceilDecade(float $precision = 1) Ceil the current instance decade with given precision. + * @method $this ceilDecades(float $precision = 1) Ceil the current instance decade with given precision. + * @method $this roundQuarter(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method $this roundQuarters(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method $this floorQuarter(float $precision = 1) Truncate the current instance quarter with given precision. + * @method $this floorQuarters(float $precision = 1) Truncate the current instance quarter with given precision. + * @method $this ceilQuarter(float $precision = 1) Ceil the current instance quarter with given precision. + * @method $this ceilQuarters(float $precision = 1) Ceil the current instance quarter with given precision. + * @method $this roundMillisecond(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method $this roundMilliseconds(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method $this floorMillisecond(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method $this floorMilliseconds(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method $this ceilMillisecond(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method $this ceilMilliseconds(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method $this roundMicrosecond(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method $this roundMicroseconds(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method $this floorMicrosecond(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method $this floorMicroseconds(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method $this ceilMicrosecond(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method $this ceilMicroseconds(float $precision = 1) Ceil the current instance microsecond with given precision. + */ +class CarbonPeriod implements Iterator, Countable, JsonSerializable +{ + use IntervalRounding; + use Mixin { + Mixin::mixin as baseMixin; + } + use Options; + + /** + * Built-in filters. + * + * @var string + */ + public const RECURRENCES_FILTER = [self::class, 'filterRecurrences']; + public const END_DATE_FILTER = [self::class, 'filterEndDate']; + + /** + * Special value which can be returned by filters to end iteration. Also a filter. + * + * @var string + */ + public const END_ITERATION = [self::class, 'endIteration']; + + /** + * Exclude start date from iteration. + * + * @var int + */ + public const EXCLUDE_START_DATE = 1; + + /** + * Exclude end date from iteration. + * + * @var int + */ + public const EXCLUDE_END_DATE = 2; + + /** + * Yield CarbonImmutable instances. + * + * @var int + */ + public const IMMUTABLE = 4; + + /** + * Number of maximum attempts before giving up on finding next valid date. + * + * @var int + */ + public const NEXT_MAX_ATTEMPTS = 1000; + + /** + * Number of maximum attempts before giving up on finding end date. + * + * @var int + */ + public const END_MAX_ATTEMPTS = 10000; + + /** + * The registered macros. + * + * @var array + */ + protected static $macros = []; + + /** + * Date class of iteration items. + * + * @var string + */ + protected $dateClass = Carbon::class; + + /** + * Underlying date interval instance. Always present, one day by default. + * + * @var CarbonInterval + */ + protected $dateInterval; + + /** + * Whether current date interval was set by default. + * + * @var bool + */ + protected $isDefaultInterval; + + /** + * The filters stack. + * + * @var array + */ + protected $filters = []; + + /** + * Period start date. Applied on rewind. Always present, now by default. + * + * @var CarbonInterface + */ + protected $startDate; + + /** + * Period end date. For inverted interval should be before the start date. Applied via a filter. + * + * @var CarbonInterface|null + */ + protected $endDate; + + /** + * Limit for number of recurrences. Applied via a filter. + * + * @var int|null + */ + protected $recurrences; + + /** + * Iteration options. + * + * @var int + */ + protected $options; + + /** + * Index of current date. Always sequential, even if some dates are skipped by filters. + * Equal to null only before the first iteration. + * + * @var int + */ + protected $key; + + /** + * Current date. May temporarily hold unaccepted value when looking for a next valid date. + * Equal to null only before the first iteration. + * + * @var CarbonInterface + */ + protected $current; + + /** + * Timezone of current date. Taken from the start date. + * + * @var \DateTimeZone|null + */ + protected $timezone; + + /** + * The cached validation result for current date. + * + * @var bool|string|null + */ + protected $validationResult; + + /** + * Timezone handler for settings() method. + * + * @var mixed + */ + protected $tzName; + + /** + * Make a CarbonPeriod instance from given variable if possible. + * + * @param mixed $var + * + * @return static|null + */ + public static function make($var) + { + try { + return static::instance($var); + } catch (NotAPeriodException $e) { + return static::create($var); + } + } + + /** + * Create a new instance from a DatePeriod or CarbonPeriod object. + * + * @param CarbonPeriod|DatePeriod $period + * + * @return static + */ + public static function instance($period) + { + if ($period instanceof static) { + return $period->copy(); + } + + if ($period instanceof self) { + return new static( + $period->getStartDate(), + $period->getEndDate() ?: $period->getRecurrences(), + $period->getDateInterval(), + $period->getOptions() + ); + } + + if ($period instanceof DatePeriod) { + return new static( + $period->start, + $period->end ?: ($period->recurrences - 1), + $period->interval, + $period->include_start_date ? 0 : static::EXCLUDE_START_DATE + ); + } + + $class = \get_called_class(); + $type = \gettype($period); + + throw new NotAPeriodException( + 'Argument 1 passed to '.$class.'::'.__METHOD__.'() '. + 'must be an instance of DatePeriod or '.$class.', '. + ($type === 'object' ? 'instance of '.\get_class($period) : $type).' given.' + ); + } + + /** + * Create a new instance. + * + * @return static + */ + public static function create(...$params) + { + return static::createFromArray($params); + } + + /** + * Create a new instance from an array of parameters. + * + * @param array $params + * + * @return static + */ + public static function createFromArray(array $params) + { + return new static(...$params); + } + + /** + * Create CarbonPeriod from ISO 8601 string. + * + * @param string $iso + * @param int|null $options + * + * @return static + */ + public static function createFromIso($iso, $options = null) + { + $params = static::parseIso8601($iso); + + $instance = static::createFromArray($params); + + if ($options !== null) { + $instance->setOptions($options); + } + + return $instance; + } + + /** + * Return whether given interval contains non zero value of any time unit. + * + * @param \DateInterval $interval + * + * @return bool + */ + protected static function intervalHasTime(DateInterval $interval) + { + return $interval->h || $interval->i || $interval->s || $interval->f; + } + + /** + * Return whether given variable is an ISO 8601 specification. + * + * Note: Check is very basic, as actual validation will be done later when parsing. + * We just want to ensure that variable is not any other type of a valid parameter. + * + * @param mixed $var + * + * @return bool + */ + protected static function isIso8601($var) + { + if (!\is_string($var)) { + return false; + } + + // Match slash but not within a timezone name. + $part = '[a-z]+(?:[_-][a-z]+)*'; + + preg_match("#\b$part/$part\b|(/)#i", $var, $match); + + return isset($match[1]); + } + + /** + * Parse given ISO 8601 string into an array of arguments. + * + * @SuppressWarnings(PHPMD.ElseExpression) + * + * @param string $iso + * + * @return array + */ + protected static function parseIso8601($iso) + { + $result = []; + + $interval = null; + $start = null; + $end = null; + + foreach (explode('/', $iso) as $key => $part) { + if ($key === 0 && preg_match('/^R([0-9]*)$/', $part, $match)) { + $parsed = \strlen($match[1]) ? (int) $match[1] : null; + } elseif ($interval === null && $parsed = CarbonInterval::make($part)) { + $interval = $part; + } elseif ($start === null && $parsed = Carbon::make($part)) { + $start = $part; + } elseif ($end === null && $parsed = Carbon::make(static::addMissingParts($start ?? '', $part))) { + $end = $part; + } else { + throw new InvalidPeriodParameterException("Invalid ISO 8601 specification: $iso."); + } + + $result[] = $parsed; + } + + return $result; + } + + /** + * Add missing parts of the target date from the soure date. + * + * @param string $source + * @param string $target + * + * @return string + */ + protected static function addMissingParts($source, $target) + { + $pattern = '/'.preg_replace('/[0-9]+/', '[0-9]+', preg_quote($target, '/')).'$/'; + + $result = preg_replace($pattern, $target, $source, 1, $count); + + return $count ? $result : $target; + } + + /** + * Register a custom macro. + * + * @example + * ``` + * CarbonPeriod::macro('middle', function () { + * return $this->getStartDate()->average($this->getEndDate()); + * }); + * echo CarbonPeriod::since('2011-05-12')->until('2011-06-03')->middle(); + * ``` + * + * @param string $name + * @param object|callable $macro + * + * @return void + */ + public static function macro($name, $macro) + { + static::$macros[$name] = $macro; + } + + /** + * Register macros from a mixin object. + * + * @example + * ``` + * CarbonPeriod::mixin(new class { + * public function addDays() { + * return function ($count = 1) { + * return $this->setStartDate( + * $this->getStartDate()->addDays($count) + * )->setEndDate( + * $this->getEndDate()->addDays($count) + * ); + * }; + * } + * public function subDays() { + * return function ($count = 1) { + * return $this->setStartDate( + * $this->getStartDate()->subDays($count) + * )->setEndDate( + * $this->getEndDate()->subDays($count) + * ); + * }; + * } + * }); + * echo CarbonPeriod::create('2000-01-01', '2000-02-01')->addDays(5)->subDays(3); + * ``` + * + * @param object|string $mixin + * + * @throws ReflectionException + * + * @return void + */ + public static function mixin($mixin) + { + static::baseMixin($mixin); + } + + /** + * Check if macro is registered. + * + * @param string $name + * + * @return bool + */ + public static function hasMacro($name) + { + return isset(static::$macros[$name]); + } + + /** + * Provide static proxy for instance aliases. + * + * @param string $method + * @param array $parameters + * + * @return mixed + */ + public static function __callStatic($method, $parameters) + { + $date = new static(); + + if (static::hasMacro($method)) { + return static::bindMacroContext(null, function () use (&$method, &$parameters, &$date) { + return $date->callMacro($method, $parameters); + }); + } + + return $date->$method(...$parameters); + } + + /** + * CarbonPeriod constructor. + * + * @SuppressWarnings(PHPMD.ElseExpression) + * + * @throws InvalidArgumentException + */ + public function __construct(...$arguments) + { + // Parse and assign arguments one by one. First argument may be an ISO 8601 spec, + // which will be first parsed into parts and then processed the same way. + + $argumentsCount = \count($arguments); + + if ($argumentsCount && static::isIso8601($iso = $arguments[0])) { + array_splice($arguments, 0, 1, static::parseIso8601($iso)); + } + + if ($argumentsCount === 1) { + if ($arguments[0] instanceof DatePeriod) { + $arguments = [ + $arguments[0]->start, + $arguments[0]->end ?: ($arguments[0]->recurrences - 1), + $arguments[0]->interval, + $arguments[0]->include_start_date ? 0 : static::EXCLUDE_START_DATE, + ]; + } elseif ($arguments[0] instanceof self) { + $arguments = [ + $arguments[0]->getStartDate(), + $arguments[0]->getEndDate() ?: $arguments[0]->getRecurrences(), + $arguments[0]->getDateInterval(), + $arguments[0]->getOptions(), + ]; + } + } + + foreach ($arguments as $argument) { + $parsedDate = null; + + if ($argument instanceof DateTimeZone) { + $this->setTimezone($argument); + } elseif ($this->dateInterval === null && + ( + \is_string($argument) && preg_match( + '/^(-?\d(\d(?![\/-])|[^\d\/-]([\/-])?)*|P[T0-9].*|(?:\h*\d+(?:\.\d+)?\h*[a-z]+)+)$/i', + $argument + ) || + $argument instanceof DateInterval || + $argument instanceof Closure + ) && + $parsedInterval = @CarbonInterval::make($argument) + ) { + $this->setDateInterval($parsedInterval); + } elseif ($this->startDate === null && $parsedDate = $this->makeDateTime($argument)) { + $this->setStartDate($parsedDate); + } elseif ($this->endDate === null && ($parsedDate = $parsedDate ?? $this->makeDateTime($argument))) { + $this->setEndDate($parsedDate); + } elseif ($this->recurrences === null && $this->endDate === null && is_numeric($argument)) { + $this->setRecurrences($argument); + } elseif ($this->options === null && (\is_int($argument) || $argument === null)) { + $this->setOptions($argument); + } else { + throw new InvalidPeriodParameterException('Invalid constructor parameters.'); + } + } + + if ($this->startDate === null) { + $this->setStartDate(Carbon::now()); + } + + if ($this->dateInterval === null) { + $this->setDateInterval(CarbonInterval::day()); + + $this->isDefaultInterval = true; + } + + if ($this->options === null) { + $this->setOptions(0); + } + } + + /** + * Get a copy of the instance. + * + * @return static + */ + public function copy() + { + return clone $this; + } + + /** + * Get the getter for a property allowing both `DatePeriod` snakeCase and camelCase names. + * + * @param string $name + * + * @return callable|null + */ + protected function getGetter(string $name) + { + switch (strtolower(preg_replace('/[A-Z]/', '_$0', $name))) { + case 'start': + case 'start_date': + return [$this, 'getStartDate']; + case 'end': + case 'end_date': + return [$this, 'getEndDate']; + case 'interval': + case 'date_interval': + return [$this, 'getDateInterval']; + case 'recurrences': + return [$this, 'getRecurrences']; + case 'include_start_date': + return [$this, 'isStartIncluded']; + case 'include_end_date': + return [$this, 'isEndIncluded']; + case 'current': + return [$this, 'current']; + default: + return null; + } + } + + /** + * Get a property allowing both `DatePeriod` snakeCase and camelCase names. + * + * @param string $name + * + * @return bool|CarbonInterface|CarbonInterval|int|null + */ + public function get(string $name) + { + $getter = $this->getGetter($name); + + if ($getter) { + return $getter(); + } + + throw new UnknownGetterException($name); + } + + /** + * Get a property allowing both `DatePeriod` snakeCase and camelCase names. + * + * @param string $name + * + * @return bool|CarbonInterface|CarbonInterval|int|null + */ + public function __get(string $name) + { + return $this->get($name); + } + + /** + * Check if an attribute exists on the object + * + * @param string $name + * + * @return bool + */ + public function __isset(string $name): bool + { + return $this->getGetter($name) !== null; + } + + /** + * @alias copy + * + * Get a copy of the instance. + * + * @return static + */ + public function clone() + { + return clone $this; + } + + /** + * Set the iteration item class. + * + * @param string $dateClass + * + * @return $this + */ + public function setDateClass(string $dateClass) + { + if (!is_a($dateClass, CarbonInterface::class, true)) { + throw new NotACarbonClassException($dateClass); + } + + $this->dateClass = $dateClass; + + if (is_a($dateClass, Carbon::class, true)) { + $this->toggleOptions(static::IMMUTABLE, false); + } elseif (is_a($dateClass, CarbonImmutable::class, true)) { + $this->toggleOptions(static::IMMUTABLE, true); + } + + return $this; + } + + /** + * Returns iteration item date class. + * + * @return string + */ + public function getDateClass(): string + { + return $this->dateClass; + } + + /** + * Change the period date interval. + * + * @param DateInterval|string $interval + * + * @throws InvalidIntervalException + * + * @return $this + */ + public function setDateInterval($interval) + { + if (!$interval = CarbonInterval::make($interval)) { + throw new InvalidIntervalException('Invalid interval.'); + } + + if ($interval->spec() === 'PT0S' && !$interval->f && !$interval->getStep()) { + throw new InvalidIntervalException('Empty interval is not accepted.'); + } + + $this->dateInterval = $interval; + + $this->isDefaultInterval = false; + + $this->handleChangedParameters(); + + return $this; + } + + /** + * Invert the period date interval. + * + * @return $this + */ + public function invertDateInterval() + { + $interval = $this->dateInterval->invert(); + + return $this->setDateInterval($interval); + } + + /** + * Set start and end date. + * + * @param DateTime|DateTimeInterface|string $start + * @param DateTime|DateTimeInterface|string|null $end + * + * @return $this + */ + public function setDates($start, $end) + { + $this->setStartDate($start); + $this->setEndDate($end); + + return $this; + } + + /** + * Change the period options. + * + * @param int|null $options + * + * @throws InvalidArgumentException + * + * @return $this + */ + public function setOptions($options) + { + if (!\is_int($options) && $options !== null) { + throw new InvalidPeriodParameterException('Invalid options.'); + } + + $this->options = $options ?: 0; + + $this->handleChangedParameters(); + + return $this; + } + + /** + * Get the period options. + * + * @return int + */ + public function getOptions() + { + return $this->options; + } + + /** + * Toggle given options on or off. + * + * @param int $options + * @param bool|null $state + * + * @throws \InvalidArgumentException + * + * @return $this + */ + public function toggleOptions($options, $state = null) + { + if ($state === null) { + $state = ($this->options & $options) !== $options; + } + + return $this->setOptions( + $state ? + $this->options | $options : + $this->options & ~$options + ); + } + + /** + * Toggle EXCLUDE_START_DATE option. + * + * @param bool $state + * + * @return $this + */ + public function excludeStartDate($state = true) + { + return $this->toggleOptions(static::EXCLUDE_START_DATE, $state); + } + + /** + * Toggle EXCLUDE_END_DATE option. + * + * @param bool $state + * + * @return $this + */ + public function excludeEndDate($state = true) + { + return $this->toggleOptions(static::EXCLUDE_END_DATE, $state); + } + + /** + * Get the underlying date interval. + * + * @return CarbonInterval + */ + public function getDateInterval() + { + return $this->dateInterval->copy(); + } + + /** + * Get start date of the period. + * + * @param string|null $rounding Optional rounding 'floor', 'ceil', 'round' using the period interval. + * + * @return CarbonInterface + */ + public function getStartDate(string $rounding = null) + { + $date = $this->startDate->avoidMutation(); + + return $rounding ? $date->round($this->getDateInterval(), $rounding) : $date; + } + + /** + * Get end date of the period. + * + * @param string|null $rounding Optional rounding 'floor', 'ceil', 'round' using the period interval. + * + * @return CarbonInterface|null + */ + public function getEndDate(string $rounding = null) + { + if (!$this->endDate) { + return null; + } + + $date = $this->endDate->avoidMutation(); + + return $rounding ? $date->round($this->getDateInterval(), $rounding) : $date; + } + + /** + * Get number of recurrences. + * + * @return int|float|null + */ + public function getRecurrences() + { + return $this->recurrences; + } + + /** + * Returns true if the start date should be excluded. + * + * @return bool + */ + public function isStartExcluded() + { + return ($this->options & static::EXCLUDE_START_DATE) !== 0; + } + + /** + * Returns true if the end date should be excluded. + * + * @return bool + */ + public function isEndExcluded() + { + return ($this->options & static::EXCLUDE_END_DATE) !== 0; + } + + /** + * Returns true if the start date should be included. + * + * @return bool + */ + public function isStartIncluded() + { + return !$this->isStartExcluded(); + } + + /** + * Returns true if the end date should be included. + * + * @return bool + */ + public function isEndIncluded() + { + return !$this->isEndExcluded(); + } + + /** + * Return the start if it's included by option, else return the start + 1 period interval. + * + * @return CarbonInterface + */ + public function getIncludedStartDate() + { + $start = $this->getStartDate(); + + if ($this->isStartExcluded()) { + return $start->add($this->getDateInterval()); + } + + return $start; + } + + /** + * Return the end if it's included by option, else return the end - 1 period interval. + * Warning: if the period has no fixed end, this method will iterate the period to calculate it. + * + * @return CarbonInterface + */ + public function getIncludedEndDate() + { + $end = $this->getEndDate(); + + if (!$end) { + return $this->calculateEnd(); + } + + if ($this->isEndExcluded()) { + return $end->sub($this->getDateInterval()); + } + + return $end; + } + + /** + * Add a filter to the stack. + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @param callable $callback + * @param string $name + * + * @return $this + */ + public function addFilter($callback, $name = null) + { + $tuple = $this->createFilterTuple(\func_get_args()); + + $this->filters[] = $tuple; + + $this->handleChangedParameters(); + + return $this; + } + + /** + * Prepend a filter to the stack. + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @param callable $callback + * @param string $name + * + * @return $this + */ + public function prependFilter($callback, $name = null) + { + $tuple = $this->createFilterTuple(\func_get_args()); + + array_unshift($this->filters, $tuple); + + $this->handleChangedParameters(); + + return $this; + } + + /** + * Remove a filter by instance or name. + * + * @param callable|string $filter + * + * @return $this + */ + public function removeFilter($filter) + { + $key = \is_callable($filter) ? 0 : 1; + + $this->filters = array_values(array_filter( + $this->filters, + function ($tuple) use ($key, $filter) { + return $tuple[$key] !== $filter; + } + )); + + $this->updateInternalState(); + + $this->handleChangedParameters(); + + return $this; + } + + /** + * Return whether given instance or name is in the filter stack. + * + * @param callable|string $filter + * + * @return bool + */ + public function hasFilter($filter) + { + $key = \is_callable($filter) ? 0 : 1; + + foreach ($this->filters as $tuple) { + if ($tuple[$key] === $filter) { + return true; + } + } + + return false; + } + + /** + * Get filters stack. + * + * @return array + */ + public function getFilters() + { + return $this->filters; + } + + /** + * Set filters stack. + * + * @param array $filters + * + * @return $this + */ + public function setFilters(array $filters) + { + $this->filters = $filters; + + $this->updateInternalState(); + + $this->handleChangedParameters(); + + return $this; + } + + /** + * Reset filters stack. + * + * @return $this + */ + public function resetFilters() + { + $this->filters = []; + + if ($this->endDate !== null) { + $this->filters[] = [static::END_DATE_FILTER, null]; + } + + if ($this->recurrences !== null) { + $this->filters[] = [static::RECURRENCES_FILTER, null]; + } + + $this->handleChangedParameters(); + + return $this; + } + + /** + * Add a recurrences filter (set maximum number of recurrences). + * + * @param int|float|null $recurrences + * + * @throws InvalidArgumentException + * + * @return $this + */ + public function setRecurrences($recurrences) + { + if (!is_numeric($recurrences) && $recurrences !== null || $recurrences < 0) { + throw new InvalidPeriodParameterException('Invalid number of recurrences.'); + } + + if ($recurrences === null) { + return $this->removeFilter(static::RECURRENCES_FILTER); + } + + $this->recurrences = $recurrences === INF ? INF : (int) $recurrences; + + if (!$this->hasFilter(static::RECURRENCES_FILTER)) { + return $this->addFilter(static::RECURRENCES_FILTER); + } + + $this->handleChangedParameters(); + + return $this; + } + + /** + * Change the period start date. + * + * @param DateTime|DateTimeInterface|string $date + * @param bool|null $inclusive + * + * @throws InvalidPeriodDateException + * + * @return $this + */ + public function setStartDate($date, $inclusive = null) + { + if (!$date = ([$this->dateClass, 'make'])($date)) { + throw new InvalidPeriodDateException('Invalid start date.'); + } + + $this->startDate = $date; + + if ($inclusive !== null) { + $this->toggleOptions(static::EXCLUDE_START_DATE, !$inclusive); + } + + return $this; + } + + /** + * Change the period end date. + * + * @param DateTime|DateTimeInterface|string|null $date + * @param bool|null $inclusive + * + * @throws \InvalidArgumentException + * + * @return $this + */ + public function setEndDate($date, $inclusive = null) + { + if ($date !== null && !$date = ([$this->dateClass, 'make'])($date)) { + throw new InvalidPeriodDateException('Invalid end date.'); + } + + if (!$date) { + return $this->removeFilter(static::END_DATE_FILTER); + } + + $this->endDate = $date; + + if ($inclusive !== null) { + $this->toggleOptions(static::EXCLUDE_END_DATE, !$inclusive); + } + + if (!$this->hasFilter(static::END_DATE_FILTER)) { + return $this->addFilter(static::END_DATE_FILTER); + } + + $this->handleChangedParameters(); + + return $this; + } + + /** + * Check if the current position is valid. + * + * @return bool + */ + #[ReturnTypeWillChange] + public function valid() + { + return $this->validateCurrentDate() === true; + } + + /** + * Return the current key. + * + * @return int|null + */ + #[ReturnTypeWillChange] + public function key() + { + return $this->valid() + ? $this->key + : null; + } + + /** + * Return the current date. + * + * @return CarbonInterface|null + */ + #[ReturnTypeWillChange] + public function current() + { + return $this->valid() + ? $this->prepareForReturn($this->current) + : null; + } + + /** + * Move forward to the next date. + * + * @throws RuntimeException + * + * @return void + */ + #[ReturnTypeWillChange] + public function next() + { + if ($this->current === null) { + $this->rewind(); + } + + if ($this->validationResult !== static::END_ITERATION) { + $this->key++; + + $this->incrementCurrentDateUntilValid(); + } + } + + /** + * Rewind to the start date. + * + * Iterating over a date in the UTC timezone avoids bug during backward DST change. + * + * @see https://bugs.php.net/bug.php?id=72255 + * @see https://bugs.php.net/bug.php?id=74274 + * @see https://wiki.php.net/rfc/datetime_and_daylight_saving_time + * + * @throws RuntimeException + * + * @return void + */ + #[ReturnTypeWillChange] + public function rewind() + { + $this->key = 0; + $this->current = ([$this->dateClass, 'make'])($this->startDate); + $settings = $this->getSettings(); + + if ($this->hasLocalTranslator()) { + $settings['locale'] = $this->getTranslatorLocale(); + } + + $this->current->settings($settings); + $this->timezone = static::intervalHasTime($this->dateInterval) ? $this->current->getTimezone() : null; + + if ($this->timezone) { + $this->current = $this->current->utc(); + } + + $this->validationResult = null; + + if ($this->isStartExcluded() || $this->validateCurrentDate() === false) { + $this->incrementCurrentDateUntilValid(); + } + } + + /** + * Skip iterations and returns iteration state (false if ended, true if still valid). + * + * @param int $count steps number to skip (1 by default) + * + * @return bool + */ + public function skip($count = 1) + { + for ($i = $count; $this->valid() && $i > 0; $i--) { + $this->next(); + } + + return $this->valid(); + } + + /** + * Format the date period as ISO 8601. + * + * @return string + */ + public function toIso8601String() + { + $parts = []; + + if ($this->recurrences !== null) { + $parts[] = 'R'.$this->recurrences; + } + + $parts[] = $this->startDate->toIso8601String(); + + $parts[] = $this->dateInterval->spec(); + + if ($this->endDate !== null) { + $parts[] = $this->endDate->toIso8601String(); + } + + return implode('/', $parts); + } + + /** + * Convert the date period into a string. + * + * @return string + */ + public function toString() + { + $translator = ([$this->dateClass, 'getTranslator'])(); + + $parts = []; + + $format = !$this->startDate->isStartOfDay() || $this->endDate && !$this->endDate->isStartOfDay() + ? 'Y-m-d H:i:s' + : 'Y-m-d'; + + if ($this->recurrences !== null) { + $parts[] = $this->translate('period_recurrences', [], $this->recurrences, $translator); + } + + $parts[] = $this->translate('period_interval', [':interval' => $this->dateInterval->forHumans([ + 'join' => true, + ])], null, $translator); + + $parts[] = $this->translate('period_start_date', [':date' => $this->startDate->rawFormat($format)], null, $translator); + + if ($this->endDate !== null) { + $parts[] = $this->translate('period_end_date', [':date' => $this->endDate->rawFormat($format)], null, $translator); + } + + $result = implode(' ', $parts); + + return mb_strtoupper(mb_substr($result, 0, 1)).mb_substr($result, 1); + } + + /** + * Format the date period as ISO 8601. + * + * @return string + */ + public function spec() + { + return $this->toIso8601String(); + } + + /** + * Cast the current instance into the given class. + * + * @param string $className The $className::instance() method will be called to cast the current object. + * + * @return DatePeriod + */ + public function cast(string $className) + { + if (!method_exists($className, 'instance')) { + if (is_a($className, DatePeriod::class, true)) { + return new $className( + $this->getStartDate(), + $this->getDateInterval(), + $this->getEndDate() ? $this->getIncludedEndDate() : $this->getRecurrences(), + $this->isStartExcluded() ? DatePeriod::EXCLUDE_START_DATE : 0 + ); + } + + throw new InvalidCastException("$className has not the instance() method needed to cast the date."); + } + + return $className::instance($this); + } + + /** + * Return native DatePeriod PHP object matching the current instance. + * + * @example + * ``` + * var_dump(CarbonPeriod::create('2021-01-05', '2021-02-15')->toDatePeriod()); + * ``` + * + * @return DatePeriod + */ + public function toDatePeriod() + { + return $this->cast(DatePeriod::class); + } + + /** + * Convert the date period into an array without changing current iteration state. + * + * @return CarbonInterface[] + */ + public function toArray() + { + $state = [ + $this->key, + $this->current ? $this->current->avoidMutation() : null, + $this->validationResult, + ]; + + $result = iterator_to_array($this); + + [$this->key, $this->current, $this->validationResult] = $state; + + return $result; + } + + /** + * Count dates in the date period. + * + * @return int + */ + #[ReturnTypeWillChange] + public function count() + { + return \count($this->toArray()); + } + + /** + * Return the first date in the date period. + * + * @return CarbonInterface|null + */ + public function first() + { + return ($this->toArray() ?: [])[0] ?? null; + } + + /** + * Return the last date in the date period. + * + * @return CarbonInterface|null + */ + public function last() + { + $array = $this->toArray(); + + return $array ? $array[\count($array) - 1] : null; + } + + /** + * Convert the date period into a string. + * + * @return string + */ + public function __toString() + { + return $this->toString(); + } + + /** + * Add aliases for setters. + * + * CarbonPeriod::days(3)->hours(5)->invert() + * ->sinceNow()->until('2010-01-10') + * ->filter(...) + * ->count() + * + * Note: We use magic method to let static and instance aliases with the same names. + * + * @param string $method + * @param array $parameters + * + * @return mixed + */ + public function __call($method, $parameters) + { + if (static::hasMacro($method)) { + return static::bindMacroContext($this, function () use (&$method, &$parameters) { + return $this->callMacro($method, $parameters); + }); + } + + $roundedValue = $this->callRoundMethod($method, $parameters); + + if ($roundedValue !== null) { + return $roundedValue; + } + + $first = \count($parameters) >= 1 ? $parameters[0] : null; + $second = \count($parameters) >= 2 ? $parameters[1] : null; + + switch ($method) { + case 'start': + case 'since': + return $this->setStartDate($first, $second); + + case 'sinceNow': + return $this->setStartDate(new Carbon(), $first); + + case 'end': + case 'until': + return $this->setEndDate($first, $second); + + case 'untilNow': + return $this->setEndDate(new Carbon(), $first); + + case 'dates': + case 'between': + return $this->setDates($first, $second); + + case 'recurrences': + case 'times': + return $this->setRecurrences($first); + + case 'options': + return $this->setOptions($first); + + case 'toggle': + return $this->toggleOptions($first, $second); + + case 'filter': + case 'push': + return $this->addFilter($first, $second); + + case 'prepend': + return $this->prependFilter($first, $second); + + case 'filters': + return $this->setFilters($first ?: []); + + case 'interval': + case 'each': + case 'every': + case 'step': + case 'stepBy': + return $this->setDateInterval($first); + + case 'invert': + return $this->invertDateInterval(); + + case 'years': + case 'year': + case 'months': + case 'month': + case 'weeks': + case 'week': + case 'days': + case 'dayz': + case 'day': + case 'hours': + case 'hour': + case 'minutes': + case 'minute': + case 'seconds': + case 'second': + return $this->setDateInterval(( + // Override default P1D when instantiating via fluent setters. + [$this->isDefaultInterval ? new CarbonInterval('PT0S') : $this->dateInterval, $method] + )( + \count($parameters) === 0 ? 1 : $first + )); + } + + if ($this->localStrictModeEnabled ?? Carbon::isStrictModeEnabled()) { + throw new UnknownMethodException($method); + } + + return $this; + } + + /** + * Set the instance's timezone from a string or object and apply it to start/end. + * + * @param \DateTimeZone|string $timezone + * + * @return static + */ + public function setTimezone($timezone) + { + $this->tzName = $timezone; + $this->timezone = $timezone; + + if ($this->startDate) { + $this->setStartDate($this->startDate->setTimezone($timezone)); + } + + if ($this->endDate) { + $this->setEndDate($this->endDate->setTimezone($timezone)); + } + + return $this; + } + + /** + * Set the instance's timezone from a string or object and add/subtract the offset difference to start/end. + * + * @param \DateTimeZone|string $timezone + * + * @return static + */ + public function shiftTimezone($timezone) + { + $this->tzName = $timezone; + $this->timezone = $timezone; + + if ($this->startDate) { + $this->setStartDate($this->startDate->shiftTimezone($timezone)); + } + + if ($this->endDate) { + $this->setEndDate($this->endDate->shiftTimezone($timezone)); + } + + return $this; + } + + /** + * Returns the end is set, else calculated from start an recurrences. + * + * @param string|null $rounding Optional rounding 'floor', 'ceil', 'round' using the period interval. + * + * @return CarbonInterface + */ + public function calculateEnd(string $rounding = null) + { + if ($end = $this->getEndDate($rounding)) { + return $end; + } + + if ($this->dateInterval->isEmpty()) { + return $this->getStartDate($rounding); + } + + $date = $this->getEndFromRecurrences() ?? $this->iterateUntilEnd(); + + if ($date && $rounding) { + $date = $date->avoidMutation()->round($this->getDateInterval(), $rounding); + } + + return $date; + } + + /** + * @return CarbonInterface|null + */ + private function getEndFromRecurrences() + { + if ($this->recurrences === null) { + throw new UnreachableException( + "Could not calculate period end without either explicit end or recurrences.\n". + "If you're looking for a forever-period, use ->setRecurrences(INF)." + ); + } + + if ($this->recurrences === INF) { + $start = $this->getStartDate(); + + return $start < $start->avoidMutation()->add($this->getDateInterval()) + ? CarbonImmutable::endOfTime() + : CarbonImmutable::startOfTime(); + } + + if ($this->filters === [[static::RECURRENCES_FILTER, null]]) { + return $this->getStartDate()->avoidMutation()->add( + $this->getDateInterval()->times( + $this->recurrences - ($this->isStartExcluded() ? 0 : 1) + ) + ); + } + + return null; + } + + /** + * @return CarbonInterface|null + */ + private function iterateUntilEnd() + { + $attempts = 0; + $date = null; + + foreach ($this as $date) { + if (++$attempts > static::END_MAX_ATTEMPTS) { + throw new UnreachableException( + 'Could not calculate period end after iterating '.static::END_MAX_ATTEMPTS.' times.' + ); + } + } + + return $date; + } + + /** + * Returns true if the current period overlaps the given one (if 1 parameter passed) + * or the period between 2 dates (if 2 parameters passed). + * + * @param CarbonPeriod|\DateTimeInterface|Carbon|CarbonImmutable|string $rangeOrRangeStart + * @param \DateTimeInterface|Carbon|CarbonImmutable|string|null $rangeEnd + * + * @return bool + */ + public function overlaps($rangeOrRangeStart, $rangeEnd = null) + { + $range = $rangeEnd ? static::create($rangeOrRangeStart, $rangeEnd) : $rangeOrRangeStart; + + if (!($range instanceof self)) { + $range = static::create($range); + } + + [$start, $end] = $this->orderCouple($this->getStartDate(), $this->calculateEnd()); + [$rangeStart, $rangeEnd] = $this->orderCouple($range->getStartDate(), $range->calculateEnd()); + + return $end > $rangeStart && $rangeEnd > $start; + } + + /** + * Execute a given function on each date of the period. + * + * @example + * ``` + * Carbon::create('2020-11-29')->daysUntil('2020-12-24')->forEach(function (Carbon $date) { + * echo $date->diffInDays('2020-12-25')." days before Christmas!\n"; + * }); + * ``` + * + * @param callable $callback + */ + public function forEach(callable $callback) + { + foreach ($this as $date) { + $callback($date); + } + } + + /** + * Execute a given function on each date of the period and yield the result of this function. + * + * @example + * ``` + * $period = Carbon::create('2020-11-29')->daysUntil('2020-12-24'); + * echo implode("\n", iterator_to_array($period->map(function (Carbon $date) { + * return $date->diffInDays('2020-12-25').' days before Christmas!'; + * }))); + * ``` + * + * @param callable $callback + * + * @return \Generator + */ + public function map(callable $callback) + { + foreach ($this as $date) { + yield $callback($date); + } + } + + /** + * Determines if the instance is equal to another. + * Warning: if options differ, instances wil never be equal. + * + * @param mixed $period + * + * @see equalTo() + * + * @return bool + */ + public function eq($period): bool + { + return $this->equalTo($period); + } + + /** + * Determines if the instance is equal to another. + * Warning: if options differ, instances wil never be equal. + * + * @param mixed $period + * + * @return bool + */ + public function equalTo($period): bool + { + if (!($period instanceof self)) { + $period = self::make($period); + } + + $end = $this->getEndDate(); + + return $period !== null + && $this->getDateInterval()->eq($period->getDateInterval()) + && $this->getStartDate()->eq($period->getStartDate()) + && ($end ? $end->eq($period->getEndDate()) : $this->getRecurrences() === $period->getRecurrences()) + && ($this->getOptions() & (~static::IMMUTABLE)) === ($period->getOptions() & (~static::IMMUTABLE)); + } + + /** + * Determines if the instance is not equal to another. + * Warning: if options differ, instances wil never be equal. + * + * @param mixed $period + * + * @see notEqualTo() + * + * @return bool + */ + public function ne($period): bool + { + return $this->notEqualTo($period); + } + + /** + * Determines if the instance is not equal to another. + * Warning: if options differ, instances wil never be equal. + * + * @param mixed $period + * + * @return bool + */ + public function notEqualTo($period): bool + { + return !$this->eq($period); + } + + /** + * Determines if the start date is before an other given date. + * (Rather start/end are included by options is ignored.) + * + * @param mixed $date + * + * @return bool + */ + public function startsBefore($date = null): bool + { + return $this->getStartDate()->lessThan($this->resolveCarbon($date)); + } + + /** + * Determines if the start date is before or the same as a given date. + * (Rather start/end are included by options is ignored.) + * + * @param mixed $date + * + * @return bool + */ + public function startsBeforeOrAt($date = null): bool + { + return $this->getStartDate()->lessThanOrEqualTo($this->resolveCarbon($date)); + } + + /** + * Determines if the start date is after an other given date. + * (Rather start/end are included by options is ignored.) + * + * @param mixed $date + * + * @return bool + */ + public function startsAfter($date = null): bool + { + return $this->getStartDate()->greaterThan($this->resolveCarbon($date)); + } + + /** + * Determines if the start date is after or the same as a given date. + * (Rather start/end are included by options is ignored.) + * + * @param mixed $date + * + * @return bool + */ + public function startsAfterOrAt($date = null): bool + { + return $this->getStartDate()->greaterThanOrEqualTo($this->resolveCarbon($date)); + } + + /** + * Determines if the start date is the same as a given date. + * (Rather start/end are included by options is ignored.) + * + * @param mixed $date + * + * @return bool + */ + public function startsAt($date = null): bool + { + return $this->getStartDate()->equalTo($this->resolveCarbon($date)); + } + + /** + * Determines if the end date is before an other given date. + * (Rather start/end are included by options is ignored.) + * + * @param mixed $date + * + * @return bool + */ + public function endsBefore($date = null): bool + { + return $this->calculateEnd()->lessThan($this->resolveCarbon($date)); + } + + /** + * Determines if the end date is before or the same as a given date. + * (Rather start/end are included by options is ignored.) + * + * @param mixed $date + * + * @return bool + */ + public function endsBeforeOrAt($date = null): bool + { + return $this->calculateEnd()->lessThanOrEqualTo($this->resolveCarbon($date)); + } + + /** + * Determines if the end date is after an other given date. + * (Rather start/end are included by options is ignored.) + * + * @param mixed $date + * + * @return bool + */ + public function endsAfter($date = null): bool + { + return $this->calculateEnd()->greaterThan($this->resolveCarbon($date)); + } + + /** + * Determines if the end date is after or the same as a given date. + * (Rather start/end are included by options is ignored.) + * + * @param mixed $date + * + * @return bool + */ + public function endsAfterOrAt($date = null): bool + { + return $this->calculateEnd()->greaterThanOrEqualTo($this->resolveCarbon($date)); + } + + /** + * Determines if the end date is the same as a given date. + * (Rather start/end are included by options is ignored.) + * + * @param mixed $date + * + * @return bool + */ + public function endsAt($date = null): bool + { + return $this->calculateEnd()->equalTo($this->resolveCarbon($date)); + } + + /** + * Return true if start date is now or later. + * (Rather start/end are included by options is ignored.) + * + * @return bool + */ + public function isStarted(): bool + { + return $this->startsBeforeOrAt(); + } + + /** + * Return true if end date is now or later. + * (Rather start/end are included by options is ignored.) + * + * @return bool + */ + public function isEnded(): bool + { + return $this->endsBeforeOrAt(); + } + + /** + * Return true if now is between start date (included) and end date (excluded). + * (Rather start/end are included by options is ignored.) + * + * @return bool + */ + public function isInProgress(): bool + { + return $this->isStarted() && !$this->isEnded(); + } + + /** + * Round the current instance at the given unit with given precision if specified and the given function. + * + * @param string $unit + * @param float|int|string|\DateInterval|null $precision + * @param string $function + * + * @return $this + */ + public function roundUnit($unit, $precision = 1, $function = 'round') + { + $this->setStartDate($this->getStartDate()->roundUnit($unit, $precision, $function)); + + if ($this->endDate) { + $this->setEndDate($this->getEndDate()->roundUnit($unit, $precision, $function)); + } + + $this->setDateInterval($this->getDateInterval()->roundUnit($unit, $precision, $function)); + + return $this; + } + + /** + * Truncate the current instance at the given unit with given precision if specified. + * + * @param string $unit + * @param float|int|string|\DateInterval|null $precision + * + * @return $this + */ + public function floorUnit($unit, $precision = 1) + { + return $this->roundUnit($unit, $precision, 'floor'); + } + + /** + * Ceil the current instance at the given unit with given precision if specified. + * + * @param string $unit + * @param float|int|string|\DateInterval|null $precision + * + * @return $this + */ + public function ceilUnit($unit, $precision = 1) + { + return $this->roundUnit($unit, $precision, 'ceil'); + } + + /** + * Round the current instance second with given precision if specified (else period interval is used). + * + * @param float|int|string|\DateInterval|null $precision + * @param string $function + * + * @return $this + */ + public function round($precision = null, $function = 'round') + { + return $this->roundWith($precision ?? (string) $this->getDateInterval(), $function); + } + + /** + * Round the current instance second with given precision if specified (else period interval is used). + * + * @param float|int|string|\DateInterval|null $precision + * + * @return $this + */ + public function floor($precision = null) + { + return $this->round($precision, 'floor'); + } + + /** + * Ceil the current instance second with given precision if specified (else period interval is used). + * + * @param float|int|string|\DateInterval|null $precision + * + * @return $this + */ + public function ceil($precision = null) + { + return $this->round($precision, 'ceil'); + } + + /** + * Specify data which should be serialized to JSON. + * + * @link https://php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return CarbonInterface[] + */ + #[ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * Return true if the given date is between start and end. + * + * @param \Carbon\Carbon|\Carbon\CarbonPeriod|\Carbon\CarbonInterval|\DateInterval|\DatePeriod|\DateTimeInterface|string|null $date + * + * @return bool + */ + public function contains($date = null): bool + { + $startMethod = 'startsBefore'.($this->isStartIncluded() ? 'OrAt' : ''); + $endMethod = 'endsAfter'.($this->isEndIncluded() ? 'OrAt' : ''); + + return $this->$startMethod($date) && $this->$endMethod($date); + } + + /** + * Return true if the current period follows a given other period (with no overlap). + * For instance, [2019-08-01 -> 2019-08-12] follows [2019-07-29 -> 2019-07-31] + * Note than in this example, follows() would be false if 2019-08-01 or 2019-07-31 was excluded by options. + * + * @param \Carbon\CarbonPeriod|\DatePeriod|string $period + * + * @return bool + */ + public function follows($period, ...$arguments): bool + { + $period = $this->resolveCarbonPeriod($period, ...$arguments); + + return $this->getIncludedStartDate()->equalTo($period->getIncludedEndDate()->add($period->getDateInterval())); + } + + /** + * Return true if the given other period follows the current one (with no overlap). + * For instance, [2019-07-29 -> 2019-07-31] is followed by [2019-08-01 -> 2019-08-12] + * Note than in this example, isFollowedBy() would be false if 2019-08-01 or 2019-07-31 was excluded by options. + * + * @param \Carbon\CarbonPeriod|\DatePeriod|string $period + * + * @return bool + */ + public function isFollowedBy($period, ...$arguments): bool + { + $period = $this->resolveCarbonPeriod($period, ...$arguments); + + return $period->follows($this); + } + + /** + * Return true if the given period either follows or is followed by the current one. + * + * @see follows() + * @see isFollowedBy() + * + * @param \Carbon\CarbonPeriod|\DatePeriod|string $period + * + * @return bool + */ + public function isConsecutiveWith($period, ...$arguments): bool + { + return $this->follows($period, ...$arguments) || $this->isFollowedBy($period, ...$arguments); + } + + /** + * Update properties after removing built-in filters. + * + * @return void + */ + protected function updateInternalState() + { + if (!$this->hasFilter(static::END_DATE_FILTER)) { + $this->endDate = null; + } + + if (!$this->hasFilter(static::RECURRENCES_FILTER)) { + $this->recurrences = null; + } + } + + /** + * Create a filter tuple from raw parameters. + * + * Will create an automatic filter callback for one of Carbon's is* methods. + * + * @param array $parameters + * + * @return array + */ + protected function createFilterTuple(array $parameters) + { + $method = array_shift($parameters); + + if (!$this->isCarbonPredicateMethod($method)) { + return [$method, array_shift($parameters)]; + } + + return [function ($date) use ($method, $parameters) { + return ([$date, $method])(...$parameters); + }, $method]; + } + + /** + * Return whether given callable is a string pointing to one of Carbon's is* methods + * and should be automatically converted to a filter callback. + * + * @param callable $callable + * + * @return bool + */ + protected function isCarbonPredicateMethod($callable) + { + return \is_string($callable) && str_starts_with($callable, 'is') && + (method_exists($this->dateClass, $callable) || ([$this->dateClass, 'hasMacro'])($callable)); + } + + /** + * Recurrences filter callback (limits number of recurrences). + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @param \Carbon\Carbon $current + * @param int $key + * + * @return bool|string + */ + protected function filterRecurrences($current, $key) + { + if ($key < $this->recurrences) { + return true; + } + + return static::END_ITERATION; + } + + /** + * End date filter callback. + * + * @param \Carbon\Carbon $current + * + * @return bool|string + */ + protected function filterEndDate($current) + { + if (!$this->isEndExcluded() && $current == $this->endDate) { + return true; + } + + if ($this->dateInterval->invert ? $current > $this->endDate : $current < $this->endDate) { + return true; + } + + return static::END_ITERATION; + } + + /** + * End iteration filter callback. + * + * @return string + */ + protected function endIteration() + { + return static::END_ITERATION; + } + + /** + * Handle change of the parameters. + */ + protected function handleChangedParameters() + { + if (($this->getOptions() & static::IMMUTABLE) && $this->dateClass === Carbon::class) { + $this->setDateClass(CarbonImmutable::class); + } elseif (!($this->getOptions() & static::IMMUTABLE) && $this->dateClass === CarbonImmutable::class) { + $this->setDateClass(Carbon::class); + } + + $this->validationResult = null; + } + + /** + * Validate current date and stop iteration when necessary. + * + * Returns true when current date is valid, false if it is not, or static::END_ITERATION + * when iteration should be stopped. + * + * @return bool|string + */ + protected function validateCurrentDate() + { + if ($this->current === null) { + $this->rewind(); + } + + // Check after the first rewind to avoid repeating the initial validation. + return $this->validationResult ?? ($this->validationResult = $this->checkFilters()); + } + + /** + * Check whether current value and key pass all the filters. + * + * @return bool|string + */ + protected function checkFilters() + { + $current = $this->prepareForReturn($this->current); + + foreach ($this->filters as $tuple) { + $result = \call_user_func( + $tuple[0], + $current->avoidMutation(), + $this->key, + $this + ); + + if ($result === static::END_ITERATION) { + return static::END_ITERATION; + } + + if (!$result) { + return false; + } + } + + return true; + } + + /** + * Prepare given date to be returned to the external logic. + * + * @param CarbonInterface $date + * + * @return CarbonInterface + */ + protected function prepareForReturn(CarbonInterface $date) + { + $date = ([$this->dateClass, 'make'])($date); + + if ($this->timezone) { + $date = $date->setTimezone($this->timezone); + } + + return $date; + } + + /** + * Keep incrementing the current date until a valid date is found or the iteration is ended. + * + * @throws RuntimeException + * + * @return void + */ + protected function incrementCurrentDateUntilValid() + { + $attempts = 0; + + do { + $this->current = $this->current->add($this->dateInterval); + + $this->validationResult = null; + + if (++$attempts > static::NEXT_MAX_ATTEMPTS) { + throw new UnreachableException('Could not find next valid date.'); + } + } while ($this->validateCurrentDate() === false); + } + + /** + * Call given macro. + * + * @param string $name + * @param array $parameters + * + * @return mixed + */ + protected function callMacro($name, $parameters) + { + $macro = static::$macros[$name]; + + if ($macro instanceof Closure) { + $boundMacro = @$macro->bindTo($this, static::class) ?: @$macro->bindTo(null, static::class); + + return ($boundMacro ?: $macro)(...$parameters); + } + + return $macro(...$parameters); + } + + /** + * Return the Carbon instance passed through, a now instance in the same timezone + * if null given or parse the input if string given. + * + * @param \Carbon\Carbon|\Carbon\CarbonPeriod|\Carbon\CarbonInterval|\DateInterval|\DatePeriod|\DateTimeInterface|string|null $date + * + * @return \Carbon\CarbonInterface + */ + protected function resolveCarbon($date = null) + { + return $this->getStartDate()->nowWithSameTz()->carbonize($date); + } + + /** + * Resolve passed arguments or DatePeriod to a CarbonPeriod object. + * + * @param mixed $period + * @param mixed ...$arguments + * + * @return static + */ + protected function resolveCarbonPeriod($period, ...$arguments) + { + if ($period instanceof self) { + return $period; + } + + return $period instanceof DatePeriod + ? static::instance($period) + : static::create($period, ...$arguments); + } + + private function orderCouple($first, $second): array + { + return $first > $second ? [$second, $first] : [$first, $second]; + } + + private function makeDateTime($value): ?DateTimeInterface + { + if ($value instanceof DateTimeInterface) { + return $value; + } + + if (\is_string($value)) { + $value = trim($value); + + if (!preg_match('/^P[0-9T]/', $value) && + !preg_match('/^R[0-9]/', $value) && + preg_match('/[a-z0-9]/i', $value) + ) { + return Carbon::parse($value, $this->tzName); + } + } + + return null; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/CarbonTimeZone.php b/vendor/nesbot/carbon/src/Carbon/CarbonTimeZone.php new file mode 100644 index 0000000..b4d16ba --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/CarbonTimeZone.php @@ -0,0 +1,308 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use Carbon\Exceptions\InvalidCastException; +use Carbon\Exceptions\InvalidTimeZoneException; +use DateTimeInterface; +use DateTimeZone; +use Throwable; + +class CarbonTimeZone extends DateTimeZone +{ + public function __construct($timezone = null) + { + parent::__construct(static::getDateTimeZoneNameFromMixed($timezone)); + } + + protected static function parseNumericTimezone($timezone) + { + if ($timezone <= -100 || $timezone >= 100) { + throw new InvalidTimeZoneException('Absolute timezone offset cannot be greater than 100.'); + } + + return ($timezone >= 0 ? '+' : '').$timezone.':00'; + } + + protected static function getDateTimeZoneNameFromMixed($timezone) + { + if ($timezone === null) { + return date_default_timezone_get(); + } + + if (\is_string($timezone)) { + $timezone = preg_replace('/^\s*([+-]\d+)(\d{2})\s*$/', '$1:$2', $timezone); + } + + if (is_numeric($timezone)) { + return static::parseNumericTimezone($timezone); + } + + return $timezone; + } + + protected static function getDateTimeZoneFromName(&$name) + { + return @timezone_open($name = (string) static::getDateTimeZoneNameFromMixed($name)); + } + + /** + * Cast the current instance into the given class. + * + * @param string $className The $className::instance() method will be called to cast the current object. + * + * @return DateTimeZone + */ + public function cast(string $className) + { + if (!method_exists($className, 'instance')) { + if (is_a($className, DateTimeZone::class, true)) { + return new $className($this->getName()); + } + + throw new InvalidCastException("$className has not the instance() method needed to cast the date."); + } + + return $className::instance($this); + } + + /** + * Create a CarbonTimeZone from mixed input. + * + * @param DateTimeZone|string|int|null $object original value to get CarbonTimeZone from it. + * @param DateTimeZone|string|int|null $objectDump dump of the object for error messages. + * + * @throws InvalidTimeZoneException + * + * @return false|static + */ + public static function instance($object = null, $objectDump = null) + { + $tz = $object; + + if ($tz instanceof static) { + return $tz; + } + + if ($tz === null) { + return new static(); + } + + if (!$tz instanceof DateTimeZone) { + $tz = static::getDateTimeZoneFromName($object); + } + + if ($tz === false) { + if (Carbon::isStrictModeEnabled()) { + throw new InvalidTimeZoneException('Unknown or bad timezone ('.($objectDump ?: $object).')'); + } + + return false; + } + + return new static($tz->getName()); + } + + /** + * Returns abbreviated name of the current timezone according to DST setting. + * + * @param bool $dst + * + * @return string + */ + public function getAbbreviatedName($dst = false) + { + $name = $this->getName(); + + foreach ($this->listAbbreviations() as $abbreviation => $zones) { + foreach ($zones as $zone) { + if ($zone['timezone_id'] === $name && $zone['dst'] == $dst) { + return $abbreviation; + } + } + } + + return 'unknown'; + } + + /** + * @alias getAbbreviatedName + * + * Returns abbreviated name of the current timezone according to DST setting. + * + * @param bool $dst + * + * @return string + */ + public function getAbbr($dst = false) + { + return $this->getAbbreviatedName($dst); + } + + /** + * Get the offset as string "sHH:MM" (such as "+00:00" or "-12:30"). + * + * @param DateTimeInterface|null $date + * + * @return string + */ + public function toOffsetName(DateTimeInterface $date = null) + { + return static::getOffsetNameFromMinuteOffset( + $this->getOffset($date ?: Carbon::now($this)) / 60 + ); + } + + /** + * Returns a new CarbonTimeZone object using the offset string instead of region string. + * + * @param DateTimeInterface|null $date + * + * @return CarbonTimeZone + */ + public function toOffsetTimeZone(DateTimeInterface $date = null) + { + return new static($this->toOffsetName($date)); + } + + /** + * Returns the first region string (such as "America/Toronto") that matches the current timezone or + * false if no match is found. + * + * @see timezone_name_from_abbr native PHP function. + * + * @param DateTimeInterface|null $date + * @param int $isDst + * + * @return string|false + */ + public function toRegionName(DateTimeInterface $date = null, $isDst = 1) + { + $name = $this->getName(); + $firstChar = substr($name, 0, 1); + + if ($firstChar !== '+' && $firstChar !== '-') { + return $name; + } + + $date = $date ?: Carbon::now($this); + + // Integer construction no longer supported since PHP 8 + // @codeCoverageIgnoreStart + try { + $offset = @$this->getOffset($date) ?: 0; + } catch (Throwable $e) { + $offset = 0; + } + // @codeCoverageIgnoreEnd + + $name = @timezone_name_from_abbr('', $offset, $isDst); + + if ($name) { + return $name; + } + + foreach (timezone_identifiers_list() as $timezone) { + if (Carbon::instance($date)->tz($timezone)->getOffset() === $offset) { + return $timezone; + } + } + + return false; + } + + /** + * Returns a new CarbonTimeZone object using the region string instead of offset string. + * + * @param DateTimeInterface|null $date + * + * @return CarbonTimeZone|false + */ + public function toRegionTimeZone(DateTimeInterface $date = null) + { + $tz = $this->toRegionName($date); + + if ($tz === false) { + if (Carbon::isStrictModeEnabled()) { + throw new InvalidTimeZoneException('Unknown timezone for offset '.$this->getOffset($date ?: Carbon::now($this)).' seconds.'); + } + + return false; + } + + return new static($tz); + } + + /** + * Cast to string (get timezone name). + * + * @return string + */ + public function __toString() + { + return $this->getName(); + } + + /** + * Create a CarbonTimeZone from mixed input. + * + * @param DateTimeZone|string|int|null $object + * + * @return false|static + */ + public static function create($object = null) + { + return static::instance($object); + } + + /** + * Create a CarbonTimeZone from int/float hour offset. + * + * @param float $hourOffset number of hour of the timezone shift (can be decimal). + * + * @return false|static + */ + public static function createFromHourOffset(float $hourOffset) + { + return static::createFromMinuteOffset($hourOffset * Carbon::MINUTES_PER_HOUR); + } + + /** + * Create a CarbonTimeZone from int/float minute offset. + * + * @param float $minuteOffset number of total minutes of the timezone shift. + * + * @return false|static + */ + public static function createFromMinuteOffset(float $minuteOffset) + { + return static::instance(static::getOffsetNameFromMinuteOffset($minuteOffset)); + } + + /** + * Convert a total minutes offset into a standardized timezone offset string. + * + * @param float $minutes number of total minutes of the timezone shift. + * + * @return string + */ + public static function getOffsetNameFromMinuteOffset(float $minutes): string + { + $minutes = round($minutes); + $unsignedMinutes = abs($minutes); + + return ($minutes < 0 ? '-' : '+'). + str_pad((string) floor($unsignedMinutes / 60), 2, '0', STR_PAD_LEFT). + ':'. + str_pad((string) ($unsignedMinutes % 60), 2, '0', STR_PAD_LEFT); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Cli/Invoker.php b/vendor/nesbot/carbon/src/Carbon/Cli/Invoker.php new file mode 100644 index 0000000..4f35d6c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Cli/Invoker.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Cli; + +class Invoker +{ + public const CLI_CLASS_NAME = 'Carbon\\Cli'; + + protected function runWithCli(string $className, array $parameters): bool + { + $cli = new $className(); + + return $cli(...$parameters); + } + + public function __invoke(...$parameters): bool + { + if (class_exists(self::CLI_CLASS_NAME)) { + return $this->runWithCli(self::CLI_CLASS_NAME, $parameters); + } + + $function = (($parameters[1] ?? '') === 'install' ? ($parameters[2] ?? null) : null) ?: 'shell_exec'; + $function('composer require carbon-cli/carbon-cli --no-interaction'); + + echo 'Installation succeeded.'; + + return true; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Doctrine/CarbonDoctrineType.php b/vendor/nesbot/carbon/src/Carbon/Doctrine/CarbonDoctrineType.php new file mode 100644 index 0000000..ccc457f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Doctrine/CarbonDoctrineType.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Doctrine; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +interface CarbonDoctrineType +{ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform); + + public function convertToPHPValue($value, AbstractPlatform $platform); + + public function convertToDatabaseValue($value, AbstractPlatform $platform); +} diff --git a/vendor/nesbot/carbon/src/Carbon/Doctrine/CarbonImmutableType.php b/vendor/nesbot/carbon/src/Carbon/Doctrine/CarbonImmutableType.php new file mode 100644 index 0000000..bf476a7 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Doctrine/CarbonImmutableType.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Doctrine; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +class CarbonImmutableType extends DateTimeImmutableType implements CarbonDoctrineType +{ + /** + * {@inheritdoc} + * + * @return string + */ + public function getName() + { + return 'carbon_immutable'; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function requiresSQLCommentHint(AbstractPlatform $platform) + { + return true; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Doctrine/CarbonType.php b/vendor/nesbot/carbon/src/Carbon/Doctrine/CarbonType.php new file mode 100644 index 0000000..9289d84 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Doctrine/CarbonType.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Doctrine; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +class CarbonType extends DateTimeType implements CarbonDoctrineType +{ + /** + * {@inheritdoc} + * + * @return string + */ + public function getName() + { + return 'carbon'; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function requiresSQLCommentHint(AbstractPlatform $platform) + { + return true; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Doctrine/CarbonTypeConverter.php b/vendor/nesbot/carbon/src/Carbon/Doctrine/CarbonTypeConverter.php new file mode 100644 index 0000000..8f2fdee --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Doctrine/CarbonTypeConverter.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Doctrine; + +use Carbon\Carbon; +use Carbon\CarbonInterface; +use DateTimeInterface; +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Types\ConversionException; +use Exception; + +/** + * @template T of CarbonInterface + */ +trait CarbonTypeConverter +{ + /** + * @return class-string + */ + protected function getCarbonClassName(): string + { + return Carbon::class; + } + + /** + * @return string + */ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + $precision = ($fieldDeclaration['precision'] ?: 10) === 10 + ? DateTimeDefaultPrecision::get() + : $fieldDeclaration['precision']; + $type = parent::getSQLDeclaration($fieldDeclaration, $platform); + + if (!$precision) { + return $type; + } + + if (str_contains($type, '(')) { + return preg_replace('/\(\d+\)/', "($precision)", $type); + } + + [$before, $after] = explode(' ', "$type "); + + return trim("$before($precision) $after"); + } + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @return T|null + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + $class = $this->getCarbonClassName(); + + if ($value === null || is_a($value, $class)) { + return $value; + } + + if ($value instanceof DateTimeInterface) { + return $class::instance($value); + } + + $date = null; + $error = null; + + try { + $date = $class::parse($value); + } catch (Exception $exception) { + $error = $exception; + } + + if (!$date) { + throw ConversionException::conversionFailedFormat( + $value, + $this->getName(), + 'Y-m-d H:i:s.u or any format supported by '.$class.'::parse()', + $error + ); + } + + return $date; + } + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @return string|null + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform) + { + if ($value === null) { + return $value; + } + + if ($value instanceof DateTimeInterface) { + return $value->format('Y-m-d H:i:s.u'); + } + + throw ConversionException::conversionFailedInvalidType( + $value, + $this->getName(), + ['null', 'DateTime', 'Carbon'] + ); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Doctrine/DateTimeDefaultPrecision.php b/vendor/nesbot/carbon/src/Carbon/Doctrine/DateTimeDefaultPrecision.php new file mode 100644 index 0000000..642fd41 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Doctrine/DateTimeDefaultPrecision.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Doctrine; + +class DateTimeDefaultPrecision +{ + private static $precision = 6; + + /** + * Change the default Doctrine datetime and datetime_immutable precision. + * + * @param int $precision + */ + public static function set(int $precision): void + { + self::$precision = $precision; + } + + /** + * Get the default Doctrine datetime and datetime_immutable precision. + * + * @return int + */ + public static function get(): int + { + return self::$precision; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Doctrine/DateTimeImmutableType.php b/vendor/nesbot/carbon/src/Carbon/Doctrine/DateTimeImmutableType.php new file mode 100644 index 0000000..4992710 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Doctrine/DateTimeImmutableType.php @@ -0,0 +1,24 @@ + */ + use CarbonTypeConverter; + + /** + * @return class-string + */ + protected function getCarbonClassName(): string + { + return CarbonImmutable::class; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Doctrine/DateTimeType.php b/vendor/nesbot/carbon/src/Carbon/Doctrine/DateTimeType.php new file mode 100644 index 0000000..29b0bb9 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Doctrine/DateTimeType.php @@ -0,0 +1,16 @@ + */ + use CarbonTypeConverter; +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/BadComparisonUnitException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/BadComparisonUnitException.php new file mode 100644 index 0000000..b3a0871 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/BadComparisonUnitException.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use Exception; + +class BadComparisonUnitException extends UnitException +{ + /** + * Constructor. + * + * @param string $unit + * @param int $code + * @param Exception|null $previous + */ + public function __construct($unit, $code = 0, Exception $previous = null) + { + parent::__construct("Bad comparison unit: '$unit'", $code, $previous); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/BadFluentConstructorException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/BadFluentConstructorException.php new file mode 100644 index 0000000..d5cd556 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/BadFluentConstructorException.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use BadMethodCallException as BaseBadMethodCallException; +use Exception; + +class BadFluentConstructorException extends BaseBadMethodCallException implements BadMethodCallException +{ + /** + * Constructor. + * + * @param string $method + * @param int $code + * @param Exception|null $previous + */ + public function __construct($method, $code = 0, Exception $previous = null) + { + parent::__construct(sprintf("Unknown fluent constructor '%s'.", $method), $code, $previous); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/BadFluentSetterException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/BadFluentSetterException.php new file mode 100644 index 0000000..1d7ec54 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/BadFluentSetterException.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use BadMethodCallException as BaseBadMethodCallException; +use Exception; + +class BadFluentSetterException extends BaseBadMethodCallException implements BadMethodCallException +{ + /** + * Constructor. + * + * @param string $method + * @param int $code + * @param Exception|null $previous + */ + public function __construct($method, $code = 0, Exception $previous = null) + { + parent::__construct(sprintf("Unknown fluent setter '%s'", $method), $code, $previous); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/BadMethodCallException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/BadMethodCallException.php new file mode 100644 index 0000000..73c2dd8 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/BadMethodCallException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +interface BadMethodCallException extends Exception +{ +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/Exception.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/Exception.php new file mode 100644 index 0000000..3bbbd77 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/Exception.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +interface Exception +{ +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/ImmutableException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/ImmutableException.php new file mode 100644 index 0000000..a48d4f9 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/ImmutableException.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use Exception; +use RuntimeException as BaseRuntimeException; + +class ImmutableException extends BaseRuntimeException implements RuntimeException +{ + /** + * Constructor. + * + * @param string $value the immutable type/value + * @param int $code + * @param Exception|null $previous + */ + public function __construct($value, $code = 0, Exception $previous = null) + { + parent::__construct("$value is immutable.", $code, $previous); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidArgumentException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidArgumentException.php new file mode 100644 index 0000000..9739f4d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidArgumentException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +interface InvalidArgumentException extends Exception +{ +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidCastException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidCastException.php new file mode 100644 index 0000000..d2f3701 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidCastException.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use Exception; +use InvalidArgumentException as BaseInvalidArgumentException; + +class InvalidCastException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + /** + * Constructor. + * + * @param string $message + * @param int $code + * @param Exception|null $previous + */ + public function __construct($message, $code = 0, Exception $previous = null) + { + parent::__construct($message, $code, $previous); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidDateException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidDateException.php new file mode 100644 index 0000000..99bb91c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidDateException.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use Exception; +use InvalidArgumentException as BaseInvalidArgumentException; + +class InvalidDateException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + /** + * The invalid field. + * + * @var string + */ + private $field; + + /** + * The invalid value. + * + * @var mixed + */ + private $value; + + /** + * Constructor. + * + * @param string $field + * @param mixed $value + * @param int $code + * @param Exception|null $previous + */ + public function __construct($field, $value, $code = 0, Exception $previous = null) + { + $this->field = $field; + $this->value = $value; + parent::__construct($field.' : '.$value.' is not a valid value.', $code, $previous); + } + + /** + * Get the invalid field. + * + * @return string + */ + public function getField() + { + return $this->field; + } + + /** + * Get the invalid value. + * + * @return mixed + */ + public function getValue() + { + return $this->value; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidFormatException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidFormatException.php new file mode 100644 index 0000000..3341b49 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidFormatException.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use Exception; +use InvalidArgumentException as BaseInvalidArgumentException; + +class InvalidFormatException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + /** + * Constructor. + * + * @param string $message + * @param int $code + * @param Exception|null $previous + */ + public function __construct($message, $code = 0, Exception $previous = null) + { + parent::__construct($message, $code, $previous); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidIntervalException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidIntervalException.php new file mode 100644 index 0000000..5f9f142 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidIntervalException.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use Exception; +use InvalidArgumentException as BaseInvalidArgumentException; + +class InvalidIntervalException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + /** + * Constructor. + * + * @param string $message + * @param int $code + * @param Exception|null $previous + */ + public function __construct($message, $code = 0, Exception $previous = null) + { + parent::__construct($message, $code, $previous); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidPeriodDateException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidPeriodDateException.php new file mode 100644 index 0000000..a37e3f5 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidPeriodDateException.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use Exception; +use InvalidArgumentException as BaseInvalidArgumentException; + +class InvalidPeriodDateException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + /** + * Constructor. + * + * @param string $message + * @param int $code + * @param Exception|null $previous + */ + public function __construct($message, $code = 0, Exception $previous = null) + { + parent::__construct($message, $code, $previous); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidPeriodParameterException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidPeriodParameterException.php new file mode 100644 index 0000000..ede4771 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidPeriodParameterException.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use Exception; +use InvalidArgumentException as BaseInvalidArgumentException; + +class InvalidPeriodParameterException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + /** + * Constructor. + * + * @param string $message + * @param int $code + * @param Exception|null $previous + */ + public function __construct($message, $code = 0, Exception $previous = null) + { + parent::__construct($message, $code, $previous); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidTimeZoneException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidTimeZoneException.php new file mode 100644 index 0000000..892e16e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidTimeZoneException.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use Exception; +use InvalidArgumentException as BaseInvalidArgumentException; + +class InvalidTimeZoneException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + /** + * Constructor. + * + * @param string $message + * @param int $code + * @param Exception|null $previous + */ + public function __construct($message, $code = 0, Exception $previous = null) + { + parent::__construct($message, $code, $previous); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidTypeException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidTypeException.php new file mode 100644 index 0000000..3fbe3fc --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidTypeException.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use Exception; +use InvalidArgumentException as BaseInvalidArgumentException; + +class InvalidTypeException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + /** + * Constructor. + * + * @param string $message + * @param int $code + * @param Exception|null $previous + */ + public function __construct($message, $code = 0, Exception $previous = null) + { + parent::__construct($message, $code, $previous); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/NotACarbonClassException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/NotACarbonClassException.php new file mode 100644 index 0000000..2b4c48e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/NotACarbonClassException.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use Carbon\CarbonInterface; +use Exception; +use InvalidArgumentException as BaseInvalidArgumentException; + +class NotACarbonClassException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + /** + * Constructor. + * + * @param string $className + * @param int $code + * @param Exception|null $previous + */ + public function __construct($className, $code = 0, Exception $previous = null) + { + parent::__construct(sprintf( + 'Given class does not implement %s: %s', + CarbonInterface::class, + $className + ), $code, $previous); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/NotAPeriodException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/NotAPeriodException.php new file mode 100644 index 0000000..41bb629 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/NotAPeriodException.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use Exception; +use InvalidArgumentException as BaseInvalidArgumentException; + +class NotAPeriodException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + /** + * Constructor. + * + * @param string $message + * @param int $code + * @param Exception|null $previous + */ + public function __construct($message, $code = 0, Exception $previous = null) + { + parent::__construct($message, $code, $previous); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/NotLocaleAwareException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/NotLocaleAwareException.php new file mode 100644 index 0000000..adbc36c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/NotLocaleAwareException.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use Exception; +use InvalidArgumentException as BaseInvalidArgumentException; + +class NotLocaleAwareException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + /** + * Constructor. + * + * @param mixed $object + * @param int $code + * @param Exception|null $previous + */ + public function __construct($object, $code = 0, Exception $previous = null) + { + $dump = \is_object($object) ? \get_class($object) : \gettype($object); + + parent::__construct("$dump does neither implements Symfony\Contracts\Translation\LocaleAwareInterface nor getLocale() method.", $code, $previous); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/OutOfRangeException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/OutOfRangeException.php new file mode 100644 index 0000000..54822d9 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/OutOfRangeException.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use Exception; +use InvalidArgumentException as BaseInvalidArgumentException; + +// This will extends OutOfRangeException instead of InvalidArgumentException since 3.0.0 +// use OutOfRangeException as BaseOutOfRangeException; + +class OutOfRangeException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + /** + * The unit or name of the value. + * + * @var string + */ + private $unit; + + /** + * The range minimum. + * + * @var mixed + */ + private $min; + + /** + * The range maximum. + * + * @var mixed + */ + private $max; + + /** + * The invalid value. + * + * @var mixed + */ + private $value; + + /** + * Constructor. + * + * @param string $unit + * @param mixed $min + * @param mixed $max + * @param mixed $value + * @param int $code + * @param Exception|null $previous + */ + public function __construct($unit, $min, $max, $value, $code = 0, Exception $previous = null) + { + $this->unit = $unit; + $this->min = $min; + $this->max = $max; + $this->value = $value; + + parent::__construct("$unit must be between $min and $max, $value given", $code, $previous); + } + + /** + * @return mixed + */ + public function getMax() + { + return $this->max; + } + + /** + * @return mixed + */ + public function getMin() + { + return $this->min; + } + + /** + * @return mixed + */ + public function getUnit() + { + return $this->unit; + } + + /** + * @return mixed + */ + public function getValue() + { + return $this->value; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/ParseErrorException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/ParseErrorException.php new file mode 100644 index 0000000..0314c5d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/ParseErrorException.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use Exception; +use InvalidArgumentException as BaseInvalidArgumentException; + +class ParseErrorException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + /** + * Constructor. + * + * @param string $expected + * @param string $actual + * @param int $code + * @param Exception|null $previous + */ + public function __construct($expected, $actual, $help = '', $code = 0, Exception $previous = null) + { + $actual = $actual === '' ? 'data is missing' : "get '$actual'"; + + parent::__construct(trim("Format expected $expected but $actual\n$help"), $code, $previous); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/RuntimeException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/RuntimeException.php new file mode 100644 index 0000000..24bf5a6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/RuntimeException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +interface RuntimeException extends Exception +{ +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/UnitException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/UnitException.php new file mode 100644 index 0000000..8bd8653 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/UnitException.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use Exception; +use InvalidArgumentException as BaseInvalidArgumentException; + +class UnitException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + /** + * Constructor. + * + * @param string $message + * @param int $code + * @param Exception|null $previous + */ + public function __construct($message, $code = 0, Exception $previous = null) + { + parent::__construct($message, $code, $previous); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/UnitNotConfiguredException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/UnitNotConfiguredException.php new file mode 100644 index 0000000..39ee12c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/UnitNotConfiguredException.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use Exception; + +class UnitNotConfiguredException extends UnitException +{ + /** + * Constructor. + * + * @param string $unit + * @param int $code + * @param Exception|null $previous + */ + public function __construct($unit, $code = 0, Exception $previous = null) + { + parent::__construct("Unit $unit have no configuration to get total from other units.", $code, $previous); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownGetterException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownGetterException.php new file mode 100644 index 0000000..6c8c01b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownGetterException.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use Exception; +use InvalidArgumentException as BaseInvalidArgumentException; + +class UnknownGetterException extends BaseInvalidArgumentException implements InvalidArgumentException +{ + /** + * Constructor. + * + * @param string $name getter name + * @param int $code + * @param Exception|null $previous + */ + public function __construct($name, $code = 0, Exception $previous = null) + { + parent::__construct("Unknown getter '$name'", $code, $previous); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownMethodException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownMethodException.php new file mode 100644 index 0000000..901db98 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownMethodException.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use BadMethodCallException as BaseBadMethodCallException; +use Exception; + +class UnknownMethodException extends BaseBadMethodCallException implements BadMethodCallException +{ + /** + * Constructor. + * + * @param string $method + * @param int $code + * @param Exception|null $previous + */ + public function __construct($method, $code = 0, Exception $previous = null) + { + parent::__construct("Method $method does not exist.", $code, $previous); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownSetterException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownSetterException.php new file mode 100644 index 0000000..c9e9c9f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownSetterException.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use Exception; +use InvalidArgumentException as BaseInvalidArgumentException; + +class UnknownSetterException extends BaseInvalidArgumentException implements BadMethodCallException +{ + /** + * Constructor. + * + * @param string $name setter name + * @param int $code + * @param Exception|null $previous + */ + public function __construct($name, $code = 0, Exception $previous = null) + { + parent::__construct("Unknown setter '$name'", $code, $previous); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownUnitException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownUnitException.php new file mode 100644 index 0000000..d965c82 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownUnitException.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use Exception; + +class UnknownUnitException extends UnitException +{ + /** + * Constructor. + * + * @param string $unit + * @param int $code + * @param Exception|null $previous + */ + public function __construct($unit, $code = 0, Exception $previous = null) + { + parent::__construct("Unknown unit '$unit'.", $code, $previous); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Exceptions/UnreachableException.php b/vendor/nesbot/carbon/src/Carbon/Exceptions/UnreachableException.php new file mode 100644 index 0000000..6f8b39f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Exceptions/UnreachableException.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use Exception; +use RuntimeException as BaseRuntimeException; + +class UnreachableException extends BaseRuntimeException implements RuntimeException +{ + /** + * Constructor. + * + * @param string $message + * @param int $code + * @param Exception|null $previous + */ + public function __construct($message, $code = 0, Exception $previous = null) + { + parent::__construct($message, $code, $previous); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Factory.php b/vendor/nesbot/carbon/src/Carbon/Factory.php new file mode 100644 index 0000000..f8c7289 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Factory.php @@ -0,0 +1,326 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use Closure; +use DateTimeInterface; +use ReflectionMethod; + +/** + * A factory to generate Carbon instances with common settings. + * + * + * + * @method bool canBeCreatedFromFormat($date, $format) Checks if the (date)time string is in a given format and valid to create a + * new instance. + * @method Carbon|false create($year = 0, $month = 1, $day = 1, $hour = 0, $minute = 0, $second = 0, $tz = null) Create a new Carbon instance from a specific date and time. + * If any of $year, $month or $day are set to null their now() values will + * be used. + * If $hour is null it will be set to its now() value and the default + * values for $minute and $second will be their now() values. + * If $hour is not null then the default values for $minute and $second + * will be 0. + * @method Carbon createFromDate($year = null, $month = null, $day = null, $tz = null) Create a Carbon instance from just a date. The time portion is set to now. + * @method Carbon|false createFromFormat($format, $time, $tz = null) Create a Carbon instance from a specific format. + * @method Carbon|false createFromIsoFormat($format, $time, $tz = null, $locale = 'en', $translator = null) Create a Carbon instance from a specific ISO format (same replacements as ->isoFormat()). + * @method Carbon|false createFromLocaleFormat($format, $locale, $time, $tz = null) Create a Carbon instance from a specific format and a string in a given language. + * @method Carbon|false createFromLocaleIsoFormat($format, $locale, $time, $tz = null) Create a Carbon instance from a specific ISO format and a string in a given language. + * @method Carbon createFromTime($hour = 0, $minute = 0, $second = 0, $tz = null) Create a Carbon instance from just a time. The date portion is set to today. + * @method Carbon createFromTimeString($time, $tz = null) Create a Carbon instance from a time string. The date portion is set to today. + * @method Carbon createFromTimestamp($timestamp, $tz = null) Create a Carbon instance from a timestamp and set the timezone (use default one if not specified). + * Timestamp input can be given as int, float or a string containing one or more numbers. + * @method Carbon createFromTimestampMs($timestamp, $tz = null) Create a Carbon instance from a timestamp in milliseconds. + * Timestamp input can be given as int, float or a string containing one or more numbers. + * @method Carbon createFromTimestampMsUTC($timestamp) Create a Carbon instance from a timestamp in milliseconds. + * Timestamp input can be given as int, float or a string containing one or more numbers. + * @method Carbon createFromTimestampUTC($timestamp) Create a Carbon instance from an timestamp keeping the timezone to UTC. + * Timestamp input can be given as int, float or a string containing one or more numbers. + * @method Carbon createMidnightDate($year = null, $month = null, $day = null, $tz = null) Create a Carbon instance from just a date. The time portion is set to midnight. + * @method Carbon|false createSafe($year = null, $month = null, $day = null, $hour = null, $minute = null, $second = null, $tz = null) Create a new safe Carbon instance from a specific date and time. + * If any of $year, $month or $day are set to null their now() values will + * be used. + * If $hour is null it will be set to its now() value and the default + * values for $minute and $second will be their now() values. + * If $hour is not null then the default values for $minute and $second + * will be 0. + * If one of the set values is not valid, an InvalidDateException + * will be thrown. + * @method CarbonInterface createStrict(?int $year = 0, ?int $month = 1, ?int $day = 1, ?int $hour = 0, ?int $minute = 0, ?int $second = 0, $tz = null) Create a new Carbon instance from a specific date and time using strict validation. + * @method Carbon disableHumanDiffOption($humanDiffOption) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @method Carbon enableHumanDiffOption($humanDiffOption) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @method mixed executeWithLocale($locale, $func) Set the current locale to the given, execute the passed function, reset the locale to previous one, + * then return the result of the closure (or null if the closure was void). + * @method Carbon fromSerialized($value) Create an instance from a serialized string. + * @method void genericMacro($macro, $priority = 0) Register a custom macro. + * @method array getAvailableLocales() Returns the list of internally available locales and already loaded custom locales. + * (It will ignore custom translator dynamic loading.) + * @method Language[] getAvailableLocalesInfo() Returns list of Language object for each available locale. This object allow you to get the ISO name, native + * name, region and variant of the locale. + * @method array getDays() Get the days of the week + * @method string|null getFallbackLocale() Get the fallback locale. + * @method array getFormatsToIsoReplacements() List of replacements from date() format to isoFormat(). + * @method int getHumanDiffOptions() Return default humanDiff() options (merged flags as integer). + * @method array getIsoUnits() Returns list of locale units for ISO formatting. + * @method array getLastErrors() {@inheritdoc} + * @method string getLocale() Get the current translator locale. + * @method callable|null getMacro($name) Get the raw callable macro registered globally for a given name. + * @method int getMidDayAt() get midday/noon hour + * @method Closure|Carbon getTestNow() Get the Carbon instance (real or mock) to be returned when a "now" + * instance is created. + * @method string getTimeFormatByPrecision($unitPrecision) Return a format from H:i to H:i:s.u according to given unit precision. + * @method string getTranslationMessageWith($translator, string $key, ?string $locale = null, ?string $default = null) Returns raw translation message for a given key. + * @method \Symfony\Component\Translation\TranslatorInterface getTranslator() Get the default translator instance in use. + * @method int getWeekEndsAt() Get the last day of week + * @method int getWeekStartsAt() Get the first day of week + * @method array getWeekendDays() Get weekend days + * @method bool hasFormat($date, $format) Checks if the (date)time string is in a given format. + * @method bool hasFormatWithModifiers($date, $format) Checks if the (date)time string is in a given format. + * @method bool hasMacro($name) Checks if macro is registered globally. + * @method bool hasRelativeKeywords($time) Determine if a time string will produce a relative date. + * @method bool hasTestNow() Determine if there is a valid test instance set. A valid test instance + * is anything that is not null. + * @method Carbon instance($date) Create a Carbon instance from a DateTime one. + * @method bool isImmutable() Returns true if the current class/instance is immutable. + * @method bool isModifiableUnit($unit) Returns true if a property can be changed via setter. + * @method bool isMutable() Returns true if the current class/instance is mutable. + * @method bool isStrictModeEnabled() Returns true if the strict mode is globally in use, false else. + * (It can be overridden in specific instances.) + * @method bool localeHasDiffOneDayWords($locale) Returns true if the given locale is internally supported and has words for 1-day diff (just now, yesterday, tomorrow). + * Support is considered enabled if the 3 words are translated in the given locale. + * @method bool localeHasDiffSyntax($locale) Returns true if the given locale is internally supported and has diff syntax support (ago, from now, before, after). + * Support is considered enabled if the 4 sentences are translated in the given locale. + * @method bool localeHasDiffTwoDayWords($locale) Returns true if the given locale is internally supported and has words for 2-days diff (before yesterday, after tomorrow). + * Support is considered enabled if the 2 words are translated in the given locale. + * @method bool localeHasPeriodSyntax($locale) Returns true if the given locale is internally supported and has period syntax support (X times, every X, from X, to X). + * Support is considered enabled if the 4 sentences are translated in the given locale. + * @method bool localeHasShortUnits($locale) Returns true if the given locale is internally supported and has short-units support. + * Support is considered enabled if either year, day or hour has a short variant translated. + * @method void macro($name, $macro) Register a custom macro. + * @method Carbon|null make($var) Make a Carbon instance from given variable if possible. + * Always return a new instance. Parse only strings and only these likely to be dates (skip intervals + * and recurrences). Throw an exception for invalid format, but otherwise return null. + * @method Carbon maxValue() Create a Carbon instance for the greatest supported date. + * @method Carbon minValue() Create a Carbon instance for the lowest supported date. + * @method void mixin($mixin) Mix another object into the class. + * @method Carbon now($tz = null) Get a Carbon instance for the current date and time. + * @method Carbon parse($time = null, $tz = null) Create a carbon instance from a string. + * This is an alias for the constructor that allows better fluent syntax + * as it allows you to do Carbon::parse('Monday next week')->fn() rather + * than (new Carbon('Monday next week'))->fn(). + * @method Carbon parseFromLocale($time, $locale = null, $tz = null) Create a carbon instance from a localized string (in French, Japanese, Arabic, etc.). + * @method string pluralUnit(string $unit) Returns standardized plural of a given singular/plural unit name (in English). + * @method Carbon|false rawCreateFromFormat($format, $time, $tz = null) Create a Carbon instance from a specific format. + * @method Carbon rawParse($time = null, $tz = null) Create a carbon instance from a string. + * This is an alias for the constructor that allows better fluent syntax + * as it allows you to do Carbon::parse('Monday next week')->fn() rather + * than (new Carbon('Monday next week'))->fn(). + * @method Carbon resetMacros() Remove all macros and generic macros. + * @method void resetMonthsOverflow() @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addMonthsWithOverflow/addMonthsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @method void resetToStringFormat() Reset the format used to the default when type juggling a Carbon instance to a string + * @method void resetYearsOverflow() @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addYearsWithOverflow/addYearsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @method void serializeUsing($callback) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather transform Carbon object before the serialization. + * JSON serialize all Carbon instances using the given callback. + * @method Carbon setFallbackLocale($locale) Set the fallback locale. + * @method Carbon setHumanDiffOptions($humanDiffOptions) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @method bool setLocale($locale) Set the current translator locale and indicate if the source locale file exists. + * Pass 'auto' as locale to use closest language from the current LC_TIME locale. + * @method void setMidDayAt($hour) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather consider mid-day is always 12pm, then if you need to test if it's an other + * hour, test it explicitly: + * $date->format('G') == 13 + * or to set explicitly to a given hour: + * $date->setTime(13, 0, 0, 0) + * Set midday/noon hour + * @method Carbon setTestNow($testNow = null) Set a Carbon instance (real or mock) to be returned when a "now" + * instance is created. The provided instance will be returned + * specifically under the following conditions: + * - A call to the static now() method, ex. Carbon::now() + * - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null) + * - When the string "now" is passed to the constructor or parse(), ex. new Carbon('now') + * - When a string containing the desired time is passed to Carbon::parse(). + * Note the timezone parameter was left out of the examples above and + * has no affect as the mock value will be returned regardless of its value. + * Only the moment is mocked with setTestNow(), the timezone will still be the one passed + * as parameter of date_default_timezone_get() as a fallback (see setTestNowAndTimezone()). + * To clear the test instance call this method using the default + * parameter of null. + * /!\ Use this method for unit tests only. + * @method Carbon setTestNowAndTimezone($testNow = null, $tz = null) Set a Carbon instance (real or mock) to be returned when a "now" + * instance is created. The provided instance will be returned + * specifically under the following conditions: + * - A call to the static now() method, ex. Carbon::now() + * - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null) + * - When the string "now" is passed to the constructor or parse(), ex. new Carbon('now') + * - When a string containing the desired time is passed to Carbon::parse(). + * It will also align default timezone (e.g. call date_default_timezone_set()) with + * the second argument or if null, with the timezone of the given date object. + * To clear the test instance call this method using the default + * parameter of null. + * /!\ Use this method for unit tests only. + * @method void setToStringFormat($format) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather let Carbon object being casted to string with DEFAULT_TO_STRING_FORMAT, and + * use other method or custom format passed to format() method if you need to dump an other string + * format. + * Set the default format used when type juggling a Carbon instance to a string + * @method void setTranslator(TranslatorInterface $translator) Set the default translator instance to use. + * @method Carbon setUtf8($utf8) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use UTF-8 language packages on every machine. + * Set if UTF8 will be used for localized date/time. + * @method void setWeekEndsAt($day) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * Use $weekStartsAt optional parameter instead when using startOfWeek, floorWeek, ceilWeek + * or roundWeek method. You can also use the 'first_day_of_week' locale setting to change the + * start of week according to current locale selected and implicitly the end of week. + * Set the last day of week + * @method void setWeekStartsAt($day) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * Use $weekEndsAt optional parameter instead when using endOfWeek method. You can also use the + * 'first_day_of_week' locale setting to change the start of week according to current locale + * selected and implicitly the end of week. + * Set the first day of week + * @method void setWeekendDays($days) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather consider week-end is always saturday and sunday, and if you have some custom + * week-end days to handle, give to those days an other name and create a macro for them: + * ``` + * Carbon::macro('isDayOff', function ($date) { + * return $date->isSunday() || $date->isMonday(); + * }); + * Carbon::macro('isNotDayOff', function ($date) { + * return !$date->isDayOff(); + * }); + * if ($someDate->isDayOff()) ... + * if ($someDate->isNotDayOff()) ... + * // Add 5 not-off days + * $count = 5; + * while ($someDate->isDayOff() || ($count-- > 0)) { + * $someDate->addDay(); + * } + * ``` + * Set weekend days + * @method bool shouldOverflowMonths() Get the month overflow global behavior (can be overridden in specific instances). + * @method bool shouldOverflowYears() Get the month overflow global behavior (can be overridden in specific instances). + * @method string singularUnit(string $unit) Returns standardized singular of a given singular/plural unit name (in English). + * @method Carbon today($tz = null) Create a Carbon instance for today. + * @method Carbon tomorrow($tz = null) Create a Carbon instance for tomorrow. + * @method string translateTimeString($timeString, $from = null, $to = null, $mode = CarbonInterface::TRANSLATE_ALL) Translate a time string from a locale to an other. + * @method string translateWith(TranslatorInterface $translator, string $key, array $parameters = [], $number = null) Translate using translation string or callback available. + * @method void useMonthsOverflow($monthsOverflow = true) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addMonthsWithOverflow/addMonthsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @method Carbon useStrictMode($strictModeEnabled = true) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @method void useYearsOverflow($yearsOverflow = true) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addYearsWithOverflow/addYearsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @method mixed withTestNow($testNow = null, $callback = null) Temporarily sets a static date to be used within the callback. + * Using setTestNow to set the date, executing the callback, then + * clearing the test instance. + * /!\ Use this method for unit tests only. + * @method Carbon yesterday($tz = null) Create a Carbon instance for yesterday. + * + * + */ +class Factory +{ + protected $className = Carbon::class; + + protected $settings = []; + + public function __construct(array $settings = [], ?string $className = null) + { + if ($className) { + $this->className = $className; + } + + $this->settings = $settings; + } + + public function getClassName() + { + return $this->className; + } + + public function setClassName(string $className) + { + $this->className = $className; + + return $this; + } + + public function className(string $className = null) + { + return $className === null ? $this->getClassName() : $this->setClassName($className); + } + + public function getSettings() + { + return $this->settings; + } + + public function setSettings(array $settings) + { + $this->settings = $settings; + + return $this; + } + + public function settings(array $settings = null) + { + return $settings === null ? $this->getSettings() : $this->setSettings($settings); + } + + public function mergeSettings(array $settings) + { + $this->settings = array_merge($this->settings, $settings); + + return $this; + } + + public function __call($name, $arguments) + { + $method = new ReflectionMethod($this->className, $name); + $settings = $this->settings; + + if ($settings && isset($settings['timezone'])) { + $tzParameters = array_filter($method->getParameters(), function ($parameter) { + return \in_array($parameter->getName(), ['tz', 'timezone'], true); + }); + + if (isset($arguments[0]) && \in_array($name, ['instance', 'make', 'create', 'parse'], true)) { + if ($arguments[0] instanceof DateTimeInterface) { + $settings['innerTimezone'] = $settings['timezone']; + } elseif (\is_string($arguments[0]) && date_parse($arguments[0])['is_localtime']) { + unset($settings['timezone'], $settings['innerTimezone']); + } + } elseif (\count($tzParameters)) { + array_splice($arguments, key($tzParameters), 0, [$settings['timezone']]); + unset($settings['timezone']); + } + } + + $result = $this->className::$name(...$arguments); + + return $result instanceof CarbonInterface && !empty($settings) + ? $result->settings($settings) + : $result; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/FactoryImmutable.php b/vendor/nesbot/carbon/src/Carbon/FactoryImmutable.php new file mode 100644 index 0000000..596ee80 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/FactoryImmutable.php @@ -0,0 +1,243 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use Closure; + +/** + * A factory to generate CarbonImmutable instances with common settings. + * + * + * + * @method bool canBeCreatedFromFormat($date, $format) Checks if the (date)time string is in a given format and valid to create a + * new instance. + * @method CarbonImmutable|false create($year = 0, $month = 1, $day = 1, $hour = 0, $minute = 0, $second = 0, $tz = null) Create a new Carbon instance from a specific date and time. + * If any of $year, $month or $day are set to null their now() values will + * be used. + * If $hour is null it will be set to its now() value and the default + * values for $minute and $second will be their now() values. + * If $hour is not null then the default values for $minute and $second + * will be 0. + * @method CarbonImmutable createFromDate($year = null, $month = null, $day = null, $tz = null) Create a Carbon instance from just a date. The time portion is set to now. + * @method CarbonImmutable|false createFromFormat($format, $time, $tz = null) Create a Carbon instance from a specific format. + * @method CarbonImmutable|false createFromIsoFormat($format, $time, $tz = null, $locale = 'en', $translator = null) Create a Carbon instance from a specific ISO format (same replacements as ->isoFormat()). + * @method CarbonImmutable|false createFromLocaleFormat($format, $locale, $time, $tz = null) Create a Carbon instance from a specific format and a string in a given language. + * @method CarbonImmutable|false createFromLocaleIsoFormat($format, $locale, $time, $tz = null) Create a Carbon instance from a specific ISO format and a string in a given language. + * @method CarbonImmutable createFromTime($hour = 0, $minute = 0, $second = 0, $tz = null) Create a Carbon instance from just a time. The date portion is set to today. + * @method CarbonImmutable createFromTimeString($time, $tz = null) Create a Carbon instance from a time string. The date portion is set to today. + * @method CarbonImmutable createFromTimestamp($timestamp, $tz = null) Create a Carbon instance from a timestamp and set the timezone (use default one if not specified). + * Timestamp input can be given as int, float or a string containing one or more numbers. + * @method CarbonImmutable createFromTimestampMs($timestamp, $tz = null) Create a Carbon instance from a timestamp in milliseconds. + * Timestamp input can be given as int, float or a string containing one or more numbers. + * @method CarbonImmutable createFromTimestampMsUTC($timestamp) Create a Carbon instance from a timestamp in milliseconds. + * Timestamp input can be given as int, float or a string containing one or more numbers. + * @method CarbonImmutable createFromTimestampUTC($timestamp) Create a Carbon instance from an timestamp keeping the timezone to UTC. + * Timestamp input can be given as int, float or a string containing one or more numbers. + * @method CarbonImmutable createMidnightDate($year = null, $month = null, $day = null, $tz = null) Create a Carbon instance from just a date. The time portion is set to midnight. + * @method CarbonImmutable|false createSafe($year = null, $month = null, $day = null, $hour = null, $minute = null, $second = null, $tz = null) Create a new safe Carbon instance from a specific date and time. + * If any of $year, $month or $day are set to null their now() values will + * be used. + * If $hour is null it will be set to its now() value and the default + * values for $minute and $second will be their now() values. + * If $hour is not null then the default values for $minute and $second + * will be 0. + * If one of the set values is not valid, an InvalidDateException + * will be thrown. + * @method CarbonInterface createStrict(?int $year = 0, ?int $month = 1, ?int $day = 1, ?int $hour = 0, ?int $minute = 0, ?int $second = 0, $tz = null) Create a new Carbon instance from a specific date and time using strict validation. + * @method CarbonImmutable disableHumanDiffOption($humanDiffOption) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @method CarbonImmutable enableHumanDiffOption($humanDiffOption) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @method mixed executeWithLocale($locale, $func) Set the current locale to the given, execute the passed function, reset the locale to previous one, + * then return the result of the closure (or null if the closure was void). + * @method CarbonImmutable fromSerialized($value) Create an instance from a serialized string. + * @method void genericMacro($macro, $priority = 0) Register a custom macro. + * @method array getAvailableLocales() Returns the list of internally available locales and already loaded custom locales. + * (It will ignore custom translator dynamic loading.) + * @method Language[] getAvailableLocalesInfo() Returns list of Language object for each available locale. This object allow you to get the ISO name, native + * name, region and variant of the locale. + * @method array getDays() Get the days of the week + * @method string|null getFallbackLocale() Get the fallback locale. + * @method array getFormatsToIsoReplacements() List of replacements from date() format to isoFormat(). + * @method int getHumanDiffOptions() Return default humanDiff() options (merged flags as integer). + * @method array getIsoUnits() Returns list of locale units for ISO formatting. + * @method array getLastErrors() {@inheritdoc} + * @method string getLocale() Get the current translator locale. + * @method callable|null getMacro($name) Get the raw callable macro registered globally for a given name. + * @method int getMidDayAt() get midday/noon hour + * @method Closure|CarbonImmutable getTestNow() Get the Carbon instance (real or mock) to be returned when a "now" + * instance is created. + * @method string getTimeFormatByPrecision($unitPrecision) Return a format from H:i to H:i:s.u according to given unit precision. + * @method string getTranslationMessageWith($translator, string $key, ?string $locale = null, ?string $default = null) Returns raw translation message for a given key. + * @method \Symfony\Component\Translation\TranslatorInterface getTranslator() Get the default translator instance in use. + * @method int getWeekEndsAt() Get the last day of week + * @method int getWeekStartsAt() Get the first day of week + * @method array getWeekendDays() Get weekend days + * @method bool hasFormat($date, $format) Checks if the (date)time string is in a given format. + * @method bool hasFormatWithModifiers($date, $format) Checks if the (date)time string is in a given format. + * @method bool hasMacro($name) Checks if macro is registered globally. + * @method bool hasRelativeKeywords($time) Determine if a time string will produce a relative date. + * @method bool hasTestNow() Determine if there is a valid test instance set. A valid test instance + * is anything that is not null. + * @method CarbonImmutable instance($date) Create a Carbon instance from a DateTime one. + * @method bool isImmutable() Returns true if the current class/instance is immutable. + * @method bool isModifiableUnit($unit) Returns true if a property can be changed via setter. + * @method bool isMutable() Returns true if the current class/instance is mutable. + * @method bool isStrictModeEnabled() Returns true if the strict mode is globally in use, false else. + * (It can be overridden in specific instances.) + * @method bool localeHasDiffOneDayWords($locale) Returns true if the given locale is internally supported and has words for 1-day diff (just now, yesterday, tomorrow). + * Support is considered enabled if the 3 words are translated in the given locale. + * @method bool localeHasDiffSyntax($locale) Returns true if the given locale is internally supported and has diff syntax support (ago, from now, before, after). + * Support is considered enabled if the 4 sentences are translated in the given locale. + * @method bool localeHasDiffTwoDayWords($locale) Returns true if the given locale is internally supported and has words for 2-days diff (before yesterday, after tomorrow). + * Support is considered enabled if the 2 words are translated in the given locale. + * @method bool localeHasPeriodSyntax($locale) Returns true if the given locale is internally supported and has period syntax support (X times, every X, from X, to X). + * Support is considered enabled if the 4 sentences are translated in the given locale. + * @method bool localeHasShortUnits($locale) Returns true if the given locale is internally supported and has short-units support. + * Support is considered enabled if either year, day or hour has a short variant translated. + * @method void macro($name, $macro) Register a custom macro. + * @method CarbonImmutable|null make($var) Make a Carbon instance from given variable if possible. + * Always return a new instance. Parse only strings and only these likely to be dates (skip intervals + * and recurrences). Throw an exception for invalid format, but otherwise return null. + * @method CarbonImmutable maxValue() Create a Carbon instance for the greatest supported date. + * @method CarbonImmutable minValue() Create a Carbon instance for the lowest supported date. + * @method void mixin($mixin) Mix another object into the class. + * @method CarbonImmutable now($tz = null) Get a Carbon instance for the current date and time. + * @method CarbonImmutable parse($time = null, $tz = null) Create a carbon instance from a string. + * This is an alias for the constructor that allows better fluent syntax + * as it allows you to do Carbon::parse('Monday next week')->fn() rather + * than (new Carbon('Monday next week'))->fn(). + * @method CarbonImmutable parseFromLocale($time, $locale = null, $tz = null) Create a carbon instance from a localized string (in French, Japanese, Arabic, etc.). + * @method string pluralUnit(string $unit) Returns standardized plural of a given singular/plural unit name (in English). + * @method CarbonImmutable|false rawCreateFromFormat($format, $time, $tz = null) Create a Carbon instance from a specific format. + * @method CarbonImmutable rawParse($time = null, $tz = null) Create a carbon instance from a string. + * This is an alias for the constructor that allows better fluent syntax + * as it allows you to do Carbon::parse('Monday next week')->fn() rather + * than (new Carbon('Monday next week'))->fn(). + * @method CarbonImmutable resetMacros() Remove all macros and generic macros. + * @method void resetMonthsOverflow() @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addMonthsWithOverflow/addMonthsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @method void resetToStringFormat() Reset the format used to the default when type juggling a Carbon instance to a string + * @method void resetYearsOverflow() @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addYearsWithOverflow/addYearsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @method void serializeUsing($callback) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather transform Carbon object before the serialization. + * JSON serialize all Carbon instances using the given callback. + * @method CarbonImmutable setFallbackLocale($locale) Set the fallback locale. + * @method CarbonImmutable setHumanDiffOptions($humanDiffOptions) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @method bool setLocale($locale) Set the current translator locale and indicate if the source locale file exists. + * Pass 'auto' as locale to use closest language from the current LC_TIME locale. + * @method void setMidDayAt($hour) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather consider mid-day is always 12pm, then if you need to test if it's an other + * hour, test it explicitly: + * $date->format('G') == 13 + * or to set explicitly to a given hour: + * $date->setTime(13, 0, 0, 0) + * Set midday/noon hour + * @method CarbonImmutable setTestNow($testNow = null) Set a Carbon instance (real or mock) to be returned when a "now" + * instance is created. The provided instance will be returned + * specifically under the following conditions: + * - A call to the static now() method, ex. Carbon::now() + * - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null) + * - When the string "now" is passed to the constructor or parse(), ex. new Carbon('now') + * - When a string containing the desired time is passed to Carbon::parse(). + * Note the timezone parameter was left out of the examples above and + * has no affect as the mock value will be returned regardless of its value. + * Only the moment is mocked with setTestNow(), the timezone will still be the one passed + * as parameter of date_default_timezone_get() as a fallback (see setTestNowAndTimezone()). + * To clear the test instance call this method using the default + * parameter of null. + * /!\ Use this method for unit tests only. + * @method CarbonImmutable setTestNowAndTimezone($testNow = null, $tz = null) Set a Carbon instance (real or mock) to be returned when a "now" + * instance is created. The provided instance will be returned + * specifically under the following conditions: + * - A call to the static now() method, ex. Carbon::now() + * - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null) + * - When the string "now" is passed to the constructor or parse(), ex. new Carbon('now') + * - When a string containing the desired time is passed to Carbon::parse(). + * It will also align default timezone (e.g. call date_default_timezone_set()) with + * the second argument or if null, with the timezone of the given date object. + * To clear the test instance call this method using the default + * parameter of null. + * /!\ Use this method for unit tests only. + * @method void setToStringFormat($format) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather let Carbon object being casted to string with DEFAULT_TO_STRING_FORMAT, and + * use other method or custom format passed to format() method if you need to dump an other string + * format. + * Set the default format used when type juggling a Carbon instance to a string + * @method void setTranslator(TranslatorInterface $translator) Set the default translator instance to use. + * @method CarbonImmutable setUtf8($utf8) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use UTF-8 language packages on every machine. + * Set if UTF8 will be used for localized date/time. + * @method void setWeekEndsAt($day) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * Use $weekStartsAt optional parameter instead when using startOfWeek, floorWeek, ceilWeek + * or roundWeek method. You can also use the 'first_day_of_week' locale setting to change the + * start of week according to current locale selected and implicitly the end of week. + * Set the last day of week + * @method void setWeekStartsAt($day) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * Use $weekEndsAt optional parameter instead when using endOfWeek method. You can also use the + * 'first_day_of_week' locale setting to change the start of week according to current locale + * selected and implicitly the end of week. + * Set the first day of week + * @method void setWeekendDays($days) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather consider week-end is always saturday and sunday, and if you have some custom + * week-end days to handle, give to those days an other name and create a macro for them: + * ``` + * Carbon::macro('isDayOff', function ($date) { + * return $date->isSunday() || $date->isMonday(); + * }); + * Carbon::macro('isNotDayOff', function ($date) { + * return !$date->isDayOff(); + * }); + * if ($someDate->isDayOff()) ... + * if ($someDate->isNotDayOff()) ... + * // Add 5 not-off days + * $count = 5; + * while ($someDate->isDayOff() || ($count-- > 0)) { + * $someDate->addDay(); + * } + * ``` + * Set weekend days + * @method bool shouldOverflowMonths() Get the month overflow global behavior (can be overridden in specific instances). + * @method bool shouldOverflowYears() Get the month overflow global behavior (can be overridden in specific instances). + * @method string singularUnit(string $unit) Returns standardized singular of a given singular/plural unit name (in English). + * @method CarbonImmutable today($tz = null) Create a Carbon instance for today. + * @method CarbonImmutable tomorrow($tz = null) Create a Carbon instance for tomorrow. + * @method string translateTimeString($timeString, $from = null, $to = null, $mode = CarbonInterface::TRANSLATE_ALL) Translate a time string from a locale to an other. + * @method string translateWith(TranslatorInterface $translator, string $key, array $parameters = [], $number = null) Translate using translation string or callback available. + * @method void useMonthsOverflow($monthsOverflow = true) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addMonthsWithOverflow/addMonthsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @method CarbonImmutable useStrictMode($strictModeEnabled = true) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @method void useYearsOverflow($yearsOverflow = true) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addYearsWithOverflow/addYearsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @method mixed withTestNow($testNow = null, $callback = null) Temporarily sets a static date to be used within the callback. + * Using setTestNow to set the date, executing the callback, then + * clearing the test instance. + * /!\ Use this method for unit tests only. + * @method CarbonImmutable yesterday($tz = null) Create a Carbon instance for yesterday. + * + * + */ +class FactoryImmutable extends Factory +{ + protected $className = CarbonImmutable::class; +} diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/aa.php b/vendor/nesbot/carbon/src/Carbon/Lang/aa.php new file mode 100644 index 0000000..f3431e4 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/aa.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/aa_DJ.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/aa_DJ.php b/vendor/nesbot/carbon/src/Carbon/Lang/aa_DJ.php new file mode 100644 index 0000000..c6e23c0 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/aa_DJ.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['Qunxa Garablu', 'Kudo', 'Ciggilta Kudo', 'Agda Baxisso', 'Caxah Alsa', 'Qasa Dirri', 'Qado Dirri', 'Liiqen', 'Waysu', 'Diteli', 'Ximoli', 'Kaxxa Garablu'], + 'months_short' => ['qun', 'nah', 'cig', 'agd', 'cax', 'qas', 'qad', 'leq', 'way', 'dit', 'xim', 'kax'], + 'weekdays' => ['Acaada', 'Etleeni', 'Talaata', 'Arbaqa', 'Kamiisi', 'Gumqata', 'Sabti'], + 'weekdays_short' => ['aca', 'etl', 'tal', 'arb', 'kam', 'gum', 'sab'], + 'weekdays_min' => ['aca', 'etl', 'tal', 'arb', 'kam', 'gum', 'sab'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['saaku', 'carra'], + + 'year' => ':count gaqambo', // less reliable + 'y' => ':count gaqambo', // less reliable + 'a_year' => ':count gaqambo', // less reliable + + 'month' => ':count àlsa', + 'm' => ':count àlsa', + 'a_month' => ':count àlsa', + + 'day' => ':count saaku', // less reliable + 'd' => ':count saaku', // less reliable + 'a_day' => ':count saaku', // less reliable + + 'hour' => ':count ayti', // less reliable + 'h' => ':count ayti', // less reliable + 'a_hour' => ':count ayti', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/aa_ER.php b/vendor/nesbot/carbon/src/Carbon/Lang/aa_ER.php new file mode 100644 index 0000000..f8f395b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/aa_ER.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Qunxa Garablu', 'Naharsi Kudo', 'Ciggilta Kudo', 'Agda Baxisso', 'Caxah Alsa', 'Qasa Dirri', 'Qado Dirri', 'Leqeeni', 'Waysu', 'Diteli', 'Ximoli', 'Kaxxa Garablu'], + 'months_short' => ['Qun', 'Nah', 'Cig', 'Agd', 'Cax', 'Qas', 'Qad', 'Leq', 'Way', 'Dit', 'Xim', 'Kax'], + 'weekdays' => ['Acaada', 'Etleeni', 'Talaata', 'Arbaqa', 'Kamiisi', 'Gumqata', 'Sabti'], + 'weekdays_short' => ['Aca', 'Etl', 'Tal', 'Arb', 'Kam', 'Gum', 'Sab'], + 'weekdays_min' => ['Aca', 'Etl', 'Tal', 'Arb', 'Kam', 'Gum', 'Sab'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['saaku', 'carra'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/aa_ER@saaho.php b/vendor/nesbot/carbon/src/Carbon/Lang/aa_ER@saaho.php new file mode 100644 index 0000000..6461225 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/aa_ER@saaho.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Qunxa Garablu', 'Naharsi Kudo', 'Ciggilta Kudo', 'Agda Baxisso', 'Caxah Alsa', 'Qasa Dirri', 'Qado Dirri', 'Leqeeni', 'Waysu', 'Diteli', 'Ximoli', 'Kaxxa Garablu'], + 'months_short' => ['Qun', 'Nah', 'Cig', 'Agd', 'Cax', 'Qas', 'Qad', 'Leq', 'Way', 'Dit', 'Xim', 'Kax'], + 'weekdays' => ['Naba Sambat', 'Sani', 'Salus', 'Rabuq', 'Camus', 'Jumqata', 'Qunxa Sambat'], + 'weekdays_short' => ['Nab', 'San', 'Sal', 'Rab', 'Cam', 'Jum', 'Qun'], + 'weekdays_min' => ['Nab', 'San', 'Sal', 'Rab', 'Cam', 'Jum', 'Qun'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['saaku', 'carra'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/aa_ET.php b/vendor/nesbot/carbon/src/Carbon/Lang/aa_ET.php new file mode 100644 index 0000000..e55e591 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/aa_ET.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Qunxa Garablu', 'Kudo', 'Ciggilta Kudo', 'Agda Baxisso', 'Caxah Alsa', 'Qasa Dirri', 'Qado Dirri', 'Liiqen', 'Waysu', 'Diteli', 'Ximoli', 'Kaxxa Garablu'], + 'months_short' => ['Qun', 'Kud', 'Cig', 'Agd', 'Cax', 'Qas', 'Qad', 'Leq', 'Way', 'Dit', 'Xim', 'Kax'], + 'weekdays' => ['Acaada', 'Etleeni', 'Talaata', 'Arbaqa', 'Kamiisi', 'Gumqata', 'Sabti'], + 'weekdays_short' => ['Aca', 'Etl', 'Tal', 'Arb', 'Kam', 'Gum', 'Sab'], + 'weekdays_min' => ['Aca', 'Etl', 'Tal', 'Arb', 'Kam', 'Gum', 'Sab'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['saaku', 'carra'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/af.php b/vendor/nesbot/carbon/src/Carbon/Lang/af.php new file mode 100644 index 0000000..27771d7 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/af.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - JD Isaacks + * - Pierre du Plessis + */ +return [ + 'year' => ':count jaar', + 'a_year' => '\'n jaar|:count jaar', + 'y' => ':count j.', + 'month' => ':count maand|:count maande', + 'a_month' => '\'n maand|:count maande', + 'm' => ':count maa.', + 'week' => ':count week|:count weke', + 'a_week' => '\'n week|:count weke', + 'w' => ':count w.', + 'day' => ':count dag|:count dae', + 'a_day' => '\'n dag|:count dae', + 'd' => ':count d.', + 'hour' => ':count uur', + 'a_hour' => '\'n uur|:count uur', + 'h' => ':count u.', + 'minute' => ':count minuut|:count minute', + 'a_minute' => '\'n minuut|:count minute', + 'min' => ':count min.', + 'second' => ':count sekond|:count sekondes', + 'a_second' => '\'n paar sekondes|:count sekondes', + 's' => ':count s.', + 'ago' => ':time gelede', + 'from_now' => 'oor :time', + 'after' => ':time na', + 'before' => ':time voor', + 'diff_now' => 'Nou', + 'diff_today' => 'Vandag', + 'diff_today_regexp' => 'Vandag(?:\\s+om)?', + 'diff_yesterday' => 'Gister', + 'diff_yesterday_regexp' => 'Gister(?:\\s+om)?', + 'diff_tomorrow' => 'Môre', + 'diff_tomorrow_regexp' => 'Môre(?:\\s+om)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Vandag om] LT', + 'nextDay' => '[Môre om] LT', + 'nextWeek' => 'dddd [om] LT', + 'lastDay' => '[Gister om] LT', + 'lastWeek' => '[Laas] dddd [om] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + return $number.(($number === 1 || $number === 8 || $number >= 20) ? 'ste' : 'de'); + }, + 'meridiem' => ['VM', 'NM'], + 'months' => ['Januarie', 'Februarie', 'Maart', 'April', 'Mei', 'Junie', 'Julie', 'Augustus', 'September', 'Oktober', 'November', 'Desember'], + 'months_short' => ['Jan', 'Feb', 'Mrt', 'Apr', 'Mei', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'], + 'weekdays' => ['Sondag', 'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrydag', 'Saterdag'], + 'weekdays_short' => ['Son', 'Maa', 'Din', 'Woe', 'Don', 'Vry', 'Sat'], + 'weekdays_min' => ['So', 'Ma', 'Di', 'Wo', 'Do', 'Vr', 'Sa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' en '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/af_NA.php b/vendor/nesbot/carbon/src/Carbon/Lang/af_NA.php new file mode 100644 index 0000000..f2fcf05 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/af_NA.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/af.php', [ + 'meridiem' => ['v', 'n'], + 'weekdays' => ['Sondag', 'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrydag', 'Saterdag'], + 'weekdays_short' => ['So.', 'Ma.', 'Di.', 'Wo.', 'Do.', 'Vr.', 'Sa.'], + 'weekdays_min' => ['So.', 'Ma.', 'Di.', 'Wo.', 'Do.', 'Vr.', 'Sa.'], + 'months' => ['Januarie', 'Februarie', 'Maart', 'April', 'Mei', 'Junie', 'Julie', 'Augustus', 'September', 'Oktober', 'November', 'Desember'], + 'months_short' => ['Jan.', 'Feb.', 'Mrt.', 'Apr.', 'Mei', 'Jun.', 'Jul.', 'Aug.', 'Sep.', 'Okt.', 'Nov.', 'Des.'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-DD', + 'LL' => 'DD MMM YYYY', + 'LLL' => 'DD MMMM YYYY HH:mm', + 'LLLL' => 'dddd, DD MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/af_ZA.php b/vendor/nesbot/carbon/src/Carbon/Lang/af_ZA.php new file mode 100644 index 0000000..27896bd --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/af_ZA.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/af.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/agq.php b/vendor/nesbot/carbon/src/Carbon/Lang/agq.php new file mode 100644 index 0000000..7011464 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/agq.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['a.g', 'a.k'], + 'weekdays' => ['tsuʔntsɨ', 'tsuʔukpà', 'tsuʔughɔe', 'tsuʔutɔ̀mlò', 'tsuʔumè', 'tsuʔughɨ̂m', 'tsuʔndzɨkɔʔɔ'], + 'weekdays_short' => ['nts', 'kpa', 'ghɔ', 'tɔm', 'ume', 'ghɨ', 'dzk'], + 'weekdays_min' => ['nts', 'kpa', 'ghɔ', 'tɔm', 'ume', 'ghɨ', 'dzk'], + 'months' => ['ndzɔ̀ŋɔ̀nùm', 'ndzɔ̀ŋɔ̀kƗ̀zùʔ', 'ndzɔ̀ŋɔ̀tƗ̀dʉ̀ghà', 'ndzɔ̀ŋɔ̀tǎafʉ̄ghā', 'ndzɔ̀ŋèsèe', 'ndzɔ̀ŋɔ̀nzùghò', 'ndzɔ̀ŋɔ̀dùmlo', 'ndzɔ̀ŋɔ̀kwîfɔ̀e', 'ndzɔ̀ŋɔ̀tƗ̀fʉ̀ghàdzughù', 'ndzɔ̀ŋɔ̀ghǔuwelɔ̀m', 'ndzɔ̀ŋɔ̀chwaʔàkaa wo', 'ndzɔ̀ŋèfwòo'], + 'months_short' => ['nùm', 'kɨz', 'tɨd', 'taa', 'see', 'nzu', 'dum', 'fɔe', 'dzu', 'lɔm', 'kaa', 'fwo'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/agr.php b/vendor/nesbot/carbon/src/Carbon/Lang/agr.php new file mode 100644 index 0000000..8f036ae --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/agr.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/agr_PE.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/agr_PE.php b/vendor/nesbot/carbon/src/Carbon/Lang/agr_PE.php new file mode 100644 index 0000000..54a326a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/agr_PE.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - somosazucar.org libc-alpha@sourceware.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['Petsatin', 'Kupitin', 'Uyaitin', 'Tayutin', 'Kegketin', 'Tegmatin', 'Kuntutin', 'Yagkujutin', 'Daiktatin', 'Ipamtatin', 'Shinutin', 'Sakamtin'], + 'months_short' => ['Pet', 'Kup', 'Uya', 'Tay', 'Keg', 'Teg', 'Kun', 'Yag', 'Dait', 'Ipam', 'Shin', 'Sak'], + 'weekdays' => ['Tuntuamtin', 'Achutin', 'Kugkuktin', 'Saketin', 'Shimpitin', 'Imaptin', 'Bataetin'], + 'weekdays_short' => ['Tun', 'Ach', 'Kug', 'Sak', 'Shim', 'Im', 'Bat'], + 'weekdays_min' => ['Tun', 'Ach', 'Kug', 'Sak', 'Shim', 'Im', 'Bat'], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 7, + 'meridiem' => ['VM', 'NM'], + + 'year' => ':count yaya', // less reliable + 'y' => ':count yaya', // less reliable + 'a_year' => ':count yaya', // less reliable + + 'month' => ':count nantu', // less reliable + 'm' => ':count nantu', // less reliable + 'a_month' => ':count nantu', // less reliable + + 'day' => ':count nayaim', // less reliable + 'd' => ':count nayaim', // less reliable + 'a_day' => ':count nayaim', // less reliable + + 'hour' => ':count kuwiš', // less reliable + 'h' => ':count kuwiš', // less reliable + 'a_hour' => ':count kuwiš', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ak.php b/vendor/nesbot/carbon/src/Carbon/Lang/ak.php new file mode 100644 index 0000000..5a64be3 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ak.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ak_GH.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ak_GH.php b/vendor/nesbot/carbon/src/Carbon/Lang/ak_GH.php new file mode 100644 index 0000000..1381946 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ak_GH.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Sugar Labs // OLPC sugarlabs.org libc-alpha@sourceware.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYY/MM/DD', + ], + 'months' => ['Sanda-Ɔpɛpɔn', 'Kwakwar-Ɔgyefuo', 'Ebɔw-Ɔbenem', 'Ebɔbira-Oforisuo', 'Esusow Aketseaba-Kɔtɔnimba', 'Obirade-Ayɛwohomumu', 'Ayɛwoho-Kitawonsa', 'Difuu-Ɔsandaa', 'Fankwa-Ɛbɔ', 'Ɔbɛsɛ-Ahinime', 'Ɔberɛfɛw-Obubuo', 'Mumu-Ɔpɛnimba'], + 'months_short' => ['S-Ɔ', 'K-Ɔ', 'E-Ɔ', 'E-O', 'E-K', 'O-A', 'A-K', 'D-Ɔ', 'F-Ɛ', 'Ɔ-A', 'Ɔ-O', 'M-Ɔ'], + 'weekdays' => ['Kwesida', 'Dwowda', 'Benada', 'Wukuda', 'Yawda', 'Fida', 'Memeneda'], + 'weekdays_short' => ['Kwe', 'Dwo', 'Ben', 'Wuk', 'Yaw', 'Fia', 'Mem'], + 'weekdays_min' => ['Kwe', 'Dwo', 'Ben', 'Wuk', 'Yaw', 'Fia', 'Mem'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['AN', 'EW'], + + 'year' => ':count afe', + 'y' => ':count afe', + 'a_year' => ':count afe', + + 'month' => ':count bosume', + 'm' => ':count bosume', + 'a_month' => ':count bosume', + + 'day' => ':count ɛda', + 'd' => ':count ɛda', + 'a_day' => ':count ɛda', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/am.php b/vendor/nesbot/carbon/src/Carbon/Lang/am.php new file mode 100644 index 0000000..63bf72d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/am.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/am_ET.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/am_ET.php b/vendor/nesbot/carbon/src/Carbon/Lang/am_ET.php new file mode 100644 index 0000000..ece8062 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/am_ET.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['ጃንዩወሪ', 'ፌብሩወሪ', 'ማርች', 'ኤፕሪል', 'ሜይ', 'ጁን', 'ጁላይ', 'ኦገስት', 'ሴፕቴምበር', 'ኦክቶበር', 'ኖቬምበር', 'ዲሴምበር'], + 'months_short' => ['ጃንዩ', 'ፌብሩ', 'ማርች', 'ኤፕረ', 'ሜይ ', 'ጁን ', 'ጁላይ', 'ኦገስ', 'ሴፕቴ', 'ኦክተ', 'ኖቬም', 'ዲሴም'], + 'weekdays' => ['እሑድ', 'ሰኞ', 'ማክሰኞ', 'ረቡዕ', 'ሐሙስ', 'ዓርብ', 'ቅዳሜ'], + 'weekdays_short' => ['እሑድ', 'ሰኞ ', 'ማክሰ', 'ረቡዕ', 'ሐሙስ', 'ዓርብ', 'ቅዳሜ'], + 'weekdays_min' => ['እሑድ', 'ሰኞ ', 'ማክሰ', 'ረቡዕ', 'ሐሙስ', 'ዓርብ', 'ቅዳሜ'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ጡዋት', 'ከሰዓት'], + + 'year' => ':count አመት', + 'y' => ':count አመት', + 'a_year' => ':count አመት', + + 'month' => ':count ወር', + 'm' => ':count ወር', + 'a_month' => ':count ወር', + + 'week' => ':count ሳምንት', + 'w' => ':count ሳምንት', + 'a_week' => ':count ሳምንት', + + 'day' => ':count ቀን', + 'd' => ':count ቀን', + 'a_day' => ':count ቀን', + + 'hour' => ':count ሰዓት', + 'h' => ':count ሰዓት', + 'a_hour' => ':count ሰዓት', + + 'minute' => ':count ደቂቃ', + 'min' => ':count ደቂቃ', + 'a_minute' => ':count ደቂቃ', + + 'second' => ':count ሴኮንድ', + 's' => ':count ሴኮንድ', + 'a_second' => ':count ሴኮንድ', + + 'ago' => 'ከ:time በፊት', + 'from_now' => 'በ:time ውስጥ', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/an.php b/vendor/nesbot/carbon/src/Carbon/Lang/an.php new file mode 100644 index 0000000..565abf2 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/an.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/an_ES.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/an_ES.php b/vendor/nesbot/carbon/src/Carbon/Lang/an_ES.php new file mode 100644 index 0000000..faf8ae0 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/an_ES.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Softaragones Jordi Mallach Pérez, Juan Pablo Martínez bug-glibc-locales@gnu.org, softaragones@softaragones.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['chinero', 'febrero', 'marzo', 'abril', 'mayo', 'chunyo', 'chuliol', 'agosto', 'setiembre', 'octubre', 'noviembre', 'aviento'], + 'months_short' => ['chi', 'feb', 'mar', 'abr', 'may', 'chn', 'chl', 'ago', 'set', 'oct', 'nov', 'avi'], + 'weekdays' => ['domingo', 'luns', 'martes', 'mierques', 'chueves', 'viernes', 'sabado'], + 'weekdays_short' => ['dom', 'lun', 'mar', 'mie', 'chu', 'vie', 'sab'], + 'weekdays_min' => ['dom', 'lun', 'mar', 'mie', 'chu', 'vie', 'sab'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'year' => ':count año', + 'y' => ':count año', + 'a_year' => ':count año', + + 'month' => ':count mes', + 'm' => ':count mes', + 'a_month' => ':count mes', + + 'week' => ':count semana', + 'w' => ':count semana', + 'a_week' => ':count semana', + + 'day' => ':count día', + 'd' => ':count día', + 'a_day' => ':count día', + + 'hour' => ':count reloch', // less reliable + 'h' => ':count reloch', // less reliable + 'a_hour' => ':count reloch', // less reliable + + 'minute' => ':count minuto', + 'min' => ':count minuto', + 'a_minute' => ':count minuto', + + 'second' => ':count segundo', + 's' => ':count segundo', + 'a_second' => ':count segundo', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/anp.php b/vendor/nesbot/carbon/src/Carbon/Lang/anp.php new file mode 100644 index 0000000..b56c67b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/anp.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/anp_IN.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/anp_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/anp_IN.php new file mode 100644 index 0000000..11069be --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/anp_IN.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bhashaghar@googlegroups.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['जनवरी', 'फरवरी', 'मार्च', 'अप्रैल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितंबर', 'अक्टूबर', 'नवंबर', 'दिसंबर"'], + 'months_short' => ['जनवरी', 'फरवरी', 'मार्च', 'अप्रैल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितंबर', 'अक्टूबर', 'नवंबर', 'दिसंबर'], + 'weekdays' => ['रविवार', 'सोमवार', 'मंगलवार', 'बुधवार', 'बृहस्पतिवार', 'शुक्रवार', 'शनिवार'], + 'weekdays_short' => ['रवि', 'सोम', 'मंगल', 'बुध', 'बृहस्पति', 'शुक्र', 'शनि'], + 'weekdays_min' => ['रवि', 'सोम', 'मंगल', 'बुध', 'बृहस्पति', 'शुक्र', 'शनि'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['पूर्वाह्न', 'अपराह्न'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar.php new file mode 100644 index 0000000..5f73f63 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Atef Ben Ali (atefBB) + * - Ibrahim AshShohail + * - MLTDev + * - Mohamed Sabil (mohamedsabil83) + * - Yazan Alnugnugh (yazan-alnugnugh) + */ +$months = [ + 'يناير', + 'فبراير', + 'مارس', + 'أبريل', + 'مايو', + 'يونيو', + 'يوليو', + 'أغسطس', + 'سبتمبر', + 'أكتوبر', + 'نوفمبر', + 'ديسمبر', +]; + +return [ + 'year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), + 'a_year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), + 'month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), + 'a_month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), + 'week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), + 'a_week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), + 'day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), + 'a_day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), + 'hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), + 'a_hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), + 'minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), + 'a_minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), + 'second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), + 'a_second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), + 'ago' => 'منذ :time', + 'from_now' => ':time من الآن', + 'after' => 'بعد :time', + 'before' => 'قبل :time', + 'diff_now' => 'الآن', + 'diff_today' => 'اليوم', + 'diff_today_regexp' => 'اليوم(?:\\s+عند)?(?:\\s+الساعة)?', + 'diff_yesterday' => 'أمس', + 'diff_yesterday_regexp' => 'أمس(?:\\s+عند)?(?:\\s+الساعة)?', + 'diff_tomorrow' => 'غداً', + 'diff_tomorrow_regexp' => 'غدًا(?:\\s+عند)?(?:\\s+الساعة)?', + 'diff_before_yesterday' => 'قبل الأمس', + 'diff_after_tomorrow' => 'بعد غد', + 'period_recurrences' => implode('|', ['{0}مرة', '{1}مرة', '{2}:count مرتين', ']2,11[:count مرات', ']10,Inf[:count مرة']), + 'period_interval' => 'كل :interval', + 'period_start_date' => 'من :date', + 'period_end_date' => 'إلى :date', + 'months' => $months, + 'months_short' => $months, + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['أحد', 'اثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة', 'سبت'], + 'weekdays_min' => ['ح', 'اث', 'ثل', 'أر', 'خم', 'ج', 'س'], + 'list' => ['، ', ' و '], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[اليوم عند الساعة] LT', + 'nextDay' => '[غدًا عند الساعة] LT', + 'nextWeek' => 'dddd [عند الساعة] LT', + 'lastDay' => '[أمس عند الساعة] LT', + 'lastWeek' => 'dddd [عند الساعة] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['ص', 'م'], + 'weekend' => [5, 6], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_AE.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_AE.php new file mode 100644 index 0000000..75fe47f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_AE.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'], + 'months_short' => ['ينا', 'فبر', 'مار', 'أبر', 'ماي', 'يون', 'يول', 'أغس', 'سبت', 'أكت', 'نوف', 'ديس'], + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت '], + 'weekdays_short' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_BH.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_BH.php new file mode 100644 index 0000000..362009e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_BH.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'], + 'months_short' => ['ينا', 'فبر', 'مار', 'أبر', 'ماي', 'يون', 'يول', 'أغس', 'سبت', 'أكت', 'نوف', 'ديس'], + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_DJ.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_DJ.php new file mode 100644 index 0000000..e790b99 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_DJ.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ar.php', [ +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_DZ.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_DZ.php new file mode 100644 index 0000000..aea4eee --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_DZ.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Authors: + * - Josh Soref + * - Noureddine LOUAHEDJ + * - JD Isaacks + * - Atef Ben Ali (atefBB) + * - Mohamed Sabil (mohamedsabil83) + */ +$months = [ + 'جانفي', + 'فيفري', + 'مارس', + 'أفريل', + 'ماي', + 'جوان', + 'جويلية', + 'أوت', + 'سبتمبر', + 'أكتوبر', + 'نوفمبر', + 'ديسمبر', +]; + +return [ + 'year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), + 'a_year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), + 'month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), + 'a_month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), + 'week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), + 'a_week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), + 'day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), + 'a_day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), + 'hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), + 'a_hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), + 'minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), + 'a_minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), + 'second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), + 'a_second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), + 'ago' => 'منذ :time', + 'from_now' => 'في :time', + 'after' => 'بعد :time', + 'before' => 'قبل :time', + 'diff_now' => 'الآن', + 'diff_today' => 'اليوم', + 'diff_today_regexp' => 'اليوم(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_yesterday' => 'أمس', + 'diff_yesterday_regexp' => 'أمس(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_tomorrow' => 'غداً', + 'diff_tomorrow_regexp' => 'غدا(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_before_yesterday' => 'قبل الأمس', + 'diff_after_tomorrow' => 'بعد غد', + 'period_recurrences' => implode('|', ['{0}مرة', '{1}مرة', '{2}:count مرتين', ']2,11[:count مرات', ']10,Inf[:count مرة']), + 'period_interval' => 'كل :interval', + 'period_start_date' => 'من :date', + 'period_end_date' => 'إلى :date', + 'months' => $months, + 'months_short' => $months, + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['أحد', 'اثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة', 'سبت'], + 'weekdays_min' => ['أح', 'إث', 'ثلا', 'أر', 'خم', 'جم', 'سب'], + 'list' => ['، ', ' و '], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 4, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[اليوم على الساعة] LT', + 'nextDay' => '[غدا على الساعة] LT', + 'nextWeek' => 'dddd [على الساعة] LT', + 'lastDay' => '[أمس على الساعة] LT', + 'lastWeek' => 'dddd [على الساعة] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['ص', 'م'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_EG.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_EG.php new file mode 100644 index 0000000..362009e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_EG.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'], + 'months_short' => ['ينا', 'فبر', 'مار', 'أبر', 'ماي', 'يون', 'يول', 'أغس', 'سبت', 'أكت', 'نوف', 'ديس'], + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_EH.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_EH.php new file mode 100644 index 0000000..e790b99 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_EH.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ar.php', [ +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_ER.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_ER.php new file mode 100644 index 0000000..e790b99 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_ER.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ar.php', [ +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_IL.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_IL.php new file mode 100644 index 0000000..e790b99 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_IL.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ar.php', [ +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_IN.php new file mode 100644 index 0000000..5fecf70 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_IN.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'], + 'months_short' => ['ينا', 'فبر', 'مار', 'أبر', 'ماي', 'يون', 'يول', 'أغس', 'سبت', 'أكت', 'نوف', 'ديس'], + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_IQ.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_IQ.php new file mode 100644 index 0000000..0ac0995 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_IQ.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['كانون الثاني', 'شباط', 'آذار', 'نيسان', 'أيار', 'حزيران', 'تموز', 'آب', 'أيلول', 'تشرين الأول', 'تشرين الثاني', 'كانون الأول'], + 'months_short' => ['كانون الثاني', 'شباط', 'آذار', 'نيسان', 'أيار', 'حزيران', 'تموز', 'آب', 'أيلول', 'تشرين الأول', 'تشرين الثاني', 'كانون الأول'], + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_JO.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_JO.php new file mode 100644 index 0000000..0ac0995 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_JO.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['كانون الثاني', 'شباط', 'آذار', 'نيسان', 'أيار', 'حزيران', 'تموز', 'آب', 'أيلول', 'تشرين الأول', 'تشرين الثاني', 'كانون الأول'], + 'months_short' => ['كانون الثاني', 'شباط', 'آذار', 'نيسان', 'أيار', 'حزيران', 'تموز', 'آب', 'أيلول', 'تشرين الأول', 'تشرين الثاني', 'كانون الأول'], + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_KM.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_KM.php new file mode 100644 index 0000000..e790b99 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_KM.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ar.php', [ +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_KW.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_KW.php new file mode 100644 index 0000000..e6f0531 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_KW.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Authors: + * - Josh Soref + * - Nusret Parlak + * - JD Isaacks + * - Atef Ben Ali (atefBB) + * - Mohamed Sabil (mohamedsabil83) + */ +$months = [ + 'يناير', + 'فبراير', + 'مارس', + 'أبريل', + 'ماي', + 'يونيو', + 'يوليوز', + 'غشت', + 'شتنبر', + 'أكتوبر', + 'نونبر', + 'دجنبر', +]; + +return [ + 'year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), + 'a_year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), + 'month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), + 'a_month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), + 'week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), + 'a_week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), + 'day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), + 'a_day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), + 'hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), + 'a_hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), + 'minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), + 'a_minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), + 'second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), + 'a_second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), + 'ago' => 'منذ :time', + 'from_now' => 'في :time', + 'after' => 'بعد :time', + 'before' => 'قبل :time', + 'diff_now' => 'الآن', + 'diff_today' => 'اليوم', + 'diff_today_regexp' => 'اليوم(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_yesterday' => 'أمس', + 'diff_yesterday_regexp' => 'أمس(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_tomorrow' => 'غداً', + 'diff_tomorrow_regexp' => 'غدا(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_before_yesterday' => 'قبل الأمس', + 'diff_after_tomorrow' => 'بعد غد', + 'period_recurrences' => implode('|', ['{0}مرة', '{1}مرة', '{2}:count مرتين', ']2,11[:count مرات', ']10,Inf[:count مرة']), + 'period_interval' => 'كل :interval', + 'period_start_date' => 'من :date', + 'period_end_date' => 'إلى :date', + 'months' => $months, + 'months_short' => $months, + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['أحد', 'اثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة', 'سبت'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'list' => ['، ', ' و '], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[اليوم على الساعة] LT', + 'nextDay' => '[غدا على الساعة] LT', + 'nextWeek' => 'dddd [على الساعة] LT', + 'lastDay' => '[أمس على الساعة] LT', + 'lastWeek' => 'dddd [على الساعة] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['ص', 'م'], + 'weekend' => [5, 6], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_LB.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_LB.php new file mode 100644 index 0000000..55bb10c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_LB.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['كانون الثاني', 'شباط', 'آذار', 'نيسان', 'أيار', 'حزيران', 'تموز', 'آب', 'أيلول', 'تشرين الأول', 'تشرين الثاني', 'كانون الأول'], + 'months_short' => ['كانون الثاني', 'شباط', 'آذار', 'نيسان', 'أيار', 'حزيران', 'تموز', 'آب', 'أيلول', 'تشرين الأول', 'تشرين الثاني', 'كانون الأول'], + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_LY.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_LY.php new file mode 100644 index 0000000..1f0af49 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_LY.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Atef Ben Ali (atefBB) + * - Ibrahim AshShohail + * - MLTDev + */ + +$months = [ + 'يناير', + 'فبراير', + 'مارس', + 'أبريل', + 'مايو', + 'يونيو', + 'يوليو', + 'أغسطس', + 'سبتمبر', + 'أكتوبر', + 'نوفمبر', + 'ديسمبر', +]; + +return [ + 'year' => implode('|', [':count سنة', 'سنة', 'سنتين', ':count سنوات', ':count سنة']), + 'a_year' => implode('|', [':count سنة', 'سنة', 'سنتين', ':count سنوات', ':count سنة']), + 'month' => implode('|', [':count شهر', 'شهر', 'شهرين', ':count أشهر', ':count شهر']), + 'a_month' => implode('|', [':count شهر', 'شهر', 'شهرين', ':count أشهر', ':count شهر']), + 'week' => implode('|', [':count أسبوع', 'أسبوع', 'أسبوعين', ':count أسابيع', ':count أسبوع']), + 'a_week' => implode('|', [':count أسبوع', 'أسبوع', 'أسبوعين', ':count أسابيع', ':count أسبوع']), + 'day' => implode('|', [':count يوم', 'يوم', 'يومين', ':count أيام', ':count يوم']), + 'a_day' => implode('|', [':count يوم', 'يوم', 'يومين', ':count أيام', ':count يوم']), + 'hour' => implode('|', [':count ساعة', 'ساعة', 'ساعتين', ':count ساعات', ':count ساعة']), + 'a_hour' => implode('|', [':count ساعة', 'ساعة', 'ساعتين', ':count ساعات', ':count ساعة']), + 'minute' => implode('|', [':count دقيقة', 'دقيقة', 'دقيقتين', ':count دقائق', ':count دقيقة']), + 'a_minute' => implode('|', [':count دقيقة', 'دقيقة', 'دقيقتين', ':count دقائق', ':count دقيقة']), + 'second' => implode('|', [':count ثانية', 'ثانية', 'ثانيتين', ':count ثواني', ':count ثانية']), + 'a_second' => implode('|', [':count ثانية', 'ثانية', 'ثانيتين', ':count ثواني', ':count ثانية']), + 'ago' => 'منذ :time', + 'from_now' => ':time من الآن', + 'after' => 'بعد :time', + 'before' => 'قبل :time', + 'diff_now' => 'الآن', + 'diff_today' => 'اليوم', + 'diff_today_regexp' => 'اليوم(?:\\s+عند)?(?:\\s+الساعة)?', + 'diff_yesterday' => 'أمس', + 'diff_yesterday_regexp' => 'أمس(?:\\s+عند)?(?:\\s+الساعة)?', + 'diff_tomorrow' => 'غداً', + 'diff_tomorrow_regexp' => 'غدًا(?:\\s+عند)?(?:\\s+الساعة)?', + 'diff_before_yesterday' => 'قبل الأمس', + 'diff_after_tomorrow' => 'بعد غد', + 'period_recurrences' => implode('|', ['مرة', 'مرة', ':count مرتين', ':count مرات', ':count مرة']), + 'period_interval' => 'كل :interval', + 'period_start_date' => 'من :date', + 'period_end_date' => 'إلى :date', + 'months' => $months, + 'months_short' => $months, + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['أحد', 'اثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة', 'سبت'], + 'weekdays_min' => ['ح', 'اث', 'ثل', 'أر', 'خم', 'ج', 'س'], + 'list' => ['، ', ' و '], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[اليوم عند الساعة] LT', + 'nextDay' => '[غدًا عند الساعة] LT', + 'nextWeek' => 'dddd [عند الساعة] LT', + 'lastDay' => '[أمس عند الساعة] LT', + 'lastWeek' => 'dddd [عند الساعة] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['ص', 'م'], + 'weekend' => [5, 6], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_MA.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_MA.php new file mode 100644 index 0000000..047ae05 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_MA.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Authors: + * - Josh Soref + * - JD Isaacks + * - Atef Ben Ali (atefBB) + * - Mohamed Sabil (mohamedsabil83) + */ +$months = [ + 'يناير', + 'فبراير', + 'مارس', + 'أبريل', + 'ماي', + 'يونيو', + 'يوليوز', + 'غشت', + 'شتنبر', + 'أكتوبر', + 'نونبر', + 'دجنبر', +]; + +return [ + 'year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), + 'a_year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), + 'month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), + 'a_month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), + 'week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), + 'a_week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), + 'day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), + 'a_day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), + 'hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), + 'a_hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), + 'minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), + 'a_minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), + 'second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), + 'a_second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), + 'ago' => 'منذ :time', + 'from_now' => 'في :time', + 'after' => 'بعد :time', + 'before' => 'قبل :time', + 'diff_now' => 'الآن', + 'diff_today' => 'اليوم', + 'diff_today_regexp' => 'اليوم(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_yesterday' => 'أمس', + 'diff_yesterday_regexp' => 'أمس(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_tomorrow' => 'غداً', + 'diff_tomorrow_regexp' => 'غدا(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_before_yesterday' => 'قبل الأمس', + 'diff_after_tomorrow' => 'بعد غد', + 'period_recurrences' => implode('|', ['{0}مرة', '{1}مرة', '{2}:count مرتين', ']2,11[:count مرات', ']10,Inf[:count مرة']), + 'period_interval' => 'كل :interval', + 'period_start_date' => 'من :date', + 'period_end_date' => 'إلى :date', + 'months' => $months, + 'months_short' => $months, + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['أحد', 'اثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة', 'سبت'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'list' => ['، ', ' و '], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[اليوم على الساعة] LT', + 'nextDay' => '[غدا على الساعة] LT', + 'nextWeek' => 'dddd [على الساعة] LT', + 'lastDay' => '[أمس على الساعة] LT', + 'lastWeek' => 'dddd [على الساعة] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['ص', 'م'], + 'weekend' => [5, 6], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_MR.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_MR.php new file mode 100644 index 0000000..e790b99 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_MR.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ar.php', [ +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_OM.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_OM.php new file mode 100644 index 0000000..362009e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_OM.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'], + 'months_short' => ['ينا', 'فبر', 'مار', 'أبر', 'ماي', 'يون', 'يول', 'أغس', 'سبت', 'أكت', 'نوف', 'ديس'], + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_PS.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_PS.php new file mode 100644 index 0000000..e790b99 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_PS.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ar.php', [ +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_QA.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_QA.php new file mode 100644 index 0000000..362009e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_QA.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'], + 'months_short' => ['ينا', 'فبر', 'مار', 'أبر', 'ماي', 'يون', 'يول', 'أغس', 'سبت', 'أكت', 'نوف', 'ديس'], + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_SA.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_SA.php new file mode 100644 index 0000000..10aaa2e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_SA.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Authors: + * - Josh Soref + * - JD Isaacks + * - Atef Ben Ali (atefBB) + * - Mohamed Sabil (mohamedsabil83) + */ +$months = [ + 'يناير', + 'فبراير', + 'مارس', + 'أبريل', + 'مايو', + 'يونيو', + 'يوليو', + 'أغسطس', + 'سبتمبر', + 'أكتوبر', + 'نوفمبر', + 'ديسمبر', +]; + +return [ + 'year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), + 'a_year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), + 'month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), + 'a_month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), + 'week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), + 'a_week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), + 'day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), + 'a_day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), + 'hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), + 'a_hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), + 'minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), + 'a_minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), + 'second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), + 'a_second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), + 'ago' => 'منذ :time', + 'from_now' => 'في :time', + 'after' => 'بعد :time', + 'before' => 'قبل :time', + 'diff_now' => 'الآن', + 'diff_today' => 'اليوم', + 'diff_today_regexp' => 'اليوم(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_yesterday' => 'أمس', + 'diff_yesterday_regexp' => 'أمس(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_tomorrow' => 'غداً', + 'diff_tomorrow_regexp' => 'غدا(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_before_yesterday' => 'قبل الأمس', + 'diff_after_tomorrow' => 'بعد غد', + 'period_recurrences' => implode('|', ['{0}مرة', '{1}مرة', '{2}:count مرتين', ']2,11[:count مرات', ']10,Inf[:count مرة']), + 'period_interval' => 'كل :interval', + 'period_start_date' => 'من :date', + 'period_end_date' => 'إلى :date', + 'months' => $months, + 'months_short' => $months, + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['أحد', 'اثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة', 'سبت'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'list' => ['، ', ' و '], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[اليوم على الساعة] LT', + 'nextDay' => '[غدا على الساعة] LT', + 'nextWeek' => 'dddd [على الساعة] LT', + 'lastDay' => '[أمس على الساعة] LT', + 'lastWeek' => 'dddd [على الساعة] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['ص', 'م'], + 'weekend' => [5, 6], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_SD.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_SD.php new file mode 100644 index 0000000..362009e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_SD.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'], + 'months_short' => ['ينا', 'فبر', 'مار', 'أبر', 'ماي', 'يون', 'يول', 'أغس', 'سبت', 'أكت', 'نوف', 'ديس'], + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_SO.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_SO.php new file mode 100644 index 0000000..e790b99 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_SO.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ar.php', [ +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_SS.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_SS.php new file mode 100644 index 0000000..32f3282 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_SS.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'], + 'months_short' => ['ينا', 'فبر', 'مار', 'أبر', 'ماي', 'يون', 'يول', 'أغس', 'سبت', 'أكت', 'نوف', 'ديس'], + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_SY.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_SY.php new file mode 100644 index 0000000..0ac0995 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_SY.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['كانون الثاني', 'شباط', 'آذار', 'نيسان', 'أيار', 'حزيران', 'تموز', 'آب', 'أيلول', 'تشرين الأول', 'تشرين الثاني', 'كانون الأول'], + 'months_short' => ['كانون الثاني', 'شباط', 'آذار', 'نيسان', 'أيار', 'حزيران', 'تموز', 'آب', 'أيلول', 'تشرين الأول', 'تشرين الثاني', 'كانون الأول'], + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_Shakl.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_Shakl.php new file mode 100644 index 0000000..c2d4b43 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_Shakl.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Abdellah Chadidi + * - Atef Ben Ali (atefBB) + * - Mohamed Sabil (mohamedsabil83) + */ +// Same for long and short +$months = [ + // @TODO add shakl to months + 'يناير', + 'فبراير', + 'مارس', + 'أبريل', + 'مايو', + 'يونيو', + 'يوليو', + 'أغسطس', + 'سبتمبر', + 'أكتوبر', + 'نوفمبر', + 'ديسمبر', +]; + +return [ + 'year' => implode('|', ['{0}:count سَنَة', '{1}سَنَة', '{2}سَنَتَيْن', ']2,11[:count سَنَوَات', ']10,Inf[:count سَنَة']), + 'a_year' => implode('|', ['{0}:count سَنَة', '{1}سَنَة', '{2}سَنَتَيْن', ']2,11[:count سَنَوَات', ']10,Inf[:count سَنَة']), + 'month' => implode('|', ['{0}:count شَهْرَ', '{1}شَهْرَ', '{2}شَهْرَيْن', ']2,11[:count أَشْهُر', ']10,Inf[:count شَهْرَ']), + 'a_month' => implode('|', ['{0}:count شَهْرَ', '{1}شَهْرَ', '{2}شَهْرَيْن', ']2,11[:count أَشْهُر', ']10,Inf[:count شَهْرَ']), + 'week' => implode('|', ['{0}:count أُسْبُوع', '{1}أُسْبُوع', '{2}أُسْبُوعَيْن', ']2,11[:count أَسَابِيع', ']10,Inf[:count أُسْبُوع']), + 'a_week' => implode('|', ['{0}:count أُسْبُوع', '{1}أُسْبُوع', '{2}أُسْبُوعَيْن', ']2,11[:count أَسَابِيع', ']10,Inf[:count أُسْبُوع']), + 'day' => implode('|', ['{0}:count يَوْم', '{1}يَوْم', '{2}يَوْمَيْن', ']2,11[:count أَيَّام', ']10,Inf[:count يَوْم']), + 'a_day' => implode('|', ['{0}:count يَوْم', '{1}يَوْم', '{2}يَوْمَيْن', ']2,11[:count أَيَّام', ']10,Inf[:count يَوْم']), + 'hour' => implode('|', ['{0}:count سَاعَة', '{1}سَاعَة', '{2}سَاعَتَيْن', ']2,11[:count سَاعَات', ']10,Inf[:count سَاعَة']), + 'a_hour' => implode('|', ['{0}:count سَاعَة', '{1}سَاعَة', '{2}سَاعَتَيْن', ']2,11[:count سَاعَات', ']10,Inf[:count سَاعَة']), + 'minute' => implode('|', ['{0}:count دَقِيقَة', '{1}دَقِيقَة', '{2}دَقِيقَتَيْن', ']2,11[:count دَقَائِق', ']10,Inf[:count دَقِيقَة']), + 'a_minute' => implode('|', ['{0}:count دَقِيقَة', '{1}دَقِيقَة', '{2}دَقِيقَتَيْن', ']2,11[:count دَقَائِق', ']10,Inf[:count دَقِيقَة']), + 'second' => implode('|', ['{0}:count ثَانِيَة', '{1}ثَانِيَة', '{2}ثَانِيَتَيْن', ']2,11[:count ثَوَان', ']10,Inf[:count ثَانِيَة']), + 'a_second' => implode('|', ['{0}:count ثَانِيَة', '{1}ثَانِيَة', '{2}ثَانِيَتَيْن', ']2,11[:count ثَوَان', ']10,Inf[:count ثَانِيَة']), + 'ago' => 'مُنْذُ :time', + 'from_now' => 'مِنَ الْآن :time', + 'after' => 'بَعْدَ :time', + 'before' => 'قَبْلَ :time', + + // @TODO add shakl to translations below + 'diff_now' => 'الآن', + 'diff_today' => 'اليوم', + 'diff_today_regexp' => 'اليوم(?:\\s+عند)?(?:\\s+الساعة)?', + 'diff_yesterday' => 'أمس', + 'diff_yesterday_regexp' => 'أمس(?:\\s+عند)?(?:\\s+الساعة)?', + 'diff_tomorrow' => 'غداً', + 'diff_tomorrow_regexp' => 'غدًا(?:\\s+عند)?(?:\\s+الساعة)?', + 'diff_before_yesterday' => 'قبل الأمس', + 'diff_after_tomorrow' => 'بعد غد', + 'period_recurrences' => implode('|', ['{0}مرة', '{1}مرة', '{2}:count مرتين', ']2,11[:count مرات', ']10,Inf[:count مرة']), + 'period_interval' => 'كل :interval', + 'period_start_date' => 'من :date', + 'period_end_date' => 'إلى :date', + 'months' => $months, + 'months_short' => $months, + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['أحد', 'اثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة', 'سبت'], + 'weekdays_min' => ['ح', 'اث', 'ثل', 'أر', 'خم', 'ج', 'س'], + 'list' => ['، ', ' و '], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[اليوم عند الساعة] LT', + 'nextDay' => '[غدًا عند الساعة] LT', + 'nextWeek' => 'dddd [عند الساعة] LT', + 'lastDay' => '[أمس عند الساعة] LT', + 'lastWeek' => 'dddd [عند الساعة] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['ص', 'م'], + 'weekend' => [5, 6], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_TD.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_TD.php new file mode 100644 index 0000000..e790b99 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_TD.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ar.php', [ +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_TN.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_TN.php new file mode 100644 index 0000000..f096678 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_TN.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Authors: + * - JD Isaacks + * - Atef Ben Ali (atefBB) + * - Mohamed Sabil (mohamedsabil83) + */ +$months = [ + 'جانفي', + 'فيفري', + 'مارس', + 'أفريل', + 'ماي', + 'جوان', + 'جويلية', + 'أوت', + 'سبتمبر', + 'أكتوبر', + 'نوفمبر', + 'ديسمبر', +]; + +return [ + 'year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), + 'a_year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), + 'month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), + 'a_month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), + 'week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), + 'a_week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), + 'day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), + 'a_day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), + 'hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), + 'a_hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), + 'minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), + 'a_minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), + 'second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), + 'a_second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), + 'ago' => 'منذ :time', + 'from_now' => 'في :time', + 'after' => 'بعد :time', + 'before' => 'قبل :time', + 'diff_now' => 'الآن', + 'diff_today' => 'اليوم', + 'diff_today_regexp' => 'اليوم(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_yesterday' => 'أمس', + 'diff_yesterday_regexp' => 'أمس(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_tomorrow' => 'غداً', + 'diff_tomorrow_regexp' => 'غدا(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_before_yesterday' => 'قبل الأمس', + 'diff_after_tomorrow' => 'بعد غد', + 'period_recurrences' => implode('|', ['{0}مرة', '{1}مرة', '{2}:count مرتين', ']2,11[:count مرات', ']10,Inf[:count مرة']), + 'period_interval' => 'كل :interval', + 'period_start_date' => 'من :date', + 'period_end_date' => 'إلى :date', + 'months' => $months, + 'months_short' => $months, + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['أحد', 'اثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة', 'سبت'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'list' => ['، ', ' و '], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[اليوم على الساعة] LT', + 'nextDay' => '[غدا على الساعة] LT', + 'nextWeek' => 'dddd [على الساعة] LT', + 'lastDay' => '[أمس على الساعة] LT', + 'lastWeek' => 'dddd [على الساعة] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['ص', 'م'], + 'weekend' => [5, 6], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ar_YE.php b/vendor/nesbot/carbon/src/Carbon/Lang/ar_YE.php new file mode 100644 index 0000000..5dc2938 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ar_YE.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/ar.php', [ + 'formats' => [ + 'L' => 'DD MMM, YYYY', + ], + 'months' => ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'], + 'months_short' => ['ينا', 'فبر', 'مار', 'أبر', 'ماي', 'يون', 'يول', 'أغس', 'سبت', 'أكت', 'نوف', 'ديس'], + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], + 'weekdays_short' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/as.php b/vendor/nesbot/carbon/src/Carbon/Lang/as.php new file mode 100644 index 0000000..04bc3df --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/as.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/as_IN.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/as_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/as_IN.php new file mode 100644 index 0000000..5fbc3db --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/as_IN.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Amitakhya Phukan, Red Hat bug-glibc@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D-MM-YYYY', + ], + 'months' => ['জানুৱাৰী', 'ফেব্ৰুৱাৰী', 'মাৰ্চ', 'এপ্ৰিল', 'মে', 'জুন', 'জুলাই', 'আগষ্ট', 'ছেপ্তেম্বৰ', 'অক্টোবৰ', 'নৱেম্বৰ', 'ডিচেম্বৰ'], + 'months_short' => ['জানু', 'ফেব্ৰু', 'মাৰ্চ', 'এপ্ৰিল', 'মে', 'জুন', 'জুলাই', 'আগ', 'সেপ্ট', 'অক্টো', 'নভে', 'ডিসে'], + 'weekdays' => ['দেওবাৰ', 'সোমবাৰ', 'মঙ্গলবাৰ', 'বুধবাৰ', 'বৃহষ্পতিবাৰ', 'শুক্ৰবাৰ', 'শনিবাৰ'], + 'weekdays_short' => ['দেও', 'সোম', 'মঙ্গল', 'বুধ', 'বৃহষ্পতি', 'শুক্ৰ', 'শনি'], + 'weekdays_min' => ['দেও', 'সোম', 'মঙ্গল', 'বুধ', 'বৃহষ্পতি', 'শুক্ৰ', 'শনি'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['পূৰ্ব্বাহ্ন', 'অপৰাহ্ন'], + + 'year' => ':count বছৰ', + 'y' => ':count বছৰ', + 'a_year' => ':count বছৰ', + + 'month' => ':count মাহ', + 'm' => ':count মাহ', + 'a_month' => ':count মাহ', + + 'week' => ':count সপ্তাহ', + 'w' => ':count সপ্তাহ', + 'a_week' => ':count সপ্তাহ', + + 'day' => ':count বাৰ', + 'd' => ':count বাৰ', + 'a_day' => ':count বাৰ', + + 'hour' => ':count ঘণ্টা', + 'h' => ':count ঘণ্টা', + 'a_hour' => ':count ঘণ্টা', + + 'minute' => ':count মিনিট', + 'min' => ':count মিনিট', + 'a_minute' => ':count মিনিট', + + 'second' => ':count দ্বিতীয়', + 's' => ':count দ্বিতীয়', + 'a_second' => ':count দ্বিতীয়', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/asa.php b/vendor/nesbot/carbon/src/Carbon/Lang/asa.php new file mode 100644 index 0000000..03bb483 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/asa.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['icheheavo', 'ichamthi'], + 'weekdays' => ['Jumapili', 'Jumatatu', 'Jumanne', 'Jumatano', 'Alhamisi', 'Ijumaa', 'Jumamosi'], + 'weekdays_short' => ['Jpi', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Ijm', 'Jmo'], + 'weekdays_min' => ['Jpi', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Ijm', 'Jmo'], + 'months' => ['Januari', 'Februari', 'Machi', 'Aprili', 'Mei', 'Juni', 'Julai', 'Agosti', 'Septemba', 'Oktoba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Sep', 'Okt', 'Nov', 'Dec'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ast.php b/vendor/nesbot/carbon/src/Carbon/Lang/ast.php new file mode 100644 index 0000000..d9bdebe --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ast.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Jordi Mallach jordi@gnu.org + * - Adolfo Jayme-Barrientos (fitojb) + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['de xineru', 'de febreru', 'de marzu', 'd’abril', 'de mayu', 'de xunu', 'de xunetu', 'd’agostu', 'de setiembre', 'd’ochobre', 'de payares', 'd’avientu'], + 'months_short' => ['xin', 'feb', 'mar', 'abr', 'may', 'xun', 'xnt', 'ago', 'set', 'och', 'pay', 'avi'], + 'weekdays' => ['domingu', 'llunes', 'martes', 'miércoles', 'xueves', 'vienres', 'sábadu'], + 'weekdays_short' => ['dom', 'llu', 'mar', 'mié', 'xue', 'vie', 'sáb'], + 'weekdays_min' => ['dom', 'llu', 'mar', 'mié', 'xue', 'vie', 'sáb'], + + 'year' => ':count añu|:count años', + 'y' => ':count añu|:count años', + 'a_year' => 'un añu|:count años', + + 'month' => ':count mes', + 'm' => ':count mes', + 'a_month' => 'un mes|:count mes', + + 'week' => ':count selmana|:count selmanes', + 'w' => ':count selmana|:count selmanes', + 'a_week' => 'una selmana|:count selmanes', + + 'day' => ':count día|:count díes', + 'd' => ':count día|:count díes', + 'a_day' => 'un día|:count díes', + + 'hour' => ':count hora|:count hores', + 'h' => ':count hora|:count hores', + 'a_hour' => 'una hora|:count hores', + + 'minute' => ':count minutu|:count minutos', + 'min' => ':count minutu|:count minutos', + 'a_minute' => 'un minutu|:count minutos', + + 'second' => ':count segundu|:count segundos', + 's' => ':count segundu|:count segundos', + 'a_second' => 'un segundu|:count segundos', + + 'ago' => 'hai :time', + 'from_now' => 'en :time', + 'after' => ':time dempués', + 'before' => ':time enantes', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ast_ES.php b/vendor/nesbot/carbon/src/Carbon/Lang/ast_ES.php new file mode 100644 index 0000000..04d7562 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ast_ES.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ast.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ayc.php b/vendor/nesbot/carbon/src/Carbon/Lang/ayc.php new file mode 100644 index 0000000..d6a6f63 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ayc.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ayc_PE.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ayc_PE.php b/vendor/nesbot/carbon/src/Carbon/Lang/ayc_PE.php new file mode 100644 index 0000000..ff18504 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ayc_PE.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - runasimipi.org libc-alpha@sourceware.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['inïru', 'phiwriru', 'marsu', 'awrila', 'mayu', 'junyu', 'julyu', 'awustu', 'sitimri', 'uktuwri', 'nuwimri', 'risimri'], + 'months_short' => ['ini', 'phi', 'mar', 'awr', 'may', 'jun', 'jul', 'awu', 'sit', 'ukt', 'nuw', 'ris'], + 'weekdays' => ['tuminku', 'lunisa', 'martisa', 'mirkulisa', 'juywisa', 'wirnisa', 'sawäru'], + 'weekdays_short' => ['tum', 'lun', 'mar', 'mir', 'juy', 'wir', 'saw'], + 'weekdays_min' => ['tum', 'lun', 'mar', 'mir', 'juy', 'wir', 'saw'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['VM', 'NM'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/az.php b/vendor/nesbot/carbon/src/Carbon/Lang/az.php new file mode 100644 index 0000000..1e92106 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/az.php @@ -0,0 +1,128 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - Kunal Marwaha + * - François B + * - JD Isaacks + * - Orxan + * - Şəhriyar İmanov + * - Baran Şengül + */ +return [ + 'year' => ':count il', + 'a_year' => '{1}bir il|]1,Inf[:count il', + 'y' => ':count il', + 'month' => ':count ay', + 'a_month' => '{1}bir ay|]1,Inf[:count ay', + 'm' => ':count ay', + 'week' => ':count həftə', + 'a_week' => '{1}bir həftə|]1,Inf[:count həftə', + 'w' => ':count h.', + 'day' => ':count gün', + 'a_day' => '{1}bir gün|]1,Inf[:count gün', + 'd' => ':count g.', + 'hour' => ':count saat', + 'a_hour' => '{1}bir saat|]1,Inf[:count saat', + 'h' => ':count saat', + 'minute' => ':count d.', + 'a_minute' => '{1}bir dəqiqə|]1,Inf[:count dəqiqə', + 'min' => ':count dəqiqə', + 'second' => ':count san.', + 'a_second' => '{1}birneçə saniyə|]1,Inf[:count saniyə', + 's' => ':count saniyə', + 'ago' => ':time əvvəl', + 'from_now' => ':time sonra', + 'after' => ':time sonra', + 'before' => ':time əvvəl', + 'diff_now' => 'indi', + 'diff_today' => 'bugün', + 'diff_today_regexp' => 'bugün(?:\\s+saat)?', + 'diff_yesterday' => 'dünən', + 'diff_tomorrow' => 'sabah', + 'diff_tomorrow_regexp' => 'sabah(?:\\s+saat)?', + 'diff_before_yesterday' => 'srağagün', + 'diff_after_tomorrow' => 'birisi gün', + 'period_recurrences' => ':count dəfədən bir', + 'period_interval' => 'hər :interval', + 'period_start_date' => ':date tarixindən başlayaraq', + 'period_end_date' => ':date tarixinədək', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[bugün saat] LT', + 'nextDay' => '[sabah saat] LT', + 'nextWeek' => '[gələn həftə] dddd [saat] LT', + 'lastDay' => '[dünən] LT', + 'lastWeek' => '[keçən həftə] dddd [saat] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + if ($number === 0) { // special case for zero + return "$number-ıncı"; + } + + static $suffixes = [ + 1 => '-inci', + 5 => '-inci', + 8 => '-inci', + 70 => '-inci', + 80 => '-inci', + 2 => '-nci', + 7 => '-nci', + 20 => '-nci', + 50 => '-nci', + 3 => '-üncü', + 4 => '-üncü', + 100 => '-üncü', + 6 => '-ncı', + 9 => '-uncu', + 10 => '-uncu', + 30 => '-uncu', + 60 => '-ıncı', + 90 => '-ıncı', + ]; + + $lastDigit = $number % 10; + + return $number.($suffixes[$lastDigit] ?? $suffixes[$number % 100 - $lastDigit] ?? $suffixes[$number >= 100 ? 100 : -1] ?? ''); + }, + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'gecə'; + } + if ($hour < 12) { + return 'səhər'; + } + if ($hour < 17) { + return 'gündüz'; + } + + return 'axşam'; + }, + 'months' => ['yanvar', 'fevral', 'mart', 'aprel', 'may', 'iyun', 'iyul', 'avqust', 'sentyabr', 'oktyabr', 'noyabr', 'dekabr'], + 'months_short' => ['yan', 'fev', 'mar', 'apr', 'may', 'iyn', 'iyl', 'avq', 'sen', 'okt', 'noy', 'dek'], + 'months_standalone' => ['Yanvar', 'Fevral', 'Mart', 'Aprel', 'May', 'İyun', 'İyul', 'Avqust', 'Sentyabr', 'Oktyabr', 'Noyabr', 'Dekabr'], + 'weekdays' => ['bazar', 'bazar ertəsi', 'çərşənbə axşamı', 'çərşənbə', 'cümə axşamı', 'cümə', 'şənbə'], + 'weekdays_short' => ['baz', 'bze', 'çax', 'çər', 'cax', 'cüm', 'şən'], + 'weekdays_min' => ['bz', 'be', 'ça', 'çə', 'ca', 'cü', 'şə'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' və '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/az_AZ.php b/vendor/nesbot/carbon/src/Carbon/Lang/az_AZ.php new file mode 100644 index 0000000..2acf881 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/az_AZ.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Pablo Saratxaga pablo@mandrakesoft.com + */ +return array_replace_recursive(require __DIR__.'/az.php', [ + 'months_short' => ['Yan', 'Fev', 'Mar', 'Apr', 'May', 'İyn', 'İyl', 'Avq', 'Sen', 'Okt', 'Noy', 'Dek'], + 'weekdays' => ['bazar günü', 'bazar ertəsi', 'çərşənbə axşamı', 'çərşənbə', 'cümə axşamı', 'cümə', 'şənbə'], + 'weekdays_short' => ['baz', 'ber', 'çax', 'çər', 'cax', 'cüm', 'şnb'], + 'weekdays_min' => ['baz', 'ber', 'çax', 'çər', 'cax', 'cüm', 'şnb'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/az_Cyrl.php b/vendor/nesbot/carbon/src/Carbon/Lang/az_Cyrl.php new file mode 100644 index 0000000..28fc62f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/az_Cyrl.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/az.php', [ + 'weekdays' => ['базар', 'базар ертәси', 'чәршәнбә ахшамы', 'чәршәнбә', 'ҹүмә ахшамы', 'ҹүмә', 'шәнбә'], + 'weekdays_short' => ['Б.', 'Б.Е.', 'Ч.А.', 'Ч.', 'Ҹ.А.', 'Ҹ.', 'Ш.'], + 'weekdays_min' => ['Б.', 'Б.Е.', 'Ч.А.', 'Ч.', 'Ҹ.А.', 'Ҹ.', 'Ш.'], + 'months' => ['јанвар', 'феврал', 'март', 'апрел', 'май', 'ијун', 'ијул', 'август', 'сентјабр', 'октјабр', 'нојабр', 'декабр'], + 'months_short' => ['јан', 'фев', 'мар', 'апр', 'май', 'ијн', 'ијл', 'авг', 'сен', 'окт', 'ној', 'дек'], + 'months_standalone' => ['Јанвар', 'Феврал', 'Март', 'Апрел', 'Май', 'Ијун', 'Ијул', 'Август', 'Сентјабр', 'Октјабр', 'Нојабр', 'Декабр'], + 'meridiem' => ['а', 'п'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/az_IR.php b/vendor/nesbot/carbon/src/Carbon/Lang/az_IR.php new file mode 100644 index 0000000..991a0ef --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/az_IR.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Mousa Moradi mousamk@gmail.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'OY/OM/OD', + ], + 'months' => ['ژانویه', 'فوریه', 'مارس', 'آوریل', 'مئی', 'ژوئن', 'جولای', 'آقۇست', 'سپتامبر', 'اوْکتوْبر', 'نوْوامبر', 'دسامبر'], + 'months_short' => ['ژانویه', 'فوریه', 'مارس', 'آوریل', 'مئی', 'ژوئن', 'جولای', 'آقۇست', 'سپتامبر', 'اوْکتوْبر', 'نوْوامبر', 'دسامبر'], + 'weekdays' => ['یکشنبه', 'دوشنبه', 'سه‌شنبه', 'چارشنبه', 'جۆمعه آخشامی', 'جۆمعه', 'شنبه'], + 'weekdays_short' => ['یکشنبه', 'دوشنبه', 'سه‌شنبه', 'چارشنبه', 'جۆمعه آخشامی', 'جۆمعه', 'شنبه'], + 'weekdays_min' => ['یکشنبه', 'دوشنبه', 'سه‌شنبه', 'چارشنبه', 'جۆمعه آخشامی', 'جۆمعه', 'شنبه'], + 'first_day_of_week' => 6, + 'alt_numbers' => ['۰۰', '۰۱', '۰۲', '۰۳', '۰۴', '۰۵', '۰۶', '۰۷', '۰۸', '۰۹', '۱۰', '۱۱', '۱۲', '۱۳', '۱۴', '۱۵', '۱۶', '۱۷', '۱۸', '۱۹', '۲۰', '۲۱', '۲۲', '۲۳', '۲۴', '۲۵', '۲۶', '۲۷', '۲۸', '۲۹', '۳۰', '۳۱', '۳۲', '۳۳', '۳۴', '۳۵', '۳۶', '۳۷', '۳۸', '۳۹', '۴۰', '۴۱', '۴۲', '۴۳', '۴۴', '۴۵', '۴۶', '۴۷', '۴۸', '۴۹', '۵۰', '۵۱', '۵۲', '۵۳', '۵۴', '۵۵', '۵۶', '۵۷', '۵۸', '۵۹', '۶۰', '۶۱', '۶۲', '۶۳', '۶۴', '۶۵', '۶۶', '۶۷', '۶۸', '۶۹', '۷۰', '۷۱', '۷۲', '۷۳', '۷۴', '۷۵', '۷۶', '۷۷', '۷۸', '۷۹', '۸۰', '۸۱', '۸۲', '۸۳', '۸۴', '۸۵', '۸۶', '۸۷', '۸۸', '۸۹', '۹۰', '۹۱', '۹۲', '۹۳', '۹۴', '۹۵', '۹۶', '۹۷', '۹۸', '۹۹'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/az_Latn.php b/vendor/nesbot/carbon/src/Carbon/Lang/az_Latn.php new file mode 100644 index 0000000..0be3391 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/az_Latn.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/az.php', [ + 'meridiem' => ['a', 'p'], + 'weekdays' => ['bazar', 'bazar ertəsi', 'çərşənbə axşamı', 'çərşənbə', 'cümə axşamı', 'cümə', 'şənbə'], + 'weekdays_short' => ['B.', 'B.E.', 'Ç.A.', 'Ç.', 'C.A.', 'C.', 'Ş.'], + 'weekdays_min' => ['B.', 'B.E.', 'Ç.A.', 'Ç.', 'C.A.', 'C.', 'Ş.'], + 'months' => ['yanvar', 'fevral', 'mart', 'aprel', 'may', 'iyun', 'iyul', 'avqust', 'sentyabr', 'oktyabr', 'noyabr', 'dekabr'], + 'months_short' => ['yan', 'fev', 'mar', 'apr', 'may', 'iyn', 'iyl', 'avq', 'sen', 'okt', 'noy', 'dek'], + 'months_standalone' => ['Yanvar', 'Fevral', 'Mart', 'Aprel', 'May', 'İyun', 'İyul', 'Avqust', 'Sentyabr', 'Oktyabr', 'Noyabr', 'Dekabr'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'D MMMM YYYY, dddd HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bas.php b/vendor/nesbot/carbon/src/Carbon/Lang/bas.php new file mode 100644 index 0000000..41bfa1d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bas.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['I bikɛ̂glà', 'I ɓugajɔp'], + 'weekdays' => ['ŋgwà nɔ̂y', 'ŋgwà njaŋgumba', 'ŋgwà ûm', 'ŋgwà ŋgê', 'ŋgwà mbɔk', 'ŋgwà kɔɔ', 'ŋgwà jôn'], + 'weekdays_short' => ['nɔy', 'nja', 'uum', 'ŋge', 'mbɔ', 'kɔɔ', 'jon'], + 'weekdays_min' => ['nɔy', 'nja', 'uum', 'ŋge', 'mbɔ', 'kɔɔ', 'jon'], + 'months' => ['Kɔndɔŋ', 'Màcɛ̂l', 'Màtùmb', 'Màtop', 'M̀puyɛ', 'Hìlòndɛ̀', 'Njèbà', 'Hìkaŋ', 'Dìpɔ̀s', 'Bìòôm', 'Màyɛsèp', 'Lìbuy li ńyèe'], + 'months_short' => ['kɔn', 'mac', 'mat', 'mto', 'mpu', 'hil', 'nje', 'hik', 'dip', 'bio', 'may', 'liɓ'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + + 'second' => ':count móndî', // less reliable + 's' => ':count móndî', // less reliable + 'a_second' => ':count móndî', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/be.php b/vendor/nesbot/carbon/src/Carbon/Lang/be.php new file mode 100644 index 0000000..51b4d0c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/be.php @@ -0,0 +1,173 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +// @codeCoverageIgnoreStart + +use Carbon\CarbonInterface; +use Symfony\Component\Translation\PluralizationRules; + +if (class_exists('Symfony\\Component\\Translation\\PluralizationRules')) { + PluralizationRules::set(function ($number) { + return (($number % 10 == 1) && ($number % 100 != 11)) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2); + }, 'be'); +} +// @codeCoverageIgnoreEnd + +/* + * Authors: + * - Josh Soref + * - SobakaSlava + * - François B + * - Serhan Apaydın + * - JD Isaacks + * - AbadonnaAbbys + * - Siomkin Alexander + */ +return [ + 'year' => ':count год|:count гады|:count гадоў', + 'a_year' => '{1}год|:count год|:count гады|:count гадоў', + 'y' => ':count год|:count гады|:count гадоў', + 'month' => ':count месяц|:count месяцы|:count месяцаў', + 'a_month' => '{1}месяц|:count месяц|:count месяцы|:count месяцаў', + 'm' => ':count месяц|:count месяцы|:count месяцаў', + 'week' => ':count тыдзень|:count тыдні|:count тыдняў', + 'a_week' => '{1}тыдзень|:count тыдзень|:count тыдні|:count тыдняў', + 'w' => ':count тыдзень|:count тыдні|:count тыдняў', + 'day' => ':count дзень|:count дні|:count дзён', + 'a_day' => '{1}дзень|:count дзень|:count дні|:count дзён', + 'd' => ':count дн', + 'hour' => ':count гадзіну|:count гадзіны|:count гадзін', + 'a_hour' => '{1}гадзіна|:count гадзіна|:count гадзіны|:count гадзін', + 'h' => ':count гадзіна|:count гадзіны|:count гадзін', + 'minute' => ':count хвіліна|:count хвіліны|:count хвілін', + 'a_minute' => '{1}хвіліна|:count хвіліна|:count хвіліны|:count хвілін', + 'min' => ':count хв', + 'second' => ':count секунда|:count секунды|:count секунд', + 'a_second' => '{1}некалькі секунд|:count секунда|:count секунды|:count секунд', + 's' => ':count сек', + + 'hour_ago' => ':count гадзіну|:count гадзіны|:count гадзін', + 'a_hour_ago' => '{1}гадзіну|:count гадзіну|:count гадзіны|:count гадзін', + 'h_ago' => ':count гадзіну|:count гадзіны|:count гадзін', + 'minute_ago' => ':count хвіліну|:count хвіліны|:count хвілін', + 'a_minute_ago' => '{1}хвіліну|:count хвіліну|:count хвіліны|:count хвілін', + 'min_ago' => ':count хвіліну|:count хвіліны|:count хвілін', + 'second_ago' => ':count секунду|:count секунды|:count секунд', + 'a_second_ago' => '{1}некалькі секунд|:count секунду|:count секунды|:count секунд', + 's_ago' => ':count секунду|:count секунды|:count секунд', + + 'hour_from_now' => ':count гадзіну|:count гадзіны|:count гадзін', + 'a_hour_from_now' => '{1}гадзіну|:count гадзіну|:count гадзіны|:count гадзін', + 'h_from_now' => ':count гадзіну|:count гадзіны|:count гадзін', + 'minute_from_now' => ':count хвіліну|:count хвіліны|:count хвілін', + 'a_minute_from_now' => '{1}хвіліну|:count хвіліну|:count хвіліны|:count хвілін', + 'min_from_now' => ':count хвіліну|:count хвіліны|:count хвілін', + 'second_from_now' => ':count секунду|:count секунды|:count секунд', + 'a_second_from_now' => '{1}некалькі секунд|:count секунду|:count секунды|:count секунд', + 's_from_now' => ':count секунду|:count секунды|:count секунд', + + 'hour_after' => ':count гадзіну|:count гадзіны|:count гадзін', + 'a_hour_after' => '{1}гадзіну|:count гадзіну|:count гадзіны|:count гадзін', + 'h_after' => ':count гадзіну|:count гадзіны|:count гадзін', + 'minute_after' => ':count хвіліну|:count хвіліны|:count хвілін', + 'a_minute_after' => '{1}хвіліну|:count хвіліну|:count хвіліны|:count хвілін', + 'min_after' => ':count хвіліну|:count хвіліны|:count хвілін', + 'second_after' => ':count секунду|:count секунды|:count секунд', + 'a_second_after' => '{1}некалькі секунд|:count секунду|:count секунды|:count секунд', + 's_after' => ':count секунду|:count секунды|:count секунд', + + 'hour_before' => ':count гадзіну|:count гадзіны|:count гадзін', + 'a_hour_before' => '{1}гадзіну|:count гадзіну|:count гадзіны|:count гадзін', + 'h_before' => ':count гадзіну|:count гадзіны|:count гадзін', + 'minute_before' => ':count хвіліну|:count хвіліны|:count хвілін', + 'a_minute_before' => '{1}хвіліну|:count хвіліну|:count хвіліны|:count хвілін', + 'min_before' => ':count хвіліну|:count хвіліны|:count хвілін', + 'second_before' => ':count секунду|:count секунды|:count секунд', + 'a_second_before' => '{1}некалькі секунд|:count секунду|:count секунды|:count секунд', + 's_before' => ':count секунду|:count секунды|:count секунд', + + 'ago' => ':time таму', + 'from_now' => 'праз :time', + 'after' => ':time пасля', + 'before' => ':time да', + 'diff_now' => 'цяпер', + 'diff_today' => 'Сёння', + 'diff_today_regexp' => 'Сёння(?:\\s+ў)?', + 'diff_yesterday' => 'учора', + 'diff_yesterday_regexp' => 'Учора(?:\\s+ў)?', + 'diff_tomorrow' => 'заўтра', + 'diff_tomorrow_regexp' => 'Заўтра(?:\\s+ў)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY г.', + 'LLL' => 'D MMMM YYYY г., HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY г., HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Сёння ў] LT', + 'nextDay' => '[Заўтра ў] LT', + 'nextWeek' => '[У] dddd [ў] LT', + 'lastDay' => '[Учора ў] LT', + 'lastWeek' => function (CarbonInterface $current) { + switch ($current->dayOfWeek) { + case 1: + case 2: + case 4: + return '[У мінулы] dddd [ў] LT'; + default: + return '[У мінулую] dddd [ў] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + switch ($period) { + case 'M': + case 'd': + case 'DDD': + case 'w': + case 'W': + return ($number % 10 === 2 || $number % 10 === 3) && ($number % 100 !== 12 && $number % 100 !== 13) ? $number.'-і' : $number.'-ы'; + case 'D': + return $number.'-га'; + default: + return $number; + } + }, + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'ночы'; + } + if ($hour < 12) { + return 'раніцы'; + } + if ($hour < 17) { + return 'дня'; + } + + return 'вечара'; + }, + 'months' => ['студзеня', 'лютага', 'сакавіка', 'красавіка', 'траўня', 'чэрвеня', 'ліпеня', 'жніўня', 'верасня', 'кастрычніка', 'лістапада', 'снежня'], + 'months_standalone' => ['студзень', 'люты', 'сакавік', 'красавік', 'травень', 'чэрвень', 'ліпень', 'жнівень', 'верасень', 'кастрычнік', 'лістапад', 'снежань'], + 'months_short' => ['студ', 'лют', 'сак', 'крас', 'трав', 'чэрв', 'ліп', 'жнів', 'вер', 'каст', 'ліст', 'снеж'], + 'months_regexp' => '/(DD?o?\.?(\[[^\[\]]*\]|\s)+MMMM?|L{2,4}|l{2,4})/', + 'weekdays' => ['нядзелю', 'панядзелак', 'аўторак', 'сераду', 'чацвер', 'пятніцу', 'суботу'], + 'weekdays_standalone' => ['нядзеля', 'панядзелак', 'аўторак', 'серада', 'чацвер', 'пятніца', 'субота'], + 'weekdays_short' => ['нд', 'пн', 'ат', 'ср', 'чц', 'пт', 'сб'], + 'weekdays_min' => ['нд', 'пн', 'ат', 'ср', 'чц', 'пт', 'сб'], + 'weekdays_regexp' => '/\[ ?[Ууў] ?(?:мінулую|наступную)? ?\] ?dddd/', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' і '], + 'months_short_standalone' => ['сту', 'лют', 'сак', 'кра', 'май', 'чэр', 'ліп', 'жні', 'вер', 'кас', 'ліс', 'сне'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/be_BY.php b/vendor/nesbot/carbon/src/Carbon/Lang/be_BY.php new file mode 100644 index 0000000..26684b4 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/be_BY.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/be.php', [ + 'months' => ['студзеня', 'лютага', 'сакавіка', 'красавіка', 'мая', 'чэрвеня', 'ліпеня', 'жніўня', 'верасня', 'кастрычніка', 'лістапада', 'снежня'], + 'months_short' => ['сту', 'лют', 'сак', 'кра', 'мая', 'чэр', 'ліп', 'жні', 'вер', 'кас', 'ліс', 'сне'], + 'weekdays' => ['Нядзеля', 'Панядзелак', 'Аўторак', 'Серада', 'Чацвер', 'Пятніца', 'Субота'], + 'weekdays_short' => ['Няд', 'Пан', 'Аўт', 'Срд', 'Чцв', 'Пят', 'Суб'], + 'weekdays_min' => ['Няд', 'Пан', 'Аўт', 'Срд', 'Чцв', 'Пят', 'Суб'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/be_BY@latin.php b/vendor/nesbot/carbon/src/Carbon/Lang/be_BY@latin.php new file mode 100644 index 0000000..517ce83 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/be_BY@latin.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['studzienia', 'lutaha', 'sakavika', 'krasavika', 'maja', 'červienia', 'lipienia', 'žniŭnia', 'vieraśnia', 'kastryčnika', 'listapada', 'śniežnia'], + 'months_short' => ['Stu', 'Lut', 'Sak', 'Kra', 'Maj', 'Čer', 'Lip', 'Žni', 'Vie', 'Kas', 'Lis', 'Śni'], + 'weekdays' => ['Niadziela', 'Paniadziełak', 'Aŭtorak', 'Sierada', 'Čaćvier', 'Piatnica', 'Subota'], + 'weekdays_short' => ['Nia', 'Pan', 'Aŭt', 'Sie', 'Čać', 'Pia', 'Sub'], + 'weekdays_min' => ['Nia', 'Pan', 'Aŭt', 'Sie', 'Čać', 'Pia', 'Sub'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bem.php b/vendor/nesbot/carbon/src/Carbon/Lang/bem.php new file mode 100644 index 0000000..1c3ef03 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bem.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/bem_ZM.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bem_ZM.php b/vendor/nesbot/carbon/src/Carbon/Lang/bem_ZM.php new file mode 100644 index 0000000..620b579 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bem_ZM.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - ANLoc Martin Benjamin locales@africanlocalization.net + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'MM/DD/YYYY', + ], + 'months' => ['Januari', 'Februari', 'Machi', 'Epreo', 'Mei', 'Juni', 'Julai', 'Ogasti', 'Septemba', 'Oktoba', 'Novemba', 'Disemba'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Epr', 'Mei', 'Jun', 'Jul', 'Oga', 'Sep', 'Okt', 'Nov', 'Dis'], + 'weekdays' => ['Pa Mulungu', 'Palichimo', 'Palichibuli', 'Palichitatu', 'Palichine', 'Palichisano', 'Pachibelushi'], + 'weekdays_short' => ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + 'weekdays_min' => ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['uluchelo', 'akasuba'], + + 'year' => 'myaka :count', + 'y' => 'myaka :count', + 'a_year' => 'myaka :count', + + 'month' => 'myeshi :count', + 'm' => 'myeshi :count', + 'a_month' => 'myeshi :count', + + 'week' => 'umulungu :count', + 'w' => 'umulungu :count', + 'a_week' => 'umulungu :count', + + 'day' => 'inshiku :count', + 'd' => 'inshiku :count', + 'a_day' => 'inshiku :count', + + 'hour' => 'awala :count', + 'h' => 'awala :count', + 'a_hour' => 'awala :count', + + 'minute' => 'miniti :count', + 'min' => 'miniti :count', + 'a_minute' => 'miniti :count', + + 'second' => 'sekondi :count', + 's' => 'sekondi :count', + 'a_second' => 'sekondi :count', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ber.php b/vendor/nesbot/carbon/src/Carbon/Lang/ber.php new file mode 100644 index 0000000..685603c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ber.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ber_DZ.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ber_DZ.php b/vendor/nesbot/carbon/src/Carbon/Lang/ber_DZ.php new file mode 100644 index 0000000..38de10a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ber_DZ.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Pablo Saratxaga pablo@mandrakesoft.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['yanvar', 'fevral', 'mart', 'aprel', 'may', 'iyun', 'iyul', 'avqust', 'sentyabr', 'oktyabr', 'noyabr', 'dekabr'], + 'months_short' => ['Yan', 'Fev', 'Mar', 'Apr', 'May', 'İyn', 'İyl', 'Avq', 'Sen', 'Okt', 'Noy', 'Dek'], + 'weekdays' => ['bazar günü', 'birinci gün', 'ikinci gün', 'üçüncü gün', 'dördüncü gün', 'beşinci gün', 'altıncı gün'], + 'weekdays_short' => ['baz', 'bir', 'iki', 'üçü', 'dör', 'beş', 'alt'], + 'weekdays_min' => ['baz', 'bir', 'iki', 'üçü', 'dör', 'beş', 'alt'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ber_MA.php b/vendor/nesbot/carbon/src/Carbon/Lang/ber_MA.php new file mode 100644 index 0000000..38de10a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ber_MA.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Pablo Saratxaga pablo@mandrakesoft.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['yanvar', 'fevral', 'mart', 'aprel', 'may', 'iyun', 'iyul', 'avqust', 'sentyabr', 'oktyabr', 'noyabr', 'dekabr'], + 'months_short' => ['Yan', 'Fev', 'Mar', 'Apr', 'May', 'İyn', 'İyl', 'Avq', 'Sen', 'Okt', 'Noy', 'Dek'], + 'weekdays' => ['bazar günü', 'birinci gün', 'ikinci gün', 'üçüncü gün', 'dördüncü gün', 'beşinci gün', 'altıncı gün'], + 'weekdays_short' => ['baz', 'bir', 'iki', 'üçü', 'dör', 'beş', 'alt'], + 'weekdays_min' => ['baz', 'bir', 'iki', 'üçü', 'dör', 'beş', 'alt'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bez.php b/vendor/nesbot/carbon/src/Carbon/Lang/bez.php new file mode 100644 index 0000000..d59c5ef --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bez.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['pamilau', 'pamunyi'], + 'weekdays' => ['pa mulungu', 'pa shahuviluha', 'pa hivili', 'pa hidatu', 'pa hitayi', 'pa hihanu', 'pa shahulembela'], + 'weekdays_short' => ['Mul', 'Vil', 'Hiv', 'Hid', 'Hit', 'Hih', 'Lem'], + 'weekdays_min' => ['Mul', 'Vil', 'Hiv', 'Hid', 'Hit', 'Hih', 'Lem'], + 'months' => ['pa mwedzi gwa hutala', 'pa mwedzi gwa wuvili', 'pa mwedzi gwa wudatu', 'pa mwedzi gwa wutai', 'pa mwedzi gwa wuhanu', 'pa mwedzi gwa sita', 'pa mwedzi gwa saba', 'pa mwedzi gwa nane', 'pa mwedzi gwa tisa', 'pa mwedzi gwa kumi', 'pa mwedzi gwa kumi na moja', 'pa mwedzi gwa kumi na mbili'], + 'months_short' => ['Hut', 'Vil', 'Dat', 'Tai', 'Han', 'Sit', 'Sab', 'Nan', 'Tis', 'Kum', 'Kmj', 'Kmb'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bg.php b/vendor/nesbot/carbon/src/Carbon/Lang/bg.php new file mode 100644 index 0000000..f768074 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bg.php @@ -0,0 +1,114 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - François B + * - Serhan Apaydın + * - JD Isaacks + * - Glavić + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count година|:count години', + 'a_year' => 'година|:count години', + 'y' => ':count година|:count години', + 'month' => ':count месец|:count месеца', + 'a_month' => 'месец|:count месеца', + 'm' => ':count месец|:count месеца', + 'week' => ':count седмица|:count седмици', + 'a_week' => 'седмица|:count седмици', + 'w' => ':count седмица|:count седмици', + 'day' => ':count ден|:count дни', + 'a_day' => 'ден|:count дни', + 'd' => ':count ден|:count дни', + 'hour' => ':count час|:count часа', + 'a_hour' => 'час|:count часа', + 'h' => ':count час|:count часа', + 'minute' => ':count минута|:count минути', + 'a_minute' => 'минута|:count минути', + 'min' => ':count минута|:count минути', + 'second' => ':count секунда|:count секунди', + 'a_second' => 'няколко секунди|:count секунди', + 's' => ':count секунда|:count секунди', + 'ago' => 'преди :time', + 'from_now' => 'след :time', + 'after' => 'след :time', + 'before' => 'преди :time', + 'diff_now' => 'сега', + 'diff_today' => 'Днес', + 'diff_today_regexp' => 'Днес(?:\\s+в)?', + 'diff_yesterday' => 'вчера', + 'diff_yesterday_regexp' => 'Вчера(?:\\s+в)?', + 'diff_tomorrow' => 'утре', + 'diff_tomorrow_regexp' => 'Утре(?:\\s+в)?', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'D.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY H:mm', + 'LLLL' => 'dddd, D MMMM YYYY H:mm', + ], + 'calendar' => [ + 'sameDay' => '[Днес в] LT', + 'nextDay' => '[Утре в] LT', + 'nextWeek' => 'dddd [в] LT', + 'lastDay' => '[Вчера в] LT', + 'lastWeek' => function (CarbonInterface $current) { + switch ($current->dayOfWeek) { + case 0: + case 3: + case 6: + return '[В изминалата] dddd [в] LT'; + default: + return '[В изминалия] dddd [в] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + $lastDigit = $number % 10; + $last2Digits = $number % 100; + if ($number === 0) { + return "$number-ев"; + } + if ($last2Digits === 0) { + return "$number-ен"; + } + if ($last2Digits > 10 && $last2Digits < 20) { + return "$number-ти"; + } + if ($lastDigit === 1) { + return "$number-ви"; + } + if ($lastDigit === 2) { + return "$number-ри"; + } + if ($lastDigit === 7 || $lastDigit === 8) { + return "$number-ми"; + } + + return "$number-ти"; + }, + 'months' => ['януари', 'февруари', 'март', 'април', 'май', 'юни', 'юли', 'август', 'септември', 'октомври', 'ноември', 'декември'], + 'months_short' => ['яну', 'фев', 'мар', 'апр', 'май', 'юни', 'юли', 'авг', 'сеп', 'окт', 'ное', 'дек'], + 'weekdays' => ['неделя', 'понеделник', 'вторник', 'сряда', 'четвъртък', 'петък', 'събота'], + 'weekdays_short' => ['нед', 'пон', 'вто', 'сря', 'чет', 'пет', 'съб'], + 'weekdays_min' => ['нд', 'пн', 'вт', 'ср', 'чт', 'пт', 'сб'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' и '], + 'meridiem' => ['преди обяд', 'следобед'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bg_BG.php b/vendor/nesbot/carbon/src/Carbon/Lang/bg_BG.php new file mode 100644 index 0000000..b53874d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bg_BG.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/bg.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bhb.php b/vendor/nesbot/carbon/src/Carbon/Lang/bhb.php new file mode 100644 index 0000000..49f0803 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bhb.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/bhb_IN.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bhb_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/bhb_IN.php new file mode 100644 index 0000000..ab557cb --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bhb_IN.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Samsung Electronics Co., Ltd. alexey.merzlyakov@samsung.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + 'weekdays' => ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], + 'weekdays_short' => ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + 'weekdays_min' => ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bho.php b/vendor/nesbot/carbon/src/Carbon/Lang/bho.php new file mode 100644 index 0000000..e9ed0b6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bho.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/bho_IN.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bho_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/bho_IN.php new file mode 100644 index 0000000..bc54f36 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bho_IN.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bhashaghar@googlegroups.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['जनवरी', 'फरवरी', 'मार्च', 'अप्रैल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितम्बर', 'अक्टूबर', 'नवम्बर', 'दिसम्बर"'], + 'months_short' => ['जनवरी', 'फरवरी', 'मार्च', 'अप्रैल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितम्बर', 'अक्टूबर', 'नवम्बर', 'दिसम्बर"'], + 'weekdays' => ['रविवार', 'सोमवार', 'मंगलवार', 'बुधवार', 'गुरुवार', 'शुक्रवार', 'शनिवार'], + 'weekdays_short' => ['रवि', 'सोम', 'मंगल', 'बुध', 'गुरु', 'शुक्र', 'शनि'], + 'weekdays_min' => ['रवि', 'सोम', 'मंगल', 'बुध', 'गुरु', 'शुक्र', 'शनि'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['पूर्वाह्न', 'अपराह्न'], + + 'hour' => ':count मौसम', + 'h' => ':count मौसम', + 'a_hour' => ':count मौसम', + + 'minute' => ':count कला', + 'min' => ':count कला', + 'a_minute' => ':count कला', + + 'second' => ':count सोमार', + 's' => ':count सोमार', + 'a_second' => ':count सोमार', + + 'year' => ':count साल', + 'y' => ':count साल', + 'a_year' => ':count साल', + + 'month' => ':count महिना', + 'm' => ':count महिना', + 'a_month' => ':count महिना', + + 'week' => ':count सप्ताह', + 'w' => ':count सप्ताह', + 'a_week' => ':count सप्ताह', + + 'day' => ':count दिन', + 'd' => ':count दिन', + 'a_day' => ':count दिन', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bi.php b/vendor/nesbot/carbon/src/Carbon/Lang/bi.php new file mode 100644 index 0000000..dd08128 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bi.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/bi_VU.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bi_VU.php b/vendor/nesbot/carbon/src/Carbon/Lang/bi_VU.php new file mode 100644 index 0000000..1fe7770 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bi_VU.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Samsung Electronics Co., Ltd. akhilesh.k@samsung.com & maninder1.s@samsung.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'dddd DD MMM YYYY', + ], + 'months' => ['jenuware', 'febwari', 'maj', 'epril', 'mei', 'jun', 'julae', 'ogis', 'septemba', 'oktoba', 'novemba', 'disemba'], + 'months_short' => ['jen', 'feb', 'maj', 'epr', 'mei', 'jun', 'jul', 'ogi', 'sep', 'okt', 'nov', 'dis'], + 'weekdays' => ['sande', 'mande', 'maj', 'wota', 'fraede', 'sarede'], + 'weekdays_short' => ['san', 'man', 'maj', 'wot', 'fra', 'sar'], + 'weekdays_min' => ['san', 'man', 'maj', 'wot', 'fra', 'sar'], + + 'year' => ':count seven', // less reliable + 'y' => ':count seven', // less reliable + 'a_year' => ':count seven', // less reliable + + 'month' => ':count mi', // less reliable + 'm' => ':count mi', // less reliable + 'a_month' => ':count mi', // less reliable + + 'week' => ':count sarede', // less reliable + 'w' => ':count sarede', // less reliable + 'a_week' => ':count sarede', // less reliable + + 'day' => ':count betde', // less reliable + 'd' => ':count betde', // less reliable + 'a_day' => ':count betde', // less reliable + + 'hour' => ':count klok', // less reliable + 'h' => ':count klok', // less reliable + 'a_hour' => ':count klok', // less reliable + + 'minute' => ':count smol', // less reliable + 'min' => ':count smol', // less reliable + 'a_minute' => ':count smol', // less reliable + + 'second' => ':count tu', // less reliable + 's' => ':count tu', // less reliable + 'a_second' => ':count tu', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bm.php b/vendor/nesbot/carbon/src/Carbon/Lang/bm.php new file mode 100644 index 0000000..92822d2 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bm.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Estelle Comment + */ +return [ + 'year' => 'san :count', + 'a_year' => '{1}san kelen|san :count', + 'y' => 'san :count', + 'month' => 'kalo :count', + 'a_month' => '{1}kalo kelen|kalo :count', + 'm' => 'k. :count', + 'week' => 'dɔgɔkun :count', + 'a_week' => 'dɔgɔkun kelen', + 'w' => 'd. :count', + 'day' => 'tile :count', + 'd' => 't. :count', + 'a_day' => '{1}tile kelen|tile :count', + 'hour' => 'lɛrɛ :count', + 'a_hour' => '{1}lɛrɛ kelen|lɛrɛ :count', + 'h' => 'l. :count', + 'minute' => 'miniti :count', + 'a_minute' => '{1}miniti kelen|miniti :count', + 'min' => 'm. :count', + 'second' => 'sekondi :count', + 'a_second' => '{1}sanga dama dama|sekondi :count', + 's' => 'sek. :count', + 'ago' => 'a bɛ :time bɔ', + 'from_now' => ':time kɔnɔ', + 'diff_today' => 'Bi', + 'diff_yesterday' => 'Kunu', + 'diff_yesterday_regexp' => 'Kunu(?:\\s+lɛrɛ)?', + 'diff_tomorrow' => 'Sini', + 'diff_tomorrow_regexp' => 'Sini(?:\\s+lɛrɛ)?', + 'diff_today_regexp' => 'Bi(?:\\s+lɛrɛ)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'MMMM [tile] D [san] YYYY', + 'LLL' => 'MMMM [tile] D [san] YYYY [lɛrɛ] HH:mm', + 'LLLL' => 'dddd MMMM [tile] D [san] YYYY [lɛrɛ] HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Bi lɛrɛ] LT', + 'nextDay' => '[Sini lɛrɛ] LT', + 'nextWeek' => 'dddd [don lɛrɛ] LT', + 'lastDay' => '[Kunu lɛrɛ] LT', + 'lastWeek' => 'dddd [tɛmɛnen lɛrɛ] LT', + 'sameElse' => 'L', + ], + 'months' => ['Zanwuyekalo', 'Fewuruyekalo', 'Marisikalo', 'Awirilikalo', 'Mɛkalo', 'Zuwɛnkalo', 'Zuluyekalo', 'Utikalo', 'Sɛtanburukalo', 'ɔkutɔburukalo', 'Nowanburukalo', 'Desanburukalo'], + 'months_short' => ['Zan', 'Few', 'Mar', 'Awi', 'Mɛ', 'Zuw', 'Zul', 'Uti', 'Sɛt', 'ɔku', 'Now', 'Des'], + 'weekdays' => ['Kari', 'Ntɛnɛn', 'Tarata', 'Araba', 'Alamisa', 'Juma', 'Sibiri'], + 'weekdays_short' => ['Kar', 'Ntɛ', 'Tar', 'Ara', 'Ala', 'Jum', 'Sib'], + 'weekdays_min' => ['Ka', 'Nt', 'Ta', 'Ar', 'Al', 'Ju', 'Si'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' ni '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bn.php b/vendor/nesbot/carbon/src/Carbon/Lang/bn.php new file mode 100644 index 0000000..8e14789 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bn.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - Shakib Hossain + * - Raju + * - Aniruddha Adhikary + * - JD Isaacks + * - Saiful Islam + * - Faisal Islam + */ +return [ + 'year' => ':count বছর', + 'a_year' => 'এক বছর|:count বছর', + 'y' => '১ বছর|:count বছর', + 'month' => ':count মাস', + 'a_month' => 'এক মাস|:count মাস', + 'm' => '১ মাস|:count মাস', + 'week' => ':count সপ্তাহ', + 'a_week' => '১ সপ্তাহ|:count সপ্তাহ', + 'w' => '১ সপ্তাহ|:count সপ্তাহ', + 'day' => ':count দিন', + 'a_day' => 'এক দিন|:count দিন', + 'd' => '১ দিন|:count দিন', + 'hour' => ':count ঘন্টা', + 'a_hour' => 'এক ঘন্টা|:count ঘন্টা', + 'h' => '১ ঘন্টা|:count ঘন্টা', + 'minute' => ':count মিনিট', + 'a_minute' => 'এক মিনিট|:count মিনিট', + 'min' => '১ মিনিট|:count মিনিট', + 'second' => ':count সেকেন্ড', + 'a_second' => 'কয়েক সেকেন্ড|:count সেকেন্ড', + 's' => '১ সেকেন্ড|:count সেকেন্ড', + 'ago' => ':time আগে', + 'from_now' => ':time পরে', + 'after' => ':time পরে', + 'before' => ':time আগে', + 'diff_now' => 'এখন', + 'diff_today' => 'আজ', + 'diff_yesterday' => 'গতকাল', + 'diff_tomorrow' => 'আগামীকাল', + 'period_recurrences' => ':count বার|:count বার', + 'period_interval' => 'প্রতি :interval', + 'period_start_date' => ':date থেকে', + 'period_end_date' => ':date পর্যন্ত', + 'formats' => [ + 'LT' => 'A Oh:Om সময়', + 'LTS' => 'A Oh:Om:Os সময়', + 'L' => 'OD/OM/OY', + 'LL' => 'OD MMMM OY', + 'LLL' => 'OD MMMM OY, A Oh:Om সময়', + 'LLLL' => 'dddd, OD MMMM OY, A Oh:Om সময়', + ], + 'calendar' => [ + 'sameDay' => '[আজ] LT', + 'nextDay' => '[আগামীকাল] LT', + 'nextWeek' => 'dddd, LT', + 'lastDay' => '[গতকাল] LT', + 'lastWeek' => '[গত] dddd, LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'রাত'; + } + if ($hour < 10) { + return 'সকাল'; + } + if ($hour < 17) { + return 'দুপুর'; + } + if ($hour < 20) { + return 'বিকাল'; + } + + return 'রাত'; + }, + 'months' => ['জানুয়ারী', 'ফেব্রুয়ারি', 'মার্চ', 'এপ্রিল', 'মে', 'জুন', 'জুলাই', 'আগস্ট', 'সেপ্টেম্বর', 'অক্টোবর', 'নভেম্বর', 'ডিসেম্বর'], + 'months_short' => ['জানু', 'ফেব', 'মার্চ', 'এপ্র', 'মে', 'জুন', 'জুল', 'আগ', 'সেপ্ট', 'অক্টো', 'নভে', 'ডিসে'], + 'weekdays' => ['রবিবার', 'সোমবার', 'মঙ্গলবার', 'বুধবার', 'বৃহস্পতিবার', 'শুক্রবার', 'শনিবার'], + 'weekdays_short' => ['রবি', 'সোম', 'মঙ্গল', 'বুধ', 'বৃহস্পতি', 'শুক্র', 'শনি'], + 'weekdays_min' => ['রবি', 'সোম', 'মঙ্গ', 'বুধ', 'বৃহঃ', 'শুক্র', 'শনি'], + 'list' => [', ', ' এবং '], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'weekdays_standalone' => ['রবিবার', 'সোমবার', 'মঙ্গলবার', 'বুধবার', 'বৃহষ্পতিবার', 'শুক্রবার', 'শনিবার'], + 'weekdays_min_standalone' => ['রঃ', 'সোঃ', 'মঃ', 'বুঃ', 'বৃঃ', 'শুঃ', 'শনি'], + 'months_short_standalone' => ['জানুয়ারী', 'ফেব্রুয়ারী', 'মার্চ', 'এপ্রিল', 'মে', 'জুন', 'জুলাই', 'আগস্ট', 'সেপ্টেম্বর', 'অক্টোবর', 'নভেম্বর', 'ডিসেম্বর'], + 'alt_numbers' => ['০', '১', '২', '৩', '৪', '৫', '৬', '৭', '৮', '৯'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bn_BD.php b/vendor/nesbot/carbon/src/Carbon/Lang/bn_BD.php new file mode 100644 index 0000000..b5b28dd --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bn_BD.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ankur Group, Taneem Ahmed, Jamil Ahmed + */ +return array_replace_recursive(require __DIR__.'/bn.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['জানুয়ারী', 'ফেব্রুয়ারী', 'মার্চ', 'এপ্রিল', 'মে', 'জুন', 'জুলাই', 'আগস্ট', 'সেপ্টেম্বর', 'অক্টোবর', 'নভেম্বর', 'ডিসেম্বর'], + 'months_short' => ['জানু', 'ফেব', 'মার্চ', 'এপ্রিল', 'মে', 'জুন', 'জুলাই', 'আগস্ট', 'সেপ্টেম্বর', 'অক্টোবর', 'নভেম্বর', 'ডিসেম্বর'], + 'weekdays' => ['রবিবার', 'সোমবার', 'মঙ্গলবার', 'বুধবার', 'বৃহস্পতিবার', 'শুক্রবার', 'শনিবার'], + 'weekdays_short' => ['রবি', 'সোম', 'মঙ্গল', 'বুধ', 'বৃহঃ', 'শুক্র', 'শনি'], + 'weekdays_min' => ['রবি', 'সোম', 'মঙ্গল', 'বুধ', 'বৃহঃ', 'শুক্র', 'শনি'], + 'first_day_of_week' => 5, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bn_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/bn_IN.php new file mode 100644 index 0000000..8b3a50e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bn_IN.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/bn.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['জানুয়ারী', 'ফেব্রুয়ারী', 'মার্চ', 'এপ্রিল', 'মে', 'জুন', 'জুলাই', 'আগস্ট', 'সেপ্টেম্বর', 'অক্টোবর', 'নভেম্বর', 'ডিসেম্বর'], + 'months_short' => ['জানু', 'ফেব', 'মার্চ', 'এপ্রিল', 'মে', 'জুন', 'জুলাই', 'আগস্ট', 'সেপ্টেম্বর', 'অক্টোবর', 'নভেম্বর', 'ডিসেম্বর'], + 'weekdays' => ['রবিবার', 'সোমবার', 'মঙ্গলবার', 'বুধবার', 'বৃহস্পতিবার', 'শুক্রবার', 'শনিবার'], + 'weekdays_short' => ['রবি', 'সোম', 'মঙ্গল', 'বুধ', 'বৃহস্পতি', 'শুক্র', 'শনি'], + 'weekdays_min' => ['রবি', 'সোম', 'মঙ্গল', 'বুধ', 'বৃহস্পতি', 'শুক্র', 'শনি'], + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bo.php b/vendor/nesbot/carbon/src/Carbon/Lang/bo.php new file mode 100644 index 0000000..99e1bf4 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bo.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - JD Isaacks + */ +return [ + 'year' => '{1}ལོ་གཅིག|]1,Inf[:count ལོ', + 'month' => '{1}ཟླ་བ་གཅིག|]1,Inf[:count ཟླ་བ', + 'week' => ':count བདུན་ཕྲག', + 'day' => '{1}ཉིན་གཅིག|]1,Inf[:count ཉིན་', + 'hour' => '{1}ཆུ་ཚོད་གཅིག|]1,Inf[:count ཆུ་ཚོད', + 'minute' => '{1}སྐར་མ་གཅིག|]1,Inf[:count སྐར་མ', + 'second' => '{1}ལམ་སང|]1,Inf[:count སྐར་ཆ།', + 'ago' => ':time སྔན་ལ', + 'from_now' => ':time ལ་', + 'diff_yesterday' => 'ཁ་སང', + 'diff_today' => 'དི་རིང', + 'diff_tomorrow' => 'སང་ཉིན', + 'formats' => [ + 'LT' => 'A h:mm', + 'LTS' => 'A h:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, A h:mm', + 'LLLL' => 'dddd, D MMMM YYYY, A h:mm', + ], + 'calendar' => [ + 'sameDay' => '[དི་རིང] LT', + 'nextDay' => '[སང་ཉིན] LT', + 'nextWeek' => '[བདུན་ཕྲག་རྗེས་མ], LT', + 'lastDay' => '[ཁ་སང] LT', + 'lastWeek' => '[བདུན་ཕྲག་མཐའ་མ] dddd, LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'མཚན་མོ'; + } + if ($hour < 10) { + return 'ཞོགས་ཀས'; + } + if ($hour < 17) { + return 'ཉིན་གུང'; + } + if ($hour < 20) { + return 'དགོང་དག'; + } + + return 'མཚན་མོ'; + }, + 'months' => ['ཟླ་བ་དང་པོ', 'ཟླ་བ་གཉིས་པ', 'ཟླ་བ་གསུམ་པ', 'ཟླ་བ་བཞི་པ', 'ཟླ་བ་ལྔ་པ', 'ཟླ་བ་དྲུག་པ', 'ཟླ་བ་བདུན་པ', 'ཟླ་བ་བརྒྱད་པ', 'ཟླ་བ་དགུ་པ', 'ཟླ་བ་བཅུ་པ', 'ཟླ་བ་བཅུ་གཅིག་པ', 'ཟླ་བ་བཅུ་གཉིས་པ'], + 'months_short' => ['ཟླ་བ་དང་པོ', 'ཟླ་བ་གཉིས་པ', 'ཟླ་བ་གསུམ་པ', 'ཟླ་བ་བཞི་པ', 'ཟླ་བ་ལྔ་པ', 'ཟླ་བ་དྲུག་པ', 'ཟླ་བ་བདུན་པ', 'ཟླ་བ་བརྒྱད་པ', 'ཟླ་བ་དགུ་པ', 'ཟླ་བ་བཅུ་པ', 'ཟླ་བ་བཅུ་གཅིག་པ', 'ཟླ་བ་བཅུ་གཉིས་པ'], + 'weekdays' => ['གཟའ་ཉི་མ་', 'གཟའ་ཟླ་བ་', 'གཟའ་མིག་དམར་', 'གཟའ་ལྷག་པ་', 'གཟའ་ཕུར་བུ', 'གཟའ་པ་སངས་', 'གཟའ་སྤེན་པ་'], + 'weekdays_short' => ['ཉི་མ་', 'ཟླ་བ་', 'མིག་དམར་', 'ལྷག་པ་', 'ཕུར་བུ', 'པ་སངས་', 'སྤེན་པ་'], + 'weekdays_min' => ['ཉི་མ་', 'ཟླ་བ་', 'མིག་དམར་', 'ལྷག་པ་', 'ཕུར་བུ', 'པ་སངས་', 'སྤེན་པ་'], + 'list' => [', ', ' ཨནད་ '], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'months_standalone' => ['ཟླ་བ་དང་པོ་', 'ཟླ་བ་གཉིས་པ་', 'ཟླ་བ་གསུམ་པ་', 'ཟླ་བ་བཞི་པ་', 'ཟླ་བ་ལྔ་པ་', 'ཟླ་བ་དྲུག་པ་', 'ཟླ་བ་བདུན་པ་', 'ཟླ་བ་བརྒྱད་པ་', 'ཟླ་བ་དགུ་པ་', 'ཟླ་བ་བཅུ་པ་', 'ཟླ་བ་བཅུ་གཅིག་པ་', 'ཟླ་བ་བཅུ་གཉིས་པ་'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bo_CN.php b/vendor/nesbot/carbon/src/Carbon/Lang/bo_CN.php new file mode 100644 index 0000000..380abb1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bo_CN.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/bo.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bo_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/bo_IN.php new file mode 100644 index 0000000..ca50d04 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bo_IN.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/bo.php', [ + 'meridiem' => ['སྔ་དྲོ་', 'ཕྱི་དྲོ་'], + 'weekdays' => ['གཟའ་ཉི་མ་', 'གཟའ་ཟླ་བ་', 'གཟའ་མིག་དམར་', 'གཟའ་ལྷག་པ་', 'གཟའ་ཕུར་བུ་', 'གཟའ་པ་སངས་', 'གཟའ་སྤེན་པ་'], + 'weekdays_short' => ['ཉི་མ་', 'ཟླ་བ་', 'མིག་དམར་', 'ལྷག་པ་', 'ཕུར་བུ་', 'པ་སངས་', 'སྤེན་པ་'], + 'weekdays_min' => ['ཉི་མ་', 'ཟླ་བ་', 'མིག་དམར་', 'ལྷག་པ་', 'ཕུར་བུ་', 'པ་སངས་', 'སྤེན་པ་'], + 'months' => ['ཟླ་བ་དང་པོ', 'ཟླ་བ་གཉིས་པ', 'ཟླ་བ་གསུམ་པ', 'ཟླ་བ་བཞི་པ', 'ཟླ་བ་ལྔ་པ', 'ཟླ་བ་དྲུག་པ', 'ཟླ་བ་བདུན་པ', 'ཟླ་བ་བརྒྱད་པ', 'ཟླ་བ་དགུ་པ', 'ཟླ་བ་བཅུ་པ', 'ཟླ་བ་བཅུ་གཅིག་པ', 'ཟླ་བ་བཅུ་གཉིས་པ'], + 'months_short' => ['ཟླ་༡', 'ཟླ་༢', 'ཟླ་༣', 'ཟླ་༤', 'ཟླ་༥', 'ཟླ་༦', 'ཟླ་༧', 'ཟླ་༨', 'ཟླ་༩', 'ཟླ་༡༠', 'ཟླ་༡༡', 'ཟླ་༡༢'], + 'months_standalone' => ['ཟླ་བ་དང་པོ་', 'ཟླ་བ་གཉིས་པ་', 'ཟླ་བ་གསུམ་པ་', 'ཟླ་བ་བཞི་པ་', 'ཟླ་བ་ལྔ་པ་', 'ཟླ་བ་དྲུག་པ་', 'ཟླ་བ་བདུན་པ་', 'ཟླ་བ་བརྒྱད་པ་', 'ཟླ་བ་དགུ་པ་', 'ཟླ་བ་བཅུ་པ་', 'ཟླ་བ་བཅུ་གཅིག་པ་', 'ཟླ་བ་བཅུ་གཉིས་པ་'], + 'weekend' => [0, 0], + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'YYYY-MM-DD', + 'LL' => 'YYYY ལོའི་MMMཚེས་D', + 'LLL' => 'སྤྱི་ལོ་YYYY MMMMའི་ཚེས་D h:mm a', + 'LLLL' => 'YYYY MMMMའི་ཚེས་D, dddd h:mm a', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/br.php b/vendor/nesbot/carbon/src/Carbon/Lang/br.php new file mode 100644 index 0000000..583472f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/br.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Serhan Apaydın + * - JD Isaacks + */ +return [ + 'year' => '{1}:count bloaz|{3,4,5,9}:count bloaz|[0,Inf[:count vloaz', + 'a_year' => '{1}ur bloaz|{3,4,5,9}:count bloaz|[0,Inf[:count vloaz', + 'month' => '{1}:count miz|{2}:count viz|[0,Inf[:count miz', + 'a_month' => '{1}ur miz|{2}:count viz|[0,Inf[:count miz', + 'week' => ':count sizhun', + 'a_week' => '{1}ur sizhun|:count sizhun', + 'day' => '{1}:count devezh|{2}:count zevezh|[0,Inf[:count devezh', + 'a_day' => '{1}un devezh|{2}:count zevezh|[0,Inf[:count devezh', + 'hour' => ':count eur', + 'a_hour' => '{1}un eur|:count eur', + 'minute' => '{1}:count vunutenn|{2}:count vunutenn|[0,Inf[:count munutenn', + 'a_minute' => '{1}ur vunutenn|{2}:count vunutenn|[0,Inf[:count munutenn', + 'second' => ':count eilenn', + 'a_second' => '{1}un nebeud segondennoù|[0,Inf[:count eilenn', + 'ago' => ':time \'zo', + 'from_now' => 'a-benn :time', + 'diff_now' => 'bremañ', + 'diff_today' => 'Hiziv', + 'diff_today_regexp' => 'Hiziv(?:\\s+da)?', + 'diff_yesterday' => 'decʼh', + 'diff_yesterday_regexp' => 'Dec\'h(?:\\s+da)?', + 'diff_tomorrow' => 'warcʼhoazh', + 'diff_tomorrow_regexp' => 'Warc\'hoazh(?:\\s+da)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D [a viz] MMMM YYYY', + 'LLL' => 'D [a viz] MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D [a viz] MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Hiziv da] LT', + 'nextDay' => '[Warc\'hoazh da] LT', + 'nextWeek' => 'dddd [da] LT', + 'lastDay' => '[Dec\'h da] LT', + 'lastWeek' => 'dddd [paset da] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + return $number.($number === 1 ? 'añ' : 'vet'); + }, + 'months' => ['Genver', 'C\'hwevrer', 'Meurzh', 'Ebrel', 'Mae', 'Mezheven', 'Gouere', 'Eost', 'Gwengolo', 'Here', 'Du', 'Kerzu'], + 'months_short' => ['Gen', 'C\'hwe', 'Meu', 'Ebr', 'Mae', 'Eve', 'Gou', 'Eos', 'Gwe', 'Her', 'Du', 'Ker'], + 'weekdays' => ['Sul', 'Lun', 'Meurzh', 'Merc\'her', 'Yaou', 'Gwener', 'Sadorn'], + 'weekdays_short' => ['Sul', 'Lun', 'Meu', 'Mer', 'Yao', 'Gwe', 'Sad'], + 'weekdays_min' => ['Su', 'Lu', 'Me', 'Mer', 'Ya', 'Gw', 'Sa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' hag '], + 'meridiem' => ['A.M.', 'G.M.'], + + 'y' => ':count bl.', + 'd' => ':count d', + 'h' => ':count e', + 'min' => ':count min', + 's' => ':count s', +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/br_FR.php b/vendor/nesbot/carbon/src/Carbon/Lang/br_FR.php new file mode 100644 index 0000000..7f54185 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/br_FR.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/br.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/brx.php b/vendor/nesbot/carbon/src/Carbon/Lang/brx.php new file mode 100644 index 0000000..a0a7bf9 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/brx.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/brx_IN.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/brx_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/brx_IN.php new file mode 100644 index 0000000..2d80ced --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/brx_IN.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Red Hat Pune bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'M/D/YY', + ], + 'months' => ['जानुवारी', 'फेब्रुवारी', 'मार्स', 'एफ्रिल', 'मे', 'जुन', 'जुलाइ', 'आगस्थ', 'सेबथेज्ब़र', 'अखथबर', 'नबेज्ब़र', 'दिसेज्ब़र'], + 'months_short' => ['जानुवारी', 'फेब्रुवारी', 'मार्स', 'एप्रिल', 'मे', 'जुन', 'जुलाइ', 'आगस्थ', 'सेबथेज्ब़र', 'अखथबर', 'नबेज्ब़र', 'दिसेज्ब़र'], + 'weekdays' => ['रबिबार', 'सोबार', 'मंगलबार', 'बुदबार', 'बिसथिबार', 'सुखुरबार', 'सुनिबार'], + 'weekdays_short' => ['रबि', 'सम', 'मंगल', 'बुद', 'बिसथि', 'सुखुर', 'सुनि'], + 'weekdays_min' => ['रबि', 'सम', 'मंगल', 'बुद', 'बिसथि', 'सुखुर', 'सुनि'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['फुं.', 'बेलासे.'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bs.php b/vendor/nesbot/carbon/src/Carbon/Lang/bs.php new file mode 100644 index 0000000..e5d6808 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bs.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bokideckonja + * - Josh Soref + * - François B + * - shaishavgandhi05 + * - Serhan Apaydın + * - JD Isaacks + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count godina|:count godine|:count godina', + 'y' => ':count godina|:count godine|:count godina', + 'month' => ':count mjesec|:count mjeseca|:count mjeseci', + 'm' => ':count mjesec|:count mjeseca|:count mjeseci', + 'week' => ':count sedmice|:count sedmicu|:count sedmica', + 'w' => ':count sedmice|:count sedmicu|:count sedmica', + 'day' => ':count dan|:count dana|:count dana', + 'd' => ':count dan|:count dana|:count dana', + 'hour' => ':count sat|:count sata|:count sati', + 'h' => ':count sat|:count sata|:count sati', + 'minute' => ':count minut|:count minuta|:count minuta', + 'min' => ':count minut|:count minuta|:count minuta', + 'second' => ':count sekund|:count sekunda|:count sekundi', + 's' => ':count sekund|:count sekunda|:count sekundi', + 'ago' => 'prije :time', + 'from_now' => 'za :time', + 'after' => 'nakon :time', + 'before' => ':time ranije', + 'diff_now' => 'sada', + 'diff_today' => 'danas', + 'diff_today_regexp' => 'danas(?:\\s+u)?', + 'diff_yesterday' => 'jučer', + 'diff_yesterday_regexp' => 'jučer(?:\\s+u)?', + 'diff_tomorrow' => 'sutra', + 'diff_tomorrow_regexp' => 'sutra(?:\\s+u)?', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY H:mm', + 'LLLL' => 'dddd, D. MMMM YYYY H:mm', + ], + 'calendar' => [ + 'sameDay' => '[danas u] LT', + 'nextDay' => '[sutra u] LT', + 'nextWeek' => function (CarbonInterface $current) { + switch ($current->dayOfWeek) { + case 0: + return '[u] [nedjelju] [u] LT'; + case 3: + return '[u] [srijedu] [u] LT'; + case 6: + return '[u] [subotu] [u] LT'; + default: + return '[u] dddd [u] LT'; + } + }, + 'lastDay' => '[jučer u] LT', + 'lastWeek' => function (CarbonInterface $current) { + switch ($current->dayOfWeek) { + case 0: + case 3: + return '[prošlu] dddd [u] LT'; + case 6: + return '[prošle] [subote] [u] LT'; + default: + return '[prošli] dddd [u] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['januar', 'februar', 'mart', 'april', 'maj', 'juni', 'juli', 'august', 'septembar', 'oktobar', 'novembar', 'decembar'], + 'months_short' => ['jan.', 'feb.', 'mar.', 'apr.', 'maj.', 'jun.', 'jul.', 'aug.', 'sep.', 'okt.', 'nov.', 'dec.'], + 'weekdays' => ['nedjelja', 'ponedjeljak', 'utorak', 'srijeda', 'četvrtak', 'petak', 'subota'], + 'weekdays_short' => ['ned.', 'pon.', 'uto.', 'sri.', 'čet.', 'pet.', 'sub.'], + 'weekdays_min' => ['ne', 'po', 'ut', 'sr', 'če', 'pe', 'su'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' i '], + 'meridiem' => ['prijepodne', 'popodne'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bs_BA.php b/vendor/nesbot/carbon/src/Carbon/Lang/bs_BA.php new file mode 100644 index 0000000..0a59117 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bs_BA.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/bs.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bs_Cyrl.php b/vendor/nesbot/carbon/src/Carbon/Lang/bs_Cyrl.php new file mode 100644 index 0000000..e1a1744 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bs_Cyrl.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/bs.php', [ + 'meridiem' => ['пре подне', 'поподне'], + 'weekdays' => ['недјеља', 'понедјељак', 'уторак', 'сриједа', 'четвртак', 'петак', 'субота'], + 'weekdays_short' => ['нед', 'пон', 'уто', 'сри', 'чет', 'пет', 'суб'], + 'weekdays_min' => ['нед', 'пон', 'уто', 'сри', 'чет', 'пет', 'суб'], + 'months' => ['јануар', 'фебруар', 'март', 'април', 'мај', 'јуни', 'јули', 'аугуст', 'септембар', 'октобар', 'новембар', 'децембар'], + 'months_short' => ['јан', 'феб', 'мар', 'апр', 'мај', 'јун', 'јул', 'ауг', 'сеп', 'окт', 'нов', 'дец'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D.M.YYYY.', + 'LL' => 'DD.MM.YYYY.', + 'LLL' => 'DD. MMMM YYYY. HH:mm', + 'LLLL' => 'dddd, DD. MMMM YYYY. HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/bs_Latn.php b/vendor/nesbot/carbon/src/Carbon/Lang/bs_Latn.php new file mode 100644 index 0000000..b4e363e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/bs_Latn.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/bs.php', [ +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/byn.php b/vendor/nesbot/carbon/src/Carbon/Lang/byn.php new file mode 100644 index 0000000..7125f3d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/byn.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/byn_ER.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/byn_ER.php b/vendor/nesbot/carbon/src/Carbon/Lang/byn_ER.php new file mode 100644 index 0000000..ad67533 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/byn_ER.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['ልደትሪ', 'ካብኽብቲ', 'ክብላ', 'ፋጅኺሪ', 'ክቢቅሪ', 'ምኪኤል ትጓ̅ኒሪ', 'ኰርኩ', 'ማርያም ትሪ', 'ያኸኒ መሳቅለሪ', 'መተሉ', 'ምኪኤል መሽወሪ', 'ተሕሳስሪ'], + 'months_short' => ['ልደት', 'ካብኽ', 'ክብላ', 'ፋጅኺ', 'ክቢቅ', 'ም/ት', 'ኰር', 'ማርያ', 'ያኸኒ', 'መተሉ', 'ም/ም', 'ተሕሳ'], + 'weekdays' => ['ሰንበር ቅዳዅ', 'ሰኑ', 'ሰሊጝ', 'ለጓ ወሪ ለብዋ', 'ኣምድ', 'ኣርብ', 'ሰንበር ሽጓዅ'], + 'weekdays_short' => ['ሰ/ቅ', 'ሰኑ', 'ሰሊጝ', 'ለጓ', 'ኣምድ', 'ኣርብ', 'ሰ/ሽ'], + 'weekdays_min' => ['ሰ/ቅ', 'ሰኑ', 'ሰሊጝ', 'ለጓ', 'ኣምድ', 'ኣርብ', 'ሰ/ሽ'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ፋዱስ ጃብ', 'ፋዱስ ደምቢ'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ca.php b/vendor/nesbot/carbon/src/Carbon/Lang/ca.php new file mode 100644 index 0000000..b8b1994 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ca.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - mestremuten + * - François B + * - Marc Ordinas i Llopis + * - Pere Orga + * - JD Isaacks + * - Quentí + * - Víctor Díaz + * - Xavi + * - qcardona + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count any|:count anys', + 'a_year' => 'un any|:count anys', + 'y' => ':count any|:count anys', + 'month' => ':count mes|:count mesos', + 'a_month' => 'un mes|:count mesos', + 'm' => ':count mes|:count mesos', + 'week' => ':count setmana|:count setmanes', + 'a_week' => 'una setmana|:count setmanes', + 'w' => ':count setmana|:count setmanes', + 'day' => ':count dia|:count dies', + 'a_day' => 'un dia|:count dies', + 'd' => ':count d', + 'hour' => ':count hora|:count hores', + 'a_hour' => 'una hora|:count hores', + 'h' => ':count h', + 'minute' => ':count minut|:count minuts', + 'a_minute' => 'un minut|:count minuts', + 'min' => ':count min', + 'second' => ':count segon|:count segons', + 'a_second' => 'uns segons|:count segons', + 's' => ':count s', + 'ago' => 'fa :time', + 'from_now' => 'd\'aquí a :time', + 'after' => ':time després', + 'before' => ':time abans', + 'diff_now' => 'ara mateix', + 'diff_today' => 'avui', + 'diff_today_regexp' => 'avui(?:\\s+a)?(?:\\s+les)?', + 'diff_yesterday' => 'ahir', + 'diff_yesterday_regexp' => 'ahir(?:\\s+a)?(?:\\s+les)?', + 'diff_tomorrow' => 'demà', + 'diff_tomorrow_regexp' => 'demà(?:\\s+a)?(?:\\s+les)?', + 'diff_before_yesterday' => 'abans d\'ahir', + 'diff_after_tomorrow' => 'demà passat', + 'period_recurrences' => ':count cop|:count cops', + 'period_interval' => 'cada :interval', + 'period_start_date' => 'de :date', + 'period_end_date' => 'fins a :date', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM [de] YYYY', + 'LLL' => 'D MMMM [de] YYYY [a les] H:mm', + 'LLLL' => 'dddd D MMMM [de] YYYY [a les] H:mm', + ], + 'calendar' => [ + 'sameDay' => function (CarbonInterface $current) { + return '[avui a '.($current->hour !== 1 ? 'les' : 'la').'] LT'; + }, + 'nextDay' => function (CarbonInterface $current) { + return '[demà a '.($current->hour !== 1 ? 'les' : 'la').'] LT'; + }, + 'nextWeek' => function (CarbonInterface $current) { + return 'dddd [a '.($current->hour !== 1 ? 'les' : 'la').'] LT'; + }, + 'lastDay' => function (CarbonInterface $current) { + return '[ahir a '.($current->hour !== 1 ? 'les' : 'la').'] LT'; + }, + 'lastWeek' => function (CarbonInterface $current) { + return '[el] dddd [passat a '.($current->hour !== 1 ? 'les' : 'la').'] LT'; + }, + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + return $number.( + ($period === 'w' || $period === 'W') ? 'a' : ( + ($number === 1) ? 'r' : ( + ($number === 2) ? 'n' : ( + ($number === 3) ? 'r' : ( + ($number === 4) ? 't' : 'è' + ) + ) + ) + ) + ); + }, + 'months' => ['de gener', 'de febrer', 'de març', 'd\'abril', 'de maig', 'de juny', 'de juliol', 'd\'agost', 'de setembre', 'd\'octubre', 'de novembre', 'de desembre'], + 'months_standalone' => ['gener', 'febrer', 'març', 'abril', 'maig', 'juny', 'juliol', 'agost', 'setembre', 'octubre', 'novembre', 'desembre'], + 'months_short' => ['de gen.', 'de febr.', 'de març', 'd\'abr.', 'de maig', 'de juny', 'de jul.', 'd\'ag.', 'de set.', 'd\'oct.', 'de nov.', 'de des.'], + 'months_short_standalone' => ['gen.', 'febr.', 'març', 'abr.', 'maig', 'juny', 'jul.', 'ag.', 'set.', 'oct.', 'nov.', 'des.'], + 'months_regexp' => '/(D[oD]?[\s,]+MMMM?|L{2,4}|l{2,4})/', + 'weekdays' => ['diumenge', 'dilluns', 'dimarts', 'dimecres', 'dijous', 'divendres', 'dissabte'], + 'weekdays_short' => ['dg.', 'dl.', 'dt.', 'dc.', 'dj.', 'dv.', 'ds.'], + 'weekdays_min' => ['dg', 'dl', 'dt', 'dc', 'dj', 'dv', 'ds'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' i '], + 'meridiem' => ['a. m.', 'p. m.'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ca_AD.php b/vendor/nesbot/carbon/src/Carbon/Lang/ca_AD.php new file mode 100644 index 0000000..861acd2 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ca_AD.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ca.php', [ +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ca_ES.php b/vendor/nesbot/carbon/src/Carbon/Lang/ca_ES.php new file mode 100644 index 0000000..5004978 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ca_ES.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ca.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ca_ES_Valencia.php b/vendor/nesbot/carbon/src/Carbon/Lang/ca_ES_Valencia.php new file mode 100644 index 0000000..861acd2 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ca_ES_Valencia.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ca.php', [ +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ca_FR.php b/vendor/nesbot/carbon/src/Carbon/Lang/ca_FR.php new file mode 100644 index 0000000..861acd2 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ca_FR.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ca.php', [ +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ca_IT.php b/vendor/nesbot/carbon/src/Carbon/Lang/ca_IT.php new file mode 100644 index 0000000..861acd2 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ca_IT.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ca.php', [ +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ccp.php b/vendor/nesbot/carbon/src/Carbon/Lang/ccp.php new file mode 100644 index 0000000..99c1dca --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ccp.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'weekdays' => ['𑄢𑄧𑄝𑄨𑄝𑄢𑄴', '𑄥𑄧𑄟𑄴𑄝𑄢𑄴', '𑄟𑄧𑄁𑄉𑄧𑄣𑄴𑄝𑄢𑄴', '𑄝𑄪𑄖𑄴𑄝𑄢𑄴', '𑄝𑄳𑄢𑄨𑄥𑄪𑄛𑄴𑄝𑄢𑄴', '𑄥𑄪𑄇𑄴𑄇𑄮𑄢𑄴𑄝𑄢𑄴', '𑄥𑄧𑄚𑄨𑄝𑄢𑄴'], + 'weekdays_short' => ['𑄢𑄧𑄝𑄨', '𑄥𑄧𑄟𑄴', '𑄟𑄧𑄁𑄉𑄧𑄣𑄴', '𑄝𑄪𑄖𑄴', '𑄝𑄳𑄢𑄨𑄥𑄪𑄛𑄴', '𑄥𑄪𑄇𑄴𑄇𑄮𑄢𑄴', '𑄥𑄧𑄚𑄨'], + 'weekdays_min' => ['𑄢𑄧𑄝𑄨', '𑄥𑄧𑄟𑄴', '𑄟𑄧𑄁𑄉𑄧𑄣𑄴', '𑄝𑄪𑄖𑄴', '𑄝𑄳𑄢𑄨𑄥𑄪𑄛𑄴', '𑄥𑄪𑄇𑄴𑄇𑄮𑄢𑄴', '𑄥𑄧𑄚𑄨'], + 'months' => ['𑄎𑄚𑄪𑄠𑄢𑄨', '𑄜𑄬𑄛𑄴𑄝𑄳𑄢𑄪𑄠𑄢𑄨', '𑄟𑄢𑄴𑄌𑄧', '𑄃𑄬𑄛𑄳𑄢𑄨𑄣𑄴', '𑄟𑄬', '𑄎𑄪𑄚𑄴', '𑄎𑄪𑄣𑄭', '𑄃𑄉𑄧𑄌𑄴𑄑𑄴', '𑄥𑄬𑄛𑄴𑄑𑄬𑄟𑄴𑄝𑄧𑄢𑄴', '𑄃𑄧𑄇𑄴𑄑𑄬𑄝𑄧𑄢𑄴', '𑄚𑄧𑄞𑄬𑄟𑄴𑄝𑄧𑄢𑄴', '𑄓𑄨𑄥𑄬𑄟𑄴𑄝𑄧𑄢𑄴'], + 'months_short' => ['𑄎𑄚𑄪', '𑄜𑄬𑄛𑄴', '𑄟𑄢𑄴𑄌𑄧', '𑄃𑄬𑄛𑄳𑄢𑄨𑄣𑄴', '𑄟𑄬', '𑄎𑄪𑄚𑄴', '𑄎𑄪𑄣𑄭', '𑄃𑄉𑄧𑄌𑄴𑄑𑄴', '𑄥𑄬𑄛𑄴𑄑𑄬𑄟𑄴𑄝𑄧𑄢𑄴', '𑄃𑄧𑄇𑄴𑄑𑄮𑄝𑄧𑄢𑄴', '𑄚𑄧𑄞𑄬𑄟𑄴𑄝𑄧𑄢𑄴', '𑄓𑄨𑄥𑄬𑄟𑄴𑄝𑄢𑄴'], + 'months_short_standalone' => ['𑄎𑄚𑄪𑄠𑄢𑄨', '𑄜𑄬𑄛𑄴𑄝𑄳𑄢𑄪𑄠𑄢𑄨', '𑄟𑄢𑄴𑄌𑄧', '𑄃𑄬𑄛𑄳𑄢𑄨𑄣𑄴', '𑄟𑄬', '𑄎𑄪𑄚𑄴', '𑄎𑄪𑄣𑄭', '𑄃𑄉𑄧𑄌𑄴𑄑𑄴', '𑄥𑄬𑄛𑄴𑄑𑄬𑄟𑄴𑄝𑄧𑄢𑄴', '𑄃𑄧𑄇𑄴𑄑𑄮𑄝𑄧𑄢𑄴', '𑄚𑄧𑄞𑄬𑄟𑄴𑄝𑄧𑄢𑄴', '𑄓𑄨𑄥𑄬𑄟𑄴𑄝𑄧𑄢𑄴'], + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM, YYYY h:mm a', + 'LLLL' => 'dddd, D MMMM, YYYY h:mm a', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ccp_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/ccp_IN.php new file mode 100644 index 0000000..c1fa8af --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ccp_IN.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ccp.php', [ + 'weekend' => [0, 0], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ce.php b/vendor/nesbot/carbon/src/Carbon/Lang/ce.php new file mode 100644 index 0000000..f99f6ff --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ce.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ce_RU.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ce_RU.php b/vendor/nesbot/carbon/src/Carbon/Lang/ce_RU.php new file mode 100644 index 0000000..f769856 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ce_RU.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - ANCHR + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYY.DD.MM', + ], + 'months' => ['Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'], + 'months_short' => ['янв', 'фев', 'мар', 'апр', 'май', 'июн', 'июл', 'авг', 'сен', 'окт', 'ноя', 'дек'], + 'weekdays' => ['КӀиранан де', 'Оршотан де', 'Шинарин де', 'Кхаарин де', 'Еарин де', 'ПӀераскан де', 'Шот де'], + 'weekdays_short' => ['КӀ', 'Ор', 'Ши', 'Кх', 'Еа', 'ПӀ', 'Шо'], + 'weekdays_min' => ['КӀ', 'Ор', 'Ши', 'Кх', 'Еа', 'ПӀ', 'Шо'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'year' => ':count шо', + 'y' => ':count шо', + 'a_year' => ':count шо', + + 'month' => ':count бутт', + 'm' => ':count бутт', + 'a_month' => ':count бутт', + + 'week' => ':count кӏира', + 'w' => ':count кӏира', + 'a_week' => ':count кӏира', + + 'day' => ':count де', + 'd' => ':count де', + 'a_day' => ':count де', + + 'hour' => ':count сахьт', + 'h' => ':count сахьт', + 'a_hour' => ':count сахьт', + + 'minute' => ':count минот', + 'min' => ':count минот', + 'a_minute' => ':count минот', + + 'second' => ':count секунд', + 's' => ':count секунд', + 'a_second' => ':count секунд', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/cgg.php b/vendor/nesbot/carbon/src/Carbon/Lang/cgg.php new file mode 100644 index 0000000..09bcc1c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/cgg.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'weekdays' => ['Sande', 'Orwokubanza', 'Orwakabiri', 'Orwakashatu', 'Orwakana', 'Orwakataano', 'Orwamukaaga'], + 'weekdays_short' => ['SAN', 'ORK', 'OKB', 'OKS', 'OKN', 'OKT', 'OMK'], + 'weekdays_min' => ['SAN', 'ORK', 'OKB', 'OKS', 'OKN', 'OKT', 'OMK'], + 'months' => ['Okwokubanza', 'Okwakabiri', 'Okwakashatu', 'Okwakana', 'Okwakataana', 'Okwamukaaga', 'Okwamushanju', 'Okwamunaana', 'Okwamwenda', 'Okwaikumi', 'Okwaikumi na kumwe', 'Okwaikumi na ibiri'], + 'months_short' => ['KBZ', 'KBR', 'KST', 'KKN', 'KTN', 'KMK', 'KMS', 'KMN', 'KMW', 'KKM', 'KNK', 'KNB'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + + 'day' => ':count ruhanga', // less reliable + 'd' => ':count ruhanga', // less reliable + 'a_day' => ':count ruhanga', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/chr.php b/vendor/nesbot/carbon/src/Carbon/Lang/chr.php new file mode 100644 index 0000000..e26190f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/chr.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/chr_US.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/chr_US.php b/vendor/nesbot/carbon/src/Carbon/Lang/chr_US.php new file mode 100644 index 0000000..371353e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/chr_US.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Cherokee Nation Joseph Erb josepherb7@gmail.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'MM/DD/YYYY', + ], + 'months' => ['ᎤᏃᎸᏔᏅ', 'ᎧᎦᎵ', 'ᎠᏅᏱ', 'ᎧᏬᏂ', 'ᎠᏂᏍᎬᏘ', 'ᏕᎭᎷᏱ', 'ᎫᏰᏉᏂ', 'ᎦᎶᏂ', 'ᏚᎵᏍᏗ', 'ᏚᏂᏅᏗ', 'ᏅᏓᏕᏆ', 'ᎥᏍᎩᏱ'], + 'months_short' => ['ᎤᏃ', 'ᎧᎦ', 'ᎠᏅ', 'ᎧᏬ', 'ᎠᏂ', 'ᏕᎭ', 'ᎫᏰ', 'ᎦᎶ', 'ᏚᎵ', 'ᏚᏂ', 'ᏅᏓ', 'ᎥᏍ'], + 'weekdays' => ['ᎤᎾᏙᏓᏆᏍᎬ', 'ᎤᎾᏙᏓᏉᏅᎯ', 'ᏔᎵᏁᎢᎦ', 'ᏦᎢᏁᎢᎦ', 'ᏅᎩᏁᎢᎦ', 'ᏧᎾᎩᎶᏍᏗ', 'ᎤᎾᏙᏓᏈᏕᎾ'], + 'weekdays_short' => ['ᏆᏍᎬ', 'ᏉᏅᎯ', 'ᏔᎵᏁ', 'ᏦᎢᏁ', 'ᏅᎩᏁ', 'ᏧᎾᎩ', 'ᏈᏕᎾ'], + 'weekdays_min' => ['ᏆᏍᎬ', 'ᏉᏅᎯ', 'ᏔᎵᏁ', 'ᏦᎢᏁ', 'ᏅᎩᏁ', 'ᏧᎾᎩ', 'ᏈᏕᎾ'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ᏌᎾᎴ', 'ᏒᎯᏱᎢᏗᏢ', 'ꮜꮎꮄ', 'ꮢꭿᏹꭲꮧꮲ'], + + 'second' => ':count ᏐᎢ', // less reliable + 's' => ':count ᏐᎢ', // less reliable + 'a_second' => ':count ᏐᎢ', // less reliable + + 'year' => ':count ᏑᏕᏘᏴᏓ', + 'y' => ':count ᏑᏕᏘᏴᏓ', + 'a_year' => ':count ᏑᏕᏘᏴᏓ', + + 'month' => ':count ᏏᏅᏙ', + 'm' => ':count ᏏᏅᏙ', + 'a_month' => ':count ᏏᏅᏙ', + + 'week' => ':count ᏑᎾᏙᏓᏆᏍᏗ', + 'w' => ':count ᏑᎾᏙᏓᏆᏍᏗ', + 'a_week' => ':count ᏑᎾᏙᏓᏆᏍᏗ', + + 'day' => ':count ᎢᎦ', + 'd' => ':count ᎢᎦ', + 'a_day' => ':count ᎢᎦ', + + 'hour' => ':count ᏑᏟᎶᏛ', + 'h' => ':count ᏑᏟᎶᏛ', + 'a_hour' => ':count ᏑᏟᎶᏛ', + + 'minute' => ':count ᎢᏯᏔᏬᏍᏔᏅ', + 'min' => ':count ᎢᏯᏔᏬᏍᏔᏅ', + 'a_minute' => ':count ᎢᏯᏔᏬᏍᏔᏅ', + + 'ago' => ':time ᏥᎨᏒ', + 'from_now' => 'ᎾᎿ :time', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/cmn.php b/vendor/nesbot/carbon/src/Carbon/Lang/cmn.php new file mode 100644 index 0000000..80b1d69 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/cmn.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/cmn_TW.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/cmn_TW.php b/vendor/nesbot/carbon/src/Carbon/Lang/cmn_TW.php new file mode 100644 index 0000000..7e43f9d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/cmn_TW.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYY年MM月DD號', + ], + 'months' => ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'], + 'months_short' => [' 1月', ' 2月', ' 3月', ' 4月', ' 5月', ' 6月', ' 7月', ' 8月', ' 9月', '10月', '11月', '12月'], + 'weekdays' => ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'], + 'weekdays_short' => ['日', '一', '二', '三', '四', '五', '六'], + 'weekdays_min' => ['日', '一', '二', '三', '四', '五', '六'], + 'meridiem' => ['上午', '下午'], + + 'year' => ':count 年', + 'y' => ':count 年', + 'a_year' => ':count 年', + + 'month' => ':count 月', + 'm' => ':count 月', + 'a_month' => ':count 月', + + 'week' => ':count 周', + 'w' => ':count 周', + 'a_week' => ':count 周', + + 'day' => ':count 白天', + 'd' => ':count 白天', + 'a_day' => ':count 白天', + + 'hour' => ':count 小时', + 'h' => ':count 小时', + 'a_hour' => ':count 小时', + + 'minute' => ':count 分钟', + 'min' => ':count 分钟', + 'a_minute' => ':count 分钟', + + 'second' => ':count 秒', + 's' => ':count 秒', + 'a_second' => ':count 秒', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/crh.php b/vendor/nesbot/carbon/src/Carbon/Lang/crh.php new file mode 100644 index 0000000..a1d7ce6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/crh.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/crh_UA.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/crh_UA.php b/vendor/nesbot/carbon/src/Carbon/Lang/crh_UA.php new file mode 100644 index 0000000..0513933 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/crh_UA.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Reşat SABIQ tilde.birlik@gmail.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['Yanvar', 'Fevral', 'Mart', 'Aprel', 'Mayıs', 'İyun', 'İyul', 'Avgust', 'Sentâbr', 'Oktâbr', 'Noyabr', 'Dekabr'], + 'months_short' => ['Yan', 'Fev', 'Mar', 'Apr', 'May', 'İyn', 'İyl', 'Avg', 'Sen', 'Okt', 'Noy', 'Dek'], + 'weekdays' => ['Bazar', 'Bazarertesi', 'Salı', 'Çarşembe', 'Cumaaqşamı', 'Cuma', 'Cumaertesi'], + 'weekdays_short' => ['Baz', 'Ber', 'Sal', 'Çar', 'Caq', 'Cum', 'Cer'], + 'weekdays_min' => ['Baz', 'Ber', 'Sal', 'Çar', 'Caq', 'Cum', 'Cer'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ÜE', 'ÜS'], + + 'year' => ':count yıl', + 'y' => ':count yıl', + 'a_year' => ':count yıl', + + 'month' => ':count ay', + 'm' => ':count ay', + 'a_month' => ':count ay', + + 'week' => ':count afta', + 'w' => ':count afta', + 'a_week' => ':count afta', + + 'day' => ':count kün', + 'd' => ':count kün', + 'a_day' => ':count kün', + + 'hour' => ':count saat', + 'h' => ':count saat', + 'a_hour' => ':count saat', + + 'minute' => ':count daqqa', + 'min' => ':count daqqa', + 'a_minute' => ':count daqqa', + + 'second' => ':count ekinci', + 's' => ':count ekinci', + 'a_second' => ':count ekinci', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/cs.php b/vendor/nesbot/carbon/src/Carbon/Lang/cs.php new file mode 100644 index 0000000..8cff9a0 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/cs.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - Jakub Tesinsky + * - Martin Suja + * - Nikos Timiopulos + * - Bohuslav Blín + * - Tsutomu Kuroda + * - tjku + * - Lukas Svoboda + * - Max Melentiev + * - Juanito Fatas + * - Akira Matsuda + * - Christopher Dell + * - Václav Pávek + * - CodeSkills + * - Tlapi + * - newman101 + * - Petr Kadlec + * - tommaskraus + * - Karel Sommer (calvera) + */ +$za = function ($time) { + return 'za '.strtr($time, [ + 'hodina' => 'hodinu', + 'minuta' => 'minutu', + 'sekunda' => 'sekundu', + ]); +}; + +$pred = function ($time) { + $time = strtr($time, [ + 'hodina' => 'hodinou', + 'minuta' => 'minutou', + 'sekunda' => 'sekundou', + ]); + $time = preg_replace('/hodiny?(?!\w)/', 'hodinami', $time); + $time = preg_replace('/minuty?(?!\w)/', 'minutami', $time); + $time = preg_replace('/sekundy?(?!\w)/', 'sekundami', $time); + + return "před $time"; +}; + +return [ + 'year' => ':count rok|:count roky|:count let', + 'y' => ':count rok|:count roky|:count let', + 'a_year' => 'rok|:count roky|:count let', + 'month' => ':count měsíc|:count měsíce|:count měsíců', + 'm' => ':count měs.', + 'a_month' => 'měsíc|:count měsíce|:count měsíců', + 'week' => ':count týden|:count týdny|:count týdnů', + 'w' => ':count týd.', + 'a_week' => 'týden|:count týdny|:count týdnů', + 'day' => ':count den|:count dny|:count dní', + 'd' => ':count den|:count dny|:count dní', + 'a_day' => 'den|:count dny|:count dní', + 'hour' => ':count hodina|:count hodiny|:count hodin', + 'h' => ':count hod.', + 'a_hour' => 'hodina|:count hodiny|:count hodin', + 'minute' => ':count minuta|:count minuty|:count minut', + 'min' => ':count min.', + 'a_minute' => 'minuta|:count minuty|:count minut', + 'second' => ':count sekunda|:count sekundy|:count sekund', + 's' => ':count sek.', + 'a_second' => 'pár sekund|:count sekundy|:count sekund', + + 'month_ago' => ':count měsícem|:count měsíci|:count měsíci', + 'a_month_ago' => 'měsícem|:count měsíci|:count měsíci', + 'day_ago' => ':count dnem|:count dny|:count dny', + 'a_day_ago' => 'dnem|:count dny|:count dny', + 'week_ago' => ':count týdnem|:count týdny|:count týdny', + 'a_week_ago' => 'týdnem|:count týdny|:count týdny', + 'year_ago' => ':count rokem|:count roky|:count lety', + 'y_ago' => ':count rok.|:count rok.|:count let.', + 'a_year_ago' => 'rokem|:count roky|:count lety', + + 'month_before' => ':count měsícem|:count měsíci|:count měsíci', + 'a_month_before' => 'měsícem|:count měsíci|:count měsíci', + 'day_before' => ':count dnem|:count dny|:count dny', + 'a_day_before' => 'dnem|:count dny|:count dny', + 'week_before' => ':count týdnem|:count týdny|:count týdny', + 'a_week_before' => 'týdnem|:count týdny|:count týdny', + 'year_before' => ':count rokem|:count roky|:count lety', + 'y_before' => ':count rok.|:count rok.|:count let.', + 'a_year_before' => 'rokem|:count roky|:count lety', + + 'ago' => $pred, + 'from_now' => $za, + 'before' => $pred, + 'after' => $za, + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'months' => ['leden', 'únor', 'březen', 'duben', 'květen', 'červen', 'červenec', 'srpen', 'září', 'říjen', 'listopad', 'prosinec'], + 'months_short' => ['led', 'úno', 'bře', 'dub', 'kvě', 'čvn', 'čvc', 'srp', 'zář', 'říj', 'lis', 'pro'], + 'weekdays' => ['neděle', 'pondělí', 'úterý', 'středa', 'čtvrtek', 'pátek', 'sobota'], + 'weekdays_short' => ['ned', 'pon', 'úte', 'stř', 'čtv', 'pát', 'sob'], + 'weekdays_min' => ['ne', 'po', 'út', 'st', 'čt', 'pá', 'so'], + 'list' => [', ', ' a '], + 'diff_now' => 'nyní', + 'diff_yesterday' => 'včera', + 'diff_tomorrow' => 'zítra', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD. MM. YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY HH:mm', + 'LLLL' => 'dddd D. MMMM YYYY HH:mm', + ], + 'meridiem' => ['dopoledne', 'odpoledne'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/cs_CZ.php b/vendor/nesbot/carbon/src/Carbon/Lang/cs_CZ.php new file mode 100644 index 0000000..ea2517e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/cs_CZ.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/cs.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/csb.php b/vendor/nesbot/carbon/src/Carbon/Lang/csb.php new file mode 100644 index 0000000..a35d281 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/csb.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/csb_PL.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/csb_PL.php b/vendor/nesbot/carbon/src/Carbon/Lang/csb_PL.php new file mode 100644 index 0000000..25e0ca8 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/csb_PL.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - csb_PL locale Michal Ostrowski bug-glibc-locales@gnu.org + */ +return [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-DD', + 'LL' => 'MMMM DD, YYYY', + 'LLL' => 'DD MMM HH:mm', + 'LLLL' => 'MMMM DD, YYYY HH:mm', + ], + 'months' => ['stëcznika', 'gromicznika', 'strëmiannika', 'łżëkwiata', 'maja', 'czerwińca', 'lëpińca', 'zélnika', 'séwnika', 'rujana', 'lëstopadnika', 'gòdnika'], + 'months_short' => ['stë', 'gro', 'str', 'łżë', 'maj', 'cze', 'lëp', 'zél', 'séw', 'ruj', 'lës', 'gòd'], + 'weekdays' => ['niedzela', 'pòniedzôłk', 'wtórk', 'strzoda', 'czwiôrtk', 'piątk', 'sobòta'], + 'weekdays_short' => ['nie', 'pòn', 'wtó', 'str', 'czw', 'pią', 'sob'], + 'weekdays_min' => ['nie', 'pòn', 'wtó', 'str', 'czw', 'pią', 'sob'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' a téż '], + 'two_words_connector' => ' a téż ', + 'year' => ':count rok', + 'month' => ':count miesiąc', + 'week' => ':count tidzéń', + 'day' => ':count dzéń', + 'hour' => ':count gòdzëna', + 'minute' => ':count minuta', + 'second' => ':count sekunda', +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/cu.php b/vendor/nesbot/carbon/src/Carbon/Lang/cu.php new file mode 100644 index 0000000..d6d1312 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/cu.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'months' => ['M01', 'M02', 'M03', 'M04', 'M05', 'M06', 'M07', 'M08', 'M09', 'M10', 'M11', 'M12'], + 'months_short' => ['M01', 'M02', 'M03', 'M04', 'M05', 'M06', 'M07', 'M08', 'M09', 'M10', 'M11', 'M12'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-DD', + 'LL' => 'YYYY MMM D', + 'LLL' => 'YYYY MMMM D HH:mm', + 'LLLL' => 'YYYY MMMM D, dddd HH:mm', + ], + + 'year' => ':count лѣто', + 'y' => ':count лѣто', + 'a_year' => ':count лѣто', + + 'month' => ':count мѣсѧць', + 'm' => ':count мѣсѧць', + 'a_month' => ':count мѣсѧць', + + 'week' => ':count сєдмица', + 'w' => ':count сєдмица', + 'a_week' => ':count сєдмица', + + 'day' => ':count дьнь', + 'd' => ':count дьнь', + 'a_day' => ':count дьнь', + + 'hour' => ':count година', + 'h' => ':count година', + 'a_hour' => ':count година', + + 'minute' => ':count малъ', // less reliable + 'min' => ':count малъ', // less reliable + 'a_minute' => ':count малъ', // less reliable + + 'second' => ':count въторъ', + 's' => ':count въторъ', + 'a_second' => ':count въторъ', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/cv.php b/vendor/nesbot/carbon/src/Carbon/Lang/cv.php new file mode 100644 index 0000000..8aeb73a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/cv.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - François B + * - JD Isaacks + */ +return [ + 'year' => ':count ҫул', + 'a_year' => '{1}пӗр ҫул|:count ҫул', + 'month' => ':count уйӑх', + 'a_month' => '{1}пӗр уйӑх|:count уйӑх', + 'week' => ':count эрне', + 'a_week' => '{1}пӗр эрне|:count эрне', + 'day' => ':count кун', + 'a_day' => '{1}пӗр кун|:count кун', + 'hour' => ':count сехет', + 'a_hour' => '{1}пӗр сехет|:count сехет', + 'minute' => ':count минут', + 'a_minute' => '{1}пӗр минут|:count минут', + 'second' => ':count ҫеккунт', + 'a_second' => '{1}пӗр-ик ҫеккунт|:count ҫеккунт', + 'ago' => ':time каялла', + 'from_now' => function ($time) { + return $time.(preg_match('/сехет$/u', $time) ? 'рен' : (preg_match('/ҫул/u', $time) ? 'тан' : 'ран')); + }, + 'diff_yesterday' => 'Ӗнер', + 'diff_today' => 'Паян', + 'diff_tomorrow' => 'Ыран', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD-MM-YYYY', + 'LL' => 'YYYY [ҫулхи] MMMM [уйӑхӗн] D[-мӗшӗ]', + 'LLL' => 'YYYY [ҫулхи] MMMM [уйӑхӗн] D[-мӗшӗ], HH:mm', + 'LLLL' => 'dddd, YYYY [ҫулхи] MMMM [уйӑхӗн] D[-мӗшӗ], HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Паян] LT [сехетре]', + 'nextDay' => '[Ыран] LT [сехетре]', + 'nextWeek' => '[Ҫитес] dddd LT [сехетре]', + 'lastDay' => '[Ӗнер] LT [сехетре]', + 'lastWeek' => '[Иртнӗ] dddd LT [сехетре]', + 'sameElse' => 'L', + ], + 'ordinal' => ':number-мӗш', + 'months' => ['кӑрлач', 'нарӑс', 'пуш', 'ака', 'май', 'ҫӗртме', 'утӑ', 'ҫурла', 'авӑн', 'юпа', 'чӳк', 'раштав'], + 'months_short' => ['кӑр', 'нар', 'пуш', 'ака', 'май', 'ҫӗр', 'утӑ', 'ҫур', 'авн', 'юпа', 'чӳк', 'раш'], + 'weekdays' => ['вырсарникун', 'тунтикун', 'ытларикун', 'юнкун', 'кӗҫнерникун', 'эрнекун', 'шӑматкун'], + 'weekdays_short' => ['выр', 'тун', 'ытл', 'юн', 'кӗҫ', 'эрн', 'шӑм'], + 'weekdays_min' => ['вр', 'тн', 'ыт', 'юн', 'кҫ', 'эр', 'шм'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' тата '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/cv_RU.php b/vendor/nesbot/carbon/src/Carbon/Lang/cv_RU.php new file mode 100644 index 0000000..197bd8d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/cv_RU.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/cv.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/cy.php b/vendor/nesbot/carbon/src/Carbon/Lang/cy.php new file mode 100644 index 0000000..ab7c45a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/cy.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - JD Isaacks + * - Daniel Monaghan + */ +return [ + 'year' => '{1}blwyddyn|]1,Inf[:count flynedd', + 'y' => ':countbl', + 'month' => '{1}mis|]1,Inf[:count mis', + 'm' => ':countmi', + 'week' => ':count wythnos', + 'w' => ':countw', + 'day' => '{1}diwrnod|]1,Inf[:count diwrnod', + 'd' => ':countd', + 'hour' => '{1}awr|]1,Inf[:count awr', + 'h' => ':counth', + 'minute' => '{1}munud|]1,Inf[:count munud', + 'min' => ':countm', + 'second' => '{1}ychydig eiliadau|]1,Inf[:count eiliad', + 's' => ':counts', + 'ago' => ':time yn ôl', + 'from_now' => 'mewn :time', + 'after' => ':time ar ôl', + 'before' => ':time o\'r blaen', + 'diff_now' => 'nawr', + 'diff_today' => 'Heddiw', + 'diff_today_regexp' => 'Heddiw(?:\\s+am)?', + 'diff_yesterday' => 'ddoe', + 'diff_yesterday_regexp' => 'Ddoe(?:\\s+am)?', + 'diff_tomorrow' => 'yfory', + 'diff_tomorrow_regexp' => 'Yfory(?:\\s+am)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Heddiw am] LT', + 'nextDay' => '[Yfory am] LT', + 'nextWeek' => 'dddd [am] LT', + 'lastDay' => '[Ddoe am] LT', + 'lastWeek' => 'dddd [diwethaf am] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + return $number.( + $number > 20 + ? (\in_array($number, [40, 50, 60, 80, 100]) ? 'fed' : 'ain') + : ([ + '', 'af', 'il', 'ydd', 'ydd', 'ed', 'ed', 'ed', 'fed', 'fed', 'fed', // 1af to 10fed + 'eg', 'fed', 'eg', 'eg', 'fed', 'eg', 'eg', 'fed', 'eg', 'fed', // 11eg to 20fed + ])[$number] ?? '' + ); + }, + 'months' => ['Ionawr', 'Chwefror', 'Mawrth', 'Ebrill', 'Mai', 'Mehefin', 'Gorffennaf', 'Awst', 'Medi', 'Hydref', 'Tachwedd', 'Rhagfyr'], + 'months_short' => ['Ion', 'Chwe', 'Maw', 'Ebr', 'Mai', 'Meh', 'Gor', 'Aws', 'Med', 'Hyd', 'Tach', 'Rhag'], + 'weekdays' => ['Dydd Sul', 'Dydd Llun', 'Dydd Mawrth', 'Dydd Mercher', 'Dydd Iau', 'Dydd Gwener', 'Dydd Sadwrn'], + 'weekdays_short' => ['Sul', 'Llun', 'Maw', 'Mer', 'Iau', 'Gwe', 'Sad'], + 'weekdays_min' => ['Su', 'Ll', 'Ma', 'Me', 'Ia', 'Gw', 'Sa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' a '], + 'meridiem' => ['yb', 'yh'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/cy_GB.php b/vendor/nesbot/carbon/src/Carbon/Lang/cy_GB.php new file mode 100644 index 0000000..2c8148d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/cy_GB.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/cy.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/da.php b/vendor/nesbot/carbon/src/Carbon/Lang/da.php new file mode 100644 index 0000000..4e6640a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/da.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Rune Mønnike + * - François B + * - codenhagen + * - JD Isaacks + * - Jens Herlevsen + * - Ulrik McArdle (mcardle) + * - Frederik Sauer (FrittenKeeZ) + */ +return [ + 'year' => ':count år|:count år', + 'a_year' => 'et år|:count år', + 'y' => ':count år|:count år', + 'month' => ':count måned|:count måneder', + 'a_month' => 'en måned|:count måneder', + 'm' => ':count mdr.', + 'week' => ':count uge|:count uger', + 'a_week' => 'en uge|:count uger', + 'w' => ':count u.', + 'day' => ':count dag|:count dage', + 'a_day' => ':count dag|:count dage', + 'd' => ':count d.', + 'hour' => ':count time|:count timer', + 'a_hour' => 'en time|:count timer', + 'h' => ':count t.', + 'minute' => ':count minut|:count minutter', + 'a_minute' => 'et minut|:count minutter', + 'min' => ':count min.', + 'second' => ':count sekund|:count sekunder', + 'a_second' => 'få sekunder|:count sekunder', + 's' => ':count s.', + 'ago' => ':time siden', + 'from_now' => 'om :time', + 'after' => ':time efter', + 'before' => ':time før', + 'diff_now' => 'nu', + 'diff_today' => 'i dag', + 'diff_today_regexp' => 'i dag(?:\\s+kl.)?', + 'diff_yesterday' => 'i går', + 'diff_yesterday_regexp' => 'i går(?:\\s+kl.)?', + 'diff_tomorrow' => 'i morgen', + 'diff_tomorrow_regexp' => 'i morgen(?:\\s+kl.)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY HH:mm', + 'LLLL' => 'dddd [d.] D. MMMM YYYY [kl.] HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[i dag kl.] LT', + 'nextDay' => '[i morgen kl.] LT', + 'nextWeek' => 'på dddd [kl.] LT', + 'lastDay' => '[i går kl.] LT', + 'lastWeek' => '[i] dddd[s kl.] LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['januar', 'februar', 'marts', 'april', 'maj', 'juni', 'juli', 'august', 'september', 'oktober', 'november', 'december'], + 'months_short' => ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'], + 'weekdays' => ['søndag', 'mandag', 'tirsdag', 'onsdag', 'torsdag', 'fredag', 'lørdag'], + 'weekdays_short' => ['søn', 'man', 'tir', 'ons', 'tor', 'fre', 'lør'], + 'weekdays_min' => ['sø', 'ma', 'ti', 'on', 'to', 'fr', 'lø'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' og '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/da_DK.php b/vendor/nesbot/carbon/src/Carbon/Lang/da_DK.php new file mode 100644 index 0000000..392c484 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/da_DK.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/da.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/da_GL.php b/vendor/nesbot/carbon/src/Carbon/Lang/da_GL.php new file mode 100644 index 0000000..ea5698b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/da_GL.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/da.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + 'LL' => 'D. MMM YYYY', + 'LLL' => 'D. MMMM YYYY HH.mm', + 'LLLL' => 'dddd [den] D. MMMM YYYY HH.mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/dav.php b/vendor/nesbot/carbon/src/Carbon/Lang/dav.php new file mode 100644 index 0000000..e95ec4b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/dav.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Luma lwa K', 'luma lwa p'], + 'weekdays' => ['Ituku ja jumwa', 'Kuramuka jimweri', 'Kuramuka kawi', 'Kuramuka kadadu', 'Kuramuka kana', 'Kuramuka kasanu', 'Kifula nguwo'], + 'weekdays_short' => ['Jum', 'Jim', 'Kaw', 'Kad', 'Kan', 'Kas', 'Ngu'], + 'weekdays_min' => ['Jum', 'Jim', 'Kaw', 'Kad', 'Kan', 'Kas', 'Ngu'], + 'months' => ['Mori ghwa imbiri', 'Mori ghwa kawi', 'Mori ghwa kadadu', 'Mori ghwa kana', 'Mori ghwa kasanu', 'Mori ghwa karandadu', 'Mori ghwa mfungade', 'Mori ghwa wunyanya', 'Mori ghwa ikenda', 'Mori ghwa ikumi', 'Mori ghwa ikumi na imweri', 'Mori ghwa ikumi na iwi'], + 'months_short' => ['Imb', 'Kaw', 'Kad', 'Kan', 'Kas', 'Kar', 'Mfu', 'Wun', 'Ike', 'Iku', 'Imw', 'Iwi'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/de.php b/vendor/nesbot/carbon/src/Carbon/Lang/de.php new file mode 100644 index 0000000..ff00c97 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/de.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Michael Hohl + * - sheriffmarley + * - dennisoderwald + * - Timo + * - Karag2006 + * - Pete Scopes (pdscopes) + */ +return [ + 'year' => ':count Jahr|:count Jahre', + 'a_year' => 'ein Jahr|:count Jahre', + 'y' => ':count J.', + 'month' => ':count Monat|:count Monate', + 'a_month' => 'ein Monat|:count Monate', + 'm' => ':count Mon.', + 'week' => ':count Woche|:count Wochen', + 'a_week' => 'eine Woche|:count Wochen', + 'w' => ':count Wo.', + 'day' => ':count Tag|:count Tage', + 'a_day' => 'ein Tag|:count Tage', + 'd' => ':count Tg.', + 'hour' => ':count Stunde|:count Stunden', + 'a_hour' => 'eine Stunde|:count Stunden', + 'h' => ':count Std.', + 'minute' => ':count Minute|:count Minuten', + 'a_minute' => 'eine Minute|:count Minuten', + 'min' => ':count Min.', + 'second' => ':count Sekunde|:count Sekunden', + 'a_second' => 'ein paar Sekunden|:count Sekunden', + 's' => ':count Sek.', + 'millisecond' => ':count Millisekunde|:count Millisekunden', + 'a_millisecond' => 'eine Millisekunde|:count Millisekunden', + 'ms' => ':countms', + 'microsecond' => ':count Mikrosekunde|:count Mikrosekunden', + 'a_microsecond' => 'eine Mikrosekunde|:count Mikrosekunden', + 'µs' => ':countµs', + 'ago' => 'vor :time', + 'from_now' => 'in :time', + 'after' => ':time später', + 'before' => ':time zuvor', + + 'year_from_now' => ':count Jahr|:count Jahren', + 'month_from_now' => ':count Monat|:count Monaten', + 'week_from_now' => ':count Woche|:count Wochen', + 'day_from_now' => ':count Tag|:count Tagen', + 'year_ago' => ':count Jahr|:count Jahren', + 'month_ago' => ':count Monat|:count Monaten', + 'week_ago' => ':count Woche|:count Wochen', + 'day_ago' => ':count Tag|:count Tagen', + 'a_year_from_now' => 'ein Jahr|:count Jahren', + 'a_month_from_now' => 'ein Monat|:count Monaten', + 'a_week_from_now' => 'eine Woche|:count Wochen', + 'a_day_from_now' => 'ein Tag|:count Tagen', + 'a_year_ago' => 'ein Jahr|:count Jahren', + 'a_month_ago' => 'ein Monat|:count Monaten', + 'a_week_ago' => 'eine Woche|:count Wochen', + 'a_day_ago' => 'ein Tag|:count Tagen', + + 'diff_now' => 'Gerade eben', + 'diff_today' => 'heute', + 'diff_today_regexp' => 'heute(?:\\s+um)?', + 'diff_yesterday' => 'Gestern', + 'diff_yesterday_regexp' => 'gestern(?:\\s+um)?', + 'diff_tomorrow' => 'Morgen', + 'diff_tomorrow_regexp' => 'morgen(?:\\s+um)?', + 'diff_before_yesterday' => 'Vorgestern', + 'diff_after_tomorrow' => 'Übermorgen', + + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D. MMMM YYYY HH:mm', + ], + + 'calendar' => [ + 'sameDay' => '[heute um] LT [Uhr]', + 'nextDay' => '[morgen um] LT [Uhr]', + 'nextWeek' => 'dddd [um] LT [Uhr]', + 'lastDay' => '[gestern um] LT [Uhr]', + 'lastWeek' => '[letzten] dddd [um] LT [Uhr]', + 'sameElse' => 'L', + ], + + 'months' => ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'], + 'months_short' => ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'], + 'weekdays' => ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'], + 'weekdays_short' => ['So.', 'Mo.', 'Di.', 'Mi.', 'Do.', 'Fr.', 'Sa.'], + 'weekdays_min' => ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'], + 'ordinal' => ':number.', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' und '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/de_AT.php b/vendor/nesbot/carbon/src/Carbon/Lang/de_AT.php new file mode 100644 index 0000000..a2ea4c0 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/de_AT.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - sheriffmarley + * - Timo + * - Michael Hohl + * - Namoshek + * - Bernhard Baumrock (BernhardBaumrock) + */ +return array_replace_recursive(require __DIR__.'/de.php', [ + 'months' => [ + 0 => 'Jänner', + ], + 'months_short' => [ + 0 => 'Jän', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/de_BE.php b/vendor/nesbot/carbon/src/Carbon/Lang/de_BE.php new file mode 100644 index 0000000..8ed8dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/de_BE.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/de.php', [ + 'formats' => [ + 'L' => 'YYYY-MM-DD', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/de_CH.php b/vendor/nesbot/carbon/src/Carbon/Lang/de_CH.php new file mode 100644 index 0000000..a869ab4 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/de_CH.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - sheriffmarley + * - Timo + * - Michael Hohl + */ +return array_replace_recursive(require __DIR__.'/de.php', [ + 'weekdays_short' => ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/de_DE.php b/vendor/nesbot/carbon/src/Carbon/Lang/de_DE.php new file mode 100644 index 0000000..fb1209d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/de_DE.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Free Software Foundation, Inc. bug-glibc-locales@gnu.org + */ +return require __DIR__.'/de.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/de_IT.php b/vendor/nesbot/carbon/src/Carbon/Lang/de_IT.php new file mode 100644 index 0000000..604a856 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/de_IT.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Matthias Dieter Wallno:fer libc-locales@sourceware.org + */ +return require __DIR__.'/de.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/de_LI.php b/vendor/nesbot/carbon/src/Carbon/Lang/de_LI.php new file mode 100644 index 0000000..03e606a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/de_LI.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/de.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/de_LU.php b/vendor/nesbot/carbon/src/Carbon/Lang/de_LU.php new file mode 100644 index 0000000..8ed8dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/de_LU.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/de.php', [ + 'formats' => [ + 'L' => 'YYYY-MM-DD', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/dje.php b/vendor/nesbot/carbon/src/Carbon/Lang/dje.php new file mode 100644 index 0000000..74b7ac1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/dje.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Subbaahi', 'Zaarikay b'], + 'weekdays' => ['Alhadi', 'Atinni', 'Atalaata', 'Alarba', 'Alhamisi', 'Alzuma', 'Asibti'], + 'weekdays_short' => ['Alh', 'Ati', 'Ata', 'Ala', 'Alm', 'Alz', 'Asi'], + 'weekdays_min' => ['Alh', 'Ati', 'Ata', 'Ala', 'Alm', 'Alz', 'Asi'], + 'months' => ['Žanwiye', 'Feewiriye', 'Marsi', 'Awiril', 'Me', 'Žuweŋ', 'Žuyye', 'Ut', 'Sektanbur', 'Oktoobur', 'Noowanbur', 'Deesanbur'], + 'months_short' => ['Žan', 'Fee', 'Mar', 'Awi', 'Me', 'Žuw', 'Žuy', 'Ut', 'Sek', 'Okt', 'Noo', 'Dee'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + + 'year' => ':count hari', // less reliable + 'y' => ':count hari', // less reliable + 'a_year' => ':count hari', // less reliable + + 'week' => ':count alzuma', // less reliable + 'w' => ':count alzuma', // less reliable + 'a_week' => ':count alzuma', // less reliable + + 'second' => ':count atinni', // less reliable + 's' => ':count atinni', // less reliable + 'a_second' => ':count atinni', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/doi.php b/vendor/nesbot/carbon/src/Carbon/Lang/doi.php new file mode 100644 index 0000000..cb679c5 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/doi.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/doi_IN.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/doi_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/doi_IN.php new file mode 100644 index 0000000..d359721 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/doi_IN.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Red Hat Pune libc-alpha@sourceware.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['जनवरी', 'फरवरी', 'मार्च', 'एप्रैल', 'मेई', 'जून', 'जूलै', 'अगस्त', 'सितंबर', 'अक्तूबर', 'नवंबर', 'दिसंबर'], + 'months_short' => ['जनवरी', 'फरवरी', 'मार्च', 'एप्रैल', 'मेई', 'जून', 'जूलै', 'अगस्त', 'सितंबर', 'अक्तूबर', 'नवंबर', 'दिसंबर'], + 'weekdays' => ['ऐतबार', 'सोमबार', 'मंगलबर', 'बुधबार', 'बीरबार', 'शुक्करबार', 'श्नीचरबार'], + 'weekdays_short' => ['ऐत', 'सोम', 'मंगल', 'बुध', 'बीर', 'शुक्कर', 'श्नीचर'], + 'weekdays_min' => ['ऐत', 'सोम', 'मंगल', 'बुध', 'बीर', 'शुक्कर', 'श्नीचर'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['सञं', 'सबेर'], + + 'second' => ':count सङार', // less reliable + 's' => ':count सङार', // less reliable + 'a_second' => ':count सङार', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/dsb.php b/vendor/nesbot/carbon/src/Carbon/Lang/dsb.php new file mode 100644 index 0000000..1d214d5 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/dsb.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/dsb_DE.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/dsb_DE.php b/vendor/nesbot/carbon/src/Carbon/Lang/dsb_DE.php new file mode 100644 index 0000000..1b94187 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/dsb_DE.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Information from Michael Wolf bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'DD. MMMM YYYY', + 'LLL' => 'DD. MMMM, HH:mm [góź.]', + 'LLLL' => 'dddd, DD. MMMM YYYY, HH:mm [góź.]', + ], + 'months' => ['januara', 'februara', 'měrca', 'apryla', 'maja', 'junija', 'julija', 'awgusta', 'septembra', 'oktobra', 'nowembra', 'decembra'], + 'months_short' => ['Jan', 'Feb', 'Měr', 'Apr', 'Maj', 'Jun', 'Jul', 'Awg', 'Sep', 'Okt', 'Now', 'Dec'], + 'weekdays' => ['Njeźela', 'Pónjeźele', 'Wałtora', 'Srjoda', 'Stwórtk', 'Pětk', 'Sobota'], + 'weekdays_short' => ['Nj', 'Pó', 'Wa', 'Sr', 'St', 'Pě', 'So'], + 'weekdays_min' => ['Nj', 'Pó', 'Wa', 'Sr', 'St', 'Pě', 'So'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'year' => ':count lěto', + 'y' => ':count lěto', + 'a_year' => ':count lěto', + + 'month' => ':count mjasec', + 'm' => ':count mjasec', + 'a_month' => ':count mjasec', + + 'week' => ':count tyźeń', + 'w' => ':count tyźeń', + 'a_week' => ':count tyźeń', + + 'day' => ':count źeń', + 'd' => ':count źeń', + 'a_day' => ':count źeń', + + 'hour' => ':count góźina', + 'h' => ':count góźina', + 'a_hour' => ':count góźina', + + 'minute' => ':count minuta', + 'min' => ':count minuta', + 'a_minute' => ':count minuta', + + 'second' => ':count drugi', + 's' => ':count drugi', + 'a_second' => ':count drugi', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/dua.php b/vendor/nesbot/carbon/src/Carbon/Lang/dua.php new file mode 100644 index 0000000..55e5c7c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/dua.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['idiɓa', 'ebyámu'], + 'weekdays' => ['éti', 'mɔ́sú', 'kwasú', 'mukɔ́sú', 'ŋgisú', 'ɗónɛsú', 'esaɓasú'], + 'weekdays_short' => ['ét', 'mɔ́s', 'kwa', 'muk', 'ŋgi', 'ɗón', 'esa'], + 'weekdays_min' => ['ét', 'mɔ́s', 'kwa', 'muk', 'ŋgi', 'ɗón', 'esa'], + 'months' => ['dimɔ́di', 'ŋgɔndɛ', 'sɔŋɛ', 'diɓáɓá', 'emiasele', 'esɔpɛsɔpɛ', 'madiɓɛ́díɓɛ́', 'diŋgindi', 'nyɛtɛki', 'mayésɛ́', 'tiníní', 'eláŋgɛ́'], + 'months_short' => ['di', 'ŋgɔn', 'sɔŋ', 'diɓ', 'emi', 'esɔ', 'mad', 'diŋ', 'nyɛt', 'may', 'tin', 'elá'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + + 'year' => ':count ma mbu', // less reliable + 'y' => ':count ma mbu', // less reliable + 'a_year' => ':count ma mbu', // less reliable + + 'month' => ':count myo̱di', // less reliable + 'm' => ':count myo̱di', // less reliable + 'a_month' => ':count myo̱di', // less reliable + + 'week' => ':count woki', // less reliable + 'w' => ':count woki', // less reliable + 'a_week' => ':count woki', // less reliable + + 'day' => ':count buńa', // less reliable + 'd' => ':count buńa', // less reliable + 'a_day' => ':count buńa', // less reliable + + 'hour' => ':count ma awa', // less reliable + 'h' => ':count ma awa', // less reliable + 'a_hour' => ':count ma awa', // less reliable + + 'minute' => ':count minuti', // less reliable + 'min' => ':count minuti', // less reliable + 'a_minute' => ':count minuti', // less reliable + + 'second' => ':count maba', // less reliable + 's' => ':count maba', // less reliable + 'a_second' => ':count maba', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/dv.php b/vendor/nesbot/carbon/src/Carbon/Lang/dv.php new file mode 100644 index 0000000..4b8d7e1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/dv.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +$months = [ + 'ޖެނުއަރީ', + 'ފެބްރުއަރީ', + 'މާރިޗު', + 'އޭޕްރީލު', + 'މޭ', + 'ޖޫން', + 'ޖުލައި', + 'އޯގަސްޓު', + 'ސެޕްޓެމްބަރު', + 'އޮކްޓޯބަރު', + 'ނޮވެމްބަރު', + 'ޑިސެމްބަރު', +]; + +$weekdays = [ + 'އާދިއްތަ', + 'ހޯމަ', + 'އަންގާރަ', + 'ބުދަ', + 'ބުރާސްފަތި', + 'ހުކުރު', + 'ހޮނިހިރު', +]; + +/* + * Authors: + * - Josh Soref + * - Jawish Hameed + */ +return [ + 'year' => ':count '.'އަހަރު', + 'a_year' => '{1}'.'އަހަރެއް'.'|:count '.'އަހަރު', + 'month' => ':count '.'މަސް', + 'a_month' => '{1}'.'މަހެއް'.'|:count '.'މަސް', + 'week' => ':count '.'ހަފްތާ', + 'a_week' => '{1}'.'ސިކުންތުކޮޅެއް'.'|:count '.'ހަފްތާ', + 'day' => ':count '.'ދުވަސް', + 'a_day' => '{1}'.'ދުވަހެއް'.'|:count '.'ދުވަސް', + 'hour' => ':count '.'ގަޑިއިރު', + 'a_hour' => '{1}'.'ގަޑިއިރެއް'.'|:count '.'ގަޑިއިރު', + 'minute' => ':count '.'މިނިޓު', + 'a_minute' => '{1}'.'މިނިޓެއް'.'|:count '.'މިނިޓު', + 'second' => ':count '.'ސިކުންތު', + 'a_second' => '{1}'.'ސިކުންތުކޮޅެއް'.'|:count '.'ސިކުންތު', + 'ago' => 'ކުރިން :time', + 'from_now' => 'ތެރޭގައި :time', + 'after' => ':time ފަހުން', + 'before' => ':time ކުރި', + 'diff_yesterday' => 'އިއްޔެ', + 'diff_today' => 'މިއަދު', + 'diff_tomorrow' => 'މާދަމާ', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[މިއަދު] LT', + 'nextDay' => '[މާދަމާ] LT', + 'nextWeek' => 'dddd LT', + 'lastDay' => '[އިއްޔެ] LT', + 'lastWeek' => '[ފާއިތުވި] dddd LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['މކ', 'މފ'], + 'months' => $months, + 'months_short' => $months, + 'weekdays' => $weekdays, + 'weekdays_short' => $weekdays, + 'weekdays_min' => ['އާދި', 'ހޯމަ', 'އަން', 'ބުދަ', 'ބުރާ', 'ހުކު', 'ހޮނި'], + 'list' => [', ', ' އަދި '], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/dv_MV.php b/vendor/nesbot/carbon/src/Carbon/Lang/dv_MV.php new file mode 100644 index 0000000..2668d5b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/dv_MV.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ahmed Ali + */ + +$months = [ + 'ޖެނުއަރީ', + 'ފެބްރުއަރީ', + 'މާރިޗު', + 'އޭޕްރީލު', + 'މޭ', + 'ޖޫން', + 'ޖުލައި', + 'އޯގަސްޓު', + 'ސެޕްޓެމްބަރު', + 'އޮކްޓޯބަރު', + 'ނޮވެމްބަރު', + 'ޑިސެމްބަރު', +]; + +$weekdays = [ + 'އާދިއްތަ', + 'ހޯމަ', + 'އަންގާރަ', + 'ބުދަ', + 'ބުރާސްފަތި', + 'ހުކުރު', + 'ހޮނިހިރު', +]; + +return [ + 'year' => '{0}އަހަރެއް|[1,Inf]:count އަހަރު', + 'y' => '{0}އަހަރެއް|[1,Inf]:count އަހަރު', + 'month' => '{0}މައްސަރެއް|[1,Inf]:count މަސް', + 'm' => '{0}މައްސަރެއް|[1,Inf]:count މަސް', + 'week' => '{0}ހަފްތާއެއް|[1,Inf]:count ހަފްތާ', + 'w' => '{0}ހަފްތާއެއް|[1,Inf]:count ހަފްތާ', + 'day' => '{0}ދުވަސް|[1,Inf]:count ދުވަސް', + 'd' => '{0}ދުވަސް|[1,Inf]:count ދުވަސް', + 'hour' => '{0}ގަޑިއިރެއް|[1,Inf]:count ގަޑި', + 'h' => '{0}ގަޑިއިރެއް|[1,Inf]:count ގަޑި', + 'minute' => '{0}މިނެޓެއް|[1,Inf]:count މިނެޓް', + 'min' => '{0}މިނެޓެއް|[1,Inf]:count މިނެޓް', + 'second' => '{0}ސިކުންތެއް|[1,Inf]:count ސިކުންތު', + 's' => '{0}ސިކުންތެއް|[1,Inf]:count ސިކުންތު', + 'ago' => ':time ކުރިން', + 'from_now' => ':time ފަހުން', + 'after' => ':time ފަހުން', + 'before' => ':time ކުރި', + 'diff_yesterday' => 'އިއްޔެ', + 'diff_today' => 'މިއަދު', + 'diff_tomorrow' => 'މާދަމާ', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[މިއަދު] LT', + 'nextDay' => '[މާދަމާ] LT', + 'nextWeek' => 'dddd LT', + 'lastDay' => '[އިއްޔެ] LT', + 'lastWeek' => '[ފާއިތުވި] dddd LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['މކ', 'މފ'], + 'months' => $months, + 'months_short' => $months, + 'weekdays' => $weekdays, + 'weekdays_short' => $weekdays, + 'weekdays_min' => ['އާދި', 'ހޯމަ', 'އަން', 'ބުދަ', 'ބުރާ', 'ހުކު', 'ހޮނި'], + 'list' => [', ', ' އަދި '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/dyo.php b/vendor/nesbot/carbon/src/Carbon/Lang/dyo.php new file mode 100644 index 0000000..33082e6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/dyo.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'weekdays' => ['Dimas', 'Teneŋ', 'Talata', 'Alarbay', 'Aramisay', 'Arjuma', 'Sibiti'], + 'weekdays_short' => ['Dim', 'Ten', 'Tal', 'Ala', 'Ara', 'Arj', 'Sib'], + 'weekdays_min' => ['Dim', 'Ten', 'Tal', 'Ala', 'Ara', 'Arj', 'Sib'], + 'months' => ['Sanvie', 'Fébirie', 'Mars', 'Aburil', 'Mee', 'Sueŋ', 'Súuyee', 'Ut', 'Settembar', 'Oktobar', 'Novembar', 'Disambar'], + 'months_short' => ['Sa', 'Fe', 'Ma', 'Ab', 'Me', 'Su', 'Sú', 'Ut', 'Se', 'Ok', 'No', 'De'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/dz.php b/vendor/nesbot/carbon/src/Carbon/Lang/dz.php new file mode 100644 index 0000000..cc17e69 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/dz.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/dz_BT.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/dz_BT.php b/vendor/nesbot/carbon/src/Carbon/Lang/dz_BT.php new file mode 100644 index 0000000..bfbcaf4 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/dz_BT.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Sherubtse College bug-glibc@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'པསྱི་ལོYYཟལMMཚེསDD', + ], + 'months' => ['ཟླ་བ་དང་པ་', 'ཟླ་བ་གཉིས་པ་', 'ཟླ་བ་གསུམ་པ་', 'ཟླ་བ་བཞི་པ་', 'ཟླ་བ་ལྔ་ཕ་', 'ཟླ་བ་དྲུག་པ་', 'ཟླ་བ་བདུནཔ་', 'ཟླ་བ་བརྒྱད་པ་', 'ཟླ་བ་དགུ་པ་', 'ཟླ་བ་བཅུ་པ་', 'ཟླ་བ་བཅུ་གཅིག་པ་', 'ཟླ་བ་བཅུ་གཉིས་པ་'], + 'months_short' => ['ཟླ་༡', 'ཟླ་༢', 'ཟླ་༣', 'ཟླ་༤', 'ཟླ་༥', 'ཟླ་༦', 'ཟླ་༧', 'ཟླ་༨', 'ཟླ་༩', 'ཟླ་༡༠', 'ཟླ་༡༡', 'ཟླ་༡༢'], + 'weekdays' => ['གཟའ་ཟླ་བ་', 'གཟའ་མིག་དམར་', 'གཟའ་ལྷག་ཕ་', 'གཟའ་པུར་བུ་', 'གཟའ་པ་སངས་', 'གཟའ་སྤེན་ཕ་', 'གཟའ་ཉི་མ་'], + 'weekdays_short' => ['ཟླ་', 'མིར་', 'ལྷག་', 'པུར་', 'སངས་', 'སྤེན་', 'ཉི་'], + 'weekdays_min' => ['ཟླ་', 'མིར་', 'ལྷག་', 'པུར་', 'སངས་', 'སྤེན་', 'ཉི་'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ངས་ཆ', 'ཕྱི་ཆ'], + + 'year' => ':count ཆརཔ', // less reliable + 'y' => ':count ཆརཔ', // less reliable + 'a_year' => ':count ཆརཔ', // less reliable + + 'month' => ':count ཟླ་བ', // less reliable + 'm' => ':count ཟླ་བ', // less reliable + 'a_month' => ':count ཟླ་བ', // less reliable + + 'day' => ':count ཉི', // less reliable + 'd' => ':count ཉི', // less reliable + 'a_day' => ':count ཉི', // less reliable + + 'second' => ':count ཆ', // less reliable + 's' => ':count ཆ', // less reliable + 'a_second' => ':count ཆ', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ebu.php b/vendor/nesbot/carbon/src/Carbon/Lang/ebu.php new file mode 100644 index 0000000..f60bc6f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ebu.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['KI', 'UT'], + 'weekdays' => ['Kiumia', 'Njumatatu', 'Njumaine', 'Njumatano', 'Aramithi', 'Njumaa', 'NJumamothii'], + 'weekdays_short' => ['Kma', 'Tat', 'Ine', 'Tan', 'Arm', 'Maa', 'NMM'], + 'weekdays_min' => ['Kma', 'Tat', 'Ine', 'Tan', 'Arm', 'Maa', 'NMM'], + 'months' => ['Mweri wa mbere', 'Mweri wa kaĩri', 'Mweri wa kathatũ', 'Mweri wa kana', 'Mweri wa gatano', 'Mweri wa gatantatũ', 'Mweri wa mũgwanja', 'Mweri wa kanana', 'Mweri wa kenda', 'Mweri wa ikũmi', 'Mweri wa ikũmi na ũmwe', 'Mweri wa ikũmi na Kaĩrĩ'], + 'months_short' => ['Mbe', 'Kai', 'Kat', 'Kan', 'Gat', 'Gan', 'Mug', 'Knn', 'Ken', 'Iku', 'Imw', 'Igi'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ee.php b/vendor/nesbot/carbon/src/Carbon/Lang/ee.php new file mode 100644 index 0000000..f96c5c9 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ee.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['ŋ', 'ɣ'], + 'weekdays' => ['kɔsiɖa', 'dzoɖa', 'blaɖa', 'kuɖa', 'yawoɖa', 'fiɖa', 'memleɖa'], + 'weekdays_short' => ['kɔs', 'dzo', 'bla', 'kuɖ', 'yaw', 'fiɖ', 'mem'], + 'weekdays_min' => ['kɔs', 'dzo', 'bla', 'kuɖ', 'yaw', 'fiɖ', 'mem'], + 'months' => ['dzove', 'dzodze', 'tedoxe', 'afɔfĩe', 'dama', 'masa', 'siamlɔm', 'deasiamime', 'anyɔnyɔ', 'kele', 'adeɛmekpɔxe', 'dzome'], + 'months_short' => ['dzv', 'dzd', 'ted', 'afɔ', 'dam', 'mas', 'sia', 'dea', 'any', 'kel', 'ade', 'dzm'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'a [ga] h:mm', + 'LTS' => 'a [ga] h:mm:ss', + 'L' => 'M/D/YYYY', + 'LL' => 'MMM D [lia], YYYY', + 'LLL' => 'a [ga] h:mm MMMM D [lia] YYYY', + 'LLLL' => 'a [ga] h:mm dddd, MMMM D [lia] YYYY', + ], + + 'year' => 'ƒe :count', + 'y' => 'ƒe :count', + 'a_year' => 'ƒe :count', + + 'month' => 'ɣleti :count', + 'm' => 'ɣleti :count', + 'a_month' => 'ɣleti :count', + + 'week' => 'kwasiɖa :count', + 'w' => 'kwasiɖa :count', + 'a_week' => 'kwasiɖa :count', + + 'day' => 'ŋkeke :count', + 'd' => 'ŋkeke :count', + 'a_day' => 'ŋkeke :count', + + 'hour' => 'gaƒoƒo :count', + 'h' => 'gaƒoƒo :count', + 'a_hour' => 'gaƒoƒo :count', + + 'minute' => 'miniti :count', // less reliable + 'min' => 'miniti :count', // less reliable + 'a_minute' => 'miniti :count', // less reliable + + 'second' => 'sɛkɛnd :count', // less reliable + 's' => 'sɛkɛnd :count', // less reliable + 'a_second' => 'sɛkɛnd :count', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ee_TG.php b/vendor/nesbot/carbon/src/Carbon/Lang/ee_TG.php new file mode 100644 index 0000000..7a8b36c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ee_TG.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ee.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'LLL' => 'HH:mm MMMM D [lia] YYYY', + 'LLLL' => 'HH:mm dddd, MMMM D [lia] YYYY', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/el.php b/vendor/nesbot/carbon/src/Carbon/Lang/el.php new file mode 100644 index 0000000..7c40f9c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/el.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Alessandro Di Felice + * - François B + * - Tim Fish + * - Gabriel Monteagudo + * - JD Isaacks + * - yiannisdesp + * - Ilias Kasmeridis (iliaskasm) + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count χρόνος|:count χρόνια', + 'a_year' => 'ένας χρόνος|:count χρόνια', + 'y' => ':count χρ.', + 'month' => ':count μήνας|:count μήνες', + 'a_month' => 'ένας μήνας|:count μήνες', + 'm' => ':count μήν.', + 'week' => ':count εβδομάδα|:count εβδομάδες', + 'a_week' => 'μια εβδομάδα|:count εβδομάδες', + 'w' => ':count εβδ.', + 'day' => ':count μέρα|:count μέρες', + 'a_day' => 'μία μέρα|:count μέρες', + 'd' => ':count μέρ.', + 'hour' => ':count ώρα|:count ώρες', + 'a_hour' => 'μία ώρα|:count ώρες', + 'h' => ':count ώρα|:count ώρες', + 'minute' => ':count λεπτό|:count λεπτά', + 'a_minute' => 'ένα λεπτό|:count λεπτά', + 'min' => ':count λεπ.', + 'second' => ':count δευτερόλεπτο|:count δευτερόλεπτα', + 'a_second' => 'λίγα δευτερόλεπτα|:count δευτερόλεπτα', + 's' => ':count δευ.', + 'ago' => 'πριν :time', + 'from_now' => 'σε :time', + 'after' => ':time μετά', + 'before' => ':time πριν', + 'diff_now' => 'τώρα', + 'diff_today' => 'Σήμερα', + 'diff_today_regexp' => 'Σήμερα(?:\\s+{})?', + 'diff_yesterday' => 'χθες', + 'diff_yesterday_regexp' => 'Χθες(?:\\s+{})?', + 'diff_tomorrow' => 'αύριο', + 'diff_tomorrow_regexp' => 'Αύριο(?:\\s+{})?', + 'formats' => [ + 'LT' => 'h:mm A', + 'LTS' => 'h:mm:ss A', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm A', + 'LLLL' => 'dddd, D MMMM YYYY h:mm A', + ], + 'calendar' => [ + 'sameDay' => '[Σήμερα {}] LT', + 'nextDay' => '[Αύριο {}] LT', + 'nextWeek' => 'dddd [{}] LT', + 'lastDay' => '[Χθες {}] LT', + 'lastWeek' => function (CarbonInterface $current) { + switch ($current->dayOfWeek) { + case 6: + return '[το προηγούμενο] dddd [{}] LT'; + default: + return '[την προηγούμενη] dddd [{}] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => ':numberη', + 'meridiem' => ['ΠΜ', 'ΜΜ', 'πμ', 'μμ'], + 'months' => ['Ιανουαρίου', 'Φεβρουαρίου', 'Μαρτίου', 'Απριλίου', 'Μαΐου', 'Ιουνίου', 'Ιουλίου', 'Αυγούστου', 'Σεπτεμβρίου', 'Οκτωβρίου', 'Νοεμβρίου', 'Δεκεμβρίου'], + 'months_standalone' => ['Ιανουάριος', 'Φεβρουάριος', 'Μάρτιος', 'Απρίλιος', 'Μάιος', 'Ιούνιος', 'Ιούλιος', 'Αύγουστος', 'Σεπτέμβριος', 'Οκτώβριος', 'Νοέμβριος', 'Δεκέμβριος'], + 'months_regexp' => '/(D[oD]?[\s,]+MMMM|L{2,4}|l{2,4})/', + 'months_short' => ['Ιαν', 'Φεβ', 'Μαρ', 'Απρ', 'Μαϊ', 'Ιουν', 'Ιουλ', 'Αυγ', 'Σεπ', 'Οκτ', 'Νοε', 'Δεκ'], + 'weekdays' => ['Κυριακή', 'Δευτέρα', 'Τρίτη', 'Τετάρτη', 'Πέμπτη', 'Παρασκευή', 'Σάββατο'], + 'weekdays_short' => ['Κυρ', 'Δευ', 'Τρι', 'Τετ', 'Πεμ', 'Παρ', 'Σαβ'], + 'weekdays_min' => ['Κυ', 'Δε', 'Τρ', 'Τε', 'Πε', 'Πα', 'Σα'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' και '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/el_CY.php b/vendor/nesbot/carbon/src/Carbon/Lang/el_CY.php new file mode 100644 index 0000000..8a693c1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/el_CY.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Greek Debian Translation Team bug-glibc@gnu.org + */ +return array_replace_recursive(require __DIR__.'/el.php', [ + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/el_GR.php b/vendor/nesbot/carbon/src/Carbon/Lang/el_GR.php new file mode 100644 index 0000000..df196af --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/el_GR.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/el.php', [ + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en.php b/vendor/nesbot/carbon/src/Carbon/Lang/en.php new file mode 100644 index 0000000..a8633fe --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Milos Sakovic + * - Paul + * - Pete Scopes (pdscopes) + */ +return [ + /* + * {1}, {0} and ]1,Inf[ are not needed as it's the default for English pluralization. + * But as some languages are using en.php as a fallback, it's better to specify it + * explicitly so those languages also fallback to English pluralization when a unit + * is missing. + */ + 'year' => '{1}:count year|{0}:count years|]1,Inf[:count years', + 'a_year' => '{1}a year|{0}:count years|]1,Inf[:count years', + 'y' => '{1}:countyr|{0}:countyrs|]1,Inf[:countyrs', + 'month' => '{1}:count month|{0}:count months|]1,Inf[:count months', + 'a_month' => '{1}a month|{0}:count months|]1,Inf[:count months', + 'm' => '{1}:countmo|{0}:countmos|]1,Inf[:countmos', + 'week' => '{1}:count week|{0}:count weeks|]1,Inf[:count weeks', + 'a_week' => '{1}a week|{0}:count weeks|]1,Inf[:count weeks', + 'w' => ':countw', + 'day' => '{1}:count day|{0}:count days|]1,Inf[:count days', + 'a_day' => '{1}a day|{0}:count days|]1,Inf[:count days', + 'd' => ':countd', + 'hour' => '{1}:count hour|{0}:count hours|]1,Inf[:count hours', + 'a_hour' => '{1}an hour|{0}:count hours|]1,Inf[:count hours', + 'h' => ':counth', + 'minute' => '{1}:count minute|{0}:count minutes|]1,Inf[:count minutes', + 'a_minute' => '{1}a minute|{0}:count minutes|]1,Inf[:count minutes', + 'min' => ':countm', + 'second' => '{1}:count second|{0}:count seconds|]1,Inf[:count seconds', + 'a_second' => '{1}a few seconds|{0}:count seconds|]1,Inf[:count seconds', + 's' => ':counts', + 'millisecond' => '{1}:count millisecond|{0}:count milliseconds|]1,Inf[:count milliseconds', + 'a_millisecond' => '{1}a millisecond|{0}:count milliseconds|]1,Inf[:count milliseconds', + 'ms' => ':countms', + 'microsecond' => '{1}:count microsecond|{0}:count microseconds|]1,Inf[:count microseconds', + 'a_microsecond' => '{1}a microsecond|{0}:count microseconds|]1,Inf[:count microseconds', + 'µs' => ':countµs', + 'ago' => ':time ago', + 'from_now' => ':time from now', + 'after' => ':time after', + 'before' => ':time before', + 'diff_now' => 'just now', + 'diff_today' => 'today', + 'diff_yesterday' => 'yesterday', + 'diff_tomorrow' => 'tomorrow', + 'diff_before_yesterday' => 'before yesterday', + 'diff_after_tomorrow' => 'after tomorrow', + 'period_recurrences' => '{1}once|{0}:count times|]1,Inf[:count times', + 'period_interval' => 'every :interval', + 'period_start_date' => 'from :date', + 'period_end_date' => 'to :date', + 'months' => ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + 'weekdays' => ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], + 'weekdays_short' => ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + 'weekdays_min' => ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'], + 'ordinal' => function ($number) { + $lastDigit = $number % 10; + + return $number.( + (~~($number % 100 / 10) === 1) ? 'th' : ( + ($lastDigit === 1) ? 'st' : ( + ($lastDigit === 2) ? 'nd' : ( + ($lastDigit === 3) ? 'rd' : 'th' + ) + ) + ) + ); + }, + 'list' => [', ', ' and '], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_001.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_001.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_001.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_150.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_150.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_150.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_AG.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_AG.php new file mode 100644 index 0000000..2c1c64f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_AG.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Free Software Foundation, Inc. bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_AI.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_AI.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_AI.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_AS.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_AS.php new file mode 100644 index 0000000..f086dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_AS.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_AT.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_AT.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_AT.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_AU.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_AU.php new file mode 100644 index 0000000..f16bd4f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_AU.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kunal Marwaha + * - François B + * - Mayank Badola + * - JD Isaacks + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'from_now' => 'in :time', + 'formats' => [ + 'LT' => 'h:mm A', + 'LTS' => 'h:mm:ss A', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm A', + 'LLLL' => 'dddd, D MMMM YYYY h:mm A', + ], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_BB.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_BB.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_BB.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_BE.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_BE.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_BE.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_BI.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_BI.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_BI.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_BM.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_BM.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_BM.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_BS.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_BS.php new file mode 100644 index 0000000..f086dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_BS.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_BW.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_BW.php new file mode 100644 index 0000000..f086dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_BW.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_BZ.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_BZ.php new file mode 100644 index 0000000..f086dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_BZ.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_CA.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_CA.php new file mode 100644 index 0000000..e656086 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_CA.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Zhan Tong Zhang + * - Mayank Badola + * - JD Isaacks + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'from_now' => 'in :time', + 'formats' => [ + 'LT' => 'h:mm A', + 'LTS' => 'h:mm:ss A', + 'L' => 'YYYY-MM-DD', + 'LL' => 'MMMM D, YYYY', + 'LLL' => 'MMMM D, YYYY h:mm A', + 'LLLL' => 'dddd, MMMM D, YYYY h:mm A', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_CC.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_CC.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_CC.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_CH.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_CH.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_CH.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_CK.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_CK.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_CK.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_CM.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_CM.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_CM.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_CX.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_CX.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_CX.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_CY.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_CY.php new file mode 100644 index 0000000..a44c350 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_CY.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - NehaGautam + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'from_now' => 'in :time', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD-MM-YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_DE.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_DE.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_DE.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_DG.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_DG.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_DG.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_DK.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_DK.php new file mode 100644 index 0000000..9e8a8c6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_DK.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Danish Standards Association bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYY-MM-DD', + ], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_DM.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_DM.php new file mode 100644 index 0000000..f086dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_DM.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_ER.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_ER.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_ER.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_FI.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_FI.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_FI.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_FJ.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_FJ.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_FJ.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_FK.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_FK.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_FK.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_FM.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_FM.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_FM.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_GB.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_GB.php new file mode 100644 index 0000000..67d9fd6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_GB.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Mayank Badola + * - JD Isaacks + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'from_now' => 'in :time', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_GD.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_GD.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_GD.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_GG.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_GG.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_GG.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_GH.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_GH.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_GH.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_GI.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_GI.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_GI.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_GM.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_GM.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_GM.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_GU.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_GU.php new file mode 100644 index 0000000..f086dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_GU.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_GY.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_GY.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_GY.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_HK.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_HK.php new file mode 100644 index 0000000..34aae98 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_HK.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_IE.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_IE.php new file mode 100644 index 0000000..c8d3c2f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_IE.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Martin McWhorter + * - François B + * - Chris Cartlidge + * - JD Isaacks + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'from_now' => 'in :time', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD-MM-YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_IL.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_IL.php new file mode 100644 index 0000000..e607924 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_IL.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Yoav Amit + * - François B + * - Mayank Badola + * - JD Isaacks + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'from_now' => 'in :time', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_IM.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_IM.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_IM.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_IN.php new file mode 100644 index 0000000..00414e9 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_IN.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YY', + 'LL' => 'MMMM DD, YYYY', + 'LLL' => 'DD MMM HH:mm', + 'LLLL' => 'MMMM DD, YYYY HH:mm', + ], + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_IO.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_IO.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_IO.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_ISO.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_ISO.php new file mode 100644 index 0000000..11457b0 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_ISO.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-dd', + 'LL' => 'YYYY MMM D', + 'LLL' => 'YYYY MMMM D HH:mm', + 'LLLL' => 'dddd, YYYY MMMM DD HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_JE.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_JE.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_JE.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_JM.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_JM.php new file mode 100644 index 0000000..f086dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_JM.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_KE.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_KE.php new file mode 100644 index 0000000..f086dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_KE.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_KI.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_KI.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_KI.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_KN.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_KN.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_KN.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_KY.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_KY.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_KY.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_LC.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_LC.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_LC.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_LR.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_LR.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_LR.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_LS.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_LS.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_LS.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_MG.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_MG.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_MG.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_MH.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_MH.php new file mode 100644 index 0000000..f086dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_MH.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_MO.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_MO.php new file mode 100644 index 0000000..f086dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_MO.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_MP.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_MP.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_MP.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_MS.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_MS.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_MS.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_MT.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_MT.php new file mode 100644 index 0000000..f086dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_MT.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_MU.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_MU.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_MU.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_MW.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_MW.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_MW.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_MY.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_MY.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_MY.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_NA.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_NA.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_NA.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_NF.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_NF.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_NF.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_NG.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_NG.php new file mode 100644 index 0000000..67bceaa --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_NG.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_NL.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_NL.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_NL.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_NR.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_NR.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_NR.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_NU.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_NU.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_NU.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_NZ.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_NZ.php new file mode 100644 index 0000000..6a206a0 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_NZ.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Mayank Badola + * - Luke McGregor + * - JD Isaacks + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'from_now' => 'in :time', + 'formats' => [ + 'LT' => 'h:mm A', + 'LTS' => 'h:mm:ss A', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm A', + 'LLLL' => 'dddd, D MMMM YYYY h:mm A', + ], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_PG.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_PG.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_PG.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_PH.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_PH.php new file mode 100644 index 0000000..34aae98 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_PH.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_PK.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_PK.php new file mode 100644 index 0000000..f086dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_PK.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_PN.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_PN.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_PN.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_PR.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_PR.php new file mode 100644 index 0000000..f086dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_PR.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_PW.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_PW.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_PW.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_RW.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_RW.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_RW.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_SB.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_SB.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_SB.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_SC.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_SC.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_SC.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_SD.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_SD.php new file mode 100644 index 0000000..c4e2557 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_SD.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 6, + 'weekend' => [5, 6], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_SE.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_SE.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_SE.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_SG.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_SG.php new file mode 100644 index 0000000..5ee9524 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_SG.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'from_now' => 'in :time', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_SH.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_SH.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_SH.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_SI.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_SI.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_SI.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_SL.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_SL.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_SL.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_SS.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_SS.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_SS.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_SX.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_SX.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_SX.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_SZ.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_SZ.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_SZ.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_TC.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_TC.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_TC.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_TK.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_TK.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_TK.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_TO.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_TO.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_TO.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_TT.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_TT.php new file mode 100644 index 0000000..f086dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_TT.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_TV.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_TV.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_TV.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_TZ.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_TZ.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_TZ.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_UG.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_UG.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_UG.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_UM.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_UM.php new file mode 100644 index 0000000..f086dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_UM.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_US.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_US.php new file mode 100644 index 0000000..f086dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_US.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_US_Posix.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_US_Posix.php new file mode 100644 index 0000000..f086dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_US_Posix.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_VC.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_VC.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_VC.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_VG.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_VG.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_VG.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_VI.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_VI.php new file mode 100644 index 0000000..f086dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_VI.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_VU.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_VU.php new file mode 100644 index 0000000..e2dd81d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_VU.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_WS.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_WS.php new file mode 100644 index 0000000..f086dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_WS.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_ZA.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_ZA.php new file mode 100644 index 0000000..48ea947 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_ZA.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Zuza Software Foundation (Translate.org.za) Dwayne Bailey dwayne@translate.org.za + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YY', + 'LL' => 'MMMM DD, YYYY', + 'LLL' => 'DD MMM HH:mm', + 'LLLL' => 'MMMM DD, YYYY HH:mm', + ], + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_ZM.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_ZM.php new file mode 100644 index 0000000..d8a8cb5 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_ZM.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - ANLoc Martin Benjamin locales@africanlocalization.net + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/en_ZW.php b/vendor/nesbot/carbon/src/Carbon/Lang/en_ZW.php new file mode 100644 index 0000000..f086dc6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/en_ZW.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/en.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/eo.php b/vendor/nesbot/carbon/src/Carbon/Lang/eo.php new file mode 100644 index 0000000..7c2efba --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/eo.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - François B + * - Mia Nordentoft + * - JD Isaacks + */ +return [ + 'year' => ':count jaro|:count jaroj', + 'a_year' => 'jaro|:count jaroj', + 'y' => ':count j.', + 'month' => ':count monato|:count monatoj', + 'a_month' => 'monato|:count monatoj', + 'm' => ':count mo.', + 'week' => ':count semajno|:count semajnoj', + 'a_week' => 'semajno|:count semajnoj', + 'w' => ':count sem.', + 'day' => ':count tago|:count tagoj', + 'a_day' => 'tago|:count tagoj', + 'd' => ':count t.', + 'hour' => ':count horo|:count horoj', + 'a_hour' => 'horo|:count horoj', + 'h' => ':count h.', + 'minute' => ':count minuto|:count minutoj', + 'a_minute' => 'minuto|:count minutoj', + 'min' => ':count min.', + 'second' => ':count sekundo|:count sekundoj', + 'a_second' => 'sekundoj|:count sekundoj', + 's' => ':count sek.', + 'ago' => 'antaŭ :time', + 'from_now' => 'post :time', + 'after' => ':time poste', + 'before' => ':time antaŭe', + 'diff_yesterday' => 'Hieraŭ', + 'diff_yesterday_regexp' => 'Hieraŭ(?:\\s+je)?', + 'diff_today' => 'Hodiaŭ', + 'diff_today_regexp' => 'Hodiaŭ(?:\\s+je)?', + 'diff_tomorrow' => 'Morgaŭ', + 'diff_tomorrow_regexp' => 'Morgaŭ(?:\\s+je)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-DD', + 'LL' => 'D[-a de] MMMM, YYYY', + 'LLL' => 'D[-a de] MMMM, YYYY HH:mm', + 'LLLL' => 'dddd, [la] D[-a de] MMMM, YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Hodiaŭ je] LT', + 'nextDay' => '[Morgaŭ je] LT', + 'nextWeek' => 'dddd [je] LT', + 'lastDay' => '[Hieraŭ je] LT', + 'lastWeek' => '[pasinta] dddd [je] LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':numbera', + 'meridiem' => ['a.t.m.', 'p.t.m.'], + 'months' => ['januaro', 'februaro', 'marto', 'aprilo', 'majo', 'junio', 'julio', 'aŭgusto', 'septembro', 'oktobro', 'novembro', 'decembro'], + 'months_short' => ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'aŭg', 'sep', 'okt', 'nov', 'dec'], + 'weekdays' => ['dimanĉo', 'lundo', 'mardo', 'merkredo', 'ĵaŭdo', 'vendredo', 'sabato'], + 'weekdays_short' => ['dim', 'lun', 'mard', 'merk', 'ĵaŭ', 'ven', 'sab'], + 'weekdays_min' => ['di', 'lu', 'ma', 'me', 'ĵa', 've', 'sa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' kaj '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es.php b/vendor/nesbot/carbon/src/Carbon/Lang/es.php new file mode 100644 index 0000000..f77ec39 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kunal Marwaha + * - kostas + * - François B + * - Tim Fish + * - Claire Coloma + * - Steven Heinrich + * - JD Isaacks + * - Raphael Amorim + * - Jorge Y. Castillo + * - Víctor Díaz + * - Diego + * - Sebastian Thierer + * - quinterocesar + * - Daniel Commesse Liévanos (danielcommesse) + * - Pete Scopes (pdscopes) + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count año|:count años', + 'a_year' => 'un año|:count años', + 'y' => ':count año|:count años', + 'month' => ':count mes|:count meses', + 'a_month' => 'un mes|:count meses', + 'm' => ':count mes|:count meses', + 'week' => ':count semana|:count semanas', + 'a_week' => 'una semana|:count semanas', + 'w' => ':countsem', + 'day' => ':count día|:count días', + 'a_day' => 'un día|:count días', + 'd' => ':countd', + 'hour' => ':count hora|:count horas', + 'a_hour' => 'una hora|:count horas', + 'h' => ':counth', + 'minute' => ':count minuto|:count minutos', + 'a_minute' => 'un minuto|:count minutos', + 'min' => ':countm', + 'second' => ':count segundo|:count segundos', + 'a_second' => 'unos segundos|:count segundos', + 's' => ':counts', + 'millisecond' => ':count milisegundo|:count milisegundos', + 'a_millisecond' => 'un milisegundo|:count milisegundos', + 'ms' => ':countms', + 'microsecond' => ':count microsegundo|:count microsegundos', + 'a_microsecond' => 'un microsegundo|:count microsegundos', + 'µs' => ':countµs', + 'ago' => 'hace :time', + 'from_now' => 'en :time', + 'after' => ':time después', + 'before' => ':time antes', + 'diff_now' => 'ahora mismo', + 'diff_today' => 'hoy', + 'diff_today_regexp' => 'hoy(?:\\s+a)?(?:\\s+las)?', + 'diff_yesterday' => 'ayer', + 'diff_yesterday_regexp' => 'ayer(?:\\s+a)?(?:\\s+las)?', + 'diff_tomorrow' => 'mañana', + 'diff_tomorrow_regexp' => 'mañana(?:\\s+a)?(?:\\s+las)?', + 'diff_before_yesterday' => 'anteayer', + 'diff_after_tomorrow' => 'pasado mañana', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D [de] MMMM [de] YYYY', + 'LLL' => 'D [de] MMMM [de] YYYY H:mm', + 'LLLL' => 'dddd, D [de] MMMM [de] YYYY H:mm', + ], + 'calendar' => [ + 'sameDay' => function (CarbonInterface $current) { + return '[hoy a la'.($current->hour !== 1 ? 's' : '').'] LT'; + }, + 'nextDay' => function (CarbonInterface $current) { + return '[mañana a la'.($current->hour !== 1 ? 's' : '').'] LT'; + }, + 'nextWeek' => function (CarbonInterface $current) { + return 'dddd [a la'.($current->hour !== 1 ? 's' : '').'] LT'; + }, + 'lastDay' => function (CarbonInterface $current) { + return '[ayer a la'.($current->hour !== 1 ? 's' : '').'] LT'; + }, + 'lastWeek' => function (CarbonInterface $current) { + return '[el] dddd [pasado a la'.($current->hour !== 1 ? 's' : '').'] LT'; + }, + 'sameElse' => 'L', + ], + 'months' => ['enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'], + 'months_short' => ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic'], + 'mmm_suffix' => '.', + 'ordinal' => ':numberº', + 'weekdays' => ['domingo', 'lunes', 'martes', 'miércoles', 'jueves', 'viernes', 'sábado'], + 'weekdays_short' => ['dom.', 'lun.', 'mar.', 'mié.', 'jue.', 'vie.', 'sáb.'], + 'weekdays_min' => ['do', 'lu', 'ma', 'mi', 'ju', 'vi', 'sá'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' y '], + 'meridiem' => ['a. m.', 'p. m.'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_419.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_419.php new file mode 100644 index 0000000..a74806e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_419.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_AR.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_AR.php new file mode 100644 index 0000000..a74806e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_AR.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_BO.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_BO.php new file mode 100644 index 0000000..c9b8432 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_BO.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_BR.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_BR.php new file mode 100644 index 0000000..378d054 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_BR.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_BZ.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_BZ.php new file mode 100644 index 0000000..378d054 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_BZ.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_CL.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_CL.php new file mode 100644 index 0000000..a74806e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_CL.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_CO.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_CO.php new file mode 100644 index 0000000..a74806e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_CO.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_CR.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_CR.php new file mode 100644 index 0000000..553fc09 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_CR.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Free Software Foundation, Inc. bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_CU.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_CU.php new file mode 100644 index 0000000..f02e1a6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_CU.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_DO.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_DO.php new file mode 100644 index 0000000..0f855ba --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_DO.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - kostas + * - François B + * - Tim Fish + * - Chiel Robben + * - Claire Coloma + * - Steven Heinrich + * - JD Isaacks + * - Raphael Amorim + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'diff_before_yesterday' => 'anteayer', + 'formats' => [ + 'LT' => 'h:mm A', + 'LTS' => 'h:mm:ss A', + 'LLL' => 'D [de] MMMM [de] YYYY h:mm A', + 'LLLL' => 'dddd, D [de] MMMM [de] YYYY h:mm A', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_EA.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_EA.php new file mode 100644 index 0000000..f02e1a6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_EA.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_EC.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_EC.php new file mode 100644 index 0000000..a74806e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_EC.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_ES.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_ES.php new file mode 100644 index 0000000..19217c2 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_ES.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return require __DIR__.'/es.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_GQ.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_GQ.php new file mode 100644 index 0000000..f02e1a6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_GQ.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_GT.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_GT.php new file mode 100644 index 0000000..a74806e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_GT.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_HN.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_HN.php new file mode 100644 index 0000000..a74806e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_HN.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_IC.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_IC.php new file mode 100644 index 0000000..f02e1a6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_IC.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_MX.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_MX.php new file mode 100644 index 0000000..61e14cf --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_MX.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'diff_before_yesterday' => 'antier', + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_NI.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_NI.php new file mode 100644 index 0000000..6b964c1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_NI.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Free Software Foundation, Inc. bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_PA.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_PA.php new file mode 100644 index 0000000..a74806e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_PA.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_PE.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_PE.php new file mode 100644 index 0000000..a74806e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_PE.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_PH.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_PH.php new file mode 100644 index 0000000..deae06a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_PH.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'D/M/yy', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D [de] MMMM [de] YYYY h:mm a', + 'LLLL' => 'dddd, D [de] MMMM [de] YYYY h:mm a', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_PR.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_PR.php new file mode 100644 index 0000000..6b964c1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_PR.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Free Software Foundation, Inc. bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_PY.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_PY.php new file mode 100644 index 0000000..a74806e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_PY.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_SV.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_SV.php new file mode 100644 index 0000000..00db08e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_SV.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'months' => ['enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'], + 'months_short' => ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic'], + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_US.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_US.php new file mode 100644 index 0000000..f333136 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_US.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kunal Marwaha + * - Josh Soref + * - Jørn Ølmheim + * - Craig Patik + * - bustta + * - François B + * - Tim Fish + * - Claire Coloma + * - Steven Heinrich + * - JD Isaacks + * - Raphael Amorim + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'diff_before_yesterday' => 'anteayer', + 'formats' => [ + 'LT' => 'h:mm A', + 'LTS' => 'h:mm:ss A', + 'L' => 'MM/DD/YYYY', + 'LL' => 'MMMM [de] D [de] YYYY', + 'LLL' => 'MMMM [de] D [de] YYYY h:mm A', + 'LLLL' => 'dddd, MMMM [de] D [de] YYYY h:mm A', + ], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_UY.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_UY.php new file mode 100644 index 0000000..39baff8 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_UY.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'months' => ['enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'setiembre', 'octubre', 'noviembre', 'diciembre'], + 'months_short' => ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'set', 'oct', 'nov', 'dic'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/es_VE.php b/vendor/nesbot/carbon/src/Carbon/Lang/es_VE.php new file mode 100644 index 0000000..a74806e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/es_VE.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/es.php', [ + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/et.php b/vendor/nesbot/carbon/src/Carbon/Lang/et.php new file mode 100644 index 0000000..f49c880 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/et.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - Andres Ivanov + * - Tsutomu Kuroda + * - tjku + * - Max Melentiev + * - Juanito Fatas + * - RM87 + * - Akira Matsuda + * - Christopher Dell + * - Enrique Vidal + * - Simone Carletti + * - Aaron Patterson + * - Esko Lehtme + * - Mart Karu + * - Nicolás Hock Isaza + * - Kevin Valdek + * - Zahhar Kirillov + * - João Magalhães + * - Ingmar + * - Illimar Tambek + * - Mihkel + */ +return [ + 'year' => ':count aasta|:count aastat', + 'y' => ':count a', + 'month' => ':count kuu|:count kuud', + 'm' => ':count k', + 'week' => ':count nädal|:count nädalat', + 'w' => ':count näd', + 'day' => ':count päev|:count päeva', + 'd' => ':count p', + 'hour' => ':count tund|:count tundi', + 'h' => ':count t', + 'minute' => ':count minut|:count minutit', + 'min' => ':count min', + 'second' => ':count sekund|:count sekundit', + 's' => ':count s', + 'ago' => ':time tagasi', + 'from_now' => ':time pärast', + 'after' => ':time pärast', + 'before' => ':time enne', + 'year_from_now' => ':count aasta', + 'month_from_now' => ':count kuu', + 'week_from_now' => ':count nädala', + 'day_from_now' => ':count päeva', + 'hour_from_now' => ':count tunni', + 'minute_from_now' => ':count minuti', + 'second_from_now' => ':count sekundi', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'diff_now' => 'nüüd', + 'diff_today' => 'täna', + 'diff_yesterday' => 'eile', + 'diff_tomorrow' => 'homme', + 'diff_before_yesterday' => 'üleeile', + 'diff_after_tomorrow' => 'ülehomme', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D. MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[täna] LT', + 'nextDay' => '[homme] LT', + 'lastDay' => '[eile] LT', + 'nextWeek' => 'dddd LT', + 'lastWeek' => '[eelmine] dddd LT', + 'sameElse' => 'L', + ], + 'months' => ['jaanuar', 'veebruar', 'märts', 'aprill', 'mai', 'juuni', 'juuli', 'august', 'september', 'oktoober', 'november', 'detsember'], + 'months_short' => ['jaan', 'veebr', 'märts', 'apr', 'mai', 'juuni', 'juuli', 'aug', 'sept', 'okt', 'nov', 'dets'], + 'weekdays' => ['pühapäev', 'esmaspäev', 'teisipäev', 'kolmapäev', 'neljapäev', 'reede', 'laupäev'], + 'weekdays_short' => ['P', 'E', 'T', 'K', 'N', 'R', 'L'], + 'weekdays_min' => ['P', 'E', 'T', 'K', 'N', 'R', 'L'], + 'list' => [', ', ' ja '], + 'meridiem' => ['enne lõunat', 'pärast lõunat'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/et_EE.php b/vendor/nesbot/carbon/src/Carbon/Lang/et_EE.php new file mode 100644 index 0000000..0f112b3 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/et_EE.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/et.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/eu.php b/vendor/nesbot/carbon/src/Carbon/Lang/eu.php new file mode 100644 index 0000000..a543f1a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/eu.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - François B + * - JD Isaacks + */ +return [ + 'year' => 'urte bat|:count urte', + 'y' => 'Urte 1|:count urte', + 'month' => 'hilabete bat|:count hilabete', + 'm' => 'Hile 1|:count hile', + 'week' => 'Aste 1|:count aste', + 'w' => 'Aste 1|:count aste', + 'day' => 'egun bat|:count egun', + 'd' => 'Egun 1|:count egun', + 'hour' => 'ordu bat|:count ordu', + 'h' => 'Ordu 1|:count ordu', + 'minute' => 'minutu bat|:count minutu', + 'min' => 'Minutu 1|:count minutu', + 'second' => 'segundo batzuk|:count segundo', + 's' => 'Segundu 1|:count segundu', + 'ago' => 'duela :time', + 'from_now' => ':time barru', + 'after' => ':time geroago', + 'before' => ':time lehenago', + 'diff_now' => 'orain', + 'diff_today' => 'gaur', + 'diff_yesterday' => 'atzo', + 'diff_tomorrow' => 'bihar', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-DD', + 'LL' => 'YYYY[ko] MMMM[ren] D[a]', + 'LLL' => 'YYYY[ko] MMMM[ren] D[a] HH:mm', + 'LLLL' => 'dddd, YYYY[ko] MMMM[ren] D[a] HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[gaur] LT[etan]', + 'nextDay' => '[bihar] LT[etan]', + 'nextWeek' => 'dddd LT[etan]', + 'lastDay' => '[atzo] LT[etan]', + 'lastWeek' => '[aurreko] dddd LT[etan]', + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['urtarrila', 'otsaila', 'martxoa', 'apirila', 'maiatza', 'ekaina', 'uztaila', 'abuztua', 'iraila', 'urria', 'azaroa', 'abendua'], + 'months_short' => ['urt.', 'ots.', 'mar.', 'api.', 'mai.', 'eka.', 'uzt.', 'abu.', 'ira.', 'urr.', 'aza.', 'abe.'], + 'weekdays' => ['igandea', 'astelehena', 'asteartea', 'asteazkena', 'osteguna', 'ostirala', 'larunbata'], + 'weekdays_short' => ['ig.', 'al.', 'ar.', 'az.', 'og.', 'ol.', 'lr.'], + 'weekdays_min' => ['ig', 'al', 'ar', 'az', 'og', 'ol', 'lr'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' eta '], + 'meridiem' => ['g', 'a'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/eu_ES.php b/vendor/nesbot/carbon/src/Carbon/Lang/eu_ES.php new file mode 100644 index 0000000..0d1e82a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/eu_ES.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/eu.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ewo.php b/vendor/nesbot/carbon/src/Carbon/Lang/ewo.php new file mode 100644 index 0000000..7808ab5 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ewo.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['kíkíríg', 'ngəgógəle'], + 'weekdays' => ['sɔ́ndɔ', 'mɔ́ndi', 'sɔ́ndɔ məlú mə́bɛ̌', 'sɔ́ndɔ məlú mə́lɛ́', 'sɔ́ndɔ məlú mə́nyi', 'fúladé', 'séradé'], + 'weekdays_short' => ['sɔ́n', 'mɔ́n', 'smb', 'sml', 'smn', 'fúl', 'sér'], + 'weekdays_min' => ['sɔ́n', 'mɔ́n', 'smb', 'sml', 'smn', 'fúl', 'sér'], + 'months' => ['ngɔn osú', 'ngɔn bɛ̌', 'ngɔn lála', 'ngɔn nyina', 'ngɔn tána', 'ngɔn saməna', 'ngɔn zamgbála', 'ngɔn mwom', 'ngɔn ebulú', 'ngɔn awóm', 'ngɔn awóm ai dziá', 'ngɔn awóm ai bɛ̌'], + 'months_short' => ['ngo', 'ngb', 'ngl', 'ngn', 'ngt', 'ngs', 'ngz', 'ngm', 'nge', 'nga', 'ngad', 'ngab'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + + // Too unreliable + /* + 'year' => ':count mbu', // less reliable + 'y' => ':count mbu', // less reliable + 'a_year' => ':count mbu', // less reliable + + 'month' => ':count ngòn', // less reliable + 'm' => ':count ngòn', // less reliable + 'a_month' => ':count ngòn', // less reliable + + 'week' => ':count mësë', // less reliable + 'w' => ':count mësë', // less reliable + 'a_week' => ':count mësë', // less reliable + + 'day' => ':count mësë', // less reliable + 'd' => ':count mësë', // less reliable + 'a_day' => ':count mësë', // less reliable + + 'hour' => ':count awola', // less reliable + 'h' => ':count awola', // less reliable + 'a_hour' => ':count awola', // less reliable + + 'minute' => ':count awola', // less reliable + 'min' => ':count awola', // less reliable + 'a_minute' => ':count awola', // less reliable + */ +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fa.php b/vendor/nesbot/carbon/src/Carbon/Lang/fa.php new file mode 100644 index 0000000..72e0308 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fa.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - François B + * - Nasser Ghiasi + * - JD Isaacks + * - Hossein Jabbari + * - nimamo + * - hafezdivandari + * - Hassan Pezeshk (hpez) + */ +return [ + 'year' => ':count سال', + 'a_year' => 'یک سال'.'|:count '.'سال', + 'y' => ':count سال', + 'month' => ':count ماه', + 'a_month' => 'یک ماه'.'|:count '.'ماه', + 'm' => ':count ماه', + 'week' => ':count هفته', + 'a_week' => 'یک هفته'.'|:count '.'هفته', + 'w' => ':count هفته', + 'day' => ':count روز', + 'a_day' => 'یک روز'.'|:count '.'روز', + 'd' => ':count روز', + 'hour' => ':count ساعت', + 'a_hour' => 'یک ساعت'.'|:count '.'ساعت', + 'h' => ':count ساعت', + 'minute' => ':count دقیقه', + 'a_minute' => 'یک دقیقه'.'|:count '.'دقیقه', + 'min' => ':count دقیقه', + 'second' => ':count ثانیه', + 's' => ':count ثانیه', + 'ago' => ':time پیش', + 'from_now' => ':time دیگر', + 'after' => ':time پس از', + 'before' => ':time پیش از', + 'diff_now' => 'اکنون', + 'diff_today' => 'امروز', + 'diff_today_regexp' => 'امروز(?:\\s+ساعت)?', + 'diff_yesterday' => 'دیروز', + 'diff_yesterday_regexp' => 'دیروز(?:\\s+ساعت)?', + 'diff_tomorrow' => 'فردا', + 'diff_tomorrow_regexp' => 'فردا(?:\\s+ساعت)?', + 'formats' => [ + 'LT' => 'OH:Om', + 'LTS' => 'OH:Om:Os', + 'L' => 'OD/OM/OY', + 'LL' => 'OD MMMM OY', + 'LLL' => 'OD MMMM OY OH:Om', + 'LLLL' => 'dddd, OD MMMM OY OH:Om', + ], + 'calendar' => [ + 'sameDay' => '[امروز ساعت] LT', + 'nextDay' => '[فردا ساعت] LT', + 'nextWeek' => 'dddd [ساعت] LT', + 'lastDay' => '[دیروز ساعت] LT', + 'lastWeek' => 'dddd [پیش] [ساعت] LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':timeم', + 'meridiem' => ['قبل از ظهر', 'بعد از ظهر'], + 'months' => ['ژانویه', 'فوریه', 'مارس', 'آوریل', 'مه', 'ژوئن', 'ژوئیه', 'اوت', 'سپتامبر', 'اکتبر', 'نوامبر', 'دسامبر'], + 'months_short' => ['ژانویه', 'فوریه', 'مارس', 'آوریل', 'مه', 'ژوئن', 'ژوئیه', 'اوت', 'سپتامبر', 'اکتبر', 'نوامبر', 'دسامبر'], + 'weekdays' => ['یکشنبه', 'دوشنبه', 'سه‌شنبه', 'چهارشنبه', 'پنجشنبه', 'جمعه', 'شنبه'], + 'weekdays_short' => ['یکشنبه', 'دوشنبه', 'سه‌شنبه', 'چهارشنبه', 'پنجشنبه', 'جمعه', 'شنبه'], + 'weekdays_min' => ['ی', 'د', 'س', 'چ', 'پ', 'ج', 'ش'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'list' => ['، ', ' و '], + 'alt_numbers' => ['۰۰', '۰۱', '۰۲', '۰۳', '۰۴', '۰۵', '۰۶', '۰۷', '۰۸', '۰۹', '۱۰', '۱۱', '۱۲', '۱۳', '۱۴', '۱۵', '۱۶', '۱۷', '۱۸', '۱۹', '۲۰', '۲۱', '۲۲', '۲۳', '۲۴', '۲۵', '۲۶', '۲۷', '۲۸', '۲۹', '۳۰', '۳۱', '۳۲', '۳۳', '۳۴', '۳۵', '۳۶', '۳۷', '۳۸', '۳۹', '۴۰', '۴۱', '۴۲', '۴۳', '۴۴', '۴۵', '۴۶', '۴۷', '۴۸', '۴۹', '۵۰', '۵۱', '۵۲', '۵۳', '۵۴', '۵۵', '۵۶', '۵۷', '۵۸', '۵۹', '۶۰', '۶۱', '۶۲', '۶۳', '۶۴', '۶۵', '۶۶', '۶۷', '۶۸', '۶۹', '۷۰', '۷۱', '۷۲', '۷۳', '۷۴', '۷۵', '۷۶', '۷۷', '۷۸', '۷۹', '۸۰', '۸۱', '۸۲', '۸۳', '۸۴', '۸۵', '۸۶', '۸۷', '۸۸', '۸۹', '۹۰', '۹۱', '۹۲', '۹۳', '۹۴', '۹۵', '۹۶', '۹۷', '۹۸', '۹۹'], + 'months_short_standalone' => ['ژانویه', 'فوریه', 'مارس', 'آوریل', 'مه', 'ژوئن', 'ژوئیه', 'اوت', 'سپتامبر', 'اکتبر', 'نوامبر', 'دسامبر'], + 'weekend' => [5, 5], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fa_AF.php b/vendor/nesbot/carbon/src/Carbon/Lang/fa_AF.php new file mode 100644 index 0000000..6947100 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fa_AF.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fa.php', [ + 'meridiem' => ['ق', 'ب'], + 'weekend' => [4, 5], + 'formats' => [ + 'L' => 'OY/OM/OD', + 'LL' => 'OD MMM OY', + 'LLL' => 'OD MMMM OY،‏ H:mm', + 'LLLL' => 'dddd OD MMMM OY،‏ H:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fa_IR.php b/vendor/nesbot/carbon/src/Carbon/Lang/fa_IR.php new file mode 100644 index 0000000..08d0182 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fa_IR.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fa.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ff.php b/vendor/nesbot/carbon/src/Carbon/Lang/ff.php new file mode 100644 index 0000000..9525c95 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ff.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'months' => ['siilo', 'colte', 'mbooy', 'seeɗto', 'duujal', 'korse', 'morso', 'juko', 'siilto', 'yarkomaa', 'jolal', 'bowte'], + 'months_short' => ['sii', 'col', 'mbo', 'see', 'duu', 'kor', 'mor', 'juk', 'slt', 'yar', 'jol', 'bow'], + 'weekdays' => ['dewo', 'aaɓnde', 'mawbaare', 'njeslaare', 'naasaande', 'mawnde', 'hoore-biir'], + 'weekdays_short' => ['dew', 'aaɓ', 'maw', 'nje', 'naa', 'mwd', 'hbi'], + 'weekdays_min' => ['dew', 'aaɓ', 'maw', 'nje', 'naa', 'mwd', 'hbi'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['subaka', 'kikiiɗe'], + + 'year' => ':count baret', // less reliable + 'y' => ':count baret', // less reliable + 'a_year' => ':count baret', // less reliable + + 'month' => ':count lewru', // less reliable + 'm' => ':count lewru', // less reliable + 'a_month' => ':count lewru', // less reliable + + 'week' => ':count naange', // less reliable + 'w' => ':count naange', // less reliable + 'a_week' => ':count naange', // less reliable + + 'day' => ':count dian', // less reliable + 'd' => ':count dian', // less reliable + 'a_day' => ':count dian', // less reliable + + 'hour' => ':count montor', // less reliable + 'h' => ':count montor', // less reliable + 'a_hour' => ':count montor', // less reliable + + 'minute' => ':count tokossuoum', // less reliable + 'min' => ':count tokossuoum', // less reliable + 'a_minute' => ':count tokossuoum', // less reliable + + 'second' => ':count tenen', // less reliable + 's' => ':count tenen', // less reliable + 'a_second' => ':count tenen', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ff_CM.php b/vendor/nesbot/carbon/src/Carbon/Lang/ff_CM.php new file mode 100644 index 0000000..b797ac0 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ff_CM.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ff.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ff_GN.php b/vendor/nesbot/carbon/src/Carbon/Lang/ff_GN.php new file mode 100644 index 0000000..b797ac0 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ff_GN.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ff.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ff_MR.php b/vendor/nesbot/carbon/src/Carbon/Lang/ff_MR.php new file mode 100644 index 0000000..2f4c29f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ff_MR.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ff.php', [ + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd D MMMM YYYY h:mm a', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ff_SN.php b/vendor/nesbot/carbon/src/Carbon/Lang/ff_SN.php new file mode 100644 index 0000000..1e4c8b6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ff_SN.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Pular-Fulfulde.org Ibrahima Sarr admin@pulaar-fulfulde.org + */ +return require __DIR__.'/ff.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fi.php b/vendor/nesbot/carbon/src/Carbon/Lang/fi.php new file mode 100644 index 0000000..2003e1e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fi.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - Janne Warén + * - digitalfrost + * - Tsutomu Kuroda + * - Roope Salmi + * - tjku + * - Max Melentiev + * - Sami Haahtinen + * - Teemu Leisti + * - Artem Ignatyev + * - Akira Matsuda + * - Christopher Dell + * - Enrique Vidal + * - Simone Carletti + * - Robert Bjarnason + * - Aaron Patterson + * - Nicolás Hock Isaza + * - Tom Hughes + * - Sven Fuchs + * - Petri Kivikangas + * - Nizar Jouini + * - Marko Seppae + * - Tomi Mynttinen (Pikseli) + * - Petteri (powergrip) + */ +return [ + 'year' => ':count vuosi|:count vuotta', + 'y' => ':count v', + 'month' => ':count kuukausi|:count kuukautta', + 'm' => ':count kk', + 'week' => ':count viikko|:count viikkoa', + 'w' => ':count vk', + 'day' => ':count päivä|:count päivää', + 'd' => ':count pv', + 'hour' => ':count tunti|:count tuntia', + 'h' => ':count t', + 'minute' => ':count minuutti|:count minuuttia', + 'min' => ':count min', + 'second' => ':count sekunti|:count sekuntia', + 'a_second' => 'muutama sekunti|:count sekuntia', + 's' => ':count s', + 'ago' => ':time sitten', + 'from_now' => ':time päästä', + 'year_from_now' => ':count vuoden', + 'month_from_now' => ':count kuukauden', + 'week_from_now' => ':count viikon', + 'day_from_now' => ':count päivän', + 'hour_from_now' => ':count tunnin', + 'minute_from_now' => ':count minuutin', + 'second_from_now' => ':count sekunnin', + 'after' => ':time sen jälkeen', + 'before' => ':time ennen', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' ja '], + 'diff_now' => 'nyt', + 'diff_yesterday' => 'eilen', + 'diff_tomorrow' => 'huomenna', + 'formats' => [ + 'LT' => 'HH.mm', + 'LTS' => 'HH.mm:ss', + 'L' => 'D.M.YYYY', + 'LL' => 'dddd D. MMMM[ta] YYYY', + 'LLL' => 'D.MM. HH.mm', + 'LLLL' => 'D. MMMM[ta] YYYY HH.mm', + ], + 'weekdays' => ['sunnuntai', 'maanantai', 'tiistai', 'keskiviikko', 'torstai', 'perjantai', 'lauantai'], + 'weekdays_short' => ['su', 'ma', 'ti', 'ke', 'to', 'pe', 'la'], + 'weekdays_min' => ['su', 'ma', 'ti', 'ke', 'to', 'pe', 'la'], + 'months' => ['tammikuu', 'helmikuu', 'maaliskuu', 'huhtikuu', 'toukokuu', 'kesäkuu', 'heinäkuu', 'elokuu', 'syyskuu', 'lokakuu', 'marraskuu', 'joulukuu'], + 'months_short' => ['tammi', 'helmi', 'maalis', 'huhti', 'touko', 'kesä', 'heinä', 'elo', 'syys', 'loka', 'marras', 'joulu'], + 'meridiem' => ['aamupäivä', 'iltapäivä'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fi_FI.php b/vendor/nesbot/carbon/src/Carbon/Lang/fi_FI.php new file mode 100644 index 0000000..920f1ca --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fi_FI.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fi.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fil.php b/vendor/nesbot/carbon/src/Carbon/Lang/fil.php new file mode 100644 index 0000000..61114e3 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fil.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/fil_PH.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fil_PH.php b/vendor/nesbot/carbon/src/Carbon/Lang/fil_PH.php new file mode 100644 index 0000000..bcf1580 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fil_PH.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Rene Torres Rene Torres, Pablo Saratxaga rgtorre@rocketmail.com, pablo@mandrakesoft.com + * - Jaycee Mariano (alohajaycee) + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'MM/DD/YY', + ], + 'months' => ['Enero', 'Pebrero', 'Marso', 'Abril', 'Mayo', 'Hunyo', 'Hulyo', 'Agosto', 'Setyembre', 'Oktubre', 'Nobyembre', 'Disyembre'], + 'months_short' => ['Ene', 'Peb', 'Mar', 'Abr', 'May', 'Hun', 'Hul', 'Ago', 'Set', 'Okt', 'Nob', 'Dis'], + 'weekdays' => ['Linggo', 'Lunes', 'Martes', 'Miyerkoles', 'Huwebes', 'Biyernes', 'Sabado'], + 'weekdays_short' => ['Lin', 'Lun', 'Mar', 'Miy', 'Huw', 'Biy', 'Sab'], + 'weekdays_min' => ['Lin', 'Lun', 'Mar', 'Miy', 'Huw', 'Biy', 'Sab'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['N.U.', 'N.H.'], + + 'before' => ':time bago', + 'after' => ':time pagkatapos', + + 'year' => ':count taon', + 'y' => ':count taon', + 'a_year' => ':count taon', + + 'month' => ':count buwan', + 'm' => ':count buwan', + 'a_month' => ':count buwan', + + 'week' => ':count linggo', + 'w' => ':count linggo', + 'a_week' => ':count linggo', + + 'day' => ':count araw', + 'd' => ':count araw', + 'a_day' => ':count araw', + + 'hour' => ':count oras', + 'h' => ':count oras', + 'a_hour' => ':count oras', + + 'minute' => ':count minuto', + 'min' => ':count minuto', + 'a_minute' => ':count minuto', + + 'second' => ':count segundo', + 's' => ':count segundo', + 'a_second' => ':count segundo', + + 'ago' => ':time ang nakalipas', + 'from_now' => 'sa :time', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fo.php b/vendor/nesbot/carbon/src/Carbon/Lang/fo.php new file mode 100644 index 0000000..6a14a6f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fo.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kristian Sakarisson + * - François B + * - JD Isaacks + * - Sverri Mohr Olsen + */ +return [ + 'year' => 'eitt ár|:count ár', + 'y' => ':count ár|:count ár', + 'month' => 'ein mánaði|:count mánaðir', + 'm' => ':count mánaður|:count mánaðir', + 'week' => ':count vika|:count vikur', + 'w' => ':count vika|:count vikur', + 'day' => 'ein dagur|:count dagar', + 'd' => ':count dag|:count dagar', + 'hour' => 'ein tími|:count tímar', + 'h' => ':count tími|:count tímar', + 'minute' => 'ein minutt|:count minuttir', + 'min' => ':count minutt|:count minuttir', + 'second' => 'fá sekund|:count sekundir', + 's' => ':count sekund|:count sekundir', + 'ago' => ':time síðani', + 'from_now' => 'um :time', + 'after' => ':time aftaná', + 'before' => ':time áðrenn', + 'diff_today' => 'Í', + 'diff_yesterday' => 'Í', + 'diff_yesterday_regexp' => 'Í(?:\\s+gjár)?(?:\\s+kl.)?', + 'diff_tomorrow' => 'Í', + 'diff_tomorrow_regexp' => 'Í(?:\\s+morgin)?(?:\\s+kl.)?', + 'diff_today_regexp' => 'Í(?:\\s+dag)?(?:\\s+kl.)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D. MMMM, YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Í dag kl.] LT', + 'nextDay' => '[Í morgin kl.] LT', + 'nextWeek' => 'dddd [kl.] LT', + 'lastDay' => '[Í gjár kl.] LT', + 'lastWeek' => '[síðstu] dddd [kl] LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['januar', 'februar', 'mars', 'apríl', 'mai', 'juni', 'juli', 'august', 'september', 'oktober', 'november', 'desember'], + 'months_short' => ['jan', 'feb', 'mar', 'apr', 'mai', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'des'], + 'weekdays' => ['sunnudagur', 'mánadagur', 'týsdagur', 'mikudagur', 'hósdagur', 'fríggjadagur', 'leygardagur'], + 'weekdays_short' => ['sun', 'mán', 'týs', 'mik', 'hós', 'frí', 'ley'], + 'weekdays_min' => ['su', 'má', 'tý', 'mi', 'hó', 'fr', 'le'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' og '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fo_DK.php b/vendor/nesbot/carbon/src/Carbon/Lang/fo_DK.php new file mode 100644 index 0000000..657f2c5 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fo_DK.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fo.php', [ + 'formats' => [ + 'L' => 'DD.MM.yy', + 'LL' => 'DD.MM.YYYY', + 'LLL' => 'D. MMMM YYYY, HH:mm', + 'LLLL' => 'dddd, D. MMMM YYYY, HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fo_FO.php b/vendor/nesbot/carbon/src/Carbon/Lang/fo_FO.php new file mode 100644 index 0000000..6d73616 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fo_FO.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fo.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr.php new file mode 100644 index 0000000..73fe5e4 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr.php @@ -0,0 +1,114 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Dieter Sting + * - François B + * - Maxime VALY + * - JD Isaacks + * - Dieter Sting + * - François B + * - JD Isaacks + * - Sebastian Thierer + * - Fastfuel + * - Pete Scopes (pdscopes) + */ +return [ + 'year' => ':count an|:count ans', + 'a_year' => 'un an|:count ans', + 'y' => ':count an|:count ans', + 'month' => ':count mois|:count mois', + 'a_month' => 'un mois|:count mois', + 'm' => ':count mois', + 'week' => ':count semaine|:count semaines', + 'a_week' => 'une semaine|:count semaines', + 'w' => ':count sem.', + 'day' => ':count jour|:count jours', + 'a_day' => 'un jour|:count jours', + 'd' => ':count j', + 'hour' => ':count heure|:count heures', + 'a_hour' => 'une heure|:count heures', + 'h' => ':count h', + 'minute' => ':count minute|:count minutes', + 'a_minute' => 'une minute|:count minutes', + 'min' => ':count min', + 'second' => ':count seconde|:count secondes', + 'a_second' => 'quelques secondes|:count secondes', + 's' => ':count s', + 'millisecond' => ':count milliseconde|:count millisecondes', + 'a_millisecond' => 'une milliseconde|:count millisecondes', + 'ms' => ':countms', + 'microsecond' => ':count microseconde|:count microsecondes', + 'a_microsecond' => 'une microseconde|:count microsecondes', + 'µs' => ':countµs', + 'ago' => 'il y a :time', + 'from_now' => 'dans :time', + 'after' => ':time après', + 'before' => ':time avant', + 'diff_now' => "à l'instant", + 'diff_today' => "aujourd'hui", + 'diff_today_regexp' => "aujourd'hui(?:\s+à)?", + 'diff_yesterday' => 'hier', + 'diff_yesterday_regexp' => 'hier(?:\s+à)?', + 'diff_tomorrow' => 'demain', + 'diff_tomorrow_regexp' => 'demain(?:\s+à)?', + 'diff_before_yesterday' => 'avant-hier', + 'diff_after_tomorrow' => 'après-demain', + 'period_recurrences' => ':count fois', + 'period_interval' => 'tous les :interval', + 'period_start_date' => 'de :date', + 'period_end_date' => 'à :date', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Aujourd’hui à] LT', + 'nextDay' => '[Demain à] LT', + 'nextWeek' => 'dddd [à] LT', + 'lastDay' => '[Hier à] LT', + 'lastWeek' => 'dddd [dernier à] LT', + 'sameElse' => 'L', + ], + 'months' => ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre', 'octobre', 'novembre', 'décembre'], + 'months_short' => ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin', 'juil.', 'août', 'sept.', 'oct.', 'nov.', 'déc.'], + 'weekdays' => ['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi'], + 'weekdays_short' => ['dim.', 'lun.', 'mar.', 'mer.', 'jeu.', 'ven.', 'sam.'], + 'weekdays_min' => ['di', 'lu', 'ma', 'me', 'je', 've', 'sa'], + 'ordinal' => function ($number, $period) { + switch ($period) { + // In french, only the first has be ordinal, other number remains cardinal + // @link https://fr.wikihow.com/%C3%A9crire-la-date-en-fran%C3%A7ais + case 'D': + return $number.($number === 1 ? 'er' : ''); + + default: + case 'M': + case 'Q': + case 'DDD': + case 'd': + return $number.($number === 1 ? 'er' : 'e'); + + // Words with feminine grammatical gender: semaine + case 'w': + case 'W': + return $number.($number === 1 ? 're' : 'e'); + } + }, + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' et '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_BE.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_BE.php new file mode 100644 index 0000000..f6cafe8 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_BE.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'months_short' => ['jan', 'fév', 'mar', 'avr', 'mai', 'jun', 'jui', 'aoû', 'sep', 'oct', 'nov', 'déc'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_BF.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_BF.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_BF.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_BI.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_BI.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_BI.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_BJ.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_BJ.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_BJ.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_BL.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_BL.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_BL.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_CA.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_CA.php new file mode 100644 index 0000000..c9f6346 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_CA.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Dieter Sting + * - François B + * - Maxime VALY + * - JD Isaacks + */ +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'formats' => [ + 'L' => 'YYYY-MM-DD', + ], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_CD.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_CD.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_CD.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_CF.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_CF.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_CF.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_CG.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_CG.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_CG.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_CH.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_CH.php new file mode 100644 index 0000000..8674c27 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_CH.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Dieter Sting + * - François B + * - Gaspard Bucher + * - Maxime VALY + * - JD Isaacks + */ +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_CI.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_CI.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_CI.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_CM.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_CM.php new file mode 100644 index 0000000..67d3787 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_CM.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'meridiem' => ['mat.', 'soir'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_DJ.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_DJ.php new file mode 100644 index 0000000..2f06086 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_DJ.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'first_day_of_week' => 6, + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd D MMMM YYYY h:mm a', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_DZ.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_DZ.php new file mode 100644 index 0000000..ae8db5f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_DZ.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'first_day_of_week' => 6, + 'weekend' => [5, 6], + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd D MMMM YYYY h:mm a', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_FR.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_FR.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_FR.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_GA.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_GA.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_GA.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_GF.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_GF.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_GF.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_GN.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_GN.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_GN.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_GP.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_GP.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_GP.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_GQ.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_GQ.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_GQ.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_HT.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_HT.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_HT.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_KM.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_KM.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_KM.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_LU.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_LU.php new file mode 100644 index 0000000..8e37d85 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_LU.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months_short' => ['jan', 'fév', 'mar', 'avr', 'mai', 'jun', 'jui', 'aoû', 'sep', 'oct', 'nov', 'déc'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_MA.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_MA.php new file mode 100644 index 0000000..1bf034d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_MA.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'first_day_of_week' => 6, + 'weekend' => [5, 6], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_MC.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_MC.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_MC.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_MF.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_MF.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_MF.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_MG.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_MG.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_MG.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_ML.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_ML.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_ML.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_MQ.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_MQ.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_MQ.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_MR.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_MR.php new file mode 100644 index 0000000..37cf83f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_MR.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd D MMMM YYYY h:mm a', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_MU.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_MU.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_MU.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_NC.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_NC.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_NC.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_NE.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_NE.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_NE.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_PF.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_PF.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_PF.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_PM.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_PM.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_PM.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_RE.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_RE.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_RE.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_RW.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_RW.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_RW.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_SC.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_SC.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_SC.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_SN.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_SN.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_SN.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_SY.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_SY.php new file mode 100644 index 0000000..ae8db5f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_SY.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'first_day_of_week' => 6, + 'weekend' => [5, 6], + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd D MMMM YYYY h:mm a', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_TD.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_TD.php new file mode 100644 index 0000000..37cf83f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_TD.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd D MMMM YYYY h:mm a', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_TG.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_TG.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_TG.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_TN.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_TN.php new file mode 100644 index 0000000..6905e7a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_TN.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'weekend' => [5, 6], + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd D MMMM YYYY h:mm a', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_VU.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_VU.php new file mode 100644 index 0000000..37cf83f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_VU.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fr.php', [ + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd D MMMM YYYY h:mm a', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_WF.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_WF.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_WF.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fr_YT.php b/vendor/nesbot/carbon/src/Carbon/Lang/fr_YT.php new file mode 100644 index 0000000..ec3ee35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fr_YT.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/fr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fur.php b/vendor/nesbot/carbon/src/Carbon/Lang/fur.php new file mode 100644 index 0000000..36c2564 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fur.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/fur_IT.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fur_IT.php b/vendor/nesbot/carbon/src/Carbon/Lang/fur_IT.php new file mode 100644 index 0000000..0147a59 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fur_IT.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Pablo Saratxaga pablo@mandrakesoft.com + */ +return [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD. MM. YY', + 'LL' => 'DD di MMMM dal YYYY', + 'LLL' => 'DD di MMM HH:mm', + 'LLLL' => 'DD di MMMM dal YYYY HH:mm', + ], + 'months' => ['zenâr', 'fevrâr', 'març', 'avrîl', 'mai', 'jugn', 'lui', 'avost', 'setembar', 'otubar', 'novembar', 'dicembar'], + 'months_short' => ['zen', 'fev', 'mar', 'avr', 'mai', 'jug', 'lui', 'avo', 'set', 'otu', 'nov', 'dic'], + 'weekdays' => ['domenie', 'lunis', 'martars', 'miercus', 'joibe', 'vinars', 'sabide'], + 'weekdays_short' => ['dom', 'lun', 'mar', 'mie', 'joi', 'vin', 'sab'], + 'weekdays_min' => ['dom', 'lun', 'mar', 'mie', 'joi', 'vin', 'sab'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'year' => ':count an', + 'month' => ':count mês', + 'week' => ':count setemane', + 'day' => ':count zornade', + 'hour' => ':count ore', + 'minute' => ':count minût', + 'second' => ':count secont', +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fy.php b/vendor/nesbot/carbon/src/Carbon/Lang/fy.php new file mode 100644 index 0000000..c1b5439 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fy.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Tim Fish + * - JD Isaacks + */ +return [ + 'year' => ':count jier|:count jierren', + 'a_year' => 'ien jier|:count jierren', + 'y' => ':count j', + 'month' => ':count moanne|:count moannen', + 'a_month' => 'ien moanne|:count moannen', + 'm' => ':count moa.', + 'week' => ':count wike|:count wiken', + 'a_week' => 'in wike|:count wiken', + 'a' => ':count w.', + 'day' => ':count dei|:count dagen', + 'a_day' => 'ien dei|:count dagen', + 'd' => ':count d.', + 'hour' => ':count oere|:count oeren', + 'a_hour' => 'ien oere|:count oeren', + 'h' => ':count o.', + 'minute' => ':count minút|:count minuten', + 'a_minute' => 'ien minút|:count minuten', + 'min' => ':count min.', + 'second' => ':count sekonde|:count sekonden', + 'a_second' => 'in pear sekonden|:count sekonden', + 's' => ':count s.', + 'ago' => ':time lyn', + 'from_now' => 'oer :time', + 'diff_yesterday' => 'juster', + 'diff_yesterday_regexp' => 'juster(?:\\s+om)?', + 'diff_today' => 'hjoed', + 'diff_today_regexp' => 'hjoed(?:\\s+om)?', + 'diff_tomorrow' => 'moarn', + 'diff_tomorrow_regexp' => 'moarn(?:\\s+om)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD-MM-YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[hjoed om] LT', + 'nextDay' => '[moarn om] LT', + 'nextWeek' => 'dddd [om] LT', + 'lastDay' => '[juster om] LT', + 'lastWeek' => '[ôfrûne] dddd [om] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + return $number.(($number === 1 || $number === 8 || $number >= 20) ? 'ste' : 'de'); + }, + 'months' => ['jannewaris', 'febrewaris', 'maart', 'april', 'maaie', 'juny', 'july', 'augustus', 'septimber', 'oktober', 'novimber', 'desimber'], + 'months_short' => ['jan', 'feb', 'mrt', 'apr', 'mai', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'des'], + 'mmm_suffix' => '.', + 'weekdays' => ['snein', 'moandei', 'tiisdei', 'woansdei', 'tongersdei', 'freed', 'sneon'], + 'weekdays_short' => ['si.', 'mo.', 'ti.', 'wo.', 'to.', 'fr.', 'so.'], + 'weekdays_min' => ['Si', 'Mo', 'Ti', 'Wo', 'To', 'Fr', 'So'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' en '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fy_DE.php b/vendor/nesbot/carbon/src/Carbon/Lang/fy_DE.php new file mode 100644 index 0000000..8559d5c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fy_DE.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - information from Kenneth Christiansen Kenneth Christiansen, Pablo Saratxaga kenneth@gnu.org, pablo@mandriva.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['Jaunuwoa', 'Februwoa', 'Moaz', 'Aprell', 'Mai', 'Juni', 'Juli', 'August', 'Septamba', 'Oktoba', 'Nowamba', 'Dezamba'], + 'months_short' => ['Jan', 'Feb', 'Moz', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Now', 'Dez'], + 'weekdays' => ['Sinndag', 'Mondag', 'Dingsdag', 'Meddwäakj', 'Donnadag', 'Friedag', 'Sinnowend'], + 'weekdays_short' => ['Sdg', 'Mdg', 'Dsg', 'Mwk', 'Ddg', 'Fdg', 'Swd'], + 'weekdays_min' => ['Sdg', 'Mdg', 'Dsg', 'Mwk', 'Ddg', 'Fdg', 'Swd'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/fy_NL.php b/vendor/nesbot/carbon/src/Carbon/Lang/fy_NL.php new file mode 100644 index 0000000..01cc96c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/fy_NL.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Free Software Foundation, Inc. bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/fy.php', [ + 'formats' => [ + 'L' => 'DD-MM-YY', + ], + 'months' => ['Jannewaris', 'Febrewaris', 'Maart', 'April', 'Maaie', 'Juny', 'July', 'Augustus', 'Septimber', 'Oktober', 'Novimber', 'Desimber'], + 'months_short' => ['Jan', 'Feb', 'Mrt', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'], + 'weekdays' => ['Snein', 'Moandei', 'Tiisdei', 'Woansdei', 'Tongersdei', 'Freed', 'Sneon'], + 'weekdays_short' => ['Sn', 'Mo', 'Ti', 'Wo', 'To', 'Fr', 'Sn'], + 'weekdays_min' => ['Sn', 'Mo', 'Ti', 'Wo', 'To', 'Fr', 'Sn'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ga.php b/vendor/nesbot/carbon/src/Carbon/Lang/ga.php new file mode 100644 index 0000000..9f07a26 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ga.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Thanks to André Silva : https://github.com/askpt + */ + +return [ + 'year' => ':count bliain', + 'a_year' => '{1}bliain|:count bliain', + 'y' => ':countb', + 'month' => ':count mí', + 'a_month' => '{1}mí|:count mí', + 'm' => ':countm', + 'week' => ':count sheachtain', + 'a_week' => '{1}sheachtain|:count sheachtain', + 'w' => ':countsh', + 'day' => ':count lá', + 'a_day' => '{1}lá|:count lá', + 'd' => ':countl', + 'hour' => ':count uair an chloig', + 'a_hour' => '{1}uair an chloig|:count uair an chloig', + 'h' => ':countu', + 'minute' => ':count nóiméad', + 'a_minute' => '{1}nóiméad|:count nóiméad', + 'min' => ':countn', + 'second' => ':count soicind', + 'a_second' => '{1}cúpla soicind|:count soicind', + 's' => ':countso', + 'ago' => ':time ó shin', + 'from_now' => 'i :time', + 'after' => ':time tar éis', + 'before' => ':time roimh', + 'diff_now' => 'anois', + 'diff_today' => 'Inniu', + 'diff_today_regexp' => 'Inniu(?:\\s+ag)?', + 'diff_yesterday' => 'inné', + 'diff_yesterday_regexp' => 'Inné(?:\\s+aig)?', + 'diff_tomorrow' => 'amárach', + 'diff_tomorrow_regexp' => 'Amárach(?:\\s+ag)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Inniu ag] LT', + 'nextDay' => '[Amárach ag] LT', + 'nextWeek' => 'dddd [ag] LT', + 'lastDay' => '[Inné aig] LT', + 'lastWeek' => 'dddd [seo caite] [ag] LT', + 'sameElse' => 'L', + ], + 'months' => ['Eanáir', 'Feabhra', 'Márta', 'Aibreán', 'Bealtaine', 'Méitheamh', 'Iúil', 'Lúnasa', 'Meán Fómhair', 'Deaireadh Fómhair', 'Samhain', 'Nollaig'], + 'months_short' => ['Eaná', 'Feab', 'Márt', 'Aibr', 'Beal', 'Méit', 'Iúil', 'Lúna', 'Meán', 'Deai', 'Samh', 'Noll'], + 'weekdays' => ['Dé Domhnaigh', 'Dé Luain', 'Dé Máirt', 'Dé Céadaoin', 'Déardaoin', 'Dé hAoine', 'Dé Satharn'], + 'weekdays_short' => ['Dom', 'Lua', 'Mái', 'Céa', 'Déa', 'hAo', 'Sat'], + 'weekdays_min' => ['Do', 'Lu', 'Má', 'Ce', 'Dé', 'hA', 'Sa'], + 'ordinal' => function ($number) { + return $number.($number === 1 ? 'd' : ($number % 10 === 2 ? 'na' : 'mh')); + }, + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' agus '], + 'meridiem' => ['r.n.', 'i.n.'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ga_IE.php b/vendor/nesbot/carbon/src/Carbon/Lang/ga_IE.php new file mode 100644 index 0000000..57b0c4f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ga_IE.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ga.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/gd.php b/vendor/nesbot/carbon/src/Carbon/Lang/gd.php new file mode 100644 index 0000000..63d064d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/gd.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Jon Ashdown + */ +return [ + 'year' => ':count bliadhna', + 'a_year' => '{1}bliadhna|:count bliadhna', + 'y' => ':count b.', + 'month' => ':count mìosan', + 'a_month' => '{1}mìos|:count mìosan', + 'm' => ':count ms.', + 'week' => ':count seachdainean', + 'a_week' => '{1}seachdain|:count seachdainean', + 'w' => ':count s.', + 'day' => ':count latha', + 'a_day' => '{1}latha|:count latha', + 'd' => ':count l.', + 'hour' => ':count uairean', + 'a_hour' => '{1}uair|:count uairean', + 'h' => ':count u.', + 'minute' => ':count mionaidean', + 'a_minute' => '{1}mionaid|:count mionaidean', + 'min' => ':count md.', + 'second' => ':count diogan', + 'a_second' => '{1}beagan diogan|:count diogan', + 's' => ':count d.', + 'ago' => 'bho chionn :time', + 'from_now' => 'ann an :time', + 'diff_yesterday' => 'An-dè', + 'diff_yesterday_regexp' => 'An-dè(?:\\s+aig)?', + 'diff_today' => 'An-diugh', + 'diff_today_regexp' => 'An-diugh(?:\\s+aig)?', + 'diff_tomorrow' => 'A-màireach', + 'diff_tomorrow_regexp' => 'A-màireach(?:\\s+aig)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[An-diugh aig] LT', + 'nextDay' => '[A-màireach aig] LT', + 'nextWeek' => 'dddd [aig] LT', + 'lastDay' => '[An-dè aig] LT', + 'lastWeek' => 'dddd [seo chaidh] [aig] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + return $number.($number === 1 ? 'd' : ($number % 10 === 2 ? 'na' : 'mh')); + }, + 'months' => ['Am Faoilleach', 'An Gearran', 'Am Màrt', 'An Giblean', 'An Cèitean', 'An t-Ògmhios', 'An t-Iuchar', 'An Lùnastal', 'An t-Sultain', 'An Dàmhair', 'An t-Samhain', 'An Dùbhlachd'], + 'months_short' => ['Faoi', 'Gear', 'Màrt', 'Gibl', 'Cèit', 'Ògmh', 'Iuch', 'Lùn', 'Sult', 'Dàmh', 'Samh', 'Dùbh'], + 'weekdays' => ['Didòmhnaich', 'Diluain', 'Dimàirt', 'Diciadain', 'Diardaoin', 'Dihaoine', 'Disathairne'], + 'weekdays_short' => ['Did', 'Dil', 'Dim', 'Dic', 'Dia', 'Dih', 'Dis'], + 'weekdays_min' => ['Dò', 'Lu', 'Mà', 'Ci', 'Ar', 'Ha', 'Sa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' agus '], + 'meridiem' => ['m', 'f'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/gd_GB.php b/vendor/nesbot/carbon/src/Carbon/Lang/gd_GB.php new file mode 100644 index 0000000..4fc26b3 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/gd_GB.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/gd.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/gez.php b/vendor/nesbot/carbon/src/Carbon/Lang/gez.php new file mode 100644 index 0000000..b8a2f0e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/gez.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/gez_ER.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/gez_ER.php b/vendor/nesbot/carbon/src/Carbon/Lang/gez_ER.php new file mode 100644 index 0000000..f19d1df --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/gez_ER.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['ጠሐረ', 'ከተተ', 'መገበ', 'አኀዘ', 'ግንባት', 'ሠንየ', 'ሐመለ', 'ነሐሰ', 'ከረመ', 'ጠቀመ', 'ኀደረ', 'ኀሠሠ'], + 'months_short' => ['ጠሐረ', 'ከተተ', 'መገበ', 'አኀዘ', 'ግንባ', 'ሠንየ', 'ሐመለ', 'ነሐሰ', 'ከረመ', 'ጠቀመ', 'ኀደረ', 'ኀሠሠ'], + 'weekdays' => ['እኁድ', 'ሰኑይ', 'ሠሉስ', 'ራብዕ', 'ሐሙስ', 'ዓርበ', 'ቀዳሚት'], + 'weekdays_short' => ['እኁድ', 'ሰኑይ', 'ሠሉስ', 'ራብዕ', 'ሐሙስ', 'ዓርበ', 'ቀዳሚ'], + 'weekdays_min' => ['እኁድ', 'ሰኑይ', 'ሠሉስ', 'ራብዕ', 'ሐሙስ', 'ዓርበ', 'ቀዳሚ'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ጽባሕ', 'ምሴት'], + + 'month' => ':count ወርሕ', // less reliable + 'm' => ':count ወርሕ', // less reliable + 'a_month' => ':count ወርሕ', // less reliable + + 'week' => ':count ሰብዑ', // less reliable + 'w' => ':count ሰብዑ', // less reliable + 'a_week' => ':count ሰብዑ', // less reliable + + 'hour' => ':count አንትሙ', // less reliable + 'h' => ':count አንትሙ', // less reliable + 'a_hour' => ':count አንትሙ', // less reliable + + 'minute' => ':count ንኡስ', // less reliable + 'min' => ':count ንኡስ', // less reliable + 'a_minute' => ':count ንኡስ', // less reliable + + 'year' => ':count ዓመት', + 'y' => ':count ዓመት', + 'a_year' => ':count ዓመት', + + 'day' => ':count ዕለት', + 'd' => ':count ዕለት', + 'a_day' => ':count ዕለት', + + 'second' => ':count ካልእ', + 's' => ':count ካልእ', + 'a_second' => ':count ካልእ', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/gez_ET.php b/vendor/nesbot/carbon/src/Carbon/Lang/gez_ET.php new file mode 100644 index 0000000..3933009 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/gez_ET.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['ጃንዩወሪ', 'ፌብሩወሪ', 'ማርች', 'ኤፕረል', 'ሜይ', 'ጁን', 'ጁላይ', 'ኦገስት', 'ሴፕቴምበር', 'ኦክተውበር', 'ኖቬምበር', 'ዲሴምበር'], + 'months_short' => ['ጃንዩ', 'ፌብሩ', 'ማርች', 'ኤፕረ', 'ሜይ ', 'ጁን ', 'ጁላይ', 'ኦገስ', 'ሴፕቴ', 'ኦክተ', 'ኖቬም', 'ዲሴም'], + 'weekdays' => ['እኁድ', 'ሰኑይ', 'ሠሉስ', 'ራብዕ', 'ሐሙስ', 'ዓርበ', 'ቀዳሚት'], + 'weekdays_short' => ['እኁድ', 'ሰኑይ', 'ሠሉስ', 'ራብዕ', 'ሐሙስ', 'ዓርበ', 'ቀዳሚ'], + 'weekdays_min' => ['እኁድ', 'ሰኑይ', 'ሠሉስ', 'ራብዕ', 'ሐሙስ', 'ዓርበ', 'ቀዳሚ'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ጽባሕ', 'ምሴት'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/gl.php b/vendor/nesbot/carbon/src/Carbon/Lang/gl.php new file mode 100644 index 0000000..088b0f2 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/gl.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Fidel Pita + * - JD Isaacks + * - Diego Vilariño + * - Sebastian Thierer + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count ano|:count anos', + 'a_year' => 'un ano|:count anos', + 'y' => ':count a.', + 'month' => ':count mes|:count meses', + 'a_month' => 'un mes|:count meses', + 'm' => ':count mes.', + 'week' => ':count semana|:count semanas', + 'a_week' => 'unha semana|:count semanas', + 'w' => ':count sem.', + 'day' => ':count día|:count días', + 'a_day' => 'un día|:count días', + 'd' => ':count d.', + 'hour' => ':count hora|:count horas', + 'a_hour' => 'unha hora|:count horas', + 'h' => ':count h.', + 'minute' => ':count minuto|:count minutos', + 'a_minute' => 'un minuto|:count minutos', + 'min' => ':count min.', + 'second' => ':count segundo|:count segundos', + 'a_second' => 'uns segundos|:count segundos', + 's' => ':count seg.', + 'ago' => 'hai :time', + 'from_now' => function ($time) { + if (str_starts_with($time, 'un')) { + return "n$time"; + } + + return "en $time"; + }, + 'diff_now' => 'agora', + 'diff_today' => 'hoxe', + 'diff_today_regexp' => 'hoxe(?:\\s+ás)?', + 'diff_yesterday' => 'onte', + 'diff_yesterday_regexp' => 'onte(?:\\s+á)?', + 'diff_tomorrow' => 'mañá', + 'diff_tomorrow_regexp' => 'mañá(?:\\s+ás)?', + 'after' => ':time despois', + 'before' => ':time antes', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D [de] MMMM [de] YYYY', + 'LLL' => 'D [de] MMMM [de] YYYY H:mm', + 'LLLL' => 'dddd, D [de] MMMM [de] YYYY H:mm', + ], + 'calendar' => [ + 'sameDay' => function (CarbonInterface $current) { + return '[hoxe '.($current->hour !== 1 ? 'ás' : 'á').'] LT'; + }, + 'nextDay' => function (CarbonInterface $current) { + return '[mañá '.($current->hour !== 1 ? 'ás' : 'á').'] LT'; + }, + 'nextWeek' => function (CarbonInterface $current) { + return 'dddd ['.($current->hour !== 1 ? 'ás' : 'á').'] LT'; + }, + 'lastDay' => function (CarbonInterface $current) { + return '[onte '.($current->hour !== 1 ? 'á' : 'a').'] LT'; + }, + 'lastWeek' => function (CarbonInterface $current) { + return '[o] dddd [pasado '.($current->hour !== 1 ? 'ás' : 'á').'] LT'; + }, + 'sameElse' => 'L', + ], + 'ordinal' => ':numberº', + 'months' => ['xaneiro', 'febreiro', 'marzo', 'abril', 'maio', 'xuño', 'xullo', 'agosto', 'setembro', 'outubro', 'novembro', 'decembro'], + 'months_short' => ['xan.', 'feb.', 'mar.', 'abr.', 'mai.', 'xuñ.', 'xul.', 'ago.', 'set.', 'out.', 'nov.', 'dec.'], + 'weekdays' => ['domingo', 'luns', 'martes', 'mércores', 'xoves', 'venres', 'sábado'], + 'weekdays_short' => ['dom.', 'lun.', 'mar.', 'mér.', 'xov.', 'ven.', 'sáb.'], + 'weekdays_min' => ['do', 'lu', 'ma', 'mé', 'xo', 've', 'sá'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' e '], + 'meridiem' => ['a.m.', 'p.m.'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/gl_ES.php b/vendor/nesbot/carbon/src/Carbon/Lang/gl_ES.php new file mode 100644 index 0000000..9d6c1d9 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/gl_ES.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/gl.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/gom.php b/vendor/nesbot/carbon/src/Carbon/Lang/gom.php new file mode 100644 index 0000000..2a0584f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/gom.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/gom_Latn.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/gom_Latn.php b/vendor/nesbot/carbon/src/Carbon/Lang/gom_Latn.php new file mode 100644 index 0000000..612bb88 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/gom_Latn.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'year' => ':count voros|:count vorsam', + 'y' => ':countv', + 'month' => ':count mhoino|:count mhoine', + 'm' => ':countmh', + 'week' => ':count satolleacho|:count satolleache', + 'w' => ':countsa|:countsa', + 'day' => ':count dis', + 'd' => ':countd', + 'hour' => ':count hor|:count horam', + 'h' => ':counth', + 'minute' => ':count minute|:count mintam', + 'min' => ':countm', + 'second' => ':count second', + 's' => ':counts', + + 'diff_today' => 'Aiz', + 'diff_yesterday' => 'Kal', + 'diff_tomorrow' => 'Faleam', + 'formats' => [ + 'LT' => 'A h:mm [vazta]', + 'LTS' => 'A h:mm:ss [vazta]', + 'L' => 'DD-MM-YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY A h:mm [vazta]', + 'LLLL' => 'dddd, MMMM[achea] Do, YYYY, A h:mm [vazta]', + 'llll' => 'ddd, D MMM YYYY, A h:mm [vazta]', + ], + + 'calendar' => [ + 'sameDay' => '[Aiz] LT', + 'nextDay' => '[Faleam] LT', + 'nextWeek' => '[Ieta to] dddd[,] LT', + 'lastDay' => '[Kal] LT', + 'lastWeek' => '[Fatlo] dddd[,] LT', + 'sameElse' => 'L', + ], + + 'months' => ['Janer', 'Febrer', 'Mars', 'Abril', 'Mai', 'Jun', 'Julai', 'Agost', 'Setembr', 'Otubr', 'Novembr', 'Dezembr'], + 'months_short' => ['Jan.', 'Feb.', 'Mars', 'Abr.', 'Mai', 'Jun', 'Jul.', 'Ago.', 'Set.', 'Otu.', 'Nov.', 'Dez.'], + 'weekdays' => ['Aitar', 'Somar', 'Mongllar', 'Budvar', 'Brestar', 'Sukrar', 'Son\'var'], + 'weekdays_short' => ['Ait.', 'Som.', 'Mon.', 'Bud.', 'Bre.', 'Suk.', 'Son.'], + 'weekdays_min' => ['Ai', 'Sm', 'Mo', 'Bu', 'Br', 'Su', 'Sn'], + + 'ordinal' => function ($number, $period) { + return $number.($period === 'D' ? 'er' : ''); + }, + + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'rati'; + } + if ($hour < 12) { + return 'sokalli'; + } + if ($hour < 16) { + return 'donparam'; + } + if ($hour < 20) { + return 'sanje'; + } + + return 'rati'; + }, + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' ani '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/gsw.php b/vendor/nesbot/carbon/src/Carbon/Lang/gsw.php new file mode 100644 index 0000000..c5c850e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/gsw.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Christopher Dell + * - Akira Matsuda + * - Enrique Vidal + * - Simone Carletti + * - Henning Kiel + * - Aaron Patterson + * - Florian Hanke + */ +return [ + 'year' => ':count Johr', + 'month' => ':count Monet', + 'week' => ':count Woche', + 'day' => ':count Tag', + 'hour' => ':count Schtund', + 'minute' => ':count Minute', + 'second' => ':count Sekunde', + 'weekdays' => ['Sunntig', 'Mäntig', 'Ziischtig', 'Mittwuch', 'Dunschtig', 'Friitig', 'Samschtig'], + 'weekdays_short' => ['Su', 'Mä', 'Zi', 'Mi', 'Du', 'Fr', 'Sa'], + 'weekdays_min' => ['Su', 'Mä', 'Zi', 'Mi', 'Du', 'Fr', 'Sa'], + 'months' => ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'Auguscht', 'September', 'Oktober', 'November', 'Dezember'], + 'months_short' => ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'], + 'meridiem' => ['am Vormittag', 'am Namittag'], + 'ordinal' => ':number.', + 'list' => [', ', ' und '], + 'diff_now' => 'now', + 'diff_yesterday' => 'geschter', + 'diff_tomorrow' => 'moorn', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'Do MMMM YYYY', + 'LLL' => 'Do MMMM, HH:mm [Uhr]', + 'LLLL' => 'dddd, Do MMMM YYYY, HH:mm [Uhr]', + ], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/gsw_CH.php b/vendor/nesbot/carbon/src/Carbon/Lang/gsw_CH.php new file mode 100644 index 0000000..594eb25 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/gsw_CH.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/gsw.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/gsw_FR.php b/vendor/nesbot/carbon/src/Carbon/Lang/gsw_FR.php new file mode 100644 index 0000000..3581dcf --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/gsw_FR.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/gsw.php', [ + 'meridiem' => ['vorm.', 'nam.'], + 'months' => ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'Auguscht', 'Septämber', 'Oktoober', 'Novämber', 'Dezämber'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LLL' => 'Do MMMM YYYY HH:mm', + 'LLLL' => 'dddd, Do MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/gsw_LI.php b/vendor/nesbot/carbon/src/Carbon/Lang/gsw_LI.php new file mode 100644 index 0000000..3581dcf --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/gsw_LI.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/gsw.php', [ + 'meridiem' => ['vorm.', 'nam.'], + 'months' => ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'Auguscht', 'Septämber', 'Oktoober', 'Novämber', 'Dezämber'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LLL' => 'Do MMMM YYYY HH:mm', + 'LLLL' => 'dddd, Do MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/gu.php b/vendor/nesbot/carbon/src/Carbon/Lang/gu.php new file mode 100644 index 0000000..8bc4311 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/gu.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - Kaushik Thanki + * - Josh Soref + */ +return [ + 'year' => 'એક વર્ષ|:count વર્ષ', + 'y' => ':countવર્ષ|:countવર્ષો', + 'month' => 'એક મહિનો|:count મહિના', + 'm' => ':countમહિનો|:countમહિના', + 'week' => ':count અઠવાડિયું|:count અઠવાડિયા', + 'w' => ':countઅઠ.|:countઅઠ.', + 'day' => 'એક દિવસ|:count દિવસ', + 'd' => ':countદિ.|:countદિ.', + 'hour' => 'એક કલાક|:count કલાક', + 'h' => ':countક.|:countક.', + 'minute' => 'એક મિનિટ|:count મિનિટ', + 'min' => ':countમિ.|:countમિ.', + 'second' => 'અમુક પળો|:count સેકંડ', + 's' => ':countસે.|:countસે.', + 'ago' => ':time પેહલા', + 'from_now' => ':time મા', + 'after' => ':time પછી', + 'before' => ':time પહેલા', + 'diff_now' => 'હમણાં', + 'diff_today' => 'આજ', + 'diff_yesterday' => 'ગઇકાલે', + 'diff_tomorrow' => 'કાલે', + 'formats' => [ + 'LT' => 'A h:mm વાગ્યે', + 'LTS' => 'A h:mm:ss વાગ્યે', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, A h:mm વાગ્યે', + 'LLLL' => 'dddd, D MMMM YYYY, A h:mm વાગ્યે', + ], + 'calendar' => [ + 'sameDay' => '[આજ] LT', + 'nextDay' => '[કાલે] LT', + 'nextWeek' => 'dddd, LT', + 'lastDay' => '[ગઇકાલે] LT', + 'lastWeek' => '[પાછલા] dddd, LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'રાત'; + } + if ($hour < 10) { + return 'સવાર'; + } + if ($hour < 17) { + return 'બપોર'; + } + if ($hour < 20) { + return 'સાંજ'; + } + + return 'રાત'; + }, + 'months' => ['જાન્યુઆરી', 'ફેબ્રુઆરી', 'માર્ચ', 'એપ્રિલ', 'મે', 'જૂન', 'જુલાઈ', 'ઑગસ્ટ', 'સપ્ટેમ્બર', 'ઑક્ટ્બર', 'નવેમ્બર', 'ડિસેમ્બર'], + 'months_short' => ['જાન્યુ.', 'ફેબ્રુ.', 'માર્ચ', 'એપ્રિ.', 'મે', 'જૂન', 'જુલા.', 'ઑગ.', 'સપ્ટે.', 'ઑક્ટ્.', 'નવે.', 'ડિસે.'], + 'weekdays' => ['રવિવાર', 'સોમવાર', 'મંગળવાર', 'બુધ્વાર', 'ગુરુવાર', 'શુક્રવાર', 'શનિવાર'], + 'weekdays_short' => ['રવિ', 'સોમ', 'મંગળ', 'બુધ્', 'ગુરુ', 'શુક્ર', 'શનિ'], + 'weekdays_min' => ['ર', 'સો', 'મં', 'બુ', 'ગુ', 'શુ', 'શ'], + 'list' => [', ', ' અને '], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'weekend' => [0, 0], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/gu_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/gu_IN.php new file mode 100644 index 0000000..02654b1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/gu_IN.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/gu.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/guz.php b/vendor/nesbot/carbon/src/Carbon/Lang/guz.php new file mode 100644 index 0000000..6230165 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/guz.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Ma', 'Mo'], + 'weekdays' => ['Chumapiri', 'Chumatato', 'Chumaine', 'Chumatano', 'Aramisi', 'Ichuma', 'Esabato'], + 'weekdays_short' => ['Cpr', 'Ctt', 'Cmn', 'Cmt', 'Ars', 'Icm', 'Est'], + 'weekdays_min' => ['Cpr', 'Ctt', 'Cmn', 'Cmt', 'Ars', 'Icm', 'Est'], + 'months' => ['Chanuari', 'Feburari', 'Machi', 'Apiriri', 'Mei', 'Juni', 'Chulai', 'Agosti', 'Septemba', 'Okitoba', 'Nobemba', 'Disemba'], + 'months_short' => ['Can', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Cul', 'Agt', 'Sep', 'Okt', 'Nob', 'Dis'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + + 'month' => ':count omotunyi', // less reliable + 'm' => ':count omotunyi', // less reliable + 'a_month' => ':count omotunyi', // less reliable + + 'week' => ':count isano naibere', // less reliable + 'w' => ':count isano naibere', // less reliable + 'a_week' => ':count isano naibere', // less reliable + + 'second' => ':count ibere', // less reliable + 's' => ':count ibere', // less reliable + 'a_second' => ':count ibere', // less reliable + + 'year' => ':count omwaka', + 'y' => ':count omwaka', + 'a_year' => ':count omwaka', + + 'day' => ':count rituko', + 'd' => ':count rituko', + 'a_day' => ':count rituko', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/gv.php b/vendor/nesbot/carbon/src/Carbon/Lang/gv.php new file mode 100644 index 0000000..7c52b94 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/gv.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/gv_GB.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/gv_GB.php b/vendor/nesbot/carbon/src/Carbon/Lang/gv_GB.php new file mode 100644 index 0000000..6b1168f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/gv_GB.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Alastair McKinstry bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['Jerrey-geuree', 'Toshiaght-arree', 'Mayrnt', 'Averil', 'Boaldyn', 'Mean-souree', 'Jerrey-souree', 'Luanistyn', 'Mean-fouyir', 'Jerrey-fouyir', 'Mee Houney', 'Mee ny Nollick'], + 'months_short' => ['J-guer', 'T-arree', 'Mayrnt', 'Avrril', 'Boaldyn', 'M-souree', 'J-souree', 'Luanistyn', 'M-fouyir', 'J-fouyir', 'M.Houney', 'M.Nollick'], + 'weekdays' => ['Jedoonee', 'Jelhein', 'Jemayrt', 'Jercean', 'Jerdein', 'Jeheiney', 'Jesarn'], + 'weekdays_short' => ['Jed', 'Jel', 'Jem', 'Jerc', 'Jerd', 'Jeh', 'Jes'], + 'weekdays_min' => ['Jed', 'Jel', 'Jem', 'Jerc', 'Jerd', 'Jeh', 'Jes'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'year' => ':count blein', + 'y' => ':count blein', + 'a_year' => ':count blein', + + 'month' => ':count mee', + 'm' => ':count mee', + 'a_month' => ':count mee', + + 'week' => ':count shiaghtin', + 'w' => ':count shiaghtin', + 'a_week' => ':count shiaghtin', + + 'day' => ':count laa', + 'd' => ':count laa', + 'a_day' => ':count laa', + + 'hour' => ':count oor', + 'h' => ':count oor', + 'a_hour' => ':count oor', + + 'minute' => ':count feer veg', + 'min' => ':count feer veg', + 'a_minute' => ':count feer veg', + + 'second' => ':count derrey', + 's' => ':count derrey', + 'a_second' => ':count derrey', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ha.php b/vendor/nesbot/carbon/src/Carbon/Lang/ha.php new file mode 100644 index 0000000..cd8e34d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ha.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - pablo@mandriva.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM, YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM, YYYY HH:mm', + ], + 'months' => ['Janairu', 'Faburairu', 'Maris', 'Afirilu', 'Mayu', 'Yuni', 'Yuli', 'Agusta', 'Satumba', 'Oktoba', 'Nuwamba', 'Disamba'], + 'months_short' => ['Jan', 'Fab', 'Mar', 'Afi', 'May', 'Yun', 'Yul', 'Agu', 'Sat', 'Okt', 'Nuw', 'Dis'], + 'weekdays' => ['Lahadi', 'Litini', 'Talata', 'Laraba', 'Alhamis', 'Jumaʼa', 'Asabar'], + 'weekdays_short' => ['Lah', 'Lit', 'Tal', 'Lar', 'Alh', 'Jum', 'Asa'], + 'weekdays_min' => ['Lh', 'Li', 'Ta', 'Lr', 'Al', 'Ju', 'As'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'year' => 'shekara :count', + 'y' => 'shekara :count', + 'a_year' => 'shekara :count', + + 'month' => ':count wátàa', + 'm' => ':count wátàa', + 'a_month' => ':count wátàa', + + 'week' => ':count mako', + 'w' => ':count mako', + 'a_week' => ':count mako', + + 'day' => ':count rana', + 'd' => ':count rana', + 'a_day' => ':count rana', + + 'hour' => ':count áwàa', + 'h' => ':count áwàa', + 'a_hour' => ':count áwàa', + + 'minute' => 'minti :count', + 'min' => 'minti :count', + 'a_minute' => 'minti :count', + + 'second' => ':count ná bíyú', + 's' => ':count ná bíyú', + 'a_second' => ':count ná bíyú', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ha_GH.php b/vendor/nesbot/carbon/src/Carbon/Lang/ha_GH.php new file mode 100644 index 0000000..f9f99a7 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ha_GH.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ha.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ha_NE.php b/vendor/nesbot/carbon/src/Carbon/Lang/ha_NE.php new file mode 100644 index 0000000..f9f99a7 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ha_NE.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ha.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ha_NG.php b/vendor/nesbot/carbon/src/Carbon/Lang/ha_NG.php new file mode 100644 index 0000000..f9f99a7 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ha_NG.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ha.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/hak.php b/vendor/nesbot/carbon/src/Carbon/Lang/hak.php new file mode 100644 index 0000000..6c3260e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/hak.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/hak_TW.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/hak_TW.php b/vendor/nesbot/carbon/src/Carbon/Lang/hak_TW.php new file mode 100644 index 0000000..fe23986 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/hak_TW.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYY年MM月DD日', + ], + 'months' => ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'], + 'months_short' => [' 1月', ' 2月', ' 3月', ' 4月', ' 5月', ' 6月', ' 7月', ' 8月', ' 9月', '10月', '11月', '12月'], + 'weekdays' => ['禮拜日', '禮拜一', '禮拜二', '禮拜三', '禮拜四', '禮拜五', '禮拜六'], + 'weekdays_short' => ['日', '一', '二', '三', '四', '五', '六'], + 'weekdays_min' => ['日', '一', '二', '三', '四', '五', '六'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['上晝', '下晝'], + + 'year' => ':count ngien11', + 'y' => ':count ngien11', + 'a_year' => ':count ngien11', + + 'month' => ':count ngie̍t', + 'm' => ':count ngie̍t', + 'a_month' => ':count ngie̍t', + + 'week' => ':count lî-pai', + 'w' => ':count lî-pai', + 'a_week' => ':count lî-pai', + + 'day' => ':count ngit', + 'd' => ':count ngit', + 'a_day' => ':count ngit', + + 'hour' => ':count sṳ̀', + 'h' => ':count sṳ̀', + 'a_hour' => ':count sṳ̀', + + 'minute' => ':count fûn', + 'min' => ':count fûn', + 'a_minute' => ':count fûn', + + 'second' => ':count miéu', + 's' => ':count miéu', + 'a_second' => ':count miéu', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/haw.php b/vendor/nesbot/carbon/src/Carbon/Lang/haw.php new file mode 100644 index 0000000..cdd3686 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/haw.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'months' => ['Ianuali', 'Pepeluali', 'Malaki', 'ʻApelila', 'Mei', 'Iune', 'Iulai', 'ʻAukake', 'Kepakemapa', 'ʻOkakopa', 'Nowemapa', 'Kekemapa'], + 'months_short' => ['Ian.', 'Pep.', 'Mal.', 'ʻAp.', 'Mei', 'Iun.', 'Iul.', 'ʻAu.', 'Kep.', 'ʻOk.', 'Now.', 'Kek.'], + 'weekdays' => ['Lāpule', 'Poʻakahi', 'Poʻalua', 'Poʻakolu', 'Poʻahā', 'Poʻalima', 'Poʻaono'], + 'weekdays_short' => ['LP', 'P1', 'P2', 'P3', 'P4', 'P5', 'P6'], + 'weekdays_min' => ['S', 'M', 'T', 'W', 'T', 'F', 'S'], + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd, D MMMM YYYY h:mm a', + ], + + 'year' => ':count makahiki', + 'y' => ':count makahiki', + 'a_year' => ':count makahiki', + + 'month' => ':count mahina', + 'm' => ':count mahina', + 'a_month' => ':count mahina', + + 'week' => ':count pule', + 'w' => ':count pule', + 'a_week' => ':count pule', + + 'day' => ':count lā', + 'd' => ':count lā', + 'a_day' => ':count lā', + + 'hour' => ':count hola', + 'h' => ':count hola', + 'a_hour' => ':count hola', + + 'minute' => ':count minuke', + 'min' => ':count minuke', + 'a_minute' => ':count minuke', + + 'second' => ':count lua', + 's' => ':count lua', + 'a_second' => ':count lua', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/he.php b/vendor/nesbot/carbon/src/Carbon/Lang/he.php new file mode 100644 index 0000000..c3fb3e9 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/he.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Daniel Cohen Gindi + * - JD Isaacks + * - Itai Nathaniel + * - GabMic + * - Yaakov Dahan (yakidahan) + */ +return [ + 'year' => 'שנה|{2}שנתיים|:count שנים', + 'y' => 'שנה|:count שנ׳', + 'month' => 'חודש|{2}חודשיים|:count חודשים', + 'm' => 'חודש|:count חו׳', + 'week' => 'שבוע|{2}שבועיים|:count שבועות', + 'w' => 'שבוע|:count שב׳', + 'day' => 'יום|{2}יומיים|:count ימים', + 'd' => 'יום|:count ימ׳', + 'hour' => 'שעה|{2}שעתיים|:count שעות', + 'h' => 'שעה|:count שע׳', + 'minute' => 'דקה|{2}שתי דקות|:count דקות', + 'min' => 'דקה|:count דק׳', + 'second' => 'שנייה|:count שניות', + 'a_second' => 'כמה שניות|:count שניות', + 's' => 'שניה|:count שנ׳', + 'ago' => 'לפני :time', + 'from_now' => 'בעוד :time מעכשיו', + 'after' => 'אחרי :time', + 'before' => 'לפני :time', + 'diff_now' => 'עכשיו', + 'diff_today' => 'היום', + 'diff_today_regexp' => 'היום(?:\\s+ב־)?', + 'diff_yesterday' => 'אתמול', + 'diff_yesterday_regexp' => 'אתמול(?:\\s+ב־)?', + 'diff_tomorrow' => 'מחר', + 'diff_tomorrow_regexp' => 'מחר(?:\\s+ב־)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D [ב]MMMM YYYY', + 'LLL' => 'D [ב]MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D [ב]MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[היום ב־]LT', + 'nextDay' => '[מחר ב־]LT', + 'nextWeek' => 'dddd [בשעה] LT', + 'lastDay' => '[אתמול ב־]LT', + 'lastWeek' => '[ביום] dddd [האחרון בשעה] LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour, $minute, $isLower) { + if ($hour < 5) { + return 'לפנות בוקר'; + } + if ($hour < 10) { + return 'בבוקר'; + } + if ($hour < 12) { + return $isLower ? 'לפנה"צ' : 'לפני הצהריים'; + } + if ($hour < 18) { + return $isLower ? 'אחה"צ' : 'אחרי הצהריים'; + } + + return 'בערב'; + }, + 'months' => ['ינואר', 'פברואר', 'מרץ', 'אפריל', 'מאי', 'יוני', 'יולי', 'אוגוסט', 'ספטמבר', 'אוקטובר', 'נובמבר', 'דצמבר'], + 'months_short' => ['ינו׳', 'פבר׳', 'מרץ', 'אפר׳', 'מאי', 'יוני', 'יולי', 'אוג׳', 'ספט׳', 'אוק׳', 'נוב׳', 'דצמ׳'], + 'weekdays' => ['ראשון', 'שני', 'שלישי', 'רביעי', 'חמישי', 'שישי', 'שבת'], + 'weekdays_short' => ['א׳', 'ב׳', 'ג׳', 'ד׳', 'ה׳', 'ו׳', 'ש׳'], + 'weekdays_min' => ['א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ש'], + 'list' => [', ', ' ו -'], + 'weekend' => [5, 6], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/he_IL.php b/vendor/nesbot/carbon/src/Carbon/Lang/he_IL.php new file mode 100644 index 0000000..14fab3e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/he_IL.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/he.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/hi.php b/vendor/nesbot/carbon/src/Carbon/Lang/hi.php new file mode 100644 index 0000000..70c57a2 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/hi.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - abhimanyu003 + * - Josh Soref + * - JD Isaacks + */ +return [ + 'year' => 'एक वर्ष|:count वर्ष', + 'y' => '1 वर्ष|:count वर्षों', + 'month' => 'एक महीने|:count महीने', + 'm' => '1 माह|:count महीने', + 'week' => '1 सप्ताह|:count सप्ताह', + 'w' => '1 सप्ताह|:count सप्ताह', + 'day' => 'एक दिन|:count दिन', + 'd' => '1 दिन|:count दिनों', + 'hour' => 'एक घंटा|:count घंटे', + 'h' => '1 घंटा|:count घंटे', + 'minute' => 'एक मिनट|:count मिनट', + 'min' => '1 मिनट|:count मिनटों', + 'second' => 'कुछ ही क्षण|:count सेकंड', + 's' => '1 सेकंड|:count सेकंड', + 'ago' => ':time पहले', + 'from_now' => ':time में', + 'after' => ':time के बाद', + 'before' => ':time के पहले', + 'diff_now' => 'अब', + 'diff_today' => 'आज', + 'diff_yesterday' => 'कल', + 'diff_tomorrow' => 'कल', + 'formats' => [ + 'LT' => 'A h:mm बजे', + 'LTS' => 'A h:mm:ss बजे', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, A h:mm बजे', + 'LLLL' => 'dddd, D MMMM YYYY, A h:mm बजे', + ], + 'calendar' => [ + 'sameDay' => '[आज] LT', + 'nextDay' => '[कल] LT', + 'nextWeek' => 'dddd, LT', + 'lastDay' => '[कल] LT', + 'lastWeek' => '[पिछले] dddd, LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'रात'; + } + if ($hour < 10) { + return 'सुबह'; + } + if ($hour < 17) { + return 'दोपहर'; + } + if ($hour < 20) { + return 'शाम'; + } + + return 'रात'; + }, + 'months' => ['जनवरी', 'फ़रवरी', 'मार्च', 'अप्रैल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितम्बर', 'अक्टूबर', 'नवम्बर', 'दिसम्बर'], + 'months_short' => ['जन.', 'फ़र.', 'मार्च', 'अप्रै.', 'मई', 'जून', 'जुल.', 'अग.', 'सित.', 'अक्टू.', 'नव.', 'दिस.'], + 'weekdays' => ['रविवार', 'सोमवार', 'मंगलवार', 'बुधवार', 'गुरूवार', 'शुक्रवार', 'शनिवार'], + 'weekdays_short' => ['रवि', 'सोम', 'मंगल', 'बुध', 'गुरू', 'शुक्र', 'शनि'], + 'weekdays_min' => ['र', 'सो', 'मं', 'बु', 'गु', 'शु', 'श'], + 'list' => [', ', ' और '], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'weekend' => [0, 0], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/hi_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/hi_IN.php new file mode 100644 index 0000000..749dd97 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/hi_IN.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/hi.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/hif.php b/vendor/nesbot/carbon/src/Carbon/Lang/hif.php new file mode 100644 index 0000000..65791dd --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/hif.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/hif_FJ.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/hif_FJ.php b/vendor/nesbot/carbon/src/Carbon/Lang/hif_FJ.php new file mode 100644 index 0000000..30ad5e7 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/hif_FJ.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Samsung Electronics Co., Ltd. akhilesh.k@samsung.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'dddd DD MMM YYYY', + ], + 'months' => ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + 'weekdays' => ['Ravivar', 'Somvar', 'Mangalvar', 'Budhvar', 'Guruvar', 'Shukravar', 'Shanivar'], + 'weekdays_short' => ['Ravi', 'Som', 'Mangal', 'Budh', 'Guru', 'Shukra', 'Shani'], + 'weekdays_min' => ['Ravi', 'Som', 'Mangal', 'Budh', 'Guru', 'Shukra', 'Shani'], + 'meridiem' => ['Purvahan', 'Aparaahna'], + + 'hour' => ':count minit', // less reliable + 'h' => ':count minit', // less reliable + 'a_hour' => ':count minit', // less reliable + + 'year' => ':count saal', + 'y' => ':count saal', + 'a_year' => ':count saal', + + 'month' => ':count Mahina', + 'm' => ':count Mahina', + 'a_month' => ':count Mahina', + + 'week' => ':count Hafta', + 'w' => ':count Hafta', + 'a_week' => ':count Hafta', + + 'day' => ':count Din', + 'd' => ':count Din', + 'a_day' => ':count Din', + + 'minute' => ':count Minit', + 'min' => ':count Minit', + 'a_minute' => ':count Minit', + + 'second' => ':count Second', + 's' => ':count Second', + 'a_second' => ':count Second', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/hne.php b/vendor/nesbot/carbon/src/Carbon/Lang/hne.php new file mode 100644 index 0000000..4bcb05c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/hne.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/hne_IN.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/hne_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/hne_IN.php new file mode 100644 index 0000000..a5ca758 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/hne_IN.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Red Hat, Pune bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['जनवरी', 'फरवरी', 'मार्च', 'अपरेल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितमबर', 'अकटूबर', 'नवमबर', 'दिसमबर'], + 'months_short' => ['जन', 'फर', 'मार्च', 'अप', 'मई', 'जून', 'जुला', 'अग', 'सित', 'अकटू', 'नव', 'दिस'], + 'weekdays' => ['इतवार', 'सोमवार', 'मंगलवार', 'बुधवार', 'बिरसपत', 'सुकरवार', 'सनिवार'], + 'weekdays_short' => ['इत', 'सोम', 'मंग', 'बुध', 'बिर', 'सुक', 'सनि'], + 'weekdays_min' => ['इत', 'सोम', 'मंग', 'बुध', 'बिर', 'सुक', 'सनि'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['बिहिनियाँ', 'मंझनियाँ'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/hr.php b/vendor/nesbot/carbon/src/Carbon/Lang/hr.php new file mode 100644 index 0000000..cfd85fd --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/hr.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - François B + * - Tim Fish + * - shaishavgandhi05 + * - Serhan Apaydın + * - JD Isaacks + * - tomhorvat + * - Josh Soref + * - François B + * - shaishavgandhi05 + * - Serhan Apaydın + * - JD Isaacks + * - tomhorvat + * - Stjepan Majdak + * - Vanja Retkovac (vr00) + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count godinu|:count godine|:count godina', + 'y' => ':count god.|:count god.|:count god.', + 'month' => ':count mjesec|:count mjeseca|:count mjeseci', + 'm' => ':count mj.|:count mj.|:count mj.', + 'week' => ':count tjedan|:count tjedna|:count tjedana', + 'w' => ':count tj.|:count tj.|:count tj.', + 'day' => ':count dan|:count dana|:count dana', + 'd' => ':count d.|:count d.|:count d.', + 'hour' => ':count sat|:count sata|:count sati', + 'h' => ':count sat|:count sata|:count sati', + 'minute' => ':count minutu|:count minute|:count minuta', + 'min' => ':count min.|:count min.|:count min.', + 'second' => ':count sekundu|:count sekunde|:count sekundi', + 'a_second' => 'nekoliko sekundi|:count sekunde|:count sekundi', + 's' => ':count sek.|:count sek.|:count sek.', + 'ago' => 'prije :time', + 'from_now' => 'za :time', + 'after' => ':time poslije', + 'before' => ':time prije', + 'diff_now' => 'sad', + 'diff_today' => 'danas', + 'diff_today_regexp' => 'danas(?:\\s+u)?', + 'diff_yesterday' => 'jučer', + 'diff_yesterday_regexp' => 'jučer(?:\\s+u)?', + 'diff_tomorrow' => 'sutra', + 'diff_tomorrow_regexp' => 'sutra(?:\\s+u)?', + 'diff_before_yesterday' => 'prekjučer', + 'diff_after_tomorrow' => 'prekosutra', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'D. M. YYYY.', + 'LL' => 'D. MMMM YYYY.', + 'LLL' => 'D. MMMM YYYY. H:mm', + 'LLLL' => 'dddd, D. MMMM YYYY. H:mm', + ], + 'calendar' => [ + 'sameDay' => '[danas u] LT', + 'nextDay' => '[sutra u] LT', + 'nextWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[u] [nedjelju] [u] LT'; + case 3: + return '[u] [srijedu] [u] LT'; + case 6: + return '[u] [subotu] [u] LT'; + default: + return '[u] dddd [u] LT'; + } + }, + 'lastDay' => '[jučer u] LT', + 'lastWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + case 3: + return '[prošlu] dddd [u] LT'; + case 6: + return '[prošle] [subote] [u] LT'; + default: + return '[prošli] dddd [u] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['siječnja', 'veljače', 'ožujka', 'travnja', 'svibnja', 'lipnja', 'srpnja', 'kolovoza', 'rujna', 'listopada', 'studenoga', 'prosinca'], + 'months_standalone' => ['siječanj', 'veljača', 'ožujak', 'travanj', 'svibanj', 'lipanj', 'srpanj', 'kolovoz', 'rujan', 'listopad', 'studeni', 'prosinac'], + 'months_short' => ['sij.', 'velj.', 'ožu.', 'tra.', 'svi.', 'lip.', 'srp.', 'kol.', 'ruj.', 'lis.', 'stu.', 'pro.'], + 'months_regexp' => '/(D[oD]?(\[[^\[\]]*\]|\s)+MMMM?|L{2,4}|l{2,4})/', + 'weekdays' => ['nedjelju', 'ponedjeljak', 'utorak', 'srijedu', 'četvrtak', 'petak', 'subotu'], + 'weekdays_standalone' => ['nedjelja', 'ponedjeljak', 'utorak', 'srijeda', 'četvrtak', 'petak', 'subota'], + 'weekdays_short' => ['ned.', 'pon.', 'uto.', 'sri.', 'čet.', 'pet.', 'sub.'], + 'weekdays_min' => ['ne', 'po', 'ut', 'sr', 'če', 'pe', 'su'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' i '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/hr_BA.php b/vendor/nesbot/carbon/src/Carbon/Lang/hr_BA.php new file mode 100644 index 0000000..7763a45 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/hr_BA.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - DarkoDevelop + */ +return array_replace_recursive(require __DIR__.'/hr.php', [ + 'weekdays' => ['nedjelja', 'ponedjeljak', 'utorak', 'srijeda', 'četvrtak', 'petak', 'subota'], + 'weekdays_short' => ['ned', 'pon', 'uto', 'sri', 'čet', 'pet', 'sub'], + 'weekdays_min' => ['ned', 'pon', 'uto', 'sri', 'čet', 'pet', 'sub'], + 'months' => ['siječnja', 'veljače', 'ožujka', 'travnja', 'svibnja', 'lipnja', 'srpnja', 'kolovoza', 'rujna', 'listopada', 'studenoga', 'prosinca'], + 'months_short' => ['sij', 'velj', 'ožu', 'tra', 'svi', 'lip', 'srp', 'kol', 'ruj', 'lis', 'stu', 'pro'], + 'months_standalone' => ['siječanj', 'veljača', 'ožujak', 'travanj', 'svibanj', 'lipanj', 'srpanj', 'kolovoz', 'rujan', 'listopad', 'studeni', 'prosinac'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D. M. yy.', + 'LL' => 'D. MMM YYYY.', + 'LLL' => 'D. MMMM YYYY. HH:mm', + 'LLLL' => 'dddd, D. MMMM YYYY. HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/hr_HR.php b/vendor/nesbot/carbon/src/Carbon/Lang/hr_HR.php new file mode 100644 index 0000000..db74d8c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/hr_HR.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/hr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/hsb.php b/vendor/nesbot/carbon/src/Carbon/Lang/hsb.php new file mode 100644 index 0000000..3537b8b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/hsb.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/hsb_DE.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/hsb_DE.php b/vendor/nesbot/carbon/src/Carbon/Lang/hsb_DE.php new file mode 100644 index 0000000..6ba2271 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/hsb_DE.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Information from Michael Wolf Andrzej Krzysztofowicz ankry@mif.pg.gda.pl + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'DD. MMMM YYYY', + 'LLL' => 'DD. MMMM, HH:mm [hodź.]', + 'LLLL' => 'dddd, DD. MMMM YYYY, HH:mm [hodź.]', + ], + 'months' => ['januara', 'februara', 'měrca', 'apryla', 'meje', 'junija', 'julija', 'awgusta', 'septembra', 'oktobra', 'nowembra', 'decembra'], + 'months_short' => ['Jan', 'Feb', 'Měr', 'Apr', 'Mej', 'Jun', 'Jul', 'Awg', 'Sep', 'Okt', 'Now', 'Dec'], + 'weekdays' => ['Njedźela', 'Póndźela', 'Wutora', 'Srjeda', 'Štvórtk', 'Pjatk', 'Sobota'], + 'weekdays_short' => ['Nj', 'Pó', 'Wu', 'Sr', 'Št', 'Pj', 'So'], + 'weekdays_min' => ['Nj', 'Pó', 'Wu', 'Sr', 'Št', 'Pj', 'So'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'year' => ':count lěto', + 'y' => ':count lěto', + 'a_year' => ':count lěto', + + 'month' => ':count měsac', + 'm' => ':count měsac', + 'a_month' => ':count měsac', + + 'week' => ':count tydźeń', + 'w' => ':count tydźeń', + 'a_week' => ':count tydźeń', + + 'day' => ':count dźeń', + 'd' => ':count dźeń', + 'a_day' => ':count dźeń', + + 'hour' => ':count hodźina', + 'h' => ':count hodźina', + 'a_hour' => ':count hodźina', + + 'minute' => ':count chwila', + 'min' => ':count chwila', + 'a_minute' => ':count chwila', + + 'second' => ':count druhi', + 's' => ':count druhi', + 'a_second' => ':count druhi', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ht.php b/vendor/nesbot/carbon/src/Carbon/Lang/ht.php new file mode 100644 index 0000000..ebd12ad --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ht.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ht_HT.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ht_HT.php b/vendor/nesbot/carbon/src/Carbon/Lang/ht_HT.php new file mode 100644 index 0000000..139b813 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ht_HT.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Sugar Labs // OLPC sugarlabs.org libc-alpha@sourceware.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['janvye', 'fevriye', 'mas', 'avril', 'me', 'jen', 'jiyè', 'out', 'septanm', 'oktòb', 'novanm', 'desanm'], + 'months_short' => ['jan', 'fev', 'mas', 'avr', 'me', 'jen', 'jiy', 'out', 'sep', 'okt', 'nov', 'des'], + 'weekdays' => ['dimanch', 'lendi', 'madi', 'mèkredi', 'jedi', 'vandredi', 'samdi'], + 'weekdays_short' => ['dim', 'len', 'mad', 'mèk', 'jed', 'van', 'sam'], + 'weekdays_min' => ['dim', 'len', 'mad', 'mèk', 'jed', 'van', 'sam'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'year' => ':count lane', + 'y' => ':count lane', + 'a_year' => ':count lane', + + 'month' => 'mwa :count', + 'm' => 'mwa :count', + 'a_month' => 'mwa :count', + + 'week' => 'semèn :count', + 'w' => 'semèn :count', + 'a_week' => 'semèn :count', + + 'day' => ':count jou', + 'd' => ':count jou', + 'a_day' => ':count jou', + + 'hour' => ':count lè', + 'h' => ':count lè', + 'a_hour' => ':count lè', + + 'minute' => ':count minit', + 'min' => ':count minit', + 'a_minute' => ':count minit', + + 'second' => ':count segonn', + 's' => ':count segonn', + 'a_second' => ':count segonn', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/hu.php b/vendor/nesbot/carbon/src/Carbon/Lang/hu.php new file mode 100644 index 0000000..b2d2ac1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/hu.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Adam Brunner + * - Brett Johnson + * - balping + */ + +use Carbon\CarbonInterface; + +$huWeekEndings = ['vasárnap', 'hétfőn', 'kedden', 'szerdán', 'csütörtökön', 'pénteken', 'szombaton']; + +return [ + 'year' => ':count év', + 'y' => ':count év', + 'month' => ':count hónap', + 'm' => ':count hónap', + 'week' => ':count hét', + 'w' => ':count hét', + 'day' => ':count nap', + 'd' => ':count nap', + 'hour' => ':count óra', + 'h' => ':count óra', + 'minute' => ':count perc', + 'min' => ':count perc', + 'second' => ':count másodperc', + 's' => ':count másodperc', + 'ago' => ':time', + 'from_now' => ':time múlva', + 'after' => ':time később', + 'before' => ':time korábban', + 'year_ago' => ':count éve', + 'y_ago' => ':count éve', + 'month_ago' => ':count hónapja', + 'm_ago' => ':count hónapja', + 'week_ago' => ':count hete', + 'w_ago' => ':count hete', + 'day_ago' => ':count napja', + 'd_ago' => ':count napja', + 'hour_ago' => ':count órája', + 'h_ago' => ':count órája', + 'minute_ago' => ':count perce', + 'min_ago' => ':count perce', + 'second_ago' => ':count másodperce', + 's_ago' => ':count másodperce', + 'year_after' => ':count évvel', + 'y_after' => ':count évvel', + 'month_after' => ':count hónappal', + 'm_after' => ':count hónappal', + 'week_after' => ':count héttel', + 'w_after' => ':count héttel', + 'day_after' => ':count nappal', + 'd_after' => ':count nappal', + 'hour_after' => ':count órával', + 'h_after' => ':count órával', + 'minute_after' => ':count perccel', + 'min_after' => ':count perccel', + 'second_after' => ':count másodperccel', + 's_after' => ':count másodperccel', + 'year_before' => ':count évvel', + 'y_before' => ':count évvel', + 'month_before' => ':count hónappal', + 'm_before' => ':count hónappal', + 'week_before' => ':count héttel', + 'w_before' => ':count héttel', + 'day_before' => ':count nappal', + 'd_before' => ':count nappal', + 'hour_before' => ':count órával', + 'h_before' => ':count órával', + 'minute_before' => ':count perccel', + 'min_before' => ':count perccel', + 'second_before' => ':count másodperccel', + 's_before' => ':count másodperccel', + 'months' => ['január', 'február', 'március', 'április', 'május', 'június', 'július', 'augusztus', 'szeptember', 'október', 'november', 'december'], + 'months_short' => ['jan.', 'feb.', 'márc.', 'ápr.', 'máj.', 'jún.', 'júl.', 'aug.', 'szept.', 'okt.', 'nov.', 'dec.'], + 'weekdays' => ['vasárnap', 'hétfő', 'kedd', 'szerda', 'csütörtök', 'péntek', 'szombat'], + 'weekdays_short' => ['vas', 'hét', 'kedd', 'sze', 'csüt', 'pén', 'szo'], + 'weekdays_min' => ['v', 'h', 'k', 'sze', 'cs', 'p', 'sz'], + 'ordinal' => ':number.', + 'diff_now' => 'most', + 'diff_today' => 'ma', + 'diff_yesterday' => 'tegnap', + 'diff_tomorrow' => 'holnap', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'YYYY.MM.DD.', + 'LL' => 'YYYY. MMMM D.', + 'LLL' => 'YYYY. MMMM D. H:mm', + 'LLLL' => 'YYYY. MMMM D., dddd H:mm', + ], + 'calendar' => [ + 'sameDay' => '[ma] LT[-kor]', + 'nextDay' => '[holnap] LT[-kor]', + 'nextWeek' => function (CarbonInterface $date) use ($huWeekEndings) { + return '['.$huWeekEndings[$date->dayOfWeek].'] LT[-kor]'; + }, + 'lastDay' => '[tegnap] LT[-kor]', + 'lastWeek' => function (CarbonInterface $date) use ($huWeekEndings) { + return '[múlt '.$huWeekEndings[$date->dayOfWeek].'] LT[-kor]'; + }, + 'sameElse' => 'L', + ], + 'meridiem' => ['DE', 'DU'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' és '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/hu_HU.php b/vendor/nesbot/carbon/src/Carbon/Lang/hu_HU.php new file mode 100644 index 0000000..b1c4854 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/hu_HU.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/hu.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/hy.php b/vendor/nesbot/carbon/src/Carbon/Lang/hy.php new file mode 100644 index 0000000..8b12994 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/hy.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - mhamlet + */ +return [ + 'year' => ':count տարի', + 'a_year' => 'տարի|:count տարի', + 'y' => ':countտ', + 'month' => ':count ամիս', + 'a_month' => 'ամիս|:count ամիս', + 'm' => ':countամ', + 'week' => ':count շաբաթ', + 'a_week' => 'շաբաթ|:count շաբաթ', + 'w' => ':countշ', + 'day' => ':count օր', + 'a_day' => 'օր|:count օր', + 'd' => ':countօր', + 'hour' => ':count ժամ', + 'a_hour' => 'ժամ|:count ժամ', + 'h' => ':countժ', + 'minute' => ':count րոպե', + 'a_minute' => 'րոպե|:count րոպե', + 'min' => ':countր', + 'second' => ':count վայրկյան', + 'a_second' => 'մի քանի վայրկյան|:count վայրկյան', + 's' => ':countվրկ', + 'ago' => ':time առաջ', + 'from_now' => ':timeից', + 'after' => ':time հետո', + 'before' => ':time առաջ', + 'diff_now' => 'հիմա', + 'diff_today' => 'այսօր', + 'diff_yesterday' => 'երեկ', + 'diff_tomorrow' => 'վաղը', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY թ.', + 'LLL' => 'D MMMM YYYY թ., HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY թ., HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[այսօր] LT', + 'nextDay' => '[վաղը] LT', + 'nextWeek' => 'dddd [օրը ժամը] LT', + 'lastDay' => '[երեկ] LT', + 'lastWeek' => '[անցած] dddd [օրը ժամը] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + switch ($period) { + case 'DDD': + case 'w': + case 'W': + case 'DDDo': + return $number.($number === 1 ? '-ին' : '-րդ'); + default: + return $number; + } + }, + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'գիշերվա'; + } + if ($hour < 12) { + return 'առավոտվա'; + } + if ($hour < 17) { + return 'ցերեկվա'; + } + + return 'երեկոյան'; + }, + 'months' => ['հունվարի', 'փետրվարի', 'մարտի', 'ապրիլի', 'մայիսի', 'հունիսի', 'հուլիսի', 'օգոստոսի', 'սեպտեմբերի', 'հոկտեմբերի', 'նոյեմբերի', 'դեկտեմբերի'], + 'months_standalone' => ['հունվար', 'փետրվար', 'մարտ', 'ապրիլ', 'մայիս', 'հունիս', 'հուլիս', 'օգոստոս', 'սեպտեմբեր', 'հոկտեմբեր', 'նոյեմբեր', 'դեկտեմբեր'], + 'months_short' => ['հնվ', 'փտր', 'մրտ', 'ապր', 'մյս', 'հնս', 'հլս', 'օգս', 'սպտ', 'հկտ', 'նմբ', 'դկտ'], + 'months_regexp' => '/(D[oD]?(\[[^\[\]]*\]|\s)+MMMM?|L{2,4}|l{2,4})/', + 'weekdays' => ['կիրակի', 'երկուշաբթի', 'երեքշաբթի', 'չորեքշաբթի', 'հինգշաբթի', 'ուրբաթ', 'շաբաթ'], + 'weekdays_short' => ['կրկ', 'երկ', 'երք', 'չրք', 'հնգ', 'ուրբ', 'շբթ'], + 'weekdays_min' => ['կրկ', 'երկ', 'երք', 'չրք', 'հնգ', 'ուրբ', 'շբթ'], + 'list' => [', ', ' եւ '], + 'first_day_of_week' => 1, +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/hy_AM.php b/vendor/nesbot/carbon/src/Carbon/Lang/hy_AM.php new file mode 100644 index 0000000..4587df5 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/hy_AM.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - François B + * - Tim Fish + * - Serhan Apaydın + * - JD Isaacks + */ +return array_replace_recursive(require __DIR__.'/hy.php', [ + 'from_now' => ':time հետո', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/i18n.php b/vendor/nesbot/carbon/src/Carbon/Lang/i18n.php new file mode 100644 index 0000000..e65449b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/i18n.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYY-MM-DD', + ], + 'months' => ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12'], + 'months_short' => ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12'], + 'weekdays' => ['1', '2', '3', '4', '5', '6', '7'], + 'weekdays_short' => ['1', '2', '3', '4', '5', '6', '7'], + 'weekdays_min' => ['1', '2', '3', '4', '5', '6', '7'], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 4, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ia.php b/vendor/nesbot/carbon/src/Carbon/Lang/ia.php new file mode 100644 index 0000000..0a0d5e6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ia.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ia_FR.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ia_FR.php b/vendor/nesbot/carbon/src/Carbon/Lang/ia_FR.php new file mode 100644 index 0000000..de4b2fa --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ia_FR.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Fedora Project Nik Kalach nikka@fedoraproject.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['januario', 'februario', 'martio', 'april', 'maio', 'junio', 'julio', 'augusto', 'septembre', 'octobre', 'novembre', 'decembre'], + 'months_short' => ['jan', 'feb', 'mar', 'apr', 'mai', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'], + 'weekdays' => ['dominica', 'lunedi', 'martedi', 'mercuridi', 'jovedi', 'venerdi', 'sabbato'], + 'weekdays_short' => ['dom', 'lun', 'mar', 'mer', 'jov', 'ven', 'sab'], + 'weekdays_min' => ['dom', 'lun', 'mar', 'mer', 'jov', 'ven', 'sab'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'year' => 'anno :count', + 'y' => 'anno :count', + 'a_year' => 'anno :count', + + 'month' => ':count mense', + 'm' => ':count mense', + 'a_month' => ':count mense', + + 'week' => ':count septimana', + 'w' => ':count septimana', + 'a_week' => ':count septimana', + + 'day' => ':count die', + 'd' => ':count die', + 'a_day' => ':count die', + + 'hour' => ':count hora', + 'h' => ':count hora', + 'a_hour' => ':count hora', + + 'minute' => ':count minuscule', + 'min' => ':count minuscule', + 'a_minute' => ':count minuscule', + + 'second' => ':count secunda', + 's' => ':count secunda', + 'a_second' => ':count secunda', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/id.php b/vendor/nesbot/carbon/src/Carbon/Lang/id.php new file mode 100644 index 0000000..afaf78f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/id.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - du + * - JD Isaacks + * - Nafies Luthfi + * - Raymundus Jati Primanda (mundusjp) + * - diankur313 + * - a-wip0 + */ +return [ + 'year' => ':count tahun', + 'a_year' => '{1}setahun|]1,Inf[:count tahun', + 'y' => ':countthn', + 'month' => ':count bulan', + 'a_month' => '{1}sebulan|]1,Inf[:count bulan', + 'm' => ':countbln', + 'week' => ':count minggu', + 'a_week' => '{1}seminggu|]1,Inf[:count minggu', + 'w' => ':countmgg', + 'day' => ':count hari', + 'a_day' => '{1}sehari|]1,Inf[:count hari', + 'd' => ':counthr', + 'hour' => ':count jam', + 'a_hour' => '{1}sejam|]1,Inf[:count jam', + 'h' => ':countj', + 'minute' => ':count menit', + 'a_minute' => '{1}semenit|]1,Inf[:count menit', + 'min' => ':countmnt', + 'second' => ':count detik', + 'a_second' => '{1}beberapa detik|]1,Inf[:count detik', + 's' => ':countdt', + 'ago' => ':time yang lalu', + 'from_now' => ':time dari sekarang', + 'after' => ':time setelahnya', + 'before' => ':time sebelumnya', + 'diff_now' => 'sekarang', + 'diff_today' => 'Hari', + 'diff_today_regexp' => 'Hari(?:\\s+ini)?(?:\\s+pukul)?', + 'diff_yesterday' => 'kemarin', + 'diff_yesterday_regexp' => 'Kemarin(?:\\s+pukul)?', + 'diff_tomorrow' => 'besok', + 'diff_tomorrow_regexp' => 'Besok(?:\\s+pukul)?', + 'formats' => [ + 'LT' => 'HH.mm', + 'LTS' => 'HH.mm.ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY [pukul] HH.mm', + 'LLLL' => 'dddd, D MMMM YYYY [pukul] HH.mm', + ], + 'calendar' => [ + 'sameDay' => '[Hari ini pukul] LT', + 'nextDay' => '[Besok pukul] LT', + 'nextWeek' => 'dddd [pukul] LT', + 'lastDay' => '[Kemarin pukul] LT', + 'lastWeek' => 'dddd [lalu pukul] LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour) { + if ($hour < 11) { + return 'pagi'; + } + if ($hour < 15) { + return 'siang'; + } + if ($hour < 19) { + return 'sore'; + } + + return 'malam'; + }, + 'months' => ['Januari', 'Februari', 'Maret', 'April', 'Mei', 'Juni', 'Juli', 'Agustus', 'September', 'Oktober', 'November', 'Desember'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Agt', 'Sep', 'Okt', 'Nov', 'Des'], + 'weekdays' => ['Minggu', 'Senin', 'Selasa', 'Rabu', 'Kamis', 'Jumat', 'Sabtu'], + 'weekdays_short' => ['Min', 'Sen', 'Sel', 'Rab', 'Kam', 'Jum', 'Sab'], + 'weekdays_min' => ['Mg', 'Sn', 'Sl', 'Rb', 'Km', 'Jm', 'Sb'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' dan '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/id_ID.php b/vendor/nesbot/carbon/src/Carbon/Lang/id_ID.php new file mode 100644 index 0000000..d5953a1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/id_ID.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/id.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ig.php b/vendor/nesbot/carbon/src/Carbon/Lang/ig.php new file mode 100644 index 0000000..de51e9c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ig.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ig_NG.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ig_NG.php b/vendor/nesbot/carbon/src/Carbon/Lang/ig_NG.php new file mode 100644 index 0000000..0034e35 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ig_NG.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - pablo@mandriva.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['Jenụwarị', 'Febrụwarị', 'Maachị', 'Eprel', 'Mee', 'Juun', 'Julaị', 'Ọgọọst', 'Septemba', 'Ọktoba', 'Novemba', 'Disemba'], + 'months_short' => ['Jen', 'Feb', 'Maa', 'Epr', 'Mee', 'Juu', 'Jul', 'Ọgọ', 'Sep', 'Ọkt', 'Nov', 'Dis'], + 'weekdays' => ['sọnde', 'mọnde', 'tuzde', 'wenzde', 'tọsde', 'fraịde', 'satọde'], + 'weekdays_short' => ['sọn', 'mọn', 'tuz', 'wen', 'tọs', 'fra', 'sat'], + 'weekdays_min' => ['sọn', 'mọn', 'tuz', 'wen', 'tọs', 'fra', 'sat'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'year' => 'afo :count', + 'y' => 'afo :count', + 'a_year' => 'afo :count', + + 'month' => 'önwa :count', + 'm' => 'önwa :count', + 'a_month' => 'önwa :count', + + 'week' => 'izu :count', + 'w' => 'izu :count', + 'a_week' => 'izu :count', + + 'day' => 'ụbọchị :count', + 'd' => 'ụbọchị :count', + 'a_day' => 'ụbọchị :count', + + 'hour' => 'awa :count', + 'h' => 'awa :count', + 'a_hour' => 'awa :count', + + 'minute' => 'minit :count', + 'min' => 'minit :count', + 'a_minute' => 'minit :count', + + 'second' => 'sekọnd :count', + 's' => 'sekọnd :count', + 'a_second' => 'sekọnd :count', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ii.php b/vendor/nesbot/carbon/src/Carbon/Lang/ii.php new file mode 100644 index 0000000..a4246c2 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ii.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['ꎸꄑ', 'ꁯꋒ'], + 'weekdays' => ['ꑭꆏꑍ', 'ꆏꊂꋍ', 'ꆏꊂꑍ', 'ꆏꊂꌕ', 'ꆏꊂꇖ', 'ꆏꊂꉬ', 'ꆏꊂꃘ'], + 'weekdays_short' => ['ꑭꆏ', 'ꆏꋍ', 'ꆏꑍ', 'ꆏꌕ', 'ꆏꇖ', 'ꆏꉬ', 'ꆏꃘ'], + 'weekdays_min' => ['ꑭꆏ', 'ꆏꋍ', 'ꆏꑍ', 'ꆏꌕ', 'ꆏꇖ', 'ꆏꉬ', 'ꆏꃘ'], + 'months' => null, + 'months_short' => ['ꋍꆪ', 'ꑍꆪ', 'ꌕꆪ', 'ꇖꆪ', 'ꉬꆪ', 'ꃘꆪ', 'ꏃꆪ', 'ꉆꆪ', 'ꈬꆪ', 'ꊰꆪ', 'ꊰꊪꆪ', 'ꊰꑋꆪ'], + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'YYYY-MM-dd', + 'LL' => 'YYYY MMM D', + 'LLL' => 'YYYY MMMM D h:mm a', + 'LLLL' => 'YYYY MMMM D, dddd h:mm a', + ], + + 'year' => ':count ꒉ', // less reliable + 'y' => ':count ꒉ', // less reliable + 'a_year' => ':count ꒉ', // less reliable + + 'month' => ':count ꆪ', + 'm' => ':count ꆪ', + 'a_month' => ':count ꆪ', + + 'week' => ':count ꏃ', // less reliable + 'w' => ':count ꏃ', // less reliable + 'a_week' => ':count ꏃ', // less reliable + + 'day' => ':count ꏜ', // less reliable + 'd' => ':count ꏜ', // less reliable + 'a_day' => ':count ꏜ', // less reliable + + 'hour' => ':count ꄮꈉ', + 'h' => ':count ꄮꈉ', + 'a_hour' => ':count ꄮꈉ', + + 'minute' => ':count ꀄꊭ', // less reliable + 'min' => ':count ꀄꊭ', // less reliable + 'a_minute' => ':count ꀄꊭ', // less reliable + + 'second' => ':count ꇅ', // less reliable + 's' => ':count ꇅ', // less reliable + 'a_second' => ':count ꇅ', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ik.php b/vendor/nesbot/carbon/src/Carbon/Lang/ik.php new file mode 100644 index 0000000..7a13aa2 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ik.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ik_CA.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ik_CA.php b/vendor/nesbot/carbon/src/Carbon/Lang/ik_CA.php new file mode 100644 index 0000000..bb2a109 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ik_CA.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - pablo@mandriva.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['Siqiññaatchiaq', 'Siqiññaasrugruk', 'Paniqsiqsiivik', 'Qilġich Tatqiat', 'Suppivik', 'Iġñivik', 'Itchavik', 'Tiññivik', 'Amiġaiqsivik', 'Sikkuvik', 'Nippivik', 'Siqiñġiḷaq'], + 'months_short' => ['Sñt', 'Sñs', 'Pan', 'Qil', 'Sup', 'Iġñ', 'Itc', 'Tiñ', 'Ami', 'Sik', 'Nip', 'Siq'], + 'weekdays' => ['Minġuiqsioiq', 'Savałłiq', 'Ilaqtchiioiq', 'Qitchiioiq', 'Sisamiioiq', 'Tallimmiioiq', 'Maqinġuoiq'], + 'weekdays_short' => ['Min', 'Sav', 'Ila', 'Qit', 'Sis', 'Tal', 'Maq'], + 'weekdays_min' => ['Min', 'Sav', 'Ila', 'Qit', 'Sis', 'Tal', 'Maq'], + 'day_of_first_week_of_year' => 1, + + 'year' => ':count ukiuq', + 'y' => ':count ukiuq', + 'a_year' => ':count ukiuq', + + 'month' => ':count Tatqiat', + 'm' => ':count Tatqiat', + 'a_month' => ':count Tatqiat', + + 'week' => ':count tatqiat', // less reliable + 'w' => ':count tatqiat', // less reliable + 'a_week' => ':count tatqiat', // less reliable + + 'day' => ':count siqiñiq', // less reliable + 'd' => ':count siqiñiq', // less reliable + 'a_day' => ':count siqiñiq', // less reliable + + 'hour' => ':count Siḷa', // less reliable + 'h' => ':count Siḷa', // less reliable + 'a_hour' => ':count Siḷa', // less reliable + + 'second' => ':count iġñiq', // less reliable + 's' => ':count iġñiq', // less reliable + 'a_second' => ':count iġñiq', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/in.php b/vendor/nesbot/carbon/src/Carbon/Lang/in.php new file mode 100644 index 0000000..d5953a1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/in.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/id.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/is.php b/vendor/nesbot/carbon/src/Carbon/Lang/is.php new file mode 100644 index 0000000..9990168 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/is.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kristján Ingi Geirsson + */ +return [ + 'year' => '1 ár|:count ár', + 'y' => '1 ár|:count ár', + 'month' => '1 mánuður|:count mánuðir', + 'm' => '1 mánuður|:count mánuðir', + 'week' => '1 vika|:count vikur', + 'w' => '1 vika|:count vikur', + 'day' => '1 dagur|:count dagar', + 'd' => '1 dagur|:count dagar', + 'hour' => '1 klukkutími|:count klukkutímar', + 'h' => '1 klukkutími|:count klukkutímar', + 'minute' => '1 mínúta|:count mínútur', + 'min' => '1 mínúta|:count mínútur', + 'second' => '1 sekúnda|:count sekúndur', + 's' => '1 sekúnda|:count sekúndur', + 'ago' => ':time síðan', + 'from_now' => ':time síðan', + 'after' => ':time eftir', + 'before' => ':time fyrir', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' og '], + 'meridiem' => ['fh', 'eh'], + 'diff_now' => 'núna', + 'diff_yesterday' => 'í gær', + 'diff_tomorrow' => 'á morgun', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM [kl.] HH:mm', + 'LLLL' => 'dddd D. MMMM YYYY [kl.] HH:mm', + ], + 'weekdays' => ['sunnudaginn', 'mánudaginn', 'þriðjudaginn', 'miðvikudaginn', 'fimmtudaginn', 'föstudaginn', 'laugardaginn'], + 'weekdays_short' => ['sun', 'mán', 'þri', 'mið', 'fim', 'fös', 'lau'], + 'weekdays_min' => ['sun', 'mán', 'þri', 'mið', 'fim', 'fös', 'lau'], + 'months' => ['janúar', 'febrúar', 'mars', 'apríl', 'maí', 'júní', 'júlí', 'ágúst', 'september', 'október', 'nóvember', 'desember'], + 'months_short' => ['jan', 'feb', 'mar', 'apr', 'maí', 'jún', 'júl', 'ágú', 'sep', 'okt', 'nóv', 'des'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/is_IS.php b/vendor/nesbot/carbon/src/Carbon/Lang/is_IS.php new file mode 100644 index 0000000..4d35c44 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/is_IS.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/is.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/it.php b/vendor/nesbot/carbon/src/Carbon/Lang/it.php new file mode 100644 index 0000000..605bcbb --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/it.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ash + * - François B + * - Marco Perrando + * - Massimiliano Caniparoli + * - JD Isaacks + * - Andrea Martini + * - Francesco Marasco + * - Tizianoz93 + * - Davide Casiraghi (davide-casiraghi) + * - Pete Scopes (pdscopes) + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count anno|:count anni', + 'a_year' => 'un anno|:count anni', + 'y' => ':count anno|:count anni', + 'month' => ':count mese|:count mesi', + 'a_month' => 'un mese|:count mesi', + 'm' => ':count mese|:count mesi', + 'week' => ':count settimana|:count settimane', + 'a_week' => 'una settimana|:count settimane', + 'w' => ':count set.', + 'day' => ':count giorno|:count giorni', + 'a_day' => 'un giorno|:count giorni', + 'd' => ':count g|:count gg', + 'hour' => ':count ora|:count ore', + 'a_hour' => 'un\'ora|:count ore', + 'h' => ':count h', + 'minute' => ':count minuto|:count minuti', + 'a_minute' => 'un minuto|:count minuti', + 'min' => ':count min.', + 'second' => ':count secondo|:count secondi', + 'a_second' => 'alcuni secondi|:count secondi', + 's' => ':count sec.', + 'millisecond' => ':count millisecondo|:count millisecondi', + 'a_millisecond' => 'un millisecondo|:count millisecondi', + 'ms' => ':countms', + 'microsecond' => ':count microsecondo|:count microsecondi', + 'a_microsecond' => 'un microsecondo|:count microsecondi', + 'µs' => ':countµs', + 'ago' => ':time fa', + 'from_now' => function ($time) { + return (preg_match('/^[0-9].+$/', $time) ? 'tra' : 'in')." $time"; + }, + 'after' => ':time dopo', + 'before' => ':time prima', + 'diff_now' => 'proprio ora', + 'diff_today' => 'Oggi', + 'diff_today_regexp' => 'Oggi(?:\\s+alle)?', + 'diff_yesterday' => 'ieri', + 'diff_yesterday_regexp' => 'Ieri(?:\\s+alle)?', + 'diff_tomorrow' => 'domani', + 'diff_tomorrow_regexp' => 'Domani(?:\\s+alle)?', + 'diff_before_yesterday' => 'l\'altro ieri', + 'diff_after_tomorrow' => 'dopodomani', + 'period_interval' => 'ogni :interval', + 'period_start_date' => 'dal :date', + 'period_end_date' => 'al :date', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Oggi alle] LT', + 'nextDay' => '[Domani alle] LT', + 'nextWeek' => 'dddd [alle] LT', + 'lastDay' => '[Ieri alle] LT', + 'lastWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[la scorsa] dddd [alle] LT'; + default: + return '[lo scorso] dddd [alle] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => ':numberº', + 'months' => ['gennaio', 'febbraio', 'marzo', 'aprile', 'maggio', 'giugno', 'luglio', 'agosto', 'settembre', 'ottobre', 'novembre', 'dicembre'], + 'months_short' => ['gen', 'feb', 'mar', 'apr', 'mag', 'giu', 'lug', 'ago', 'set', 'ott', 'nov', 'dic'], + 'weekdays' => ['domenica', 'lunedì', 'martedì', 'mercoledì', 'giovedì', 'venerdì', 'sabato'], + 'weekdays_short' => ['dom', 'lun', 'mar', 'mer', 'gio', 'ven', 'sab'], + 'weekdays_min' => ['do', 'lu', 'ma', 'me', 'gi', 've', 'sa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' e '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/it_CH.php b/vendor/nesbot/carbon/src/Carbon/Lang/it_CH.php new file mode 100644 index 0000000..c23cc50 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/it_CH.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Propaganistas + */ +return array_replace_recursive(require __DIR__.'/it.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/it_IT.php b/vendor/nesbot/carbon/src/Carbon/Lang/it_IT.php new file mode 100644 index 0000000..a5d1981 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/it_IT.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return require __DIR__.'/it.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/it_SM.php b/vendor/nesbot/carbon/src/Carbon/Lang/it_SM.php new file mode 100644 index 0000000..5e8fc92 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/it_SM.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/it.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/it_VA.php b/vendor/nesbot/carbon/src/Carbon/Lang/it_VA.php new file mode 100644 index 0000000..5e8fc92 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/it_VA.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/it.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/iu.php b/vendor/nesbot/carbon/src/Carbon/Lang/iu.php new file mode 100644 index 0000000..4fa9742 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/iu.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/iu_CA.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/iu_CA.php b/vendor/nesbot/carbon/src/Carbon/Lang/iu_CA.php new file mode 100644 index 0000000..6ab7e14 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/iu_CA.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Pablo Saratxaga pablo@mandriva.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'MM/DD/YY', + ], + 'months' => ['ᔮᓄᐊᓕ', 'ᕕᕗᐊᓕ', 'ᒪᔅᓯ', 'ᐃᐳᓗ', 'ᒪᐃ', 'ᔪᓂ', 'ᔪᓚᐃ', 'ᐊᒋᓯ', 'ᓯᑎᕙ', 'ᐊᑦᑐᕙ', 'ᓄᕕᕙ', 'ᑎᓯᕝᕙ'], + 'months_short' => ['ᔮᓄ', 'ᕕᕗ', 'ᒪᔅ', 'ᐃᐳ', 'ᒪᐃ', 'ᔪᓂ', 'ᔪᓚ', 'ᐊᒋ', 'ᓯᑎ', 'ᐊᑦ', 'ᓄᕕ', 'ᑎᓯ'], + 'weekdays' => ['ᓈᑦᑎᖑᔭᕐᕕᒃ', 'ᓇᒡᒐᔾᔭᐅ', 'ᓇᒡᒐᔾᔭᐅᓕᖅᑭᑦ', 'ᐱᖓᓲᓕᖅᓯᐅᑦ', 'ᕿᑎᖅᑰᑦ', 'ᐅᓪᓗᕈᓘᑐᐃᓇᖅ', 'ᓯᕙᑖᕕᒃ'], + 'weekdays_short' => ['ᓈ', 'ᓇ', 'ᓕ', 'ᐱ', 'ᕿ', 'ᐅ', 'ᓯ'], + 'weekdays_min' => ['ᓈ', 'ᓇ', 'ᓕ', 'ᐱ', 'ᕿ', 'ᐅ', 'ᓯ'], + 'day_of_first_week_of_year' => 1, + + 'year' => ':count ᐅᑭᐅᖅ', + 'y' => ':count ᐅᑭᐅᖅ', + 'a_year' => ':count ᐅᑭᐅᖅ', + + 'month' => ':count qaammat', + 'm' => ':count qaammat', + 'a_month' => ':count qaammat', + + 'week' => ':count sapaatip akunnera', + 'w' => ':count sapaatip akunnera', + 'a_week' => ':count sapaatip akunnera', + + 'day' => ':count ulloq', + 'd' => ':count ulloq', + 'a_day' => ':count ulloq', + + 'hour' => ':count ikarraq', + 'h' => ':count ikarraq', + 'a_hour' => ':count ikarraq', + + 'minute' => ':count titiqqaralaaq', // less reliable + 'min' => ':count titiqqaralaaq', // less reliable + 'a_minute' => ':count titiqqaralaaq', // less reliable + + 'second' => ':count marluk', // less reliable + 's' => ':count marluk', // less reliable + 'a_second' => ':count marluk', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/iw.php b/vendor/nesbot/carbon/src/Carbon/Lang/iw.php new file mode 100644 index 0000000..a26e350 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/iw.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'months' => ['ינואר', 'פברואר', 'מרץ', 'אפריל', 'מאי', 'יוני', 'יולי', 'אוגוסט', 'ספטמבר', 'אוקטובר', 'נובמבר', 'דצמבר'], + 'months_short' => ['ינו׳', 'פבר׳', 'מרץ', 'אפר׳', 'מאי', 'יוני', 'יולי', 'אוג׳', 'ספט׳', 'אוק׳', 'נוב׳', 'דצמ׳'], + 'weekdays' => ['יום ראשון', 'יום שני', 'יום שלישי', 'יום רביעי', 'יום חמישי', 'יום שישי', 'יום שבת'], + 'weekdays_short' => ['יום א׳', 'יום ב׳', 'יום ג׳', 'יום ד׳', 'יום ה׳', 'יום ו׳', 'שבת'], + 'weekdays_min' => ['א׳', 'ב׳', 'ג׳', 'ד׳', 'ה׳', 'ו׳', 'ש׳'], + 'meridiem' => ['לפנה״צ', 'אחה״צ'], + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'D.M.YYYY', + 'LL' => 'D בMMM YYYY', + 'LLL' => 'D בMMMM YYYY H:mm', + 'LLLL' => 'dddd, D בMMMM YYYY H:mm', + ], + + 'year' => ':count שנה', + 'y' => ':count שנה', + 'a_year' => ':count שנה', + + 'month' => ':count חודש', + 'm' => ':count חודש', + 'a_month' => ':count חודש', + + 'week' => ':count שבוע', + 'w' => ':count שבוע', + 'a_week' => ':count שבוע', + + 'day' => ':count יום', + 'd' => ':count יום', + 'a_day' => ':count יום', + + 'hour' => ':count שעה', + 'h' => ':count שעה', + 'a_hour' => ':count שעה', + + 'minute' => ':count דקה', + 'min' => ':count דקה', + 'a_minute' => ':count דקה', + + 'second' => ':count שניה', + 's' => ':count שניה', + 'a_second' => ':count שניה', + + 'ago' => 'לפני :time', + 'from_now' => 'בעוד :time', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ja.php b/vendor/nesbot/carbon/src/Carbon/Lang/ja.php new file mode 100644 index 0000000..1ca6751 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ja.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Takuya Sawada + * - Atsushi Tanaka + * - François B + * - Jason Katz-Brown + * - Serhan Apaydın + * - XueWei + * - JD Isaacks + * - toyama satoshi + * - atakigawa + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count年', + 'y' => ':count年', + 'month' => ':countヶ月', + 'm' => ':countヶ月', + 'week' => ':count週間', + 'w' => ':count週間', + 'day' => ':count日', + 'd' => ':count日', + 'hour' => ':count時間', + 'h' => ':count時間', + 'minute' => ':count分', + 'min' => ':count分', + 'second' => ':count秒', + 'a_second' => '{1}数秒|]1,Inf[:count秒', + 's' => ':count秒', + 'ago' => ':time前', + 'from_now' => ':time後', + 'after' => ':time後', + 'before' => ':time前', + 'diff_now' => '今', + 'diff_today' => '今日', + 'diff_yesterday' => '昨日', + 'diff_tomorrow' => '明日', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY/MM/DD', + 'LL' => 'YYYY年M月D日', + 'LLL' => 'YYYY年M月D日 HH:mm', + 'LLLL' => 'YYYY年M月D日 dddd HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[今日] LT', + 'nextDay' => '[明日] LT', + 'nextWeek' => function (CarbonInterface $current, CarbonInterface $other) { + if ($other->week !== $current->week) { + return '[来週]dddd LT'; + } + + return 'dddd LT'; + }, + 'lastDay' => '[昨日] LT', + 'lastWeek' => function (CarbonInterface $current, CarbonInterface $other) { + if ($other->week !== $current->week) { + return '[先週]dddd LT'; + } + + return 'dddd LT'; + }, + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + switch ($period) { + case 'd': + case 'D': + case 'DDD': + return $number.'日'; + default: + return $number; + } + }, + 'meridiem' => ['午前', '午後'], + 'months' => ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'], + 'months_short' => ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'], + 'weekdays' => ['日曜日', '月曜日', '火曜日', '水曜日', '木曜日', '金曜日', '土曜日'], + 'weekdays_short' => ['日', '月', '火', '水', '木', '金', '土'], + 'weekdays_min' => ['日', '月', '火', '水', '木', '金', '土'], + 'list' => '、', + 'alt_numbers' => ['〇', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二', '十三', '十四', '十五', '十六', '十七', '十八', '十九', '二十', '二十一', '二十二', '二十三', '二十四', '二十五', '二十六', '二十七', '二十八', '二十九', '三十', '三十一', '三十二', '三十三', '三十四', '三十五', '三十六', '三十七', '三十八', '三十九', '四十', '四十一', '四十二', '四十三', '四十四', '四十五', '四十六', '四十七', '四十八', '四十九', '五十', '五十一', '五十二', '五十三', '五十四', '五十五', '五十六', '五十七', '五十八', '五十九', '六十', '六十一', '六十二', '六十三', '六十四', '六十五', '六十六', '六十七', '六十八', '六十九', '七十', '七十一', '七十二', '七十三', '七十四', '七十五', '七十六', '七十七', '七十八', '七十九', '八十', '八十一', '八十二', '八十三', '八十四', '八十五', '八十六', '八十七', '八十八', '八十九', '九十', '九十一', '九十二', '九十三', '九十四', '九十五', '九十六', '九十七', '九十八', '九十九'], + 'alt_numbers_pow' => [ + 10000 => '万', + 1000 => '千', + 100 => '百', + ], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ja_JP.php b/vendor/nesbot/carbon/src/Carbon/Lang/ja_JP.php new file mode 100644 index 0000000..c283625 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ja_JP.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ja.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/jgo.php b/vendor/nesbot/carbon/src/Carbon/Lang/jgo.php new file mode 100644 index 0000000..6a1e77a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/jgo.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/jmc.php b/vendor/nesbot/carbon/src/Carbon/Lang/jmc.php new file mode 100644 index 0000000..ed92e8e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/jmc.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['utuko', 'kyiukonyi'], + 'weekdays' => ['Jumapilyi', 'Jumatatuu', 'Jumanne', 'Jumatanu', 'Alhamisi', 'Ijumaa', 'Jumamosi'], + 'weekdays_short' => ['Jpi', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'weekdays_min' => ['Jpi', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'months' => ['Januari', 'Februari', 'Machi', 'Aprilyi', 'Mei', 'Junyi', 'Julyai', 'Agusti', 'Septemba', 'Oktoba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Sep', 'Okt', 'Nov', 'Des'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/jv.php b/vendor/nesbot/carbon/src/Carbon/Lang/jv.php new file mode 100644 index 0000000..bcbe044 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/jv.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - tgfjt + * - JD Isaacks + */ +return [ + 'year' => '{1}setaun|]1,Inf[:count taun', + 'month' => '{1}sewulan|]1,Inf[:count wulan', + 'week' => '{1}sakminggu|]1,Inf[:count minggu', + 'day' => '{1}sedinten|]1,Inf[:count dinten', + 'hour' => '{1}setunggal jam|]1,Inf[:count jam', + 'minute' => '{1}setunggal menit|]1,Inf[:count menit', + 'second' => '{1}sawetawis detik|]1,Inf[:count detik', + 'ago' => ':time ingkang kepengker', + 'from_now' => 'wonten ing :time', + 'diff_today' => 'Dinten', + 'diff_yesterday' => 'Kala', + 'diff_yesterday_regexp' => 'Kala(?:\\s+wingi)?(?:\\s+pukul)?', + 'diff_tomorrow' => 'Mbenjang', + 'diff_tomorrow_regexp' => 'Mbenjang(?:\\s+pukul)?', + 'diff_today_regexp' => 'Dinten(?:\\s+puniko)?(?:\\s+pukul)?', + 'formats' => [ + 'LT' => 'HH.mm', + 'LTS' => 'HH.mm.ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY [pukul] HH.mm', + 'LLLL' => 'dddd, D MMMM YYYY [pukul] HH.mm', + ], + 'calendar' => [ + 'sameDay' => '[Dinten puniko pukul] LT', + 'nextDay' => '[Mbenjang pukul] LT', + 'nextWeek' => 'dddd [pukul] LT', + 'lastDay' => '[Kala wingi pukul] LT', + 'lastWeek' => 'dddd [kepengker pukul] LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour) { + if ($hour < 11) { + return 'enjing'; + } + if ($hour < 15) { + return 'siyang'; + } + if ($hour < 19) { + return 'sonten'; + } + + return 'ndalu'; + }, + 'months' => ['Januari', 'Februari', 'Maret', 'April', 'Mei', 'Juni', 'Juli', 'Agustus', 'September', 'Oktober', 'Nopember', 'Desember'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Ags', 'Sep', 'Okt', 'Nop', 'Des'], + 'weekdays' => ['Minggu', 'Senen', 'Seloso', 'Rebu', 'Kemis', 'Jemuwah', 'Septu'], + 'weekdays_short' => ['Min', 'Sen', 'Sel', 'Reb', 'Kem', 'Jem', 'Sep'], + 'weekdays_min' => ['Mg', 'Sn', 'Sl', 'Rb', 'Km', 'Jm', 'Sp'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' lan '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ka.php b/vendor/nesbot/carbon/src/Carbon/Lang/ka.php new file mode 100644 index 0000000..a5d563d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ka.php @@ -0,0 +1,204 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Tornike Razmadze + * - François B + * - Lasha Dolidze + * - Tim Fish + * - JD Isaacks + * - Tornike Razmadze + * - François B + * - Lasha Dolidze + * - JD Isaacks + * - LONGMAN + * - Avtandil Kikabidze (akalongman) + * - Levan Velijanashvili (Stichoza) + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count წელი', + 'y' => ':count წელი', + 'a_year' => '{1}წელი|]1,Inf[:count წელი', + 'month' => ':count თვე', + 'm' => ':count თვე', + 'a_month' => '{1}თვე|]1,Inf[:count თვე', + 'week' => ':count კვირა', + 'w' => ':count კვირა', + 'a_week' => '{1}კვირა|]1,Inf[:count კვირა', + 'day' => ':count დღე', + 'd' => ':count დღე', + 'a_day' => '{1}დღე|]1,Inf[:count დღე', + 'hour' => ':count საათი', + 'h' => ':count საათი', + 'a_hour' => '{1}საათი|]1,Inf[:count საათი', + 'minute' => ':count წუთი', + 'min' => ':count წუთი', + 'a_minute' => '{1}წუთი|]1,Inf[:count წუთი', + 'second' => ':count წამი', + 's' => ':count წამი', + 'a_second' => '{1}რამდენიმე წამი|]1,Inf[:count წამი', + 'ago' => function ($time) { + $replacements = [ + // year + 'წელი' => 'წლის', + // month + 'თვე' => 'თვის', + // week + 'კვირა' => 'კვირის', + // day + 'დღე' => 'დღის', + // hour + 'საათი' => 'საათის', + // minute + 'წუთი' => 'წუთის', + // second + 'წამი' => 'წამის', + ]; + $time = strtr($time, array_flip($replacements)); + $time = strtr($time, $replacements); + + return "$time წინ"; + }, + 'from_now' => function ($time) { + $replacements = [ + // year + 'წელი' => 'წელიწადში', + // week + 'კვირა' => 'კვირაში', + // day + 'დღე' => 'დღეში', + // month + 'თვე' => 'თვეში', + // hour + 'საათი' => 'საათში', + // minute + 'წუთი' => 'წუთში', + // second + 'წამი' => 'წამში', + ]; + $time = strtr($time, array_flip($replacements)); + $time = strtr($time, $replacements); + + return $time; + }, + 'after' => function ($time) { + $replacements = [ + // year + 'წელი' => 'წლის', + // month + 'თვე' => 'თვის', + // week + 'კვირა' => 'კვირის', + // day + 'დღე' => 'დღის', + // hour + 'საათი' => 'საათის', + // minute + 'წუთი' => 'წუთის', + // second + 'წამი' => 'წამის', + ]; + $time = strtr($time, array_flip($replacements)); + $time = strtr($time, $replacements); + + return "$time შემდეგ"; + }, + 'before' => function ($time) { + $replacements = [ + // year + 'წელი' => 'წლით', + // month + 'თვე' => 'თვით', + // week + 'კვირა' => 'კვირით', + // day + 'დღე' => 'დღით', + // hour + 'საათი' => 'საათით', + // minute + 'წუთი' => 'წუთით', + // second + 'წამი' => 'წამით', + ]; + $time = strtr($time, array_flip($replacements)); + $time = strtr($time, $replacements); + + return "$time ადრე"; + }, + 'diff_now' => 'ახლა', + 'diff_today' => 'დღეს', + 'diff_yesterday' => 'გუშინ', + 'diff_tomorrow' => 'ხვალ', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[დღეს], LT[-ზე]', + 'nextDay' => '[ხვალ], LT[-ზე]', + 'nextWeek' => function (CarbonInterface $current, CarbonInterface $other) { + return ($current->isSameWeek($other) ? '' : '[შემდეგ] ').'dddd, LT[-ზე]'; + }, + 'lastDay' => '[გუშინ], LT[-ზე]', + 'lastWeek' => '[წინა] dddd, LT-ზე', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + if ($number === 0) { + return $number; + } + if ($number === 1) { + return $number.'-ლი'; + } + if (($number < 20) || ($number <= 100 && ($number % 20 === 0)) || ($number % 100 === 0)) { + return 'მე-'.$number; + } + + return $number.'-ე'; + }, + 'months' => ['იანვარი', 'თებერვალი', 'მარტი', 'აპრილი', 'მაისი', 'ივნისი', 'ივლისი', 'აგვისტო', 'სექტემბერი', 'ოქტომბერი', 'ნოემბერი', 'დეკემბერი'], + 'months_standalone' => ['იანვარს', 'თებერვალს', 'მარტს', 'აპრილს', 'მაისს', 'ივნისს', 'ივლისს', 'აგვისტოს', 'სექტემბერს', 'ოქტომბერს', 'ნოემბერს', 'დეკემბერს'], + 'months_short' => ['იან', 'თებ', 'მარ', 'აპრ', 'მაი', 'ივნ', 'ივლ', 'აგვ', 'სექ', 'ოქტ', 'ნოე', 'დეკ'], + 'months_regexp' => '/(D[oD]?(\[[^\[\]]*\]|\s)+MMMM?|L{2,4}|l{2,4})/', + 'weekdays' => ['კვირას', 'ორშაბათს', 'სამშაბათს', 'ოთხშაბათს', 'ხუთშაბათს', 'პარასკევს', 'შაბათს'], + 'weekdays_standalone' => ['კვირა', 'ორშაბათი', 'სამშაბათი', 'ოთხშაბათი', 'ხუთშაბათი', 'პარასკევი', 'შაბათი'], + 'weekdays_short' => ['კვი', 'ორშ', 'სამ', 'ოთხ', 'ხუთ', 'პარ', 'შაბ'], + 'weekdays_min' => ['კვ', 'ორ', 'სა', 'ოთ', 'ხუ', 'პა', 'შა'], + 'weekdays_regexp' => '/^([^d].*|.*[^d])$/', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' და '], + 'meridiem' => function ($hour) { + if ($hour >= 4) { + if ($hour < 11) { + return 'დილის'; + } + + if ($hour < 16) { + return 'შუადღის'; + } + + if ($hour < 22) { + return 'საღამოს'; + } + } + + return 'ღამის'; + }, +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ka_GE.php b/vendor/nesbot/carbon/src/Carbon/Lang/ka_GE.php new file mode 100644 index 0000000..a26d930 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ka_GE.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ka.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/kab.php b/vendor/nesbot/carbon/src/Carbon/Lang/kab.php new file mode 100644 index 0000000..94d6473 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/kab.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/kab_DZ.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/kab_DZ.php b/vendor/nesbot/carbon/src/Carbon/Lang/kab_DZ.php new file mode 100644 index 0000000..796660b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/kab_DZ.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - belkacem77@gmail.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Yennayer', 'Fuṛar', 'Meɣres', 'Yebrir', 'Mayyu', 'Yunyu', 'Yulyu', 'ɣuct', 'Ctembeṛ', 'Tubeṛ', 'Wambeṛ', 'Dujembeṛ'], + 'months_short' => ['Yen', 'Fur', 'Meɣ', 'Yeb', 'May', 'Yun', 'Yul', 'ɣuc', 'Cte', 'Tub', 'Wam', 'Duj'], + 'weekdays' => ['Acer', 'Arim', 'Aram', 'Ahad', 'Amhad', 'Sem', 'Sed'], + 'weekdays_short' => ['Ace', 'Ari', 'Ara', 'Aha', 'Amh', 'Sem', 'Sed'], + 'weekdays_min' => ['Ace', 'Ari', 'Ara', 'Aha', 'Amh', 'Sem', 'Sed'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['FT', 'MD'], + + 'year' => ':count n yiseggasen', + 'y' => ':count n yiseggasen', + 'a_year' => ':count n yiseggasen', + + 'month' => ':count n wayyuren', + 'm' => ':count n wayyuren', + 'a_month' => ':count n wayyuren', + + 'week' => ':count n ledwaṛ', // less reliable + 'w' => ':count n ledwaṛ', // less reliable + 'a_week' => ':count n ledwaṛ', // less reliable + + 'day' => ':count n wussan', + 'd' => ':count n wussan', + 'a_day' => ':count n wussan', + + 'hour' => ':count n tsaɛtin', + 'h' => ':count n tsaɛtin', + 'a_hour' => ':count n tsaɛtin', + + 'minute' => ':count n tedqiqin', + 'min' => ':count n tedqiqin', + 'a_minute' => ':count n tedqiqin', + + 'second' => ':count tasdidt', // less reliable + 's' => ':count tasdidt', // less reliable + 'a_second' => ':count tasdidt', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/kam.php b/vendor/nesbot/carbon/src/Carbon/Lang/kam.php new file mode 100644 index 0000000..0fc70d7 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/kam.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Ĩyakwakya', 'Ĩyawĩoo'], + 'weekdays' => ['Wa kyumwa', 'Wa kwambĩlĩlya', 'Wa kelĩ', 'Wa katatũ', 'Wa kana', 'Wa katano', 'Wa thanthatũ'], + 'weekdays_short' => ['Wky', 'Wkw', 'Wkl', 'Wtũ', 'Wkn', 'Wtn', 'Wth'], + 'weekdays_min' => ['Wky', 'Wkw', 'Wkl', 'Wtũ', 'Wkn', 'Wtn', 'Wth'], + 'months' => ['Mwai wa mbee', 'Mwai wa kelĩ', 'Mwai wa katatũ', 'Mwai wa kana', 'Mwai wa katano', 'Mwai wa thanthatũ', 'Mwai wa muonza', 'Mwai wa nyaanya', 'Mwai wa kenda', 'Mwai wa ĩkumi', 'Mwai wa ĩkumi na ĩmwe', 'Mwai wa ĩkumi na ilĩ'], + 'months_short' => ['Mbe', 'Kel', 'Ktũ', 'Kan', 'Ktn', 'Tha', 'Moo', 'Nya', 'Knd', 'Ĩku', 'Ĩkm', 'Ĩkl'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + + // Too unreliable + /* + 'year' => ':count mbua', // less reliable + 'y' => ':count mbua', // less reliable + 'a_year' => ':count mbua', // less reliable + + 'month' => ':count ndakitali', // less reliable + 'm' => ':count ndakitali', // less reliable + 'a_month' => ':count ndakitali', // less reliable + + 'day' => ':count wia', // less reliable + 'd' => ':count wia', // less reliable + 'a_day' => ':count wia', // less reliable + + 'hour' => ':count orasan', // less reliable + 'h' => ':count orasan', // less reliable + 'a_hour' => ':count orasan', // less reliable + + 'minute' => ':count orasan', // less reliable + 'min' => ':count orasan', // less reliable + 'a_minute' => ':count orasan', // less reliable + */ +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/kde.php b/vendor/nesbot/carbon/src/Carbon/Lang/kde.php new file mode 100644 index 0000000..fbcc9f3 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/kde.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Muhi', 'Chilo'], + 'weekdays' => ['Liduva lyapili', 'Liduva lyatatu', 'Liduva lyanchechi', 'Liduva lyannyano', 'Liduva lyannyano na linji', 'Liduva lyannyano na mavili', 'Liduva litandi'], + 'weekdays_short' => ['Ll2', 'Ll3', 'Ll4', 'Ll5', 'Ll6', 'Ll7', 'Ll1'], + 'weekdays_min' => ['Ll2', 'Ll3', 'Ll4', 'Ll5', 'Ll6', 'Ll7', 'Ll1'], + 'months' => ['Mwedi Ntandi', 'Mwedi wa Pili', 'Mwedi wa Tatu', 'Mwedi wa Nchechi', 'Mwedi wa Nnyano', 'Mwedi wa Nnyano na Umo', 'Mwedi wa Nnyano na Mivili', 'Mwedi wa Nnyano na Mitatu', 'Mwedi wa Nnyano na Nchechi', 'Mwedi wa Nnyano na Nnyano', 'Mwedi wa Nnyano na Nnyano na U', 'Mwedi wa Nnyano na Nnyano na M'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Sep', 'Okt', 'Nov', 'Des'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/kea.php b/vendor/nesbot/carbon/src/Carbon/Lang/kea.php new file mode 100644 index 0000000..8b6c21b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/kea.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['a', 'p'], + 'weekdays' => ['dumingu', 'sigunda-fera', 'tersa-fera', 'kuarta-fera', 'kinta-fera', 'sesta-fera', 'sabadu'], + 'weekdays_short' => ['dum', 'sig', 'ter', 'kua', 'kin', 'ses', 'sab'], + 'weekdays_min' => ['du', 'si', 'te', 'ku', 'ki', 'se', 'sa'], + 'weekdays_standalone' => ['dumingu', 'sigunda-fera', 'tersa-fera', 'kuarta-fera', 'kinta-fera', 'sesta-fera', 'sábadu'], + 'months' => ['Janeru', 'Febreru', 'Marsu', 'Abril', 'Maiu', 'Junhu', 'Julhu', 'Agostu', 'Setenbru', 'Otubru', 'Nuvenbru', 'Dizenbru'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Otu', 'Nuv', 'Diz'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D [di] MMMM [di] YYYY HH:mm', + 'LLLL' => 'dddd, D [di] MMMM [di] YYYY HH:mm', + ], + + 'year' => ':count otunu', // less reliable + 'y' => ':count otunu', // less reliable + 'a_year' => ':count otunu', // less reliable + + 'week' => ':count día dumingu', // less reliable + 'w' => ':count día dumingu', // less reliable + 'a_week' => ':count día dumingu', // less reliable + + 'day' => ':count diâ', // less reliable + 'd' => ':count diâ', // less reliable + 'a_day' => ':count diâ', // less reliable + + 'minute' => ':count sugundu', // less reliable + 'min' => ':count sugundu', // less reliable + 'a_minute' => ':count sugundu', // less reliable + + 'second' => ':count dós', // less reliable + 's' => ':count dós', // less reliable + 'a_second' => ':count dós', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/khq.php b/vendor/nesbot/carbon/src/Carbon/Lang/khq.php new file mode 100644 index 0000000..7a834cf --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/khq.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Adduha', 'Aluula'], + 'weekdays' => ['Alhadi', 'Atini', 'Atalata', 'Alarba', 'Alhamiisa', 'Aljuma', 'Assabdu'], + 'weekdays_short' => ['Alh', 'Ati', 'Ata', 'Ala', 'Alm', 'Alj', 'Ass'], + 'weekdays_min' => ['Alh', 'Ati', 'Ata', 'Ala', 'Alm', 'Alj', 'Ass'], + 'months' => ['Žanwiye', 'Feewiriye', 'Marsi', 'Awiril', 'Me', 'Žuweŋ', 'Žuyye', 'Ut', 'Sektanbur', 'Oktoobur', 'Noowanbur', 'Deesanbur'], + 'months_short' => ['Žan', 'Fee', 'Mar', 'Awi', 'Me', 'Žuw', 'Žuy', 'Ut', 'Sek', 'Okt', 'Noo', 'Dee'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ki.php b/vendor/nesbot/carbon/src/Carbon/Lang/ki.php new file mode 100644 index 0000000..d86afc5 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ki.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Kiroko', 'Hwaĩ-inĩ'], + 'weekdays' => ['Kiumia', 'Njumatatũ', 'Njumaine', 'Njumatana', 'Aramithi', 'Njumaa', 'Njumamothi'], + 'weekdays_short' => ['KMA', 'NTT', 'NMN', 'NMT', 'ART', 'NMA', 'NMM'], + 'weekdays_min' => ['KMA', 'NTT', 'NMN', 'NMT', 'ART', 'NMA', 'NMM'], + 'months' => ['Njenuarĩ', 'Mwere wa kerĩ', 'Mwere wa gatatũ', 'Mwere wa kana', 'Mwere wa gatano', 'Mwere wa gatandatũ', 'Mwere wa mũgwanja', 'Mwere wa kanana', 'Mwere wa kenda', 'Mwere wa ikũmi', 'Mwere wa ikũmi na ũmwe', 'Ndithemba'], + 'months_short' => ['JEN', 'WKR', 'WGT', 'WKN', 'WTN', 'WTD', 'WMJ', 'WNN', 'WKD', 'WIK', 'WMW', 'DIT'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + + 'year' => ':count mĩaka', // less reliable + 'y' => ':count mĩaka', // less reliable + 'a_year' => ':count mĩaka', // less reliable + + 'month' => ':count mweri', // less reliable + 'm' => ':count mweri', // less reliable + 'a_month' => ':count mweri', // less reliable + + 'week' => ':count kiumia', // less reliable + 'w' => ':count kiumia', // less reliable + 'a_week' => ':count kiumia', // less reliable + + 'day' => ':count mũthenya', // less reliable + 'd' => ':count mũthenya', // less reliable + 'a_day' => ':count mũthenya', // less reliable + + 'hour' => ':count thaa', // less reliable + 'h' => ':count thaa', // less reliable + 'a_hour' => ':count thaa', // less reliable + + 'minute' => ':count mundu', // less reliable + 'min' => ':count mundu', // less reliable + 'a_minute' => ':count mundu', // less reliable + + 'second' => ':count igego', // less reliable + 's' => ':count igego', // less reliable + 'a_second' => ':count igego', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/kk.php b/vendor/nesbot/carbon/src/Carbon/Lang/kk.php new file mode 100644 index 0000000..59fa9af --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/kk.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - François B + * - Talat Uspanov + * - Нурлан Рахимжанов + * - Toleugazy Kali + */ +return [ + 'year' => ':count жыл', + 'a_year' => '{1}бір жыл|:count жыл', + 'y' => ':count ж.', + 'month' => ':count ай', + 'a_month' => '{1}бір ай|:count ай', + 'm' => ':count ай', + 'week' => ':count апта', + 'a_week' => '{1}бір апта', + 'w' => ':count ап.', + 'day' => ':count күн', + 'a_day' => '{1}бір күн|:count күн', + 'd' => ':count к.', + 'hour' => ':count сағат', + 'a_hour' => '{1}бір сағат|:count сағат', + 'h' => ':count са.', + 'minute' => ':count минут', + 'a_minute' => '{1}бір минут|:count минут', + 'min' => ':count м.', + 'second' => ':count секунд', + 'a_second' => '{1}бірнеше секунд|:count секунд', + 's' => ':count се.', + 'ago' => ':time бұрын', + 'from_now' => ':time ішінде', + 'after' => ':time кейін', + 'before' => ':time бұрын', + 'diff_now' => 'қазір', + 'diff_today' => 'Бүгін', + 'diff_today_regexp' => 'Бүгін(?:\\s+сағат)?', + 'diff_yesterday' => 'кеше', + 'diff_yesterday_regexp' => 'Кеше(?:\\s+сағат)?', + 'diff_tomorrow' => 'ертең', + 'diff_tomorrow_regexp' => 'Ертең(?:\\s+сағат)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Бүгін сағат] LT', + 'nextDay' => '[Ертең сағат] LT', + 'nextWeek' => 'dddd [сағат] LT', + 'lastDay' => '[Кеше сағат] LT', + 'lastWeek' => '[Өткен аптаның] dddd [сағат] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + static $suffixes = [ + 0 => '-ші', + 1 => '-ші', + 2 => '-ші', + 3 => '-ші', + 4 => '-ші', + 5 => '-ші', + 6 => '-шы', + 7 => '-ші', + 8 => '-ші', + 9 => '-шы', + 10 => '-шы', + 20 => '-шы', + 30 => '-шы', + 40 => '-шы', + 50 => '-ші', + 60 => '-шы', + 70 => '-ші', + 80 => '-ші', + 90 => '-шы', + 100 => '-ші', + ]; + + return $number.($suffixes[$number] ?? $suffixes[$number % 10] ?? $suffixes[$number >= 100 ? 100 : -1] ?? ''); + }, + 'months' => ['қаңтар', 'ақпан', 'наурыз', 'сәуір', 'мамыр', 'маусым', 'шілде', 'тамыз', 'қыркүйек', 'қазан', 'қараша', 'желтоқсан'], + 'months_short' => ['қаң', 'ақп', 'нау', 'сәу', 'мам', 'мау', 'шіл', 'там', 'қыр', 'қаз', 'қар', 'жел'], + 'weekdays' => ['жексенбі', 'дүйсенбі', 'сейсенбі', 'сәрсенбі', 'бейсенбі', 'жұма', 'сенбі'], + 'weekdays_short' => ['жек', 'дүй', 'сей', 'сәр', 'бей', 'жұм', 'сен'], + 'weekdays_min' => ['жк', 'дй', 'сй', 'ср', 'бй', 'жм', 'сн'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' және '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/kk_KZ.php b/vendor/nesbot/carbon/src/Carbon/Lang/kk_KZ.php new file mode 100644 index 0000000..7dc5ebc --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/kk_KZ.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/kk.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/kkj.php b/vendor/nesbot/carbon/src/Carbon/Lang/kkj.php new file mode 100644 index 0000000..6a1e77a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/kkj.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/kl.php b/vendor/nesbot/carbon/src/Carbon/Lang/kl.php new file mode 100644 index 0000000..7329a07 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/kl.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/kl_GL.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/kl_GL.php b/vendor/nesbot/carbon/src/Carbon/Lang/kl_GL.php new file mode 100644 index 0000000..4fed720 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/kl_GL.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Danish Standards Association bug-glibc-locales@gnu.org + * - John Eyðstein Johannesen (mashema) + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY HH:mm', + 'LLLL' => 'dddd [d.] D. MMMM YYYY [kl.] HH:mm', + ], + 'months' => ['januaarip', 'februaarip', 'marsip', 'apriilip', 'maajip', 'juunip', 'juulip', 'aggustip', 'septembarip', 'oktobarip', 'novembarip', 'decembarip'], + 'months_short' => ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'], + 'weekdays' => ['sapaat', 'ataasinngorneq', 'marlunngorneq', 'pingasunngorneq', 'sisamanngorneq', 'tallimanngorneq', 'arfininngorneq'], + 'weekdays_short' => ['sap', 'ata', 'mar', 'pin', 'sis', 'tal', 'arf'], + 'weekdays_min' => ['sap', 'ata', 'mar', 'pin', 'sis', 'tal', 'arf'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'year' => '{1}ukioq :count|{0}:count ukiut|]1,Inf[ukiut :count', + 'a_year' => '{1}ukioq|{0}:count ukiut|]1,Inf[ukiut :count', + 'y' => '{1}:countyr|{0}:countyrs|]1,Inf[:countyrs', + + 'month' => '{1}qaammat :count|{0}:count qaammatit|]1,Inf[qaammatit :count', + 'a_month' => '{1}qaammat|{0}:count qaammatit|]1,Inf[qaammatit :count', + 'm' => '{1}:countmo|{0}:countmos|]1,Inf[:countmos', + + 'week' => '{1}:count sap. ak.|{0}:count sap. ak.|]1,Inf[:count sap. ak.', + 'a_week' => '{1}a sap. ak.|{0}:count sap. ak.|]1,Inf[:count sap. ak.', + 'w' => ':countw', + + 'day' => '{1}:count ulloq|{0}:count ullut|]1,Inf[:count ullut', + 'a_day' => '{1}a ulloq|{0}:count ullut|]1,Inf[:count ullut', + 'd' => ':countd', + + 'hour' => '{1}:count tiimi|{0}:count tiimit|]1,Inf[:count tiimit', + 'a_hour' => '{1}tiimi|{0}:count tiimit|]1,Inf[:count tiimit', + 'h' => ':counth', + + 'minute' => '{1}:count minutsi|{0}:count minutsit|]1,Inf[:count minutsit', + 'a_minute' => '{1}a minutsi|{0}:count minutsit|]1,Inf[:count minutsit', + 'min' => ':countm', + + 'second' => '{1}:count sikunti|{0}:count sikuntit|]1,Inf[:count sikuntit', + 'a_second' => '{1}sikunti|{0}:count sikuntit|]1,Inf[:count sikuntit', + 's' => ':counts', + + 'ago' => ':time matuma siorna', + +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/kln.php b/vendor/nesbot/carbon/src/Carbon/Lang/kln.php new file mode 100644 index 0000000..b9c3996 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/kln.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['krn', 'koosk'], + 'weekdays' => ['Kotisap', 'Kotaai', 'Koaeng’', 'Kosomok', 'Koang’wan', 'Komuut', 'Kolo'], + 'weekdays_short' => ['Kts', 'Kot', 'Koo', 'Kos', 'Koa', 'Kom', 'Kol'], + 'weekdays_min' => ['Kts', 'Kot', 'Koo', 'Kos', 'Koa', 'Kom', 'Kol'], + 'months' => ['Mulgul', 'Ng’atyaato', 'Kiptaamo', 'Iwootkuut', 'Mamuut', 'Paagi', 'Ng’eiyeet', 'Rooptui', 'Bureet', 'Epeeso', 'Kipsuunde ne taai', 'Kipsuunde nebo aeng’'], + 'months_short' => ['Mul', 'Ngat', 'Taa', 'Iwo', 'Mam', 'Paa', 'Nge', 'Roo', 'Bur', 'Epe', 'Kpt', 'Kpa'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + + 'year' => ':count maghatiat', // less reliable + 'y' => ':count maghatiat', // less reliable + 'a_year' => ':count maghatiat', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/km.php b/vendor/nesbot/carbon/src/Carbon/Lang/km.php new file mode 100644 index 0000000..da790ac --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/km.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kruy Vanna + * - Sereysethy Touch + * - JD Isaacks + * - Sovichet Tep + */ +return [ + 'year' => '{1}មួយឆ្នាំ|]1,Inf[:count ឆ្នាំ', + 'y' => ':count ឆ្នាំ', + 'month' => '{1}មួយខែ|]1,Inf[:count ខែ', + 'm' => ':count ខែ', + 'week' => ':count សប្ដាហ៍', + 'w' => ':count សប្ដាហ៍', + 'day' => '{1}មួយថ្ងៃ|]1,Inf[:count ថ្ងៃ', + 'd' => ':count ថ្ងៃ', + 'hour' => '{1}មួយម៉ោង|]1,Inf[:count ម៉ោង', + 'h' => ':count ម៉ោង', + 'minute' => '{1}មួយនាទី|]1,Inf[:count នាទី', + 'min' => ':count នាទី', + 'second' => '{1}ប៉ុន្មានវិនាទី|]1,Inf[:count វិនាទី', + 's' => ':count វិនាទី', + 'ago' => ':timeមុន', + 'from_now' => ':timeទៀត', + 'after' => 'នៅ​ក្រោយ :time', + 'before' => 'នៅ​មុន :time', + 'diff_now' => 'ឥឡូវ', + 'diff_today' => 'ថ្ងៃនេះ', + 'diff_today_regexp' => 'ថ្ងៃនេះ(?:\\s+ម៉ោង)?', + 'diff_yesterday' => 'ម្សិលមិញ', + 'diff_yesterday_regexp' => 'ម្សិលមិញ(?:\\s+ម៉ោង)?', + 'diff_tomorrow' => 'ថ្ងៃ​ស្អែក', + 'diff_tomorrow_regexp' => 'ស្អែក(?:\\s+ម៉ោង)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[ថ្ងៃនេះ ម៉ោង] LT', + 'nextDay' => '[ស្អែក ម៉ោង] LT', + 'nextWeek' => 'dddd [ម៉ោង] LT', + 'lastDay' => '[ម្សិលមិញ ម៉ោង] LT', + 'lastWeek' => 'dddd [សប្តាហ៍មុន] [ម៉ោង] LT', + 'sameElse' => 'L', + ], + 'ordinal' => 'ទី:number', + 'meridiem' => ['ព្រឹក', 'ល្ងាច'], + 'months' => ['មករា', 'កុម្ភៈ', 'មីនា', 'មេសា', 'ឧសភា', 'មិថុនា', 'កក្កដា', 'សីហា', 'កញ្ញា', 'តុលា', 'វិច្ឆិកា', 'ធ្នូ'], + 'months_short' => ['មករា', 'កុម្ភៈ', 'មីនា', 'មេសា', 'ឧសភា', 'មិថុនា', 'កក្កដា', 'សីហា', 'កញ្ញា', 'តុលា', 'វិច្ឆិកា', 'ធ្នូ'], + 'weekdays' => ['អាទិត្យ', 'ច័ន្ទ', 'អង្គារ', 'ពុធ', 'ព្រហស្បតិ៍', 'សុក្រ', 'សៅរ៍'], + 'weekdays_short' => ['អា', 'ច', 'អ', 'ព', 'ព្រ', 'សុ', 'ស'], + 'weekdays_min' => ['អា', 'ច', 'អ', 'ព', 'ព្រ', 'សុ', 'ស'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', 'និង '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/km_KH.php b/vendor/nesbot/carbon/src/Carbon/Lang/km_KH.php new file mode 100644 index 0000000..92e5fdb --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/km_KH.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/km.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/kn.php b/vendor/nesbot/carbon/src/Carbon/Lang/kn.php new file mode 100644 index 0000000..0d2ad08 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/kn.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - MOHAN M U + * - François B + * - rajeevnaikte + */ +return [ + 'year' => '{1}ಒಂದು ವರ್ಷ|]1,Inf[:count ವರ್ಷ', + 'month' => '{1}ಒಂದು ತಿಂಗಳು|]1,Inf[:count ತಿಂಗಳು', + 'week' => '{1}ಒಂದು ವಾರ|]1,Inf[:count ವಾರಗಳು', + 'day' => '{1}ಒಂದು ದಿನ|]1,Inf[:count ದಿನ', + 'hour' => '{1}ಒಂದು ಗಂಟೆ|]1,Inf[:count ಗಂಟೆ', + 'minute' => '{1}ಒಂದು ನಿಮಿಷ|]1,Inf[:count ನಿಮಿಷ', + 'second' => '{1}ಕೆಲವು ಕ್ಷಣಗಳು|]1,Inf[:count ಸೆಕೆಂಡುಗಳು', + 'ago' => ':time ಹಿಂದೆ', + 'from_now' => ':time ನಂತರ', + 'diff_now' => 'ಈಗ', + 'diff_today' => 'ಇಂದು', + 'diff_yesterday' => 'ನಿನ್ನೆ', + 'diff_tomorrow' => 'ನಾಳೆ', + 'formats' => [ + 'LT' => 'A h:mm', + 'LTS' => 'A h:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, A h:mm', + 'LLLL' => 'dddd, D MMMM YYYY, A h:mm', + ], + 'calendar' => [ + 'sameDay' => '[ಇಂದು] LT', + 'nextDay' => '[ನಾಳೆ] LT', + 'nextWeek' => 'dddd, LT', + 'lastDay' => '[ನಿನ್ನೆ] LT', + 'lastWeek' => '[ಕೊನೆಯ] dddd, LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':numberನೇ', + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'ರಾತ್ರಿ'; + } + if ($hour < 10) { + return 'ಬೆಳಿಗ್ಗೆ'; + } + if ($hour < 17) { + return 'ಮಧ್ಯಾಹ್ನ'; + } + if ($hour < 20) { + return 'ಸಂಜೆ'; + } + + return 'ರಾತ್ರಿ'; + }, + 'months' => ['ಜನವರಿ', 'ಫೆಬ್ರವರಿ', 'ಮಾರ್ಚ್', 'ಏಪ್ರಿಲ್', 'ಮೇ', 'ಜೂನ್', 'ಜುಲೈ', 'ಆಗಸ್ಟ್', 'ಸೆಪ್ಟೆಂಬರ್', 'ಅಕ್ಟೋಬರ್', 'ನವೆಂಬರ್', 'ಡಿಸೆಂಬರ್'], + 'months_short' => ['ಜನ', 'ಫೆಬ್ರ', 'ಮಾರ್ಚ್', 'ಏಪ್ರಿಲ್', 'ಮೇ', 'ಜೂನ್', 'ಜುಲೈ', 'ಆಗಸ್ಟ್', 'ಸೆಪ್ಟೆಂ', 'ಅಕ್ಟೋ', 'ನವೆಂ', 'ಡಿಸೆಂ'], + 'weekdays' => ['ಭಾನುವಾರ', 'ಸೋಮವಾರ', 'ಮಂಗಳವಾರ', 'ಬುಧವಾರ', 'ಗುರುವಾರ', 'ಶುಕ್ರವಾರ', 'ಶನಿವಾರ'], + 'weekdays_short' => ['ಭಾನು', 'ಸೋಮ', 'ಮಂಗಳ', 'ಬುಧ', 'ಗುರು', 'ಶುಕ್ರ', 'ಶನಿ'], + 'weekdays_min' => ['ಭಾ', 'ಸೋ', 'ಮಂ', 'ಬು', 'ಗು', 'ಶು', 'ಶ'], + 'list' => ', ', + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'weekend' => [0, 0], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/kn_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/kn_IN.php new file mode 100644 index 0000000..30e3d88 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/kn_IN.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/kn.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ko.php b/vendor/nesbot/carbon/src/Carbon/Lang/ko.php new file mode 100644 index 0000000..4fa6237 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ko.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kunal Marwaha + * - FourwingsY + * - François B + * - Jason Katz-Brown + * - Seokjun Kim + * - Junho Kim + * - JD Isaacks + * - Juwon Kim + */ +return [ + 'year' => ':count년', + 'a_year' => '{1}일년|]1,Inf[:count년', + 'y' => ':count년', + 'month' => ':count개월', + 'a_month' => '{1}한달|]1,Inf[:count개월', + 'm' => ':count개월', + 'week' => ':count주', + 'a_week' => '{1}일주일|]1,Inf[:count 주', + 'w' => ':count주일', + 'day' => ':count일', + 'a_day' => '{1}하루|]1,Inf[:count일', + 'd' => ':count일', + 'hour' => ':count시간', + 'a_hour' => '{1}한시간|]1,Inf[:count시간', + 'h' => ':count시간', + 'minute' => ':count분', + 'a_minute' => '{1}일분|]1,Inf[:count분', + 'min' => ':count분', + 'second' => ':count초', + 'a_second' => '{1}몇초|]1,Inf[:count초', + 's' => ':count초', + 'ago' => ':time 전', + 'from_now' => ':time 후', + 'after' => ':time 후', + 'before' => ':time 전', + 'diff_now' => '지금', + 'diff_today' => '오늘', + 'diff_yesterday' => '어제', + 'diff_tomorrow' => '내일', + 'formats' => [ + 'LT' => 'A h:mm', + 'LTS' => 'A h:mm:ss', + 'L' => 'YYYY.MM.DD.', + 'LL' => 'YYYY년 MMMM D일', + 'LLL' => 'YYYY년 MMMM D일 A h:mm', + 'LLLL' => 'YYYY년 MMMM D일 dddd A h:mm', + ], + 'calendar' => [ + 'sameDay' => '오늘 LT', + 'nextDay' => '내일 LT', + 'nextWeek' => 'dddd LT', + 'lastDay' => '어제 LT', + 'lastWeek' => '지난주 dddd LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + switch ($period) { + case 'd': + case 'D': + case 'DDD': + return $number.'일'; + case 'M': + return $number.'월'; + case 'w': + case 'W': + return $number.'주'; + default: + return $number; + } + }, + 'meridiem' => ['오전', '오후'], + 'months' => ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'], + 'months_short' => ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'], + 'weekdays' => ['일요일', '월요일', '화요일', '수요일', '목요일', '금요일', '토요일'], + 'weekdays_short' => ['일', '월', '화', '수', '목', '금', '토'], + 'weekdays_min' => ['일', '월', '화', '수', '목', '금', '토'], + 'list' => ' ', +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ko_KP.php b/vendor/nesbot/carbon/src/Carbon/Lang/ko_KP.php new file mode 100644 index 0000000..4ba802b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ko_KP.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ko.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ko_KR.php b/vendor/nesbot/carbon/src/Carbon/Lang/ko_KR.php new file mode 100644 index 0000000..9d873a2 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ko_KR.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ko.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/kok.php b/vendor/nesbot/carbon/src/Carbon/Lang/kok.php new file mode 100644 index 0000000..4adcddc --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/kok.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/kok_IN.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/kok_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/kok_IN.php new file mode 100644 index 0000000..92ba844 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/kok_IN.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Red Hat, Pune bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D-M-YY', + ], + 'months' => ['जानेवारी', 'फेब्रुवारी', 'मार्च', 'एप्रिल', 'मे', 'जून', 'जुलै', 'ओगस्ट', 'सेप्टेंबर', 'ओक्टोबर', 'नोव्हेंबर', 'डिसेंबर'], + 'months_short' => ['जानेवारी', 'फेब्रुवारी', 'मार्च', 'एप्रिल', 'मे', 'जून', 'जुलै', 'ओगस्ट', 'सेप्टेंबर', 'ओक्टोबर', 'नोव्हेंबर', 'डिसेंबर'], + 'weekdays' => ['आयतार', 'सोमार', 'मंगळवार', 'बुधवार', 'बेरेसतार', 'शुकरार', 'शेनवार'], + 'weekdays_short' => ['आयतार', 'सोमार', 'मंगळवार', 'बुधवार', 'बेरेसतार', 'शुकरार', 'शेनवार'], + 'weekdays_min' => ['आयतार', 'सोमार', 'मंगळवार', 'बुधवार', 'बेरेसतार', 'शुकरार', 'शेनवार'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['म.पू.', 'म.नं.'], + + 'year' => ':count वैशाकु', // less reliable + 'y' => ':count वैशाकु', // less reliable + 'a_year' => ':count वैशाकु', // less reliable + + 'week' => ':count आदित्यवार', // less reliable + 'w' => ':count आदित्यवार', // less reliable + 'a_week' => ':count आदित्यवार', // less reliable + + 'minute' => ':count नोंद', // less reliable + 'min' => ':count नोंद', // less reliable + 'a_minute' => ':count नोंद', // less reliable + + 'second' => ':count तेंको', // less reliable + 's' => ':count तेंको', // less reliable + 'a_second' => ':count तेंको', // less reliable + + 'month' => ':count मैनो', + 'm' => ':count मैनो', + 'a_month' => ':count मैनो', + + 'day' => ':count दिवसु', + 'd' => ':count दिवसु', + 'a_day' => ':count दिवसु', + + 'hour' => ':count घंते', + 'h' => ':count घंते', + 'a_hour' => ':count घंते', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ks.php b/vendor/nesbot/carbon/src/Carbon/Lang/ks.php new file mode 100644 index 0000000..9876079 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ks.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ks_IN.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ks_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/ks_IN.php new file mode 100644 index 0000000..ce9d5d4 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ks_IN.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Red Hat, Pune bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'M/D/YY', + ], + 'months' => ['جنؤری', 'فرؤری', 'مارٕچ', 'اپریل', 'میٔ', 'جوٗن', 'جوٗلایی', 'اگست', 'ستمبر', 'اکتوٗبر', 'نومبر', 'دسمبر'], + 'months_short' => ['جنؤری', 'فرؤری', 'مارٕچ', 'اپریل', 'میٔ', 'جوٗن', 'جوٗلایی', 'اگست', 'ستمبر', 'اکتوٗبر', 'نومبر', 'دسمبر'], + 'weekdays' => ['آتهوار', 'ژءندروار', 'بوءںوار', 'بودهوار', 'برىسوار', 'جمع', 'بٹوار'], + 'weekdays_short' => ['آتهوار', 'ژءنتروار', 'بوءںوار', 'بودهوار', 'برىسوار', 'جمع', 'بٹوار'], + 'weekdays_min' => ['آتهوار', 'ژءنتروار', 'بوءںوار', 'بودهوار', 'برىسوار', 'جمع', 'بٹوار'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['دوپھربرونھ', 'دوپھرپتھ'], + + 'year' => ':count آب', // less reliable + 'y' => ':count آب', // less reliable + 'a_year' => ':count آب', // less reliable + + 'month' => ':count रान्', // less reliable + 'm' => ':count रान्', // less reliable + 'a_month' => ':count रान्', // less reliable + + 'week' => ':count آتھٕوار', // less reliable + 'w' => ':count آتھٕوار', // less reliable + 'a_week' => ':count آتھٕوار', // less reliable + + 'hour' => ':count سۄن', // less reliable + 'h' => ':count سۄن', // less reliable + 'a_hour' => ':count سۄن', // less reliable + + 'minute' => ':count فَن', // less reliable + 'min' => ':count فَن', // less reliable + 'a_minute' => ':count فَن', // less reliable + + 'second' => ':count दोʼयुम', // less reliable + 's' => ':count दोʼयुम', // less reliable + 'a_second' => ':count दोʼयुम', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ks_IN@devanagari.php b/vendor/nesbot/carbon/src/Carbon/Lang/ks_IN@devanagari.php new file mode 100644 index 0000000..a2ae8b6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ks_IN@devanagari.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - ks-gnome-trans-commits@lists.code.indlinux.net + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'M/D/YY', + ], + 'months' => ['जनवरी', 'फ़रवरी', 'मार्च', 'अप्रेल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितम्बर', 'अक्टूबर', 'नवम्बर', 'दिसम्बर'], + 'months_short' => ['जनवरी', 'फ़रवरी', 'मार्च', 'अप्रेल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितम्बर', 'अक्टूबर', 'नवम्बर', 'दिसम्बर'], + 'weekdays' => ['आथवार', 'चॅ़दुरवार', 'बोमवार', 'ब्वदवार', 'ब्रसवार', 'शोकुरवार', 'बटुवार'], + 'weekdays_short' => ['आथ ', 'चॅ़दुर', 'बोम', 'ब्वद', 'ब्रस', 'शोकुर', 'बटु'], + 'weekdays_min' => ['आथ ', 'चॅ़दुर', 'बोम', 'ब्वद', 'ब्रस', 'शोकुर', 'बटु'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['पूर्वाह्न', 'अपराह्न'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ksb.php b/vendor/nesbot/carbon/src/Carbon/Lang/ksb.php new file mode 100644 index 0000000..aaa0061 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ksb.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['makeo', 'nyiaghuo'], + 'weekdays' => ['Jumaapii', 'Jumaatatu', 'Jumaane', 'Jumaatano', 'Alhamisi', 'Ijumaa', 'Jumaamosi'], + 'weekdays_short' => ['Jpi', 'Jtt', 'Jmn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'weekdays_min' => ['Jpi', 'Jtt', 'Jmn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'months' => ['Januali', 'Febluali', 'Machi', 'Aplili', 'Mei', 'Juni', 'Julai', 'Agosti', 'Septemba', 'Oktoba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Sep', 'Okt', 'Nov', 'Des'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ksf.php b/vendor/nesbot/carbon/src/Carbon/Lang/ksf.php new file mode 100644 index 0000000..84a5967 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ksf.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['sárúwá', 'cɛɛ́nko'], + 'weekdays' => ['sɔ́ndǝ', 'lǝndí', 'maadí', 'mɛkrɛdí', 'jǝǝdí', 'júmbá', 'samdí'], + 'weekdays_short' => ['sɔ́n', 'lǝn', 'maa', 'mɛk', 'jǝǝ', 'júm', 'sam'], + 'weekdays_min' => ['sɔ́n', 'lǝn', 'maa', 'mɛk', 'jǝǝ', 'júm', 'sam'], + 'months' => ['ŋwíí a ntɔ́ntɔ', 'ŋwíí akǝ bɛ́ɛ', 'ŋwíí akǝ ráá', 'ŋwíí akǝ nin', 'ŋwíí akǝ táan', 'ŋwíí akǝ táafɔk', 'ŋwíí akǝ táabɛɛ', 'ŋwíí akǝ táaraa', 'ŋwíí akǝ táanin', 'ŋwíí akǝ ntɛk', 'ŋwíí akǝ ntɛk di bɔ́k', 'ŋwíí akǝ ntɛk di bɛ́ɛ'], + 'months_short' => ['ŋ1', 'ŋ2', 'ŋ3', 'ŋ4', 'ŋ5', 'ŋ6', 'ŋ7', 'ŋ8', 'ŋ9', 'ŋ10', 'ŋ11', 'ŋ12'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ksh.php b/vendor/nesbot/carbon/src/Carbon/Lang/ksh.php new file mode 100644 index 0000000..95457e2 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ksh.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['v.M.', 'n.M.'], + 'weekdays' => ['Sunndaach', 'Mohndaach', 'Dinnsdaach', 'Metwoch', 'Dunnersdaach', 'Friidaach', 'Samsdaach'], + 'weekdays_short' => ['Su.', 'Mo.', 'Di.', 'Me.', 'Du.', 'Fr.', 'Sa.'], + 'weekdays_min' => ['Su', 'Mo', 'Di', 'Me', 'Du', 'Fr', 'Sa'], + 'months' => ['Jannewa', 'Fäbrowa', 'Määz', 'Aprell', 'Mai', 'Juuni', 'Juuli', 'Oujoß', 'Septämber', 'Oktohber', 'Novämber', 'Dezämber'], + 'months_short' => ['Jan', 'Fäb', 'Mäz', 'Apr', 'Mai', 'Jun', 'Jul', 'Ouj', 'Säp', 'Okt', 'Nov', 'Dez'], + 'months_short_standalone' => ['Jan.', 'Fäb.', 'Mäz.', 'Apr.', 'Mai', 'Jun.', 'Jul.', 'Ouj.', 'Säp.', 'Okt.', 'Nov.', 'Dez.'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D. M. YYYY', + 'LL' => 'D. MMM. YYYY', + 'LLL' => 'D. MMMM YYYY HH:mm', + 'LLLL' => 'dddd, [dä] D. MMMM YYYY HH:mm', + ], + + 'year' => ':count Johr', + 'y' => ':count Johr', + 'a_year' => ':count Johr', + + 'month' => ':count Moohnd', + 'm' => ':count Moohnd', + 'a_month' => ':count Moohnd', + + 'week' => ':count woch', + 'w' => ':count woch', + 'a_week' => ':count woch', + + 'day' => ':count Daach', + 'd' => ':count Daach', + 'a_day' => ':count Daach', + + 'hour' => ':count Uhr', + 'h' => ':count Uhr', + 'a_hour' => ':count Uhr', + + 'minute' => ':count Menutt', + 'min' => ':count Menutt', + 'a_minute' => ':count Menutt', + + 'second' => ':count Sekůndt', + 's' => ':count Sekůndt', + 'a_second' => ':count Sekůndt', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ku.php b/vendor/nesbot/carbon/src/Carbon/Lang/ku.php new file mode 100644 index 0000000..b001e30 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ku.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Halwest Manguri + * - Kardo Qadir + */ +$months = ['کانونی دووەم', 'شوبات', 'ئازار', 'نیسان', 'ئایار', '‌حوزەیران', 'تەمموز', 'ئاب', 'ئەیلول', 'تشرینی یەکەم', 'تشرینی دووەم', 'کانونی یەکەم']; + +$weekdays = ['دوو شەممە', 'سێ شەممە', 'چوار شەممە', 'پێنج شەممە', 'هەینی', 'شەممە', 'یەک شەممە']; + +return [ + 'ago' => 'پێش :time', + 'from_now' => ':time لە ئێستاوە', + 'after' => 'دوای :time', + 'before' => 'پێش :time', + 'year' => '{0}ساڵ|{1}ساڵێک|{2}٢ ساڵ|[3,10]:count ساڵ|[11,Inf]:count ساڵ', + 'month' => '{0}مانگ|{1}مانگێک|{2}٢ مانگ|[3,10]:count مانگ|[11,Inf]:count مانگ', + 'week' => '{0}هەفتە|{1}هەفتەیەک|{2}٢ هەفتە|[3,10]:count هەفتە|[11,Inf]:count هەفتە', + 'day' => '{0}ڕۆژ|{1}ڕۆژێک|{2}٢ ڕۆژ|[3,10]:count ڕۆژ|[11,Inf]:count ڕۆژ', + 'hour' => '{0}کاتژمێر|{1}کاتژمێرێک|{2}٢ کاتژمێر|[3,10]:count کاتژمێر|[11,Inf]:count کاتژمێر', + 'minute' => '{0}خولەک|{1}خولەکێک|{2}٢ خولەک|[3,10]:count خولەک|[11,Inf]:count خولەک', + 'second' => '{0}چرکە|{1}چرکەیەک|{2}٢ چرکە|[3,10]:count چرکە|[11,Inf]:count چرکە', + 'months' => $months, + 'months_standalone' => $months, + 'months_short' => $months, + 'weekdays' => $weekdays, + 'weekdays_short' => $weekdays, + 'weekdays_min' => $weekdays, + 'list' => [', ', ' û '], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ku_TR.php b/vendor/nesbot/carbon/src/Carbon/Lang/ku_TR.php new file mode 100644 index 0000000..4243a82 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ku_TR.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ku.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/kw.php b/vendor/nesbot/carbon/src/Carbon/Lang/kw.php new file mode 100644 index 0000000..26e242e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/kw.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/kw_GB.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/kw_GB.php b/vendor/nesbot/carbon/src/Carbon/Lang/kw_GB.php new file mode 100644 index 0000000..00bf52b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/kw_GB.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Alastair McKinstry bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['mis Genver', 'mis Hwevrer', 'mis Meurth', 'mis Ebrel', 'mis Me', 'mis Metheven', 'mis Gortheren', 'mis Est', 'mis Gwynngala', 'mis Hedra', 'mis Du', 'mis Kevardhu'], + 'months_short' => ['Gen', 'Hwe', 'Meu', 'Ebr', 'Me', 'Met', 'Gor', 'Est', 'Gwn', 'Hed', 'Du', 'Kev'], + 'weekdays' => ['De Sul', 'De Lun', 'De Merth', 'De Merher', 'De Yow', 'De Gwener', 'De Sadorn'], + 'weekdays_short' => ['Sul', 'Lun', 'Mth', 'Mhr', 'Yow', 'Gwe', 'Sad'], + 'weekdays_min' => ['Sul', 'Lun', 'Mth', 'Mhr', 'Yow', 'Gwe', 'Sad'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'year' => ':count bledhen', + 'y' => ':count bledhen', + 'a_year' => ':count bledhen', + + 'month' => ':count mis', + 'm' => ':count mis', + 'a_month' => ':count mis', + + 'week' => ':count seythen', + 'w' => ':count seythen', + 'a_week' => ':count seythen', + + 'day' => ':count dydh', + 'd' => ':count dydh', + 'a_day' => ':count dydh', + + 'hour' => ':count eur', + 'h' => ':count eur', + 'a_hour' => ':count eur', + + 'minute' => ':count mynysen', + 'min' => ':count mynysen', + 'a_minute' => ':count mynysen', + + 'second' => ':count pryjwyth', + 's' => ':count pryjwyth', + 'a_second' => ':count pryjwyth', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ky.php b/vendor/nesbot/carbon/src/Carbon/Lang/ky.php new file mode 100644 index 0000000..e0d1af1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ky.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - acutexyz + * - Josh Soref + * - François B + * - Chyngyz Arystan uulu + * - Chyngyz + * - acutexyz + * - Josh Soref + * - François B + * - Chyngyz Arystan uulu + */ +return [ + 'year' => ':count жыл', + 'a_year' => '{1}бир жыл|:count жыл', + 'y' => ':count жыл', + 'month' => ':count ай', + 'a_month' => '{1}бир ай|:count ай', + 'm' => ':count ай', + 'week' => ':count апта', + 'a_week' => '{1}бир апта|:count апта', + 'w' => ':count апт.', + 'day' => ':count күн', + 'a_day' => '{1}бир күн|:count күн', + 'd' => ':count күн', + 'hour' => ':count саат', + 'a_hour' => '{1}бир саат|:count саат', + 'h' => ':count саат.', + 'minute' => ':count мүнөт', + 'a_minute' => '{1}бир мүнөт|:count мүнөт', + 'min' => ':count мүн.', + 'second' => ':count секунд', + 'a_second' => '{1}бирнече секунд|:count секунд', + 's' => ':count сек.', + 'ago' => ':time мурун', + 'from_now' => ':time ичинде', + 'diff_now' => 'азыр', + 'diff_today' => 'Бүгүн', + 'diff_today_regexp' => 'Бүгүн(?:\\s+саат)?', + 'diff_yesterday' => 'кечээ', + 'diff_yesterday_regexp' => 'Кече(?:\\s+саат)?', + 'diff_tomorrow' => 'эртең', + 'diff_tomorrow_regexp' => 'Эртең(?:\\s+саат)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Бүгүн саат] LT', + 'nextDay' => '[Эртең саат] LT', + 'nextWeek' => 'dddd [саат] LT', + 'lastDay' => '[Кече саат] LT', + 'lastWeek' => '[Өткен аптанын] dddd [күнү] [саат] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + static $suffixes = [ + 0 => '-чү', + 1 => '-чи', + 2 => '-чи', + 3 => '-чү', + 4 => '-чү', + 5 => '-чи', + 6 => '-чы', + 7 => '-чи', + 8 => '-чи', + 9 => '-чу', + 10 => '-чу', + 20 => '-чы', + 30 => '-чу', + 40 => '-чы', + 50 => '-чү', + 60 => '-чы', + 70 => '-чи', + 80 => '-чи', + 90 => '-чу', + 100 => '-чү', + ]; + + return $number.($suffixes[$number] ?? $suffixes[$number % 10] ?? $suffixes[$number >= 100 ? 100 : -1] ?? ''); + }, + 'months' => ['январь', 'февраль', 'март', 'апрель', 'май', 'июнь', 'июль', 'август', 'сентябрь', 'октябрь', 'ноябрь', 'декабрь'], + 'months_short' => ['янв', 'фев', 'март', 'апр', 'май', 'июнь', 'июль', 'авг', 'сен', 'окт', 'ноя', 'дек'], + 'weekdays' => ['Жекшемби', 'Дүйшөмбү', 'Шейшемби', 'Шаршемби', 'Бейшемби', 'Жума', 'Ишемби'], + 'weekdays_short' => ['Жек', 'Дүй', 'Шей', 'Шар', 'Бей', 'Жум', 'Ише'], + 'weekdays_min' => ['Жк', 'Дй', 'Шй', 'Шр', 'Бй', 'Жм', 'Иш'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => ' ', + 'meridiem' => ['таңкы', 'түштөн кийинки'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ky_KG.php b/vendor/nesbot/carbon/src/Carbon/Lang/ky_KG.php new file mode 100644 index 0000000..9923a31 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ky_KG.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ky.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/lag.php b/vendor/nesbot/carbon/src/Carbon/Lang/lag.php new file mode 100644 index 0000000..f3f57f6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/lag.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['TOO', 'MUU'], + 'weekdays' => ['Jumapíiri', 'Jumatátu', 'Jumaíne', 'Jumatáano', 'Alamíisi', 'Ijumáa', 'Jumamóosi'], + 'weekdays_short' => ['Píili', 'Táatu', 'Íne', 'Táano', 'Alh', 'Ijm', 'Móosi'], + 'weekdays_min' => ['Píili', 'Táatu', 'Íne', 'Táano', 'Alh', 'Ijm', 'Móosi'], + 'months' => ['Kʉfúngatɨ', 'Kʉnaanɨ', 'Kʉkeenda', 'Kwiikumi', 'Kwiinyambála', 'Kwiidwaata', 'Kʉmʉʉnchɨ', 'Kʉvɨɨrɨ', 'Kʉsaatʉ', 'Kwiinyi', 'Kʉsaano', 'Kʉsasatʉ'], + 'months_short' => ['Fúngatɨ', 'Naanɨ', 'Keenda', 'Ikúmi', 'Inyambala', 'Idwaata', 'Mʉʉnchɨ', 'Vɨɨrɨ', 'Saatʉ', 'Inyi', 'Saano', 'Sasatʉ'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/lb.php b/vendor/nesbot/carbon/src/Carbon/Lang/lb.php new file mode 100644 index 0000000..7636655 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/lb.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - Tsutomu Kuroda + * - dan-nl + * - Simon Lelorrain (slelorrain) + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count Joer', + 'y' => ':countJ', + 'month' => ':count Mount|:count Méint', + 'm' => ':countMo', + 'week' => ':count Woch|:count Wochen', + 'w' => ':countWo|:countWo', + 'day' => ':count Dag|:count Deeg', + 'd' => ':countD', + 'hour' => ':count Stonn|:count Stonnen', + 'h' => ':countSto', + 'minute' => ':count Minutt|:count Minutten', + 'min' => ':countM', + 'second' => ':count Sekonn|:count Sekonnen', + 's' => ':countSek', + + 'ago' => 'virun :time', + 'from_now' => 'an :time', + 'before' => ':time virdrun', + 'after' => ':time duerno', + + 'diff_today' => 'Haut', + 'diff_yesterday' => 'Gëschter', + 'diff_yesterday_regexp' => 'Gëschter(?:\\s+um)?', + 'diff_tomorrow' => 'Muer', + 'diff_tomorrow_regexp' => 'Muer(?:\\s+um)?', + 'diff_today_regexp' => 'Haut(?:\\s+um)?', + 'formats' => [ + 'LT' => 'H:mm [Auer]', + 'LTS' => 'H:mm:ss [Auer]', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY H:mm [Auer]', + 'LLLL' => 'dddd, D. MMMM YYYY H:mm [Auer]', + ], + + 'calendar' => [ + 'sameDay' => '[Haut um] LT', + 'nextDay' => '[Muer um] LT', + 'nextWeek' => 'dddd [um] LT', + 'lastDay' => '[Gëschter um] LT', + 'lastWeek' => function (CarbonInterface $date) { + // Different date string for 'Dënschdeg' (Tuesday) and 'Donneschdeg' (Thursday) due to phonological rule + switch ($date->dayOfWeek) { + case 2: + case 4: + return '[Leschten] dddd [um] LT'; + default: + return '[Leschte] dddd [um] LT'; + } + }, + 'sameElse' => 'L', + ], + + 'months' => ['Januar', 'Februar', 'Mäerz', 'Abrëll', 'Mee', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'], + 'months_short' => ['Jan.', 'Febr.', 'Mrz.', 'Abr.', 'Mee', 'Jun.', 'Jul.', 'Aug.', 'Sept.', 'Okt.', 'Nov.', 'Dez.'], + 'weekdays' => ['Sonndeg', 'Méindeg', 'Dënschdeg', 'Mëttwoch', 'Donneschdeg', 'Freideg', 'Samschdeg'], + 'weekdays_short' => ['So.', 'Mé.', 'Dë.', 'Më.', 'Do.', 'Fr.', 'Sa.'], + 'weekdays_min' => ['So', 'Mé', 'Dë', 'Më', 'Do', 'Fr', 'Sa'], + 'ordinal' => ':number.', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' an '], + 'meridiem' => ['moies', 'mëttes'], + 'weekdays_short_standalone' => ['Son', 'Méi', 'Dën', 'Mët', 'Don', 'Fre', 'Sam'], + 'months_short_standalone' => ['Jan', 'Feb', 'Mäe', 'Abr', 'Mee', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/lb_LU.php b/vendor/nesbot/carbon/src/Carbon/Lang/lb_LU.php new file mode 100644 index 0000000..414bd4d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/lb_LU.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/lb.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/lg.php b/vendor/nesbot/carbon/src/Carbon/Lang/lg.php new file mode 100644 index 0000000..48bc68b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/lg.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/lg_UG.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/lg_UG.php b/vendor/nesbot/carbon/src/Carbon/Lang/lg_UG.php new file mode 100644 index 0000000..aa02214 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/lg_UG.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Akademe ya Luganda Kizito Birabwa kompyuta@kizito.uklinux.net + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['Janwaliyo', 'Febwaliyo', 'Marisi', 'Apuli', 'Maayi', 'Juuni', 'Julaayi', 'Agusito', 'Sebuttemba', 'Okitobba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apu', 'Maa', 'Juu', 'Jul', 'Agu', 'Seb', 'Oki', 'Nov', 'Des'], + 'weekdays' => ['Sabiiti', 'Balaza', 'Lwakubiri', 'Lwakusatu', 'Lwakuna', 'Lwakutaano', 'Lwamukaaga'], + 'weekdays_short' => ['Sab', 'Bal', 'Lw2', 'Lw3', 'Lw4', 'Lw5', 'Lw6'], + 'weekdays_min' => ['Sab', 'Bal', 'Lw2', 'Lw3', 'Lw4', 'Lw5', 'Lw6'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'month' => ':count njuba', // less reliable + 'm' => ':count njuba', // less reliable + 'a_month' => ':count njuba', // less reliable + + 'year' => ':count mwaaka', + 'y' => ':count mwaaka', + 'a_year' => ':count mwaaka', + + 'week' => ':count sabbiiti', + 'w' => ':count sabbiiti', + 'a_week' => ':count sabbiiti', + + 'day' => ':count lunaku', + 'd' => ':count lunaku', + 'a_day' => ':count lunaku', + + 'hour' => 'saawa :count', + 'h' => 'saawa :count', + 'a_hour' => 'saawa :count', + + 'minute' => 'ddakiika :count', + 'min' => 'ddakiika :count', + 'a_minute' => 'ddakiika :count', + + 'second' => ':count kyʼokubiri', + 's' => ':count kyʼokubiri', + 'a_second' => ':count kyʼokubiri', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/li.php b/vendor/nesbot/carbon/src/Carbon/Lang/li.php new file mode 100644 index 0000000..86c3009 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/li.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/li_NL.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/li_NL.php b/vendor/nesbot/carbon/src/Carbon/Lang/li_NL.php new file mode 100644 index 0000000..6c5feb7 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/li_NL.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - information from Kenneth Christiansen Kenneth Christiansen, Pablo Saratxaga kenneth@gnu.org, pablo@mandriva.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['jannewarie', 'fibberwarie', 'miert', 'eprèl', 'meij', 'junie', 'julie', 'augustus', 'september', 'oktober', 'november', 'desember'], + 'months_short' => ['jan', 'fib', 'mie', 'epr', 'mei', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'des'], + 'weekdays' => ['zóndig', 'maondig', 'daensdig', 'goonsdig', 'dónderdig', 'vriedig', 'zaoterdig'], + 'weekdays_short' => ['zón', 'mao', 'dae', 'goo', 'dón', 'vri', 'zao'], + 'weekdays_min' => ['zón', 'mao', 'dae', 'goo', 'dón', 'vri', 'zao'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'minute' => ':count momênt', // less reliable + 'min' => ':count momênt', // less reliable + 'a_minute' => ':count momênt', // less reliable + + 'year' => ':count jaor', + 'y' => ':count jaor', + 'a_year' => ':count jaor', + + 'month' => ':count maond', + 'm' => ':count maond', + 'a_month' => ':count maond', + + 'week' => ':count waek', + 'w' => ':count waek', + 'a_week' => ':count waek', + + 'day' => ':count daag', + 'd' => ':count daag', + 'a_day' => ':count daag', + + 'hour' => ':count oer', + 'h' => ':count oer', + 'a_hour' => ':count oer', + + 'second' => ':count Secónd', + 's' => ':count Secónd', + 'a_second' => ':count Secónd', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/lij.php b/vendor/nesbot/carbon/src/Carbon/Lang/lij.php new file mode 100644 index 0000000..45732b5 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/lij.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/lij_IT.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/lij_IT.php b/vendor/nesbot/carbon/src/Carbon/Lang/lij_IT.php new file mode 100644 index 0000000..f8726fd --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/lij_IT.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Gastaldi alessio.gastaldi@libero.it + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['zenâ', 'fevrâ', 'marzo', 'avrî', 'mazzo', 'zûgno', 'lûggio', 'agosto', 'settembre', 'ottobre', 'novembre', 'dixembre'], + 'months_short' => ['zen', 'fev', 'mar', 'arv', 'maz', 'zûg', 'lûg', 'ago', 'set', 'ött', 'nov', 'dix'], + 'weekdays' => ['domenega', 'lûnedì', 'martedì', 'mercUrdì', 'zêggia', 'venardì', 'sabbo'], + 'weekdays_short' => ['dom', 'lûn', 'mar', 'mer', 'zêu', 'ven', 'sab'], + 'weekdays_min' => ['dom', 'lûn', 'mar', 'mer', 'zêu', 'ven', 'sab'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'year' => ':count etæ', // less reliable + 'y' => ':count etæ', // less reliable + 'a_year' => ':count etæ', // less reliable + + 'month' => ':count meize', + 'm' => ':count meize', + 'a_month' => ':count meize', + + 'week' => ':count settemannha', + 'w' => ':count settemannha', + 'a_week' => ':count settemannha', + + 'day' => ':count giorno', + 'd' => ':count giorno', + 'a_day' => ':count giorno', + + 'hour' => ':count reléuio', // less reliable + 'h' => ':count reléuio', // less reliable + 'a_hour' => ':count reléuio', // less reliable + + 'minute' => ':count menûo', + 'min' => ':count menûo', + 'a_minute' => ':count menûo', + + 'second' => ':count segondo', + 's' => ':count segondo', + 'a_second' => ':count segondo', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/lkt.php b/vendor/nesbot/carbon/src/Carbon/Lang/lkt.php new file mode 100644 index 0000000..ae73a97 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/lkt.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + + 'month' => ':count haŋwí', // less reliable + 'm' => ':count haŋwí', // less reliable + 'a_month' => ':count haŋwí', // less reliable + + 'week' => ':count šakówiŋ', // less reliable + 'w' => ':count šakówiŋ', // less reliable + 'a_week' => ':count šakówiŋ', // less reliable + + 'hour' => ':count maza škaŋškaŋ', // less reliable + 'h' => ':count maza škaŋškaŋ', // less reliable + 'a_hour' => ':count maza škaŋškaŋ', // less reliable + + 'minute' => ':count číkʼala', // less reliable + 'min' => ':count číkʼala', // less reliable + 'a_minute' => ':count číkʼala', // less reliable + + 'year' => ':count waníyetu', + 'y' => ':count waníyetu', + 'a_year' => ':count waníyetu', + + 'day' => ':count aŋpétu', + 'd' => ':count aŋpétu', + 'a_day' => ':count aŋpétu', + + 'second' => ':count icinuŋpa', + 's' => ':count icinuŋpa', + 'a_second' => ':count icinuŋpa', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ln.php b/vendor/nesbot/carbon/src/Carbon/Lang/ln.php new file mode 100644 index 0000000..9d5c35d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ln.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ubuntu René Manassé GALEKWA renemanasse@gmail.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'months' => ['sánzá ya yambo', 'sánzá ya míbalé', 'sánzá ya mísáto', 'sánzá ya mínei', 'sánzá ya mítáno', 'sánzá ya motóbá', 'sánzá ya nsambo', 'sánzá ya mwambe', 'sánzá ya libwa', 'sánzá ya zómi', 'sánzá ya zómi na mɔ̌kɔ́', 'sánzá ya zómi na míbalé'], + 'months_short' => ['yan', 'fbl', 'msi', 'apl', 'mai', 'yun', 'yul', 'agt', 'stb', 'ɔtb', 'nvb', 'dsb'], + 'weekdays' => ['Lomíngo', 'Mosálá mɔ̌kɔ́', 'Misálá míbalé', 'Misálá mísáto', 'Misálá mínei', 'Misálá mítáno', 'Mpɔ́sɔ'], + 'weekdays_short' => ['m1.', 'm2.', 'm3.', 'm4.', 'm5.', 'm6.', 'm7.'], + 'weekdays_min' => ['m1.', 'm2.', 'm3.', 'm4.', 'm5.', 'm6.', 'm7.'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'year' => 'mbula :count', + 'y' => 'mbula :count', + 'a_year' => 'mbula :count', + + 'month' => 'sánzá :count', + 'm' => 'sánzá :count', + 'a_month' => 'sánzá :count', + + 'week' => 'mpɔ́sɔ :count', + 'w' => 'mpɔ́sɔ :count', + 'a_week' => 'mpɔ́sɔ :count', + + 'day' => 'mokɔlɔ :count', + 'd' => 'mokɔlɔ :count', + 'a_day' => 'mokɔlɔ :count', + + 'hour' => 'ngonga :count', + 'h' => 'ngonga :count', + 'a_hour' => 'ngonga :count', + + 'minute' => 'miniti :count', + 'min' => 'miniti :count', + 'a_minute' => 'miniti :count', + + 'second' => 'segɔnde :count', + 's' => 'segɔnde :count', + 'a_second' => 'segɔnde :count', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ln_AO.php b/vendor/nesbot/carbon/src/Carbon/Lang/ln_AO.php new file mode 100644 index 0000000..7fdb7f1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ln_AO.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ln.php', [ + 'weekdays' => ['eyenga', 'mokɔlɔ mwa yambo', 'mokɔlɔ mwa míbalé', 'mokɔlɔ mwa mísáto', 'mokɔlɔ ya mínéi', 'mokɔlɔ ya mítáno', 'mpɔ́sɔ'], + 'weekdays_short' => ['eye', 'ybo', 'mbl', 'mst', 'min', 'mtn', 'mps'], + 'weekdays_min' => ['eye', 'ybo', 'mbl', 'mst', 'min', 'mtn', 'mps'], + 'meridiem' => ['ntɔ́ngɔ́', 'mpókwa'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ln_CD.php b/vendor/nesbot/carbon/src/Carbon/Lang/ln_CD.php new file mode 100644 index 0000000..13635fc --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ln_CD.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ubuntu René Manassé GALEKWA renemanasse@gmail.com + */ +return require __DIR__.'/ln.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ln_CF.php b/vendor/nesbot/carbon/src/Carbon/Lang/ln_CF.php new file mode 100644 index 0000000..7fdb7f1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ln_CF.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ln.php', [ + 'weekdays' => ['eyenga', 'mokɔlɔ mwa yambo', 'mokɔlɔ mwa míbalé', 'mokɔlɔ mwa mísáto', 'mokɔlɔ ya mínéi', 'mokɔlɔ ya mítáno', 'mpɔ́sɔ'], + 'weekdays_short' => ['eye', 'ybo', 'mbl', 'mst', 'min', 'mtn', 'mps'], + 'weekdays_min' => ['eye', 'ybo', 'mbl', 'mst', 'min', 'mtn', 'mps'], + 'meridiem' => ['ntɔ́ngɔ́', 'mpókwa'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ln_CG.php b/vendor/nesbot/carbon/src/Carbon/Lang/ln_CG.php new file mode 100644 index 0000000..7fdb7f1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ln_CG.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ln.php', [ + 'weekdays' => ['eyenga', 'mokɔlɔ mwa yambo', 'mokɔlɔ mwa míbalé', 'mokɔlɔ mwa mísáto', 'mokɔlɔ ya mínéi', 'mokɔlɔ ya mítáno', 'mpɔ́sɔ'], + 'weekdays_short' => ['eye', 'ybo', 'mbl', 'mst', 'min', 'mtn', 'mps'], + 'weekdays_min' => ['eye', 'ybo', 'mbl', 'mst', 'min', 'mtn', 'mps'], + 'meridiem' => ['ntɔ́ngɔ́', 'mpókwa'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/lo.php b/vendor/nesbot/carbon/src/Carbon/Lang/lo.php new file mode 100644 index 0000000..48715f5 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/lo.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - ryanhart2 + */ +return [ + 'year' => ':count ປີ', + 'y' => ':count ປີ', + 'month' => ':count ເດືອນ', + 'm' => ':count ດ. ', + 'week' => ':count ອາທິດ', + 'w' => ':count ອທ. ', + 'day' => ':count ມື້', + 'd' => ':count ມື້', + 'hour' => ':count ຊົ່ວໂມງ', + 'h' => ':count ຊມ. ', + 'minute' => ':count ນາທີ', + 'min' => ':count ນທ. ', + 'second' => '{1}ບໍ່ເທົ່າໃດວິນາທີ|]1,Inf[:count ວິນາທີ', + 's' => ':count ວິ. ', + 'ago' => ':timeຜ່ານມາ', + 'from_now' => 'ອີກ :time', + 'diff_now' => 'ຕອນນີ້', + 'diff_today' => 'ມື້ນີ້ເວລາ', + 'diff_yesterday' => 'ມື້ວານນີ້ເວລາ', + 'diff_tomorrow' => 'ມື້ອື່ນເວລາ', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'ວັນdddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[ມື້ນີ້ເວລາ] LT', + 'nextDay' => '[ມື້ອື່ນເວລາ] LT', + 'nextWeek' => '[ວັນ]dddd[ໜ້າເວລາ] LT', + 'lastDay' => '[ມື້ວານນີ້ເວລາ] LT', + 'lastWeek' => '[ວັນ]dddd[ແລ້ວນີ້ເວລາ] LT', + 'sameElse' => 'L', + ], + 'ordinal' => 'ທີ່:number', + 'meridiem' => ['ຕອນເຊົ້າ', 'ຕອນແລງ'], + 'months' => ['ມັງກອນ', 'ກຸມພາ', 'ມີນາ', 'ເມສາ', 'ພຶດສະພາ', 'ມິຖຸນາ', 'ກໍລະກົດ', 'ສິງຫາ', 'ກັນຍາ', 'ຕຸລາ', 'ພະຈິກ', 'ທັນວາ'], + 'months_short' => ['ມັງກອນ', 'ກຸມພາ', 'ມີນາ', 'ເມສາ', 'ພຶດສະພາ', 'ມິຖຸນາ', 'ກໍລະກົດ', 'ສິງຫາ', 'ກັນຍາ', 'ຕຸລາ', 'ພະຈິກ', 'ທັນວາ'], + 'weekdays' => ['ອາທິດ', 'ຈັນ', 'ອັງຄານ', 'ພຸດ', 'ພະຫັດ', 'ສຸກ', 'ເສົາ'], + 'weekdays_short' => ['ທິດ', 'ຈັນ', 'ອັງຄານ', 'ພຸດ', 'ພະຫັດ', 'ສຸກ', 'ເສົາ'], + 'weekdays_min' => ['ທ', 'ຈ', 'ອຄ', 'ພ', 'ພຫ', 'ສກ', 'ສ'], + 'list' => [', ', 'ແລະ '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/lo_LA.php b/vendor/nesbot/carbon/src/Carbon/Lang/lo_LA.php new file mode 100644 index 0000000..9b7fd9b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/lo_LA.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/lo.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/lrc.php b/vendor/nesbot/carbon/src/Carbon/Lang/lrc.php new file mode 100644 index 0000000..546e679 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/lrc.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + + 'minute' => ':count هنر', // less reliable + 'min' => ':count هنر', // less reliable + 'a_minute' => ':count هنر', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/lrc_IQ.php b/vendor/nesbot/carbon/src/Carbon/Lang/lrc_IQ.php new file mode 100644 index 0000000..d42f5e9 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/lrc_IQ.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/lrc.php', [ +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/lt.php b/vendor/nesbot/carbon/src/Carbon/Lang/lt.php new file mode 100644 index 0000000..7d1b6f7 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/lt.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - Tsutomu Kuroda + * - tjku + * - valdas406 + * - Justas Palumickas + * - Max Melentiev + * - Andrius Janauskas + * - Juanito Fatas + * - Akira Matsuda + * - Christopher Dell + * - Enrique Vidal + * - Simone Carletti + * - Aaron Patterson + * - Nicolás Hock Isaza + * - Laurynas Butkus + * - Sven Fuchs + * - Dominykas Tijūnaitis + * - Justinas Bolys + * - Ričardas + * - Kirill Chalkin + * - Rolandas + * - Justinas (Gamesh) + */ +return [ + 'year' => ':count metai|:count metai|:count metų', + 'y' => ':count m.', + 'month' => ':count mėnuo|:count mėnesiai|:count mėnesį', + 'm' => ':count mėn.', + 'week' => ':count savaitė|:count savaitės|:count savaitę', + 'w' => ':count sav.', + 'day' => ':count diena|:count dienos|:count dienų', + 'd' => ':count d.', + 'hour' => ':count valanda|:count valandos|:count valandų', + 'h' => ':count val.', + 'minute' => ':count minutė|:count minutės|:count minutę', + 'min' => ':count min.', + 'second' => ':count sekundė|:count sekundės|:count sekundžių', + 's' => ':count sek.', + + 'year_ago' => ':count metus|:count metus|:count metų', + 'month_ago' => ':count mėnesį|:count mėnesius|:count mėnesių', + 'week_ago' => ':count savaitę|:count savaites|:count savaičių', + 'day_ago' => ':count dieną|:count dienas|:count dienų', + 'hour_ago' => ':count valandą|:count valandas|:count valandų', + 'minute_ago' => ':count minutę|:count minutes|:count minučių', + 'second_ago' => ':count sekundę|:count sekundes|:count sekundžių', + + 'year_from_now' => ':count metų', + 'month_from_now' => ':count mėnesio|:count mėnesių|:count mėnesių', + 'week_from_now' => ':count savaitės|:count savaičių|:count savaičių', + 'day_from_now' => ':count dienos|:count dienų|:count dienų', + 'hour_from_now' => ':count valandos|:count valandų|:count valandų', + 'minute_from_now' => ':count minutės|:count minučių|:count minučių', + 'second_from_now' => ':count sekundės|:count sekundžių|:count sekundžių', + + 'year_after' => ':count metų', + 'month_after' => ':count mėnesio|:count mėnesių|:count mėnesių', + 'week_after' => ':count savaitės|:count savaičių|:count savaičių', + 'day_after' => ':count dienos|:count dienų|:count dienų', + 'hour_after' => ':count valandos|:count valandų|:count valandų', + 'minute_after' => ':count minutės|:count minučių|:count minučių', + 'second_after' => ':count sekundės|:count sekundžių|:count sekundžių', + + 'ago' => 'prieš :time', + 'from_now' => ':time nuo dabar', + 'after' => 'po :time', + 'before' => 'už :time', + + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'diff_now' => 'ką tik', + 'diff_today' => 'Šiandien', + 'diff_yesterday' => 'vakar', + 'diff_yesterday_regexp' => 'Vakar', + 'diff_tomorrow' => 'rytoj', + 'diff_tomorrow_regexp' => 'Rytoj', + 'diff_before_yesterday' => 'užvakar', + 'diff_after_tomorrow' => 'poryt', + + 'period_recurrences' => 'kartą|:count kartų', + 'period_interval' => 'kiekvieną :interval', + 'period_start_date' => 'nuo :date', + 'period_end_date' => 'iki :date', + + 'months' => ['sausio', 'vasario', 'kovo', 'balandžio', 'gegužės', 'birželio', 'liepos', 'rugpjūčio', 'rugsėjo', 'spalio', 'lapkričio', 'gruodžio'], + 'months_standalone' => ['sausis', 'vasaris', 'kovas', 'balandis', 'gegužė', 'birželis', 'liepa', 'rugpjūtis', 'rugsėjis', 'spalis', 'lapkritis', 'gruodis'], + 'months_regexp' => '/(L{2,4}|D[oD]?(\[[^\[\]]*\]|\s)+MMMM?|MMMM?(\[[^\[\]]*\]|\s)+D[oD]?)/', + 'months_short' => ['sau', 'vas', 'kov', 'bal', 'geg', 'bir', 'lie', 'rgp', 'rgs', 'spa', 'lap', 'gru'], + 'weekdays' => ['sekmadienį', 'pirmadienį', 'antradienį', 'trečiadienį', 'ketvirtadienį', 'penktadienį', 'šeštadienį'], + 'weekdays_standalone' => ['sekmadienis', 'pirmadienis', 'antradienis', 'trečiadienis', 'ketvirtadienis', 'penktadienis', 'šeštadienis'], + 'weekdays_short' => ['sek', 'pir', 'ant', 'tre', 'ket', 'pen', 'šeš'], + 'weekdays_min' => ['se', 'pi', 'an', 'tr', 'ke', 'pe', 'še'], + 'list' => [', ', ' ir '], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-DD', + 'LL' => 'MMMM DD, YYYY', + 'LLL' => 'DD MMM HH:mm', + 'LLLL' => 'MMMM DD, YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Šiandien] LT', + 'nextDay' => '[Rytoj] LT', + 'nextWeek' => 'dddd LT', + 'lastDay' => '[Vakar] LT', + 'lastWeek' => '[Paskutinį] dddd LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + switch ($number) { + case 0: + return '0-is'; + case 3: + return '3-ias'; + default: + return "$number-as"; + } + }, + 'meridiem' => ['priešpiet', 'popiet'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/lt_LT.php b/vendor/nesbot/carbon/src/Carbon/Lang/lt_LT.php new file mode 100644 index 0000000..f772d38 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/lt_LT.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/lt.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/lu.php b/vendor/nesbot/carbon/src/Carbon/Lang/lu.php new file mode 100644 index 0000000..c8cd83a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/lu.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Dinda', 'Dilolo'], + 'weekdays' => ['Lumingu', 'Nkodya', 'Ndàayà', 'Ndangù', 'Njòwa', 'Ngòvya', 'Lubingu'], + 'weekdays_short' => ['Lum', 'Nko', 'Ndy', 'Ndg', 'Njw', 'Ngv', 'Lub'], + 'weekdays_min' => ['Lum', 'Nko', 'Ndy', 'Ndg', 'Njw', 'Ngv', 'Lub'], + 'months' => ['Ciongo', 'Lùishi', 'Lusòlo', 'Mùuyà', 'Lumùngùlù', 'Lufuimi', 'Kabàlàshìpù', 'Lùshìkà', 'Lutongolo', 'Lungùdi', 'Kaswèkèsè', 'Ciswà'], + 'months_short' => ['Cio', 'Lui', 'Lus', 'Muu', 'Lum', 'Luf', 'Kab', 'Lush', 'Lut', 'Lun', 'Kas', 'Cis'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/luo.php b/vendor/nesbot/carbon/src/Carbon/Lang/luo.php new file mode 100644 index 0000000..b55af73 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/luo.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['OD', 'OT'], + 'weekdays' => ['Jumapil', 'Wuok Tich', 'Tich Ariyo', 'Tich Adek', 'Tich Ang’wen', 'Tich Abich', 'Ngeso'], + 'weekdays_short' => ['JMP', 'WUT', 'TAR', 'TAD', 'TAN', 'TAB', 'NGS'], + 'weekdays_min' => ['JMP', 'WUT', 'TAR', 'TAD', 'TAN', 'TAB', 'NGS'], + 'months' => ['Dwe mar Achiel', 'Dwe mar Ariyo', 'Dwe mar Adek', 'Dwe mar Ang’wen', 'Dwe mar Abich', 'Dwe mar Auchiel', 'Dwe mar Abiriyo', 'Dwe mar Aboro', 'Dwe mar Ochiko', 'Dwe mar Apar', 'Dwe mar gi achiel', 'Dwe mar Apar gi ariyo'], + 'months_short' => ['DAC', 'DAR', 'DAD', 'DAN', 'DAH', 'DAU', 'DAO', 'DAB', 'DOC', 'DAP', 'DGI', 'DAG'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + + 'year' => 'higni :count', + 'y' => 'higni :count', + 'a_year' => ':higni :count', + + 'month' => 'dweche :count', + 'm' => 'dweche :count', + 'a_month' => 'dweche :count', + + 'week' => 'jumbe :count', + 'w' => 'jumbe :count', + 'a_week' => 'jumbe :count', + + 'day' => 'ndalo :count', + 'd' => 'ndalo :count', + 'a_day' => 'ndalo :count', + + 'hour' => 'seche :count', + 'h' => 'seche :count', + 'a_hour' => 'seche :count', + + 'minute' => 'dakika :count', + 'min' => 'dakika :count', + 'a_minute' => 'dakika :count', + + 'second' => 'nus dakika :count', + 's' => 'nus dakika :count', + 'a_second' => 'nus dakika :count', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/luy.php b/vendor/nesbot/carbon/src/Carbon/Lang/luy.php new file mode 100644 index 0000000..2b37e3e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/luy.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'weekdays' => ['Jumapiri', 'Jumatatu', 'Jumanne', 'Jumatano', 'Murwa wa Kanne', 'Murwa wa Katano', 'Jumamosi'], + 'weekdays_short' => ['J2', 'J3', 'J4', 'J5', 'Al', 'Ij', 'J1'], + 'weekdays_min' => ['J2', 'J3', 'J4', 'J5', 'Al', 'Ij', 'J1'], + 'months' => ['Januari', 'Februari', 'Machi', 'Aprili', 'Mei', 'Juni', 'Julai', 'Agosti', 'Septemba', 'Oktoba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Sep', 'Okt', 'Nov', 'Des'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + + // Too unreliable + /* + 'year' => ':count liliino', // less reliable + 'y' => ':count liliino', // less reliable + 'a_year' => ':count liliino', // less reliable + + 'month' => ':count kumwesi', // less reliable + 'm' => ':count kumwesi', // less reliable + 'a_month' => ':count kumwesi', // less reliable + + 'week' => ':count olutambi', // less reliable + 'w' => ':count olutambi', // less reliable + 'a_week' => ':count olutambi', // less reliable + + 'day' => ':count luno', // less reliable + 'd' => ':count luno', // less reliable + 'a_day' => ':count luno', // less reliable + + 'hour' => ':count ekengele', // less reliable + 'h' => ':count ekengele', // less reliable + 'a_hour' => ':count ekengele', // less reliable + + 'minute' => ':count omundu', // less reliable + 'min' => ':count omundu', // less reliable + 'a_minute' => ':count omundu', // less reliable + + 'second' => ':count liliino', // less reliable + 's' => ':count liliino', // less reliable + 'a_second' => ':count liliino', // less reliable + */ +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/lv.php b/vendor/nesbot/carbon/src/Carbon/Lang/lv.php new file mode 100644 index 0000000..693eceb --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/lv.php @@ -0,0 +1,182 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Carbon\CarbonInterface; + +/** + * This file is part of the Carbon package. + * + * (c) Brian Nesbitt + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - pirminis + * - Tsutomu Kuroda + * - tjku + * - Andris Zāģeris + * - Max Melentiev + * - Edgars Beigarts + * - Juanito Fatas + * - Vitauts Stočka + * - Akira Matsuda + * - Christopher Dell + * - Enrique Vidal + * - Simone Carletti + * - Aaron Patterson + * - Kaspars Bankovskis + * - Nicolás Hock Isaza + * - Viesturs Kavacs (Kavacky) + * - zakse + * - Janis Eglitis (janiseglitis) + * - Guntars + * - Juris Sudmalis + */ +$daysOfWeek = ['svētdiena', 'pirmdiena', 'otrdiena', 'trešdiena', 'ceturtdiena', 'piektdiena', 'sestdiena']; +$daysOfWeekLocativum = ['svētdien', 'pirmdien', 'otrdien', 'trešdien', 'ceturtdien', 'piektdien', 'sestdien']; + +$transformDiff = function ($input) { + return strtr($input, [ + // Nominative => "pirms/pēc" Dative + 'gads' => 'gada', + 'gadi' => 'gadiem', + 'gadu' => 'gadiem', + 'mēnesis' => 'mēneša', + 'mēneši' => 'mēnešiem', + 'mēnešu' => 'mēnešiem', + 'nedēļa' => 'nedēļas', + 'nedēļas' => 'nedēļām', + 'nedēļu' => 'nedēļām', + 'diena' => 'dienas', + 'dienas' => 'dienām', + 'dienu' => 'dienām', + 'stunda' => 'stundas', + 'stundas' => 'stundām', + 'stundu' => 'stundām', + 'minūte' => 'minūtes', + 'minūtes' => 'minūtēm', + 'minūšu' => 'minūtēm', + 'sekunde' => 'sekundes', + 'sekundes' => 'sekundēm', + 'sekunžu' => 'sekundēm', + ]); +}; + +return [ + 'ago' => function ($time) use ($transformDiff) { + return 'pirms '.$transformDiff($time); + }, + 'from_now' => function ($time) use ($transformDiff) { + return 'pēc '.$transformDiff($time); + }, + + 'year' => '0 gadu|:count gads|:count gadi', + 'y' => ':count g.', + 'a_year' => '{1}gads|0 gadu|:count gads|:count gadi', + 'month' => '0 mēnešu|:count mēnesis|:count mēneši', + 'm' => ':count mēn.', + 'a_month' => '{1}mēnesis|0 mēnešu|:count mēnesis|:count mēneši', + 'week' => '0 nedēļu|:count nedēļa|:count nedēļas', + 'w' => ':count ned.', + 'a_week' => '{1}nedēļa|0 nedēļu|:count nedēļa|:count nedēļas', + 'day' => '0 dienu|:count diena|:count dienas', + 'd' => ':count d.', + 'a_day' => '{1}diena|0 dienu|:count diena|:count dienas', + 'hour' => '0 stundu|:count stunda|:count stundas', + 'h' => ':count st.', + 'a_hour' => '{1}stunda|0 stundu|:count stunda|:count stundas', + 'minute' => '0 minūšu|:count minūte|:count minūtes', + 'min' => ':count min.', + 'a_minute' => '{1}minūte|0 minūšu|:count minūte|:count minūtes', + 'second' => '0 sekunžu|:count sekunde|:count sekundes', + 's' => ':count sek.', + 'a_second' => '{1}sekunde|0 sekunžu|:count sekunde|:count sekundes', + + 'after' => ':time vēlāk', + 'year_after' => '0 gadus|:count gadu|:count gadus', + 'a_year_after' => '{1}gadu|0 gadus|:count gadu|:count gadus', + 'month_after' => '0 mēnešus|:count mēnesi|:count mēnešus', + 'a_month_after' => '{1}mēnesi|0 mēnešus|:count mēnesi|:count mēnešus', + 'week_after' => '0 nedēļas|:count nedēļu|:count nedēļas', + 'a_week_after' => '{1}nedēļu|0 nedēļas|:count nedēļu|:count nedēļas', + 'day_after' => '0 dienas|:count dienu|:count dienas', + 'a_day_after' => '{1}dienu|0 dienas|:count dienu|:count dienas', + 'hour_after' => '0 stundas|:count stundu|:count stundas', + 'a_hour_after' => '{1}stundu|0 stundas|:count stundu|:count stundas', + 'minute_after' => '0 minūtes|:count minūti|:count minūtes', + 'a_minute_after' => '{1}minūti|0 minūtes|:count minūti|:count minūtes', + 'second_after' => '0 sekundes|:count sekundi|:count sekundes', + 'a_second_after' => '{1}sekundi|0 sekundes|:count sekundi|:count sekundes', + + 'before' => ':time agrāk', + 'year_before' => '0 gadus|:count gadu|:count gadus', + 'a_year_before' => '{1}gadu|0 gadus|:count gadu|:count gadus', + 'month_before' => '0 mēnešus|:count mēnesi|:count mēnešus', + 'a_month_before' => '{1}mēnesi|0 mēnešus|:count mēnesi|:count mēnešus', + 'week_before' => '0 nedēļas|:count nedēļu|:count nedēļas', + 'a_week_before' => '{1}nedēļu|0 nedēļas|:count nedēļu|:count nedēļas', + 'day_before' => '0 dienas|:count dienu|:count dienas', + 'a_day_before' => '{1}dienu|0 dienas|:count dienu|:count dienas', + 'hour_before' => '0 stundas|:count stundu|:count stundas', + 'a_hour_before' => '{1}stundu|0 stundas|:count stundu|:count stundas', + 'minute_before' => '0 minūtes|:count minūti|:count minūtes', + 'a_minute_before' => '{1}minūti|0 minūtes|:count minūti|:count minūtes', + 'second_before' => '0 sekundes|:count sekundi|:count sekundes', + 'a_second_before' => '{1}sekundi|0 sekundes|:count sekundi|:count sekundes', + + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' un '], + + 'diff_now' => 'tagad', + 'diff_today' => 'šodien', + 'diff_yesterday' => 'vakar', + 'diff_before_yesterday' => 'aizvakar', + 'diff_tomorrow' => 'rīt', + 'diff_after_tomorrow' => 'parīt', + + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY.', + 'LL' => 'YYYY. [gada] D. MMMM', + 'LLL' => 'DD.MM.YYYY., HH:mm', + 'LLLL' => 'YYYY. [gada] D. MMMM, HH:mm', + ], + + 'calendar' => [ + 'sameDay' => '[šodien] [plkst.] LT', + 'nextDay' => '[rīt] [plkst.] LT', + 'nextWeek' => function (CarbonInterface $current, CarbonInterface $other) use ($daysOfWeekLocativum) { + if ($current->week !== $other->week) { + return '[nākošo] ['.$daysOfWeekLocativum[$current->dayOfWeek].'] [plkst.] LT'; + } + + return '['.$daysOfWeekLocativum[$current->dayOfWeek].'] [plkst.] LT'; + }, + 'lastDay' => '[vakar] [plkst.] LT', + 'lastWeek' => function (CarbonInterface $current) use ($daysOfWeekLocativum) { + return '[pagājušo] ['.$daysOfWeekLocativum[$current->dayOfWeek].'] [plkst.] LT'; + }, + 'sameElse' => 'L', + ], + + 'weekdays' => $daysOfWeek, + 'weekdays_short' => ['Sv.', 'P.', 'O.', 'T.', 'C.', 'Pk.', 'S.'], + 'weekdays_min' => ['Sv.', 'P.', 'O.', 'T.', 'C.', 'Pk.', 'S.'], + 'months' => ['janvārī', 'februārī', 'martā', 'aprīlī', 'maijā', 'jūnijā', 'jūlijā', 'augustā', 'septembrī', 'oktobrī', 'novembrī', 'decembrī'], + 'months_short' => ['janv.', 'febr.', 'martā', 'apr.', 'maijā', 'jūn.', 'jūl.', 'aug.', 'sept.', 'okt.', 'nov.', 'dec.'], + 'meridiem' => ['priekšpusdiena', 'pēcpusdiena'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/lv_LV.php b/vendor/nesbot/carbon/src/Carbon/Lang/lv_LV.php new file mode 100644 index 0000000..ee91c36 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/lv_LV.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/lv.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/lzh.php b/vendor/nesbot/carbon/src/Carbon/Lang/lzh.php new file mode 100644 index 0000000..1180c6b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/lzh.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/lzh_TW.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/lzh_TW.php b/vendor/nesbot/carbon/src/Carbon/Lang/lzh_TW.php new file mode 100644 index 0000000..3b1493e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/lzh_TW.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'OY[年]MMMMOD[日]', + ], + 'months' => ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'], + 'months_short' => [' 一 ', ' 二 ', ' 三 ', ' 四 ', ' 五 ', ' 六 ', ' 七 ', ' 八 ', ' 九 ', ' 十 ', '十一', '十二'], + 'weekdays' => ['週日', '週一', '週二', '週三', '週四', '週五', '週六'], + 'weekdays_short' => ['日', '一', '二', '三', '四', '五', '六'], + 'weekdays_min' => ['日', '一', '二', '三', '四', '五', '六'], + 'day_of_first_week_of_year' => 1, + 'alt_numbers' => ['〇', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二', '十三', '十四', '十五', '十六', '十七', '十八', '十九', '廿', '廿一', '廿二', '廿三', '廿四', '廿五', '廿六', '廿七', '廿八', '廿九', '卅', '卅一'], + 'meridiem' => ['朝', '暮'], + + 'year' => ':count 夏', // less reliable + 'y' => ':count 夏', // less reliable + 'a_year' => ':count 夏', // less reliable + + 'month' => ':count 月', // less reliable + 'm' => ':count 月', // less reliable + 'a_month' => ':count 月', // less reliable + + 'hour' => ':count 氧', // less reliable + 'h' => ':count 氧', // less reliable + 'a_hour' => ':count 氧', // less reliable + + 'minute' => ':count 點', // less reliable + 'min' => ':count 點', // less reliable + 'a_minute' => ':count 點', // less reliable + + 'second' => ':count 楚', // less reliable + 's' => ':count 楚', // less reliable + 'a_second' => ':count 楚', // less reliable + + 'week' => ':count 星期', + 'w' => ':count 星期', + 'a_week' => ':count 星期', + + 'day' => ':count 日(曆法)', + 'd' => ':count 日(曆法)', + 'a_day' => ':count 日(曆法)', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mag.php b/vendor/nesbot/carbon/src/Carbon/Lang/mag.php new file mode 100644 index 0000000..7532436 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mag.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/mag_IN.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mag_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/mag_IN.php new file mode 100644 index 0000000..193f67a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mag_IN.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bhashaghar@googlegroups.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['जनवरी', 'फ़रवरी', 'मार्च', 'अप्रेल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितम्बर', 'अक्टूबर', 'नवम्बर', 'दिसम्बर'], + 'months_short' => ['जनवरी', 'फ़रवरी', 'मार्च', 'अप्रेल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितम्बर', 'अक्टूबर', 'नवम्बर', 'दिसम्बर'], + 'weekdays' => ['एतवार', 'सोमार', 'मंगर', 'बुध', 'बिफे', 'सूक', 'सनिचर'], + 'weekdays_short' => ['एतवार', 'सोमार', 'मंगर', 'बुध', 'बिफे', 'सूक', 'सनिचर'], + 'weekdays_min' => ['एतवार', 'सोमार', 'मंगर', 'बुध', 'बिफे', 'सूक', 'सनिचर'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['पूर्वाह्न', 'अपराह्न'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mai.php b/vendor/nesbot/carbon/src/Carbon/Lang/mai.php new file mode 100644 index 0000000..792b973 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mai.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/mai_IN.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mai_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/mai_IN.php new file mode 100644 index 0000000..03049d4 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mai_IN.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Maithili Computing Research Center, Pune, India rajeshkajha@yahoo.com,akhilesh.k@samusng.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['बैसाख', 'जेठ', 'अषाढ़', 'सावोन', 'भादो', 'आसिन', 'कातिक', 'अगहन', 'पूस', 'माघ', 'फागुन', 'चैति'], + 'months_short' => ['बैसाख', 'जेठ', 'अषाढ़', 'सावोन', 'भादो', 'आसिन', 'कातिक', 'अगहन', 'पूस', 'माघ', 'फागुन', 'चैति'], + 'weekdays' => ['रविदिन', 'सोमदिन', 'मंगलदिन', 'बुधदिन', 'बृहस्पतीदिन', 'शुक्रदिन', 'शनीदिन'], + 'weekdays_short' => ['रवि', 'सोम', 'मंगल', 'बुध', 'बृहस्पती', 'शुक्र', 'शनी'], + 'weekdays_min' => ['रवि', 'सोम', 'मंगल', 'बुध', 'बृहस्पती', 'शुक्र', 'शनी'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['पूर्वाह्न', 'अपराह्न'], + + 'year' => ':count ऋतु', // less reliable + 'y' => ':count ऋतु', // less reliable + 'a_year' => ':count ऋतु', // less reliable + + 'month' => ':count महिना', + 'm' => ':count महिना', + 'a_month' => ':count महिना', + + 'week' => ':count श्रेणी:क्यालेन्डर', // less reliable + 'w' => ':count श्रेणी:क्यालेन्डर', // less reliable + 'a_week' => ':count श्रेणी:क्यालेन्डर', // less reliable + + 'day' => ':count दिन', + 'd' => ':count दिन', + 'a_day' => ':count दिन', + + 'hour' => ':count घण्टा', + 'h' => ':count घण्टा', + 'a_hour' => ':count घण्टा', + + 'minute' => ':count समय', // less reliable + 'min' => ':count समय', // less reliable + 'a_minute' => ':count समय', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mas.php b/vendor/nesbot/carbon/src/Carbon/Lang/mas.php new file mode 100644 index 0000000..cbd610c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mas.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Ɛnkakɛnyá', 'Ɛndámâ'], + 'weekdays' => ['Jumapílí', 'Jumatátu', 'Jumane', 'Jumatánɔ', 'Alaámisi', 'Jumáa', 'Jumamósi'], + 'weekdays_short' => ['Jpi', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'weekdays_min' => ['Jpi', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'months' => ['Oladalʉ́', 'Arát', 'Ɔɛnɨ́ɔɨŋɔk', 'Olodoyíóríê inkókúâ', 'Oloilépūnyīē inkókúâ', 'Kújúɔrɔk', 'Mórusásin', 'Ɔlɔ́ɨ́bɔ́rárɛ', 'Kúshîn', 'Olgísan', 'Pʉshʉ́ka', 'Ntʉ́ŋʉ́s'], + 'months_short' => ['Dal', 'Ará', 'Ɔɛn', 'Doy', 'Lép', 'Rok', 'Sás', 'Bɔ́r', 'Kús', 'Gís', 'Shʉ́', 'Ntʉ́'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + + 'year' => ':count olameyu', // less reliable + 'y' => ':count olameyu', // less reliable + 'a_year' => ':count olameyu', // less reliable + + 'week' => ':count engolongeare orwiki', // less reliable + 'w' => ':count engolongeare orwiki', // less reliable + 'a_week' => ':count engolongeare orwiki', // less reliable + + 'hour' => ':count esahabu', // less reliable + 'h' => ':count esahabu', // less reliable + 'a_hour' => ':count esahabu', // less reliable + + 'second' => ':count are', // less reliable + 's' => ':count are', // less reliable + 'a_second' => ':count are', // less reliable + + 'month' => ':count olapa', + 'm' => ':count olapa', + 'a_month' => ':count olapa', + + 'day' => ':count enkolongʼ', + 'd' => ':count enkolongʼ', + 'a_day' => ':count enkolongʼ', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mas_TZ.php b/vendor/nesbot/carbon/src/Carbon/Lang/mas_TZ.php new file mode 100644 index 0000000..56e2905 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mas_TZ.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/mas.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mer.php b/vendor/nesbot/carbon/src/Carbon/Lang/mer.php new file mode 100644 index 0000000..2e14597 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mer.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['RŨ', 'ŨG'], + 'weekdays' => ['Kiumia', 'Muramuko', 'Wairi', 'Wethatu', 'Wena', 'Wetano', 'Jumamosi'], + 'weekdays_short' => ['KIU', 'MRA', 'WAI', 'WET', 'WEN', 'WTN', 'JUM'], + 'weekdays_min' => ['KIU', 'MRA', 'WAI', 'WET', 'WEN', 'WTN', 'JUM'], + 'months' => ['Januarĩ', 'Feburuarĩ', 'Machi', 'Ĩpurũ', 'Mĩĩ', 'Njuni', 'Njuraĩ', 'Agasti', 'Septemba', 'Oktũba', 'Novemba', 'Dicemba'], + 'months_short' => ['JAN', 'FEB', 'MAC', 'ĨPU', 'MĨĨ', 'NJU', 'NJR', 'AGA', 'SPT', 'OKT', 'NOV', 'DEC'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + + 'year' => ':count murume', // less reliable + 'y' => ':count murume', // less reliable + 'a_year' => ':count murume', // less reliable + + 'month' => ':count muchaara', // less reliable + 'm' => ':count muchaara', // less reliable + 'a_month' => ':count muchaara', // less reliable + + 'minute' => ':count monto', // less reliable + 'min' => ':count monto', // less reliable + 'a_minute' => ':count monto', // less reliable + + 'second' => ':count gikeno', // less reliable + 's' => ':count gikeno', // less reliable + 'a_second' => ':count gikeno', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mfe.php b/vendor/nesbot/carbon/src/Carbon/Lang/mfe.php new file mode 100644 index 0000000..4d6e6b6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mfe.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/mfe_MU.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mfe_MU.php b/vendor/nesbot/carbon/src/Carbon/Lang/mfe_MU.php new file mode 100644 index 0000000..2d27b45 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mfe_MU.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Samsung Electronics Co., Ltd. akhilesh.k@samsung.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['zanvie', 'fevriye', 'mars', 'avril', 'me', 'zin', 'zilye', 'out', 'septam', 'oktob', 'novam', 'desam'], + 'months_short' => ['zan', 'fev', 'mar', 'avr', 'me', 'zin', 'zil', 'out', 'sep', 'okt', 'nov', 'des'], + 'weekdays' => ['dimans', 'lindi', 'mardi', 'merkredi', 'zedi', 'vandredi', 'samdi'], + 'weekdays_short' => ['dim', 'lin', 'mar', 'mer', 'ze', 'van', 'sam'], + 'weekdays_min' => ['dim', 'lin', 'mar', 'mer', 'ze', 'van', 'sam'], + + 'year' => ':count banané', + 'y' => ':count banané', + 'a_year' => ':count banané', + + 'month' => ':count mwa', + 'm' => ':count mwa', + 'a_month' => ':count mwa', + + 'week' => ':count sémenn', + 'w' => ':count sémenn', + 'a_week' => ':count sémenn', + + 'day' => ':count zour', + 'd' => ':count zour', + 'a_day' => ':count zour', + + 'hour' => ':count -er-tan', + 'h' => ':count -er-tan', + 'a_hour' => ':count -er-tan', + + 'minute' => ':count minitt', + 'min' => ':count minitt', + 'a_minute' => ':count minitt', + + 'second' => ':count déziém', + 's' => ':count déziém', + 'a_second' => ':count déziém', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mg.php b/vendor/nesbot/carbon/src/Carbon/Lang/mg.php new file mode 100644 index 0000000..40bc2a8 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mg.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/mg_MG.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mg_MG.php b/vendor/nesbot/carbon/src/Carbon/Lang/mg_MG.php new file mode 100644 index 0000000..6a14535 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mg_MG.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - The Debian Project modified by GNU//Linux Malagasy Rado Ramarotafika,Do-Risika RAFIEFERANTSIARONJY rado@linuxmg.org,dourix@free.fr + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['Janoary', 'Febroary', 'Martsa', 'Aprily', 'Mey', 'Jona', 'Jolay', 'Aogositra', 'Septambra', 'Oktobra', 'Novambra', 'Desambra'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apr', 'Mey', 'Jon', 'Jol', 'Aog', 'Sep', 'Okt', 'Nov', 'Des'], + 'weekdays' => ['alahady', 'alatsinainy', 'talata', 'alarobia', 'alakamisy', 'zoma', 'sabotsy'], + 'weekdays_short' => ['lhd', 'lts', 'tlt', 'lrb', 'lkm', 'zom', 'sab'], + 'weekdays_min' => ['lhd', 'lts', 'tlt', 'lrb', 'lkm', 'zom', 'sab'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'minute' => ':count minitra', // less reliable + 'min' => ':count minitra', // less reliable + 'a_minute' => ':count minitra', // less reliable + + 'year' => ':count taona', + 'y' => ':count taona', + 'a_year' => ':count taona', + + 'month' => ':count volana', + 'm' => ':count volana', + 'a_month' => ':count volana', + + 'week' => ':count herinandro', + 'w' => ':count herinandro', + 'a_week' => ':count herinandro', + + 'day' => ':count andro', + 'd' => ':count andro', + 'a_day' => ':count andro', + + 'hour' => ':count ora', + 'h' => ':count ora', + 'a_hour' => ':count ora', + + 'second' => ':count segondra', + 's' => ':count segondra', + 'a_second' => ':count segondra', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mgh.php b/vendor/nesbot/carbon/src/Carbon/Lang/mgh.php new file mode 100644 index 0000000..2a80960 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mgh.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['wichishu', 'mchochil’l'], + 'weekdays' => ['Sabato', 'Jumatatu', 'Jumanne', 'Jumatano', 'Arahamisi', 'Ijumaa', 'Jumamosi'], + 'weekdays_short' => ['Sab', 'Jtt', 'Jnn', 'Jtn', 'Ara', 'Iju', 'Jmo'], + 'weekdays_min' => ['Sab', 'Jtt', 'Jnn', 'Jtn', 'Ara', 'Iju', 'Jmo'], + 'months' => ['Mweri wo kwanza', 'Mweri wo unayeli', 'Mweri wo uneraru', 'Mweri wo unecheshe', 'Mweri wo unethanu', 'Mweri wo thanu na mocha', 'Mweri wo saba', 'Mweri wo nane', 'Mweri wo tisa', 'Mweri wo kumi', 'Mweri wo kumi na moja', 'Mweri wo kumi na yel’li'], + 'months_short' => ['Kwa', 'Una', 'Rar', 'Che', 'Tha', 'Moc', 'Sab', 'Nan', 'Tis', 'Kum', 'Moj', 'Yel'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mgo.php b/vendor/nesbot/carbon/src/Carbon/Lang/mgo.php new file mode 100644 index 0000000..a126c9f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mgo.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'weekdays' => ['Aneg 1', 'Aneg 2', 'Aneg 3', 'Aneg 4', 'Aneg 5', 'Aneg 6', 'Aneg 7'], + 'weekdays_short' => ['Aneg 1', 'Aneg 2', 'Aneg 3', 'Aneg 4', 'Aneg 5', 'Aneg 6', 'Aneg 7'], + 'weekdays_min' => ['1', '2', '3', '4', '5', '6', '7'], + 'months' => ['iməg mbegtug', 'imeg àbùbì', 'imeg mbəŋchubi', 'iməg ngwə̀t', 'iməg fog', 'iməg ichiibɔd', 'iməg àdùmbə̀ŋ', 'iməg ichika', 'iməg kud', 'iməg tèsiʼe', 'iməg zò', 'iməg krizmed'], + 'months_short' => ['mbegtug', 'imeg àbùbì', 'imeg mbəŋchubi', 'iməg ngwə̀t', 'iməg fog', 'iməg ichiibɔd', 'iməg àdùmbə̀ŋ', 'iməg ichika', 'iməg kud', 'iməg tèsiʼe', 'iməg zò', 'iməg krizmed'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-dd', + 'LL' => 'YYYY MMM D', + 'LLL' => 'YYYY MMMM D HH:mm', + 'LLLL' => 'dddd, YYYY MMMM DD HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mhr.php b/vendor/nesbot/carbon/src/Carbon/Lang/mhr.php new file mode 100644 index 0000000..6bbc9f6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mhr.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/mhr_RU.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mhr_RU.php b/vendor/nesbot/carbon/src/Carbon/Lang/mhr_RU.php new file mode 100644 index 0000000..309ead9 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mhr_RU.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - PeshSajSoft Ltd. Vyacheslav Kileev slavakileev@yandex.ru + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYY.MM.DD', + ], + 'months' => ['Шорыкйол', 'Пургыж', 'Ӱярня', 'Вӱдшор', 'Ага', 'Пеледыш', 'Сӱрем', 'Сорла', 'Идым', 'Шыжа', 'Кылме', 'Теле'], + 'months_short' => ['Шрк', 'Пгж', 'Ӱрн', 'Вшр', 'Ага', 'Пдш', 'Срм', 'Срл', 'Идм', 'Шыж', 'Клм', 'Тел'], + 'weekdays' => ['Рушарня', 'Шочмо', 'Кушкыжмо', 'Вӱргече', 'Изарня', 'Кугарня', 'Шуматкече'], + 'weekdays_short' => ['Ршр', 'Шчм', 'Кжм', 'Вгч', 'Изр', 'Кгр', 'Шмт'], + 'weekdays_min' => ['Ршр', 'Шчм', 'Кжм', 'Вгч', 'Изр', 'Кгр', 'Шмт'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'year' => ':count идалык', + 'y' => ':count идалык', + 'a_year' => ':count идалык', + + 'month' => ':count Тылзе', + 'm' => ':count Тылзе', + 'a_month' => ':count Тылзе', + + 'week' => ':count арня', + 'w' => ':count арня', + 'a_week' => ':count арня', + + 'day' => ':count кече', + 'd' => ':count кече', + 'a_day' => ':count кече', + + 'hour' => ':count час', + 'h' => ':count час', + 'a_hour' => ':count час', + + 'minute' => ':count минут', + 'min' => ':count минут', + 'a_minute' => ':count минут', + + 'second' => ':count кокымшан', + 's' => ':count кокымшан', + 'a_second' => ':count кокымшан', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mi.php b/vendor/nesbot/carbon/src/Carbon/Lang/mi.php new file mode 100644 index 0000000..b7f51ec --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mi.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - John Corrigan + * - François B + */ +return [ + 'year' => ':count tau', + 'a_year' => '{1}he tau|:count tau', + 'month' => ':count marama', + 'a_month' => '{1}he marama|:count marama', + 'week' => ':count wiki', + 'a_week' => '{1}he wiki|:count wiki', + 'day' => ':count ra', + 'a_day' => '{1}he ra|:count ra', + 'hour' => ':count haora', + 'a_hour' => '{1}te haora|:count haora', + 'minute' => ':count meneti', + 'a_minute' => '{1}he meneti|:count meneti', + 'second' => ':count hēkona', + 'a_second' => '{1}te hēkona ruarua|:count hēkona', + 'ago' => ':time i mua', + 'from_now' => 'i roto i :time', + 'diff_yesterday' => 'inanahi', + 'diff_yesterday_regexp' => 'inanahi(?:\\s+i)?', + 'diff_today' => 'i teie', + 'diff_today_regexp' => 'i teie(?:\\s+mahana,)?(?:\\s+i)?', + 'diff_tomorrow' => 'apopo', + 'diff_tomorrow_regexp' => 'apopo(?:\\s+i)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY [i] HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY [i] HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[i teie mahana, i] LT', + 'nextDay' => '[apopo i] LT', + 'nextWeek' => 'dddd [i] LT', + 'lastDay' => '[inanahi i] LT', + 'lastWeek' => 'dddd [whakamutunga i] LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':numberº', + 'months' => ['Kohi-tāte', 'Hui-tanguru', 'Poutū-te-rangi', 'Paenga-whāwhā', 'Haratua', 'Pipiri', 'Hōngoingoi', 'Here-turi-kōkā', 'Mahuru', 'Whiringa-ā-nuku', 'Whiringa-ā-rangi', 'Hakihea'], + 'months_short' => ['Kohi', 'Hui', 'Pou', 'Pae', 'Hara', 'Pipi', 'Hōngoi', 'Here', 'Mahu', 'Whi-nu', 'Whi-ra', 'Haki'], + 'weekdays' => ['Rātapu', 'Mane', 'Tūrei', 'Wenerei', 'Tāite', 'Paraire', 'Hātarei'], + 'weekdays_short' => ['Ta', 'Ma', 'Tū', 'We', 'Tāi', 'Pa', 'Hā'], + 'weekdays_min' => ['Ta', 'Ma', 'Tū', 'We', 'Tāi', 'Pa', 'Hā'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' me te '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mi_NZ.php b/vendor/nesbot/carbon/src/Carbon/Lang/mi_NZ.php new file mode 100644 index 0000000..6b964e3 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mi_NZ.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/mi.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/miq.php b/vendor/nesbot/carbon/src/Carbon/Lang/miq.php new file mode 100644 index 0000000..51e5a98 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/miq.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/miq_NI.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/miq_NI.php b/vendor/nesbot/carbon/src/Carbon/Lang/miq_NI.php new file mode 100644 index 0000000..57faa31 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/miq_NI.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['siakwa kati', 'kuswa kati', 'kakamuk kati', 'lî wainhka kati', 'lih mairin kati', 'lî kati', 'pastara kati', 'sikla kati', 'wîs kati', 'waupasa kati', 'yahbra kati', 'trisu kati'], + 'months_short' => ['siakwa kati', 'kuswa kati', 'kakamuk kati', 'lî wainhka kati', 'lih mairin kati', 'lî kati', 'pastara kati', 'sikla kati', 'wîs kati', 'waupasa kati', 'yahbra kati', 'trisu kati'], + 'weekdays' => ['sandi', 'mundi', 'tiusdi', 'wensde', 'tausde', 'praidi', 'satadi'], + 'weekdays_short' => ['san', 'mun', 'tius', 'wens', 'taus', 'prai', 'sat'], + 'weekdays_min' => ['san', 'mun', 'tius', 'wens', 'taus', 'prai', 'sat'], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 7, + 'meridiem' => ['VM', 'NM'], + + 'month' => ':count kati', // less reliable + 'm' => ':count kati', // less reliable + 'a_month' => ':count kati', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mjw.php b/vendor/nesbot/carbon/src/Carbon/Lang/mjw.php new file mode 100644 index 0000000..617154c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mjw.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/mjw_IN.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mjw_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/mjw_IN.php new file mode 100644 index 0000000..58ed0d1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mjw_IN.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Jor Teron bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['Arkoi', 'Thangthang', 'There', 'Jangmi', 'Aru', 'Vosik', 'Jakhong', 'Paipai', 'Chiti', 'Phere', 'Phaikuni', 'Matijong'], + 'months_short' => ['Ark', 'Thang', 'The', 'Jang', 'Aru', 'Vos', 'Jak', 'Pai', 'Chi', 'Phe', 'Phai', 'Mati'], + 'weekdays' => ['Bhomkuru', 'Urmi', 'Durmi', 'Thelang', 'Theman', 'Bhomta', 'Bhomti'], + 'weekdays_short' => ['Bhom', 'Ur', 'Dur', 'Tkel', 'Tkem', 'Bhta', 'Bhti'], + 'weekdays_min' => ['Bhom', 'Ur', 'Dur', 'Tkel', 'Tkem', 'Bhta', 'Bhti'], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mk.php b/vendor/nesbot/carbon/src/Carbon/Lang/mk.php new file mode 100644 index 0000000..d822de0 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mk.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Sashko Todorov + * - Josh Soref + * - François B + * - Serhan Apaydın + * - Borislav Mickov + * - JD Isaacks + * - Tomi Atanasoski + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count година|:count години', + 'a_year' => 'година|:count години', + 'y' => ':count год.', + 'month' => ':count месец|:count месеци', + 'a_month' => 'месец|:count месеци', + 'm' => ':count месец|:count месеци', + 'week' => ':count седмица|:count седмици', + 'a_week' => 'седмица|:count седмици', + 'w' => ':count седмица|:count седмици', + 'day' => ':count ден|:count дена', + 'a_day' => 'ден|:count дена', + 'd' => ':count ден|:count дена', + 'hour' => ':count час|:count часа', + 'a_hour' => 'час|:count часа', + 'h' => ':count час|:count часа', + 'minute' => ':count минута|:count минути', + 'a_minute' => 'минута|:count минути', + 'min' => ':count мин.', + 'second' => ':count секунда|:count секунди', + 'a_second' => 'неколку секунди|:count секунди', + 's' => ':count сек.', + 'ago' => 'пред :time', + 'from_now' => 'после :time', + 'after' => 'по :time', + 'before' => 'пред :time', + 'diff_now' => 'сега', + 'diff_today' => 'Денес', + 'diff_today_regexp' => 'Денес(?:\\s+во)?', + 'diff_yesterday' => 'вчера', + 'diff_yesterday_regexp' => 'Вчера(?:\\s+во)?', + 'diff_tomorrow' => 'утре', + 'diff_tomorrow_regexp' => 'Утре(?:\\s+во)?', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'D.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY H:mm', + 'LLLL' => 'dddd, D MMMM YYYY H:mm', + ], + 'calendar' => [ + 'sameDay' => '[Денес во] LT', + 'nextDay' => '[Утре во] LT', + 'nextWeek' => '[Во] dddd [во] LT', + 'lastDay' => '[Вчера во] LT', + 'lastWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + case 3: + case 6: + return '[Изминатата] dddd [во] LT'; + default: + return '[Изминатиот] dddd [во] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + $lastDigit = $number % 10; + $last2Digits = $number % 100; + if ($number === 0) { + return $number.'-ев'; + } + if ($last2Digits === 0) { + return $number.'-ен'; + } + if ($last2Digits > 10 && $last2Digits < 20) { + return $number.'-ти'; + } + if ($lastDigit === 1) { + return $number.'-ви'; + } + if ($lastDigit === 2) { + return $number.'-ри'; + } + if ($lastDigit === 7 || $lastDigit === 8) { + return $number.'-ми'; + } + + return $number.'-ти'; + }, + 'months' => ['јануари', 'февруари', 'март', 'април', 'мај', 'јуни', 'јули', 'август', 'септември', 'октомври', 'ноември', 'декември'], + 'months_short' => ['јан', 'фев', 'мар', 'апр', 'мај', 'јун', 'јул', 'авг', 'сеп', 'окт', 'ное', 'дек'], + 'weekdays' => ['недела', 'понеделник', 'вторник', 'среда', 'четврток', 'петок', 'сабота'], + 'weekdays_short' => ['нед', 'пон', 'вто', 'сре', 'чет', 'пет', 'саб'], + 'weekdays_min' => ['нe', 'пo', 'вт', 'ср', 'че', 'пе', 'сa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' и '], + 'meridiem' => ['АМ', 'ПМ'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mk_MK.php b/vendor/nesbot/carbon/src/Carbon/Lang/mk_MK.php new file mode 100644 index 0000000..95e2ff9 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mk_MK.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/mk.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ml.php b/vendor/nesbot/carbon/src/Carbon/Lang/ml.php new file mode 100644 index 0000000..1abd6c4 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ml.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - JD Isaacks + */ +return [ + 'year' => ':count വർഷം', + 'a_year' => 'ഒരു വർഷം|:count വർഷം', + 'month' => ':count മാസം', + 'a_month' => 'ഒരു മാസം|:count മാസം', + 'week' => ':count ആഴ്ച', + 'a_week' => 'ഒരാഴ്ച|:count ആഴ്ച', + 'day' => ':count ദിവസം', + 'a_day' => 'ഒരു ദിവസം|:count ദിവസം', + 'hour' => ':count മണിക്കൂർ', + 'a_hour' => 'ഒരു മണിക്കൂർ|:count മണിക്കൂർ', + 'minute' => ':count മിനിറ്റ്', + 'a_minute' => 'ഒരു മിനിറ്റ്|:count മിനിറ്റ്', + 'second' => ':count സെക്കൻഡ്', + 'a_second' => 'അൽപ നിമിഷങ്ങൾ|:count സെക്കൻഡ്', + 'ago' => ':time മുൻപ്', + 'from_now' => ':time കഴിഞ്ഞ്', + 'diff_now' => 'ഇപ്പോൾ', + 'diff_today' => 'ഇന്ന്', + 'diff_yesterday' => 'ഇന്നലെ', + 'diff_tomorrow' => 'നാളെ', + 'formats' => [ + 'LT' => 'A h:mm -നു', + 'LTS' => 'A h:mm:ss -നു', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, A h:mm -നു', + 'LLLL' => 'dddd, D MMMM YYYY, A h:mm -നു', + ], + 'calendar' => [ + 'sameDay' => '[ഇന്ന്] LT', + 'nextDay' => '[നാളെ] LT', + 'nextWeek' => 'dddd, LT', + 'lastDay' => '[ഇന്നലെ] LT', + 'lastWeek' => '[കഴിഞ്ഞ] dddd, LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'രാത്രി'; + } + if ($hour < 12) { + return 'രാവിലെ'; + } + if ($hour < 17) { + return 'ഉച്ച കഴിഞ്ഞ്'; + } + if ($hour < 20) { + return 'വൈകുന്നേരം'; + } + + return 'രാത്രി'; + }, + 'months' => ['ജനുവരി', 'ഫെബ്രുവരി', 'മാർച്ച്', 'ഏപ്രിൽ', 'മേയ്', 'ജൂൺ', 'ജൂലൈ', 'ഓഗസ്റ്റ്', 'സെപ്റ്റംബർ', 'ഒക്ടോബർ', 'നവംബർ', 'ഡിസംബർ'], + 'months_short' => ['ജനു.', 'ഫെബ്രു.', 'മാർ.', 'ഏപ്രി.', 'മേയ്', 'ജൂൺ', 'ജൂലൈ.', 'ഓഗ.', 'സെപ്റ്റ.', 'ഒക്ടോ.', 'നവം.', 'ഡിസം.'], + 'weekdays' => ['ഞായറാഴ്ച', 'തിങ്കളാഴ്ച', 'ചൊവ്വാഴ്ച', 'ബുധനാഴ്ച', 'വ്യാഴാഴ്ച', 'വെള്ളിയാഴ്ച', 'ശനിയാഴ്ച'], + 'weekdays_short' => ['ഞായർ', 'തിങ്കൾ', 'ചൊവ്വ', 'ബുധൻ', 'വ്യാഴം', 'വെള്ളി', 'ശനി'], + 'weekdays_min' => ['ഞാ', 'തി', 'ചൊ', 'ബു', 'വ്യാ', 'വെ', 'ശ'], + 'list' => ', ', + 'weekend' => [0, 0], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ml_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/ml_IN.php new file mode 100644 index 0000000..000e795 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ml_IN.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ml.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mn.php b/vendor/nesbot/carbon/src/Carbon/Lang/mn.php new file mode 100644 index 0000000..717d199 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mn.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - Tsutomu Kuroda + * - tjku + * - Max Melentiev + * - Zolzaya Erdenebaatar + * - Tom Hughes + * - Akira Matsuda + * - Christopher Dell + * - Michael Kessler + * - Enrique Vidal + * - Simone Carletti + * - Aaron Patterson + * - Nicolás Hock Isaza + * - Ochirkhuyag + * - Batmandakh + */ +return [ + 'year' => ':count жил', + 'y' => ':count жил', + 'month' => ':count сар', + 'm' => ':count сар', + 'week' => ':count долоо хоног', + 'w' => ':count долоо хоног', + 'day' => ':count өдөр', + 'd' => ':count өдөр', + 'hour' => ':count цаг', + 'h' => ':countц', + 'minute' => ':count минут', + 'min' => ':countм', + 'second' => ':count секунд', + 's' => ':countс', + + 'ago' => ':timeн өмнө', + 'year_ago' => ':count жилий', + 'month_ago' => ':count сары', + 'day_ago' => ':count хоногий', + 'hour_ago' => ':count цагий', + 'minute_ago' => ':count минуты', + 'second_ago' => ':count секунды', + + 'from_now' => 'одоогоос :time', + 'year_from_now' => ':count жилийн дараа', + 'month_from_now' => ':count сарын дараа', + 'day_from_now' => ':count хоногийн дараа', + 'hour_from_now' => ':count цагийн дараа', + 'minute_from_now' => ':count минутын дараа', + 'second_from_now' => ':count секундын дараа', + + // Does it required to make translation for before, after as follows? hmm, I think we've made it with ago and from now keywords already. Anyway, I've included it just in case of undesired action... + 'after' => ':timeн дараа', + 'year_after' => ':count жилий', + 'month_after' => ':count сары', + 'day_after' => ':count хоногий', + 'hour_after' => ':count цагий', + 'minute_after' => ':count минуты', + 'second_after' => ':count секунды', + + 'before' => ':timeн өмнө', + 'year_before' => ':count жилий', + 'month_before' => ':count сары', + 'day_before' => ':count хоногий', + 'hour_before' => ':count цагий', + 'minute_before' => ':count минуты', + 'second_before' => ':count секунды', + + 'list' => ', ', + 'diff_now' => 'одоо', + 'diff_yesterday' => 'өчигдөр', + 'diff_tomorrow' => 'маргааш', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-DD', + 'LL' => 'YYYY MMMM DD', + 'LLL' => 'YY-MM-DD, HH:mm', + 'LLLL' => 'YYYY MMMM DD, HH:mm', + ], + 'weekdays' => ['Ням', 'Даваа', 'Мягмар', 'Лхагва', 'Пүрэв', 'Баасан', 'Бямба'], + 'weekdays_short' => ['Ня', 'Да', 'Мя', 'Лх', 'Пү', 'Ба', 'Бя'], + 'weekdays_min' => ['Ня', 'Да', 'Мя', 'Лх', 'Пү', 'Ба', 'Бя'], + 'months' => ['1 сар', '2 сар', '3 сар', '4 сар', '5 сар', '6 сар', '7 сар', '8 сар', '9 сар', '10 сар', '11 сар', '12 сар'], + 'months_short' => ['1 сар', '2 сар', '3 сар', '4 сар', '5 сар', '6 сар', '7 сар', '8 сар', '9 сар', '10 сар', '11 сар', '12 сар'], + 'meridiem' => ['өглөө', 'орой'], + 'first_day_of_week' => 1, +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mn_MN.php b/vendor/nesbot/carbon/src/Carbon/Lang/mn_MN.php new file mode 100644 index 0000000..e5ce426 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mn_MN.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/mn.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mni.php b/vendor/nesbot/carbon/src/Carbon/Lang/mni.php new file mode 100644 index 0000000..cafa2f8 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mni.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/mni_IN.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mni_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/mni_IN.php new file mode 100644 index 0000000..45d430e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mni_IN.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Red Hat Pune libc-alpha@sourceware.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['জানুৱারি', 'ফেব্রুৱারি', 'মার্চ', 'এপ্রিল', 'মে', 'জুন', 'জুলাই', 'আগষ্ট', 'সেপ্তেম্বর', 'ওক্তোবর', 'নবেম্বর', 'ডিসেম্বর'], + 'months_short' => ['জান', 'ফেব', 'মার', 'এপ্রি', 'মে', 'জুন', 'জুল', 'আগ', 'সেপ', 'ওক্ত', 'নবে', 'ডিস'], + 'weekdays' => ['নোংমাইজিং', 'নিংথৌকাবা', 'লৈবাকপোকপা', 'য়ুমশকৈশা', 'শগোলশেন', 'ইরাই', 'থাংজ'], + 'weekdays_short' => ['নোং', 'নিং', 'লৈবাক', 'য়ুম', 'শগোল', 'ইরা', 'থাং'], + 'weekdays_min' => ['নোং', 'নিং', 'লৈবাক', 'য়ুম', 'শগোল', 'ইরা', 'থাং'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['এ.ম.', 'প.ম.'], + + 'year' => ':count ইসিং', // less reliable + 'y' => ':count ইসিং', // less reliable + 'a_year' => ':count ইসিং', // less reliable + + 'second' => ':count ꯅꯤꯡꯊꯧꯀꯥꯕ', // less reliable + 's' => ':count ꯅꯤꯡꯊꯧꯀꯥꯕ', // less reliable + 'a_second' => ':count ꯅꯤꯡꯊꯧꯀꯥꯕ', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mo.php b/vendor/nesbot/carbon/src/Carbon/Lang/mo.php new file mode 100644 index 0000000..102afcd --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mo.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ro.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mr.php b/vendor/nesbot/carbon/src/Carbon/Lang/mr.php new file mode 100644 index 0000000..4aaeafd --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mr.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Vikram-enyota + */ +return [ + 'year' => ':count वर्ष', + 'y' => ':count वर्ष', + 'month' => ':count महिना|:count महिने', + 'm' => ':count महिना|:count महिने', + 'week' => ':count आठवडा|:count आठवडे', + 'w' => ':count आठवडा|:count आठवडे', + 'day' => ':count दिवस', + 'd' => ':count दिवस', + 'hour' => ':count तास', + 'h' => ':count तास', + 'minute' => ':count मिनिटे', + 'min' => ':count मिनिटे', + 'second' => ':count सेकंद', + 's' => ':count सेकंद', + + 'ago' => ':timeपूर्वी', + 'from_now' => ':timeमध्ये', + 'before' => ':timeपूर्वी', + 'after' => ':timeनंतर', + + 'diff_now' => 'आत्ता', + 'diff_today' => 'आज', + 'diff_yesterday' => 'काल', + 'diff_tomorrow' => 'उद्या', + + 'formats' => [ + 'LT' => 'A h:mm वाजता', + 'LTS' => 'A h:mm:ss वाजता', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, A h:mm वाजता', + 'LLLL' => 'dddd, D MMMM YYYY, A h:mm वाजता', + ], + + 'calendar' => [ + 'sameDay' => '[आज] LT', + 'nextDay' => '[उद्या] LT', + 'nextWeek' => 'dddd, LT', + 'lastDay' => '[काल] LT', + 'lastWeek' => '[मागील] dddd, LT', + 'sameElse' => 'L', + ], + + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'रात्री'; + } + if ($hour < 10) { + return 'सकाळी'; + } + if ($hour < 17) { + return 'दुपारी'; + } + if ($hour < 20) { + return 'सायंकाळी'; + } + + return 'रात्री'; + }, + + 'months' => ['जानेवारी', 'फेब्रुवारी', 'मार्च', 'एप्रिल', 'मे', 'जून', 'जुलै', 'ऑगस्ट', 'सप्टेंबर', 'ऑक्टोबर', 'नोव्हेंबर', 'डिसेंबर'], + 'months_short' => ['जाने.', 'फेब्रु.', 'मार्च.', 'एप्रि.', 'मे.', 'जून.', 'जुलै.', 'ऑग.', 'सप्टें.', 'ऑक्टो.', 'नोव्हें.', 'डिसें.'], + 'weekdays' => ['रविवार', 'सोमवार', 'मंगळवार', 'बुधवार', 'गुरूवार', 'शुक्रवार', 'शनिवार'], + 'weekdays_short' => ['रवि', 'सोम', 'मंगळ', 'बुध', 'गुरू', 'शुक्र', 'शनि'], + 'weekdays_min' => ['र', 'सो', 'मं', 'बु', 'गु', 'शु', 'श'], + 'list' => [', ', ' आणि '], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'weekend' => [0, 0], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mr_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/mr_IN.php new file mode 100644 index 0000000..7bca919 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mr_IN.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/mr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ms.php b/vendor/nesbot/carbon/src/Carbon/Lang/ms.php new file mode 100644 index 0000000..36934ee --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ms.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - Azri Jamil + * - JD Isaacks + * - Josh Soref + * - Azri Jamil + * - Hariadi Hinta + * - Ashraf Kamarudin + */ +return [ + 'year' => ':count tahun', + 'a_year' => '{1}setahun|]1,Inf[:count tahun', + 'y' => ':count tahun', + 'month' => ':count bulan', + 'a_month' => '{1}sebulan|]1,Inf[:count bulan', + 'm' => ':count bulan', + 'week' => ':count minggu', + 'a_week' => '{1}seminggu|]1,Inf[:count minggu', + 'w' => ':count minggu', + 'day' => ':count hari', + 'a_day' => '{1}sehari|]1,Inf[:count hari', + 'd' => ':count hari', + 'hour' => ':count jam', + 'a_hour' => '{1}sejam|]1,Inf[:count jam', + 'h' => ':count jam', + 'minute' => ':count minit', + 'a_minute' => '{1}seminit|]1,Inf[:count minit', + 'min' => ':count minit', + 'second' => ':count saat', + 'a_second' => '{1}beberapa saat|]1,Inf[:count saat', + 'millisecond' => ':count milisaat', + 'a_millisecond' => '{1}semilisaat|]1,Inf[:count milliseconds', + 'microsecond' => ':count mikrodetik', + 'a_microsecond' => '{1}semikrodetik|]1,Inf[:count mikrodetik', + 's' => ':count saat', + 'ago' => ':time yang lepas', + 'from_now' => ':time dari sekarang', + 'after' => ':time kemudian', + 'before' => ':time lepas', + 'diff_now' => 'sekarang', + 'diff_today' => 'Hari', + 'diff_today_regexp' => 'Hari(?:\\s+ini)?(?:\\s+pukul)?', + 'diff_yesterday' => 'semalam', + 'diff_yesterday_regexp' => 'Semalam(?:\\s+pukul)?', + 'diff_tomorrow' => 'esok', + 'diff_tomorrow_regexp' => 'Esok(?:\\s+pukul)?', + 'diff_before_yesterday' => 'kelmarin', + 'diff_after_tomorrow' => 'lusa', + 'formats' => [ + 'LT' => 'HH.mm', + 'LTS' => 'HH.mm.ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY [pukul] HH.mm', + 'LLLL' => 'dddd, D MMMM YYYY [pukul] HH.mm', + ], + 'calendar' => [ + 'sameDay' => '[Hari ini pukul] LT', + 'nextDay' => '[Esok pukul] LT', + 'nextWeek' => 'dddd [pukul] LT', + 'lastDay' => '[Kelmarin pukul] LT', + 'lastWeek' => 'dddd [lepas pukul] LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour) { + if ($hour < 1) { + return 'tengah malam'; + } + + if ($hour < 12) { + return 'pagi'; + } + + if ($hour < 13) { + return 'tengah hari'; + } + + if ($hour < 19) { + return 'petang'; + } + + return 'malam'; + }, + 'months' => ['Januari', 'Februari', 'Mac', 'April', 'Mei', 'Jun', 'Julai', 'Ogos', 'September', 'Oktober', 'November', 'Disember'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Jul', 'Ogs', 'Sep', 'Okt', 'Nov', 'Dis'], + 'weekdays' => ['Ahad', 'Isnin', 'Selasa', 'Rabu', 'Khamis', 'Jumaat', 'Sabtu'], + 'weekdays_short' => ['Ahd', 'Isn', 'Sel', 'Rab', 'Kha', 'Jum', 'Sab'], + 'weekdays_min' => ['Ah', 'Is', 'Sl', 'Rb', 'Km', 'Jm', 'Sb'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' dan '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ms_BN.php b/vendor/nesbot/carbon/src/Carbon/Lang/ms_BN.php new file mode 100644 index 0000000..ef837a2 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ms_BN.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ms.php', [ + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'D/MM/yy', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY, h:mm a', + 'LLLL' => 'dd MMMM YYYY, h:mm a', + ], + 'meridiem' => ['a', 'p'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ms_MY.php b/vendor/nesbot/carbon/src/Carbon/Lang/ms_MY.php new file mode 100644 index 0000000..970d604 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ms_MY.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - Azri Jamil + * - JD Isaacks + */ +return require __DIR__.'/ms.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ms_SG.php b/vendor/nesbot/carbon/src/Carbon/Lang/ms_SG.php new file mode 100644 index 0000000..77cb83d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ms_SG.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ms.php', [ + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'D/MM/yy', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY, h:mm a', + 'LLLL' => 'dddd, D MMMM YYYY, h:mm a', + ], + 'meridiem' => ['a', 'p'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mt.php b/vendor/nesbot/carbon/src/Carbon/Lang/mt.php new file mode 100644 index 0000000..e8aadcc --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mt.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Alessandro Maruccia + */ +return [ + 'year' => 'sena|:count sni|:count sni|:count sni', + 'y' => 'sa sena|:count snin|:count snin|:count snin', + 'month' => 'xahar|:count xhur|:count xhur|:count xhur', + 'm' => ':count xahar|:count xhur|:count xhur|:count xhur', + 'week' => 'gimgħa|:count ġimgħat|:count ġimgħat|:count ġimgħat', + 'w' => 'ġimgħa|:count ġimgħat|:count ġimgħat|:count ġimgħat', + 'day' => 'ġurnata|:count ġranet|:count ġranet|:count ġranet', + 'd' => 'ġurnata|:count ġranet|:count ġranet|:count ġranet', + 'hour' => 'siegħa|:count siegħat|:count siegħat|:count siegħat', + 'h' => 'siegħa|:count sigħat|:count sigħat|:count sigħat', + 'minute' => 'minuta|:count minuti|:count minuti|:count minuti', + 'min' => 'min.|:count min.|:count min.|:count min.', + 'second' => 'ftit sekondi|:count sekondi|:count sekondi|:count sekondi', + 's' => 'sek.|:count sek.|:count sek.|:count sek.', + 'ago' => ':time ilu', + 'from_now' => 'f’ :time', + 'diff_now' => 'issa', + 'diff_today' => 'Illum', + 'diff_today_regexp' => 'Illum(?:\\s+fil-)?', + 'diff_yesterday' => 'lbieraħ', + 'diff_yesterday_regexp' => 'Il-bieraħ(?:\\s+fil-)?', + 'diff_tomorrow' => 'għada', + 'diff_tomorrow_regexp' => 'Għada(?:\\s+fil-)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Illum fil-]LT', + 'nextDay' => '[Għada fil-]LT', + 'nextWeek' => 'dddd [fil-]LT', + 'lastDay' => '[Il-bieraħ fil-]LT', + 'lastWeek' => 'dddd [li għadda] [fil-]LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':numberº', + 'months' => ['Jannar', 'Frar', 'Marzu', 'April', 'Mejju', 'Ġunju', 'Lulju', 'Awwissu', 'Settembru', 'Ottubru', 'Novembru', 'Diċembru'], + 'months_short' => ['Jan', 'Fra', 'Mar', 'Apr', 'Mej', 'Ġun', 'Lul', 'Aww', 'Set', 'Ott', 'Nov', 'Diċ'], + 'weekdays' => ['Il-Ħadd', 'It-Tnejn', 'It-Tlieta', 'L-Erbgħa', 'Il-Ħamis', 'Il-Ġimgħa', 'Is-Sibt'], + 'weekdays_short' => ['Ħad', 'Tne', 'Tli', 'Erb', 'Ħam', 'Ġim', 'Sib'], + 'weekdays_min' => ['Ħa', 'Tn', 'Tl', 'Er', 'Ħa', 'Ġi', 'Si'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' u '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mt_MT.php b/vendor/nesbot/carbon/src/Carbon/Lang/mt_MT.php new file mode 100644 index 0000000..9534f68 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mt_MT.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/mt.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mua.php b/vendor/nesbot/carbon/src/Carbon/Lang/mua.php new file mode 100644 index 0000000..a3a3c6f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mua.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['comme', 'lilli'], + 'weekdays' => ['Com’yakke', 'Comlaaɗii', 'Comzyiiɗii', 'Comkolle', 'Comkaldǝɓlii', 'Comgaisuu', 'Comzyeɓsuu'], + 'weekdays_short' => ['Cya', 'Cla', 'Czi', 'Cko', 'Cka', 'Cga', 'Cze'], + 'weekdays_min' => ['Cya', 'Cla', 'Czi', 'Cko', 'Cka', 'Cga', 'Cze'], + 'months' => ['Fĩi Loo', 'Cokcwaklaŋne', 'Cokcwaklii', 'Fĩi Marfoo', 'Madǝǝuutǝbijaŋ', 'Mamǝŋgwãafahbii', 'Mamǝŋgwãalii', 'Madǝmbii', 'Fĩi Dǝɓlii', 'Fĩi Mundaŋ', 'Fĩi Gwahlle', 'Fĩi Yuru'], + 'months_short' => ['FLO', 'CLA', 'CKI', 'FMF', 'MAD', 'MBI', 'MLI', 'MAM', 'FDE', 'FMU', 'FGW', 'FYU'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/my.php b/vendor/nesbot/carbon/src/Carbon/Lang/my.php new file mode 100644 index 0000000..bbdfba4 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/my.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - JD Isaacks + * - Nay Lin Aung + */ +return [ + 'year' => '{1}တစ်နှစ်|]1,Inf[:count နှစ်', + 'y' => ':count နှစ်', + 'month' => '{1}တစ်လ|]1,Inf[:count လ', + 'm' => ':count လ', + 'week' => ':count ပတ်', + 'w' => ':count ပတ်', + 'day' => '{1}တစ်ရက်|]1,Inf[:count ရက်', + 'd' => ':count ရက်', + 'hour' => '{1}တစ်နာရီ|]1,Inf[:count နာရီ', + 'h' => ':count နာရီ', + 'minute' => '{1}တစ်မိနစ်|]1,Inf[:count မိနစ်', + 'min' => ':count မိနစ်', + 'second' => '{1}စက္ကန်.အနည်းငယ်|]1,Inf[:count စက္ကန့်', + 's' => ':count စက္ကန့်', + 'ago' => 'လွန်ခဲ့သော :time က', + 'from_now' => 'လာမည့် :time မှာ', + 'after' => ':time ကြာပြီးနောက်', + 'before' => ':time မတိုင်ခင်', + 'diff_now' => 'အခုလေးတင်', + 'diff_today' => 'ယနေ.', + 'diff_yesterday' => 'မနေ့က', + 'diff_yesterday_regexp' => 'မနေ.က', + 'diff_tomorrow' => 'မနက်ဖြန်', + 'diff_before_yesterday' => 'တမြန်နေ့က', + 'diff_after_tomorrow' => 'တဘက်ခါ', + 'period_recurrences' => ':count ကြိမ်', + 'formats' => [ + 'LT' => 'Oh:Om A', + 'LTS' => 'Oh:Om:Os A', + 'L' => 'OD/OM/OY', + 'LL' => 'OD MMMM OY', + 'LLL' => 'OD MMMM OY Oh:Om A', + 'LLLL' => 'dddd OD MMMM OY Oh:Om A', + ], + 'calendar' => [ + 'sameDay' => '[ယနေ.] LT [မှာ]', + 'nextDay' => '[မနက်ဖြန်] LT [မှာ]', + 'nextWeek' => 'dddd LT [မှာ]', + 'lastDay' => '[မနေ.က] LT [မှာ]', + 'lastWeek' => '[ပြီးခဲ့သော] dddd LT [မှာ]', + 'sameElse' => 'L', + ], + 'months' => ['ဇန်နဝါရီ', 'ဖေဖော်ဝါရီ', 'မတ်', 'ဧပြီ', 'မေ', 'ဇွန်', 'ဇူလိုင်', 'သြဂုတ်', 'စက်တင်ဘာ', 'အောက်တိုဘာ', 'နိုဝင်ဘာ', 'ဒီဇင်ဘာ'], + 'months_short' => ['ဇန်', 'ဖေ', 'မတ်', 'ပြီ', 'မေ', 'ဇွန်', 'လိုင်', 'သြ', 'စက်', 'အောက်', 'နို', 'ဒီ'], + 'weekdays' => ['တနင်္ဂနွေ', 'တနင်္လာ', 'အင်္ဂါ', 'ဗုဒ္ဓဟူး', 'ကြာသပတေး', 'သောကြာ', 'စနေ'], + 'weekdays_short' => ['နွေ', 'လာ', 'ဂါ', 'ဟူး', 'ကြာ', 'သော', 'နေ'], + 'weekdays_min' => ['နွေ', 'လာ', 'ဂါ', 'ဟူး', 'ကြာ', 'သော', 'နေ'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'alt_numbers' => ['၀၀', '၀၁', '၀၂', '၀၃', '၀၄', '၀၅', '၀၆', '၀၇', '၀၈', '၀၉', '၁၀', '၁၁', '၁၂', '၁၃', '၁၄', '၁၅', '၁၆', '၁၇', '၁၈', '၁၉', '၂၀', '၂၁', '၂၂', '၂၃', '၂၄', '၂၅', '၂၆', '၂၇', '၂၈', '၂၉', '၃၀', '၃၁', '၃၂', '၃၃', '၃၄', '၃၅', '၃၆', '၃၇', '၃၈', '၃၉', '၄၀', '၄၁', '၄၂', '၄၃', '၄၄', '၄၅', '၄၆', '၄၇', '၄၈', '၄၉', '၅၀', '၅၁', '၅၂', '၅၃', '၅၄', '၅၅', '၅၆', '၅၇', '၅၈', '၅၉', '၆၀', '၆၁', '၆၂', '၆၃', '၆၄', '၆၅', '၆၆', '၆၇', '၆၈', '၆၉', '၇၀', '၇၁', '၇၂', '၇၃', '၇၄', '၇၅', '၇၆', '၇၇', '၇၈', '၇၉', '၈၀', '၈၁', '၈၂', '၈၃', '၈၄', '၈၅', '၈၆', '၈၇', '၈၈', '၈၉', '၉၀', '၉၁', '၉၂', '၉၃', '၉၄', '၉၅', '၉၆', '၉၇', '၉၈', '၉၉'], + 'meridiem' => ['နံနက်', 'ညနေ'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/my_MM.php b/vendor/nesbot/carbon/src/Carbon/Lang/my_MM.php new file mode 100644 index 0000000..a0108dd --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/my_MM.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/my.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/mzn.php b/vendor/nesbot/carbon/src/Carbon/Lang/mzn.php new file mode 100644 index 0000000..70f5f23 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/mzn.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fa.php', [ + 'months' => ['ژانویه', 'فوریه', 'مارس', 'آوریل', 'مه', 'ژوئن', 'ژوئیه', 'اوت', 'سپتامبر', 'اکتبر', 'نوامبر', 'دسامبر'], + 'months_short' => ['ژانویه', 'فوریه', 'مارس', 'آوریل', 'مه', 'ژوئن', 'ژوئیه', 'اوت', 'سپتامبر', 'اکتبر', 'نوامبر', 'دسامبر'], + 'first_day_of_week' => 6, + 'weekend' => [5, 5], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-dd', + 'LL' => 'YYYY MMM D', + 'LLL' => 'YYYY MMMM D HH:mm', + 'LLLL' => 'YYYY MMMM D, dddd HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nan.php b/vendor/nesbot/carbon/src/Carbon/Lang/nan.php new file mode 100644 index 0000000..0affece --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nan.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/nan_TW.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nan_TW.php b/vendor/nesbot/carbon/src/Carbon/Lang/nan_TW.php new file mode 100644 index 0000000..5c50aa4 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nan_TW.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYY年MM月DD日', + ], + 'months' => ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'], + 'months_short' => [' 1月', ' 2月', ' 3月', ' 4月', ' 5月', ' 6月', ' 7月', ' 8月', ' 9月', '10月', '11月', '12月'], + 'weekdays' => ['禮拜日', '禮拜一', '禮拜二', '禮拜三', '禮拜四', '禮拜五', '禮拜六'], + 'weekdays_short' => ['日', '一', '二', '三', '四', '五', '六'], + 'weekdays_min' => ['日', '一', '二', '三', '四', '五', '六'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['頂晡', '下晡'], + + 'year' => ':count 年', + 'y' => ':count 年', + 'a_year' => ':count 年', + + 'month' => ':count goe̍h', + 'm' => ':count goe̍h', + 'a_month' => ':count goe̍h', + + 'week' => ':count lé-pài', + 'w' => ':count lé-pài', + 'a_week' => ':count lé-pài', + + 'day' => ':count 日', + 'd' => ':count 日', + 'a_day' => ':count 日', + + 'hour' => ':count tiám-cheng', + 'h' => ':count tiám-cheng', + 'a_hour' => ':count tiám-cheng', + + 'minute' => ':count Hun-cheng', + 'min' => ':count Hun-cheng', + 'a_minute' => ':count Hun-cheng', + + 'second' => ':count Bió', + 's' => ':count Bió', + 'a_second' => ':count Bió', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nan_TW@latin.php b/vendor/nesbot/carbon/src/Carbon/Lang/nan_TW@latin.php new file mode 100644 index 0000000..99ca2a4 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nan_TW@latin.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Arne Goetje arne@canonical.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYY-MM-DD', + ], + 'months' => ['1goe̍h', '2goe̍h', '3goe̍h', '4goe̍h', '5goe̍h', '6goe̍h', '7goe̍h', '8goe̍h', '9goe̍h', '10goe̍h', '11goe̍h', '12goe̍h'], + 'months_short' => ['1g', '2g', '3g', '4g', '5g', '6g', '7g', '8g', '9g', '10g', '11g', '12g'], + 'weekdays' => ['lé-pài-ji̍t', 'pài-it', 'pài-jī', 'pài-saⁿ', 'pài-sì', 'pài-gō͘', 'pài-la̍k'], + 'weekdays_short' => ['lp', 'p1', 'p2', 'p3', 'p4', 'p5', 'p6'], + 'weekdays_min' => ['lp', 'p1', 'p2', 'p3', 'p4', 'p5', 'p6'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['téng-po͘', 'ē-po͘'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/naq.php b/vendor/nesbot/carbon/src/Carbon/Lang/naq.php new file mode 100644 index 0000000..fbd9be9 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/naq.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['ǁgoagas', 'ǃuias'], + 'weekdays' => ['Sontaxtsees', 'Mantaxtsees', 'Denstaxtsees', 'Wunstaxtsees', 'Dondertaxtsees', 'Fraitaxtsees', 'Satertaxtsees'], + 'weekdays_short' => ['Son', 'Ma', 'De', 'Wu', 'Do', 'Fr', 'Sat'], + 'weekdays_min' => ['Son', 'Ma', 'De', 'Wu', 'Do', 'Fr', 'Sat'], + 'months' => ['ǃKhanni', 'ǃKhanǀgôab', 'ǀKhuuǁkhâb', 'ǃHôaǂkhaib', 'ǃKhaitsâb', 'Gamaǀaeb', 'ǂKhoesaob', 'Aoǁkhuumûǁkhâb', 'Taraǀkhuumûǁkhâb', 'ǂNûǁnâiseb', 'ǀHooǂgaeb', 'Hôasoreǁkhâb'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd, D MMMM YYYY h:mm a', + ], + + 'year' => ':count kurigu', + 'y' => ':count kurigu', + 'a_year' => ':count kurigu', + + 'month' => ':count ǁaub', // less reliable + 'm' => ':count ǁaub', // less reliable + 'a_month' => ':count ǁaub', // less reliable + + 'week' => ':count hû', // less reliable + 'w' => ':count hû', // less reliable + 'a_week' => ':count hû', // less reliable + + 'day' => ':count ǀhobas', // less reliable + 'd' => ':count ǀhobas', // less reliable + 'a_day' => ':count ǀhobas', // less reliable + + 'hour' => ':count ǂgaes', // less reliable + 'h' => ':count ǂgaes', // less reliable + 'a_hour' => ':count ǂgaes', // less reliable + + 'minute' => ':count minutga', // less reliable + 'min' => ':count minutga', // less reliable + 'a_minute' => ':count minutga', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nb.php b/vendor/nesbot/carbon/src/Carbon/Lang/nb.php new file mode 100644 index 0000000..371ee84 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nb.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Alexander Tømmerås + * - Sigurd Gartmann + * - JD Isaacks + */ +return [ + 'year' => ':count år|:count år', + 'a_year' => 'ett år|:count år', + 'y' => ':count år|:count år', + 'month' => ':count måned|:count måneder', + 'a_month' => 'en måned|:count måneder', + 'm' => ':count md.', + 'week' => ':count uke|:count uker', + 'a_week' => 'en uke|:count uker', + 'w' => ':count u.', + 'day' => ':count dag|:count dager', + 'a_day' => 'en dag|:count dager', + 'd' => ':count d.', + 'hour' => ':count time|:count timer', + 'a_hour' => 'en time|:count timer', + 'h' => ':count t', + 'minute' => ':count minutt|:count minutter', + 'a_minute' => 'ett minutt|:count minutter', + 'min' => ':count min', + 'second' => ':count sekund|:count sekunder', + 'a_second' => 'noen sekunder|:count sekunder', + 's' => ':count sek', + 'ago' => ':time siden', + 'from_now' => 'om :time', + 'after' => ':time etter', + 'before' => ':time før', + 'diff_now' => 'akkurat nå', + 'diff_today' => 'i dag', + 'diff_today_regexp' => 'i dag(?:\\s+kl.)?', + 'diff_yesterday' => 'i går', + 'diff_yesterday_regexp' => 'i går(?:\\s+kl.)?', + 'diff_tomorrow' => 'i morgen', + 'diff_tomorrow_regexp' => 'i morgen(?:\\s+kl.)?', + 'diff_before_yesterday' => 'i forgårs', + 'diff_after_tomorrow' => 'i overmorgen', + 'period_recurrences' => 'en gang|:count ganger', + 'period_interval' => 'hver :interval', + 'period_start_date' => 'fra :date', + 'period_end_date' => 'til :date', + 'months' => ['januar', 'februar', 'mars', 'april', 'mai', 'juni', 'juli', 'august', 'september', 'oktober', 'november', 'desember'], + 'months_short' => ['jan', 'feb', 'mar', 'apr', 'mai', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'des'], + 'weekdays' => ['søndag', 'mandag', 'tirsdag', 'onsdag', 'torsdag', 'fredag', 'lørdag'], + 'weekdays_short' => ['søn', 'man', 'tir', 'ons', 'tor', 'fre', 'lør'], + 'weekdays_min' => ['sø', 'ma', 'ti', 'on', 'to', 'fr', 'lø'], + 'ordinal' => ':number.', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY [kl.] HH:mm', + 'LLLL' => 'dddd D. MMMM YYYY [kl.] HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[i dag kl.] LT', + 'nextDay' => '[i morgen kl.] LT', + 'nextWeek' => 'dddd [kl.] LT', + 'lastDay' => '[i går kl.] LT', + 'lastWeek' => '[forrige] dddd [kl.] LT', + 'sameElse' => 'L', + ], + 'list' => [', ', ' og '], + 'meridiem' => ['a.m.', 'p.m.'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nb_NO.php b/vendor/nesbot/carbon/src/Carbon/Lang/nb_NO.php new file mode 100644 index 0000000..31678c5 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nb_NO.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/nb.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nb_SJ.php b/vendor/nesbot/carbon/src/Carbon/Lang/nb_SJ.php new file mode 100644 index 0000000..ce0210b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nb_SJ.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/nb.php', [ + 'formats' => [ + 'LL' => 'D. MMM YYYY', + 'LLL' => 'D. MMMM YYYY, HH:mm', + 'LLLL' => 'dddd D. MMMM YYYY, HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nd.php b/vendor/nesbot/carbon/src/Carbon/Lang/nd.php new file mode 100644 index 0000000..f75d9a7 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nd.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'weekdays' => ['Sonto', 'Mvulo', 'Sibili', 'Sithathu', 'Sine', 'Sihlanu', 'Mgqibelo'], + 'weekdays_short' => ['Son', 'Mvu', 'Sib', 'Sit', 'Sin', 'Sih', 'Mgq'], + 'weekdays_min' => ['Son', 'Mvu', 'Sib', 'Sit', 'Sin', 'Sih', 'Mgq'], + 'months' => ['Zibandlela', 'Nhlolanja', 'Mbimbitho', 'Mabasa', 'Nkwenkwezi', 'Nhlangula', 'Ntulikazi', 'Ncwabakazi', 'Mpandula', 'Mfumfu', 'Lwezi', 'Mpalakazi'], + 'months_short' => ['Zib', 'Nhlo', 'Mbi', 'Mab', 'Nkw', 'Nhla', 'Ntu', 'Ncw', 'Mpan', 'Mfu', 'Lwe', 'Mpal'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + + 'year' => 'okweminyaka engu-:count', // less reliable + 'y' => 'okweminyaka engu-:count', // less reliable + 'a_year' => 'okweminyaka engu-:count', // less reliable + + 'month' => 'inyanga ezingu-:count', + 'm' => 'inyanga ezingu-:count', + 'a_month' => 'inyanga ezingu-:count', + + 'week' => 'amaviki angu-:count', + 'w' => 'amaviki angu-:count', + 'a_week' => 'amaviki angu-:count', + + 'day' => 'kwamalanga angu-:count', + 'd' => 'kwamalanga angu-:count', + 'a_day' => 'kwamalanga angu-:count', + + 'hour' => 'amahola angu-:count', + 'h' => 'amahola angu-:count', + 'a_hour' => 'amahola angu-:count', + + 'minute' => 'imizuzu engu-:count', + 'min' => 'imizuzu engu-:count', + 'a_minute' => 'imizuzu engu-:count', + + 'second' => 'imizuzwana engu-:count', + 's' => 'imizuzwana engu-:count', + 'a_second' => 'imizuzwana engu-:count', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nds.php b/vendor/nesbot/carbon/src/Carbon/Lang/nds.php new file mode 100644 index 0000000..c0b3775 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nds.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/nds_DE.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nds_DE.php b/vendor/nesbot/carbon/src/Carbon/Lang/nds_DE.php new file mode 100644 index 0000000..a6c57a9 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nds_DE.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - information from Kenneth Christiansen Kenneth Christiansen, Pablo Saratxaga kenneth@gnu.org, pablo@mandrakesoft.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['Jannuaar', 'Feberwaar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'], + 'months_short' => ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'], + 'weekdays' => ['Sünndag', 'Maandag', 'Dingsdag', 'Middeweek', 'Dunnersdag', 'Freedag', 'Sünnavend'], + 'weekdays_short' => ['Sdag', 'Maan', 'Ding', 'Midd', 'Dunn', 'Free', 'Svd.'], + 'weekdays_min' => ['Sd', 'Ma', 'Di', 'Mi', 'Du', 'Fr', 'Sa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'year' => ':count Johr', + 'y' => ':countJ', + 'a_year' => '{1}een Johr|:count Johr', + + 'month' => ':count Maand', + 'm' => ':countM', + 'a_month' => '{1}een Maand|:count Maand', + + 'week' => ':count Week|:count Weken', + 'w' => ':countW', + 'a_week' => '{1}een Week|:count Week|:count Weken', + + 'day' => ':count Dag|:count Daag', + 'd' => ':countD', + 'a_day' => '{1}een Dag|:count Dag|:count Daag', + + 'hour' => ':count Stünn|:count Stünnen', + 'h' => ':countSt', + 'a_hour' => '{1}een Stünn|:count Stünn|:count Stünnen', + + 'minute' => ':count Minuut|:count Minuten', + 'min' => ':countm', + 'a_minute' => '{1}een Minuut|:count Minuut|:count Minuten', + + 'second' => ':count Sekunn|:count Sekunnen', + 's' => ':counts', + 'a_second' => 'en poor Sekunnen|:count Sekunn|:count Sekunnen', + + 'ago' => 'vör :time', + 'from_now' => 'in :time', + 'before' => ':time vörher', + 'after' => ':time later', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nds_NL.php b/vendor/nesbot/carbon/src/Carbon/Lang/nds_NL.php new file mode 100644 index 0000000..de2c57b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nds_NL.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - information from Kenneth Christiansen Kenneth Christiansen, Pablo Saratxaga kenneth@gnu.org, pablo@mandrakesoft.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['Jaunuwoa', 'Februwoa', 'Moaz', 'Aprell', 'Mai', 'Juni', 'Juli', 'August', 'Septamba', 'Oktoba', 'Nowamba', 'Dezamba'], + 'months_short' => ['Jan', 'Feb', 'Moz', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Now', 'Dez'], + 'weekdays' => ['Sinndag', 'Mondag', 'Dingsdag', 'Meddwäakj', 'Donnadag', 'Friedag', 'Sinnowend'], + 'weekdays_short' => ['Sdg', 'Mdg', 'Dsg', 'Mwk', 'Ddg', 'Fdg', 'Swd'], + 'weekdays_min' => ['Sdg', 'Mdg', 'Dsg', 'Mwk', 'Ddg', 'Fdg', 'Swd'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ne.php b/vendor/nesbot/carbon/src/Carbon/Lang/ne.php new file mode 100644 index 0000000..d4caf0e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ne.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - nootanghimire + * - Josh Soref + * - Nj Subedi + * - JD Isaacks + */ +return [ + 'year' => 'एक बर्ष|:count बर्ष', + 'y' => ':count वर्ष', + 'month' => 'एक महिना|:count महिना', + 'm' => ':count महिना', + 'week' => ':count हप्ता', + 'w' => ':count हप्ता', + 'day' => 'एक दिन|:count दिन', + 'd' => ':count दिन', + 'hour' => 'एक घण्टा|:count घण्टा', + 'h' => ':count घण्टा', + 'minute' => 'एक मिनेट|:count मिनेट', + 'min' => ':count मिनेट', + 'second' => 'केही क्षण|:count सेकेण्ड', + 's' => ':count सेकेण्ड', + 'ago' => ':time अगाडि', + 'from_now' => ':timeमा', + 'after' => ':time पछि', + 'before' => ':time अघि', + 'diff_now' => 'अहिले', + 'diff_today' => 'आज', + 'diff_yesterday' => 'हिजो', + 'diff_tomorrow' => 'भोलि', + 'formats' => [ + 'LT' => 'Aको h:mm बजे', + 'LTS' => 'Aको h:mm:ss बजे', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, Aको h:mm बजे', + 'LLLL' => 'dddd, D MMMM YYYY, Aको h:mm बजे', + ], + 'calendar' => [ + 'sameDay' => '[आज] LT', + 'nextDay' => '[भोलि] LT', + 'nextWeek' => '[आउँदो] dddd[,] LT', + 'lastDay' => '[हिजो] LT', + 'lastWeek' => '[गएको] dddd[,] LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour) { + if ($hour < 3) { + return 'राति'; + } + if ($hour < 12) { + return 'बिहान'; + } + if ($hour < 16) { + return 'दिउँसो'; + } + if ($hour < 20) { + return 'साँझ'; + } + + return 'राति'; + }, + 'months' => ['जनवरी', 'फेब्रुवरी', 'मार्च', 'अप्रिल', 'मई', 'जुन', 'जुलाई', 'अगष्ट', 'सेप्टेम्बर', 'अक्टोबर', 'नोभेम्बर', 'डिसेम्बर'], + 'months_short' => ['जन.', 'फेब्रु.', 'मार्च', 'अप्रि.', 'मई', 'जुन', 'जुलाई.', 'अग.', 'सेप्ट.', 'अक्टो.', 'नोभे.', 'डिसे.'], + 'weekdays' => ['आइतबार', 'सोमबार', 'मङ्गलबार', 'बुधबार', 'बिहिबार', 'शुक्रबार', 'शनिबार'], + 'weekdays_short' => ['आइत.', 'सोम.', 'मङ्गल.', 'बुध.', 'बिहि.', 'शुक्र.', 'शनि.'], + 'weekdays_min' => ['आ.', 'सो.', 'मं.', 'बु.', 'बि.', 'शु.', 'श.'], + 'list' => [', ', ' र '], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ne_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/ne_IN.php new file mode 100644 index 0000000..f68d00e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ne_IN.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ne.php', [ + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'yy/M/d', + 'LL' => 'YYYY MMM D', + 'LLL' => 'YYYY MMMM D, h:mm a', + 'LLLL' => 'YYYY MMMM D, dddd, h:mm a', + ], + 'months' => ['जनवरी', 'फेब्रुअरी', 'मार्च', 'अप्रिल', 'मे', 'जुन', 'जुलाई', 'अगस्ट', 'सेप्टेम्बर', 'अक्टोबर', 'नोभेम्बर', 'डिसेम्बर'], + 'months_short' => ['जनवरी', 'फेब्रुअरी', 'मार्च', 'अप्रिल', 'मे', 'जुन', 'जुलाई', 'अगस्ट', 'सेप्टेम्बर', 'अक्टोबर', 'नोभेम्बर', 'डिसेम्बर'], + 'weekend' => [0, 0], + 'meridiem' => ['पूर्वाह्न', 'अपराह्न'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ne_NP.php b/vendor/nesbot/carbon/src/Carbon/Lang/ne_NP.php new file mode 100644 index 0000000..27840c0 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ne_NP.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ne.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nhn.php b/vendor/nesbot/carbon/src/Carbon/Lang/nhn.php new file mode 100644 index 0000000..5a85831 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nhn.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/nhn_MX.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nhn_MX.php b/vendor/nesbot/carbon/src/Carbon/Lang/nhn_MX.php new file mode 100644 index 0000000..9db88a1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nhn_MX.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP libc-alpha@sourceware.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'], + 'months_short' => ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic'], + 'weekdays' => ['teoilhuitl', 'ceilhuitl', 'omeilhuitl', 'yeilhuitl', 'nahuilhuitl', 'macuililhuitl', 'chicuaceilhuitl'], + 'weekdays_short' => ['teo', 'cei', 'ome', 'yei', 'nau', 'mac', 'chi'], + 'weekdays_min' => ['teo', 'cei', 'ome', 'yei', 'nau', 'mac', 'chi'], + 'day_of_first_week_of_year' => 1, + + 'month' => ':count metztli', // less reliable + 'm' => ':count metztli', // less reliable + 'a_month' => ':count metztli', // less reliable + + 'week' => ':count tonalli', // less reliable + 'w' => ':count tonalli', // less reliable + 'a_week' => ':count tonalli', // less reliable + + 'day' => ':count tonatih', // less reliable + 'd' => ':count tonatih', // less reliable + 'a_day' => ':count tonatih', // less reliable + + 'minute' => ':count toltecayotl', // less reliable + 'min' => ':count toltecayotl', // less reliable + 'a_minute' => ':count toltecayotl', // less reliable + + 'second' => ':count ome', // less reliable + 's' => ':count ome', // less reliable + 'a_second' => ':count ome', // less reliable + + 'year' => ':count xihuitl', + 'y' => ':count xihuitl', + 'a_year' => ':count xihuitl', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/niu.php b/vendor/nesbot/carbon/src/Carbon/Lang/niu.php new file mode 100644 index 0000000..bd9be8a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/niu.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/niu_NU.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/niu_NU.php b/vendor/nesbot/carbon/src/Carbon/Lang/niu_NU.php new file mode 100644 index 0000000..6e7a697 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/niu_NU.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RockET Systems Emani Fakaotimanava-Lui emani@niue.nu + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['Ianuali', 'Fepuali', 'Masi', 'Apelila', 'Me', 'Iuni', 'Iulai', 'Aokuso', 'Sepetema', 'Oketopa', 'Novema', 'Tesemo'], + 'months_short' => ['Ian', 'Fep', 'Mas', 'Ape', 'Me', 'Iun', 'Iul', 'Aok', 'Sep', 'Oke', 'Nov', 'Tes'], + 'weekdays' => ['Aho Tapu', 'Aho Gofua', 'Aho Ua', 'Aho Lotu', 'Aho Tuloto', 'Aho Falaile', 'Aho Faiumu'], + 'weekdays_short' => ['Tapu', 'Gofua', 'Ua', 'Lotu', 'Tuloto', 'Falaile', 'Faiumu'], + 'weekdays_min' => ['Tapu', 'Gofua', 'Ua', 'Lotu', 'Tuloto', 'Falaile', 'Faiumu'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'year' => ':count tau', + 'y' => ':count tau', + 'a_year' => ':count tau', + + 'month' => ':count mahina', + 'm' => ':count mahina', + 'a_month' => ':count mahina', + + 'week' => ':count faahi tapu', + 'w' => ':count faahi tapu', + 'a_week' => ':count faahi tapu', + + 'day' => ':count aho', + 'd' => ':count aho', + 'a_day' => ':count aho', + + 'hour' => ':count e tulā', + 'h' => ':count e tulā', + 'a_hour' => ':count e tulā', + + 'minute' => ':count minuti', + 'min' => ':count minuti', + 'a_minute' => ':count minuti', + + 'second' => ':count sekone', + 's' => ':count sekone', + 'a_second' => ':count sekone', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nl.php b/vendor/nesbot/carbon/src/Carbon/Lang/nl.php new file mode 100644 index 0000000..2d73770 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nl.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Roy + * - Stephan + * - François B + * - Tim Fish + * - Kevin Huang + * - Jacob Middag + * - JD Isaacks + * - Roy + * - Stephan + * - François B + * - Tim Fish + * - Jacob Middag + * - JD Isaacks + * - Propaganistas + * - MegaXLR + * - adriaanzon + * - MonkeyPhysics + * - JeroenG + * - RikSomers + * - proclame + * - Rik de Groot (hwdegroot) + */ +return [ + 'year' => ':count jaar|:count jaar', + 'a_year' => 'een jaar|:count jaar', + 'y' => ':countj', + 'month' => ':count maand|:count maanden', + 'a_month' => 'een maand|:count maanden', + 'm' => ':countmnd', + 'week' => ':count week|:count weken', + 'a_week' => 'een week|:count weken', + 'w' => ':countw', + 'day' => ':count dag|:count dagen', + 'a_day' => 'een dag|:count dagen', + 'd' => ':countd', + 'hour' => ':count uur|:count uur', + 'a_hour' => 'een uur|:count uur', + 'h' => ':countu', + 'minute' => ':count minuut|:count minuten', + 'a_minute' => 'een minuut|:count minuten', + 'min' => ':countmin', + 'second' => ':count seconde|:count seconden', + 'a_second' => 'een paar seconden|:count seconden', + 's' => ':counts', + 'ago' => ':time geleden', + 'from_now' => 'over :time', + 'after' => ':time later', + 'before' => ':time eerder', + 'diff_now' => 'nu', + 'diff_today' => 'vandaag', + 'diff_today_regexp' => 'vandaag(?:\\s+om)?', + 'diff_yesterday' => 'gisteren', + 'diff_yesterday_regexp' => 'gisteren(?:\\s+om)?', + 'diff_tomorrow' => 'morgen', + 'diff_tomorrow_regexp' => 'morgen(?:\\s+om)?', + 'diff_after_tomorrow' => 'overmorgen', + 'diff_before_yesterday' => 'eergisteren', + 'period_recurrences' => ':count keer', + 'period_interval' => function (string $interval = '') { + /** @var string $output */ + $output = preg_replace('/^(een|één|1)\s+/u', '', $interval); + + if (preg_match('/^(een|één|1)( jaar|j| uur|u)/u', $interval)) { + return "elk $output"; + } + + return "elke $output"; + }, + 'period_start_date' => 'van :date', + 'period_end_date' => 'tot :date', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD-MM-YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[vandaag om] LT', + 'nextDay' => '[morgen om] LT', + 'nextWeek' => 'dddd [om] LT', + 'lastDay' => '[gisteren om] LT', + 'lastWeek' => '[afgelopen] dddd [om] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + return $number.(($number === 1 || $number === 8 || $number >= 20) ? 'ste' : 'de'); + }, + 'months' => ['januari', 'februari', 'maart', 'april', 'mei', 'juni', 'juli', 'augustus', 'september', 'oktober', 'november', 'december'], + 'months_short' => ['jan', 'feb', 'mrt', 'apr', 'mei', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'], + 'mmm_suffix' => '.', + 'weekdays' => ['zondag', 'maandag', 'dinsdag', 'woensdag', 'donderdag', 'vrijdag', 'zaterdag'], + 'weekdays_short' => ['zo.', 'ma.', 'di.', 'wo.', 'do.', 'vr.', 'za.'], + 'weekdays_min' => ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' en '], + 'meridiem' => ['\'s ochtends', '\'s middags'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nl_AW.php b/vendor/nesbot/carbon/src/Carbon/Lang/nl_AW.php new file mode 100644 index 0000000..5ec136d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nl_AW.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Free Software Foundation, Inc. bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/nl.php', [ + 'formats' => [ + 'L' => 'DD-MM-YY', + ], + 'months' => ['januari', 'februari', 'maart', 'april', 'mei', 'juni', 'juli', 'augustus', 'september', 'oktober', 'november', 'december'], + 'months_short' => ['jan', 'feb', 'mrt', 'apr', 'mei', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'], + 'weekdays' => ['zondag', 'maandag', 'dinsdag', 'woensdag', 'donderdag', 'vrijdag', 'zaterdag'], + 'weekdays_short' => ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'], + 'weekdays_min' => ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nl_BE.php b/vendor/nesbot/carbon/src/Carbon/Lang/nl_BE.php new file mode 100644 index 0000000..037f5b4 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nl_BE.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Roy + * - Stephan + * - François B + * - Tim Fish + * - Kevin Huang + * - Jacob Middag + * - JD Isaacks + * - Propaganistas + */ +return array_replace_recursive(require __DIR__.'/nl.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nl_BQ.php b/vendor/nesbot/carbon/src/Carbon/Lang/nl_BQ.php new file mode 100644 index 0000000..c269197 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nl_BQ.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/nl.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nl_CW.php b/vendor/nesbot/carbon/src/Carbon/Lang/nl_CW.php new file mode 100644 index 0000000..c269197 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nl_CW.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/nl.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nl_NL.php b/vendor/nesbot/carbon/src/Carbon/Lang/nl_NL.php new file mode 100644 index 0000000..14e4853 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nl_NL.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/nl.php', [ + 'months' => ['januari', 'februari', 'maart', 'april', 'mei', 'juni', 'juli', 'augustus', 'september', 'oktober', 'november', 'december'], + 'months_short' => ['jan', 'feb', 'mrt', 'apr', 'mei', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'], + 'weekdays' => ['zondag', 'maandag', 'dinsdag', 'woensdag', 'donderdag', 'vrijdag', 'zaterdag'], + 'weekdays_short' => ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'], + 'weekdays_min' => ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nl_SR.php b/vendor/nesbot/carbon/src/Carbon/Lang/nl_SR.php new file mode 100644 index 0000000..c269197 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nl_SR.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/nl.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nl_SX.php b/vendor/nesbot/carbon/src/Carbon/Lang/nl_SX.php new file mode 100644 index 0000000..c269197 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nl_SX.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/nl.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nmg.php b/vendor/nesbot/carbon/src/Carbon/Lang/nmg.php new file mode 100644 index 0000000..4d1df6e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nmg.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['maná', 'kugú'], + 'weekdays' => ['sɔ́ndɔ', 'mɔ́ndɔ', 'sɔ́ndɔ mafú mába', 'sɔ́ndɔ mafú málal', 'sɔ́ndɔ mafú mána', 'mabágá má sukul', 'sásadi'], + 'weekdays_short' => ['sɔ́n', 'mɔ́n', 'smb', 'sml', 'smn', 'mbs', 'sas'], + 'weekdays_min' => ['sɔ́n', 'mɔ́n', 'smb', 'sml', 'smn', 'mbs', 'sas'], + 'months' => ['ngwɛn matáhra', 'ngwɛn ńmba', 'ngwɛn ńlal', 'ngwɛn ńna', 'ngwɛn ńtan', 'ngwɛn ńtuó', 'ngwɛn hɛmbuɛrí', 'ngwɛn lɔmbi', 'ngwɛn rɛbvuâ', 'ngwɛn wum', 'ngwɛn wum navǔr', 'krísimin'], + 'months_short' => ['ng1', 'ng2', 'ng3', 'ng4', 'ng5', 'ng6', 'ng7', 'ng8', 'ng9', 'ng10', 'ng11', 'kris'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nn.php b/vendor/nesbot/carbon/src/Carbon/Lang/nn.php new file mode 100644 index 0000000..041f7b2 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nn.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Alexander Tømmerås + * - Øystein + * - JD Isaacks + * - Gaute Hvoslef Kvalnes (gaute) + */ +return [ + 'year' => ':count år', + 'a_year' => 'eit år|:count år', + 'y' => ':count år', + 'month' => ':count månad|:count månader', + 'a_month' => 'ein månad|:count månader', + 'm' => ':count md', + 'week' => ':count veke|:count veker', + 'a_week' => 'ei veke|:count veker', + 'w' => ':countv', + 'day' => ':count dag|:count dagar', + 'a_day' => 'ein dag|:count dagar', + 'd' => ':countd', + 'hour' => ':count time|:count timar', + 'a_hour' => 'ein time|:count timar', + 'h' => ':countt', + 'minute' => ':count minutt', + 'a_minute' => 'eit minutt|:count minutt', + 'min' => ':countm', + 'second' => ':count sekund', + 'a_second' => 'nokre sekund|:count sekund', + 's' => ':counts', + 'ago' => ':time sidan', + 'from_now' => 'om :time', + 'after' => ':time etter', + 'before' => ':time før', + 'diff_today' => 'I dag', + 'diff_yesterday' => 'I går', + 'diff_yesterday_regexp' => 'I går(?:\\s+klokka)?', + 'diff_tomorrow' => 'I morgon', + 'diff_tomorrow_regexp' => 'I morgon(?:\\s+klokka)?', + 'diff_today_regexp' => 'I dag(?:\\s+klokka)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY [kl.] H:mm', + 'LLLL' => 'dddd D. MMMM YYYY [kl.] HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[I dag klokka] LT', + 'nextDay' => '[I morgon klokka] LT', + 'nextWeek' => 'dddd [klokka] LT', + 'lastDay' => '[I går klokka] LT', + 'lastWeek' => '[Føregåande] dddd [klokka] LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['januar', 'februar', 'mars', 'april', 'mai', 'juni', 'juli', 'august', 'september', 'oktober', 'november', 'desember'], + 'months_short' => ['jan', 'feb', 'mar', 'apr', 'mai', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'des'], + 'weekdays' => ['sundag', 'måndag', 'tysdag', 'onsdag', 'torsdag', 'fredag', 'laurdag'], + 'weekdays_short' => ['sun', 'mån', 'tys', 'ons', 'tor', 'fre', 'lau'], + 'weekdays_min' => ['su', 'må', 'ty', 'on', 'to', 'fr', 'la'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' og '], + 'meridiem' => ['f.m.', 'e.m.'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nn_NO.php b/vendor/nesbot/carbon/src/Carbon/Lang/nn_NO.php new file mode 100644 index 0000000..8e16871 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nn_NO.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/nn.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nnh.php b/vendor/nesbot/carbon/src/Carbon/Lang/nnh.php new file mode 100644 index 0000000..007d239 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nnh.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['mbaʼámbaʼ', 'ncwònzém'], + 'weekdays' => null, + 'weekdays_short' => ['lyɛʼɛ́ sẅíŋtè', 'mvfò lyɛ̌ʼ', 'mbɔ́ɔntè mvfò lyɛ̌ʼ', 'tsètsɛ̀ɛ lyɛ̌ʼ', 'mbɔ́ɔntè tsetsɛ̀ɛ lyɛ̌ʼ', 'mvfò màga lyɛ̌ʼ', 'màga lyɛ̌ʼ'], + 'weekdays_min' => null, + 'months' => null, + 'months_short' => ['saŋ tsetsɛ̀ɛ lùm', 'saŋ kàg ngwóŋ', 'saŋ lepyè shúm', 'saŋ cÿó', 'saŋ tsɛ̀ɛ cÿó', 'saŋ njÿoláʼ', 'saŋ tyɛ̀b tyɛ̀b mbʉ̀ŋ', 'saŋ mbʉ̀ŋ', 'saŋ ngwɔ̀ʼ mbÿɛ', 'saŋ tàŋa tsetsáʼ', 'saŋ mejwoŋó', 'saŋ lùm'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/yy', + 'LL' => 'D MMM, YYYY', + 'LLL' => '[lyɛ]̌ʼ d [na] MMMM, YYYY HH:mm', + 'LLLL' => 'dddd , [lyɛ]̌ʼ d [na] MMMM, YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/no.php b/vendor/nesbot/carbon/src/Carbon/Lang/no.php new file mode 100644 index 0000000..f4497c7 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/no.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Daniel S. Billing + * - Paul + * - Jimmie Johansson + * - Jens Herlevsen + */ +return array_replace_recursive(require __DIR__.'/nb.php', [ + 'formats' => [ + 'LLL' => 'D. MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D. MMMM YYYY [kl.] HH:mm', + ], + 'calendar' => [ + 'nextWeek' => 'på dddd [kl.] LT', + 'lastWeek' => '[i] dddd[s kl.] LT', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nr.php b/vendor/nesbot/carbon/src/Carbon/Lang/nr.php new file mode 100644 index 0000000..1bc999f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nr.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/nr_ZA.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nr_ZA.php b/vendor/nesbot/carbon/src/Carbon/Lang/nr_ZA.php new file mode 100644 index 0000000..f9a7be8 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nr_ZA.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Zuza Software Foundation (Translate.org.za) Dwayne Bailey dwayne@translate.org.za + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Janabari', 'uFeberbari', 'uMatjhi', 'u-Apreli', 'Meyi', 'Juni', 'Julayi', 'Arhostosi', 'Septemba', 'Oktoba', 'Usinyikhaba', 'Disemba'], + 'months_short' => ['Jan', 'Feb', 'Mat', 'Apr', 'Mey', 'Jun', 'Jul', 'Arh', 'Sep', 'Okt', 'Usi', 'Dis'], + 'weekdays' => ['uSonto', 'uMvulo', 'uLesibili', 'lesithathu', 'uLesine', 'ngoLesihlanu', 'umGqibelo'], + 'weekdays_short' => ['Son', 'Mvu', 'Bil', 'Tha', 'Ne', 'Hla', 'Gqi'], + 'weekdays_min' => ['Son', 'Mvu', 'Bil', 'Tha', 'Ne', 'Hla', 'Gqi'], + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nso.php b/vendor/nesbot/carbon/src/Carbon/Lang/nso.php new file mode 100644 index 0000000..2a6cabb --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nso.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/nso_ZA.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nso_ZA.php b/vendor/nesbot/carbon/src/Carbon/Lang/nso_ZA.php new file mode 100644 index 0000000..b08fe6d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nso_ZA.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Zuza Software Foundation (Translate.org.za) Dwayne Bailey dwayne@translate.org.za + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Janaware', 'Febereware', 'Matšhe', 'Aprele', 'Mei', 'June', 'Julae', 'Agostose', 'Setemere', 'Oktobere', 'Nofemere', 'Disemere'], + 'months_short' => ['Jan', 'Feb', 'Mat', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Set', 'Okt', 'Nof', 'Dis'], + 'weekdays' => ['LaMorena', 'Mošupologo', 'Labobedi', 'Laboraro', 'Labone', 'Labohlano', 'Mokibelo'], + 'weekdays_short' => ['Son', 'Moš', 'Bed', 'Rar', 'Ne', 'Hla', 'Mok'], + 'weekdays_min' => ['Son', 'Moš', 'Bed', 'Rar', 'Ne', 'Hla', 'Mok'], + 'day_of_first_week_of_year' => 1, + + 'year' => ':count ngwaga', + 'y' => ':count ngwaga', + 'a_year' => ':count ngwaga', + + 'month' => ':count Kgwedi', + 'm' => ':count Kgwedi', + 'a_month' => ':count Kgwedi', + + 'week' => ':count Beke', + 'w' => ':count Beke', + 'a_week' => ':count Beke', + + 'day' => ':count Letšatši', + 'd' => ':count Letšatši', + 'a_day' => ':count Letšatši', + + 'hour' => ':count Iri', + 'h' => ':count Iri', + 'a_hour' => ':count Iri', + + 'minute' => ':count Motsotso', + 'min' => ':count Motsotso', + 'a_minute' => ':count Motsotso', + + 'second' => ':count motsotswana', + 's' => ':count motsotswana', + 'a_second' => ':count motsotswana', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nus.php b/vendor/nesbot/carbon/src/Carbon/Lang/nus.php new file mode 100644 index 0000000..789bc39 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nus.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['RW', 'TŊ'], + 'weekdays' => ['Cäŋ kuɔth', 'Jiec la̱t', 'Rɛw lätni', 'Diɔ̱k lätni', 'Ŋuaan lätni', 'Dhieec lätni', 'Bäkɛl lätni'], + 'weekdays_short' => ['Cäŋ', 'Jiec', 'Rɛw', 'Diɔ̱k', 'Ŋuaan', 'Dhieec', 'Bäkɛl'], + 'weekdays_min' => ['Cäŋ', 'Jiec', 'Rɛw', 'Diɔ̱k', 'Ŋuaan', 'Dhieec', 'Bäkɛl'], + 'months' => ['Tiop thar pɛt', 'Pɛt', 'Duɔ̱ɔ̱ŋ', 'Guak', 'Duät', 'Kornyoot', 'Pay yie̱tni', 'Tho̱o̱r', 'Tɛɛr', 'Laath', 'Kur', 'Tio̱p in di̱i̱t'], + 'months_short' => ['Tiop', 'Pɛt', 'Duɔ̱ɔ̱', 'Guak', 'Duä', 'Kor', 'Pay', 'Thoo', 'Tɛɛ', 'Laa', 'Kur', 'Tid'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'D/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd D MMMM YYYY h:mm a', + ], + + 'year' => ':count jiök', // less reliable + 'y' => ':count jiök', // less reliable + 'a_year' => ':count jiök', // less reliable + + 'month' => ':count pay', // less reliable + 'm' => ':count pay', // less reliable + 'a_month' => ':count pay', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/nyn.php b/vendor/nesbot/carbon/src/Carbon/Lang/nyn.php new file mode 100644 index 0000000..8660ea4 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/nyn.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'weekdays' => ['Sande', 'Orwokubanza', 'Orwakabiri', 'Orwakashatu', 'Orwakana', 'Orwakataano', 'Orwamukaaga'], + 'weekdays_short' => ['SAN', 'ORK', 'OKB', 'OKS', 'OKN', 'OKT', 'OMK'], + 'weekdays_min' => ['SAN', 'ORK', 'OKB', 'OKS', 'OKN', 'OKT', 'OMK'], + 'months' => ['Okwokubanza', 'Okwakabiri', 'Okwakashatu', 'Okwakana', 'Okwakataana', 'Okwamukaaga', 'Okwamushanju', 'Okwamunaana', 'Okwamwenda', 'Okwaikumi', 'Okwaikumi na kumwe', 'Okwaikumi na ibiri'], + 'months_short' => ['KBZ', 'KBR', 'KST', 'KKN', 'KTN', 'KMK', 'KMS', 'KMN', 'KMW', 'KKM', 'KNK', 'KNB'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/oc.php b/vendor/nesbot/carbon/src/Carbon/Lang/oc.php new file mode 100644 index 0000000..89693e6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/oc.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Quentí + */ +// @codeCoverageIgnoreStart +use Symfony\Component\Translation\PluralizationRules; + +if (class_exists('Symfony\\Component\\Translation\\PluralizationRules')) { + PluralizationRules::set(function ($number) { + return $number == 1 ? 0 : 1; + }, 'oc'); +} +// @codeCoverageIgnoreEnd + +return [ + 'year' => ':count an|:count ans', + 'a_year' => 'un an|:count ans', + 'y' => ':count an|:count ans', + 'month' => ':count mes|:count meses', + 'a_month' => 'un mes|:count meses', + 'm' => ':count mes|:count meses', + 'week' => ':count setmana|:count setmanas', + 'a_week' => 'una setmana|:count setmanas', + 'w' => ':count setmana|:count setmanas', + 'day' => ':count jorn|:count jorns', + 'a_day' => 'un jorn|:count jorns', + 'd' => ':count jorn|:count jorns', + 'hour' => ':count ora|:count oras', + 'a_hour' => 'una ora|:count oras', + 'h' => ':count ora|:count oras', + 'minute' => ':count minuta|:count minutas', + 'a_minute' => 'una minuta|:count minutas', + 'min' => ':count minuta|:count minutas', + 'second' => ':count segonda|:count segondas', + 'a_second' => 'una segonda|:count segondas', + 's' => ':count segonda|:count segondas', + 'ago' => 'fa :time', + 'from_now' => 'd\'aquí :time', + 'after' => ':time aprèp', + 'before' => ':time abans', + 'diff_now' => 'ara meteis', + 'diff_today' => 'Uèi', + 'diff_today_regexp' => 'Uèi(?:\\s+a)?', + 'diff_yesterday' => 'ièr', + 'diff_yesterday_regexp' => 'Ièr(?:\\s+a)?', + 'diff_tomorrow' => 'deman', + 'diff_tomorrow_regexp' => 'Deman(?:\\s+a)?', + 'diff_before_yesterday' => 'ièr delà', + 'diff_after_tomorrow' => 'deman passat', + 'period_recurrences' => ':count còp|:count còps', + 'period_interval' => 'cada :interval', + 'period_start_date' => 'de :date', + 'period_end_date' => 'fins a :date', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM [de] YYYY', + 'LLL' => 'D MMMM [de] YYYY [a] H:mm', + 'LLLL' => 'dddd D MMMM [de] YYYY [a] H:mm', + ], + 'calendar' => [ + 'sameDay' => '[Uèi a] LT', + 'nextDay' => '[Deman a] LT', + 'nextWeek' => 'dddd [a] LT', + 'lastDay' => '[Ièr a] LT', + 'lastWeek' => 'dddd [passat a] LT', + 'sameElse' => 'L', + ], + 'months' => ['de genièr', 'de febrièr', 'de març', 'd\'abrial', 'de mai', 'de junh', 'de julhet', 'd\'agost', 'de setembre', 'd’octòbre', 'de novembre', 'de decembre'], + 'months_standalone' => ['genièr', 'febrièr', 'març', 'abrial', 'mai', 'junh', 'julh', 'agost', 'setembre', 'octòbre', 'novembre', 'decembre'], + 'months_short' => ['gen.', 'feb.', 'març', 'abr.', 'mai', 'junh', 'julh', 'ago.', 'sep.', 'oct.', 'nov.', 'dec.'], + 'weekdays' => ['dimenge', 'diluns', 'dimars', 'dimècres', 'dijòus', 'divendres', 'dissabte'], + 'weekdays_short' => ['dg', 'dl', 'dm', 'dc', 'dj', 'dv', 'ds'], + 'weekdays_min' => ['dg', 'dl', 'dm', 'dc', 'dj', 'dv', 'ds'], + 'ordinal' => function ($number, string $period = '') { + $ordinal = [1 => 'èr', 2 => 'nd'][(int) $number] ?? 'en'; + + // feminine for year, week, hour, minute, second + if (preg_match('/^[yYwWhHgGis]$/', $period)) { + $ordinal .= 'a'; + } + + return $number.$ordinal; + }, + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' e '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/oc_FR.php b/vendor/nesbot/carbon/src/Carbon/Lang/oc_FR.php new file mode 100644 index 0000000..01eb5c1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/oc_FR.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/oc.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/om.php b/vendor/nesbot/carbon/src/Carbon/Lang/om.php new file mode 100644 index 0000000..b8d5a0b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/om.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation & Sagalee Oromoo Publishing Co. Inc. locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'dd-MMM-YYYY', + 'LLL' => 'dd MMMM YYYY HH:mm', + 'LLLL' => 'dddd, MMMM D, YYYY HH:mm', + ], + 'months' => ['Amajjii', 'Guraandhala', 'Bitooteessa', 'Elba', 'Caamsa', 'Waxabajjii', 'Adooleessa', 'Hagayya', 'Fuulbana', 'Onkololeessa', 'Sadaasa', 'Muddee'], + 'months_short' => ['Ama', 'Gur', 'Bit', 'Elb', 'Cam', 'Wax', 'Ado', 'Hag', 'Ful', 'Onk', 'Sad', 'Mud'], + 'weekdays' => ['Dilbata', 'Wiixata', 'Qibxata', 'Roobii', 'Kamiisa', 'Jimaata', 'Sanbata'], + 'weekdays_short' => ['Dil', 'Wix', 'Qib', 'Rob', 'Kam', 'Jim', 'San'], + 'weekdays_min' => ['Dil', 'Wix', 'Qib', 'Rob', 'Kam', 'Jim', 'San'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['WD', 'WB'], + + 'year' => 'wggoota :count', + 'y' => 'wggoota :count', + 'a_year' => 'wggoota :count', + + 'month' => 'ji’a :count', + 'm' => 'ji’a :count', + 'a_month' => 'ji’a :count', + + 'week' => 'torban :count', + 'w' => 'torban :count', + 'a_week' => 'torban :count', + + 'day' => 'guyyaa :count', + 'd' => 'guyyaa :count', + 'a_day' => 'guyyaa :count', + + 'hour' => 'saʼaatii :count', + 'h' => 'saʼaatii :count', + 'a_hour' => 'saʼaatii :count', + + 'minute' => 'daqiiqaa :count', + 'min' => 'daqiiqaa :count', + 'a_minute' => 'daqiiqaa :count', + + 'second' => 'sekoondii :count', + 's' => 'sekoondii :count', + 'a_second' => 'sekoondii :count', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/om_ET.php b/vendor/nesbot/carbon/src/Carbon/Lang/om_ET.php new file mode 100644 index 0000000..044760e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/om_ET.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/om.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/om_KE.php b/vendor/nesbot/carbon/src/Carbon/Lang/om_KE.php new file mode 100644 index 0000000..f5a4d1c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/om_KE.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/om.php', [ + 'day_of_first_week_of_year' => 0, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/or.php b/vendor/nesbot/carbon/src/Carbon/Lang/or.php new file mode 100644 index 0000000..3aa7173 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/or.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/or_IN.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/or_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/or_IN.php new file mode 100644 index 0000000..57a89f5 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/or_IN.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM AP Linux Technology Center, Yamato Software Laboratory bug-glibc@gnu.org + */ +return [ + 'diff_now' => 'ବର୍ତ୍ତମାନ', + 'diff_yesterday' => 'ଗତକାଲି', + 'diff_tomorrow' => 'ଆସନ୍ତାକାଲି', + 'formats' => [ + 'LT' => 'Oh:Om A', + 'LTS' => 'Oh:Om:Os A', + 'L' => 'OD-OM-OY', + 'LL' => 'OD MMMM OY', + 'LLL' => 'OD MMMM OY Oh:Om A', + 'LLLL' => 'dddd OD MMMM OY Oh:Om A', + ], + 'months' => ['ଜାନୁଆରୀ', 'ଫେବୃଆରୀ', 'ମାର୍ଚ୍ଚ', 'ଅପ୍ରେଲ', 'ମଇ', 'ଜୁନ', 'ଜୁଲାଇ', 'ଅଗଷ୍ଟ', 'ସେପ୍ଟେମ୍ବର', 'ଅକ୍ଟୋବର', 'ନଭେମ୍ବର', 'ଡିସେମ୍ବର'], + 'months_short' => ['ଜାନୁଆରୀ', 'ଫେବୃଆରୀ', 'ମାର୍ଚ୍ଚ', 'ଅପ୍ରେଲ', 'ମଇ', 'ଜୁନ', 'ଜୁଲାଇ', 'ଅଗଷ୍ଟ', 'ସେପ୍ଟେମ୍ବର', 'ଅକ୍ଟୋବର', 'ନଭେମ୍ବର', 'ଡିସେମ୍ବର'], + 'weekdays' => ['ରବିବାର', 'ସୋମବାର', 'ମଙ୍ଗଳବାର', 'ବୁଧବାର', 'ଗୁରୁବାର', 'ଶୁକ୍ରବାର', 'ଶନିବାର'], + 'weekdays_short' => ['ରବି', 'ସୋମ', 'ମଙ୍ଗଳ', 'ବୁଧ', 'ଗୁରୁ', 'ଶୁକ୍ର', 'ଶନି'], + 'weekdays_min' => ['ରବି', 'ସୋମ', 'ମଙ୍ଗଳ', 'ବୁଧ', 'ଗୁରୁ', 'ଶୁକ୍ର', 'ଶନି'], + 'day_of_first_week_of_year' => 1, + 'alt_numbers' => ['୦', '୧', '୨', '୩', '୪', '୫', '୬', '୭', '୮', '୯', '୧୦', '୧୧', '୧୨', '୧୩', '୧୪', '୧୫', '୧୬', '୧୭', '୧୮', '୧୯', '୨୦', '୨୧', '୨୨', '୨୩', '୨୪', '୨୫', '୨୬', '୨୭', '୨୮', '୨୯', '୩୦', '୩୧', '୩୨', '୩୩', '୩୪', '୩୫', '୩୬', '୩୭', '୩୮', '୩୯', '୪୦', '୪୧', '୪୨', '୪୩', '୪୪', '୪୫', '୪୬', '୪୭', '୪୮', '୪୯', '୫୦', '୫୧', '୫୨', '୫୩', '୫୪', '୫୫', '୫୬', '୫୭', '୫୮', '୫୯', '୬୦', '୬୧', '୬୨', '୬୩', '୬୪', '୬୫', '୬୬', '୬୭', '୬୮', '୬୯', '୭୦', '୭୧', '୭୨', '୭୩', '୭୪', '୭୫', '୭୬', '୭୭', '୭୮', '୭୯', '୮୦', '୮୧', '୮୨', '୮୩', '୮୪', '୮୫', '୮୬', '୮୭', '୮୮', '୮୯', '୯୦', '୯୧', '୯୨', '୯୩', '୯୪', '୯୫', '୯୬', '୯୭', '୯୮', '୯୯'], + 'year' => ':count ବର୍ଷ', + 'y' => ':count ବ.', + 'month' => ':count ମାସ', + 'm' => ':count ମା.', + 'week' => ':count ସପ୍ତାହ', + 'w' => ':count ସପ୍ତା.', + 'day' => ':count ଦିନ', + 'd' => ':count ଦିନ', + 'hour' => ':count ଘଣ୍ତ', + 'h' => ':count ଘ.', + 'minute' => ':count ମିନଟ', + 'min' => ':count ମି.', + 'second' => ':count ସେକଣ୍ଢ', + 's' => ':count ସେ.', + 'ago' => ':time ପୂର୍ବେ', + 'from_now' => ':timeରେ', +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/os.php b/vendor/nesbot/carbon/src/Carbon/Lang/os.php new file mode 100644 index 0000000..5f55e8a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/os.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/os_RU.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/os_RU.php b/vendor/nesbot/carbon/src/Carbon/Lang/os_RU.php new file mode 100644 index 0000000..9592d15 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/os_RU.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['январы', 'февралы', 'мартъийы', 'апрелы', 'майы', 'июны', 'июлы', 'августы', 'сентябры', 'октябры', 'ноябры', 'декабры'], + 'months_short' => ['Янв', 'Фев', 'Мар', 'Апр', 'Май', 'Июн', 'Июл', 'Авг', 'Сен', 'Окт', 'Ноя', 'Дек'], + 'weekdays' => ['Хуыцаубон', 'Къуырисæр', 'Дыццæг', 'Æртыццæг', 'Цыппæрæм', 'Майрæмбон', 'Сабат'], + 'weekdays_short' => ['Хцб', 'Крс', 'Дцг', 'Æрт', 'Цпр', 'Мрб', 'Сбт'], + 'weekdays_min' => ['Хцб', 'Крс', 'Дцг', 'Æрт', 'Цпр', 'Мрб', 'Сбт'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'minute' => ':count гыццыл', // less reliable + 'min' => ':count гыццыл', // less reliable + 'a_minute' => ':count гыццыл', // less reliable + + 'second' => ':count æндæр', // less reliable + 's' => ':count æндæр', // less reliable + 'a_second' => ':count æндæр', // less reliable + + 'year' => ':count аз', + 'y' => ':count аз', + 'a_year' => ':count аз', + + 'month' => ':count мӕй', + 'm' => ':count мӕй', + 'a_month' => ':count мӕй', + + 'week' => ':count къуыри', + 'w' => ':count къуыри', + 'a_week' => ':count къуыри', + + 'day' => ':count бон', + 'd' => ':count бон', + 'a_day' => ':count бон', + + 'hour' => ':count сахат', + 'h' => ':count сахат', + 'a_hour' => ':count сахат', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pa.php b/vendor/nesbot/carbon/src/Carbon/Lang/pa.php new file mode 100644 index 0000000..48b2033 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pa.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - Tsutomu Kuroda + * - Punjab + */ +return [ + 'year' => 'ਇੱਕ ਸਾਲ|:count ਸਾਲ', + 'month' => 'ਇੱਕ ਮਹੀਨਾ|:count ਮਹੀਨੇ', + 'week' => 'ਹਫਤਾ|:count ਹਫ਼ਤੇ', + 'day' => 'ਇੱਕ ਦਿਨ|:count ਦਿਨ', + 'hour' => 'ਇੱਕ ਘੰਟਾ|:count ਘੰਟੇ', + 'minute' => 'ਇਕ ਮਿੰਟ|:count ਮਿੰਟ', + 'second' => 'ਕੁਝ ਸਕਿੰਟ|:count ਸਕਿੰਟ', + 'ago' => ':time ਪਹਿਲਾਂ', + 'from_now' => ':time ਵਿੱਚ', + 'before' => ':time ਤੋਂ ਪਹਿਲਾਂ', + 'after' => ':time ਤੋਂ ਬਾਅਦ', + 'diff_now' => 'ਹੁਣ', + 'diff_today' => 'ਅਜ', + 'diff_yesterday' => 'ਕਲ', + 'diff_tomorrow' => 'ਕਲ', + 'formats' => [ + 'LT' => 'A h:mm ਵਜੇ', + 'LTS' => 'A h:mm:ss ਵਜੇ', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, A h:mm ਵਜੇ', + 'LLLL' => 'dddd, D MMMM YYYY, A h:mm ਵਜੇ', + ], + 'calendar' => [ + 'sameDay' => '[ਅਜ] LT', + 'nextDay' => '[ਕਲ] LT', + 'nextWeek' => '[ਅਗਲਾ] dddd, LT', + 'lastDay' => '[ਕਲ] LT', + 'lastWeek' => '[ਪਿਛਲੇ] dddd, LT', + 'sameElse' => 'L', + ], + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'ਰਾਤ'; + } + if ($hour < 10) { + return 'ਸਵੇਰ'; + } + if ($hour < 17) { + return 'ਦੁਪਹਿਰ'; + } + if ($hour < 20) { + return 'ਸ਼ਾਮ'; + } + + return 'ਰਾਤ'; + }, + 'months' => ['ਜਨਵਰੀ', 'ਫ਼ਰਵਰੀ', 'ਮਾਰਚ', 'ਅਪ੍ਰੈਲ', 'ਮਈ', 'ਜੂਨ', 'ਜੁਲਾਈ', 'ਅਗਸਤ', 'ਸਤੰਬਰ', 'ਅਕਤੂਬਰ', 'ਨਵੰਬਰ', 'ਦਸੰਬਰ'], + 'months_short' => ['ਜਨਵਰੀ', 'ਫ਼ਰਵਰੀ', 'ਮਾਰਚ', 'ਅਪ੍ਰੈਲ', 'ਮਈ', 'ਜੂਨ', 'ਜੁਲਾਈ', 'ਅਗਸਤ', 'ਸਤੰਬਰ', 'ਅਕਤੂਬਰ', 'ਨਵੰਬਰ', 'ਦਸੰਬਰ'], + 'weekdays' => ['ਐਤਵਾਰ', 'ਸੋਮਵਾਰ', 'ਮੰਗਲਵਾਰ', 'ਬੁਧਵਾਰ', 'ਵੀਰਵਾਰ', 'ਸ਼ੁੱਕਰਵਾਰ', 'ਸ਼ਨੀਚਰਵਾਰ'], + 'weekdays_short' => ['ਐਤ', 'ਸੋਮ', 'ਮੰਗਲ', 'ਬੁਧ', 'ਵੀਰ', 'ਸ਼ੁਕਰ', 'ਸ਼ਨੀ'], + 'weekdays_min' => ['ਐਤ', 'ਸੋਮ', 'ਮੰਗਲ', 'ਬੁਧ', 'ਵੀਰ', 'ਸ਼ੁਕਰ', 'ਸ਼ਨੀ'], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' ਅਤੇ '], + 'weekend' => [0, 0], + 'alt_numbers' => ['੦', '੧', '੨', '੩', '੪', '੫', '੬', '੭', '੮', '੯'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pa_Arab.php b/vendor/nesbot/carbon/src/Carbon/Lang/pa_Arab.php new file mode 100644 index 0000000..39b0653 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pa_Arab.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ur.php', [ + 'weekdays' => ['اتوار', 'پیر', 'منگل', 'بُدھ', 'جمعرات', 'جمعہ', 'ہفتہ'], + 'weekdays_short' => ['اتوار', 'پیر', 'منگل', 'بُدھ', 'جمعرات', 'جمعہ', 'ہفتہ'], + 'weekdays_min' => ['اتوار', 'پیر', 'منگل', 'بُدھ', 'جمعرات', 'جمعہ', 'ہفتہ'], + 'months' => ['جنوری', 'فروری', 'مارچ', 'اپریل', 'مئ', 'جون', 'جولائی', 'اگست', 'ستمبر', 'اکتوبر', 'نومبر', 'دسمبر'], + 'months_short' => ['جنوری', 'فروری', 'مارچ', 'اپریل', 'مئ', 'جون', 'جولائی', 'اگست', 'ستمبر', 'اکتوبر', 'نومبر', 'دسمبر'], + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd, DD MMMM YYYY h:mm a', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pa_Guru.php b/vendor/nesbot/carbon/src/Carbon/Lang/pa_Guru.php new file mode 100644 index 0000000..7adff5c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pa_Guru.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/pa.php', [ + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'D/M/yy', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY, h:mm a', + 'LLLL' => 'dddd, D MMMM YYYY, h:mm a', + ], + 'months' => ['ਜਨਵਰੀ', 'ਫ਼ਰਵਰੀ', 'ਮਾਰਚ', 'ਅਪ੍ਰੈਲ', 'ਮਈ', 'ਜੂਨ', 'ਜੁਲਾਈ', 'ਅਗਸਤ', 'ਸਤੰਬਰ', 'ਅਕਤੂਬਰ', 'ਨਵੰਬਰ', 'ਦਸੰਬਰ'], + 'months_short' => ['ਜਨ', 'ਫ਼ਰ', 'ਮਾਰਚ', 'ਅਪ੍ਰੈ', 'ਮਈ', 'ਜੂਨ', 'ਜੁਲਾ', 'ਅਗ', 'ਸਤੰ', 'ਅਕਤੂ', 'ਨਵੰ', 'ਦਸੰ'], + 'weekdays' => ['ਐਤਵਾਰ', 'ਸੋਮਵਾਰ', 'ਮੰਗਲਵਾਰ', 'ਬੁੱਧਵਾਰ', 'ਵੀਰਵਾਰ', 'ਸ਼ੁੱਕਰਵਾਰ', 'ਸ਼ਨਿੱਚਰਵਾਰ'], + 'weekdays_short' => ['ਐਤ', 'ਸੋਮ', 'ਮੰਗਲ', 'ਬੁੱਧ', 'ਵੀਰ', 'ਸ਼ੁੱਕਰ', 'ਸ਼ਨਿੱਚਰ'], + 'weekdays_min' => ['ਐਤ', 'ਸੋਮ', 'ਮੰਗ', 'ਬੁੱਧ', 'ਵੀਰ', 'ਸ਼ੁੱਕ', 'ਸ਼ਨਿੱ'], + 'weekend' => [0, 0], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pa_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/pa_IN.php new file mode 100644 index 0000000..ca67642 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pa_IN.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Guo Xiang Tan + * - Josh Soref + * - Ash + * - harpreetkhalsagtbit + */ +return require __DIR__.'/pa.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pa_PK.php b/vendor/nesbot/carbon/src/Carbon/Lang/pa_PK.php new file mode 100644 index 0000000..f9af11c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pa_PK.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['جنوري', 'فروري', 'مارچ', 'اپريل', 'مٓی', 'جون', 'جولاي', 'اگست', 'ستمبر', 'اكتوبر', 'نومبر', 'دسمبر'], + 'months_short' => ['جنوري', 'فروري', 'مارچ', 'اپريل', 'مٓی', 'جون', 'جولاي', 'اگست', 'ستمبر', 'اكتوبر', 'نومبر', 'دسمبر'], + 'weekdays' => ['اتوار', 'پير', 'منگل', 'بدھ', 'جمعرات', 'جمعه', 'هفته'], + 'weekdays_short' => ['اتوار', 'پير', 'منگل', 'بدھ', 'جمعرات', 'جمعه', 'هفته'], + 'weekdays_min' => ['اتوار', 'پير', 'منگل', 'بدھ', 'جمعرات', 'جمعه', 'هفته'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ص', 'ش'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pap.php b/vendor/nesbot/carbon/src/Carbon/Lang/pap.php new file mode 100644 index 0000000..b4c1706 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pap.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return [ + 'formats' => [ + 'LT' => 'HH.mm', + 'LTS' => 'HH.mm:ss', + 'L' => 'DD-MM-YY', + 'LL' => 'MMMM [di] DD, YYYY', + 'LLL' => 'DD MMM HH.mm', + 'LLLL' => 'MMMM DD, YYYY HH.mm', + ], + 'months' => ['yanüari', 'febrüari', 'mart', 'aprel', 'mei', 'yüni', 'yüli', 'ougùstùs', 'sèptèmber', 'oktober', 'novèmber', 'desèmber'], + 'months_short' => ['yan', 'feb', 'mar', 'apr', 'mei', 'yün', 'yül', 'oug', 'sèp', 'okt', 'nov', 'des'], + 'weekdays' => ['djadomingo', 'djaluna', 'djamars', 'djawebs', 'djarason', 'djabierne', 'djasabra'], + 'weekdays_short' => ['do', 'lu', 'ma', 'we', 'ra', 'bi', 'sa'], + 'weekdays_min' => ['do', 'lu', 'ma', 'we', 'ra', 'bi', 'sa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'year' => ':count aña', + 'month' => ':count luna', + 'week' => ':count siman', + 'day' => ':count dia', + 'hour' => ':count ora', + 'minute' => ':count minüt', + 'second' => ':count sekònde', + 'list' => [', ', ' i '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pap_AW.php b/vendor/nesbot/carbon/src/Carbon/Lang/pap_AW.php new file mode 100644 index 0000000..e9a48ff --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pap_AW.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - information from native speaker Pablo Saratxaga pablo@mandrakesoft.com + */ +return require __DIR__.'/pap.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pap_CW.php b/vendor/nesbot/carbon/src/Carbon/Lang/pap_CW.php new file mode 100644 index 0000000..e9a48ff --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pap_CW.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - information from native speaker Pablo Saratxaga pablo@mandrakesoft.com + */ +return require __DIR__.'/pap.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pl.php b/vendor/nesbot/carbon/src/Carbon/Lang/pl.php new file mode 100644 index 0000000..f80b25e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pl.php @@ -0,0 +1,143 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Wacław Jacek + * - François B + * - Tim Fish + * - Serhan Apaydın + * - Massimiliano Caniparoli + * - JD Isaacks + * - Jakub Szwacz + * - Jan + * - Paul + * - damlys + * - Marek (marast78) + * - Peter (UnrulyNatives) + * - Qrzysio + * - Jan (aso824) + * - diverpl + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count rok|:count lata|:count lat', + 'a_year' => 'rok|:count lata|:count lat', + 'y' => ':count r|:count l|:count l', + 'month' => ':count miesiąc|:count miesiące|:count miesięcy', + 'a_month' => 'miesiąc|:count miesiące|:count miesięcy', + 'm' => ':count mies.', + 'week' => ':count tydzień|:count tygodnie|:count tygodni', + 'a_week' => 'tydzień|:count tygodnie|:count tygodni', + 'w' => ':count tyg.', + 'day' => ':count dzień|:count dni|:count dni', + 'a_day' => 'dzień|:count dni|:count dni', + 'd' => ':count d', + 'hour' => ':count godzina|:count godziny|:count godzin', + 'a_hour' => 'godzina|:count godziny|:count godzin', + 'h' => ':count godz.', + 'minute' => ':count minuta|:count minuty|:count minut', + 'a_minute' => 'minuta|:count minuty|:count minut', + 'min' => ':count min', + 'second' => ':count sekunda|:count sekundy|:count sekund', + 'a_second' => '{1}kilka sekund|:count sekunda|:count sekundy|:count sekund', + 's' => ':count sek.', + 'ago' => ':time temu', + 'from_now' => static function ($time) { + switch ($time) { + case '1 godzina': + return 'za 1 godzinę'; + + case '1 minuta': + return 'za 1 minutę'; + + case '1 sekunda': + return 'za 1 sekundę'; + + case 'godzina': + return 'za godzinę'; + + case 'minuta': + return 'za minutę'; + + case 'sekunda': + return 'za sekundę'; + + default: + return "za $time"; + } + }, + 'after' => ':time po', + 'before' => ':time przed', + 'diff_now' => 'przed chwilą', + 'diff_today' => 'Dziś', + 'diff_today_regexp' => 'Dziś(?:\\s+o)?', + 'diff_yesterday' => 'wczoraj', + 'diff_yesterday_regexp' => 'Wczoraj(?:\\s+o)?', + 'diff_tomorrow' => 'jutro', + 'diff_tomorrow_regexp' => 'Jutro(?:\\s+o)?', + 'diff_before_yesterday' => 'przedwczoraj', + 'diff_after_tomorrow' => 'pojutrze', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Dziś o] LT', + 'nextDay' => '[Jutro o] LT', + 'nextWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[W niedzielę o] LT'; + case 2: + return '[We wtorek o] LT'; + case 3: + return '[W środę o] LT'; + case 6: + return '[W sobotę o] LT'; + default: + return '[W] dddd [o] LT'; + } + }, + 'lastDay' => '[Wczoraj o] LT', + 'lastWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[W zeszłą niedzielę o] LT'; + case 3: + return '[W zeszłą środę o] LT'; + case 6: + return '[W zeszłą sobotę o] LT'; + default: + return '[W zeszły] dddd [o] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['stycznia', 'lutego', 'marca', 'kwietnia', 'maja', 'czerwca', 'lipca', 'sierpnia', 'września', 'października', 'listopada', 'grudnia'], + 'months_standalone' => ['styczeń', 'luty', 'marzec', 'kwiecień', 'maj', 'czerwiec', 'lipiec', 'sierpień', 'wrzesień', 'październik', 'listopad', 'grudzień'], + 'months_short' => ['sty', 'lut', 'mar', 'kwi', 'maj', 'cze', 'lip', 'sie', 'wrz', 'paź', 'lis', 'gru'], + 'months_regexp' => '/(DD?o?\.?(\[[^\[\]]*\]|\s)+MMMM?|L{2,4}|l{2,4})/', + 'weekdays' => ['niedziela', 'poniedziałek', 'wtorek', 'środa', 'czwartek', 'piątek', 'sobota'], + 'weekdays_short' => ['ndz', 'pon', 'wt', 'śr', 'czw', 'pt', 'sob'], + 'weekdays_min' => ['Nd', 'Pn', 'Wt', 'Śr', 'Cz', 'Pt', 'So'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' i '], + 'meridiem' => ['przed południem', 'po południu'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pl_PL.php b/vendor/nesbot/carbon/src/Carbon/Lang/pl_PL.php new file mode 100644 index 0000000..222bcdb --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pl_PL.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/pl.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/prg.php b/vendor/nesbot/carbon/src/Carbon/Lang/prg.php new file mode 100644 index 0000000..6e63f4a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/prg.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'months' => ['M01', 'M02', 'M03', 'M04', 'M05', 'M06', 'M07', 'M08', 'M09', 'M10', 'M11', 'M12'], + 'months_short' => ['M01', 'M02', 'M03', 'M04', 'M05', 'M06', 'M07', 'M08', 'M09', 'M10', 'M11', 'M12'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-dd', + 'LL' => 'YYYY MMM D', + 'LLL' => 'YYYY MMMM D HH:mm', + 'LLLL' => 'YYYY MMMM D, dddd HH:mm', + ], + + 'year' => ':count meta', + 'y' => ':count meta', + 'a_year' => ':count meta', + + 'month' => ':count mēniks', // less reliable + 'm' => ':count mēniks', // less reliable + 'a_month' => ':count mēniks', // less reliable + + 'week' => ':count sawaītin', // less reliable + 'w' => ':count sawaītin', // less reliable + 'a_week' => ':count sawaītin', // less reliable + + 'day' => ':count di', + 'd' => ':count di', + 'a_day' => ':count di', + + 'hour' => ':count bruktēt', // less reliable + 'h' => ':count bruktēt', // less reliable + 'a_hour' => ':count bruktēt', // less reliable + + 'minute' => ':count līkuts', // less reliable + 'min' => ':count līkuts', // less reliable + 'a_minute' => ':count līkuts', // less reliable + + 'second' => ':count kitan', // less reliable + 's' => ':count kitan', // less reliable + 'a_second' => ':count kitan', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ps.php b/vendor/nesbot/carbon/src/Carbon/Lang/ps.php new file mode 100644 index 0000000..a928b28 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ps.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Muhammad Nasir Rahimi + * - Nassim Nasibullah (spinzar) + */ +return [ + 'year' => ':count کال|:count کاله', + 'y' => ':countکال|:countکاله', + 'month' => ':count مياشت|:count مياشتي', + 'm' => ':countمياشت|:countمياشتي', + 'week' => ':count اونۍ|:count اونۍ', + 'w' => ':countاونۍ|:countاونۍ', + 'day' => ':count ورځ|:count ورځي', + 'd' => ':countورځ|:countورځي', + 'hour' => ':count ساعت|:count ساعته', + 'h' => ':countساعت|:countساعته', + 'minute' => ':count دقيقه|:count دقيقې', + 'min' => ':countدقيقه|:countدقيقې', + 'second' => ':count ثانيه|:count ثانيې', + 's' => ':countثانيه|:countثانيې', + 'ago' => ':time دمخه', + 'from_now' => ':time له اوس څخه', + 'after' => ':time وروسته', + 'before' => ':time دمخه', + 'list' => ['، ', ' او '], + 'meridiem' => ['غ.م.', 'غ.و.'], + 'weekdays' => ['اتوار', 'ګل', 'نهه', 'شورو', 'زيارت', 'جمعه', 'خالي'], + 'weekdays_short' => ['ا', 'ګ', 'ن', 'ش', 'ز', 'ج', 'خ'], + 'weekdays_min' => ['ا', 'ګ', 'ن', 'ش', 'ز', 'ج', 'خ'], + 'months' => ['جنوري', 'فبروري', 'مارچ', 'اپریل', 'مۍ', 'جون', 'جولای', 'اگست', 'سېپتمبر', 'اکتوبر', 'نومبر', 'دسمبر'], + 'months_short' => ['جنوري', 'فبروري', 'مارچ', 'اپریل', 'مۍ', 'جون', 'جولای', 'اگست', 'سېپتمبر', 'اکتوبر', 'نومبر', 'دسمبر'], + 'months_standalone' => ['جنوري', 'فېبروري', 'مارچ', 'اپریل', 'مۍ', 'جون', 'جولای', 'اگست', 'سپتمبر', 'اکتوبر', 'نومبر', 'دسمبر'], + 'months_short_standalone' => ['جنوري', 'فبروري', 'مارچ', 'اپریل', 'مۍ', 'جون', 'جولای', 'اگست', 'سپتمبر', 'اکتوبر', 'نومبر', 'دسمبر'], + 'first_day_of_week' => 6, + 'weekend' => [4, 5], + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'YYYY/M/d', + 'LL' => 'YYYY MMM D', + 'LLL' => 'د YYYY د MMMM D H:mm', + 'LLLL' => 'dddd د YYYY د MMMM D H:mm', + ], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ps_AF.php b/vendor/nesbot/carbon/src/Carbon/Lang/ps_AF.php new file mode 100644 index 0000000..6ec5180 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ps_AF.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ps.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pt.php b/vendor/nesbot/carbon/src/Carbon/Lang/pt.php new file mode 100644 index 0000000..0af8949 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pt.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Cassiano Montanari + * - Matt Pope + * - François B + * - Prodis + * - JD Isaacks + * - Raphael Amorim + * - João Magalhães + * - victortobias + * - Paulo Freitas + * - Sebastian Thierer + * - Claudson Martins (claudsonm) + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count ano|:count anos', + 'a_year' => 'um ano|:count anos', + 'y' => ':counta', + 'month' => ':count mês|:count meses', + 'a_month' => 'um mês|:count meses', + 'm' => ':countm', + 'week' => ':count semana|:count semanas', + 'a_week' => 'uma semana|:count semanas', + 'w' => ':countsem', + 'day' => ':count dia|:count dias', + 'a_day' => 'um dia|:count dias', + 'd' => ':countd', + 'hour' => ':count hora|:count horas', + 'a_hour' => 'uma hora|:count horas', + 'h' => ':counth', + 'minute' => ':count minuto|:count minutos', + 'a_minute' => 'um minuto|:count minutos', + 'min' => ':countmin', + 'second' => ':count segundo|:count segundos', + 'a_second' => 'alguns segundos|:count segundos', + 's' => ':counts', + 'millisecond' => ':count milissegundo|:count milissegundos', + 'a_millisecond' => 'um milissegundo|:count milissegundos', + 'ms' => ':countms', + 'microsecond' => ':count microssegundo|:count microssegundos', + 'a_microsecond' => 'um microssegundo|:count microssegundos', + 'µs' => ':countµs', + 'ago' => 'há :time', + 'from_now' => 'em :time', + 'after' => ':time depois', + 'before' => ':time antes', + 'diff_now' => 'agora', + 'diff_today' => 'Hoje', + 'diff_today_regexp' => 'Hoje(?:\\s+às)?', + 'diff_yesterday' => 'ontem', + 'diff_yesterday_regexp' => 'Ontem(?:\\s+às)?', + 'diff_tomorrow' => 'amanhã', + 'diff_tomorrow_regexp' => 'Amanhã(?:\\s+às)?', + 'diff_before_yesterday' => 'anteontem', + 'diff_after_tomorrow' => 'depois de amanhã', + 'period_recurrences' => 'uma vez|:count vezes', + 'period_interval' => 'cada :interval', + 'period_start_date' => 'de :date', + 'period_end_date' => 'até :date', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D [de] MMMM [de] YYYY', + 'LLL' => 'D [de] MMMM [de] YYYY HH:mm', + 'LLLL' => 'dddd, D [de] MMMM [de] YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Hoje às] LT', + 'nextDay' => '[Amanhã às] LT', + 'nextWeek' => 'dddd [às] LT', + 'lastDay' => '[Ontem às] LT', + 'lastWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + case 6: + return '[Último] dddd [às] LT'; + default: + return '[Última] dddd [às] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => ':numberº', + 'months' => ['janeiro', 'fevereiro', 'março', 'abril', 'maio', 'junho', 'julho', 'agosto', 'setembro', 'outubro', 'novembro', 'dezembro'], + 'months_short' => ['jan', 'fev', 'mar', 'abr', 'mai', 'jun', 'jul', 'ago', 'set', 'out', 'nov', 'dez'], + 'weekdays' => ['domingo', 'segunda-feira', 'terça-feira', 'quarta-feira', 'quinta-feira', 'sexta-feira', 'sábado'], + 'weekdays_short' => ['dom', 'seg', 'ter', 'qua', 'qui', 'sex', 'sáb'], + 'weekdays_min' => ['Do', '2ª', '3ª', '4ª', '5ª', '6ª', 'Sá'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' e '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pt_AO.php b/vendor/nesbot/carbon/src/Carbon/Lang/pt_AO.php new file mode 100644 index 0000000..22c01ec --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pt_AO.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/pt.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pt_BR.php b/vendor/nesbot/carbon/src/Carbon/Lang/pt_BR.php new file mode 100644 index 0000000..e917c5c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pt_BR.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Cassiano Montanari + * - Eduardo Dalla Vecchia + * - David Rodrigues + * - Matt Pope + * - François B + * - Prodis + * - Marlon Maxwel + * - JD Isaacks + * - Raphael Amorim + * - Rafael Raupp + * - felipeleite1 + * - swalker + * - Lucas Macedo + * - Paulo Freitas + * - Sebastian Thierer + */ +return array_replace_recursive(require __DIR__.'/pt.php', [ + 'period_recurrences' => 'uma|:count vez', + 'period_interval' => 'toda :interval', + 'formats' => [ + 'LLL' => 'D [de] MMMM [de] YYYY [às] HH:mm', + 'LLLL' => 'dddd, D [de] MMMM [de] YYYY [às] HH:mm', + ], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pt_CH.php b/vendor/nesbot/carbon/src/Carbon/Lang/pt_CH.php new file mode 100644 index 0000000..22c01ec --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pt_CH.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/pt.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pt_CV.php b/vendor/nesbot/carbon/src/Carbon/Lang/pt_CV.php new file mode 100644 index 0000000..22c01ec --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pt_CV.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/pt.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pt_GQ.php b/vendor/nesbot/carbon/src/Carbon/Lang/pt_GQ.php new file mode 100644 index 0000000..22c01ec --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pt_GQ.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/pt.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pt_GW.php b/vendor/nesbot/carbon/src/Carbon/Lang/pt_GW.php new file mode 100644 index 0000000..22c01ec --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pt_GW.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/pt.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pt_LU.php b/vendor/nesbot/carbon/src/Carbon/Lang/pt_LU.php new file mode 100644 index 0000000..22c01ec --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pt_LU.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/pt.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pt_MO.php b/vendor/nesbot/carbon/src/Carbon/Lang/pt_MO.php new file mode 100644 index 0000000..f2b5eab --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pt_MO.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/pt.php', [ + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'LLL' => 'D [de] MMMM [de] YYYY, h:mm a', + 'LLLL' => 'dddd, D [de] MMMM [de] YYYY, h:mm a', + ], + 'first_day_of_week' => 0, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pt_MZ.php b/vendor/nesbot/carbon/src/Carbon/Lang/pt_MZ.php new file mode 100644 index 0000000..fbc0c97 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pt_MZ.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/pt.php', [ + 'first_day_of_week' => 0, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pt_PT.php b/vendor/nesbot/carbon/src/Carbon/Lang/pt_PT.php new file mode 100644 index 0000000..2a76fc1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pt_PT.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RAP bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/pt.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['janeiro', 'fevereiro', 'março', 'abril', 'maio', 'junho', 'julho', 'agosto', 'setembro', 'outubro', 'novembro', 'dezembro'], + 'months_short' => ['jan', 'fev', 'mar', 'abr', 'mai', 'jun', 'jul', 'ago', 'set', 'out', 'nov', 'dez'], + 'weekdays' => ['domingo', 'segunda', 'terça', 'quarta', 'quinta', 'sexta', 'sábado'], + 'weekdays_short' => ['dom', 'seg', 'ter', 'qua', 'qui', 'sex', 'sáb'], + 'weekdays_min' => ['dom', 'seg', 'ter', 'qua', 'qui', 'sex', 'sáb'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pt_ST.php b/vendor/nesbot/carbon/src/Carbon/Lang/pt_ST.php new file mode 100644 index 0000000..22c01ec --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pt_ST.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/pt.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/pt_TL.php b/vendor/nesbot/carbon/src/Carbon/Lang/pt_TL.php new file mode 100644 index 0000000..22c01ec --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/pt_TL.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/pt.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/qu.php b/vendor/nesbot/carbon/src/Carbon/Lang/qu.php new file mode 100644 index 0000000..65278cd --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/qu.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/es_UY.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM, YYYY HH:mm', + ], + 'first_day_of_week' => 0, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/qu_BO.php b/vendor/nesbot/carbon/src/Carbon/Lang/qu_BO.php new file mode 100644 index 0000000..d5db6bf --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/qu_BO.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/qu.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/qu_EC.php b/vendor/nesbot/carbon/src/Carbon/Lang/qu_EC.php new file mode 100644 index 0000000..d5db6bf --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/qu_EC.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/qu.php', [ + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/quz.php b/vendor/nesbot/carbon/src/Carbon/Lang/quz.php new file mode 100644 index 0000000..1640c02 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/quz.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/quz_PE.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/quz_PE.php b/vendor/nesbot/carbon/src/Carbon/Lang/quz_PE.php new file mode 100644 index 0000000..d322918 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/quz_PE.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Sugar Labs // OLPC sugarlabs.org libc-alpha@sourceware.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['iniru', 'phiwriru', 'marsu', 'awril', 'mayu', 'huniyu', 'huliyu', 'agustu', 'siptiyimri', 'uktuwri', 'nuwiyimri', 'tisiyimri'], + 'months_short' => ['ini', 'phi', 'mar', 'awr', 'may', 'hun', 'hul', 'agu', 'sip', 'ukt', 'nuw', 'tis'], + 'weekdays' => ['tuminku', 'lunis', 'martis', 'miyirkulis', 'juywis', 'wiyirnis', 'sawatu'], + 'weekdays_short' => ['tum', 'lun', 'mar', 'miy', 'juy', 'wiy', 'saw'], + 'weekdays_min' => ['tum', 'lun', 'mar', 'miy', 'juy', 'wiy', 'saw'], + 'day_of_first_week_of_year' => 1, + + 'minute' => ':count uchuy', // less reliable + 'min' => ':count uchuy', // less reliable + 'a_minute' => ':count uchuy', // less reliable + + 'year' => ':count wata', + 'y' => ':count wata', + 'a_year' => ':count wata', + + 'month' => ':count killa', + 'm' => ':count killa', + 'a_month' => ':count killa', + + 'week' => ':count simana', + 'w' => ':count simana', + 'a_week' => ':count simana', + + 'day' => ':count pʼunchaw', + 'd' => ':count pʼunchaw', + 'a_day' => ':count pʼunchaw', + + 'hour' => ':count ura', + 'h' => ':count ura', + 'a_hour' => ':count ura', + + 'second' => ':count iskay ñiqin', + 's' => ':count iskay ñiqin', + 'a_second' => ':count iskay ñiqin', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/raj.php b/vendor/nesbot/carbon/src/Carbon/Lang/raj.php new file mode 100644 index 0000000..26138c9 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/raj.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/raj_IN.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/raj_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/raj_IN.php new file mode 100644 index 0000000..7b4589c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/raj_IN.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - meghrajsuthar03@gmail.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['जनवरी', 'फरवरी', 'मार्च', 'अप्रैल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितंबर', 'अक्टूबर', 'नवंबर', 'दिसंबर'], + 'months_short' => ['जन', 'फर', 'मार्च', 'अप्रै', 'मई', 'जून', 'जुल', 'अग', 'सित', 'अक्टू', 'नव', 'दिस'], + 'weekdays' => ['रविवार', 'सोमवार', 'मंगल्लवार', 'बुधवार', 'बृहस्पतिवार', 'शुक्रवार', 'शनिवार'], + 'weekdays_short' => ['रवि', 'सोम', 'मंगल', 'बुध', 'बृहस्पति', 'शुक्र', 'शनि'], + 'weekdays_min' => ['रवि', 'सोम', 'मंगल', 'बुध', 'बृहस्पति', 'शुक्र', 'शनि'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['पूर्वाह्न', 'अपराह्न'], + + 'year' => ':count आंहू', // less reliable + 'y' => ':count आंहू', // less reliable + 'a_year' => ':count आंहू', // less reliable + + 'month' => ':count सूरज', // less reliable + 'm' => ':count सूरज', // less reliable + 'a_month' => ':count सूरज', // less reliable + + 'week' => ':count निवाज', // less reliable + 'w' => ':count निवाज', // less reliable + 'a_week' => ':count निवाज', // less reliable + + 'day' => ':count अेक', // less reliable + 'd' => ':count अेक', // less reliable + 'a_day' => ':count अेक', // less reliable + + 'hour' => ':count दुनियांण', // less reliable + 'h' => ':count दुनियांण', // less reliable + 'a_hour' => ':count दुनियांण', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/rm.php b/vendor/nesbot/carbon/src/Carbon/Lang/rm.php new file mode 100644 index 0000000..1843f45 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/rm.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - tjku + * - Max Melentiev + * - Juanito Fatas + * - Tsutomu Kuroda + * - Akira Matsuda + * - Christopher Dell + * - Enrique Vidal + * - Simone Carletti + * - Aaron Patterson + * - Nicolás Hock Isaza + * - sebastian de castelberg + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'Do MMMM YYYY', + 'LLL' => 'Do MMMM, HH:mm [Uhr]', + 'LLLL' => 'dddd, Do MMMM YYYY, HH:mm [Uhr]', + ], + 'year' => ':count onn|:count onns', + 'month' => ':count mais', + 'week' => ':count emna|:count emnas', + 'day' => ':count di|:count dis', + 'hour' => ':count oura|:count ouras', + 'minute' => ':count minuta|:count minutas', + 'second' => ':count secunda|:count secundas', + 'weekdays' => ['dumengia', 'glindesdi', 'mardi', 'mesemna', 'gievgia', 'venderdi', 'sonda'], + 'weekdays_short' => ['du', 'gli', 'ma', 'me', 'gie', 've', 'so'], + 'weekdays_min' => ['du', 'gli', 'ma', 'me', 'gie', 've', 'so'], + 'months' => ['schaner', 'favrer', 'mars', 'avrigl', 'matg', 'zercladur', 'fanadur', 'avust', 'settember', 'october', 'november', 'december'], + 'months_short' => ['schan', 'favr', 'mars', 'avr', 'matg', 'zercl', 'fan', 'avust', 'sett', 'oct', 'nov', 'dec'], + 'meridiem' => ['avantmezdi', 'suentermezdi'], + 'list' => [', ', ' e '], + 'first_day_of_week' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/rn.php b/vendor/nesbot/carbon/src/Carbon/Lang/rn.php new file mode 100644 index 0000000..8ab958e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/rn.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Z.MU.', 'Z.MW.'], + 'weekdays' => ['Ku w’indwi', 'Ku wa mbere', 'Ku wa kabiri', 'Ku wa gatatu', 'Ku wa kane', 'Ku wa gatanu', 'Ku wa gatandatu'], + 'weekdays_short' => ['cu.', 'mbe.', 'kab.', 'gtu.', 'kan.', 'gnu.', 'gnd.'], + 'weekdays_min' => ['cu.', 'mbe.', 'kab.', 'gtu.', 'kan.', 'gnu.', 'gnd.'], + 'months' => ['Nzero', 'Ruhuhuma', 'Ntwarante', 'Ndamukiza', 'Rusama', 'Ruheshi', 'Mukakaro', 'Nyandagaro', 'Nyakanga', 'Gitugutu', 'Munyonyo', 'Kigarama'], + 'months_short' => ['Mut.', 'Gas.', 'Wer.', 'Mat.', 'Gic.', 'Kam.', 'Nya.', 'Kan.', 'Nze.', 'Ukw.', 'Ugu.', 'Uku.'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + + 'year' => 'imyaka :count', + 'y' => 'imyaka :count', + 'a_year' => 'imyaka :count', + + 'month' => 'amezi :count', + 'm' => 'amezi :count', + 'a_month' => 'amezi :count', + + 'week' => 'indwi :count', + 'w' => 'indwi :count', + 'a_week' => 'indwi :count', + + 'day' => 'imisi :count', + 'd' => 'imisi :count', + 'a_day' => 'imisi :count', + + 'hour' => 'amasaha :count', + 'h' => 'amasaha :count', + 'a_hour' => 'amasaha :count', + + 'minute' => 'iminuta :count', + 'min' => 'iminuta :count', + 'a_minute' => 'iminuta :count', + + 'second' => 'inguvu :count', // less reliable + 's' => 'inguvu :count', // less reliable + 'a_second' => 'inguvu :count', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ro.php b/vendor/nesbot/carbon/src/Carbon/Lang/ro.php new file mode 100644 index 0000000..868a327 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ro.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - JD Isaacks + * - Cătălin Georgescu + * - Valentin Ivaşcu (oriceon) + */ +return [ + 'year' => ':count an|:count ani|:count ani', + 'a_year' => 'un an|:count ani|:count ani', + 'y' => ':count a.', + 'month' => ':count lună|:count luni|:count luni', + 'a_month' => 'o lună|:count luni|:count luni', + 'm' => ':count l.', + 'week' => ':count săptămână|:count săptămâni|:count săptămâni', + 'a_week' => 'o săptămână|:count săptămâni|:count săptămâni', + 'w' => ':count săp.', + 'day' => ':count zi|:count zile|:count zile', + 'a_day' => 'o zi|:count zile|:count zile', + 'd' => ':count z.', + 'hour' => ':count oră|:count ore|:count ore', + 'a_hour' => 'o oră|:count ore|:count ore', + 'h' => ':count o.', + 'minute' => ':count minut|:count minute|:count minute', + 'a_minute' => 'un minut|:count minute|:count minute', + 'min' => ':count m.', + 'second' => ':count secundă|:count secunde|:count secunde', + 'a_second' => 'câteva secunde|:count secunde|:count secunde', + 's' => ':count sec.', + 'ago' => ':time în urmă', + 'from_now' => 'peste :time', + 'after' => 'peste :time', + 'before' => 'acum :time', + 'diff_now' => 'acum', + 'diff_today' => 'azi', + 'diff_today_regexp' => 'azi(?:\\s+la)?', + 'diff_yesterday' => 'ieri', + 'diff_yesterday_regexp' => 'ieri(?:\\s+la)?', + 'diff_tomorrow' => 'mâine', + 'diff_tomorrow_regexp' => 'mâine(?:\\s+la)?', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY H:mm', + 'LLLL' => 'dddd, D MMMM YYYY H:mm', + ], + 'calendar' => [ + 'sameDay' => '[azi la] LT', + 'nextDay' => '[mâine la] LT', + 'nextWeek' => 'dddd [la] LT', + 'lastDay' => '[ieri la] LT', + 'lastWeek' => '[fosta] dddd [la] LT', + 'sameElse' => 'L', + ], + 'months' => ['ianuarie', 'februarie', 'martie', 'aprilie', 'mai', 'iunie', 'iulie', 'august', 'septembrie', 'octombrie', 'noiembrie', 'decembrie'], + 'months_short' => ['ian.', 'feb.', 'mar.', 'apr.', 'mai', 'iun.', 'iul.', 'aug.', 'sept.', 'oct.', 'nov.', 'dec.'], + 'weekdays' => ['duminică', 'luni', 'marți', 'miercuri', 'joi', 'vineri', 'sâmbătă'], + 'weekdays_short' => ['dum', 'lun', 'mar', 'mie', 'joi', 'vin', 'sâm'], + 'weekdays_min' => ['du', 'lu', 'ma', 'mi', 'jo', 'vi', 'sâ'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' și '], + 'meridiem' => ['a.m.', 'p.m.'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ro_MD.php b/vendor/nesbot/carbon/src/Carbon/Lang/ro_MD.php new file mode 100644 index 0000000..ad1d2fa --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ro_MD.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ro.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY, HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY, HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ro_RO.php b/vendor/nesbot/carbon/src/Carbon/Lang/ro_RO.php new file mode 100644 index 0000000..102afcd --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ro_RO.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ro.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/rof.php b/vendor/nesbot/carbon/src/Carbon/Lang/rof.php new file mode 100644 index 0000000..205fc26 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/rof.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['kang’ama', 'kingoto'], + 'weekdays' => ['Ijumapili', 'Ijumatatu', 'Ijumanne', 'Ijumatano', 'Alhamisi', 'Ijumaa', 'Ijumamosi'], + 'weekdays_short' => ['Ijp', 'Ijt', 'Ijn', 'Ijtn', 'Alh', 'Iju', 'Ijm'], + 'weekdays_min' => ['Ijp', 'Ijt', 'Ijn', 'Ijtn', 'Alh', 'Iju', 'Ijm'], + 'months' => ['Mweri wa kwanza', 'Mweri wa kaili', 'Mweri wa katatu', 'Mweri wa kaana', 'Mweri wa tanu', 'Mweri wa sita', 'Mweri wa saba', 'Mweri wa nane', 'Mweri wa tisa', 'Mweri wa ikumi', 'Mweri wa ikumi na moja', 'Mweri wa ikumi na mbili'], + 'months_short' => ['M1', 'M2', 'M3', 'M4', 'M5', 'M6', 'M7', 'M8', 'M9', 'M10', 'M11', 'M12'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ru.php b/vendor/nesbot/carbon/src/Carbon/Lang/ru.php new file mode 100644 index 0000000..673b043 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ru.php @@ -0,0 +1,191 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Bari Badamshin + * - Jørn Ølmheim + * - François B + * - Tim Fish + * - Коренберг Марк (imac) + * - Serhan Apaydın + * - RomeroMsk + * - vsn4ik + * - JD Isaacks + * - Bari Badamshin + * - Jørn Ølmheim + * - François B + * - Коренберг Марк (imac) + * - Serhan Apaydın + * - RomeroMsk + * - vsn4ik + * - JD Isaacks + * - Fellzo + * - andrey-helldar + * - Pavel Skripkin (psxx) + * - AlexWalkerson + * - Vladislav UnsealedOne + * - dima-bzz + */ + +use Carbon\CarbonInterface; + +$transformDiff = function ($input) { + return strtr($input, [ + 'неделя' => 'неделю', + 'секунда' => 'секунду', + 'минута' => 'минуту', + ]); +}; + +return [ + 'year' => ':count год|:count года|:count лет', + 'y' => ':count г.|:count г.|:count л.', + 'a_year' => '{1}год|:count год|:count года|:count лет', + 'month' => ':count месяц|:count месяца|:count месяцев', + 'm' => ':count мес.', + 'a_month' => '{1}месяц|:count месяц|:count месяца|:count месяцев', + 'week' => ':count неделя|:count недели|:count недель', + 'w' => ':count нед.', + 'a_week' => '{1}неделя|:count неделю|:count недели|:count недель', + 'day' => ':count день|:count дня|:count дней', + 'd' => ':count д.', + 'a_day' => '{1}день|:count день|:count дня|:count дней', + 'hour' => ':count час|:count часа|:count часов', + 'h' => ':count ч.', + 'a_hour' => '{1}час|:count час|:count часа|:count часов', + 'minute' => ':count минута|:count минуты|:count минут', + 'min' => ':count мин.', + 'a_minute' => '{1}минута|:count минута|:count минуты|:count минут', + 'second' => ':count секунда|:count секунды|:count секунд', + 's' => ':count сек.', + 'a_second' => '{1}несколько секунд|:count секунду|:count секунды|:count секунд', + 'ago' => function ($time) use ($transformDiff) { + return $transformDiff($time).' назад'; + }, + 'from_now' => function ($time) use ($transformDiff) { + return 'через '.$transformDiff($time); + }, + 'after' => function ($time) use ($transformDiff) { + return $transformDiff($time).' после'; + }, + 'before' => function ($time) use ($transformDiff) { + return $transformDiff($time).' до'; + }, + 'diff_now' => 'только что', + 'diff_today' => 'Сегодня,', + 'diff_today_regexp' => 'Сегодня,?(?:\\s+в)?', + 'diff_yesterday' => 'вчера', + 'diff_yesterday_regexp' => 'Вчера,?(?:\\s+в)?', + 'diff_tomorrow' => 'завтра', + 'diff_tomorrow_regexp' => 'Завтра,?(?:\\s+в)?', + 'diff_before_yesterday' => 'позавчера', + 'diff_after_tomorrow' => 'послезавтра', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY г.', + 'LLL' => 'D MMMM YYYY г., H:mm', + 'LLLL' => 'dddd, D MMMM YYYY г., H:mm', + ], + 'calendar' => [ + 'sameDay' => '[Сегодня, в] LT', + 'nextDay' => '[Завтра, в] LT', + 'nextWeek' => function (CarbonInterface $current, CarbonInterface $other) { + if ($current->week !== $other->week) { + switch ($current->dayOfWeek) { + case 0: + return '[В следующее] dddd, [в] LT'; + case 1: + case 2: + case 4: + return '[В следующий] dddd, [в] LT'; + case 3: + case 5: + case 6: + return '[В следующую] dddd, [в] LT'; + } + } + + if ($current->dayOfWeek === 2) { + return '[Во] dddd, [в] LT'; + } + + return '[В] dddd, [в] LT'; + }, + 'lastDay' => '[Вчера, в] LT', + 'lastWeek' => function (CarbonInterface $current, CarbonInterface $other) { + if ($current->week !== $other->week) { + switch ($current->dayOfWeek) { + case 0: + return '[В прошлое] dddd, [в] LT'; + case 1: + case 2: + case 4: + return '[В прошлый] dddd, [в] LT'; + case 3: + case 5: + case 6: + return '[В прошлую] dddd, [в] LT'; + } + } + + if ($current->dayOfWeek === 2) { + return '[Во] dddd, [в] LT'; + } + + return '[В] dddd, [в] LT'; + }, + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + switch ($period) { + case 'M': + case 'd': + case 'DDD': + return $number.'-й'; + case 'D': + return $number.'-го'; + case 'w': + case 'W': + return $number.'-я'; + default: + return $number; + } + }, + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'ночи'; + } + if ($hour < 12) { + return 'утра'; + } + if ($hour < 17) { + return 'дня'; + } + + return 'вечера'; + }, + 'months' => ['января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря'], + 'months_standalone' => ['январь', 'февраль', 'март', 'апрель', 'май', 'июнь', 'июль', 'август', 'сентябрь', 'октябрь', 'ноябрь', 'декабрь'], + 'months_short' => ['янв', 'фев', 'мар', 'апр', 'мая', 'июн', 'июл', 'авг', 'сен', 'окт', 'ноя', 'дек'], + 'months_short_standalone' => ['янв', 'фев', 'мар', 'апр', 'май', 'июн', 'июл', 'авг', 'сен', 'окт', 'ноя', 'дек'], + 'months_regexp' => '/(DD?o?\.?(\[[^\[\]]*\]|\s)+MMMM?|L{2,4}|l{2,4})/', + 'weekdays' => ['воскресенье', 'понедельник', 'вторник', 'среду', 'четверг', 'пятницу', 'субботу'], + 'weekdays_standalone' => ['воскресенье', 'понедельник', 'вторник', 'среда', 'четверг', 'пятница', 'суббота'], + 'weekdays_short' => ['вск', 'пнд', 'втр', 'срд', 'чтв', 'птн', 'сбт'], + 'weekdays_min' => ['вс', 'пн', 'вт', 'ср', 'чт', 'пт', 'сб'], + 'weekdays_regexp' => '/\[\s*(В|в)\s*((?:прошлую|следующую|эту)\s*)?\]\s*dddd/', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' и '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ru_BY.php b/vendor/nesbot/carbon/src/Carbon/Lang/ru_BY.php new file mode 100644 index 0000000..8ca7df3 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ru_BY.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ru.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ru_KG.php b/vendor/nesbot/carbon/src/Carbon/Lang/ru_KG.php new file mode 100644 index 0000000..8ca7df3 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ru_KG.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ru.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ru_KZ.php b/vendor/nesbot/carbon/src/Carbon/Lang/ru_KZ.php new file mode 100644 index 0000000..8ca7df3 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ru_KZ.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ru.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ru_MD.php b/vendor/nesbot/carbon/src/Carbon/Lang/ru_MD.php new file mode 100644 index 0000000..8ca7df3 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ru_MD.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ru.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ru_RU.php b/vendor/nesbot/carbon/src/Carbon/Lang/ru_RU.php new file mode 100644 index 0000000..8ca7df3 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ru_RU.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ru.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ru_UA.php b/vendor/nesbot/carbon/src/Carbon/Lang/ru_UA.php new file mode 100644 index 0000000..db958d6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ru_UA.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - RFC 2319 bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/ru.php', [ + 'weekdays' => ['воскресенье', 'понедельник', 'вторник', 'среда', 'четверг', 'пятница', 'суббота'], + 'weekdays_short' => ['вск', 'пнд', 'вто', 'срд', 'чтв', 'птн', 'суб'], + 'weekdays_min' => ['вс', 'пн', 'вт', 'ср', 'чт', 'пт', 'су'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/rw.php b/vendor/nesbot/carbon/src/Carbon/Lang/rw.php new file mode 100644 index 0000000..bc4a347 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/rw.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/rw_RW.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/rw_RW.php b/vendor/nesbot/carbon/src/Carbon/Lang/rw_RW.php new file mode 100644 index 0000000..9b3e068 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/rw_RW.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Rwanda Steve Murphy murf@e-tools.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['Mutarama', 'Gashyantare', 'Werurwe', 'Mata', 'Gicuransi', 'Kamena', 'Nyakanga', 'Kanama', 'Nzeli', 'Ukwakira', 'Ugushyingo', 'Ukuboza'], + 'months_short' => ['Mut', 'Gas', 'Wer', 'Mat', 'Gic', 'Kam', 'Nya', 'Kan', 'Nze', 'Ukw', 'Ugu', 'Uku'], + 'weekdays' => ['Ku cyumweru', 'Kuwa mbere', 'Kuwa kabiri', 'Kuwa gatatu', 'Kuwa kane', 'Kuwa gatanu', 'Kuwa gatandatu'], + 'weekdays_short' => ['Mwe', 'Mbe', 'Kab', 'Gtu', 'Kan', 'Gnu', 'Gnd'], + 'weekdays_min' => ['Mwe', 'Mbe', 'Kab', 'Gtu', 'Kan', 'Gnu', 'Gnd'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'second' => ':count vuna', // less reliable + 's' => ':count vuna', // less reliable + 'a_second' => ':count vuna', // less reliable + + 'year' => 'aka :count', + 'y' => 'aka :count', + 'a_year' => 'aka :count', + + 'month' => 'ezi :count', + 'm' => 'ezi :count', + 'a_month' => 'ezi :count', + + 'week' => ':count icyumweru', + 'w' => ':count icyumweru', + 'a_week' => ':count icyumweru', + + 'day' => ':count nsi', + 'd' => ':count nsi', + 'a_day' => ':count nsi', + + 'hour' => 'saha :count', + 'h' => 'saha :count', + 'a_hour' => 'saha :count', + + 'minute' => ':count -nzinya', + 'min' => ':count -nzinya', + 'a_minute' => ':count -nzinya', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/rwk.php b/vendor/nesbot/carbon/src/Carbon/Lang/rwk.php new file mode 100644 index 0000000..ed92e8e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/rwk.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['utuko', 'kyiukonyi'], + 'weekdays' => ['Jumapilyi', 'Jumatatuu', 'Jumanne', 'Jumatanu', 'Alhamisi', 'Ijumaa', 'Jumamosi'], + 'weekdays_short' => ['Jpi', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'weekdays_min' => ['Jpi', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'months' => ['Januari', 'Februari', 'Machi', 'Aprilyi', 'Mei', 'Junyi', 'Julyai', 'Agusti', 'Septemba', 'Oktoba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Sep', 'Okt', 'Nov', 'Des'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sa.php b/vendor/nesbot/carbon/src/Carbon/Lang/sa.php new file mode 100644 index 0000000..1357c03 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sa.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/sa_IN.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sa_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/sa_IN.php new file mode 100644 index 0000000..cfda9a6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sa_IN.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - The Debian project Christian Perrier bubulle@debian.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D-MM-YY', + ], + 'months' => ['जनवरी', 'फ़रवरी', 'मार्च', 'अप्रेल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितम्बर', 'अक्टूबर', 'नवम्बर', 'दिसम्बर'], + 'months_short' => ['जनवरी', 'फ़रवरी', 'मार्च', 'अप्रेल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितम्बर', 'अक्टूबर', 'नवम्बर', 'दिसम्बर'], + 'weekdays' => ['रविवासर:', 'सोमवासर:', 'मंगलवासर:', 'बुधवासर:', 'बृहस्पतिवासरः', 'शुक्रवासर', 'शनिवासर:'], + 'weekdays_short' => ['रविः', 'सोम:', 'मंगल:', 'बुध:', 'बृहस्पतिः', 'शुक्र', 'शनि:'], + 'weekdays_min' => ['रविः', 'सोम:', 'मंगल:', 'बुध:', 'बृहस्पतिः', 'शुक्र', 'शनि:'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['पूर्वाह्न', 'अपराह्न'], + + 'minute' => ':count होरा', // less reliable + 'min' => ':count होरा', // less reliable + 'a_minute' => ':count होरा', // less reliable + + 'year' => ':count वर्ष', + 'y' => ':count वर्ष', + 'a_year' => ':count वर्ष', + + 'month' => ':count मास', + 'm' => ':count मास', + 'a_month' => ':count मास', + + 'week' => ':count सप्ताहः saptahaĥ', + 'w' => ':count सप्ताहः saptahaĥ', + 'a_week' => ':count सप्ताहः saptahaĥ', + + 'day' => ':count दिन', + 'd' => ':count दिन', + 'a_day' => ':count दिन', + + 'hour' => ':count घण्टा', + 'h' => ':count घण्टा', + 'a_hour' => ':count घण्टा', + + 'second' => ':count द्वितीयः', + 's' => ':count द्वितीयः', + 'a_second' => ':count द्वितीयः', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sah.php b/vendor/nesbot/carbon/src/Carbon/Lang/sah.php new file mode 100644 index 0000000..b828824 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sah.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/sah_RU.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sah_RU.php b/vendor/nesbot/carbon/src/Carbon/Lang/sah_RU.php new file mode 100644 index 0000000..94cc0cb --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sah_RU.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Valery Timiriliyev Valery Timiriliyev timiriliyev@gmail.com + */ +return array_replace_recursive(require __DIR__.'/ru.php', [ + 'formats' => [ + 'L' => 'YYYY.MM.DD', + ], + 'months' => ['тохсунньу', 'олунньу', 'кулун тутар', 'муус устар', 'ыам ыйын', 'бэс ыйын', 'от ыйын', 'атырдьах ыйын', 'балаҕан ыйын', 'алтынньы', 'сэтинньи', 'ахсынньы'], + 'months_short' => ['тохс', 'олун', 'кул', 'муус', 'ыам', 'бэс', 'от', 'атыр', 'бал', 'алт', 'сэт', 'ахс'], + 'weekdays' => ['баскыһыанньа', 'бэнидиэнньик', 'оптуорунньук', 'сэрэдэ', 'чэппиэр', 'бээтинсэ', 'субуота'], + 'weekdays_short' => ['бс', 'бн', 'оп', 'ср', 'чп', 'бт', 'сб'], + 'weekdays_min' => ['бс', 'бн', 'оп', 'ср', 'чп', 'бт', 'сб'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/saq.php b/vendor/nesbot/carbon/src/Carbon/Lang/saq.php new file mode 100644 index 0000000..ff8bf60 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/saq.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Tesiran', 'Teipa'], + 'weekdays' => ['Mderot ee are', 'Mderot ee kuni', 'Mderot ee ong’wan', 'Mderot ee inet', 'Mderot ee ile', 'Mderot ee sapa', 'Mderot ee kwe'], + 'weekdays_short' => ['Are', 'Kun', 'Ong', 'Ine', 'Ile', 'Sap', 'Kwe'], + 'weekdays_min' => ['Are', 'Kun', 'Ong', 'Ine', 'Ile', 'Sap', 'Kwe'], + 'months' => ['Lapa le obo', 'Lapa le waare', 'Lapa le okuni', 'Lapa le ong’wan', 'Lapa le imet', 'Lapa le ile', 'Lapa le sapa', 'Lapa le isiet', 'Lapa le saal', 'Lapa le tomon', 'Lapa le tomon obo', 'Lapa le tomon waare'], + 'months_short' => ['Obo', 'Waa', 'Oku', 'Ong', 'Ime', 'Ile', 'Sap', 'Isi', 'Saa', 'Tom', 'Tob', 'Tow'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sat.php b/vendor/nesbot/carbon/src/Carbon/Lang/sat.php new file mode 100644 index 0000000..c9914c6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sat.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/sat_IN.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sat_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/sat_IN.php new file mode 100644 index 0000000..632b1af --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sat_IN.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Red Hat Pune libc-alpha@sourceware.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['जनवरी', 'फरवरी', 'मार्च', 'अप्रेल', 'मई', 'जुन', 'जुलाई', 'अगस्त', 'सितम्बर', 'अखथबर', 'नवम्बर', 'दिसम्बर'], + 'months_short' => ['जनवरी', 'फरवरी', 'मार्च', 'अप्रेल', 'मई', 'जुन', 'जुलाई', 'अगस्त', 'सितम्बर', 'अखथबर', 'नवम्बर', 'दिसम्बर'], + 'weekdays' => ['सिंगेमाँहाँ', 'ओतेमाँहाँ', 'बालेमाँहाँ', 'सागुनमाँहाँ', 'सारदीमाँहाँ', 'जारुममाँहाँ', 'ञुहुममाँहाँ'], + 'weekdays_short' => ['सिंगे', 'ओते', 'बाले', 'सागुन', 'सारदी', 'जारुम', 'ञुहुम'], + 'weekdays_min' => ['सिंगे', 'ओते', 'बाले', 'सागुन', 'सारदी', 'जारुम', 'ञुहुम'], + 'day_of_first_week_of_year' => 1, + + 'month' => ':count ńindạ cando', // less reliable + 'm' => ':count ńindạ cando', // less reliable + 'a_month' => ':count ńindạ cando', // less reliable + + 'week' => ':count mãhã', // less reliable + 'w' => ':count mãhã', // less reliable + 'a_week' => ':count mãhã', // less reliable + + 'hour' => ':count ᱥᱳᱱᱚ', // less reliable + 'h' => ':count ᱥᱳᱱᱚ', // less reliable + 'a_hour' => ':count ᱥᱳᱱᱚ', // less reliable + + 'minute' => ':count ᱯᱤᱞᱪᱩ', // less reliable + 'min' => ':count ᱯᱤᱞᱪᱩ', // less reliable + 'a_minute' => ':count ᱯᱤᱞᱪᱩ', // less reliable + + 'second' => ':count ar', // less reliable + 's' => ':count ar', // less reliable + 'a_second' => ':count ar', // less reliable + + 'year' => ':count ne̲s', + 'y' => ':count ne̲s', + 'a_year' => ':count ne̲s', + + 'day' => ':count ᱫᱤᱱ', + 'd' => ':count ᱫᱤᱱ', + 'a_day' => ':count ᱫᱤᱱ', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sbp.php b/vendor/nesbot/carbon/src/Carbon/Lang/sbp.php new file mode 100644 index 0000000..e29ca37 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sbp.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Lwamilawu', 'Pashamihe'], + 'weekdays' => ['Mulungu', 'Jumatatu', 'Jumanne', 'Jumatano', 'Alahamisi', 'Ijumaa', 'Jumamosi'], + 'weekdays_short' => ['Mul', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'weekdays_min' => ['Mul', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'months' => ['Mupalangulwa', 'Mwitope', 'Mushende', 'Munyi', 'Mushende Magali', 'Mujimbi', 'Mushipepo', 'Mupuguto', 'Munyense', 'Mokhu', 'Musongandembwe', 'Muhaano'], + 'months_short' => ['Mup', 'Mwi', 'Msh', 'Mun', 'Mag', 'Muj', 'Msp', 'Mpg', 'Mye', 'Mok', 'Mus', 'Muh'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sc.php b/vendor/nesbot/carbon/src/Carbon/Lang/sc.php new file mode 100644 index 0000000..7178cf4 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sc.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/sc_IT.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sc_IT.php b/vendor/nesbot/carbon/src/Carbon/Lang/sc_IT.php new file mode 100644 index 0000000..5d1e4ce --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sc_IT.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Sardinian Translators Team Massimeddu Cireddu massimeddu@gmail.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD. MM. YY', + ], + 'months' => ['Ghennàrgiu', 'Freàrgiu', 'Martzu', 'Abrile', 'Maju', 'Làmpadas', 'Argiolas//Trìulas', 'Austu', 'Cabudanni', 'Santugaine//Ladàmine', 'Onniasantu//Santandria', 'Nadale//Idas'], + 'months_short' => ['Ghe', 'Fre', 'Mar', 'Abr', 'Maj', 'Làm', 'Arg', 'Aus', 'Cab', 'Lad', 'Onn', 'Nad'], + 'weekdays' => ['Domìnigu', 'Lunis', 'Martis', 'Mèrcuris', 'Giòbia', 'Chenàbura', 'Sàbadu'], + 'weekdays_short' => ['Dom', 'Lun', 'Mar', 'Mèr', 'Giò', 'Che', 'Sàb'], + 'weekdays_min' => ['Dom', 'Lun', 'Mar', 'Mèr', 'Giò', 'Che', 'Sàb'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'minute' => ':count mementu', // less reliable + 'min' => ':count mementu', // less reliable + 'a_minute' => ':count mementu', // less reliable + + 'year' => ':count annu', + 'y' => ':count annu', + 'a_year' => ':count annu', + + 'month' => ':count mese', + 'm' => ':count mese', + 'a_month' => ':count mese', + + 'week' => ':count chida', + 'w' => ':count chida', + 'a_week' => ':count chida', + + 'day' => ':count dí', + 'd' => ':count dí', + 'a_day' => ':count dí', + + 'hour' => ':count ora', + 'h' => ':count ora', + 'a_hour' => ':count ora', + + 'second' => ':count secundu', + 's' => ':count secundu', + 'a_second' => ':count secundu', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sd.php b/vendor/nesbot/carbon/src/Carbon/Lang/sd.php new file mode 100644 index 0000000..0022c5a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sd.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +$months = [ + 'جنوري', + 'فيبروري', + 'مارچ', + 'اپريل', + 'مئي', + 'جون', + 'جولاءِ', + 'آگسٽ', + 'سيپٽمبر', + 'آڪٽوبر', + 'نومبر', + 'ڊسمبر', +]; + +$weekdays = [ + 'آچر', + 'سومر', + 'اڱارو', + 'اربع', + 'خميس', + 'جمع', + 'ڇنڇر', +]; + +/* + * Authors: + * - Narain Sagar + * - Sawood Alam + * - Narain Sagar + */ +return [ + 'year' => '{1}'.'هڪ سال'.'|:count '.'سال', + 'month' => '{1}'.'هڪ مهينو'.'|:count '.'مهينا', + 'week' => '{1}'.'ھڪ ھفتو'.'|:count '.'هفتا', + 'day' => '{1}'.'هڪ ڏينهن'.'|:count '.'ڏينهن', + 'hour' => '{1}'.'هڪ ڪلاڪ'.'|:count '.'ڪلاڪ', + 'minute' => '{1}'.'هڪ منٽ'.'|:count '.'منٽ', + 'second' => '{1}'.'چند سيڪنڊ'.'|:count '.'سيڪنڊ', + 'ago' => ':time اڳ', + 'from_now' => ':time پوء', + 'diff_yesterday' => 'ڪالهه', + 'diff_today' => 'اڄ', + 'diff_tomorrow' => 'سڀاڻي', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd، D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[اڄ] LT', + 'nextDay' => '[سڀاڻي] LT', + 'nextWeek' => 'dddd [اڳين هفتي تي] LT', + 'lastDay' => '[ڪالهه] LT', + 'lastWeek' => '[گزريل هفتي] dddd [تي] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['صبح', 'شام'], + 'months' => $months, + 'months_short' => $months, + 'weekdays' => $weekdays, + 'weekdays_short' => $weekdays, + 'weekdays_min' => $weekdays, + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => ['، ', ' ۽ '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sd_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/sd_IN.php new file mode 100644 index 0000000..de1dad0 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sd_IN.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Red Hat, Pune bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/sd.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['جنوري', 'فبروري', 'مارچ', 'اپريل', 'مي', 'جون', 'جولاءِ', 'آگسٽ', 'سيپٽيمبر', 'آڪٽوبر', 'نومبر', 'ڊسمبر'], + 'months_short' => ['جنوري', 'فبروري', 'مارچ', 'اپريل', 'مي', 'جون', 'جولاءِ', 'آگسٽ', 'سيپٽيمبر', 'آڪٽوبر', 'نومبر', 'ڊسمبر'], + 'weekdays' => ['آرتوارُ', 'سومرُ', 'منگلُ', 'ٻُڌرُ', 'وسپت', 'جُمو', 'ڇنڇر'], + 'weekdays_short' => ['آرتوارُ', 'سومرُ', 'منگلُ', 'ٻُڌرُ', 'وسپت', 'جُمو', 'ڇنڇر'], + 'weekdays_min' => ['آرتوارُ', 'سومرُ', 'منگلُ', 'ٻُڌرُ', 'وسپت', 'جُمو', 'ڇنڇر'], + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sd_IN@devanagari.php b/vendor/nesbot/carbon/src/Carbon/Lang/sd_IN@devanagari.php new file mode 100644 index 0000000..061fcc1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sd_IN@devanagari.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Red Hat, Pune bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/sd.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['जनवरी', 'फबरवरी', 'मार्चि', 'अप्रेल', 'मे', 'जूनि', 'जूलाइ', 'आगस्टु', 'सेप्टेंबरू', 'आक्टूबरू', 'नवंबरू', 'ॾिसंबरू'], + 'months_short' => ['जनवरी', 'फबरवरी', 'मार्चि', 'अप्रेल', 'मे', 'जूनि', 'जूलाइ', 'आगस्टु', 'सेप्टेंबरू', 'आक्टूबरू', 'नवंबरू', 'ॾिसंबरू'], + 'weekdays' => ['आर्तवारू', 'सूमरू', 'मंगलू', 'ॿुधरू', 'विस्पति', 'जुमो', 'छंछस'], + 'weekdays_short' => ['आर्तवारू', 'सूमरू', 'मंगलू', 'ॿुधरू', 'विस्पति', 'जुमो', 'छंछस'], + 'weekdays_min' => ['आर्तवारू', 'सूमरू', 'मंगलू', 'ॿुधरू', 'विस्पति', 'जुमो', 'छंछस'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['म.पू.', 'म.नं.'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/se.php b/vendor/nesbot/carbon/src/Carbon/Lang/se.php new file mode 100644 index 0000000..7c4b92a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/se.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Karamell + */ +return [ + 'year' => '{1}:count jahki|:count jagit', + 'a_year' => '{1}okta jahki|:count jagit', + 'y' => ':count j.', + 'month' => '{1}:count mánnu|:count mánut', + 'a_month' => '{1}okta mánnu|:count mánut', + 'm' => ':count mán.', + 'week' => '{1}:count vahkku|:count vahkku', + 'a_week' => '{1}okta vahkku|:count vahkku', + 'w' => ':count v.', + 'day' => '{1}:count beaivi|:count beaivvit', + 'a_day' => '{1}okta beaivi|:count beaivvit', + 'd' => ':count b.', + 'hour' => '{1}:count diimmu|:count diimmut', + 'a_hour' => '{1}okta diimmu|:count diimmut', + 'h' => ':count d.', + 'minute' => '{1}:count minuhta|:count minuhtat', + 'a_minute' => '{1}okta minuhta|:count minuhtat', + 'min' => ':count min.', + 'second' => '{1}:count sekunddat|:count sekunddat', + 'a_second' => '{1}moadde sekunddat|:count sekunddat', + 's' => ':count s.', + 'ago' => 'maŋit :time', + 'from_now' => ':time geažes', + 'diff_yesterday' => 'ikte', + 'diff_yesterday_regexp' => 'ikte(?:\\s+ti)?', + 'diff_today' => 'otne', + 'diff_today_regexp' => 'otne(?:\\s+ti)?', + 'diff_tomorrow' => 'ihttin', + 'diff_tomorrow_regexp' => 'ihttin(?:\\s+ti)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'MMMM D. [b.] YYYY', + 'LLL' => 'MMMM D. [b.] YYYY [ti.] HH:mm', + 'LLLL' => 'dddd, MMMM D. [b.] YYYY [ti.] HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[otne ti] LT', + 'nextDay' => '[ihttin ti] LT', + 'nextWeek' => 'dddd [ti] LT', + 'lastDay' => '[ikte ti] LT', + 'lastWeek' => '[ovddit] dddd [ti] LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['ođđajagemánnu', 'guovvamánnu', 'njukčamánnu', 'cuoŋománnu', 'miessemánnu', 'geassemánnu', 'suoidnemánnu', 'borgemánnu', 'čakčamánnu', 'golggotmánnu', 'skábmamánnu', 'juovlamánnu'], + 'months_short' => ['ođđj', 'guov', 'njuk', 'cuo', 'mies', 'geas', 'suoi', 'borg', 'čakč', 'golg', 'skáb', 'juov'], + 'weekdays' => ['sotnabeaivi', 'vuossárga', 'maŋŋebárga', 'gaskavahkku', 'duorastat', 'bearjadat', 'lávvardat'], + 'weekdays_short' => ['sotn', 'vuos', 'maŋ', 'gask', 'duor', 'bear', 'láv'], + 'weekdays_min' => ['s', 'v', 'm', 'g', 'd', 'b', 'L'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' ja '], + 'meridiem' => ['i.b.', 'e.b.'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/se_FI.php b/vendor/nesbot/carbon/src/Carbon/Lang/se_FI.php new file mode 100644 index 0000000..cf01805 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/se_FI.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/se.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'months' => ['ođđajagemánnu', 'guovvamánnu', 'njukčamánnu', 'cuoŋománnu', 'miessemánnu', 'geassemánnu', 'suoidnemánnu', 'borgemánnu', 'čakčamánnu', 'golggotmánnu', 'skábmamánnu', 'juovlamánnu'], + 'months_short' => ['ođđj', 'guov', 'njuk', 'cuoŋ', 'mies', 'geas', 'suoi', 'borg', 'čakč', 'golg', 'skáb', 'juov'], + 'weekdays' => ['sotnabeaivi', 'mánnodat', 'disdat', 'gaskavahkku', 'duorastat', 'bearjadat', 'lávvordat'], + 'weekdays_short' => ['so', 'má', 'di', 'ga', 'du', 'be', 'lá'], + 'weekdays_min' => ['so', 'má', 'di', 'ga', 'du', 'be', 'lá'], + 'meridiem' => ['i', 'e'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/se_NO.php b/vendor/nesbot/carbon/src/Carbon/Lang/se_NO.php new file mode 100644 index 0000000..177c7e9 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/se_NO.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/se.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/se_SE.php b/vendor/nesbot/carbon/src/Carbon/Lang/se_SE.php new file mode 100644 index 0000000..177c7e9 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/se_SE.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/se.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/seh.php b/vendor/nesbot/carbon/src/Carbon/Lang/seh.php new file mode 100644 index 0000000..babf9af --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/seh.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'weekdays' => ['Dimingu', 'Chiposi', 'Chipiri', 'Chitatu', 'Chinai', 'Chishanu', 'Sabudu'], + 'weekdays_short' => ['Dim', 'Pos', 'Pir', 'Tat', 'Nai', 'Sha', 'Sab'], + 'weekdays_min' => ['Dim', 'Pos', 'Pir', 'Tat', 'Nai', 'Sha', 'Sab'], + 'months' => ['Janeiro', 'Fevreiro', 'Marco', 'Abril', 'Maio', 'Junho', 'Julho', 'Augusto', 'Setembro', 'Otubro', 'Novembro', 'Decembro'], + 'months_short' => ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Aug', 'Set', 'Otu', 'Nov', 'Dec'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'd [de] MMM [de] YYYY', + 'LLL' => 'd [de] MMMM [de] YYYY HH:mm', + 'LLLL' => 'dddd, d [de] MMMM [de] YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ses.php b/vendor/nesbot/carbon/src/Carbon/Lang/ses.php new file mode 100644 index 0000000..e1099e6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ses.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Adduha', 'Aluula'], + 'weekdays' => ['Alhadi', 'Atinni', 'Atalaata', 'Alarba', 'Alhamiisa', 'Alzuma', 'Asibti'], + 'weekdays_short' => ['Alh', 'Ati', 'Ata', 'Ala', 'Alm', 'Alz', 'Asi'], + 'weekdays_min' => ['Alh', 'Ati', 'Ata', 'Ala', 'Alm', 'Alz', 'Asi'], + 'months' => ['Žanwiye', 'Feewiriye', 'Marsi', 'Awiril', 'Me', 'Žuweŋ', 'Žuyye', 'Ut', 'Sektanbur', 'Oktoobur', 'Noowanbur', 'Deesanbur'], + 'months_short' => ['Žan', 'Fee', 'Mar', 'Awi', 'Me', 'Žuw', 'Žuy', 'Ut', 'Sek', 'Okt', 'Noo', 'Dee'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + + 'month' => ':count alaada', // less reliable + 'm' => ':count alaada', // less reliable + 'a_month' => ':count alaada', // less reliable + + 'hour' => ':count ɲaajin', // less reliable + 'h' => ':count ɲaajin', // less reliable + 'a_hour' => ':count ɲaajin', // less reliable + + 'minute' => ':count zarbu', // less reliable + 'min' => ':count zarbu', // less reliable + 'a_minute' => ':count zarbu', // less reliable + + 'year' => ':count jiiri', + 'y' => ':count jiiri', + 'a_year' => ':count jiiri', + + 'week' => ':count jirbiiyye', + 'w' => ':count jirbiiyye', + 'a_week' => ':count jirbiiyye', + + 'day' => ':count zaari', + 'd' => ':count zaari', + 'a_day' => ':count zaari', + + 'second' => ':count ihinkante', + 's' => ':count ihinkante', + 'a_second' => ':count ihinkante', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sg.php b/vendor/nesbot/carbon/src/Carbon/Lang/sg.php new file mode 100644 index 0000000..9264e89 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sg.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['ND', 'LK'], + 'weekdays' => ['Bikua-ôko', 'Bïkua-ûse', 'Bïkua-ptâ', 'Bïkua-usïö', 'Bïkua-okü', 'Lâpôsö', 'Lâyenga'], + 'weekdays_short' => ['Bk1', 'Bk2', 'Bk3', 'Bk4', 'Bk5', 'Lâp', 'Lây'], + 'weekdays_min' => ['Bk1', 'Bk2', 'Bk3', 'Bk4', 'Bk5', 'Lâp', 'Lây'], + 'months' => ['Nyenye', 'Fulundïgi', 'Mbängü', 'Ngubùe', 'Bêläwü', 'Föndo', 'Lengua', 'Kükürü', 'Mvuka', 'Ngberere', 'Nabändüru', 'Kakauka'], + 'months_short' => ['Nye', 'Ful', 'Mbä', 'Ngu', 'Bêl', 'Fön', 'Len', 'Kük', 'Mvu', 'Ngb', 'Nab', 'Kak'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + + 'year' => ':count dā', // less reliable + 'y' => ':count dā', // less reliable + 'a_year' => ':count dā', // less reliable + + 'week' => ':count bïkua-okü', // less reliable + 'w' => ':count bïkua-okü', // less reliable + 'a_week' => ':count bïkua-okü', // less reliable + + 'day' => ':count ziggawâ', // less reliable + 'd' => ':count ziggawâ', // less reliable + 'a_day' => ':count ziggawâ', // less reliable + + 'hour' => ':count yângâködörö', // less reliable + 'h' => ':count yângâködörö', // less reliable + 'a_hour' => ':count yângâködörö', // less reliable + + 'second' => ':count bïkua-ôko', // less reliable + 's' => ':count bïkua-ôko', // less reliable + 'a_second' => ':count bïkua-ôko', // less reliable + + 'month' => ':count Nze tî ngu', + 'm' => ':count Nze tî ngu', + 'a_month' => ':count Nze tî ngu', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sgs.php b/vendor/nesbot/carbon/src/Carbon/Lang/sgs.php new file mode 100644 index 0000000..864b989 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sgs.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/sgs_LT.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sgs_LT.php b/vendor/nesbot/carbon/src/Carbon/Lang/sgs_LT.php new file mode 100644 index 0000000..aa9e942 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sgs_LT.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Arnas Udovičius bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYY.MM.DD', + ], + 'months' => ['sausė', 'vasarė', 'kuova', 'balondė', 'gegožės', 'bėrželė', 'lëpas', 'rogpjūtė', 'siejės', 'spalė', 'lapkrėstė', 'grůdė'], + 'months_short' => ['Sau', 'Vas', 'Kuo', 'Bal', 'Geg', 'Bėr', 'Lëp', 'Rgp', 'Sie', 'Spa', 'Lap', 'Grd'], + 'weekdays' => ['nedielės dëna', 'panedielis', 'oterninks', 'sereda', 'četvergs', 'petnīčė', 'sobata'], + 'weekdays_short' => ['Nd', 'Pn', 'Ot', 'Sr', 'Čt', 'Pt', 'Sb'], + 'weekdays_min' => ['Nd', 'Pn', 'Ot', 'Sr', 'Čt', 'Pt', 'Sb'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'minute' => ':count mažos', // less reliable + 'min' => ':count mažos', // less reliable + 'a_minute' => ':count mažos', // less reliable + + 'year' => ':count metā', + 'y' => ':count metā', + 'a_year' => ':count metā', + + 'month' => ':count mienou', + 'm' => ':count mienou', + 'a_month' => ':count mienou', + + 'week' => ':count nedielė', + 'w' => ':count nedielė', + 'a_week' => ':count nedielė', + + 'day' => ':count dīna', + 'd' => ':count dīna', + 'a_day' => ':count dīna', + + 'hour' => ':count adīna', + 'h' => ':count adīna', + 'a_hour' => ':count adīna', + + 'second' => ':count Sekondė', + 's' => ':count Sekondė', + 'a_second' => ':count Sekondė', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sh.php b/vendor/nesbot/carbon/src/Carbon/Lang/sh.php new file mode 100644 index 0000000..e4aa5a1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sh.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +// @codeCoverageIgnoreStart +use Symfony\Component\Translation\PluralizationRules; + +if (class_exists('Symfony\\Component\\Translation\\PluralizationRules')) { + PluralizationRules::set(function ($number) { + return (($number % 10 == 1) && ($number % 100 != 11)) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2); + }, 'sh'); +} +// @codeCoverageIgnoreEnd + +/* + * Authors: + * - Томица Кораћ + * - Enrique Vidal + * - Christopher Dell + * - dmilisic + * - danijel + * - Miroslav Matkovic (mikki021) + */ +return [ + 'diff_now' => 'sada', + 'diff_yesterday' => 'juče', + 'diff_tomorrow' => 'sutra', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'MMMM D, YYYY', + 'LLL' => 'DD MMM HH:mm', + 'LLLL' => 'MMMM DD, YYYY HH:mm', + ], + 'year' => ':count godina|:count godine|:count godina', + 'y' => ':count g.', + 'month' => ':count mesec|:count meseca|:count meseci', + 'm' => ':count m.', + 'week' => ':count nedelja|:count nedelje|:count nedelja', + 'w' => ':count n.', + 'day' => ':count dan|:count dana|:count dana', + 'd' => ':count d.', + 'hour' => ':count sat|:count sata|:count sati', + 'h' => ':count č.', + 'minute' => ':count minut|:count minuta|:count minuta', + 'min' => ':count min.', + 'second' => ':count sekund|:count sekunde|:count sekundi', + 's' => ':count s.', + 'ago' => 'pre :time', + 'from_now' => 'za :time', + 'after' => 'nakon :time', + 'before' => ':time raniјe', + 'weekdays' => ['Nedelja', 'Ponedeljak', 'Utorak', 'Sreda', 'Četvrtak', 'Petak', 'Subota'], + 'weekdays_short' => ['Ned', 'Pon', 'Uto', 'Sre', 'Čet', 'Pet', 'Sub'], + 'weekdays_min' => ['Ned', 'Pon', 'Uto', 'Sre', 'Čet', 'Pet', 'Sub'], + 'months' => ['Januar', 'Februar', 'Mart', 'April', 'Maj', 'Jun', 'Jul', 'Avgust', 'Septembar', 'Oktobar', 'Novembar', 'Decembar'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apr', 'Maj', 'Jun', 'Jul', 'Avg', 'Sep', 'Okt', 'Nov', 'Dec'], + 'list' => [', ', ' i '], + 'meridiem' => ['pre podne', 'po podne'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/shi.php b/vendor/nesbot/carbon/src/Carbon/Lang/shi.php new file mode 100644 index 0000000..7815186 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/shi.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['ⵜⵉⴼⴰⵡⵜ', 'ⵜⴰⴷⴳⴳⵯⴰⵜ'], + 'weekdays' => ['ⴰⵙⴰⵎⴰⵙ', 'ⴰⵢⵏⴰⵙ', 'ⴰⵙⵉⵏⴰⵙ', 'ⴰⴽⵕⴰⵙ', 'ⴰⴽⵡⴰⵙ', 'ⵙⵉⵎⵡⴰⵙ', 'ⴰⵙⵉⴹⵢⴰⵙ'], + 'weekdays_short' => ['ⴰⵙⴰ', 'ⴰⵢⵏ', 'ⴰⵙⵉ', 'ⴰⴽⵕ', 'ⴰⴽⵡ', 'ⴰⵙⵉⵎ', 'ⴰⵙⵉⴹ'], + 'weekdays_min' => ['ⴰⵙⴰ', 'ⴰⵢⵏ', 'ⴰⵙⵉ', 'ⴰⴽⵕ', 'ⴰⴽⵡ', 'ⴰⵙⵉⵎ', 'ⴰⵙⵉⴹ'], + 'months' => ['ⵉⵏⵏⴰⵢⵔ', 'ⴱⵕⴰⵢⵕ', 'ⵎⴰⵕⵚ', 'ⵉⴱⵔⵉⵔ', 'ⵎⴰⵢⵢⵓ', 'ⵢⵓⵏⵢⵓ', 'ⵢⵓⵍⵢⵓⵣ', 'ⵖⵓⵛⵜ', 'ⵛⵓⵜⴰⵏⴱⵉⵔ', 'ⴽⵜⵓⴱⵔ', 'ⵏⵓⵡⴰⵏⴱⵉⵔ', 'ⴷⵓⵊⴰⵏⴱⵉⵔ'], + 'months_short' => ['ⵉⵏⵏ', 'ⴱⵕⴰ', 'ⵎⴰⵕ', 'ⵉⴱⵔ', 'ⵎⴰⵢ', 'ⵢⵓⵏ', 'ⵢⵓⵍ', 'ⵖⵓⵛ', 'ⵛⵓⵜ', 'ⴽⵜⵓ', 'ⵏⵓⵡ', 'ⴷⵓⵊ'], + 'first_day_of_week' => 6, + 'weekend' => [5, 6], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + + 'year' => ':count aseggwas', + 'y' => ':count aseggwas', + 'a_year' => ':count aseggwas', + + 'month' => ':count ayyur', + 'm' => ':count ayyur', + 'a_month' => ':count ayyur', + + 'week' => ':count imalass', + 'w' => ':count imalass', + 'a_week' => ':count imalass', + + 'day' => ':count ass', + 'd' => ':count ass', + 'a_day' => ':count ass', + + 'hour' => ':count urɣ', // less reliable + 'h' => ':count urɣ', // less reliable + 'a_hour' => ':count urɣ', // less reliable + + 'minute' => ':count ⴰⵎⵥⵉ', // less reliable + 'min' => ':count ⴰⵎⵥⵉ', // less reliable + 'a_minute' => ':count ⴰⵎⵥⵉ', // less reliable + + 'second' => ':count sin', // less reliable + 's' => ':count sin', // less reliable + 'a_second' => ':count sin', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/shi_Latn.php b/vendor/nesbot/carbon/src/Carbon/Lang/shi_Latn.php new file mode 100644 index 0000000..cddfb24 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/shi_Latn.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/shi.php', [ + 'meridiem' => ['tifawt', 'tadggʷat'], + 'weekdays' => ['asamas', 'aynas', 'asinas', 'akṛas', 'akwas', 'asimwas', 'asiḍyas'], + 'weekdays_short' => ['asa', 'ayn', 'asi', 'akṛ', 'akw', 'asim', 'asiḍ'], + 'weekdays_min' => ['asa', 'ayn', 'asi', 'akṛ', 'akw', 'asim', 'asiḍ'], + 'months' => ['innayr', 'bṛayṛ', 'maṛṣ', 'ibrir', 'mayyu', 'yunyu', 'yulyuz', 'ɣuct', 'cutanbir', 'ktubr', 'nuwanbir', 'dujanbir'], + 'months_short' => ['inn', 'bṛa', 'maṛ', 'ibr', 'may', 'yun', 'yul', 'ɣuc', 'cut', 'ktu', 'nuw', 'duj'], + 'first_day_of_week' => 6, + 'weekend' => [5, 6], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + + 'minute' => ':count agur', // less reliable + 'min' => ':count agur', // less reliable + 'a_minute' => ':count agur', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/shi_Tfng.php b/vendor/nesbot/carbon/src/Carbon/Lang/shi_Tfng.php new file mode 100644 index 0000000..f3df1f2 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/shi_Tfng.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/shi.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/shn.php b/vendor/nesbot/carbon/src/Carbon/Lang/shn.php new file mode 100644 index 0000000..fe7b1ea --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/shn.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/shn_MM.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/shn_MM.php b/vendor/nesbot/carbon/src/Carbon/Lang/shn_MM.php new file mode 100644 index 0000000..f399acf --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/shn_MM.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - ubuntu Myanmar LoCo Team https://ubuntu-mm.net Bone Pyae Sone bone.burma@mail.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'OY MMM OD dddd', + ], + 'months' => ['လိူၼ်ၵမ်', 'လိူၼ်သၢမ်', 'လိူၼ်သီ', 'လိူၼ်ႁႃႈ', 'လိူၼ်ႁူၵ်း', 'လိူၼ်ၸဵတ်း', 'လိူၼ်ပႅတ်ႇ', 'လိူၼ်ၵဝ်ႈ', 'လိူၼ်သိပ်း', 'လိူၼ်သိပ်းဢိတ်း', 'လိူၼ်သိပ်းဢိတ်းသွင်', 'လိူၼ်ၸဵင်'], + 'months_short' => ['လိူၼ်ၵမ်', 'လိူၼ်သၢမ်', 'လိူၼ်သီ', 'လိူၼ်ႁႃႈ', 'လိူၼ်ႁူၵ်း', 'လိူၼ်ၸဵတ်း', 'လိူၼ်ပႅတ်ႇ', 'လိူၼ်ၵဝ်ႈ', 'လိူၼ်သိပ်း', 'လိူၼ်သိပ်းဢိတ်း', 'လိူၼ်သိပ်းဢိတ်းသွင်', 'လိူၼ်ၸဵင်'], + 'weekdays' => ['ဝၼ်းဢႃးတိတ်ႉ', 'ဝၼ်းၸၼ်', 'ဝၼ်း​ဢၢင်း​ၵၢၼ်း', 'ဝၼ်းပူတ်ႉ', 'ဝၼ်းၽတ်း', 'ဝၼ်းသုၵ်း', 'ဝၼ်းသဝ်'], + 'weekdays_short' => ['တိတ့်', 'ၸၼ်', 'ၵၢၼ်း', 'ပုတ့်', 'ၽတ်း', 'သုၵ်း', 'သဝ်'], + 'weekdays_min' => ['တိတ့်', 'ၸၼ်', 'ၵၢၼ်း', 'ပုတ့်', 'ၽတ်း', 'သုၵ်း', 'သဝ်'], + 'alt_numbers' => ['႐႐', '႐႑', '႐႒', '႐႓', '႐႔', '႐႕', '႐႖', '႐႗', '႐႘', '႐႙', '႑႐', '႑႑', '႑႒', '႑႓', '႑႔', '႑႕', '႑႖', '႑႗', '႑႘', '႑႙', '႒႐', '႒႑', '႒႒', '႒႓', '႒႔', '႒႕', '႒႖', '႒႗', '႒႘', '႒႙', '႓႐', '႓႑', '႓႒', '႓႓', '႓႔', '႓႕', '႓႖', '႓႗', '႓႘', '႓႙', '႔႐', '႔႑', '႔႒', '႔႓', '႔႔', '႔႕', '႔႖', '႔႗', '႔႘', '႔႙', '႕႐', '႕႑', '႕႒', '႕႓', '႕႔', '႕႕', '႕႖', '႕႗', '႕႘', '႕႙', '႖႐', '႖႑', '႖႒', '႖႓', '႖႔', '႖႕', '႖႖', '႖႗', '႖႘', '႖႙', '႗႐', '႗႑', '႗႒', '႗႓', '႗႔', '႗႕', '႗႖', '႗႗', '႗႘', '႗႙', '႘႐', '႘႑', '႘႒', '႘႓', '႘႔', '႘႕', '႘႖', '႘႗', '႘႘', '႘႙', '႙႐', '႙႑', '႙႒', '႙႓', '႙႔', '႙႕', '႙႖', '႙႗', '႙႘', '႙႙'], + 'meridiem' => ['ၵၢင်ၼႂ်', 'တၢမ်းၶမ်ႈ'], + + 'month' => ':count လိူၼ်', // less reliable + 'm' => ':count လိူၼ်', // less reliable + 'a_month' => ':count လိူၼ်', // less reliable + + 'week' => ':count ဝၼ်း', // less reliable + 'w' => ':count ဝၼ်း', // less reliable + 'a_week' => ':count ဝၼ်း', // less reliable + + 'hour' => ':count ຕີ', // less reliable + 'h' => ':count ຕີ', // less reliable + 'a_hour' => ':count ຕີ', // less reliable + + 'minute' => ':count ເດັກ', // less reliable + 'min' => ':count ເດັກ', // less reliable + 'a_minute' => ':count ເດັກ', // less reliable + + 'second' => ':count ဢိုၼ်ႇ', // less reliable + 's' => ':count ဢိုၼ်ႇ', // less reliable + 'a_second' => ':count ဢိုၼ်ႇ', // less reliable + + 'year' => ':count ပီ', + 'y' => ':count ပီ', + 'a_year' => ':count ပီ', + + 'day' => ':count ກາງວັນ', + 'd' => ':count ກາງວັນ', + 'a_day' => ':count ກາງວັນ', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/shs.php b/vendor/nesbot/carbon/src/Carbon/Lang/shs.php new file mode 100644 index 0000000..8d2e1d7 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/shs.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/shs_CA.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/shs_CA.php b/vendor/nesbot/carbon/src/Carbon/Lang/shs_CA.php new file mode 100644 index 0000000..08d385e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/shs_CA.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Neskie Manuel bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['Pellkwet̓min', 'Pelctsipwen̓ten', 'Pellsqépts', 'Peslléwten', 'Pell7ell7é7llqten', 'Pelltspéntsk', 'Pelltqwelq̓wél̓t', 'Pellct̓éxel̓cten', 'Pesqelqlélten', 'Pesllwélsten', 'Pellc7ell7é7llcwten̓', 'Pelltetétq̓em'], + 'months_short' => ['Kwe', 'Tsi', 'Sqe', 'Éwt', 'Ell', 'Tsp', 'Tqw', 'Ct̓é', 'Qel', 'Wél', 'U7l', 'Tet'], + 'weekdays' => ['Sxetspesq̓t', 'Spetkesq̓t', 'Selesq̓t', 'Skellesq̓t', 'Smesesq̓t', 'Stselkstesq̓t', 'Stqmekstesq̓t'], + 'weekdays_short' => ['Sxe', 'Spe', 'Sel', 'Ske', 'Sme', 'Sts', 'Stq'], + 'weekdays_min' => ['Sxe', 'Spe', 'Sel', 'Ske', 'Sme', 'Sts', 'Stq'], + 'day_of_first_week_of_year' => 1, + + 'year' => ':count sqlélten', // less reliable + 'y' => ':count sqlélten', // less reliable + 'a_year' => ':count sqlélten', // less reliable + + 'month' => ':count swewll', // less reliable + 'm' => ':count swewll', // less reliable + 'a_month' => ':count swewll', // less reliable + + 'hour' => ':count seqwlút', // less reliable + 'h' => ':count seqwlút', // less reliable + 'a_hour' => ':count seqwlút', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/si.php b/vendor/nesbot/carbon/src/Carbon/Lang/si.php new file mode 100644 index 0000000..636bf69 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/si.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Serhan Apaydın + * - JD Isaacks + * - Malinda Weerasinghe (MalindaWMD) + */ +return [ + 'year' => '{1}වසර 1|වසර :count', + 'a_year' => '{1}වසරක්|වසර :count', + 'month' => '{1}මාස 1|මාස :count', + 'a_month' => '{1}මාසය|මාස :count', + 'week' => '{1}සති 1|සති :count', + 'a_week' => '{1}සතියක්|සති :count', + 'day' => '{1}දින 1|දින :count', + 'a_day' => '{1}දිනක්|දින :count', + 'hour' => '{1}පැය 1|පැය :count', + 'a_hour' => '{1}පැයක්|පැය :count', + 'minute' => '{1}මිනිත්තු 1|මිනිත්තු :count', + 'a_minute' => '{1}මිනිත්තුවක්|මිනිත්තු :count', + 'second' => '{1}තත්පර 1|තත්පර :count', + 'a_second' => '{1}තත්පර කිහිපයකට|තත්පර :count', + 'ago' => ':time කට පෙර', + 'from_now' => function ($time) { + if (preg_match('/දින \d/u', $time)) { + return $time.' න්'; + } + + return $time.' කින්'; + }, + 'before' => ':time කට පෙර', + 'after' => function ($time) { + if (preg_match('/දින \d/u', $time)) { + return $time.' න්'; + } + + return $time.' කින්'; + }, + 'diff_now' => 'දැන්', + 'diff_today' => 'අද', + 'diff_yesterday' => 'ඊයේ', + 'diff_tomorrow' => 'හෙට', + 'formats' => [ + 'LT' => 'a h:mm', + 'LTS' => 'a h:mm:ss', + 'L' => 'YYYY/MM/DD', + 'LL' => 'YYYY MMMM D', + 'LLL' => 'YYYY MMMM D, a h:mm', + 'LLLL' => 'YYYY MMMM D [වැනි] dddd, a h:mm:ss', + ], + 'calendar' => [ + 'sameDay' => '[අද] LT[ට]', + 'nextDay' => '[හෙට] LT[ට]', + 'nextWeek' => 'dddd LT[ට]', + 'lastDay' => '[ඊයේ] LT[ට]', + 'lastWeek' => '[පසුගිය] dddd LT[ට]', + 'sameElse' => 'L', + ], + 'ordinal' => ':number වැනි', + 'meridiem' => ['පෙර වරු', 'පස් වරු', 'පෙ.ව.', 'ප.ව.'], + 'months' => ['ජනවාරි', 'පෙබරවාරි', 'මාර්තු', 'අප්‍රේල්', 'මැයි', 'ජූනි', 'ජූලි', 'අගෝස්තු', 'සැප්තැම්බර්', 'ඔක්තෝබර්', 'නොවැම්බර්', 'දෙසැම්බර්'], + 'months_short' => ['ජන', 'පෙබ', 'මාර්', 'අප්', 'මැයි', 'ජූනි', 'ජූලි', 'අගෝ', 'සැප්', 'ඔක්', 'නොවැ', 'දෙසැ'], + 'weekdays' => ['ඉරිදා', 'සඳුදා', 'අඟහරුවාදා', 'බදාදා', 'බ්‍රහස්පතින්දා', 'සිකුරාදා', 'සෙනසුරාදා'], + 'weekdays_short' => ['ඉරි', 'සඳු', 'අඟ', 'බදා', 'බ්‍රහ', 'සිකු', 'සෙන'], + 'weekdays_min' => ['ඉ', 'ස', 'අ', 'බ', 'බ්‍ර', 'සි', 'සෙ'], + 'first_day_of_week' => 1, +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/si_LK.php b/vendor/nesbot/carbon/src/Carbon/Lang/si_LK.php new file mode 100644 index 0000000..81c44e0 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/si_LK.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/si.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sid.php b/vendor/nesbot/carbon/src/Carbon/Lang/sid.php new file mode 100644 index 0000000..b1c6521 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sid.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/sid_ET.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sid_ET.php b/vendor/nesbot/carbon/src/Carbon/Lang/sid_ET.php new file mode 100644 index 0000000..1296f9b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sid_ET.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + 'weekdays' => ['Sambata', 'Sanyo', 'Maakisanyo', 'Roowe', 'Hamuse', 'Arbe', 'Qidaame'], + 'weekdays_short' => ['Sam', 'San', 'Mak', 'Row', 'Ham', 'Arb', 'Qid'], + 'weekdays_min' => ['Sam', 'San', 'Mak', 'Row', 'Ham', 'Arb', 'Qid'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['soodo', 'hawwaro'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sk.php b/vendor/nesbot/carbon/src/Carbon/Lang/sk.php new file mode 100644 index 0000000..fd0f6b3 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sk.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - Martin Suja + * - Tsutomu Kuroda + * - tjku + * - Max Melentiev + * - Juanito Fatas + * - Ivan Stana + * - Akira Matsuda + * - Christopher Dell + * - James McKinney + * - Enrique Vidal + * - Simone Carletti + * - Aaron Patterson + * - Jozef Fulop + * - Nicolás Hock Isaza + * - Tom Hughes + * - Simon Hürlimann (CyT) + * - jofi + * - Jakub ADAMEC + * - Marek Adamický + */ +return [ + 'year' => 'rok|:count roky|:count rokov', + 'y' => ':count r', + 'month' => 'mesiac|:count mesiace|:count mesiacov', + 'm' => ':count m', + 'week' => 'týždeň|:count týždne|:count týždňov', + 'w' => ':count t', + 'day' => 'deň|:count dni|:count dní', + 'd' => ':count d', + 'hour' => 'hodinu|:count hodiny|:count hodín', + 'h' => ':count h', + 'minute' => 'minútu|:count minúty|:count minút', + 'min' => ':count min', + 'second' => 'sekundu|:count sekundy|:count sekúnd', + 's' => ':count s', + 'ago' => 'pred :time', + 'from_now' => 'za :time', + 'after' => 'o :time neskôr', + 'before' => ':time predtým', + 'year_ago' => 'rokom|:count rokmi|:count rokmi', + 'month_ago' => 'mesiacom|:count mesiacmi|:count mesiacmi', + 'week_ago' => 'týždňom|:count týždňami|:count týždňami', + 'day_ago' => 'dňom|:count dňami|:count dňami', + 'hour_ago' => 'hodinou|:count hodinami|:count hodinami', + 'minute_ago' => 'minútou|:count minútami|:count minútami', + 'second_ago' => 'sekundou|:count sekundami|:count sekundami', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' a '], + 'diff_now' => 'teraz', + 'diff_yesterday' => 'včera', + 'diff_tomorrow' => 'zajtra', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'DD. MMMM YYYY', + 'LLL' => 'D. M. HH:mm', + 'LLLL' => 'dddd D. MMMM YYYY HH:mm', + ], + 'weekdays' => ['nedeľa', 'pondelok', 'utorok', 'streda', 'štvrtok', 'piatok', 'sobota'], + 'weekdays_short' => ['ne', 'po', 'ut', 'st', 'št', 'pi', 'so'], + 'weekdays_min' => ['ne', 'po', 'ut', 'st', 'št', 'pi', 'so'], + 'months' => ['január', 'február', 'marec', 'apríl', 'máj', 'jún', 'júl', 'august', 'september', 'október', 'november', 'december'], + 'months_short' => ['jan', 'feb', 'mar', 'apr', 'máj', 'jún', 'júl', 'aug', 'sep', 'okt', 'nov', 'dec'], + 'meridiem' => ['dopoludnia', 'popoludní'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sk_SK.php b/vendor/nesbot/carbon/src/Carbon/Lang/sk_SK.php new file mode 100644 index 0000000..0515601 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sk_SK.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/sk.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sl.php b/vendor/nesbot/carbon/src/Carbon/Lang/sl.php new file mode 100644 index 0000000..2e19721 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sl.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - Tsutomu Kuroda + * - tjku + * - Max Melentiev + * - Juanito Fatas + * - Akira Matsuda + * - Christopher Dell + * - Enrique Vidal + * - Simone Carletti + * - Aaron Patterson + * - Nicolás Hock Isaza + * - Miha Rebernik + * - Gal Jakič (morpheus7CS) + * - Glavić + * - Anže Časar + * - Lovro Tramšek (Lovro1107) + * - burut13 + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count leto|:count leti|:count leta|:count let', + 'y' => ':count leto|:count leti|:count leta|:count let', + 'month' => ':count mesec|:count meseca|:count mesece|:count mesecev', + 'm' => ':count mes.', + 'week' => ':count teden|:count tedna|:count tedne|:count tednov', + 'w' => ':count ted.', + 'day' => ':count dan|:count dni|:count dni|:count dni', + 'd' => ':count dan|:count dni|:count dni|:count dni', + 'hour' => ':count ura|:count uri|:count ure|:count ur', + 'h' => ':count h', + 'minute' => ':count minuta|:count minuti|:count minute|:count minut', + 'min' => ':count min.', + 'second' => ':count sekunda|:count sekundi|:count sekunde|:count sekund', + 'a_second' => '{1}nekaj sekund|:count sekunda|:count sekundi|:count sekunde|:count sekund', + 's' => ':count s', + + 'year_ago' => ':count letom|:count leti|:count leti|:count leti', + 'y_ago' => ':count letom|:count leti|:count leti|:count leti', + 'month_ago' => ':count mesecem|:count meseci|:count meseci|:count meseci', + 'week_ago' => ':count tednom|:count tednoma|:count tedni|:count tedni', + 'day_ago' => ':count dnem|:count dnevoma|:count dnevi|:count dnevi', + 'd_ago' => ':count dnem|:count dnevoma|:count dnevi|:count dnevi', + 'hour_ago' => ':count uro|:count urama|:count urami|:count urami', + 'minute_ago' => ':count minuto|:count minutama|:count minutami|:count minutami', + 'second_ago' => ':count sekundo|:count sekundama|:count sekundami|:count sekundami', + + 'day_from_now' => ':count dan|:count dneva|:count dni|:count dni', + 'd_from_now' => ':count dan|:count dneva|:count dni|:count dni', + 'hour_from_now' => ':count uro|:count uri|:count ure|:count ur', + 'minute_from_now' => ':count minuto|:count minuti|:count minute|:count minut', + 'second_from_now' => ':count sekundo|:count sekundi|:count sekunde|:count sekund', + + 'ago' => 'pred :time', + 'from_now' => 'čez :time', + 'after' => ':time kasneje', + 'before' => ':time prej', + + 'diff_now' => 'ravnokar', + 'diff_today' => 'danes', + 'diff_today_regexp' => 'danes(?:\\s+ob)?', + 'diff_yesterday' => 'včeraj', + 'diff_yesterday_regexp' => 'včeraj(?:\\s+ob)?', + 'diff_tomorrow' => 'jutri', + 'diff_tomorrow_regexp' => 'jutri(?:\\s+ob)?', + 'diff_before_yesterday' => 'predvčerajšnjim', + 'diff_after_tomorrow' => 'pojutrišnjem', + + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'period_start_date' => 'od :date', + 'period_end_date' => 'do :date', + + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY H:mm', + 'LLLL' => 'dddd, D. MMMM YYYY H:mm', + ], + 'calendar' => [ + 'sameDay' => '[danes ob] LT', + 'nextDay' => '[jutri ob] LT', + 'nextWeek' => 'dddd [ob] LT', + 'lastDay' => '[včeraj ob] LT', + 'lastWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[preteklo] [nedeljo] [ob] LT'; + case 1: + return '[pretekli] [ponedeljek] [ob] LT'; + case 2: + return '[pretekli] [torek] [ob] LT'; + case 3: + return '[preteklo] [sredo] [ob] LT'; + case 4: + return '[pretekli] [četrtek] [ob] LT'; + case 5: + return '[pretekli] [petek] [ob] LT'; + case 6: + return '[preteklo] [soboto] [ob] LT'; + } + }, + 'sameElse' => 'L', + ], + 'months' => ['januar', 'februar', 'marec', 'april', 'maj', 'junij', 'julij', 'avgust', 'september', 'oktober', 'november', 'december'], + 'months_short' => ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'avg', 'sep', 'okt', 'nov', 'dec'], + 'weekdays' => ['nedelja', 'ponedeljek', 'torek', 'sreda', 'četrtek', 'petek', 'sobota'], + 'weekdays_short' => ['ned', 'pon', 'tor', 'sre', 'čet', 'pet', 'sob'], + 'weekdays_min' => ['ne', 'po', 'to', 'sr', 'če', 'pe', 'so'], + 'list' => [', ', ' in '], + 'meridiem' => ['dopoldan', 'popoldan'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sl_SI.php b/vendor/nesbot/carbon/src/Carbon/Lang/sl_SI.php new file mode 100644 index 0000000..5dad8c8 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sl_SI.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/sl.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sm.php b/vendor/nesbot/carbon/src/Carbon/Lang/sm.php new file mode 100644 index 0000000..e8c118a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sm.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/sm_WS.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sm_WS.php b/vendor/nesbot/carbon/src/Carbon/Lang/sm_WS.php new file mode 100644 index 0000000..f066068 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sm_WS.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Samsung Electronics Co., Ltd. akhilesh.k@samsung.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Ianuari', 'Fepuari', 'Mati', 'Aperila', 'Me', 'Iuni', 'Iulai', 'Auguso', 'Setema', 'Oketopa', 'Novema', 'Tesema'], + 'months_short' => ['Ian', 'Fep', 'Mat', 'Ape', 'Me', 'Iun', 'Iul', 'Aug', 'Set', 'Oke', 'Nov', 'Tes'], + 'weekdays' => ['Aso Sa', 'Aso Gafua', 'Aso Lua', 'Aso Lulu', 'Aso Tofi', 'Aso Farail', 'Aso To\'ana\'i'], + 'weekdays_short' => ['Aso Sa', 'Aso Gaf', 'Aso Lua', 'Aso Lul', 'Aso Tof', 'Aso Far', 'Aso To\''], + 'weekdays_min' => ['Aso Sa', 'Aso Gaf', 'Aso Lua', 'Aso Lul', 'Aso Tof', 'Aso Far', 'Aso To\''], + + 'hour' => ':count uati', // less reliable + 'h' => ':count uati', // less reliable + 'a_hour' => ':count uati', // less reliable + + 'minute' => ':count itiiti', // less reliable + 'min' => ':count itiiti', // less reliable + 'a_minute' => ':count itiiti', // less reliable + + 'second' => ':count lua', // less reliable + 's' => ':count lua', // less reliable + 'a_second' => ':count lua', // less reliable + + 'year' => ':count tausaga', + 'y' => ':count tausaga', + 'a_year' => ':count tausaga', + + 'month' => ':count māsina', + 'm' => ':count māsina', + 'a_month' => ':count māsina', + + 'week' => ':count vaiaso', + 'w' => ':count vaiaso', + 'a_week' => ':count vaiaso', + + 'day' => ':count aso', + 'd' => ':count aso', + 'a_day' => ':count aso', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/smn.php b/vendor/nesbot/carbon/src/Carbon/Lang/smn.php new file mode 100644 index 0000000..20add02 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/smn.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['ip.', 'ep.'], + 'weekdays' => ['pasepeeivi', 'vuossaargâ', 'majebaargâ', 'koskoho', 'tuorâstuv', 'vástuppeeivi', 'lávurduv'], + 'weekdays_short' => ['pas', 'vuo', 'maj', 'kos', 'tuo', 'vás', 'láv'], + 'weekdays_min' => ['pa', 'vu', 'ma', 'ko', 'tu', 'vá', 'lá'], + 'weekdays_standalone' => ['pasepeivi', 'vuossargâ', 'majebargâ', 'koskokko', 'tuorâstâh', 'vástuppeivi', 'lávurdâh'], + 'months' => ['uđđâivemáánu', 'kuovâmáánu', 'njuhčâmáánu', 'cuáŋuimáánu', 'vyesimáánu', 'kesimáánu', 'syeinimáánu', 'porgemáánu', 'čohčâmáánu', 'roovvâdmáánu', 'skammâmáánu', 'juovlâmáánu'], + 'months_short' => ['uđiv', 'kuovâ', 'njuhčâ', 'cuáŋui', 'vyesi', 'kesi', 'syeini', 'porge', 'čohčâ', 'roovvâd', 'skammâ', 'juovlâ'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'H.mm', + 'LTS' => 'H.mm.ss', + 'L' => 'D.M.YYYY', + 'LL' => 'MMM D. YYYY', + 'LLL' => 'MMMM D. YYYY H.mm', + 'LLLL' => 'dddd, MMMM D. YYYY H.mm', + ], + + 'hour' => ':count äigi', // less reliable + 'h' => ':count äigi', // less reliable + 'a_hour' => ':count äigi', // less reliable + + 'year' => ':count ihe', + 'y' => ':count ihe', + 'a_year' => ':count ihe', + + 'month' => ':count mánuppaje', + 'm' => ':count mánuppaje', + 'a_month' => ':count mánuppaje', + + 'week' => ':count okko', + 'w' => ':count okko', + 'a_week' => ':count okko', + + 'day' => ':count peivi', + 'd' => ':count peivi', + 'a_day' => ':count peivi', + + 'minute' => ':count miinut', + 'min' => ':count miinut', + 'a_minute' => ':count miinut', + + 'second' => ':count nubbe', + 's' => ':count nubbe', + 'a_second' => ':count nubbe', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sn.php b/vendor/nesbot/carbon/src/Carbon/Lang/sn.php new file mode 100644 index 0000000..4f25028 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sn.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['a', 'p'], + 'weekdays' => ['Svondo', 'Muvhuro', 'Chipiri', 'Chitatu', 'China', 'Chishanu', 'Mugovera'], + 'weekdays_short' => ['Svo', 'Muv', 'Chp', 'Cht', 'Chn', 'Chs', 'Mug'], + 'weekdays_min' => ['Sv', 'Mu', 'Cp', 'Ct', 'Cn', 'Cs', 'Mg'], + 'months' => ['Ndira', 'Kukadzi', 'Kurume', 'Kubvumbi', 'Chivabvu', 'Chikumi', 'Chikunguru', 'Nyamavhuvhu', 'Gunyana', 'Gumiguru', 'Mbudzi', 'Zvita'], + 'months_short' => ['Ndi', 'Kuk', 'Kur', 'Kub', 'Chv', 'Chk', 'Chg', 'Nya', 'Gun', 'Gum', 'Mbu', 'Zvi'], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-dd', + 'LL' => 'YYYY MMM D', + 'LLL' => 'YYYY MMMM D HH:mm', + 'LLLL' => 'YYYY MMMM D, dddd HH:mm', + ], + + 'year' => 'makore :count', + 'y' => 'makore :count', + 'a_year' => 'makore :count', + + 'month' => 'mwedzi :count', + 'm' => 'mwedzi :count', + 'a_month' => 'mwedzi :count', + + 'week' => 'vhiki :count', + 'w' => 'vhiki :count', + 'a_week' => 'vhiki :count', + + 'day' => 'mazuva :count', + 'd' => 'mazuva :count', + 'a_day' => 'mazuva :count', + + 'hour' => 'maawa :count', + 'h' => 'maawa :count', + 'a_hour' => 'maawa :count', + + 'minute' => 'minitsi :count', + 'min' => 'minitsi :count', + 'a_minute' => 'minitsi :count', + + 'second' => 'sekonzi :count', + 's' => 'sekonzi :count', + 'a_second' => 'sekonzi :count', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/so.php b/vendor/nesbot/carbon/src/Carbon/Lang/so.php new file mode 100644 index 0000000..5785271 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/so.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Author: + * - Abdifatah Abdilahi(@abdifatahz) + */ +return [ + 'year' => ':count sanad|:count sanadood', + 'a_year' => 'sanad|:count sanadood', + 'y' => '{1}:countsn|{0}:countsns|]1,Inf[:countsn', + 'month' => ':count bil|:count bilood', + 'a_month' => 'bil|:count bilood', + 'm' => ':countbil', + 'week' => ':count isbuuc', + 'a_week' => 'isbuuc|:count isbuuc', + 'w' => ':countis', + 'day' => ':count maalin|:count maalmood', + 'a_day' => 'maalin|:count maalmood', + 'd' => ':countml', + 'hour' => ':count saac', + 'a_hour' => 'saacad|:count saac', + 'h' => ':countsc', + 'minute' => ':count daqiiqo', + 'a_minute' => 'daqiiqo|:count daqiiqo', + 'min' => ':countdq', + 'second' => ':count ilbidhiqsi', + 'a_second' => 'xooga ilbidhiqsiyo|:count ilbidhiqsi', + 's' => ':countil', + 'ago' => ':time kahor', + 'from_now' => ':time gudahood', + 'after' => ':time kedib', + 'before' => ':time kahor', + 'diff_now' => 'hada', + 'diff_today' => 'maanta', + 'diff_today_regexp' => 'maanta(?:\s+markay\s+(?:tahay|ahayd))?', + 'diff_yesterday' => 'shalayto', + 'diff_yesterday_regexp' => 'shalayto(?:\s+markay\s+ahayd)?', + 'diff_tomorrow' => 'beri', + 'diff_tomorrow_regexp' => 'beri(?:\s+markay\s+tahay)?', + 'diff_before_yesterday' => 'doraato', + 'diff_after_tomorrow' => 'saadanbe', + 'period_recurrences' => 'mar|:count jeer', + 'period_interval' => ':interval kasta', + 'period_start_date' => 'laga bilaabo :date', + 'period_end_date' => 'ilaa :date', + 'months' => ['Janaayo', 'Febraayo', 'Abriil', 'Maajo', 'Juun', 'Luuliyo', 'Agoosto', 'Sebteembar', 'Oktoobar', 'Nofeembar', 'Diseembar'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Abr', 'Mjo', 'Jun', 'Lyo', 'Agt', 'Seb', 'Okt', 'Nof', 'Dis'], + 'weekdays' => ['Axad', 'Isniin', 'Talaada', 'Arbaca', 'Khamiis', 'Jimce', 'Sabti'], + 'weekdays_short' => ['Axd', 'Isn', 'Tal', 'Arb', 'Kha', 'Jim', 'Sbt'], + 'weekdays_min' => ['Ax', 'Is', 'Ta', 'Ar', 'Kh', 'Ji', 'Sa'], + 'list' => [', ', ' and '], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'calendar' => [ + 'sameDay' => '[Maanta markay tahay] LT', + 'nextDay' => '[Beri markay tahay] LT', + 'nextWeek' => 'dddd [markay tahay] LT', + 'lastDay' => '[Shalay markay ahayd] LT', + 'lastWeek' => '[Hore] dddd [Markay ahayd] LT', + 'sameElse' => 'L', + ], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/so_DJ.php b/vendor/nesbot/carbon/src/Carbon/Lang/so_DJ.php new file mode 100644 index 0000000..273dda8 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/so_DJ.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/so.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/so_ET.php b/vendor/nesbot/carbon/src/Carbon/Lang/so_ET.php new file mode 100644 index 0000000..7b69971 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/so_ET.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return require __DIR__.'/so.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/so_KE.php b/vendor/nesbot/carbon/src/Carbon/Lang/so_KE.php new file mode 100644 index 0000000..7b69971 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/so_KE.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return require __DIR__.'/so.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/so_SO.php b/vendor/nesbot/carbon/src/Carbon/Lang/so_SO.php new file mode 100644 index 0000000..7b69971 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/so_SO.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return require __DIR__.'/so.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sq.php b/vendor/nesbot/carbon/src/Carbon/Lang/sq.php new file mode 100644 index 0000000..ffa592e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sq.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - JD Isaacks + * - Fadion Dashi + */ +return [ + 'year' => ':count vit|:count vjet', + 'a_year' => 'një vit|:count vite', + 'y' => ':count v.', + 'month' => ':count muaj', + 'a_month' => 'një muaj|:count muaj', + 'm' => ':count muaj', + 'week' => ':count javë', + 'a_week' => ':count javë|:count javë', + 'w' => ':count j.', + 'day' => ':count ditë', + 'a_day' => 'një ditë|:count ditë', + 'd' => ':count d.', + 'hour' => ':count orë', + 'a_hour' => 'një orë|:count orë', + 'h' => ':count o.', + 'minute' => ':count minutë|:count minuta', + 'a_minute' => 'një minutë|:count minuta', + 'min' => ':count min.', + 'second' => ':count sekondë|:count sekonda', + 'a_second' => 'disa sekonda|:count sekonda', + 's' => ':count s.', + 'ago' => ':time më parë', + 'from_now' => 'në :time', + 'after' => ':time pas', + 'before' => ':time para', + 'diff_now' => 'tani', + 'diff_today' => 'Sot', + 'diff_today_regexp' => 'Sot(?:\\s+në)?', + 'diff_yesterday' => 'dje', + 'diff_yesterday_regexp' => 'Dje(?:\\s+në)?', + 'diff_tomorrow' => 'nesër', + 'diff_tomorrow_regexp' => 'Nesër(?:\\s+në)?', + 'diff_before_yesterday' => 'pardje', + 'diff_after_tomorrow' => 'pasnesër', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Sot në] LT', + 'nextDay' => '[Nesër në] LT', + 'nextWeek' => 'dddd [në] LT', + 'lastDay' => '[Dje në] LT', + 'lastWeek' => 'dddd [e kaluar në] LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'meridiem' => ['PD', 'MD'], + 'months' => ['janar', 'shkurt', 'mars', 'prill', 'maj', 'qershor', 'korrik', 'gusht', 'shtator', 'tetor', 'nëntor', 'dhjetor'], + 'months_short' => ['jan', 'shk', 'mar', 'pri', 'maj', 'qer', 'kor', 'gus', 'sht', 'tet', 'nën', 'dhj'], + 'weekdays' => ['e diel', 'e hënë', 'e martë', 'e mërkurë', 'e enjte', 'e premte', 'e shtunë'], + 'weekdays_short' => ['die', 'hën', 'mar', 'mër', 'enj', 'pre', 'sht'], + 'weekdays_min' => ['d', 'h', 'ma', 'më', 'e', 'p', 'sh'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' dhe '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sq_AL.php b/vendor/nesbot/carbon/src/Carbon/Lang/sq_AL.php new file mode 100644 index 0000000..ea5df3f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sq_AL.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/sq.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sq_MK.php b/vendor/nesbot/carbon/src/Carbon/Lang/sq_MK.php new file mode 100644 index 0000000..62f752c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sq_MK.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/sq.php', [ + 'formats' => [ + 'L' => 'D.M.YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY, HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY, HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sq_XK.php b/vendor/nesbot/carbon/src/Carbon/Lang/sq_XK.php new file mode 100644 index 0000000..62f752c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sq_XK.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/sq.php', [ + 'formats' => [ + 'L' => 'D.M.YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY, HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY, HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sr.php b/vendor/nesbot/carbon/src/Carbon/Lang/sr.php new file mode 100644 index 0000000..68ba663 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sr.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - François B + * - shaishavgandhi05 + * - Serhan Apaydın + * - JD Isaacks + * - Glavić + * - Milos Sakovic + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => ':count godina|:count godine|:count godina', + 'y' => ':count g.', + 'month' => ':count mesec|:count meseca|:count meseci', + 'm' => ':count mj.', + 'week' => ':count nedelja|:count nedelje|:count nedelja', + 'w' => ':count ned.', + 'day' => ':count dan|:count dana|:count dana', + 'd' => ':count d.', + 'hour' => ':count sat|:count sata|:count sati', + 'h' => ':count č.', + 'minute' => ':count minut|:count minuta|:count minuta', + 'min' => ':count min.', + 'second' => ':count sekundu|:count sekunde|:count sekundi', + 's' => ':count sek.', + 'ago' => 'pre :time', + 'from_now' => 'za :time', + 'after' => 'nakon :time', + 'before' => 'pre :time', + + 'year_from_now' => ':count godinu|:count godine|:count godina', + 'year_ago' => ':count godinu|:count godine|:count godina', + 'week_from_now' => ':count nedelju|:count nedelje|:count nedelja', + 'week_ago' => ':count nedelju|:count nedelje|:count nedelja', + + 'diff_now' => 'upravo sada', + 'diff_today' => 'danas', + 'diff_today_regexp' => 'danas(?:\\s+u)?', + 'diff_yesterday' => 'juče', + 'diff_yesterday_regexp' => 'juče(?:\\s+u)?', + 'diff_tomorrow' => 'sutra', + 'diff_tomorrow_regexp' => 'sutra(?:\\s+u)?', + 'diff_before_yesterday' => 'prekjuče', + 'diff_after_tomorrow' => 'preksutra', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY H:mm', + 'LLLL' => 'dddd, D. MMMM YYYY H:mm', + ], + 'calendar' => [ + 'sameDay' => '[danas u] LT', + 'nextDay' => '[sutra u] LT', + 'nextWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[u nedelju u] LT'; + case 3: + return '[u sredu u] LT'; + case 6: + return '[u subotu u] LT'; + default: + return '[u] dddd [u] LT'; + } + }, + 'lastDay' => '[juče u] LT', + 'lastWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[prošle nedelje u] LT'; + case 1: + return '[prošlog ponedeljka u] LT'; + case 2: + return '[prošlog utorka u] LT'; + case 3: + return '[prošle srede u] LT'; + case 4: + return '[prošlog četvrtka u] LT'; + case 5: + return '[prošlog petka u] LT'; + default: + return '[prošle subote u] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['januar', 'februar', 'mart', 'april', 'maj', 'jun', 'jul', 'avgust', 'septembar', 'oktobar', 'novembar', 'decembar'], + 'months_short' => ['jan.', 'feb.', 'mar.', 'apr.', 'maj', 'jun', 'jul', 'avg.', 'sep.', 'okt.', 'nov.', 'dec.'], + 'weekdays' => ['nedelja', 'ponedeljak', 'utorak', 'sreda', 'četvrtak', 'petak', 'subota'], + 'weekdays_short' => ['ned.', 'pon.', 'uto.', 'sre.', 'čet.', 'pet.', 'sub.'], + 'weekdays_min' => ['ne', 'po', 'ut', 'sr', 'če', 'pe', 'su'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' i '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl.php b/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl.php new file mode 100644 index 0000000..c09df19 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - François B + * - shaishavgandhi05 + * - Serhan Apaydın + * - JD Isaacks + * - Glavić + * - Nikola Zeravcic + * - Milos Sakovic + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => '{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54}:count године|[0,Inf[:count година', + 'y' => ':count г.', + 'month' => '{1}:count месец|{2,3,4}:count месеца|[0,Inf[:count месеци', + 'm' => ':count м.', + 'week' => '{1}:count недеља|{2,3,4}:count недеље|[0,Inf[:count недеља', + 'w' => ':count нед.', + 'day' => '{1,21,31}:count дан|[0,Inf[:count дана', + 'd' => ':count д.', + 'hour' => '{1,21}:count сат|{2,3,4,22,23,24}:count сата|[0,Inf[:count сати', + 'h' => ':count ч.', + 'minute' => '{1,21,31,41,51}:count минут|[0,Inf[:count минута', + 'min' => ':count мин.', + 'second' => '{1,21,31,41,51}:count секунд|{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54}:count секунде|[0,Inf[:count секунди', + 's' => ':count сек.', + 'ago' => 'пре :time', + 'from_now' => 'за :time', + 'after' => ':time након', + 'before' => ':time пре', + 'year_from_now' => '{1,21,31,41,51}:count годину|{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54}:count године|[0,Inf[:count година', + 'year_ago' => '{1,21,31,41,51}:count годину|{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54}:count године|[0,Inf[:count година', + 'week_from_now' => '{1}:count недељу|{2,3,4}:count недеље|[0,Inf[:count недеља', + 'week_ago' => '{1}:count недељу|{2,3,4}:count недеље|[0,Inf[:count недеља', + 'diff_now' => 'управо сада', + 'diff_today' => 'данас', + 'diff_today_regexp' => 'данас(?:\\s+у)?', + 'diff_yesterday' => 'јуче', + 'diff_yesterday_regexp' => 'јуче(?:\\s+у)?', + 'diff_tomorrow' => 'сутра', + 'diff_tomorrow_regexp' => 'сутра(?:\\s+у)?', + 'diff_before_yesterday' => 'прекјуче', + 'diff_after_tomorrow' => 'прекосутра', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY H:mm', + 'LLLL' => 'dddd, D. MMMM YYYY H:mm', + ], + 'calendar' => [ + 'sameDay' => '[данас у] LT', + 'nextDay' => '[сутра у] LT', + 'nextWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[у недељу у] LT'; + case 3: + return '[у среду у] LT'; + case 6: + return '[у суботу у] LT'; + default: + return '[у] dddd [у] LT'; + } + }, + 'lastDay' => '[јуче у] LT', + 'lastWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[прошле недеље у] LT'; + case 1: + return '[прошлог понедељка у] LT'; + case 2: + return '[прошлог уторка у] LT'; + case 3: + return '[прошле среде у] LT'; + case 4: + return '[прошлог четвртка у] LT'; + case 5: + return '[прошлог петка у] LT'; + default: + return '[прошле суботе у] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['јануар', 'фебруар', 'март', 'април', 'мај', 'јун', 'јул', 'август', 'септембар', 'октобар', 'новембар', 'децембар'], + 'months_short' => ['јан.', 'феб.', 'мар.', 'апр.', 'мај', 'јун', 'јул', 'авг.', 'сеп.', 'окт.', 'нов.', 'дец.'], + 'weekdays' => ['недеља', 'понедељак', 'уторак', 'среда', 'четвртак', 'петак', 'субота'], + 'weekdays_short' => ['нед.', 'пон.', 'уто.', 'сре.', 'чет.', 'пет.', 'суб.'], + 'weekdays_min' => ['не', 'по', 'ут', 'ср', 'че', 'пе', 'су'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' и '], + 'meridiem' => ['АМ', 'ПМ'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl_BA.php b/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl_BA.php new file mode 100644 index 0000000..0fb63d7 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl_BA.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/sr_Cyrl.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D.M.yy.', + 'LL' => 'DD.MM.YYYY.', + 'LLL' => 'DD. MMMM YYYY. HH:mm', + 'LLLL' => 'dddd, DD. MMMM YYYY. HH:mm', + ], + 'weekdays' => ['недјеља', 'понедељак', 'уторак', 'сриједа', 'четвртак', 'петак', 'субота'], + 'weekdays_short' => ['нед.', 'пон.', 'ут.', 'ср.', 'чет.', 'пет.', 'суб.'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl_ME.php b/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl_ME.php new file mode 100644 index 0000000..d13229a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl_ME.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Glavić + * - Milos Sakovic + */ + +use Carbon\CarbonInterface; + +return [ + 'year' => '{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54}:count године|[0,Inf[:count година', + 'y' => ':count г.', + 'month' => '{1}:count мјесец|{2,3,4}:count мјесеца|[0,Inf[:count мјесеци', + 'm' => ':count мј.', + 'week' => '{1}:count недјеља|{2,3,4}:count недјеље|[0,Inf[:count недјеља', + 'w' => ':count нед.', + 'day' => '{1,21,31}:count дан|[0,Inf[:count дана', + 'd' => ':count д.', + 'hour' => '{1,21}:count сат|{2,3,4,22,23,24}:count сата|[0,Inf[:count сати', + 'h' => ':count ч.', + 'minute' => '{1,21,31,41,51}:count минут|[0,Inf[:count минута', + 'min' => ':count мин.', + 'second' => '{1,21,31,41,51}:count секунд|{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54}:count секунде|[0,Inf[:count секунди', + 's' => ':count сек.', + 'ago' => 'прије :time', + 'from_now' => 'за :time', + 'after' => ':time након', + 'before' => ':time прије', + + 'year_from_now' => '{1,21,31,41,51}:count годину|{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54}:count године|[0,Inf[:count година', + 'year_ago' => '{1,21,31,41,51}:count годину|{2,3,4,22,23,24,32,33,34,42,43,44,52,53,54}:count године|[0,Inf[:count година', + + 'week_from_now' => '{1}:count недјељу|{2,3,4}:count недјеље|[0,Inf[:count недјеља', + 'week_ago' => '{1}:count недјељу|{2,3,4}:count недјеље|[0,Inf[:count недјеља', + + 'diff_now' => 'управо сада', + 'diff_today' => 'данас', + 'diff_today_regexp' => 'данас(?:\\s+у)?', + 'diff_yesterday' => 'јуче', + 'diff_yesterday_regexp' => 'јуче(?:\\s+у)?', + 'diff_tomorrow' => 'сутра', + 'diff_tomorrow_regexp' => 'сутра(?:\\s+у)?', + 'diff_before_yesterday' => 'прекјуче', + 'diff_after_tomorrow' => 'прекосјутра', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM YYYY', + 'LLL' => 'D. MMMM YYYY H:mm', + 'LLLL' => 'dddd, D. MMMM YYYY H:mm', + ], + 'calendar' => [ + 'sameDay' => '[данас у] LT', + 'nextDay' => '[сутра у] LT', + 'nextWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[у недељу у] LT'; + case 3: + return '[у среду у] LT'; + case 6: + return '[у суботу у] LT'; + default: + return '[у] dddd [у] LT'; + } + }, + 'lastDay' => '[јуче у] LT', + 'lastWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[прошле недеље у] LT'; + case 1: + return '[прошлог понедељка у] LT'; + case 2: + return '[прошлог уторка у] LT'; + case 3: + return '[прошле среде у] LT'; + case 4: + return '[прошлог четвртка у] LT'; + case 5: + return '[прошлог петка у] LT'; + default: + return '[прошле суботе у] LT'; + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['јануар', 'фебруар', 'март', 'април', 'мај', 'јун', 'јул', 'август', 'септембар', 'октобар', 'новембар', 'децембар'], + 'months_short' => ['јан.', 'феб.', 'мар.', 'апр.', 'мај', 'јун', 'јул', 'авг.', 'сеп.', 'окт.', 'нов.', 'дец.'], + 'weekdays' => ['недеља', 'понедељак', 'уторак', 'среда', 'четвртак', 'петак', 'субота'], + 'weekdays_short' => ['нед.', 'пон.', 'уто.', 'сре.', 'чет.', 'пет.', 'суб.'], + 'weekdays_min' => ['не', 'по', 'ут', 'ср', 'че', 'пе', 'су'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' и '], + 'meridiem' => ['АМ', 'ПМ'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl_XK.php b/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl_XK.php new file mode 100644 index 0000000..492baf0 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl_XK.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/sr_Cyrl_BA.php', [ + 'weekdays' => ['недеља', 'понедељак', 'уторак', 'среда', 'четвртак', 'петак', 'субота'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn.php b/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn.php new file mode 100644 index 0000000..9971674 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/sr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn_BA.php b/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn_BA.php new file mode 100644 index 0000000..897c674 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn_BA.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/sr_Latn.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D.M.yy.', + 'LL' => 'DD.MM.YYYY.', + 'LLL' => 'DD. MMMM YYYY. HH:mm', + 'LLLL' => 'dddd, DD. MMMM YYYY. HH:mm', + ], + 'weekdays' => ['nedjelja', 'ponedeljak', 'utorak', 'srijeda', 'četvrtak', 'petak', 'subota'], + 'weekdays_short' => ['ned.', 'pon.', 'ut.', 'sr.', 'čet.', 'pet.', 'sub.'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn_ME.php b/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn_ME.php new file mode 100644 index 0000000..e2133ef --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn_ME.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Glavić + * - Milos Sakovic + */ + +use Carbon\CarbonInterface; + +return array_replace_recursive(require __DIR__.'/sr.php', [ + 'month' => ':count mjesec|:count mjeseca|:count mjeseci', + 'week' => ':count nedjelja|:count nedjelje|:count nedjelja', + 'second' => ':count sekund|:count sekunde|:count sekundi', + 'ago' => 'prije :time', + 'from_now' => 'za :time', + 'after' => ':time nakon', + 'before' => ':time prije', + 'week_from_now' => ':count nedjelju|:count nedjelje|:count nedjelja', + 'week_ago' => ':count nedjelju|:count nedjelje|:count nedjelja', + 'diff_tomorrow' => 'sjutra', + 'calendar' => [ + 'nextDay' => '[sjutra u] LT', + 'nextWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[u nedjelju u] LT'; + case 3: + return '[u srijedu u] LT'; + case 6: + return '[u subotu u] LT'; + default: + return '[u] dddd [u] LT'; + } + }, + 'lastWeek' => function (CarbonInterface $date) { + switch ($date->dayOfWeek) { + case 0: + return '[prošle nedjelje u] LT'; + case 1: + return '[prošle nedjelje u] LT'; + case 2: + return '[prošlog utorka u] LT'; + case 3: + return '[prošle srijede u] LT'; + case 4: + return '[prošlog četvrtka u] LT'; + case 5: + return '[prošlog petka u] LT'; + default: + return '[prošle subote u] LT'; + } + }, + ], + 'weekdays' => ['nedjelja', 'ponedjeljak', 'utorak', 'srijeda', 'četvrtak', 'petak', 'subota'], + 'weekdays_short' => ['ned.', 'pon.', 'uto.', 'sri.', 'čet.', 'pet.', 'sub.'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn_XK.php b/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn_XK.php new file mode 100644 index 0000000..d0b9d10 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn_XK.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/sr_Latn_BA.php', [ + 'weekdays' => ['nedelja', 'ponedeljak', 'utorak', 'sreda', 'četvrtak', 'petak', 'subota'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sr_ME.php b/vendor/nesbot/carbon/src/Carbon/Lang/sr_ME.php new file mode 100644 index 0000000..d7c65b9 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sr_ME.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/sr_Latn_ME.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sr_RS.php b/vendor/nesbot/carbon/src/Carbon/Lang/sr_RS.php new file mode 100644 index 0000000..bc5e04b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sr_RS.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - sr_YU, sr_CS locale Danilo Segan bug-glibc-locales@gnu.org + */ +return require __DIR__.'/sr_Cyrl.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sr_RS@latin.php b/vendor/nesbot/carbon/src/Carbon/Lang/sr_RS@latin.php new file mode 100644 index 0000000..9971674 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sr_RS@latin.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/sr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ss.php b/vendor/nesbot/carbon/src/Carbon/Lang/ss.php new file mode 100644 index 0000000..cd4b919 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ss.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Nicolai Davies + */ +return [ + 'year' => '{1}umnyaka|:count iminyaka', + 'month' => '{1}inyanga|:count tinyanga', + 'week' => '{1}:count liviki|:count emaviki', + 'day' => '{1}lilanga|:count emalanga', + 'hour' => '{1}lihora|:count emahora', + 'minute' => '{1}umzuzu|:count emizuzu', + 'second' => '{1}emizuzwana lomcane|:count mzuzwana', + 'ago' => 'wenteka nga :time', + 'from_now' => 'nga :time', + 'diff_yesterday' => 'Itolo', + 'diff_yesterday_regexp' => 'Itolo(?:\\s+nga)?', + 'diff_today' => 'Namuhla', + 'diff_today_regexp' => 'Namuhla(?:\\s+nga)?', + 'diff_tomorrow' => 'Kusasa', + 'diff_tomorrow_regexp' => 'Kusasa(?:\\s+nga)?', + 'formats' => [ + 'LT' => 'h:mm A', + 'LTS' => 'h:mm:ss A', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm A', + 'LLLL' => 'dddd, D MMMM YYYY h:mm A', + ], + 'calendar' => [ + 'sameDay' => '[Namuhla nga] LT', + 'nextDay' => '[Kusasa nga] LT', + 'nextWeek' => 'dddd [nga] LT', + 'lastDay' => '[Itolo nga] LT', + 'lastWeek' => 'dddd [leliphelile] [nga] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + $lastDigit = $number % 10; + + return $number.( + (~~($number % 100 / 10) === 1) ? 'e' : ( + ($lastDigit === 1 || $lastDigit === 2) ? 'a' : 'e' + ) + ); + }, + 'meridiem' => function ($hour) { + if ($hour < 11) { + return 'ekuseni'; + } + if ($hour < 15) { + return 'emini'; + } + if ($hour < 19) { + return 'entsambama'; + } + + return 'ebusuku'; + }, + 'months' => ['Bhimbidvwane', 'Indlovana', 'Indlov\'lenkhulu', 'Mabasa', 'Inkhwekhweti', 'Inhlaba', 'Kholwane', 'Ingci', 'Inyoni', 'Imphala', 'Lweti', 'Ingongoni'], + 'months_short' => ['Bhi', 'Ina', 'Inu', 'Mab', 'Ink', 'Inh', 'Kho', 'Igc', 'Iny', 'Imp', 'Lwe', 'Igo'], + 'weekdays' => ['Lisontfo', 'Umsombuluko', 'Lesibili', 'Lesitsatfu', 'Lesine', 'Lesihlanu', 'Umgcibelo'], + 'weekdays_short' => ['Lis', 'Umb', 'Lsb', 'Les', 'Lsi', 'Lsh', 'Umg'], + 'weekdays_min' => ['Li', 'Us', 'Lb', 'Lt', 'Ls', 'Lh', 'Ug'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ss_ZA.php b/vendor/nesbot/carbon/src/Carbon/Lang/ss_ZA.php new file mode 100644 index 0000000..ba89527 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ss_ZA.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/ss.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/st.php b/vendor/nesbot/carbon/src/Carbon/Lang/st.php new file mode 100644 index 0000000..b065445 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/st.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/st_ZA.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/st_ZA.php b/vendor/nesbot/carbon/src/Carbon/Lang/st_ZA.php new file mode 100644 index 0000000..5bce7f2 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/st_ZA.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Zuza Software Foundation (Translate.org.za) Dwayne Bailey dwayne@translate.org.za + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Pherekgong', 'Hlakola', 'Tlhakubele', 'Mmese', 'Motsheanong', 'Phupjane', 'Phupu', 'Phato', 'Leotse', 'Mphalane', 'Pudungwana', 'Tshitwe'], + 'months_short' => ['Phe', 'Hla', 'TlH', 'Mme', 'Mot', 'Jan', 'Upu', 'Pha', 'Leo', 'Mph', 'Pud', 'Tsh'], + 'weekdays' => ['Sontaha', 'Mantaha', 'Labobedi', 'Laboraro', 'Labone', 'Labohlano', 'Moqebelo'], + 'weekdays_short' => ['Son', 'Mma', 'Bed', 'Rar', 'Ne', 'Hla', 'Moq'], + 'weekdays_min' => ['Son', 'Mma', 'Bed', 'Rar', 'Ne', 'Hla', 'Moq'], + 'day_of_first_week_of_year' => 1, + + 'week' => ':count Sontaha', // less reliable + 'w' => ':count Sontaha', // less reliable + 'a_week' => ':count Sontaha', // less reliable + + 'day' => ':count letsatsi', // less reliable + 'd' => ':count letsatsi', // less reliable + 'a_day' => ':count letsatsi', // less reliable + + 'hour' => ':count sešupanako', // less reliable + 'h' => ':count sešupanako', // less reliable + 'a_hour' => ':count sešupanako', // less reliable + + 'minute' => ':count menyane', // less reliable + 'min' => ':count menyane', // less reliable + 'a_minute' => ':count menyane', // less reliable + + 'second' => ':count thusa', // less reliable + 's' => ':count thusa', // less reliable + 'a_second' => ':count thusa', // less reliable + + 'year' => ':count selemo', + 'y' => ':count selemo', + 'a_year' => ':count selemo', + + 'month' => ':count kgwedi', + 'm' => ':count kgwedi', + 'a_month' => ':count kgwedi', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sv.php b/vendor/nesbot/carbon/src/Carbon/Lang/sv.php new file mode 100644 index 0000000..ca33e1c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sv.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Kristoffer Snabb + * - JD Isaacks + * - Jens Herlevsen + * - Nightpine + * - Anders Nygren (litemerafrukt) + */ +return [ + 'year' => ':count år', + 'a_year' => 'ett år|:count år', + 'y' => ':count år', + 'month' => ':count månad|:count månader', + 'a_month' => 'en månad|:count månader', + 'm' => ':count mån', + 'week' => ':count vecka|:count veckor', + 'a_week' => 'en vecka|:count veckor', + 'w' => ':count v', + 'day' => ':count dag|:count dagar', + 'a_day' => 'en dag|:count dagar', + 'd' => ':count dgr', + 'hour' => ':count timme|:count timmar', + 'a_hour' => 'en timme|:count timmar', + 'h' => ':count tim', + 'minute' => ':count minut|:count minuter', + 'a_minute' => 'en minut|:count minuter', + 'min' => ':count min', + 'second' => ':count sekund|:count sekunder', + 'a_second' => 'några sekunder|:count sekunder', + 's' => ':count s', + 'ago' => 'för :time sedan', + 'from_now' => 'om :time', + 'after' => ':time efter', + 'before' => ':time före', + 'diff_now' => 'nu', + 'diff_today' => 'I dag', + 'diff_yesterday' => 'i går', + 'diff_yesterday_regexp' => 'I går', + 'diff_tomorrow' => 'i morgon', + 'diff_tomorrow_regexp' => 'I morgon', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-DD', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY [kl.] HH:mm', + 'LLLL' => 'dddd D MMMM YYYY [kl.] HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[I dag] LT', + 'nextDay' => '[I morgon] LT', + 'nextWeek' => '[På] dddd LT', + 'lastDay' => '[I går] LT', + 'lastWeek' => '[I] dddd[s] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + $lastDigit = $number % 10; + + return $number.( + (~~($number % 100 / 10) === 1) ? 'e' : ( + ($lastDigit === 1 || $lastDigit === 2) ? 'a' : 'e' + ) + ); + }, + 'months' => ['januari', 'februari', 'mars', 'april', 'maj', 'juni', 'juli', 'augusti', 'september', 'oktober', 'november', 'december'], + 'months_short' => ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'], + 'weekdays' => ['söndag', 'måndag', 'tisdag', 'onsdag', 'torsdag', 'fredag', 'lördag'], + 'weekdays_short' => ['sön', 'mån', 'tis', 'ons', 'tors', 'fre', 'lör'], + 'weekdays_min' => ['sö', 'må', 'ti', 'on', 'to', 'fr', 'lö'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' och '], + 'meridiem' => ['fm', 'em'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sv_AX.php b/vendor/nesbot/carbon/src/Carbon/Lang/sv_AX.php new file mode 100644 index 0000000..70cc558 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sv_AX.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/sv.php', [ + 'formats' => [ + 'L' => 'YYYY-MM-dd', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sv_FI.php b/vendor/nesbot/carbon/src/Carbon/Lang/sv_FI.php new file mode 100644 index 0000000..d7182c8 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sv_FI.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/sv.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sv_SE.php b/vendor/nesbot/carbon/src/Carbon/Lang/sv_SE.php new file mode 100644 index 0000000..d7182c8 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sv_SE.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/sv.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sw.php b/vendor/nesbot/carbon/src/Carbon/Lang/sw.php new file mode 100644 index 0000000..f8630d5 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sw.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - leyluj + * - Josh Soref + * - ryanhart2 + */ +return [ + 'year' => 'mwaka :count|miaka :count', + 'a_year' => 'mwaka mmoja|miaka :count', + 'y' => 'mwaka :count|miaka :count', + 'month' => 'mwezi :count|miezi :count', + 'a_month' => 'mwezi mmoja|miezi :count', + 'm' => 'mwezi :count|miezi :count', + 'week' => 'wiki :count', + 'a_week' => 'wiki mmoja|wiki :count', + 'w' => 'w. :count', + 'day' => 'siku :count', + 'a_day' => 'siku moja|masiku :count', + 'd' => 'si. :count', + 'hour' => 'saa :count|masaa :count', + 'a_hour' => 'saa limoja|masaa :count', + 'h' => 'saa :count|masaa :count', + 'minute' => 'dakika :count', + 'a_minute' => 'dakika moja|dakika :count', + 'min' => 'd. :count', + 'second' => 'sekunde :count', + 'a_second' => 'hivi punde|sekunde :count', + 's' => 'se. :count', + 'ago' => 'tokea :time', + 'from_now' => ':time baadaye', + 'after' => ':time baada', + 'before' => ':time kabla', + 'diff_now' => 'sasa hivi', + 'diff_today' => 'leo', + 'diff_today_regexp' => 'leo(?:\\s+saa)?', + 'diff_yesterday' => 'jana', + 'diff_tomorrow' => 'kesho', + 'diff_tomorrow_regexp' => 'kesho(?:\\s+saa)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[leo saa] LT', + 'nextDay' => '[kesho saa] LT', + 'nextWeek' => '[wiki ijayo] dddd [saat] LT', + 'lastDay' => '[jana] LT', + 'lastWeek' => '[wiki iliyopita] dddd [saat] LT', + 'sameElse' => 'L', + ], + 'months' => ['Januari', 'Februari', 'Machi', 'Aprili', 'Mei', 'Juni', 'Julai', 'Agosti', 'Septemba', 'Oktoba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Sep', 'Okt', 'Nov', 'Des'], + 'weekdays' => ['Jumapili', 'Jumatatu', 'Jumanne', 'Jumatano', 'Alhamisi', 'Ijumaa', 'Jumamosi'], + 'weekdays_short' => ['Jpl', 'Jtat', 'Jnne', 'Jtan', 'Alh', 'Ijm', 'Jmos'], + 'weekdays_min' => ['J2', 'J3', 'J4', 'J5', 'Al', 'Ij', 'J1'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' na '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sw_CD.php b/vendor/nesbot/carbon/src/Carbon/Lang/sw_CD.php new file mode 100644 index 0000000..ec9117b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sw_CD.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/sw.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sw_KE.php b/vendor/nesbot/carbon/src/Carbon/Lang/sw_KE.php new file mode 100644 index 0000000..2ace0db --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sw_KE.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kamusi Project Martin Benjamin locales@kamusi.org + */ +return array_replace_recursive(require __DIR__.'/sw.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Januari', 'Februari', 'Machi', 'Aprili', 'Mei', 'Juni', 'Julai', 'Agosti', 'Septemba', 'Oktoba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Sep', 'Okt', 'Nov', 'Des'], + 'weekdays' => ['Jumapili', 'Jumatatu', 'Jumanne', 'Jumatano', 'Alhamisi', 'Ijumaa', 'Jumamosi'], + 'weekdays_short' => ['J2', 'J3', 'J4', 'J5', 'Alh', 'Ij', 'J1'], + 'weekdays_min' => ['J2', 'J3', 'J4', 'J5', 'Alh', 'Ij', 'J1'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['asubuhi', 'alasiri'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sw_TZ.php b/vendor/nesbot/carbon/src/Carbon/Lang/sw_TZ.php new file mode 100644 index 0000000..fab3cd6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sw_TZ.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kamusi Project Martin Benjamin locales@kamusi.org + */ +return array_replace_recursive(require __DIR__.'/sw.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Januari', 'Februari', 'Machi', 'Aprili', 'Mei', 'Juni', 'Julai', 'Agosti', 'Septemba', 'Oktoba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Sep', 'Okt', 'Nov', 'Des'], + 'weekdays' => ['Jumapili', 'Jumatatu', 'Jumanne', 'Jumatano', 'Alhamisi', 'Ijumaa', 'Jumamosi'], + 'weekdays_short' => ['J2', 'J3', 'J4', 'J5', 'Alh', 'Ij', 'J1'], + 'weekdays_min' => ['J2', 'J3', 'J4', 'J5', 'Alh', 'Ij', 'J1'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['asubuhi', 'alasiri'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/sw_UG.php b/vendor/nesbot/carbon/src/Carbon/Lang/sw_UG.php new file mode 100644 index 0000000..ec9117b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/sw_UG.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/sw.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/szl.php b/vendor/nesbot/carbon/src/Carbon/Lang/szl.php new file mode 100644 index 0000000..4429c4f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/szl.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/szl_PL.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/szl_PL.php b/vendor/nesbot/carbon/src/Carbon/Lang/szl_PL.php new file mode 100644 index 0000000..4b0b541 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/szl_PL.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - szl_PL locale Przemyslaw Buczkowski libc-alpha@sourceware.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['styczyń', 'luty', 'merc', 'kwjeciyń', 'moj', 'czyrwjyń', 'lipjyń', 'siyrpjyń', 'wrzesiyń', 'październik', 'listopad', 'grudziyń'], + 'months_short' => ['sty', 'lut', 'mer', 'kwj', 'moj', 'czy', 'lip', 'siy', 'wrz', 'paź', 'lis', 'gru'], + 'weekdays' => ['niydziela', 'pyńdziŏek', 'wtŏrek', 'strzŏda', 'sztwortek', 'pjōntek', 'sobŏta'], + 'weekdays_short' => ['niy', 'pyń', 'wtŏ', 'str', 'szt', 'pjō', 'sob'], + 'weekdays_min' => ['niy', 'pyń', 'wtŏ', 'str', 'szt', 'pjō', 'sob'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'year' => ':count rok', + 'y' => ':count rok', + 'a_year' => ':count rok', + + 'month' => ':count mjeśůnc', + 'm' => ':count mjeśůnc', + 'a_month' => ':count mjeśůnc', + + 'week' => ':count Tydźyń', + 'w' => ':count Tydźyń', + 'a_week' => ':count Tydźyń', + + 'day' => ':count dźyń', + 'd' => ':count dźyń', + 'a_day' => ':count dźyń', + + 'hour' => ':count godzina', + 'h' => ':count godzina', + 'a_hour' => ':count godzina', + + 'minute' => ':count Minuta', + 'min' => ':count Minuta', + 'a_minute' => ':count Minuta', + + 'second' => ':count Sekůnda', + 's' => ':count Sekůnda', + 'a_second' => ':count Sekůnda', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ta.php b/vendor/nesbot/carbon/src/Carbon/Lang/ta.php new file mode 100644 index 0000000..c1d89cb --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ta.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - François B + * - JD Isaacks + * - Satheez + */ +return [ + 'year' => ':count வருடம்|:count ஆண்டுகள்', + 'a_year' => 'ஒரு வருடம்|:count ஆண்டுகள்', + 'y' => ':count வருட.|:count ஆண்.', + 'month' => ':count மாதம்|:count மாதங்கள்', + 'a_month' => 'ஒரு மாதம்|:count மாதங்கள்', + 'm' => ':count மாத.', + 'week' => ':count வாரம்|:count வாரங்கள்', + 'a_week' => 'ஒரு வாரம்|:count வாரங்கள்', + 'w' => ':count வார.', + 'day' => ':count நாள்|:count நாட்கள்', + 'a_day' => 'ஒரு நாள்|:count நாட்கள்', + 'd' => ':count நாள்|:count நாட்.', + 'hour' => ':count மணி நேரம்|:count மணி நேரம்', + 'a_hour' => 'ஒரு மணி நேரம்|:count மணி நேரம்', + 'h' => ':count மணி.', + 'minute' => ':count நிமிடம்|:count நிமிடங்கள்', + 'a_minute' => 'ஒரு நிமிடம்|:count நிமிடங்கள்', + 'min' => ':count நிமி.', + 'second' => ':count சில விநாடிகள்|:count விநாடிகள்', + 'a_second' => 'ஒரு சில விநாடிகள்|:count விநாடிகள்', + 's' => ':count விநா.', + 'ago' => ':time முன்', + 'from_now' => ':time இல்', + 'before' => ':time முன்', + 'after' => ':time பின்', + 'diff_now' => 'இப்போது', + 'diff_today' => 'இன்று', + 'diff_yesterday' => 'நேற்று', + 'diff_tomorrow' => 'நாளை', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY, HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[இன்று] LT', + 'nextDay' => '[நாளை] LT', + 'nextWeek' => 'dddd, LT', + 'lastDay' => '[நேற்று] LT', + 'lastWeek' => '[கடந்த வாரம்] dddd, LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':numberவது', + 'meridiem' => function ($hour) { + if ($hour < 2) { + return ' யாமம்'; + } + if ($hour < 6) { + return ' வைகறை'; + } + if ($hour < 10) { + return ' காலை'; + } + if ($hour < 14) { + return ' நண்பகல்'; + } + if ($hour < 18) { + return ' எற்பாடு'; + } + if ($hour < 22) { + return ' மாலை'; + } + + return ' யாமம்'; + }, + 'months' => ['ஜனவரி', 'பிப்ரவரி', 'மார்ச்', 'ஏப்ரல்', 'மே', 'ஜூன்', 'ஜூலை', 'ஆகஸ்ட்', 'செப்டெம்பர்', 'அக்டோபர்', 'நவம்பர்', 'டிசம்பர்'], + 'months_short' => ['ஜனவரி', 'பிப்ரவரி', 'மார்ச்', 'ஏப்ரல்', 'மே', 'ஜூன்', 'ஜூலை', 'ஆகஸ்ட்', 'செப்டெம்பர்', 'அக்டோபர்', 'நவம்பர்', 'டிசம்பர்'], + 'weekdays' => ['ஞாயிற்றுக்கிழமை', 'திங்கட்கிழமை', 'செவ்வாய்கிழமை', 'புதன்கிழமை', 'வியாழக்கிழமை', 'வெள்ளிக்கிழமை', 'சனிக்கிழமை'], + 'weekdays_short' => ['ஞாயிறு', 'திங்கள்', 'செவ்வாய்', 'புதன்', 'வியாழன்', 'வெள்ளி', 'சனி'], + 'weekdays_min' => ['ஞா', 'தி', 'செ', 'பு', 'வி', 'வெ', 'ச'], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' மற்றும் '], + 'weekend' => [0, 0], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ta_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/ta_IN.php new file mode 100644 index 0000000..492d4c5 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ta_IN.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/ta.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['ஜனவரி', 'பிப்ரவரி', 'மார்ச்', 'ஏப்ரல்', 'மே', 'ஜூன்', 'ஜூலை', 'ஆகஸ்ட்', 'செப்டம்பர்', 'அக்டோபர்', 'நவம்பர்', 'டிசம்பர்'], + 'months_short' => ['ஜன.', 'பிப்.', 'மார்.', 'ஏப்.', 'மே', 'ஜூன்', 'ஜூலை', 'ஆக.', 'செப்.', 'அக்.', 'நவ.', 'டிச.'], + 'weekdays' => ['ஞாயிறு', 'திங்கள்', 'செவ்வாய்', 'புதன்', 'வியாழன்', 'வெள்ளி', 'சனி'], + 'weekdays_short' => ['ஞா', 'தி', 'செ', 'பு', 'வி', 'வெ', 'ச'], + 'weekdays_min' => ['ஞா', 'தி', 'செ', 'பு', 'வி', 'வெ', 'ச'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['காலை', 'மாலை'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ta_LK.php b/vendor/nesbot/carbon/src/Carbon/Lang/ta_LK.php new file mode 100644 index 0000000..8e2afbf --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ta_LK.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - J.Yogaraj 94-777-315206 yogaraj.ubuntu@gmail.com + */ +return array_replace_recursive(require __DIR__.'/ta.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['ஜனவரி', 'பிப்ரவரி', 'மார்ச்', 'ஏப்ரல்', 'மே', 'ஜூன்', 'ஜூலை', 'ஆகஸ்ட்', 'செப்டம்பர்', 'அக்டோபர்', 'நவம்பர்', 'டிசம்பர்'], + 'months_short' => ['ஜன', 'பிப்', 'மார்', 'ஏப்', 'மே', 'ஜூன்', 'ஜூலை', 'ஆக', 'செப்', 'அக்', 'நவ', 'டிச'], + 'weekdays' => ['ஞாயிறு', 'திங்கள்', 'செவ்வாய்', 'புதன்', 'வியாழன்', 'வெள்ளி', 'சனி'], + 'weekdays_short' => ['ஞா', 'தி', 'செ', 'பு', 'வி', 'வெ', 'ச'], + 'weekdays_min' => ['ஞா', 'தி', 'செ', 'பு', 'வி', 'வெ', 'ச'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['காலை', 'மாலை'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ta_MY.php b/vendor/nesbot/carbon/src/Carbon/Lang/ta_MY.php new file mode 100644 index 0000000..a6cd8b5 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ta_MY.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ta.php', [ + 'formats' => [ + 'LT' => 'a h:mm', + 'LTS' => 'a h:mm:ss', + 'L' => 'D/M/yy', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM, YYYY, a h:mm', + 'LLLL' => 'dddd, D MMMM, YYYY, a h:mm', + ], + 'months' => ['ஜனவரி', 'பிப்ரவரி', 'மார்ச்', 'ஏப்ரல்', 'மே', 'ஜூன்', 'ஜூலை', 'ஆகஸ்ட்', 'செப்டம்பர்', 'அக்டோபர்', 'நவம்பர்', 'டிசம்பர்'], + 'months_short' => ['ஜன.', 'பிப்.', 'மார்.', 'ஏப்.', 'மே', 'ஜூன்', 'ஜூலை', 'ஆக.', 'செப்.', 'அக்.', 'நவ.', 'டிச.'], + 'weekdays' => ['ஞாயிறு', 'திங்கள்', 'செவ்வாய்', 'புதன்', 'வியாழன்', 'வெள்ளி', 'சனி'], + 'weekdays_short' => ['ஞாயி.', 'திங்.', 'செவ்.', 'புத.', 'வியா.', 'வெள்.', 'சனி'], + 'weekdays_min' => ['ஞா', 'தி', 'செ', 'பு', 'வி', 'வெ', 'ச'], + 'first_day_of_week' => 1, + 'meridiem' => ['மு.ப', 'பி.ப'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ta_SG.php b/vendor/nesbot/carbon/src/Carbon/Lang/ta_SG.php new file mode 100644 index 0000000..7dbedee --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ta_SG.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ta.php', [ + 'formats' => [ + 'LT' => 'a h:mm', + 'LTS' => 'a h:mm:ss', + 'L' => 'D/M/yy', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM, YYYY, a h:mm', + 'LLLL' => 'dddd, D MMMM, YYYY, a h:mm', + ], + 'months' => ['ஜனவரி', 'பிப்ரவரி', 'மார்ச்', 'ஏப்ரல்', 'மே', 'ஜூன்', 'ஜூலை', 'ஆகஸ்ட்', 'செப்டம்பர்', 'அக்டோபர்', 'நவம்பர்', 'டிசம்பர்'], + 'months_short' => ['ஜன.', 'பிப்.', 'மார்.', 'ஏப்.', 'மே', 'ஜூன்', 'ஜூலை', 'ஆக.', 'செப்.', 'அக்.', 'நவ.', 'டிச.'], + 'weekdays' => ['ஞாயிறு', 'திங்கள்', 'செவ்வாய்', 'புதன்', 'வியாழன்', 'வெள்ளி', 'சனி'], + 'weekdays_short' => ['ஞாயி.', 'திங்.', 'செவ்.', 'புத.', 'வியா.', 'வெள்.', 'சனி'], + 'weekdays_min' => ['ஞா', 'தி', 'செ', 'பு', 'வி', 'வெ', 'ச'], + 'meridiem' => ['மு.ப', 'பி.ப'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tcy.php b/vendor/nesbot/carbon/src/Carbon/Lang/tcy.php new file mode 100644 index 0000000..2eb9905 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tcy.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/tcy_IN.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tcy_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/tcy_IN.php new file mode 100644 index 0000000..2ff20e0 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tcy_IN.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IndLinux.org, Samsung Electronics Co., Ltd. alexey.merzlyakov@samsung.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['ಜನವರಿ', 'ಫೆಬ್ರುವರಿ', 'ಮಾರ್ಚ್', 'ಏಪ್ರಿಲ್‌‌', 'ಮೇ', 'ಜೂನ್', 'ಜುಲೈ', 'ಆಗಸ್ಟ್', 'ಸೆಪ್ಟೆಂಬರ್‌', 'ಅಕ್ಟೋಬರ್', 'ನವೆಂಬರ್', 'ಡಿಸೆಂಬರ್'], + 'months_short' => ['ಜ', 'ಫೆ', 'ಮಾ', 'ಏ', 'ಮೇ', 'ಜೂ', 'ಜು', 'ಆ', 'ಸೆ', 'ಅ', 'ನ', 'ಡಿ'], + 'weekdays' => ['ಐಥಾರ', 'ಸೋಮಾರ', 'ಅಂಗರೆ', 'ಬುಧಾರ', 'ಗುರುವಾರ', 'ಶುಕ್ರರ', 'ಶನಿವಾರ'], + 'weekdays_short' => ['ಐ', 'ಸೋ', 'ಅಂ', 'ಬು', 'ಗು', 'ಶು', 'ಶ'], + 'weekdays_min' => ['ಐ', 'ಸೋ', 'ಅಂ', 'ಬು', 'ಗು', 'ಶು', 'ಶ'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ಕಾಂಡೆ', 'ಬಯ್ಯ'], + + 'year' => ':count ನೀರ್', // less reliable + 'y' => ':count ನೀರ್', // less reliable + 'a_year' => ':count ನೀರ್', // less reliable + + 'month' => ':count ಮೀನ್', // less reliable + 'm' => ':count ಮೀನ್', // less reliable + 'a_month' => ':count ಮೀನ್', // less reliable + + 'day' => ':count ಸುಗ್ಗಿ', // less reliable + 'd' => ':count ಸುಗ್ಗಿ', // less reliable + 'a_day' => ':count ಸುಗ್ಗಿ', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/te.php b/vendor/nesbot/carbon/src/Carbon/Lang/te.php new file mode 100644 index 0000000..ac38218 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/te.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kunal Marwaha + * - Josh Soref + * - François B + * - kc + */ +return [ + 'year' => ':count సంవత్సరం|:count సంవత్సరాలు', + 'a_year' => 'ఒక సంవత్సరం|:count సంవత్సరాలు', + 'y' => ':count సం.', + 'month' => ':count నెల|:count నెలలు', + 'a_month' => 'ఒక నెల|:count నెలలు', + 'm' => ':count నెల|:count నెల.', + 'week' => ':count వారం|:count వారాలు', + 'a_week' => 'ఒక వారం|:count వారాలు', + 'w' => ':count వార.|:count వారా.', + 'day' => ':count రోజు|:count రోజులు', + 'a_day' => 'ఒక రోజు|:count రోజులు', + 'd' => ':count రోజు|:count రోజు.', + 'hour' => ':count గంట|:count గంటలు', + 'a_hour' => 'ఒక గంట|:count గంటలు', + 'h' => ':count గం.', + 'minute' => ':count నిమిషం|:count నిమిషాలు', + 'a_minute' => 'ఒక నిమిషం|:count నిమిషాలు', + 'min' => ':count నిమి.', + 'second' => ':count సెకను|:count సెకన్లు', + 'a_second' => 'కొన్ని క్షణాలు|:count సెకన్లు', + 's' => ':count సెక.', + 'ago' => ':time క్రితం', + 'from_now' => ':time లో', + 'diff_now' => 'ప్రస్తుతం', + 'diff_today' => 'నేడు', + 'diff_yesterday' => 'నిన్న', + 'diff_tomorrow' => 'రేపు', + 'formats' => [ + 'LT' => 'A h:mm', + 'LTS' => 'A h:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, A h:mm', + 'LLLL' => 'dddd, D MMMM YYYY, A h:mm', + ], + 'calendar' => [ + 'sameDay' => '[నేడు] LT', + 'nextDay' => '[రేపు] LT', + 'nextWeek' => 'dddd, LT', + 'lastDay' => '[నిన్న] LT', + 'lastWeek' => '[గత] dddd, LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':numberవ', + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'రాత్రి'; + } + if ($hour < 10) { + return 'ఉదయం'; + } + if ($hour < 17) { + return 'మధ్యాహ్నం'; + } + if ($hour < 20) { + return 'సాయంత్రం'; + } + + return ' రాత్రి'; + }, + 'months' => ['జనవరి', 'ఫిబ్రవరి', 'మార్చి', 'ఏప్రిల్', 'మే', 'జూన్', 'జూలై', 'ఆగస్టు', 'సెప్టెంబర్', 'అక్టోబర్', 'నవంబర్', 'డిసెంబర్'], + 'months_short' => ['జన.', 'ఫిబ్ర.', 'మార్చి', 'ఏప్రి.', 'మే', 'జూన్', 'జూలై', 'ఆగ.', 'సెప్.', 'అక్టో.', 'నవ.', 'డిసె.'], + 'weekdays' => ['ఆదివారం', 'సోమవారం', 'మంగళవారం', 'బుధవారం', 'గురువారం', 'శుక్రవారం', 'శనివారం'], + 'weekdays_short' => ['ఆది', 'సోమ', 'మంగళ', 'బుధ', 'గురు', 'శుక్ర', 'శని'], + 'weekdays_min' => ['ఆ', 'సో', 'మం', 'బు', 'గు', 'శు', 'శ'], + 'list' => ', ', + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'weekend' => [0, 0], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/te_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/te_IN.php new file mode 100644 index 0000000..3963f8d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/te_IN.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/te.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/teo.php b/vendor/nesbot/carbon/src/Carbon/Lang/teo.php new file mode 100644 index 0000000..ca30c37 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/teo.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ta.php', [ + 'meridiem' => ['Taparachu', 'Ebongi'], + 'weekdays' => ['Nakaejuma', 'Nakaebarasa', 'Nakaare', 'Nakauni', 'Nakaung’on', 'Nakakany', 'Nakasabiti'], + 'weekdays_short' => ['Jum', 'Bar', 'Aar', 'Uni', 'Ung', 'Kan', 'Sab'], + 'weekdays_min' => ['Jum', 'Bar', 'Aar', 'Uni', 'Ung', 'Kan', 'Sab'], + 'months' => ['Orara', 'Omuk', 'Okwamg’', 'Odung’el', 'Omaruk', 'Omodok’king’ol', 'Ojola', 'Opedel', 'Osokosokoma', 'Otibar', 'Olabor', 'Opoo'], + 'months_short' => ['Rar', 'Muk', 'Kwa', 'Dun', 'Mar', 'Mod', 'Jol', 'Ped', 'Sok', 'Tib', 'Lab', 'Poo'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/teo_KE.php b/vendor/nesbot/carbon/src/Carbon/Lang/teo_KE.php new file mode 100644 index 0000000..010a04f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/teo_KE.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/teo.php', [ + 'first_day_of_week' => 0, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tet.php b/vendor/nesbot/carbon/src/Carbon/Lang/tet.php new file mode 100644 index 0000000..d0544d4 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tet.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Joshua Brooks + * - François B + */ +return [ + 'year' => 'tinan :count', + 'a_year' => '{1}tinan ida|tinan :count', + 'month' => 'fulan :count', + 'a_month' => '{1}fulan ida|fulan :count', + 'week' => 'semana :count', + 'a_week' => '{1}semana ida|semana :count', + 'day' => 'loron :count', + 'a_day' => '{1}loron ida|loron :count', + 'hour' => 'oras :count', + 'a_hour' => '{1}oras ida|oras :count', + 'minute' => 'minutu :count', + 'a_minute' => '{1}minutu ida|minutu :count', + 'second' => 'segundu :count', + 'a_second' => '{1}segundu balun|segundu :count', + 'ago' => ':time liuba', + 'from_now' => 'iha :time', + 'diff_yesterday' => 'Horiseik', + 'diff_yesterday_regexp' => 'Horiseik(?:\\s+iha)?', + 'diff_today' => 'Ohin', + 'diff_today_regexp' => 'Ohin(?:\\s+iha)?', + 'diff_tomorrow' => 'Aban', + 'diff_tomorrow_regexp' => 'Aban(?:\\s+iha)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Ohin iha] LT', + 'nextDay' => '[Aban iha] LT', + 'nextWeek' => 'dddd [iha] LT', + 'lastDay' => '[Horiseik iha] LT', + 'lastWeek' => 'dddd [semana kotuk] [iha] LT', + 'sameElse' => 'L', + ], + 'ordinal' => ':numberº', + 'months' => ['Janeiru', 'Fevereiru', 'Marsu', 'Abril', 'Maiu', 'Juñu', 'Jullu', 'Agustu', 'Setembru', 'Outubru', 'Novembru', 'Dezembru'], + 'months_short' => ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez'], + 'weekdays' => ['Domingu', 'Segunda', 'Tersa', 'Kuarta', 'Kinta', 'Sesta', 'Sabadu'], + 'weekdays_short' => ['Dom', 'Seg', 'Ters', 'Kua', 'Kint', 'Sest', 'Sab'], + 'weekdays_min' => ['Do', 'Seg', 'Te', 'Ku', 'Ki', 'Ses', 'Sa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tg.php b/vendor/nesbot/carbon/src/Carbon/Lang/tg.php new file mode 100644 index 0000000..b7df893 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tg.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Orif N. Jr + */ +return [ + 'year' => '{1}як сол|:count сол', + 'month' => '{1}як моҳ|:count моҳ', + 'week' => '{1}як ҳафта|:count ҳафта', + 'day' => '{1}як рӯз|:count рӯз', + 'hour' => '{1}як соат|:count соат', + 'minute' => '{1}як дақиқа|:count дақиқа', + 'second' => '{1}якчанд сония|:count сония', + 'ago' => ':time пеш', + 'from_now' => 'баъди :time', + 'diff_today' => 'Имрӯз', + 'diff_yesterday' => 'Дирӯз', + 'diff_yesterday_regexp' => 'Дирӯз(?:\\s+соати)?', + 'diff_tomorrow' => 'Пагоҳ', + 'diff_tomorrow_regexp' => 'Пагоҳ(?:\\s+соати)?', + 'diff_today_regexp' => 'Имрӯз(?:\\s+соати)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Имрӯз соати] LT', + 'nextDay' => '[Пагоҳ соати] LT', + 'nextWeek' => 'dddd[и] [ҳафтаи оянда соати] LT', + 'lastDay' => '[Дирӯз соати] LT', + 'lastWeek' => 'dddd[и] [ҳафтаи гузашта соати] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number) { + if ($number === 0) { // special case for zero + return "$number-ıncı"; + } + + static $suffixes = [ + 0 => '-ум', + 1 => '-ум', + 2 => '-юм', + 3 => '-юм', + 4 => '-ум', + 5 => '-ум', + 6 => '-ум', + 7 => '-ум', + 8 => '-ум', + 9 => '-ум', + 10 => '-ум', + 12 => '-ум', + 13 => '-ум', + 20 => '-ум', + 30 => '-юм', + 40 => '-ум', + 50 => '-ум', + 60 => '-ум', + 70 => '-ум', + 80 => '-ум', + 90 => '-ум', + 100 => '-ум', + ]; + + return $number.($suffixes[$number] ?? $suffixes[$number % 10] ?? $suffixes[$number >= 100 ? 100 : -1] ?? ''); + }, + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'шаб'; + } + if ($hour < 11) { + return 'субҳ'; + } + if ($hour < 16) { + return 'рӯз'; + } + if ($hour < 19) { + return 'бегоҳ'; + } + + return 'шаб'; + }, + 'months' => ['январ', 'феврал', 'март', 'апрел', 'май', 'июн', 'июл', 'август', 'сентябр', 'октябр', 'ноябр', 'декабр'], + 'months_short' => ['янв', 'фев', 'мар', 'апр', 'май', 'июн', 'июл', 'авг', 'сен', 'окт', 'ноя', 'дек'], + 'weekdays' => ['якшанбе', 'душанбе', 'сешанбе', 'чоршанбе', 'панҷшанбе', 'ҷумъа', 'шанбе'], + 'weekdays_short' => ['яшб', 'дшб', 'сшб', 'чшб', 'пшб', 'ҷум', 'шнб'], + 'weekdays_min' => ['яш', 'дш', 'сш', 'чш', 'пш', 'ҷм', 'шб'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' ва '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tg_TJ.php b/vendor/nesbot/carbon/src/Carbon/Lang/tg_TJ.php new file mode 100644 index 0000000..badc7d1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tg_TJ.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/tg.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/th.php b/vendor/nesbot/carbon/src/Carbon/Lang/th.php new file mode 100644 index 0000000..6397f6e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/th.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Nate Whittaker + * - John MacAslan + * - Chanintorn Asavavichairoj + * - JD Isaacks + * - ROKAISAKKON + * - RO'KAISAKKON + * - Andreas Möller + * - nithisa + */ +return [ + 'year' => ':count ปี', + 'y' => ':count ปี', + 'month' => ':count เดือน', + 'm' => ':count เดือน', + 'week' => ':count สัปดาห์', + 'w' => ':count สัปดาห์', + 'day' => ':count วัน', + 'd' => ':count วัน', + 'hour' => ':count ชั่วโมง', + 'h' => ':count ชั่วโมง', + 'minute' => ':count นาที', + 'min' => ':count นาที', + 'second' => ':count วินาที', + 'a_second' => '{1}ไม่กี่วินาที|]1,Inf[:count วินาที', + 's' => ':count วินาที', + 'ago' => ':timeที่แล้ว', + 'from_now' => 'อีก :time', + 'after' => ':timeหลังจากนี้', + 'before' => ':timeก่อน', + 'diff_now' => 'ขณะนี้', + 'diff_today' => 'วันนี้', + 'diff_today_regexp' => 'วันนี้(?:\\s+เวลา)?', + 'diff_yesterday' => 'เมื่อวาน', + 'diff_yesterday_regexp' => 'เมื่อวานนี้(?:\\s+เวลา)?', + 'diff_tomorrow' => 'พรุ่งนี้', + 'diff_tomorrow_regexp' => 'พรุ่งนี้(?:\\s+เวลา)?', + 'formats' => [ + 'LT' => 'H:mm', + 'LTS' => 'H:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY เวลา H:mm', + 'LLLL' => 'วันddddที่ D MMMM YYYY เวลา H:mm', + ], + 'calendar' => [ + 'sameDay' => '[วันนี้ เวลา] LT', + 'nextDay' => '[พรุ่งนี้ เวลา] LT', + 'nextWeek' => 'dddd[หน้า เวลา] LT', + 'lastDay' => '[เมื่อวานนี้ เวลา] LT', + 'lastWeek' => '[วัน]dddd[ที่แล้ว เวลา] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['ก่อนเที่ยง', 'หลังเที่ยง'], + 'months' => ['มกราคม', 'กุมภาพันธ์', 'มีนาคม', 'เมษายน', 'พฤษภาคม', 'มิถุนายน', 'กรกฎาคม', 'สิงหาคม', 'กันยายน', 'ตุลาคม', 'พฤศจิกายน', 'ธันวาคม'], + 'months_short' => ['ม.ค.', 'ก.พ.', 'มี.ค.', 'เม.ย.', 'พ.ค.', 'มิ.ย.', 'ก.ค.', 'ส.ค.', 'ก.ย.', 'ต.ค.', 'พ.ย.', 'ธ.ค.'], + 'weekdays' => ['อาทิตย์', 'จันทร์', 'อังคาร', 'พุธ', 'พฤหัสบดี', 'ศุกร์', 'เสาร์'], + 'weekdays_short' => ['อาทิตย์', 'จันทร์', 'อังคาร', 'พุธ', 'พฤหัส', 'ศุกร์', 'เสาร์'], + 'weekdays_min' => ['อา.', 'จ.', 'อ.', 'พ.', 'พฤ.', 'ศ.', 'ส.'], + 'list' => [', ', ' และ '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/th_TH.php b/vendor/nesbot/carbon/src/Carbon/Lang/th_TH.php new file mode 100644 index 0000000..b9f94b2 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/th_TH.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/th.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/the.php b/vendor/nesbot/carbon/src/Carbon/Lang/the.php new file mode 100644 index 0000000..85f8333 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/the.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/the_NP.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/the_NP.php b/vendor/nesbot/carbon/src/Carbon/Lang/the_NP.php new file mode 100644 index 0000000..34da162 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/the_NP.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Chitwanix OS Development info@chitwanix.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'dddd DD MMM YYYY', + ], + 'months' => ['जनवरी', 'फ़रवरी', 'मार्च', 'अप्रेल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितम्बर', 'अक्टूबर', 'नवम्बर', 'दिसम्बर'], + 'months_short' => ['जनवरी', 'फ़रवरी', 'मार्च', 'अप्रेल', 'मई', 'जून', 'जुलाई', 'अगस्त', 'सितम्बर', 'अक्टूबर', 'नवम्बर', 'दिसम्बर'], + 'weekdays' => ['आइतबार', 'सोमबार', 'मंगलबार', 'बुधबार', 'बिहिबार', 'शुक्रबार', 'शनिबार'], + 'weekdays_short' => ['आइत', 'सोम', 'मंगल', 'बुध', 'बिहि', 'शुक्र', 'शनि'], + 'weekdays_min' => ['आइत', 'सोम', 'मंगल', 'बुध', 'बिहि', 'शुक्र', 'शनि'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['पूर्वाह्न', 'अपराह्न'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ti.php b/vendor/nesbot/carbon/src/Carbon/Lang/ti.php new file mode 100644 index 0000000..ffd3236 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ti.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ti_ER.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ti_ER.php b/vendor/nesbot/carbon/src/Carbon/Lang/ti_ER.php new file mode 100644 index 0000000..310c51c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ti_ER.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['ጥሪ', 'ለካቲት', 'መጋቢት', 'ሚያዝያ', 'ግንቦት', 'ሰነ', 'ሓምለ', 'ነሓሰ', 'መስከረም', 'ጥቅምቲ', 'ሕዳር', 'ታሕሳስ'], + 'months_short' => ['ጥሪ ', 'ለካቲ', 'መጋቢ', 'ሚያዝ', 'ግንቦ', 'ሰነ ', 'ሓምለ', 'ነሓሰ', 'መስከ', 'ጥቅም', 'ሕዳር', 'ታሕሳ'], + 'weekdays' => ['ሰንበት', 'ሰኑይ', 'ሰሉስ', 'ረቡዕ', 'ሓሙስ', 'ዓርቢ', 'ቀዳም'], + 'weekdays_short' => ['ሰንበ', 'ሰኑይ', 'ሰሉስ', 'ረቡዕ', 'ሓሙስ', 'ዓርቢ', 'ቀዳም'], + 'weekdays_min' => ['ሰንበ', 'ሰኑይ', 'ሰሉስ', 'ረቡዕ', 'ሓሙስ', 'ዓርቢ', 'ቀዳም'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ንጉሆ ሰዓተ', 'ድሕር ሰዓት'], + + 'year' => ':count ዓመት', + 'y' => ':count ዓመት', + 'a_year' => ':count ዓመት', + + 'month' => 'ወርሒ :count', + 'm' => 'ወርሒ :count', + 'a_month' => 'ወርሒ :count', + + 'week' => ':count ሰሙን', + 'w' => ':count ሰሙን', + 'a_week' => ':count ሰሙን', + + 'day' => ':count መዓልቲ', + 'd' => ':count መዓልቲ', + 'a_day' => ':count መዓልቲ', + + 'hour' => ':count ሰዓት', + 'h' => ':count ሰዓት', + 'a_hour' => ':count ሰዓት', + + 'minute' => ':count ደቒቕ', + 'min' => ':count ደቒቕ', + 'a_minute' => ':count ደቒቕ', + + 'second' => ':count ሰከንድ', + 's' => ':count ሰከንድ', + 'a_second' => ':count ሰከንድ', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ti_ET.php b/vendor/nesbot/carbon/src/Carbon/Lang/ti_ET.php new file mode 100644 index 0000000..024217f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ti_ET.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['ጃንዩወሪ', 'ፌብሩወሪ', 'ማርች', 'ኤፕረል', 'ሜይ', 'ጁን', 'ጁላይ', 'ኦገስት', 'ሴፕቴምበር', 'ኦክተውበር', 'ኖቬምበር', 'ዲሴምበር'], + 'months_short' => ['ጃንዩ', 'ፌብሩ', 'ማርች', 'ኤፕረ', 'ሜይ ', 'ጁን ', 'ጁላይ', 'ኦገስ', 'ሴፕቴ', 'ኦክተ', 'ኖቬም', 'ዲሴም'], + 'weekdays' => ['ሰንበት', 'ሰኑይ', 'ሰሉስ', 'ረቡዕ', 'ሓሙስ', 'ዓርቢ', 'ቀዳም'], + 'weekdays_short' => ['ሰንበ', 'ሰኑይ', 'ሰሉስ', 'ረቡዕ', 'ሓሙስ', 'ዓርቢ', 'ቀዳም'], + 'weekdays_min' => ['ሰንበ', 'ሰኑይ', 'ሰሉስ', 'ረቡዕ', 'ሓሙስ', 'ዓርቢ', 'ቀዳም'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ንጉሆ ሰዓተ', 'ድሕር ሰዓት'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tig.php b/vendor/nesbot/carbon/src/Carbon/Lang/tig.php new file mode 100644 index 0000000..186fe71 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tig.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/tig_ER.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tig_ER.php b/vendor/nesbot/carbon/src/Carbon/Lang/tig_ER.php new file mode 100644 index 0000000..46887b0 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tig_ER.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['ጥሪ', 'ለካቲት', 'መጋቢት', 'ሚያዝያ', 'ግንቦት', 'ሰነ', 'ሓምለ', 'ነሓሰ', 'መስከረም', 'ጥቅምቲ', 'ሕዳር', 'ታሕሳስ'], + 'months_short' => ['ጥሪ ', 'ለካቲ', 'መጋቢ', 'ሚያዝ', 'ግንቦ', 'ሰነ ', 'ሓምለ', 'ነሓሰ', 'መስከ', 'ጥቅም', 'ሕዳር', 'ታሕሳ'], + 'weekdays' => ['ሰንበት ዓባይ', 'ሰኖ', 'ታላሸኖ', 'ኣረርባዓ', 'ከሚሽ', 'ጅምዓት', 'ሰንበት ንኢሽ'], + 'weekdays_short' => ['ሰ//ዓ', 'ሰኖ ', 'ታላሸ', 'ኣረር', 'ከሚሽ', 'ጅምዓ', 'ሰ//ን'], + 'weekdays_min' => ['ሰ//ዓ', 'ሰኖ ', 'ታላሸ', 'ኣረር', 'ከሚሽ', 'ጅምዓ', 'ሰ//ን'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ቀደም ሰር ምዕል', 'ሓቆ ሰር ምዕል'], + + 'year' => ':count ማይ', // less reliable + 'y' => ':count ማይ', // less reliable + 'a_year' => ':count ማይ', // less reliable + + 'month' => ':count ሸምሽ', // less reliable + 'm' => ':count ሸምሽ', // less reliable + 'a_month' => ':count ሸምሽ', // less reliable + + 'week' => ':count ሰቡዕ', // less reliable + 'w' => ':count ሰቡዕ', // less reliable + 'a_week' => ':count ሰቡዕ', // less reliable + + 'day' => ':count ዎሮ', // less reliable + 'd' => ':count ዎሮ', // less reliable + 'a_day' => ':count ዎሮ', // less reliable + + 'hour' => ':count ሰዓት', // less reliable + 'h' => ':count ሰዓት', // less reliable + 'a_hour' => ':count ሰዓት', // less reliable + + 'minute' => ':count ካልኣይት', // less reliable + 'min' => ':count ካልኣይት', // less reliable + 'a_minute' => ':count ካልኣይት', // less reliable + + 'second' => ':count ካልኣይ', + 's' => ':count ካልኣይ', + 'a_second' => ':count ካልኣይ', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tk.php b/vendor/nesbot/carbon/src/Carbon/Lang/tk.php new file mode 100644 index 0000000..d8f7d19 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tk.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/tk_TM.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tk_TM.php b/vendor/nesbot/carbon/src/Carbon/Lang/tk_TM.php new file mode 100644 index 0000000..f949a43 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tk_TM.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Authors: + * - Ghorban M. Tavakoly Pablo Saratxaga & Ghorban M. Tavakoly pablo@walon.org & gmt314@yahoo.com + * - SuperManPHP + * - Maksat Meredow (isadma) + */ +$transformDiff = function ($input) { + return strtr($input, [ + 'sekunt' => 'sekunt', + 'hepde' => 'hepde', + ]); +}; + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['Ýanwar', 'Fewral', 'Mart', 'Aprel', 'Maý', 'Iýun', 'Iýul', 'Awgust', 'Sentýabr', 'Oktýabr', 'Noýabr', 'Dekabr'], + 'months_short' => ['Ýan', 'Few', 'Mar', 'Apr', 'Maý', 'Iýn', 'Iýl', 'Awg', 'Sen', 'Okt', 'Noý', 'Dek'], + 'weekdays' => ['Duşenbe', 'Sişenbe', 'Çarşenbe', 'Penşenbe', 'Anna', 'Şenbe', 'Ýekşenbe'], + 'weekdays_short' => ['Duş', 'Siş', 'Çar', 'Pen', 'Ann', 'Şen', 'Ýek'], + 'weekdays_min' => ['Du', 'Si', 'Ça', 'Pe', 'An', 'Şe', 'Ýe'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + + 'year' => ':count ýyl', + 'y' => ':count ýyl', + 'a_year' => ':count ýyl', + + 'month' => ':count aý', + 'm' => ':count aý', + 'a_month' => ':count aý', + + 'week' => ':count hepde', + 'w' => ':count hepde', + 'a_week' => ':count hepde', + + 'day' => ':count gün', + 'd' => ':count gün', + 'a_day' => ':count gün', + + 'hour' => ':count sagat', + 'h' => ':count sagat', + 'a_hour' => ':count sagat', + + 'minute' => ':count minut', + 'min' => ':count minut', + 'a_minute' => ':count minut', + + 'second' => ':count sekunt', + 's' => ':count sekunt', + 'a_second' => ':count sekunt', + + 'ago' => function ($time) use ($transformDiff) { + return $transformDiff($time).' ozal'; + }, + 'from_now' => function ($time) use ($transformDiff) { + return $transformDiff($time).' soňra'; + }, + 'after' => function ($time) use ($transformDiff) { + return $transformDiff($time).' soň'; + }, + 'before' => function ($time) use ($transformDiff) { + return $transformDiff($time).' öň'; + }, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tl.php b/vendor/nesbot/carbon/src/Carbon/Lang/tl.php new file mode 100644 index 0000000..410a266 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tl.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'year' => ':count taon', + 'a_year' => '{1}isang taon|:count taon', + 'month' => ':count buwan', + 'a_month' => '{1}isang buwan|:count buwan', + 'week' => ':count linggo', + 'a_week' => '{1}isang linggo|:count linggo', + 'day' => ':count araw', + 'a_day' => '{1}isang araw|:count araw', + 'hour' => ':count oras', + 'a_hour' => '{1}isang oras|:count oras', + 'minute' => ':count minuto', + 'a_minute' => '{1}isang minuto|:count minuto', + 'min' => ':count min.', + 'second' => ':count segundo', + 'a_second' => '{1}ilang segundo|:count segundo', + 's' => ':count seg.', + 'ago' => ':time ang nakalipas', + 'from_now' => 'sa loob ng :time', + 'diff_now' => 'ngayon', + 'diff_today' => 'ngayong', + 'diff_today_regexp' => 'ngayong(?:\\s+araw)?', + 'diff_yesterday' => 'kahapon', + 'diff_tomorrow' => 'bukas', + 'diff_tomorrow_regexp' => 'Bukas(?:\\s+ng)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'MM/D/YYYY', + 'LL' => 'MMMM D, YYYY', + 'LLL' => 'MMMM D, YYYY HH:mm', + 'LLLL' => 'dddd, MMMM DD, YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => 'LT [ngayong araw]', + 'nextDay' => '[Bukas ng] LT', + 'nextWeek' => 'LT [sa susunod na] dddd', + 'lastDay' => 'LT [kahapon]', + 'lastWeek' => 'LT [noong nakaraang] dddd', + 'sameElse' => 'L', + ], + 'months' => ['Enero', 'Pebrero', 'Marso', 'Abril', 'Mayo', 'Hunyo', 'Hulyo', 'Agosto', 'Setyembre', 'Oktubre', 'Nobyembre', 'Disyembre'], + 'months_short' => ['Ene', 'Peb', 'Mar', 'Abr', 'May', 'Hun', 'Hul', 'Ago', 'Set', 'Okt', 'Nob', 'Dis'], + 'weekdays' => ['Linggo', 'Lunes', 'Martes', 'Miyerkules', 'Huwebes', 'Biyernes', 'Sabado'], + 'weekdays_short' => ['Lin', 'Lun', 'Mar', 'Miy', 'Huw', 'Biy', 'Sab'], + 'weekdays_min' => ['Li', 'Lu', 'Ma', 'Mi', 'Hu', 'Bi', 'Sab'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' at '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tl_PH.php b/vendor/nesbot/carbon/src/Carbon/Lang/tl_PH.php new file mode 100644 index 0000000..95f508c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tl_PH.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Ian De La Cruz + * - JD Isaacks + */ +return require __DIR__.'/tl.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tlh.php b/vendor/nesbot/carbon/src/Carbon/Lang/tlh.php new file mode 100644 index 0000000..fbf9e6f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tlh.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Serhan Apaydın + * - Dominika + */ +return [ + 'year' => '{1}wa’ DIS|:count DIS', + 'month' => '{1}wa’ jar|:count jar', + 'week' => '{1}wa’ hogh|:count hogh', + 'day' => '{1}wa’ jaj|:count jaj', + 'hour' => '{1}wa’ rep|:count rep', + 'minute' => '{1}wa’ tup|:count tup', + 'second' => '{1}puS lup|:count lup', + 'ago' => function ($time) { + $output = strtr($time, [ + 'jaj' => 'Hu’', + 'jar' => 'wen', + 'DIS' => 'ben', + ]); + + return $output === $time ? "$time ret" : $output; + }, + 'from_now' => function ($time) { + $output = strtr($time, [ + 'jaj' => 'leS', + 'jar' => 'waQ', + 'DIS' => 'nem', + ]); + + return $output === $time ? "$time pIq" : $output; + }, + 'diff_yesterday' => 'wa’Hu’', + 'diff_today' => 'DaHjaj', + 'diff_tomorrow' => 'wa’leS', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[DaHjaj] LT', + 'nextDay' => '[wa’leS] LT', + 'nextWeek' => 'LLL', + 'lastDay' => '[wa’Hu’] LT', + 'lastWeek' => 'LLL', + 'sameElse' => 'L', + ], + 'ordinal' => ':number.', + 'months' => ['tera’ jar wa’', 'tera’ jar cha’', 'tera’ jar wej', 'tera’ jar loS', 'tera’ jar vagh', 'tera’ jar jav', 'tera’ jar Soch', 'tera’ jar chorgh', 'tera’ jar Hut', 'tera’ jar wa’maH', 'tera’ jar wa’maH wa’', 'tera’ jar wa’maH cha’'], + 'months_short' => ['jar wa’', 'jar cha’', 'jar wej', 'jar loS', 'jar vagh', 'jar jav', 'jar Soch', 'jar chorgh', 'jar Hut', 'jar wa’maH', 'jar wa’maH wa’', 'jar wa’maH cha’'], + 'weekdays' => ['lojmItjaj', 'DaSjaj', 'povjaj', 'ghItlhjaj', 'loghjaj', 'buqjaj', 'ghInjaj'], + 'weekdays_short' => ['lojmItjaj', 'DaSjaj', 'povjaj', 'ghItlhjaj', 'loghjaj', 'buqjaj', 'ghInjaj'], + 'weekdays_min' => ['lojmItjaj', 'DaSjaj', 'povjaj', 'ghItlhjaj', 'loghjaj', 'buqjaj', 'ghInjaj'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' ’ej '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tn.php b/vendor/nesbot/carbon/src/Carbon/Lang/tn.php new file mode 100644 index 0000000..f29bdf6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tn.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/tn_ZA.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tn_ZA.php b/vendor/nesbot/carbon/src/Carbon/Lang/tn_ZA.php new file mode 100644 index 0000000..aada7db --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tn_ZA.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Zuza Software Foundation (Translate.org.za) Dwayne Bailey dwayne@translate.org.za + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Ferikgong', 'Tlhakole', 'Mopitlwe', 'Moranang', 'Motsheganong', 'Seetebosigo', 'Phukwi', 'Phatwe', 'Lwetse', 'Diphalane', 'Ngwanatsele', 'Sedimonthole'], + 'months_short' => ['Fer', 'Tlh', 'Mop', 'Mor', 'Mot', 'See', 'Phu', 'Pha', 'Lwe', 'Dip', 'Ngw', 'Sed'], + 'weekdays' => ['laTshipi', 'Mosupologo', 'Labobedi', 'Laboraro', 'Labone', 'Labotlhano', 'Lamatlhatso'], + 'weekdays_short' => ['Tsh', 'Mos', 'Bed', 'Rar', 'Ne', 'Tlh', 'Mat'], + 'weekdays_min' => ['Tsh', 'Mos', 'Bed', 'Rar', 'Ne', 'Tlh', 'Mat'], + 'day_of_first_week_of_year' => 1, + + 'year' => 'dingwaga di le :count', + 'y' => 'dingwaga di le :count', + 'a_year' => 'dingwaga di le :count', + + 'month' => 'dikgwedi di le :count', + 'm' => 'dikgwedi di le :count', + 'a_month' => 'dikgwedi di le :count', + + 'week' => 'dibeke di le :count', + 'w' => 'dibeke di le :count', + 'a_week' => 'dibeke di le :count', + + 'day' => 'malatsi :count', + 'd' => 'malatsi :count', + 'a_day' => 'malatsi :count', + + 'hour' => 'diura di le :count', + 'h' => 'diura di le :count', + 'a_hour' => 'diura di le :count', + + 'minute' => 'metsotso e le :count', + 'min' => 'metsotso e le :count', + 'a_minute' => 'metsotso e le :count', + + 'second' => 'metsotswana e le :count', + 's' => 'metsotswana e le :count', + 'a_second' => 'metsotswana e le :count', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/to.php b/vendor/nesbot/carbon/src/Carbon/Lang/to.php new file mode 100644 index 0000000..20581bb --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/to.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/to_TO.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/to_TO.php b/vendor/nesbot/carbon/src/Carbon/Lang/to_TO.php new file mode 100644 index 0000000..335c69a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/to_TO.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - International Components for Unicode akhilesh.k@samsung.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'dddd DD MMM YYYY', + ], + 'months' => ['Sānuali', 'Fēpueli', 'Maʻasi', 'ʻEpeleli', 'Mē', 'Sune', 'Siulai', 'ʻAokosi', 'Sepitema', 'ʻOkatopa', 'Nōvema', 'Tīsema'], + 'months_short' => ['Sān', 'Fēp', 'Maʻa', 'ʻEpe', 'Mē', 'Sun', 'Siu', 'ʻAok', 'Sep', 'ʻOka', 'Nōv', 'Tīs'], + 'weekdays' => ['Sāpate', 'Mōnite', 'Tūsite', 'Pulelulu', 'Tuʻapulelulu', 'Falaite', 'Tokonaki'], + 'weekdays_short' => ['Sāp', 'Mōn', 'Tūs', 'Pul', 'Tuʻa', 'Fal', 'Tok'], + 'weekdays_min' => ['Sāp', 'Mōn', 'Tūs', 'Pul', 'Tuʻa', 'Fal', 'Tok'], + 'meridiem' => ['hengihengi', 'efiafi'], + + 'year' => ':count fitu', // less reliable + 'y' => ':count fitu', // less reliable + 'a_year' => ':count fitu', // less reliable + + 'month' => ':count mahina', // less reliable + 'm' => ':count mahina', // less reliable + 'a_month' => ':count mahina', // less reliable + + 'week' => ':count Sapate', // less reliable + 'w' => ':count Sapate', // less reliable + 'a_week' => ':count Sapate', // less reliable + + 'day' => ':count ʻaho', // less reliable + 'd' => ':count ʻaho', // less reliable + 'a_day' => ':count ʻaho', // less reliable + + 'hour' => ':count houa', + 'h' => ':count houa', + 'a_hour' => ':count houa', + + 'minute' => ':count miniti', + 'min' => ':count miniti', + 'a_minute' => ':count miniti', + + 'second' => ':count sekoni', + 's' => ':count sekoni', + 'a_second' => ':count sekoni', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tpi.php b/vendor/nesbot/carbon/src/Carbon/Lang/tpi.php new file mode 100644 index 0000000..7d38dae --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tpi.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/tpi_PG.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tpi_PG.php b/vendor/nesbot/carbon/src/Carbon/Lang/tpi_PG.php new file mode 100644 index 0000000..5f58c44 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tpi_PG.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Samsung Electronics Co., Ltd. akhilesh.k@samsung.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Janueri', 'Februeri', 'Mas', 'Epril', 'Me', 'Jun', 'Julai', 'Ogas', 'Septemba', 'Oktoba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mas', 'Epr', 'Me', 'Jun', 'Jul', 'Oga', 'Sep', 'Okt', 'Nov', 'Des'], + 'weekdays' => ['Sande', 'Mande', 'Tunde', 'Trinde', 'Fonde', 'Fraide', 'Sarere'], + 'weekdays_short' => ['San', 'Man', 'Tun', 'Tri', 'Fon', 'Fra', 'Sar'], + 'weekdays_min' => ['San', 'Man', 'Tun', 'Tri', 'Fon', 'Fra', 'Sar'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['biknait', 'apinun'], + + 'year' => 'yia :count', + 'y' => 'yia :count', + 'a_year' => 'yia :count', + + 'month' => ':count mun', + 'm' => ':count mun', + 'a_month' => ':count mun', + + 'week' => ':count wik', + 'w' => ':count wik', + 'a_week' => ':count wik', + + 'day' => ':count de', + 'd' => ':count de', + 'a_day' => ':count de', + + 'hour' => ':count aua', + 'h' => ':count aua', + 'a_hour' => ':count aua', + + 'minute' => ':count minit', + 'min' => ':count minit', + 'a_minute' => ':count minit', + + 'second' => ':count namba tu', + 's' => ':count namba tu', + 'a_second' => ':count namba tu', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tr.php b/vendor/nesbot/carbon/src/Carbon/Lang/tr.php new file mode 100644 index 0000000..f5d9f4c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tr.php @@ -0,0 +1,121 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - Alan Agius + * - Erhan Gundogan + * - François B + * - JD Isaacks + * - Murat Yüksel + * - Baran Şengül + * - Selami (selamialtin) + * - TeomanBey + */ +return [ + 'year' => ':count yıl', + 'a_year' => '{1}bir yıl|]1,Inf[:count yıl', + 'y' => ':county', + 'month' => ':count ay', + 'a_month' => '{1}bir ay|]1,Inf[:count ay', + 'm' => ':countay', + 'week' => ':count hafta', + 'a_week' => '{1}bir hafta|]1,Inf[:count hafta', + 'w' => ':counth', + 'day' => ':count gün', + 'a_day' => '{1}bir gün|]1,Inf[:count gün', + 'd' => ':countg', + 'hour' => ':count saat', + 'a_hour' => '{1}bir saat|]1,Inf[:count saat', + 'h' => ':countsa', + 'minute' => ':count dakika', + 'a_minute' => '{1}bir dakika|]1,Inf[:count dakika', + 'min' => ':countdk', + 'second' => ':count saniye', + 'a_second' => '{1}birkaç saniye|]1,Inf[:count saniye', + 's' => ':countsn', + 'ago' => ':time önce', + 'from_now' => ':time sonra', + 'after' => ':time sonra', + 'before' => ':time önce', + 'diff_now' => 'şimdi', + 'diff_today' => 'bugün', + 'diff_today_regexp' => 'bugün(?:\\s+saat)?', + 'diff_yesterday' => 'dün', + 'diff_tomorrow' => 'yarın', + 'diff_tomorrow_regexp' => 'yarın(?:\\s+saat)?', + 'diff_before_yesterday' => 'evvelsi gün', + 'diff_after_tomorrow' => 'öbür gün', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[bugün saat] LT', + 'nextDay' => '[yarın saat] LT', + 'nextWeek' => '[gelecek] dddd [saat] LT', + 'lastDay' => '[dün] LT', + 'lastWeek' => '[geçen] dddd [saat] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + switch ($period) { + case 'd': + case 'D': + case 'Do': + case 'DD': + return $number; + default: + if ($number === 0) { // special case for zero + return "$number'ıncı"; + } + + static $suffixes = [ + 1 => '\'inci', + 5 => '\'inci', + 8 => '\'inci', + 70 => '\'inci', + 80 => '\'inci', + 2 => '\'nci', + 7 => '\'nci', + 20 => '\'nci', + 50 => '\'nci', + 3 => '\'üncü', + 4 => '\'üncü', + 100 => '\'üncü', + 6 => '\'ncı', + 9 => '\'uncu', + 10 => '\'uncu', + 30 => '\'uncu', + 60 => '\'ıncı', + 90 => '\'ıncı', + ]; + + $lastDigit = $number % 10; + + return $number.($suffixes[$lastDigit] ?? $suffixes[$number % 100 - $lastDigit] ?? $suffixes[$number >= 100 ? 100 : -1] ?? ''); + } + }, + 'meridiem' => ['ÖÖ', 'ÖS', 'öö', 'ös'], + 'months' => ['Ocak', 'Şubat', 'Mart', 'Nisan', 'Mayıs', 'Haziran', 'Temmuz', 'Ağustos', 'Eylül', 'Ekim', 'Kasım', 'Aralık'], + 'months_short' => ['Oca', 'Şub', 'Mar', 'Nis', 'May', 'Haz', 'Tem', 'Ağu', 'Eyl', 'Eki', 'Kas', 'Ara'], + 'weekdays' => ['Pazar', 'Pazartesi', 'Salı', 'Çarşamba', 'Perşembe', 'Cuma', 'Cumartesi'], + 'weekdays_short' => ['Paz', 'Pts', 'Sal', 'Çar', 'Per', 'Cum', 'Cts'], + 'weekdays_min' => ['Pz', 'Pt', 'Sa', 'Ça', 'Pe', 'Cu', 'Ct'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' ve '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tr_CY.php b/vendor/nesbot/carbon/src/Carbon/Lang/tr_CY.php new file mode 100644 index 0000000..23f1144 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tr_CY.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/tr.php', [ + 'weekdays_short' => ['Paz', 'Pzt', 'Sal', 'Çar', 'Per', 'Cum', 'Cmt'], + 'weekdays_min' => ['Pa', 'Pt', 'Sa', 'Ça', 'Pe', 'Cu', 'Ct'], + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'D.MM.YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'D MMMM YYYY dddd h:mm a', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tr_TR.php b/vendor/nesbot/carbon/src/Carbon/Lang/tr_TR.php new file mode 100644 index 0000000..9e99482 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tr_TR.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/tr.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ts.php b/vendor/nesbot/carbon/src/Carbon/Lang/ts.php new file mode 100644 index 0000000..525736b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ts.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ts_ZA.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ts_ZA.php b/vendor/nesbot/carbon/src/Carbon/Lang/ts_ZA.php new file mode 100644 index 0000000..37a24ec --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ts_ZA.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Zuza Software Foundation (Translate.org.za) Dwayne Bailey dwayne@translate.org.za + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Sunguti', 'Nyenyenyani', 'Nyenyankulu', 'Dzivamisoko', 'Mudyaxihi', 'Khotavuxika', 'Mawuwani', 'Mhawuri', 'Ndzhati', 'Nhlangula', 'Hukuri', 'N\'wendzamhala'], + 'months_short' => ['Sun', 'Yan', 'Kul', 'Dzi', 'Mud', 'Kho', 'Maw', 'Mha', 'Ndz', 'Nhl', 'Huk', 'N\'w'], + 'weekdays' => ['Sonto', 'Musumbhunuku', 'Ravumbirhi', 'Ravunharhu', 'Ravumune', 'Ravuntlhanu', 'Mugqivela'], + 'weekdays_short' => ['Son', 'Mus', 'Bir', 'Har', 'Ne', 'Tlh', 'Mug'], + 'weekdays_min' => ['Son', 'Mus', 'Bir', 'Har', 'Ne', 'Tlh', 'Mug'], + 'day_of_first_week_of_year' => 1, + + 'year' => 'malembe ya :count', + 'y' => 'malembe ya :count', + 'a_year' => 'malembe ya :count', + + 'month' => 'tin’hweti ta :count', + 'm' => 'tin’hweti ta :count', + 'a_month' => 'tin’hweti ta :count', + + 'week' => 'mavhiki ya :count', + 'w' => 'mavhiki ya :count', + 'a_week' => 'mavhiki ya :count', + + 'day' => 'masiku :count', + 'd' => 'masiku :count', + 'a_day' => 'masiku :count', + + 'hour' => 'tiawara ta :count', + 'h' => 'tiawara ta :count', + 'a_hour' => 'tiawara ta :count', + + 'minute' => 'timinete ta :count', + 'min' => 'timinete ta :count', + 'a_minute' => 'timinete ta :count', + + 'second' => 'tisekoni ta :count', + 's' => 'tisekoni ta :count', + 'a_second' => 'tisekoni ta :count', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tt.php b/vendor/nesbot/carbon/src/Carbon/Lang/tt.php new file mode 100644 index 0000000..d67d896 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tt.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/tt_RU.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tt_RU.php b/vendor/nesbot/carbon/src/Carbon/Lang/tt_RU.php new file mode 100644 index 0000000..38e42d0 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tt_RU.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Rinat Norkin Pablo Saratxaga, Rinat Norkin pablo@mandrakesoft.com, rinat@taif.ru + */ +return [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'DD MMM, HH:mm', + 'LLLL' => 'DD MMMM YYYY, HH:mm', + ], + 'months' => ['января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря'], + 'months_short' => ['янв', 'фев', 'мар', 'апр', 'май', 'июн', 'июл', 'авг', 'сен', 'окт', 'ноя', 'дек'], + 'weekdays' => ['якшәмбе', 'дышәмбе', 'сишәмбе', 'чәршәәмбе', 'пәнҗешмбе', 'җомга', 'шимбә'], + 'weekdays_short' => ['якш', 'дыш', 'сиш', 'чәрш', 'пәнҗ', 'җом', 'шим'], + 'weekdays_min' => ['якш', 'дыш', 'сиш', 'чәрш', 'пәнҗ', 'җом', 'шим'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'year' => ':count ел', + 'month' => ':count ай', + 'week' => ':count атна', + 'day' => ':count көн', + 'hour' => ':count сәгать', + 'minute' => ':count минут', + 'second' => ':count секунд', +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tt_RU@iqtelif.php b/vendor/nesbot/carbon/src/Carbon/Lang/tt_RU@iqtelif.php new file mode 100644 index 0000000..16b8efb --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tt_RU@iqtelif.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Reshat Sabiq tatar.iqtelif.i18n@gmail.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD.MM.YYYY', + ], + 'months' => ['Ğınwar', 'Fiwral\'', 'Mart', 'April', 'May', 'Yün', 'Yül', 'Awgust', 'Sintebír', 'Üktebír', 'Noyebír', 'Dikebír'], + 'months_short' => ['Ğın', 'Fiw', 'Mar', 'Apr', 'May', 'Yün', 'Yül', 'Awg', 'Sin', 'Ükt', 'Noy', 'Dik'], + 'weekdays' => ['Yekşembí', 'Düşembí', 'Sişembí', 'Çerşembí', 'Pencíşembí', 'Comğa', 'Şimbe'], + 'weekdays_short' => ['Yek', 'Düş', 'Siş', 'Çer', 'Pen', 'Com', 'Şim'], + 'weekdays_min' => ['Yek', 'Düş', 'Siş', 'Çer', 'Pen', 'Com', 'Şim'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ÖA', 'ÖS'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/twq.php b/vendor/nesbot/carbon/src/Carbon/Lang/twq.php new file mode 100644 index 0000000..5cbb46e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/twq.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/ses.php', [ + 'meridiem' => ['Subbaahi', 'Zaarikay b'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tzl.php b/vendor/nesbot/carbon/src/Carbon/Lang/tzl.php new file mode 100644 index 0000000..50bf26d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tzl.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'year' => '[0,1]:count ar|:count ars', + 'y' => '[0,1]:count ar|:count ars', + 'month' => '[0,1]:count mes|:count mesen', + 'm' => '[0,1]:count mes|:count mesen', + 'week' => '[0,1]:count seifetziua|:count seifetziuas', + 'w' => '[0,1]:count seifetziua|:count seifetziuas', + 'day' => '[0,1]:count ziua|:count ziuas', + 'd' => '[0,1]:count ziua|:count ziuas', + 'hour' => '[0,1]:count þora|:count þoras', + 'h' => '[0,1]:count þora|:count þoras', + 'minute' => '[0,1]:count míut|:count míuts', + 'min' => '[0,1]:count míut|:count míuts', + 'second' => ':count secunds', + 's' => ':count secunds', + + 'ago' => 'ja :time', + 'from_now' => 'osprei :time', + + 'diff_yesterday' => 'ieiri', + 'diff_yesterday_regexp' => 'ieiri(?:\\s+à)?', + 'diff_today' => 'oxhi', + 'diff_today_regexp' => 'oxhi(?:\\s+à)?', + 'diff_tomorrow' => 'demà', + 'diff_tomorrow_regexp' => 'demà(?:\\s+à)?', + + 'formats' => [ + 'LT' => 'HH.mm', + 'LTS' => 'HH.mm.ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D. MMMM [dallas] YYYY', + 'LLL' => 'D. MMMM [dallas] YYYY HH.mm', + 'LLLL' => 'dddd, [li] D. MMMM [dallas] YYYY HH.mm', + ], + + 'calendar' => [ + 'sameDay' => '[oxhi à] LT', + 'nextDay' => '[demà à] LT', + 'nextWeek' => 'dddd [à] LT', + 'lastDay' => '[ieiri à] LT', + 'lastWeek' => '[sür el] dddd [lasteu à] LT', + 'sameElse' => 'L', + ], + + 'meridiem' => ["D'A", "D'O"], + 'months' => ['Januar', 'Fevraglh', 'Març', 'Avrïu', 'Mai', 'Gün', 'Julia', 'Guscht', 'Setemvar', 'Listopäts', 'Noemvar', 'Zecemvar'], + 'months_short' => ['Jan', 'Fev', 'Mar', 'Avr', 'Mai', 'Gün', 'Jul', 'Gus', 'Set', 'Lis', 'Noe', 'Zec'], + 'weekdays' => ['Súladi', 'Lúneçi', 'Maitzi', 'Márcuri', 'Xhúadi', 'Viénerçi', 'Sáturi'], + 'weekdays_short' => ['Súl', 'Lún', 'Mai', 'Már', 'Xhú', 'Vié', 'Sát'], + 'weekdays_min' => ['Sú', 'Lú', 'Ma', 'Má', 'Xh', 'Vi', 'Sá'], + 'ordinal' => ':number.', + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tzm.php b/vendor/nesbot/carbon/src/Carbon/Lang/tzm.php new file mode 100644 index 0000000..2a1a0f2 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tzm.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - JD Isaacks + */ +return [ + 'year' => '{1}ⴰⵙⴳⴰⵙ|:count ⵉⵙⴳⴰⵙⵏ', + 'month' => '{1}ⴰⵢoⵓⵔ|:count ⵉⵢⵢⵉⵔⵏ', + 'week' => ':count ⵉⵎⴰⵍⴰⵙⵙ', + 'day' => '{1}ⴰⵙⵙ|:count oⵙⵙⴰⵏ', + 'hour' => '{1}ⵙⴰⵄⴰ|:count ⵜⴰⵙⵙⴰⵄⵉⵏ', + 'minute' => '{1}ⵎⵉⵏⵓⴺ|:count ⵎⵉⵏⵓⴺ', + 'second' => '{1}ⵉⵎⵉⴽ|:count ⵉⵎⵉⴽ', + 'ago' => 'ⵢⴰⵏ :time', + 'from_now' => 'ⴷⴰⴷⵅ ⵙ ⵢⴰⵏ :time', + 'diff_today' => 'ⴰⵙⴷⵅ', + 'diff_yesterday' => 'ⴰⵚⴰⵏⵜ', + 'diff_yesterday_regexp' => 'ⴰⵚⴰⵏⵜ(?:\\s+ⴴ)?', + 'diff_tomorrow' => 'ⴰⵙⴽⴰ', + 'diff_tomorrow_regexp' => 'ⴰⵙⴽⴰ(?:\\s+ⴴ)?', + 'diff_today_regexp' => 'ⴰⵙⴷⵅ(?:\\s+ⴴ)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[ⴰⵙⴷⵅ ⴴ] LT', + 'nextDay' => '[ⴰⵙⴽⴰ ⴴ] LT', + 'nextWeek' => 'dddd [ⴴ] LT', + 'lastDay' => '[ⴰⵚⴰⵏⵜ ⴴ] LT', + 'lastWeek' => 'dddd [ⴴ] LT', + 'sameElse' => 'L', + ], + 'months' => ['ⵉⵏⵏⴰⵢⵔ', 'ⴱⵕⴰⵢⵕ', 'ⵎⴰⵕⵚ', 'ⵉⴱⵔⵉⵔ', 'ⵎⴰⵢⵢⵓ', 'ⵢⵓⵏⵢⵓ', 'ⵢⵓⵍⵢⵓⵣ', 'ⵖⵓⵛⵜ', 'ⵛⵓⵜⴰⵏⴱⵉⵔ', 'ⴽⵟⵓⴱⵕ', 'ⵏⵓⵡⴰⵏⴱⵉⵔ', 'ⴷⵓⵊⵏⴱⵉⵔ'], + 'months_short' => ['ⵉⵏⵏⴰⵢⵔ', 'ⴱⵕⴰⵢⵕ', 'ⵎⴰⵕⵚ', 'ⵉⴱⵔⵉⵔ', 'ⵎⴰⵢⵢⵓ', 'ⵢⵓⵏⵢⵓ', 'ⵢⵓⵍⵢⵓⵣ', 'ⵖⵓⵛⵜ', 'ⵛⵓⵜⴰⵏⴱⵉⵔ', 'ⴽⵟⵓⴱⵕ', 'ⵏⵓⵡⴰⵏⴱⵉⵔ', 'ⴷⵓⵊⵏⴱⵉⵔ'], + 'weekdays' => ['ⴰⵙⴰⵎⴰⵙ', 'ⴰⵢⵏⴰⵙ', 'ⴰⵙⵉⵏⴰⵙ', 'ⴰⴽⵔⴰⵙ', 'ⴰⴽⵡⴰⵙ', 'ⴰⵙⵉⵎⵡⴰⵙ', 'ⴰⵙⵉⴹⵢⴰⵙ'], + 'weekdays_short' => ['ⴰⵙⴰⵎⴰⵙ', 'ⴰⵢⵏⴰⵙ', 'ⴰⵙⵉⵏⴰⵙ', 'ⴰⴽⵔⴰⵙ', 'ⴰⴽⵡⴰⵙ', 'ⴰⵙⵉⵎⵡⴰⵙ', 'ⴰⵙⵉⴹⵢⴰⵙ'], + 'weekdays_min' => ['ⴰⵙⴰⵎⴰⵙ', 'ⴰⵢⵏⴰⵙ', 'ⴰⵙⵉⵏⴰⵙ', 'ⴰⴽⵔⴰⵙ', 'ⴰⴽⵡⴰⵙ', 'ⴰⵙⵉⵎⵡⴰⵙ', 'ⴰⵙⵉⴹⵢⴰⵙ'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, + 'weekend' => [5, 6], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/tzm_Latn.php b/vendor/nesbot/carbon/src/Carbon/Lang/tzm_Latn.php new file mode 100644 index 0000000..5840d20 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/tzm_Latn.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - JD Isaacks + */ +return [ + 'year' => '{1}:count asgas|:count isgasn', + 'a_year' => 'asgas|:count isgasn', + 'month' => '{1}:count ayowr|:count iyyirn', + 'a_month' => 'ayowr|:count iyyirn', + 'week' => ':count imalass', + 'a_week' => ':imalass', + 'day' => '{1}:count ass|:count ossan', + 'a_day' => 'ass|:count ossan', + 'hour' => '{1}:count saɛa|:count tassaɛin', + 'a_hour' => '{1}saɛa|:count tassaɛin', + 'minute' => ':count minuḍ', + 'a_minute' => '{1}minuḍ|:count minuḍ', + 'second' => ':count imik', + 'a_second' => '{1}imik|:count imik', + 'ago' => 'yan :time', + 'from_now' => 'dadkh s yan :time', + 'diff_yesterday' => 'assant', + 'diff_yesterday_regexp' => 'assant(?:\\s+g)?', + 'diff_today' => 'asdkh', + 'diff_today_regexp' => 'asdkh(?:\\s+g)?', + 'diff_tomorrow' => 'aska', + 'diff_tomorrow_regexp' => 'aska(?:\\s+g)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[asdkh g] LT', + 'nextDay' => '[aska g] LT', + 'nextWeek' => 'dddd [g] LT', + 'lastDay' => '[assant g] LT', + 'lastWeek' => 'dddd [g] LT', + 'sameElse' => 'L', + ], + 'months' => ['innayr', 'brˤayrˤ', 'marˤsˤ', 'ibrir', 'mayyw', 'ywnyw', 'ywlywz', 'ɣwšt', 'šwtanbir', 'ktˤwbrˤ', 'nwwanbir', 'dwjnbir'], + 'months_short' => ['innayr', 'brˤayrˤ', 'marˤsˤ', 'ibrir', 'mayyw', 'ywnyw', 'ywlywz', 'ɣwšt', 'šwtanbir', 'ktˤwbrˤ', 'nwwanbir', 'dwjnbir'], + 'weekdays' => ['asamas', 'aynas', 'asinas', 'akras', 'akwas', 'asimwas', 'asiḍyas'], + 'weekdays_short' => ['asamas', 'aynas', 'asinas', 'akras', 'akwas', 'asimwas', 'asiḍyas'], + 'weekdays_min' => ['asamas', 'aynas', 'asinas', 'akras', 'akwas', 'asimwas', 'asiḍyas'], + 'meridiem' => ['Zdat azal', 'Ḍeffir aza'], + 'first_day_of_week' => 6, + 'day_of_first_week_of_year' => 1, +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ug.php b/vendor/nesbot/carbon/src/Carbon/Lang/ug.php new file mode 100644 index 0000000..259b99a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ug.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Philippe Vaucher + * - Tsutomu Kuroda + * - yasinn + */ +return [ + 'year' => '{1}'.'بىر يىل'.'|:count '.'يىل', + 'month' => '{1}'.'بىر ئاي'.'|:count '.'ئاي', + 'week' => '{1}'.'بىر ھەپتە'.'|:count '.'ھەپتە', + 'day' => '{1}'.'بىر كۈن'.'|:count '.'كۈن', + 'hour' => '{1}'.'بىر سائەت'.'|:count '.'سائەت', + 'minute' => '{1}'.'بىر مىنۇت'.'|:count '.'مىنۇت', + 'second' => '{1}'.'نەچچە سېكونت'.'|:count '.'سېكونت', + 'ago' => ':time بۇرۇن', + 'from_now' => ':time كېيىن', + 'diff_today' => 'بۈگۈن', + 'diff_yesterday' => 'تۆنۈگۈن', + 'diff_tomorrow' => 'ئەتە', + 'diff_tomorrow_regexp' => 'ئەتە(?:\\s+سائەت)?', + 'diff_today_regexp' => 'بۈگۈن(?:\\s+سائەت)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-DD', + 'LL' => 'YYYY-يىلىM-ئاينىڭD-كۈنى', + 'LLL' => 'YYYY-يىلىM-ئاينىڭD-كۈنى، HH:mm', + 'LLLL' => 'dddd، YYYY-يىلىM-ئاينىڭD-كۈنى، HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[بۈگۈن سائەت] LT', + 'nextDay' => '[ئەتە سائەت] LT', + 'nextWeek' => '[كېلەركى] dddd [سائەت] LT', + 'lastDay' => '[تۆنۈگۈن] LT', + 'lastWeek' => '[ئالدىنقى] dddd [سائەت] LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + switch ($period) { + case 'd': + case 'D': + case 'DDD': + return $number.'-كۈنى'; + case 'w': + case 'W': + return $number.'-ھەپتە'; + default: + return $number; + } + }, + 'meridiem' => function ($hour, $minute) { + $time = $hour * 100 + $minute; + if ($time < 600) { + return 'يېرىم كېچە'; + } + if ($time < 900) { + return 'سەھەر'; + } + if ($time < 1130) { + return 'چۈشتىن بۇرۇن'; + } + if ($time < 1230) { + return 'چۈش'; + } + if ($time < 1800) { + return 'چۈشتىن كېيىن'; + } + + return 'كەچ'; + }, + 'months' => ['يانۋار', 'فېۋرال', 'مارت', 'ئاپرېل', 'ماي', 'ئىيۇن', 'ئىيۇل', 'ئاۋغۇست', 'سېنتەبىر', 'ئۆكتەبىر', 'نويابىر', 'دېكابىر'], + 'months_short' => ['يانۋار', 'فېۋرال', 'مارت', 'ئاپرېل', 'ماي', 'ئىيۇن', 'ئىيۇل', 'ئاۋغۇست', 'سېنتەبىر', 'ئۆكتەبىر', 'نويابىر', 'دېكابىر'], + 'weekdays' => ['يەكشەنبە', 'دۈشەنبە', 'سەيشەنبە', 'چارشەنبە', 'پەيشەنبە', 'جۈمە', 'شەنبە'], + 'weekdays_short' => ['يە', 'دۈ', 'سە', 'چا', 'پە', 'جۈ', 'شە'], + 'weekdays_min' => ['يە', 'دۈ', 'سە', 'چا', 'پە', 'جۈ', 'شە'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' ۋە '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ug_CN.php b/vendor/nesbot/carbon/src/Carbon/Lang/ug_CN.php new file mode 100644 index 0000000..deb828c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ug_CN.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Kunal Marwaha + * - Alim Boyaq + */ +return require __DIR__.'/ug.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/uk.php b/vendor/nesbot/carbon/src/Carbon/Lang/uk.php new file mode 100644 index 0000000..b267b00 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/uk.php @@ -0,0 +1,211 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Carbon\CarbonInterface; + +$processHoursFunction = function (CarbonInterface $date, string $format) { + return $format.'о'.($date->hour === 11 ? 'б' : '').'] LT'; +}; + +/* + * Authors: + * - Kunal Marwaha + * - Josh Soref + * - François B + * - Tim Fish + * - Serhan Apaydın + * - Max Mykhailenko + * - JD Isaacks + * - Max Kovpak + * - AucT + * - Philippe Vaucher + * - Ilya Shaplyko + * - Vadym Ievsieiev + * - Denys Kurets + * - Igor Kasyanchuk + * - Tsutomu Kuroda + * - tjku + * - Max Melentiev + * - Oleh + * - epaminond + * - Juanito Fatas + * - Vitalii Khustochka + * - Akira Matsuda + * - Christopher Dell + * - Enrique Vidal + * - Simone Carletti + * - Aaron Patterson + * - Andriy Tyurnikov + * - Nicolás Hock Isaza + * - Iwakura Taro + * - Andrii Ponomarov + * - alecrabbit + * - vystepanenko + * - AlexWalkerson + * - Andre Havryliuk (Andrend) + * - Max Datsenko (datsenko-md) + */ +return [ + 'year' => ':count рік|:count роки|:count років', + 'y' => ':countр', + 'a_year' => '{1}рік|:count рік|:count роки|:count років', + 'month' => ':count місяць|:count місяці|:count місяців', + 'm' => ':countм', + 'a_month' => '{1}місяць|:count місяць|:count місяці|:count місяців', + 'week' => ':count тиждень|:count тижні|:count тижнів', + 'w' => ':countт', + 'a_week' => '{1}тиждень|:count тиждень|:count тижні|:count тижнів', + 'day' => ':count день|:count дні|:count днів', + 'd' => ':countд', + 'a_day' => '{1}день|:count день|:count дні|:count днів', + 'hour' => ':count година|:count години|:count годин', + 'h' => ':countг', + 'a_hour' => '{1}година|:count година|:count години|:count годин', + 'minute' => ':count хвилина|:count хвилини|:count хвилин', + 'min' => ':countхв', + 'a_minute' => '{1}хвилина|:count хвилина|:count хвилини|:count хвилин', + 'second' => ':count секунда|:count секунди|:count секунд', + 's' => ':countсек', + 'a_second' => '{1}декілька секунд|:count секунда|:count секунди|:count секунд', + + 'hour_ago' => ':count годину|:count години|:count годин', + 'a_hour_ago' => '{1}годину|:count годину|:count години|:count годин', + 'minute_ago' => ':count хвилину|:count хвилини|:count хвилин', + 'a_minute_ago' => '{1}хвилину|:count хвилину|:count хвилини|:count хвилин', + 'second_ago' => ':count секунду|:count секунди|:count секунд', + 'a_second_ago' => '{1}декілька секунд|:count секунду|:count секунди|:count секунд', + + 'hour_from_now' => ':count годину|:count години|:count годин', + 'a_hour_from_now' => '{1}годину|:count годину|:count години|:count годин', + 'minute_from_now' => ':count хвилину|:count хвилини|:count хвилин', + 'a_minute_from_now' => '{1}хвилину|:count хвилину|:count хвилини|:count хвилин', + 'second_from_now' => ':count секунду|:count секунди|:count секунд', + 'a_second_from_now' => '{1}декілька секунд|:count секунду|:count секунди|:count секунд', + + 'hour_after' => ':count годину|:count години|:count годин', + 'a_hour_after' => '{1}годину|:count годину|:count години|:count годин', + 'minute_after' => ':count хвилину|:count хвилини|:count хвилин', + 'a_minute_after' => '{1}хвилину|:count хвилину|:count хвилини|:count хвилин', + 'second_after' => ':count секунду|:count секунди|:count секунд', + 'a_second_after' => '{1}декілька секунд|:count секунду|:count секунди|:count секунд', + + 'hour_before' => ':count годину|:count години|:count годин', + 'a_hour_before' => '{1}годину|:count годину|:count години|:count годин', + 'minute_before' => ':count хвилину|:count хвилини|:count хвилин', + 'a_minute_before' => '{1}хвилину|:count хвилину|:count хвилини|:count хвилин', + 'second_before' => ':count секунду|:count секунди|:count секунд', + 'a_second_before' => '{1}декілька секунд|:count секунду|:count секунди|:count секунд', + + 'ago' => ':time тому', + 'from_now' => 'за :time', + 'after' => ':time після', + 'before' => ':time до', + 'diff_now' => 'щойно', + 'diff_today' => 'Сьогодні', + 'diff_today_regexp' => 'Сьогодні(?:\\s+о)?', + 'diff_yesterday' => 'вчора', + 'diff_yesterday_regexp' => 'Вчора(?:\\s+о)?', + 'diff_tomorrow' => 'завтра', + 'diff_tomorrow_regexp' => 'Завтра(?:\\s+о)?', + 'diff_before_yesterday' => 'позавчора', + 'diff_after_tomorrow' => 'післязавтра', + 'period_recurrences' => 'один раз|:count рази|:count разів', + 'period_interval' => 'кожні :interval', + 'period_start_date' => 'з :date', + 'period_end_date' => 'до :date', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY, HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY, HH:mm', + ], + 'calendar' => [ + 'sameDay' => function (CarbonInterface $date) use ($processHoursFunction) { + return $processHoursFunction($date, '[Сьогодні '); + }, + 'nextDay' => function (CarbonInterface $date) use ($processHoursFunction) { + return $processHoursFunction($date, '[Завтра '); + }, + 'nextWeek' => function (CarbonInterface $date) use ($processHoursFunction) { + return $processHoursFunction($date, '[У] dddd ['); + }, + 'lastDay' => function (CarbonInterface $date) use ($processHoursFunction) { + return $processHoursFunction($date, '[Вчора '); + }, + 'lastWeek' => function (CarbonInterface $date) use ($processHoursFunction) { + switch ($date->dayOfWeek) { + case 0: + case 3: + case 5: + case 6: + return $processHoursFunction($date, '[Минулої] dddd ['); + default: + return $processHoursFunction($date, '[Минулого] dddd ['); + } + }, + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + switch ($period) { + case 'M': + case 'd': + case 'DDD': + case 'w': + case 'W': + return $number.'-й'; + case 'D': + return $number.'-го'; + default: + return $number; + } + }, + 'meridiem' => function ($hour) { + if ($hour < 4) { + return 'ночі'; + } + if ($hour < 12) { + return 'ранку'; + } + if ($hour < 17) { + return 'дня'; + } + + return 'вечора'; + }, + 'months' => ['січня', 'лютого', 'березня', 'квітня', 'травня', 'червня', 'липня', 'серпня', 'вересня', 'жовтня', 'листопада', 'грудня'], + 'months_standalone' => ['січень', 'лютий', 'березень', 'квітень', 'травень', 'червень', 'липень', 'серпень', 'вересень', 'жовтень', 'листопад', 'грудень'], + 'months_short' => ['січ', 'лют', 'бер', 'кві', 'тра', 'чер', 'лип', 'сер', 'вер', 'жов', 'лис', 'гру'], + 'months_regexp' => '/(D[oD]?(\[[^\[\]]*\]|\s)+MMMM?|L{2,4}|l{2,4})/', + 'weekdays' => function (CarbonInterface $date, $format, $index) { + static $words = [ + 'nominative' => ['неділя', 'понеділок', 'вівторок', 'середа', 'четвер', 'п’ятниця', 'субота'], + 'accusative' => ['неділю', 'понеділок', 'вівторок', 'середу', 'четвер', 'п’ятницю', 'суботу'], + 'genitive' => ['неділі', 'понеділка', 'вівторка', 'середи', 'четверга', 'п’ятниці', 'суботи'], + ]; + + $nounCase = preg_match('/(\[(В|в|У|у)\])\s+dddd/u', $format) + ? 'accusative' + : ( + preg_match('/\[?(?:минулої|наступної)?\s*\]\s+dddd/u', $format) + ? 'genitive' + : 'nominative' + ); + + return $words[$nounCase][$index] ?? null; + }, + 'weekdays_short' => ['нд', 'пн', 'вт', 'ср', 'чт', 'пт', 'сб'], + 'weekdays_min' => ['нд', 'пн', 'вт', 'ср', 'чт', 'пт', 'сб'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' i '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/uk_UA.php b/vendor/nesbot/carbon/src/Carbon/Lang/uk_UA.php new file mode 100644 index 0000000..bd11d86 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/uk_UA.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/uk.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/unm.php b/vendor/nesbot/carbon/src/Carbon/Lang/unm.php new file mode 100644 index 0000000..d3f19f0 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/unm.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/unm_US.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/unm_US.php b/vendor/nesbot/carbon/src/Carbon/Lang/unm_US.php new file mode 100644 index 0000000..fa5c374 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/unm_US.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['enikwsi', 'chkwali', 'xamokhwite', 'kwetayoxe', 'tainipen', 'kichinipen', 'lainipen', 'winaminke', 'kichitahkok', 'puksit', 'wini', 'muxkotae'], + 'months_short' => ['eni', 'chk', 'xam', 'kwe', 'tai', 'nip', 'lai', 'win', 'tah', 'puk', 'kun', 'mux'], + 'weekdays' => ['kentuwei', 'manteke', 'tusteke', 'lelai', 'tasteke', 'pelaiteke', 'sateteke'], + 'weekdays_short' => ['ken', 'man', 'tus', 'lel', 'tas', 'pel', 'sat'], + 'weekdays_min' => ['ken', 'man', 'tus', 'lel', 'tas', 'pel', 'sat'], + 'day_of_first_week_of_year' => 1, + + // Too unreliable + /* + 'year' => ':count kaxtëne', + 'y' => ':count kaxtëne', + 'a_year' => ':count kaxtëne', + + 'month' => ':count piskewëni kishux', // less reliable + 'm' => ':count piskewëni kishux', // less reliable + 'a_month' => ':count piskewëni kishux', // less reliable + + 'week' => ':count kishku', // less reliable + 'w' => ':count kishku', // less reliable + 'a_week' => ':count kishku', // less reliable + + 'day' => ':count kishku', + 'd' => ':count kishku', + 'a_day' => ':count kishku', + + 'hour' => ':count xkuk', // less reliable + 'h' => ':count xkuk', // less reliable + 'a_hour' => ':count xkuk', // less reliable + + 'minute' => ':count txituwàk', // less reliable + 'min' => ':count txituwàk', // less reliable + 'a_minute' => ':count txituwàk', // less reliable + + 'second' => ':count nisha', // less reliable + 's' => ':count nisha', // less reliable + 'a_second' => ':count nisha', // less reliable + */ +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ur.php b/vendor/nesbot/carbon/src/Carbon/Lang/ur.php new file mode 100644 index 0000000..dc16c2c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ur.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +$months = [ + 'جنوری', + 'فروری', + 'مارچ', + 'اپریل', + 'مئی', + 'جون', + 'جولائی', + 'اگست', + 'ستمبر', + 'اکتوبر', + 'نومبر', + 'دسمبر', +]; + +$weekdays = [ + 'اتوار', + 'پیر', + 'منگل', + 'بدھ', + 'جمعرات', + 'جمعہ', + 'ہفتہ', +]; + +/* + * Authors: + * - Sawood Alam + * - Mehshan + * - Philippe Vaucher + * - Tsutomu Kuroda + * - tjku + * - Zaid Akram + * - Max Melentiev + * - hafezdivandari + * - Hossein Jabbari + * - nimamo + */ +return [ + 'year' => 'ایک سال|:count سال', + 'month' => 'ایک ماہ|:count ماہ', + 'week' => ':count ہفتے', + 'day' => 'ایک دن|:count دن', + 'hour' => 'ایک گھنٹہ|:count گھنٹے', + 'minute' => 'ایک منٹ|:count منٹ', + 'second' => 'چند سیکنڈ|:count سیکنڈ', + 'ago' => ':time قبل', + 'from_now' => ':time بعد', + 'after' => ':time بعد', + 'before' => ':time پہلے', + 'diff_now' => 'اب', + 'diff_today' => 'آج', + 'diff_today_regexp' => 'آج(?:\\s+بوقت)?', + 'diff_yesterday' => 'گزشتہ کل', + 'diff_yesterday_regexp' => 'گذشتہ(?:\\s+روز)?(?:\\s+بوقت)?', + 'diff_tomorrow' => 'آئندہ کل', + 'diff_tomorrow_regexp' => 'کل(?:\\s+بوقت)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd، D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[آج بوقت] LT', + 'nextDay' => '[کل بوقت] LT', + 'nextWeek' => 'dddd [بوقت] LT', + 'lastDay' => '[گذشتہ روز بوقت] LT', + 'lastWeek' => '[گذشتہ] dddd [بوقت] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['صبح', 'شام'], + 'months' => $months, + 'months_short' => $months, + 'weekdays' => $weekdays, + 'weekdays_short' => $weekdays, + 'weekdays_min' => $weekdays, + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => ['، ', ' اور '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ur_IN.php b/vendor/nesbot/carbon/src/Carbon/Lang/ur_IN.php new file mode 100644 index 0000000..f81c84d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ur_IN.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Red Hat, Pune bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/ur.php', [ + 'formats' => [ + 'L' => 'D/M/YY', + ], + 'months' => ['جنوری', 'فروری', 'مارچ', 'اپریل', 'مئی', 'جون', 'جولائی', 'اگست', 'ستمبر', 'اکتوبر', 'نومبر', 'دسمبر'], + 'months_short' => ['جنوری', 'فروری', 'مارچ', 'اپریل', 'مئی', 'جون', 'جولائی', 'اگست', 'ستمبر', 'اکتوبر', 'نومبر', 'دسمبر'], + 'weekdays' => ['اتوار', 'پیر', 'منگل', 'بدھ', 'جمعرات', 'جمعہ', 'سنیچر'], + 'weekdays_short' => ['اتوار', 'پیر', 'منگل', 'بدھ', 'جمعرات', 'جمعہ', 'سنیچر'], + 'weekdays_min' => ['اتوار', 'پیر', 'منگل', 'بدھ', 'جمعرات', 'جمعہ', 'سنیچر'], + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ur_PK.php b/vendor/nesbot/carbon/src/Carbon/Lang/ur_PK.php new file mode 100644 index 0000000..8cd593d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ur_PK.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/ur.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['جنوری', 'فروری', 'مارچ', 'اپریل', 'مئی', 'جون', 'جولائی', 'اگست', 'ستمبر', 'اکتوبر', 'نومبر', 'دسمبر'], + 'months_short' => ['جنوری', 'فروری', 'مارچ', 'اپریل', 'مئی', 'جون', 'جولائی', 'اگست', 'ستمبر', 'اکتوبر', 'نومبر', 'دسمبر'], + 'weekdays' => ['اتوار', 'پير', 'منگل', 'بدھ', 'جمعرات', 'جمعه', 'هفته'], + 'weekdays_short' => ['اتوار', 'پير', 'منگل', 'بدھ', 'جمعرات', 'جمعه', 'هفته'], + 'weekdays_min' => ['اتوار', 'پير', 'منگل', 'بدھ', 'جمعرات', 'جمعه', 'هفته'], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ص', 'ش'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/uz.php b/vendor/nesbot/carbon/src/Carbon/Lang/uz.php new file mode 100644 index 0000000..61f3b64 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/uz.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Dmitriy Shabanov + * - JD Isaacks + * - Inoyatulloh + * - Jamshid + * - aarkhipov + * - Philippe Vaucher + * - felixthemagnificent + * - Tsutomu Kuroda + * - tjku + * - Max Melentiev + * - Juanito Fatas + * - Alisher Ulugbekov + * - Ergashev Adizbek + */ +return [ + 'year' => ':count йил', + 'a_year' => '{1}бир йил|:count йил', + 'y' => ':count й', + 'month' => ':count ой', + 'a_month' => '{1}бир ой|:count ой', + 'm' => ':count о', + 'week' => ':count ҳафта', + 'a_week' => '{1}бир ҳафта|:count ҳафта', + 'w' => ':count ҳ', + 'day' => ':count кун', + 'a_day' => '{1}бир кун|:count кун', + 'd' => ':count к', + 'hour' => ':count соат', + 'a_hour' => '{1}бир соат|:count соат', + 'h' => ':count с', + 'minute' => ':count дақиқа', + 'a_minute' => '{1}бир дақиқа|:count дақиқа', + 'min' => ':count д', + 'second' => ':count сония', + 'a_second' => '{1}сония|:count сония', + 's' => ':count с', + 'ago' => ':time аввал', + 'from_now' => 'Якин :time ичида', + 'after' => ':timeдан кейин', + 'before' => ':time олдин', + 'diff_now' => 'ҳозир', + 'diff_today' => 'Бугун', + 'diff_today_regexp' => 'Бугун(?:\\s+соат)?', + 'diff_yesterday' => 'Кеча', + 'diff_yesterday_regexp' => 'Кеча(?:\\s+соат)?', + 'diff_tomorrow' => 'Эртага', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'D MMMM YYYY, dddd HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Бугун соат] LT [да]', + 'nextDay' => '[Эртага] LT [да]', + 'nextWeek' => 'dddd [куни соат] LT [да]', + 'lastDay' => '[Кеча соат] LT [да]', + 'lastWeek' => '[Утган] dddd [куни соат] LT [да]', + 'sameElse' => 'L', + ], + 'months' => ['январ', 'феврал', 'март', 'апрел', 'май', 'июн', 'июл', 'август', 'сентябр', 'октябр', 'ноябр', 'декабр'], + 'months_short' => ['янв', 'фев', 'мар', 'апр', 'май', 'июн', 'июл', 'авг', 'сен', 'окт', 'ноя', 'дек'], + 'weekdays' => ['якшанба', 'душанба', 'сешанба', 'чоршанба', 'пайшанба', 'жума', 'шанба'], + 'weekdays_short' => ['якш', 'душ', 'сеш', 'чор', 'пай', 'жум', 'шан'], + 'weekdays_min' => ['як', 'ду', 'се', 'чо', 'па', 'жу', 'ша'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['эрталаб', 'кечаси'], + 'list' => [', ', ' ва '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/uz_Arab.php b/vendor/nesbot/carbon/src/Carbon/Lang/uz_Arab.php new file mode 100644 index 0000000..ffb5131 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/uz_Arab.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/fa.php', [ + 'weekdays' => ['یکشنبه', 'دوشنبه', 'سه‌شنبه', 'چهارشنبه', 'پنجشنبه', 'جمعه', 'شنبه'], + 'weekdays_short' => ['ی.', 'د.', 'س.', 'چ.', 'پ.', 'ج.', 'ش.'], + 'weekdays_min' => ['ی.', 'د.', 'س.', 'چ.', 'پ.', 'ج.', 'ش.'], + 'months' => ['جنوری', 'فبروری', 'مارچ', 'اپریل', 'می', 'جون', 'جولای', 'اگست', 'سپتمبر', 'اکتوبر', 'نومبر', 'دسمبر'], + 'months_short' => ['جنو', 'فبر', 'مار', 'اپر', 'می', 'جون', 'جول', 'اگس', 'سپت', 'اکت', 'نوم', 'دسم'], + 'first_day_of_week' => 6, + 'weekend' => [4, 5], + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-dd', + 'LL' => 'YYYY MMM D', + 'LLL' => 'YYYY MMMM D HH:mm', + 'LLLL' => 'YYYY MMMM D, dddd HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/uz_Cyrl.php b/vendor/nesbot/carbon/src/Carbon/Lang/uz_Cyrl.php new file mode 100644 index 0000000..89e9971 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/uz_Cyrl.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/uz.php', [ + 'formats' => [ + 'L' => 'DD/MM/yy', + 'LL' => 'D MMM, YYYY', + 'LLL' => 'D MMMM, YYYY HH:mm', + 'LLLL' => 'dddd, DD MMMM, YYYY HH:mm', + ], + 'meridiem' => ['ТО', 'ТК'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/uz_Latn.php b/vendor/nesbot/carbon/src/Carbon/Lang/uz_Latn.php new file mode 100644 index 0000000..ecceeaa --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/uz_Latn.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Josh Soref + * - Rasulbek + * - Ilyosjon Kamoldinov (ilyosjon09) + */ +return [ + 'year' => ':count yil', + 'a_year' => '{1}bir yil|:count yil', + 'y' => ':count y', + 'month' => ':count oy', + 'a_month' => '{1}bir oy|:count oy', + 'm' => ':count o', + 'week' => ':count hafta', + 'a_week' => '{1}bir hafta|:count hafta', + 'w' => ':count h', + 'day' => ':count kun', + 'a_day' => '{1}bir kun|:count kun', + 'd' => ':count k', + 'hour' => ':count soat', + 'a_hour' => '{1}bir soat|:count soat', + 'h' => ':count soat', + 'minute' => ':count daqiqa', + 'a_minute' => '{1}bir daqiqa|:count daqiqa', + 'min' => ':count d', + 'second' => ':count soniya', + 'a_second' => '{1}soniya|:count soniya', + 's' => ':count son.', + 'ago' => ':time avval', + 'from_now' => 'Yaqin :time ichida', + 'after' => ':timedan keyin', + 'before' => ':time oldin', + 'diff_yesterday' => 'Kecha', + 'diff_yesterday_regexp' => 'Kecha(?:\\s+soat)?', + 'diff_today' => 'Bugun', + 'diff_today_regexp' => 'Bugun(?:\\s+soat)?', + 'diff_tomorrow' => 'Ertaga', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'D MMMM YYYY, dddd HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Bugun soat] LT [da]', + 'nextDay' => '[Ertaga] LT [da]', + 'nextWeek' => 'dddd [kuni soat] LT [da]', + 'lastDay' => '[Kecha soat] LT [da]', + 'lastWeek' => '[O\'tgan] dddd [kuni soat] LT [da]', + 'sameElse' => 'L', + ], + 'months' => ['Yanvar', 'Fevral', 'Mart', 'Aprel', 'May', 'Iyun', 'Iyul', 'Avgust', 'Sentabr', 'Oktabr', 'Noyabr', 'Dekabr'], + 'months_short' => ['Yan', 'Fev', 'Mar', 'Apr', 'May', 'Iyun', 'Iyul', 'Avg', 'Sen', 'Okt', 'Noy', 'Dek'], + 'weekdays' => ['Yakshanba', 'Dushanba', 'Seshanba', 'Chorshanba', 'Payshanba', 'Juma', 'Shanba'], + 'weekdays_short' => ['Yak', 'Dush', 'Sesh', 'Chor', 'Pay', 'Jum', 'Shan'], + 'weekdays_min' => ['Ya', 'Du', 'Se', 'Cho', 'Pa', 'Ju', 'Sha'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' va '], + 'meridiem' => ['TO', 'TK'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/uz_UZ.php b/vendor/nesbot/carbon/src/Carbon/Lang/uz_UZ.php new file mode 100644 index 0000000..d41bfee --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/uz_UZ.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Bobir Ismailov Bobir Ismailov, Pablo Saratxaga, Mashrab Kuvatov bobir_is@yahoo.com, pablo@mandrakesoft.com, kmashrab@uni-bremen.de + */ +return array_replace_recursive(require __DIR__.'/uz_Latn.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['Yanvar', 'Fevral', 'Mart', 'Aprel', 'May', 'Iyun', 'Iyul', 'Avgust', 'Sentabr', 'Oktabr', 'Noyabr', 'Dekabr'], + 'months_short' => ['Yan', 'Fev', 'Mar', 'Apr', 'May', 'Iyn', 'Iyl', 'Avg', 'Sen', 'Okt', 'Noy', 'Dek'], + 'weekdays' => ['Yakshanba', 'Dushanba', 'Seshanba', 'Chorshanba', 'Payshanba', 'Juma', 'Shanba'], + 'weekdays_short' => ['Yak', 'Du', 'Se', 'Cho', 'Pay', 'Ju', 'Sha'], + 'weekdays_min' => ['Yak', 'Du', 'Se', 'Cho', 'Pay', 'Ju', 'Sha'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/uz_UZ@cyrillic.php b/vendor/nesbot/carbon/src/Carbon/Lang/uz_UZ@cyrillic.php new file mode 100644 index 0000000..2fa967c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/uz_UZ@cyrillic.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Mashrab Kuvatov Mashrab Kuvatov, Pablo Saratxaga kmashrab@uni-bremen.de, pablo@mandrakesoft.com + */ +return array_replace_recursive(require __DIR__.'/uz.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['Январ', 'Феврал', 'Март', 'Апрел', 'Май', 'Июн', 'Июл', 'Август', 'Сентябр', 'Октябр', 'Ноябр', 'Декабр'], + 'months_short' => ['Янв', 'Фев', 'Мар', 'Апр', 'Май', 'Июн', 'Июл', 'Авг', 'Сен', 'Окт', 'Ноя', 'Дек'], + 'weekdays' => ['Якшанба', 'Душанба', 'Сешанба', 'Чоршанба', 'Пайшанба', 'Жума', 'Шанба'], + 'weekdays_short' => ['Якш', 'Душ', 'Сеш', 'Чор', 'Пай', 'Жум', 'Шан'], + 'weekdays_min' => ['Якш', 'Душ', 'Сеш', 'Чор', 'Пай', 'Жум', 'Шан'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/vai.php b/vendor/nesbot/carbon/src/Carbon/Lang/vai.php new file mode 100644 index 0000000..3c378df --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/vai.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'weekdays' => ['ꕞꕌꔵ', 'ꗳꗡꘉ', 'ꕚꕞꕚ', 'ꕉꕞꕒ', 'ꕉꔤꕆꕢ', 'ꕉꔤꕀꕮ', 'ꔻꔬꔳ'], + 'weekdays_short' => ['ꕞꕌꔵ', 'ꗳꗡꘉ', 'ꕚꕞꕚ', 'ꕉꕞꕒ', 'ꕉꔤꕆꕢ', 'ꕉꔤꕀꕮ', 'ꔻꔬꔳ'], + 'weekdays_min' => ['ꕞꕌꔵ', 'ꗳꗡꘉ', 'ꕚꕞꕚ', 'ꕉꕞꕒ', 'ꕉꔤꕆꕢ', 'ꕉꔤꕀꕮ', 'ꔻꔬꔳ'], + 'months' => ['ꖨꖕ ꕪꕴ ꔞꔀꕮꕊ', 'ꕒꕡꖝꖕ', 'ꕾꖺ', 'ꖢꖕ', 'ꖑꕱ', 'ꖱꘋ', 'ꖱꕞꔤ', 'ꗛꔕ', 'ꕢꕌ', 'ꕭꖃ', 'ꔞꘋꕔꕿ ꕸꖃꗏ', 'ꖨꖕ ꕪꕴ ꗏꖺꕮꕊ'], + 'months_short' => ['ꖨꖕꔞ', 'ꕒꕡ', 'ꕾꖺ', 'ꖢꖕ', 'ꖑꕱ', 'ꖱꘋ', 'ꖱꕞ', 'ꗛꔕ', 'ꕢꕌ', 'ꕭꖃ', 'ꔞꘋ', 'ꖨꖕꗏ'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd, D MMMM YYYY h:mm a', + ], + + 'year' => ':count ꕀ', // less reliable + 'y' => ':count ꕀ', // less reliable + 'a_year' => ':count ꕀ', // less reliable + + 'second' => ':count ꗱꕞꕯꕊ', // less reliable + 's' => ':count ꗱꕞꕯꕊ', // less reliable + 'a_second' => ':count ꗱꕞꕯꕊ', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/vai_Latn.php b/vendor/nesbot/carbon/src/Carbon/Lang/vai_Latn.php new file mode 100644 index 0000000..51e83cc --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/vai_Latn.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'weekdays' => ['lahadi', 'tɛɛnɛɛ', 'talata', 'alaba', 'aimisa', 'aijima', 'siɓiti'], + 'weekdays_short' => ['lahadi', 'tɛɛnɛɛ', 'talata', 'alaba', 'aimisa', 'aijima', 'siɓiti'], + 'weekdays_min' => ['lahadi', 'tɛɛnɛɛ', 'talata', 'alaba', 'aimisa', 'aijima', 'siɓiti'], + 'months' => ['luukao kemã', 'ɓandaɓu', 'vɔɔ', 'fulu', 'goo', '6', '7', 'kɔnde', 'saah', 'galo', 'kenpkato ɓololɔ', 'luukao lɔma'], + 'months_short' => ['luukao kemã', 'ɓandaɓu', 'vɔɔ', 'fulu', 'goo', '6', '7', 'kɔnde', 'saah', 'galo', 'kenpkato ɓololɔ', 'luukao lɔma'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'h:mm a', + 'LTS' => 'h:mm:ss a', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm a', + 'LLLL' => 'dddd, D MMMM YYYY h:mm a', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/vai_Vaii.php b/vendor/nesbot/carbon/src/Carbon/Lang/vai_Vaii.php new file mode 100644 index 0000000..b4bb533 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/vai_Vaii.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/vai.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ve.php b/vendor/nesbot/carbon/src/Carbon/Lang/ve.php new file mode 100644 index 0000000..7f10aeb --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ve.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/ve_ZA.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/ve_ZA.php b/vendor/nesbot/carbon/src/Carbon/Lang/ve_ZA.php new file mode 100644 index 0000000..5eb2b91 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/ve_ZA.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Zuza Software Foundation (Translate.org.za) Dwayne Bailey dwayne@translate.org.za + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Phando', 'Luhuhi', 'Ṱhafamuhwe', 'Lambamai', 'Shundunthule', 'Fulwi', 'Fulwana', 'Ṱhangule', 'Khubvumedzi', 'Tshimedzi', 'Ḽara', 'Nyendavhusiku'], + 'months_short' => ['Pha', 'Luh', 'Fam', 'Lam', 'Shu', 'Lwi', 'Lwa', 'Ngu', 'Khu', 'Tsh', 'Ḽar', 'Nye'], + 'weekdays' => ['Swondaha', 'Musumbuluwo', 'Ḽavhuvhili', 'Ḽavhuraru', 'Ḽavhuṋa', 'Ḽavhuṱanu', 'Mugivhela'], + 'weekdays_short' => ['Swo', 'Mus', 'Vhi', 'Rar', 'ṋa', 'Ṱan', 'Mug'], + 'weekdays_min' => ['Swo', 'Mus', 'Vhi', 'Rar', 'ṋa', 'Ṱan', 'Mug'], + 'day_of_first_week_of_year' => 1, + + // Too unreliable + /* + 'day' => ':count vhege', // less reliable + 'd' => ':count vhege', // less reliable + 'a_day' => ':count vhege', // less reliable + + 'hour' => ':count watshi', // less reliable + 'h' => ':count watshi', // less reliable + 'a_hour' => ':count watshi', // less reliable + + 'minute' => ':count watshi', // less reliable + 'min' => ':count watshi', // less reliable + 'a_minute' => ':count watshi', // less reliable + + 'second' => ':count Mu', // less reliable + 's' => ':count Mu', // less reliable + 'a_second' => ':count Mu', // less reliable + + 'week' => ':count vhege', + 'w' => ':count vhege', + 'a_week' => ':count vhege', + */ +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/vi.php b/vendor/nesbot/carbon/src/Carbon/Lang/vi.php new file mode 100644 index 0000000..73e2852 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/vi.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Andre Polykanine A.K.A. Menelion Elensúlë + * - JD Isaacks + */ +return [ + 'year' => ':count năm', + 'a_year' => '{1}một năm|]1, Inf[:count năm', + 'y' => ':count năm', + 'month' => ':count tháng', + 'a_month' => '{1}một tháng|]1, Inf[:count tháng', + 'm' => ':count tháng', + 'week' => ':count tuần', + 'a_week' => '{1}một tuần|]1, Inf[:count tuần', + 'w' => ':count tuần', + 'day' => ':count ngày', + 'a_day' => '{1}một ngày|]1, Inf[:count ngày', + 'd' => ':count ngày', + 'hour' => ':count giờ', + 'a_hour' => '{1}một giờ|]1, Inf[:count giờ', + 'h' => ':count giờ', + 'minute' => ':count phút', + 'a_minute' => '{1}một phút|]1, Inf[:count phút', + 'min' => ':count phút', + 'second' => ':count giây', + 'a_second' => '{1}vài giây|]1, Inf[:count giây', + 's' => ':count giây', + 'ago' => ':time trước', + 'from_now' => ':time tới', + 'after' => ':time sau', + 'before' => ':time trước', + 'diff_now' => 'bây giờ', + 'diff_today' => 'Hôm', + 'diff_today_regexp' => 'Hôm(?:\\s+nay)?(?:\\s+lúc)?', + 'diff_yesterday' => 'Hôm qua', + 'diff_yesterday_regexp' => 'Hôm(?:\\s+qua)?(?:\\s+lúc)?', + 'diff_tomorrow' => 'Ngày mai', + 'diff_tomorrow_regexp' => 'Ngày(?:\\s+mai)?(?:\\s+lúc)?', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM [năm] YYYY', + 'LLL' => 'D MMMM [năm] YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM [năm] YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[Hôm nay lúc] LT', + 'nextDay' => '[Ngày mai lúc] LT', + 'nextWeek' => 'dddd [tuần tới lúc] LT', + 'lastDay' => '[Hôm qua lúc] LT', + 'lastWeek' => 'dddd [tuần trước lúc] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['SA', 'CH'], + 'months' => ['tháng 1', 'tháng 2', 'tháng 3', 'tháng 4', 'tháng 5', 'tháng 6', 'tháng 7', 'tháng 8', 'tháng 9', 'tháng 10', 'tháng 11', 'tháng 12'], + 'months_short' => ['Th01', 'Th02', 'Th03', 'Th04', 'Th05', 'Th06', 'Th07', 'Th08', 'Th09', 'Th10', 'Th11', 'Th12'], + 'weekdays' => ['chủ nhật', 'thứ hai', 'thứ ba', 'thứ tư', 'thứ năm', 'thứ sáu', 'thứ bảy'], + 'weekdays_short' => ['CN', 'T2', 'T3', 'T4', 'T5', 'T6', 'T7'], + 'weekdays_min' => ['CN', 'T2', 'T3', 'T4', 'T5', 'T6', 'T7'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => [', ', ' và '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/vi_VN.php b/vendor/nesbot/carbon/src/Carbon/Lang/vi_VN.php new file mode 100644 index 0000000..18d8987 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/vi_VN.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/vi.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/vo.php b/vendor/nesbot/carbon/src/Carbon/Lang/vo.php new file mode 100644 index 0000000..e273033 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/vo.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'months' => ['M01', 'M02', 'M03', 'M04', 'M05', 'M06', 'M07', 'M08', 'M09', 'M10', 'M11', 'M12'], + 'months_short' => ['M01', 'M02', 'M03', 'M04', 'M05', 'M06', 'M07', 'M08', 'M09', 'M10', 'M11', 'M12'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY-MM-dd', + 'LL' => 'YYYY MMM D', + 'LLL' => 'YYYY MMMM D HH:mm', + 'LLLL' => 'YYYY MMMM D, dddd HH:mm', + ], + + 'year' => ':count yel', + 'y' => ':count yel', + 'a_year' => ':count yel', + + 'month' => ':count mul', + 'm' => ':count mul', + 'a_month' => ':count mul', + + 'week' => ':count vig', + 'w' => ':count vig', + 'a_week' => ':count vig', + + 'day' => ':count del', + 'd' => ':count del', + 'a_day' => ':count del', + + 'hour' => ':count düp', + 'h' => ':count düp', + 'a_hour' => ':count düp', + + 'minute' => ':count minut', + 'min' => ':count minut', + 'a_minute' => ':count minut', + + 'second' => ':count sekun', + 's' => ':count sekun', + 'a_second' => ':count sekun', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/vun.php b/vendor/nesbot/carbon/src/Carbon/Lang/vun.php new file mode 100644 index 0000000..ed92e8e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/vun.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['utuko', 'kyiukonyi'], + 'weekdays' => ['Jumapilyi', 'Jumatatuu', 'Jumanne', 'Jumatanu', 'Alhamisi', 'Ijumaa', 'Jumamosi'], + 'weekdays_short' => ['Jpi', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'weekdays_min' => ['Jpi', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Iju', 'Jmo'], + 'months' => ['Januari', 'Februari', 'Machi', 'Aprilyi', 'Mei', 'Junyi', 'Julyai', 'Agusti', 'Septemba', 'Oktoba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mac', 'Apr', 'Mei', 'Jun', 'Jul', 'Ago', 'Sep', 'Okt', 'Nov', 'Des'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/wa.php b/vendor/nesbot/carbon/src/Carbon/Lang/wa.php new file mode 100644 index 0000000..f6dc4cc --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/wa.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/wa_BE.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/wa_BE.php b/vendor/nesbot/carbon/src/Carbon/Lang/wa_BE.php new file mode 100644 index 0000000..a76d80d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/wa_BE.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Djan SACRE Pablo Saratxaga pablo@mandrakesoft.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['di djanvî', 'di fevrî', 'di måss', 'd’ avri', 'di may', 'di djun', 'di djulete', 'd’ awousse', 'di setimbe', 'd’ octôbe', 'di nôvimbe', 'di decimbe'], + 'months_short' => ['dja', 'fev', 'mås', 'avr', 'may', 'djn', 'djl', 'awo', 'set', 'oct', 'nôv', 'dec'], + 'weekdays' => ['dimegne', 'londi', 'mårdi', 'mierkidi', 'djudi', 'vénrdi', 'semdi'], + 'weekdays_short' => ['dim', 'lon', 'mår', 'mie', 'dju', 'vén', 'sem'], + 'weekdays_min' => ['dim', 'lon', 'mår', 'mie', 'dju', 'vén', 'sem'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'year' => ':count anêye', + 'y' => ':count anêye', + 'a_year' => ':count anêye', + + 'month' => ':count meûs', + 'm' => ':count meûs', + 'a_month' => ':count meûs', + + 'week' => ':count samwinne', + 'w' => ':count samwinne', + 'a_week' => ':count samwinne', + + 'day' => ':count djoû', + 'd' => ':count djoû', + 'a_day' => ':count djoû', + + 'hour' => ':count eure', + 'h' => ':count eure', + 'a_hour' => ':count eure', + + 'minute' => ':count munute', + 'min' => ':count munute', + 'a_minute' => ':count munute', + + 'second' => ':count Sigonde', + 's' => ':count Sigonde', + 'a_second' => ':count Sigonde', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/wae.php b/vendor/nesbot/carbon/src/Carbon/Lang/wae.php new file mode 100644 index 0000000..bf57f23 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/wae.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/wae_CH.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/wae_CH.php b/vendor/nesbot/carbon/src/Carbon/Lang/wae_CH.php new file mode 100644 index 0000000..2af50b4 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/wae_CH.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Walser Translation Team ml@translate-wae.ch + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'YYYY-MM-DD', + ], + 'months' => ['Jenner', 'Hornig', 'Märze', 'Abrille', 'Meije', 'Bráčet', 'Heiwet', 'Öigšte', 'Herbštmánet', 'Wímánet', 'Wintermánet', 'Chrištmánet'], + 'months_short' => ['Jen', 'Hor', 'Mär', 'Abr', 'Mei', 'Brá', 'Hei', 'Öig', 'Her', 'Wím', 'Win', 'Chr'], + 'weekdays' => ['Suntag', 'Mäntag', 'Zischtag', 'Mittwuch', 'Frontag', 'Fritag', 'Samschtag'], + 'weekdays_short' => ['Sun', 'Män', 'Zis', 'Mit', 'Fro', 'Fri', 'Sam'], + 'weekdays_min' => ['Sun', 'Män', 'Zis', 'Mit', 'Fro', 'Fri', 'Sam'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + + 'month' => ':count Maano', // less reliable + 'm' => ':count Maano', // less reliable + 'a_month' => ':count Maano', // less reliable +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/wal.php b/vendor/nesbot/carbon/src/Carbon/Lang/wal.php new file mode 100644 index 0000000..e8ec40f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/wal.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/wal_ET.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/wal_ET.php b/vendor/nesbot/carbon/src/Carbon/Lang/wal_ET.php new file mode 100644 index 0000000..a4e619a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/wal_ET.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Ge'ez Frontier Foundation locales@geez.org + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['ጃንዩወሪ', 'ፌብሩወሪ', 'ማርች', 'ኤፕረል', 'ሜይ', 'ጁን', 'ጁላይ', 'ኦገስት', 'ሴፕቴምበር', 'ኦክተውበር', 'ኖቬምበር', 'ዲሴምበር'], + 'months_short' => ['ጃንዩ', 'ፌብሩ', 'ማርች', 'ኤፕረ', 'ሜይ ', 'ጁን ', 'ጁላይ', 'ኦገስ', 'ሴፕቴ', 'ኦክተ', 'ኖቬም', 'ዲሴም'], + 'weekdays' => ['ወጋ', 'ሳይኖ', 'ማቆሳኛ', 'አሩዋ', 'ሃሙሳ', 'አርባ', 'ቄራ'], + 'weekdays_short' => ['ወጋ ', 'ሳይኖ', 'ማቆሳ', 'አሩዋ', 'ሃሙሳ', 'አርባ', 'ቄራ '], + 'weekdays_min' => ['ወጋ ', 'ሳይኖ', 'ማቆሳ', 'አሩዋ', 'ሃሙሳ', 'አርባ', 'ቄራ '], + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['ማለዶ', 'ቃማ'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/wo.php b/vendor/nesbot/carbon/src/Carbon/Lang/wo.php new file mode 100644 index 0000000..74b95df --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/wo.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/wo_SN.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/wo_SN.php b/vendor/nesbot/carbon/src/Carbon/Lang/wo_SN.php new file mode 100644 index 0000000..f8a85b3 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/wo_SN.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - The Debian Project Christian Perrier bubulle@debian.org + */ +return [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD.MM.YYYY', + 'LL' => 'MMMM DD, YYYY', + 'LLL' => 'DD MMM HH:mm', + 'LLLL' => 'MMMM DD, YYYY HH:mm', + ], + 'months' => ['sanwiy\'e', 'feebriy\'e', 'mars', 'awril', 'me', 'suwen', 'sulet', 'uut', 'septaambar', 'oktoobar', 'nowaambar', 'desaambar'], + 'months_short' => ['san', 'fee', 'mar', 'awr', 'me ', 'suw', 'sul', 'uut', 'sep', 'okt', 'now', 'des'], + 'weekdays' => ['dib\'eer', 'altine', 'talaata', 'allarba', 'alxames', 'ajjuma', 'gaawu'], + 'weekdays_short' => ['dib', 'alt', 'tal', 'all', 'alx', 'ajj', 'gaa'], + 'weekdays_min' => ['dib', 'alt', 'tal', 'all', 'alx', 'ajj', 'gaa'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'year' => ':count at', + 'month' => ':count wèr', + 'week' => ':count ayubés', + 'day' => ':count bés', + 'hour' => ':count waxtu', + 'minute' => ':count simili', + 'second' => ':count saa', +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/xh.php b/vendor/nesbot/carbon/src/Carbon/Lang/xh.php new file mode 100644 index 0000000..e88c78d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/xh.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/xh_ZA.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/xh_ZA.php b/vendor/nesbot/carbon/src/Carbon/Lang/xh_ZA.php new file mode 100644 index 0000000..910f831 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/xh_ZA.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Zuza Software Foundation (Translate.org.za) Dwayne Bailey dwayne@translate.org.za + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['eyoMqungu', 'eyoMdumba', 'eyoKwindla', 'uTshazimpuzi', 'uCanzibe', 'eyeSilimela', 'eyeKhala', 'eyeThupa', 'eyoMsintsi', 'eyeDwarha', 'eyeNkanga', 'eyoMnga'], + 'months_short' => ['Mqu', 'Mdu', 'Kwi', 'Tsh', 'Can', 'Sil', 'Kha', 'Thu', 'Msi', 'Dwa', 'Nka', 'Mng'], + 'weekdays' => ['iCawa', 'uMvulo', 'lwesiBini', 'lwesiThathu', 'ulweSine', 'lwesiHlanu', 'uMgqibelo'], + 'weekdays_short' => ['Caw', 'Mvu', 'Bin', 'Tha', 'Sin', 'Hla', 'Mgq'], + 'weekdays_min' => ['Caw', 'Mvu', 'Bin', 'Tha', 'Sin', 'Hla', 'Mgq'], + 'day_of_first_week_of_year' => 1, + + 'year' => ':count ihlobo', // less reliable + 'y' => ':count ihlobo', // less reliable + 'a_year' => ':count ihlobo', // less reliable + + 'hour' => ':count iwotshi', // less reliable + 'h' => ':count iwotshi', // less reliable + 'a_hour' => ':count iwotshi', // less reliable + + 'minute' => ':count ingqalelo', // less reliable + 'min' => ':count ingqalelo', // less reliable + 'a_minute' => ':count ingqalelo', // less reliable + + 'second' => ':count nceda', // less reliable + 's' => ':count nceda', // less reliable + 'a_second' => ':count nceda', // less reliable + + 'month' => ':count inyanga', + 'm' => ':count inyanga', + 'a_month' => ':count inyanga', + + 'week' => ':count veki', + 'w' => ':count veki', + 'a_week' => ':count veki', + + 'day' => ':count imini', + 'd' => ':count imini', + 'a_day' => ':count imini', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/xog.php b/vendor/nesbot/carbon/src/Carbon/Lang/xog.php new file mode 100644 index 0000000..eb55b4a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/xog.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['Munkyo', 'Eigulo'], + 'weekdays' => ['Sabiiti', 'Balaza', 'Owokubili', 'Owokusatu', 'Olokuna', 'Olokutaanu', 'Olomukaaga'], + 'weekdays_short' => ['Sabi', 'Bala', 'Kubi', 'Kusa', 'Kuna', 'Kuta', 'Muka'], + 'weekdays_min' => ['Sabi', 'Bala', 'Kubi', 'Kusa', 'Kuna', 'Kuta', 'Muka'], + 'months' => ['Janwaliyo', 'Febwaliyo', 'Marisi', 'Apuli', 'Maayi', 'Juuni', 'Julaayi', 'Agusito', 'Sebuttemba', 'Okitobba', 'Novemba', 'Desemba'], + 'months_short' => ['Jan', 'Feb', 'Mar', 'Apu', 'Maa', 'Juu', 'Jul', 'Agu', 'Seb', 'Oki', 'Nov', 'Des'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/yav.php b/vendor/nesbot/carbon/src/Carbon/Lang/yav.php new file mode 100644 index 0000000..225a20d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/yav.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/en.php', [ + 'meridiem' => ['kiɛmɛ́ɛm', 'kisɛ́ndɛ'], + 'weekdays' => ['sɔ́ndiɛ', 'móndie', 'muányáŋmóndie', 'metúkpíápɛ', 'kúpélimetúkpiapɛ', 'feléte', 'séselé'], + 'weekdays_short' => ['sd', 'md', 'mw', 'et', 'kl', 'fl', 'ss'], + 'weekdays_min' => ['sd', 'md', 'mw', 'et', 'kl', 'fl', 'ss'], + 'months' => ['pikítíkítie, oólí ú kutúan', 'siɛyɛ́, oóli ú kándíɛ', 'ɔnsúmbɔl, oóli ú kátátúɛ', 'mesiŋ, oóli ú kénie', 'ensil, oóli ú kátánuɛ', 'ɔsɔn', 'efute', 'pisuyú', 'imɛŋ i puɔs', 'imɛŋ i putúk,oóli ú kátíɛ', 'makandikɛ', 'pilɔndɔ́'], + 'months_short' => ['o.1', 'o.2', 'o.3', 'o.4', 'o.5', 'o.6', 'o.7', 'o.8', 'o.9', 'o.10', 'o.11', 'o.12'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'D/M/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/yi.php b/vendor/nesbot/carbon/src/Carbon/Lang/yi.php new file mode 100644 index 0000000..8f32022 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/yi.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/yi_US.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/yi_US.php b/vendor/nesbot/carbon/src/Carbon/Lang/yi_US.php new file mode 100644 index 0000000..f764d36 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/yi_US.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - http://www.uyip.org/ Pablo Saratxaga pablo@mandrakesoft.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['יאַנואַר', 'פֿעברואַר', 'מערץ', 'אַפּריל', 'מיי', 'יוני', 'יולי', 'אויגוסט', 'סעפּטעמבער', 'אקטאבער', 'נאוועמבער', 'דעצעמבער'], + 'months_short' => ['יאַנ', 'פֿעב', 'מאַר', 'אַפּר', 'מײַ ', 'יונ', 'יול', 'אױג', 'סעפּ', 'אָקט', 'נאָװ', 'דעצ'], + 'weekdays' => ['זונטיק', 'מאָנטיק', 'דינסטיק', 'מיטװאָך', 'דאָנערשטיק', 'פֿרײַטיק', 'שבת'], + 'weekdays_short' => ['זונ\'', 'מאָנ\'', 'דינ\'', 'מיט\'', 'דאָנ\'', 'פֿרײַ\'', 'שבת'], + 'weekdays_min' => ['זונ\'', 'מאָנ\'', 'דינ\'', 'מיט\'', 'דאָנ\'', 'פֿרײַ\'', 'שבת'], + 'day_of_first_week_of_year' => 1, + + 'year' => ':count יאר', + 'y' => ':count יאר', + 'a_year' => ':count יאר', + + 'month' => ':count חודש', + 'm' => ':count חודש', + 'a_month' => ':count חודש', + + 'week' => ':count וואָך', + 'w' => ':count וואָך', + 'a_week' => ':count וואָך', + + 'day' => ':count טאָג', + 'd' => ':count טאָג', + 'a_day' => ':count טאָג', + + 'hour' => ':count שעה', + 'h' => ':count שעה', + 'a_hour' => ':count שעה', + + 'minute' => ':count מינוט', + 'min' => ':count מינוט', + 'a_minute' => ':count מינוט', + + 'second' => ':count סעקונדע', + 's' => ':count סעקונדע', + 'a_second' => ':count סעקונדע', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/yo.php b/vendor/nesbot/carbon/src/Carbon/Lang/yo.php new file mode 100644 index 0000000..0a82981 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/yo.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - François B + * - Atolagbe Abisoye + */ +return [ + 'year' => 'ọdún :count', + 'a_year' => '{1}ọdún kan|ọdún :count', + 'month' => 'osù :count', + 'a_month' => '{1}osù kan|osù :count', + 'week' => 'ọsẹ :count', + 'a_week' => '{1}ọsẹ kan|ọsẹ :count', + 'day' => 'ọjọ́ :count', + 'a_day' => '{1}ọjọ́ kan|ọjọ́ :count', + 'hour' => 'wákati :count', + 'a_hour' => '{1}wákati kan|wákati :count', + 'minute' => 'ìsẹjú :count', + 'a_minute' => '{1}ìsẹjú kan|ìsẹjú :count', + 'second' => 'iaayá :count', + 'a_second' => '{1}ìsẹjú aayá die|aayá :count', + 'ago' => ':time kọjá', + 'from_now' => 'ní :time', + 'diff_yesterday' => 'Àna', + 'diff_yesterday_regexp' => 'Àna(?:\\s+ni)?', + 'diff_today' => 'Ònì', + 'diff_today_regexp' => 'Ònì(?:\\s+ni)?', + 'diff_tomorrow' => 'Ọ̀la', + 'diff_tomorrow_regexp' => 'Ọ̀la(?:\\s+ni)?', + 'formats' => [ + 'LT' => 'h:mm A', + 'LTS' => 'h:mm:ss A', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY h:mm A', + 'LLLL' => 'dddd, D MMMM YYYY h:mm A', + ], + 'calendar' => [ + 'sameDay' => '[Ònì ni] LT', + 'nextDay' => '[Ọ̀la ni] LT', + 'nextWeek' => 'dddd [Ọsẹ̀ tón\'bọ] [ni] LT', + 'lastDay' => '[Àna ni] LT', + 'lastWeek' => 'dddd [Ọsẹ̀ tólọ́] [ni] LT', + 'sameElse' => 'L', + ], + 'ordinal' => 'ọjọ́ :number', + 'months' => ['Sẹ́rẹ́', 'Èrèlè', 'Ẹrẹ̀nà', 'Ìgbé', 'Èbibi', 'Òkùdu', 'Agẹmo', 'Ògún', 'Owewe', 'Ọ̀wàrà', 'Bélú', 'Ọ̀pẹ̀̀'], + 'months_short' => ['Sẹ́r', 'Èrl', 'Ẹrn', 'Ìgb', 'Èbi', 'Òkù', 'Agẹ', 'Ògú', 'Owe', 'Ọ̀wà', 'Bél', 'Ọ̀pẹ̀̀'], + 'weekdays' => ['Àìkú', 'Ajé', 'Ìsẹ́gun', 'Ọjọ́rú', 'Ọjọ́bọ', 'Ẹtì', 'Àbámẹ́ta'], + 'weekdays_short' => ['Àìk', 'Ajé', 'Ìsẹ́', 'Ọjr', 'Ọjb', 'Ẹtì', 'Àbá'], + 'weekdays_min' => ['Àì', 'Aj', 'Ìs', 'Ọr', 'Ọb', 'Ẹt', 'Àb'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'meridiem' => ['Àárọ̀', 'Ọ̀sán'], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/yo_BJ.php b/vendor/nesbot/carbon/src/Carbon/Lang/yo_BJ.php new file mode 100644 index 0000000..12b9e81 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/yo_BJ.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array_replace_recursive(require __DIR__.'/yo.php', [ + 'meridiem' => ['Àárɔ̀', 'Ɔ̀sán'], + 'weekdays' => ['Ɔjɔ́ Àìkú', 'Ɔjɔ́ Ajé', 'Ɔjɔ́ Ìsɛ́gun', 'Ɔjɔ́rú', 'Ɔjɔ́bɔ', 'Ɔjɔ́ Ɛtì', 'Ɔjɔ́ Àbámɛ́ta'], + 'weekdays_short' => ['Àìkú', 'Ajé', 'Ìsɛ́gun', 'Ɔjɔ́rú', 'Ɔjɔ́bɔ', 'Ɛtì', 'Àbámɛ́ta'], + 'weekdays_min' => ['Àìkú', 'Ajé', 'Ìsɛ́gun', 'Ɔjɔ́rú', 'Ɔjɔ́bɔ', 'Ɛtì', 'Àbámɛ́ta'], + 'months' => ['Oshù Shɛ́rɛ́', 'Oshù Èrèlè', 'Oshù Ɛrɛ̀nà', 'Oshù Ìgbé', 'Oshù Ɛ̀bibi', 'Oshù Òkúdu', 'Oshù Agɛmɔ', 'Oshù Ògún', 'Oshù Owewe', 'Oshù Ɔ̀wàrà', 'Oshù Bélú', 'Oshù Ɔ̀pɛ̀'], + 'months_short' => ['Shɛ́rɛ́', 'Èrèlè', 'Ɛrɛ̀nà', 'Ìgbé', 'Ɛ̀bibi', 'Òkúdu', 'Agɛmɔ', 'Ògún', 'Owewe', 'Ɔ̀wàrà', 'Bélú', 'Ɔ̀pɛ̀'], + 'first_day_of_week' => 1, + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd, D MMMM YYYY HH:mm', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/yo_NG.php b/vendor/nesbot/carbon/src/Carbon/Lang/yo_NG.php new file mode 100644 index 0000000..6860bc1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/yo_NG.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/yo.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/yue.php b/vendor/nesbot/carbon/src/Carbon/Lang/yue.php new file mode 100644 index 0000000..ce233a4 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/yue.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/yue_HK.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/yue_HK.php b/vendor/nesbot/carbon/src/Carbon/Lang/yue_HK.php new file mode 100644 index 0000000..4e7d5c3 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/yue_HK.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/zh_HK.php', [ + 'formats' => [ + 'L' => 'YYYY年MM月DD日 dddd', + ], + 'months' => ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'], + 'months_short' => ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'], + 'weekdays' => ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'], + 'weekdays_short' => ['日', '一', '二', '三', '四', '五', '六'], + 'weekdays_min' => ['日', '一', '二', '三', '四', '五', '六'], + 'first_day_of_week' => 0, + 'day_of_first_week_of_year' => 1, + 'meridiem' => ['上午', '下午'], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/yue_Hans.php b/vendor/nesbot/carbon/src/Carbon/Lang/yue_Hans.php new file mode 100644 index 0000000..db913ca --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/yue_Hans.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/zh_Hans.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/yue_Hant.php b/vendor/nesbot/carbon/src/Carbon/Lang/yue_Hant.php new file mode 100644 index 0000000..e2526f1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/yue_Hant.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/zh_Hant.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/yuw.php b/vendor/nesbot/carbon/src/Carbon/Lang/yuw.php new file mode 100644 index 0000000..8efdc93 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/yuw.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/yuw_PG.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/yuw_PG.php b/vendor/nesbot/carbon/src/Carbon/Lang/yuw_PG.php new file mode 100644 index 0000000..b99ad2e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/yuw_PG.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Information from native speakers Hannah Sarvasy nungon.localization@gmail.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YY', + ], + 'months' => ['jenuari', 'febuari', 'mas', 'epril', 'mei', 'jun', 'julai', 'ögus', 'septemba', 'öktoba', 'nöwemba', 'diksemba'], + 'months_short' => ['jen', 'feb', 'mas', 'epr', 'mei', 'jun', 'jul', 'ögu', 'sep', 'ökt', 'nöw', 'dis'], + 'weekdays' => ['sönda', 'mönda', 'sinda', 'mitiwö', 'sogipbono', 'nenggo', 'söndanggie'], + 'weekdays_short' => ['sön', 'mön', 'sin', 'mit', 'soi', 'nen', 'sab'], + 'weekdays_min' => ['sön', 'mön', 'sin', 'mit', 'soi', 'nen', 'sab'], + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/zgh.php b/vendor/nesbot/carbon/src/Carbon/Lang/zgh.php new file mode 100644 index 0000000..4d2c3b3 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/zgh.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - BAKTETE Miloud + */ +return [ + 'year' => ':count ⵓⵙⴳⴳⵯⴰⵙ|:count ⵉⵙⴳⴳⵓⵙⴰ', + 'a_year' => 'ⵓⵙⴳⴳⵯⴰⵙ|:count ⵉⵙⴳⴳⵓⵙⴰ', + 'y' => ':count ⵓⵙⴳⴳⵯⴰⵙ|:count ⵉⵙⴳⴳⵓⵙⴰ', + 'month' => ':count ⵡⴰⵢⵢⵓⵔ|:count ⴰⵢⵢⵓⵔⵏ', + 'a_month' => 'ⵉⴷⵊ ⵡⴰⵢⵢⵓⵔ|:count ⴰⵢⵢⵓⵔⵏ', + 'm' => ':count ⴰⵢⵢⵓⵔⵏ', + 'week' => ':count ⵉⵎⴰⵍⴰⵙⵙ|:count ⵉⵎⴰⵍⴰⵙⵙⵏ', + 'a_week' => 'ⵉⵛⵜ ⵉⵎⴰⵍⴰⵙⵙ|:count ⵉⵎⴰⵍⴰⵙⵙⵏ', + 'w' => ':count ⵉⵎⴰⵍⴰⵙⵙ.', + 'day' => ':count ⵡⴰⵙⵙ|:count ⵓⵙⵙⴰⵏ', + 'a_day' => 'ⵉⴷⵊ ⵡⴰⵙⵙ|:count ⵓⵙⵙⴰⵏ', + 'd' => ':count ⵓ', + 'hour' => ':count ⵜⵙⵔⴰⴳⵜ|:count ⵜⵉⵙⵔⴰⴳⵉⵏ', + 'a_hour' => 'ⵉⵛⵜ ⵜⵙⵔⴰⴳⵜ|:count ⵜⵉⵙⵔⴰⴳⵉⵏ', + 'h' => ':count ⵜ', + 'minute' => ':count ⵜⵓⵙⴷⵉⴷⵜ|:count ⵜⵓⵙⴷⵉⴷⵉⵏ', + 'a_minute' => 'ⵉⵛⵜ ⵜⵓⵙⴷⵉⴷⵜ|:count ⵜⵓⵙⴷⵉⴷⵉⵏ', + 'min' => ':count ⵜⵓⵙ', + 'second' => ':count ⵜⵙⵉⵏⵜ|:count ⵜⵉⵙⵉⵏⴰ', + 'a_second' => 'ⴽⵔⴰ ⵜⵉⵙⵉⵏⴰ|:count ⵜⵉⵙⵉⵏⴰ', + 's' => ':count ⵜ', + 'ago' => 'ⵣⴳ :time', + 'from_now' => 'ⴷⴳ :time', + 'after' => ':time ⴰⵡⴰⵔ', + 'before' => ':time ⴷⴰⵜ', + 'diff_now' => 'ⴰⴷⵡⴰⵍⵉ', + 'diff_today' => 'ⴰⵙⵙ', + 'diff_today_regexp' => 'ⴰⵙⵙ(?:\\s+ⴰ/ⴰⴷ)?(?:\\s+ⴳ)?', + 'diff_yesterday' => 'ⴰⵙⵙⵏⵏⴰⵟ', + 'diff_yesterday_regexp' => 'ⴰⵙⵙⵏⵏⴰⵟ(?:\\s+ⴳ)?', + 'diff_tomorrow' => 'ⴰⵙⴽⴽⴰ', + 'diff_tomorrow_regexp' => 'ⴰⵙⴽⴽⴰ(?:\\s+ⴳ)?', + 'diff_before_yesterday' => 'ⴼⵔ ⵉⴹⵏⵏⴰⵟ', + 'diff_after_tomorrow' => 'ⵏⴰⴼ ⵓⵙⴽⴽⴰ', + 'period_recurrences' => ':count ⵜⵉⴽⴽⴰⵍ', + 'period_interval' => 'ⴽⵓ :interval', + 'period_start_date' => 'ⴳ :date', + 'period_end_date' => 'ⵉ :date', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'DD/MM/YYYY', + 'LL' => 'D MMMM YYYY', + 'LLL' => 'D MMMM YYYY HH:mm', + 'LLLL' => 'dddd D MMMM YYYY HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[ⴰⵙⵙ ⴰ/ⴰⴷ ⴳ] LT', + 'nextDay' => '[ⴰⵙⴽⴽⴰ ⴳ] LT', + 'nextWeek' => 'dddd [ⴳ] LT', + 'lastDay' => '[ⴰⵙⵙⵏⵏⴰⵟ ⴳ] LT', + 'lastWeek' => 'dddd [ⴰⵎⴳⴳⴰⵔⵓ ⴳ] LT', + 'sameElse' => 'L', + ], + 'meridiem' => ['ⵜⵉⴼⴰⵡⵜ', 'ⵜⴰⴷⴳⴳⵯⴰⵜ'], + 'months' => ['ⵉⵏⵏⴰⵢⵔ', 'ⴱⵕⴰⵢⵕ', 'ⵎⴰⵕⵚ', 'ⵉⴱⵔⵉⵔ', 'ⵎⴰⵢⵢⵓ', 'ⵢⵓⵏⵢⵓ', 'ⵢⵓⵍⵢⵓⵣ', 'ⵖⵓⵛⵜ', 'ⵛⵓⵜⴰⵏⴱⵉⵔ', 'ⴽⵟⵓⴱⵕ', 'ⵏⵓⵡⴰⵏⴱⵉⵔ', 'ⴷⵓⵊⴰⵏⴱⵉⵔ'], + 'months_short' => ['ⵉⵏⵏ', 'ⴱⵕⴰ', 'ⵎⴰⵕ', 'ⵉⴱⵔ', 'ⵎⴰⵢ', 'ⵢⵓⵏ', 'ⵢⵓⵍ', 'ⵖⵓⵛ', 'ⵛⵓⵜ', 'ⴽⵟⵓ', 'ⵏⵓⵡ', 'ⴷⵓⵊ'], + 'weekdays' => ['ⵓⵙⴰⵎⴰⵙ', 'ⵡⴰⵢⵏⴰⵙ', 'ⵓⵙⵉⵏⴰⵙ', 'ⵡⴰⴽⵕⴰⵙ', 'ⵓⴽⵡⴰⵙ', 'ⵓⵙⵉⵎⵡⴰⵙ', 'ⵓⵙⵉⴹⵢⴰⵙ'], + 'weekdays_short' => ['ⵓⵙⴰ', 'ⵡⴰⵢ', 'ⵓⵙⵉ', 'ⵡⴰⴽ', 'ⵓⴽⵡ', 'ⵓⵙⵉⵎ', 'ⵓⵙⵉⴹ'], + 'weekdays_min' => ['ⵓⵙⴰ', 'ⵡⴰⵢ', 'ⵓⵙⵉ', 'ⵡⴰⴽ', 'ⵓⴽⵡ', 'ⵓⵙⵉⵎ', 'ⵓⵙⵉⴹ'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 1, + 'list' => [', ', ' ⴷ '], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/zh.php b/vendor/nesbot/carbon/src/Carbon/Lang/zh.php new file mode 100644 index 0000000..1187c3d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/zh.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - xuri + * - sycuato + * - bokideckonja + * - Luo Ning + * - William Yang (williamyang233) + */ +return array_merge(require __DIR__.'/zh_Hans.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY/MM/DD', + 'LL' => 'YYYY年M月D日', + 'LLL' => 'YYYY年M月D日 A h点mm分', + 'LLLL' => 'YYYY年M月D日dddd A h点mm分', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/zh_CN.php b/vendor/nesbot/carbon/src/Carbon/Lang/zh_CN.php new file mode 100644 index 0000000..9c05d5a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/zh_CN.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - monkeycon + * - François B + * - Jason Katz-Brown + * - Serhan Apaydın + * - Matt Johnson + * - JD Isaacks + * - Zeno Zeng + * - Chris Hemp + * - shankesgk2 + */ +return array_merge(require __DIR__.'/zh.php', [ + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY/MM/DD', + 'LL' => 'YYYY年M月D日', + 'LLL' => 'YYYY年M月D日Ah点mm分', + 'LLLL' => 'YYYY年M月D日ddddAh点mm分', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/zh_HK.php b/vendor/nesbot/carbon/src/Carbon/Lang/zh_HK.php new file mode 100644 index 0000000..c3ee9fc --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/zh_HK.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/zh_Hant_HK.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hans.php b/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hans.php new file mode 100644 index 0000000..9b91785 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hans.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - monkeycon + * - François B + * - Jason Katz-Brown + * - Konstantin Konev + * - Chris Lam + * - Serhan Apaydın + * - Gary Lo + * - JD Isaacks + * - Chris Hemp + * - shankesgk2 + * - Daniel Cheung (danvim) + */ +return [ + 'year' => ':count:optional-space年', + 'y' => ':count:optional-space年', + 'month' => ':count:optional-space个月', + 'm' => ':count:optional-space个月', + 'week' => ':count:optional-space周', + 'w' => ':count:optional-space周', + 'day' => ':count:optional-space天', + 'd' => ':count:optional-space天', + 'hour' => ':count:optional-space小时', + 'h' => ':count:optional-space小时', + 'minute' => ':count:optional-space分钟', + 'min' => ':count:optional-space分钟', + 'second' => ':count:optional-space秒', + 'a_second' => '{1}几秒|]1,Inf[:count:optional-space秒', + 's' => ':count:optional-space秒', + 'ago' => ':time前', + 'from_now' => ':time后', + 'after' => ':time后', + 'before' => ':time前', + 'diff_now' => '现在', + 'diff_today' => '今天', + 'diff_yesterday' => '昨天', + 'diff_tomorrow' => '明天', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY/MM/DD', + 'LL' => 'YYYY年M月D日', + 'LLL' => 'YYYY年M月D日 HH:mm', + 'LLLL' => 'YYYY年M月D日dddd HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[今天]LT', + 'nextDay' => '[明天]LT', + 'nextWeek' => '[下]ddddLT', + 'lastDay' => '[昨天]LT', + 'lastWeek' => '[上]ddddLT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + switch ($period) { + case 'd': + case 'D': + case 'DDD': + return $number.'日'; + case 'M': + return $number.'月'; + case 'w': + case 'W': + return $number.'周'; + default: + return $number; + } + }, + 'meridiem' => function ($hour, $minute) { + $time = $hour * 100 + $minute; + if ($time < 600) { + return '凌晨'; + } + if ($time < 900) { + return '早上'; + } + if ($time < 1130) { + return '上午'; + } + if ($time < 1230) { + return '中午'; + } + if ($time < 1800) { + return '下午'; + } + + return '晚上'; + }, + 'months' => ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'], + 'months_short' => ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'], + 'weekdays' => ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'], + 'weekdays_short' => ['周日', '周一', '周二', '周三', '周四', '周五', '周六'], + 'weekdays_min' => ['日', '一', '二', '三', '四', '五', '六'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => '', +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hans_HK.php b/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hans_HK.php new file mode 100644 index 0000000..db913ca --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hans_HK.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/zh_Hans.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hans_MO.php b/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hans_MO.php new file mode 100644 index 0000000..db913ca --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hans_MO.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/zh_Hans.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hans_SG.php b/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hans_SG.php new file mode 100644 index 0000000..db913ca --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hans_SG.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/zh_Hans.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hant.php b/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hant.php new file mode 100644 index 0000000..a27b610 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hant.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Adam + * - monkeycon + * - François B + * - Jason Katz-Brown + * - Chris Lam + * - Serhan Apaydın + * - Gary Lo + * - JD Isaacks + * - Chris Hemp + * - Eddie + * - KID + * - shankesgk2 + * - Daniel Cheung (danvim) + */ +return [ + 'year' => ':count:optional-space年', + 'y' => ':count:optional-space年', + 'month' => ':count:optional-space個月', + 'm' => ':count:optional-space月', + 'week' => ':count:optional-space週', + 'w' => ':count:optional-space週', + 'day' => ':count:optional-space天', + 'd' => ':count:optional-space天', + 'hour' => ':count:optional-space小時', + 'h' => ':count:optional-space小時', + 'minute' => ':count:optional-space分鐘', + 'min' => ':count:optional-space分鐘', + 'second' => ':count:optional-space秒', + 'a_second' => '{1}幾秒|]1,Inf[:count:optional-space秒', + 's' => ':count:optional-space秒', + 'ago' => ':time前', + 'from_now' => ':time後', + 'after' => ':time後', + 'before' => ':time前', + 'diff_now' => '現在', + 'diff_today' => '今天', + 'diff_yesterday' => '昨天', + 'diff_tomorrow' => '明天', + 'formats' => [ + 'LT' => 'HH:mm', + 'LTS' => 'HH:mm:ss', + 'L' => 'YYYY/MM/DD', + 'LL' => 'YYYY年M月D日', + 'LLL' => 'YYYY年M月D日 HH:mm', + 'LLLL' => 'YYYY年M月D日dddd HH:mm', + ], + 'calendar' => [ + 'sameDay' => '[今天] LT', + 'nextDay' => '[明天] LT', + 'nextWeek' => '[下]dddd LT', + 'lastDay' => '[昨天] LT', + 'lastWeek' => '[上]dddd LT', + 'sameElse' => 'L', + ], + 'ordinal' => function ($number, $period) { + switch ($period) { + case 'd': + case 'D': + case 'DDD': + return $number.'日'; + case 'M': + return $number.'月'; + case 'w': + case 'W': + return $number.'周'; + default: + return $number; + } + }, + 'meridiem' => function ($hour, $minute) { + $time = $hour * 100 + $minute; + if ($time < 600) { + return '凌晨'; + } + if ($time < 900) { + return '早上'; + } + if ($time < 1130) { + return '上午'; + } + if ($time < 1230) { + return '中午'; + } + if ($time < 1800) { + return '下午'; + } + + return '晚上'; + }, + 'months' => ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'], + 'months_short' => ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'], + 'weekdays' => ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'], + 'weekdays_short' => ['週日', '週一', '週二', '週三', '週四', '週五', '週六'], + 'weekdays_min' => ['日', '一', '二', '三', '四', '五', '六'], + 'first_day_of_week' => 1, + 'day_of_first_week_of_year' => 4, + 'list' => '', +]; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hant_HK.php b/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hant_HK.php new file mode 100644 index 0000000..e2526f1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hant_HK.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/zh_Hant.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hant_MO.php b/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hant_MO.php new file mode 100644 index 0000000..e2526f1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hant_MO.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/zh_Hant.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hant_TW.php b/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hant_TW.php new file mode 100644 index 0000000..e2526f1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hant_TW.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/zh_Hant.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/zh_MO.php b/vendor/nesbot/carbon/src/Carbon/Lang/zh_MO.php new file mode 100644 index 0000000..1c86d47 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/zh_MO.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - tarunvelli + * - Eddie + * - KID + * - shankesgk2 + */ +return array_replace_recursive(require __DIR__.'/zh_Hant.php', [ + 'after' => ':time后', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/zh_SG.php b/vendor/nesbot/carbon/src/Carbon/Lang/zh_SG.php new file mode 100644 index 0000000..c451a56 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/zh_SG.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/zh.php', [ + 'formats' => [ + 'L' => 'YYYY年MM月DD日', + ], + 'months' => ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'], + 'months_short' => ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'], + 'weekdays' => ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'], + 'weekdays_short' => ['日', '一', '二', '三', '四', '五', '六'], + 'weekdays_min' => ['日', '一', '二', '三', '四', '五', '六'], + 'day_of_first_week_of_year' => 1, +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/zh_TW.php b/vendor/nesbot/carbon/src/Carbon/Lang/zh_TW.php new file mode 100644 index 0000000..c6789ed --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/zh_TW.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return require __DIR__.'/zh_Hant_TW.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/zh_YUE.php b/vendor/nesbot/carbon/src/Carbon/Lang/zh_YUE.php new file mode 100644 index 0000000..b0d9ba8 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/zh_YUE.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org + */ +return array_replace_recursive(require __DIR__.'/zh.php', [ + 'formats' => [ + 'L' => 'YYYY-MM-DD', + ], +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/zu.php b/vendor/nesbot/carbon/src/Carbon/Lang/zu.php new file mode 100644 index 0000000..9a6cce0 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/zu.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Unknown default region, use the first alphabetically. + */ +return require __DIR__.'/zu_ZA.php'; diff --git a/vendor/nesbot/carbon/src/Carbon/Lang/zu_ZA.php b/vendor/nesbot/carbon/src/Carbon/Lang/zu_ZA.php new file mode 100644 index 0000000..6bfb72f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Lang/zu_ZA.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Zuza Software Foundation (Translate.org.za) Dwayne Bailey dwayne@translate.org.za + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'DD/MM/YYYY', + ], + 'months' => ['Januwari', 'Februwari', 'Mashi', 'Ephreli', 'Meyi', 'Juni', 'Julayi', 'Agasti', 'Septhemba', 'Okthoba', 'Novemba', 'Disemba'], + 'months_short' => ['Jan', 'Feb', 'Mas', 'Eph', 'Mey', 'Jun', 'Jul', 'Aga', 'Sep', 'Okt', 'Nov', 'Dis'], + 'weekdays' => ['iSonto', 'uMsombuluko', 'uLwesibili', 'uLwesithathu', 'uLwesine', 'uLwesihlanu', 'uMgqibelo'], + 'weekdays_short' => ['Son', 'Mso', 'Bil', 'Tha', 'Sin', 'Hla', 'Mgq'], + 'weekdays_min' => ['Son', 'Mso', 'Bil', 'Tha', 'Sin', 'Hla', 'Mgq'], + 'day_of_first_week_of_year' => 1, + + 'year' => 'kweminyaka engu-:count', + 'y' => 'kweminyaka engu-:count', + 'a_year' => 'kweminyaka engu-:count', + + 'month' => 'izinyanga ezingu-:count', + 'm' => 'izinyanga ezingu-:count', + 'a_month' => 'izinyanga ezingu-:count', + + 'week' => 'lwamasonto angu-:count', + 'w' => 'lwamasonto angu-:count', + 'a_week' => 'lwamasonto angu-:count', + + 'day' => 'ezingaba ngu-:count', + 'd' => 'ezingaba ngu-:count', + 'a_day' => 'ezingaba ngu-:count', + + 'hour' => 'amahora angu-:count', + 'h' => 'amahora angu-:count', + 'a_hour' => 'amahora angu-:count', + + 'minute' => 'ngemizuzu engu-:count', + 'min' => 'ngemizuzu engu-:count', + 'a_minute' => 'ngemizuzu engu-:count', + + 'second' => 'imizuzwana engu-:count', + 's' => 'imizuzwana engu-:count', + 'a_second' => 'imizuzwana engu-:count', +]); diff --git a/vendor/nesbot/carbon/src/Carbon/Language.php b/vendor/nesbot/carbon/src/Carbon/Language.php new file mode 100644 index 0000000..1fb5baf --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Language.php @@ -0,0 +1,342 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use JsonSerializable; +use ReturnTypeWillChange; + +class Language implements JsonSerializable +{ + /** + * @var array + */ + protected static $languagesNames; + + /** + * @var array + */ + protected static $regionsNames; + + /** + * @var string + */ + protected $id; + + /** + * @var string + */ + protected $code; + + /** + * @var string|null + */ + protected $variant; + + /** + * @var string|null + */ + protected $region; + + /** + * @var array + */ + protected $names; + + /** + * @var string + */ + protected $isoName; + + /** + * @var string + */ + protected $nativeName; + + public function __construct(string $id) + { + $this->id = str_replace('-', '_', $id); + $parts = explode('_', $this->id); + $this->code = $parts[0]; + + if (isset($parts[1])) { + if (!preg_match('/^[A-Z]+$/', $parts[1])) { + $this->variant = $parts[1]; + $parts[1] = $parts[2] ?? null; + } + if ($parts[1]) { + $this->region = $parts[1]; + } + } + } + + /** + * Get the list of the known languages. + * + * @return array + */ + public static function all() + { + if (!static::$languagesNames) { + static::$languagesNames = require __DIR__.'/List/languages.php'; + } + + return static::$languagesNames; + } + + /** + * Get the list of the known regions. + * + * @return array + */ + public static function regions() + { + if (!static::$regionsNames) { + static::$regionsNames = require __DIR__.'/List/regions.php'; + } + + return static::$regionsNames; + } + + /** + * Get both isoName and nativeName as an array. + * + * @return array + */ + public function getNames(): array + { + if (!$this->names) { + $this->names = static::all()[$this->code] ?? [ + 'isoName' => $this->code, + 'nativeName' => $this->code, + ]; + } + + return $this->names; + } + + /** + * Returns the original locale ID. + * + * @return string + */ + public function getId(): string + { + return $this->id; + } + + /** + * Returns the code of the locale "en"/"fr". + * + * @return string + */ + public function getCode(): string + { + return $this->code; + } + + /** + * Returns the variant code such as cyrl/latn. + * + * @return string|null + */ + public function getVariant(): ?string + { + return $this->variant; + } + + /** + * Returns the variant such as Cyrillic/Latin. + * + * @return string|null + */ + public function getVariantName(): ?string + { + if ($this->variant === 'Latn') { + return 'Latin'; + } + + if ($this->variant === 'Cyrl') { + return 'Cyrillic'; + } + + return $this->variant; + } + + /** + * Returns the region part of the locale. + * + * @return string|null + */ + public function getRegion(): ?string + { + return $this->region; + } + + /** + * Returns the region name for the current language. + * + * @return string|null + */ + public function getRegionName(): ?string + { + return $this->region ? (static::regions()[$this->region] ?? $this->region) : null; + } + + /** + * Returns the long ISO language name. + * + * @return string + */ + public function getFullIsoName(): string + { + if (!$this->isoName) { + $this->isoName = $this->getNames()['isoName']; + } + + return $this->isoName; + } + + /** + * Set the ISO language name. + * + * @param string $isoName + */ + public function setIsoName(string $isoName): self + { + $this->isoName = $isoName; + + return $this; + } + + /** + * Return the full name of the language in this language. + * + * @return string + */ + public function getFullNativeName(): string + { + if (!$this->nativeName) { + $this->nativeName = $this->getNames()['nativeName']; + } + + return $this->nativeName; + } + + /** + * Set the name of the language in this language. + * + * @param string $nativeName + */ + public function setNativeName(string $nativeName): self + { + $this->nativeName = $nativeName; + + return $this; + } + + /** + * Returns the short ISO language name. + * + * @return string + */ + public function getIsoName(): string + { + $name = $this->getFullIsoName(); + + return trim(strstr($name, ',', true) ?: $name); + } + + /** + * Get the short name of the language in this language. + * + * @return string + */ + public function getNativeName(): string + { + $name = $this->getFullNativeName(); + + return trim(strstr($name, ',', true) ?: $name); + } + + /** + * Get a string with short ISO name, region in parentheses if applicable, variant in parentheses if applicable. + * + * @return string + */ + public function getIsoDescription() + { + $region = $this->getRegionName(); + $variant = $this->getVariantName(); + + return $this->getIsoName().($region ? ' ('.$region.')' : '').($variant ? ' ('.$variant.')' : ''); + } + + /** + * Get a string with short native name, region in parentheses if applicable, variant in parentheses if applicable. + * + * @return string + */ + public function getNativeDescription() + { + $region = $this->getRegionName(); + $variant = $this->getVariantName(); + + return $this->getNativeName().($region ? ' ('.$region.')' : '').($variant ? ' ('.$variant.')' : ''); + } + + /** + * Get a string with long ISO name, region in parentheses if applicable, variant in parentheses if applicable. + * + * @return string + */ + public function getFullIsoDescription() + { + $region = $this->getRegionName(); + $variant = $this->getVariantName(); + + return $this->getFullIsoName().($region ? ' ('.$region.')' : '').($variant ? ' ('.$variant.')' : ''); + } + + /** + * Get a string with long native name, region in parentheses if applicable, variant in parentheses if applicable. + * + * @return string + */ + public function getFullNativeDescription() + { + $region = $this->getRegionName(); + $variant = $this->getVariantName(); + + return $this->getFullNativeName().($region ? ' ('.$region.')' : '').($variant ? ' ('.$variant.')' : ''); + } + + /** + * Returns the original locale ID. + * + * @return string + */ + public function __toString() + { + return $this->getId(); + } + + /** + * Get a string with short ISO name, region in parentheses if applicable, variant in parentheses if applicable. + * + * @return string + */ + #[ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->getIsoDescription(); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Laravel/ServiceProvider.php b/vendor/nesbot/carbon/src/Carbon/Laravel/ServiceProvider.php new file mode 100644 index 0000000..8be0569 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Laravel/ServiceProvider.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Laravel; + +use Carbon\Carbon; +use Carbon\CarbonImmutable; +use Carbon\CarbonInterval; +use Carbon\CarbonPeriod; +use Illuminate\Contracts\Events\Dispatcher as DispatcherContract; +use Illuminate\Events\Dispatcher; +use Illuminate\Events\EventDispatcher; +use Illuminate\Support\Carbon as IlluminateCarbon; +use Illuminate\Support\Facades\Date; +use Throwable; + +class ServiceProvider extends \Illuminate\Support\ServiceProvider +{ + public function boot() + { + $this->updateLocale(); + + if (!$this->app->bound('events')) { + return; + } + + $service = $this; + $events = $this->app['events']; + + if ($this->isEventDispatcher($events)) { + $events->listen(class_exists('Illuminate\Foundation\Events\LocaleUpdated') ? 'Illuminate\Foundation\Events\LocaleUpdated' : 'locale.changed', function () use ($service) { + $service->updateLocale(); + }); + } + } + + public function updateLocale() + { + $app = $this->app && method_exists($this->app, 'getLocale') ? $this->app : app('translator'); + $locale = $app->getLocale(); + Carbon::setLocale($locale); + CarbonImmutable::setLocale($locale); + CarbonPeriod::setLocale($locale); + CarbonInterval::setLocale($locale); + + if (class_exists(IlluminateCarbon::class)) { + IlluminateCarbon::setLocale($locale); + } + + if (class_exists(Date::class)) { + try { + $root = Date::getFacadeRoot(); + $root->setLocale($locale); + } catch (Throwable $e) { + // Non Carbon class in use in Date facade + } + } + } + + public function register() + { + // Needed for Laravel < 5.3 compatibility + } + + protected function isEventDispatcher($instance) + { + return $instance instanceof EventDispatcher + || $instance instanceof Dispatcher + || $instance instanceof DispatcherContract; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/List/languages.php b/vendor/nesbot/carbon/src/Carbon/List/languages.php new file mode 100644 index 0000000..5b5d9a1 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/List/languages.php @@ -0,0 +1,1239 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + /* + * ISO 639-2 + */ + 'ab' => [ + 'isoName' => 'Abkhazian', + 'nativeName' => 'аҧсуа бызшәа, аҧсшәа', + ], + 'aa' => [ + 'isoName' => 'Afar', + 'nativeName' => 'Afaraf', + ], + 'af' => [ + 'isoName' => 'Afrikaans', + 'nativeName' => 'Afrikaans', + ], + 'ak' => [ + 'isoName' => 'Akan', + 'nativeName' => 'Akan', + ], + 'sq' => [ + 'isoName' => 'Albanian', + 'nativeName' => 'Shqip', + ], + 'am' => [ + 'isoName' => 'Amharic', + 'nativeName' => 'አማርኛ', + ], + 'ar' => [ + 'isoName' => 'Arabic', + 'nativeName' => 'العربية', + ], + 'an' => [ + 'isoName' => 'Aragonese', + 'nativeName' => 'aragonés', + ], + 'hy' => [ + 'isoName' => 'Armenian', + 'nativeName' => 'Հայերեն', + ], + 'as' => [ + 'isoName' => 'Assamese', + 'nativeName' => 'অসমীয়া', + ], + 'av' => [ + 'isoName' => 'Avaric', + 'nativeName' => 'авар мацӀ, магӀарул мацӀ', + ], + 'ae' => [ + 'isoName' => 'Avestan', + 'nativeName' => 'avesta', + ], + 'ay' => [ + 'isoName' => 'Aymara', + 'nativeName' => 'aymar aru', + ], + 'az' => [ + 'isoName' => 'Azerbaijani', + 'nativeName' => 'azərbaycan dili', + ], + 'bm' => [ + 'isoName' => 'Bambara', + 'nativeName' => 'bamanankan', + ], + 'ba' => [ + 'isoName' => 'Bashkir', + 'nativeName' => 'башҡорт теле', + ], + 'eu' => [ + 'isoName' => 'Basque', + 'nativeName' => 'euskara, euskera', + ], + 'be' => [ + 'isoName' => 'Belarusian', + 'nativeName' => 'беларуская мова', + ], + 'bn' => [ + 'isoName' => 'Bengali', + 'nativeName' => 'বাংলা', + ], + 'bh' => [ + 'isoName' => 'Bihari languages', + 'nativeName' => 'भोजपुरी', + ], + 'bi' => [ + 'isoName' => 'Bislama', + 'nativeName' => 'Bislama', + ], + 'bs' => [ + 'isoName' => 'Bosnian', + 'nativeName' => 'bosanski jezik', + ], + 'br' => [ + 'isoName' => 'Breton', + 'nativeName' => 'brezhoneg', + ], + 'bg' => [ + 'isoName' => 'Bulgarian', + 'nativeName' => 'български език', + ], + 'my' => [ + 'isoName' => 'Burmese', + 'nativeName' => 'ဗမာစာ', + ], + 'ca' => [ + 'isoName' => 'Catalan, Valencian', + 'nativeName' => 'català, valencià', + ], + 'ch' => [ + 'isoName' => 'Chamorro', + 'nativeName' => 'Chamoru', + ], + 'ce' => [ + 'isoName' => 'Chechen', + 'nativeName' => 'нохчийн мотт', + ], + 'ny' => [ + 'isoName' => 'Chichewa, Chewa, Nyanja', + 'nativeName' => 'chiCheŵa, chinyanja', + ], + 'zh' => [ + 'isoName' => 'Chinese', + 'nativeName' => '中文 (Zhōngwén), 汉语, 漢語', + ], + 'cv' => [ + 'isoName' => 'Chuvash', + 'nativeName' => 'чӑваш чӗлхи', + ], + 'kw' => [ + 'isoName' => 'Cornish', + 'nativeName' => 'Kernewek', + ], + 'co' => [ + 'isoName' => 'Corsican', + 'nativeName' => 'corsu, lingua corsa', + ], + 'cr' => [ + 'isoName' => 'Cree', + 'nativeName' => 'ᓀᐦᐃᔭᐍᐏᐣ', + ], + 'hr' => [ + 'isoName' => 'Croatian', + 'nativeName' => 'hrvatski jezik', + ], + 'cs' => [ + 'isoName' => 'Czech', + 'nativeName' => 'čeština, český jazyk', + ], + 'da' => [ + 'isoName' => 'Danish', + 'nativeName' => 'dansk', + ], + 'dv' => [ + 'isoName' => 'Divehi, Dhivehi, Maldivian', + 'nativeName' => 'ދިވެހި', + ], + 'nl' => [ + 'isoName' => 'Dutch, Flemish', + 'nativeName' => 'Nederlands, Vlaams', + ], + 'dz' => [ + 'isoName' => 'Dzongkha', + 'nativeName' => 'རྫོང་ཁ', + ], + 'en' => [ + 'isoName' => 'English', + 'nativeName' => 'English', + ], + 'eo' => [ + 'isoName' => 'Esperanto', + 'nativeName' => 'Esperanto', + ], + 'et' => [ + 'isoName' => 'Estonian', + 'nativeName' => 'eesti, eesti keel', + ], + 'ee' => [ + 'isoName' => 'Ewe', + 'nativeName' => 'Eʋegbe', + ], + 'fo' => [ + 'isoName' => 'Faroese', + 'nativeName' => 'føroyskt', + ], + 'fj' => [ + 'isoName' => 'Fijian', + 'nativeName' => 'vosa Vakaviti', + ], + 'fi' => [ + 'isoName' => 'Finnish', + 'nativeName' => 'suomi, suomen kieli', + ], + 'fr' => [ + 'isoName' => 'French', + 'nativeName' => 'français', + ], + 'ff' => [ + 'isoName' => 'Fulah', + 'nativeName' => 'Fulfulde, Pulaar, Pular', + ], + 'gl' => [ + 'isoName' => 'Galician', + 'nativeName' => 'Galego', + ], + 'ka' => [ + 'isoName' => 'Georgian', + 'nativeName' => 'ქართული', + ], + 'de' => [ + 'isoName' => 'German', + 'nativeName' => 'Deutsch', + ], + 'el' => [ + 'isoName' => 'Greek (modern)', + 'nativeName' => 'ελληνικά', + ], + 'gn' => [ + 'isoName' => 'Guaraní', + 'nativeName' => 'Avañe\'ẽ', + ], + 'gu' => [ + 'isoName' => 'Gujarati', + 'nativeName' => 'ગુજરાતી', + ], + 'ht' => [ + 'isoName' => 'Haitian, Haitian Creole', + 'nativeName' => 'Kreyòl ayisyen', + ], + 'ha' => [ + 'isoName' => 'Hausa', + 'nativeName' => '(Hausa) هَوُسَ', + ], + 'he' => [ + 'isoName' => 'Hebrew (modern)', + 'nativeName' => 'עברית', + ], + 'hz' => [ + 'isoName' => 'Herero', + 'nativeName' => 'Otjiherero', + ], + 'hi' => [ + 'isoName' => 'Hindi', + 'nativeName' => 'हिन्दी, हिंदी', + ], + 'ho' => [ + 'isoName' => 'Hiri Motu', + 'nativeName' => 'Hiri Motu', + ], + 'hu' => [ + 'isoName' => 'Hungarian', + 'nativeName' => 'magyar', + ], + 'ia' => [ + 'isoName' => 'Interlingua', + 'nativeName' => 'Interlingua', + ], + 'id' => [ + 'isoName' => 'Indonesian', + 'nativeName' => 'Bahasa Indonesia', + ], + 'ie' => [ + 'isoName' => 'Interlingue', + 'nativeName' => 'Originally called Occidental; then Interlingue after WWII', + ], + 'ga' => [ + 'isoName' => 'Irish', + 'nativeName' => 'Gaeilge', + ], + 'ig' => [ + 'isoName' => 'Igbo', + 'nativeName' => 'Asụsụ Igbo', + ], + 'ik' => [ + 'isoName' => 'Inupiaq', + 'nativeName' => 'Iñupiaq, Iñupiatun', + ], + 'io' => [ + 'isoName' => 'Ido', + 'nativeName' => 'Ido', + ], + 'is' => [ + 'isoName' => 'Icelandic', + 'nativeName' => 'Íslenska', + ], + 'it' => [ + 'isoName' => 'Italian', + 'nativeName' => 'Italiano', + ], + 'iu' => [ + 'isoName' => 'Inuktitut', + 'nativeName' => 'ᐃᓄᒃᑎᑐᑦ', + ], + 'ja' => [ + 'isoName' => 'Japanese', + 'nativeName' => '日本語 (にほんご)', + ], + 'jv' => [ + 'isoName' => 'Javanese', + 'nativeName' => 'ꦧꦱꦗꦮ, Basa Jawa', + ], + 'kl' => [ + 'isoName' => 'Kalaallisut, Greenlandic', + 'nativeName' => 'kalaallisut, kalaallit oqaasii', + ], + 'kn' => [ + 'isoName' => 'Kannada', + 'nativeName' => 'ಕನ್ನಡ', + ], + 'kr' => [ + 'isoName' => 'Kanuri', + 'nativeName' => 'Kanuri', + ], + 'ks' => [ + 'isoName' => 'Kashmiri', + 'nativeName' => 'कश्मीरी, كشميري‎', + ], + 'kk' => [ + 'isoName' => 'Kazakh', + 'nativeName' => 'қазақ тілі', + ], + 'km' => [ + 'isoName' => 'Central Khmer', + 'nativeName' => 'ខ្មែរ, ខេមរភាសា, ភាសាខ្មែរ', + ], + 'ki' => [ + 'isoName' => 'Kikuyu, Gikuyu', + 'nativeName' => 'Gĩkũyũ', + ], + 'rw' => [ + 'isoName' => 'Kinyarwanda', + 'nativeName' => 'Ikinyarwanda', + ], + 'ky' => [ + 'isoName' => 'Kirghiz, Kyrgyz', + 'nativeName' => 'Кыргызча, Кыргыз тили', + ], + 'kv' => [ + 'isoName' => 'Komi', + 'nativeName' => 'коми кыв', + ], + 'kg' => [ + 'isoName' => 'Kongo', + 'nativeName' => 'Kikongo', + ], + 'ko' => [ + 'isoName' => 'Korean', + 'nativeName' => '한국어', + ], + 'ku' => [ + 'isoName' => 'Kurdish', + 'nativeName' => 'Kurdî, کوردی‎', + ], + 'kj' => [ + 'isoName' => 'Kuanyama, Kwanyama', + 'nativeName' => 'Kuanyama', + ], + 'la' => [ + 'isoName' => 'Latin', + 'nativeName' => 'latine, lingua latina', + ], + 'lb' => [ + 'isoName' => 'Luxembourgish, Letzeburgesch', + 'nativeName' => 'Lëtzebuergesch', + ], + 'lg' => [ + 'isoName' => 'Ganda', + 'nativeName' => 'Luganda', + ], + 'li' => [ + 'isoName' => 'Limburgan, Limburger, Limburgish', + 'nativeName' => 'Limburgs', + ], + 'ln' => [ + 'isoName' => 'Lingala', + 'nativeName' => 'Lingála', + ], + 'lo' => [ + 'isoName' => 'Lao', + 'nativeName' => 'ພາສາລາວ', + ], + 'lt' => [ + 'isoName' => 'Lithuanian', + 'nativeName' => 'lietuvių kalba', + ], + 'lu' => [ + 'isoName' => 'Luba-Katanga', + 'nativeName' => 'Kiluba', + ], + 'lv' => [ + 'isoName' => 'Latvian', + 'nativeName' => 'latviešu valoda', + ], + 'gv' => [ + 'isoName' => 'Manx', + 'nativeName' => 'Gaelg, Gailck', + ], + 'mk' => [ + 'isoName' => 'Macedonian', + 'nativeName' => 'македонски јазик', + ], + 'mg' => [ + 'isoName' => 'Malagasy', + 'nativeName' => 'fiteny malagasy', + ], + 'ms' => [ + 'isoName' => 'Malay', + 'nativeName' => 'Bahasa Melayu, بهاس ملايو‎', + ], + 'ml' => [ + 'isoName' => 'Malayalam', + 'nativeName' => 'മലയാളം', + ], + 'mt' => [ + 'isoName' => 'Maltese', + 'nativeName' => 'Malti', + ], + 'mi' => [ + 'isoName' => 'Maori', + 'nativeName' => 'te reo Māori', + ], + 'mr' => [ + 'isoName' => 'Marathi', + 'nativeName' => 'मराठी', + ], + 'mh' => [ + 'isoName' => 'Marshallese', + 'nativeName' => 'Kajin M̧ajeļ', + ], + 'mn' => [ + 'isoName' => 'Mongolian', + 'nativeName' => 'Монгол хэл', + ], + 'na' => [ + 'isoName' => 'Nauru', + 'nativeName' => 'Dorerin Naoero', + ], + 'nv' => [ + 'isoName' => 'Navajo, Navaho', + 'nativeName' => 'Diné bizaad', + ], + 'nd' => [ + 'isoName' => 'North Ndebele', + 'nativeName' => 'isiNdebele', + ], + 'ne' => [ + 'isoName' => 'Nepali', + 'nativeName' => 'नेपाली', + ], + 'ng' => [ + 'isoName' => 'Ndonga', + 'nativeName' => 'Owambo', + ], + 'nb' => [ + 'isoName' => 'Norwegian Bokmål', + 'nativeName' => 'Norsk Bokmål', + ], + 'nn' => [ + 'isoName' => 'Norwegian Nynorsk', + 'nativeName' => 'Norsk Nynorsk', + ], + 'no' => [ + 'isoName' => 'Norwegian', + 'nativeName' => 'Norsk', + ], + 'ii' => [ + 'isoName' => 'Sichuan Yi, Nuosu', + 'nativeName' => 'ꆈꌠ꒿ Nuosuhxop', + ], + 'nr' => [ + 'isoName' => 'South Ndebele', + 'nativeName' => 'isiNdebele', + ], + 'oc' => [ + 'isoName' => 'Occitan', + 'nativeName' => 'occitan, lenga d\'òc', + ], + 'oj' => [ + 'isoName' => 'Ojibwa', + 'nativeName' => 'ᐊᓂᔑᓈᐯᒧᐎᓐ', + ], + 'cu' => [ + 'isoName' => 'Church Slavic, Church Slavonic, Old Church Slavonic, Old Slavonic, Old Bulgarian', + 'nativeName' => 'ѩзыкъ словѣньскъ', + ], + 'om' => [ + 'isoName' => 'Oromo', + 'nativeName' => 'Afaan Oromoo', + ], + 'or' => [ + 'isoName' => 'Oriya', + 'nativeName' => 'ଓଡ଼ିଆ', + ], + 'os' => [ + 'isoName' => 'Ossetian, Ossetic', + 'nativeName' => 'ирон æвзаг', + ], + 'pa' => [ + 'isoName' => 'Panjabi, Punjabi', + 'nativeName' => 'ਪੰਜਾਬੀ', + ], + 'pi' => [ + 'isoName' => 'Pali', + 'nativeName' => 'पाऴि', + ], + 'fa' => [ + 'isoName' => 'Persian', + 'nativeName' => 'فارسی', + ], + 'pl' => [ + 'isoName' => 'Polish', + 'nativeName' => 'język polski, polszczyzna', + ], + 'ps' => [ + 'isoName' => 'Pashto, Pushto', + 'nativeName' => 'پښتو', + ], + 'pt' => [ + 'isoName' => 'Portuguese', + 'nativeName' => 'Português', + ], + 'qu' => [ + 'isoName' => 'Quechua', + 'nativeName' => 'Runa Simi, Kichwa', + ], + 'rm' => [ + 'isoName' => 'Romansh', + 'nativeName' => 'Rumantsch Grischun', + ], + 'rn' => [ + 'isoName' => 'Rundi', + 'nativeName' => 'Ikirundi', + ], + 'ro' => [ + 'isoName' => 'Romanian, Moldavian, Moldovan', + 'nativeName' => 'Română', + ], + 'ru' => [ + 'isoName' => 'Russian', + 'nativeName' => 'русский', + ], + 'sa' => [ + 'isoName' => 'Sanskrit', + 'nativeName' => 'संस्कृतम्', + ], + 'sc' => [ + 'isoName' => 'Sardinian', + 'nativeName' => 'sardu', + ], + 'sd' => [ + 'isoName' => 'Sindhi', + 'nativeName' => 'सिन्धी, سنڌي، سندھی‎', + ], + 'se' => [ + 'isoName' => 'Northern Sami', + 'nativeName' => 'Davvisámegiella', + ], + 'sm' => [ + 'isoName' => 'Samoan', + 'nativeName' => 'gagana fa\'a Samoa', + ], + 'sg' => [ + 'isoName' => 'Sango', + 'nativeName' => 'yângâ tî sängö', + ], + 'sr' => [ + 'isoName' => 'Serbian', + 'nativeName' => 'српски језик', + ], + 'gd' => [ + 'isoName' => 'Gaelic, Scottish Gaelic', + 'nativeName' => 'Gàidhlig', + ], + 'sn' => [ + 'isoName' => 'Shona', + 'nativeName' => 'chiShona', + ], + 'si' => [ + 'isoName' => 'Sinhala, Sinhalese', + 'nativeName' => 'සිංහල', + ], + 'sk' => [ + 'isoName' => 'Slovak', + 'nativeName' => 'Slovenčina, Slovenský Jazyk', + ], + 'sl' => [ + 'isoName' => 'Slovene', + 'nativeName' => 'Slovenski Jezik, Slovenščina', + ], + 'so' => [ + 'isoName' => 'Somali', + 'nativeName' => 'Soomaaliga, af Soomaali', + ], + 'st' => [ + 'isoName' => 'Southern Sotho', + 'nativeName' => 'Sesotho', + ], + 'es' => [ + 'isoName' => 'Spanish, Castilian', + 'nativeName' => 'Español', + ], + 'su' => [ + 'isoName' => 'Sundanese', + 'nativeName' => 'Basa Sunda', + ], + 'sw' => [ + 'isoName' => 'Swahili', + 'nativeName' => 'Kiswahili', + ], + 'ss' => [ + 'isoName' => 'Swati', + 'nativeName' => 'SiSwati', + ], + 'sv' => [ + 'isoName' => 'Swedish', + 'nativeName' => 'Svenska', + ], + 'ta' => [ + 'isoName' => 'Tamil', + 'nativeName' => 'தமிழ்', + ], + 'te' => [ + 'isoName' => 'Telugu', + 'nativeName' => 'తెలుగు', + ], + 'tg' => [ + 'isoName' => 'Tajik', + 'nativeName' => 'тоҷикӣ, toçikī, تاجیکی‎', + ], + 'th' => [ + 'isoName' => 'Thai', + 'nativeName' => 'ไทย', + ], + 'ti' => [ + 'isoName' => 'Tigrinya', + 'nativeName' => 'ትግርኛ', + ], + 'bo' => [ + 'isoName' => 'Tibetan', + 'nativeName' => 'བོད་ཡིག', + ], + 'tk' => [ + 'isoName' => 'Turkmen', + 'nativeName' => 'Türkmen, Түркмен', + ], + 'tl' => [ + 'isoName' => 'Tagalog', + 'nativeName' => 'Wikang Tagalog', + ], + 'tn' => [ + 'isoName' => 'Tswana', + 'nativeName' => 'Setswana', + ], + 'to' => [ + 'isoName' => 'Tongan (Tonga Islands)', + 'nativeName' => 'Faka Tonga', + ], + 'tr' => [ + 'isoName' => 'Turkish', + 'nativeName' => 'Türkçe', + ], + 'ts' => [ + 'isoName' => 'Tsonga', + 'nativeName' => 'Xitsonga', + ], + 'tt' => [ + 'isoName' => 'Tatar', + 'nativeName' => 'татар теле, tatar tele', + ], + 'tw' => [ + 'isoName' => 'Twi', + 'nativeName' => 'Twi', + ], + 'ty' => [ + 'isoName' => 'Tahitian', + 'nativeName' => 'Reo Tahiti', + ], + 'ug' => [ + 'isoName' => 'Uighur, Uyghur', + 'nativeName' => 'Uyƣurqə, ‫ئۇيغۇرچ', + ], + 'uk' => [ + 'isoName' => 'Ukrainian', + 'nativeName' => 'Українська', + ], + 'ur' => [ + 'isoName' => 'Urdu', + 'nativeName' => 'اردو', + ], + 'uz' => [ + 'isoName' => 'Uzbek', + 'nativeName' => 'Oʻzbek, Ўзбек, أۇزبېك‎', + ], + 've' => [ + 'isoName' => 'Venda', + 'nativeName' => 'Tshivenḓa', + ], + 'vi' => [ + 'isoName' => 'Vietnamese', + 'nativeName' => 'Tiếng Việt', + ], + 'vo' => [ + 'isoName' => 'Volapük', + 'nativeName' => 'Volapük', + ], + 'wa' => [ + 'isoName' => 'Walloon', + 'nativeName' => 'Walon', + ], + 'cy' => [ + 'isoName' => 'Welsh', + 'nativeName' => 'Cymraeg', + ], + 'wo' => [ + 'isoName' => 'Wolof', + 'nativeName' => 'Wollof', + ], + 'fy' => [ + 'isoName' => 'Western Frisian', + 'nativeName' => 'Frysk', + ], + 'xh' => [ + 'isoName' => 'Xhosa', + 'nativeName' => 'isiXhosa', + ], + 'yi' => [ + 'isoName' => 'Yiddish', + 'nativeName' => 'ייִדיש', + ], + 'yo' => [ + 'isoName' => 'Yoruba', + 'nativeName' => 'Yorùbá', + ], + 'za' => [ + 'isoName' => 'Zhuang, Chuang', + 'nativeName' => 'Saɯ cueŋƅ, Saw cuengh', + ], + 'zu' => [ + 'isoName' => 'Zulu', + 'nativeName' => 'isiZulu', + ], + /* + * Add ISO 639-3 languages available in Carbon + */ + 'agq' => [ + 'isoName' => 'Aghem', + 'nativeName' => 'Aghem', + ], + 'agr' => [ + 'isoName' => 'Aguaruna', + 'nativeName' => 'Aguaruna', + ], + 'anp' => [ + 'isoName' => 'Angika', + 'nativeName' => 'Angika', + ], + 'asa' => [ + 'isoName' => 'Asu', + 'nativeName' => 'Asu', + ], + 'ast' => [ + 'isoName' => 'Asturian', + 'nativeName' => 'Asturian', + ], + 'ayc' => [ + 'isoName' => 'Southern Aymara', + 'nativeName' => 'Southern Aymara', + ], + 'bas' => [ + 'isoName' => 'Basaa', + 'nativeName' => 'Basaa', + ], + 'bem' => [ + 'isoName' => 'Bemba', + 'nativeName' => 'Bemba', + ], + 'bez' => [ + 'isoName' => 'Bena', + 'nativeName' => 'Bena', + ], + 'bhb' => [ + 'isoName' => 'Bhili', + 'nativeName' => 'Bhili', + ], + 'bho' => [ + 'isoName' => 'Bhojpuri', + 'nativeName' => 'Bhojpuri', + ], + 'brx' => [ + 'isoName' => 'Bodo', + 'nativeName' => 'Bodo', + ], + 'byn' => [ + 'isoName' => 'Bilin', + 'nativeName' => 'Bilin', + ], + 'ccp' => [ + 'isoName' => 'Chakma', + 'nativeName' => 'Chakma', + ], + 'cgg' => [ + 'isoName' => 'Chiga', + 'nativeName' => 'Chiga', + ], + 'chr' => [ + 'isoName' => 'Cherokee', + 'nativeName' => 'Cherokee', + ], + 'cmn' => [ + 'isoName' => 'Chinese', + 'nativeName' => 'Chinese', + ], + 'crh' => [ + 'isoName' => 'Crimean Turkish', + 'nativeName' => 'Crimean Turkish', + ], + 'csb' => [ + 'isoName' => 'Kashubian', + 'nativeName' => 'Kashubian', + ], + 'dav' => [ + 'isoName' => 'Taita', + 'nativeName' => 'Taita', + ], + 'dje' => [ + 'isoName' => 'Zarma', + 'nativeName' => 'Zarma', + ], + 'doi' => [ + 'isoName' => 'Dogri (macrolanguage)', + 'nativeName' => 'Dogri (macrolanguage)', + ], + 'dsb' => [ + 'isoName' => 'Lower Sorbian', + 'nativeName' => 'Lower Sorbian', + ], + 'dua' => [ + 'isoName' => 'Duala', + 'nativeName' => 'Duala', + ], + 'dyo' => [ + 'isoName' => 'Jola-Fonyi', + 'nativeName' => 'Jola-Fonyi', + ], + 'ebu' => [ + 'isoName' => 'Embu', + 'nativeName' => 'Embu', + ], + 'ewo' => [ + 'isoName' => 'Ewondo', + 'nativeName' => 'Ewondo', + ], + 'fil' => [ + 'isoName' => 'Filipino', + 'nativeName' => 'Filipino', + ], + 'fur' => [ + 'isoName' => 'Friulian', + 'nativeName' => 'Friulian', + ], + 'gez' => [ + 'isoName' => 'Geez', + 'nativeName' => 'Geez', + ], + 'gom' => [ + 'isoName' => 'Konkani, Goan', + 'nativeName' => 'ಕೊಂಕಣಿ', + ], + 'gsw' => [ + 'isoName' => 'Swiss German', + 'nativeName' => 'Swiss German', + ], + 'guz' => [ + 'isoName' => 'Gusii', + 'nativeName' => 'Gusii', + ], + 'hak' => [ + 'isoName' => 'Hakka Chinese', + 'nativeName' => 'Hakka Chinese', + ], + 'haw' => [ + 'isoName' => 'Hawaiian', + 'nativeName' => 'Hawaiian', + ], + 'hif' => [ + 'isoName' => 'Fiji Hindi', + 'nativeName' => 'Fiji Hindi', + ], + 'hne' => [ + 'isoName' => 'Chhattisgarhi', + 'nativeName' => 'Chhattisgarhi', + ], + 'hsb' => [ + 'isoName' => 'Upper Sorbian', + 'nativeName' => 'Upper Sorbian', + ], + 'jgo' => [ + 'isoName' => 'Ngomba', + 'nativeName' => 'Ngomba', + ], + 'jmc' => [ + 'isoName' => 'Machame', + 'nativeName' => 'Machame', + ], + 'kab' => [ + 'isoName' => 'Kabyle', + 'nativeName' => 'Kabyle', + ], + 'kam' => [ + 'isoName' => 'Kamba', + 'nativeName' => 'Kamba', + ], + 'kde' => [ + 'isoName' => 'Makonde', + 'nativeName' => 'Makonde', + ], + 'kea' => [ + 'isoName' => 'Kabuverdianu', + 'nativeName' => 'Kabuverdianu', + ], + 'khq' => [ + 'isoName' => 'Koyra Chiini', + 'nativeName' => 'Koyra Chiini', + ], + 'kkj' => [ + 'isoName' => 'Kako', + 'nativeName' => 'Kako', + ], + 'kln' => [ + 'isoName' => 'Kalenjin', + 'nativeName' => 'Kalenjin', + ], + 'kok' => [ + 'isoName' => 'Konkani', + 'nativeName' => 'Konkani', + ], + 'ksb' => [ + 'isoName' => 'Shambala', + 'nativeName' => 'Shambala', + ], + 'ksf' => [ + 'isoName' => 'Bafia', + 'nativeName' => 'Bafia', + ], + 'ksh' => [ + 'isoName' => 'Colognian', + 'nativeName' => 'Colognian', + ], + 'lag' => [ + 'isoName' => 'Langi', + 'nativeName' => 'Langi', + ], + 'lij' => [ + 'isoName' => 'Ligurian', + 'nativeName' => 'Ligurian', + ], + 'lkt' => [ + 'isoName' => 'Lakota', + 'nativeName' => 'Lakota', + ], + 'lrc' => [ + 'isoName' => 'Northern Luri', + 'nativeName' => 'Northern Luri', + ], + 'luo' => [ + 'isoName' => 'Luo', + 'nativeName' => 'Luo', + ], + 'luy' => [ + 'isoName' => 'Luyia', + 'nativeName' => 'Luyia', + ], + 'lzh' => [ + 'isoName' => 'Literary Chinese', + 'nativeName' => 'Literary Chinese', + ], + 'mag' => [ + 'isoName' => 'Magahi', + 'nativeName' => 'Magahi', + ], + 'mai' => [ + 'isoName' => 'Maithili', + 'nativeName' => 'Maithili', + ], + 'mas' => [ + 'isoName' => 'Masai', + 'nativeName' => 'Masai', + ], + 'mer' => [ + 'isoName' => 'Meru', + 'nativeName' => 'Meru', + ], + 'mfe' => [ + 'isoName' => 'Morisyen', + 'nativeName' => 'Morisyen', + ], + 'mgh' => [ + 'isoName' => 'Makhuwa-Meetto', + 'nativeName' => 'Makhuwa-Meetto', + ], + 'mgo' => [ + 'isoName' => 'Metaʼ', + 'nativeName' => 'Metaʼ', + ], + 'mhr' => [ + 'isoName' => 'Eastern Mari', + 'nativeName' => 'Eastern Mari', + ], + 'miq' => [ + 'isoName' => 'Mískito', + 'nativeName' => 'Mískito', + ], + 'mjw' => [ + 'isoName' => 'Karbi', + 'nativeName' => 'Karbi', + ], + 'mni' => [ + 'isoName' => 'Manipuri', + 'nativeName' => 'Manipuri', + ], + 'mua' => [ + 'isoName' => 'Mundang', + 'nativeName' => 'Mundang', + ], + 'mzn' => [ + 'isoName' => 'Mazanderani', + 'nativeName' => 'Mazanderani', + ], + 'nan' => [ + 'isoName' => 'Min Nan Chinese', + 'nativeName' => 'Min Nan Chinese', + ], + 'naq' => [ + 'isoName' => 'Nama', + 'nativeName' => 'Nama', + ], + 'nds' => [ + 'isoName' => 'Low German', + 'nativeName' => 'Low German', + ], + 'nhn' => [ + 'isoName' => 'Central Nahuatl', + 'nativeName' => 'Central Nahuatl', + ], + 'niu' => [ + 'isoName' => 'Niuean', + 'nativeName' => 'Niuean', + ], + 'nmg' => [ + 'isoName' => 'Kwasio', + 'nativeName' => 'Kwasio', + ], + 'nnh' => [ + 'isoName' => 'Ngiemboon', + 'nativeName' => 'Ngiemboon', + ], + 'nso' => [ + 'isoName' => 'Northern Sotho', + 'nativeName' => 'Northern Sotho', + ], + 'nus' => [ + 'isoName' => 'Nuer', + 'nativeName' => 'Nuer', + ], + 'nyn' => [ + 'isoName' => 'Nyankole', + 'nativeName' => 'Nyankole', + ], + 'pap' => [ + 'isoName' => 'Papiamento', + 'nativeName' => 'Papiamento', + ], + 'prg' => [ + 'isoName' => 'Prussian', + 'nativeName' => 'Prussian', + ], + 'quz' => [ + 'isoName' => 'Cusco Quechua', + 'nativeName' => 'Cusco Quechua', + ], + 'raj' => [ + 'isoName' => 'Rajasthani', + 'nativeName' => 'Rajasthani', + ], + 'rof' => [ + 'isoName' => 'Rombo', + 'nativeName' => 'Rombo', + ], + 'rwk' => [ + 'isoName' => 'Rwa', + 'nativeName' => 'Rwa', + ], + 'sah' => [ + 'isoName' => 'Sakha', + 'nativeName' => 'Sakha', + ], + 'saq' => [ + 'isoName' => 'Samburu', + 'nativeName' => 'Samburu', + ], + 'sat' => [ + 'isoName' => 'Santali', + 'nativeName' => 'Santali', + ], + 'sbp' => [ + 'isoName' => 'Sangu', + 'nativeName' => 'Sangu', + ], + 'scr' => [ + 'isoName' => 'Serbo Croatian', + 'nativeName' => 'Serbo Croatian', + ], + 'seh' => [ + 'isoName' => 'Sena', + 'nativeName' => 'Sena', + ], + 'ses' => [ + 'isoName' => 'Koyraboro Senni', + 'nativeName' => 'Koyraboro Senni', + ], + 'sgs' => [ + 'isoName' => 'Samogitian', + 'nativeName' => 'Samogitian', + ], + 'shi' => [ + 'isoName' => 'Tachelhit', + 'nativeName' => 'Tachelhit', + ], + 'shn' => [ + 'isoName' => 'Shan', + 'nativeName' => 'Shan', + ], + 'shs' => [ + 'isoName' => 'Shuswap', + 'nativeName' => 'Shuswap', + ], + 'sid' => [ + 'isoName' => 'Sidamo', + 'nativeName' => 'Sidamo', + ], + 'smn' => [ + 'isoName' => 'Inari Sami', + 'nativeName' => 'Inari Sami', + ], + 'szl' => [ + 'isoName' => 'Silesian', + 'nativeName' => 'Silesian', + ], + 'tcy' => [ + 'isoName' => 'Tulu', + 'nativeName' => 'Tulu', + ], + 'teo' => [ + 'isoName' => 'Teso', + 'nativeName' => 'Teso', + ], + 'tet' => [ + 'isoName' => 'Tetum', + 'nativeName' => 'Tetum', + ], + 'the' => [ + 'isoName' => 'Chitwania Tharu', + 'nativeName' => 'Chitwania Tharu', + ], + 'tig' => [ + 'isoName' => 'Tigre', + 'nativeName' => 'Tigre', + ], + 'tlh' => [ + 'isoName' => 'Klingon', + 'nativeName' => 'tlhIngan Hol', + ], + 'tpi' => [ + 'isoName' => 'Tok Pisin', + 'nativeName' => 'Tok Pisin', + ], + 'twq' => [ + 'isoName' => 'Tasawaq', + 'nativeName' => 'Tasawaq', + ], + 'tzl' => [ + 'isoName' => 'Talossan', + 'nativeName' => 'Talossan', + ], + 'tzm' => [ + 'isoName' => 'Tamazight, Central Atlas', + 'nativeName' => 'ⵜⵎⴰⵣⵉⵖⵜ', + ], + 'unm' => [ + 'isoName' => 'Unami', + 'nativeName' => 'Unami', + ], + 'vai' => [ + 'isoName' => 'Vai', + 'nativeName' => 'Vai', + ], + 'vun' => [ + 'isoName' => 'Vunjo', + 'nativeName' => 'Vunjo', + ], + 'wae' => [ + 'isoName' => 'Walser', + 'nativeName' => 'Walser', + ], + 'wal' => [ + 'isoName' => 'Wolaytta', + 'nativeName' => 'Wolaytta', + ], + 'xog' => [ + 'isoName' => 'Soga', + 'nativeName' => 'Soga', + ], + 'yav' => [ + 'isoName' => 'Yangben', + 'nativeName' => 'Yangben', + ], + 'yue' => [ + 'isoName' => 'Cantonese', + 'nativeName' => 'Cantonese', + ], + 'yuw' => [ + 'isoName' => 'Yau (Morobe Province)', + 'nativeName' => 'Yau (Morobe Province)', + ], + 'zgh' => [ + 'isoName' => 'Standard Moroccan Tamazight', + 'nativeName' => 'Standard Moroccan Tamazight', + ], +]; diff --git a/vendor/nesbot/carbon/src/Carbon/List/regions.php b/vendor/nesbot/carbon/src/Carbon/List/regions.php new file mode 100644 index 0000000..8ab8a9e --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/List/regions.php @@ -0,0 +1,265 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * ISO 3166-2 + */ +return [ + 'AD' => 'Andorra', + 'AE' => 'United Arab Emirates', + 'AF' => 'Afghanistan', + 'AG' => 'Antigua and Barbuda', + 'AI' => 'Anguilla', + 'AL' => 'Albania', + 'AM' => 'Armenia', + 'AO' => 'Angola', + 'AQ' => 'Antarctica', + 'AR' => 'Argentina', + 'AS' => 'American Samoa', + 'AT' => 'Austria', + 'AU' => 'Australia', + 'AW' => 'Aruba', + 'AX' => 'Åland Islands', + 'AZ' => 'Azerbaijan', + 'BA' => 'Bosnia and Herzegovina', + 'BB' => 'Barbados', + 'BD' => 'Bangladesh', + 'BE' => 'Belgium', + 'BF' => 'Burkina Faso', + 'BG' => 'Bulgaria', + 'BH' => 'Bahrain', + 'BI' => 'Burundi', + 'BJ' => 'Benin', + 'BL' => 'Saint Barthélemy', + 'BM' => 'Bermuda', + 'BN' => 'Brunei Darussalam', + 'BO' => 'Bolivia (Plurinational State of)', + 'BQ' => 'Bonaire, Sint Eustatius and Saba', + 'BR' => 'Brazil', + 'BS' => 'Bahamas', + 'BT' => 'Bhutan', + 'BV' => 'Bouvet Island', + 'BW' => 'Botswana', + 'BY' => 'Belarus', + 'BZ' => 'Belize', + 'CA' => 'Canada', + 'CC' => 'Cocos (Keeling) Islands', + 'CD' => 'Congo, Democratic Republic of the', + 'CF' => 'Central African Republic', + 'CG' => 'Congo', + 'CH' => 'Switzerland', + 'CI' => 'Côte d\'Ivoire', + 'CK' => 'Cook Islands', + 'CL' => 'Chile', + 'CM' => 'Cameroon', + 'CN' => 'China', + 'CO' => 'Colombia', + 'CR' => 'Costa Rica', + 'CU' => 'Cuba', + 'CV' => 'Cabo Verde', + 'CW' => 'Curaçao', + 'CX' => 'Christmas Island', + 'CY' => 'Cyprus', + 'CZ' => 'Czechia', + 'DE' => 'Germany', + 'DJ' => 'Djibouti', + 'DK' => 'Denmark', + 'DM' => 'Dominica', + 'DO' => 'Dominican Republic', + 'DZ' => 'Algeria', + 'EC' => 'Ecuador', + 'EE' => 'Estonia', + 'EG' => 'Egypt', + 'EH' => 'Western Sahara', + 'ER' => 'Eritrea', + 'ES' => 'Spain', + 'ET' => 'Ethiopia', + 'FI' => 'Finland', + 'FJ' => 'Fiji', + 'FK' => 'Falkland Islands (Malvinas)', + 'FM' => 'Micronesia (Federated States of)', + 'FO' => 'Faroe Islands', + 'FR' => 'France', + 'GA' => 'Gabon', + 'GB' => 'United Kingdom of Great Britain and Northern Ireland', + 'GD' => 'Grenada', + 'GE' => 'Georgia', + 'GF' => 'French Guiana', + 'GG' => 'Guernsey', + 'GH' => 'Ghana', + 'GI' => 'Gibraltar', + 'GL' => 'Greenland', + 'GM' => 'Gambia', + 'GN' => 'Guinea', + 'GP' => 'Guadeloupe', + 'GQ' => 'Equatorial Guinea', + 'GR' => 'Greece', + 'GS' => 'South Georgia and the South Sandwich Islands', + 'GT' => 'Guatemala', + 'GU' => 'Guam', + 'GW' => 'Guinea-Bissau', + 'GY' => 'Guyana', + 'HK' => 'Hong Kong', + 'HM' => 'Heard Island and McDonald Islands', + 'HN' => 'Honduras', + 'HR' => 'Croatia', + 'HT' => 'Haiti', + 'HU' => 'Hungary', + 'ID' => 'Indonesia', + 'IE' => 'Ireland', + 'IL' => 'Israel', + 'IM' => 'Isle of Man', + 'IN' => 'India', + 'IO' => 'British Indian Ocean Territory', + 'IQ' => 'Iraq', + 'IR' => 'Iran (Islamic Republic of)', + 'IS' => 'Iceland', + 'IT' => 'Italy', + 'JE' => 'Jersey', + 'JM' => 'Jamaica', + 'JO' => 'Jordan', + 'JP' => 'Japan', + 'KE' => 'Kenya', + 'KG' => 'Kyrgyzstan', + 'KH' => 'Cambodia', + 'KI' => 'Kiribati', + 'KM' => 'Comoros', + 'KN' => 'Saint Kitts and Nevis', + 'KP' => 'Korea (Democratic People\'s Republic of)', + 'KR' => 'Korea, Republic of', + 'KW' => 'Kuwait', + 'KY' => 'Cayman Islands', + 'KZ' => 'Kazakhstan', + 'LA' => 'Lao People\'s Democratic Republic', + 'LB' => 'Lebanon', + 'LC' => 'Saint Lucia', + 'LI' => 'Liechtenstein', + 'LK' => 'Sri Lanka', + 'LR' => 'Liberia', + 'LS' => 'Lesotho', + 'LT' => 'Lithuania', + 'LU' => 'Luxembourg', + 'LV' => 'Latvia', + 'LY' => 'Libya', + 'MA' => 'Morocco', + 'MC' => 'Monaco', + 'MD' => 'Moldova, Republic of', + 'ME' => 'Montenegro', + 'MF' => 'Saint Martin (French part)', + 'MG' => 'Madagascar', + 'MH' => 'Marshall Islands', + 'MK' => 'Macedonia, the former Yugoslav Republic of', + 'ML' => 'Mali', + 'MM' => 'Myanmar', + 'MN' => 'Mongolia', + 'MO' => 'Macao', + 'MP' => 'Northern Mariana Islands', + 'MQ' => 'Martinique', + 'MR' => 'Mauritania', + 'MS' => 'Montserrat', + 'MT' => 'Malta', + 'MU' => 'Mauritius', + 'MV' => 'Maldives', + 'MW' => 'Malawi', + 'MX' => 'Mexico', + 'MY' => 'Malaysia', + 'MZ' => 'Mozambique', + 'NA' => 'Namibia', + 'NC' => 'New Caledonia', + 'NE' => 'Niger', + 'NF' => 'Norfolk Island', + 'NG' => 'Nigeria', + 'NI' => 'Nicaragua', + 'NL' => 'Netherlands', + 'NO' => 'Norway', + 'NP' => 'Nepal', + 'NR' => 'Nauru', + 'NU' => 'Niue', + 'NZ' => 'New Zealand', + 'OM' => 'Oman', + 'PA' => 'Panama', + 'PE' => 'Peru', + 'PF' => 'French Polynesia', + 'PG' => 'Papua New Guinea', + 'PH' => 'Philippines', + 'PK' => 'Pakistan', + 'PL' => 'Poland', + 'PM' => 'Saint Pierre and Miquelon', + 'PN' => 'Pitcairn', + 'PR' => 'Puerto Rico', + 'PS' => 'Palestine, State of', + 'PT' => 'Portugal', + 'PW' => 'Palau', + 'PY' => 'Paraguay', + 'QA' => 'Qatar', + 'RE' => 'Réunion', + 'RO' => 'Romania', + 'RS' => 'Serbia', + 'RU' => 'Russian Federation', + 'RW' => 'Rwanda', + 'SA' => 'Saudi Arabia', + 'SB' => 'Solomon Islands', + 'SC' => 'Seychelles', + 'SD' => 'Sudan', + 'SE' => 'Sweden', + 'SG' => 'Singapore', + 'SH' => 'Saint Helena, Ascension and Tristan da Cunha', + 'SI' => 'Slovenia', + 'SJ' => 'Svalbard and Jan Mayen', + 'SK' => 'Slovakia', + 'SL' => 'Sierra Leone', + 'SM' => 'San Marino', + 'SN' => 'Senegal', + 'SO' => 'Somalia', + 'SR' => 'Suriname', + 'SS' => 'South Sudan', + 'ST' => 'Sao Tome and Principe', + 'SV' => 'El Salvador', + 'SX' => 'Sint Maarten (Dutch part)', + 'SY' => 'Syrian Arab Republic', + 'SZ' => 'Eswatini', + 'TC' => 'Turks and Caicos Islands', + 'TD' => 'Chad', + 'TF' => 'French Southern Territories', + 'TG' => 'Togo', + 'TH' => 'Thailand', + 'TJ' => 'Tajikistan', + 'TK' => 'Tokelau', + 'TL' => 'Timor-Leste', + 'TM' => 'Turkmenistan', + 'TN' => 'Tunisia', + 'TO' => 'Tonga', + 'TR' => 'Turkey', + 'TT' => 'Trinidad and Tobago', + 'TV' => 'Tuvalu', + 'TW' => 'Taiwan, Province of China', + 'TZ' => 'Tanzania, United Republic of', + 'UA' => 'Ukraine', + 'UG' => 'Uganda', + 'UM' => 'United States Minor Outlying Islands', + 'US' => 'United States of America', + 'UY' => 'Uruguay', + 'UZ' => 'Uzbekistan', + 'VA' => 'Holy See', + 'VC' => 'Saint Vincent and the Grenadines', + 'VE' => 'Venezuela (Bolivarian Republic of)', + 'VG' => 'Virgin Islands (British)', + 'VI' => 'Virgin Islands (U.S.)', + 'VN' => 'Viet Nam', + 'VU' => 'Vanuatu', + 'WF' => 'Wallis and Futuna', + 'WS' => 'Samoa', + 'YE' => 'Yemen', + 'YT' => 'Mayotte', + 'ZA' => 'South Africa', + 'ZM' => 'Zambia', + 'ZW' => 'Zimbabwe', +]; diff --git a/vendor/nesbot/carbon/src/Carbon/PHPStan/Macro.php b/vendor/nesbot/carbon/src/Carbon/PHPStan/Macro.php new file mode 100644 index 0000000..baee204 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/PHPStan/Macro.php @@ -0,0 +1,247 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\PHPStan; + +use Closure; +use PHPStan\Reflection\Php\BuiltinMethodReflection; +use PHPStan\TrinaryLogic; +use ReflectionClass; +use ReflectionFunction; +use ReflectionMethod; +use ReflectionParameter; +use ReflectionType; +use stdClass; +use Throwable; + +final class Macro implements BuiltinMethodReflection +{ + /** + * The class name. + * + * @var class-string + */ + private $className; + + /** + * The method name. + * + * @var string + */ + private $methodName; + + /** + * The reflection function/method. + * + * @var ReflectionFunction|ReflectionMethod + */ + private $reflectionFunction; + + /** + * The parameters. + * + * @var ReflectionParameter[] + */ + private $parameters; + + /** + * The is static. + * + * @var bool + */ + private $static = false; + + /** + * Macro constructor. + * + * @param string $className + * @phpstan-param class-string $className + * + * @param string $methodName + * @param callable $macro + */ + public function __construct(string $className, string $methodName, $macro) + { + $this->className = $className; + $this->methodName = $methodName; + $this->reflectionFunction = \is_array($macro) + ? new ReflectionMethod($macro[0], $macro[1]) + : new ReflectionFunction($macro); + $this->parameters = $this->reflectionFunction->getParameters(); + + if ($this->reflectionFunction->isClosure()) { + try { + $closure = $this->reflectionFunction->getClosure(); + $boundClosure = Closure::bind($closure, new stdClass()); + $this->static = (!$boundClosure || (new ReflectionFunction($boundClosure))->getClosureThis() === null); + } catch (Throwable $e) { + $this->static = true; + } + } + } + + /** + * {@inheritdoc} + */ + public function getDeclaringClass(): ReflectionClass + { + return new ReflectionClass($this->className); + } + + /** + * {@inheritdoc} + */ + public function isPrivate(): bool + { + return false; + } + + /** + * {@inheritdoc} + */ + public function isPublic(): bool + { + return true; + } + + /** + * {@inheritdoc} + */ + public function isFinal(): bool + { + return false; + } + + /** + * {@inheritdoc} + */ + public function isInternal(): bool + { + return false; + } + + /** + * {@inheritdoc} + */ + public function isAbstract(): bool + { + return false; + } + + /** + * {@inheritdoc} + */ + public function isStatic(): bool + { + return $this->static; + } + + /** + * {@inheritdoc} + */ + public function getDocComment(): ?string + { + return $this->reflectionFunction->getDocComment() ?: null; + } + + /** + * {@inheritdoc} + * + * @return string|false + */ + public function getFileName() + { + return $this->reflectionFunction->getFileName(); + } + + /** + * {@inheritdoc} + */ + public function getName(): string + { + return $this->methodName; + } + + /** + * {@inheritdoc} + */ + public function getParameters(): array + { + return $this->parameters; + } + + /** + * {@inheritdoc} + */ + public function getReturnType(): ?ReflectionType + { + return $this->reflectionFunction->getReturnType(); + } + + /** + * {@inheritdoc} + * + * @return int|false + */ + public function getStartLine() + { + return $this->reflectionFunction->getStartLine(); + } + + /** + * {@inheritdoc} + * + * @return int|false + */ + public function getEndLine() + { + return $this->reflectionFunction->getEndLine(); + } + + /** + * {@inheritdoc} + */ + public function isDeprecated(): TrinaryLogic + { + return TrinaryLogic::createFromBoolean( + $this->reflectionFunction->isDeprecated() || + preg_match('/@deprecated/i', $this->getDocComment() ?: '') + ); + } + + /** + * {@inheritdoc} + */ + public function isVariadic(): bool + { + return $this->reflectionFunction->isVariadic(); + } + + /** + * {@inheritdoc} + */ + public function getPrototype(): BuiltinMethodReflection + { + return $this; + } + + /** + * {@inheritdoc} + */ + public function getReflection(): ?ReflectionMethod + { + return $this->reflectionFunction instanceof ReflectionMethod + ? $this->reflectionFunction + : null; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/PHPStan/MacroExtension.php b/vendor/nesbot/carbon/src/Carbon/PHPStan/MacroExtension.php new file mode 100644 index 0000000..8e2524c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/PHPStan/MacroExtension.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\PHPStan; + +use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\MethodReflection; +use PHPStan\Reflection\MethodsClassReflectionExtension; +use PHPStan\Reflection\Php\PhpMethodReflectionFactory; +use PHPStan\Type\TypehintHelper; + +/** + * Class MacroExtension. + * + * @codeCoverageIgnore Pure PHPStan wrapper. + */ +final class MacroExtension implements MethodsClassReflectionExtension +{ + /** + * @var PhpMethodReflectionFactory + */ + protected $methodReflectionFactory; + + /** + * @var MacroScanner + */ + protected $scanner; + + /** + * Extension constructor. + * + * @param PhpMethodReflectionFactory $methodReflectionFactory + */ + public function __construct(PhpMethodReflectionFactory $methodReflectionFactory) + { + $this->scanner = new MacroScanner(); + $this->methodReflectionFactory = $methodReflectionFactory; + } + + /** + * {@inheritdoc} + */ + public function hasMethod(ClassReflection $classReflection, string $methodName): bool + { + return $this->scanner->hasMethod($classReflection->getName(), $methodName); + } + + /** + * {@inheritdoc} + */ + public function getMethod(ClassReflection $classReflection, string $methodName): MethodReflection + { + $builtinMacro = $this->scanner->getMethod($classReflection->getName(), $methodName); + + return $this->methodReflectionFactory->create( + $classReflection, + null, + $builtinMacro, + $classReflection->getActiveTemplateTypeMap(), + [], + TypehintHelper::decideTypeFromReflection($builtinMacro->getReturnType()), + null, + null, + $builtinMacro->isDeprecated()->yes(), + $builtinMacro->isInternal(), + $builtinMacro->isFinal(), + $builtinMacro->getDocComment() + ); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/PHPStan/MacroScanner.php b/vendor/nesbot/carbon/src/Carbon/PHPStan/MacroScanner.php new file mode 100644 index 0000000..d169939 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/PHPStan/MacroScanner.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\PHPStan; + +use Carbon\CarbonInterface; +use ReflectionClass; +use ReflectionException; + +final class MacroScanner +{ + /** + * Return true if the given pair class-method is a Carbon macro. + * + * @param string $className + * @phpstan-param class-string $className + * + * @param string $methodName + * + * @return bool + */ + public function hasMethod(string $className, string $methodName): bool + { + return is_a($className, CarbonInterface::class, true) && + \is_callable([$className, 'hasMacro']) && + $className::hasMacro($methodName); + } + + /** + * Return the Macro for a given pair class-method. + * + * @param string $className + * @phpstan-param class-string $className + * + * @param string $methodName + * + * @throws ReflectionException + * + * @return Macro + */ + public function getMethod(string $className, string $methodName): Macro + { + $reflectionClass = new ReflectionClass($className); + $property = $reflectionClass->getProperty('globalMacros'); + + $property->setAccessible(true); + $macro = $property->getValue()[$methodName]; + + return new Macro( + $className, + $methodName, + $macro + ); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/Boundaries.php b/vendor/nesbot/carbon/src/Carbon/Traits/Boundaries.php new file mode 100644 index 0000000..71bbb72 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/Boundaries.php @@ -0,0 +1,443 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\Exceptions\UnknownUnitException; + +/** + * Trait Boundaries. + * + * startOf, endOf and derived method for each unit. + * + * Depends on the following properties: + * + * @property int $year + * @property int $month + * @property int $daysInMonth + * @property int $quarter + * + * Depends on the following methods: + * + * @method $this setTime(int $hour, int $minute, int $second = 0, int $microseconds = 0) + * @method $this setDate(int $year, int $month, int $day) + * @method $this addMonths(int $value = 1) + */ +trait Boundaries +{ + /** + * Resets the time to 00:00:00 start of day + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfDay(); + * ``` + * + * @return static + */ + public function startOfDay() + { + return $this->setTime(0, 0, 0, 0); + } + + /** + * Resets the time to 23:59:59.999999 end of day + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfDay(); + * ``` + * + * @return static + */ + public function endOfDay() + { + return $this->setTime(static::HOURS_PER_DAY - 1, static::MINUTES_PER_HOUR - 1, static::SECONDS_PER_MINUTE - 1, static::MICROSECONDS_PER_SECOND - 1); + } + + /** + * Resets the date to the first day of the month and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfMonth(); + * ``` + * + * @return static + */ + public function startOfMonth() + { + return $this->setDate($this->year, $this->month, 1)->startOfDay(); + } + + /** + * Resets the date to end of the month and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfMonth(); + * ``` + * + * @return static + */ + public function endOfMonth() + { + return $this->setDate($this->year, $this->month, $this->daysInMonth)->endOfDay(); + } + + /** + * Resets the date to the first day of the quarter and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfQuarter(); + * ``` + * + * @return static + */ + public function startOfQuarter() + { + $month = ($this->quarter - 1) * static::MONTHS_PER_QUARTER + 1; + + return $this->setDate($this->year, $month, 1)->startOfDay(); + } + + /** + * Resets the date to end of the quarter and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfQuarter(); + * ``` + * + * @return static + */ + public function endOfQuarter() + { + return $this->startOfQuarter()->addMonths(static::MONTHS_PER_QUARTER - 1)->endOfMonth(); + } + + /** + * Resets the date to the first day of the year and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfYear(); + * ``` + * + * @return static + */ + public function startOfYear() + { + return $this->setDate($this->year, 1, 1)->startOfDay(); + } + + /** + * Resets the date to end of the year and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfYear(); + * ``` + * + * @return static + */ + public function endOfYear() + { + return $this->setDate($this->year, 12, 31)->endOfDay(); + } + + /** + * Resets the date to the first day of the decade and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfDecade(); + * ``` + * + * @return static + */ + public function startOfDecade() + { + $year = $this->year - $this->year % static::YEARS_PER_DECADE; + + return $this->setDate($year, 1, 1)->startOfDay(); + } + + /** + * Resets the date to end of the decade and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfDecade(); + * ``` + * + * @return static + */ + public function endOfDecade() + { + $year = $this->year - $this->year % static::YEARS_PER_DECADE + static::YEARS_PER_DECADE - 1; + + return $this->setDate($year, 12, 31)->endOfDay(); + } + + /** + * Resets the date to the first day of the century and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfCentury(); + * ``` + * + * @return static + */ + public function startOfCentury() + { + $year = $this->year - ($this->year - 1) % static::YEARS_PER_CENTURY; + + return $this->setDate($year, 1, 1)->startOfDay(); + } + + /** + * Resets the date to end of the century and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfCentury(); + * ``` + * + * @return static + */ + public function endOfCentury() + { + $year = $this->year - 1 - ($this->year - 1) % static::YEARS_PER_CENTURY + static::YEARS_PER_CENTURY; + + return $this->setDate($year, 12, 31)->endOfDay(); + } + + /** + * Resets the date to the first day of the millennium and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfMillennium(); + * ``` + * + * @return static + */ + public function startOfMillennium() + { + $year = $this->year - ($this->year - 1) % static::YEARS_PER_MILLENNIUM; + + return $this->setDate($year, 1, 1)->startOfDay(); + } + + /** + * Resets the date to end of the millennium and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfMillennium(); + * ``` + * + * @return static + */ + public function endOfMillennium() + { + $year = $this->year - 1 - ($this->year - 1) % static::YEARS_PER_MILLENNIUM + static::YEARS_PER_MILLENNIUM; + + return $this->setDate($year, 12, 31)->endOfDay(); + } + + /** + * Resets the date to the first day of week (defined in $weekStartsAt) and the time to 00:00:00 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfWeek() . "\n"; + * echo Carbon::parse('2018-07-25 12:45:16')->locale('ar')->startOfWeek() . "\n"; + * echo Carbon::parse('2018-07-25 12:45:16')->startOfWeek(Carbon::SUNDAY) . "\n"; + * ``` + * + * @param int $weekStartsAt optional start allow you to specify the day of week to use to start the week + * + * @return static + */ + public function startOfWeek($weekStartsAt = null) + { + return $this->subDays((7 + $this->dayOfWeek - ($weekStartsAt ?? $this->firstWeekDay)) % 7)->startOfDay(); + } + + /** + * Resets the date to end of week (defined in $weekEndsAt) and time to 23:59:59.999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfWeek() . "\n"; + * echo Carbon::parse('2018-07-25 12:45:16')->locale('ar')->endOfWeek() . "\n"; + * echo Carbon::parse('2018-07-25 12:45:16')->endOfWeek(Carbon::SATURDAY) . "\n"; + * ``` + * + * @param int $weekEndsAt optional start allow you to specify the day of week to use to end the week + * + * @return static + */ + public function endOfWeek($weekEndsAt = null) + { + return $this->addDays((7 - $this->dayOfWeek + ($weekEndsAt ?? $this->lastWeekDay)) % 7)->endOfDay(); + } + + /** + * Modify to start of current hour, minutes and seconds become 0 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfHour(); + * ``` + * + * @return static + */ + public function startOfHour() + { + return $this->setTime($this->hour, 0, 0, 0); + } + + /** + * Modify to end of current hour, minutes and seconds become 59 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfHour(); + * ``` + * + * @return static + */ + public function endOfHour() + { + return $this->setTime($this->hour, static::MINUTES_PER_HOUR - 1, static::SECONDS_PER_MINUTE - 1, static::MICROSECONDS_PER_SECOND - 1); + } + + /** + * Modify to start of current minute, seconds become 0 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->startOfMinute(); + * ``` + * + * @return static + */ + public function startOfMinute() + { + return $this->setTime($this->hour, $this->minute, 0, 0); + } + + /** + * Modify to end of current minute, seconds become 59 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16')->endOfMinute(); + * ``` + * + * @return static + */ + public function endOfMinute() + { + return $this->setTime($this->hour, $this->minute, static::SECONDS_PER_MINUTE - 1, static::MICROSECONDS_PER_SECOND - 1); + } + + /** + * Modify to start of current second, microseconds become 0 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16.334455') + * ->startOfSecond() + * ->format('H:i:s.u'); + * ``` + * + * @return static + */ + public function startOfSecond() + { + return $this->setTime($this->hour, $this->minute, $this->second, 0); + } + + /** + * Modify to end of current second, microseconds become 999999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16.334455') + * ->endOfSecond() + * ->format('H:i:s.u'); + * ``` + * + * @return static + */ + public function endOfSecond() + { + return $this->setTime($this->hour, $this->minute, $this->second, static::MICROSECONDS_PER_SECOND - 1); + } + + /** + * Modify to start of current given unit. + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16.334455') + * ->startOf('month') + * ->endOf('week', Carbon::FRIDAY); + * ``` + * + * @param string $unit + * @param array $params + * + * @return static + */ + public function startOf($unit, ...$params) + { + $ucfUnit = ucfirst(static::singularUnit($unit)); + $method = "startOf$ucfUnit"; + if (!method_exists($this, $method)) { + throw new UnknownUnitException($unit); + } + + return $this->$method(...$params); + } + + /** + * Modify to end of current given unit. + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16.334455') + * ->startOf('month') + * ->endOf('week', Carbon::FRIDAY); + * ``` + * + * @param string $unit + * @param array $params + * + * @return static + */ + public function endOf($unit, ...$params) + { + $ucfUnit = ucfirst(static::singularUnit($unit)); + $method = "endOf$ucfUnit"; + if (!method_exists($this, $method)) { + throw new UnknownUnitException($unit); + } + + return $this->$method(...$params); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/Cast.php b/vendor/nesbot/carbon/src/Carbon/Traits/Cast.php new file mode 100644 index 0000000..5f7c7c0 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/Cast.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\Exceptions\InvalidCastException; +use DateTimeInterface; + +/** + * Trait Cast. + * + * Utils to cast into an other class. + */ +trait Cast +{ + /** + * Cast the current instance into the given class. + * + * @param string $className The $className::instance() method will be called to cast the current object. + * + * @return DateTimeInterface + */ + public function cast(string $className) + { + if (!method_exists($className, 'instance')) { + if (is_a($className, DateTimeInterface::class, true)) { + return new $className($this->rawFormat('Y-m-d H:i:s.u'), $this->getTimezone()); + } + + throw new InvalidCastException("$className has not the instance() method needed to cast the date."); + } + + return $className::instance($this); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/Comparison.php b/vendor/nesbot/carbon/src/Carbon/Traits/Comparison.php new file mode 100644 index 0000000..68240b7 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/Comparison.php @@ -0,0 +1,1070 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use BadMethodCallException; +use Carbon\CarbonInterface; +use Carbon\Exceptions\BadComparisonUnitException; +use InvalidArgumentException; + +/** + * Trait Comparison. + * + * Comparison utils and testers. All the following methods return booleans. + * nowWithSameTz + * + * Depends on the following methods: + * + * @method static resolveCarbon($date) + * @method static copy() + * @method static nowWithSameTz() + * @method static static yesterday($timezone = null) + * @method static static tomorrow($timezone = null) + */ +trait Comparison +{ + /** @var bool */ + protected $endOfTime = false; + + /** @var bool */ + protected $startOfTime = false; + + /** + * Determines if the instance is equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->eq('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->eq(Carbon::parse('2018-07-25 12:45:16')); // true + * Carbon::parse('2018-07-25 12:45:16')->eq('2018-07-25 12:45:17'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see equalTo() + * + * @return bool + */ + public function eq($date): bool + { + return $this->equalTo($date); + } + + /** + * Determines if the instance is equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->equalTo('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->equalTo(Carbon::parse('2018-07-25 12:45:16')); // true + * Carbon::parse('2018-07-25 12:45:16')->equalTo('2018-07-25 12:45:17'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function equalTo($date): bool + { + return $this == $date; + } + + /** + * Determines if the instance is not equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->ne('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->ne(Carbon::parse('2018-07-25 12:45:16')); // false + * Carbon::parse('2018-07-25 12:45:16')->ne('2018-07-25 12:45:17'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see notEqualTo() + * + * @return bool + */ + public function ne($date): bool + { + return $this->notEqualTo($date); + } + + /** + * Determines if the instance is not equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->notEqualTo('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->notEqualTo(Carbon::parse('2018-07-25 12:45:16')); // false + * Carbon::parse('2018-07-25 12:45:16')->notEqualTo('2018-07-25 12:45:17'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function notEqualTo($date): bool + { + return !$this->equalTo($date); + } + + /** + * Determines if the instance is greater (after) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->gt('2018-07-25 12:45:15'); // true + * Carbon::parse('2018-07-25 12:45:16')->gt('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->gt('2018-07-25 12:45:17'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see greaterThan() + * + * @return bool + */ + public function gt($date): bool + { + return $this->greaterThan($date); + } + + /** + * Determines if the instance is greater (after) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->greaterThan('2018-07-25 12:45:15'); // true + * Carbon::parse('2018-07-25 12:45:16')->greaterThan('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->greaterThan('2018-07-25 12:45:17'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function greaterThan($date): bool + { + return $this > $date; + } + + /** + * Determines if the instance is greater (after) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->isAfter('2018-07-25 12:45:15'); // true + * Carbon::parse('2018-07-25 12:45:16')->isAfter('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->isAfter('2018-07-25 12:45:17'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see greaterThan() + * + * @return bool + */ + public function isAfter($date): bool + { + return $this->greaterThan($date); + } + + /** + * Determines if the instance is greater (after) than or equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->gte('2018-07-25 12:45:15'); // true + * Carbon::parse('2018-07-25 12:45:16')->gte('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->gte('2018-07-25 12:45:17'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see greaterThanOrEqualTo() + * + * @return bool + */ + public function gte($date): bool + { + return $this->greaterThanOrEqualTo($date); + } + + /** + * Determines if the instance is greater (after) than or equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->greaterThanOrEqualTo('2018-07-25 12:45:15'); // true + * Carbon::parse('2018-07-25 12:45:16')->greaterThanOrEqualTo('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->greaterThanOrEqualTo('2018-07-25 12:45:17'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function greaterThanOrEqualTo($date): bool + { + return $this >= $date; + } + + /** + * Determines if the instance is less (before) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->lt('2018-07-25 12:45:15'); // false + * Carbon::parse('2018-07-25 12:45:16')->lt('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->lt('2018-07-25 12:45:17'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see lessThan() + * + * @return bool + */ + public function lt($date): bool + { + return $this->lessThan($date); + } + + /** + * Determines if the instance is less (before) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->lessThan('2018-07-25 12:45:15'); // false + * Carbon::parse('2018-07-25 12:45:16')->lessThan('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->lessThan('2018-07-25 12:45:17'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function lessThan($date): bool + { + return $this < $date; + } + + /** + * Determines if the instance is less (before) than another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->isBefore('2018-07-25 12:45:15'); // false + * Carbon::parse('2018-07-25 12:45:16')->isBefore('2018-07-25 12:45:16'); // false + * Carbon::parse('2018-07-25 12:45:16')->isBefore('2018-07-25 12:45:17'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see lessThan() + * + * @return bool + */ + public function isBefore($date): bool + { + return $this->lessThan($date); + } + + /** + * Determines if the instance is less (before) or equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->lte('2018-07-25 12:45:15'); // false + * Carbon::parse('2018-07-25 12:45:16')->lte('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->lte('2018-07-25 12:45:17'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see lessThanOrEqualTo() + * + * @return bool + */ + public function lte($date): bool + { + return $this->lessThanOrEqualTo($date); + } + + /** + * Determines if the instance is less (before) or equal to another + * + * @example + * ``` + * Carbon::parse('2018-07-25 12:45:16')->lessThanOrEqualTo('2018-07-25 12:45:15'); // false + * Carbon::parse('2018-07-25 12:45:16')->lessThanOrEqualTo('2018-07-25 12:45:16'); // true + * Carbon::parse('2018-07-25 12:45:16')->lessThanOrEqualTo('2018-07-25 12:45:17'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function lessThanOrEqualTo($date): bool + { + return $this <= $date; + } + + /** + * Determines if the instance is between two others. + * + * The third argument allow you to specify if bounds are included or not (true by default) + * but for when you including/excluding bounds may produce different results in your application, + * we recommend to use the explicit methods ->betweenIncluded() or ->betweenExcluded() instead. + * + * @example + * ``` + * Carbon::parse('2018-07-25')->between('2018-07-14', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->between('2018-08-01', '2018-08-20'); // false + * Carbon::parse('2018-07-25')->between('2018-07-25', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->between('2018-07-25', '2018-08-01', false); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 + * @param bool $equal Indicates if an equal to comparison should be done + * + * @return bool + */ + public function between($date1, $date2, $equal = true): bool + { + $date1 = $this->resolveCarbon($date1); + $date2 = $this->resolveCarbon($date2); + + if ($date1->greaterThan($date2)) { + [$date1, $date2] = [$date2, $date1]; + } + + if ($equal) { + return $this->greaterThanOrEqualTo($date1) && $this->lessThanOrEqualTo($date2); + } + + return $this->greaterThan($date1) && $this->lessThan($date2); + } + + /** + * Determines if the instance is between two others, bounds included. + * + * @example + * ``` + * Carbon::parse('2018-07-25')->betweenIncluded('2018-07-14', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->betweenIncluded('2018-08-01', '2018-08-20'); // false + * Carbon::parse('2018-07-25')->betweenIncluded('2018-07-25', '2018-08-01'); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 + * + * @return bool + */ + public function betweenIncluded($date1, $date2): bool + { + return $this->between($date1, $date2, true); + } + + /** + * Determines if the instance is between two others, bounds excluded. + * + * @example + * ``` + * Carbon::parse('2018-07-25')->betweenExcluded('2018-07-14', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->betweenExcluded('2018-08-01', '2018-08-20'); // false + * Carbon::parse('2018-07-25')->betweenExcluded('2018-07-25', '2018-08-01'); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 + * + * @return bool + */ + public function betweenExcluded($date1, $date2): bool + { + return $this->between($date1, $date2, false); + } + + /** + * Determines if the instance is between two others + * + * @example + * ``` + * Carbon::parse('2018-07-25')->isBetween('2018-07-14', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->isBetween('2018-08-01', '2018-08-20'); // false + * Carbon::parse('2018-07-25')->isBetween('2018-07-25', '2018-08-01'); // true + * Carbon::parse('2018-07-25')->isBetween('2018-07-25', '2018-08-01', false); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 + * @param bool $equal Indicates if an equal to comparison should be done + * + * @return bool + */ + public function isBetween($date1, $date2, $equal = true): bool + { + return $this->between($date1, $date2, $equal); + } + + /** + * Determines if the instance is a weekday. + * + * @example + * ``` + * Carbon::parse('2019-07-14')->isWeekday(); // false + * Carbon::parse('2019-07-15')->isWeekday(); // true + * ``` + * + * @return bool + */ + public function isWeekday() + { + return !$this->isWeekend(); + } + + /** + * Determines if the instance is a weekend day. + * + * @example + * ``` + * Carbon::parse('2019-07-14')->isWeekend(); // true + * Carbon::parse('2019-07-15')->isWeekend(); // false + * ``` + * + * @return bool + */ + public function isWeekend() + { + return \in_array($this->dayOfWeek, static::$weekendDays); + } + + /** + * Determines if the instance is yesterday. + * + * @example + * ``` + * Carbon::yesterday()->isYesterday(); // true + * Carbon::tomorrow()->isYesterday(); // false + * ``` + * + * @return bool + */ + public function isYesterday() + { + return $this->toDateString() === static::yesterday($this->getTimezone())->toDateString(); + } + + /** + * Determines if the instance is today. + * + * @example + * ``` + * Carbon::today()->isToday(); // true + * Carbon::tomorrow()->isToday(); // false + * ``` + * + * @return bool + */ + public function isToday() + { + return $this->toDateString() === $this->nowWithSameTz()->toDateString(); + } + + /** + * Determines if the instance is tomorrow. + * + * @example + * ``` + * Carbon::tomorrow()->isTomorrow(); // true + * Carbon::yesterday()->isTomorrow(); // false + * ``` + * + * @return bool + */ + public function isTomorrow() + { + return $this->toDateString() === static::tomorrow($this->getTimezone())->toDateString(); + } + + /** + * Determines if the instance is in the future, ie. greater (after) than now. + * + * @example + * ``` + * Carbon::now()->addHours(5)->isFuture(); // true + * Carbon::now()->subHours(5)->isFuture(); // false + * ``` + * + * @return bool + */ + public function isFuture() + { + return $this->greaterThan($this->nowWithSameTz()); + } + + /** + * Determines if the instance is in the past, ie. less (before) than now. + * + * @example + * ``` + * Carbon::now()->subHours(5)->isPast(); // true + * Carbon::now()->addHours(5)->isPast(); // false + * ``` + * + * @return bool + */ + public function isPast() + { + return $this->lessThan($this->nowWithSameTz()); + } + + /** + * Determines if the instance is a leap year. + * + * @example + * ``` + * Carbon::parse('2020-01-01')->isLeapYear(); // true + * Carbon::parse('2019-01-01')->isLeapYear(); // false + * ``` + * + * @return bool + */ + public function isLeapYear() + { + return $this->rawFormat('L') === '1'; + } + + /** + * Determines if the instance is a long year + * + * @example + * ``` + * Carbon::parse('2015-01-01')->isLongYear(); // true + * Carbon::parse('2016-01-01')->isLongYear(); // false + * ``` + * + * @see https://en.wikipedia.org/wiki/ISO_8601#Week_dates + * + * @return bool + */ + public function isLongYear() + { + return static::create($this->year, 12, 28, 0, 0, 0, $this->tz)->weekOfYear === 53; + } + + /** + * Compares the formatted values of the two dates. + * + * @example + * ``` + * Carbon::parse('2019-06-13')->isSameAs('Y-d', Carbon::parse('2019-12-13')); // true + * Carbon::parse('2019-06-13')->isSameAs('Y-d', Carbon::parse('2019-06-14')); // false + * ``` + * + * @param string $format date formats to compare. + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date instance to compare with or null to use current day. + * + * @return bool + */ + public function isSameAs($format, $date = null) + { + return $this->rawFormat($format) === $this->resolveCarbon($date)->rawFormat($format); + } + + /** + * Determines if the instance is in the current unit given. + * + * @example + * ``` + * Carbon::parse('2019-01-13')->isSameUnit('year', Carbon::parse('2019-12-25')); // true + * Carbon::parse('2018-12-13')->isSameUnit('year', Carbon::parse('2019-12-25')); // false + * ``` + * + * @param string $unit singular unit string + * @param \Carbon\Carbon|\DateTimeInterface|null $date instance to compare with or null to use current day. + * + * @throws BadComparisonUnitException + * + * @return bool + */ + public function isSameUnit($unit, $date = null) + { + $units = [ + // @call isSameUnit + 'year' => 'Y', + // @call isSameUnit + 'week' => 'o-W', + // @call isSameUnit + 'day' => 'Y-m-d', + // @call isSameUnit + 'hour' => 'Y-m-d H', + // @call isSameUnit + 'minute' => 'Y-m-d H:i', + // @call isSameUnit + 'second' => 'Y-m-d H:i:s', + // @call isSameUnit + 'micro' => 'Y-m-d H:i:s.u', + // @call isSameUnit + 'microsecond' => 'Y-m-d H:i:s.u', + ]; + + if (!isset($units[$unit])) { + if (isset($this->$unit)) { + return $this->resolveCarbon($date)->$unit === $this->$unit; + } + + if ($this->localStrictModeEnabled ?? static::isStrictModeEnabled()) { + throw new BadComparisonUnitException($unit); + } + + return false; + } + + return $this->isSameAs($units[$unit], $date); + } + + /** + * Determines if the instance is in the current unit given. + * + * @example + * ``` + * Carbon::now()->isCurrentUnit('hour'); // true + * Carbon::now()->subHours(2)->isCurrentUnit('hour'); // false + * ``` + * + * @param string $unit The unit to test. + * + * @throws BadMethodCallException + * + * @return bool + */ + public function isCurrentUnit($unit) + { + return $this->{'isSame'.ucfirst($unit)}(); + } + + /** + * Checks if the passed in date is in the same quarter as the instance quarter (and year if needed). + * + * @example + * ``` + * Carbon::parse('2019-01-12')->isSameQuarter(Carbon::parse('2019-03-01')); // true + * Carbon::parse('2019-01-12')->isSameQuarter(Carbon::parse('2019-04-01')); // false + * Carbon::parse('2019-01-12')->isSameQuarter(Carbon::parse('2018-03-01')); // false + * Carbon::parse('2019-01-12')->isSameQuarter(Carbon::parse('2018-03-01'), false); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date The instance to compare with or null to use current day. + * @param bool $ofSameYear Check if it is the same month in the same year. + * + * @return bool + */ + public function isSameQuarter($date = null, $ofSameYear = true) + { + $date = $this->resolveCarbon($date); + + return $this->quarter === $date->quarter && (!$ofSameYear || $this->isSameYear($date)); + } + + /** + * Checks if the passed in date is in the same month as the instance´s month. + * + * @example + * ``` + * Carbon::parse('2019-01-12')->isSameMonth(Carbon::parse('2019-01-01')); // true + * Carbon::parse('2019-01-12')->isSameMonth(Carbon::parse('2019-02-01')); // false + * Carbon::parse('2019-01-12')->isSameMonth(Carbon::parse('2018-01-01')); // false + * Carbon::parse('2019-01-12')->isSameMonth(Carbon::parse('2018-01-01'), false); // true + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use the current date. + * @param bool $ofSameYear Check if it is the same month in the same year. + * + * @return bool + */ + public function isSameMonth($date = null, $ofSameYear = true) + { + return $this->isSameAs($ofSameYear ? 'Y-m' : 'm', $date); + } + + /** + * Checks if this day is a specific day of the week. + * + * @example + * ``` + * Carbon::parse('2019-07-17')->isDayOfWeek(Carbon::WEDNESDAY); // true + * Carbon::parse('2019-07-17')->isDayOfWeek(Carbon::FRIDAY); // false + * Carbon::parse('2019-07-17')->isDayOfWeek('Wednesday'); // true + * Carbon::parse('2019-07-17')->isDayOfWeek('Friday'); // false + * ``` + * + * @param int $dayOfWeek + * + * @return bool + */ + public function isDayOfWeek($dayOfWeek) + { + if (\is_string($dayOfWeek) && \defined($constant = static::class.'::'.strtoupper($dayOfWeek))) { + $dayOfWeek = \constant($constant); + } + + return $this->dayOfWeek === $dayOfWeek; + } + + /** + * Check if its the birthday. Compares the date/month values of the two dates. + * + * @example + * ``` + * Carbon::now()->subYears(5)->isBirthday(); // true + * Carbon::now()->subYears(5)->subDay()->isBirthday(); // false + * Carbon::parse('2019-06-05')->isBirthday(Carbon::parse('2001-06-05')); // true + * Carbon::parse('2019-06-05')->isBirthday(Carbon::parse('2001-06-06')); // false + * ``` + * + * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use current day. + * + * @return bool + */ + public function isBirthday($date = null) + { + return $this->isSameAs('md', $date); + } + + /** + * Check if today is the last day of the Month + * + * @example + * ``` + * Carbon::parse('2019-02-28')->isLastOfMonth(); // true + * Carbon::parse('2019-03-28')->isLastOfMonth(); // false + * Carbon::parse('2019-03-30')->isLastOfMonth(); // false + * Carbon::parse('2019-03-31')->isLastOfMonth(); // true + * Carbon::parse('2019-04-30')->isLastOfMonth(); // true + * ``` + * + * @return bool + */ + public function isLastOfMonth() + { + return $this->day === $this->daysInMonth; + } + + /** + * Check if the instance is start of day / midnight. + * + * @example + * ``` + * Carbon::parse('2019-02-28 00:00:00')->isStartOfDay(); // true + * Carbon::parse('2019-02-28 00:00:00.999999')->isStartOfDay(); // true + * Carbon::parse('2019-02-28 00:00:01')->isStartOfDay(); // false + * Carbon::parse('2019-02-28 00:00:00.000000')->isStartOfDay(true); // true + * Carbon::parse('2019-02-28 00:00:00.000012')->isStartOfDay(true); // false + * ``` + * + * @param bool $checkMicroseconds check time at microseconds precision + * + * @return bool + */ + public function isStartOfDay($checkMicroseconds = false) + { + /* @var CarbonInterface $this */ + return $checkMicroseconds + ? $this->rawFormat('H:i:s.u') === '00:00:00.000000' + : $this->rawFormat('H:i:s') === '00:00:00'; + } + + /** + * Check if the instance is end of day. + * + * @example + * ``` + * Carbon::parse('2019-02-28 23:59:59.999999')->isEndOfDay(); // true + * Carbon::parse('2019-02-28 23:59:59.123456')->isEndOfDay(); // true + * Carbon::parse('2019-02-28 23:59:59')->isEndOfDay(); // true + * Carbon::parse('2019-02-28 23:59:58.999999')->isEndOfDay(); // false + * Carbon::parse('2019-02-28 23:59:59.999999')->isEndOfDay(true); // true + * Carbon::parse('2019-02-28 23:59:59.123456')->isEndOfDay(true); // false + * Carbon::parse('2019-02-28 23:59:59')->isEndOfDay(true); // false + * ``` + * + * @param bool $checkMicroseconds check time at microseconds precision + * + * @return bool + */ + public function isEndOfDay($checkMicroseconds = false) + { + /* @var CarbonInterface $this */ + return $checkMicroseconds + ? $this->rawFormat('H:i:s.u') === '23:59:59.999999' + : $this->rawFormat('H:i:s') === '23:59:59'; + } + + /** + * Check if the instance is start of day / midnight. + * + * @example + * ``` + * Carbon::parse('2019-02-28 00:00:00')->isMidnight(); // true + * Carbon::parse('2019-02-28 00:00:00.999999')->isMidnight(); // true + * Carbon::parse('2019-02-28 00:00:01')->isMidnight(); // false + * ``` + * + * @return bool + */ + public function isMidnight() + { + return $this->isStartOfDay(); + } + + /** + * Check if the instance is midday. + * + * @example + * ``` + * Carbon::parse('2019-02-28 11:59:59.999999')->isMidday(); // false + * Carbon::parse('2019-02-28 12:00:00')->isMidday(); // true + * Carbon::parse('2019-02-28 12:00:00.999999')->isMidday(); // true + * Carbon::parse('2019-02-28 12:00:01')->isMidday(); // false + * ``` + * + * @return bool + */ + public function isMidday() + { + /* @var CarbonInterface $this */ + return $this->rawFormat('G:i:s') === static::$midDayAt.':00:00'; + } + + /** + * Checks if the (date)time string is in a given format. + * + * @example + * ``` + * Carbon::hasFormat('11:12:45', 'h:i:s'); // true + * Carbon::hasFormat('13:12:45', 'h:i:s'); // false + * ``` + * + * @param string $date + * @param string $format + * + * @return bool + */ + public static function hasFormat($date, $format) + { + // createFromFormat() is known to handle edge cases silently. + // E.g. "1975-5-1" (Y-n-j) will still be parsed correctly when "Y-m-d" is supplied as the format. + // To ensure we're really testing against our desired format, perform an additional regex validation. + + return self::matchFormatPattern((string) $date, preg_quote((string) $format, '/'), static::$regexFormats); + } + + /** + * Checks if the (date)time string is in a given format. + * + * @example + * ``` + * Carbon::hasFormatWithModifiers('31/08/2015', 'd#m#Y'); // true + * Carbon::hasFormatWithModifiers('31/08/2015', 'm#d#Y'); // false + * ``` + * + * @param string $date + * @param string $format + * + * @return bool + */ + public static function hasFormatWithModifiers($date, $format): bool + { + return self::matchFormatPattern((string) $date, (string) $format, array_merge(static::$regexFormats, static::$regexFormatModifiers)); + } + + /** + * Checks if the (date)time string is in a given format and valid to create a + * new instance. + * + * @example + * ``` + * Carbon::canBeCreatedFromFormat('11:12:45', 'h:i:s'); // true + * Carbon::canBeCreatedFromFormat('13:12:45', 'h:i:s'); // false + * ``` + * + * @param string $date + * @param string $format + * + * @return bool + */ + public static function canBeCreatedFromFormat($date, $format) + { + try { + // Try to create a DateTime object. Throws an InvalidArgumentException if the provided time string + // doesn't match the format in any way. + if (!static::rawCreateFromFormat($format, $date)) { + return false; + } + } catch (InvalidArgumentException $e) { + return false; + } + + return static::hasFormatWithModifiers($date, $format); + } + + /** + * Returns true if the current date matches the given string. + * + * @example + * ``` + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('2019')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('2018')); // false + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('2019-06')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('06-02')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('2019-06-02')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('Sunday')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('June')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('12:23')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('12:23:45')); // true + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('12:23:00')); // false + * var_dump(Carbon::parse('2019-06-02 12:23:45')->is('12h')); // true + * var_dump(Carbon::parse('2019-06-02 15:23:45')->is('3pm')); // true + * var_dump(Carbon::parse('2019-06-02 15:23:45')->is('3am')); // false + * ``` + * + * @param string $tester day name, month name, hour, date, etc. as string + * + * @return bool + */ + public function is(string $tester) + { + $tester = trim($tester); + + if (preg_match('/^\d+$/', $tester)) { + return $this->year === (int) $tester; + } + + if (preg_match('/^\d{3,}-\d{1,2}$/', $tester)) { + return $this->isSameMonth(static::parse($tester)); + } + + if (preg_match('/^\d{1,2}-\d{1,2}$/', $tester)) { + return $this->isSameDay(static::parse($this->year.'-'.$tester)); + } + + $modifier = preg_replace('/(\d)h$/i', '$1:00', $tester); + + /* @var CarbonInterface $max */ + $median = static::parse('5555-06-15 12:30:30.555555')->modify($modifier); + $current = $this->avoidMutation(); + /* @var CarbonInterface $other */ + $other = $this->avoidMutation()->modify($modifier); + + if ($current->eq($other)) { + return true; + } + + if (preg_match('/\d:\d{1,2}:\d{1,2}$/', $tester)) { + return $current->startOfSecond()->eq($other); + } + + if (preg_match('/\d:\d{1,2}$/', $tester)) { + return $current->startOfMinute()->eq($other); + } + + if (preg_match('/\d(h|am|pm)$/', $tester)) { + return $current->startOfHour()->eq($other); + } + + if (preg_match( + '/^(january|february|march|april|may|june|july|august|september|october|november|december)\s+\d+$/i', + $tester + )) { + return $current->startOfMonth()->eq($other->startOfMonth()); + } + + $units = [ + 'month' => [1, 'year'], + 'day' => [1, 'month'], + 'hour' => [0, 'day'], + 'minute' => [0, 'hour'], + 'second' => [0, 'minute'], + 'microsecond' => [0, 'second'], + ]; + + foreach ($units as $unit => [$minimum, $startUnit]) { + if ($minimum === $median->$unit) { + $current = $current->startOf($startUnit); + + break; + } + } + + return $current->eq($other); + } + + /** + * Checks if the (date)time string is in a given format with + * given list of pattern replacements. + * + * @example + * ``` + * Carbon::hasFormat('11:12:45', 'h:i:s'); // true + * Carbon::hasFormat('13:12:45', 'h:i:s'); // false + * ``` + * + * @param string $date + * @param string $format + * @param array $replacements + * + * @return bool + */ + private static function matchFormatPattern(string $date, string $format, array $replacements): bool + { + // Preg quote, but remove escaped backslashes since we'll deal with escaped characters in the format string. + $regex = str_replace('\\\\', '\\', $format); + // Replace not-escaped letters + $regex = preg_replace_callback( + '/(?startOfTime ?? false; + } + + /** + * Returns true if the date was created using CarbonImmutable::endOfTime() + * + * @return bool + */ + public function isEndOfTime(): bool + { + return $this->endOfTime ?? false; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/Converter.php b/vendor/nesbot/carbon/src/Carbon/Traits/Converter.php new file mode 100644 index 0000000..8fe008a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/Converter.php @@ -0,0 +1,653 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\Carbon; +use Carbon\CarbonImmutable; +use Carbon\CarbonInterface; +use Carbon\CarbonInterval; +use Carbon\CarbonPeriod; +use Carbon\Exceptions\UnitException; +use Closure; +use DateTime; +use DateTimeImmutable; +use ReturnTypeWillChange; + +/** + * Trait Converter. + * + * Change date into different string formats and types and + * handle the string cast. + * + * Depends on the following methods: + * + * @method static copy() + */ +trait Converter +{ + /** + * Format to use for __toString method when type juggling occurs. + * + * @var string|Closure|null + */ + protected static $toStringFormat; + + /** + * Reset the format used to the default when type juggling a Carbon instance to a string + * + * @return void + */ + public static function resetToStringFormat() + { + static::setToStringFormat(null); + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather let Carbon object being casted to string with DEFAULT_TO_STRING_FORMAT, and + * use other method or custom format passed to format() method if you need to dump an other string + * format. + * + * Set the default format used when type juggling a Carbon instance to a string + * + * @param string|Closure|null $format + * + * @return void + */ + public static function setToStringFormat($format) + { + static::$toStringFormat = $format; + } + + /** + * Returns the formatted date string on success or FALSE on failure. + * + * @see https://php.net/manual/en/datetime.format.php + * + * @param string $format + * + * @return string + */ + #[ReturnTypeWillChange] + public function format($format) + { + $function = $this->localFormatFunction ?: static::$formatFunction; + + if (!$function) { + return $this->rawFormat($format); + } + + if (\is_string($function) && method_exists($this, $function)) { + $function = [$this, $function]; + } + + return $function(...\func_get_args()); + } + + /** + * @see https://php.net/manual/en/datetime.format.php + * + * @param string $format + * + * @return string + */ + public function rawFormat($format) + { + return parent::format($format); + } + + /** + * Format the instance as a string using the set format + * + * @example + * ``` + * echo Carbon::now(); // Carbon instances can be casted to string + * ``` + * + * @return string + */ + public function __toString() + { + $format = $this->localToStringFormat ?? static::$toStringFormat; + + return $format instanceof Closure + ? $format($this) + : $this->rawFormat($format ?: ( + \defined('static::DEFAULT_TO_STRING_FORMAT') + ? static::DEFAULT_TO_STRING_FORMAT + : CarbonInterface::DEFAULT_TO_STRING_FORMAT + )); + } + + /** + * Format the instance as date + * + * @example + * ``` + * echo Carbon::now()->toDateString(); + * ``` + * + * @return string + */ + public function toDateString() + { + return $this->rawFormat('Y-m-d'); + } + + /** + * Format the instance as a readable date + * + * @example + * ``` + * echo Carbon::now()->toFormattedDateString(); + * ``` + * + * @return string + */ + public function toFormattedDateString() + { + return $this->rawFormat('M j, Y'); + } + + /** + * Format the instance as time + * + * @example + * ``` + * echo Carbon::now()->toTimeString(); + * ``` + * + * @param string $unitPrecision + * + * @return string + */ + public function toTimeString($unitPrecision = 'second') + { + return $this->rawFormat(static::getTimeFormatByPrecision($unitPrecision)); + } + + /** + * Format the instance as date and time + * + * @example + * ``` + * echo Carbon::now()->toDateTimeString(); + * ``` + * + * @param string $unitPrecision + * + * @return string + */ + public function toDateTimeString($unitPrecision = 'second') + { + return $this->rawFormat('Y-m-d '.static::getTimeFormatByPrecision($unitPrecision)); + } + + /** + * Return a format from H:i to H:i:s.u according to given unit precision. + * + * @param string $unitPrecision "minute", "second", "millisecond" or "microsecond" + * + * @return string + */ + public static function getTimeFormatByPrecision($unitPrecision) + { + switch (static::singularUnit($unitPrecision)) { + case 'minute': + return 'H:i'; + case 'second': + return 'H:i:s'; + case 'm': + case 'millisecond': + return 'H:i:s.v'; + case 'µ': + case 'microsecond': + return 'H:i:s.u'; + } + + throw new UnitException('Precision unit expected among: minute, second, millisecond and microsecond.'); + } + + /** + * Format the instance as date and time T-separated with no timezone + * + * @example + * ``` + * echo Carbon::now()->toDateTimeLocalString(); + * echo "\n"; + * echo Carbon::now()->toDateTimeLocalString('minute'); // You can specify precision among: minute, second, millisecond and microsecond + * ``` + * + * @param string $unitPrecision + * + * @return string + */ + public function toDateTimeLocalString($unitPrecision = 'second') + { + return $this->rawFormat('Y-m-d\T'.static::getTimeFormatByPrecision($unitPrecision)); + } + + /** + * Format the instance with day, date and time + * + * @example + * ``` + * echo Carbon::now()->toDayDateTimeString(); + * ``` + * + * @return string + */ + public function toDayDateTimeString() + { + return $this->rawFormat('D, M j, Y g:i A'); + } + + /** + * Format the instance as ATOM + * + * @example + * ``` + * echo Carbon::now()->toAtomString(); + * ``` + * + * @return string + */ + public function toAtomString() + { + return $this->rawFormat(DateTime::ATOM); + } + + /** + * Format the instance as COOKIE + * + * @example + * ``` + * echo Carbon::now()->toCookieString(); + * ``` + * + * @return string + */ + public function toCookieString() + { + return $this->rawFormat(DateTime::COOKIE); + } + + /** + * Format the instance as ISO8601 + * + * @example + * ``` + * echo Carbon::now()->toIso8601String(); + * ``` + * + * @return string + */ + public function toIso8601String() + { + return $this->toAtomString(); + } + + /** + * Format the instance as RFC822 + * + * @example + * ``` + * echo Carbon::now()->toRfc822String(); + * ``` + * + * @return string + */ + public function toRfc822String() + { + return $this->rawFormat(DateTime::RFC822); + } + + /** + * Convert the instance to UTC and return as Zulu ISO8601 + * + * @example + * ``` + * echo Carbon::now()->toIso8601ZuluString(); + * ``` + * + * @param string $unitPrecision + * + * @return string + */ + public function toIso8601ZuluString($unitPrecision = 'second') + { + return $this->avoidMutation() + ->utc() + ->rawFormat('Y-m-d\T'.static::getTimeFormatByPrecision($unitPrecision).'\Z'); + } + + /** + * Format the instance as RFC850 + * + * @example + * ``` + * echo Carbon::now()->toRfc850String(); + * ``` + * + * @return string + */ + public function toRfc850String() + { + return $this->rawFormat(DateTime::RFC850); + } + + /** + * Format the instance as RFC1036 + * + * @example + * ``` + * echo Carbon::now()->toRfc1036String(); + * ``` + * + * @return string + */ + public function toRfc1036String() + { + return $this->rawFormat(DateTime::RFC1036); + } + + /** + * Format the instance as RFC1123 + * + * @example + * ``` + * echo Carbon::now()->toRfc1123String(); + * ``` + * + * @return string + */ + public function toRfc1123String() + { + return $this->rawFormat(DateTime::RFC1123); + } + + /** + * Format the instance as RFC2822 + * + * @example + * ``` + * echo Carbon::now()->toRfc2822String(); + * ``` + * + * @return string + */ + public function toRfc2822String() + { + return $this->rawFormat(DateTime::RFC2822); + } + + /** + * Format the instance as RFC3339 + * + * @param bool $extended + * + * @example + * ``` + * echo Carbon::now()->toRfc3339String() . "\n"; + * echo Carbon::now()->toRfc3339String(true) . "\n"; + * ``` + * + * @return string + */ + public function toRfc3339String($extended = false) + { + $format = DateTime::RFC3339; + if ($extended) { + $format = DateTime::RFC3339_EXTENDED; + } + + return $this->rawFormat($format); + } + + /** + * Format the instance as RSS + * + * @example + * ``` + * echo Carbon::now()->toRssString(); + * ``` + * + * @return string + */ + public function toRssString() + { + return $this->rawFormat(DateTime::RSS); + } + + /** + * Format the instance as W3C + * + * @example + * ``` + * echo Carbon::now()->toW3cString(); + * ``` + * + * @return string + */ + public function toW3cString() + { + return $this->rawFormat(DateTime::W3C); + } + + /** + * Format the instance as RFC7231 + * + * @example + * ``` + * echo Carbon::now()->toRfc7231String(); + * ``` + * + * @return string + */ + public function toRfc7231String() + { + return $this->avoidMutation() + ->setTimezone('GMT') + ->rawFormat(\defined('static::RFC7231_FORMAT') ? static::RFC7231_FORMAT : CarbonInterface::RFC7231_FORMAT); + } + + /** + * Get default array representation. + * + * @example + * ``` + * var_dump(Carbon::now()->toArray()); + * ``` + * + * @return array + */ + public function toArray() + { + return [ + 'year' => $this->year, + 'month' => $this->month, + 'day' => $this->day, + 'dayOfWeek' => $this->dayOfWeek, + 'dayOfYear' => $this->dayOfYear, + 'hour' => $this->hour, + 'minute' => $this->minute, + 'second' => $this->second, + 'micro' => $this->micro, + 'timestamp' => $this->timestamp, + 'formatted' => $this->rawFormat(\defined('static::DEFAULT_TO_STRING_FORMAT') ? static::DEFAULT_TO_STRING_FORMAT : CarbonInterface::DEFAULT_TO_STRING_FORMAT), + 'timezone' => $this->timezone, + ]; + } + + /** + * Get default object representation. + * + * @example + * ``` + * var_dump(Carbon::now()->toObject()); + * ``` + * + * @return object + */ + public function toObject() + { + return (object) $this->toArray(); + } + + /** + * Returns english human readable complete date string. + * + * @example + * ``` + * echo Carbon::now()->toString(); + * ``` + * + * @return string + */ + public function toString() + { + return $this->avoidMutation()->locale('en')->isoFormat('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ'); + } + + /** + * Return the ISO-8601 string (ex: 1977-04-22T06:00:00Z, if $keepOffset truthy, offset will be kept: + * 1977-04-22T01:00:00-05:00). + * + * @example + * ``` + * echo Carbon::now('America/Toronto')->toISOString() . "\n"; + * echo Carbon::now('America/Toronto')->toISOString(true) . "\n"; + * ``` + * + * @param bool $keepOffset Pass true to keep the date offset. Else forced to UTC. + * + * @return null|string + */ + public function toISOString($keepOffset = false) + { + if (!$this->isValid()) { + return null; + } + + $yearFormat = $this->year < 0 || $this->year > 9999 ? 'YYYYYY' : 'YYYY'; + $tzFormat = $keepOffset ? 'Z' : '[Z]'; + $date = $keepOffset ? $this : $this->avoidMutation()->utc(); + + return $date->isoFormat("$yearFormat-MM-DD[T]HH:mm:ss.SSSSSS$tzFormat"); + } + + /** + * Return the ISO-8601 string (ex: 1977-04-22T06:00:00Z) with UTC timezone. + * + * @example + * ``` + * echo Carbon::now('America/Toronto')->toJSON(); + * ``` + * + * @return null|string + */ + public function toJSON() + { + return $this->toISOString(); + } + + /** + * Return native DateTime PHP object matching the current instance. + * + * @example + * ``` + * var_dump(Carbon::now()->toDateTime()); + * ``` + * + * @return DateTime + */ + public function toDateTime() + { + return new DateTime($this->rawFormat('Y-m-d H:i:s.u'), $this->getTimezone()); + } + + /** + * Return native toDateTimeImmutable PHP object matching the current instance. + * + * @example + * ``` + * var_dump(Carbon::now()->toDateTimeImmutable()); + * ``` + * + * @return DateTimeImmutable + */ + public function toDateTimeImmutable() + { + return new DateTimeImmutable($this->rawFormat('Y-m-d H:i:s.u'), $this->getTimezone()); + } + + /** + * @alias toDateTime + * + * Return native DateTime PHP object matching the current instance. + * + * @example + * ``` + * var_dump(Carbon::now()->toDate()); + * ``` + * + * @return DateTime + */ + public function toDate() + { + return $this->toDateTime(); + } + + /** + * Create a iterable CarbonPeriod object from current date to a given end date (and optional interval). + * + * @param \DateTimeInterface|Carbon|CarbonImmutable|int|null $end period end date or recurrences count if int + * @param int|\DateInterval|string|null $interval period default interval or number of the given $unit + * @param string|null $unit if specified, $interval must be an integer + * + * @return CarbonPeriod + */ + public function toPeriod($end = null, $interval = null, $unit = null) + { + if ($unit) { + $interval = CarbonInterval::make("$interval ".static::pluralUnit($unit)); + } + + $period = (new CarbonPeriod())->setDateClass(static::class)->setStartDate($this); + + if ($interval) { + $period->setDateInterval($interval); + } + + if (\is_int($end) || \is_string($end) && ctype_digit($end)) { + $period->setRecurrences($end); + } elseif ($end) { + $period->setEndDate($end); + } + + return $period; + } + + /** + * Create a iterable CarbonPeriod object from current date to a given end date (and optional interval). + * + * @param \DateTimeInterface|Carbon|CarbonImmutable|null $end period end date + * @param int|\DateInterval|string|null $interval period default interval or number of the given $unit + * @param string|null $unit if specified, $interval must be an integer + * + * @return CarbonPeriod + */ + public function range($end = null, $interval = null, $unit = null) + { + return $this->toPeriod($end, $interval, $unit); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/Creator.php b/vendor/nesbot/carbon/src/Carbon/Traits/Creator.php new file mode 100644 index 0000000..f90195a --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/Creator.php @@ -0,0 +1,943 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\Carbon; +use Carbon\CarbonImmutable; +use Carbon\CarbonInterface; +use Carbon\Exceptions\InvalidDateException; +use Carbon\Exceptions\InvalidFormatException; +use Carbon\Exceptions\OutOfRangeException; +use Carbon\Translator; +use Closure; +use DateTimeInterface; +use DateTimeZone; +use Exception; +use ReturnTypeWillChange; + +/** + * Trait Creator. + * + * Static creators. + * + * Depends on the following methods: + * + * @method static Carbon|CarbonImmutable getTestNow() + */ +trait Creator +{ + use ObjectInitialisation; + + /** + * The errors that can occur. + * + * @var array + */ + protected static $lastErrors; + + /** + * Create a new Carbon instance. + * + * Please see the testing aids section (specifically static::setTestNow()) + * for more on the possibility of this constructor returning a test instance. + * + * @param DateTimeInterface|string|null $time + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + */ + public function __construct($time = null, $tz = null) + { + if ($time instanceof DateTimeInterface) { + $time = $this->constructTimezoneFromDateTime($time, $tz)->format('Y-m-d H:i:s.u'); + } + + if (is_numeric($time) && (!\is_string($time) || !preg_match('/^\d{1,14}$/', $time))) { + $time = static::createFromTimestampUTC($time)->format('Y-m-d\TH:i:s.uP'); + } + + // If the class has a test now set and we are trying to create a now() + // instance then override as required + $isNow = empty($time) || $time === 'now'; + + if (method_exists(static::class, 'hasTestNow') && + method_exists(static::class, 'getTestNow') && + static::hasTestNow() && + ($isNow || static::hasRelativeKeywords($time)) + ) { + static::mockConstructorParameters($time, $tz); + } + + // Work-around for PHP bug https://bugs.php.net/bug.php?id=67127 + if (!str_contains((string) .1, '.')) { + $locale = setlocale(LC_NUMERIC, '0'); + setlocale(LC_NUMERIC, 'C'); + } + + try { + parent::__construct($time ?: 'now', static::safeCreateDateTimeZone($tz) ?: null); + } catch (Exception $exception) { + throw new InvalidFormatException($exception->getMessage(), 0, $exception); + } + + $this->constructedObjectId = spl_object_hash($this); + + if (isset($locale)) { + setlocale(LC_NUMERIC, $locale); + } + + static::setLastErrors(parent::getLastErrors()); + } + + /** + * Get timezone from a datetime instance. + * + * @param DateTimeInterface $date + * @param DateTimeZone|string|null $tz + * + * @return DateTimeInterface + */ + private function constructTimezoneFromDateTime(DateTimeInterface $date, &$tz) + { + if ($tz !== null) { + $safeTz = static::safeCreateDateTimeZone($tz); + + if ($safeTz) { + return $date->setTimezone($safeTz); + } + + return $date; + } + + $tz = $date->getTimezone(); + + return $date; + } + + /** + * Update constructedObjectId on cloned. + */ + public function __clone() + { + $this->constructedObjectId = spl_object_hash($this); + } + + /** + * Create a Carbon instance from a DateTime one. + * + * @param DateTimeInterface $date + * + * @return static + */ + public static function instance($date) + { + if ($date instanceof static) { + return clone $date; + } + + static::expectDateTime($date); + + $instance = new static($date->format('Y-m-d H:i:s.u'), $date->getTimezone()); + + if ($date instanceof CarbonInterface || $date instanceof Options) { + $settings = $date->getSettings(); + + if (!$date->hasLocalTranslator()) { + unset($settings['locale']); + } + + $instance->settings($settings); + } + + return $instance; + } + + /** + * Create a carbon instance from a string. + * + * This is an alias for the constructor that allows better fluent syntax + * as it allows you to do Carbon::parse('Monday next week')->fn() rather + * than (new Carbon('Monday next week'))->fn(). + * + * @param string|DateTimeInterface|null $time + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function rawParse($time = null, $tz = null) + { + if ($time instanceof DateTimeInterface) { + return static::instance($time); + } + + try { + return new static($time, $tz); + } catch (Exception $exception) { + $date = @static::now($tz)->change($time); + + if (!$date) { + throw new InvalidFormatException("Could not parse '$time': ".$exception->getMessage(), 0, $exception); + } + + return $date; + } + } + + /** + * Create a carbon instance from a string. + * + * This is an alias for the constructor that allows better fluent syntax + * as it allows you to do Carbon::parse('Monday next week')->fn() rather + * than (new Carbon('Monday next week'))->fn(). + * + * @param string|DateTimeInterface|null $time + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function parse($time = null, $tz = null) + { + $function = static::$parseFunction; + + if (!$function) { + return static::rawParse($time, $tz); + } + + if (\is_string($function) && method_exists(static::class, $function)) { + $function = [static::class, $function]; + } + + return $function(...\func_get_args()); + } + + /** + * Create a carbon instance from a localized string (in French, Japanese, Arabic, etc.). + * + * @param string $time date/time string in the given language (may also contain English). + * @param string|null $locale if locale is null or not specified, current global locale will be + * used instead. + * @param DateTimeZone|string|null $tz optional timezone for the new instance. + * + * @throws InvalidFormatException + * + * @return static + */ + public static function parseFromLocale($time, $locale = null, $tz = null) + { + return static::rawParse(static::translateTimeString($time, $locale, 'en'), $tz); + } + + /** + * Get a Carbon instance for the current date and time. + * + * @param DateTimeZone|string|null $tz + * + * @return static + */ + public static function now($tz = null) + { + return new static(null, $tz); + } + + /** + * Create a Carbon instance for today. + * + * @param DateTimeZone|string|null $tz + * + * @return static + */ + public static function today($tz = null) + { + return static::rawParse('today', $tz); + } + + /** + * Create a Carbon instance for tomorrow. + * + * @param DateTimeZone|string|null $tz + * + * @return static + */ + public static function tomorrow($tz = null) + { + return static::rawParse('tomorrow', $tz); + } + + /** + * Create a Carbon instance for yesterday. + * + * @param DateTimeZone|string|null $tz + * + * @return static + */ + public static function yesterday($tz = null) + { + return static::rawParse('yesterday', $tz); + } + + /** + * Create a Carbon instance for the greatest supported date. + * + * @return static + */ + public static function maxValue() + { + if (self::$PHPIntSize === 4) { + // 32 bit + return static::createFromTimestamp(PHP_INT_MAX); // @codeCoverageIgnore + } + + // 64 bit + return static::create(9999, 12, 31, 23, 59, 59); + } + + /** + * Create a Carbon instance for the lowest supported date. + * + * @return static + */ + public static function minValue() + { + if (self::$PHPIntSize === 4) { + // 32 bit + return static::createFromTimestamp(~PHP_INT_MAX); // @codeCoverageIgnore + } + + // 64 bit + return static::create(1, 1, 1, 0, 0, 0); + } + + private static function assertBetween($unit, $value, $min, $max) + { + if (static::isStrictModeEnabled() && ($value < $min || $value > $max)) { + throw new OutOfRangeException($unit, $min, $max, $value); + } + } + + private static function createNowInstance($tz) + { + if (!static::hasTestNow()) { + return static::now($tz); + } + + $now = static::getTestNow(); + + if ($now instanceof Closure) { + return $now(static::now($tz)); + } + + return $now->avoidMutation()->tz($tz); + } + + /** + * Create a new Carbon instance from a specific date and time. + * + * If any of $year, $month or $day are set to null their now() values will + * be used. + * + * If $hour is null it will be set to its now() value and the default + * values for $minute and $second will be their now() values. + * + * If $hour is not null then the default values for $minute and $second + * will be 0. + * + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param int|null $hour + * @param int|null $minute + * @param int|null $second + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static|false + */ + public static function create($year = 0, $month = 1, $day = 1, $hour = 0, $minute = 0, $second = 0, $tz = null) + { + if (\is_string($year) && !is_numeric($year) || $year instanceof DateTimeInterface) { + return static::parse($year, $tz ?: (\is_string($month) || $month instanceof DateTimeZone ? $month : null)); + } + + $defaults = null; + $getDefault = function ($unit) use ($tz, &$defaults) { + if ($defaults === null) { + $now = self::createNowInstance($tz); + + $defaults = array_combine([ + 'year', + 'month', + 'day', + 'hour', + 'minute', + 'second', + ], explode('-', $now->rawFormat('Y-n-j-G-i-s.u'))); + } + + return $defaults[$unit]; + }; + + $year = $year ?? $getDefault('year'); + $month = $month ?? $getDefault('month'); + $day = $day ?? $getDefault('day'); + $hour = $hour ?? $getDefault('hour'); + $minute = $minute ?? $getDefault('minute'); + $second = (float) ($second ?? $getDefault('second')); + + self::assertBetween('month', $month, 0, 99); + self::assertBetween('day', $day, 0, 99); + self::assertBetween('hour', $hour, 0, 99); + self::assertBetween('minute', $minute, 0, 99); + self::assertBetween('second', $second, 0, 99); + + $fixYear = null; + + if ($year < 0) { + $fixYear = $year; + $year = 0; + } elseif ($year > 9999) { + $fixYear = $year - 9999; + $year = 9999; + } + + $second = ($second < 10 ? '0' : '').number_format($second, 6); + $instance = static::rawCreateFromFormat('!Y-n-j G:i:s.u', sprintf('%s-%s-%s %s:%02s:%02s', $year, $month, $day, $hour, $minute, $second), $tz); + + if ($fixYear !== null) { + $instance = $instance->addYears($fixYear); + } + + return $instance; + } + + /** + * Create a new safe Carbon instance from a specific date and time. + * + * If any of $year, $month or $day are set to null their now() values will + * be used. + * + * If $hour is null it will be set to its now() value and the default + * values for $minute and $second will be their now() values. + * + * If $hour is not null then the default values for $minute and $second + * will be 0. + * + * If one of the set values is not valid, an InvalidDateException + * will be thrown. + * + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param int|null $hour + * @param int|null $minute + * @param int|null $second + * @param DateTimeZone|string|null $tz + * + * @throws InvalidDateException + * + * @return static|false + */ + public static function createSafe($year = null, $month = null, $day = null, $hour = null, $minute = null, $second = null, $tz = null) + { + $fields = static::getRangesByUnit(); + + foreach ($fields as $field => $range) { + if ($$field !== null && (!\is_int($$field) || $$field < $range[0] || $$field > $range[1])) { + if (static::isStrictModeEnabled()) { + throw new InvalidDateException($field, $$field); + } + + return false; + } + } + + $instance = static::create($year, $month, $day, $hour, $minute, $second, $tz); + + foreach (array_reverse($fields) as $field => $range) { + if ($$field !== null && (!\is_int($$field) || $$field !== $instance->$field)) { + if (static::isStrictModeEnabled()) { + throw new InvalidDateException($field, $$field); + } + + return false; + } + } + + return $instance; + } + + /** + * Create a new Carbon instance from a specific date and time using strict validation. + * + * @see create() + * + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param int|null $hour + * @param int|null $minute + * @param int|null $second + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function createStrict(?int $year = 0, ?int $month = 1, ?int $day = 1, ?int $hour = 0, ?int $minute = 0, ?int $second = 0, $tz = null): self + { + $initialStrictMode = static::isStrictModeEnabled(); + static::useStrictMode(true); + + try { + $date = static::create($year, $month, $day, $hour, $minute, $second, $tz); + } finally { + static::useStrictMode($initialStrictMode); + } + + return $date; + } + + /** + * Create a Carbon instance from just a date. The time portion is set to now. + * + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function createFromDate($year = null, $month = null, $day = null, $tz = null) + { + return static::create($year, $month, $day, null, null, null, $tz); + } + + /** + * Create a Carbon instance from just a date. The time portion is set to midnight. + * + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function createMidnightDate($year = null, $month = null, $day = null, $tz = null) + { + return static::create($year, $month, $day, 0, 0, 0, $tz); + } + + /** + * Create a Carbon instance from just a time. The date portion is set to today. + * + * @param int|null $hour + * @param int|null $minute + * @param int|null $second + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function createFromTime($hour = 0, $minute = 0, $second = 0, $tz = null) + { + return static::create(null, null, null, $hour, $minute, $second, $tz); + } + + /** + * Create a Carbon instance from a time string. The date portion is set to today. + * + * @param string $time + * @param DateTimeZone|string|null $tz + * + * @throws InvalidFormatException + * + * @return static + */ + public static function createFromTimeString($time, $tz = null) + { + return static::today($tz)->setTimeFromTimeString($time); + } + + /** + * @param string $format Datetime format + * @param string $time + * @param DateTimeZone|string|false|null $originalTz + * + * @return DateTimeInterface|false + */ + private static function createFromFormatAndTimezone($format, $time, $originalTz) + { + // Work-around for https://bugs.php.net/bug.php?id=75577 + // @codeCoverageIgnoreStart + if (version_compare(PHP_VERSION, '7.3.0-dev', '<')) { + $format = str_replace('.v', '.u', $format); + } + // @codeCoverageIgnoreEnd + + if ($originalTz === null) { + return parent::createFromFormat($format, (string) $time); + } + + $tz = \is_int($originalTz) + ? @timezone_name_from_abbr('', (int) ($originalTz * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE), 1) + : $originalTz; + + $tz = static::safeCreateDateTimeZone($tz, $originalTz); + + if ($tz === false) { + return false; + } + + return parent::createFromFormat($format, (string) $time, $tz); + } + + /** + * Create a Carbon instance from a specific format. + * + * @param string $format Datetime format + * @param string $time + * @param DateTimeZone|string|false|null $tz + * + * @throws InvalidFormatException + * + * @return static|false + */ + public static function rawCreateFromFormat($format, $time, $tz = null) + { + // Work-around for https://bugs.php.net/bug.php?id=80141 + $format = preg_replace('/(?getTimezone(); + } + + // Set microseconds to zero to match behavior of DateTime::createFromFormat() + // See https://bugs.php.net/bug.php?id=74332 + $mock = $mock->copy()->microsecond(0); + + // Prepend mock datetime only if the format does not contain non escaped unix epoch reset flag. + if (!preg_match("/{$nonEscaped}[!|]/", $format)) { + $format = static::MOCK_DATETIME_FORMAT.' '.$format; + $time = ($mock instanceof self ? $mock->rawFormat(static::MOCK_DATETIME_FORMAT) : $mock->format(static::MOCK_DATETIME_FORMAT)).' '.$time; + } + + // Regenerate date from the modified format to base result on the mocked instance instead of now. + $date = self::createFromFormatAndTimezone($format, $time, $tz); + } + + if ($date instanceof DateTimeInterface) { + $instance = static::instance($date); + $instance::setLastErrors($lastErrors); + + return $instance; + } + + if (static::isStrictModeEnabled()) { + throw new InvalidFormatException(implode(PHP_EOL, $lastErrors['errors'])); + } + + return false; + } + + /** + * Create a Carbon instance from a specific format. + * + * @param string $format Datetime format + * @param string $time + * @param DateTimeZone|string|false|null $tz + * + * @throws InvalidFormatException + * + * @return static|false + */ + #[ReturnTypeWillChange] + public static function createFromFormat($format, $time, $tz = null) + { + $function = static::$createFromFormatFunction; + + if (!$function) { + return static::rawCreateFromFormat($format, $time, $tz); + } + + if (\is_string($function) && method_exists(static::class, $function)) { + $function = [static::class, $function]; + } + + return $function(...\func_get_args()); + } + + /** + * Create a Carbon instance from a specific ISO format (same replacements as ->isoFormat()). + * + * @param string $format Datetime format + * @param string $time + * @param DateTimeZone|string|false|null $tz optional timezone + * @param string|null $locale locale to be used for LTS, LT, LL, LLL, etc. macro-formats (en by fault, unneeded if no such macro-format in use) + * @param \Symfony\Component\Translation\TranslatorInterface $translator optional custom translator to use for macro-formats + * + * @throws InvalidFormatException + * + * @return static|false + */ + public static function createFromIsoFormat($format, $time, $tz = null, $locale = 'en', $translator = null) + { + $format = preg_replace_callback('/(? static::getTranslationMessageWith($translator, 'formats.LT', $locale, 'h:mm A'), + 'LTS' => static::getTranslationMessageWith($translator, 'formats.LTS', $locale, 'h:mm:ss A'), + 'L' => static::getTranslationMessageWith($translator, 'formats.L', $locale, 'MM/DD/YYYY'), + 'LL' => static::getTranslationMessageWith($translator, 'formats.LL', $locale, 'MMMM D, YYYY'), + 'LLL' => static::getTranslationMessageWith($translator, 'formats.LLL', $locale, 'MMMM D, YYYY h:mm A'), + 'LLLL' => static::getTranslationMessageWith($translator, 'formats.LLLL', $locale, 'dddd, MMMM D, YYYY h:mm A'), + ]; + } + + return $formats[$code] ?? preg_replace_callback( + '/MMMM|MM|DD|dddd/', + function ($code) { + return mb_substr($code[0], 1); + }, + $formats[strtoupper($code)] ?? '' + ); + }, $format); + + $format = preg_replace_callback('/(? 'd', + 'OM' => 'M', + 'OY' => 'Y', + 'OH' => 'G', + 'Oh' => 'g', + 'Om' => 'i', + 'Os' => 's', + 'D' => 'd', + 'DD' => 'd', + 'Do' => 'd', + 'd' => '!', + 'dd' => '!', + 'ddd' => 'D', + 'dddd' => 'D', + 'DDD' => 'z', + 'DDDD' => 'z', + 'DDDo' => 'z', + 'e' => '!', + 'E' => '!', + 'H' => 'G', + 'HH' => 'H', + 'h' => 'g', + 'hh' => 'h', + 'k' => 'G', + 'kk' => 'G', + 'hmm' => 'gi', + 'hmmss' => 'gis', + 'Hmm' => 'Gi', + 'Hmmss' => 'Gis', + 'm' => 'i', + 'mm' => 'i', + 'a' => 'a', + 'A' => 'a', + 's' => 's', + 'ss' => 's', + 'S' => '*', + 'SS' => '*', + 'SSS' => '*', + 'SSSS' => '*', + 'SSSSS' => '*', + 'SSSSSS' => 'u', + 'SSSSSSS' => 'u*', + 'SSSSSSSS' => 'u*', + 'SSSSSSSSS' => 'u*', + 'M' => 'm', + 'MM' => 'm', + 'MMM' => 'M', + 'MMMM' => 'M', + 'Mo' => 'm', + 'Q' => '!', + 'Qo' => '!', + 'G' => '!', + 'GG' => '!', + 'GGG' => '!', + 'GGGG' => '!', + 'GGGGG' => '!', + 'g' => '!', + 'gg' => '!', + 'ggg' => '!', + 'gggg' => '!', + 'ggggg' => '!', + 'W' => '!', + 'WW' => '!', + 'Wo' => '!', + 'w' => '!', + 'ww' => '!', + 'wo' => '!', + 'x' => 'U???', + 'X' => 'U', + 'Y' => 'Y', + 'YY' => 'y', + 'YYYY' => 'Y', + 'YYYYY' => 'Y', + 'YYYYYY' => 'Y', + 'z' => 'e', + 'zz' => 'e', + 'Z' => 'e', + 'ZZ' => 'e', + ]; + } + + $format = $replacements[$code] ?? '?'; + + if ($format === '!') { + throw new InvalidFormatException("Format $code not supported for creation."); + } + + return $format; + }, $format); + + return static::rawCreateFromFormat($format, $time, $tz); + } + + /** + * Create a Carbon instance from a specific format and a string in a given language. + * + * @param string $format Datetime format + * @param string $locale + * @param string $time + * @param DateTimeZone|string|false|null $tz + * + * @throws InvalidFormatException + * + * @return static|false + */ + public static function createFromLocaleFormat($format, $locale, $time, $tz = null) + { + return static::rawCreateFromFormat($format, static::translateTimeString($time, $locale, 'en'), $tz); + } + + /** + * Create a Carbon instance from a specific ISO format and a string in a given language. + * + * @param string $format Datetime ISO format + * @param string $locale + * @param string $time + * @param DateTimeZone|string|false|null $tz + * + * @throws InvalidFormatException + * + * @return static|false + */ + public static function createFromLocaleIsoFormat($format, $locale, $time, $tz = null) + { + $time = static::translateTimeString($time, $locale, 'en', CarbonInterface::TRANSLATE_MONTHS | CarbonInterface::TRANSLATE_DAYS | CarbonInterface::TRANSLATE_MERIDIEM); + + return static::createFromIsoFormat($format, $time, $tz, $locale); + } + + /** + * Make a Carbon instance from given variable if possible. + * + * Always return a new instance. Parse only strings and only these likely to be dates (skip intervals + * and recurrences). Throw an exception for invalid format, but otherwise return null. + * + * @param mixed $var + * + * @throws InvalidFormatException + * + * @return static|null + */ + public static function make($var) + { + if ($var instanceof DateTimeInterface) { + return static::instance($var); + } + + $date = null; + + if (\is_string($var)) { + $var = trim($var); + + if (!preg_match('/^P[0-9T]/', $var) && + !preg_match('/^R[0-9]/', $var) && + preg_match('/[a-z0-9]/i', $var) + ) { + $date = static::parse($var); + } + } + + return $date; + } + + /** + * Set last errors. + * + * @param array $lastErrors + * + * @return void + */ + private static function setLastErrors(array $lastErrors) + { + static::$lastErrors = $lastErrors; + } + + /** + * {@inheritdoc} + * + * @return array + */ + #[ReturnTypeWillChange] + public static function getLastErrors() + { + return static::$lastErrors; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/Date.php b/vendor/nesbot/carbon/src/Carbon/Traits/Date.php new file mode 100644 index 0000000..023da4d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/Date.php @@ -0,0 +1,2688 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use BadMethodCallException; +use Carbon\Carbon; +use Carbon\CarbonInterface; +use Carbon\CarbonPeriod; +use Carbon\CarbonTimeZone; +use Carbon\Exceptions\BadComparisonUnitException; +use Carbon\Exceptions\ImmutableException; +use Carbon\Exceptions\InvalidTimeZoneException; +use Carbon\Exceptions\InvalidTypeException; +use Carbon\Exceptions\UnknownGetterException; +use Carbon\Exceptions\UnknownMethodException; +use Carbon\Exceptions\UnknownSetterException; +use Carbon\Exceptions\UnknownUnitException; +use Closure; +use DateInterval; +use DatePeriod; +use DateTime; +use DateTimeImmutable; +use DateTimeInterface; +use DateTimeZone; +use InvalidArgumentException; +use ReflectionException; +use ReturnTypeWillChange; +use Throwable; + +/** + * A simple API extension for DateTime. + * + * @mixin DeprecatedProperties + * + * + * + * @property int $year + * @property int $yearIso + * @property int $month + * @property int $day + * @property int $hour + * @property int $minute + * @property int $second + * @property int $micro + * @property int $microsecond + * @property int|float|string $timestamp seconds since the Unix Epoch + * @property string $englishDayOfWeek the day of week in English + * @property string $shortEnglishDayOfWeek the abbreviated day of week in English + * @property string $englishMonth the month in English + * @property string $shortEnglishMonth the abbreviated month in English + * @property int $milliseconds + * @property int $millisecond + * @property int $milli + * @property int $week 1 through 53 + * @property int $isoWeek 1 through 53 + * @property int $weekYear year according to week format + * @property int $isoWeekYear year according to ISO week format + * @property int $dayOfYear 1 through 366 + * @property int $age does a diffInYears() with default parameters + * @property int $offset the timezone offset in seconds from UTC + * @property int $offsetMinutes the timezone offset in minutes from UTC + * @property int $offsetHours the timezone offset in hours from UTC + * @property CarbonTimeZone $timezone the current timezone + * @property CarbonTimeZone $tz alias of $timezone + * @property-read int $dayOfWeek 0 (for Sunday) through 6 (for Saturday) + * @property-read int $dayOfWeekIso 1 (for Monday) through 7 (for Sunday) + * @property-read int $weekOfYear ISO-8601 week number of year, weeks starting on Monday + * @property-read int $daysInMonth number of days in the given month + * @property-read string $latinMeridiem "am"/"pm" (Ante meridiem or Post meridiem latin lowercase mark) + * @property-read string $latinUpperMeridiem "AM"/"PM" (Ante meridiem or Post meridiem latin uppercase mark) + * @property-read string $timezoneAbbreviatedName the current timezone abbreviated name + * @property-read string $tzAbbrName alias of $timezoneAbbreviatedName + * @property-read string $dayName long name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $shortDayName short name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $minDayName very short name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $monthName long name of month translated according to Carbon locale, in english if no translation available for current language + * @property-read string $shortMonthName short name of month translated according to Carbon locale, in english if no translation available for current language + * @property-read string $meridiem lowercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language + * @property-read string $upperMeridiem uppercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language + * @property-read int $noZeroHour current hour from 1 to 24 + * @property-read int $weeksInYear 51 through 53 + * @property-read int $isoWeeksInYear 51 through 53 + * @property-read int $weekOfMonth 1 through 5 + * @property-read int $weekNumberInMonth 1 through 5 + * @property-read int $firstWeekDay 0 through 6 + * @property-read int $lastWeekDay 0 through 6 + * @property-read int $daysInYear 365 or 366 + * @property-read int $quarter the quarter of this instance, 1 - 4 + * @property-read int $decade the decade of this instance + * @property-read int $century the century of this instance + * @property-read int $millennium the millennium of this instance + * @property-read bool $dst daylight savings time indicator, true if DST, false otherwise + * @property-read bool $local checks if the timezone is local, true if local, false otherwise + * @property-read bool $utc checks if the timezone is UTC, true if UTC, false otherwise + * @property-read string $timezoneName the current timezone name + * @property-read string $tzName alias of $timezoneName + * @property-read string $locale locale of the current instance + * + * @method bool isUtc() Check if the current instance has UTC timezone. (Both isUtc and isUTC cases are valid.) + * @method bool isLocal() Check if the current instance has non-UTC timezone. + * @method bool isValid() Check if the current instance is a valid date. + * @method bool isDST() Check if the current instance is in a daylight saving time. + * @method bool isSunday() Checks if the instance day is sunday. + * @method bool isMonday() Checks if the instance day is monday. + * @method bool isTuesday() Checks if the instance day is tuesday. + * @method bool isWednesday() Checks if the instance day is wednesday. + * @method bool isThursday() Checks if the instance day is thursday. + * @method bool isFriday() Checks if the instance day is friday. + * @method bool isSaturday() Checks if the instance day is saturday. + * @method bool isSameYear(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same year as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentYear() Checks if the instance is in the same year as the current moment. + * @method bool isNextYear() Checks if the instance is in the same year as the current moment next year. + * @method bool isLastYear() Checks if the instance is in the same year as the current moment last year. + * @method bool isSameWeek(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same week as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentWeek() Checks if the instance is in the same week as the current moment. + * @method bool isNextWeek() Checks if the instance is in the same week as the current moment next week. + * @method bool isLastWeek() Checks if the instance is in the same week as the current moment last week. + * @method bool isSameDay(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same day as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentDay() Checks if the instance is in the same day as the current moment. + * @method bool isNextDay() Checks if the instance is in the same day as the current moment next day. + * @method bool isLastDay() Checks if the instance is in the same day as the current moment last day. + * @method bool isSameHour(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same hour as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentHour() Checks if the instance is in the same hour as the current moment. + * @method bool isNextHour() Checks if the instance is in the same hour as the current moment next hour. + * @method bool isLastHour() Checks if the instance is in the same hour as the current moment last hour. + * @method bool isSameMinute(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same minute as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMinute() Checks if the instance is in the same minute as the current moment. + * @method bool isNextMinute() Checks if the instance is in the same minute as the current moment next minute. + * @method bool isLastMinute() Checks if the instance is in the same minute as the current moment last minute. + * @method bool isSameSecond(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same second as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentSecond() Checks if the instance is in the same second as the current moment. + * @method bool isNextSecond() Checks if the instance is in the same second as the current moment next second. + * @method bool isLastSecond() Checks if the instance is in the same second as the current moment last second. + * @method bool isSameMicro(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMicro() Checks if the instance is in the same microsecond as the current moment. + * @method bool isNextMicro() Checks if the instance is in the same microsecond as the current moment next microsecond. + * @method bool isLastMicro() Checks if the instance is in the same microsecond as the current moment last microsecond. + * @method bool isSameMicrosecond(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMicrosecond() Checks if the instance is in the same microsecond as the current moment. + * @method bool isNextMicrosecond() Checks if the instance is in the same microsecond as the current moment next microsecond. + * @method bool isLastMicrosecond() Checks if the instance is in the same microsecond as the current moment last microsecond. + * @method bool isCurrentMonth() Checks if the instance is in the same month as the current moment. + * @method bool isNextMonth() Checks if the instance is in the same month as the current moment next month. + * @method bool isLastMonth() Checks if the instance is in the same month as the current moment last month. + * @method bool isCurrentQuarter() Checks if the instance is in the same quarter as the current moment. + * @method bool isNextQuarter() Checks if the instance is in the same quarter as the current moment next quarter. + * @method bool isLastQuarter() Checks if the instance is in the same quarter as the current moment last quarter. + * @method bool isSameDecade(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same decade as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentDecade() Checks if the instance is in the same decade as the current moment. + * @method bool isNextDecade() Checks if the instance is in the same decade as the current moment next decade. + * @method bool isLastDecade() Checks if the instance is in the same decade as the current moment last decade. + * @method bool isSameCentury(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same century as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentCentury() Checks if the instance is in the same century as the current moment. + * @method bool isNextCentury() Checks if the instance is in the same century as the current moment next century. + * @method bool isLastCentury() Checks if the instance is in the same century as the current moment last century. + * @method bool isSameMillennium(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same millennium as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMillennium() Checks if the instance is in the same millennium as the current moment. + * @method bool isNextMillennium() Checks if the instance is in the same millennium as the current moment next millennium. + * @method bool isLastMillennium() Checks if the instance is in the same millennium as the current moment last millennium. + * @method CarbonInterface years(int $value) Set current instance year to the given value. + * @method CarbonInterface year(int $value) Set current instance year to the given value. + * @method CarbonInterface setYears(int $value) Set current instance year to the given value. + * @method CarbonInterface setYear(int $value) Set current instance year to the given value. + * @method CarbonInterface months(int $value) Set current instance month to the given value. + * @method CarbonInterface month(int $value) Set current instance month to the given value. + * @method CarbonInterface setMonths(int $value) Set current instance month to the given value. + * @method CarbonInterface setMonth(int $value) Set current instance month to the given value. + * @method CarbonInterface days(int $value) Set current instance day to the given value. + * @method CarbonInterface day(int $value) Set current instance day to the given value. + * @method CarbonInterface setDays(int $value) Set current instance day to the given value. + * @method CarbonInterface setDay(int $value) Set current instance day to the given value. + * @method CarbonInterface hours(int $value) Set current instance hour to the given value. + * @method CarbonInterface hour(int $value) Set current instance hour to the given value. + * @method CarbonInterface setHours(int $value) Set current instance hour to the given value. + * @method CarbonInterface setHour(int $value) Set current instance hour to the given value. + * @method CarbonInterface minutes(int $value) Set current instance minute to the given value. + * @method CarbonInterface minute(int $value) Set current instance minute to the given value. + * @method CarbonInterface setMinutes(int $value) Set current instance minute to the given value. + * @method CarbonInterface setMinute(int $value) Set current instance minute to the given value. + * @method CarbonInterface seconds(int $value) Set current instance second to the given value. + * @method CarbonInterface second(int $value) Set current instance second to the given value. + * @method CarbonInterface setSeconds(int $value) Set current instance second to the given value. + * @method CarbonInterface setSecond(int $value) Set current instance second to the given value. + * @method CarbonInterface millis(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface milli(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface setMillis(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface setMilli(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface milliseconds(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface millisecond(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface setMilliseconds(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface setMillisecond(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface micros(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface micro(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface setMicros(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface setMicro(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface microseconds(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface microsecond(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface setMicroseconds(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface setMicrosecond(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface addYears(int $value = 1) Add years (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addYear() Add one year to the instance (using date interval). + * @method CarbonInterface subYears(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subYear() Sub one year to the instance (using date interval). + * @method CarbonInterface addYearsWithOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addYearWithOverflow() Add one year to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subYearsWithOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subYearWithOverflow() Sub one year to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addYearsWithoutOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearWithoutOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearsWithoutOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearWithoutOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearsWithNoOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearWithNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearsWithNoOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearWithNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearsNoOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearsNoOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonths(int $value = 1) Add months (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMonth() Add one month to the instance (using date interval). + * @method CarbonInterface subMonths(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMonth() Sub one month to the instance (using date interval). + * @method CarbonInterface addMonthsWithOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addMonthWithOverflow() Add one month to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subMonthsWithOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subMonthWithOverflow() Sub one month to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addMonthsWithoutOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthWithoutOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthsWithoutOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthWithoutOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthsWithNoOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthWithNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthsWithNoOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthWithNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthsNoOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthsNoOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDays(int $value = 1) Add days (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addDay() Add one day to the instance (using date interval). + * @method CarbonInterface subDays(int $value = 1) Sub days (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subDay() Sub one day to the instance (using date interval). + * @method CarbonInterface addHours(int $value = 1) Add hours (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addHour() Add one hour to the instance (using date interval). + * @method CarbonInterface subHours(int $value = 1) Sub hours (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subHour() Sub one hour to the instance (using date interval). + * @method CarbonInterface addMinutes(int $value = 1) Add minutes (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMinute() Add one minute to the instance (using date interval). + * @method CarbonInterface subMinutes(int $value = 1) Sub minutes (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMinute() Sub one minute to the instance (using date interval). + * @method CarbonInterface addSeconds(int $value = 1) Add seconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addSecond() Add one second to the instance (using date interval). + * @method CarbonInterface subSeconds(int $value = 1) Sub seconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subSecond() Sub one second to the instance (using date interval). + * @method CarbonInterface addMillis(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMilli() Add one millisecond to the instance (using date interval). + * @method CarbonInterface subMillis(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMilli() Sub one millisecond to the instance (using date interval). + * @method CarbonInterface addMilliseconds(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMillisecond() Add one millisecond to the instance (using date interval). + * @method CarbonInterface subMilliseconds(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMillisecond() Sub one millisecond to the instance (using date interval). + * @method CarbonInterface addMicros(int $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMicro() Add one microsecond to the instance (using date interval). + * @method CarbonInterface subMicros(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMicro() Sub one microsecond to the instance (using date interval). + * @method CarbonInterface addMicroseconds(int $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMicrosecond() Add one microsecond to the instance (using date interval). + * @method CarbonInterface subMicroseconds(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMicrosecond() Sub one microsecond to the instance (using date interval). + * @method CarbonInterface addMillennia(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMillennium() Add one millennium to the instance (using date interval). + * @method CarbonInterface subMillennia(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMillennium() Sub one millennium to the instance (using date interval). + * @method CarbonInterface addMillenniaWithOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addMillenniumWithOverflow() Add one millennium to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subMillenniaWithOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subMillenniumWithOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addMillenniaWithoutOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniumWithoutOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniaWithoutOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniumWithoutOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniaWithNoOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniumWithNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniaWithNoOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniumWithNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniaNoOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniumNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniaNoOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniumNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturies(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addCentury() Add one century to the instance (using date interval). + * @method CarbonInterface subCenturies(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subCentury() Sub one century to the instance (using date interval). + * @method CarbonInterface addCenturiesWithOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addCenturyWithOverflow() Add one century to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subCenturiesWithOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subCenturyWithOverflow() Sub one century to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addCenturiesWithoutOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturyWithoutOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturiesWithoutOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturyWithoutOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturiesWithNoOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturyWithNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturiesWithNoOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturyWithNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturiesNoOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturyNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturiesNoOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturyNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecades(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addDecade() Add one decade to the instance (using date interval). + * @method CarbonInterface subDecades(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subDecade() Sub one decade to the instance (using date interval). + * @method CarbonInterface addDecadesWithOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addDecadeWithOverflow() Add one decade to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subDecadesWithOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subDecadeWithOverflow() Sub one decade to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addDecadesWithoutOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadeWithoutOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadesWithoutOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadeWithoutOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadesWithNoOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadeWithNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadesWithNoOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadeWithNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadesNoOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadeNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadesNoOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadeNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuarters(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addQuarter() Add one quarter to the instance (using date interval). + * @method CarbonInterface subQuarters(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subQuarter() Sub one quarter to the instance (using date interval). + * @method CarbonInterface addQuartersWithOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addQuarterWithOverflow() Add one quarter to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subQuartersWithOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subQuarterWithOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addQuartersWithoutOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuarterWithoutOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuartersWithoutOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuarterWithoutOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuartersWithNoOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuarterWithNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuartersWithNoOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuarterWithNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuartersNoOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuarterNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuartersNoOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuarterNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addWeeks(int $value = 1) Add weeks (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addWeek() Add one week to the instance (using date interval). + * @method CarbonInterface subWeeks(int $value = 1) Sub weeks (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subWeek() Sub one week to the instance (using date interval). + * @method CarbonInterface addWeekdays(int $value = 1) Add weekdays (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addWeekday() Add one weekday to the instance (using date interval). + * @method CarbonInterface subWeekdays(int $value = 1) Sub weekdays (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subWeekday() Sub one weekday to the instance (using date interval). + * @method CarbonInterface addRealMicros(int $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMicro() Add one microsecond to the instance (using timestamp). + * @method CarbonInterface subRealMicros(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMicro() Sub one microsecond to the instance (using timestamp). + * @method CarbonPeriod microsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each microsecond or every X microseconds if a factor is given. + * @method CarbonInterface addRealMicroseconds(int $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMicrosecond() Add one microsecond to the instance (using timestamp). + * @method CarbonInterface subRealMicroseconds(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMicrosecond() Sub one microsecond to the instance (using timestamp). + * @method CarbonPeriod microsecondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each microsecond or every X microseconds if a factor is given. + * @method CarbonInterface addRealMillis(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMilli() Add one millisecond to the instance (using timestamp). + * @method CarbonInterface subRealMillis(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMilli() Sub one millisecond to the instance (using timestamp). + * @method CarbonPeriod millisUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millisecond or every X milliseconds if a factor is given. + * @method CarbonInterface addRealMilliseconds(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMillisecond() Add one millisecond to the instance (using timestamp). + * @method CarbonInterface subRealMilliseconds(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMillisecond() Sub one millisecond to the instance (using timestamp). + * @method CarbonPeriod millisecondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millisecond or every X milliseconds if a factor is given. + * @method CarbonInterface addRealSeconds(int $value = 1) Add seconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealSecond() Add one second to the instance (using timestamp). + * @method CarbonInterface subRealSeconds(int $value = 1) Sub seconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealSecond() Sub one second to the instance (using timestamp). + * @method CarbonPeriod secondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each second or every X seconds if a factor is given. + * @method CarbonInterface addRealMinutes(int $value = 1) Add minutes (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMinute() Add one minute to the instance (using timestamp). + * @method CarbonInterface subRealMinutes(int $value = 1) Sub minutes (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMinute() Sub one minute to the instance (using timestamp). + * @method CarbonPeriod minutesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each minute or every X minutes if a factor is given. + * @method CarbonInterface addRealHours(int $value = 1) Add hours (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealHour() Add one hour to the instance (using timestamp). + * @method CarbonInterface subRealHours(int $value = 1) Sub hours (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealHour() Sub one hour to the instance (using timestamp). + * @method CarbonPeriod hoursUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each hour or every X hours if a factor is given. + * @method CarbonInterface addRealDays(int $value = 1) Add days (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealDay() Add one day to the instance (using timestamp). + * @method CarbonInterface subRealDays(int $value = 1) Sub days (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealDay() Sub one day to the instance (using timestamp). + * @method CarbonPeriod daysUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each day or every X days if a factor is given. + * @method CarbonInterface addRealWeeks(int $value = 1) Add weeks (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealWeek() Add one week to the instance (using timestamp). + * @method CarbonInterface subRealWeeks(int $value = 1) Sub weeks (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealWeek() Sub one week to the instance (using timestamp). + * @method CarbonPeriod weeksUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each week or every X weeks if a factor is given. + * @method CarbonInterface addRealMonths(int $value = 1) Add months (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMonth() Add one month to the instance (using timestamp). + * @method CarbonInterface subRealMonths(int $value = 1) Sub months (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMonth() Sub one month to the instance (using timestamp). + * @method CarbonPeriod monthsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each month or every X months if a factor is given. + * @method CarbonInterface addRealQuarters(int $value = 1) Add quarters (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealQuarter() Add one quarter to the instance (using timestamp). + * @method CarbonInterface subRealQuarters(int $value = 1) Sub quarters (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealQuarter() Sub one quarter to the instance (using timestamp). + * @method CarbonPeriod quartersUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each quarter or every X quarters if a factor is given. + * @method CarbonInterface addRealYears(int $value = 1) Add years (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealYear() Add one year to the instance (using timestamp). + * @method CarbonInterface subRealYears(int $value = 1) Sub years (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealYear() Sub one year to the instance (using timestamp). + * @method CarbonPeriod yearsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each year or every X years if a factor is given. + * @method CarbonInterface addRealDecades(int $value = 1) Add decades (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealDecade() Add one decade to the instance (using timestamp). + * @method CarbonInterface subRealDecades(int $value = 1) Sub decades (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealDecade() Sub one decade to the instance (using timestamp). + * @method CarbonPeriod decadesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each decade or every X decades if a factor is given. + * @method CarbonInterface addRealCenturies(int $value = 1) Add centuries (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealCentury() Add one century to the instance (using timestamp). + * @method CarbonInterface subRealCenturies(int $value = 1) Sub centuries (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealCentury() Sub one century to the instance (using timestamp). + * @method CarbonPeriod centuriesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each century or every X centuries if a factor is given. + * @method CarbonInterface addRealMillennia(int $value = 1) Add millennia (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addRealMillennium() Add one millennium to the instance (using timestamp). + * @method CarbonInterface subRealMillennia(int $value = 1) Sub millennia (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subRealMillennium() Sub one millennium to the instance (using timestamp). + * @method CarbonPeriod millenniaUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millennium or every X millennia if a factor is given. + * @method CarbonInterface roundYear(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method CarbonInterface roundYears(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method CarbonInterface floorYear(float $precision = 1) Truncate the current instance year with given precision. + * @method CarbonInterface floorYears(float $precision = 1) Truncate the current instance year with given precision. + * @method CarbonInterface ceilYear(float $precision = 1) Ceil the current instance year with given precision. + * @method CarbonInterface ceilYears(float $precision = 1) Ceil the current instance year with given precision. + * @method CarbonInterface roundMonth(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method CarbonInterface roundMonths(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method CarbonInterface floorMonth(float $precision = 1) Truncate the current instance month with given precision. + * @method CarbonInterface floorMonths(float $precision = 1) Truncate the current instance month with given precision. + * @method CarbonInterface ceilMonth(float $precision = 1) Ceil the current instance month with given precision. + * @method CarbonInterface ceilMonths(float $precision = 1) Ceil the current instance month with given precision. + * @method CarbonInterface roundDay(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method CarbonInterface roundDays(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method CarbonInterface floorDay(float $precision = 1) Truncate the current instance day with given precision. + * @method CarbonInterface floorDays(float $precision = 1) Truncate the current instance day with given precision. + * @method CarbonInterface ceilDay(float $precision = 1) Ceil the current instance day with given precision. + * @method CarbonInterface ceilDays(float $precision = 1) Ceil the current instance day with given precision. + * @method CarbonInterface roundHour(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method CarbonInterface roundHours(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method CarbonInterface floorHour(float $precision = 1) Truncate the current instance hour with given precision. + * @method CarbonInterface floorHours(float $precision = 1) Truncate the current instance hour with given precision. + * @method CarbonInterface ceilHour(float $precision = 1) Ceil the current instance hour with given precision. + * @method CarbonInterface ceilHours(float $precision = 1) Ceil the current instance hour with given precision. + * @method CarbonInterface roundMinute(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method CarbonInterface roundMinutes(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method CarbonInterface floorMinute(float $precision = 1) Truncate the current instance minute with given precision. + * @method CarbonInterface floorMinutes(float $precision = 1) Truncate the current instance minute with given precision. + * @method CarbonInterface ceilMinute(float $precision = 1) Ceil the current instance minute with given precision. + * @method CarbonInterface ceilMinutes(float $precision = 1) Ceil the current instance minute with given precision. + * @method CarbonInterface roundSecond(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method CarbonInterface roundSeconds(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method CarbonInterface floorSecond(float $precision = 1) Truncate the current instance second with given precision. + * @method CarbonInterface floorSeconds(float $precision = 1) Truncate the current instance second with given precision. + * @method CarbonInterface ceilSecond(float $precision = 1) Ceil the current instance second with given precision. + * @method CarbonInterface ceilSeconds(float $precision = 1) Ceil the current instance second with given precision. + * @method CarbonInterface roundMillennium(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method CarbonInterface roundMillennia(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method CarbonInterface floorMillennium(float $precision = 1) Truncate the current instance millennium with given precision. + * @method CarbonInterface floorMillennia(float $precision = 1) Truncate the current instance millennium with given precision. + * @method CarbonInterface ceilMillennium(float $precision = 1) Ceil the current instance millennium with given precision. + * @method CarbonInterface ceilMillennia(float $precision = 1) Ceil the current instance millennium with given precision. + * @method CarbonInterface roundCentury(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method CarbonInterface roundCenturies(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method CarbonInterface floorCentury(float $precision = 1) Truncate the current instance century with given precision. + * @method CarbonInterface floorCenturies(float $precision = 1) Truncate the current instance century with given precision. + * @method CarbonInterface ceilCentury(float $precision = 1) Ceil the current instance century with given precision. + * @method CarbonInterface ceilCenturies(float $precision = 1) Ceil the current instance century with given precision. + * @method CarbonInterface roundDecade(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method CarbonInterface roundDecades(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method CarbonInterface floorDecade(float $precision = 1) Truncate the current instance decade with given precision. + * @method CarbonInterface floorDecades(float $precision = 1) Truncate the current instance decade with given precision. + * @method CarbonInterface ceilDecade(float $precision = 1) Ceil the current instance decade with given precision. + * @method CarbonInterface ceilDecades(float $precision = 1) Ceil the current instance decade with given precision. + * @method CarbonInterface roundQuarter(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method CarbonInterface roundQuarters(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method CarbonInterface floorQuarter(float $precision = 1) Truncate the current instance quarter with given precision. + * @method CarbonInterface floorQuarters(float $precision = 1) Truncate the current instance quarter with given precision. + * @method CarbonInterface ceilQuarter(float $precision = 1) Ceil the current instance quarter with given precision. + * @method CarbonInterface ceilQuarters(float $precision = 1) Ceil the current instance quarter with given precision. + * @method CarbonInterface roundMillisecond(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method CarbonInterface roundMilliseconds(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method CarbonInterface floorMillisecond(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method CarbonInterface floorMilliseconds(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method CarbonInterface ceilMillisecond(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method CarbonInterface ceilMilliseconds(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method CarbonInterface roundMicrosecond(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method CarbonInterface roundMicroseconds(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method CarbonInterface floorMicrosecond(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method CarbonInterface floorMicroseconds(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method CarbonInterface ceilMicrosecond(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method CarbonInterface ceilMicroseconds(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method string shortAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * + * + */ +trait Date +{ + use Boundaries; + use Comparison; + use Converter; + use Creator; + use Difference; + use Macro; + use Modifiers; + use Mutability; + use ObjectInitialisation; + use Options; + use Rounding; + use Serialization; + use Test; + use Timestamp; + use Units; + use Week; + + /** + * Names of days of the week. + * + * @var array + */ + protected static $days = [ + // @call isDayOfWeek + CarbonInterface::SUNDAY => 'Sunday', + // @call isDayOfWeek + CarbonInterface::MONDAY => 'Monday', + // @call isDayOfWeek + CarbonInterface::TUESDAY => 'Tuesday', + // @call isDayOfWeek + CarbonInterface::WEDNESDAY => 'Wednesday', + // @call isDayOfWeek + CarbonInterface::THURSDAY => 'Thursday', + // @call isDayOfWeek + CarbonInterface::FRIDAY => 'Friday', + // @call isDayOfWeek + CarbonInterface::SATURDAY => 'Saturday', + ]; + + /** + * Will UTF8 encoding be used to print localized date/time ? + * + * @var bool + */ + protected static $utf8 = false; + + /** + * List of unit and magic methods associated as doc-comments. + * + * @var array + */ + protected static $units = [ + // @call setUnit + // @call addUnit + 'year', + // @call setUnit + // @call addUnit + 'month', + // @call setUnit + // @call addUnit + 'day', + // @call setUnit + // @call addUnit + 'hour', + // @call setUnit + // @call addUnit + 'minute', + // @call setUnit + // @call addUnit + 'second', + // @call setUnit + // @call addUnit + 'milli', + // @call setUnit + // @call addUnit + 'millisecond', + // @call setUnit + // @call addUnit + 'micro', + // @call setUnit + // @call addUnit + 'microsecond', + ]; + + /** + * Creates a DateTimeZone from a string, DateTimeZone or integer offset. + * + * @param DateTimeZone|string|int|null $object original value to get CarbonTimeZone from it. + * @param DateTimeZone|string|int|null $objectDump dump of the object for error messages. + * + * @throws InvalidTimeZoneException + * + * @return CarbonTimeZone|false + */ + protected static function safeCreateDateTimeZone($object, $objectDump = null) + { + return CarbonTimeZone::instance($object, $objectDump); + } + + /** + * Get the TimeZone associated with the Carbon instance (as CarbonTimeZone). + * + * @return CarbonTimeZone + * + * @link https://php.net/manual/en/datetime.gettimezone.php + */ + #[ReturnTypeWillChange] + public function getTimezone() + { + return CarbonTimeZone::instance(parent::getTimezone()); + } + + /** + * List of minimum and maximums for each unit. + * + * @return array + */ + protected static function getRangesByUnit() + { + return [ + // @call roundUnit + 'year' => [1, 9999], + // @call roundUnit + 'month' => [1, static::MONTHS_PER_YEAR], + // @call roundUnit + 'day' => [1, 31], + // @call roundUnit + 'hour' => [0, static::HOURS_PER_DAY - 1], + // @call roundUnit + 'minute' => [0, static::MINUTES_PER_HOUR - 1], + // @call roundUnit + 'second' => [0, static::SECONDS_PER_MINUTE - 1], + ]; + } + + /** + * Get a copy of the instance. + * + * @return static + */ + public function copy() + { + return clone $this; + } + + /** + * @alias copy + * + * Get a copy of the instance. + * + * @return static + */ + public function clone() + { + return clone $this; + } + + /** + * Clone the current instance if it's mutable. + * + * This method is convenient to ensure you don't mutate the initial object + * but avoid to make a useless copy of it if it's already immutable. + * + * @return static + */ + public function avoidMutation(): self + { + if ($this instanceof DateTimeImmutable) { + return $this; + } + + return clone $this; + } + + /** + * Returns a present instance in the same timezone. + * + * @return static + */ + public function nowWithSameTz() + { + return static::now($this->getTimezone()); + } + + /** + * Throws an exception if the given object is not a DateTime and does not implement DateTimeInterface. + * + * @param mixed $date + * @param string|array $other + * + * @throws InvalidTypeException + */ + protected static function expectDateTime($date, $other = []) + { + $message = 'Expected '; + foreach ((array) $other as $expect) { + $message .= "$expect, "; + } + + if (!$date instanceof DateTime && !$date instanceof DateTimeInterface) { + throw new InvalidTypeException( + $message.'DateTime or DateTimeInterface, '. + (\is_object($date) ? \get_class($date) : \gettype($date)).' given' + ); + } + } + + /** + * Return the Carbon instance passed through, a now instance in the same timezone + * if null given or parse the input if string given. + * + * @param Carbon|DateTimeInterface|string|null $date + * + * @return static + */ + protected function resolveCarbon($date = null) + { + if (!$date) { + return $this->nowWithSameTz(); + } + + if (\is_string($date)) { + return static::parse($date, $this->getTimezone()); + } + + static::expectDateTime($date, ['null', 'string']); + + return $date instanceof self ? $date : static::instance($date); + } + + /** + * Return the Carbon instance passed through, a now instance in UTC + * if null given or parse the input if string given (using current timezone + * then switching to UTC). + * + * @param Carbon|DateTimeInterface|string|null $date + * + * @return static + */ + protected function resolveUTC($date = null): self + { + if (!$date) { + return static::now('UTC'); + } + + if (\is_string($date)) { + return static::parse($date, $this->getTimezone())->utc(); + } + + static::expectDateTime($date, ['null', 'string']); + + return $date instanceof self ? $date : static::instance($date)->utc(); + } + + /** + * Return the Carbon instance passed through, a now instance in the same timezone + * if null given or parse the input if string given. + * + * @param Carbon|\Carbon\CarbonPeriod|\Carbon\CarbonInterval|\DateInterval|\DatePeriod|DateTimeInterface|string|null $date + * + * @return static + */ + public function carbonize($date = null) + { + if ($date instanceof DateInterval) { + return $this->avoidMutation()->add($date); + } + + if ($date instanceof DatePeriod || $date instanceof CarbonPeriod) { + $date = $date->getStartDate(); + } + + return $this->resolveCarbon($date); + } + + /////////////////////////////////////////////////////////////////// + ///////////////////////// GETTERS AND SETTERS ///////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Get a part of the Carbon object + * + * @param string $name + * + * @throws UnknownGetterException + * + * @return string|int|bool|DateTimeZone|null + */ + public function __get($name) + { + return $this->get($name); + } + + /** + * Get a part of the Carbon object + * + * @param string $name + * + * @throws UnknownGetterException + * + * @return string|int|bool|DateTimeZone|null + */ + public function get($name) + { + static $formats = [ + // @property int + 'year' => 'Y', + // @property int + 'yearIso' => 'o', + // @property int + // @call isSameUnit + 'month' => 'n', + // @property int + 'day' => 'j', + // @property int + 'hour' => 'G', + // @property int + 'minute' => 'i', + // @property int + 'second' => 's', + // @property int + 'micro' => 'u', + // @property int + 'microsecond' => 'u', + // @property-read int 0 (for Sunday) through 6 (for Saturday) + 'dayOfWeek' => 'w', + // @property-read int 1 (for Monday) through 7 (for Sunday) + 'dayOfWeekIso' => 'N', + // @property-read int ISO-8601 week number of year, weeks starting on Monday + 'weekOfYear' => 'W', + // @property-read int number of days in the given month + 'daysInMonth' => 't', + // @property int|float|string seconds since the Unix Epoch + 'timestamp' => 'U', + // @property-read string "am"/"pm" (Ante meridiem or Post meridiem latin lowercase mark) + 'latinMeridiem' => 'a', + // @property-read string "AM"/"PM" (Ante meridiem or Post meridiem latin uppercase mark) + 'latinUpperMeridiem' => 'A', + // @property string the day of week in English + 'englishDayOfWeek' => 'l', + // @property string the abbreviated day of week in English + 'shortEnglishDayOfWeek' => 'D', + // @property string the month in English + 'englishMonth' => 'F', + // @property string the abbreviated month in English + 'shortEnglishMonth' => 'M', + // @property string the day of week in current locale LC_TIME + // @deprecated + // reason: It uses OS language package and strftime() which is deprecated since PHP 8.1. + // replacement: Use ->isoFormat('MMM') instead. + // since: 2.55.0 + 'localeDayOfWeek' => '%A', + // @property string the abbreviated day of week in current locale LC_TIME + // @deprecated + // reason: It uses OS language package and strftime() which is deprecated since PHP 8.1. + // replacement: Use ->isoFormat('dddd') instead. + // since: 2.55.0 + 'shortLocaleDayOfWeek' => '%a', + // @property string the month in current locale LC_TIME + // @deprecated + // reason: It uses OS language package and strftime() which is deprecated since PHP 8.1. + // replacement: Use ->isoFormat('ddd') instead. + // since: 2.55.0 + 'localeMonth' => '%B', + // @property string the abbreviated month in current locale LC_TIME + // @deprecated + // reason: It uses OS language package and strftime() which is deprecated since PHP 8.1. + // replacement: Use ->isoFormat('MMMM') instead. + // since: 2.55.0 + 'shortLocaleMonth' => '%b', + // @property-read string $timezoneAbbreviatedName the current timezone abbreviated name + 'timezoneAbbreviatedName' => 'T', + // @property-read string $tzAbbrName alias of $timezoneAbbreviatedName + 'tzAbbrName' => 'T', + ]; + + switch (true) { + case isset($formats[$name]): + $format = $formats[$name]; + $method = str_starts_with($format, '%') ? 'formatLocalized' : 'rawFormat'; + $value = $this->$method($format); + + return is_numeric($value) ? (int) $value : $value; + + // @property-read string long name of weekday translated according to Carbon locale, in english if no translation available for current language + case $name === 'dayName': + return $this->getTranslatedDayName(); + // @property-read string short name of weekday translated according to Carbon locale, in english if no translation available for current language + case $name === 'shortDayName': + return $this->getTranslatedShortDayName(); + // @property-read string very short name of weekday translated according to Carbon locale, in english if no translation available for current language + case $name === 'minDayName': + return $this->getTranslatedMinDayName(); + // @property-read string long name of month translated according to Carbon locale, in english if no translation available for current language + case $name === 'monthName': + return $this->getTranslatedMonthName(); + // @property-read string short name of month translated according to Carbon locale, in english if no translation available for current language + case $name === 'shortMonthName': + return $this->getTranslatedShortMonthName(); + // @property-read string lowercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language + case $name === 'meridiem': + return $this->meridiem(true); + // @property-read string uppercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language + case $name === 'upperMeridiem': + return $this->meridiem(); + // @property-read int current hour from 1 to 24 + case $name === 'noZeroHour': + return $this->hour ?: 24; + // @property int + case $name === 'milliseconds': + // @property int + case $name === 'millisecond': + // @property int + case $name === 'milli': + return (int) floor($this->rawFormat('u') / 1000); + + // @property int 1 through 53 + case $name === 'week': + return (int) $this->week(); + + // @property int 1 through 53 + case $name === 'isoWeek': + return (int) $this->isoWeek(); + + // @property int year according to week format + case $name === 'weekYear': + return (int) $this->weekYear(); + + // @property int year according to ISO week format + case $name === 'isoWeekYear': + return (int) $this->isoWeekYear(); + + // @property-read int 51 through 53 + case $name === 'weeksInYear': + return $this->weeksInYear(); + + // @property-read int 51 through 53 + case $name === 'isoWeeksInYear': + return $this->isoWeeksInYear(); + + // @property-read int 1 through 5 + case $name === 'weekOfMonth': + return (int) ceil($this->day / static::DAYS_PER_WEEK); + + // @property-read int 1 through 5 + case $name === 'weekNumberInMonth': + return (int) ceil(($this->day + $this->avoidMutation()->startOfMonth()->dayOfWeekIso - 1) / static::DAYS_PER_WEEK); + + // @property-read int 0 through 6 + case $name === 'firstWeekDay': + return $this->localTranslator ? ($this->getTranslationMessage('first_day_of_week') ?? 0) : static::getWeekStartsAt(); + + // @property-read int 0 through 6 + case $name === 'lastWeekDay': + return $this->localTranslator ? (($this->getTranslationMessage('first_day_of_week') ?? 0) + static::DAYS_PER_WEEK - 1) % static::DAYS_PER_WEEK : static::getWeekEndsAt(); + + // @property int 1 through 366 + case $name === 'dayOfYear': + return 1 + (int) ($this->rawFormat('z')); + + // @property-read int 365 or 366 + case $name === 'daysInYear': + return $this->isLeapYear() ? 366 : 365; + + // @property int does a diffInYears() with default parameters + case $name === 'age': + return $this->diffInYears(); + + // @property-read int the quarter of this instance, 1 - 4 + // @call isSameUnit + case $name === 'quarter': + return (int) ceil($this->month / static::MONTHS_PER_QUARTER); + + // @property-read int the decade of this instance + // @call isSameUnit + case $name === 'decade': + return (int) ceil($this->year / static::YEARS_PER_DECADE); + + // @property-read int the century of this instance + // @call isSameUnit + case $name === 'century': + $factor = 1; + $year = $this->year; + if ($year < 0) { + $year = -$year; + $factor = -1; + } + + return (int) ($factor * ceil($year / static::YEARS_PER_CENTURY)); + + // @property-read int the millennium of this instance + // @call isSameUnit + case $name === 'millennium': + $factor = 1; + $year = $this->year; + if ($year < 0) { + $year = -$year; + $factor = -1; + } + + return (int) ($factor * ceil($year / static::YEARS_PER_MILLENNIUM)); + + // @property int the timezone offset in seconds from UTC + case $name === 'offset': + return $this->getOffset(); + + // @property int the timezone offset in minutes from UTC + case $name === 'offsetMinutes': + return $this->getOffset() / static::SECONDS_PER_MINUTE; + + // @property int the timezone offset in hours from UTC + case $name === 'offsetHours': + return $this->getOffset() / static::SECONDS_PER_MINUTE / static::MINUTES_PER_HOUR; + + // @property-read bool daylight savings time indicator, true if DST, false otherwise + case $name === 'dst': + return $this->rawFormat('I') === '1'; + + // @property-read bool checks if the timezone is local, true if local, false otherwise + case $name === 'local': + return $this->getOffset() === $this->avoidMutation()->setTimezone(date_default_timezone_get())->getOffset(); + + // @property-read bool checks if the timezone is UTC, true if UTC, false otherwise + case $name === 'utc': + return $this->getOffset() === 0; + + // @property CarbonTimeZone $timezone the current timezone + // @property CarbonTimeZone $tz alias of $timezone + case $name === 'timezone' || $name === 'tz': + return CarbonTimeZone::instance($this->getTimezone()); + + // @property-read string $timezoneName the current timezone name + // @property-read string $tzName alias of $timezoneName + case $name === 'timezoneName' || $name === 'tzName': + return $this->getTimezone()->getName(); + + // @property-read string locale of the current instance + case $name === 'locale': + return $this->getTranslatorLocale(); + + default: + $macro = $this->getLocalMacro('get'.ucfirst($name)); + + if ($macro) { + return $this->executeCallableWithContext($macro); + } + + throw new UnknownGetterException($name); + } + } + + /** + * Check if an attribute exists on the object + * + * @param string $name + * + * @return bool + */ + public function __isset($name) + { + try { + $this->__get($name); + } catch (UnknownGetterException | ReflectionException $e) { + return false; + } + + return true; + } + + /** + * Set a part of the Carbon object + * + * @param string $name + * @param string|int|DateTimeZone $value + * + * @throws UnknownSetterException|ReflectionException + * + * @return void + */ + public function __set($name, $value) + { + if ($this->constructedObjectId === spl_object_hash($this)) { + $this->set($name, $value); + + return; + } + + $this->$name = $value; + } + + /** + * Set a part of the Carbon object + * + * @param string|array $name + * @param string|int|DateTimeZone $value + * + * @throws ImmutableException|UnknownSetterException + * + * @return $this + */ + public function set($name, $value = null) + { + if ($this->isImmutable()) { + throw new ImmutableException(sprintf('%s class', static::class)); + } + + if (\is_array($name)) { + foreach ($name as $key => $value) { + $this->set($key, $value); + } + + return $this; + } + + switch ($name) { + case 'milliseconds': + case 'millisecond': + case 'milli': + case 'microseconds': + case 'microsecond': + case 'micro': + if (str_starts_with($name, 'milli')) { + $value *= 1000; + } + + while ($value < 0) { + $this->subSecond(); + $value += static::MICROSECONDS_PER_SECOND; + } + + while ($value >= static::MICROSECONDS_PER_SECOND) { + $this->addSecond(); + $value -= static::MICROSECONDS_PER_SECOND; + } + + $this->modify($this->rawFormat('H:i:s.').str_pad((string) round($value), 6, '0', STR_PAD_LEFT)); + + break; + + case 'year': + case 'month': + case 'day': + case 'hour': + case 'minute': + case 'second': + [$year, $month, $day, $hour, $minute, $second] = array_map('intval', explode('-', $this->rawFormat('Y-n-j-G-i-s'))); + $$name = $value; + $this->setDateTime($year, $month, $day, $hour, $minute, $second); + + break; + + case 'week': + $this->week($value); + + break; + + case 'isoWeek': + $this->isoWeek($value); + + break; + + case 'weekYear': + $this->weekYear($value); + + break; + + case 'isoWeekYear': + $this->isoWeekYear($value); + + break; + + case 'dayOfYear': + $this->addDays($value - $this->dayOfYear); + + break; + + case 'timestamp': + $this->setTimestamp($value); + + break; + + case 'offset': + $this->setTimezone(static::safeCreateDateTimeZone($value / static::SECONDS_PER_MINUTE / static::MINUTES_PER_HOUR)); + + break; + + case 'offsetMinutes': + $this->setTimezone(static::safeCreateDateTimeZone($value / static::MINUTES_PER_HOUR)); + + break; + + case 'offsetHours': + $this->setTimezone(static::safeCreateDateTimeZone($value)); + + break; + + case 'timezone': + case 'tz': + $this->setTimezone($value); + + break; + + default: + $macro = $this->getLocalMacro('set'.ucfirst($name)); + + if ($macro) { + $this->executeCallableWithContext($macro, $value); + + break; + } + + if ($this->localStrictModeEnabled ?? static::isStrictModeEnabled()) { + throw new UnknownSetterException($name); + } + + $this->$name = $value; + } + + return $this; + } + + protected function getTranslatedFormByRegExp($baseKey, $keySuffix, $context, $subKey, $defaultValue) + { + $key = $baseKey.$keySuffix; + $standaloneKey = "${key}_standalone"; + $baseTranslation = $this->getTranslationMessage($key); + + if ($baseTranslation instanceof Closure) { + return $baseTranslation($this, $context, $subKey) ?: $defaultValue; + } + + if ( + $this->getTranslationMessage("$standaloneKey.$subKey") && + (!$context || ($regExp = $this->getTranslationMessage("${baseKey}_regexp")) && !preg_match($regExp, $context)) + ) { + $key = $standaloneKey; + } + + return $this->getTranslationMessage("$key.$subKey", null, $defaultValue); + } + + /** + * Get the translation of the current week day name (with context for languages with multiple forms). + * + * @param string|null $context whole format string + * @param string $keySuffix "", "_short" or "_min" + * @param string|null $defaultValue default value if translation missing + * + * @return string + */ + public function getTranslatedDayName($context = null, $keySuffix = '', $defaultValue = null) + { + return $this->getTranslatedFormByRegExp('weekdays', $keySuffix, $context, $this->dayOfWeek, $defaultValue ?: $this->englishDayOfWeek); + } + + /** + * Get the translation of the current short week day name (with context for languages with multiple forms). + * + * @param string|null $context whole format string + * + * @return string + */ + public function getTranslatedShortDayName($context = null) + { + return $this->getTranslatedDayName($context, '_short', $this->shortEnglishDayOfWeek); + } + + /** + * Get the translation of the current abbreviated week day name (with context for languages with multiple forms). + * + * @param string|null $context whole format string + * + * @return string + */ + public function getTranslatedMinDayName($context = null) + { + return $this->getTranslatedDayName($context, '_min', $this->shortEnglishDayOfWeek); + } + + /** + * Get the translation of the current month day name (with context for languages with multiple forms). + * + * @param string|null $context whole format string + * @param string $keySuffix "" or "_short" + * @param string|null $defaultValue default value if translation missing + * + * @return string + */ + public function getTranslatedMonthName($context = null, $keySuffix = '', $defaultValue = null) + { + return $this->getTranslatedFormByRegExp('months', $keySuffix, $context, $this->month - 1, $defaultValue ?: $this->englishMonth); + } + + /** + * Get the translation of the current short month day name (with context for languages with multiple forms). + * + * @param string|null $context whole format string + * + * @return string + */ + public function getTranslatedShortMonthName($context = null) + { + return $this->getTranslatedMonthName($context, '_short', $this->shortEnglishMonth); + } + + /** + * Get/set the day of year. + * + * @param int|null $value new value for day of year if using as setter. + * + * @return static|int + */ + public function dayOfYear($value = null) + { + $dayOfYear = $this->dayOfYear; + + return $value === null ? $dayOfYear : $this->addDays($value - $dayOfYear); + } + + /** + * Get/set the weekday from 0 (Sunday) to 6 (Saturday). + * + * @param int|null $value new value for weekday if using as setter. + * + * @return static|int + */ + public function weekday($value = null) + { + $dayOfWeek = ($this->dayOfWeek + 7 - (int) ($this->getTranslationMessage('first_day_of_week') ?? 0)) % 7; + + return $value === null ? $dayOfWeek : $this->addDays($value - $dayOfWeek); + } + + /** + * Get/set the ISO weekday from 1 (Monday) to 7 (Sunday). + * + * @param int|null $value new value for weekday if using as setter. + * + * @return static|int + */ + public function isoWeekday($value = null) + { + $dayOfWeekIso = $this->dayOfWeekIso; + + return $value === null ? $dayOfWeekIso : $this->addDays($value - $dayOfWeekIso); + } + + /** + * Set any unit to a new value without overflowing current other unit given. + * + * @param string $valueUnit unit name to modify + * @param int $value new value for the input unit + * @param string $overflowUnit unit name to not overflow + * + * @return static + */ + public function setUnitNoOverflow($valueUnit, $value, $overflowUnit) + { + try { + $original = $this->avoidMutation(); + /** @var static $date */ + $date = $this->$valueUnit($value); + $end = $original->avoidMutation()->endOf($overflowUnit); + $start = $original->avoidMutation()->startOf($overflowUnit); + if ($date < $start) { + $date = $date->setDateTimeFrom($start); + } elseif ($date > $end) { + $date = $date->setDateTimeFrom($end); + } + + return $date; + } catch (BadMethodCallException | ReflectionException $exception) { + throw new UnknownUnitException($valueUnit, 0, $exception); + } + } + + /** + * Add any unit to a new value without overflowing current other unit given. + * + * @param string $valueUnit unit name to modify + * @param int $value amount to add to the input unit + * @param string $overflowUnit unit name to not overflow + * + * @return static + */ + public function addUnitNoOverflow($valueUnit, $value, $overflowUnit) + { + return $this->setUnitNoOverflow($valueUnit, $this->$valueUnit + $value, $overflowUnit); + } + + /** + * Subtract any unit to a new value without overflowing current other unit given. + * + * @param string $valueUnit unit name to modify + * @param int $value amount to subtract to the input unit + * @param string $overflowUnit unit name to not overflow + * + * @return static + */ + public function subUnitNoOverflow($valueUnit, $value, $overflowUnit) + { + return $this->setUnitNoOverflow($valueUnit, $this->$valueUnit - $value, $overflowUnit); + } + + /** + * Returns the minutes offset to UTC if no arguments passed, else set the timezone with given minutes shift passed. + * + * @param int|null $minuteOffset + * + * @return int|static + */ + public function utcOffset(int $minuteOffset = null) + { + if (\func_num_args() < 1) { + return $this->offsetMinutes; + } + + return $this->setTimezone(CarbonTimeZone::createFromMinuteOffset($minuteOffset)); + } + + /** + * Set the date with gregorian year, month and day numbers. + * + * @see https://php.net/manual/en/datetime.setdate.php + * + * @param int $year + * @param int $month + * @param int $day + * + * @return static + */ + #[ReturnTypeWillChange] + public function setDate($year, $month, $day) + { + return parent::setDate((int) $year, (int) $month, (int) $day); + } + + /** + * Set a date according to the ISO 8601 standard - using weeks and day offsets rather than specific dates. + * + * @see https://php.net/manual/en/datetime.setisodate.php + * + * @param int $year + * @param int $week + * @param int $day + * + * @return static + */ + #[ReturnTypeWillChange] + public function setISODate($year, $week, $day = 1) + { + return parent::setISODate((int) $year, (int) $week, (int) $day); + } + + /** + * Set the date and time all together. + * + * @param int $year + * @param int $month + * @param int $day + * @param int $hour + * @param int $minute + * @param int $second + * @param int $microseconds + * + * @return static + */ + public function setDateTime($year, $month, $day, $hour, $minute, $second = 0, $microseconds = 0) + { + return $this->setDate($year, $month, $day)->setTime((int) $hour, (int) $minute, (int) $second, (int) $microseconds); + } + + /** + * Resets the current time of the DateTime object to a different time. + * + * @see https://php.net/manual/en/datetime.settime.php + * + * @param int $hour + * @param int $minute + * @param int $second + * @param int $microseconds + * + * @return static + */ + #[ReturnTypeWillChange] + public function setTime($hour, $minute, $second = 0, $microseconds = 0) + { + return parent::setTime((int) $hour, (int) $minute, (int) $second, (int) $microseconds); + } + + /** + * Set the instance's timestamp. + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $unixTimestamp + * + * @return static + */ + #[ReturnTypeWillChange] + public function setTimestamp($unixTimestamp) + { + [$timestamp, $microseconds] = self::getIntegerAndDecimalParts($unixTimestamp); + + return parent::setTimestamp((int) $timestamp)->setMicroseconds((int) $microseconds); + } + + /** + * Set the time by time string. + * + * @param string $time + * + * @return static + */ + public function setTimeFromTimeString($time) + { + if (!str_contains($time, ':')) { + $time .= ':0'; + } + + return $this->modify($time); + } + + /** + * @alias setTimezone + * + * @param DateTimeZone|string $value + * + * @return static + */ + public function timezone($value) + { + return $this->setTimezone($value); + } + + /** + * Set the timezone or returns the timezone name if no arguments passed. + * + * @param DateTimeZone|string $value + * + * @return static|string + */ + public function tz($value = null) + { + if (\func_num_args() < 1) { + return $this->tzName; + } + + return $this->setTimezone($value); + } + + /** + * Set the instance's timezone from a string or object. + * + * @param DateTimeZone|string $value + * + * @return static + */ + #[ReturnTypeWillChange] + public function setTimezone($value) + { + $tz = static::safeCreateDateTimeZone($value); + + if ($tz === false && !self::isStrictModeEnabled()) { + $tz = new CarbonTimeZone(); + } + + return parent::setTimezone($tz); + } + + /** + * Set the instance's timezone from a string or object and add/subtract the offset difference. + * + * @param DateTimeZone|string $value + * + * @return static + */ + public function shiftTimezone($value) + { + $dateTimeString = $this->format('Y-m-d H:i:s.u'); + + return $this + ->setTimezone($value) + ->modify($dateTimeString); + } + + /** + * Set the instance's timezone to UTC. + * + * @return static + */ + public function utc() + { + return $this->setTimezone('UTC'); + } + + /** + * Set the year, month, and date for this instance to that of the passed instance. + * + * @param Carbon|DateTimeInterface $date now if null + * + * @return static + */ + public function setDateFrom($date = null) + { + $date = $this->resolveCarbon($date); + + return $this->setDate($date->year, $date->month, $date->day); + } + + /** + * Set the hour, minute, second and microseconds for this instance to that of the passed instance. + * + * @param Carbon|DateTimeInterface $date now if null + * + * @return static + */ + public function setTimeFrom($date = null) + { + $date = $this->resolveCarbon($date); + + return $this->setTime($date->hour, $date->minute, $date->second, $date->microsecond); + } + + /** + * Set the date and time for this instance to that of the passed instance. + * + * @param Carbon|DateTimeInterface $date + * + * @return static + */ + public function setDateTimeFrom($date = null) + { + $date = $this->resolveCarbon($date); + + return $this->modify($date->rawFormat('Y-m-d H:i:s.u')); + } + + /** + * Get the days of the week + * + * @return array + */ + public static function getDays() + { + return static::$days; + } + + /////////////////////////////////////////////////////////////////// + /////////////////////// WEEK SPECIAL DAYS ///////////////////////// + /////////////////////////////////////////////////////////////////// + + private static function getFirstDayOfWeek(): int + { + return (int) static::getTranslationMessageWith( + static::getTranslator(), + 'first_day_of_week' + ); + } + + /** + * Get the first day of week + * + * @return int + */ + public static function getWeekStartsAt() + { + if (static::$weekStartsAt === static::WEEK_DAY_AUTO) { + return static::getFirstDayOfWeek(); + } + + return static::$weekStartsAt; + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * Use $weekEndsAt optional parameter instead when using endOfWeek method. You can also use the + * 'first_day_of_week' locale setting to change the start of week according to current locale + * selected and implicitly the end of week. + * + * Set the first day of week + * + * @param int|string $day week start day (or 'auto' to get the first day of week from Carbon::getLocale() culture). + * + * @return void + */ + public static function setWeekStartsAt($day) + { + static::$weekStartsAt = $day === static::WEEK_DAY_AUTO ? $day : max(0, (7 + $day) % 7); + } + + /** + * Get the last day of week + * + * @return int + */ + public static function getWeekEndsAt() + { + if (static::$weekStartsAt === static::WEEK_DAY_AUTO) { + return (int) (static::DAYS_PER_WEEK - 1 + static::getFirstDayOfWeek()) % static::DAYS_PER_WEEK; + } + + return static::$weekEndsAt; + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * Use $weekStartsAt optional parameter instead when using startOfWeek, floorWeek, ceilWeek + * or roundWeek method. You can also use the 'first_day_of_week' locale setting to change the + * start of week according to current locale selected and implicitly the end of week. + * + * Set the last day of week + * + * @param int|string $day week end day (or 'auto' to get the day before the first day of week + * from Carbon::getLocale() culture). + * + * @return void + */ + public static function setWeekEndsAt($day) + { + static::$weekEndsAt = $day === static::WEEK_DAY_AUTO ? $day : max(0, (7 + $day) % 7); + } + + /** + * Get weekend days + * + * @return array + */ + public static function getWeekendDays() + { + return static::$weekendDays; + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather consider week-end is always saturday and sunday, and if you have some custom + * week-end days to handle, give to those days an other name and create a macro for them: + * + * ``` + * Carbon::macro('isDayOff', function ($date) { + * return $date->isSunday() || $date->isMonday(); + * }); + * Carbon::macro('isNotDayOff', function ($date) { + * return !$date->isDayOff(); + * }); + * if ($someDate->isDayOff()) ... + * if ($someDate->isNotDayOff()) ... + * // Add 5 not-off days + * $count = 5; + * while ($someDate->isDayOff() || ($count-- > 0)) { + * $someDate->addDay(); + * } + * ``` + * + * Set weekend days + * + * @param array $days + * + * @return void + */ + public static function setWeekendDays($days) + { + static::$weekendDays = $days; + } + + /** + * Determine if a time string will produce a relative date. + * + * @param string $time + * + * @return bool true if time match a relative date, false if absolute or invalid time string + */ + public static function hasRelativeKeywords($time) + { + if (!$time || strtotime($time) === false) { + return false; + } + + $date1 = new DateTime('2000-01-01T00:00:00Z'); + $date1->modify($time); + $date2 = new DateTime('2001-12-25T00:00:00Z'); + $date2->modify($time); + + return $date1 != $date2; + } + + /////////////////////////////////////////////////////////////////// + /////////////////////// STRING FORMATTING ///////////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use UTF-8 language packages on every machine. + * + * Set if UTF8 will be used for localized date/time. + * + * @param bool $utf8 + */ + public static function setUtf8($utf8) + { + static::$utf8 = $utf8; + } + + /** + * Format the instance with the current locale. You can set the current + * locale using setlocale() https://php.net/setlocale. + * + * @deprecated It uses OS language package and strftime() which is deprecated since PHP 8.1. + * Use ->isoFormat() instead. + * Deprecated since 2.55.0 + * + * @param string $format + * + * @return string + */ + public function formatLocalized($format) + { + // Check for Windows to find and replace the %e modifier correctly. + if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { + $format = preg_replace('#(?toDateTimeString())); + + return static::$utf8 ? utf8_encode($formatted) : $formatted; + } + + /** + * Returns list of locale formats for ISO formatting. + * + * @param string|null $locale current locale used if null + * + * @return array + */ + public function getIsoFormats($locale = null) + { + return [ + 'LT' => $this->getTranslationMessage('formats.LT', $locale, 'h:mm A'), + 'LTS' => $this->getTranslationMessage('formats.LTS', $locale, 'h:mm:ss A'), + 'L' => $this->getTranslationMessage('formats.L', $locale, 'MM/DD/YYYY'), + 'LL' => $this->getTranslationMessage('formats.LL', $locale, 'MMMM D, YYYY'), + 'LLL' => $this->getTranslationMessage('formats.LLL', $locale, 'MMMM D, YYYY h:mm A'), + 'LLLL' => $this->getTranslationMessage('formats.LLLL', $locale, 'dddd, MMMM D, YYYY h:mm A'), + ]; + } + + /** + * Returns list of calendar formats for ISO formatting. + * + * @param string|null $locale current locale used if null + * + * @return array + */ + public function getCalendarFormats($locale = null) + { + return [ + 'sameDay' => $this->getTranslationMessage('calendar.sameDay', $locale, '[Today at] LT'), + 'nextDay' => $this->getTranslationMessage('calendar.nextDay', $locale, '[Tomorrow at] LT'), + 'nextWeek' => $this->getTranslationMessage('calendar.nextWeek', $locale, 'dddd [at] LT'), + 'lastDay' => $this->getTranslationMessage('calendar.lastDay', $locale, '[Yesterday at] LT'), + 'lastWeek' => $this->getTranslationMessage('calendar.lastWeek', $locale, '[Last] dddd [at] LT'), + 'sameElse' => $this->getTranslationMessage('calendar.sameElse', $locale, 'L'), + ]; + } + + /** + * Returns list of locale units for ISO formatting. + * + * @return array + */ + public static function getIsoUnits() + { + static $units = null; + + if ($units === null) { + $units = [ + 'OD' => ['getAltNumber', ['day']], + 'OM' => ['getAltNumber', ['month']], + 'OY' => ['getAltNumber', ['year']], + 'OH' => ['getAltNumber', ['hour']], + 'Oh' => ['getAltNumber', ['h']], + 'Om' => ['getAltNumber', ['minute']], + 'Os' => ['getAltNumber', ['second']], + 'D' => 'day', + 'DD' => ['rawFormat', ['d']], + 'Do' => ['ordinal', ['day', 'D']], + 'd' => 'dayOfWeek', + 'dd' => function (CarbonInterface $date, $originalFormat = null) { + return $date->getTranslatedMinDayName($originalFormat); + }, + 'ddd' => function (CarbonInterface $date, $originalFormat = null) { + return $date->getTranslatedShortDayName($originalFormat); + }, + 'dddd' => function (CarbonInterface $date, $originalFormat = null) { + return $date->getTranslatedDayName($originalFormat); + }, + 'DDD' => 'dayOfYear', + 'DDDD' => ['getPaddedUnit', ['dayOfYear', 3]], + 'DDDo' => ['ordinal', ['dayOfYear', 'DDD']], + 'e' => ['weekday', []], + 'E' => 'dayOfWeekIso', + 'H' => ['rawFormat', ['G']], + 'HH' => ['rawFormat', ['H']], + 'h' => ['rawFormat', ['g']], + 'hh' => ['rawFormat', ['h']], + 'k' => 'noZeroHour', + 'kk' => ['getPaddedUnit', ['noZeroHour']], + 'hmm' => ['rawFormat', ['gi']], + 'hmmss' => ['rawFormat', ['gis']], + 'Hmm' => ['rawFormat', ['Gi']], + 'Hmmss' => ['rawFormat', ['Gis']], + 'm' => 'minute', + 'mm' => ['rawFormat', ['i']], + 'a' => 'meridiem', + 'A' => 'upperMeridiem', + 's' => 'second', + 'ss' => ['getPaddedUnit', ['second']], + 'S' => function (CarbonInterface $date) { + return (string) floor($date->micro / 100000); + }, + 'SS' => function (CarbonInterface $date) { + return str_pad((string) floor($date->micro / 10000), 2, '0', STR_PAD_LEFT); + }, + 'SSS' => function (CarbonInterface $date) { + return str_pad((string) floor($date->micro / 1000), 3, '0', STR_PAD_LEFT); + }, + 'SSSS' => function (CarbonInterface $date) { + return str_pad((string) floor($date->micro / 100), 4, '0', STR_PAD_LEFT); + }, + 'SSSSS' => function (CarbonInterface $date) { + return str_pad((string) floor($date->micro / 10), 5, '0', STR_PAD_LEFT); + }, + 'SSSSSS' => ['getPaddedUnit', ['micro', 6]], + 'SSSSSSS' => function (CarbonInterface $date) { + return str_pad((string) floor($date->micro * 10), 7, '0', STR_PAD_LEFT); + }, + 'SSSSSSSS' => function (CarbonInterface $date) { + return str_pad((string) floor($date->micro * 100), 8, '0', STR_PAD_LEFT); + }, + 'SSSSSSSSS' => function (CarbonInterface $date) { + return str_pad((string) floor($date->micro * 1000), 9, '0', STR_PAD_LEFT); + }, + 'M' => 'month', + 'MM' => ['rawFormat', ['m']], + 'MMM' => function (CarbonInterface $date, $originalFormat = null) { + $month = $date->getTranslatedShortMonthName($originalFormat); + $suffix = $date->getTranslationMessage('mmm_suffix'); + if ($suffix && $month !== $date->monthName) { + $month .= $suffix; + } + + return $month; + }, + 'MMMM' => function (CarbonInterface $date, $originalFormat = null) { + return $date->getTranslatedMonthName($originalFormat); + }, + 'Mo' => ['ordinal', ['month', 'M']], + 'Q' => 'quarter', + 'Qo' => ['ordinal', ['quarter', 'M']], + 'G' => 'isoWeekYear', + 'GG' => ['getPaddedUnit', ['isoWeekYear']], + 'GGG' => ['getPaddedUnit', ['isoWeekYear', 3]], + 'GGGG' => ['getPaddedUnit', ['isoWeekYear', 4]], + 'GGGGG' => ['getPaddedUnit', ['isoWeekYear', 5]], + 'g' => 'weekYear', + 'gg' => ['getPaddedUnit', ['weekYear']], + 'ggg' => ['getPaddedUnit', ['weekYear', 3]], + 'gggg' => ['getPaddedUnit', ['weekYear', 4]], + 'ggggg' => ['getPaddedUnit', ['weekYear', 5]], + 'W' => 'isoWeek', + 'WW' => ['getPaddedUnit', ['isoWeek']], + 'Wo' => ['ordinal', ['isoWeek', 'W']], + 'w' => 'week', + 'ww' => ['getPaddedUnit', ['week']], + 'wo' => ['ordinal', ['week', 'w']], + 'x' => ['valueOf', []], + 'X' => 'timestamp', + 'Y' => 'year', + 'YY' => ['rawFormat', ['y']], + 'YYYY' => ['getPaddedUnit', ['year', 4]], + 'YYYYY' => ['getPaddedUnit', ['year', 5]], + 'YYYYYY' => function (CarbonInterface $date) { + return ($date->year < 0 ? '' : '+').$date->getPaddedUnit('year', 6); + }, + 'z' => ['rawFormat', ['T']], + 'zz' => 'tzName', + 'Z' => ['getOffsetString', []], + 'ZZ' => ['getOffsetString', ['']], + ]; + } + + return $units; + } + + /** + * Returns a unit of the instance padded with 0 by default or any other string if specified. + * + * @param string $unit Carbon unit name + * @param int $length Length of the output (2 by default) + * @param string $padString String to use for padding ("0" by default) + * @param int $padType Side(s) to pad (STR_PAD_LEFT by default) + * + * @return string + */ + public function getPaddedUnit($unit, $length = 2, $padString = '0', $padType = STR_PAD_LEFT) + { + return ($this->$unit < 0 ? '-' : '').str_pad((string) abs($this->$unit), $length, $padString, $padType); + } + + /** + * Return a property with its ordinal. + * + * @param string $key + * @param string|null $period + * + * @return string + */ + public function ordinal(string $key, ?string $period = null): string + { + $number = $this->$key; + $result = $this->translate('ordinal', [ + ':number' => $number, + ':period' => (string) $period, + ]); + + return (string) ($result === 'ordinal' ? $number : $result); + } + + /** + * Return the meridiem of the current time in the current locale. + * + * @param bool $isLower if true, returns lowercase variant if available in the current locale. + * + * @return string + */ + public function meridiem(bool $isLower = false): string + { + $hour = $this->hour; + $index = $hour < 12 ? 0 : 1; + + if ($isLower) { + $key = 'meridiem.'.($index + 2); + $result = $this->translate($key); + + if ($result !== $key) { + return $result; + } + } + + $key = "meridiem.$index"; + $result = $this->translate($key); + if ($result === $key) { + $result = $this->translate('meridiem', [ + ':hour' => $this->hour, + ':minute' => $this->minute, + ':isLower' => $isLower, + ]); + + if ($result === 'meridiem') { + return $isLower ? $this->latinMeridiem : $this->latinUpperMeridiem; + } + } elseif ($isLower) { + $result = mb_strtolower($result); + } + + return $result; + } + + /** + * Returns the alternative number for a given date property if available in the current locale. + * + * @param string $key date property + * + * @return string + */ + public function getAltNumber(string $key): string + { + return $this->translateNumber(\strlen($key) > 1 ? $this->$key : $this->rawFormat('h')); + } + + /** + * Format in the current language using ISO replacement patterns. + * + * @param string $format + * @param string|null $originalFormat provide context if a chunk has been passed alone + * + * @return string + */ + public function isoFormat(string $format, ?string $originalFormat = null): string + { + $result = ''; + $length = mb_strlen($format); + $originalFormat = $originalFormat ?: $format; + $inEscaped = false; + $formats = null; + $units = null; + + for ($i = 0; $i < $length; $i++) { + $char = mb_substr($format, $i, 1); + + if ($char === '\\') { + $result .= mb_substr($format, ++$i, 1); + + continue; + } + + if ($char === '[' && !$inEscaped) { + $inEscaped = true; + + continue; + } + + if ($char === ']' && $inEscaped) { + $inEscaped = false; + + continue; + } + + if ($inEscaped) { + $result .= $char; + + continue; + } + + $input = mb_substr($format, $i); + + if (preg_match('/^(LTS|LT|[Ll]{1,4})/', $input, $match)) { + if ($formats === null) { + $formats = $this->getIsoFormats(); + } + + $code = $match[0]; + $sequence = $formats[$code] ?? preg_replace_callback( + '/MMMM|MM|DD|dddd/', + function ($code) { + return mb_substr($code[0], 1); + }, + $formats[strtoupper($code)] ?? '' + ); + $rest = mb_substr($format, $i + mb_strlen($code)); + $format = mb_substr($format, 0, $i).$sequence.$rest; + $length = mb_strlen($format); + $input = $sequence.$rest; + } + + if (preg_match('/^'.CarbonInterface::ISO_FORMAT_REGEXP.'/', $input, $match)) { + $code = $match[0]; + + if ($units === null) { + $units = static::getIsoUnits(); + } + + $sequence = $units[$code] ?? ''; + + if ($sequence instanceof Closure) { + $sequence = $sequence($this, $originalFormat); + } elseif (\is_array($sequence)) { + try { + $sequence = $this->{$sequence[0]}(...$sequence[1]); + } catch (ReflectionException | InvalidArgumentException | BadMethodCallException $e) { + $sequence = ''; + } + } elseif (\is_string($sequence)) { + $sequence = $this->$sequence ?? $code; + } + + $format = mb_substr($format, 0, $i).$sequence.mb_substr($format, $i + mb_strlen($code)); + $i += mb_strlen((string) $sequence) - 1; + $length = mb_strlen($format); + $char = $sequence; + } + + $result .= $char; + } + + return $result; + } + + /** + * List of replacements from date() format to isoFormat(). + * + * @return array + */ + public static function getFormatsToIsoReplacements() + { + static $replacements = null; + + if ($replacements === null) { + $replacements = [ + 'd' => true, + 'D' => 'ddd', + 'j' => true, + 'l' => 'dddd', + 'N' => true, + 'S' => function ($date) { + $day = $date->rawFormat('j'); + + return str_replace((string) $day, '', $date->isoFormat('Do')); + }, + 'w' => true, + 'z' => true, + 'W' => true, + 'F' => 'MMMM', + 'm' => true, + 'M' => 'MMM', + 'n' => true, + 't' => true, + 'L' => true, + 'o' => true, + 'Y' => true, + 'y' => true, + 'a' => 'a', + 'A' => 'A', + 'B' => true, + 'g' => true, + 'G' => true, + 'h' => true, + 'H' => true, + 'i' => true, + 's' => true, + 'u' => true, + 'v' => true, + 'E' => true, + 'I' => true, + 'O' => true, + 'P' => true, + 'Z' => true, + 'c' => true, + 'r' => true, + 'U' => true, + ]; + } + + return $replacements; + } + + /** + * Format as ->format() do (using date replacements patterns from https://php.net/manual/en/function.date.php) + * but translate words whenever possible (months, day names, etc.) using the current locale. + * + * @param string $format + * + * @return string + */ + public function translatedFormat(string $format): string + { + $replacements = static::getFormatsToIsoReplacements(); + $context = ''; + $isoFormat = ''; + $length = mb_strlen($format); + + for ($i = 0; $i < $length; $i++) { + $char = mb_substr($format, $i, 1); + + if ($char === '\\') { + $replacement = mb_substr($format, $i, 2); + $isoFormat .= $replacement; + $i++; + + continue; + } + + if (!isset($replacements[$char])) { + $replacement = preg_match('/^[A-Za-z]$/', $char) ? "\\$char" : $char; + $isoFormat .= $replacement; + $context .= $replacement; + + continue; + } + + $replacement = $replacements[$char]; + + if ($replacement === true) { + static $contextReplacements = null; + + if ($contextReplacements === null) { + $contextReplacements = [ + 'm' => 'MM', + 'd' => 'DD', + 't' => 'D', + 'j' => 'D', + 'N' => 'e', + 'w' => 'e', + 'n' => 'M', + 'o' => 'YYYY', + 'Y' => 'YYYY', + 'y' => 'YY', + 'g' => 'h', + 'G' => 'H', + 'h' => 'hh', + 'H' => 'HH', + 'i' => 'mm', + 's' => 'ss', + ]; + } + + $isoFormat .= '['.$this->rawFormat($char).']'; + $context .= $contextReplacements[$char] ?? ' '; + + continue; + } + + if ($replacement instanceof Closure) { + $replacement = '['.$replacement($this).']'; + $isoFormat .= $replacement; + $context .= $replacement; + + continue; + } + + $isoFormat .= $replacement; + $context .= $replacement; + } + + return $this->isoFormat($isoFormat, $context); + } + + /** + * Returns the offset hour and minute formatted with +/- and a given separator (":" by default). + * For example, if the time zone is 9 hours 30 minutes, you'll get "+09:30", with "@@" as first + * argument, "+09@@30", with "" as first argument, "+0930". Negative offset will return something + * like "-12:00". + * + * @param string $separator string to place between hours and minutes (":" by default) + * + * @return string + */ + public function getOffsetString($separator = ':') + { + $second = $this->getOffset(); + $symbol = $second < 0 ? '-' : '+'; + $minute = abs($second) / static::SECONDS_PER_MINUTE; + $hour = str_pad((string) floor($minute / static::MINUTES_PER_HOUR), 2, '0', STR_PAD_LEFT); + $minute = str_pad((string) ($minute % static::MINUTES_PER_HOUR), 2, '0', STR_PAD_LEFT); + + return "$symbol$hour$separator$minute"; + } + + protected static function executeStaticCallable($macro, ...$parameters) + { + return static::bindMacroContext(null, function () use (&$macro, &$parameters) { + if ($macro instanceof Closure) { + $boundMacro = @Closure::bind($macro, null, static::class); + + return ($boundMacro ?: $macro)(...$parameters); + } + + return $macro(...$parameters); + }); + } + + /** + * Dynamically handle calls to the class. + * + * @param string $method magic method name called + * @param array $parameters parameters list + * + * @throws BadMethodCallException + * + * @return mixed + */ + public static function __callStatic($method, $parameters) + { + if (!static::hasMacro($method)) { + foreach (static::getGenericMacros() as $callback) { + try { + return static::executeStaticCallable($callback, $method, ...$parameters); + } catch (BadMethodCallException $exception) { + continue; + } + } + if (static::isStrictModeEnabled()) { + throw new UnknownMethodException(sprintf('%s::%s', static::class, $method)); + } + + return null; + } + + return static::executeStaticCallable(static::$globalMacros[$method], ...$parameters); + } + + /** + * Set specified unit to new given value. + * + * @param string $unit year, month, day, hour, minute, second or microsecond + * @param int $value new value for given unit + * + * @return static + */ + public function setUnit($unit, $value = null) + { + $unit = static::singularUnit($unit); + $dateUnits = ['year', 'month', 'day']; + if (\in_array($unit, $dateUnits)) { + return $this->setDate(...array_map(function ($name) use ($unit, $value) { + return (int) ($name === $unit ? $value : $this->$name); + }, $dateUnits)); + } + + $units = ['hour', 'minute', 'second', 'micro']; + if ($unit === 'millisecond' || $unit === 'milli') { + $value *= 1000; + $unit = 'micro'; + } elseif ($unit === 'microsecond') { + $unit = 'micro'; + } + + return $this->setTime(...array_map(function ($name) use ($unit, $value) { + return (int) ($name === $unit ? $value : $this->$name); + }, $units)); + } + + /** + * Returns standardized singular of a given singular/plural unit name (in English). + * + * @param string $unit + * + * @return string + */ + public static function singularUnit(string $unit): string + { + $unit = rtrim(mb_strtolower($unit), 's'); + + if ($unit === 'centurie') { + return 'century'; + } + + if ($unit === 'millennia') { + return 'millennium'; + } + + return $unit; + } + + /** + * Returns standardized plural of a given singular/plural unit name (in English). + * + * @param string $unit + * + * @return string + */ + public static function pluralUnit(string $unit): string + { + $unit = rtrim(strtolower($unit), 's'); + + if ($unit === 'century') { + return 'centuries'; + } + + if ($unit === 'millennium' || $unit === 'millennia') { + return 'millennia'; + } + + return "${unit}s"; + } + + protected function executeCallable($macro, ...$parameters) + { + if ($macro instanceof Closure) { + $boundMacro = @$macro->bindTo($this, static::class) ?: @$macro->bindTo(null, static::class); + + return ($boundMacro ?: $macro)(...$parameters); + } + + return $macro(...$parameters); + } + + protected function executeCallableWithContext($macro, ...$parameters) + { + return static::bindMacroContext($this, function () use (&$macro, &$parameters) { + return $this->executeCallable($macro, ...$parameters); + }); + } + + protected static function getGenericMacros() + { + foreach (static::$globalGenericMacros as $list) { + foreach ($list as $macro) { + yield $macro; + } + } + } + + /** + * Dynamically handle calls to the class. + * + * @param string $method magic method name called + * @param array $parameters parameters list + * + * @throws UnknownMethodException|BadMethodCallException|ReflectionException|Throwable + * + * @return mixed + */ + public function __call($method, $parameters) + { + $diffSizes = [ + // @mode diffForHumans + 'short' => true, + // @mode diffForHumans + 'long' => false, + ]; + $diffSyntaxModes = [ + // @call diffForHumans + 'Absolute' => CarbonInterface::DIFF_ABSOLUTE, + // @call diffForHumans + 'Relative' => CarbonInterface::DIFF_RELATIVE_AUTO, + // @call diffForHumans + 'RelativeToNow' => CarbonInterface::DIFF_RELATIVE_TO_NOW, + // @call diffForHumans + 'RelativeToOther' => CarbonInterface::DIFF_RELATIVE_TO_OTHER, + ]; + $sizePattern = implode('|', array_keys($diffSizes)); + $syntaxPattern = implode('|', array_keys($diffSyntaxModes)); + + if (preg_match("/^(?$sizePattern)(?$syntaxPattern)DiffForHumans$/", $method, $match)) { + $dates = array_filter($parameters, function ($parameter) { + return $parameter instanceof DateTimeInterface; + }); + $other = null; + + if (\count($dates)) { + $key = key($dates); + $other = current($dates); + array_splice($parameters, $key, 1); + } + + return $this->diffForHumans($other, $diffSyntaxModes[$match['syntax']], $diffSizes[$match['size']], ...$parameters); + } + + $roundedValue = $this->callRoundMethod($method, $parameters); + + if ($roundedValue !== null) { + return $roundedValue; + } + + $unit = rtrim($method, 's'); + + if (str_starts_with($unit, 'is')) { + $word = substr($unit, 2); + + if (\in_array($word, static::$days)) { + return $this->isDayOfWeek($word); + } + + switch ($word) { + // @call is Check if the current instance has UTC timezone. (Both isUtc and isUTC cases are valid.) + case 'Utc': + case 'UTC': + return $this->utc; + // @call is Check if the current instance has non-UTC timezone. + case 'Local': + return $this->local; + // @call is Check if the current instance is a valid date. + case 'Valid': + return $this->year !== 0; + // @call is Check if the current instance is in a daylight saving time. + case 'DST': + return $this->dst; + } + } + + $action = substr($unit, 0, 3); + $overflow = null; + + if ($action === 'set') { + $unit = strtolower(substr($unit, 3)); + } + + if (\in_array($unit, static::$units)) { + return $this->setUnit($unit, ...$parameters); + } + + if ($action === 'add' || $action === 'sub') { + $unit = substr($unit, 3); + + if (str_starts_with($unit, 'Real')) { + $unit = static::singularUnit(substr($unit, 4)); + + return $this->{"${action}RealUnit"}($unit, ...$parameters); + } + + if (preg_match('/^(Month|Quarter|Year|Decade|Century|Centurie|Millennium|Millennia)s?(No|With|Without|WithNo)Overflow$/', $unit, $match)) { + $unit = $match[1]; + $overflow = $match[2] === 'With'; + } + + $unit = static::singularUnit($unit); + } + + if (static::isModifiableUnit($unit)) { + return $this->{"${action}Unit"}($unit, $parameters[0] ?? 1, $overflow); + } + + $sixFirstLetters = substr($unit, 0, 6); + $factor = -1; + + if ($sixFirstLetters === 'isLast') { + $sixFirstLetters = 'isNext'; + $factor = 1; + } + + if ($sixFirstLetters === 'isNext') { + $lowerUnit = strtolower(substr($unit, 6)); + + if (static::isModifiableUnit($lowerUnit)) { + return $this->copy()->addUnit($lowerUnit, $factor, false)->isSameUnit($lowerUnit, ...$parameters); + } + } + + if ($sixFirstLetters === 'isSame') { + try { + return $this->isSameUnit(strtolower(substr($unit, 6)), ...$parameters); + } catch (BadComparisonUnitException $exception) { + // Try next + } + } + + if (str_starts_with($unit, 'isCurrent')) { + try { + return $this->isCurrentUnit(strtolower(substr($unit, 9))); + } catch (BadComparisonUnitException | BadMethodCallException $exception) { + // Try next + } + } + + if (str_ends_with($method, 'Until')) { + try { + $unit = static::singularUnit(substr($method, 0, -5)); + + return $this->range($parameters[0] ?? $this, $parameters[1] ?? 1, $unit); + } catch (InvalidArgumentException $exception) { + // Try macros + } + } + + return static::bindMacroContext($this, function () use (&$method, &$parameters) { + $macro = $this->getLocalMacro($method); + + if (!$macro) { + foreach ([$this->localGenericMacros ?: [], static::getGenericMacros()] as $list) { + foreach ($list as $callback) { + try { + return $this->executeCallable($callback, $method, ...$parameters); + } catch (BadMethodCallException $exception) { + continue; + } + } + } + + if ($this->localStrictModeEnabled ?? static::isStrictModeEnabled()) { + throw new UnknownMethodException($method); + } + + return null; + } + + return $this->executeCallable($macro, ...$parameters); + }); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/DeprecatedProperties.php b/vendor/nesbot/carbon/src/Carbon/Traits/DeprecatedProperties.php new file mode 100644 index 0000000..5acc6f5 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/DeprecatedProperties.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +trait DeprecatedProperties +{ + /** + * the day of week in current locale LC_TIME + * + * @var string + * + * @deprecated It uses OS language package and strftime() which is deprecated since PHP 8.1. + * Use ->isoFormat('MMM') instead. + * Deprecated since 2.55.0 + */ + public $localeDayOfWeek; + + /** + * the abbreviated day of week in current locale LC_TIME + * + * @var string + * + * @deprecated It uses OS language package and strftime() which is deprecated since PHP 8.1. + * Use ->isoFormat('dddd') instead. + * Deprecated since 2.55.0 + */ + public $shortLocaleDayOfWeek; + + /** + * the month in current locale LC_TIME + * + * @var string + * + * @deprecated It uses OS language package and strftime() which is deprecated since PHP 8.1. + * Use ->isoFormat('ddd') instead. + * Deprecated since 2.55.0 + */ + public $localeMonth; + + /** + * the abbreviated month in current locale LC_TIME + * + * @var string + * + * @deprecated It uses OS language package and strftime() which is deprecated since PHP 8.1. + * Use ->isoFormat('MMMM') instead. + * Deprecated since 2.55.0 + */ + public $shortLocaleMonth; +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/Difference.php b/vendor/nesbot/carbon/src/Carbon/Traits/Difference.php new file mode 100644 index 0000000..fa7a49f --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/Difference.php @@ -0,0 +1,1152 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\Carbon; +use Carbon\CarbonImmutable; +use Carbon\CarbonInterface; +use Carbon\CarbonInterval; +use Carbon\CarbonPeriod; +use Carbon\Translator; +use Closure; +use DateInterval; +use DateTimeInterface; +use ReturnTypeWillChange; + +/** + * Trait Difference. + * + * Depends on the following methods: + * + * @method bool lessThan($date) + * @method static copy() + * @method static resolveCarbon($date = null) + * @method static Translator translator() + */ +trait Difference +{ + /** + * @codeCoverageIgnore + * + * @param CarbonInterval $diff + */ + protected static function fixNegativeMicroseconds(CarbonInterval $diff) + { + if ($diff->s !== 0 || $diff->i !== 0 || $diff->h !== 0 || $diff->d !== 0 || $diff->m !== 0 || $diff->y !== 0) { + $diff->f = (round($diff->f * 1000000) + 1000000) / 1000000; + $diff->s--; + + if ($diff->s < 0) { + $diff->s += 60; + $diff->i--; + + if ($diff->i < 0) { + $diff->i += 60; + $diff->h--; + + if ($diff->h < 0) { + $diff->h += 24; + $diff->d--; + + if ($diff->d < 0) { + $diff->d += 30; + $diff->m--; + + if ($diff->m < 0) { + $diff->m += 12; + $diff->y--; + } + } + } + } + } + + return; + } + + $diff->f *= -1; + $diff->invert(); + } + + /** + * @param DateInterval $diff + * @param bool $absolute + * + * @return CarbonInterval + */ + protected static function fixDiffInterval(DateInterval $diff, $absolute) + { + $diff = CarbonInterval::instance($diff); + + // Work-around for https://bugs.php.net/bug.php?id=77145 + // @codeCoverageIgnoreStart + if ($diff->f > 0 && $diff->y === -1 && $diff->m === 11 && $diff->d >= 27 && $diff->h === 23 && $diff->i === 59 && $diff->s === 59) { + $diff->y = 0; + $diff->m = 0; + $diff->d = 0; + $diff->h = 0; + $diff->i = 0; + $diff->s = 0; + $diff->f = (1000000 - round($diff->f * 1000000)) / 1000000; + $diff->invert(); + } elseif ($diff->f < 0) { + static::fixNegativeMicroseconds($diff); + } + // @codeCoverageIgnoreEnd + + if ($absolute && $diff->invert) { + $diff->invert(); + } + + return $diff; + } + + /** + * Get the difference as a DateInterval instance. + * Return relative interval (negative if $absolute flag is not set to true and the given date is before + * current one). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return DateInterval + */ + #[ReturnTypeWillChange] + public function diff($date = null, $absolute = false) + { + $other = $this->resolveCarbon($date); + + // Work-around for https://bugs.php.net/bug.php?id=81458 + // It was initially introduced for https://bugs.php.net/bug.php?id=80998 + // The very specific case of 80998 was fixed in PHP 8.1beta3, but it introduced 81458 + // So we still need to keep this for now + // @codeCoverageIgnoreStart + if (version_compare(PHP_VERSION, '8.1.0-dev', '>=') && $other->tz !== $this->tz) { + $other = $other->avoidMutation()->tz($this->tz); + } + // @codeCoverageIgnoreEnd + + return parent::diff($other, (bool) $absolute); + } + + /** + * Get the difference as a CarbonInterval instance. + * Return relative interval (negative if $absolute flag is not set to true and the given date is before + * current one). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return CarbonInterval + */ + public function diffAsCarbonInterval($date = null, $absolute = true) + { + return static::fixDiffInterval($this->diff($this->resolveCarbon($date), $absolute), $absolute); + } + + /** + * Get the difference in years + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInYears($date = null, $absolute = true) + { + return (int) $this->diff($this->resolveCarbon($date), $absolute)->format('%r%y'); + } + + /** + * Get the difference in quarters rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInQuarters($date = null, $absolute = true) + { + return (int) ($this->diffInMonths($date, $absolute) / static::MONTHS_PER_QUARTER); + } + + /** + * Get the difference in months rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInMonths($date = null, $absolute = true) + { + $date = $this->resolveCarbon($date); + + return $this->diffInYears($date, $absolute) * static::MONTHS_PER_YEAR + (int) $this->diff($date, $absolute)->format('%r%m'); + } + + /** + * Get the difference in weeks rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInWeeks($date = null, $absolute = true) + { + return (int) ($this->diffInDays($date, $absolute) / static::DAYS_PER_WEEK); + } + + /** + * Get the difference in days rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInDays($date = null, $absolute = true) + { + return (int) $this->diff($this->resolveCarbon($date), $absolute)->format('%r%a'); + } + + /** + * Get the difference in days using a filter closure rounded down. + * + * @param Closure $callback + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInDaysFiltered(Closure $callback, $date = null, $absolute = true) + { + return $this->diffFiltered(CarbonInterval::day(), $callback, $date, $absolute); + } + + /** + * Get the difference in hours using a filter closure rounded down. + * + * @param Closure $callback + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInHoursFiltered(Closure $callback, $date = null, $absolute = true) + { + return $this->diffFiltered(CarbonInterval::hour(), $callback, $date, $absolute); + } + + /** + * Get the difference by the given interval using a filter closure. + * + * @param CarbonInterval $ci An interval to traverse by + * @param Closure $callback + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffFiltered(CarbonInterval $ci, Closure $callback, $date = null, $absolute = true) + { + $start = $this; + $end = $this->resolveCarbon($date); + $inverse = false; + + if ($end < $start) { + $start = $end; + $end = $this; + $inverse = true; + } + + $options = CarbonPeriod::EXCLUDE_END_DATE | ($this->isMutable() ? 0 : CarbonPeriod::IMMUTABLE); + $diff = $ci->toPeriod($start, $end, $options)->filter($callback)->count(); + + return $inverse && !$absolute ? -$diff : $diff; + } + + /** + * Get the difference in weekdays rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInWeekdays($date = null, $absolute = true) + { + return $this->diffInDaysFiltered(function (CarbonInterface $date) { + return $date->isWeekday(); + }, $date, $absolute); + } + + /** + * Get the difference in weekend days using a filter rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInWeekendDays($date = null, $absolute = true) + { + return $this->diffInDaysFiltered(function (CarbonInterface $date) { + return $date->isWeekend(); + }, $date, $absolute); + } + + /** + * Get the difference in hours rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInHours($date = null, $absolute = true) + { + return (int) ($this->diffInSeconds($date, $absolute) / static::SECONDS_PER_MINUTE / static::MINUTES_PER_HOUR); + } + + /** + * Get the difference in hours rounded down using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealHours($date = null, $absolute = true) + { + return (int) ($this->diffInRealSeconds($date, $absolute) / static::SECONDS_PER_MINUTE / static::MINUTES_PER_HOUR); + } + + /** + * Get the difference in minutes rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInMinutes($date = null, $absolute = true) + { + return (int) ($this->diffInSeconds($date, $absolute) / static::SECONDS_PER_MINUTE); + } + + /** + * Get the difference in minutes rounded down using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealMinutes($date = null, $absolute = true) + { + return (int) ($this->diffInRealSeconds($date, $absolute) / static::SECONDS_PER_MINUTE); + } + + /** + * Get the difference in seconds rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInSeconds($date = null, $absolute = true) + { + $diff = $this->diff($date); + + if ($diff->days === 0) { + $diff = static::fixDiffInterval($diff, $absolute); + } + + $value = (((($diff->m || $diff->y ? $diff->days : $diff->d) * static::HOURS_PER_DAY) + + $diff->h) * static::MINUTES_PER_HOUR + + $diff->i) * static::SECONDS_PER_MINUTE + + $diff->s; + + return $absolute || !$diff->invert ? $value : -$value; + } + + /** + * Get the difference in microseconds. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInMicroseconds($date = null, $absolute = true) + { + $diff = $this->diff($date); + $value = (int) round(((((($diff->m || $diff->y ? $diff->days : $diff->d) * static::HOURS_PER_DAY) + + $diff->h) * static::MINUTES_PER_HOUR + + $diff->i) * static::SECONDS_PER_MINUTE + + ($diff->f + $diff->s)) * static::MICROSECONDS_PER_SECOND); + + return $absolute || !$diff->invert ? $value : -$value; + } + + /** + * Get the difference in milliseconds rounded down. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInMilliseconds($date = null, $absolute = true) + { + return (int) ($this->diffInMicroseconds($date, $absolute) / static::MICROSECONDS_PER_MILLISECOND); + } + + /** + * Get the difference in seconds using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealSeconds($date = null, $absolute = true) + { + /** @var CarbonInterface $date */ + $date = $this->resolveCarbon($date); + $value = $date->getTimestamp() - $this->getTimestamp(); + + return $absolute ? abs($value) : $value; + } + + /** + * Get the difference in microseconds using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealMicroseconds($date = null, $absolute = true) + { + /** @var CarbonInterface $date */ + $date = $this->resolveCarbon($date); + $value = ($date->timestamp - $this->timestamp) * static::MICROSECONDS_PER_SECOND + + $date->micro - $this->micro; + + return $absolute ? abs($value) : $value; + } + + /** + * Get the difference in milliseconds rounded down using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealMilliseconds($date = null, $absolute = true) + { + return (int) ($this->diffInRealMicroseconds($date, $absolute) / static::MICROSECONDS_PER_MILLISECOND); + } + + /** + * Get the difference in seconds as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInSeconds($date = null, $absolute = true) + { + return $this->diffInMicroseconds($date, $absolute) / static::MICROSECONDS_PER_SECOND; + } + + /** + * Get the difference in minutes as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInMinutes($date = null, $absolute = true) + { + return $this->floatDiffInSeconds($date, $absolute) / static::SECONDS_PER_MINUTE; + } + + /** + * Get the difference in hours as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInHours($date = null, $absolute = true) + { + return $this->floatDiffInMinutes($date, $absolute) / static::MINUTES_PER_HOUR; + } + + /** + * Get the difference in days as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInDays($date = null, $absolute = true) + { + $hoursDiff = $this->floatDiffInHours($date, $absolute); + $interval = $this->diff($date, $absolute); + + if ($interval->y === 0 && $interval->m === 0 && $interval->d === 0) { + return $hoursDiff / static::HOURS_PER_DAY; + } + + $daysDiff = (int) $interval->format('%r%a'); + + return $daysDiff + fmod($hoursDiff, static::HOURS_PER_DAY) / static::HOURS_PER_DAY; + } + + /** + * Get the difference in weeks as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInWeeks($date = null, $absolute = true) + { + return $this->floatDiffInDays($date, $absolute) / static::DAYS_PER_WEEK; + } + + /** + * Get the difference in months as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInMonths($date = null, $absolute = true) + { + $start = $this; + $end = $this->resolveCarbon($date); + $ascending = ($start <= $end); + $sign = $absolute || $ascending ? 1 : -1; + if (!$ascending) { + [$start, $end] = [$end, $start]; + } + $monthsDiff = $start->diffInMonths($end); + /** @var Carbon|CarbonImmutable $floorEnd */ + $floorEnd = $start->avoidMutation()->addMonths($monthsDiff); + + if ($floorEnd >= $end) { + return $sign * $monthsDiff; + } + + /** @var Carbon|CarbonImmutable $startOfMonthAfterFloorEnd */ + $startOfMonthAfterFloorEnd = $floorEnd->avoidMutation()->addMonth()->startOfMonth(); + + if ($startOfMonthAfterFloorEnd > $end) { + return $sign * ($monthsDiff + $floorEnd->floatDiffInDays($end) / $floorEnd->daysInMonth); + } + + return $sign * ($monthsDiff + $floorEnd->floatDiffInDays($startOfMonthAfterFloorEnd) / $floorEnd->daysInMonth + $startOfMonthAfterFloorEnd->floatDiffInDays($end) / $end->daysInMonth); + } + + /** + * Get the difference in year as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInYears($date = null, $absolute = true) + { + $start = $this; + $end = $this->resolveCarbon($date); + $ascending = ($start <= $end); + $sign = $absolute || $ascending ? 1 : -1; + if (!$ascending) { + [$start, $end] = [$end, $start]; + } + $yearsDiff = $start->diffInYears($end); + /** @var Carbon|CarbonImmutable $floorEnd */ + $floorEnd = $start->avoidMutation()->addYears($yearsDiff); + + if ($floorEnd >= $end) { + return $sign * $yearsDiff; + } + + /** @var Carbon|CarbonImmutable $startOfYearAfterFloorEnd */ + $startOfYearAfterFloorEnd = $floorEnd->avoidMutation()->addYear()->startOfYear(); + + if ($startOfYearAfterFloorEnd > $end) { + return $sign * ($yearsDiff + $floorEnd->floatDiffInDays($end) / $floorEnd->daysInYear); + } + + return $sign * ($yearsDiff + $floorEnd->floatDiffInDays($startOfYearAfterFloorEnd) / $floorEnd->daysInYear + $startOfYearAfterFloorEnd->floatDiffInDays($end) / $end->daysInYear); + } + + /** + * Get the difference in seconds as float (microsecond-precision) using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealSeconds($date = null, $absolute = true) + { + return $this->diffInRealMicroseconds($date, $absolute) / static::MICROSECONDS_PER_SECOND; + } + + /** + * Get the difference in minutes as float (microsecond-precision) using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealMinutes($date = null, $absolute = true) + { + return $this->floatDiffInRealSeconds($date, $absolute) / static::SECONDS_PER_MINUTE; + } + + /** + * Get the difference in hours as float (microsecond-precision) using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealHours($date = null, $absolute = true) + { + return $this->floatDiffInRealMinutes($date, $absolute) / static::MINUTES_PER_HOUR; + } + + /** + * Get the difference in days as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealDays($date = null, $absolute = true) + { + $date = $this->resolveUTC($date); + $utc = $this->avoidMutation()->utc(); + $hoursDiff = $utc->floatDiffInRealHours($date, $absolute); + + return ($hoursDiff < 0 ? -1 : 1) * $utc->diffInDays($date) + fmod($hoursDiff, static::HOURS_PER_DAY) / static::HOURS_PER_DAY; + } + + /** + * Get the difference in weeks as float (microsecond-precision). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealWeeks($date = null, $absolute = true) + { + return $this->floatDiffInRealDays($date, $absolute) / static::DAYS_PER_WEEK; + } + + /** + * Get the difference in months as float (microsecond-precision) using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealMonths($date = null, $absolute = true) + { + $start = $this; + $end = $this->resolveCarbon($date); + $ascending = ($start <= $end); + $sign = $absolute || $ascending ? 1 : -1; + if (!$ascending) { + [$start, $end] = [$end, $start]; + } + $monthsDiff = $start->diffInMonths($end); + /** @var Carbon|CarbonImmutable $floorEnd */ + $floorEnd = $start->avoidMutation()->addMonths($monthsDiff); + + if ($floorEnd >= $end) { + return $sign * $monthsDiff; + } + + /** @var Carbon|CarbonImmutable $startOfMonthAfterFloorEnd */ + $startOfMonthAfterFloorEnd = $floorEnd->avoidMutation()->addMonth()->startOfMonth(); + + if ($startOfMonthAfterFloorEnd > $end) { + return $sign * ($monthsDiff + $floorEnd->floatDiffInRealDays($end) / $floorEnd->daysInMonth); + } + + return $sign * ($monthsDiff + $floorEnd->floatDiffInRealDays($startOfMonthAfterFloorEnd) / $floorEnd->daysInMonth + $startOfMonthAfterFloorEnd->floatDiffInRealDays($end) / $end->daysInMonth); + } + + /** + * Get the difference in year as float (microsecond-precision) using timestamps. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function floatDiffInRealYears($date = null, $absolute = true) + { + $start = $this; + $end = $this->resolveCarbon($date); + $ascending = ($start <= $end); + $sign = $absolute || $ascending ? 1 : -1; + if (!$ascending) { + [$start, $end] = [$end, $start]; + } + $yearsDiff = $start->diffInYears($end); + /** @var Carbon|CarbonImmutable $floorEnd */ + $floorEnd = $start->avoidMutation()->addYears($yearsDiff); + + if ($floorEnd >= $end) { + return $sign * $yearsDiff; + } + + /** @var Carbon|CarbonImmutable $startOfYearAfterFloorEnd */ + $startOfYearAfterFloorEnd = $floorEnd->avoidMutation()->addYear()->startOfYear(); + + if ($startOfYearAfterFloorEnd > $end) { + return $sign * ($yearsDiff + $floorEnd->floatDiffInRealDays($end) / $floorEnd->daysInYear); + } + + return $sign * ($yearsDiff + $floorEnd->floatDiffInRealDays($startOfYearAfterFloorEnd) / $floorEnd->daysInYear + $startOfYearAfterFloorEnd->floatDiffInRealDays($end) / $end->daysInYear); + } + + /** + * The number of seconds since midnight. + * + * @return int + */ + public function secondsSinceMidnight() + { + return $this->diffInSeconds($this->avoidMutation()->startOfDay()); + } + + /** + * The number of seconds until 23:59:59. + * + * @return int + */ + public function secondsUntilEndOfDay() + { + return $this->diffInSeconds($this->avoidMutation()->endOfDay()); + } + + /** + * Get the difference in a human readable format in the current locale from current instance to an other + * instance given (or now if null given). + * + * @example + * ``` + * echo Carbon::tomorrow()->diffForHumans() . "\n"; + * echo Carbon::tomorrow()->diffForHumans(['parts' => 2]) . "\n"; + * echo Carbon::tomorrow()->diffForHumans(['parts' => 3, 'join' => true]) . "\n"; + * echo Carbon::tomorrow()->diffForHumans(Carbon::yesterday()) . "\n"; + * echo Carbon::tomorrow()->diffForHumans(Carbon::yesterday(), ['short' => true]) . "\n"; + * ``` + * + * @param Carbon|\DateTimeInterface|string|array|null $other if array passed, will be used as parameters array, see $syntax below; + * if null passed, now will be used as comparison reference; + * if any other type, it will be converted to date and used as reference. + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'skip' entry, list of units to skip (array of strings or a single string, + * ` it can be the unit name (singular or plural) or its shortcut + * ` (y, m, w, d, h, min, s, ms, µs). + * - 'aUnit' entry, prefer "an hour" over "1 hour" if true + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * - 'other' entry (see above) + * - 'minimumUnit' entry determines the smallest unit of time to display can be long or + * ` short form of the units, e.g. 'hour' or 'h' (default value: s) + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single unit) + * @param int $options human diff options + * + * @return string + */ + public function diffForHumans($other = null, $syntax = null, $short = false, $parts = 1, $options = null) + { + /* @var CarbonInterface $this */ + if (\is_array($other)) { + $other['syntax'] = \array_key_exists('syntax', $other) ? $other['syntax'] : $syntax; + $syntax = $other; + $other = $syntax['other'] ?? null; + } + + $intSyntax = &$syntax; + if (\is_array($syntax)) { + $syntax['syntax'] = $syntax['syntax'] ?? null; + $intSyntax = &$syntax['syntax']; + } + $intSyntax = (int) ($intSyntax ?? static::DIFF_RELATIVE_AUTO); + $intSyntax = $intSyntax === static::DIFF_RELATIVE_AUTO && $other === null ? static::DIFF_RELATIVE_TO_NOW : $intSyntax; + + $parts = min(7, max(1, (int) $parts)); + + return $this->diffAsCarbonInterval($other, false) + ->setLocalTranslator($this->getLocalTranslator()) + ->forHumans($syntax, (bool) $short, $parts, $options ?? $this->localHumanDiffOptions ?? static::getHumanDiffOptions()); + } + + /** + * @alias diffForHumans + * + * Get the difference in a human readable format in the current locale from current instance to an other + * instance given (or now if null given). + * + * @param Carbon|\DateTimeInterface|string|array|null $other if array passed, will be used as parameters array, see $syntax below; + * if null passed, now will be used as comparison reference; + * if any other type, it will be converted to date and used as reference. + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * - 'other' entry (see above) + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single unit) + * @param int $options human diff options + * + * @return string + */ + public function from($other = null, $syntax = null, $short = false, $parts = 1, $options = null) + { + return $this->diffForHumans($other, $syntax, $short, $parts, $options); + } + + /** + * @alias diffForHumans + * + * Get the difference in a human readable format in the current locale from current instance to an other + * instance given (or now if null given). + */ + public function since($other = null, $syntax = null, $short = false, $parts = 1, $options = null) + { + return $this->diffForHumans($other, $syntax, $short, $parts, $options); + } + + /** + * Get the difference in a human readable format in the current locale from an other + * instance given (or now if null given) to current instance. + * + * When comparing a value in the past to default now: + * 1 hour from now + * 5 months from now + * + * When comparing a value in the future to default now: + * 1 hour ago + * 5 months ago + * + * When comparing a value in the past to another value: + * 1 hour after + * 5 months after + * + * When comparing a value in the future to another value: + * 1 hour before + * 5 months before + * + * @param Carbon|\DateTimeInterface|string|array|null $other if array passed, will be used as parameters array, see $syntax below; + * if null passed, now will be used as comparison reference; + * if any other type, it will be converted to date and used as reference. + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * - 'other' entry (see above) + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single unit) + * @param int $options human diff options + * + * @return string + */ + public function to($other = null, $syntax = null, $short = false, $parts = 1, $options = null) + { + if (!$syntax && !$other) { + $syntax = CarbonInterface::DIFF_RELATIVE_TO_NOW; + } + + return $this->resolveCarbon($other)->diffForHumans($this, $syntax, $short, $parts, $options); + } + + /** + * @alias to + * + * Get the difference in a human readable format in the current locale from an other + * instance given (or now if null given) to current instance. + * + * @param Carbon|\DateTimeInterface|string|array|null $other if array passed, will be used as parameters array, see $syntax below; + * if null passed, now will be used as comparison reference; + * if any other type, it will be converted to date and used as reference. + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * - 'other' entry (see above) + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single unit) + * @param int $options human diff options + * + * @return string + */ + public function until($other = null, $syntax = null, $short = false, $parts = 1, $options = null) + { + return $this->to($other, $syntax, $short, $parts, $options); + } + + /** + * Get the difference in a human readable format in the current locale from current + * instance to now. + * + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single unit) + * @param int $options human diff options + * + * @return string + */ + public function fromNow($syntax = null, $short = false, $parts = 1, $options = null) + { + $other = null; + + if ($syntax instanceof DateTimeInterface) { + [$other, $syntax, $short, $parts, $options] = array_pad(\func_get_args(), 5, null); + } + + return $this->from($other, $syntax, $short, $parts, $options); + } + + /** + * Get the difference in a human readable format in the current locale from an other + * instance given to now + * + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single part) + * @param int $options human diff options + * + * @return string + */ + public function toNow($syntax = null, $short = false, $parts = 1, $options = null) + { + return $this->to(null, $syntax, $short, $parts, $options); + } + + /** + * Get the difference in a human readable format in the current locale from an other + * instance given to now + * + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * - 'syntax' entry (see below) + * - 'short' entry (see below) + * - 'parts' entry (see below) + * - 'options' entry (see below) + * - 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * if int passed, it add modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single part) + * @param int $options human diff options + * + * @return string + */ + public function ago($syntax = null, $short = false, $parts = 1, $options = null) + { + $other = null; + + if ($syntax instanceof DateTimeInterface) { + [$other, $syntax, $short, $parts, $options] = array_pad(\func_get_args(), 5, null); + } + + return $this->from($other, $syntax, $short, $parts, $options); + } + + /** + * Get the difference in a human readable format in the current locale from current instance to an other + * instance given (or now if null given). + * + * @return string + */ + public function timespan($other = null, $timezone = null) + { + if (!$other instanceof DateTimeInterface) { + $other = static::parse($other, $timezone); + } + + return $this->diffForHumans($other, [ + 'join' => ', ', + 'syntax' => CarbonInterface::DIFF_ABSOLUTE, + 'options' => CarbonInterface::NO_ZERO_DIFF, + 'parts' => -1, + ]); + } + + /** + * Returns either day of week + time (e.g. "Last Friday at 3:30 PM") if reference time is within 7 days, + * or a calendar date (e.g. "10/29/2017") otherwise. + * + * Language, date and time formats will change according to the current locale. + * + * @param Carbon|\DateTimeInterface|string|null $referenceTime + * @param array $formats + * + * @return string + */ + public function calendar($referenceTime = null, array $formats = []) + { + /** @var CarbonInterface $current */ + $current = $this->avoidMutation()->startOfDay(); + /** @var CarbonInterface $other */ + $other = $this->resolveCarbon($referenceTime)->avoidMutation()->setTimezone($this->getTimezone())->startOfDay(); + $diff = $other->diffInDays($current, false); + $format = $diff < -6 ? 'sameElse' : ( + $diff < -1 ? 'lastWeek' : ( + $diff < 0 ? 'lastDay' : ( + $diff < 1 ? 'sameDay' : ( + $diff < 2 ? 'nextDay' : ( + $diff < 7 ? 'nextWeek' : 'sameElse' + ) + ) + ) + ) + ); + $format = array_merge($this->getCalendarFormats(), $formats)[$format]; + if ($format instanceof Closure) { + $format = $format($current, $other) ?? ''; + } + + return $this->isoFormat((string) $format); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/IntervalRounding.php b/vendor/nesbot/carbon/src/Carbon/Traits/IntervalRounding.php new file mode 100644 index 0000000..4cd66b6 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/IntervalRounding.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\CarbonInterval; +use Carbon\Exceptions\InvalidIntervalException; +use DateInterval; + +/** + * Trait to call rounding methods to interval or the interval of a period. + */ +trait IntervalRounding +{ + protected function callRoundMethod(string $method, array $parameters) + { + $action = substr($method, 0, 4); + + if ($action !== 'ceil') { + $action = substr($method, 0, 5); + } + + if (\in_array($action, ['round', 'floor', 'ceil'])) { + return $this->{$action.'Unit'}(substr($method, \strlen($action)), ...$parameters); + } + + return null; + } + + protected function roundWith($precision, $function) + { + $unit = 'second'; + + if ($precision instanceof DateInterval) { + $precision = (string) CarbonInterval::instance($precision); + } + + if (\is_string($precision) && preg_match('/^\s*(?\d+)?\s*(?\w+)(?\W.*)?$/', $precision, $match)) { + if (trim($match['other'] ?? '') !== '') { + throw new InvalidIntervalException('Rounding is only possible with single unit intervals.'); + } + + $precision = (int) ($match['precision'] ?: 1); + $unit = $match['unit']; + } + + return $this->roundUnit($unit, $precision, $function); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/IntervalStep.php b/vendor/nesbot/carbon/src/Carbon/Traits/IntervalStep.php new file mode 100644 index 0000000..82d7c32 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/IntervalStep.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\Carbon; +use Carbon\CarbonImmutable; +use Carbon\CarbonInterface; +use Closure; +use DateTimeImmutable; +use DateTimeInterface; + +trait IntervalStep +{ + /** + * Step to apply instead of a fixed interval to get the new date. + * + * @var Closure|null + */ + protected $step; + + /** + * Get the dynamic step in use. + * + * @return Closure + */ + public function getStep(): ?Closure + { + return $this->step; + } + + /** + * Set a step to apply instead of a fixed interval to get the new date. + * + * Or pass null to switch to fixed interval. + * + * @param Closure|null $step + */ + public function setStep(?Closure $step): void + { + $this->step = $step; + } + + /** + * Take a date and apply either the step if set, or the current interval else. + * + * The interval/step is applied negatively (typically subtraction instead of addition) if $negated is true. + * + * @param DateTimeInterface $dateTime + * @param bool $negated + * + * @return CarbonInterface + */ + public function convertDate(DateTimeInterface $dateTime, bool $negated = false): CarbonInterface + { + /** @var CarbonInterface $carbonDate */ + $carbonDate = $dateTime instanceof CarbonInterface ? $dateTime : $this->resolveCarbon($dateTime); + + if ($this->step) { + return $carbonDate->setDateTimeFrom(($this->step)($carbonDate->avoidMutation(), $negated)); + } + + if ($negated) { + return $carbonDate->rawSub($this); + } + + return $carbonDate->rawAdd($this); + } + + /** + * Convert DateTimeImmutable instance to CarbonImmutable instance and DateTime instance to Carbon instance. + * + * @param DateTimeInterface $dateTime + * + * @return Carbon|CarbonImmutable + */ + private function resolveCarbon(DateTimeInterface $dateTime) + { + if ($dateTime instanceof DateTimeImmutable) { + return CarbonImmutable::instance($dateTime); + } + + return Carbon::instance($dateTime); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/Localization.php b/vendor/nesbot/carbon/src/Carbon/Traits/Localization.php new file mode 100644 index 0000000..b3d01c7 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/Localization.php @@ -0,0 +1,826 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\CarbonInterface; +use Carbon\Exceptions\InvalidTypeException; +use Carbon\Exceptions\NotLocaleAwareException; +use Carbon\Language; +use Carbon\Translator; +use Carbon\TranslatorStrongTypeInterface; +use Closure; +use Symfony\Component\Translation\TranslatorBagInterface; +use Symfony\Component\Translation\TranslatorInterface; +use Symfony\Contracts\Translation\LocaleAwareInterface; +use Symfony\Contracts\Translation\TranslatorInterface as ContractsTranslatorInterface; + +if (!interface_exists('Symfony\\Component\\Translation\\TranslatorInterface')) { + class_alias( + 'Symfony\\Contracts\\Translation\\TranslatorInterface', + 'Symfony\\Component\\Translation\\TranslatorInterface' + ); +} + +/** + * Trait Localization. + * + * Embed default and locale translators and translation base methods. + */ +trait Localization +{ + /** + * Default translator. + * + * @var \Symfony\Component\Translation\TranslatorInterface + */ + protected static $translator; + + /** + * Specific translator of the current instance. + * + * @var \Symfony\Component\Translation\TranslatorInterface + */ + protected $localTranslator; + + /** + * Options for diffForHumans(). + * + * @var int + */ + protected static $humanDiffOptions = CarbonInterface::NO_ZERO_DIFF; + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @see settings + * + * @param int $humanDiffOptions + */ + public static function setHumanDiffOptions($humanDiffOptions) + { + static::$humanDiffOptions = $humanDiffOptions; + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @see settings + * + * @param int $humanDiffOption + */ + public static function enableHumanDiffOption($humanDiffOption) + { + static::$humanDiffOptions = static::getHumanDiffOptions() | $humanDiffOption; + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @see settings + * + * @param int $humanDiffOption + */ + public static function disableHumanDiffOption($humanDiffOption) + { + static::$humanDiffOptions = static::getHumanDiffOptions() & ~$humanDiffOption; + } + + /** + * Return default humanDiff() options (merged flags as integer). + * + * @return int + */ + public static function getHumanDiffOptions() + { + return static::$humanDiffOptions; + } + + /** + * Get the default translator instance in use. + * + * @return \Symfony\Component\Translation\TranslatorInterface + */ + public static function getTranslator() + { + return static::translator(); + } + + /** + * Set the default translator instance to use. + * + * @param \Symfony\Component\Translation\TranslatorInterface $translator + * + * @return void + */ + public static function setTranslator(TranslatorInterface $translator) + { + static::$translator = $translator; + } + + /** + * Return true if the current instance has its own translator. + * + * @return bool + */ + public function hasLocalTranslator() + { + return isset($this->localTranslator); + } + + /** + * Get the translator of the current instance or the default if none set. + * + * @return \Symfony\Component\Translation\TranslatorInterface + */ + public function getLocalTranslator() + { + return $this->localTranslator ?: static::translator(); + } + + /** + * Set the translator for the current instance. + * + * @param \Symfony\Component\Translation\TranslatorInterface $translator + * + * @return $this + */ + public function setLocalTranslator(TranslatorInterface $translator) + { + $this->localTranslator = $translator; + + return $this; + } + + /** + * Returns raw translation message for a given key. + * + * @param \Symfony\Component\Translation\TranslatorInterface $translator the translator to use + * @param string $key key to find + * @param string|null $locale current locale used if null + * @param string|null $default default value if translation returns the key + * + * @return string + */ + public static function getTranslationMessageWith($translator, string $key, ?string $locale = null, ?string $default = null) + { + if (!($translator instanceof TranslatorBagInterface && $translator instanceof TranslatorInterface)) { + throw new InvalidTypeException( + 'Translator does not implement '.TranslatorInterface::class.' and '.TranslatorBagInterface::class.'. '. + (\is_object($translator) ? \get_class($translator) : \gettype($translator)).' has been given.' + ); + } + + if (!$locale && $translator instanceof LocaleAwareInterface) { + $locale = $translator->getLocale(); + } + + $result = self::getFromCatalogue($translator, $translator->getCatalogue($locale), $key); + + return $result === $key ? $default : $result; + } + + /** + * Returns raw translation message for a given key. + * + * @param string $key key to find + * @param string|null $locale current locale used if null + * @param string|null $default default value if translation returns the key + * @param \Symfony\Component\Translation\TranslatorInterface $translator an optional translator to use + * + * @return string + */ + public function getTranslationMessage(string $key, ?string $locale = null, ?string $default = null, $translator = null) + { + return static::getTranslationMessageWith($translator ?: $this->getLocalTranslator(), $key, $locale, $default); + } + + /** + * Translate using translation string or callback available. + * + * @param \Symfony\Component\Translation\TranslatorInterface $translator + * @param string $key + * @param array $parameters + * @param null $number + * + * @return string + */ + public static function translateWith(TranslatorInterface $translator, string $key, array $parameters = [], $number = null): string + { + $message = static::getTranslationMessageWith($translator, $key, null, $key); + if ($message instanceof Closure) { + return (string) $message(...array_values($parameters)); + } + + if ($number !== null) { + $parameters['%count%'] = $number; + } + if (isset($parameters['%count%'])) { + $parameters[':count'] = $parameters['%count%']; + } + + // @codeCoverageIgnoreStart + $choice = $translator instanceof ContractsTranslatorInterface + ? $translator->trans($key, $parameters) + : $translator->transChoice($key, $number, $parameters); + // @codeCoverageIgnoreEnd + + return (string) $choice; + } + + /** + * Translate using translation string or callback available. + * + * @param string $key + * @param array $parameters + * @param string|int|float|null $number + * @param \Symfony\Component\Translation\TranslatorInterface|null $translator + * @param bool $altNumbers + * + * @return string + */ + public function translate(string $key, array $parameters = [], $number = null, ?TranslatorInterface $translator = null, bool $altNumbers = false): string + { + $translation = static::translateWith($translator ?: $this->getLocalTranslator(), $key, $parameters, $number); + + if ($number !== null && $altNumbers) { + return str_replace($number, $this->translateNumber($number), $translation); + } + + return $translation; + } + + /** + * Returns the alternative number for a given integer if available in the current locale. + * + * @param int $number + * + * @return string + */ + public function translateNumber(int $number): string + { + $translateKey = "alt_numbers.$number"; + $symbol = $this->translate($translateKey); + + if ($symbol !== $translateKey) { + return $symbol; + } + + if ($number > 99 && $this->translate('alt_numbers.99') !== 'alt_numbers.99') { + $start = ''; + foreach ([10000, 1000, 100] as $exp) { + $key = "alt_numbers_pow.$exp"; + if ($number >= $exp && $number < $exp * 10 && ($pow = $this->translate($key)) !== $key) { + $unit = floor($number / $exp); + $number -= $unit * $exp; + $start .= ($unit > 1 ? $this->translate("alt_numbers.$unit") : '').$pow; + } + } + $result = ''; + while ($number) { + $chunk = $number % 100; + $result = $this->translate("alt_numbers.$chunk").$result; + $number = floor($number / 100); + } + + return "$start$result"; + } + + if ($number > 9 && $this->translate('alt_numbers.9') !== 'alt_numbers.9') { + $result = ''; + while ($number) { + $chunk = $number % 10; + $result = $this->translate("alt_numbers.$chunk").$result; + $number = floor($number / 10); + } + + return $result; + } + + return (string) $number; + } + + /** + * Translate a time string from a locale to an other. + * + * @param string $timeString date/time/duration string to translate (may also contain English) + * @param string|null $from input locale of the $timeString parameter (`Carbon::getLocale()` by default) + * @param string|null $to output locale of the result returned (`"en"` by default) + * @param int $mode specify what to translate with options: + * - CarbonInterface::TRANSLATE_ALL (default) + * - CarbonInterface::TRANSLATE_MONTHS + * - CarbonInterface::TRANSLATE_DAYS + * - CarbonInterface::TRANSLATE_UNITS + * - CarbonInterface::TRANSLATE_MERIDIEM + * You can use pipe to group: CarbonInterface::TRANSLATE_MONTHS | CarbonInterface::TRANSLATE_DAYS + * + * @return string + */ + public static function translateTimeString($timeString, $from = null, $to = null, $mode = CarbonInterface::TRANSLATE_ALL) + { + // Fallback source and destination locales + $from = $from ?: static::getLocale(); + $to = $to ?: 'en'; + + if ($from === $to) { + return $timeString; + } + + // Standardize apostrophe + $timeString = strtr($timeString, ['’' => "'"]); + + $fromTranslations = []; + $toTranslations = []; + + foreach (['from', 'to'] as $key) { + $language = $$key; + $translator = Translator::get($language); + $translations = $translator->getMessages(); + + if (!isset($translations[$language])) { + return $timeString; + } + + $translationKey = $key.'Translations'; + $messages = $translations[$language]; + $months = $messages['months'] ?? []; + $weekdays = $messages['weekdays'] ?? []; + $meridiem = $messages['meridiem'] ?? ['AM', 'PM']; + + if ($key === 'from') { + foreach (['months', 'weekdays'] as $variable) { + $list = $messages[$variable.'_standalone'] ?? null; + + if ($list) { + foreach ($$variable as $index => &$name) { + $name .= '|'.$messages[$variable.'_standalone'][$index]; + } + } + } + } + + $$translationKey = array_merge( + $mode & CarbonInterface::TRANSLATE_MONTHS ? static::getTranslationArray($months, 12, $timeString) : [], + $mode & CarbonInterface::TRANSLATE_MONTHS ? static::getTranslationArray($messages['months_short'] ?? [], 12, $timeString) : [], + $mode & CarbonInterface::TRANSLATE_DAYS ? static::getTranslationArray($weekdays, 7, $timeString) : [], + $mode & CarbonInterface::TRANSLATE_DAYS ? static::getTranslationArray($messages['weekdays_short'] ?? [], 7, $timeString) : [], + $mode & CarbonInterface::TRANSLATE_DIFF ? static::translateWordsByKeys([ + 'diff_now', + 'diff_today', + 'diff_yesterday', + 'diff_tomorrow', + 'diff_before_yesterday', + 'diff_after_tomorrow', + ], $messages, $key) : [], + $mode & CarbonInterface::TRANSLATE_UNITS ? static::translateWordsByKeys([ + 'year', + 'month', + 'week', + 'day', + 'hour', + 'minute', + 'second', + ], $messages, $key) : [], + $mode & CarbonInterface::TRANSLATE_MERIDIEM ? array_map(function ($hour) use ($meridiem) { + if (\is_array($meridiem)) { + return $meridiem[$hour < 12 ? 0 : 1]; + } + + return $meridiem($hour, 0, false); + }, range(0, 23)) : [] + ); + } + + return substr(preg_replace_callback('/(?<=[\d\s+.\/,_-])('.implode('|', $fromTranslations).')(?=[\d\s+.\/,_-])/iu', function ($match) use ($fromTranslations, $toTranslations) { + [$chunk] = $match; + + foreach ($fromTranslations as $index => $word) { + if (preg_match("/^$word\$/iu", $chunk)) { + return $toTranslations[$index] ?? ''; + } + } + + return $chunk; // @codeCoverageIgnore + }, " $timeString "), 1, -1); + } + + /** + * Translate a time string from the current locale (`$date->locale()`) to an other. + * + * @param string $timeString time string to translate + * @param string|null $to output locale of the result returned ("en" by default) + * + * @return string + */ + public function translateTimeStringTo($timeString, $to = null) + { + return static::translateTimeString($timeString, $this->getTranslatorLocale(), $to); + } + + /** + * Get/set the locale for the current instance. + * + * @param string|null $locale + * @param string ...$fallbackLocales + * + * @return $this|string + */ + public function locale(string $locale = null, ...$fallbackLocales) + { + if ($locale === null) { + return $this->getTranslatorLocale(); + } + + if (!$this->localTranslator || $this->getTranslatorLocale($this->localTranslator) !== $locale) { + $translator = Translator::get($locale); + + if (!empty($fallbackLocales)) { + $translator->setFallbackLocales($fallbackLocales); + + foreach ($fallbackLocales as $fallbackLocale) { + $messages = Translator::get($fallbackLocale)->getMessages(); + + if (isset($messages[$fallbackLocale])) { + $translator->setMessages($fallbackLocale, $messages[$fallbackLocale]); + } + } + } + + $this->setLocalTranslator($translator); + } + + return $this; + } + + /** + * Get the current translator locale. + * + * @return string + */ + public static function getLocale() + { + return static::getLocaleAwareTranslator()->getLocale(); + } + + /** + * Set the current translator locale and indicate if the source locale file exists. + * Pass 'auto' as locale to use closest language from the current LC_TIME locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function setLocale($locale) + { + return static::getLocaleAwareTranslator()->setLocale($locale) !== false; + } + + /** + * Set the fallback locale. + * + * @see https://symfony.com/doc/current/components/translation.html#fallback-locales + * + * @param string $locale + */ + public static function setFallbackLocale($locale) + { + $translator = static::getTranslator(); + + if (method_exists($translator, 'setFallbackLocales')) { + $translator->setFallbackLocales([$locale]); + + if ($translator instanceof Translator) { + $preferredLocale = $translator->getLocale(); + $translator->setMessages($preferredLocale, array_replace_recursive( + $translator->getMessages()[$locale] ?? [], + Translator::get($locale)->getMessages()[$locale] ?? [], + $translator->getMessages($preferredLocale) + )); + } + } + } + + /** + * Get the fallback locale. + * + * @see https://symfony.com/doc/current/components/translation.html#fallback-locales + * + * @return string|null + */ + public static function getFallbackLocale() + { + $translator = static::getTranslator(); + + if (method_exists($translator, 'getFallbackLocales')) { + return $translator->getFallbackLocales()[0] ?? null; + } + + return null; + } + + /** + * Set the current locale to the given, execute the passed function, reset the locale to previous one, + * then return the result of the closure (or null if the closure was void). + * + * @param string $locale locale ex. en + * @param callable $func + * + * @return mixed + */ + public static function executeWithLocale($locale, $func) + { + $currentLocale = static::getLocale(); + $result = $func(static::setLocale($locale) ? static::getLocale() : false, static::translator()); + static::setLocale($currentLocale); + + return $result; + } + + /** + * Returns true if the given locale is internally supported and has short-units support. + * Support is considered enabled if either year, day or hour has a short variant translated. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasShortUnits($locale) + { + return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) { + return $newLocale && + ( + ($y = static::translateWith($translator, 'y')) !== 'y' && + $y !== static::translateWith($translator, 'year') + ) || ( + ($y = static::translateWith($translator, 'd')) !== 'd' && + $y !== static::translateWith($translator, 'day') + ) || ( + ($y = static::translateWith($translator, 'h')) !== 'h' && + $y !== static::translateWith($translator, 'hour') + ); + }); + } + + /** + * Returns true if the given locale is internally supported and has diff syntax support (ago, from now, before, after). + * Support is considered enabled if the 4 sentences are translated in the given locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasDiffSyntax($locale) + { + return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) { + if (!$newLocale) { + return false; + } + + foreach (['ago', 'from_now', 'before', 'after'] as $key) { + if ($translator instanceof TranslatorBagInterface && + self::getFromCatalogue($translator, $translator->getCatalogue($newLocale), $key) instanceof Closure + ) { + continue; + } + + if ($translator->trans($key) === $key) { + return false; + } + } + + return true; + }); + } + + /** + * Returns true if the given locale is internally supported and has words for 1-day diff (just now, yesterday, tomorrow). + * Support is considered enabled if the 3 words are translated in the given locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasDiffOneDayWords($locale) + { + return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) { + return $newLocale && + $translator->trans('diff_now') !== 'diff_now' && + $translator->trans('diff_yesterday') !== 'diff_yesterday' && + $translator->trans('diff_tomorrow') !== 'diff_tomorrow'; + }); + } + + /** + * Returns true if the given locale is internally supported and has words for 2-days diff (before yesterday, after tomorrow). + * Support is considered enabled if the 2 words are translated in the given locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasDiffTwoDayWords($locale) + { + return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) { + return $newLocale && + $translator->trans('diff_before_yesterday') !== 'diff_before_yesterday' && + $translator->trans('diff_after_tomorrow') !== 'diff_after_tomorrow'; + }); + } + + /** + * Returns true if the given locale is internally supported and has period syntax support (X times, every X, from X, to X). + * Support is considered enabled if the 4 sentences are translated in the given locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasPeriodSyntax($locale) + { + return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) { + return $newLocale && + $translator->trans('period_recurrences') !== 'period_recurrences' && + $translator->trans('period_interval') !== 'period_interval' && + $translator->trans('period_start_date') !== 'period_start_date' && + $translator->trans('period_end_date') !== 'period_end_date'; + }); + } + + /** + * Returns the list of internally available locales and already loaded custom locales. + * (It will ignore custom translator dynamic loading.) + * + * @return array + */ + public static function getAvailableLocales() + { + $translator = static::getLocaleAwareTranslator(); + + return $translator instanceof Translator + ? $translator->getAvailableLocales() + : [$translator->getLocale()]; + } + + /** + * Returns list of Language object for each available locale. This object allow you to get the ISO name, native + * name, region and variant of the locale. + * + * @return Language[] + */ + public static function getAvailableLocalesInfo() + { + $languages = []; + foreach (static::getAvailableLocales() as $id) { + $languages[$id] = new Language($id); + } + + return $languages; + } + + /** + * Initialize the default translator instance if necessary. + * + * @return \Symfony\Component\Translation\TranslatorInterface + */ + protected static function translator() + { + if (static::$translator === null) { + static::$translator = Translator::get(); + } + + return static::$translator; + } + + /** + * Get the locale of a given translator. + * + * If null or omitted, current local translator is used. + * If no local translator is in use, current global translator is used. + * + * @param null $translator + * + * @return string|null + */ + protected function getTranslatorLocale($translator = null): ?string + { + if (\func_num_args() === 0) { + $translator = $this->getLocalTranslator(); + } + + $translator = static::getLocaleAwareTranslator($translator); + + return $translator ? $translator->getLocale() : null; + } + + /** + * Throw an error if passed object is not LocaleAwareInterface. + * + * @param LocaleAwareInterface|null $translator + * + * @return LocaleAwareInterface|null + */ + protected static function getLocaleAwareTranslator($translator = null) + { + if (\func_num_args() === 0) { + $translator = static::translator(); + } + + if ($translator && !($translator instanceof LocaleAwareInterface || method_exists($translator, 'getLocale'))) { + throw new NotLocaleAwareException($translator); + } + + return $translator; + } + + /** + * @param mixed $translator + * @param \Symfony\Component\Translation\MessageCatalogueInterface $catalogue + * + * @return mixed + */ + private static function getFromCatalogue($translator, $catalogue, string $id, string $domain = 'messages') + { + return $translator instanceof TranslatorStrongTypeInterface + ? $translator->getFromCatalogue($catalogue, $id, $domain) // @codeCoverageIgnore + : $catalogue->get($id, $domain); + } + + /** + * Return the word cleaned from its translation codes. + * + * @param string $word + * + * @return string + */ + private static function cleanWordFromTranslationString($word) + { + $word = str_replace([':count', '%count', ':time'], '', $word); + $word = strtr($word, ['’' => "'"]); + $word = preg_replace('/({\d+(,(\d+|Inf))?}|[\[\]]\d+(,(\d+|Inf))?[\[\]])/', '', $word); + + return trim($word); + } + + /** + * Translate a list of words. + * + * @param string[] $keys keys to translate. + * @param string[] $messages messages bag handling translations. + * @param string $key 'to' (to get the translation) or 'from' (to get the detection RegExp pattern). + * + * @return string[] + */ + private static function translateWordsByKeys($keys, $messages, $key): array + { + return array_map(function ($wordKey) use ($messages, $key) { + $message = $key === 'from' && isset($messages[$wordKey.'_regexp']) + ? $messages[$wordKey.'_regexp'] + : ($messages[$wordKey] ?? null); + + if (!$message) { + return '>>DO NOT REPLACE<<'; + } + + $parts = explode('|', $message); + + return $key === 'to' + ? static::cleanWordFromTranslationString(end($parts)) + : '(?:'.implode('|', array_map([static::class, 'cleanWordFromTranslationString'], $parts)).')'; + }, $keys); + } + + /** + * Get an array of translations based on the current date. + * + * @param callable $translation + * @param int $length + * @param string $timeString + * + * @return string[] + */ + private static function getTranslationArray($translation, $length, $timeString): array + { + $filler = '>>DO NOT REPLACE<<'; + + if (\is_array($translation)) { + return array_pad($translation, $length, $filler); + } + + $list = []; + $date = static::now(); + + for ($i = 0; $i < $length; $i++) { + $list[] = $translation($date, $timeString, $i) ?? $filler; + } + + return $list; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/Macro.php b/vendor/nesbot/carbon/src/Carbon/Traits/Macro.php new file mode 100644 index 0000000..92b6c9d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/Macro.php @@ -0,0 +1,136 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +/** + * Trait Macros. + * + * Allows users to register macros within the Carbon class. + */ +trait Macro +{ + use Mixin; + + /** + * The registered macros. + * + * @var array + */ + protected static $globalMacros = []; + + /** + * The registered generic macros. + * + * @var array + */ + protected static $globalGenericMacros = []; + + /** + * Register a custom macro. + * + * @example + * ``` + * $userSettings = [ + * 'locale' => 'pt', + * 'timezone' => 'America/Sao_Paulo', + * ]; + * Carbon::macro('userFormat', function () use ($userSettings) { + * return $this->copy()->locale($userSettings['locale'])->tz($userSettings['timezone'])->calendar(); + * }); + * echo Carbon::yesterday()->hours(11)->userFormat(); + * ``` + * + * @param string $name + * @param object|callable $macro + * + * @return void + */ + public static function macro($name, $macro) + { + static::$globalMacros[$name] = $macro; + } + + /** + * Remove all macros and generic macros. + */ + public static function resetMacros() + { + static::$globalMacros = []; + static::$globalGenericMacros = []; + } + + /** + * Register a custom macro. + * + * @param object|callable $macro + * @param int $priority marco with higher priority is tried first + * + * @return void + */ + public static function genericMacro($macro, $priority = 0) + { + if (!isset(static::$globalGenericMacros[$priority])) { + static::$globalGenericMacros[$priority] = []; + krsort(static::$globalGenericMacros, SORT_NUMERIC); + } + + static::$globalGenericMacros[$priority][] = $macro; + } + + /** + * Checks if macro is registered globally. + * + * @param string $name + * + * @return bool + */ + public static function hasMacro($name) + { + return isset(static::$globalMacros[$name]); + } + + /** + * Get the raw callable macro registered globally for a given name. + * + * @param string $name + * + * @return callable|null + */ + public static function getMacro($name) + { + return static::$globalMacros[$name] ?? null; + } + + /** + * Checks if macro is registered globally or locally. + * + * @param string $name + * + * @return bool + */ + public function hasLocalMacro($name) + { + return ($this->localMacros && isset($this->localMacros[$name])) || static::hasMacro($name); + } + + /** + * Get the raw callable macro registered globally or locally for a given name. + * + * @param string $name + * + * @return callable|null + */ + public function getLocalMacro($name) + { + return ($this->localMacros ?? [])[$name] ?? static::getMacro($name); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/Mixin.php b/vendor/nesbot/carbon/src/Carbon/Traits/Mixin.php new file mode 100644 index 0000000..ac06084 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/Mixin.php @@ -0,0 +1,191 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Closure; +use Generator; +use ReflectionClass; +use ReflectionException; +use ReflectionMethod; +use Throwable; + +/** + * Trait Mixin. + * + * Allows mixing in entire classes with multiple macros. + */ +trait Mixin +{ + /** + * Stack of macro instance contexts. + * + * @var array + */ + protected static $macroContextStack = []; + + /** + * Mix another object into the class. + * + * @example + * ``` + * Carbon::mixin(new class { + * public function addMoon() { + * return function () { + * return $this->addDays(30); + * }; + * } + * public function subMoon() { + * return function () { + * return $this->subDays(30); + * }; + * } + * }); + * $fullMoon = Carbon::create('2018-12-22'); + * $nextFullMoon = $fullMoon->addMoon(); + * $blackMoon = Carbon::create('2019-01-06'); + * $previousBlackMoon = $blackMoon->subMoon(); + * echo "$nextFullMoon\n"; + * echo "$previousBlackMoon\n"; + * ``` + * + * @param object|string $mixin + * + * @throws ReflectionException + * + * @return void + */ + public static function mixin($mixin) + { + \is_string($mixin) && trait_exists($mixin) + ? static::loadMixinTrait($mixin) + : static::loadMixinClass($mixin); + } + + /** + * @param object|string $mixin + * + * @throws ReflectionException + */ + private static function loadMixinClass($mixin) + { + $methods = (new ReflectionClass($mixin))->getMethods( + ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED + ); + + foreach ($methods as $method) { + if ($method->isConstructor() || $method->isDestructor()) { + continue; + } + + $method->setAccessible(true); + + static::macro($method->name, $method->invoke($mixin)); + } + } + + /** + * @param string $trait + */ + private static function loadMixinTrait($trait) + { + $context = eval(self::getAnonymousClassCodeForTrait($trait)); + $className = \get_class($context); + + foreach (self::getMixableMethods($context) as $name) { + $closureBase = Closure::fromCallable([$context, $name]); + + static::macro($name, function () use ($closureBase, $className) { + /** @phpstan-ignore-next-line */ + $context = isset($this) ? $this->cast($className) : new $className(); + + try { + // @ is required to handle error if not converted into exceptions + $closure = @$closureBase->bindTo($context); + } catch (Throwable $throwable) { // @codeCoverageIgnore + $closure = $closureBase; // @codeCoverageIgnore + } + + // in case of errors not converted into exceptions + $closure = $closure ?? $closureBase; + + return $closure(...\func_get_args()); + }); + } + } + + private static function getAnonymousClassCodeForTrait(string $trait) + { + return 'return new class() extends '.static::class.' {use '.$trait.';};'; + } + + private static function getMixableMethods(self $context): Generator + { + foreach (get_class_methods($context) as $name) { + if (method_exists(static::class, $name)) { + continue; + } + + yield $name; + } + } + + /** + * Stack a Carbon context from inside calls of self::this() and execute a given action. + * + * @param static|null $context + * @param callable $callable + * + * @throws Throwable + * + * @return mixed + */ + protected static function bindMacroContext($context, callable $callable) + { + static::$macroContextStack[] = $context; + $exception = null; + $result = null; + + try { + $result = $callable(); + } catch (Throwable $throwable) { + $exception = $throwable; + } + + array_pop(static::$macroContextStack); + + if ($exception) { + throw $exception; + } + + return $result; + } + + /** + * Return the current context from inside a macro callee or a null if static. + * + * @return static|null + */ + protected static function context() + { + return end(static::$macroContextStack) ?: null; + } + + /** + * Return the current context from inside a macro callee or a new one if static. + * + * @return static + */ + protected static function this() + { + return end(static::$macroContextStack) ?: new static(); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/Modifiers.php b/vendor/nesbot/carbon/src/Carbon/Traits/Modifiers.php new file mode 100644 index 0000000..164dbbd --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/Modifiers.php @@ -0,0 +1,472 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\CarbonInterface; +use ReturnTypeWillChange; + +/** + * Trait Modifiers. + * + * Returns dates relative to current date using modifier short-hand. + */ +trait Modifiers +{ + /** + * Midday/noon hour. + * + * @var int + */ + protected static $midDayAt = 12; + + /** + * get midday/noon hour + * + * @return int + */ + public static function getMidDayAt() + { + return static::$midDayAt; + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather consider mid-day is always 12pm, then if you need to test if it's an other + * hour, test it explicitly: + * $date->format('G') == 13 + * or to set explicitly to a given hour: + * $date->setTime(13, 0, 0, 0) + * + * Set midday/noon hour + * + * @param int $hour midday hour + * + * @return void + */ + public static function setMidDayAt($hour) + { + static::$midDayAt = $hour; + } + + /** + * Modify to midday, default to self::$midDayAt + * + * @return static + */ + public function midDay() + { + return $this->setTime(static::$midDayAt, 0, 0, 0); + } + + /** + * Modify to the next occurrence of a given modifier such as a day of + * the week. If no modifier is provided, modify to the next occurrence + * of the current day of the week. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param string|int|null $modifier + * + * @return static + */ + public function next($modifier = null) + { + if ($modifier === null) { + $modifier = $this->dayOfWeek; + } + + return $this->change( + 'next '.(\is_string($modifier) ? $modifier : static::$days[$modifier]) + ); + } + + /** + * Go forward or backward to the next week- or weekend-day. + * + * @param bool $weekday + * @param bool $forward + * + * @return static + */ + private function nextOrPreviousDay($weekday = true, $forward = true) + { + /** @var CarbonInterface $date */ + $date = $this; + $step = $forward ? 1 : -1; + + do { + $date = $date->addDays($step); + } while ($weekday ? $date->isWeekend() : $date->isWeekday()); + + return $date; + } + + /** + * Go forward to the next weekday. + * + * @return static + */ + public function nextWeekday() + { + return $this->nextOrPreviousDay(); + } + + /** + * Go backward to the previous weekday. + * + * @return static + */ + public function previousWeekday() + { + return $this->nextOrPreviousDay(true, false); + } + + /** + * Go forward to the next weekend day. + * + * @return static + */ + public function nextWeekendDay() + { + return $this->nextOrPreviousDay(false); + } + + /** + * Go backward to the previous weekend day. + * + * @return static + */ + public function previousWeekendDay() + { + return $this->nextOrPreviousDay(false, false); + } + + /** + * Modify to the previous occurrence of a given modifier such as a day of + * the week. If no dayOfWeek is provided, modify to the previous occurrence + * of the current day of the week. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param string|int|null $modifier + * + * @return static + */ + public function previous($modifier = null) + { + if ($modifier === null) { + $modifier = $this->dayOfWeek; + } + + return $this->change( + 'last '.(\is_string($modifier) ? $modifier : static::$days[$modifier]) + ); + } + + /** + * Modify to the first occurrence of a given day of the week + * in the current month. If no dayOfWeek is provided, modify to the + * first day of the current month. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek + * + * @return static + */ + public function firstOfMonth($dayOfWeek = null) + { + $date = $this->startOfDay(); + + if ($dayOfWeek === null) { + return $date->day(1); + } + + return $date->modify('first '.static::$days[$dayOfWeek].' of '.$date->rawFormat('F').' '.$date->year); + } + + /** + * Modify to the last occurrence of a given day of the week + * in the current month. If no dayOfWeek is provided, modify to the + * last day of the current month. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek + * + * @return static + */ + public function lastOfMonth($dayOfWeek = null) + { + $date = $this->startOfDay(); + + if ($dayOfWeek === null) { + return $date->day($date->daysInMonth); + } + + return $date->modify('last '.static::$days[$dayOfWeek].' of '.$date->rawFormat('F').' '.$date->year); + } + + /** + * Modify to the given occurrence of a given day of the week + * in the current month. If the calculated occurrence is outside the scope + * of the current month, then return false and no modifications are made. + * Use the supplied constants to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int $nth + * @param int $dayOfWeek + * + * @return mixed + */ + public function nthOfMonth($nth, $dayOfWeek) + { + $date = $this->avoidMutation()->firstOfMonth(); + $check = $date->rawFormat('Y-m'); + $date = $date->modify('+'.$nth.' '.static::$days[$dayOfWeek]); + + return $date->rawFormat('Y-m') === $check ? $this->modify((string) $date) : false; + } + + /** + * Modify to the first occurrence of a given day of the week + * in the current quarter. If no dayOfWeek is provided, modify to the + * first day of the current quarter. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek day of the week default null + * + * @return static + */ + public function firstOfQuarter($dayOfWeek = null) + { + return $this->setDate($this->year, $this->quarter * static::MONTHS_PER_QUARTER - 2, 1)->firstOfMonth($dayOfWeek); + } + + /** + * Modify to the last occurrence of a given day of the week + * in the current quarter. If no dayOfWeek is provided, modify to the + * last day of the current quarter. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek day of the week default null + * + * @return static + */ + public function lastOfQuarter($dayOfWeek = null) + { + return $this->setDate($this->year, $this->quarter * static::MONTHS_PER_QUARTER, 1)->lastOfMonth($dayOfWeek); + } + + /** + * Modify to the given occurrence of a given day of the week + * in the current quarter. If the calculated occurrence is outside the scope + * of the current quarter, then return false and no modifications are made. + * Use the supplied constants to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int $nth + * @param int $dayOfWeek + * + * @return mixed + */ + public function nthOfQuarter($nth, $dayOfWeek) + { + $date = $this->avoidMutation()->day(1)->month($this->quarter * static::MONTHS_PER_QUARTER); + $lastMonth = $date->month; + $year = $date->year; + $date = $date->firstOfQuarter()->modify('+'.$nth.' '.static::$days[$dayOfWeek]); + + return ($lastMonth < $date->month || $year !== $date->year) ? false : $this->modify((string) $date); + } + + /** + * Modify to the first occurrence of a given day of the week + * in the current year. If no dayOfWeek is provided, modify to the + * first day of the current year. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek day of the week default null + * + * @return static + */ + public function firstOfYear($dayOfWeek = null) + { + return $this->month(1)->firstOfMonth($dayOfWeek); + } + + /** + * Modify to the last occurrence of a given day of the week + * in the current year. If no dayOfWeek is provided, modify to the + * last day of the current year. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek day of the week default null + * + * @return static + */ + public function lastOfYear($dayOfWeek = null) + { + return $this->month(static::MONTHS_PER_YEAR)->lastOfMonth($dayOfWeek); + } + + /** + * Modify to the given occurrence of a given day of the week + * in the current year. If the calculated occurrence is outside the scope + * of the current year, then return false and no modifications are made. + * Use the supplied constants to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int $nth + * @param int $dayOfWeek + * + * @return mixed + */ + public function nthOfYear($nth, $dayOfWeek) + { + $date = $this->avoidMutation()->firstOfYear()->modify('+'.$nth.' '.static::$days[$dayOfWeek]); + + return $this->year === $date->year ? $this->modify((string) $date) : false; + } + + /** + * Modify the current instance to the average of a given instance (default now) and the current instance + * (second-precision). + * + * @param \Carbon\Carbon|\DateTimeInterface|null $date + * + * @return static + */ + public function average($date = null) + { + return $this->addRealMicroseconds((int) ($this->diffInRealMicroseconds($this->resolveCarbon($date), false) / 2)); + } + + /** + * Get the closest date from the instance (second-precision). + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 + * + * @return static + */ + public function closest($date1, $date2) + { + return $this->diffInRealMicroseconds($date1) < $this->diffInRealMicroseconds($date2) ? $date1 : $date2; + } + + /** + * Get the farthest date from the instance (second-precision). + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 + * + * @return static + */ + public function farthest($date1, $date2) + { + return $this->diffInRealMicroseconds($date1) > $this->diffInRealMicroseconds($date2) ? $date1 : $date2; + } + + /** + * Get the minimum instance between a given instance (default now) and the current instance. + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return static + */ + public function min($date = null) + { + $date = $this->resolveCarbon($date); + + return $this->lt($date) ? $this : $date; + } + + /** + * Get the minimum instance between a given instance (default now) and the current instance. + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see min() + * + * @return static + */ + public function minimum($date = null) + { + return $this->min($date); + } + + /** + * Get the maximum instance between a given instance (default now) and the current instance. + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return static + */ + public function max($date = null) + { + $date = $this->resolveCarbon($date); + + return $this->gt($date) ? $this : $date; + } + + /** + * Get the maximum instance between a given instance (default now) and the current instance. + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see max() + * + * @return static + */ + public function maximum($date = null) + { + return $this->max($date); + } + + /** + * Calls \DateTime::modify if mutable or \DateTimeImmutable::modify else. + * + * @see https://php.net/manual/en/datetime.modify.php + * + * @return static|false + */ + #[ReturnTypeWillChange] + public function modify($modify) + { + return parent::modify((string) $modify); + } + + /** + * Similar to native modify() method of DateTime but can handle more grammars. + * + * @example + * ``` + * echo Carbon::now()->change('next 2pm'); + * ``` + * + * @link https://php.net/manual/en/datetime.modify.php + * + * @param string $modifier + * + * @return static + */ + public function change($modifier) + { + return $this->modify(preg_replace_callback('/^(next|previous|last)\s+(\d{1,2}(h|am|pm|:\d{1,2}(:\d{1,2})?))$/i', function ($match) { + $match[2] = str_replace('h', ':00', $match[2]); + $test = $this->avoidMutation()->modify($match[2]); + $method = $match[1] === 'next' ? 'lt' : 'gt'; + $match[1] = $test->$method($this) ? $match[1].' day' : 'today'; + + return $match[1].' '.$match[2]; + }, strtr(trim($modifier), [ + ' at ' => ' ', + 'just now' => 'now', + 'after tomorrow' => 'tomorrow +1 day', + 'before yesterday' => 'yesterday -1 day', + ]))); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/Mutability.php b/vendor/nesbot/carbon/src/Carbon/Traits/Mutability.php new file mode 100644 index 0000000..561c867 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/Mutability.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\Carbon; +use Carbon\CarbonImmutable; + +/** + * Trait Mutability. + * + * Utils to know if the current object is mutable or immutable and convert it. + */ +trait Mutability +{ + use Cast; + + /** + * Returns true if the current class/instance is mutable. + * + * @return bool + */ + public static function isMutable() + { + return false; + } + + /** + * Returns true if the current class/instance is immutable. + * + * @return bool + */ + public static function isImmutable() + { + return !static::isMutable(); + } + + /** + * Return a mutable copy of the instance. + * + * @return Carbon + */ + public function toMutable() + { + /** @var Carbon $date */ + $date = $this->cast(Carbon::class); + + return $date; + } + + /** + * Return a immutable copy of the instance. + * + * @return CarbonImmutable + */ + public function toImmutable() + { + /** @var CarbonImmutable $date */ + $date = $this->cast(CarbonImmutable::class); + + return $date; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/ObjectInitialisation.php b/vendor/nesbot/carbon/src/Carbon/Traits/ObjectInitialisation.php new file mode 100644 index 0000000..c77a102 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/ObjectInitialisation.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +trait ObjectInitialisation +{ + /** + * True when parent::__construct has been called. + * + * @var string + */ + protected $constructedObjectId; +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/Options.php b/vendor/nesbot/carbon/src/Carbon/Traits/Options.php new file mode 100644 index 0000000..0ddee8d --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/Options.php @@ -0,0 +1,471 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\CarbonInterface; +use DateTimeInterface; +use Throwable; + +/** + * Trait Options. + * + * Embed base methods to change settings of Carbon classes. + * + * Depends on the following methods: + * + * @method \Carbon\Carbon|\Carbon\CarbonImmutable shiftTimezone($timezone) Set the timezone + */ +trait Options +{ + use Localization; + + /** + * Customizable PHP_INT_SIZE override. + * + * @var int + */ + public static $PHPIntSize = PHP_INT_SIZE; + + /** + * First day of week. + * + * @var int|string + */ + protected static $weekStartsAt = CarbonInterface::MONDAY; + + /** + * Last day of week. + * + * @var int|string + */ + protected static $weekEndsAt = CarbonInterface::SUNDAY; + + /** + * Days of weekend. + * + * @var array + */ + protected static $weekendDays = [ + CarbonInterface::SATURDAY, + CarbonInterface::SUNDAY, + ]; + + /** + * Format regex patterns. + * + * @var array + */ + protected static $regexFormats = [ + 'd' => '(3[01]|[12][0-9]|0[1-9])', + 'D' => '(Sun|Mon|Tue|Wed|Thu|Fri|Sat)', + 'j' => '([123][0-9]|[1-9])', + 'l' => '([a-zA-Z]{2,})', + 'N' => '([1-7])', + 'S' => '(st|nd|rd|th)', + 'w' => '([0-6])', + 'z' => '(36[0-5]|3[0-5][0-9]|[12][0-9]{2}|[1-9]?[0-9])', + 'W' => '(5[012]|[1-4][0-9]|0?[1-9])', + 'F' => '([a-zA-Z]{2,})', + 'm' => '(1[012]|0[1-9])', + 'M' => '([a-zA-Z]{3})', + 'n' => '(1[012]|[1-9])', + 't' => '(2[89]|3[01])', + 'L' => '(0|1)', + 'o' => '([1-9][0-9]{0,4})', + 'Y' => '([1-9]?[0-9]{4})', + 'y' => '([0-9]{2})', + 'a' => '(am|pm)', + 'A' => '(AM|PM)', + 'B' => '([0-9]{3})', + 'g' => '(1[012]|[1-9])', + 'G' => '(2[0-3]|1?[0-9])', + 'h' => '(1[012]|0[1-9])', + 'H' => '(2[0-3]|[01][0-9])', + 'i' => '([0-5][0-9])', + 's' => '([0-5][0-9])', + 'u' => '([0-9]{1,6})', + 'v' => '([0-9]{1,3})', + 'e' => '([a-zA-Z]{1,5})|([a-zA-Z]*\\/[a-zA-Z]*)', + 'I' => '(0|1)', + 'O' => '([+-](1[012]|0[0-9])[0134][05])', + 'P' => '([+-](1[012]|0[0-9]):[0134][05])', + 'p' => '(Z|[+-](1[012]|0[0-9]):[0134][05])', + 'T' => '([a-zA-Z]{1,5})', + 'Z' => '(-?[1-5]?[0-9]{1,4})', + 'U' => '([0-9]*)', + + // The formats below are combinations of the above formats. + 'c' => '(([1-9]?[0-9]{4})-(1[012]|0[1-9])-(3[01]|[12][0-9]|0[1-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])[+-](1[012]|0[0-9]):([0134][05]))', // Y-m-dTH:i:sP + 'r' => '(([a-zA-Z]{3}), ([123][0-9]|0[1-9]) ([a-zA-Z]{3}) ([1-9]?[0-9]{4}) (2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9]) [+-](1[012]|0[0-9])([0134][05]))', // D, d M Y H:i:s O + ]; + + /** + * Format modifiers (such as available in createFromFormat) regex patterns. + * + * @var array + */ + protected static $regexFormatModifiers = [ + '*' => '.+', + ' ' => '[ ]', + '#' => '[;:\\/.,()-]', + '?' => '([^a]|[a])', + '!' => '', + '|' => '', + '+' => '', + ]; + + /** + * Indicates if months should be calculated with overflow. + * Global setting. + * + * @var bool + */ + protected static $monthsOverflow = true; + + /** + * Indicates if years should be calculated with overflow. + * Global setting. + * + * @var bool + */ + protected static $yearsOverflow = true; + + /** + * Indicates if the strict mode is in use. + * Global setting. + * + * @var bool + */ + protected static $strictModeEnabled = true; + + /** + * Function to call instead of format. + * + * @var string|callable|null + */ + protected static $formatFunction; + + /** + * Function to call instead of createFromFormat. + * + * @var string|callable|null + */ + protected static $createFromFormatFunction; + + /** + * Function to call instead of parse. + * + * @var string|callable|null + */ + protected static $parseFunction; + + /** + * Indicates if months should be calculated with overflow. + * Specific setting. + * + * @var bool|null + */ + protected $localMonthsOverflow; + + /** + * Indicates if years should be calculated with overflow. + * Specific setting. + * + * @var bool|null + */ + protected $localYearsOverflow; + + /** + * Indicates if the strict mode is in use. + * Specific setting. + * + * @var bool|null + */ + protected $localStrictModeEnabled; + + /** + * Options for diffForHumans and forHumans methods. + * + * @var bool|null + */ + protected $localHumanDiffOptions; + + /** + * Format to use on string cast. + * + * @var string|null + */ + protected $localToStringFormat; + + /** + * Format to use on JSON serialization. + * + * @var string|null + */ + protected $localSerializer; + + /** + * Instance-specific macros. + * + * @var array|null + */ + protected $localMacros; + + /** + * Instance-specific generic macros. + * + * @var array|null + */ + protected $localGenericMacros; + + /** + * Function to call instead of format. + * + * @var string|callable|null + */ + protected $localFormatFunction; + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @see settings + * + * Enable the strict mode (or disable with passing false). + * + * @param bool $strictModeEnabled + */ + public static function useStrictMode($strictModeEnabled = true) + { + static::$strictModeEnabled = $strictModeEnabled; + } + + /** + * Returns true if the strict mode is globally in use, false else. + * (It can be overridden in specific instances.) + * + * @return bool + */ + public static function isStrictModeEnabled() + { + return static::$strictModeEnabled; + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addMonthsWithOverflow/addMonthsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @see settings + * + * Indicates if months should be calculated with overflow. + * + * @param bool $monthsOverflow + * + * @return void + */ + public static function useMonthsOverflow($monthsOverflow = true) + { + static::$monthsOverflow = $monthsOverflow; + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addMonthsWithOverflow/addMonthsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @see settings + * + * Reset the month overflow behavior. + * + * @return void + */ + public static function resetMonthsOverflow() + { + static::$monthsOverflow = true; + } + + /** + * Get the month overflow global behavior (can be overridden in specific instances). + * + * @return bool + */ + public static function shouldOverflowMonths() + { + return static::$monthsOverflow; + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addYearsWithOverflow/addYearsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @see settings + * + * Indicates if years should be calculated with overflow. + * + * @param bool $yearsOverflow + * + * @return void + */ + public static function useYearsOverflow($yearsOverflow = true) + { + static::$yearsOverflow = $yearsOverflow; + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addYearsWithOverflow/addYearsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @see settings + * + * Reset the month overflow behavior. + * + * @return void + */ + public static function resetYearsOverflow() + { + static::$yearsOverflow = true; + } + + /** + * Get the month overflow global behavior (can be overridden in specific instances). + * + * @return bool + */ + public static function shouldOverflowYears() + { + return static::$yearsOverflow; + } + + /** + * Set specific options. + * - strictMode: true|false|null + * - monthOverflow: true|false|null + * - yearOverflow: true|false|null + * - humanDiffOptions: int|null + * - toStringFormat: string|Closure|null + * - toJsonFormat: string|Closure|null + * - locale: string|null + * - timezone: \DateTimeZone|string|int|null + * - macros: array|null + * - genericMacros: array|null + * + * @param array $settings + * + * @return $this|static + */ + public function settings(array $settings) + { + $this->localStrictModeEnabled = $settings['strictMode'] ?? null; + $this->localMonthsOverflow = $settings['monthOverflow'] ?? null; + $this->localYearsOverflow = $settings['yearOverflow'] ?? null; + $this->localHumanDiffOptions = $settings['humanDiffOptions'] ?? null; + $this->localToStringFormat = $settings['toStringFormat'] ?? null; + $this->localSerializer = $settings['toJsonFormat'] ?? null; + $this->localMacros = $settings['macros'] ?? null; + $this->localGenericMacros = $settings['genericMacros'] ?? null; + $this->localFormatFunction = $settings['formatFunction'] ?? null; + + if (isset($settings['locale'])) { + $locales = $settings['locale']; + + if (!\is_array($locales)) { + $locales = [$locales]; + } + + $this->locale(...$locales); + } + + if (isset($settings['innerTimezone'])) { + return $this->setTimezone($settings['innerTimezone']); + } + + if (isset($settings['timezone'])) { + return $this->shiftTimezone($settings['timezone']); + } + + return $this; + } + + /** + * Returns current local settings. + * + * @return array + */ + public function getSettings() + { + $settings = []; + $map = [ + 'localStrictModeEnabled' => 'strictMode', + 'localMonthsOverflow' => 'monthOverflow', + 'localYearsOverflow' => 'yearOverflow', + 'localHumanDiffOptions' => 'humanDiffOptions', + 'localToStringFormat' => 'toStringFormat', + 'localSerializer' => 'toJsonFormat', + 'localMacros' => 'macros', + 'localGenericMacros' => 'genericMacros', + 'locale' => 'locale', + 'tzName' => 'timezone', + 'localFormatFunction' => 'formatFunction', + ]; + + foreach ($map as $property => $key) { + $value = $this->$property ?? null; + + if ($value !== null) { + $settings[$key] = $value; + } + } + + return $settings; + } + + /** + * Show truthy properties on var_dump(). + * + * @return array + */ + public function __debugInfo() + { + $infos = array_filter(get_object_vars($this), function ($var) { + return $var; + }); + + foreach (['dumpProperties', 'constructedObjectId'] as $property) { + if (isset($infos[$property])) { + unset($infos[$property]); + } + } + + $this->addExtraDebugInfos($infos); + + return $infos; + } + + protected function addExtraDebugInfos(&$infos): void + { + if ($this instanceof DateTimeInterface) { + try { + if (!isset($infos['date'])) { + $infos['date'] = $this->format(CarbonInterface::MOCK_DATETIME_FORMAT); + } + + if (!isset($infos['timezone'])) { + $infos['timezone'] = $this->tzName; + } + } catch (Throwable $exception) { + // noop + } + } + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/Rounding.php b/vendor/nesbot/carbon/src/Carbon/Traits/Rounding.php new file mode 100644 index 0000000..3306239 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/Rounding.php @@ -0,0 +1,239 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\CarbonInterface; +use Carbon\Exceptions\UnknownUnitException; + +/** + * Trait Rounding. + * + * Round, ceil, floor units. + * + * Depends on the following methods: + * + * @method static copy() + * @method static startOfWeek(int $weekStartsAt = null) + */ +trait Rounding +{ + use IntervalRounding; + + /** + * Round the current instance at the given unit with given precision if specified and the given function. + * + * @param string $unit + * @param float|int $precision + * @param string $function + * + * @return CarbonInterface + */ + public function roundUnit($unit, $precision = 1, $function = 'round') + { + $metaUnits = [ + // @call roundUnit + 'millennium' => [static::YEARS_PER_MILLENNIUM, 'year'], + // @call roundUnit + 'century' => [static::YEARS_PER_CENTURY, 'year'], + // @call roundUnit + 'decade' => [static::YEARS_PER_DECADE, 'year'], + // @call roundUnit + 'quarter' => [static::MONTHS_PER_QUARTER, 'month'], + // @call roundUnit + 'millisecond' => [1000, 'microsecond'], + ]; + $normalizedUnit = static::singularUnit($unit); + $ranges = array_merge(static::getRangesByUnit(), [ + // @call roundUnit + 'microsecond' => [0, 999999], + ]); + $factor = 1; + $initialMonth = $this->month; + + if ($normalizedUnit === 'week') { + $normalizedUnit = 'day'; + $precision *= static::DAYS_PER_WEEK; + } + + if (isset($metaUnits[$normalizedUnit])) { + [$factor, $normalizedUnit] = $metaUnits[$normalizedUnit]; + } + + $precision *= $factor; + + if (!isset($ranges[$normalizedUnit])) { + throw new UnknownUnitException($unit); + } + + $found = false; + $fraction = 0; + $arguments = null; + $factor = $this->year < 0 ? -1 : 1; + $changes = []; + + foreach ($ranges as $unit => [$minimum, $maximum]) { + if ($normalizedUnit === $unit) { + $arguments = [$this->$unit, $minimum]; + $fraction = $precision - floor($precision); + $found = true; + + continue; + } + + if ($found) { + $delta = $maximum + 1 - $minimum; + $factor /= $delta; + $fraction *= $delta; + $arguments[0] += $this->$unit * $factor; + $changes[$unit] = round( + $minimum + ($fraction ? $fraction * $function(($this->$unit - $minimum) / $fraction) : 0) + ); + + // Cannot use modulo as it lose double precision + while ($changes[$unit] >= $delta) { + $changes[$unit] -= $delta; + } + + $fraction -= floor($fraction); + } + } + + [$value, $minimum] = $arguments; + $normalizedValue = floor($function(($value - $minimum) / $precision) * $precision + $minimum); + + /** @var CarbonInterface $result */ + $result = $this->$normalizedUnit($normalizedValue); + + foreach ($changes as $unit => $value) { + $result = $result->$unit($value); + } + + return $normalizedUnit === 'month' && $precision <= 1 && abs($result->month - $initialMonth) === 2 + // Re-run the change in case an overflow occurred + ? $result->$normalizedUnit($normalizedValue) + : $result; + } + + /** + * Truncate the current instance at the given unit with given precision if specified. + * + * @param string $unit + * @param float|int $precision + * + * @return CarbonInterface + */ + public function floorUnit($unit, $precision = 1) + { + return $this->roundUnit($unit, $precision, 'floor'); + } + + /** + * Ceil the current instance at the given unit with given precision if specified. + * + * @param string $unit + * @param float|int $precision + * + * @return CarbonInterface + */ + public function ceilUnit($unit, $precision = 1) + { + return $this->roundUnit($unit, $precision, 'ceil'); + } + + /** + * Round the current instance second with given precision if specified. + * + * @param float|int|string|\DateInterval|null $precision + * @param string $function + * + * @return CarbonInterface + */ + public function round($precision = 1, $function = 'round') + { + return $this->roundWith($precision, $function); + } + + /** + * Round the current instance second with given precision if specified. + * + * @param float|int|string|\DateInterval|null $precision + * + * @return CarbonInterface + */ + public function floor($precision = 1) + { + return $this->round($precision, 'floor'); + } + + /** + * Ceil the current instance second with given precision if specified. + * + * @param float|int|string|\DateInterval|null $precision + * + * @return CarbonInterface + */ + public function ceil($precision = 1) + { + return $this->round($precision, 'ceil'); + } + + /** + * Round the current instance week. + * + * @param int $weekStartsAt optional start allow you to specify the day of week to use to start the week + * + * @return CarbonInterface + */ + public function roundWeek($weekStartsAt = null) + { + return $this->closest( + $this->avoidMutation()->floorWeek($weekStartsAt), + $this->avoidMutation()->ceilWeek($weekStartsAt) + ); + } + + /** + * Truncate the current instance week. + * + * @param int $weekStartsAt optional start allow you to specify the day of week to use to start the week + * + * @return CarbonInterface + */ + public function floorWeek($weekStartsAt = null) + { + return $this->startOfWeek($weekStartsAt); + } + + /** + * Ceil the current instance week. + * + * @param int $weekStartsAt optional start allow you to specify the day of week to use to start the week + * + * @return CarbonInterface + */ + public function ceilWeek($weekStartsAt = null) + { + if ($this->isMutable()) { + $startOfWeek = $this->avoidMutation()->startOfWeek($weekStartsAt); + + return $startOfWeek != $this ? + $this->startOfWeek($weekStartsAt)->addWeek() : + $this; + } + + $startOfWeek = $this->startOfWeek($weekStartsAt); + + return $startOfWeek != $this ? + $startOfWeek->addWeek() : + $this->avoidMutation(); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/Serialization.php b/vendor/nesbot/carbon/src/Carbon/Traits/Serialization.php new file mode 100644 index 0000000..eebc69b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/Serialization.php @@ -0,0 +1,240 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\Exceptions\InvalidFormatException; +use ReturnTypeWillChange; +use Throwable; + +/** + * Trait Serialization. + * + * Serialization and JSON stuff. + * + * Depends on the following properties: + * + * @property int $year + * @property int $month + * @property int $daysInMonth + * @property int $quarter + * + * Depends on the following methods: + * + * @method string|static locale(string $locale = null, string ...$fallbackLocales) + * @method string toJSON() + */ +trait Serialization +{ + use ObjectInitialisation; + + /** + * The custom Carbon JSON serializer. + * + * @var callable|null + */ + protected static $serializer; + + /** + * List of key to use for dump/serialization. + * + * @var string[] + */ + protected $dumpProperties = ['date', 'timezone_type', 'timezone']; + + /** + * Locale to dump comes here before serialization. + * + * @var string|null + */ + protected $dumpLocale; + + /** + * Embed date properties to dump in a dedicated variables so it won't overlap native + * DateTime ones. + * + * @var array|null + */ + protected $dumpDateProperties; + + /** + * Return a serialized string of the instance. + * + * @return string + */ + public function serialize() + { + return serialize($this); + } + + /** + * Create an instance from a serialized string. + * + * @param string $value + * + * @throws InvalidFormatException + * + * @return static + */ + public static function fromSerialized($value) + { + $instance = @unserialize((string) $value); + + if (!$instance instanceof static) { + throw new InvalidFormatException("Invalid serialized value: $value"); + } + + return $instance; + } + + /** + * The __set_state handler. + * + * @param string|array $dump + * + * @return static + */ + #[ReturnTypeWillChange] + public static function __set_state($dump) + { + if (\is_string($dump)) { + return static::parse($dump); + } + + /** @var \DateTimeInterface $date */ + $date = get_parent_class(static::class) && method_exists(parent::class, '__set_state') + ? parent::__set_state((array) $dump) + : (object) $dump; + + return static::instance($date); + } + + /** + * Returns the list of properties to dump on serialize() called on. + * + * @return array + */ + public function __sleep() + { + $properties = $this->getSleepProperties(); + + if ($this->localTranslator ?? null) { + $properties[] = 'dumpLocale'; + $this->dumpLocale = $this->locale ?? null; + } + + return $properties; + } + + /** + * Set locale if specified on unserialize() called. + * + * @return void + */ + #[ReturnTypeWillChange] + public function __wakeup() + { + if (get_parent_class() && method_exists(parent::class, '__wakeup')) { + // @codeCoverageIgnoreStart + try { + parent::__wakeup(); + } catch (Throwable $exception) { + // FatalError occurs when calling msgpack_unpack() in PHP 7.4 or later. + ['date' => $date, 'timezone' => $timezone] = $this->dumpDateProperties; + parent::__construct($date, unserialize($timezone)); + } + // @codeCoverageIgnoreEnd + } + + $this->constructedObjectId = spl_object_hash($this); + + if (isset($this->dumpLocale)) { + $this->locale($this->dumpLocale); + $this->dumpLocale = null; + } + + $this->cleanupDumpProperties(); + } + + /** + * Prepare the object for JSON serialization. + * + * @return array|string + */ + #[ReturnTypeWillChange] + public function jsonSerialize() + { + $serializer = $this->localSerializer ?? static::$serializer; + + if ($serializer) { + return \is_string($serializer) + ? $this->rawFormat($serializer) + : $serializer($this); + } + + return $this->toJSON(); + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather transform Carbon object before the serialization. + * + * JSON serialize all Carbon instances using the given callback. + * + * @param callable $callback + * + * @return void + */ + public static function serializeUsing($callback) + { + static::$serializer = $callback; + } + + /** + * Cleanup properties attached to the public scope of DateTime when a dump of the date is requested. + * foreach ($date as $_) {} + * serializer($date) + * var_export($date) + * get_object_vars($date) + */ + public function cleanupDumpProperties() + { + foreach ($this->dumpProperties as $property) { + if (isset($this->$property)) { + unset($this->$property); + } + } + + return $this; + } + + private function getSleepProperties(): array + { + $properties = $this->dumpProperties; + + // @codeCoverageIgnoreStart + if (!\extension_loaded('msgpack')) { + return $properties; + } + + if (isset($this->constructedObjectId)) { + $this->dumpDateProperties = [ + 'date' => $this->format('Y-m-d H:i:s.u'), + 'timezone' => serialize($this->timezone ?? null), + ]; + + $properties[] = 'dumpDateProperties'; + } + + return $properties; + // @codeCoverageIgnoreEnd + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/Test.php b/vendor/nesbot/carbon/src/Carbon/Traits/Test.php new file mode 100644 index 0000000..d22988c --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/Test.php @@ -0,0 +1,208 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\CarbonInterface; +use Carbon\CarbonTimeZone; +use Closure; +use DateTimeImmutable; +use DateTimeInterface; +use InvalidArgumentException; +use Throwable; + +trait Test +{ + /////////////////////////////////////////////////////////////////// + ///////////////////////// TESTING AIDS //////////////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * A test Carbon instance to be returned when now instances are created. + * + * @var static + */ + protected static $testNow; + + /** + * Set a Carbon instance (real or mock) to be returned when a "now" + * instance is created. The provided instance will be returned + * specifically under the following conditions: + * - A call to the static now() method, ex. Carbon::now() + * - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null) + * - When the string "now" is passed to the constructor or parse(), ex. new Carbon('now') + * - When a string containing the desired time is passed to Carbon::parse(). + * + * Note the timezone parameter was left out of the examples above and + * has no affect as the mock value will be returned regardless of its value. + * + * Only the moment is mocked with setTestNow(), the timezone will still be the one passed + * as parameter of date_default_timezone_get() as a fallback (see setTestNowAndTimezone()). + * + * To clear the test instance call this method using the default + * parameter of null. + * + * /!\ Use this method for unit tests only. + * + * @param Closure|static|string|false|null $testNow real or mock Carbon instance + */ + public static function setTestNow($testNow = null) + { + if ($testNow === false) { + $testNow = null; + } + + static::$testNow = \is_string($testNow) ? static::parse($testNow) : $testNow; + } + + /** + * Set a Carbon instance (real or mock) to be returned when a "now" + * instance is created. The provided instance will be returned + * specifically under the following conditions: + * - A call to the static now() method, ex. Carbon::now() + * - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null) + * - When the string "now" is passed to the constructor or parse(), ex. new Carbon('now') + * - When a string containing the desired time is passed to Carbon::parse(). + * + * It will also align default timezone (e.g. call date_default_timezone_set()) with + * the second argument or if null, with the timezone of the given date object. + * + * To clear the test instance call this method using the default + * parameter of null. + * + * /!\ Use this method for unit tests only. + * + * @param Closure|static|string|false|null $testNow real or mock Carbon instance + */ + public static function setTestNowAndTimezone($testNow = null, $tz = null) + { + $useDateInstanceTimezone = $testNow instanceof DateTimeInterface; + + if ($useDateInstanceTimezone) { + self::setDefaultTimezone($testNow->getTimezone()->getName(), $testNow); + } + + static::setTestNow($testNow); + + if (!$useDateInstanceTimezone) { + $now = static::getMockedTestNow(\func_num_args() === 1 ? null : $tz); + self::setDefaultTimezone($now->tzName, $now); + } + } + + /** + * Temporarily sets a static date to be used within the callback. + * Using setTestNow to set the date, executing the callback, then + * clearing the test instance. + * + * /!\ Use this method for unit tests only. + * + * @param Closure|static|string|false|null $testNow real or mock Carbon instance + * @param Closure|null $callback + * + * @return mixed + */ + public static function withTestNow($testNow = null, $callback = null) + { + static::setTestNow($testNow); + $result = $callback(); + static::setTestNow(); + + return $result; + } + + /** + * Get the Carbon instance (real or mock) to be returned when a "now" + * instance is created. + * + * @return Closure|static the current instance used for testing + */ + public static function getTestNow() + { + return static::$testNow; + } + + /** + * Determine if there is a valid test instance set. A valid test instance + * is anything that is not null. + * + * @return bool true if there is a test instance, otherwise false + */ + public static function hasTestNow() + { + return static::getTestNow() !== null; + } + + /** + * Get the mocked date passed in setTestNow() and if it's a Closure, execute it. + * + * @param string|\DateTimeZone $tz + * + * @return \Carbon\CarbonImmutable|\Carbon\Carbon|null + */ + protected static function getMockedTestNow($tz) + { + $testNow = static::getTestNow(); + + if ($testNow instanceof Closure) { + $realNow = new DateTimeImmutable('now'); + $testNow = $testNow(static::parse( + $realNow->format('Y-m-d H:i:s.u'), + $tz ?: $realNow->getTimezone() + )); + } + /* @var \Carbon\CarbonImmutable|\Carbon\Carbon|null $testNow */ + + return $testNow instanceof CarbonInterface + ? $testNow->avoidMutation()->tz($tz) + : $testNow; + } + + protected static function mockConstructorParameters(&$time, $tz) + { + /** @var \Carbon\CarbonImmutable|\Carbon\Carbon $testInstance */ + $testInstance = clone static::getMockedTestNow($tz); + + if (static::hasRelativeKeywords($time)) { + $testInstance = $testInstance->modify($time); + } + + $time = $testInstance instanceof self + ? $testInstance->rawFormat(static::MOCK_DATETIME_FORMAT) + : $testInstance->format(static::MOCK_DATETIME_FORMAT); + } + + private static function setDefaultTimezone($timezone, DateTimeInterface $date = null) + { + $previous = null; + $success = false; + + try { + $success = date_default_timezone_set($timezone); + } catch (Throwable $exception) { + $previous = $exception; + } + + if (!$success) { + $suggestion = @CarbonTimeZone::create($timezone)->toRegionName($date); + + throw new InvalidArgumentException( + "Timezone ID '$timezone' is invalid". + ($suggestion && $suggestion !== $timezone ? ", did you mean '$suggestion'?" : '.')."\n". + "It must be one of the IDs from DateTimeZone::listIdentifiers(),\n". + 'For the record, hours/minutes offset are relevant only for a particular moment, '. + 'but not as a default timezone.', + 0, + $previous + ); + } + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/Timestamp.php b/vendor/nesbot/carbon/src/Carbon/Traits/Timestamp.php new file mode 100644 index 0000000..2cb5c48 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/Timestamp.php @@ -0,0 +1,198 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +/** + * Trait Timestamp. + */ +trait Timestamp +{ + /** + * Create a Carbon instance from a timestamp and set the timezone (use default one if not specified). + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $timestamp + * @param \DateTimeZone|string|null $tz + * + * @return static + */ + public static function createFromTimestamp($timestamp, $tz = null) + { + return static::createFromTimestampUTC($timestamp)->setTimezone($tz); + } + + /** + * Create a Carbon instance from an timestamp keeping the timezone to UTC. + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $timestamp + * + * @return static + */ + public static function createFromTimestampUTC($timestamp) + { + [$integer, $decimal] = self::getIntegerAndDecimalParts($timestamp); + $delta = floor($decimal / static::MICROSECONDS_PER_SECOND); + $integer += $delta; + $decimal -= $delta * static::MICROSECONDS_PER_SECOND; + $decimal = str_pad((string) $decimal, 6, '0', STR_PAD_LEFT); + + return static::rawCreateFromFormat('U u', "$integer $decimal"); + } + + /** + * Create a Carbon instance from a timestamp in milliseconds. + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $timestamp + * + * @return static + */ + public static function createFromTimestampMsUTC($timestamp) + { + [$milliseconds, $microseconds] = self::getIntegerAndDecimalParts($timestamp, 3); + $sign = $milliseconds < 0 || $milliseconds === 0.0 && $microseconds < 0 ? -1 : 1; + $milliseconds = abs($milliseconds); + $microseconds = $sign * abs($microseconds) + static::MICROSECONDS_PER_MILLISECOND * ($milliseconds % static::MILLISECONDS_PER_SECOND); + $seconds = $sign * floor($milliseconds / static::MILLISECONDS_PER_SECOND); + $delta = floor($microseconds / static::MICROSECONDS_PER_SECOND); + $seconds += $delta; + $microseconds -= $delta * static::MICROSECONDS_PER_SECOND; + $microseconds = str_pad($microseconds, 6, '0', STR_PAD_LEFT); + + return static::rawCreateFromFormat('U u', "$seconds $microseconds"); + } + + /** + * Create a Carbon instance from a timestamp in milliseconds. + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $timestamp + * @param \DateTimeZone|string|null $tz + * + * @return static + */ + public static function createFromTimestampMs($timestamp, $tz = null) + { + return static::createFromTimestampMsUTC($timestamp) + ->setTimezone($tz); + } + + /** + * Set the instance's timestamp. + * + * Timestamp input can be given as int, float or a string containing one or more numbers. + * + * @param float|int|string $unixTimestamp + * + * @return static + */ + public function timestamp($unixTimestamp) + { + return $this->setTimestamp($unixTimestamp); + } + + /** + * Returns a timestamp rounded with the given precision (6 by default). + * + * @example getPreciseTimestamp() 1532087464437474 (microsecond maximum precision) + * @example getPreciseTimestamp(6) 1532087464437474 + * @example getPreciseTimestamp(5) 153208746443747 (1/100000 second precision) + * @example getPreciseTimestamp(4) 15320874644375 (1/10000 second precision) + * @example getPreciseTimestamp(3) 1532087464437 (millisecond precision) + * @example getPreciseTimestamp(2) 153208746444 (1/100 second precision) + * @example getPreciseTimestamp(1) 15320874644 (1/10 second precision) + * @example getPreciseTimestamp(0) 1532087464 (second precision) + * @example getPreciseTimestamp(-1) 153208746 (10 second precision) + * @example getPreciseTimestamp(-2) 15320875 (100 second precision) + * + * @param int $precision + * + * @return float + */ + public function getPreciseTimestamp($precision = 6) + { + return round($this->rawFormat('Uu') / pow(10, 6 - $precision)); + } + + /** + * Returns the milliseconds timestamps used amongst other by Date javascript objects. + * + * @return float + */ + public function valueOf() + { + return $this->getPreciseTimestamp(3); + } + + /** + * Returns the timestamp with millisecond precision. + * + * @return int + */ + public function getTimestampMs() + { + return (int) $this->getPreciseTimestamp(3); + } + + /** + * @alias getTimestamp + * + * Returns the UNIX timestamp for the current date. + * + * @return int + */ + public function unix() + { + return $this->getTimestamp(); + } + + /** + * Return an array with integer part digits and decimals digits split from one or more positive numbers + * (such as timestamps) as string with the given number of decimals (6 by default). + * + * By splitting integer and decimal, this method obtain a better precision than + * number_format when the input is a string. + * + * @param float|int|string $numbers one or more numbers + * @param int $decimals number of decimals precision (6 by default) + * + * @return array 0-index is integer part, 1-index is decimal part digits + */ + private static function getIntegerAndDecimalParts($numbers, $decimals = 6) + { + if (\is_int($numbers) || \is_float($numbers)) { + $numbers = number_format($numbers, $decimals, '.', ''); + } + + $sign = str_starts_with($numbers, '-') ? -1 : 1; + $integer = 0; + $decimal = 0; + + foreach (preg_split('`[^0-9.]+`', $numbers) as $chunk) { + [$integerPart, $decimalPart] = explode('.', "$chunk."); + + $integer += (int) $integerPart; + $decimal += (float) ("0.$decimalPart"); + } + + $overflow = floor($decimal); + $integer += $overflow; + $decimal -= $overflow; + + return [$sign * $integer, $decimal === 0.0 ? 0.0 : $sign * round($decimal * pow(10, $decimals))]; + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/Units.php b/vendor/nesbot/carbon/src/Carbon/Traits/Units.php new file mode 100644 index 0000000..2902a8b --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/Units.php @@ -0,0 +1,406 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\CarbonConverterInterface; +use Carbon\CarbonInterface; +use Carbon\CarbonInterval; +use Carbon\Exceptions\UnitException; +use Closure; +use DateInterval; +use ReturnTypeWillChange; + +/** + * Trait Units. + * + * Add, subtract and set units. + */ +trait Units +{ + /** + * Add seconds to the instance using timestamp. Positive $value travels + * forward while negative $value travels into the past. + * + * @param string $unit + * @param int $value + * + * @return static + */ + public function addRealUnit($unit, $value = 1) + { + switch ($unit) { + // @call addRealUnit + case 'micro': + + // @call addRealUnit + case 'microsecond': + /* @var CarbonInterface $this */ + $diff = $this->microsecond + $value; + $time = $this->getTimestamp(); + $seconds = (int) floor($diff / static::MICROSECONDS_PER_SECOND); + $time += $seconds; + $diff -= $seconds * static::MICROSECONDS_PER_SECOND; + $microtime = str_pad((string) $diff, 6, '0', STR_PAD_LEFT); + $tz = $this->tz; + + return $this->tz('UTC')->modify("@$time.$microtime")->tz($tz); + + // @call addRealUnit + case 'milli': + // @call addRealUnit + case 'millisecond': + return $this->addRealUnit('microsecond', $value * static::MICROSECONDS_PER_MILLISECOND); + + break; + + // @call addRealUnit + case 'second': + break; + + // @call addRealUnit + case 'minute': + $value *= static::SECONDS_PER_MINUTE; + + break; + + // @call addRealUnit + case 'hour': + $value *= static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; + + break; + + // @call addRealUnit + case 'day': + $value *= static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; + + break; + + // @call addRealUnit + case 'week': + $value *= static::DAYS_PER_WEEK * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; + + break; + + // @call addRealUnit + case 'month': + $value *= 30 * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; + + break; + + // @call addRealUnit + case 'quarter': + $value *= static::MONTHS_PER_QUARTER * 30 * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; + + break; + + // @call addRealUnit + case 'year': + $value *= 365 * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; + + break; + + // @call addRealUnit + case 'decade': + $value *= static::YEARS_PER_DECADE * 365 * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; + + break; + + // @call addRealUnit + case 'century': + $value *= static::YEARS_PER_CENTURY * 365 * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; + + break; + + // @call addRealUnit + case 'millennium': + $value *= static::YEARS_PER_MILLENNIUM * 365 * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; + + break; + + default: + if ($this->localStrictModeEnabled ?? static::isStrictModeEnabled()) { + throw new UnitException("Invalid unit for real timestamp add/sub: '$unit'"); + } + + return $this; + } + + /* @var CarbonInterface $this */ + return $this->setTimestamp((int) ($this->getTimestamp() + $value)); + } + + public function subRealUnit($unit, $value = 1) + { + return $this->addRealUnit($unit, -$value); + } + + /** + * Returns true if a property can be changed via setter. + * + * @param string $unit + * + * @return bool + */ + public static function isModifiableUnit($unit) + { + static $modifiableUnits = [ + // @call addUnit + 'millennium', + // @call addUnit + 'century', + // @call addUnit + 'decade', + // @call addUnit + 'quarter', + // @call addUnit + 'week', + // @call addUnit + 'weekday', + ]; + + return \in_array($unit, $modifiableUnits) || \in_array($unit, static::$units); + } + + /** + * Call native PHP DateTime/DateTimeImmutable add() method. + * + * @param DateInterval $interval + * + * @return static + */ + public function rawAdd(DateInterval $interval) + { + return parent::add($interval); + } + + /** + * Add given units or interval to the current instance. + * + * @example $date->add('hour', 3) + * @example $date->add(15, 'days') + * @example $date->add(CarbonInterval::days(4)) + * + * @param string|DateInterval|Closure|CarbonConverterInterface $unit + * @param int $value + * @param bool|null $overflow + * + * @return static + */ + #[ReturnTypeWillChange] + public function add($unit, $value = 1, $overflow = null) + { + if (\is_string($unit) && \func_num_args() === 1) { + $unit = CarbonInterval::make($unit); + } + + if ($unit instanceof CarbonConverterInterface) { + return $this->resolveCarbon($unit->convertDate($this, false)); + } + + if ($unit instanceof Closure) { + return $this->resolveCarbon($unit($this, false)); + } + + if ($unit instanceof DateInterval) { + return parent::add($unit); + } + + if (is_numeric($unit)) { + [$value, $unit] = [$unit, $value]; + } + + return $this->addUnit($unit, $value, $overflow); + } + + /** + * Add given units to the current instance. + * + * @param string $unit + * @param int $value + * @param bool|null $overflow + * + * @return static + */ + public function addUnit($unit, $value = 1, $overflow = null) + { + $date = $this; + + if (!is_numeric($value) || !(float) $value) { + return $date->isMutable() ? $date : $date->avoidMutation(); + } + + $unit = self::singularUnit($unit); + $metaUnits = [ + 'millennium' => [static::YEARS_PER_MILLENNIUM, 'year'], + 'century' => [static::YEARS_PER_CENTURY, 'year'], + 'decade' => [static::YEARS_PER_DECADE, 'year'], + 'quarter' => [static::MONTHS_PER_QUARTER, 'month'], + ]; + + if (isset($metaUnits[$unit])) { + [$factor, $unit] = $metaUnits[$unit]; + $value *= $factor; + } + + if ($unit === 'weekday') { + $weekendDays = static::getWeekendDays(); + + if ($weekendDays !== [static::SATURDAY, static::SUNDAY]) { + $absoluteValue = abs($value); + $sign = $value / max(1, $absoluteValue); + $weekDaysCount = 7 - min(6, \count(array_unique($weekendDays))); + $weeks = floor($absoluteValue / $weekDaysCount); + + for ($diff = $absoluteValue % $weekDaysCount; $diff; $diff--) { + /** @var static $date */ + $date = $date->addDays($sign); + + while (\in_array($date->dayOfWeek, $weekendDays)) { + $date = $date->addDays($sign); + } + } + + $value = $weeks * $sign; + $unit = 'week'; + } + + $timeString = $date->toTimeString(); + } elseif ($canOverflow = \in_array($unit, [ + 'month', + 'year', + ]) && ($overflow === false || ( + $overflow === null && + ($ucUnit = ucfirst($unit).'s') && + !($this->{'local'.$ucUnit.'Overflow'} ?? static::{'shouldOverflow'.$ucUnit}()) + ))) { + $day = $date->day; + } + + $value = (int) $value; + + if ($unit === 'milli' || $unit === 'millisecond') { + $unit = 'microsecond'; + $value *= static::MICROSECONDS_PER_MILLISECOND; + } + + // Work-around for bug https://bugs.php.net/bug.php?id=75642 + if ($unit === 'micro' || $unit === 'microsecond') { + $microseconds = $this->micro + $value; + $second = (int) floor($microseconds / static::MICROSECONDS_PER_SECOND); + $microseconds %= static::MICROSECONDS_PER_SECOND; + if ($microseconds < 0) { + $microseconds += static::MICROSECONDS_PER_SECOND; + } + $date = $date->microseconds($microseconds); + $unit = 'second'; + $value = $second; + } + $date = $date->modify("$value $unit"); + + if (isset($timeString)) { + $date = $date->setTimeFromTimeString($timeString); + } elseif (isset($canOverflow, $day) && $canOverflow && $day !== $date->day) { + $date = $date->modify('last day of previous month'); + } + + if (!$date) { + throw new UnitException('Unable to add unit '.var_export(\func_get_args(), true)); + } + + return $date; + } + + /** + * Subtract given units to the current instance. + * + * @param string $unit + * @param int $value + * @param bool|null $overflow + * + * @return static + */ + public function subUnit($unit, $value = 1, $overflow = null) + { + return $this->addUnit($unit, -$value, $overflow); + } + + /** + * Call native PHP DateTime/DateTimeImmutable sub() method. + * + * @param DateInterval $interval + * + * @return static + */ + public function rawSub(DateInterval $interval) + { + return parent::sub($interval); + } + + /** + * Subtract given units or interval to the current instance. + * + * @example $date->sub('hour', 3) + * @example $date->sub(15, 'days') + * @example $date->sub(CarbonInterval::days(4)) + * + * @param string|DateInterval|Closure|CarbonConverterInterface $unit + * @param int $value + * @param bool|null $overflow + * + * @return static + */ + #[ReturnTypeWillChange] + public function sub($unit, $value = 1, $overflow = null) + { + if (\is_string($unit) && \func_num_args() === 1) { + $unit = CarbonInterval::make($unit); + } + + if ($unit instanceof CarbonConverterInterface) { + return $this->resolveCarbon($unit->convertDate($this, true)); + } + + if ($unit instanceof Closure) { + return $this->resolveCarbon($unit($this, true)); + } + + if ($unit instanceof DateInterval) { + return parent::sub($unit); + } + + if (is_numeric($unit)) { + [$value, $unit] = [$unit, $value]; + } + + return $this->addUnit($unit, -(float) $value, $overflow); + } + + /** + * Subtract given units or interval to the current instance. + * + * @see sub() + * + * @param string|DateInterval $unit + * @param int $value + * @param bool|null $overflow + * + * @return static + */ + public function subtract($unit, $value = 1, $overflow = null) + { + if (\is_string($unit) && \func_num_args() === 1) { + $unit = CarbonInterval::make($unit); + } + + return $this->sub($unit, $value, $overflow); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Traits/Week.php b/vendor/nesbot/carbon/src/Carbon/Traits/Week.php new file mode 100644 index 0000000..6f14814 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Traits/Week.php @@ -0,0 +1,219 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +/** + * Trait Week. + * + * week and ISO week number, year and count in year. + * + * Depends on the following properties: + * + * @property int $daysInYear + * @property int $dayOfWeek + * @property int $dayOfYear + * @property int $year + * + * Depends on the following methods: + * + * @method static addWeeks(int $weeks = 1) + * @method static copy() + * @method static dayOfYear(int $dayOfYear) + * @method string getTranslationMessage(string $key, ?string $locale = null, ?string $default = null, $translator = null) + * @method static next(int|string $day = null) + * @method static startOfWeek(int $day = 1) + * @method static subWeeks(int $weeks = 1) + * @method static year(int $year = null) + */ +trait Week +{ + /** + * Set/get the week number of year using given first day of week and first + * day of year included in the first week. Or use ISO format if no settings + * given. + * + * @param int|null $year if null, act as a getter, if not null, set the year and return current instance. + * @param int|null $dayOfWeek first date of week from 0 (Sunday) to 6 (Saturday) + * @param int|null $dayOfYear first day of year included in the week #1 + * + * @return int|static + */ + public function isoWeekYear($year = null, $dayOfWeek = null, $dayOfYear = null) + { + return $this->weekYear( + $year, + $dayOfWeek ?? 1, + $dayOfYear ?? 4 + ); + } + + /** + * Set/get the week number of year using given first day of week and first + * day of year included in the first week. Or use US format if no settings + * given (Sunday / Jan 6). + * + * @param int|null $year if null, act as a getter, if not null, set the year and return current instance. + * @param int|null $dayOfWeek first date of week from 0 (Sunday) to 6 (Saturday) + * @param int|null $dayOfYear first day of year included in the week #1 + * + * @return int|static + */ + public function weekYear($year = null, $dayOfWeek = null, $dayOfYear = null) + { + $dayOfWeek = $dayOfWeek ?? $this->getTranslationMessage('first_day_of_week') ?? 0; + $dayOfYear = $dayOfYear ?? $this->getTranslationMessage('day_of_first_week_of_year') ?? 1; + + if ($year !== null) { + $year = (int) round($year); + + if ($this->weekYear(null, $dayOfWeek, $dayOfYear) === $year) { + return $this->avoidMutation(); + } + + $week = $this->week(null, $dayOfWeek, $dayOfYear); + $day = $this->dayOfWeek; + $date = $this->year($year); + switch ($date->weekYear(null, $dayOfWeek, $dayOfYear) - $year) { + case 1: + $date = $date->subWeeks(26); + + break; + case -1: + $date = $date->addWeeks(26); + + break; + } + + $date = $date->addWeeks($week - $date->week(null, $dayOfWeek, $dayOfYear))->startOfWeek($dayOfWeek); + + if ($date->dayOfWeek === $day) { + return $date; + } + + return $date->next($day); + } + + $year = $this->year; + $day = $this->dayOfYear; + $date = $this->avoidMutation()->dayOfYear($dayOfYear)->startOfWeek($dayOfWeek); + + if ($date->year === $year && $day < $date->dayOfYear) { + return $year - 1; + } + + $date = $this->avoidMutation()->addYear()->dayOfYear($dayOfYear)->startOfWeek($dayOfWeek); + + if ($date->year === $year && $day >= $date->dayOfYear) { + return $year + 1; + } + + return $year; + } + + /** + * Get the number of weeks of the current week-year using given first day of week and first + * day of year included in the first week. Or use ISO format if no settings + * given. + * + * @param int|null $dayOfWeek first date of week from 0 (Sunday) to 6 (Saturday) + * @param int|null $dayOfYear first day of year included in the week #1 + * + * @return int + */ + public function isoWeeksInYear($dayOfWeek = null, $dayOfYear = null) + { + return $this->weeksInYear( + $dayOfWeek ?? 1, + $dayOfYear ?? 4 + ); + } + + /** + * Get the number of weeks of the current week-year using given first day of week and first + * day of year included in the first week. Or use US format if no settings + * given (Sunday / Jan 6). + * + * @param int|null $dayOfWeek first date of week from 0 (Sunday) to 6 (Saturday) + * @param int|null $dayOfYear first day of year included in the week #1 + * + * @return int + */ + public function weeksInYear($dayOfWeek = null, $dayOfYear = null) + { + $dayOfWeek = $dayOfWeek ?? $this->getTranslationMessage('first_day_of_week') ?? 0; + $dayOfYear = $dayOfYear ?? $this->getTranslationMessage('day_of_first_week_of_year') ?? 1; + $year = $this->year; + $start = $this->avoidMutation()->dayOfYear($dayOfYear)->startOfWeek($dayOfWeek); + $startDay = $start->dayOfYear; + if ($start->year !== $year) { + $startDay -= $start->daysInYear; + } + $end = $this->avoidMutation()->addYear()->dayOfYear($dayOfYear)->startOfWeek($dayOfWeek); + $endDay = $end->dayOfYear; + if ($end->year !== $year) { + $endDay += $this->daysInYear; + } + + return (int) round(($endDay - $startDay) / 7); + } + + /** + * Get/set the week number using given first day of week and first + * day of year included in the first week. Or use US format if no settings + * given (Sunday / Jan 6). + * + * @param int|null $week + * @param int|null $dayOfWeek + * @param int|null $dayOfYear + * + * @return int|static + */ + public function week($week = null, $dayOfWeek = null, $dayOfYear = null) + { + $date = $this; + $dayOfWeek = $dayOfWeek ?? $this->getTranslationMessage('first_day_of_week') ?? 0; + $dayOfYear = $dayOfYear ?? $this->getTranslationMessage('day_of_first_week_of_year') ?? 1; + + if ($week !== null) { + return $date->addWeeks(round($week) - $this->week(null, $dayOfWeek, $dayOfYear)); + } + + $start = $date->avoidMutation()->dayOfYear($dayOfYear)->startOfWeek($dayOfWeek); + $end = $date->avoidMutation()->startOfWeek($dayOfWeek); + if ($start > $end) { + $start = $start->subWeeks(26)->dayOfYear($dayOfYear)->startOfWeek($dayOfWeek); + } + $week = (int) ($start->diffInDays($end) / 7 + 1); + + return $week > $end->weeksInYear($dayOfWeek, $dayOfYear) ? 1 : $week; + } + + /** + * Get/set the week number using given first day of week and first + * day of year included in the first week. Or use ISO format if no settings + * given. + * + * @param int|null $week + * @param int|null $dayOfWeek + * @param int|null $dayOfYear + * + * @return int|static + */ + public function isoWeek($week = null, $dayOfWeek = null, $dayOfYear = null) + { + return $this->week( + $week, + $dayOfWeek ?? 1, + $dayOfYear ?? 4 + ); + } +} diff --git a/vendor/nesbot/carbon/src/Carbon/Translator.php b/vendor/nesbot/carbon/src/Carbon/Translator.php new file mode 100644 index 0000000..491c9e7 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Translator.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use ReflectionMethod; +use Symfony\Component\Translation; +use Symfony\Contracts\Translation\TranslatorInterface; + +$transMethod = new ReflectionMethod( + class_exists(TranslatorInterface::class) + ? TranslatorInterface::class + : Translation\Translator::class, + 'trans' +); + +require $transMethod->hasReturnType() + ? __DIR__.'/../../lazy/Carbon/TranslatorStrongType.php' + : __DIR__.'/../../lazy/Carbon/TranslatorWeakType.php'; + +class Translator extends LazyTranslator +{ + // Proxy dynamically loaded LazyTranslator in a static way +} diff --git a/vendor/nesbot/carbon/src/Carbon/TranslatorStrongTypeInterface.php b/vendor/nesbot/carbon/src/Carbon/TranslatorStrongTypeInterface.php new file mode 100644 index 0000000..ef4dee8 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/TranslatorStrongTypeInterface.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use Symfony\Component\Translation\MessageCatalogueInterface; + +/** + * Mark translator using strong type from symfony/translation >= 6. + */ +interface TranslatorStrongTypeInterface +{ + public function getFromCatalogue(MessageCatalogueInterface $catalogue, string $id, string $domain = 'messages'); +} diff --git a/vendor/overtrue/socialite/.github/FUNDING.yml b/vendor/overtrue/socialite/.github/FUNDING.yml new file mode 100644 index 0000000..5ea9ebf --- /dev/null +++ b/vendor/overtrue/socialite/.github/FUNDING.yml @@ -0,0 +1 @@ +github: [overtrue] diff --git a/vendor/overtrue/socialite/.github/dependabot.yml b/vendor/overtrue/socialite/.github/dependabot.yml new file mode 100644 index 0000000..a51bb0b --- /dev/null +++ b/vendor/overtrue/socialite/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "composer" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "daily" diff --git a/vendor/overtrue/socialite/.gitignore b/vendor/overtrue/socialite/.gitignore new file mode 100644 index 0000000..d6eb268 --- /dev/null +++ b/vendor/overtrue/socialite/.gitignore @@ -0,0 +1,9 @@ +/vendor +composer.phar +composer.lock +.DS_Store +/.idea +Thumbs.db +/*.php +sftp-config.json +.php_cs.cache \ No newline at end of file diff --git a/vendor/overtrue/socialite/.phpunit.result.cache b/vendor/overtrue/socialite/.phpunit.result.cache new file mode 100644 index 0000000..c9e9724 --- /dev/null +++ b/vendor/overtrue/socialite/.phpunit.result.cache @@ -0,0 +1 @@ +C:37:"PHPUnit\Runner\DefaultTestResultCache":1272:{a:2:{s:7:"defects";a:6:{s:50:"Providers\FeiShuTest::testProviderCanCreateCorrect";i:3;s:50:"Providers\FeiShuTest::testConfigAppAccessTokenWork";i:4;s:90:"Providers\FeiShuTest::testConfigAppAccessTokenWithDefaultModeAndAppTicketWorkInBadResponse";i:3;s:91:"Providers\FeiShuTest::testConfigAppAccessTokenWithDefaultModeAndAppTicketWorkInGoodResponse";i:4;s:71:"Providers\FeiShuTest::testConfigAppAccessTokenWithInternalInBadResponse";i:3;s:72:"Providers\FeiShuTest::testConfigAppAccessTokenWithInternalInGoodResponse";i:4;}s:5:"times";a:9:{s:50:"Providers\FeiShuTest::testProviderCanCreateCorrect";d:0.003;s:57:"Providers\FeiShuTest::testProviderWithInternalAppModeWork";d:0;s:51:"Providers\FeiShuTest::testProviderWithAppTicketWork";d:0;s:50:"Providers\FeiShuTest::testConfigAppAccessTokenWork";d:0.003;s:76:"Providers\FeiShuTest::testConfigAppAccessTokenWithDefaultModeNoAppTicketWork";d:0.003;s:90:"Providers\FeiShuTest::testConfigAppAccessTokenWithDefaultModeAndAppTicketWorkInBadResponse";d:0.002;s:91:"Providers\FeiShuTest::testConfigAppAccessTokenWithDefaultModeAndAppTicketWorkInGoodResponse";d:0;s:71:"Providers\FeiShuTest::testConfigAppAccessTokenWithInternalInBadResponse";d:0;s:72:"Providers\FeiShuTest::testConfigAppAccessTokenWithInternalInGoodResponse";d:0;}}} \ No newline at end of file diff --git a/vendor/overtrue/socialite/.travis.yml b/vendor/overtrue/socialite/.travis.yml new file mode 100644 index 0000000..39912f9 --- /dev/null +++ b/vendor/overtrue/socialite/.travis.yml @@ -0,0 +1,13 @@ +language: php + +php: + - 7.0 + - 7.1 + - 7.2 + +sudo: false +dist: trusty + +install: travis_retry composer install --no-interaction --prefer-source + +script: vendor/bin/phpunit --verbose diff --git a/vendor/overtrue/socialite/LICENSE.txt b/vendor/overtrue/socialite/LICENSE.txt new file mode 100644 index 0000000..c5fe984 --- /dev/null +++ b/vendor/overtrue/socialite/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) overtrue + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/overtrue/socialite/README.md b/vendor/overtrue/socialite/README.md new file mode 100644 index 0000000..62e5e81 --- /dev/null +++ b/vendor/overtrue/socialite/README.md @@ -0,0 +1,599 @@ +

        Socialite

        +

        +Latest Stable Version +Latest Unstable Version +Build Status +Total Downloads +License +

        + + +

        Socialite is an OAuth2 Authentication tool. It is inspired by laravel/socialite, You can easily use it in any PHP project. 中文文档

        + +

        This tool now supports platforms such as Facebook, GitHub, Google, LinkedIn, Outlook, QQ, Tapd, Alipay, Taobao, Baidu, DingTalk, Weibo, WeChat, Douyin, Feishu, Douban, WeWork, Tencent Cloud, Line, Gitee.

        + +[![Sponsor me](https://raw.githubusercontent.com/overtrue/overtrue/master/sponsor-me-button-s.svg)](https://github.com/sponsors/overtrue) + +如果你喜欢我的项目并想支持它,[点击这里 :heart:](https://github.com/sponsors/overtrue) + + +- [Requirement](#requirement) +- [Installation](#installation) +- [Usage](#usage) + - [Configuration](#configuration) + - [Custom app name](#custom-app-name) + - [Extends custom provider](#extends-custom-provider) + - [Platform](#platform) + - [Alipay](#alipay) + - [DingTalk](#dingtalk) + - [Douyin](#douyin) + - [Baidu](#baidu) + - [Feishu](#feishu) + - [Taobao](#taobao) + - [WeChat](#wechat) + - [Some Skill](#some-skill) + - [Scopes](#scopes) + - [Redirect URL](#redirect-url) + - [State](#state) + - [Redirect with `state` parameter](#redirect-with-state-parameter) + - [Validate the callback `state`](#validate-the-callback-state) + - [Additional parameters](#additional-parameters) + - [User interface](#user-interface) + - [Standard user api:](#standard-user-api) + - [Get raw response from OAuth API](#get-raw-response-from-oauth-api) + - [Get the token response when you use userFromCode()](#get-the-token-response-when-you-use-userfromcode) + - [Get user with access token](#get-user-with-access-token) +- [Enjoy it! :heart:](#enjoy-it-heart) +- [Reference](#reference) +- [PHP 扩展包开发](#php-扩展包开发) +- [License](#license) + +# Requirement + +``` +PHP >= 7.4 +``` +# Installation + +```shell +$ composer require "overtrue/socialite" -vvv +``` + +# Usage + +Users just need to create the corresponding configuration variables, then create the authentication application for each platform through the tool, and easily obtain the access_token and user information for that platform. The implementation logic of the tool is referred to OAuth2 documents of major platforms for details. + +The tool is used in the following steps: + +1. Configurate platform config +2. Use this tool to create a platform application +3. Let the user redirect to platform authentication +4. The server receives a Code callback from the platform, and uses the Code to exchange the user information on the platform (including access_token). + +Packages created for Laravel users are easier to integrate: [overtrue/laravel-socialite](https://github.com/overtrue/laravel-socialite) + +`authorize.php`: + +```php + [ + 'client_id' => 'your-app-id', + 'client_secret' => 'your-app-secret', + 'redirect' => 'http://localhost/socialite/callback.php', + ], +]; + +$socialite = new SocialiteManager($config); + +$url = $socialite->create('github')->redirect(); + +return redirect($url); +``` + +`callback.php`: + +```php + [ + 'client_id' => 'your-app-id', + 'client_secret' => 'your-app-secret', + 'redirect' => 'http://localhost/socialite/callback.php', + ], +]; + +$socialite = new SocialiteManager($config); + +$code = request()->query('code'); + +$user = $socialite->create('github')->userFromCode($code); + +$user->getId(); // 1472352 +$user->getNickname(); // "overtrue" +$user->getUsername(); // "overtrue" +$user->getName(); // "安正超" +$user->getEmail(); // "anzhengchao@gmail.com" +... +``` + +## Configuration + +Each create uses the same configuration keys: `client_id`, `client_secret`, `redirect`. + +Example: +```php +$config = [ + 'weibo' => [ + 'client_id' => 'your-app-id', + 'client_secret' => 'your-app-secret', + 'redirect' => 'http://localhost/socialite/callback.php', + ], + 'facebook' => [ + 'client_id' => 'your-app-id', + 'client_secret' => 'your-app-secret', + 'redirect' => 'http://localhost/socialite/callback.php', + ], +]; +``` + +### Custom app name + +You can use any name you like as the name of the application, such as `foo`, and set provider using `provider` key: + +```php +$config = [ + 'foo' => [ + 'provider' => 'github', // <-- provider name + 'client_id' => 'your-app-id', + 'client_secret' => 'your-app-secret', + 'redirect' => 'http://localhost/socialite/callback.php', + ], + + // another github app + 'bar' => [ + 'provider' => 'github', // <-- provider name + 'client_id' => 'your-app-id', + 'client_secret' => 'your-app-secret', + 'redirect' => 'http://localhost/socialite/callback.php', + ], + //... +]; +``` + +### Extends custom provider + +You can create application from you custom provider easily,you have to ways to do this: + +1. Using custom creator: + As shown in the following code, the service provider name is defined for the Foo application, but the tool itself does not support it, so use the creator `extend()` to create an instance of the service provider as a closure function. + +```php +$config = [ + 'foo' => [ + 'provider' => 'myprovider', // <-- provider name + 'client_id' => 'your-app-id', + 'client_secret' => 'your-app-secret', + 'redirect' => 'http://localhost/socialite/callback.php', + ], +]; + +$socialite = new SocialiteManager($config); + +$socialite->extend('myprovider', function(array $config) { + return new MyCustomProvider($config); +}); + +$app = $socialite->create('foo'); +``` + +2. Using provider: + +>👋🏻 Your custom provider class must be implements of `Overtrue\Socialite\Contracts\ProviderInterface`. + +```php +class MyCustomProvider implements \Overtrue\Socialite\Contracts\ProviderInterface +{ + //... +} +``` + +then set `provider` with the class name: + +```php +$config = [ + 'foo' => [ + 'provider' => MyCustomProvider::class, // <-- class name + 'client_id' => 'your-app-id', + 'client_secret' => 'your-app-secret', + 'redirect' => 'http://localhost/socialite/callback.php', + ], +]; + +$socialite = new SocialiteManager($config); +$app = $socialite->create('foo'); +``` + + + +## Platform + +Different platforms have different configuration methods, so please check the platform Settings you are using. + +### [Alipay](https://opendocs.alipay.com/open/200/105310#s2) + +You must have the following configuration. +```php +$config = [ + 'alipay' => [ + // This can also be named as 'app_id' like the official documentation. + 'client_id' => 'your-app-id', + + // Please refer to the official documentation, in the official management background configuration RSA2. + // Note: This is your own private key. + // Note: Do not allow the private key content to have extra characters. + // Recommendation: For security, you can read directly from the file. But here as long as the value, please remember to remove the head and tail of the decoration. + 'rsa_private_key' => 'your-rsa-private-key', + + // Be sure to set this value and make sure that it is the same address value as set in the official admin system. + // This can also be named as 'redirect_url' like the official documentation. + 'redirect' => 'http://localhost/socialite/callback.php', + ] + ... +]; + +$socialite = new SocialiteManager($config); + +$user = $socialite->create('alipay')->userFromCode('here is auth code'); + +// See this documents "User interface" +$user->getId(); // 1472352 +$user->getNickname(); // "overtrue" +$user->getUsername(); // "overtrue" +$user->getName(); // "安正超" +... +``` +Only RSA2 personal private keys are supported, so stay tuned if you want to log in with a certificate. + +### [DingTalk](https://ding-doc.dingtalk.com/doc#/serverapi3/mrugr3) + +Follow the documentation and configure it like following. + +> Note: It only supported QR code access to third-part websites. i.e exchange for user information(opendid, unionid and nickname) + +```php +$config = [ + 'dingtalk' => [ + // or 'app_id' + 'client_id' => 'your app id', + + // or 'app_secret' + 'client_secret' => 'your app secret', + + // or 'redirect_url' + 'redirect' => 'redirect URL' + ] +]; + +$socialite = new SocialiteManager($config); + +$user = $socialite->create('dingtalk')->userFromCode('here is auth code'); + +// See this documents "User interface" +$user->getId(); // 1472352 +$user->getNickname(); // "overtrue" +$user->getUsername(); // "overtrue" +$user->getName(); // "安正超" +... +``` + +### [Douyin](https://open.douyin.com/platform/doc/OpenAPI-oauth2) + +> Note: using the Douyin create that if you get user information directly using access token, set up the openid first. the openid can be obtained by code when access is obtained, so call `userFromCode()` automatically configured for you openid, if call `userFromToken()` first call `withOpenId()` + +```php +$config = [ + 'douyin' => [ + 'client_id' => 'your app id', + + 'client_secret' => 'your app secret', + + 'redirect' => 'redirect URL' + ] +]; + +$socialite = new SocialiteManager($config); + +$user = $socialite->create('douyin')->userFromCode('here is auth code'); + +$user = $socialite->create('douyin')->withOpenId('openId')->userFromToken('here is the access token'); +``` + + +### [Baidu](https://developer.baidu.com/wiki/index.php?title=docs/oauth) + +You can choose the form you want display by using `withDisplay()`. + +- **page** +- **popup** +- **dialog** +- **mobile** +- **tv** +- **pad** + +```php +$authUrl = $socialite->create('baidu')->withDisplay('mobile')->redirect(); + +``` +`popup` mode is the default setting with display. `basic` is the default with scopes. + +### [Feishu](https://open.feishu.cn/document/ukTMukTMukTM/uITNz4iM1MjLyUzM) + +Some simple way to use by internal app mode and config app_ticket. + +```php +$config = [ + 'feishu' => [ + // or 'app_id' + 'client_id' => 'your app id', + + // or 'app_secret' + 'client_secret' => 'your app secret', + + // or 'redirect_url' + 'redirect' => 'redirect URL', + + // if you want to use internal way to get app_access_token + // set this key by 'internal' then you already turn on the internal app mode + 'app_mode' => 'internal' + ] +]; + +$socialite = new SocialiteManager($config); + +$feishuDriver = $socialite->create('feishu'); + +$feishuDriver->withInternalAppMode()->userFromCode('here is code'); +$feishuDriver->withDefaultMode()->withAppTicket('app_ticket')->userFromCode('here is code'); +``` + +### [Taobao](https://open.taobao.com/doc.htm?docId=102635&docType=1&source=search) + +You can choose the form you want display by using `withView()`. + +```php +$authUrl = $socialite->create('taobao')->withView('wap')->redirect(); +``` +`web` mode is the default setting with display. `user_info` is the default with scopes. + +### [WeChat](https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/Official_Accounts/official_account_website_authorization.html) + +We support Open Platform Third-party Platform webpage authorizations on behalf of Official Account. + +You just need input your config like below config. Official Accounts authorizations only doesn't need. +```php +... +[ + 'wechat' => + [ + 'client_id' => 'client_id', + 'client_secret' => 'client_secret', + 'redirect' => 'redirect-url', + + // Open Platform - Third-party Platform Need + 'component' => [ + 'id' => 'component-app-id', + 'token' => 'component-access-token', // or Using a callable as value. + ] + ] +], +... +``` + +## Some Skill + +### Scopes + +Before redirecting the user, you may also set "scopes" on the request using the `scopes()` method. This method will overwrite all existing scopes: + +```php +$response = $socialite->create('github') + ->scopes(['scope1', 'scope2'])->redirect(); +``` + +### Redirect URL + +You may also want to dynamically set `redirect_uri`,you can use the following methods to change the `redirect_uri` URL: + +```php +$url = 'your callback url.'; + +$socialite->redirect($url); +// or +$socialite->withRedirectUrl($url)->redirect(); +``` + +### State + +Your app can use a state parameter for making sure the response belongs to a request initiated by the same user, therefore preventing cross-site request forgery (CSFR) attacks. A CSFR attack occurs when a malicious attacker tricks the user into performing unwanted actions that only the user is authorized to perform on a trusted web application, and all will be done without involving or alerting the user. + +Here's the simplest example of how providing the state can make your app more secure. in this example, we use the session ID as the state parameter, but you can use whatever logic you want to create value for the state. + +### Redirect with `state` parameter +```php +create('github')->withState($state)->redirect(); + +return redirect($url); +``` + +### Validate the callback `state` + +Once the user has authorized your app, the user will be redirected back to your app's redirect_uri. The OAuth server will return the state parameter unchanged. Check if the state provided in the redirect_uri matches the state generated by your app: + +```php +query('state'); +$code = request()->query('code'); + +// Check the state received with current session id +if ($state != hash('sha256', session_id())) { + exit('State does not match!'); +} +$user = $socialite->create('github')->userFromCode($code); + +// authorized +``` + +[Read more about `state` parameter](https://auth0.com/docs/protocols/oauth2/oauth-state) + +### Additional parameters + +To include any optional parameters in the request, call the `with()` method with an associative array: + +```php +$response = $socialite->create('google') + ->with(['hd' => 'example.com'])->redirect(); +``` + + +## User interface + +### Standard user api: + +```php +$user = $socialite->create('github')->userFromCode($code); +``` + +```json +{ + "id": 1472352, + "nickname": "overtrue", + "name": "安正超", + "email": "anzhengchao@gmail.com", + "avatar": "https://avatars.githubusercontent.com/u/1472352?v=3", + "raw": { + "login": "overtrue", + "id": 1472352, + "avatar_url": "https://avatars.githubusercontent.com/u/1472352?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/overtrue", + "html_url": "https://github.com/overtrue", + ... + }, + "token_response": { + "access_token": "5b1dc56d64fffbd052359f032716cc4e0a1cb9a0", + "token_type": "bearer", + "scope": "user:email" + } +} +``` + +You can fetch the user attribute as a array keys like these: + +```php +$user['id']; // 1472352 +$user['nickname']; // "overtrue" +$user['name']; // "安正超" +$user['email']; // "anzhengchao@gmail.com" +... +``` + +Or using the method: + +```php +mixed $user->getId(); +?string $user->getNickname(); +?string $user->getName(); +?string $user->getEmail(); +?string $user->getAvatar(); +?string $user->getRaw(); +?string $user->getAccessToken(); +?string $user->getRefreshToken(); +?int $user->getExpiresIn(); +?array $user->getTokenResponse(); + + +``` + +### Get raw response from OAuth API + +The `$user->getRaw()` method will return an **array** of the API raw response. + +### Get the token response when you use userFromCode() + +The `$user->getTokenResponse()` method will return an **array** of the get token(access token) API response. + +> Note: This method only return a **valid array** when you use `userFromCode()`, else will return **null** because use `userFromToken()` have no token response. + +### Get user with access token + +```php +$accessToken = 'xxxxxxxxxxx'; +$user = $socialite->userFromToken($accessToken); +``` + + + +# Enjoy it! :heart: + +# Reference + +- [Alipay - 用户信息授权](https://opendocs.alipay.com/open/289/105656) +- [DingTalk - 扫码登录第三方网站](https://ding-doc.dingtalk.com/doc#/serverapi3/mrugr3) +- [Google - OpenID Connect](https://developers.google.com/identity/protocols/OpenIDConnect) +- [Github - Authorizing OAuth Apps](https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/) +- [Facebook - Graph API](https://developers.facebook.com/docs/graph-api) +- [Linkedin - Authenticating with OAuth 2.0](https://developer.linkedin.com/docs/oauth2) +- [微博 - OAuth 2.0 授权机制说明](http://open.weibo.com/wiki/%E6%8E%88%E6%9D%83%E6%9C%BA%E5%88%B6%E8%AF%B4%E6%98%8E) +- [QQ - OAuth 2.0 登录 QQ](http://wiki.connect.qq.com/oauth2-0%E7%AE%80%E4%BB%8B) +- [腾讯云 - OAuth2.0](https://cloud.tencent.com/document/product/306/37730#.E6.8E.A5.E5.85.A5.E8.85.BE.E8.AE.AF.E4.BA.91-oauth) +- [微信公众平台 - OAuth 文档](http://mp.weixin.qq.com/wiki/9/01f711493b5a02f24b04365ac5d8fd95.html) +- [微信开放平台 - 网站应用微信登录开发指南](https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316505&token=&lang=zh_CN) +- [微信开放平台 - 代公众号发起网页授权](https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419318590&token=&lang=zh_CN) +- [企业微信 - OAuth 文档](https://open.work.weixin.qq.com/api/doc/90000/90135/91020) +- [企业微信第三方应用 - OAuth 文档](https://open.work.weixin.qq.com/api/doc/90001/90143/91118) +- [豆瓣 - OAuth 2.0 授权机制说明](http://developers.douban.com/wiki/?title=oauth2) +- [抖音 - 网站应用开发指南](http://open.douyin.com/platform/doc) +- [飞书 - 授权说明](https://open.feishu.cn/document/ukTMukTMukTM/uMTNz4yM1MjLzUzM) +- [Tapd - 用户授权说明](https://www.tapd.cn/help/show#1120003271001000093) +- [Line - OAuth 2.0](https://developers.line.biz/en/docs/line-login/integrate-line-login/) +- [Gitee - OAuth文档](https://gitee.com/api/v5/oauth_doc#/) + +[![Sponsor me](https://raw.githubusercontent.com/overtrue/overtrue/master/sponsor-me.svg)](https://github.com/sponsors/overtrue) + +## Project supported by JetBrains + +Many thanks to Jetbrains for kindly providing a license for me to work on this and other open-source projects. + +[![](https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg)](https://www.jetbrains.com/?from=https://github.com/overtrue) + + +# PHP 扩展包开发 + +> 想知道如何从零开始构建 PHP 扩展包? +> +> 请关注我的实战课程,我会在此课程中分享一些扩展开发经验 —— [《PHP 扩展包实战教程 - 从入门到发布》](https://learnku.com/courses/creating-package) + +# License + +MIT diff --git a/vendor/overtrue/socialite/README_CN.md b/vendor/overtrue/socialite/README_CN.md new file mode 100644 index 0000000..8456e92 --- /dev/null +++ b/vendor/overtrue/socialite/README_CN.md @@ -0,0 +1,610 @@ +

        Socialite

        +

        +Latest Stable Version +Latest Unstable Version +Build Status +Scrutinizer Code Quality +Code Coverage +Total Downloads +License +

        + + +

        Socialite 是一个 OAuth2 认证工具。 它的灵感来源于 laravel/socialite, 你可以很轻易的在任何 PHP 项目中使用它。

        + +

        该工具现已支持平台有:Facebook,Github,Google,Linkedin,Outlook,QQ,TAPD,支付宝,淘宝,百度,钉钉,微博,微信,抖音,飞书,豆瓣,企业微信,腾讯云,Line,Gitee。

        + +- [版本要求](#版本要求) +- [安装](#安装) +- [使用指南](#使用指南) + - [配置](#配置) + - [自定义应用名](#自定义应用名) + - [扩展自定义服务提供程序](#扩展自定义服务提供程序) + - [平台](#平台) + - [支付宝](#支付宝) + - [钉钉](#钉钉) + - [抖音](#抖音) + - [百度](#百度) + - [飞书](#飞书) + - [淘宝](#淘宝) + - [微信](#微信) + - [其他一些技巧](#其他一些技巧) + - [Scopes](#scopes) + - [Redirect URL](#redirect-url) + - [State](#state) + - [带着 `state` 参数的重定向](#带着-state-参数的重定向) + - [检验回调的 `state`](#检验回调的-state) + - [其他的一些参数](#其他的一些参数) + - [User interface](#user-interface) + - [标准的 user api:](#标准的-user-api) + - [从 OAuth API 响应中取得原始数据](#从-oauth-api-响应中取得原始数据) + - [当你使用 userFromCode() 想要获取 token 响应的原始数据](#当你使用-userfromcode-想要获取-token-响应的原始数据) + - [通过 access token 获取用户信息](#通过-access-token-获取用户信息) +- [Enjoy it! :heart:](#enjoy-it-heart) +- [参照](#参照) +- [PHP 扩展包开发](#php-扩展包开发) +- [License](#license) + +# 版本要求 + +``` +PHP >= 7.4 +``` + +# 安装 + +```shell +$ composer require "overtrue/socialite" -vvv +``` + +# 使用指南 + +用户只需要创建相应配置变量,然后通过工具为各个平台创建认证应用,并轻松获取该平台的 access_token 和用户相关信息。工具实现逻辑详见参照各大平台 OAuth2 文档。 + +工具使用大致分为以下几步: + +1. 配置平台设置 +2. 创建对应平台应用 +3. 让用户跳转至平台认证 +4. 服务器收到平台回调 Code,使用 Code 换取平台处用户信息(包括 access_token) + +为 Laravel 用户创建的更方便的整合的包: [overtrue/laravel-socialite](https://github.com/overtrue/laravel-socialite) + +`authorize.php`: 让用户跳转至平台认证 + +```php + [ + 'client_id' => 'your-app-id', + 'client_secret' => 'your-app-secret', + 'redirect' => 'http://localhost/socialite/callback.php', + ], +]; + +$socialite = new SocialiteManager($config); + +$url = $socialite->create('github')->redirect(); + +return redirect($url); +``` + +`callback.php`: + +```php + [ + 'client_id' => 'your-app-id', + 'client_secret' => 'your-app-secret', + 'redirect' => 'http://localhost/socialite/callback.php', + ], +]; + +$socialite = new SocialiteManager($config); + +$code = request()->query('code'); + +$user = $socialite->create('github')->userFromCode($code); + +$user->getId(); // 1472352 +$user->getNickname(); // "overtrue" +$user->getUsername(); // "overtrue" +$user->getName(); // "安正超" +$user->getEmail(); // "anzhengchao@gmail.com" +... +``` + +## 配置 + +为每个平台设置相同的键值对后就能开箱即用:`client_id`, `client_secret`, `redirect`. + +示例: + +```php +$config = [ + 'weibo' => [ + 'client_id' => 'your-app-id', + 'client_secret' => 'your-app-secret', + 'redirect' => 'http://localhost/socialite/callback.php', + ], + 'facebook' => [ + 'client_id' => 'your-app-id', + 'client_secret' => 'your-app-secret', + 'redirect' => 'http://localhost/socialite/callback.php', + ], +]; +``` + +### 自定义应用名 + +你可以使用任意你喜欢的名字对每个平台进行命名,比如说 `foo`, 采用别名的方法后需要在配置中多设置一个 `provider` 键,这样才能告诉工具包如何正确找到你想要的程序: + +```php +$config = [ + // 为 github 应用起别名为 foo + 'foo' => [ + 'provider' => 'github', // <-- provider name + 'client_id' => 'your-app-id', + 'client_secret' => 'your-app-secret', + 'redirect' => 'http://localhost/socialite/callback.php', + ], + + // 另外一个名字叫做 bar 的 github 应用 + 'bar' => [ + 'provider' => 'github', // <-- provider name + 'client_id' => 'your-app-id', + 'client_secret' => 'your-app-secret', + 'redirect' => 'http://localhost/socialite/callback.php', + ], + + //... +]; + +$socialite = new SocialiteManager($config); + +$appFoo = $socialite->create('foo'); +$appBar = $socialite->create('bar'); +``` + +### 扩展自定义服务提供程序 + +你可以很容易的从自定义的服务提供中创建应用,只需要遵循如下两点: + +1. 使用自定义创建器 + + 如下代码所示,为 foo 应用定义了服务提供名,但是工具本身还未支持,所以使用创建器 `extend()`,以闭包函数的形式为该服务提供创建一个实例。 + +```php +$config = [ + 'foo' => [ + 'provider' => 'myprovider', // <-- 一个工具还未支持的服务提供程序 + 'client_id' => 'your-app-id', + 'client_secret' => 'your-app-secret', + 'redirect' => 'http://localhost/socialite/callback.php', + ], +]; + +$socialite = new SocialiteManager($config); + +$socialite->extend('myprovider', function(array $config) { + return new MyCustomProvider($config); +}); + +$app = $socialite->create('foo'); +``` + +2. 使用服务提供类 + +>👋🏻 你的自定义服务提供类必须实现`Overtrue\Socialite\Contracts\ProviderInterface` 接口 + +```php +class MyCustomProvider implements \Overtrue\Socialite\Contracts\ProviderInterface +{ + //... +} +``` + +接下来为 `provider` 设置该类名让工具可以找到该类并实例化: + +```php +$config = [ + 'foo' => [ + 'provider' => MyCustomProvider::class, // <-- 类名 + 'client_id' => 'your-app-id', + 'client_secret' => 'your-app-secret', + 'redirect' => 'http://localhost/socialite/callback.php', + ], +]; + +$socialite = new SocialiteManager($config); +$app = $socialite->create('foo'); +``` + + + +## 平台 + +不同的平台有不同的配置方法,为了确保工具的正常运行,所以请确保你所使用的平台的配置都是如期设置的。 + +### [支付宝](https://opendocs.alipay.com/open/200/105310#s2) + +请按如下方式配置 + +```php +$config = [ + 'alipay' => [ + // 这个键名还能像官方文档那样叫做 'app_id' + 'client_id' => 'your-app-id', + + // 请根据官方文档,在官方管理后台配置 RSA2 + // 注意: 这是你自己的私钥 + // 注意: 不允许私钥内容有其他字符 + // 建议: 为了保证安全,你可以将文本信息从磁盘文件中读取,而不是在这里明文 + 'rsa_private_key' => 'your-rsa-private-key', + + // 确保这里的值与你在服务后台绑定的地址值一致 + // 这个键名还能像官方文档那样叫做 'redirect_url' + 'redirect' => 'http://localhost/socialite/callback.php', + + // 沙箱模式接入地址见 https://opendocs.alipay.com/open/220/105337#%E5%85%B3%E4%BA%8E%E6%B2%99%E7%AE%B1 + 'sandbox' => false, + ] + ... +]; + +$socialite = new SocialiteManager($config); + +$user = $socialite->create('alipay')->userFromCode('here is auth code'); + +// 详见文档后面 "User interface" +$user->getId(); // 1472352 +$user->getNickname(); // "overtrue" +$user->getUsername(); // "overtrue" +$user->getName(); // "安正超" +... +``` + +本工具暂时只支持 RSA2 个人私钥认证方式。 + +### [钉钉](https://ding-doc.dingtalk.com/doc#/serverapi3/mrugr3) + +如文档所示 + +> 注意:该工具仅支持 QR code 连接到第三方网站,用来获取用户信息(opeid, unionid 和 nickname) + +```php +$config = [ + 'dingtalk' => [ + // or 'app_id' + 'client_id' => 'your app id', + + // or 'app_secret' + 'client_secret' => 'your app secret', + + // or 'redirect_url' + 'redirect' => 'redirect URL' + ] +]; + +$socialite = new SocialiteManager($config); + +$user = $socialite->create('dingtalk')->userFromCode('here is auth code'); + +// 详见文档后面 "User interface" +$user->getId(); // 1472352 +$user->getNickname(); // "overtrue" +$user->getUsername(); // "overtrue" +$user->getName(); // "安正超" +... +``` + +### [抖音](https://open.douyin.com/platform/doc/OpenAPI-oauth2) + +> 注意: 使用抖音服务提供的时候,如果你想直接使用 access_token 获取用户信息时,请先设置 openid。 先调用 `withOpenId()` 再调用 `userFromToken()` + +```php +$config = [ + 'douyin' => [ + 'client_id' => 'your app id', + + 'client_secret' => 'your app secret', + + 'redirect' => 'redirect URL' + ] +]; + +$socialite = new SocialiteManager($config); + +$user = $socialite->create('douyin')->userFromCode('here is auth code'); + +$user = $socialite->create('douyin')->withOpenId('openId')->userFromToken('here is the access token'); +``` + + +### [百度](https://developer.baidu.com/wiki/index.php?title=docs/oauth) + +其他配置没啥区别,在用法上,可以很轻易的选择重定向登录页面的模式,通过 `withDisplay()` + +- **page:**全屏形式的授权页面 (默认),适用于 web 应用。 +- **popup:** 弹框形式的授权页面,适用于桌面软件应用和 web 应用。 +- **dialog:** 浮层形式的授权页面,只能用于站内 web 应用。 +- **mobile:** Iphone/Android 等智能移动终端上用的授权页面,适用于 Iphone/Android 等智能移动终端上的应用。 +- **tv:** 电视等超大显示屏使用的授权页面。 +- **pad:** IPad/Android 等智能平板电脑使用的授权页面。 + +```php +$authUrl = $socialite->create('baidu')->withDisplay('mobile')->redirect(); + +``` + +`popup` 模式是工具内默认的使用模式。`basic` 是默认使用的 scopes 值。 + +### [飞书](https://open.feishu.cn/document/ukTMukTMukTM/uITNz4iM1MjLyUzM) + +通过一些简单的方法配置 app_ticket 就能使用内部应用模式 + +```php +$config = [ + 'feishu' => [ + // or 'app_id' + 'client_id' => 'your app id', + + // or 'app_secret' + 'client_secret' => 'your app secret', + + // or 'redirect_url' + 'redirect' => 'redirect URL', + + // 如果你想使用使用内部应用的方式获取 app_access_token + // 对这个键设置了 'internal' 值那么你已经开启了内部应用模式 + 'app_mode' => 'internal' + ] +]; + +$socialite = new SocialiteManager($config); + +$feishuDriver = $socialite->create('feishu'); + +$feishuDriver->withInternalAppMode()->userFromCode('here is code'); +$feishuDriver->withDefaultMode()->withAppTicket('app_ticket')->userFromCode('here is code'); +``` + +### [淘宝](https://open.taobao.com/doc.htm?docId=102635&docType=1&source=search) + +其他配置与其他平台的一样,你能选择你想要展示的重定向页面类型通过使用 `withView()` + +```php +$authUrl = $socialite->create('taobao')->withView('wap')->redirect(); +``` + +`web` 模式是工具默认使用的展示方式, `user_info` 是默认使用的 scopes 范围值。 + +### [微信](https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/Official_Accounts/official_account_website_authorization.html) + +我们支持开放平台代表公众号进行第三方平台网页授权。 + +你只需要像下面这样输入你的配置。官方账号不需要授权。 + +```php +... +[ + 'wechat' => + [ + 'client_id' => 'client_id', + 'client_secret' => 'client_secret', + 'redirect' => 'redirect-url', + + // 开放平台 - 第三方平台所需 + 'component' => [ + // or 'app_id', 'component_app_id' as key + 'id' => 'component-app-id', + // or 'app_token', 'access_token', 'component_access_token' as key + 'token' => 'component-access-token', + ] + ] +], +... +``` + +## 其他一些技巧 + +### Scopes + +在重定向用户之前,您还可以使用 `scopes()` 方法在请求上设置 “范围”。此方法将覆盖所有现有的作用域: + +```php +$response = $socialite->create('github') + ->scopes(['scope1', 'scope2'])->redirect(); +``` + +### Redirect URL + +你也可以动态设置' redirect_uri ',你可以使用以下方法来改变 `redirect_uri` URL: + +```php +$url = 'your callback url.'; + +$socialite->redirect($url); +// or +$socialite->withRedirectUrl($url)->redirect(); +``` + +### State + +你的应用程序可以使用一个状态参数来确保响应属于同一个用户发起的请求,从而防止跨站请求伪造 (CSFR) 攻击。当恶意攻击者欺骗用户执行不需要的操作 (只有用户有权在受信任的 web 应用程序上执行) 时,就会发生 CSFR 攻击,所有操作都将在不涉及或警告用户的情况下完成。 + +这里有一个最简单的例子,说明了如何提供状态可以让你的应用程序更安全。在本例中,我们使用会话 ID 作为状态参数,但是您可以使用您想要为状态创建值的任何逻辑。 + +### 带着 `state` 参数的重定向 + +```php +create('github')->withState($state)->redirect(); + +return redirect($url); +``` + +### 检验回调的 `state` + +一旦用户授权你的应用程序,用户将被重定向回你的应用程序的 redirect_uri。OAuth 服务器将不加修改地返回状态参数。检查 redirect_uri 中提供的状态是否与应用程序生成的状态相匹配: + +```php +query('state'); +$code = request()->query('code'); + +// Check the state received with current session id +if ($state != hash('sha256', session_id())) { + exit('State does not match!'); +} +$user = $socialite->create('github')->userFromCode($code); + +// authorized +``` + +[查看更多关于 `state` 参数的文档](https://auth0.com/docs/protocols/oauth2/oauth-state) + +### 其他的一些参数 + +要在请求中包含任何可选参数,调用 `with()` 方法传入一个你想要设置的关联数组: + +```php +$response = $socialite->create('google') + ->with(['hd' => 'example.com'])->redirect(); +``` + + +## User interface + +### 标准的 user api: + +```php +$user = $socialite->create('github')->userFromCode($code); +``` + +```json +{ + "id": 1472352, + "nickname": "overtrue", + "name": "安正超", + "email": "anzhengchao@gmail.com", + "avatar": "https://avatars.githubusercontent.com/u/1472352?v=3", + "raw": { + "login": "overtrue", + "id": 1472352, + "avatar_url": "https://avatars.githubusercontent.com/u/1472352?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/overtrue", + "html_url": "https://github.com/overtrue", + ... + }, + "token_response": { + "access_token": "5b1dc56d64fffbd052359f032716cc4e0a1cb9a0", + "token_type": "bearer", + "scope": "user:email" + } +} +``` + +你可以像这样以数组键的形式获取 user 属性: + +```php +$user['id']; // 1472352 +$user['nickname']; // "overtrue" +$user['name']; // "安正超" +$user['email']; // "anzhengchao@gmail.com" +... +``` + +或者使用该 `User` 对象的方法: + +```php +mixed $user->getId(); +?string $user->getNickname(); +?string $user->getName(); +?string $user->getEmail(); +?string $user->getAvatar(); +?string $user->getRaw(); +?string $user->getAccessToken(); +?string $user->getRefreshToken(); +?int $user->getExpiresIn(); +?array $user->getTokenResponse(); + + +``` + +### 从 OAuth API 响应中取得原始数据 + +`$user->getRaw()` 方法会返回一个 **array**。 + +### 当你使用 userFromCode() 想要获取 token 响应的原始数据 + +`$user->getTokenResponse()` 方法会返回一个 **array** 里面是响应从获取 token 时候 API 返回的响应。 + +> 注意:当你使用 `userFromCode()` 时,这个方法只返回一个 **有效的数组**,否则将返回 **null**,因为 `userFromToken() ` 没有 token 的 HTTP 响应。 + +### 通过 access token 获取用户信息 + +```php +$accessToken = 'xxxxxxxxxxx'; +$user = $socialite->userFromToken($accessToken); +``` + + + +# Enjoy it! :heart: + +# 参照 + +- [Alipay - 用户信息授权](https://opendocs.alipay.com/open/289/105656) +- [DingTalk - 扫码登录第三方网站](https://ding-doc.dingtalk.com/doc#/serverapi3/mrugr3) +- [Google - OpenID Connect](https://developers.google.com/identity/protocols/OpenIDConnect) +- [Github - Authorizing OAuth Apps](https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/) +- [Facebook - Graph API](https://developers.facebook.com/docs/graph-api) +- [Linkedin - Authenticating with OAuth 2.0](https://developer.linkedin.com/docs/oauth2) +- [微博 - OAuth 2.0 授权机制说明](http://open.weibo.com/wiki/%E6%8E%88%E6%9D%83%E6%9C%BA%E5%88%B6%E8%AF%B4%E6%98%8E) +- [QQ - OAuth 2.0 登录 QQ](http://wiki.connect.qq.com/oauth2-0%E7%AE%80%E4%BB%8B) +- [腾讯云 - OAuth2.0](https://cloud.tencent.com/document/product/306/37730#.E6.8E.A5.E5.85.A5.E8.85.BE.E8.AE.AF.E4.BA.91-oauth) +- [微信公众平台 - OAuth 文档](http://mp.weixin.qq.com/wiki/9/01f711493b5a02f24b04365ac5d8fd95.html) +- [微信开放平台 - 网站应用微信登录开发指南](https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316505&token=&lang=zh_CN) +- [微信开放平台 - 代公众号发起网页授权](https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419318590&token=&lang=zh_CN) +- [企业微信 - OAuth 文档](https://open.work.weixin.qq.com/api/doc/90000/90135/91020) +- [企业微信第三方应用 - OAuth 文档](https://open.work.weixin.qq.com/api/doc/90001/90143/91118) +- [豆瓣 - OAuth 2.0 授权机制说明](http://developers.douban.com/wiki/?title=oauth2) +- [抖音 - 网站应用开发指南](http://open.douyin.com/platform/doc) +- [飞书 - 授权说明](https://open.feishu.cn/document/ukTMukTMukTM/uMTNz4yM1MjLzUzM) +- [Tapd - 用户授权说明](https://www.tapd.cn/help/show#1120003271001000093) +- [Line - OAuth 2.0](https://developers.line.biz/en/docs/line-login/integrate-line-login/) +- [Gitee - OAuth文档](https://gitee.com/api/v5/oauth_doc#/) + + + +# PHP 扩展包开发 + +> 想知道如何从零开始构建 PHP 扩展包? +> +> 请关注我的实战课程,我会在此课程中分享一些扩展开发经验 —— [《PHP 扩展包实战教程 - 从入门到发布》](https://learnku.com/courses/creating-package) + +# License + +MIT \ No newline at end of file diff --git a/vendor/overtrue/socialite/composer.json b/vendor/overtrue/socialite/composer.json new file mode 100644 index 0000000..abdc1fb --- /dev/null +++ b/vendor/overtrue/socialite/composer.json @@ -0,0 +1,44 @@ +{ + "name": "overtrue/socialite", + "description": "A collection of OAuth 2 packages.", + "keywords": [ + "oauth", + "social", + "login", + "weibo", + "wechat", + "qq", + "feishu", + "qcloud" + ], + "autoload": { + "psr-4": { + "Overtrue\\Socialite\\": "src/" + } + }, + "require": { + "php": ">=7.4", + "symfony/http-foundation": "^5.0", + "guzzlehttp/guzzle": "~6.0|~7.0", + "ext-json": "*", + "symfony/psr-http-message-bridge": "^2.0", + "ext-openssl": "*" + }, + "require-dev": { + "mockery/mockery": "~1.2", + "phpunit/phpunit": "~9.0", + "friendsofphp/php-cs-fixer": "^3.0" + }, + "license": "MIT", + "authors": [ + { + "name": "overtrue", + "email": "anzhengchao@gmail.com" + } + ], + "scripts": { + "check-style": "php-cs-fixer fix --using-cache=no --diff --dry-run --ansi", + "fix-style": "php-cs-fixer fix --using-cache=no --ansi", + "test": "vendor/bin/phpunit --colors=always" + } +} diff --git a/vendor/overtrue/socialite/phpunit.xml b/vendor/overtrue/socialite/phpunit.xml new file mode 100644 index 0000000..422eeac --- /dev/null +++ b/vendor/overtrue/socialite/phpunit.xml @@ -0,0 +1,17 @@ + + + + + ./tests/ + + + diff --git a/vendor/overtrue/socialite/src/Config.php b/vendor/overtrue/socialite/src/Config.php new file mode 100644 index 0000000..dd51ce7 --- /dev/null +++ b/vendor/overtrue/socialite/src/Config.php @@ -0,0 +1,118 @@ +config = $config; + } + + /** + * @param string $key + * @param mixed $default + * + * @return mixed + */ + public function get(string $key, $default = null) + { + $config = $this->config; + + if (is_null($key)) { + return $config; + } + + if (isset($config[$key])) { + return $config[$key]; + } + + foreach (explode('.', $key) as $segment) { + if (!is_array($config) || !array_key_exists($segment, $config)) { + return $default; + } + $config = $config[$segment]; + } + + return $config; + } + + /** + * @param string $key + * @param mixed $value + * + * @return array + */ + public function set(string $key, $value) + { + if (is_null($key)) { + throw new InvalidArgumentException('Invalid config key.'); + } + + $keys = explode('.', $key); + $config = &$this->config; + + while (count($keys) > 1) { + $key = array_shift($keys); + if (!isset($config[$key]) || !is_array($config[$key])) { + $config[$key] = []; + } + $config = &$config[$key]; + } + + $config[array_shift($keys)] = $value; + + return $config; + } + + /** + * @param string $key + * + * @return bool + */ + public function has(string $key): bool + { + return (bool) $this->get($key); + } + + public function offsetExists($offset) + { + return array_key_exists($offset, $this->config); + } + + public function offsetGet($offset) + { + return $this->get($offset); + } + + public function offsetSet($offset, $value) + { + $this->set($offset, $value); + } + + public function offsetUnset($offset) + { + $this->set($offset, null); + } + + public function jsonSerialize() + { + return $this->config; + } + + public function __toString() + { + return \json_encode($this, \JSON_UNESCAPED_UNICODE); + } +} diff --git a/vendor/overtrue/socialite/src/Contracts/FactoryInterface.php b/vendor/overtrue/socialite/src/Contracts/FactoryInterface.php new file mode 100644 index 0000000..943c11c --- /dev/null +++ b/vendor/overtrue/socialite/src/Contracts/FactoryInterface.php @@ -0,0 +1,13 @@ +body = (array) $body; + } +} diff --git a/vendor/overtrue/socialite/src/Exceptions/BadRequestException.php b/vendor/overtrue/socialite/src/Exceptions/BadRequestException.php new file mode 100644 index 0000000..05baa7e --- /dev/null +++ b/vendor/overtrue/socialite/src/Exceptions/BadRequestException.php @@ -0,0 +1,10 @@ +token = $token; + } +} diff --git a/vendor/overtrue/socialite/src/Exceptions/MethodDoesNotSupportException.php b/vendor/overtrue/socialite/src/Exceptions/MethodDoesNotSupportException.php new file mode 100644 index 0000000..397b290 --- /dev/null +++ b/vendor/overtrue/socialite/src/Exceptions/MethodDoesNotSupportException.php @@ -0,0 +1,8 @@ +sandbox = $this->config->get('sandbox', false); + if ($this->sandbox) { + $this->baseUrl = 'https://openapi.alipaydev.com/gateway.do'; + $this->authUrl = 'https://openauth.alipaydev.com/oauth2/publicAppAuthorize.htm'; + } + } + + protected function getAuthUrl(): string + { + return $this->buildAuthUrlFromBase($this->authUrl); + } + + protected function getTokenUrl(): string + { + return $this->baseUrl; + } + + /** + * @param string $token + * + * @return array + * @throws \Overtrue\Socialite\Exceptions\InvalidArgumentException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function getUserByToken(string $token): array + { + $params = $this->getPublicFields('alipay.user.info.share'); + $params += ['auth_token' => $token]; + $params['sign'] = $this->generateSign($params); + + $response = $this->getHttpClient()->post( + $this->baseUrl, + [ + 'form_params' => $params, + 'headers' => [ + "content-type" => "application/x-www-form-urlencoded;charset=utf-8", + ], + ] + ); + + $response = json_decode($response->getBody()->getContents(), true); + + if (!empty($response['error_response']) || empty($response['alipay_user_info_share_response'])) { + throw new \InvalidArgumentException('You have error! ' . \json_encode($response, JSON_UNESCAPED_UNICODE)); + } + + return $response['alipay_user_info_share_response']; + } + + /** + * @param array $user + * + * @return \Overtrue\Socialite\User + */ + protected function mapUserToObject(array $user): User + { + return new User( + [ + 'id' => $user['user_id'] ?? null, + 'name' => $user['nick_name'] ?? null, + 'avatar' => $user['avatar'] ?? null, + 'email' => $user['email'] ?? null, + ] + ); + } + + /** + * @param string $code + * + * @return array + * @throws \GuzzleHttp\Exception\GuzzleException + * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException + * @throws \Overtrue\Socialite\Exceptions\InvalidArgumentException + */ + public function tokenFromCode(string $code): array + { + $response = $this->getHttpClient()->post( + $this->getTokenUrl(), + [ + 'form_params' => $this->getTokenFields($code), + 'headers' => [ + "content-type" => "application/x-www-form-urlencoded;charset=utf-8", + ], + ] + ); + $response = json_decode($response->getBody()->getContents(), true); + + if (!empty($response['error_response'])) { + throw new \InvalidArgumentException('You have error! ' . json_encode($response, JSON_UNESCAPED_UNICODE)); + } + + return $this->normalizeAccessTokenResponse($response['alipay_system_oauth_token_response']); + } + + /** + * @return array + * @throws \Overtrue\Socialite\Exceptions\InvalidArgumentException + */ + protected function getCodeFields(): array + { + if (empty($this->redirectUrl)) { + throw new InvalidArgumentException('Please set same redirect URL like your Alipay Official Admin'); + } + + $fields = array_merge( + [ + 'app_id' => $this->getConfig()->get('client_id') ?? $this->getConfig()->get('app_id'), + 'scope' => implode(',', $this->scopes), + 'redirect_uri' => $this->redirectUrl, + ], + $this->parameters + ); + + return $fields; + } + + /** + * @param string $code + * + * @return array|string[] + * @throws \Overtrue\Socialite\Exceptions\InvalidArgumentException + */ + protected function getTokenFields(string $code): array + { + $params = $this->getPublicFields('alipay.system.oauth.token'); + $params += [ + 'code' => $code, + 'grant_type' => 'authorization_code', + ]; + $params['sign'] = $this->generateSign($params); + + return $params; + } + + /** + * @param string $method + * + * @return array + */ + public function getPublicFields(string $method): array + { + return [ + 'app_id' => $this->getConfig()->get('client_id') ?? $this->getConfig()->get('app_id'), + 'format' => $this->format, + 'charset' => $this->postCharset, + 'sign_type' => $this->signType, + 'method' => $method, + 'timestamp' => date('Y-m-d H:m:s'), + 'version' => $this->apiVersion, + ]; + } + + /** + * @param $params + * + * @return string + * @throws InvalidArgumentException + * + * @see https://opendocs.alipay.com/open/289/105656 + */ + protected function generateSign($params) + { + ksort($params); + + $signContent = $this->buildParams($params); + $key = $this->getConfig()->get('rsa_private_key'); + $signValue = $this->signWithSHA256RSA($signContent, $key); + + return $signValue; + } + + /** + * @param string $signContent + * @param string $key + * + * @return string + * @throws \Overtrue\Socialite\Exceptions\InvalidArgumentException + */ + protected function signWithSHA256RSA(string $signContent, string $key) + { + if (empty($key)) { + throw new InvalidArgumentException('no RSA private key set.'); + } + + $key = "-----BEGIN RSA PRIVATE KEY-----\n" . + chunk_split($key, 64, "\n") . + "-----END RSA PRIVATE KEY-----"; + + openssl_sign($signContent, $signValue, $key, OPENSSL_ALGO_SHA256); + + return base64_encode($signValue); + } + + /** + * @param array $params + * @param bool $urlencode + * @param array|string[] $except + * + * @return string + */ + public static function buildParams(array $params, bool $urlencode = false, array $except = ['sign']) + { + $param_str = ''; + foreach ($params as $k => $v) { + if (in_array($k, $except)) { + continue; + } + $param_str .= $k . '='; + $param_str .= $urlencode ? rawurlencode($v) : $v; + $param_str .= '&'; + } + + return rtrim($param_str, '&'); + } +} diff --git a/vendor/overtrue/socialite/src/Providers/Baidu.php b/vendor/overtrue/socialite/src/Providers/Baidu.php new file mode 100644 index 0000000..e8a9b2c --- /dev/null +++ b/vendor/overtrue/socialite/src/Providers/Baidu.php @@ -0,0 +1,119 @@ +display = $display; + + return $this; + } + + /** + * @param array $scopes + * + * @return self + */ + public function withScopes(array $scopes): self + { + $this->scopes = $scopes; + + return $this; + } + + /** + * @return string + */ + protected function getAuthUrl(): string + { + return $this->buildAuthUrlFromBase($this->baseUrl . '/oauth/' . $this->version . '/authorize'); + } + + protected function getCodeFields(): array + { + return [ + 'response_type' => 'code', + 'client_id' => $this->getClientId(), + 'redirect_uri' => $this->redirectUrl, + 'scope' => $this->formatScopes($this->scopes, $this->scopeSeparator), + 'display' => $this->display, + ] + $this->parameters; + } + + /** + * @return string + */ + protected function getTokenUrl(): string + { + return $this->baseUrl . '/oauth/' . $this->version . '/token'; + } + + /** + * @param string $code + * + * @return array + */ + protected function getTokenFields($code): array + { + return parent::getTokenFields($code) + ['grant_type' => 'authorization_code']; + } + + /** + * @param string $token + * + * @return array + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function getUserByToken(string $token): array + { + $response = $this->getHttpClient()->get( + $this->baseUrl . '/rest/' . $this->version . '/passport/users/getInfo', + [ + 'query' => [ + 'access_token' => $token, + ], + 'headers' => [ + 'Accept' => 'application/json', + ], + ] + ); + + return json_decode($response->getBody(), true) ?? []; + } + + /** + * @param array $user + * + * @return \Overtrue\Socialite\User + */ + protected function mapUserToObject(array $user): User + { + return new User( + [ + 'id' => $user['userid'] ?? null, + 'nickname' => $user['realname'] ?? null, + 'name' => $user['username'] ?? null, + 'email' => '', + 'avatar' => $user['portrait'] ? 'http://tb.himg.baidu.com/sys/portraitn/item/' . $user['portrait'] : null, + ] + ); + } +} diff --git a/vendor/overtrue/socialite/src/Providers/Base.php b/vendor/overtrue/socialite/src/Providers/Base.php new file mode 100644 index 0000000..e4bab50 --- /dev/null +++ b/vendor/overtrue/socialite/src/Providers/Base.php @@ -0,0 +1,333 @@ +config = new Config($config); + + // set scopes + if ($this->config->has('scopes') && is_array($this->config->get('scopes'))) { + $this->scopes = $this->getConfig()->get('scopes'); + } elseif ($this->config->has('scope') && is_string($this->getConfig()->get('scope'))) { + $this->scopes = array($this->getConfig()->get('scope')); + } + + // normalize 'client_id' + if (!$this->config->has('client_id')) { + $id = $this->config->get('app_id'); + if (null != $id) { + $this->config->set('client_id', $id); + } + } + + // normalize 'client_secret' + if (!$this->config->has('client_secret')) { + $secret = $this->config->get('app_secret'); + if (null != $secret) { + $this->config->set('client_secret', $secret); + } + } + + // normalize 'redirect_url' + if (!$this->config->has('redirect_url')) { + $this->config->set('redirect_url', $this->config->get('redirect')); + } + $this->redirectUrl = $this->config->get('redirect_url'); + } + + abstract protected function getAuthUrl(): string; + + abstract protected function getTokenUrl(): string; + + abstract protected function getUserByToken(string $token): array; + + abstract protected function mapUserToObject(array $user): User; + + /** + * @param string|null $redirectUrl + * + * @return string + */ + public function redirect(?string $redirectUrl = null): string + { + if (!empty($redirectUrl)) { + $this->withRedirectUrl($redirectUrl); + } + + return $this->getAuthUrl(); + } + + /** + * @param string $code + * + * @return \Overtrue\Socialite\User + * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function userFromCode(string $code): User + { + $tokenResponse = $this->tokenFromCode($code); + $user = $this->userFromToken($tokenResponse[$this->accessTokenKey]); + + return $user->setRefreshToken($tokenResponse[$this->refreshTokenKey] ?? null) + ->setExpiresIn($tokenResponse[$this->expiresInKey] ?? null) + ->setTokenResponse($tokenResponse); + } + + /** + * @param string $token + * + * @return \Overtrue\Socialite\User + */ + public function userFromToken(string $token): User + { + $user = $this->getUserByToken($token); + + return $this->mapUserToObject($user)->setProvider($this)->setRaw($user)->setAccessToken($token); + } + + /** + * @param string $code + * + * @return array + * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException|\GuzzleHttp\Exception\GuzzleException + */ + public function tokenFromCode(string $code): array + { + $response = $this->getHttpClient()->post( + $this->getTokenUrl(), + [ + 'form_params' => $this->getTokenFields($code), + 'headers' => [ + 'Accept' => 'application/json', + ], + ] + ); + + return $this->normalizeAccessTokenResponse($response->getBody()->getContents()); + } + + /** + * @param string $refreshToken + * + * @throws \Overtrue\Socialite\Exceptions\MethodDoesNotSupportException + */ + public function refreshToken(string $refreshToken) + { + throw new MethodDoesNotSupportException('refreshToken does not support.'); + } + + /** + * @param string $redirectUrl + * + * @return $this|\Overtrue\Socialite\Contracts\ProviderInterface + */ + public function withRedirectUrl(string $redirectUrl): ProviderInterface + { + $this->redirectUrl = $redirectUrl; + + return $this; + } + + /** + * @param string $state + * + * @return \Overtrue\Socialite\Contracts\ProviderInterface + */ + public function withState(string $state): ProviderInterface + { + $this->state = $state; + + return $this; + } + + /** + * @param array $scopes + * + * @return $this + */ + public function scopes(array $scopes): self + { + $this->scopes = $scopes; + + return $this; + } + + /** + * @param array $parameters + * + * @return $this + */ + public function with(array $parameters): self + { + $this->parameters = $parameters; + + return $this; + } + + public function getConfig(): Config + { + return $this->config; + } + + /** + * @param string $scopeSeparator + * + * @return self + */ + public function withScopeSeparator(string $scopeSeparator): self + { + $this->scopeSeparator = $scopeSeparator; + + return $this; + } + + public function getClientId(): ?string + { + return $this->config->get('client_id'); + } + + public function getClientSecret(): ?string + { + return $this->config->get('client_secret'); + } + + public function getHttpClient(): GuzzleClient + { + return $this->httpClient ?? new GuzzleClient($this->guzzleOptions); + } + + /** + * @param array $config + * + * @return \Overtrue\Socialite\Contracts\ProviderInterface + */ + public function setGuzzleOptions($config = []): ProviderInterface + { + $this->guzzleOptions = $config; + + return $this; + } + + public function getGuzzleOptions(): array + { + return $this->guzzleOptions; + } + + /** + * @param array $scopes + * @param string $scopeSeparator + * + * @return string + */ + protected function formatScopes(array $scopes, $scopeSeparator): string + { + return implode($scopeSeparator, $scopes); + } + + /** + * @param string $code + * + * @return array + */ + protected function getTokenFields(string $code): array + { + return [ + 'client_id' => $this->getClientId(), + 'client_secret' => $this->getClientSecret(), + 'code' => $code, + 'redirect_uri' => $this->redirectUrl, + ]; + } + + /** + * @param string $url + * + * @return string + */ + protected function buildAuthUrlFromBase(string $url): string + { + $query = $this->getCodeFields() + ($this->state ? ['state' => $this->state] : []); + + return $url . '?' . \http_build_query($query, '', '&', $this->encodingType); + } + + protected function getCodeFields(): array + { + $fields = array_merge( + [ + 'client_id' => $this->getClientId(), + 'redirect_uri' => $this->redirectUrl, + 'scope' => $this->formatScopes($this->scopes, $this->scopeSeparator), + 'response_type' => 'code', + ], + $this->parameters + ); + + if ($this->state) { + $fields['state'] = $this->state; + } + + return $fields; + } + + /** + * @param array|string $response + * + * @return mixed + * @return array + * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException + * + */ + protected function normalizeAccessTokenResponse($response): array + { + if ($response instanceof Stream) { + $response->rewind(); + $response = $response->getContents(); + } + + if (\is_string($response)) { + $response = json_decode($response, true) ?? []; + } + + if (!\is_array($response)) { + throw new AuthorizeFailedException('Invalid token response', [$response]); + } + + if (empty($response[$this->accessTokenKey])) { + throw new AuthorizeFailedException('Authorize Failed: ' . json_encode($response, JSON_UNESCAPED_UNICODE), $response); + } + + return $response + [ + 'access_token' => $response[$this->accessTokenKey], + 'refresh_token' => $response[$this->refreshTokenKey] ?? null, + 'expires_in' => \intval($response[$this->expiresInKey] ?? 0), + ]; + } +} diff --git a/vendor/overtrue/socialite/src/Providers/DingTalk.php b/vendor/overtrue/socialite/src/Providers/DingTalk.php new file mode 100644 index 0000000..548f652 --- /dev/null +++ b/vendor/overtrue/socialite/src/Providers/DingTalk.php @@ -0,0 +1,126 @@ +buildAuthUrlFromBase('https://oapi.dingtalk.com/connect/qrconnect'); + } + + protected function getTokenUrl(): string + { + throw new \InvalidArgumentException('not supported to get access token.'); + } + + /** + * @param string $token + * + * @return array + */ + protected function getUserByToken(string $token): array + { + throw new \InvalidArgumentException('Unable to use token get User.'); + } + + /** + * @param array $user + * + * @return \Overtrue\Socialite\User + */ + protected function mapUserToObject(array $user): User + { + return new User( + [ + 'name' => $user['nick'] ?? null, + 'nickname' => $user['nick'] ?? null, + 'id' => $user['openid'] ?? null, + 'email' => null, + 'avatar' => null, + ] + ); + } + + protected function getCodeFields(): array + { + return array_merge( + [ + 'appid' => $this->getClientId(), + 'response_type' => 'code', + 'scope' => implode($this->scopes), + 'redirect_uri' => $this->redirectUrl, + ], + $this->parameters + ); + } + + public function getClientId(): ?string + { + return $this->getConfig()->get('app_id') ?? $this->getConfig()->get('appid') ?? $this->getConfig()->get('appId') + ?? $this->getConfig()->get('client_id'); + } + + public function getClientSecret(): ?string + { + return $this->getConfig()->get('app_secret') ?? $this->getConfig()->get('appSecret') + ?? $this->getConfig()->get('client_secret'); + } + + protected function createSignature(int $time) + { + return base64_encode(hash_hmac('sha256', $time, $this->getClientSecret(), true)); + } + + /** + * @param string $code + * + * @return User + * + * @throws \GuzzleHttp\Exception\GuzzleException + * @see https://ding-doc.dingtalk.com/doc#/personnal/tmudue + */ + public function userFromCode(string $code): User + { + $time = (int)microtime(true) * 1000; + $queryParams = [ + 'accessKey' => $this->getClientId(), + 'timestamp' => $time, + 'signature' => $this->createSignature($time), + ]; + + $response = $this->getHttpClient()->post( + $this->getUserByCode . '?' . http_build_query($queryParams), + [ + 'json' => ['tmp_auth_code' => $code], + ] + ); + $response = \json_decode($response->getBody()->getContents(), true); + + if (0 != $response['errcode'] ?? 1) { + throw new \InvalidArgumentException('You get error: ' . json_encode($response, JSON_UNESCAPED_UNICODE)); + } + + return new User( + [ + 'name' => $response['user_info']['nick'], + 'nickname' => $response['user_info']['nick'], + 'id' => $response['user_info']['openid'], + ] + ); + } +} diff --git a/vendor/overtrue/socialite/src/Providers/DouYin.php b/vendor/overtrue/socialite/src/Providers/DouYin.php new file mode 100644 index 0000000..4b6dc84 --- /dev/null +++ b/vendor/overtrue/socialite/src/Providers/DouYin.php @@ -0,0 +1,120 @@ +buildAuthUrlFromBase($this->baseUrl . '/platform/oauth/connect/'); + } + + public function getCodeFields(): array + { + return [ + 'client_key' => $this->getClientId(), + 'redirect_uri' => $this->redirectUrl, + 'scope' => $this->formatScopes($this->scopes, $this->scopeSeparator), + 'response_type' => 'code', + ]; + } + + protected function getTokenUrl(): string + { + return $this->baseUrl . '/oauth/access_token/'; + } + + /** + * @param string $code + * + * @return array + * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException + * @throws \GuzzleHttp\Exception\GuzzleException + * + */ + public function tokenFromCode($code): array + { + $response = $this->getHttpClient()->get( + $this->getTokenUrl(), + [ + 'query' => $this->getTokenFields($code), + ] + ); + + $body = \json_decode($response->getBody()->getContents(), true) ?? []; + + if (empty($body['data']) || $body['data']['error_code'] != 0) { + throw new AuthorizeFailedException('Invalid token response', $body); + } + + $this->withOpenId($body['data']['open_id']); + + return $this->normalizeAccessTokenResponse($body['data']); + } + + protected function getTokenFields(string $code): array + { + return [ + 'client_key' => $this->getClientId(), + 'client_secret' => $this->getClientSecret(), + 'code' => $code, + 'grant_type' => 'authorization_code', + ]; + } + + protected function getUserByToken(string $token): array + { + $userUrl = $this->baseUrl . '/oauth/userinfo/'; + + if (empty($this->openId)) { + throw new InvalidArgumentException('please set open_id before your query.'); + } + + $response = $this->getHttpClient()->get( + $userUrl, + [ + 'query' => [ + 'access_token' => $token, + 'open_id' => $this->openId, + ], + ] + ); + + $body = \json_decode($response->getBody()->getContents(), true); + + return $body['data'] ?? []; + } + + protected function mapUserToObject(array $user): User + { + return new User( + [ + 'id' => $user['open_id'] ?? null, + 'name' => $user['nickname'] ?? null, + 'nickname' => $user['nickname'] ?? null, + 'avatar' => $user['avatar'] ?? null, + 'email' => $user['email'] ?? null, + ] + ); + } + + public function withOpenId(string $openId): self + { + $this->openId = $openId; + + return $this; + } +} diff --git a/vendor/overtrue/socialite/src/Providers/Douban.php b/vendor/overtrue/socialite/src/Providers/Douban.php new file mode 100644 index 0000000..06f71e1 --- /dev/null +++ b/vendor/overtrue/socialite/src/Providers/Douban.php @@ -0,0 +1,83 @@ +buildAuthUrlFromBase('https://www.douban.com/service/auth2/auth'); + } + + protected function getTokenUrl(): string + { + return 'https://www.douban.com/service/auth2/token'; + } + + /** + * @param string $token + * @param array|null $query + * + * @return array + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function getUserByToken(string $token, ?array $query = []): array + { + $response = $this->getHttpClient()->get('https://api.douban.com/v2/user/~me', [ + 'headers' => [ + 'Authorization' => 'Bearer '.$token, + ], + ]); + + return json_decode($response->getBody()->getContents(), true) ?? []; + } + + /** + * @param array $user + * + * @return \Overtrue\Socialite\User + */ + protected function mapUserToObject(array $user): User + { + return new User([ + 'id' => $user['id'] ?? null, + 'nickname' => $user['name'] ?? null, + 'name' => $user['name'] ?? null, + 'avatar' => $user['avatar'] ?? null, + 'email' => null, + ]); + } + + /** + * @param string $code + * + * @return array|string[] + */ + protected function getTokenFields(string $code): array + { + return parent::getTokenFields($code) + ['grant_type' => 'authorization_code']; + } + + /** + * @param string $code + * + * @return array + * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function tokenFromCode(string $code): array + { + $response = $this->getHttpClient()->post($this->getTokenUrl(), [ + 'form_params' => $this->getTokenFields($code), + ]); + + return $this->normalizeAccessTokenResponse($response->getBody()->getContents()); + } +} diff --git a/vendor/overtrue/socialite/src/Providers/Facebook.php b/vendor/overtrue/socialite/src/Providers/Facebook.php new file mode 100644 index 0000000..a7b5f33 --- /dev/null +++ b/vendor/overtrue/socialite/src/Providers/Facebook.php @@ -0,0 +1,125 @@ +buildAuthUrlFromBase('https://www.facebook.com/' . $this->version . '/dialog/oauth'); + } + + protected function getTokenUrl(): string + { + return $this->graphUrl . '/oauth/access_token'; + } + + /** + * @param string $code + * + * @return array + * @throws \GuzzleHttp\Exception\GuzzleException + * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException + */ + public function tokenFromCode(string $code): array + { + $response = $this->getHttpClient()->get( + $this->getTokenUrl(), + [ + 'query' => $this->getTokenFields($code), + ] + ); + + return $this->normalizeAccessTokenResponse($response->getBody()); + } + + /** + * @param string $token + * @param array|null $query + * + * @return array + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function getUserByToken(string $token, ?array $query = []): array + { + $appSecretProof = hash_hmac('sha256', $token, $this->getConfig()->get('client_secret')); + $endpoint = $this->graphUrl . '/' . $this->version . '/me?access_token=' . $token . '&appsecret_proof=' . $appSecretProof . '&fields=' . + implode(',', $this->fields); + + $response = $this->getHttpClient()->get( + $endpoint, + [ + 'headers' => [ + 'Accept' => 'application/json', + ], + ] + ); + + return \json_decode($response->getBody(), true) ?? []; + } + + protected function mapUserToObject(array $user): User + { + $userId = $user['id'] ?? null; + $avatarUrl = $this->graphUrl . '/' . $this->version . '/' . $userId . '/picture'; + + $firstName = $user['first_name'] ?? null; + $lastName = $user['last_name'] ?? null; + + return new User( + [ + 'id' => $user['id'] ?? null, + 'nickname' => null, + 'name' => $firstName . ' ' . $lastName, + 'email' => $user['email'] ?? null, + 'avatar' => $userId ? $avatarUrl . '?type=normal' : null, + 'avatar_original' => $userId ? $avatarUrl . '?width=1920' : null, + ] + ); + } + + protected function getCodeFields(): array + { + $fields = parent::getCodeFields(); + + if ($this->popup) { + $fields['display'] = 'popup'; + } + + return $fields; + } + + /** + * @param array $fields + * + * @return $this + */ + public function fields(array $fields) + { + $this->fields = $fields; + + return $this; + } + + /** + * @return $this + */ + public function asPopup() + { + $this->popup = true; + + return $this; + } +} diff --git a/vendor/overtrue/socialite/src/Providers/FeiShu.php b/vendor/overtrue/socialite/src/Providers/FeiShu.php new file mode 100644 index 0000000..9cb1f15 --- /dev/null +++ b/vendor/overtrue/socialite/src/Providers/FeiShu.php @@ -0,0 +1,239 @@ +isInternalApp = ($this->config->get('app_mode') ?? $this->config->get('mode')) == 'internal'; + } + + protected function getAuthUrl(): string + { + return $this->buildAuthUrlFromBase($this->baseUrl . '/authen/v1/index'); + } + + protected function getCodeFields(): array + { + return [ + 'redirect_uri' => $this->redirectUrl, + 'app_id' => $this->getClientId(), + ]; + } + + protected function getTokenUrl(): string + { + return $this->baseUrl . '/authen/v1/access_token'; + } + + /** + * @param string $code + * + * @return array + * @throws AuthorizeFailedException + * @throws GuzzleException + */ + public function tokenFromCode(string $code): array + { + return $this->normalizeAccessTokenResponse($this->getTokenFromCode($code)); + } + + /** + * @param string $code + * + * @return array + * @throws AuthorizeFailedException + * + * @throws AuthorizeFailedException + * @throws GuzzleException + */ + protected function getTokenFromCode(string $code): array + { + $this->configAppAccessToken(); + $response = $this->getHttpClient()->post( + $this->getTokenUrl(), + [ + 'json' => [ + 'app_access_token' => $this->config->get('app_access_token'), + 'code' => $code, + 'grant_type' => 'authorization_code', + ], + ] + ); + $response = \json_decode($response->getBody(), true) ?? []; + + if (empty($response['data'])) { + throw new AuthorizeFailedException('Invalid token response', $response); + } + + return $this->normalizeAccessTokenResponse($response['data']); + } + + /** + * @param string $token + * + * @return array + * @throws GuzzleException + */ + protected function getUserByToken(string $token): array + { + $response = $this->getHttpClient()->get( + $this->baseUrl . '/authen/v1/user_info', + [ + 'headers' => ['Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . $token], + 'query' => array_filter( + [ + 'user_access_token' => $token, + ] + ), + ] + ); + + $response = \json_decode($response->getBody(), true) ?? []; + + if (empty($response['data'])) { + throw new \InvalidArgumentException('You have error! ' . json_encode($response, JSON_UNESCAPED_UNICODE)); + } + + return $response['data']; + } + + /** + * @param array $user + * + * @return User + */ + protected function mapUserToObject(array $user): User + { + return new User( + [ + 'id' => $user['user_id'] ?? null, + 'name' => $user['name'] ?? null, + 'nickname' => $user['name'] ?? null, + 'avatar' => $user['avatar_url'] ?? null, + 'email' => $user['email'] ?? null, + ] + ); + } + + public function withInternalAppMode(): self + { + $this->isInternalApp = true; + return $this; + } + + public function withDefaultMode(): self + { + $this->isInternalApp = false; + return $this; + } + + /** + * set 'app_ticket' in config attribute + * + * @param string $appTicket + * + * @return FeiShu + */ + public function withAppTicket(string $appTicket): self + { + $this->config->set('app_ticket', $appTicket); + return $this; + } + + /** + * 设置 app_access_token 到 config 设置中 + * 应用维度授权凭证,开放平台可据此识别调用方的应用身份 + * 分内建和自建 + */ + protected function configAppAccessToken() + { + $url = $this->baseUrl . '/auth/v3/app_access_token/'; + $params = [ + 'json' => [ + 'app_id' => $this->config->get('client_id'), + 'app_secret' => $this->config->get('client_secret'), + 'app_ticket' => $this->config->get('app_ticket'), + ], + ]; + + if ($this->isInternalApp) { + $url = $this->baseUrl . '/auth/v3/app_access_token/internal/'; + $params = [ + 'json' => [ + 'app_id' => $this->config->get('client_id'), + 'app_secret' => $this->config->get('client_secret'), + ], + ]; + } + + if (!$this->isInternalApp && !$this->config->has('app_ticket')) { + throw new InvalidTicketException('You are using default mode, please config \'app_ticket\' first'); + } + + $response = $this->getHttpClient()->post($url, $params); + $response = \json_decode($response->getBody(), true) ?? []; + + if (empty($response['app_access_token'])) { + throw new InvalidTokenException('Invalid \'app_access_token\' response', json_encode($response)); + } + + $this->config->set('app_access_token', $response['app_access_token']); + } + + /** + * 设置 tenant_access_token 到 config 属性中 + * 应用的企业授权凭证,开放平台据此识别调用方的应用身份和企业身份 + * 分内建和自建 + */ + protected function configTenantAccessToken() + { + $url = $this->baseUrl . '/auth/v3/tenant_access_token/'; + $params = [ + 'json' => [ + 'app_id' => $this->config->get('client_id'), + 'app_secret' => $this->config->get('client_secret'), + 'app_ticket' => $this->config->get('app_ticket'), + ], + ]; + + if ($this->isInternalApp) { + $url = $this->baseUrl . '/auth/v3/tenant_access_token/internal/'; + $params = [ + 'json' => [ + 'app_id' => $this->config->get('client_id'), + 'app_secret' => $this->config->get('client_secret'), + ], + ]; + } + + if (!$this->isInternalApp && !$this->config->has('app_ticket')) { + throw new BadRequestException('You are using default mode, please config \'app_ticket\' first'); + } + + $response = $this->getHttpClient()->post($url, $params); + $response = \json_decode($response->getBody(), true) ?? []; + if (empty($response['tenant_access_token'])) { + throw new AuthorizeFailedException('Invalid tenant_access_token response', $response); + } + + $this->config->set('tenant_access_token', $response['tenant_access_token']); + } +} diff --git a/vendor/overtrue/socialite/src/Providers/GitHub.php b/vendor/overtrue/socialite/src/Providers/GitHub.php new file mode 100644 index 0000000..d0e1771 --- /dev/null +++ b/vendor/overtrue/socialite/src/Providers/GitHub.php @@ -0,0 +1,97 @@ +buildAuthUrlFromBase('https://github.com/login/oauth/authorize'); + } + + protected function getTokenUrl(): string + { + return 'https://github.com/login/oauth/access_token'; + } + + /** + * @param string $token + * + * @return array + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function getUserByToken(string $token): array + { + $userUrl = 'https://api.github.com/user'; + + $response = $this->getHttpClient()->get( + $userUrl, + $this->createAuthorizationHeaders($token) + ); + + $user = json_decode($response->getBody(), true); + + if (in_array('user:email', $this->scopes)) { + $user['email'] = $this->getEmailByToken($token); + } + + return $user; + } + + /** + * @param string $token + * + * @return string + */ + protected function getEmailByToken(string $token) + { + $emailsUrl = 'https://api.github.com/user/emails'; + + try { + $response = $this->getHttpClient()->get( + $emailsUrl, + $this->createAuthorizationHeaders($token) + ); + } catch (\Throwable $e) { + return ''; + } + + foreach (json_decode($response->getBody(), true) as $email) { + if ($email['primary'] && $email['verified']) { + return $email['email']; + } + } + } + + protected function mapUserToObject(array $user): User + { + return new User([ + 'id' => $user['id'] ?? null, + 'nickname' => $user['login'] ?? null, + 'name' => $user['name'] ?? null, + 'email' => $user['email'] ?? null, + 'avatar' => $user['avatar_url'] ?? null, + ]); + } + + /** + * @param string $token + * + * @return array + */ + protected function createAuthorizationHeaders(string $token) + { + return [ + 'headers' => [ + 'Accept' => 'application/vnd.github.v3+json', + 'Authorization' => sprintf('token %s', $token), + ], + ]; + } +} diff --git a/vendor/overtrue/socialite/src/Providers/Gitee.php b/vendor/overtrue/socialite/src/Providers/Gitee.php new file mode 100644 index 0000000..6a1a93e --- /dev/null +++ b/vendor/overtrue/socialite/src/Providers/Gitee.php @@ -0,0 +1,65 @@ +buildAuthUrlFromBase('https://gitee.com/oauth/authorize'); + } + + protected function getTokenUrl(): string + { + return 'https://gitee.com/oauth/token'; + } + + protected function getUserByToken(string $token): array + { + $userUrl = 'https://gitee.com/api/v5/user'; + $response = $this->getHttpClient()->get( + $userUrl, + [ + 'query' => ['access_token' => $token], + ] + ); + return \json_decode($response->getBody()->getContents(), true) ?? []; + } + + protected function mapUserToObject(array $user): User + { + return new User([ + 'id' => $user['id'] ?? null, + 'nickname' => $user['login'] ?? null, + 'name' => $user['name'] ?? null, + 'email' => $user['email'] ?? null, + 'avatar' => $user['avatar_url'] ?? null, + ]); + } + + protected function getTokenFields(string $code): array + { + return [ + 'client_id' => $this->getClientId(), + 'client_secret' => $this->getClientSecret(), + 'code' => $code, + 'redirect_uri' => $this->redirectUrl, + 'grant_type' => 'authorization_code', + ]; + } +} \ No newline at end of file diff --git a/vendor/overtrue/socialite/src/Providers/Google.php b/vendor/overtrue/socialite/src/Providers/Google.php new file mode 100644 index 0000000..35430c3 --- /dev/null +++ b/vendor/overtrue/socialite/src/Providers/Google.php @@ -0,0 +1,90 @@ +buildAuthUrlFromBase('https://accounts.google.com/o/oauth2/v2/auth'); + } + + protected function getTokenUrl(): string + { + return 'https://www.googleapis.com/oauth2/v4/token'; + } + + /** + * @param string $code + * + * @return array + * @throws \GuzzleHttp\Exception\GuzzleException + * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException + */ + public function tokenFromCode($code): array + { + $response = $this->getHttpClient()->post($this->getTokenUrl(), [ + 'form_params' => $this->getTokenFields($code), + ]); + + return $this->normalizeAccessTokenResponse($response->getBody()); + } + + /** + * @param string $code + * + * @return array + */ + protected function getTokenFields($code): array + { + return parent::getTokenFields($code) + ['grant_type' => 'authorization_code']; + } + + /** + * @param string $token + * @param array|null $query + * + * @return array + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function getUserByToken(string $token, ?array $query = []): array + { + $response = $this->getHttpClient()->get('https://www.googleapis.com/userinfo/v2/me', [ + 'headers' => [ + 'Accept' => 'application/json', + 'Authorization' => 'Bearer '.$token, + ], + ]); + + return \json_decode($response->getBody(), true) ?? []; + } + + /** + * @param array $user + * + * @return \Overtrue\Socialite\User + */ + protected function mapUserToObject(array $user): User + { + return new User([ + 'id' => $user['id'] ?? null, + 'username' => $user['email'] ?? null, + 'nickname' => $user['name'] ?? null, + 'name' => $user['name'] ?? null, + 'email' => $user['email'] ?? null, + 'avatar' => $user['picture'] ?? null, + ]); + } +} diff --git a/vendor/overtrue/socialite/src/Providers/Line.php b/vendor/overtrue/socialite/src/Providers/Line.php new file mode 100644 index 0000000..844b1e3 --- /dev/null +++ b/vendor/overtrue/socialite/src/Providers/Line.php @@ -0,0 +1,77 @@ +state = $this->state ?: md5(uniqid()); + return $this->buildAuthUrlFromBase('https://access.line.me/oauth2/'.$this->version.'/authorize'); + } + + protected function getTokenUrl(): string + { + return $this->baseUrl.$this->version.'/token'; + } + + /** + * @param string $code + * + * @return array + */ + protected function getTokenFields(string $code): array + { + return [ + 'client_id' => $this->getClientId(), + 'client_secret' => $this->getClientSecret(), + 'code' => $code, + 'grant_type' => 'authorization_code', + 'redirect_uri' => $this->redirectUrl, + ]; + } + + /** + * @param string $token + * + * @return array + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function getUserByToken(string $token): array + { + $response = $this->getHttpClient()->get( + 'https://api.line.me/v2/profile', + [ + 'headers' => [ + 'Accept' => 'application/json', + 'Authorization' => 'Bearer '.$token, + ], + ] + ); + + return \json_decode($response->getBody(), true) ?? []; + } + + protected function mapUserToObject(array $user): User + { + return new User( + [ + 'id' => $user['userId'] ?? null, + 'name' => $user['displayName'] ?? null, + 'nickname' => $user['displayName'] ?? null, + 'avatar' => $user['pictureUrl'] ?? null, + 'email' => null, + ] + ); + } +} diff --git a/vendor/overtrue/socialite/src/Providers/Linkedin.php b/vendor/overtrue/socialite/src/Providers/Linkedin.php new file mode 100644 index 0000000..ae6df2c --- /dev/null +++ b/vendor/overtrue/socialite/src/Providers/Linkedin.php @@ -0,0 +1,121 @@ +buildAuthUrlFromBase('https://www.linkedin.com/oauth/v2/authorization'); + } + + protected function getTokenUrl(): string + { + return 'https://www.linkedin.com/oauth/v2/accessToken'; + } + + /** + * @param string $code + * + * @return array + */ + protected function getTokenFields($code): array + { + return parent::getTokenFields($code) + ['grant_type' => 'authorization_code']; + } + + /** + * @param string $token + * @param array|null $query + * + * @return array + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function getUserByToken(string $token, ?array $query = []): array + { + $basicProfile = $this->getBasicProfile($token); + $emailAddress = $this->getEmailAddress($token); + + return array_merge($basicProfile, $emailAddress); + } + + /** + * @param string $token + * + * @return array + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function getBasicProfile(string $token) + { + $url = 'https://api.linkedin.com/v2/me?projection=(id,firstName,lastName,profilePicture(displayImage~:playableStreams))'; + + $response = $this->getHttpClient()->get($url, [ + 'headers' => [ + 'Authorization' => 'Bearer '.$token, + 'X-RestLi-Protocol-Version' => '2.0.0', + ], + ]); + + return \json_decode($response->getBody(), true) ?? []; + } + + /** + * @param string $token + * + * @return array + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function getEmailAddress(string $token) + { + $url = 'https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))'; + + $response = $this->getHttpClient()->get($url, [ + 'headers' => [ + 'Authorization' => 'Bearer '.$token, + 'X-RestLi-Protocol-Version' => '2.0.0', + ], + ]); + + return \json_decode($response->getBody(), true)['elements.0.handle~'] ?? []; + } + + /** + * @param array $user + * + * @return \Overtrue\Socialite\User + */ + protected function mapUserToObject(array $user): User + { + $preferredLocale = ($user['firstName.preferredLocale.language'] ?? null).'_'.($user['firstName.preferredLocale.country']) ?? null; + $firstName = $user['firstName.localized.'.$preferredLocale] ?? null; + $lastName = $user['lastName.localized.'.$preferredLocale] ?? null; + $name = $firstName.' '.$lastName; + + $images = $user['profilePicture.displayImage~.elements'] ?? []; + $avatars = array_filter($images, function ($image) { + return ($image['data']['com.linkedin.digitalmedia.mediaartifact.StillImage']['storageSize']['width'] ?? 0) === 100; + }); + $avatar = array_shift($avatars); + $originalAvatars = array_filter($images, function ($image) { + return ($image['data']['com.linkedin.digitalmedia.mediaartifact.StillImage']['storageSize']['width'] ?? 0) === 800; + }); + $originalAvatar = array_shift($originalAvatars); + + return new User([ + 'id' => $user['id'] ?? null, + 'nickname' => $name, + 'name' => $name, + 'email' => $user['emailAddress'] ?? null, + 'avatar' => $avatar['identifiers.0.identifier'] ?? null, + 'avatar_original' => $originalAvatar['identifiers.0.identifier'] ?? null, + ]); + } +} diff --git a/vendor/overtrue/socialite/src/Providers/OpenWeWork.php b/vendor/overtrue/socialite/src/Providers/OpenWeWork.php new file mode 100644 index 0000000..f4eee68 --- /dev/null +++ b/vendor/overtrue/socialite/src/Providers/OpenWeWork.php @@ -0,0 +1,217 @@ +getConfig()->has('base_url')) { + $this->baseUrl = $this->getConfig()->get('base_url'); + } + } + + public function withAgentId(int $agentId): OpenWeWork + { + $this->agentId = $agentId; + + return $this; + } + + public function userFromCode(string $code): User + { + $user = $this->getUser($this->getSuiteAccessToken(), $code); + + if ($this->detailed) { + $user = array_merge($user, $this->getUserByTicket($user['user_ticket'])); + } + + return $this->mapUserToObject($user)->setProvider($this)->setRaw($user); + } + + public function withSuiteTicket(string $suiteTicket): OpenWeWork + { + $this->suiteTicket = $suiteTicket; + + return $this; + } + + public function withSuiteAccessToken(string $suiteAccessToken): OpenWeWork + { + $this->suiteAccessToken = $suiteAccessToken; + + return $this; + } + + public function getAuthUrl(): string + { + $queries = \array_filter([ + 'appid' => $this->getClientId(), + 'redirect_uri' => $this->redirectUrl, + 'response_type' => 'code', + 'scope' => $this->formatScopes($this->scopes, $this->scopeSeparator), + 'state' => $this->state, + 'agentid' => $this->agentId, + ]); + + if ((\in_array('snsapi_userinfo', $this->scopes) || \in_array('snsapi_privateinfo', $this->scopes)) && empty($this->agentId)) { + throw new InvalidArgumentException('agentid is required when scopes is snsapi_userinfo or snsapi_privateinfo.'); + } + + return sprintf('https://open.weixin.qq.com/connect/oauth2/authorize?%s#wechat_redirect', http_build_query($queries)); + } + + /** + * @param string $token + * + * @return array + * @throws \Overtrue\Socialite\Exceptions\MethodDoesNotSupportException + */ + protected function getUserByToken(string $token): array + { + throw new MethodDoesNotSupportException('Open WeWork doesn\'t support access_token mode'); + } + + /** + * @throws \GuzzleHttp\Exception\GuzzleException + * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException + */ + protected function getSuiteAccessToken(): string + { + return $this->suiteAccessToken ?? $this->suiteAccessToken = $this->requestSuiteAccessToken(); + } + + /** + * @param string $token + * @param string $code + * + * @return array + * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function getUser(string $token, string $code): array + { + $response = $this->getHttpClient()->get( + $this->baseUrl . '/cgi-bin/service/getuserinfo3rd', + [ + 'query' => array_filter( + [ + 'suite_access_token' => $token, + 'code' => $code, + ] + ), + ] + ); + + $response = \json_decode($response->getBody(), true) ?? []; + + if (($response['errcode'] ?? 1) > 0 || (empty($response['UserId']) && empty($response['open_userid']))) { + throw new AuthorizeFailedException('Failed to get user openid:' . $response['errmsg'] ?? 'Unknown.', $response); + } + + return $response; + } + + /** + * @param string $userTicket + * + * @return array + * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function getUserByTicket(string $userTicket): array + { + $response = $this->getHttpClient()->post( + $this->baseUrl . '/cgi-bin/user/get', + [ + 'query' => [ + 'suite_access_token' => $this->getSuiteAccessToken(), + 'user_ticket' => $userTicket, + ], + ] + ); + + $response = \json_decode($response->getBody(), true) ?? []; + + if (($response['errcode'] ?? 1) > 0 || empty($response['userid'])) { + throw new AuthorizeFailedException('Failed to get user:' . $response['errmsg'] ?? 'Unknown.', $response); + } + + return $response; + } + + /** + * @param array $user + * + * @return \Overtrue\Socialite\User + */ + protected function mapUserToObject(array $user): User + { + if ($this->detailed) { + return new User( + [ + 'id' => $user['userid'] ?? null, + 'name' => $user['name'] ?? null, + 'avatar' => $user['avatar'] ?? null, + 'email' => $user['email'] ?? null, + ] + ); + } + + return new User( + [ + 'id' => $user['UserId'] ?? null ?: $user['OpenId'] ?? null, + ] + ); + } + + /** + * @return string + * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function requestSuiteAccessToken(): string + { + $response = $this->getHttpClient()->post( + $this->baseUrl . '/cgi-bin/service/get_suite_token', + [ + 'json' => + [ + 'suite_id' => $this->config->get('suite_id') ?? $this->config->get('client_id'), + 'suite_secret' => $this->config->get('suite_secret') ?? $this->config->get('client_secret'), + 'suite_ticket' => $this->suiteTicket, + ] + ] + ); + + $response = \json_decode($response->getBody()->getContents(), true) ?? []; + + if (isset($response['errcode']) && $response['errcode'] > 0) { + throw new AuthorizeFailedException('Failed to get api access_token:' . $response['errmsg'] ?? 'Unknown.', $response); + } + + return $response['suite_access_token']; + } + + protected function getTokenUrl(): string + { + return ''; + } +} diff --git a/vendor/overtrue/socialite/src/Providers/Outlook.php b/vendor/overtrue/socialite/src/Providers/Outlook.php new file mode 100644 index 0000000..55cdc96 --- /dev/null +++ b/vendor/overtrue/socialite/src/Providers/Outlook.php @@ -0,0 +1,71 @@ +buildAuthUrlFromBase('https://login.microsoftonline.com/common/oauth2/v2.0/authorize'); + } + + protected function getTokenUrl(): string + { + return 'https://login.microsoftonline.com/common/oauth2/v2.0/token'; + } + + /** + * @param string $token + * @param array|null $query + * + * @return array + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function getUserByToken(string $token, ?array $query = []): array + { + $response = $this->getHttpClient()->get( + 'https://graph.microsoft.com/v1.0/me', + ['headers' => [ + 'Accept' => 'application/json', + 'Authorization' => 'Bearer '.$token, + ], + ] + ); + + return \json_decode($response->getBody()->getContents(), true) ?? []; + } + + /** + * @param array $user + * + * @return \Overtrue\Socialite\User + */ + protected function mapUserToObject(array $user): User + { + return new User([ + 'id' => $user['id'] ?? null, + 'nickname' => null, + 'name' => $user['displayName'] ?? null, + 'email' => $user['userPrincipalName'] ?? null, + 'avatar' => null, + ]); + } + + /** + * @param string $code + * + * @return array|string[] + */ + protected function getTokenFields(string $code): array + { + return parent::getTokenFields($code) + [ + 'grant_type' => 'authorization_code', + ]; + } +} diff --git a/vendor/overtrue/socialite/src/Providers/QCloud.php b/vendor/overtrue/socialite/src/Providers/QCloud.php new file mode 100644 index 0000000..898898d --- /dev/null +++ b/vendor/overtrue/socialite/src/Providers/QCloud.php @@ -0,0 +1,260 @@ +buildAuthUrlFromBase('https://cloud.tencent.com/open/authorize'); + } + + protected function getTokenUrl(): string + { + return ''; + } + + protected function getAppId(): string + { + return $this->config->get('app_id') ?? $this->getClientId(); + } + + protected function getSecretId(): string + { + return $this->config->get('secret_id'); + } + + protected function getSecretKey(): string + { + return $this->config->get('secret_key'); + } + + /** + * @param string $code + * + * @return array + * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException + */ + public function TokenFromCode(string $code): array + { + $response = $this->performRequest( + 'GET', + 'open.tencentcloudapi.com', + 'GetUserAccessToken', + '2018-12-25', + [ + 'query' => [ + 'UserAuthCode' => $code, + ], + ] + ); + + return $this->parseAccessToken($response); + } + + /** + * @param string $token + * + * @return array + * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException + */ + protected function getUserByToken(string $token): array + { + $secret = $this->getFederationToken($token); + + return $this->performRequest( + 'GET', + 'open.tencentcloudapi.com', + 'GetUserBaseInfo', + '2018-12-25', + [ + 'headers' => [ + 'X-TC-Token' => $secret['Token'], + ], + ], + $secret['TmpSecretId'], + $secret['TmpSecretKey'], + ); + } + + /** + * @param array $user + * + * @return \Overtrue\Socialite\User + */ + protected function mapUserToObject(array $user): User + { + return new User( + [ + 'id' => $this->openId ?? null, + 'name' => $user['Nickname'] ?? null, + 'nickname' => $user['Nickname'] ?? null, + ] + ); + } + + public function performRequest(string $method, string $host, string $action, string $version, array $options = [], ?string $secretId = null, ?string $secretKey = null) + { + $method = \strtoupper($method); + $timestamp = \time(); + $credential = \sprintf('%s/%s/tc3_request', \gmdate('Y-m-d', $timestamp), $this->getServiceFromHost($host)); + $options['headers'] = \array_merge( + $options['headers'] ?? [], + [ + 'X-TC-Action' => $action, + 'X-TC-Timestamp' => $timestamp, + 'X-TC-Version' => $version, + 'Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8', + ] + ); + + $signature = $this->sign($method, $host, $options['query'] ?? [], '', $options['headers'], $credential, $secretKey); + $options['headers']['Authorization'] = + \sprintf( + 'TC3-HMAC-SHA256 Credential=%s/%s, SignedHeaders=content-type;host, Signature=%s', + $secretId ?? $this->getSecretId(), + $credential, + $signature + ); + $options['debug'] = \fopen(storage_path('logs/laravel-2020-07-15.log'), 'w+'); + $response = $this->getHttpClient()->get("https://{$host}/", $options); + + $response = json_decode($response->getBody()->getContents(), true) ?? []; + + if (!empty($response['Response']['Error'])) { + throw new AuthorizeFailedException( + \sprintf('%s: %s', $response['Response']['Error']['Code'], $response['Response']['Error']['Message']), + $response + ); + } + + return $response['Response'] ?? []; + } + + protected function sign(string $requestMethod, string $host, array $query, string $payload, $headers, $credential, ?string $secretKey = null) + { + $canonicalRequestString = \join( + "\n", + [ + $requestMethod, + '/', + \http_build_query($query), + "content-type:{$headers['Content-Type']}\nhost:{$host}\n", + "content-type;host", + hash('SHA256', $payload), + ] + ); + + $signString = \join( + "\n", + [ + 'TC3-HMAC-SHA256', + $headers['X-TC-Timestamp'], + $credential, + hash('SHA256', $canonicalRequestString), + ] + ); + + $secretKey = $secretKey ?? $this->getSecretKey(); + $secretDate = hash_hmac('SHA256', \gmdate('Y-m-d', $headers['X-TC-Timestamp']), "TC3{$secretKey}", true); + $secretService = hash_hmac('SHA256', $this->getServiceFromHost($host), $secretDate, true); + $secretSigning = hash_hmac('SHA256', "tc3_request", $secretService, true); + + return hash_hmac('SHA256', $signString, $secretSigning); + } + + /** + * @param string|array $body + * + * @return array + * @throws AuthorizeFailedException + */ + protected function parseAccessToken($body) + { + if (!is_array($body)) { + $body = json_decode($body, true); + } + + if (empty($body['UserOpenId'])) { + throw new AuthorizeFailedException('Authorize Failed: ' . json_encode($body, JSON_UNESCAPED_UNICODE), $body); + } + + $this->openId = $body['UserOpenId'] ?? null; + $this->unionId = $body['UserUnionId'] ?? null; + + return $body; + } + + /** + * @param string $accessToken + * + * @return mixed + * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException + */ + protected function getFederationToken(string $accessToken) + { + $response = $this->performRequest( + 'GET', + 'sts.tencentcloudapi.com', + 'GetThirdPartyFederationToken', + '2018-08-13', + [ + 'query' => [ + 'UserAccessToken' => $accessToken, + 'Duration' => 7200, + 'ApiAppId' => 0, + ], + 'headers' => [ + 'X-TC-Region' => 'ap-guangzhou', // 官方人员说写死 + ] + ] + ); + + if (empty($response['Credentials'])) { + throw new AuthorizeFailedException('Get Federation Token failed.', $response); + } + + return $response['Credentials']; + } + + protected function getCodeFields(): array + { + $fields = array_merge( + [ + 'app_id' => $this->getAppId(), + 'redirect_url' => $this->redirectUrl, + 'scope' => $this->formatScopes($this->scopes, $this->scopeSeparator), + 'response_type' => 'code', + ], + $this->parameters + ); + + if ($this->state) { + $fields['state'] = $this->state; + } + + return $fields; + } + + /** + * @param string $host + * + * @return mixed|string + */ + protected function getServiceFromHost(string $host) + { + return explode('.', $host)[0]; + } +} diff --git a/vendor/overtrue/socialite/src/Providers/QQ.php b/vendor/overtrue/socialite/src/Providers/QQ.php new file mode 100644 index 0000000..b9c5ce5 --- /dev/null +++ b/vendor/overtrue/socialite/src/Providers/QQ.php @@ -0,0 +1,109 @@ +buildAuthUrlFromBase($this->baseUrl.'/oauth2.0/authorize'); + } + + protected function getTokenUrl(): string + { + return $this->baseUrl.'/oauth2.0/token'; + } + + /** + * @param string $code + * + * @return array + */ + protected function getTokenFields(string $code): array + { + return parent::getTokenFields($code) + [ + 'grant_type' => 'authorization_code', + ]; + } + + /** + * @param string $code + * + * @return array + * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function tokenFromCode(string $code): array + { + $response = $this->getHttpClient()->get($this->getTokenUrl(), [ + 'query' => $this->getTokenFields($code), + ]); + + \parse_str($response->getBody()->getContents(), $token); + + return $this->normalizeAccessTokenResponse($token); + } + + public function withUnionId(): self + { + $this->withUnionId = true; + + return $this; + } + + /** + * @param string $token + * + * @return array + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function getUserByToken(string $token): array + { + $url = $this->baseUrl.'/oauth2.0/me?fmt=json&access_token='.$token; + $this->withUnionId && $url .= '&unionid=1'; + + $response = $this->getHttpClient()->get($url); + + $me = \json_decode($response->getBody()->getContents(), true); + + $queries = [ + 'access_token' => $token, + 'fmt' => 'json', + 'openid' => $me['openid'], + 'oauth_consumer_key' => $this->getClientId(), + ]; + + $response = $this->getHttpClient()->get($this->baseUrl.'/user/get_user_info?'.http_build_query($queries)); + + return (\json_decode($response->getBody()->getContents(), true) ?? []) + [ + 'unionid' => $me['unionid'] ?? null, + 'openid' => $me['openid'] ?? null, + ]; + } + + /** + * @param array $user + * + * @return \Overtrue\Socialite\User + */ + protected function mapUserToObject(array $user): User + { + return new User([ + 'id' => $user['openid'] ?? null, + 'name' => $user['nickname'] ?? null, + 'nickname' => $user['nickname'] ?? null, + 'email' => $user['email'] ?? null, + 'avatar' => $user['figureurl_qq_2'] ?? null, + ]); + } +} diff --git a/vendor/overtrue/socialite/src/Providers/Taobao.php b/vendor/overtrue/socialite/src/Providers/Taobao.php new file mode 100644 index 0000000..80da804 --- /dev/null +++ b/vendor/overtrue/socialite/src/Providers/Taobao.php @@ -0,0 +1,160 @@ +view = $view; + + return $this; + } + + protected function getAuthUrl(): string + { + return $this->buildAuthUrlFromBase($this->baseUrl.'/authorize'); + } + + public function getCodeFields(): array + { + return [ + 'client_id' => $this->getClientId(), + 'redirect_uri' => $this->redirectUrl, + 'view' => $this->view, + 'response_type' => 'code', + ]; + } + + protected function getTokenUrl(): string + { + return $this->baseUrl.'/token'; + } + + /** + * @param string $code + * + * @return array + */ + protected function getTokenFields($code): array + { + return parent::getTokenFields($code) + ['grant_type' => 'authorization_code', 'view' => $this->view]; + } + + /** + * @param string $code + * + * @return array + * @throws \GuzzleHttp\Exception\GuzzleException + * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException + */ + public function tokenFromCode(string $code): array + { + $response = $this->getHttpClient()->post($this->getTokenUrl(), [ + 'query' => $this->getTokenFields($code), + ]); + + return $this->normalizeAccessTokenResponse($response->getBody()->getContents()); + } + + /** + * @param string $token + * @param array|null $query + * + * @return array + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function getUserByToken(string $token, ?array $query = []): array + { + $response = $this->getHttpClient()->post($this->getUserInfoUrl($this->gatewayUrl, $token)); + + return \json_decode($response->getBody()->getContents(), true) ?? []; + } + + /** + * @param array $user + * + * @return \Overtrue\Socialite\User + */ + protected function mapUserToObject(array $user): User + { + return new User([ + 'id' => $user['open_id'] ?? null, + 'nickname' => $user['nick'] ?? null, + 'name' => $user['nick'] ?? null, + 'avatar' => $user['avatar'] ?? null, + 'email' => $user['email'] ?? null, + ]); + } + + /** + * @param array $params + * + * @return string + */ + protected function generateSign(array $params) + { + ksort($params); + + $stringToBeSigned = $this->getConfig()->get('client_secret'); + + foreach ($params as $k => $v) { + if (!is_array($v) && '@' != substr($v, 0, 1)) { + $stringToBeSigned .= "$k$v"; + } + } + + $stringToBeSigned .= $this->getConfig()->get('client_secret'); + + return strtoupper(md5($stringToBeSigned)); + } + + /** + * @param string $token + * @param array $apiFields + * + * @return array + */ + protected function getPublicFields(string $token, array $apiFields = []) + { + $fields = [ + 'app_key' => $this->getClientId(), + 'sign_method' => 'md5', + 'session' => $token, + 'timestamp' => \date('Y-m-d H:i:s'), + 'v' => '2.0', + 'format' => 'json', + ]; + + $fields = array_merge($apiFields, $fields); + $fields['sign'] = $this->generateSign($fields); + + return $fields; + } + + /** + * @param string $url + * @param string $token + * + * @return string + */ + protected function getUserInfoUrl(string $url, string $token) + { + $apiFields = ['method' => 'taobao.miniapp.userInfo.get']; + + $query = http_build_query($this->getPublicFields($token, $apiFields), '', '&', $this->encodingType); + + return $url.'?'.$query; + } +} diff --git a/vendor/overtrue/socialite/src/Providers/Tapd.php b/vendor/overtrue/socialite/src/Providers/Tapd.php new file mode 100644 index 0000000..48624ab --- /dev/null +++ b/vendor/overtrue/socialite/src/Providers/Tapd.php @@ -0,0 +1,185 @@ +buildAuthUrlFromBase($this->baseUrl . '/quickstart/testauth'); + } + + /** + * @return string + */ + protected function getTokenUrl(): string + { + return $this->baseUrl . '/tokens/request_token'; + } + + /** + * @return string + */ + protected function getRefreshTokenUrl(): string + { + return $this->baseUrl . '/tokens/refresh_token'; + } + + /** + * @param string $code + * + * @return array + * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function tokenFromCode($code): array + { + $response = $this->getHttpClient()->post($this->getTokenUrl(), [ + 'headers' => [ + 'Accept' => 'application/json', + 'Authorization' => 'Basic ' . \base64_encode(\sprintf('%s:%s', $this->getClientId(), $this->getClientSecret())) + ], + 'form_params' => $this->getTokenFields($code), + ]); + + return $this->normalizeAccessTokenResponse($response->getBody()->getContents()); + } + + /** + * @param string $code + * + * @return array + */ + protected function getTokenFields(string $code): array + { + return [ + 'grant_type' => 'authorization_code', + 'redirect_uri' => $this->redirectUrl, + 'code' => $code, + ]; + } + + /** + * @param $refreshToken + * + * @return array + */ + protected function getRefreshTokenFields($refreshToken): array + { + return [ + 'grant_type' => 'refresh_token', + 'redirect_uri' => $this->redirectUrl, + 'refresh_token' => $refreshToken, + ]; + } + + /** + * @param string $refreshToken + * + * @return array + * + * @throws \GuzzleHttp\Exception\GuzzleException + * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException + */ + public function tokenFromRefreshToken(string $refreshToken): array + { + $response = $this->getHttpClient()->post($this->getRefreshTokenUrl(), [ + 'headers' => [ + 'Accept' => 'application/json', + 'Authorization' => 'Basic ' . \base64_encode(\sprintf('%s:%s', $this->getClientId(), $this->getClientSecret())) + ], + 'form_params' => $this->getRefreshTokenFields($refreshToken), + ]); + + return $this->normalizeAccessTokenResponse($response->getBody()->getContents()); + } + + /** + * @param string $token + * + * @return array + * + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function getUserByToken(string $token): array + { + $response = $this->getHttpClient()->get($this->baseUrl . '/users/info', [ + 'headers' => [ + 'Accept' => 'application/json', + 'Authorization' => 'Bearer ' . $token, + ], + ]); + + return \json_decode($response->getBody(), true) ?? []; + } + + /** + * @param array $user + * + * @return \Overtrue\Socialite\User + * + * @throws \Overtrue\Socialite\Exceptions\BadRequestException + */ + protected function mapUserToObject(array $user): User + { + if (!isset($user['status']) && $user['status'] != 1) { + throw new BadRequestException("用户信息获取失败"); + } + + return new User([ + 'id' => $user['data']['id'] ?? null, + 'nickname' => $user['data']['nick'] ?? null, + 'name' => $user['data']['name'] ?? null, + 'email' => $user['data']['email'] ?? null, + 'avatar' => $user['data']['avatar'] ?? null, + ]); + } + + /** + * @param array|string $response + * + * @return mixed + * @return array + * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException + * + */ + protected function normalizeAccessTokenResponse($response): array + { + if ($response instanceof Stream) { + $response->rewind(); + $response = $response->getContents(); + } + + if (\is_string($response)) { + $response = json_decode($response, true) ?? []; + } + + if (!\is_array($response)) { + throw new AuthorizeFailedException('Invalid token response', [$response]); + } + + if (empty($response['data'][$this->accessTokenKey])) { + throw new AuthorizeFailedException('Authorize Failed: ' . json_encode($response, JSON_UNESCAPED_UNICODE), $response); + } + + return $response + [ + 'access_token' => $response['data'][$this->accessTokenKey], + 'refresh_token' => $response['data'][$this->refreshTokenKey] ?? null, + 'expires_in' => \intval($response['data'][$this->expiresInKey] ?? 0), + ]; + } +} diff --git a/vendor/overtrue/socialite/src/Providers/WeChat.php b/vendor/overtrue/socialite/src/Providers/WeChat.php new file mode 100644 index 0000000..e9ce827 --- /dev/null +++ b/vendor/overtrue/socialite/src/Providers/WeChat.php @@ -0,0 +1,262 @@ +getConfig()->has('component')) { + $this->prepareForComponent((array) $this->getConfig()->get('component')); + } + } + + /** + * @param string $openid + * + * @return $this + */ + public function withOpenid(string $openid): self + { + $this->openid = $openid; + + return $this; + } + + public function withCountryCode() + { + $this->withCountryCode = true; + + return $this; + } + + /** + * @param string $code + * + * @return array + * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException|\GuzzleHttp\Exception\GuzzleException + */ + public function tokenFromCode(string $code): array + { + $response = $this->getTokenFromCode($code); + + return $this->normalizeAccessTokenResponse($response->getBody()->getContents()); + } + + /** + * @param array $componentConfig ['id' => xxx, 'token' => xxx] + * + * @return \Overtrue\Socialite\Providers\WeChat + * @throws \Overtrue\Socialite\Exceptions\InvalidArgumentException + */ + public function withComponent(array $componentConfig) + { + $this->prepareForComponent($componentConfig); + + return $this; + } + + public function getComponent() + { + return $this->component; + } + + protected function getAuthUrl(): string + { + $path = 'oauth2/authorize'; + + if (in_array('snsapi_login', $this->scopes)) { + $path = 'qrconnect'; + } + + return $this->buildAuthUrlFromBase("https://open.weixin.qq.com/connect/{$path}"); + } + + /** + * @param string $url + * + * @return string + */ + protected function buildAuthUrlFromBase(string $url): string + { + $query = http_build_query($this->getCodeFields(), '', '&', $this->encodingType); + + return $url . '?' . $query . '#wechat_redirect'; + } + + protected function getCodeFields(): array + { + if (!empty($this->component)) { + $this->with(array_merge($this->parameters, ['component_appid' => $this->component['id']])); + } + + return array_merge([ + 'appid' => $this->getClientId(), + 'redirect_uri' => $this->redirectUrl, + 'response_type' => 'code', + 'scope' => $this->formatScopes($this->scopes, $this->scopeSeparator), + 'state' => $this->state ?: md5(uniqid()), + 'connect_redirect' => 1, + ], $this->parameters); + } + + protected function getTokenUrl(): string + { + if (!empty($this->component)) { + return $this->baseUrl . '/oauth2/component/access_token'; + } + + return $this->baseUrl . '/oauth2/access_token'; + } + + /** + * @param string $code + * + * @return \Overtrue\Socialite\User + * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException|\GuzzleHttp\Exception\GuzzleException + */ + public function userFromCode(string $code): User + { + if (in_array('snsapi_base', $this->scopes)) { + return $this->mapUserToObject(\json_decode($this->getTokenFromCode($code)->getBody()->getContents(), true) ?? []); + } + + $token = $this->tokenFromCode($code); + + $this->withOpenid($token['openid']); + + $user = $this->userFromToken($token[$this->accessTokenKey]); + + return $user->setRefreshToken($token['refresh_token']) + ->setExpiresIn($token['expires_in']) + ->setTokenResponse($token); + } + + /** + * @param string $token + * + * @return array + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function getUserByToken(string $token): array + { + $language = $this->withCountryCode ? null : (isset($this->parameters['lang']) ? $this->parameters['lang'] : 'zh_CN'); + + $response = $this->getHttpClient()->get($this->baseUrl . '/userinfo', [ + 'query' => array_filter([ + 'access_token' => $token, + 'openid' => $this->openid, + 'lang' => $language, + ]), + ]); + + return \json_decode($response->getBody()->getContents(), true) ?? []; + } + + /** + * @param array $user + * + * @return \Overtrue\Socialite\User + */ + protected function mapUserToObject(array $user): User + { + return new User([ + 'id' => $user['openid'] ?? null, + 'name' => $user['nickname'] ?? null, + 'nickname' => $user['nickname'] ?? null, + 'avatar' => $user['headimgurl'] ?? null, + 'email' => null, + ]); + } + + /** + * @param string $code + * + * @return array + */ + protected function getTokenFields(string $code): array + { + if (!empty($this->component)) { + return [ + 'appid' => $this->getClientId(), + 'component_appid' => $this->component['id'], + 'component_access_token' => $this->component['token'], + 'code' => $code, + 'grant_type' => 'authorization_code', + ]; + } + + return [ + 'appid' => $this->getClientId(), + 'secret' => $this->getClientSecret(), + 'code' => $code, + 'grant_type' => 'authorization_code', + ]; + } + + /** + * @param string $code + * + * @return \Psr\Http\Message\ResponseInterface + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function getTokenFromCode(string $code): ResponseInterface + { + return $this->getHttpClient()->get($this->getTokenUrl(), [ + 'headers' => ['Accept' => 'application/json'], + 'query' => $this->getTokenFields($code), + ]); + } + + protected function prepareForComponent(array $component) + { + $config = []; + foreach ($component as $key => $value) { + if (\is_callable($value)) { + $value = \call_user_func($value, $this); + } + + switch ($key) { + case 'id': + case 'app_id': + case 'component_app_id': + $config['id'] = $value; + break; + case 'token': + case 'app_token': + case 'access_token': + case 'component_access_token': + $config['token'] = $value; + break; + } + } + + if (2 !== count($config)) { + throw new InvalidArgumentException('Please check your config arguments is available.'); + } + + if (1 === count($this->scopes) && in_array('snsapi_login', $this->scopes)) { + $this->scopes = ['snsapi_base']; + } + + $this->component = $config; + } +} diff --git a/vendor/overtrue/socialite/src/Providers/WeWork.php b/vendor/overtrue/socialite/src/Providers/WeWork.php new file mode 100644 index 0000000..6374b0d --- /dev/null +++ b/vendor/overtrue/socialite/src/Providers/WeWork.php @@ -0,0 +1,239 @@ +getConfig()->has('base_url')) { + $this->baseUrl = $this->getConfig()->get('base_url'); + } + } + + /** + * @deprecated will remove at 4.0 + */ + public function setAgentId(int $agentId): WeWork + { + $this->agentId = $agentId; + + return $this; + } + + /** + * @deprecated will remove at 4.0 + */ + public function withAgentId(int $agentId): WeWork + { + $this->agentId = $agentId; + + return $this; + } + + public function getBaseUrl() + { + return $this->baseUrl; + } + + public function userFromCode(string $code): User + { + $token = $this->getApiAccessToken(); + $user = $this->getUser($token, $code); + + if ($this->detailed) { + $user = $this->getUserById($user['UserId']); + } + + return $this->mapUserToObject($user)->setProvider($this)->setRaw($user); + } + + public function detailed(): self + { + $this->detailed = true; + + return $this; + } + + public function withApiAccessToken(string $apiAccessToken): WeWork + { + $this->apiAccessToken = $apiAccessToken; + + return $this; + } + + public function getAuthUrl(): string + { + // 网页授权登录 + if (empty($this->agentId)) { + $queries = [ + 'appid' => $this->getClientId(), + 'redirect_uri' => $this->redirectUrl, + 'response_type' => 'code', + 'scope' => $this->formatScopes($this->scopes, $this->scopeSeparator), + 'state' => $this->state, + ]; + + return sprintf('https://open.weixin.qq.com/connect/oauth2/authorize?%s#wechat_redirect', http_build_query($queries)); + } + + // 第三方网页应用登录(扫码登录) + return $this->getQrConnectUrl(); + } + + /** + * @deprecated will remove at 4.0 + */ + public function getQrConnectUrl() + { + $queries = [ + 'appid' => $this->getClientId(), + 'agentid' => $this->agentId ?? $this->config->get('agentid'), + 'redirect_uri' => $this->redirectUrl, + 'state' => $this->state, + ]; + + if (empty($queries['agentid'])) { + throw new InvalidArgumentException('You must config the `agentid` in configuration or using `setAgentid($agentId)`.'); + } + + return sprintf('https://open.work.weixin.qq.com/wwopen/sso/qrConnect?%s#wechat_redirect', http_build_query($queries)); + } + + /** + * @throws \Overtrue\Socialite\Exceptions\MethodDoesNotSupportException + */ + protected function getUserByToken(string $token): array + { + throw new MethodDoesNotSupportException('WeWork doesn\'t support access_token mode'); + } + + protected function getApiAccessToken(): string + { + return $this->apiAccessToken ?? $this->apiAccessToken = $this->requestApiAccessToken(); + } + + protected function getUser(string $token, string $code): array + { + $response = $this->getHttpClient()->get( + $this->baseUrl . '/cgi-bin/user/getuserinfo', + [ + 'query' => array_filter( + [ + 'access_token' => $token, + 'code' => $code, + ] + ), + ] + ); + + $response = \json_decode($response->getBody(), true) ?? []; + + if (($response['errcode'] ?? 1) > 0 || (empty($response['UserId']) && empty($response['OpenId']))) { + throw new AuthorizeFailedException('Failed to get user openid:' . $response['errmsg'] ?? 'Unknown.', $response); + } elseif (empty($response['UserId'])) { + $this->detailed = false; + } + + return $response; + } + + /** + * @throws \GuzzleHttp\Exception\GuzzleException + * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException + */ + protected function getUserById(string $userId): array + { + $response = $this->getHttpClient()->post( + $this->baseUrl . '/cgi-bin/user/get', + [ + 'query' => [ + 'access_token' => $this->getApiAccessToken(), + 'userid' => $userId, + ], + ] + ); + + $response = \json_decode($response->getBody(), true) ?? []; + + if (($response['errcode'] ?? 1) > 0 || empty($response['userid'])) { + throw new AuthorizeFailedException('Failed to get user:' . $response['errmsg'] ?? 'Unknown.', $response); + } + + return $response; + } + + /** + * @param array $user + * + * @return \Overtrue\Socialite\User + */ + protected function mapUserToObject(array $user): User + { + if ($this->detailed) { + return new User( + [ + 'id' => $user['userid'] ?? null, + 'name' => $user['name'] ?? null, + 'avatar' => $user['avatar'] ?? null, + 'email' => $user['email'] ?? null, + ] + ); + } + + return new User( + [ + 'id' => $user['UserId'] ?? null ?: $user['OpenId'] ?? null, + ] + ); + } + + /** + * @return string + * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function requestApiAccessToken(): string + { + $response = $this->getHttpClient()->get( + $this->baseUrl . '/cgi-bin/gettoken', + [ + 'query' => array_filter( + [ + 'corpid' => $this->config->get('corp_id') ?? $this->config->get('corpid') ?? $this->config->get('client_id'), + 'corpsecret' => $this->config->get('corp_secret') ?? $this->config->get('corpsecret') ?? $this->config->get('client_secret'), + ] + ), + ] + ); + + $response = \json_decode($response->getBody(), true) ?? []; + + if (($response['errcode'] ?? 1) > 0) { + throw new AuthorizeFailedException('Failed to get api access_token:' . $response['errmsg'] ?? 'Unknown.', $response); + } + + return $response['access_token']; + } + + protected function getTokenUrl(): string + { + return ''; + } +} diff --git a/vendor/overtrue/socialite/src/Providers/Weibo.php b/vendor/overtrue/socialite/src/Providers/Weibo.php new file mode 100644 index 0000000..cb7f55b --- /dev/null +++ b/vendor/overtrue/socialite/src/Providers/Weibo.php @@ -0,0 +1,108 @@ +buildAuthUrlFromBase($this->baseUrl.'/oauth2/authorize'); + } + + protected function getTokenUrl(): string + { + return $this->baseUrl.'/2/oauth2/access_token'; + } + + /** + * @param string $code + * + * @return array + */ + protected function getTokenFields(string $code): array + { + return parent::getTokenFields($code) + ['grant_type' => 'authorization_code']; + } + + /** + * @param string $token + * + * @return array + * @throws \GuzzleHttp\Exception\GuzzleException + * + * @throws \Overtrue\Socialite\Exceptions\InvalidTokenException + */ + protected function getUserByToken(string $token): array + { + $uid = $this->getTokenPayload($token)['uid'] ?? null; + + if (empty($uid)) { + throw new InvalidTokenException('Invalid token.', $token); + } + + $response = $this->getHttpClient()->get($this->baseUrl.'/2/users/show.json', [ + 'query' => [ + 'uid' => $uid, + 'access_token' => $token, + ], + 'headers' => [ + 'Accept' => 'application/json', + ], + ]); + + return \json_decode($response->getBody(), true) ?? []; + } + + /** + * @param string $token + * + * @return array + * @throws \GuzzleHttp\Exception\GuzzleException + * @throws \Overtrue\Socialite\Exceptions\InvalidTokenException + */ + protected function getTokenPayload(string $token): array + { + $response = $this->getHttpClient()->post($this->baseUrl.'/oauth2/get_token_info', [ + 'query' => [ + 'access_token' => $token, + ], + 'headers' => [ + 'Accept' => 'application/json', + ], + ]); + + $response = \json_decode($response->getBody(), true) ?? []; + + if (empty($response['uid'])) { + throw new InvalidTokenException(\sprintf('Invalid token %s', $token), $token); + } + + return $response; + } + + /** + * @param array $user + * + * @return \Overtrue\Socialite\User + */ + protected function mapUserToObject(array $user): User + { + return new User([ + 'id' => $user['id'] ?? null, + 'nickname' => $user['screen_name'] ?? null, + 'name' => $user['name'] ?? null, + 'email' => $user['email'] ?? null, + 'avatar' => $user['avatar_large'] ?? null, + ]); + } +} diff --git a/vendor/overtrue/socialite/src/SocialiteManager.php b/vendor/overtrue/socialite/src/SocialiteManager.php new file mode 100644 index 0000000..b68ae89 --- /dev/null +++ b/vendor/overtrue/socialite/src/SocialiteManager.php @@ -0,0 +1,146 @@ + Providers\QQ::class, + Providers\Tapd::NAME => Providers\Tapd::class, + Providers\Weibo::NAME => Providers\Weibo::class, + Providers\Alipay::NAME => Providers\Alipay::class, + Providers\QCloud::NAME => Providers\QCloud::class, + Providers\GitHub::NAME => Providers\GitHub::class, + Providers\Google::NAME => Providers\Google::class, + Providers\WeChat::NAME => Providers\WeChat::class, + Providers\Douban::NAME => Providers\Douban::class, + Providers\WeWork::NAME => Providers\WeWork::class, + Providers\DouYin::NAME => Providers\DouYin::class, + Providers\Taobao::NAME => Providers\Taobao::class, + Providers\FeiShu::NAME => Providers\FeiShu::class, + Providers\Outlook::NAME => Providers\Outlook::class, + Providers\Linkedin::NAME => Providers\Linkedin::class, + Providers\Facebook::NAME => Providers\Facebook::class, + Providers\DingTalk::NAME => Providers\DingTalk::class, + Providers\OpenWeWork::NAME => Providers\OpenWeWork::class, + Providers\Line::NAME => Providers\Line::class, + Providers\Gitee::NAME => Providers\Gitee::class, + ]; + + public function __construct(array $config) + { + $this->config = new Config($config); + } + + /** + * @param \Overtrue\Socialite\Config $config + * + * @return $this + */ + public function config(Config $config) + { + $this->config = $config; + + return $this; + } + + /** + * @param string $name + * + * @return \Overtrue\Socialite\Contracts\ProviderInterface + */ + public function create(string $name): ProviderInterface + { + $name = strtolower($name); + + if (!isset($this->resolved[$name])) { + $this->resolved[$name] = $this->createProvider($name); + } + + return $this->resolved[$name]; + } + + /** + * @param string $name + * @param \Closure $callback + * + * @return $this + */ + public function extend(string $name, Closure $callback): self + { + $this->customCreators[strtolower($name)] = $callback; + + return $this; + } + + /** + * @return \Overtrue\Socialite\Contracts\ProviderInterface[] + */ + public function getResolvedProviders(): array + { + return $this->resolved; + } + + /** + * @param string $provider + * @param array $config + * + * @return \Overtrue\Socialite\Contracts\ProviderInterface + */ + public function buildProvider(string $provider, array $config): ProviderInterface + { + return new $provider($config); + } + + /** + * @param string $name + * + * @return ProviderInterface + * @throws \InvalidArgumentException + * + */ + protected function createProvider(string $name) + { + $config = $this->config->get($name, []); + $provider = $config['provider'] ?? $name; + + if (isset($this->customCreators[$provider])) { + return $this->callCustomCreator($provider, $config); + } + + if (!$this->isValidProvider($provider)) { + throw new InvalidArgumentException("Provider [$provider] not supported."); + } + + return $this->buildProvider($this->providers[$provider] ?? $provider, $config); + } + + /** + * @param string $driver + * @param array $config + * + * @return ProviderInterface + */ + protected function callCustomCreator(string $driver, array $config): ProviderInterface + { + return $this->customCreators[$driver]($config); + } + + /** + * @param string $provider + * + * @return bool + */ + protected function isValidProvider(string $provider): bool + { + return isset($this->providers[$provider]) || is_subclass_of($provider, ProviderInterface::class); + } +} diff --git a/vendor/overtrue/socialite/src/Traits/HasAttributes.php b/vendor/overtrue/socialite/src/Traits/HasAttributes.php new file mode 100644 index 0000000..1f78ea6 --- /dev/null +++ b/vendor/overtrue/socialite/src/Traits/HasAttributes.php @@ -0,0 +1,87 @@ +attributes; + } + + /** + * @param string $name + * @param string $default + * + * @return mixed + */ + public function getAttribute($name, $default = null) + { + return isset($this->attributes[$name]) ? $this->attributes[$name] : $default; + } + + /** + * @param string $name + * @param mixed $value + * + * @return $this + */ + public function setAttribute($name, $value) + { + $this->attributes[$name] = $value; + + return $this; + } + + /** + * @param array $attributes + * + * @return $this + */ + public function merge(array $attributes) + { + $this->attributes = array_merge($this->attributes, $attributes); + + return $this; + } + + public function offsetExists($offset) + { + return array_key_exists($offset, $this->attributes); + } + + public function offsetGet($offset) + { + return $this->getAttribute($offset); + } + + public function offsetSet($offset, $value) + { + $this->setAttribute($offset, $value); + } + + public function offsetUnset($offset) + { + unset($this->attributes[$offset]); + } + + public function __get($property) + { + return $this->getAttribute($property); + } + + public function toArray(): array + { + return $this->getAttributes(); + } + + public function toJSON(): string + { + return \json_encode($this->getAttributes(), JSON_UNESCAPED_UNICODE); + } +} diff --git a/vendor/overtrue/socialite/src/User.php b/vendor/overtrue/socialite/src/User.php new file mode 100644 index 0000000..9d68701 --- /dev/null +++ b/vendor/overtrue/socialite/src/User.php @@ -0,0 +1,145 @@ +attributes = $attributes; + $this->provider = $provider; + } + + public function getId() + { + return $this->getAttribute('id') ?? $this->getEmail(); + } + + public function getNickname(): ?string + { + return $this->getAttribute('nickname') ?? $this->getName(); + } + + public function getName(): ?string + { + return $this->getAttribute('name'); + } + + public function getEmail(): ?string + { + return $this->getAttribute('email'); + } + + public function getAvatar(): ?string + { + return $this->getAttribute('avatar'); + } + + public function setAccessToken(string $token): self + { + $this->setAttribute('access_token', $token); + + return $this; + } + + public function getAccessToken(): ?string + { + return $this->getAttribute('access_token'); + } + + public function setRefreshToken(?string $refreshToken): self + { + $this->setAttribute('refresh_token', $refreshToken); + + return $this; + } + + public function getRefreshToken(): ?string + { + return $this->getAttribute('refresh_token'); + } + + public function setExpiresIn(int $expiresIn): self + { + $this->setAttribute('expires_in', $expiresIn); + + return $this; + } + + public function getExpiresIn(): ?int + { + return $this->getAttribute('expires_in'); + } + + public function setRaw(array $user): self + { + $this->setAttribute('raw', $user); + + return $this; + } + + public function getRaw(): array + { + return $this->getAttribute('raw'); + } + + public function setTokenResponse(array $response) + { + $this->setAttribute('token_response', $response); + + return $this; + } + + public function getTokenResponse() + { + return $this->getAttribute('token_response'); + } + + public function jsonSerialize(): array + { + return $this->attributes; + } + + public function serialize() + { + return serialize($this->attributes); + } + + public function unserialize($serialized) + { + $this->attributes = unserialize($serialized) ?: []; + } + + /** + * @return \Overtrue\Socialite\Contracts\ProviderInterface + */ + public function getProvider(): \Overtrue\Socialite\Contracts\ProviderInterface + { + return $this->provider; + } + + /** + * @param \Overtrue\Socialite\Contracts\ProviderInterface $provider + * + * @return $this + */ + public function setProvider(\Overtrue\Socialite\Contracts\ProviderInterface $provider) + { + $this->provider = $provider; + + return $this; + } +} diff --git a/vendor/overtrue/socialite/tests/OAuthTest.php b/vendor/overtrue/socialite/tests/OAuthTest.php new file mode 100644 index 0000000..90f06c9 --- /dev/null +++ b/vendor/overtrue/socialite/tests/OAuthTest.php @@ -0,0 +1,205 @@ + 'fake_client_id', + 'client_secret' => 'fake_client_secret', + ]; + $provider = new OAuthTestProviderStub($config); + + $this->assertSame('http://auth.url?client_id=fake_client_id&scope=info&response_type=code', $provider->redirect()); + } + + public function test_it_can_get_auth_url_with_redirect() + { + // 手动配置 + $config = [ + 'client_id' => 'fake_client_id', + 'client_secret' => 'fake_client_secret', + ]; + $provider = new OAuthTestProviderStub($config); + + $this->assertSame('http://auth.url?client_id=fake_client_id&redirect_uri=fake_redirect&scope=info&response_type=code', $provider->redirect('fake_redirect')); + + // 用配置属性配置 + $config += ['redirect_url' => 'fake_redirect']; + $provider = new OAuthTestProviderStub($config); + + $this->assertSame('http://auth.url?client_id=fake_client_id&redirect_uri=fake_redirect&scope=info&response_type=code', $provider->redirect('fake_redirect')); + } + + public function test_it_can_get_auth_url_with_scopes() + { + $config = [ + 'client_id' => 'fake_client_id', + 'client_secret' => 'fake_client_secret', + ]; + $provider = new OAuthTestProviderStub($config); + $url = $provider->scopes(['test_info', 'test_email'])->redirect(); + + $this->assertSame('http://auth.url?client_id=fake_client_id&scope=test_info%2Ctest_email&response_type=code', $url); + + // 切换scope分割符 + $url = $provider->scopes(['test_info', 'test_email'])->withScopeSeparator(' ')->redirect(); + $this->assertSame('http://auth.url?client_id=fake_client_id&scope=test_info%20test_email&response_type=code', $url); + } + + public function test_it_can_get_auth_url_with_state() + { + $config = [ + 'client_id' => 'fake_client_id', + 'client_secret' => 'fake_client_secret', + ]; + $provider = new OAuthTestProviderStub($config); + $url = $provider->withState(123456)->redirect(); + + $this->assertSame('http://auth.url?client_id=fake_client_id&scope=info&response_type=code&state=123456', $url); + } + + public function test_it_can_get_token() + { + $config = [ + 'client_id' => 'fake_client_id', + 'client_secret' => 'fake_client_secret', + ]; + $provider = new OAuthTestProviderStub($config); + $response = m::mock(\Psr\Http\Message\ResponseInterface::class); + + $response->shouldReceive('getBody')->andReturn($response); + $response->shouldReceive('getContents')->andReturn([ + 'access_token' => 'fake_access_token', + 'refresh_token' => 'fake_refresh_token', + 'expires_in' => 123456, + ]); + + $provider->getHttpClient()->shouldReceive('post')->with('http://token.url', [ + 'form_params' => [ + 'client_id' => 'fake_client_id', + 'client_secret' => 'fake_client_secret', + 'code' => 'fake_code', + 'redirect_uri' => null, + ], + ])->andReturn($response); + + $this->assertSame([ + 'access_token' => 'fake_access_token', + 'refresh_token' => 'fake_refresh_token', + 'expires_in' => 123456, + ], $provider->tokenFromCode('fake_code')); + } + + public function test_it_can_get_user_by_token() + { + $config = [ + 'client_id' => 'fake_client_id', + 'client_secret' => 'fake_client_secret', + ]; + $provider = new OAuthTestProviderStub($config); + + $user = $provider->userFromToken('fake_access_token'); + + $this->assertSame('foo', $user->getId()); + $this->assertSame(['id' => 'foo'], $user->getRaw()); + $this->assertSame('fake_access_token', $user->getAccessToken()); + } + + public function test_it_can_get_user_by_code() + { + $config = [ + 'client_id' => 'fake_client_id', + 'client_secret' => 'fake_client_secret', + ]; + $provider = new OAuthTestProviderStub($config); + + $response = m::mock(\Psr\Http\Message\ResponseInterface::class); + $response->shouldReceive('getBody')->andReturn($response); + $response->shouldReceive('getContents')->andReturn([ + 'access_token' => 'fake_access_token', + 'refresh_token' => 'fake_refresh_token', + 'expires_in' => 123456, + ]); + + $provider->getHttpClient()->shouldReceive('post')->with('http://token.url', [ + 'form_params' => [ + 'client_id' => 'fake_client_id', + 'client_secret' => 'fake_client_secret', + 'code' => 'fake_code', + 'redirect_uri' => null, + ], + ])->andReturn($response); + + $this->assertSame([ + 'access_token' => 'fake_access_token', + 'refresh_token' => 'fake_refresh_token', + 'expires_in' => 123456, + ], $provider->tokenFromCode('fake_code')); + + $user = $provider->userFromCode('fake_code'); + $tokenResponse = [ + 'access_token' => 'fake_access_token', + 'refresh_token' => 'fake_refresh_token', + 'expires_in' => 123456, + ]; + + $this->assertSame('foo', $user->getId()); + $this->assertSame($tokenResponse, $user->getTokenResponse()); + $this->assertSame('fake_access_token', $user->getAccessToken()); + $this->assertSame('fake_refresh_token', $user->getRefreshToken()); + } +} + +class OAuthTestProviderStub extends Base +{ + public $http; + protected array $scopes = ['info']; + protected int $encodingType = PHP_QUERY_RFC3986; + + protected function getAuthUrl(): string + { + $url = 'http://auth.url'; + + return $this->buildAuthUrlFromBase($url); + } + + protected function getTokenUrl(): string + { + return 'http://token.url'; + } + + protected function getUserByToken(string $token): array + { + return ['id' => 'foo']; + } + + protected function mapUserToObject(array $user): User + { + return new User(['id' => $user['id']]); + } + + /** + * Get a fresh instance of the Guzzle HTTP client. + * + * @return \GuzzleHttp\Client + */ + public function getHttpClient(): \GuzzleHttp\Client + { + if ($this->http) { + return $this->http; + } + + return $this->http = m::mock(\GuzzleHttp\Client::class); + } +} diff --git a/vendor/overtrue/socialite/tests/Providers/FeiShuTest.php b/vendor/overtrue/socialite/tests/Providers/FeiShuTest.php new file mode 100644 index 0000000..59cc82f --- /dev/null +++ b/vendor/overtrue/socialite/tests/Providers/FeiShuTest.php @@ -0,0 +1,246 @@ + 'xxxxx', + 'app_secret' => 'yyyyy', + 'app_mode' => 'internal' + ]; + $f = new FeiShu($config); + $rf = new \ReflectionObject($f); + + $this->assertEquals('xxxxx', $f->getClientId()); + $this->assertEquals('yyyyy', $f->getClientSecret()); + + $rfProperty = $rf->getProperty('isInternalApp'); + $rfProperty->setAccessible(true); + $this->assertEquals(true, $rfProperty->getValue($f)); + + // diff filed way + $config = [ + 'client_id' => 'xxxxx', + 'client_secret' => 'yyyyy', + 'mode' => 'internal' + ]; + + $f = new FeiShu($config); + $rf = new \ReflectionObject($f); + + $this->assertEquals('xxxxx', $f->getClientId()); + $this->assertEquals('yyyyy', $f->getClientSecret()); + $rfProperty = $rf->getProperty('isInternalApp'); + $rfProperty->setAccessible(true); + $this->assertEquals(true, $rfProperty->getValue($f)); + + // no mode config way + $config = [ + 'client_id' => 'xxxxx', + 'client_secret' => 'yyyyy', + ]; + + $f = new FeiShu($config); + $rf = new \ReflectionObject($f); + + $this->assertEquals('xxxxx', $f->getClientId()); + $this->assertEquals('yyyyy', $f->getClientSecret()); + $rfProperty = $rf->getProperty('isInternalApp'); + $rfProperty->setAccessible(true); + $this->assertEquals(false, $rfProperty->getValue($f)); + } + + public function testProviderWithInternalAppModeWork() + { + $config = [ + 'client_id' => 'xxxxx', + 'client_secret' => 'yyyyy', + ]; + + $f = new FeiShu($config); + $rf = new \ReflectionObject($f); + + $rfProperty = $rf->getProperty('isInternalApp'); + $rfProperty->setAccessible(true); + + $f->withInternalAppMode(); + $this->assertEquals(true, $rfProperty->getValue($f)); + + $f->withDefaultMode(); + $this->assertEquals(false, $rfProperty->getValue($f)); + } + + public function testProviderWithAppTicketWork() + { + $config = [ + 'client_id' => 'xxxxx', + 'client_secret' => 'yyyyy', + ]; + + $f = new FeiShu($config); + $f->withAppTicket('app_ticket'); + $this->assertEquals('app_ticket', $f->getConfig()->get('app_ticket')); + } + + public function testConfigAppAccessTokenWithDefaultModeNoAppTicketWork() + { + $config = [ + 'client_id' => 'xxxxx', + 'client_secret' => 'yyyyy', + ]; + + $f = new FeiShu($config); + $fr = new \ReflectionObject($f); + $frClient = $fr->getProperty('httpClient'); + $frClient->setAccessible(true); + $ff = new \ReflectionMethod(FeiShu::class, 'configAppAccessToken'); + + $mock = new MockHandler([ + new Response(403, []), + new Response(200, [], json_encode([ + 'app_access_token' => 'app_access_token' + ])) + ]); + + $handler = HandlerStack::create($mock); + $client = new Client(['handler' => $handler]); + $frClient->setValue($f, $client); + $ff->setAccessible(true); + + // 默认模式下没有 app_ticket + $this->expectException(InvalidTicketException::class); + $ff->invoke($f); + + $ff->invoke($f); + $f->withAppTicket('app_ticket'); + $this->assertEquals('app_access_token', $f->getConfig()->get('app_access_token')); + + $this->expectException(InvalidTokenException::class); + $ff->invoke($f); + } + + public function testConfigAppAccessTokenWithDefaultModeAndAppTicketWorkInBadResponse() + { + $config = [ + 'client_id' => 'xxxxx', + 'client_secret' => 'yyyyy', + ]; + + $f = new FeiShu($config); + $fr = new \ReflectionObject($f); + $frClient = $fr->getProperty('httpClient'); + $frClient->setAccessible(true); + $ff = new \ReflectionMethod(FeiShu::class, 'configAppAccessToken'); + + $mock = new MockHandler([ + new Response(200, []), + ]); + + $handler = HandlerStack::create($mock); + $client = new Client(['handler' => $handler]); + $frClient->setValue($f, $client); + $ff->setAccessible(true); + + $this->expectException(InvalidTokenException::class); + $ff->invoke($f->withAppTicket('app_ticket')); + } + + public function testConfigAppAccessTokenWithDefaultModeAndAppTicketWorkInGoodResponse() + { + $config = [ + 'client_id' => 'xxxxx', + 'client_secret' => 'yyyyy', + ]; + + $f = new FeiShu($config); + $fr = new \ReflectionObject($f); + $frClient = $fr->getProperty('httpClient'); + $frClient->setAccessible(true); + $ff = new \ReflectionMethod(FeiShu::class, 'configAppAccessToken'); + + $mock = new MockHandler([ + new Response(200, [], json_encode([ + 'app_access_token' => 'app_access_token' + ])) + ]); + + $handler = HandlerStack::create($mock); + $client = new Client(['handler' => $handler]); + $frClient->setValue($f, $client); + $ff->setAccessible(true); + + $this->assertEquals(null, $f->getConfig()->get('app_access_token')); + $ff->invoke($f->withAppTicket('app_ticket')); + $this->assertEquals('app_access_token', $f->getConfig()->get('app_access_token')); + } + + public function testConfigAppAccessTokenWithInternalInBadResponse() + { + $config = [ + 'client_id' => 'xxxxx', + 'client_secret' => 'yyyyy', + 'mode' => 'internal' + ]; + + $f = new FeiShu($config); + $fr = new \ReflectionObject($f); + $frClient = $fr->getProperty('httpClient'); + $frClient->setAccessible(true); + $ff = new \ReflectionMethod(FeiShu::class, 'configAppAccessToken'); + + $mock = new MockHandler([ + new Response(200, []), + ]); + + $handler = HandlerStack::create($mock); + $client = new Client(['handler' => $handler]); + $frClient->setValue($f, $client); + $ff->setAccessible(true); + + $this->expectException(InvalidTokenException::class); + $ff->invoke($f); + } + + public function testConfigAppAccessTokenWithInternalInGoodResponse() + { + $config = [ + 'client_id' => 'xxxxx', + 'client_secret' => 'yyyyy', + 'mode' => 'internal' + ]; + + $f = new FeiShu($config); + $fr = new \ReflectionObject($f); + $frClient = $fr->getProperty('httpClient'); + $frClient->setAccessible(true); + $ff = new \ReflectionMethod(FeiShu::class, 'configAppAccessToken'); + + $mock = new MockHandler([ + new Response(200, [], json_encode([ + 'app_access_token' => 'app_access_token' + ])) + ]); + + $handler = HandlerStack::create($mock); + $client = new Client(['handler' => $handler]); + $frClient->setValue($f, $client); + $ff->setAccessible(true); + + $this->assertEquals(null, $f->getConfig()->get('app_access_token')); + $ff->invoke($f); + $this->assertEquals('app_access_token', $f->getConfig()->get('app_access_token')); + } +} diff --git a/vendor/overtrue/socialite/tests/Providers/WeWorkTest.php b/vendor/overtrue/socialite/tests/Providers/WeWorkTest.php new file mode 100644 index 0000000..22e6f34 --- /dev/null +++ b/vendor/overtrue/socialite/tests/Providers/WeWorkTest.php @@ -0,0 +1,46 @@ + 'ww100000a5f2191', + 'client_secret' => 'client_secret', + 'redirect' => 'http://www.oa.com', + ])) + ->setAgentId('1000000') + ->redirect(); + + $this->assertSame('https://open.work.weixin.qq.com/wwopen/sso/qrConnect?appid=ww100000a5f2191&agentid=1000000&redirect_uri=http%3A%2F%2Fwww.oa.com#wechat_redirect', $response); + } + + public function testOAuthWithAgentId() + { + $response = (new WeWork([ + 'client_id' => 'CORPID', + 'client_secret' => 'client_secret', + 'redirect' => 'REDIRECT_URI', + ])) + ->scopes(['snsapi_base']) + ->redirect(); + + $this->assertSame('https://open.weixin.qq.com/connect/oauth2/authorize?appid=CORPID&redirect_uri=REDIRECT_URI&response_type=code&scope=snsapi_base#wechat_redirect', $response); + } + + public function testOAuthWithoutAgentId() + { + $response = (new WeWork([ + 'client_id' => 'CORPID', + 'client_secret' => 'client_secret', + 'redirect' => 'REDIRECT_URI', + ])) + ->scopes(['snsapi_base']) + ->redirect(); + + $this->assertSame('https://open.weixin.qq.com/connect/oauth2/authorize?appid=CORPID&redirect_uri=REDIRECT_URI&response_type=code&scope=snsapi_base#wechat_redirect', $response); + } +} diff --git a/vendor/overtrue/socialite/tests/Providers/WechatTest.php b/vendor/overtrue/socialite/tests/Providers/WechatTest.php new file mode 100644 index 0000000..03e5395 --- /dev/null +++ b/vendor/overtrue/socialite/tests/Providers/WechatTest.php @@ -0,0 +1,118 @@ + 'client_id', + 'client_secret' => 'client_secret', + 'redirect_url' => 'http://localhost/socialite/callback.php', + ]))->redirect(); + + $this->assertStringStartsWith('https://open.weixin.qq.com/connect/qrconnect', $response); + $this->assertRegExp('/redirect_uri=http%3A%2F%2Flocalhost%2Fsocialite%2Fcallback.php/', $response); + } + + public function testWeChatProviderTokenUrlAndRequestFields() + { + $provider = new WeChat([ + 'client_id' => 'client_id', + 'client_secret' => 'client_secret', + 'redirect_url' => 'http://localhost/socialite/callback.php', + ]); + + $getTokenUrl = new ReflectionMethod(WeChat::class, 'getTokenUrl'); + $getTokenUrl->setAccessible(true); + + $getTokenFields = new ReflectionMethod(WeChat::class, 'getTokenFields'); + $getTokenFields->setAccessible(true); + + $getCodeFields = new ReflectionMethod(WeChat::class, 'getCodeFields'); + $getCodeFields->setAccessible(true); + + $this->assertSame('https://api.weixin.qq.com/sns/oauth2/access_token', $getTokenUrl->invoke($provider)); + $this->assertSame([ + 'appid' => 'client_id', + 'secret' => 'client_secret', + 'code' => 'iloveyou', + 'grant_type' => 'authorization_code', + ], $getTokenFields->invoke($provider, 'iloveyou')); + + $this->assertSame([ + 'appid' => 'client_id', + 'redirect_uri' => 'http://localhost/socialite/callback.php', + 'response_type' => 'code', + 'scope' => 'snsapi_login', + 'state' => 'wechat-state', + 'connect_redirect' => 1, + ], $getCodeFields->invoke($provider->withState('wechat-state'))); + } + + public function testOpenPlatformComponent() + { + $provider = new WeChat([ + 'client_id' => 'client_id', + 'client_secret' => null, + 'redirect' => 'redirect-url', + 'component' => [ + 'id' => 'component-app-id', + 'token' => 'token', + ], + ]); + $getTokenUrl = new ReflectionMethod(WeChat::class, 'getTokenUrl'); + $getTokenUrl->setAccessible(true); + + $getTokenFields = new ReflectionMethod(WeChat::class, 'getTokenFields'); + $getTokenFields->setAccessible(true); + + $getCodeFields = new ReflectionMethod(WeChat::class, 'getCodeFields'); + $getCodeFields->setAccessible(true); + + + $this->assertSame([ + 'appid' => 'client_id', + 'redirect_uri' => 'redirect-url', + 'response_type' => 'code', + 'scope' => 'snsapi_base', + 'state' => 'state', + 'connect_redirect' => 1, + 'component_appid' => 'component-app-id', + ], $getCodeFields->invoke($provider->withState('state'))); + + $this->assertSame([ + 'appid' => 'client_id', + 'component_appid' => 'component-app-id', + 'component_access_token' => 'token', + 'code' => 'simcode', + 'grant_type' => 'authorization_code', + ], $getTokenFields->invoke($provider, 'simcode')); + + $this->assertSame('https://api.weixin.qq.com/sns/oauth2/component/access_token', $getTokenUrl->invoke($provider)); + } + + public function testOpenPlatformComponentWithCustomParameters() + { + $provider = new WeChat([ + 'client_id' => 'client_id', + 'client_secret' => null, + 'redirect' => 'redirect-url', + 'component' => [ + 'id' => 'component-app-id', + 'token' => 'token', + ], + ]); + + $getCodeFields = new ReflectionMethod(WeChat::class, 'getCodeFields'); + $getCodeFields->setAccessible(true); + + $provider->with(['foo' => 'bar']); + + $fields = $getCodeFields->invoke($provider->withState('wechat-state')); + $this->assertArrayHasKey('foo', $fields); + $this->assertSame('bar', $fields['foo']); + } +} diff --git a/vendor/overtrue/socialite/tests/SocialiteManagerTest.php b/vendor/overtrue/socialite/tests/SocialiteManagerTest.php new file mode 100644 index 0000000..26dc1e1 --- /dev/null +++ b/vendor/overtrue/socialite/tests/SocialiteManagerTest.php @@ -0,0 +1,109 @@ + [ + 'provider' => 'github', + 'client_id' => 'foo-app-id', + 'client_secret' => 'your-app-secret', + 'redirect' => 'http://localhost/socialite/callback.php', + ], + 'bar' => [ + 'provider' => 'github', + 'client_id' => 'bar-app-id', + 'client_secret' => 'your-app-secret', + 'redirect' => 'http://localhost/socialite/callback.php', + ], + ]; + + $manager = new SocialiteManager($config); + + $this->assertInstanceOf(GitHub::class, $manager->create('foo')); + $this->assertSame('foo-app-id', $manager->create('foo')->getClientId()); + + $this->assertInstanceOf(GitHub::class, $manager->create('bar')); + $this->assertSame('bar-app-id', $manager->create('bar')->getClientId()); + + // from name + $config = [ + 'github' => [ + 'client_id' => 'your-app-id', + 'client_secret' => 'your-app-secret', + 'redirect' => 'http://localhost/socialite/callback.php', + ], + ]; + + $manager = new SocialiteManager($config); + + $this->assertInstanceOf(GitHub::class, $manager->create('github')); + $this->assertSame('your-app-id', $manager->create('github')->getClientId()); + } + + public function test_it_can_create_from_custom_creator() + { + $config = [ + 'foo' => [ + 'provider' => 'myprovider', + 'client_id' => 'your-app-id', + 'client_secret' => 'your-app-secret', + 'redirect' => 'http://localhost/socialite/callback.php', + ], + ]; + + $manager = new SocialiteManager($config); + + $manager->extend('myprovider', function ($config) { + return new DummyProviderForCustomProviderTest($config); + }); + + $this->assertInstanceOf(DummyProviderForCustomProviderTest::class, $manager->create('foo')); + } + + public function test_it_can_create_from_custom_provider_class() + { + $config = [ + 'foo' => [ + 'provider' => DummyProviderForCustomProviderTest::class, + 'client_id' => 'your-app-id', + 'client_secret' => 'your-app-secret', + 'redirect' => 'http://localhost/socialite/callback.php', + ], + ]; + + $manager = new SocialiteManager($config); + + $this->assertInstanceOf(DummyProviderForCustomProviderTest::class, $manager->create('foo')); + } +} + +class DummyProviderForCustomProviderTest extends Base +{ + protected function getAuthUrl(): string + { + return ''; + } + + protected function getTokenUrl(): string + { + return ''; + } + + protected function getUserByToken(string $token): array + { + return []; + } + + protected function mapUserToObject(array $user): User + { + return new User([]); + } +} diff --git a/vendor/overtrue/socialite/tests/UserTest.php b/vendor/overtrue/socialite/tests/UserTest.php new file mode 100644 index 0000000..a8226e8 --- /dev/null +++ b/vendor/overtrue/socialite/tests/UserTest.php @@ -0,0 +1,51 @@ +assertSame('[]', json_encode(new User([]))); + $this->assertSame('{"access_token":"mock-token"}', json_encode(new User(['access_token' => 'mock-token']))); + } + + public function test_it_can_get_refresh_token() + { + $user = new User([ + 'name' => 'fake_name', + 'access_token' => 'mock-token', + 'refresh_token' => 'fake_refresh', + ]); + + // 能通过用 User 对象获取 refresh token + $this->assertSame('fake_refresh', $user->getRefreshToken()); + $this->assertSame('{"name":"fake_name","access_token":"mock-token","refresh_token":"fake_refresh"}', json_encode($user)); + + // 无 refresh_token 属性返回 null + $user = new User([]); + $this->assertSame(null, $user->getRefreshToken()); + // 能通过 setRefreshToken() 设置 + $user->setRefreshToken('fake_refreshToken'); + $this->assertSame('fake_refreshToken', $user->getRefreshToken()); + $this->assertSame('{"refresh_token":"fake_refreshToken"}', json_encode($user)); + } + + public function test_it_can_set_raw_data() + { + $user = new User([]); + $data = ['data' => 'raw']; + + $user->setRaw($data); + $this->assertSame($data, $user->getRaw()); + $this->assertSame(json_encode(['raw' => ['data' => 'raw']]), json_encode($user)); + } + + public function test_ie_can_get_attribute_by_magic_method() + { + $user = new User(['xx' => 'data']); + + $this->assertSame('data', $user->xx); + } +} diff --git a/vendor/overtrue/wechat/CHANGELOG.md b/vendor/overtrue/wechat/CHANGELOG.md new file mode 100644 index 0000000..df42aa6 --- /dev/null +++ b/vendor/overtrue/wechat/CHANGELOG.md @@ -0,0 +1,1401 @@ +# Change Log + +## [Unreleased](https://github.com/overtrue/wechat/tree/HEAD) + +[Full Changelog](https://github.com/overtrue/wechat/compare/4.0.0...HEAD) + +**Closed issues:** + +- 能否增加对symfony4的支持 [\#1044](https://github.com/overtrue/wechat/issues/1044) + +## [4.0.0](https://github.com/overtrue/wechat/tree/4.0.0) (2017-12-11) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.21...4.0.0) + +**Closed issues:** + +- filecache.php 文件 createPathIfNeeded\(string $path\) : bool [\#1046](https://github.com/overtrue/wechat/issues/1046) +- 沙箱模式的Notify总是出错:Invalid request payloads. [\#1045](https://github.com/overtrue/wechat/issues/1045) +- 你好我是SwooleDistributed框架的作者 [\#1040](https://github.com/overtrue/wechat/issues/1040) + +## [3.3.21](https://github.com/overtrue/wechat/tree/3.3.21) (2017-12-10) +[Full Changelog](https://github.com/overtrue/wechat/compare/4.0.0-beta.4...3.3.21) + +**Closed issues:** + +- 开启开放平台自动路由后报错 [\#1042](https://github.com/overtrue/wechat/issues/1042) +- 关于3.x升级4.x的问题 [\#1041](https://github.com/overtrue/wechat/issues/1041) +- 获取不了unionid [\#1038](https://github.com/overtrue/wechat/issues/1038) +- authorizer\_refresh\_token刷新问题 [\#1033](https://github.com/overtrue/wechat/issues/1033) +- lumen+swoole无法获取request信息。 [\#1032](https://github.com/overtrue/wechat/issues/1032) +- 上传素材报错, empty post data hint [\#1031](https://github.com/overtrue/wechat/issues/1031) +- 开放平台不支持全网发布接入检测(第三方) [\#1029](https://github.com/overtrue/wechat/issues/1029) +- 公众号模板消息不兼容跳转小程序 [\#1025](https://github.com/overtrue/wechat/issues/1025) +- swoole下无法使用 [\#1017](https://github.com/overtrue/wechat/issues/1017) +- 请教有没有高清素材下载方法? [\#997](https://github.com/overtrue/wechat/issues/997) +- 自动回复多图文素材,错误 [\#996](https://github.com/overtrue/wechat/issues/996) +- xml解释失败 [\#989](https://github.com/overtrue/wechat/issues/989) +- Curl error 77 [\#982](https://github.com/overtrue/wechat/issues/982) +- 3.1.10 H5支付不晓得算不算BUG的BUG [\#968](https://github.com/overtrue/wechat/issues/968) +- 请问是否有遇到微信扫码或内部打开外部网站出现请求2次的情况 [\#963](https://github.com/overtrue/wechat/issues/963) +- 请问4.0何时正式发布? [\#962](https://github.com/overtrue/wechat/issues/962) +- dev-master 不能用于laravel5.1 [\#952](https://github.com/overtrue/wechat/issues/952) +- 请教小程序的模板消息是否支持 [\#920](https://github.com/overtrue/wechat/issues/920) +- 模板消息的颜色设置问题 [\#914](https://github.com/overtrue/wechat/issues/914) +- 英文文档跳转问题 [\#854](https://github.com/overtrue/wechat/issues/854) +- \[4.0\] 功能测试 [\#849](https://github.com/overtrue/wechat/issues/849) +- \[4.0\] 命名变更 [\#743](https://github.com/overtrue/wechat/issues/743) + +**Merged pull requests:** + +- Scrutinizer Auto-Fixes [\#1043](https://github.com/overtrue/wechat/pull/1043) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer)) +- 修复解密小程序转发信息数据\(wx.getShareInfo\)失败的问题 [\#1037](https://github.com/overtrue/wechat/pull/1037) ([yyqqing](https://github.com/yyqqing)) +- 修復微信支付沙盒模式的通知結果本地校驗失敗錯誤。 [\#1036](https://github.com/overtrue/wechat/pull/1036) ([amyuki](https://github.com/amyuki)) +- 修复 verifyTicket 使用不了自定义缓存的问题 [\#1034](https://github.com/overtrue/wechat/pull/1034) ([mingyoung](https://github.com/mingyoung)) +- 🚧 Auto discover extensions. [\#1027](https://github.com/overtrue/wechat/pull/1027) ([mingyoung](https://github.com/mingyoung)) + +## [4.0.0-beta.4](https://github.com/overtrue/wechat/tree/4.0.0-beta.4) (2017-11-21) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.20...4.0.0-beta.4) + +**Closed issues:** + +- Order对象的$attributes中不能传入device\_info参数 [\#1030](https://github.com/overtrue/wechat/issues/1030) +- 默认文件缓存的路径是否可以简单修改? [\#1023](https://github.com/overtrue/wechat/issues/1023) +- 3.3.17 版本获取 token 的问题 [\#1022](https://github.com/overtrue/wechat/issues/1022) +- \[V3\] AccessToken.php:243 [\#1021](https://github.com/overtrue/wechat/issues/1021) + +**Merged pull requests:** + +- more detailed cache key. [\#1028](https://github.com/overtrue/wechat/pull/1028) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#1026](https://github.com/overtrue/wechat/pull/1026) ([mingyoung](https://github.com/mingyoung)) +- Specify the request instance. [\#1024](https://github.com/overtrue/wechat/pull/1024) ([mingyoung](https://github.com/mingyoung)) + +## [3.3.20](https://github.com/overtrue/wechat/tree/3.3.20) (2017-11-13) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.18...3.3.20) + +## [3.3.18](https://github.com/overtrue/wechat/tree/3.3.18) (2017-11-11) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.17...3.3.18) + +**Closed issues:** + +- 临时二维码接口无法生成以字符串为参数的二维码 [\#1020](https://github.com/overtrue/wechat/issues/1020) +- 现金红包出现了500错误,跟进显示http\_error:true [\#1016](https://github.com/overtrue/wechat/issues/1016) +- 4.0 企业微信agent OA的错误 [\#1015](https://github.com/overtrue/wechat/issues/1015) +- 求thinkphp框架demo [\#1010](https://github.com/overtrue/wechat/issues/1010) +- 沙箱模式获取验签 key 时产生无限循环 , 无法正常获取 [\#1009](https://github.com/overtrue/wechat/issues/1009) +- JSSDK里面url导致的invalid signature错误 [\#1002](https://github.com/overtrue/wechat/issues/1002) +- 微信支付沙箱模式下,回调验签错误 [\#998](https://github.com/overtrue/wechat/issues/998) +- 有微信退款回调接口吗? [\#985](https://github.com/overtrue/wechat/issues/985) +- 希望兼容新出的微信H5支付 [\#966](https://github.com/overtrue/wechat/issues/966) +- 小程序生成无限量二维码接口缺少参数 [\#965](https://github.com/overtrue/wechat/issues/965) + +**Merged pull requests:** + +- 查询企业付款接口参数调整,加入企业付款到银行卡接口(RSA 参数加密待完成) [\#1019](https://github.com/overtrue/wechat/pull/1019) ([tianyong90](https://github.com/tianyong90)) +- Token AESKey can be null. [\#1013](https://github.com/overtrue/wechat/pull/1013) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#1012](https://github.com/overtrue/wechat/pull/1012) ([mingyoung](https://github.com/mingyoung)) +- Add Mini-program tester's binding/unbinding feature [\#1011](https://github.com/overtrue/wechat/pull/1011) ([caikeal](https://github.com/caikeal)) +- Apply fixes from StyleCI [\#1008](https://github.com/overtrue/wechat/pull/1008) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#1007](https://github.com/overtrue/wechat/pull/1007) ([overtrue](https://github.com/overtrue)) +- Added open-platform's mini-program code management [\#1003](https://github.com/overtrue/wechat/pull/1003) ([caikeal](https://github.com/caikeal)) +- Cleanup payment [\#1001](https://github.com/overtrue/wechat/pull/1001) ([mingyoung](https://github.com/mingyoung)) +- Unify get stream. [\#995](https://github.com/overtrue/wechat/pull/995) ([mingyoung](https://github.com/mingyoung)) +- Add appCode `page` param. [\#991](https://github.com/overtrue/wechat/pull/991) ([mingyoung](https://github.com/mingyoung)) + +## [3.3.17](https://github.com/overtrue/wechat/tree/3.3.17) (2017-10-27) +[Full Changelog](https://github.com/overtrue/wechat/compare/4.0.0-beta.3...3.3.17) + +**Closed issues:** + +- open platform component\_verify\_ticket 错误 [\#984](https://github.com/overtrue/wechat/issues/984) +- 请教下载语音后的文件不完整怎么处理? [\#980](https://github.com/overtrue/wechat/issues/980) +- 微信支付 API 调用下单解析缓缓 [\#977](https://github.com/overtrue/wechat/issues/977) +- 是否可以加入微信收款(个人转账版)服务接口 [\#970](https://github.com/overtrue/wechat/issues/970) +- 微信公众号消息加解密方式‘兼容模式’也需要填写‘aes\_key’参数,不能为空 [\#967](https://github.com/overtrue/wechat/issues/967) +- 第三方平台 接收消息一直报错 但是能回复消息 也会提示错误 [\#961](https://github.com/overtrue/wechat/issues/961) +- 中文官网无法访问 [\#960](https://github.com/overtrue/wechat/issues/960) +- laravel队列中使用了SDK报Component verify ticket does not exists. [\#958](https://github.com/overtrue/wechat/issues/958) +- 接口调用次数每日限额清零方法没有? [\#953](https://github.com/overtrue/wechat/issues/953) +- 获取access\_toekn失败之后抛出异常的地方,能够与其他地方统一使用下述这个 resolveResponse 返回数据 [\#951](https://github.com/overtrue/wechat/issues/951) +- 官网挂了 [\#950](https://github.com/overtrue/wechat/issues/950) +- 无法接收到菜单点击事件推送的消息 [\#949](https://github.com/overtrue/wechat/issues/949) +- 请教这个sdk是否可用于android 或者ios 登录? [\#948](https://github.com/overtrue/wechat/issues/948) +- 关于access token 后端分布式部署的中控服务器的问题 [\#947](https://github.com/overtrue/wechat/issues/947) +- 4.0 不支持laravel 5.2? [\#946](https://github.com/overtrue/wechat/issues/946) +- log不能打印出来 [\#945](https://github.com/overtrue/wechat/issues/945) +- EasyWeChat.org域名挂了?? [\#940](https://github.com/overtrue/wechat/issues/940) +- 微信静默授权的时候,页面上老是会显示一段很长的英文Redirecting to http://xxxx,很影响用户体验,有没有什么方法可以去掉,保留空白页,或者允许自定义显示内容 [\#939](https://github.com/overtrue/wechat/issues/939) +- 微信小程序生成二维码(接口B)微信扫描不出来结果 [\#938](https://github.com/overtrue/wechat/issues/938) +- 官网可否支持看老版本的文档? [\#937](https://github.com/overtrue/wechat/issues/937) +- 客服发送消息 收到的中文信息被unicode 编码 [\#935](https://github.com/overtrue/wechat/issues/935) +- 有多个商户时,订单通知的 $payment 怎么创建 [\#934](https://github.com/overtrue/wechat/issues/934) +- console中使用$app-\>user-\>get报错 [\#932](https://github.com/overtrue/wechat/issues/932) +- PC端扫描登录的问题 [\#930](https://github.com/overtrue/wechat/issues/930) +- 关于小程序支付的疑问 [\#912](https://github.com/overtrue/wechat/issues/912) +- 服务商api模式使用可以更加详细吗 [\#653](https://github.com/overtrue/wechat/issues/653) + +**Merged pull requests:** + +- 修正 微信公众号要求 所有接口使用 HTTPS 方式访问 [\#988](https://github.com/overtrue/wechat/pull/988) ([drogjh](https://github.com/drogjh)) +- Apply fixes from StyleCI [\#987](https://github.com/overtrue/wechat/pull/987) ([mingyoung](https://github.com/mingyoung)) +- 修复微信收款(个人转账版)商户添加、查询含有多余字段导致签名失败的问题 [\#986](https://github.com/overtrue/wechat/pull/986) ([chenhaizano](https://github.com/chenhaizano)) +- Add merchant client. [\#983](https://github.com/overtrue/wechat/pull/983) ([mingyoung](https://github.com/mingyoung)) +- Fix PKCS7 unpad issue. [\#981](https://github.com/overtrue/wechat/pull/981) ([mingyoung](https://github.com/mingyoung)) +- 💯 Add unit tests. [\#979](https://github.com/overtrue/wechat/pull/979) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#978](https://github.com/overtrue/wechat/pull/978) ([overtrue](https://github.com/overtrue)) +- Add sub-merchant support. [\#976](https://github.com/overtrue/wechat/pull/976) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#974](https://github.com/overtrue/wechat/pull/974) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#973](https://github.com/overtrue/wechat/pull/973) ([mingyoung](https://github.com/mingyoung)) +- Refactoring payment [\#972](https://github.com/overtrue/wechat/pull/972) ([mingyoung](https://github.com/mingyoung)) +- Fix request method. [\#964](https://github.com/overtrue/wechat/pull/964) ([mingyoung](https://github.com/mingyoung)) +- MiniProgram template. [\#959](https://github.com/overtrue/wechat/pull/959) ([mingyoung](https://github.com/mingyoung)) +- 企业微信 jssdk ticket [\#954](https://github.com/overtrue/wechat/pull/954) ([mingyoung](https://github.com/mingyoung)) +- Scrutinizer Auto-Fixes [\#944](https://github.com/overtrue/wechat/pull/944) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer)) +- 简化子商户js config [\#943](https://github.com/overtrue/wechat/pull/943) ([HanSon](https://github.com/HanSon)) +- Apply fixes from StyleCI [\#942](https://github.com/overtrue/wechat/pull/942) ([overtrue](https://github.com/overtrue)) +- 支持子商户JS CONFIG生成 [\#941](https://github.com/overtrue/wechat/pull/941) ([HanSon](https://github.com/HanSon)) + +## [4.0.0-beta.3](https://github.com/overtrue/wechat/tree/4.0.0-beta.3) (2017-09-23) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.16...4.0.0-beta.3) + +**Closed issues:** + +- 退款结果通知 [\#858](https://github.com/overtrue/wechat/issues/858) + +**Merged pull requests:** + +- Update Application.php [\#936](https://github.com/overtrue/wechat/pull/936) ([HanSon](https://github.com/HanSon)) + +## [3.3.16](https://github.com/overtrue/wechat/tree/3.3.16) (2017-09-20) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.15...3.3.16) + +**Closed issues:** + +- 希望能增加获取回复数据的方法 [\#929](https://github.com/overtrue/wechat/issues/929) +- 3.3 版本 数据类型不对导致无法运行 [\#928](https://github.com/overtrue/wechat/issues/928) + +**Merged pull requests:** + +- 增加退款回调处理 [\#931](https://github.com/overtrue/wechat/pull/931) ([leo108](https://github.com/leo108)) + +## [3.3.15](https://github.com/overtrue/wechat/tree/3.3.15) (2017-09-13) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.14...3.3.15) + +**Closed issues:** + +- 微信 for windows 发送文件的时候报错 [\#927](https://github.com/overtrue/wechat/issues/927) + +## [3.3.14](https://github.com/overtrue/wechat/tree/3.3.14) (2017-09-13) +[Full Changelog](https://github.com/overtrue/wechat/compare/4.0.0-beta.2...3.3.14) + +**Closed issues:** + +- 请教授权的时候什么方法拿到用户是否关注了本公众号? [\#926](https://github.com/overtrue/wechat/issues/926) + +## [4.0.0-beta.2](https://github.com/overtrue/wechat/tree/4.0.0-beta.2) (2017-09-12) +[Full Changelog](https://github.com/overtrue/wechat/compare/4.0.0-beta.1...4.0.0-beta.2) + +**Closed issues:** + +- readme.md写错了? [\#923](https://github.com/overtrue/wechat/issues/923) +- token验证成功,但还是回复暂时不可用,困扰1个星期多了,真心求助!!!有偿都可以!! [\#922](https://github.com/overtrue/wechat/issues/922) +- 条件判断错了,stripos返回的是“返回在字符串 haystack 中 needle 首次出现的数字位置。”,所以不能直接作为条件判断 [\#915](https://github.com/overtrue/wechat/issues/915) +- README中的链接是否错误 [\#913](https://github.com/overtrue/wechat/issues/913) +- 测试公众号无法接受用户信息 [\#911](https://github.com/overtrue/wechat/issues/911) +- ReadMe文件过期 [\#910](https://github.com/overtrue/wechat/issues/910) +- 开放平台服务,取消授权会有哪些参数过来? [\#909](https://github.com/overtrue/wechat/issues/909) +- token无法验证 [\#908](https://github.com/overtrue/wechat/issues/908) +- laravel 5.4 composer 失败 [\#907](https://github.com/overtrue/wechat/issues/907) +- 开放平台:组件ticket无法通过 [\#904](https://github.com/overtrue/wechat/issues/904) +- 官方网站一直登陆不了,浙江丽水地区 [\#903](https://github.com/overtrue/wechat/issues/903) +- \[4.0\] Pimple\Exception\UnknownIdentifierException [\#901](https://github.com/overtrue/wechat/issues/901) +- 4.0 报错“Your requirements could not be resolved to an installable set of packages.” [\#898](https://github.com/overtrue/wechat/issues/898) + +**Merged pull requests:** + +- 修改通过ticket换取二维码图片地址的逻辑 [\#925](https://github.com/overtrue/wechat/pull/925) ([Gwill](https://github.com/Gwill)) +- make domain more flexible [\#924](https://github.com/overtrue/wechat/pull/924) ([HanSon](https://github.com/HanSon)) +- add code & domain comment [\#921](https://github.com/overtrue/wechat/pull/921) ([HanSon](https://github.com/HanSon)) +- Apply fixes from StyleCI [\#919](https://github.com/overtrue/wechat/pull/919) ([overtrue](https://github.com/overtrue)) +- \[3.1\] Custom PreAuthCode Support [\#918](https://github.com/overtrue/wechat/pull/918) ([freyo](https://github.com/freyo)) +- 修改acess\_token无效时微信返回错误码的判断 [\#916](https://github.com/overtrue/wechat/pull/916) ([blackjune](https://github.com/blackjune)) +- \[4.0\] Add optional 'request' parameter to notify handler methods [\#905](https://github.com/overtrue/wechat/pull/905) ([edwardaa](https://github.com/edwardaa)) +- Apply fixes from StyleCI [\#902](https://github.com/overtrue/wechat/pull/902) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#897](https://github.com/overtrue/wechat/pull/897) ([overtrue](https://github.com/overtrue)) +- 增加OAuth中Guzzle\Client的配置项的设置 [\#893](https://github.com/overtrue/wechat/pull/893) ([khsing](https://github.com/khsing)) +- Apply fixes from StyleCI [\#887](https://github.com/overtrue/wechat/pull/887) ([overtrue](https://github.com/overtrue)) +- Scrutinizer Auto-Fixes [\#884](https://github.com/overtrue/wechat/pull/884) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer)) + +## [4.0.0-beta.1](https://github.com/overtrue/wechat/tree/4.0.0-beta.1) (2017-08-31) +[Full Changelog](https://github.com/overtrue/wechat/compare/4.0.0-alpha.2...4.0.0-beta.1) + +**Closed issues:** + +- http://easywechat.org/ 网站访问不了了? [\#896](https://github.com/overtrue/wechat/issues/896) +- 关于缓存,请问为什么key中包含appId \* 2,有什么讲究吗? [\#892](https://github.com/overtrue/wechat/issues/892) +- 小程序调用解密程序报-41003错误 [\#891](https://github.com/overtrue/wechat/issues/891) +- 小程序调用加密数据解密时报错,不存在方法 [\#890](https://github.com/overtrue/wechat/issues/890) +- 有关4.0使用文档的问题 [\#883](https://github.com/overtrue/wechat/issues/883) +- \[4.0\] PHP最低版本能否降到7.0 [\#880](https://github.com/overtrue/wechat/issues/880) + +**Merged pull requests:** + +- \[4.0\] Pass proper arguments to the Response constructor [\#895](https://github.com/overtrue/wechat/pull/895) ([edwardaa](https://github.com/edwardaa)) +- Fix baseUrl and json issues. [\#894](https://github.com/overtrue/wechat/pull/894) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#889](https://github.com/overtrue/wechat/pull/889) ([overtrue](https://github.com/overtrue)) +- Scrutinizer Auto-Fixes [\#885](https://github.com/overtrue/wechat/pull/885) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer)) +- Apply fixes from StyleCI [\#882](https://github.com/overtrue/wechat/pull/882) ([overtrue](https://github.com/overtrue)) +- 补充通用卡接口 [\#881](https://github.com/overtrue/wechat/pull/881) ([XiaoLer](https://github.com/XiaoLer)) +- Apply fixes from StyleCI [\#879](https://github.com/overtrue/wechat/pull/879) ([overtrue](https://github.com/overtrue)) +- \[3.1\] Payment/API 没有使用全局的 cache [\#878](https://github.com/overtrue/wechat/pull/878) ([edwardaa](https://github.com/edwardaa)) +- Add JSON\_UNESCAPED\_UNICODE option. [\#874](https://github.com/overtrue/wechat/pull/874) ([mingyoung](https://github.com/mingyoung)) +- update \_\_set\_state magic method to static [\#872](https://github.com/overtrue/wechat/pull/872) ([8090Lambert](https://github.com/8090Lambert)) + +## [4.0.0-alpha.2](https://github.com/overtrue/wechat/tree/4.0.0-alpha.2) (2017-08-20) +[Full Changelog](https://github.com/overtrue/wechat/compare/4.0.0-alpha.1...4.0.0-alpha.2) + +**Closed issues:** + +- 你好,怎么用的 [\#869](https://github.com/overtrue/wechat/issues/869) + +**Merged pull requests:** + +- Tweak dir [\#871](https://github.com/overtrue/wechat/pull/871) ([mingyoung](https://github.com/mingyoung)) +- Fix mini-program guard. [\#870](https://github.com/overtrue/wechat/pull/870) ([mingyoung](https://github.com/mingyoung)) + +## [4.0.0-alpha.1](https://github.com/overtrue/wechat/tree/4.0.0-alpha.1) (2017-08-14) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.13...4.0.0-alpha.1) + +**Closed issues:** + +- 对doctrine/cache依赖的版本锁定 [\#867](https://github.com/overtrue/wechat/issues/867) + +## [3.3.13](https://github.com/overtrue/wechat/tree/3.3.13) (2017-08-13) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.12...3.3.13) + +**Closed issues:** + +- 文档中网页授权实例写的不明确 [\#850](https://github.com/overtrue/wechat/issues/850) +- \[意见\]作者能否提供getTokenFromServer方法扩展从外部第三方获取access\_token [\#837](https://github.com/overtrue/wechat/issues/837) +- invalid credential, access\_token is invalid or not latest [\#808](https://github.com/overtrue/wechat/issues/808) +- \[4.0\] 重构卡券 [\#806](https://github.com/overtrue/wechat/issues/806) +- \[4.0\] 重构 Broadcasting [\#805](https://github.com/overtrue/wechat/issues/805) +- \[4.0\] 变更日志 [\#746](https://github.com/overtrue/wechat/issues/746) + +**Merged pull requests:** + +- Fixed open-platform authorizer server token. [\#866](https://github.com/overtrue/wechat/pull/866) ([mingyoung](https://github.com/mingyoung)) +- payment\ClientTest 优化 [\#865](https://github.com/overtrue/wechat/pull/865) ([tianyong90](https://github.com/tianyong90)) +- Apply fixes from StyleCI [\#864](https://github.com/overtrue/wechat/pull/864) ([overtrue](https://github.com/overtrue)) +- 退款通知处理及相关单元测试 [\#863](https://github.com/overtrue/wechat/pull/863) ([tianyong90](https://github.com/tianyong90)) +- Apply fixes from StyleCI [\#862](https://github.com/overtrue/wechat/pull/862) ([overtrue](https://github.com/overtrue)) +- Update dependence version. [\#861](https://github.com/overtrue/wechat/pull/861) ([mingyoung](https://github.com/mingyoung)) +- Add tests. [\#859](https://github.com/overtrue/wechat/pull/859) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#857](https://github.com/overtrue/wechat/pull/857) ([overtrue](https://github.com/overtrue)) +- Payment 单元测试优化 [\#856](https://github.com/overtrue/wechat/pull/856) ([tianyong90](https://github.com/tianyong90)) +- Apply fixes from StyleCI [\#855](https://github.com/overtrue/wechat/pull/855) ([overtrue](https://github.com/overtrue)) +- lists 方法重命名为 list,相关单元测试调整 [\#853](https://github.com/overtrue/wechat/pull/853) ([tianyong90](https://github.com/tianyong90)) +- Apply fixes from StyleCI [\#852](https://github.com/overtrue/wechat/pull/852) ([overtrue](https://github.com/overtrue)) +- Payment 单元测试及部分问题修复 [\#851](https://github.com/overtrue/wechat/pull/851) ([tianyong90](https://github.com/tianyong90)) +- Apply fixes from StyleCI [\#848](https://github.com/overtrue/wechat/pull/848) ([overtrue](https://github.com/overtrue)) +- 调整 Payment\BaseClient 注入的 $app 类型 [\#847](https://github.com/overtrue/wechat/pull/847) ([tianyong90](https://github.com/tianyong90)) +- array\_merge 方法参数类型转换, type hints [\#846](https://github.com/overtrue/wechat/pull/846) ([tianyong90](https://github.com/tianyong90)) +- Fix oauth. [\#845](https://github.com/overtrue/wechat/pull/845) ([mingyoung](https://github.com/mingyoung)) +- Text message. [\#844](https://github.com/overtrue/wechat/pull/844) ([mingyoung](https://github.com/mingyoung)) +- Rename BaseService -\> BasicService. [\#843](https://github.com/overtrue/wechat/pull/843) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#842](https://github.com/overtrue/wechat/pull/842) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#841](https://github.com/overtrue/wechat/pull/841) ([overtrue](https://github.com/overtrue)) +- phpdoc types。 [\#840](https://github.com/overtrue/wechat/pull/840) ([tianyong90](https://github.com/tianyong90)) +- Apply fixes from StyleCI [\#839](https://github.com/overtrue/wechat/pull/839) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#836](https://github.com/overtrue/wechat/pull/836) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#835](https://github.com/overtrue/wechat/pull/835) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#833](https://github.com/overtrue/wechat/pull/833) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#831](https://github.com/overtrue/wechat/pull/831) ([overtrue](https://github.com/overtrue)) + +## [3.3.12](https://github.com/overtrue/wechat/tree/3.3.12) (2017-08-01) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.11...3.3.12) + +**Closed issues:** + +- 能否整合微信开放平台在给出一套demo [\#816](https://github.com/overtrue/wechat/issues/816) +- 请教这个项目的支付部分,尤其是签名和结果回调,是否支持小程序? [\#814](https://github.com/overtrue/wechat/issues/814) +- 微信意图识别接口返回invalid param [\#804](https://github.com/overtrue/wechat/issues/804) +- 返回param invalid [\#803](https://github.com/overtrue/wechat/issues/803) + +**Merged pull requests:** + +- change comment word [\#830](https://github.com/overtrue/wechat/pull/830) ([tianyong90](https://github.com/tianyong90)) +- Fix getTicket. [\#829](https://github.com/overtrue/wechat/pull/829) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#827](https://github.com/overtrue/wechat/pull/827) ([overtrue](https://github.com/overtrue)) +- 修正 HasAttributes Trait 引用错误 [\#825](https://github.com/overtrue/wechat/pull/825) ([tianyong90](https://github.com/tianyong90)) +- Apply fixes from StyleCI [\#824](https://github.com/overtrue/wechat/pull/824) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#822](https://github.com/overtrue/wechat/pull/822) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#820](https://github.com/overtrue/wechat/pull/820) ([mingyoung](https://github.com/mingyoung)) +- Add subscribe message. [\#819](https://github.com/overtrue/wechat/pull/819) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#818](https://github.com/overtrue/wechat/pull/818) ([mingyoung](https://github.com/mingyoung)) +- 微信开放平台帐号管理 [\#817](https://github.com/overtrue/wechat/pull/817) ([XiaoLer](https://github.com/XiaoLer)) +- add method in comment [\#813](https://github.com/overtrue/wechat/pull/813) ([HanSon](https://github.com/HanSon)) +- fixed guzzle version [\#812](https://github.com/overtrue/wechat/pull/812) ([HanSon](https://github.com/HanSon)) +- Apply fixes from StyleCI [\#811](https://github.com/overtrue/wechat/pull/811) ([mingyoung](https://github.com/mingyoung)) +- Downgrade to php 7.0 [\#809](https://github.com/overtrue/wechat/pull/809) ([HanSon](https://github.com/HanSon)) + +## [3.3.11](https://github.com/overtrue/wechat/tree/3.3.11) (2017-07-17) +[Full Changelog](https://github.com/overtrue/wechat/compare/4.0.0-alpha1...3.3.11) + +**Closed issues:** + +- 请添加 「退款原因」 参数 [\#802](https://github.com/overtrue/wechat/issues/802) + +## [4.0.0-alpha1](https://github.com/overtrue/wechat/tree/4.0.0-alpha1) (2017-07-17) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.10...4.0.0-alpha1) + +**Closed issues:** + +- Overtrue\Wechat\Media not found [\#801](https://github.com/overtrue/wechat/issues/801) +- 在微信的接口配置时Token 无效,可任意输入 [\#800](https://github.com/overtrue/wechat/issues/800) + +## [3.3.10](https://github.com/overtrue/wechat/tree/3.3.10) (2017-07-13) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.9...3.3.10) + +**Closed issues:** + +- 第三方平台refresh\_token的保存问题 [\#798](https://github.com/overtrue/wechat/issues/798) +- 网页授权共享session已晚 [\#792](https://github.com/overtrue/wechat/issues/792) + +**Merged pull requests:** + +- 临时二维码也是支持scene\_str的,这里补充上 [\#797](https://github.com/overtrue/wechat/pull/797) ([lornewang](https://github.com/lornewang)) +- Apply fixes from StyleCI [\#795](https://github.com/overtrue/wechat/pull/795) ([overtrue](https://github.com/overtrue)) +- add card message type [\#794](https://github.com/overtrue/wechat/pull/794) ([IanGely](https://github.com/IanGely)) +- add staff message type wxcard [\#793](https://github.com/overtrue/wechat/pull/793) ([IanGely](https://github.com/IanGely)) + +## [3.3.9](https://github.com/overtrue/wechat/tree/3.3.9) (2017-07-07) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.8...3.3.9) + +**Closed issues:** + +- \[4.0\] Http 模块 [\#678](https://github.com/overtrue/wechat/issues/678) +- \[4.0\] Http 请求类 [\#582](https://github.com/overtrue/wechat/issues/582) + +**Merged pull requests:** + +- Apply fixes from StyleCI [\#791](https://github.com/overtrue/wechat/pull/791) ([overtrue](https://github.com/overtrue)) +- Add get user portrait method. [\#790](https://github.com/overtrue/wechat/pull/790) ([getive](https://github.com/getive)) +- \[Feature\] Move directories [\#789](https://github.com/overtrue/wechat/pull/789) ([overtrue](https://github.com/overtrue)) +- \[Feature\] Move traits to kernel. [\#788](https://github.com/overtrue/wechat/pull/788) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#787](https://github.com/overtrue/wechat/pull/787) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#786](https://github.com/overtrue/wechat/pull/786) ([overtrue](https://github.com/overtrue)) + +## [3.3.8](https://github.com/overtrue/wechat/tree/3.3.8) (2017-07-07) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.7...3.3.8) + +**Closed issues:** + +- $temporary-\>getStream\($media\_id\) 与 file\_get\_contents\(\) 有区别??? [\#742](https://github.com/overtrue/wechat/issues/742) + +## [3.3.7](https://github.com/overtrue/wechat/tree/3.3.7) (2017-07-06) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.6...3.3.7) + +**Closed issues:** + +- 多添加一个$option [\#772](https://github.com/overtrue/wechat/issues/772) +- 消息群发,指定openid群发视频时,微信报错invalid message type hint: \[JUs0Oa0779ge25\] [\#757](https://github.com/overtrue/wechat/issues/757) + +## [3.3.6](https://github.com/overtrue/wechat/tree/3.3.6) (2017-07-06) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.5...3.3.6) + +**Fixed bugs:** + +- 素材管理,如果media\_id不存在会保存网页返回的错误代码 [\#592](https://github.com/overtrue/wechat/issues/592) + +**Closed issues:** + +- https://easywechat.org网站证书刚过期了,知会作者一声 [\#781](https://github.com/overtrue/wechat/issues/781) +- access\_token 是否能不内部主动请求微信 [\#778](https://github.com/overtrue/wechat/issues/778) +- 门店创建API \($poi-\>create\) 建议返回 poi\_id / exception [\#774](https://github.com/overtrue/wechat/issues/774) +- 扩展门店小程序错误 [\#762](https://github.com/overtrue/wechat/issues/762) +- \[4.0\] jssdk 抽出独立模块 [\#754](https://github.com/overtrue/wechat/issues/754) +- \[4.0\] 消息加密解密模块提取到 Kernel [\#753](https://github.com/overtrue/wechat/issues/753) +- 网页能授权但无法获取用户信息,代码跟官方文档一样。 [\#713](https://github.com/overtrue/wechat/issues/713) + +**Merged pull requests:** + +- Feature: BaseService. [\#785](https://github.com/overtrue/wechat/pull/785) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#784](https://github.com/overtrue/wechat/pull/784) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#783](https://github.com/overtrue/wechat/pull/783) ([mingyoung](https://github.com/mingyoung)) + +## [3.3.5](https://github.com/overtrue/wechat/tree/3.3.5) (2017-07-04) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.4...3.3.5) + +**Implemented enhancements:** + +- 并发下access\_token存在脏写隐患 [\#696](https://github.com/overtrue/wechat/issues/696) + +**Merged pull requests:** + +- Apply fixes from StyleCI [\#780](https://github.com/overtrue/wechat/pull/780) ([overtrue](https://github.com/overtrue)) + +## [3.3.4](https://github.com/overtrue/wechat/tree/3.3.4) (2017-07-04) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.3...3.3.4) + +**Closed issues:** + +- 网页授权获取用户信息无法打开授权页面 [\#773](https://github.com/overtrue/wechat/issues/773) +- Class 'EasyWechat\Foundation\Application' not found [\#769](https://github.com/overtrue/wechat/issues/769) +- 获取小程序二维码报错 [\#766](https://github.com/overtrue/wechat/issues/766) +- Call to undefined method EasyWeChat\Server\Guard::setRequest\(\) [\#765](https://github.com/overtrue/wechat/issues/765) +- 网页授权问题,提示scopes类型错误 [\#764](https://github.com/overtrue/wechat/issues/764) +- 门店小程序扩展错误问题 [\#763](https://github.com/overtrue/wechat/issues/763) +- 微信开发者平台,全网发布怎么通过 [\#761](https://github.com/overtrue/wechat/issues/761) +- 微信网页授权重复请求报code无效 [\#714](https://github.com/overtrue/wechat/issues/714) + +**Merged pull requests:** + +- 新版客服功能-获取聊天记录 [\#775](https://github.com/overtrue/wechat/pull/775) ([wuwenbao](https://github.com/wuwenbao)) +- Fix mini-program qrcode. [\#768](https://github.com/overtrue/wechat/pull/768) ([mingyoung](https://github.com/mingyoung)) +- Add code comments [\#756](https://github.com/overtrue/wechat/pull/756) ([daxiong123](https://github.com/daxiong123)) + +## [3.3.3](https://github.com/overtrue/wechat/tree/3.3.3) (2017-06-22) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.2...3.3.3) + +**Implemented enhancements:** + +- \[4.0\] Trait HasHttpRequests [\#671](https://github.com/overtrue/wechat/issues/671) +- \[4.0\] 缓存抽象成 trait: InteractsWithCache [\#670](https://github.com/overtrue/wechat/issues/670) +- \[4.0\] 返回值类型可配置 [\#661](https://github.com/overtrue/wechat/issues/661) +- \[4.0\] 报错信息可选 [\#596](https://github.com/overtrue/wechat/issues/596) +- \[4.0\] 简化并完善开发者配置项 [\#584](https://github.com/overtrue/wechat/issues/584) + +**Fixed bugs:** + +- open\_platform.oauth 过早的获取 access token [\#701](https://github.com/overtrue/wechat/issues/701) + +**Closed issues:** + +- 微信网页支付配置生成 [\#751](https://github.com/overtrue/wechat/issues/751) +- configForJSSDKPayment [\#744](https://github.com/overtrue/wechat/issues/744) +- 发现微信上有管理公众号留言的接口,不知道是不是新出的 [\#721](https://github.com/overtrue/wechat/issues/721) +- oauth能获取用户信息,再通过access\_token与用户openid去获取信息,部分用户的信息为空 [\#720](https://github.com/overtrue/wechat/issues/720) +- 接入多个公众号 [\#718](https://github.com/overtrue/wechat/issues/718) +- guzzle curl error28 - 去哪设置默认timeout ? [\#715](https://github.com/overtrue/wechat/issues/715) +- 使用$server-\>getMessage\(\);报错 [\#712](https://github.com/overtrue/wechat/issues/712) +- 怎样从数据库中调取配置 [\#711](https://github.com/overtrue/wechat/issues/711) +- \[4.0\] 支持企业微信 [\#707](https://github.com/overtrue/wechat/issues/707) +- defaultColor does not work. [\#703](https://github.com/overtrue/wechat/issues/703) +- 是否支持H5支付 [\#694](https://github.com/overtrue/wechat/issues/694) +- 生成AccessToken时,似乎没有调用自定义缓存的delete方法 [\#693](https://github.com/overtrue/wechat/issues/693) +- \[4.0\] PSR-6 缓存接口 [\#692](https://github.com/overtrue/wechat/issues/692) +- 微信支付沙盒模式支持配置文件配置 [\#690](https://github.com/overtrue/wechat/issues/690) +- \[4.0\] 优化服务提供器结构 [\#689](https://github.com/overtrue/wechat/issues/689) +- 强制项目不要自动获取AccessToken [\#688](https://github.com/overtrue/wechat/issues/688) +- 小程序解密$encryptedData数据 [\#687](https://github.com/overtrue/wechat/issues/687) +- 微信坑爹timestamp已经解决不需要configForJSSDKPayment改变timestamp中s大小写 [\#686](https://github.com/overtrue/wechat/issues/686) +- \[4.0\] 所有 API 改名为 Client. [\#677](https://github.com/overtrue/wechat/issues/677) +- sandbox\_signkey 过期 [\#675](https://github.com/overtrue/wechat/issues/675) +- 接口配置失败 [\#672](https://github.com/overtrue/wechat/issues/672) +- 下载语音文件偶尔报错:ErrorException: is\_readable\(\) expects parameter 1 to be a valid path [\#667](https://github.com/overtrue/wechat/issues/667) +- 微信支付沙箱地址混乱 [\#665](https://github.com/overtrue/wechat/issues/665) +- 开放平台自动回复出错,提示“该服务号暂时无法提供服务” [\#654](https://github.com/overtrue/wechat/issues/654) +- \[4.0\]自定义微信API的区域接入点 [\#636](https://github.com/overtrue/wechat/issues/636) +- 在命令行使用easywechat如何关闭日志 [\#601](https://github.com/overtrue/wechat/issues/601) +- \[4.0\] PHP 版本最低要求 7.1 [\#586](https://github.com/overtrue/wechat/issues/586) +- \[4.0\] 简化微信 API 请求 [\#583](https://github.com/overtrue/wechat/issues/583) +- \[4.0\] 自定义 endpoint [\#521](https://github.com/overtrue/wechat/issues/521) + +**Merged pull requests:** + +- Apply fixes from StyleCI [\#750](https://github.com/overtrue/wechat/pull/750) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#749](https://github.com/overtrue/wechat/pull/749) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#747](https://github.com/overtrue/wechat/pull/747) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#745](https://github.com/overtrue/wechat/pull/745) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#740](https://github.com/overtrue/wechat/pull/740) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#737](https://github.com/overtrue/wechat/pull/737) ([mingyoung](https://github.com/mingyoung)) +- 分模块静态调用 [\#734](https://github.com/overtrue/wechat/pull/734) ([mingyoung](https://github.com/mingyoung)) +- Revert "Apply fixes from StyleCI" [\#731](https://github.com/overtrue/wechat/pull/731) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#730](https://github.com/overtrue/wechat/pull/730) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#729](https://github.com/overtrue/wechat/pull/729) ([overtrue](https://github.com/overtrue)) +- Revert "Apply fixes from StyleCI" [\#728](https://github.com/overtrue/wechat/pull/728) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#727](https://github.com/overtrue/wechat/pull/727) ([overtrue](https://github.com/overtrue)) +- 修复Https 请求判断不准 [\#726](https://github.com/overtrue/wechat/pull/726) ([xutl](https://github.com/xutl)) +- Apply fixes from StyleCI [\#725](https://github.com/overtrue/wechat/pull/725) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#724](https://github.com/overtrue/wechat/pull/724) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#723](https://github.com/overtrue/wechat/pull/723) ([mingyoung](https://github.com/mingyoung)) +- Correction notes [\#722](https://github.com/overtrue/wechat/pull/722) ([PersiLiao](https://github.com/PersiLiao)) +- Apply fixes from StyleCI [\#717](https://github.com/overtrue/wechat/pull/717) ([mingyoung](https://github.com/mingyoung)) +- 新增图文消息留言管理接口 [\#716](https://github.com/overtrue/wechat/pull/716) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#710](https://github.com/overtrue/wechat/pull/710) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#709](https://github.com/overtrue/wechat/pull/709) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#708](https://github.com/overtrue/wechat/pull/708) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#706](https://github.com/overtrue/wechat/pull/706) ([overtrue](https://github.com/overtrue)) +- 命令行下不打印日志 [\#705](https://github.com/overtrue/wechat/pull/705) ([mingyoung](https://github.com/mingyoung)) +- add defaultColor [\#704](https://github.com/overtrue/wechat/pull/704) ([damonto](https://github.com/damonto)) +- Fix [\#702](https://github.com/overtrue/wechat/pull/702) ([mingyoung](https://github.com/mingyoung)) +- Add api. [\#700](https://github.com/overtrue/wechat/pull/700) ([mingyoung](https://github.com/mingyoung)) +- Rename method. [\#699](https://github.com/overtrue/wechat/pull/699) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#698](https://github.com/overtrue/wechat/pull/698) ([mingyoung](https://github.com/mingyoung)) +- 修正素材管理中的返回值文档注释,正确的类型应该是集合,而不是字符串。 [\#695](https://github.com/overtrue/wechat/pull/695) ([starlight36](https://github.com/starlight36)) +- Payment sandbox config. [\#691](https://github.com/overtrue/wechat/pull/691) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#684](https://github.com/overtrue/wechat/pull/684) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#683](https://github.com/overtrue/wechat/pull/683) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#682](https://github.com/overtrue/wechat/pull/682) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#681](https://github.com/overtrue/wechat/pull/681) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#680](https://github.com/overtrue/wechat/pull/680) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#679](https://github.com/overtrue/wechat/pull/679) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#676](https://github.com/overtrue/wechat/pull/676) ([mingyoung](https://github.com/mingyoung)) +- checks via composer. [\#673](https://github.com/overtrue/wechat/pull/673) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#668](https://github.com/overtrue/wechat/pull/668) ([overtrue](https://github.com/overtrue)) +- Correct payment sandbox endpoint and add a method to get sandbox sign key [\#666](https://github.com/overtrue/wechat/pull/666) ([skyred](https://github.com/skyred)) + +## [3.3.2](https://github.com/overtrue/wechat/tree/3.3.2) (2017-04-27) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.1...3.3.2) + +**Implemented enhancements:** + +- \[4.0\] Open Platform 模块 [\#587](https://github.com/overtrue/wechat/issues/587) +- \[4.0\] 微信支付 sandbox模式 [\#507](https://github.com/overtrue/wechat/issues/507) + +**Closed issues:** + +- \[4.0\] staff 模块改名为 customer service [\#585](https://github.com/overtrue/wechat/issues/585) + +**Merged pull requests:** + +- Module rename. [\#664](https://github.com/overtrue/wechat/pull/664) ([mingyoung](https://github.com/mingyoung)) +- Merge branch master into branch develop. [\#663](https://github.com/overtrue/wechat/pull/663) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#662](https://github.com/overtrue/wechat/pull/662) ([mingyoung](https://github.com/mingyoung)) +- Fix payment tools API [\#660](https://github.com/overtrue/wechat/pull/660) ([mingyoung](https://github.com/mingyoung)) +- Avoid ambiguity [\#659](https://github.com/overtrue/wechat/pull/659) ([mingyoung](https://github.com/mingyoung)) +- Support Payment Sandbox mode [\#658](https://github.com/overtrue/wechat/pull/658) ([skyred](https://github.com/skyred)) +- Apply fixes from StyleCI [\#656](https://github.com/overtrue/wechat/pull/656) ([overtrue](https://github.com/overtrue)) +- Mini program datacube. [\#655](https://github.com/overtrue/wechat/pull/655) ([mingyoung](https://github.com/mingyoung)) + +## [3.3.1](https://github.com/overtrue/wechat/tree/3.3.1) (2017-04-16) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.0...3.3.1) + +**Closed issues:** + +- 微信第三方平台缓存位置,是否可以在配置文件中自定义 [\#648](https://github.com/overtrue/wechat/issues/648) +- 微信开放平台authorizer token缓存问题 [\#644](https://github.com/overtrue/wechat/issues/644) +- 微信开放平台发起网页授权bug [\#638](https://github.com/overtrue/wechat/issues/638) +- 微信公众号不能回复接收到的消息,日志无报错 [\#637](https://github.com/overtrue/wechat/issues/637) +- \[4.0\]黑名单管理 [\#538](https://github.com/overtrue/wechat/issues/538) + +**Merged pull requests:** + +- optimizes [\#652](https://github.com/overtrue/wechat/pull/652) ([mingyoung](https://github.com/mingyoung)) + +## [3.3.0](https://github.com/overtrue/wechat/tree/3.3.0) (2017-04-13) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.2.7...3.3.0) + +**Closed issues:** + +- 微信接口获取openid是怎么排序的? [\#650](https://github.com/overtrue/wechat/issues/650) +- 缺少网页扫码支付接口 [\#647](https://github.com/overtrue/wechat/issues/647) +- 微信下的单的默认过期时间是多少啊 [\#645](https://github.com/overtrue/wechat/issues/645) +- 在获取用户信息是出错 [\#643](https://github.com/overtrue/wechat/issues/643) +- 调用$app =app\('wechat'\);时报错Use of undefined constant CURLOPT\_IPRESOLVE - assumed 'CURLOPT\_IPRESOLVE' [\#633](https://github.com/overtrue/wechat/issues/633) +- 提示找不到EasyWeChat\Server\Guard::setRequest\(\)方法 [\#626](https://github.com/overtrue/wechat/issues/626) +- 开放平台接收ComponentVerifyTicket,会出现Undefined index: FromUserName [\#623](https://github.com/overtrue/wechat/issues/623) +- 美国移动网络获取不到accessToken [\#610](https://github.com/overtrue/wechat/issues/610) +- 开放平台 APP 微信登录 [\#604](https://github.com/overtrue/wechat/issues/604) + +**Merged pull requests:** + +- Merge from open-platform branch. [\#651](https://github.com/overtrue/wechat/pull/651) ([mingyoung](https://github.com/mingyoung)) +- Update code for open-platform [\#649](https://github.com/overtrue/wechat/pull/649) ([mingyoung](https://github.com/mingyoung)) +- Code cleanup & refactoring. [\#646](https://github.com/overtrue/wechat/pull/646) ([mingyoung](https://github.com/mingyoung)) +- support cash coupon [\#642](https://github.com/overtrue/wechat/pull/642) ([HanSon](https://github.com/HanSon)) +- ♻️ All tests have been namespaced. [\#641](https://github.com/overtrue/wechat/pull/641) ([mingyoung](https://github.com/mingyoung)) +- tweak code. [\#640](https://github.com/overtrue/wechat/pull/640) ([mingyoung](https://github.com/mingyoung)) +- modify oauth property [\#639](https://github.com/overtrue/wechat/pull/639) ([jekst](https://github.com/jekst)) +- Apply fixes from StyleCI [\#635](https://github.com/overtrue/wechat/pull/635) ([overtrue](https://github.com/overtrue)) +- ✨ Blacklist. [\#634](https://github.com/overtrue/wechat/pull/634) ([mingyoung](https://github.com/mingyoung)) +- 🔨 Refactoring for mini-program. [\#632](https://github.com/overtrue/wechat/pull/632) ([mingyoung](https://github.com/mingyoung)) + +## [3.2.7](https://github.com/overtrue/wechat/tree/3.2.7) (2017-03-31) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.2.6...3.2.7) + +**Closed issues:** + +- 不管哪个公众号,只要填写 这个接口地址,都能配置或应用成功,实际上是不成功的,不到怎么找错。。 [\#611](https://github.com/overtrue/wechat/issues/611) + +**Merged pull requests:** + +- 修复一个创建卡券时的 bug, 添加获取微信门店类目表的api [\#631](https://github.com/overtrue/wechat/pull/631) ([Hexor](https://github.com/Hexor)) + +## [3.2.6](https://github.com/overtrue/wechat/tree/3.2.6) (2017-03-31) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.2.5...3.2.6) + +**Closed issues:** + +- 我想大量发模板消息,但send每次都等待返回太慢,有啥解决办法吗? [\#630](https://github.com/overtrue/wechat/issues/630) +- 3.2开放平台缺少authorizer\_token和authorization [\#629](https://github.com/overtrue/wechat/issues/629) +- 微信开发平台接受消息报Invalid request signature bug [\#625](https://github.com/overtrue/wechat/issues/625) +- 图文上传thumb\_media\_id 返回 {"errcode":40007,"errmsg":"invalid media\_id hint: \[\]"} [\#622](https://github.com/overtrue/wechat/issues/622) +- Encryptor基类hack导致小程序的sessionKey base64\_decode失败 [\#614](https://github.com/overtrue/wechat/issues/614) +- 是否有 2.1 升级到最新版的方案? [\#609](https://github.com/overtrue/wechat/issues/609) +- laravel5.3 安装 "overtrue/wechat:~3.1 失败 [\#607](https://github.com/overtrue/wechat/issues/607) +- overtrue/wechat和phpdoc包依赖冲突。 [\#605](https://github.com/overtrue/wechat/issues/605) +- \[bug\]2个问题 [\#597](https://github.com/overtrue/wechat/issues/597) +- 微信第三方平台开发是否只做了一部分? [\#594](https://github.com/overtrue/wechat/issues/594) +- \[4.0\] ServiceProvider 移动到各自模块里 [\#588](https://github.com/overtrue/wechat/issues/588) +- Cannot use EasyWeChat\OpenPlatform\Traits\VerifyTicket as VerifyTicket because the name is already in use [\#579](https://github.com/overtrue/wechat/issues/579) +- 授权state值怎么设置 [\#573](https://github.com/overtrue/wechat/issues/573) +- mini\_app get jscode problem, report appid & secret value is null [\#569](https://github.com/overtrue/wechat/issues/569) +- 小程序生成二维码问题 [\#568](https://github.com/overtrue/wechat/issues/568) + +**Merged pull requests:** + +- Update OpenPlatform AppId [\#624](https://github.com/overtrue/wechat/pull/624) ([jeftom](https://github.com/jeftom)) +- Apply fixes from StyleCI [\#621](https://github.com/overtrue/wechat/pull/621) ([overtrue](https://github.com/overtrue)) +- Apply fixes from StyleCI [\#618](https://github.com/overtrue/wechat/pull/618) ([overtrue](https://github.com/overtrue)) +- Compatible with php5.5 [\#617](https://github.com/overtrue/wechat/pull/617) ([mingyoung](https://github.com/mingyoung)) +- Make the testcase works. [\#616](https://github.com/overtrue/wechat/pull/616) ([mingyoung](https://github.com/mingyoung)) +- Fix mini-program decryptor [\#615](https://github.com/overtrue/wechat/pull/615) ([mingyoung](https://github.com/mingyoung)) +- Missing message handling [\#613](https://github.com/overtrue/wechat/pull/613) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#612](https://github.com/overtrue/wechat/pull/612) ([overtrue](https://github.com/overtrue)) +- 添加卡券创建二维码接口 [\#608](https://github.com/overtrue/wechat/pull/608) ([forecho](https://github.com/forecho)) +- 开放平台大幅重构并且添加测试 [\#606](https://github.com/overtrue/wechat/pull/606) ([tsunamilx](https://github.com/tsunamilx)) +- Update MessageBuilder.php [\#603](https://github.com/overtrue/wechat/pull/603) ([U2Fsd](https://github.com/U2Fsd)) +- 生成 js添加到卡包接口 增加fixed\_begintimestamp、outer\_str字段 [\#602](https://github.com/overtrue/wechat/pull/602) ([gychg](https://github.com/gychg)) +- tests for speed [\#600](https://github.com/overtrue/wechat/pull/600) ([mingyoung](https://github.com/mingyoung)) +- Update test files [\#599](https://github.com/overtrue/wechat/pull/599) ([mingyoung](https://github.com/mingyoung)) +- 允许自定义ticket缓存key [\#598](https://github.com/overtrue/wechat/pull/598) ([XiaoLer](https://github.com/XiaoLer)) +- delete top color [\#595](https://github.com/overtrue/wechat/pull/595) ([HanSon](https://github.com/HanSon)) +- Add payment scan notify handler [\#593](https://github.com/overtrue/wechat/pull/593) ([acgrid](https://github.com/acgrid)) +- Apply fixes from StyleCI [\#591](https://github.com/overtrue/wechat/pull/591) ([overtrue](https://github.com/overtrue)) +- Upgrade packages version to 4.0 [\#590](https://github.com/overtrue/wechat/pull/590) ([reatang](https://github.com/reatang)) +- Move providers to module dir. \#588 [\#589](https://github.com/overtrue/wechat/pull/589) ([overtrue](https://github.com/overtrue)) +- 把OpenPlatform中的组件依赖解耦 [\#581](https://github.com/overtrue/wechat/pull/581) ([reatang](https://github.com/reatang)) + +## [3.2.5](https://github.com/overtrue/wechat/tree/3.2.5) (2017-02-04) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.2.4...3.2.5) + +**Merged pull requests:** + +- fix naming [\#580](https://github.com/overtrue/wechat/pull/580) ([mingyoung](https://github.com/mingyoung)) +- Allow client code configure its own GuzzleHTTP handler [\#578](https://github.com/overtrue/wechat/pull/578) ([acgrid](https://github.com/acgrid)) + +## [3.2.4](https://github.com/overtrue/wechat/tree/3.2.4) (2017-01-24) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.2.3...3.2.4) + +**Closed issues:** + +- 如何在其他框架下使用$app-\>payment-\>handleNotify [\#574](https://github.com/overtrue/wechat/issues/574) +- 前后端分离单页下获取的config,认证失败 [\#565](https://github.com/overtrue/wechat/issues/565) +- 支付签名错误 [\#563](https://github.com/overtrue/wechat/issues/563) + +**Merged pull requests:** + +- Update Authorizer.php [\#577](https://github.com/overtrue/wechat/pull/577) ([ww380459000](https://github.com/ww380459000)) +- 补全通用卡接口 [\#575](https://github.com/overtrue/wechat/pull/575) ([XiaoLer](https://github.com/XiaoLer)) +- require ext-SimpleXML [\#572](https://github.com/overtrue/wechat/pull/572) ([garveen](https://github.com/garveen)) +- fix README Contribution link [\#571](https://github.com/overtrue/wechat/pull/571) ([zhwei](https://github.com/zhwei)) +- Add user data decryption. [\#570](https://github.com/overtrue/wechat/pull/570) ([mingyoung](https://github.com/mingyoung)) +- change request parameter [\#567](https://github.com/overtrue/wechat/pull/567) ([cloudsthere](https://github.com/cloudsthere)) +- 完善小程序代码 [\#566](https://github.com/overtrue/wechat/pull/566) ([mingyoung](https://github.com/mingyoung)) +- 添加小程序支持 [\#564](https://github.com/overtrue/wechat/pull/564) ([mingyoung](https://github.com/mingyoung)) + +## [3.2.3](https://github.com/overtrue/wechat/tree/3.2.3) (2017-01-04) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.2.2...3.2.3) + +**Closed issues:** + +- 文档里的自定义菜单中,group\_id是否为tag\_id的误写? [\#561](https://github.com/overtrue/wechat/issues/561) +- Open Platform有简明的使用文档吗?3ks [\#560](https://github.com/overtrue/wechat/issues/560) +- 刷新access\_token有效期,未发现有相关的封装 [\#540](https://github.com/overtrue/wechat/issues/540) + +**Merged pull requests:** + +- Update Card.php [\#562](https://github.com/overtrue/wechat/pull/562) ([XiaoLer](https://github.com/XiaoLer)) +- Apply fixes from StyleCI [\#559](https://github.com/overtrue/wechat/pull/559) ([overtrue](https://github.com/overtrue)) +- Update API.php [\#558](https://github.com/overtrue/wechat/pull/558) ([drogjh](https://github.com/drogjh)) +- optimized code [\#557](https://github.com/overtrue/wechat/pull/557) ([mingyoung](https://github.com/mingyoung)) + +## [3.2.2](https://github.com/overtrue/wechat/tree/3.2.2) (2016-12-27) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.2.1...3.2.2) + +**Closed issues:** + +- How to get authorize url? [\#555](https://github.com/overtrue/wechat/issues/555) + +**Merged pull requests:** + +- fixed downloadBill method result [\#556](https://github.com/overtrue/wechat/pull/556) ([hidehalo](https://github.com/hidehalo)) +- add config:log.permission for monolog [\#554](https://github.com/overtrue/wechat/pull/554) ([woshizoufeng](https://github.com/woshizoufeng)) +- Improve open platform support. [\#553](https://github.com/overtrue/wechat/pull/553) ([mingyoung](https://github.com/mingyoung)) +- Improve. [\#552](https://github.com/overtrue/wechat/pull/552) ([mingyoung](https://github.com/mingyoung)) +- add $forceRefresh param to js-\>ticket\(\) method [\#551](https://github.com/overtrue/wechat/pull/551) ([leo108](https://github.com/leo108)) + +## [3.2.1](https://github.com/overtrue/wechat/tree/3.2.1) (2016-12-20) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.2.0...3.2.1) + +**Merged pull requests:** + +- 增加小程序用jscode获取用户信息的接口 [\#550](https://github.com/overtrue/wechat/pull/550) ([soone](https://github.com/soone)) + +## [3.2.0](https://github.com/overtrue/wechat/tree/3.2.0) (2016-12-19) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.1.9...3.2.0) + +**Closed issues:** + +- 喵喵喵 [\#545](https://github.com/overtrue/wechat/issues/545) +- HttpException with uploadArticle API [\#544](https://github.com/overtrue/wechat/issues/544) +- 是否有接入小程序的计划 [\#543](https://github.com/overtrue/wechat/issues/543) +- "Call to undefined method Overtrue\Socialite\Providers\WeChat Provider::driver\(\) [\#536](https://github.com/overtrue/wechat/issues/536) +- 服务端Server模块回复音乐消息出错 [\#533](https://github.com/overtrue/wechat/issues/533) +- 用户授权出现The key "access\_token" could not be empty [\#527](https://github.com/overtrue/wechat/issues/527) + +**Merged pull requests:** + +- Apply fixes from StyleCI [\#549](https://github.com/overtrue/wechat/pull/549) ([overtrue](https://github.com/overtrue)) +- 添加摇一摇周边模块 [\#548](https://github.com/overtrue/wechat/pull/548) ([allen05ren](https://github.com/allen05ren)) +- Make some compatible. [\#542](https://github.com/overtrue/wechat/pull/542) ([mingyoung](https://github.com/mingyoung)) +- Apply fixes from StyleCI [\#541](https://github.com/overtrue/wechat/pull/541) ([overtrue](https://github.com/overtrue)) +- 改变了http 中 json 方法的接口, 从而支持 添加 添加 query参数 [\#539](https://github.com/overtrue/wechat/pull/539) ([shoaly](https://github.com/shoaly)) +- 提交 [\#537](https://github.com/overtrue/wechat/pull/537) ([shoaly](https://github.com/shoaly)) +- Apply fixes from StyleCI [\#535](https://github.com/overtrue/wechat/pull/535) ([overtrue](https://github.com/overtrue)) + +## [3.1.9](https://github.com/overtrue/wechat/tree/3.1.9) (2016-12-01) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.1.8...3.1.9) + +**Closed issues:** + +- 还是不懂怎么获取unionid [\#531](https://github.com/overtrue/wechat/issues/531) +- Scope 参数错误或没有 Scope 权限 [\#528](https://github.com/overtrue/wechat/issues/528) +- $\_SERVER\['SERVER\_ADDR'\] 在mac php7中获取不到 [\#520](https://github.com/overtrue/wechat/issues/520) +- 能否永久素材其他类型封装个download方法,跟临时一样 [\#505](https://github.com/overtrue/wechat/issues/505) +- V3.1 JSSDK使用疑惑 [\#503](https://github.com/overtrue/wechat/issues/503) +- 如何加入QQ群 [\#501](https://github.com/overtrue/wechat/issues/501) +- 能否在下一个版本把企业的相关接口整合集成进去 [\#496](https://github.com/overtrue/wechat/issues/496) +- 既然使用了monolog,那么在Application::initializeLogger只使用了文件流的特定形式来记录日志是否合理? [\#494](https://github.com/overtrue/wechat/issues/494) +- configForShareAddress [\#482](https://github.com/overtrue/wechat/issues/482) +- 更新微信文章的时候MatialEasyWeChat\Material,如果设置了show\_pic\_cover和content\_source\_url不会生效 [\#470](https://github.com/overtrue/wechat/issues/470) +- 请问 SDK 是否支持授权接入的公众号接口调用? [\#438](https://github.com/overtrue/wechat/issues/438) +- 通过unionid发送信息。 [\#411](https://github.com/overtrue/wechat/issues/411) +- 【新增】设备管理 [\#77](https://github.com/overtrue/wechat/issues/77) + +**Merged pull requests:** + +- Add support wechat open platform. [\#532](https://github.com/overtrue/wechat/pull/532) ([mingyoung](https://github.com/mingyoung)) +- Applied fixes from StyleCI [\#530](https://github.com/overtrue/wechat/pull/530) ([overtrue](https://github.com/overtrue)) +- 新增硬件设备api [\#529](https://github.com/overtrue/wechat/pull/529) ([soone](https://github.com/soone)) + +## [3.1.8](https://github.com/overtrue/wechat/tree/3.1.8) (2016-11-23) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.1.7...3.1.8) + +**Closed issues:** + +- SCAN 事件会出现无法提供服务 [\#525](https://github.com/overtrue/wechat/issues/525) + +## [3.1.7](https://github.com/overtrue/wechat/tree/3.1.7) (2016-10-26) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.1.6...3.1.7) + +**Closed issues:** + +- preg\_replace unicode 的兼容问题 [\#515](https://github.com/overtrue/wechat/issues/515) + +**Merged pull requests:** + +- support psr-http-message-bridge 1.0 [\#524](https://github.com/overtrue/wechat/pull/524) ([wppd](https://github.com/wppd)) +- Applied fixes from StyleCI [\#523](https://github.com/overtrue/wechat/pull/523) ([overtrue](https://github.com/overtrue)) +- for \#520 [\#522](https://github.com/overtrue/wechat/pull/522) ([jinchun](https://github.com/jinchun)) + +## [3.1.6](https://github.com/overtrue/wechat/tree/3.1.6) (2016-10-19) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.1.5...3.1.6) + +**Closed issues:** + +- PHP Fatal error: Uncaught HttpException [\#517](https://github.com/overtrue/wechat/issues/517) +- 微信支付回调出错 [\#514](https://github.com/overtrue/wechat/issues/514) + +**Merged pull requests:** + +- Fix xml preg replace [\#519](https://github.com/overtrue/wechat/pull/519) ([springjk](https://github.com/springjk)) +- fix the DOC [\#518](https://github.com/overtrue/wechat/pull/518) ([ac1982](https://github.com/ac1982)) + +## [3.1.5](https://github.com/overtrue/wechat/tree/3.1.5) (2016-10-13) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.1.4...3.1.5) + +**Closed issues:** + +- wechat 在 larave l5.3 使用 passport 包下无法安装 [\#513](https://github.com/overtrue/wechat/issues/513) + +**Merged pull requests:** + +- Applied fixes from StyleCI [\#512](https://github.com/overtrue/wechat/pull/512) ([overtrue](https://github.com/overtrue)) + +## [3.1.4](https://github.com/overtrue/wechat/tree/3.1.4) (2016-10-12) +[Full Changelog](https://github.com/overtrue/wechat/compare/2.1.39...3.1.4) + +**Closed issues:** + +- 微信卡券特殊票券创建之后为什么无法更新卡券信息一致提示code非法。 [\#511](https://github.com/overtrue/wechat/issues/511) +- 请添加 「退款方式」 参数 [\#509](https://github.com/overtrue/wechat/issues/509) +- 2.1.40命名空间巨变引发的重大问题\(疑似提错版本了\) [\#508](https://github.com/overtrue/wechat/issues/508) +- 卡券核销、查询建议 [\#506](https://github.com/overtrue/wechat/issues/506) +- 支付重复回调问题 [\#504](https://github.com/overtrue/wechat/issues/504) + +**Merged pull requests:** + +- Changed method doc to the right accepted param type [\#510](https://github.com/overtrue/wechat/pull/510) ([marianoasselborn](https://github.com/marianoasselborn)) +- 增加判断是否有人工客服帐号,避免出现无账号时候,头像为默认头像的情况 [\#502](https://github.com/overtrue/wechat/pull/502) ([hello2t](https://github.com/hello2t)) +- Applied fixes from StyleCI [\#500](https://github.com/overtrue/wechat/pull/500) ([overtrue](https://github.com/overtrue)) +- 为initializeLogger日志初始话函数添加判断分支 [\#499](https://github.com/overtrue/wechat/pull/499) ([403studio](https://github.com/403studio)) + +## [2.1.39](https://github.com/overtrue/wechat/tree/2.1.39) (2016-09-05) +[Full Changelog](https://github.com/overtrue/wechat/compare/2.1.41...2.1.39) + +## [2.1.41](https://github.com/overtrue/wechat/tree/2.1.41) (2016-09-05) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.1.3...2.1.41) + +**Closed issues:** + +- 调用接口次数超过最大限制问题 [\#493](https://github.com/overtrue/wechat/issues/493) +- 微信退款证书报错 Unable to set private key file [\#492](https://github.com/overtrue/wechat/issues/492) +- 微信支付存在问题 [\#489](https://github.com/overtrue/wechat/issues/489) +- 预支付下单 response body 为空 [\#488](https://github.com/overtrue/wechat/issues/488) +- https check issue [\#486](https://github.com/overtrue/wechat/issues/486) + +**Merged pull requests:** + +- update composer.json [\#498](https://github.com/overtrue/wechat/pull/498) ([ac1982](https://github.com/ac1982)) +- use openssl instead of mcrypt [\#497](https://github.com/overtrue/wechat/pull/497) ([ac1982](https://github.com/ac1982)) +- 修复 with 方法带数据的问题 [\#491](https://github.com/overtrue/wechat/pull/491) ([XiaoLer](https://github.com/XiaoLer)) + +## [3.1.3](https://github.com/overtrue/wechat/tree/3.1.3) (2016-08-08) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.1.2...3.1.3) + +**Closed issues:** + +- Laravel中写的最简单的例子在phpunit出错。 [\#485](https://github.com/overtrue/wechat/issues/485) +- 微信的消息回复的FromUserName和ToUserName是不是对调了 [\#484](https://github.com/overtrue/wechat/issues/484) +- 微信红包不能发给别的公众号的用户吗 [\#483](https://github.com/overtrue/wechat/issues/483) +- 用户授权登录问题 [\#481](https://github.com/overtrue/wechat/issues/481) +- cURL error 56: SSLRead\(\) return error -9806 [\#473](https://github.com/overtrue/wechat/issues/473) +- 会员卡开卡字段文档有错误 [\#471](https://github.com/overtrue/wechat/issues/471) +- Getting more done in GitHub with ZenHub [\#439](https://github.com/overtrue/wechat/issues/439) +- 微信支付下单错误 [\#376](https://github.com/overtrue/wechat/issues/376) + +**Merged pull requests:** + +- update the File class to recognize pdf file. [\#480](https://github.com/overtrue/wechat/pull/480) ([ac1982](https://github.com/ac1982)) +- update testActivateUserForm [\#478](https://github.com/overtrue/wechat/pull/478) ([wangniuniu](https://github.com/wangniuniu)) +- Scrutinizer Auto-Fixes [\#477](https://github.com/overtrue/wechat/pull/477) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer)) +- Applied fixes from StyleCI [\#476](https://github.com/overtrue/wechat/pull/476) ([overtrue](https://github.com/overtrue)) +- Scrutinizer Auto-Fixes [\#475](https://github.com/overtrue/wechat/pull/475) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer)) +- 开放自定义prefix和缓存键值方法 [\#474](https://github.com/overtrue/wechat/pull/474) ([XiaoLer](https://github.com/XiaoLer)) +- Applied fixes from StyleCI [\#469](https://github.com/overtrue/wechat/pull/469) ([overtrue](https://github.com/overtrue)) +- modify stats [\#468](https://github.com/overtrue/wechat/pull/468) ([wangniuniu](https://github.com/wangniuniu)) + +## [3.1.2](https://github.com/overtrue/wechat/tree/3.1.2) (2016-07-21) +[Full Changelog](https://github.com/overtrue/wechat/compare/2.1.38...3.1.2) + +**Closed issues:** + +- 素材管理中,上传图文下的上传图片,关于返回内容的差异 [\#466](https://github.com/overtrue/wechat/issues/466) +- spbill\_create\_ip参数设置 [\#461](https://github.com/overtrue/wechat/issues/461) + +**Merged pull requests:** + +- 更新获取标签下粉丝列表方法 [\#467](https://github.com/overtrue/wechat/pull/467) ([dingdayu](https://github.com/dingdayu)) +- Applied fixes from StyleCI [\#465](https://github.com/overtrue/wechat/pull/465) ([overtrue](https://github.com/overtrue)) +- card module. [\#464](https://github.com/overtrue/wechat/pull/464) ([wangniuniu](https://github.com/wangniuniu)) +- Applied fixes from StyleCI [\#463](https://github.com/overtrue/wechat/pull/463) ([overtrue](https://github.com/overtrue)) +- Scrutinizer Auto-Fixes [\#462](https://github.com/overtrue/wechat/pull/462) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer)) + +## [2.1.38](https://github.com/overtrue/wechat/tree/2.1.38) (2016-07-16) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.1.1...2.1.38) + +**Closed issues:** + +- 请问卡券管理功能整合上日程表了吗 [\#454](https://github.com/overtrue/wechat/issues/454) + +**Merged pull requests:** + +- Typo. [\#460](https://github.com/overtrue/wechat/pull/460) ([tianyong90](https://github.com/tianyong90)) +- Applied fixes from StyleCI [\#459](https://github.com/overtrue/wechat/pull/459) ([overtrue](https://github.com/overtrue)) +- add voice recognition [\#458](https://github.com/overtrue/wechat/pull/458) ([leniy](https://github.com/leniy)) +- Applied fixes from StyleCI [\#457](https://github.com/overtrue/wechat/pull/457) ([overtrue](https://github.com/overtrue)) +- Update API.php [\#456](https://github.com/overtrue/wechat/pull/456) ([marvin8212](https://github.com/marvin8212)) +- Update XML.php [\#455](https://github.com/overtrue/wechat/pull/455) ([canon4ever](https://github.com/canon4ever)) + +## [3.1.1](https://github.com/overtrue/wechat/tree/3.1.1) (2016-07-12) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.1.0...3.1.1) + +**Closed issues:** + +- 拿到code=CODE&state=STATE之后怎么拿到openid? [\#452](https://github.com/overtrue/wechat/issues/452) +- 安装出错 [\#450](https://github.com/overtrue/wechat/issues/450) +- 自定义菜单接口\(新版\)出错 [\#448](https://github.com/overtrue/wechat/issues/448) +- h5上没法打开微信app授权界面 [\#447](https://github.com/overtrue/wechat/issues/447) +- 重构卡券 [\#76](https://github.com/overtrue/wechat/issues/76) + +**Merged pull requests:** + +- typos. [\#453](https://github.com/overtrue/wechat/pull/453) ([tianye](https://github.com/tianye)) +- edit readme.md [\#451](https://github.com/overtrue/wechat/pull/451) ([tianyong90](https://github.com/tianyong90)) +- Add cache driver config. [\#449](https://github.com/overtrue/wechat/pull/449) ([dingdayu](https://github.com/dingdayu)) + +## [3.1.0](https://github.com/overtrue/wechat/tree/3.1.0) (2016-06-28) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.0.21...3.1.0) + +**Merged pull requests:** + +- Applied fixes from StyleCI [\#446](https://github.com/overtrue/wechat/pull/446) ([overtrue](https://github.com/overtrue)) +- New Staff API. [\#445](https://github.com/overtrue/wechat/pull/445) ([overtrue](https://github.com/overtrue)) +- 2.1 [\#444](https://github.com/overtrue/wechat/pull/444) ([dongnanyanhai](https://github.com/dongnanyanhai)) +- Fix path. [\#443](https://github.com/overtrue/wechat/pull/443) ([overtrue](https://github.com/overtrue)) + +## [3.0.21](https://github.com/overtrue/wechat/tree/3.0.21) (2016-06-17) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.0.1...3.0.21) + +**Closed issues:** + +- scan出现公众号暂时无法服务的消息 [\#436](https://github.com/overtrue/wechat/issues/436) +- scan出现公众号暂时无法服务的消息 [\#435](https://github.com/overtrue/wechat/issues/435) +- 用户标签接口无法使用 [\#433](https://github.com/overtrue/wechat/issues/433) +- WeChatProvider下的getAuthUrl个人觉得应该暴露出来 [\#432](https://github.com/overtrue/wechat/issues/432) +- 支持二维码扫描进入公众号推送的SCAN事件 [\#431](https://github.com/overtrue/wechat/issues/431) +- \[3.0\] EasyWeChat\Support\XML::parse方法会将空节点解析为空数组,而不是空字符串 [\#426](https://github.com/overtrue/wechat/issues/426) +- 下载二维码, $qrcode-\>download\($ticket,$paths\); 目录参数不可加入 中文 [\#420](https://github.com/overtrue/wechat/issues/420) +- \[help want\]Is hard to change default configuration of GuzzleHttp [\#415](https://github.com/overtrue/wechat/issues/415) +- PHP7.0 curl\_setopt 设置问题 [\#413](https://github.com/overtrue/wechat/issues/413) +- 无法通知微信支付完成 [\#412](https://github.com/overtrue/wechat/issues/412) +- 如何获取用户的unionid? [\#407](https://github.com/overtrue/wechat/issues/407) +- 是否支持多框架 [\#406](https://github.com/overtrue/wechat/issues/406) +- fuckTheWeChatInvalidJSON [\#405](https://github.com/overtrue/wechat/issues/405) +- Class 'GuzzleHttp\Middleware' not found [\#404](https://github.com/overtrue/wechat/issues/404) +- 支付统一下单接口签名错误 [\#402](https://github.com/overtrue/wechat/issues/402) +- payment里没有configForJSSDKPayment方法 [\#401](https://github.com/overtrue/wechat/issues/401) +- 查询支付的地址多了一个空格,导致查询失败,去掉最后的那个空格后就好了 [\#393](https://github.com/overtrue/wechat/issues/393) +- 网页授权过不了 [\#392](https://github.com/overtrue/wechat/issues/392) +- 微信AccessToken被动更新可能会有并发更新的情况出现 [\#390](https://github.com/overtrue/wechat/issues/390) +- 临时素材下载,文件名和扩展名之间会有2个\[.\] [\#389](https://github.com/overtrue/wechat/issues/389) +- 有一个地方变量名对不上 [\#380](https://github.com/overtrue/wechat/issues/380) +- 自定义缓存 [\#379](https://github.com/overtrue/wechat/issues/379) +- https://easywechat.org/ 底部 “开始使用” url拼错 [\#378](https://github.com/overtrue/wechat/issues/378) +- 在server.php里面调用yii的model,一直报错 [\#375](https://github.com/overtrue/wechat/issues/375) +- overture/wechat 2.1.36\(客服消息转发错误\) [\#374](https://github.com/overtrue/wechat/issues/374) +- 建议支持开发模式下禁用验证 [\#373](https://github.com/overtrue/wechat/issues/373) +- https://easywechat.org/ 导航 首页 about:blank [\#370](https://github.com/overtrue/wechat/issues/370) +- laravel 下session问题 [\#369](https://github.com/overtrue/wechat/issues/369) +- 关于Access——toekn [\#368](https://github.com/overtrue/wechat/issues/368) +- 返回支付页面时报错:"access\_token" could not be empty [\#367](https://github.com/overtrue/wechat/issues/367) +- xampp下js-\>config报错 [\#366](https://github.com/overtrue/wechat/issues/366) +- 官方文档有误 [\#360](https://github.com/overtrue/wechat/issues/360) +- \[BUG\] 微信收货地址无法成功 [\#359](https://github.com/overtrue/wechat/issues/359) +- 无法获取 $message-\>ScanCodeInfo-\>ScanType 对象 [\#358](https://github.com/overtrue/wechat/issues/358) +- \[Bugs\] 项目文档首页跳转问题 [\#357](https://github.com/overtrue/wechat/issues/357) +- Business和UnifiedOrder没有定义 [\#356](https://github.com/overtrue/wechat/issues/356) +- 你的网站访问不了。。。。https://easywechat.org/ [\#352](https://github.com/overtrue/wechat/issues/352) +- 连续多次执行微信支付退款报错 [\#348](https://github.com/overtrue/wechat/issues/348) +- 客服操作 都是 -1 错误 [\#344](https://github.com/overtrue/wechat/issues/344) +- 请使用openssl 而不是不安全的mcrypt来加密 [\#342](https://github.com/overtrue/wechat/issues/342) +- 文本类型的通知消息 [\#341](https://github.com/overtrue/wechat/issues/341) +- 服务器配置https 并且 通过阿里云 https cdn之后, 会出现 https 判断语句失效 [\#338](https://github.com/overtrue/wechat/issues/338) +- 作者请问者个sdk支持企业号吗? [\#336](https://github.com/overtrue/wechat/issues/336) +- laravel 5.1引入包报错 [\#331](https://github.com/overtrue/wechat/issues/331) +- 申请退款有问题 [\#328](https://github.com/overtrue/wechat/issues/328) +- 订单相关接口bug [\#327](https://github.com/overtrue/wechat/issues/327) +- 临时素材接口无法使用 [\#319](https://github.com/overtrue/wechat/issues/319) +- 使用sendNormal\(\),sendGroup\(\)发送红包时,报Undefined index: HTTP\_CLIENT\_IP [\#316](https://github.com/overtrue/wechat/issues/316) +- v3中微信卡券功能缺失? [\#307](https://github.com/overtrue/wechat/issues/307) +- 测试 [\#305](https://github.com/overtrue/wechat/issues/305) +- \[3.0\] 永久素材上传视频无法上传问题 [\#304](https://github.com/overtrue/wechat/issues/304) +- Cannot destroy active lambda function [\#296](https://github.com/overtrue/wechat/issues/296) +- 微信支付-》企业付款也可以增加个类上去,跟企业红包类似 [\#232](https://github.com/overtrue/wechat/issues/232) + +**Merged pull requests:** + +- Applied fixes from StyleCI [\#442](https://github.com/overtrue/wechat/pull/442) ([overtrue](https://github.com/overtrue)) +- NGINX HTTPS无法签名 [\#441](https://github.com/overtrue/wechat/pull/441) ([ares333](https://github.com/ares333)) +- Develop [\#440](https://github.com/overtrue/wechat/pull/440) ([overtrue](https://github.com/overtrue)) +- Develop [\#437](https://github.com/overtrue/wechat/pull/437) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#434](https://github.com/overtrue/wechat/pull/434) ([overtrue](https://github.com/overtrue)) +- 修改错误提示信息,方便跟踪错误 [\#430](https://github.com/overtrue/wechat/pull/430) ([zerozh](https://github.com/zerozh)) +- Develop [\#429](https://github.com/overtrue/wechat/pull/429) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#428](https://github.com/overtrue/wechat/pull/428) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#427](https://github.com/overtrue/wechat/pull/427) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#425](https://github.com/overtrue/wechat/pull/425) ([overtrue](https://github.com/overtrue)) +- update annotation [\#424](https://github.com/overtrue/wechat/pull/424) ([lilocon](https://github.com/lilocon)) +- Develop [\#421](https://github.com/overtrue/wechat/pull/421) ([overtrue](https://github.com/overtrue)) +- Set default timeout. [\#419](https://github.com/overtrue/wechat/pull/419) ([overtrue](https://github.com/overtrue)) +- Develop [\#418](https://github.com/overtrue/wechat/pull/418) ([overtrue](https://github.com/overtrue)) +- Develop [\#416](https://github.com/overtrue/wechat/pull/416) ([overtrue](https://github.com/overtrue)) +- better implementation for prepare oauth callback url [\#414](https://github.com/overtrue/wechat/pull/414) ([lichunqiang](https://github.com/lichunqiang)) +- Develop [\#410](https://github.com/overtrue/wechat/pull/410) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#409](https://github.com/overtrue/wechat/pull/409) ([overtrue](https://github.com/overtrue)) +- 增加微信支付服务商支持 [\#408](https://github.com/overtrue/wechat/pull/408) ([takatost](https://github.com/takatost)) +- Develop [\#403](https://github.com/overtrue/wechat/pull/403) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#400](https://github.com/overtrue/wechat/pull/400) ([overtrue](https://github.com/overtrue)) +- Scrutinizer Auto-Fixes [\#399](https://github.com/overtrue/wechat/pull/399) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer)) +- Develop [\#398](https://github.com/overtrue/wechat/pull/398) ([overtrue](https://github.com/overtrue)) +- Develop [\#397](https://github.com/overtrue/wechat/pull/397) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#396](https://github.com/overtrue/wechat/pull/396) ([overtrue](https://github.com/overtrue)) +- Typo & Improve code. [\#395](https://github.com/overtrue/wechat/pull/395) ([jinchun](https://github.com/jinchun)) +- Develop [\#394](https://github.com/overtrue/wechat/pull/394) ([overtrue](https://github.com/overtrue)) +- Bugfix close \#389 [\#391](https://github.com/overtrue/wechat/pull/391) ([overtrue](https://github.com/overtrue)) +- Update NoticeNoticeTest.php [\#388](https://github.com/overtrue/wechat/pull/388) ([xiabeifeng](https://github.com/xiabeifeng)) +- Update Notice.php [\#387](https://github.com/overtrue/wechat/pull/387) ([xiabeifeng](https://github.com/xiabeifeng)) +- Tests for \#384 [\#386](https://github.com/overtrue/wechat/pull/386) ([xiabeifeng](https://github.com/xiabeifeng)) +- Improve Notice API. [\#384](https://github.com/overtrue/wechat/pull/384) ([xiabeifeng](https://github.com/xiabeifeng)) +- 对应根 版本依赖 [\#382](https://github.com/overtrue/wechat/pull/382) ([parkshinhye](https://github.com/parkshinhye)) +- Develop [\#381](https://github.com/overtrue/wechat/pull/381) ([overtrue](https://github.com/overtrue)) +- Develop [\#377](https://github.com/overtrue/wechat/pull/377) ([overtrue](https://github.com/overtrue)) +- Fix test for \#371 [\#372](https://github.com/overtrue/wechat/pull/372) ([overtrue](https://github.com/overtrue)) +- 刷卡支付不需要notify\_url参数 [\#371](https://github.com/overtrue/wechat/pull/371) ([lilocon](https://github.com/lilocon)) +- Applied fixes from StyleCI [\#365](https://github.com/overtrue/wechat/pull/365) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#364](https://github.com/overtrue/wechat/pull/364) ([overtrue](https://github.com/overtrue)) +- Merge Develop [\#363](https://github.com/overtrue/wechat/pull/363) ([overtrue](https://github.com/overtrue)) +- Update composer.json [\#361](https://github.com/overtrue/wechat/pull/361) ([jaychan](https://github.com/jaychan)) +- Applied fixes from StyleCI [\#355](https://github.com/overtrue/wechat/pull/355) ([overtrue](https://github.com/overtrue)) +- \[ci skip\]fix document typo [\#354](https://github.com/overtrue/wechat/pull/354) ([lichunqiang](https://github.com/lichunqiang)) +- 自定义Logger [\#353](https://github.com/overtrue/wechat/pull/353) ([lilocon](https://github.com/lilocon)) +- Update Refund.php [\#351](https://github.com/overtrue/wechat/pull/351) ([jaring](https://github.com/jaring)) +- Applied fixes from StyleCI [\#350](https://github.com/overtrue/wechat/pull/350) ([overtrue](https://github.com/overtrue)) +- OpenSSL bugfix. [\#349](https://github.com/overtrue/wechat/pull/349) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#347](https://github.com/overtrue/wechat/pull/347) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#346](https://github.com/overtrue/wechat/pull/346) ([overtrue](https://github.com/overtrue)) +- Merge Develop [\#345](https://github.com/overtrue/wechat/pull/345) ([overtrue](https://github.com/overtrue)) +- 添加代码提示 [\#343](https://github.com/overtrue/wechat/pull/343) ([lilocon](https://github.com/lilocon)) +- Applied fixes from StyleCI [\#340](https://github.com/overtrue/wechat/pull/340) ([overtrue](https://github.com/overtrue)) +- Fix bug: Payment::downloadBill\(\) response error. [\#339](https://github.com/overtrue/wechat/pull/339) ([overtrue](https://github.com/overtrue)) +- change get\_client\_ip to get\_server\_ip [\#335](https://github.com/overtrue/wechat/pull/335) ([tianyong90](https://github.com/tianyong90)) +- Payment SSL. [\#334](https://github.com/overtrue/wechat/pull/334) ([overtrue](https://github.com/overtrue)) +- Add a helper to get correct client ip address. fixed \#316 [\#333](https://github.com/overtrue/wechat/pull/333) ([tianyong90](https://github.com/tianyong90)) +- Dependency Bugfix. overtrue/laravel-wechat\#24 [\#332](https://github.com/overtrue/wechat/pull/332) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#330](https://github.com/overtrue/wechat/pull/330) ([overtrue](https://github.com/overtrue)) +- Merge Develop [\#329](https://github.com/overtrue/wechat/pull/329) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#326](https://github.com/overtrue/wechat/pull/326) ([overtrue](https://github.com/overtrue)) +- Add order default notify\_url. [\#325](https://github.com/overtrue/wechat/pull/325) ([foreverglory](https://github.com/foreverglory)) +- Revert "Applied fixes from StyleCI" [\#323](https://github.com/overtrue/wechat/pull/323) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#322](https://github.com/overtrue/wechat/pull/322) ([overtrue](https://github.com/overtrue)) +- Develop [\#321](https://github.com/overtrue/wechat/pull/321) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#320](https://github.com/overtrue/wechat/pull/320) ([overtrue](https://github.com/overtrue)) +- 模板消息添加【 获取模板列表】和【 删除模板】接口 [\#318](https://github.com/overtrue/wechat/pull/318) ([forecho](https://github.com/forecho)) +- Applied fixes from StyleCI [\#314](https://github.com/overtrue/wechat/pull/314) ([overtrue](https://github.com/overtrue)) +- fix Temporary upload bug [\#313](https://github.com/overtrue/wechat/pull/313) ([mani95lisa](https://github.com/mani95lisa)) +- Applied fixes from StyleCI [\#312](https://github.com/overtrue/wechat/pull/312) ([overtrue](https://github.com/overtrue)) +- MerchantPay Class [\#311](https://github.com/overtrue/wechat/pull/311) ([ac1982](https://github.com/ac1982)) +- Applied fixes from StyleCI [\#309](https://github.com/overtrue/wechat/pull/309) ([overtrue](https://github.com/overtrue)) +- Merge Develop [\#308](https://github.com/overtrue/wechat/pull/308) ([overtrue](https://github.com/overtrue)) +- 删除裂变红包接口中的ip参数 [\#306](https://github.com/overtrue/wechat/pull/306) ([xjchengo](https://github.com/xjchengo)) +- fix code style and some spelling mistakes [\#303](https://github.com/overtrue/wechat/pull/303) ([jinchun](https://github.com/jinchun)) +- Merge Develop [\#302](https://github.com/overtrue/wechat/pull/302) ([overtrue](https://github.com/overtrue)) +- Add method for app payment [\#301](https://github.com/overtrue/wechat/pull/301) ([lichunqiang](https://github.com/lichunqiang)) +- Removed the return syntax [\#300](https://github.com/overtrue/wechat/pull/300) ([lichunqiang](https://github.com/lichunqiang)) +- add return tag [\#299](https://github.com/overtrue/wechat/pull/299) ([lichunqiang](https://github.com/lichunqiang)) +- Merge Develop [\#298](https://github.com/overtrue/wechat/pull/298) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#297](https://github.com/overtrue/wechat/pull/297) ([overtrue](https://github.com/overtrue)) +- \[ci skip\]Update .gitattributes [\#295](https://github.com/overtrue/wechat/pull/295) ([lichunqiang](https://github.com/lichunqiang)) +- Merge Develop [\#294](https://github.com/overtrue/wechat/pull/294) ([overtrue](https://github.com/overtrue)) + +## [3.0.1](https://github.com/overtrue/wechat/tree/3.0.1) (2016-02-19) +[Full Changelog](https://github.com/overtrue/wechat/compare/3.0...3.0.1) + +**Closed issues:** + +- composer 安装 3.0版本,报错如下: [\#291](https://github.com/overtrue/wechat/issues/291) +- \[3.0\] 下载永久素材时,微信返回的Content-Type不正确,导致出错。 [\#290](https://github.com/overtrue/wechat/issues/290) +- 挖个坑,自己跳 [\#147](https://github.com/overtrue/wechat/issues/147) + +**Merged pull requests:** + +- Applied fixes from StyleCI [\#293](https://github.com/overtrue/wechat/pull/293) ([overtrue](https://github.com/overtrue)) +- Merge Develop [\#292](https://github.com/overtrue/wechat/pull/292) ([overtrue](https://github.com/overtrue)) + +## [3.0](https://github.com/overtrue/wechat/tree/3.0) (2016-02-17) +[Full Changelog](https://github.com/overtrue/wechat/compare/2.1.0...3.0) + +**Implemented enhancements:** + +- MIME json 格式检查优化 [\#49](https://github.com/overtrue/wechat/issues/49) +- 获取 refresh\_token,access\_token [\#43](https://github.com/overtrue/wechat/issues/43) +- 关于API\_TOKEN\_REFRESH [\#20](https://github.com/overtrue/wechat/issues/20) + +**Closed issues:** + +- \[3.0\] 无法获取用户分组信息 [\#285](https://github.com/overtrue/wechat/issues/285) +- 新的laravel 5.2 不能兼容了 [\#284](https://github.com/overtrue/wechat/issues/284) +- \[3.0\]Message/Article类的$properties内的source\_url没有正常转换为content\_source\_url. [\#281](https://github.com/overtrue/wechat/issues/281) +- 3.0删除个性菜单失败 [\#280](https://github.com/overtrue/wechat/issues/280) +- 也许你该给一个代码贡献规范 [\#277](https://github.com/overtrue/wechat/issues/277) +- 3.0网页授权时scope为snsapi\_base得不到openid [\#276](https://github.com/overtrue/wechat/issues/276) +- wechat3.0中 有2个地方的js调用参数不一样,超哥没有提供 [\#272](https://github.com/overtrue/wechat/issues/272) +- 我想知道2.X和3.0有什么大的区别! [\#270](https://github.com/overtrue/wechat/issues/270) +- 2.1: Link 消息类型没有实现 [\#269](https://github.com/overtrue/wechat/issues/269) +- 关于模板消息换行的问题 [\#266](https://github.com/overtrue/wechat/issues/266) +- easywechat Invalid request [\#265](https://github.com/overtrue/wechat/issues/265) +- 40029不合法的oauth\_code [\#264](https://github.com/overtrue/wechat/issues/264) +- 下载素材的一个小问题 [\#263](https://github.com/overtrue/wechat/issues/263) +- \[2.1\] 微信自定义菜单结构变更导致`Menu::get\(\)` 无法读取个性化菜单 [\#262](https://github.com/overtrue/wechat/issues/262) +- payment中是不是不包含H5和JS的生成配置文件的方法了? [\#261](https://github.com/overtrue/wechat/issues/261) +- payment下prepare方法bug [\#260](https://github.com/overtrue/wechat/issues/260) +- UserServiceProvider中似乎忘记注册user.group了 [\#256](https://github.com/overtrue/wechat/issues/256) +- 2.1.X版媒体下载没有扩展名 [\#252](https://github.com/overtrue/wechat/issues/252) +- 为什么所有的子模块在自己的库都是develop分支 [\#247](https://github.com/overtrue/wechat/issues/247) +- 网页授权使用跳转的bug [\#246](https://github.com/overtrue/wechat/issues/246) +- typo of variable [\#245](https://github.com/overtrue/wechat/issues/245) +- The implementation class of ServerServiceProvider missing an important [\#244](https://github.com/overtrue/wechat/issues/244) +- \[3.0\]\[payment\] 两个可能的bug [\#235](https://github.com/overtrue/wechat/issues/235) +- 发送多图文 [\#233](https://github.com/overtrue/wechat/issues/233) +- 自定义菜单返回应该把个性化自定义菜单也一起返回 [\#231](https://github.com/overtrue/wechat/issues/231) +- 发送模板消息 CRUL 错误 [\#223](https://github.com/overtrue/wechat/issues/223) +- 客服接口暂时测到有3个bug,麻烦修复 [\#222](https://github.com/overtrue/wechat/issues/222) +- JSSDK access\_token missing [\#211](https://github.com/overtrue/wechat/issues/211) +- Js.php/ticket [\#210](https://github.com/overtrue/wechat/issues/210) +- 微信支付里有一个收货地址共享 ,超哥你这里没有,可以加一下不? [\#204](https://github.com/overtrue/wechat/issues/204) +- 小问题 [\#203](https://github.com/overtrue/wechat/issues/203) +- 网页授权 跳转 [\#202](https://github.com/overtrue/wechat/issues/202) +- access token 重复添加的问题 [\#201](https://github.com/overtrue/wechat/issues/201) +- authorize snsapi\_base 下可以获取unionid [\#198](https://github.com/overtrue/wechat/issues/198) +- 网页授权 [\#189](https://github.com/overtrue/wechat/issues/189) +- 一点建议 [\#188](https://github.com/overtrue/wechat/issues/188) +- 接口更新-新增临时素材接口变动 [\#186](https://github.com/overtrue/wechat/issues/186) +- 接入多个公众号不用id [\#185](https://github.com/overtrue/wechat/issues/185) +- \[Insight\] Files should not be executable [\#184](https://github.com/overtrue/wechat/issues/184) +- 建议不要写死Http [\#183](https://github.com/overtrue/wechat/issues/183) +- laravel4.2安装不成功 [\#182](https://github.com/overtrue/wechat/issues/182) +- 是否支持laravel4.2 [\#181](https://github.com/overtrue/wechat/issues/181) +- 微信出个性化菜单了,希望支持 [\#180](https://github.com/overtrue/wechat/issues/180) +- 3.0 composer依赖Symfony2.7。能不能支持Symfony3.0? [\#179](https://github.com/overtrue/wechat/issues/179) +- 发送链接类消息错误 [\#175](https://github.com/overtrue/wechat/issues/175) +- Throw Exception的时候 Intel server status 设置为200是不是好一些 [\#174](https://github.com/overtrue/wechat/issues/174) +- 生成临时二维码时,返回EventKey不是传递的值 [\#173](https://github.com/overtrue/wechat/issues/173) +- 关于素材获取的一个建议 [\#172](https://github.com/overtrue/wechat/issues/172) +- 能否增加微信APP支付相关方法 [\#171](https://github.com/overtrue/wechat/issues/171) +- 微信回调URL回调不到 [\#170](https://github.com/overtrue/wechat/issues/170) +- 素材管理添加永久素材返回JSON/XML内容错误 [\#169](https://github.com/overtrue/wechat/issues/169) +- \[消息的使用\] 中 \[上传素材文件\] 的文档示例貌似有误 [\#168](https://github.com/overtrue/wechat/issues/168) +- 素材管理里的download方法不是很符合sdk一站式的解决. [\#165](https://github.com/overtrue/wechat/issues/165) +- \[Wechat\]不合法的oauth\_code' in /src/Wechat/Http.php:124 [\#164](https://github.com/overtrue/wechat/issues/164) +- AccessToken Expired Error Code [\#163](https://github.com/overtrue/wechat/issues/163) +- 素材管理接口出错 [\#162](https://github.com/overtrue/wechat/issues/162) +- 两处代码php5.4才能运行 [\#158](https://github.com/overtrue/wechat/issues/158) +- extension is null when calling `download video` in wechat.media [\#157](https://github.com/overtrue/wechat/issues/157) +- Payment/UnifiedOrder does not support serialize or create by array [\#155](https://github.com/overtrue/wechat/issues/155) +- 没有找到"微信支付-\>查询订单"相关功能 [\#150](https://github.com/overtrue/wechat/issues/150) +- 请教,Cache::setter中your\_custom\_set\_cache怎么使用 [\#149](https://github.com/overtrue/wechat/issues/149) +- 发生异常时, 希望能把发送和接收的原始数据记录下来. [\#148](https://github.com/overtrue/wechat/issues/148) +- 发送红包,证书错误 [\#144](https://github.com/overtrue/wechat/issues/144) +- 发视频消息总返回 -1 [\#143](https://github.com/overtrue/wechat/issues/143) +- 关于PHP版本 [\#141](https://github.com/overtrue/wechat/issues/141) +- Server消息回复必须以事件方式吗? [\#140](https://github.com/overtrue/wechat/issues/140) +- 微信支付相关文档细化 [\#138](https://github.com/overtrue/wechat/issues/138) +- 好奇地问个问题,这项目的测试用例放在哪? [\#135](https://github.com/overtrue/wechat/issues/135) +- 试了两次,真的不会用 [\#134](https://github.com/overtrue/wechat/issues/134) +- 不知道这算不算是个BUG [\#133](https://github.com/overtrue/wechat/issues/133) +- 微信小店 [\#130](https://github.com/overtrue/wechat/issues/130) +- 多次遇到 accesstoken 无效的问题 [\#129](https://github.com/overtrue/wechat/issues/129) +- MCH\_KEY 微信支付 [\#128](https://github.com/overtrue/wechat/issues/128) +- 使用flightphp框架,验证URL的时候,在Apache下接入成功,在Nginx接入失败 [\#126](https://github.com/overtrue/wechat/issues/126) +- 好东西!可惜没有我需要的微信红包 [\#125](https://github.com/overtrue/wechat/issues/125) +- Cache存储部件可定制 [\#120](https://github.com/overtrue/wechat/issues/120) +- 关于Bag [\#119](https://github.com/overtrue/wechat/issues/119) +- 将代码部署到负载均衡上如何管理access token [\#118](https://github.com/overtrue/wechat/issues/118) +- 消息接受和回复时,如果不对消息做回复,该如何做? [\#117](https://github.com/overtrue/wechat/issues/117) +- 请教一个问题 [\#116](https://github.com/overtrue/wechat/issues/116) +- 关于 Cache [\#115](https://github.com/overtrue/wechat/issues/115) +- 如何才能获取普通的access\_token [\#113](https://github.com/overtrue/wechat/issues/113) +- $HTTP\_RAW\_POST\_DATA DEPRECATED [\#111](https://github.com/overtrue/wechat/issues/111) +- App支付缺少错误码 [\#109](https://github.com/overtrue/wechat/issues/109) +- 当用户信息有 " 字符时系统出错 \(用户与用户组管理接口\) [\#107](https://github.com/overtrue/wechat/issues/107) +- 提示错误 [\#106](https://github.com/overtrue/wechat/issues/106) +- 使用企业号的时候 接入失败啊,在验证url的时候 [\#104](https://github.com/overtrue/wechat/issues/104) +- 支付签名错误 [\#101](https://github.com/overtrue/wechat/issues/101) +- 微信支付.$payment-\>getConfig\(\)调用时候\[Wechat\]系统繁忙,此时请开发者稍候再试. [\#96](https://github.com/overtrue/wechat/issues/96) +- wechat/src/Wechat/Payment/UnifiedOrder.php 小问题 [\#94](https://github.com/overtrue/wechat/issues/94) +- 请教laravel中如何在微信支付中 catch UnifiedOrder 抛出的异常? [\#93](https://github.com/overtrue/wechat/issues/93) +- 是否可以增加一个第三方接口融合功能 [\#91](https://github.com/overtrue/wechat/issues/91) +- 订单查询 [\#90](https://github.com/overtrue/wechat/issues/90) +- 如何不下载图片,通过mediaId获取图片存储的URL [\#89](https://github.com/overtrue/wechat/issues/89) +- 'Undefined index: HTTP\_HOST' [\#88](https://github.com/overtrue/wechat/issues/88) +- Undefined index: HTTP\_HOST [\#87](https://github.com/overtrue/wechat/issues/87) +- 不能上传gif格式的图片素材 [\#84](https://github.com/overtrue/wechat/issues/84) +- OAuth重构 [\#74](https://github.com/overtrue/wechat/issues/74) +- \[3.0\] Tasks [\#50](https://github.com/overtrue/wechat/issues/50) +- appId 和 appSecret不要作为各个类的构造参数 [\#114](https://github.com/overtrue/wechat/issues/114) +- 增加debug相关的选项 [\#112](https://github.com/overtrue/wechat/issues/112) +- 好像没有获取自动回复数据接口 [\#108](https://github.com/overtrue/wechat/issues/108) +- js端查看微信卡券接口 chooseCard [\#79](https://github.com/overtrue/wechat/issues/79) +- 【新增】支付 [\#78](https://github.com/overtrue/wechat/issues/78) +- 模板消息重构 [\#75](https://github.com/overtrue/wechat/issues/75) +- 素材下载自动识别MIME生成后缀 [\#54](https://github.com/overtrue/wechat/issues/54) +- \[建议\] 深度结合微信多图文与素材管理 [\#46](https://github.com/overtrue/wechat/issues/46) +- 群发功能 [\#18](https://github.com/overtrue/wechat/issues/18) + +**Merged pull requests:** + +- 3.0 [\#289](https://github.com/overtrue/wechat/pull/289) ([overtrue](https://github.com/overtrue)) +- Merge Develop [\#288](https://github.com/overtrue/wechat/pull/288) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#287](https://github.com/overtrue/wechat/pull/287) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#286](https://github.com/overtrue/wechat/pull/286) ([overtrue](https://github.com/overtrue)) +- Fix bug in batchGet method. [\#283](https://github.com/overtrue/wechat/pull/283) ([tianyong90](https://github.com/tianyong90)) +- Typo. [\#279](https://github.com/overtrue/wechat/pull/279) ([overtrue](https://github.com/overtrue)) +- Add contribution guide. resolves \#277 [\#278](https://github.com/overtrue/wechat/pull/278) ([overtrue](https://github.com/overtrue)) +- Develop [\#274](https://github.com/overtrue/wechat/pull/274) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#273](https://github.com/overtrue/wechat/pull/273) ([overtrue](https://github.com/overtrue)) +- Develop [\#271](https://github.com/overtrue/wechat/pull/271) ([overtrue](https://github.com/overtrue)) +- Merge Develop [\#268](https://github.com/overtrue/wechat/pull/268) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#267](https://github.com/overtrue/wechat/pull/267) ([overtrue](https://github.com/overtrue)) +- Update QRCode.php [\#258](https://github.com/overtrue/wechat/pull/258) ([webshiyue](https://github.com/webshiyue)) +- Add tests for LuckyMoney. [\#255](https://github.com/overtrue/wechat/pull/255) ([tianyong90](https://github.com/tianyong90)) +- CS. [\#254](https://github.com/overtrue/wechat/pull/254) ([overtrue](https://github.com/overtrue)) +- Scrutinizer Auto-Fixes [\#253](https://github.com/overtrue/wechat/pull/253) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer)) +- Applied fixes from StyleCI [\#251](https://github.com/overtrue/wechat/pull/251) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#250](https://github.com/overtrue/wechat/pull/250) ([overtrue](https://github.com/overtrue)) +- Merge Develop [\#249](https://github.com/overtrue/wechat/pull/249) ([overtrue](https://github.com/overtrue)) +- Merge Develop [\#248](https://github.com/overtrue/wechat/pull/248) ([overtrue](https://github.com/overtrue)) +- Merge from Develop [\#243](https://github.com/overtrue/wechat/pull/243) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#242](https://github.com/overtrue/wechat/pull/242) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#241](https://github.com/overtrue/wechat/pull/241) ([overtrue](https://github.com/overtrue)) +- Add Luckymoney. [\#240](https://github.com/overtrue/wechat/pull/240) ([tianyong90](https://github.com/tianyong90)) +- Applied fixes from StyleCI [\#237](https://github.com/overtrue/wechat/pull/237) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#234](https://github.com/overtrue/wechat/pull/234) ([overtrue](https://github.com/overtrue)) +- Multiple News Items Support [\#230](https://github.com/overtrue/wechat/pull/230) ([fanglinks](https://github.com/fanglinks)) +- Applied fixes from StyleCI [\#221](https://github.com/overtrue/wechat/pull/221) ([overtrue](https://github.com/overtrue)) +- \[3.0\]\[Bugfix\]发送图文消息缺少type [\#217](https://github.com/overtrue/wechat/pull/217) ([sunbiao0526](https://github.com/sunbiao0526)) +- fix Js.php 获取自定义cache对象 [\#215](https://github.com/overtrue/wechat/pull/215) ([sunbiao0526](https://github.com/sunbiao0526)) +- Applied fixes from StyleCI [\#197](https://github.com/overtrue/wechat/pull/197) ([overtrue](https://github.com/overtrue)) +- Add alias [\#196](https://github.com/overtrue/wechat/pull/196) ([ruchengtang](https://github.com/ruchengtang)) +- Applied fixes from StyleCI [\#195](https://github.com/overtrue/wechat/pull/195) ([overtrue](https://github.com/overtrue)) +- Applied fixes from StyleCI [\#194](https://github.com/overtrue/wechat/pull/194) ([overtrue](https://github.com/overtrue)) +- Add Broadcast. [\#193](https://github.com/overtrue/wechat/pull/193) ([ruchengtang](https://github.com/ruchengtang)) +- 微信红包类优化 [\#190](https://github.com/overtrue/wechat/pull/190) ([tianyong90](https://github.com/tianyong90)) +- Update ServerServiceProvider.php [\#187](https://github.com/overtrue/wechat/pull/187) ([ghost](https://github.com/ghost)) +- Update README\_EN.md [\#178](https://github.com/overtrue/wechat/pull/178) ([spekulatius](https://github.com/spekulatius)) +- 添加群发消息文档 [\#177](https://github.com/overtrue/wechat/pull/177) ([ruchengtang](https://github.com/ruchengtang)) +- 群发消息 [\#176](https://github.com/overtrue/wechat/pull/176) ([ruchengtang](https://github.com/ruchengtang)) +- Master [\#167](https://github.com/overtrue/wechat/pull/167) ([xiaohome](https://github.com/xiaohome)) +- 微信小店 [\#166](https://github.com/overtrue/wechat/pull/166) ([xiaohome](https://github.com/xiaohome)) +- 红包类更新 [\#161](https://github.com/overtrue/wechat/pull/161) ([overtrue](https://github.com/overtrue)) +- 加入摇一摇红包类,红包类提升至Overtrue命名空间 [\#160](https://github.com/overtrue/wechat/pull/160) ([tianyong90](https://github.com/tianyong90)) +- 2.1 [\#159](https://github.com/overtrue/wechat/pull/159) ([overtrue](https://github.com/overtrue)) +- Update QRCode.php [\#156](https://github.com/overtrue/wechat/pull/156) ([ruchengtang](https://github.com/ruchengtang)) +- 修复使用!=,来判断0 != null 的时候的一个bug [\#154](https://github.com/overtrue/wechat/pull/154) ([Liv1020](https://github.com/Liv1020)) +- 调整多客服类删除客服方法 [\#151](https://github.com/overtrue/wechat/pull/151) ([tianyong90](https://github.com/tianyong90)) +- 修复个bug [\#146](https://github.com/overtrue/wechat/pull/146) ([xiaohome](https://github.com/xiaohome)) +- Update README.md [\#142](https://github.com/overtrue/wechat/pull/142) ([parkshinhye](https://github.com/parkshinhye)) +- Fix code style to PSR-2 [\#139](https://github.com/overtrue/wechat/pull/139) ([tianyong90](https://github.com/tianyong90)) +- 加入红包工具类,支持现金和裂变红包的发送及查询 [\#137](https://github.com/overtrue/wechat/pull/137) ([tianyong90](https://github.com/tianyong90)) +- 卡券类批量获取卡券ID方法支持仅获取指定状态卡券 [\#132](https://github.com/overtrue/wechat/pull/132) ([tianyong90](https://github.com/tianyong90)) +- 添加客服 卡券回复!!! [\#124](https://github.com/overtrue/wechat/pull/124) ([parkshinhye](https://github.com/parkshinhye)) +- 调整退款类中一处异常抛出逻辑并修正单词拼写错误 [\#122](https://github.com/overtrue/wechat/pull/122) ([tianyong90](https://github.com/tianyong90)) +- 加入创建卡券货架接口 [\#121](https://github.com/overtrue/wechat/pull/121) ([tianyong90](https://github.com/tianyong90)) +- 增加退款类 [\#105](https://github.com/overtrue/wechat/pull/105) ([jaring](https://github.com/jaring)) +- 增加获取用户已领取卡券方法 [\#103](https://github.com/overtrue/wechat/pull/103) ([tenstone](https://github.com/tenstone)) +- Scrutinizer Auto-Fixes [\#100](https://github.com/overtrue/wechat/pull/100) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer)) +- 修正二维码类中生成卡券二维码方法 [\#99](https://github.com/overtrue/wechat/pull/99) ([tianyong90](https://github.com/tianyong90)) +- 卡券接口加入添加测试白名单方法 [\#98](https://github.com/overtrue/wechat/pull/98) ([tianyong90](https://github.com/tianyong90)) +- 依样画葫芦写了一个查询订单,更改了UnifiedOrder中Http初始化 [\#95](https://github.com/overtrue/wechat/pull/95) ([jaring](https://github.com/jaring)) +- accessToken根据appId变化 [\#92](https://github.com/overtrue/wechat/pull/92) ([keepeye](https://github.com/keepeye)) +- Fix payment sign bug. [\#82](https://github.com/overtrue/wechat/pull/82) ([0i](https://github.com/0i)) +- \[wiki\] wechat payment [\#81](https://github.com/overtrue/wechat/pull/81) ([0i](https://github.com/0i)) + +## [2.1.0](https://github.com/overtrue/wechat/tree/2.1.0) (2015-08-18) +[Full Changelog](https://github.com/overtrue/wechat/compare/2.0.35...2.1.0) + +**Merged pull requests:** + +- Wechat Payment [\#80](https://github.com/overtrue/wechat/pull/80) ([0i](https://github.com/0i)) + +## [2.0.35](https://github.com/overtrue/wechat/tree/2.0.35) (2015-08-11) +[Full Changelog](https://github.com/overtrue/wechat/compare/2.0.1...2.0.35) + +**Implemented enhancements:** + +- Overtrue\Wechat\Http识别JSON的问题 [\#47](https://github.com/overtrue/wechat/issues/47) + +**Fixed bugs:** + +- 模板消息简单格式无效 [\#34](https://github.com/overtrue/wechat/issues/34) + +**Closed issues:** + +- $data是数组,title输出不了内容 [\#73](https://github.com/overtrue/wechat/issues/73) +- 回调是如何传递外部参数的? [\#72](https://github.com/overtrue/wechat/issues/72) +- 【建议】可以添加微信js的功能吗? [\#71](https://github.com/overtrue/wechat/issues/71) +- Message::make\('link'\) 无效 [\#70](https://github.com/overtrue/wechat/issues/70) +- 监听消息 返回Bad Request [\#65](https://github.com/overtrue/wechat/issues/65) +- 微信素材管理小改版,求跟上~ [\#64](https://github.com/overtrue/wechat/issues/64) +- 在新浪SAE平台上的部署问题 [\#63](https://github.com/overtrue/wechat/issues/63) +- $xmlInput = file\_get\_contents\('php://input'\);貌似在某些版本的PHP有问题还是怎的 [\#57](https://github.com/overtrue/wechat/issues/57) +- 卡券的 attachExtension 方法 [\#56](https://github.com/overtrue/wechat/issues/56) +- 网页授权$auth-\>authorize\(\) 后还需要保存access\_token吗? [\#53](https://github.com/overtrue/wechat/issues/53) +- php 5.6版本下出现错误(5.6以下版本正常) [\#51](https://github.com/overtrue/wechat/issues/51) +- 消息发送后服务器无法正确返回响应 [\#48](https://github.com/overtrue/wechat/issues/48) +- token验证失败 [\#45](https://github.com/overtrue/wechat/issues/45) +- 微信关注自动回复问题 [\#44](https://github.com/overtrue/wechat/issues/44) +- js sdk config 建议增加 beta 字段 [\#35](https://github.com/overtrue/wechat/issues/35) +- 关于Util\HTTP::encode\(\)中的urlencode\(\)/urldecode\(\)成组操作的疑问 [\#31](https://github.com/overtrue/wechat/issues/31) +- Media::updateNews\(\) 方法与微信API不一致 [\#29](https://github.com/overtrue/wechat/issues/29) +- 希望能有一个ThinkPHP的使用示例 [\#28](https://github.com/overtrue/wechat/issues/28) +- 事件消息 [\#22](https://github.com/overtrue/wechat/issues/22) +- 模板消息notice [\#21](https://github.com/overtrue/wechat/issues/21) +- 关于获取(接收)用户发送消息 [\#19](https://github.com/overtrue/wechat/issues/19) +- 微信公众号绑定的一点问题,请教。 [\#16](https://github.com/overtrue/wechat/issues/16) +- 获取素材列表错误 [\#15](https://github.com/overtrue/wechat/issues/15) + +**Merged pull requests:** + +- Scrutinizer Auto-Fixes [\#69](https://github.com/overtrue/wechat/pull/69) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer)) +- Scrutinizer Auto-Fixes [\#68](https://github.com/overtrue/wechat/pull/68) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer)) +- Scrutinizer Auto-Fixes [\#67](https://github.com/overtrue/wechat/pull/67) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer)) +- Fixed StyleCI config [\#66](https://github.com/overtrue/wechat/pull/66) ([GrahamCampbell](https://github.com/GrahamCampbell)) +- 洁癖爆发了。。。 [\#62](https://github.com/overtrue/wechat/pull/62) ([TheNorthMemory](https://github.com/TheNorthMemory)) +- fix: js getUrl use Url::current\(\) [\#61](https://github.com/overtrue/wechat/pull/61) ([wdjwxh](https://github.com/wdjwxh)) +- bug-fix: add x-forwarded-host for Url::current [\#60](https://github.com/overtrue/wechat/pull/60) ([wdjwxh](https://github.com/wdjwxh)) +- Fix request method for User::batchGet\(\), should be POST with JSON. [\#59](https://github.com/overtrue/wechat/pull/59) ([acgrid](https://github.com/acgrid)) +- optimize some code [\#58](https://github.com/overtrue/wechat/pull/58) ([tabalt](https://github.com/tabalt)) +- 增加使用media id发送图文消息的功能 [\#52](https://github.com/overtrue/wechat/pull/52) ([zengohm](https://github.com/zengohm)) +- fix Staff::delete, let it works [\#42](https://github.com/overtrue/wechat/pull/42) ([TheNorthMemory](https://github.com/TheNorthMemory)) +- 支持自定义菜单类型:下发消息media\_id、跳转图文消息view\_limited [\#40](https://github.com/overtrue/wechat/pull/40) ([acgrid](https://github.com/acgrid)) +- docline comments & fix AccessToken parameter typos [\#39](https://github.com/overtrue/wechat/pull/39) ([TheNorthMemory](https://github.com/TheNorthMemory)) +- Merge from master [\#38](https://github.com/overtrue/wechat/pull/38) ([overtrue](https://github.com/overtrue)) +- 客服接口Bugfix [\#37](https://github.com/overtrue/wechat/pull/37) ([overtrue](https://github.com/overtrue)) +- fix Staff and AccessToken typos [\#36](https://github.com/overtrue/wechat/pull/36) ([TheNorthMemory](https://github.com/TheNorthMemory)) +- Update QRCode.php [\#33](https://github.com/overtrue/wechat/pull/33) ([refear99](https://github.com/refear99)) +- English Readme [\#32](https://github.com/overtrue/wechat/pull/32) ([hareluya](https://github.com/hareluya)) +- 更新图文消息方法Media::updateNews\(\)与微信API不一致 [\#30](https://github.com/overtrue/wechat/pull/30) ([acgrid](https://github.com/acgrid)) +- 代码之美在于不断修正 :\) [\#27](https://github.com/overtrue/wechat/pull/27) ([TheNorthMemory](https://github.com/TheNorthMemory)) +- the json\_encode $depth parameter was added@5.5.0 [\#26](https://github.com/overtrue/wechat/pull/26) ([TheNorthMemory](https://github.com/TheNorthMemory)) +- fix \#4 for PHP5.3 [\#25](https://github.com/overtrue/wechat/pull/25) ([TheNorthMemory](https://github.com/TheNorthMemory)) +- fix \#4 for PHP5.3 [\#23](https://github.com/overtrue/wechat/pull/23) ([TheNorthMemory](https://github.com/TheNorthMemory)) +- Update QRCode.php [\#17](https://github.com/overtrue/wechat/pull/17) ([gundanx10](https://github.com/gundanx10)) + +## [2.0.1](https://github.com/overtrue/wechat/tree/2.0.1) (2015-05-08) +[Full Changelog](https://github.com/overtrue/wechat/compare/2.0.0...2.0.1) + +**Closed issues:** + +- 2.0版本使用问题 [\#14](https://github.com/overtrue/wechat/issues/14) + +## [2.0.0](https://github.com/overtrue/wechat/tree/2.0.0) (2015-05-07) +[Full Changelog](https://github.com/overtrue/wechat/compare/1.0.1...2.0.0) + +**Closed issues:** + +- 素材管理 -- 部分图片下载失败 [\#13](https://github.com/overtrue/wechat/issues/13) +- 素材管理 -- 图片下载失败 [\#12](https://github.com/overtrue/wechat/issues/12) +- 请问这样判断Mcrypt到底准不准? [\#11](https://github.com/overtrue/wechat/issues/11) +- 好奇怪啊,开发者中心的服务器配置已经提交并验证成功了,可是message不起作用 [\#10](https://github.com/overtrue/wechat/issues/10) +- 网页授权一刷新页面就出现40029 不合法的oauth\_code [\#8](https://github.com/overtrue/wechat/issues/8) +- mcrypt\_module\_open error [\#7](https://github.com/overtrue/wechat/issues/7) +- composer update 之后报错 [\#6](https://github.com/overtrue/wechat/issues/6) +- 今天开始,授权时候一直报40029,invalid code的错误 [\#5](https://github.com/overtrue/wechat/issues/5) +- Using $this when not in object context [\#4](https://github.com/overtrue/wechat/issues/4) +- 监听事件时不区分 $target(监听所有event和message) [\#3](https://github.com/overtrue/wechat/issues/3) +- Does this support Oauth already? [\#1](https://github.com/overtrue/wechat/issues/1) + +**Merged pull requests:** + +- Fix wiki url error [\#9](https://github.com/overtrue/wechat/pull/9) ([sinoon](https://github.com/sinoon)) +- Update Bag.php [\#2](https://github.com/overtrue/wechat/pull/2) ([zerozh](https://github.com/zerozh)) + +## [1.0.1](https://github.com/overtrue/wechat/tree/1.0.1) (2015-03-19) +[Full Changelog](https://github.com/overtrue/wechat/compare/1.0...1.0.1) + +## [1.0](https://github.com/overtrue/wechat/tree/1.0) (2015-03-13) + + +\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* \ No newline at end of file diff --git a/vendor/overtrue/wechat/CONTRIBUTING.md b/vendor/overtrue/wechat/CONTRIBUTING.md new file mode 100644 index 0000000..134204f --- /dev/null +++ b/vendor/overtrue/wechat/CONTRIBUTING.md @@ -0,0 +1,67 @@ +# Contribute + +## Introduction + +First, thank you for considering contributing to wechat! It's people like you that make the open source community such a great community! 😊 + +We welcome any type of contribution, not only code. You can help with +- **QA**: file bug reports, the more details you can give the better (e.g. screenshots with the console open) +- **Marketing**: writing blog posts, howto's, printing stickers, ... +- **Community**: presenting the project at meetups, organizing a dedicated meetup for the local community, ... +- **Code**: take a look at the [open issues](issues). Even if you can't write code, commenting on them, showing that you care about a given issue matters. It helps us triage them. +- **Money**: we welcome financial contributions in full transparency on our [open collective](https://opencollective.com/wechat). + +## Your First Contribution + +Working on your first Pull Request? You can learn how from this *free* series, [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github). + +## Submitting code + +Any code change should be submitted as a pull request. The description should explain what the code does and give steps to execute it. The pull request should also contain tests. + +## Code review process + +The bigger the pull request, the longer it will take to review and merge. Try to break down large pull requests in smaller chunks that are easier to review and merge. +It is also always helpful to have some context for your pull request. What was the purpose? Why does it matter to you? + +## Financial contributions + +We also welcome financial contributions in full transparency on our [open collective](https://opencollective.com/wechat). +Anyone can file an expense. If the expense makes sense for the development of the community, it will be "merged" in the ledger of our open collective by the core contributors and the person who filed the expense will be reimbursed. + +## Questions + +If you have any questions, create an [issue](issue) (protip: do a quick search first to see if someone else didn't ask the same question before!). +You can also reach us at hello@wechat.opencollective.com. + +## Credits + +### Contributors + +Thank you to all the people who have already contributed to wechat! + + + +### Backers + +Thank you to all our backers! [[Become a backer](https://opencollective.com/wechat#backer)] + + + + +### Sponsors + +Thank you to all our sponsors! (please ask your company to also support this open source project by [becoming a sponsor](https://opencollective.com/wechat#sponsor)) + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/overtrue/wechat/LICENSE b/vendor/overtrue/wechat/LICENSE new file mode 100644 index 0000000..f80ba02 --- /dev/null +++ b/vendor/overtrue/wechat/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) overtrue + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/overtrue/wechat/README.md b/vendor/overtrue/wechat/README.md new file mode 100644 index 0000000..655787a --- /dev/null +++ b/vendor/overtrue/wechat/README.md @@ -0,0 +1,99 @@ +

        EasyWeChat

        + +📦 一个 PHP 微信开发 SDK。 + +[![Test Status](https://github.com/w7corp/easywechat/workflows/Test/badge.svg)](https://github.com/w7corp/easywechat/actions) +[![Lint Status](https://github.com/w7corp/easywechat/workflows/Lint/badge.svg)](https://github.com/w7corp/easywechat/actions) +[![Latest Stable Version](https://poser.pugx.org/w7corp/easywechat/v/stable.svg)](https://packagist.org/packages/w7corp/easywechat) +[![Latest Unstable Version](https://poser.pugx.org/w7corp/easywechat/v/unstable.svg)](https://packagist.org/packages/w7corp/easywechat) +[![Total Downloads](https://poser.pugx.org/w7corp/easywechat/downloads)](https://packagist.org/packages/w7corp/easywechat) +[![License](https://poser.pugx.org/w7corp/easywechat/license)](https://packagist.org/packages/w7corp/easywechat) +[![huntr](https://cdn.huntr.dev/huntr_security_badge_mono.svg)](https://huntr.dev) + +> 📣 **公告** +> +> 为了更好的推进项目发展,保障项目更新迭代速度,EasyWeChat 正式并入微擎旗下,加上微擎团队的助力,将会为大家提供更强大更稳固更多元化的开源项目。 +> +> - 微擎与 EasyWeChat 结合,基于微擎技术资源方面的优势,将积极发展 EasyWeChat 的开源社区,将为 EasyWeChat 开源项目注入巨大活力。 +> - EasyWeChat 原作者 overtrue 将继续担任开源项目的核心开发者,继续参与项目的发展规划,共同打造更强大的开源生态社区。 +> - 项目从 6.0 版本开始将修改包名为 `w7corp/easywechat`,5.x 及以下版本不受影响。 + +> 🚨 注意:请 PR 时往 5.x 提交,感谢您的贡献! + + +## Requirement + +1. PHP >= 7.4 +2. **[Composer](https://getcomposer.org/)** +3. openssl 拓展 +4. fileinfo 拓展(素材管理模块需要用到) + +## Installation + +```shell +$ composer require "overtrue/wechat:^5.0" -vvv +``` + +## Usage + +基本使用(以服务端为例): + +```php + 'wx3cf0f39249eb0exxx', + 'secret' => 'f1c242f4f28f735d4687abb469072xxx', + 'token' => 'easywechat', + 'log' => [ + 'level' => 'debug', + 'file' => '/tmp/easywechat.log', + ], + // ... +]; + +$app = Factory::officialAccount($options); + +$server = $app->server; +$user = $app->user; + +$server->push(function($message) use ($user) { + $fromUser = $user->get($message['FromUserName']); + + return "{$fromUser->nickname} 您好!欢迎关注 overtrue!"; +}); + +$server->serve()->send(); +``` + +更多请参考 [https://www.easywechat.com/](https://www.easywechat.com/)。 + +## Documentation + +[官网](https://www.easywechat.com) · [教程](https://www.aliyundrive.com/s/6CwgtkiBqFV) · [讨论](https://github.com/w7corp/easywechat/discussions) · [微信公众平台](https://mp.weixin.qq.com/wiki) · [WeChat Official](http://admin.wechat.com/wiki) + +## Integration + +[Laravel 5 拓展包: overtrue/laravel-wechat](https://github.com/overtrue/laravel-wechat) + +## Contributors + +This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)]. + + + +## PHP 扩展包开发 + +> 想知道如何从零开始构建 PHP 扩展包? +> +> 请关注我的实战课程,我会在此课程中分享一些扩展开发经验 —— [《PHP 扩展包实战教程 - 从入门到发布》](https://learnku.com/courses/creating-package) + + +## License + +MIT + + +[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fovertrue%2Fwechat.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fovertrue%2Fwechat?ref=badge_large) diff --git a/vendor/overtrue/wechat/SECURITY.md b/vendor/overtrue/wechat/SECURITY.md new file mode 100644 index 0000000..c03d2cf --- /dev/null +++ b/vendor/overtrue/wechat/SECURITY.md @@ -0,0 +1,21 @@ +# 更新策略 + +从最初的稳定版本开始,EasyWeChat 的每个发行分支都会得到两年的全面支持。在这期间,被报告的错误和安全问题会被修复并发布。 +在这两年的积极支持期之后,每个分支再被支持一年,只针对关键安全问题。在此期间的发布是根据需要进行的:根据报告的数量,可能有多个版本发布,也可能没有。 + +## 当前支持版本情况 + +| 版本 | 发布日期 | 积极维护更新 | 安全性修复 | +| ------- | ---------------- | -------------------- | ------------------------- | +| 6.x | 2022.02.02 | 2024.02.02 | 2025.02.02 | +| 5.x | 2020.07.27 | 2022.07.27 | 2023.07.27 | +| 4.x | 2017.12.12 | 2019.12.12 | 2020.12.12 | +| 3.x | 2016.02.19 | 2018.02.19 | 2019.02.19 | +| 2.x | 2015.05.06 | 2017.05.06 | 2018.05.06 | +| 1.x | 2015.02.13 | 2017.02.13 | 2017.02.13 | + + +## 报告安全问题 + + +如果您发现 EasyWeChat 有安全漏洞,请发送电子邮件至 anzhengchao@gmail.com。所有的安全漏洞都会得到及时的解决。 diff --git a/vendor/overtrue/wechat/composer.json b/vendor/overtrue/wechat/composer.json new file mode 100644 index 0000000..edaf18c --- /dev/null +++ b/vendor/overtrue/wechat/composer.json @@ -0,0 +1,84 @@ +{ + "name": "overtrue/wechat", + "description": "微信SDK", + "keywords": [ + "easywechat", + "wechat", + "weixin", + "weixin-sdk", + "sdk" + ], + "license": "MIT", + "authors": [ + { + "name": "overtrue", + "email": "anzhengchao@gmail.com" + } + ], + "require": { + "php": ">=7.4", + "ext-fileinfo": "*", + "ext-openssl": "*", + "ext-simplexml": "*", + "easywechat-composer/easywechat-composer": "^1.1", + "guzzlehttp/guzzle": "^6.2 || ^7.0", + "monolog/monolog": "^1.22 || ^2.0", + "overtrue/socialite": "^3.2", + "pimple/pimple": "^3.0", + "psr/simple-cache": "^1.0", + "symfony/cache": "^3.3 || ^4.3 || ^5.0", + "symfony/event-dispatcher": "^4.3 || ^5.0", + "symfony/http-foundation": "^2.7 || ^3.0 || ^4.0 || ^5.0", + "symfony/psr-http-message-bridge": "^0.3 || ^1.0 || ^2.0", + "ext-libxml": "*" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.15", + "brainmaestro/composer-git-hooks": "^2.7", + "mikey179/vfsstream": "^1.6", + "mockery/mockery": "^1.2.3", + "phpstan/phpstan": "^0.12.0", + "phpunit/phpunit": "^9.3", + "dms/phpunit-arraysubset-asserts": "^0.2.0" + }, + "autoload": { + "psr-4": { + "EasyWeChat\\": "src/" + }, + "files": [ + "src/Kernel/Support/Helpers.php", + "src/Kernel/Helpers.php" + ] + }, + "autoload-dev": { + "psr-4": { + "EasyWeChat\\Tests\\": "tests/" + } + }, + "extra": { + "hooks": { + "pre-commit": [ + "composer test", + "composer fix-style" + ], + "pre-push": [ + "composer test", + "composer fix-style" + ] + } + }, + "scripts": { + "post-update-cmd": [ + "cghooks update" + ], + "post-merge": "composer install", + "post-install-cmd": [ + "cghooks add --ignore-lock", + "cghooks update" + ], + "phpstan": "vendor/bin/phpstan analyse", + "check-style": "vendor/bin/php-cs-fixer fix --using-cache=no --diff --config=.php_cs --dry-run --ansi", + "fix-style": "vendor/bin/php-cs-fixer fix --using-cache=no --config=.php_cs --ansi", + "test": "vendor/bin/phpunit --colors=always --testdox" + } +} diff --git a/vendor/overtrue/wechat/src/BasicService/Application.php b/vendor/overtrue/wechat/src/BasicService/Application.php new file mode 100644 index 0000000..e4b1e8a --- /dev/null +++ b/vendor/overtrue/wechat/src/BasicService/Application.php @@ -0,0 +1,39 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\BasicService; + +use EasyWeChat\Kernel\ServiceContainer; + +/** + * Class Application. + * + * @author overtrue + * + * @property \EasyWeChat\BasicService\Jssdk\Client $jssdk + * @property \EasyWeChat\BasicService\Media\Client $media + * @property \EasyWeChat\BasicService\QrCode\Client $qrcode + * @property \EasyWeChat\BasicService\Url\Client $url + * @property \EasyWeChat\BasicService\ContentSecurity\Client $content_security + */ +class Application extends ServiceContainer +{ + /** + * @var array + */ + protected $providers = [ + Jssdk\ServiceProvider::class, + QrCode\ServiceProvider::class, + Media\ServiceProvider::class, + Url\ServiceProvider::class, + ContentSecurity\ServiceProvider::class, + ]; +} diff --git a/vendor/overtrue/wechat/src/BasicService/ContentSecurity/Client.php b/vendor/overtrue/wechat/src/BasicService/ContentSecurity/Client.php new file mode 100644 index 0000000..5609a3c --- /dev/null +++ b/vendor/overtrue/wechat/src/BasicService/ContentSecurity/Client.php @@ -0,0 +1,118 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\BasicService\ContentSecurity; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; + +/** + * Class Client. + * + * @author tianyong90 <412039588@qq.com> + */ +class Client extends BaseClient +{ + /** + * Text content security check. + * + * @param string $text + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function checkText(string $text) + { + $params = [ + 'content' => $text, + ]; + + return $this->httpPostJson('/wxa/msg_sec_check', $params); + } + + /** + * Image security check. + * + * @param string $path + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function checkImage(string $path) + { + return $this->httpUpload('/wxa/img_sec_check', ['media' => $path]); + } + + /** + * Media security check. + * + * @param string $mediaUrl + * @param int $mediaType + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + public function checkMediaAsync(string $mediaUrl, int $mediaType) + { + /* + * 1:音频;2:图片 + */ + $mediaTypes = [1, 2]; + + if (!in_array($mediaType, $mediaTypes, true)) { + throw new InvalidArgumentException('media type must be 1 or 2'); + } + + $params = [ + 'media_url' => $mediaUrl, + 'media_type' => $mediaType, + ]; + + return $this->httpPostJson('/wxa/media_check_async', $params); + } + + /** + * Image security check async. + * + * @param string $mediaUrl + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function checkImageAsync(string $mediaUrl) + { + return $this->checkMediaAsync($mediaUrl, 2); + } + + /** + * Audio security check async. + * + * @param string $mediaUrl + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function checkAudioAsync(string $mediaUrl) + { + return $this->checkMediaAsync($mediaUrl, 1); + } +} diff --git a/vendor/overtrue/wechat/src/BasicService/ContentSecurity/ServiceProvider.php b/vendor/overtrue/wechat/src/BasicService/ContentSecurity/ServiceProvider.php new file mode 100644 index 0000000..3645de9 --- /dev/null +++ b/vendor/overtrue/wechat/src/BasicService/ContentSecurity/ServiceProvider.php @@ -0,0 +1,31 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\BasicService\ContentSecurity; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['content_security'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/BasicService/Jssdk/Client.php b/vendor/overtrue/wechat/src/BasicService/Jssdk/Client.php new file mode 100644 index 0000000..4c5439e --- /dev/null +++ b/vendor/overtrue/wechat/src/BasicService/Jssdk/Client.php @@ -0,0 +1,223 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\BasicService\Jssdk; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Exceptions\RuntimeException; +use EasyWeChat\Kernel\Support; +use EasyWeChat\Kernel\Traits\InteractsWithCache; + +/** + * Class Client. + * + * @author overtrue + */ +class Client extends BaseClient +{ + use InteractsWithCache; + + /** + * @var string + */ + protected $ticketEndpoint = '/cgi-bin/ticket/getticket'; + + /** + * Current URI. + * + * @var string + */ + protected $url; + + /** + * Get config json for jsapi. + * + * @param array $jsApiList + * @param bool $debug + * @param bool $beta + * @param bool $json + * @param array $openTagList + * @param string|null $url + * + * @return array|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \Psr\SimpleCache\InvalidArgumentException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function buildConfig(array $jsApiList, bool $debug = false, bool $beta = false, bool $json = true, array $openTagList = [], string $url = null) + { + $config = array_merge(compact('debug', 'beta', 'jsApiList', 'openTagList'), $this->configSignature($url)); + + return $json ? json_encode($config) : $config; + } + + /** + * Return jsapi config as a PHP array. + * + * @param array $apis + * @param bool $debug + * @param bool $beta + * @param array $openTagList + * @param string|null $url + * + * @return array|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \Psr\SimpleCache\InvalidArgumentException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getConfigArray(array $apis, bool $debug = false, bool $beta = false, array $openTagList = [], string $url = null) + { + return $this->buildConfig($apis, $debug, $beta, false, $openTagList, $url); + } + + /** + * Get js ticket. + * + * @param bool $refresh + * @param string $type + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \GuzzleHttp\Exception\GuzzleException + * @throws \Psr\SimpleCache\InvalidArgumentException + */ + public function getTicket(bool $refresh = false, string $type = 'jsapi'): array + { + $cacheKey = sprintf('easywechat.basic_service.jssdk.ticket.%s.%s', $type, $this->getAppId()); + + if (!$refresh && $this->getCache()->has($cacheKey)) { + return $this->getCache()->get($cacheKey); + } + + /** @var array $result */ + $result = $this->castResponseToType( + $this->requestRaw($this->ticketEndpoint, 'GET', ['query' => ['type' => $type]]), + 'array' + ); + + $this->getCache()->set($cacheKey, $result, $result['expires_in'] - 500); + + if (!$this->getCache()->has($cacheKey)) { + throw new RuntimeException('Failed to cache jssdk ticket.'); + } + + return $result; + } + + /** + * Build signature. + * + * @param string|null $url + * @param string|null $nonce + * @param null $timestamp + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \GuzzleHttp\Exception\GuzzleException + * @throws \Psr\SimpleCache\InvalidArgumentException + */ + protected function configSignature(string $url = null, string $nonce = null, $timestamp = null): array + { + $url = $url ?: $this->getUrl(); + $nonce = $nonce ?: Support\Str::quickRandom(10); + $timestamp = $timestamp ?: time(); + + return [ + 'appId' => $this->getAppId(), + 'nonceStr' => $nonce, + 'timestamp' => $timestamp, + 'url' => $url, + 'signature' => $this->getTicketSignature($this->getTicket()['ticket'], $nonce, $timestamp, $url), + ]; + } + + /** + * Sign the params. + * + * @param string $ticket + * @param string $nonce + * @param int $timestamp + * @param string $url + * + * @return string + */ + public function getTicketSignature($ticket, $nonce, $timestamp, $url): string + { + return sha1(sprintf('jsapi_ticket=%s&noncestr=%s×tamp=%s&url=%s', $ticket, $nonce, $timestamp, $url)); + } + + /** + * @return string + */ + public function dictionaryOrderSignature() + { + $params = func_get_args(); + + sort($params, SORT_STRING); + + return sha1(implode('', $params)); + } + + /** + * Set current url. + * + * @param string $url + * + * @return $this + */ + public function setUrl(string $url) + { + $this->url = $url; + + return $this; + } + + /** + * Get current url. + * + * @return string + */ + public function getUrl(): string + { + if ($this->url) { + return $this->url; + } + + return Support\current_url(); + } + + /** + * @return string + */ + protected function getAppId() + { + return $this->app['config']->get('app_id'); + } + + /** + * @return string + */ + protected function getAgentId() + { + return $this->app['config']->get('agent_id'); + } +} diff --git a/vendor/overtrue/wechat/src/BasicService/Jssdk/ServiceProvider.php b/vendor/overtrue/wechat/src/BasicService/Jssdk/ServiceProvider.php new file mode 100644 index 0000000..5581a1e --- /dev/null +++ b/vendor/overtrue/wechat/src/BasicService/Jssdk/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\BasicService\Jssdk; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['jssdk'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/BasicService/Media/Client.php b/vendor/overtrue/wechat/src/BasicService/Media/Client.php new file mode 100644 index 0000000..cd300db --- /dev/null +++ b/vendor/overtrue/wechat/src/BasicService/Media/Client.php @@ -0,0 +1,207 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\BasicService\Media; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; +use EasyWeChat\Kernel\Http\StreamResponse; + +/** + * Class Client. + * + * @author overtrue + */ +class Client extends BaseClient +{ + /** + * Allow media type. + * + * @var array + */ + protected $allowTypes = ['image', 'voice', 'video', 'thumb']; + + /** + * Upload image. + * + * @param string $path + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function uploadImage($path) + { + return $this->upload('image', $path); + } + + /** + * Upload video. + * + * @param string $path + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function uploadVideo($path) + { + return $this->upload('video', $path); + } + + /** + * @param string $path + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function uploadVoice($path) + { + return $this->upload('voice', $path); + } + + /** + * @param string $path + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function uploadThumb($path) + { + return $this->upload('thumb', $path); + } + + /** + * Upload temporary material. + * + * @param string $type + * @param string $path + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function upload(string $type, string $path) + { + if (!file_exists($path) || !is_readable($path)) { + throw new InvalidArgumentException(sprintf("File does not exist, or the file is unreadable: '%s'", $path)); + } + + if (!in_array($type, $this->allowTypes, true)) { + throw new InvalidArgumentException(sprintf("Unsupported media type: '%s'", $type)); + } + + return $this->httpUpload('/cgi-bin/media/upload', ['media' => $path], ['type' => $type]); + } + + /** + * @param string $path + * @param string $title + * @param string $description + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function uploadVideoForBroadcasting(string $path, string $title, string $description) + { + $response = $this->uploadVideo($path); + /** @var array $arrayResponse */ + $arrayResponse = $this->detectAndCastResponseToType($response, 'array'); + + if (!empty($arrayResponse['media_id'])) { + return $this->createVideoForBroadcasting($arrayResponse['media_id'], $title, $description); + } + + return $response; + } + + /** + * @param string $mediaId + * @param string $title + * @param string $description + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function createVideoForBroadcasting(string $mediaId, string $title, string $description) + { + return $this->httpPostJson('/cgi-bin/media/uploadvideo', [ + 'media_id' => $mediaId, + 'title' => $title, + 'description' => $description, + ]); + } + + /** + * Fetch item from WeChat server. + * + * @param string $mediaId + * + * @return \EasyWeChat\Kernel\Http\StreamResponse|\Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get(string $mediaId) + { + $response = $this->requestRaw('/cgi-bin/media/get', 'GET', [ + 'query' => [ + 'media_id' => $mediaId, + ], + ]); + + if (false !== stripos($response->getHeaderLine('Content-disposition'), 'attachment')) { + return StreamResponse::buildFromPsrResponse($response); + } + + return $this->castResponseToType($response, $this->app['config']->get('response_type')); + } + + /** + * @param string $mediaId + * + * @return array|\EasyWeChat\Kernel\Http\Response|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getJssdkMedia(string $mediaId) + { + $response = $this->requestRaw('/cgi-bin/media/get/jssdk', 'GET', [ + 'query' => [ + 'media_id' => $mediaId, + ], + ]); + + if (false !== stripos($response->getHeaderLine('Content-disposition'), 'attachment')) { + return StreamResponse::buildFromPsrResponse($response); + } + + return $this->castResponseToType($response, $this->app['config']->get('response_type')); + } +} diff --git a/vendor/overtrue/wechat/src/BasicService/Media/ServiceProvider.php b/vendor/overtrue/wechat/src/BasicService/Media/ServiceProvider.php new file mode 100644 index 0000000..45de142 --- /dev/null +++ b/vendor/overtrue/wechat/src/BasicService/Media/ServiceProvider.php @@ -0,0 +1,44 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * ServiceProvider.php. + * + * This file is part of the wechat. + * + * (c) overtrue + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\BasicService\Media; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['media'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/BasicService/QrCode/Client.php b/vendor/overtrue/wechat/src/BasicService/QrCode/Client.php new file mode 100644 index 0000000..8333979 --- /dev/null +++ b/vendor/overtrue/wechat/src/BasicService/QrCode/Client.php @@ -0,0 +1,115 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\BasicService\QrCode; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author overtrue + */ +class Client extends BaseClient +{ + public const DAY = 86400; + public const SCENE_MAX_VALUE = 100000; + public const SCENE_QR_CARD = 'QR_CARD'; + public const SCENE_QR_TEMPORARY = 'QR_SCENE'; + public const SCENE_QR_TEMPORARY_STR = 'QR_STR_SCENE'; + public const SCENE_QR_FOREVER = 'QR_LIMIT_SCENE'; + public const SCENE_QR_FOREVER_STR = 'QR_LIMIT_STR_SCENE'; + + /** + * Create forever QR code. + * + * @param string|int $sceneValue + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + */ + public function forever($sceneValue) + { + if (is_int($sceneValue) && $sceneValue > 0 && $sceneValue < self::SCENE_MAX_VALUE) { + $type = self::SCENE_QR_FOREVER; + $sceneKey = 'scene_id'; + } else { + $type = self::SCENE_QR_FOREVER_STR; + $sceneKey = 'scene_str'; + } + $scene = [$sceneKey => $sceneValue]; + + return $this->create($type, $scene, false); + } + + /** + * Create temporary QR code. + * + * @param string|int $sceneValue + * @param int|null $expireSeconds + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + */ + public function temporary($sceneValue, $expireSeconds = null) + { + if (is_int($sceneValue) && $sceneValue > 0) { + $type = self::SCENE_QR_TEMPORARY; + $sceneKey = 'scene_id'; + } else { + $type = self::SCENE_QR_TEMPORARY_STR; + $sceneKey = 'scene_str'; + } + $scene = [$sceneKey => $sceneValue]; + + return $this->create($type, $scene, true, $expireSeconds); + } + + /** + * Return url for ticket. + * Detail: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1443433542 . + * + * @param string $ticket + * + * @return string + */ + public function url($ticket) + { + return sprintf('https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=%s', urlencode($ticket)); + } + + /** + * Create a QrCode. + * + * @param string $actionName + * @param array $actionInfo + * @param bool $temporary + * @param int $expireSeconds + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function create($actionName, $actionInfo, $temporary = true, $expireSeconds = null) + { + null !== $expireSeconds || $expireSeconds = 7 * self::DAY; + + $params = [ + 'action_name' => $actionName, + 'action_info' => ['scene' => $actionInfo], + ]; + + if ($temporary) { + $params['expire_seconds'] = min($expireSeconds, 30 * self::DAY); + } + + return $this->httpPostJson('/cgi-bin/qrcode/create', $params); + } +} diff --git a/vendor/overtrue/wechat/src/BasicService/QrCode/ServiceProvider.php b/vendor/overtrue/wechat/src/BasicService/QrCode/ServiceProvider.php new file mode 100644 index 0000000..1638c66 --- /dev/null +++ b/vendor/overtrue/wechat/src/BasicService/QrCode/ServiceProvider.php @@ -0,0 +1,31 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\BasicService\QrCode; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['qrcode'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/BasicService/Url/Client.php b/vendor/overtrue/wechat/src/BasicService/Url/Client.php new file mode 100644 index 0000000..0363b52 --- /dev/null +++ b/vendor/overtrue/wechat/src/BasicService/Url/Client.php @@ -0,0 +1,42 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\BasicService\Url; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author overtrue + */ +class Client extends BaseClient +{ + /** + * Shorten the url. + * + * @param string $url + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function shorten(string $url) + { + $params = [ + 'action' => 'long2short', + 'long_url' => $url, + ]; + + return $this->httpPostJson('cgi-bin/shorturl', $params); + } +} diff --git a/vendor/overtrue/wechat/src/BasicService/Url/ServiceProvider.php b/vendor/overtrue/wechat/src/BasicService/Url/ServiceProvider.php new file mode 100644 index 0000000..6740bb8 --- /dev/null +++ b/vendor/overtrue/wechat/src/BasicService/Url/ServiceProvider.php @@ -0,0 +1,31 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\BasicService\Url; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['url'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Factory.php b/vendor/overtrue/wechat/src/Factory.php new file mode 100644 index 0000000..295b4ff --- /dev/null +++ b/vendor/overtrue/wechat/src/Factory.php @@ -0,0 +1,54 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat; + +/** + * Class Factory. + * + * @method static \EasyWeChat\Payment\Application payment(array $config) + * @method static \EasyWeChat\MiniProgram\Application miniProgram(array $config) + * @method static \EasyWeChat\OpenPlatform\Application openPlatform(array $config) + * @method static \EasyWeChat\OfficialAccount\Application officialAccount(array $config) + * @method static \EasyWeChat\BasicService\Application basicService(array $config) + * @method static \EasyWeChat\Work\Application work(array $config) + * @method static \EasyWeChat\OpenWork\Application openWork(array $config) + * @method static \EasyWeChat\MicroMerchant\Application microMerchant(array $config) + */ +class Factory +{ + /** + * @param string $name + * @param array $config + * + * @return \EasyWeChat\Kernel\ServiceContainer + */ + public static function make($name, array $config) + { + $namespace = Kernel\Support\Str::studly($name); + $application = "\\EasyWeChat\\{$namespace}\\Application"; + + return new $application($config); + } + + /** + * Dynamically pass methods to the application. + * + * @param string $name + * @param array $arguments + * + * @return mixed + */ + public static function __callStatic($name, $arguments) + { + return self::make($name, ...$arguments); + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/AccessToken.php b/vendor/overtrue/wechat/src/Kernel/AccessToken.php new file mode 100644 index 0000000..1a846e8 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/AccessToken.php @@ -0,0 +1,277 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel; + +use EasyWeChat\Kernel\Contracts\AccessTokenInterface; +use EasyWeChat\Kernel\Exceptions\HttpException; +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; +use EasyWeChat\Kernel\Exceptions\RuntimeException; +use EasyWeChat\Kernel\Traits\HasHttpRequests; +use EasyWeChat\Kernel\Traits\InteractsWithCache; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; + +/** + * Class AccessToken. + * + * @author overtrue + */ +abstract class AccessToken implements AccessTokenInterface +{ + use HasHttpRequests; + use InteractsWithCache; + + /** + * @var \EasyWeChat\Kernel\ServiceContainer + */ + protected $app; + + /** + * @var string + */ + protected $requestMethod = 'GET'; + + /** + * @var string + */ + protected $endpointToGetToken; + + /** + * @var string + */ + protected $queryName; + + /** + * @var array + */ + protected $token; + + /** + * @var string + */ + protected $tokenKey = 'access_token'; + + /** + * @var string + */ + protected $cachePrefix = 'easywechat.kernel.access_token.'; + + /** + * AccessToken constructor. + * + * @param \EasyWeChat\Kernel\ServiceContainer $app + */ + public function __construct(ServiceContainer $app) + { + $this->app = $app; + } + + /** + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\HttpException + * @throws \Psr\SimpleCache\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public function getRefreshedToken(): array + { + return $this->getToken(true); + } + + /** + * @param bool $refresh + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\HttpException + * @throws \Psr\SimpleCache\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public function getToken(bool $refresh = false): array + { + $cacheKey = $this->getCacheKey(); + $cache = $this->getCache(); + + if (!$refresh && $cache->has($cacheKey) && $result = $cache->get($cacheKey)) { + return $result; + } + + /** @var array $token */ + $token = $this->requestToken($this->getCredentials(), true); + + $this->setToken($token[$this->tokenKey], $token['expires_in'] ?? 7200); + + $this->app->events->dispatch(new Events\AccessTokenRefreshed($this)); + + return $token; + } + + /** + * @param string $token + * @param int $lifetime + * + * @return \EasyWeChat\Kernel\Contracts\AccessTokenInterface + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \Psr\SimpleCache\InvalidArgumentException + */ + public function setToken(string $token, int $lifetime = 7200): AccessTokenInterface + { + $this->getCache()->set($this->getCacheKey(), [ + $this->tokenKey => $token, + 'expires_in' => $lifetime, + ], $lifetime); + + if (!$this->getCache()->has($this->getCacheKey())) { + throw new RuntimeException('Failed to cache access token.'); + } + + return $this; + } + + /** + * @return \EasyWeChat\Kernel\Contracts\AccessTokenInterface + * + * @throws \EasyWeChat\Kernel\Exceptions\HttpException + * @throws \Psr\SimpleCache\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public function refresh(): AccessTokenInterface + { + $this->getToken(true); + + return $this; + } + + /** + * @param array $credentials + * @param bool $toArray + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\HttpException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + public function requestToken(array $credentials, $toArray = false) + { + $response = $this->sendRequest($credentials); + $result = json_decode($response->getBody()->getContents(), true); + $formatted = $this->castResponseToType($response, $this->app['config']->get('response_type')); + + if (empty($result[$this->tokenKey])) { + throw new HttpException('Request access_token fail: '.json_encode($result, JSON_UNESCAPED_UNICODE), $response, $formatted); + } + + return $toArray ? $result : $formatted; + } + + /** + * @param \Psr\Http\Message\RequestInterface $request + * @param array $requestOptions + * + * @return \Psr\Http\Message\RequestInterface + * + * @throws \EasyWeChat\Kernel\Exceptions\HttpException + * @throws \Psr\SimpleCache\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public function applyToRequest(RequestInterface $request, array $requestOptions = []): RequestInterface + { + parse_str($request->getUri()->getQuery(), $query); + + $query = http_build_query(array_merge($this->getQuery(), $query)); + + return $request->withUri($request->getUri()->withQuery($query)); + } + + /** + * Send http request. + * + * @param array $credentials + * + * @return ResponseInterface + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function sendRequest(array $credentials): ResponseInterface + { + $options = [ + ('GET' === $this->requestMethod) ? 'query' : 'json' => $credentials, + ]; + + return $this->setHttpClient($this->app['http_client'])->request($this->getEndpoint(), $this->requestMethod, $options); + } + + /** + * @return string + */ + protected function getCacheKey() + { + return $this->cachePrefix.md5(json_encode($this->getCredentials())); + } + + /** + * The request query will be used to add to the request. + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\HttpException + * @throws \Psr\SimpleCache\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + protected function getQuery(): array + { + return [$this->queryName ?? $this->tokenKey => $this->getToken()[$this->tokenKey]]; + } + + /** + * @return string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + public function getEndpoint(): string + { + if (empty($this->endpointToGetToken)) { + throw new InvalidArgumentException('No endpoint for access token request.'); + } + + return $this->endpointToGetToken; + } + + /** + * @return string + */ + public function getTokenKey() + { + return $this->tokenKey; + } + + /** + * Credential for get token. + * + * @return array + */ + abstract protected function getCredentials(): array; +} diff --git a/vendor/overtrue/wechat/src/Kernel/BaseClient.php b/vendor/overtrue/wechat/src/Kernel/BaseClient.php new file mode 100644 index 0000000..ed02f0d --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/BaseClient.php @@ -0,0 +1,286 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel; + +use EasyWeChat\Kernel\Contracts\AccessTokenInterface; +use EasyWeChat\Kernel\Http\Response; +use EasyWeChat\Kernel\Traits\HasHttpRequests; +use GuzzleHttp\MessageFormatter; +use GuzzleHttp\Middleware; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Log\LogLevel; + +/** + * Class BaseClient. + * + * @author overtrue + */ +class BaseClient +{ + use HasHttpRequests { + request as performRequest; + } + + /** + * @var \EasyWeChat\Kernel\ServiceContainer + */ + protected $app; + /** + * @var \EasyWeChat\Kernel\Contracts\AccessTokenInterface|null + */ + protected $accessToken = null; + /** + * @var string + */ + protected $baseUri; + + /** + * BaseClient constructor. + * + * @param \EasyWeChat\Kernel\ServiceContainer $app + * @param \EasyWeChat\Kernel\Contracts\AccessTokenInterface|null $accessToken + */ + public function __construct(ServiceContainer $app, AccessTokenInterface $accessToken = null) + { + $this->app = $app; + $this->accessToken = $accessToken ?? $this->app['access_token']; + } + + /** + * GET request. + * + * @param string $url + * @param array $query + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function httpGet(string $url, array $query = []) + { + return $this->request($url, 'GET', ['query' => $query]); + } + + /** + * POST request. + * + * @param string $url + * @param array $data + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function httpPost(string $url, array $data = []) + { + return $this->request($url, 'POST', ['form_params' => $data]); + } + + /** + * JSON request. + * + * @param string $url + * @param array $data + * @param array $query + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function httpPostJson(string $url, array $data = [], array $query = []) + { + return $this->request($url, 'POST', ['query' => $query, 'json' => $data]); + } + + /** + * Upload file. + * + * @param string $url + * @param array $files + * @param array $form + * @param array $query + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function httpUpload(string $url, array $files = [], array $form = [], array $query = []) + { + $multipart = []; + $headers = []; + + if (isset($form['filename'])) { + $headers = [ + 'Content-Disposition' => 'form-data; name="media"; filename="'.$form['filename'].'"' + ]; + } + + foreach ($files as $name => $path) { + $multipart[] = [ + 'name' => $name, + 'contents' => fopen($path, 'r'), + 'headers' => $headers + ]; + } + + foreach ($form as $name => $contents) { + $multipart[] = compact('name', 'contents'); + } + + return $this->request( + $url, + 'POST', + ['query' => $query, 'multipart' => $multipart, 'connect_timeout' => 30, 'timeout' => 30, 'read_timeout' => 30] + ); + } + + /** + * @return AccessTokenInterface + */ + public function getAccessToken(): AccessTokenInterface + { + return $this->accessToken; + } + + /** + * @param \EasyWeChat\Kernel\Contracts\AccessTokenInterface $accessToken + * + * @return $this + */ + public function setAccessToken(AccessTokenInterface $accessToken) + { + $this->accessToken = $accessToken; + + return $this; + } + + /** + * @param string $url + * @param string $method + * @param array $options + * @param bool $returnRaw + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function request(string $url, string $method = 'GET', array $options = [], $returnRaw = false) + { + if (empty($this->middlewares)) { + $this->registerHttpMiddlewares(); + } + + $response = $this->performRequest($url, $method, $options); + + $this->app->events->dispatch(new Events\HttpResponseCreated($response)); + + return $returnRaw ? $response : $this->castResponseToType($response, $this->app->config->get('response_type')); + } + + /** + * @param string $url + * @param string $method + * @param array $options + * + * @return \EasyWeChat\Kernel\Http\Response + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function requestRaw(string $url, string $method = 'GET', array $options = []) + { + return Response::buildFromPsrResponse($this->request($url, $method, $options, true)); + } + + /** + * Register Guzzle middlewares. + */ + protected function registerHttpMiddlewares() + { + // retry + $this->pushMiddleware($this->retryMiddleware(), 'retry'); + // access token + $this->pushMiddleware($this->accessTokenMiddleware(), 'access_token'); + // log + $this->pushMiddleware($this->logMiddleware(), 'log'); + } + + /** + * Attache access token to request query. + * + * @return \Closure + */ + protected function accessTokenMiddleware() + { + return function (callable $handler) { + return function (RequestInterface $request, array $options) use ($handler) { + if ($this->accessToken) { + $request = $this->accessToken->applyToRequest($request, $options); + } + + return $handler($request, $options); + }; + }; + } + + /** + * Log the request. + * + * @return \Closure + */ + protected function logMiddleware() + { + $formatter = new MessageFormatter($this->app['config']['http.log_template'] ?? MessageFormatter::DEBUG); + + return Middleware::log($this->app['logger'], $formatter, LogLevel::DEBUG); + } + + /** + * Return retry middleware. + * + * @return \Closure + */ + protected function retryMiddleware() + { + return Middleware::retry( + function ( + $retries, + RequestInterface $request, + ResponseInterface $response = null + ) { + // Limit the number of retries to 2 + if ($retries < $this->app->config->get('http.max_retries', 1) && $response && $body = $response->getBody()) { + // Retry on server errors + $response = json_decode($body, true); + + if (!empty($response['errcode']) && in_array(abs($response['errcode']), [40001, 40014, 42001], true)) { + $this->accessToken->refresh(); + $this->app['logger']->debug('Retrying with refreshed access token.'); + + return true; + } + } + + return false; + }, + function () { + return abs($this->app->config->get('http.retry_delay', 500)); + } + ); + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Clauses/Clause.php b/vendor/overtrue/wechat/src/Kernel/Clauses/Clause.php new file mode 100644 index 0000000..b798f32 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Clauses/Clause.php @@ -0,0 +1,75 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Clauses; + +/** + * Class Clause. + * + * @author mingyoung + */ +class Clause +{ + /** + * @var array + */ + protected $clauses = [ + 'where' => [], + ]; + + /** + * @param mixed ...$args + * + * @return $this + */ + public function where(...$args) + { + array_push($this->clauses['where'], $args); + + return $this; + } + + /** + * @param mixed $payload + * + * @return bool + */ + public function intercepted($payload) + { + return (bool) $this->interceptWhereClause($payload); + } + + /** + * @param mixed $payload + * + * @return bool + */ + protected function interceptWhereClause($payload) + { + foreach ($this->clauses['where'] as $item) { + list($key, $value) = $item; + + if (!isset($payload[$key])) { + continue; + } + + if (is_array($value) && !in_array($payload[$key], $value)) { + return true; + } + + if (!is_array($value) && $payload[$key] !== $value) { + return true; + } + } + + return false; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Config.php b/vendor/overtrue/wechat/src/Kernel/Config.php new file mode 100644 index 0000000..081f6fd --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Config.php @@ -0,0 +1,23 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel; + +use EasyWeChat\Kernel\Support\Collection; + +/** + * Class Config. + * + * @author overtrue + */ +class Config extends Collection +{ +} diff --git a/vendor/overtrue/wechat/src/Kernel/Contracts/AccessTokenInterface.php b/vendor/overtrue/wechat/src/Kernel/Contracts/AccessTokenInterface.php new file mode 100644 index 0000000..56f4a4d --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Contracts/AccessTokenInterface.php @@ -0,0 +1,40 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Contracts; + +use Psr\Http\Message\RequestInterface; + +/** + * Interface AuthorizerAccessToken. + * + * @author overtrue + */ +interface AccessTokenInterface +{ + /** + * @return array + */ + public function getToken(): array; + + /** + * @return \EasyWeChat\Kernel\Contracts\AccessTokenInterface + */ + public function refresh(): self; + + /** + * @param \Psr\Http\Message\RequestInterface $request + * @param array $requestOptions + * + * @return \Psr\Http\Message\RequestInterface + */ + public function applyToRequest(RequestInterface $request, array $requestOptions = []): RequestInterface; +} diff --git a/vendor/overtrue/wechat/src/Kernel/Contracts/Arrayable.php b/vendor/overtrue/wechat/src/Kernel/Contracts/Arrayable.php new file mode 100644 index 0000000..d947f8f --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Contracts/Arrayable.php @@ -0,0 +1,29 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Contracts; + +use ArrayAccess; + +/** + * Interface Arrayable. + * + * @author overtrue + */ +interface Arrayable extends ArrayAccess +{ + /** + * Get the instance as an array. + * + * @return array + */ + public function toArray(); +} diff --git a/vendor/overtrue/wechat/src/Kernel/Contracts/EventHandlerInterface.php b/vendor/overtrue/wechat/src/Kernel/Contracts/EventHandlerInterface.php new file mode 100644 index 0000000..c9d116c --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Contracts/EventHandlerInterface.php @@ -0,0 +1,25 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Contracts; + +/** + * Interface EventHandlerInterface. + * + * @author mingyoung + */ +interface EventHandlerInterface +{ + /** + * @param mixed $payload + */ + public function handle($payload = null); +} diff --git a/vendor/overtrue/wechat/src/Kernel/Contracts/MediaInterface.php b/vendor/overtrue/wechat/src/Kernel/Contracts/MediaInterface.php new file mode 100644 index 0000000..63768cf --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Contracts/MediaInterface.php @@ -0,0 +1,25 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Contracts; + +/** + * Interface MediaInterface. + * + * @author overtrue + */ +interface MediaInterface extends MessageInterface +{ + /** + * @return string + */ + public function getMediaId(): string; +} diff --git a/vendor/overtrue/wechat/src/Kernel/Contracts/MessageInterface.php b/vendor/overtrue/wechat/src/Kernel/Contracts/MessageInterface.php new file mode 100644 index 0000000..29ddb57 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Contracts/MessageInterface.php @@ -0,0 +1,35 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Contracts; + +/** + * Interface MessageInterface. + * + * @author overtrue + */ +interface MessageInterface +{ + /** + * @return string + */ + public function getType(): string; + + /** + * @return array + */ + public function transformForJsonRequest(): array; + + /** + * @return string + */ + public function transformToXml(): string; +} diff --git a/vendor/overtrue/wechat/src/Kernel/Decorators/FinallyResult.php b/vendor/overtrue/wechat/src/Kernel/Decorators/FinallyResult.php new file mode 100644 index 0000000..e698bbf --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Decorators/FinallyResult.php @@ -0,0 +1,35 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Decorators; + +/** + * Class FinallyResult. + * + * @author overtrue + */ +class FinallyResult +{ + /** + * @var mixed + */ + public $content; + + /** + * FinallyResult constructor. + * + * @param mixed $content + */ + public function __construct($content) + { + $this->content = $content; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Decorators/TerminateResult.php b/vendor/overtrue/wechat/src/Kernel/Decorators/TerminateResult.php new file mode 100644 index 0000000..cf1042d --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Decorators/TerminateResult.php @@ -0,0 +1,35 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Decorators; + +/** + * Class TerminateResult. + * + * @author overtrue + */ +class TerminateResult +{ + /** + * @var mixed + */ + public $content; + + /** + * FinallyResult constructor. + * + * @param mixed $content + */ + public function __construct($content) + { + $this->content = $content; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Encryptor.php b/vendor/overtrue/wechat/src/Kernel/Encryptor.php new file mode 100644 index 0000000..a154809 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Encryptor.php @@ -0,0 +1,219 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel; + +use EasyWeChat\Kernel\Exceptions\RuntimeException; +use EasyWeChat\Kernel\Support\AES; +use EasyWeChat\Kernel\Support\XML; +use Throwable; +use function EasyWeChat\Kernel\Support\str_random; + +/** + * Class Encryptor. + * + * @author overtrue + */ +class Encryptor +{ + public const ERROR_INVALID_SIGNATURE = -40001; // Signature verification failed + public const ERROR_PARSE_XML = -40002; // Parse XML failed + public const ERROR_CALC_SIGNATURE = -40003; // Calculating the signature failed + public const ERROR_INVALID_AES_KEY = -40004; // Invalid AESKey + public const ERROR_INVALID_APP_ID = -40005; // Check AppID failed + public const ERROR_ENCRYPT_AES = -40006; // AES EncryptionInterface failed + public const ERROR_DECRYPT_AES = -40007; // AES decryption failed + public const ERROR_INVALID_XML = -40008; // Invalid XML + public const ERROR_BASE64_ENCODE = -40009; // Base64 encoding failed + public const ERROR_BASE64_DECODE = -40010; // Base64 decoding failed + public const ERROR_XML_BUILD = -40011; // XML build failed + public const ILLEGAL_BUFFER = -41003; // Illegal buffer + + /** + * App id. + * + * @var string + */ + protected $appId; + + /** + * App token. + * + * @var string + */ + protected $token; + + /** + * @var string + */ + protected $aesKey; + + /** + * Block size. + * + * @var int + */ + protected $blockSize = 32; + + /** + * Constructor. + * + * @param string $appId + * @param string|null $token + * @param string|null $aesKey + */ + public function __construct(string $appId, string $token = null, string $aesKey = null) + { + $this->appId = $appId; + $this->token = $token; + $this->aesKey = base64_decode($aesKey.'=', true); + } + + /** + * Get the app token. + * + * @return string + */ + public function getToken(): string + { + return $this->token; + } + + /** + * Encrypt the message and return XML. + * + * @param string $xml + * @param string $nonce + * @param int $timestamp + * + * @return string + * + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public function encrypt($xml, $nonce = null, $timestamp = null): string + { + try { + $xml = $this->pkcs7Pad(str_random(16).pack('N', strlen($xml)).$xml.$this->appId, $this->blockSize); + + $encrypted = base64_encode(AES::encrypt( + $xml, + $this->aesKey, + substr($this->aesKey, 0, 16), + OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING + )); + // @codeCoverageIgnoreStart + } catch (Throwable $e) { + throw new RuntimeException($e->getMessage(), self::ERROR_ENCRYPT_AES); + } + // @codeCoverageIgnoreEnd + + !is_null($nonce) || $nonce = substr($this->appId, 0, 10); + !is_null($timestamp) || $timestamp = time(); + + $response = [ + 'Encrypt' => $encrypted, + 'MsgSignature' => $this->signature($this->token, $timestamp, $nonce, $encrypted), + 'TimeStamp' => $timestamp, + 'Nonce' => $nonce, + ]; + + //生成响应xml + return XML::build($response); + } + + /** + * Decrypt message. + * + * @param string $content + * @param string $msgSignature + * @param string $nonce + * @param string $timestamp + * + * @return string + * + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public function decrypt($content, $msgSignature, $nonce, $timestamp): string + { + $signature = $this->signature($this->token, $timestamp, $nonce, $content); + + if ($signature !== $msgSignature) { + throw new RuntimeException('Invalid Signature.', self::ERROR_INVALID_SIGNATURE); + } + + $decrypted = AES::decrypt( + base64_decode($content, true), + $this->aesKey, + substr($this->aesKey, 0, 16), + OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING + ); + $result = $this->pkcs7Unpad($decrypted); + $content = substr($result, 16, strlen($result)); + $contentLen = unpack('N', substr($content, 0, 4))[1]; + + if (trim(substr($content, $contentLen + 4)) !== $this->appId) { + throw new RuntimeException('Invalid appId.', self::ERROR_INVALID_APP_ID); + } + + return substr($content, 4, $contentLen); + } + + /** + * Get SHA1. + * + * @return string + */ + public function signature(): string + { + $array = func_get_args(); + sort($array, SORT_STRING); + + return sha1(implode($array)); + } + + /** + * PKCS#7 pad. + * + * @param string $text + * @param int $blockSize + * + * @return string + * + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public function pkcs7Pad(string $text, int $blockSize): string + { + if ($blockSize > 256) { + throw new RuntimeException('$blockSize may not be more than 256'); + } + $padding = $blockSize - (strlen($text) % $blockSize); + $pattern = chr($padding); + + return $text.str_repeat($pattern, $padding); + } + + /** + * PKCS#7 unpad. + * + * @param string $text + * + * @return string + */ + public function pkcs7Unpad(string $text): string + { + $pad = ord(substr($text, -1)); + if ($pad < 1 || $pad > $this->blockSize) { + $pad = 0; + } + + return substr($text, 0, (strlen($text) - $pad)); + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Events/AccessTokenRefreshed.php b/vendor/overtrue/wechat/src/Kernel/Events/AccessTokenRefreshed.php new file mode 100644 index 0000000..a829901 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Events/AccessTokenRefreshed.php @@ -0,0 +1,35 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Events; + +use EasyWeChat\Kernel\AccessToken; + +/** + * Class AccessTokenRefreshed. + * + * @author mingyoung + */ +class AccessTokenRefreshed +{ + /** + * @var \EasyWeChat\Kernel\AccessToken + */ + public $accessToken; + + /** + * @param \EasyWeChat\Kernel\AccessToken $accessToken + */ + public function __construct(AccessToken $accessToken) + { + $this->accessToken = $accessToken; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Events/ApplicationInitialized.php b/vendor/overtrue/wechat/src/Kernel/Events/ApplicationInitialized.php new file mode 100644 index 0000000..89d67fe --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Events/ApplicationInitialized.php @@ -0,0 +1,35 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Events; + +use EasyWeChat\Kernel\ServiceContainer; + +/** + * Class ApplicationInitialized. + * + * @author mingyoung + */ +class ApplicationInitialized +{ + /** + * @var \EasyWeChat\Kernel\ServiceContainer + */ + public $app; + + /** + * @param \EasyWeChat\Kernel\ServiceContainer $app + */ + public function __construct(ServiceContainer $app) + { + $this->app = $app; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Events/HttpResponseCreated.php b/vendor/overtrue/wechat/src/Kernel/Events/HttpResponseCreated.php new file mode 100644 index 0000000..2257260 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Events/HttpResponseCreated.php @@ -0,0 +1,35 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Events; + +use Psr\Http\Message\ResponseInterface; + +/** + * Class HttpResponseCreated. + * + * @author mingyoung + */ +class HttpResponseCreated +{ + /** + * @var \Psr\Http\Message\ResponseInterface + */ + public $response; + + /** + * @param \Psr\Http\Message\ResponseInterface $response + */ + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Events/ServerGuardResponseCreated.php b/vendor/overtrue/wechat/src/Kernel/Events/ServerGuardResponseCreated.php new file mode 100644 index 0000000..3ab9925 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Events/ServerGuardResponseCreated.php @@ -0,0 +1,35 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Events; + +use Symfony\Component\HttpFoundation\Response; + +/** + * Class ServerGuardResponseCreated. + * + * @author mingyoung + */ +class ServerGuardResponseCreated +{ + /** + * @var \Symfony\Component\HttpFoundation\Response + */ + public $response; + + /** + * @param \Symfony\Component\HttpFoundation\Response $response + */ + public function __construct(Response $response) + { + $this->response = $response; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Exceptions/BadRequestException.php b/vendor/overtrue/wechat/src/Kernel/Exceptions/BadRequestException.php new file mode 100644 index 0000000..a0b4f91 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Exceptions/BadRequestException.php @@ -0,0 +1,21 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Exceptions; + +/** + * Class BadRequestException. + * + * @author overtrue + */ +class BadRequestException extends Exception +{ +} diff --git a/vendor/overtrue/wechat/src/Kernel/Exceptions/DecryptException.php b/vendor/overtrue/wechat/src/Kernel/Exceptions/DecryptException.php new file mode 100644 index 0000000..f29acca --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Exceptions/DecryptException.php @@ -0,0 +1,16 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Exceptions; + +class DecryptException extends Exception +{ +} diff --git a/vendor/overtrue/wechat/src/Kernel/Exceptions/Exception.php b/vendor/overtrue/wechat/src/Kernel/Exceptions/Exception.php new file mode 100644 index 0000000..9eba298 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Exceptions/Exception.php @@ -0,0 +1,23 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Exceptions; + +use Exception as BaseException; + +/** + * Class Exception. + * + * @author overtrue + */ +class Exception extends BaseException +{ +} diff --git a/vendor/overtrue/wechat/src/Kernel/Exceptions/HttpException.php b/vendor/overtrue/wechat/src/Kernel/Exceptions/HttpException.php new file mode 100644 index 0000000..8ab130d --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Exceptions/HttpException.php @@ -0,0 +1,52 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Exceptions; + +use Psr\Http\Message\ResponseInterface; + +/** + * Class HttpException. + * + * @author overtrue + */ +class HttpException extends Exception +{ + /** + * @var \Psr\Http\Message\ResponseInterface|null + */ + public $response; + + /** + * @var \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string|null + */ + public $formattedResponse; + + /** + * HttpException constructor. + * + * @param string $message + * @param \Psr\Http\Message\ResponseInterface|null $response + * @param null $formattedResponse + * @param int|null $code + */ + public function __construct($message, ResponseInterface $response = null, $formattedResponse = null, $code = null) + { + parent::__construct($message, $code); + + $this->response = $response; + $this->formattedResponse = $formattedResponse; + + if ($response) { + $response->getBody()->rewind(); + } + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Exceptions/InvalidArgumentException.php b/vendor/overtrue/wechat/src/Kernel/Exceptions/InvalidArgumentException.php new file mode 100644 index 0000000..386a144 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Exceptions/InvalidArgumentException.php @@ -0,0 +1,21 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Exceptions; + +/** + * Class InvalidArgumentException. + * + * @author overtrue + */ +class InvalidArgumentException extends Exception +{ +} diff --git a/vendor/overtrue/wechat/src/Kernel/Exceptions/InvalidConfigException.php b/vendor/overtrue/wechat/src/Kernel/Exceptions/InvalidConfigException.php new file mode 100644 index 0000000..1e4ae2a --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Exceptions/InvalidConfigException.php @@ -0,0 +1,21 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Exceptions; + +/** + * Class InvalidConfigException. + * + * @author overtrue + */ +class InvalidConfigException extends Exception +{ +} diff --git a/vendor/overtrue/wechat/src/Kernel/Exceptions/RuntimeException.php b/vendor/overtrue/wechat/src/Kernel/Exceptions/RuntimeException.php new file mode 100644 index 0000000..25f2f5b --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Exceptions/RuntimeException.php @@ -0,0 +1,21 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Exceptions; + +/** + * Class RuntimeException. + * + * @author overtrue + */ +class RuntimeException extends Exception +{ +} diff --git a/vendor/overtrue/wechat/src/Kernel/Exceptions/UnboundServiceException.php b/vendor/overtrue/wechat/src/Kernel/Exceptions/UnboundServiceException.php new file mode 100644 index 0000000..78ea85c --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Exceptions/UnboundServiceException.php @@ -0,0 +1,21 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Exceptions; + +/** + * Class InvalidConfigException. + * + * @author overtrue + */ +class UnboundServiceException extends Exception +{ +} diff --git a/vendor/overtrue/wechat/src/Kernel/Helpers.php b/vendor/overtrue/wechat/src/Kernel/Helpers.php new file mode 100644 index 0000000..3ea48a4 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Helpers.php @@ -0,0 +1,57 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel; + +use EasyWeChat\Kernel\Contracts\Arrayable; +use EasyWeChat\Kernel\Exceptions\RuntimeException; +use EasyWeChat\Kernel\Support\Arr; +use EasyWeChat\Kernel\Support\Collection; + +function data_get($data, $key, $default = null) +{ + switch (true) { + case is_array($data): + return Arr::get($data, $key, $default); + case $data instanceof Collection: + return $data->get($key, $default); + case $data instanceof Arrayable: + return Arr::get($data->toArray(), $key, $default); + case $data instanceof \ArrayIterator: + return $data->getArrayCopy()[$key] ?? $default; + case $data instanceof \ArrayAccess: + return $data[$key] ?? $default; + case $data instanceof \IteratorAggregate && $data->getIterator() instanceof \ArrayIterator: + return $data->getIterator()->getArrayCopy()[$key] ?? $default; + case is_object($data): + return $data->{$key} ?? $default; + default: + throw new RuntimeException(sprintf('Can\'t access data with key "%s"', $key)); + } +} + +function data_to_array($data) +{ + switch (true) { + case is_array($data): + return $data; + case $data instanceof Collection: + return $data->all(); + case $data instanceof Arrayable: + return $data->toArray(); + case $data instanceof \IteratorAggregate && $data->getIterator() instanceof \ArrayIterator: + return $data->getIterator()->getArrayCopy(); + case $data instanceof \ArrayIterator: + return $data->getArrayCopy(); + default: + throw new RuntimeException(sprintf('Can\'t transform data to array')); + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Http/Response.php b/vendor/overtrue/wechat/src/Kernel/Http/Response.php new file mode 100644 index 0000000..adcd416 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Http/Response.php @@ -0,0 +1,121 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Http; + +use EasyWeChat\Kernel\Support\Collection; +use EasyWeChat\Kernel\Support\XML; +use GuzzleHttp\Psr7\Response as GuzzleResponse; +use Psr\Http\Message\ResponseInterface; + +/** + * Class Response. + * + * @author overtrue + */ +class Response extends GuzzleResponse +{ + /** + * @return string + */ + public function getBodyContents() + { + $this->getBody()->rewind(); + $contents = $this->getBody()->getContents(); + $this->getBody()->rewind(); + + return $contents; + } + + /** + * @param \Psr\Http\Message\ResponseInterface $response + * + * @return \EasyWeChat\Kernel\Http\Response + */ + public static function buildFromPsrResponse(ResponseInterface $response) + { + return new static( + $response->getStatusCode(), + $response->getHeaders(), + $response->getBody(), + $response->getProtocolVersion(), + $response->getReasonPhrase() + ); + } + + /** + * Build to json. + * + * @return string + */ + public function toJson() + { + return json_encode($this->toArray()); + } + + /** + * Build to array. + * + * @return array + */ + public function toArray() + { + $content = $this->removeControlCharacters($this->getBodyContents()); + + if (false !== stripos($this->getHeaderLine('Content-Type'), 'xml') || 0 === stripos($content, 'toArray()); + } + + /** + * @return object + */ + public function toObject() + { + return json_decode($this->toJson()); + } + + /** + * @return bool|string + */ + public function __toString() + { + return $this->getBodyContents(); + } + + /** + * @param string $content + * + * @return string + */ + protected function removeControlCharacters(string $content) + { + return \preg_replace('/[\x00-\x1F\x80-\x9F]/u', '', $content); + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Http/StreamResponse.php b/vendor/overtrue/wechat/src/Kernel/Http/StreamResponse.php new file mode 100644 index 0000000..104e8c3 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Http/StreamResponse.php @@ -0,0 +1,86 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Http; + +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; +use EasyWeChat\Kernel\Exceptions\RuntimeException; +use EasyWeChat\Kernel\Support\File; + +/** + * Class StreamResponse. + * + * @author overtrue + */ +class StreamResponse extends Response +{ + /** + * @param string $directory + * @param string $filename + * @param bool $appendSuffix + * + * @return bool|int + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public function save(string $directory, string $filename = '', bool $appendSuffix = true) + { + $this->getBody()->rewind(); + + $directory = rtrim($directory, '/'); + + if (!is_dir($directory)) { + mkdir($directory, 0755, true); // @codeCoverageIgnore + } + + if (!is_writable($directory)) { + throw new InvalidArgumentException(sprintf("'%s' is not writable.", $directory)); + } + + $contents = $this->getBody()->getContents(); + + if (empty($contents) || '{' === $contents[0]) { + throw new RuntimeException('Invalid media response content.'); + } + + if (empty($filename)) { + if (preg_match('/filename="(?.*?)"/', $this->getHeaderLine('Content-Disposition'), $match)) { + $filename = $match['filename']; + } else { + $filename = md5($contents); + } + } + + if ($appendSuffix && empty(pathinfo($filename, PATHINFO_EXTENSION))) { + $filename .= File::getStreamExt($contents); + } + + file_put_contents($directory.'/'.$filename, $contents); + + return $filename; + } + + /** + * @param string $directory + * @param string $filename + * @param bool $appendSuffix + * + * @return bool|int + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public function saveAs(string $directory, string $filename, bool $appendSuffix = true) + { + return $this->save($directory, $filename, $appendSuffix); + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Log/LogManager.php b/vendor/overtrue/wechat/src/Kernel/Log/LogManager.php new file mode 100644 index 0000000..870b46f --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Log/LogManager.php @@ -0,0 +1,594 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Log; + +use EasyWeChat\Kernel\ServiceContainer; +use InvalidArgumentException; +use Monolog\Formatter\LineFormatter; +use Monolog\Handler\ErrorLogHandler; +use Monolog\Handler\FormattableHandlerInterface; +use Monolog\Handler\HandlerInterface; +use Monolog\Handler\RotatingFileHandler; +use Monolog\Handler\SlackWebhookHandler; +use Monolog\Handler\StreamHandler; +use Monolog\Handler\SyslogHandler; +use Monolog\Handler\WhatFailureGroupHandler; +use Monolog\Logger as Monolog; +use Psr\Log\LoggerInterface; + +/** + * Class LogManager. + * + * @author overtrue + */ +class LogManager implements LoggerInterface +{ + /** + * @var \EasyWeChat\Kernel\ServiceContainer + */ + protected $app; + + /** + * The array of resolved channels. + * + * @var array + */ + protected $channels = []; + + /** + * The registered custom driver creators. + * + * @var array + */ + protected $customCreators = []; + + /** + * The Log levels. + * + * @var array + */ + protected $levels = [ + 'debug' => Monolog::DEBUG, + 'info' => Monolog::INFO, + 'notice' => Monolog::NOTICE, + 'warning' => Monolog::WARNING, + 'error' => Monolog::ERROR, + 'critical' => Monolog::CRITICAL, + 'alert' => Monolog::ALERT, + 'emergency' => Monolog::EMERGENCY, + ]; + + /** + * LogManager constructor. + * + * @param \EasyWeChat\Kernel\ServiceContainer $app + */ + public function __construct(ServiceContainer $app) + { + $this->app = $app; + } + + /** + * Create a new, on-demand aggregate logger instance. + * + * @param array $channels + * @param string|null $channel + * + * @return \Psr\Log\LoggerInterface + * + * @throws \Exception + */ + public function stack(array $channels, $channel = null) + { + return $this->createStackDriver(compact('channels', 'channel')); + } + + /** + * Get a log channel instance. + * + * @param string|null $channel + * + * @return mixed + * + * @throws \Exception + */ + public function channel($channel = null) + { + return $this->driver($channel); + } + + /** + * Get a log driver instance. + * + * @param string|null $driver + * + * @return mixed + * + * @throws \Exception + */ + public function driver($driver = null) + { + return $this->get($driver ?? $this->getDefaultDriver()); + } + + /** + * Attempt to get the log from the local cache. + * + * @param string $name + * + * @return \Psr\Log\LoggerInterface + * + * @throws \Exception + */ + protected function get($name) + { + try { + return $this->channels[$name] ?? ($this->channels[$name] = $this->resolve($name)); + } catch (\Throwable $e) { + $logger = $this->createEmergencyLogger(); + + $logger->emergency('Unable to create configured logger. Using emergency logger.', [ + 'exception' => $e, + ]); + + return $logger; + } + } + + /** + * Resolve the given log instance by name. + * + * @param string $name + * + * @return \Psr\Log\LoggerInterface + * + * @throws InvalidArgumentException + */ + protected function resolve($name) + { + $config = $this->app['config']->get(\sprintf('log.channels.%s', $name)); + + if (is_null($config)) { + throw new InvalidArgumentException(\sprintf('Log [%s] is not defined.', $name)); + } + + if (isset($this->customCreators[$config['driver']])) { + return $this->callCustomCreator($config); + } + + $driverMethod = 'create'.ucfirst($config['driver']).'Driver'; + + if (method_exists($this, $driverMethod)) { + return $this->{$driverMethod}($config); + } + + throw new InvalidArgumentException(\sprintf('Driver [%s] is not supported.', $config['driver'])); + } + + /** + * Create an emergency log handler to avoid white screens of death. + * + * @return \Monolog\Logger + * + * @throws \Exception + */ + protected function createEmergencyLogger() + { + return new Monolog('EasyWeChat', $this->prepareHandlers([new StreamHandler( + \sys_get_temp_dir().'/easywechat/easywechat.log', + $this->level(['level' => 'debug']) + )])); + } + + /** + * Call a custom driver creator. + * + * @param array $config + * + * @return mixed + */ + protected function callCustomCreator(array $config) + { + return $this->customCreators[$config['driver']]($this->app, $config); + } + + /** + * Create an aggregate log driver instance. + * + * @param array $config + * + * @return \Monolog\Logger + * + * @throws \Exception + */ + protected function createStackDriver(array $config) + { + $handlers = []; + + foreach ($config['channels'] ?? [] as $channel) { + $handlers = \array_merge($handlers, $this->channel($channel)->getHandlers()); + } + + if ($config['ignore_exceptions'] ?? false) { + $handlers = [new WhatFailureGroupHandler($handlers)]; + } + + return new Monolog($this->parseChannel($config), $handlers); + } + + /** + * Create an instance of the single file log driver. + * + * @param array $config + * + * @return \Psr\Log\LoggerInterface + * + * @throws \Exception + */ + protected function createSingleDriver(array $config) + { + return new Monolog($this->parseChannel($config), [ + $this->prepareHandler(new StreamHandler( + $config['path'], + $this->level($config), + $config['bubble'] ?? true, + $config['permission'] ?? null, + $config['locking'] ?? false + ), $config), + ]); + } + + /** + * Create an instance of the daily file log driver. + * + * @param array $config + * + * @return \Psr\Log\LoggerInterface + */ + protected function createDailyDriver(array $config) + { + return new Monolog($this->parseChannel($config), [ + $this->prepareHandler(new RotatingFileHandler( + $config['path'], + $config['days'] ?? 7, + $this->level($config), + $config['bubble'] ?? true, + $config['permission'] ?? null, + $config['locking'] ?? false + ), $config), + ]); + } + + /** + * Create an instance of the Slack log driver. + * + * @param array $config + * + * @return \Psr\Log\LoggerInterface + * @throws \Monolog\Handler\MissingExtensionException + */ + protected function createSlackDriver(array $config) + { + return new Monolog($this->parseChannel($config), [ + $this->prepareHandler(new SlackWebhookHandler( + $config['url'], + $config['channel'] ?? null, + $config['username'] ?? 'EasyWeChat', + $config['attachment'] ?? true, + $config['emoji'] ?? ':boom:', + $config['short'] ?? false, + $config['context'] ?? true, + $this->level($config), + $config['bubble'] ?? true, + $config['exclude_fields'] ?? [] + ), $config), + ]); + } + + /** + * Create an instance of the syslog log driver. + * + * @param array $config + * + * @return \Psr\Log\LoggerInterface + */ + protected function createSyslogDriver(array $config) + { + return new Monolog($this->parseChannel($config), [ + $this->prepareHandler(new SyslogHandler( + 'EasyWeChat', + $config['facility'] ?? LOG_USER, + $this->level($config) + ), $config), + ]); + } + + /** + * Create an instance of the "error log" log driver. + * + * @param array $config + * + * @return \Psr\Log\LoggerInterface + */ + protected function createErrorlogDriver(array $config) + { + return new Monolog($this->parseChannel($config), [ + $this->prepareHandler( + new ErrorLogHandler( + $config['type'] ?? ErrorLogHandler::OPERATING_SYSTEM, + $this->level($config) + ) + ), + ]); + } + + /** + * Prepare the handlers for usage by Monolog. + * + * @param array $handlers + * + * @return array + */ + protected function prepareHandlers(array $handlers): array + { + foreach ($handlers as $key => $handler) { + $handlers[$key] = $this->prepareHandler($handler); + } + + return $handlers; + } + + /** + * Prepare the handler for usage by Monolog. + * + * @param \Monolog\Handler\HandlerInterface $handler + * @param array $config + * + * @return \Monolog\Handler\HandlerInterface + */ + protected function prepareHandler(HandlerInterface $handler, array $config = []) + { + if (!isset($config['formatter'])) { + if ($handler instanceof FormattableHandlerInterface) { + $handler->setFormatter($this->formatter()); + } + } + + return $handler; + } + + /** + * Get a Monolog formatter instance. + * + * @return \Monolog\Formatter\FormatterInterface + */ + protected function formatter() + { + $formatter = new LineFormatter(null, null, true, true); + $formatter->includeStacktraces(); + + return $formatter; + } + + /** + * Extract the log channel from the given configuration. + * + * @param array $config + * + * @return string + */ + protected function parseChannel(array $config): string + { + return $config['name'] ?? 'EasyWeChat'; + } + + /** + * Parse the string level into a Monolog constant. + * + * @param array $config + * + * @return int + * + * @throws InvalidArgumentException + */ + protected function level(array $config): int + { + $level = $config['level'] ?? 'debug'; + + if (isset($this->levels[$level])) { + return $this->levels[$level]; + } + + throw new InvalidArgumentException('Invalid log level.'); + } + + /** + * Get the default log driver name. + * + * @return string + */ + public function getDefaultDriver(): ?string + { + return $this->app['config']['log.default']; + } + + /** + * Set the default log driver name. + * + * @param string $name + */ + public function setDefaultDriver($name) + { + $this->app['config']['log.default'] = $name; + } + + /** + * Register a custom driver creator Closure. + * + * @param string $driver + * @param \Closure $callback + * + * @return $this + */ + public function extend(string $driver, \Closure $callback): LogManager + { + $this->customCreators[$driver] = $callback->bindTo($this, $this); + + return $this; + } + + /** + * System is unusable. + * + * @param string $message + * @param array $context + * + * @return void + * + * @throws \Exception + */ + public function emergency($message, array $context = []): void + { + $this->driver()->emergency($message, $context); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param array $context + * + * @return void + * + * @throws \Exception + */ + public function alert($message, array $context = []): void + { + $this->driver()->alert($message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param array $context + * + * @throws \Exception + */ + public function critical($message, array $context = []): void + { + $this->driver()->critical($message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param array $context + * + * @throws \Exception + */ + public function error($message, array $context = []): void + { + $this->driver()->error($message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param array $context + * + * @throws \Exception + */ + public function warning($message, array $context = []): void + { + $this->driver()->warning($message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param array $context + * + * @throws \Exception + */ + public function notice($message, array $context = []): void + { + $this->driver()->notice($message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param array $context + * + * @throws \Exception + */ + public function info($message, array $context = []): void + { + $this->driver()->info($message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param array $context + * + * @throws \Exception + */ + public function debug($message, array $context = []): void + { + $this->driver()->debug($message, $context); + } + + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * + * @throws \Exception + */ + public function log($level, $message, array $context = []): void + { + $this->driver()->log($level, $message, $context); + } + + /** + * Dynamically call the default driver instance. + * + * @param string $method + * @param array $parameters + * + * @throws \Exception + */ + public function __call($method, $parameters) + { + return $this->driver()->$method(...$parameters); + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Article.php b/vendor/overtrue/wechat/src/Kernel/Messages/Article.php new file mode 100644 index 0000000..4792a1f --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/Article.php @@ -0,0 +1,58 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +/** + * Class Article. + */ +class Article extends Message +{ + /** + * @var string + */ + protected $type = 'mpnews'; + + /** + * Properties. + * + * @var array + */ + protected $properties = [ + 'thumb_media_id', + 'author', + 'title', + 'content', + 'digest', + 'source_url', + 'show_cover', + ]; + + /** + * Aliases of attribute. + * + * @var array + */ + protected $jsonAliases = [ + 'content_source_url' => 'source_url', + 'show_cover_pic' => 'show_cover', + ]; + + /** + * @var array + */ + protected $required = [ + 'thumb_media_id', + 'title', + 'content', + 'show_cover', + ]; +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Card.php b/vendor/overtrue/wechat/src/Kernel/Messages/Card.php new file mode 100644 index 0000000..0e21231 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/Card.php @@ -0,0 +1,52 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Card.php. + * + * @author overtrue + * @copyright 2015 overtrue + * + * @see https://github.com/overtrue + * @see http://overtrue.me + */ + +namespace EasyWeChat\Kernel\Messages; + +/** + * Class Card. + */ +class Card extends Message +{ + /** + * Message type. + * + * @var string + */ + protected $type = 'wxcard'; + + /** + * Properties. + * + * @var array + */ + protected $properties = ['card_id']; + + /** + * Media constructor. + * + * @param string $cardId + */ + public function __construct(string $cardId) + { + parent::__construct(['card_id' => $cardId]); + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/DeviceEvent.php b/vendor/overtrue/wechat/src/Kernel/Messages/DeviceEvent.php new file mode 100644 index 0000000..ae57910 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/DeviceEvent.php @@ -0,0 +1,40 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +/** + * Class DeviceEvent. + * + * @property string $media_id + */ +class DeviceEvent extends Message +{ + /** + * Messages type. + * + * @var string + */ + protected $type = 'device_event'; + + /** + * Properties. + * + * @var array + */ + protected $properties = [ + 'device_type', + 'device_id', + 'content', + 'session_id', + 'open_id', + ]; +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/DeviceText.php b/vendor/overtrue/wechat/src/Kernel/Messages/DeviceText.php new file mode 100644 index 0000000..87e627a --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/DeviceText.php @@ -0,0 +1,50 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +/** + * Class DeviceText. + * + * @property string $content + */ +class DeviceText extends Message +{ + /** + * Messages type. + * + * @var string + */ + protected $type = 'device_text'; + + /** + * Properties. + * + * @var array + */ + protected $properties = [ + 'device_type', + 'device_id', + 'content', + 'session_id', + 'open_id', + ]; + + public function toXmlArray() + { + return [ + 'DeviceType' => $this->get('device_type'), + 'DeviceID' => $this->get('device_id'), + 'SessionID' => $this->get('session_id'), + 'Content' => base64_encode($this->get('content')), + ]; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/File.php b/vendor/overtrue/wechat/src/Kernel/Messages/File.php new file mode 100644 index 0000000..a14c42f --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/File.php @@ -0,0 +1,25 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +/** + * Class Image. + * + * @property string $media_id + */ +class File extends Media +{ + /** + * @var string + */ + protected $type = 'file'; +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Image.php b/vendor/overtrue/wechat/src/Kernel/Messages/Image.php new file mode 100644 index 0000000..982033b --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/Image.php @@ -0,0 +1,27 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +/** + * Class Image. + * + * @property string $media_id + */ +class Image extends Media +{ + /** + * Messages type. + * + * @var string + */ + protected $type = 'image'; +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/InteractiveTaskCard.php b/vendor/overtrue/wechat/src/Kernel/Messages/InteractiveTaskCard.php new file mode 100644 index 0000000..e0f7d13 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/InteractiveTaskCard.php @@ -0,0 +1,49 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +/** + * Class InteractiveTaskCard. + * + * @description 企业微信 interactive_taskcard 任务卡片消息类型 + * + * @author xyj2156 + * @date 2021年5月25日 15:21:03 + * + * @property string $title + * @property string $description + * @property string $url + * @property string $task_id + * @property array $btn + */ +class InteractiveTaskCard extends Message +{ + /** + * Messages type. + * + * @var string + */ + protected $type = 'interactive_taskcard'; + + /** + * Properties. + * + * @var array + */ + protected $properties = [ + 'title', + 'description', + 'url', + 'task_id', + 'btn', + ]; +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Link.php b/vendor/overtrue/wechat/src/Kernel/Messages/Link.php new file mode 100644 index 0000000..9c126b5 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/Link.php @@ -0,0 +1,36 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +/** + * Class Link. + */ +class Link extends Message +{ + /** + * Messages type. + * + * @var string + */ + protected $type = 'link'; + + /** + * Properties. + * + * @var array + */ + protected $properties = [ + 'title', + 'description', + 'url', + ]; +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Location.php b/vendor/overtrue/wechat/src/Kernel/Messages/Location.php new file mode 100644 index 0000000..f10351b --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/Location.php @@ -0,0 +1,38 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +/** + * Class Location. + */ +class Location extends Message +{ + /** + * Messages type. + * + * @var string + */ + protected $type = 'location'; + + /** + * Properties. + * + * @var array + */ + protected $properties = [ + 'latitude', + 'longitude', + 'scale', + 'label', + 'precision', + ]; +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Media.php b/vendor/overtrue/wechat/src/Kernel/Messages/Media.php new file mode 100644 index 0000000..d8706fe --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/Media.php @@ -0,0 +1,70 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +use EasyWeChat\Kernel\Contracts\MediaInterface; +use EasyWeChat\Kernel\Support\Str; + +/** + * Class Media. + */ +class Media extends Message implements MediaInterface +{ + /** + * Properties. + * + * @var array + */ + protected $properties = ['media_id']; + + /** + * @var array + */ + protected $required = [ + 'media_id', + ]; + + /** + * MaterialClient constructor. + * + * @param string $mediaId + * @param string $type + * @param array $attributes + */ + public function __construct(string $mediaId, $type = null, array $attributes = []) + { + parent::__construct(array_merge(['media_id' => $mediaId], $attributes)); + + !empty($type) && $this->setType($type); + } + + /** + * @return string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + public function getMediaId(): string + { + $this->checkRequiredAttributes(); + + return $this->get('media_id'); + } + + public function toXmlArray() + { + return [ + Str::studly($this->getType()) => [ + 'MediaId' => $this->get('media_id'), + ], + ]; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Message.php b/vendor/overtrue/wechat/src/Kernel/Messages/Message.php new file mode 100644 index 0000000..3135cb1 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/Message.php @@ -0,0 +1,210 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +use EasyWeChat\Kernel\Contracts\MessageInterface; +use EasyWeChat\Kernel\Exceptions\RuntimeException; +use EasyWeChat\Kernel\Support\XML; +use EasyWeChat\Kernel\Traits\HasAttributes; + +/** + * Class Messages. + */ +abstract class Message implements MessageInterface +{ + use HasAttributes; + + public const TEXT = 2; + public const IMAGE = 4; + public const VOICE = 8; + public const VIDEO = 16; + public const SHORT_VIDEO = 32; + public const LOCATION = 64; + public const LINK = 128; + public const DEVICE_EVENT = 256; + public const DEVICE_TEXT = 512; + public const FILE = 1024; + public const TEXT_CARD = 2048; + public const TRANSFER = 4096; + public const EVENT = 1048576; + public const MINIPROGRAM_PAGE = 2097152; + public const MINIPROGRAM_NOTICE = 4194304; + public const ALL = self::TEXT | self::IMAGE | self::VOICE | self::VIDEO | self::SHORT_VIDEO | self::LOCATION | self::LINK + | self::DEVICE_EVENT | self::DEVICE_TEXT | self::FILE | self::TEXT_CARD | self::TRANSFER | self::EVENT + | self::MINIPROGRAM_PAGE | self::MINIPROGRAM_NOTICE; + + /** + * @var string + */ + protected $type; + + /** + * @var int + */ + protected $id; + + /** + * @var string + */ + protected $to; + + /** + * @var string + */ + protected $from; + + /** + * @var array + */ + protected $properties = []; + + /** + * @var array + */ + protected $jsonAliases = []; + + /** + * Message constructor. + * + * @param array $attributes + */ + public function __construct(array $attributes = []) + { + $this->setAttributes($attributes); + } + + /** + * Return type name message. + * + * @return string + */ + public function getType(): string + { + return $this->type; + } + + /** + * @param string $type + */ + public function setType(string $type) + { + $this->type = $type; + } + + /** + * Magic getter. + * + * @param string $property + * + * @return mixed + */ + public function __get($property) + { + if (property_exists($this, $property)) { + return $this->$property; + } + + return $this->getAttribute($property); + } + + /** + * Magic setter. + * + * @param string $property + * @param mixed $value + * + * @return Message + */ + public function __set($property, $value) + { + if (property_exists($this, $property)) { + $this->$property = $value; + } else { + $this->setAttribute($property, $value); + } + + return $this; + } + + /** + * @param array $appends + * + * @return array + */ + public function transformForJsonRequestWithoutType(array $appends = []) + { + return $this->transformForJsonRequest($appends, false); + } + + /** + * @param array $appends + * @param bool $withType + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + public function transformForJsonRequest(array $appends = [], $withType = true): array + { + if (!$withType) { + return $this->propertiesToArray([], $this->jsonAliases); + } + $messageType = $this->getType(); + $data = array_merge(['msgtype' => $messageType], $appends); + + $data[$messageType] = array_merge($data[$messageType] ?? [], $this->propertiesToArray([], $this->jsonAliases)); + + return $data; + } + + /** + * @param array $appends + * @param bool $returnAsArray + * + * @return string + */ + public function transformToXml(array $appends = [], bool $returnAsArray = false): string + { + $data = array_merge(['MsgType' => $this->getType()], $this->toXmlArray(), $appends); + + return $returnAsArray ? $data : XML::build($data); + } + + /** + * @param array $data + * @param array $aliases + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + protected function propertiesToArray(array $data, array $aliases = []): array + { + $this->checkRequiredAttributes(); + + foreach ($this->attributes as $property => $value) { + if (is_null($value) && !$this->isRequired($property)) { + continue; + } + $alias = array_search($property, $aliases, true); + + $data[$alias ?: $property] = $this->get($property); + } + + return $data; + } + + public function toXmlArray() + { + throw new RuntimeException(sprintf('Class "%s" cannot support transform to XML message.', __CLASS__)); + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/MiniProgramPage.php b/vendor/overtrue/wechat/src/Kernel/Messages/MiniProgramPage.php new file mode 100644 index 0000000..e4973b1 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/MiniProgramPage.php @@ -0,0 +1,31 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +/** + * Class MiniProgramPage. + */ +class MiniProgramPage extends Message +{ + protected $type = 'miniprogrampage'; + + protected $properties = [ + 'title', + 'appid', + 'pagepath', + 'thumb_media_id', + ]; + + protected $required = [ + 'thumb_media_id', 'appid', 'pagepath', + ]; +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/MiniprogramNotice.php b/vendor/overtrue/wechat/src/Kernel/Messages/MiniprogramNotice.php new file mode 100644 index 0000000..c18b259 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/MiniprogramNotice.php @@ -0,0 +1,13 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +/** + * Class Music. + * + * @property string $url + * @property string $hq_url + * @property string $title + * @property string $description + * @property string $thumb_media_id + * @property string $format + */ +class Music extends Message +{ + /** + * Messages type. + * + * @var string + */ + protected $type = 'music'; + + /** + * Properties. + * + * @var array + */ + protected $properties = [ + 'title', + 'description', + 'url', + 'hq_url', + 'thumb_media_id', + 'format', + ]; + + /** + * Aliases of attribute. + * + * @var array + */ + protected $jsonAliases = [ + 'musicurl' => 'url', + 'hqmusicurl' => 'hq_url', + ]; + + public function toXmlArray() + { + $music = [ + 'Music' => [ + 'Title' => $this->get('title'), + 'Description' => $this->get('description'), + 'MusicUrl' => $this->get('url'), + 'HQMusicUrl' => $this->get('hq_url'), + ], + ]; + if ($thumbMediaId = $this->get('thumb_media_id')) { + $music['Music']['ThumbMediaId'] = $thumbMediaId; + } + + return $music; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/News.php b/vendor/overtrue/wechat/src/Kernel/Messages/News.php new file mode 100644 index 0000000..2ad46e8 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/News.php @@ -0,0 +1,73 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +/** + * Class News. + * + * @author overtrue + */ +class News extends Message +{ + /** + * @var string + */ + protected $type = 'news'; + + /** + * @var array + */ + protected $properties = [ + 'items', + ]; + + /** + * News constructor. + * + * @param array $items + */ + public function __construct(array $items = []) + { + parent::__construct(compact('items')); + } + + /** + * @param array $data + * @param array $aliases + * + * @return array + */ + public function propertiesToArray(array $data, array $aliases = []): array + { + return ['articles' => array_map(function ($item) { + if ($item instanceof NewsItem) { + return $item->toJsonArray(); + } + }, $this->get('items'))]; + } + + public function toXmlArray() + { + $items = []; + + foreach ($this->get('items') as $item) { + if ($item instanceof NewsItem) { + $items[] = $item->toXmlArray(); + } + } + + return [ + 'ArticleCount' => count($items), + 'Articles' => $items, + ]; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/NewsItem.php b/vendor/overtrue/wechat/src/Kernel/Messages/NewsItem.php new file mode 100644 index 0000000..50bf336 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/NewsItem.php @@ -0,0 +1,57 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +/** + * Class NewsItem. + */ +class NewsItem extends Message +{ + /** + * Messages type. + * + * @var string + */ + protected $type = 'news'; + + /** + * Properties. + * + * @var array + */ + protected $properties = [ + 'title', + 'description', + 'url', + 'image', + ]; + + public function toJsonArray() + { + return [ + 'title' => $this->get('title'), + 'description' => $this->get('description'), + 'url' => $this->get('url'), + 'picurl' => $this->get('image'), + ]; + } + + public function toXmlArray() + { + return [ + 'Title' => $this->get('title'), + 'Description' => $this->get('description'), + 'Url' => $this->get('url'), + 'PicUrl' => $this->get('image'), + ]; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Raw.php b/vendor/overtrue/wechat/src/Kernel/Messages/Raw.php new file mode 100644 index 0000000..53f2a78 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/Raw.php @@ -0,0 +1,56 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +/** + * Class Raw. + */ +class Raw extends Message +{ + /** + * @var string + */ + protected $type = 'raw'; + + /** + * Properties. + * + * @var array + */ + protected $properties = ['content']; + + /** + * Constructor. + * + * @param string $content + */ + public function __construct(string $content) + { + parent::__construct(['content' => strval($content)]); + } + + /** + * @param array $appends + * @param bool $withType + * + * @return array + */ + public function transformForJsonRequest(array $appends = [], $withType = true): array + { + return json_decode($this->content, true) ?? []; + } + + public function __toString() + { + return $this->get('content') ?? ''; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/ReplyInteractiveTaskCard.php b/vendor/overtrue/wechat/src/Kernel/Messages/ReplyInteractiveTaskCard.php new file mode 100644 index 0000000..f917935 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/ReplyInteractiveTaskCard.php @@ -0,0 +1,60 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +/** + * Class ReplyInteractiveTaskCard + * + * @property array{'replace_name':string} $properties + * + * @description 专门为回复 InteractiveTaskCard 类型任务卡片消息而创建的类型 + * @author xyj2156 + * + * @package App\Extend\EnterpriseApplication\BusinessWX\Message + */ +class ReplyInteractiveTaskCard extends Message +{ + /** + * Message Type + * + * @var string + */ + protected $type = 'update_taskcard'; + + /** + * Properties. + * + * @var array + */ + protected $properties = [ + 'replace_name', + ]; + + /** + * ReplyInteractiveTaskCard constructor. + * + * @param string $replace_name + */ + public function __construct(string $replace_name = '') + { + parent::__construct(compact('replace_name')); + } + + public function toXmlArray() + { + return [ + 'TaskCard' => [ + 'ReplaceName' => $this->get('replace_name'), + ], + ]; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/ShortVideo.php b/vendor/overtrue/wechat/src/Kernel/Messages/ShortVideo.php new file mode 100644 index 0000000..1dc1db9 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/ShortVideo.php @@ -0,0 +1,30 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +/** + * Class ShortVideo. + * + * @property string $title + * @property string $media_id + * @property string $description + * @property string $thumb_media_id + */ +class ShortVideo extends Video +{ + /** + * Messages type. + * + * @var string + */ + protected $type = 'shortvideo'; +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/TaskCard.php b/vendor/overtrue/wechat/src/Kernel/Messages/TaskCard.php new file mode 100644 index 0000000..d02c411 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/TaskCard.php @@ -0,0 +1,44 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +/** + * Class TaskCard. + * + * @property string $title + * @property string $description + * @property string $url + * @property string $task_id + * @property array $btn + */ +class TaskCard extends Message +{ + /** + * Messages type. + * + * @var string + */ + protected $type = 'taskcard'; + + /** + * Properties. + * + * @var array + */ + protected $properties = [ + 'title', + 'description', + 'url', + 'task_id', + 'btn', + ]; +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Text.php b/vendor/overtrue/wechat/src/Kernel/Messages/Text.php new file mode 100644 index 0000000..050e54a --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/Text.php @@ -0,0 +1,54 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +/** + * Class Text. + * + * @property string $content + */ +class Text extends Message +{ + /** + * Message type. + * + * @var string + */ + protected $type = 'text'; + + /** + * Properties. + * + * @var array + */ + protected $properties = ['content']; + + /** + * Text constructor. + * + * @param string $content + */ + public function __construct(string $content = '') + { + parent::__construct(compact('content')); + } + + /** + * @return array + */ + public function toXmlArray() + { + return [ + 'Content' => $this->get('content'), + ]; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/TextCard.php b/vendor/overtrue/wechat/src/Kernel/Messages/TextCard.php new file mode 100644 index 0000000..edfb7c5 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/TextCard.php @@ -0,0 +1,40 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +/** + * Class Text. + * + * @property string $title + * @property string $description + * @property string $url + */ +class TextCard extends Message +{ + /** + * Messages type. + * + * @var string + */ + protected $type = 'textcard'; + + /** + * Properties. + * + * @var array + */ + protected $properties = [ + 'title', + 'description', + 'url', + ]; +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Transfer.php b/vendor/overtrue/wechat/src/Kernel/Messages/Transfer.php new file mode 100644 index 0000000..ff27d1a --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/Transfer.php @@ -0,0 +1,56 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +/** + * Class Transfer. + * + * @property string $to + * @property string $account + */ +class Transfer extends Message +{ + /** + * Messages type. + * + * @var string + */ + protected $type = 'transfer_customer_service'; + + /** + * Properties. + * + * @var array + */ + protected $properties = [ + 'account', + ]; + + /** + * Transfer constructor. + * + * @param string|null $account + */ + public function __construct(string $account = null) + { + parent::__construct(compact('account')); + } + + public function toXmlArray() + { + return empty($this->get('account')) ? [] : [ + 'TransInfo' => [ + 'KfAccount' => $this->get('account'), + ], + ]; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Video.php b/vendor/overtrue/wechat/src/Kernel/Messages/Video.php new file mode 100644 index 0000000..90f72a0 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/Video.php @@ -0,0 +1,65 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +/** + * Class Video. + * + * @property string $video + * @property string $title + * @property string $media_id + * @property string $description + * @property string $thumb_media_id + */ +class Video extends Media +{ + /** + * Messages type. + * + * @var string + */ + protected $type = 'video'; + + /** + * Properties. + * + * @var array + */ + protected $properties = [ + 'title', + 'description', + 'media_id', + 'thumb_media_id', + ]; + + /** + * Video constructor. + * + * @param string $mediaId + * @param array $attributes + */ + public function __construct(string $mediaId, array $attributes = []) + { + parent::__construct($mediaId, 'video', $attributes); + } + + public function toXmlArray() + { + return [ + 'Video' => [ + 'MediaId' => $this->get('media_id'), + 'Title' => $this->get('title'), + 'Description' => $this->get('description'), + ], + ]; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Voice.php b/vendor/overtrue/wechat/src/Kernel/Messages/Voice.php new file mode 100644 index 0000000..ff723ea --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Messages/Voice.php @@ -0,0 +1,37 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Messages; + +/** + * Class Voice. + * + * @property string $media_id + */ +class Voice extends Media +{ + /** + * Messages type. + * + * @var string + */ + protected $type = 'voice'; + + /** + * Properties. + * + * @var array + */ + protected $properties = [ + 'media_id', + 'recognition', + ]; +} diff --git a/vendor/overtrue/wechat/src/Kernel/Providers/ConfigServiceProvider.php b/vendor/overtrue/wechat/src/Kernel/Providers/ConfigServiceProvider.php new file mode 100644 index 0000000..24ff919 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Providers/ConfigServiceProvider.php @@ -0,0 +1,39 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Providers; + +use EasyWeChat\Kernel\Config; +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ConfigServiceProvider. + * + * @author overtrue + */ +class ConfigServiceProvider implements ServiceProviderInterface +{ + /** + * Registers services on the given container. + * + * This method should only be used to configure services and parameters. + * It should not get services. + * + * @param Container $pimple A container instance + */ + public function register(Container $pimple) + { + !isset($pimple['config']) && $pimple['config'] = function ($app) { + return new Config($app->getConfig()); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Providers/EventDispatcherServiceProvider.php b/vendor/overtrue/wechat/src/Kernel/Providers/EventDispatcherServiceProvider.php new file mode 100644 index 0000000..97b2b99 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Providers/EventDispatcherServiceProvider.php @@ -0,0 +1,47 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Providers; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Symfony\Component\EventDispatcher\EventDispatcher; + +/** + * Class EventDispatcherServiceProvider. + * + * @author mingyoung + */ +class EventDispatcherServiceProvider implements ServiceProviderInterface +{ + /** + * Registers services on the given container. + * + * This method should only be used to configure services and parameters. + * It should not get services. + * + * @param Container $pimple A container instance + */ + public function register(Container $pimple) + { + !isset($pimple['events']) && $pimple['events'] = function ($app) { + $dispatcher = new EventDispatcher(); + + foreach ($app->config->get('events.listen', []) as $event => $listeners) { + foreach ($listeners as $listener) { + $dispatcher->addListener($event, $listener); + } + } + + return $dispatcher; + }; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Providers/ExtensionServiceProvider.php b/vendor/overtrue/wechat/src/Kernel/Providers/ExtensionServiceProvider.php new file mode 100644 index 0000000..1d8badd --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Providers/ExtensionServiceProvider.php @@ -0,0 +1,39 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Providers; + +use EasyWeChatComposer\Extension; +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ExtensionServiceProvider. + * + * @author overtrue + */ +class ExtensionServiceProvider implements ServiceProviderInterface +{ + /** + * Registers services on the given container. + * + * This method should only be used to configure services and parameters. + * It should not get services. + * + * @param Container $pimple A container instance + */ + public function register(Container $pimple) + { + !isset($pimple['extension']) && $pimple['extension'] = function ($app) { + return new Extension($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Providers/HttpClientServiceProvider.php b/vendor/overtrue/wechat/src/Kernel/Providers/HttpClientServiceProvider.php new file mode 100644 index 0000000..e21edfa --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Providers/HttpClientServiceProvider.php @@ -0,0 +1,39 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Providers; + +use GuzzleHttp\Client; +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class HttpClientServiceProvider. + * + * @author overtrue + */ +class HttpClientServiceProvider implements ServiceProviderInterface +{ + /** + * Registers services on the given container. + * + * This method should only be used to configure services and parameters. + * It should not get services. + * + * @param Container $pimple A container instance + */ + public function register(Container $pimple) + { + !isset($pimple['http_client']) && $pimple['http_client'] = function ($app) { + return new Client($app['config']->get('http', [])); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Providers/LogServiceProvider.php b/vendor/overtrue/wechat/src/Kernel/Providers/LogServiceProvider.php new file mode 100644 index 0000000..88058bb --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Providers/LogServiceProvider.php @@ -0,0 +1,47 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Providers; + +use EasyWeChat\Kernel\Log\LogManager; +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class LoggingServiceProvider. + * + * @author overtrue + */ +class LogServiceProvider implements ServiceProviderInterface +{ + /** + * Registers services on the given container. + * + * This method should only be used to configure services and parameters. + * It should not get services. + * + * @param Container $pimple A container instance + */ + public function register(Container $pimple) + { + !isset($pimple['log']) && $pimple['log'] = function ($app) { + $config = $app['config']->get('log'); + + if (!empty($config)) { + $app->rebind('config', $app['config']->merge($config)); + } + + return new LogManager($app); + }; + + !isset($pimple['logger']) && $pimple['logger'] = $pimple['log']; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Providers/RequestServiceProvider.php b/vendor/overtrue/wechat/src/Kernel/Providers/RequestServiceProvider.php new file mode 100644 index 0000000..d38522e --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Providers/RequestServiceProvider.php @@ -0,0 +1,39 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Providers; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Symfony\Component\HttpFoundation\Request; + +/** + * Class RequestServiceProvider. + * + * @author overtrue + */ +class RequestServiceProvider implements ServiceProviderInterface +{ + /** + * Registers services on the given container. + * + * This method should only be used to configure services and parameters. + * It should not get services. + * + * @param Container $pimple A container instance + */ + public function register(Container $pimple) + { + !isset($pimple['request']) && $pimple['request'] = function () { + return Request::createFromGlobals(); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/ServerGuard.php b/vendor/overtrue/wechat/src/Kernel/ServerGuard.php new file mode 100644 index 0000000..066b220 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/ServerGuard.php @@ -0,0 +1,375 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel; + +use EasyWeChat\Kernel\Contracts\MessageInterface; +use EasyWeChat\Kernel\Exceptions\BadRequestException; +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; +use EasyWeChat\Kernel\Messages\Message; +use EasyWeChat\Kernel\Messages\News; +use EasyWeChat\Kernel\Messages\NewsItem; +use EasyWeChat\Kernel\Messages\Raw as RawMessage; +use EasyWeChat\Kernel\Messages\Text; +use EasyWeChat\Kernel\Support\XML; +use EasyWeChat\Kernel\Traits\Observable; +use EasyWeChat\Kernel\Traits\ResponseCastable; +use Symfony\Component\HttpFoundation\Response; + +/** + * Class ServerGuard. + * + * 1. url 里的 signature 只是将 token+nonce+timestamp 得到的签名,只是用于验证当前请求的,在公众号环境下一直有 + * 2. 企业号消息发送时是没有的,因为固定为完全模式,所以 url 里不会存在 signature, 只有 msg_signature 用于解密消息的 + * + * @author overtrue + */ +class ServerGuard +{ + use Observable; + use ResponseCastable; + + /** + * @var bool + */ + protected $alwaysValidate = false; + + /** + * Empty string. + */ + public const SUCCESS_EMPTY_RESPONSE = 'success'; + + /** + * @var array + */ + public const MESSAGE_TYPE_MAPPING = [ + 'text' => Message::TEXT, + 'image' => Message::IMAGE, + 'voice' => Message::VOICE, + 'video' => Message::VIDEO, + 'shortvideo' => Message::SHORT_VIDEO, + 'location' => Message::LOCATION, + 'link' => Message::LINK, + 'device_event' => Message::DEVICE_EVENT, + 'device_text' => Message::DEVICE_TEXT, + 'event' => Message::EVENT, + 'file' => Message::FILE, + 'miniprogrampage' => Message::MINIPROGRAM_PAGE, + ]; + + /** + * @var \EasyWeChat\Kernel\ServiceContainer + */ + protected $app; + + /** + * Constructor. + * + * @codeCoverageIgnore + * + * @param \EasyWeChat\Kernel\ServiceContainer $app + */ + public function __construct(ServiceContainer $app) + { + $this->app = $app; + + foreach ($this->app->extension->observers() as $observer) { + call_user_func_array([$this, 'push'], $observer); + } + } + + /** + * Handle and return response. + * + * @return Response + * + * @throws BadRequestException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function serve(): Response + { + $this->app['logger']->debug('Request received:', [ + 'method' => $this->app['request']->getMethod(), + 'uri' => $this->app['request']->getUri(), + 'content-type' => $this->app['request']->getContentType(), + 'content' => $this->app['request']->getContent(), + ]); + + $response = $this->validate()->resolve(); + + $this->app['logger']->debug('Server response created:', ['content' => $response->getContent()]); + + return $response; + } + + /** + * @return $this + * + * @throws \EasyWeChat\Kernel\Exceptions\BadRequestException + */ + public function validate() + { + if (!$this->alwaysValidate && !$this->isSafeMode()) { + return $this; + } + + if ($this->app['request']->get('signature') !== $this->signature([ + $this->getToken(), + $this->app['request']->get('timestamp'), + $this->app['request']->get('nonce'), + ])) { + throw new BadRequestException('Invalid request signature.', 400); + } + + return $this; + } + + /** + * Force validate request. + * + * @return $this + */ + public function forceValidate() + { + $this->alwaysValidate = true; + + return $this; + } + + /** + * Get request message. + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|string + * + * @throws BadRequestException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function getMessage() + { + $message = $this->parseMessage($this->app['request']->getContent(false)); + + if (!is_array($message) || empty($message)) { + throw new BadRequestException('No message received.'); + } + + if ($this->isSafeMode() && !empty($message['Encrypt'])) { + $message = $this->decryptMessage($message); + + // Handle JSON format. + $dataSet = json_decode($message, true); + + if ($dataSet && (JSON_ERROR_NONE === json_last_error())) { + return $dataSet; + } + + $message = XML::parse($message); + } + + return $this->detectAndCastResponseToType($message, $this->app->config->get('response_type')); + } + + /** + * Resolve server request and return the response. + * + * @return \Symfony\Component\HttpFoundation\Response + * + * @throws \EasyWeChat\Kernel\Exceptions\BadRequestException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + protected function resolve(): Response + { + $result = $this->handleRequest(); + + if ($this->shouldReturnRawResponse()) { + $response = new Response($result['response']); + } else { + $response = new Response( + $this->buildResponse($result['to'], $result['from'], $result['response']), + 200, + ['Content-Type' => 'application/xml'] + ); + } + + $this->app->events->dispatch(new Events\ServerGuardResponseCreated($response)); + + return $response; + } + + /** + * @return string|null + */ + protected function getToken() + { + return $this->app['config']['token']; + } + + /** + * @param string $to + * @param string $from + * @param \EasyWeChat\Kernel\Contracts\MessageInterface|string|int $message + * + * @return string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + public function buildResponse(string $to, string $from, $message) + { + if (empty($message) || self::SUCCESS_EMPTY_RESPONSE === $message) { + return self::SUCCESS_EMPTY_RESPONSE; + } + + if ($message instanceof RawMessage) { + return $message->get('content', self::SUCCESS_EMPTY_RESPONSE); + } + + if (is_string($message) || is_numeric($message)) { + $message = new Text((string) $message); + } + + if (is_array($message) && reset($message) instanceof NewsItem) { + $message = new News($message); + } + + if (!($message instanceof Message)) { + throw new InvalidArgumentException(sprintf('Invalid Messages type "%s".', gettype($message))); + } + + return $this->buildReply($to, $from, $message); + } + + /** + * Handle request. + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\BadRequestException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + protected function handleRequest(): array + { + $castedMessage = $this->getMessage(); + + $messageArray = $this->detectAndCastResponseToType($castedMessage, 'array'); + + $response = $this->dispatch(self::MESSAGE_TYPE_MAPPING[$messageArray['MsgType'] ?? $messageArray['msg_type'] ?? 'text'], $castedMessage); + + return [ + 'to' => $messageArray['FromUserName'] ?? '', + 'from' => $messageArray['ToUserName'] ?? '', + 'response' => $response, + ]; + } + + /** + * Build reply XML. + * + * @param string $to + * @param string $from + * @param \EasyWeChat\Kernel\Contracts\MessageInterface $message + * + * @return string + */ + protected function buildReply(string $to, string $from, MessageInterface $message): string + { + $prepends = [ + 'ToUserName' => $to, + 'FromUserName' => $from, + 'CreateTime' => time(), + 'MsgType' => $message->getType(), + ]; + + $response = $message->transformToXml($prepends); + + if ($this->isSafeMode()) { + $this->app['logger']->debug('Messages safe mode is enabled.'); + $response = $this->app['encryptor']->encrypt($response); + } + + return $response; + } + + /** + * @param array $params + * + * @return string + */ + protected function signature(array $params) + { + sort($params, SORT_STRING); + + return sha1(implode($params)); + } + + /** + * Parse message array from raw php input. + * + * @param string $content + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\BadRequestException + */ + protected function parseMessage($content) + { + try { + if (0 === stripos($content, '<')) { + $content = XML::parse($content); + } else { + // Handle JSON format. + $dataSet = json_decode($content, true); + if ($dataSet && (JSON_ERROR_NONE === json_last_error())) { + $content = $dataSet; + } + } + + return (array) $content; + } catch (\Exception $e) { + throw new BadRequestException(sprintf('Invalid message content:(%s) %s', $e->getCode(), $e->getMessage()), $e->getCode()); + } + } + + /** + * Check the request message safe mode. + * + * @return bool + */ + protected function isSafeMode(): bool + { + return $this->app['request']->get('signature') && 'aes' === $this->app['request']->get('encrypt_type'); + } + + /** + * @return bool + */ + protected function shouldReturnRawResponse(): bool + { + return false; + } + + /** + * @param array $message + * + * @return mixed + */ + protected function decryptMessage(array $message) + { + return $message = $this->app['encryptor']->decrypt( + $message['Encrypt'], + $this->app['request']->get('msg_signature'), + $this->app['request']->get('nonce'), + $this->app['request']->get('timestamp') + ); + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/ServiceContainer.php b/vendor/overtrue/wechat/src/Kernel/ServiceContainer.php new file mode 100644 index 0000000..be1e0a1 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/ServiceContainer.php @@ -0,0 +1,167 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel; + +use EasyWeChat\Kernel\Providers\ConfigServiceProvider; +use EasyWeChat\Kernel\Providers\EventDispatcherServiceProvider; +use EasyWeChat\Kernel\Providers\ExtensionServiceProvider; +use EasyWeChat\Kernel\Providers\HttpClientServiceProvider; +use EasyWeChat\Kernel\Providers\LogServiceProvider; +use EasyWeChat\Kernel\Providers\RequestServiceProvider; +use EasyWeChatComposer\Traits\WithAggregator; +use Pimple\Container; + +/** + * Class ServiceContainer. + * + * @author overtrue + * + * @property \EasyWeChat\Kernel\Config $config + * @property \Symfony\Component\HttpFoundation\Request $request + * @property \GuzzleHttp\Client $http_client + * @property \Monolog\Logger $logger + * @property \Symfony\Component\EventDispatcher\EventDispatcher $events + */ +class ServiceContainer extends Container +{ + use WithAggregator; + + /** + * @var string + */ + protected $id; + + /** + * @var array + */ + protected $providers = []; + + /** + * @var array + */ + protected $defaultConfig = []; + + /** + * @var array + */ + protected $userConfig = []; + + /** + * Constructor. + * + * @param array $config + * @param array $prepends + * @param string|null $id + */ + public function __construct(array $config = [], array $prepends = [], string $id = null) + { + $this->userConfig = $config; + + parent::__construct($prepends); + + $this->registerProviders($this->getProviders()); + + $this->id = $id; + + $this->aggregate(); + + $this->events->dispatch(new Events\ApplicationInitialized($this)); + } + + /** + * @return string + */ + public function getId() + { + return $this->id ?? $this->id = md5(json_encode($this->userConfig)); + } + + /** + * @return array + */ + public function getConfig() + { + $base = [ + // http://docs.guzzlephp.org/en/stable/request-options.html + 'http' => [ + 'timeout' => 30.0, + 'base_uri' => 'https://api.weixin.qq.com/', + ], + ]; + + return array_replace_recursive($base, $this->defaultConfig, $this->userConfig); + } + + /** + * Return all providers. + * + * @return array + */ + public function getProviders() + { + return array_merge([ + ConfigServiceProvider::class, + LogServiceProvider::class, + RequestServiceProvider::class, + HttpClientServiceProvider::class, + ExtensionServiceProvider::class, + EventDispatcherServiceProvider::class, + ], $this->providers); + } + + /** + * @param string $id + * @param mixed $value + */ + public function rebind($id, $value) + { + $this->offsetUnset($id); + $this->offsetSet($id, $value); + } + + /** + * Magic get access. + * + * @param string $id + * + * @return mixed + */ + public function __get($id) + { + if ($this->shouldDelegate($id)) { + return $this->delegateTo($id); + } + + return $this->offsetGet($id); + } + + /** + * Magic set access. + * + * @param string $id + * @param mixed $value + */ + public function __set($id, $value) + { + $this->offsetSet($id, $value); + } + + /** + * @param array $providers + */ + public function registerProviders(array $providers) + { + foreach ($providers as $provider) { + parent::register(new $provider()); + } + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Support/AES.php b/vendor/overtrue/wechat/src/Kernel/Support/AES.php new file mode 100644 index 0000000..73b1b24 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Support/AES.php @@ -0,0 +1,85 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Support; + +/** + * Class AES. + * + * @author overtrue + */ +class AES +{ + /** + * @param string $text + * @param string $key + * @param string $iv + * @param int $option + * + * @return string + */ + public static function encrypt(string $text, string $key, string $iv, int $option = OPENSSL_RAW_DATA): string + { + self::validateKey($key); + self::validateIv($iv); + + return openssl_encrypt($text, self::getMode($key), $key, $option, $iv); + } + + /** + * @param string $cipherText + * @param string $key + * @param string $iv + * @param int $option + * @param string|null $method + * + * @return string + */ + public static function decrypt(string $cipherText, string $key, string $iv, int $option = OPENSSL_RAW_DATA, $method = null): string + { + self::validateKey($key); + self::validateIv($iv); + + return openssl_decrypt($cipherText, $method ?: self::getMode($key), $key, $option, $iv); + } + + /** + * @param string $key + * + * @return string + */ + public static function getMode($key) + { + return 'aes-'.(8 * strlen($key)).'-cbc'; + } + + /** + * @param string $key + */ + public static function validateKey(string $key) + { + if (!in_array(strlen($key), [16, 24, 32], true)) { + throw new \InvalidArgumentException(sprintf('Key length must be 16, 24, or 32 bytes; got key len (%s).', strlen($key))); + } + } + + /** + * @param string $iv + * + * @throws \InvalidArgumentException + */ + public static function validateIv(string $iv) + { + if (!empty($iv) && 16 !== strlen($iv)) { + throw new \InvalidArgumentException('IV length must be 16 bytes.'); + } + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Support/Arr.php b/vendor/overtrue/wechat/src/Kernel/Support/Arr.php new file mode 100644 index 0000000..c251da2 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Support/Arr.php @@ -0,0 +1,466 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Support; + +/** + * Array helper from Illuminate\Support\Arr. + */ +class Arr +{ + /** + * Add an element to an array using "dot" notation if it doesn't exist. + * + * @param array $array + * @param string $key + * @param mixed $value + * + * @return array + */ + public static function add(array $array, $key, $value) + { + if (is_null(static::get($array, $key))) { + static::set($array, $key, $value); + } + + return $array; + } + + /** + * Cross join the given arrays, returning all possible permutations. + * + * @param array ...$arrays + * + * @return array + */ + public static function crossJoin(...$arrays) + { + $results = [[]]; + + foreach ($arrays as $index => $array) { + $append = []; + + foreach ($results as $product) { + foreach ($array as $item) { + $product[$index] = $item; + + $append[] = $product; + } + } + + $results = $append; + } + + return $results; + } + + /** + * Divide an array into two arrays. One with keys and the other with values. + * + * @param array $array + * + * @return array + */ + public static function divide(array $array) + { + return [array_keys($array), array_values($array)]; + } + + /** + * Flatten a multi-dimensional associative array with dots. + * + * @param array $array + * @param string $prepend + * + * @return array + */ + public static function dot(array $array, $prepend = '') + { + $results = []; + + foreach ($array as $key => $value) { + if (is_array($value) && !empty($value)) { + $results = array_merge($results, static::dot($value, $prepend.$key.'.')); + } else { + $results[$prepend.$key] = $value; + } + } + + return $results; + } + + /** + * Get all of the given array except for a specified array of items. + * + * @param array $array + * @param array|string $keys + * + * @return array + */ + public static function except(array $array, $keys) + { + static::forget($array, $keys); + + return $array; + } + + /** + * Determine if the given key exists in the provided array. + * + * @param array $array + * @param string|int $key + * + * @return bool + */ + public static function exists(array $array, $key) + { + return array_key_exists($key, $array); + } + + /** + * Return the first element in an array passing a given truth test. + * + * @param array $array + * @param callable|null $callback + * @param mixed $default + * + * @return mixed + */ + public static function first(array $array, callable $callback = null, $default = null) + { + if (is_null($callback)) { + if (empty($array)) { + return $default; + } + + foreach ($array as $item) { + return $item; + } + } + + foreach ($array as $key => $value) { + if (call_user_func($callback, $value, $key)) { + return $value; + } + } + + return $default; + } + + /** + * Return the last element in an array passing a given truth test. + * + * @param array $array + * @param callable|null $callback + * @param mixed $default + * + * @return mixed + */ + public static function last(array $array, callable $callback = null, $default = null) + { + if (is_null($callback)) { + return empty($array) ? $default : end($array); + } + + return static::first(array_reverse($array, true), $callback, $default); + } + + /** + * Flatten a multi-dimensional array into a single level. + * + * @param array $array + * @param int $depth + * + * @return array + */ + public static function flatten(array $array, $depth = \PHP_INT_MAX) + { + return array_reduce($array, function ($result, $item) use ($depth) { + $item = $item instanceof Collection ? $item->all() : $item; + + if (!is_array($item)) { + return array_merge($result, [$item]); + } elseif (1 === $depth) { + return array_merge($result, array_values($item)); + } + + return array_merge($result, static::flatten($item, $depth - 1)); + }, []); + } + + /** + * Remove one or many array items from a given array using "dot" notation. + * + * @param array $array + * @param array|string $keys + */ + public static function forget(array &$array, $keys) + { + $original = &$array; + + $keys = (array) $keys; + + if (0 === count($keys)) { + return; + } + + foreach ($keys as $key) { + // if the exact key exists in the top-level, remove it + if (static::exists($array, $key)) { + unset($array[$key]); + + continue; + } + + $parts = explode('.', $key); + + // clean up before each pass + $array = &$original; + + while (count($parts) > 1) { + $part = array_shift($parts); + + if (isset($array[$part]) && is_array($array[$part])) { + $array = &$array[$part]; + } else { + continue 2; + } + } + + unset($array[array_shift($parts)]); + } + } + + /** + * Get an item from an array using "dot" notation. + * + * @param array $array + * @param string $key + * @param mixed $default + * + * @return mixed + */ + public static function get(array $array, $key, $default = null) + { + if (is_null($key)) { + return $array; + } + + if (static::exists($array, $key)) { + return $array[$key]; + } + + foreach (explode('.', $key) as $segment) { + if (static::exists($array, $segment)) { + $array = $array[$segment]; + } else { + return $default; + } + } + + return $array; + } + + /** + * Check if an item or items exist in an array using "dot" notation. + * + * @param array $array + * @param string|array $keys + * + * @return bool + */ + public static function has(array $array, $keys) + { + if (is_null($keys)) { + return false; + } + + $keys = (array) $keys; + + if (empty($array)) { + return false; + } + + if ($keys === []) { + return false; + } + + foreach ($keys as $key) { + $subKeyArray = $array; + + if (static::exists($array, $key)) { + continue; + } + + foreach (explode('.', $key) as $segment) { + if (static::exists($subKeyArray, $segment)) { + $subKeyArray = $subKeyArray[$segment]; + } else { + return false; + } + } + } + + return true; + } + + /** + * Determines if an array is associative. + * + * An array is "associative" if it doesn't have sequential numerical keys beginning with zero. + * + * @param array $array + * + * @return bool + */ + public static function isAssoc(array $array) + { + $keys = array_keys($array); + + return array_keys($keys) !== $keys; + } + + /** + * Get a subset of the items from the given array. + * + * @param array $array + * @param array|string $keys + * + * @return array + */ + public static function only(array $array, $keys) + { + return array_intersect_key($array, array_flip((array) $keys)); + } + + /** + * Push an item onto the beginning of an array. + * + * @param array $array + * @param mixed $value + * @param mixed $key + * + * @return array + */ + public static function prepend(array $array, $value, $key = null) + { + if (is_null($key)) { + array_unshift($array, $value); + } else { + $array = [$key => $value] + $array; + } + + return $array; + } + + /** + * Get a value from the array, and remove it. + * + * @param array $array + * @param string $key + * @param mixed $default + * + * @return mixed + */ + public static function pull(array &$array, $key, $default = null) + { + $value = static::get($array, $key, $default); + + static::forget($array, $key); + + return $value; + } + + /** + * Get a 1 value from an array. + * + * @param array $array + * @param int|null $amount + * + * @return mixed + * + * @throws \InvalidArgumentException + */ + public static function random(array $array, int $amount = null) + { + if (is_null($amount)) { + return $array[array_rand($array)]; + } + + $keys = array_rand($array, $amount); + + $results = []; + + foreach ((array) $keys as $key) { + $results[] = $array[$key]; + } + + return $results; + } + + /** + * Set an array item to a given value using "dot" notation. + * + * If no key is given to the method, the entire array will be replaced. + * + * @param array $array + * @param string $key + * @param mixed $value + * + * @return array + */ + public static function set(array &$array, string $key, $value) + { + $keys = explode('.', $key); + + while (count($keys) > 1) { + $key = array_shift($keys); + + // If the key doesn't exist at this depth, we will just create an empty array + // to hold the next value, allowing us to create the arrays to hold final + // values at the correct depth. Then we'll keep digging into the array. + if (!isset($array[$key]) || !is_array($array[$key])) { + $array[$key] = []; + } + + $array = &$array[$key]; + } + + $array[array_shift($keys)] = $value; + + return $array; + } + + /** + * Filter the array using the given callback. + * + * @param array $array + * @param callable $callback + * + * @return array + */ + public static function where(array $array, callable $callback) + { + return array_filter($array, $callback, ARRAY_FILTER_USE_BOTH); + } + + /** + * If the given value is not an array, wrap it in one. + * + * @param mixed $value + * + * @return array + */ + public static function wrap($value) + { + return !is_array($value) ? [$value] : $value; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Support/ArrayAccessible.php b/vendor/overtrue/wechat/src/Kernel/Support/ArrayAccessible.php new file mode 100644 index 0000000..72e0b04 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Support/ArrayAccessible.php @@ -0,0 +1,66 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Support; + +use ArrayAccess; +use ArrayIterator; +use EasyWeChat\Kernel\Contracts\Arrayable; +use IteratorAggregate; + +/** + * Class ArrayAccessible. + * + * @author overtrue + */ +class ArrayAccessible implements ArrayAccess, IteratorAggregate, Arrayable +{ + private $array; + + public function __construct(array $array = []) + { + $this->array = $array; + } + + public function offsetExists($offset) + { + return array_key_exists($offset, $this->array); + } + + public function offsetGet($offset) + { + return $this->array[$offset]; + } + + public function offsetSet($offset, $value) + { + if (null === $offset) { + $this->array[] = $value; + } else { + $this->array[$offset] = $value; + } + } + + public function offsetUnset($offset) + { + unset($this->array[$offset]); + } + + public function getIterator() + { + return new ArrayIterator($this->array); + } + + public function toArray() + { + return $this->array; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Support/Collection.php b/vendor/overtrue/wechat/src/Kernel/Support/Collection.php new file mode 100644 index 0000000..848498d --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Support/Collection.php @@ -0,0 +1,423 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Support; + +use ArrayAccess; +use ArrayIterator; +use Countable; +use EasyWeChat\Kernel\Contracts\Arrayable; +use IteratorAggregate; +use JsonSerializable; +use Serializable; + +/** + * Class Collection. + */ +class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable, Serializable, Arrayable +{ + /** + * The collection data. + * + * @var array + */ + protected $items = []; + + /** + * set data. + * + * @param array $items + */ + public function __construct(array $items = []) + { + foreach ($items as $key => $value) { + $this->set($key, $value); + } + } + + /** + * Return all items. + * + * @return array + */ + public function all() + { + return $this->items; + } + + /** + * Return specific items. + * + * @param array $keys + * + * @return \EasyWeChat\Kernel\Support\Collection + */ + public function only(array $keys) + { + $return = []; + + foreach ($keys as $key) { + $value = $this->get($key); + + if (!is_null($value)) { + $return[$key] = $value; + } + } + + return new static($return); + } + + /** + * Get all items except for those with the specified keys. + * + * @param mixed $keys + * + * @return static + */ + public function except($keys) + { + $keys = is_array($keys) ? $keys : func_get_args(); + + return new static(Arr::except($this->items, $keys)); + } + + /** + * Merge data. + * + * @param Collection|array $items + * + * @return \EasyWeChat\Kernel\Support\Collection + */ + public function merge($items) + { + $clone = new static($this->all()); + + foreach ($items as $key => $value) { + $clone->set($key, $value); + } + + return $clone; + } + + /** + * To determine Whether the specified element exists. + * + * @param string $key + * + * @return bool + */ + public function has($key) + { + return !is_null(Arr::get($this->items, $key)); + } + + /** + * Retrieve the first item. + * + * @return mixed + */ + public function first() + { + return reset($this->items); + } + + /** + * Retrieve the last item. + * + * @return bool + */ + public function last() + { + $end = end($this->items); + + reset($this->items); + + return $end; + } + + /** + * add the item value. + * + * @param string $key + * @param mixed $value + */ + public function add($key, $value) + { + Arr::set($this->items, $key, $value); + } + + /** + * Set the item value. + * + * @param string $key + * @param mixed $value + */ + public function set($key, $value) + { + Arr::set($this->items, $key, $value); + } + + /** + * Retrieve item from Collection. + * + * @param string $key + * @param mixed $default + * + * @return mixed + */ + public function get($key, $default = null) + { + return Arr::get($this->items, $key, $default); + } + + /** + * Remove item form Collection. + * + * @param string $key + */ + public function forget($key) + { + Arr::forget($this->items, $key); + } + + /** + * Build to array. + * + * @return array + */ + public function toArray() + { + return $this->all(); + } + + /** + * Build to json. + * + * @param int $option + * + * @return string + */ + public function toJson($option = JSON_UNESCAPED_UNICODE) + { + return json_encode($this->all(), $option); + } + + /** + * To string. + * + * @return string + */ + public function __toString() + { + return $this->toJson(); + } + + /** + * (PHP 5 >= 5.4.0)
        + * Specify data which should be serialized to JSON. + * + * @see http://php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return mixed data which can be serialized by json_encode, + * which is a value of any type other than a resource + */ + public function jsonSerialize() + { + return $this->items; + } + + /** + * (PHP 5 >= 5.1.0)
        + * String representation of object. + * + * @see http://php.net/manual/en/serializable.serialize.php + * + * @return string the string representation of the object or null + */ + public function serialize() + { + return serialize($this->items); + } + + /** + * (PHP 5 >= 5.0.0)
        + * Retrieve an external iterator. + * + * @see http://php.net/manual/en/iteratoraggregate.getiterator.php + * + * @return \ArrayIterator An instance of an object implementing Iterator or + * Traversable + */ + public function getIterator() + { + return new ArrayIterator($this->items); + } + + /** + * (PHP 5 >= 5.1.0)
        + * Count elements of an object. + * + * @see http://php.net/manual/en/countable.count.php + * + * @return int the custom count as an integer. + *

        + *

        + * The return value is cast to an integer + */ + public function count() + { + return count($this->items); + } + + /** + * (PHP 5 >= 5.1.0)
        + * Constructs the object. + * + * @see http://php.net/manual/en/serializable.unserialize.php + * + * @param string $serialized

        + * The string representation of the object. + *

        + * + * @return mixed|void + */ + public function unserialize($serialized) + { + return $this->items = unserialize($serialized); + } + + /** + * Get a data by key. + * + * @param string $key + * + * @return mixed + */ + public function __get($key) + { + return $this->get($key); + } + + /** + * Assigns a value to the specified data. + * + * @param string $key + * @param mixed $value + */ + public function __set($key, $value) + { + $this->set($key, $value); + } + + /** + * Whether or not an data exists by key. + * + * @param string $key + * + * @return bool + */ + public function __isset($key) + { + return $this->has($key); + } + + /** + * Unset an data by key. + * + * @param string $key + */ + public function __unset($key) + { + $this->forget($key); + } + + /** + * var_export. + * + * @param array $properties + * + * @return array + */ + public static function __set_state(array $properties) + { + return (new static($properties))->all(); + } + + /** + * (PHP 5 >= 5.0.0)
        + * Whether a offset exists. + * + * @see http://php.net/manual/en/arrayaccess.offsetexists.php + * + * @param mixed $offset

        + * An offset to check for. + *

        + * + * @return bool true on success or false on failure. + * The return value will be casted to boolean if non-boolean was returned + */ + public function offsetExists($offset) + { + return $this->has($offset); + } + + /** + * (PHP 5 >= 5.0.0)
        + * Offset to unset. + * + * @see http://php.net/manual/en/arrayaccess.offsetunset.php + * + * @param mixed $offset

        + * The offset to unset. + *

        + */ + public function offsetUnset($offset) + { + if ($this->offsetExists($offset)) { + $this->forget($offset); + } + } + + /** + * (PHP 5 >= 5.0.0)
        + * Offset to retrieve. + * + * @see http://php.net/manual/en/arrayaccess.offsetget.php + * + * @param mixed $offset

        + * The offset to retrieve. + *

        + * + * @return mixed Can return all value types + */ + public function offsetGet($offset) + { + return $this->offsetExists($offset) ? $this->get($offset) : null; + } + + /** + * (PHP 5 >= 5.0.0)
        + * Offset to set. + * + * @see http://php.net/manual/en/arrayaccess.offsetset.php + * + * @param mixed $offset

        + * The offset to assign the value to. + *

        + * @param mixed $value

        + * The value to set. + *

        + */ + public function offsetSet($offset, $value) + { + $this->set($offset, $value); + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Support/File.php b/vendor/overtrue/wechat/src/Kernel/Support/File.php new file mode 100644 index 0000000..b51b41d --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Support/File.php @@ -0,0 +1,135 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Support; + +use finfo; + +/** + * Class File. + */ +class File +{ + /** + * MIME mapping. + * + * @var array + */ + protected static $extensionMap = [ + 'audio/wav' => '.wav', + 'audio/x-ms-wma' => '.wma', + 'video/x-ms-wmv' => '.wmv', + 'video/mp4' => '.mp4', + 'audio/mpeg' => '.mp3', + 'audio/amr' => '.amr', + 'application/vnd.rn-realmedia' => '.rm', + 'audio/mid' => '.mid', + 'image/bmp' => '.bmp', + 'image/gif' => '.gif', + 'image/png' => '.png', + 'image/tiff' => '.tiff', + 'image/jpeg' => '.jpg', + 'application/pdf' => '.pdf', + + // 列举更多的文件 mime, 企业号是支持的,公众平台这边之后万一也更新了呢 + 'application/msword' => '.doc', + + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => '.docx', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => '.dotx', + 'application/vnd.ms-word.document.macroEnabled.12' => '.docm', + 'application/vnd.ms-word.template.macroEnabled.12' => '.dotm', + + 'application/vnd.ms-excel' => '.xls', + + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => '.xlsx', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => '.xltx', + 'application/vnd.ms-excel.sheet.macroEnabled.12' => '.xlsm', + 'application/vnd.ms-excel.template.macroEnabled.12' => '.xltm', + 'application/vnd.ms-excel.addin.macroEnabled.12' => '.xlam', + 'application/vnd.ms-excel.sheet.binary.macroEnabled.12' => '.xlsb', + + 'application/vnd.ms-powerpoint' => '.ppt', + + 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => '.pptx', + 'application/vnd.openxmlformats-officedocument.presentationml.template' => '.potx', + 'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => '.ppsx', + 'application/vnd.ms-powerpoint.addin.macroEnabled.12' => '.ppam', + ]; + + /** + * File header signatures. + * + * @var array + */ + protected static $signatures = [ + 'ffd8ff' => '.jpg', + '424d' => '.bmp', + '47494638' => '.gif', + '2f55736572732f6f7665' => '.png', + '89504e47' => '.png', + '494433' => '.mp3', + 'fffb' => '.mp3', + 'fff3' => '.mp3', + '3026b2758e66cf11' => '.wma', + '52494646' => '.wav', + '57415645' => '.wav', + '41564920' => '.avi', + '000001ba' => '.mpg', + '000001b3' => '.mpg', + '2321414d52' => '.amr', + '25504446' => '.pdf', + ]; + + /** + * Return steam extension. + * + * @param string $stream + * + * @return string|false + */ + public static function getStreamExt($stream) + { + $ext = self::getExtBySignature($stream); + + try { + if (empty($ext) && is_readable($stream)) { + $stream = file_get_contents($stream); + } + } catch (\Exception $e) { + } + + $fileInfo = new finfo(FILEINFO_MIME); + + $mime = strstr($fileInfo->buffer($stream), ';', true); + + return isset(self::$extensionMap[$mime]) ? self::$extensionMap[$mime] : $ext; + } + + /** + * Get file extension by file header signature. + * + * @param string $stream + * + * @return string + */ + public static function getExtBySignature($stream) + { + $prefix = strval(bin2hex(mb_strcut($stream, 0, 10))); + + foreach (self::$signatures as $signature => $extension) { + if (0 === strpos($prefix, strval($signature))) { + return $extension; + } + } + + return ''; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Support/Helpers.php b/vendor/overtrue/wechat/src/Kernel/Support/Helpers.php new file mode 100644 index 0000000..08d4092 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Support/Helpers.php @@ -0,0 +1,131 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Support; + +/* + * helpers. + * + * @author overtrue + */ + +/** + * Generate a signature. + * + * @param array $attributes + * @param string $key + * @param string $encryptMethod + * + * @return string + */ +function generate_sign(array $attributes, $key, $encryptMethod = 'md5') +{ + ksort($attributes); + + $attributes['key'] = $key; + + return strtoupper(call_user_func_array($encryptMethod, [urldecode(http_build_query($attributes))])); +} + +/** + * @param string $signType + * @param string $secretKey + * + * @return \Closure|string + */ +function get_encrypt_method(string $signType, string $secretKey = '') +{ + if ('HMAC-SHA256' === $signType) { + return function ($str) use ($secretKey) { + return hash_hmac('sha256', $str, $secretKey); + }; + } + + return 'md5'; +} + +/** + * Get client ip. + * + * @return string + */ +function get_client_ip() +{ + if (!empty($_SERVER['REMOTE_ADDR'])) { + $ip = $_SERVER['REMOTE_ADDR']; + } else { + // for php-cli(phpunit etc.) + $ip = defined('PHPUNIT_RUNNING') ? '127.0.0.1' : gethostbyname(gethostname()); + } + + return filter_var($ip, FILTER_VALIDATE_IP) ?: '127.0.0.1'; +} + +/** + * Get current server ip. + * + * @return string + */ +function get_server_ip() +{ + if (!empty($_SERVER['SERVER_ADDR'])) { + $ip = $_SERVER['SERVER_ADDR']; + } elseif (!empty($_SERVER['SERVER_NAME'])) { + $ip = gethostbyname($_SERVER['SERVER_NAME']); + } else { + // for php-cli(phpunit etc.) + $ip = defined('PHPUNIT_RUNNING') ? '127.0.0.1' : gethostbyname(gethostname()); + } + + return filter_var($ip, FILTER_VALIDATE_IP) ?: '127.0.0.1'; +} + +/** + * Return current url. + * + * @return string + */ +function current_url() +{ + $protocol = 'http://'; + + if ((!empty($_SERVER['HTTPS']) && 'off' !== $_SERVER['HTTPS']) || ($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? 'http') === 'https') { + $protocol = 'https://'; + } + + return $protocol.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']; +} + +/** + * Return random string. + * + * @param string $length + * + * @return string + */ +function str_random($length) +{ + return Str::random($length); +} + +/** + * @param string $content + * @param string $publicKey + * + * @return string + */ +function rsa_public_encrypt($content, $publicKey) +{ + $encrypted = ''; + openssl_public_encrypt($content, $encrypted, openssl_pkey_get_public($publicKey), OPENSSL_PKCS1_OAEP_PADDING); + + return base64_encode($encrypted); +} diff --git a/vendor/overtrue/wechat/src/Kernel/Support/Str.php b/vendor/overtrue/wechat/src/Kernel/Support/Str.php new file mode 100644 index 0000000..3a51968 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Support/Str.php @@ -0,0 +1,193 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Support; + +use EasyWeChat\Kernel\Exceptions\RuntimeException; + +/** + * Class Str. + */ +class Str +{ + /** + * The cache of snake-cased words. + * + * @var array + */ + protected static $snakeCache = []; + + /** + * The cache of camel-cased words. + * + * @var array + */ + protected static $camelCache = []; + + /** + * The cache of studly-cased words. + * + * @var array + */ + protected static $studlyCache = []; + + /** + * Convert a value to camel case. + * + * @param string $value + * + * @return string + */ + public static function camel($value) + { + if (isset(static::$camelCache[$value])) { + return static::$camelCache[$value]; + } + + return static::$camelCache[$value] = lcfirst(static::studly($value)); + } + + /** + * Generate a more truly "random" alpha-numeric string. + * + * @param int $length + * + * @return string + * + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public static function random($length = 16) + { + $string = ''; + + while (($len = strlen($string)) < $length) { + $size = $length - $len; + + $bytes = static::randomBytes($size); + + $string .= substr(str_replace(['/', '+', '='], '', base64_encode($bytes)), 0, $size); + } + + return $string; + } + + /** + * Generate a more truly "random" bytes. + * + * @param int $length + * + * @return string + * + * @throws RuntimeException + * + * @codeCoverageIgnore + * + * @throws \Exception + */ + public static function randomBytes($length = 16) + { + if (function_exists('random_bytes')) { + $bytes = random_bytes($length); + } elseif (function_exists('openssl_random_pseudo_bytes')) { + $bytes = openssl_random_pseudo_bytes($length, $strong); + if (false === $bytes || false === $strong) { + throw new RuntimeException('Unable to generate random string.'); + } + } else { + throw new RuntimeException('OpenSSL extension is required for PHP 5 users.'); + } + + return $bytes; + } + + /** + * Generate a "random" alpha-numeric string. + * + * Should not be considered sufficient for cryptography, etc. + * + * @param int $length + * + * @return string + */ + public static function quickRandom($length = 16) + { + $pool = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + + return substr(str_shuffle(str_repeat($pool, $length)), 0, $length); + } + + /** + * Convert the given string to upper-case. + * + * @param string $value + * + * @return string + */ + public static function upper($value) + { + return mb_strtoupper($value); + } + + /** + * Convert the given string to title case. + * + * @param string $value + * + * @return string + */ + public static function title($value) + { + return mb_convert_case($value, MB_CASE_TITLE, 'UTF-8'); + } + + /** + * Convert a string to snake case. + * + * @param string $value + * @param string $delimiter + * + * @return string + */ + public static function snake($value, $delimiter = '_') + { + $key = $value.$delimiter; + + if (isset(static::$snakeCache[$key])) { + return static::$snakeCache[$key]; + } + + if (!ctype_lower($value)) { + $value = strtolower(preg_replace('/(.)(?=[A-Z])/', '$1'.$delimiter, $value)); + } + + return static::$snakeCache[$key] = trim($value, '_'); + } + + /** + * Convert a value to studly caps case. + * + * @param string $value + * + * @return string + */ + public static function studly($value) + { + $key = $value; + + if (isset(static::$studlyCache[$key])) { + return static::$studlyCache[$key]; + } + + $value = ucwords(str_replace(['-', '_'], ' ', $value)); + + return static::$studlyCache[$key] = str_replace(' ', '', $value); + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Support/XML.php b/vendor/overtrue/wechat/src/Kernel/Support/XML.php new file mode 100644 index 0000000..1d22bc5 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Support/XML.php @@ -0,0 +1,167 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Support; + +use SimpleXMLElement; + +/** + * Class XML. + */ +class XML +{ + /** + * XML to array. + * + * @param string $xml XML string + * + * @return array + */ + public static function parse($xml) + { + $backup = PHP_MAJOR_VERSION < 8 ? libxml_disable_entity_loader(true) : null; + + $result = self::normalize(simplexml_load_string(self::sanitize($xml), 'SimpleXMLElement', LIBXML_COMPACT | LIBXML_NOCDATA | LIBXML_NOBLANKS)); + + PHP_MAJOR_VERSION < 8 && libxml_disable_entity_loader($backup); + + return $result; + } + + /** + * XML encode. + * + * @param mixed $data + * @param string $root + * @param string $item + * @param string $attr + * @param string $id + * + * @return string + */ + public static function build( + $data, + $root = 'xml', + $item = 'item', + $attr = '', + $id = 'id' + ) { + if (is_array($attr)) { + $_attr = []; + + foreach ($attr as $key => $value) { + $_attr[] = "{$key}=\"{$value}\""; + } + + $attr = implode(' ', $_attr); + } + + $attr = trim($attr); + $attr = empty($attr) ? '' : " {$attr}"; + $xml = "<{$root}{$attr}>"; + $xml .= self::data2Xml($data, $item, $id); + $xml .= ""; + + return $xml; + } + + /** + * Build CDATA. + * + * @param string $string + * + * @return string + */ + public static function cdata($string) + { + return sprintf('', $string); + } + + /** + * Object to array. + * + * + * @param SimpleXMLElement $obj + * + * @return array + */ + protected static function normalize($obj) + { + $result = null; + + if (is_object($obj)) { + $obj = (array) $obj; + } + + if (is_array($obj)) { + foreach ($obj as $key => $value) { + $res = self::normalize($value); + if (('@attributes' === $key) && ($key)) { + $result = $res; // @codeCoverageIgnore + } else { + $result[$key] = $res; + } + } + } else { + $result = $obj; + } + + return $result; + } + + /** + * Array to XML. + * + * @param array $data + * @param string $item + * @param string $id + * + * @return string + */ + protected static function data2Xml($data, $item = 'item', $id = 'id') + { + $xml = $attr = ''; + + foreach ($data as $key => $val) { + if (is_numeric($key)) { + $id && $attr = " {$id}=\"{$key}\""; + $key = $item; + } + + $xml .= "<{$key}{$attr}>"; + + if ((is_array($val) || is_object($val))) { + $xml .= self::data2Xml((array) $val, $item, $id); + } else { + $xml .= is_numeric($val) ? $val : self::cdata($val); + } + + $xml .= ""; + } + + return $xml; + } + + /** + * Delete invalid characters in XML. + * + * @see https://www.w3.org/TR/2008/REC-xml-20081126/#charsets - XML charset range + * @see http://php.net/manual/en/regexp.reference.escape.php - escape in UTF-8 mode + * + * @param string $xml + * + * @return string + */ + public static function sanitize($xml) + { + return preg_replace('/[^\x{9}\x{A}\x{D}\x{20}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]+/u', '', $xml); + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Traits/HasAttributes.php b/vendor/overtrue/wechat/src/Kernel/Traits/HasAttributes.php new file mode 100644 index 0000000..238d9e2 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Traits/HasAttributes.php @@ -0,0 +1,251 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Traits; + +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; +use EasyWeChat\Kernel\Support\Arr; +use EasyWeChat\Kernel\Support\Str; + +/** + * Trait Attributes. + */ +trait HasAttributes +{ + /** + * @var array + */ + protected $attributes = []; + + /** + * @var bool + */ + protected $snakeable = true; + + /** + * Set Attributes. + * + * @param array $attributes + * + * @return $this + */ + public function setAttributes(array $attributes = []) + { + $this->attributes = $attributes; + + return $this; + } + + /** + * Set attribute. + * + * @param string $attribute + * @param string $value + * + * @return $this + */ + public function setAttribute($attribute, $value) + { + Arr::set($this->attributes, $attribute, $value); + + return $this; + } + + /** + * Get attribute. + * + * @param string $attribute + * @param mixed $default + * + * @return mixed + */ + public function getAttribute($attribute, $default = null) + { + return Arr::get($this->attributes, $attribute, $default); + } + + /** + * @param string $attribute + * + * @return bool + */ + public function isRequired($attribute) + { + return in_array($attribute, $this->getRequired(), true); + } + + /** + * @return array|mixed + */ + public function getRequired() + { + return property_exists($this, 'required') ? $this->required : []; + } + + /** + * Set attribute. + * + * @param string $attribute + * @param mixed $value + * + * @return $this + */ + public function with($attribute, $value) + { + $this->snakeable && $attribute = Str::snake($attribute); + + $this->setAttribute($attribute, $value); + + return $this; + } + + /** + * Override parent set() method. + * + * @param string $attribute + * @param mixed $value + * + * @return $this + */ + public function set($attribute, $value) + { + $this->setAttribute($attribute, $value); + + return $this; + } + + /** + * Override parent get() method. + * + * @param string $attribute + * @param mixed $default + * + * @return mixed + */ + public function get($attribute, $default = null) + { + return $this->getAttribute($attribute, $default); + } + + /** + * @param string $key + * + * @return bool + */ + public function has(string $key) + { + return Arr::has($this->attributes, $key); + } + + /** + * @param array $attributes + * + * @return $this + */ + public function merge(array $attributes) + { + $this->attributes = array_merge($this->attributes, $attributes); + + return $this; + } + + /** + * @param array|string $keys + * + * @return array + */ + public function only($keys) + { + return Arr::only($this->attributes, $keys); + } + + /** + * Return all items. + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + public function all() + { + $this->checkRequiredAttributes(); + + return $this->attributes; + } + + /** + * Magic call. + * + * @param string $method + * @param array $args + * + * @return $this + */ + public function __call($method, $args) + { + if (0 === stripos($method, 'with')) { + return $this->with(substr($method, 4), array_shift($args)); + } + + throw new \BadMethodCallException(sprintf('Method "%s" does not exists.', $method)); + } + + /** + * Magic get. + * + * @param string $property + * + * @return mixed + */ + public function __get($property) + { + return $this->get($property); + } + + /** + * Magic set. + * + * @param string $property + * @param mixed $value + * + * @return $this + */ + public function __set($property, $value) + { + return $this->with($property, $value); + } + + /** + * Whether or not an data exists by key. + * + * @param string $key + * + * @return bool + */ + public function __isset($key) + { + return isset($this->attributes[$key]); + } + + /** + * Check required attributes. + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + protected function checkRequiredAttributes() + { + foreach ($this->getRequired() as $attribute) { + if (is_null($this->get($attribute))) { + throw new InvalidArgumentException(sprintf('"%s" cannot be empty.', $attribute)); + } + } + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Traits/HasHttpRequests.php b/vendor/overtrue/wechat/src/Kernel/Traits/HasHttpRequests.php new file mode 100644 index 0000000..9bf0a02 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Traits/HasHttpRequests.php @@ -0,0 +1,231 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Traits; + +use GuzzleHttp\Client; +use GuzzleHttp\ClientInterface; +use GuzzleHttp\HandlerStack; +use Psr\Http\Message\ResponseInterface; + +/** + * Trait HasHttpRequests. + * + * @author overtrue + */ +trait HasHttpRequests +{ + use ResponseCastable; + + /** + * @var \GuzzleHttp\ClientInterface + */ + protected $httpClient; + + /** + * @var array + */ + protected $middlewares = []; + + /** + * @var \GuzzleHttp\HandlerStack + */ + protected $handlerStack; + + /** + * @var array + */ + protected static $defaults = [ + 'curl' => [ + CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4, + ], + ]; + + /** + * Set guzzle default settings. + * + * @param array $defaults + */ + public static function setDefaultOptions($defaults = []) + { + self::$defaults = $defaults; + } + + /** + * Return current guzzle default settings. + * + * @return array + */ + public static function getDefaultOptions(): array + { + return self::$defaults; + } + + /** + * Set GuzzleHttp\Client. + * + * @param \GuzzleHttp\ClientInterface $httpClient + * + * @return $this + */ + public function setHttpClient(ClientInterface $httpClient) + { + $this->httpClient = $httpClient; + + return $this; + } + + /** + * Return GuzzleHttp\ClientInterface instance. + * + * @return ClientInterface + */ + public function getHttpClient(): ClientInterface + { + if (!($this->httpClient instanceof ClientInterface)) { + if (property_exists($this, 'app') && $this->app['http_client']) { + $this->httpClient = $this->app['http_client']; + } else { + $this->httpClient = new Client(['handler' => HandlerStack::create($this->getGuzzleHandler())]); + } + } + + return $this->httpClient; + } + + /** + * Add a middleware. + * + * @param callable $middleware + * @param string $name + * + * @return $this + */ + public function pushMiddleware(callable $middleware, string $name = null) + { + if (!is_null($name)) { + $this->middlewares[$name] = $middleware; + } else { + array_push($this->middlewares, $middleware); + } + + return $this; + } + + /** + * Return all middlewares. + * + * @return array + */ + public function getMiddlewares(): array + { + return $this->middlewares; + } + + /** + * Make a request. + * + * @param string $url + * @param string $method + * @param array $options + * + * @return \Psr\Http\Message\ResponseInterface + * + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function request($url, $method = 'GET', $options = []): ResponseInterface + { + $method = strtoupper($method); + + $options = array_merge(self::$defaults, $options, ['handler' => $this->getHandlerStack()]); + + $options = $this->fixJsonIssue($options); + + if (property_exists($this, 'baseUri') && !is_null($this->baseUri)) { + $options['base_uri'] = $this->baseUri; + } + + $response = $this->getHttpClient()->request($method, $url, $options); + $response->getBody()->rewind(); + + return $response; + } + + /** + * @param \GuzzleHttp\HandlerStack $handlerStack + * + * @return $this + */ + public function setHandlerStack(HandlerStack $handlerStack) + { + $this->handlerStack = $handlerStack; + + return $this; + } + + /** + * Build a handler stack. + * + * @return \GuzzleHttp\HandlerStack + */ + public function getHandlerStack(): HandlerStack + { + if ($this->handlerStack) { + return $this->handlerStack; + } + + $this->handlerStack = HandlerStack::create($this->getGuzzleHandler()); + + foreach ($this->middlewares as $name => $middleware) { + $this->handlerStack->push($middleware, $name); + } + + return $this->handlerStack; + } + + /** + * @param array $options + * + * @return array + */ + protected function fixJsonIssue(array $options): array + { + if (isset($options['json']) && is_array($options['json'])) { + $options['headers'] = array_merge($options['headers'] ?? [], ['Content-Type' => 'application/json']); + + if (empty($options['json'])) { + $options['body'] = \GuzzleHttp\json_encode($options['json'], JSON_FORCE_OBJECT); + } else { + $options['body'] = \GuzzleHttp\json_encode($options['json'], JSON_UNESCAPED_UNICODE); + } + + unset($options['json']); + } + + return $options; + } + + /** + * Get guzzle handler. + * + * @return callable + */ + protected function getGuzzleHandler() + { + if (property_exists($this, 'app') && isset($this->app['guzzle_handler'])) { + return is_string($handler = $this->app->raw('guzzle_handler')) + ? new $handler() + : $handler; + } + + return \GuzzleHttp\choose_handler(); + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Traits/InteractsWithCache.php b/vendor/overtrue/wechat/src/Kernel/Traits/InteractsWithCache.php new file mode 100644 index 0000000..6ee9e25 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Traits/InteractsWithCache.php @@ -0,0 +1,105 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Traits; + +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; +use EasyWeChat\Kernel\ServiceContainer; +use Psr\Cache\CacheItemPoolInterface; +use Psr\SimpleCache\CacheInterface as SimpleCacheInterface; +use Symfony\Component\Cache\Adapter\FilesystemAdapter; +use Symfony\Component\Cache\Psr16Cache; +use Symfony\Component\Cache\Simple\FilesystemCache; + +/** + * Trait InteractsWithCache. + * + * @author overtrue + */ +trait InteractsWithCache +{ + /** + * @var \Psr\SimpleCache\CacheInterface + */ + protected $cache; + + /** + * Get cache instance. + * + * @return \Psr\SimpleCache\CacheInterface + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + public function getCache() + { + if ($this->cache) { + return $this->cache; + } + + if (property_exists($this, 'app') && $this->app instanceof ServiceContainer && isset($this->app['cache'])) { + $this->setCache($this->app['cache']); + + // Fix PHPStan error + assert($this->cache instanceof \Psr\SimpleCache\CacheInterface); + + return $this->cache; + } + + return $this->cache = $this->createDefaultCache(); + } + + /** + * Set cache instance. + * + * @param \Psr\SimpleCache\CacheInterface|\Psr\Cache\CacheItemPoolInterface $cache + * + * @return $this + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + public function setCache($cache) + { + if (empty(\array_intersect([SimpleCacheInterface::class, CacheItemPoolInterface::class], \class_implements($cache)))) { + throw new InvalidArgumentException(\sprintf('The cache instance must implements %s or %s interface.', SimpleCacheInterface::class, CacheItemPoolInterface::class)); + } + + if ($cache instanceof CacheItemPoolInterface) { + if (!$this->isSymfony43OrHigher()) { + throw new InvalidArgumentException(sprintf('The cache instance must implements %s', SimpleCacheInterface::class)); + } + $cache = new Psr16Cache($cache); + } + + $this->cache = $cache; + + return $this; + } + + /** + * @return \Psr\SimpleCache\CacheInterface + */ + protected function createDefaultCache() + { + if ($this->isSymfony43OrHigher()) { + return new Psr16Cache(new FilesystemAdapter('easywechat', 1500)); + } + + return new FilesystemCache(); + } + + /** + * @return bool + */ + protected function isSymfony43OrHigher(): bool + { + return \class_exists('Symfony\Component\Cache\Psr16Cache'); + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Traits/Observable.php b/vendor/overtrue/wechat/src/Kernel/Traits/Observable.php new file mode 100644 index 0000000..ba46fd9 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Traits/Observable.php @@ -0,0 +1,285 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Traits; + +use EasyWeChat\Kernel\Clauses\Clause; +use EasyWeChat\Kernel\Contracts\EventHandlerInterface; +use EasyWeChat\Kernel\Decorators\FinallyResult; +use EasyWeChat\Kernel\Decorators\TerminateResult; +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; +use EasyWeChat\Kernel\ServiceContainer; + +/** + * Trait Observable. + * + * @author overtrue + */ +trait Observable +{ + /** + * @var array + */ + protected $handlers = []; + + /** + * @var array + */ + protected $clauses = []; + + /** + * @param \Closure|EventHandlerInterface|callable|string $handler + * @param \Closure|EventHandlerInterface|callable|string $condition + * + * @return \EasyWeChat\Kernel\Clauses\Clause + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \ReflectionException + */ + public function push($handler, $condition = '*') + { + list($handler, $condition) = $this->resolveHandlerAndCondition($handler, $condition); + + if (!isset($this->handlers[$condition])) { + $this->handlers[$condition] = []; + } + + array_push($this->handlers[$condition], $handler); + + return $this->newClause($handler); + } + + /** + * @param array $handlers + * + * @return $this + */ + public function setHandlers(array $handlers = []) + { + $this->handlers = $handlers; + + return $this; + } + + /** + * @param \Closure|EventHandlerInterface|string $handler + * @param \Closure|EventHandlerInterface|string $condition + * + * @return \EasyWeChat\Kernel\Clauses\Clause + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \ReflectionException + */ + public function unshift($handler, $condition = '*') + { + list($handler, $condition) = $this->resolveHandlerAndCondition($handler, $condition); + + if (!isset($this->handlers[$condition])) { + $this->handlers[$condition] = []; + } + + array_unshift($this->handlers[$condition], $handler); + + return $this->newClause($handler); + } + + /** + * @param string $condition + * @param \Closure|EventHandlerInterface|string $handler + * + * @return \EasyWeChat\Kernel\Clauses\Clause + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \ReflectionException + */ + public function observe($condition, $handler) + { + return $this->push($handler, $condition); + } + + /** + * @param string $condition + * @param \Closure|EventHandlerInterface|string $handler + * + * @return \EasyWeChat\Kernel\Clauses\Clause + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \ReflectionException + */ + public function on($condition, $handler) + { + return $this->push($handler, $condition); + } + + /** + * @param string|int $event + * @param mixed ...$payload + * + * @return mixed|null + */ + public function dispatch($event, $payload) + { + return $this->notify($event, $payload); + } + + /** + * @param string|int $event + * @param mixed ...$payload + * + * @return mixed|null + */ + public function notify($event, $payload) + { + $result = null; + + foreach ($this->handlers as $condition => $handlers) { + if ('*' === $condition || ($condition & $event) === $event) { + foreach ($handlers as $handler) { + if ($clause = $this->clauses[$this->getHandlerHash($handler)] ?? null) { + if ($clause->intercepted($payload)) { + continue; + } + } + + $response = $this->callHandler($handler, $payload); + + switch (true) { + case $response instanceof TerminateResult: + return $response->content; + case true === $response: + continue 2; + case false === $response: + break 3; + case !empty($response) && !($result instanceof FinallyResult): + $result = $response; + } + } + } + } + + return $result instanceof FinallyResult ? $result->content : $result; + } + + /** + * @return array + */ + public function getHandlers() + { + return $this->handlers; + } + + /** + * @param mixed $handler + * + * @return \EasyWeChat\Kernel\Clauses\Clause + */ + protected function newClause($handler): Clause + { + return $this->clauses[$this->getHandlerHash($handler)] = new Clause(); + } + + /** + * @param mixed $handler + * + * @return string + */ + protected function getHandlerHash($handler) + { + if (is_string($handler)) { + return $handler; + } + + if (is_array($handler)) { + return is_string($handler[0]) + ? $handler[0].'::'.$handler[1] + : get_class($handler[0]).$handler[1]; + } + + return spl_object_hash($handler); + } + + /** + * @param callable $handler + * @param mixed $payload + * + * @return mixed + */ + protected function callHandler(callable $handler, $payload) + { + try { + return call_user_func_array($handler, [$payload]); + } catch (\Exception $e) { + if (property_exists($this, 'app') && $this->app instanceof ServiceContainer) { + $this->app['logger']->error($e->getCode().': '.$e->getMessage(), [ + 'code' => $e->getCode(), + 'message' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + ]); + } + } + } + + /** + * @param mixed $handler + * + * @return \Closure + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \ReflectionException + */ + protected function makeClosure($handler) + { + if (is_callable($handler)) { + return $handler; + } + + if (is_string($handler) && '*' !== $handler) { + if (!class_exists($handler)) { + throw new InvalidArgumentException(sprintf('Class "%s" not exists.', $handler)); + } + + if (!in_array(EventHandlerInterface::class, (new \ReflectionClass($handler))->getInterfaceNames(), true)) { + throw new InvalidArgumentException(sprintf('Class "%s" not an instance of "%s".', $handler, EventHandlerInterface::class)); + } + + return function ($payload) use ($handler) { + return (new $handler($this->app ?? null))->handle($payload); + }; + } + + if ($handler instanceof EventHandlerInterface) { + return function () use ($handler) { + return $handler->handle(...func_get_args()); + }; + } + + throw new InvalidArgumentException('No valid handler is found in arguments.'); + } + + /** + * @param mixed $handler + * @param mixed $condition + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \ReflectionException + */ + protected function resolveHandlerAndCondition($handler, $condition): array + { + if (is_int($handler) || (is_string($handler) && !class_exists($handler))) { + list($handler, $condition) = [$condition, $handler]; + } + + return [$this->makeClosure($handler), $condition]; + } +} diff --git a/vendor/overtrue/wechat/src/Kernel/Traits/ResponseCastable.php b/vendor/overtrue/wechat/src/Kernel/Traits/ResponseCastable.php new file mode 100644 index 0000000..5492bd4 --- /dev/null +++ b/vendor/overtrue/wechat/src/Kernel/Traits/ResponseCastable.php @@ -0,0 +1,93 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Traits; + +use EasyWeChat\Kernel\Contracts\Arrayable; +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; +use EasyWeChat\Kernel\Exceptions\InvalidConfigException; +use EasyWeChat\Kernel\Http\Response; +use EasyWeChat\Kernel\Support\Collection; +use Psr\Http\Message\ResponseInterface; + +/** + * Trait ResponseCastable. + * + * @author overtrue + */ +trait ResponseCastable +{ + /** + * @param \Psr\Http\Message\ResponseInterface $response + * @param string|null $type + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + protected function castResponseToType(ResponseInterface $response, $type = null) + { + $response = Response::buildFromPsrResponse($response); + $response->getBody()->rewind(); + + switch ($type ?? 'array') { + case 'collection': + return $response->toCollection(); + case 'array': + return $response->toArray(); + case 'object': + return $response->toObject(); + case 'raw': + return $response; + default: + if (!is_subclass_of($type, Arrayable::class)) { + throw new InvalidConfigException(sprintf('Config key "response_type" classname must be an instanceof %s', Arrayable::class)); + } + + return new $type($response); + } + } + + /** + * @param mixed $response + * @param string|null $type + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + protected function detectAndCastResponseToType($response, $type = null) + { + switch (true) { + case $response instanceof ResponseInterface: + $response = Response::buildFromPsrResponse($response); + + break; + case $response instanceof Arrayable: + $response = new Response(200, [], json_encode($response->toArray())); + + break; + case ($response instanceof Collection) || is_array($response) || is_object($response): + $response = new Response(200, [], json_encode($response)); + + break; + case is_scalar($response): + $response = new Response(200, [], (string) $response); + + break; + default: + throw new InvalidArgumentException(sprintf('Unsupported response type "%s"', gettype($response))); + } + + return $this->castResponseToType($response, $type); + } +} diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Application.php b/vendor/overtrue/wechat/src/MicroMerchant/Application.php new file mode 100644 index 0000000..cd455fb --- /dev/null +++ b/vendor/overtrue/wechat/src/MicroMerchant/Application.php @@ -0,0 +1,173 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MicroMerchant; + +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; +use EasyWeChat\Kernel\ServiceContainer; +use EasyWeChat\Kernel\Support; +use EasyWeChat\MicroMerchant\Kernel\Exceptions\InvalidSignException; + +/** + * Class Application. + * + * @author liuml + * + * @property \EasyWeChat\MicroMerchant\Certficates\Client $certficates + * @property \EasyWeChat\MicroMerchant\Material\Client $material + * @property \EasyWeChat\MicroMerchant\MerchantConfig\Client $merchantConfig + * @property \EasyWeChat\MicroMerchant\Withdraw\Client $withdraw + * @property \EasyWeChat\MicroMerchant\Media\Client $media + * + * @method mixed submitApplication(array $params) + * @method mixed getStatus(string $applymentId, string $businessCode = '') + * @method mixed upgrade(array $params) + * @method mixed getUpgradeStatus(string $subMchId = '') + */ +class Application extends ServiceContainer +{ + /** + * @var array + */ + protected $providers = [ + // Base services + Base\ServiceProvider::class, + Certficates\ServiceProvider::class, + MerchantConfig\ServiceProvider::class, + Material\ServiceProvider::class, + Withdraw\ServiceProvider::class, + Media\ServiceProvider::class, + ]; + + /** + * @var array + */ + protected $defaultConfig = [ + 'http' => [ + 'base_uri' => 'https://api.mch.weixin.qq.com/', + ], + 'log' => [ + 'default' => 'dev', // 默认使用的 channel,生产环境可以改为下面的 prod + 'channels' => [ + // 测试环境 + 'dev' => [ + 'driver' => 'single', + 'path' => '/tmp/easywechat.log', + 'level' => 'debug', + ], + // 生产环境 + 'prod' => [ + 'driver' => 'daily', + 'path' => '/tmp/easywechat.log', + 'level' => 'info', + ], + ], + ], + ]; + + /** + * @return string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + public function getKey() + { + $key = $this['config']->key; + + if (empty($key)) { + throw new InvalidArgumentException('config key connot be empty.'); + } + + if (32 !== strlen($key)) { + throw new InvalidArgumentException(sprintf("'%s' should be 32 chars length.", $key)); + } + + return $key; + } + + /** + * set sub-mch-id and appid. + * + * @param string $subMchId Identification Number of Small and Micro Businessmen Reported by Service Providers + * @param string $appId Public Account ID of Service Provider + * + * @return $this + */ + public function setSubMchId(string $subMchId, string $appId = '') + { + $this['config']->set('sub_mch_id', $subMchId); + if ($appId) { + $this['config']->set('appid', $appId); + } + + return $this; + } + + /** + * setCertificate. + * + * @param string $certificate + * @param string $serialNo + * + * @return $this + */ + public function setCertificate(string $certificate, string $serialNo) + { + $this['config']->set('certificate', $certificate); + $this['config']->set('serial_no', $serialNo); + + return $this; + } + + /** + * Returning true indicates that the verification is successful, + * returning false indicates that the signature field does not exist or is empty, + * and if the signature verification is wrong, the InvalidSignException will be thrown directly. + * + * @param array $data + * + * @return bool + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\InvalidSignException + */ + public function verifySignature(array $data) + { + if (!isset($data['sign']) || empty($data['sign'])) { + return false; + } + + $sign = $data['sign']; + unset($data['sign']); + + $signType = strlen($sign) > 32 ? 'HMAC-SHA256' : 'MD5'; + $secretKey = $this->getKey(); + + $encryptMethod = Support\get_encrypt_method($signType, $secretKey); + + if (Support\generate_sign($data, $secretKey, $encryptMethod) === $sign) { + return true; + } + + throw new InvalidSignException('return value signature verification error'); + } + + /** + * @param string $name + * @param array $arguments + * + * @return mixed + */ + public function __call($name, $arguments) + { + return call_user_func_array([$this['base'], $name], $arguments); + } +} diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Base/Client.php b/vendor/overtrue/wechat/src/MicroMerchant/Base/Client.php new file mode 100644 index 0000000..ba92756 --- /dev/null +++ b/vendor/overtrue/wechat/src/MicroMerchant/Base/Client.php @@ -0,0 +1,126 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MicroMerchant\Base; + +use EasyWeChat\MicroMerchant\Kernel\BaseClient; + +/** + * Class Client. + * + * @author liuml + * @DateTime 2019-05-30 14:19 + */ +class Client extends BaseClient +{ + /** + * apply to settle in to become a small micro merchant. + * + * @param array $params + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\EncryptException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function submitApplication(array $params) + { + $params = $this->processParams(array_merge($params, [ + 'version' => '3.0', + 'cert_sn' => '', + 'sign_type' => 'HMAC-SHA256', + 'nonce_str' => uniqid('micro'), + ])); + + return $this->safeRequest('applyment/micro/submit', $params); + } + + /** + * query application status. + * + * @param string $applymentId + * @param string $businessCode + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getStatus(string $applymentId, string $businessCode = '') + { + if (!empty($applymentId)) { + $params = [ + 'applyment_id' => $applymentId, + ]; + } else { + $params = [ + 'business_code' => $businessCode, + ]; + } + + $params = array_merge($params, [ + 'version' => '1.0', + 'sign_type' => 'HMAC-SHA256', + 'nonce_str' => uniqid('micro'), + ]); + + return $this->safeRequest('applyment/micro/getstate', $params); + } + + /** + * merchant upgrade api. + * + * @param array $params + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\EncryptException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function upgrade(array $params) + { + $params['sub_mch_id'] = $params['sub_mch_id'] ?? $this->app['config']->sub_mch_id; + $params = $this->processParams(array_merge($params, [ + 'version' => '1.0', + 'cert_sn' => '', + 'sign_type' => 'HMAC-SHA256', + 'nonce_str' => uniqid('micro'), + ])); + + return $this->safeRequest('applyment/micro/submitupgrade', $params); + } + + /** + * get upgrade status. + * + * @param string $subMchId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getUpgradeStatus(string $subMchId = '') + { + return $this->safeRequest('applyment/micro/getupgradestate', [ + 'version' => '1.0', + 'sign_type' => 'HMAC-SHA256', + 'sub_mch_id' => $subMchId ?: $this->app['config']->sub_mch_id, + 'nonce_str' => uniqid('micro'), + ]); + } +} diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Base/ServiceProvider.php b/vendor/overtrue/wechat/src/MicroMerchant/Base/ServiceProvider.php new file mode 100644 index 0000000..db2f056 --- /dev/null +++ b/vendor/overtrue/wechat/src/MicroMerchant/Base/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MicroMerchant\Base; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['base'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Certficates/Client.php b/vendor/overtrue/wechat/src/MicroMerchant/Certficates/Client.php new file mode 100644 index 0000000..62a6cf5 --- /dev/null +++ b/vendor/overtrue/wechat/src/MicroMerchant/Certficates/Client.php @@ -0,0 +1,93 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MicroMerchant\Certficates; + +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; +use EasyWeChat\MicroMerchant\Kernel\BaseClient; +use EasyWeChat\MicroMerchant\Kernel\Exceptions\InvalidExtensionException; + +/** + * Class Client. + * + * @author liuml + * @DateTime 2019-05-30 14:19 + */ +class Client extends BaseClient +{ + /** + * get certficates. + * + * @param bool $returnRaw + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\InvalidExtensionException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get(bool $returnRaw = false) + { + $params = [ + 'sign_type' => 'HMAC-SHA256', + 'nonce_str' => uniqid('micro'), + ]; + + if (true === $returnRaw) { + return $this->requestRaw('risk/getcertficates', $params); + } + /** @var array $response */ + $response = $this->requestArray('risk/getcertficates', $params); + + if ('SUCCESS' !== $response['return_code']) { + throw new InvalidArgumentException(sprintf('Failed to get certificate. return_code_msg: "%s" .', $response['return_code'].'('.$response['return_msg'].')')); + } + if ('SUCCESS' !== $response['result_code']) { + throw new InvalidArgumentException(sprintf('Failed to get certificate. result_err_code_desc: "%s" .', $response['result_code'].'('.$response['err_code'].'['.$response['err_code_desc'].'])')); + } + $certificates = \GuzzleHttp\json_decode($response['certificates'], true)['data'][0]; + $ciphertext = $this->decrypt($certificates['encrypt_certificate']); + unset($certificates['encrypt_certificate']); + $certificates['certificates'] = $ciphertext; + + return $certificates; + } + + /** + * decrypt ciphertext. + * + * @param array $encryptCertificate + * + * @return string + * + * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\InvalidExtensionException + */ + public function decrypt(array $encryptCertificate) + { + if (false === extension_loaded('sodium')) { + throw new InvalidExtensionException('sodium extension is not installed,Reference link https://php.net/manual/zh/book.sodium.php'); + } + + if (false === sodium_crypto_aead_aes256gcm_is_available()) { + throw new InvalidExtensionException('aes256gcm is not currently supported'); + } + + // sodium_crypto_aead_aes256gcm_decrypt function needs to open libsodium extension. + // https://www.php.net/manual/zh/function.sodium-crypto-aead-aes256gcm-decrypt.php + return sodium_crypto_aead_aes256gcm_decrypt( + base64_decode($encryptCertificate['ciphertext'], true), + $encryptCertificate['associated_data'], + $encryptCertificate['nonce'], + $this->app['config']->apiv3_key + ); + } +} diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Certficates/ServiceProvider.php b/vendor/overtrue/wechat/src/MicroMerchant/Certficates/ServiceProvider.php new file mode 100644 index 0000000..2e88b7e --- /dev/null +++ b/vendor/overtrue/wechat/src/MicroMerchant/Certficates/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MicroMerchant\Certficates; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['certficates'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Kernel/BaseClient.php b/vendor/overtrue/wechat/src/MicroMerchant/Kernel/BaseClient.php new file mode 100644 index 0000000..6f49542 --- /dev/null +++ b/vendor/overtrue/wechat/src/MicroMerchant/Kernel/BaseClient.php @@ -0,0 +1,256 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MicroMerchant\Kernel; + +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; +use EasyWeChat\Kernel\Support; +use EasyWeChat\MicroMerchant\Application; +use EasyWeChat\MicroMerchant\Kernel\Exceptions\EncryptException; +use EasyWeChat\Payment\Kernel\BaseClient as PaymentBaseClient; + +/** + * Class BaseClient. + * + * @author liuml + * @DateTime 2019-07-10 12:06 + */ +class BaseClient extends PaymentBaseClient +{ + /** + * @var string + */ + protected $certificates; + + /** + * BaseClient constructor. + * + * @param \EasyWeChat\MicroMerchant\Application $app + */ + public function __construct(Application $app) + { + $this->app = $app; + + $this->setHttpClient($this->app['http_client']); + } + + /** + * Extra request params. + * + * @return array + */ + protected function prepends() + { + return []; + } + + /** + * httpUpload. + * + * @param string $url + * @param array $files + * @param array $form + * @param array $query + * @param bool $returnResponse + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\InvalidSignException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function httpUpload(string $url, array $files = [], array $form = [], array $query = [], $returnResponse = false) + { + $multipart = []; + + foreach ($files as $name => $path) { + $multipart[] = [ + 'name' => $name, + 'contents' => fopen($path, 'r'), + ]; + } + + $base = [ + 'mch_id' => $this->app['config']['mch_id'], + ]; + + $form = array_merge($base, $form); + + $form['sign'] = $this->getSign($form); + + foreach ($form as $name => $contents) { + $multipart[] = compact('name', 'contents'); + } + + $options = [ + 'query' => $query, + 'multipart' => $multipart, + 'connect_timeout' => 30, + 'timeout' => 30, + 'read_timeout' => 30, + 'cert' => $this->app['config']->get('cert_path'), + 'ssl_key' => $this->app['config']->get('key_path'), + ]; + + $this->pushMiddleware($this->logMiddleware(), 'log'); + + $response = $this->performRequest($url, 'POST', $options); + + $result = $returnResponse ? $response : $this->castResponseToType($response, $this->app->config->get('response_type')); + // auto verify signature + if ($returnResponse || 'array' !== ($this->app->config->get('response_type') ?? 'array')) { + $this->app->verifySignature($this->castResponseToType($response, 'array')); + } else { + $this->app->verifySignature($result); + } + + return $result; + } + + /** + * request. + * + * @param string $endpoint + * @param array $params + * @param string $method + * @param array $options + * @param bool $returnResponse + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\InvalidSignException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function request(string $endpoint, array $params = [], $method = 'post', array $options = [], $returnResponse = false) + { + $base = [ + 'mch_id' => $this->app['config']['mch_id'], + ]; + + $params = array_merge($base, $this->prepends(), $params); + $params['sign'] = $this->getSign($params); + $options = array_merge([ + 'body' => Support\XML::build($params), + ], $options); + + $this->pushMiddleware($this->logMiddleware(), 'log'); + $response = $this->performRequest($endpoint, $method, $options); + $result = $returnResponse ? $response : $this->castResponseToType($response, $this->app->config->get('response_type')); + // auto verify signature + if ($returnResponse || 'array' !== ($this->app->config->get('response_type') ?? 'array')) { + $this->app->verifySignature($this->castResponseToType($response, 'array')); + } else { + $this->app->verifySignature($result); + } + + return $result; + } + + /** + * processing parameters contain fields that require sensitive information encryption. + * + * @param array $params + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\EncryptException + */ + protected function processParams(array $params) + { + $serial_no = $this->app['config']->get('serial_no'); + if (null === $serial_no) { + throw new InvalidArgumentException('config serial_no connot be empty.'); + } + + $params['cert_sn'] = $serial_no; + $sensitive_fields = $this->getSensitiveFieldsName(); + foreach ($params as $k => $v) { + if (in_array($k, $sensitive_fields, true)) { + $params[$k] = $this->encryptSensitiveInformation($v); + } + } + + return $params; + } + + /** + * To id card, mobile phone number and other fields sensitive information encryption. + * + * @param string $string + * + * @return string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\EncryptException + */ + protected function encryptSensitiveInformation(string $string) + { + $certificates = $this->app['config']->get('certificate'); + if (null === $certificates) { + throw new InvalidArgumentException('config certificate connot be empty.'); + } + + $encrypted = ''; + $publicKeyResource = openssl_get_publickey($certificates); + $f = openssl_public_encrypt($string, $encrypted, $publicKeyResource, OPENSSL_NO_PADDING); + openssl_free_key($publicKeyResource); + if ($f) { + return base64_encode($encrypted); + } + + throw new EncryptException('Encryption of sensitive information failed'); + } + + /** + * get sensitive fields name. + * + * @return array + */ + protected function getSensitiveFieldsName() + { + return [ + 'id_card_name', + 'id_card_number', + 'account_name', + 'account_number', + 'contact', + 'contact_phone', + 'contact_email', + 'legal_person', + 'mobile_phone', + 'email', + ]; + } + + /** + * getSign. + * + * @param array $params + * + * @return string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + protected function getSign(array $params) + { + $params = array_filter($params); + + $key = $this->app->getKey(); + + $encryptMethod = Support\get_encrypt_method(Support\Arr::get($params, 'sign_type', 'MD5'), $key); + + return Support\generate_sign($params, $key, $encryptMethod); + } +} diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/EncryptException.php b/vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/EncryptException.php new file mode 100644 index 0000000..dd874ae --- /dev/null +++ b/vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/EncryptException.php @@ -0,0 +1,23 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MicroMerchant\Kernel\Exceptions; + +use EasyWeChat\Kernel\Exceptions\Exception; + +/** + * Class EncryptException. + * + * @author liuml + */ +class EncryptException extends Exception +{ +} diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/InvalidExtensionException.php b/vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/InvalidExtensionException.php new file mode 100644 index 0000000..41e21cf --- /dev/null +++ b/vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/InvalidExtensionException.php @@ -0,0 +1,23 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MicroMerchant\Kernel\Exceptions; + +use EasyWeChat\Kernel\Exceptions\Exception; + +/** + * Class InvalidExtensionException. + * + * @author liuml + */ +class InvalidExtensionException extends Exception +{ +} diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/InvalidSignException.php b/vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/InvalidSignException.php new file mode 100644 index 0000000..d09d92b --- /dev/null +++ b/vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/InvalidSignException.php @@ -0,0 +1,23 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MicroMerchant\Kernel\Exceptions; + +use EasyWeChat\Kernel\Exceptions\Exception; + +/** + * Class InvalidSignException. + * + * @author liuml + */ +class InvalidSignException extends Exception +{ +} diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Material/Client.php b/vendor/overtrue/wechat/src/MicroMerchant/Material/Client.php new file mode 100644 index 0000000..ee1038a --- /dev/null +++ b/vendor/overtrue/wechat/src/MicroMerchant/Material/Client.php @@ -0,0 +1,73 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MicroMerchant\Material; + +use EasyWeChat\MicroMerchant\Kernel\BaseClient; + +/** + * Class Client. + * + * @author liuml + * @DateTime 2019-05-30 14:19 + */ +class Client extends BaseClient +{ + /** + * update settlement card. + * + * @param array $params + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\EncryptException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function setSettlementCard(array $params) + { + $params['sub_mch_id'] = $params['sub_mch_id'] ?? $this->app['config']->sub_mch_id; + $params = $this->processParams(array_merge($params, [ + 'version' => '1.0', + 'cert_sn' => '', + 'sign_type' => 'HMAC-SHA256', + 'nonce_str' => uniqid('micro'), + ])); + + return $this->safeRequest('applyment/micro/modifyarchives', $params); + } + + /** + * update contact info. + * + * @param array $params + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\EncryptException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function updateContact(array $params) + { + $params['sub_mch_id'] = $params['sub_mch_id'] ?? $this->app['config']->sub_mch_id; + $params = $this->processParams(array_merge($params, [ + 'version' => '1.0', + 'cert_sn' => '', + 'sign_type' => 'HMAC-SHA256', + 'nonce_str' => uniqid('micro'), + ])); + + return $this->safeRequest('applyment/micro/modifycontactinfo', $params); + } +} diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Material/ServiceProvider.php b/vendor/overtrue/wechat/src/MicroMerchant/Material/ServiceProvider.php new file mode 100644 index 0000000..dec5af7 --- /dev/null +++ b/vendor/overtrue/wechat/src/MicroMerchant/Material/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MicroMerchant\Material; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['material'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Media/Client.php b/vendor/overtrue/wechat/src/MicroMerchant/Media/Client.php new file mode 100644 index 0000000..fde755b --- /dev/null +++ b/vendor/overtrue/wechat/src/MicroMerchant/Media/Client.php @@ -0,0 +1,49 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MicroMerchant\Media; + +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; +use EasyWeChat\MicroMerchant\Kernel\BaseClient; + +/** + * Class Client. + * + * @author liuml + * @DateTime 2019-06-10 14:50 + */ +class Client extends BaseClient +{ + /** + * Upload material. + * + * @param string $path + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\InvalidSignException + */ + public function upload(string $path) + { + if (!file_exists($path) || !is_readable($path)) { + throw new InvalidArgumentException(sprintf("File does not exist, or the file is unreadable: '%s'", $path)); + } + + $form = [ + 'media_hash' => strtolower(md5_file($path)), + 'sign_type' => 'HMAC-SHA256', + ]; + + return $this->httpUpload('secapi/mch/uploadmedia', ['media' => $path], $form); + } +} diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Media/ServiceProvider.php b/vendor/overtrue/wechat/src/MicroMerchant/Media/ServiceProvider.php new file mode 100644 index 0000000..9d46a84 --- /dev/null +++ b/vendor/overtrue/wechat/src/MicroMerchant/Media/ServiceProvider.php @@ -0,0 +1,44 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * ServiceProvider.php. + * + * This file is part of the wechat. + * + * (c) overtrue + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MicroMerchant\Media; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['media'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MicroMerchant/MerchantConfig/Client.php b/vendor/overtrue/wechat/src/MicroMerchant/MerchantConfig/Client.php new file mode 100644 index 0000000..c8573af --- /dev/null +++ b/vendor/overtrue/wechat/src/MicroMerchant/MerchantConfig/Client.php @@ -0,0 +1,134 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MicroMerchant\MerchantConfig; + +use EasyWeChat\MicroMerchant\Kernel\BaseClient; + +/** + * Class Client. + * + * @author liuml + * @DateTime 2019-05-30 14:19 + */ +class Client extends BaseClient +{ + /** + * Service providers configure recommendation functions for small and micro businesses. + * + * @param string $subAppId + * @param string $subscribeAppId + * @param string $receiptAppId + * @param string $subMchId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function setFollowConfig(string $subAppId, string $subscribeAppId, string $receiptAppId = '', string $subMchId = '') + { + $params = [ + 'sub_appid' => $subAppId, + 'sub_mch_id' => $subMchId ?: $this->app['config']->sub_mch_id, + ]; + + if (!empty($subscribeAppId)) { + $params['subscribe_appid'] = $subscribeAppId; + } else { + $params['receipt_appid'] = $receiptAppId; + } + + return $this->safeRequest('secapi/mkt/addrecommendconf', array_merge($params, [ + 'sign_type' => 'HMAC-SHA256', + 'nonce_str' => uniqid('micro'), + ])); + } + + /** + * Configure the new payment directory. + * + * @param string $jsapiPath + * @param string $appId + * @param string $subMchId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function addPath(string $jsapiPath, string $appId = '', string $subMchId = '') + { + return $this->addConfig([ + 'appid' => $appId ?: $this->app['config']->appid, + 'sub_mch_id' => $subMchId ?: $this->app['config']->sub_mch_id, + 'jsapi_path' => $jsapiPath, + ]); + } + + /** + * bind appid. + * + * @param string $subAppId + * @param string $appId + * @param string $subMchId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function bindAppId(string $subAppId, string $appId = '', string $subMchId = '') + { + return $this->addConfig([ + 'appid' => $appId ?: $this->app['config']->appid, + 'sub_mch_id' => $subMchId ?: $this->app['config']->sub_mch_id, + 'sub_appid' => $subAppId, + ]); + } + + /** + * add sub dev config. + * + * @param array $params + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + private function addConfig(array $params) + { + return $this->safeRequest('secapi/mch/addsubdevconfig', $params); + } + + /** + * query Sub Dev Config. + * + * @param string $subMchId + * @param string $appId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getConfig(string $subMchId = '', string $appId = '') + { + return $this->safeRequest('secapi/mch/querysubdevconfig', [ + 'sub_mch_id' => $subMchId ?: $this->app['config']->sub_mch_id, + 'appid' => $appId ?: $this->app['config']->appid, + ]); + } +} diff --git a/vendor/overtrue/wechat/src/MicroMerchant/MerchantConfig/ServiceProvider.php b/vendor/overtrue/wechat/src/MicroMerchant/MerchantConfig/ServiceProvider.php new file mode 100644 index 0000000..5b78710 --- /dev/null +++ b/vendor/overtrue/wechat/src/MicroMerchant/MerchantConfig/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MicroMerchant\MerchantConfig; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['merchantConfig'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Withdraw/Client.php b/vendor/overtrue/wechat/src/MicroMerchant/Withdraw/Client.php new file mode 100644 index 0000000..c96c363 --- /dev/null +++ b/vendor/overtrue/wechat/src/MicroMerchant/Withdraw/Client.php @@ -0,0 +1,67 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MicroMerchant\Withdraw; + +use EasyWeChat\MicroMerchant\Kernel\BaseClient; + +/** + * Class Client. + * + * @author liuml + * @DateTime 2019-05-30 14:19 + */ +class Client extends BaseClient +{ + /** + * Query withdrawal status. + * + * @param string $date + * @param string $subMchId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function queryWithdrawalStatus($date, $subMchId = '') + { + return $this->safeRequest('fund/queryautowithdrawbydate', [ + 'date' => $date, + 'sign_type' => 'HMAC-SHA256', + 'nonce_str' => uniqid('micro'), + 'sub_mch_id' => $subMchId ?: $this->app['config']->sub_mch_id, + ]); + } + + /** + * Re-initiation of withdrawal. + * + * @param string $date + * @param string $subMchId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function requestWithdraw($date, $subMchId = '') + { + return $this->safeRequest('fund/reautowithdrawbydate', [ + 'date' => $date, + 'sign_type' => 'HMAC-SHA256', + 'nonce_str' => uniqid('micro'), + 'sub_mch_id' => $subMchId ?: $this->app['config']->sub_mch_id, + ]); + } +} diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Withdraw/ServiceProvider.php b/vendor/overtrue/wechat/src/MicroMerchant/Withdraw/ServiceProvider.php new file mode 100644 index 0000000..b9c0141 --- /dev/null +++ b/vendor/overtrue/wechat/src/MicroMerchant/Withdraw/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MicroMerchant\Withdraw; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['withdraw'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/ActivityMessage/Client.php b/vendor/overtrue/wechat/src/MiniProgram/ActivityMessage/Client.php new file mode 100644 index 0000000..16f273d --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/ActivityMessage/Client.php @@ -0,0 +1,85 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\ActivityMessage; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; + +class Client extends BaseClient +{ + /** + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function createActivityId() + { + return $this->httpGet('cgi-bin/message/wxopen/activityid/create'); + } + + /** + * @param string $activityId + * @param int $state + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function updateMessage(string $activityId, int $state = 0, array $params = []) + { + if (!in_array($state, [0, 1], true)) { + throw new InvalidArgumentException('"state" should be "0" or "1".'); + } + + $params = $this->formatParameters($params); + + $params = [ + 'activity_id' => $activityId, + 'target_state' => $state, + 'template_info' => ['parameter_list' => $params], + ]; + + return $this->httpPostJson('cgi-bin/message/wxopen/updatablemsg/send', $params); + } + + /** + * @param array $params + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + protected function formatParameters(array $params) + { + $formatted = []; + + foreach ($params as $name => $value) { + if (!in_array($name, ['member_count', 'room_limit', 'path', 'version_type'], true)) { + continue; + } + + if ('version_type' === $name && !in_array($value, ['develop', 'trial', 'release'], true)) { + throw new InvalidArgumentException('Invalid value of attribute "version_type".'); + } + + $formatted[] = [ + 'name' => $name, + 'value' => strval($value), + ]; + } + + return $formatted; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/ActivityMessage/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/ActivityMessage/ServiceProvider.php new file mode 100644 index 0000000..fa7d3cf --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/ActivityMessage/ServiceProvider.php @@ -0,0 +1,28 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\ActivityMessage; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['activity_message'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/AppCode/Client.php b/vendor/overtrue/wechat/src/MiniProgram/AppCode/Client.php new file mode 100644 index 0000000..82d2af2 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/AppCode/Client.php @@ -0,0 +1,92 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\AppCode; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Http\StreamResponse; + +/** + * Class Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + /** + * Get AppCode. + * + * @param string $path + * @param array $optional + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + */ + public function get(string $path, array $optional = []) + { + $params = array_merge([ + 'path' => $path, + ], $optional); + + return $this->getStream('wxa/getwxacode', $params); + } + + /** + * Get AppCode unlimit. + * + * @param string $scene + * @param array $optional + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + */ + public function getUnlimit(string $scene, array $optional = []) + { + $params = array_merge([ + 'scene' => $scene, + ], $optional); + + return $this->getStream('wxa/getwxacodeunlimit', $params); + } + + /** + * Create QrCode. + * + * @param string $path + * @param int|null $width + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + */ + public function getQrCode(string $path, int $width = null) + { + return $this->getStream('cgi-bin/wxaapp/createwxaqrcode', compact('path', 'width')); + } + + /** + * Get stream. + * + * @param string $endpoint + * @param array $params + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function getStream(string $endpoint, array $params) + { + $response = $this->requestRaw($endpoint, 'POST', ['json' => $params]); + + if (false !== stripos($response->getHeaderLine('Content-disposition'), 'attachment')) { + return StreamResponse::buildFromPsrResponse($response); + } + + return $this->castResponseToType($response, $this->app['config']->get('response_type')); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/AppCode/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/AppCode/ServiceProvider.php new file mode 100644 index 0000000..fefdc22 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/AppCode/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\AppCode; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author mingyoung + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['app_code'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Application.php b/vendor/overtrue/wechat/src/MiniProgram/Application.php new file mode 100644 index 0000000..9702ef8 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Application.php @@ -0,0 +1,120 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram; + +use EasyWeChat\BasicService; +use EasyWeChat\Kernel\ServiceContainer; + +/** + * Class Application. + * + * @author mingyoung + * + * @property \EasyWeChat\MiniProgram\Auth\AccessToken $access_token + * @property \EasyWeChat\MiniProgram\DataCube\Client $data_cube + * @property \EasyWeChat\MiniProgram\AppCode\Client $app_code + * @property \EasyWeChat\MiniProgram\Auth\Client $auth + * @property \EasyWeChat\OfficialAccount\Server\Guard $server + * @property \EasyWeChat\MiniProgram\Encryptor $encryptor + * @property \EasyWeChat\MiniProgram\TemplateMessage\Client $template_message + * @property \EasyWeChat\OfficialAccount\CustomerService\Client $customer_service + * @property \EasyWeChat\MiniProgram\Plugin\Client $plugin + * @property \EasyWeChat\MiniProgram\Plugin\DevClient $plugin_dev + * @property \EasyWeChat\MiniProgram\UniformMessage\Client $uniform_message + * @property \EasyWeChat\MiniProgram\ActivityMessage\Client $activity_message + * @property \EasyWeChat\MiniProgram\Express\Client $logistics + * @property \EasyWeChat\MiniProgram\NearbyPoi\Client $nearby_poi + * @property \EasyWeChat\MiniProgram\OCR\Client $ocr + * @property \EasyWeChat\MiniProgram\Soter\Client $soter + * @property \EasyWeChat\BasicService\Media\Client $media + * @property \EasyWeChat\BasicService\ContentSecurity\Client $content_security + * @property \EasyWeChat\MiniProgram\Mall\ForwardsMall $mall + * @property \EasyWeChat\MiniProgram\SubscribeMessage\Client $subscribe_message + * @property \EasyWeChat\MiniProgram\RealtimeLog\Client $realtime_log + * @property \EasyWeChat\MiniProgram\RiskControl\Client $risk_control + * @property \EasyWeChat\MiniProgram\Search\Client $search + * @property \EasyWeChat\MiniProgram\Live\Client $live + * @property \EasyWeChat\MiniProgram\Broadcast\Client $broadcast + * @property \EasyWeChat\MiniProgram\UrlScheme\Client $url_scheme + * @property \EasyWeChat\MiniProgram\Union\Client $union + * @property \EasyWeChat\MiniProgram\Shop\Register\Client $shop_register + * @property \EasyWeChat\MiniProgram\Shop\Basic\Client $shop_basic + * @property \EasyWeChat\MiniProgram\Shop\Account\Client $shop_account + * @property \EasyWeChat\MiniProgram\Shop\Spu\Client $shop_spu + * @property \EasyWeChat\MiniProgram\Shop\Order\Client $shop_order + * @property \EasyWeChat\MiniProgram\Shop\Delivery\Client $shop_delivery + * @property \EasyWeChat\MiniProgram\Shop\Aftersale\Client $shop_aftersale + * @property \EasyWeChat\MiniProgram\Business\Client $business + * @property \EasyWeChat\MiniProgram\UrlLink\Client $url_link + * @property \EasyWeChat\MiniProgram\QrCode\Client $qr_code + * @property \EasyWeChat\MiniProgram\PhoneNumber\Client $phone_number + */ +class Application extends ServiceContainer +{ + /** + * @var array + */ + protected $providers = [ + Auth\ServiceProvider::class, + DataCube\ServiceProvider::class, + AppCode\ServiceProvider::class, + Server\ServiceProvider::class, + TemplateMessage\ServiceProvider::class, + CustomerService\ServiceProvider::class, + UniformMessage\ServiceProvider::class, + ActivityMessage\ServiceProvider::class, + OpenData\ServiceProvider::class, + Plugin\ServiceProvider::class, + QrCode\ServiceProvider::class, + Base\ServiceProvider::class, + Express\ServiceProvider::class, + NearbyPoi\ServiceProvider::class, + OCR\ServiceProvider::class, + Soter\ServiceProvider::class, + Mall\ServiceProvider::class, + SubscribeMessage\ServiceProvider::class, + RealtimeLog\ServiceProvider::class, + RiskControl\ServiceProvider::class, + Search\ServiceProvider::class, + Live\ServiceProvider::class, + Broadcast\ServiceProvider::class, + UrlScheme\ServiceProvider::class, + UrlLink\ServiceProvider::class, + Union\ServiceProvider::class, + PhoneNumber\ServiceProvider::class, + // Base services + BasicService\Media\ServiceProvider::class, + BasicService\ContentSecurity\ServiceProvider::class, + + Shop\Register\ServiceProvider::class, + Shop\Basic\ServiceProvider::class, + Shop\Account\ServiceProvider::class, + Shop\Spu\ServiceProvider::class, + Shop\Order\ServiceProvider::class, + Shop\Delivery\ServiceProvider::class, + Shop\Aftersale\ServiceProvider::class, + Business\ServiceProvider::class, + ]; + + /** + * Handle dynamic calls. + * + * @param string $method + * @param array $args + * + * @return mixed + */ + public function __call($method, $args) + { + return $this->base->$method(...$args); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Auth/AccessToken.php b/vendor/overtrue/wechat/src/MiniProgram/Auth/AccessToken.php new file mode 100644 index 0000000..7bdc8a9 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Auth/AccessToken.php @@ -0,0 +1,39 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Auth; + +use EasyWeChat\Kernel\AccessToken as BaseAccessToken; + +/** + * Class AccessToken. + * + * @author mingyoung + */ +class AccessToken extends BaseAccessToken +{ + /** + * @var string + */ + protected $endpointToGetToken = '/cgi-bin/token'; + + /** + * {@inheritdoc} + */ + protected function getCredentials(): array + { + return [ + 'grant_type' => 'client_credential', + 'appid' => $this->app['config']['app_id'], + 'secret' => $this->app['config']['secret'], + ]; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Auth/Client.php b/vendor/overtrue/wechat/src/MiniProgram/Auth/Client.php new file mode 100644 index 0000000..90d2c88 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Auth/Client.php @@ -0,0 +1,43 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Auth; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Auth. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + /** + * Get session info by code. + * + * @param string $code + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function session(string $code) + { + $params = [ + 'appid' => $this->app['config']['app_id'], + 'secret' => $this->app['config']['secret'], + 'js_code' => $code, + 'grant_type' => 'authorization_code', + ]; + + return $this->httpGet('sns/jscode2session', $params); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Auth/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Auth/ServiceProvider.php new file mode 100644 index 0000000..fcb687b --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Auth/ServiceProvider.php @@ -0,0 +1,32 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Auth; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + !isset($app['access_token']) && $app['access_token'] = function ($app) { + return new AccessToken($app); + }; + + !isset($app['auth']) && $app['auth'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Base/Client.php b/vendor/overtrue/wechat/src/MiniProgram/Base/Client.php new file mode 100644 index 0000000..84d6b81 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Base/Client.php @@ -0,0 +1,38 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Base; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + /** + * Get paid unionid. + * + * @param string $openid + * @param array $options + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getPaidUnionid($openid, $options = []) + { + return $this->httpGet('wxa/getpaidunionid', compact('openid') + $options); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Base/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Base/ServiceProvider.php new file mode 100644 index 0000000..d9aa41b --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Base/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Base; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author mingyoung + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['base'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Broadcast/Client.php b/vendor/overtrue/wechat/src/MiniProgram/Broadcast/Client.php new file mode 100644 index 0000000..d9fd6ca --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Broadcast/Client.php @@ -0,0 +1,539 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Broadcast; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author Abbotton + */ +class Client extends BaseClient +{ + /** + * Add broadcast goods. + * + * @param array $goodsInfo + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function create(array $goodsInfo) + { + $params = [ + 'goodsInfo' => $goodsInfo, + ]; + + return $this->httpPostJson('wxaapi/broadcast/goods/add', $params); + } + + /** + * Reset audit. + * + * @param int $auditId + * @param int $goodsId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function resetAudit(int $auditId, int $goodsId) + { + $params = [ + 'auditId' => $auditId, + 'goodsId' => $goodsId, + ]; + + return $this->httpPostJson('wxaapi/broadcast/goods/resetaudit', $params); + } + + /** + * Resubmit audit goods. + * + * @param int $goodsId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function resubmitAudit(int $goodsId) + { + $params = [ + 'goodsId' => $goodsId, + ]; + + return $this->httpPostJson('wxaapi/broadcast/goods/audit', $params); + } + + /** + * Delete broadcast goods. + * + * @param int $goodsId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete(int $goodsId) + { + $params = [ + 'goodsId' => $goodsId, + ]; + + return $this->httpPostJson('wxaapi/broadcast/goods/delete', $params); + } + + /** + * Update goods info. + * + * @param array $goodsInfo + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(array $goodsInfo) + { + $params = [ + 'goodsInfo' => $goodsInfo, + ]; + + return $this->httpPostJson('wxaapi/broadcast/goods/update', $params); + } + + /** + * Get goods information and review status. + * + * @param array $goodsIdArray + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getGoodsWarehouse(array $goodsIdArray) + { + $params = [ + 'goods_ids' => $goodsIdArray, + ]; + + return $this->httpPostJson('wxa/business/getgoodswarehouse', $params); + } + + /** + * Get goods list based on status + * + * @param array $params + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getApproved(array $params) + { + return $this->httpGet('wxaapi/broadcast/goods/getapproved', $params); + } + + /** + * Add goods to the designated live room. + * + * @param array $params + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function addGoods(array $params) + { + return $this->httpPost('wxaapi/broadcast/room/addgoods', $params); + } + + /** + * Get Room List. + * + * @param int $start + * @param int $limit + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + * @author onekb <1@1kb.ren> + */ + public function getRooms(int $start = 0, int $limit = 10) + { + $params = [ + 'start' => $start, + 'limit' => $limit, + ]; + + return $this->httpPostJson('wxa/business/getliveinfo', $params); + } + + /** + * Get Playback List. + * + * @param int $roomId + * @param int $start + * @param int $limit + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + * @author onekb <1@1kb.ren> + */ + public function getPlaybacks(int $roomId, int $start = 0, int $limit = 10) + { + $params = [ + 'action' => 'get_replay', + 'room_id' => $roomId, + 'start' => $start, + 'limit' => $limit, + ]; + + return $this->httpPostJson('wxa/business/getliveinfo', $params); + } + + /** + * Create a live room. + * + * @param array $params + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function createLiveRoom(array $params) + { + return $this->httpPost('wxaapi/broadcast/room/create', $params); + } + + /** + * Delete a live room. + * + * @param array $params + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function deleteLiveRoom(array $params) + { + return $this->httpPost('wxaapi/broadcast/room/deleteroom', $params); + } + + /** + * Update a live room. + * + * @param array $params + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function updateLiveRoom(array $params) + { + return $this->httpPost('wxaapi/broadcast/room/editroom', $params); + } + + /** + * Gets the live room push stream url. + * + * @param array $params + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function getPushUrl(array $params) + { + return $this->httpGet('wxaapi/broadcast/room/getpushurl', $params); + } + + /** + * Gets the live room share qrcode. + * + * @param array $params + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function getShareQrcode(array $params) + { + return $this->httpGet('wxaapi/broadcast/room/getsharedcode', $params); + } + + /** + * Add a live room assistant. + * + * @param array $params + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function addAssistant(array $params) + { + return $this->httpPost('wxaapi/broadcast/room/addassistant', $params); + } + + /** + * Update a live room assistant. + * + * @param array $params + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function updateAssistant(array $params) + { + return $this->httpPost('wxaapi/broadcast/room/modifyassistant', $params); + } + + /** + * Delete a live room assistant. + * + * @param array $params + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function deleteAssistant(array $params) + { + return $this->httpPost('wxaapi/broadcast/room/removeassistant', $params); + } + + /** + * Gets the assistant list. + * + * @param array $params + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function getAssistantList(array $params) + { + return $this->httpGet('wxaapi/broadcast/room/getassistantlist', $params); + } + + /** + * Add the sub anchor. + * + * @param array $params + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function addSubAnchor(array $params) + { + return $this->httpPost('wxaapi/broadcast/room/addsubanchor', $params); + } + + /** + * Update the sub anchor. + * + * @param array $params + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function updateSubAnchor(array $params) + { + return $this->httpPost('wxaapi/broadcast/room/modifysubanchor', $params); + } + + /** + * Delete the sub anchor. + * + * @param array $params + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function deleteSubAnchor(array $params) + { + return $this->httpPost('wxaapi/broadcast/room/deletesubanchor', $params); + } + + /** + * Gets the sub anchor info. + * + * @param array $params + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function getSubAnchor(array $params) + { + return $this->httpGet('wxaapi/broadcast/room/getsubanchor', $params); + } + + /** + * Turn official index on/off. + * + * @param array $params + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function updateFeedPublic(array $params) + { + return $this->httpPost('wxaapi/broadcast/room/updatefeedpublic', $params); + } + + /** + * Turn playback status on/off. + * + * @param array $params + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function updateReplay(array $params) + { + return $this->httpPost('wxaapi/broadcast/room/updatereplay', $params); + } + + /** + * Turn customer service status on/off. + * + * @param array $params + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function updateKf(array $params) + { + return $this->httpPost('wxaapi/broadcast/room/updatekf', $params); + } + + /** + * Turn global comments status on/off. + * + * @param array $params + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function updateComment(array $params) + { + return $this->httpPost('wxaapi/broadcast/room/updatecomment', $params); + } + + /** + * Add member role. + * + * @param array $params + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function addRole(array $params) + { + return $this->httpPost('wxaapi/broadcast/role/addrole', $params); + } + + /** + * Delete member role. + * + * @param array $params + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function deleteRole(array $params) + { + return $this->httpPost('wxaapi/broadcast/role/deleterole', $params); + } + + /** + * Gets the role list. + * + * @param array $params + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function getRoleList(array $params) + { + return $this->httpGet('wxaapi/broadcast/role/getrolelist', $params); + } + + /** + * Gets long-term subscribers. + * + * @param array $params + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getFollowers(array $params) + { + return $this->httpPost('wxa/business/get_wxa_followers', $params); + } + + /** + * Sending live broadcast start event to long-term subscribers. + * + * @param array $params + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function pushMessage(array $params) + { + return $this->httpPost('wxa/business/push_message', $params); + } + + /** + * Change the status of goods on/off shelves in room. + * + * @param array $params + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function updateGoodsInRoom(array $params) + { + return $this->httpPost('wxaapi/broadcast/goods/onsale', $params); + } + + /** + * Delete goods in room. + * + * @param array $params + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function deleteGoodsInRoom(array $params) + { + return $this->httpPost('wxaapi/broadcast/goods/deleteInRoom', $params); + } + + /** + * Push goods in room. + * + * @param array $params + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function pushGoods(array $params) + { + return $this->httpPost('wxaapi/broadcast/goods/push', $params); + } + + /** + * Change goods sort in room. + * + * @param array $params + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function sortGoods(array $params) + { + return $this->httpPost('wxaapi/broadcast/goods/sort', $params); + } + + /** + * Download goods explanation video. + * + * @param array $params + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function downloadGoodsExplanationVideo(array $params) + { + return $this->httpPost('wxaapi/broadcast/goods/getVideo', $params); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Broadcast/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Broadcast/ServiceProvider.php new file mode 100644 index 0000000..f33f29f --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Broadcast/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Broadcast; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author Abbotton + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['broadcast'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Business/Client.php b/vendor/overtrue/wechat/src/MiniProgram/Business/Client.php new file mode 100644 index 0000000..b320b70 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Business/Client.php @@ -0,0 +1,149 @@ + + */ +class Client extends BaseClient +{ + /** + * Business register + * @param string $accountName + * @param string $nickname + * @param string $iconMediaId + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function register(string $accountName, string $nickname, string $iconMediaId) + { + $params = [ + 'account_name' => $accountName, + 'nickname' => $nickname, + 'icon_media_id' => $iconMediaId, + ]; + + return $this->httpPostJson('cgi-bin/business/register', $params); + } + + /** + * Get business + * @param int $businessId + * @param string $accountName + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getBusiness(int $businessId = 0, string $accountName = '') + { + if (empty($businessId) && empty($accountName)) { + throw new InvalidArgumentException('Missing parameter.'); + } + if ($businessId) { + $params = [ + 'business_id' => $businessId, + ]; + } else { + $params = [ + 'account_name' => $accountName, + ]; + } + + return $this->httpPostJson('cgi-bin/business/get', $params); + } + + /** + * Get business list + * @param int $offset + * @param int $count + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function list(int $offset = 0, int $count = 10) + { + $params = [ + 'offset' => $offset, + 'count' => $count, + ]; + + return $this->httpPostJson('cgi-bin/business/list', $params); + } + + /** + * Update business. + * @param int $businessId + * @param string $nickname + * @param string $iconMediaId + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(int $businessId, string $nickname = '', string $iconMediaId = '') + { + $params = [ + 'business_id' => $businessId, + 'nickname' => $nickname, + 'icon_media_id' => $iconMediaId, + ]; + + return $this->httpPostJson('cgi-bin/business/update', $params); + } + + /** + * Get message builder. + * + * @param \EasyWeChat\Kernel\Messages\Message|string $message + * + * @return \EasyWeChat\MiniProgram\Business\Messenger + */ + public function message($message) + { + $messageBuilder = new Messenger($this); + + return $messageBuilder->message($message); + } + + /** + * Send a message. + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function send(array $message) + { + return $this->httpPostJson('cgi-bin/message/custom/business/send', $message); + } + + /** + * Typing status. + * @param int $businessId + * @param string $toUser openid + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function typing(int $businessId, string $toUser) + { + $params = [ + 'business_id' => $businessId, + 'touser' => $toUser, + 'command' => 'Typing', + ]; + + return $this->httpPostJson('cgi-bin/business/typing', $params); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Business/Messenger.php b/vendor/overtrue/wechat/src/MiniProgram/Business/Messenger.php new file mode 100644 index 0000000..fee2e50 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Business/Messenger.php @@ -0,0 +1,179 @@ + + */ +class Messenger +{ + /** + * Messages to send. + * + * @var \EasyWeChat\Kernel\Messages\Message; + */ + protected $message; + + /** + * Messages target user open id. + * + * @var string + */ + protected $to; + + /** + * Messages sender staff id. + * + * @var string + */ + protected $account; + + /** + * Customer service instance. + * + * @var \EasyWeChat\MiniProgram\Business\Client + */ + protected $client; + + /** + * Messages businessId + * + * @var int + */ + protected $businessId; + + /** + * MessageBuilder constructor. + * + * @param \EasyWeChat\MiniProgram\Business\Client $client + */ + public function __construct(Client $client) + { + $this->client = $client; + } + + /** + * Set message to send. + * + * @param string|Message $message + * + * @return Messenger + */ + public function message($message) + { + if (is_string($message)) { + $message = new Text($message); + } + + $this->message = $message; + + return $this; + } + + /** + * Set staff account to send message. + * + * @return Messenger + */ + public function by(string $account) + { + $this->account = $account; + + return $this; + } + + /** + * @return Messenger + */ + public function from(string $account) + { + return $this->by($account); + } + + /** + * Set target user open id. + * + * @param string $openid + * + * @return Messenger + */ + public function to($openid) + { + $this->to = $openid; + + return $this; + } + + /** + * Set target business id. + * + * @param int $businessId + * + * @return Messenger + */ + public function business($businessId) + { + $this->businessId = $businessId; + + return $this; + } + + /** + * Send the message. + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public function send() + { + if (empty($this->message)) { + throw new RuntimeException('No message to send.'); + } + + if ($this->message instanceof RawMessage) { + $message = json_decode($this->message->get('content'), true); + } else { + $prepends = [ + 'touser' => $this->to, + ]; + if ($this->account) { + $prepends['customservice'] = ['kf_account' => $this->account]; + } + if ($this->businessId) { + $prepends['businessid'] = $this->businessId; + } + $message = $this->message->transformForJsonRequest($prepends); + } + + return $this->client->send($message); + } + + /** + * Return property. + * + * @return mixed + */ + public function __get(string $property) + { + if (property_exists($this, $property)) { + return $this->$property; + } + + return null; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Business/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Business/ServiceProvider.php new file mode 100644 index 0000000..8a4c5d1 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Business/ServiceProvider.php @@ -0,0 +1,29 @@ + + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['business'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/CustomerService/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/CustomerService/ServiceProvider.php new file mode 100644 index 0000000..cfd3039 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/CustomerService/ServiceProvider.php @@ -0,0 +1,34 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\CustomerService; + +use EasyWeChat\OfficialAccount\CustomerService\Client; +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['customer_service'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/DataCube/Client.php b/vendor/overtrue/wechat/src/MiniProgram/DataCube/Client.php new file mode 100644 index 0000000..385ef45 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/DataCube/Client.php @@ -0,0 +1,203 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\DataCube; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + /** + * Get summary trend. + * + * @param string $from + * @param string $to + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + */ + public function summaryTrend(string $from, string $to) + { + return $this->query('datacube/getweanalysisappiddailysummarytrend', $from, $to); + } + + /** + * Get daily visit trend. + * + * @param string $from + * @param string $to + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + */ + public function dailyVisitTrend(string $from, string $to) + { + return $this->query('datacube/getweanalysisappiddailyvisittrend', $from, $to); + } + + /** + * Get weekly visit trend. + * + * @param string $from + * @param string $to + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + */ + public function weeklyVisitTrend(string $from, string $to) + { + return $this->query('datacube/getweanalysisappidweeklyvisittrend', $from, $to); + } + + /** + * Get monthly visit trend. + * + * @param string $from + * @param string $to + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + */ + public function monthlyVisitTrend(string $from, string $to) + { + return $this->query('datacube/getweanalysisappidmonthlyvisittrend', $from, $to); + } + + /** + * Get visit distribution. + * + * @param string $from + * @param string $to + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + */ + public function visitDistribution(string $from, string $to) + { + return $this->query('datacube/getweanalysisappidvisitdistribution', $from, $to); + } + + /** + * Get daily retain info. + * + * @param string $from + * @param string $to + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + */ + public function dailyRetainInfo(string $from, string $to) + { + return $this->query('datacube/getweanalysisappiddailyretaininfo', $from, $to); + } + + /** + * Get weekly retain info. + * + * @param string $from + * @param string $to + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + */ + public function weeklyRetainInfo(string $from, string $to) + { + return $this->query('datacube/getweanalysisappidweeklyretaininfo', $from, $to); + } + + /** + * Get monthly retain info. + * + * @param string $from + * @param string $to + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + */ + public function monthlyRetainInfo(string $from, string $to) + { + return $this->query('datacube/getweanalysisappidmonthlyretaininfo', $from, $to); + } + + /** + * Get visit page. + * + * @param string $from + * @param string $to + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + */ + public function visitPage(string $from, string $to) + { + return $this->query('datacube/getweanalysisappidvisitpage', $from, $to); + } + + /** + * Get user portrait. + * + * @param string $from + * @param string $to + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + */ + public function userPortrait(string $from, string $to) + { + return $this->query('datacube/getweanalysisappiduserportrait', $from, $to); + } + + /** + * get performance data + * @param string $from + * @param string $to + * @param string $module + * @param string $networktype + * @param string $device_level + * @param string $device + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function devicePerformanceData(string $from, string $to, string $module, string $networktype = '-1', string $device_level = '-1', string $device = '-1') + { + $payload = [ + 'time' => [ + 'end_timestamp' => strtotime($to), + 'begin_timestamp' => strtotime($from), + ], + 'module' => $module, + 'params' => [ + ['field' => 'networktype', 'value' => $networktype], + ['field' => 'device_level', 'value' => $device_level], + ['field' => 'device', 'value' => $device], + ] + ]; + return $this->httpPostJson('wxa/business/performance/boot', $payload); + } + + /** + * Unify query. + * + * @param string $api + * @param string $from + * @param string $to + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function query(string $api, string $from, string $to) + { + $params = [ + 'begin_date' => $from, + 'end_date' => $to, + ]; + + return $this->httpPostJson($api, $params); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/DataCube/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/DataCube/ServiceProvider.php new file mode 100644 index 0000000..258d7c4 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/DataCube/ServiceProvider.php @@ -0,0 +1,28 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\DataCube; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['data_cube'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Encryptor.php b/vendor/overtrue/wechat/src/MiniProgram/Encryptor.php new file mode 100644 index 0000000..fc6ec5f --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Encryptor.php @@ -0,0 +1,52 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram; + +use EasyWeChat\Kernel\Encryptor as BaseEncryptor; +use EasyWeChat\Kernel\Exceptions\DecryptException; +use EasyWeChat\Kernel\Support\AES; + +/** + * Class Encryptor. + * + * @author mingyoung + */ +class Encryptor extends BaseEncryptor +{ + /** + * Decrypt data. + * + * @param string $sessionKey + * @param string $iv + * @param string $encrypted + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\DecryptException + */ + public function decryptData(string $sessionKey, string $iv, string $encrypted): array + { + $decrypted = AES::decrypt( + base64_decode($encrypted, false), + base64_decode($sessionKey, false), + base64_decode($iv, false) + ); + + $decrypted = json_decode($decrypted, true); + + if (!$decrypted) { + throw new DecryptException('The given payload is invalid.'); + } + + return $decrypted; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Express/Client.php b/vendor/overtrue/wechat/src/MiniProgram/Express/Client.php new file mode 100644 index 0000000..0645c06 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Express/Client.php @@ -0,0 +1,154 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Express; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author kehuanhuan <1152018701@qq.com> + */ +class Client extends BaseClient +{ + /** + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function listProviders() + { + return $this->httpGet('cgi-bin/express/business/delivery/getall'); + } + + /** + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function getAllAccount() + { + return $this->httpGet('cgi-bin/express/business/account/getall'); + } + + /** + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function createWaybill(array $params = []) + { + return $this->httpPostJson('cgi-bin/express/business/order/add', $params); + } + + /** + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function deleteWaybill(array $params = []) + { + return $this->httpPostJson('cgi-bin/express/business/order/cancel', $params); + } + + /** + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getWaybill(array $params = []) + { + return $this->httpPostJson('cgi-bin/express/business/order/get', $params); + } + + /** + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getWaybillTrack(array $params = []) + { + return $this->httpPostJson('cgi-bin/express/business/path/get', $params); + } + + /** + * @param string $deliveryId + * @param string $bizId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getBalance(string $deliveryId, string $bizId) + { + return $this->httpPostJson('cgi-bin/express/business/quota/get', [ + 'delivery_id' => $deliveryId, + 'biz_id' => $bizId, + ]); + } + + /** + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getPrinter() + { + return $this->httpPostJson('cgi-bin/express/business/printer/getall'); + } + + /** + * @param string $openid + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function bindPrinter(string $openid) + { + return $this->httpPostJson('cgi-bin/express/business/printer/update', [ + 'update_type' => 'bind', + 'openid' => $openid, + ]); + } + + /** + * @param string $openid + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function unbindPrinter(string $openid) + { + return $this->httpPostJson('cgi-bin/express/business/printer/update', [ + 'update_type' => 'unbind', + 'openid' => $openid, + ]); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Express/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Express/ServiceProvider.php new file mode 100644 index 0000000..4794094 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Express/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Express; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author kehuanhuan <1152018701@qq.com> + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['express'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Live/Client.php b/vendor/overtrue/wechat/src/MiniProgram/Live/Client.php new file mode 100644 index 0000000..ecc4324 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Live/Client.php @@ -0,0 +1,63 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Live; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author onekb <1@1kb.ren> + */ +class Client extends BaseClient +{ + /** + * Get Room List. + * + * @param int $start + * @param int $limit + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @deprecated This method has been merged into `\EasyWeChat\MiniProgram\Broadcast` + */ + public function getRooms(int $start = 0, int $limit = 10) + { + $params = [ + 'start' => $start, + 'limit' => $limit, + ]; + + return $this->httpPostJson('wxa/business/getliveinfo', $params); + } + + /** + * Get Playback List. + * + * @param int $roomId + * @param int $start + * @param int $limit + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @deprecated This method has been merged into `\EasyWeChat\MiniProgram\Broadcast` + */ + public function getPlaybacks(int $roomId, int $start = 0, int $limit = 10) + { + $params = [ + 'action' => 'get_replay', + 'room_id' => $roomId, + 'start' => $start, + 'limit' => $limit, + ]; + + return $this->httpPostJson('wxa/business/getliveinfo', $params); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Live/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Live/ServiceProvider.php new file mode 100644 index 0000000..6d6dff6 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Live/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Live; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author onekb <1@1kb.ren> + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['live'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Mall/CartClient.php b/vendor/overtrue/wechat/src/MiniProgram/Mall/CartClient.php new file mode 100644 index 0000000..7a07e77 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Mall/CartClient.php @@ -0,0 +1,88 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Mall; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author mingyoung + */ +class CartClient extends BaseClient +{ + /** + * 导入收藏. + * + * @param array $params + * @param bool $isTest + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function add($params, $isTest = false) + { + return $this->httpPostJson('mall/addshoppinglist', $params, ['is_test' => (int) $isTest]); + } + + /** + * 查询用户收藏信息. + * + * @param array $params + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function query($params) + { + return $this->httpPostJson('mall/queryshoppinglist', $params, ['type' => 'batchquery']); + } + + /** + * 查询用户收藏信息. + * + * @param array $params + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function queryByPage($params) + { + return $this->httpPostJson('mall/queryshoppinglist', $params, ['type' => 'getbypage']); + } + + /** + * 删除收藏. + * + * @param string $openid + * @param array $products + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete($openid, array $products = []) + { + if (empty($products)) { + return $this->httpPostJson('mall/deletebizallshoppinglist', ['user_open_id' => $openid]); + } + + return $this->httpPostJson('mall/deleteshoppinglist', ['user_open_id' => $openid, 'sku_product_list' => $products]); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Mall/ForwardsMall.php b/vendor/overtrue/wechat/src/MiniProgram/Mall/ForwardsMall.php new file mode 100644 index 0000000..f727b17 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Mall/ForwardsMall.php @@ -0,0 +1,48 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Mall; + +/** + * Class Application. + * + * @author mingyoung + * + * @property \EasyWeChat\MiniProgram\Mall\OrderClient $order + * @property \EasyWeChat\MiniProgram\Mall\CartClient $cart + * @property \EasyWeChat\MiniProgram\Mall\ProductClient $product + * @property \EasyWeChat\MiniProgram\Mall\MediaClient $media + */ +class ForwardsMall +{ + /** + * @var \EasyWeChat\Kernel\ServiceContainer + */ + protected $app; + + /** + * @param \EasyWeChat\Kernel\ServiceContainer $app + */ + public function __construct($app) + { + $this->app = $app; + } + + /** + * @param string $property + * + * @return mixed + */ + public function __get($property) + { + return $this->app["mall.{$property}"]; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Mall/MediaClient.php b/vendor/overtrue/wechat/src/MiniProgram/Mall/MediaClient.php new file mode 100644 index 0000000..a3827bc --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Mall/MediaClient.php @@ -0,0 +1,37 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Mall; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author mingyoung + */ +class MediaClient extends BaseClient +{ + /** + * 更新或导入媒体信息. + * + * @param array $params + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function import($params) + { + return $this->httpPostJson('mall/importmedia', $params); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Mall/OrderClient.php b/vendor/overtrue/wechat/src/MiniProgram/Mall/OrderClient.php new file mode 100644 index 0000000..9879695 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Mall/OrderClient.php @@ -0,0 +1,75 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Mall; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author mingyoung + */ +class OrderClient extends BaseClient +{ + /** + * 导入订单. + * + * @param array $params + * @param bool $isHistory + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function add($params, $isHistory = false) + { + return $this->httpPostJson('mall/importorder', $params, ['action' => 'add-order', 'is_history' => (int) $isHistory]); + } + + /** + * 导入订单. + * + * @param array $params + * @param bool $isHistory + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update($params, $isHistory = false) + { + return $this->httpPostJson('mall/importorder', $params, ['action' => 'update-order', 'is_history' => (int) $isHistory]); + } + + /** + * 删除订单. + * + * @param string $openid + * @param string $orderId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete($openid, $orderId) + { + $params = [ + 'user_open_id' => $openid, + 'order_id' => $orderId, + ]; + + return $this->httpPostJson('mall/deleteorder', $params); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Mall/ProductClient.php b/vendor/overtrue/wechat/src/MiniProgram/Mall/ProductClient.php new file mode 100644 index 0000000..f7fbfd0 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Mall/ProductClient.php @@ -0,0 +1,68 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Mall; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author mingyoung + */ +class ProductClient extends BaseClient +{ + /** + * 更新或导入物品信息. + * + * @param array $params + * @param bool $isTest + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function import($params, $isTest = false) + { + return $this->httpPostJson('mall/importproduct', $params, ['is_test' => (int) $isTest]); + } + + /** + * 查询物品信息. + * + * @param array $params + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function query($params) + { + return $this->httpPostJson('mall/queryproduct', $params, ['type' => 'batchquery']); + } + + /** + * 小程序的物品是否可被搜索. + * + * @param bool $value + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function setSearchable($value) + { + return $this->httpPostJson('mall/brandmanage', ['can_be_search' => $value], ['action' => 'set_biz_can_be_search']); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Mall/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Mall/ServiceProvider.php new file mode 100644 index 0000000..964395b --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Mall/ServiceProvider.php @@ -0,0 +1,44 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Mall; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['mall'] = function ($app) { + return new ForwardsMall($app); + }; + + $app['mall.order'] = function ($app) { + return new OrderClient($app); + }; + + $app['mall.cart'] = function ($app) { + return new CartClient($app); + }; + + $app['mall.product'] = function ($app) { + return new ProductClient($app); + }; + + $app['mall.media'] = function ($app) { + return new MediaClient($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/NearbyPoi/Client.php b/vendor/overtrue/wechat/src/MiniProgram/NearbyPoi/Client.php new file mode 100644 index 0000000..da9ee05 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/NearbyPoi/Client.php @@ -0,0 +1,123 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\NearbyPoi; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; + +/** + * Class Client. + * + * @author joyeekk + */ +class Client extends BaseClient +{ + /** + * Add nearby poi. + * + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function add(array $params) + { + $params = array_merge([ + 'is_comm_nearby' => '1', + 'poi_id' => '', + ], $params); + + return $this->httpPostJson('wxa/addnearbypoi', $params); + } + + /** + * Update nearby poi. + * + * @param string $poiId + * @param array $params + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(string $poiId, array $params) + { + $params = array_merge([ + 'is_comm_nearby' => '1', + 'poi_id' => $poiId, + ], $params); + + return $this->httpPostJson('wxa/addnearbypoi', $params); + } + + /** + * Delete nearby poi. + * + * @param string $poiId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete(string $poiId) + { + return $this->httpPostJson('wxa/delnearbypoi', [ + 'poi_id' => $poiId, + ]); + } + + /** + * Get nearby poi list. + * + * @param int $page + * @param int $pageRows + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function list(int $page, int $pageRows) + { + return $this->httpGet('wxa/getnearbypoilist', [ + 'page' => $page, + 'page_rows' => $pageRows, + ]); + } + + /** + * Set nearby poi show status. + * + * @param string $poiId + * @param int $status + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function setVisibility(string $poiId, int $status) + { + if (!in_array($status, [0, 1], true)) { + throw new InvalidArgumentException('status should be 0 or 1.'); + } + + return $this->httpPostJson('wxa/setnearbypoishowstatus', [ + 'poi_id' => $poiId, + 'status' => $status, + ]); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/NearbyPoi/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/NearbyPoi/ServiceProvider.php new file mode 100644 index 0000000..cb15bb3 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/NearbyPoi/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\NearbyPoi; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author joyeekk + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['nearby_poi'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/OCR/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/OCR/ServiceProvider.php new file mode 100644 index 0000000..80e0001 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/OCR/ServiceProvider.php @@ -0,0 +1,34 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\OCR; + +use EasyWeChat\OfficialAccount\OCR\Client; +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author joyeekk + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['ocr'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/OpenData/Client.php b/vendor/overtrue/wechat/src/MiniProgram/OpenData/Client.php new file mode 100644 index 0000000..835bf7b --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/OpenData/Client.php @@ -0,0 +1,91 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\OpenData; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author tianyong90 <412039588@qq.com> + */ +class Client extends BaseClient +{ + /** + * removeUserStorage. + * + * @param string $openid + * @param string $sessionKey + * @param array $key + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function removeUserStorage(string $openid, string $sessionKey, array $key) + { + $data = ['key' => $key]; + $query = [ + 'openid' => $openid, + 'sig_method' => 'hmac_sha256', + 'signature' => hash_hmac('sha256', json_encode($data), $sessionKey), + ]; + + return $this->httpPostJson('/wxa/remove_user_storage', $data, $query); + } + + /** + * setUserStorage. + * + * @param string $openid + * @param string $sessionKey + * @param array $kvList + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function setUserStorage(string $openid, string $sessionKey, array $kvList) + { + $kvList = $this->formatKVLists($kvList); + + $data = ['kv_list' => $kvList]; + $query = [ + 'openid' => $openid, + 'sig_method' => 'hmac_sha256', + 'signature' => hash_hmac('sha256', json_encode($data), $sessionKey), + ]; + + return $this->httpPostJson('/wxa/set_user_storage', $data, $query); + } + + /** + * @param array $params + * + * @return array + */ + protected function formatKVLists(array $params) + { + $formatted = []; + + foreach ($params as $name => $value) { + $formatted[] = [ + 'key' => $name, + 'value' => strval($value), + ]; + } + + return $formatted; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/OpenData/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/OpenData/ServiceProvider.php new file mode 100644 index 0000000..3ea1287 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/OpenData/ServiceProvider.php @@ -0,0 +1,28 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\OpenData; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['open_data'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/PhoneNumber/Client.php b/vendor/overtrue/wechat/src/MiniProgram/PhoneNumber/Client.php new file mode 100644 index 0000000..e8a32d4 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/PhoneNumber/Client.php @@ -0,0 +1,47 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\PhoneNumber; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @package EasyWeChat\MiniProgram\PhoneNumber + * + * @author 读心印 + */ +class Client extends BaseClient +{ + /** + * 获取用户手机号. + * + * @see https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/phonenumber/phonenumber.getPhoneNumber.html + * + * @param string $code + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + * + * @author 读心印 + */ + public function getUserPhoneNumber(string $code) + { + $params = [ + 'code' => $code + ]; + + return $this->httpPostJson('/wxa/business/getuserphonenumber', $params); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/PhoneNumber/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/PhoneNumber/ServiceProvider.php new file mode 100644 index 0000000..db4e76f --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/PhoneNumber/ServiceProvider.php @@ -0,0 +1,28 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\PhoneNumber; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['phone_number'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Plugin/Client.php b/vendor/overtrue/wechat/src/MiniProgram/Plugin/Client.php new file mode 100644 index 0000000..cd885cc --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Plugin/Client.php @@ -0,0 +1,67 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Plugin; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + /** + * @param string $appId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function apply($appId) + { + return $this->httpPostJson('wxa/plugin', [ + 'action' => 'apply', + 'plugin_appid' => $appId, + ]); + } + + /** + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function list() + { + return $this->httpPostJson('wxa/plugin', [ + 'action' => 'list', + ]); + } + + /** + * @param string $appId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function unbind($appId) + { + return $this->httpPostJson('wxa/plugin', [ + 'action' => 'unbind', + 'plugin_appid' => $appId, + ]); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Plugin/DevClient.php b/vendor/overtrue/wechat/src/MiniProgram/Plugin/DevClient.php new file mode 100644 index 0000000..3f25edc --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Plugin/DevClient.php @@ -0,0 +1,93 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Plugin; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class DevClient. + * + * @author her-cat + */ +class DevClient extends BaseClient +{ + /** + * Get users. + * + * @param int $page + * @param int $size + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getUsers(int $page = 1, int $size = 10) + { + return $this->httpPostJson('wxa/devplugin', [ + 'action' => 'dev_apply_list', + 'page' => $page, + 'num' => $size, + ]); + } + + /** + * Agree to use plugin. + * + * @param string $appId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function agree(string $appId) + { + return $this->httpPostJson('wxa/devplugin', [ + 'action' => 'dev_agree', + 'appid' => $appId, + ]); + } + + /** + * Refuse to use plugin. + * + * @param string $reason + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function refuse(string $reason) + { + return $this->httpPostJson('wxa/devplugin', [ + 'action' => 'dev_refuse', + 'reason' => $reason, + ]); + } + + /** + * Delete rejected applications. + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete() + { + return $this->httpPostJson('wxa/devplugin', [ + 'action' => 'dev_delete', + ]); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Plugin/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Plugin/ServiceProvider.php new file mode 100644 index 0000000..097c77e --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Plugin/ServiceProvider.php @@ -0,0 +1,42 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Plugin; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author mingyoung + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * Registers services on the given container. + * + * This method should only be used to configure services and parameters. + * It should not get services. + * + * @param \Pimple\Container $app + */ + public function register(Container $app) + { + $app['plugin'] = function ($app) { + return new Client($app); + }; + + $app['plugin_dev'] = function ($app) { + return new DevClient($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/QrCode/Client.php b/vendor/overtrue/wechat/src/MiniProgram/QrCode/Client.php new file mode 100644 index 0000000..2fb98e5 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/QrCode/Client.php @@ -0,0 +1,126 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\QrCode; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Exceptions\InvalidConfigException; +use EasyWeChat\Kernel\Support\Collection; +use GuzzleHttp\Exception\GuzzleException; +use Psr\Http\Message\ResponseInterface; + +/** + * QrCode Client + * + * 普通链接二维码 + * + * @link https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/qrcode/qrcode.html + * @link https://developers.weixin.qq.com/miniprogram/introduction/qrcode.html + * + * @author dysodeng + */ +class Client extends BaseClient +{ + /** + * 获取已设置的二维码规则 + * + * @return array|Collection|object|ResponseInterface|string + * + * @throws InvalidConfigException + * @throws GuzzleException + */ + public function list() + { + return $this->httpPostJson('cgi-bin/wxopen/qrcodejumpget'); + } + + /** + * 获取校验文件名称及内容 + * + * @return array|Collection|object|ResponseInterface|string + * + * @throws GuzzleException + * @throws InvalidConfigException + */ + public function getVerifyFile() + { + return $this->httpPostJson('cgi-bin/wxopen/qrcodejumpdownload'); + } + + /** + * 增加或修改二维码规则 + * + * @param array $params + * + * @return array|Collection|object|ResponseInterface|string + * @throws GuzzleException + * @throws InvalidConfigException + */ + public function set(array $params) + { + return $this->httpPostJson('cgi-bin/wxopen/qrcodejumpadd', $params); + } + + /** + * 发布已设置的二维码规则 + * + * @param string $prefix + * + * @return array|Collection|object|ResponseInterface|string + * + * @throws GuzzleException + * @throws InvalidConfigException + */ + public function publish(string $prefix) + { + $params = [ + 'prefix' => $prefix + ]; + return $this->httpPostJson('cgi-bin/wxopen/qrcodejumppublish', $params); + } + + /** + * 删除已设置的二维码规则 + * + * @param string $prefix + * + * @return array|Collection|object|ResponseInterface|string + * + * @throws GuzzleException + * @throws InvalidConfigException + */ + public function delete(string $prefix) + { + $params = [ + 'prefix' => $prefix + ]; + return $this->httpPostJson('cgi-bin/wxopen/qrcodejumpdelete', $params); + } + + /** + * 将二维码长链接转成短链接 + * + * @param string $long_url + * + * @return array|Collection|object|ResponseInterface|string + * + * @throws GuzzleException + * @throws InvalidConfigException + */ + public function shortUrl(string $long_url) + { + $params = [ + 'long_url' => $long_url, + 'action' => 'long2short' + ]; + return $this->httpPostJson('cgi-bin/shorturl', $params); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/QrCode/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/QrCode/ServiceProvider.php new file mode 100644 index 0000000..ee2a0aa --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/QrCode/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\QrCode; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * QrCode ServiceProvider. + * + * @author dysodeng + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $pimple) + { + $pimple['qr_code'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/RealtimeLog/Client.php b/vendor/overtrue/wechat/src/MiniProgram/RealtimeLog/Client.php new file mode 100644 index 0000000..9363619 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/RealtimeLog/Client.php @@ -0,0 +1,46 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\RealtimeLog; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author her-cat + */ +class Client extends BaseClient +{ + /** + * Real time log query. + * + * @param string $date + * @param int $beginTime + * @param int $endTime + * @param array $options + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function search(string $date, int $beginTime, int $endTime, array $options = []) + { + $params = [ + 'date' => $date, + 'begintime' => $beginTime, + 'endtime' => $endTime, + ]; + + return $this->httpGet('wxaapi/userlog/userlog_search', $params + $options); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/RealtimeLog/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/RealtimeLog/ServiceProvider.php new file mode 100644 index 0000000..9a28141 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/RealtimeLog/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\RealtimeLog; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author her-cat + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc} + */ + public function register(Container $app) + { + $app['realtime_log'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/RiskControl/Client.php b/vendor/overtrue/wechat/src/MiniProgram/RiskControl/Client.php new file mode 100644 index 0000000..a6c00c0 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/RiskControl/Client.php @@ -0,0 +1,32 @@ +httpPostJson('wxa/getuserriskrank', $params); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/RiskControl/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/RiskControl/ServiceProvider.php new file mode 100644 index 0000000..94d2758 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/RiskControl/ServiceProvider.php @@ -0,0 +1,25 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Search; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author her-cat + */ +class Client extends BaseClient +{ + /** + * Submit applet page URL and parameter information. + * + * @param array $pages + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function submitPage(array $pages) + { + return $this->httpPostJson('wxa/search/wxaapi_submitpages', compact('pages')); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Search/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Search/ServiceProvider.php new file mode 100644 index 0000000..85cb569 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Search/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Search; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author her-cat + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['search'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Server/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Server/ServiceProvider.php new file mode 100644 index 0000000..0586bb9 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Server/ServiceProvider.php @@ -0,0 +1,42 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Server; + +use EasyWeChat\MiniProgram\Encryptor; +use EasyWeChat\OfficialAccount\Server\Guard; +use EasyWeChat\OfficialAccount\Server\Handlers\EchoStrHandler; +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + !isset($app['encryptor']) && $app['encryptor'] = function ($app) { + return new Encryptor( + $app['config']['app_id'], + $app['config']['token'], + $app['config']['aes_key'] + ); + }; + + !isset($app['server']) && $app['server'] = function ($app) { + $guard = new Guard($app); + $guard->push(new EchoStrHandler($app)); + + return $guard; + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Shop/Account/Client.php b/vendor/overtrue/wechat/src/MiniProgram/Shop/Account/Client.php new file mode 100644 index 0000000..1988e7e --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Shop/Account/Client.php @@ -0,0 +1,67 @@ + + */ +class Client extends BaseClient +{ + /** + * 获取商家类目列表 + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getCategoryList() + { + return $this->httpPostJson('shop/account/get_category_list'); + } + + /** + * 获取商家品牌列表 + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getBrandList() + { + return $this->httpPostJson('shop/account/get_brand_list'); + } + + /** + * 更新商家信息 + * + * @param string $path 小程序path + * @param string $phone 客服联系方式 + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function updateInfo(string $path = '', string $phone = '') + { + return $this->httpPostJson('shop/account/update_info', [ + 'service_agent_path' => $path, + 'service_agent_phone' => $phone, + ]); + } + + /** + * 获取商家信息 + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getInfo() + { + return $this->httpPostJson('shop/account/get_info'); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Shop/Account/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Shop/Account/ServiceProvider.php new file mode 100644 index 0000000..42e8bf8 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Shop/Account/ServiceProvider.php @@ -0,0 +1,25 @@ + + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc} + */ + public function register(Container $app) + { + $app['shop_account'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Shop/Aftersale/Client.php b/vendor/overtrue/wechat/src/MiniProgram/Shop/Aftersale/Client.php new file mode 100644 index 0000000..3a3f164 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Shop/Aftersale/Client.php @@ -0,0 +1,54 @@ + + */ +class Client extends BaseClient +{ + /** + * 创建售后 + * + * @param array $aftersale + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function add(array $aftersale) + { + return $this->httpPostJson('shop/aftersale/add', $aftersale); + } + + /** + * 获取订单下售后单 + * + * @param array $order 订单数据 + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get(array $order) + { + return $this->httpPostJson('shop/aftersale/get', $order); + } + + /** + * 更新售后 + * + * @param array $order 订单数据 + * @param array $aftersale 售后数据 + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(array $order, array $aftersale) + { + return $this->httpPostJson('shop/aftersale/update', array_merge($order, $aftersale)); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Shop/Aftersale/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Shop/Aftersale/ServiceProvider.php new file mode 100644 index 0000000..91d9ab7 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Shop/Aftersale/ServiceProvider.php @@ -0,0 +1,25 @@ + + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * @inheritDoc + */ + public function register(Container $app) + { + $app['shop_aftersale'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Shop/Basic/Client.php b/vendor/overtrue/wechat/src/MiniProgram/Shop/Basic/Client.php new file mode 100644 index 0000000..e741227 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Shop/Basic/Client.php @@ -0,0 +1,105 @@ + + */ +class Client extends BaseClient +{ + /** + * 获取商品类目 + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getCat() + { + return $this->httpPostJson('shop/cat/get'); + } + + /** + * 上传图片 + * 此接口目前只用于图片上传。 + * + * @param string $imageFilePath 图片文件路径 + * @param int $respType 返回类型 + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function imgUpload(string $imageFilePath, int $respType = 1) + { + return $this->httpUpload('shop/img/upload', [ + 'media' => $imageFilePath, + ], [ + 'resp_type' => $respType, + ]); + } + + /** + * 品牌审核 + * + * @param array $brand 品牌信息 + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function auditBrand(array $brand) + { + return $this->httpPostJson('shop/audit/audit_brand', [ + 'audit_req' => $brand + ]); + } + + /** + * 类目审核 + * + * @param array $category 类目资质 + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function auditCategory(array $category) + { + return $this->httpPostJson('shop/audit/audit_category', [ + 'audit_req' => $category + ]); + } + + /** + * 获取审核结果 + * + * @param string $auditId 提交审核时返回的id + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function auditResult(string $auditId) + { + return $this->httpPostJson('shop/audit/result', [ + 'audit_id' => $auditId + ]); + } + + /** + * 获取小程序资质 + * + * @param int $reqType + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getMiniAppCertificate(int $reqType = 2) + { + return $this->httpPostJson('shop/audit/get_miniapp_certificate', [ + 'req_type' => $reqType + ]); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Shop/Basic/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Shop/Basic/ServiceProvider.php new file mode 100644 index 0000000..a8ed616 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Shop/Basic/ServiceProvider.php @@ -0,0 +1,25 @@ + + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * @inheritDoc + */ + public function register(Container $app) + { + $app['shop_basic'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Shop/Delivery/Client.php b/vendor/overtrue/wechat/src/MiniProgram/Shop/Delivery/Client.php new file mode 100644 index 0000000..c924034 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Shop/Delivery/Client.php @@ -0,0 +1,52 @@ + + */ +class Client extends BaseClient +{ + /** + * 获取快递公司列表 + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getCompanyList() + { + return $this->httpPostJson('shop/delivery/get_company_list'); + } + + /** + * 订单发货 + * + * @param array $order + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function send(array $order) + { + return $this->httpPostJson('shop/delivery/send', $order); + } + + /** + * 订单确认收货 + * + * @param array $order + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function recieve(array $order) + { + return $this->httpPostJson('shop/delivery/recieve', $order); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Shop/Delivery/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Shop/Delivery/ServiceProvider.php new file mode 100644 index 0000000..eb8ce16 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Shop/Delivery/ServiceProvider.php @@ -0,0 +1,25 @@ + + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * @inheritDoc + */ + public function register(Container $app) + { + $app['shop_delivery'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Shop/Order/Client.php b/vendor/overtrue/wechat/src/MiniProgram/Shop/Order/Client.php new file mode 100644 index 0000000..fad56a0 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Shop/Order/Client.php @@ -0,0 +1,67 @@ + + */ +class Client extends BaseClient +{ + /** + * 检查场景值是否在支付校验范围内 + * + * @param int $scene 场景值 + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function sceneCheck(int $scene) + { + return $this->httpPostJson('shop/scene/check', ['scene' => $scene]); + } + + /** + * 生成订单 + * + * @param array $order + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function add(array $order) + { + return $this->httpPostJson('shop/order/add', $order); + } + + /** + * 获取订单详情 + * + * @param string $openid 用户的openid + * @param array $orderId 微信侧订单id (订单id二选一) + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get(string $openid, array $orderId) + { + return $this->httpPostJson('shop/order/get', array_merge($orderId, ['openid' => $openid])); + } + + /** + * 同步订单支付结果 + * + * @param array $pay + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function pay(array $pay) + { + return $this->httpPostJson('shop/order/pay', $pay); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Shop/Order/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Shop/Order/ServiceProvider.php new file mode 100644 index 0000000..4b28114 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Shop/Order/ServiceProvider.php @@ -0,0 +1,25 @@ + + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * @inheritDoc + */ + public function register(Container $app) + { + $app['shop_order'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Shop/Register/Client.php b/vendor/overtrue/wechat/src/MiniProgram/Shop/Register/Client.php new file mode 100644 index 0000000..4667cee --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Shop/Register/Client.php @@ -0,0 +1,76 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Shop\Register; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author her-cat + */ +class Client extends BaseClient +{ + /** + * 接入申请 + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function apply() + { + return $this->httpPostJson('shop/register/apply'); + } + + /** + * 获取接入状态 + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function check() + { + return $this->httpPostJson('shop/register/check'); + } + + /** + * 完成接入任务 + * + * @param int $accessInfoItem + * 6:完成spu接口,7:完成订单接口,8:完成物流接口,9:完成售后接口,10:测试完成,11:发版完成 + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function finishAccessInfo(int $accessInfoItem) + { + return $this->httpPostJson('shop/register/finish_access_info', [ + 'access_info_item' => $accessInfoItem + ]); + } + + /** + * 场景接入申请 + * + * @param int $sceneGroupId 1:视频号、公众号场景 + + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function applyScene(int $sceneGroupId = 1) + { + return $this->httpPostJson('shop/register/apply_scene', [ + 'scene_group_id' => $sceneGroupId + ]); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Shop/Register/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Shop/Register/ServiceProvider.php new file mode 100644 index 0000000..58fd286 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Shop/Register/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Shop\Register; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author her-cat + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc} + */ + public function register(Container $app) + { + $app['shop_register'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Shop/Spu/Client.php b/vendor/overtrue/wechat/src/MiniProgram/Shop/Spu/Client.php new file mode 100644 index 0000000..dc06fd1 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Shop/Spu/Client.php @@ -0,0 +1,132 @@ + + */ +class Client extends BaseClient +{ + /** + * 添加商品 + * + * @param array $product 商品信息 + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function add(array $product) + { + return $this->httpPostJson('shop/spu/add', $product); + } + + /** + * 删除商品 + * + * @param array $productId 商品编号信息 + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function del(array $productId) + { + return $this->httpPostJson('shop/spu/del', $productId); + } + + /** + * 获取商品 + * + * @param array $productId 商品编号信息 + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get(array $productId) + { + return $this->httpPostJson('shop/spu/get', $productId); + } + + /** + * 获取商品列表 + * + * @param array $product 商品信息 + * @param array $page 分页信息 + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getList(array $product, array $page) + { + return $this->httpPostJson('shop/spu/get_list', array_merge($product, $page)); + } + + /** + * 撤回商品审核 + * + * @param array $productId 商品编号信息 交易组件平台内部商品ID,与out_product_id二选一 + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delAudit(array $productId) + { + return $this->httpPostJson('shop/spu/del_audit', $productId); + } + + /** + * 更新商品 + * + * @param array $product + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(array $product) + { + return $this->httpPostJson('shop/spu/update', $product); + } + + /** + * 该免审更新商品 + * + * @param array $product + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function updateWithoutAudit(array $product) + { + return $this->httpPostJson('shop/spu/update_without_audit', $product); + } + + /** + * 上架商品 + * + * @param array $productId 商品编号数据 交易组件平台内部商品ID,与out_product_id二选一 + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function listing(array $productId) + { + return $this->httpPostJson('shop/spu/listing', $productId); + } + + /** + * 下架商品 + * + * @param array $productId + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delisting(array $productId) + { + return $this->httpPostJson('shop/spu/delisting', $productId); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Shop/Spu/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Shop/Spu/ServiceProvider.php new file mode 100644 index 0000000..c87344d --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Shop/Spu/ServiceProvider.php @@ -0,0 +1,25 @@ + + * @package EasyWeChat\MiniProgram\Shop\Spu + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc} + */ + public function register(Container $app) + { + $app['shop_spu'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/ShortLink/Client.php b/vendor/overtrue/wechat/src/MiniProgram/ShortLink/Client.php new file mode 100644 index 0000000..efce5f7 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/ShortLink/Client.php @@ -0,0 +1,42 @@ + + */ +class Client extends BaseClient +{ + /** + * 获取小程序 Short Link + * + * @param string $pageUrl + * @param string $pageTitle + * @param bool $isPermanent + * + * @return array|Collection|object|ResponseInterface|string + * + * @throws GuzzleException + * @throws InvalidConfigException + */ + public function getShortLink(string $pageUrl, string $pageTitle, bool $isPermanent = false) + { + $params = [ + 'page_url' => $pageUrl, + 'page_title' => $pageTitle, + 'is_permanent' => $isPermanent, + ]; + + return $this->httpPostJson('wxa/genwxashortlink', $params); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/ShortLink/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/ShortLink/ServiceProvider.php new file mode 100644 index 0000000..79a28ec --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/ShortLink/ServiceProvider.php @@ -0,0 +1,19 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Soter; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author her-cat + */ +class Client extends BaseClient +{ + /** + * @param string $openid + * @param string $json + * @param string $signature + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function verifySignature(string $openid, string $json, string $signature) + { + return $this->httpPostJson('cgi-bin/soter/verify_signature', [ + 'openid' => $openid, + 'json_string' => $json, + 'json_signature' => $signature, + ]); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Soter/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Soter/ServiceProvider.php new file mode 100644 index 0000000..c8520db --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Soter/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Soter; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author her-cat + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc} + */ + public function register(Container $app) + { + $app['soter'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/SubscribeMessage/Client.php b/vendor/overtrue/wechat/src/MiniProgram/SubscribeMessage/Client.php new file mode 100644 index 0000000..1e797ee --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/SubscribeMessage/Client.php @@ -0,0 +1,209 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\SubscribeMessage; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; +use ReflectionClass; + +/** + * Class Client. + * + * @author hugo + */ +class Client extends BaseClient +{ + /** + * {@inheritdoc}. + */ + protected $message = [ + 'touser' => '', + 'template_id' => '', + 'page' => '', + 'data' => [], + 'miniprogram_state' => 'formal', + ]; + + /** + * {@inheritdoc}. + */ + protected $required = ['touser', 'template_id', 'data']; + + /** + * Send a template message. + * + * @param array $data + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function send(array $data = []) + { + $params = $this->formatMessage($data); + + $this->restoreMessage(); + + return $this->httpPostJson('cgi-bin/message/subscribe/send', $params); + } + + /** + * @param array $data + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + protected function formatMessage(array $data = []) + { + $params = array_merge($this->message, $data); + + foreach ($params as $key => $value) { + if (in_array($key, $this->required, true) && empty($value) && empty($this->message[$key])) { + throw new InvalidArgumentException(sprintf('Attribute "%s" can not be empty!', $key)); + } + + $params[$key] = empty($value) ? $this->message[$key] : $value; + } + + foreach ($params['data'] as $key => $value) { + if (is_array($value)) { + if (\array_key_exists('value', $value)) { + $params['data'][$key] = ['value' => $value['value']]; + + continue; + } + + if (count($value) >= 1) { + $value = [ + 'value' => $value[0], +// 'color' => $value[1],// color unsupported + ]; + } + } else { + $value = [ + 'value' => strval($value), + ]; + } + + $params['data'][$key] = $value; + } + + return $params; + } + + /** + * Restore message. + */ + protected function restoreMessage() + { + $this->message = (new ReflectionClass(static::class))->getDefaultProperties()['message']; + } + + /** + * Combine templates and add them to your personal template library under your account. + * + * @param string $tid + * @param array $kidList + * @param string|null $sceneDesc + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function addTemplate(string $tid, array $kidList, string $sceneDesc = null) + { + $sceneDesc = $sceneDesc ?? ''; + $data = \compact('tid', 'kidList', 'sceneDesc'); + + return $this->httpPost('wxaapi/newtmpl/addtemplate', $data); + } + + /** + * Delete personal template under account. + * + * @param string $id + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function deleteTemplate(string $id) + { + return $this->httpPost('wxaapi/newtmpl/deltemplate', ['priTmplId' => $id]); + } + + /** + * Get keyword list under template title. + * + * @param string $tid + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getTemplateKeywords(string $tid) + { + return $this->httpGet('wxaapi/newtmpl/getpubtemplatekeywords', compact('tid')); + } + + /** + * Get the title of the public template under the category to which the account belongs. + * + * @param array $ids + * @param int $start + * @param int $limit + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getTemplateTitles(array $ids, int $start = 0, int $limit = 30) + { + $ids = \implode(',', $ids); + $query = \compact('ids', 'start', 'limit'); + + return $this->httpGet('wxaapi/newtmpl/getpubtemplatetitles', $query); + } + + /** + * Get list of personal templates under the current account. + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getTemplates() + { + return $this->httpGet('wxaapi/newtmpl/gettemplate'); + } + + /** + * Get the category of the applet account. + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getCategory() + { + return $this->httpGet('wxaapi/newtmpl/getcategory'); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/SubscribeMessage/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/SubscribeMessage/ServiceProvider.php new file mode 100644 index 0000000..726b3ed --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/SubscribeMessage/ServiceProvider.php @@ -0,0 +1,28 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\SubscribeMessage; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['subscribe_message'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/TemplateMessage/Client.php b/vendor/overtrue/wechat/src/MiniProgram/TemplateMessage/Client.php new file mode 100644 index 0000000..06dfbcb --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/TemplateMessage/Client.php @@ -0,0 +1,114 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\TemplateMessage; + +use EasyWeChat\OfficialAccount\TemplateMessage\Client as BaseClient; + +/** + * Class Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + public const API_SEND = 'cgi-bin/message/wxopen/template/send'; + + /** + * {@inheritdoc}. + */ + protected $message = [ + 'touser' => '', + 'template_id' => '', + 'page' => '', + 'form_id' => '', + 'data' => [], + 'emphasis_keyword' => '', + ]; + + /** + * {@inheritdoc}. + */ + protected $required = ['touser', 'template_id', 'form_id']; + + /** + * @param int $offset + * @param int $count + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function list(int $offset, int $count) + { + return $this->httpPostJson('cgi-bin/wxopen/template/library/list', compact('offset', 'count')); + } + + /** + * @param string $id + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get(string $id) + { + return $this->httpPostJson('cgi-bin/wxopen/template/library/get', compact('id')); + } + + /** + * @param string $id + * @param array $keyword + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function add(string $id, array $keyword) + { + return $this->httpPostJson('cgi-bin/wxopen/template/add', [ + 'id' => $id, + 'keyword_id_list' => $keyword, + ]); + } + + /** + * @param string $templateId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete(string $templateId) + { + return $this->httpPostJson('cgi-bin/wxopen/template/del', [ + 'template_id' => $templateId, + ]); + } + + /** + * @param int $offset + * @param int $count + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getTemplates(int $offset, int $count) + { + return $this->httpPostJson('cgi-bin/wxopen/template/list', compact('offset', 'count')); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/TemplateMessage/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/TemplateMessage/ServiceProvider.php new file mode 100644 index 0000000..776a15e --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/TemplateMessage/ServiceProvider.php @@ -0,0 +1,28 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\TemplateMessage; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['template_message'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/UniformMessage/Client.php b/vendor/overtrue/wechat/src/MiniProgram/UniformMessage/Client.php new file mode 100644 index 0000000..0cc84fe --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/UniformMessage/Client.php @@ -0,0 +1,146 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\UniformMessage; + +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; +use EasyWeChat\OfficialAccount\TemplateMessage\Client as BaseClient; + +class Client extends BaseClient +{ + public const API_SEND = 'cgi-bin/message/wxopen/template/uniform_send'; + + /** + * {@inheritdoc}. + * + * @var array + */ + protected $message = [ + 'touser' => '', + ]; + + /** + * Weapp Attributes. + * + * @var array + */ + protected $weappMessage = [ + 'template_id' => '', + 'page' => '', + 'form_id' => '', + 'data' => [], + 'emphasis_keyword' => '', + ]; + + /** + * Official account attributes. + * + * @var array + */ + protected $mpMessage = [ + 'appid' => '', + 'template_id' => '', + 'url' => '', + 'miniprogram' => [], + 'data' => [], + ]; + + /** + * Required attributes. + * + * @var array + */ + protected $required = ['touser', 'template_id', 'form_id', 'miniprogram', 'appid']; + + /** + * @param array $data + * + * @return array + * + * @throws InvalidArgumentException + */ + protected function formatMessage(array $data = []) + { + $params = array_merge($this->message, $data); + + if (empty($params['touser'])) { + throw new InvalidArgumentException(sprintf('Attribute "touser" can not be empty!')); + } + + if (!empty($params['weapp_template_msg'])) { + $params['weapp_template_msg'] = $this->formatWeappMessage($params['weapp_template_msg']); + } + + if (!empty($params['mp_template_msg'])) { + $params['mp_template_msg'] = $this->formatMpMessage($params['mp_template_msg']); + } + + return $params; + } + + /** + * @param array $data + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + protected function formatWeappMessage(array $data = []) + { + $params = $this->baseFormat($data, $this->weappMessage); + + $params['data'] = $this->formatData($params['data'] ?? []); + + return $params; + } + + /** + * @param array $data + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + protected function formatMpMessage(array $data = []) + { + $params = $this->baseFormat($data, $this->mpMessage); + + if (empty($params['miniprogram']['appid'])) { + $params['miniprogram']['appid'] = $this->app['config']['app_id']; + } + + $params['data'] = $this->formatData($params['data'] ?? []); + + return $params; + } + + /** + * @param array $data + * @param array $default + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + protected function baseFormat($data = [], $default = []) + { + $params = array_merge($default, $data); + foreach ($params as $key => $value) { + if (in_array($key, $this->required, true) && empty($value) && empty($default[$key])) { + throw new InvalidArgumentException(sprintf('Attribute "%s" can not be empty!', $key)); + } + + $params[$key] = empty($value) ? $default[$key] : $value; + } + + return $params; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/UniformMessage/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/UniformMessage/ServiceProvider.php new file mode 100644 index 0000000..0e86db3 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/UniformMessage/ServiceProvider.php @@ -0,0 +1,28 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\UniformMessage; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['uniform_message'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Union/Client.php b/vendor/overtrue/wechat/src/MiniProgram/Union/Client.php new file mode 100644 index 0000000..dd46d80 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Union/Client.php @@ -0,0 +1,228 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Union; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author Abbotton + */ +class Client extends BaseClient +{ + /** + * Add promotion. + * + * @param string $promotionSourceName + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function createPromotion(string $promotionSourceName) + { + $params = [ + 'promotionSourceName' => $promotionSourceName, + ]; + + return $this->httpPostJson('union/promoter/promotion/add', $params); + } + + /** + * Delete promotion. + * + * @param string $promotionSourcePid + * @param string $promotionSourceName + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function deletePromotion(string $promotionSourcePid, string $promotionSourceName) + { + $params = [ + 'promotionSourceName' => $promotionSourceName, + 'promotionSourcePid' => $promotionSourcePid, + ]; + + return $this->httpPostJson('union/promoter/promotion/del', $params); + } + + /** + * Update promotion. + * + * @param array $previousPromotionInfo + * @param array $promotionInfo + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function updatePromotion(array $previousPromotionInfo, array $promotionInfo) + { + $params = [ + 'previousPromotionInfo' => $previousPromotionInfo, + 'promotionInfo' => $promotionInfo, + ]; + + return $this->httpPostJson('union/promoter/promotion/upd', $params); + } + + /** + * Get a list of promotion spots. + * + * @param int $start + * @param int $limit + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getPromotionSourceList(int $start = 0, int $limit = 20) + { + $params = [ + 'start' => $start, + 'limit' => $limit + ]; + + return $this->httpGet('union/promoter/promotion/list', $params); + } + + /** + * Get the list of affiliate product categories and category IDs. + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getProductCategory() + { + return $this->httpGet('union/promoter/product/category'); + } + + /** + * Get the list and detail of affiliate product. + * + * @param array $params + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getProductList(array $params) + { + return $this->httpGet('union/promoter/product/list', $params); + } + + /** + * Get product promotion materials + * + * @param string $pid + * @param array $productList + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getProductMaterial(string $pid, array $productList) + { + $params = [ + 'pid' => $pid, + 'productList' => $productList, + ]; + + return $this->httpPostJson('union/promoter/product/generate', $params); + } + + /** + * Query order details based on order ID array. + * + * @param array $orderIdList + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getOrderInfo(array $orderIdList) + { + return $this->httpPostJson('union/promoter/order/info', $orderIdList); + } + + /** + * Query and filter the order list. + * + * @param int $page + * @param string $startTimestamp + * @param string $endTimestamp + * @param string $commissionStatus + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function searchOrder(int $page = 1, $startTimestamp = '', $endTimestamp = '', $commissionStatus = '') + { + $params = [ + 'page' => $page, + 'startTimestamp' => $startTimestamp, + 'endTimestamp' => $endTimestamp, + 'commissionStatus' => $commissionStatus + ]; + + return $this->httpGet('union/promoter/order/search', $params); + } + + /** + * Get featured products of union. + * + * @param array $params + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getFeaturedProducts(array $params) + { + return $this->httpGet('union/promoter/product/select', $params); + } + + /** + * Query the details of the targeted plan. + * + * @param array $params + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getTargetPlanInfo(array $params) + { + return $this->httpGet('union/promoter/target/plan_info', $params); + } + + /** + * Apply to join the targeted plan. + * + * @param array $params + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function applyJoinTargetPlan(array $params) + { + return $this->httpPostJson('union/promoter/target/apply_target', $params); + } + + /** + * Query the status of the targeted plan apply. + * + * @param array $params + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getTargetPlanStatus(array $params) + { + return $this->httpGet('union/promoter/target/apply_status', $params); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/Union/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Union/ServiceProvider.php new file mode 100644 index 0000000..ba1207a --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/Union/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\MiniProgram\Union; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author Abbotton + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['union'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/UrlLink/Client.php b/vendor/overtrue/wechat/src/MiniProgram/UrlLink/Client.php new file mode 100644 index 0000000..45b189f --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/UrlLink/Client.php @@ -0,0 +1,32 @@ +httpPostJson('wxa/generate_urllink', $param); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/UrlLink/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/UrlLink/ServiceProvider.php new file mode 100644 index 0000000..3201d36 --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/UrlLink/ServiceProvider.php @@ -0,0 +1,19 @@ +httpPostJson('wxa/generatescheme', $param); + } +} diff --git a/vendor/overtrue/wechat/src/MiniProgram/UrlScheme/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/UrlScheme/ServiceProvider.php new file mode 100644 index 0000000..cb8ad1a --- /dev/null +++ b/vendor/overtrue/wechat/src/MiniProgram/UrlScheme/ServiceProvider.php @@ -0,0 +1,19 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount; + +use EasyWeChat\BasicService; +use EasyWeChat\Kernel\ServiceContainer; + +/** + * Class Application. + * + * @author overtrue + * + * @property \EasyWeChat\BasicService\Media\Client $media + * @property \EasyWeChat\BasicService\Url\Client $url + * @property \EasyWeChat\BasicService\QrCode\Client $qrcode + * @property \EasyWeChat\BasicService\Jssdk\Client $jssdk + * @property \EasyWeChat\OfficialAccount\Auth\AccessToken $access_token + * @property \EasyWeChat\OfficialAccount\Server\Guard $server + * @property \EasyWeChat\OfficialAccount\User\UserClient $user + * @property \EasyWeChat\OfficialAccount\User\TagClient $user_tag + * @property \EasyWeChat\OfficialAccount\Menu\Client $menu + * @property \EasyWeChat\OfficialAccount\TemplateMessage\Client $template_message + * @property \EasyWeChat\OfficialAccount\SubscribeMessage\Client $subscribe_message + * @property \EasyWeChat\OfficialAccount\Material\Client $material + * @property \EasyWeChat\OfficialAccount\CustomerService\Client $customer_service + * @property \EasyWeChat\OfficialAccount\CustomerService\SessionClient $customer_service_session + * @property \EasyWeChat\OfficialAccount\Semantic\Client $semantic + * @property \EasyWeChat\OfficialAccount\DataCube\Client $data_cube + * @property \EasyWeChat\OfficialAccount\AutoReply\Client $auto_reply + * @property \EasyWeChat\OfficialAccount\Broadcasting\Client $broadcasting + * @property \EasyWeChat\OfficialAccount\Card\Card $card + * @property \EasyWeChat\OfficialAccount\Device\Client $device + * @property \EasyWeChat\OfficialAccount\ShakeAround\ShakeAround $shake_around + * @property \EasyWeChat\OfficialAccount\POI\Client $poi + * @property \EasyWeChat\OfficialAccount\Store\Client $store + * @property \EasyWeChat\OfficialAccount\Base\Client $base + * @property \EasyWeChat\OfficialAccount\Comment\Client $comment + * @property \EasyWeChat\OfficialAccount\OCR\Client $ocr + * @property \EasyWeChat\OfficialAccount\Goods\Client $goods + * @property \Overtrue\Socialite\Providers\WeChat $oauth + * @property \EasyWeChat\OfficialAccount\WiFi\Client $wifi + * @property \EasyWeChat\OfficialAccount\WiFi\CardClient $wifi_card + * @property \EasyWeChat\OfficialAccount\WiFi\DeviceClient $wifi_device + * @property \EasyWeChat\OfficialAccount\WiFi\ShopClient $wifi_shop + * @property \EasyWeChat\OfficialAccount\Guide\Client $guide + * @property \EasyWeChat\OfficialAccount\Draft\Client $draft + * @property \EasyWeChat\OfficialAccount\FreePublish\Client $free_publish + */ +class Application extends ServiceContainer +{ + /** + * @var array + */ + protected $providers = [ + Auth\ServiceProvider::class, + Server\ServiceProvider::class, + User\ServiceProvider::class, + OAuth\ServiceProvider::class, + Menu\ServiceProvider::class, + TemplateMessage\ServiceProvider::class, + SubscribeMessage\ServiceProvider::class, + Material\ServiceProvider::class, + CustomerService\ServiceProvider::class, + Semantic\ServiceProvider::class, + DataCube\ServiceProvider::class, + POI\ServiceProvider::class, + AutoReply\ServiceProvider::class, + Broadcasting\ServiceProvider::class, + Card\ServiceProvider::class, + Device\ServiceProvider::class, + ShakeAround\ServiceProvider::class, + Store\ServiceProvider::class, + Comment\ServiceProvider::class, + Base\ServiceProvider::class, + OCR\ServiceProvider::class, + Goods\ServiceProvider::class, + WiFi\ServiceProvider::class, + Draft\ServiceProvider::class, + FreePublish\ServiceProvider::class, + // Base services + BasicService\QrCode\ServiceProvider::class, + BasicService\Media\ServiceProvider::class, + BasicService\Url\ServiceProvider::class, + BasicService\Jssdk\ServiceProvider::class, + // Append Guide Interface + Guide\ServiceProvider::class, + ]; +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Auth/AccessToken.php b/vendor/overtrue/wechat/src/OfficialAccount/Auth/AccessToken.php new file mode 100644 index 0000000..6a6bc38 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Auth/AccessToken.php @@ -0,0 +1,39 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Auth; + +use EasyWeChat\Kernel\AccessToken as BaseAccessToken; + +/** + * Class AuthorizerAccessToken. + * + * @author overtrue + */ +class AccessToken extends BaseAccessToken +{ + /** + * @var string + */ + protected $endpointToGetToken = '/cgi-bin/token'; + + /** + * @return array + */ + protected function getCredentials(): array + { + return [ + 'grant_type' => 'client_credential', + 'appid' => $this->app['config']['app_id'], + 'secret' => $this->app['config']['secret'], + ]; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Auth/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Auth/ServiceProvider.php new file mode 100644 index 0000000..e748730 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Auth/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Auth; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + !isset($app['access_token']) && $app['access_token'] = function ($app) { + return new AccessToken($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/AutoReply/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/AutoReply/Client.php new file mode 100644 index 0000000..6d2e4d6 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/AutoReply/Client.php @@ -0,0 +1,34 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\AutoReply; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author overtrue + */ +class Client extends BaseClient +{ + /** + * Get current auto reply settings. + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function current() + { + return $this->httpGet('cgi-bin/get_current_autoreply_info'); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/AutoReply/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/AutoReply/ServiceProvider.php new file mode 100644 index 0000000..4377550 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/AutoReply/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\AutoReply; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['auto_reply'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Base/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Base/Client.php new file mode 100644 index 0000000..ce8abc5 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Base/Client.php @@ -0,0 +1,84 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Base; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; + +/** + * Class Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + /** + * Clear quota. + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function clearQuota() + { + $params = [ + 'appid' => $this->app['config']['app_id'], + ]; + + return $this->httpPostJson('cgi-bin/clear_quota', $params); + } + + /** + * Get wechat callback ip. + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function getValidIps() + { + return $this->httpGet('cgi-bin/getcallbackip'); + } + + /** + * Check the callback address network. + * + * @param string $action + * @param string $operator + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function checkCallbackUrl(string $action = 'all', string $operator = 'DEFAULT') + { + if (!in_array($action, ['dns', 'ping', 'all'], true)) { + throw new InvalidArgumentException('The action must be dns, ping, all.'); + } + + $operator = strtoupper($operator); + + if (!in_array($operator, ['CHINANET', 'UNICOM', 'CAP', 'DEFAULT'], true)) { + throw new InvalidArgumentException('The operator must be CHINANET, UNICOM, CAP, DEFAULT.'); + } + + $params = [ + 'action' => $action, + 'check_operator' => $operator, + ]; + + return $this->httpPostJson('cgi-bin/callback/check', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Base/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Base/ServiceProvider.php new file mode 100644 index 0000000..409593d --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Base/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Base; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author mingyoung + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['base'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/Client.php new file mode 100644 index 0000000..4910a64 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/Client.php @@ -0,0 +1,383 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Broadcasting; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Contracts\MessageInterface; +use EasyWeChat\Kernel\Exceptions\RuntimeException; +use EasyWeChat\Kernel\Messages\Card; +use EasyWeChat\Kernel\Messages\Image; +use EasyWeChat\Kernel\Messages\Media; +use EasyWeChat\Kernel\Messages\Text; +use EasyWeChat\Kernel\Support\Arr; + +/** + * Class Client. + * + * @method \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string previewTextByName($text, $name); + * @method \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string previewNewsByName($mediaId, $name); + * @method \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string previewVoiceByName($mediaId, $name); + * @method \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string previewImageByName($mediaId, $name); + * @method \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string previewVideoByName($message, $name); + * @method \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string previewCardByName($cardId, $name); + * + * @author overtrue + */ +class Client extends BaseClient +{ + public const PREVIEW_BY_OPENID = 'touser'; + public const PREVIEW_BY_NAME = 'towxname'; + + /** + * Send a message. + * + * @param array $message + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function send(array $message) + { + if (empty($message['filter']) && empty($message['touser'])) { + throw new RuntimeException('The message reception object is not specified'); + } + + $api = Arr::get($message, 'touser') ? 'cgi-bin/message/mass/send' : 'cgi-bin/message/mass/sendall'; + + return $this->httpPostJson($api, $message); + } + + /** + * Preview a message. + * + * @param array $message + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function preview(array $message) + { + return $this->httpPostJson('cgi-bin/message/mass/preview', $message); + } + + /** + * Delete a broadcast. + * + * @param string $msgId + * @param int $index + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete(string $msgId, int $index = 0) + { + $options = [ + 'msg_id' => $msgId, + 'article_idx' => $index, + ]; + + return $this->httpPostJson('cgi-bin/message/mass/delete', $options); + } + + /** + * Get a broadcast status. + * + * @param string $msgId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function status(string $msgId) + { + $options = [ + 'msg_id' => $msgId, + ]; + + return $this->httpPostJson('cgi-bin/message/mass/get', $options); + } + + /** + * Send a text message. + * + * @param string $message + * @param mixed $reception + * @param array $attributes + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public function sendText(string $message, $reception = null, array $attributes = []) + { + return $this->sendMessage(new Text($message), $reception, $attributes); + } + + /** + * Send a news message. + * + * @param string $mediaId + * @param mixed $reception + * @param array $attributes + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public function sendNews(string $mediaId, $reception = null, array $attributes = []) + { + return $this->sendMessage(new Media($mediaId, 'mpnews'), $reception, $attributes); + } + + /** + * Send a voice message. + * + * @param string $mediaId + * @param mixed $reception + * @param array $attributes + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public function sendVoice(string $mediaId, $reception = null, array $attributes = []) + { + return $this->sendMessage(new Media($mediaId, 'voice'), $reception, $attributes); + } + + /** + * Send a image message. + * + * @param string $mediaId + * @param mixed $reception + * @param array $attributes + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public function sendImage(string $mediaId, $reception = null, array $attributes = []) + { + return $this->sendMessage(new Image($mediaId), $reception, $attributes); + } + + /** + * Send a video message. + * + * @param string $mediaId + * @param mixed $reception + * @param array $attributes + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public function sendVideo(string $mediaId, $reception = null, array $attributes = []) + { + return $this->sendMessage(new Media($mediaId, 'mpvideo'), $reception, $attributes); + } + + /** + * Send a card message. + * + * @param string $cardId + * @param mixed $reception + * @param array $attributes + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public function sendCard(string $cardId, $reception = null, array $attributes = []) + { + return $this->sendMessage(new Card($cardId), $reception, $attributes); + } + + /** + * Preview a text message. + * + * @param string $message message + * @param string $reception + * @param string $method + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function previewText(string $message, $reception, $method = self::PREVIEW_BY_OPENID) + { + return $this->previewMessage(new Text($message), $reception, $method); + } + + /** + * Preview a news message. + * + * @param string $mediaId message + * @param string $reception + * @param string $method + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function previewNews(string $mediaId, $reception, $method = self::PREVIEW_BY_OPENID) + { + return $this->previewMessage(new Media($mediaId, 'mpnews'), $reception, $method); + } + + /** + * Preview a voice message. + * + * @param string $mediaId message + * @param string $reception + * @param string $method + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function previewVoice(string $mediaId, $reception, $method = self::PREVIEW_BY_OPENID) + { + return $this->previewMessage(new Media($mediaId, 'voice'), $reception, $method); + } + + /** + * Preview a image message. + * + * @param string $mediaId message + * @param string $reception + * @param string $method + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function previewImage(string $mediaId, $reception, $method = self::PREVIEW_BY_OPENID) + { + return $this->previewMessage(new Image($mediaId), $reception, $method); + } + + /** + * Preview a video message. + * + * @param string $mediaId message + * @param string $reception + * @param string $method + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function previewVideo(string $mediaId, $reception, $method = self::PREVIEW_BY_OPENID) + { + return $this->previewMessage(new Media($mediaId, 'mpvideo'), $reception, $method); + } + + /** + * Preview a card message. + * + * @param string $cardId message + * @param string $reception + * @param string $method + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function previewCard(string $cardId, $reception, $method = self::PREVIEW_BY_OPENID) + { + return $this->previewMessage(new Card($cardId), $reception, $method); + } + + /** + * @param \EasyWeChat\Kernel\Contracts\MessageInterface $message + * @param string $reception + * @param string $method + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function previewMessage(MessageInterface $message, string $reception, $method = self::PREVIEW_BY_OPENID) + { + $message = (new MessageBuilder())->message($message)->buildForPreview($method, $reception); + + return $this->preview($message); + } + + /** + * @param \EasyWeChat\Kernel\Contracts\MessageInterface $message + * @param mixed $reception + * @param array $attributes + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public function sendMessage(MessageInterface $message, $reception = null, $attributes = []) + { + $message = (new MessageBuilder())->message($message)->with($attributes)->toAll(); + + if (\is_int($reception)) { + $message->toTag($reception); + } elseif (\is_array($reception)) { + $message->toUsers($reception); + } + + return $this->send($message->build()); + } + + /** + * @codeCoverageIgnore + * + * @param string $method + * @param array $args + * + * @return mixed + */ + public function __call($method, $args) + { + if (strpos($method, 'ByName') > 0) { + $method = strstr($method, 'ByName', true); + + if (method_exists($this, $method)) { + array_push($args, self::PREVIEW_BY_NAME); + + return $this->$method(...$args); + } + } + + throw new \BadMethodCallException(sprintf('Method %s not exists.', $method)); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/MessageBuilder.php b/vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/MessageBuilder.php new file mode 100644 index 0000000..70465b0 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/MessageBuilder.php @@ -0,0 +1,162 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Broadcasting; + +use EasyWeChat\Kernel\Contracts\MessageInterface; +use EasyWeChat\Kernel\Exceptions\RuntimeException; + +/** + * Class MessageBuilder. + * + * @author overtrue + */ +class MessageBuilder +{ + /** + * @var array + */ + protected $to = []; + + /** + * @var \EasyWeChat\Kernel\Contracts\MessageInterface + */ + protected $message; + + /** + * @var array + */ + protected $attributes = []; + + /** + * Set message. + * + * @param \EasyWeChat\Kernel\Contracts\MessageInterface $message + * + * @return $this + */ + public function message(MessageInterface $message) + { + $this->message = $message; + + return $this; + } + + /** + * Set target user or group. + * + * @param array $to + * + * @return $this + */ + public function to(array $to) + { + $this->to = $to; + + return $this; + } + + /** + * @param int $tagId + * + * @return \EasyWeChat\OfficialAccount\Broadcasting\MessageBuilder + */ + public function toTag(int $tagId) + { + $this->to([ + 'filter' => [ + 'is_to_all' => false, + 'tag_id' => $tagId, + ], + ]); + + return $this; + } + + /** + * @param array $openids + * + * @return \EasyWeChat\OfficialAccount\Broadcasting\MessageBuilder + */ + public function toUsers(array $openids) + { + $this->to([ + 'touser' => $openids, + ]); + + return $this; + } + + /** + * @return $this + */ + public function toAll() + { + $this->to([ + 'filter' => ['is_to_all' => true], + ]); + + return $this; + } + + /** + * @param array $attributes + * + * @return \EasyWeChat\OfficialAccount\Broadcasting\MessageBuilder + */ + public function with(array $attributes) + { + $this->attributes = $attributes; + + return $this; + } + + /** + * Build message. + * + * @param array $prepends + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public function build(array $prepends = []): array + { + if (empty($this->message)) { + throw new RuntimeException('No message content to send.'); + } + + $content = $this->message->transformForJsonRequest(); + + if (empty($prepends)) { + $prepends = $this->to; + } + + $message = array_merge($prepends, $content, $this->attributes); + + return $message; + } + + /** + * Build preview message. + * + * @param string $by + * @param string $user + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public function buildForPreview(string $by, string $user): array + { + return $this->build([$by => $user]); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/ServiceProvider.php new file mode 100644 index 0000000..1f956cf --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Broadcasting; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['broadcasting'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/BoardingPassClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/BoardingPassClient.php new file mode 100644 index 0000000..b1575ab --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/BoardingPassClient.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Card; + +/** + * Class BoardingPassClient. + * + * @author overtrue + */ +class BoardingPassClient extends Client +{ + /** + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function checkin(array $params) + { + return $this->httpPostJson('card/boardingpass/checkin', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/Card.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/Card.php new file mode 100644 index 0000000..14e817b --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/Card.php @@ -0,0 +1,52 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Card; + +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; + +/** + * Class Card. + * + * @author overtrue + * + * @property \EasyWeChat\OfficialAccount\Card\CodeClient $code + * @property \EasyWeChat\OfficialAccount\Card\MeetingTicketClient $meeting_ticket + * @property \EasyWeChat\OfficialAccount\Card\MemberCardClient $member_card + * @property \EasyWeChat\OfficialAccount\Card\GeneralCardClient $general_card + * @property \EasyWeChat\OfficialAccount\Card\MovieTicketClient $movie_ticket + * @property \EasyWeChat\OfficialAccount\Card\CoinClient $coin + * @property \EasyWeChat\OfficialAccount\Card\SubMerchantClient $sub_merchant + * @property \EasyWeChat\OfficialAccount\Card\BoardingPassClient $boarding_pass + * @property \EasyWeChat\OfficialAccount\Card\JssdkClient $jssdk + * @property \EasyWeChat\OfficialAccount\Card\GiftCardClient $gift_card + * @property \EasyWeChat\OfficialAccount\Card\GiftCardOrderClient $gift_card_order + * @property \EasyWeChat\OfficialAccount\Card\GiftCardPageClient $gift_card_page + * @property \EasyWeChat\OfficialAccount\Card\InvoiceClient $invoice + */ +class Card extends Client +{ + /** + * @param string $property + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + public function __get($property) + { + if (isset($this->app["card.{$property}"])) { + return $this->app["card.{$property}"]; + } + + throw new InvalidArgumentException(sprintf('No card service named "%s".', $property)); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/Client.php new file mode 100644 index 0000000..7395d09 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/Client.php @@ -0,0 +1,428 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Card; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Traits\InteractsWithCache; + +/** + * Class Client. + * + * @author overtrue + */ +class Client extends BaseClient +{ + use InteractsWithCache; + + /** + * @var string + */ + protected $url; + + /** + * Ticket cache key. + * + * @var string + */ + protected $ticketCacheKey; + + /** + * Ticket cache prefix. + * + * @var string + */ + protected $ticketCachePrefix = 'easywechat.official_account.card.api_ticket.'; + + /** + * 获取卡券颜色. + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function colors() + { + return $this->httpGet('card/getcolors'); + } + + /** + * 卡券开放类目查询接口. + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function categories() + { + return $this->httpGet('card/getapplyprotocol'); + } + + /** + * 创建卡券. + * + * @param string $cardType + * @param array $attributes + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function create($cardType = 'member_card', array $attributes = []) + { + $params = [ + 'card' => [ + 'card_type' => strtoupper($cardType), + strtolower($cardType) => $attributes, + ], + ]; + + return $this->httpPostJson('card/create', $params); + } + + /** + * 查看卡券详情. + * + * @param string $cardId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get($cardId) + { + $params = [ + 'card_id' => $cardId, + ]; + + return $this->httpPostJson('card/get', $params); + } + + /** + * 批量查询卡列表. + * + * @param int $offset + * @param int $count + * @param string $statusList + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function list($offset = 0, $count = 10, $statusList = 'CARD_STATUS_VERIFY_OK') + { + $params = [ + 'offset' => $offset, + 'count' => $count, + 'status_list' => $statusList, + ]; + + return $this->httpPostJson('card/batchget', $params); + } + + /** + * 更改卡券信息接口 and 设置跟随推荐接口. + * + * @param string $cardId + * @param string $type + * @param array $attributes + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update($cardId, $type, array $attributes = []) + { + $card = []; + $card['card_id'] = $cardId; + $card[strtolower($type)] = $attributes; + + return $this->httpPostJson('card/update', $card); + } + + /** + * 删除卡券接口. + * + * @param string $cardId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete($cardId) + { + $params = [ + 'card_id' => $cardId, + ]; + + return $this->httpPostJson('card/delete', $params); + } + + /** + * 创建二维码. + * + * @param array $cards + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function createQrCode(array $cards) + { + return $this->httpPostJson('card/qrcode/create', $cards); + } + + /** + * ticket 换取二维码图片. + * + * @param string $ticket + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getQrCode($ticket) + { + $baseUri = 'https://mp.weixin.qq.com/cgi-bin/showqrcode'; + $params = [ + 'ticket' => $ticket, + ]; + + $response = $this->requestRaw($baseUri, 'GET', $params); + + return [ + 'status' => $response->getStatusCode(), + 'reason' => $response->getReasonPhrase(), + 'headers' => $response->getHeaders(), + 'body' => strval($response->getBody()), + 'url' => $baseUri.'?'.http_build_query($params), + ]; + } + + /** + * 通过ticket换取二维码 链接. + * + * @param string $ticket + * + * @return string + */ + public function getQrCodeUrl($ticket) + { + return sprintf('https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=%s', $ticket); + } + + /** + * 创建货架接口. + * + * @param string $banner + * @param string $pageTitle + * @param bool $canShare + * @param string $scene [SCENE_NEAR_BY 附近,SCENE_MENU 自定义菜单,SCENE_QRCODE 二维码,SCENE_ARTICLE 公众号文章, + * SCENE_H5 h5页面,SCENE_IVR 自动回复,SCENE_CARD_CUSTOM_CELL 卡券自定义cell] + * @param array $cardList + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function createLandingPage($banner, $pageTitle, $canShare, $scene, $cardList) + { + $params = [ + 'banner' => $banner, + 'page_title' => $pageTitle, + 'can_share' => $canShare, + 'scene' => $scene, + 'card_list' => $cardList, + ]; + + return $this->httpPostJson('card/landingpage/create', $params); + } + + /** + * 图文消息群发卡券. + * + * @param string $cardId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getHtml($cardId) + { + $params = [ + 'card_id' => $cardId, + ]; + + return $this->httpPostJson('card/mpnews/gethtml', $params); + } + + /** + * 设置测试白名单. + * + * @param array $openids + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function setTestWhitelist($openids) + { + $params = [ + 'openid' => $openids, + ]; + + return $this->httpPostJson('card/testwhitelist/set', $params); + } + + /** + * 设置测试白名单(by username). + * + * @param array $usernames + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function setTestWhitelistByName(array $usernames) + { + $params = [ + 'username' => $usernames, + ]; + + return $this->httpPostJson('card/testwhitelist/set', $params); + } + + /** + * 获取用户已领取卡券接口. + * + * @param string $openid + * @param string $cardId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getUserCards($openid, $cardId = '') + { + $params = [ + 'openid' => $openid, + 'card_id' => $cardId, + ]; + + return $this->httpPostJson('card/user/getcardlist', $params); + } + + /** + * 设置微信买单接口. + * 设置买单的 card_id 必须已经配置了门店,否则会报错. + * + * @param string $cardId + * @param bool $isOpen + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function setPayCell($cardId, $isOpen = true) + { + $params = [ + 'card_id' => $cardId, + 'is_open' => $isOpen, + ]; + + return $this->httpPostJson('card/paycell/set', $params); + } + + /** + * 设置自助核销接口 + * 设置买单的 card_id 必须已经配置了门店,否则会报错. + * + * @param string $cardId + * @param bool $isOpen + * @param bool $verifyCod + * @param bool $remarkAmount + * + * @return mixed + */ + public function setPayConsumeCell($cardId, $isOpen = true, $verifyCod = false, $remarkAmount = false) + { + $params = [ + 'card_id' => $cardId, + 'is_open' => $isOpen, + 'need_verify_cod' => $verifyCod, + 'need_remark_amount' => $remarkAmount, + ]; + + return $this->httpPostJson('card/selfconsumecell/set', $params); + } + + /** + * 增加库存. + * + * @param string $cardId + * @param int $amount + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + */ + public function increaseStock($cardId, $amount) + { + return $this->updateStock($cardId, $amount, 'increase'); + } + + /** + * 减少库存. + * + * @param string $cardId + * @param int $amount + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + */ + public function reduceStock($cardId, $amount) + { + return $this->updateStock($cardId, $amount, 'reduce'); + } + + /** + * 修改库存接口. + * + * @param string $cardId + * @param int $amount + * @param string $action + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function updateStock($cardId, $amount, $action = 'increase') + { + $key = 'increase' === $action ? 'increase_stock_value' : 'reduce_stock_value'; + $params = [ + 'card_id' => $cardId, + $key => abs($amount), + ]; + + return $this->httpPostJson('card/modifystock', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/CodeClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/CodeClient.php new file mode 100644 index 0000000..dcf96da --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/CodeClient.php @@ -0,0 +1,193 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Card; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class CodeClient. + * + * @author overtrue + */ +class CodeClient extends BaseClient +{ + /** + * 导入code接口. + * + * @param string $cardId + * @param array $codes + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function deposit(string $cardId, array $codes) + { + $params = [ + 'card_id' => $cardId, + 'code' => $codes, + ]; + + return $this->httpPostJson('card/code/deposit', $params); + } + + /** + * 查询导入code数目. + * + * @param string $cardId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getDepositedCount(string $cardId) + { + $params = [ + 'card_id' => $cardId, + ]; + + return $this->httpPostJson('card/code/getdepositcount', $params); + } + + /** + * 核查code接口. + * + * @param string $cardId + * @param array $codes + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function check(string $cardId, array $codes) + { + $params = [ + 'card_id' => $cardId, + 'code' => $codes, + ]; + + return $this->httpPostJson('card/code/checkcode', $params); + } + + /** + * 查询 Code 接口. + * + * @param string $code + * @param string $cardId + * @param bool $checkConsume + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get(string $code, string $cardId = '', bool $checkConsume = true) + { + $params = [ + 'code' => $code, + 'check_consume' => $checkConsume, + 'card_id' => $cardId, + ]; + + return $this->httpPostJson('card/code/get', $params); + } + + /** + * 更改Code接口. + * + * @param string $code + * @param string $newCode + * @param string $cardId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(string $code, string $newCode, string $cardId = '') + { + $params = [ + 'code' => $code, + 'new_code' => $newCode, + 'card_id' => $cardId, + ]; + + return $this->httpPostJson('card/code/update', $params); + } + + /** + * 设置卡券失效. + * + * @param string $code + * @param string $cardId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function disable(string $code, string $cardId = '') + { + $params = [ + 'code' => $code, + 'card_id' => $cardId, + ]; + + return $this->httpPostJson('card/code/unavailable', $params); + } + + /** + * 核销 Code 接口. + * + * @param string $code + * @param string|null $cardId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function consume(string $code, string $cardId = null) + { + $params = [ + 'code' => $code, + ]; + + if (!is_null($cardId)) { + $params['card_id'] = $cardId; + } + + return $this->httpPostJson('card/code/consume', $params); + } + + /** + * Code解码接口. + * + * @param string $encryptedCode + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function decrypt(string $encryptedCode) + { + $params = [ + 'encrypt_code' => $encryptedCode, + ]; + + return $this->httpPostJson('card/code/decrypt', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/CoinClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/CoinClient.php new file mode 100644 index 0000000..b99d841 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/CoinClient.php @@ -0,0 +1,119 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Card; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class CoinClient. + * + * @author overtrue + */ +class CoinClient extends BaseClient +{ + /** + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function activate() + { + return $this->httpGet('card/pay/activate'); + } + + /** + * @param string $cardId + * @param int $quantity + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getPrice(string $cardId, int $quantity) + { + return $this->httpPostJson('card/pay/getpayprice', [ + 'card_id' => $cardId, + 'quantity' => $quantity, + ]); + } + + /** + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function summary() + { + return $this->httpGet('card/pay/getcoinsinfo'); + } + + /** + * @param int $count + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function recharge(int $count) + { + return $this->httpPostJson('card/pay/recharge', [ + 'coin_count' => $count, + ]); + } + + /** + * @param string $orderId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function order(string $orderId) + { + return $this->httpPostJson('card/pay/getorder', ['order_id' => $orderId]); + } + + /** + * @param array $filters + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function orders(array $filters) + { + return $this->httpPostJson('card/pay/getorderlist', $filters); + } + + /** + * @param string $cardId + * @param string $orderId + * @param int $quantity + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function confirm(string $cardId, string $orderId, int $quantity) + { + return $this->httpPostJson('card/pay/confirm', [ + 'card_id' => $cardId, + 'order_id' => $orderId, + 'quantity' => $quantity, + ]); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/GeneralCardClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/GeneralCardClient.php new file mode 100644 index 0000000..1505981 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/GeneralCardClient.php @@ -0,0 +1,71 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Card; + +/** + * Class GeneralCardClient. + * + * @author overtrue + */ +class GeneralCardClient extends Client +{ + /** + * 通用卡接口激活. + * + * @param array $info + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function activate(array $info = []) + { + return $this->httpPostJson('card/generalcard/activate', $info); + } + + /** + * 通用卡撤销激活. + * + * @param string $cardId + * @param string $code + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function deactivate(string $cardId, string $code) + { + $params = [ + 'card_id' => $cardId, + 'code' => $code, + ]; + + return $this->httpPostJson('card/generalcard/unactivate', $params); + } + + /** + * 更新会员信息. + * + * @param array $params + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function updateUser(array $params = []) + { + return $this->httpPostJson('card/generalcard/updateuser', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/GiftCardClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/GiftCardClient.php new file mode 100644 index 0000000..d8779d0 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/GiftCardClient.php @@ -0,0 +1,74 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Card; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class GiftCardClient. + * + * @author overtrue + */ +class GiftCardClient extends BaseClient +{ + /** + * 申请微信支付礼品卡权限接口. + * + * @param string $subMchId + * + * @return mixed + */ + public function add(string $subMchId) + { + $params = [ + 'sub_mch_id' => $subMchId, + ]; + + return $this->httpPostJson('card/giftcard/pay/whitelist/add', $params); + } + + /** + * 绑定商户号到礼品卡小程序接口(商户号必须为公众号申请的商户号,否则报错). + * + * @param string $subMchId + * @param string $wxaAppid + * + * @return mixed + */ + public function bind(string $subMchId, string $wxaAppid) + { + $params = [ + 'sub_mch_id' => $subMchId, + 'wxa_appid' => $wxaAppid, + ]; + + return $this->httpPostJson('card/giftcard/pay/submch/bind', $params); + } + + /** + * 上传小程序代码. + * + * @param string $wxaAppid + * @param string $pageId + * + * @return mixed + */ + public function set(string $wxaAppid, string $pageId) + { + $params = [ + 'wxa_appid' => $wxaAppid, + 'page_id' => $pageId, + ]; + + return $this->httpPostJson('card/giftcard/wxa/set', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/GiftCardOrderClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/GiftCardOrderClient.php new file mode 100644 index 0000000..b4e1b15 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/GiftCardOrderClient.php @@ -0,0 +1,78 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Card; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class GiftCardOrderClient. + * + * @author overtrue + */ +class GiftCardOrderClient extends BaseClient +{ + /** + * 查询-单个礼品卡订单信息接口. + * + * @param string $orderId + * + * @return mixed + */ + public function get(string $orderId) + { + $params = [ + 'order_id' => $orderId, + ]; + + return $this->httpPostJson('card/giftcard/order/get', $params); + } + + /** + * 查询-批量查询礼品卡订单信息接口. + * + * @param int $beginTime + * @param int $endTime + * @param int $offset + * @param int $count + * @param string $sortType + * + * @return mixed + */ + public function list(int $beginTime, int $endTime, int $offset = 0, int $count = 10, string $sortType = 'ASC') + { + $params = [ + 'begin_time' => $beginTime, + 'end_time' => $endTime, + 'sort_type' => $sortType, + 'offset' => $offset, + 'count' => $count, + ]; + + return $this->httpPostJson('card/giftcard/order/batchget', $params); + } + + /** + * 退款接口. + * + * @param string $orderId + * + * @return mixed + */ + public function refund(string $orderId) + { + $params = [ + 'order_id' => $orderId, + ]; + + return $this->httpPostJson('card/giftcard/order/refund', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/GiftCardPageClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/GiftCardPageClient.php new file mode 100644 index 0000000..bf36109 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/GiftCardPageClient.php @@ -0,0 +1,102 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Card; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class GiftCardPageClient. + * + * @author overtrue + */ +class GiftCardPageClient extends BaseClient +{ + /** + * 创建-礼品卡货架接口. + * + * @param array $attributes + * + * @return mixed + */ + public function add(array $attributes) + { + $params = [ + 'page' => $attributes, + ]; + + return $this->httpPostJson('card/giftcard/page/add', $params); + } + + /** + * 查询-礼品卡货架信息接口. + * + * @param string $pageId + * + * @return mixed + */ + public function get(string $pageId) + { + $params = [ + 'page_id' => $pageId, + ]; + + return $this->httpPostJson('card/giftcard/page/get', $params); + } + + /** + * 修改-礼品卡货架信息接口. + * + * @param string $pageId + * @param string $bannerPicUrl + * @param array $themeList + * + * @return mixed + */ + public function update(string $pageId, string $bannerPicUrl, array $themeList) + { + $params = [ + 'page' => [ + 'page_id' => $pageId, + 'banner_pic_url' => $bannerPicUrl, + 'theme_list' => $themeList, + ], + ]; + + return $this->httpPostJson('card/giftcard/page/update', $params); + } + + /** + * 查询-礼品卡货架列表接口. + * + * @return mixed + */ + public function list() + { + return $this->httpPostJson('card/giftcard/page/batchget'); + } + + /** + * 下架-礼品卡货架接口(下架某一个货架或者全部货架). + * + * @param string $pageId + * + * @return mixed + */ + public function setMaintain(string $pageId = '') + { + $params = ($pageId ? ['page_id' => $pageId] : ['all' => true]) + [ + 'maintain' => true, + ]; + + return $this->httpPostJson('card/giftcard/maintain/set', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/InvoiceClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/InvoiceClient.php new file mode 100644 index 0000000..127b3af --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/InvoiceClient.php @@ -0,0 +1,113 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Card; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class InvoiceClient. + * + * @author overtrue + */ +class InvoiceClient extends BaseClient +{ + /** + * 设置支付后开票信息接口. + * + * @param string $mchid + * @param string $sPappid + * + * @return mixed + */ + public function set(string $mchid, string $sPappid) + { + $params = [ + 'paymch_info' => [ + 'mchid' => $mchid, + 's_pappid' => $sPappid, + ], + ]; + + return $this->setBizAttr('set_pay_mch', $params); + } + + /** + * 查询支付后开票信息接口. + * + * @return mixed + */ + public function get() + { + return $this->setBizAttr('get_pay_mch'); + } + + /** + * 设置授权页字段信息接口. + * + * @param array $userData + * @param array $bizData + * + * @return mixed + */ + public function setAuthField(array $userData, array $bizData) + { + $params = [ + 'auth_field' => [ + 'user_field' => $userData, + 'biz_field' => $bizData, + ], + ]; + + return $this->setBizAttr('set_auth_field', $params); + } + + /** + * 查询授权页字段信息接口. + * + * @return mixed + */ + public function getAuthField() + { + return $this->setBizAttr('get_auth_field'); + } + + /** + * 查询开票信息. + * + * @param string $orderId + * @param string $appId + * + * @return mixed + */ + public function getAuthData(string $appId, string $orderId) + { + $params = [ + 'order_id' => $orderId, + 's_appid' => $appId, + ]; + + return $this->httpPost('card/invoice/getauthdata', $params); + } + + /** + * @param string $action + * @param array $params + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + private function setBizAttr(string $action, array $params = []) + { + return $this->httpPostJson('card/invoice/setbizattr', $params, ['action' => $action]); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/JssdkClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/JssdkClient.php new file mode 100644 index 0000000..9a6bb51 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/JssdkClient.php @@ -0,0 +1,85 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Card; + +use EasyWeChat\BasicService\Jssdk\Client as Jssdk; +use EasyWeChat\Kernel\Support\Arr; +use function EasyWeChat\Kernel\Support\str_random; + +/** + * Class Jssdk. + * + * @author overtrue + */ +class JssdkClient extends Jssdk +{ + /** + * @param bool $refresh + * @param string $type + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \Psr\SimpleCache\InvalidArgumentException + */ + public function getTicket(bool $refresh = false, string $type = 'wx_card'): array + { + return parent::getTicket($refresh, $type); + } + + /** + * 微信卡券:JSAPI 卡券发放. + * + * @param array $cards + * + * @return string + */ + public function assign(array $cards) + { + return json_encode(array_map(function ($card) { + return $this->attachExtension($card['card_id'], $card); + }, $cards)); + } + + /** + * 生成 js添加到卡包 需要的 card_list 项. + * + * @param string $cardId + * @param array $extension + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \Psr\SimpleCache\InvalidArgumentException + */ + public function attachExtension($cardId, array $extension = []) + { + $timestamp = time(); + $nonce = str_random(6); + $ticket = $this->getTicket()['ticket']; + + $ext = array_merge(['timestamp' => $timestamp, 'nonce_str' => $nonce], Arr::only( + $extension, + ['code', 'openid', 'outer_id', 'balance', 'fixed_begintimestamp', 'outer_str'] + )); + + $ext['signature'] = $this->dictionaryOrderSignature($ticket, $timestamp, $cardId, $ext['code'] ?? '', $ext['openid'] ?? '', $nonce); + + return [ + 'cardId' => $cardId, + 'cardExt' => json_encode($ext), + ]; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/MeetingTicketClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/MeetingTicketClient.php new file mode 100644 index 0000000..892c5ad --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/MeetingTicketClient.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Card; + +/** + * Class MeetingTicketClient. + * + * @author overtrue + */ +class MeetingTicketClient extends Client +{ + /** + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function updateUser(array $params) + { + return $this->httpPostJson('card/meetingticket/updateuser', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/MemberCardClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/MemberCardClient.php new file mode 100644 index 0000000..41d051c --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/MemberCardClient.php @@ -0,0 +1,123 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Card; + +/** + * Class MemberCardClient. + * + * @author overtrue + */ +class MemberCardClient extends Client +{ + /** + * 会员卡接口激活. + * + * @param array $info + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function activate(array $info = []) + { + return $this->httpPostJson('card/membercard/activate', $info); + } + + /** + * 设置开卡字段接口. + * + * @param string $cardId + * @param array $settings + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function setActivationForm(string $cardId, array $settings) + { + $params = array_merge(['card_id' => $cardId], $settings); + + return $this->httpPostJson('card/membercard/activateuserform/set', $params); + } + + /** + * 拉取会员信息接口. + * + * @param string $cardId + * @param string $code + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getUser(string $cardId, string $code) + { + $params = [ + 'card_id' => $cardId, + 'code' => $code, + ]; + + return $this->httpPostJson('card/membercard/userinfo/get', $params); + } + + /** + * 更新会员信息. + * + * @param array $params + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function updateUser(array $params = []) + { + return $this->httpPostJson('card/membercard/updateuser', $params); + } + + /** + * 获取用户提交资料. + * + * @param string $activateTicket + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getActivationForm($activateTicket) + { + $params = [ + 'activate_ticket' => $activateTicket, + ]; + + return $this->httpPostJson('card/membercard/activatetempinfo/get', $params); + } + + /** + * 获取开卡组件链接接口. + * + * @param array $params 包含会员卡ID和随机字符串 + * + * @return string 开卡组件链接 + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getActivateUrl(array $params = []) + { + return $this->httpPostJson('card/membercard/activate/geturl', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/MovieTicketClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/MovieTicketClient.php new file mode 100644 index 0000000..e6d9036 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/MovieTicketClient.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Card; + +/** + * Class MovieTicketClient. + * + * @author overtrue + */ +class MovieTicketClient extends Client +{ + /** + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function updateUser(array $params) + { + return $this->httpPostJson('card/movieticket/updateuser', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/ServiceProvider.php new file mode 100644 index 0000000..3807fa3 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/ServiceProvider.php @@ -0,0 +1,89 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Card; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['card'] = function ($app) { + return new Card($app); + }; + + $app['card.client'] = function ($app) { + return new Client($app); + }; + + $app['card.coin'] = function ($app) { + return new CoinClient($app); + }; + + $app['card.sub_merchant'] = function ($app) { + return new SubMerchantClient($app); + }; + + $app['card.code'] = function ($app) { + return new CodeClient($app); + }; + + $app['card.movie_ticket'] = function ($app) { + return new MovieTicketClient($app); + }; + + $app['card.member_card'] = function ($app) { + return new MemberCardClient($app); + }; + + $app['card.general_card'] = function ($app) { + return new GeneralCardClient($app); + }; + + $app['card.boarding_pass'] = function ($app) { + return new BoardingPassClient($app); + }; + + $app['card.meeting_ticket'] = function ($app) { + return new MeetingTicketClient($app); + }; + + $app['card.jssdk'] = function ($app) { + return new JssdkClient($app); + }; + + $app['card.gift_card'] = function ($app) { + return new GiftCardClient($app); + }; + + $app['card.gift_card_order'] = function ($app) { + return new GiftCardOrderClient($app); + }; + + $app['card.gift_card_page'] = function ($app) { + return new GiftCardPageClient($app); + }; + + $app['card.invoice'] = function ($app) { + return new InvoiceClient($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/SubMerchantClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/SubMerchantClient.php new file mode 100644 index 0000000..77cfad2 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/SubMerchantClient.php @@ -0,0 +1,123 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Card; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Support\Arr; + +/** + * Class SubMerchantClient. + * + * @author overtrue + */ +class SubMerchantClient extends BaseClient +{ + /** + * 添加子商户. + * + * @param array $info + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function create(array $info = []) + { + $params = [ + 'info' => Arr::only($info, [ + 'brand_name', + 'logo_url', + 'protocol', + 'end_time', + 'primary_category_id', + 'secondary_category_id', + 'agreement_media_id', + 'operator_media_id', + 'app_id', + ]), + ]; + + return $this->httpPostJson('card/submerchant/submit', $params); + } + + /** + * 更新子商户. + * + * @param int $merchantId + * @param array $info + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(int $merchantId, array $info = []) + { + $params = [ + 'info' => array_merge( + ['merchant_id' => $merchantId], + Arr::only($info, [ + 'brand_name', + 'logo_url', + 'protocol', + 'end_time', + 'primary_category_id', + 'secondary_category_id', + 'agreement_media_id', + 'operator_media_id', + 'app_id', + ]) + ), + ]; + + return $this->httpPostJson('card/submerchant/update', $params); + } + + /** + * 获取子商户信息. + * + * @param int $merchantId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get(int $merchantId) + { + return $this->httpPostJson('card/submerchant/get', ['merchant_id' => $merchantId]); + } + + /** + * 批量获取子商户信息. + * + * @param int $beginId + * @param int $limit + * @param string $status + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function list(int $beginId = 0, int $limit = 50, string $status = 'CHECKING') + { + $params = [ + 'begin_id' => $beginId, + 'limit' => $limit, + 'status' => $status, + ]; + + return $this->httpPostJson('card/submerchant/batchget', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Comment/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Comment/Client.php new file mode 100644 index 0000000..4f45b74 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Comment/Client.php @@ -0,0 +1,208 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Comment; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author overtrue + */ +class Client extends BaseClient +{ + /** + * Open article comment. + * + * @param string $msgId + * @param int|null $index + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function open(string $msgId, int $index = null) + { + $params = [ + 'msg_data_id' => $msgId, + 'index' => $index, + ]; + + return $this->httpPostJson('cgi-bin/comment/open', $params); + } + + /** + * Close comment. + * + * @param string $msgId + * @param int|null $index + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function close(string $msgId, int $index = null) + { + $params = [ + 'msg_data_id' => $msgId, + 'index' => $index, + ]; + + return $this->httpPostJson('cgi-bin/comment/close', $params); + } + + /** + * Get article comments. + * + * @param string $msgId + * @param int $index + * @param int $begin + * @param int $count + * @param int $type + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function list(string $msgId, int $index, int $begin, int $count, int $type = 0) + { + $params = [ + 'msg_data_id' => $msgId, + 'index' => $index, + 'begin' => $begin, + 'count' => $count, + 'type' => $type, + ]; + + return $this->httpPostJson('cgi-bin/comment/list', $params); + } + + /** + * Mark elect comment. + * + * @param string $msgId + * @param int $index + * @param int $commentId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function markElect(string $msgId, int $index, int $commentId) + { + $params = [ + 'msg_data_id' => $msgId, + 'index' => $index, + 'user_comment_id' => $commentId, + ]; + + return $this->httpPostJson('cgi-bin/comment/markelect', $params); + } + + /** + * Unmark elect comment. + * + * @param string $msgId + * @param int $index + * @param int $commentId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function unmarkElect(string $msgId, int $index, int $commentId) + { + $params = [ + 'msg_data_id' => $msgId, + 'index' => $index, + 'user_comment_id' => $commentId, + ]; + + return $this->httpPostJson('cgi-bin/comment/unmarkelect', $params); + } + + /** + * Delete comment. + * + * @param string $msgId + * @param int $index + * @param int $commentId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete(string $msgId, int $index, int $commentId) + { + $params = [ + 'msg_data_id' => $msgId, + 'index' => $index, + 'user_comment_id' => $commentId, + ]; + + return $this->httpPostJson('cgi-bin/comment/delete', $params); + } + + /** + * Reply to a comment. + * + * @param string $msgId + * @param int $index + * @param int $commentId + * @param string $content + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function reply(string $msgId, int $index, int $commentId, string $content) + { + $params = [ + 'msg_data_id' => $msgId, + 'index' => $index, + 'user_comment_id' => $commentId, + 'content' => $content, + ]; + + return $this->httpPostJson('cgi-bin/comment/reply/add', $params); + } + + /** + * Delete a reply. + * + * @param string $msgId + * @param int $index + * @param int $commentId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function deleteReply(string $msgId, int $index, int $commentId) + { + $params = [ + 'msg_data_id' => $msgId, + 'index' => $index, + 'user_comment_id' => $commentId, + ]; + + return $this->httpPostJson('cgi-bin/comment/reply/delete', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Comment/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Comment/ServiceProvider.php new file mode 100644 index 0000000..8d6806c --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Comment/ServiceProvider.php @@ -0,0 +1,44 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * ServiceProvider.php. + * + * This file is part of the wechat. + * + * (c) overtrue + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Comment; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['comment'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/Client.php new file mode 100644 index 0000000..64e43e5 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/Client.php @@ -0,0 +1,230 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\CustomerService; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author overtrue + */ +class Client extends BaseClient +{ + /** + * List all staffs. + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function list() + { + return $this->httpGet('cgi-bin/customservice/getkflist'); + } + + /** + * List all online staffs. + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function online() + { + return $this->httpGet('cgi-bin/customservice/getonlinekflist'); + } + + /** + * Create a staff. + * + * @param string $account + * @param string $nickname + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function create(string $account, string $nickname) + { + $params = [ + 'kf_account' => $account, + 'nickname' => $nickname, + ]; + + return $this->httpPostJson('customservice/kfaccount/add', $params); + } + + /** + * Update a staff. + * + * @param string $account + * @param string $nickname + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(string $account, string $nickname) + { + $params = [ + 'kf_account' => $account, + 'nickname' => $nickname, + ]; + + return $this->httpPostJson('customservice/kfaccount/update', $params); + } + + /** + * Delete a staff. + * + * @param string $account + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete(string $account) + { + return $this->httpPostJson('customservice/kfaccount/del', [], ['kf_account' => $account]); + } + + /** + * Invite a staff. + * + * @param string $account + * @param string $wechatId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function invite(string $account, string $wechatId) + { + $params = [ + 'kf_account' => $account, + 'invite_wx' => $wechatId, + ]; + + return $this->httpPostJson('customservice/kfaccount/inviteworker', $params); + } + + /** + * Set staff avatar. + * + * @param string $account + * @param string $path + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function setAvatar(string $account, string $path) + { + return $this->httpUpload('customservice/kfaccount/uploadheadimg', ['media' => $path], [], ['kf_account' => $account]); + } + + /** + * Get message builder. + * + * @param \EasyWeChat\Kernel\Messages\Message|string $message + * + * @return \EasyWeChat\OfficialAccount\CustomerService\Messenger + */ + public function message($message) + { + $messageBuilder = new Messenger($this); + + return $messageBuilder->message($message); + } + + /** + * Send a message. + * + * @param array $message + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function send(array $message) + { + return $this->httpPostJson('cgi-bin/message/custom/send', $message); + } + + /** + * Show typing status. + * + * @param string $openid + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function showTypingStatusToUser(string $openid) + { + return $this->httpPostJson('cgi-bin/message/custom/typing', [ + 'touser' => $openid, + 'command' => 'Typing', + ]); + } + + /** + * Hide typing status. + * + * @param string $openid + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function hideTypingStatusToUser(string $openid) + { + return $this->httpPostJson('cgi-bin/message/custom/typing', [ + 'touser' => $openid, + 'command' => 'CancelTyping', + ]); + } + + /** + * Get messages history. + * + * @param int $startTime + * @param int $endTime + * @param int $msgId + * @param int $number + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function messages($startTime, $endTime, int $msgId = 1, int $number = 10000) + { + $params = [ + 'starttime' => is_numeric($startTime) ? $startTime : strtotime($startTime), + 'endtime' => is_numeric($endTime) ? $endTime : strtotime($endTime), + 'msgid' => $msgId, + 'number' => $number, + ]; + + return $this->httpPostJson('customservice/msgrecord/getmsglist', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/Messenger.php b/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/Messenger.php new file mode 100644 index 0000000..69bf2f3 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/Messenger.php @@ -0,0 +1,165 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\CustomerService; + +use EasyWeChat\Kernel\Exceptions\RuntimeException; +use EasyWeChat\Kernel\Messages\Message; +use EasyWeChat\Kernel\Messages\Raw as RawMessage; +use EasyWeChat\Kernel\Messages\Text; + +/** + * Class MessageBuilder. + * + * @author overtrue + */ +class Messenger +{ + /** + * Messages to send. + * + * @var \EasyWeChat\Kernel\Messages\Message; + */ + protected $message; + + /** + * Messages target user open id. + * + * @var string + */ + protected $to; + + /** + * Messages sender staff id. + * + * @var string + */ + protected $account; + + /** + * Customer service instance. + * + * @var \EasyWeChat\OfficialAccount\CustomerService\Client + */ + protected $client; + + /** + * MessageBuilder constructor. + * + * @param \EasyWeChat\OfficialAccount\CustomerService\Client $client + */ + public function __construct(Client $client) + { + $this->client = $client; + } + + /** + * Set message to send. + * + * @param string|Message $message + * + * @return Messenger + */ + public function message($message) + { + if (is_string($message)) { + $message = new Text($message); + } + + $this->message = $message; + + return $this; + } + + /** + * Set staff account to send message. + * + * @param string $account + * + * @return Messenger + */ + public function by(string $account) + { + $this->account = $account; + + return $this; + } + + /** + * @param string $account + * + * @return Messenger + */ + public function from(string $account) + { + return $this->by($account); + } + + /** + * Set target user open id. + * + * @param string $openid + * + * @return Messenger + */ + public function to($openid) + { + $this->to = $openid; + + return $this; + } + + /** + * Send the message. + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public function send() + { + if (empty($this->message)) { + throw new RuntimeException('No message to send.'); + } + + if ($this->message instanceof RawMessage) { + $message = json_decode($this->message->get('content'), true); + } else { + $prepends = [ + 'touser' => $this->to, + ]; + if ($this->account) { + $prepends['customservice'] = ['kf_account' => $this->account]; + } + $message = $this->message->transformForJsonRequest($prepends); + } + + return $this->client->send($message); + } + + /** + * Return property. + * + * @param string $property + * + * @return mixed + */ + public function __get(string $property) + { + if (property_exists($this, $property)) { + return $this->$property; + } + + return null; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/ServiceProvider.php new file mode 100644 index 0000000..a879ce8 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/ServiceProvider.php @@ -0,0 +1,37 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\CustomerService; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['customer_service'] = function ($app) { + return new Client($app); + }; + + $app['customer_service_session'] = function ($app) { + return new SessionClient($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/SessionClient.php b/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/SessionClient.php new file mode 100644 index 0000000..b92b6db --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/SessionClient.php @@ -0,0 +1,104 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\CustomerService; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class SessionClient. + * + * @author overtrue + */ +class SessionClient extends BaseClient +{ + /** + * List all sessions of $account. + * + * @param string $account + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function list(string $account) + { + return $this->httpGet('customservice/kfsession/getsessionlist', ['kf_account' => $account]); + } + + /** + * List all the people waiting. + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function waiting() + { + return $this->httpGet('customservice/kfsession/getwaitcase'); + } + + /** + * Create a session. + * + * @param string $account + * @param string $openid + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function create(string $account, string $openid) + { + $params = [ + 'kf_account' => $account, + 'openid' => $openid, + ]; + + return $this->httpPostJson('customservice/kfsession/create', $params); + } + + /** + * Close a session. + * + * @param string $account + * @param string $openid + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function close(string $account, string $openid) + { + $params = [ + 'kf_account' => $account, + 'openid' => $openid, + ]; + + return $this->httpPostJson('customservice/kfsession/close', $params); + } + + /** + * Get a session. + * + * @param string $openid + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function get(string $openid) + { + return $this->httpGet('customservice/kfsession/getsession', ['openid' => $openid]); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/DataCube/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/DataCube/Client.php new file mode 100644 index 0000000..0bf1c21 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/DataCube/Client.php @@ -0,0 +1,340 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\DataCube; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author overtrue + */ +class Client extends BaseClient +{ + /** + * 获取用户增减数据. + * + * @param string $from + * @param string $to + * + * @return mixed + */ + public function userSummary(string $from, string $to) + { + return $this->query('datacube/getusersummary', $from, $to); + } + + /** + * 获取累计用户数据. + * + * @param string $from + * @param string $to + * + * @return mixed + */ + public function userCumulate(string $from, string $to) + { + return $this->query('datacube/getusercumulate', $from, $to); + } + + /** + * 获取图文群发每日数据. + * + * @param string $from + * @param string $to + * + * @return mixed + */ + public function articleSummary(string $from, string $to) + { + return $this->query('datacube/getarticlesummary', $from, $to); + } + + /** + * 获取图文群发总数据. + * + * @param string $from + * @param string $to + * + * @return mixed + */ + public function articleTotal(string $from, string $to) + { + return $this->query('datacube/getarticletotal', $from, $to); + } + + /** + * 获取图文统计数据. + * + * @param string $from + * @param string $to + * + * @return mixed + */ + public function userReadSummary(string $from, string $to) + { + return $this->query('datacube/getuserread', $from, $to); + } + + /** + * 获取图文统计分时数据. + * + * @param string $from + * @param string $to + * + * @return mixed + */ + public function userReadHourly(string $from, string $to) + { + return $this->query('datacube/getuserreadhour', $from, $to); + } + + /** + * 获取图文分享转发数据. + * + * @param string $from + * @param string $to + * + * @return mixed + */ + public function userShareSummary(string $from, string $to) + { + return $this->query('datacube/getusershare', $from, $to); + } + + /** + * 获取图文分享转发分时数据. + * + * @param string $from + * @param string $to + * + * @return mixed + */ + public function userShareHourly(string $from, string $to) + { + return $this->query('datacube/getusersharehour', $from, $to); + } + + /** + * 获取消息发送概况数据. + * + * @param string $from + * @param string $to + * + * @return mixed + */ + public function upstreamMessageSummary(string $from, string $to) + { + return $this->query('datacube/getupstreammsg', $from, $to); + } + + /** + * 获取消息分送分时数据. + * + * @param string $from + * @param string $to + * + * @return mixed + */ + public function upstreamMessageHourly(string $from, string $to) + { + return $this->query('datacube/getupstreammsghour', $from, $to); + } + + /** + * 获取消息发送周数据. + * + * @param string $from + * @param string $to + * + * @return mixed + */ + public function upstreamMessageWeekly(string $from, string $to) + { + return $this->query('datacube/getupstreammsgweek', $from, $to); + } + + /** + * 获取消息发送月数据. + * + * @param string $from + * @param string $to + * + * @return mixed + */ + public function upstreamMessageMonthly(string $from, string $to) + { + return $this->query('datacube/getupstreammsgmonth', $from, $to); + } + + /** + * 获取消息发送分布数据. + * + * @param string $from + * @param string $to + * + * @return mixed + */ + public function upstreamMessageDistSummary(string $from, string $to) + { + return $this->query('datacube/getupstreammsgdist', $from, $to); + } + + /** + * 获取消息发送分布周数据. + * + * @param string $from + * @param string $to + * + * @return mixed + */ + public function upstreamMessageDistWeekly(string $from, string $to) + { + return $this->query('datacube/getupstreammsgdistweek', $from, $to); + } + + /** + * 获取消息发送分布月数据. + * + * @param string $from + * @param string $to + * + * @return mixed + */ + public function upstreamMessageDistMonthly(string $from, string $to) + { + return $this->query('datacube/getupstreammsgdistmonth', $from, $to); + } + + /** + * 获取接口分析数据. + * + * @param string $from + * @param string $to + * + * @return mixed + */ + public function interfaceSummary(string $from, string $to) + { + return $this->query('datacube/getinterfacesummary', $from, $to); + } + + /** + * 获取接口分析分时数据. + * + * @param string $from + * @param string $to + * + * @return mixed + */ + public function interfaceSummaryHourly(string $from, string $to) + { + return $this->query('datacube/getinterfacesummaryhour', $from, $to); + } + + /** + * 拉取卡券概况数据接口. + * + * @param string $from + * @param string $to + * @param int $condSource + * + * @return mixed + */ + public function cardSummary(string $from, string $to, $condSource = 0) + { + $ext = [ + 'cond_source' => intval($condSource), + ]; + + return $this->query('datacube/getcardbizuininfo', $from, $to, $ext); + } + + /** + * 获取免费券数据接口. + * + * @param string $from + * @param string $to + * @param int $condSource + * @param string $cardId + * + * @return mixed + */ + public function freeCardSummary(string $from, string $to, int $condSource = 0, string $cardId = '') + { + $ext = [ + 'cond_source' => intval($condSource), + 'card_id' => $cardId, + ]; + + return $this->query('datacube/getcardcardinfo', $from, $to, $ext); + } + + /** + * 拉取会员卡数据接口. + * + * @param string $from + * @param string $to + * @param int $condSource + * + * @return mixed + */ + public function memberCardSummary(string $from, string $to, $condSource = 0) + { + $ext = [ + 'cond_source' => intval($condSource), + ]; + + return $this->query('datacube/getcardmembercardinfo', $from, $to, $ext); + } + + /** + * 拉取单张会员卡数据接口. + * + * @param string $from + * @param string $to + * @param string $cardId + * + * @return mixed + */ + public function memberCardSummaryById(string $from, string $to, string $cardId) + { + $ext = [ + 'card_id' => $cardId, + ]; + + return $this->query('datacube/getcardmembercarddetail', $from, $to, $ext); + } + + /** + * 查询数据. + * + * @param string $api + * @param string $from + * @param string $to + * @param array $ext + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function query(string $api, string $from, string $to, array $ext = []) + { + $params = array_merge([ + 'begin_date' => $from, + 'end_date' => $to, + ], $ext); + + return $this->httpPostJson($api, $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/DataCube/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/DataCube/ServiceProvider.php new file mode 100644 index 0000000..bfec89a --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/DataCube/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\DataCube; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['data_cube'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Device/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Device/Client.php new file mode 100644 index 0000000..8dc9d73 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Device/Client.php @@ -0,0 +1,251 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Device; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @see http://iot.weixin.qq.com/wiki/new/index.html + * + * @author soone <66812590@qq.com> + */ +class Client extends BaseClient +{ + /** + * @param string $deviceId + * @param string $openid + * @param string $content + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function message(string $deviceId, string $openid, string $content) + { + $params = [ + 'device_type' => $this->app['config']['device_type'], + 'device_id' => $deviceId, + 'open_id' => $openid, + 'content' => base64_encode($content), + ]; + + return $this->httpPostJson('device/transmsg', $params); + } + + /** + * Get device qrcode. + * + * @param array $deviceIds + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function qrCode(array $deviceIds) + { + $params = [ + 'device_num' => count($deviceIds), + 'device_id_list' => $deviceIds, + ]; + + return $this->httpPostJson('device/create_qrcode', $params); + } + + /** + * @param array $devices + * @param string $productId + * @param int $opType + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function authorize(array $devices, string $productId, int $opType = 0) + { + $params = [ + 'device_num' => count($devices), + 'device_list' => $devices, + 'op_type' => $opType, + 'product_id' => $productId, + ]; + + return $this->httpPostJson('device/authorize_device', $params); + } + + /** + * 获取 device id 和二维码 + * + * @param string $productId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function createId(string $productId) + { + $params = [ + 'product_id' => $productId, + ]; + + return $this->httpGet('device/getqrcode', $params); + } + + /** + * @param string $openid + * @param string $deviceId + * @param string $ticket + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function bind(string $openid, string $deviceId, string $ticket) + { + $params = [ + 'ticket' => $ticket, + 'device_id' => $deviceId, + 'openid' => $openid, + ]; + + return $this->httpPostJson('device/bind', $params); + } + + /** + * @param string $openid + * @param string $deviceId + * @param string $ticket + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function unbind(string $openid, string $deviceId, string $ticket) + { + $params = [ + 'ticket' => $ticket, + 'device_id' => $deviceId, + 'openid' => $openid, + ]; + + return $this->httpPostJson('device/unbind', $params); + } + + /** + * @param string $openid + * @param string $deviceId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function forceBind(string $openid, string $deviceId) + { + $params = [ + 'device_id' => $deviceId, + 'openid' => $openid, + ]; + + return $this->httpPostJson('device/compel_bind', $params); + } + + /** + * @param string $openid + * @param string $deviceId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function forceUnbind(string $openid, string $deviceId) + { + $params = [ + 'device_id' => $deviceId, + 'openid' => $openid, + ]; + + return $this->httpPostJson('device/compel_unbind', $params); + } + + /** + * @param string $deviceId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function status(string $deviceId) + { + $params = [ + 'device_id' => $deviceId, + ]; + + return $this->httpGet('device/get_stat', $params); + } + + /** + * @param string $ticket + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function verify(string $ticket) + { + $params = [ + 'ticket' => $ticket, + ]; + + return $this->httpPost('device/verify_qrcode', $params); + } + + /** + * @param string $deviceId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function openid(string $deviceId) + { + $params = [ + 'device_type' => $this->app['config']['device_type'], + 'device_id' => $deviceId, + ]; + + return $this->httpGet('device/get_openid', $params); + } + + /** + * @param string $openid + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function listByOpenid(string $openid) + { + $params = [ + 'openid' => $openid, + ]; + + return $this->httpGet('device/get_bind_device', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Device/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Device/ServiceProvider.php new file mode 100644 index 0000000..e3dce8e --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Device/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Device; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author soone <66812590@qq.com + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['device'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Draft/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Draft/Client.php new file mode 100644 index 0000000..7b4bb0e --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Draft/Client.php @@ -0,0 +1,110 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Draft; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; +use EasyWeChat\Kernel\Http\StreamResponse; +use EasyWeChat\Kernel\Messages\Article; + +/** + * Class Client. + * + * @author wangdongzhao + */ +class Client extends BaseClient +{ + /** + * Add new articles to the draft. + * @param array $articles + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function add(array $articles) + { + return $this->httpPostJson('cgi-bin/draft/add', $articles); + } + + /** + * Get article from the draft. + * @param string $mediaId + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get(string $mediaId) + { + return $this->httpPostJson('cgi-bin/draft/get', ['media_id' => $mediaId]); + } + + /** + * Update article + * @param string $mediaId + * @param int $index + * @param $article + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(string $mediaId, int $index, $article) + { + $params = [ + 'media_id' => $mediaId, + 'index' => $index, + 'articles' => $article + ]; + return $this->httpPostJson('cgi-bin/draft/update', $params); + } + + /** + * Get draft total count + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function count() + { + return $this->httpPostJson('cgi-bin/draft/count'); + } + + /** + * Batch get articles from the draft. + * @param int $offset + * @param int $count + * @param int $noContent + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function batchGet(int $offset = 0, int $count = 20, int $noContent = 0) + { + $params = [ + 'offset' => $offset, + 'count' => $count, + 'no_content' => $noContent + ]; + return $this->httpPostJson('cgi-bin/draft/batchget', $params); + } + + /** + * Delete article. + * @param string $mediaId + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete(string $mediaId) + { + return $this->httpPostJson('cgi-bin/draft/delete', ['media_id' => $mediaId]); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Draft/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Draft/ServiceProvider.php new file mode 100644 index 0000000..f38b8ea --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Draft/ServiceProvider.php @@ -0,0 +1,35 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Draft; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author wangdongzhao + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['draft'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/FreePublish/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/FreePublish/Client.php new file mode 100644 index 0000000..93dc679 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/FreePublish/Client.php @@ -0,0 +1,92 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\FreePublish; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; +use EasyWeChat\Kernel\Http\StreamResponse; +use EasyWeChat\Kernel\Messages\Article; + +/** + * Class Client. + * + * @author wangdongzhao + */ +class Client extends BaseClient +{ + /** + * Get publish status. + * @param string $publishId + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get(string $publishId) + { + return $this->httpPostJson('cgi-bin/freepublish/get', ['publish_id' => $publishId]); + } + + /** + * Submit article. + * @param string $mediaId + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function submit(string $mediaId) + { + return $this->httpPostJson('cgi-bin/freepublish/submit', ['media_id' => $mediaId]); + } + + /** + * Get article. + * @param string $articleId + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getArticle(string $articleId) + { + return $this->httpPostJson('cgi-bin/freepublish/getarticle', ['article_id' => $articleId]); + } + + /** + * Batch get articles. + * @param int $offset + * @param int $count + * @param int $noContent + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function batchGet(int $offset = 0, int $count = 20, int $noContent = 0) + { + $params = [ + 'offset' => $offset, + 'count' => $count, + 'no_content' => $noContent + ]; + return $this->httpPostJson('cgi-bin/freepublish/batchget', $params); + } + + /** + * Delete article + * @param string $articleId + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete(string $articleId) + { + return $this->httpPostJson('cgi-bin/freepublish/delete', ['article_id' => $articleId]); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/FreePublish/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/FreePublish/ServiceProvider.php new file mode 100644 index 0000000..a4696b5 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/FreePublish/ServiceProvider.php @@ -0,0 +1,35 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\FreePublish; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author wangdongzhao + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['free_publish'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Goods/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Goods/Client.php new file mode 100644 index 0000000..c720e31 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Goods/Client.php @@ -0,0 +1,113 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Goods; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author her-cat + */ +class Client extends BaseClient +{ + /** + * Add the goods. + * + * @param array $data + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function add(array $data) + { + return $this->httpPostJson('scan/product/v2/add', [ + 'product' => $data, + ]); + } + + /** + * Update the goods. + * + * @param array $data + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(array $data) + { + return $this->httpPostJson('scan/product/v2/add', [ + 'product' => $data, + ]); + } + + /** + * Get add or update goods results. + * + * @param string $ticket + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function status(string $ticket) + { + return $this->httpPostJson('scan/product/v2/status', [ + 'status_ticket' => $ticket, + ]); + } + + /** + * Get goods information. + * + * @param string $pid + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get(string $pid) + { + return $this->httpPostJson('scan/product/v2/getinfo', [ + 'product' => [ + 'pid' => $pid, + ], + ]); + } + + /** + * Get a list of goods. + * + * @param string $context + * @param int $page + * @param int $size + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function list(string $context = '', int $page = 1, int $size = 10) + { + return $this->httpPostJson('scan/product/v2/getinfobypage', [ + 'page_context' => $context, + 'page_num' => $page, + 'page_size' => $size, + ]); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Goods/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Goods/ServiceProvider.php new file mode 100644 index 0000000..38a0902 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Goods/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Goods; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author her-cat + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['goods'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Guide/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Guide/Client.php new file mode 100644 index 0000000..9c771dc --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Guide/Client.php @@ -0,0 +1,991 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Guide; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Exceptions\InvalidConfigException; +use EasyWeChat\Kernel\Support\Collection; +use Psr\Http\Message\ResponseInterface; + +/** + * Class Client. + * + * @author MillsGuo + */ +class Client extends BaseClient +{ + /** + * 添加顾问 + * @param string $guideAccount + * @param string $guideOpenid + * @param string $guideHeadImgUrl + * @param string $guideNickname + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function createAdviser($guideAccount = '', $guideOpenid = '', $guideHeadImgUrl = '', $guideNickname = '') + { + $params = $this->selectAccountAndOpenid(array(), $guideAccount, $guideOpenid); + if (!empty($guideHeadImgUrl)) { + $params['guide_headimgurl'] = $guideHeadImgUrl; + } + if (!empty($guideNickname)) { + $params['guide_nickname'] = $guideNickname; + } + + return $this->httpPostJson('cgi-bin/guide/addguideacct', $params); + } + + /** + * 获取顾问信息 + * @param string $guideAccount + * @param string $guideOpenid + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function getAdviser($guideAccount = '', $guideOpenid = '') + { + $params = $this->selectAccountAndOpenid(array(), $guideAccount, $guideOpenid); + + return $this->httpPostJson('cgi-bin/guide/getguideacct', $params); + } + + /** + * 修改顾问的昵称或头像 + * @param string $guideAccount + * @param string $guideOpenid + * @param string $guideHeadImgUrl + * @param string $guideNickname + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function updateAdviser($guideAccount = '', $guideOpenid = '', $guideHeadImgUrl = '', $guideNickname = '') + { + $params = $this->selectAccountAndOpenid(array(), $guideAccount, $guideOpenid); + if (!empty($guideHeadImgUrl)) { + $params['guide_headimgurl'] = $guideHeadImgUrl; + } + if (!empty($guideNickname)) { + $params['guide_nickname'] = $guideNickname; + } + + return $this->httpPostJson('cgi-bin/guide/updateguideacct', $params); + } + + /** + * 删除顾问 + * @param string $guideAccount + * @param string $guideOpenid + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function deleteAdviser($guideAccount = '', $guideOpenid = '') + { + $params = $this->selectAccountAndOpenid(array(), $guideAccount, $guideOpenid); + + return $this->httpPostJson('cgi-bin/guide/delguideacct', $params); + } + + /** + * 获取服务号顾问列表 + * + * @return mixed + * + * @throws InvalidConfigException + */ + public function getAdvisers($count, $page) + { + $params = [ + 'page' => $page, + 'num' => $count + ]; + + return $this->httpPostJson('cgi-bin/guide/getguideacctlist', $params); + } + + /** + * 生成顾问二维码 + * @param string $guideAccount + * @param string $guideOpenid + * @param string $qrCodeInfo + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function createQrCode($guideAccount = '', $guideOpenid = '', $qrCodeInfo = '') + { + $params = $this->selectAccountAndOpenid(array(), $guideAccount, $guideOpenid); + if (!empty($qrCodeInfo)) { + $params['qrcode_info'] = $qrCodeInfo; + } + + return $this->httpPostJson('cgi-bin/guide/guidecreateqrcode', $params); + } + + /** + * 获取顾问聊天记录 + * @param string $guideAccount + * @param string $guideOpenid + * @param string $openid + * @param int $beginTime + * @param int $endTime + * @param int $page + * @param int $count + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function getBuyerChatRecords($guideAccount = '', $guideOpenid = '', $openid = '', $beginTime = 0, $endTime = 0, $page = 1, $count = 100) + { + $params = [ + 'page' => $page, + 'num' => $count + ]; + $params = $this->selectAccountAndOpenid($params, $guideAccount, $guideOpenid); + if (!empty($openid)) { + $params['openid'] = $openid; + } + if (!empty($beginTime)) { + $params['begin_time'] = $beginTime; + } + if (!empty($endTime)) { + $params['end_time'] = $endTime; + } + + return $this->httpPostJson('cgi-bin/guide/getguidebuyerchatrecord', $params); + } + + /** + * 设置快捷回复与关注自动回复 + * @param string $guideAccount + * @param string $guideOpenid + * @param bool $isDelete + * @param array $fastReplyListArray + * @param array $guideAutoReply + * @param array $guideAutoReplyPlus + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function setConfig($guideAccount = '', $guideOpenid = '', $isDelete = false, $fastReplyListArray = array(), $guideAutoReply = array(), $guideAutoReplyPlus = array()) + { + $params = [ + 'is_delete' => $isDelete + ]; + $params = $this->selectAccountAndOpenid($params, $guideAccount, $guideOpenid); + if (!empty($fastReplyListArray)) { + $params['guide_fast_reply_list'] = $fastReplyListArray; + } + if (!empty($guideAutoReply)) { + $params['guide_auto_reply'] = $guideAutoReply; + } + if (!empty($guideAutoReplyPlus)) { + $params['guide_auto_reply_plus'] = $guideAutoReplyPlus; + } + + return $this->httpPostJson('cgi-bin/guide/setguideconfig', $params); + } + + /** + * 获取快捷回复与关注自动回复 + * @param string $guideAccount + * @param string $guideOpenid + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function getConfig($guideAccount = '', $guideOpenid = '') + { + try { + $params = $this->selectAccountAndOpenid(array(), $guideAccount, $guideOpenid); + } catch (InvalidConfigException $e) { + $params = array(); + } + + return $this->httpPostJson('cgi-bin/guide/getguideconfig', $params); + } + + /** + * 设置离线自动回复与敏感词 + * @param bool $isDelete + * @param array $blackKeyword + * @param array $guideAutoReply + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function setAdviserConfig(bool $isDelete, array $blackKeyword = [], array $guideAutoReply = []) + { + $params = [ + 'is_delete' => $isDelete + ]; + if (!empty($blackKeyword)) { + $params['black_keyword'] = $blackKeyword; + } + if (!empty($guideAutoReply)) { + $params['guide_auto_reply'] = $guideAutoReply; + } + + return $this->httpPostJson('cgi-bin/guide/setguideacctconfig', $params); + } + + /** + * 获取离线自动回复与敏感词 + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function getAdviserConfig() + { + return $this->httpPostJson('cgi-bin/guide/getguideacctconfig', array()); + } + + /** + * 允许微信用户复制小程序页面路径 + * @param string $wxaAppid 小程序APPID + * @param string $wxUsername 微信用户的微信号 + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function allowCopyMiniAppPath(string $wxaAppid, string $wxUsername) + { + $params = [ + 'wxa_appid' => $wxaAppid, + 'wx_username' => $wxUsername + ]; + + return $this->httpPostJson('cgi-bin/guide/pushshowwxapathmenu', $params); + } + + /** + * 传入微信号或OPENID二选一 + * @param array $params + * @param string $guideAccount + * @param string $guideOpenid + * @return array + * @throws InvalidConfigException + */ + protected function selectAccountAndOpenid($params, $guideAccount = '', $guideOpenid = '') + { + if (!is_array($params)) { + throw new InvalidConfigException("传入配置参数必须为数组"); + } + if (!empty($guideOpenid)) { + $params['guide_openid'] = $guideOpenid; + } elseif (!empty($guideAccount)) { + $params['guide_account'] = $guideAccount; + } else { + throw new InvalidConfigException("微信号和OPENID不能同时为空"); + } + + return $params; + } + + /** + * 新建顾问分组 + * @param string $groupName + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function createGroup(string $groupName) + { + $params = [ + 'group_name' => $groupName + ]; + + return $this->httpPostJson('cgi-bin/guide/newguidegroup', $params); + } + + /** + * 获取顾问分组列表 + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function getGuideGroups() + { + return $this->httpPostJson('cgi-bin/guide/getguidegrouplist', array()); + } + + /** + * 获取指定顾问分组信息 + * @param int $groupId + * @param int $page + * @param int $num + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function getGroups(int $groupId, int $page, int $num) + { + $params = [ + 'group_id' => $groupId, + 'page' => $page, + 'num' => $num + ]; + + return $this->httpPostJson('cgi-bin/guide/getgroupinfo', $params); + } + + /** + * 分组内添加顾问 + * @param int $groupId + * @param string $guideAccount + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function addGroupGuide(int $groupId, string $guideAccount) + { + $params = [ + 'group_id' => $groupId, + 'gruide_account' => $guideAccount + ]; + + return $this->httpPostJson('cgi-bin/guide/addguide2guidegroup', $params); + } + + /** + * 分组内删除顾问 + * @param int $groupId + * @param string $guideAccount + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function deleteGroupGuide(int $groupId, string $guideAccount) + { + $params = [ + 'group_id' => $groupId, + 'guide_account' => $guideAccount + ]; + + return $this->httpPostJson('cgi-bin/guide/delguide2guidegroup', $params); + } + + /** + * 获取顾问所在分组 + * @param string $guideAccount + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function getGuideGroup(string $guideAccount) + { + $params = [ + 'guide_account' => $guideAccount + ]; + + return $this->httpPostJson('cgi-bin/guide/getgroupbyguide', $params); + } + + /** + * 删除指定顾问分组 + * @param int $groupId + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function deleteGroup(int $groupId) + { + $params = [ + 'group_id' => $groupId + ]; + + return $this->httpPostJson('cgi-bin/guide/delguidegroup', $params); + } + + /** + * 为顾问分配客户 + * @param string $guideAccount + * @param string $guideOpenid + * @param array $buyerList + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function createBuyerRelation(string $guideAccount, string $guideOpenid, array $buyerList) + { + $params = [ + 'buyer_list' => $buyerList + ]; + $params = $this->selectAccountAndOpenid($params, $guideAccount, $guideOpenid); + + return $this->httpPostJson('cgi-bin/guide/addguidebuyerrelation', $params); + } + + /** + * 为顾问移除客户 + * @param string $guideAccount + * @param string $guideOpenid + * @param array $openidList + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function deleteBuyerRelation(string $guideAccount, string $guideOpenid, array $openidList) + { + $params = [ + 'openid_list' => $openidList + ]; + $params = $this->selectAccountAndOpenid($params, $guideAccount, $guideOpenid); + + return $this->httpPostJson('cgi-bin/guide/delguidebuyerrelation', $params); + } + + /** + * 获取顾问的客户列表 + * @param string $guideAccount + * @param string $guideOpenid + * @param int $page + * @param int $num + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function getBuyerRelations(string $guideAccount, string $guideOpenid, int $page, int $num) + { + $params = [ + 'page' => $page, + 'num' => $num + ]; + $params = $this->selectAccountAndOpenid($params, $guideAccount, $guideOpenid); + + return $this->httpPostJson('cgi-bin/guide/getguidebuyerrelationlist', $params); + } + + /** + * 为客户更换顾问 + * @param string $oldGuideTarget + * @param string $newGuideTarget + * @param array $openidList + * @param bool $useTargetOpenid true使用OPENID,false使用微信号 + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function rebindBuyerGuide(string $oldGuideTarget, string $newGuideTarget, array $openidList, bool $useTargetOpenid = true) + { + $params = [ + 'openid_list' => $openidList + ]; + if ($useTargetOpenid) { + $params['old_guide_openid'] = $oldGuideTarget; + $params['new_guide_openid'] = $newGuideTarget; + } else { + $params['old_guide_account'] = $oldGuideTarget; + $params['new_guide_account'] = $newGuideTarget; + } + + return $this->httpPostJson('cgi-bin/guide/rebindguideacctforbuyer', $params); + } + + /** + * 修改客户昵称 + * @param string $guideAccount + * @param string $guideOpenid + * @param string $openid + * @param string $nickname + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function updateBuyerRelation(string $guideAccount, string $guideOpenid, string $openid, string $nickname) + { + $params = [ + 'openid' => $openid, + 'buyer_nickname' => $nickname + ]; + $params = $this->selectAccountAndOpenid($params, $guideAccount, $guideOpenid); + + return $this->httpPostJson('cgi-bin/guide/updateguidebuyerrelation', $params); + } + + /** + * 查询客户所属顾问 + * @param string $openid + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function getBuyerRelation(string $openid) + { + $params = [ + 'openid' => $openid + ]; + + return $this->httpPostJson('cgi-bin/guide/getguidebuyerrelationbybuyer', $params); + } + + /** + * 查询指定顾问和客户的关系 + * @param string $guideAccount + * @param string $guideOpenid + * @param string $openid + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function getBuyerRelationByGuide(string $guideAccount, string $guideOpenid, string $openid) + { + $params = [ + 'openid' => $openid + ]; + $params = $this->selectAccountAndOpenid($params, $guideAccount, $guideOpenid); + + return $this->httpPostJson('cgi-bin/guide/getguidebuyerrelation', $params); + } + + /** + * 新建可查询的标签类型 + * @param string $tagName + * @param array $tagValues + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function newTagOption(string $tagName, array $tagValues) + { + $params = [ + 'tag_name' => $tagName, + 'tag_values' => $tagValues + ]; + + return $this->httpPostJson('cgi-bin/guide/newguidetagoption', $params); + } + + /** + * 删除指定标签类型 + * @param string $tagName + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function deleteTagOption(string $tagName) + { + $params = [ + 'tag_name' => $tagName + ]; + + return $this->httpPostJson('cgi-bin/guide/delguidetagoption', $params); + } + + /** + * 为标签添加可选值 + * @param string $tagName + * @param array $tagValues + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function createTagOption(string $tagName, array $tagValues) + { + $params = [ + 'tag_name' => $tagName, + 'tag_values' => $tagValues + ]; + + return $this->httpPostJson('cgi-bin/guide/addguidetagoption', $params); + } + + /** + * 获取标签和可选值 + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function getTagOption() + { + return $this->httpPostJson('cgi-bin/guide/getguidetagoption', array()); + } + + /** + * 为客户设置标签 + * @param string $guideAccount + * @param string $guideOpenid + * @param array $openidList + * @param string $tagValue + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function setBuyersTag(string $guideAccount, string $guideOpenid, array $openidList, string $tagValue) + { + $params = [ + 'tag_value' => $tagValue, + 'openid_list' => $openidList + ]; + $params = $this->selectAccountAndOpenid($params, $guideAccount, $guideOpenid); + + return $this->httpPostJson('cgi-bin/guide/addguidebuyertag', $params); + } + + /** + * 查询客户标签 + * @param string $guideAccount + * @param string $guideOpenid + * @param string $openid + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function getBuyerTags(string $guideAccount, string $guideOpenid, string $openid) + { + $params = [ + 'openid' => $openid + ]; + $params = $this->selectAccountAndOpenid($params, $guideAccount, $guideOpenid); + + return $this->httpPostJson('cgi-bin/guide/getguidebuyertag', $params); + } + + /** + * 根据标签值筛选粉丝 + * @param string $guideAccount + * @param string $guideOpenid + * @param int $pushCount + * @param array $tagValues + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function getBuyerByTag(string $guideAccount, string $guideOpenid, int $pushCount = 0, array $tagValues = array()) + { + $params = $this->selectAccountAndOpenid(array(), $guideAccount, $guideOpenid); + if ($pushCount > 0) { + $params['push_count'] = $pushCount; + } + if (count($tagValues) > 0) { + $params['tag_values'] = $tagValues; + } + + return $this->httpPostJson('cgi-bin/guide/queryguidebuyerbytag', $params); + } + + /** + * 删除客户标签 + * @param string $guideAccount + * @param string $guideOpenid + * @param string $tagValue + * @param array $openidList + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function deleteBuyerTag(string $guideAccount, string $guideOpenid, string $tagValue, array $openidList) + { + $params = [ + 'tag_value' => $tagValue + ]; + $params = $this->selectAccountAndOpenid($params, $guideAccount, $guideOpenid); + if (count($openidList) > 0) { + $params['openid_list'] = $openidList; + } + + return $this->httpPostJson('cgi-bin/guide/delguidebuyertag', $params); + } + + /** + * 设置自定义客户信息 + * @param string $guideAccount + * @param string $guideOpenid + * @param string $openid + * @param array $displayTagList + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function setBuyerDisplayTags(string $guideAccount, string $guideOpenid, string $openid, array $displayTagList) + { + $params = [ + 'openid' => $openid, + 'display_tag_list' => $displayTagList + ]; + $params = $this->selectAccountAndOpenid($params, $guideAccount, $guideOpenid); + + return $this->httpPostJson('cgi-bin/guide/addguidebuyerdisplaytag', $params); + } + + /** + * 获取自定义客户信息 + * @param string $guideAccount + * @param string $guideOpenid + * @param string $openid + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function getBuyerDisplayTags(string $guideAccount, string $guideOpenid, string $openid) + { + $params = [ + 'openid' => $openid + ]; + $params = $this->selectAccountAndOpenid($params, $guideAccount, $guideOpenid); + + return $this->httpPostJson('cgi-bin/guide/getguidebuyerdisplaytag', $params); + } + + /** + * 添加小程序卡片素材 + * @param string $mediaId + * @param string $title + * @param string $path + * @param string $appid + * @param int $type + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function createCardMaterial(string $mediaId, string $title, string $path, string $appid, int $type = 0) + { + $params = [ + 'media_id' => $mediaId, + 'type' => $type, + 'title' => $title, + 'path' => $path, + 'appid' => $appid + ]; + + return $this->httpPostJson('cgi-bin/guide/setguidecardmaterial', $params); + } + + /** + * 查询小程序卡片素材 + * @param int $type + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function getCardMaterial(int $type = 0) + { + $params = [ + 'type' => $type + ]; + + return $this->httpPostJson('cgi-bin/guide/getguidecardmaterial', $params); + } + + /** + * 删除小程序卡片素材 + * @param string $title + * @param string $path + * @param string $appid + * @param int $type + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function deleteCardMaterial(string $title, string $path, string $appid, int $type = 0) + { + $params = [ + 'type' => $type, + 'title' => $title, + 'path' => $path, + 'appid' => $appid + ]; + + return $this->httpPostJson('cgi-bin/guide/delguidecardmaterial', $params); + } + + /** + * 添加图片素材 + * @param string $mediaId + * @param int $type + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function createImageMaterial(string $mediaId, int $type = 0) + { + $params = [ + 'media_id' => $mediaId, + 'type' => $type + ]; + + return $this->httpPostJson('cgi-bin/guide/setguideimagematerial', $params); + } + + /** + * 查询图片素材 + * @param int $type + * @param int $start + * @param int $num + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function getImageMaterial(int $type, int $start, int $num) + { + $params = [ + 'type' => $type, + 'start' => $start, + 'num' => $num + ]; + + return $this->httpPostJson('cgi-bin/guide/getguideimagematerial', $params); + } + + /** + * 删除图片素材 + * @param int $type + * @param string $picUrl + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function deleteImageMaterial(int $type, string $picUrl) + { + $params = [ + 'type' => $type, + 'picurl' => $picUrl + ]; + + return $this->httpPostJson('cgi-bin/guide/delguideimagematerial', $params); + } + + /** + * 添加文字素材 + * @param int $type + * @param string $word + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function createWordMaterial(int $type, string $word) + { + $params = [ + 'type' => $type, + 'word' => $word + ]; + + return $this->httpPostJson('cgi-bin/guide/setguidewordmaterial', $params); + } + + /** + * 查询文字素材 + * @param int $type + * @param int $start + * @param int $num + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function getWordMaterial(int $type, int $start, int $num) + { + $params = [ + 'type' => $type, + 'start' => $start, + 'num' => $num + ]; + + return $this->httpPostJson('cgi-bin/guide/getguidewordmaterial', $params); + } + + /** + * 删除文字素材 + * @param int $type + * @param string $word + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function deleteWordMaterial(int $type, string $word) + { + $params = [ + 'type' => $type, + 'word' => $word + ]; + + return $this->httpPostJson('cgi-bin/guide/delguidewordmaterial', $params); + } + + /** + * 添加群发任务,为指定顾问添加群发任务 + * @param string $guideAccount + * @param string $guideOpenid + * @param string $taskName + * @param string $taskRemark + * @param int $pushTime + * @param array $openidArray + * @param array $materialArray + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function createMasSendJob(string $guideAccount, string $guideOpenid, string $taskName, string $taskRemark, int $pushTime, array $openidArray, array $materialArray) + { + $params = [ + 'task_name' => $taskName, + 'push_time' => $pushTime, + 'openid' => $openidArray, + 'material' => $materialArray + ]; + if (!empty($taskRemark)) { + $params['task_remark'] = $taskRemark; + } + $params = $this->selectAccountAndOpenid($params, $guideAccount, $guideOpenid); + + return $this->httpPostJson('cgi-bin/guide/addguidemassendjob', $params); + } + + /** + * 获取群发任务列表 + * @param string $guideAccount + * @param string $guideOpenid + * @param array $taskStatus + * @param int $offset + * @param int $limit + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function getMasSendJobs(string $guideAccount, string $guideOpenid, array $taskStatus = [], int $offset = 0, int $limit = 50) + { + $params = $this->selectAccountAndOpenid(array(), $guideAccount, $guideOpenid); + if (!empty($taskStatus)) { + $params['task_status'] = $taskStatus; + } + if ($offset > 0) { + $params['offset'] = $offset; + } + if ($limit != 50) { + $params['limit'] = $limit; + } + + return $this->httpPostJson('cgi-bin/guide/getguidemassendjoblist', $params); + } + + /** + * 获取指定群发任务信息 + * @param string $taskId + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function getMasSendJob(string $taskId) + { + $params = [ + 'task_id' => $taskId + ]; + + return $this->httpPostJson('cgi-bin/guide/getguidemassendjob', $params); + } + + /** + * 修改群发任务 + * @param string $taskId + * @param string $taskName + * @param string $taskRemark + * @param int $pushTime + * @param array $openidArray + * @param array $materialArray + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function updateMasSendJob(string $taskId, string $taskName, string $taskRemark, int $pushTime, array $openidArray, array $materialArray) + { + $params = [ + 'task_id' => $taskId + ]; + if (!empty($taskName)) { + $params['task_name'] = $taskName; + } + if (!empty($taskRemark)) { + $params['task_remark'] = $taskRemark; + } + if (!empty($pushTime)) { + $params['push_time'] = $pushTime; + } + if (!empty($openidArray)) { + $params['openid'] = $openidArray; + } + if (!empty($materialArray)) { + $params['material'] = $materialArray; + } + + return $this->httpPostJson('cgi-bin/guide/updateguidemassendjob', $params); + } + + /** + * 取消群发任务 + * @param string $taskId + * @return array|Collection|object|ResponseInterface|string + * @throws InvalidConfigException + */ + public function cancelMasSendJob(string $taskId) + { + $params = [ + 'task_id' => $taskId + ]; + + return $this->httpPostJson('cgi-bin/guide/cancelguidemassendjob', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Guide/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Guide/ServiceProvider.php new file mode 100644 index 0000000..46ebbca --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Guide/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Guide; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author millsguo + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['guide'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Material/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Material/Client.php new file mode 100644 index 0000000..592689c --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Material/Client.php @@ -0,0 +1,303 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Material; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; +use EasyWeChat\Kernel\Http\StreamResponse; +use EasyWeChat\Kernel\Messages\Article; + +/** + * Class Client. + * + * @author overtrue + */ +class Client extends BaseClient +{ + /** + * Allow media type. + * + * @var array + */ + protected $allowTypes = ['image', 'voice', 'video', 'thumb', 'news_image']; + + /** + * Upload image. + * + * @param string $path + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function uploadImage(string $path) + { + return $this->upload('image', $path); + } + + /** + * Upload voice. + * + * @param string $path + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function uploadVoice(string $path) + { + return $this->upload('voice', $path); + } + + /** + * Upload thumb. + * + * @param string $path + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function uploadThumb(string $path) + { + return $this->upload('thumb', $path); + } + + /** + * Upload video. + * + * @param string $path + * @param string $title + * @param string $description + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function uploadVideo(string $path, string $title, string $description) + { + $params = [ + 'description' => json_encode( + [ + 'title' => $title, + 'introduction' => $description, + ], + JSON_UNESCAPED_UNICODE + ), + ]; + + return $this->upload('video', $path, $params); + } + + /** + * Upload articles. + * + * @param array|Article $articles + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function uploadArticle($articles) + { + if ($articles instanceof Article || !empty($articles['title'])) { + $articles = [$articles]; + } + + $params = ['articles' => array_map(function ($article) { + if ($article instanceof Article) { + return $article->transformForJsonRequestWithoutType(); + } + + return $article; + }, $articles)]; + + return $this->httpPostJson('cgi-bin/material/add_news', $params); + } + + /** + * Update article. + * + * @param string $mediaId + * @param array|Article $article + * @param int $index + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function updateArticle(string $mediaId, $article, int $index = 0) + { + if ($article instanceof Article) { + $article = $article->transformForJsonRequestWithoutType(); + } + + $params = [ + 'media_id' => $mediaId, + 'index' => $index, + 'articles' => isset($article['title']) ? $article : (isset($article[$index]) ? $article[$index] : []), + ]; + + return $this->httpPostJson('cgi-bin/material/update_news', $params); + } + + /** + * Upload image for article. + * + * @param string $path + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function uploadArticleImage(string $path) + { + return $this->upload('news_image', $path); + } + + /** + * Fetch material. + * + * @param string $mediaId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get(string $mediaId) + { + $response = $this->requestRaw('cgi-bin/material/get_material', 'POST', ['json' => ['media_id' => $mediaId]]); + + if (false !== stripos($response->getHeaderLine('Content-disposition'), 'attachment')) { + return StreamResponse::buildFromPsrResponse($response); + } + + return $this->castResponseToType($response, $this->app['config']->get('response_type')); + } + + /** + * Delete material by media ID. + * + * @param string $mediaId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete(string $mediaId) + { + return $this->httpPostJson('cgi-bin/material/del_material', ['media_id' => $mediaId]); + } + + /** + * List materials. + * + * example: + * + * { + * "total_count": TOTAL_COUNT, + * "item_count": ITEM_COUNT, + * "item": [{ + * "media_id": MEDIA_ID, + * "name": NAME, + * "update_time": UPDATE_TIME + * }, + * // more... + * ] + * } + * + * @param string $type + * @param int $offset + * @param int $count + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function list(string $type, int $offset = 0, int $count = 20) + { + $params = [ + 'type' => $type, + 'offset' => $offset, + 'count' => $count, + ]; + + return $this->httpPostJson('cgi-bin/material/batchget_material', $params); + } + + /** + * Get stats of materials. + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function stats() + { + return $this->httpGet('cgi-bin/material/get_materialcount'); + } + + /** + * Upload material. + * + * @param string $type + * @param string $path + * @param array $form + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function upload(string $type, string $path, array $form = []) + { + if (!file_exists($path) || !is_readable($path)) { + throw new InvalidArgumentException(sprintf('File does not exist, or the file is unreadable: "%s"', $path)); + } + + $form['type'] = $type; + + return $this->httpUpload($this->getApiByType($type), ['media' => $path], $form); + } + + /** + * Get API by type. + * + * @param string $type + * + * @return string + */ + public function getApiByType(string $type) + { + switch ($type) { + case 'news_image': + return 'cgi-bin/media/uploadimg'; + default: + return 'cgi-bin/material/add_material'; + } + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Material/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Material/ServiceProvider.php new file mode 100644 index 0000000..089a8f8 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Material/ServiceProvider.php @@ -0,0 +1,44 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * ServiceProvider.php. + * + * This file is part of the wechat. + * + * (c) overtrue + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Material; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['material'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Menu/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Menu/Client.php new file mode 100644 index 0000000..9c132c6 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Menu/Client.php @@ -0,0 +1,103 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Menu; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author overtrue + */ +class Client extends BaseClient +{ + /** + * Get all menus. + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function list() + { + return $this->httpGet('cgi-bin/menu/get'); + } + + /** + * Get current menus. + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function current() + { + return $this->httpGet('cgi-bin/get_current_selfmenu_info'); + } + + /** + * Add menu. + * + * @param array $buttons + * @param array $matchRule + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function create(array $buttons, array $matchRule = []) + { + if (!empty($matchRule)) { + return $this->httpPostJson('cgi-bin/menu/addconditional', [ + 'button' => $buttons, + 'matchrule' => $matchRule, + ]); + } + + return $this->httpPostJson('cgi-bin/menu/create', ['button' => $buttons]); + } + + /** + * Destroy menu. + * + * @param int $menuId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete(int $menuId = null) + { + if (is_null($menuId)) { + return $this->httpGet('cgi-bin/menu/delete'); + } + + return $this->httpPostJson('cgi-bin/menu/delconditional', ['menuid' => $menuId]); + } + + /** + * Test conditional menu. + * + * @param string $userId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function match(string $userId) + { + return $this->httpPostJson('cgi-bin/menu/trymatch', ['user_id' => $userId]); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Menu/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Menu/ServiceProvider.php new file mode 100644 index 0000000..e79b105 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Menu/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Menu; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['menu'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/OAuth/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/OAuth/ServiceProvider.php new file mode 100644 index 0000000..c9588c4 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/OAuth/ServiceProvider.php @@ -0,0 +1,75 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\OAuth; + +use Overtrue\Socialite\SocialiteManager as Socialite; +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['oauth'] = function ($app) { + $wechat = [ + 'wechat' => [ + 'client_id' => $app['config']['app_id'], + 'client_secret' => $app['config']['secret'], + 'redirect' => $this->prepareCallbackUrl($app), + ], + ]; + + if (!empty($app['config']['component_app_id'] && !empty($app['config']['component_app_token']))) { + $wechat['wechat']['component'] = [ + 'id' => $app['config']['component_app_id'], + 'token' => $app['config']['token'], + ] ; + } + + $socialite = (new Socialite($wechat))->create('wechat'); + + $scopes = (array)$app['config']->get('oauth.scopes', ['snsapi_userinfo']); + + if (!empty($scopes)) { + $socialite->scopes($scopes); + } + + return $socialite; + }; + } + + /** + * Prepare the OAuth callback url for wechat. + * + * @param Container $app + * + * @return string + */ + private function prepareCallbackUrl($app) + { + $callback = $app['config']->get('oauth.callback'); + if (0 === stripos($callback, 'http')) { + return $callback; + } + $baseUrl = $app['request']->getSchemeAndHttpHost(); + + return $baseUrl . '/' . ltrim($callback, '/'); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/OCR/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/OCR/Client.php new file mode 100644 index 0000000..57b5da5 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/OCR/Client.php @@ -0,0 +1,149 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\OCR; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; + +/** + * Class Client. + * + * @author joyeekk + */ +class Client extends BaseClient +{ + /** + * Allow image parameter type. + * + * @var array + */ + protected $allowTypes = ['photo', 'scan']; + + /** + * ID card OCR. + * + * @param string $path + * @param string $type + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function idCard(string $path, string $type = 'photo') + { + if (!\in_array($type, $this->allowTypes, true)) { + throw new InvalidArgumentException(sprintf("Unsupported type: '%s'", $type)); + } + + return $this->httpPost('cv/ocr/idcard', [ + 'type' => $type, + 'img_url' => $path, + ]); + } + + /** + * Bank card OCR. + * + * @param string $path + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function bankCard(string $path) + { + return $this->httpPost('cv/ocr/bankcard', [ + 'img_url' => $path, + ]); + } + + /** + * Vehicle license OCR. + * + * @param string $path + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function vehicleLicense(string $path) + { + return $this->httpPost('cv/ocr/drivinglicense', [ + 'img_url' => $path, + ]); + } + + /** + * Driving OCR. + * + * @param string $path + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function driving(string $path) + { + return $this->httpPost('cv/ocr/driving', [ + 'img_url' => $path, + ]); + } + + /** + * Biz License OCR. + * + * @param string $path + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function bizLicense(string $path) + { + return $this->httpPost('cv/ocr/bizlicense', [ + 'img_url' => $path, + ]); + } + + /** + * Common OCR. + * + * @param string $path + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function common(string $path) + { + return $this->httpPost('cv/ocr/comm', [ + 'img_url' => $path, + ]); + } + + /** + * Plate Number OCR. + * + * @param string $path + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function plateNumber(string $path) + { + return $this->httpPost('cv/ocr/platenum', [ + 'img_url' => $path, + ]); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/OCR/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/OCR/ServiceProvider.php new file mode 100644 index 0000000..1773079 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/OCR/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\OCR; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author joyeekk + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['ocr'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/POI/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/POI/Client.php new file mode 100644 index 0000000..6a8e570 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/POI/Client.php @@ -0,0 +1,145 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\POI; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author overtrue + */ +class Client extends BaseClient +{ + /** + * Get POI supported categories. + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function categories() + { + return $this->httpGet('cgi-bin/poi/getwxcategory'); + } + + /** + * Get POI by ID. + * + * @param int $poiId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get(int $poiId) + { + return $this->httpPostJson('cgi-bin/poi/getpoi', ['poi_id' => $poiId]); + } + + /** + * List POI. + * + * @param int $offset + * @param int $limit + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function list(int $offset = 0, int $limit = 10) + { + $params = [ + 'begin' => $offset, + 'limit' => $limit, + ]; + + return $this->httpPostJson('cgi-bin/poi/getpoilist', $params); + } + + /** + * Create a POI. + * + * @param array $baseInfo + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function create(array $baseInfo) + { + $params = [ + 'business' => [ + 'base_info' => $baseInfo, + ], + ]; + + return $this->httpPostJson('cgi-bin/poi/addpoi', $params); + } + + /** + * @param array $databaseInfo + * + * @return int + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + public function createAndGetId(array $databaseInfo) + { + /** @var array $response */ + $response = $this->detectAndCastResponseToType($this->create($databaseInfo), 'array'); + + return $response['poi_id']; + } + + /** + * Update a POI. + * + * @param int $poiId + * @param array $baseInfo + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(int $poiId, array $baseInfo) + { + $params = [ + 'business' => [ + 'base_info' => array_merge($baseInfo, ['poi_id' => $poiId]), + ], + ]; + + return $this->httpPostJson('cgi-bin/poi/updatepoi', $params); + } + + /** + * Delete a POI. + * + * @param int $poiId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete(int $poiId) + { + return $this->httpPostJson('cgi-bin/poi/delpoi', ['poi_id' => $poiId]); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/POI/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/POI/ServiceProvider.php new file mode 100644 index 0000000..156440b --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/POI/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\POI; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['poi'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Semantic/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Semantic/Client.php new file mode 100644 index 0000000..e83792f --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Semantic/Client.php @@ -0,0 +1,45 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Semantic; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author overtrue + */ +class Client extends BaseClient +{ + /** + * Get the semantic content of giving string. + * + * @param string $keyword + * @param string $categories + * @param array $optional + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function query(string $keyword, string $categories, array $optional = []) + { + $params = [ + 'query' => $keyword, + 'category' => $categories, + 'appid' => $this->app['config']['app_id'], + ]; + + return $this->httpPostJson('semantic/semproxy/search', array_merge($params, $optional)); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Semantic/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Semantic/ServiceProvider.php new file mode 100644 index 0000000..835b7fc --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Semantic/ServiceProvider.php @@ -0,0 +1,31 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Semantic; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['semantic'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Server/Guard.php b/vendor/overtrue/wechat/src/OfficialAccount/Server/Guard.php new file mode 100644 index 0000000..c723847 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Server/Guard.php @@ -0,0 +1,30 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Server; + +use EasyWeChat\Kernel\ServerGuard; + +/** + * Class Guard. + * + * @author overtrue + */ +class Guard extends ServerGuard +{ + /** + * @return bool + */ + protected function shouldReturnRawResponse(): bool + { + return !is_null($this->app['request']->get('echostr')); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Server/Handlers/EchoStrHandler.php b/vendor/overtrue/wechat/src/OfficialAccount/Server/Handlers/EchoStrHandler.php new file mode 100644 index 0000000..e985692 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Server/Handlers/EchoStrHandler.php @@ -0,0 +1,53 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Server\Handlers; + +use EasyWeChat\Kernel\Contracts\EventHandlerInterface; +use EasyWeChat\Kernel\Decorators\FinallyResult; +use EasyWeChat\Kernel\ServiceContainer; + +/** + * Class EchoStrHandler. + * + * @author overtrue + */ +class EchoStrHandler implements EventHandlerInterface +{ + /** + * @var ServiceContainer + */ + protected $app; + + /** + * EchoStrHandler constructor. + * + * @param ServiceContainer $app + */ + public function __construct(ServiceContainer $app) + { + $this->app = $app; + } + + /** + * @param mixed $payload + * + * @return FinallyResult|null + */ + public function handle($payload = null) + { + if ($str = $this->app['request']->get('echostr')) { + return new FinallyResult($str); + } + + return null; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Server/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Server/ServiceProvider.php new file mode 100644 index 0000000..0c6716b --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Server/ServiceProvider.php @@ -0,0 +1,46 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Server; + +use EasyWeChat\Kernel\Encryptor; +use EasyWeChat\OfficialAccount\Server\Handlers\EchoStrHandler; +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + !isset($app['encryptor']) && $app['encryptor'] = function ($app) { + return new Encryptor( + $app['config']['app_id'], + $app['config']['token'], + $app['config']['aes_key'] + ); + }; + + !isset($app['server']) && $app['server'] = function ($app) { + $guard = new Guard($app); + $guard->push(new EchoStrHandler($app)); + + return $guard; + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/Client.php new file mode 100644 index 0000000..c401008 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/Client.php @@ -0,0 +1,81 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\ShakeAround; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author overtrue + */ +class Client extends BaseClient +{ + /** + * @param array $data + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function register($data) + { + return $this->httpPostJson('shakearound/account/register', $data); + } + + /** + * Get audit status. + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function status() + { + return $this->httpGet('shakearound/account/auditstatus'); + } + + /** + * Get shake info. + * + * @param string $ticket + * @param bool $needPoi + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function user(string $ticket, bool $needPoi = false) + { + $params = [ + 'ticket' => $ticket, + ]; + + if ($needPoi) { + $params['need_poi'] = 1; + } + + return $this->httpPostJson('shakearound/user/getshakeinfo', $params); + } + + /** + * @param string $ticket + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + */ + public function userWithPoi(string $ticket) + { + return $this->user($ticket, true); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/DeviceClient.php b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/DeviceClient.php new file mode 100644 index 0000000..859a120 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/DeviceClient.php @@ -0,0 +1,190 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\ShakeAround; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class DeviceClient. + * + * @author allen05ren + */ +class DeviceClient extends BaseClient +{ + /** + * @param array $data + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function apply(array $data) + { + return $this->httpPostJson('shakearound/device/applyid', $data); + } + + /** + * Get audit status. + * + * @param int $applyId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function status(int $applyId) + { + $params = [ + 'apply_id' => $applyId, + ]; + + return $this->httpPostJson('shakearound/device/applystatus', $params); + } + + /** + * Update a device comment. + * + * @param array $deviceIdentifier + * @param string $comment + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(array $deviceIdentifier, string $comment) + { + $params = [ + 'device_identifier' => $deviceIdentifier, + 'comment' => $comment, + ]; + + return $this->httpPostJson('shakearound/device/update', $params); + } + + /** + * Bind location for device. + * + * @param array $deviceIdentifier + * @param int $poiId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function bindPoi(array $deviceIdentifier, int $poiId) + { + $params = [ + 'device_identifier' => $deviceIdentifier, + 'poi_id' => $poiId, + ]; + + return $this->httpPostJson('shakearound/device/bindlocation', $params); + } + + /** + * @param array $deviceIdentifier + * @param int $poiId + * @param string $appId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function bindThirdPoi(array $deviceIdentifier, int $poiId, string $appId) + { + $params = [ + 'device_identifier' => $deviceIdentifier, + 'poi_id' => $poiId, + 'type' => 2, + 'poi_appid' => $appId, + ]; + + return $this->httpPostJson('shakearound/device/bindlocation', $params); + } + + /** + * Fetch batch of devices by deviceIds. + * + * @param array $deviceIdentifiers + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + */ + public function listByIds(array $deviceIdentifiers) + { + $params = [ + 'type' => 1, + 'device_identifiers' => $deviceIdentifiers, + ]; + + return $this->search($params); + } + + /** + * Pagination to get batch of devices. + * + * @param int $lastId + * @param int $count + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + */ + public function list(int $lastId, int $count) + { + $params = [ + 'type' => 2, + 'last_seen' => $lastId, + 'count' => $count, + ]; + + return $this->search($params); + } + + /** + * Fetch batch of devices by applyId. + * + * @param int $applyId + * @param int $lastId + * @param int $count + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + */ + public function listByApplyId(int $applyId, int $lastId, int $count) + { + $params = [ + 'type' => 3, + 'apply_id' => $applyId, + 'last_seen' => $lastId, + 'count' => $count, + ]; + + return $this->search($params); + } + + /** + * Fetch batch of devices. + * + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function search(array $params) + { + return $this->httpPostJson('shakearound/device/search', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/GroupClient.php b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/GroupClient.php new file mode 100644 index 0000000..9fd578d --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/GroupClient.php @@ -0,0 +1,167 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\ShakeAround; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class GroupClient. + * + * @author allen05ren + */ +class GroupClient extends BaseClient +{ + /** + * Add device group. + * + * @param string $name + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function create(string $name) + { + $params = [ + 'group_name' => $name, + ]; + + return $this->httpPostJson('shakearound/device/group/add', $params); + } + + /** + * Update a device group name. + * + * @param int $groupId + * @param string $name + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(int $groupId, string $name) + { + $params = [ + 'group_id' => $groupId, + 'group_name' => $name, + ]; + + return $this->httpPostJson('shakearound/device/group/update', $params); + } + + /** + * Delete device group. + * + * @param int $groupId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete(int $groupId) + { + $params = [ + 'group_id' => $groupId, + ]; + + return $this->httpPostJson('shakearound/device/group/delete', $params); + } + + /** + * List all device groups. + * + * @param int $begin + * @param int $count + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function list(int $begin, int $count) + { + $params = [ + 'begin' => $begin, + 'count' => $count, + ]; + + return $this->httpPostJson('shakearound/device/group/getlist', $params); + } + + /** + * Get detail of a device group. + * + * @param int $groupId + * @param int $begin + * @param int $count + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get(int $groupId, int $begin, int $count) + { + $params = [ + 'group_id' => $groupId, + 'begin' => $begin, + 'count' => $count, + ]; + + return $this->httpPostJson('shakearound/device/group/getdetail', $params); + } + + /** + * Add one or more devices to a device group. + * + * @param int $groupId + * @param array $deviceIdentifiers + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function addDevices(int $groupId, array $deviceIdentifiers) + { + $params = [ + 'group_id' => $groupId, + 'device_identifiers' => $deviceIdentifiers, + ]; + + return $this->httpPostJson('shakearound/device/group/adddevice', $params); + } + + /** + * Remove one or more devices from a device group. + * + * @param int $groupId + * @param array $deviceIdentifiers + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function removeDevices(int $groupId, array $deviceIdentifiers) + { + $params = [ + 'group_id' => $groupId, + 'device_identifiers' => $deviceIdentifiers, + ]; + + return $this->httpPostJson('shakearound/device/group/deletedevice', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/MaterialClient.php b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/MaterialClient.php new file mode 100644 index 0000000..edc40b2 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/MaterialClient.php @@ -0,0 +1,44 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\ShakeAround; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; + +/** + * Class MaterialClient. + * + * @author allen05ren + */ +class MaterialClient extends BaseClient +{ + /** + * Upload image material. + * + * @param string $path + * @param string $type + * + * @return string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function uploadImage(string $path, string $type = 'icon') + { + if (!file_exists($path) || !is_readable($path)) { + throw new InvalidArgumentException(sprintf('File does not exist, or the file is unreadable: "%s"', $path)); + } + + return $this->httpUpload('shakearound/material/add', ['media' => $path], [], ['type' => strtolower($type)]); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/PageClient.php b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/PageClient.php new file mode 100644 index 0000000..73ba1ac --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/PageClient.php @@ -0,0 +1,110 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\ShakeAround; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class PageClient. + * + * @author allen05ren + */ +class PageClient extends BaseClient +{ + /** + * @param array $data + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function create(array $data) + { + return $this->httpPostJson('shakearound/page/add', $data); + } + + /** + * @param int $pageId + * @param array $data + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(int $pageId, array $data) + { + return $this->httpPostJson('shakearound/page/update', array_merge(['page_id' => $pageId], $data)); + } + + /** + * Fetch batch of pages by pageIds. + * + * @param array $pageIds + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function listByIds(array $pageIds) + { + $params = [ + 'type' => 1, + 'page_ids' => $pageIds, + ]; + + return $this->httpPostJson('shakearound/page/search', $params); + } + + /** + * Pagination to get batch of pages. + * + * @param int $begin + * @param int $count + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function list(int $begin, int $count) + { + $params = [ + 'type' => 2, + 'begin' => $begin, + 'count' => $count, + ]; + + return $this->httpPostJson('shakearound/page/search', $params); + } + + /** + * delete a page. + * + * @param int $pageId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete(int $pageId) + { + $params = [ + 'page_id' => $pageId, + ]; + + return $this->httpPostJson('shakearound/page/delete', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/RelationClient.php b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/RelationClient.php new file mode 100644 index 0000000..de80911 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/RelationClient.php @@ -0,0 +1,87 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\ShakeAround; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class RelationClient. + * + * @author allen05ren + */ +class RelationClient extends BaseClient +{ + /** + * Bind pages for device. + * + * @param array $deviceIdentifier + * @param array $pageIds + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function bindPages(array $deviceIdentifier, array $pageIds) + { + $params = [ + 'device_identifier' => $deviceIdentifier, + 'page_ids' => $pageIds, + ]; + + return $this->httpPostJson('shakearound/device/bindpage', $params); + } + + /** + * Get pageIds by deviceId. + * + * @param array $deviceIdentifier + * + * @return array|\EasyWeChat\Kernel\Support\Collection + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function listByDeviceId(array $deviceIdentifier) + { + $params = [ + 'type' => 1, + 'device_identifier' => $deviceIdentifier, + ]; + + return $this->httpPostJson('shakearound/relation/search', $params); + } + + /** + * Get devices by pageId. + * + * @param int $pageId + * @param int $begin + * @param int $count + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function listByPageId(int $pageId, int $begin, int $count) + { + $params = [ + 'type' => 2, + 'page_id' => $pageId, + 'begin' => $begin, + 'count' => $count, + ]; + + return $this->httpPostJson('shakearound/relation/search', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/ServiceProvider.php new file mode 100644 index 0000000..a13d2b0 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/ServiceProvider.php @@ -0,0 +1,57 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\ShakeAround; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author allen05ren + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['shake_around'] = function ($app) { + return new ShakeAround($app); + }; + + $app['shake_around.device'] = function ($app) { + return new DeviceClient($app); + }; + + $app['shake_around.page'] = function ($app) { + return new PageClient($app); + }; + + $app['shake_around.material'] = function ($app) { + return new MaterialClient($app); + }; + + $app['shake_around.group'] = function ($app) { + return new GroupClient($app); + }; + + $app['shake_around.relation'] = function ($app) { + return new RelationClient($app); + }; + + $app['shake_around.stats'] = function ($app) { + return new StatsClient($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/ShakeAround.php b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/ShakeAround.php new file mode 100644 index 0000000..adcfcb4 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/ShakeAround.php @@ -0,0 +1,45 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\ShakeAround; + +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; + +/** + * Class Card. + * + * @author overtrue + * + * @property \EasyWeChat\OfficialAccount\ShakeAround\DeviceClient $device + * @property \EasyWeChat\OfficialAccount\ShakeAround\GroupClient $group + * @property \EasyWeChat\OfficialAccount\ShakeAround\MaterialClient $material + * @property \EasyWeChat\OfficialAccount\ShakeAround\RelationClient $relation + * @property \EasyWeChat\OfficialAccount\ShakeAround\StatsClient $stats + * @property \EasyWeChat\OfficialAccount\ShakeAround\PageClient $page + */ +class ShakeAround extends Client +{ + /** + * @param string $property + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + public function __get($property) + { + if (isset($this->app["shake_around.{$property}"])) { + return $this->app["shake_around.{$property}"]; + } + + throw new InvalidArgumentException(sprintf('No shake_around service named "%s".', $property)); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/StatsClient.php b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/StatsClient.php new file mode 100644 index 0000000..a4b55eb --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/StatsClient.php @@ -0,0 +1,110 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\ShakeAround; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class StatsClient. + * + * @author allen05ren + */ +class StatsClient extends BaseClient +{ + /** + * Fetch statistics data by deviceId. + * + * @param array $deviceIdentifier + * @param int $beginTime (Unix timestamp) + * @param int $endTime (Unix timestamp) + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function deviceSummary(array $deviceIdentifier, int $beginTime, int $endTime) + { + $params = [ + 'device_identifier' => $deviceIdentifier, + 'begin_date' => $beginTime, + 'end_date' => $endTime, + ]; + + return $this->httpPostJson('shakearound/statistics/device', $params); + } + + /** + * Fetch all devices statistics data by date. + * + * @param int $timestamp + * @param int $pageIndex + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function devicesSummary(int $timestamp, int $pageIndex) + { + $params = [ + 'date' => $timestamp, + 'page_index' => $pageIndex, + ]; + + return $this->httpPostJson('shakearound/statistics/devicelist', $params); + } + + /** + * Fetch statistics data by pageId. + * + * @param int $pageId + * @param int $beginTime (Unix timestamp) + * @param int $endTime (Unix timestamp) + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function pageSummary(int $pageId, int $beginTime, int $endTime) + { + $params = [ + 'page_id' => $pageId, + 'begin_date' => $beginTime, + 'end_date' => $endTime, + ]; + + return $this->httpPostJson('shakearound/statistics/page', $params); + } + + /** + * Fetch all pages statistics data by date. + * + * @param int $timestamp + * @param int $pageIndex + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function pagesSummary(int $timestamp, int $pageIndex) + { + $params = [ + 'date' => $timestamp, + 'page_index' => $pageIndex, + ]; + + return $this->httpPostJson('shakearound/statistics/pagelist', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Store/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Store/Client.php new file mode 100644 index 0000000..b873469 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Store/Client.php @@ -0,0 +1,209 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Store; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author bigface + */ +class Client extends BaseClient +{ + /** + * Get WXA supported categories. + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function categories() + { + return $this->httpGet('wxa/get_merchant_category'); + } + + /** + * Get district from tencent map . + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function districts() + { + return $this->httpGet('wxa/get_district'); + } + + /** + * Search store from tencent map. + * + * @param int $districtId + * @param string $keyword + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function searchFromMap(int $districtId, string $keyword) + { + $params = [ + 'districtid' => $districtId, + 'keyword' => $keyword, + ]; + + return $this->httpPostJson('wxa/search_map_poi', $params); + } + + /** + * Get store check status. + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getStatus() + { + return $this->httpGet('wxa/get_merchant_audit_info'); + } + + /** + * Create a merchant. + * + * @param array $baseInfo + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function createMerchant(array $baseInfo) + { + return $this->httpPostJson('wxa/apply_merchant', $baseInfo); + } + + /** + * Update a merchant. + * + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function updateMerchant(array $params) + { + return $this->httpPostJson('wxa/modify_merchant', $params); + } + + /** + * Create a store from tencent map. + * + * @param array $baseInfo + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function createFromMap(array $baseInfo) + { + return $this->httpPostJson('wxa/create_map_poi', $baseInfo); + } + + /** + * Create a store. + * + * @param array $baseInfo + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function create(array $baseInfo) + { + return $this->httpPostJson('wxa/add_store', $baseInfo); + } + + /** + * Update a store. + * + * @param int $poiId + * @param array $baseInfo + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(int $poiId, array $baseInfo) + { + $params = array_merge($baseInfo, ['poi_id' => $poiId]); + + return $this->httpPostJson('wxa/update_store', $params); + } + + /** + * Get store by ID. + * + * @param int $poiId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get(int $poiId) + { + return $this->httpPostJson('wxa/get_store_info', ['poi_id' => $poiId]); + } + + /** + * List store. + * + * @param int $offset + * @param int $limit + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function list(int $offset = 0, int $limit = 10) + { + $params = [ + 'offset' => $offset, + 'limit' => $limit, + ]; + + return $this->httpPostJson('wxa/get_store_list', $params); + } + + /** + * Delete a store. + * + * @param int $poiId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete(int $poiId) + { + return $this->httpPostJson('wxa/del_store', ['poi_id' => $poiId]); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Store/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Store/ServiceProvider.php new file mode 100644 index 0000000..f5c48d7 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/Store/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\Store; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author bigface + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['store'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/SubscribeMessage/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/SubscribeMessage/Client.php new file mode 100644 index 0000000..f303fc4 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/SubscribeMessage/Client.php @@ -0,0 +1,189 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\SubscribeMessage; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; +use ReflectionClass; + +/** + * Class Client. + * + * @author tegic + */ +class Client extends BaseClient +{ + protected $message = [ + 'touser' => '', + 'template_id' => '', + 'page' => '', + 'miniprogram' => '', + 'data' => [], + ]; + + /** + * {@inheritdoc}. + */ + protected $required = ['touser', 'template_id', 'data']; + + /** + * Combine templates and add them to your personal template library under your account. + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function addTemplate(string $tid, array $kidList, string $sceneDesc = null) + { + $sceneDesc = $sceneDesc ?? ''; + $data = \compact('tid', 'kidList', 'sceneDesc'); + + return $this->httpPost('wxaapi/newtmpl/addtemplate', $data); + } + + /** + * Delete personal template under account. + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function deleteTemplate(string $id) + { + return $this->httpPost('wxaapi/newtmpl/deltemplate', ['priTmplId' => $id]); + } + + /** + * Get the category of the applet account. + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getCategory() + { + return $this->httpGet('wxaapi/newtmpl/getcategory'); + } + + /** + * Get keyword list under template title. + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getTemplateKeywords(string $tid) + { + return $this->httpGet('wxaapi/newtmpl/getpubtemplatekeywords', compact('tid')); + } + + /** + * Get the title of the public template under the category to which the account belongs. + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getTemplateTitles(array $ids, int $start = 0, int $limit = 30) + { + $ids = \implode(',', $ids); + $query = \compact('ids', 'start', 'limit'); + + return $this->httpGet('wxaapi/newtmpl/getpubtemplatetitles', $query); + } + + /** + * Get list of personal templates under the current account. + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getTemplates() + { + return $this->httpGet('wxaapi/newtmpl/gettemplate'); + } + + /** + * Send a subscribe message. + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function send(array $data = []) + { + $params = $this->formatMessage($data); + + $this->restoreMessage(); + + return $this->httpPostJson('cgi-bin/message/subscribe/bizsend', $params); + } + + /** + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + protected function formatMessage(array $data = []) + { + $params = array_merge($this->message, $data); + + foreach ($params as $key => $value) { + if (in_array($key, $this->required, true) && empty($value) && empty($this->message[$key])) { + throw new InvalidArgumentException(sprintf('Attribute "%s" can not be empty!', $key)); + } + + $params[$key] = empty($value) ? $this->message[$key] : $value; + } + + foreach ($params['data'] as $key => $value) { + if (is_array($value)) { + if (\array_key_exists('value', $value)) { + $params['data'][$key] = ['value' => $value['value']]; + + continue; + } + + if (count($value) >= 1) { + $value = [ + 'value' => $value[0], +// 'color' => $value[1],// color unsupported + ]; + } + } else { + $value = [ + 'value' => strval($value), + ]; + } + + $params['data'][$key] = $value; + } + + return $params; + } + + /** + * Restore message. + */ + protected function restoreMessage() + { + $this->message = (new ReflectionClass(static::class))->getDefaultProperties()['message']; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/SubscribeMessage/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/SubscribeMessage/ServiceProvider.php new file mode 100644 index 0000000..da0eef9 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/SubscribeMessage/ServiceProvider.php @@ -0,0 +1,27 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\SubscribeMessage; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['subscribe_message'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/TemplateMessage/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/TemplateMessage/Client.php new file mode 100644 index 0000000..475be1b --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/TemplateMessage/Client.php @@ -0,0 +1,234 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\TemplateMessage; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; +use ReflectionClass; + +/** + * Class Client. + * + * @author overtrue + */ +class Client extends BaseClient +{ + public const API_SEND = 'cgi-bin/message/template/send'; + + /** + * Attributes. + * + * @var array + */ + protected $message = [ + 'touser' => '', + 'template_id' => '', + 'url' => '', + 'data' => [], + 'miniprogram' => '', + ]; + + /** + * Required attributes. + * + * @var array + */ + protected $required = ['touser', 'template_id']; + + /** + * Set industry. + * + * @param int $industryOne + * @param int $industryTwo + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function setIndustry($industryOne, $industryTwo) + { + $params = [ + 'industry_id1' => $industryOne, + 'industry_id2' => $industryTwo, + ]; + + return $this->httpPostJson('cgi-bin/template/api_set_industry', $params); + } + + /** + * Get industry. + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getIndustry() + { + return $this->httpPostJson('cgi-bin/template/get_industry'); + } + + /** + * Add a template and get template ID. + * + * @param string $shortId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function addTemplate($shortId) + { + $params = ['template_id_short' => $shortId]; + + return $this->httpPostJson('cgi-bin/template/api_add_template', $params); + } + + /** + * Get private templates. + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getPrivateTemplates() + { + return $this->httpPostJson('cgi-bin/template/get_all_private_template'); + } + + /** + * Delete private template. + * + * @param string $templateId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function deletePrivateTemplate($templateId) + { + $params = ['template_id' => $templateId]; + + return $this->httpPostJson('cgi-bin/template/del_private_template', $params); + } + + /** + * Send a template message. + * + * @param array $data + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function send(array $data = []) + { + $params = $this->formatMessage($data); + + $this->restoreMessage(); + + return $this->httpPostJson(static::API_SEND, $params); + } + + /** + * Send template-message for subscription. + * + * @param array $data + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function sendSubscription(array $data = []) + { + $params = $this->formatMessage($data); + + $this->restoreMessage(); + + return $this->httpPostJson('cgi-bin/message/template/subscribe', $params); + } + + /** + * @param array $data + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + protected function formatMessage(array $data = []) + { + $params = array_merge($this->message, $data); + + foreach ($params as $key => $value) { + if (in_array($key, $this->required, true) && empty($value) && empty($this->message[$key])) { + throw new InvalidArgumentException(sprintf('Attribute "%s" can not be empty!', $key)); + } + + $params[$key] = empty($value) ? $this->message[$key] : $value; + } + + $params['data'] = $this->formatData($params['data'] ?? []); + + return $params; + } + + /** + * @param array $data + * + * @return array + */ + protected function formatData(array $data) + { + $formatted = []; + + foreach ($data as $key => $value) { + if (is_array($value)) { + if (\array_key_exists('value', $value)) { + $formatted[$key] = $value; + + continue; + } + + if (count($value) >= 2) { + $value = [ + 'value' => $value[0], + 'color' => $value[1], + ]; + } + } else { + $value = [ + 'value' => strval($value), + ]; + } + + $formatted[$key] = $value; + } + + return $formatted; + } + + /** + * Restore message. + */ + protected function restoreMessage() + { + $this->message = (new ReflectionClass(static::class))->getDefaultProperties()['message']; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/TemplateMessage/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/TemplateMessage/ServiceProvider.php new file mode 100644 index 0000000..98476fc --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/TemplateMessage/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\TemplateMessage; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['template_message'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/User/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/User/ServiceProvider.php new file mode 100644 index 0000000..c11d8b3 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/User/ServiceProvider.php @@ -0,0 +1,35 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\User; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['user'] = function ($app) { + return new UserClient($app); + }; + + $app['user_tag'] = function ($app) { + return new TagClient($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/User/TagClient.php b/vendor/overtrue/wechat/src/OfficialAccount/User/TagClient.php new file mode 100644 index 0000000..d41e363 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/User/TagClient.php @@ -0,0 +1,175 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\User; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class TagClient. + * + * @author overtrue + */ +class TagClient extends BaseClient +{ + /** + * Create tag. + * + * @param string $name + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function create(string $name) + { + $params = [ + 'tag' => ['name' => $name], + ]; + + return $this->httpPostJson('cgi-bin/tags/create', $params); + } + + /** + * List all tags. + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function list() + { + return $this->httpGet('cgi-bin/tags/get'); + } + + /** + * Update a tag name. + * + * @param int $tagId + * @param string $name + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(int $tagId, string $name) + { + $params = [ + 'tag' => [ + 'id' => $tagId, + 'name' => $name, + ], + ]; + + return $this->httpPostJson('cgi-bin/tags/update', $params); + } + + /** + * Delete tag. + * + * @param int $tagId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete(int $tagId) + { + $params = [ + 'tag' => ['id' => $tagId], + ]; + + return $this->httpPostJson('cgi-bin/tags/delete', $params); + } + + /** + * Get user tags. + * + * @param string $openid + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function userTags(string $openid) + { + $params = ['openid' => $openid]; + + return $this->httpPostJson('cgi-bin/tags/getidlist', $params); + } + + /** + * Get users from a tag. + * + * @param int $tagId + * @param string $nextOpenId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function usersOfTag(int $tagId, string $nextOpenId = '') + { + $params = [ + 'tagid' => $tagId, + 'next_openid' => $nextOpenId, + ]; + + return $this->httpPostJson('cgi-bin/user/tag/get', $params); + } + + /** + * Batch tag users. + * + * @param array $openids + * @param int $tagId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function tagUsers(array $openids, int $tagId) + { + $params = [ + 'openid_list' => $openids, + 'tagid' => $tagId, + ]; + + return $this->httpPostJson('cgi-bin/tags/members/batchtagging', $params); + } + + /** + * Untag users from a tag. + * + * @param array $openids + * @param int $tagId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function untagUsers(array $openids, int $tagId) + { + $params = [ + 'openid_list' => $openids, + 'tagid' => $tagId, + ]; + + return $this->httpPostJson('cgi-bin/tags/members/batchuntagging', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/User/UserClient.php b/vendor/overtrue/wechat/src/OfficialAccount/User/UserClient.php new file mode 100644 index 0000000..6b9491f --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/User/UserClient.php @@ -0,0 +1,172 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\User; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class UserClient. + * + * @author overtrue + */ +class UserClient extends BaseClient +{ + /** + * Fetch a user by open id. + * + * @param string $openid + * @param string $lang + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function get(string $openid, string $lang = 'zh_CN') + { + $params = [ + 'openid' => $openid, + 'lang' => $lang, + ]; + + return $this->httpGet('cgi-bin/user/info', $params); + } + + /** + * Batch get users. + * + * @param array $openids + * @param string $lang + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function select(array $openids, string $lang = 'zh_CN') + { + return $this->httpPostJson('cgi-bin/user/info/batchget', [ + 'user_list' => array_map(function ($openid) use ($lang) { + return [ + 'openid' => $openid, + 'lang' => $lang, + ]; + }, $openids), + ]); + } + + /** + * List users. + * + * @param string $nextOpenId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function list(string $nextOpenId = null) + { + $params = ['next_openid' => $nextOpenId]; + + return $this->httpGet('cgi-bin/user/get', $params); + } + + /** + * Set user remark. + * + * @param string $openid + * @param string $remark + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function remark(string $openid, string $remark) + { + $params = [ + 'openid' => $openid, + 'remark' => $remark, + ]; + + return $this->httpPostJson('cgi-bin/user/info/updateremark', $params); + } + + /** + * Get black list. + * + * @param string|null $beginOpenid + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function blacklist(string $beginOpenid = null) + { + $params = ['begin_openid' => $beginOpenid]; + + return $this->httpPostJson('cgi-bin/tags/members/getblacklist', $params); + } + + /** + * Batch block user. + * + * @param array|string $openidList + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function block($openidList) + { + $params = ['openid_list' => (array) $openidList]; + + return $this->httpPostJson('cgi-bin/tags/members/batchblacklist', $params); + } + + /** + * Batch unblock user. + * + * @param array $openidList + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function unblock($openidList) + { + $params = ['openid_list' => (array) $openidList]; + + return $this->httpPostJson('cgi-bin/tags/members/batchunblacklist', $params); + } + + /** + * @param string $oldAppId + * @param array $openidList + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function changeOpenid(string $oldAppId, array $openidList) + { + $params = [ + 'from_appid' => $oldAppId, + 'openid_list' => $openidList, + ]; + + return $this->httpPostJson('cgi-bin/changeopenid', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/WiFi/CardClient.php b/vendor/overtrue/wechat/src/OfficialAccount/WiFi/CardClient.php new file mode 100644 index 0000000..76a4e6a --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/WiFi/CardClient.php @@ -0,0 +1,52 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\WiFi; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class CardClient. + * + * @author her-cat + */ +class CardClient extends BaseClient +{ + /** + * Set shop card coupon delivery information. + * + * @param array $data + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function set(array $data) + { + return $this->httpPostJson('bizwifi/couponput/set', $data); + } + + /** + * Get shop card coupon delivery information. + * + * @param int $shopId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get(int $shopId = 0) + { + return $this->httpPostJson('bizwifi/couponput/get', ['shop_id' => $shopId]); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/WiFi/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/WiFi/Client.php new file mode 100644 index 0000000..076957d --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/WiFi/Client.php @@ -0,0 +1,98 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\WiFi; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author her-cat + */ +class Client extends BaseClient +{ + /** + * Get Wi-Fi statistics. + * + * @param string $beginDate + * @param string $endDate + * @param int $shopId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function summary(string $beginDate, string $endDate, int $shopId = -1) + { + $data = [ + 'begin_date' => $beginDate, + 'end_date' => $endDate, + 'shop_id' => $shopId, + ]; + + return $this->httpPostJson('bizwifi/statistics/list', $data); + } + + /** + * Get the material QR code. + * + * @param int $shopId + * @param string $ssid + * @param int $type + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getQrCodeUrl(int $shopId, string $ssid, int $type = 0) + { + $data = [ + 'shop_id' => $shopId, + 'ssid' => $ssid, + 'img_id' => $type, + ]; + + return $this->httpPostJson('bizwifi/qrcode/get', $data); + } + + /** + * Wi-Fi completion page jump applet. + * + * @param array $data + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function setFinishPage(array $data) + { + return $this->httpPostJson('bizwifi/finishpage/set', $data); + } + + /** + * Set the top banner jump applet. + * + * @param array $data + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function setHomePage(array $data) + { + return $this->httpPostJson('bizwifi/homepage/set', $data); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/WiFi/DeviceClient.php b/vendor/overtrue/wechat/src/OfficialAccount/WiFi/DeviceClient.php new file mode 100644 index 0000000..8fecbf7 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/WiFi/DeviceClient.php @@ -0,0 +1,127 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\WiFi; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class DeviceClient. + * + * @author her-cat + */ +class DeviceClient extends BaseClient +{ + /** + * Add a password device. + * + * @param int $shopId + * @param string $ssid + * @param string $password + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function addPasswordDevice(int $shopId, string $ssid, string $password) + { + $data = [ + 'shop_id' => $shopId, + 'ssid' => $ssid, + 'password' => $password, + ]; + + return $this->httpPostJson('bizwifi/device/add', $data); + } + + /** + * Add a portal device. + * + * @param int $shopId + * @param string $ssid + * @param bool $reset + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function addPortalDevice(int $shopId, string $ssid, bool $reset = false) + { + $data = [ + 'shop_id' => $shopId, + 'ssid' => $ssid, + 'reset' => $reset, + ]; + + return $this->httpPostJson('bizwifi/apportal/register', $data); + } + + /** + * Delete device by MAC address. + * + * @param string $macAddress + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete(string $macAddress) + { + return $this->httpPostJson('bizwifi/device/delete', ['bssid' => $macAddress]); + } + + /** + * Get a list of devices. + * + * @param int $page + * @param int $size + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function list(int $page = 1, int $size = 10) + { + $data = [ + 'pageindex' => $page, + 'pagesize' => $size, + ]; + + return $this->httpPostJson('bizwifi/device/list', $data); + } + + /** + * Get a list of devices by shop ID. + * + * @param int $shopId + * @param int $page + * @param int $size + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function listByShopId(int $shopId, int $page = 1, int $size = 10) + { + $data = [ + 'shop_id' => $shopId, + 'pageindex' => $page, + 'pagesize' => $size, + ]; + + return $this->httpPostJson('bizwifi/device/list', $data); + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/WiFi/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/WiFi/ServiceProvider.php new file mode 100644 index 0000000..7a28b51 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/WiFi/ServiceProvider.php @@ -0,0 +1,45 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\WiFi; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author her-cat + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc} + */ + public function register(Container $app) + { + $app['wifi'] = function ($app) { + return new Client($app); + }; + + $app['wifi_card'] = function ($app) { + return new CardClient($app); + }; + + $app['wifi_device'] = function ($app) { + return new DeviceClient($app); + }; + + $app['wifi_shop'] = function ($app) { + return new ShopClient($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OfficialAccount/WiFi/ShopClient.php b/vendor/overtrue/wechat/src/OfficialAccount/WiFi/ShopClient.php new file mode 100644 index 0000000..34f1c97 --- /dev/null +++ b/vendor/overtrue/wechat/src/OfficialAccount/WiFi/ShopClient.php @@ -0,0 +1,100 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OfficialAccount\WiFi; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class ShopClient. + * + * @author her-cat + */ +class ShopClient extends BaseClient +{ + /** + * Get shop Wi-Fi information. + * + * @param int $shopId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get(int $shopId) + { + return $this->httpPostJson('bizwifi/shop/get', ['shop_id' => $shopId]); + } + + /** + * Get a list of Wi-Fi shops. + * + * @param int $page + * @param int $size + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function list(int $page = 1, int $size = 10) + { + $data = [ + 'pageindex' => $page, + 'pagesize' => $size, + ]; + + return $this->httpPostJson('bizwifi/shop/list', $data); + } + + /** + * Update shop Wi-Fi information. + * + * @param int $shopId + * @param array $data + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(int $shopId, array $data) + { + $data = array_merge(['shop_id' => $shopId], $data); + + return $this->httpPostJson('bizwifi/shop/update', $data); + } + + /** + * Clear shop network and equipment. + * + * @param int $shopId + * @param string|null $ssid + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function clearDevice(int $shopId, string $ssid = null) + { + $data = [ + 'shop_id' => $shopId, + ]; + + if (!is_null($ssid)) { + $data['ssid'] = $ssid; + } + + return $this->httpPostJson('bizwifi/shop/clean', $data); + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Application.php b/vendor/overtrue/wechat/src/OpenPlatform/Application.php new file mode 100644 index 0000000..386e7a3 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Application.php @@ -0,0 +1,251 @@ + [ + 'timeout' => 5.0, + 'base_uri' => 'https://api.weixin.qq.com/', + ], + ]; + + /** + * Creates the officialAccount application. + * + * @param string $appId + * @param string|null $refreshToken + * @param \EasyWeChat\OpenPlatform\Authorizer\Auth\AccessToken|null $accessToken + * + * @return \EasyWeChat\OpenPlatform\Authorizer\OfficialAccount\Application + */ + public function officialAccount( + string $appId, + string $refreshToken = null, + AccessToken $accessToken = null + ): OfficialAccount { + $application = new OfficialAccount( + $this->getAuthorizerConfig($appId, $refreshToken), + $this->getReplaceServices($accessToken) + [ + 'encryptor' => $this['encryptor'], + + 'account' => function ($app) { + return new AccountClient($app, $this); + }, + ] + ); + + $application->extend( + 'oauth', + function ($socialite) { + /* @var \Overtrue\Socialite\Providers\WeChat $socialite */ + $socialite->withComponent( + [ + 'id' => $this['config']['app_id'], + 'token' => fn () => $this['access_token']->getToken()['component_access_token'], + ] + ); + + return $socialite; + } + ); + + return $application; + } + + /** + * Creates the miniProgram application. + * + * @param string $appId + * @param string|null $refreshToken + * @param \EasyWeChat\OpenPlatform\Authorizer\Auth\AccessToken|null $accessToken + * + * @return \EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Application + */ + public function miniProgram( + string $appId, + string $refreshToken = null, + AccessToken $accessToken = null + ): MiniProgram { + return new MiniProgram( + $this->getAuthorizerConfig($appId, $refreshToken), + $this->getReplaceServices($accessToken) + [ + 'encryptor' => function () { + return new Encryptor( + $this['config']['app_id'], + $this['config']['token'], + $this['config']['aes_key'] + ); + }, + + 'auth' => function ($app) { + return new Client($app, $this); + }, + ] + ); + } + + /** + * Return the pre-authorization login page url. + * + * @param string $callbackUrl + * @param string|array|null $optional + * + * @return string + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public function getPreAuthorizationUrl(string $callbackUrl, $optional = []): string + { + // 兼容旧版 API 设计 + if (\is_string($optional)) { + $optional = [ + 'pre_auth_code' => $optional, + ]; + } else { + $optional['pre_auth_code'] = data_get($this->createPreAuthorizationCode(), 'pre_auth_code'); + } + + $queries = \array_merge( + $optional, + [ + 'component_appid' => $this['config']['app_id'], + 'redirect_uri' => $callbackUrl, + ] + ); + + return 'https://mp.weixin.qq.com/cgi-bin/componentloginpage?'.http_build_query($queries); + } + + /** + * Return the pre-authorization login page url (mobile). + * + * @param string $callbackUrl + * @param string|array|null $optional + * + * @return string + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + public function getMobilePreAuthorizationUrl(string $callbackUrl, $optional = []): string + { + // 兼容旧版 API 设计 + if (\is_string($optional)) { + $optional = [ + 'pre_auth_code' => $optional, + ]; + } else { + $optional['pre_auth_code'] = data_get($this->createPreAuthorizationCode(), 'pre_auth_code'); + } + + $queries = \array_merge( + ['auth_type' => 3], + $optional, + [ + 'component_appid' => $this['config']['app_id'], + 'redirect_uri' => $callbackUrl, + 'action' => 'bindcomponent', + 'no_scan' => 1, + ] + ); + + return 'https://mp.weixin.qq.com/safe/bindcomponent?'.http_build_query($queries).'#wechat_redirect'; + } + + /** + * @param string $appId + * @param string|null $refreshToken + * + * @return array + */ + protected function getAuthorizerConfig(string $appId, string $refreshToken = null): array + { + return $this['config']->merge( + [ + 'component_app_id' => $this['config']['app_id'], + 'component_app_token' => $this['access_token']->getToken()['component_access_token'], + 'app_id' => $appId, + 'refresh_token' => $refreshToken, + ] + )->toArray(); + } + + /** + * @param \EasyWeChat\OpenPlatform\Authorizer\Auth\AccessToken|null $accessToken + * + * @return array + */ + protected function getReplaceServices(AccessToken $accessToken = null): array + { + $services = [ + 'access_token' => $accessToken ?: function ($app) { + return new AccessToken($app, $this); + }, + + 'server' => function ($app) { + return new Guard($app); + }, + ]; + + foreach (['cache', 'http_client', 'log', 'logger', 'request'] as $reuse) { + if (isset($this[$reuse])) { + $services[$reuse] = $this[$reuse]; + } + } + + return $services; + } + + /** + * Handle dynamic calls. + * + * @param string $method + * @param array $args + * + * @return mixed + */ + public function __call($method, $args) + { + return $this->base->$method(...$args); + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Auth/AccessToken.php b/vendor/overtrue/wechat/src/OpenPlatform/Auth/AccessToken.php new file mode 100644 index 0000000..c15d78d --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Auth/AccessToken.php @@ -0,0 +1,49 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Auth; + +use EasyWeChat\Kernel\AccessToken as BaseAccessToken; + +/** + * Class AccessToken. + * + * @author mingyoung + */ +class AccessToken extends BaseAccessToken +{ + /** + * @var string + */ + protected $requestMethod = 'POST'; + + /** + * @var string + */ + protected $tokenKey = 'component_access_token'; + + /** + * @var string + */ + protected $endpointToGetToken = 'cgi-bin/component/api_component_token'; + + /** + * @return array + */ + protected function getCredentials(): array + { + return [ + 'component_appid' => $this->app['config']['app_id'], + 'component_appsecret' => $this->app['config']['secret'], + 'component_verify_ticket' => $this->app['verify_ticket']->getTicket(), + ]; + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Auth/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Auth/ServiceProvider.php new file mode 100644 index 0000000..c60784b --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Auth/ServiceProvider.php @@ -0,0 +1,37 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Auth; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author mingyoung + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['verify_ticket'] = function ($app) { + return new VerifyTicket($app); + }; + + $app['access_token'] = function ($app) { + return new AccessToken($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Auth/VerifyTicket.php b/vendor/overtrue/wechat/src/OpenPlatform/Auth/VerifyTicket.php new file mode 100644 index 0000000..9ad04c7 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Auth/VerifyTicket.php @@ -0,0 +1,91 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Auth; + +use EasyWeChat\Kernel\Exceptions\RuntimeException; +use EasyWeChat\Kernel\Traits\InteractsWithCache; +use EasyWeChat\OpenPlatform\Application; + +/** + * Class VerifyTicket. + * + * @author mingyoung + */ +class VerifyTicket +{ + use InteractsWithCache; + + /** + * @var \EasyWeChat\OpenPlatform\Application + */ + protected $app; + + /** + * Constructor. + * + * @param \EasyWeChat\OpenPlatform\Application $app + */ + public function __construct(Application $app) + { + $this->app = $app; + } + + /** + * Put the credential `component_verify_ticket` in cache. + * + * @param string $ticket + * + * @return $this + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \Psr\SimpleCache\InvalidArgumentException + */ + public function setTicket(string $ticket) + { + $this->getCache()->set($this->getCacheKey(), $ticket, 3600); + + if (!$this->getCache()->has($this->getCacheKey())) { + throw new RuntimeException('Failed to cache verify ticket.'); + } + + return $this; + } + + /** + * Get the credential `component_verify_ticket` from cache. + * + * @return string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \Psr\SimpleCache\InvalidArgumentException + */ + public function getTicket(): string + { + if ($cached = $this->getCache()->get($this->getCacheKey())) { + return $cached; + } + + throw new RuntimeException('Credential "component_verify_ticket" does not exist in cache.'); + } + + /** + * Get cache key. + * + * @return string + */ + protected function getCacheKey(): string + { + return 'easywechat.open_platform.verify_ticket.'.$this->app['config']['app_id']; + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Aggregate/Account/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Aggregate/Account/Client.php new file mode 100644 index 0000000..b062e3a --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Aggregate/Account/Client.php @@ -0,0 +1,96 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\Aggregate\Account; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author Scholer + */ +class Client extends BaseClient +{ + /** + * 创建开放平台帐号并绑定公众号/小程序. + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function create() + { + $params = [ + 'appid' => $this->app['config']['app_id'], + ]; + + return $this->httpPostJson('cgi-bin/open/create', $params); + } + + /** + * 将公众号/小程序绑定到开放平台帐号下. + * + * @param string $openAppId 开放平台帐号appid + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function bindTo(string $openAppId) + { + $params = [ + 'appid' => $this->app['config']['app_id'], + 'open_appid' => $openAppId, + ]; + + return $this->httpPostJson('cgi-bin/open/bind', $params); + } + + /** + * 将公众号/小程序从开放平台帐号下解绑. + * + * @param string $openAppId 开放平台帐号appid + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function unbindFrom(string $openAppId) + { + $params = [ + 'appid' => $this->app['config']['app_id'], + 'open_appid' => $openAppId, + ]; + + return $this->httpPostJson('cgi-bin/open/unbind', $params); + } + + /** + * 获取公众号/小程序所绑定的开放平台帐号. + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getBinding() + { + $params = [ + 'appid' => $this->app['config']['app_id'], + ]; + + return $this->httpPostJson('cgi-bin/open/get', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Aggregate/AggregateServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Aggregate/AggregateServiceProvider.php new file mode 100644 index 0000000..d93293d --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Aggregate/AggregateServiceProvider.php @@ -0,0 +1,22 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\Aggregate; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class AggregateServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Auth/AccessToken.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Auth/AccessToken.php new file mode 100644 index 0000000..07b808c --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Auth/AccessToken.php @@ -0,0 +1,79 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\Auth; + +use EasyWeChat\Kernel\AccessToken as BaseAccessToken; +use EasyWeChat\OpenPlatform\Application; +use Pimple\Container; + +/** + * Class AccessToken. + * + * @author mingyoung + */ +class AccessToken extends BaseAccessToken +{ + /** + * @var string + */ + protected $requestMethod = 'POST'; + + /** + * @var string + */ + protected $queryName = 'access_token'; + + /** + * {@inheritdoc}. + */ + protected $tokenKey = 'authorizer_access_token'; + + /** + * @var \EasyWeChat\OpenPlatform\Application + */ + protected $component; + + /** + * AuthorizerAccessToken constructor. + * + * @param \Pimple\Container $app + * @param \EasyWeChat\OpenPlatform\Application $component + */ + public function __construct(Container $app, Application $component) + { + parent::__construct($app); + + $this->component = $component; + } + + /** + * {@inheritdoc}. + */ + protected function getCredentials(): array + { + return [ + 'component_appid' => $this->component['config']['app_id'], + 'authorizer_appid' => $this->app['config']['app_id'], + 'authorizer_refresh_token' => $this->app['config']['refresh_token'], + ]; + } + + /** + * @return string + */ + public function getEndpoint(): string + { + return 'cgi-bin/component/api_authorizer_token?'.http_build_query([ + 'component_access_token' => $this->component['access_token']->getToken()['component_access_token'], + ]); + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Account/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Account/Client.php new file mode 100644 index 0000000..f1b7afb --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Account/Client.php @@ -0,0 +1,79 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Account; + +use EasyWeChat\OpenPlatform\Authorizer\Aggregate\Account\Client as BaseClient; + +/** + * Class Client. + * + * @author ClouderSky + */ +class Client extends BaseClient +{ + /** + * 获取账号基本信息. + */ + public function getBasicInfo() + { + return $this->httpPostJson('cgi-bin/account/getaccountbasicinfo'); + } + + /** + * 修改头像. + * + * @param string $mediaId 头像素材mediaId + * @param string $left 剪裁框左上角x坐标(取值范围:[0, 1]) + * @param string $top 剪裁框左上角y坐标(取值范围:[0, 1]) + * @param string $right 剪裁框右下角x坐标(取值范围:[0, 1]) + * @param string $bottom 剪裁框右下角y坐标(取值范围:[0, 1]) + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function updateAvatar( + string $mediaId, + $left = '0.0', + $top = '0.0', + $right = '1.0', + $bottom = '1.0' + ) { + $params = [ + 'head_img_media_id' => $mediaId, + 'x1' => \strval($left), + 'y1' => \strval($top), + 'x2' => \strval($right), + 'y2' => \strval($bottom), + ]; + + return $this->httpPostJson('cgi-bin/account/modifyheadimage', $params); + } + + /** + * 修改功能介绍. + * + * @param string $signature 功能介绍(简介) + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function updateSignature(string $signature) + { + $params = ['signature' => $signature]; + + return $this->httpPostJson('cgi-bin/account/modifysignature', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Account/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Account/ServiceProvider.php new file mode 100644 index 0000000..f062954 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Account/ServiceProvider.php @@ -0,0 +1,25 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Account; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class ServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + $app['account'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Application.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Application.php new file mode 100644 index 0000000..081dbf7 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Application.php @@ -0,0 +1,56 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram; + +use EasyWeChat\MiniProgram\Application as MiniProgram; +use EasyWeChat\OpenPlatform\Authorizer\Aggregate\AggregateServiceProvider; + +/** + * Class Application. + * + * @author mingyoung + * + * @property \EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Account\Client $account + * @property \EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Code\Client $code + * @property \EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Domain\Client $domain + * @property \EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Setting\Client $setting + * @property \EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Tester\Client $tester + * @property \EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Material\Client $material + */ +class Application extends MiniProgram +{ + /** + * Application constructor. + * + * @param array $config + * @param array $prepends + */ + public function __construct(array $config = [], array $prepends = []) + { + parent::__construct($config, $prepends); + + $providers = [ + AggregateServiceProvider::class, + Code\ServiceProvider::class, + Domain\ServiceProvider::class, + Account\ServiceProvider::class, + Setting\ServiceProvider::class, + Tester\ServiceProvider::class, + Material\ServiceProvider::class, + Privacy\ServiceProvider::class, + ]; + + foreach ($providers as $provider) { + $this->register(new $provider()); + } + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Auth/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Auth/Client.php new file mode 100644 index 0000000..05bb704 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Auth/Client.php @@ -0,0 +1,64 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Auth; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\ServiceContainer; +use EasyWeChat\OpenPlatform\Application; + +/** + * Class Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + /** + * @var \EasyWeChat\OpenPlatform\Application + */ + protected $component; + + /** + * Client constructor. + * + * @param \EasyWeChat\Kernel\ServiceContainer $app + * @param \EasyWeChat\OpenPlatform\Application $component + */ + public function __construct(ServiceContainer $app, Application $component) + { + parent::__construct($app); + + $this->component = $component; + } + + /** + * Get session info by code. + * + * @param string $code + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function session(string $code) + { + $params = [ + 'appid' => $this->app['config']['app_id'], + 'js_code' => $code, + 'grant_type' => 'authorization_code', + 'component_appid' => $this->component['config']['app_id'], + 'component_access_token' => $this->component['access_token']->getToken()['component_access_token'], + ]; + + return $this->httpGet('sns/component/jscode2session', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Code/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Code/Client.php new file mode 100644 index 0000000..3d1b823 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Code/Client.php @@ -0,0 +1,271 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Code; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + /** + * @param int $templateId + * @param string $extJson + * @param string $version + * @param string $description + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function commit(int $templateId, string $extJson, string $version, string $description) + { + return $this->httpPostJson('wxa/commit', [ + 'template_id' => $templateId, + 'ext_json' => $extJson, + 'user_version' => $version, + 'user_desc' => $description, + ]); + } + + /** + * @param string|null $path + * + * @return \EasyWeChat\Kernel\Http\Response + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getQrCode(string $path = null) + { + return $this->requestRaw('wxa/get_qrcode', 'GET', [ + 'query' => ['path' => $path], + ]); + } + + /** + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function getCategory() + { + return $this->httpGet('wxa/get_category'); + } + + /** + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function getPage() + { + return $this->httpGet('wxa/get_page'); + } + + /** + * @param array $data + * @param string|null $feedbackInfo + * @param string|null $feedbackStuff + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function submitAudit(array $data, string $feedbackInfo = null, string $feedbackStuff = null) + { + if (isset($data['item_list'])) { + return $this->httpPostJson('wxa/submit_audit', $data); + } + + return $this->httpPostJson('wxa/submit_audit', [ + 'item_list' => $data, + 'feedback_info' => $feedbackInfo, + 'feedback_stuff' => $feedbackStuff, + ]); + } + + /** + * @param int $auditId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getAuditStatus(int $auditId) + { + return $this->httpPostJson('wxa/get_auditstatus', [ + 'auditid' => $auditId, + ]); + } + + /** + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function getLatestAuditStatus() + { + return $this->httpGet('wxa/get_latest_auditstatus'); + } + + /** + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function release() + { + return $this->httpPostJson('wxa/release'); + } + + /** + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function withdrawAudit() + { + return $this->httpGet('wxa/undocodeaudit'); + } + + /** + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function rollbackRelease() + { + return $this->httpGet('wxa/revertcoderelease'); + } + + /** + * @param string $action + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function changeVisitStatus(string $action) + { + return $this->httpPostJson('wxa/change_visitstatus', [ + 'action' => $action, + ]); + } + + /** + * 分阶段发布. + * + * @param int $grayPercentage + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function grayRelease(int $grayPercentage) + { + return $this->httpPostJson('wxa/grayrelease', [ + 'gray_percentage' => $grayPercentage, + ]); + } + + /** + * 取消分阶段发布. + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function revertGrayRelease() + { + return $this->httpGet('wxa/revertgrayrelease'); + } + + /** + * 查询当前分阶段发布详情. + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function getGrayRelease() + { + return $this->httpGet('wxa/getgrayreleaseplan'); + } + + /** + * 查询当前设置的最低基础库版本及各版本用户占比. + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getSupportVersion() + { + return $this->httpPostJson('cgi-bin/wxopen/getweappsupportversion'); + } + + /** + * 设置最低基础库版本. + * + * @param string $version + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function setSupportVersion(string $version) + { + return $this->httpPostJson('cgi-bin/wxopen/setweappsupportversion', [ + 'version' => $version, + ]); + } + + /** + * 查询服务商的当月提审限额(quota)和加急次数. + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function queryQuota() + { + return $this->httpGet('wxa/queryquota'); + } + + /** + * 加急审核申请. + * + * @param int $auditId 审核单ID + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function speedupAudit(int $auditId) + { + return $this->httpPostJson('wxa/speedupaudit', [ + 'auditid' => $auditId, + ]); + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Code/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Code/ServiceProvider.php new file mode 100644 index 0000000..bce8611 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Code/ServiceProvider.php @@ -0,0 +1,25 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Code; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class ServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + $app['code'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Domain/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Domain/Client.php new file mode 100644 index 0000000..2a6547c --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Domain/Client.php @@ -0,0 +1,54 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Domain; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + /** + * @param array $params + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function modify(array $params) + { + return $this->httpPostJson('wxa/modify_domain', $params); + } + + /** + * 设置小程序业务域名. + * + * @param array $domains + * @param string $action + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function setWebviewDomain(array $domains, $action = 'add') + { + return $this->httpPostJson('wxa/setwebviewdomain', [ + 'action' => $action, + 'webviewdomain' => $domains, + ]); + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Domain/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Domain/ServiceProvider.php new file mode 100644 index 0000000..4eaef13 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Domain/ServiceProvider.php @@ -0,0 +1,25 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Domain; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class ServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + $app['domain'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Material/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Material/Client.php new file mode 100644 index 0000000..a9686c9 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Material/Client.php @@ -0,0 +1,51 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Material; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Http\StreamResponse; + +/** + * Class Client. + * + * @author overtrue + */ +class Client extends BaseClient +{ + /** + * Allow media type. + * + * @var array + */ + protected $allowTypes = ['image', 'voice', 'video', 'thumb', 'news_image']; + + /** + * Fetch material. + * + * @param string $mediaId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get(string $mediaId) + { + $response = $this->requestRaw('cgi-bin/material/get_material', 'POST', ['json' => ['media_id' => $mediaId]]); + + if (false !== stripos($response->getHeaderLine('Content-disposition'), 'attachment')) { + return StreamResponse::buildFromPsrResponse($response); + } + + return $this->castResponseToType($response, $this->app['config']->get('response_type')); + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Material/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Material/ServiceProvider.php new file mode 100644 index 0000000..820a1ac --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Material/ServiceProvider.php @@ -0,0 +1,44 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * ServiceProvider.php. + * + * This file is part of the wechat. + * + * (c) overtrue + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Material; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['material'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Privacy/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Privacy/Client.php new file mode 100644 index 0000000..3b159d3 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Privacy/Client.php @@ -0,0 +1,66 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Privacy; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; + +/** + * Class Client. + * + * @author lujunyi + */ +class Client extends BaseClient +{ + /** + * 查询小程序用户隐私保护指引. + */ + public function get() + { + return $this->httpPostJson('cgi-bin/component/getprivacysetting', []); + } + + /** + * 配置小程序用户隐私保护指引 + * + * @param array $params + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function set(array $params) + { + return $this->httpPostJson('cgi-bin/component/setprivacysetting', $params); + } + + /** + * 上传小程序用户隐私保护指引 + * + * @param string $path + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\InvalidSignException + */ + public function upload(string $path) + { + if (!file_exists($path) || !is_readable($path)) { + throw new InvalidArgumentException(sprintf("File does not exist, or the file is unreadable: '%s'", $path)); + } + + return $this->httpUpload('cgi-bin/component/uploadprivacyextfile', ['file' => $path]); + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Privacy/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Privacy/ServiceProvider.php new file mode 100644 index 0000000..85ec390 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Privacy/ServiceProvider.php @@ -0,0 +1,25 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Privacy; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class ServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + $app['privacy'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Setting/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Setting/Client.php new file mode 100644 index 0000000..8d979b0 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Setting/Client.php @@ -0,0 +1,250 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Setting; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author ClouderSky + */ +class Client extends BaseClient +{ + /** + * 获取账号可以设置的所有类目. + */ + public function getAllCategories() + { + return $this->httpPostJson('cgi-bin/wxopen/getallcategories'); + } + + /** + * 添加类目. + * + * @param array $categories 类目数组 + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function addCategories(array $categories) + { + $params = ['categories' => $categories]; + + return $this->httpPostJson('cgi-bin/wxopen/addcategory', $params); + } + + /** + * 删除类目. + * + * @param int $firstId 一级类目ID + * @param int $secondId 二级类目ID + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function deleteCategories(int $firstId, int $secondId) + { + $params = ['first' => $firstId, 'second' => $secondId]; + + return $this->httpPostJson('cgi-bin/wxopen/deletecategory', $params); + } + + /** + * 获取账号已经设置的所有类目. + */ + public function getCategories() + { + return $this->httpPostJson('cgi-bin/wxopen/getcategory'); + } + + /** + * 修改类目. + * + * @param array $category 单个类目 + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function updateCategory(array $category) + { + return $this->httpPostJson('cgi-bin/wxopen/modifycategory', $category); + } + + /** + * 小程序名称设置及改名. + * + * @param string $nickname 昵称 + * @param string $idCardMediaId 身份证照片素材ID + * @param string $licenseMediaId 组织机构代码证或营业执照素材ID + * @param array $otherStuffs 其他证明材料素材ID + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function setNickname( + string $nickname, + string $idCardMediaId = '', + string $licenseMediaId = '', + array $otherStuffs = [] + ) { + $params = [ + 'nick_name' => $nickname, + 'id_card' => $idCardMediaId, + 'license' => $licenseMediaId, + ]; + + for ($i = \count($otherStuffs) - 1; $i >= 0; --$i) { + $params['naming_other_stuff_'.($i + 1)] = $otherStuffs[$i]; + } + + return $this->httpPostJson('wxa/setnickname', $params); + } + + /** + * 小程序改名审核状态查询. + * + * @param int $auditId 审核单id + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getNicknameAuditStatus($auditId) + { + $params = ['audit_id' => $auditId]; + + return $this->httpPostJson('wxa/api_wxa_querynickname', $params); + } + + /** + * 微信认证名称检测. + * + * @param string $nickname 名称(昵称) + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function isAvailableNickname($nickname) + { + $params = ['nick_name' => $nickname]; + + return $this->httpPostJson( + 'cgi-bin/wxverify/checkwxverifynickname', + $params + ); + } + + /** + * 查询小程序是否可被搜索. + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getSearchStatus() + { + return $this->httpGet('wxa/getwxasearchstatus'); + } + + /** + * 设置小程序可被搜素. + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function setSearchable() + { + return $this->httpPostJson('wxa/changewxasearchstatus', [ + 'status' => 0, + ]); + } + + /** + * 设置小程序不可被搜素. + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function setUnsearchable() + { + return $this->httpPostJson('wxa/changewxasearchstatus', [ + 'status' => 1, + ]); + } + + /** + * 获取展示的公众号信息. + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getDisplayedOfficialAccount() + { + return $this->httpGet('wxa/getshowwxaitem'); + } + + /** + * 设置展示的公众号. + * + * @param string|bool $appid + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function setDisplayedOfficialAccount($appid) + { + return $this->httpPostJson('wxa/updateshowwxaitem', [ + 'appid' => $appid ?: null, + 'wxa_subscribe_biz_flag' => $appid ? 1 : 0, + ]); + } + + /** + * 获取可以用来设置的公众号列表. + * + * @param int $page + * @param int $num + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getDisplayableOfficialAccounts(int $page, int $num) + { + return $this->httpGet('wxa/getwxamplinkforshow', [ + 'page' => $page, + 'num' => $num, + ]); + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Setting/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Setting/ServiceProvider.php new file mode 100644 index 0000000..917aec5 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Setting/ServiceProvider.php @@ -0,0 +1,25 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Setting; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class ServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + $app['setting'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Tester/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Tester/Client.php new file mode 100644 index 0000000..58c9854 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Tester/Client.php @@ -0,0 +1,80 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Tester; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author caikeal + */ +class Client extends BaseClient +{ + /** + * 绑定小程序体验者. + * + * @param string $wechatId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function bind(string $wechatId) + { + return $this->httpPostJson('wxa/bind_tester', [ + 'wechatid' => $wechatId, + ]); + } + + /** + * 解绑小程序体验者. + * + * @param string $wechatId + * @param string $userStr + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function unbind(string $wechatId = null, string $userStr = null) + { + return $this->httpPostJson('wxa/unbind_tester', [ + ($userStr ? 'userstr' : 'wechatid') => $userStr ?? $wechatId, + ]); + } + + public function unbindWithUserStr(string $userStr) + { + return $this->httpPostJson('wxa/unbind_tester', [ + 'userstr' => $userStr, + ]); + } + + /** + * 获取体验者列表. + * + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function list() + { + return $this->httpPostJson('wxa/memberauth', [ + 'action' => 'get_experiencer', + ]); + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Tester/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Tester/ServiceProvider.php new file mode 100644 index 0000000..ff1ffb3 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Tester/ServiceProvider.php @@ -0,0 +1,25 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Tester; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class ServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + $app['tester'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/Account/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/Account/Client.php new file mode 100644 index 0000000..9e7b38b --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/Account/Client.php @@ -0,0 +1,81 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\OfficialAccount\Account; + +use EasyWeChat\Kernel\ServiceContainer; +use EasyWeChat\OpenPlatform\Application; +use EasyWeChat\OpenPlatform\Authorizer\Aggregate\Account\Client as BaseClient; + +/** + * Class Client. + * + * @author Keal + */ +class Client extends BaseClient +{ + /** + * @var \EasyWeChat\OpenPlatform\Application + */ + protected $component; + + /** + * Client constructor. + * + * @param \EasyWeChat\Kernel\ServiceContainer $app + * @param \EasyWeChat\OpenPlatform\Application $component + */ + public function __construct(ServiceContainer $app, Application $component) + { + parent::__construct($app); + + $this->component = $component; + } + + /** + * 从第三方平台跳转至微信公众平台授权注册页面, 授权注册小程序. + * + * @param string $callbackUrl + * @param bool $copyWxVerify + * + * @return string + */ + public function getFastRegistrationUrl(string $callbackUrl, bool $copyWxVerify = true): string + { + $queries = [ + 'copy_wx_verify' => $copyWxVerify, + 'component_appid' => $this->component['config']['app_id'], + 'appid' => $this->app['config']['app_id'], + 'redirect_uri' => $callbackUrl, + ]; + + return 'https://mp.weixin.qq.com/cgi-bin/fastregisterauth?'.http_build_query($queries); + } + + /** + * 小程序快速注册. + * + * @param string $ticket + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function register(string $ticket) + { + $params = [ + 'ticket' => $ticket, + ]; + + return $this->httpPostJson('cgi-bin/account/fastregister', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/Application.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/Application.php new file mode 100644 index 0000000..08509d2 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/Application.php @@ -0,0 +1,46 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\OfficialAccount; + +use EasyWeChat\OfficialAccount\Application as OfficialAccount; +use EasyWeChat\OpenPlatform\Authorizer\Aggregate\AggregateServiceProvider; + +/** + * Class Application. + * + * @author mingyoung + * + * @property \EasyWeChat\OpenPlatform\Authorizer\OfficialAccount\Account\Client $account + * @property \EasyWeChat\OpenPlatform\Authorizer\OfficialAccount\MiniProgram\Client $mini_program + */ +class Application extends OfficialAccount +{ + /** + * Application constructor. + * + * @param array $config + * @param array $prepends + */ + public function __construct(array $config = [], array $prepends = []) + { + parent::__construct($config, $prepends); + + $providers = [ + AggregateServiceProvider::class, + MiniProgram\ServiceProvider::class, + ]; + + foreach ($providers as $provider) { + $this->register(new $provider()); + } + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/MiniProgram/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/MiniProgram/Client.php new file mode 100644 index 0000000..d32b51b --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/MiniProgram/Client.php @@ -0,0 +1,77 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\OfficialAccount\MiniProgram; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author Keal + */ +class Client extends BaseClient +{ + /** + * 获取公众号关联的小程序. + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function list() + { + return $this->httpPostJson('cgi-bin/wxopen/wxamplinkget'); + } + + /** + * 关联小程序. + * + * @param string $appId + * @param bool $notifyUsers + * @param bool $showProfile + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function link(string $appId, bool $notifyUsers = true, bool $showProfile = false) + { + $params = [ + 'appid' => $appId, + 'notify_users' => (string) $notifyUsers, + 'show_profile' => (string) $showProfile, + ]; + + return $this->httpPostJson('cgi-bin/wxopen/wxamplink', $params); + } + + /** + * 解除已关联的小程序. + * + * @param string $appId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function unlink(string $appId) + { + $params = [ + 'appid' => $appId, + ]; + + return $this->httpPostJson('cgi-bin/wxopen/wxampunlink', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/MiniProgram/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/MiniProgram/ServiceProvider.php new file mode 100644 index 0000000..31ce10b --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/MiniProgram/ServiceProvider.php @@ -0,0 +1,25 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\OfficialAccount\MiniProgram; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class ServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + $app['mini_program'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Server/Guard.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Server/Guard.php new file mode 100644 index 0000000..d7efbd3 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Server/Guard.php @@ -0,0 +1,32 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Authorizer\Server; + +use EasyWeChat\Kernel\ServerGuard; + +/** + * Class Guard. + * + * @author mingyoung + */ +class Guard extends ServerGuard +{ + /** + * Get token from OpenPlatform encryptor. + * + * @return string + */ + protected function getToken() + { + return $this->app['encryptor']->getToken(); + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Base/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Base/Client.php new file mode 100644 index 0000000..ec78a5f --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Base/Client.php @@ -0,0 +1,166 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Base; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + /** + * Get authorization info. + * + * @param string|null $authCode + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function handleAuthorize(string $authCode = null) + { + $params = [ + 'component_appid' => $this->app['config']['app_id'], + 'authorization_code' => $authCode ?? $this->app['request']->get('auth_code'), + ]; + + return $this->httpPostJson('cgi-bin/component/api_query_auth', $params); + } + + /** + * Get authorizer info. + * + * @param string $appId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getAuthorizer(string $appId) + { + $params = [ + 'component_appid' => $this->app['config']['app_id'], + 'authorizer_appid' => $appId, + ]; + + return $this->httpPostJson('cgi-bin/component/api_get_authorizer_info', $params); + } + + /** + * Get options. + * + * @param string $appId + * @param string $name + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getAuthorizerOption(string $appId, string $name) + { + $params = [ + 'component_appid' => $this->app['config']['app_id'], + 'authorizer_appid' => $appId, + 'option_name' => $name, + ]; + + return $this->httpPostJson('cgi-bin/component/api_get_authorizer_option', $params); + } + + /** + * Set authorizer option. + * + * @param string $appId + * @param string $name + * @param string $value + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function setAuthorizerOption(string $appId, string $name, string $value) + { + $params = [ + 'component_appid' => $this->app['config']['app_id'], + 'authorizer_appid' => $appId, + 'option_name' => $name, + 'option_value' => $value, + ]; + + return $this->httpPostJson('cgi-bin/component/api_set_authorizer_option', $params); + } + + /** + * Get authorizer list. + * + * @param int $offset + * @param int $count + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getAuthorizers($offset = 0, $count = 500) + { + $params = [ + 'component_appid' => $this->app['config']['app_id'], + 'offset' => $offset, + 'count' => $count, + ]; + + return $this->httpPostJson('cgi-bin/component/api_get_authorizer_list', $params); + } + + /** + * Create pre-authorization code. + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function createPreAuthorizationCode() + { + $params = [ + 'component_appid' => $this->app['config']['app_id'], + ]; + + return $this->httpPostJson('cgi-bin/component/api_create_preauthcode', $params); + } + + /** + * OpenPlatform Clear quota. + * + * @see https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419318587 + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function clearQuota() + { + $params = [ + 'component_appid' => $this->app['config']['app_id'], + ]; + + return $this->httpPostJson('cgi-bin/component/clear_quota', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Base/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Base/ServiceProvider.php new file mode 100644 index 0000000..e647c41 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Base/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Base; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author mingyoung + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['base'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/CodeTemplate/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/CodeTemplate/Client.php new file mode 100644 index 0000000..3441041 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/CodeTemplate/Client.php @@ -0,0 +1,94 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\CodeTemplate; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author caikeal + */ +class Client extends BaseClient +{ + /** + * 获取草稿箱内的所有临时代码草稿 + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getDrafts() + { + return $this->httpGet('wxa/gettemplatedraftlist'); + } + + /** + * 将草稿箱的草稿选为小程序代码模版. + * + * @param int $draftId + * @param int $templateType + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function createFromDraft(int $draftId, int $templateType = 0) + { + $params = [ + 'draft_id' => $draftId, + 'template_type' => $templateType, + ]; + + return $this->httpPostJson('wxa/addtotemplate', $params); + } + + /** + * 获取代码模版库中的所有小程序代码模版. + * + * @param int $templateType + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function list(int $templateType = null) + { + $params = [ + 'template_type' => $templateType, + ]; + + return $this->httpGet('wxa/gettemplatelist', $params); + } + + /** + * 删除指定小程序代码模版. + * + * @param string $templateId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete($templateId) + { + $params = [ + 'template_id' => $templateId, + ]; + + return $this->httpPostJson('wxa/deletetemplate', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/CodeTemplate/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/CodeTemplate/ServiceProvider.php new file mode 100644 index 0000000..ab543a7 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/CodeTemplate/ServiceProvider.php @@ -0,0 +1,25 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\CodeTemplate; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class ServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + $app['code_template'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Component/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Component/Client.php new file mode 100644 index 0000000..eafb3f5 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Component/Client.php @@ -0,0 +1,60 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Component; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author dudashuang + */ +class Client extends BaseClient +{ + /** + * 通过法人微信快速创建小程序. + * + * @param array $params + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function registerMiniProgram(array $params) + { + return $this->httpPostJson('cgi-bin/component/fastregisterweapp', $params, ['action' => 'create']); + } + + /** + * 查询创建任务状态. + * + * @param string $companyName + * @param string $legalPersonaWechat + * @param string $legalPersonaName + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getRegistrationStatus(string $companyName, string $legalPersonaWechat, string $legalPersonaName) + { + $params = [ + 'name' => $companyName, + 'legal_persona_wechat' => $legalPersonaWechat, + 'legal_persona_name' => $legalPersonaName, + ]; + + return $this->httpPostJson('cgi-bin/component/fastregisterweapp', $params, ['action' => 'search']); + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Component/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Component/ServiceProvider.php new file mode 100644 index 0000000..83e309f --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Component/ServiceProvider.php @@ -0,0 +1,25 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Component; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class ServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + $app['component'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Server/Guard.php b/vendor/overtrue/wechat/src/OpenPlatform/Server/Guard.php new file mode 100644 index 0000000..8d29d5e --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Server/Guard.php @@ -0,0 +1,65 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Server; + +use EasyWeChat\Kernel\ServerGuard; +use EasyWeChat\OpenPlatform\Server\Handlers\Authorized; +use EasyWeChat\OpenPlatform\Server\Handlers\Unauthorized; +use EasyWeChat\OpenPlatform\Server\Handlers\UpdateAuthorized; +use EasyWeChat\OpenPlatform\Server\Handlers\VerifyTicketRefreshed; +use Symfony\Component\HttpFoundation\Response; +use function EasyWeChat\Kernel\data_get; + +/** + * Class Guard. + * + * @author mingyoung + */ +class Guard extends ServerGuard +{ + public const EVENT_AUTHORIZED = 'authorized'; + public const EVENT_UNAUTHORIZED = 'unauthorized'; + public const EVENT_UPDATE_AUTHORIZED = 'updateauthorized'; + public const EVENT_COMPONENT_VERIFY_TICKET = 'component_verify_ticket'; + public const EVENT_THIRD_FAST_REGISTERED = 'notify_third_fasteregister'; + + /** + * @return \Symfony\Component\HttpFoundation\Response + * + * @throws \EasyWeChat\Kernel\Exceptions\BadRequestException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + protected function resolve(): Response + { + $this->registerHandlers(); + + $message = $this->getMessage(); + + if ($infoType = data_get($message, 'InfoType')) { + $this->dispatch($infoType, $message); + } + + return new Response(static::SUCCESS_EMPTY_RESPONSE); + } + + /** + * Register event handlers. + */ + protected function registerHandlers() + { + $this->on(self::EVENT_AUTHORIZED, Authorized::class); + $this->on(self::EVENT_UNAUTHORIZED, Unauthorized::class); + $this->on(self::EVENT_UPDATE_AUTHORIZED, UpdateAuthorized::class); + $this->on(self::EVENT_COMPONENT_VERIFY_TICKET, VerifyTicketRefreshed::class); + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/Authorized.php b/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/Authorized.php new file mode 100644 index 0000000..fe7f9c6 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/Authorized.php @@ -0,0 +1,30 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Server\Handlers; + +use EasyWeChat\Kernel\Contracts\EventHandlerInterface; + +/** + * Class Authorized. + * + * @author mingyoung + */ +class Authorized implements EventHandlerInterface +{ + /** + * {@inheritdoc}. + */ + public function handle($payload = null) + { + // Do nothing for the time being. + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/Unauthorized.php b/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/Unauthorized.php new file mode 100644 index 0000000..158228b --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/Unauthorized.php @@ -0,0 +1,30 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Server\Handlers; + +use EasyWeChat\Kernel\Contracts\EventHandlerInterface; + +/** + * Class Unauthorized. + * + * @author mingyoung + */ +class Unauthorized implements EventHandlerInterface +{ + /** + * {@inheritdoc}. + */ + public function handle($payload = null) + { + // Do nothing for the time being. + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/UpdateAuthorized.php b/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/UpdateAuthorized.php new file mode 100644 index 0000000..e73caa5 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/UpdateAuthorized.php @@ -0,0 +1,30 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Server\Handlers; + +use EasyWeChat\Kernel\Contracts\EventHandlerInterface; + +/** + * Class UpdateAuthorized. + * + * @author mingyoung + */ +class UpdateAuthorized implements EventHandlerInterface +{ + /** + * {@inheritdoc} + */ + public function handle($payload = null) + { + // Do nothing for the time being. + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/VerifyTicketRefreshed.php b/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/VerifyTicketRefreshed.php new file mode 100644 index 0000000..f268a85 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/VerifyTicketRefreshed.php @@ -0,0 +1,55 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Server\Handlers; + +use EasyWeChat\Kernel\Contracts\EventHandlerInterface; +use EasyWeChat\Kernel\Traits\ResponseCastable; +use EasyWeChat\OpenPlatform\Application; + +use function EasyWeChat\Kernel\data_get; + +/** + * Class VerifyTicketRefreshed. + * + * @author mingyoung + */ +class VerifyTicketRefreshed implements EventHandlerInterface +{ + use ResponseCastable; + + /** + * @var \EasyWeChat\OpenPlatform\Application + */ + protected $app; + + /** + * Constructor. + * + * @param \EasyWeChat\OpenPlatform\Application $app + */ + public function __construct(Application $app) + { + $this->app = $app; + } + + /** + * {@inheritdoc}. + */ + public function handle($payload = null) + { + $ticket = data_get($payload, 'ComponentVerifyTicket'); + + if (!empty($ticket)) { + $this->app['verify_ticket']->setTicket($ticket); + } + } +} diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Server/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Server/ServiceProvider.php new file mode 100644 index 0000000..7747061 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenPlatform/Server/ServiceProvider.php @@ -0,0 +1,34 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenPlatform\Server; + +use EasyWeChat\Kernel\Encryptor; +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class ServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + $app['encryptor'] = function ($app) { + return new Encryptor( + $app['config']['app_id'], + $app['config']['token'], + $app['config']['aes_key'] + ); + }; + + $app['server'] = function ($app) { + return new Guard($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OpenWork/Application.php b/vendor/overtrue/wechat/src/OpenWork/Application.php new file mode 100644 index 0000000..bde1421 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenWork/Application.php @@ -0,0 +1,85 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenWork; + +use EasyWeChat\Kernel\ServiceContainer; +use EasyWeChat\OpenWork\Work\Application as Work; + +/** + * Application. + * + * @author xiaomin + * + * @property \EasyWeChat\OpenWork\Server\Guard $server + * @property \EasyWeChat\OpenWork\Corp\Client $corp + * @property \EasyWeChat\OpenWork\Provider\Client $provider + * @property \EasyWeChat\OpenWork\SuiteAuth\AccessToken $suite_access_token + * @property \EasyWeChat\OpenWork\Auth\AccessToken $provider_access_token + * @property \EasyWeChat\OpenWork\SuiteAuth\SuiteTicket $suite_ticket + * @property \EasyWeChat\OpenWork\MiniProgram\Auth\Client $mini_program + */ +class Application extends ServiceContainer +{ + /** + * @var array + */ + protected $providers = [ + Auth\ServiceProvider::class, + SuiteAuth\ServiceProvider::class, + Server\ServiceProvider::class, + Corp\ServiceProvider::class, + Provider\ServiceProvider::class, + MiniProgram\ServiceProvider::class, + ]; + + /** + * @var array + */ + protected $defaultConfig = [ + // http://docs.guzzlephp.org/en/stable/request-options.html + 'http' => [ + 'base_uri' => 'https://qyapi.weixin.qq.com/', + ], + ]; + + /** + * Creates the miniProgram application. + * + * @return \EasyWeChat\Work\MiniProgram\Application + */ + public function miniProgram(): \EasyWeChat\Work\MiniProgram\Application + { + return new \EasyWeChat\Work\MiniProgram\Application($this->getConfig()); + } + + /** + * @param string $authCorpId 企业 corp_id + * @param string $permanentCode 企业永久授权码 + * + * @return Work + */ + public function work(string $authCorpId, string $permanentCode): Work + { + return new Work($authCorpId, $permanentCode, $this); + } + + /** + * @param string $method + * @param array $arguments + * + * @return mixed + */ + public function __call($method, $arguments) + { + return $this['base']->$method(...$arguments); + } +} diff --git a/vendor/overtrue/wechat/src/OpenWork/Auth/AccessToken.php b/vendor/overtrue/wechat/src/OpenWork/Auth/AccessToken.php new file mode 100644 index 0000000..3f00a25 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenWork/Auth/AccessToken.php @@ -0,0 +1,52 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenWork\Auth; + +use EasyWeChat\Kernel\AccessToken as BaseAccessToken; + +/** + * AccessToken. + * + * @author xiaomin + */ +class AccessToken extends BaseAccessToken +{ + protected $requestMethod = 'POST'; + + /** + * @var string + */ + protected $endpointToGetToken = 'cgi-bin/service/get_provider_token'; + + /** + * @var string + */ + protected $tokenKey = 'provider_access_token'; + + /** + * @var string + */ + protected $cachePrefix = 'easywechat.kernel.provider_access_token.'; + + /** + * Credential for get token. + * + * @return array + */ + protected function getCredentials(): array + { + return [ + 'corpid' => $this->app['config']['corp_id'], //服务商的corpid + 'provider_secret' => $this->app['config']['secret'], + ]; + } +} diff --git a/vendor/overtrue/wechat/src/OpenWork/Auth/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenWork/Auth/ServiceProvider.php new file mode 100644 index 0000000..78aba05 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenWork/Auth/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenWork\Auth; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * ServiceProvider. + * + * @author xiaomin + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + isset($app['provider_access_token']) || $app['provider_access_token'] = function ($app) { + return new AccessToken($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OpenWork/Corp/Client.php b/vendor/overtrue/wechat/src/OpenWork/Corp/Client.php new file mode 100644 index 0000000..83040b8 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenWork/Corp/Client.php @@ -0,0 +1,217 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenWork\Corp; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\ServiceContainer; + +/** + * Client. + * + * @author xiaomin + */ +class Client extends BaseClient +{ + /** + * Client constructor. + * 三方接口有三个access_token,这里用的是suite_access_token. + * + * @param \EasyWeChat\Kernel\ServiceContainer $app + */ + public function __construct(ServiceContainer $app) + { + parent::__construct($app, $app['suite_access_token']); + } + + /** + * 企业微信安装应用授权 url. + * + * @param string $preAuthCode 预授权码 + * @param string $redirectUri 回调地址 + * @param string $state + * + * @return string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function getPreAuthorizationUrl(string $preAuthCode = '', string $redirectUri = '', string $state = '') + { + $redirectUri || $redirectUri = $this->app->config['redirect_uri_install']; + $preAuthCode || $preAuthCode = $this->getPreAuthCode()['pre_auth_code']; + $state || $state = random_bytes(64); + + $params = [ + 'suite_id' => $this->app['config']['suite_id'], + 'redirect_uri' => $redirectUri, + 'pre_auth_code' => $preAuthCode, + 'state' => $state, + ]; + + return 'https://open.work.weixin.qq.com/3rdapp/install?'.http_build_query($params); + } + + /** + * 获取预授权码. + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function getPreAuthCode() + { + return $this->httpGet('cgi-bin/service/get_pre_auth_code'); + } + + /** + * 设置授权配置. + * 该接口可对某次授权进行配置. + * + * @param string $preAuthCode + * @param array $sessionInfo + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function setSession(string $preAuthCode, array $sessionInfo) + { + $params = [ + 'pre_auth_code' => $preAuthCode, + 'session_info' => $sessionInfo, + ]; + + return $this->httpPostJson('cgi-bin/service/set_session_info', $params); + } + + /** + * 获取企业永久授权码. + * + * @param string $authCode 临时授权码,会在授权成功时附加在redirect_uri中跳转回第三方服务商网站,或通过回调推送给服务商。长度为64至512个字节 + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getPermanentByCode(string $authCode) + { + $params = [ + 'auth_code' => $authCode, + ]; + + return $this->httpPostJson('cgi-bin/service/get_permanent_code', $params); + } + + /** + * 获取企业授权信息. + * + * @param string $authCorpId + * @param string $permanentCode + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getAuthorization(string $authCorpId, string $permanentCode) + { + $params = [ + 'auth_corpid' => $authCorpId, + 'permanent_code' => $permanentCode, + ]; + + return $this->httpPostJson('cgi-bin/service/get_auth_info', $params); + } + + /** + * 获取应用的管理员列表. + * + * @param string $authCorpId + * @param string $agentId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getManagers(string $authCorpId, string $agentId) + { + $params = [ + 'auth_corpid' => $authCorpId, + 'agentid' => $agentId, + ]; + + return $this->httpPostJson('cgi-bin/service/get_admin_list', $params); + } + + /** + * 获取登录url. + * + * @param string $redirectUri + * @param string $scope + * @param string|null $state + * + * @return string + */ + public function getOAuthRedirectUrl(string $redirectUri = '', string $scope = 'snsapi_userinfo', string $state = null) + { + $redirectUri || $redirectUri = $this->app->config['redirect_uri_oauth']; + $state || $state = random_bytes(64); + $params = [ + 'appid' => $this->app['config']['suite_id'], + 'redirect_uri' => $redirectUri, + 'response_type' => 'code', + 'scope' => $scope, + 'state' => $state, + ]; + + return 'https://open.weixin.qq.com/connect/oauth2/authorize?'.http_build_query($params).'#wechat_redirect'; + } + + /** + * 第三方根据code获取企业成员信息. + * + * @param string $code + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function getUserByCode(string $code) + { + $params = [ + 'code' => $code, + ]; + + return $this->httpGet('cgi-bin/service/getuserinfo3rd', $params); + } + + /** + * 第三方使用user_ticket获取成员详情. + * + * @param string $userTicket + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getUserByTicket(string $userTicket) + { + $params = [ + 'user_ticket' => $userTicket, + ]; + + return $this->httpPostJson('cgi-bin/service/getuserdetail3rd', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OpenWork/Corp/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenWork/Corp/ServiceProvider.php new file mode 100644 index 0000000..0cc8ea9 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenWork/Corp/ServiceProvider.php @@ -0,0 +1,38 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenWork\Corp; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * ServiceProvider. + * + * @author xiaomin + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * Registers services on the given container. + * + * This method should only be used to configure services and parameters. + * It should not get services. + * + * @param \Pimple\Container $app + */ + public function register(Container $app) + { + isset($app['corp']) || $app['corp'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OpenWork/MiniProgram/Client.php b/vendor/overtrue/wechat/src/OpenWork/MiniProgram/Client.php new file mode 100644 index 0000000..29dae19 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenWork/MiniProgram/Client.php @@ -0,0 +1,50 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenWork\MiniProgram; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\ServiceContainer; + +/** + * Class Client. + */ +class Client extends BaseClient +{ + /** + * Client constructor. + * + * @param \EasyWeChat\Kernel\ServiceContainer $app + */ + public function __construct(ServiceContainer $app) + { + parent::__construct($app, $app['suite_access_token']); + } + + /** + * Get session info by code. + * + * @param string $code + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function session(string $code) + { + $params = [ + 'js_code' => $code, + 'grant_type' => 'authorization_code', + ]; + + return $this->httpGet('cgi-bin/service/miniprogram/jscode2session', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OpenWork/MiniProgram/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenWork/MiniProgram/ServiceProvider.php new file mode 100644 index 0000000..8e7dba2 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenWork/MiniProgram/ServiceProvider.php @@ -0,0 +1,31 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenWork\MiniProgram; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * ServiceProvider. + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + !isset($app['mini_program']) && $app['mini_program'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OpenWork/Provider/Client.php b/vendor/overtrue/wechat/src/OpenWork/Provider/Client.php new file mode 100644 index 0000000..c63b589 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenWork/Provider/Client.php @@ -0,0 +1,242 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenWork\Provider; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\ServiceContainer; + +/** + * Client. + * + * @author xiaomin + */ +class Client extends BaseClient +{ + /** + * Client constructor. + * + * + * @param ServiceContainer $app + */ + public function __construct(ServiceContainer $app) + { + parent::__construct($app, $app['provider_access_token']); + } + + /** + * 单点登录 - 获取登录的地址. + * + * @param string $redirectUri + * @param string $userType + * @param string $state + * + * @return string + */ + public function getLoginUrl(string $redirectUri = '', string $userType = 'admin', string $state = '') + { + $redirectUri || $redirectUri = $this->app->config['redirect_uri_single']; + $state || $state = random_bytes(64); + $params = [ + 'appid' => $this->app['config']['corp_id'], + 'redirect_uri' => $redirectUri, + 'usertype' => $userType, + 'state' => $state, + ]; + + return 'https://open.work.weixin.qq.com/wwopen/sso/3rd_qrConnect?'.http_build_query($params); + } + + /** + * 单点登录 - 获取登录用户信息. + * + * @param string $authCode + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getLoginInfo(string $authCode) + { + $params = [ + 'auth_code' => $authCode, + ]; + + return $this->httpPostJson('cgi-bin/service/get_login_info', $params); + } + + /** + * 获取注册定制化URL. + * + * @param string $registerCode + * + * @return string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + public function getRegisterUri(string $registerCode = '') + { + if (!$registerCode) { + /** @var array $response */ + $response = $this->detectAndCastResponseToType($this->getRegisterCode(), 'array'); + + $registerCode = $response['register_code']; + } + + $params = ['register_code' => $registerCode]; + + return 'https://open.work.weixin.qq.com/3rdservice/wework/register?'.http_build_query($params); + } + + /** + * 获取注册码. + * + * @param string $corpName + * @param string $adminName + * @param string $adminMobile + * @param string $state + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getRegisterCode( + string $corpName = '', + string $adminName = '', + string $adminMobile = '', + string $state = '' + ) { + $params = []; + $params['template_id'] = $this->app['config']['reg_template_id']; + !empty($corpName) && $params['corp_name'] = $corpName; + !empty($adminName) && $params['admin_name'] = $adminName; + !empty($adminMobile) && $params['admin_mobile'] = $adminMobile; + !empty($state) && $params['state'] = $state; + + return $this->httpPostJson('cgi-bin/service/get_register_code', $params); + } + + /** + * 查询注册状态. + * + * Desc:该API用于查询企业注册状态,企业注册成功返回注册信息. + * + * @param string $registerCode + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getRegisterInfo(string $registerCode) + { + $params = [ + 'register_code' => $registerCode, + ]; + + return $this->httpPostJson('cgi-bin/service/get_register_info', $params); + } + + /** + * 设置授权应用可见范围. + * + * Desc:调用该接口前提是开启通讯录迁移,收到授权成功通知后可调用。 + * 企业注册初始化安装应用后,应用默认可见范围为根部门。 + * 如需修改应用可见范围,服务商可以调用该接口设置授权应用的可见范围。 + * 该接口只能使用注册完成回调事件或者查询注册状态返回的access_token。 + * 调用设置通讯录同步完成后或者access_token超过30分钟失效(即解除通讯录锁定状态)则不能继续调用该接口。 + * + * @param string $accessToken + * @param string $agentId + * @param array $allowUser + * @param array $allowParty + * @param array $allowTag + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function setAgentScope( + string $accessToken, + string $agentId, + array $allowUser = [], + array $allowParty = [], + array $allowTag = [] + ) { + $params = [ + 'agentid' => $agentId, + 'allow_user' => $allowUser, + 'allow_party' => $allowParty, + 'allow_tag' => $allowTag, + 'access_token' => $accessToken, + ]; + + return $this->httpGet('cgi-bin/agent/set_scope', $params); + } + + /** + * 设置通讯录同步完成. + * + * Desc:该API用于设置通讯录同步完成,解除通讯录锁定状态,同时使通讯录迁移access_token失效。 + * + * @param string $accessToken + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function contactSyncSuccess(string $accessToken) + { + $params = ['access_token' => $accessToken]; + + return $this->httpGet('cgi-bin/sync/contact_sync_success', $params); + } + + /** + * 通讯录单个搜索 + * + * @param string $corpId + * @param string $queryWord + * @param int|string $agentId + * @param int $offset + * @param int $limit + * @param int $queryType + * @param null $fullMatchField + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function searchContact( + string $corpId, + string $queryWord, + $agentId, + int $offset = 0, + int $limit = 50, + int $queryType = 0, + $fullMatchField = null + ) { + $params = []; + $params['auth_corpid'] = $corpId; + $params['query_word'] = $queryWord; + $params['query_type'] = $queryType; + $params['agentid'] = $agentId; + $params['offset'] = $offset; + $params['limit'] = $limit; + !empty($fullMatchField) && $params['full_match_field'] = $fullMatchField; + + return $this->httpPostJson('cgi-bin/service/contact/search', $params); + } +} diff --git a/vendor/overtrue/wechat/src/OpenWork/Provider/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenWork/Provider/ServiceProvider.php new file mode 100644 index 0000000..2185fec --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenWork/Provider/ServiceProvider.php @@ -0,0 +1,36 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenWork\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * ServiceProvider. + * + * @author xiaomin + */ +class ServiceProvider implements ServiceProviderInterface +{ + protected $app; + + /** + * @param Container $app + */ + public function register(Container $app) + { + $this->app = $app; + isset($app['provider']) || $app['provider'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OpenWork/Server/Guard.php b/vendor/overtrue/wechat/src/OpenWork/Server/Guard.php new file mode 100644 index 0000000..5700a77 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenWork/Server/Guard.php @@ -0,0 +1,68 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenWork\Server; + +use EasyWeChat\Kernel\Encryptor; +use EasyWeChat\Kernel\ServerGuard; + +/** + * Guard. + * + * @author xiaomin + */ +class Guard extends ServerGuard +{ + /** + * @var bool + */ + protected $alwaysValidate = true; + + /** + * @return $this + */ + public function validate() + { + return $this; + } + + /** + * @return bool + */ + protected function shouldReturnRawResponse(): bool + { + return !is_null($this->app['request']->get('echostr')); + } + + protected function isSafeMode(): bool + { + return true; + } + + /** + * @param array $message + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + */ + protected function decryptMessage(array $message) + { + $encryptor = new Encryptor($message['ToUserName'], $this->app['config']->get('token'), $this->app['config']->get('aes_key')); + + return $message = $encryptor->decrypt( + $message['Encrypt'], + $this->app['request']->get('msg_signature'), + $this->app['request']->get('nonce'), + $this->app['request']->get('timestamp') + ); + } +} diff --git a/vendor/overtrue/wechat/src/OpenWork/Server/Handlers/EchoStrHandler.php b/vendor/overtrue/wechat/src/OpenWork/Server/Handlers/EchoStrHandler.php new file mode 100644 index 0000000..7843cc8 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenWork/Server/Handlers/EchoStrHandler.php @@ -0,0 +1,66 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenWork\Server\Handlers; + +use EasyWeChat\Kernel\Contracts\EventHandlerInterface; +use EasyWeChat\Kernel\Decorators\FinallyResult; +use EasyWeChat\Kernel\ServiceContainer; + +/** + * EchoStrHandler. + * + * @author xiaomin + */ +class EchoStrHandler implements EventHandlerInterface +{ + /** + * @var ServiceContainer + */ + protected $app; + + /** + * EchoStrHandler constructor. + * + * @param ServiceContainer $app + */ + public function __construct(ServiceContainer $app) + { + $this->app = $app; + } + + /** + * @param mixed $payload + * + * @return FinallyResult|null + */ + public function handle($payload = null) + { + if ($decrypted = $this->app['request']->get('echostr')) { + $str = $this->app['encryptor_corp']->decrypt( + $decrypted, + $this->app['request']->get('msg_signature'), + $this->app['request']->get('nonce'), + $this->app['request']->get('timestamp') + ); + + return new FinallyResult($str); + } + //把SuiteTicket缓存起来 + if (!empty($payload['SuiteTicket'])) { + $this->app['suite_ticket']->setTicket($payload['SuiteTicket']); + + return new FinallyResult("success"); + } + + return null; + } +} diff --git a/vendor/overtrue/wechat/src/OpenWork/Server/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenWork/Server/ServiceProvider.php new file mode 100644 index 0000000..d33d5c8 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenWork/Server/ServiceProvider.php @@ -0,0 +1,56 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenWork\Server; + +use EasyWeChat\Kernel\Encryptor; +use EasyWeChat\OpenWork\Server\Handlers\EchoStrHandler; +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * ServiceProvider. + * + * @author xiaomin + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + //微信第三方在校验url是使用的是GET方式请求和corp_id进行加密 + !isset($app['encryptor_corp']) && $app['encryptor_corp'] = function ($app) { + return new Encryptor( + $app['config']['corp_id'], + $app['config']['token'], + $app['config']['aes_key'] + ); + }; + + //微信第三方推送数据时使用的是suite_id进行加密 + !isset($app['encryptor']) && $app['encryptor'] = function ($app) { + return new Encryptor( + $app['config']['suite_id'], + $app['config']['token'], + $app['config']['aes_key'] + ); + }; + + !isset($app['server']) && $app['server'] = function ($app) { + $guard = new Guard($app); + $guard->push(new EchoStrHandler($app)); + + return $guard; + }; + } +} diff --git a/vendor/overtrue/wechat/src/OpenWork/SuiteAuth/AccessToken.php b/vendor/overtrue/wechat/src/OpenWork/SuiteAuth/AccessToken.php new file mode 100644 index 0000000..e4e248d --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenWork/SuiteAuth/AccessToken.php @@ -0,0 +1,56 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenWork\SuiteAuth; + +use EasyWeChat\Kernel\AccessToken as BaseAccessToken; + +/** + * AccessToken. + * + * @author xiaomin + */ +class AccessToken extends BaseAccessToken +{ + /** + * @var string + */ + protected $requestMethod = 'POST'; + + /** + * @var string + */ + protected $endpointToGetToken = 'cgi-bin/service/get_suite_token'; + + /** + * @var string + */ + protected $tokenKey = 'suite_access_token'; + + /** + * @var string + */ + protected $cachePrefix = 'easywechat.kernel.suite_access_token.'; + + /** + * Credential for get token. + * + * @return array + */ + protected function getCredentials(): array + { + return [ + 'suite_id' => $this->app['config']['suite_id'], + 'suite_secret' => $this->app['config']['suite_secret'], + 'suite_ticket' => $this->app['suite_ticket']->getTicket(), + ]; + } +} diff --git a/vendor/overtrue/wechat/src/OpenWork/SuiteAuth/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenWork/SuiteAuth/ServiceProvider.php new file mode 100644 index 0000000..a4f5386 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenWork/SuiteAuth/ServiceProvider.php @@ -0,0 +1,37 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenWork\SuiteAuth; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * ServiceProvider. + * + * @author xiaomin + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['suite_ticket'] = function ($app) { + return new SuiteTicket($app); + }; + + isset($app['suite_access_token']) || $app['suite_access_token'] = function ($app) { + return new AccessToken($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/OpenWork/SuiteAuth/SuiteTicket.php b/vendor/overtrue/wechat/src/OpenWork/SuiteAuth/SuiteTicket.php new file mode 100644 index 0000000..4c01710 --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenWork/SuiteAuth/SuiteTicket.php @@ -0,0 +1,85 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenWork\SuiteAuth; + +use EasyWeChat\Kernel\Exceptions\RuntimeException; +use EasyWeChat\Kernel\Traits\InteractsWithCache; +use EasyWeChat\OpenWork\Application; + +/** + * SuiteTicket. + * + * @author xiaomin + */ +class SuiteTicket +{ + use InteractsWithCache; + + /** + * @var Application + */ + protected $app; + + /** + * SuiteTicket constructor. + * + * @param Application $app + */ + public function __construct(Application $app) + { + $this->app = $app; + } + + /** + * @param string $ticket + * + * @return $this + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \Psr\SimpleCache\InvalidArgumentException + */ + public function setTicket(string $ticket) + { + $this->getCache()->set($this->getCacheKey(), $ticket, 1800); + + if (!$this->getCache()->has($this->getCacheKey())) { + throw new RuntimeException('Failed to cache suite ticket.'); + } + + return $this; + } + + /** + * @return string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \Psr\SimpleCache\InvalidArgumentException + */ + public function getTicket(): string + { + if ($cached = $this->getCache()->get($this->getCacheKey())) { + return $cached; + } + + throw new RuntimeException('Credential "suite_ticket" does not exist in cache.'); + } + + /** + * @return string + */ + protected function getCacheKey(): string + { + return 'easywechat.open_work.suite_ticket.'.$this->app['config']['suite_id']; + } +} diff --git a/vendor/overtrue/wechat/src/OpenWork/Work/Application.php b/vendor/overtrue/wechat/src/OpenWork/Work/Application.php new file mode 100644 index 0000000..c2f611a --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenWork/Work/Application.php @@ -0,0 +1,41 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenWork\Work; + +use EasyWeChat\OpenWork\Application as OpenWork; +use EasyWeChat\OpenWork\Work\Auth\AccessToken; +use EasyWeChat\Work\Application as Work; + +/** + * Application. + * + * @author xiaomin + */ +class Application extends Work +{ + /** + * Application constructor. + * + * @param string $authCorpId + * @param string $permanentCode + * @param OpenWork $component + * @param array $prepends + */ + public function __construct(string $authCorpId, string $permanentCode, OpenWork $component, array $prepends = []) + { + parent::__construct(\array_merge($component->getConfig(), ['corp_id' => $authCorpId]), $prepends + [ + 'access_token' => function ($app) use ($authCorpId, $permanentCode, $component) { + return new AccessToken($app, $authCorpId, $permanentCode, $component); + }, + ]); + } +} diff --git a/vendor/overtrue/wechat/src/OpenWork/Work/Auth/AccessToken.php b/vendor/overtrue/wechat/src/OpenWork/Work/Auth/AccessToken.php new file mode 100644 index 0000000..e80758a --- /dev/null +++ b/vendor/overtrue/wechat/src/OpenWork/Work/Auth/AccessToken.php @@ -0,0 +1,80 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\OpenWork\Work\Auth; + +use EasyWeChat\Kernel\AccessToken as BaseAccessToken; +use EasyWeChat\OpenWork\Application; +use Pimple\Container; + +/** + * AccessToken. + * + * @author xiaomin + */ +class AccessToken extends BaseAccessToken +{ + /** + * @var string + */ + protected $requestMethod = 'POST'; + + /** + * @var string 授权方企业ID + */ + protected $authCorpid; + + /** + * @var string 授权方企业永久授权码,通过get_permanent_code获取 + */ + protected $permanentCode; + + protected $component; + + /** + * AccessToken constructor. + * + * @param Container $app + * @param string $authCorpId + * @param string $permanentCode + * @param Application $component + */ + public function __construct(Container $app, string $authCorpId, string $permanentCode, Application $component) + { + $this->authCorpid = $authCorpId; + $this->permanentCode = $permanentCode; + $this->component = $component; + parent::__construct($app); + } + + /** + * Credential for get token. + * + * @return array + */ + protected function getCredentials(): array + { + return [ + 'auth_corpid' => $this->authCorpid, + 'permanent_code' => $this->permanentCode, + ]; + } + + /** + * @return string + */ + public function getEndpoint(): string + { + return 'cgi-bin/service/get_corp_token?'.http_build_query([ + 'suite_access_token' => $this->component['suite_access_token']->getToken()['suite_access_token'], + ]); + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Application.php b/vendor/overtrue/wechat/src/Payment/Application.php new file mode 100644 index 0000000..ce820ac --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Application.php @@ -0,0 +1,210 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment; + +use Closure; +use EasyWeChat\BasicService; +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; +use EasyWeChat\Kernel\ServiceContainer; +use EasyWeChat\Kernel\Support; +use EasyWeChat\OfficialAccount; + +/** + * Class Application. + * + * @property \EasyWeChat\Payment\Bill\Client $bill + * @property \EasyWeChat\Payment\Fundflow\Client $fundflow + * @property \EasyWeChat\Payment\Jssdk\Client $jssdk + * @property \EasyWeChat\Payment\Order\Client $order + * @property \EasyWeChat\Payment\Refund\Client $refund + * @property \EasyWeChat\Payment\Coupon\Client $coupon + * @property \EasyWeChat\Payment\Reverse\Client $reverse + * @property \EasyWeChat\Payment\Redpack\Client $redpack + * @property \EasyWeChat\BasicService\Url\Client $url + * @property \EasyWeChat\Payment\Transfer\Client $transfer + * @property \EasyWeChat\Payment\Security\Client $security + * @property \EasyWeChat\Payment\ProfitSharing\Client $profit_sharing + * @property \EasyWeChat\Payment\Contract\Client $contract + * @property \EasyWeChat\OfficialAccount\Auth\AccessToken $access_token + * + * @method mixed pay(array $attributes) + * @method mixed authCodeToOpenid(string $authCode) + */ +class Application extends ServiceContainer +{ + /** + * @var array + */ + protected $providers = [ + OfficialAccount\Auth\ServiceProvider::class, + BasicService\Url\ServiceProvider::class, + Base\ServiceProvider::class, + Bill\ServiceProvider::class, + Fundflow\ServiceProvider::class, + Coupon\ServiceProvider::class, + Jssdk\ServiceProvider::class, + Merchant\ServiceProvider::class, + Order\ServiceProvider::class, + Redpack\ServiceProvider::class, + Refund\ServiceProvider::class, + Reverse\ServiceProvider::class, + Sandbox\ServiceProvider::class, + Transfer\ServiceProvider::class, + Security\ServiceProvider::class, + ProfitSharing\ServiceProvider::class, + Contract\ServiceProvider::class, + ]; + + /** + * @var array + */ + protected $defaultConfig = [ + 'http' => [ + 'base_uri' => 'https://api.mch.weixin.qq.com/', + ], + ]; + + /** + * Build payment scheme for product. + * + * @param string $productId + * + * @return string + */ + public function scheme(string $productId): string + { + $params = [ + 'appid' => $this['config']->app_id, + 'mch_id' => $this['config']->mch_id, + 'time_stamp' => time(), + 'nonce_str' => uniqid(), + 'product_id' => $productId, + ]; + + $params['sign'] = Support\generate_sign($params, $this['config']->key); + + return 'weixin://wxpay/bizpayurl?'.http_build_query($params); + } + + /** + * @param string $codeUrl + * + * @return string + */ + public function codeUrlScheme(string $codeUrl) + { + return \sprintf('weixin://wxpay/bizpayurl?sr=%s', $codeUrl); + } + + /** + * @param \Closure $closure + * + * @return \Symfony\Component\HttpFoundation\Response + * + * @codeCoverageIgnore + * + * @throws \EasyWeChat\Kernel\Exceptions\Exception + */ + public function handlePaidNotify(Closure $closure) + { + return (new Notify\Paid($this))->handle($closure); + } + + /** + * @param \Closure $closure + * + * @return \Symfony\Component\HttpFoundation\Response + * + * @codeCoverageIgnore + * + * @throws \EasyWeChat\Kernel\Exceptions\Exception + */ + public function handleRefundedNotify(Closure $closure) + { + return (new Notify\Refunded($this))->handle($closure); + } + + /** + * @param \Closure $closure + * + * @return \Symfony\Component\HttpFoundation\Response + * + * @codeCoverageIgnore + * + * @throws \EasyWeChat\Kernel\Exceptions\Exception + */ + public function handleScannedNotify(Closure $closure) + { + return (new Notify\Scanned($this))->handle($closure); + } + + /** + * Set sub-merchant. + * + * @param string $mchId + * @param string|null $appId + * + * @return $this + */ + public function setSubMerchant(string $mchId, string $appId = null) + { + $this['config']->set('sub_mch_id', $mchId); + $this['config']->set('sub_appid', $appId); + + return $this; + } + + /** + * @return bool + */ + public function inSandbox(): bool + { + return (bool) $this['config']->get('sandbox'); + } + + /** + * @param string|null $endpoint + * + * @return string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + public function getKey(string $endpoint = null) + { + if ('sandboxnew/pay/getsignkey' === $endpoint) { + return $this['config']->key; + } + + $key = $this->inSandbox() ? $this['sandbox']->getKey() : $this['config']->key; + + if (empty($key)) { + throw new InvalidArgumentException('config key should not empty.'); + } + + if (32 !== strlen($key)) { + throw new InvalidArgumentException(sprintf("'%s' should be 32 chars length.", $key)); + } + + return $key; + } + + /** + * @param string $name + * @param array $arguments + * + * @return mixed + */ + public function __call($name, $arguments) + { + return call_user_func_array([$this['base'], $name], $arguments); + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Base/Client.php b/vendor/overtrue/wechat/src/Payment/Base/Client.php new file mode 100644 index 0000000..75d4c9a --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Base/Client.php @@ -0,0 +1,54 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Base; + +use EasyWeChat\Payment\Kernel\BaseClient; + +class Client extends BaseClient +{ + /** + * Pay the order. + * + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function pay(array $params) + { + $params['appid'] = $this->app['config']->app_id; + + return $this->request($this->wrap('pay/micropay'), $params); + } + + /** + * Get openid by auth code. + * + * @param string $authCode + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function authCodeToOpenid(string $authCode) + { + return $this->request('tools/authcodetoopenid', [ + 'appid' => $this->app['config']->app_id, + 'auth_code' => $authCode, + ]); + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Base/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Base/ServiceProvider.php new file mode 100644 index 0000000..71aebd9 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Base/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Base; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['base'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Bill/Client.php b/vendor/overtrue/wechat/src/Payment/Bill/Client.php new file mode 100644 index 0000000..20a9b63 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Bill/Client.php @@ -0,0 +1,48 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Bill; + +use EasyWeChat\Kernel\Http\StreamResponse; +use EasyWeChat\Payment\Kernel\BaseClient; + +class Client extends BaseClient +{ + /** + * Download bill history as a table file. + * + * @param string $date + * @param string $type + * @param array $optional + * + * @return \EasyWeChat\Kernel\Http\StreamResponse|\Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get(string $date, string $type = 'ALL', array $optional = []) + { + $params = [ + 'appid' => $this->app['config']->app_id, + 'bill_date' => $date, + 'bill_type' => $type, + ] + $optional; + + $response = $this->requestRaw($this->wrap('pay/downloadbill'), $params); + + if (0 === strpos($response->getBody()->getContents(), '')) { + return $this->castResponseToType($response, $this->app['config']->get('response_type')); + } + + return StreamResponse::buildFromPsrResponse($response); + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Bill/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Bill/ServiceProvider.php new file mode 100644 index 0000000..3fd98d4 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Bill/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Bill; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['bill'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Contract/Client.php b/vendor/overtrue/wechat/src/Payment/Contract/Client.php new file mode 100644 index 0000000..22acebc --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Contract/Client.php @@ -0,0 +1,112 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Contract; + +use EasyWeChat\Payment\Kernel\BaseClient; + +/** + * Class Client. + * + * @author tianyong90 <412039588@qq.com> + */ +class Client extends BaseClient +{ + /** + * entrust official account. + * + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function web(array $params) + { + $params['appid'] = $this->app['config']->app_id; + + return $this->safeRequest('papay/entrustweb', $params); + } + + /** + * entrust app. + * + * @param array $params + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function app(array $params) + { + $params['appid'] = $this->app['config']->app_id; + + return $this->safeRequest('papay/preentrustweb', $params); + } + + /** + * entrust html 5. + * + * @param array $params + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function h5(array $params) + { + $params['appid'] = $this->app['config']->app_id; + + return $this->safeRequest('papay/h5entrustweb', $params); + } + + /** + * apply papay. + * + * @param array $params + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function apply(array $params) + { + $params['appid'] = $this->app['config']->app_id; + + return $this->safeRequest('pay/pappayapply', $params); + } + + /** + * delete papay contrace. + * + * @param array $params + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete(array $params) + { + $params['appid'] = $this->app['config']->app_id; + + return $this->safeRequest('papay/deletecontract', $params); + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Contract/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Contract/ServiceProvider.php new file mode 100644 index 0000000..945ea68 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Contract/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Contract; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['contract'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Coupon/Client.php b/vendor/overtrue/wechat/src/Payment/Coupon/Client.php new file mode 100644 index 0000000..101a42b --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Coupon/Client.php @@ -0,0 +1,77 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Coupon; + +use EasyWeChat\Payment\Kernel\BaseClient; + +/** + * Class Client. + * + * @author tianyong90 <412039588@qq.com> + */ +class Client extends BaseClient +{ + /** + * send a cash coupon. + * + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function send(array $params) + { + $params['appid'] = $this->app['config']->app_id; + $params['openid_count'] = 1; + + return $this->safeRequest('mmpaymkttransfers/send_coupon', $params); + } + + /** + * query a coupon stock. + * + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function stock(array $params) + { + $params['appid'] = $this->app['config']->app_id; + + return $this->request('mmpaymkttransfers/query_coupon_stock', $params); + } + + /** + * query a info of coupon. + * + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function info(array $params) + { + $params['appid'] = $this->app['config']->app_id; + + return $this->request('mmpaymkttransfers/querycouponsinfo', $params); + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Coupon/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Coupon/ServiceProvider.php new file mode 100644 index 0000000..513734b --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Coupon/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Coupon; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['coupon'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Fundflow/Client.php b/vendor/overtrue/wechat/src/Payment/Fundflow/Client.php new file mode 100644 index 0000000..359d4f3 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Fundflow/Client.php @@ -0,0 +1,56 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Fundflow; + +use EasyWeChat\Kernel\Http\StreamResponse; +use EasyWeChat\Payment\Kernel\BaseClient; + +class Client extends BaseClient +{ + /** + * Download fundflow history as a table file. + * + * @param string $date + * @param string $type + * @param array $options + * + * @return array|\EasyWeChat\Kernel\Http\Response|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get(string $date, string $type = 'Basic', $options = []) + { + $params = [ + 'appid' => $this->app['config']->app_id, + 'bill_date' => $date, + 'account_type' => $type, + 'sign_type' => 'HMAC-SHA256', + 'nonce_str' => uniqid('micro'), + ]; + $options = array_merge( + [ + 'cert' => $this->app['config']->get('cert_path'), + 'ssl_key' => $this->app['config']->get('key_path'), + ], + $options + ); + $response = $this->requestRaw('pay/downloadfundflow', $params, 'post', $options); + + if (0 === strpos($response->getBody()->getContents(), '')) { + return $this->castResponseToType($response, $this->app['config']->get('response_type')); + } + + return StreamResponse::buildFromPsrResponse($response); + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Fundflow/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Fundflow/ServiceProvider.php new file mode 100644 index 0000000..9901124 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Fundflow/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Fundflow; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['fundflow'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Jssdk/Client.php b/vendor/overtrue/wechat/src/Payment/Jssdk/Client.php new file mode 100644 index 0000000..0780134 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Jssdk/Client.php @@ -0,0 +1,175 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Jssdk; + +use EasyWeChat\BasicService\Jssdk\Client as JssdkClient; +use EasyWeChat\Kernel\Support; + +/** + * Class Client. + * + * @author overtrue + */ +class Client extends JssdkClient +{ + /** + * [WeixinJSBridge] Generate js config for payment. + * + *
        +     * WeixinJSBridge.invoke(
        +     *  'getBrandWCPayRequest',
        +     *  ...
        +     * );
        +     * 
        + * + * @param string $prepayId + * @param bool $json + * + * @return string|array + */ + public function bridgeConfig(string $prepayId, bool $json = true) + { + $params = [ + 'appId' => $this->app['config']->sub_appid ?: $this->app['config']->app_id, + 'timeStamp' => strval(time()), + 'nonceStr' => uniqid(), + 'package' => "prepay_id=$prepayId", + 'signType' => 'MD5', + ]; + + $params['paySign'] = Support\generate_sign($params, $this->app['config']->key, 'md5'); + + return $json ? json_encode($params) : $params; + } + + /** + * [JSSDK] Generate js config for payment. + * + *
        +     * wx.chooseWXPay({...});
        +     * 
        + * + * @param string $prepayId + * + * @return array + */ + public function sdkConfig(string $prepayId): array + { + $config = $this->bridgeConfig($prepayId, false); + + $config['timestamp'] = $config['timeStamp']; + unset($config['timeStamp']); + + return $config; + } + + /** + * Generate app payment parameters. + * + * @param string $prepayId + * + * @return array + */ + public function appConfig(string $prepayId): array + { + $params = [ + 'appid' => $this->app['config']->app_id, + 'partnerid' => $this->app['config']->mch_id, + 'prepayid' => $prepayId, + 'noncestr' => uniqid(), + 'timestamp' => time(), + 'package' => 'Sign=WXPay', + ]; + + $params['sign'] = Support\generate_sign($params, $this->app['config']->key); + + return $params; + } + + /** + * Generate js config for share user address. + * + * @param string $accessToken + * @param bool $json + * + * @return string|array + */ + public function shareAddressConfig(string $accessToken, bool $json = true) + { + $params = [ + 'appId' => $this->app['config']->app_id, + 'scope' => 'jsapi_address', + 'timeStamp' => strval(time()), + 'nonceStr' => uniqid(), + 'signType' => 'SHA1', + ]; + + $signParams = [ + 'appid' => $params['appId'], + 'url' => $this->getUrl(), + 'timestamp' => $params['timeStamp'], + 'noncestr' => $params['nonceStr'], + 'accesstoken' => strval($accessToken), + ]; + + ksort($signParams); + + $params['addrSign'] = sha1(urldecode(http_build_query($signParams))); + + return $json ? json_encode($params) : $params; + } + + /** + * Generate js config for contract of mini program. + * + * @param array $params + * + * @return array + */ + public function contractConfig(array $params): array + { + $params['appid'] = $this->app['config']->app_id; + $params['timestamp'] = time(); + + $params['sign'] = Support\generate_sign($params, $this->app['config']->key); + + return $params; + } + + /** + * Generate js config for biz red packet of mini program. + * + * @param string $package + * @return array + */ + public function miniprogramRedpackConfig(string $package): array + { + $param = [ + 'appId' => $this->app['config']->app_id, + 'timeStamp' => '' . time(), + 'nonceStr' => uniqid(), + 'package' => urlencode($package), + ]; + ksort($param); + + $buff = ''; + foreach ($param as $k => $v) { + $buff .= $k . "=" . $v . "&"; + } + + $param['paySign'] = md5($buff . 'key=' . $this->app['config']->key); + $param['signType'] = 'MD5'; + unset($param['appId']); + + return $param; + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Jssdk/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Jssdk/ServiceProvider.php new file mode 100644 index 0000000..24f2a76 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Jssdk/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Jssdk; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['jssdk'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Kernel/BaseClient.php b/vendor/overtrue/wechat/src/Payment/Kernel/BaseClient.php new file mode 100644 index 0000000..74f3939 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Kernel/BaseClient.php @@ -0,0 +1,190 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Kernel; + +use EasyWeChat\Kernel\Support; +use EasyWeChat\Kernel\Traits\HasHttpRequests; +use EasyWeChat\Payment\Application; +use GuzzleHttp\MessageFormatter; +use GuzzleHttp\Middleware; +use Psr\Http\Message\ResponseInterface; + +/** + * Class BaseClient. + * + * @author overtrue + */ +class BaseClient +{ + use HasHttpRequests { request as performRequest; } + + /** + * @var \EasyWeChat\Payment\Application + */ + protected $app; + + /** + * Constructor. + * + * @param \EasyWeChat\Payment\Application $app + */ + public function __construct(Application $app) + { + $this->app = $app; + + $this->setHttpClient($this->app['http_client']); + } + + /** + * Extra request params. + * + * @return array + */ + protected function prepends() + { + return []; + } + + /** + * Make a API request. + * + * @param string $endpoint + * @param array $params + * @param string $method + * @param array $options + * @param bool $returnResponse + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function request(string $endpoint, array $params = [], $method = 'post', array $options = [], $returnResponse = false) + { + $base = [ + 'mch_id' => $this->app['config']['mch_id'], + 'nonce_str' => uniqid(), + 'sub_mch_id' => $this->app['config']['sub_mch_id'], + 'sub_appid' => $this->app['config']['sub_appid'], + ]; + + $params = array_filter(array_merge($base, $this->prepends(), $params), 'strlen'); + + $secretKey = $this->app->getKey($endpoint); + + $encryptMethod = Support\get_encrypt_method(Support\Arr::get($params, 'sign_type', 'MD5'), $secretKey); + + $params['sign'] = Support\generate_sign($params, $secretKey, $encryptMethod); + + $options = array_merge([ + 'body' => Support\XML::build($params), + ], $options); + + $this->pushMiddleware($this->logMiddleware(), 'log'); + + $response = $this->performRequest($endpoint, $method, $options); + + return $returnResponse ? $response : $this->castResponseToType($response, $this->app->config->get('response_type')); + } + + /** + * Log the request. + * + * @return \Closure + */ + protected function logMiddleware() + { + $formatter = new MessageFormatter($this->app['config']['http.log_template'] ?? MessageFormatter::DEBUG); + + return Middleware::log($this->app['logger'], $formatter); + } + + /** + * Make a request and return raw response. + * + * @param string $endpoint + * @param array $params + * @param string $method + * @param array $options + * + * @return ResponseInterface + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function requestRaw(string $endpoint, array $params = [], $method = 'post', array $options = []) + { + /** @var ResponseInterface $response */ + $response = $this->request($endpoint, $params, $method, $options, true); + + return $response; + } + + /** + * Make a request and return an array. + * + * @param string $endpoint + * @param array $params + * @param string $method + * @param array $options + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function requestArray(string $endpoint, array $params = [], $method = 'post', array $options = []): array + { + $response = $this->requestRaw($endpoint, $params, $method, $options); + + return $this->castResponseToType($response, 'array'); + } + + /** + * Request with SSL. + * + * @param string $endpoint + * @param array $params + * @param string $method + * @param array $options + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function safeRequest($endpoint, array $params, $method = 'post', array $options = []) + { + $options = array_merge([ + 'cert' => $this->app['config']->get('cert_path'), + 'ssl_key' => $this->app['config']->get('key_path'), + ], $options); + + return $this->request($endpoint, $params, $method, $options); + } + + /** + * Wrapping an API endpoint. + * + * @param string $endpoint + * + * @return string + */ + protected function wrap(string $endpoint): string + { + return $this->app->inSandbox() ? "sandboxnew/{$endpoint}" : $endpoint; + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Kernel/Exceptions/InvalidSignException.php b/vendor/overtrue/wechat/src/Payment/Kernel/Exceptions/InvalidSignException.php new file mode 100644 index 0000000..cdd25ba --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Kernel/Exceptions/InvalidSignException.php @@ -0,0 +1,18 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Kernel\Exceptions; + +use EasyWeChat\Kernel\Exceptions\Exception; + +class InvalidSignException extends Exception +{ +} diff --git a/vendor/overtrue/wechat/src/Payment/Kernel/Exceptions/SandboxException.php b/vendor/overtrue/wechat/src/Payment/Kernel/Exceptions/SandboxException.php new file mode 100644 index 0000000..01f9dd5 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Kernel/Exceptions/SandboxException.php @@ -0,0 +1,18 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Kernel\Exceptions; + +use EasyWeChat\Kernel\Exceptions\Exception; + +class SandboxException extends Exception +{ +} diff --git a/vendor/overtrue/wechat/src/Payment/Merchant/Client.php b/vendor/overtrue/wechat/src/Payment/Merchant/Client.php new file mode 100644 index 0000000..f31a7e2 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Merchant/Client.php @@ -0,0 +1,94 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Merchant; + +use EasyWeChat\Payment\Kernel\BaseClient; + +/** + * Class Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + /** + * Add sub-merchant. + * + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function addSubMerchant(array $params) + { + return $this->manage($params, ['action' => 'add']); + } + + /** + * Query sub-merchant by merchant id. + * + * @param string $id + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function querySubMerchantByMerchantId(string $id) + { + $params = [ + 'micro_mch_id' => $id, + ]; + + return $this->manage($params, ['action' => 'query']); + } + + /** + * Query sub-merchant by wechat id. + * + * @param string $id + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function querySubMerchantByWeChatId(string $id) + { + $params = [ + 'recipient_wechatid' => $id, + ]; + + return $this->manage($params, ['action' => 'query']); + } + + /** + * @param array $params + * @param array $query + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function manage(array $params, array $query) + { + $params = array_merge($params, [ + 'appid' => $this->app['config']->app_id, + 'nonce_str' => '', + 'sub_mch_id' => '', + 'sub_appid' => '', + ]); + + return $this->safeRequest('secapi/mch/submchmanage', $params, 'post', compact('query')); + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Merchant/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Merchant/ServiceProvider.php new file mode 100644 index 0000000..5d05c95 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Merchant/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Merchant; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author mingyoung + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['merchant'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Notify/Handler.php b/vendor/overtrue/wechat/src/Payment/Notify/Handler.php new file mode 100644 index 0000000..96a9956 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Notify/Handler.php @@ -0,0 +1,205 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Notify; + +use Closure; +use EasyWeChat\Kernel\Exceptions\Exception; +use EasyWeChat\Kernel\Support; +use EasyWeChat\Kernel\Support\XML; +use EasyWeChat\Payment\Kernel\Exceptions\InvalidSignException; +use Symfony\Component\HttpFoundation\Response; + +abstract class Handler +{ + public const SUCCESS = 'SUCCESS'; + public const FAIL = 'FAIL'; + + /** + * @var \EasyWeChat\Payment\Application + */ + protected $app; + + /** + * @var array + */ + protected $message; + + /** + * @var string|null + */ + protected $fail; + + /** + * @var array + */ + protected $attributes = []; + + /** + * Check sign. + * If failed, throws an exception. + * + * @var bool + */ + protected $check = true; + + /** + * Respond with sign. + * + * @var bool + */ + protected $sign = false; + + /** + * @param \EasyWeChat\Payment\Application $app + */ + public function __construct($app) + { + $this->app = $app; + } + + /** + * Handle incoming notify. + * + * @param \Closure $closure + * + * @return \Symfony\Component\HttpFoundation\Response + */ + abstract public function handle(Closure $closure); + + /** + * @param string $message + */ + public function fail(string $message) + { + $this->fail = $message; + } + + /** + * @param array $attributes + * @param bool $sign + * + * @return $this + */ + public function respondWith(array $attributes, bool $sign = false) + { + $this->attributes = $attributes; + $this->sign = $sign; + + return $this; + } + + /** + * Build xml and return the response to WeChat. + * + * @return \Symfony\Component\HttpFoundation\Response + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + public function toResponse(): Response + { + $base = [ + 'return_code' => is_null($this->fail) ? static::SUCCESS : static::FAIL, + 'return_msg' => $this->fail, + ]; + + $attributes = array_merge($base, $this->attributes); + + if ($this->sign) { + $attributes['sign'] = Support\generate_sign($attributes, $this->app->getKey()); + } + + return new Response(XML::build($attributes)); + } + + /** + * Return the notify message from request. + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\Exception + */ + public function getMessage(): array + { + if (!empty($this->message)) { + return $this->message; + } + + try { + $message = XML::parse(strval($this->app['request']->getContent())); + } catch (\Throwable $e) { + throw new Exception('Invalid request XML: '.$e->getMessage(), 400); + } + + if (!is_array($message) || empty($message)) { + throw new Exception('Invalid request XML.', 400); + } + + if ($this->check) { + $this->validate($message); + } + + return $this->message = $message; + } + + /** + * Decrypt message. + * + * @param string $key + * + * @return string|null + * + * @throws \EasyWeChat\Kernel\Exceptions\Exception + */ + public function decryptMessage(string $key) + { + $message = $this->getMessage(); + if (empty($message[$key])) { + return null; + } + + return Support\AES::decrypt( + base64_decode($message[$key], true), + md5($this->app['config']->key), + '', + OPENSSL_RAW_DATA, + 'AES-256-ECB' + ); + } + + /** + * Validate the request params. + * + * @param array $message + * + * @throws \EasyWeChat\Payment\Kernel\Exceptions\InvalidSignException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + protected function validate(array $message) + { + $sign = $message['sign']; + unset($message['sign']); + + if (Support\generate_sign($message, $this->app->getKey()) !== $sign) { + throw new InvalidSignException(); + } + } + + /** + * @param mixed $result + */ + protected function strict($result) + { + if (true !== $result && is_null($this->fail)) { + $this->fail(strval($result)); + } + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Notify/Paid.php b/vendor/overtrue/wechat/src/Payment/Notify/Paid.php new file mode 100644 index 0000000..c54b9cd --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Notify/Paid.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Notify; + +use Closure; + +class Paid extends Handler +{ + /** + * @param \Closure $closure + * + * @return \Symfony\Component\HttpFoundation\Response + * + * @throws \EasyWeChat\Kernel\Exceptions\Exception + */ + public function handle(Closure $closure) + { + $this->strict( + \call_user_func($closure, $this->getMessage(), [$this, 'fail']) + ); + + return $this->toResponse(); + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Notify/Refunded.php b/vendor/overtrue/wechat/src/Payment/Notify/Refunded.php new file mode 100644 index 0000000..8241892 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Notify/Refunded.php @@ -0,0 +1,48 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Notify; + +use Closure; +use EasyWeChat\Kernel\Support\XML; + +class Refunded extends Handler +{ + protected $check = false; + + /** + * @param \Closure $closure + * + * @return \Symfony\Component\HttpFoundation\Response + * + * @throws \EasyWeChat\Kernel\Exceptions\Exception + */ + public function handle(Closure $closure) + { + $this->strict( + \call_user_func($closure, $this->getMessage(), $this->reqInfo(), [$this, 'fail']) + ); + + return $this->toResponse(); + } + + /** + * Decrypt the `req_info` from request message. + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\Exception + */ + public function reqInfo() + { + return XML::parse($this->decryptMessage('req_info')); + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Notify/Scanned.php b/vendor/overtrue/wechat/src/Payment/Notify/Scanned.php new file mode 100644 index 0000000..023a905 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Notify/Scanned.php @@ -0,0 +1,60 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Notify; + +use Closure; + +class Scanned extends Handler +{ + protected $check = false; + + /** + * @var string|null + */ + protected $alert; + + /** + * @param string $message + */ + public function alert(string $message) + { + $this->alert = $message; + } + + /** + * @param \Closure $closure + * + * @return \Symfony\Component\HttpFoundation\Response + * + * @throws \EasyWeChat\Kernel\Exceptions\Exception + */ + public function handle(Closure $closure) + { + $result = \call_user_func($closure, $this->getMessage(), [$this, 'fail'], [$this, 'alert']); + + $attributes = [ + 'result_code' => is_null($this->alert) && is_null($this->fail) ? static::SUCCESS : static::FAIL, + 'err_code_des' => $this->alert, + ]; + + if (is_null($this->alert) && is_string($result)) { + $attributes += [ + 'appid' => $this->app['config']->app_id, + 'mch_id' => $this->app['config']->mch_id, + 'nonce_str' => uniqid(), + 'prepay_id' => $result, + ]; + } + + return $this->respondWith($attributes, true)->toResponse(); + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Order/Client.php b/vendor/overtrue/wechat/src/Payment/Order/Client.php new file mode 100644 index 0000000..ff2ca2b --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Order/Client.php @@ -0,0 +1,126 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Order; + +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; +use EasyWeChat\Kernel\Exceptions\InvalidConfigException; +use EasyWeChat\Kernel\Support; +use EasyWeChat\Kernel\Support\Collection; +use EasyWeChat\Payment\Kernel\BaseClient; +use Psr\Http\Message\ResponseInterface; + +class Client extends BaseClient +{ + /** + * Unify order. + * + * @param array $params + * @param bool $isContract + * + * @return ResponseInterface|Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function unify(array $params, $isContract = false) + { + if (empty($params['spbill_create_ip'])) { + $params['spbill_create_ip'] = ('NATIVE' === $params['trade_type']) ? Support\get_server_ip() : Support\get_client_ip(); + } + + $params['appid'] = $this->app['config']->app_id; + $params['notify_url'] = $params['notify_url'] ?? $this->app['config']['notify_url']; + + if ($isContract) { + $params['contract_appid'] = $this->app['config']['app_id']; + $params['contract_mchid'] = $this->app['config']['mch_id']; + $params['request_serial'] = $params['request_serial'] ?? time(); + $params['contract_notify_url'] = $params['contract_notify_url'] ?? $this->app['config']['contract_notify_url']; + + return $this->request($this->wrap('pay/contractorder'), $params); + } + + return $this->request($this->wrap('pay/unifiedorder'), $params); + } + + /** + * Query order by out trade number. + * + * @param string $number + * + * @return ResponseInterface|Collection|array|object|string + * + * @throws InvalidArgumentException + * @throws InvalidConfigException + */ + public function queryByOutTradeNumber(string $number) + { + return $this->query([ + 'out_trade_no' => $number, + ]); + } + + /** + * Query order by transaction id. + * + * @param string $transactionId + * + * @return ResponseInterface|Collection|array|object|string + * + * @throws InvalidArgumentException + * @throws InvalidConfigException + */ + public function queryByTransactionId(string $transactionId) + { + return $this->query([ + 'transaction_id' => $transactionId, + ]); + } + + /** + * @param array $params + * + * @return ResponseInterface|Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function query(array $params) + { + $params['appid'] = $this->app['config']->app_id; + + return $this->request($this->wrap('pay/orderquery'), $params); + } + + /** + * Close order by out_trade_no. + * + * @param string $tradeNo + * + * @return ResponseInterface|Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function close(string $tradeNo) + { + $params = [ + 'appid' => $this->app['config']->app_id, + 'out_trade_no' => $tradeNo, + ]; + + return $this->request($this->wrap('pay/closeorder'), $params); + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Order/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Order/ServiceProvider.php new file mode 100644 index 0000000..9e781c0 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Order/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Order; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author mingyoung + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['order'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Payment/ProfitSharing/Client.php b/vendor/overtrue/wechat/src/Payment/ProfitSharing/Client.php new file mode 100644 index 0000000..c051a3d --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/ProfitSharing/Client.php @@ -0,0 +1,251 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\ProfitSharing; + +use EasyWeChat\Payment\Kernel\BaseClient; + +/** + * Class Client. + * + * @author ClouderSky + */ +class Client extends BaseClient +{ + /** + * {@inheritdoc}. + */ + protected function prepends() + { + return [ + 'sign_type' => 'HMAC-SHA256', + ]; + } + + /** + * Add profit sharing receiver. + * 服务商代子商户发起添加分账接收方请求. + * 后续可通过发起分账请求将结算后的钱分到该分账接收方. + * + * @param array $receiver 分账接收方对象,json格式 + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function addReceiver(array $receiver) + { + $params = [ + 'appid' => $this->app['config']->app_id, + 'receiver' => json_encode( + $receiver, + JSON_UNESCAPED_UNICODE + ), + ]; + + return $this->request( + 'pay/profitsharingaddreceiver', + $params + ); + } + + /** + * Delete profit sharing receiver. + * 服务商代子商户发起删除分账接收方请求. + * 删除后不支持将结算后的钱分到该分账接收方. + * + * @param array $receiver 分账接收方对象,json格式 + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function deleteReceiver(array $receiver) + { + $params = [ + 'appid' => $this->app['config']->app_id, + 'receiver' => json_encode( + $receiver, + JSON_UNESCAPED_UNICODE + ), + ]; + + return $this->request( + 'pay/profitsharingremovereceiver', + $params + ); + } + + /** + * Single profit sharing. + * 请求单次分账. + * + * @param string $transactionId 微信支付订单号 + * @param string $outOrderNo 商户系统内部的分账单号 + * @param array $receivers 分账接收方列表 + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function share( + string $transactionId, + string $outOrderNo, + array $receivers + ) { + $params = [ + 'appid' => $this->app['config']->app_id, + 'transaction_id' => $transactionId, + 'out_order_no' => $outOrderNo, + 'receivers' => json_encode( + $receivers, + JSON_UNESCAPED_UNICODE + ), + ]; + + return $this->safeRequest( + 'secapi/pay/profitsharing', + $params + ); + } + + /** + * Multi profit sharing. + * 请求多次分账. + * + * @param string $transactionId 微信支付订单号 + * @param string $outOrderNo 商户系统内部的分账单号 + * @param array $receivers 分账接收方列表 + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function multiShare( + string $transactionId, + string $outOrderNo, + array $receivers + ) { + $params = [ + 'appid' => $this->app['config']->app_id, + 'transaction_id' => $transactionId, + 'out_order_no' => $outOrderNo, + 'receivers' => json_encode( + $receivers, + JSON_UNESCAPED_UNICODE + ), + ]; + + return $this->safeRequest( + 'secapi/pay/multiprofitsharing', + $params + ); + } + + /** + * Finish profit sharing. + * 完结分账. + * + * @param array $params + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function markOrderAsFinished(array $params) + { + $params['appid'] = $this->app['config']->app_id; + $params['sub_appid'] = null; + + return $this->safeRequest( + 'secapi/pay/profitsharingfinish', + $params + ); + } + + /** + * Query profit sharing result. + * 查询分账结果. + * + * @param string $transactionId 微信支付订单号 + * @param string $outOrderNo 商户系统内部的分账单号 + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function query( + string $transactionId, + string $outOrderNo + ) { + $params = [ + 'sub_appid' => null, + 'transaction_id' => $transactionId, + 'out_order_no' => $outOrderNo, + ]; + + return $this->request( + 'pay/profitsharingquery', + $params + ); + } + + /** + * Profit sharing return. + * 分账回退. + * + * @param string $outOrderNo 商户系统内部的分账单号 + * @param string $outReturnNo 商户系统内部分账回退单号 + * @param int $returnAmount 回退金额 + * @param string $returnAccount 回退方账号 + * @param string $description 回退描述 + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function returnShare( + string $outOrderNo, + string $outReturnNo, + int $returnAmount, + string $returnAccount, + string $description + ) { + $params = [ + 'appid' => $this->app['config']->app_id, + 'out_order_no' => $outOrderNo, + 'out_return_no' => $outReturnNo, + 'return_account_type' => 'MERCHANT_ID', + 'return_account' => $returnAccount, + 'return_amount' => $returnAmount, + 'description' => $description, + ]; + + return $this->safeRequest( + 'secapi/pay/profitsharingreturn', + $params + ); + } +} diff --git a/vendor/overtrue/wechat/src/Payment/ProfitSharing/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/ProfitSharing/ServiceProvider.php new file mode 100644 index 0000000..d247d53 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/ProfitSharing/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\ProfitSharing; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author ClouderSky + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['profit_sharing'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Redpack/Client.php b/vendor/overtrue/wechat/src/Payment/Redpack/Client.php new file mode 100644 index 0000000..5b49ef5 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Redpack/Client.php @@ -0,0 +1,109 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Redpack; + +use EasyWeChat\Kernel\Support; +use EasyWeChat\Payment\Kernel\BaseClient; + +/** + * Class Client. + * + * @author tianyong90 <412039588@qq.com> + */ +class Client extends BaseClient +{ + /** + * Query redpack. + * + * @param mixed $mchBillno + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function info($mchBillno) + { + $params = is_array($mchBillno) ? $mchBillno : ['mch_billno' => $mchBillno]; + $base = [ + 'appid' => $this->app['config']->app_id, + 'bill_type' => 'MCHT', + ]; + + return $this->safeRequest('mmpaymkttransfers/gethbinfo', array_merge($base, $params)); + } + + /** + * Send miniprogram normal redpack. + * + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function sendMiniprogramNormal(array $params) + { + $base = [ + 'total_num' => 1, + 'client_ip' => $params['client_ip'] ?? Support\get_server_ip(), + 'wxappid' => $this->app['config']->app_id, + 'notify_way' => 'MINI_PROGRAM_JSAPI', + ]; + + return $this->safeRequest('mmpaymkttransfers/sendminiprogramhb', array_merge($base, $params)); + } + + /** + * Send normal redpack. + * + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function sendNormal(array $params) + { + $base = [ + 'total_num' => 1, + 'client_ip' => $params['client_ip'] ?? Support\get_server_ip(), + 'wxappid' => $this->app['config']->app_id, + ]; + + return $this->safeRequest('mmpaymkttransfers/sendredpack', array_merge($base, $params)); + } + + /** + * Send group redpack. + * + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function sendGroup(array $params) + { + $base = [ + 'amt_type' => 'ALL_RAND', + 'wxappid' => $this->app['config']->app_id, + ]; + + return $this->safeRequest('mmpaymkttransfers/sendgroupredpack', array_merge($base, $params)); + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Redpack/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Redpack/ServiceProvider.php new file mode 100644 index 0000000..af36f35 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Redpack/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Redpack; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['redpack'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Refund/Client.php b/vendor/overtrue/wechat/src/Payment/Refund/Client.php new file mode 100644 index 0000000..128c6e7 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Refund/Client.php @@ -0,0 +1,159 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Refund; + +use EasyWeChat\Payment\Kernel\BaseClient; + +class Client extends BaseClient +{ + /** + * Refund by out trade number. + * + * @param string $number + * @param string $refundNumber + * @param int $totalFee + * @param int $refundFee + * @param array $optional + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function byOutTradeNumber(string $number, string $refundNumber, int $totalFee, int $refundFee, array $optional = []) + { + return $this->refund($refundNumber, $totalFee, $refundFee, array_merge($optional, ['out_trade_no' => $number])); + } + + /** + * Refund by transaction id. + * + * @param string $transactionId + * @param string $refundNumber + * @param int $totalFee + * @param int $refundFee + * @param array $optional + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function byTransactionId(string $transactionId, string $refundNumber, int $totalFee, int $refundFee, array $optional = []) + { + return $this->refund($refundNumber, $totalFee, $refundFee, array_merge($optional, ['transaction_id' => $transactionId])); + } + + /** + * Query refund by transaction id. + * + * @param string $transactionId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function queryByTransactionId(string $transactionId) + { + return $this->query($transactionId, 'transaction_id'); + } + + /** + * Query refund by out trade number. + * + * @param string $outTradeNumber + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function queryByOutTradeNumber(string $outTradeNumber) + { + return $this->query($outTradeNumber, 'out_trade_no'); + } + + /** + * Query refund by out refund number. + * + * @param string $outRefundNumber + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function queryByOutRefundNumber(string $outRefundNumber) + { + return $this->query($outRefundNumber, 'out_refund_no'); + } + + /** + * Query refund by refund id. + * + * @param string $refundId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function queryByRefundId(string $refundId) + { + return $this->query($refundId, 'refund_id'); + } + + /** + * Refund. + * + * @param string $refundNumber + * @param int $totalFee + * @param int $refundFee + * @param array $optional + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function refund(string $refundNumber, int $totalFee, int $refundFee, $optional = []) + { + $params = array_merge([ + 'out_refund_no' => $refundNumber, + 'total_fee' => $totalFee, + 'refund_fee' => $refundFee, + 'appid' => $this->app['config']->app_id, + ], $optional); + + return $this->safeRequest($this->wrap( + $this->app->inSandbox() ? 'pay/refund' : 'secapi/pay/refund' + ), $params); + } + + /** + * Query refund. + * + * @param string $number + * @param string $type + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function query(string $number, string $type) + { + $params = [ + 'appid' => $this->app['config']->app_id, + $type => $number, + ]; + + return $this->request($this->wrap('pay/refundquery'), $params); + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Refund/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Refund/ServiceProvider.php new file mode 100644 index 0000000..faa4e89 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Refund/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Refund; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author mingyoung + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['refund'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Reverse/Client.php b/vendor/overtrue/wechat/src/Payment/Reverse/Client.php new file mode 100644 index 0000000..990e6e6 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Reverse/Client.php @@ -0,0 +1,67 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Reverse; + +use EasyWeChat\Payment\Kernel\BaseClient; + +class Client extends BaseClient +{ + /** + * Reverse order by out trade number. + * + * @param string $outTradeNumber + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function byOutTradeNumber(string $outTradeNumber) + { + return $this->reverse($outTradeNumber, 'out_trade_no'); + } + + /** + * Reverse order by transaction_id. + * + * @param string $transactionId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function byTransactionId(string $transactionId) + { + return $this->reverse($transactionId, 'transaction_id'); + } + + /** + * Reverse order. + * + * @param string $number + * @param string $type + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function reverse(string $number, string $type) + { + $params = [ + 'appid' => $this->app['config']->app_id, + $type => $number, + ]; + + return $this->safeRequest($this->wrap('secapi/pay/reverse'), $params); + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Reverse/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Reverse/ServiceProvider.php new file mode 100644 index 0000000..2417874 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Reverse/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Reverse; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['reverse'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Sandbox/Client.php b/vendor/overtrue/wechat/src/Payment/Sandbox/Client.php new file mode 100644 index 0000000..9f1ec7c --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Sandbox/Client.php @@ -0,0 +1,60 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Sandbox; + +use EasyWeChat\Kernel\Traits\InteractsWithCache; +use EasyWeChat\Payment\Kernel\BaseClient; +use EasyWeChat\Payment\Kernel\Exceptions\SandboxException; + +/** + * Class Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + use InteractsWithCache; + + /** + * @return string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Payment\Kernel\Exceptions\SandboxException + * @throws \Psr\SimpleCache\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getKey(): string + { + if ($cache = $this->getCache()->get($this->getCacheKey())) { + return $cache; + } + + $response = $this->requestArray('sandboxnew/pay/getsignkey'); + + if ('SUCCESS' === $response['return_code']) { + $this->getCache()->set($this->getCacheKey(), $key = $response['sandbox_signkey'], 24 * 3600); + + return $key; + } + + throw new SandboxException($response['retmsg'] ?? $response['return_msg']); + } + + /** + * @return string + */ + protected function getCacheKey(): string + { + return 'easywechat.payment.sandbox.'.md5($this->app['config']->app_id.$this->app['config']['mch_id']); + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Sandbox/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Sandbox/ServiceProvider.php new file mode 100644 index 0000000..d0b5d6e --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Sandbox/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Sandbox; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author mingyoung + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * @param \Pimple\Container $app + */ + public function register(Container $app) + { + $app['sandbox'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Security/Client.php b/vendor/overtrue/wechat/src/Payment/Security/Client.php new file mode 100644 index 0000000..01aa752 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Security/Client.php @@ -0,0 +1,38 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Security; + +use EasyWeChat\Payment\Kernel\BaseClient; + +/** + * Class Client. + * + * @author overtrue + */ +class Client extends BaseClient +{ + /** + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getPublicKey() + { + $params = [ + 'sign_type' => 'MD5', + ]; + + return $this->safeRequest('https://fraud.mch.weixin.qq.com/risk/getpublickey', $params); + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Security/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Security/ServiceProvider.php new file mode 100644 index 0000000..e0e31f8 --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Security/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Security; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['security'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Transfer/Client.php b/vendor/overtrue/wechat/src/Payment/Transfer/Client.php new file mode 100644 index 0000000..619cc0f --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Transfer/Client.php @@ -0,0 +1,122 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Transfer; + +use EasyWeChat\Kernel\Exceptions\RuntimeException; +use EasyWeChat\Payment\Kernel\BaseClient; +use function EasyWeChat\Kernel\Support\get_server_ip; +use function EasyWeChat\Kernel\Support\rsa_public_encrypt; + +/** + * Class Client. + * + * @author AC + */ +class Client extends BaseClient +{ + /** + * Query MerchantPay to balance. + * + * @param string $partnerTradeNo + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function queryBalanceOrder(string $partnerTradeNo) + { + $params = [ + 'appid' => $this->app['config']->app_id, + 'mch_id' => $this->app['config']->mch_id, + 'partner_trade_no' => $partnerTradeNo, + ]; + + return $this->safeRequest('mmpaymkttransfers/gettransferinfo', $params); + } + + /** + * Send MerchantPay to balance. + * + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function toBalance(array $params) + { + $base = [ + 'mch_id' => null, + 'mchid' => $this->app['config']->mch_id, + 'mch_appid' => $this->app['config']->app_id, + ]; + + if (empty($params['spbill_create_ip'])) { + $params['spbill_create_ip'] = get_server_ip(); + } + + return $this->safeRequest('mmpaymkttransfers/promotion/transfers', array_merge($base, $params)); + } + + /** + * Query MerchantPay order to BankCard. + * + * @param string $partnerTradeNo + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function queryBankCardOrder(string $partnerTradeNo) + { + $params = [ + 'mch_id' => $this->app['config']->mch_id, + 'partner_trade_no' => $partnerTradeNo, + ]; + + return $this->safeRequest('mmpaysptrans/query_bank', $params); + } + + /** + * Send MerchantPay to BankCard. + * + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function toBankCard(array $params) + { + foreach (['bank_code', 'partner_trade_no', 'enc_bank_no', 'enc_true_name', 'amount'] as $key) { + if (empty($params[$key])) { + throw new RuntimeException(\sprintf('"%s" is required.', $key)); + } + } + + $publicKey = file_get_contents($this->app['config']->get('rsa_public_key_path')); + + $params['enc_bank_no'] = rsa_public_encrypt($params['enc_bank_no'], $publicKey); + $params['enc_true_name'] = rsa_public_encrypt($params['enc_true_name'], $publicKey); + + return $this->safeRequest('mmpaysptrans/pay_bank', $params); + } +} diff --git a/vendor/overtrue/wechat/src/Payment/Transfer/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Transfer/ServiceProvider.php new file mode 100644 index 0000000..0e27aad --- /dev/null +++ b/vendor/overtrue/wechat/src/Payment/Transfer/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Payment\Transfer; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['transfer'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Work/Agent/Client.php b/vendor/overtrue/wechat/src/Work/Agent/Client.php new file mode 100644 index 0000000..9e11aac --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Agent/Client.php @@ -0,0 +1,68 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Agent; + +use EasyWeChat\Kernel\BaseClient; + +/** + * This is WeWork Agent Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + /** + * Get agent. + * + * @param int $agentId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function get(int $agentId) + { + $params = [ + 'agentid' => $agentId, + ]; + + return $this->httpGet('cgi-bin/agent/get', $params); + } + + /** + * Set agent. + * + * @param int $agentId + * @param array $attributes + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function set(int $agentId, array $attributes) + { + return $this->httpPostJson('cgi-bin/agent/set', array_merge(['agentid' => $agentId], $attributes)); + } + + /** + * Get agent list. + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function list() + { + return $this->httpGet('cgi-bin/agent/list'); + } +} diff --git a/vendor/overtrue/wechat/src/Work/Agent/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/Agent/ServiceProvider.php new file mode 100644 index 0000000..87f4384 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Agent/ServiceProvider.php @@ -0,0 +1,37 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Agent; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author mingyoung + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['agent'] = function ($app) { + return new Client($app); + }; + + $app['agent_workbench'] = function ($app) { + return new WorkbenchClient($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Work/Agent/WorkbenchClient.php b/vendor/overtrue/wechat/src/Work/Agent/WorkbenchClient.php new file mode 100644 index 0000000..2a4bf0b --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Agent/WorkbenchClient.php @@ -0,0 +1,74 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Agent; + +use EasyWeChat\Kernel\BaseClient; + +/** + * This is WeWork Agent WorkbenchClient. + * + * @author 读心印 + */ +class WorkbenchClient extends BaseClient +{ + /** + * 设置应用在工作台展示的模版. + * + * @see https://open.work.weixin.qq.com/api/doc/90000/90135/92535#设置应用在工作台展示的模版 + * + * @param array $params + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException|\GuzzleHttp\Exception\GuzzleException + */ + public function setWorkbenchTemplate(array $params) + { + return $this->httpPostJson('cgi-bin/agent/set_workbench_template', $params); + } + + /** + * 获取应用在工作台展示的模版. + * + * @see https://open.work.weixin.qq.com/api/doc/90000/90135/92535#获取应用在工作台展示的模版 + * + * @param int $agentId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException|\GuzzleHttp\Exception\GuzzleException + */ + public function getWorkbenchTemplate(int $agentId) + { + $params = [ + 'agentid' => $agentId + ]; + + return $this->httpPostJson('cgi-bin/agent/get_workbench_template', $params); + } + + /** + * 设置应用在用户工作台展示的数据. + * + * @see https://open.work.weixin.qq.com/api/doc/90000/90135/92535#设置应用在用户工作台展示的数据 + * + * @param array $params + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException|\GuzzleHttp\Exception\GuzzleException + */ + public function setWorkbenchData(array $params) + { + return $this->httpPostJson('cgi-bin/agent/set_workbench_data', $params); + } +} diff --git a/vendor/overtrue/wechat/src/Work/Application.php b/vendor/overtrue/wechat/src/Work/Application.php new file mode 100644 index 0000000..19dc191 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Application.php @@ -0,0 +1,117 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work; + +use EasyWeChat\Kernel\ServiceContainer; +use EasyWeChat\Work\MiniProgram\Application as MiniProgram; + +/** + * Application. + * + * @author mingyoung + * + * @property \EasyWeChat\Work\OA\Client $oa + * @property \EasyWeChat\Work\Auth\AccessToken $access_token + * @property \EasyWeChat\Work\Agent\Client $agent + * @property \EasyWeChat\Work\Department\Client $department + * @property \EasyWeChat\Work\Media\Client $media + * @property \EasyWeChat\Work\Menu\Client $menu + * @property \EasyWeChat\Work\Message\Client $message + * @property \EasyWeChat\Work\Message\Messenger $messenger + * @property \EasyWeChat\Work\User\Client $user + * @property \EasyWeChat\Work\User\TagClient $tag + * @property \EasyWeChat\Work\Server\Guard $server + * @property \EasyWeChat\Work\Jssdk\Client $jssdk + * @property \Overtrue\Socialite\Providers\WeWork $oauth + * @property \EasyWeChat\Work\Invoice\Client $invoice + * @property \EasyWeChat\Work\Chat\Client $chat + * @property \EasyWeChat\Work\ExternalContact\Client $external_contact + * @property \EasyWeChat\Work\ExternalContact\ContactWayClient $contact_way + * @property \EasyWeChat\Work\ExternalContact\StatisticsClient $external_contact_statistics + * @property \EasyWeChat\Work\ExternalContact\MessageClient $external_contact_message + * @property \EasyWeChat\Work\GroupRobot\Client $group_robot + * @property \EasyWeChat\Work\GroupRobot\Messenger $group_robot_messenger + * @property \EasyWeChat\Work\Calendar\Client $calendar + * @property \EasyWeChat\Work\Schedule\Client $schedule + * @property \EasyWeChat\Work\MsgAudit\Client $msg_audit + * @property \EasyWeChat\Work\Live\Client $live + * @property \EasyWeChat\Work\CorpGroup\Client $corp_group + * @property \EasyWeChat\Work\ExternalContact\SchoolClient $school + * @property \EasyWeChat\Work\ExternalContact\MessageTemplateClient $external_contact_message_template + * @property \EasyWeChat\Work\Kf\AccountClient $kf_account + * @property \EasyWeChat\Work\Kf\ServicerClient $kf_servicer + * @property \EasyWeChat\Work\Kf\MessageClient $kf_message + * + * @method mixed getCallbackIp() + */ +class Application extends ServiceContainer +{ + /** + * @var array + */ + protected $providers = [ + OA\ServiceProvider::class, + Auth\ServiceProvider::class, + Base\ServiceProvider::class, + Menu\ServiceProvider::class, + OAuth\ServiceProvider::class, + User\ServiceProvider::class, + Agent\ServiceProvider::class, + Media\ServiceProvider::class, + Message\ServiceProvider::class, + Department\ServiceProvider::class, + Server\ServiceProvider::class, + Jssdk\ServiceProvider::class, + Invoice\ServiceProvider::class, + Chat\ServiceProvider::class, + ExternalContact\ServiceProvider::class, + GroupRobot\ServiceProvider::class, + Calendar\ServiceProvider::class, + Schedule\ServiceProvider::class, + MsgAudit\ServiceProvider::class, + Live\ServiceProvider::class, + CorpGroup\ServiceProvider::class, + Mobile\ServiceProvider::class, + Kf\ServiceProvider::class, + ]; + + /** + * @var array + */ + protected $defaultConfig = [ + // http://docs.guzzlephp.org/en/stable/request-options.html + 'http' => [ + 'base_uri' => 'https://qyapi.weixin.qq.com/', + ], + ]; + + /** + * Creates the miniProgram application. + * + * @return \EasyWeChat\Work\MiniProgram\Application + */ + public function miniProgram(): MiniProgram + { + return new MiniProgram($this->getConfig()); + } + + /** + * @param string $method + * @param array $arguments + * + * @return mixed + */ + public function __call($method, $arguments) + { + return $this['base']->$method(...$arguments); + } +} diff --git a/vendor/overtrue/wechat/src/Work/Auth/AccessToken.php b/vendor/overtrue/wechat/src/Work/Auth/AccessToken.php new file mode 100644 index 0000000..56d5218 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Auth/AccessToken.php @@ -0,0 +1,45 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Auth; + +use EasyWeChat\Kernel\AccessToken as BaseAccessToken; + +/** + * Class AccessToken. + * + * @author mingyoung + */ +class AccessToken extends BaseAccessToken +{ + /** + * @var string + */ + protected $endpointToGetToken = 'cgi-bin/gettoken'; + + /** + * @var int + */ + protected $safeSeconds = 0; + + /** + * Credential for get token. + * + * @return array + */ + protected function getCredentials(): array + { + return [ + 'corpid' => $this->app['config']['corp_id'], + 'corpsecret' => $this->app['config']['secret'], + ]; + } +} diff --git a/vendor/overtrue/wechat/src/Work/Auth/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/Auth/ServiceProvider.php new file mode 100644 index 0000000..5f0dba9 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Auth/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Auth; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + isset($app['access_token']) || $app['access_token'] = function ($app) { + return new AccessToken($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Work/Base/Client.php b/vendor/overtrue/wechat/src/Work/Base/Client.php new file mode 100644 index 0000000..97b5445 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Base/Client.php @@ -0,0 +1,34 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Base; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + /** + * Get callback ip. + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function getCallbackIp() + { + return $this->httpGet('cgi-bin/getcallbackip'); + } +} diff --git a/vendor/overtrue/wechat/src/Work/Base/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/Base/ServiceProvider.php new file mode 100644 index 0000000..5e28fd1 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Base/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Base; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author mingyoung + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * @param Container $app + */ + public function register(Container $app) + { + $app['base'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Work/Calendar/Client.php b/vendor/overtrue/wechat/src/Work/Calendar/Client.php new file mode 100644 index 0000000..90a6849 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Calendar/Client.php @@ -0,0 +1,85 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Calendar; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author her-cat + */ +class Client extends BaseClient +{ + /** + * Add a calendar. + * + * @param array $calendar + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function add(array $calendar) + { + return $this->httpPostJson('cgi-bin/oa/calendar/add', compact('calendar')); + } + + /** + * Update the calendar. + * + * @param string $id + * @param array $calendar + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(string $id, array $calendar) + { + $calendar += ['cal_id' => $id]; + + return $this->httpPostJson('cgi-bin/oa/calendar/update', compact('calendar')); + } + + /** + * Get one or more calendars. + * + * @param string|array $ids + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get($ids) + { + return $this->httpPostJson('cgi-bin/oa/calendar/get', ['cal_id_list' => (array) $ids]); + } + + /** + * Delete a calendar. + * + * @param string $id + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete(string $id) + { + return $this->httpPostJson('cgi-bin/oa/calendar/del', ['cal_id' => $id]); + } +} diff --git a/vendor/overtrue/wechat/src/Work/Calendar/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/Calendar/ServiceProvider.php new file mode 100644 index 0000000..1819e4f --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Calendar/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Calendar; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author her-cat + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc} + */ + public function register(Container $app) + { + $app['calendar'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Work/Chat/Client.php b/vendor/overtrue/wechat/src/Work/Chat/Client.php new file mode 100644 index 0000000..0d4fdec --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Chat/Client.php @@ -0,0 +1,82 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Chat; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author XiaolonY + */ +class Client extends BaseClient +{ + /** + * Get chat. + * + * @param string $chatId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function get(string $chatId) + { + return $this->httpGet('cgi-bin/appchat/get', ['chatid' => $chatId]); + } + + /** + * Create chat. + * + * @param array $data + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function create(array $data) + { + return $this->httpPostJson('cgi-bin/appchat/create', $data); + } + + /** + * Update chat. + * + * @param string $chatId + * @param array $data + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(string $chatId, array $data) + { + return $this->httpPostJson('cgi-bin/appchat/update', array_merge(['chatid' => $chatId], $data)); + } + + /** + * Send a message. + * + * @param array $message + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function send(array $message) + { + return $this->httpPostJson('cgi-bin/appchat/send', $message); + } +} diff --git a/vendor/overtrue/wechat/src/Work/Chat/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/Chat/ServiceProvider.php new file mode 100644 index 0000000..5bf9b2c --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Chat/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Chat; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author XiaolonY + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['chat'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Work/CorpGroup/Client.php b/vendor/overtrue/wechat/src/Work/CorpGroup/Client.php new file mode 100644 index 0000000..e1ecd42 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/CorpGroup/Client.php @@ -0,0 +1,121 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\CorpGroup; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author 读心印 + */ +class Client extends BaseClient +{ + /** + * 获取应用共享信息. + * + * @see https://open.work.weixin.qq.com/api/doc/90000/90135/93403 + * + * @param int $agentId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getAppShareInfo(int $agentId) + { + $params = [ + 'agentid' => $agentId + ]; + + return $this->httpPostJson('cgi-bin/corpgroup/corp/list_app_share_info', $params); + } + + /** + * 获取下级企业的access_token. + * + * @see https://open.work.weixin.qq.com/api/doc/90000/90135/93359 + * + * @param string $corpId + * @param int $agentId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getToken(string $corpId, int $agentId) + { + $params = [ + 'corpid' => $corpId, + 'agentid' => $agentId + ]; + + return $this->httpPostJson('cgi-bin/corpgroup/corp/gettoken', $params); + } + + /** + * 获取下级企业的小程序session. + * + * @see https://open.work.weixin.qq.com/api/doc/90000/90135/93355 + * + * @param string $userId + * @param string $sessionKey + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getMiniProgramTransferSession(string $userId, string $sessionKey) + { + $params = [ + 'userid' => $userId, + 'session_key' => $sessionKey + ]; + + return $this->httpPostJson('cgi-bin/miniprogram/transfer_session', $params); + } + + /** + * 将明文corpid转换为第三方应用获取的corpid(仅限第三方服务商,转换已获授权企业的corpid) + * + * @see https://open.work.weixin.qq.com/api/doc/90001/90143/95327#1.4%20corpid%E8%BD%AC%E6%8D%A2 + * + * @param string $corpId 获取到的企业ID + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getOpenCorpid(string $corpId) + { + return $this->httpPostJson('cgi-bin/corp/to_open_corpid', ['corpid' => $corpId]); + } + + /** + * 将自建应用获取的userid转换为第三方应用获取的userid(仅代开发自建应用或第三方应用可调用) + * + * @see https://open.work.weixin.qq.com/api/doc/90001/90143/95327#2.4%20userid%E7%9A%84%E8%BD%AC%E6%8D%A2 + * + * @param array $useridList 获取到的成员ID + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function batchUseridToOpenUserid(array $useridList) + { + return $this->httpPostJson('cgi-bin/batch/userid_to_openuserid', ['userid_list' => $useridList]); + } +} diff --git a/vendor/overtrue/wechat/src/Work/CorpGroup/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/CorpGroup/ServiceProvider.php new file mode 100644 index 0000000..0e558c3 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/CorpGroup/ServiceProvider.php @@ -0,0 +1,35 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\CorpGroup; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * ServiceProvider. + * + * @author 读心印 + */ +class ServiceProvider implements ServiceProviderInterface +{ + protected $app; + + /** + * @param Container $app + */ + public function register(Container $app) + { + $app['corp_group'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Work/Department/Client.php b/vendor/overtrue/wechat/src/Work/Department/Client.php new file mode 100644 index 0000000..c34cc86 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Department/Client.php @@ -0,0 +1,81 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Department; + +use EasyWeChat\Kernel\BaseClient; + +/** + * This is WeWork Department Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + /** + * Create a department. + * + * @param array $data + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function create(array $data) + { + return $this->httpPostJson('cgi-bin/department/create', $data); + } + + /** + * Update a department. + * + * @param int $id + * @param array $data + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(int $id, array $data) + { + return $this->httpPostJson('cgi-bin/department/update', array_merge(compact('id'), $data)); + } + + /** + * Delete a department. + * + * @param int $id + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function delete($id) + { + return $this->httpGet('cgi-bin/department/delete', compact('id')); + } + + /** + * Get department lists. + * + * @param int|null $id + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function list($id = null) + { + return $this->httpGet('cgi-bin/department/list', compact('id')); + } +} diff --git a/vendor/overtrue/wechat/src/Work/Department/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/Department/ServiceProvider.php new file mode 100644 index 0000000..2fe5bc6 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Department/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Department; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author mingyoung + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['department'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Work/ExternalContact/Client.php b/vendor/overtrue/wechat/src/Work/ExternalContact/Client.php new file mode 100644 index 0000000..11a5b7a --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/ExternalContact/Client.php @@ -0,0 +1,590 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\ExternalContact; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + /** + * 获取配置了客户联系功能的成员列表. + * + * @see https://work.weixin.qq.com/api/doc#90000/90135/91554 + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException|\GuzzleHttp\Exception\GuzzleException + */ + public function getFollowUsers() + { + return $this->httpGet('cgi-bin/externalcontact/get_follow_user_list'); + } + + /** + * 获取外部联系人列表. + * + * @see https://work.weixin.qq.com/api/doc#90000/90135/91555 + * + * @param string $userId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException|\GuzzleHttp\Exception\GuzzleException + */ + public function list(string $userId) + { + return $this->httpGet('cgi-bin/externalcontact/list', [ + 'userid' => $userId, + ]); + } + + /** + * 批量获取客户详情. + * + * @see https://work.weixin.qq.com/api/doc/90000/90135/92994 + * + * @param array $userIdList + * @param string $cursor + * @param integer $limit + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException|\GuzzleHttp\Exception\GuzzleException + */ + public function batchGet(array $userIdList, string $cursor = '', int $limit = 100) + { + return $this->httpPostJson('cgi-bin/externalcontact/batch/get_by_user', [ + 'userid_list' => $userIdList, + 'cursor' => $cursor, + 'limit' => $limit, + ]); + } + + /** + * 获取外部联系人详情. + * + * @see https://work.weixin.qq.com/api/doc#90000/90135/91556 + * + * @param string $externalUserId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException|\GuzzleHttp\Exception\GuzzleException + */ + public function get(string $externalUserId) + { + return $this->httpGet('cgi-bin/externalcontact/get', [ + 'external_userid' => $externalUserId, + ]); + } + + /** + * 批量获取外部联系人详情. + * + * @see https://work.weixin.qq.com/api/doc/90001/90143/93010 + * + * @param array $userIdList + * @param string $cursor + * @param int $limit + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function batchGetByUser(array $userIdList, string $cursor, int $limit) + { + return $this->httpPostJson('cgi-bin/externalcontact/batch/get_by_user', [ + 'userid_list' => $userIdList, + 'cursor' => $cursor, + 'limit' => $limit + ]); + } + + + /** + * 修改客户备注信息. + * + * @see https://work.weixin.qq.com/api/doc/90000/90135/92115 + * + * @param array $data + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException|\GuzzleHttp\Exception\GuzzleException + */ + public function remark(array $data) + { + return $this->httpPostJson( + 'cgi-bin/externalcontact/remark', + $data + ); + } + + + /** + * 获取离职成员的客户列表. + * + * @see https://work.weixin.qq.com/api/doc/90000/90135/92124 + * + * @param null|int $pageId + * @param null|int $pageSize + * @param string $cursor + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getUnassigned(?int $pageId = null, ?int $pageSize = 1000, ?string $cursor = null) + { + $params = [ + 'page_id' => $pageId, + 'page_size' => $pageSize, + 'cursor' => $cursor, + ]; + $writableParams = array_filter($params, function (string $key) use ($params) { + return !is_null($params[$key]); + }, ARRAY_FILTER_USE_KEY); + return $this->httpPostJson('cgi-bin/externalcontact/get_unassigned_list', $writableParams); + } + + /** + * 离职成员的外部联系人再分配. + * + * @see https://work.weixin.qq.com/api/doc#90000/90135/91564 + * + * @param string $externalUserId + * @param string $handoverUserId + * @param string $takeoverUserId + * @param string $transferSuccessMessage + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function transfer(string $externalUserId, string $handoverUserId, string $takeoverUserId, string $transferSuccessMessage) + { + $params = [ + 'external_userid' => $externalUserId, + 'handover_userid' => $handoverUserId, + 'takeover_userid' => $takeoverUserId, + 'transfer_success_msg' => $transferSuccessMessage + ]; + + return $this->httpPostJson('cgi-bin/externalcontact/transfer', $params); + } + + /** + * 分配在职成员的客户. + * + * @see https://work.weixin.qq.com/api/doc/90000/90135/92125 + * + * @param array $externalUserId + * @param string $handoverUserId + * @param string $takeoverUserId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function transferCustomer(array $externalUserId, string $handoverUserId, string $takeoverUserId, string $transferSuccessMessage) + { + $params = [ + 'external_userid' => $externalUserId, + 'handover_userid' => $handoverUserId, + 'takeover_userid' => $takeoverUserId, + 'transfer_success_msg' => $transferSuccessMessage + ]; + + return $this->httpPostJson('cgi-bin/externalcontact/transfer_customer', $params); + } + + /** + * 分配离职成员的客户. + * + * @see https://work.weixin.qq.com/api/doc/90000/90135/94081 + * + * @param array $externalUserId + * @param string $handoverUserId + * @param string $takeoverUserId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function resignedTransferCustomer(array $externalUserId, string $handoverUserId, string $takeoverUserId) + { + $params = [ + 'external_userid' => $externalUserId, + 'handover_userid' => $handoverUserId, + 'takeover_userid' => $takeoverUserId, + ]; + + return $this->httpPostJson('cgi-bin/externalcontact/resigned/transfer_customer', $params); + } + + /** + * 离职成员的群再分配. + * + * @see https://work.weixin.qq.com/api/doc/90000/90135/92127 + * + * @param array $chatIds + * @param string $newOwner + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function transferGroupChat(array $chatIds, string $newOwner) + { + $params = [ + 'chat_id_list' => $chatIds, + 'new_owner' => $newOwner + ]; + + return $this->httpPostJson('cgi-bin/externalcontact/groupchat/transfer', $params); + } + + /** + * 查询客户接替状态. + * + * @see https://work.weixin.qq.com/api/doc/90000/90135/94082 + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @param string $handoverUserId + * @param string $takeoverUserId + * @param null|string $cursor + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function transferResult(string $handoverUserId, string $takeoverUserId, ?string $cursor = null) + { + $params = [ + 'handover_userid' => $handoverUserId, + 'takeover_userid' => $takeoverUserId, + 'cursor' => $cursor, + ]; + + return $this->httpPostJson('cgi-bin/externalcontact/resigned/transfer_result', $params); + } + + /** + * 查询客户接替结果. + * + * @see https://work.weixin.qq.com/api/doc/90001/90143/93009 + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @param string $externalUserId + * @param string $handoverUserId + * @param string $takeoverUserId + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getTransferResult(string $externalUserId, string $handoverUserId, string $takeoverUserId) + { + $params = [ + 'external_userid' => $externalUserId, + 'handover_userid' => $handoverUserId, + 'takeover_userid' => $takeoverUserId, + ]; + + return $this->httpPostJson('cgi-bin/externalcontact/get_transfer_result', $params); + } + + /** + * 获取客户群列表. + * + * @see https://work.weixin.qq.com/api/doc/90000/90135/92120 + * + * @param array $params + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + + public function getGroupChats(array $params) + { + return $this->httpPostJson('cgi-bin/externalcontact/groupchat/list', $params); + } + + /** + * 获取客户群详情. + * + * @see https://work.weixin.qq.com/api/doc/90000/90135/92122 + * + * @param string $chatId + * @param int $needName + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + + public function getGroupChat(string $chatId, int $needName = 0) + { + $params = [ + 'chat_id' => $chatId, + 'need_name' => $needName, + ]; + + return $this->httpPostJson('cgi-bin/externalcontact/groupchat/get', $params); + } + + /** + * 获取企业标签库. + * + * @see https://work.weixin.qq.com/api/doc/90000/90135/92117#获取企业标签库 + * + * @param array $tagIds + * @param array $groupIds + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + + public function getCorpTags(array $tagIds = [], array $groupIds = []) + { + $params = [ + 'tag_id' => $tagIds, + 'group_id' => $groupIds + ]; + + return $this->httpPostJson('cgi-bin/externalcontact/get_corp_tag_list', $params); + } + + + /** + * 添加企业客户标签. + * + * @see https://work.weixin.qq.com/api/doc/90000/90135/92117#添加企业客户标签 + * + * @param array $params + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + + public function addCorpTag(array $params) + { + return $this->httpPostJson('cgi-bin/externalcontact/add_corp_tag', $params); + } + + + /** + * 编辑企业客户标签. + * + * @see https://work.weixin.qq.com/api/doc/90000/90135/92117#编辑企业客户标签 + * + * @param string $id + * @param string|null $name + * @param int|null $order + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + + public function updateCorpTag(string $id, ?string $name = null, ?int $order = null) + { + $params = [ + "id" => $id + ]; + + if (!\is_null($name)) { + $params['name'] = $name; + } + + if (!\is_null($order)) { + $params['order'] = $order; + } + + return $this->httpPostJson('cgi-bin/externalcontact/edit_corp_tag', $params); + } + + + /** + * 删除企业客户标签. + * + * @see https://work.weixin.qq.com/api/doc/90000/90135/92117#删除企业客户标签 + * + * @param array $tagId + * @param array $groupId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + + public function deleteCorpTag(array $tagId, array $groupId) + { + $params = [ + "tag_id" => $tagId, + "group_id" => $groupId, + ]; + + return $this->httpPostJson('cgi-bin/externalcontact/del_corp_tag', $params); + } + + + /** + * 编辑客户企业标签. + * + * @see https://work.weixin.qq.com/api/doc/90000/90135/92118 + * + * @param array $params + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + + public function markTags(array $params) + { + return $this->httpPostJson('cgi-bin/externalcontact/mark_tag', $params); + } + + /** + * 外部联系人unionid转换. + * + * @see https://work.weixin.qq.com/api/doc/90001/90143/93274 + * + * @param string|null $unionid 微信客户的unionid + * @param string|null $openid 微信客户的openid + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function unionidToExternalUserid(?string $unionid = null, ?string $openid = null) + { + return $this->httpPostJson( + 'cgi-bin/externalcontact/unionid_to_external_userid', + [ + 'unionid' => $unionid, + 'openid' => $openid, + ] + ); + } + + /** + * 代开发应用external_userid转换. + * + * @see https://work.weixin.qq.com/api/doc/90001/90143/95195 + * + * @param string $externalUserid 代开发自建应用获取到的外部联系人ID + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function toServiceExternalUserid(string $externalUserid) + { + return $this->httpPostJson( + 'cgi-bin/externalcontact/to_service_external_userid', + [ + 'external_userid' => $externalUserid, + ] + ); + } + + + /** + * 转换external_userid + * + * @see https://open.work.weixin.qq.com/api/doc/90001/90143/95327 + * + * @param array $externalUserIds + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + * + * @author 读心印 + */ + + public function getNewExternalUserid(array $externalUserIds) + { + return $this->httpPostJson('cgi-bin/externalcontact/get_new_external_userid', ['external_userid_list' => $externalUserIds]); + } + + /** + * 设置迁移完成 + * + * @see https://open.work.weixin.qq.com/api/doc/90001/90143/95327 + * + * @param string $corpid + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + * + * @author 读心印 + */ + + public function finishExternalUseridMigration(string $corpid) + { + return $this->httpPostJson('cgi-bin/externalcontact/finish_external_userid_migration', ['corpid' => $corpid]); + } + + /** + * unionid查询external_userid + * + * @see https://open.work.weixin.qq.com/api/doc/90001/90143/95327 + * + * @param string $unionid + * @param string $openid + * @param string $corpid + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + * + * @author 读心印 + */ + + public function unionidToexternalUserid3rd(string $unionid, string $openid, string $corpid = '') + { + $params = [ + 'unionid' => $unionid, + 'openid' => $openid, + 'corpid' => $corpid + ]; + + return $this->httpPostJson('cgi-bin/externalcontact/unionid_to_external_userid_3rd', $params); + } +} diff --git a/vendor/overtrue/wechat/src/Work/ExternalContact/ContactWayClient.php b/vendor/overtrue/wechat/src/Work/ExternalContact/ContactWayClient.php new file mode 100644 index 0000000..cea2ca7 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/ExternalContact/ContactWayClient.php @@ -0,0 +1,122 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\ExternalContact; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class ContactWayClient. + * + * @author milkmeowo + */ +class ContactWayClient extends BaseClient +{ + /** + * 配置客户联系「联系我」方式. + * + * @param int $type + * @param int $scene + * @param array $config + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function create(int $type, int $scene, array $config = []) + { + $params = array_merge([ + 'type' => $type, + 'scene' => $scene, + ], $config); + + return $this->httpPostJson('cgi-bin/externalcontact/add_contact_way', $params); + } + + /** + * 获取企业已配置的「联系我」方式. + * + * @param string $configId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get(string $configId) + { + return $this->httpPostJson('cgi-bin/externalcontact/get_contact_way', [ + 'config_id' => $configId, + ]); + } + + /** + * 更新企业已配置的「联系我」方式. + * + * @param string $configId + * @param array $config + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(string $configId, array $config = []) + { + $params = array_merge([ + 'config_id' => $configId, + ], $config); + + return $this->httpPostJson('cgi-bin/externalcontact/update_contact_way', $params); + } + + /** + * 删除企业已配置的「联系我」方式. + * + * @param string $configId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete(string $configId) + { + return $this->httpPostJson('cgi-bin/externalcontact/del_contact_way', [ + 'config_id' => $configId, + ]); + } + + /** + * 获取企业已配置的「联系我」列表,注意,该接口仅可获取2021年7月10日以后创建的「联系我」 + * + * @param string $cursor 分页查询使用的游标,为上次请求返回的 next_cursor + * @param int $limit 每次查询的分页大小,默认为100条,最多支持1000条 + * @param int|null $startTime 「联系我」创建起始时间戳, 不传默认为90天前 + * @param int|null $endTime 「联系我」创建结束时间戳, 不传默认为当前时间 + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function list(string $cursor = '', int $limit = 100, int $startTime = null, int $endTime = null) + { + $data = [ + 'cursor' => $cursor, + 'limit' => $limit, + ]; + if ($startTime) $data['start_time'] = $startTime; + if ($endTime) $data['end_time'] = $endTime; + return $this->httpPostJson('cgi-bin/externalcontact/list_contact_way', $data); + } +} diff --git a/vendor/overtrue/wechat/src/Work/ExternalContact/MessageClient.php b/vendor/overtrue/wechat/src/Work/ExternalContact/MessageClient.php new file mode 100644 index 0000000..d75c13f --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/ExternalContact/MessageClient.php @@ -0,0 +1,262 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\ExternalContact; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; + +/** + * Class MessageClient. + * + * @author milkmeowo + */ +class MessageClient extends BaseClient +{ + /** + * Required attributes. + * + * @var array + */ + protected $required = ['content', 'title', 'url', 'pic_media_id', 'appid', 'page']; + + protected $textMessage = [ + 'content' => '', + ]; + + protected $imageMessage = [ + + ]; + + protected $linkMessage = [ + 'title' => '', + 'picurl' => '', + 'desc' => '', + 'url' => '', + ]; + + protected $miniprogramMessage = [ + 'title' => '', + 'pic_media_id' => '', + 'appid' => '', + 'page' => '', + ]; + + /** + * 添加企业群发消息模板 + * + * @see https://work.weixin.qq.com/api/doc#90000/90135/91560 + * + * @param array $msg + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function submit(array $msg) + { + $params = $this->formatMessage($msg); + + return $this->httpPostJson('cgi-bin/externalcontact/add_msg_template', $params); + } + + /** + * 获取企业群发消息发送结果. + * + * @see https://work.weixin.qq.com/api/doc#90000/90135/91561 + * + * @param string $msgId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get(string $msgId) + { + return $this->httpPostJson('cgi-bin/externalcontact/get_group_msg_result', [ + 'msgid' => $msgId, + ]); + } + + /** + * 获取群发记录列表. + * + * @see https://open.work.weixin.qq.com/api/doc/90000/90135/93338#%E8%8E%B7%E5%8F%96%E7%BE%A4%E5%8F%91%E8%AE%B0%E5%BD%95%E5%88%97%E8%A1%A8 + * + * @param string $chatType 群发任务的类型,默认为single,表示发送给客户,group表示发送给客户群 + * @param int $startTime 群发任务记录开始时间 + * @param int $endTime 群发任务记录结束时间 + * @param string|null $creator 群发任务创建人企业账号id + * @param int|null $filterType 创建人类型。0:企业发表 1:个人发表 2:所有,包括个人创建以及企业创建,默认情况下为所有类型 + * @param int|null $limit 返回的最大记录数,整型,最大值100,默认值50,超过最大值时取默认值 + * @param string|null $cursor 用于分页查询的游标,字符串类型,由上一次调用返回,首次调用可不填 + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getGroupmsgListV2(string $chatType, int $startTime, int $endTime, ?string $creator = null, ?int $filterType = null, ?int $limit = null, ?string $cursor = null) + { + $data = [ + 'chat_type' => $chatType, + 'start_time' => $startTime, + 'end_time' => $endTime, + 'creator' => $creator, + 'filter_type' => $filterType, + 'limit' => $limit, + 'cursor' => $cursor, + ]; + $writableData = array_filter($data, function (string $key) use ($data) { + return !is_null($data[$key]); + }, ARRAY_FILTER_USE_KEY); + return $this->httpPostJson('cgi-bin/externalcontact/get_groupmsg_list_v2', $writableData); + } + + /** + * 获取群发成员发送任务列表. + * + * @see https://open.work.weixin.qq.com/api/doc/90000/90135/93338#%E8%8E%B7%E5%8F%96%E7%BE%A4%E5%8F%91%E6%88%90%E5%91%98%E5%8F%91%E9%80%81%E4%BB%BB%E5%8A%A1%E5%88%97%E8%A1%A8 + * + * @param string $msgId 群发消息的id,通过获取群发记录列表接口返回 + * @param int|null $limit 返回的最大记录数,整型,最大值1000,默认值500,超过最大值时取默认值 + * @param string|null $cursor 用于分页查询的游标,字符串类型,由上一次调用返回,首次调用可不填 + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getGroupmsgTask(string $msgId, ?int $limit = null, ?string $cursor = null) + { + $data = [ + 'msgid' => $msgId, + 'limit' => $limit, + 'cursor' => $cursor, + ]; + $writableData = array_filter($data, function (string $key) use ($data) { + return !is_null($data[$key]); + }, ARRAY_FILTER_USE_KEY); + return $this->httpPostJson('cgi-bin/externalcontact/get_groupmsg_task', $writableData); + } + + /** + * 获取企业群发成员执行结果. + * + * @see https://open.work.weixin.qq.com/api/doc/90000/90135/93338#%E8%8E%B7%E5%8F%96%E4%BC%81%E4%B8%9A%E7%BE%A4%E5%8F%91%E6%88%90%E5%91%98%E6%89%A7%E8%A1%8C%E7%BB%93%E6%9E%9C + * + * @param string $msgId 群发消息的id,通过获取群发记录列表接口返回 + * @param string $userid 发送成员userid,通过获取群发成员发送任务列表接口返回 + * @param int|null $limit 返回的最大记录数,整型,最大值1000,默认值500,超过最大值时取默认值 + * @param string|null $cursor 用于分页查询的游标,字符串类型,由上一次调用返回,首次调用可不填 + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getGroupmsgSendResult(string $msgId, string $userid, ?int $limit = null, ?string $cursor = null) + { + $data = [ + 'msgid' => $msgId, + 'userid' => $userid, + 'limit' => $limit, + 'cursor' => $cursor, + ]; + $writableData = array_filter($data, function (string $key) use ($data) { + return !is_null($data[$key]); + }, ARRAY_FILTER_USE_KEY); + return $this->httpPostJson('cgi-bin/externalcontact/get_groupmsg_send_result', $writableData); + } + + /** + * 发送新客户欢迎语. + * + * @see https://work.weixin.qq.com/api/doc#90000/90135/91688 + * + * @param string $welcomeCode + * @param array $msg + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function sendWelcome(string $welcomeCode, array $msg) + { + $formattedMsg = $this->formatMessage($msg); + + $params = array_merge($formattedMsg, [ + 'welcome_code' => $welcomeCode, + ]); + + return $this->httpPostJson('cgi-bin/externalcontact/send_welcome_msg', $params); + } + + /** + * @param array $data + * + * @return array + * + * @throws InvalidArgumentException + */ + protected function formatMessage(array $data = []) + { + $params = $data; + + if (!empty($params['text'])) { + $params['text'] = $this->formatFields($params['text'], $this->textMessage); + } + + if (!empty($params['image'])) { + $params['image'] = $this->formatFields($params['image'], $this->imageMessage); + } + + if (!empty($params['link'])) { + $params['link'] = $this->formatFields($params['link'], $this->linkMessage); + } + + if (!empty($params['miniprogram'])) { + $params['miniprogram'] = $this->formatFields($params['miniprogram'], $this->miniprogramMessage); + } + + return $params; + } + + /** + * @param array $data + * @param array $default + * + * @return array + * + * @throws InvalidArgumentException + */ + protected function formatFields(array $data = [], array $default = []) + { + $params = array_merge($default, $data); + foreach ($params as $key => $value) { + if (in_array($key, $this->required, true) && empty($value) && empty($default[$key])) { + throw new InvalidArgumentException(sprintf('Attribute "%s" can not be empty!', $key)); + } + + $params[$key] = empty($value) ? $default[$key] : $value; + } + + return $params; + } +} diff --git a/vendor/overtrue/wechat/src/Work/ExternalContact/MessageTemplateClient.php b/vendor/overtrue/wechat/src/Work/ExternalContact/MessageTemplateClient.php new file mode 100644 index 0000000..92d34ca --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/ExternalContact/MessageTemplateClient.php @@ -0,0 +1,166 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\ExternalContact; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; + +/** + * Class MessageTemplateClient. + * + * @author ljyljy0211 + */ +class MessageTemplateClient extends BaseClient +{ + /** + * Required attributes. + * + * @var array + */ + protected $required = ['title', 'url', 'pic_media_id', 'appid', 'page']; + + protected $textMessage = [ + 'content' => '', + ]; + + protected $imageMessage = [ + 'media_id' => '', + 'pic_url' => '', + ]; + + protected $linkMessage = [ + 'title' => '', + 'picurl' => '', + 'desc' => '', + 'url' => '', + ]; + + protected $miniprogramMessage = [ + 'title' => '', + 'pic_media_id' => '', + 'appid' => '', + 'page' => '', + ]; + + /** + * 添加入群欢迎语素材. + * + * @see https://work.weixin.qq.com/api/doc/90000/90135/92366 + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + */ + public function create(array $msgTemplate) + { + $params = $this->formatMessage($msgTemplate); + + return $this->httpPostJson('cgi-bin/externalcontact/group_welcome_template/add', $params); + } + + /** + * 编辑入群欢迎语素材. + * + * @see https://work.weixin.qq.com/api/doc/90000/90135/92366 + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + */ + public function update(string $templateId, array $msgTemplate) + { + $params = $this->formatMessage($msgTemplate); + $params = array_merge([ + 'template_id' => $templateId, + ], $params); + return $this->httpPostJson('cgi-bin/externalcontact/group_welcome_template/edit', $params); + } + + /** + * 获取入群欢迎语素材. + * + * @see https://work.weixin.qq.com/api/doc/90000/90135/92366 + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + */ + public function get(string $templateId) + { + return $this->httpPostJson('cgi-bin/externalcontact/group_welcome_template/get', [ + 'template_id' => $templateId, + ]); + } + + /** + * 删除入群欢迎语素材. + * + * @see https://work.weixin.qq.com/api/doc/90000/90135/92366 + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + */ + public function delete(string $templateId) + { + return $this->httpPostJson('cgi-bin/externalcontact/group_welcome_template/del', [ + 'template_id' => $templateId, + ]); + } + + /** + * @throws InvalidArgumentException + * @return array + */ + protected function formatMessage(array $data = []) + { + $params = $data; + + if (!empty($params['text'])) { + $params['text'] = $this->formatFields($params['text'], $this->textMessage); + } + + if (!empty($params['image'])) { + $params['image'] = $this->formatFields($params['image'], $this->imageMessage); + } + + if (!empty($params['link'])) { + $params['link'] = $this->formatFields($params['link'], $this->linkMessage); + } + + if (!empty($params['miniprogram'])) { + $params['miniprogram'] = $this->formatFields($params['miniprogram'], $this->miniprogramMessage); + } + + return $params; + } + + /** + * @throws InvalidArgumentException + * @return array + */ + protected function formatFields(array $data = [], array $default = []) + { + $params = array_merge($default, $data); + foreach ($params as $key => $value) { + if (in_array($key, $this->required, true) && empty($value) && empty($default[$key])) { + throw new InvalidArgumentException(sprintf('Attribute "%s" can not be empty!', $key)); + } + + $params[$key] = empty($value) ? $default[$key] : $value; + } + + return $params; + } +} diff --git a/vendor/overtrue/wechat/src/Work/ExternalContact/MomentClient.php b/vendor/overtrue/wechat/src/Work/ExternalContact/MomentClient.php new file mode 100644 index 0000000..72abb42 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/ExternalContact/MomentClient.php @@ -0,0 +1,136 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\ExternalContact; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class MomentClient. + * + * @author 读心印 + */ +class MomentClient extends BaseClient +{ + /** + * 获取企业全部的发表列表. + * + * @see https://open.work.weixin.qq.com/api/doc/90000/90135/93333#获取企业全部的发表列表 + * + * @param array $params + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException|\GuzzleHttp\Exception\GuzzleException + */ + public function list(array $params) + { + return $this->httpPostJson('cgi-bin/externalcontact/get_moment_list', $params); + } + + /** + * 获取客户朋友圈企业发表的列表. + * + * @see https://open.work.weixin.qq.com/api/doc/90000/90135/93333#获取客户朋友圈企业发表的列表 + * + * @param string $momentId + * @param string $cursor + * @param int $limit + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException|\GuzzleHttp\Exception\GuzzleException + */ + public function getTasks(string $momentId, string $cursor, int $limit) + { + $params = [ + 'moment_id' => $momentId, + 'cursor' => $cursor, + 'limit' => $limit + ]; + + return $this->httpPostJson('cgi-bin/externalcontact/get_moment_task', $params); + } + + /** + * 获取客户朋友圈发表时选择的可见范围. + * + * @see https://open.work.weixin.qq.com/api/doc/90000/90135/93333#获取客户朋友圈发表时选择的可见范围 + * + * @param string $momentId + * @param string $userId + * @param string $cursor + * @param int $limit + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException|\GuzzleHttp\Exception\GuzzleException + */ + public function getCustomers(string $momentId, string $userId, string $cursor, int $limit) + { + $params = [ + 'moment_id' => $momentId, + 'userid' => $userId, + 'cursor' => $cursor, + 'limit' => $limit + ]; + + return $this->httpPostJson('cgi-bin/externalcontact/get_moment_customer_list', $params); + } + + /** + * 获取客户朋友圈发表后的可见客户列表. + * + * @see https://open.work.weixin.qq.com/api/doc/90000/90135/93333#获取客户朋友圈发表后的可见客户列表 + * + * @param string $momentId + * @param string $userId + * @param string $cursor + * @param int $limit + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException|\GuzzleHttp\Exception\GuzzleException + */ + public function getSendResult(string $momentId, string $userId, string $cursor, int $limit) + { + $params = [ + 'moment_id' => $momentId, + 'userid' => $userId, + 'cursor' => $cursor, + 'limit' => $limit + ]; + + return $this->httpPostJson('cgi-bin/externalcontact/get_moment_send_result', $params); + } + + /** + * 获取客户朋友圈的互动数据. + * + * @see https://open.work.weixin.qq.com/api/doc/90000/90135/93333#获取客户朋友圈的互动数据 + * + * @param string $momentId + * @param string $userId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException|\GuzzleHttp\Exception\GuzzleException + */ + public function getComments(string $momentId, string $userId) + { + $params = [ + 'moment_id' => $momentId, + 'userid' => $userId + ]; + + return $this->httpPostJson('cgi-bin/externalcontact/get_moment_comments', $params); + } +} diff --git a/vendor/overtrue/wechat/src/Work/ExternalContact/SchoolClient.php b/vendor/overtrue/wechat/src/Work/ExternalContact/SchoolClient.php new file mode 100644 index 0000000..e0e1faf --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/ExternalContact/SchoolClient.php @@ -0,0 +1,441 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\ExternalContact; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author MillsGuo + */ +class SchoolClient extends BaseClient +{ + /** + * 创建部门 + * @see https://work.weixin.qq.com/api/doc/90000/90135/92340 + * @param string $name + * @param int $parentId + * @param int $type + * @param int $standardGrade + * @param int $registerYear + * @param int $order + * @param array $departmentAdmins [['userid':'139','type':1],['userid':'1399','type':2]] + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function createDepartment(string $name, int $parentId, int $type, int $standardGrade, int $registerYear, int $order, array $departmentAdmins) + { + $params = [ + 'name' => $name, + 'parentid' => $parentId, + 'type' => $type, + 'standard_grade' => $standardGrade, + 'register_year' => $registerYear, + 'order' => $order, + 'department_admins' => $departmentAdmins + ]; + + return $this->httpPostJson('cgi-bin/school/department/create', $params); + } + + /** + * 更新部门 + * @see https://work.weixin.qq.com/api/doc/90000/90135/92341 + * @param int $id + * @param string $name + * @param int $parentId + * @param int $type + * @param int $standardGrade + * @param int $registerYear + * @param int $order + * @param array $departmentAdmins [['op':0,'userid':'139','type':1],['op':1,'userid':'1399','type':2]] OP=0表示新增或更新,OP=1表示删除管理员 + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function updateDepartment(int $id, string $name, int $parentId, int $type, int $standardGrade, int $registerYear, int $order, array $departmentAdmins) + { + $params = [ + 'id' => $id, + 'name' => $name, + 'parentid' => $parentId, + 'type' => $type, + 'standard_grade' => $standardGrade, + 'register_year' => $registerYear, + 'order' => $order, + 'department_admins' => $departmentAdmins + ]; + $params = $this->filterNullValue($params); + + return $this->httpPostJson('cgi-bin/school/department/update', $params); + } + + /** + * 删除部门 + * @see https://work.weixin.qq.com/api/doc/90000/90135/92342 + * @param int $id + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function deleteDepartment(int $id) + { + return $this->httpGet('cgi-bin/school/department/delete', [ + 'id' => $id + ]); + } + + /** + * 获取部门列表 + * @see https://work.weixin.qq.com/api/doc/90000/90135/92343 + * @param int $id 如果ID为0,则获取全量组织架构 + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function getDepartments(int $id) + { + if ($id > 0) { + $params = [ + 'id' => $id + ]; + } else { + $params = []; + } + + return $this->httpGet('cgi-bin/school/department/list', $params); + } + + /** + * 创建学生 + * @see https://work.weixin.qq.com/api/doc/90000/90135/92325 + * @param string $userId + * @param string $name + * @param array $department + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function createStudent(string $userId, string $name, array $department) + { + $params = [ + 'student_userid' => $userId, + 'name' => $name, + 'department' => $department + ]; + + return $this->httpPostJson('cgi-bin/school/user/create_student', $params); + } + + /** + * 删除学生 + * @see https://work.weixin.qq.com/api/doc/90000/90135/92326 + * @param string $userId + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function deleteStudent(string $userId) + { + return $this->httpGet('cgi-bin/school/user/delete_student', [ + 'userid' => $userId + ]); + } + + /** + * 更新学生 + * @see https://work.weixin.qq.com/api/doc/90000/90135/92327 + * @param string $userId + * @param string $name + * @param string $newUserId + * @param array $department + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function updateStudent(string $userId, string $name, string $newUserId, array $department) + { + $params = [ + 'student_userid' => $userId + ]; + if (!empty($name)) { + $params['name'] = $name; + } + if (!empty($newUserId)) { + $params['new_student_userid'] = $newUserId; + } + if (!empty($department)) { + $params['department'] = $department; + } + + return $this->httpPostJson('cgi-bin/school/user/update_student', $params); + } + + /** + * 批量创建学生,学生最多100个 + * @see https://work.weixin.qq.com/api/doc/90000/90135/92328 + * @param array $students 学生格式:[[student_userid:'','name':'','department':[1,2]],['student_userid':'','name':'','department':[1,2]]] + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function batchCreateStudents(array $students) + { + $params = [ + 'students' => $students + ]; + + return $this->httpPostJson('cgi-bin/school/user/batch_create_student', $params); + } + + /** + * 批量删除学生,每次最多100个学生 + * @see https://work.weixin.qq.com/api/doc/90000/90135/92329 + * @param array $useridList 学生USERID,格式:['zhangsan','lisi'] + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function batchDeleteStudents(array $useridList) + { + return $this->httpPostJson('cgi-bin/school/user/batch_delete_student', [ + 'useridlist' => $useridList + ]); + } + + /** + * 批量更新学生,每次最多100个 + * @see https://open.work.weixin.qq.com/api/doc/90001/90143/92042 + * @param array $students 格式:[['student_userid':'lisi','new_student_userid':'lisi2','name':'','department':[1,2]],.....] + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function batchUpdateStudents(array $students) + { + return $this->httpPostJson('cgi-bin/school/user/batch_update_student', [ + 'students' => $students + ]); + } + + /** + * 创建家长 + * @see https://open.work.weixin.qq.com/api/doc/90001/90143/92077 + * @param string $userId + * @param string $mobile + * @param bool $toInvite + * @param array $children + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function createParent(string $userId, string $mobile, bool $toInvite, array $children) + { + $params = [ + 'parent_userid' => $userId, + 'mobile' => $mobile, + 'to_invite' => $toInvite, + 'children' => $children + ]; + + return $this->httpPostJson('cgi-bin/school/user/create_parent', $params); + } + + /** + * 删除家长 + * @see https://open.work.weixin.qq.com/api/doc/90001/90143/92079 + * @param string $userId + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function deleteParent(string $userId) + { + return $this->httpPostJson('cgi-bin/school/user/delete_parent', [ + 'userid' => $userId + ]); + } + + /** + * 更新家长 + * @see https://open.work.weixin.qq.com/api/doc/90001/90143/92081 + * @param string $userId + * @param string $mobile + * @param string $newUserId + * @param array $children 格式:[['student_userid':'','relation':''],[]] + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function updateParent(string $userId, string $mobile, string $newUserId, array $children) + { + $params = [ + 'parent_userid' => $userId + ]; + if (!empty($newUserId)) { + $params['new_parent_userid'] = $newUserId; + } + if (!empty($mobile)) { + $params['mobile'] = $mobile; + } + if (!empty($children)) { + $params['children'] = $children; + } + + return $this->httpPostJson('cgi-bin/school/user/update_parent', $params); + } + + /** + * 批量创建家长 每次最多100个 + * @see https://open.work.weixin.qq.com/api/doc/90001/90143/92078 + * @param array $parents [['parent_userid':'','mobile':'','to_invite':true,'children':['student_userid':'','relation':'']],.....] + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function batchCreateParents(array $parents) + { + return $this->httpPostJson('cgi-bin/school/user/batch_create_parent', [ + 'parents' => $parents + ]); + } + + /** + * 批量删除家长,每次最多100个 + * @see https://open.work.weixin.qq.com/api/doc/90001/90143/92080 + * @param array $userIdList 格式:['chang','lisi'] + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function batchDeleteParents(array $userIdList) + { + return $this->httpPostJson('cgi-bin/school/user/batch_delete_parent', [ + 'useridlist' => $userIdList + ]); + } + + /** + * 批量更新家长,每次最多100个 + * @see https://open.work.weixin.qq.com/api/doc/90001/90143/92082 + * @param array $parents 格式:[['parent_userid':'','new_parent_userid':'','mobile':'','children':[['student_userid':'','relation':''],...]],.....] + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function batchUpdateParents(array $parents) + { + return $this->httpPostJson('cgi-bin/school/user/batch_update_parent', [ + 'parents' => $parents + ]); + } + + /** + * 读取学生或家长 + * @see https://open.work.weixin.qq.com/api/doc/90001/90143/92038 + * @param string $userId 学生或家长的userid + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function getUser(string $userId) + { + return $this->httpGet('cgi-bin/school/user/get', [ + 'userid' => $userId + ]); + } + + /** + * 获取部门成员详情 + * @see https://open.work.weixin.qq.com/api/doc/90001/90143/92038 + * @param int $departmentId + * @param bool $fetchChild + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function getStudents(int $departmentId, bool $fetchChild) + { + $params = [ + 'department_id' => $departmentId + ]; + if ($fetchChild) { + $params['fetch_child'] = 1; + } else { + $params['fetch_child'] = 0; + } + + return $this->httpGet('cgi-bin/school/user/list', $params); + } + + /** + * 获取学校通知二维码 + * @see https://open.work.weixin.qq.com/api/doc/90001/90143/92197 + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function getSubscribeQrCode() + { + return $this->httpGet('cgi-bin/externalcontact/get_subscribe_qr_code'); + } + + /** + * 设置关注学校通知的模式 + * @see https://open.work.weixin.qq.com/api/doc/90001/90143/92290 + * @param int $mode 关注模式,1可扫码填写资料加入,2禁止扫码填写资料加入 + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function setSubscribeMode(int $mode) + { + return $this->httpPostJson('cgi-bin/externalcontact/set_subscribe_mode', [ + 'subscribe_mode' => $mode + ]); + } + + /** + * 获取关注学校通知的模式 + * @see https://open.work.weixin.qq.com/api/doc/90001/90143/92290 + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function getSubscribeMode() + { + return $this->httpGet('cgi-bin/externalcontact/get_subscribe_mode'); + } + + /** + * 设置【老师可查看班级】的模式 + * @see https://open.work.weixin.qq.com/api/doc/90001/90143/92652 + * @param int $mode + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function setTeacherViewMode(int $mode) + { + return $this->httpPostJson('cgi-bin/school/set_teacher_view_mode', [ + 'view_mode' => $mode + ]); + } + + /** + * 获取【老师可查看班级】的模式 + * @see https://open.work.weixin.qq.com/api/doc/90001/90143/92652 + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function getTeacherViewMode() + { + return $this->httpGet('cgi-bin/school/get_teacher_view_mode'); + } + + /** + * 过滤数组中值为NULL的键 + * @param array $data + * @return array + */ + protected function filterNullValue(array $data) + { + $returnData = []; + foreach ($data as $key => $value) { + if ($value !== null) { + $returnData[$key] = trim($value); + } + } + + return $returnData; + } +} diff --git a/vendor/overtrue/wechat/src/Work/ExternalContact/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/ExternalContact/ServiceProvider.php new file mode 100644 index 0000000..a8e8d5e --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/ExternalContact/ServiceProvider.php @@ -0,0 +1,57 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\ExternalContact; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author mingyoung + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['external_contact'] = function ($app) { + return new Client($app); + }; + + $app['contact_way'] = function ($app) { + return new ContactWayClient($app); + }; + + $app['external_contact_statistics'] = function ($app) { + return new StatisticsClient($app); + }; + + $app['external_contact_message'] = function ($app) { + return new MessageClient($app); + }; + + $app['school'] = function ($app) { + return new SchoolClient($app); + }; + + $app['external_contact_moment'] = function ($app) { + return new MomentClient($app); + }; + + $app['external_contact_message_template'] = function ($app) { + return new MessageTemplateClient($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Work/ExternalContact/StatisticsClient.php b/vendor/overtrue/wechat/src/Work/ExternalContact/StatisticsClient.php new file mode 100644 index 0000000..7cfa4d0 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/ExternalContact/StatisticsClient.php @@ -0,0 +1,96 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\ExternalContact; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class StatisticsClient. + * + * @author 读心印 + */ +class StatisticsClient extends BaseClient +{ + /** + * 获取「联系客户统计」数据. + * + * @see https://work.weixin.qq.com/api/doc/90000/90135/92132 + * + * @param array $userIds + * @param string $from + * @param string $to + * @param array $partyIds + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + + public function userBehavior(array $userIds, string $from, string $to, array $partyIds = []) + { + $params = [ + 'userid' => $userIds, + 'partyid' => $partyIds, + 'start_time' => $from, + 'end_time' => $to, + ]; + return $this->httpPostJson('cgi-bin/externalcontact/get_user_behavior_data', $params); + } + + + /** + * 获取「群聊数据统计」数据. (按群主聚合的方式) + * + * @see https://work.weixin.qq.com/api/doc/90000/90135/92133 + * + * @param array $params + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + + public function groupChatStatistic(array $params) + { + return $this->httpPostJson('cgi-bin/externalcontact/groupchat/statistic', $params); + } + + + /** + * 获取「群聊数据统计」数据. (按自然日聚合的方式) + * + * @see https://work.weixin.qq.com/api/doc/90000/90135/92133 + * + * @param int $dayBeginTime + * @param int $dayEndTime + * @param array $userIds + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + + public function groupChatStatisticGroupByDay(int $dayBeginTime, int $dayEndTime, array $userIds = []) + { + $params = [ + 'day_begin_time' => $dayBeginTime, + 'day_end_time' => $dayEndTime, + 'owner_filter' => [ + 'userid_list' => $userIds + ] + ]; + return $this->httpPostJson('cgi-bin/externalcontact/groupchat/statistic_group_by_day', $params); + } +} diff --git a/vendor/overtrue/wechat/src/Work/GroupRobot/Client.php b/vendor/overtrue/wechat/src/Work/GroupRobot/Client.php new file mode 100644 index 0000000..c2e0aa0 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/GroupRobot/Client.php @@ -0,0 +1,51 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\GroupRobot; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Work\GroupRobot\Messages\Message; + +/** + * Class Client. + * + * @author her-cat + */ +class Client extends BaseClient +{ + /** + * @param string|Message $message + * + * @return Messenger + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + public function message($message) + { + return (new Messenger($this))->message($message); + } + + /** + * @param string $key + * @param array $message + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function send(string $key, array $message) + { + $this->accessToken = null; + + return $this->httpPostJson('cgi-bin/webhook/send', $message, ['key' => $key]); + } +} diff --git a/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Image.php b/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Image.php new file mode 100644 index 0000000..14beb81 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Image.php @@ -0,0 +1,41 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\GroupRobot\Messages; + +/** + * Class Image. + * + * @author her-cat + */ +class Image extends Message +{ + /** + * @var string + */ + protected $type = 'image'; + + /** + * @var array + */ + protected $properties = ['base64', 'md5']; + + /** + * Image constructor. + * + * @param string $base64 + * @param string $md5 + */ + public function __construct(string $base64, string $md5) + { + parent::__construct(compact('base64', 'md5')); + } +} diff --git a/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Markdown.php b/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Markdown.php new file mode 100644 index 0000000..dfe6980 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Markdown.php @@ -0,0 +1,40 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\GroupRobot\Messages; + +/** + * Class Markdown. + * + * @author her-cat + */ +class Markdown extends Message +{ + /** + * @var string + */ + protected $type = 'markdown'; + + /** + * @var array + */ + protected $properties = ['content']; + + /** + * Markdown constructor. + * + * @param string $content + */ + public function __construct(string $content) + { + parent::__construct(compact('content')); + } +} diff --git a/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Message.php b/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Message.php new file mode 100644 index 0000000..50201b9 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Message.php @@ -0,0 +1,23 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\GroupRobot\Messages; + +use EasyWeChat\Kernel\Messages\Message as BaseMessage; + +/** + * Class Message. + * + * @author her-cat + */ +class Message extends BaseMessage +{ +} diff --git a/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/News.php b/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/News.php new file mode 100644 index 0000000..835023e --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/News.php @@ -0,0 +1,55 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\GroupRobot\Messages; + +/** + * Class News. + * + * @author her-cat + */ +class News extends Message +{ + /** + * @var string + */ + protected $type = 'news'; + + /** + * @var array + */ + protected $properties = ['items']; + + /** + * News constructor. + * + * @param array $items + */ + public function __construct(array $items = []) + { + parent::__construct(compact('items')); + } + + /** + * @param array $data + * @param array $aliases + * + * @return array + */ + public function propertiesToArray(array $data, array $aliases = []): array + { + return ['articles' => array_map(function ($item) { + if ($item instanceof NewsItem) { + return $item->toJsonArray(); + } + }, $this->get('items'))]; + } +} diff --git a/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/NewsItem.php b/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/NewsItem.php new file mode 100644 index 0000000..2d206de --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/NewsItem.php @@ -0,0 +1,45 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\GroupRobot\Messages; + +/** + * Class NewsItem. + * + * @author her-cat + */ +class NewsItem extends Message +{ + /** + * @var string + */ + protected $type = 'news'; + + /** + * @var array + */ + protected $properties = [ + 'title', + 'description', + 'url', + 'image', + ]; + + public function toJsonArray() + { + return [ + 'title' => $this->get('title'), + 'description' => $this->get('description'), + 'url' => $this->get('url'), + 'picurl' => $this->get('image'), + ]; + } +} diff --git a/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Text.php b/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Text.php new file mode 100644 index 0000000..a1e6dda --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/GroupRobot/Messages/Text.php @@ -0,0 +1,70 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\GroupRobot\Messages; + +/** + * Class Text. + * + * @author her-cat + */ +class Text extends Message +{ + /** + * @var string + */ + protected $type = 'text'; + + /** + * @var array + */ + protected $properties = ['content', 'mentioned_list', 'mentioned_mobile_list']; + + /** + * Text constructor. + * + * @param string $content + * @param string|array $userIds + * @param string|array $mobiles + */ + public function __construct(string $content, $userIds = [], $mobiles = []) + { + parent::__construct([ + 'content' => $content, + 'mentioned_list' => (array) $userIds, + 'mentioned_mobile_list' => (array) $mobiles, + ]); + } + + /** + * @param array $userIds + * + * @return Text + */ + public function mention($userIds) + { + $this->set('mentioned_list', (array) $userIds); + + return $this; + } + + /** + * @param array $mobiles + * + * @return Text + */ + public function mentionByMobile($mobiles) + { + $this->set('mentioned_mobile_list', (array) $mobiles); + + return $this; + } +} diff --git a/vendor/overtrue/wechat/src/Work/GroupRobot/Messenger.php b/vendor/overtrue/wechat/src/Work/GroupRobot/Messenger.php new file mode 100644 index 0000000..573296a --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/GroupRobot/Messenger.php @@ -0,0 +1,129 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\GroupRobot; + +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; +use EasyWeChat\Kernel\Exceptions\InvalidConfigException; +use EasyWeChat\Kernel\Exceptions\RuntimeException; +use EasyWeChat\Work\GroupRobot\Messages\Message; +use EasyWeChat\Work\GroupRobot\Messages\Text; + +/** + * Class Messenger. + * + * @author her-cat + */ +class Messenger +{ + /** + * @var Client + */ + protected $client; + + /** + * @var Message|null + */ + protected $message; + + /** + * @var string|null + */ + protected $groupKey; + + /** + * Messenger constructor. + * + * @param Client $client + */ + public function __construct(Client $client) + { + $this->client = $client; + } + + /** + * @param string|Message $message + * + * @return Messenger + * + * @throws InvalidArgumentException + */ + public function message($message) + { + if (is_string($message) || is_numeric($message)) { + $message = new Text($message); + } + + if (!($message instanceof Message)) { + throw new InvalidArgumentException('Invalid message.'); + } + + $this->message = $message; + + return $this; + } + + /** + * @param string $groupKey + * + * @return Messenger + */ + public function toGroup(string $groupKey) + { + $this->groupKey = $groupKey; + + return $this; + } + + /** + * @param string|Message|null $message + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws RuntimeException + * @throws InvalidArgumentException + * @throws InvalidConfigException + */ + public function send($message = null) + { + if ($message) { + $this->message($message); + } + + if (empty($this->message)) { + throw new RuntimeException('No message to send.'); + } + + if (is_null($this->groupKey)) { + throw new RuntimeException('No group key specified.'); + } + + $message = $this->message->transformForJsonRequest(); + + return $this->client->send($this->groupKey, $message); + } + + /** + * @param string $property + * + * @return mixed + * + * @throws InvalidArgumentException + */ + public function __get($property) + { + if (property_exists($this, $property)) { + return $this->$property; + } + + throw new InvalidArgumentException(sprintf('No property named "%s"', $property)); + } +} diff --git a/vendor/overtrue/wechat/src/Work/GroupRobot/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/GroupRobot/ServiceProvider.php new file mode 100644 index 0000000..d72d630 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/GroupRobot/ServiceProvider.php @@ -0,0 +1,37 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\GroupRobot; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author her-cat + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc} + */ + public function register(Container $app) + { + $app['group_robot'] = function ($app) { + return new Client($app); + }; + + $app['group_robot_messenger'] = function ($app) { + return new Messenger($app['group_robot']); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Work/Invoice/Client.php b/vendor/overtrue/wechat/src/Work/Invoice/Client.php new file mode 100644 index 0000000..edbd34b --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Invoice/Client.php @@ -0,0 +1,100 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Invoice; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + /** + * @param string $cardId + * @param string $encryptCode + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get(string $cardId, string $encryptCode) + { + $params = [ + 'card_id' => $cardId, + 'encrypt_code' => $encryptCode, + ]; + + return $this->httpPostJson('cgi-bin/card/invoice/reimburse/getinvoiceinfo', $params); + } + + /** + * @param array $invoices + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function select(array $invoices) + { + $params = [ + 'item_list' => $invoices, + ]; + + return $this->httpPostJson('cgi-bin/card/invoice/reimburse/getinvoiceinfobatch', $params); + } + + /** + * @param string $cardId + * @param string $encryptCode + * @param string $status + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(string $cardId, string $encryptCode, string $status) + { + $params = [ + 'card_id' => $cardId, + 'encrypt_code' => $encryptCode, + 'reimburse_status' => $status, + ]; + + return $this->httpPostJson('cgi-bin/card/invoice/reimburse/updateinvoicestatus', $params); + } + + /** + * @param array $invoices + * @param string $openid + * @param string $status + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function batchUpdate(array $invoices, string $openid, string $status) + { + $params = [ + 'openid' => $openid, + 'reimburse_status' => $status, + 'invoice_list' => $invoices, + ]; + + return $this->httpPostJson('cgi-bin/card/invoice/reimburse/updatestatusbatch', $params); + } +} diff --git a/vendor/overtrue/wechat/src/Work/Invoice/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/Invoice/ServiceProvider.php new file mode 100644 index 0000000..3259d80 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Invoice/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Invoice; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author mingyoung + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['invoice'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Work/Jssdk/Client.php b/vendor/overtrue/wechat/src/Work/Jssdk/Client.php new file mode 100644 index 0000000..f146c3c --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Jssdk/Client.php @@ -0,0 +1,206 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Jssdk; + +use EasyWeChat\BasicService\Jssdk\Client as BaseClient; +use EasyWeChat\Kernel\Contracts\AccessTokenInterface; +use EasyWeChat\Kernel\Exceptions\RuntimeException; +use EasyWeChat\Kernel\ServiceContainer; +use EasyWeChat\Kernel\Support; + +/** + * Class Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + public function __construct(ServiceContainer $app, AccessTokenInterface $accessToken = null) + { + parent::__construct($app, $accessToken); + + $this->ticketEndpoint = \rtrim($app->config->get('http.base_uri'), '/').'/cgi-bin/get_jsapi_ticket'; + } + + /** + * @return string + */ + protected function getAppId() + { + return $this->app['config']->get('corp_id'); + } + + /** + * Return jsapi agent config as a PHP array. + * + * @param array $apis + * @param int|string $agentId + * @param bool $debug + * @param bool $beta + * @param array $openTagList + * @param string|null $url + * + * @return array|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \GuzzleHttp\Exception\GuzzleException + * @throws \Psr\SimpleCache\InvalidArgumentException + */ + public function getAgentConfigArray( + array $apis, + $agentId, + bool $debug = false, + bool $beta = false, + array $openTagList = [], + string $url = null + ) { + return $this->buildAgentConfig($apis, $agentId, $debug, $beta, false, $openTagList, $url); + } + + /** + * Get agent config json for jsapi. + * + * @param array $jsApiList + * @param int|string $agentId + * @param bool $debug + * @param bool $beta + * @param bool $json + * @param array $openTagList + * @param string|null $url + * + * @return array|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \GuzzleHttp\Exception\GuzzleException + * @throws \Psr\SimpleCache\InvalidArgumentException + */ + public function buildAgentConfig( + array $jsApiList, + $agentId, + bool $debug = false, + bool $beta = false, + bool $json = true, + array $openTagList = [], + string $url = null + ) { + $config = array_merge(compact('debug', 'beta', 'jsApiList', 'openTagList'), $this->agentConfigSignature($agentId, $url)); + + return $json ? json_encode($config) : $config; + } + + /** + * @param int|string $agentId + * @param string|null $url + * @param string|null $nonce + * @param null $timestamp + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \GuzzleHttp\Exception\GuzzleException + * @throws \Psr\SimpleCache\InvalidArgumentException + */ + protected function agentConfigSignature($agentId, string $url = null, string $nonce = null, $timestamp = null): array + { + $url = $url ?: $this->getUrl(); + $nonce = $nonce ?: Support\Str::quickRandom(10); + $timestamp = $timestamp ?: time(); + + return [ + 'corpid' => $this->getAppId(), + 'agentid' => $agentId, + 'nonceStr' => $nonce, + 'timestamp' => $timestamp, + 'url' => $url, + 'signature' => $this->getTicketSignature($this->getAgentTicket($agentId)['ticket'], $nonce, $timestamp, $url), + ]; + } + + /** + * Get js ticket. + * + * @param bool $refresh + * @param string $type + * + * @return array + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \GuzzleHttp\Exception\GuzzleException + * @throws \Psr\SimpleCache\InvalidArgumentException + */ + public function getTicket(bool $refresh = false, string $type = 'config'): array + { + $cacheKey = sprintf('easywechat.work.jssdk.ticket.%s.%s', $type, $this->getAppId()); + + if (!$refresh && $this->getCache()->has($cacheKey)) { + return $this->getCache()->get($cacheKey); + } + + /** @var array $result */ + $result = $this->castResponseToType( + $this->requestRaw($this->ticketEndpoint, 'GET'), + 'array' + ); + + $this->getCache()->set($cacheKey, $result, $result['expires_in'] - 500); + + if (!$this->getCache()->has($cacheKey)) { + throw new RuntimeException('Failed to cache jssdk ticket.'); + } + + return $result; + } + + /** + * @param int $agentId + * @param bool $refresh + * @param string $type + * + * @return array|\EasyWeChat\Kernel\Support\Collection|mixed|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \GuzzleHttp\Exception\GuzzleException + * @throws \Psr\SimpleCache\InvalidArgumentException + */ + public function getAgentTicket($agentId, bool $refresh = false, string $type = 'agent_config') + { + $cacheKey = sprintf('easywechat.work.jssdk.ticket.%s.%s.%s', $agentId, $type, $this->getAppId()); + + if (!$refresh && $this->getCache()->has($cacheKey)) { + return $this->getCache()->get($cacheKey); + } + + /** @var array $result */ + $result = $this->castResponseToType( + $this->requestRaw('cgi-bin/ticket/get', 'GET', ['query' => ['type' => $type]]), + 'array' + ); + + $this->getCache()->set($cacheKey, $result, $result['expires_in'] - 500); + + if (!$this->getCache()->has($cacheKey)) { + throw new RuntimeException('Failed to cache jssdk ticket.'); + } + + return $result; + } +} diff --git a/vendor/overtrue/wechat/src/Work/Jssdk/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/Jssdk/ServiceProvider.php new file mode 100644 index 0000000..efb509c --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Jssdk/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Jssdk; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author mingyoung + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['jssdk'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Work/Kf/AccountClient.php b/vendor/overtrue/wechat/src/Work/Kf/AccountClient.php new file mode 100644 index 0000000..afee269 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Kf/AccountClient.php @@ -0,0 +1,131 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Kf; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class AccountClient. + * + * @package EasyWeChat\Work\Kf + * + * @author 读心印 + */ +class AccountClient extends BaseClient +{ + /** + * 添加客服帐号. + * + * @see https://open.work.weixin.qq.com/api/doc/90000/90135/94662 + * + * @param string $name + * @param string $mediaId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function add(string $name, string $mediaId) + { + $params = [ + 'name' => $name, + 'media_id' => $mediaId, + ]; + + return $this->httpPostJson('cgi-bin/kf/account/add', $params); + } + + /** + * 修改客服帐号. + * + * @see https://open.work.weixin.qq.com/api/doc/90000/90135/94664 + * + * @param string $openKfId + * @param string $name + * @param string $mediaId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(string $openKfId, string $name, string $mediaId) + { + $params = [ + 'open_kfid' => $openKfId, + 'name' => $name, + 'media_id' => $mediaId, + ]; + + return $this->httpPostJson('cgi-bin/kf/account/update', $params); + } + + /** + * 删除客服帐号. + * + * @see https://open.work.weixin.qq.com/api/doc/90000/90135/94663 + * + * @param string $openKfId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function del(string $openKfId) + { + $params = [ + 'open_kfid' => $openKfId + ]; + + return $this->httpPostJson('cgi-bin/kf/account/del', $params); + } + + /** + * 获取客服帐号列表. + * + * @see https://open.work.weixin.qq.com/api/doc/90000/90135/94661 + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function list() + { + return $this->httpGet('cgi-bin/kf/account/list'); + } + + /** + * 获取客服帐号链接. + * + * @see https://open.work.weixin.qq.com/api/doc/90000/90135/94665 + * + * @param string $openKfId + * @param string $scene + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getAccountLink(string $openKfId, string $scene) + { + $params = [ + 'open_kfid' => $openKfId, + 'scene' => $scene + ]; + + return $this->httpPostJson('cgi-bin/kf/add_contact_way', $params); + } +} diff --git a/vendor/overtrue/wechat/src/Work/Kf/MessageClient.php b/vendor/overtrue/wechat/src/Work/Kf/MessageClient.php new file mode 100644 index 0000000..4246879 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Kf/MessageClient.php @@ -0,0 +1,133 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Kf; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class MessageClient. + * + * @package EasyWeChat\Work\Kf + * + * @author 读心印 + */ +class MessageClient extends BaseClient +{ + /** + * 获取会话状态. + * + * @see https://open.work.weixin.qq.com/api/doc/90000/90135/94669 + * + * @param string $openKfId + * @param string $externalUserId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function state(string $openKfId, string $externalUserId) + { + $params = [ + 'open_kfid' => $openKfId, + 'external_userid' => $externalUserId + ]; + + return $this->httpPostJson('cgi-bin/kf/service_state/get', $params); + } + + /** + * 变更会话状态. + * + * @see https://open.work.weixin.qq.com/api/doc/90000/90135/94669 + * + * @param string $openKfId + * @param string $externalUserId + * @param int $serviceState + * @param string $serviceUserId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function updateState(string $openKfId, string $externalUserId, int $serviceState, string $serviceUserId) + { + $params = [ + 'open_kfid' => $openKfId, + 'external_userid' => $externalUserId, + 'service_state' => $serviceState, + 'servicer_userid' => $serviceUserId + ]; + + return $this->httpPostJson('cgi-bin/kf/service_state/trans', $params); + } + + /** + * 读取消息. + * + * @see https://open.work.weixin.qq.com/api/doc/90000/90135/94670 + * + * @param string $cursor + * @param string $token + * @param int $limit + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function sync(string $cursor, string $token, int $limit) + { + $params = [ + 'cursor' => $cursor, + 'token' => $token, + 'limit' => $limit + ]; + + return $this->httpPostJson('cgi-bin/kf/sync_msg', $params); + } + + /** + * 发送消息. + * + * @see https://open.work.weixin.qq.com/api/doc/90000/90135/94677 + * + * @param array $params + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function send(array $params) + { + return $this->httpPostJson('cgi-bin/kf/send_msg', $params); + } + + /** + * 发送事件响应消息. + * + * @see https://open.work.weixin.qq.com/api/doc/90000/90135/94677 + * + * @param array $params + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function event(array $params) + { + return $this->httpPostJson('cgi-bin/kf/send_msg_on_event', $params); + } +} diff --git a/vendor/overtrue/wechat/src/Work/Kf/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/Kf/ServiceProvider.php new file mode 100644 index 0000000..5bd3b9f --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Kf/ServiceProvider.php @@ -0,0 +1,43 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Kf; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @package EasyWeChat\Work\Kf + * + * @author 读心印 + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['kf_account'] = function ($app) { + return new AccountClient($app); + }; + + $app['kf_servicer'] = function ($app) { + return new ServicerClient($app); + }; + + $app['kf_message'] = function ($app) { + return new MessageClient($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Work/Kf/ServicerClient.php b/vendor/overtrue/wechat/src/Work/Kf/ServicerClient.php new file mode 100644 index 0000000..90cba0b --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Kf/ServicerClient.php @@ -0,0 +1,91 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Kf; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class ServicerClient. + * + * @package EasyWeChat\Work\Kf + * + * @author 读心印 + */ +class ServicerClient extends BaseClient +{ + /** + * 添加接待人员. + * + * @see https://open.work.weixin.qq.com/api/doc/90000/90135/94646 + * + * @param string $openKfId + * @param array $userIds + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function add(string $openKfId, array $userIds) + { + $params = [ + 'open_kfid' => $openKfId, + 'userid_list' => $userIds + ]; + + return $this->httpPostJson('cgi-bin/kf/servicer/add', $params); + } + + /** + * 删除接待人员. + * + * @see https://open.work.weixin.qq.com/api/doc/90000/90135/94647 + * + * @param string $openKfId + * @param array $userIds + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function del(string $openKfId, array $userIds) + { + $params = [ + 'open_kfid' => $openKfId, + 'userid_list' => $userIds + ]; + + return $this->httpPostJson('cgi-bin/kf/servicer/del', $params); + } + + /** + * 获取接待人员列表. + * + * @see https://open.work.weixin.qq.com/api/doc/90000/90135/94645 + * + * @param string $openKfId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function list(string $openKfId) + { + $params = [ + 'open_kfid' => $openKfId + ]; + + return $this->httpGet('cgi-bin/kf/servicer/list', $params); + } +} diff --git a/vendor/overtrue/wechat/src/Work/Live/Client.php b/vendor/overtrue/wechat/src/Work/Live/Client.php new file mode 100644 index 0000000..4861731 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Live/Client.php @@ -0,0 +1,84 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Live; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author arthasking + */ +class Client extends BaseClient +{ + /** + * 获取成员直播ID列表 + * + * @see https://work.weixin.qq.com/api/doc/90000/90135/92735 + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getUserLivingId(string $userId, int $beginTime, int $endTime, string $nextKey = '0', int $limit = 100) + { + $params = [ + 'userid' => $userId, + 'begin_time' => $beginTime, + 'end_time' => $endTime, + 'next_key' => $nextKey, + 'limit' => $limit + ]; + + return $this->httpPostJson('cgi-bin/living/get_user_livingid', $params); + } + + /** + * 获取直播详情 + * + * @see https://work.weixin.qq.com/api/doc/90000/90135/92734 + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getLiving(string $livingId) + { + $params = [ + 'livingid' => $livingId, + ]; + + return $this->httpGet('cgi-bin/living/get_living_info', $params); + } + + /** + * 获取看直播统计 + * + * @see https://work.weixin.qq.com/api/doc/90000/90135/92736 + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getWatchStat(string $livingId, string $nextKey = '0') + { + $params = [ + 'livingid' => $livingId, + 'next_key' => $nextKey, + ]; + + return $this->httpPostJson('cgi-bin/living/get_watch_stat', $params); + } +} diff --git a/vendor/overtrue/wechat/src/Work/Live/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/Live/ServiceProvider.php new file mode 100644 index 0000000..bb12eb3 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Live/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Live; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author arthasking + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['live'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Work/Media/Client.php b/vendor/overtrue/wechat/src/Work/Media/Client.php new file mode 100644 index 0000000..ba81e7f --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Media/Client.php @@ -0,0 +1,173 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Media; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Http\StreamResponse; + +/** + * Class Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + /** + * Get media. + * + * @param string $mediaId + * + * @return array|\EasyWeChat\Kernel\Http\Response|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get(string $mediaId) + { + return $this->getResources($mediaId, 'cgi-bin/media/get'); + } + + /** + * Upload Image. + * + * @param string $path + * @param array $form + * + * @return mixed + */ + public function uploadImage(string $path, array $form = []) + { + return $this->upload('image', $path, $form); + } + + /** + * Upload Voice. + * + * @param string $path + * @param array $form + * + * @return mixed + */ + public function uploadVoice(string $path, array $form = []) + { + return $this->upload('voice', $path, $form); + } + + /** + * Upload Video. + * + * @param string $path + * @param array $form + * + * @return mixed + */ + public function uploadVideo(string $path, array $form = []) + { + return $this->upload('video', $path, $form); + } + + /** + * Upload File. + * + * @param string $path + * @param array $form + * + * @return mixed + */ + public function uploadFile(string $path, array $form = []) + { + return $this->upload('file', $path, $form); + } + + /** + * Upload media. + * + * @param string $type + * @param string $path + * @param array $form + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function upload(string $type, string $path, array $form = []) + { + $files = [ + 'media' => $path, + ]; + + return $this->httpUpload('cgi-bin/media/upload', $files, $form, compact('type')); + } + + /** + * Upload permanently valid images + * + * @see https://work.weixin.qq.com/api/doc/90000/90135/90256 + * @param string $path + * @param array $form + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function uploadImg(string $path, array $form = []) + { + $files = [ + 'media' => $path, + ]; + + return $this->httpUpload('cgi-bin/media/uploadimg', $files, $form); + } + + + /** + * Get HD voice material + * + * @see https://work.weixin.qq.com/api/doc/90000/90135/90255 + * @param string $mediaId + * + * @return array|\EasyWeChat\Kernel\Http\Response|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getHdVoice(string $mediaId) + { + return $this->getResources($mediaId, 'cgi-bin/media/get/jssdk'); + } + + /** + * @param string $mediaId + * @param string $uri + * + * @return array|\EasyWeChat\Kernel\Http\Response|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function getResources(string $mediaId, string $uri) + { + $response = $this->requestRaw($uri, 'GET', [ + 'query' => [ + 'media_id' => $mediaId, + ], + ]); + + if (false !== stripos($response->getHeaderLine('Content-Type'), 'text/plain')) { + return $this->castResponseToType($response, $this->app['config']->get('response_type')); + } + + return StreamResponse::buildFromPsrResponse($response); + } +} diff --git a/vendor/overtrue/wechat/src/Work/Media/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/Media/ServiceProvider.php new file mode 100644 index 0000000..450d156 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Media/ServiceProvider.php @@ -0,0 +1,28 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Media; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['media'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Work/Menu/Client.php b/vendor/overtrue/wechat/src/Work/Menu/Client.php new file mode 100644 index 0000000..e57a3a1 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Menu/Client.php @@ -0,0 +1,61 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Menu; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + /** + * Get menu. + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function get() + { + return $this->httpGet('cgi-bin/menu/get', ['agentid' => $this->app['config']['agent_id']]); + } + + /** + * Create menu for the given agent. + * + * @param array $data + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function create(array $data) + { + return $this->httpPostJson('cgi-bin/menu/create', $data, ['agentid' => $this->app['config']['agent_id']]); + } + + /** + * Delete menu. + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function delete() + { + return $this->httpGet('cgi-bin/menu/delete', ['agentid' => $this->app['config']['agent_id']]); + } +} diff --git a/vendor/overtrue/wechat/src/Work/Menu/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/Menu/ServiceProvider.php new file mode 100644 index 0000000..02c4290 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Menu/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Menu; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author mingyoung + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['menu'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Work/Message/Client.php b/vendor/overtrue/wechat/src/Work/Message/Client.php new file mode 100644 index 0000000..8cd2501 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Message/Client.php @@ -0,0 +1,76 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Message; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Messages\Message; + +/** + * Class Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + /** + * @param string|\EasyWeChat\Kernel\Messages\Message $message + * + * @return \EasyWeChat\Work\Message\Messenger + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + public function message($message) + { + return (new Messenger($this))->message($message); + } + + /** + * @param array $message + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function send(array $message) + { + return $this->httpPostJson('cgi-bin/message/send', $message); + } + + /** + * 更新任务卡片消息状态 + * + * @see https://open.work.weixin.qq.com/api/doc/90000/90135/91579 + * + * @param array $userids + * @param int $agentId + * @param string $taskId + * @param string $replaceName + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + * + */ + public function updateTaskcard(array $userids, int $agentId, string $taskId, string $replaceName = '已收到') + { + $params = [ + 'userids' => $userids, + 'agentid' => $agentId, + 'task_id' => $taskId, + 'replace_name' => $replaceName + ]; + + return $this->httpPostJson('cgi-bin/message/update_taskcard', $params); + } +} diff --git a/vendor/overtrue/wechat/src/Work/Message/Messenger.php b/vendor/overtrue/wechat/src/Work/Message/Messenger.php new file mode 100644 index 0000000..62bc62d --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Message/Messenger.php @@ -0,0 +1,205 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Message; + +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; +use EasyWeChat\Kernel\Exceptions\RuntimeException; +use EasyWeChat\Kernel\Messages\Message; +use EasyWeChat\Kernel\Messages\Text; + +/** + * Class MessageBuilder. + * + * @author overtrue + */ +class Messenger +{ + /** + * @var \EasyWeChat\Kernel\Messages\Message; + */ + protected $message; + + /** + * @var array + */ + protected $to = ['touser' => '@all']; + + /** + * @var int + */ + protected $agentId; + + /** + * @var bool + */ + protected $secretive = false; + + /** + * @var \EasyWeChat\Work\Message\Client + */ + protected $client; + + /** + * MessageBuilder constructor. + * + * @param \EasyWeChat\Work\Message\Client $client + */ + public function __construct(Client $client) + { + $this->client = $client; + } + + /** + * Set message to send. + * + * @param string|Message $message + * + * @return \EasyWeChat\Work\Message\Messenger + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + public function message($message) + { + if (is_string($message) || is_numeric($message)) { + $message = new Text($message); + } + + if (!($message instanceof Message)) { + throw new InvalidArgumentException('Invalid message.'); + } + + $this->message = $message; + + return $this; + } + + /** + * @param int $agentId + * + * @return \EasyWeChat\Work\Message\Messenger + */ + public function ofAgent(int $agentId) + { + $this->agentId = $agentId; + + return $this; + } + + /** + * @param array|string $userIds + * + * @return \EasyWeChat\Work\Message\Messenger + */ + public function toUser($userIds) + { + return $this->setRecipients($userIds, 'touser'); + } + + /** + * @param array|string $partyIds + * + * @return \EasyWeChat\Work\Message\Messenger + */ + public function toParty($partyIds) + { + return $this->setRecipients($partyIds, 'toparty'); + } + + /** + * @param array|string $tagIds + * + * @return \EasyWeChat\Work\Message\Messenger + */ + public function toTag($tagIds) + { + return $this->setRecipients($tagIds, 'totag'); + } + + /** + * Keep secret. + * + * @return \EasyWeChat\Work\Message\Messenger + */ + public function secretive() + { + $this->secretive = true; + + return $this; + } + + /** + * @param array|string $ids + * @param string $key + * + * @return \EasyWeChat\Work\Message\Messenger + */ + protected function setRecipients($ids, string $key): self + { + if (is_array($ids)) { + $ids = implode('|', $ids); + } + + $this->to = [$key => $ids]; + + return $this; + } + + /** + * @param \EasyWeChat\Kernel\Messages\Message|string|null $message + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + public function send($message = null) + { + if ($message) { + $this->message($message); + } + + if (empty($this->message)) { + throw new RuntimeException('No message to send.'); + } + + if (is_null($this->agentId)) { + throw new RuntimeException('No agentid specified.'); + } + + $message = $this->message->transformForJsonRequest(array_merge([ + 'agentid' => $this->agentId, + 'safe' => intval($this->secretive), + ], $this->to)); + + $this->secretive = false; + + return $this->client->send($message); + } + + /** + * Return property. + * + * @param string $property + * + * @return mixed + * + * @throws InvalidArgumentException + */ + public function __get($property) + { + if (property_exists($this, $property)) { + return $this->$property; + } + + throw new InvalidArgumentException(sprintf('No property named "%s"', $property)); + } +} diff --git a/vendor/overtrue/wechat/src/Work/Message/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/Message/ServiceProvider.php new file mode 100644 index 0000000..ea14f9e --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Message/ServiceProvider.php @@ -0,0 +1,43 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Message; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author mingyoung + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['message'] = function ($app) { + return new Client($app); + }; + + $app['messenger'] = function ($app) { + $messenger = new Messenger($app['message']); + + if (is_numeric($app['config']['agent_id'])) { + $messenger->ofAgent($app['config']['agent_id']); + } + + return $messenger; + }; + } +} diff --git a/vendor/overtrue/wechat/src/Work/MiniProgram/Application.php b/vendor/overtrue/wechat/src/Work/MiniProgram/Application.php new file mode 100644 index 0000000..aa290cd --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/MiniProgram/Application.php @@ -0,0 +1,44 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\MiniProgram; + +use EasyWeChat\MiniProgram\Application as MiniProgram; +use EasyWeChat\Work\Auth\AccessToken; +use EasyWeChat\Work\MiniProgram\Auth\Client; + +/** + * Class Application. + * + * @author Caikeal + * + * @property \EasyWeChat\Work\MiniProgram\Auth\Client $auth + */ +class Application extends MiniProgram +{ + /** + * Application constructor. + * + * @param array $config + * @param array $prepends + */ + public function __construct(array $config = [], array $prepends = []) + { + parent::__construct($config, $prepends + [ + 'access_token' => function ($app) { + return new AccessToken($app); + }, + 'auth' => function ($app) { + return new Client($app); + }, + ]); + } +} diff --git a/vendor/overtrue/wechat/src/Work/MiniProgram/Auth/Client.php b/vendor/overtrue/wechat/src/Work/MiniProgram/Auth/Client.php new file mode 100644 index 0000000..ac9baff --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/MiniProgram/Auth/Client.php @@ -0,0 +1,39 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\MiniProgram\Auth; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + */ +class Client extends BaseClient +{ + /** + * Get session info by code. + * + * @param string $code + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function session(string $code) + { + $params = [ + 'js_code' => $code, + 'grant_type' => 'authorization_code', + ]; + + return $this->httpGet('cgi-bin/miniprogram/jscode2session', $params); + } +} diff --git a/vendor/overtrue/wechat/src/Work/Mobile/Auth/Client.php b/vendor/overtrue/wechat/src/Work/Mobile/Auth/Client.php new file mode 100644 index 0000000..357339b --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Mobile/Auth/Client.php @@ -0,0 +1,40 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Mobile\Auth; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + */ +class Client extends BaseClient +{ + /** + * 通过code获取用户信息. + * + * @see https://open.work.weixin.qq.com/api/doc/90000/90136/91193 + * + * @param string $code + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function getUser(string $code) + { + $params = [ + 'code' => $code, + ]; + + return $this->httpGet('cgi-bin/user/getuserinfo', $params); + } +} diff --git a/vendor/overtrue/wechat/src/Work/Mobile/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/Mobile/ServiceProvider.php new file mode 100644 index 0000000..eb26844 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Mobile/ServiceProvider.php @@ -0,0 +1,36 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Mobile; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use EasyWeChat\Work\Mobile\Auth\Client; + +/** + * ServiceProvider. + * + * @author 读心印 + */ +class ServiceProvider implements ServiceProviderInterface +{ + protected $app; + + /** + * @param Container $app + */ + public function register(Container $app) + { + $app['mobile'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Work/MsgAudit/Client.php b/vendor/overtrue/wechat/src/Work/MsgAudit/Client.php new file mode 100644 index 0000000..8192ab2 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/MsgAudit/Client.php @@ -0,0 +1,85 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\MsgAudit; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author ZengJJ + */ +class Client extends BaseClient +{ + /** + * @param string|null $type + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException|\GuzzleHttp\Exception\GuzzleException + */ + public function getPermitUsers(string $type = null) + { + return $this->httpPostJson('cgi-bin/msgaudit/get_permit_user_list', (empty($type) ? [] : ['type' => $type])); + } + + /** + * @param array $info 数组,格式: [[userid, exteranalopenid], [userid, exteranalopenid]] + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getSingleAgreeStatus(array $info) + { + $params = [ + 'info' => $info + ]; + + return $this->httpPostJson('cgi-bin/msgaudit/check_single_agree', $params); + } + + /** + * @param string $roomId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getRoomAgreeStatus(string $roomId) + { + $params = [ + 'roomid' => $roomId + ]; + + return $this->httpPostJson('cgi-bin/msgaudit/check_room_agree', $params); + } + + /** + * @param string $roomId + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getRoom(string $roomId) + { + $params = [ + 'roomid' => $roomId + ]; + + return $this->httpPostJson('cgi-bin/msgaudit/groupchat/get', $params); + } +} diff --git a/vendor/overtrue/wechat/src/Work/MsgAudit/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/MsgAudit/ServiceProvider.php new file mode 100644 index 0000000..e24c8b3 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/MsgAudit/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\MsgAudit; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author ZengJJ + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['msg_audit'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Work/OA/Client.php b/vendor/overtrue/wechat/src/Work/OA/Client.php new file mode 100644 index 0000000..1adc6ed --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/OA/Client.php @@ -0,0 +1,339 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\OA; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + /** + * Get the checkin data. + * + * @param int $startTime + * @param int $endTime + * @param array $userList + * @param int $type + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function checkinRecords(int $startTime, int $endTime, array $userList, int $type = 3) + { + $params = [ + 'opencheckindatatype' => $type, + 'starttime' => $startTime, + 'endtime' => $endTime, + 'useridlist' => $userList, + ]; + + return $this->httpPostJson('cgi-bin/checkin/getcheckindata', $params); + } + + /** + * Get the checkin rules. + * + * @param int $datetime + * @param array $userList + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function checkinRules(int $datetime, array $userList) + { + $params = [ + 'datetime' => $datetime, + 'useridlist' => $userList, + ]; + + return $this->httpPostJson('cgi-bin/checkin/getcheckinoption', $params); + } + + /** + * 获取企业所有打卡规则. + * + * @see https://open.work.weixin.qq.com/api/doc/90000/90135/93384 + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + * + * @author 读心印 + */ + public function corpCheckinRules() + { + return $this->httpPostJson('cgi-bin/checkin/getcorpcheckinoption'); + } + + /** + * 获取打卡日报数据. + * + * @sse https://open.work.weixin.qq.com/api/doc/90000/90135/93374 + * + * @param int $startTime + * @param int $endTime + * @param array $userids + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + * + * @author 读心印 + */ + public function checkinDayData(int $startTime, int $endTime, array $userids) + { + $params = [ + 'starttime' => $startTime, + 'endtime' => $endTime, + 'useridlist' => $userids, + ]; + + return $this->httpPostJson('cgi-bin/checkin/getcheckin_daydata', $params); + } + + /** + * 获取打卡日报数据. + * + * @sse https://open.work.weixin.qq.com/api/doc/90000/90135/93387 + * + * @param int $startTime + * @param int $endTime + * @param array $userids + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + * + * @author 读心印 + */ + public function checkinMonthData(int $startTime, int $endTime, array $userids) + { + $params = [ + 'starttime' => $startTime, + 'endtime' => $endTime, + 'useridlist' => $userids, + ]; + + return $this->httpPostJson('cgi-bin/checkin/getcheckin_monthdata', $params); + } + + /** + * 获取打卡人员排班信息. + * + * @sse https://open.work.weixin.qq.com/api/doc/90000/90135/93380 + * + * @param int $startTime + * @param int $endTime + * @param array $userids + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + * + * @author 读心印 + */ + public function checkinSchedus(int $startTime, int $endTime, array $userids) + { + $params = [ + 'starttime' => $startTime, + 'endtime' => $endTime, + 'useridlist' => $userids, + ]; + + return $this->httpPostJson('cgi-bin/checkin/getcheckinschedulist', $params); + } + + /** + * 为打卡人员排班. + * + * @sse https://open.work.weixin.qq.com/api/doc/90000/90135/93385 + * + * @param array $params + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + * + * @author 读心印 + */ + public function setCheckinSchedus(array $params) + { + return $this->httpPostJson('cgi-bin/checkin/setcheckinschedulist', $params); + } + + /** + * 录入打卡人员人脸信息. + * + * @sse https://open.work.weixin.qq.com/api/doc/90000/90135/93378 + * + * @param string $userid + * @param string $userface + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + * + * @author 读心印 + */ + public function addCheckinUserface(string $userid, string $userface) + { + $params = [ + 'userid' => $userid, + 'userface' => $userface + ]; + + return $this->httpPostJson('cgi-bin/checkin/addcheckinuserface', $params); + } + + /** + * Get approval template details. + * + * @param string $templateId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function approvalTemplate(string $templateId) + { + $params = [ + 'template_id' => $templateId, + ]; + + return $this->httpPostJson('cgi-bin/oa/gettemplatedetail', $params); + } + + /** + * Submit an application for approval. + * + * @param array $data + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function createApproval(array $data) + { + return $this->httpPostJson('cgi-bin/oa/applyevent', $data); + } + + /** + * Get Approval number. + * + * @param int $startTime + * @param int $endTime + * @param int $nextCursor + * @param int $size + * @param array $filters + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function approvalNumbers(int $startTime, int $endTime, int $nextCursor = 0, int $size = 100, array $filters = []) + { + $params = [ + 'starttime' => $startTime, + 'endtime' => $endTime, + 'cursor' => $nextCursor, + 'size' => $size > 100 ? 100 : $size, + 'filters' => $filters, + ]; + + return $this->httpPostJson('cgi-bin/oa/getapprovalinfo', $params); + } + + /** + * Get approval detail. + * + * @param int $number + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function approvalDetail(int $number) + { + $params = [ + 'sp_no' => $number, + ]; + + return $this->httpPostJson('cgi-bin/oa/getapprovaldetail', $params); + } + + /** + * Get Approval Data. + * + * @param int $startTime + * @param int $endTime + * @param int $nextNumber + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function approvalRecords(int $startTime, int $endTime, int $nextNumber = null) + { + $params = [ + 'starttime' => $startTime, + 'endtime' => $endTime, + 'next_spnum' => $nextNumber, + ]; + + return $this->httpPostJson('cgi-bin/corp/getapprovaldata', $params); + } + + + /** + * 获取公费电话拨打记录. + * + * @param int $startTime + * @param int $endTime + * @param int $offset + * @param int $limit + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function dialRecords(int $startTime, int $endTime, int $offset = 0, $limit = 100) + { + $params = [ + 'start_time' => $startTime, + 'end_time' => $endTime, + 'offset' => $offset, + 'limit' => $limit + ]; + + return $this->httpPostJson('cgi-bin/dial/get_dial_record', $params); + } +} diff --git a/vendor/overtrue/wechat/src/Work/OA/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/OA/ServiceProvider.php new file mode 100644 index 0000000..5d389c2 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/OA/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\OA; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author mingyoung + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['oa'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Work/OAuth/Manager.php b/vendor/overtrue/wechat/src/Work/OAuth/Manager.php new file mode 100644 index 0000000..e0d4b16 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/OAuth/Manager.php @@ -0,0 +1,51 @@ +config = $config; + $this->app = $app; + } + + public function redirect($redirect = null) + { + return new RedirectResponse($this->getProvider()->redirect($redirect)); + } + + public function user() + { + $this->getProvider()->withApiAccessToken($this->app['access_token']->getToken()['access_token']); + + return $this->getProvider()->userFromCode($this->app->request->get('code')); + } + + protected function getProvider(): ProviderInterface + { + return $this->provider ?? $this->provider = (new SocialiteManager($this->config))->create('wework'); + } + + public function __call($name, $arguments) + { + return \call_user_func_array([$this->getProvider(), $name], $arguments); + } +} diff --git a/vendor/overtrue/wechat/src/Work/OAuth/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/OAuth/ServiceProvider.php new file mode 100644 index 0000000..b46af8f --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/OAuth/ServiceProvider.php @@ -0,0 +1,64 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\OAuth; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +class ServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + $app['oauth'] = function ($app) { + $socialite = (new Manager([ + 'wework' => [ + 'base_url' => $app['config']['http']['base_uri'], + 'client_id' => $app['config']['corp_id'], + 'client_secret' => null, + 'corp_id' => $app['config']['corp_id'], + 'corp_secret' => $app['config']['secret'], + 'redirect' => $this->prepareCallbackUrl($app), + ], + ], $app)); + + $scopes = (array) $app['config']->get('oauth.scopes', ['snsapi_base']); + + if (!empty($scopes)) { + $socialite->scopes($scopes); + } else { + $socialite->setAgentId($app['config']['agent_id']); + } + + return $socialite; + }; + } + + /** + * Prepare the OAuth callback url for wechat. + * + * @param Container $app + * + * @return string + */ + private function prepareCallbackUrl($app) + { + $callback = $app['config']->get('oauth.callback'); + + if (0 === stripos($callback, 'http')) { + return $callback; + } + + $baseUrl = $app['request']->getSchemeAndHttpHost(); + + return $baseUrl.'/'.ltrim($callback, '/'); + } +} diff --git a/vendor/overtrue/wechat/src/Work/Schedule/Client.php b/vendor/overtrue/wechat/src/Work/Schedule/Client.php new file mode 100644 index 0000000..ba41ddb --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Schedule/Client.php @@ -0,0 +1,104 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Schedule; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author her-cat + */ +class Client extends BaseClient +{ + /** + * Add a schedule. + * + * @param array $schedule + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function add(array $schedule) + { + return $this->httpPostJson('cgi-bin/oa/schedule/add', compact('schedule')); + } + + /** + * Update the schedule. + * + * @param string $id + * @param array $schedule + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(string $id, array $schedule) + { + $schedule += ['schedule_id' => $id]; + + return $this->httpPostJson('cgi-bin/oa/schedule/update', compact('schedule')); + } + + /** + * Get one or more schedules. + * + * @param string|array $ids + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function get($ids) + { + return $this->httpPostJson('cgi-bin/oa/schedule/get', ['schedule_id_list' => (array) $ids]); + } + + /** + * Get the list of schedules under a calendar. + * + * @param string $calendarId + * @param int $offset + * @param int $limit + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getByCalendar(string $calendarId, int $offset = 0, int $limit = 500) + { + $data = compact('offset', 'limit') + ['cal_id' => $calendarId]; + + return $this->httpPostJson('cgi-bin/oa/schedule/get_by_calendar', $data); + } + + /** + * Delete a schedule. + * + * @param string $id + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete(string $id) + { + return $this->httpPostJson('cgi-bin/oa/schedule/del', ['schedule_id' => $id]); + } +} diff --git a/vendor/overtrue/wechat/src/Work/Schedule/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/Schedule/ServiceProvider.php new file mode 100644 index 0000000..9bdd17b --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Schedule/ServiceProvider.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Schedule; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author her-cat + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc} + */ + public function register(Container $app) + { + $app['schedule'] = function ($app) { + return new Client($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Work/Server/Guard.php b/vendor/overtrue/wechat/src/Work/Server/Guard.php new file mode 100644 index 0000000..b77c6c1 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Server/Guard.php @@ -0,0 +1,48 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Server; + +use EasyWeChat\Kernel\ServerGuard; + +/** + * Class Guard. + * + * @author overtrue + */ +class Guard extends ServerGuard +{ + /** + * @return $this + */ + public function validate() + { + return $this; + } + + /** + * Check the request message safe mode. + * + * @return bool + */ + protected function isSafeMode(): bool + { + return true; + } + + /** + * @return bool + */ + protected function shouldReturnRawResponse(): bool + { + return !is_null($this->app['request']->get('echostr')); + } +} diff --git a/vendor/overtrue/wechat/src/Work/Server/Handlers/EchoStrHandler.php b/vendor/overtrue/wechat/src/Work/Server/Handlers/EchoStrHandler.php new file mode 100644 index 0000000..52bf8d3 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Server/Handlers/EchoStrHandler.php @@ -0,0 +1,60 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Server\Handlers; + +use EasyWeChat\Kernel\Contracts\EventHandlerInterface; +use EasyWeChat\Kernel\Decorators\FinallyResult; +use EasyWeChat\Kernel\ServiceContainer; + +/** + * Class EchoStrHandler. + * + * @author overtrue + */ +class EchoStrHandler implements EventHandlerInterface +{ + /** + * @var ServiceContainer + */ + protected $app; + + /** + * EchoStrHandler constructor. + * + * @param ServiceContainer $app + */ + public function __construct(ServiceContainer $app) + { + $this->app = $app; + } + + /** + * @param mixed $payload + * + * @return FinallyResult|null + */ + public function handle($payload = null) + { + if ($decrypted = $this->app['request']->get('echostr')) { + $str = $this->app['encryptor']->decrypt( + $decrypted, + $this->app['request']->get('msg_signature'), + $this->app['request']->get('nonce'), + $this->app['request']->get('timestamp') + ); + + return new FinallyResult($str); + } + + return null; + } +} diff --git a/vendor/overtrue/wechat/src/Work/Server/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/Server/ServiceProvider.php new file mode 100644 index 0000000..8a2fc3e --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/Server/ServiceProvider.php @@ -0,0 +1,46 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\Server; + +use EasyWeChat\Kernel\Encryptor; +use EasyWeChat\Work\Server\Handlers\EchoStrHandler; +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author overtrue + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + !isset($app['encryptor']) && $app['encryptor'] = function ($app) { + return new Encryptor( + $app['config']['corp_id'], + $app['config']['token'], + $app['config']['aes_key'] + ); + }; + + !isset($app['server']) && $app['server'] = function ($app) { + $guard = new Guard($app); + $guard->push(new EchoStrHandler($app)); + + return $guard; + }; + } +} diff --git a/vendor/overtrue/wechat/src/Work/User/BatchJobsClient.php b/vendor/overtrue/wechat/src/Work/User/BatchJobsClient.php new file mode 100644 index 0000000..d1e4716 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/User/BatchJobsClient.php @@ -0,0 +1,94 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\User; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class BatchJobsClient. + * + * @author 读心印 + */ +class BatchJobsClient extends BaseClient +{ + /** + * 增量更新成员. + * + * @see https://open.work.weixin.qq.com/api/doc/90000/90135/90980 + * + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function batchUpdateUsers(array $params) + { + return $this->httpPostJson('cgi-bin/batch/syncuser', $params); + } + + /** + * 全量覆盖成员. + * + * @see https://open.work.weixin.qq.com/api/doc/90000/90135/90981 + * + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function batchReplaceUsers(array $params) + { + return $this->httpPostJson('cgi-bin/batch/replaceuser', $params); + } + + /** + * 全量覆盖部门. + * + * @see https://open.work.weixin.qq.com/api/doc/90000/90135/90982 + * + * @param array $params + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function batchReplaceDepartments(array $params) + { + return $this->httpPostJson('cgi-bin/batch/replaceparty', $params); + } + + /** + * 获取异步任务结果. + * + * @see https://open.work.weixin.qq.com/api/doc/90000/90135/93169 + * + * @param string $jobId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getJobStatus(string $jobId) + { + $params = [ + 'jobid' => $jobId + ]; + + return $this->httpGet('cgi-bin/batch/getresult', $params); + } +} diff --git a/vendor/overtrue/wechat/src/Work/User/Client.php b/vendor/overtrue/wechat/src/Work/User/Client.php new file mode 100644 index 0000000..8c07ec6 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/User/Client.php @@ -0,0 +1,251 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\User; + +use EasyWeChat\Kernel\BaseClient; +use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; + +/** + * Class Client. + * + * @author mingyoung + */ +class Client extends BaseClient +{ + /** + * Create a user. + * + * @param array $data + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function create(array $data) + { + return $this->httpPostJson('cgi-bin/user/create', $data); + } + + /** + * Update an exist user. + * + * @param string $id + * @param array $data + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(string $id, array $data) + { + return $this->httpPostJson('cgi-bin/user/update', array_merge(['userid' => $id], $data)); + } + + /** + * Delete a user. + * + * @param string|array $userId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function delete($userId) + { + if (is_array($userId)) { + return $this->batchDelete($userId); + } + + return $this->httpGet('cgi-bin/user/delete', ['userid' => $userId]); + } + + /** + * Batch delete users. + * + * @param array $userIds + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function batchDelete(array $userIds) + { + return $this->httpPostJson('cgi-bin/user/batchdelete', ['useridlist' => $userIds]); + } + + /** + * Get user. + * + * @param string $userId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function get(string $userId) + { + return $this->httpGet('cgi-bin/user/get', ['userid' => $userId]); + } + + /** + * Get simple user list. + * + * @param int $departmentId + * @param bool $fetchChild + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function getDepartmentUsers(int $departmentId, bool $fetchChild = false) + { + $params = [ + 'department_id' => $departmentId, + 'fetch_child' => (int) $fetchChild, + ]; + + return $this->httpGet('cgi-bin/user/simplelist', $params); + } + + /** + * Get user list. + * + * @param int $departmentId + * @param bool $fetchChild + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function getDetailedDepartmentUsers(int $departmentId, bool $fetchChild = false) + { + $params = [ + 'department_id' => $departmentId, + 'fetch_child' => (int) $fetchChild, + ]; + + return $this->httpGet('cgi-bin/user/list', $params); + } + + /** + * Convert userId to openid. + * + * @param string $userId + * @param int|null $agentId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function userIdToOpenid(string $userId, int $agentId = null) + { + $params = [ + 'userid' => $userId, + 'agentid' => $agentId, + ]; + + return $this->httpPostJson('cgi-bin/user/convert_to_openid', $params); + } + + /** + * Convert openid to userId. + * + * @param string $openid + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function openidToUserId(string $openid) + { + $params = [ + 'openid' => $openid, + ]; + + return $this->httpPostJson('cgi-bin/user/convert_to_userid', $params); + } + + /** + * Convert mobile to userId. + * + * @param string $mobile + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function mobileToUserId(string $mobile) + { + $params = [ + 'mobile' => $mobile, + ]; + + return $this->httpPostJson('cgi-bin/user/getuserid', $params); + } + + /** + * @param string $userId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function accept(string $userId) + { + $params = [ + 'userid' => $userId, + ]; + + return $this->httpGet('cgi-bin/user/authsucc', $params); + } + + /** + * Batch invite users. + * + * @param array $params + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function invite(array $params) + { + return $this->httpPostJson('cgi-bin/batch/invite', $params); + } + + /** + * Get invitation QR code. + * + * @param int $sizeType + * + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws InvalidArgumentException + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function getInvitationQrCode(int $sizeType = 1) + { + if (!\in_array($sizeType, [1, 2, 3, 4], true)) { + throw new InvalidArgumentException('The sizeType must be 1, 2, 3, 4.'); + } + + return $this->httpGet('cgi-bin/corp/get_join_qrcode', ['size_type' => $sizeType]); + } +} diff --git a/vendor/overtrue/wechat/src/Work/User/LinkedCorpClient.php b/vendor/overtrue/wechat/src/Work/User/LinkedCorpClient.php new file mode 100644 index 0000000..b8595be --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/User/LinkedCorpClient.php @@ -0,0 +1,125 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\User; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class Client. + * + * @author 读心印 + */ +class LinkedCorpClient extends BaseClient +{ + /** + * 获取应用的可见范围. + * + * @see https://open.work.weixin.qq.com/api/doc/90000/90135/93172 + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getAgentPermissions() + { + return $this->httpPostJson('cgi-bin/linkedcorp/agent/get_perm_list'); + } + + /** + * 获取互联企业成员详细信息. + * + * @see https://open.work.weixin.qq.com/api/doc/90000/90135/93171 + * + * @param string $userId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getUser(string $userId) + { + $params = [ + 'userid' => $userId + ]; + + return $this->httpPostJson('cgi-bin/linkedcorp/user/get', $params); + } + + /** + * 获取互联企业部门成员. + * + * @see https://open.work.weixin.qq.com/api/doc/90000/90135/93168 + * + * @param string $departmentId + * @param bool $fetchChild + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getUsers(string $departmentId, bool $fetchChild = true) + { + $params = [ + 'department_id' => $departmentId, + 'fetch_child' => $fetchChild + ]; + + return $this->httpPostJson('cgi-bin/linkedcorp/user/simplelist', $params); + } + + /** + * 获取互联企业部门成员详情. + * + * @see https://open.work.weixin.qq.com/api/doc/90000/90135/93169 + * + * @param string $departmentId + * @param bool $fetchChild + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getDetailedUsers(string $departmentId, bool $fetchChild = true) + { + $params = [ + 'department_id' => $departmentId, + 'fetch_child' => $fetchChild + ]; + + return $this->httpPostJson('cgi-bin/linkedcorp/user/list', $params); + } + + /** + * 获取互联企业部门列表. + * + * @see https://open.work.weixin.qq.com/api/doc/90000/90135/93170 + * + * @param string $departmentId + * + * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getDepartments(string $departmentId) + { + $params = [ + 'department_id' => $departmentId, + ]; + + return $this->httpPostJson('cgi-bin/linkedcorp/department/list', $params); + } +} diff --git a/vendor/overtrue/wechat/src/Work/User/ServiceProvider.php b/vendor/overtrue/wechat/src/Work/User/ServiceProvider.php new file mode 100644 index 0000000..f5cdbc7 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/User/ServiceProvider.php @@ -0,0 +1,45 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\User; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ServiceProvider. + * + * @author mingyoung + */ +class ServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc}. + */ + public function register(Container $app) + { + $app['user'] = function ($app) { + return new Client($app); + }; + + $app['tag'] = function ($app) { + return new TagClient($app); + }; + + $app['linked_corp'] = function ($app) { + return new LinkedCorpClient($app); + }; + + $app['batch_jobs'] = function ($app) { + return new BatchJobsClient($app); + }; + } +} diff --git a/vendor/overtrue/wechat/src/Work/User/TagClient.php b/vendor/overtrue/wechat/src/Work/User/TagClient.php new file mode 100644 index 0000000..aae7cf1 --- /dev/null +++ b/vendor/overtrue/wechat/src/Work/User/TagClient.php @@ -0,0 +1,178 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Work\User; + +use EasyWeChat\Kernel\BaseClient; + +/** + * Class TagClient. + * + * @author mingyoung + */ +class TagClient extends BaseClient +{ + /** + * Create tag. + * + * @param string $tagName + * @param int|null $tagId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function create(string $tagName, int $tagId = null) + { + $params = [ + 'tagname' => $tagName, + 'tagid' => $tagId, + ]; + + return $this->httpPostJson('cgi-bin/tag/create', $params); + } + + /** + * Update tag. + * + * @param int $tagId + * @param string $tagName + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function update(int $tagId, string $tagName) + { + $params = [ + 'tagid' => $tagId, + 'tagname' => $tagName, + ]; + + return $this->httpPostJson('cgi-bin/tag/update', $params); + } + + /** + * Delete tag. + * + * @param int $tagId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function delete(int $tagId) + { + return $this->httpGet('cgi-bin/tag/delete', ['tagid' => $tagId]); + } + + /** + * @param int $tagId + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function get(int $tagId) + { + return $this->httpGet('cgi-bin/tag/get', ['tagid' => $tagId]); + } + + /** + * @param int $tagId + * @param array $userList + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function tagUsers(int $tagId, array $userList = []) + { + return $this->tagOrUntagUsers('cgi-bin/tag/addtagusers', $tagId, $userList); + } + + /** + * @param int $tagId + * @param array $partyList + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function tagDepartments(int $tagId, array $partyList = []) + { + return $this->tagOrUntagUsers('cgi-bin/tag/addtagusers', $tagId, [], $partyList); + } + + /** + * @param int $tagId + * @param array $userList + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function untagUsers(int $tagId, array $userList = []) + { + return $this->tagOrUntagUsers('cgi-bin/tag/deltagusers', $tagId, $userList); + } + + /** + * @param int $tagId + * @param array $partyList + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function untagDepartments(int $tagId, array $partyList = []) + { + return $this->tagOrUntagUsers('cgi-bin/tag/deltagusers', $tagId, [], $partyList); + } + + /** + * @param string $endpoint + * @param int $tagId + * @param array $userList + * @param array $partyList + * + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function tagOrUntagUsers(string $endpoint, int $tagId, array $userList = [], array $partyList = []) + { + $data = [ + 'tagid' => $tagId, + 'userlist' => $userList, + 'partylist' => $partyList, + ]; + + return $this->httpPostJson($endpoint, $data); + } + + /** + * @return mixed + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException + */ + public function list() + { + return $this->httpGet('cgi-bin/tag/list'); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/.php-cs-fixer.dist.php b/vendor/phpoffice/phpspreadsheet/.php-cs-fixer.dist.php new file mode 100644 index 0000000..c0e8f9d --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/.php-cs-fixer.dist.php @@ -0,0 +1,226 @@ +exclude('vendor') + ->in(__DIR__); + +$config = new PhpCsFixer\Config(); +$config + ->setRiskyAllowed(true) + ->setFinder($finder) + ->setCacheFile(sys_get_temp_dir() . '/php-cs-fixer' . preg_replace('~\W~', '-', __DIR__)) + ->setRules([ + 'align_multiline_comment' => true, + 'array_indentation' => true, + 'array_syntax' => ['syntax' => 'short'], + 'backtick_to_shell_exec' => true, + 'binary_operator_spaces' => true, + 'blank_line_after_namespace' => true, + 'blank_line_after_opening_tag' => true, + 'blank_line_before_statement' => true, + 'braces' => true, + 'cast_spaces' => true, + 'class_attributes_separation' => ['elements' => ['method' => 'one', 'property' => 'one']], // const are often grouped with other related const + 'class_definition' => true, + 'class_keyword_remove' => false, // ::class keyword gives us better support in IDE + 'combine_consecutive_issets' => true, + 'combine_consecutive_unsets' => true, + 'combine_nested_dirname' => true, + 'comment_to_phpdoc' => true, + 'compact_nullable_typehint' => true, + 'concat_space' => ['spacing' => 'one'], + 'constant_case' => true, + 'date_time_immutable' => false, // Break our unit tests + 'declare_equal_normalize' => true, + 'declare_strict_types' => false, // Too early to adopt strict types + 'dir_constant' => true, + 'doctrine_annotation_array_assignment' => true, + 'doctrine_annotation_braces' => true, + 'doctrine_annotation_indentation' => true, + 'doctrine_annotation_spaces' => true, + 'elseif' => true, + 'encoding' => true, + 'ereg_to_preg' => true, + 'escape_implicit_backslashes' => true, + 'explicit_indirect_variable' => false, // I feel it makes the code actually harder to read + 'explicit_string_variable' => false, // I feel it makes the code actually harder to read + 'final_class' => false, // We need non-final classes + 'final_internal_class' => true, + 'final_public_method_for_abstract_class' => false, // We need non-final methods + 'self_static_accessor' => true, + 'fopen_flag_order' => true, + 'fopen_flags' => true, + 'full_opening_tag' => true, + 'fully_qualified_strict_types' => true, + 'function_declaration' => true, + 'function_to_constant' => true, + 'function_typehint_space' => true, + 'general_phpdoc_annotation_remove' => ['annotations' => ['access', 'category', 'copyright', 'method', 'throws']], + 'global_namespace_import' => true, + 'header_comment' => false, // We don't use common header in all our files + 'heredoc_indentation' => false, // Requires PHP >= 7.3 + 'heredoc_to_nowdoc' => false, // Not sure about this one + 'implode_call' => true, + 'include' => true, + 'increment_style' => true, + 'indentation_type' => true, + 'is_null' => true, + 'line_ending' => true, + 'linebreak_after_opening_tag' => true, + 'list_syntax' => ['syntax' => 'short'], + 'logical_operators' => true, + 'lowercase_cast' => true, + 'lowercase_keywords' => true, + 'lowercase_static_reference' => true, + 'magic_constant_casing' => true, + 'magic_method_casing' => true, + 'mb_str_functions' => false, // No, too dangerous to change that + 'method_argument_space' => true, + 'method_chaining_indentation' => true, + 'modernize_types_casting' => true, + 'multiline_comment_opening_closing' => true, + 'multiline_whitespace_before_semicolons' => true, + 'native_constant_invocation' => false, // Micro optimization that look messy + 'native_function_casing' => true, + 'native_function_invocation' => false, // I suppose this would be best, but I am still unconvinced about the visual aspect of it + 'native_function_type_declaration_casing' => true, + 'new_with_braces' => true, + 'no_alias_functions' => true, + 'no_alternative_syntax' => true, + 'no_binary_string' => true, + 'no_blank_lines_after_class_opening' => true, + 'no_blank_lines_after_phpdoc' => true, + 'no_blank_lines_before_namespace' => false, // we want 1 blank line before namespace + 'no_break_comment' => true, + 'no_closing_tag' => true, + 'no_empty_comment' => true, + 'no_empty_phpdoc' => true, + 'no_empty_statement' => true, + 'no_extra_blank_lines' => true, + 'no_homoglyph_names' => true, + 'no_leading_import_slash' => true, + 'no_leading_namespace_whitespace' => true, + 'no_mixed_echo_print' => true, + 'no_multiline_whitespace_around_double_arrow' => true, + 'no_null_property_initialization' => true, + 'no_php4_constructor' => true, + 'no_short_bool_cast' => true, + 'echo_tag_syntax' => ['format' => 'long'], + 'no_singleline_whitespace_before_semicolons' => true, + 'no_spaces_after_function_name' => true, + 'no_spaces_around_offset' => true, + 'no_spaces_inside_parenthesis' => true, + 'no_superfluous_elseif' => false, // Might be risky on a huge code base + 'no_superfluous_phpdoc_tags' => ['allow_mixed' => true], + 'no_trailing_comma_in_list_call' => true, + 'no_trailing_comma_in_singleline_array' => true, + 'no_trailing_whitespace' => true, + 'no_trailing_whitespace_in_comment' => true, + 'no_unneeded_control_parentheses' => true, + 'no_unneeded_curly_braces' => true, + 'no_unneeded_final_method' => true, + 'no_unreachable_default_argument_value' => true, + 'no_unset_cast' => true, + 'no_unset_on_property' => true, + 'no_unused_imports' => true, + 'no_useless_else' => true, + 'no_useless_return' => true, + 'no_whitespace_before_comma_in_array' => true, + 'no_whitespace_in_blank_line' => true, + 'non_printable_character' => true, + 'normalize_index_brace' => true, + 'not_operator_with_space' => false, // No we prefer to keep '!' without spaces + 'not_operator_with_successor_space' => false, // idem + 'nullable_type_declaration_for_default_null_value' => true, + 'object_operator_without_whitespace' => true, + 'ordered_class_elements' => false, // We prefer to keep some freedom + 'ordered_imports' => true, + 'ordered_interfaces' => true, + 'php_unit_construct' => true, + 'php_unit_dedicate_assert' => true, + 'php_unit_dedicate_assert_internal_type' => true, + 'php_unit_expectation' => true, + 'php_unit_fqcn_annotation' => true, + 'php_unit_internal_class' => false, // Because tests are excluded from package + 'php_unit_method_casing' => true, + 'php_unit_mock' => true, + 'php_unit_mock_short_will_return' => true, + 'php_unit_namespaced' => true, + 'php_unit_no_expectation_annotation' => true, + 'phpdoc_order_by_value' => ['annotations' => ['covers']], + 'php_unit_set_up_tear_down_visibility' => true, + 'php_unit_size_class' => false, // That seems extra work to maintain for little benefits + 'php_unit_strict' => false, // We sometime actually need assertEquals + 'php_unit_test_annotation' => true, + 'php_unit_test_case_static_method_calls' => ['call_type' => 'self'], + 'php_unit_test_class_requires_covers' => false, // We don't care as much as we should about coverage + 'phpdoc_add_missing_param_annotation' => false, // Don't add things that bring no value + 'phpdoc_align' => false, // Waste of time + 'phpdoc_annotation_without_dot' => true, + 'phpdoc_indent' => true, + //'phpdoc_inline_tag' => true, + 'phpdoc_line_span' => false, // Unfortunately our old comments turn even uglier with this + 'phpdoc_no_access' => true, + 'phpdoc_no_alias_tag' => true, + 'phpdoc_no_empty_return' => true, + 'phpdoc_no_package' => true, + 'phpdoc_no_useless_inheritdoc' => true, + 'phpdoc_order' => true, + 'phpdoc_return_self_reference' => true, + 'phpdoc_scalar' => true, + 'phpdoc_separation' => true, + 'phpdoc_single_line_var_spacing' => true, + 'phpdoc_summary' => true, + 'phpdoc_to_comment' => true, + 'phpdoc_to_param_type' => false, // Because experimental, but interesting for one shot use + 'phpdoc_to_return_type' => false, // idem + 'phpdoc_trim' => true, + 'phpdoc_trim_consecutive_blank_line_separation' => true, + 'phpdoc_types' => true, + 'phpdoc_types_order' => true, + 'phpdoc_var_annotation_correct_order' => true, + 'phpdoc_var_without_name' => true, + 'pow_to_exponentiation' => true, + 'protected_to_private' => true, + //'psr0' => true, + //'psr4' => true, + 'random_api_migration' => true, + 'return_assignment' => false, // Sometimes useful for clarity or debug + 'return_type_declaration' => true, + 'self_accessor' => true, + 'self_static_accessor' => true, + 'semicolon_after_instruction' => false, // Buggy in `samples/index.php` + 'set_type_to_cast' => true, + 'short_scalar_cast' => true, + 'simple_to_complex_string_variable' => false, // Would differ from TypeScript without obvious advantages + 'simplified_null_return' => false, // Even if technically correct we prefer to be explicit + 'single_blank_line_at_eof' => true, + 'single_blank_line_before_namespace' => true, + 'single_class_element_per_statement' => true, + 'single_import_per_statement' => true, + 'single_line_after_imports' => true, + 'single_line_comment_style' => true, + 'single_line_throw' => false, // I don't see any reason for having a special case for Exception + 'single_quote' => true, + 'single_trait_insert_per_statement' => true, + 'space_after_semicolon' => true, + 'standardize_increment' => true, + 'standardize_not_equals' => true, + 'static_lambda' => false, // Risky if we can't guarantee nobody use `bindTo()` + 'strict_comparison' => false, // No, too dangerous to change that + 'strict_param' => false, // No, too dangerous to change that + 'string_line_ending' => true, + 'switch_case_semicolon_to_colon' => true, + 'switch_case_space' => true, + 'ternary_operator_spaces' => true, + 'ternary_to_null_coalescing' => true, + 'trailing_comma_in_multiline' => true, + 'trim_array_spaces' => true, + 'unary_operator_spaces' => true, + 'visibility_required' => ['elements' => ['property', 'method']], // not const + 'void_return' => true, + 'whitespace_after_comma_in_array' => true, + 'yoda_style' => false, + ]); + +return $config; diff --git a/vendor/phpoffice/phpspreadsheet/.phpcs.xml.dist b/vendor/phpoffice/phpspreadsheet/.phpcs.xml.dist new file mode 100644 index 0000000..3eafb6c --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/.phpcs.xml.dist @@ -0,0 +1,22 @@ + + + + samples + src + tests + + samples/Header.php + */tests/Core/*/*Test\.(inc|css|js)$ + + + + + + + + + + + + diff --git a/vendor/phpoffice/phpspreadsheet/CHANGELOG.md b/vendor/phpoffice/phpspreadsheet/CHANGELOG.md new file mode 100644 index 0000000..64b4b7e --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/CHANGELOG.md @@ -0,0 +1,830 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com) +and this project adheres to [Semantic Versioning](https://semver.org). + +## 1.20.0 - 2021-11-23 + +### Added + +- Xlsx Writer Support for WMF Files [#2339](https://github.com/PHPOffice/PhpSpreadsheet/issues/2339) +- Use standard temporary file for internal use of HTMLPurifier [#2383](https://github.com/PHPOffice/PhpSpreadsheet/issues/2383) + +### Changed + +- Drop support for PHP 7.2, according to https://phpspreadsheet.readthedocs.io/en/latest/#php-version-support +- Use native typing for objects that were already documented as such + +### Deprecated + +- Nothing + +### Removed + +- Nothing + +### Fixed + +- Fixed null conversation for strToUpper [#2292](https://github.com/PHPOffice/PhpSpreadsheet/issues/2292) +- Fixed Trying to access array offset on value of type null (Xls Reader) [#2315](https://github.com/PHPOffice/PhpSpreadsheet/issues/2315) +- Don't corrupt XLSX files containing data validation [#2377](https://github.com/PHPOffice/PhpSpreadsheet/issues/2377) +- Non-fixed cells were not updated if shared formula has a fixed cell [#2354](https://github.com/PHPOffice/PhpSpreadsheet/issues/2354) +- Declare key of generic ArrayObject +- CSV reader better support for boolean values [#2374](https://github.com/PHPOffice/PhpSpreadsheet/pull/2374) +- Some ZIP file could not be read [#2376](https://github.com/PHPOffice/PhpSpreadsheet/pull/2376) +- Fix regression were hyperlinks could not be read [#2391](https://github.com/PHPOffice/PhpSpreadsheet/pull/2391) +- AutoFilter Improvements [#2393](https://github.com/PHPOffice/PhpSpreadsheet/pull/2393) +- Don't corrupt file when using chart with fill color [#589](https://github.com/PHPOffice/PhpSpreadsheet/pull/589) +- Restore imperfect array formula values in xlsx writer [#2343](https://github.com/PHPOffice/PhpSpreadsheet/pull/2343) +- Restore explicit list of changes to PHPExcel migration document [#1546](https://github.com/PHPOffice/PhpSpreadsheet/issues/1546) + +## 1.19.0 - 2021-10-31 + +### Added + +- Ability to set style on named range, and validate input to setSelectedCells [Issue #2279](https://github.com/PHPOffice/PhpSpreadsheet/issues/2279) [PR #2280](https://github.com/PHPOffice/PhpSpreadsheet/pull/2280) +- Process comments in Sylk file [Issue #2276](https://github.com/PHPOffice/PhpSpreadsheet/issues/2276) [PR #2277](https://github.com/PHPOffice/PhpSpreadsheet/pull/2277) +- Addition of Custom Properties to Ods Writer, and 32-bit-safe timestamps for Document Properties [PR #2113](https://github.com/PHPOffice/PhpSpreadsheet/pull/2113) +- Added callback to CSV reader to set user-specified defaults for various properties (especially for escape which has a poor PHP-inherited default of backslash which does not correspond with Excel) [PR #2103](https://github.com/PHPOffice/PhpSpreadsheet/pull/2103) +- Phase 1 of better namespace handling for Xlsx, resolving many open issues [PR #2173](https://github.com/PHPOffice/PhpSpreadsheet/pull/2173) [PR #2204](https://github.com/PHPOffice/PhpSpreadsheet/pull/2204) [PR #2303](https://github.com/PHPOffice/PhpSpreadsheet/pull/2303) +- Add ability to extract images if source is a URL [Issue #1997](https://github.com/PHPOffice/PhpSpreadsheet/issues/1997) [PR #2072](https://github.com/PHPOffice/PhpSpreadsheet/pull/2072) +- Support for passing flags in the Reader `load()` and Writer `save()`methods, and through the IOFactory, to set behaviours [PR #2136](https://github.com/PHPOffice/PhpSpreadsheet/pull/2136) + - See [documentation](https://phpspreadsheet.readthedocs.io/en/latest/topics/reading-and-writing-to-file/#readerwriter-flags) for details +- More flexibility in the StringValueBinder to determine what datatypes should be treated as strings [PR #2138](https://github.com/PHPOffice/PhpSpreadsheet/pull/2138) +- Helper class for conversion between css size Units of measure (`px`, `pt`, `pc`, `in`, `cm`, `mm`) [PR #2152](https://github.com/PHPOffice/PhpSpreadsheet/issues/2145) +- Allow Row height and Column Width to be set using different units of measure (`px`, `pt`, `pc`, `in`, `cm`, `mm`), rather than only in points or MS Excel column width units [PR #2152](https://github.com/PHPOffice/PhpSpreadsheet/issues/2145) +- Ability to stream to an Amazon S3 bucket [Issue #2249](https://github.com/PHPOffice/PhpSpreadsheet/issues/2249) +- Provided a Size Helper class to validate size values (pt, px, em) [PR #1694](https://github.com/PHPOffice/PhpSpreadsheet/pull/1694) + +### Changed + +- Nothing. + +### Deprecated + +- PHP 8.1 will deprecate auto_detect_line_endings. As a result of this change, Csv Reader using PHP8.1+ will no longer be able to handle a Csv with Mac line endings. + +### Removed + +- Nothing. + +### Fixed + +- Unexpected format in Xlsx Timestamp [Issue #2331](https://github.com/PHPOffice/PhpSpreadsheet/issues/2331) [PR #2332](https://github.com/PHPOffice/PhpSpreadsheet/pull/2332) +- Corrections for HLOOKUP [Issue #2123](https://github.com/PHPOffice/PhpSpreadsheet/issues/2123) [PR #2330](https://github.com/PHPOffice/PhpSpreadsheet/pull/2330) +- Corrections for Xlsx Read Comments [Issue #2316](https://github.com/PHPOffice/PhpSpreadsheet/issues/2316) [PR #2329](https://github.com/PHPOffice/PhpSpreadsheet/pull/2329) +- Lowercase Calibri font names [Issue #2273](https://github.com/PHPOffice/PhpSpreadsheet/issues/2273) [PR #2325](https://github.com/PHPOffice/PhpSpreadsheet/pull/2325) +- isFormula Referencing Sheet with Space in Title [Issue #2304](https://github.com/PHPOffice/PhpSpreadsheet/issues/2304) [PR #2306](https://github.com/PHPOffice/PhpSpreadsheet/pull/2306) +- Xls Reader Fatal Error due to Undefined Offset [Issue #1114](https://github.com/PHPOffice/PhpSpreadsheet/issues/1114) [PR #2308](https://github.com/PHPOffice/PhpSpreadsheet/pull/2308) +- Permit Csv Reader delimiter to be set to null [Issue #2287](https://github.com/PHPOffice/PhpSpreadsheet/issues/2287) [PR #2288](https://github.com/PHPOffice/PhpSpreadsheet/pull/2288) +- Csv Reader did not handle booleans correctly [PR #2232](https://github.com/PHPOffice/PhpSpreadsheet/pull/2232) +- Problems when deleting sheet with local defined name [Issue #2266](https://github.com/PHPOffice/PhpSpreadsheet/issues/2266) [PR #2284](https://github.com/PHPOffice/PhpSpreadsheet/pull/2284) +- Worksheet passwords were not always handled correctly [Issue #1897](https://github.com/PHPOffice/PhpSpreadsheet/issues/1897) [PR #2197](https://github.com/PHPOffice/PhpSpreadsheet/pull/2197) +- Gnumeric Reader will now distinguish between Created and Modified timestamp [PR #2133](https://github.com/PHPOffice/PhpSpreadsheet/pull/2133) +- Xls Reader will now handle MACCENTRALEUROPE with or without hyphen [Issue #549](https://github.com/PHPOffice/PhpSpreadsheet/issues/549) [PR #2213](https://github.com/PHPOffice/PhpSpreadsheet/pull/2213) +- Tweaks to input file validation [Issue #1718](https://github.com/PHPOffice/PhpSpreadsheet/issues/1718) [PR #2217](https://github.com/PHPOffice/PhpSpreadsheet/pull/2217) +- Html Reader did not handle comments correctly [Issue #2234](https://github.com/PHPOffice/PhpSpreadsheet/issues/2234) [PR #2235](https://github.com/PHPOffice/PhpSpreadsheet/pull/2235) +- Apache OpenOffice Uses Unexpected Case for General format [Issue #2239](https://github.com/PHPOffice/PhpSpreadsheet/issues/2239) [PR #2242](https://github.com/PHPOffice/PhpSpreadsheet/pull/2242) +- Problems with fraction formatting [Issue #2253](https://github.com/PHPOffice/PhpSpreadsheet/issues/2253) [PR #2254](https://github.com/PHPOffice/PhpSpreadsheet/pull/2254) +- Xlsx Reader had problems reading file with no styles.xml or empty styles.xml [Issue #2246](https://github.com/PHPOffice/PhpSpreadsheet/issues/2246) [PR #2247](https://github.com/PHPOffice/PhpSpreadsheet/pull/2247) +- Xlsx Reader did not read Data Validation flags correctly [Issue #2224](https://github.com/PHPOffice/PhpSpreadsheet/issues/2224) [PR #2225](https://github.com/PHPOffice/PhpSpreadsheet/pull/2225) +- Better handling of empty arguments in Calculation engine [PR #2143](https://github.com/PHPOffice/PhpSpreadsheet/pull/2143) +- Many fixes for Autofilter [Issue #2216](https://github.com/PHPOffice/PhpSpreadsheet/issues/2216) [PR #2141](https://github.com/PHPOffice/PhpSpreadsheet/pull/2141) [PR #2162](https://github.com/PHPOffice/PhpSpreadsheet/pull/2162) [PR #2218](https://github.com/PHPOffice/PhpSpreadsheet/pull/2218) +- Locale generator will now use Unix line endings even on Windows [Issue #2172](https://github.com/PHPOffice/PhpSpreadsheet/issues/2172) [PR #2174](https://github.com/PHPOffice/PhpSpreadsheet/pull/2174) +- Support differences in implementation of Text functions between Excel/Ods/Gnumeric [PR #2151](https://github.com/PHPOffice/PhpSpreadsheet/pull/2151) +- Fixes to places where PHP8.1 enforces new or previously unenforced restrictions [PR #2137](https://github.com/PHPOffice/PhpSpreadsheet/pull/2137) [PR #2191](https://github.com/PHPOffice/PhpSpreadsheet/pull/2191) [PR #2231](https://github.com/PHPOffice/PhpSpreadsheet/pull/2231) +- Clone for HashTable was incorrect [PR #2130](https://github.com/PHPOffice/PhpSpreadsheet/pull/2130) +- Xlsx Reader was not evaluating Document Security Lock correctly [PR #2128](https://github.com/PHPOffice/PhpSpreadsheet/pull/2128) +- Error in COUPNCD handling end of month [Issue #2116](https://github.com/PHPOffice/PhpSpreadsheet/issues/2116) [PR #2119](https://github.com/PHPOffice/PhpSpreadsheet/pull/2119) +- Xls Writer Parser did not handle concatenation operator correctly [PR #2080](https://github.com/PHPOffice/PhpSpreadsheet/pull/2080) +- Xlsx Writer did not handle boolean false correctly [Issue #2082](https://github.com/PHPOffice/PhpSpreadsheet/issues/2082) [PR #2087](https://github.com/PHPOffice/PhpSpreadsheet/pull/2087) +- SUM needs to treat invalid strings differently depending on whether they come from a cell or are used as literals [Issue #2042](https://github.com/PHPOffice/PhpSpreadsheet/issues/2042) [PR #2045](https://github.com/PHPOffice/PhpSpreadsheet/pull/2045) +- Html reader could have set illegal coordinates when dealing with embedded tables [Issue #2029](https://github.com/PHPOffice/PhpSpreadsheet/issues/2029) [PR #2032](https://github.com/PHPOffice/PhpSpreadsheet/pull/2032) +- Documentation for printing gridlines was wrong [PR #2188](https://github.com/PHPOffice/PhpSpreadsheet/pull/2188) +- Return Value Error - DatabaseAbstruct::buildQuery() return null but must be string [Issue #2158](https://github.com/PHPOffice/PhpSpreadsheet/issues/2158) [PR #2160](https://github.com/PHPOffice/PhpSpreadsheet/pull/2160) +- Xlsx reader not recognize data validations that references another sheet [Issue #1432](https://github.com/PHPOffice/PhpSpreadsheet/issues/1432) [Issue #2149](https://github.com/PHPOffice/PhpSpreadsheet/issues/2149) [PR #2150](https://github.com/PHPOffice/PhpSpreadsheet/pull/2150) [PR #2265](https://github.com/PHPOffice/PhpSpreadsheet/pull/2265) +- Don't calculate cell width for autosize columns if a cell contains a null or empty string value [Issue #2165](https://github.com/PHPOffice/PhpSpreadsheet/issues/2165) [PR #2167](https://github.com/PHPOffice/PhpSpreadsheet/pull/2167) +- Allow negative interest rate values in a number of the Financial functions (`PPMT()`, `PMT()`, `FV()`, `PV()`, `NPER()`, etc) [Issue #2163](https://github.com/PHPOffice/PhpSpreadsheet/issues/2163) [PR #2164](https://github.com/PHPOffice/PhpSpreadsheet/pull/2164) +- Xls Reader changing grey background to black in Excel template [Issue #2147](https://github.com/PHPOffice/PhpSpreadsheet/issues/2147) [PR #2156](https://github.com/PHPOffice/PhpSpreadsheet/pull/2156) +- Column width and Row height styles in the Html Reader when the value includes a unit of measure [Issue #2145](https://github.com/PHPOffice/PhpSpreadsheet/issues/2145). +- Data Validation flags not set correctly when reading XLSX files [Issue #2224](https://github.com/PHPOffice/PhpSpreadsheet/issues/2224) [PR #2225](https://github.com/PHPOffice/PhpSpreadsheet/pull/2225) +- Reading XLSX files without styles.xml throws an exception [Issue #2246](https://github.com/PHPOffice/PhpSpreadsheet/issues/2246) +- Improved performance of `Style::applyFromArray()` when applied to several cells [PR #1785](https://github.com/PHPOffice/PhpSpreadsheet/issues/1785). +- Improve XLSX parsing speed if no readFilter is applied (again) - [#772](https://github.com/PHPOffice/PhpSpreadsheet/issues/772) + +## 1.18.0 - 2021-05-31 + +### Added + +- Enhancements to CSV Reader, allowing options to be set when using `IOFactory::load()` with a callback to set delimiter, enclosure, charset etc [PR #2103](https://github.com/PHPOffice/PhpSpreadsheet/pull/2103) - See [documentation](https://github.com/PHPOffice/PhpSpreadsheet/blob/master/docs/topics/reading-and-writing-to-file.md#csv-comma-separated-values) for details. +- Implemented basic AutoFiltering for Ods Reader and Writer [PR #2053](https://github.com/PHPOffice/PhpSpreadsheet/pull/2053) +- Implemented basic AutoFiltering for Gnumeric Reader [PR #2055](https://github.com/PHPOffice/PhpSpreadsheet/pull/2055) +- Improved support for Row and Column ranges in formulae [Issue #1755](https://github.com/PHPOffice/PhpSpreadsheet/issues/1755) [PR #2028](https://github.com/PHPOffice/PhpSpreadsheet/pull/2028) +- Implemented URLENCODE() Web Function +- Implemented the CHITEST(), CHISQ.DIST() and CHISQ.INV() and equivalent Statistical functions, for both left- and right-tailed distributions. +- Support for ActiveSheet and SelectedCells in the ODS Reader and Writer [PR #1908](https://github.com/PHPOffice/PhpSpreadsheet/pull/1908) +- Support for notContainsText Conditional Style in xlsx [Issue #984](https://github.com/PHPOffice/PhpSpreadsheet/issues/984) + +### Changed + +- Use of `nb` rather than `no` as the locale code for Norsk Bokmål. + +### Deprecated + +- All Excel Function implementations in `Calculation\Database`, `Calculation\DateTime`, `Calculation\Engineering`, `Calculation\Financial`, `Calculation\Logical`, `Calculation\LookupRef`, `Calculation\MathTrig`, `Calculation\Statistical`, `Calculation\TextData` and `Calculation\Web` have been moved to dedicated classes for individual functions or groups of related functions. See the docblocks against all the deprecated methods for details of the new methods to call instead. At some point, these old classes will be deleted. + +### Removed + +- Use of `nb` rather than `no` as the locale language code for Norsk Bokmål. + +### Fixed + +- Fixed error in COUPNCD() calculation for end of month [Issue #2116](https://github.com/PHPOffice/PhpSpreadsheet/issues/2116) - [PR #2119](https://github.com/PHPOffice/PhpSpreadsheet/pull/2119) +- Resolve default values when a null argument is passed for HLOOKUP(), VLOOKUP() and ADDRESS() functions [Issue #2120](https://github.com/PHPOffice/PhpSpreadsheet/issues/2120) - [PR #2121](https://github.com/PHPOffice/PhpSpreadsheet/pull/2121) +- Fixed incorrect R1C1 to A1 subtraction formula conversion (`R[-2]C-R[2]C`) [Issue #2076](https://github.com/PHPOffice/PhpSpreadsheet/pull/2076) [PR #2086](https://github.com/PHPOffice/PhpSpreadsheet/pull/2086) +- Correctly handle absolute A1 references when converting to R1C1 format [PR #2060](https://github.com/PHPOffice/PhpSpreadsheet/pull/2060) +- Correct default fill style for conditional without a pattern defined [Issue #2035](https://github.com/PHPOffice/PhpSpreadsheet/issues/2035) [PR #2050](https://github.com/PHPOffice/PhpSpreadsheet/pull/2050) +- Fixed issue where array key check for existince before accessing arrays in Xlsx.php [PR #1970](https://github.com/PHPOffice/PhpSpreadsheet/pull/1970) +- Fixed issue with quoted strings in number format mask rendered with toFormattedString() [Issue 1972#](https://github.com/PHPOffice/PhpSpreadsheet/issues/1972) [PR #1978](https://github.com/PHPOffice/PhpSpreadsheet/pull/1978) +- Fixed issue with percentage formats in number format mask rendered with toFormattedString() [Issue 1929#](https://github.com/PHPOffice/PhpSpreadsheet/issues/1929) [PR #1928](https://github.com/PHPOffice/PhpSpreadsheet/pull/1928) +- Fixed issue with _ spacing character in number format mask corrupting output from toFormattedString() [Issue 1924#](https://github.com/PHPOffice/PhpSpreadsheet/issues/1924) [PR #1927](https://github.com/PHPOffice/PhpSpreadsheet/pull/1927) +- Fix for [Issue #1887](https://github.com/PHPOffice/PhpSpreadsheet/issues/1887) - Lose Track of Selected Cells After Save +- Fixed issue with Xlsx@listWorksheetInfo not returning any data +- Fixed invalid arguments triggering mb_substr() error in LEFT(), MID() and RIGHT() text functions [Issue #640](https://github.com/PHPOffice/PhpSpreadsheet/issues/640) +- Fix for [Issue #1916](https://github.com/PHPOffice/PhpSpreadsheet/issues/1916) - Invalid signature check for XML files +- Fix change in `Font::setSize()` behavior for PHP8 [PR #2100](https://github.com/PHPOffice/PhpSpreadsheet/pull/2100) + +## 1.17.1 - 2021-03-01 + +### Added + +- Implementation of the Excel `AVERAGEIFS()` functions as part of a restructuring of Database functions and Conditional Statistical functions. +- Support for date values and percentages in query parameters for Database functions, and the IF expressions in functions like COUNTIF() and AVERAGEIF(). [#1875](https://github.com/PHPOffice/PhpSpreadsheet/pull/1875) +- Support for booleans, and for wildcard text search in query parameters for Database functions, and the IF expressions in functions like COUNTIF() and AVERAGEIF(). [#1876](https://github.com/PHPOffice/PhpSpreadsheet/pull/1876) +- Implemented DataBar for conditional formatting in Xlsx, providing read/write and creation of (type, value, direction, fills, border, axis position, color settings) as DataBar options in Excel. [#1754](https://github.com/PHPOffice/PhpSpreadsheet/pull/1754) +- Alignment for ODS Writer [#1796](https://github.com/PHPOffice/PhpSpreadsheet/issues/1796) +- Basic implementation of the PERMUTATIONA() Statistical Function + +### Changed + +- Formula functions that previously called PHP functions directly are now processed through the Excel Functions classes; resolving issues with PHP8 stricter typing. [#1789](https://github.com/PHPOffice/PhpSpreadsheet/issues/1789) + + The following MathTrig functions are affected: + `ABS()`, `ACOS()`, `ACOSH()`, `ASIN()`, `ASINH()`, `ATAN()`, `ATANH()`, + `COS()`, `COSH()`, `DEGREES()` (rad2deg), `EXP()`, `LN()` (log), `LOG10()`, + `RADIANS()` (deg2rad), `SIN()`, `SINH()`, `SQRT()`, `TAN()`, `TANH()`. + + One TextData function is also affected: `REPT()` (str_repeat). +- `formatAsDate` correctly matches language metadata, reverting c55272e +- Formulae that previously crashed on sub function call returning excel error value now return said value. + The following functions are affected `CUMPRINC()`, `CUMIPMT()`, `AMORLINC()`, + `AMORDEGRC()`. +- Adapt some function error return value to match excel's error. + The following functions are affected `PPMT()`, `IPMT()`. + +### Deprecated + +- Calling many of the Excel formula functions directly rather than through the Calculation Engine. + + The logic for these Functions is now being moved out of the categorised `Database`, `DateTime`, `Engineering`, `Financial`, `Logical`, `LookupRef`, `MathTrig`, `Statistical`, `TextData` and `Web` classes into small, dedicated classes for individual functions or related groups of functions. + + This makes the logic in these classes easier to maintain; and will reduce the memory footprint required to execute formulae when calling these functions. + +### Removed + +- Nothing. + +### Fixed + +- Avoid Duplicate Titles When Reading Multiple HTML Files.[Issue #1823](https://github.com/PHPOffice/PhpSpreadsheet/issues/1823) [PR #1829](https://github.com/PHPOffice/PhpSpreadsheet/pull/1829) +- Fixed issue with Worksheet's `getCell()` method when trying to get a cell by defined name. [#1858](https://github.com/PHPOffice/PhpSpreadsheet/issues/1858) +- Fix possible endless loop in NumberFormat Masks [#1792](https://github.com/PHPOffice/PhpSpreadsheet/issues/1792) +- Fix problem resulting from literal dot inside quotes in number format masks [PR #1830](https://github.com/PHPOffice/PhpSpreadsheet/pull/1830) +- Resolve Google Sheets Xlsx charts issue. Google Sheets uses oneCellAnchor positioning and does not include *Cache values in the exported Xlsx [PR #1761](https://github.com/PHPOffice/PhpSpreadsheet/pull/1761) +- Fix for Xlsx Chart axis titles mapping to correct X or Y axis label when only one is present [PR #1760](https://github.com/PHPOffice/PhpSpreadsheet/pull/1760) +- Fix For Null Exception on ODS Read of Page Settings. [#1772](https://github.com/PHPOffice/PhpSpreadsheet/issues/1772) +- Fix Xlsx reader overriding manually set number format with builtin number format [PR #1805](https://github.com/PHPOffice/PhpSpreadsheet/pull/1805) +- Fix Xlsx reader cell alignment [PR #1710](https://github.com/PHPOffice/PhpSpreadsheet/pull/1710) +- Fix for not yet implemented data-types in Open Document writer [Issue #1674](https://github.com/PHPOffice/PhpSpreadsheet/issues/1674) +- Fix XLSX reader when having a corrupt numeric cell data type [PR #1664](https://github.com/phpoffice/phpspreadsheet/pull/1664) +- Fix on `CUMPRINC()`, `CUMIPMT()`, `AMORLINC()`, `AMORDEGRC()` usage. When those functions called one of `YEARFRAC()`, `PPMT()`, `IPMT()` and they would get back an error value (represented as a string), trying to use numeral operands (`+`, `/`, `-`, `*`) on said return value and a number (`float or `int`) would fail. + +## 1.16.0 - 2020-12-31 + +### Added + +- CSV Reader - Best Guess for Encoding, and Handle Null-string Escape [#1647](https://github.com/PHPOffice/PhpSpreadsheet/issues/1647) + +### Changed + +- Updated the CONVERT() function to support all current MS Excel categories and Units of Measure. + +### Deprecated + +- All Excel Function implementations in `Calculation\Database`, `Calculation\DateTime`, `Calculation\Engineering`, `Calculation\Financial`, `Calculation\Logical`, `Calculation\LookupRef`, `Calculation\MathTrig`, `Calculation\Statistical`, `Calculation\TextData` and `Calculation\Web` have been moved to dedicated classes for individual functions or groups of related functions. See the docblocks against all the deprecated methods for details of the new methods to call instead. At some point, these old classes will be deleted. + +### Removed + +- Nothing. + +### Fixed + +- Fixed issue with absolute path in worksheets' Target [PR #1769](https://github.com/PHPOffice/PhpSpreadsheet/pull/1769) +- Fix for Xls Reader when SST has a bad length [#1592](https://github.com/PHPOffice/PhpSpreadsheet/issues/1592) +- Resolve Xlsx loader issue whe hyperlinks don't have a destination +- Resolve issues when printer settings resources IDs clash with drawing IDs +- Resolve issue with SLK long filenames [#1612](https://github.com/PHPOffice/PhpSpreadsheet/issues/1612) +- ROUNDUP and ROUNDDOWN return incorrect results for values of 0 [#1627](https://github.com/phpoffice/phpspreadsheet/pull/1627) +- Apply Column and Row Styles to Existing Cells [#1712](https://github.com/PHPOffice/PhpSpreadsheet/issues/1712) [PR #1721](https://github.com/PHPOffice/PhpSpreadsheet/pull/1721) +- Resolve issues with defined names where worksheet doesn't exist (#1686)[https://github.com/PHPOffice/PhpSpreadsheet/issues/1686] and [#1723](https://github.com/PHPOffice/PhpSpreadsheet/issues/1723) - [PR #1742](https://github.com/PHPOffice/PhpSpreadsheet/pull/1742) +- Fix for issue [#1735](https://github.com/PHPOffice/PhpSpreadsheet/issues/1735) Incorrect activeSheetIndex after RemoveSheetByIndex - [PR #1743](https://github.com/PHPOffice/PhpSpreadsheet/pull/1743) +- Ensure that the list of shared formulae is maintained when an xlsx file is chunked with readFilter[Issue #169](https://github.com/PHPOffice/PhpSpreadsheet/issues/1669). +- Fix for notice during accessing "cached magnification factor" offset [#1354](https://github.com/PHPOffice/PhpSpreadsheet/pull/1354) +- Fix compatibility with ext-gd on php 8 + +### Security Fix (CVE-2020-7776) + +- Prevent XSS through cell comments in the HTML Writer. + +## 1.15.0 - 2020-10-11 + +### Added + +- Implemented Page Order for Xlsx and Xls Readers, and provided Page Settings (Orientation, Scale, Horizontal/Vertical Centering, Page Order, Margins) support for Ods, Gnumeric and Xls Readers [#1559](https://github.com/PHPOffice/PhpSpreadsheet/pull/1559) +- Implementation of the Excel `LOGNORM.DIST()`, `NORM.S.DIST()`, `GAMMA()` and `GAUSS()` functions. [#1588](https://github.com/PHPOffice/PhpSpreadsheet/pull/1588) +- Named formula implementation, and improved handling of Defined Names generally [#1535](https://github.com/PHPOffice/PhpSpreadsheet/pull/1535) + - Defined Names are now case-insensitive + - Distinction between named ranges and named formulae + - Correct handling of union and intersection operators in named ranges + - Correct evaluation of named range operators in calculations + - fix resolution of relative named range values in the calculation engine; previously all named range values had been treated as absolute. + - Calculation support for named formulae + - Support for nested ranges and formulae (named ranges and formulae that reference other named ranges/formulae) in calculations + - Introduction of a helper to convert address formats between R1C1 and A1 (and the reverse) + - Proper support for both named ranges and named formulae in all appropriate Readers + - **Xlsx** (Previously only simple named ranges were supported) + - **Xls** (Previously only simple named ranges were supported) + - **Gnumeric** (Previously neither named ranges nor formulae were supported) + - **Ods** (Previously neither named ranges nor formulae were supported) + - **Xml** (Previously neither named ranges nor formulae were supported) + - Proper support for named ranges and named formulae in all appropriate Writers + - **Xlsx** (Previously only simple named ranges were supported) + - **Xls** (Previously neither named ranges nor formulae were supported) - Still not supported, but some parser issues resolved that previously failed to differentiate between a defined name and a function name + - **Ods** (Previously neither named ranges nor formulae were supported) +- Support for PHP 8.0 + +### Changed + +- Improve Coverage for ODS Reader [#1545](https://github.com/phpoffice/phpspreadsheet/pull/1545) +- Named formula implementation, and improved handling of Defined Names generally [#1535](https://github.com/PHPOffice/PhpSpreadsheet/pull/1535) +- fix resolution of relative named range values in the calculation engine; previously all named range values had been treated as absolute. +- Drop $this->spreadSheet null check from Xlsx Writer [#1646](https://github.com/phpoffice/phpspreadsheet/pull/1646) +- Improving Coverage for Excel2003 XML Reader [#1557](https://github.com/phpoffice/phpspreadsheet/pull/1557) + +### Deprecated + +- **IMPORTANT NOTE:** This Introduces a **BC break** in the handling of named ranges. Previously, a named range cell reference of `B2` would be treated identically to a named range cell reference of `$B2` or `B$2` or `$B$2` because the calculation engine treated then all as absolute references. These changes "fix" that, so the calculation engine now handles relative references in named ranges correctly. + This change that resolves previously incorrect behaviour in the calculation may affect users who have dynamically defined named ranges using relative references when they should have used absolute references. + +### Removed + +- Nothing. + +### Fixed + +- PrintArea causes exception [#1544](https://github.com/phpoffice/phpspreadsheet/pull/1544) +- Calculation/DateTime Failure With PHP8 [#1661](https://github.com/phpoffice/phpspreadsheet/pull/1661) +- Reader/Gnumeric Failure with PHP8 [#1662](https://github.com/phpoffice/phpspreadsheet/pull/1662) +- ReverseSort bug, exposed but not caused by PHP8 [#1660](https://github.com/phpoffice/phpspreadsheet/pull/1660) +- Bug setting Superscript/Subscript to false [#1567](https://github.com/phpoffice/phpspreadsheet/pull/1567) + +## 1.14.1 - 2020-07-19 + +### Added + +- nothing + +### Fixed + +- WEBSERVICE is HTTP client agnostic and must be configured via `Settings::setHttpClient()` [#1562](https://github.com/PHPOffice/PhpSpreadsheet/issues/1562) +- Borders were not complete on rowspanned columns using HTML reader [#1473](https://github.com/PHPOffice/PhpSpreadsheet/pull/1473) + +### Changed + +## 1.14.0 - 2020-06-29 + +### Added + +- Add support for IFS() logical function [#1442](https://github.com/PHPOffice/PhpSpreadsheet/pull/1442) +- Add Cell Address Helper to provide conversions between the R1C1 and A1 address formats [#1558](https://github.com/PHPOffice/PhpSpreadsheet/pull/1558) +- Add ability to edit Html/Pdf before saving [#1499](https://github.com/PHPOffice/PhpSpreadsheet/pull/1499) +- Add ability to set codepage explicitly for BIFF5 [#1018](https://github.com/PHPOffice/PhpSpreadsheet/issues/1018) +- Added support for the WEBSERVICE function [#1409](https://github.com/PHPOffice/PhpSpreadsheet/pull/1409) + +### Fixed + +- Resolve evaluation of utf-8 named ranges in calculation engine [#1522](https://github.com/PHPOffice/PhpSpreadsheet/pull/1522) +- Fix HLOOKUP on single row [#1512](https://github.com/PHPOffice/PhpSpreadsheet/pull/1512) +- Fix MATCH when comparing different numeric types [#1521](https://github.com/PHPOffice/PhpSpreadsheet/pull/1521) +- Fix exact MATCH on ranges with empty cells [#1520](https://github.com/PHPOffice/PhpSpreadsheet/pull/1520) +- Fix for Issue [#1516](https://github.com/PHPOffice/PhpSpreadsheet/issues/1516) (Cloning worksheet makes corrupted Xlsx) [#1530](https://github.com/PHPOffice/PhpSpreadsheet/pull/1530) +- Fix For Issue [#1509](https://github.com/PHPOffice/PhpSpreadsheet/issues/1509) (Can not set empty enclosure for CSV) [#1518](https://github.com/PHPOffice/PhpSpreadsheet/pull/1518) +- Fix for Issue [#1505](https://github.com/PHPOffice/PhpSpreadsheet/issues/1505) (TypeError : Argument 4 passed to PhpOffice\PhpSpreadsheet\Writer\Xlsx\Worksheet::writeAttributeIf() must be of the type string) [#1525](https://github.com/PHPOffice/PhpSpreadsheet/pull/1525) +- Fix for Issue [#1495](https://github.com/PHPOffice/PhpSpreadsheet/issues/1495) (Sheet index being changed when multiple sheets are used in formula) [#1500]((https://github.com/PHPOffice/PhpSpreadsheet/pull/1500)) +- Fix for Issue [#1533](https://github.com/PHPOffice/PhpSpreadsheet/issues/1533) (A reference to a cell containing a string starting with "#" leads to errors in the generated xlsx.) [#1534](https://github.com/PHPOffice/PhpSpreadsheet/pull/1534) +- Xls Writer - Correct Timestamp Bug [#1493](https://github.com/PHPOffice/PhpSpreadsheet/pull/1493) +- Don't ouput row and columns without any cells in HTML writer [#1235](https://github.com/PHPOffice/PhpSpreadsheet/issues/1235) + +## 1.13.0 - 2020-05-31 + +### Added + +- Support writing to streams in all writers [#1292](https://github.com/PHPOffice/PhpSpreadsheet/issues/1292) +- Support CSV files with data wrapping a lot of lines [#1468](https://github.com/PHPOffice/PhpSpreadsheet/pull/1468) +- Support protection of worksheet by a specific hash algorithm [#1485](https://github.com/PHPOffice/PhpSpreadsheet/pull/1485) + +### Fixed + +- Fix Chart samples by updating chart parameter from 0 to DataSeries::EMPTY_AS_GAP [#1448](https://github.com/PHPOffice/PhpSpreadsheet/pull/1448) +- Fix return type in docblock for the Cells::get() [#1398](https://github.com/PHPOffice/PhpSpreadsheet/pull/1398) +- Fix RATE, PRICE, XIRR, and XNPV Functions [#1456](https://github.com/PHPOffice/PhpSpreadsheet/pull/1456) +- Save Excel 2010+ functions properly in XLSX [#1461](https://github.com/PHPOffice/PhpSpreadsheet/pull/1461) +- Several improvements in HTML writer [#1464](https://github.com/PHPOffice/PhpSpreadsheet/pull/1464) +- Fix incorrect behaviour when saving XLSX file with drawings [#1462](https://github.com/PHPOffice/PhpSpreadsheet/pull/1462), +- Fix Crash while trying setting a cell the value "123456\n" [#1476](https://github.com/PHPOffice/PhpSpreadsheet/pull/1481) +- Improved DATEDIF() function and reduced errors for Y and YM units [#1466](https://github.com/PHPOffice/PhpSpreadsheet/pull/1466) +- Stricter typing for mergeCells [#1494](https://github.com/PHPOffice/PhpSpreadsheet/pull/1494) + +### Changed + +- Drop support for PHP 7.1, according to https://phpspreadsheet.readthedocs.io/en/latest/#php-version-support +- Drop partial migration tool in favor of complete migration via RectorPHP [#1445](https://github.com/PHPOffice/PhpSpreadsheet/issues/1445) +- Limit composer package to `src/` [#1424](https://github.com/PHPOffice/PhpSpreadsheet/pull/1424) + +## 1.12.0 - 2020-04-27 + +### Added + +- Improved the ARABIC function to also handle short-hand roman numerals +- Added support for the FLOOR.MATH and FLOOR.PRECISE functions [#1351](https://github.com/PHPOffice/PhpSpreadsheet/pull/1351) + +### Fixed + +- Fix ROUNDUP and ROUNDDOWN for floating-point rounding error [#1404](https://github.com/PHPOffice/PhpSpreadsheet/pull/1404) +- Fix ROUNDUP and ROUNDDOWN for negative number [#1417](https://github.com/PHPOffice/PhpSpreadsheet/pull/1417) +- Fix loading styles from vmlDrawings when containing whitespace [#1347](https://github.com/PHPOffice/PhpSpreadsheet/issues/1347) +- Fix incorrect behavior when removing last row [#1365](https://github.com/PHPOffice/PhpSpreadsheet/pull/1365) +- MATCH with a static array should return the position of the found value based on the values submitted [#1332](https://github.com/PHPOffice/PhpSpreadsheet/pull/1332) +- Fix Xlsx Reader's handling of undefined fill color [#1353](https://github.com/PHPOffice/PhpSpreadsheet/pull/1353) + +## 1.11.0 - 2020-03-02 + +### Added + +- Added support for the BASE function +- Added support for the ARABIC function +- Conditionals - Extend Support for (NOT)CONTAINSBLANKS [#1278](https://github.com/PHPOffice/PhpSpreadsheet/pull/1278) + +### Fixed + +- Handle Error in Formula Processing Better for Xls [#1267](https://github.com/PHPOffice/PhpSpreadsheet/pull/1267) +- Handle ConditionalStyle NumberFormat When Reading Xlsx File [#1296](https://github.com/PHPOffice/PhpSpreadsheet/pull/1296) +- Fix Xlsx Writer's handling of decimal commas [#1282](https://github.com/PHPOffice/PhpSpreadsheet/pull/1282) +- Fix for issue by removing test code mistakenly left in [#1328](https://github.com/PHPOffice/PhpSpreadsheet/pull/1328) +- Fix for Xls writer wrong selected cells and active sheet [#1256](https://github.com/PHPOffice/PhpSpreadsheet/pull/1256) +- Fix active cell when freeze pane is used [#1323](https://github.com/PHPOffice/PhpSpreadsheet/pull/1323) +- Fix XLSX file loading with autofilter containing '$' [#1326](https://github.com/PHPOffice/PhpSpreadsheet/pull/1326) +- PHPDoc - Use `@return $this` for fluent methods [#1362](https://github.com/PHPOffice/PhpSpreadsheet/pull/1362) + +## 1.10.1 - 2019-12-02 + +### Changed + +- PHP 7.4 compatibility + +### Fixed + +- FLOOR() function accept negative number and negative significance [#1245](https://github.com/PHPOffice/PhpSpreadsheet/pull/1245) +- Correct column style even when using rowspan [#1249](https://github.com/PHPOffice/PhpSpreadsheet/pull/1249) +- Do not confuse defined names and cell refs [#1263](https://github.com/PHPOffice/PhpSpreadsheet/pull/1263) +- XLSX reader/writer keep decimal for floats with a zero decimal part [#1262](https://github.com/PHPOffice/PhpSpreadsheet/pull/1262) +- ODS writer prevent invalid numeric value if locale decimal separator is comma [#1268](https://github.com/PHPOffice/PhpSpreadsheet/pull/1268) +- Xlsx writer actually writes plotVisOnly and dispBlanksAs from chart properties [#1266](https://github.com/PHPOffice/PhpSpreadsheet/pull/1266) + +## 1.10.0 - 2019-11-18 + +### Changed + +- Change license from LGPL 2.1 to MIT [#140](https://github.com/PHPOffice/PhpSpreadsheet/issues/140) + +### Added + +- Implementation of IFNA() logical function +- Support "showZeros" worksheet option to change how Excel shows and handles "null" values returned from a calculation +- Allow HTML Reader to accept HTML as a string into an existing spreadsheet [#1212](https://github.com/PHPOffice/PhpSpreadsheet/pull/1212) + +### Fixed + +- IF implementation properly handles the value `#N/A` [#1165](https://github.com/PHPOffice/PhpSpreadsheet/pull/1165) +- Formula Parser: Wrong line count for stuff like "MyOtherSheet!A:D" [#1215](https://github.com/PHPOffice/PhpSpreadsheet/issues/1215) +- Call garbage collector after removing a column to prevent stale cached values +- Trying to remove a column that doesn't exist deletes the latest column +- Keep big integer as integer instead of lossely casting to float [#874](https://github.com/PHPOffice/PhpSpreadsheet/pull/874) +- Fix branch pruning handling of non boolean conditions [#1167](https://github.com/PHPOffice/PhpSpreadsheet/pull/1167) +- Fix ODS Reader when no DC namespace are defined [#1182](https://github.com/PHPOffice/PhpSpreadsheet/pull/1182) +- Fixed Functions->ifCondition for allowing <> and empty condition [#1206](https://github.com/PHPOffice/PhpSpreadsheet/pull/1206) +- Validate XIRR inputs and return correct error values [#1120](https://github.com/PHPOffice/PhpSpreadsheet/issues/1120) +- Allow to read xlsx files with exotic workbook names like "workbook2.xml" [#1183](https://github.com/PHPOffice/PhpSpreadsheet/pull/1183) + +## 1.9.0 - 2019-08-17 + +### Changed + +- Drop support for PHP 5.6 and 7.0, according to https://phpspreadsheet.readthedocs.io/en/latest/#php-version-support + +### Added + +- When <br> appears in a table cell, set the cell to wrap [#1071](https://github.com/PHPOffice/PhpSpreadsheet/issues/1071) and [#1070](https://github.com/PHPOffice/PhpSpreadsheet/pull/1070) +- Add MAXIFS, MINIFS, COUNTIFS and Remove MINIF, MAXIF [#1056](https://github.com/PHPOffice/PhpSpreadsheet/issues/1056) +- HLookup needs an ordered list even if range_lookup is set to false [#1055](https://github.com/PHPOffice/PhpSpreadsheet/issues/1055) and [#1076](https://github.com/PHPOffice/PhpSpreadsheet/pull/1076) +- Improve performance of IF function calls via ranch pruning to avoid resolution of every branches [#844](https://github.com/PHPOffice/PhpSpreadsheet/pull/844) +- MATCH function supports `*?~` Excel functionality, when match_type=0 [#1116](https://github.com/PHPOffice/PhpSpreadsheet/issues/1116) +- Allow HTML Reader to accept HTML as a string [#1136](https://github.com/PHPOffice/PhpSpreadsheet/pull/1136) + +### Fixed + +- Fix to AVERAGEIF() function when called with a third argument +- Eliminate duplicate fill none style entries [#1066](https://github.com/PHPOffice/PhpSpreadsheet/issues/1066) +- Fix number format masks containing literal (non-decimal point) dots [#1079](https://github.com/PHPOffice/PhpSpreadsheet/issues/1079) +- Fix number format masks containing named colours that were being misinterpreted as date formats; and add support for masks that fully replace the value with a full text string [#1009](https://github.com/PHPOffice/PhpSpreadsheet/issues/1009) +- Stricter-typed comparison testing in COUNTIF() and COUNTIFS() evaluation [#1046](https://github.com/PHPOffice/PhpSpreadsheet/issues/1046) +- COUPNUM should not return zero when settlement is in the last period [#1020](https://github.com/PHPOffice/PhpSpreadsheet/issues/1020) and [#1021](https://github.com/PHPOffice/PhpSpreadsheet/pull/1021) +- Fix handling of named ranges referencing sheets with spaces or "!" in their title +- Cover `getSheetByName()` with tests for name with quote and spaces [#739](https://github.com/PHPOffice/PhpSpreadsheet/issues/739) +- Best effort to support invalid colspan values in HTML reader - [#878](https://github.com/PHPOffice/PhpSpreadsheet/pull/878) +- Fixes incorrect rows deletion [#868](https://github.com/PHPOffice/PhpSpreadsheet/issues/868) +- MATCH function fix (value search by type, stop search when match_type=-1 and unordered element encountered) [#1116](https://github.com/PHPOffice/PhpSpreadsheet/issues/1116) +- Fix `getCalculatedValue()` error with more than two INDIRECT [#1115](https://github.com/PHPOffice/PhpSpreadsheet/pull/1115) +- Writer\Html did not hide columns [#985](https://github.com/PHPOffice/PhpSpreadsheet/pull/985) + +## 1.8.2 - 2019-07-08 + +### Fixed + +- Uncaught error when opening ods file and properties aren't defined [#1047](https://github.com/PHPOffice/PhpSpreadsheet/issues/1047) +- Xlsx Reader Cell datavalidations bug [#1052](https://github.com/PHPOffice/PhpSpreadsheet/pull/1052) + +## 1.8.1 - 2019-07-02 + +### Fixed + +- Allow nullable theme for Xlsx Style Reader class [#1043](https://github.com/PHPOffice/PhpSpreadsheet/issues/1043) + +## 1.8.0 - 2019-07-01 + +### Security Fix (CVE-2019-12331) + +- Detect double-encoded xml in the Security scanner, and reject as suspicious. +- This change also broadens the scope of the `libxml_disable_entity_loader` setting when reading XML-based formats, so that it is enabled while the xml is being parsed and not simply while it is loaded. + On some versions of PHP, this can cause problems because it is not thread-safe, and can affect other PHP scripts running on the same server. This flag is set to true when instantiating a loader, and back to its original setting when the Reader is no longer in scope, or manually unset. +- Provide a check to identify whether libxml_disable_entity_loader is thread-safe or not. + + `XmlScanner::threadSafeLibxmlDisableEntityLoaderAvailability()` +- Provide an option to disable the libxml_disable_entity_loader call through settings. This is not recommended as it reduces the security of the XML-based readers, and should only be used if you understand the consequences and have no other choice. + +### Added + +- Added support for the SWITCH function [#963](https://github.com/PHPOffice/PhpSpreadsheet/issues/963) and [#983](https://github.com/PHPOffice/PhpSpreadsheet/pull/983) +- Add accounting number format style [#974](https://github.com/PHPOffice/PhpSpreadsheet/pull/974) + +### Fixed + +- Whitelist `tsv` extension when opening CSV files [#429](https://github.com/PHPOffice/PhpSpreadsheet/issues/429) +- Fix a SUMIF warning with some versions of PHP when having different length of arrays provided as input [#873](https://github.com/PHPOffice/PhpSpreadsheet/pull/873) +- Fix incorrectly handled backslash-escaped space characters in number format + +## 1.7.0 - 2019-05-26 + +- Added support for inline styles in Html reader (borders, alignment, width, height) +- QuotedText cells no longer treated as formulae if the content begins with a `=` +- Clean handling for DDE in formulae + +### Fixed + +- Fix handling for escaped enclosures and new lines in CSV Separator Inference +- Fix MATCH an error was appearing when comparing strings against 0 (always true) +- Fix wrong calculation of highest column with specified row [#700](https://github.com/PHPOffice/PhpSpreadsheet/issues/700) +- Fix VLOOKUP +- Fix return type hint + +## 1.6.0 - 2019-01-02 + +### Added + +- Refactored Matrix Functions to use external Matrix library +- Possibility to specify custom colors of values for pie and donut charts [#768](https://github.com/PHPOffice/PhpSpreadsheet/pull/768) + +### Fixed + +- Improve XLSX parsing speed if no readFilter is applied [#772](https://github.com/PHPOffice/PhpSpreadsheet/issues/772) +- Fix column names if read filter calls in XLSX reader skip columns [#777](https://github.com/PHPOffice/PhpSpreadsheet/pull/777) +- XLSX reader can now ignore blank cells, using the setReadEmptyCells(false) method. [#810](https://github.com/PHPOffice/PhpSpreadsheet/issues/810) +- Fix LOOKUP function which was breaking on edge cases [#796](https://github.com/PHPOffice/PhpSpreadsheet/issues/796) +- Fix VLOOKUP with exact matches [#809](https://github.com/PHPOffice/PhpSpreadsheet/pull/809) +- Support COUNTIFS multiple arguments [#830](https://github.com/PHPOffice/PhpSpreadsheet/pull/830) +- Change `libxml_disable_entity_loader()` as shortly as possible [#819](https://github.com/PHPOffice/PhpSpreadsheet/pull/819) +- Improved memory usage and performance when loading large spreadsheets [#822](https://github.com/PHPOffice/PhpSpreadsheet/pull/822) +- Improved performance when loading large spreadsheets [#825](https://github.com/PHPOffice/PhpSpreadsheet/pull/825) +- Improved performance when loading large spreadsheets [#824](https://github.com/PHPOffice/PhpSpreadsheet/pull/824) +- Fix color from CSS when reading from HTML [#831](https://github.com/PHPOffice/PhpSpreadsheet/pull/831) +- Fix infinite loop when reading invalid ODS files [#832](https://github.com/PHPOffice/PhpSpreadsheet/pull/832) +- Fix time format for duration is incorrect [#666](https://github.com/PHPOffice/PhpSpreadsheet/pull/666) +- Fix iconv unsupported `//IGNORE//TRANSLIT` on IBM i [#791](https://github.com/PHPOffice/PhpSpreadsheet/issues/791) + +### Changed + +- `master` is the new default branch, `develop` does not exist anymore + +## 1.5.2 - 2018-11-25 + +### Security + +- Improvements to the design of the XML Security Scanner [#771](https://github.com/PHPOffice/PhpSpreadsheet/issues/771) + +## 1.5.1 - 2018-11-20 + +### Security + +- Fix and improve XXE security scanning for XML-based and HTML Readers [#771](https://github.com/PHPOffice/PhpSpreadsheet/issues/771) + +### Added + +- Support page margin in mPDF [#750](https://github.com/PHPOffice/PhpSpreadsheet/issues/750) + +### Fixed + +- Support numeric condition in SUMIF, SUMIFS, AVERAGEIF, COUNTIF, MAXIF and MINIF [#683](https://github.com/PHPOffice/PhpSpreadsheet/issues/683) +- SUMIFS containing multiple conditions [#704](https://github.com/PHPOffice/PhpSpreadsheet/issues/704) +- Csv reader avoid notice when the file is empty [#743](https://github.com/PHPOffice/PhpSpreadsheet/pull/743) +- Fix print area parser for XLSX reader [#734](https://github.com/PHPOffice/PhpSpreadsheet/pull/734) +- Support overriding `DefaultValueBinder::dataTypeForValue()` without overriding `DefaultValueBinder::bindValue()` [#735](https://github.com/PHPOffice/PhpSpreadsheet/pull/735) +- Mpdf export can exceed pcre.backtrack_limit [#637](https://github.com/PHPOffice/PhpSpreadsheet/issues/637) +- Fix index overflow on data values array [#748](https://github.com/PHPOffice/PhpSpreadsheet/pull/748) + +## 1.5.0 - 2018-10-21 + +### Added + +- PHP 7.3 support +- Add the DAYS() function [#594](https://github.com/PHPOffice/PhpSpreadsheet/pull/594) + +### Fixed + +- Sheet title can contain exclamation mark [#325](https://github.com/PHPOffice/PhpSpreadsheet/issues/325) +- Xls file cause the exception during open by Xls reader [#402](https://github.com/PHPOffice/PhpSpreadsheet/issues/402) +- Skip non numeric value in SUMIF [#618](https://github.com/PHPOffice/PhpSpreadsheet/pull/618) +- OFFSET should allow omitted height and width [#561](https://github.com/PHPOffice/PhpSpreadsheet/issues/561) +- Correctly determine delimiter when CSV contains line breaks inside enclosures [#716](https://github.com/PHPOffice/PhpSpreadsheet/issues/716) + +## 1.4.1 - 2018-09-30 + +### Fixed + +- Remove locale from formatting string [#644](https://github.com/PHPOffice/PhpSpreadsheet/pull/644) +- Allow iterators to go out of bounds with prev [#587](https://github.com/PHPOffice/PhpSpreadsheet/issues/587) +- Fix warning when reading xlsx without styles [#631](https://github.com/PHPOffice/PhpSpreadsheet/pull/631) +- Fix broken sample links on windows due to $baseDir having backslash [#653](https://github.com/PHPOffice/PhpSpreadsheet/pull/653) + +## 1.4.0 - 2018-08-06 + +### Added + +- Add excel function EXACT(value1, value2) support [#595](https://github.com/PHPOffice/PhpSpreadsheet/pull/595) +- Support workbook view attributes for Xlsx format [#523](https://github.com/PHPOffice/PhpSpreadsheet/issues/523) +- Read and write hyperlink for drawing image [#490](https://github.com/PHPOffice/PhpSpreadsheet/pull/490) +- Added calculation engine support for the new bitwise functions that were added in MS Excel 2013 + - BITAND() Returns a Bitwise 'And' of two numbers + - BITOR() Returns a Bitwise 'Or' of two number + - BITXOR() Returns a Bitwise 'Exclusive Or' of two numbers + - BITLSHIFT() Returns a number shifted left by a specified number of bits + - BITRSHIFT() Returns a number shifted right by a specified number of bits +- Added calculation engine support for other new functions that were added in MS Excel 2013 and MS Excel 2016 + - Text Functions + - CONCAT() Synonym for CONCATENATE() + - NUMBERVALUE() Converts text to a number, in a locale-independent way + - UNICHAR() Synonym for CHAR() in PHPSpreadsheet, which has always used UTF-8 internally + - UNIORD() Synonym for ORD() in PHPSpreadsheet, which has always used UTF-8 internally + - TEXTJOIN() Joins together two or more text strings, separated by a delimiter + - Logical Functions + - XOR() Returns a logical Exclusive Or of all arguments + - Date/Time Functions + - ISOWEEKNUM() Returns the ISO 8601 week number of the year for a given date + - Lookup and Reference Functions + - FORMULATEXT() Returns a formula as a string + - Financial Functions + - PDURATION() Calculates the number of periods required for an investment to reach a specified value + - RRI() Calculates the interest rate required for an investment to grow to a specified future value + - Engineering Functions + - ERF.PRECISE() Returns the error function integrated between 0 and a supplied limit + - ERFC.PRECISE() Synonym for ERFC + - Math and Trig Functions + - SEC() Returns the secant of an angle + - SECH() Returns the hyperbolic secant of an angle + - CSC() Returns the cosecant of an angle + - CSCH() Returns the hyperbolic cosecant of an angle + - COT() Returns the cotangent of an angle + - COTH() Returns the hyperbolic cotangent of an angle + - ACOT() Returns the cotangent of an angle + - ACOTH() Returns the hyperbolic cotangent of an angle +- Refactored Complex Engineering Functions to use external complex number library +- Added calculation engine support for the new complex number functions that were added in MS Excel 2013 + - IMCOSH() Returns the hyperbolic cosine of a complex number + - IMCOT() Returns the cotangent of a complex number + - IMCSC() Returns the cosecant of a complex number + - IMCSCH() Returns the hyperbolic cosecant of a complex number + - IMSEC() Returns the secant of a complex number + - IMSECH() Returns the hyperbolic secant of a complex number + - IMSINH() Returns the hyperbolic sine of a complex number + - IMTAN() Returns the tangent of a complex number + +### Fixed + +- Fix ISFORMULA() function to work with a cell reference to another worksheet +- Xlsx reader crashed when reading a file with workbook protection [#553](https://github.com/PHPOffice/PhpSpreadsheet/pull/553) +- Cell formats with escaped spaces were causing incorrect date formatting [#557](https://github.com/PHPOffice/PhpSpreadsheet/issues/557) +- Could not open CSV file containing HTML fragment [#564](https://github.com/PHPOffice/PhpSpreadsheet/issues/564) +- Exclude the vendor folder in migration [#481](https://github.com/PHPOffice/PhpSpreadsheet/issues/481) +- Chained operations on cell ranges involving borders operated on last cell only [#428](https://github.com/PHPOffice/PhpSpreadsheet/issues/428) +- Avoid memory exhaustion when cloning worksheet with a drawing [#437](https://github.com/PHPOffice/PhpSpreadsheet/issues/437) +- Migration tool keep variables containing $PHPExcel untouched [#598](https://github.com/PHPOffice/PhpSpreadsheet/issues/598) +- Rowspans/colspans were incorrect when adding worksheet using loadIntoExisting [#619](https://github.com/PHPOffice/PhpSpreadsheet/issues/619) + +## 1.3.1 - 2018-06-12 + +### Fixed + +- Ranges across Z and AA columns incorrectly threw an exception [#545](https://github.com/PHPOffice/PhpSpreadsheet/issues/545) + +## 1.3.0 - 2018-06-10 + +### Added + +- Support to read Xlsm templates with form elements, macros, printer settings, protected elements and back compatibility drawing, and save result without losing important elements of document [#435](https://github.com/PHPOffice/PhpSpreadsheet/issues/435) +- Expose sheet title maximum length as `Worksheet::SHEET_TITLE_MAXIMUM_LENGTH` [#482](https://github.com/PHPOffice/PhpSpreadsheet/issues/482) +- Allow escape character to be set in CSV reader [#492](https://github.com/PHPOffice/PhpSpreadsheet/issues/492) + +### Fixed + +- Subtotal 9 in a group that has other subtotals 9 exclude the totals of the other subtotals in the range [#332](https://github.com/PHPOffice/PhpSpreadsheet/issues/332) +- `Helper\Html` support UTF-8 HTML input [#444](https://github.com/PHPOffice/PhpSpreadsheet/issues/444) +- Xlsx loaded an extra empty comment for each real comment [#375](https://github.com/PHPOffice/PhpSpreadsheet/issues/375) +- Xlsx reader do not read rows and columns filtered out in readFilter at all [#370](https://github.com/PHPOffice/PhpSpreadsheet/issues/370) +- Make newer Excel versions properly recalculate formulas on document open [#456](https://github.com/PHPOffice/PhpSpreadsheet/issues/456) +- `Coordinate::extractAllCellReferencesInRange()` throws an exception for an invalid range [#519](https://github.com/PHPOffice/PhpSpreadsheet/issues/519) +- Fixed parsing of conditionals in COUNTIF functions [#526](https://github.com/PHPOffice/PhpSpreadsheet/issues/526) +- Corruption errors for saved Xlsx docs with frozen panes [#532](https://github.com/PHPOffice/PhpSpreadsheet/issues/532) + +## 1.2.1 - 2018-04-10 + +### Fixed + +- Plain text and richtext mixed in same cell can be read [#442](https://github.com/PHPOffice/PhpSpreadsheet/issues/442) + +## 1.2.0 - 2018-03-04 + +### Added + +- HTML writer creates a generator meta tag [#312](https://github.com/PHPOffice/PhpSpreadsheet/issues/312) +- Support invalid zoom value in XLSX format [#350](https://github.com/PHPOffice/PhpSpreadsheet/pull/350) +- Support for `_xlfn.` prefixed functions and `ISFORMULA`, `MODE.SNGL`, `STDEV.S`, `STDEV.P` [#390](https://github.com/PHPOffice/PhpSpreadsheet/pull/390) + +### Fixed + +- Avoid potentially unsupported PSR-16 cache keys [#354](https://github.com/PHPOffice/PhpSpreadsheet/issues/354) +- Check for MIME type to know if CSV reader can read a file [#167](https://github.com/PHPOffice/PhpSpreadsheet/issues/167) +- Use proper € symbol for currency format [#379](https://github.com/PHPOffice/PhpSpreadsheet/pull/379) +- Read printing area correctly when skipping some sheets [#371](https://github.com/PHPOffice/PhpSpreadsheet/issues/371) +- Avoid incorrectly overwriting calculated value type [#394](https://github.com/PHPOffice/PhpSpreadsheet/issues/394) +- Select correct cell when calling freezePane [#389](https://github.com/PHPOffice/PhpSpreadsheet/issues/389) +- `setStrikethrough()` did not set the font [#403](https://github.com/PHPOffice/PhpSpreadsheet/issues/403) + +## 1.1.0 - 2018-01-28 + +### Added + +- Support for PHP 7.2 +- Support cell comments in HTML writer and reader [#308](https://github.com/PHPOffice/PhpSpreadsheet/issues/308) +- Option to stop at a conditional styling, if it matches (only XLSX format) [#292](https://github.com/PHPOffice/PhpSpreadsheet/pull/292) +- Support for line width for data series when rendering Xlsx [#329](https://github.com/PHPOffice/PhpSpreadsheet/pull/329) + +### Fixed + +- Better auto-detection of CSV separators [#305](https://github.com/PHPOffice/PhpSpreadsheet/issues/305) +- Support for shape style ending with `;` [#304](https://github.com/PHPOffice/PhpSpreadsheet/issues/304) +- Freeze Panes takes wrong coordinates for XLSX [#322](https://github.com/PHPOffice/PhpSpreadsheet/issues/322) +- `COLUMNS` and `ROWS` functions crashed in some cases [#336](https://github.com/PHPOffice/PhpSpreadsheet/issues/336) +- Support XML file without styles [#331](https://github.com/PHPOffice/PhpSpreadsheet/pull/331) +- Cell coordinates which are already a range cause an exception [#319](https://github.com/PHPOffice/PhpSpreadsheet/issues/319) + +## 1.0.0 - 2017-12-25 + +### Added + +- Support to write merged cells in ODS format [#287](https://github.com/PHPOffice/PhpSpreadsheet/issues/287) +- Able to set the `topLeftCell` in freeze panes [#261](https://github.com/PHPOffice/PhpSpreadsheet/pull/261) +- Support `DateTimeImmutable` as cell value +- Support migration of prefixed classes + +### Fixed + +- Can read very small HTML files [#194](https://github.com/PHPOffice/PhpSpreadsheet/issues/194) +- Written DataValidation was corrupted [#290](https://github.com/PHPOffice/PhpSpreadsheet/issues/290) +- Date format compatible with both LibreOffice and Excel [#298](https://github.com/PHPOffice/PhpSpreadsheet/issues/298) + +### BREAKING CHANGE + +- Constant `TYPE_DOUGHTNUTCHART` is now `TYPE_DOUGHNUTCHART`. + +## 1.0.0-beta2 - 2017-11-26 + +### Added + +- Support for chart fill color - @CrazyBite [#158](https://github.com/PHPOffice/PhpSpreadsheet/pull/158) +- Support for read Hyperlink for xml - @GreatHumorist [#223](https://github.com/PHPOffice/PhpSpreadsheet/pull/223) +- Support for cell value validation according to data validation rules - @SailorMax [#257](https://github.com/PHPOffice/PhpSpreadsheet/pull/257) +- Support for custom implementation, or configuration, of PDF libraries - @SailorMax [#266](https://github.com/PHPOffice/PhpSpreadsheet/pull/266) + +### Changed + +- Merge data-validations to reduce written worksheet size - @billblume [#131](https://github.com/PHPOffice/PhpSpreadSheet/issues/131) +- Throws exception if a XML file is invalid - @GreatHumorist [#222](https://github.com/PHPOffice/PhpSpreadsheet/pull/222) +- Upgrade to mPDF 7.0+ [#144](https://github.com/PHPOffice/PhpSpreadsheet/issues/144) + +### Fixed + +- Control characters in cell values are automatically escaped [#212](https://github.com/PHPOffice/PhpSpreadsheet/issues/212) +- Prevent color changing when copy/pasting xls files written by PhpSpreadsheet to another file - @al-lala [#218](https://github.com/PHPOffice/PhpSpreadsheet/issues/218) +- Add cell reference automatic when there is no cell reference('r' attribute) in Xlsx file. - @GreatHumorist [#225](https://github.com/PHPOffice/PhpSpreadsheet/pull/225) Refer to [#201](https://github.com/PHPOffice/PhpSpreadsheet/issues/201) +- `Reader\Xlsx::getFromZipArchive()` function return false if the zip entry could not be located. - @anton-harvey [#268](https://github.com/PHPOffice/PhpSpreadsheet/pull/268) + +### BREAKING CHANGE + +- Extracted coordinate method to dedicate class [migration guide](./docs/topics/migration-from-PHPExcel.md). +- Column indexes are based on 1, see the [migration guide](./docs/topics/migration-from-PHPExcel.md). +- Standardization of array keys used for style, see the [migration guide](./docs/topics/migration-from-PHPExcel.md). +- Easier usage of PDF writers, and other custom readers and writers, see the [migration guide](./docs/topics/migration-from-PHPExcel.md). +- Easier usage of chart renderers, see the [migration guide](./docs/topics/migration-from-PHPExcel.md). +- Rename a few more classes to keep them in their related namespaces: + - `CalcEngine` => `Calculation\Engine` + - `PhpSpreadsheet\Calculation` => `PhpSpreadsheet\Calculation\Calculation` + - `PhpSpreadsheet\Cell` => `PhpSpreadsheet\Cell\Cell` + - `PhpSpreadsheet\Chart` => `PhpSpreadsheet\Chart\Chart` + - `PhpSpreadsheet\RichText` => `PhpSpreadsheet\RichText\RichText` + - `PhpSpreadsheet\Style` => `PhpSpreadsheet\Style\Style` + - `PhpSpreadsheet\Worksheet` => `PhpSpreadsheet\Worksheet\Worksheet` + +## 1.0.0-beta - 2017-08-17 + +### Added + +- Initial implementation of SUMIFS() function +- Additional codepages +- MemoryDrawing not working in HTML writer [#808](https://github.com/PHPOffice/PHPExcel/issues/808) +- CSV Reader can auto-detect the separator used in file [#141](https://github.com/PHPOffice/PhpSpreadsheet/pull/141) +- HTML Reader supports some basic inline styles [#180](https://github.com/PHPOffice/PhpSpreadsheet/pull/180) + +### Changed + +- Start following [SemVer](https://semver.org) properly. + +### Fixed + +- Fix to getCell() method when cell reference includes a worksheet reference - @MarkBaker +- Ignore inlineStr type if formula element exists - @ncrypthic [#570](https://github.com/PHPOffice/PHPExcel/issues/570) +- Excel 2007 Reader freezes because of conditional formatting - @rentalhost [#575](https://github.com/PHPOffice/PHPExcel/issues/575) +- Readers will now parse files containing worksheet titles over 31 characters [#176](https://github.com/PHPOffice/PhpSpreadsheet/pull/176) +- Fixed PHP8 deprecation warning for libxml_disable_entity_loader() [#1625](https://github.com/phpoffice/phpspreadsheet/pull/1625) + +### General + +- Whitespace after toRichTextObject() - @MarkBaker [#554](https://github.com/PHPOffice/PHPExcel/issues/554) +- Optimize vlookup() sort - @umpirsky [#548](https://github.com/PHPOffice/PHPExcel/issues/548) +- c:max and c:min elements shall NOT be inside c:orientation elements - @vitalyrepin [#869](https://github.com/PHPOffice/PHPExcel/pull/869) +- Implement actual timezone adjustment into PHPExcel_Shared_Date::PHPToExcel - @sim642 [#489](https://github.com/PHPOffice/PHPExcel/pull/489) + +### BREAKING CHANGE + +- Introduction of namespaces for all classes, eg: `PHPExcel_Calculation_Functions` becomes `PhpOffice\PhpSpreadsheet\Calculation\Functions` +- Some classes were renamed for clarity and/or consistency: + +For a comprehensive list of all class changes, and a semi-automated migration path, read the [migration guide](./docs/topics/migration-from-PHPExcel.md). + +- Dropped `PHPExcel_Calculation_Functions::VERSION()`. Composer or git should be used to know the version. +- Dropped `PHPExcel_Settings::setPdfRenderer()` and `PHPExcel_Settings::setPdfRenderer()`. Composer should be used to autoload PDF libs. +- Dropped support for HHVM + +## Previous versions of PHPExcel + +The changelog for the project when it was called PHPExcel is [still available](./CHANGELOG.PHPExcel.md). diff --git a/vendor/phpoffice/phpspreadsheet/CONTRIBUTING.md b/vendor/phpoffice/phpspreadsheet/CONTRIBUTING.md new file mode 100644 index 0000000..f595353 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/CONTRIBUTING.md @@ -0,0 +1,20 @@ +# Want to contribute? + +If you would like to contribute, here are some notes and guidelines: + + - All new development happens on feature/fix branches, and are then merged to the `master` branch once stable; so the `master` branch is always the most up-to-date, working code + - Tagged releases are made from the `master` branch + - If you are going to be submitting a pull request, please fork from `master`, and submit your pull request back as a fix/feature branch referencing the GitHub issue number + - Code style might be automatically fixed by `composer fix` + - All code changes must be validated by `composer check` + - [Helpful article about forking](https://help.github.com/articles/fork-a-repo/ "Forking a GitHub repository") + - [Helpful article about pull requests](https://help.github.com/articles/using-pull-requests/ "Pull Requests") + +## How to release + +1. Complete CHANGELOG.md and commit +2. Create an annotated tag + 1. `git tag -a 1.2.3` + 2. Tag subject must be the version number, eg: `1.2.3` + 3. Tag body must be a copy-paste of the changelog entries +3. Push tag with `git push --tags`, GitHub Actions will create a GitHub release automatically diff --git a/vendor/phpoffice/phpspreadsheet/LICENSE b/vendor/phpoffice/phpspreadsheet/LICENSE new file mode 100644 index 0000000..3ec5723 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 PhpSpreadsheet Authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/phpoffice/phpspreadsheet/README.md b/vendor/phpoffice/phpspreadsheet/README.md new file mode 100644 index 0000000..2a94e0d --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/README.md @@ -0,0 +1,30 @@ +# PhpSpreadsheet + +[![Build Status](https://github.com/PHPOffice/PhpSpreadsheet/workflows/main/badge.svg)](https://github.com/PHPOffice/PhpSpreadsheet/actions) +[![Code Quality](https://scrutinizer-ci.com/g/PHPOffice/PhpSpreadsheet/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/PHPOffice/PhpSpreadsheet/?branch=master) +[![Code Coverage](https://scrutinizer-ci.com/g/PHPOffice/PhpSpreadsheet/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/PHPOffice/PhpSpreadsheet/?branch=master) +[![Total Downloads](https://img.shields.io/packagist/dt/PHPOffice/PhpSpreadsheet)](https://packagist.org/packages/phpoffice/phpspreadsheet) +[![Latest Stable Version](https://img.shields.io/github/v/release/PHPOffice/PhpSpreadsheet)](https://packagist.org/packages/phpoffice/phpspreadsheet) +[![License](https://img.shields.io/github/license/PHPOffice/PhpSpreadsheet)](https://packagist.org/packages/phpoffice/phpspreadsheet) +[![Join the chat at https://gitter.im/PHPOffice/PhpSpreadsheet](https://img.shields.io/badge/GITTER-join%20chat-green.svg)](https://gitter.im/PHPOffice/PhpSpreadsheet) + +PhpSpreadsheet is a library written in pure PHP and offers a set of classes that +allow you to read and write various spreadsheet file formats such as Excel and LibreOffice Calc. + +## Documentation + +Read more about it, including install instructions, in the [official documentation](https://phpspreadsheet.readthedocs.io). Or check out the [API documentation](https://phpoffice.github.io/PhpSpreadsheet). + +Please ask your support questions on [StackOverflow](https://stackoverflow.com/questions/tagged/phpspreadsheet), or have a quick chat on [Gitter](https://gitter.im/PHPOffice/PhpSpreadsheet). + +## PHPExcel vs PhpSpreadsheet ? + +PhpSpreadsheet is the next version of PHPExcel. It breaks compatibility to dramatically improve the code base quality (namespaces, PSR compliance, use of latest PHP language features, etc.). + +Because all efforts have shifted to PhpSpreadsheet, PHPExcel will no longer be maintained. All contributions for PHPExcel, patches and new features, should target PhpSpreadsheet `master` branch. + +Do you need to migrate? There is [an automated tool](/docs/topics/migration-from-PHPExcel.md) for that. + +## License + +PhpSpreadsheet is licensed under [MIT](https://github.com/PHPOffice/PhpSpreadsheet/blob/master/LICENSE). diff --git a/vendor/phpoffice/phpspreadsheet/composer.json b/vendor/phpoffice/phpspreadsheet/composer.json new file mode 100644 index 0000000..d80fc62 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/composer.json @@ -0,0 +1,107 @@ +{ + "name": "phpoffice/phpspreadsheet", + "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine", + "keywords": [ + "PHP", + "OpenXML", + "Excel", + "xlsx", + "xls", + "ods", + "gnumeric", + "spreadsheet" + ], + "config": { + "sort-packages": true + }, + "homepage": "https://github.com/PHPOffice/PhpSpreadsheet", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Maarten Balliauw", + "homepage": "https://blog.maartenballiauw.be" + }, + { + "name": "Mark Baker", + "homepage": "https://markbakeruk.net" + }, + { + "name": "Franck Lefevre", + "homepage": "https://rootslabs.net" + }, + { + "name": "Erik Tilt" + }, + { + "name": "Adrien Crivelli" + } + ], + "scripts": { + "check": [ + "php-cs-fixer fix --ansi --dry-run --diff", + "phpcs", + "phpunit --color=always", + "phpstan analyse --ansi" + ], + "fix": [ + "php-cs-fixer fix --ansi" + ], + "versions": [ + "phpcs --report-width=200 samples/ src/ tests/ --ignore=samples/Header.php --standard=PHPCompatibility --runtime-set testVersion 7.3- -n" + ] + }, + "require": { + "php": "^7.3 || ^8.0", + "ext-ctype": "*", + "ext-dom": "*", + "ext-fileinfo": "*", + "ext-gd": "*", + "ext-iconv": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-xml": "*", + "ext-xmlreader": "*", + "ext-xmlwriter": "*", + "ext-zip": "*", + "ext-zlib": "*", + "ezyang/htmlpurifier": "^4.13", + "maennchen/zipstream-php": "^2.1", + "markbaker/complex": "^3.0", + "markbaker/matrix": "^3.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "psr/simple-cache": "^1.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "dompdf/dompdf": "^1.0", + "friendsofphp/php-cs-fixer": "^3.2", + "jpgraph/jpgraph": "^4.0", + "mpdf/mpdf": "^8.0", + "phpcompatibility/php-compatibility": "^9.3", + "phpstan/phpstan": "^1.1", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^8.5 || ^9.0", + "squizlabs/php_codesniffer": "^3.6", + "tecnickcom/tcpdf": "^6.4" + }, + "suggest": { + "mpdf/mpdf": "Option for rendering PDF with PDF Writer", + "dompdf/dompdf": "Option for rendering PDF with PDF Writer (doesn't yet support PHP8)", + "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer (doesn't yet support PHP8)", + "jpgraph/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers" + }, + "autoload": { + "psr-4": { + "PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet" + } + }, + "autoload-dev": { + "psr-4": { + "PhpOffice\\PhpSpreadsheetTests\\": "tests/PhpSpreadsheetTests", + "PhpOffice\\PhpSpreadsheetInfra\\": "infra" + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/phpstan-baseline.neon b/vendor/phpoffice/phpspreadsheet/phpstan-baseline.neon new file mode 100644 index 0000000..84e4ba1 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/phpstan-baseline.neon @@ -0,0 +1,9137 @@ +parameters: + ignoreErrors: + - + message: "#^Argument of an invalid type array\\\\|false supplied for foreach, only iterables are supported\\.$#" + count: 3 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Cannot access offset 'onlyIf' on mixed\\.$#" + count: 3 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Cannot access offset 'onlyIfNot' on mixed\\.$#" + count: 3 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Cannot access offset 'reference' on mixed\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Cannot access offset 'storeKey' on mixed\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Cannot access offset 'type' on mixed\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Cannot access offset 'value' on mixed\\.$#" + count: 16 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Cannot access offset 0 on mixed\\.$#" + count: 4 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Cannot access offset int on mixed\\.$#" + count: 16 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Cannot call method attach\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Cannot call method cellExists\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 4 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Cannot call method getCell\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 6 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Cannot call method getColumn\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Cannot call method getCoordinate\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Cannot call method getHighestColumn\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Cannot call method getHighestRow\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Cannot call method getRow\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Cannot call method getTitle\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Cannot call method has\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Collection\\\\Cells\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Cannot use array destructuring on mixed\\.$#" + count: 6 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:_translateFormulaToEnglish\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:_translateFormulaToEnglish\\(\\) has parameter \\$formula with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:_translateFormulaToLocale\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:_translateFormulaToLocale\\(\\) has parameter \\$formula with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:dataTestReference\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:dataTestReference\\(\\) has parameter \\$operandData with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:getTokensAsString\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:getTokensAsString\\(\\) has parameter \\$tokens with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:getUnusedBranchStoreKey\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:localeFunc\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:localeFunc\\(\\) has parameter \\$function with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:raiseFormulaError\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:raiseFormulaError\\(\\) has parameter \\$errorMessage with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:validateBinaryOperand\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:validateBinaryOperand\\(\\) has parameter \\$operand with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:validateBinaryOperand\\(\\) has parameter \\$stack with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Parameter \\#1 \\$cellRange of static method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Coordinate\\:\\:extractAllCellReferencesInRange\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Parameter \\#1 \\$column of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\:\\:getHighestDataRow\\(\\) expects string\\|null, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Parameter \\#1 \\$definedName of static method PhpOffice\\\\PhpSpreadsheet\\\\DefinedName\\:\\:resolveName\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Parameter \\#1 \\$formula of method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:_calculateFormulaValue\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Parameter \\#1 \\$haystack of function stripos expects string, float\\|int\\|string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Parameter \\#1 \\$haystack of function strpos expects string, float\\|int\\|string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Parameter \\#1 \\$parent of method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell\\:\\:attach\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Collection\\\\Cells, PhpOffice\\\\PhpSpreadsheet\\\\Collection\\\\Cells\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Parameter \\#1 \\$str of function preg_quote expects string, int\\|string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Parameter \\#1 \\$str of function strtoupper expects string, mixed given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Parameter \\#1 \\$str of function trim expects string, int\\|string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Parameter \\#1 \\$str of function trim expects string, mixed given\\.$#" + count: 3 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Parameter \\#1 \\$str of function trim expects string, null given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Parameter \\#1 \\$str1 of method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:strcmpAllowNull\\(\\) expects string\\|null, mixed given\\.$#" + count: 4 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Parameter \\#1 \\$str1 of method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:strcmpLowercaseFirst\\(\\) expects string\\|null, mixed given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Parameter \\#1 \\$string of function strlen expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Parameter \\#1 \\$textValue of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\StringHelper\\:\\:strCaseReverse\\(\\) expects string, string\\|null given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Parameter \\#1 \\$worksheetName of method PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\:\\:getSheetByName\\(\\) expects string, mixed given\\.$#" + count: 3 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Parameter \\#2 \\$str2 of method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:strcmpAllowNull\\(\\) expects string\\|null, mixed given\\.$#" + count: 4 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Parameter \\#2 \\$subject of function preg_match expects string, mixed given\\.$#" + count: 4 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Parameter \\#2 \\$worksheet of static method PhpOffice\\\\PhpSpreadsheet\\\\DefinedName\\:\\:resolveName\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet, PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Parameter \\#3 \\$formula of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:translateSeparator\\(\\) expects string, string\\|null given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Parameter \\#4 \\$operation of method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:executeBinaryComparisonOperation\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Parameter \\#4 \\$storeKey of method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Token\\\\Stack\\:\\:push\\(\\) expects string\\|null, mixed given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Parameter \\#5 \\$onlyIf of method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Token\\\\Stack\\:\\:push\\(\\) expects string\\|null, mixed given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Parameter \\#6 \\$onlyIfNot of method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Token\\\\Stack\\:\\:push\\(\\) expects string\\|null, mixed given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Part \\$argumentCount \\(mixed\\) of encapsed string cannot be cast to string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Part \\$token \\(mixed\\) of encapsed string cannot be cast to string\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Part \\$val \\(mixed\\) of encapsed string cannot be cast to string\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:\\$branchPruningEnabled has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:\\$cellStack has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:\\$comparisonOperators has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:\\$controlFunctions has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:\\$cyclicFormulaCell has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:\\$functionReplaceFromExcel has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:\\$functionReplaceFromLocale has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:\\$functionReplaceToExcel has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:\\$functionReplaceToLocale has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:\\$localeFunctions has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:\\$operatorAssociativity has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:\\$operatorPrecedence has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:\\$phpSpreadsheetFunctions has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:\\$returnArrayAsType has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:\\$spreadsheet \\(PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Static property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:\\$instance \\(PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\) in isset\\(\\) is not nullable\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Strict comparison using \\=\\=\\= between mixed and null will always evaluate to false\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Variable \\$inversedStr1 on left side of \\?\\? always exists and is not nullable\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Variable \\$inversedStr2 on left side of \\?\\? always exists and is not nullable\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Calculation.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DMAX\\(\\) should return float but returns float\\|string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Database.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DMIN\\(\\) should return float but returns float\\|string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Database.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DPRODUCT\\(\\) should return float\\|string but returns float\\|string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Database.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DSTDEV\\(\\) should return float\\|string but returns float\\|string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Database.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DSTDEVP\\(\\) should return float\\|string but returns float\\|string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Database.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DSUM\\(\\) should return float\\|string but returns float\\|string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Database.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DVAR\\(\\) should return float\\|string but returns float\\|string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Database.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DVARP\\(\\) should return float\\|string but returns float\\|string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Database.php + + - + message: "#^Parameter \\#2 \\$field of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\\\DCountA\\:\\:evaluate\\(\\) expects int\\|string, int\\|string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Database.php + + - + message: "#^Cannot access offset int\\|string\\|null on mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Database/DatabaseAbstract.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\\\DatabaseAbstract\\:\\:buildCondition\\(\\) has parameter \\$criterion with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Database/DatabaseAbstract.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\\\DatabaseAbstract\\:\\:evaluate\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Database/DatabaseAbstract.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\\\DatabaseAbstract\\:\\:evaluate\\(\\) has parameter \\$criteria with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Database/DatabaseAbstract.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\\\DatabaseAbstract\\:\\:evaluate\\(\\) has parameter \\$database with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Database/DatabaseAbstract.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\\\DatabaseAbstract\\:\\:evaluate\\(\\) has parameter \\$field with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Database/DatabaseAbstract.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\\\DatabaseAbstract\\:\\:processCondition\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Database/DatabaseAbstract.php + + - + message: "#^Parameter \\#1 \\$callback of function array_map expects \\(callable\\(mixed\\)\\: mixed\\)\\|null, 'strtoupper' given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Database/DatabaseAbstract.php + + - + message: "#^Parameter \\#1 \\$criteriaNames of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\\\DatabaseAbstract\\:\\:buildQuery\\(\\) expects array, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Database/DatabaseAbstract.php + + - + message: "#^Parameter \\#1 \\$input of function array_keys expects array, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Database/DatabaseAbstract.php + + - + message: "#^Parameter \\#1 \\$str of function strtoupper expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Database/DatabaseAbstract.php + + - + message: "#^Parameter \\#2 \\$array of function array_map expects array, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Database/DatabaseAbstract.php + + - + message: "#^Parameter \\#3 \\$criteria of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\\\DatabaseAbstract\\:\\:executeQuery\\(\\) expects array, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Database/DatabaseAbstract.php + + - + message: "#^Parameter \\#4 \\$fields of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\\\DatabaseAbstract\\:\\:executeQuery\\(\\) expects array, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Database/DatabaseAbstract.php + + - + message: "#^Cannot cast mixed to string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/DateTimeExcel/Date.php + + - + message: "#^Parameter \\#1 \\$day of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Date\\:\\:dayStringToNumber\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/DateTimeExcel/Date.php + + - + message: "#^Parameter \\#1 \\$monthName of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Date\\:\\:monthStringToNumber\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/DateTimeExcel/Date.php + + - + message: "#^Parameter \\#1 \\$str of function trim expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php + + - + message: "#^Variable \\$dateValue on left side of \\?\\? always exists and is not nullable\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php + + - + message: "#^Parameter \\#1 \\$str of function strtoupper expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/DateTimeExcel/Difference.php + + - + message: "#^Parameter \\#1 \\$excelTimestamp of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Date\\:\\:excelToDateTimeObject\\(\\) expects float\\|int, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php + + - + message: "#^Parameter \\#1 \\$x of function fmod expects float, mixed given\\.$#" + count: 3 + path: src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeParts.php + + - + message: "#^Parameter \\#1 \\$str of function trim expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php + + - + message: "#^Variable \\$timeValue on left side of \\?\\? always exists and is not nullable\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php + + - + message: "#^Binary operation \"/\" between int\\|string and \\(float\\|int\\) results in an error\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/DateTimeExcel/YearFrac.php + + - + message: "#^Binary operation \"/\" between int\\|string and 360 results in an error\\.$#" + count: 3 + path: src/PhpSpreadsheet/Calculation/DateTimeExcel/YearFrac.php + + - + message: "#^Binary operation \"/\" between int\\|string and 365 results in an error\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/DateTimeExcel/YearFrac.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engine\\\\Logger\\:\\:writeDebugLog\\(\\) has parameter \\$args with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engine/Logger.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\:\\:BITLSHIFT\\(\\) should return int\\|string but returns float\\|int\\|string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\:\\:BITRSHIFT\\(\\) should return int\\|string but returns float\\|int\\|string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering.php + + - + message: "#^Parameter \\#1 \\$category of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\ConvertUOM\\:\\:getConversionCategoryUnitDetails\\(\\) expects string\\|null, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering.php + + - + message: "#^Parameter \\#1 \\$category of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\ConvertUOM\\:\\:getConversionCategoryUnits\\(\\) expects string\\|null, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering.php + + - + message: "#^Parameter \\#1 \\$value of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\ConvertBinary\\:\\:toDecimal\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering.php + + - + message: "#^Parameter \\#1 \\$value of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\ConvertBinary\\:\\:toHex\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering.php + + - + message: "#^Parameter \\#1 \\$value of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\ConvertBinary\\:\\:toOctal\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering.php + + - + message: "#^Parameter \\#1 \\$value of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\ConvertDecimal\\:\\:toBinary\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering.php + + - + message: "#^Parameter \\#1 \\$value of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\ConvertDecimal\\:\\:toHex\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering.php + + - + message: "#^Parameter \\#1 \\$value of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\ConvertDecimal\\:\\:toOctal\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering.php + + - + message: "#^Parameter \\#1 \\$value of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\ConvertHex\\:\\:toBinary\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering.php + + - + message: "#^Parameter \\#1 \\$value of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\ConvertHex\\:\\:toDecimal\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering.php + + - + message: "#^Parameter \\#1 \\$value of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\ConvertHex\\:\\:toOctal\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering.php + + - + message: "#^Parameter \\#1 \\$value of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\ConvertOctal\\:\\:toBinary\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering.php + + - + message: "#^Parameter \\#1 \\$value of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\ConvertOctal\\:\\:toDecimal\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering.php + + - + message: "#^Parameter \\#1 \\$value of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\ConvertOctal\\:\\:toHex\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering.php + + - + message: "#^Parameter \\#2 \\$places of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\ConvertBinary\\:\\:toHex\\(\\) expects int\\|null, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering.php + + - + message: "#^Parameter \\#2 \\$places of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\ConvertBinary\\:\\:toOctal\\(\\) expects int\\|null, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering.php + + - + message: "#^Parameter \\#2 \\$places of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\ConvertDecimal\\:\\:toBinary\\(\\) expects int\\|null, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering.php + + - + message: "#^Parameter \\#2 \\$places of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\ConvertDecimal\\:\\:toHex\\(\\) expects int\\|null, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering.php + + - + message: "#^Parameter \\#2 \\$places of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\ConvertDecimal\\:\\:toOctal\\(\\) expects int\\|null, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering.php + + - + message: "#^Parameter \\#2 \\$places of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\ConvertHex\\:\\:toBinary\\(\\) expects int\\|null, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering.php + + - + message: "#^Parameter \\#2 \\$places of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\ConvertHex\\:\\:toOctal\\(\\) expects int\\|null, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering.php + + - + message: "#^Parameter \\#2 \\$places of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\ConvertOctal\\:\\:toBinary\\(\\) expects int\\|null, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering.php + + - + message: "#^Parameter \\#2 \\$places of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\ConvertOctal\\:\\:toHex\\(\\) expects int\\|null, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\BesselJ\\:\\:besselj2a\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering/BesselJ.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\BesselJ\\:\\:besselj2b\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering/BesselJ.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\BesselK\\:\\:besselK2\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering/BesselK.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\BitWise\\:\\:validateBitwiseArgument\\(\\) never returns int so it can be removed from the return type\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering/BitWise.php + + - + message: "#^Parameter \\#1 \\$number of function floor expects float, float\\|int\\<0, 281474976710655\\>\\|string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering/BitWise.php + + - + message: "#^Parameter \\#1 \\$number of function floor expects float, float\\|int\\|string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering/BitWise.php + + - + message: "#^Parameter \\#1 \\$complexNumber of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\ComplexFunctions\\:\\:IMARGUMENT\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering/ComplexFunctions.php + + - + message: "#^Parameter \\#1 \\$power of method Complex\\\\Complex\\:\\:pow\\(\\) expects float\\|int, float\\|int\\|string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering/ComplexFunctions.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\ConvertBase\\:\\:validatePlaces\\(\\) has parameter \\$places with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering/ConvertBase.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\ConvertBase\\:\\:validateValue\\(\\) has parameter \\$value with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering/ConvertBase.php + + - + message: "#^Cannot access offset 'AllowPrefix' on mixed\\.$#" + count: 3 + path: src/PhpSpreadsheet/Calculation/Engineering/ConvertUOM.php + + - + message: "#^Cannot access offset 'Group' on mixed\\.$#" + count: 9 + path: src/PhpSpreadsheet/Calculation/Engineering/ConvertUOM.php + + - + message: "#^Cannot access offset 'Unit Name' on mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering/ConvertUOM.php + + - + message: "#^Cannot access offset 'multiplier' on mixed\\.$#" + count: 3 + path: src/PhpSpreadsheet/Calculation/Engineering/ConvertUOM.php + + - + message: "#^Cannot access offset mixed on mixed\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/Engineering/ConvertUOM.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\ConvertUOM\\:\\:getUOMDetails\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering/ConvertUOM.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\ConvertUOM\\:\\:resolveTemperatureSynonyms\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering/ConvertUOM.php + + - + message: "#^Parameter \\#1 \\$uom of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\ConvertUOM\\:\\:getUOMDetails\\(\\) expects string, mixed given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/Engineering/ConvertUOM.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\Erf\\:\\:erfValue\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering/Erf.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\Erf\\:\\:erfValue\\(\\) has parameter \\$value with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering/Erf.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\Erf\\:\\:\\$twoSqrtPi has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering/Erf.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\ErfC\\:\\:erfcValue\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering/ErfC.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\ErfC\\:\\:erfcValue\\(\\) has parameter \\$value with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering/ErfC.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\\\ErfC\\:\\:\\$oneSqrtPi has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Engineering/ErfC.php + + - + message: "#^Parameter \\#1 \\$message of class PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Exception constructor expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Exception.php + + - + message: "#^Parameter \\#2 \\$code of class PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Exception constructor expects int, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Exception.php + + - + message: "#^Parameter \\#1 \\$callback of function set_error_handler expects \\(callable\\(int, string, string, int, array\\)\\: bool\\)\\|null, array\\{'PhpOffice\\\\\\\\PhpSpreadsheet\\\\\\\\Calculation\\\\\\\\Exception', 'errorHandlerCallback'\\} given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/ExceptionHandler.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\:\\:ISPMT\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\:\\:ISPMT\\(\\) has parameter \\$args with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\:\\:NPV\\(\\) has parameter \\$args with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Constant\\\\Periodic\\\\Interest\\:\\:rateNextGuess\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Constant\\\\Periodic\\\\Interest\\:\\:rateNextGuess\\(\\) has parameter \\$futureValue with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Constant\\\\Periodic\\\\Interest\\:\\:rateNextGuess\\(\\) has parameter \\$numberOfPeriods with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Constant\\\\Periodic\\\\Interest\\:\\:rateNextGuess\\(\\) has parameter \\$payment with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Constant\\\\Periodic\\\\Interest\\:\\:rateNextGuess\\(\\) has parameter \\$presentValue with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Constant\\\\Periodic\\\\Interest\\:\\:rateNextGuess\\(\\) has parameter \\$rate with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Constant\\\\Periodic\\\\Interest\\:\\:rateNextGuess\\(\\) has parameter \\$type with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Constant\\\\Periodic\\\\Interest\\:\\:schedulePayment\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php + + - + message: "#^Binary operation \"\\-\" between float\\|string and 0\\|float results in an error\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/InterestAndPrincipal.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Constant\\\\Periodic\\\\InterestAndPrincipal\\:\\:\\$interest has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/InterestAndPrincipal.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Constant\\\\Periodic\\\\InterestAndPrincipal\\:\\:\\$principal has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/InterestAndPrincipal.php + + - + message: "#^Parameter \\#4 \\$x2 of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Variable\\\\NonPeriodic\\:\\:xirrPart3\\(\\) expects float, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\CashFlow\\\\Variable\\\\Periodic\\:\\:presentValue\\(\\) has parameter \\$args with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/Periodic.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\Depreciation\\:\\:validateCost\\(\\) has parameter \\$cost with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/Depreciation.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\Depreciation\\:\\:validateFactor\\(\\) has parameter \\$factor with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/Depreciation.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\Depreciation\\:\\:validateLife\\(\\) has parameter \\$life with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/Depreciation.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\Depreciation\\:\\:validateMonth\\(\\) has parameter \\$month with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/Depreciation.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\Depreciation\\:\\:validatePeriod\\(\\) has parameter \\$period with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/Depreciation.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\Depreciation\\:\\:validateSalvage\\(\\) has parameter \\$salvage with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Financial/Depreciation.php + + - + message: "#^Cannot cast mixed to int\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/Financial/Dollar.php + + - + message: "#^Parameter \\#1 \\$number of function floor expects float, mixed given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/Financial/Dollar.php + + - + message: "#^Parameter \\#1 \\$x of function fmod expects float, mixed given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/Financial/Dollar.php + + - + message: "#^Binary operation \"/\" between float\\|string and float\\|string results in an error\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/Financial/Securities/Price.php + + - + message: "#^Cannot call method getTokenSubType\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\FormulaToken\\|null\\.$#" + count: 4 + path: src/PhpSpreadsheet/Calculation/FormulaParser.php + + - + message: "#^Cannot call method getTokenType\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\FormulaToken\\|null\\.$#" + count: 9 + path: src/PhpSpreadsheet/Calculation/FormulaParser.php + + - + message: "#^Cannot call method setTokenSubType\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\FormulaToken\\|null\\.$#" + count: 5 + path: src/PhpSpreadsheet/Calculation/FormulaParser.php + + - + message: "#^Cannot call method setValue\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\FormulaToken\\|null\\.$#" + count: 5 + path: src/PhpSpreadsheet/Calculation/FormulaParser.php + + - + message: "#^Strict comparison using \\=\\=\\= between PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\FormulaToken and null will always evaluate to false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/FormulaParser.php + + - + message: "#^Strict comparison using \\=\\=\\= between string and null will always evaluate to false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/FormulaParser.php + + - + message: "#^Cannot call method getCell\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Functions.php + + - + message: "#^Cannot cast mixed to string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Functions.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Functions\\:\\:ifCondition\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Functions.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Functions\\:\\:ifCondition\\(\\) has parameter \\$condition with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Functions.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Functions\\:\\:isCellValue\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Functions.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Functions\\:\\:isCellValue\\(\\) has parameter \\$idx with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Functions.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Functions\\:\\:isMatrixValue\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Functions.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Functions\\:\\:isMatrixValue\\(\\) has parameter \\$idx with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Functions.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Functions\\:\\:isValue\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Functions.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Functions\\:\\:isValue\\(\\) has parameter \\$idx with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Functions.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Functions\\:\\:operandSpecialHandling\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Functions.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Functions\\:\\:operandSpecialHandling\\(\\) has parameter \\$operand with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Functions.php + + - + message: "#^Parameter \\#1 \\$number of function abs expects float\\|int\\|string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Functions.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Internal\\\\MakeMatrix\\:\\:make\\(\\) has parameter \\$args with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Internal/MakeMatrix.php + + - + message: "#^Call to function is_string\\(\\) with null will always evaluate to false\\.$#" + count: 3 + path: src/PhpSpreadsheet/Calculation/Logical/Operations.php + + - + message: "#^Result of && is always false\\.$#" + count: 3 + path: src/PhpSpreadsheet/Calculation/Logical/Operations.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\:\\:CHOOSE\\(\\) has parameter \\$chooseArgs with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\:\\:HYPERLINK\\(\\) should return string but returns mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\:\\:OFFSET\\(\\) should return array\\|string but returns array\\|int\\|string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\Address\\:\\:sheetName\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Address.php + + - + message: "#^Parameter \\#1 \\$row of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\Address\\:\\:formatAsA1\\(\\) expects int, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Address.php + + - + message: "#^Parameter \\#1 \\$row of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\Address\\:\\:formatAsR1C1\\(\\) expects int, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Address.php + + - + message: "#^Parameter \\#1 \\$sheetName of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\Address\\:\\:sheetName\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Address.php + + - + message: "#^Parameter \\#2 \\$column of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\Address\\:\\:formatAsA1\\(\\) expects int, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Address.php + + - + message: "#^Parameter \\#2 \\$column of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\Address\\:\\:formatAsR1C1\\(\\) expects int, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Address.php + + - + message: "#^Parameter \\#3 \\$relativity of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\Address\\:\\:formatAsA1\\(\\) expects int, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Address.php + + - + message: "#^Parameter \\#3 \\$relativity of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\Address\\:\\:formatAsR1C1\\(\\) expects int, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Address.php + + - + message: "#^Cannot cast mixed to int\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:matchFirstValue\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:matchFirstValue\\(\\) has parameter \\$lookupArray with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:matchFirstValue\\(\\) has parameter \\$lookupValue with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:matchLargestValue\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:matchLargestValue\\(\\) has parameter \\$keySet with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:matchLargestValue\\(\\) has parameter \\$lookupArray with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:matchLargestValue\\(\\) has parameter \\$lookupValue with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:matchSmallestValue\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:matchSmallestValue\\(\\) has parameter \\$lookupArray with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:matchSmallestValue\\(\\) has parameter \\$lookupValue with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:prepareLookupArray\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:prepareLookupArray\\(\\) has parameter \\$lookupArray with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:prepareLookupArray\\(\\) has parameter \\$matchType with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:validateLookupArray\\(\\) has parameter \\$lookupArray with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:validateLookupValue\\(\\) has parameter \\$lookupValue with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:validateMatchType\\(\\) has parameter \\$matchType with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\Formula\\:\\:text\\(\\) should return string but returns mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Formula.php + + - + message: "#^Parameter \\#2 \\$subject of function preg_match expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Formula.php + + - + message: "#^Parameter \\#1 \\$lookupArray of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\HLookup\\:\\:convertLiteralArray\\(\\) expects array, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/HLookup.php + + - + message: "#^Parameter \\#1 \\$textValue of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\StringHelper\\:\\:strToLower\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/HLookup.php + + - + message: "#^Cannot use array destructuring on mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Helpers.php + + - + message: "#^Parameter \\#1 \\$str of function trim expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Helpers.php + + - + message: "#^Parameter \\#1 \\$str of function trim expects string, mixed given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/LookupRef/Hyperlink.php + + - + message: "#^Parameter \\#1 \\$tooltip of method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Hyperlink\\:\\:setTooltip\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Hyperlink.php + + - + message: "#^Parameter \\#1 \\$url of method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Hyperlink\\:\\:setUrl\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Hyperlink.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\Indirect\\:\\:INDIRECT\\(\\) should return array\\|string but returns mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Indirect.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\Lookup\\:\\:verifyResultVector\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Lookup.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\Lookup\\:\\:verifyResultVector\\(\\) has parameter \\$resultVector with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Lookup.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\LookupBase\\:\\:checkMatch\\(\\) has parameter \\$notExactMatch with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/LookupBase.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\LookupBase\\:\\:validateIndexLookup\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/LookupBase.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\LookupBase\\:\\:validateIndexLookup\\(\\) has parameter \\$index_number with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/LookupBase.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\LookupBase\\:\\:validateIndexLookup\\(\\) has parameter \\$lookup_array with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/LookupBase.php + + - + message: "#^Parameter \\#1 \\$message of class PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Exception constructor expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/LookupRefValidations.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\Matrix\\:\\:extractRowValue\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Matrix.php + + - + message: "#^Binary operation \"\\+\\=\" between string and mixed results in an error\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Offset.php + + - + message: "#^Cannot use array destructuring on mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Offset.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\Offset\\:\\:adjustEndCellColumnForWidth\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Offset.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\Offset\\:\\:adjustEndCellColumnForWidth\\(\\) has parameter \\$columns with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Offset.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\Offset\\:\\:adjustEndCellColumnForWidth\\(\\) has parameter \\$width with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Offset.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\Offset\\:\\:adustEndCellRowForHeight\\(\\) has parameter \\$endCellRow with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Offset.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\Offset\\:\\:adustEndCellRowForHeight\\(\\) has parameter \\$height with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Offset.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\Offset\\:\\:adustEndCellRowForHeight\\(\\) has parameter \\$rows with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Offset.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\Offset\\:\\:extractRequiredCells\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Offset.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\Offset\\:\\:extractWorksheet\\(\\) has parameter \\$cellAddress with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Offset.php + + - + message: "#^Parameter \\#1 \\$str of function trim expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/Offset.php + + - + message: "#^Cannot use array destructuring on mixed\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php + + - + message: "#^Parameter \\#1 \\$columnAddress of static method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Coordinate\\:\\:columnIndexFromString\\(\\) expects string, string\\|null given\\.$#" + count: 3 + path: src/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php + + - + message: "#^Parameter \\#1 \\$haystack of function strpos expects string, mixed given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php + + - + message: "#^Parameter \\#1 \\$low of function range expects float\\|int\\|string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php + + - + message: "#^Parameter \\#2 \\$high of function range expects float\\|int\\|string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php + + - + message: "#^Parameter \\#2 \\$str of function explode expects string, mixed given\\.$#" + count: 3 + path: src/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php + + - + message: "#^Parameter \\#3 \\$subject of function preg_replace expects array\\|string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php + + - + message: "#^Cannot access offset \\(int\\|string\\) on mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php + + - + message: "#^Cannot access offset int\\|string\\|null on mixed\\.$#" + count: 3 + path: src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php + + - + message: "#^Cannot access offset mixed on mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\VLookup\\:\\:vLookupSearch\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\VLookup\\:\\:vLookupSearch\\(\\) has parameter \\$column with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\VLookup\\:\\:vLookupSearch\\(\\) has parameter \\$lookupArray with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\VLookup\\:\\:vLookupSearch\\(\\) has parameter \\$lookupValue with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\VLookup\\:\\:vLookupSearch\\(\\) has parameter \\$notExactMatch with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\VLookup\\:\\:vlookupSort\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\VLookup\\:\\:vlookupSort\\(\\) has parameter \\$a with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\VLookup\\:\\:vlookupSort\\(\\) has parameter \\$b with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php + + - + message: "#^Parameter \\#1 \\$array_arg of function uasort expects array, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php + + - + message: "#^Parameter \\#1 \\$input of function array_keys expects array, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php + + - + message: "#^Parameter \\#2 \\$callback of function uasort expects callable\\(mixed, mixed\\)\\: int, array\\{'self', 'vlookupSort'\\} given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php + + - + message: "#^Parameter \\#1 \\$number of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\MathTrig\\\\Trig\\\\Cosine\\:\\:acos\\(\\) expects float, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/MathTrig.php + + - + message: "#^Parameter \\#1 \\$number of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\MathTrig\\\\Trig\\\\Cosine\\:\\:acosh\\(\\) expects float, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/MathTrig.php + + - + message: "#^Parameter \\#1 \\$number of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\MathTrig\\\\Trig\\\\Sine\\:\\:asin\\(\\) expects float, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/MathTrig.php + + - + message: "#^Parameter \\#1 \\$number of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\MathTrig\\\\Trig\\\\Sine\\:\\:asinh\\(\\) expects float, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/MathTrig.php + + - + message: "#^Parameter \\#1 \\$number of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\MathTrig\\\\Trig\\\\Tangent\\:\\:atan\\(\\) expects float, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/MathTrig.php + + - + message: "#^Parameter \\#1 \\$number of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\MathTrig\\\\Trig\\\\Tangent\\:\\:atanh\\(\\) expects float, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/MathTrig.php + + - + message: "#^Cannot cast mixed to string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/MathTrig/Arabic.php + + - + message: "#^Binary operation \"/\" between float\\|int\\|string and float\\|int\\|string results in an error\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/MathTrig/Combinations.php + + - + message: "#^Binary operation \"/\" between float\\|int\\|string and float\\|int results in an error\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/MathTrig/Factorial.php + + - + message: "#^Cannot call method getWorksheet\\(\\) on mixed\\.$#" + count: 4 + path: src/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\MathTrig\\\\Subtotal\\:\\:evaluate\\(\\) should return float\\|string but returns mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php + + - + message: "#^PHPDoc tag @var for constant PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\MathTrig\\\\Subtotal\\:\\:CALL_FUNCTIONS with type array\\ is not subtype of value array\\{1\\: array\\{'PhpOffice…', 'average'\\}, 2\\: array\\{'PhpOffice…', 'COUNT'\\}, 3\\: array\\{'PhpOffice…', 'COUNTA'\\}, 4\\: array\\{'PhpOffice…', 'max'\\}, 5\\: array\\{'PhpOffice…', 'min'\\}, 6\\: array\\{'PhpOffice…', 'product'\\}, 7\\: array\\{'PhpOffice…', 'STDEV'\\}, 8\\: array\\{'PhpOffice…', 'STDEVP'\\}, \\.\\.\\.\\}\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php + + - + message: "#^Parameter \\#1 \\$input of function array_filter expects array, mixed given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php + + - + message: "#^Parameter \\#2 \\$str of function explode expects string, mixed given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\:\\:MAXIFS\\(\\) should return float but returns float\\|string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\:\\:MINIFS\\(\\) should return float but returns float\\|string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical.php + + - + message: "#^Parameter \\#1 \\$range of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Conditional\\:\\:AVERAGEIF\\(\\) expects array, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical.php + + - + message: "#^Parameter \\#1 \\$range of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Conditional\\:\\:COUNTIF\\(\\) expects array, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical.php + + - + message: "#^Binary operation \"\\-\" between mixed and float\\|string results in an error\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Averages.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Averages\\:\\:filterArguments\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Averages.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Averages\\:\\:filterArguments\\(\\) has parameter \\$args with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Averages.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Averages\\:\\:modeCalc\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Averages.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Averages\\:\\:modeCalc\\(\\) has parameter \\$data with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Averages.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Conditional\\:\\:SUMIF\\(\\) should return float\\|string but returns float\\|string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Conditional.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Conditional\\:\\:buildConditionSet\\(\\) has parameter \\$args with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Conditional.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Conditional\\:\\:buildConditionSetForValueRange\\(\\) has parameter \\$args with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Conditional.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Conditional\\:\\:buildConditions\\(\\) has parameter \\$args with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Conditional.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Conditional\\:\\:buildDataSet\\(\\) has parameter \\$args with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Conditional.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Conditional\\:\\:buildDatabase\\(\\) has parameter \\$args with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Conditional.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Conditional\\:\\:buildDatabaseWithValueRange\\(\\) has parameter \\$args with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Conditional.php + + - + message: "#^Parameter \\#1 \\$range of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Conditional\\:\\:AVERAGEIF\\(\\) expects array, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Conditional.php + + - + message: "#^Parameter \\#1 \\$range of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Conditional\\:\\:COUNTIF\\(\\) expects array, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Conditional.php + + - + message: "#^Parameter \\#1 \\$range of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Conditional\\:\\:databaseFromRangeAndValue\\(\\) expects array, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Conditional.php + + - + message: "#^Parameter \\#2 \\$condition of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Conditional\\:\\:AVERAGEIF\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Conditional.php + + - + message: "#^Parameter \\#2 \\$valueRange of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Conditional\\:\\:databaseFromRangeAndValue\\(\\) expects array, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Conditional.php + + - + message: "#^Parameter \\#3 \\$averageRange of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Conditional\\:\\:AVERAGEIF\\(\\) expects array, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Conditional.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\Beta\\:\\:\\$logBetaCacheP has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/Beta.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\Beta\\:\\:\\$logBetaCacheQ has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/Beta.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\Beta\\:\\:\\$logBetaCacheResult has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/Beta.php + + - + message: "#^Static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\Beta\\:\\:regularizedIncompleteBeta\\(\\) is unused\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/Beta.php + + - + message: "#^Constant PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\ChiSquared\\:\\:MAX_ITERATIONS is unused\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\ChiSquared\\:\\:gammp\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\ChiSquared\\:\\:gammp\\(\\) has parameter \\$n with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\ChiSquared\\:\\:gammp\\(\\) has parameter \\$x with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\ChiSquared\\:\\:gcf\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\ChiSquared\\:\\:gcf\\(\\) has parameter \\$n with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\ChiSquared\\:\\:gcf\\(\\) has parameter \\$x with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\ChiSquared\\:\\:gser\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\ChiSquared\\:\\:gser\\(\\) has parameter \\$n with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\ChiSquared\\:\\:gser\\(\\) has parameter \\$x with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\ChiSquared\\:\\:pchisq\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\ChiSquared\\:\\:pchisq\\(\\) has parameter \\$chi2 with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\ChiSquared\\:\\:pchisq\\(\\) has parameter \\$degrees with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php + + - + message: "#^Parameter \\#1 \\$var of function count expects array\\|Countable, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php + + - + message: "#^Parameter \\#2 \\$columns of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\ChiSquared\\:\\:degrees\\(\\) expects int, float\\|int\\<0, max\\> given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\GammaBase\\:\\:calculateDistribution\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/GammaBase.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\GammaBase\\:\\:calculateInverse\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/GammaBase.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\GammaBase\\:\\:logGamma1\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/GammaBase.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\GammaBase\\:\\:logGamma2\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/GammaBase.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\GammaBase\\:\\:logGamma3\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/GammaBase.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\GammaBase\\:\\:logGamma4\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/GammaBase.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\GammaBase\\:\\:\\$logGammaCacheResult has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/GammaBase.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\GammaBase\\:\\:\\$logGammaCacheX has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/GammaBase.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\NewtonRaphson\\:\\:execute\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/NewtonRaphson.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\NewtonRaphson\\:\\:\\$callback has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/NewtonRaphson.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\Normal\\:\\:inverseNcdf\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/Normal.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\Normal\\:\\:inverseNcdf\\(\\) has parameter \\$p with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/Normal.php + + - + message: "#^Binary operation \"\\-\" between float\\|string and float\\|int\\|numeric\\-string results in an error\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/StandardNormal.php + + - + message: "#^Constant PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Distributions\\\\StudentT\\:\\:MAX_ITERATIONS is unused\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/StudentT.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\MaxMinBase\\:\\:datatypeAdjustmentAllowStrings\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/MaxMinBase.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\MaxMinBase\\:\\:datatypeAdjustmentAllowStrings\\(\\) has parameter \\$value with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/MaxMinBase.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Percentiles\\:\\:percentileFilterValues\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Percentiles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Percentiles\\:\\:rankFilterValues\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Percentiles.php + + - + message: "#^Binary operation \"/\" between float\\|int\\|string and float\\|int\\|string results in an error\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Permutations.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Trends\\:\\:GROWTH\\(\\) should return array\\ but returns array\\\\>\\>\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Trends.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Trends\\:\\:TREND\\(\\) should return array\\ but returns array\\\\>\\>\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Trends.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Trends\\:\\:checkTrendArrays\\(\\) has parameter \\$array1 with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Trends.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\Trends\\:\\:checkTrendArrays\\(\\) has parameter \\$array2 with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/Trends.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\VarianceBase\\:\\:datatypeAdjustmentAllowStrings\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/VarianceBase.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\VarianceBase\\:\\:datatypeAdjustmentAllowStrings\\(\\) has parameter \\$value with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/VarianceBase.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\VarianceBase\\:\\:datatypeAdjustmentBooleans\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/VarianceBase.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\\\VarianceBase\\:\\:datatypeAdjustmentBooleans\\(\\) has parameter \\$value with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Statistical/VarianceBase.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\TextData\\:\\:CONCATENATE\\(\\) has parameter \\$args with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/TextData.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\TextData\\:\\:SEARCHINSENSITIVE\\(\\) should return string but returns int\\|string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/TextData.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\TextData\\:\\:SEARCHSENSITIVE\\(\\) should return string but returns int\\|string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/TextData.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\TextData\\:\\:TRIMNONPRINTABLE\\(\\) should return string but returns string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/TextData.php + + - + message: "#^Parameter \\#1 \\$glue of function implode expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/TextData/Concatenate.php + + - + message: "#^Variable \\$value on left side of \\?\\? always exists and is not nullable\\.$#" + count: 4 + path: src/PhpSpreadsheet/Calculation/TextData/Extract.php + + - + message: "#^Cannot cast mixed to string\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/TextData/Format.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\TextData\\\\Format\\:\\:VALUE\\(\\) should return DateTimeInterface\\|float\\|int\\|string but returns mixed\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/TextData/Format.php + + - + message: "#^Parameter \\#1 \\$dateValue of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\DateTimeExcel\\\\DateValue\\:\\:fromString\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/TextData/Format.php + + - + message: "#^Parameter \\#1 \\$haystack of function strpos expects string, mixed given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Calculation/TextData/Format.php + + - + message: "#^Parameter \\#1 \\$str of function trim expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/TextData/Format.php + + - + message: "#^Parameter \\#1 \\$timeValue of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\DateTimeExcel\\\\TimeValue\\:\\:fromString\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/TextData/Format.php + + - + message: "#^Parameter \\#2 \\$subject of function preg_match_all expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/TextData/Format.php + + - + message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/TextData/Format.php + + - + message: "#^Cannot cast mixed to int\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/TextData/Helpers.php + + - + message: "#^Cannot cast mixed to string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/TextData/Helpers.php + + - + message: "#^Variable \\$value on left side of \\?\\? always exists and is not nullable\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/TextData/Text.php + + - + message: "#^Cannot access offset 'value' on mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Token/Stack.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Token\\\\Stack\\:\\:getStackItem\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Token/Stack.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Token\\\\Stack\\:\\:getStackItem\\(\\) has parameter \\$onlyIf with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Token/Stack.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Token\\\\Stack\\:\\:getStackItem\\(\\) has parameter \\$onlyIfNot with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Token/Stack.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Token\\\\Stack\\:\\:getStackItem\\(\\) has parameter \\$reference with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Token/Stack.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Token\\\\Stack\\:\\:getStackItem\\(\\) has parameter \\$storeKey with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Token/Stack.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Token\\\\Stack\\:\\:getStackItem\\(\\) has parameter \\$type with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Token/Stack.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Token\\\\Stack\\:\\:getStackItem\\(\\) has parameter \\$value with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Calculation/Token/Stack.php + + - + message: "#^Parameter \\#1 \\$dateValue of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Date\\:\\:stringToExcel\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Cell/AdvancedValueBinder.php + + - + message: "#^Parameter \\#1 \\$haystack of function strpos expects string, mixed given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Cell/AdvancedValueBinder.php + + - + message: "#^Parameter \\#1 \\$value of method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\AdvancedValueBinder\\:\\:setPercentage\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Cell/AdvancedValueBinder.php + + - + message: "#^Parameter \\#1 \\$value of method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\AdvancedValueBinder\\:\\:setTimeHoursMinutes\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Cell/AdvancedValueBinder.php + + - + message: "#^Parameter \\#1 \\$value of method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\AdvancedValueBinder\\:\\:setTimeHoursMinutesSeconds\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Cell/AdvancedValueBinder.php + + - + message: "#^Parameter \\#2 \\$subject of function preg_match expects string, mixed given\\.$#" + count: 7 + path: src/PhpSpreadsheet/Cell/AdvancedValueBinder.php + + - + message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, mixed given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Cell/AdvancedValueBinder.php + + - + message: "#^Cannot cast mixed to string\\.$#" + count: 2 + path: src/PhpSpreadsheet/Cell/Cell.php + + - + message: "#^Elseif branch is unreachable because previous condition is always true\\.$#" + count: 1 + path: src/PhpSpreadsheet/Cell/Cell.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell\\:\\:getFormulaAttributes\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Cell/Cell.php + + - + message: "#^Parameter \\#1 \\$textValue of static method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\DataType\\:\\:checkString\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\RichText\\\\RichText\\|string\\|null, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Cell/Cell.php + + - + message: "#^Parameter \\#2 \\$format of static method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\:\\:toFormattedString\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Cell/Cell.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell\\:\\:\\$formulaAttributes has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Cell/Cell.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell\\:\\:\\$parent \\(PhpOffice\\\\PhpSpreadsheet\\\\Collection\\\\Cells\\) in isset\\(\\) is not nullable\\.$#" + count: 6 + path: src/PhpSpreadsheet/Cell/Cell.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 1 + path: src/PhpSpreadsheet/Cell/Cell.php + + - + message: "#^Call to an undefined method object\\:\\:getHashCode\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Cell/Coordinate.php + + - + message: "#^Cannot use array destructuring on array\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Cell/Coordinate.php + + - + message: "#^Cannot use array destructuring on mixed\\.$#" + count: 2 + path: src/PhpSpreadsheet/Cell/Coordinate.php + + - + message: "#^Parameter \\#1 \\$cellAddress of static method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Coordinate\\:\\:absoluteCoordinate\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Cell/Coordinate.php + + - + message: "#^Parameter \\#1 \\$cellAddress of static method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Coordinate\\:\\:coordinateFromString\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Cell/Coordinate.php + + - + message: "#^Parameter \\#1 \\$input of function array_chunk expects array, array\\\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Cell/Coordinate.php + + - + message: "#^Parameter \\#1 \\$str of function strtoupper expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Cell/Coordinate.php + + - + message: "#^Parameter \\#2 \\$str of function explode expects string, array\\\\|string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Cell/Coordinate.php + + - + message: "#^Parameter \\#4 \\$currentRow of static method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Coordinate\\:\\:validateRange\\(\\) expects int, string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Cell/Coordinate.php + + - + message: "#^Parameter \\#5 \\$endRow of static method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Coordinate\\:\\:validateRange\\(\\) expects int, string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Cell/Coordinate.php + + - + message: "#^Cannot cast mixed to string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Cell/DataType.php + + - + message: "#^Parameter \\#1 \\$textValue of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\StringHelper\\:\\:substring\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Cell/DataType.php + + - + message: "#^Parameter \\#1 \\$str of function strtolower expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Cell/DataValidator.php + + - + message: "#^Parameter \\#1 \\$haystack of function strpos expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Cell/DefaultValueBinder.php + + - + message: "#^Parameter \\#1 \\$str of function ltrim expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Cell/DefaultValueBinder.php + + - + message: "#^Parameter \\#2 \\$subject of function preg_match expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Cell/DefaultValueBinder.php + + - + message: "#^Cannot cast mixed to string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Cell/StringValueBinder.php + + - + message: "#^Cannot access offset 'alpha' on mixed\\.$#" + count: 2 + path: src/PhpSpreadsheet/Chart/Axis.php + + - + message: "#^Cannot access offset 'size' on mixed\\.$#" + count: 4 + path: src/PhpSpreadsheet/Chart/Axis.php + + - + message: "#^Cannot access offset 'type' on mixed\\.$#" + count: 4 + path: src/PhpSpreadsheet/Chart/Axis.php + + - + message: "#^Cannot access offset 'value' on mixed\\.$#" + count: 2 + path: src/PhpSpreadsheet/Chart/Axis.php + + - + message: "#^Cannot access offset \\(int\\|string\\) on mixed\\.$#" + count: 2 + path: src/PhpSpreadsheet/Chart/Axis.php + + - + message: "#^Cannot access offset string on mixed\\.$#" + count: 2 + path: src/PhpSpreadsheet/Chart/Axis.php + + - + message: "#^Cannot cast mixed to int\\.$#" + count: 2 + path: src/PhpSpreadsheet/Chart/Axis.php + + - + message: "#^Cannot cast mixed to string\\.$#" + count: 2 + path: src/PhpSpreadsheet/Chart/Axis.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis\\:\\:getAxisNumberFormat\\(\\) should return string but returns mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Axis.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis\\:\\:getAxisOptionsProperty\\(\\) should return string but returns mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Axis.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis\\:\\:getFillProperty\\(\\) should return string but returns mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Axis.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis\\:\\:getLineProperty\\(\\) should return string but returns mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Axis.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis\\:\\:getSoftEdgesSize\\(\\) should return string but returns mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Axis.php + + - + message: "#^Parameter \\#1 \\$angle of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis\\:\\:setShadowAngle\\(\\) expects int, int\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Axis.php + + - + message: "#^Parameter \\#1 \\$blur of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis\\:\\:setShadowBlur\\(\\) expects float, float\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Axis.php + + - + message: "#^Parameter \\#1 \\$color of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis\\:\\:setGlowColor\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Axis.php + + - + message: "#^Parameter \\#1 \\$color of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis\\:\\:setShadowColor\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Axis.php + + - + message: "#^Parameter \\#1 \\$distance of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis\\:\\:setShadowDistance\\(\\) expects float, float\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Axis.php + + - + message: "#^Parameter \\#2 \\$alpha of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis\\:\\:setShadowColor\\(\\) expects int, int\\|string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Axis.php + + - + message: "#^Parameter \\#3 \\$alphaType of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis\\:\\:setShadowColor\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Axis.php + + - + message: "#^Parameter \\#3 \\$colorType of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis\\:\\:setGlowColor\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Axis.php + + - + message: "#^Call to an undefined method object\\:\\:render\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:getBottomRightXOffset\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:getBottomRightYOffset\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:getTopLeftXOffset\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:getTopLeftYOffset\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:setBottomRightCell\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:setBottomRightCell\\(\\) has parameter \\$cell with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:setBottomRightXOffset\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:setBottomRightXOffset\\(\\) has parameter \\$xOffset with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:setBottomRightYOffset\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:setBottomRightYOffset\\(\\) has parameter \\$yOffset with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:setTopLeftXOffset\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:setTopLeftXOffset\\(\\) has parameter \\$xOffset with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:setTopLeftYOffset\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:setTopLeftYOffset\\(\\) has parameter \\$yOffset with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:\\$legend \\(PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Legend\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Legend\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:\\$majorGridlines \\(PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\GridLines\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\GridLines\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:\\$minorGridlines \\(PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\GridLines\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\GridLines\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:\\$name \\(string\\) does not accept mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:\\$plotArea \\(PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\PlotArea\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\PlotArea\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:\\$plotVisibleOnly \\(bool\\) does not accept mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:\\$title \\(PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Title\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Title\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:\\$worksheet \\(PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:\\$xAxis \\(PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:\\$xAxisLabel \\(PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Title\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Title\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:\\$yAxis \\(PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\:\\:\\$yAxisLabel \\(PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Title\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Title\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Chart.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeries\\:\\:\\$plotGrouping \\(string\\) does not accept mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/DataSeries.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeries\\:\\:\\$plotType \\(string\\) does not accept mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/DataSeries.php + + - + message: "#^Strict comparison using \\=\\=\\= between PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeriesValues and null will always evaluate to false\\.$#" + count: 2 + path: src/PhpSpreadsheet/Chart/DataSeries.php + + - + message: "#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\\.$#" + count: 2 + path: src/PhpSpreadsheet/Chart/DataSeriesValues.php + + - + message: "#^Cannot use array destructuring on mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/DataSeriesValues.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeriesValues\\:\\:refresh\\(\\) has parameter \\$flatten with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/DataSeriesValues.php + + - + message: "#^Parameter \\#1 \\$input of function array_values expects array, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/DataSeriesValues.php + + - + message: "#^Parameter \\#1 \\$stack of function array_shift expects array, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/DataSeriesValues.php + + - + message: "#^Parameter \\#1 \\$var of function count expects array\\|Countable, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/DataSeriesValues.php + + - + message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/DataSeriesValues.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeriesValues\\:\\:\\$dataSource \\(string\\) does not accept string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/DataSeriesValues.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeriesValues\\:\\:\\$dataTypeValues has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/DataSeriesValues.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeriesValues\\:\\:\\$dataValues \\(array\\) does not accept mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/DataSeriesValues.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeriesValues\\:\\:\\$fillColor \\(array\\\\|string\\) does not accept array\\\\|string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/DataSeriesValues.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeriesValues\\:\\:\\$formatCode \\(string\\) does not accept mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/DataSeriesValues.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeriesValues\\:\\:\\$pointMarker \\(string\\) does not accept mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/DataSeriesValues.php + + - + message: "#^Cannot access offset \\(int\\|string\\) on mixed\\.$#" + count: 2 + path: src/PhpSpreadsheet/Chart/GridLines.php + + - + message: "#^Parameter \\#1 \\$angle of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\GridLines\\:\\:setShadowAngle\\(\\) expects int, int\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/GridLines.php + + - + message: "#^Parameter \\#1 \\$color of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\GridLines\\:\\:setGlowColor\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/GridLines.php + + - + message: "#^Parameter \\#1 \\$distance of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\GridLines\\:\\:setShadowDistance\\(\\) expects float, float\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/GridLines.php + + - + message: "#^Parameter \\#2 \\$alpha of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\GridLines\\:\\:setGlowColor\\(\\) expects int, int\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/GridLines.php + + - + message: "#^Parameter \\#3 \\$colorType of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\GridLines\\:\\:setGlowColor\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/GridLines.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\GridLines\\:\\:\\$glowProperties has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/GridLines.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\GridLines\\:\\:\\$lineProperties has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/GridLines.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\GridLines\\:\\:\\$objectState has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/GridLines.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\GridLines\\:\\:\\$shadowProperties has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/GridLines.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\GridLines\\:\\:\\$softEdges has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/GridLines.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Legend\\:\\:\\$layout \\(PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Layout\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Layout\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Legend.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Legend\\:\\:\\$positionXLref has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Legend.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\PlotArea\\:\\:\\$layout \\(PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Layout\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Layout\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/PlotArea.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Properties\\:\\:getArrayElementsValue\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Properties\\:\\:getArrayElementsValue\\(\\) has parameter \\$elements with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Properties\\:\\:getArrayElementsValue\\(\\) has parameter \\$properties with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Properties\\:\\:getLineStyleArrowSize\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Properties\\:\\:getLineStyleArrowSize\\(\\) has parameter \\$arrayKaySelector with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Properties\\:\\:getLineStyleArrowSize\\(\\) has parameter \\$arraySelector with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Properties\\:\\:getShadowPresetsMap\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Properties\\:\\:getShadowPresetsMap\\(\\) has parameter \\$presetsOption with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Properties\\:\\:getTrueAlpha\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Properties\\:\\:getTrueAlpha\\(\\) has parameter \\$alpha with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Properties\\:\\:setColorProperties\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Properties\\:\\:setColorProperties\\(\\) has parameter \\$alpha with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Properties\\:\\:setColorProperties\\(\\) has parameter \\$color with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Properties\\:\\:setColorProperties\\(\\) has parameter \\$colorType with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:formatDataSetLabels\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:formatDataSetLabels\\(\\) has parameter \\$datasetLabels with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:formatDataSetLabels\\(\\) has parameter \\$groupID with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:formatDataSetLabels\\(\\) has parameter \\$labelCount with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:formatDataSetLabels\\(\\) has parameter \\$rotation with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:formatPointMarker\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:formatPointMarker\\(\\) has parameter \\$markerID with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:formatPointMarker\\(\\) has parameter \\$seriesPlot with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:getCaption\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:getCaption\\(\\) has parameter \\$captionElement with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:percentageAdjustValues\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:percentageAdjustValues\\(\\) has parameter \\$dataValues with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:percentageAdjustValues\\(\\) has parameter \\$sumValues with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:percentageSumCalculation\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:percentageSumCalculation\\(\\) has parameter \\$groupID with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:percentageSumCalculation\\(\\) has parameter \\$seriesCount with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderAreaChart\\(\\) has parameter \\$dimensions with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderAreaChart\\(\\) has parameter \\$groupCount with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderBarChart\\(\\) has parameter \\$dimensions with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderBarChart\\(\\) has parameter \\$groupCount with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderBubbleChart\\(\\) has parameter \\$groupCount with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderCartesianPlotArea\\(\\) has parameter \\$type with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderCombinationChart\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderCombinationChart\\(\\) has parameter \\$dimensions with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderCombinationChart\\(\\) has parameter \\$groupCount with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderCombinationChart\\(\\) has parameter \\$outputDestination with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderContourChart\\(\\) has parameter \\$dimensions with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderContourChart\\(\\) has parameter \\$groupCount with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderLineChart\\(\\) has parameter \\$dimensions with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderLineChart\\(\\) has parameter \\$groupCount with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderPieChart\\(\\) has parameter \\$dimensions with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderPieChart\\(\\) has parameter \\$doughnut with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderPieChart\\(\\) has parameter \\$groupCount with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderPieChart\\(\\) has parameter \\$multiplePlots with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderPlotBar\\(\\) has parameter \\$dimensions with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderPlotBar\\(\\) has parameter \\$groupID with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderPlotContour\\(\\) has parameter \\$groupID with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderPlotLine\\(\\) has parameter \\$combination with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderPlotLine\\(\\) has parameter \\$dimensions with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderPlotLine\\(\\) has parameter \\$filled with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderPlotLine\\(\\) has parameter \\$groupID with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderPlotRadar\\(\\) has parameter \\$groupID with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderPlotScatter\\(\\) has parameter \\$bubble with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderPlotScatter\\(\\) has parameter \\$groupID with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderPlotStock\\(\\) has parameter \\$groupID with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderRadarChart\\(\\) has parameter \\$groupCount with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderScatterChart\\(\\) has parameter \\$groupCount with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:renderStockChart\\(\\) has parameter \\$groupCount with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:\\$chart has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:\\$colourSet has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:\\$graph has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:\\$height has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:\\$markSet has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:\\$plotColour has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:\\$plotMark has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:\\$width has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Renderer/JpGraph.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Title\\:\\:\\$layout \\(PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Layout\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Layout\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Chart/Title.php + + - + message: "#^Cannot call method attach\\(\\) on mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Collection/Cells.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Collection\\\\Cells\\:\\:get\\(\\) should return PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell\\|null but returns mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Collection/Cells.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Collection\\\\Cells\\:\\:\\$currentCell \\(PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell\\|null\\) does not accept mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Collection/Cells.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Collection\\\\Memory\\:\\:\\$cache has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Collection/Memory.php + + - + message: "#^Parameter \\#1 \\$namedRange of method PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\:\\:addNamedRange\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\NamedRange, \\$this\\(PhpOffice\\\\PhpSpreadsheet\\\\DefinedName\\) given\\.$#" + count: 1 + path: src/PhpSpreadsheet/DefinedName.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\DefinedName\\:\\:\\$scope \\(PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 3 + path: src/PhpSpreadsheet/DefinedName.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\DefinedName\\:\\:\\$worksheet \\(PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/DefinedName.php + + - + message: "#^Cannot cast mixed to float\\.$#" + count: 1 + path: src/PhpSpreadsheet/Document/Properties.php + + - + message: "#^Cannot cast mixed to int\\.$#" + count: 1 + path: src/PhpSpreadsheet/Document/Properties.php + + - + message: "#^Parameter \\#1 \\$timestamp of static method PhpOffice\\\\PhpSpreadsheet\\\\Document\\\\Properties\\:\\:intOrFloatTimestamp\\(\\) expects float\\|int\\|string\\|null, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Document/Properties.php + + - + message: "#^Cannot use array destructuring on array\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Dimension.php + + - + message: "#^Cannot call method setBold\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Cannot call method setColor\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Cannot call method setItalic\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Cannot call method setName\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Cannot call method setSize\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Cannot call method setStrikethrough\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Cannot call method setSubscript\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Cannot call method setSuperscript\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Cannot call method setUnderline\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Helper\\\\Html\\:\\:startFontTag\\(\\) has parameter \\$tag with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Parameter \\#1 \\$function of function call_user_func expects callable\\(\\)\\: mixed, array\\{\\$this\\(PhpOffice\\\\PhpSpreadsheet\\\\Helper\\\\Html\\), mixed\\} given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Parameter \\#1 \\$text of method PhpOffice\\\\PhpSpreadsheet\\\\RichText\\\\ITextElement\\:\\:setText\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Helper\\\\Html\\:\\:\\$bold has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Helper\\\\Html\\:\\:\\$color has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Helper\\\\Html\\:\\:\\$colourMap has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Helper\\\\Html\\:\\:\\$endTagCallbacks has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Helper\\\\Html\\:\\:\\$face has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Helper\\\\Html\\:\\:\\$italic has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Helper\\\\Html\\:\\:\\$size has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Helper\\\\Html\\:\\:\\$stack has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Helper\\\\Html\\:\\:\\$startTagCallbacks has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Helper\\\\Html\\:\\:\\$strikethrough has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Helper\\\\Html\\:\\:\\$stringData has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Helper\\\\Html\\:\\:\\$subscript has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Helper\\\\Html\\:\\:\\$superscript has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Helper\\\\Html\\:\\:\\$underline has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Html.php + + - + message: "#^Cannot access offset 0 on mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Sample.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Helper\\\\Sample\\:\\:getSamples\\(\\) should return array\\\\> but returns array\\\\>\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Sample.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Helper\\\\Sample\\:\\:log\\(\\) has parameter \\$message with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Sample.php + + - + message: "#^Parameter \\#1 \\$directory of class RecursiveDirectoryIterator constructor expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Sample.php + + - + message: "#^Parameter \\#1 \\$filename of function unlink expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Sample.php + + - + message: "#^Parameter \\#1 \\$path of function pathinfo expects string, array\\|string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Sample.php + + - + message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Sample.php + + - + message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, string\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Sample.php + + - + message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Helper/Sample.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\IOFactory\\:\\:createReader\\(\\) should return PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\IReader but returns object\\.$#" + count: 1 + path: src/PhpSpreadsheet/IOFactory.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\IOFactory\\:\\:createWriter\\(\\) should return PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\IWriter but returns object\\.$#" + count: 1 + path: src/PhpSpreadsheet/IOFactory.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\IOFactory\\:\\:\\$readers has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/IOFactory.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\IOFactory\\:\\:\\$writers has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/IOFactory.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\BaseReader\\:\\:getSecurityScanner\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/BaseReader.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\BaseReader\\:\\:\\$fileHandle has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/BaseReader.php + + - + message: "#^Cannot use array destructuring on mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Gnumeric.php + + - + message: "#^Comparison operation \"\\<\" between int and SimpleXMLElement\\|null results in an error\\.$#" + count: 2 + path: src/PhpSpreadsheet/Reader/Gnumeric.php + + - + message: "#^Parameter \\#1 \\$str of function trim expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Gnumeric.php + + - + message: "#^Offset 'percentage' does not exist on SimpleXMLElement\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Gnumeric/PageSetup.php + + - + message: "#^Offset 'value' does not exist on SimpleXMLElement\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Reader/Gnumeric/PageSetup.php + + - + message: "#^Variable \\$orientation on left side of \\?\\? always exists and is not nullable\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Gnumeric/PageSetup.php + + - + message: "#^Comparison operation \"\\<\\=\" between SimpleXMLElement\\|null and int results in an error\\.$#" + count: 2 + path: src/PhpSpreadsheet/Reader/Gnumeric/Styles.php + + - + message: "#^Comparison operation \"\\>\" between SimpleXMLElement\\|null and int results in an error\\.$#" + count: 2 + path: src/PhpSpreadsheet/Reader/Gnumeric/Styles.php + + - + message: "#^Cannot cast mixed to string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Html.php + + - + message: "#^Variable \\$value on left side of \\?\\? always exists and is not nullable\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Html.php + + - + message: "#^Cannot call method children\\(\\) on SimpleXMLElement\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods.php + + - + message: "#^Cannot call method getAttributeNS\\(\\) on DOMElement\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods.php + + - + message: "#^Cannot call method getElementsByTagNameNS\\(\\) on DOMElement\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Reader/Ods.php + + - + message: "#^Cannot call method getNamedItem\\(\\) on DOMNamedNodeMap\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods.php + + - + message: "#^Cannot call method getNamespaces\\(\\) on SimpleXMLElement\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods.php + + - + message: "#^Cannot call method setSelectedCellByColumnAndRow\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods.php + + - + message: "#^If condition is always true\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\:\\:listWorksheetNames\\(\\) should return array\\ but returns array\\\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods.php + + - + message: "#^Parameter \\#1 \\$element of method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\:\\:scanElementForText\\(\\) expects DOMNode, DOMElement\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods.php + + - + message: "#^Parameter \\#1 \\$settings of method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\:\\:lookForActiveSheet\\(\\) expects DOMElement, DOMElement\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods.php + + - + message: "#^Parameter \\#1 \\$settings of method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\:\\:lookForSelectedCells\\(\\) expects DOMElement, DOMElement\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods.php + + - + message: "#^While loop condition is always true\\.$#" + count: 2 + path: src/PhpSpreadsheet/Reader/Ods.php + + - + message: "#^Cannot use array destructuring on mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods/DefinedNames.php + + - + message: "#^Parameter \\#1 \\$worksheetName of method PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\:\\:getSheetByName\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods/DefinedNames.php + + - + message: "#^Cannot call method getElementsByTagNameNS\\(\\) on DOMElement\\|null\\.$#" + count: 3 + path: src/PhpSpreadsheet/Reader/Ods/PageSettings.php + + - + message: "#^Expression on left side of \\?\\? is not nullable\\.$#" + count: 6 + path: src/PhpSpreadsheet/Reader/Ods/PageSettings.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\PageSettings\\:\\:\\$masterPrintStylesCrossReference has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods/PageSettings.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\PageSettings\\:\\:\\$masterStylesCrossReference has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods/PageSettings.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\PageSettings\\:\\:\\$officeNs has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods/PageSettings.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\PageSettings\\:\\:\\$pageLayoutStyles has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods/PageSettings.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\PageSettings\\:\\:\\$stylesFo has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods/PageSettings.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\PageSettings\\:\\:\\$stylesNs has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods/PageSettings.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\Properties\\:\\:load\\(\\) has parameter \\$namespacesMeta with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\Properties\\:\\:setMetaProperties\\(\\) has parameter \\$namespacesMeta with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\Properties\\:\\:setMetaProperties\\(\\) has parameter \\$propertyName with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\Properties\\:\\:setUserDefinedProperty\\(\\) has parameter \\$propertyValue with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\Properties\\:\\:setUserDefinedProperty\\(\\) has parameter \\$propertyValueAttributes with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods/Properties.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\Properties\\:\\:\\$spreadsheet has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Ods/Properties.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Security\\\\XmlScanner\\:\\:__construct\\(\\) has parameter \\$pattern with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Security/XmlScanner.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Security\\\\XmlScanner\\:\\:getInstance\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Security/XmlScanner.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Security\\\\XmlScanner\\:\\:threadSafeLibxmlDisableEntityLoaderAvailability\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Security/XmlScanner.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Security\\\\XmlScanner\\:\\:toUtf8\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Security/XmlScanner.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Security\\\\XmlScanner\\:\\:toUtf8\\(\\) has parameter \\$xml with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Security/XmlScanner.php + + - + message: "#^Parameter \\#2 \\$subject of function preg_match expects string, array\\\\|string\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Security/XmlScanner.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Security\\\\XmlScanner\\:\\:\\$callback has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Security/XmlScanner.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Security\\\\XmlScanner\\:\\:\\$libxmlDisableEntityLoaderValue has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Security/XmlScanner.php + + - + message: "#^Parameter \\#4 \\$cellData of method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Slk\\:\\:processCFinal\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Slk.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\\\SpgrContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\\\SpgrContainer\\\\SpContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\\\BstoreContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\\\BstoreContainer\\\\BSE\\:\\:getDgContainer\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\\\SpgrContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\\\SpgrContainer\\\\SpContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\\\BstoreContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\\\BstoreContainer\\\\BSE\\:\\:getDggContainer\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Call to an undefined method object\\:\\:getEndCoordinates\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Call to an undefined method object\\:\\:getEndOffsetX\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Call to an undefined method object\\:\\:getEndOffsetY\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Call to an undefined method object\\:\\:getNestingLevel\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Call to an undefined method object\\:\\:getOPT\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Call to an undefined method object\\:\\:getStartCoordinates\\(\\)\\.$#" + count: 2 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Call to an undefined method object\\:\\:getStartOffsetX\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Call to an undefined method object\\:\\:getStartOffsetY\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Cannot access offset 0 on mixed\\.$#" + count: 4 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Cannot access offset 1 on array\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Cannot access offset 1 on mixed\\.$#" + count: 7 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^If condition is always true\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\:\\:includeCellRangeFiltered\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\:\\:includeCellRangeFiltered\\(\\) has parameter \\$cellRangeAddress with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\:\\:parseRichText\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\:\\:parseRichText\\(\\) has parameter \\$is with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Negated boolean expression is always false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#1 \\$block of method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\:\\:makeKey\\(\\) expects int, float given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#1 \\$errorStyle of method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\DataValidation\\:\\:setErrorStyle\\(\\) expects string, int\\|string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#1 \\$haystack of function strpos expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#1 \\$operator of method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\DataValidation\\:\\:setOperator\\(\\) expects string, int\\|string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#1 \\$showSummaryBelow of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\:\\:setShowSummaryBelow\\(\\) expects bool, int given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#1 \\$showSummaryRight of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\:\\:setShowSummaryRight\\(\\) expects bool, int given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#1 \\$str of function trim expects string, mixed given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#1 \\$type of method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\DataValidation\\:\\:setType\\(\\) expects string, int\\|string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#1 \\$var of function count expects array\\|Countable, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#1 \\$worksheetName of method PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\:\\:getSheetByName\\(\\) expects string, mixed given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#2 \\$row of method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\IReadFilter\\:\\:readCell\\(\\) expects int, string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#2 \\$row of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Xls\\:\\:sizeRow\\(\\) expects int, string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#2 \\$startRow of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Xls\\:\\:getDistanceY\\(\\) expects int, string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, int\\|string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, mixed given\\.$#" + count: 3 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Parameter \\#4 \\$endRow of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Xls\\:\\:getDistanceY\\(\\) expects int, string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\:\\:\\$data \\(string\\) does not accept string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\:\\:\\$documentSummaryInformation \\(string\\) does not accept string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\:\\:\\$documentSummaryInformation \\(string\\) in isset\\(\\) is not nullable\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\:\\:\\$md5Ctxt is never written, only read\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\:\\:\\$summaryInformation \\(string\\) does not accept string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\:\\:\\$summaryInformation \\(string\\) in isset\\(\\) is not nullable\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 8 + path: src/PhpSpreadsheet/Reader/Xls.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\\\Color\\\\BIFF5\\:\\:\\$map has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/Color/BIFF5.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\\\Color\\\\BIFF8\\:\\:\\$map has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/Color/BIFF8.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\\\Color\\\\BuiltIn\\:\\:\\$map has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/Color/BuiltIn.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\\\ErrorCode\\:\\:\\$map has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/ErrorCode.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\\\Escher\\:\\:\\$object \\(PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\\\SpgrContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\\\SpgrContainer\\\\SpContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\\\BstoreContainer\\|PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\\\BstoreContainer\\\\BSE\\) does not accept mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/Escher.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\\\MD5\\:\\:f\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/MD5.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\\\MD5\\:\\:g\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/MD5.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\\\MD5\\:\\:h\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/MD5.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\\\MD5\\:\\:i\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/MD5.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\\\MD5\\:\\:rotate\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/MD5.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\\\MD5\\:\\:step\\(\\) has parameter \\$func with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/MD5.php + + - + message: "#^Parameter \\#1 \\$input of function array_values expects array, array\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/MD5.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\\\RC4\\:\\:\\$i has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/RC4.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\\\RC4\\:\\:\\$j has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/RC4.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xls\\\\RC4\\:\\:\\$s has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xls/RC4.php + + - + message: "#^Argument of an invalid type array\\\\|false supplied for foreach, only iterables are supported\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Cannot access offset 0 on array\\\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Cannot access offset 0 on mixed\\.$#" + count: 5 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Cannot access property \\$r on SimpleXMLElement\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Cannot call method addChart\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Cannot call method setBold\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Cannot call method setColor\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Cannot call method setItalic\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Cannot call method setName\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Cannot call method setSize\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Cannot call method setStrikethrough\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Cannot call method setSubscript\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Cannot call method setSuperscript\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Cannot call method setUnderline\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Cannot use array destructuring on mixed\\.$#" + count: 2 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Comparison operation \"\\>\" between SimpleXMLElement\\|null and 0 results in an error\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:boolean\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:boolean\\(\\) has parameter \\$value with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:castToBoolean\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:castToBoolean\\(\\) has parameter \\$c with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:castToError\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:castToError\\(\\) has parameter \\$c with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:castToFormula\\(\\) has parameter \\$c with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:castToFormula\\(\\) has parameter \\$calculatedValue with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:castToFormula\\(\\) has parameter \\$castBaseType with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:castToFormula\\(\\) has parameter \\$cellDataType with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:castToFormula\\(\\) has parameter \\$r with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:castToFormula\\(\\) has parameter \\$sharedFormulas with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:castToFormula\\(\\) has parameter \\$value with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:castToString\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:castToString\\(\\) has parameter \\$c with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:dirAdd\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:dirAdd\\(\\) has parameter \\$add with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:dirAdd\\(\\) has parameter \\$base with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:getArrayItem\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:getArrayItem\\(\\) has parameter \\$array with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:getArrayItem\\(\\) has parameter \\$key with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:getFromZipArchive\\(\\) should return string but returns string\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:readFormControlProperties\\(\\) has parameter \\$dir with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:readFormControlProperties\\(\\) has parameter \\$docSheet with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:readFormControlProperties\\(\\) has parameter \\$fileWorksheet with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:readPrinterSettings\\(\\) has parameter \\$dir with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:readPrinterSettings\\(\\) has parameter \\$docSheet with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:readPrinterSettings\\(\\) has parameter \\$fileWorksheet with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:stripWhiteSpaceFromStyleString\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:stripWhiteSpaceFromStyleString\\(\\) has parameter \\$string with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:toCSSArray\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\:\\:toCSSArray\\(\\) has parameter \\$style with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Negated boolean expression is always true\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Parameter \\#1 \\$fontSizeInPoints of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Font\\:\\:fontSizeToPixels\\(\\) expects int, string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Parameter \\#1 \\$haystack of function strpos expects string, int\\|string given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Parameter \\#1 \\$haystack of function strpos expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Parameter \\#1 \\$sizeInCm of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Font\\:\\:centimeterSizeToPixels\\(\\) expects int, string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Parameter \\#1 \\$sizeInInch of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Font\\:\\:inchSizeToPixels\\(\\) expects int, string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Parameter \\#1 \\$str of function trim expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Parameter \\#1 \\$worksheetName of method PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\:\\:getSheetByName\\(\\) expects string, array\\|string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, int\\|string given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, mixed given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Reader/Xlsx.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\AutoFilter\\:\\:readAutoFilter\\(\\) has parameter \\$autoFilterRange with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\AutoFilter\\:\\:readAutoFilter\\(\\) has parameter \\$xmlSheet with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php + + - + message: "#^Parameter \\#1 \\$operator of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter\\\\Column\\\\Rule\\:\\:setRule\\(\\) expects string, null given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\AutoFilter\\:\\:\\$worksheet has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\AutoFilter\\:\\:\\$worksheetXml has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\BaseParserClass\\:\\:boolean\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/BaseParserClass.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\BaseParserClass\\:\\:boolean\\(\\) has parameter \\$value with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/BaseParserClass.php + + - + message: "#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Cannot call method getFont\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\RichText\\\\Run\\|null\\.$#" + count: 12 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Cannot call method setBold\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Cannot call method setColor\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Cannot call method setItalic\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Cannot call method setName\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Cannot call method setSize\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Cannot call method setStrikethrough\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Cannot call method setSubscript\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Cannot call method setSuperscript\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Cannot call method setUnderline\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 3 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartDataSeries\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartDataSeries\\(\\) has parameter \\$chartDetail with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartDataSeries\\(\\) has parameter \\$namespacesChartMeta with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartDataSeries\\(\\) has parameter \\$plotType with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartDataSeriesValueSet\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartDataSeriesValueSet\\(\\) has parameter \\$marker with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartDataSeriesValueSet\\(\\) has parameter \\$namespacesChartMeta with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartDataSeriesValueSet\\(\\) has parameter \\$seriesDetail with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartDataSeriesValues\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartDataSeriesValues\\(\\) has parameter \\$dataType with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartDataSeriesValues\\(\\) has parameter \\$seriesValueSet with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartDataSeriesValuesMultiLevel\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartDataSeriesValuesMultiLevel\\(\\) has parameter \\$dataType with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartDataSeriesValuesMultiLevel\\(\\) has parameter \\$seriesValueSet with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartLayoutDetails\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartLayoutDetails\\(\\) has parameter \\$chartDetail with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartLayoutDetails\\(\\) has parameter \\$namespacesChartMeta with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:chartTitle\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:parseRichText\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:readChartAttributes\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:readChartAttributes\\(\\) has parameter \\$chartDetail with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:readColor\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:readColor\\(\\) has parameter \\$background with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Chart\\:\\:readColor\\(\\) has parameter \\$color with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Parameter \\#1 \\$position of class PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Legend constructor expects string, bool\\|float\\|int\\|string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Parameter \\#1 \\$showBubbleSize of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Layout\\:\\:setShowBubbleSize\\(\\) expects bool, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Parameter \\#1 \\$showCategoryName of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Layout\\:\\:setShowCatName\\(\\) expects bool, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Parameter \\#1 \\$showDataLabelValues of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Layout\\:\\:setShowVal\\(\\) expects bool, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Parameter \\#1 \\$showLeaderLines of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Layout\\:\\:setShowLeaderLines\\(\\) expects bool, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Parameter \\#1 \\$showLegendKey of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Layout\\:\\:setShowLegendKey\\(\\) expects bool, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Parameter \\#1 \\$showPercentage of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Layout\\:\\:setShowPercent\\(\\) expects bool, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Parameter \\#1 \\$showSeriesName of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Layout\\:\\:setShowSerName\\(\\) expects bool, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Parameter \\#3 \\$overlay of class PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Legend constructor expects bool, bool\\|float\\|int\\|string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Parameter \\#3 \\$plotOrder of class PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeries constructor expects array\\, array\\ given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Parameter \\#4 \\$pointCount of class PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeriesValues constructor expects int, null given\\.$#" + count: 4 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Parameter \\#6 \\$displayBlanksAs of class PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart constructor expects string, bool\\|float\\|int\\|string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Parameter \\#7 \\$plotDirection of class PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeries constructor expects string\\|null, bool\\|float\\|int\\|string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ColumnAndRowAttributes\\:\\:isFilteredColumn\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ColumnAndRowAttributes\\:\\:isFilteredColumn\\(\\) has parameter \\$columnCoordinate with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ColumnAndRowAttributes\\:\\:isFilteredRow\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ColumnAndRowAttributes\\:\\:isFilteredRow\\(\\) has parameter \\$rowCoordinate with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ColumnAndRowAttributes\\:\\:readColumnAttributes\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ColumnAndRowAttributes\\:\\:readColumnAttributes\\(\\) has parameter \\$readDataOnly with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ColumnAndRowAttributes\\:\\:readColumnRangeAttributes\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ColumnAndRowAttributes\\:\\:readColumnRangeAttributes\\(\\) has parameter \\$readDataOnly with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ColumnAndRowAttributes\\:\\:readRowAttributes\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ColumnAndRowAttributes\\:\\:readRowAttributes\\(\\) has parameter \\$readDataOnly with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ColumnAndRowAttributes\\:\\:\\$worksheet has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ColumnAndRowAttributes\\:\\:\\$worksheetXml has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ConditionalStyles\\:\\:readConditionalStyles\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ConditionalStyles\\:\\:readConditionalStyles\\(\\) has parameter \\$xmlSheet with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ConditionalStyles\\:\\:readDataBarExtLstOfConditionalRule\\(\\) has parameter \\$cfRule with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ConditionalStyles\\:\\:readDataBarExtLstOfConditionalRule\\(\\) has parameter \\$conditionalFormattingRuleExtensions with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ConditionalStyles\\:\\:readDataBarOfConditionalRule\\(\\) has parameter \\$cfRule with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ConditionalStyles\\:\\:readDataBarOfConditionalRule\\(\\) has parameter \\$conditionalFormattingRuleExtensions with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ConditionalStyles\\:\\:readStyleRules\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ConditionalStyles\\:\\:readStyleRules\\(\\) has parameter \\$cfRules with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ConditionalStyles\\:\\:readStyleRules\\(\\) has parameter \\$extLst with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ConditionalStyles\\:\\:setConditionalStyles\\(\\) has parameter \\$xmlExtLst with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ConditionalStyles\\:\\:\\$dxfs has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ConditionalStyles\\:\\:\\$worksheet has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\ConditionalStyles\\:\\:\\$worksheetXml has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\DataValidations\\:\\:\\$worksheet has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\DataValidations\\:\\:\\$worksheetXml has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Hyperlinks\\:\\:\\$hyperlinks has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Hyperlinks\\:\\:\\$worksheet has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\PageSetup\\:\\:load\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\PageSetup\\:\\:pageSetup\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\PageSetup\\:\\:\\$worksheet has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\PageSetup\\:\\:\\$worksheetXml has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\SheetViewOptions\\:\\:\\$worksheet has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\SheetViewOptions\\:\\:\\$worksheetXml has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles\\:\\:dxfs\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles\\:\\:dxfs\\(\\) has parameter \\$readDataOnly with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles\\:\\:getArrayItem\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles\\:\\:getArrayItem\\(\\) has parameter \\$array with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles\\:\\:getArrayItem\\(\\) has parameter \\$key with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles\\:\\:readColor\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles\\:\\:readColor\\(\\) has parameter \\$background with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles\\:\\:readColor\\(\\) has parameter \\$color with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles\\:\\:readProtectionHidden\\(\\) has parameter \\$style with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles\\:\\:readProtectionLocked\\(\\) has parameter \\$style with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles\\:\\:readStyle\\(\\) has parameter \\$style with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles\\:\\:setStyleBaseData\\(\\) has parameter \\$cellStyles with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles\\:\\:setStyleBaseData\\(\\) has parameter \\$styles with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles\\:\\:styles\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php + + - + message: "#^Parameter \\#1 \\$hexColourValue of static method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Color\\:\\:changeBrightness\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php + + - + message: "#^Parameter \\#2 \\$alignmentXml of static method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles\\:\\:readAlignmentStyle\\(\\) expects SimpleXMLElement, object given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles\\:\\:\\$cellStyles has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles\\:\\:\\$styleXml has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles\\:\\:\\$styles has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php + + - + message: "#^Static property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Styles\\:\\:\\$theme \\(PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Theme\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xlsx\\\\Theme\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xlsx/Styles.php + + - + message: "#^Parameter \\#1 \\$haystack of function strpos expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xml.php + + - + message: "#^Parameter \\#1 \\$textValue of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\StringHelper\\:\\:convertEncoding\\(\\) expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xml.php + + - + message: "#^Parameter \\#2 \\$subject of function preg_match expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xml.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Xml\\:\\:\\$fileContents has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xml.php + + - + message: "#^Argument of an invalid type SimpleXMLElement\\|null supplied for foreach, only iterables are supported\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xml/Properties.php + + - + message: "#^Argument of an invalid type SimpleXMLElement\\|null supplied for foreach, only iterables are supported\\.$#" + count: 1 + path: src/PhpSpreadsheet/Reader/Xml/Style.php + + - + message: "#^Cannot use array destructuring on array\\|null\\.$#" + count: 4 + path: src/PhpSpreadsheet/ReferenceHelper.php + + - + message: "#^Elseif condition is always true\\.$#" + count: 1 + path: src/PhpSpreadsheet/ReferenceHelper.php + + - + message: "#^Expression on left side of \\?\\? is not nullable\\.$#" + count: 1 + path: src/PhpSpreadsheet/ReferenceHelper.php + + - + message: "#^Parameter \\#1 \\$formula of method PhpOffice\\\\PhpSpreadsheet\\\\ReferenceHelper\\:\\:updateFormulaReferences\\(\\) expects string, mixed given\\.$#" + count: 2 + path: src/PhpSpreadsheet/ReferenceHelper.php + + - + message: "#^Parameter \\#1 \\$haystack of function strpos expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/ReferenceHelper.php + + - + message: "#^Parameter \\#1 \\$index of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension\\:\\:setRowIndex\\(\\) expects int, string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/ReferenceHelper.php + + - + message: "#^Parameter \\#2 \\$callback of function uksort expects callable\\(\\(int\\|string\\), \\(int\\|string\\)\\)\\: int, array\\{'self', 'cellReverseSort'\\} given\\.$#" + count: 4 + path: src/PhpSpreadsheet/ReferenceHelper.php + + - + message: "#^Parameter \\#2 \\$callback of function uksort expects callable\\(\\(int\\|string\\), \\(int\\|string\\)\\)\\: int, array\\{'self', 'cellSort'\\} given\\.$#" + count: 4 + path: src/PhpSpreadsheet/ReferenceHelper.php + + - + message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/ReferenceHelper.php + + - + message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/ReferenceHelper.php + + - + message: "#^Static property PhpOffice\\\\PhpSpreadsheet\\\\ReferenceHelper\\:\\:\\$instance \\(PhpOffice\\\\PhpSpreadsheet\\\\ReferenceHelper\\) in isset\\(\\) is not nullable\\.$#" + count: 1 + path: src/PhpSpreadsheet/ReferenceHelper.php + + - + message: "#^Parameter \\#1 \\$text of class PhpOffice\\\\PhpSpreadsheet\\\\RichText\\\\Run constructor expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/RichText/RichText.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\RichText\\\\Run\\:\\:\\$font \\(PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/RichText/Run.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Settings\\:\\:getHttpClient\\(\\) should return Psr\\\\Http\\\\Client\\\\ClientInterface but returns Psr\\\\Http\\\\Client\\\\ClientInterface\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Settings.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Settings\\:\\:getRequestFactory\\(\\) should return Psr\\\\Http\\\\Message\\\\RequestFactoryInterface but returns Psr\\\\Http\\\\Message\\\\RequestFactoryInterface\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Settings.php + + - + message: "#^Negated boolean expression is always false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Settings.php + + - + message: "#^Result of && is always false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Settings.php + + - + message: "#^Strict comparison using \\=\\=\\= between int and null will always evaluate to false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Settings.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Date\\:\\:stringToExcel\\(\\) should return float\\|false but returns mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Date.php + + - + message: "#^Parameter \\#1 \\$excelFormatCode of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Date\\:\\:isDateTimeFormatCode\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Date.php + + - + message: "#^Parameter \\#1 \\$string of function substr expects string, int given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Shared/Date.php + + - + message: "#^Parameter \\#1 \\$unixTimestamp of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Date\\:\\:timestampToExcel\\(\\) expects int, float\\|int\\|string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Date.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Date\\:\\:\\$possibleDateFormatCharacters has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Date.php + + - + message: "#^Cannot access offset 1 on array\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Drawing.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Drawing\\:\\:imagecreatefrombmp\\(\\) should return GdImage\\|resource but returns resource\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Drawing.php + + - + message: "#^Parameter \\#1 \\$fp of function feof expects resource, resource\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Drawing.php + + - + message: "#^Parameter \\#1 \\$fp of function fread expects resource, resource\\|false given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Shared/Drawing.php + + - + message: "#^Parameter \\#1 \\$im of function imagecolorallocate expects resource, resource\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Drawing.php + + - + message: "#^Parameter \\#1 \\$im of function imagesetpixel expects resource, resource\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Drawing.php + + - + message: "#^Parameter \\#1 \\$x_size of function imagecreatetruecolor expects int, float\\|int given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Drawing.php + + - + message: "#^Parameter \\#2 \\$data of function unpack expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Drawing.php + + - + message: "#^Parameter \\#2 \\$red of function imagecolorallocate expects int, float\\|int given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Drawing.php + + - + message: "#^Parameter \\#2 \\$y_size of function imagecreatetruecolor expects int, float\\|int given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Drawing.php + + - + message: "#^Parameter \\#3 \\$green of function imagecolorallocate expects int, float\\|int given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Drawing.php + + - + message: "#^Parameter \\#3 \\$y of function imagesetpixel expects int, float\\|int given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Drawing.php + + - + message: "#^Parameter \\#4 \\$blue of function imagecolorallocate expects int, float\\|int given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Drawing.php + + - + message: "#^Parameter \\#4 \\$col of function imagesetpixel expects int, int\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Drawing.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\:\\:getDgId\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Escher/DgContainer.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\:\\:getLastSpId\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Escher/DgContainer.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\:\\:getSpgrContainer\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Escher/DgContainer.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\:\\:setDgId\\(\\) has parameter \\$value with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Escher/DgContainer.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\:\\:setLastSpId\\(\\) has parameter \\$value with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Escher/DgContainer.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\:\\:setSpgrContainer\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Escher/DgContainer.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\:\\:setSpgrContainer\\(\\) has parameter \\$spgrContainer with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Escher/DgContainer.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\:\\:\\$spgrContainer has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Escher/DgContainer.php + + - + message: "#^Cannot call method setParent\\(\\) on mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DgContainer\\\\SpgrContainer\\:\\:getChildren\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\\\BstoreContainer\\\\BSE\\:\\:\\$parent is never read, only written\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE.php + + - + message: "#^Cannot access offset 0 on array\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Font.php + + - + message: "#^Cannot access offset 2 on array\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Font.php + + - + message: "#^Cannot access offset 4 on array\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Font.php + + - + message: "#^Cannot access offset 6 on array\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Font.php + + - + message: "#^Parameter \\#1 \\$size of function imagettfbbox expects float, float\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Font.php + + - + message: "#^Parameter \\#2 \\$defaultFont of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Drawing\\:\\:pixelsToCellDimension\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font, PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Font.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Font\\:\\:\\$autoSizeMethods has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Font.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Font.php + + - + message: "#^Variable \\$cellText on left side of \\?\\? always exists and is not nullable\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Font.php + + - + message: "#^Parameter \\#1 \\$number of function abs expects float\\|int\\|string, mixed given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Shared/JAMA/EigenvalueDecomposition.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\JAMA\\\\EigenvalueDecomposition\\:\\:\\$cdivi has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/EigenvalueDecomposition.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\JAMA\\\\EigenvalueDecomposition\\:\\:\\$e has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/EigenvalueDecomposition.php + + - + message: "#^Else branch is unreachable because previous condition is always true\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/LUDecomposition.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\JAMA\\\\LUDecomposition\\:\\:getDoublePivot\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/LUDecomposition.php + + - + message: "#^Call to function is_string\\(\\) with float\\|int will always evaluate to false\\.$#" + count: 5 + path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\JAMA\\\\Matrix\\:\\:__construct\\(\\) has parameter \\$args with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\JAMA\\\\Matrix\\:\\:arrayLeftDivide\\(\\) has parameter \\$args with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\JAMA\\\\Matrix\\:\\:arrayLeftDivideEquals\\(\\) has parameter \\$args with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\JAMA\\\\Matrix\\:\\:arrayRightDivide\\(\\) has parameter \\$args with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\JAMA\\\\Matrix\\:\\:arrayRightDivideEquals\\(\\) has parameter \\$args with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\JAMA\\\\Matrix\\:\\:arrayTimes\\(\\) has parameter \\$args with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\JAMA\\\\Matrix\\:\\:arrayTimesEquals\\(\\) has parameter \\$args with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\JAMA\\\\Matrix\\:\\:concat\\(\\) has parameter \\$args with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\JAMA\\\\Matrix\\:\\:getMatrix\\(\\) has parameter \\$args with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\JAMA\\\\Matrix\\:\\:minus\\(\\) has parameter \\$args with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\JAMA\\\\Matrix\\:\\:minusEquals\\(\\) has parameter \\$args with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\JAMA\\\\Matrix\\:\\:plus\\(\\) has parameter \\$args with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\JAMA\\\\Matrix\\:\\:plusEquals\\(\\) has parameter \\$args with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\JAMA\\\\Matrix\\:\\:power\\(\\) has parameter \\$args with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\JAMA\\\\Matrix\\:\\:times\\(\\) has parameter \\$args with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php + + - + message: "#^Parameter \\#1 \\$str of function trim expects string, float\\|int given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php + + - + message: "#^Parameter \\#3 \\$c of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\JAMA\\\\Matrix\\:\\:set\\(\\) expects float\\|int\\|null, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php + + - + message: "#^Parameter \\#3 \\$c of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\JAMA\\\\Matrix\\:\\:set\\(\\) expects float\\|int\\|null, string given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php + + - + message: "#^Result of && is always false\\.$#" + count: 10 + path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 19 + path: src/PhpSpreadsheet/Shared/JAMA/Matrix.php + + - + message: "#^Cannot call method getArray\\(\\) on mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/SingularValueDecomposition.php + + - + message: "#^Cannot call method getColumnDimension\\(\\) on mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/SingularValueDecomposition.php + + - + message: "#^Cannot call method getRowDimension\\(\\) on mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/JAMA/SingularValueDecomposition.php + + - + message: "#^If condition is always true\\.$#" + count: 7 + path: src/PhpSpreadsheet/Shared/JAMA/SingularValueDecomposition.php + + - + message: "#^Left side of && is always true\\.$#" + count: 4 + path: src/PhpSpreadsheet/Shared/JAMA/SingularValueDecomposition.php + + - + message: "#^Parameter \\#1 \\$number of function abs expects float\\|int\\|string, mixed given\\.$#" + count: 4 + path: src/PhpSpreadsheet/Shared/JAMA/utils/Maths.php + + - + message: "#^Cannot access offset 1 on array\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE.php + + - + message: "#^Cannot access offset 2 on array\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE.php + + - + message: "#^Cannot access offset 3 on array\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE.php + + - + message: "#^Cannot access offset 4 on array\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE.php + + - + message: "#^Cannot use array destructuring on array\\|false\\.$#" + count: 3 + path: src/PhpSpreadsheet/Shared/OLE.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\:\\:getData\\(\\) should return string but returns string\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\:\\:getStream\\(\\) should return resource but returns resource\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE.php + + - + message: "#^Parameter \\#1 \\$No of class PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS constructor expects int, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE.php + + - + message: "#^Parameter \\#1 \\$oleTimestamp of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\:\\:OLE2LocalDate\\(\\) expects string, string\\|false given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Shared/OLE.php + + - + message: "#^Parameter \\#1 \\$string of function substr expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE.php + + - + message: "#^Parameter \\#2 \\$data of function unpack expects string, string\\|false given\\.$#" + count: 3 + path: src/PhpSpreadsheet/Shared/OLE.php + + - + message: "#^Parameter \\#2 \\$name of class PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS constructor expects string, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE.php + + - + message: "#^Parameter \\#3 \\$type of class PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS constructor expects int, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE.php + + - + message: "#^Parameter \\#4 \\$prev of class PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS constructor expects int, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE.php + + - + message: "#^Parameter \\#5 \\$next of class PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS constructor expects int, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE.php + + - + message: "#^Parameter \\#6 \\$dir of class PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS constructor expects int, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE.php + + - + message: "#^Parameter \\#9 \\$data of class PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS constructor expects string, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\:\\:\\$root \\(PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\\\Root\\) in isset\\(\\) is not nullable\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE.php + + - + message: "#^Parameter \\#1 \\$var of function count expects array\\|Countable, string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/ChainedBlockStream.php + + - + message: "#^Parameter \\#2 \\$offset of function array_slice expects int, float given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/PPS.php + + - + message: "#^Parameter \\#3 \\$length of function array_slice expects int\\|null, float given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/PPS.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\:\\:\\$_data \\(string\\) in isset\\(\\) is not nullable\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/PPS.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\:\\:\\$startBlock \\(int\\) on left side of \\?\\? is not nullable\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/PPS.php + + - + message: "#^Parameter \\#1 \\$No of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\:\\:__construct\\(\\) expects int, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/PPS/File.php + + - + message: "#^Parameter \\#4 \\$prev of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\:\\:__construct\\(\\) expects int, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/PPS/File.php + + - + message: "#^Parameter \\#5 \\$next of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\:\\:__construct\\(\\) expects int, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/PPS/File.php + + - + message: "#^Parameter \\#6 \\$dir of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\:\\:__construct\\(\\) expects int, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/PPS/File.php + + - + message: "#^Parameter \\#1 \\$No of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\:\\:__construct\\(\\) expects int, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/PPS/Root.php + + - + message: "#^Parameter \\#1 \\$iSBDcnt of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\\\Root\\:\\:saveHeader\\(\\) expects int, float given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/PPS/Root.php + + - + message: "#^Parameter \\#1 \\$iSbdSize of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\\\Root\\:\\:saveBbd\\(\\) expects int, float given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/PPS/Root.php + + - + message: "#^Parameter \\#1 \\$iStBlk of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\\\Root\\:\\:saveBigData\\(\\) expects int, float given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/PPS/Root.php + + - + message: "#^Parameter \\#2 \\$iBBcnt of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\\\Root\\:\\:saveHeader\\(\\) expects int, float given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/PPS/Root.php + + - + message: "#^Parameter \\#2 \\$iBsize of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\\\Root\\:\\:saveBbd\\(\\) expects int, float given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/PPS/Root.php + + - + message: "#^Parameter \\#3 \\$iPPScnt of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\\\Root\\:\\:saveHeader\\(\\) expects int, float given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/PPS/Root.php + + - + message: "#^Parameter \\#3 \\$iPpsCnt of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\\\Root\\:\\:saveBbd\\(\\) expects int, float given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/PPS/Root.php + + - + message: "#^Parameter \\#4 \\$prev of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\:\\:__construct\\(\\) expects int, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/PPS/Root.php + + - + message: "#^Parameter \\#5 \\$next of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\:\\:__construct\\(\\) expects int, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/PPS/Root.php + + - + message: "#^Parameter \\#6 \\$dir of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\:\\:__construct\\(\\) expects int, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/PPS/Root.php + + - + message: "#^Parameter \\#9 \\$data of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\:\\:__construct\\(\\) expects string, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/PPS/Root.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\\\Root\\:\\:\\$bigBlockSize \\(int\\) in isset\\(\\) is not nullable\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/PPS/Root.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\\\Root\\:\\:\\$smallBlockSize \\(int\\) in isset\\(\\) is not nullable\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLE/PPS/Root.php + + - + message: "#^Parameter \\#1 \\$data of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLERead\\:\\:getInt4d\\(\\) expects string, string\\|false given\\.$#" + count: 8 + path: src/PhpSpreadsheet/Shared/OLERead.php + + - + message: "#^Parameter \\#1 \\$string of function substr expects string, string\\|false given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Shared/OLERead.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLERead\\:\\:\\$data has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLERead.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLERead\\:\\:\\$documentSummaryInformation has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLERead.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLERead\\:\\:\\$summaryInformation has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLERead.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLERead\\:\\:\\$wrkbook has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLERead.php + + - + message: "#^Strict comparison using \\=\\=\\= between int and null will always evaluate to false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/OLERead.php + + - + message: "#^Cannot access offset 'fontidx' on mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/StringHelper.php + + - + message: "#^Cannot access offset 'strlen' on mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/StringHelper.php + + - + message: "#^Cannot cast mixed to string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/StringHelper.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\StringHelper\\:\\:countCharacters\\(\\) should return int but returns int\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/StringHelper.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\StringHelper\\:\\:formatNumber\\(\\) should return string but returns array\\|string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/StringHelper.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\StringHelper\\:\\:mbIsUpper\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/StringHelper.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\StringHelper\\:\\:mbIsUpper\\(\\) has parameter \\$character with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/StringHelper.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\StringHelper\\:\\:mbStrSplit\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/StringHelper.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\StringHelper\\:\\:mbStrSplit\\(\\) has parameter \\$string with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/StringHelper.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\StringHelper\\:\\:sanitizeUTF8\\(\\) should return string but returns string\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/StringHelper.php + + - + message: "#^Parameter \\#1 \\$string of function strlen expects string, float given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/StringHelper.php + + - + message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, float given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/StringHelper.php + + - + message: "#^Static property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\StringHelper\\:\\:\\$decimalSeparator \\(string\\) in isset\\(\\) is not nullable\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/StringHelper.php + + - + message: "#^Static property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\StringHelper\\:\\:\\$isIconvEnabled \\(bool\\) in isset\\(\\) is not nullable\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/StringHelper.php + + - + message: "#^Static property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\StringHelper\\:\\:\\$thousandsSeparator \\(string\\) in isset\\(\\) is not nullable\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/StringHelper.php + + - + message: "#^Variable \\$textValue on left side of \\?\\? always exists and is not nullable\\.$#" + count: 3 + path: src/PhpSpreadsheet/Shared/StringHelper.php + + - + message: "#^Static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\TimeZone\\:\\:validateTimeZone\\(\\) is unused\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/TimeZone.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:calculateGoodnessOfFit\\(\\) has parameter \\$const with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:calculateGoodnessOfFit\\(\\) has parameter \\$meanX with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:calculateGoodnessOfFit\\(\\) has parameter \\$meanY with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:calculateGoodnessOfFit\\(\\) has parameter \\$sumX with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:calculateGoodnessOfFit\\(\\) has parameter \\$sumX2 with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:calculateGoodnessOfFit\\(\\) has parameter \\$sumXY with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:calculateGoodnessOfFit\\(\\) has parameter \\$sumY with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:calculateGoodnessOfFit\\(\\) has parameter \\$sumY2 with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:getBestFitType\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:getError\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:sumSquares\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:\\$DFResiduals has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:\\$SSRegression has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:\\$SSResiduals has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:\\$correlation has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:\\$covariance has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:\\$f has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:\\$goodnessOfFit has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:\\$intersect has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:\\$intersectSE has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:\\$slope has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:\\$slopeSE has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:\\$stdevOfResiduals has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:\\$xOffset has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:\\$yOffset has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/BestFit.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\PolynomialBestFit\\:\\:getCoefficients\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\PolynomialBestFit\\:\\:getCoefficients\\(\\) has parameter \\$dp with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php + + - + message: "#^Parameter \\#2 \\.\\.\\.\\$args of function array_merge expects array, float given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php + + - + message: "#^Array \\(array\\\\) does not accept object\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/Trend.php + + - + message: "#^Call to an undefined method object\\:\\:getGoodnessOfFit\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/Trend.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\Trend\\:\\:calculate\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/Trend.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\Trend\\:\\:calculate\\(\\) has parameter \\$const with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/Trend.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\Trend\\:\\:calculate\\(\\) has parameter \\$trendType with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/Trend.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\Trend\\:\\:calculate\\(\\) has parameter \\$xValues with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/Trend.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\Trend\\:\\:calculate\\(\\) has parameter \\$yValues with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Trend/Trend.php + + - + message: "#^Parameter \\#1 \\$order of class PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\PolynomialBestFit constructor expects int, string given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Shared/Trend/Trend.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\XMLWriter\\:\\:getData\\(\\) should return string but returns string\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/XMLWriter.php + + - + message: "#^Parameter \\#1 \\$uri of method XMLWriter\\:\\:openUri\\(\\) expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/XMLWriter.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\XMLWriter\\:\\:\\$debugEnabled has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/XMLWriter.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\XMLWriter\\:\\:\\$tempFileName \\(string\\) does not accept string\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/XMLWriter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Xls\\:\\:sizeCol\\(\\) should return int but returns mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Shared/Xls.php + + - + message: "#^Call to function is_array\\(\\) with string will always evaluate to false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Spreadsheet.php + + - + message: "#^Comparison operation \"\\<\\=\" between int\\ and 1000 is always true\\.$#" + count: 1 + path: src/PhpSpreadsheet/Spreadsheet.php + + - + message: "#^Parameter \\#1 \\$path of function pathinfo expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Spreadsheet.php + + - + message: "#^Parameter \\#1 \\$worksheet of method PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\:\\:getIndex\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet, PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Spreadsheet.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\:\\:\\$ribbonXMLData \\(array\\{target\\: string, data\\: string\\}\\|null\\) does not accept array\\{target\\: mixed, data\\: mixed\\}\\.$#" + count: 1 + path: src/PhpSpreadsheet/Spreadsheet.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\:\\:\\$workbookViewVisibilityValues has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Spreadsheet.php + + - + message: "#^Result of \\|\\| is always true\\.$#" + count: 1 + path: src/PhpSpreadsheet/Spreadsheet.php + + - + message: "#^Strict comparison using \\=\\=\\= between PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet and null will always evaluate to false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Spreadsheet.php + + - + message: "#^Strict comparison using \\=\\=\\= between string and null will always evaluate to false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Spreadsheet.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 1 + path: src/PhpSpreadsheet/Spreadsheet.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\|PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Style\\:\\:getSharedComponent\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/Alignment.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\|PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Style\\:\\:getSharedComponent\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/Border.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\|PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Style\\:\\:getStyleArray\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/Border.php + + - + message: "#^Parameter \\#1 \\$parent of method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Supervisor\\:\\:bindParent\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\|PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Style, \\$this\\(PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Border\\) given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/Border.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\|PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Style\\:\\:getSharedComponent\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/Borders.php + + - + message: "#^Parameter \\#1 \\$parent of method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Supervisor\\:\\:bindParent\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\|PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Style, \\$this\\(PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Borders\\) given\\.$#" + count: 10 + path: src/PhpSpreadsheet/Style/Borders.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\|PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Style\\:\\:getSharedComponent\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/Color.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\|PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Style\\:\\:getStyleArray\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/Color.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Border\\|PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Fill\\:\\:getColor\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/Color.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Border\\|PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Fill\\:\\:getEndColor\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/Color.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Border\\|PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Fill\\:\\:getStartColor\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/Color.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Color\\:\\:getColourComponent\\(\\) should return int\\|string but returns float\\|int\\|string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/Color.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Conditional\\:\\:\\$condition \\(array\\\\) does not accept array\\\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/Conditional.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Conditional\\:\\:\\$style \\(PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Style\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Style\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/Conditional.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalDataBar\\:\\:setConditionalFormattingRuleExt\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBar.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalDataBar\\:\\:setMaximumConditionalFormatValueObject\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBar.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalDataBar\\:\\:setMinimumConditionalFormatValueObject\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBar.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalDataBar\\:\\:setShowValue\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBar.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalDataBarExtension\\:\\:getXmlAttributes\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBarExtension.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalDataBarExtension\\:\\:getXmlElements\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBarExtension.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalDataBarExtension\\:\\:setMaximumConditionalFormatValueObject\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBarExtension.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalDataBarExtension\\:\\:setMinimumConditionalFormatValueObject\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBarExtension.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalFormatValueObject\\:\\:__construct\\(\\) has parameter \\$type with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormatValueObject.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalFormatValueObject\\:\\:__construct\\(\\) has parameter \\$value with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormatValueObject.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalFormatValueObject\\:\\:setCellFormula\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormatValueObject.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalFormatValueObject\\:\\:setType\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormatValueObject.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalFormatValueObject\\:\\:setValue\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormatValueObject.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalFormatValueObject\\:\\:\\$cellFormula has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormatValueObject.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalFormatValueObject\\:\\:\\$type has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormatValueObject.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalFormatValueObject\\:\\:\\$value has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormatValueObject.php + + - + message: "#^Cannot access property \\$axisPosition on SimpleXMLElement\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php + + - + message: "#^Cannot access property \\$border on SimpleXMLElement\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php + + - + message: "#^Cannot access property \\$direction on SimpleXMLElement\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php + + - + message: "#^Cannot access property \\$gradient on SimpleXMLElement\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php + + - + message: "#^Cannot access property \\$maxLength on SimpleXMLElement\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php + + - + message: "#^Cannot access property \\$minLength on SimpleXMLElement\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php + + - + message: "#^Cannot access property \\$negativeBarBorderColorSameAsPositive on SimpleXMLElement\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalFormattingRuleExtension\\:\\:__construct\\(\\) has parameter \\$id with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalFormattingRuleExtension\\:\\:generateUuid\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalFormattingRuleExtension\\:\\:parseExtDataBarElementChildrenFromXml\\(\\) has parameter \\$ns with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalFormattingRuleExtension\\:\\:parseExtLstXml\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalFormattingRuleExtension\\:\\:parseExtLstXml\\(\\) has parameter \\$extLstXml with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php + + - + message: "#^Offset 'rgb' does not exist on SimpleXMLElement\\|null\\.$#" + count: 4 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php + + - + message: "#^Offset 'theme' does not exist on SimpleXMLElement\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php + + - + message: "#^Offset 'tint' does not exist on SimpleXMLElement\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalFormattingRuleExtension\\:\\:\\$id has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\|PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Style\\:\\:getSharedComponent\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/Fill.php + + - + message: "#^Parameter \\#1 \\$parent of method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Supervisor\\:\\:bindParent\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\|PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Style, \\$this\\(PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Fill\\) given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Style/Fill.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\|PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Style\\:\\:getSharedComponent\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/Font.php + + - + message: "#^Parameter \\#1 \\$parent of method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Supervisor\\:\\:bindParent\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\|PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Style, \\$this\\(PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\) given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/Font.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\|PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Style\\:\\:getSharedComponent\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\:\\:\\$builtInFormatCode \\(int\\|false\\) does not accept bool\\|int\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\DateFormatter\\:\\:escapeQuotesCallback\\(\\) has parameter \\$matches with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/DateFormatter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\DateFormatter\\:\\:format\\(\\) has parameter \\$value with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/DateFormatter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\DateFormatter\\:\\:setLowercaseCallback\\(\\) has parameter \\$matches with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/DateFormatter.php + + - + message: "#^Parameter \\#1 \\$format of method DateTime\\:\\:format\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/DateFormatter.php + + - + message: "#^Parameter \\#2 \\$callback of function preg_replace_callback expects callable\\(\\)\\: mixed, array\\{'self', 'escapeQuotesCallback'\\} given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/DateFormatter.php + + - + message: "#^Parameter \\#2 \\$callback of function preg_replace_callback expects callable\\(\\)\\: mixed, array\\{'self', 'setLowercaseCallback'\\} given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/DateFormatter.php + + - + message: "#^Parameter \\#2 \\$replace of function str_replace expects array\\|string, int given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/DateFormatter.php + + - + message: "#^Parameter \\#2 \\$str of function explode expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/DateFormatter.php + + - + message: "#^Parameter \\#3 \\$subject of function preg_replace expects array\\|string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/DateFormatter.php + + - + message: "#^Parameter \\#3 \\$subject of function preg_replace_callback expects array\\|string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/DateFormatter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\Formatter\\:\\:splitFormat\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/Formatter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\Formatter\\:\\:splitFormat\\(\\) has parameter \\$sections with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/Formatter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\Formatter\\:\\:splitFormat\\(\\) has parameter \\$value with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/Formatter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\Formatter\\:\\:splitFormatCompare\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/Formatter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\Formatter\\:\\:splitFormatCompare\\(\\) has parameter \\$cond with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/Formatter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\Formatter\\:\\:splitFormatCompare\\(\\) has parameter \\$dfcond with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/Formatter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\Formatter\\:\\:splitFormatCompare\\(\\) has parameter \\$dfval with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/Formatter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\Formatter\\:\\:splitFormatCompare\\(\\) has parameter \\$val with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/Formatter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\Formatter\\:\\:splitFormatCompare\\(\\) has parameter \\$value with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/Formatter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\Formatter\\:\\:toFormattedString\\(\\) should return string but returns float\\|int\\|string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/Formatter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\Formatter\\:\\:toFormattedString\\(\\) should return string but returns mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/Formatter.php + + - + message: "#^Parameter \\#2 \\$subject of function preg_split expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/Formatter.php + + - + message: "#^Parameter \\#3 \\$subject of function preg_replace expects array\\|string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/Formatter.php + + - + message: "#^Cannot cast mixed to float\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/FractionFormatter.php + + - + message: "#^Cannot cast mixed to int\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/NumberFormatter.php + + - + message: "#^Cannot cast mixed to string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/NumberFormatter.php + + - + message: "#^Parameter \\#1 \\$number of function abs expects float\\|int\\|string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/NumberFormatter.php + + - + message: "#^Parameter \\#1 \\$number of function number_format expects float, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/NumberFormatter.php + + - + message: "#^Parameter \\#1 \\$x of function fmod expects float, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/NumberFormatter.php + + - + message: "#^Parameter \\#2 \\.\\.\\.\\$values of function sprintf expects bool\\|float\\|int\\|string\\|null, mixed given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Style/NumberFormat/NumberFormatter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\\\PercentageFormatter\\:\\:format\\(\\) has parameter \\$value with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/PercentageFormatter.php + + - + message: "#^Parameter \\#1 \\$format of function sprintf expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/NumberFormat/PercentageFormatter.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\|PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Style\\:\\:getSharedComponent\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/Protection.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\|PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Style\\:\\:getCellXfByIndex\\(\\)\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/Style.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Style\\:\\:getParent\\(\\) should return PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet but returns PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\|PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Style\\.$#" + count: 1 + path: src/PhpSpreadsheet/Style/Style.php + + - + message: "#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Cannot access offset 'date' on mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Cannot access offset 'dateTime' on mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Cannot access offset 'time' on mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Cannot use array destructuring on mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter\\:\\:filterTestInDateGroupSet\\(\\) should return bool but returns mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter\\:\\:filterTestInSimpleDataSet\\(\\) should return bool but returns mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Parameter \\#1 \\$haystack of function strpos expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Parameter \\#1 \\$range of static method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Coordinate\\:\\:rangeBoundaries\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Parameter \\#1 \\$string of function strlen expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Parameter \\#1 \\$visible of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Dimension\\:\\:setVisible\\(\\) expects bool, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Parameter \\#2 \\$haystack of function in_array expects array, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Parameter \\#3 \\$length of function array_slice expects int\\|null, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter\\:\\:\\$range \\(string\\) does not accept mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter.php + + - + message: "#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter/Column.php + + - + message: "#^Array \\(array\\\\) does not accept mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter/Column.php + + - + message: "#^Cannot call method setParent\\(\\) on mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter/Column.php + + - + message: "#^Cannot clone non\\-object variable \\$v of type mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter/Column.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter\\\\Column\\:\\:getAttribute\\(\\) should return int\\|string\\|null but returns mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter/Column.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter\\\\Column\\:\\:getAttributes\\(\\) should return array\\ but returns array\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/AutoFilter/Column.php + + - + message: "#^Cannot call method getCell\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/BaseDrawing.php + + - + message: "#^Cannot call method getDrawingCollection\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/BaseDrawing.php + + - + message: "#^Cannot call method getHashCode\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/BaseDrawing.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\BaseDrawing\\:\\:\\$shadow \\(PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Drawing\\\\Shadow\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Drawing\\\\Shadow\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/BaseDrawing.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\CellIterator\\:\\:adjustForExistingOnlyRange\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/CellIterator.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Column\\:\\:\\$parent \\(PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Column.php + + - + message: "#^Cannot use array destructuring on array\\|false\\.$#" + count: 2 + path: src/PhpSpreadsheet/Worksheet/Drawing.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Drawing\\\\Shadow\\:\\:\\$color \\(PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Color\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Color\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Drawing/Shadow.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\PageSetup\\:\\:getPrintArea\\(\\) should return string but returns string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/PageSetup.php + + - + message: "#^Parameter \\#1 \\$value of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\PageSetup\\:\\:setFirstPageNumber\\(\\) expects int, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/PageSetup.php + + - + message: "#^Parameter \\#2 \\$str of function explode expects string, string\\|null given\\.$#" + count: 5 + path: src/PhpSpreadsheet/Worksheet/PageSetup.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\PageSetup\\:\\:\\$pageOrder has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/PageSetup.php + + - + message: "#^Strict comparison using \\=\\=\\= between int\\ and null will always evaluate to false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/PageSetup.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Row\\:\\:\\$worksheet \\(PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Row.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\SheetView\\:\\:\\$sheetViewTypes has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/SheetView.php + + - + message: "#^Strict comparison using \\=\\=\\= between int\\ and null will always evaluate to false\\.$#" + count: 2 + path: src/PhpSpreadsheet/Worksheet/SheetView.php + + - + message: "#^Strict comparison using \\=\\=\\= between string and null will always evaluate to false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/SheetView.php + + - + message: "#^Cannot access offset 0 on mixed\\.$#" + count: 2 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Cannot access offset 1 on mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Cannot call method getCalculatedValue\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Cannot call method getHashCode\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Cannot call method getValue\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell\\|null\\.$#" + count: 4 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Cannot call method getValue\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\DefinedName\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Cannot call method getWorksheet\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\DefinedName\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Cannot call method getXfIndex\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Cannot call method rangeToArray\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^If condition is always true\\.$#" + count: 2 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Left side of && is always true\\.$#" + count: 2 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\:\\:getChartByName\\(\\) should return PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\|false but returns PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Parameter \\#1 \\$index of class PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\ColumnDimension constructor expects string, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Parameter \\#1 \\$index of class PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowDimension constructor expects int, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Parameter \\#1 \\$input of function array_splice expects array, ArrayObject\\ given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Parameter \\#1 \\$range of class PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter constructor expects string, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Parameter \\#1 \\$range of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter\\:\\:setRange\\(\\) expects string, null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Parameter \\#1 \\$range of static method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Coordinate\\:\\:rangeDimension\\(\\) expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Parameter \\#1 \\$row of method PhpOffice\\\\PhpSpreadsheet\\\\Collection\\\\Cells\\:\\:removeRow\\(\\) expects string, int given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Parameter \\#1 \\$str of function strtoupper expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Parameter \\#1 \\$worksheetName of method PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\:\\:getSheetByName\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Parameter \\#2 \\$format of static method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\:\\:toFormattedString\\(\\) expects string, string\\|null given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Parameter \\#2 \\$start of function substr expects int, int\\<0, max\\>\\|false given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Parameter \\#3 \\$rotation of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Font\\:\\:calculateColumnWidth\\(\\) expects int, int\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\:\\:\\$parent \\(PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\) does not accept PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Result of && is always true\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Right side of && is always true\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Strict comparison using \\=\\=\\= between string and null will always evaluate to false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Worksheet/Worksheet.php + + - + message: "#^Cannot cast mixed to string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Csv.php + + - + message: "#^Call to function array_key_exists\\(\\) with int and array\\{none\\: 'none', dashDot\\: '1px dashed', dashDotDot\\: '1px dotted', dashed\\: '1px dashed', dotted\\: '1px dotted', double\\: '3px double', hair\\: '1px solid', medium\\: '2px solid', \\.\\.\\.\\} will always evaluate to false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Cannot access offset 'mime' on array\\|false\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Cannot access offset 0 on array\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Cannot access offset 1 on array\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Cannot call method getSubscript\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Cannot call method getSuperscript\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:calculateSpansOmitRows\\(\\) has parameter \\$candidateSpannedRow with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:calculateSpansOmitRows\\(\\) has parameter \\$sheet with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:calculateSpansOmitRows\\(\\) has parameter \\$sheetIndex with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateHTMLFooter\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateMeta\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateMeta\\(\\) has parameter \\$desc with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateMeta\\(\\) has parameter \\$val with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowCellCss\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowCellCss\\(\\) has parameter \\$cellAddress with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowCellCss\\(\\) has parameter \\$columnNumber with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowCellCss\\(\\) has parameter \\$row with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowCellData\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowCellData\\(\\) has parameter \\$cell with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowCellData\\(\\) has parameter \\$cellType with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowCellData\\(\\) has parameter \\$cssClass with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowCellDataValue\\(\\) has parameter \\$cell with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowCellDataValue\\(\\) has parameter \\$cellData with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowCellDataValueRich\\(\\) has parameter \\$cell with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowCellDataValueRich\\(\\) has parameter \\$cellData with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowIncludeCharts\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowIncludeCharts\\(\\) has parameter \\$coordinate with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowSpans\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowSpans\\(\\) has parameter \\$colSpan with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowSpans\\(\\) has parameter \\$html with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowSpans\\(\\) has parameter \\$rowSpan with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowWriteCell\\(\\) has parameter \\$cellData with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowWriteCell\\(\\) has parameter \\$cellType with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowWriteCell\\(\\) has parameter \\$colNum with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowWriteCell\\(\\) has parameter \\$colSpan with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowWriteCell\\(\\) has parameter \\$coordinate with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowWriteCell\\(\\) has parameter \\$cssClass with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowWriteCell\\(\\) has parameter \\$html with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowWriteCell\\(\\) has parameter \\$row with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowWriteCell\\(\\) has parameter \\$rowSpan with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateRowWriteCell\\(\\) has parameter \\$sheetIndex with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateSheetPrep\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateSheetStarts\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateSheetStarts\\(\\) has parameter \\$rowMin with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateSheetStarts\\(\\) has parameter \\$sheet with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateSheetTags\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateSheetTags\\(\\) has parameter \\$row with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateSheetTags\\(\\) has parameter \\$tbodyStart with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateSheetTags\\(\\) has parameter \\$theadEnd with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateSheetTags\\(\\) has parameter \\$theadStart with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateTableFooter\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateTableTag\\(\\) has parameter \\$html with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateTableTag\\(\\) has parameter \\$id with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateTableTag\\(\\) has parameter \\$sheetIndex with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateTableTagInline\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:generateTableTagInline\\(\\) has parameter \\$id with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:getSheetIndex\\(\\) should return int but returns int\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Parameter \\#1 \\$borderStyle of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:mapBorderStyle\\(\\) expects int, string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Parameter \\#1 \\$font of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:createCSSStyleFont\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font, PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Parameter \\#1 \\$hAlign of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:mapHAlign\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Parameter \\#1 \\$im of function imagepng expects resource, GdImage\\|resource given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Parameter \\#1 \\$str of function base64_encode expects string, string\\|false given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Parameter \\#1 \\$string of function htmlspecialchars expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Parameter \\#1 \\$vAlign of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Html\\:\\:mapVAlign\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Parameter \\#2 \\$length of function fread expects int\\<0, max\\>, int\\<0, max\\>\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Parameter \\#3 \\$use_include_path of function fopen expects bool, int given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Ternary operator condition is always true\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Html.php + + - + message: "#^Negated boolean expression is always false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Ods.php + + - + message: "#^If condition is always true\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Ods/Cell/Style.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Ods\\\\Cell\\\\Style\\:\\:\\$writer has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Ods/Cell/Style.php + + - + message: "#^Parameter \\#1 \\$range of static method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Coordinate\\:\\:splitRange\\(\\) expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Ods/Content.php + + - + message: "#^Parameter \\#2 \\$content of method XMLWriter\\:\\:writeElement\\(\\) expects string\\|null, mixed given\\.$#" + count: 4 + path: src/PhpSpreadsheet/Writer/Ods/Content.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int given\\.$#" + count: 4 + path: src/PhpSpreadsheet/Writer/Ods/Content.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int\\<2, max\\> given\\.$#" + count: 3 + path: src/PhpSpreadsheet/Writer/Ods/Content.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, mixed given\\.$#" + count: 3 + path: src/PhpSpreadsheet/Writer/Ods/Content.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Ods\\\\Content\\:\\:\\$formulaConvertor has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Ods/Content.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Ods\\\\Formula\\:\\:\\$definedNames has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Ods/Formula.php + + - + message: "#^Parameter \\#1 \\$date of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Date\\:\\:dateTimeFromTimestamp\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Ods/Meta.php + + - + message: "#^Parameter \\#1 \\$rawTextData of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\XMLWriter\\:\\:writeRawData\\(\\) expects array\\\\|string\\|null, mixed given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Ods/Meta.php + + - + message: "#^Parameter \\#1 \\$content of method XMLWriter\\:\\:text\\(\\) expects string, int given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Ods/Settings.php + + - + message: "#^Parameter \\#2 \\$str of function fwrite expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Pdf/Dompdf.php + + - + message: "#^Strict comparison using \\=\\=\\= between int and null will always evaluate to false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Pdf/Dompdf.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and int will always evaluate to false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Pdf/Mpdf.php + + - + message: "#^Strict comparison using \\=\\=\\= between int and null will always evaluate to false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Pdf/Tcpdf.php + + - + message: "#^Cannot call method getHashCode\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls.php + + - + message: "#^Cannot use array destructuring on array\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls.php + + - + message: "#^Offset 'endCoordinates' does not exist on array\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls.php + + - + message: "#^Offset 'endOffsetX' does not exist on array\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls.php + + - + message: "#^Offset 'endOffsetY' does not exist on array\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls.php + + - + message: "#^Offset 'startCoordinates' does not exist on array\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls.php + + - + message: "#^Offset 'startOffsetX' does not exist on array\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls.php + + - + message: "#^Offset 'startOffsetY' does not exist on array\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls.php + + - + message: "#^Parameter \\#1 \\$blipType of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\\\BstoreContainer\\\\BSE\\:\\:setBlipType\\(\\) expects int, int\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls.php + + - + message: "#^Parameter \\#1 \\$data of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Escher\\\\DggContainer\\\\BstoreContainer\\\\BSE\\\\Blip\\:\\:setData\\(\\) expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls.php + + - + message: "#^Parameter \\#1 \\$font of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Workbook\\:\\:addFont\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font, PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls.php + + - + message: "#^Parameter \\#1 \\$im of function imagepng expects resource, GdImage\\|resource given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls.php + + - + message: "#^Parameter \\#1 \\$im of function imagepng expects resource, resource\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\:\\:\\$documentSummaryInformation \\(string\\) in isset\\(\\) is not nullable\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\:\\:\\$summaryInformation \\(string\\) in isset\\(\\) is not nullable\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\BIFFwriter\\:\\:writeEof\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/BIFFwriter.php + + - + message: "#^Static property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\BIFFwriter\\:\\:\\$byteOrder \\(int\\) in isset\\(\\) is not nullable\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/BIFFwriter.php + + - + message: "#^Elseif condition is always true\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Escher.php + + - + message: "#^If condition is always true\\.$#" + count: 3 + path: src/PhpSpreadsheet/Writer/Xls/Escher.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Escher\\:\\:\\$data has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Escher.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Escher\\:\\:\\$object has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Escher.php + + - + message: "#^If condition is always false\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xls/Font.php + + - + message: "#^Parameter \\#1 \\$bold of static method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Font\\:\\:mapBold\\(\\) expects bool, bool\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Font.php + + - + message: "#^Parameter \\#1 \\$fontName of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Font\\:\\:getCharsetFromFontName\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Font.php + + - + message: "#^Parameter \\#1 \\$textValue of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\StringHelper\\:\\:UTF8toBIFF8UnicodeShort\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Font.php + + - + message: "#^Parameter \\#1 \\$underline of static method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Font\\:\\:mapUnderline\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Font.php + + - + message: "#^Cannot use array destructuring on mixed\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xls/Parser.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Parser\\:\\:advance\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Parser.php + + - + message: "#^Offset 'left' does not exist on non\\-empty\\-array\\|string\\.$#" + count: 6 + path: src/PhpSpreadsheet/Writer/Xls/Parser.php + + - + message: "#^Offset 'right' does not exist on non\\-empty\\-array\\|string\\.$#" + count: 5 + path: src/PhpSpreadsheet/Writer/Xls/Parser.php + + - + message: "#^Offset 'value' does not exist on non\\-empty\\-array\\|string\\.$#" + count: 7 + path: src/PhpSpreadsheet/Writer/Xls/Parser.php + + - + message: "#^Parameter \\#1 \\$cell of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Parser\\:\\:cellToPackedRowcol\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Parser.php + + - + message: "#^Parameter \\#1 \\$cell of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Parser\\:\\:convertRef2d\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Parser.php + + - + message: "#^Parameter \\#1 \\$cell of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Parser\\:\\:convertRef3d\\(\\) expects string, mixed given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xls/Parser.php + + - + message: "#^Parameter \\#1 \\$errorCode of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Parser\\:\\:convertError\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Parser.php + + - + message: "#^Parameter \\#1 \\$ext_ref of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Parser\\:\\:getRefIndex\\(\\) expects string, mixed given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xls/Parser.php + + - + message: "#^Parameter \\#1 \\$haystack of function substr_count expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Parser.php + + - + message: "#^Parameter \\#1 \\$name of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Parser\\:\\:convertDefinedName\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Parser.php + + - + message: "#^Parameter \\#1 \\$range of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Parser\\:\\:convertRange2d\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Parser.php + + - + message: "#^Parameter \\#1 \\$str of function strrev expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Parser.php + + - + message: "#^Parameter \\#1 \\$string of function substr expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Parser.php + + - + message: "#^Parameter \\#1 \\$string of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Parser\\:\\:convertString\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Parser.php + + - + message: "#^Parameter \\#1 \\$token of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Parser\\:\\:convertRange3d\\(\\) expects string, mixed given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xls/Parser.php + + - + message: "#^Parameter \\#2 \\$str of function explode expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Parser.php + + - + message: "#^Parameter \\#2 \\$subject of function preg_match expects string, mixed given\\.$#" + count: 20 + path: src/PhpSpreadsheet/Writer/Xls/Parser.php + + - + message: "#^Parameter \\#3 \\$subject of function preg_replace expects array\\|string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Parser.php + + - + message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Parser.php + + - + message: "#^Part \\$token \\(mixed\\) of encapsed string cannot be cast to string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Parser.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Parser\\:\\:\\$parseTree \\(string\\) does not accept mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Parser.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Parser\\:\\:\\$spreadsheet has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Parser.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 5 + path: src/PhpSpreadsheet/Writer/Xls/Parser.php + + - + message: "#^Cannot access offset 'encoding' on array\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Workbook.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Workbook\\:\\:writeAllDefinedNamesBiff8\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Workbook.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Workbook\\:\\:writeExternalsheetBiff8\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Workbook.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Workbook\\:\\:writeMsoDrawingGroup\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Workbook.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Workbook\\:\\:writeSupbookInternal\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Workbook.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Workbook\\:\\:\\$biffSize is never read, only written\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Workbook.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Workbook\\:\\:\\$colors has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Workbook.php + + - + message: "#^Cannot access offset 'comp' on array\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Cannot access offset 'ident' on array\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Cannot access offset 'sa' on array\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Cannot access offset 1 on array\\|false\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Cannot access offset 2 on array\\|false\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Cannot call method getHashCode\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Expression on left side of \\?\\? is not nullable\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#1 \\$ascii of function chr expects int, float given\\.$#" + count: 3 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#1 \\$bitmap of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Worksheet\\:\\:processBitmap\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#1 \\$coordinates of static method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Coordinate\\:\\:indexesFromString\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#1 \\$errorCode of static method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\ErrorCode\\:\\:error\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#1 \\$im of function imagecolorat expects resource, GdImage\\|resource given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#1 \\$string of function strlen expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#1 \\$string of function substr expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#2 \\$col of function imagecolorsforindex expects int, int\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#2 \\$data of function unpack expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#2 \\$length of function fread expects int\\<0, max\\>, int\\<0, max\\>\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#2 \\$pieces of function implode expects array, array\\\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#2 \\$subject of function preg_match expects string, string\\|null given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#2 \\$subject of function preg_match_all expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#3 \\$formula of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Worksheet\\:\\:writeFormula\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#3 \\$num of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Worksheet\\:\\:writeNumber\\(\\) expects float, mixed given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#3 \\$str of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Worksheet\\:\\:writeString\\(\\) expects string, mixed given\\.$#" + count: 3 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#3 \\$subject of function preg_replace expects array\\|string, string\\|null given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#3 \\$value of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Worksheet\\:\\:writeBoolErr\\(\\) expects int, mixed given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#4 \\$isError of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Worksheet\\:\\:writeBoolErr\\(\\) expects bool, int given\\.$#" + count: 3 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#5 \\$width of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Worksheet\\:\\:positionImage\\(\\) expects int, float given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#6 \\$height of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Worksheet\\:\\:positionImage\\(\\) expects int, float given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Worksheet\\:\\:\\$activePane \\(int\\) does not accept int\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Worksheet\\:\\:\\$colors has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Worksheet\\:\\:\\$countCellStyleXfs is never read, only written\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Worksheet\\:\\:\\$outlineBelow is never read, only written\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Worksheet\\:\\:\\$outlineRight is never read, only written\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Worksheet\\:\\:\\$selection is never read, only written\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Worksheet\\:\\:\\$xlsStringMaxLength is never read, only written\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php + + - + message: "#^Parameter \\#1 \\$textRotation of static method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Xf\\:\\:mapTextRotation\\(\\) expects int, int\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Xf.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Xf\\:\\:\\$diag is never read, only written\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xls/Xf.php + + - + message: "#^Argument of an invalid type array\\|null supplied for foreach, only iterables are supported\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx.php + + - + message: "#^Parameter \\#1 \\$function of function call_user_func expects callable\\(\\)\\: mixed, string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx.php + + - + message: "#^Parameter \\#1 \\$path of function basename expects string, array\\|string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx.php + + - + message: "#^Parameter \\#1 \\$path of function dirname expects string, array\\|string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx.php + + - + message: "#^Possibly invalid array key type array\\|string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\:\\:\\$pathNames has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx.php + + - + message: "#^Cannot access offset int on mixed\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Cannot call method getDataValues\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeriesValues\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Cannot call method getFillColor\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeriesValues\\|false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Else branch is unreachable because previous condition is always true\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Parameter \\#1 \\$plotSeriesValues of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Chart\\:\\:writeBubbles\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeriesValues|null, PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\DataSeriesValues\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Parameter \\#1 \\$rawTextData of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\XMLWriter\\:\\:writeRawData\\(\\) expects array\\\\|string\\|null, int given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Parameter \\#1 \\$rawTextData of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\XMLWriter\\:\\:writeRawData\\(\\) expects array\\\\|string\\|null, mixed given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, array\\|int\\|string given\\.$#" + count: 8 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, array\\|int\\|string\\|null given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, float given\\.$#" + count: 6 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, float\\|int given\\.$#" + count: 4 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int given\\.$#" + count: 45 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, string\\|null given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Parameter \\#3 \\$id1 of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Chart\\:\\:writeCategoryAxis\\(\\) expects string, int\\|string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Parameter \\#4 \\$id1 of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Chart\\:\\:writeValueAxis\\(\\) expects string, int\\|string given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Parameter \\#4 \\$id2 of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Chart\\:\\:writeCategoryAxis\\(\\) expects string, int\\|string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Parameter \\#5 \\$id2 of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Chart\\:\\:writeValueAxis\\(\\) expects string, int\\|string given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Parameter \\#6 \\$yAxis of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Chart\\:\\:writeCategoryAxis\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis, PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Parameter \\#7 \\$xAxis of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Chart\\:\\:writeValueAxis\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis, PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis\\|null given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Parameter \\#8 \\$majorGridlines of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Chart\\:\\:writeValueAxis\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\GridLines, PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\GridLines\\|null given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Parameter \\#9 \\$minorGridlines of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Chart\\:\\:writeValueAxis\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\GridLines, PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\GridLines\\|null given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Part \\$xAxis\\-\\>getShadowProperty\\('effect'\\) \\(array\\|int\\|string\\|null\\) of encapsed string cannot be cast to string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Part \\$xAxis\\-\\>getShadowProperty\\(\\['color', 'type'\\]\\) \\(array\\|int\\|string\\|null\\) of encapsed string cannot be cast to string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Chart\\:\\:\\$calculateCellValues has no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Strict comparison using \\=\\=\\= between PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\PlotArea and null will always evaluate to false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php + + - + message: "#^Parameter \\#1 \\$string of function substr expects string, int given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Comments.php + + - + message: "#^Parameter \\#2 \\$content of method XMLWriter\\:\\:writeElement\\(\\) expects string\\|null, int given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xlsx/Comments.php + + - + message: "#^Cannot use array destructuring on mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/DefinedNames.php + + - + message: "#^Expression on left side of \\?\\? is not nullable\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/DefinedNames.php + + - + message: "#^Parameter \\#2 \\$content of method XMLWriter\\:\\:writeElement\\(\\) expects string\\|null, int given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/DocProps.php + + - + message: "#^Parameter \\#2 \\$content of method XMLWriter\\:\\:writeElement\\(\\) expects string\\|null, mixed given\\.$#" + count: 3 + path: src/PhpSpreadsheet/Writer/Xlsx/DocProps.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xlsx/DocProps.php + + - + message: "#^Part \\$propertyValue \\(mixed\\) of encapsed string cannot be cast to string\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/DocProps.php + + - + message: "#^Parameter \\#1 \\$index of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\:\\:getChartByIndex\\(\\) expects string, int given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Drawing.php + + - + message: "#^Parameter \\#2 \\$chart of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Drawing\\:\\:writeChart\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart, PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\|false given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Drawing.php + + - + message: "#^Parameter \\#2 \\$content of method XMLWriter\\:\\:writeElement\\(\\) expects string\\|null, int given\\.$#" + count: 12 + path: src/PhpSpreadsheet/Writer/Xlsx/Drawing.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int given\\.$#" + count: 8 + path: src/PhpSpreadsheet/Writer/Xlsx/Drawing.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int\\<0, max\\> given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Drawing.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Rels\\:\\:writeUnparsedRelationship\\(\\) has parameter \\$relationship with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Rels.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Rels\\:\\:writeUnparsedRelationship\\(\\) has parameter \\$type with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Rels.php + + - + message: "#^Parameter \\#2 \\$id of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Rels\\:\\:writeRelationship\\(\\) expects int, string given\\.$#" + count: 4 + path: src/PhpSpreadsheet/Writer/Xlsx/Rels.php + + - + message: "#^Parameter \\#4 \\$target of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Rels\\:\\:writeRelationship\\(\\) expects string, array\\|string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Rels.php + + - + message: "#^Cannot call method getBold\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xlsx/StringTable.php + + - + message: "#^Cannot call method getColor\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/StringTable.php + + - + message: "#^Cannot call method getItalic\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xlsx/StringTable.php + + - + message: "#^Cannot call method getName\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xlsx/StringTable.php + + - + message: "#^Cannot call method getSize\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/StringTable.php + + - + message: "#^Cannot call method getStrikethrough\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xlsx/StringTable.php + + - + message: "#^Cannot call method getSubscript\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xlsx/StringTable.php + + - + message: "#^Cannot call method getSuperscript\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xlsx/StringTable.php + + - + message: "#^Cannot call method getUnderline\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xlsx/StringTable.php + + - + message: "#^Instanceof between \\*NEVER\\* and PhpOffice\\\\PhpSpreadsheet\\\\RichText\\\\RichText will always evaluate to false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/StringTable.php + + - + message: "#^Instanceof between string and PhpOffice\\\\PhpSpreadsheet\\\\RichText\\\\RichText will always evaluate to false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/StringTable.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\StringTable\\:\\:createStringTable\\(\\) should return array\\ but returns array\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/StringTable.php + + - + message: "#^Parameter \\#1 \\$text of method PhpOffice\\\\PhpSpreadsheet\\\\RichText\\\\RichText\\:\\:createTextRun\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/StringTable.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, float\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/StringTable.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xlsx/StringTable.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int\\<0, max\\> given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/StringTable.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, string\\|null given\\.$#" + count: 5 + path: src/PhpSpreadsheet/Writer/Xlsx/StringTable.php + + - + message: "#^Cannot call method getStyle\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Conditional\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Style.php + + - + message: "#^Comparison operation \"\\<\" between int\\ and 0 is always true\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xlsx/Style.php + + - + message: "#^Parameter \\#2 \\$borders of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Style\\:\\:writeBorder\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Borders, PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Borders\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Style.php + + - + message: "#^Parameter \\#2 \\$fill of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Style\\:\\:writeFill\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Fill, PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Fill\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Style.php + + - + message: "#^Parameter \\#2 \\$font of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Style\\:\\:writeFont\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font, PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Style.php + + - + message: "#^Parameter \\#2 \\$numberFormat of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Style\\:\\:writeNumFmt\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat, PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Style.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, float given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Style.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int given\\.$#" + count: 22 + path: src/PhpSpreadsheet/Writer/Xlsx/Style.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int\\<0, max\\> given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Style.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int\\<1, max\\> given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xlsx/Style.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Style.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, string\\|null given\\.$#" + count: 7 + path: src/PhpSpreadsheet/Writer/Xlsx/Style.php + + - + message: "#^Result of \\|\\| is always true\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Style.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int given\\.$#" + count: 7 + path: src/PhpSpreadsheet/Writer/Xlsx/Workbook.php + + - + message: "#^Cannot cast mixed to string\\.$#" + count: 4 + path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php + + - + message: "#^Cannot use array destructuring on mixed\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php + + - + message: "#^Expression on left side of \\?\\? is not nullable\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php + + - + message: "#^If condition is always true\\.$#" + count: 6 + path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Worksheet\\:\\:writeAttributeIf\\(\\) has parameter \\$condition with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Worksheet\\:\\:writeDataBarElements\\(\\) has parameter \\$dataBar with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Worksheet\\:\\:writeElementIf\\(\\) has parameter \\$condition with no type specified\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php + + - + message: "#^Negated boolean expression is always false\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php + + - + message: "#^Offset int on array\\\\> in isset\\(\\) does not exist\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php + + - + message: "#^Parameter \\#1 \\$content of method XMLWriter\\:\\:text\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php + + - + message: "#^Parameter \\#1 \\$string of function substr expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php + + - + message: "#^Parameter \\#2 \\$cellValue of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Worksheet\\:\\:writeCellFormula\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php + + - + message: "#^Parameter \\#2 \\$cellValue of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Worksheet\\:\\:writeCellNumeric\\(\\) expects float\\|int, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php + + - + message: "#^Parameter \\#2 \\$content of method XMLWriter\\:\\:writeElement\\(\\) expects string\\|null, int\\|string given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php + + - + message: "#^Parameter \\#2 \\$content of method XMLWriter\\:\\:writeElement\\(\\) expects string\\|null, mixed given\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int given\\.$#" + count: 19 + path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int\\<0, max\\> given\\.$#" + count: 3 + path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int\\<1, max\\> given\\.$#" + count: 7 + path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, mixed given\\.$#" + count: 3 + path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php + + - + message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, string\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php + + - + message: "#^Parameter \\#3 \\$cellValue of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Worksheet\\:\\:writeCellBoolean\\(\\) expects bool, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php + + - + message: "#^Parameter \\#3 \\$cellValue of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Worksheet\\:\\:writeCellError\\(\\) expects string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php + + - + message: "#^Parameter \\#3 \\$cellValue of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Worksheet\\:\\:writeCellInlineStr\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\RichText\\\\RichText\\|string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php + + - + message: "#^Parameter \\#3 \\$cellValue of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Worksheet\\:\\:writeCellString\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\RichText\\\\RichText\\|string, mixed given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php + + - + message: "#^Parameter \\#3 \\$namespace of method XMLWriter\\:\\:startElementNs\\(\\) expects string, null given\\.$#" + count: 8 + path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php + + - + message: "#^Parameter \\#3 \\$stringTable of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Worksheet\\:\\:writeSheetData\\(\\) expects array\\, array\\\\|null given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php + + - + message: "#^Parameter \\#4 \\$val of static method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Worksheet\\:\\:writeAttributeIf\\(\\) expects string, int given\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php + + - + message: "#^Strict comparison using \\=\\=\\= between false and true will always evaluate to false\\.$#" + count: 2 + path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Xlfn\\:\\:addXlfn\\(\\) should return string but returns string\\|null\\.$#" + count: 1 + path: src/PhpSpreadsheet/Writer/Xlsx/Xlfn.php + + - + message: "#^Parameter \\#1 \\$formula of method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:_calculateFormulaValue\\(\\) expects string, mixed given\\.$#" + count: 2 + path: tests/PhpSpreadsheetTests/Calculation/CalculationTest.php + + - + message: "#^Parameter \\#1 \\$source of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\:\\:fromArray\\(\\) expects array, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/CalculationTest.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Engine/RangeTest.php + + - + message: "#^Parameter \\#1 \\$database of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DAVERAGE\\(\\) expects array, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Database/DAverageTest.php + + - + message: "#^Parameter \\#2 \\$field of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DAVERAGE\\(\\) expects int\\|string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Database/DAverageTest.php + + - + message: "#^Parameter \\#3 \\$criteria of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DAVERAGE\\(\\) expects array, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Database/DAverageTest.php + + - + message: "#^Parameter \\#1 \\$database of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DCOUNTA\\(\\) expects array, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Database/DCountATest.php + + - + message: "#^Parameter \\#2 \\$field of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DCOUNTA\\(\\) expects int\\|string\\|null, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Database/DCountATest.php + + - + message: "#^Parameter \\#3 \\$criteria of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DCOUNTA\\(\\) expects array, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Database/DCountATest.php + + - + message: "#^Parameter \\#1 \\$database of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DCOUNT\\(\\) expects array, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Database/DCountTest.php + + - + message: "#^Parameter \\#2 \\$field of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DCOUNT\\(\\) expects int\\|string\\|null, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Database/DCountTest.php + + - + message: "#^Parameter \\#3 \\$criteria of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DCOUNT\\(\\) expects array, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Database/DCountTest.php + + - + message: "#^Parameter \\#1 \\$database of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DGET\\(\\) expects array, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Database/DGetTest.php + + - + message: "#^Parameter \\#2 \\$field of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DGET\\(\\) expects int\\|string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Database/DGetTest.php + + - + message: "#^Parameter \\#3 \\$criteria of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DGET\\(\\) expects array, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Database/DGetTest.php + + - + message: "#^Parameter \\#1 \\$database of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DMAX\\(\\) expects array, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Database/DMaxTest.php + + - + message: "#^Parameter \\#2 \\$field of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DMAX\\(\\) expects int\\|string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Database/DMaxTest.php + + - + message: "#^Parameter \\#3 \\$criteria of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DMAX\\(\\) expects array, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Database/DMaxTest.php + + - + message: "#^Parameter \\#1 \\$database of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DMIN\\(\\) expects array, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Database/DMinTest.php + + - + message: "#^Parameter \\#2 \\$field of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DMIN\\(\\) expects int\\|string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Database/DMinTest.php + + - + message: "#^Parameter \\#3 \\$criteria of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DMIN\\(\\) expects array, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Database/DMinTest.php + + - + message: "#^Parameter \\#1 \\$database of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DPRODUCT\\(\\) expects array, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Database/DProductTest.php + + - + message: "#^Parameter \\#2 \\$field of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DPRODUCT\\(\\) expects int\\|string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Database/DProductTest.php + + - + message: "#^Parameter \\#3 \\$criteria of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DPRODUCT\\(\\) expects array, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Database/DProductTest.php + + - + message: "#^Parameter \\#1 \\$database of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DSTDEVP\\(\\) expects array, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Database/DStDevPTest.php + + - + message: "#^Parameter \\#2 \\$field of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DSTDEVP\\(\\) expects int\\|string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Database/DStDevPTest.php + + - + message: "#^Parameter \\#3 \\$criteria of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DSTDEVP\\(\\) expects array, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Database/DStDevPTest.php + + - + message: "#^Parameter \\#1 \\$database of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DSTDEV\\(\\) expects array, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Database/DStDevTest.php + + - + message: "#^Parameter \\#2 \\$field of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DSTDEV\\(\\) expects int\\|string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Database/DStDevTest.php + + - + message: "#^Parameter \\#3 \\$criteria of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DSTDEV\\(\\) expects array, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Database/DStDevTest.php + + - + message: "#^Parameter \\#1 \\$database of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DSUM\\(\\) expects array, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Database/DSumTest.php + + - + message: "#^Parameter \\#2 \\$field of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DSUM\\(\\) expects int\\|string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Database/DSumTest.php + + - + message: "#^Parameter \\#3 \\$criteria of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DSUM\\(\\) expects array, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Database/DSumTest.php + + - + message: "#^Parameter \\#1 \\$database of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DVARP\\(\\) expects array, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Database/DVarPTest.php + + - + message: "#^Parameter \\#2 \\$field of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DVARP\\(\\) expects int\\|string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Database/DVarPTest.php + + - + message: "#^Parameter \\#3 \\$criteria of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DVARP\\(\\) expects array, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Database/DVarPTest.php + + - + message: "#^Parameter \\#1 \\$database of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DVAR\\(\\) expects array, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Database/DVarTest.php + + - + message: "#^Parameter \\#2 \\$field of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DVAR\\(\\) expects int\\|string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Database/DVarTest.php + + - + message: "#^Parameter \\#3 \\$criteria of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Database\\:\\:DVAR\\(\\) expects array, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Database/DVarTest.php + + - + message: "#^Part \\$timeValue \\(mixed\\) of encapsed string cannot be cast to string\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/TimeValueTest.php + + - + message: "#^Part \\$formula \\(mixed\\) of encapsed string cannot be cast to string\\.$#" + count: 2 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Bin2DecTest.php + + - + message: "#^Part \\$formula \\(mixed\\) of encapsed string cannot be cast to string\\.$#" + count: 2 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Bin2HexTest.php + + - + message: "#^Part \\$formula \\(mixed\\) of encapsed string cannot be cast to string\\.$#" + count: 2 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Bin2OctTest.php + + - + message: "#^Part \\$formula \\(mixed\\) of encapsed string cannot be cast to string\\.$#" + count: 2 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Dec2BinTest.php + + - + message: "#^Part \\$formula \\(mixed\\) of encapsed string cannot be cast to string\\.$#" + count: 2 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Dec2HexTest.php + + - + message: "#^Part \\$formula \\(mixed\\) of encapsed string cannot be cast to string\\.$#" + count: 2 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Dec2OctTest.php + + - + message: "#^Part \\$formula \\(mixed\\) of encapsed string cannot be cast to string\\.$#" + count: 2 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Hex2BinTest.php + + - + message: "#^Part \\$formula \\(mixed\\) of encapsed string cannot be cast to string\\.$#" + count: 2 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Hex2DecTest.php + + - + message: "#^Part \\$formula \\(mixed\\) of encapsed string cannot be cast to string\\.$#" + count: 2 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Hex2OctTest.php + + - + message: "#^Parameter \\#1 \\$complexNumber of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\:\\:IMABS\\(\\) expects string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImAbsTest.php + + - + message: "#^Parameter \\#1 \\$complexNumber of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\:\\:IMARGUMENT\\(\\) expects string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImArgumentTest.php + + - + message: "#^Parameter \\#1 \\$complexNumber of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\:\\:IMCONJUGATE\\(\\) expects string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImConjugateTest.php + + - + message: "#^Parameter \\#1 \\$complexNumber of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\:\\:IMCOS\\(\\) expects string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImCosTest.php + + - + message: "#^Parameter \\#1 \\$complexNumber of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\:\\:IMCOSH\\(\\) expects string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImCoshTest.php + + - + message: "#^Parameter \\#1 \\$complexNumber of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\:\\:IMCOT\\(\\) expects string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImCotTest.php + + - + message: "#^Parameter \\#1 \\$complexNumber of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\:\\:IMCSC\\(\\) expects string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImCscTest.php + + - + message: "#^Parameter \\#1 \\$complexNumber of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\:\\:IMCSCH\\(\\) expects string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImCschTest.php + + - + message: "#^Parameter \\#1 \\$complexNumber of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\:\\:IMEXP\\(\\) expects string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImExpTest.php + + - + message: "#^Parameter \\#1 \\$complexNumber of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\:\\:IMLN\\(\\) expects string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImLnTest.php + + - + message: "#^Parameter \\#1 \\$complexNumber of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\:\\:IMLOG10\\(\\) expects string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImLog10Test.php + + - + message: "#^Parameter \\#1 \\$complexNumber of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\:\\:IMLOG2\\(\\) expects string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImLog2Test.php + + - + message: "#^Parameter \\#1 \\$complexNumber of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\:\\:IMREAL\\(\\) expects string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImRealTest.php + + - + message: "#^Parameter \\#1 \\$complexNumber of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\:\\:IMSEC\\(\\) expects string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImSecTest.php + + - + message: "#^Parameter \\#1 \\$complexNumber of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\:\\:IMSECH\\(\\) expects string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImSechTest.php + + - + message: "#^Parameter \\#1 \\$complexNumber of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\:\\:IMSIN\\(\\) expects string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImSinTest.php + + - + message: "#^Parameter \\#1 \\$complexNumber of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\:\\:IMSINH\\(\\) expects string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImSinhTest.php + + - + message: "#^Parameter \\#1 \\$complexNumber of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\:\\:IMSQRT\\(\\) expects string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImSqrtTest.php + + - + message: "#^Parameter \\#1 \\$complexNumber of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\:\\:IMTAN\\(\\) expects string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImTanTest.php + + - + message: "#^Parameter \\#1 \\$complexNumber of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engineering\\:\\:IMAGINARY\\(\\) expects string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/ImaginaryTest.php + + - + message: "#^Part \\$formula \\(mixed\\) of encapsed string cannot be cast to string\\.$#" + count: 2 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Oct2BinTest.php + + - + message: "#^Part \\$formula \\(mixed\\) of encapsed string cannot be cast to string\\.$#" + count: 2 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Oct2DecTest.php + + - + message: "#^Part \\$formula \\(mixed\\) of encapsed string cannot be cast to string\\.$#" + count: 2 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Engineering/Oct2HexTest.php + + - + message: "#^Parameter \\#1 \\$nominalRate of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\:\\:EFFECT\\(\\) expects float, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Financial/EffectTest.php + + - + message: "#^Parameter \\#2 \\$periodsPerYear of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\:\\:EFFECT\\(\\) expects int, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Financial/EffectTest.php + + - + message: "#^Parameter \\#1 \\$year of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\Helpers\\:\\:daysPerYear\\(\\) expects int\\|string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Financial/HelpersTest.php + + - + message: "#^Parameter \\#2 \\$basis of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\\\Helpers\\:\\:daysPerYear\\(\\) expects int\\|string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Financial/HelpersTest.php + + - + message: "#^Parameter \\#1 \\$effectiveRate of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\:\\:NOMINAL\\(\\) expects float, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Financial/NominalTest.php + + - + message: "#^Parameter \\#2 \\$periodsPerYear of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Financial\\:\\:NOMINAL\\(\\) expects int, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Financial/NominalTest.php + + - + message: "#^Parameter \\#3 \\$message of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertEquals\\(\\) expects string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Financial/XNpvTest.php + + - + message: "#^Parameter \\#3 \\$message of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertEquals\\(\\) expects string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Financial/XirrTest.php + + - + message: "#^Parameter \\#1 \\$cellAddress of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\:\\:COLUMN\\(\\) expects array\\|string\\|null, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/ColumnTest.php + + - + message: "#^Parameter \\#1 \\$cellAddress of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\:\\:OFFSET\\(\\) expects string\\|null, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/OffsetTest.php + + - + message: "#^Parameter \\#1 \\$cellAddress of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\:\\:ROW\\(\\) expects array\\|string\\|null, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/RowTest.php + + - + message: "#^Parameter \\#1 \\$matrixData of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\:\\:TRANSPOSE\\(\\) expects array, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/TransposeTest.php + + - + message: "#^Part \\$number \\(mixed\\) of encapsed string cannot be cast to string\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/AcotTest.php + + - + message: "#^Part \\$number \\(mixed\\) of encapsed string cannot be cast to string\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/AcothTest.php + + - + message: "#^Part \\$angle \\(mixed\\) of encapsed string cannot be cast to string\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CotTest.php + + - + message: "#^Part \\$angle \\(mixed\\) of encapsed string cannot be cast to string\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CothTest.php + + - + message: "#^Part \\$angle \\(mixed\\) of encapsed string cannot be cast to string\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CscTest.php + + - + message: "#^Part \\$angle \\(mixed\\) of encapsed string cannot be cast to string\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/CschTest.php + + - + message: "#^Part \\$value \\(mixed\\) of encapsed string cannot be cast to string\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/EvenTest.php + + - + message: "#^Part \\$formula \\(mixed\\) of encapsed string cannot be cast to string\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/MRoundTest.php + + - + message: "#^Part \\$matrix \\(mixed\\) of encapsed string cannot be cast to string\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/MdeTermTest.php + + - + message: "#^Part \\$value \\(mixed\\) of encapsed string cannot be cast to string\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/OddTest.php + + - + message: "#^Cannot cast mixed to int\\.$#" + count: 2 + path: tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/RandBetweenTest.php + + - + message: "#^Part \\$formula \\(mixed\\) of encapsed string cannot be cast to string\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/RomanTest.php + + - + message: "#^Part \\$formula \\(mixed\\) of encapsed string cannot be cast to string\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/RoundDownTest.php + + - + message: "#^Part \\$formula \\(mixed\\) of encapsed string cannot be cast to string\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/RoundTest.php + + - + message: "#^Part \\$formula \\(mixed\\) of encapsed string cannot be cast to string\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/RoundUpTest.php + + - + message: "#^Part \\$angle \\(mixed\\) of encapsed string cannot be cast to string\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SecTest.php + + - + message: "#^Part \\$angle \\(mixed\\) of encapsed string cannot be cast to string\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SechTest.php + + - + message: "#^Part \\$value \\(mixed\\) of encapsed string cannot be cast to string\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SignTest.php + + - + message: "#^Part \\$type \\(mixed\\) of encapsed string cannot be cast to string\\.$#" + count: 3 + path: tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SubTotalTest.php + + - + message: "#^Parameter \\#1 \\$probability of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\:\\:CHIINV\\(\\) expects float, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/ChiInvRightTailTest.php + + - + message: "#^Parameter \\#2 \\$degrees of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\:\\:CHIINV\\(\\) expects float, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/ChiInvRightTailTest.php + + - + message: "#^Parameter \\#1 \\$value of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\:\\:FISHERINV\\(\\) expects float, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/FisherInvTest.php + + - + message: "#^Parameter \\#1 \\$value of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\:\\:FISHER\\(\\) expects float, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/FisherTest.php + + - + message: "#^Parameter \\#1 \\$value of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\:\\:GAMMALN\\(\\) expects float, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/GammaLnTest.php + + - + message: "#^Parameter \\#1 \\$value of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\:\\:GAMMAFunction\\(\\) expects float, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/GammaTest.php + + - + message: "#^Parameter \\#1 \\$value of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\:\\:GAUSS\\(\\) expects float, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/GaussTest.php + + - + message: "#^Parameter \\#1 \\$yValues of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\:\\:GROWTH\\(\\) expects array, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/GrowthTest.php + + - + message: "#^Parameter \\#1 \\$yValues of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\:\\:LINEST\\(\\) expects array, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/LinEstTest.php + + - + message: "#^Parameter \\#2 \\$xValues of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\:\\:LINEST\\(\\) expects array\\|null, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/LinEstTest.php + + - + message: "#^Parameter \\#3 \\$const of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\:\\:LINEST\\(\\) expects bool, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/LinEstTest.php + + - + message: "#^Parameter \\#4 \\$stats of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\:\\:LINEST\\(\\) expects bool, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/LinEstTest.php + + - + message: "#^Parameter \\#1 \\$yValues of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\:\\:LOGEST\\(\\) expects array, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/LogEstTest.php + + - + message: "#^Parameter \\#2 \\$xValues of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\:\\:LOGEST\\(\\) expects array\\|null, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/LogEstTest.php + + - + message: "#^Parameter \\#3 \\$const of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\:\\:LOGEST\\(\\) expects bool, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/LogEstTest.php + + - + message: "#^Parameter \\#4 \\$stats of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\:\\:LOGEST\\(\\) expects bool, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/LogEstTest.php + + - + message: "#^Parameter \\#1 \\$value of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\:\\:TDIST\\(\\) expects float, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/TDistTest.php + + - + message: "#^Parameter \\#2 \\$degrees of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\:\\:TDIST\\(\\) expects float, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/TDistTest.php + + - + message: "#^Parameter \\#3 \\$tails of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\:\\:TDIST\\(\\) expects float, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/TDistTest.php + + - + message: "#^Parameter \\#1 \\$probability of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\:\\:TINV\\(\\) expects float, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/TinvTest.php + + - + message: "#^Parameter \\#2 \\$degrees of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\:\\:TINV\\(\\) expects float, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/TinvTest.php + + - + message: "#^Parameter \\#1 \\$yValues of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\:\\:TREND\\(\\) expects array, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/TrendTest.php + + - + message: "#^Parameter \\#1 \\$value of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\:\\:WEIBULL\\(\\) expects float, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/WeibullTest.php + + - + message: "#^Parameter \\#2 \\$alpha of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\:\\:WEIBULL\\(\\) expects float, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/WeibullTest.php + + - + message: "#^Parameter \\#3 \\$beta of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\:\\:WEIBULL\\(\\) expects float, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/WeibullTest.php + + - + message: "#^Parameter \\#4 \\$cumulative of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\:\\:WEIBULL\\(\\) expects bool, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/WeibullTest.php + + - + message: "#^Parameter \\#1 \\$dataSet of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\:\\:ZTEST\\(\\) expects float, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/ZTestTest.php + + - + message: "#^Parameter \\#2 \\$m0 of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\:\\:ZTEST\\(\\) expects float, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/ZTestTest.php + + - + message: "#^Parameter \\#3 \\$sigma of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\:\\:ZTEST\\(\\) expects float\\|null, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/ZTestTest.php + + - + message: "#^Parameter \\#1 \\$locale of static method PhpOffice\\\\PhpSpreadsheet\\\\Settings\\:\\:setLocale\\(\\) expects string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/TextData/LeftTest.php + + - + message: "#^Parameter \\#1 \\$locale of static method PhpOffice\\\\PhpSpreadsheet\\\\Settings\\:\\:setLocale\\(\\) expects string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/TextData/LowerTest.php + + - + message: "#^Parameter \\#1 \\$locale of static method PhpOffice\\\\PhpSpreadsheet\\\\Settings\\:\\:setLocale\\(\\) expects string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/TextData/MidTest.php + + - + message: "#^Parameter \\#1 \\$locale of static method PhpOffice\\\\PhpSpreadsheet\\\\Settings\\:\\:setLocale\\(\\) expects string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ProperTest.php + + - + message: "#^Parameter \\#1 \\$locale of static method PhpOffice\\\\PhpSpreadsheet\\\\Settings\\:\\:setLocale\\(\\) expects string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/TextData/RightTest.php + + - + message: "#^Parameter \\#1 \\$locale of static method PhpOffice\\\\PhpSpreadsheet\\\\Settings\\:\\:setLocale\\(\\) expects string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/Functions/TextData/UpperTest.php + + - + message: "#^Parameter \\#1 \\$string of function substr expects string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Calculation/LookupRefTest.php + + - + message: "#^Parameter \\#1 \\$currencyCode of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\StringHelper\\:\\:setCurrencyCode\\(\\) expects string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Cell/AdvancedValueBinderTest.php + + - + message: "#^Parameter \\#1 \\$separator of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\StringHelper\\:\\:setDecimalSeparator\\(\\) expects string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Cell/AdvancedValueBinderTest.php + + - + message: "#^Parameter \\#1 \\$separator of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\StringHelper\\:\\:setThousandsSeparator\\(\\) expects string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Cell/AdvancedValueBinderTest.php + + - + message: "#^Cannot access offset \\(int\\|string\\) on mixed\\.$#" + count: 3 + path: tests/PhpSpreadsheetTests/Cell/CoordinateTest.php + + - + message: "#^Parameter \\#1 \\$columnAddress of static method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Coordinate\\:\\:columnIndexFromString\\(\\) expects string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Cell/CoordinateTest.php + + - + message: "#^Parameter \\#1 \\$coordinateCollection of static method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Coordinate\\:\\:mergeRangesInCollection\\(\\) expects array, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Cell/CoordinateTest.php + + - + message: "#^Parameter \\#1 \\$range of static method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Coordinate\\:\\:buildRange\\(\\) expects array, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Cell/CoordinateTest.php + + - + message: "#^Call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertSame\\(\\) with arguments PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell, null and 'should get exact…' will always evaluate to false\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Collection/CellsTest.php + + - + message: "#^Cannot call method getCoordinate\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell\\|null\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Collection/CellsTest.php + + - + message: "#^Cannot call method getParent\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell\\|null\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Collection/CellsTest.php + + - + message: "#^Parameter \\#1 \\$propertyName of method PhpOffice\\\\PhpSpreadsheet\\\\Document\\\\Properties\\:\\:getCustomPropertyType\\(\\) expects string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Document/PropertiesTest.php + + - + message: "#^Parameter \\#1 \\$propertyName of method PhpOffice\\\\PhpSpreadsheet\\\\Document\\\\Properties\\:\\:getCustomPropertyValue\\(\\) expects string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Document/PropertiesTest.php + + - + message: "#^Parameter \\#1 \\$propertyName of method PhpOffice\\\\PhpSpreadsheet\\\\Document\\\\Properties\\:\\:isCustomPropertySet\\(\\) expects string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Document/PropertiesTest.php + + - + message: "#^Parameter \\#1 \\$propertyName of method PhpOffice\\\\PhpSpreadsheet\\\\Document\\\\Properties\\:\\:setCustomProperty\\(\\) expects string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Document/PropertiesTest.php + + - + message: "#^Parameter \\#1 \\$timestamp of method PhpOffice\\\\PhpSpreadsheet\\\\Document\\\\Properties\\:\\:setCreated\\(\\) expects float\\|int\\|string\\|null, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Document/PropertiesTest.php + + - + message: "#^Parameter \\#1 \\$timestamp of method PhpOffice\\\\PhpSpreadsheet\\\\Document\\\\Properties\\:\\:setModified\\(\\) expects float\\|int\\|string\\|null, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Document/PropertiesTest.php + + - + message: "#^Part \\$result \\(mixed\\) of encapsed string cannot be cast to string\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Document/PropertiesTest.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheetTests\\\\Functional\\\\ColumnWidthTest\\:\\:testReadColumnWidth\\(\\) has parameter \\$format with no type specified\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Functional/ColumnWidthTest.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheetTests\\\\Functional\\\\CommentsTest\\:\\:testComments\\(\\) has parameter \\$format with no type specified\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Functional/CommentsTest.php + + - + message: "#^Parameter \\#1 \\$condition of method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Conditional\\:\\:addCondition\\(\\) expects string, float given\\.$#" + count: 2 + path: tests/PhpSpreadsheetTests/Functional/ConditionalStopIfTrueTest.php + + - + message: "#^Cannot call method getUrl\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Hyperlink\\|null\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Functional/DrawingImageHyperlinkTest.php + + - + message: "#^Parameter \\#1 \\$im of function imagecolorallocate expects resource, resource\\|false given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Functional/DrawingImageHyperlinkTest.php + + - + message: "#^Parameter \\#1 \\$im of function imagestring expects resource, resource\\|false given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Functional/DrawingImageHyperlinkTest.php + + - + message: "#^Parameter \\#1 \\$value of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\MemoryDrawing\\:\\:setImageResource\\(\\) expects GdImage\\|resource, resource\\|false given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Functional/DrawingImageHyperlinkTest.php + + - + message: "#^Parameter \\#6 \\$col of function imagestring expects int, int\\|false given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Functional/DrawingImageHyperlinkTest.php + + - + message: "#^Cannot call method getPageSetup\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 5 + path: tests/PhpSpreadsheetTests/Functional/PrintAreaTest.php + + - + message: "#^Parameter \\#2 \\$format of method PhpOffice\\\\PhpSpreadsheetTests\\\\Functional\\\\AbstractFunctional\\:\\:writeAndReload\\(\\) expects string, mixed given\\.$#" + count: 2 + path: tests/PhpSpreadsheetTests/Functional/ReadBlankCellsTest.php + + - + message: "#^Parameter \\#2 \\$format of method PhpOffice\\\\PhpSpreadsheetTests\\\\Functional\\\\AbstractFunctional\\:\\:writeAndReload\\(\\) expects string, mixed given\\.$#" + count: 2 + path: tests/PhpSpreadsheetTests/Functional/ReadFilterTest.php + + - + message: "#^Cannot access offset 'size' on array\\{0\\: int, 1\\: int, 2\\: int, 3\\: int, 4\\: int, 5\\: int, 6\\: int, 7\\: int, \\.\\.\\.\\}\\|false\\.$#" + count: 2 + path: tests/PhpSpreadsheetTests/Functional/StreamTest.php + + - + message: "#^Parameter \\#1 \\$filename of method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\IWriter\\:\\:save\\(\\) expects resource\\|string, resource\\|false given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Functional/StreamTest.php + + - + message: "#^Parameter \\#1 \\$fp of function fstat expects resource, resource\\|false given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Functional/StreamTest.php + + - + message: "#^Parameter \\#1 \\$html of method PhpOffice\\\\PhpSpreadsheet\\\\Helper\\\\Html\\:\\:toRichTextObject\\(\\) expects string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Helper/HtmlTest.php + + - + message: "#^Parameter \\#1 \\$expected of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertInstanceOf\\(\\) expects class\\-string\\, string given\\.$#" + count: 3 + path: tests/PhpSpreadsheetTests/IOFactoryTest.php + + - + message: "#^Parameter \\#3 \\$phpSpreadsheetFunctions of class PhpOffice\\\\PhpSpreadsheetInfra\\\\LocaleGenerator constructor expects array, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/LocaleGeneratorTest.php + + - + message: "#^Cannot call method getValue\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\NamedFormula\\|null\\.$#" + count: 5 + path: tests/PhpSpreadsheetTests/NamedFormulaTest.php + + - + message: "#^Cannot call method getValue\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\NamedRange\\|null\\.$#" + count: 5 + path: tests/PhpSpreadsheetTests/NamedRangeTest.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheetTests\\\\Reader\\\\Csv\\\\CsvContiguousFilter\\:\\:\\$startRow \\(int\\) does not accept mixed\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/Csv/CsvContiguousFilter.php + + - + message: "#^Cannot cast mixed to string\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/Csv/CsvContiguousTest.php + + - + message: "#^Part \\$result \\(mixed\\) of encapsed string cannot be cast to string\\.$#" + count: 2 + path: tests/PhpSpreadsheetTests/Reader/Ods/OdsPropertiesTest.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheetTests\\\\Reader\\\\Ods\\\\OdsTest\\:\\:\\$spreadsheetData \\(PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\) in isset\\(\\) is not nullable\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/Ods/OdsTest.php + + - + message: "#^Property PhpOffice\\\\PhpSpreadsheetTests\\\\Reader\\\\Ods\\\\OdsTest\\:\\:\\$spreadsheetOdsTest \\(PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\) in isset\\(\\) is not nullable\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/Ods/OdsTest.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/Ods/OdsTest.php + + - + message: "#^Argument of an invalid type array\\\\|false supplied for foreach, only iterables are supported\\.$#" + count: 3 + path: tests/PhpSpreadsheetTests/Reader/Security/XmlScannerTest.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheetTests\\\\Reader\\\\Security\\\\XmlScannerTest\\:\\:testInvalidXML\\(\\) has parameter \\$libxmlDisableEntityLoader with no type specified\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/Security/XmlScannerTest.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheetTests\\\\Reader\\\\Security\\\\XmlScannerTest\\:\\:testValidXML\\(\\) has parameter \\$libxmlDisableEntityLoader with no type specified\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/Security/XmlScannerTest.php + + - + message: "#^Parameter \\#1 \\$str of function strrev expects string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/Security/XmlScannerTest.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheetTests\\\\Reader\\\\Xlsx\\\\AutoFilterTest\\:\\:getAutoFilterInstance\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/Xlsx/AutoFilterTest.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheetTests\\\\Reader\\\\Xlsx\\\\AutoFilterTest\\:\\:getWorksheetInstance\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/Xlsx/AutoFilterTest.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheetTests\\\\Reader\\\\Xlsx\\\\AutoFilterTest\\:\\:getXMLInstance\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/Xlsx/AutoFilterTest.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheetTests\\\\Reader\\\\Xlsx\\\\AutoFilterTest\\:\\:getXMLInstance\\(\\) has parameter \\$ref with no type specified\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/Xlsx/AutoFilterTest.php + + - + message: "#^Cannot call method getTitle\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\|null\\.$#" + count: 5 + path: tests/PhpSpreadsheetTests/Reader/Xlsx/ChartsTitleTest.php + + - + message: "#^Cannot call method getXAxisLabel\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\|null\\.$#" + count: 5 + path: tests/PhpSpreadsheetTests/Reader/Xlsx/ChartsTitleTest.php + + - + message: "#^Cannot call method getYAxisLabel\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\|null\\.$#" + count: 5 + path: tests/PhpSpreadsheetTests/Reader/Xlsx/ChartsTitleTest.php + + - + message: "#^Cannot call method getColor\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalDataBar\\|null\\.$#" + count: 5 + path: tests/PhpSpreadsheetTests/Reader/Xlsx/ConditionalFormattingDataBarXlsxTest.php + + - + message: "#^Cannot call method getConditionalFormattingRuleExt\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalDataBar\\|null\\.$#" + count: 8 + path: tests/PhpSpreadsheetTests/Reader/Xlsx/ConditionalFormattingDataBarXlsxTest.php + + - + message: "#^Cannot call method getMaximumConditionalFormatValueObject\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalDataBar\\|null\\.$#" + count: 13 + path: tests/PhpSpreadsheetTests/Reader/Xlsx/ConditionalFormattingDataBarXlsxTest.php + + - + message: "#^Cannot call method getMinimumConditionalFormatValueObject\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalDataBar\\|null\\.$#" + count: 13 + path: tests/PhpSpreadsheetTests/Reader/Xlsx/ConditionalFormattingDataBarXlsxTest.php + + - + message: "#^Cannot call method getShowValue\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalDataBar\\|null\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/Xlsx/ConditionalFormattingDataBarXlsxTest.php + + - + message: "#^Cannot call method setMinimumConditionalFormatValueObject\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\ConditionalFormatting\\\\ConditionalDataBar\\|null\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/Xlsx/ConditionalFormattingDataBarXlsxTest.php + + - + message: "#^Cannot cast mixed to string\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/Xlsx/Namespace.Issue2109bTest.php + + - + message: "#^Cannot cast mixed to string\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/Xlsx/Namespace.Openpyxl35Test.php + + - + message: "#^Part \\$result \\(mixed\\) of encapsed string cannot be cast to string\\.$#" + count: 2 + path: tests/PhpSpreadsheetTests/Reader/Xlsx/PropertiesTest.php + + - + message: "#^Cannot call method getPlotArea\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Chart\\|null\\.$#" + count: 2 + path: tests/PhpSpreadsheetTests/Reader/Xlsx/SheetsXlsxChartTest.php + + - + message: "#^Part \\$creationDate \\(mixed\\) of encapsed string cannot be cast to string\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/Xml/XmlLoadTest.php + + - + message: "#^Argument of an invalid type array\\\\|false supplied for foreach, only iterables are supported\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/Xml/XmlTest.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheetTests\\\\Reader\\\\Xml\\\\XmlTest\\:\\:testInvalidSimpleXML\\(\\) has parameter \\$filename with no type specified\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Reader/Xml/XmlTest.php + + - + message: "#^Parameter \\#1 \\$codePage of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\CodePage\\:\\:numberToName\\(\\) expects int, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Shared/CodePageTest.php + + - + message: "#^Parameter \\#1 \\$dateValue of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Date\\:\\:dateTimeToExcel\\(\\) expects DateTimeInterface, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Shared/DateTest.php + + - + message: "#^Parameter \\#1 \\$excelTimestamp of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Date\\:\\:excelToTimestamp\\(\\) expects float\\|int, mixed given\\.$#" + count: 3 + path: tests/PhpSpreadsheetTests/Shared/DateTest.php + + - + message: "#^Parameter \\#1 \\$unixTimestamp of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Date\\:\\:timestampToExcel\\(\\) expects int, mixed given\\.$#" + count: 3 + path: tests/PhpSpreadsheetTests/Shared/DateTest.php + + - + message: "#^Parameter \\#2 \\$timeZone of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Date\\:\\:excelToTimestamp\\(\\) expects DateTimeZone\\|string\\|null, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Shared/DateTest.php + + - + message: "#^Parameter \\#1 \\$fontSizeInPoints of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Font\\:\\:fontSizeToPixels\\(\\) expects int, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Shared/FontTest.php + + - + message: "#^Parameter \\#1 \\$sizeInCm of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Font\\:\\:centimeterSizeToPixels\\(\\) expects int, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Shared/FontTest.php + + - + message: "#^Parameter \\#1 \\$sizeInInch of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Font\\:\\:inchSizeToPixels\\(\\) expects int, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Shared/FontTest.php + + - + message: "#^Parameter \\#1 \\$currencyCode of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\StringHelper\\:\\:setCurrencyCode\\(\\) expects string, null given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Shared/StringHelperTest.php + + - + message: "#^Cannot access offset 0 on mixed\\.$#" + count: 3 + path: tests/PhpSpreadsheetTests/Shared/Trend/ExponentialBestFitTest.php + + - + message: "#^Cannot access offset 1 on mixed\\.$#" + count: 3 + path: tests/PhpSpreadsheetTests/Shared/Trend/ExponentialBestFitTest.php + + - + message: "#^Parameter \\#1 \\$yValues of class PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\ExponentialBestFit constructor expects array\\, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Shared/Trend/ExponentialBestFitTest.php + + - + message: "#^Parameter \\#2 \\$xValues of class PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\ExponentialBestFit constructor expects array\\, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Shared/Trend/ExponentialBestFitTest.php + + - + message: "#^Cannot access offset 0 on mixed\\.$#" + count: 3 + path: tests/PhpSpreadsheetTests/Shared/Trend/LinearBestFitTest.php + + - + message: "#^Cannot access offset 1 on mixed\\.$#" + count: 3 + path: tests/PhpSpreadsheetTests/Shared/Trend/LinearBestFitTest.php + + - + message: "#^Parameter \\#1 \\$yValues of class PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\LinearBestFit constructor expects array\\, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Shared/Trend/LinearBestFitTest.php + + - + message: "#^Parameter \\#2 \\$xValues of class PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\LinearBestFit constructor expects array\\, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Shared/Trend/LinearBestFitTest.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheetTests\\\\SpreadsheetTest\\:\\:testGetSheetByName\\(\\) has parameter \\$index with no type specified\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/SpreadsheetTest.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheetTests\\\\SpreadsheetTest\\:\\:testGetSheetByName\\(\\) has parameter \\$sheetName with no type specified\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/SpreadsheetTest.php + + - + message: "#^Parameter \\#1 \\$rgbValue of static method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Color\\:\\:getBlue\\(\\) expects string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Style/ColorTest.php + + - + message: "#^Parameter \\#1 \\$rgbValue of static method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Color\\:\\:getGreen\\(\\) expects string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Style/ColorTest.php + + - + message: "#^Parameter \\#1 \\$rgbValue of static method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Color\\:\\:getRed\\(\\) expects string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Style/ColorTest.php + + - + message: "#^Parameter \\#1 \\$condition of method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Conditional\\:\\:addCondition\\(\\) expects string, float given\\.$#" + count: 2 + path: tests/PhpSpreadsheetTests/Style/ConditionalTest.php + + - + message: "#^Parameter \\#1 \\$conditions of method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Conditional\\:\\:setConditions\\(\\) expects array\\\\|bool\\|float\\|int\\|string, array\\ given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Style/ConditionalTest.php + + - + message: "#^Parameter \\#2 \\$value of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter\\\\Column\\:\\:setAttribute\\(\\) expects string, int given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Worksheet/AutoFilter/ColumnTest.php + + - + message: "#^Parameter \\#2 \\$value of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter\\\\Column\\\\Rule\\:\\:setRule\\(\\) expects array\\\\|int\\|string, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Worksheet/AutoFilter/DateGroupTest.php + + - + message: "#^Parameter \\#1 \\$im of function imagecolorallocate expects resource, resource\\|false given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Worksheet/DrawingTest.php + + - + message: "#^Parameter \\#1 \\$im of function imagestring expects resource, resource\\|false given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Worksheet/DrawingTest.php + + - + message: "#^Parameter \\#1 \\$value of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\MemoryDrawing\\:\\:setImageResource\\(\\) expects GdImage\\|resource, resource\\|false given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Worksheet/DrawingTest.php + + - + message: "#^Parameter \\#6 \\$col of function imagestring expects int, int\\|false given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Worksheet/DrawingTest.php + + - + message: "#^Parameter \\#2 \\$rowIndex of class PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\RowCellIterator constructor expects int, string given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Worksheet/RowCellIterator2Test.php + + - + message: "#^Cannot access offset 0 on mixed\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Worksheet/WorksheetTest.php + + - + message: "#^Cannot access offset 1 on mixed\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Worksheet/WorksheetTest.php + + - + message: "#^Method PhpOffice\\\\PhpSpreadsheetTests\\\\Writer\\\\Html\\\\CallbackTest\\:\\:yellowBody\\(\\) should return string but returns string\\|null\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Writer/Html/CallbackTest.php + + - + message: "#^Parameter \\#1 \\$haystack of function strpos expects string, string\\|false given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Writer/Html/CallbackTest.php + + - + message: "#^Cannot call method getColor\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 3 + path: tests/PhpSpreadsheetTests/Writer/Html/GridlinesTest.php + + - + message: "#^Cannot call method setSubScript\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 3 + path: tests/PhpSpreadsheetTests/Writer/Html/GridlinesTest.php + + - + message: "#^Cannot call method setSuperScript\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Writer/Html/GridlinesTest.php + + - + message: "#^Cannot call method getPlainText\\(\\) on mixed\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Writer/Html/HtmlCommentsTest.php + + - + message: "#^Cannot call method setBold\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#" + count: 3 + path: tests/PhpSpreadsheetTests/Writer/Html/HtmlCommentsTest.php + + - + message: "#^Parameter \\#1 \\$text of method PhpOffice\\\\PhpSpreadsheet\\\\Comment\\:\\:setText\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\RichText\\\\RichText, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Writer/Html/HtmlCommentsTest.php + + - + message: "#^Parameter \\#1 \\$formatCode of method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\:\\:setFormatCode\\(\\) expects string, mixed given\\.$#" + count: 2 + path: tests/PhpSpreadsheetTests/Writer/Html/HtmlNumberFormatTest.php + + - + message: "#^Cannot access offset 10 on mixed\\.$#" + count: 2 + path: tests/PhpSpreadsheetTests/Writer/Xls/WorkbookTest.php + + - + message: "#^Cannot access offset 12 on mixed\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Writer/Xls/WorkbookTest.php + + - + message: "#^Cannot access offset 25 on mixed\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Writer/Xls/WorkbookTest.php + + - + message: "#^Cannot access offset 8 on mixed\\.$#" + count: 5 + path: tests/PhpSpreadsheetTests/Writer/Xls/WorkbookTest.php + + - + message: "#^Cannot access offset 9 on mixed\\.$#" + count: 2 + path: tests/PhpSpreadsheetTests/Writer/Xls/WorkbookTest.php + + - + message: "#^Cannot access offset int\\|string\\|false on mixed\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Writer/Xls/WorkbookTest.php + + - + message: "#^Parameter \\#1 \\$input of function array_keys expects array, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Writer/Xls/WorkbookTest.php + + - + message: "#^Parameter \\#1 \\$palette of method PhpOffice\\\\PhpSpreadsheetTests\\\\Writer\\\\Xls\\\\WorkbookTest\\:\\:paletteToColor\\(\\) expects array, mixed given\\.$#" + count: 6 + path: tests/PhpSpreadsheetTests/Writer/Xls/WorkbookTest.php + + - + message: "#^Parameter \\#2 \\$array of function array_map expects array, mixed given\\.$#" + count: 2 + path: tests/PhpSpreadsheetTests/Writer/Xls/WorkbookTest.php + + - + message: "#^Parameter \\#2 \\.\\.\\.\\$values of function sprintf expects bool\\|float\\|int\\|string\\|null, mixed given\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Writer/Xlsx/LocaleFloatsTest.php + + - + message: "#^Cannot call method getCell\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 4 + path: tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataCloneTest.php + + - + message: "#^Cannot call method getDrawingCollection\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null\\.$#" + count: 4 + path: tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataCloneTest.php + + - + message: "#^Cannot access property \\$pageSetup on SimpleXMLElement\\|false\\.$#" + count: 1 + path: tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataTest.php + + - + message: "#^Cannot access property \\$sheetProtection on SimpleXMLElement\\|false\\.$#" + count: 5 + path: tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataTest.php + + - + message: "#^Parameter \\#1 \\$data of function simplexml_load_string expects string, string\\|false given\\.$#" + count: 2 + path: tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataTest.php + + - + message: "#^Cannot call method getExtension\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\BaseDrawing\\|null\\.$#" + count: 2 + path: tests/PhpSpreadsheetTests/Writer/Xlsx/WmfTest.php + diff --git a/vendor/phpoffice/phpspreadsheet/phpstan.neon.dist b/vendor/phpoffice/phpspreadsheet/phpstan.neon.dist new file mode 100644 index 0000000..e2f78e3 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/phpstan.neon.dist @@ -0,0 +1,29 @@ +includes: + - phpstan-baseline.neon + - vendor/phpstan/phpstan-phpunit/extension.neon + - vendor/phpstan/phpstan-phpunit/rules.neon + +parameters: + level: max + paths: + - src/ + - tests/ + parallel: + processTimeout: 300.0 + checkMissingIterableValueType: false + ignoreErrors: + - '~^Class GdImage not found\.$~' + - '~^Method .* has invalid return type GdImage\.$~' + - '~^Property .* has unknown class GdImage as its type\.$~' + - '~^Parameter .* of method .* has invalid type GdImage\.$~' + - '~^Parameter \#1 \$im of function (imagedestroy|imageistruecolor|imagealphablending|imagesavealpha|imagecolortransparent|imagecolorsforindex|imagesavealpha|imagesx|imagesy) expects resource, GdImage\|resource given\.$~' + - '~^Parameter \#2 \$src_im of function imagecopy expects resource, GdImage\|resource given\.$~' + # Accept a bit anything for assert methods + - '~^Parameter \#2 .* of static method PHPUnit\\Framework\\Assert\:\:assert\w+\(\) expects .*, .* given\.$~' + - '~^Method PhpOffice\\PhpSpreadsheetTests\\.*\:\:test.*\(\) has parameter \$args with no type specified\.$~' + + # Ignore all JpGraph issues + - '~^Constant (MARK_CIRCLE|MARK_CROSS|MARK_DIAMOND|MARK_DTRIANGLE|MARK_FILLEDCIRCLE|MARK_SQUARE|MARK_STAR|MARK_UTRIANGLE|MARK_X|SIDE_RIGHT) not found\.$~' + - '~^Instantiated class (AccBarPlot|AccLinePlot|BarPlot|ContourPlot|Graph|GroupBarPlot|GroupBarPlot|LinePlot|LinePlot|PieGraph|PiePlot|PiePlot3D|PiePlotC|RadarGraph|RadarPlot|ScatterPlot|Spline|StockPlot) not found\.$~' + - '~^Call to method .*\(\) on an unknown class (AccBarPlot|AccLinePlot|BarPlot|ContourPlot|Graph|GroupBarPlot|GroupBarPlot|LinePlot|LinePlot|PieGraph|PiePlot|PiePlot3D|PiePlotC|RadarGraph|RadarPlot|ScatterPlot|Spline|StockPlot)\.$~' + - '~^Access to property .* on an unknown class (AccBarPlot|AccLinePlot|BarPlot|ContourPlot|Graph|GroupBarPlot|GroupBarPlot|LinePlot|LinePlot|PieGraph|PiePlot|PiePlot3D|PiePlotC|RadarGraph|RadarPlot|ScatterPlot|Spline|StockPlot)\.$~' diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Calculation.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Calculation.php new file mode 100644 index 0000000..b473b27 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Calculation.php @@ -0,0 +1,5555 @@ +=-]*)|(\'[^\']*\')|(\"[^\"]*\"))!)?\$?\b([a-z]{1,3})\$?(\d{1,7})(?![\w.])'; + // Cell reference (with or without a sheet reference) ensuring absolute/relative + const CALCULATION_REGEXP_CELLREF_RELATIVE = '((([^\s\(,!&%^\/\*\+<>=-]*)|(\'[^\']*\')|(\"[^\"]*\"))!)?(\$?\b[a-z]{1,3})(\$?\d{1,7})(?![\w.])'; + const CALCULATION_REGEXP_COLUMN_RANGE = '(((([^\s\(,!&%^\/\*\+<>=-]*)|(\'[^\']*\')|(\"[^\"]*\"))!)?(\$?[a-z]{1,3})):(?![.*])'; + const CALCULATION_REGEXP_ROW_RANGE = '(((([^\s\(,!&%^\/\*\+<>=-]*)|(\'[^\']*\')|(\"[^\"]*\"))!)?(\$?[1-9][0-9]{0,6})):(?![.*])'; + // Cell reference (with or without a sheet reference) ensuring absolute/relative + // Cell ranges ensuring absolute/relative + const CALCULATION_REGEXP_COLUMNRANGE_RELATIVE = '(\$?[a-z]{1,3}):(\$?[a-z]{1,3})'; + const CALCULATION_REGEXP_ROWRANGE_RELATIVE = '(\$?\d{1,7}):(\$?\d{1,7})'; + // Defined Names: Named Range of cells, or Named Formulae + const CALCULATION_REGEXP_DEFINEDNAME = '((([^\s,!&%^\/\*\+<>=-]*)|(\'[^\']*\')|(\"[^\"]*\"))!)?([_\p{L}][_\p{L}\p{N}\.]*)'; + // Error + const CALCULATION_REGEXP_ERROR = '\#[A-Z][A-Z0_\/]*[!\?]?'; + + /** constants */ + const RETURN_ARRAY_AS_ERROR = 'error'; + const RETURN_ARRAY_AS_VALUE = 'value'; + const RETURN_ARRAY_AS_ARRAY = 'array'; + + const FORMULA_OPEN_FUNCTION_BRACE = '{'; + const FORMULA_CLOSE_FUNCTION_BRACE = '}'; + const FORMULA_STRING_QUOTE = '"'; + + private static $returnArrayAsType = self::RETURN_ARRAY_AS_VALUE; + + /** + * Instance of this class. + * + * @var Calculation + */ + private static $instance; + + /** + * Instance of the spreadsheet this Calculation Engine is using. + * + * @var Spreadsheet + */ + private $spreadsheet; + + /** + * Calculation cache. + * + * @var array + */ + private $calculationCache = []; + + /** + * Calculation cache enabled. + * + * @var bool + */ + private $calculationCacheEnabled = true; + + /** + * Used to generate unique store keys. + * + * @var int + */ + private $branchStoreKeyCounter = 0; + + private $branchPruningEnabled = true; + + /** + * List of operators that can be used within formulae + * The true/false value indicates whether it is a binary operator or a unary operator. + * + * @var array + */ + private static $operators = [ + '+' => true, '-' => true, '*' => true, '/' => true, + '^' => true, '&' => true, '%' => false, '~' => false, + '>' => true, '<' => true, '=' => true, '>=' => true, + '<=' => true, '<>' => true, '|' => true, ':' => true, + ]; + + /** + * List of binary operators (those that expect two operands). + * + * @var array + */ + private static $binaryOperators = [ + '+' => true, '-' => true, '*' => true, '/' => true, + '^' => true, '&' => true, '>' => true, '<' => true, + '=' => true, '>=' => true, '<=' => true, '<>' => true, + '|' => true, ':' => true, + ]; + + /** + * The debug log generated by the calculation engine. + * + * @var Logger + */ + private $debugLog; + + /** + * Flag to determine how formula errors should be handled + * If true, then a user error will be triggered + * If false, then an exception will be thrown. + * + * @var bool + */ + public $suppressFormulaErrors = false; + + /** + * Error message for any error that was raised/thrown by the calculation engine. + * + * @var null|string + */ + public $formulaError; + + /** + * Reference Helper. + * + * @var ReferenceHelper + */ + private static $referenceHelper; + + /** + * An array of the nested cell references accessed by the calculation engine, used for the debug log. + * + * @var CyclicReferenceStack + */ + private $cyclicReferenceStack; + + private $cellStack = []; + + /** + * Current iteration counter for cyclic formulae + * If the value is 0 (or less) then cyclic formulae will throw an exception, + * otherwise they will iterate to the limit defined here before returning a result. + * + * @var int + */ + private $cyclicFormulaCounter = 1; + + private $cyclicFormulaCell = ''; + + /** + * Number of iterations for cyclic formulae. + * + * @var int + */ + public $cyclicFormulaCount = 1; + + /** + * Epsilon Precision used for comparisons in calculations. + * + * @var float + */ + private $delta = 0.1e-12; + + /** + * The current locale setting. + * + * @var string + */ + private static $localeLanguage = 'en_us'; // US English (default locale) + + /** + * List of available locale settings + * Note that this is read for the locale subdirectory only when requested. + * + * @var string[] + */ + private static $validLocaleLanguages = [ + 'en', // English (default language) + ]; + + /** + * Locale-specific argument separator for function arguments. + * + * @var string + */ + private static $localeArgumentSeparator = ','; + + private static $localeFunctions = []; + + /** + * Locale-specific translations for Excel constants (True, False and Null). + * + * @var array + */ + public static $localeBoolean = [ + 'TRUE' => 'TRUE', + 'FALSE' => 'FALSE', + 'NULL' => 'NULL', + ]; + + /** + * Excel constant string translations to their PHP equivalents + * Constant conversion from text name/value to actual (datatyped) value. + * + * @var array + */ + private static $excelConstants = [ + 'TRUE' => true, + 'FALSE' => false, + 'NULL' => null, + ]; + + // PhpSpreadsheet functions + private static $phpSpreadsheetFunctions = [ + 'ABS' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Absolute::class, 'evaluate'], + 'argumentCount' => '1', + ], + 'ACCRINT' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\Securities\AccruedInterest::class, 'periodic'], + 'argumentCount' => '4-8', + ], + 'ACCRINTM' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\Securities\AccruedInterest::class, 'atMaturity'], + 'argumentCount' => '3-5', + ], + 'ACOS' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Trig\Cosine::class, 'acos'], + 'argumentCount' => '1', + ], + 'ACOSH' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Trig\Cosine::class, 'acosh'], + 'argumentCount' => '1', + ], + 'ACOT' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Trig\Cotangent::class, 'acot'], + 'argumentCount' => '1', + ], + 'ACOTH' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Trig\Cotangent::class, 'acoth'], + 'argumentCount' => '1', + ], + 'ADDRESS' => [ + 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, + 'functionCall' => [LookupRef\Address::class, 'cell'], + 'argumentCount' => '2-5', + ], + 'AGGREGATE' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '3+', + ], + 'AMORDEGRC' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\Amortization::class, 'AMORDEGRC'], + 'argumentCount' => '6,7', + ], + 'AMORLINC' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\Amortization::class, 'AMORLINC'], + 'argumentCount' => '6,7', + ], + 'AND' => [ + 'category' => Category::CATEGORY_LOGICAL, + 'functionCall' => [Logical\Operations::class, 'logicalAnd'], + 'argumentCount' => '1+', + ], + 'ARABIC' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Arabic::class, 'evaluate'], + 'argumentCount' => '1', + ], + 'AREAS' => [ + 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '1', + ], + 'ARRAYTOTEXT' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '?', + ], + 'ASC' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '1', + ], + 'ASIN' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Trig\Sine::class, 'asin'], + 'argumentCount' => '1', + ], + 'ASINH' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Trig\Sine::class, 'asinh'], + 'argumentCount' => '1', + ], + 'ATAN' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Trig\Tangent::class, 'atan'], + 'argumentCount' => '1', + ], + 'ATAN2' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Trig\Tangent::class, 'atan2'], + 'argumentCount' => '2', + ], + 'ATANH' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Trig\Tangent::class, 'atanh'], + 'argumentCount' => '1', + ], + 'AVEDEV' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Averages::class, 'averageDeviations'], + 'argumentCount' => '1+', + ], + 'AVERAGE' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Averages::class, 'average'], + 'argumentCount' => '1+', + ], + 'AVERAGEA' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Averages::class, 'averageA'], + 'argumentCount' => '1+', + ], + 'AVERAGEIF' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Conditional::class, 'AVERAGEIF'], + 'argumentCount' => '2,3', + ], + 'AVERAGEIFS' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Conditional::class, 'AVERAGEIFS'], + 'argumentCount' => '3+', + ], + 'BAHTTEXT' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '1', + ], + 'BASE' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Base::class, 'evaluate'], + 'argumentCount' => '2,3', + ], + 'BESSELI' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\BesselI::class, 'BESSELI'], + 'argumentCount' => '2', + ], + 'BESSELJ' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\BesselJ::class, 'BESSELJ'], + 'argumentCount' => '2', + ], + 'BESSELK' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\BesselK::class, 'BESSELK'], + 'argumentCount' => '2', + ], + 'BESSELY' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\BesselY::class, 'BESSELY'], + 'argumentCount' => '2', + ], + 'BETADIST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\Beta::class, 'distribution'], + 'argumentCount' => '3-5', + ], + 'BETA.DIST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '4-6', + ], + 'BETAINV' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\Beta::class, 'inverse'], + 'argumentCount' => '3-5', + ], + 'BETA.INV' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\Beta::class, 'inverse'], + 'argumentCount' => '3-5', + ], + 'BIN2DEC' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\ConvertBinary::class, 'toDecimal'], + 'argumentCount' => '1', + ], + 'BIN2HEX' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\ConvertBinary::class, 'toHex'], + 'argumentCount' => '1,2', + ], + 'BIN2OCT' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\ConvertBinary::class, 'toOctal'], + 'argumentCount' => '1,2', + ], + 'BINOMDIST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\Binomial::class, 'distribution'], + 'argumentCount' => '4', + ], + 'BINOM.DIST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\Binomial::class, 'distribution'], + 'argumentCount' => '4', + ], + 'BINOM.DIST.RANGE' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\Binomial::class, 'range'], + 'argumentCount' => '3,4', + ], + 'BINOM.INV' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\Binomial::class, 'inverse'], + 'argumentCount' => '3', + ], + 'BITAND' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\BitWise::class, 'BITAND'], + 'argumentCount' => '2', + ], + 'BITOR' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\BitWise::class, 'BITOR'], + 'argumentCount' => '2', + ], + 'BITXOR' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\BitWise::class, 'BITXOR'], + 'argumentCount' => '2', + ], + 'BITLSHIFT' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\BitWise::class, 'BITLSHIFT'], + 'argumentCount' => '2', + ], + 'BITRSHIFT' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\BitWise::class, 'BITRSHIFT'], + 'argumentCount' => '2', + ], + 'CEILING' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Ceiling::class, 'ceiling'], + 'argumentCount' => '1-2', // 2 for Excel, 1-2 for Ods/Gnumeric + ], + 'CEILING.MATH' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Ceiling::class, 'math'], + 'argumentCount' => '1-3', + ], + 'CEILING.PRECISE' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Ceiling::class, 'precise'], + 'argumentCount' => '1,2', + ], + 'CELL' => [ + 'category' => Category::CATEGORY_INFORMATION, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '1,2', + ], + 'CHAR' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [TextData\CharacterConvert::class, 'character'], + 'argumentCount' => '1', + ], + 'CHIDIST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'distributionRightTail'], + 'argumentCount' => '2', + ], + 'CHISQ.DIST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'distributionLeftTail'], + 'argumentCount' => '3', + ], + 'CHISQ.DIST.RT' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'distributionRightTail'], + 'argumentCount' => '2', + ], + 'CHIINV' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'inverseRightTail'], + 'argumentCount' => '2', + ], + 'CHISQ.INV' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'inverseLeftTail'], + 'argumentCount' => '2', + ], + 'CHISQ.INV.RT' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'inverseRightTail'], + 'argumentCount' => '2', + ], + 'CHITEST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'test'], + 'argumentCount' => '2', + ], + 'CHISQ.TEST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'test'], + 'argumentCount' => '2', + ], + 'CHOOSE' => [ + 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, + 'functionCall' => [LookupRef\Selection::class, 'CHOOSE'], + 'argumentCount' => '2+', + ], + 'CLEAN' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [TextData\Trim::class, 'nonPrintable'], + 'argumentCount' => '1', + ], + 'CODE' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [TextData\CharacterConvert::class, 'code'], + 'argumentCount' => '1', + ], + 'COLUMN' => [ + 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, + 'functionCall' => [LookupRef\RowColumnInformation::class, 'COLUMN'], + 'argumentCount' => '-1', + 'passCellReference' => true, + 'passByReference' => [true], + ], + 'COLUMNS' => [ + 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, + 'functionCall' => [LookupRef\RowColumnInformation::class, 'COLUMNS'], + 'argumentCount' => '1', + ], + 'COMBIN' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Combinations::class, 'withoutRepetition'], + 'argumentCount' => '2', + ], + 'COMBINA' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Combinations::class, 'withRepetition'], + 'argumentCount' => '2', + ], + 'COMPLEX' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\Complex::class, 'COMPLEX'], + 'argumentCount' => '2,3', + ], + 'CONCAT' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [TextData\Concatenate::class, 'CONCATENATE'], + 'argumentCount' => '1+', + ], + 'CONCATENATE' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [TextData\Concatenate::class, 'CONCATENATE'], + 'argumentCount' => '1+', + ], + 'CONFIDENCE' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Confidence::class, 'CONFIDENCE'], + 'argumentCount' => '3', + ], + 'CONFIDENCE.NORM' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Confidence::class, 'CONFIDENCE'], + 'argumentCount' => '3', + ], + 'CONFIDENCE.T' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '3', + ], + 'CONVERT' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\ConvertUOM::class, 'CONVERT'], + 'argumentCount' => '3', + ], + 'CORREL' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Trends::class, 'CORREL'], + 'argumentCount' => '2', + ], + 'COS' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Trig\Cosine::class, 'cos'], + 'argumentCount' => '1', + ], + 'COSH' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Trig\Cosine::class, 'cosh'], + 'argumentCount' => '1', + ], + 'COT' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Trig\Cotangent::class, 'cot'], + 'argumentCount' => '1', + ], + 'COTH' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Trig\Cotangent::class, 'coth'], + 'argumentCount' => '1', + ], + 'COUNT' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Counts::class, 'COUNT'], + 'argumentCount' => '1+', + ], + 'COUNTA' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Counts::class, 'COUNTA'], + 'argumentCount' => '1+', + ], + 'COUNTBLANK' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Counts::class, 'COUNTBLANK'], + 'argumentCount' => '1', + ], + 'COUNTIF' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Conditional::class, 'COUNTIF'], + 'argumentCount' => '2', + ], + 'COUNTIFS' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Conditional::class, 'COUNTIFS'], + 'argumentCount' => '2+', + ], + 'COUPDAYBS' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\Coupons::class, 'COUPDAYBS'], + 'argumentCount' => '3,4', + ], + 'COUPDAYS' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\Coupons::class, 'COUPDAYS'], + 'argumentCount' => '3,4', + ], + 'COUPDAYSNC' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\Coupons::class, 'COUPDAYSNC'], + 'argumentCount' => '3,4', + ], + 'COUPNCD' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\Coupons::class, 'COUPNCD'], + 'argumentCount' => '3,4', + ], + 'COUPNUM' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\Coupons::class, 'COUPNUM'], + 'argumentCount' => '3,4', + ], + 'COUPPCD' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\Coupons::class, 'COUPPCD'], + 'argumentCount' => '3,4', + ], + 'COVAR' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Trends::class, 'COVAR'], + 'argumentCount' => '2', + ], + 'COVARIANCE.P' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Trends::class, 'COVAR'], + 'argumentCount' => '2', + ], + 'COVARIANCE.S' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '2', + ], + 'CRITBINOM' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\Binomial::class, 'inverse'], + 'argumentCount' => '3', + ], + 'CSC' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Trig\Cosecant::class, 'csc'], + 'argumentCount' => '1', + ], + 'CSCH' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Trig\Cosecant::class, 'csch'], + 'argumentCount' => '1', + ], + 'CUBEKPIMEMBER' => [ + 'category' => Category::CATEGORY_CUBE, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '?', + ], + 'CUBEMEMBER' => [ + 'category' => Category::CATEGORY_CUBE, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '?', + ], + 'CUBEMEMBERPROPERTY' => [ + 'category' => Category::CATEGORY_CUBE, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '?', + ], + 'CUBERANKEDMEMBER' => [ + 'category' => Category::CATEGORY_CUBE, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '?', + ], + 'CUBESET' => [ + 'category' => Category::CATEGORY_CUBE, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '?', + ], + 'CUBESETCOUNT' => [ + 'category' => Category::CATEGORY_CUBE, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '?', + ], + 'CUBEVALUE' => [ + 'category' => Category::CATEGORY_CUBE, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '?', + ], + 'CUMIPMT' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\CashFlow\Constant\Periodic\Cumulative::class, 'interest'], + 'argumentCount' => '6', + ], + 'CUMPRINC' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\CashFlow\Constant\Periodic\Cumulative::class, 'principal'], + 'argumentCount' => '6', + ], + 'DATE' => [ + 'category' => Category::CATEGORY_DATE_AND_TIME, + 'functionCall' => [DateTimeExcel\Date::class, 'fromYMD'], + 'argumentCount' => '3', + ], + 'DATEDIF' => [ + 'category' => Category::CATEGORY_DATE_AND_TIME, + 'functionCall' => [DateTimeExcel\Difference::class, 'interval'], + 'argumentCount' => '2,3', + ], + 'DATESTRING' => [ + 'category' => Category::CATEGORY_DATE_AND_TIME, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '?', + ], + 'DATEVALUE' => [ + 'category' => Category::CATEGORY_DATE_AND_TIME, + 'functionCall' => [DateTimeExcel\DateValue::class, 'fromString'], + 'argumentCount' => '1', + ], + 'DAVERAGE' => [ + 'category' => Category::CATEGORY_DATABASE, + 'functionCall' => [Database\DAverage::class, 'evaluate'], + 'argumentCount' => '3', + ], + 'DAY' => [ + 'category' => Category::CATEGORY_DATE_AND_TIME, + 'functionCall' => [DateTimeExcel\DateParts::class, 'day'], + 'argumentCount' => '1', + ], + 'DAYS' => [ + 'category' => Category::CATEGORY_DATE_AND_TIME, + 'functionCall' => [DateTimeExcel\Days::class, 'between'], + 'argumentCount' => '2', + ], + 'DAYS360' => [ + 'category' => Category::CATEGORY_DATE_AND_TIME, + 'functionCall' => [DateTimeExcel\Days360::class, 'between'], + 'argumentCount' => '2,3', + ], + 'DB' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\Depreciation::class, 'DB'], + 'argumentCount' => '4,5', + ], + 'DBCS' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '1', + ], + 'DCOUNT' => [ + 'category' => Category::CATEGORY_DATABASE, + 'functionCall' => [Database\DCount::class, 'evaluate'], + 'argumentCount' => '3', + ], + 'DCOUNTA' => [ + 'category' => Category::CATEGORY_DATABASE, + 'functionCall' => [Database\DCountA::class, 'evaluate'], + 'argumentCount' => '3', + ], + 'DDB' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\Depreciation::class, 'DDB'], + 'argumentCount' => '4,5', + ], + 'DEC2BIN' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\ConvertDecimal::class, 'toBinary'], + 'argumentCount' => '1,2', + ], + 'DEC2HEX' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\ConvertDecimal::class, 'toHex'], + 'argumentCount' => '1,2', + ], + 'DEC2OCT' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\ConvertDecimal::class, 'toOctal'], + 'argumentCount' => '1,2', + ], + 'DECIMAL' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '2', + ], + 'DEGREES' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Angle::class, 'toDegrees'], + 'argumentCount' => '1', + ], + 'DELTA' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\Compare::class, 'DELTA'], + 'argumentCount' => '1,2', + ], + 'DEVSQ' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Deviations::class, 'sumSquares'], + 'argumentCount' => '1+', + ], + 'DGET' => [ + 'category' => Category::CATEGORY_DATABASE, + 'functionCall' => [Database\DGet::class, 'evaluate'], + 'argumentCount' => '3', + ], + 'DISC' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\Securities\Rates::class, 'discount'], + 'argumentCount' => '4,5', + ], + 'DMAX' => [ + 'category' => Category::CATEGORY_DATABASE, + 'functionCall' => [Database\DMax::class, 'evaluate'], + 'argumentCount' => '3', + ], + 'DMIN' => [ + 'category' => Category::CATEGORY_DATABASE, + 'functionCall' => [Database\DMin::class, 'evaluate'], + 'argumentCount' => '3', + ], + 'DOLLAR' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [TextData\Format::class, 'DOLLAR'], + 'argumentCount' => '1,2', + ], + 'DOLLARDE' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\Dollar::class, 'decimal'], + 'argumentCount' => '2', + ], + 'DOLLARFR' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\Dollar::class, 'fractional'], + 'argumentCount' => '2', + ], + 'DPRODUCT' => [ + 'category' => Category::CATEGORY_DATABASE, + 'functionCall' => [Database\DProduct::class, 'evaluate'], + 'argumentCount' => '3', + ], + 'DSTDEV' => [ + 'category' => Category::CATEGORY_DATABASE, + 'functionCall' => [Database\DStDev::class, 'evaluate'], + 'argumentCount' => '3', + ], + 'DSTDEVP' => [ + 'category' => Category::CATEGORY_DATABASE, + 'functionCall' => [Database\DStDevP::class, 'evaluate'], + 'argumentCount' => '3', + ], + 'DSUM' => [ + 'category' => Category::CATEGORY_DATABASE, + 'functionCall' => [Database\DSum::class, 'evaluate'], + 'argumentCount' => '3', + ], + 'DURATION' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '5,6', + ], + 'DVAR' => [ + 'category' => Category::CATEGORY_DATABASE, + 'functionCall' => [Database\DVar::class, 'evaluate'], + 'argumentCount' => '3', + ], + 'DVARP' => [ + 'category' => Category::CATEGORY_DATABASE, + 'functionCall' => [Database\DVarP::class, 'evaluate'], + 'argumentCount' => '3', + ], + 'ECMA.CEILING' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '1,2', + ], + 'EDATE' => [ + 'category' => Category::CATEGORY_DATE_AND_TIME, + 'functionCall' => [DateTimeExcel\Month::class, 'adjust'], + 'argumentCount' => '2', + ], + 'EFFECT' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\InterestRate::class, 'effective'], + 'argumentCount' => '2', + ], + 'ENCODEURL' => [ + 'category' => Category::CATEGORY_WEB, + 'functionCall' => [Web\Service::class, 'urlEncode'], + 'argumentCount' => '1', + ], + 'EOMONTH' => [ + 'category' => Category::CATEGORY_DATE_AND_TIME, + 'functionCall' => [DateTimeExcel\Month::class, 'lastDay'], + 'argumentCount' => '2', + ], + 'ERF' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\Erf::class, 'ERF'], + 'argumentCount' => '1,2', + ], + 'ERF.PRECISE' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\Erf::class, 'ERFPRECISE'], + 'argumentCount' => '1', + ], + 'ERFC' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\ErfC::class, 'ERFC'], + 'argumentCount' => '1', + ], + 'ERFC.PRECISE' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\ErfC::class, 'ERFC'], + 'argumentCount' => '1', + ], + 'ERROR.TYPE' => [ + 'category' => Category::CATEGORY_INFORMATION, + 'functionCall' => [Functions::class, 'errorType'], + 'argumentCount' => '1', + ], + 'EVEN' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Round::class, 'even'], + 'argumentCount' => '1', + ], + 'EXACT' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [TextData\Text::class, 'exact'], + 'argumentCount' => '2', + ], + 'EXP' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Exp::class, 'evaluate'], + 'argumentCount' => '1', + ], + 'EXPONDIST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\Exponential::class, 'distribution'], + 'argumentCount' => '3', + ], + 'EXPON.DIST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\Exponential::class, 'distribution'], + 'argumentCount' => '3', + ], + 'FACT' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Factorial::class, 'fact'], + 'argumentCount' => '1', + ], + 'FACTDOUBLE' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Factorial::class, 'factDouble'], + 'argumentCount' => '1', + ], + 'FALSE' => [ + 'category' => Category::CATEGORY_LOGICAL, + 'functionCall' => [Logical\Boolean::class, 'FALSE'], + 'argumentCount' => '0', + ], + 'FDIST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '3', + ], + 'F.DIST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\F::class, 'distribution'], + 'argumentCount' => '4', + ], + 'F.DIST.RT' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '3', + ], + 'FILTER' => [ + 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '3+', + ], + 'FILTERXML' => [ + 'category' => Category::CATEGORY_WEB, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '2', + ], + 'FIND' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [TextData\Search::class, 'sensitive'], + 'argumentCount' => '2,3', + ], + 'FINDB' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [TextData\Search::class, 'sensitive'], + 'argumentCount' => '2,3', + ], + 'FINV' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '3', + ], + 'F.INV' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '3', + ], + 'F.INV.RT' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '3', + ], + 'FISHER' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\Fisher::class, 'distribution'], + 'argumentCount' => '1', + ], + 'FISHERINV' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\Fisher::class, 'inverse'], + 'argumentCount' => '1', + ], + 'FIXED' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [TextData\Format::class, 'FIXEDFORMAT'], + 'argumentCount' => '1-3', + ], + 'FLOOR' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Floor::class, 'floor'], + 'argumentCount' => '1-2', // Excel requries 2, Ods/Gnumeric 1-2 + ], + 'FLOOR.MATH' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Floor::class, 'math'], + 'argumentCount' => '1-3', + ], + 'FLOOR.PRECISE' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Floor::class, 'precise'], + 'argumentCount' => '1-2', + ], + 'FORECAST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Trends::class, 'FORECAST'], + 'argumentCount' => '3', + ], + 'FORECAST.ETS' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '3-6', + ], + 'FORECAST.ETS.CONFINT' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '3-6', + ], + 'FORECAST.ETS.SEASONALITY' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '2-4', + ], + 'FORECAST.ETS.STAT' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '3-6', + ], + 'FORECAST.LINEAR' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Trends::class, 'FORECAST'], + 'argumentCount' => '3', + ], + 'FORMULATEXT' => [ + 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, + 'functionCall' => [LookupRef\Formula::class, 'text'], + 'argumentCount' => '1', + 'passCellReference' => true, + 'passByReference' => [true], + ], + 'FREQUENCY' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '2', + ], + 'FTEST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '2', + ], + 'F.TEST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '2', + ], + 'FV' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\CashFlow\Constant\Periodic::class, 'futureValue'], + 'argumentCount' => '3-5', + ], + 'FVSCHEDULE' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\CashFlow\Single::class, 'futureValue'], + 'argumentCount' => '2', + ], + 'GAMMA' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\Gamma::class, 'gamma'], + 'argumentCount' => '1', + ], + 'GAMMADIST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\Gamma::class, 'distribution'], + 'argumentCount' => '4', + ], + 'GAMMA.DIST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\Gamma::class, 'distribution'], + 'argumentCount' => '4', + ], + 'GAMMAINV' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\Gamma::class, 'inverse'], + 'argumentCount' => '3', + ], + 'GAMMA.INV' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\Gamma::class, 'inverse'], + 'argumentCount' => '3', + ], + 'GAMMALN' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\Gamma::class, 'ln'], + 'argumentCount' => '1', + ], + 'GAMMALN.PRECISE' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\Gamma::class, 'ln'], + 'argumentCount' => '1', + ], + 'GAUSS' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\StandardNormal::class, 'gauss'], + 'argumentCount' => '1', + ], + 'GCD' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Gcd::class, 'evaluate'], + 'argumentCount' => '1+', + ], + 'GEOMEAN' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Averages\Mean::class, 'geometric'], + 'argumentCount' => '1+', + ], + 'GESTEP' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\Compare::class, 'GESTEP'], + 'argumentCount' => '1,2', + ], + 'GETPIVOTDATA' => [ + 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '2+', + ], + 'GROWTH' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Trends::class, 'GROWTH'], + 'argumentCount' => '1-4', + ], + 'HARMEAN' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Averages\Mean::class, 'harmonic'], + 'argumentCount' => '1+', + ], + 'HEX2BIN' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\ConvertHex::class, 'toBinary'], + 'argumentCount' => '1,2', + ], + 'HEX2DEC' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\ConvertHex::class, 'toDecimal'], + 'argumentCount' => '1', + ], + 'HEX2OCT' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\ConvertHex::class, 'toOctal'], + 'argumentCount' => '1,2', + ], + 'HLOOKUP' => [ + 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, + 'functionCall' => [LookupRef\HLookup::class, 'lookup'], + 'argumentCount' => '3,4', + ], + 'HOUR' => [ + 'category' => Category::CATEGORY_DATE_AND_TIME, + 'functionCall' => [DateTimeExcel\TimeParts::class, 'hour'], + 'argumentCount' => '1', + ], + 'HYPERLINK' => [ + 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, + 'functionCall' => [LookupRef\Hyperlink::class, 'set'], + 'argumentCount' => '1,2', + 'passCellReference' => true, + ], + 'HYPGEOMDIST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\HyperGeometric::class, 'distribution'], + 'argumentCount' => '4', + ], + 'HYPGEOM.DIST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '5', + ], + 'IF' => [ + 'category' => Category::CATEGORY_LOGICAL, + 'functionCall' => [Logical\Conditional::class, 'statementIf'], + 'argumentCount' => '1-3', + ], + 'IFERROR' => [ + 'category' => Category::CATEGORY_LOGICAL, + 'functionCall' => [Logical\Conditional::class, 'IFERROR'], + 'argumentCount' => '2', + ], + 'IFNA' => [ + 'category' => Category::CATEGORY_LOGICAL, + 'functionCall' => [Logical\Conditional::class, 'IFNA'], + 'argumentCount' => '2', + ], + 'IFS' => [ + 'category' => Category::CATEGORY_LOGICAL, + 'functionCall' => [Logical\Conditional::class, 'IFS'], + 'argumentCount' => '2+', + ], + 'IMABS' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMABS'], + 'argumentCount' => '1', + ], + 'IMAGINARY' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\Complex::class, 'IMAGINARY'], + 'argumentCount' => '1', + ], + 'IMARGUMENT' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMARGUMENT'], + 'argumentCount' => '1', + ], + 'IMCONJUGATE' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMCONJUGATE'], + 'argumentCount' => '1', + ], + 'IMCOS' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMCOS'], + 'argumentCount' => '1', + ], + 'IMCOSH' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMCOSH'], + 'argumentCount' => '1', + ], + 'IMCOT' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMCOT'], + 'argumentCount' => '1', + ], + 'IMCSC' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMCSC'], + 'argumentCount' => '1', + ], + 'IMCSCH' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMCSCH'], + 'argumentCount' => '1', + ], + 'IMDIV' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\ComplexOperations::class, 'IMDIV'], + 'argumentCount' => '2', + ], + 'IMEXP' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMEXP'], + 'argumentCount' => '1', + ], + 'IMLN' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMLN'], + 'argumentCount' => '1', + ], + 'IMLOG10' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMLOG10'], + 'argumentCount' => '1', + ], + 'IMLOG2' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMLOG2'], + 'argumentCount' => '1', + ], + 'IMPOWER' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMPOWER'], + 'argumentCount' => '2', + ], + 'IMPRODUCT' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\ComplexOperations::class, 'IMPRODUCT'], + 'argumentCount' => '1+', + ], + 'IMREAL' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\Complex::class, 'IMREAL'], + 'argumentCount' => '1', + ], + 'IMSEC' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMSEC'], + 'argumentCount' => '1', + ], + 'IMSECH' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMSECH'], + 'argumentCount' => '1', + ], + 'IMSIN' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMSIN'], + 'argumentCount' => '1', + ], + 'IMSINH' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMSINH'], + 'argumentCount' => '1', + ], + 'IMSQRT' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMSQRT'], + 'argumentCount' => '1', + ], + 'IMSUB' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\ComplexOperations::class, 'IMSUB'], + 'argumentCount' => '2', + ], + 'IMSUM' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\ComplexOperations::class, 'IMSUM'], + 'argumentCount' => '1+', + ], + 'IMTAN' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMTAN'], + 'argumentCount' => '1', + ], + 'INDEX' => [ + 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, + 'functionCall' => [LookupRef\Matrix::class, 'index'], + 'argumentCount' => '1-4', + ], + 'INDIRECT' => [ + 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, + 'functionCall' => [LookupRef\Indirect::class, 'INDIRECT'], + 'argumentCount' => '1,2', + 'passCellReference' => true, + ], + 'INFO' => [ + 'category' => Category::CATEGORY_INFORMATION, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '1', + ], + 'INT' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\IntClass::class, 'evaluate'], + 'argumentCount' => '1', + ], + 'INTERCEPT' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Trends::class, 'INTERCEPT'], + 'argumentCount' => '2', + ], + 'INTRATE' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\Securities\Rates::class, 'interest'], + 'argumentCount' => '4,5', + ], + 'IPMT' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\CashFlow\Constant\Periodic\Interest::class, 'payment'], + 'argumentCount' => '4-6', + ], + 'IRR' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\CashFlow\Variable\Periodic::class, 'rate'], + 'argumentCount' => '1,2', + ], + 'ISBLANK' => [ + 'category' => Category::CATEGORY_INFORMATION, + 'functionCall' => [Functions::class, 'isBlank'], + 'argumentCount' => '1', + ], + 'ISERR' => [ + 'category' => Category::CATEGORY_INFORMATION, + 'functionCall' => [Functions::class, 'isErr'], + 'argumentCount' => '1', + ], + 'ISERROR' => [ + 'category' => Category::CATEGORY_INFORMATION, + 'functionCall' => [Functions::class, 'isError'], + 'argumentCount' => '1', + ], + 'ISEVEN' => [ + 'category' => Category::CATEGORY_INFORMATION, + 'functionCall' => [Functions::class, 'isEven'], + 'argumentCount' => '1', + ], + 'ISFORMULA' => [ + 'category' => Category::CATEGORY_INFORMATION, + 'functionCall' => [Functions::class, 'isFormula'], + 'argumentCount' => '1', + 'passCellReference' => true, + 'passByReference' => [true], + ], + 'ISLOGICAL' => [ + 'category' => Category::CATEGORY_INFORMATION, + 'functionCall' => [Functions::class, 'isLogical'], + 'argumentCount' => '1', + ], + 'ISNA' => [ + 'category' => Category::CATEGORY_INFORMATION, + 'functionCall' => [Functions::class, 'isNa'], + 'argumentCount' => '1', + ], + 'ISNONTEXT' => [ + 'category' => Category::CATEGORY_INFORMATION, + 'functionCall' => [Functions::class, 'isNonText'], + 'argumentCount' => '1', + ], + 'ISNUMBER' => [ + 'category' => Category::CATEGORY_INFORMATION, + 'functionCall' => [Functions::class, 'isNumber'], + 'argumentCount' => '1', + ], + 'ISO.CEILING' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '1,2', + ], + 'ISODD' => [ + 'category' => Category::CATEGORY_INFORMATION, + 'functionCall' => [Functions::class, 'isOdd'], + 'argumentCount' => '1', + ], + 'ISOWEEKNUM' => [ + 'category' => Category::CATEGORY_DATE_AND_TIME, + 'functionCall' => [DateTimeExcel\Week::class, 'isoWeekNumber'], + 'argumentCount' => '1', + ], + 'ISPMT' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\CashFlow\Constant\Periodic\Interest::class, 'schedulePayment'], + 'argumentCount' => '4', + ], + 'ISREF' => [ + 'category' => Category::CATEGORY_INFORMATION, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '1', + ], + 'ISTEXT' => [ + 'category' => Category::CATEGORY_INFORMATION, + 'functionCall' => [Functions::class, 'isText'], + 'argumentCount' => '1', + ], + 'ISTHAIDIGIT' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '?', + ], + 'JIS' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '1', + ], + 'KURT' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Deviations::class, 'kurtosis'], + 'argumentCount' => '1+', + ], + 'LARGE' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Size::class, 'large'], + 'argumentCount' => '2', + ], + 'LCM' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Lcm::class, 'evaluate'], + 'argumentCount' => '1+', + ], + 'LEFT' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [TextData\Extract::class, 'left'], + 'argumentCount' => '1,2', + ], + 'LEFTB' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [TextData\Extract::class, 'left'], + 'argumentCount' => '1,2', + ], + 'LEN' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [TextData\Text::class, 'length'], + 'argumentCount' => '1', + ], + 'LENB' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [TextData\Text::class, 'length'], + 'argumentCount' => '1', + ], + 'LINEST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Trends::class, 'LINEST'], + 'argumentCount' => '1-4', + ], + 'LN' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Logarithms::class, 'natural'], + 'argumentCount' => '1', + ], + 'LOG' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Logarithms::class, 'withBase'], + 'argumentCount' => '1,2', + ], + 'LOG10' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Logarithms::class, 'base10'], + 'argumentCount' => '1', + ], + 'LOGEST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Trends::class, 'LOGEST'], + 'argumentCount' => '1-4', + ], + 'LOGINV' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\LogNormal::class, 'inverse'], + 'argumentCount' => '3', + ], + 'LOGNORMDIST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\LogNormal::class, 'cumulative'], + 'argumentCount' => '3', + ], + 'LOGNORM.DIST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\LogNormal::class, 'distribution'], + 'argumentCount' => '4', + ], + 'LOGNORM.INV' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\LogNormal::class, 'inverse'], + 'argumentCount' => '3', + ], + 'LOOKUP' => [ + 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, + 'functionCall' => [LookupRef\Lookup::class, 'lookup'], + 'argumentCount' => '2,3', + ], + 'LOWER' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [TextData\CaseConvert::class, 'lower'], + 'argumentCount' => '1', + ], + 'MATCH' => [ + 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, + 'functionCall' => [LookupRef\ExcelMatch::class, 'MATCH'], + 'argumentCount' => '2,3', + ], + 'MAX' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Maximum::class, 'max'], + 'argumentCount' => '1+', + ], + 'MAXA' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Maximum::class, 'maxA'], + 'argumentCount' => '1+', + ], + 'MAXIFS' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Conditional::class, 'MAXIFS'], + 'argumentCount' => '3+', + ], + 'MDETERM' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\MatrixFunctions::class, 'determinant'], + 'argumentCount' => '1', + ], + 'MDURATION' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '5,6', + ], + 'MEDIAN' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Averages::class, 'median'], + 'argumentCount' => '1+', + ], + 'MEDIANIF' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '2+', + ], + 'MID' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [TextData\Extract::class, 'mid'], + 'argumentCount' => '3', + ], + 'MIDB' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [TextData\Extract::class, 'mid'], + 'argumentCount' => '3', + ], + 'MIN' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Minimum::class, 'min'], + 'argumentCount' => '1+', + ], + 'MINA' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Minimum::class, 'minA'], + 'argumentCount' => '1+', + ], + 'MINIFS' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Conditional::class, 'MINIFS'], + 'argumentCount' => '3+', + ], + 'MINUTE' => [ + 'category' => Category::CATEGORY_DATE_AND_TIME, + 'functionCall' => [DateTimeExcel\TimeParts::class, 'minute'], + 'argumentCount' => '1', + ], + 'MINVERSE' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\MatrixFunctions::class, 'inverse'], + 'argumentCount' => '1', + ], + 'MIRR' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\CashFlow\Variable\Periodic::class, 'modifiedRate'], + 'argumentCount' => '3', + ], + 'MMULT' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\MatrixFunctions::class, 'multiply'], + 'argumentCount' => '2', + ], + 'MOD' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Operations::class, 'mod'], + 'argumentCount' => '2', + ], + 'MODE' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Averages::class, 'mode'], + 'argumentCount' => '1+', + ], + 'MODE.MULT' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '1+', + ], + 'MODE.SNGL' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Averages::class, 'mode'], + 'argumentCount' => '1+', + ], + 'MONTH' => [ + 'category' => Category::CATEGORY_DATE_AND_TIME, + 'functionCall' => [DateTimeExcel\DateParts::class, 'month'], + 'argumentCount' => '1', + ], + 'MROUND' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Round::class, 'multiple'], + 'argumentCount' => '2', + ], + 'MULTINOMIAL' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Factorial::class, 'multinomial'], + 'argumentCount' => '1+', + ], + 'MUNIT' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\MatrixFunctions::class, 'identity'], + 'argumentCount' => '1', + ], + 'N' => [ + 'category' => Category::CATEGORY_INFORMATION, + 'functionCall' => [Functions::class, 'n'], + 'argumentCount' => '1', + ], + 'NA' => [ + 'category' => Category::CATEGORY_INFORMATION, + 'functionCall' => [Functions::class, 'NA'], + 'argumentCount' => '0', + ], + 'NEGBINOMDIST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\Binomial::class, 'negative'], + 'argumentCount' => '3', + ], + 'NEGBINOM.DIST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '4', + ], + 'NETWORKDAYS' => [ + 'category' => Category::CATEGORY_DATE_AND_TIME, + 'functionCall' => [DateTimeExcel\NetworkDays::class, 'count'], + 'argumentCount' => '2-3', + ], + 'NETWORKDAYS.INTL' => [ + 'category' => Category::CATEGORY_DATE_AND_TIME, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '2-4', + ], + 'NOMINAL' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\InterestRate::class, 'nominal'], + 'argumentCount' => '2', + ], + 'NORMDIST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\Normal::class, 'distribution'], + 'argumentCount' => '4', + ], + 'NORM.DIST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\Normal::class, 'distribution'], + 'argumentCount' => '4', + ], + 'NORMINV' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\Normal::class, 'inverse'], + 'argumentCount' => '3', + ], + 'NORM.INV' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\Normal::class, 'inverse'], + 'argumentCount' => '3', + ], + 'NORMSDIST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\StandardNormal::class, 'cumulative'], + 'argumentCount' => '1', + ], + 'NORM.S.DIST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\StandardNormal::class, 'distribution'], + 'argumentCount' => '1,2', + ], + 'NORMSINV' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\StandardNormal::class, 'inverse'], + 'argumentCount' => '1', + ], + 'NORM.S.INV' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\StandardNormal::class, 'inverse'], + 'argumentCount' => '1', + ], + 'NOT' => [ + 'category' => Category::CATEGORY_LOGICAL, + 'functionCall' => [Logical\Operations::class, 'NOT'], + 'argumentCount' => '1', + ], + 'NOW' => [ + 'category' => Category::CATEGORY_DATE_AND_TIME, + 'functionCall' => [DateTimeExcel\Current::class, 'now'], + 'argumentCount' => '0', + ], + 'NPER' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\CashFlow\Constant\Periodic::class, 'periods'], + 'argumentCount' => '3-5', + ], + 'NPV' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\CashFlow\Variable\Periodic::class, 'presentValue'], + 'argumentCount' => '2+', + ], + 'NUMBERSTRING' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '?', + ], + 'NUMBERVALUE' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [TextData\Format::class, 'NUMBERVALUE'], + 'argumentCount' => '1+', + ], + 'OCT2BIN' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\ConvertOctal::class, 'toBinary'], + 'argumentCount' => '1,2', + ], + 'OCT2DEC' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\ConvertOctal::class, 'toDecimal'], + 'argumentCount' => '1', + ], + 'OCT2HEX' => [ + 'category' => Category::CATEGORY_ENGINEERING, + 'functionCall' => [Engineering\ConvertOctal::class, 'toHex'], + 'argumentCount' => '1,2', + ], + 'ODD' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Round::class, 'odd'], + 'argumentCount' => '1', + ], + 'ODDFPRICE' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '8,9', + ], + 'ODDFYIELD' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '8,9', + ], + 'ODDLPRICE' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '7,8', + ], + 'ODDLYIELD' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '7,8', + ], + 'OFFSET' => [ + 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, + 'functionCall' => [LookupRef\Offset::class, 'OFFSET'], + 'argumentCount' => '3-5', + 'passCellReference' => true, + 'passByReference' => [true], + ], + 'OR' => [ + 'category' => Category::CATEGORY_LOGICAL, + 'functionCall' => [Logical\Operations::class, 'logicalOr'], + 'argumentCount' => '1+', + ], + 'PDURATION' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\CashFlow\Single::class, 'periods'], + 'argumentCount' => '3', + ], + 'PEARSON' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Trends::class, 'CORREL'], + 'argumentCount' => '2', + ], + 'PERCENTILE' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Percentiles::class, 'PERCENTILE'], + 'argumentCount' => '2', + ], + 'PERCENTILE.EXC' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '2', + ], + 'PERCENTILE.INC' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Percentiles::class, 'PERCENTILE'], + 'argumentCount' => '2', + ], + 'PERCENTRANK' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Percentiles::class, 'PERCENTRANK'], + 'argumentCount' => '2,3', + ], + 'PERCENTRANK.EXC' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '2,3', + ], + 'PERCENTRANK.INC' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Percentiles::class, 'PERCENTRANK'], + 'argumentCount' => '2,3', + ], + 'PERMUT' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Permutations::class, 'PERMUT'], + 'argumentCount' => '2', + ], + 'PERMUTATIONA' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Permutations::class, 'PERMUTATIONA'], + 'argumentCount' => '2', + ], + 'PHONETIC' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '1', + ], + 'PHI' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '1', + ], + 'PI' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => 'pi', + 'argumentCount' => '0', + ], + 'PMT' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\CashFlow\Constant\Periodic\Payments::class, 'annuity'], + 'argumentCount' => '3-5', + ], + 'POISSON' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\Poisson::class, 'distribution'], + 'argumentCount' => '3', + ], + 'POISSON.DIST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\Poisson::class, 'distribution'], + 'argumentCount' => '3', + ], + 'POWER' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Operations::class, 'power'], + 'argumentCount' => '2', + ], + 'PPMT' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\CashFlow\Constant\Periodic\Payments::class, 'interestPayment'], + 'argumentCount' => '4-6', + ], + 'PRICE' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\Securities\Price::class, 'price'], + 'argumentCount' => '6,7', + ], + 'PRICEDISC' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\Securities\Price::class, 'priceDiscounted'], + 'argumentCount' => '4,5', + ], + 'PRICEMAT' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\Securities\Price::class, 'priceAtMaturity'], + 'argumentCount' => '5,6', + ], + 'PROB' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '3,4', + ], + 'PRODUCT' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Operations::class, 'product'], + 'argumentCount' => '1+', + ], + 'PROPER' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [TextData\CaseConvert::class, 'proper'], + 'argumentCount' => '1', + ], + 'PV' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\CashFlow\Constant\Periodic::class, 'presentValue'], + 'argumentCount' => '3-5', + ], + 'QUARTILE' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Percentiles::class, 'QUARTILE'], + 'argumentCount' => '2', + ], + 'QUARTILE.EXC' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '2', + ], + 'QUARTILE.INC' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Percentiles::class, 'QUARTILE'], + 'argumentCount' => '2', + ], + 'QUOTIENT' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Operations::class, 'quotient'], + 'argumentCount' => '2', + ], + 'RADIANS' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Angle::class, 'toRadians'], + 'argumentCount' => '1', + ], + 'RAND' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Random::class, 'rand'], + 'argumentCount' => '0', + ], + 'RANDARRAY' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '0-5', + ], + 'RANDBETWEEN' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Random::class, 'randBetween'], + 'argumentCount' => '2', + ], + 'RANK' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Percentiles::class, 'RANK'], + 'argumentCount' => '2,3', + ], + 'RANK.AVG' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '2,3', + ], + 'RANK.EQ' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Percentiles::class, 'RANK'], + 'argumentCount' => '2,3', + ], + 'RATE' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\CashFlow\Constant\Periodic\Interest::class, 'rate'], + 'argumentCount' => '3-6', + ], + 'RECEIVED' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\Securities\Price::class, 'received'], + 'argumentCount' => '4-5', + ], + 'REPLACE' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [TextData\Replace::class, 'replace'], + 'argumentCount' => '4', + ], + 'REPLACEB' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [TextData\Replace::class, 'replace'], + 'argumentCount' => '4', + ], + 'REPT' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [TextData\Concatenate::class, 'builtinREPT'], + 'argumentCount' => '2', + ], + 'RIGHT' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [TextData\Extract::class, 'right'], + 'argumentCount' => '1,2', + ], + 'RIGHTB' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [TextData\Extract::class, 'right'], + 'argumentCount' => '1,2', + ], + 'ROMAN' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Roman::class, 'evaluate'], + 'argumentCount' => '1,2', + ], + 'ROUND' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Round::class, 'round'], + 'argumentCount' => '2', + ], + 'ROUNDBAHTDOWN' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '?', + ], + 'ROUNDBAHTUP' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '?', + ], + 'ROUNDDOWN' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Round::class, 'down'], + 'argumentCount' => '2', + ], + 'ROUNDUP' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Round::class, 'up'], + 'argumentCount' => '2', + ], + 'ROW' => [ + 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, + 'functionCall' => [LookupRef\RowColumnInformation::class, 'ROW'], + 'argumentCount' => '-1', + 'passCellReference' => true, + 'passByReference' => [true], + ], + 'ROWS' => [ + 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, + 'functionCall' => [LookupRef\RowColumnInformation::class, 'ROWS'], + 'argumentCount' => '1', + ], + 'RRI' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\CashFlow\Single::class, 'interestRate'], + 'argumentCount' => '3', + ], + 'RSQ' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Trends::class, 'RSQ'], + 'argumentCount' => '2', + ], + 'RTD' => [ + 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '1+', + ], + 'SEARCH' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [TextData\Search::class, 'insensitive'], + 'argumentCount' => '2,3', + ], + 'SEARCHB' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [TextData\Search::class, 'insensitive'], + 'argumentCount' => '2,3', + ], + 'SEC' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Trig\Secant::class, 'sec'], + 'argumentCount' => '1', + ], + 'SECH' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Trig\Secant::class, 'sech'], + 'argumentCount' => '1', + ], + 'SECOND' => [ + 'category' => Category::CATEGORY_DATE_AND_TIME, + 'functionCall' => [DateTimeExcel\TimeParts::class, 'second'], + 'argumentCount' => '1', + ], + 'SEQUENCE' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '2', + ], + 'SERIESSUM' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\SeriesSum::class, 'evaluate'], + 'argumentCount' => '4', + ], + 'SHEET' => [ + 'category' => Category::CATEGORY_INFORMATION, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '0,1', + ], + 'SHEETS' => [ + 'category' => Category::CATEGORY_INFORMATION, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '0,1', + ], + 'SIGN' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Sign::class, 'evaluate'], + 'argumentCount' => '1', + ], + 'SIN' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Trig\Sine::class, 'sin'], + 'argumentCount' => '1', + ], + 'SINH' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Trig\Sine::class, 'sinh'], + 'argumentCount' => '1', + ], + 'SKEW' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Deviations::class, 'skew'], + 'argumentCount' => '1+', + ], + 'SKEW.P' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '1+', + ], + 'SLN' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\Depreciation::class, 'SLN'], + 'argumentCount' => '3', + ], + 'SLOPE' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Trends::class, 'SLOPE'], + 'argumentCount' => '2', + ], + 'SMALL' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Size::class, 'small'], + 'argumentCount' => '2', + ], + 'SORT' => [ + 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '1+', + ], + 'SORTBY' => [ + 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '2+', + ], + 'SQRT' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Sqrt::class, 'sqrt'], + 'argumentCount' => '1', + ], + 'SQRTPI' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Sqrt::class, 'pi'], + 'argumentCount' => '1', + ], + 'STANDARDIZE' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Standardize::class, 'execute'], + 'argumentCount' => '3', + ], + 'STDEV' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\StandardDeviations::class, 'STDEV'], + 'argumentCount' => '1+', + ], + 'STDEV.S' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\StandardDeviations::class, 'STDEV'], + 'argumentCount' => '1+', + ], + 'STDEV.P' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\StandardDeviations::class, 'STDEVP'], + 'argumentCount' => '1+', + ], + 'STDEVA' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\StandardDeviations::class, 'STDEVA'], + 'argumentCount' => '1+', + ], + 'STDEVP' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\StandardDeviations::class, 'STDEVP'], + 'argumentCount' => '1+', + ], + 'STDEVPA' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\StandardDeviations::class, 'STDEVPA'], + 'argumentCount' => '1+', + ], + 'STEYX' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Trends::class, 'STEYX'], + 'argumentCount' => '2', + ], + 'SUBSTITUTE' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [TextData\Replace::class, 'substitute'], + 'argumentCount' => '3,4', + ], + 'SUBTOTAL' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Subtotal::class, 'evaluate'], + 'argumentCount' => '2+', + 'passCellReference' => true, + ], + 'SUM' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Sum::class, 'sumErroringStrings'], + 'argumentCount' => '1+', + ], + 'SUMIF' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [Statistical\Conditional::class, 'SUMIF'], + 'argumentCount' => '2,3', + ], + 'SUMIFS' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [Statistical\Conditional::class, 'SUMIFS'], + 'argumentCount' => '3+', + ], + 'SUMPRODUCT' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Sum::class, 'product'], + 'argumentCount' => '1+', + ], + 'SUMSQ' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\SumSquares::class, 'sumSquare'], + 'argumentCount' => '1+', + ], + 'SUMX2MY2' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\SumSquares::class, 'sumXSquaredMinusYSquared'], + 'argumentCount' => '2', + ], + 'SUMX2PY2' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\SumSquares::class, 'sumXSquaredPlusYSquared'], + 'argumentCount' => '2', + ], + 'SUMXMY2' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\SumSquares::class, 'sumXMinusYSquared'], + 'argumentCount' => '2', + ], + 'SWITCH' => [ + 'category' => Category::CATEGORY_LOGICAL, + 'functionCall' => [Logical\Conditional::class, 'statementSwitch'], + 'argumentCount' => '3+', + ], + 'SYD' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\Depreciation::class, 'SYD'], + 'argumentCount' => '4', + ], + 'T' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [TextData\Text::class, 'test'], + 'argumentCount' => '1', + ], + 'TAN' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Trig\Tangent::class, 'tan'], + 'argumentCount' => '1', + ], + 'TANH' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Trig\Tangent::class, 'tanh'], + 'argumentCount' => '1', + ], + 'TBILLEQ' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\TreasuryBill::class, 'bondEquivalentYield'], + 'argumentCount' => '3', + ], + 'TBILLPRICE' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\TreasuryBill::class, 'price'], + 'argumentCount' => '3', + ], + 'TBILLYIELD' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\TreasuryBill::class, 'yield'], + 'argumentCount' => '3', + ], + 'TDIST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\StudentT::class, 'distribution'], + 'argumentCount' => '3', + ], + 'T.DIST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '3', + ], + 'T.DIST.2T' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '2', + ], + 'T.DIST.RT' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '2', + ], + 'TEXT' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [TextData\Format::class, 'TEXTFORMAT'], + 'argumentCount' => '2', + ], + 'TEXTJOIN' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [TextData\Concatenate::class, 'TEXTJOIN'], + 'argumentCount' => '3+', + ], + 'THAIDAYOFWEEK' => [ + 'category' => Category::CATEGORY_DATE_AND_TIME, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '?', + ], + 'THAIDIGIT' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '?', + ], + 'THAIMONTHOFYEAR' => [ + 'category' => Category::CATEGORY_DATE_AND_TIME, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '?', + ], + 'THAINUMSOUND' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '?', + ], + 'THAINUMSTRING' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '?', + ], + 'THAISTRINGLENGTH' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '?', + ], + 'THAIYEAR' => [ + 'category' => Category::CATEGORY_DATE_AND_TIME, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '?', + ], + 'TIME' => [ + 'category' => Category::CATEGORY_DATE_AND_TIME, + 'functionCall' => [DateTimeExcel\Time::class, 'fromHMS'], + 'argumentCount' => '3', + ], + 'TIMEVALUE' => [ + 'category' => Category::CATEGORY_DATE_AND_TIME, + 'functionCall' => [DateTimeExcel\TimeValue::class, 'fromString'], + 'argumentCount' => '1', + ], + 'TINV' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\StudentT::class, 'inverse'], + 'argumentCount' => '2', + ], + 'T.INV' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\StudentT::class, 'inverse'], + 'argumentCount' => '2', + ], + 'T.INV.2T' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '2', + ], + 'TODAY' => [ + 'category' => Category::CATEGORY_DATE_AND_TIME, + 'functionCall' => [DateTimeExcel\Current::class, 'today'], + 'argumentCount' => '0', + ], + 'TRANSPOSE' => [ + 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, + 'functionCall' => [LookupRef\Matrix::class, 'transpose'], + 'argumentCount' => '1', + ], + 'TREND' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Trends::class, 'TREND'], + 'argumentCount' => '1-4', + ], + 'TRIM' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [TextData\Trim::class, 'spaces'], + 'argumentCount' => '1', + ], + 'TRIMMEAN' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Averages\Mean::class, 'trim'], + 'argumentCount' => '2', + ], + 'TRUE' => [ + 'category' => Category::CATEGORY_LOGICAL, + 'functionCall' => [Logical\Boolean::class, 'TRUE'], + 'argumentCount' => '0', + ], + 'TRUNC' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Trunc::class, 'evaluate'], + 'argumentCount' => '1,2', + ], + 'TTEST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '4', + ], + 'T.TEST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '4', + ], + 'TYPE' => [ + 'category' => Category::CATEGORY_INFORMATION, + 'functionCall' => [Functions::class, 'TYPE'], + 'argumentCount' => '1', + ], + 'UNICHAR' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [TextData\CharacterConvert::class, 'character'], + 'argumentCount' => '1', + ], + 'UNICODE' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [TextData\CharacterConvert::class, 'code'], + 'argumentCount' => '1', + ], + 'UNIQUE' => [ + 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '1+', + ], + 'UPPER' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [TextData\CaseConvert::class, 'upper'], + 'argumentCount' => '1', + ], + 'USDOLLAR' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\Dollar::class, 'format'], + 'argumentCount' => '2', + ], + 'VALUE' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [TextData\Format::class, 'VALUE'], + 'argumentCount' => '1', + ], + 'VALUETOTEXT' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '?', + ], + 'VAR' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Variances::class, 'VAR'], + 'argumentCount' => '1+', + ], + 'VAR.P' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Variances::class, 'VARP'], + 'argumentCount' => '1+', + ], + 'VAR.S' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Variances::class, 'VAR'], + 'argumentCount' => '1+', + ], + 'VARA' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Variances::class, 'VARA'], + 'argumentCount' => '1+', + ], + 'VARP' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Variances::class, 'VARP'], + 'argumentCount' => '1+', + ], + 'VARPA' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Variances::class, 'VARPA'], + 'argumentCount' => '1+', + ], + 'VDB' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '5-7', + ], + 'VLOOKUP' => [ + 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, + 'functionCall' => [LookupRef\VLookup::class, 'lookup'], + 'argumentCount' => '3,4', + ], + 'WEBSERVICE' => [ + 'category' => Category::CATEGORY_WEB, + 'functionCall' => [Web\Service::class, 'webService'], + 'argumentCount' => '1', + ], + 'WEEKDAY' => [ + 'category' => Category::CATEGORY_DATE_AND_TIME, + 'functionCall' => [DateTimeExcel\Week::class, 'day'], + 'argumentCount' => '1,2', + ], + 'WEEKNUM' => [ + 'category' => Category::CATEGORY_DATE_AND_TIME, + 'functionCall' => [DateTimeExcel\Week::class, 'number'], + 'argumentCount' => '1,2', + ], + 'WEIBULL' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\Weibull::class, 'distribution'], + 'argumentCount' => '4', + ], + 'WEIBULL.DIST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\Weibull::class, 'distribution'], + 'argumentCount' => '4', + ], + 'WORKDAY' => [ + 'category' => Category::CATEGORY_DATE_AND_TIME, + 'functionCall' => [DateTimeExcel\WorkDay::class, 'date'], + 'argumentCount' => '2-3', + ], + 'WORKDAY.INTL' => [ + 'category' => Category::CATEGORY_DATE_AND_TIME, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '2-4', + ], + 'XIRR' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\CashFlow\Variable\NonPeriodic::class, 'rate'], + 'argumentCount' => '2,3', + ], + 'XLOOKUP' => [ + 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '3-6', + ], + 'XNPV' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\CashFlow\Variable\NonPeriodic::class, 'presentValue'], + 'argumentCount' => '3', + ], + 'XMATCH' => [ + 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '2,3', + ], + 'XOR' => [ + 'category' => Category::CATEGORY_LOGICAL, + 'functionCall' => [Logical\Operations::class, 'logicalXor'], + 'argumentCount' => '1+', + ], + 'YEAR' => [ + 'category' => Category::CATEGORY_DATE_AND_TIME, + 'functionCall' => [DateTimeExcel\DateParts::class, 'year'], + 'argumentCount' => '1', + ], + 'YEARFRAC' => [ + 'category' => Category::CATEGORY_DATE_AND_TIME, + 'functionCall' => [DateTimeExcel\YearFrac::class, 'fraction'], + 'argumentCount' => '2,3', + ], + 'YIELD' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '6,7', + ], + 'YIELDDISC' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\Securities\Yields::class, 'yieldDiscounted'], + 'argumentCount' => '4,5', + ], + 'YIELDMAT' => [ + 'category' => Category::CATEGORY_FINANCIAL, + 'functionCall' => [Financial\Securities\Yields::class, 'yieldAtMaturity'], + 'argumentCount' => '5,6', + ], + 'ZTEST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\StandardNormal::class, 'zTest'], + 'argumentCount' => '2-3', + ], + 'Z.TEST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\StandardNormal::class, 'zTest'], + 'argumentCount' => '2-3', + ], + ]; + + // Internal functions used for special control purposes + private static $controlFunctions = [ + 'MKMATRIX' => [ + 'argumentCount' => '*', + 'functionCall' => [Internal\MakeMatrix::class, 'make'], + ], + 'NAME.ERROR' => [ + 'argumentCount' => '*', + 'functionCall' => [Functions::class, 'NAME'], + ], + 'WILDCARDMATCH' => [ + 'argumentCount' => '2', + 'functionCall' => [Internal\WildcardMatch::class, 'compare'], + ], + ]; + + public function __construct(?Spreadsheet $spreadsheet = null) + { + $this->delta = 1 * 10 ** (0 - ini_get('precision')); + + $this->spreadsheet = $spreadsheet; + $this->cyclicReferenceStack = new CyclicReferenceStack(); + $this->debugLog = new Logger($this->cyclicReferenceStack); + self::$referenceHelper = ReferenceHelper::getInstance(); + } + + private static function loadLocales(): void + { + $localeFileDirectory = __DIR__ . '/locale/'; + foreach (glob($localeFileDirectory . '*', GLOB_ONLYDIR) as $filename) { + $filename = substr($filename, strlen($localeFileDirectory)); + if ($filename != 'en') { + self::$validLocaleLanguages[] = $filename; + } + } + } + + /** + * Get an instance of this class. + * + * @param ?Spreadsheet $spreadsheet Injected spreadsheet for working with a PhpSpreadsheet Spreadsheet object, + * or NULL to create a standalone calculation engine + */ + public static function getInstance(?Spreadsheet $spreadsheet = null): self + { + if ($spreadsheet !== null) { + $instance = $spreadsheet->getCalculationEngine(); + if (isset($instance)) { + return $instance; + } + } + + if (!isset(self::$instance) || (self::$instance === null)) { + self::$instance = new self(); + } + + return self::$instance; + } + + /** + * Flush the calculation cache for any existing instance of this class + * but only if a Calculation instance exists. + */ + public function flushInstance(): void + { + $this->clearCalculationCache(); + $this->clearBranchStore(); + } + + /** + * Get the Logger for this calculation engine instance. + * + * @return Logger + */ + public function getDebugLog() + { + return $this->debugLog; + } + + /** + * __clone implementation. Cloning should not be allowed in a Singleton! + */ + final public function __clone() + { + throw new Exception('Cloning the calculation engine is not allowed!'); + } + + /** + * Return the locale-specific translation of TRUE. + * + * @return string locale-specific translation of TRUE + */ + public static function getTRUE(): string + { + return self::$localeBoolean['TRUE']; + } + + /** + * Return the locale-specific translation of FALSE. + * + * @return string locale-specific translation of FALSE + */ + public static function getFALSE(): string + { + return self::$localeBoolean['FALSE']; + } + + /** + * Set the Array Return Type (Array or Value of first element in the array). + * + * @param string $returnType Array return type + * + * @return bool Success or failure + */ + public static function setArrayReturnType($returnType) + { + if ( + ($returnType == self::RETURN_ARRAY_AS_VALUE) || + ($returnType == self::RETURN_ARRAY_AS_ERROR) || + ($returnType == self::RETURN_ARRAY_AS_ARRAY) + ) { + self::$returnArrayAsType = $returnType; + + return true; + } + + return false; + } + + /** + * Return the Array Return Type (Array or Value of first element in the array). + * + * @return string $returnType Array return type + */ + public static function getArrayReturnType() + { + return self::$returnArrayAsType; + } + + /** + * Is calculation caching enabled? + * + * @return bool + */ + public function getCalculationCacheEnabled() + { + return $this->calculationCacheEnabled; + } + + /** + * Enable/disable calculation cache. + * + * @param bool $calculationCacheEnabled + */ + public function setCalculationCacheEnabled($calculationCacheEnabled): void + { + $this->calculationCacheEnabled = $calculationCacheEnabled; + $this->clearCalculationCache(); + } + + /** + * Enable calculation cache. + */ + public function enableCalculationCache(): void + { + $this->setCalculationCacheEnabled(true); + } + + /** + * Disable calculation cache. + */ + public function disableCalculationCache(): void + { + $this->setCalculationCacheEnabled(false); + } + + /** + * Clear calculation cache. + */ + public function clearCalculationCache(): void + { + $this->calculationCache = []; + } + + /** + * Clear calculation cache for a specified worksheet. + * + * @param string $worksheetName + */ + public function clearCalculationCacheForWorksheet($worksheetName): void + { + if (isset($this->calculationCache[$worksheetName])) { + unset($this->calculationCache[$worksheetName]); + } + } + + /** + * Rename calculation cache for a specified worksheet. + * + * @param string $fromWorksheetName + * @param string $toWorksheetName + */ + public function renameCalculationCacheForWorksheet($fromWorksheetName, $toWorksheetName): void + { + if (isset($this->calculationCache[$fromWorksheetName])) { + $this->calculationCache[$toWorksheetName] = &$this->calculationCache[$fromWorksheetName]; + unset($this->calculationCache[$fromWorksheetName]); + } + } + + /** + * Enable/disable calculation cache. + * + * @param mixed $enabled + */ + public function setBranchPruningEnabled($enabled): void + { + $this->branchPruningEnabled = $enabled; + } + + public function enableBranchPruning(): void + { + $this->setBranchPruningEnabled(true); + } + + public function disableBranchPruning(): void + { + $this->setBranchPruningEnabled(false); + } + + public function clearBranchStore(): void + { + $this->branchStoreKeyCounter = 0; + } + + /** + * Get the currently defined locale code. + * + * @return string + */ + public function getLocale() + { + return self::$localeLanguage; + } + + private function getLocaleFile(string $localeDir, string $locale, string $language, string $file): string + { + $localeFileName = $localeDir . str_replace('_', DIRECTORY_SEPARATOR, $locale) . + DIRECTORY_SEPARATOR . $file; + if (!file_exists($localeFileName)) { + // If there isn't a locale specific file, look for a language specific file + $localeFileName = $localeDir . $language . DIRECTORY_SEPARATOR . $file; + if (!file_exists($localeFileName)) { + throw new Exception('Locale file not found'); + } + } + + return $localeFileName; + } + + /** + * Set the locale code. + * + * @param string $locale The locale to use for formula translation, eg: 'en_us' + * + * @return bool + */ + public function setLocale(string $locale) + { + // Identify our locale and language + $language = $locale = strtolower($locale); + if (strpos($locale, '_') !== false) { + [$language] = explode('_', $locale); + } + if (count(self::$validLocaleLanguages) == 1) { + self::loadLocales(); + } + + // Test whether we have any language data for this language (any locale) + if (in_array($language, self::$validLocaleLanguages)) { + // initialise language/locale settings + self::$localeFunctions = []; + self::$localeArgumentSeparator = ','; + self::$localeBoolean = ['TRUE' => 'TRUE', 'FALSE' => 'FALSE', 'NULL' => 'NULL']; + + // Default is US English, if user isn't requesting US english, then read the necessary data from the locale files + if ($locale !== 'en_us') { + $localeDir = implode(DIRECTORY_SEPARATOR, [__DIR__, 'locale', null]); + // Search for a file with a list of function names for locale + try { + $functionNamesFile = $this->getLocaleFile($localeDir, $locale, $language, 'functions'); + } catch (Exception $e) { + return false; + } + + // Retrieve the list of locale or language specific function names + $localeFunctions = file($functionNamesFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + foreach ($localeFunctions as $localeFunction) { + [$localeFunction] = explode('##', $localeFunction); // Strip out comments + if (strpos($localeFunction, '=') !== false) { + [$fName, $lfName] = array_map('trim', explode('=', $localeFunction)); + if ((isset(self::$phpSpreadsheetFunctions[$fName])) && ($lfName != '') && ($fName != $lfName)) { + self::$localeFunctions[$fName] = $lfName; + } + } + } + // Default the TRUE and FALSE constants to the locale names of the TRUE() and FALSE() functions + if (isset(self::$localeFunctions['TRUE'])) { + self::$localeBoolean['TRUE'] = self::$localeFunctions['TRUE']; + } + if (isset(self::$localeFunctions['FALSE'])) { + self::$localeBoolean['FALSE'] = self::$localeFunctions['FALSE']; + } + + try { + $configFile = $this->getLocaleFile($localeDir, $locale, $language, 'config'); + } catch (Exception $e) { + return false; + } + + $localeSettings = file($configFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + foreach ($localeSettings as $localeSetting) { + [$localeSetting] = explode('##', $localeSetting); // Strip out comments + if (strpos($localeSetting, '=') !== false) { + [$settingName, $settingValue] = array_map('trim', explode('=', $localeSetting)); + $settingName = strtoupper($settingName); + if ($settingValue !== '') { + switch ($settingName) { + case 'ARGUMENTSEPARATOR': + self::$localeArgumentSeparator = $settingValue; + + break; + } + } + } + } + } + + self::$functionReplaceFromExcel = self::$functionReplaceToExcel = + self::$functionReplaceFromLocale = self::$functionReplaceToLocale = null; + self::$localeLanguage = $locale; + + return true; + } + + return false; + } + + /** + * @param string $fromSeparator + * @param string $toSeparator + * @param string $formula + * @param bool $inBraces + * + * @return string + */ + public static function translateSeparator($fromSeparator, $toSeparator, $formula, &$inBraces) + { + $strlen = mb_strlen($formula); + for ($i = 0; $i < $strlen; ++$i) { + $chr = mb_substr($formula, $i, 1); + switch ($chr) { + case self::FORMULA_OPEN_FUNCTION_BRACE: + $inBraces = true; + + break; + case self::FORMULA_CLOSE_FUNCTION_BRACE: + $inBraces = false; + + break; + case $fromSeparator: + if (!$inBraces) { + $formula = mb_substr($formula, 0, $i) . $toSeparator . mb_substr($formula, $i + 1); + } + } + } + + return $formula; + } + + /** + * @param string[] $from + * @param string[] $to + * @param string $formula + * @param string $fromSeparator + * @param string $toSeparator + * + * @return string + */ + private static function translateFormula(array $from, array $to, $formula, $fromSeparator, $toSeparator) + { + // Convert any Excel function names to the required language + if (self::$localeLanguage !== 'en_us') { + $inBraces = false; + // If there is the possibility of braces within a quoted string, then we don't treat those as matrix indicators + if (strpos($formula, self::FORMULA_STRING_QUOTE) !== false) { + // So instead we skip replacing in any quoted strings by only replacing in every other array element after we've exploded + // the formula + $temp = explode(self::FORMULA_STRING_QUOTE, $formula); + $i = false; + foreach ($temp as &$value) { + // Only count/replace in alternating array entries + if ($i = !$i) { + $value = preg_replace($from, $to, $value); + $value = self::translateSeparator($fromSeparator, $toSeparator, $value, $inBraces); + } + } + unset($value); + // Then rebuild the formula string + $formula = implode(self::FORMULA_STRING_QUOTE, $temp); + } else { + // If there's no quoted strings, then we do a simple count/replace + $formula = preg_replace($from, $to, $formula); + $formula = self::translateSeparator($fromSeparator, $toSeparator, $formula, $inBraces); + } + } + + return $formula; + } + + private static $functionReplaceFromExcel; + + private static $functionReplaceToLocale; + + public function _translateFormulaToLocale($formula) + { + if (self::$functionReplaceFromExcel === null) { + self::$functionReplaceFromExcel = []; + foreach (array_keys(self::$localeFunctions) as $excelFunctionName) { + self::$functionReplaceFromExcel[] = '/(@?[^\w\.])' . preg_quote($excelFunctionName, '/') . '([\s]*\()/Ui'; + } + foreach (array_keys(self::$localeBoolean) as $excelBoolean) { + self::$functionReplaceFromExcel[] = '/(@?[^\w\.])' . preg_quote($excelBoolean, '/') . '([^\w\.])/Ui'; + } + } + + if (self::$functionReplaceToLocale === null) { + self::$functionReplaceToLocale = []; + foreach (self::$localeFunctions as $localeFunctionName) { + self::$functionReplaceToLocale[] = '$1' . trim($localeFunctionName) . '$2'; + } + foreach (self::$localeBoolean as $localeBoolean) { + self::$functionReplaceToLocale[] = '$1' . trim($localeBoolean) . '$2'; + } + } + + return self::translateFormula(self::$functionReplaceFromExcel, self::$functionReplaceToLocale, $formula, ',', self::$localeArgumentSeparator); + } + + private static $functionReplaceFromLocale; + + private static $functionReplaceToExcel; + + public function _translateFormulaToEnglish($formula) + { + if (self::$functionReplaceFromLocale === null) { + self::$functionReplaceFromLocale = []; + foreach (self::$localeFunctions as $localeFunctionName) { + self::$functionReplaceFromLocale[] = '/(@?[^\w\.])' . preg_quote($localeFunctionName, '/') . '([\s]*\()/Ui'; + } + foreach (self::$localeBoolean as $excelBoolean) { + self::$functionReplaceFromLocale[] = '/(@?[^\w\.])' . preg_quote($excelBoolean, '/') . '([^\w\.])/Ui'; + } + } + + if (self::$functionReplaceToExcel === null) { + self::$functionReplaceToExcel = []; + foreach (array_keys(self::$localeFunctions) as $excelFunctionName) { + self::$functionReplaceToExcel[] = '$1' . trim($excelFunctionName) . '$2'; + } + foreach (array_keys(self::$localeBoolean) as $excelBoolean) { + self::$functionReplaceToExcel[] = '$1' . trim($excelBoolean) . '$2'; + } + } + + return self::translateFormula(self::$functionReplaceFromLocale, self::$functionReplaceToExcel, $formula, self::$localeArgumentSeparator, ','); + } + + public static function localeFunc($function) + { + if (self::$localeLanguage !== 'en_us') { + $functionName = trim($function, '('); + if (isset(self::$localeFunctions[$functionName])) { + $brace = ($functionName != $function); + $function = self::$localeFunctions[$functionName]; + if ($brace) { + $function .= '('; + } + } + } + + return $function; + } + + /** + * Wrap string values in quotes. + * + * @param mixed $value + * + * @return mixed + */ + public static function wrapResult($value) + { + if (is_string($value)) { + // Error values cannot be "wrapped" + if (preg_match('/^' . self::CALCULATION_REGEXP_ERROR . '$/i', $value, $match)) { + // Return Excel errors "as is" + return $value; + } + + // Return strings wrapped in quotes + return self::FORMULA_STRING_QUOTE . $value . self::FORMULA_STRING_QUOTE; + } elseif ((is_float($value)) && ((is_nan($value)) || (is_infinite($value)))) { + // Convert numeric errors to NaN error + return Functions::NAN(); + } + + return $value; + } + + /** + * Remove quotes used as a wrapper to identify string values. + * + * @param mixed $value + * + * @return mixed + */ + public static function unwrapResult($value) + { + if (is_string($value)) { + if ((isset($value[0])) && ($value[0] == self::FORMULA_STRING_QUOTE) && (substr($value, -1) == self::FORMULA_STRING_QUOTE)) { + return substr($value, 1, -1); + } + // Convert numeric errors to NAN error + } elseif ((is_float($value)) && ((is_nan($value)) || (is_infinite($value)))) { + return Functions::NAN(); + } + + return $value; + } + + /** + * Calculate cell value (using formula from a cell ID) + * Retained for backward compatibility. + * + * @param Cell $cell Cell to calculate + * + * @return mixed + */ + public function calculate(?Cell $cell = null) + { + try { + return $this->calculateCellValue($cell); + } catch (\Exception $e) { + throw new Exception($e->getMessage()); + } + } + + /** + * Calculate the value of a cell formula. + * + * @param Cell $cell Cell to calculate + * @param bool $resetLog Flag indicating whether the debug log should be reset or not + * + * @return mixed + */ + public function calculateCellValue(?Cell $cell = null, $resetLog = true) + { + if ($cell === null) { + return null; + } + + $returnArrayAsType = self::$returnArrayAsType; + if ($resetLog) { + // Initialise the logging settings if requested + $this->formulaError = null; + $this->debugLog->clearLog(); + $this->cyclicReferenceStack->clear(); + $this->cyclicFormulaCounter = 1; + + self::$returnArrayAsType = self::RETURN_ARRAY_AS_ARRAY; + } + + // Execute the calculation for the cell formula + $this->cellStack[] = [ + 'sheet' => $cell->getWorksheet()->getTitle(), + 'cell' => $cell->getCoordinate(), + ]; + + try { + $result = self::unwrapResult($this->_calculateFormulaValue($cell->getValue(), $cell->getCoordinate(), $cell)); + $cellAddress = array_pop($this->cellStack); + $this->spreadsheet->getSheetByName($cellAddress['sheet'])->getCell($cellAddress['cell']); + } catch (\Exception $e) { + $cellAddress = array_pop($this->cellStack); + $this->spreadsheet->getSheetByName($cellAddress['sheet'])->getCell($cellAddress['cell']); + + throw new Exception($e->getMessage()); + } + + if ((is_array($result)) && (self::$returnArrayAsType != self::RETURN_ARRAY_AS_ARRAY)) { + self::$returnArrayAsType = $returnArrayAsType; + $testResult = Functions::flattenArray($result); + if (self::$returnArrayAsType == self::RETURN_ARRAY_AS_ERROR) { + return Functions::VALUE(); + } + // If there's only a single cell in the array, then we allow it + if (count($testResult) != 1) { + // If keys are numeric, then it's a matrix result rather than a cell range result, so we permit it + $r = array_keys($result); + $r = array_shift($r); + if (!is_numeric($r)) { + return Functions::VALUE(); + } + if (is_array($result[$r])) { + $c = array_keys($result[$r]); + $c = array_shift($c); + if (!is_numeric($c)) { + return Functions::VALUE(); + } + } + } + $result = array_shift($testResult); + } + self::$returnArrayAsType = $returnArrayAsType; + + if ($result === null && $cell->getWorksheet()->getSheetView()->getShowZeros()) { + return 0; + } elseif ((is_float($result)) && ((is_nan($result)) || (is_infinite($result)))) { + return Functions::NAN(); + } + + return $result; + } + + /** + * Validate and parse a formula string. + * + * @param string $formula Formula to parse + * + * @return array|bool + */ + public function parseFormula($formula) + { + // Basic validation that this is indeed a formula + // We return an empty array if not + $formula = trim($formula); + if ((!isset($formula[0])) || ($formula[0] != '=')) { + return []; + } + $formula = ltrim(substr($formula, 1)); + if (!isset($formula[0])) { + return []; + } + + // Parse the formula and return the token stack + return $this->internalParseFormula($formula); + } + + /** + * Calculate the value of a formula. + * + * @param string $formula Formula to parse + * @param string $cellID Address of the cell to calculate + * @param Cell $cell Cell to calculate + * + * @return mixed + */ + public function calculateFormula($formula, $cellID = null, ?Cell $cell = null) + { + // Initialise the logging settings + $this->formulaError = null; + $this->debugLog->clearLog(); + $this->cyclicReferenceStack->clear(); + + $resetCache = $this->getCalculationCacheEnabled(); + if ($this->spreadsheet !== null && $cellID === null && $cell === null) { + $cellID = 'A1'; + $cell = $this->spreadsheet->getActiveSheet()->getCell($cellID); + } else { + // Disable calculation cacheing because it only applies to cell calculations, not straight formulae + // But don't actually flush any cache + $this->calculationCacheEnabled = false; + } + + // Execute the calculation + try { + $result = self::unwrapResult($this->_calculateFormulaValue($formula, $cellID, $cell)); + } catch (\Exception $e) { + throw new Exception($e->getMessage()); + } + + if ($this->spreadsheet === null) { + // Reset calculation cacheing to its previous state + $this->calculationCacheEnabled = $resetCache; + } + + return $result; + } + + /** + * @param mixed $cellValue + */ + public function getValueFromCache(string $cellReference, &$cellValue): bool + { + $this->debugLog->writeDebugLog("Testing cache value for cell {$cellReference}"); + // Is calculation cacheing enabled? + // If so, is the required value present in calculation cache? + if (($this->calculationCacheEnabled) && (isset($this->calculationCache[$cellReference]))) { + $this->debugLog->writeDebugLog("Retrieving value for cell {$cellReference} from cache"); + // Return the cached result + + $cellValue = $this->calculationCache[$cellReference]; + + return true; + } + + return false; + } + + /** + * @param string $cellReference + * @param mixed $cellValue + */ + public function saveValueToCache($cellReference, $cellValue): void + { + if ($this->calculationCacheEnabled) { + $this->calculationCache[$cellReference] = $cellValue; + } + } + + /** + * Parse a cell formula and calculate its value. + * + * @param string $formula The formula to parse and calculate + * @param string $cellID The ID (e.g. A3) of the cell that we are calculating + * @param Cell $cell Cell to calculate + * + * @return mixed + */ + public function _calculateFormulaValue($formula, $cellID = null, ?Cell $cell = null) + { + $cellValue = null; + + // Quote-Prefixed cell values cannot be formulae, but are treated as strings + if ($cell !== null && $cell->getStyle()->getQuotePrefix() === true) { + return self::wrapResult((string) $formula); + } + + if (preg_match('/^=\s*cmd\s*\|/miu', $formula) !== 0) { + return self::wrapResult($formula); + } + + // Basic validation that this is indeed a formula + // We simply return the cell value if not + $formula = trim($formula); + if ($formula[0] != '=') { + return self::wrapResult($formula); + } + $formula = ltrim(substr($formula, 1)); + if (!isset($formula[0])) { + return self::wrapResult($formula); + } + + $pCellParent = ($cell !== null) ? $cell->getWorksheet() : null; + $wsTitle = ($pCellParent !== null) ? $pCellParent->getTitle() : "\x00Wrk"; + $wsCellReference = $wsTitle . '!' . $cellID; + + if (($cellID !== null) && ($this->getValueFromCache($wsCellReference, $cellValue))) { + return $cellValue; + } + $this->debugLog->writeDebugLog("Evaluating formula for cell {$wsCellReference}"); + + if (($wsTitle[0] !== "\x00") && ($this->cyclicReferenceStack->onStack($wsCellReference))) { + if ($this->cyclicFormulaCount <= 0) { + $this->cyclicFormulaCell = ''; + + return $this->raiseFormulaError('Cyclic Reference in Formula'); + } elseif ($this->cyclicFormulaCell === $wsCellReference) { + ++$this->cyclicFormulaCounter; + if ($this->cyclicFormulaCounter >= $this->cyclicFormulaCount) { + $this->cyclicFormulaCell = ''; + + return $cellValue; + } + } elseif ($this->cyclicFormulaCell == '') { + if ($this->cyclicFormulaCounter >= $this->cyclicFormulaCount) { + return $cellValue; + } + $this->cyclicFormulaCell = $wsCellReference; + } + } + + $this->debugLog->writeDebugLog("Formula for cell {$wsCellReference} is {$formula}"); + // Parse the formula onto the token stack and calculate the value + $this->cyclicReferenceStack->push($wsCellReference); + + $cellValue = $this->processTokenStack($this->internalParseFormula($formula, $cell), $cellID, $cell); + $this->cyclicReferenceStack->pop(); + + // Save to calculation cache + if ($cellID !== null) { + $this->saveValueToCache($wsCellReference, $cellValue); + } + + // Return the calculated value + return $cellValue; + } + + /** + * Ensure that paired matrix operands are both matrices and of the same size. + * + * @param mixed $operand1 First matrix operand + * @param mixed $operand2 Second matrix operand + * @param int $resize Flag indicating whether the matrices should be resized to match + * and (if so), whether the smaller dimension should grow or the + * larger should shrink. + * 0 = no resize + * 1 = shrink to fit + * 2 = extend to fit + * + * @return array + */ + private static function checkMatrixOperands(&$operand1, &$operand2, $resize = 1) + { + // Examine each of the two operands, and turn them into an array if they aren't one already + // Note that this function should only be called if one or both of the operand is already an array + if (!is_array($operand1)) { + [$matrixRows, $matrixColumns] = self::getMatrixDimensions($operand2); + $operand1 = array_fill(0, $matrixRows, array_fill(0, $matrixColumns, $operand1)); + $resize = 0; + } elseif (!is_array($operand2)) { + [$matrixRows, $matrixColumns] = self::getMatrixDimensions($operand1); + $operand2 = array_fill(0, $matrixRows, array_fill(0, $matrixColumns, $operand2)); + $resize = 0; + } + + [$matrix1Rows, $matrix1Columns] = self::getMatrixDimensions($operand1); + [$matrix2Rows, $matrix2Columns] = self::getMatrixDimensions($operand2); + if (($matrix1Rows == $matrix2Columns) && ($matrix2Rows == $matrix1Columns)) { + $resize = 1; + } + + if ($resize == 2) { + // Given two matrices of (potentially) unequal size, convert the smaller in each dimension to match the larger + self::resizeMatricesExtend($operand1, $operand2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns); + } elseif ($resize == 1) { + // Given two matrices of (potentially) unequal size, convert the larger in each dimension to match the smaller + self::resizeMatricesShrink($operand1, $operand2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns); + } + + return [$matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns]; + } + + /** + * Read the dimensions of a matrix, and re-index it with straight numeric keys starting from row 0, column 0. + * + * @param array $matrix matrix operand + * + * @return int[] An array comprising the number of rows, and number of columns + */ + public static function getMatrixDimensions(array &$matrix) + { + $matrixRows = count($matrix); + $matrixColumns = 0; + foreach ($matrix as $rowKey => $rowValue) { + if (!is_array($rowValue)) { + $matrix[$rowKey] = [$rowValue]; + $matrixColumns = max(1, $matrixColumns); + } else { + $matrix[$rowKey] = array_values($rowValue); + $matrixColumns = max(count($rowValue), $matrixColumns); + } + } + $matrix = array_values($matrix); + + return [$matrixRows, $matrixColumns]; + } + + /** + * Ensure that paired matrix operands are both matrices of the same size. + * + * @param mixed $matrix1 First matrix operand + * @param mixed $matrix2 Second matrix operand + * @param int $matrix1Rows Row size of first matrix operand + * @param int $matrix1Columns Column size of first matrix operand + * @param int $matrix2Rows Row size of second matrix operand + * @param int $matrix2Columns Column size of second matrix operand + */ + private static function resizeMatricesShrink(&$matrix1, &$matrix2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns): void + { + if (($matrix2Columns < $matrix1Columns) || ($matrix2Rows < $matrix1Rows)) { + if ($matrix2Rows < $matrix1Rows) { + for ($i = $matrix2Rows; $i < $matrix1Rows; ++$i) { + unset($matrix1[$i]); + } + } + if ($matrix2Columns < $matrix1Columns) { + for ($i = 0; $i < $matrix1Rows; ++$i) { + for ($j = $matrix2Columns; $j < $matrix1Columns; ++$j) { + unset($matrix1[$i][$j]); + } + } + } + } + + if (($matrix1Columns < $matrix2Columns) || ($matrix1Rows < $matrix2Rows)) { + if ($matrix1Rows < $matrix2Rows) { + for ($i = $matrix1Rows; $i < $matrix2Rows; ++$i) { + unset($matrix2[$i]); + } + } + if ($matrix1Columns < $matrix2Columns) { + for ($i = 0; $i < $matrix2Rows; ++$i) { + for ($j = $matrix1Columns; $j < $matrix2Columns; ++$j) { + unset($matrix2[$i][$j]); + } + } + } + } + } + + /** + * Ensure that paired matrix operands are both matrices of the same size. + * + * @param mixed $matrix1 First matrix operand + * @param mixed $matrix2 Second matrix operand + * @param int $matrix1Rows Row size of first matrix operand + * @param int $matrix1Columns Column size of first matrix operand + * @param int $matrix2Rows Row size of second matrix operand + * @param int $matrix2Columns Column size of second matrix operand + */ + private static function resizeMatricesExtend(&$matrix1, &$matrix2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns): void + { + if (($matrix2Columns < $matrix1Columns) || ($matrix2Rows < $matrix1Rows)) { + if ($matrix2Columns < $matrix1Columns) { + for ($i = 0; $i < $matrix2Rows; ++$i) { + $x = $matrix2[$i][$matrix2Columns - 1]; + for ($j = $matrix2Columns; $j < $matrix1Columns; ++$j) { + $matrix2[$i][$j] = $x; + } + } + } + if ($matrix2Rows < $matrix1Rows) { + $x = $matrix2[$matrix2Rows - 1]; + for ($i = 0; $i < $matrix1Rows; ++$i) { + $matrix2[$i] = $x; + } + } + } + + if (($matrix1Columns < $matrix2Columns) || ($matrix1Rows < $matrix2Rows)) { + if ($matrix1Columns < $matrix2Columns) { + for ($i = 0; $i < $matrix1Rows; ++$i) { + $x = $matrix1[$i][$matrix1Columns - 1]; + for ($j = $matrix1Columns; $j < $matrix2Columns; ++$j) { + $matrix1[$i][$j] = $x; + } + } + } + if ($matrix1Rows < $matrix2Rows) { + $x = $matrix1[$matrix1Rows - 1]; + for ($i = 0; $i < $matrix2Rows; ++$i) { + $matrix1[$i] = $x; + } + } + } + } + + /** + * Format details of an operand for display in the log (based on operand type). + * + * @param mixed $value First matrix operand + * + * @return mixed + */ + private function showValue($value) + { + if ($this->debugLog->getWriteDebugLog()) { + $testArray = Functions::flattenArray($value); + if (count($testArray) == 1) { + $value = array_pop($testArray); + } + + if (is_array($value)) { + $returnMatrix = []; + $pad = $rpad = ', '; + foreach ($value as $row) { + if (is_array($row)) { + $returnMatrix[] = implode($pad, array_map([$this, 'showValue'], $row)); + $rpad = '; '; + } else { + $returnMatrix[] = $this->showValue($row); + } + } + + return '{ ' . implode($rpad, $returnMatrix) . ' }'; + } elseif (is_string($value) && (trim($value, self::FORMULA_STRING_QUOTE) == $value)) { + return self::FORMULA_STRING_QUOTE . $value . self::FORMULA_STRING_QUOTE; + } elseif (is_bool($value)) { + return ($value) ? self::$localeBoolean['TRUE'] : self::$localeBoolean['FALSE']; + } + } + + return Functions::flattenSingleValue($value); + } + + /** + * Format type and details of an operand for display in the log (based on operand type). + * + * @param mixed $value First matrix operand + * + * @return null|string + */ + private function showTypeDetails($value) + { + if ($this->debugLog->getWriteDebugLog()) { + $testArray = Functions::flattenArray($value); + if (count($testArray) == 1) { + $value = array_pop($testArray); + } + + if ($value === null) { + return 'a NULL value'; + } elseif (is_float($value)) { + $typeString = 'a floating point number'; + } elseif (is_int($value)) { + $typeString = 'an integer number'; + } elseif (is_bool($value)) { + $typeString = 'a boolean'; + } elseif (is_array($value)) { + $typeString = 'a matrix'; + } else { + if ($value == '') { + return 'an empty string'; + } elseif ($value[0] == '#') { + return 'a ' . $value . ' error'; + } + $typeString = 'a string'; + } + + return $typeString . ' with a value of ' . $this->showValue($value); + } + + return null; + } + + /** + * @param string $formula + * + * @return false|string False indicates an error + */ + private function convertMatrixReferences($formula) + { + static $matrixReplaceFrom = [self::FORMULA_OPEN_FUNCTION_BRACE, ';', self::FORMULA_CLOSE_FUNCTION_BRACE]; + static $matrixReplaceTo = ['MKMATRIX(MKMATRIX(', '),MKMATRIX(', '))']; + + // Convert any Excel matrix references to the MKMATRIX() function + if (strpos($formula, self::FORMULA_OPEN_FUNCTION_BRACE) !== false) { + // If there is the possibility of braces within a quoted string, then we don't treat those as matrix indicators + if (strpos($formula, self::FORMULA_STRING_QUOTE) !== false) { + // So instead we skip replacing in any quoted strings by only replacing in every other array element after we've exploded + // the formula + $temp = explode(self::FORMULA_STRING_QUOTE, $formula); + // Open and Closed counts used for trapping mismatched braces in the formula + $openCount = $closeCount = 0; + $i = false; + foreach ($temp as &$value) { + // Only count/replace in alternating array entries + if ($i = !$i) { + $openCount += substr_count($value, self::FORMULA_OPEN_FUNCTION_BRACE); + $closeCount += substr_count($value, self::FORMULA_CLOSE_FUNCTION_BRACE); + $value = str_replace($matrixReplaceFrom, $matrixReplaceTo, $value); + } + } + unset($value); + // Then rebuild the formula string + $formula = implode(self::FORMULA_STRING_QUOTE, $temp); + } else { + // If there's no quoted strings, then we do a simple count/replace + $openCount = substr_count($formula, self::FORMULA_OPEN_FUNCTION_BRACE); + $closeCount = substr_count($formula, self::FORMULA_CLOSE_FUNCTION_BRACE); + $formula = str_replace($matrixReplaceFrom, $matrixReplaceTo, $formula); + } + // Trap for mismatched braces and trigger an appropriate error + if ($openCount < $closeCount) { + if ($openCount > 0) { + return $this->raiseFormulaError("Formula Error: Mismatched matrix braces '}'"); + } + + return $this->raiseFormulaError("Formula Error: Unexpected '}' encountered"); + } elseif ($openCount > $closeCount) { + if ($closeCount > 0) { + return $this->raiseFormulaError("Formula Error: Mismatched matrix braces '{'"); + } + + return $this->raiseFormulaError("Formula Error: Unexpected '{' encountered"); + } + } + + return $formula; + } + + // Binary Operators + // These operators always work on two values + // Array key is the operator, the value indicates whether this is a left or right associative operator + private static $operatorAssociativity = [ + '^' => 0, // Exponentiation + '*' => 0, '/' => 0, // Multiplication and Division + '+' => 0, '-' => 0, // Addition and Subtraction + '&' => 0, // Concatenation + '|' => 0, ':' => 0, // Intersect and Range + '>' => 0, '<' => 0, '=' => 0, '>=' => 0, '<=' => 0, '<>' => 0, // Comparison + ]; + + // Comparison (Boolean) Operators + // These operators work on two values, but always return a boolean result + private static $comparisonOperators = ['>' => true, '<' => true, '=' => true, '>=' => true, '<=' => true, '<>' => true]; + + // Operator Precedence + // This list includes all valid operators, whether binary (including boolean) or unary (such as %) + // Array key is the operator, the value is its precedence + private static $operatorPrecedence = [ + ':' => 8, // Range + '|' => 7, // Intersect + '~' => 6, // Negation + '%' => 5, // Percentage + '^' => 4, // Exponentiation + '*' => 3, '/' => 3, // Multiplication and Division + '+' => 2, '-' => 2, // Addition and Subtraction + '&' => 1, // Concatenation + '>' => 0, '<' => 0, '=' => 0, '>=' => 0, '<=' => 0, '<>' => 0, // Comparison + ]; + + // Convert infix to postfix notation + + /** + * @param string $formula + * + * @return array|false + */ + private function internalParseFormula($formula, ?Cell $cell = null) + { + if (($formula = $this->convertMatrixReferences(trim($formula))) === false) { + return false; + } + + // If we're using cell caching, then $pCell may well be flushed back to the cache (which detaches the parent worksheet), + // so we store the parent worksheet so that we can re-attach it when necessary + $pCellParent = ($cell !== null) ? $cell->getWorksheet() : null; + + $regexpMatchString = '/^(' . self::CALCULATION_REGEXP_FUNCTION . + '|' . self::CALCULATION_REGEXP_CELLREF . + '|' . self::CALCULATION_REGEXP_COLUMN_RANGE . + '|' . self::CALCULATION_REGEXP_ROW_RANGE . + '|' . self::CALCULATION_REGEXP_NUMBER . + '|' . self::CALCULATION_REGEXP_STRING . + '|' . self::CALCULATION_REGEXP_OPENBRACE . + '|' . self::CALCULATION_REGEXP_DEFINEDNAME . + '|' . self::CALCULATION_REGEXP_ERROR . + ')/sui'; + + // Start with initialisation + $index = 0; + $stack = new Stack(); + $output = []; + $expectingOperator = false; // We use this test in syntax-checking the expression to determine when a + // - is a negation or + is a positive operator rather than an operation + $expectingOperand = false; // We use this test in syntax-checking the expression to determine whether an operand + // should be null in a function call + + // IF branch pruning + // currently pending storeKey (last item of the storeKeysStack + $pendingStoreKey = null; + // stores a list of storeKeys (string[]) + $pendingStoreKeysStack = []; + $expectingConditionMap = []; // ['storeKey' => true, ...] + $expectingThenMap = []; // ['storeKey' => true, ...] + $expectingElseMap = []; // ['storeKey' => true, ...] + $parenthesisDepthMap = []; // ['storeKey' => 4, ...] + + // The guts of the lexical parser + // Loop through the formula extracting each operator and operand in turn + while (true) { + // Branch pruning: we adapt the output item to the context (it will + // be used to limit its computation) + $currentCondition = null; + $currentOnlyIf = null; + $currentOnlyIfNot = null; + $previousStoreKey = null; + $pendingStoreKey = end($pendingStoreKeysStack); + + if ($this->branchPruningEnabled) { + // this is a condition ? + if (isset($expectingConditionMap[$pendingStoreKey]) && $expectingConditionMap[$pendingStoreKey]) { + $currentCondition = $pendingStoreKey; + $stackDepth = count($pendingStoreKeysStack); + if ($stackDepth > 1) { // nested if + $previousStoreKey = $pendingStoreKeysStack[$stackDepth - 2]; + } + } + if (isset($expectingThenMap[$pendingStoreKey]) && $expectingThenMap[$pendingStoreKey]) { + $currentOnlyIf = $pendingStoreKey; + } elseif (isset($previousStoreKey)) { + if (isset($expectingThenMap[$previousStoreKey]) && $expectingThenMap[$previousStoreKey]) { + $currentOnlyIf = $previousStoreKey; + } + } + if (isset($expectingElseMap[$pendingStoreKey]) && $expectingElseMap[$pendingStoreKey]) { + $currentOnlyIfNot = $pendingStoreKey; + } elseif (isset($previousStoreKey)) { + if (isset($expectingElseMap[$previousStoreKey]) && $expectingElseMap[$previousStoreKey]) { + $currentOnlyIfNot = $previousStoreKey; + } + } + } + + $opCharacter = $formula[$index]; // Get the first character of the value at the current index position + + if ((isset(self::$comparisonOperators[$opCharacter])) && (strlen($formula) > $index) && (isset(self::$comparisonOperators[$formula[$index + 1]]))) { + $opCharacter .= $formula[++$index]; + } + // Find out if we're currently at the beginning of a number, variable, cell reference, function, parenthesis or operand + $isOperandOrFunction = (bool) preg_match($regexpMatchString, substr($formula, $index), $match); + + if ($opCharacter == '-' && !$expectingOperator) { // Is it a negation instead of a minus? + // Put a negation on the stack + $stack->push('Unary Operator', '~', null, $currentCondition, $currentOnlyIf, $currentOnlyIfNot); + ++$index; // and drop the negation symbol + } elseif ($opCharacter == '%' && $expectingOperator) { + // Put a percentage on the stack + $stack->push('Unary Operator', '%', null, $currentCondition, $currentOnlyIf, $currentOnlyIfNot); + ++$index; + } elseif ($opCharacter == '+' && !$expectingOperator) { // Positive (unary plus rather than binary operator plus) can be discarded? + ++$index; // Drop the redundant plus symbol + } elseif ((($opCharacter == '~') || ($opCharacter == '|')) && (!$isOperandOrFunction)) { // We have to explicitly deny a tilde or pipe, because they are legal + return $this->raiseFormulaError("Formula Error: Illegal character '~'"); // on the stack but not in the input expression + } elseif ((isset(self::$operators[$opCharacter]) || $isOperandOrFunction) && $expectingOperator) { // Are we putting an operator on the stack? + while ( + $stack->count() > 0 && + ($o2 = $stack->last()) && + isset(self::$operators[$o2['value']]) && + @(self::$operatorAssociativity[$opCharacter] ? self::$operatorPrecedence[$opCharacter] < self::$operatorPrecedence[$o2['value']] : self::$operatorPrecedence[$opCharacter] <= self::$operatorPrecedence[$o2['value']]) + ) { + $output[] = $stack->pop(); // Swap operands and higher precedence operators from the stack to the output + } + + // Finally put our current operator onto the stack + $stack->push('Binary Operator', $opCharacter, null, $currentCondition, $currentOnlyIf, $currentOnlyIfNot); + + ++$index; + $expectingOperator = false; + } elseif ($opCharacter == ')' && $expectingOperator) { // Are we expecting to close a parenthesis? + $expectingOperand = false; + while (($o2 = $stack->pop()) && $o2['value'] != '(') { // Pop off the stack back to the last ( + if ($o2 === null) { + return $this->raiseFormulaError('Formula Error: Unexpected closing brace ")"'); + } + $output[] = $o2; + } + $d = $stack->last(2); + + // Branch pruning we decrease the depth whether is it a function + // call or a parenthesis + if (!empty($pendingStoreKey)) { + --$parenthesisDepthMap[$pendingStoreKey]; + } + + if (is_array($d) && preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/miu', $d['value'], $matches)) { // Did this parenthesis just close a function? + if (!empty($pendingStoreKey) && $parenthesisDepthMap[$pendingStoreKey] == -1) { + // we are closing an IF( + if ($d['value'] != 'IF(') { + return $this->raiseFormulaError('Parser bug we should be in an "IF("'); + } + if ($expectingConditionMap[$pendingStoreKey]) { + return $this->raiseFormulaError('We should not be expecting a condition'); + } + $expectingThenMap[$pendingStoreKey] = false; + $expectingElseMap[$pendingStoreKey] = false; + --$parenthesisDepthMap[$pendingStoreKey]; + array_pop($pendingStoreKeysStack); + unset($pendingStoreKey); + } + + $functionName = $matches[1]; // Get the function name + $d = $stack->pop(); + $argumentCount = $d['value']; // See how many arguments there were (argument count is the next value stored on the stack) + $output[] = $d; // Dump the argument count on the output + $output[] = $stack->pop(); // Pop the function and push onto the output + if (isset(self::$controlFunctions[$functionName])) { + $expectedArgumentCount = self::$controlFunctions[$functionName]['argumentCount']; + $functionCall = self::$controlFunctions[$functionName]['functionCall']; + } elseif (isset(self::$phpSpreadsheetFunctions[$functionName])) { + $expectedArgumentCount = self::$phpSpreadsheetFunctions[$functionName]['argumentCount']; + $functionCall = self::$phpSpreadsheetFunctions[$functionName]['functionCall']; + } else { // did we somehow push a non-function on the stack? this should never happen + return $this->raiseFormulaError('Formula Error: Internal error, non-function on stack'); + } + // Check the argument count + $argumentCountError = false; + $expectedArgumentCountString = null; + if (is_numeric($expectedArgumentCount)) { + if ($expectedArgumentCount < 0) { + if ($argumentCount > abs($expectedArgumentCount)) { + $argumentCountError = true; + $expectedArgumentCountString = 'no more than ' . abs($expectedArgumentCount); + } + } else { + if ($argumentCount != $expectedArgumentCount) { + $argumentCountError = true; + $expectedArgumentCountString = $expectedArgumentCount; + } + } + } elseif ($expectedArgumentCount != '*') { + $isOperandOrFunction = preg_match('/(\d*)([-+,])(\d*)/', $expectedArgumentCount, $argMatch); + switch ($argMatch[2]) { + case '+': + if ($argumentCount < $argMatch[1]) { + $argumentCountError = true; + $expectedArgumentCountString = $argMatch[1] . ' or more '; + } + + break; + case '-': + if (($argumentCount < $argMatch[1]) || ($argumentCount > $argMatch[3])) { + $argumentCountError = true; + $expectedArgumentCountString = 'between ' . $argMatch[1] . ' and ' . $argMatch[3]; + } + + break; + case ',': + if (($argumentCount != $argMatch[1]) && ($argumentCount != $argMatch[3])) { + $argumentCountError = true; + $expectedArgumentCountString = 'either ' . $argMatch[1] . ' or ' . $argMatch[3]; + } + + break; + } + } + if ($argumentCountError) { + return $this->raiseFormulaError("Formula Error: Wrong number of arguments for $functionName() function: $argumentCount given, " . $expectedArgumentCountString . ' expected'); + } + } + ++$index; + } elseif ($opCharacter == ',') { // Is this the separator for function arguments? + if ( + !empty($pendingStoreKey) && + $parenthesisDepthMap[$pendingStoreKey] == 0 + ) { + // We must go to the IF next argument + if ($expectingConditionMap[$pendingStoreKey]) { + $expectingConditionMap[$pendingStoreKey] = false; + $expectingThenMap[$pendingStoreKey] = true; + } elseif ($expectingThenMap[$pendingStoreKey]) { + $expectingThenMap[$pendingStoreKey] = false; + $expectingElseMap[$pendingStoreKey] = true; + } elseif ($expectingElseMap[$pendingStoreKey]) { + return $this->raiseFormulaError('Reaching fourth argument of an IF'); + } + } + while (($o2 = $stack->pop()) && $o2['value'] != '(') { // Pop off the stack back to the last ( + if ($o2 === null) { + return $this->raiseFormulaError('Formula Error: Unexpected ,'); + } + $output[] = $o2; // pop the argument expression stuff and push onto the output + } + // If we've a comma when we're expecting an operand, then what we actually have is a null operand; + // so push a null onto the stack + if (($expectingOperand) || (!$expectingOperator)) { + $output[] = ['type' => 'Empty Argument', 'value' => self::$excelConstants['NULL'], 'reference' => null]; + } + // make sure there was a function + $d = $stack->last(2); + if (!preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/miu', $d['value'], $matches)) { + return $this->raiseFormulaError('Formula Error: Unexpected ,'); + } + $d = $stack->pop(); + $itemStoreKey = $d['storeKey'] ?? null; + $itemOnlyIf = $d['onlyIf'] ?? null; + $itemOnlyIfNot = $d['onlyIfNot'] ?? null; + $stack->push($d['type'], ++$d['value'], $d['reference'], $itemStoreKey, $itemOnlyIf, $itemOnlyIfNot); // increment the argument count + $stack->push('Brace', '(', null, $itemStoreKey, $itemOnlyIf, $itemOnlyIfNot); // put the ( back on, we'll need to pop back to it again + $expectingOperator = false; + $expectingOperand = true; + ++$index; + } elseif ($opCharacter == '(' && !$expectingOperator) { + if (!empty($pendingStoreKey)) { // Branch pruning: we go deeper + ++$parenthesisDepthMap[$pendingStoreKey]; + } + $stack->push('Brace', '(', null, $currentCondition, $currentOnlyIf, $currentOnlyIf); + ++$index; + } elseif ($isOperandOrFunction && !$expectingOperator) { // do we now have a function/variable/number? + $expectingOperator = true; + $expectingOperand = false; + $val = $match[1]; + $length = strlen($val); + + if (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/miu', $val, $matches)) { + $val = preg_replace('/\s/u', '', $val); + if (isset(self::$phpSpreadsheetFunctions[strtoupper($matches[1])]) || isset(self::$controlFunctions[strtoupper($matches[1])])) { // it's a function + $valToUpper = strtoupper($val); + } else { + $valToUpper = 'NAME.ERROR('; + } + // here $matches[1] will contain values like "IF" + // and $val "IF(" + if ($this->branchPruningEnabled && ($valToUpper == 'IF(')) { // we handle a new if + $pendingStoreKey = $this->getUnusedBranchStoreKey(); + $pendingStoreKeysStack[] = $pendingStoreKey; + $expectingConditionMap[$pendingStoreKey] = true; + $parenthesisDepthMap[$pendingStoreKey] = 0; + } else { // this is not an if but we go deeper + if (!empty($pendingStoreKey) && array_key_exists($pendingStoreKey, $parenthesisDepthMap)) { + ++$parenthesisDepthMap[$pendingStoreKey]; + } + } + + $stack->push('Function', $valToUpper, null, $currentCondition, $currentOnlyIf, $currentOnlyIfNot); + // tests if the function is closed right after opening + $ax = preg_match('/^\s*\)/u', substr($formula, $index + $length)); + if ($ax) { + $stack->push('Operand Count for Function ' . $valToUpper . ')', 0, null, $currentCondition, $currentOnlyIf, $currentOnlyIfNot); + $expectingOperator = true; + } else { + $stack->push('Operand Count for Function ' . $valToUpper . ')', 1, null, $currentCondition, $currentOnlyIf, $currentOnlyIfNot); + $expectingOperator = false; + } + $stack->push('Brace', '('); + } elseif (preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/i', $val, $matches)) { + // Watch for this case-change when modifying to allow cell references in different worksheets... + // Should only be applied to the actual cell column, not the worksheet name + // If the last entry on the stack was a : operator, then we have a cell range reference + $testPrevOp = $stack->last(1); + if ($testPrevOp !== null && $testPrevOp['value'] === ':') { + // If we have a worksheet reference, then we're playing with a 3D reference + if ($matches[2] == '') { + // Otherwise, we 'inherit' the worksheet reference from the start cell reference + // The start of the cell range reference should be the last entry in $output + $rangeStartCellRef = $output[count($output) - 1]['value']; + preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/i', $rangeStartCellRef, $rangeStartMatches); + if ($rangeStartMatches[2] > '') { + $val = $rangeStartMatches[2] . '!' . $val; + } + } else { + $rangeStartCellRef = $output[count($output) - 1]['value']; + preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/i', $rangeStartCellRef, $rangeStartMatches); + if ($rangeStartMatches[2] !== $matches[2]) { + return $this->raiseFormulaError('3D Range references are not yet supported'); + } + } + } elseif (strpos($val, '!') === false && $pCellParent !== null) { + $worksheet = $pCellParent->getTitle(); + $val = "'{$worksheet}'!{$val}"; + } + + $outputItem = $stack->getStackItem('Cell Reference', $val, $val, $currentCondition, $currentOnlyIf, $currentOnlyIfNot); + + $output[] = $outputItem; + } else { // it's a variable, constant, string, number or boolean + $localeConstant = false; + $stackItemType = 'Value'; + $stackItemReference = null; + + // If the last entry on the stack was a : operator, then we may have a row or column range reference + $testPrevOp = $stack->last(1); + if ($testPrevOp !== null && $testPrevOp['value'] === ':') { + $stackItemType = 'Cell Reference'; + $startRowColRef = $output[count($output) - 1]['value']; + [$rangeWS1, $startRowColRef] = Worksheet::extractSheetTitle($startRowColRef, true); + $rangeSheetRef = $rangeWS1; + if ($rangeWS1 !== '') { + $rangeWS1 .= '!'; + } + $rangeSheetRef = trim($rangeSheetRef, "'"); + [$rangeWS2, $val] = Worksheet::extractSheetTitle($val, true); + if ($rangeWS2 !== '') { + $rangeWS2 .= '!'; + } else { + $rangeWS2 = $rangeWS1; + } + + $refSheet = $pCellParent; + if ($pCellParent !== null && $rangeSheetRef !== '' && $rangeSheetRef !== $pCellParent->getTitle()) { + $refSheet = $pCellParent->getParent()->getSheetByName($rangeSheetRef); + } + + if (ctype_digit($val) && $val <= 1048576) { + // Row range + $stackItemType = 'Row Reference'; + /** @var int $valx */ + $valx = $val; + $endRowColRef = ($refSheet !== null) ? $refSheet->getHighestDataColumn($valx) : 'XFD'; // Max 16,384 columns for Excel2007 + $val = "{$rangeWS2}{$endRowColRef}{$val}"; + } elseif (ctype_alpha($val) && strlen($val) <= 3) { + // Column range + $stackItemType = 'Column Reference'; + $endRowColRef = ($refSheet !== null) ? $refSheet->getHighestDataRow($val) : 1048576; // Max 1,048,576 rows for Excel2007 + $val = "{$rangeWS2}{$val}{$endRowColRef}"; + } + $stackItemReference = $val; + } elseif ($opCharacter == self::FORMULA_STRING_QUOTE) { + // UnEscape any quotes within the string + $val = self::wrapResult(str_replace('""', self::FORMULA_STRING_QUOTE, self::unwrapResult($val))); + } elseif (isset(self::$excelConstants[trim(strtoupper($val))])) { + $stackItemType = 'Constant'; + $excelConstant = trim(strtoupper($val)); + $val = self::$excelConstants[$excelConstant]; + } elseif (($localeConstant = array_search(trim(strtoupper($val)), self::$localeBoolean)) !== false) { + $stackItemType = 'Constant'; + $val = self::$excelConstants[$localeConstant]; + } elseif ( + preg_match('/^' . self::CALCULATION_REGEXP_ROW_RANGE . '/miu', substr($formula, $index), $rowRangeReference) + ) { + $val = $rowRangeReference[1]; + $length = strlen($rowRangeReference[1]); + $stackItemType = 'Row Reference'; + $column = 'A'; + if (($testPrevOp !== null && $testPrevOp['value'] === ':') && $pCellParent !== null) { + $column = $pCellParent->getHighestDataColumn($val); + } + $val = "{$rowRangeReference[2]}{$column}{$rowRangeReference[7]}"; + $stackItemReference = $val; + } elseif ( + preg_match('/^' . self::CALCULATION_REGEXP_COLUMN_RANGE . '/miu', substr($formula, $index), $columnRangeReference) + ) { + $val = $columnRangeReference[1]; + $length = strlen($val); + $stackItemType = 'Column Reference'; + $row = '1'; + if (($testPrevOp !== null && $testPrevOp['value'] === ':') && $pCellParent !== null) { + $row = $pCellParent->getHighestDataRow($val); + } + $val = "{$val}{$row}"; + $stackItemReference = $val; + } elseif (preg_match('/^' . self::CALCULATION_REGEXP_DEFINEDNAME . '.*/miu', $val, $match)) { + $stackItemType = 'Defined Name'; + $stackItemReference = $val; + } elseif (is_numeric($val)) { + if ((strpos($val, '.') !== false) || (stripos($val, 'e') !== false) || ($val > PHP_INT_MAX) || ($val < -PHP_INT_MAX)) { + $val = (float) $val; + } else { + $val = (int) $val; + } + } + + $details = $stack->getStackItem($stackItemType, $val, $stackItemReference, $currentCondition, $currentOnlyIf, $currentOnlyIfNot); + if ($localeConstant) { + $details['localeValue'] = $localeConstant; + } + $output[] = $details; + } + $index += $length; + } elseif ($opCharacter == '$') { // absolute row or column range + ++$index; + } elseif ($opCharacter == ')') { // miscellaneous error checking + if ($expectingOperand) { + $output[] = ['type' => 'Empty Argument', 'value' => self::$excelConstants['NULL'], 'reference' => null]; + $expectingOperand = false; + $expectingOperator = true; + } else { + return $this->raiseFormulaError("Formula Error: Unexpected ')'"); + } + } elseif (isset(self::$operators[$opCharacter]) && !$expectingOperator) { + return $this->raiseFormulaError("Formula Error: Unexpected operator '$opCharacter'"); + } else { // I don't even want to know what you did to get here + return $this->raiseFormulaError('Formula Error: An unexpected error occurred'); + } + // Test for end of formula string + if ($index == strlen($formula)) { + // Did we end with an operator?. + // Only valid for the % unary operator + if ((isset(self::$operators[$opCharacter])) && ($opCharacter != '%')) { + return $this->raiseFormulaError("Formula Error: Operator '$opCharacter' has no operands"); + } + + break; + } + // Ignore white space + while (($formula[$index] == "\n") || ($formula[$index] == "\r")) { + ++$index; + } + + if ($formula[$index] == ' ') { + while ($formula[$index] == ' ') { + ++$index; + } + + // If we're expecting an operator, but only have a space between the previous and next operands (and both are + // Cell References) then we have an INTERSECTION operator + if ( + ($expectingOperator) && + ( + (preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '.*/Ui', substr($formula, $index), $match)) && + ($output[count($output) - 1]['type'] == 'Cell Reference') || + (preg_match('/^' . self::CALCULATION_REGEXP_DEFINEDNAME . '.*/miu', substr($formula, $index), $match)) && + ($output[count($output) - 1]['type'] == 'Defined Name' || $output[count($output) - 1]['type'] == 'Value') + ) + ) { + while ( + $stack->count() > 0 && + ($o2 = $stack->last()) && + isset(self::$operators[$o2['value']]) && + @(self::$operatorAssociativity[$opCharacter] ? self::$operatorPrecedence[$opCharacter] < self::$operatorPrecedence[$o2['value']] : self::$operatorPrecedence[$opCharacter] <= self::$operatorPrecedence[$o2['value']]) + ) { + $output[] = $stack->pop(); // Swap operands and higher precedence operators from the stack to the output + } + $stack->push('Binary Operator', '|'); // Put an Intersect Operator on the stack + $expectingOperator = false; + } + } + } + + while (($op = $stack->pop()) !== null) { // pop everything off the stack and push onto output + if ((is_array($op) && $op['value'] == '(') || ($op === '(')) { + return $this->raiseFormulaError("Formula Error: Expecting ')'"); // if there are any opening braces on the stack, then braces were unbalanced + } + $output[] = $op; + } + + return $output; + } + + private static function dataTestReference(&$operandData) + { + $operand = $operandData['value']; + if (($operandData['reference'] === null) && (is_array($operand))) { + $rKeys = array_keys($operand); + $rowKey = array_shift($rKeys); + $cKeys = array_keys(array_keys($operand[$rowKey])); + $colKey = array_shift($cKeys); + if (ctype_upper("$colKey")) { + $operandData['reference'] = $colKey . $rowKey; + } + } + + return $operand; + } + + // evaluate postfix notation + + /** + * @param mixed $tokens + * @param null|string $cellID + * + * @return array|false + */ + private function processTokenStack($tokens, $cellID = null, ?Cell $cell = null) + { + if ($tokens == false) { + return false; + } + + // If we're using cell caching, then $pCell may well be flushed back to the cache (which detaches the parent cell collection), + // so we store the parent cell collection so that we can re-attach it when necessary + $pCellWorksheet = ($cell !== null) ? $cell->getWorksheet() : null; + $pCellParent = ($cell !== null) ? $cell->getParent() : null; + $stack = new Stack(); + + // Stores branches that have been pruned + $fakedForBranchPruning = []; + // help us to know when pruning ['branchTestId' => true/false] + $branchStore = []; + // Loop through each token in turn + foreach ($tokens as $tokenData) { + $token = $tokenData['value']; + + // Branch pruning: skip useless resolutions + $storeKey = $tokenData['storeKey'] ?? null; + if ($this->branchPruningEnabled && isset($tokenData['onlyIf'])) { + $onlyIfStoreKey = $tokenData['onlyIf']; + $storeValue = $branchStore[$onlyIfStoreKey] ?? null; + $storeValueAsBool = ($storeValue === null) ? + true : (bool) Functions::flattenSingleValue($storeValue); + if (is_array($storeValue)) { + $wrappedItem = end($storeValue); + $storeValue = end($wrappedItem); + } + + if ( + isset($storeValue) + && ( + !$storeValueAsBool + || Functions::isError($storeValue) + || ($storeValue === 'Pruned branch') + ) + ) { + // If branching value is not true, we don't need to compute + if (!isset($fakedForBranchPruning['onlyIf-' . $onlyIfStoreKey])) { + $stack->push('Value', 'Pruned branch (only if ' . $onlyIfStoreKey . ') ' . $token); + $fakedForBranchPruning['onlyIf-' . $onlyIfStoreKey] = true; + } + + if (isset($storeKey)) { + // We are processing an if condition + // We cascade the pruning to the depending branches + $branchStore[$storeKey] = 'Pruned branch'; + $fakedForBranchPruning['onlyIfNot-' . $storeKey] = true; + $fakedForBranchPruning['onlyIf-' . $storeKey] = true; + } + + continue; + } + } + + if ($this->branchPruningEnabled && isset($tokenData['onlyIfNot'])) { + $onlyIfNotStoreKey = $tokenData['onlyIfNot']; + $storeValue = $branchStore[$onlyIfNotStoreKey] ?? null; + $storeValueAsBool = ($storeValue === null) ? + true : (bool) Functions::flattenSingleValue($storeValue); + if (is_array($storeValue)) { + $wrappedItem = end($storeValue); + $storeValue = end($wrappedItem); + } + if ( + isset($storeValue) + && ( + $storeValueAsBool + || Functions::isError($storeValue) + || ($storeValue === 'Pruned branch') + ) + ) { + // If branching value is true, we don't need to compute + if (!isset($fakedForBranchPruning['onlyIfNot-' . $onlyIfNotStoreKey])) { + $stack->push('Value', 'Pruned branch (only if not ' . $onlyIfNotStoreKey . ') ' . $token); + $fakedForBranchPruning['onlyIfNot-' . $onlyIfNotStoreKey] = true; + } + + if (isset($storeKey)) { + // We are processing an if condition + // We cascade the pruning to the depending branches + $branchStore[$storeKey] = 'Pruned branch'; + $fakedForBranchPruning['onlyIfNot-' . $storeKey] = true; + $fakedForBranchPruning['onlyIf-' . $storeKey] = true; + } + + continue; + } + } + + // if the token is a binary operator, pop the top two values off the stack, do the operation, and push the result back on the stack + if (!is_numeric($token) && isset(self::$binaryOperators[$token])) { + // We must have two operands, error if we don't + if (($operand2Data = $stack->pop()) === null) { + return $this->raiseFormulaError('Internal error - Operand value missing from stack'); + } + if (($operand1Data = $stack->pop()) === null) { + return $this->raiseFormulaError('Internal error - Operand value missing from stack'); + } + + $operand1 = self::dataTestReference($operand1Data); + $operand2 = self::dataTestReference($operand2Data); + + // Log what we're doing + if ($token == ':') { + $this->debugLog->writeDebugLog('Evaluating Range ', $this->showValue($operand1Data['reference']), ' ', $token, ' ', $this->showValue($operand2Data['reference'])); + } else { + $this->debugLog->writeDebugLog('Evaluating ', $this->showValue($operand1), ' ', $token, ' ', $this->showValue($operand2)); + } + + // Process the operation in the appropriate manner + switch ($token) { + // Comparison (Boolean) Operators + case '>': // Greater than + case '<': // Less than + case '>=': // Greater than or Equal to + case '<=': // Less than or Equal to + case '=': // Equality + case '<>': // Inequality + $result = $this->executeBinaryComparisonOperation($cellID, $operand1, $operand2, $token, $stack); + if (isset($storeKey)) { + $branchStore[$storeKey] = $result; + } + + break; + // Binary Operators + case ':': // Range + if (strpos($operand1Data['reference'], '!') !== false) { + [$sheet1, $operand1Data['reference']] = Worksheet::extractSheetTitle($operand1Data['reference'], true); + } else { + $sheet1 = ($pCellParent !== null) ? $pCellWorksheet->getTitle() : ''; + } + + [$sheet2, $operand2Data['reference']] = Worksheet::extractSheetTitle($operand2Data['reference'], true); + if (empty($sheet2)) { + $sheet2 = $sheet1; + } + + if (trim($sheet1, "'") === trim($sheet2, "'")) { + if ($operand1Data['reference'] === null) { + if ((trim($operand1Data['value']) != '') && (is_numeric($operand1Data['value']))) { + $operand1Data['reference'] = $cell->getColumn() . $operand1Data['value']; + } elseif (trim($operand1Data['reference']) == '') { + $operand1Data['reference'] = $cell->getCoordinate(); + } else { + $operand1Data['reference'] = $operand1Data['value'] . $cell->getRow(); + } + } + if ($operand2Data['reference'] === null) { + if ((trim($operand2Data['value']) != '') && (is_numeric($operand2Data['value']))) { + $operand2Data['reference'] = $cell->getColumn() . $operand2Data['value']; + } elseif (trim($operand2Data['reference']) == '') { + $operand2Data['reference'] = $cell->getCoordinate(); + } else { + $operand2Data['reference'] = $operand2Data['value'] . $cell->getRow(); + } + } + + $oData = array_merge(explode(':', $operand1Data['reference']), explode(':', $operand2Data['reference'])); + $oCol = $oRow = []; + foreach ($oData as $oDatum) { + $oCR = Coordinate::coordinateFromString($oDatum); + $oCol[] = Coordinate::columnIndexFromString($oCR[0]) - 1; + $oRow[] = $oCR[1]; + } + $cellRef = Coordinate::stringFromColumnIndex(min($oCol) + 1) . min($oRow) . ':' . Coordinate::stringFromColumnIndex(max($oCol) + 1) . max($oRow); + if ($pCellParent !== null) { + $cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($sheet1), false); + } else { + return $this->raiseFormulaError('Unable to access Cell Reference'); + } + + $stack->push('Cell Reference', $cellValue, $cellRef); + } else { + $stack->push('Error', Functions::REF(), null); + } + + break; + case '+': // Addition + $result = $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'plusEquals', $stack); + if (isset($storeKey)) { + $branchStore[$storeKey] = $result; + } + + break; + case '-': // Subtraction + $result = $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'minusEquals', $stack); + if (isset($storeKey)) { + $branchStore[$storeKey] = $result; + } + + break; + case '*': // Multiplication + $result = $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'arrayTimesEquals', $stack); + if (isset($storeKey)) { + $branchStore[$storeKey] = $result; + } + + break; + case '/': // Division + $result = $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'arrayRightDivide', $stack); + if (isset($storeKey)) { + $branchStore[$storeKey] = $result; + } + + break; + case '^': // Exponential + $result = $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'power', $stack); + if (isset($storeKey)) { + $branchStore[$storeKey] = $result; + } + + break; + case '&': // Concatenation + // If either of the operands is a matrix, we need to treat them both as matrices + // (converting the other operand to a matrix if need be); then perform the required + // matrix operation + if (is_bool($operand1)) { + $operand1 = ($operand1) ? self::$localeBoolean['TRUE'] : self::$localeBoolean['FALSE']; + } + if (is_bool($operand2)) { + $operand2 = ($operand2) ? self::$localeBoolean['TRUE'] : self::$localeBoolean['FALSE']; + } + if ((is_array($operand1)) || (is_array($operand2))) { + // Ensure that both operands are arrays/matrices + self::checkMatrixOperands($operand1, $operand2, 2); + + try { + // Convert operand 1 from a PHP array to a matrix + $matrix = new Shared\JAMA\Matrix($operand1); + // Perform the required operation against the operand 1 matrix, passing in operand 2 + $matrixResult = $matrix->concat($operand2); + $result = $matrixResult->getArray(); + } catch (\Exception $ex) { + $this->debugLog->writeDebugLog('JAMA Matrix Exception: ', $ex->getMessage()); + $result = '#VALUE!'; + } + } else { + $result = self::FORMULA_STRING_QUOTE . str_replace('""', self::FORMULA_STRING_QUOTE, self::unwrapResult($operand1) . self::unwrapResult($operand2)) . self::FORMULA_STRING_QUOTE; + } + $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($result)); + $stack->push('Value', $result); + + if (isset($storeKey)) { + $branchStore[$storeKey] = $result; + } + + break; + case '|': // Intersect + $rowIntersect = array_intersect_key($operand1, $operand2); + $cellIntersect = $oCol = $oRow = []; + foreach (array_keys($rowIntersect) as $row) { + $oRow[] = $row; + foreach ($rowIntersect[$row] as $col => $data) { + $oCol[] = Coordinate::columnIndexFromString($col) - 1; + $cellIntersect[$row] = array_intersect_key($operand1[$row], $operand2[$row]); + } + } + if (count(Functions::flattenArray($cellIntersect)) === 0) { + $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($cellIntersect)); + $stack->push('Error', Functions::null(), null); + } else { + $cellRef = Coordinate::stringFromColumnIndex(min($oCol) + 1) . min($oRow) . ':' . + Coordinate::stringFromColumnIndex(max($oCol) + 1) . max($oRow); + $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($cellIntersect)); + $stack->push('Value', $cellIntersect, $cellRef); + } + + break; + } + + // if the token is a unary operator, pop one value off the stack, do the operation, and push it back on + } elseif (($token === '~') || ($token === '%')) { + if (($arg = $stack->pop()) === null) { + return $this->raiseFormulaError('Internal error - Operand value missing from stack'); + } + $arg = $arg['value']; + if ($token === '~') { + $this->debugLog->writeDebugLog('Evaluating Negation of ', $this->showValue($arg)); + $multiplier = -1; + } else { + $this->debugLog->writeDebugLog('Evaluating Percentile of ', $this->showValue($arg)); + $multiplier = 0.01; + } + if (is_array($arg)) { + self::checkMatrixOperands($arg, $multiplier, 2); + + try { + $matrix1 = new Shared\JAMA\Matrix($arg); + $matrixResult = $matrix1->arrayTimesEquals($multiplier); + $result = $matrixResult->getArray(); + } catch (\Exception $ex) { + $this->debugLog->writeDebugLog('JAMA Matrix Exception: ', $ex->getMessage()); + $result = '#VALUE!'; + } + $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($result)); + $stack->push('Value', $result); + if (isset($storeKey)) { + $branchStore[$storeKey] = $result; + } + } else { + $this->executeNumericBinaryOperation($multiplier, $arg, '*', 'arrayTimesEquals', $stack); + } + } elseif (preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/i', $token ?? '', $matches)) { + $cellRef = null; + + if (isset($matches[8])) { + if ($cell === null) { + // We can't access the range, so return a REF error + $cellValue = Functions::REF(); + } else { + $cellRef = $matches[6] . $matches[7] . ':' . $matches[9] . $matches[10]; + if ($matches[2] > '') { + $matches[2] = trim($matches[2], "\"'"); + if ((strpos($matches[2], '[') !== false) || (strpos($matches[2], ']') !== false)) { + // It's a Reference to an external spreadsheet (not currently supported) + return $this->raiseFormulaError('Unable to access External Workbook'); + } + $matches[2] = trim($matches[2], "\"'"); + $this->debugLog->writeDebugLog('Evaluating Cell Range ', $cellRef, ' in worksheet ', $matches[2]); + if ($pCellParent !== null) { + $cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($matches[2]), false); + } else { + return $this->raiseFormulaError('Unable to access Cell Reference'); + } + $this->debugLog->writeDebugLog('Evaluation Result for cells ', $cellRef, ' in worksheet ', $matches[2], ' is ', $this->showTypeDetails($cellValue)); + } else { + $this->debugLog->writeDebugLog('Evaluating Cell Range ', $cellRef, ' in current worksheet'); + if ($pCellParent !== null) { + $cellValue = $this->extractCellRange($cellRef, $pCellWorksheet, false); + } else { + return $this->raiseFormulaError('Unable to access Cell Reference'); + } + $this->debugLog->writeDebugLog('Evaluation Result for cells ', $cellRef, ' is ', $this->showTypeDetails($cellValue)); + } + } + } else { + if ($cell === null) { + // We can't access the cell, so return a REF error + $cellValue = Functions::REF(); + } else { + $cellRef = $matches[6] . $matches[7]; + if ($matches[2] > '') { + $matches[2] = trim($matches[2], "\"'"); + if ((strpos($matches[2], '[') !== false) || (strpos($matches[2], ']') !== false)) { + // It's a Reference to an external spreadsheet (not currently supported) + return $this->raiseFormulaError('Unable to access External Workbook'); + } + $this->debugLog->writeDebugLog('Evaluating Cell ', $cellRef, ' in worksheet ', $matches[2]); + if ($pCellParent !== null) { + $cellSheet = $this->spreadsheet->getSheetByName($matches[2]); + if ($cellSheet && $cellSheet->cellExists($cellRef)) { + $cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($matches[2]), false); + $cell->attach($pCellParent); + } else { + $cellRef = ($cellSheet !== null) ? "'{$matches[2]}'!{$cellRef}" : $cellRef; + $cellValue = null; + } + } else { + return $this->raiseFormulaError('Unable to access Cell Reference'); + } + $this->debugLog->writeDebugLog('Evaluation Result for cell ', $cellRef, ' in worksheet ', $matches[2], ' is ', $this->showTypeDetails($cellValue)); + } else { + $this->debugLog->writeDebugLog('Evaluating Cell ', $cellRef, ' in current worksheet'); + if ($pCellParent->has($cellRef)) { + $cellValue = $this->extractCellRange($cellRef, $pCellWorksheet, false); + $cell->attach($pCellParent); + } else { + $cellValue = null; + } + $this->debugLog->writeDebugLog('Evaluation Result for cell ', $cellRef, ' is ', $this->showTypeDetails($cellValue)); + } + } + } + + $stack->push('Cell Value', $cellValue, $cellRef); + if (isset($storeKey)) { + $branchStore[$storeKey] = $cellValue; + } + + // if the token is a function, pop arguments off the stack, hand them to the function, and push the result back on + } elseif (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/miu', $token ?? '', $matches)) { + if ($pCellParent) { + $cell->attach($pCellParent); + } + + $functionName = $matches[1]; + $argCount = $stack->pop(); + $argCount = $argCount['value']; + if ($functionName !== 'MKMATRIX') { + $this->debugLog->writeDebugLog('Evaluating Function ', self::localeFunc($functionName), '() with ', (($argCount == 0) ? 'no' : $argCount), ' argument', (($argCount == 1) ? '' : 's')); + } + if ((isset(self::$phpSpreadsheetFunctions[$functionName])) || (isset(self::$controlFunctions[$functionName]))) { // function + $passByReference = false; + $passCellReference = false; + $functionCall = null; + if (isset(self::$phpSpreadsheetFunctions[$functionName])) { + $functionCall = self::$phpSpreadsheetFunctions[$functionName]['functionCall']; + $passByReference = isset(self::$phpSpreadsheetFunctions[$functionName]['passByReference']); + $passCellReference = isset(self::$phpSpreadsheetFunctions[$functionName]['passCellReference']); + } elseif (isset(self::$controlFunctions[$functionName])) { + $functionCall = self::$controlFunctions[$functionName]['functionCall']; + $passByReference = isset(self::$controlFunctions[$functionName]['passByReference']); + $passCellReference = isset(self::$controlFunctions[$functionName]['passCellReference']); + } + + // get the arguments for this function + $args = $argArrayVals = []; + $emptyArguments = []; + for ($i = 0; $i < $argCount; ++$i) { + $arg = $stack->pop(); + $a = $argCount - $i - 1; + if ( + ($passByReference) && + (isset(self::$phpSpreadsheetFunctions[$functionName]['passByReference'][$a])) && + (self::$phpSpreadsheetFunctions[$functionName]['passByReference'][$a]) + ) { + if ($arg['reference'] === null) { + $args[] = $cellID; + if ($functionName !== 'MKMATRIX') { + $argArrayVals[] = $this->showValue($cellID); + } + } else { + $args[] = $arg['reference']; + if ($functionName !== 'MKMATRIX') { + $argArrayVals[] = $this->showValue($arg['reference']); + } + } + } else { + $emptyArguments[] = ($arg['type'] === 'Empty Argument'); + $args[] = self::unwrapResult($arg['value']); + if ($functionName !== 'MKMATRIX') { + $argArrayVals[] = $this->showValue($arg['value']); + } + } + } + + // Reverse the order of the arguments + krsort($args); + krsort($emptyArguments); + + if ($argCount > 0) { + $args = $this->addDefaultArgumentValues($functionCall, $args, $emptyArguments); + } + + if (($passByReference) && ($argCount == 0)) { + $args[] = $cellID; + $argArrayVals[] = $this->showValue($cellID); + } + + if ($functionName !== 'MKMATRIX') { + if ($this->debugLog->getWriteDebugLog()) { + krsort($argArrayVals); + $this->debugLog->writeDebugLog('Evaluating ', self::localeFunc($functionName), '( ', implode(self::$localeArgumentSeparator . ' ', Functions::flattenArray($argArrayVals)), ' )'); + } + } + + // Process the argument with the appropriate function call + $args = $this->addCellReference($args, $passCellReference, $functionCall, $cell); + + if (!is_array($functionCall)) { + foreach ($args as &$arg) { + $arg = Functions::flattenSingleValue($arg); + } + unset($arg); + } + + $result = call_user_func_array($functionCall, $args); + + if ($functionName !== 'MKMATRIX') { + $this->debugLog->writeDebugLog('Evaluation Result for ', self::localeFunc($functionName), '() function call is ', $this->showTypeDetails($result)); + } + $stack->push('Value', self::wrapResult($result)); + if (isset($storeKey)) { + $branchStore[$storeKey] = $result; + } + } + } else { + // if the token is a number, boolean, string or an Excel error, push it onto the stack + if (isset(self::$excelConstants[strtoupper($token ?? '')])) { + $excelConstant = strtoupper($token); + $stack->push('Constant Value', self::$excelConstants[$excelConstant]); + if (isset($storeKey)) { + $branchStore[$storeKey] = self::$excelConstants[$excelConstant]; + } + $this->debugLog->writeDebugLog('Evaluating Constant ', $excelConstant, ' as ', $this->showTypeDetails(self::$excelConstants[$excelConstant])); + } elseif ((is_numeric($token)) || ($token === null) || (is_bool($token)) || ($token == '') || ($token[0] == self::FORMULA_STRING_QUOTE) || ($token[0] == '#')) { + $stack->push($tokenData['type'], $token, $tokenData['reference']); + if (isset($storeKey)) { + $branchStore[$storeKey] = $token; + } + // if the token is a named range or formula, evaluate it and push the result onto the stack + } elseif (preg_match('/^' . self::CALCULATION_REGEXP_DEFINEDNAME . '$/miu', $token, $matches)) { + $definedName = $matches[6]; + if ($cell === null || $pCellWorksheet === null) { + return $this->raiseFormulaError("undefined name '$token'"); + } + + $this->debugLog->writeDebugLog('Evaluating Defined Name ', $definedName); + $namedRange = DefinedName::resolveName($definedName, $pCellWorksheet); + if ($namedRange === null) { + return $this->raiseFormulaError("undefined name '$definedName'"); + } + + $result = $this->evaluateDefinedName($cell, $namedRange, $pCellWorksheet, $stack); + if (isset($storeKey)) { + $branchStore[$storeKey] = $result; + } + } else { + return $this->raiseFormulaError("undefined name '$token'"); + } + } + } + // when we're out of tokens, the stack should have a single element, the final result + if ($stack->count() != 1) { + return $this->raiseFormulaError('internal error'); + } + $output = $stack->pop(); + $output = $output['value']; + + return $output; + } + + private function validateBinaryOperand(&$operand, &$stack) + { + if (is_array($operand)) { + if ((count($operand, COUNT_RECURSIVE) - count($operand)) == 1) { + do { + $operand = array_pop($operand); + } while (is_array($operand)); + } + } + // Numbers, matrices and booleans can pass straight through, as they're already valid + if (is_string($operand)) { + // We only need special validations for the operand if it is a string + // Start by stripping off the quotation marks we use to identify true excel string values internally + if ($operand > '' && $operand[0] == self::FORMULA_STRING_QUOTE) { + $operand = self::unwrapResult($operand); + } + // If the string is a numeric value, we treat it as a numeric, so no further testing + if (!is_numeric($operand)) { + // If not a numeric, test to see if the value is an Excel error, and so can't be used in normal binary operations + if ($operand > '' && $operand[0] == '#') { + $stack->push('Value', $operand); + $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($operand)); + + return false; + } elseif (!Shared\StringHelper::convertToNumberIfFraction($operand)) { + // If not a numeric or a fraction, then it's a text string, and so can't be used in mathematical binary operations + $stack->push('Error', '#VALUE!'); + $this->debugLog->writeDebugLog('Evaluation Result is a ', $this->showTypeDetails('#VALUE!')); + + return false; + } + } + } + + // return a true if the value of the operand is one that we can use in normal binary operations + return true; + } + + /** + * @param null|string $cellID + * @param mixed $operand1 + * @param mixed $operand2 + * @param string $operation + * + * @return array + */ + private function executeArrayComparison($cellID, $operand1, $operand2, $operation, Stack &$stack, bool $recursingArrays) + { + $result = []; + if (!is_array($operand2)) { + // Operand 1 is an array, Operand 2 is a scalar + foreach ($operand1 as $x => $operandData) { + $this->debugLog->writeDebugLog('Evaluating Comparison ', $this->showValue($operandData), ' ', $operation, ' ', $this->showValue($operand2)); + $this->executeBinaryComparisonOperation($cellID, $operandData, $operand2, $operation, $stack); + $r = $stack->pop(); + $result[$x] = $r['value']; + } + } elseif (!is_array($operand1)) { + // Operand 1 is a scalar, Operand 2 is an array + foreach ($operand2 as $x => $operandData) { + $this->debugLog->writeDebugLog('Evaluating Comparison ', $this->showValue($operand1), ' ', $operation, ' ', $this->showValue($operandData)); + $this->executeBinaryComparisonOperation($cellID, $operand1, $operandData, $operation, $stack); + $r = $stack->pop(); + $result[$x] = $r['value']; + } + } else { + // Operand 1 and Operand 2 are both arrays + if (!$recursingArrays) { + self::checkMatrixOperands($operand1, $operand2, 2); + } + foreach ($operand1 as $x => $operandData) { + $this->debugLog->writeDebugLog('Evaluating Comparison ', $this->showValue($operandData), ' ', $operation, ' ', $this->showValue($operand2[$x])); + $this->executeBinaryComparisonOperation($cellID, $operandData, $operand2[$x], $operation, $stack, true); + $r = $stack->pop(); + $result[$x] = $r['value']; + } + } + // Log the result details + $this->debugLog->writeDebugLog('Comparison Evaluation Result is ', $this->showTypeDetails($result)); + // And push the result onto the stack + $stack->push('Array', $result); + + return $result; + } + + /** + * @param null|string $cellID + * @param mixed $operand1 + * @param mixed $operand2 + * @param string $operation + * @param bool $recursingArrays + * + * @return mixed + */ + private function executeBinaryComparisonOperation($cellID, $operand1, $operand2, $operation, Stack &$stack, $recursingArrays = false) + { + // If we're dealing with matrix operations, we want a matrix result + if ((is_array($operand1)) || (is_array($operand2))) { + return $this->executeArrayComparison($cellID, $operand1, $operand2, $operation, $stack, $recursingArrays); + } + + // Simple validate the two operands if they are string values + if (is_string($operand1) && $operand1 > '' && $operand1[0] == self::FORMULA_STRING_QUOTE) { + $operand1 = self::unwrapResult($operand1); + } + if (is_string($operand2) && $operand2 > '' && $operand2[0] == self::FORMULA_STRING_QUOTE) { + $operand2 = self::unwrapResult($operand2); + } + + // Use case insensitive comparaison if not OpenOffice mode + if (Functions::getCompatibilityMode() != Functions::COMPATIBILITY_OPENOFFICE) { + if (is_string($operand1)) { + $operand1 = Shared\StringHelper::strToUpper($operand1); + } + if (is_string($operand2)) { + $operand2 = Shared\StringHelper::strToUpper($operand2); + } + } + + $useLowercaseFirstComparison = is_string($operand1) && is_string($operand2) && Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE; + + // execute the necessary operation + switch ($operation) { + // Greater than + case '>': + if ($useLowercaseFirstComparison) { + $result = $this->strcmpLowercaseFirst($operand1, $operand2) > 0; + } else { + $result = ($operand1 > $operand2); + } + + break; + // Less than + case '<': + if ($useLowercaseFirstComparison) { + $result = $this->strcmpLowercaseFirst($operand1, $operand2) < 0; + } else { + $result = ($operand1 < $operand2); + } + + break; + // Equality + case '=': + if (is_numeric($operand1) && is_numeric($operand2)) { + $result = (abs($operand1 - $operand2) < $this->delta); + } else { + $result = $this->strcmpAllowNull($operand1, $operand2) == 0; + } + + break; + // Greater than or equal + case '>=': + if (is_numeric($operand1) && is_numeric($operand2)) { + $result = ((abs($operand1 - $operand2) < $this->delta) || ($operand1 > $operand2)); + } elseif ($useLowercaseFirstComparison) { + $result = $this->strcmpLowercaseFirst($operand1, $operand2) >= 0; + } else { + $result = $this->strcmpAllowNull($operand1, $operand2) >= 0; + } + + break; + // Less than or equal + case '<=': + if (is_numeric($operand1) && is_numeric($operand2)) { + $result = ((abs($operand1 - $operand2) < $this->delta) || ($operand1 < $operand2)); + } elseif ($useLowercaseFirstComparison) { + $result = $this->strcmpLowercaseFirst($operand1, $operand2) <= 0; + } else { + $result = $this->strcmpAllowNull($operand1, $operand2) <= 0; + } + + break; + // Inequality + case '<>': + if (is_numeric($operand1) && is_numeric($operand2)) { + $result = (abs($operand1 - $operand2) > 1E-14); + } else { + $result = $this->strcmpAllowNull($operand1, $operand2) != 0; + } + + break; + + default: + throw new Exception('Unsupported binary comparison operation'); + } + + // Log the result details + $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($result)); + // And push the result onto the stack + $stack->push('Value', $result); + + return $result; + } + + /** + * Compare two strings in the same way as strcmp() except that lowercase come before uppercase letters. + * + * @param null|string $str1 First string value for the comparison + * @param null|string $str2 Second string value for the comparison + * + * @return int + */ + private function strcmpLowercaseFirst($str1, $str2) + { + $inversedStr1 = Shared\StringHelper::strCaseReverse($str1); + $inversedStr2 = Shared\StringHelper::strCaseReverse($str2); + + return strcmp($inversedStr1 ?? '', $inversedStr2 ?? ''); + } + + /** + * PHP8.1 deprecates passing null to strcmp. + * + * @param null|string $str1 First string value for the comparison + * @param null|string $str2 Second string value for the comparison + * + * @return int + */ + private function strcmpAllowNull($str1, $str2) + { + return strcmp($str1 ?? '', $str2 ?? ''); + } + + /** + * @param mixed $operand1 + * @param mixed $operand2 + * @param mixed $operation + * @param string $matrixFunction + * @param mixed $stack + * + * @return bool|mixed + */ + private function executeNumericBinaryOperation($operand1, $operand2, $operation, $matrixFunction, &$stack) + { + // Validate the two operands + if (!$this->validateBinaryOperand($operand1, $stack)) { + return false; + } + if (!$this->validateBinaryOperand($operand2, $stack)) { + return false; + } + + // If either of the operands is a matrix, we need to treat them both as matrices + // (converting the other operand to a matrix if need be); then perform the required + // matrix operation + if ((is_array($operand1)) || (is_array($operand2))) { + // Ensure that both operands are arrays/matrices of the same size + self::checkMatrixOperands($operand1, $operand2, 2); + + try { + // Convert operand 1 from a PHP array to a matrix + $matrix = new Shared\JAMA\Matrix($operand1); + // Perform the required operation against the operand 1 matrix, passing in operand 2 + $matrixResult = $matrix->$matrixFunction($operand2); + $result = $matrixResult->getArray(); + } catch (\Exception $ex) { + $this->debugLog->writeDebugLog('JAMA Matrix Exception: ', $ex->getMessage()); + $result = '#VALUE!'; + } + } else { + if ( + (Functions::getCompatibilityMode() != Functions::COMPATIBILITY_OPENOFFICE) && + ((is_string($operand1) && !is_numeric($operand1) && strlen($operand1) > 0) || + (is_string($operand2) && !is_numeric($operand2) && strlen($operand2) > 0)) + ) { + $result = Functions::VALUE(); + } else { + // If we're dealing with non-matrix operations, execute the necessary operation + switch ($operation) { + // Addition + case '+': + $result = $operand1 + $operand2; + + break; + // Subtraction + case '-': + $result = $operand1 - $operand2; + + break; + // Multiplication + case '*': + $result = $operand1 * $operand2; + + break; + // Division + case '/': + if ($operand2 == 0) { + // Trap for Divide by Zero error + $stack->push('Error', '#DIV/0!'); + $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails('#DIV/0!')); + + return false; + } + $result = $operand1 / $operand2; + + break; + // Power + case '^': + $result = $operand1 ** $operand2; + + break; + + default: + throw new Exception('Unsupported numeric binary operation'); + } + } + } + + // Log the result details + $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($result)); + // And push the result onto the stack + $stack->push('Value', $result); + + return $result; + } + + // trigger an error, but nicely, if need be + protected function raiseFormulaError($errorMessage) + { + $this->formulaError = $errorMessage; + $this->cyclicReferenceStack->clear(); + if (!$this->suppressFormulaErrors) { + throw new Exception($errorMessage); + } + trigger_error($errorMessage, E_USER_ERROR); + + return false; + } + + /** + * Extract range values. + * + * @param string $range String based range representation + * @param Worksheet $worksheet Worksheet + * @param bool $resetLog Flag indicating whether calculation log should be reset or not + * + * @return mixed Array of values in range if range contains more than one element. Otherwise, a single value is returned. + */ + public function extractCellRange(&$range = 'A1', ?Worksheet $worksheet = null, $resetLog = true) + { + // Return value + $returnValue = []; + + if ($worksheet !== null) { + $worksheetName = $worksheet->getTitle(); + + if (strpos($range, '!') !== false) { + [$worksheetName, $range] = Worksheet::extractSheetTitle($range, true); + $worksheet = $this->spreadsheet->getSheetByName($worksheetName); + } + + // Extract range + $aReferences = Coordinate::extractAllCellReferencesInRange($range); + $range = "'" . $worksheetName . "'" . '!' . $range; + if (!isset($aReferences[1])) { + $currentCol = ''; + $currentRow = 0; + // Single cell in range + sscanf($aReferences[0], '%[A-Z]%d', $currentCol, $currentRow); + if ($worksheet->cellExists($aReferences[0])) { + $returnValue[$currentRow][$currentCol] = $worksheet->getCell($aReferences[0])->getCalculatedValue($resetLog); + } else { + $returnValue[$currentRow][$currentCol] = null; + } + } else { + // Extract cell data for all cells in the range + foreach ($aReferences as $reference) { + $currentCol = ''; + $currentRow = 0; + // Extract range + sscanf($reference, '%[A-Z]%d', $currentCol, $currentRow); + if ($worksheet->cellExists($reference)) { + $returnValue[$currentRow][$currentCol] = $worksheet->getCell($reference)->getCalculatedValue($resetLog); + } else { + $returnValue[$currentRow][$currentCol] = null; + } + } + } + } + + return $returnValue; + } + + /** + * Extract range values. + * + * @param string $range String based range representation + * @param null|Worksheet $worksheet Worksheet + * @param bool $resetLog Flag indicating whether calculation log should be reset or not + * + * @return mixed Array of values in range if range contains more than one element. Otherwise, a single value is returned. + */ + public function extractNamedRange(string &$range = 'A1', ?Worksheet $worksheet = null, $resetLog = true) + { + // Return value + $returnValue = []; + + if ($worksheet !== null) { + if (strpos($range, '!') !== false) { + [$worksheetName, $range] = Worksheet::extractSheetTitle($range, true); + $worksheet = $this->spreadsheet->getSheetByName($worksheetName); + } + + // Named range? + $namedRange = DefinedName::resolveName($range, $worksheet); + if ($namedRange === null) { + return Functions::REF(); + } + + $worksheet = $namedRange->getWorksheet(); + $range = $namedRange->getValue(); + $splitRange = Coordinate::splitRange($range); + // Convert row and column references + if (ctype_alpha($splitRange[0][0])) { + $range = $splitRange[0][0] . '1:' . $splitRange[0][1] . $namedRange->getWorksheet()->getHighestRow(); + } elseif (ctype_digit($splitRange[0][0])) { + $range = 'A' . $splitRange[0][0] . ':' . $namedRange->getWorksheet()->getHighestColumn() . $splitRange[0][1]; + } + + // Extract range + $aReferences = Coordinate::extractAllCellReferencesInRange($range); + if (!isset($aReferences[1])) { + // Single cell (or single column or row) in range + [$currentCol, $currentRow] = Coordinate::coordinateFromString($aReferences[0]); + if ($worksheet->cellExists($aReferences[0])) { + $returnValue[$currentRow][$currentCol] = $worksheet->getCell($aReferences[0])->getCalculatedValue($resetLog); + } else { + $returnValue[$currentRow][$currentCol] = null; + } + } else { + // Extract cell data for all cells in the range + foreach ($aReferences as $reference) { + // Extract range + [$currentCol, $currentRow] = Coordinate::coordinateFromString($reference); + if ($worksheet->cellExists($reference)) { + $returnValue[$currentRow][$currentCol] = $worksheet->getCell($reference)->getCalculatedValue($resetLog); + } else { + $returnValue[$currentRow][$currentCol] = null; + } + } + } + } + + return $returnValue; + } + + /** + * Is a specific function implemented? + * + * @param string $function Function Name + * + * @return bool + */ + public function isImplemented($function) + { + $function = strtoupper($function); + $notImplemented = !isset(self::$phpSpreadsheetFunctions[$function]) || (is_array(self::$phpSpreadsheetFunctions[$function]['functionCall']) && self::$phpSpreadsheetFunctions[$function]['functionCall'][1] === 'DUMMY'); + + return !$notImplemented; + } + + /** + * Get a list of all implemented functions as an array of function objects. + */ + public function getFunctions(): array + { + return self::$phpSpreadsheetFunctions; + } + + /** + * Get a list of implemented Excel function names. + * + * @return array + */ + public function getImplementedFunctionNames() + { + $returnValue = []; + foreach (self::$phpSpreadsheetFunctions as $functionName => $function) { + if ($this->isImplemented($functionName)) { + $returnValue[] = $functionName; + } + } + + return $returnValue; + } + + private function addDefaultArgumentValues(array $functionCall, array $args, array $emptyArguments): array + { + $reflector = new ReflectionMethod(implode('::', $functionCall)); + $methodArguments = $reflector->getParameters(); + + if (count($methodArguments) > 0) { + // Apply any defaults for empty argument values + foreach ($emptyArguments as $argumentId => $isArgumentEmpty) { + if ($isArgumentEmpty === true) { + $reflectedArgumentId = count($args) - (int) $argumentId - 1; + if ( + !array_key_exists($reflectedArgumentId, $methodArguments) || + $methodArguments[$reflectedArgumentId]->isVariadic() + ) { + break; + } + + $args[$argumentId] = $this->getArgumentDefaultValue($methodArguments[$reflectedArgumentId]); + } + } + } + + return $args; + } + + /** + * @return null|mixed + */ + private function getArgumentDefaultValue(ReflectionParameter $methodArgument) + { + $defaultValue = null; + + if ($methodArgument->isDefaultValueAvailable()) { + $defaultValue = $methodArgument->getDefaultValue(); + if ($methodArgument->isDefaultValueConstant()) { + $constantName = $methodArgument->getDefaultValueConstantName() ?? ''; + // read constant value + if (strpos($constantName, '::') !== false) { + [$className, $constantName] = explode('::', $constantName); + $constantReflector = new ReflectionClassConstant($className, $constantName); + + return $constantReflector->getValue(); + } + + return constant($constantName); + } + } + + return $defaultValue; + } + + /** + * Add cell reference if needed while making sure that it is the last argument. + * + * @param bool $passCellReference + * @param array|string $functionCall + * + * @return array + */ + private function addCellReference(array $args, $passCellReference, $functionCall, ?Cell $cell = null) + { + if ($passCellReference) { + if (is_array($functionCall)) { + $className = $functionCall[0]; + $methodName = $functionCall[1]; + + $reflectionMethod = new ReflectionMethod($className, $methodName); + $argumentCount = count($reflectionMethod->getParameters()); + while (count($args) < $argumentCount - 1) { + $args[] = null; + } + } + + $args[] = $cell; + } + + return $args; + } + + private function getUnusedBranchStoreKey() + { + $storeKeyValue = 'storeKey-' . $this->branchStoreKeyCounter; + ++$this->branchStoreKeyCounter; + + return $storeKeyValue; + } + + private function getTokensAsString($tokens) + { + $tokensStr = array_map(function ($token) { + $value = $token['value'] ?? 'no value'; + while (is_array($value)) { + $value = array_pop($value); + } + + return $value; + }, $tokens); + + return '[ ' . implode(' | ', $tokensStr) . ' ]'; + } + + /** + * @return mixed|string + */ + private function evaluateDefinedName(Cell $cell, DefinedName $namedRange, Worksheet $cellWorksheet, Stack $stack) + { + $definedNameScope = $namedRange->getScope(); + if ($definedNameScope !== null && $definedNameScope !== $cellWorksheet) { + // The defined name isn't in our current scope, so #REF + $result = Functions::REF(); + $stack->push('Error', $result, $namedRange->getName()); + + return $result; + } + + $definedNameValue = $namedRange->getValue(); + $definedNameType = $namedRange->isFormula() ? 'Formula' : 'Range'; + $definedNameWorksheet = $namedRange->getWorksheet(); + + if ($definedNameValue[0] !== '=') { + $definedNameValue = '=' . $definedNameValue; + } + + $this->debugLog->writeDebugLog("Defined Name is a {$definedNameType} with a value of {$definedNameValue}"); + + $recursiveCalculationCell = ($definedNameWorksheet !== null && $definedNameWorksheet !== $cellWorksheet) + ? $definedNameWorksheet->getCell('A1') + : $cell; + $recursiveCalculationCellAddress = $recursiveCalculationCell->getCoordinate(); + + // Adjust relative references in ranges and formulae so that we execute the calculation for the correct rows and columns + $definedNameValue = self::$referenceHelper->updateFormulaReferencesAnyWorksheet( + $definedNameValue, + Coordinate::columnIndexFromString($cell->getColumn()) - 1, + $cell->getRow() - 1 + ); + + $this->debugLog->writeDebugLog("Value adjusted for relative references is {$definedNameValue}"); + + $recursiveCalculator = new self($this->spreadsheet); + $recursiveCalculator->getDebugLog()->setWriteDebugLog($this->getDebugLog()->getWriteDebugLog()); + $recursiveCalculator->getDebugLog()->setEchoDebugLog($this->getDebugLog()->getEchoDebugLog()); + $result = $recursiveCalculator->_calculateFormulaValue($definedNameValue, $recursiveCalculationCellAddress, $recursiveCalculationCell); + + if ($this->getDebugLog()->getWriteDebugLog()) { + $this->debugLog->mergeDebugLog(array_slice($recursiveCalculator->getDebugLog()->getLog(), 3)); + $this->debugLog->writeDebugLog("Evaluation Result for Named {$definedNameType} {$namedRange->getName()} is {$this->showTypeDetails($result)}"); + } + + $stack->push('Defined Name', $result, $namedRange->getName()); + + return $result; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Category.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Category.php new file mode 100644 index 0000000..96bb72a --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Category.php @@ -0,0 +1,20 @@ + 1) { + return Functions::NAN(); + } + + $row = array_pop($columnData); + + return array_pop($row); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DMax.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DMax.php new file mode 100644 index 0000000..9c5c730 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DMax.php @@ -0,0 +1,46 @@ + $row) { + $keys = array_keys($row); + $key = $keys[$field] ?? null; + $columnKey = $key ?? 'A'; + $columnData[$rowKey][$columnKey] = $row[$key] ?? $defaultReturnColumnValue; + } + + return $columnData; + } + + private static function buildQuery(array $criteriaNames, array $criteria): string + { + $baseQuery = []; + foreach ($criteria as $key => $criterion) { + foreach ($criterion as $field => $value) { + $criterionName = $criteriaNames[$field]; + if ($value !== null) { + $condition = self::buildCondition($value, $criterionName); + $baseQuery[$key][] = $condition; + } + } + } + + $rowQuery = array_map( + function ($rowValue) { + return (count($rowValue) > 1) ? 'AND(' . implode(',', $rowValue) . ')' : ($rowValue[0] ?? ''); + }, + $baseQuery + ); + + return (count($rowQuery) > 1) ? 'OR(' . implode(',', $rowQuery) . ')' : ($rowQuery[0] ?? ''); + } + + private static function buildCondition($criterion, string $criterionName): string + { + $ifCondition = Functions::ifCondition($criterion); + + // Check for wildcard characters used in the condition + $result = preg_match('/(?[^"]*)(?".*[*?].*")/ui', $ifCondition, $matches); + if ($result !== 1) { + return "[:{$criterionName}]{$ifCondition}"; + } + + $trueFalse = ($matches['operator'] !== '<>'); + $wildcard = WildcardMatch::wildcard($matches['operand']); + $condition = "WILDCARDMATCH([:{$criterionName}],{$wildcard})"; + if ($trueFalse === false) { + $condition = "NOT({$condition})"; + } + + return $condition; + } + + private static function executeQuery(array $database, string $query, array $criteria, array $fields): array + { + foreach ($database as $dataRow => $dataValues) { + // Substitute actual values from the database row for our [:placeholders] + $conditions = $query; + foreach ($criteria as $criterion) { + $conditions = self::processCondition($criterion, $fields, $dataValues, $conditions); + } + + // evaluate the criteria against the row data + $result = Calculation::getInstance()->_calculateFormulaValue('=' . $conditions); + + // If the row failed to meet the criteria, remove it from the database + if ($result !== true) { + unset($database[$dataRow]); + } + } + + return $database; + } + + private static function processCondition(string $criterion, array $fields, array $dataValues, string $conditions) + { + $key = array_search($criterion, $fields, true); + + $dataValue = 'NULL'; + if (is_bool($dataValues[$key])) { + $dataValue = ($dataValues[$key]) ? 'TRUE' : 'FALSE'; + } elseif ($dataValues[$key] !== null) { + $dataValue = $dataValues[$key]; + // escape quotes if we have a string containing quotes + if (is_string($dataValue) && strpos($dataValue, '"') !== false) { + $dataValue = str_replace('"', '""', $dataValue); + } + $dataValue = (is_string($dataValue)) ? Calculation::wrapResult(strtoupper($dataValue)) : $dataValue; + } + + return str_replace('[:' . $criterion . ']', $dataValue, $conditions); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTime.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTime.php new file mode 100644 index 0000000..44a38c1 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTime.php @@ -0,0 +1,915 @@ +getMessage(); + } + } + + /** + * DATETIMENOW. + * + * Returns the current date and time. + * The NOW function is useful when you need to display the current date and time on a worksheet or + * calculate a value based on the current date and time, and have that value updated each time you + * open the worksheet. + * + * NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the date + * and time format of your regional settings. PhpSpreadsheet does not change cell formatting in this way. + * + * Excel Function: + * NOW() + * + * @Deprecated 1.18.0 + * + * @See DateTimeExcel\Current::now() + * Use the now method in the DateTimeExcel\Current class instead + * + * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object, + * depending on the value of the ReturnDateType flag + */ + public static function DATETIMENOW() + { + return DateTimeExcel\Current::now(); + } + + /** + * DATENOW. + * + * Returns the current date. + * The NOW function is useful when you need to display the current date and time on a worksheet or + * calculate a value based on the current date and time, and have that value updated each time you + * open the worksheet. + * + * NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the date + * and time format of your regional settings. PhpSpreadsheet does not change cell formatting in this way. + * + * Excel Function: + * TODAY() + * + * @Deprecated 1.18.0 + * + * @See DateTimeExcel\Current::today() + * Use the today method in the DateTimeExcel\Current class instead + * + * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object, + * depending on the value of the ReturnDateType flag + */ + public static function DATENOW() + { + return DateTimeExcel\Current::today(); + } + + /** + * DATE. + * + * The DATE function returns a value that represents a particular date. + * + * NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the date + * format of your regional settings. PhpSpreadsheet does not change cell formatting in this way. + * + * + * Excel Function: + * DATE(year,month,day) + * + * @Deprecated 1.18.0 + * + * @See DateTimeExcel\Date::fromYMD() + * Use the fromYMD method in the DateTimeExcel\Date class instead + * + * PhpSpreadsheet is a lot more forgiving than MS Excel when passing non numeric values to this function. + * A Month name or abbreviation (English only at this point) such as 'January' or 'Jan' will still be accepted, + * as will a day value with a suffix (e.g. '21st' rather than simply 21); again only English language. + * + * @param int $year The value of the year argument can include one to four digits. + * Excel interprets the year argument according to the configured + * date system: 1900 or 1904. + * If year is between 0 (zero) and 1899 (inclusive), Excel adds that + * value to 1900 to calculate the year. For example, DATE(108,1,2) + * returns January 2, 2008 (1900+108). + * If year is between 1900 and 9999 (inclusive), Excel uses that + * value as the year. For example, DATE(2008,1,2) returns January 2, + * 2008. + * If year is less than 0 or is 10000 or greater, Excel returns the + * #NUM! error value. + * @param int $month A positive or negative integer representing the month of the year + * from 1 to 12 (January to December). + * If month is greater than 12, month adds that number of months to + * the first month in the year specified. For example, DATE(2008,14,2) + * returns the serial number representing February 2, 2009. + * If month is less than 1, month subtracts the magnitude of that + * number of months, plus 1, from the first month in the year + * specified. For example, DATE(2008,-3,2) returns the serial number + * representing September 2, 2007. + * @param int $day A positive or negative integer representing the day of the month + * from 1 to 31. + * If day is greater than the number of days in the month specified, + * day adds that number of days to the first day in the month. For + * example, DATE(2008,1,35) returns the serial number representing + * February 4, 2008. + * If day is less than 1, day subtracts the magnitude that number of + * days, plus one, from the first day of the month specified. For + * example, DATE(2008,1,-15) returns the serial number representing + * December 16, 2007. + * + * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object, + * depending on the value of the ReturnDateType flag + */ + public static function DATE($year = 0, $month = 1, $day = 1) + { + return DateTimeExcel\Date::fromYMD($year, $month, $day); + } + + /** + * TIME. + * + * The TIME function returns a value that represents a particular time. + * + * NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the time + * format of your regional settings. PhpSpreadsheet does not change cell formatting in this way. + * + * Excel Function: + * TIME(hour,minute,second) + * + * @Deprecated 1.18.0 + * + * @See DateTimeExcel\Time::fromHMS() + * Use the fromHMS method in the DateTimeExcel\Time class instead + * + * @param int $hour A number from 0 (zero) to 32767 representing the hour. + * Any value greater than 23 will be divided by 24 and the remainder + * will be treated as the hour value. For example, TIME(27,0,0) = + * TIME(3,0,0) = .125 or 3:00 AM. + * @param int $minute A number from 0 to 32767 representing the minute. + * Any value greater than 59 will be converted to hours and minutes. + * For example, TIME(0,750,0) = TIME(12,30,0) = .520833 or 12:30 PM. + * @param int $second A number from 0 to 32767 representing the second. + * Any value greater than 59 will be converted to hours, minutes, + * and seconds. For example, TIME(0,0,2000) = TIME(0,33,22) = .023148 + * or 12:33:20 AM + * + * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object, + * depending on the value of the ReturnDateType flag + */ + public static function TIME($hour = 0, $minute = 0, $second = 0) + { + return DateTimeExcel\Time::fromHMS($hour, $minute, $second); + } + + /** + * DATEVALUE. + * + * Returns a value that represents a particular date. + * Use DATEVALUE to convert a date represented by a text string to an Excel or PHP date/time stamp + * value. + * + * NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the date + * format of your regional settings. PhpSpreadsheet does not change cell formatting in this way. + * + * Excel Function: + * DATEVALUE(dateValue) + * + * @Deprecated 1.18.0 + * + * @See DateTimeExcel\DateValue::fromString() + * Use the fromString method in the DateTimeExcel\DateValue class instead + * + * @param string $dateValue Text that represents a date in a Microsoft Excel date format. + * For example, "1/30/2008" or "30-Jan-2008" are text strings within + * quotation marks that represent dates. Using the default date + * system in Excel for Windows, date_text must represent a date from + * January 1, 1900, to December 31, 9999. Using the default date + * system in Excel for the Macintosh, date_text must represent a date + * from January 1, 1904, to December 31, 9999. DATEVALUE returns the + * #VALUE! error value if date_text is out of this range. + * + * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object, + * depending on the value of the ReturnDateType flag + */ + public static function DATEVALUE($dateValue) + { + return DateTimeExcel\DateValue::fromString($dateValue); + } + + /** + * TIMEVALUE. + * + * Returns a value that represents a particular time. + * Use TIMEVALUE to convert a time represented by a text string to an Excel or PHP date/time stamp + * value. + * + * NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the time + * format of your regional settings. PhpSpreadsheet does not change cell formatting in this way. + * + * Excel Function: + * TIMEVALUE(timeValue) + * + * @Deprecated 1.18.0 + * + * @See DateTimeExcel\TimeValue::fromString() + * Use the fromString method in the DateTimeExcel\TimeValue class instead + * + * @param string $timeValue A text string that represents a time in any one of the Microsoft + * Excel time formats; for example, "6:45 PM" and "18:45" text strings + * within quotation marks that represent time. + * Date information in time_text is ignored. + * + * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object, + * depending on the value of the ReturnDateType flag + */ + public static function TIMEVALUE($timeValue) + { + return DateTimeExcel\TimeValue::fromString($timeValue); + } + + /** + * DATEDIF. + * + * Excel Function: + * DATEDIF(startdate, enddate, unit) + * + * @Deprecated 1.18.0 + * + * @See DateTimeExcel\Difference::interval() + * Use the interval method in the DateTimeExcel\Difference class instead + * + * @param mixed $startDate Excel date serial value, PHP date/time stamp, PHP DateTime object + * or a standard date string + * @param mixed $endDate Excel date serial value, PHP date/time stamp, PHP DateTime object + * or a standard date string + * @param string $unit + * + * @return int|string Interval between the dates + */ + public static function DATEDIF($startDate = 0, $endDate = 0, $unit = 'D') + { + return DateTimeExcel\Difference::interval($startDate, $endDate, $unit); + } + + /** + * DAYS. + * + * Returns the number of days between two dates + * + * Excel Function: + * DAYS(endDate, startDate) + * + * @Deprecated 1.18.0 + * + * @See DateTimeExcel\Days::between() + * Use the between method in the DateTimeExcel\Days class instead + * + * @param DateTimeInterface|float|int|string $endDate Excel date serial value (float), + * PHP date timestamp (integer), PHP DateTime object, or a standard date string + * @param DateTimeInterface|float|int|string $startDate Excel date serial value (float), + * PHP date timestamp (integer), PHP DateTime object, or a standard date string + * + * @return int|string Number of days between start date and end date or an error + */ + public static function DAYS($endDate = 0, $startDate = 0) + { + return DateTimeExcel\Days::between($endDate, $startDate); + } + + /** + * DAYS360. + * + * Returns the number of days between two dates based on a 360-day year (twelve 30-day months), + * which is used in some accounting calculations. Use this function to help compute payments if + * your accounting system is based on twelve 30-day months. + * + * Excel Function: + * DAYS360(startDate,endDate[,method]) + * + * @Deprecated 1.18.0 + * + * @See DateTimeExcel\Days360::between() + * Use the between method in the DateTimeExcel\Days360 class instead + * + * @param mixed $startDate Excel date serial value (float), PHP date timestamp (integer), + * PHP DateTime object, or a standard date string + * @param mixed $endDate Excel date serial value (float), PHP date timestamp (integer), + * PHP DateTime object, or a standard date string + * @param bool $method US or European Method + * FALSE or omitted: U.S. (NASD) method. If the starting date is + * the last day of a month, it becomes equal to the 30th of the + * same month. If the ending date is the last day of a month and + * the starting date is earlier than the 30th of a month, the + * ending date becomes equal to the 1st of the next month; + * otherwise the ending date becomes equal to the 30th of the + * same month. + * TRUE: European method. Starting dates and ending dates that + * occur on the 31st of a month become equal to the 30th of the + * same month. + * + * @return int|string Number of days between start date and end date + */ + public static function DAYS360($startDate = 0, $endDate = 0, $method = false) + { + return DateTimeExcel\Days360::between($startDate, $endDate, $method); + } + + /** + * YEARFRAC. + * + * Calculates the fraction of the year represented by the number of whole days between two dates + * (the start_date and the end_date). + * Use the YEARFRAC worksheet function to identify the proportion of a whole year's benefits or + * obligations to assign to a specific term. + * + * Excel Function: + * YEARFRAC(startDate,endDate[,method]) + * + * @Deprecated 1.18.0 + * + * @See DateTimeExcel\YearFrac::fraction() + * Use the fraction method in the DateTimeExcel\YearFrac class instead + * + * See https://lists.oasis-open.org/archives/office-formula/200806/msg00039.html + * for description of algorithm used in Excel + * + * @param mixed $startDate Excel date serial value (float), PHP date timestamp (integer), + * PHP DateTime object, or a standard date string + * @param mixed $endDate Excel date serial value (float), PHP date timestamp (integer), + * PHP DateTime object, or a standard date string + * @param int $method Method used for the calculation + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + * + * @return float|string fraction of the year, or a string containing an error + */ + public static function YEARFRAC($startDate = 0, $endDate = 0, $method = 0) + { + return DateTimeExcel\YearFrac::fraction($startDate, $endDate, $method); + } + + /** + * NETWORKDAYS. + * + * Returns the number of whole working days between start_date and end_date. Working days + * exclude weekends and any dates identified in holidays. + * Use NETWORKDAYS to calculate employee benefits that accrue based on the number of days + * worked during a specific term. + * + * Excel Function: + * NETWORKDAYS(startDate,endDate[,holidays[,holiday[,...]]]) + * + * @Deprecated 1.18.0 + * + * @See DateTimeExcel\NetworkDays::count() + * Use the count method in the DateTimeExcel\NetworkDays class instead + * + * @param mixed $startDate Excel date serial value (float), PHP date timestamp (integer), + * PHP DateTime object, or a standard date string + * @param mixed $endDate Excel date serial value (float), PHP date timestamp (integer), + * PHP DateTime object, or a standard date string + * @param mixed $dateArgs + * + * @return int|string Interval between the dates + */ + public static function NETWORKDAYS($startDate, $endDate, ...$dateArgs) + { + return DateTimeExcel\NetworkDays::count($startDate, $endDate, ...$dateArgs); + } + + /** + * WORKDAY. + * + * Returns the date that is the indicated number of working days before or after a date (the + * starting date). Working days exclude weekends and any dates identified as holidays. + * Use WORKDAY to exclude weekends or holidays when you calculate invoice due dates, expected + * delivery times, or the number of days of work performed. + * + * Excel Function: + * WORKDAY(startDate,endDays[,holidays[,holiday[,...]]]) + * + * @Deprecated 1.18.0 + * + * @See DateTimeExcel\WorkDay::date() + * Use the date method in the DateTimeExcel\WorkDay class instead + * + * @param mixed $startDate Excel date serial value (float), PHP date timestamp (integer), + * PHP DateTime object, or a standard date string + * @param int $endDays The number of nonweekend and nonholiday days before or after + * startDate. A positive value for days yields a future date; a + * negative value yields a past date. + * @param mixed $dateArgs + * + * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object, + * depending on the value of the ReturnDateType flag + */ + public static function WORKDAY($startDate, $endDays, ...$dateArgs) + { + return DateTimeExcel\WorkDay::date($startDate, $endDays, ...$dateArgs); + } + + /** + * DAYOFMONTH. + * + * Returns the day of the month, for a specified date. The day is given as an integer + * ranging from 1 to 31. + * + * Excel Function: + * DAY(dateValue) + * + * @Deprecated 1.18.0 + * + * @See DateTimeExcel\DateParts::day() + * Use the day method in the DateTimeExcel\DateParts class instead + * + * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), + * PHP DateTime object, or a standard date string + * + * @return int|string Day of the month + */ + public static function DAYOFMONTH($dateValue = 1) + { + return DateTimeExcel\DateParts::day($dateValue); + } + + /** + * WEEKDAY. + * + * Returns the day of the week for a specified date. The day is given as an integer + * ranging from 0 to 7 (dependent on the requested style). + * + * Excel Function: + * WEEKDAY(dateValue[,style]) + * + * @Deprecated 1.18.0 + * + * @See DateTimeExcel\Week::day() + * Use the day method in the DateTimeExcel\Week class instead + * + * @param float|int|string $dateValue Excel date serial value (float), PHP date timestamp (integer), + * PHP DateTime object, or a standard date string + * @param int $style A number that determines the type of return value + * 1 or omitted Numbers 1 (Sunday) through 7 (Saturday). + * 2 Numbers 1 (Monday) through 7 (Sunday). + * 3 Numbers 0 (Monday) through 6 (Sunday). + * + * @return int|string Day of the week value + */ + public static function WEEKDAY($dateValue = 1, $style = 1) + { + return DateTimeExcel\Week::day($dateValue, $style); + } + + /** + * STARTWEEK_SUNDAY. + * + * @Deprecated 1.18.0 + * + * @see Use DateTimeExcel\Constants\STARTWEEK_SUNDAY instead + */ + const STARTWEEK_SUNDAY = 1; + + /** + * STARTWEEK_MONDAY. + * + * @Deprecated 1.18.0 + * + * @see Use DateTimeExcel\Constants\STARTWEEK_MONDAY instead + */ + const STARTWEEK_MONDAY = 2; + + /** + * STARTWEEK_MONDAY_ALT. + * + * @Deprecated 1.18.0 + * + * @see Use DateTimeExcel\Constants\STARTWEEK_MONDAY_ALT instead + */ + const STARTWEEK_MONDAY_ALT = 11; + + /** + * STARTWEEK_TUESDAY. + * + * @Deprecated 1.18.0 + * + * @see Use DateTimeExcel\Constants\STARTWEEK_TUESDAY instead + */ + const STARTWEEK_TUESDAY = 12; + + /** + * STARTWEEK_WEDNESDAY. + * + * @Deprecated 1.18.0 + * + * @see Use DateTimeExcel\Constants\STARTWEEK_WEDNESDAY instead + */ + const STARTWEEK_WEDNESDAY = 13; + + /** + * STARTWEEK_THURSDAY. + * + * @Deprecated 1.18.0 + * + * @see Use DateTimeExcel\Constants\STARTWEEK_THURSDAY instead + */ + const STARTWEEK_THURSDAY = 14; + + /** + * STARTWEEK_FRIDAY. + * + * @Deprecated 1.18.0 + * + * @see Use DateTimeExcel\Constants\STARTWEEK_FRIDAY instead + */ + const STARTWEEK_FRIDAY = 15; + + /** + * STARTWEEK_SATURDAY. + * + * @Deprecated 1.18.0 + * + * @see Use DateTimeExcel\Constants\STARTWEEK_SATURDAY instead + */ + const STARTWEEK_SATURDAY = 16; + + /** + * STARTWEEK_SUNDAY_ALT. + * + * @Deprecated 1.18.0 + * + * @see Use DateTimeExcel\Constants\STARTWEEK_SUNDAY_ALT instead + */ + const STARTWEEK_SUNDAY_ALT = 17; + + /** + * DOW_SUNDAY. + * + * @Deprecated 1.18.0 + * + * @see Use DateTimeExcel\Constants\DOW_SUNDAY instead + */ + const DOW_SUNDAY = 1; + + /** + * DOW_MONDAY. + * + * @Deprecated 1.18.0 + * + * @see Use DateTimeExcel\Constants\DOW_MONDAY instead + */ + const DOW_MONDAY = 2; + + /** + * DOW_TUESDAY. + * + * @Deprecated 1.18.0 + * + * @see Use DateTimeExcel\Constants\DOW_TUESDAY instead + */ + const DOW_TUESDAY = 3; + + /** + * DOW_WEDNESDAY. + * + * @Deprecated 1.18.0 + * + * @see Use DateTimeExcel\Constants\DOW_WEDNESDAY instead + */ + const DOW_WEDNESDAY = 4; + + /** + * DOW_THURSDAY. + * + * @Deprecated 1.18.0 + * + * @see Use DateTimeExcel\Constants\DOW_THURSDAY instead + */ + const DOW_THURSDAY = 5; + + /** + * DOW_FRIDAY. + * + * @Deprecated 1.18.0 + * + * @see Use DateTimeExcel\Constants\DOW_FRIDAY instead + */ + const DOW_FRIDAY = 6; + + /** + * DOW_SATURDAY. + * + * @Deprecated 1.18.0 + * + * @see Use DateTimeExcel\Constants\DOW_SATURDAY instead + */ + const DOW_SATURDAY = 7; + + /** + * STARTWEEK_MONDAY_ISO. + * + * @Deprecated 1.18.0 + * + * @see Use DateTimeExcel\Constants\STARTWEEK_MONDAY_ISO instead + */ + const STARTWEEK_MONDAY_ISO = 21; + + /** + * METHODARR. + * + * @Deprecated 1.18.0 + * + * @see Use DateTimeExcel\Constants\METHODARR instead + */ + const METHODARR = [ + self::STARTWEEK_SUNDAY => self::DOW_SUNDAY, + self::DOW_MONDAY, + self::STARTWEEK_MONDAY_ALT => self::DOW_MONDAY, + self::DOW_TUESDAY, + self::DOW_WEDNESDAY, + self::DOW_THURSDAY, + self::DOW_FRIDAY, + self::DOW_SATURDAY, + self::DOW_SUNDAY, + self::STARTWEEK_MONDAY_ISO => self::STARTWEEK_MONDAY_ISO, + ]; + + /** + * WEEKNUM. + * + * Returns the week of the year for a specified date. + * The WEEKNUM function considers the week containing January 1 to be the first week of the year. + * However, there is a European standard that defines the first week as the one with the majority + * of days (four or more) falling in the new year. This means that for years in which there are + * three days or less in the first week of January, the WEEKNUM function returns week numbers + * that are incorrect according to the European standard. + * + * Excel Function: + * WEEKNUM(dateValue[,style]) + * + * @Deprecated 1.18.0 + * + * @See DateTimeExcel\Week::number(() + * Use the number method in the DateTimeExcel\Week class instead + * + * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), + * PHP DateTime object, or a standard date string + * @param int $method Week begins on Sunday or Monday + * 1 or omitted Week begins on Sunday. + * 2 Week begins on Monday. + * 11 Week begins on Monday. + * 12 Week begins on Tuesday. + * 13 Week begins on Wednesday. + * 14 Week begins on Thursday. + * 15 Week begins on Friday. + * 16 Week begins on Saturday. + * 17 Week begins on Sunday. + * 21 ISO (Jan. 4 is week 1, begins on Monday). + * + * @return int|string Week Number + */ + public static function WEEKNUM($dateValue = 1, $method = self::STARTWEEK_SUNDAY) + { + return DateTimeExcel\Week::number($dateValue, $method); + } + + /** + * ISOWEEKNUM. + * + * Returns the ISO 8601 week number of the year for a specified date. + * + * Excel Function: + * ISOWEEKNUM(dateValue) + * + * @Deprecated 1.18.0 + * + * @See DateTimeExcel\Week::isoWeekNumber() + * Use the isoWeekNumber method in the DateTimeExcel\Week class instead + * + * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), + * PHP DateTime object, or a standard date string + * + * @return int|string Week Number + */ + public static function ISOWEEKNUM($dateValue = 1) + { + return DateTimeExcel\Week::isoWeekNumber($dateValue); + } + + /** + * MONTHOFYEAR. + * + * Returns the month of a date represented by a serial number. + * The month is given as an integer, ranging from 1 (January) to 12 (December). + * + * Excel Function: + * MONTH(dateValue) + * + * @Deprecated 1.18.0 + * + * @See DateTimeExcel\DateParts::month() + * Use the month method in the DateTimeExcel\DateParts class instead + * + * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), + * PHP DateTime object, or a standard date string + * + * @return int|string Month of the year + */ + public static function MONTHOFYEAR($dateValue = 1) + { + return DateTimeExcel\DateParts::month($dateValue); + } + + /** + * YEAR. + * + * Returns the year corresponding to a date. + * The year is returned as an integer in the range 1900-9999. + * + * Excel Function: + * YEAR(dateValue) + * + * @Deprecated 1.18.0 + * + * @See DateTimeExcel\DateParts::year() + * Use the ear method in the DateTimeExcel\DateParts class instead + * + * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), + * PHP DateTime object, or a standard date string + * + * @return int|string Year + */ + public static function YEAR($dateValue = 1) + { + return DateTimeExcel\DateParts::year($dateValue); + } + + /** + * HOUROFDAY. + * + * Returns the hour of a time value. + * The hour is given as an integer, ranging from 0 (12:00 A.M.) to 23 (11:00 P.M.). + * + * Excel Function: + * HOUR(timeValue) + * + * @Deprecated 1.18.0 + * + * @See DateTimeExcel\TimeParts::hour() + * Use the hour method in the DateTimeExcel\TimeParts class instead + * + * @param mixed $timeValue Excel date serial value (float), PHP date timestamp (integer), + * PHP DateTime object, or a standard time string + * + * @return int|string Hour + */ + public static function HOUROFDAY($timeValue = 0) + { + return DateTimeExcel\TimeParts::hour($timeValue); + } + + /** + * MINUTE. + * + * Returns the minutes of a time value. + * The minute is given as an integer, ranging from 0 to 59. + * + * Excel Function: + * MINUTE(timeValue) + * + * @Deprecated 1.18.0 + * + * @See DateTimeExcel\TimeParts::minute() + * Use the minute method in the DateTimeExcel\TimeParts class instead + * + * @param mixed $timeValue Excel date serial value (float), PHP date timestamp (integer), + * PHP DateTime object, or a standard time string + * + * @return int|string Minute + */ + public static function MINUTE($timeValue = 0) + { + return DateTimeExcel\TimeParts::minute($timeValue); + } + + /** + * SECOND. + * + * Returns the seconds of a time value. + * The second is given as an integer in the range 0 (zero) to 59. + * + * Excel Function: + * SECOND(timeValue) + * + * @Deprecated 1.18.0 + * + * @See DateTimeExcel\TimeParts::second() + * Use the second method in the DateTimeExcel\TimeParts class instead + * + * @param mixed $timeValue Excel date serial value (float), PHP date timestamp (integer), + * PHP DateTime object, or a standard time string + * + * @return int|string Second + */ + public static function SECOND($timeValue = 0) + { + return DateTimeExcel\TimeParts::second($timeValue); + } + + /** + * EDATE. + * + * Returns the serial number that represents the date that is the indicated number of months + * before or after a specified date (the start_date). + * Use EDATE to calculate maturity dates or due dates that fall on the same day of the month + * as the date of issue. + * + * Excel Function: + * EDATE(dateValue,adjustmentMonths) + * + * @Deprecated 1.18.0 + * + * @See DateTimeExcel\Month::adjust() + * Use the adjust method in the DateTimeExcel\Edate class instead + * + * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), + * PHP DateTime object, or a standard date string + * @param int $adjustmentMonths The number of months before or after start_date. + * A positive value for months yields a future date; + * a negative value yields a past date. + * + * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object, + * depending on the value of the ReturnDateType flag + */ + public static function EDATE($dateValue = 1, $adjustmentMonths = 0) + { + return DateTimeExcel\Month::adjust($dateValue, $adjustmentMonths); + } + + /** + * EOMONTH. + * + * Returns the date value for the last day of the month that is the indicated number of months + * before or after start_date. + * Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month. + * + * Excel Function: + * EOMONTH(dateValue,adjustmentMonths) + * + * @Deprecated 1.18.0 + * + * @See DateTimeExcel\Month::lastDay() + * Use the lastDay method in the DateTimeExcel\EoMonth class instead + * + * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), + * PHP DateTime object, or a standard date string + * @param int $adjustmentMonths The number of months before or after start_date. + * A positive value for months yields a future date; + * a negative value yields a past date. + * + * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object, + * depending on the value of the ReturnDateType flag + */ + public static function EOMONTH($dateValue = 1, $adjustmentMonths = 0) + { + return DateTimeExcel\Month::lastDay($dateValue, $adjustmentMonths); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Constants.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Constants.php new file mode 100644 index 0000000..1165eb1 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Constants.php @@ -0,0 +1,38 @@ + self::DOW_SUNDAY, + self::DOW_MONDAY, + self::STARTWEEK_MONDAY_ALT => self::DOW_MONDAY, + self::DOW_TUESDAY, + self::DOW_WEDNESDAY, + self::DOW_THURSDAY, + self::DOW_FRIDAY, + self::DOW_SATURDAY, + self::DOW_SUNDAY, + self::STARTWEEK_MONDAY_ISO => self::STARTWEEK_MONDAY_ISO, + ]; +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Current.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Current.php new file mode 100644 index 0000000..7a70978 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Current.php @@ -0,0 +1,59 @@ +format('c')); + + return is_array($dateArray) ? Helpers::returnIn3FormatsArray($dateArray, true) : Functions::VALUE(); + } + + /** + * DATETIMENOW. + * + * Returns the current date and time. + * The NOW function is useful when you need to display the current date and time on a worksheet or + * calculate a value based on the current date and time, and have that value updated each time you + * open the worksheet. + * + * NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the date + * and time format of your regional settings. PhpSpreadsheet does not change cell formatting in this way. + * + * Excel Function: + * NOW() + * + * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object, + * depending on the value of the ReturnDateType flag + */ + public static function now() + { + $dti = new DateTimeImmutable(); + $dateArray = date_parse($dti->format('c')); + + return is_array($dateArray) ? Helpers::returnIn3FormatsArray($dateArray) : Functions::VALUE(); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Date.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Date.php new file mode 100644 index 0000000..d18e237 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Date.php @@ -0,0 +1,168 @@ +getMessage(); + } + + // Execute function + $excelDateValue = SharedDateHelper::formattedPHPToExcel($year, $month, $day); + + return Helpers::returnIn3FormatsFloat($excelDateValue); + } + + /** + * Convert year from multiple formats to int. + * + * @param mixed $year + */ + private static function getYear($year, int $baseYear): int + { + $year = Functions::flattenSingleValue($year); + $year = ($year !== null) ? StringHelper::testStringAsNumeric((string) $year) : 0; + if (!is_numeric($year)) { + throw new Exception(Functions::VALUE()); + } + $year = (int) $year; + + if ($year < ($baseYear - 1900)) { + throw new Exception(Functions::NAN()); + } + if ((($baseYear - 1900) !== 0) && ($year < $baseYear) && ($year >= 1900)) { + throw new Exception(Functions::NAN()); + } + + if (($year < $baseYear) && ($year >= ($baseYear - 1900))) { + $year += 1900; + } + + return (int) $year; + } + + /** + * Convert month from multiple formats to int. + * + * @param mixed $month + */ + private static function getMonth($month): int + { + $month = Functions::flattenSingleValue($month); + + if (($month !== null) && (!is_numeric($month))) { + $month = SharedDateHelper::monthStringToNumber($month); + } + + $month = ($month !== null) ? StringHelper::testStringAsNumeric((string) $month) : 0; + if (!is_numeric($month)) { + throw new Exception(Functions::VALUE()); + } + + return (int) $month; + } + + /** + * Convert day from multiple formats to int. + * + * @param mixed $day + */ + private static function getDay($day): int + { + $day = Functions::flattenSingleValue($day); + + if (($day !== null) && (!is_numeric($day))) { + $day = SharedDateHelper::dayStringToNumber($day); + } + + $day = ($day !== null) ? StringHelper::testStringAsNumeric((string) $day) : 0; + if (!is_numeric($day)) { + throw new Exception(Functions::VALUE()); + } + + return (int) $day; + } + + private static function adjustYearMonth(int &$year, int &$month, int $baseYear): void + { + if ($month < 1) { + // Handle year/month adjustment if month < 1 + --$month; + $year += ceil($month / 12) - 1; + $month = 13 - abs($month % 12); + } elseif ($month > 12) { + // Handle year/month adjustment if month > 12 + $year += floor($month / 12); + $month = ($month % 12); + } + + // Re-validate the year parameter after adjustments + if (($year < $baseYear) || ($year >= 10000)) { + throw new Exception(Functions::NAN()); + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/DateParts.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/DateParts.php new file mode 100644 index 0000000..37ea031 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/DateParts.php @@ -0,0 +1,127 @@ += 0) { + return $weirdResult; + } + + try { + $dateValue = Helpers::getDateValue($dateValue); + } catch (Exception $e) { + return $e->getMessage(); + } + + // Execute function + $PHPDateObject = SharedDateHelper::excelToDateTimeObject($dateValue); + + return (int) $PHPDateObject->format('j'); + } + + /** + * MONTHOFYEAR. + * + * Returns the month of a date represented by a serial number. + * The month is given as an integer, ranging from 1 (January) to 12 (December). + * + * Excel Function: + * MONTH(dateValue) + * + * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), + * PHP DateTime object, or a standard date string + * + * @return int|string Month of the year + */ + public static function month($dateValue) + { + try { + $dateValue = Helpers::getDateValue($dateValue); + } catch (Exception $e) { + return $e->getMessage(); + } + if ($dateValue < 1 && SharedDateHelper::getExcelCalendar() === SharedDateHelper::CALENDAR_WINDOWS_1900) { + return 1; + } + + // Execute function + $PHPDateObject = SharedDateHelper::excelToDateTimeObject($dateValue); + + return (int) $PHPDateObject->format('n'); + } + + /** + * YEAR. + * + * Returns the year corresponding to a date. + * The year is returned as an integer in the range 1900-9999. + * + * Excel Function: + * YEAR(dateValue) + * + * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), + * PHP DateTime object, or a standard date string + * + * @return int|string Year + */ + public static function year($dateValue) + { + try { + $dateValue = Helpers::getDateValue($dateValue); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($dateValue < 1 && SharedDateHelper::getExcelCalendar() === SharedDateHelper::CALENDAR_WINDOWS_1900) { + return 1900; + } + // Execute function + $PHPDateObject = SharedDateHelper::excelToDateTimeObject($dateValue); + + return (int) $PHPDateObject->format('Y'); + } + + /** + * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), + * PHP DateTime object, or a standard date string + */ + private static function weirdCondition($dateValue): int + { + // Excel does not treat 0 consistently for DAY vs. (MONTH or YEAR) + if (SharedDateHelper::getExcelCalendar() === SharedDateHelper::CALENDAR_WINDOWS_1900 && Functions::getCompatibilityMode() == Functions::COMPATIBILITY_EXCEL) { + if (is_bool($dateValue)) { + return (int) $dateValue; + } + if ($dateValue === null) { + return 0; + } + if (is_numeric($dateValue) && $dateValue < 1 && $dateValue >= 0) { + return 0; + } + } + + return -1; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php new file mode 100644 index 0000000..d8e88a2 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php @@ -0,0 +1,151 @@ + 31)) { + if ($yearFound) { + return Functions::VALUE(); + } + if ($t < 100) { + $t += 1900; + } + $yearFound = true; + } + } + if (count($t1) === 1) { + // We've been fed a time value without any date + return ((strpos((string) $t, ':') === false)) ? Functions::Value() : 0.0; + } + unset($t); + + $dateValue = self::t1ToString($t1, $dti, $yearFound); + + $PHPDateArray = self::setUpArray($dateValue, $dti); + + return self::finalResults($PHPDateArray, $dti, $baseYear); + } + + private static function t1ToString(array $t1, DateTimeImmutable $dti, bool $yearFound): string + { + if (count($t1) == 2) { + // We only have two parts of the date: either day/month or month/year + if ($yearFound) { + array_unshift($t1, 1); + } else { + if (is_numeric($t1[1]) && $t1[1] > 29) { + $t1[1] += 1900; + array_unshift($t1, 1); + } else { + $t1[] = $dti->format('Y'); + } + } + } + $dateValue = implode(' ', $t1); + + return $dateValue; + } + + /** + * Parse date. + * + * @return array|bool + */ + private static function setUpArray(string $dateValue, DateTimeImmutable $dti) + { + $PHPDateArray = date_parse($dateValue); + if (($PHPDateArray === false) || ($PHPDateArray['error_count'] > 0)) { + // If original count was 1, we've already returned. + // If it was 2, we added another. + // Therefore, neither of the first 2 stroks below can fail. + $testVal1 = strtok($dateValue, '- '); + $testVal2 = strtok('- '); + $testVal3 = strtok('- ') ?: $dti->format('Y'); + Helpers::adjustYear((string) $testVal1, (string) $testVal2, $testVal3); + $PHPDateArray = date_parse($testVal1 . '-' . $testVal2 . '-' . $testVal3); + if (($PHPDateArray === false) || ($PHPDateArray['error_count'] > 0)) { + $PHPDateArray = date_parse($testVal2 . '-' . $testVal1 . '-' . $testVal3); + } + } + + return $PHPDateArray; + } + + /** + * Final results. + * + * @param array|bool $PHPDateArray + * + * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object, + * depending on the value of the ReturnDateType flag + */ + private static function finalResults($PHPDateArray, DateTimeImmutable $dti, int $baseYear) + { + $retValue = Functions::Value(); + if (is_array($PHPDateArray) && $PHPDateArray['error_count'] == 0) { + // Execute function + Helpers::replaceIfEmpty($PHPDateArray['year'], $dti->format('Y')); + if ($PHPDateArray['year'] < $baseYear) { + return Functions::VALUE(); + } + Helpers::replaceIfEmpty($PHPDateArray['month'], $dti->format('m')); + Helpers::replaceIfEmpty($PHPDateArray['day'], $dti->format('d')); + $PHPDateArray['hour'] = 0; + $PHPDateArray['minute'] = 0; + $PHPDateArray['second'] = 0; + $month = (int) $PHPDateArray['month']; + $day = (int) $PHPDateArray['day']; + $year = (int) $PHPDateArray['year']; + if (!checkdate($month, $day, $year)) { + return ($year === 1900 && $month === 2 && $day === 29) ? Helpers::returnIn3FormatsFloat(60.0) : Functions::VALUE(); + } + $retValue = Helpers::returnIn3FormatsArray($PHPDateArray, true); + } + + return $retValue; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Days.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Days.php new file mode 100644 index 0000000..5a97d8d --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Days.php @@ -0,0 +1,51 @@ +getMessage(); + } + + // Execute function + $PHPStartDateObject = SharedDateHelper::excelToDateTimeObject($startDate); + $PHPEndDateObject = SharedDateHelper::excelToDateTimeObject($endDate); + + $days = Functions::VALUE(); + $diff = $PHPStartDateObject->diff($PHPEndDateObject); + if ($diff !== false && !is_bool($diff->days)) { + $days = $diff->days; + if ($diff->invert) { + $days = -$days; + } + } + + return $days; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Days360.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Days360.php new file mode 100644 index 0000000..bbc5d16 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Days360.php @@ -0,0 +1,106 @@ +getMessage(); + } + + if (!is_bool($method)) { + return Functions::VALUE(); + } + + // Execute function + $PHPStartDateObject = SharedDateHelper::excelToDateTimeObject($startDate); + $startDay = $PHPStartDateObject->format('j'); + $startMonth = $PHPStartDateObject->format('n'); + $startYear = $PHPStartDateObject->format('Y'); + + $PHPEndDateObject = SharedDateHelper::excelToDateTimeObject($endDate); + $endDay = $PHPEndDateObject->format('j'); + $endMonth = $PHPEndDateObject->format('n'); + $endYear = $PHPEndDateObject->format('Y'); + + return self::dateDiff360((int) $startDay, (int) $startMonth, (int) $startYear, (int) $endDay, (int) $endMonth, (int) $endYear, !$method); + } + + /** + * Return the number of days between two dates based on a 360 day calendar. + */ + private static function dateDiff360(int $startDay, int $startMonth, int $startYear, int $endDay, int $endMonth, int $endYear, bool $methodUS): int + { + $startDay = self::getStartDay($startDay, $startMonth, $startYear, $methodUS); + $endDay = self::getEndDay($endDay, $endMonth, $endYear, $startDay, $methodUS); + + return $endDay + $endMonth * 30 + $endYear * 360 - $startDay - $startMonth * 30 - $startYear * 360; + } + + private static function getStartDay(int $startDay, int $startMonth, int $startYear, bool $methodUS): int + { + if ($startDay == 31) { + --$startDay; + } elseif ($methodUS && ($startMonth == 2 && ($startDay == 29 || ($startDay == 28 && !Helpers::isLeapYear($startYear))))) { + $startDay = 30; + } + + return $startDay; + } + + private static function getEndDay(int $endDay, int &$endMonth, int &$endYear, int $startDay, bool $methodUS): int + { + if ($endDay == 31) { + if ($methodUS && $startDay != 30) { + $endDay = 1; + if ($endMonth == 12) { + ++$endYear; + $endMonth = 1; + } else { + ++$endMonth; + } + } else { + $endDay = 30; + } + } + + return $endDay; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Difference.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Difference.php new file mode 100644 index 0000000..6adeca1 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Difference.php @@ -0,0 +1,146 @@ +getMessage(); + } + + // Execute function + $PHPStartDateObject = SharedDateHelper::excelToDateTimeObject($startDate); + $startDays = (int) $PHPStartDateObject->format('j'); + $startMonths = (int) $PHPStartDateObject->format('n'); + $startYears = (int) $PHPStartDateObject->format('Y'); + + $PHPEndDateObject = SharedDateHelper::excelToDateTimeObject($endDate); + $endDays = (int) $PHPEndDateObject->format('j'); + $endMonths = (int) $PHPEndDateObject->format('n'); + $endYears = (int) $PHPEndDateObject->format('Y'); + + $PHPDiffDateObject = $PHPEndDateObject->diff($PHPStartDateObject); + + $retVal = false; + $retVal = self::replaceRetValue($retVal, $unit, 'D') ?? self::datedifD($difference); + $retVal = self::replaceRetValue($retVal, $unit, 'M') ?? self::datedifM($PHPDiffDateObject); + $retVal = self::replaceRetValue($retVal, $unit, 'MD') ?? self::datedifMD($startDays, $endDays, $PHPEndDateObject, $PHPDiffDateObject); + $retVal = self::replaceRetValue($retVal, $unit, 'Y') ?? self::datedifY($PHPDiffDateObject); + $retVal = self::replaceRetValue($retVal, $unit, 'YD') ?? self::datedifYD($difference, $startYears, $endYears, $PHPStartDateObject, $PHPEndDateObject); + $retVal = self::replaceRetValue($retVal, $unit, 'YM') ?? self::datedifYM($PHPDiffDateObject); + + return is_bool($retVal) ? Functions::VALUE() : $retVal; + } + + private static function initialDiff(float $startDate, float $endDate): float + { + // Validate parameters + if ($startDate > $endDate) { + throw new Exception(Functions::NAN()); + } + + return $endDate - $startDate; + } + + /** + * Decide whether it's time to set retVal. + * + * @param bool|int $retVal + * + * @return null|bool|int + */ + private static function replaceRetValue($retVal, string $unit, string $compare) + { + if ($retVal !== false || $unit !== $compare) { + return $retVal; + } + + return null; + } + + private static function datedifD(float $difference): int + { + return (int) $difference; + } + + private static function datedifM(DateInterval $PHPDiffDateObject): int + { + return 12 * (int) $PHPDiffDateObject->format('%y') + (int) $PHPDiffDateObject->format('%m'); + } + + private static function datedifMD(int $startDays, int $endDays, DateTime $PHPEndDateObject, DateInterval $PHPDiffDateObject): int + { + if ($endDays < $startDays) { + $retVal = $endDays; + $PHPEndDateObject->modify('-' . $endDays . ' days'); + $adjustDays = (int) $PHPEndDateObject->format('j'); + $retVal += ($adjustDays - $startDays); + } else { + $retVal = (int) $PHPDiffDateObject->format('%d'); + } + + return $retVal; + } + + private static function datedifY(DateInterval $PHPDiffDateObject): int + { + return (int) $PHPDiffDateObject->format('%y'); + } + + private static function datedifYD(float $difference, int $startYears, int $endYears, DateTime $PHPStartDateObject, DateTime $PHPEndDateObject): int + { + $retVal = (int) $difference; + if ($endYears > $startYears) { + $isLeapStartYear = $PHPStartDateObject->format('L'); + $wasLeapEndYear = $PHPEndDateObject->format('L'); + + // Adjust end year to be as close as possible as start year + while ($PHPEndDateObject >= $PHPStartDateObject) { + $PHPEndDateObject->modify('-1 year'); + $endYears = $PHPEndDateObject->format('Y'); + } + $PHPEndDateObject->modify('+1 year'); + + // Get the result + $retVal = $PHPEndDateObject->diff($PHPStartDateObject)->days; + + // Adjust for leap years cases + $isLeapEndYear = $PHPEndDateObject->format('L'); + $limit = new DateTime($PHPEndDateObject->format('Y-02-29')); + if (!$isLeapStartYear && !$wasLeapEndYear && $isLeapEndYear && $PHPEndDateObject >= $limit) { + --$retVal; + } + } + + return (int) $retVal; + } + + private static function datedifYM(DateInterval $PHPDiffDateObject): int + { + return (int) $PHPDiffDateObject->format('%m'); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php new file mode 100644 index 0000000..9503401 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php @@ -0,0 +1,285 @@ +format('m'); + $oYear = (int) $PHPDateObject->format('Y'); + + $adjustmentMonthsString = (string) $adjustmentMonths; + if ($adjustmentMonths > 0) { + $adjustmentMonthsString = '+' . $adjustmentMonths; + } + if ($adjustmentMonths != 0) { + $PHPDateObject->modify($adjustmentMonthsString . ' months'); + } + $nMonth = (int) $PHPDateObject->format('m'); + $nYear = (int) $PHPDateObject->format('Y'); + + $monthDiff = ($nMonth - $oMonth) + (($nYear - $oYear) * 12); + if ($monthDiff != $adjustmentMonths) { + $adjustDays = (int) $PHPDateObject->format('d'); + $adjustDaysString = '-' . $adjustDays . ' days'; + $PHPDateObject->modify($adjustDaysString); + } + + return $PHPDateObject; + } + + /** + * Help reduce perceived complexity of some tests. + * + * @param mixed $value + * @param mixed $altValue + */ + public static function replaceIfEmpty(&$value, $altValue): void + { + $value = $value ?: $altValue; + } + + /** + * Adjust year in ambiguous situations. + */ + public static function adjustYear(string $testVal1, string $testVal2, string &$testVal3): void + { + if (!is_numeric($testVal1) || $testVal1 < 31) { + if (!is_numeric($testVal2) || $testVal2 < 12) { + if (is_numeric($testVal3) && $testVal3 < 12) { + $testVal3 += 2000; + } + } + } + } + + /** + * Return result in one of three formats. + * + * @return mixed + */ + public static function returnIn3FormatsArray(array $dateArray, bool $noFrac = false) + { + $retType = Functions::getReturnDateType(); + if ($retType === Functions::RETURNDATE_PHP_DATETIME_OBJECT) { + return new DateTime( + $dateArray['year'] + . '-' . $dateArray['month'] + . '-' . $dateArray['day'] + . ' ' . $dateArray['hour'] + . ':' . $dateArray['minute'] + . ':' . $dateArray['second'] + ); + } + $excelDateValue = + SharedDateHelper::formattedPHPToExcel( + $dateArray['year'], + $dateArray['month'], + $dateArray['day'], + $dateArray['hour'], + $dateArray['minute'], + $dateArray['second'] + ); + if ($retType === Functions::RETURNDATE_EXCEL) { + return $noFrac ? floor($excelDateValue) : (float) $excelDateValue; + } + // RETURNDATE_UNIX_TIMESTAMP) + + return (int) SharedDateHelper::excelToTimestamp($excelDateValue); + } + + /** + * Return result in one of three formats. + * + * @return mixed + */ + public static function returnIn3FormatsFloat(float $excelDateValue) + { + $retType = Functions::getReturnDateType(); + if ($retType === Functions::RETURNDATE_EXCEL) { + return $excelDateValue; + } + if ($retType === Functions::RETURNDATE_UNIX_TIMESTAMP) { + return (int) SharedDateHelper::excelToTimestamp($excelDateValue); + } + // RETURNDATE_PHP_DATETIME_OBJECT + + return SharedDateHelper::excelToDateTimeObject($excelDateValue); + } + + /** + * Return result in one of three formats. + * + * @return mixed + */ + public static function returnIn3FormatsObject(DateTime $PHPDateObject) + { + $retType = Functions::getReturnDateType(); + if ($retType === Functions::RETURNDATE_PHP_DATETIME_OBJECT) { + return $PHPDateObject; + } + if ($retType === Functions::RETURNDATE_EXCEL) { + return (float) SharedDateHelper::PHPToExcel($PHPDateObject); + } + // RETURNDATE_UNIX_TIMESTAMP + $stamp = SharedDateHelper::PHPToExcel($PHPDateObject); + $stamp = is_bool($stamp) ? ((int) $stamp) : $stamp; + + return (int) SharedDateHelper::excelToTimestamp($stamp); + } + + private static function baseDate(): int + { + if (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE) { + return 0; + } + if (SharedDateHelper::getExcelCalendar() === SharedDateHelper::CALENDAR_MAC_1904) { + return 0; + } + + return 1; + } + + /** + * Many functions accept null/false/true argument treated as 0/0/1. + * + * @param mixed $number + */ + public static function nullFalseTrueToNumber(&$number, bool $allowBool = true): void + { + $number = Functions::flattenSingleValue($number); + $nullVal = self::baseDate(); + if ($number === null) { + $number = $nullVal; + } elseif ($allowBool && is_bool($number)) { + $number = $nullVal + (int) $number; + } + } + + /** + * Many functions accept null argument treated as 0. + * + * @param mixed $number + * + * @return float|int + */ + public static function validateNumericNull($number) + { + $number = Functions::flattenSingleValue($number); + if ($number === null) { + return 0; + } + if (is_int($number)) { + return $number; + } + if (is_numeric($number)) { + return (float) $number; + } + + throw new Exception(Functions::VALUE()); + } + + /** + * Many functions accept null/false/true argument treated as 0/0/1. + * + * @param mixed $number + * + * @return float + */ + public static function validateNotNegative($number) + { + if (!is_numeric($number)) { + throw new Exception(Functions::VALUE()); + } + if ($number >= 0) { + return (float) $number; + } + + throw new Exception(Functions::NAN()); + } + + public static function silly1900(DateTime $PHPDateObject, string $mod = '-1 day'): void + { + $isoDate = $PHPDateObject->format('c'); + if ($isoDate < '1900-03-01') { + $PHPDateObject->modify($mod); + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Month.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Month.php new file mode 100644 index 0000000..560b7a8 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Month.php @@ -0,0 +1,82 @@ +getMessage(); + } + $adjustmentMonths = floor($adjustmentMonths); + + // Execute function + $PHPDateObject = Helpers::adjustDateByMonths($dateValue, $adjustmentMonths); + + return Helpers::returnIn3FormatsObject($PHPDateObject); + } + + /** + * EOMONTH. + * + * Returns the date value for the last day of the month that is the indicated number of months + * before or after start_date. + * Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month. + * + * Excel Function: + * EOMONTH(dateValue,adjustmentMonths) + * + * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), + * PHP DateTime object, or a standard date string + * @param int $adjustmentMonths The number of months before or after start_date. + * A positive value for months yields a future date; + * a negative value yields a past date. + * + * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object, + * depending on the value of the ReturnDateType flag + */ + public static function lastDay($dateValue, $adjustmentMonths) + { + try { + $dateValue = Helpers::getDateValue($dateValue, false); + $adjustmentMonths = Helpers::validateNumericNull($adjustmentMonths); + } catch (Exception $e) { + return $e->getMessage(); + } + $adjustmentMonths = floor($adjustmentMonths); + + // Execute function + $PHPDateObject = Helpers::adjustDateByMonths($dateValue, $adjustmentMonths + 1); + $adjustDays = (int) $PHPDateObject->format('d'); + $adjustDaysString = '-' . $adjustDays . ' days'; + $PHPDateObject->modify($adjustDaysString); + + return Helpers::returnIn3FormatsObject($PHPDateObject); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/NetworkDays.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/NetworkDays.php new file mode 100644 index 0000000..d0f53cf --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/NetworkDays.php @@ -0,0 +1,102 @@ +getMessage(); + } + + // Execute function + $startDow = self::calcStartDow($startDate); + $endDow = self::calcEndDow($endDate); + $wholeWeekDays = (int) floor(($endDate - $startDate) / 7) * 5; + $partWeekDays = self::calcPartWeekDays($startDow, $endDow); + + // Test any extra holiday parameters + $holidayCountedArray = []; + foreach ($holidayArray as $holidayDate) { + if (($holidayDate >= $startDate) && ($holidayDate <= $endDate)) { + if ((Week::day($holidayDate, 2) < 6) && (!in_array($holidayDate, $holidayCountedArray))) { + --$partWeekDays; + $holidayCountedArray[] = $holidayDate; + } + } + } + + return self::applySign($wholeWeekDays + $partWeekDays, $sDate, $eDate); + } + + private static function calcStartDow(float $startDate): int + { + $startDow = 6 - (int) Week::day($startDate, 2); + if ($startDow < 0) { + $startDow = 5; + } + + return $startDow; + } + + private static function calcEndDow(float $endDate): int + { + $endDow = (int) Week::day($endDate, 2); + if ($endDow >= 6) { + $endDow = 0; + } + + return $endDow; + } + + private static function calcPartWeekDays(int $startDow, int $endDow): int + { + $partWeekDays = $endDow + $startDow; + if ($partWeekDays > 5) { + $partWeekDays -= 5; + } + + return $partWeekDays; + } + + private static function applySign(int $result, float $sDate, float $eDate): int + { + return ($sDate > $eDate) ? -$result : $result; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Time.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Time.php new file mode 100644 index 0000000..fb5e496 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Time.php @@ -0,0 +1,119 @@ +getMessage(); + } + + self::adjustSecond($second, $minute); + self::adjustMinute($minute, $hour); + + if ($hour > 23) { + $hour = $hour % 24; + } elseif ($hour < 0) { + return Functions::NAN(); + } + + // Execute function + $retType = Functions::getReturnDateType(); + if ($retType === Functions::RETURNDATE_EXCEL) { + $calendar = SharedDateHelper::getExcelCalendar(); + $date = (int) ($calendar !== SharedDateHelper::CALENDAR_WINDOWS_1900); + + return (float) SharedDateHelper::formattedPHPToExcel($calendar, 1, $date, $hour, $minute, $second); + } + if ($retType === Functions::RETURNDATE_UNIX_TIMESTAMP) { + return (int) SharedDateHelper::excelToTimestamp(SharedDateHelper::formattedPHPToExcel(1970, 1, 1, $hour, $minute, $second)); // -2147468400; // -2147472000 + 3600 + } + // RETURNDATE_PHP_DATETIME_OBJECT + // Hour has already been normalized (0-23) above + $phpDateObject = new DateTime('1900-01-01 ' . $hour . ':' . $minute . ':' . $second); + + return $phpDateObject; + } + + private static function adjustSecond(int &$second, int &$minute): void + { + if ($second < 0) { + $minute += floor($second / 60); + $second = 60 - abs($second % 60); + if ($second == 60) { + $second = 0; + } + } elseif ($second >= 60) { + $minute += floor($second / 60); + $second = $second % 60; + } + } + + private static function adjustMinute(int &$minute, int &$hour): void + { + if ($minute < 0) { + $hour += floor($minute / 60); + $minute = 60 - abs($minute % 60); + if ($minute == 60) { + $minute = 0; + } + } elseif ($minute >= 60) { + $hour += floor($minute / 60); + $minute = $minute % 60; + } + } + + /** + * @param mixed $value expect int + */ + private static function toIntWithNullBool($value): int + { + $value = Functions::flattenSingleValue($value); + $value = $value ?? 0; + if (is_bool($value)) { + $value = (int) $value; + } + if (!is_numeric($value)) { + throw new Exception(Functions::VALUE()); + } + + return (int) $value; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeParts.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeParts.php new file mode 100644 index 0000000..49cd983 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeParts.php @@ -0,0 +1,112 @@ +getMessage(); + } + + // Execute function + $timeValue = fmod($timeValue, 1); + $timeValue = SharedDateHelper::excelToDateTimeObject($timeValue); + + return (int) $timeValue->format('H'); + } + + /** + * MINUTE. + * + * Returns the minutes of a time value. + * The minute is given as an integer, ranging from 0 to 59. + * + * Excel Function: + * MINUTE(timeValue) + * + * @param mixed $timeValue Excel date serial value (float), PHP date timestamp (integer), + * PHP DateTime object, or a standard time string + * + * @return int|string Minute + */ + public static function minute($timeValue) + { + try { + $timeValue = Functions::flattenSingleValue($timeValue); + Helpers::nullFalseTrueToNumber($timeValue); + if (!is_numeric($timeValue)) { + $timeValue = Helpers::getTimeValue($timeValue); + } + Helpers::validateNotNegative($timeValue); + } catch (Exception $e) { + return $e->getMessage(); + } + + // Execute function + $timeValue = fmod($timeValue, 1); + $timeValue = SharedDateHelper::excelToDateTimeObject($timeValue); + + return (int) $timeValue->format('i'); + } + + /** + * SECOND. + * + * Returns the seconds of a time value. + * The minute is given as an integer, ranging from 0 to 59. + * + * Excel Function: + * SECOND(timeValue) + * + * @param mixed $timeValue Excel date serial value (float), PHP date timestamp (integer), + * PHP DateTime object, or a standard time string + * + * @return int|string Second + */ + public static function second($timeValue) + { + try { + $timeValue = Functions::flattenSingleValue($timeValue); + Helpers::nullFalseTrueToNumber($timeValue); + if (!is_numeric($timeValue)) { + $timeValue = Helpers::getTimeValue($timeValue); + } + Helpers::validateNotNegative($timeValue); + } catch (Exception $e) { + return $e->getMessage(); + } + + // Execute function + $timeValue = fmod($timeValue, 1); + $timeValue = SharedDateHelper::excelToDateTimeObject($timeValue); + + return (int) $timeValue->format('s'); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php new file mode 100644 index 0000000..2a29434 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php @@ -0,0 +1,61 @@ + 24) { + $arraySplit[0] = ($arraySplit[0] % 24); + $timeValue = implode(':', $arraySplit); + } + + $PHPDateArray = date_parse($timeValue); + $retValue = Functions::VALUE(); + if (($PHPDateArray !== false) && ($PHPDateArray['error_count'] == 0)) { + // OpenOffice-specific code removed - it works just like Excel + $excelDateValue = SharedDateHelper::formattedPHPToExcel(1900, 1, 1, $PHPDateArray['hour'], $PHPDateArray['minute'], $PHPDateArray['second']) - 1; + + $retType = Functions::getReturnDateType(); + if ($retType === Functions::RETURNDATE_EXCEL) { + $retValue = (float) $excelDateValue; + } elseif ($retType === Functions::RETURNDATE_UNIX_TIMESTAMP) { + $retValue = (int) $phpDateValue = SharedDateHelper::excelToTimestamp($excelDateValue + 25569) - 3600; + } else { + $retValue = new DateTime('1900-01-01 ' . $PHPDateArray['hour'] . ':' . $PHPDateArray['minute'] . ':' . $PHPDateArray['second']); + } + } + + return $retValue; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Week.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Week.php new file mode 100644 index 0000000..6636222 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Week.php @@ -0,0 +1,254 @@ +getMessage(); + } + + // Execute function + $PHPDateObject = SharedDateHelper::excelToDateTimeObject($dateValue); + if ($method == Constants::STARTWEEK_MONDAY_ISO) { + Helpers::silly1900($PHPDateObject); + + return (int) $PHPDateObject->format('W'); + } + if (self::buggyWeekNum1904($method, $origDateValueNull, $PHPDateObject)) { + return 0; + } + Helpers::silly1900($PHPDateObject, '+ 5 years'); // 1905 calendar matches + $dayOfYear = (int) $PHPDateObject->format('z'); + $PHPDateObject->modify('-' . $dayOfYear . ' days'); + $firstDayOfFirstWeek = (int) $PHPDateObject->format('w'); + $daysInFirstWeek = (6 - $firstDayOfFirstWeek + $method) % 7; + $daysInFirstWeek += 7 * !$daysInFirstWeek; + $endFirstWeek = $daysInFirstWeek - 1; + $weekOfYear = floor(($dayOfYear - $endFirstWeek + 13) / 7); + + return (int) $weekOfYear; + } + + /** + * ISOWEEKNUM. + * + * Returns the ISO 8601 week number of the year for a specified date. + * + * Excel Function: + * ISOWEEKNUM(dateValue) + * + * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), + * PHP DateTime object, or a standard date string + * + * @return int|string Week Number + */ + public static function isoWeekNumber($dateValue) + { + if (self::apparentBug($dateValue)) { + return 52; + } + + try { + $dateValue = Helpers::getDateValue($dateValue); + } catch (Exception $e) { + return $e->getMessage(); + } + + // Execute function + $PHPDateObject = SharedDateHelper::excelToDateTimeObject($dateValue); + Helpers::silly1900($PHPDateObject); + + return (int) $PHPDateObject->format('W'); + } + + /** + * WEEKDAY. + * + * Returns the day of the week for a specified date. The day is given as an integer + * ranging from 0 to 7 (dependent on the requested style). + * + * Excel Function: + * WEEKDAY(dateValue[,style]) + * + * @param null|float|int|string $dateValue Excel date serial value (float), PHP date timestamp (integer), + * PHP DateTime object, or a standard date string + * @param mixed $style A number that determines the type of return value + * 1 or omitted Numbers 1 (Sunday) through 7 (Saturday). + * 2 Numbers 1 (Monday) through 7 (Sunday). + * 3 Numbers 0 (Monday) through 6 (Sunday). + * + * @return int|string Day of the week value + */ + public static function day($dateValue, $style = 1) + { + try { + $dateValue = Helpers::getDateValue($dateValue); + $style = self::validateStyle($style); + } catch (Exception $e) { + return $e->getMessage(); + } + + // Execute function + $PHPDateObject = SharedDateHelper::excelToDateTimeObject($dateValue); + Helpers::silly1900($PHPDateObject); + $DoW = (int) $PHPDateObject->format('w'); + + switch ($style) { + case 1: + ++$DoW; + + break; + case 2: + $DoW = self::dow0Becomes7($DoW); + + break; + case 3: + $DoW = self::dow0Becomes7($DoW) - 1; + + break; + } + + return $DoW; + } + + /** + * @param mixed $style expect int + */ + private static function validateStyle($style): int + { + $style = Functions::flattenSingleValue($style); + + if (!is_numeric($style)) { + throw new Exception(Functions::VALUE()); + } + $style = (int) $style; + if (($style < 1) || ($style > 3)) { + throw new Exception(Functions::NAN()); + } + + return $style; + } + + private static function dow0Becomes7(int $DoW): int + { + return ($DoW === 0) ? 7 : $DoW; + } + + /** + * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), + * PHP DateTime object, or a standard date string + */ + private static function apparentBug($dateValue): bool + { + if (SharedDateHelper::getExcelCalendar() !== SharedDateHelper::CALENDAR_MAC_1904) { + if (is_bool($dateValue)) { + return true; + } + if (is_numeric($dateValue) && !((int) $dateValue)) { + return true; + } + } + + return false; + } + + /** + * Validate dateValue parameter. + * + * @param mixed $dateValue + */ + private static function validateDateValue($dateValue): float + { + if (is_bool($dateValue)) { + throw new Exception(Functions::VALUE()); + } + + return Helpers::getDateValue($dateValue); + } + + /** + * Validate method parameter. + * + * @param mixed $method + */ + private static function validateMethod($method): int + { + if ($method === null) { + $method = Constants::STARTWEEK_SUNDAY; + } + $method = Functions::flattenSingleValue($method); + if (!is_numeric($method)) { + throw new Exception(Functions::VALUE()); + } + + $method = (int) $method; + if (!array_key_exists($method, Constants::METHODARR)) { + throw new Exception(Functions::NAN()); + } + $method = Constants::METHODARR[$method]; + + return $method; + } + + private static function buggyWeekNum1900(int $method): bool + { + return $method === Constants::DOW_SUNDAY && SharedDateHelper::getExcelCalendar() === SharedDateHelper::CALENDAR_WINDOWS_1900; + } + + private static function buggyWeekNum1904(int $method, bool $origNull, DateTime $dateObject): bool + { + // This appears to be another Excel bug. + + return $method === Constants::DOW_SUNDAY && SharedDateHelper::getExcelCalendar() === SharedDateHelper::CALENDAR_MAC_1904 && + !$origNull && $dateObject->format('Y-m-d') === '1904-01-01'; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/WorkDay.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/WorkDay.php new file mode 100644 index 0000000..89e47b9 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/WorkDay.php @@ -0,0 +1,189 @@ +getMessage(); + } + + $startDate = (float) floor($startDate); + $endDays = (int) floor($endDays); + // If endDays is 0, we always return startDate + if ($endDays == 0) { + return $startDate; + } + if ($endDays < 0) { + return self::decrementing($startDate, $endDays, $holidayArray); + } + + return self::incrementing($startDate, $endDays, $holidayArray); + } + + /** + * Use incrementing logic to determine Workday. + * + * @return mixed + */ + private static function incrementing(float $startDate, int $endDays, array $holidayArray) + { + // Adjust the start date if it falls over a weekend + + $startDoW = self::getWeekDay($startDate, 3); + if (self::getWeekDay($startDate, 3) >= 5) { + $startDate += 7 - $startDoW; + --$endDays; + } + + // Add endDays + $endDate = (float) $startDate + ((int) ($endDays / 5) * 7); + $endDays = $endDays % 5; + while ($endDays > 0) { + ++$endDate; + // Adjust the calculated end date if it falls over a weekend + $endDow = self::getWeekDay($endDate, 3); + if ($endDow >= 5) { + $endDate += 7 - $endDow; + } + --$endDays; + } + + // Test any extra holiday parameters + if (!empty($holidayArray)) { + $endDate = self::incrementingArray($startDate, $endDate, $holidayArray); + } + + return Helpers::returnIn3FormatsFloat($endDate); + } + + private static function incrementingArray(float $startDate, float $endDate, array $holidayArray): float + { + $holidayCountedArray = $holidayDates = []; + foreach ($holidayArray as $holidayDate) { + if (self::getWeekDay($holidayDate, 3) < 5) { + $holidayDates[] = $holidayDate; + } + } + sort($holidayDates, SORT_NUMERIC); + foreach ($holidayDates as $holidayDate) { + if (($holidayDate >= $startDate) && ($holidayDate <= $endDate)) { + if (!in_array($holidayDate, $holidayCountedArray)) { + ++$endDate; + $holidayCountedArray[] = $holidayDate; + } + } + // Adjust the calculated end date if it falls over a weekend + $endDoW = self::getWeekDay($endDate, 3); + if ($endDoW >= 5) { + $endDate += 7 - $endDoW; + } + } + + return $endDate; + } + + /** + * Use decrementing logic to determine Workday. + * + * @return mixed + */ + private static function decrementing(float $startDate, int $endDays, array $holidayArray) + { + // Adjust the start date if it falls over a weekend + + $startDoW = self::getWeekDay($startDate, 3); + if (self::getWeekDay($startDate, 3) >= 5) { + $startDate += -$startDoW + 4; + ++$endDays; + } + + // Add endDays + $endDate = (float) $startDate + ((int) ($endDays / 5) * 7); + $endDays = $endDays % 5; + while ($endDays < 0) { + --$endDate; + // Adjust the calculated end date if it falls over a weekend + $endDow = self::getWeekDay($endDate, 3); + if ($endDow >= 5) { + $endDate += 4 - $endDow; + } + ++$endDays; + } + + // Test any extra holiday parameters + if (!empty($holidayArray)) { + $endDate = self::decrementingArray($startDate, $endDate, $holidayArray); + } + + return Helpers::returnIn3FormatsFloat($endDate); + } + + private static function decrementingArray(float $startDate, float $endDate, array $holidayArray): float + { + $holidayCountedArray = $holidayDates = []; + foreach ($holidayArray as $holidayDate) { + if (self::getWeekDay($holidayDate, 3) < 5) { + $holidayDates[] = $holidayDate; + } + } + rsort($holidayDates, SORT_NUMERIC); + foreach ($holidayDates as $holidayDate) { + if (($holidayDate <= $startDate) && ($holidayDate >= $endDate)) { + if (!in_array($holidayDate, $holidayCountedArray)) { + --$endDate; + $holidayCountedArray[] = $holidayDate; + } + } + // Adjust the calculated end date if it falls over a weekend + $endDoW = self::getWeekDay($endDate, 3); + if ($endDoW >= 5) { + $endDate += -$endDoW + 4; + } + } + + return $endDate; + } + + private static function getWeekDay(float $date, int $wd): int + { + $result = Week::day($date, $wd); + + return is_string($result) ? -1 : $result; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/YearFrac.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/YearFrac.php new file mode 100644 index 0000000..da2ac12 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/YearFrac.php @@ -0,0 +1,120 @@ +getMessage(); + } + + switch ($method) { + case 0: + return Days360::between($startDate, $endDate) / 360; + case 1: + return self::method1($startDate, $endDate); + case 2: + return Difference::interval($startDate, $endDate) / 360; + case 3: + return Difference::interval($startDate, $endDate) / 365; + case 4: + return Days360::between($startDate, $endDate, true) / 360; + } + + return Functions::NAN(); + } + + /** + * Excel 1900 calendar treats date argument of null as 1900-01-00. Really. + * + * @param mixed $startDate + * @param mixed $endDate + */ + private static function excelBug(float $sDate, $startDate, $endDate, int $method): float + { + if (Functions::getCompatibilityMode() !== Functions::COMPATIBILITY_OPENOFFICE && SharedDateHelper::getExcelCalendar() !== SharedDateHelper::CALENDAR_MAC_1904) { + if ($endDate === null && $startDate !== null) { + if (DateParts::month($sDate) == 12 && DateParts::day($sDate) === 31 && $method === 0) { + $sDate += 2; + } else { + ++$sDate; + } + } + } + + return $sDate; + } + + private static function method1(float $startDate, float $endDate): float + { + $days = Difference::interval($startDate, $endDate); + $startYear = (int) DateParts::year($startDate); + $endYear = (int) DateParts::year($endDate); + $years = $endYear - $startYear + 1; + $startMonth = (int) DateParts::month($startDate); + $startDay = (int) DateParts::day($startDate); + $endMonth = (int) DateParts::month($endDate); + $endDay = (int) DateParts::day($endDate); + $startMonthDay = 100 * $startMonth + $startDay; + $endMonthDay = 100 * $endMonth + $endDay; + if ($years == 1) { + $tmpCalcAnnualBasis = 365 + (int) Helpers::isLeapYear($endYear); + } elseif ($years == 2 && $startMonthDay >= $endMonthDay) { + if (Helpers::isLeapYear($startYear)) { + $tmpCalcAnnualBasis = 365 + (int) ($startMonthDay <= 229); + } elseif (Helpers::isLeapYear($endYear)) { + $tmpCalcAnnualBasis = 365 + (int) ($endMonthDay >= 229); + } else { + $tmpCalcAnnualBasis = 365; + } + } else { + $tmpCalcAnnualBasis = 0; + for ($year = $startYear; $year <= $endYear; ++$year) { + $tmpCalcAnnualBasis += 365 + (int) Helpers::isLeapYear($year); + } + $tmpCalcAnnualBasis /= $years; + } + + return $days / $tmpCalcAnnualBasis; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/CyclicReferenceStack.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/CyclicReferenceStack.php new file mode 100644 index 0000000..b688e05 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/CyclicReferenceStack.php @@ -0,0 +1,73 @@ +stack); + } + + /** + * Push a new entry onto the stack. + * + * @param mixed $value + */ + public function push($value): void + { + $this->stack[$value] = $value; + } + + /** + * Pop the last entry from the stack. + * + * @return mixed + */ + public function pop() + { + return array_pop($this->stack); + } + + /** + * Test to see if a specified entry exists on the stack. + * + * @param mixed $value The value to test + * + * @return bool + */ + public function onStack($value) + { + return isset($this->stack[$value]); + } + + /** + * Clear the stack. + */ + public function clear(): void + { + $this->stack = []; + } + + /** + * Return an array of all entries on the stack. + * + * @return mixed[] + */ + public function showStack() + { + return $this->stack; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/Logger.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/Logger.php new file mode 100644 index 0000000..7f4657c --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/Logger.php @@ -0,0 +1,140 @@ +cellStack = $stack; + } + + /** + * Enable/Disable Calculation engine logging. + * + * @param bool $writeDebugLog + */ + public function setWriteDebugLog($writeDebugLog): void + { + $this->writeDebugLog = $writeDebugLog; + } + + /** + * Return whether calculation engine logging is enabled or disabled. + * + * @return bool + */ + public function getWriteDebugLog() + { + return $this->writeDebugLog; + } + + /** + * Enable/Disable echoing of debug log information. + * + * @param bool $echoDebugLog + */ + public function setEchoDebugLog($echoDebugLog): void + { + $this->echoDebugLog = $echoDebugLog; + } + + /** + * Return whether echoing of debug log information is enabled or disabled. + * + * @return bool + */ + public function getEchoDebugLog() + { + return $this->echoDebugLog; + } + + /** + * Write an entry to the calculation engine debug log. + */ + public function writeDebugLog(...$args): void + { + // Only write the debug log if logging is enabled + if ($this->writeDebugLog) { + $message = implode('', $args); + $cellReference = implode(' -> ', $this->cellStack->showStack()); + if ($this->echoDebugLog) { + echo $cellReference, + ($this->cellStack->count() > 0 ? ' => ' : ''), + $message, + PHP_EOL; + } + $this->debugLog[] = $cellReference . + ($this->cellStack->count() > 0 ? ' => ' : '') . + $message; + } + } + + /** + * Write a series of entries to the calculation engine debug log. + * + * @param string[] $args + */ + public function mergeDebugLog(array $args): void + { + if ($this->writeDebugLog) { + foreach ($args as $entry) { + $this->writeDebugLog($entry); + } + } + } + + /** + * Clear the calculation engine debug log. + */ + public function clearLog(): void + { + $this->debugLog = []; + } + + /** + * Return the calculation engine debug log. + * + * @return string[] + */ + public function getLog() + { + return $this->debugLog; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering.php new file mode 100644 index 0000000..a70ddac --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering.php @@ -0,0 +1,1446 @@ + $complex->getReal(), + 'imaginary' => $complex->getImaginary(), + 'suffix' => $complex->getSuffix(), + ]; + } + + /** + * BESSELI. + * + * Returns the modified Bessel function In(x), which is equivalent to the Bessel function evaluated + * for purely imaginary arguments + * + * Excel Function: + * BESSELI(x,ord) + * + * @Deprecated 1.17.0 + * + * @see Use the BESSELI() method in the Engineering\BesselI class instead + * + * @param float $x The value at which to evaluate the function. + * If x is nonnumeric, BESSELI returns the #VALUE! error value. + * @param int $ord The order of the Bessel function. + * If ord is not an integer, it is truncated. + * If $ord is nonnumeric, BESSELI returns the #VALUE! error value. + * If $ord < 0, BESSELI returns the #NUM! error value. + * + * @return float|string Result, or a string containing an error + */ + public static function BESSELI($x, $ord) + { + return Engineering\BesselI::BESSELI($x, $ord); + } + + /** + * BESSELJ. + * + * Returns the Bessel function + * + * Excel Function: + * BESSELJ(x,ord) + * + * @Deprecated 1.17.0 + * + * @see Use the BESSELJ() method in the Engineering\BesselJ class instead + * + * @param float $x The value at which to evaluate the function. + * If x is nonnumeric, BESSELJ returns the #VALUE! error value. + * @param int $ord The order of the Bessel function. If n is not an integer, it is truncated. + * If $ord is nonnumeric, BESSELJ returns the #VALUE! error value. + * If $ord < 0, BESSELJ returns the #NUM! error value. + * + * @return float|string Result, or a string containing an error + */ + public static function BESSELJ($x, $ord) + { + return Engineering\BesselJ::BESSELJ($x, $ord); + } + + /** + * BESSELK. + * + * Returns the modified Bessel function Kn(x), which is equivalent to the Bessel functions evaluated + * for purely imaginary arguments. + * + * Excel Function: + * BESSELK(x,ord) + * + * @Deprecated 1.17.0 + * + * @see Use the BESSELK() method in the Engineering\BesselK class instead + * + * @param float $x The value at which to evaluate the function. + * If x is nonnumeric, BESSELK returns the #VALUE! error value. + * @param int $ord The order of the Bessel function. If n is not an integer, it is truncated. + * If $ord is nonnumeric, BESSELK returns the #VALUE! error value. + * If $ord < 0, BESSELK returns the #NUM! error value. + * + * @return float|string Result, or a string containing an error + */ + public static function BESSELK($x, $ord) + { + return Engineering\BesselK::BESSELK($x, $ord); + } + + /** + * BESSELY. + * + * Returns the Bessel function, which is also called the Weber function or the Neumann function. + * + * Excel Function: + * BESSELY(x,ord) + * + * @Deprecated 1.17.0 + * + * @see Use the BESSELY() method in the Engineering\BesselY class instead + * + * @param float $x The value at which to evaluate the function. + * If x is nonnumeric, BESSELY returns the #VALUE! error value. + * @param int $ord The order of the Bessel function. If n is not an integer, it is truncated. + * If $ord is nonnumeric, BESSELY returns the #VALUE! error value. + * If $ord < 0, BESSELY returns the #NUM! error value. + * + * @return float|string Result, or a string containing an error + */ + public static function BESSELY($x, $ord) + { + return Engineering\BesselY::BESSELY($x, $ord); + } + + /** + * BINTODEC. + * + * Return a binary value as decimal. + * + * Excel Function: + * BIN2DEC(x) + * + * @Deprecated 1.17.0 + * + * @see Use the toDecimal() method in the Engineering\ConvertBinary class instead + * + * @param mixed $x The binary number (as a string) that you want to convert. The number + * cannot contain more than 10 characters (10 bits). The most significant + * bit of number is the sign bit. The remaining 9 bits are magnitude bits. + * Negative numbers are represented using two's-complement notation. + * If number is not a valid binary number, or if number contains more than + * 10 characters (10 bits), BIN2DEC returns the #NUM! error value. + * + * @return string + */ + public static function BINTODEC($x) + { + return Engineering\ConvertBinary::toDecimal($x); + } + + /** + * BINTOHEX. + * + * Return a binary value as hex. + * + * Excel Function: + * BIN2HEX(x[,places]) + * + * @Deprecated 1.17.0 + * + * @see Use the toHex() method in the Engineering\ConvertBinary class instead + * + * @param mixed $x The binary number (as a string) that you want to convert. The number + * cannot contain more than 10 characters (10 bits). The most significant + * bit of number is the sign bit. The remaining 9 bits are magnitude bits. + * Negative numbers are represented using two's-complement notation. + * If number is not a valid binary number, or if number contains more than + * 10 characters (10 bits), BIN2HEX returns the #NUM! error value. + * @param mixed $places The number of characters to use. If places is omitted, BIN2HEX uses the + * minimum number of characters necessary. Places is useful for padding the + * return value with leading 0s (zeros). + * If places is not an integer, it is truncated. + * If places is nonnumeric, BIN2HEX returns the #VALUE! error value. + * If places is negative, BIN2HEX returns the #NUM! error value. + * + * @return string + */ + public static function BINTOHEX($x, $places = null) + { + return Engineering\ConvertBinary::toHex($x, $places); + } + + /** + * BINTOOCT. + * + * Return a binary value as octal. + * + * Excel Function: + * BIN2OCT(x[,places]) + * + * @Deprecated 1.17.0 + * + * @see Use the toOctal() method in the Engineering\ConvertBinary class instead + * + * @param mixed $x The binary number (as a string) that you want to convert. The number + * cannot contain more than 10 characters (10 bits). The most significant + * bit of number is the sign bit. The remaining 9 bits are magnitude bits. + * Negative numbers are represented using two's-complement notation. + * If number is not a valid binary number, or if number contains more than + * 10 characters (10 bits), BIN2OCT returns the #NUM! error value. + * @param mixed $places The number of characters to use. If places is omitted, BIN2OCT uses the + * minimum number of characters necessary. Places is useful for padding the + * return value with leading 0s (zeros). + * If places is not an integer, it is truncated. + * If places is nonnumeric, BIN2OCT returns the #VALUE! error value. + * If places is negative, BIN2OCT returns the #NUM! error value. + * + * @return string + */ + public static function BINTOOCT($x, $places = null) + { + return Engineering\ConvertBinary::toOctal($x, $places); + } + + /** + * DECTOBIN. + * + * Return a decimal value as binary. + * + * Excel Function: + * DEC2BIN(x[,places]) + * + * @Deprecated 1.17.0 + * + * @see Use the toBinary() method in the Engineering\ConvertDecimal class instead + * + * @param mixed $x The decimal integer you want to convert. If number is negative, + * valid place values are ignored and DEC2BIN returns a 10-character + * (10-bit) binary number in which the most significant bit is the sign + * bit. The remaining 9 bits are magnitude bits. Negative numbers are + * represented using two's-complement notation. + * If number < -512 or if number > 511, DEC2BIN returns the #NUM! error + * value. + * If number is nonnumeric, DEC2BIN returns the #VALUE! error value. + * If DEC2BIN requires more than places characters, it returns the #NUM! + * error value. + * @param mixed $places The number of characters to use. If places is omitted, DEC2BIN uses + * the minimum number of characters necessary. Places is useful for + * padding the return value with leading 0s (zeros). + * If places is not an integer, it is truncated. + * If places is nonnumeric, DEC2BIN returns the #VALUE! error value. + * If places is zero or negative, DEC2BIN returns the #NUM! error value. + * + * @return string + */ + public static function DECTOBIN($x, $places = null) + { + return Engineering\ConvertDecimal::toBinary($x, $places); + } + + /** + * DECTOHEX. + * + * Return a decimal value as hex. + * + * Excel Function: + * DEC2HEX(x[,places]) + * + * @Deprecated 1.17.0 + * + * @see Use the toHex() method in the Engineering\ConvertDecimal class instead + * + * @param mixed $x The decimal integer you want to convert. If number is negative, + * places is ignored and DEC2HEX returns a 10-character (40-bit) + * hexadecimal number in which the most significant bit is the sign + * bit. The remaining 39 bits are magnitude bits. Negative numbers + * are represented using two's-complement notation. + * If number < -549,755,813,888 or if number > 549,755,813,887, + * DEC2HEX returns the #NUM! error value. + * If number is nonnumeric, DEC2HEX returns the #VALUE! error value. + * If DEC2HEX requires more than places characters, it returns the + * #NUM! error value. + * @param mixed $places The number of characters to use. If places is omitted, DEC2HEX uses + * the minimum number of characters necessary. Places is useful for + * padding the return value with leading 0s (zeros). + * If places is not an integer, it is truncated. + * If places is nonnumeric, DEC2HEX returns the #VALUE! error value. + * If places is zero or negative, DEC2HEX returns the #NUM! error value. + * + * @return string + */ + public static function DECTOHEX($x, $places = null) + { + return Engineering\ConvertDecimal::toHex($x, $places); + } + + /** + * DECTOOCT. + * + * Return an decimal value as octal. + * + * Excel Function: + * DEC2OCT(x[,places]) + * + * @Deprecated 1.17.0 + * + * @see Use the toOctal() method in the Engineering\ConvertDecimal class instead + * + * @param mixed $x The decimal integer you want to convert. If number is negative, + * places is ignored and DEC2OCT returns a 10-character (30-bit) + * octal number in which the most significant bit is the sign bit. + * The remaining 29 bits are magnitude bits. Negative numbers are + * represented using two's-complement notation. + * If number < -536,870,912 or if number > 536,870,911, DEC2OCT + * returns the #NUM! error value. + * If number is nonnumeric, DEC2OCT returns the #VALUE! error value. + * If DEC2OCT requires more than places characters, it returns the + * #NUM! error value. + * @param mixed $places The number of characters to use. If places is omitted, DEC2OCT uses + * the minimum number of characters necessary. Places is useful for + * padding the return value with leading 0s (zeros). + * If places is not an integer, it is truncated. + * If places is nonnumeric, DEC2OCT returns the #VALUE! error value. + * If places is zero or negative, DEC2OCT returns the #NUM! error value. + * + * @return string + */ + public static function DECTOOCT($x, $places = null) + { + return Engineering\ConvertDecimal::toOctal($x, $places); + } + + /** + * HEXTOBIN. + * + * Return a hex value as binary. + * + * Excel Function: + * HEX2BIN(x[,places]) + * + * @Deprecated 1.17.0 + * + * @see Use the toBinary() method in the Engineering\ConvertHex class instead + * + * @param mixed $x the hexadecimal number (as a string) that you want to convert. + * Number cannot contain more than 10 characters. + * The most significant bit of number is the sign bit (40th bit from the right). + * The remaining 9 bits are magnitude bits. + * Negative numbers are represented using two's-complement notation. + * If number is negative, HEX2BIN ignores places and returns a 10-character binary number. + * If number is negative, it cannot be less than FFFFFFFE00, + * and if number is positive, it cannot be greater than 1FF. + * If number is not a valid hexadecimal number, HEX2BIN returns the #NUM! error value. + * If HEX2BIN requires more than places characters, it returns the #NUM! error value. + * @param mixed $places The number of characters to use. If places is omitted, + * HEX2BIN uses the minimum number of characters necessary. Places + * is useful for padding the return value with leading 0s (zeros). + * If places is not an integer, it is truncated. + * If places is nonnumeric, HEX2BIN returns the #VALUE! error value. + * If places is negative, HEX2BIN returns the #NUM! error value. + * + * @return string + */ + public static function HEXTOBIN($x, $places = null) + { + return Engineering\ConvertHex::toBinary($x, $places); + } + + /** + * HEXTODEC. + * + * Return a hex value as decimal. + * + * Excel Function: + * HEX2DEC(x) + * + * @Deprecated 1.17.0 + * + * @see Use the toDecimal() method in the Engineering\ConvertHex class instead + * + * @param mixed $x The hexadecimal number (as a string) that you want to convert. This number cannot + * contain more than 10 characters (40 bits). The most significant + * bit of number is the sign bit. The remaining 39 bits are magnitude + * bits. Negative numbers are represented using two's-complement + * notation. + * If number is not a valid hexadecimal number, HEX2DEC returns the + * #NUM! error value. + * + * @return string + */ + public static function HEXTODEC($x) + { + return Engineering\ConvertHex::toDecimal($x); + } + + /** + * HEXTOOCT. + * + * Return a hex value as octal. + * + * Excel Function: + * HEX2OCT(x[,places]) + * + * @Deprecated 1.17.0 + * + * @see Use the toOctal() method in the Engineering\ConvertHex class instead + * + * @param mixed $x The hexadecimal number (as a string) that you want to convert. Number cannot + * contain more than 10 characters. The most significant bit of + * number is the sign bit. The remaining 39 bits are magnitude + * bits. Negative numbers are represented using two's-complement + * notation. + * If number is negative, HEX2OCT ignores places and returns a + * 10-character octal number. + * If number is negative, it cannot be less than FFE0000000, and + * if number is positive, it cannot be greater than 1FFFFFFF. + * If number is not a valid hexadecimal number, HEX2OCT returns + * the #NUM! error value. + * If HEX2OCT requires more than places characters, it returns + * the #NUM! error value. + * @param mixed $places The number of characters to use. If places is omitted, HEX2OCT + * uses the minimum number of characters necessary. Places is + * useful for padding the return value with leading 0s (zeros). + * If places is not an integer, it is truncated. + * If places is nonnumeric, HEX2OCT returns the #VALUE! error + * value. + * If places is negative, HEX2OCT returns the #NUM! error value. + * + * @return string + */ + public static function HEXTOOCT($x, $places = null) + { + return Engineering\ConvertHex::toOctal($x, $places); + } + + /** + * OCTTOBIN. + * + * Return an octal value as binary. + * + * Excel Function: + * OCT2BIN(x[,places]) + * + * @Deprecated 1.17.0 + * + * @see Use the toBinary() method in the Engineering\ConvertOctal class instead + * + * @param mixed $x The octal number you want to convert. Number may not + * contain more than 10 characters. The most significant + * bit of number is the sign bit. The remaining 29 bits + * are magnitude bits. Negative numbers are represented + * using two's-complement notation. + * If number is negative, OCT2BIN ignores places and returns + * a 10-character binary number. + * If number is negative, it cannot be less than 7777777000, + * and if number is positive, it cannot be greater than 777. + * If number is not a valid octal number, OCT2BIN returns + * the #NUM! error value. + * If OCT2BIN requires more than places characters, it + * returns the #NUM! error value. + * @param mixed $places The number of characters to use. If places is omitted, + * OCT2BIN uses the minimum number of characters necessary. + * Places is useful for padding the return value with + * leading 0s (zeros). + * If places is not an integer, it is truncated. + * If places is nonnumeric, OCT2BIN returns the #VALUE! + * error value. + * If places is negative, OCT2BIN returns the #NUM! error + * value. + * + * @return string + */ + public static function OCTTOBIN($x, $places = null) + { + return Engineering\ConvertOctal::toBinary($x, $places); + } + + /** + * OCTTODEC. + * + * Return an octal value as decimal. + * + * Excel Function: + * OCT2DEC(x) + * + * @Deprecated 1.17.0 + * + * @see Use the toDecimal() method in the Engineering\ConvertOctal class instead + * + * @param mixed $x The octal number you want to convert. Number may not contain + * more than 10 octal characters (30 bits). The most significant + * bit of number is the sign bit. The remaining 29 bits are + * magnitude bits. Negative numbers are represented using + * two's-complement notation. + * If number is not a valid octal number, OCT2DEC returns the + * #NUM! error value. + * + * @return string + */ + public static function OCTTODEC($x) + { + return Engineering\ConvertOctal::toDecimal($x); + } + + /** + * OCTTOHEX. + * + * Return an octal value as hex. + * + * Excel Function: + * OCT2HEX(x[,places]) + * + * @Deprecated 1.17.0 + * + * @see Use the toHex() method in the Engineering\ConvertOctal class instead + * + * @param mixed $x The octal number you want to convert. Number may not contain + * more than 10 octal characters (30 bits). The most significant + * bit of number is the sign bit. The remaining 29 bits are + * magnitude bits. Negative numbers are represented using + * two's-complement notation. + * If number is negative, OCT2HEX ignores places and returns a + * 10-character hexadecimal number. + * If number is not a valid octal number, OCT2HEX returns the + * #NUM! error value. + * If OCT2HEX requires more than places characters, it returns + * the #NUM! error value. + * @param mixed $places The number of characters to use. If places is omitted, OCT2HEX + * uses the minimum number of characters necessary. Places is useful + * for padding the return value with leading 0s (zeros). + * If places is not an integer, it is truncated. + * If places is nonnumeric, OCT2HEX returns the #VALUE! error value. + * If places is negative, OCT2HEX returns the #NUM! error value. + * + * @return string + */ + public static function OCTTOHEX($x, $places = null) + { + return Engineering\ConvertOctal::toHex($x, $places); + } + + /** + * COMPLEX. + * + * Converts real and imaginary coefficients into a complex number of the form x +/- yi or x +/- yj. + * + * Excel Function: + * COMPLEX(realNumber,imaginary[,suffix]) + * + * @Deprecated 1.18.0 + * + * @see Use the COMPLEX() method in the Engineering\Complex class instead + * + * @param float $realNumber the real coefficient of the complex number + * @param float $imaginary the imaginary coefficient of the complex number + * @param string $suffix The suffix for the imaginary component of the complex number. + * If omitted, the suffix is assumed to be "i". + * + * @return string + */ + public static function COMPLEX($realNumber = 0.0, $imaginary = 0.0, $suffix = 'i') + { + return Engineering\Complex::COMPLEX($realNumber, $imaginary, $suffix); + } + + /** + * IMAGINARY. + * + * Returns the imaginary coefficient of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMAGINARY(complexNumber) + * + * @Deprecated 1.18.0 + * + * @see Use the IMAGINARY() method in the Engineering\Complex class instead + * + * @param string $complexNumber the complex number for which you want the imaginary + * coefficient + * + * @return float|string + */ + public static function IMAGINARY($complexNumber) + { + return Engineering\Complex::IMAGINARY($complexNumber); + } + + /** + * IMREAL. + * + * Returns the real coefficient of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMREAL(complexNumber) + * + * @Deprecated 1.18.0 + * + * @see Use the IMREAL() method in the Engineering\Complex class instead + * + * @param string $complexNumber the complex number for which you want the real coefficient + * + * @return float|string + */ + public static function IMREAL($complexNumber) + { + return Engineering\Complex::IMREAL($complexNumber); + } + + /** + * IMABS. + * + * Returns the absolute value (modulus) of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMABS(complexNumber) + * + * @Deprecated 1.18.0 + * + * @see Use the IMABS() method in the Engineering\ComplexFunctions class instead + * + * @param string $complexNumber the complex number for which you want the absolute value + * + * @return float|string + */ + public static function IMABS($complexNumber) + { + return ComplexFunctions::IMABS($complexNumber); + } + + /** + * IMARGUMENT. + * + * Returns the argument theta of a complex number, i.e. the angle in radians from the real + * axis to the representation of the number in polar coordinates. + * + * Excel Function: + * IMARGUMENT(complexNumber) + * + * @Deprecated 1.18.0 + * + * @see Use the IMARGUMENT() method in the Engineering\ComplexFunctions class instead + * + * @param string $complexNumber the complex number for which you want the argument theta + * + * @return float|string + */ + public static function IMARGUMENT($complexNumber) + { + return ComplexFunctions::IMARGUMENT($complexNumber); + } + + /** + * IMCONJUGATE. + * + * Returns the complex conjugate of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMCONJUGATE(complexNumber) + * + * @Deprecated 1.18.0 + * + * @see Use the IMARGUMENT() method in the Engineering\ComplexFunctions class instead + * + * @param string $complexNumber the complex number for which you want the conjugate + * + * @return string + */ + public static function IMCONJUGATE($complexNumber) + { + return ComplexFunctions::IMCONJUGATE($complexNumber); + } + + /** + * IMCOS. + * + * Returns the cosine of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMCOS(complexNumber) + * + * @Deprecated 1.18.0 + * + * @see Use the IMCOS() method in the Engineering\ComplexFunctions class instead + * + * @param string $complexNumber the complex number for which you want the cosine + * + * @return float|string + */ + public static function IMCOS($complexNumber) + { + return ComplexFunctions::IMCOS($complexNumber); + } + + /** + * IMCOSH. + * + * Returns the hyperbolic cosine of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMCOSH(complexNumber) + * + * @Deprecated 1.18.0 + * + * @see Use the IMCOSH() method in the Engineering\ComplexFunctions class instead + * + * @param string $complexNumber the complex number for which you want the hyperbolic cosine + * + * @return float|string + */ + public static function IMCOSH($complexNumber) + { + return ComplexFunctions::IMCOSH($complexNumber); + } + + /** + * IMCOT. + * + * Returns the cotangent of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMCOT(complexNumber) + * + * @Deprecated 1.18.0 + * + * @see Use the IMCOT() method in the Engineering\ComplexFunctions class instead + * + * @param string $complexNumber the complex number for which you want the cotangent + * + * @return float|string + */ + public static function IMCOT($complexNumber) + { + return ComplexFunctions::IMCOT($complexNumber); + } + + /** + * IMCSC. + * + * Returns the cosecant of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMCSC(complexNumber) + * + * @Deprecated 1.18.0 + * + * @see Use the IMCSC() method in the Engineering\ComplexFunctions class instead + * + * @param string $complexNumber the complex number for which you want the cosecant + * + * @return float|string + */ + public static function IMCSC($complexNumber) + { + return ComplexFunctions::IMCSC($complexNumber); + } + + /** + * IMCSCH. + * + * Returns the hyperbolic cosecant of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMCSCH(complexNumber) + * + * @Deprecated 1.18.0 + * + * @see Use the IMCSCH() method in the Engineering\ComplexFunctions class instead + * + * @param string $complexNumber the complex number for which you want the hyperbolic cosecant + * + * @return float|string + */ + public static function IMCSCH($complexNumber) + { + return ComplexFunctions::IMCSCH($complexNumber); + } + + /** + * IMSIN. + * + * Returns the sine of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMSIN(complexNumber) + * + * @Deprecated 1.18.0 + * + * @see Use the IMSIN() method in the Engineering\ComplexFunctions class instead + * + * @param string $complexNumber the complex number for which you want the sine + * + * @return float|string + */ + public static function IMSIN($complexNumber) + { + return ComplexFunctions::IMSIN($complexNumber); + } + + /** + * IMSINH. + * + * Returns the hyperbolic sine of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMSINH(complexNumber) + * + * @Deprecated 1.18.0 + * + * @see Use the IMSINH() method in the Engineering\ComplexFunctions class instead + * + * @param string $complexNumber the complex number for which you want the hyperbolic sine + * + * @return float|string + */ + public static function IMSINH($complexNumber) + { + return ComplexFunctions::IMSINH($complexNumber); + } + + /** + * IMSEC. + * + * Returns the secant of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMSEC(complexNumber) + * + * @Deprecated 1.18.0 + * + * @see Use the IMSEC() method in the Engineering\ComplexFunctions class instead + * + * @param string $complexNumber the complex number for which you want the secant + * + * @return float|string + */ + public static function IMSEC($complexNumber) + { + return ComplexFunctions::IMSEC($complexNumber); + } + + /** + * IMSECH. + * + * Returns the hyperbolic secant of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMSECH(complexNumber) + * + * @Deprecated 1.18.0 + * + * @see Use the IMSECH() method in the Engineering\ComplexFunctions class instead + * + * @param string $complexNumber the complex number for which you want the hyperbolic secant + * + * @return float|string + */ + public static function IMSECH($complexNumber) + { + return ComplexFunctions::IMSECH($complexNumber); + } + + /** + * IMTAN. + * + * Returns the tangent of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMTAN(complexNumber) + * + * @Deprecated 1.18.0 + * + * @see Use the IMTAN() method in the Engineering\ComplexFunctions class instead + * + * @param string $complexNumber the complex number for which you want the tangent + * + * @return float|string + */ + public static function IMTAN($complexNumber) + { + return ComplexFunctions::IMTAN($complexNumber); + } + + /** + * IMSQRT. + * + * Returns the square root of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMSQRT(complexNumber) + * + * @Deprecated 1.18.0 + * + * @see Use the IMSQRT() method in the Engineering\ComplexFunctions class instead + * + * @param string $complexNumber the complex number for which you want the square root + * + * @return string + */ + public static function IMSQRT($complexNumber) + { + return ComplexFunctions::IMSQRT($complexNumber); + } + + /** + * IMLN. + * + * Returns the natural logarithm of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMLN(complexNumber) + * + * @Deprecated 1.18.0 + * + * @see Use the IMLN() method in the Engineering\ComplexFunctions class instead + * + * @param string $complexNumber the complex number for which you want the natural logarithm + * + * @return string + */ + public static function IMLN($complexNumber) + { + return ComplexFunctions::IMLN($complexNumber); + } + + /** + * IMLOG10. + * + * Returns the common logarithm (base 10) of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMLOG10(complexNumber) + * + * @Deprecated 1.18.0 + * + * @see Use the IMLOG10() method in the Engineering\ComplexFunctions class instead + * + * @param string $complexNumber the complex number for which you want the common logarithm + * + * @return string + */ + public static function IMLOG10($complexNumber) + { + return ComplexFunctions::IMLOG10($complexNumber); + } + + /** + * IMLOG2. + * + * Returns the base-2 logarithm of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMLOG2(complexNumber) + * + * @Deprecated 1.18.0 + * + * @see Use the IMLOG2() method in the Engineering\ComplexFunctions class instead + * + * @param string $complexNumber the complex number for which you want the base-2 logarithm + * + * @return string + */ + public static function IMLOG2($complexNumber) + { + return ComplexFunctions::IMLOG2($complexNumber); + } + + /** + * IMEXP. + * + * Returns the exponential of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMEXP(complexNumber) + * + * @Deprecated 1.18.0 + * + * @see Use the IMEXP() method in the Engineering\ComplexFunctions class instead + * + * @param string $complexNumber the complex number for which you want the exponential + * + * @return string + */ + public static function IMEXP($complexNumber) + { + return ComplexFunctions::IMEXP($complexNumber); + } + + /** + * IMPOWER. + * + * Returns a complex number in x + yi or x + yj text format raised to a power. + * + * Excel Function: + * IMPOWER(complexNumber,realNumber) + * + * @Deprecated 1.18.0 + * + * @see Use the IMPOWER() method in the Engineering\ComplexFunctions class instead + * + * @param string $complexNumber the complex number you want to raise to a power + * @param float $realNumber the power to which you want to raise the complex number + * + * @return string + */ + public static function IMPOWER($complexNumber, $realNumber) + { + return ComplexFunctions::IMPOWER($complexNumber, $realNumber); + } + + /** + * IMDIV. + * + * Returns the quotient of two complex numbers in x + yi or x + yj text format. + * + * Excel Function: + * IMDIV(complexDividend,complexDivisor) + * + * @Deprecated 1.18.0 + * + * @see Use the IMDIV() method in the Engineering\ComplexOperations class instead + * + * @param string $complexDividend the complex numerator or dividend + * @param string $complexDivisor the complex denominator or divisor + * + * @return string + */ + public static function IMDIV($complexDividend, $complexDivisor) + { + return ComplexOperations::IMDIV($complexDividend, $complexDivisor); + } + + /** + * IMSUB. + * + * Returns the difference of two complex numbers in x + yi or x + yj text format. + * + * Excel Function: + * IMSUB(complexNumber1,complexNumber2) + * + * @Deprecated 1.18.0 + * + * @see Use the IMSUB() method in the Engineering\ComplexOperations class instead + * + * @param string $complexNumber1 the complex number from which to subtract complexNumber2 + * @param string $complexNumber2 the complex number to subtract from complexNumber1 + * + * @return string + */ + public static function IMSUB($complexNumber1, $complexNumber2) + { + return ComplexOperations::IMSUB($complexNumber1, $complexNumber2); + } + + /** + * IMSUM. + * + * Returns the sum of two or more complex numbers in x + yi or x + yj text format. + * + * Excel Function: + * IMSUM(complexNumber[,complexNumber[,...]]) + * + * @Deprecated 1.18.0 + * + * @see Use the IMSUM() method in the Engineering\ComplexOperations class instead + * + * @param string ...$complexNumbers Series of complex numbers to add + * + * @return string + */ + public static function IMSUM(...$complexNumbers) + { + return ComplexOperations::IMSUM(...$complexNumbers); + } + + /** + * IMPRODUCT. + * + * Returns the product of two or more complex numbers in x + yi or x + yj text format. + * + * Excel Function: + * IMPRODUCT(complexNumber[,complexNumber[,...]]) + * + * @Deprecated 1.18.0 + * + * @see Use the IMPRODUCT() method in the Engineering\ComplexOperations class instead + * + * @param string ...$complexNumbers Series of complex numbers to multiply + * + * @return string + */ + public static function IMPRODUCT(...$complexNumbers) + { + return ComplexOperations::IMPRODUCT(...$complexNumbers); + } + + /** + * DELTA. + * + * Tests whether two values are equal. Returns 1 if number1 = number2; returns 0 otherwise. + * Use this function to filter a set of values. For example, by summing several DELTA + * functions you calculate the count of equal pairs. This function is also known as the + * Kronecker Delta function. + * + * Excel Function: + * DELTA(a[,b]) + * + * @Deprecated 1.17.0 + * + * @see Use the DELTA() method in the Engineering\Compare class instead + * + * @param float $a the first number + * @param float $b The second number. If omitted, b is assumed to be zero. + * + * @return int|string (string in the event of an error) + */ + public static function DELTA($a, $b = 0) + { + return Engineering\Compare::DELTA($a, $b); + } + + /** + * GESTEP. + * + * Excel Function: + * GESTEP(number[,step]) + * + * Returns 1 if number >= step; returns 0 (zero) otherwise + * Use this function to filter a set of values. For example, by summing several GESTEP + * functions you calculate the count of values that exceed a threshold. + * + * @Deprecated 1.17.0 + * + * @see Use the GESTEP() method in the Engineering\Compare class instead + * + * @param float $number the value to test against step + * @param float $step The threshold value. If you omit a value for step, GESTEP uses zero. + * + * @return int|string (string in the event of an error) + */ + public static function GESTEP($number, $step = 0) + { + return Engineering\Compare::GESTEP($number, $step); + } + + /** + * BITAND. + * + * Returns the bitwise AND of two integer values. + * + * Excel Function: + * BITAND(number1, number2) + * + * @Deprecated 1.17.0 + * + * @see Use the BITAND() method in the Engineering\BitWise class instead + * + * @param int $number1 + * @param int $number2 + * + * @return int|string + */ + public static function BITAND($number1, $number2) + { + return Engineering\BitWise::BITAND($number1, $number2); + } + + /** + * BITOR. + * + * Returns the bitwise OR of two integer values. + * + * Excel Function: + * BITOR(number1, number2) + * + * @Deprecated 1.17.0 + * + * @see Use the BITOR() method in the Engineering\BitWise class instead + * + * @param int $number1 + * @param int $number2 + * + * @return int|string + */ + public static function BITOR($number1, $number2) + { + return Engineering\BitWise::BITOR($number1, $number2); + } + + /** + * BITXOR. + * + * Returns the bitwise XOR of two integer values. + * + * Excel Function: + * BITXOR(number1, number2) + * + * @Deprecated 1.17.0 + * + * @see Use the BITXOR() method in the Engineering\BitWise class instead + * + * @param int $number1 + * @param int $number2 + * + * @return int|string + */ + public static function BITXOR($number1, $number2) + { + return Engineering\BitWise::BITXOR($number1, $number2); + } + + /** + * BITLSHIFT. + * + * Returns the number value shifted left by shift_amount bits. + * + * Excel Function: + * BITLSHIFT(number, shift_amount) + * + * @Deprecated 1.17.0 + * + * @see Use the BITLSHIFT() method in the Engineering\BitWise class instead + * + * @param int $number + * @param int $shiftAmount + * + * @return int|string + */ + public static function BITLSHIFT($number, $shiftAmount) + { + return Engineering\BitWise::BITLSHIFT($number, $shiftAmount); + } + + /** + * BITRSHIFT. + * + * Returns the number value shifted right by shift_amount bits. + * + * Excel Function: + * BITRSHIFT(number, shift_amount) + * + * @Deprecated 1.17.0 + * + * @see Use the BITRSHIFT() method in the Engineering\BitWise class instead + * + * @param int $number + * @param int $shiftAmount + * + * @return int|string + */ + public static function BITRSHIFT($number, $shiftAmount) + { + return Engineering\BitWise::BITRSHIFT($number, $shiftAmount); + } + + /** + * ERF. + * + * Returns the error function integrated between the lower and upper bound arguments. + * + * Note: In Excel 2007 or earlier, if you input a negative value for the upper or lower bound arguments, + * the function would return a #NUM! error. However, in Excel 2010, the function algorithm was + * improved, so that it can now calculate the function for both positive and negative ranges. + * PhpSpreadsheet follows Excel 2010 behaviour, and accepts negative arguments. + * + * Excel Function: + * ERF(lower[,upper]) + * + * @Deprecated 1.17.0 + * + * @see Use the ERF() method in the Engineering\Erf class instead + * + * @param float $lower lower bound for integrating ERF + * @param float $upper upper bound for integrating ERF. + * If omitted, ERF integrates between zero and lower_limit + * + * @return float|string + */ + public static function ERF($lower, $upper = null) + { + return Engineering\Erf::ERF($lower, $upper); + } + + /** + * ERFPRECISE. + * + * Returns the error function integrated between the lower and upper bound arguments. + * + * Excel Function: + * ERF.PRECISE(limit) + * + * @Deprecated 1.17.0 + * + * @see Use the ERFPRECISE() method in the Engineering\Erf class instead + * + * @param float $limit bound for integrating ERF + * + * @return float|string + */ + public static function ERFPRECISE($limit) + { + return Engineering\Erf::ERFPRECISE($limit); + } + + /** + * ERFC. + * + * Returns the complementary ERF function integrated between x and infinity + * + * Note: In Excel 2007 or earlier, if you input a negative value for the lower bound argument, + * the function would return a #NUM! error. However, in Excel 2010, the function algorithm was + * improved, so that it can now calculate the function for both positive and negative x values. + * PhpSpreadsheet follows Excel 2010 behaviour, and accepts nagative arguments. + * + * Excel Function: + * ERFC(x) + * + * @Deprecated 1.17.0 + * + * @see Use the ERFC() method in the Engineering\ErfC class instead + * + * @param float $x The lower bound for integrating ERFC + * + * @return float|string + */ + public static function ERFC($x) + { + return Engineering\ErfC::ERFC($x); + } + + /** + * getConversionGroups + * Returns a list of the different conversion groups for UOM conversions. + * + * @Deprecated 1.16.0 + * + * @see Use the getConversionCategories() method in the Engineering\ConvertUOM class instead + * + * @return array + */ + public static function getConversionGroups() + { + return Engineering\ConvertUOM::getConversionCategories(); + } + + /** + * getConversionGroupUnits + * Returns an array of units of measure, for a specified conversion group, or for all groups. + * + * @Deprecated 1.16.0 + * + * @see Use the getConversionCategoryUnits() method in the ConvertUOM class instead + * + * @param null|mixed $category + * + * @return array + */ + public static function getConversionGroupUnits($category = null) + { + return Engineering\ConvertUOM::getConversionCategoryUnits($category); + } + + /** + * getConversionGroupUnitDetails. + * + * @Deprecated 1.16.0 + * + * @see Use the getConversionCategoryUnitDetails() method in the ConvertUOM class instead + * + * @param null|mixed $category + * + * @return array + */ + public static function getConversionGroupUnitDetails($category = null) + { + return Engineering\ConvertUOM::getConversionCategoryUnitDetails($category); + } + + /** + * getConversionMultipliers + * Returns an array of the Multiplier prefixes that can be used with Units of Measure in CONVERTUOM(). + * + * @Deprecated 1.16.0 + * + * @see Use the getConversionMultipliers() method in the ConvertUOM class instead + * + * @return mixed[] + */ + public static function getConversionMultipliers() + { + return Engineering\ConvertUOM::getConversionMultipliers(); + } + + /** + * getBinaryConversionMultipliers. + * + * Returns an array of the additional Multiplier prefixes that can be used with Information Units of Measure + * in CONVERTUOM(). + * + * @Deprecated 1.16.0 + * + * @see Use the getBinaryConversionMultipliers() method in the ConvertUOM class instead + * + * @return mixed[] + */ + public static function getBinaryConversionMultipliers() + { + return Engineering\ConvertUOM::getBinaryConversionMultipliers(); + } + + /** + * CONVERTUOM. + * + * Converts a number from one measurement system to another. + * For example, CONVERT can translate a table of distances in miles to a table of distances + * in kilometers. + * + * Excel Function: + * CONVERT(value,fromUOM,toUOM) + * + * @Deprecated 1.16.0 + * + * @see Use the CONVERT() method in the ConvertUOM class instead + * + * @param float|int $value the value in fromUOM to convert + * @param string $fromUOM the units for value + * @param string $toUOM the units for the result + * + * @return float|string + */ + public static function CONVERTUOM($value, $fromUOM, $toUOM) + { + return Engineering\ConvertUOM::CONVERT($value, $fromUOM, $toUOM); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselI.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselI.php new file mode 100644 index 0000000..ea2577c --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselI.php @@ -0,0 +1,137 @@ +getMessage(); + } + + if ($ord < 0) { + return Functions::NAN(); + } + + $fResult = self::calculate($x, $ord); + + return (is_nan($fResult)) ? Functions::NAN() : $fResult; + } + + private static function calculate(float $x, int $ord): float + { + // special cases + switch ($ord) { + case 0: + return self::besselI0($x); + case 1: + return self::besselI1($x); + } + + return self::besselI2($x, $ord); + } + + private static function besselI0(float $x): float + { + $ax = abs($x); + + if ($ax < 3.75) { + $y = $x / 3.75; + $y = $y * $y; + + return 1.0 + $y * (3.5156229 + $y * (3.0899424 + $y * (1.2067492 + + $y * (0.2659732 + $y * (0.360768e-1 + $y * 0.45813e-2))))); + } + + $y = 3.75 / $ax; + + return (exp($ax) / sqrt($ax)) * (0.39894228 + $y * (0.1328592e-1 + $y * (0.225319e-2 + $y * (-0.157565e-2 + + $y * (0.916281e-2 + $y * (-0.2057706e-1 + $y * (0.2635537e-1 + + $y * (-0.1647633e-1 + $y * 0.392377e-2)))))))); + } + + private static function besselI1(float $x): float + { + $ax = abs($x); + + if ($ax < 3.75) { + $y = $x / 3.75; + $y = $y * $y; + $ans = $ax * (0.5 + $y * (0.87890594 + $y * (0.51498869 + $y * (0.15084934 + $y * (0.2658733e-1 + + $y * (0.301532e-2 + $y * 0.32411e-3)))))); + + return ($x < 0.0) ? -$ans : $ans; + } + + $y = 3.75 / $ax; + $ans = 0.2282967e-1 + $y * (-0.2895312e-1 + $y * (0.1787654e-1 - $y * 0.420059e-2)); + $ans = 0.39894228 + $y * (-0.3988024e-1 + $y * (-0.362018e-2 + $y * (0.163801e-2 + + $y * (-0.1031555e-1 + $y * $ans)))); + $ans *= exp($ax) / sqrt($ax); + + return ($x < 0.0) ? -$ans : $ans; + } + + private static function besselI2(float $x, int $ord): float + { + if ($x === 0.0) { + return 0.0; + } + + $tox = 2.0 / abs($x); + $bip = 0; + $ans = 0.0; + $bi = 1.0; + + for ($j = 2 * ($ord + (int) sqrt(40.0 * $ord)); $j > 0; --$j) { + $bim = $bip + $j * $tox * $bi; + $bip = $bi; + $bi = $bim; + + if (abs($bi) > 1.0e+12) { + $ans *= 1.0e-12; + $bi *= 1.0e-12; + $bip *= 1.0e-12; + } + + if ($j === $ord) { + $ans = $bip; + } + } + + $ans *= self::besselI0($x) / $bi; + + return ($x < 0.0 && (($ord % 2) === 1)) ? -$ans : $ans; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselJ.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselJ.php new file mode 100644 index 0000000..7ea45a7 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselJ.php @@ -0,0 +1,172 @@ + 8. This code provides a more accurate calculation + * + * @param mixed $x A float value at which to evaluate the function. + * If x is nonnumeric, BESSELJ returns the #VALUE! error value. + * @param mixed $ord The integer order of the Bessel function. + * If ord is not an integer, it is truncated. + * If $ord is nonnumeric, BESSELJ returns the #VALUE! error value. + * If $ord < 0, BESSELJ returns the #NUM! error value. + * + * @return float|string Result, or a string containing an error + */ + public static function BESSELJ($x, $ord) + { + $x = Functions::flattenSingleValue($x); + $ord = Functions::flattenSingleValue($ord); + + try { + $x = EngineeringValidations::validateFloat($x); + $ord = EngineeringValidations::validateInt($ord); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($ord < 0) { + return Functions::NAN(); + } + + $fResult = self::calculate($x, $ord); + + return (is_nan($fResult)) ? Functions::NAN() : $fResult; + } + + private static function calculate(float $x, int $ord): float + { + // special cases + switch ($ord) { + case 0: + return self::besselJ0($x); + case 1: + return self::besselJ1($x); + } + + return self::besselJ2($x, $ord); + } + + private static function besselJ0(float $x): float + { + $ax = abs($x); + + if ($ax < 8.0) { + $y = $x * $x; + $ans1 = 57568490574.0 + $y * (-13362590354.0 + $y * (651619640.7 + $y * (-11214424.18 + $y * + (77392.33017 + $y * (-184.9052456))))); + $ans2 = 57568490411.0 + $y * (1029532985.0 + $y * (9494680.718 + $y * (59272.64853 + $y * + (267.8532712 + $y * 1.0)))); + + return $ans1 / $ans2; + } + + $z = 8.0 / $ax; + $y = $z * $z; + $xx = $ax - 0.785398164; + $ans1 = 1.0 + $y * (-0.1098628627e-2 + $y * (0.2734510407e-4 + $y * (-0.2073370639e-5 + $y * 0.2093887211e-6))); + $ans2 = -0.1562499995e-1 + $y * (0.1430488765e-3 + $y * (-0.6911147651e-5 + $y * + (0.7621095161e-6 - $y * 0.934935152e-7))); + + return sqrt(0.636619772 / $ax) * (cos($xx) * $ans1 - $z * sin($xx) * $ans2); + } + + private static function besselJ1(float $x): float + { + $ax = abs($x); + + if ($ax < 8.0) { + $y = $x * $x; + $ans1 = $x * (72362614232.0 + $y * (-7895059235.0 + $y * (242396853.1 + $y * + (-2972611.439 + $y * (15704.48260 + $y * (-30.16036606)))))); + $ans2 = 144725228442.0 + $y * (2300535178.0 + $y * (18583304.74 + $y * (99447.43394 + $y * + (376.9991397 + $y * 1.0)))); + + return $ans1 / $ans2; + } + + $z = 8.0 / $ax; + $y = $z * $z; + $xx = $ax - 2.356194491; + + $ans1 = 1.0 + $y * (0.183105e-2 + $y * (-0.3516396496e-4 + $y * (0.2457520174e-5 + $y * (-0.240337019e-6)))); + $ans2 = 0.04687499995 + $y * (-0.2002690873e-3 + $y * (0.8449199096e-5 + $y * + (-0.88228987e-6 + $y * 0.105787412e-6))); + $ans = sqrt(0.636619772 / $ax) * (cos($xx) * $ans1 - $z * sin($xx) * $ans2); + + return ($x < 0.0) ? -$ans : $ans; + } + + private static function besselJ2(float $x, int $ord): float + { + $ax = abs($x); + if ($ax === 0.0) { + return 0.0; + } + + if ($ax > $ord) { + return self::besselj2a($ax, $ord, $x); + } + + return self::besselj2b($ax, $ord, $x); + } + + private static function besselj2a(float $ax, int $ord, float $x) + { + $tox = 2.0 / $ax; + $bjm = self::besselJ0($ax); + $bj = self::besselJ1($ax); + for ($j = 1; $j < $ord; ++$j) { + $bjp = $j * $tox * $bj - $bjm; + $bjm = $bj; + $bj = $bjp; + } + $ans = $bj; + + return ($x < 0.0 && ($ord % 2) == 1) ? -$ans : $ans; + } + + private static function besselj2b(float $ax, int $ord, float $x) + { + $tox = 2.0 / $ax; + $jsum = false; + $bjp = $ans = $sum = 0.0; + $bj = 1.0; + for ($j = 2 * ($ord + (int) sqrt(40.0 * $ord)); $j > 0; --$j) { + $bjm = $j * $tox * $bj - $bjp; + $bjp = $bj; + $bj = $bjm; + if (abs($bj) > 1.0e+10) { + $bj *= 1.0e-10; + $bjp *= 1.0e-10; + $ans *= 1.0e-10; + $sum *= 1.0e-10; + } + if ($jsum === true) { + $sum += $bj; + } + $jsum = !$jsum; + if ($j === $ord) { + $ans = $bjp; + } + } + $sum = 2.0 * $sum - $bj; + $ans /= $sum; + + return ($x < 0.0 && ($ord % 2) === 1) ? -$ans : $ans; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselK.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselK.php new file mode 100644 index 0000000..8facdff --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselK.php @@ -0,0 +1,111 @@ +getMessage(); + } + + if (($ord < 0) || ($x <= 0.0)) { + return Functions::NAN(); + } + + $fBk = self::calculate($x, $ord); + + return (is_nan($fBk)) ? Functions::NAN() : $fBk; + } + + private static function calculate(float $x, int $ord): float + { + // special cases + switch ($ord) { + case 0: + return self::besselK0($x); + case 1: + return self::besselK1($x); + } + + return self::besselK2($x, $ord); + } + + private static function besselK0(float $x): float + { + if ($x <= 2) { + $fNum2 = $x * 0.5; + $y = ($fNum2 * $fNum2); + + return -log($fNum2) * BesselI::BESSELI($x, 0) + + (-0.57721566 + $y * (0.42278420 + $y * (0.23069756 + $y * (0.3488590e-1 + $y * (0.262698e-2 + $y * + (0.10750e-3 + $y * 0.74e-5)))))); + } + + $y = 2 / $x; + + return exp(-$x) / sqrt($x) * + (1.25331414 + $y * (-0.7832358e-1 + $y * (0.2189568e-1 + $y * (-0.1062446e-1 + $y * + (0.587872e-2 + $y * (-0.251540e-2 + $y * 0.53208e-3)))))); + } + + private static function besselK1(float $x): float + { + if ($x <= 2) { + $fNum2 = $x * 0.5; + $y = ($fNum2 * $fNum2); + + return log($fNum2) * BesselI::BESSELI($x, 1) + + (1 + $y * (0.15443144 + $y * (-0.67278579 + $y * (-0.18156897 + $y * (-0.1919402e-1 + $y * + (-0.110404e-2 + $y * (-0.4686e-4))))))) / $x; + } + + $y = 2 / $x; + + return exp(-$x) / sqrt($x) * + (1.25331414 + $y * (0.23498619 + $y * (-0.3655620e-1 + $y * (0.1504268e-1 + $y * (-0.780353e-2 + $y * + (0.325614e-2 + $y * (-0.68245e-3))))))); + } + + private static function besselK2(float $x, int $ord) + { + $fTox = 2 / $x; + $fBkm = self::besselK0($x); + $fBk = self::besselK1($x); + for ($n = 1; $n < $ord; ++$n) { + $fBkp = $fBkm + $n * $fTox * $fBk; + $fBkm = $fBk; + $fBk = $fBkp; + } + + return $fBk; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselY.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselY.php new file mode 100644 index 0000000..7f38749 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselY.php @@ -0,0 +1,118 @@ +getMessage(); + } + + if (($ord < 0) || ($x <= 0.0)) { + return Functions::NAN(); + } + + $fBy = self::calculate($x, $ord); + + return (is_nan($fBy)) ? Functions::NAN() : $fBy; + } + + private static function calculate(float $x, int $ord): float + { + // special cases + switch ($ord) { + case 0: + return self::besselY0($x); + case 1: + return self::besselY1($x); + } + + return self::besselY2($x, $ord); + } + + private static function besselY0(float $x): float + { + if ($x < 8.0) { + $y = ($x * $x); + $ans1 = -2957821389.0 + $y * (7062834065.0 + $y * (-512359803.6 + $y * (10879881.29 + $y * + (-86327.92757 + $y * 228.4622733)))); + $ans2 = 40076544269.0 + $y * (745249964.8 + $y * (7189466.438 + $y * + (47447.26470 + $y * (226.1030244 + $y)))); + + return $ans1 / $ans2 + 0.636619772 * BesselJ::BESSELJ($x, 0) * log($x); + } + + $z = 8.0 / $x; + $y = ($z * $z); + $xx = $x - 0.785398164; + $ans1 = 1 + $y * (-0.1098628627e-2 + $y * (0.2734510407e-4 + $y * (-0.2073370639e-5 + $y * 0.2093887211e-6))); + $ans2 = -0.1562499995e-1 + $y * (0.1430488765e-3 + $y * (-0.6911147651e-5 + $y * (0.7621095161e-6 + $y * + (-0.934945152e-7)))); + + return sqrt(0.636619772 / $x) * (sin($xx) * $ans1 + $z * cos($xx) * $ans2); + } + + private static function besselY1(float $x): float + { + if ($x < 8.0) { + $y = ($x * $x); + $ans1 = $x * (-0.4900604943e13 + $y * (0.1275274390e13 + $y * (-0.5153438139e11 + $y * + (0.7349264551e9 + $y * (-0.4237922726e7 + $y * 0.8511937935e4))))); + $ans2 = 0.2499580570e14 + $y * (0.4244419664e12 + $y * (0.3733650367e10 + $y * (0.2245904002e8 + $y * + (0.1020426050e6 + $y * (0.3549632885e3 + $y))))); + + return ($ans1 / $ans2) + 0.636619772 * (BesselJ::BESSELJ($x, 1) * log($x) - 1 / $x); + } + + $z = 8.0 / $x; + $y = $z * $z; + $xx = $x - 2.356194491; + $ans1 = 1.0 + $y * (0.183105e-2 + $y * (-0.3516396496e-4 + $y * (0.2457520174e-5 + $y * (-0.240337019e-6)))); + $ans2 = 0.04687499995 + $y * (-0.2002690873e-3 + $y * (0.8449199096e-5 + $y * + (-0.88228987e-6 + $y * 0.105787412e-6))); + + return sqrt(0.636619772 / $x) * (sin($xx) * $ans1 + $z * cos($xx) * $ans2); + } + + private static function besselY2(float $x, int $ord): float + { + $fTox = 2.0 / $x; + $fBym = self::besselY0($x); + $fBy = self::besselY1($x); + for ($n = 1; $n < $ord; ++$n) { + $fByp = $n * $fTox * $fBy - $fBym; + $fBym = $fBy; + $fBy = $fByp; + } + + return $fBy; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BitWise.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BitWise.php new file mode 100644 index 0000000..9958f05 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BitWise.php @@ -0,0 +1,227 @@ +getMessage(); + } + $split1 = self::splitNumber($number1); + $split2 = self::splitNumber($number2); + + return self::SPLIT_DIVISOR * ($split1[0] & $split2[0]) + ($split1[1] & $split2[1]); + } + + /** + * BITOR. + * + * Returns the bitwise OR of two integer values. + * + * Excel Function: + * BITOR(number1, number2) + * + * @param int $number1 + * @param int $number2 + * + * @return int|string + */ + public static function BITOR($number1, $number2) + { + try { + $number1 = self::validateBitwiseArgument($number1); + $number2 = self::validateBitwiseArgument($number2); + } catch (Exception $e) { + return $e->getMessage(); + } + + $split1 = self::splitNumber($number1); + $split2 = self::splitNumber($number2); + + return self::SPLIT_DIVISOR * ($split1[0] | $split2[0]) + ($split1[1] | $split2[1]); + } + + /** + * BITXOR. + * + * Returns the bitwise XOR of two integer values. + * + * Excel Function: + * BITXOR(number1, number2) + * + * @param int $number1 + * @param int $number2 + * + * @return int|string + */ + public static function BITXOR($number1, $number2) + { + try { + $number1 = self::validateBitwiseArgument($number1); + $number2 = self::validateBitwiseArgument($number2); + } catch (Exception $e) { + return $e->getMessage(); + } + + $split1 = self::splitNumber($number1); + $split2 = self::splitNumber($number2); + + return self::SPLIT_DIVISOR * ($split1[0] ^ $split2[0]) + ($split1[1] ^ $split2[1]); + } + + /** + * BITLSHIFT. + * + * Returns the number value shifted left by shift_amount bits. + * + * Excel Function: + * BITLSHIFT(number, shift_amount) + * + * @param int $number + * @param int $shiftAmount + * + * @return float|int|string + */ + public static function BITLSHIFT($number, $shiftAmount) + { + try { + $number = self::validateBitwiseArgument($number); + $shiftAmount = self::validateShiftAmount($shiftAmount); + } catch (Exception $e) { + return $e->getMessage(); + } + + $result = floor($number * (2 ** $shiftAmount)); + if ($result > 2 ** 48 - 1) { + return Functions::NAN(); + } + + return $result; + } + + /** + * BITRSHIFT. + * + * Returns the number value shifted right by shift_amount bits. + * + * Excel Function: + * BITRSHIFT(number, shift_amount) + * + * @param int $number + * @param int $shiftAmount + * + * @return float|int|string + */ + public static function BITRSHIFT($number, $shiftAmount) + { + try { + $number = self::validateBitwiseArgument($number); + $shiftAmount = self::validateShiftAmount($shiftAmount); + } catch (Exception $e) { + return $e->getMessage(); + } + + $result = floor($number / (2 ** $shiftAmount)); + if ($result > 2 ** 48 - 1) { // possible because shiftAmount can be negative + return Functions::NAN(); + } + + return $result; + } + + /** + * Validate arguments passed to the bitwise functions. + * + * @param mixed $value + * + * @return float|int + */ + private static function validateBitwiseArgument($value) + { + self::nullFalseTrueToNumber($value); + + if (is_numeric($value)) { + if ($value == floor($value)) { + if (($value > 2 ** 48 - 1) || ($value < 0)) { + throw new Exception(Functions::NAN()); + } + + return floor($value); + } + + throw new Exception(Functions::NAN()); + } + + throw new Exception(Functions::VALUE()); + } + + /** + * Validate arguments passed to the bitwise functions. + * + * @param mixed $value + * + * @return int + */ + private static function validateShiftAmount($value) + { + self::nullFalseTrueToNumber($value); + + if (is_numeric($value)) { + if (abs($value) > 53) { + throw new Exception(Functions::NAN()); + } + + return (int) $value; + } + + throw new Exception(Functions::VALUE()); + } + + /** + * Many functions accept null/false/true argument treated as 0/0/1. + * + * @param mixed $number + */ + public static function nullFalseTrueToNumber(&$number): void + { + $number = Functions::flattenSingleValue($number); + if ($number === null) { + $number = 0; + } elseif (is_bool($number)) { + $number = (int) $number; + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Compare.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Compare.php new file mode 100644 index 0000000..0a63420 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Compare.php @@ -0,0 +1,70 @@ +getMessage(); + } + + return (int) ($a == $b); + } + + /** + * GESTEP. + * + * Excel Function: + * GESTEP(number[,step]) + * + * Returns 1 if number >= step; returns 0 (zero) otherwise + * Use this function to filter a set of values. For example, by summing several GESTEP + * functions you calculate the count of values that exceed a threshold. + * + * @param float $number the value to test against step + * @param float $step The threshold value. If you omit a value for step, GESTEP uses zero. + * + * @return int|string (string in the event of an error) + */ + public static function GESTEP($number, $step = 0) + { + $number = Functions::flattenSingleValue($number); + $step = Functions::flattenSingleValue($step); + + try { + $number = EngineeringValidations::validateFloat($number); + $step = EngineeringValidations::validateFloat($step); + } catch (Exception $e) { + return $e->getMessage(); + } + + return (int) ($number >= $step); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Complex.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Complex.php new file mode 100644 index 0000000..1c2f5f7 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Complex.php @@ -0,0 +1,99 @@ +getMessage(); + } + + if (($suffix == 'i') || ($suffix == 'j') || ($suffix == '')) { + $complex = new ComplexObject($realNumber, $imaginary, $suffix); + + return (string) $complex; + } + + return Functions::VALUE(); + } + + /** + * IMAGINARY. + * + * Returns the imaginary coefficient of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMAGINARY(complexNumber) + * + * @param string $complexNumber the complex number for which you want the imaginary + * coefficient + * + * @return float|string + */ + public static function IMAGINARY($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return Functions::NAN(); + } + + return $complex->getImaginary(); + } + + /** + * IMREAL. + * + * Returns the real coefficient of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMREAL(complexNumber) + * + * @param string $complexNumber the complex number for which you want the real coefficient + * + * @return float|string + */ + public static function IMREAL($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return Functions::NAN(); + } + + return $complex->getReal(); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ComplexFunctions.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ComplexFunctions.php new file mode 100644 index 0000000..3f37f37 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ComplexFunctions.php @@ -0,0 +1,513 @@ +abs(); + } + + /** + * IMARGUMENT. + * + * Returns the argument theta of a complex number, i.e. the angle in radians from the real + * axis to the representation of the number in polar coordinates. + * + * Excel Function: + * IMARGUMENT(complexNumber) + * + * @param string $complexNumber the complex number for which you want the argument theta + * + * @return float|string + */ + public static function IMARGUMENT($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return Functions::NAN(); + } + + if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { + return Functions::DIV0(); + } + + return $complex->argument(); + } + + /** + * IMCONJUGATE. + * + * Returns the complex conjugate of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMCONJUGATE(complexNumber) + * + * @param string $complexNumber the complex number for which you want the conjugate + * + * @return string + */ + public static function IMCONJUGATE($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return Functions::NAN(); + } + + return (string) $complex->conjugate(); + } + + /** + * IMCOS. + * + * Returns the cosine of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMCOS(complexNumber) + * + * @param string $complexNumber the complex number for which you want the cosine + * + * @return float|string + */ + public static function IMCOS($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return Functions::NAN(); + } + + return (string) $complex->cos(); + } + + /** + * IMCOSH. + * + * Returns the hyperbolic cosine of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMCOSH(complexNumber) + * + * @param string $complexNumber the complex number for which you want the hyperbolic cosine + * + * @return float|string + */ + public static function IMCOSH($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return Functions::NAN(); + } + + return (string) $complex->cosh(); + } + + /** + * IMCOT. + * + * Returns the cotangent of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMCOT(complexNumber) + * + * @param string $complexNumber the complex number for which you want the cotangent + * + * @return float|string + */ + public static function IMCOT($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return Functions::NAN(); + } + + return (string) $complex->cot(); + } + + /** + * IMCSC. + * + * Returns the cosecant of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMCSC(complexNumber) + * + * @param string $complexNumber the complex number for which you want the cosecant + * + * @return float|string + */ + public static function IMCSC($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return Functions::NAN(); + } + + return (string) $complex->csc(); + } + + /** + * IMCSCH. + * + * Returns the hyperbolic cosecant of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMCSCH(complexNumber) + * + * @param string $complexNumber the complex number for which you want the hyperbolic cosecant + * + * @return float|string + */ + public static function IMCSCH($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return Functions::NAN(); + } + + return (string) $complex->csch(); + } + + /** + * IMSIN. + * + * Returns the sine of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMSIN(complexNumber) + * + * @param string $complexNumber the complex number for which you want the sine + * + * @return float|string + */ + public static function IMSIN($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return Functions::NAN(); + } + + return (string) $complex->sin(); + } + + /** + * IMSINH. + * + * Returns the hyperbolic sine of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMSINH(complexNumber) + * + * @param string $complexNumber the complex number for which you want the hyperbolic sine + * + * @return float|string + */ + public static function IMSINH($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return Functions::NAN(); + } + + return (string) $complex->sinh(); + } + + /** + * IMSEC. + * + * Returns the secant of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMSEC(complexNumber) + * + * @param string $complexNumber the complex number for which you want the secant + * + * @return float|string + */ + public static function IMSEC($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return Functions::NAN(); + } + + return (string) $complex->sec(); + } + + /** + * IMSECH. + * + * Returns the hyperbolic secant of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMSECH(complexNumber) + * + * @param string $complexNumber the complex number for which you want the hyperbolic secant + * + * @return float|string + */ + public static function IMSECH($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return Functions::NAN(); + } + + return (string) $complex->sech(); + } + + /** + * IMTAN. + * + * Returns the tangent of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMTAN(complexNumber) + * + * @param string $complexNumber the complex number for which you want the tangent + * + * @return float|string + */ + public static function IMTAN($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return Functions::NAN(); + } + + return (string) $complex->tan(); + } + + /** + * IMSQRT. + * + * Returns the square root of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMSQRT(complexNumber) + * + * @param string $complexNumber the complex number for which you want the square root + * + * @return string + */ + public static function IMSQRT($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return Functions::NAN(); + } + + $theta = self::IMARGUMENT($complexNumber); + if ($theta === Functions::DIV0()) { + return '0'; + } + + return (string) $complex->sqrt(); + } + + /** + * IMLN. + * + * Returns the natural logarithm of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMLN(complexNumber) + * + * @param string $complexNumber the complex number for which you want the natural logarithm + * + * @return string + */ + public static function IMLN($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return Functions::NAN(); + } + + if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { + return Functions::NAN(); + } + + return (string) $complex->ln(); + } + + /** + * IMLOG10. + * + * Returns the common logarithm (base 10) of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMLOG10(complexNumber) + * + * @param string $complexNumber the complex number for which you want the common logarithm + * + * @return string + */ + public static function IMLOG10($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return Functions::NAN(); + } + + if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { + return Functions::NAN(); + } + + return (string) $complex->log10(); + } + + /** + * IMLOG2. + * + * Returns the base-2 logarithm of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMLOG2(complexNumber) + * + * @param string $complexNumber the complex number for which you want the base-2 logarithm + * + * @return string + */ + public static function IMLOG2($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return Functions::NAN(); + } + + if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { + return Functions::NAN(); + } + + return (string) $complex->log2(); + } + + /** + * IMEXP. + * + * Returns the exponential of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMEXP(complexNumber) + * + * @param string $complexNumber the complex number for which you want the exponential + * + * @return string + */ + public static function IMEXP($complexNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return Functions::NAN(); + } + + return (string) $complex->exp(); + } + + /** + * IMPOWER. + * + * Returns a complex number in x + yi or x + yj text format raised to a power. + * + * Excel Function: + * IMPOWER(complexNumber,realNumber) + * + * @param string $complexNumber the complex number you want to raise to a power + * @param float $realNumber the power to which you want to raise the complex number + * + * @return string + */ + public static function IMPOWER($complexNumber, $realNumber) + { + $complexNumber = Functions::flattenSingleValue($complexNumber); + $realNumber = Functions::flattenSingleValue($realNumber); + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return Functions::NAN(); + } + + if (!is_numeric($realNumber)) { + return Functions::VALUE(); + } + + return (string) $complex->pow($realNumber); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ComplexOperations.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ComplexOperations.php new file mode 100644 index 0000000..681aad8 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ComplexOperations.php @@ -0,0 +1,120 @@ +divideby(new ComplexObject($complexDivisor)); + } catch (ComplexException $e) { + return Functions::NAN(); + } + } + + /** + * IMSUB. + * + * Returns the difference of two complex numbers in x + yi or x + yj text format. + * + * Excel Function: + * IMSUB(complexNumber1,complexNumber2) + * + * @param string $complexNumber1 the complex number from which to subtract complexNumber2 + * @param string $complexNumber2 the complex number to subtract from complexNumber1 + * + * @return string + */ + public static function IMSUB($complexNumber1, $complexNumber2) + { + $complexNumber1 = Functions::flattenSingleValue($complexNumber1); + $complexNumber2 = Functions::flattenSingleValue($complexNumber2); + + try { + return (string) (new ComplexObject($complexNumber1))->subtract(new ComplexObject($complexNumber2)); + } catch (ComplexException $e) { + return Functions::NAN(); + } + } + + /** + * IMSUM. + * + * Returns the sum of two or more complex numbers in x + yi or x + yj text format. + * + * Excel Function: + * IMSUM(complexNumber[,complexNumber[,...]]) + * + * @param string ...$complexNumbers Series of complex numbers to add + * + * @return string + */ + public static function IMSUM(...$complexNumbers) + { + // Return value + $returnValue = new ComplexObject(0.0); + $aArgs = Functions::flattenArray($complexNumbers); + + try { + // Loop through the arguments + foreach ($aArgs as $complex) { + $returnValue = $returnValue->add(new ComplexObject($complex)); + } + } catch (ComplexException $e) { + return Functions::NAN(); + } + + return (string) $returnValue; + } + + /** + * IMPRODUCT. + * + * Returns the product of two or more complex numbers in x + yi or x + yj text format. + * + * Excel Function: + * IMPRODUCT(complexNumber[,complexNumber[,...]]) + * + * @param string ...$complexNumbers Series of complex numbers to multiply + * + * @return string + */ + public static function IMPRODUCT(...$complexNumbers) + { + // Return value + $returnValue = new ComplexObject(1.0); + $aArgs = Functions::flattenArray($complexNumbers); + + try { + // Loop through the arguments + foreach ($aArgs as $complex) { + $returnValue = $returnValue->multiply(new ComplexObject($complex)); + } + } catch (ComplexException $e) { + return Functions::NAN(); + } + + return (string) $returnValue; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Constants.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Constants.php new file mode 100644 index 0000000..a926db6 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Constants.php @@ -0,0 +1,11 @@ + 10) { + throw new Exception(Functions::NAN()); + } + + return (int) $places; + } + + throw new Exception(Functions::VALUE()); + } + + /** + * Formats a number base string value with leading zeroes. + * + * @param string $value The "number" to pad + * @param ?int $places The length that we want to pad this value + * + * @return string The padded "number" + */ + protected static function nbrConversionFormat(string $value, ?int $places): string + { + if ($places !== null) { + if (strlen($value) <= $places) { + return substr(str_pad($value, $places, '0', STR_PAD_LEFT), -10); + } + + return Functions::NAN(); + } + + return substr($value, -10); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertBinary.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertBinary.php new file mode 100644 index 0000000..a662b78 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertBinary.php @@ -0,0 +1,134 @@ +getMessage(); + } + + if (strlen($value) == 10) { + // Two's Complement + $value = substr($value, -9); + + return '-' . (512 - bindec($value)); + } + + return (string) bindec($value); + } + + /** + * toHex. + * + * Return a binary value as hex. + * + * Excel Function: + * BIN2HEX(x[,places]) + * + * @param string $value The binary number (as a string) that you want to convert. The number + * cannot contain more than 10 characters (10 bits). The most significant + * bit of number is the sign bit. The remaining 9 bits are magnitude bits. + * Negative numbers are represented using two's-complement notation. + * If number is not a valid binary number, or if number contains more than + * 10 characters (10 bits), BIN2HEX returns the #NUM! error value. + * @param int $places The number of characters to use. If places is omitted, BIN2HEX uses the + * minimum number of characters necessary. Places is useful for padding the + * return value with leading 0s (zeros). + * If places is not an integer, it is truncated. + * If places is nonnumeric, BIN2HEX returns the #VALUE! error value. + * If places is negative, BIN2HEX returns the #NUM! error value. + */ + public static function toHex($value, $places = null): string + { + try { + $value = self::validateValue(Functions::flattenSingleValue($value)); + $value = self::validateBinary($value); + $places = self::validatePlaces(Functions::flattenSingleValue($places)); + } catch (Exception $e) { + return $e->getMessage(); + } + + if (strlen($value) == 10) { + $high2 = substr($value, 0, 2); + $low8 = substr($value, 2); + $xarr = ['00' => '00000000', '01' => '00000001', '10' => 'FFFFFFFE', '11' => 'FFFFFFFF']; + + return $xarr[$high2] . strtoupper(substr('0' . dechex((int) bindec($low8)), -2)); + } + $hexVal = (string) strtoupper(dechex((int) bindec($value))); + + return self::nbrConversionFormat($hexVal, $places); + } + + /** + * toOctal. + * + * Return a binary value as octal. + * + * Excel Function: + * BIN2OCT(x[,places]) + * + * @param string $value The binary number (as a string) that you want to convert. The number + * cannot contain more than 10 characters (10 bits). The most significant + * bit of number is the sign bit. The remaining 9 bits are magnitude bits. + * Negative numbers are represented using two's-complement notation. + * If number is not a valid binary number, or if number contains more than + * 10 characters (10 bits), BIN2OCT returns the #NUM! error value. + * @param int $places The number of characters to use. If places is omitted, BIN2OCT uses the + * minimum number of characters necessary. Places is useful for padding the + * return value with leading 0s (zeros). + * If places is not an integer, it is truncated. + * If places is nonnumeric, BIN2OCT returns the #VALUE! error value. + * If places is negative, BIN2OCT returns the #NUM! error value. + */ + public static function toOctal($value, $places = null): string + { + try { + $value = self::validateValue(Functions::flattenSingleValue($value)); + $value = self::validateBinary($value); + $places = self::validatePlaces(Functions::flattenSingleValue($places)); + } catch (Exception $e) { + return $e->getMessage(); + } + + if (strlen($value) == 10 && substr($value, 0, 1) === '1') { // Two's Complement + return str_repeat('7', 6) . strtoupper(decoct((int) bindec("11$value"))); + } + $octVal = (string) decoct((int) bindec($value)); + + return self::nbrConversionFormat($octVal, $places); + } + + protected static function validateBinary(string $value): string + { + if ((strlen($value) > preg_match_all('/[01]/', $value)) || (strlen($value) > 10)) { + throw new Exception(Functions::NAN()); + } + + return $value; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertDecimal.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertDecimal.php new file mode 100644 index 0000000..a34332f --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertDecimal.php @@ -0,0 +1,183 @@ + 511, DEC2BIN returns the #NUM! error + * value. + * If number is nonnumeric, DEC2BIN returns the #VALUE! error value. + * If DEC2BIN requires more than places characters, it returns the #NUM! + * error value. + * @param int $places The number of characters to use. If places is omitted, DEC2BIN uses + * the minimum number of characters necessary. Places is useful for + * padding the return value with leading 0s (zeros). + * If places is not an integer, it is truncated. + * If places is nonnumeric, DEC2BIN returns the #VALUE! error value. + * If places is zero or negative, DEC2BIN returns the #NUM! error value. + */ + public static function toBinary($value, $places = null): string + { + try { + $value = self::validateValue(Functions::flattenSingleValue($value)); + $value = self::validateDecimal($value); + $places = self::validatePlaces(Functions::flattenSingleValue($places)); + } catch (Exception $e) { + return $e->getMessage(); + } + + $value = (int) floor((float) $value); + if ($value > self::LARGEST_BINARY_IN_DECIMAL || $value < self::SMALLEST_BINARY_IN_DECIMAL) { + return Functions::NAN(); + } + + $r = decbin($value); + // Two's Complement + $r = substr($r, -10); + + return self::nbrConversionFormat($r, $places); + } + + /** + * toHex. + * + * Return a decimal value as hex. + * + * Excel Function: + * DEC2HEX(x[,places]) + * + * @param string $value The decimal integer you want to convert. If number is negative, + * places is ignored and DEC2HEX returns a 10-character (40-bit) + * hexadecimal number in which the most significant bit is the sign + * bit. The remaining 39 bits are magnitude bits. Negative numbers + * are represented using two's-complement notation. + * If number < -549,755,813,888 or if number > 549,755,813,887, + * DEC2HEX returns the #NUM! error value. + * If number is nonnumeric, DEC2HEX returns the #VALUE! error value. + * If DEC2HEX requires more than places characters, it returns the + * #NUM! error value. + * @param int $places The number of characters to use. If places is omitted, DEC2HEX uses + * the minimum number of characters necessary. Places is useful for + * padding the return value with leading 0s (zeros). + * If places is not an integer, it is truncated. + * If places is nonnumeric, DEC2HEX returns the #VALUE! error value. + * If places is zero or negative, DEC2HEX returns the #NUM! error value. + */ + public static function toHex($value, $places = null): string + { + try { + $value = self::validateValue(Functions::flattenSingleValue($value)); + $value = self::validateDecimal($value); + $places = self::validatePlaces(Functions::flattenSingleValue($places)); + } catch (Exception $e) { + return $e->getMessage(); + } + + $value = floor((float) $value); + if ($value > self::LARGEST_HEX_IN_DECIMAL || $value < self::SMALLEST_HEX_IN_DECIMAL) { + return Functions::NAN(); + } + $r = strtoupper(dechex((int) $value)); + $r = self::hex32bit($value, $r); + + return self::nbrConversionFormat($r, $places); + } + + public static function hex32bit(float $value, string $hexstr, bool $force = false): string + { + if (PHP_INT_SIZE === 4 || $force) { + if ($value >= 2 ** 32) { + $quotient = (int) ($value / (2 ** 32)); + + return strtoupper(substr('0' . dechex($quotient), -2) . $hexstr); + } + if ($value < -(2 ** 32)) { + $quotient = 256 - (int) ceil((-$value) / (2 ** 32)); + + return strtoupper(substr('0' . dechex($quotient), -2) . substr("00000000$hexstr", -8)); + } + if ($value < 0) { + return "FF$hexstr"; + } + } + + return $hexstr; + } + + /** + * toOctal. + * + * Return an decimal value as octal. + * + * Excel Function: + * DEC2OCT(x[,places]) + * + * @param string $value The decimal integer you want to convert. If number is negative, + * places is ignored and DEC2OCT returns a 10-character (30-bit) + * octal number in which the most significant bit is the sign bit. + * The remaining 29 bits are magnitude bits. Negative numbers are + * represented using two's-complement notation. + * If number < -536,870,912 or if number > 536,870,911, DEC2OCT + * returns the #NUM! error value. + * If number is nonnumeric, DEC2OCT returns the #VALUE! error value. + * If DEC2OCT requires more than places characters, it returns the + * #NUM! error value. + * @param int $places The number of characters to use. If places is omitted, DEC2OCT uses + * the minimum number of characters necessary. Places is useful for + * padding the return value with leading 0s (zeros). + * If places is not an integer, it is truncated. + * If places is nonnumeric, DEC2OCT returns the #VALUE! error value. + * If places is zero or negative, DEC2OCT returns the #NUM! error value. + */ + public static function toOctal($value, $places = null): string + { + try { + $value = self::validateValue(Functions::flattenSingleValue($value)); + $value = self::validateDecimal($value); + $places = self::validatePlaces(Functions::flattenSingleValue($places)); + } catch (Exception $e) { + return $e->getMessage(); + } + + $value = (int) floor((float) $value); + if ($value > self::LARGEST_OCTAL_IN_DECIMAL || $value < self::SMALLEST_OCTAL_IN_DECIMAL) { + return Functions::NAN(); + } + $r = decoct($value); + $r = substr($r, -10); + + return self::nbrConversionFormat($r, $places); + } + + protected static function validateDecimal(string $value): string + { + if (strlen($value) > preg_match_all('/[-0123456789.]/', $value)) { + throw new Exception(Functions::VALUE()); + } + + return $value; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertHex.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertHex.php new file mode 100644 index 0000000..de1b070 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertHex.php @@ -0,0 +1,146 @@ +getMessage(); + } + + $dec = self::toDecimal($value); + + return ConvertDecimal::toBinary($dec, $places); + } + + /** + * toDecimal. + * + * Return a hex value as decimal. + * + * Excel Function: + * HEX2DEC(x) + * + * @param string $value The hexadecimal number you want to convert. This number cannot + * contain more than 10 characters (40 bits). The most significant + * bit of number is the sign bit. The remaining 39 bits are magnitude + * bits. Negative numbers are represented using two's-complement + * notation. + * If number is not a valid hexadecimal number, HEX2DEC returns the + * #NUM! error value. + */ + public static function toDecimal($value): string + { + try { + $value = self::validateValue(Functions::flattenSingleValue($value)); + $value = self::validateHex($value); + } catch (Exception $e) { + return $e->getMessage(); + } + + if (strlen($value) > 10) { + return Functions::NAN(); + } + + $binX = ''; + foreach (str_split($value) as $char) { + $binX .= str_pad(base_convert($char, 16, 2), 4, '0', STR_PAD_LEFT); + } + if (strlen($binX) == 40 && $binX[0] == '1') { + for ($i = 0; $i < 40; ++$i) { + $binX[$i] = ($binX[$i] == '1' ? '0' : '1'); + } + + return (string) ((bindec($binX) + 1) * -1); + } + + return (string) bindec($binX); + } + + /** + * toOctal. + * + * Return a hex value as octal. + * + * Excel Function: + * HEX2OCT(x[,places]) + * + * @param string $value The hexadecimal number you want to convert. Number cannot + * contain more than 10 characters. The most significant bit of + * number is the sign bit. The remaining 39 bits are magnitude + * bits. Negative numbers are represented using two's-complement + * notation. + * If number is negative, HEX2OCT ignores places and returns a + * 10-character octal number. + * If number is negative, it cannot be less than FFE0000000, and + * if number is positive, it cannot be greater than 1FFFFFFF. + * If number is not a valid hexadecimal number, HEX2OCT returns + * the #NUM! error value. + * If HEX2OCT requires more than places characters, it returns + * the #NUM! error value. + * @param int $places The number of characters to use. If places is omitted, HEX2OCT + * uses the minimum number of characters necessary. Places is + * useful for padding the return value with leading 0s (zeros). + * If places is not an integer, it is truncated. + * If places is nonnumeric, HEX2OCT returns the #VALUE! error + * value. + * If places is negative, HEX2OCT returns the #NUM! error value. + */ + public static function toOctal($value, $places = null): string + { + try { + $value = self::validateValue(Functions::flattenSingleValue($value)); + $value = self::validateHex($value); + $places = self::validatePlaces(Functions::flattenSingleValue($places)); + } catch (Exception $e) { + return $e->getMessage(); + } + + $decimal = self::toDecimal($value); + + return ConvertDecimal::toOctal($decimal, $places); + } + + protected static function validateHex(string $value): string + { + if (strlen($value) > preg_match_all('/[0123456789ABCDEF]/', $value)) { + throw new Exception(Functions::NAN()); + } + + return $value; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertOctal.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertOctal.php new file mode 100644 index 0000000..1181e2e --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertOctal.php @@ -0,0 +1,145 @@ +getMessage(); + } + + return ConvertDecimal::toBinary(self::toDecimal($value), $places); + } + + /** + * toDecimal. + * + * Return an octal value as decimal. + * + * Excel Function: + * OCT2DEC(x) + * + * @param string $value The octal number you want to convert. Number may not contain + * more than 10 octal characters (30 bits). The most significant + * bit of number is the sign bit. The remaining 29 bits are + * magnitude bits. Negative numbers are represented using + * two's-complement notation. + * If number is not a valid octal number, OCT2DEC returns the + * #NUM! error value. + */ + public static function toDecimal($value): string + { + try { + $value = self::validateValue(Functions::flattenSingleValue($value)); + $value = self::validateOctal($value); + } catch (Exception $e) { + return $e->getMessage(); + } + + $binX = ''; + foreach (str_split($value) as $char) { + $binX .= str_pad(decbin((int) $char), 3, '0', STR_PAD_LEFT); + } + if (strlen($binX) == 30 && $binX[0] == '1') { + for ($i = 0; $i < 30; ++$i) { + $binX[$i] = ($binX[$i] == '1' ? '0' : '1'); + } + + return (string) ((bindec($binX) + 1) * -1); + } + + return (string) bindec($binX); + } + + /** + * toHex. + * + * Return an octal value as hex. + * + * Excel Function: + * OCT2HEX(x[,places]) + * + * @param string $value The octal number you want to convert. Number may not contain + * more than 10 octal characters (30 bits). The most significant + * bit of number is the sign bit. The remaining 29 bits are + * magnitude bits. Negative numbers are represented using + * two's-complement notation. + * If number is negative, OCT2HEX ignores places and returns a + * 10-character hexadecimal number. + * If number is not a valid octal number, OCT2HEX returns the + * #NUM! error value. + * If OCT2HEX requires more than places characters, it returns + * the #NUM! error value. + * @param int $places The number of characters to use. If places is omitted, OCT2HEX + * uses the minimum number of characters necessary. Places is useful + * for padding the return value with leading 0s (zeros). + * If places is not an integer, it is truncated. + * If places is nonnumeric, OCT2HEX returns the #VALUE! error value. + * If places is negative, OCT2HEX returns the #NUM! error value. + */ + public static function toHex($value, $places = null): string + { + try { + $value = self::validateValue(Functions::flattenSingleValue($value)); + $value = self::validateOctal($value); + $places = self::validatePlaces(Functions::flattenSingleValue($places)); + } catch (Exception $e) { + return $e->getMessage(); + } + + $hexVal = strtoupper(dechex((int) self::toDecimal($value))); + $hexVal = (PHP_INT_SIZE === 4 && strlen($value) === 10 && $value[0] >= '4') ? "FF$hexVal" : $hexVal; + + return self::nbrConversionFormat($hexVal, $places); + } + + protected static function validateOctal(string $value): string + { + $numDigits = (int) preg_match_all('/[01234567]/', $value); + if (strlen($value) > $numDigits || $numDigits > 10) { + throw new Exception(Functions::NAN()); + } + + return $value; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertUOM.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertUOM.php new file mode 100644 index 0000000..d169ae5 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertUOM.php @@ -0,0 +1,684 @@ + ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Gram', 'AllowPrefix' => true], + 'sg' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Slug', 'AllowPrefix' => false], + 'lbm' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Pound mass (avoirdupois)', 'AllowPrefix' => false], + 'u' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'U (atomic mass unit)', 'AllowPrefix' => true], + 'ozm' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Ounce mass (avoirdupois)', 'AllowPrefix' => false], + 'grain' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Grain', 'AllowPrefix' => false], + 'cwt' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'U.S. (short) hundredweight', 'AllowPrefix' => false], + 'shweight' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'U.S. (short) hundredweight', 'AllowPrefix' => false], + 'uk_cwt' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Imperial hundredweight', 'AllowPrefix' => false], + 'lcwt' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Imperial hundredweight', 'AllowPrefix' => false], + 'hweight' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Imperial hundredweight', 'AllowPrefix' => false], + 'stone' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Stone', 'AllowPrefix' => false], + 'ton' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Ton', 'AllowPrefix' => false], + 'uk_ton' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Imperial ton', 'AllowPrefix' => false], + 'LTON' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Imperial ton', 'AllowPrefix' => false], + 'brton' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Imperial ton', 'AllowPrefix' => false], + // Distance + 'm' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Meter', 'AllowPrefix' => true], + 'mi' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Statute mile', 'AllowPrefix' => false], + 'Nmi' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Nautical mile', 'AllowPrefix' => false], + 'in' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Inch', 'AllowPrefix' => false], + 'ft' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Foot', 'AllowPrefix' => false], + 'yd' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Yard', 'AllowPrefix' => false], + 'ang' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Angstrom', 'AllowPrefix' => true], + 'ell' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Ell', 'AllowPrefix' => false], + 'ly' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Light Year', 'AllowPrefix' => false], + 'parsec' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Parsec', 'AllowPrefix' => false], + 'pc' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Parsec', 'AllowPrefix' => false], + 'Pica' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Pica (1/72 in)', 'AllowPrefix' => false], + 'Picapt' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Pica (1/72 in)', 'AllowPrefix' => false], + 'pica' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Pica (1/6 in)', 'AllowPrefix' => false], + 'survey_mi' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'U.S survey mile (statute mile)', 'AllowPrefix' => false], + // Time + 'yr' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Year', 'AllowPrefix' => false], + 'day' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Day', 'AllowPrefix' => false], + 'd' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Day', 'AllowPrefix' => false], + 'hr' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Hour', 'AllowPrefix' => false], + 'mn' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Minute', 'AllowPrefix' => false], + 'min' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Minute', 'AllowPrefix' => false], + 'sec' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Second', 'AllowPrefix' => true], + 's' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Second', 'AllowPrefix' => true], + // Pressure + 'Pa' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'Pascal', 'AllowPrefix' => true], + 'p' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'Pascal', 'AllowPrefix' => true], + 'atm' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'Atmosphere', 'AllowPrefix' => true], + 'at' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'Atmosphere', 'AllowPrefix' => true], + 'mmHg' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'mm of Mercury', 'AllowPrefix' => true], + 'psi' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'PSI', 'AllowPrefix' => true], + 'Torr' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'Torr', 'AllowPrefix' => true], + // Force + 'N' => ['Group' => self::CATEGORY_FORCE, 'Unit Name' => 'Newton', 'AllowPrefix' => true], + 'dyn' => ['Group' => self::CATEGORY_FORCE, 'Unit Name' => 'Dyne', 'AllowPrefix' => true], + 'dy' => ['Group' => self::CATEGORY_FORCE, 'Unit Name' => 'Dyne', 'AllowPrefix' => true], + 'lbf' => ['Group' => self::CATEGORY_FORCE, 'Unit Name' => 'Pound force', 'AllowPrefix' => false], + 'pond' => ['Group' => self::CATEGORY_FORCE, 'Unit Name' => 'Pond', 'AllowPrefix' => true], + // Energy + 'J' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Joule', 'AllowPrefix' => true], + 'e' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Erg', 'AllowPrefix' => true], + 'c' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Thermodynamic calorie', 'AllowPrefix' => true], + 'cal' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'IT calorie', 'AllowPrefix' => true], + 'eV' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Electron volt', 'AllowPrefix' => true], + 'ev' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Electron volt', 'AllowPrefix' => true], + 'HPh' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Horsepower-hour', 'AllowPrefix' => false], + 'hh' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Horsepower-hour', 'AllowPrefix' => false], + 'Wh' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Watt-hour', 'AllowPrefix' => true], + 'wh' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Watt-hour', 'AllowPrefix' => true], + 'flb' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Foot-pound', 'AllowPrefix' => false], + 'BTU' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'BTU', 'AllowPrefix' => false], + 'btu' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'BTU', 'AllowPrefix' => false], + // Power + 'HP' => ['Group' => self::CATEGORY_POWER, 'Unit Name' => 'Horsepower', 'AllowPrefix' => false], + 'h' => ['Group' => self::CATEGORY_POWER, 'Unit Name' => 'Horsepower', 'AllowPrefix' => false], + 'W' => ['Group' => self::CATEGORY_POWER, 'Unit Name' => 'Watt', 'AllowPrefix' => true], + 'w' => ['Group' => self::CATEGORY_POWER, 'Unit Name' => 'Watt', 'AllowPrefix' => true], + 'PS' => ['Group' => self::CATEGORY_POWER, 'Unit Name' => 'Pferdestärke', 'AllowPrefix' => false], + 'T' => ['Group' => self::CATEGORY_MAGNETISM, 'Unit Name' => 'Tesla', 'AllowPrefix' => true], + 'ga' => ['Group' => self::CATEGORY_MAGNETISM, 'Unit Name' => 'Gauss', 'AllowPrefix' => true], + // Temperature + 'C' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Degrees Celsius', 'AllowPrefix' => false], + 'cel' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Degrees Celsius', 'AllowPrefix' => false], + 'F' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Degrees Fahrenheit', 'AllowPrefix' => false], + 'fah' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Degrees Fahrenheit', 'AllowPrefix' => false], + 'K' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Kelvin', 'AllowPrefix' => false], + 'kel' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Kelvin', 'AllowPrefix' => false], + 'Rank' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Degrees Rankine', 'AllowPrefix' => false], + 'Reau' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Degrees Réaumur', 'AllowPrefix' => false], + // Volume + 'l' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Litre', 'AllowPrefix' => true], + 'L' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Litre', 'AllowPrefix' => true], + 'lt' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Litre', 'AllowPrefix' => true], + 'tsp' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Teaspoon', 'AllowPrefix' => false], + 'tspm' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Modern Teaspoon', 'AllowPrefix' => false], + 'tbs' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Tablespoon', 'AllowPrefix' => false], + 'oz' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Fluid Ounce', 'AllowPrefix' => false], + 'cup' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cup', 'AllowPrefix' => false], + 'pt' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'U.S. Pint', 'AllowPrefix' => false], + 'us_pt' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'U.S. Pint', 'AllowPrefix' => false], + 'uk_pt' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'U.K. Pint', 'AllowPrefix' => false], + 'qt' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Quart', 'AllowPrefix' => false], + 'uk_qt' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Imperial Quart (UK)', 'AllowPrefix' => false], + 'gal' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Gallon', 'AllowPrefix' => false], + 'uk_gal' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Imperial Gallon (UK)', 'AllowPrefix' => false], + 'ang3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Angstrom', 'AllowPrefix' => true], + 'ang^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Angstrom', 'AllowPrefix' => true], + 'barrel' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'US Oil Barrel', 'AllowPrefix' => false], + 'bushel' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'US Bushel', 'AllowPrefix' => false], + 'in3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Inch', 'AllowPrefix' => false], + 'in^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Inch', 'AllowPrefix' => false], + 'ft3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Foot', 'AllowPrefix' => false], + 'ft^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Foot', 'AllowPrefix' => false], + 'ly3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Light Year', 'AllowPrefix' => false], + 'ly^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Light Year', 'AllowPrefix' => false], + 'm3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Meter', 'AllowPrefix' => true], + 'm^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Meter', 'AllowPrefix' => true], + 'mi3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Mile', 'AllowPrefix' => false], + 'mi^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Mile', 'AllowPrefix' => false], + 'yd3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Yard', 'AllowPrefix' => false], + 'yd^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Yard', 'AllowPrefix' => false], + 'Nmi3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Nautical Mile', 'AllowPrefix' => false], + 'Nmi^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Nautical Mile', 'AllowPrefix' => false], + 'Pica3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Pica', 'AllowPrefix' => false], + 'Pica^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Pica', 'AllowPrefix' => false], + 'Picapt3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Pica', 'AllowPrefix' => false], + 'Picapt^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Pica', 'AllowPrefix' => false], + 'GRT' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Gross Registered Ton', 'AllowPrefix' => false], + 'regton' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Gross Registered Ton', 'AllowPrefix' => false], + 'MTON' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Measurement Ton (Freight Ton)', 'AllowPrefix' => false], + // Area + 'ha' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Hectare', 'AllowPrefix' => true], + 'uk_acre' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'International Acre', 'AllowPrefix' => false], + 'us_acre' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'US Survey/Statute Acre', 'AllowPrefix' => false], + 'ang2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Angstrom', 'AllowPrefix' => true], + 'ang^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Angstrom', 'AllowPrefix' => true], + 'ar' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Are', 'AllowPrefix' => true], + 'ft2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Feet', 'AllowPrefix' => false], + 'ft^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Feet', 'AllowPrefix' => false], + 'in2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Inches', 'AllowPrefix' => false], + 'in^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Inches', 'AllowPrefix' => false], + 'ly2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Light Years', 'AllowPrefix' => false], + 'ly^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Light Years', 'AllowPrefix' => false], + 'm2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Meters', 'AllowPrefix' => true], + 'm^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Meters', 'AllowPrefix' => true], + 'Morgen' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Morgen', 'AllowPrefix' => false], + 'mi2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Miles', 'AllowPrefix' => false], + 'mi^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Miles', 'AllowPrefix' => false], + 'Nmi2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Nautical Miles', 'AllowPrefix' => false], + 'Nmi^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Nautical Miles', 'AllowPrefix' => false], + 'Pica2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Pica', 'AllowPrefix' => false], + 'Pica^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Pica', 'AllowPrefix' => false], + 'Picapt2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Pica', 'AllowPrefix' => false], + 'Picapt^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Pica', 'AllowPrefix' => false], + 'yd2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Yards', 'AllowPrefix' => false], + 'yd^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Yards', 'AllowPrefix' => false], + // Information + 'byte' => ['Group' => self::CATEGORY_INFORMATION, 'Unit Name' => 'Byte', 'AllowPrefix' => true], + 'bit' => ['Group' => self::CATEGORY_INFORMATION, 'Unit Name' => 'Bit', 'AllowPrefix' => true], + // Speed + 'm/s' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Meters per second', 'AllowPrefix' => true], + 'm/sec' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Meters per second', 'AllowPrefix' => true], + 'm/h' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Meters per hour', 'AllowPrefix' => true], + 'm/hr' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Meters per hour', 'AllowPrefix' => true], + 'mph' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Miles per hour', 'AllowPrefix' => false], + 'admkn' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Admiralty Knot', 'AllowPrefix' => false], + 'kn' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Knot', 'AllowPrefix' => false], + ]; + + /** + * Details of the Multiplier prefixes that can be used with Units of Measure in CONVERTUOM(). + * + * @var mixed[] + */ + private static $conversionMultipliers = [ + 'Y' => ['multiplier' => 1E24, 'name' => 'yotta'], + 'Z' => ['multiplier' => 1E21, 'name' => 'zetta'], + 'E' => ['multiplier' => 1E18, 'name' => 'exa'], + 'P' => ['multiplier' => 1E15, 'name' => 'peta'], + 'T' => ['multiplier' => 1E12, 'name' => 'tera'], + 'G' => ['multiplier' => 1E9, 'name' => 'giga'], + 'M' => ['multiplier' => 1E6, 'name' => 'mega'], + 'k' => ['multiplier' => 1E3, 'name' => 'kilo'], + 'h' => ['multiplier' => 1E2, 'name' => 'hecto'], + 'e' => ['multiplier' => 1E1, 'name' => 'dekao'], + 'da' => ['multiplier' => 1E1, 'name' => 'dekao'], + 'd' => ['multiplier' => 1E-1, 'name' => 'deci'], + 'c' => ['multiplier' => 1E-2, 'name' => 'centi'], + 'm' => ['multiplier' => 1E-3, 'name' => 'milli'], + 'u' => ['multiplier' => 1E-6, 'name' => 'micro'], + 'n' => ['multiplier' => 1E-9, 'name' => 'nano'], + 'p' => ['multiplier' => 1E-12, 'name' => 'pico'], + 'f' => ['multiplier' => 1E-15, 'name' => 'femto'], + 'a' => ['multiplier' => 1E-18, 'name' => 'atto'], + 'z' => ['multiplier' => 1E-21, 'name' => 'zepto'], + 'y' => ['multiplier' => 1E-24, 'name' => 'yocto'], + ]; + + /** + * Details of the Multiplier prefixes that can be used with Units of Measure in CONVERTUOM(). + * + * @var mixed[] + */ + private static $binaryConversionMultipliers = [ + 'Yi' => ['multiplier' => 2 ** 80, 'name' => 'yobi'], + 'Zi' => ['multiplier' => 2 ** 70, 'name' => 'zebi'], + 'Ei' => ['multiplier' => 2 ** 60, 'name' => 'exbi'], + 'Pi' => ['multiplier' => 2 ** 50, 'name' => 'pebi'], + 'Ti' => ['multiplier' => 2 ** 40, 'name' => 'tebi'], + 'Gi' => ['multiplier' => 2 ** 30, 'name' => 'gibi'], + 'Mi' => ['multiplier' => 2 ** 20, 'name' => 'mebi'], + 'ki' => ['multiplier' => 2 ** 10, 'name' => 'kibi'], + ]; + + /** + * Details of the Units of measure conversion factors, organised by group. + * + * @var mixed[] + */ + private static $unitConversions = [ + // Conversion uses gram (g) as an intermediate unit + self::CATEGORY_WEIGHT_AND_MASS => [ + 'g' => 1.0, + 'sg' => 6.85217658567918E-05, + 'lbm' => 2.20462262184878E-03, + 'u' => 6.02214179421676E+23, + 'ozm' => 3.52739619495804E-02, + 'grain' => 1.54323583529414E+01, + 'cwt' => 2.20462262184878E-05, + 'shweight' => 2.20462262184878E-05, + 'uk_cwt' => 1.96841305522212E-05, + 'lcwt' => 1.96841305522212E-05, + 'hweight' => 1.96841305522212E-05, + 'stone' => 1.57473044417770E-04, + 'ton' => 1.10231131092439E-06, + 'uk_ton' => 9.84206527611061E-07, + 'LTON' => 9.84206527611061E-07, + 'brton' => 9.84206527611061E-07, + ], + // Conversion uses meter (m) as an intermediate unit + self::CATEGORY_DISTANCE => [ + 'm' => 1.0, + 'mi' => 6.21371192237334E-04, + 'Nmi' => 5.39956803455724E-04, + 'in' => 3.93700787401575E+01, + 'ft' => 3.28083989501312E+00, + 'yd' => 1.09361329833771E+00, + 'ang' => 1.0E+10, + 'ell' => 8.74890638670166E-01, + 'ly' => 1.05700083402462E-16, + 'parsec' => 3.24077928966473E-17, + 'pc' => 3.24077928966473E-17, + 'Pica' => 2.83464566929134E+03, + 'Picapt' => 2.83464566929134E+03, + 'pica' => 2.36220472440945E+02, + 'survey_mi' => 6.21369949494950E-04, + ], + // Conversion uses second (s) as an intermediate unit + self::CATEGORY_TIME => [ + 'yr' => 3.16880878140289E-08, + 'day' => 1.15740740740741E-05, + 'd' => 1.15740740740741E-05, + 'hr' => 2.77777777777778E-04, + 'mn' => 1.66666666666667E-02, + 'min' => 1.66666666666667E-02, + 'sec' => 1.0, + 's' => 1.0, + ], + // Conversion uses Pascal (Pa) as an intermediate unit + self::CATEGORY_PRESSURE => [ + 'Pa' => 1.0, + 'p' => 1.0, + 'atm' => 9.86923266716013E-06, + 'at' => 9.86923266716013E-06, + 'mmHg' => 7.50063755419211E-03, + 'psi' => 1.45037737730209E-04, + 'Torr' => 7.50061682704170E-03, + ], + // Conversion uses Newton (N) as an intermediate unit + self::CATEGORY_FORCE => [ + 'N' => 1.0, + 'dyn' => 1.0E+5, + 'dy' => 1.0E+5, + 'lbf' => 2.24808923655339E-01, + 'pond' => 1.01971621297793E+02, + ], + // Conversion uses Joule (J) as an intermediate unit + self::CATEGORY_ENERGY => [ + 'J' => 1.0, + 'e' => 9.99999519343231E+06, + 'c' => 2.39006249473467E-01, + 'cal' => 2.38846190642017E-01, + 'eV' => 6.24145700000000E+18, + 'ev' => 6.24145700000000E+18, + 'HPh' => 3.72506430801000E-07, + 'hh' => 3.72506430801000E-07, + 'Wh' => 2.77777916238711E-04, + 'wh' => 2.77777916238711E-04, + 'flb' => 2.37304222192651E+01, + 'BTU' => 9.47815067349015E-04, + 'btu' => 9.47815067349015E-04, + ], + // Conversion uses Horsepower (HP) as an intermediate unit + self::CATEGORY_POWER => [ + 'HP' => 1.0, + 'h' => 1.0, + 'W' => 7.45699871582270E+02, + 'w' => 7.45699871582270E+02, + 'PS' => 1.01386966542400E+00, + ], + // Conversion uses Tesla (T) as an intermediate unit + self::CATEGORY_MAGNETISM => [ + 'T' => 1.0, + 'ga' => 10000.0, + ], + // Conversion uses litre (l) as an intermediate unit + self::CATEGORY_VOLUME => [ + 'l' => 1.0, + 'L' => 1.0, + 'lt' => 1.0, + 'tsp' => 2.02884136211058E+02, + 'tspm' => 2.0E+02, + 'tbs' => 6.76280454036860E+01, + 'oz' => 3.38140227018430E+01, + 'cup' => 4.22675283773038E+00, + 'pt' => 2.11337641886519E+00, + 'us_pt' => 2.11337641886519E+00, + 'uk_pt' => 1.75975398639270E+00, + 'qt' => 1.05668820943259E+00, + 'uk_qt' => 8.79876993196351E-01, + 'gal' => 2.64172052358148E-01, + 'uk_gal' => 2.19969248299088E-01, + 'ang3' => 1.0E+27, + 'ang^3' => 1.0E+27, + 'barrel' => 6.28981077043211E-03, + 'bushel' => 2.83775932584017E-02, + 'in3' => 6.10237440947323E+01, + 'in^3' => 6.10237440947323E+01, + 'ft3' => 3.53146667214886E-02, + 'ft^3' => 3.53146667214886E-02, + 'ly3' => 1.18093498844171E-51, + 'ly^3' => 1.18093498844171E-51, + 'm3' => 1.0E-03, + 'm^3' => 1.0E-03, + 'mi3' => 2.39912758578928E-13, + 'mi^3' => 2.39912758578928E-13, + 'yd3' => 1.30795061931439E-03, + 'yd^3' => 1.30795061931439E-03, + 'Nmi3' => 1.57426214685811E-13, + 'Nmi^3' => 1.57426214685811E-13, + 'Pica3' => 2.27769904358706E+07, + 'Pica^3' => 2.27769904358706E+07, + 'Picapt3' => 2.27769904358706E+07, + 'Picapt^3' => 2.27769904358706E+07, + 'GRT' => 3.53146667214886E-04, + 'regton' => 3.53146667214886E-04, + 'MTON' => 8.82866668037215E-04, + ], + // Conversion uses hectare (ha) as an intermediate unit + self::CATEGORY_AREA => [ + 'ha' => 1.0, + 'uk_acre' => 2.47105381467165E+00, + 'us_acre' => 2.47104393046628E+00, + 'ang2' => 1.0E+24, + 'ang^2' => 1.0E+24, + 'ar' => 1.0E+02, + 'ft2' => 1.07639104167097E+05, + 'ft^2' => 1.07639104167097E+05, + 'in2' => 1.55000310000620E+07, + 'in^2' => 1.55000310000620E+07, + 'ly2' => 1.11725076312873E-28, + 'ly^2' => 1.11725076312873E-28, + 'm2' => 1.0E+04, + 'm^2' => 1.0E+04, + 'Morgen' => 4.0E+00, + 'mi2' => 3.86102158542446E-03, + 'mi^2' => 3.86102158542446E-03, + 'Nmi2' => 2.91553349598123E-03, + 'Nmi^2' => 2.91553349598123E-03, + 'Pica2' => 8.03521607043214E+10, + 'Pica^2' => 8.03521607043214E+10, + 'Picapt2' => 8.03521607043214E+10, + 'Picapt^2' => 8.03521607043214E+10, + 'yd2' => 1.19599004630108E+04, + 'yd^2' => 1.19599004630108E+04, + ], + // Conversion uses bit (bit) as an intermediate unit + self::CATEGORY_INFORMATION => [ + 'bit' => 1.0, + 'byte' => 0.125, + ], + // Conversion uses Meters per Second (m/s) as an intermediate unit + self::CATEGORY_SPEED => [ + 'm/s' => 1.0, + 'm/sec' => 1.0, + 'm/h' => 3.60E+03, + 'm/hr' => 3.60E+03, + 'mph' => 2.23693629205440E+00, + 'admkn' => 1.94260256941567E+00, + 'kn' => 1.94384449244060E+00, + ], + ]; + + /** + * getConversionGroups + * Returns a list of the different conversion groups for UOM conversions. + * + * @return array + */ + public static function getConversionCategories() + { + $conversionGroups = []; + foreach (self::$conversionUnits as $conversionUnit) { + $conversionGroups[] = $conversionUnit['Group']; + } + + return array_merge(array_unique($conversionGroups)); + } + + /** + * getConversionGroupUnits + * Returns an array of units of measure, for a specified conversion group, or for all groups. + * + * @param string $category The group whose units of measure you want to retrieve + * + * @return array + */ + public static function getConversionCategoryUnits($category = null) + { + $conversionGroups = []; + foreach (self::$conversionUnits as $conversionUnit => $conversionGroup) { + if (($category === null) || ($conversionGroup['Group'] == $category)) { + $conversionGroups[$conversionGroup['Group']][] = $conversionUnit; + } + } + + return $conversionGroups; + } + + /** + * getConversionGroupUnitDetails. + * + * @param string $category The group whose units of measure you want to retrieve + * + * @return array + */ + public static function getConversionCategoryUnitDetails($category = null) + { + $conversionGroups = []; + foreach (self::$conversionUnits as $conversionUnit => $conversionGroup) { + if (($category === null) || ($conversionGroup['Group'] == $category)) { + $conversionGroups[$conversionGroup['Group']][] = [ + 'unit' => $conversionUnit, + 'description' => $conversionGroup['Unit Name'], + ]; + } + } + + return $conversionGroups; + } + + /** + * getConversionMultipliers + * Returns an array of the Multiplier prefixes that can be used with Units of Measure in CONVERTUOM(). + * + * @return mixed[] + */ + public static function getConversionMultipliers() + { + return self::$conversionMultipliers; + } + + /** + * getBinaryConversionMultipliers + * Returns an array of the additional Multiplier prefixes that can be used with Information Units of Measure in CONVERTUOM(). + * + * @return mixed[] + */ + public static function getBinaryConversionMultipliers() + { + return self::$binaryConversionMultipliers; + } + + /** + * CONVERT. + * + * Converts a number from one measurement system to another. + * For example, CONVERT can translate a table of distances in miles to a table of distances + * in kilometers. + * + * Excel Function: + * CONVERT(value,fromUOM,toUOM) + * + * @param float|int $value the value in fromUOM to convert + * @param string $fromUOM the units for value + * @param string $toUOM the units for the result + * + * @return float|string + */ + public static function CONVERT($value, $fromUOM, $toUOM) + { + $value = Functions::flattenSingleValue($value); + $fromUOM = Functions::flattenSingleValue($fromUOM); + $toUOM = Functions::flattenSingleValue($toUOM); + + if (!is_numeric($value)) { + return Functions::VALUE(); + } + + try { + [$fromUOM, $fromCategory, $fromMultiplier] = self::getUOMDetails($fromUOM); + [$toUOM, $toCategory, $toMultiplier] = self::getUOMDetails($toUOM); + } catch (Exception $e) { + return Functions::NA(); + } + + if ($fromCategory !== $toCategory) { + return Functions::NA(); + } + + $value *= $fromMultiplier; + + if (($fromUOM === $toUOM) && ($fromMultiplier === $toMultiplier)) { + // We've already factored $fromMultiplier into the value, so we need + // to reverse it again + return $value / $fromMultiplier; + } elseif ($fromUOM === $toUOM) { + return $value / $toMultiplier; + } elseif ($fromCategory === self::CATEGORY_TEMPERATURE) { + return self::convertTemperature($fromUOM, $toUOM, $value); + } + + $baseValue = $value * (1.0 / self::$unitConversions[$fromCategory][$fromUOM]); + + return ($baseValue * self::$unitConversions[$fromCategory][$toUOM]) / $toMultiplier; + } + + private static function getUOMDetails(string $uom) + { + if (isset(self::$conversionUnits[$uom])) { + $unitCategory = self::$conversionUnits[$uom]['Group']; + + return [$uom, $unitCategory, 1.0]; + } + + // Check 1-character standard metric multiplier prefixes + $multiplierType = substr($uom, 0, 1); + $uom = substr($uom, 1); + if (isset(self::$conversionUnits[$uom], self::$conversionMultipliers[$multiplierType])) { + if (self::$conversionUnits[$uom]['AllowPrefix'] === false) { + throw new Exception('Prefix not allowed for UoM'); + } + $unitCategory = self::$conversionUnits[$uom]['Group']; + + return [$uom, $unitCategory, self::$conversionMultipliers[$multiplierType]['multiplier']]; + } + + $multiplierType .= substr($uom, 0, 1); + $uom = substr($uom, 1); + + // Check 2-character standard metric multiplier prefixes + if (isset(self::$conversionUnits[$uom], self::$conversionMultipliers[$multiplierType])) { + if (self::$conversionUnits[$uom]['AllowPrefix'] === false) { + throw new Exception('Prefix not allowed for UoM'); + } + $unitCategory = self::$conversionUnits[$uom]['Group']; + + return [$uom, $unitCategory, self::$conversionMultipliers[$multiplierType]['multiplier']]; + } + + // Check 2-character binary multiplier prefixes + if (isset(self::$conversionUnits[$uom], self::$binaryConversionMultipliers[$multiplierType])) { + if (self::$conversionUnits[$uom]['AllowPrefix'] === false) { + throw new Exception('Prefix not allowed for UoM'); + } + $unitCategory = self::$conversionUnits[$uom]['Group']; + if ($unitCategory !== 'Information') { + throw new Exception('Binary Prefix is only allowed for Information UoM'); + } + + return [$uom, $unitCategory, self::$binaryConversionMultipliers[$multiplierType]['multiplier']]; + } + + throw new Exception('UoM Not Found'); + } + + /** + * @param float|int $value + * + * @return float|int + */ + protected static function convertTemperature(string $fromUOM, string $toUOM, $value) + { + $fromUOM = self::resolveTemperatureSynonyms($fromUOM); + $toUOM = self::resolveTemperatureSynonyms($toUOM); + + if ($fromUOM === $toUOM) { + return $value; + } + + // Convert to Kelvin + switch ($fromUOM) { + case 'F': + $value = ($value - 32) / 1.8 + 273.15; + + break; + case 'C': + $value += 273.15; + + break; + case 'Rank': + $value /= 1.8; + + break; + case 'Reau': + $value = $value * 1.25 + 273.15; + + break; + } + + // Convert from Kelvin + switch ($toUOM) { + case 'F': + $value = ($value - 273.15) * 1.8 + 32.00; + + break; + case 'C': + $value -= 273.15; + + break; + case 'Rank': + $value *= 1.8; + + break; + case 'Reau': + $value = ($value - 273.15) * 0.80000; + + break; + } + + return $value; + } + + private static function resolveTemperatureSynonyms(string $uom) + { + switch ($uom) { + case 'fah': + return 'F'; + case 'cel': + return 'C'; + case 'kel': + return 'K'; + } + + return $uom; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/EngineeringValidations.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/EngineeringValidations.php new file mode 100644 index 0000000..01630af --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/EngineeringValidations.php @@ -0,0 +1,33 @@ + 2.2) { + return 1 - ErfC::ERFC($value); + } + $sum = $term = $value; + $xsqr = ($value * $value); + $j = 1; + do { + $term *= $xsqr / $j; + $sum -= $term / (2 * $j + 1); + ++$j; + $term *= $xsqr / $j; + $sum += $term / (2 * $j + 1); + ++$j; + if ($sum == 0.0) { + break; + } + } while (abs($term / $sum) > Functions::PRECISION); + + return self::$twoSqrtPi * $sum; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ErfC.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ErfC.php new file mode 100644 index 0000000..c57a28f --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ErfC.php @@ -0,0 +1,68 @@ + Functions::PRECISION); + + return self::$oneSqrtPi * exp(-$value * $value) * $q2; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Exception.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Exception.php new file mode 100644 index 0000000..87c7d22 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Exception.php @@ -0,0 +1,26 @@ +line = $line; + $e->file = $file; + + throw $e; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/ExceptionHandler.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/ExceptionHandler.php new file mode 100644 index 0000000..41e51d4 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/ExceptionHandler.php @@ -0,0 +1,22 @@ +getMessage(); + } + + $yearFrac = DateTimeExcel\YearFrac::fraction($purchased, $firstPeriod, $basis); + if (is_string($yearFrac)) { + return $yearFrac; + } + + $amortiseCoeff = self::getAmortizationCoefficient($rate); + + $rate *= $amortiseCoeff; + $fNRate = round($yearFrac * $rate * $cost, 0); + $cost -= $fNRate; + $fRest = $cost - $salvage; + + for ($n = 0; $n < $period; ++$n) { + $fNRate = round($rate * $cost, 0); + $fRest -= $fNRate; + + if ($fRest < 0.0) { + switch ($period - $n) { + case 0: + case 1: + return round($cost * 0.5, 0); + default: + return 0.0; + } + } + $cost -= $fNRate; + } + + return $fNRate; + } + + /** + * AMORLINC. + * + * Returns the depreciation for each accounting period. + * This function is provided for the French accounting system. If an asset is purchased in + * the middle of the accounting period, the prorated depreciation is taken into account. + * + * Excel Function: + * AMORLINC(cost,purchased,firstPeriod,salvage,period,rate[,basis]) + * + * @param mixed $cost The cost of the asset as a float + * @param mixed $purchased Date of the purchase of the asset + * @param mixed $firstPeriod Date of the end of the first period + * @param mixed $salvage The salvage value at the end of the life of the asset + * @param mixed $period The period as a float + * @param mixed $rate Rate of depreciation as float + * @param mixed $basis Integer indicating the type of day count to use. + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + * + * @return float|string (string containing the error type if there is an error) + */ + public static function AMORLINC( + $cost, + $purchased, + $firstPeriod, + $salvage, + $period, + $rate, + $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + ) { + $cost = Functions::flattenSingleValue($cost); + $purchased = Functions::flattenSingleValue($purchased); + $firstPeriod = Functions::flattenSingleValue($firstPeriod); + $salvage = Functions::flattenSingleValue($salvage); + $period = Functions::flattenSingleValue($period); + $rate = Functions::flattenSingleValue($rate); + $basis = ($basis === null) + ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + : Functions::flattenSingleValue($basis); + + try { + $cost = FinancialValidations::validateFloat($cost); + $purchased = FinancialValidations::validateDate($purchased); + $firstPeriod = FinancialValidations::validateDate($firstPeriod); + $salvage = FinancialValidations::validateFloat($salvage); + $period = FinancialValidations::validateFloat($period); + $rate = FinancialValidations::validateFloat($rate); + $basis = FinancialValidations::validateBasis($basis); + } catch (Exception $e) { + return $e->getMessage(); + } + + $fOneRate = $cost * $rate; + $fCostDelta = $cost - $salvage; + // Note, quirky variation for leap years on the YEARFRAC for this function + $purchasedYear = DateTimeExcel\DateParts::year($purchased); + $yearFrac = DateTimeExcel\YearFrac::fraction($purchased, $firstPeriod, $basis); + if (is_string($yearFrac)) { + return $yearFrac; + } + + if ( + ($basis == FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL) && + ($yearFrac < 1) && (DateTimeExcel\Helpers::isLeapYear($purchasedYear)) + ) { + $yearFrac *= 365 / 366; + } + + $f0Rate = $yearFrac * $rate * $cost; + $nNumOfFullPeriods = (int) (($cost - $salvage - $f0Rate) / $fOneRate); + + if ($period == 0) { + return $f0Rate; + } elseif ($period <= $nNumOfFullPeriods) { + return $fOneRate; + } elseif ($period == ($nNumOfFullPeriods + 1)) { + return $fCostDelta - $fOneRate * $nNumOfFullPeriods - $f0Rate; + } + + return 0.0; + } + + private static function getAmortizationCoefficient(float $rate): float + { + // The depreciation coefficients are: + // Life of assets (1/rate) Depreciation coefficient + // Less than 3 years 1 + // Between 3 and 4 years 1.5 + // Between 5 and 6 years 2 + // More than 6 years 2.5 + $fUsePer = 1.0 / $rate; + + if ($fUsePer < 3.0) { + return 1.0; + } elseif ($fUsePer < 4.0) { + return 1.5; + } elseif ($fUsePer <= 6.0) { + return 2.0; + } + + return 2.5; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/CashFlowValidations.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/CashFlowValidations.php new file mode 100644 index 0000000..e4c8a3a --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/CashFlowValidations.php @@ -0,0 +1,53 @@ +getMessage(); + } + + return self::calculateFutureValue($rate, $numberOfPeriods, $payment, $presentValue, $type); + } + + /** + * PV. + * + * Returns the Present Value of a cash flow with constant payments and interest rate (annuities). + * + * @param mixed $rate Interest rate per period + * @param mixed $numberOfPeriods Number of periods as an integer + * @param mixed $payment Periodic payment (annuity) + * @param mixed $futureValue Future Value + * @param mixed $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period + * + * @return float|string Result, or a string containing an error + */ + public static function presentValue( + $rate, + $numberOfPeriods, + $payment = 0.0, + $futureValue = 0.0, + $type = FinancialConstants::PAYMENT_END_OF_PERIOD + ) { + $rate = Functions::flattenSingleValue($rate); + $numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods); + $payment = ($payment === null) ? 0.0 : Functions::flattenSingleValue($payment); + $futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue); + $type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type); + + try { + $rate = CashFlowValidations::validateRate($rate); + $numberOfPeriods = CashFlowValidations::validateInt($numberOfPeriods); + $payment = CashFlowValidations::validateFloat($payment); + $futureValue = CashFlowValidations::validateFutureValue($futureValue); + $type = CashFlowValidations::validatePeriodType($type); + } catch (Exception $e) { + return $e->getMessage(); + } + + // Validate parameters + if ($numberOfPeriods < 0) { + return Functions::NAN(); + } + + return self::calculatePresentValue($rate, $numberOfPeriods, $payment, $futureValue, $type); + } + + /** + * NPER. + * + * Returns the number of periods for a cash flow with constant periodic payments (annuities), and interest rate. + * + * @param mixed $rate Interest rate per period + * @param mixed $payment Periodic payment (annuity) + * @param mixed $presentValue Present Value + * @param mixed $futureValue Future Value + * @param mixed $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period + * + * @return float|string Result, or a string containing an error + */ + public static function periods( + $rate, + $payment, + $presentValue, + $futureValue = 0.0, + $type = FinancialConstants::PAYMENT_END_OF_PERIOD + ) { + $rate = Functions::flattenSingleValue($rate); + $payment = Functions::flattenSingleValue($payment); + $presentValue = Functions::flattenSingleValue($presentValue); + $futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue); + $type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type); + + try { + $rate = CashFlowValidations::validateRate($rate); + $payment = CashFlowValidations::validateFloat($payment); + $presentValue = CashFlowValidations::validatePresentValue($presentValue); + $futureValue = CashFlowValidations::validateFutureValue($futureValue); + $type = CashFlowValidations::validatePeriodType($type); + } catch (Exception $e) { + return $e->getMessage(); + } + + // Validate parameters + if ($payment == 0.0) { + return Functions::NAN(); + } + + return self::calculatePeriods($rate, $payment, $presentValue, $futureValue, $type); + } + + private static function calculateFutureValue( + float $rate, + int $numberOfPeriods, + float $payment, + float $presentValue, + int $type + ): float { + if ($rate !== null && $rate != 0) { + return -$presentValue * + (1 + $rate) ** $numberOfPeriods - $payment * (1 + $rate * $type) * ((1 + $rate) ** $numberOfPeriods - 1) + / $rate; + } + + return -$presentValue - $payment * $numberOfPeriods; + } + + private static function calculatePresentValue( + float $rate, + int $numberOfPeriods, + float $payment, + float $futureValue, + int $type + ): float { + if ($rate != 0.0) { + return (-$payment * (1 + $rate * $type) + * (((1 + $rate) ** $numberOfPeriods - 1) / $rate) - $futureValue) / (1 + $rate) ** $numberOfPeriods; + } + + return -$futureValue - $payment * $numberOfPeriods; + } + + /** + * @return float|string + */ + private static function calculatePeriods( + float $rate, + float $payment, + float $presentValue, + float $futureValue, + int $type + ) { + if ($rate != 0.0) { + if ($presentValue == 0.0) { + return Functions::NAN(); + } + + return log(($payment * (1 + $rate * $type) / $rate - $futureValue) / + ($presentValue + $payment * (1 + $rate * $type) / $rate)) / log(1 + $rate); + } + + return (-$presentValue - $futureValue) / $payment; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Cumulative.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Cumulative.php new file mode 100644 index 0000000..b7f6011 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Cumulative.php @@ -0,0 +1,141 @@ +getMessage(); + } + + // Validate parameters + if ($start < 1 || $start > $end) { + return Functions::NAN(); + } + + // Calculate + $interest = 0; + for ($per = $start; $per <= $end; ++$per) { + $ipmt = Interest::payment($rate, $per, $periods, $presentValue, 0, $type); + if (is_string($ipmt)) { + return $ipmt; + } + + $interest += $ipmt; + } + + return $interest; + } + + /** + * CUMPRINC. + * + * Returns the cumulative principal paid on a loan between the start and end periods. + * + * Excel Function: + * CUMPRINC(rate,nper,pv,start,end[,type]) + * + * @param mixed $rate The Interest rate + * @param mixed $periods The total number of payment periods as an integer + * @param mixed $presentValue Present Value + * @param mixed $start The first period in the calculation. + * Payment periods are numbered beginning with 1. + * @param mixed $end the last period in the calculation + * @param mixed $type A number 0 or 1 and indicates when payments are due: + * 0 or omitted At the end of the period. + * 1 At the beginning of the period. + * + * @return float|string + */ + public static function principal( + $rate, + $periods, + $presentValue, + $start, + $end, + $type = FinancialConstants::PAYMENT_END_OF_PERIOD + ) { + $rate = Functions::flattenSingleValue($rate); + $periods = Functions::flattenSingleValue($periods); + $presentValue = Functions::flattenSingleValue($presentValue); + $start = Functions::flattenSingleValue($start); + $end = Functions::flattenSingleValue($end); + $type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type); + + try { + $rate = CashFlowValidations::validateRate($rate); + $periods = CashFlowValidations::validateInt($periods); + $presentValue = CashFlowValidations::validatePresentValue($presentValue); + $start = CashFlowValidations::validateInt($start); + $end = CashFlowValidations::validateInt($end); + $type = CashFlowValidations::validatePeriodType($type); + } catch (Exception $e) { + return $e->getMessage(); + } + + // Validate parameters + if ($start < 1 || $start > $end) { + return Functions::VALUE(); + } + + // Calculate + $principal = 0; + for ($per = $start; $per <= $end; ++$per) { + $ppmt = Payments::interestPayment($rate, $per, $periods, $presentValue, 0, $type); + if (is_string($ppmt)) { + return $ppmt; + } + + $principal += $ppmt; + } + + return $principal; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php new file mode 100644 index 0000000..56d2e37 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php @@ -0,0 +1,216 @@ +getMessage(); + } + + // Validate parameters + if ($period <= 0 || $period > $numberOfPeriods) { + return Functions::NAN(); + } + + // Calculate + $interestAndPrincipal = new InterestAndPrincipal( + $interestRate, + $period, + $numberOfPeriods, + $presentValue, + $futureValue, + $type + ); + + return $interestAndPrincipal->interest(); + } + + /** + * ISPMT. + * + * Returns the interest payment for an investment based on an interest rate and a constant payment schedule. + * + * Excel Function: + * =ISPMT(interest_rate, period, number_payments, pv) + * + * @param mixed $interestRate is the interest rate for the investment + * @param mixed $period is the period to calculate the interest rate. It must be betweeen 1 and number_payments. + * @param mixed $numberOfPeriods is the number of payments for the annuity + * @param mixed $principleRemaining is the loan amount or present value of the payments + */ + public static function schedulePayment($interestRate, $period, $numberOfPeriods, $principleRemaining) + { + $interestRate = Functions::flattenSingleValue($interestRate); + $period = Functions::flattenSingleValue($period); + $numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods); + $principleRemaining = Functions::flattenSingleValue($principleRemaining); + + try { + $interestRate = CashFlowValidations::validateRate($interestRate); + $period = CashFlowValidations::validateInt($period); + $numberOfPeriods = CashFlowValidations::validateInt($numberOfPeriods); + $principleRemaining = CashFlowValidations::validateFloat($principleRemaining); + } catch (Exception $e) { + return $e->getMessage(); + } + + // Validate parameters + if ($period <= 0 || $period > $numberOfPeriods) { + return Functions::NAN(); + } + + // Return value + $returnValue = 0; + + // Calculate + $principlePayment = ($principleRemaining * 1.0) / ($numberOfPeriods * 1.0); + for ($i = 0; $i <= $period; ++$i) { + $returnValue = $interestRate * $principleRemaining * -1; + $principleRemaining -= $principlePayment; + // principle needs to be 0 after the last payment, don't let floating point screw it up + if ($i == $numberOfPeriods) { + $returnValue = 0.0; + } + } + + return $returnValue; + } + + /** + * RATE. + * + * Returns the interest rate per period of an annuity. + * RATE is calculated by iteration and can have zero or more solutions. + * If the successive results of RATE do not converge to within 0.0000001 after 20 iterations, + * RATE returns the #NUM! error value. + * + * Excel Function: + * RATE(nper,pmt,pv[,fv[,type[,guess]]]) + * + * @param mixed $numberOfPeriods The total number of payment periods in an annuity + * @param mixed $payment The payment made each period and cannot change over the life of the annuity. + * Typically, pmt includes principal and interest but no other fees or taxes. + * @param mixed $presentValue The present value - the total amount that a series of future payments is worth now + * @param mixed $futureValue The future value, or a cash balance you want to attain after the last payment is made. + * If fv is omitted, it is assumed to be 0 (the future value of a loan, + * for example, is 0). + * @param mixed $type A number 0 or 1 and indicates when payments are due: + * 0 or omitted At the end of the period. + * 1 At the beginning of the period. + * @param mixed $guess Your guess for what the rate will be. + * If you omit guess, it is assumed to be 10 percent. + * + * @return float|string + */ + public static function rate( + $numberOfPeriods, + $payment, + $presentValue, + $futureValue = 0.0, + $type = FinancialConstants::PAYMENT_END_OF_PERIOD, + $guess = 0.1 + ) { + $numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods); + $payment = Functions::flattenSingleValue($payment); + $presentValue = Functions::flattenSingleValue($presentValue); + $futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue); + $type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type); + $guess = ($guess === null) ? 0.1 : Functions::flattenSingleValue($guess); + + try { + $numberOfPeriods = CashFlowValidations::validateInt($numberOfPeriods); + $payment = CashFlowValidations::validateFloat($payment); + $presentValue = CashFlowValidations::validatePresentValue($presentValue); + $futureValue = CashFlowValidations::validateFutureValue($futureValue); + $type = CashFlowValidations::validatePeriodType($type); + $guess = CashFlowValidations::validateFloat($guess); + } catch (Exception $e) { + return $e->getMessage(); + } + + $rate = $guess; + // rest of code adapted from python/numpy + $close = false; + $iter = 0; + while (!$close && $iter < self::FINANCIAL_MAX_ITERATIONS) { + $nextdiff = self::rateNextGuess($rate, $numberOfPeriods, $payment, $presentValue, $futureValue, $type); + if (!is_numeric($nextdiff)) { + break; + } + $rate1 = $rate - $nextdiff; + $close = abs($rate1 - $rate) < self::FINANCIAL_PRECISION; + ++$iter; + $rate = $rate1; + } + + return $close ? $rate : Functions::NAN(); + } + + private static function rateNextGuess($rate, $numberOfPeriods, $payment, $presentValue, $futureValue, $type) + { + if ($rate == 0.0) { + return Functions::NAN(); + } + $tt1 = ($rate + 1) ** $numberOfPeriods; + $tt2 = ($rate + 1) ** ($numberOfPeriods - 1); + $numerator = $futureValue + $tt1 * $presentValue + $payment * ($tt1 - 1) * ($rate * $type + 1) / $rate; + $denominator = $numberOfPeriods * $tt2 * $presentValue - $payment * ($tt1 - 1) + * ($rate * $type + 1) / ($rate * $rate) + $numberOfPeriods + * $payment * $tt2 * ($rate * $type + 1) / $rate + $payment * ($tt1 - 1) * $type / $rate; + if ($denominator == 0) { + return Functions::NAN(); + } + + return $numerator / $denominator; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/InterestAndPrincipal.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/InterestAndPrincipal.php new file mode 100644 index 0000000..ca989e0 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/InterestAndPrincipal.php @@ -0,0 +1,44 @@ +interest = $interest; + $this->principal = $principal; + } + + public function interest(): float + { + return $this->interest; + } + + public function principal(): float + { + return $this->principal; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Payments.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Payments.php new file mode 100644 index 0000000..e103f92 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Payments.php @@ -0,0 +1,115 @@ +getMessage(); + } + + // Calculate + if ($interestRate != 0.0) { + return (-$futureValue - $presentValue * (1 + $interestRate) ** $numberOfPeriods) / + (1 + $interestRate * $type) / (((1 + $interestRate) ** $numberOfPeriods - 1) / $interestRate); + } + + return (-$presentValue - $futureValue) / $numberOfPeriods; + } + + /** + * PPMT. + * + * Returns the interest payment for a given period for an investment based on periodic, constant payments + * and a constant interest rate. + * + * @param mixed $interestRate Interest rate per period + * @param mixed $period Period for which we want to find the interest + * @param mixed $numberOfPeriods Number of periods + * @param mixed $presentValue Present Value + * @param mixed $futureValue Future Value + * @param mixed $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period + * + * @return float|string Result, or a string containing an error + */ + public static function interestPayment( + $interestRate, + $period, + $numberOfPeriods, + $presentValue, + $futureValue = 0, + $type = FinancialConstants::PAYMENT_END_OF_PERIOD + ) { + $interestRate = Functions::flattenSingleValue($interestRate); + $period = Functions::flattenSingleValue($period); + $numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods); + $presentValue = Functions::flattenSingleValue($presentValue); + $futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue); + $type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type); + + try { + $interestRate = CashFlowValidations::validateRate($interestRate); + $period = CashFlowValidations::validateInt($period); + $numberOfPeriods = CashFlowValidations::validateInt($numberOfPeriods); + $presentValue = CashFlowValidations::validatePresentValue($presentValue); + $futureValue = CashFlowValidations::validateFutureValue($futureValue); + $type = CashFlowValidations::validatePeriodType($type); + } catch (Exception $e) { + return $e->getMessage(); + } + + // Validate parameters + if ($period <= 0 || $period > $numberOfPeriods) { + return Functions::NAN(); + } + + // Calculate + $interestAndPrincipal = new InterestAndPrincipal( + $interestRate, + $period, + $numberOfPeriods, + $presentValue, + $futureValue, + $type + ); + + return $interestAndPrincipal->principal(); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Single.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Single.php new file mode 100644 index 0000000..a30634d --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Single.php @@ -0,0 +1,108 @@ +getMessage(); + } + + return $principal; + } + + /** + * PDURATION. + * + * Calculates the number of periods required for an investment to reach a specified value. + * + * @param mixed $rate Interest rate per period + * @param mixed $presentValue Present Value + * @param mixed $futureValue Future Value + * + * @return float|string Result, or a string containing an error + */ + public static function periods($rate, $presentValue, $futureValue) + { + $rate = Functions::flattenSingleValue($rate); + $presentValue = Functions::flattenSingleValue($presentValue); + $futureValue = Functions::flattenSingleValue($futureValue); + + try { + $rate = CashFlowValidations::validateRate($rate); + $presentValue = CashFlowValidations::validatePresentValue($presentValue); + $futureValue = CashFlowValidations::validateFutureValue($futureValue); + } catch (Exception $e) { + return $e->getMessage(); + } + + // Validate parameters + if ($rate <= 0.0 || $presentValue <= 0.0 || $futureValue <= 0.0) { + return Functions::NAN(); + } + + return (log($futureValue) - log($presentValue)) / log(1 + $rate); + } + + /** + * RRI. + * + * Calculates the interest rate required for an investment to grow to a specified future value . + * + * @param float $periods The number of periods over which the investment is made + * @param float $presentValue Present Value + * @param float $futureValue Future Value + * + * @return float|string Result, or a string containing an error + */ + public static function interestRate($periods = 0.0, $presentValue = 0.0, $futureValue = 0.0) + { + $periods = Functions::flattenSingleValue($periods); + $presentValue = Functions::flattenSingleValue($presentValue); + $futureValue = Functions::flattenSingleValue($futureValue); + + try { + $periods = CashFlowValidations::validateFloat($periods); + $presentValue = CashFlowValidations::validatePresentValue($presentValue); + $futureValue = CashFlowValidations::validateFutureValue($futureValue); + } catch (Exception $e) { + return $e->getMessage(); + } + + // Validate parameters + if ($periods <= 0.0 || $presentValue <= 0.0 || $futureValue < 0.0) { + return Functions::NAN(); + } + + return ($futureValue / $presentValue) ** (1 / $periods) - 1; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php new file mode 100644 index 0000000..8986146 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php @@ -0,0 +1,244 @@ +getMessage(); + } + } + + return self::xirrPart2($values); + } + + private static function xirrPart2(array &$values): string + { + $valCount = count($values); + $foundpos = false; + $foundneg = false; + for ($i = 0; $i < $valCount; ++$i) { + $fld = $values[$i]; + if (!is_numeric($fld)) { + return Functions::VALUE(); + } elseif ($fld > 0) { + $foundpos = true; + } elseif ($fld < 0) { + $foundneg = true; + } + } + if (!self::bothNegAndPos($foundneg, $foundpos)) { + return Functions::NAN(); + } + + return ''; + } + + /** + * @return float|string + */ + private static function xirrPart3(array $values, array $dates, float $x1, float $x2) + { + $f = self::xnpvOrdered($x1, $values, $dates, false); + if ($f < 0.0) { + $rtb = $x1; + $dx = $x2 - $x1; + } else { + $rtb = $x2; + $dx = $x1 - $x2; + } + + $rslt = Functions::VALUE(); + for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) { + $dx *= 0.5; + $x_mid = $rtb + $dx; + $f_mid = (float) self::xnpvOrdered($x_mid, $values, $dates, false); + if ($f_mid <= 0.0) { + $rtb = $x_mid; + } + if ((abs($f_mid) < self::FINANCIAL_PRECISION) || (abs($dx) < self::FINANCIAL_PRECISION)) { + $rslt = $x_mid; + + break; + } + } + + return $rslt; + } + + /** + * @param mixed $rate + * @param mixed $values + * @param mixed $dates + * + * @return float|string + */ + private static function xnpvOrdered($rate, $values, $dates, bool $ordered = true) + { + $rate = Functions::flattenSingleValue($rate); + $values = Functions::flattenArray($values); + $dates = Functions::flattenArray($dates); + $valCount = count($values); + + try { + self::validateXnpv($rate, $values, $dates); + $date0 = DateTimeExcel\Helpers::getDateValue($dates[0]); + } catch (Exception $e) { + return $e->getMessage(); + } + + $xnpv = 0.0; + for ($i = 0; $i < $valCount; ++$i) { + if (!is_numeric($values[$i])) { + return Functions::VALUE(); + } + + try { + $datei = DateTimeExcel\Helpers::getDateValue($dates[$i]); + } catch (Exception $e) { + return $e->getMessage(); + } + if ($date0 > $datei) { + $dif = $ordered ? Functions::NAN() : -((int) DateTimeExcel\Difference::interval($datei, $date0, 'd')); + } else { + $dif = DateTimeExcel\Difference::interval($date0, $datei, 'd'); + } + if (!is_numeric($dif)) { + return $dif; + } + $xnpv += $values[$i] / (1 + $rate) ** ($dif / 365); + } + + return is_finite($xnpv) ? $xnpv : Functions::VALUE(); + } + + /** + * @param mixed $rate + */ + private static function validateXnpv($rate, array $values, array $dates): void + { + if (!is_numeric($rate)) { + throw new Exception(Functions::VALUE()); + } + $valCount = count($values); + if ($valCount != count($dates)) { + throw new Exception(Functions::NAN()); + } + if ($valCount > 1 && ((min($values) > 0) || (max($values) < 0))) { + throw new Exception(Functions::NAN()); + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/Periodic.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/Periodic.php new file mode 100644 index 0000000..c42df0c --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/Periodic.php @@ -0,0 +1,160 @@ + 0.0) { + return Functions::VALUE(); + } + + $f = self::presentValue($x1, $values); + if ($f < 0.0) { + $rtb = $x1; + $dx = $x2 - $x1; + } else { + $rtb = $x2; + $dx = $x1 - $x2; + } + + for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) { + $dx *= 0.5; + $x_mid = $rtb + $dx; + $f_mid = self::presentValue($x_mid, $values); + if ($f_mid <= 0.0) { + $rtb = $x_mid; + } + if ((abs($f_mid) < self::FINANCIAL_PRECISION) || (abs($dx) < self::FINANCIAL_PRECISION)) { + return $x_mid; + } + } + + return Functions::VALUE(); + } + + /** + * MIRR. + * + * Returns the modified internal rate of return for a series of periodic cash flows. MIRR considers both + * the cost of the investment and the interest received on reinvestment of cash. + * + * Excel Function: + * MIRR(values,finance_rate, reinvestment_rate) + * + * @param mixed $values An array or a reference to cells that contain a series of payments and + * income occurring at regular intervals. + * Payments are negative value, income is positive values. + * @param mixed $financeRate The interest rate you pay on the money used in the cash flows + * @param mixed $reinvestmentRate The interest rate you receive on the cash flows as you reinvest them + * + * @return float|string Result, or a string containing an error + */ + public static function modifiedRate($values, $financeRate, $reinvestmentRate) + { + if (!is_array($values)) { + return Functions::VALUE(); + } + $values = Functions::flattenArray($values); + $financeRate = Functions::flattenSingleValue($financeRate); + $reinvestmentRate = Functions::flattenSingleValue($reinvestmentRate); + $n = count($values); + + $rr = 1.0 + $reinvestmentRate; + $fr = 1.0 + $financeRate; + + $npvPos = $npvNeg = 0.0; + foreach ($values as $i => $v) { + if ($v >= 0) { + $npvPos += $v / $rr ** $i; + } else { + $npvNeg += $v / $fr ** $i; + } + } + + if (($npvNeg === 0.0) || ($npvPos === 0.0) || ($reinvestmentRate <= -1.0)) { + return Functions::VALUE(); + } + + $mirr = ((-$npvPos * $rr ** $n) + / ($npvNeg * ($rr))) ** (1.0 / ($n - 1)) - 1.0; + + return is_finite($mirr) ? $mirr : Functions::VALUE(); + } + + /** + * NPV. + * + * Returns the Net Present Value of a cash flow series given a discount rate. + * + * @param mixed $rate + * + * @return float + */ + public static function presentValue($rate, ...$args) + { + $returnValue = 0; + + $rate = Functions::flattenSingleValue($rate); + $aArgs = Functions::flattenArray($args); + + // Calculate + $countArgs = count($aArgs); + for ($i = 1; $i <= $countArgs; ++$i) { + // Is it a numeric value? + if (is_numeric($aArgs[$i - 1])) { + $returnValue += $aArgs[$i - 1] / (1 + $rate) ** $i; + } + } + + return $returnValue; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Constants.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Constants.php new file mode 100644 index 0000000..17740b0 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Constants.php @@ -0,0 +1,19 @@ +getMessage(); + } + + $daysPerYear = Helpers::daysPerYear(DateTimeExcel\DateParts::year($settlement), $basis); + if (is_string($daysPerYear)) { + return Functions::VALUE(); + } + $prev = self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_PREVIOUS); + + if ($basis === FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL) { + return abs((float) DateTimeExcel\Days::between($prev, $settlement)); + } + + return (float) DateTimeExcel\YearFrac::fraction($prev, $settlement, $basis) * $daysPerYear; + } + + /** + * COUPDAYS. + * + * Returns the number of days in the coupon period that contains the settlement date. + * + * Excel Function: + * COUPDAYS(settlement,maturity,frequency[,basis]) + * + * @param mixed $settlement The security's settlement date. + * The security settlement date is the date after the issue + * date when the security is traded to the buyer. + * @param mixed $maturity The security's maturity date. + * The maturity date is the date when the security expires. + * @param mixed $frequency The number of coupon payments per year. + * Valid frequency values are: + * 1 Annual + * 2 Semi-Annual + * 4 Quarterly + * @param mixed $basis The type of day count to use (int). + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + * + * @return float|string + */ + public static function COUPDAYS( + $settlement, + $maturity, + $frequency, + $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + ) { + $settlement = Functions::flattenSingleValue($settlement); + $maturity = Functions::flattenSingleValue($maturity); + $frequency = Functions::flattenSingleValue($frequency); + $basis = ($basis === null) + ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + : Functions::flattenSingleValue($basis); + + try { + $settlement = FinancialValidations::validateSettlementDate($settlement); + $maturity = FinancialValidations::validateMaturityDate($maturity); + self::validateCouponPeriod($settlement, $maturity); + $frequency = FinancialValidations::validateFrequency($frequency); + $basis = FinancialValidations::validateBasis($basis); + } catch (Exception $e) { + return $e->getMessage(); + } + + switch ($basis) { + case FinancialConstants::BASIS_DAYS_PER_YEAR_365: + // Actual/365 + return 365 / $frequency; + case FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL: + // Actual/actual + if ($frequency == FinancialConstants::FREQUENCY_ANNUAL) { + $daysPerYear = Helpers::daysPerYear(DateTimeExcel\DateParts::year($settlement), $basis); + + return $daysPerYear / $frequency; + } + $prev = self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_PREVIOUS); + $next = self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_NEXT); + + return $next - $prev; + default: + // US (NASD) 30/360, Actual/360 or European 30/360 + return 360 / $frequency; + } + } + + /** + * COUPDAYSNC. + * + * Returns the number of days from the settlement date to the next coupon date. + * + * Excel Function: + * COUPDAYSNC(settlement,maturity,frequency[,basis]) + * + * @param mixed $settlement The security's settlement date. + * The security settlement date is the date after the issue + * date when the security is traded to the buyer. + * @param mixed $maturity The security's maturity date. + * The maturity date is the date when the security expires. + * @param mixed $frequency The number of coupon payments per year. + * Valid frequency values are: + * 1 Annual + * 2 Semi-Annual + * 4 Quarterly + * @param mixed $basis The type of day count to use (int) . + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + * + * @return float|string + */ + public static function COUPDAYSNC( + $settlement, + $maturity, + $frequency, + $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + ) { + $settlement = Functions::flattenSingleValue($settlement); + $maturity = Functions::flattenSingleValue($maturity); + $frequency = Functions::flattenSingleValue($frequency); + $basis = ($basis === null) + ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + : Functions::flattenSingleValue($basis); + + try { + $settlement = FinancialValidations::validateSettlementDate($settlement); + $maturity = FinancialValidations::validateMaturityDate($maturity); + self::validateCouponPeriod($settlement, $maturity); + $frequency = FinancialValidations::validateFrequency($frequency); + $basis = FinancialValidations::validateBasis($basis); + } catch (Exception $e) { + return $e->getMessage(); + } + + $daysPerYear = Helpers::daysPerYear(DateTimeExcel\DateParts::year($settlement), $basis); + $next = self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_NEXT); + + if ($basis === FinancialConstants::BASIS_DAYS_PER_YEAR_NASD) { + $settlementDate = Date::excelToDateTimeObject($settlement); + $settlementEoM = Helpers::isLastDayOfMonth($settlementDate); + if ($settlementEoM) { + ++$settlement; + } + } + + return (float) DateTimeExcel\YearFrac::fraction($settlement, $next, $basis) * $daysPerYear; + } + + /** + * COUPNCD. + * + * Returns the next coupon date after the settlement date. + * + * Excel Function: + * COUPNCD(settlement,maturity,frequency[,basis]) + * + * @param mixed $settlement The security's settlement date. + * The security settlement date is the date after the issue + * date when the security is traded to the buyer. + * @param mixed $maturity The security's maturity date. + * The maturity date is the date when the security expires. + * @param mixed $frequency The number of coupon payments per year. + * Valid frequency values are: + * 1 Annual + * 2 Semi-Annual + * 4 Quarterly + * @param mixed $basis The type of day count to use (int). + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + * + * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object, + * depending on the value of the ReturnDateType flag + */ + public static function COUPNCD( + $settlement, + $maturity, + $frequency, + $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + ) { + $settlement = Functions::flattenSingleValue($settlement); + $maturity = Functions::flattenSingleValue($maturity); + $frequency = Functions::flattenSingleValue($frequency); + $basis = ($basis === null) + ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + : Functions::flattenSingleValue($basis); + + try { + $settlement = FinancialValidations::validateSettlementDate($settlement); + $maturity = FinancialValidations::validateMaturityDate($maturity); + self::validateCouponPeriod($settlement, $maturity); + $frequency = FinancialValidations::validateFrequency($frequency); + $basis = FinancialValidations::validateBasis($basis); + } catch (Exception $e) { + return $e->getMessage(); + } + + return self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_NEXT); + } + + /** + * COUPNUM. + * + * Returns the number of coupons payable between the settlement date and maturity date, + * rounded up to the nearest whole coupon. + * + * Excel Function: + * COUPNUM(settlement,maturity,frequency[,basis]) + * + * @param mixed $settlement The security's settlement date. + * The security settlement date is the date after the issue + * date when the security is traded to the buyer. + * @param mixed $maturity The security's maturity date. + * The maturity date is the date when the security expires. + * @param mixed $frequency The number of coupon payments per year. + * Valid frequency values are: + * 1 Annual + * 2 Semi-Annual + * 4 Quarterly + * @param mixed $basis The type of day count to use (int). + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + * + * @return int|string + */ + public static function COUPNUM( + $settlement, + $maturity, + $frequency, + $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + ) { + $settlement = Functions::flattenSingleValue($settlement); + $maturity = Functions::flattenSingleValue($maturity); + $frequency = Functions::flattenSingleValue($frequency); + $basis = ($basis === null) + ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + : Functions::flattenSingleValue($basis); + + try { + $settlement = FinancialValidations::validateSettlementDate($settlement); + $maturity = FinancialValidations::validateMaturityDate($maturity); + self::validateCouponPeriod($settlement, $maturity); + $frequency = FinancialValidations::validateFrequency($frequency); + $basis = FinancialValidations::validateBasis($basis); + } catch (Exception $e) { + return $e->getMessage(); + } + + $yearsBetweenSettlementAndMaturity = DateTimeExcel\YearFrac::fraction( + $settlement, + $maturity, + FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + ); + + return (int) ceil((float) $yearsBetweenSettlementAndMaturity * $frequency); + } + + /** + * COUPPCD. + * + * Returns the previous coupon date before the settlement date. + * + * Excel Function: + * COUPPCD(settlement,maturity,frequency[,basis]) + * + * @param mixed $settlement The security's settlement date. + * The security settlement date is the date after the issue + * date when the security is traded to the buyer. + * @param mixed $maturity The security's maturity date. + * The maturity date is the date when the security expires. + * @param mixed $frequency The number of coupon payments per year. + * Valid frequency values are: + * 1 Annual + * 2 Semi-Annual + * 4 Quarterly + * @param mixed $basis The type of day count to use (int). + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + * + * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object, + * depending on the value of the ReturnDateType flag + */ + public static function COUPPCD( + $settlement, + $maturity, + $frequency, + $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + ) { + $settlement = Functions::flattenSingleValue($settlement); + $maturity = Functions::flattenSingleValue($maturity); + $frequency = Functions::flattenSingleValue($frequency); + $basis = ($basis === null) + ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + : Functions::flattenSingleValue($basis); + + try { + $settlement = FinancialValidations::validateSettlementDate($settlement); + $maturity = FinancialValidations::validateMaturityDate($maturity); + self::validateCouponPeriod($settlement, $maturity); + $frequency = FinancialValidations::validateFrequency($frequency); + $basis = FinancialValidations::validateBasis($basis); + } catch (Exception $e) { + return $e->getMessage(); + } + + return self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_PREVIOUS); + } + + private static function monthsDiff(DateTime $result, int $months, string $plusOrMinus, int $day, bool $lastDayFlag): void + { + $result->setDate((int) $result->format('Y'), (int) $result->format('m'), 1); + $result->modify("$plusOrMinus $months months"); + $daysInMonth = (int) $result->format('t'); + $result->setDate((int) $result->format('Y'), (int) $result->format('m'), $lastDayFlag ? $daysInMonth : min($day, $daysInMonth)); + } + + private static function couponFirstPeriodDate(float $settlement, float $maturity, int $frequency, bool $next): float + { + $months = 12 / $frequency; + + $result = Date::excelToDateTimeObject($maturity); + $day = (int) $result->format('d'); + $lastDayFlag = Helpers::isLastDayOfMonth($result); + + while ($settlement < Date::PHPToExcel($result)) { + self::monthsDiff($result, $months, '-', $day, $lastDayFlag); + } + if ($next === true) { + self::monthsDiff($result, $months, '+', $day, $lastDayFlag); + } + + return (float) Date::PHPToExcel($result); + } + + private static function validateCouponPeriod(float $settlement, float $maturity): void + { + if ($settlement >= $maturity) { + throw new Exception(Functions::NAN()); + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Depreciation.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Depreciation.php new file mode 100644 index 0000000..650a486 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Depreciation.php @@ -0,0 +1,266 @@ +getMessage(); + } + + if ($cost === 0.0) { + return 0.0; + } + + // Set Fixed Depreciation Rate + $fixedDepreciationRate = 1 - ($salvage / $cost) ** (1 / $life); + $fixedDepreciationRate = round($fixedDepreciationRate, 3); + + // Loop through each period calculating the depreciation + // TODO Handle period value between 0 and 1 (e.g. 0.5) + $previousDepreciation = 0; + $depreciation = 0; + for ($per = 1; $per <= $period; ++$per) { + if ($per == 1) { + $depreciation = $cost * $fixedDepreciationRate * $month / 12; + } elseif ($per == ($life + 1)) { + $depreciation = ($cost - $previousDepreciation) * $fixedDepreciationRate * (12 - $month) / 12; + } else { + $depreciation = ($cost - $previousDepreciation) * $fixedDepreciationRate; + } + $previousDepreciation += $depreciation; + } + + return $depreciation; + } + + /** + * DDB. + * + * Returns the depreciation of an asset for a specified period using the + * double-declining balance method or some other method you specify. + * + * Excel Function: + * DDB(cost,salvage,life,period[,factor]) + * + * @param mixed $cost Initial cost of the asset + * @param mixed $salvage Value at the end of the depreciation. + * (Sometimes called the salvage value of the asset) + * @param mixed $life Number of periods over which the asset is depreciated. + * (Sometimes called the useful life of the asset) + * @param mixed $period The period for which you want to calculate the + * depreciation. Period must use the same units as life. + * @param mixed $factor The rate at which the balance declines. + * If factor is omitted, it is assumed to be 2 (the + * double-declining balance method). + * + * @return float|string + */ + public static function DDB($cost, $salvage, $life, $period, $factor = 2.0) + { + $cost = Functions::flattenSingleValue($cost); + $salvage = Functions::flattenSingleValue($salvage); + $life = Functions::flattenSingleValue($life); + $period = Functions::flattenSingleValue($period); + $factor = Functions::flattenSingleValue($factor); + + try { + $cost = self::validateCost($cost); + $salvage = self::validateSalvage($salvage); + $life = self::validateLife($life); + $period = self::validatePeriod($period); + $factor = self::validateFactor($factor); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($period > $life) { + return Functions::NAN(); + } + + // Loop through each period calculating the depreciation + // TODO Handling for fractional $period values + $previousDepreciation = 0; + $depreciation = 0; + for ($per = 1; $per <= $period; ++$per) { + $depreciation = min( + ($cost - $previousDepreciation) * ($factor / $life), + ($cost - $salvage - $previousDepreciation) + ); + $previousDepreciation += $depreciation; + } + + return $depreciation; + } + + /** + * SLN. + * + * Returns the straight-line depreciation of an asset for one period + * + * @param mixed $cost Initial cost of the asset + * @param mixed $salvage Value at the end of the depreciation + * @param mixed $life Number of periods over which the asset is depreciated + * + * @return float|string Result, or a string containing an error + */ + public static function SLN($cost, $salvage, $life) + { + $cost = Functions::flattenSingleValue($cost); + $salvage = Functions::flattenSingleValue($salvage); + $life = Functions::flattenSingleValue($life); + + try { + $cost = self::validateCost($cost, true); + $salvage = self::validateSalvage($salvage, true); + $life = self::validateLife($life, true); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($life === 0.0) { + return Functions::DIV0(); + } + + return ($cost - $salvage) / $life; + } + + /** + * SYD. + * + * Returns the sum-of-years' digits depreciation of an asset for a specified period. + * + * @param mixed $cost Initial cost of the asset + * @param mixed $salvage Value at the end of the depreciation + * @param mixed $life Number of periods over which the asset is depreciated + * @param mixed $period Period + * + * @return float|string Result, or a string containing an error + */ + public static function SYD($cost, $salvage, $life, $period) + { + $cost = Functions::flattenSingleValue($cost); + $salvage = Functions::flattenSingleValue($salvage); + $life = Functions::flattenSingleValue($life); + $period = Functions::flattenSingleValue($period); + + try { + $cost = self::validateCost($cost, true); + $salvage = self::validateSalvage($salvage); + $life = self::validateLife($life); + $period = self::validatePeriod($period); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($period > $life) { + return Functions::NAN(); + } + + $syd = (($cost - $salvage) * ($life - $period + 1) * 2) / ($life * ($life + 1)); + + return $syd; + } + + private static function validateCost($cost, bool $negativeValueAllowed = false): float + { + $cost = FinancialValidations::validateFloat($cost); + if ($cost < 0.0 && $negativeValueAllowed === false) { + throw new Exception(Functions::NAN()); + } + + return $cost; + } + + private static function validateSalvage($salvage, bool $negativeValueAllowed = false): float + { + $salvage = FinancialValidations::validateFloat($salvage); + if ($salvage < 0.0 && $negativeValueAllowed === false) { + throw new Exception(Functions::NAN()); + } + + return $salvage; + } + + private static function validateLife($life, bool $negativeValueAllowed = false): float + { + $life = FinancialValidations::validateFloat($life); + if ($life < 0.0 && $negativeValueAllowed === false) { + throw new Exception(Functions::NAN()); + } + + return $life; + } + + private static function validatePeriod($period, bool $negativeValueAllowed = false): float + { + $period = FinancialValidations::validateFloat($period); + if ($period <= 0.0 && $negativeValueAllowed === false) { + throw new Exception(Functions::NAN()); + } + + return $period; + } + + private static function validateMonth($month): int + { + $month = FinancialValidations::validateInt($month); + if ($month < 1) { + throw new Exception(Functions::NAN()); + } + + return $month; + } + + private static function validateFactor($factor): float + { + $factor = FinancialValidations::validateFloat($factor); + if ($factor <= 0.0) { + throw new Exception(Functions::NAN()); + } + + return $factor; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Dollar.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Dollar.php new file mode 100644 index 0000000..7bebb39 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Dollar.php @@ -0,0 +1,97 @@ + 4)) { + throw new Exception(Functions::NAN()); + } + + return $basis; + } + + /** + * @param mixed $price + */ + public static function validatePrice($price): float + { + $price = self::validateFloat($price); + if ($price < 0.0) { + throw new Exception(Functions::NAN()); + } + + return $price; + } + + /** + * @param mixed $parValue + */ + public static function validateParValue($parValue): float + { + $parValue = self::validateFloat($parValue); + if ($parValue < 0.0) { + throw new Exception(Functions::NAN()); + } + + return $parValue; + } + + /** + * @param mixed $yield + */ + public static function validateYield($yield): float + { + $yield = self::validateFloat($yield); + if ($yield < 0.0) { + throw new Exception(Functions::NAN()); + } + + return $yield; + } + + /** + * @param mixed $discount + */ + public static function validateDiscount($discount): float + { + $discount = self::validateFloat($discount); + if ($discount <= 0.0) { + throw new Exception(Functions::NAN()); + } + + return $discount; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Helpers.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Helpers.php new file mode 100644 index 0000000..d339b13 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Helpers.php @@ -0,0 +1,58 @@ +format('d') === $date->format('t'); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/InterestRate.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/InterestRate.php new file mode 100644 index 0000000..72df31e --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/InterestRate.php @@ -0,0 +1,72 @@ +getMessage(); + } + + if ($nominalRate <= 0 || $periodsPerYear < 1) { + return Functions::NAN(); + } + + return ((1 + $nominalRate / $periodsPerYear) ** $periodsPerYear) - 1; + } + + /** + * NOMINAL. + * + * Returns the nominal interest rate given the effective rate and the number of compounding payments per year. + * + * @param mixed $effectiveRate Effective interest rate as a float + * @param mixed $periodsPerYear Integer number of compounding payments per year + * + * @return float|string Result, or a string containing an error + */ + public static function nominal($effectiveRate = 0, $periodsPerYear = 0) + { + $effectiveRate = Functions::flattenSingleValue($effectiveRate); + $periodsPerYear = Functions::flattenSingleValue($periodsPerYear); + + try { + $effectiveRate = FinancialValidations::validateFloat($effectiveRate); + $periodsPerYear = FinancialValidations::validateInt($periodsPerYear); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($effectiveRate <= 0 || $periodsPerYear < 1) { + return Functions::NAN(); + } + + // Calculate + return $periodsPerYear * (($effectiveRate + 1) ** (1 / $periodsPerYear) - 1); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/AccruedInterest.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/AccruedInterest.php new file mode 100644 index 0000000..e167429 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/AccruedInterest.php @@ -0,0 +1,151 @@ +getMessage(); + } + + $daysBetweenIssueAndSettlement = YearFrac::fraction($issue, $settlement, $basis); + if (!is_numeric($daysBetweenIssueAndSettlement)) { + // return date error + return $daysBetweenIssueAndSettlement; + } + $daysBetweenFirstInterestAndSettlement = YearFrac::fraction($firstInterest, $settlement, $basis); + if (!is_numeric($daysBetweenFirstInterestAndSettlement)) { + // return date error + return $daysBetweenFirstInterestAndSettlement; + } + + return $parValue * $rate * $daysBetweenIssueAndSettlement; + } + + /** + * ACCRINTM. + * + * Returns the accrued interest for a security that pays interest at maturity. + * + * Excel Function: + * ACCRINTM(issue,settlement,rate[,par[,basis]]) + * + * @param mixed $issue The security's issue date + * @param mixed $settlement The security's settlement (or maturity) date + * @param mixed $rate The security's annual coupon rate + * @param mixed $parValue The security's par value. + * If you omit parValue, ACCRINT uses $1,000. + * @param mixed $basis The type of day count to use. + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + * + * @return float|string Result, or a string containing an error + */ + public static function atMaturity( + $issue, + $settlement, + $rate, + $parValue = 1000, + $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + ) { + $issue = Functions::flattenSingleValue($issue); + $settlement = Functions::flattenSingleValue($settlement); + $rate = Functions::flattenSingleValue($rate); + $parValue = ($parValue === null) ? 1000 : Functions::flattenSingleValue($parValue); + $basis = ($basis === null) + ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + : Functions::flattenSingleValue($basis); + + try { + $issue = SecurityValidations::validateIssueDate($issue); + $settlement = SecurityValidations::validateSettlementDate($settlement); + SecurityValidations::validateSecurityPeriod($issue, $settlement); + $rate = SecurityValidations::validateRate($rate); + $parValue = SecurityValidations::validateParValue($parValue); + $basis = SecurityValidations::validateBasis($basis); + } catch (Exception $e) { + return $e->getMessage(); + } + + $daysBetweenIssueAndSettlement = YearFrac::fraction($issue, $settlement, $basis); + if (!is_numeric($daysBetweenIssueAndSettlement)) { + // return date error + return $daysBetweenIssueAndSettlement; + } + + return $parValue * $rate * $daysBetweenIssueAndSettlement; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/Price.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/Price.php new file mode 100644 index 0000000..7d8d5a3 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/Price.php @@ -0,0 +1,283 @@ +getMessage(); + } + + $dsc = Coupons::COUPDAYSNC($settlement, $maturity, $frequency, $basis); + $e = Coupons::COUPDAYS($settlement, $maturity, $frequency, $basis); + $n = Coupons::COUPNUM($settlement, $maturity, $frequency, $basis); + $a = Coupons::COUPDAYBS($settlement, $maturity, $frequency, $basis); + + $baseYF = 1.0 + ($yield / $frequency); + $rfp = 100 * ($rate / $frequency); + $de = $dsc / $e; + + $result = $redemption / $baseYF ** (--$n + $de); + for ($k = 0; $k <= $n; ++$k) { + $result += $rfp / ($baseYF ** ($k + $de)); + } + $result -= $rfp * ($a / $e); + + return $result; + } + + /** + * PRICEDISC. + * + * Returns the price per $100 face value of a discounted security. + * + * @param mixed $settlement The security's settlement date. + * The security settlement date is the date after the issue date when the security + * is traded to the buyer. + * @param mixed $maturity The security's maturity date. + * The maturity date is the date when the security expires. + * @param mixed $discount The security's discount rate + * @param mixed $redemption The security's redemption value per $100 face value + * @param mixed $basis The type of day count to use. + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + * + * @return float|string Result, or a string containing an error + */ + public static function priceDiscounted( + $settlement, + $maturity, + $discount, + $redemption, + $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + ) { + $settlement = Functions::flattenSingleValue($settlement); + $maturity = Functions::flattenSingleValue($maturity); + $discount = Functions::flattenSingleValue($discount); + $redemption = Functions::flattenSingleValue($redemption); + $basis = ($basis === null) + ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + : Functions::flattenSingleValue($basis); + + try { + $settlement = SecurityValidations::validateSettlementDate($settlement); + $maturity = SecurityValidations::validateMaturityDate($maturity); + SecurityValidations::validateSecurityPeriod($settlement, $maturity); + $discount = SecurityValidations::validateDiscount($discount); + $redemption = SecurityValidations::validateRedemption($redemption); + $basis = SecurityValidations::validateBasis($basis); + } catch (Exception $e) { + return $e->getMessage(); + } + + $daysBetweenSettlementAndMaturity = DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis); + if (!is_numeric($daysBetweenSettlementAndMaturity)) { + // return date error + return $daysBetweenSettlementAndMaturity; + } + + return $redemption * (1 - $discount * $daysBetweenSettlementAndMaturity); + } + + /** + * PRICEMAT. + * + * Returns the price per $100 face value of a security that pays interest at maturity. + * + * @param mixed $settlement The security's settlement date. + * The security's settlement date is the date after the issue date when the + * security is traded to the buyer. + * @param mixed $maturity The security's maturity date. + * The maturity date is the date when the security expires. + * @param mixed $issue The security's issue date + * @param mixed $rate The security's interest rate at date of issue + * @param mixed $yield The security's annual yield + * @param mixed $basis The type of day count to use. + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + * + * @return float|string Result, or a string containing an error + */ + public static function priceAtMaturity( + $settlement, + $maturity, + $issue, + $rate, + $yield, + $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + ) { + $settlement = Functions::flattenSingleValue($settlement); + $maturity = Functions::flattenSingleValue($maturity); + $issue = Functions::flattenSingleValue($issue); + $rate = Functions::flattenSingleValue($rate); + $yield = Functions::flattenSingleValue($yield); + $basis = ($basis === null) + ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + : Functions::flattenSingleValue($basis); + + try { + $settlement = SecurityValidations::validateSettlementDate($settlement); + $maturity = SecurityValidations::validateMaturityDate($maturity); + SecurityValidations::validateSecurityPeriod($settlement, $maturity); + $issue = SecurityValidations::validateIssueDate($issue); + $rate = SecurityValidations::validateRate($rate); + $yield = SecurityValidations::validateYield($yield); + $basis = SecurityValidations::validateBasis($basis); + } catch (Exception $e) { + return $e->getMessage(); + } + + $daysPerYear = Helpers::daysPerYear(DateTimeExcel\DateParts::year($settlement), $basis); + if (!is_numeric($daysPerYear)) { + return $daysPerYear; + } + $daysBetweenIssueAndSettlement = DateTimeExcel\YearFrac::fraction($issue, $settlement, $basis); + if (!is_numeric($daysBetweenIssueAndSettlement)) { + // return date error + return $daysBetweenIssueAndSettlement; + } + $daysBetweenIssueAndSettlement *= $daysPerYear; + $daysBetweenIssueAndMaturity = DateTimeExcel\YearFrac::fraction($issue, $maturity, $basis); + if (!is_numeric($daysBetweenIssueAndMaturity)) { + // return date error + return $daysBetweenIssueAndMaturity; + } + $daysBetweenIssueAndMaturity *= $daysPerYear; + $daysBetweenSettlementAndMaturity = DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis); + if (!is_numeric($daysBetweenSettlementAndMaturity)) { + // return date error + return $daysBetweenSettlementAndMaturity; + } + $daysBetweenSettlementAndMaturity *= $daysPerYear; + + return (100 + (($daysBetweenIssueAndMaturity / $daysPerYear) * $rate * 100)) / + (1 + (($daysBetweenSettlementAndMaturity / $daysPerYear) * $yield)) - + (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate * 100); + } + + /** + * RECEIVED. + * + * Returns the amount received at maturity for a fully invested Security. + * + * @param mixed $settlement The security's settlement date. + * The security settlement date is the date after the issue date when the security + * is traded to the buyer. + * @param mixed $maturity The security's maturity date. + * The maturity date is the date when the security expires. + * @param mixed $investment The amount invested in the security + * @param mixed $discount The security's discount rate + * @param mixed $basis The type of day count to use. + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + * + * @return float|string Result, or a string containing an error + */ + public static function received( + $settlement, + $maturity, + $investment, + $discount, + $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + ) { + $settlement = Functions::flattenSingleValue($settlement); + $maturity = Functions::flattenSingleValue($maturity); + $investment = Functions::flattenSingleValue($investment); + $discount = Functions::flattenSingleValue($discount); + $basis = ($basis === null) + ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + : Functions::flattenSingleValue($basis); + + try { + $settlement = SecurityValidations::validateSettlementDate($settlement); + $maturity = SecurityValidations::validateMaturityDate($maturity); + SecurityValidations::validateSecurityPeriod($settlement, $maturity); + $investment = SecurityValidations::validateFloat($investment); + $discount = SecurityValidations::validateDiscount($discount); + $basis = SecurityValidations::validateBasis($basis); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($investment <= 0) { + return Functions::NAN(); + } + $daysBetweenSettlementAndMaturity = DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis); + if (!is_numeric($daysBetweenSettlementAndMaturity)) { + // return date error + return $daysBetweenSettlementAndMaturity; + } + + return $investment / (1 - ($discount * $daysBetweenSettlementAndMaturity)); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/Rates.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/Rates.php new file mode 100644 index 0000000..c5c5211 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/Rates.php @@ -0,0 +1,137 @@ +getMessage(); + } + + if ($price <= 0.0) { + return Functions::NAN(); + } + + $daysBetweenSettlementAndMaturity = DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis); + if (!is_numeric($daysBetweenSettlementAndMaturity)) { + // return date error + return $daysBetweenSettlementAndMaturity; + } + + return (1 - $price / $redemption) / $daysBetweenSettlementAndMaturity; + } + + /** + * INTRATE. + * + * Returns the interest rate for a fully invested security. + * + * Excel Function: + * INTRATE(settlement,maturity,investment,redemption[,basis]) + * + * @param mixed $settlement The security's settlement date. + * The security settlement date is the date after the issue date when the security + * is traded to the buyer. + * @param mixed $maturity The security's maturity date. + * The maturity date is the date when the security expires. + * @param mixed $investment the amount invested in the security + * @param mixed $redemption the amount to be received at maturity + * @param mixed $basis The type of day count to use. + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + * + * @return float|string + */ + public static function interest( + $settlement, + $maturity, + $investment, + $redemption, + $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + ) { + $settlement = Functions::flattenSingleValue($settlement); + $maturity = Functions::flattenSingleValue($maturity); + $investment = Functions::flattenSingleValue($investment); + $redemption = Functions::flattenSingleValue($redemption); + $basis = ($basis === null) + ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + : Functions::flattenSingleValue($basis); + + try { + $settlement = SecurityValidations::validateSettlementDate($settlement); + $maturity = SecurityValidations::validateMaturityDate($maturity); + SecurityValidations::validateSecurityPeriod($settlement, $maturity); + $investment = SecurityValidations::validateFloat($investment); + $redemption = SecurityValidations::validateRedemption($redemption); + $basis = SecurityValidations::validateBasis($basis); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($investment <= 0) { + return Functions::NAN(); + } + + $daysBetweenSettlementAndMaturity = DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis); + if (!is_numeric($daysBetweenSettlementAndMaturity)) { + // return date error + return $daysBetweenSettlementAndMaturity; + } + + return (($redemption / $investment) - 1) / ($daysBetweenSettlementAndMaturity); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/SecurityValidations.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/SecurityValidations.php new file mode 100644 index 0000000..497197b --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/SecurityValidations.php @@ -0,0 +1,42 @@ += $maturity) { + throw new Exception(Functions::NAN()); + } + } + + /** + * @param mixed $redemption + */ + public static function validateRedemption($redemption): float + { + $redemption = self::validateFloat($redemption); + if ($redemption <= 0.0) { + throw new Exception(Functions::NAN()); + } + + return $redemption; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/Yields.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/Yields.php new file mode 100644 index 0000000..aa62693 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/Yields.php @@ -0,0 +1,153 @@ +getMessage(); + } + + $daysPerYear = Helpers::daysPerYear(DateTimeExcel\DateParts::year($settlement), $basis); + if (!is_numeric($daysPerYear)) { + return $daysPerYear; + } + $daysBetweenSettlementAndMaturity = DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis); + if (!is_numeric($daysBetweenSettlementAndMaturity)) { + // return date error + return $daysBetweenSettlementAndMaturity; + } + $daysBetweenSettlementAndMaturity *= $daysPerYear; + + return (($redemption - $price) / $price) * ($daysPerYear / $daysBetweenSettlementAndMaturity); + } + + /** + * YIELDMAT. + * + * Returns the annual yield of a security that pays interest at maturity. + * + * @param mixed $settlement The security's settlement date. + * The security's settlement date is the date after the issue date when the security + * is traded to the buyer. + * @param mixed $maturity The security's maturity date. + * The maturity date is the date when the security expires. + * @param mixed $issue The security's issue date + * @param mixed $rate The security's interest rate at date of issue + * @param mixed $price The security's price per $100 face value + * @param mixed $basis The type of day count to use. + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + * + * @return float|string Result, or a string containing an error + */ + public static function yieldAtMaturity( + $settlement, + $maturity, + $issue, + $rate, + $price, + $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + ) { + $settlement = Functions::flattenSingleValue($settlement); + $maturity = Functions::flattenSingleValue($maturity); + $issue = Functions::flattenSingleValue($issue); + $rate = Functions::flattenSingleValue($rate); + $price = Functions::flattenSingleValue($price); + $basis = ($basis === null) + ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + : Functions::flattenSingleValue($basis); + + try { + $settlement = SecurityValidations::validateSettlementDate($settlement); + $maturity = SecurityValidations::validateMaturityDate($maturity); + SecurityValidations::validateSecurityPeriod($settlement, $maturity); + $issue = SecurityValidations::validateIssueDate($issue); + $rate = SecurityValidations::validateRate($rate); + $price = SecurityValidations::validatePrice($price); + $basis = SecurityValidations::validateBasis($basis); + } catch (Exception $e) { + return $e->getMessage(); + } + + $daysPerYear = Helpers::daysPerYear(DateTimeExcel\DateParts::year($settlement), $basis); + if (!is_numeric($daysPerYear)) { + return $daysPerYear; + } + $daysBetweenIssueAndSettlement = DateTimeExcel\YearFrac::fraction($issue, $settlement, $basis); + if (!is_numeric($daysBetweenIssueAndSettlement)) { + // return date error + return $daysBetweenIssueAndSettlement; + } + $daysBetweenIssueAndSettlement *= $daysPerYear; + $daysBetweenIssueAndMaturity = DateTimeExcel\YearFrac::fraction($issue, $maturity, $basis); + if (!is_numeric($daysBetweenIssueAndMaturity)) { + // return date error + return $daysBetweenIssueAndMaturity; + } + $daysBetweenIssueAndMaturity *= $daysPerYear; + $daysBetweenSettlementAndMaturity = DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis); + if (!is_numeric($daysBetweenSettlementAndMaturity)) { + // return date error + return $daysBetweenSettlementAndMaturity; + } + $daysBetweenSettlementAndMaturity *= $daysPerYear; + + return ((1 + (($daysBetweenIssueAndMaturity / $daysPerYear) * $rate) - + (($price / 100) + (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate))) / + (($price / 100) + (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate))) * + ($daysPerYear / $daysBetweenSettlementAndMaturity); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/TreasuryBill.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/TreasuryBill.php new file mode 100644 index 0000000..c60af0b --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/TreasuryBill.php @@ -0,0 +1,147 @@ +getMessage(); + } + + if ($discount <= 0) { + return Functions::NAN(); + } + + $daysBetweenSettlementAndMaturity = $maturity - $settlement; + $daysPerYear = Helpers::daysPerYear( + DateTimeExcel\DateParts::year($maturity), + FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL + ); + + if ($daysBetweenSettlementAndMaturity > $daysPerYear || $daysBetweenSettlementAndMaturity < 0) { + return Functions::NAN(); + } + + return (365 * $discount) / (360 - $discount * $daysBetweenSettlementAndMaturity); + } + + /** + * TBILLPRICE. + * + * Returns the price per $100 face value for a Treasury bill. + * + * @param mixed $settlement The Treasury bill's settlement date. + * The Treasury bill's settlement date is the date after the issue date + * when the Treasury bill is traded to the buyer. + * @param mixed $maturity The Treasury bill's maturity date. + * The maturity date is the date when the Treasury bill expires. + * @param mixed $discount The Treasury bill's discount rate + * + * @return float|string Result, or a string containing an error + */ + public static function price($settlement, $maturity, $discount) + { + $settlement = Functions::flattenSingleValue($settlement); + $maturity = Functions::flattenSingleValue($maturity); + $discount = Functions::flattenSingleValue($discount); + + try { + $settlement = FinancialValidations::validateSettlementDate($settlement); + $maturity = FinancialValidations::validateMaturityDate($maturity); + $discount = FinancialValidations::validateFloat($discount); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($discount <= 0) { + return Functions::NAN(); + } + + $daysBetweenSettlementAndMaturity = $maturity - $settlement; + $daysPerYear = Helpers::daysPerYear( + DateTimeExcel\DateParts::year($maturity), + FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL + ); + + if ($daysBetweenSettlementAndMaturity > $daysPerYear || $daysBetweenSettlementAndMaturity < 0) { + return Functions::NAN(); + } + + $price = 100 * (1 - (($discount * $daysBetweenSettlementAndMaturity) / 360)); + if ($price < 0.0) { + return Functions::NAN(); + } + + return $price; + } + + /** + * TBILLYIELD. + * + * Returns the yield for a Treasury bill. + * + * @param mixed $settlement The Treasury bill's settlement date. + * The Treasury bill's settlement date is the date after the issue date when + * the Treasury bill is traded to the buyer. + * @param mixed $maturity The Treasury bill's maturity date. + * The maturity date is the date when the Treasury bill expires. + * @param mixed $price The Treasury bill's price per $100 face value + * + * @return float|string + */ + public static function yield($settlement, $maturity, $price) + { + $settlement = Functions::flattenSingleValue($settlement); + $maturity = Functions::flattenSingleValue($maturity); + $price = Functions::flattenSingleValue($price); + + try { + $settlement = FinancialValidations::validateSettlementDate($settlement); + $maturity = FinancialValidations::validateMaturityDate($maturity); + $price = FinancialValidations::validatePrice($price); + } catch (Exception $e) { + return $e->getMessage(); + } + + $daysBetweenSettlementAndMaturity = $maturity - $settlement; + $daysPerYear = Helpers::daysPerYear( + DateTimeExcel\DateParts::year($maturity), + FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL + ); + + if ($daysBetweenSettlementAndMaturity > $daysPerYear || $daysBetweenSettlementAndMaturity < 0) { + return Functions::NAN(); + } + + return ((100 - $price) / $price) * (360 / $daysBetweenSettlementAndMaturity); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/FormulaParser.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/FormulaParser.php new file mode 100644 index 0000000..ddf45b2 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/FormulaParser.php @@ -0,0 +1,629 @@ +<'; + const OPERATORS_POSTFIX = '%'; + + /** + * Formula. + * + * @var string + */ + private $formula; + + /** + * Tokens. + * + * @var FormulaToken[] + */ + private $tokens = []; + + /** + * Create a new FormulaParser. + * + * @param string $formula Formula to parse + */ + public function __construct($formula = '') + { + // Check parameters + if ($formula === null) { + throw new Exception('Invalid parameter passed: formula'); + } + + // Initialise values + $this->formula = trim($formula); + // Parse! + $this->parseToTokens(); + } + + /** + * Get Formula. + * + * @return string + */ + public function getFormula() + { + return $this->formula; + } + + /** + * Get Token. + * + * @param int $id Token id + */ + public function getToken(int $id = 0): FormulaToken + { + if (isset($this->tokens[$id])) { + return $this->tokens[$id]; + } + + throw new Exception("Token with id $id does not exist."); + } + + /** + * Get Token count. + * + * @return int + */ + public function getTokenCount() + { + return count($this->tokens); + } + + /** + * Get Tokens. + * + * @return FormulaToken[] + */ + public function getTokens() + { + return $this->tokens; + } + + /** + * Parse to tokens. + */ + private function parseToTokens(): void + { + // No attempt is made to verify formulas; assumes formulas are derived from Excel, where + // they can only exist if valid; stack overflows/underflows sunk as nulls without exceptions. + + // Check if the formula has a valid starting = + $formulaLength = strlen($this->formula); + if ($formulaLength < 2 || $this->formula[0] != '=') { + return; + } + + // Helper variables + $tokens1 = $tokens2 = $stack = []; + $inString = $inPath = $inRange = $inError = false; + $token = $previousToken = $nextToken = null; + + $index = 1; + $value = ''; + + $ERRORS = ['#NULL!', '#DIV/0!', '#VALUE!', '#REF!', '#NAME?', '#NUM!', '#N/A']; + $COMPARATORS_MULTI = ['>=', '<=', '<>']; + + while ($index < $formulaLength) { + // state-dependent character evaluation (order is important) + + // double-quoted strings + // embeds are doubled + // end marks token + if ($inString) { + if ($this->formula[$index] == self::QUOTE_DOUBLE) { + if ((($index + 2) <= $formulaLength) && ($this->formula[$index + 1] == self::QUOTE_DOUBLE)) { + $value .= self::QUOTE_DOUBLE; + ++$index; + } else { + $inString = false; + $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND, FormulaToken::TOKEN_SUBTYPE_TEXT); + $value = ''; + } + } else { + $value .= $this->formula[$index]; + } + ++$index; + + continue; + } + + // single-quoted strings (links) + // embeds are double + // end does not mark a token + if ($inPath) { + if ($this->formula[$index] == self::QUOTE_SINGLE) { + if ((($index + 2) <= $formulaLength) && ($this->formula[$index + 1] == self::QUOTE_SINGLE)) { + $value .= self::QUOTE_SINGLE; + ++$index; + } else { + $inPath = false; + } + } else { + $value .= $this->formula[$index]; + } + ++$index; + + continue; + } + + // bracked strings (R1C1 range index or linked workbook name) + // no embeds (changed to "()" by Excel) + // end does not mark a token + if ($inRange) { + if ($this->formula[$index] == self::BRACKET_CLOSE) { + $inRange = false; + } + $value .= $this->formula[$index]; + ++$index; + + continue; + } + + // error values + // end marks a token, determined from absolute list of values + if ($inError) { + $value .= $this->formula[$index]; + ++$index; + if (in_array($value, $ERRORS)) { + $inError = false; + $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND, FormulaToken::TOKEN_SUBTYPE_ERROR); + $value = ''; + } + + continue; + } + + // scientific notation check + if (strpos(self::OPERATORS_SN, $this->formula[$index]) !== false) { + if (strlen($value) > 1) { + if (preg_match('/^[1-9]{1}(\\.\\d+)?E{1}$/', $this->formula[$index]) != 0) { + $value .= $this->formula[$index]; + ++$index; + + continue; + } + } + } + + // independent character evaluation (order not important) + + // establish state-dependent character evaluations + if ($this->formula[$index] == self::QUOTE_DOUBLE) { + if (strlen($value) > 0) { + // unexpected + $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_UNKNOWN); + $value = ''; + } + $inString = true; + ++$index; + + continue; + } + + if ($this->formula[$index] == self::QUOTE_SINGLE) { + if (strlen($value) > 0) { + // unexpected + $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_UNKNOWN); + $value = ''; + } + $inPath = true; + ++$index; + + continue; + } + + if ($this->formula[$index] == self::BRACKET_OPEN) { + $inRange = true; + $value .= self::BRACKET_OPEN; + ++$index; + + continue; + } + + if ($this->formula[$index] == self::ERROR_START) { + if (strlen($value) > 0) { + // unexpected + $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_UNKNOWN); + $value = ''; + } + $inError = true; + $value .= self::ERROR_START; + ++$index; + + continue; + } + + // mark start and end of arrays and array rows + if ($this->formula[$index] == self::BRACE_OPEN) { + if (strlen($value) > 0) { + // unexpected + $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_UNKNOWN); + $value = ''; + } + + $tmp = new FormulaToken('ARRAY', FormulaToken::TOKEN_TYPE_FUNCTION, FormulaToken::TOKEN_SUBTYPE_START); + $tokens1[] = $tmp; + $stack[] = clone $tmp; + + $tmp = new FormulaToken('ARRAYROW', FormulaToken::TOKEN_TYPE_FUNCTION, FormulaToken::TOKEN_SUBTYPE_START); + $tokens1[] = $tmp; + $stack[] = clone $tmp; + + ++$index; + + continue; + } + + if ($this->formula[$index] == self::SEMICOLON) { + if (strlen($value) > 0) { + $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND); + $value = ''; + } + + $tmp = array_pop($stack); + $tmp->setValue(''); + $tmp->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_STOP); + $tokens1[] = $tmp; + + $tmp = new FormulaToken(',', FormulaToken::TOKEN_TYPE_ARGUMENT); + $tokens1[] = $tmp; + + $tmp = new FormulaToken('ARRAYROW', FormulaToken::TOKEN_TYPE_FUNCTION, FormulaToken::TOKEN_SUBTYPE_START); + $tokens1[] = $tmp; + $stack[] = clone $tmp; + + ++$index; + + continue; + } + + if ($this->formula[$index] == self::BRACE_CLOSE) { + if (strlen($value) > 0) { + $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND); + $value = ''; + } + + $tmp = array_pop($stack); + $tmp->setValue(''); + $tmp->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_STOP); + $tokens1[] = $tmp; + + $tmp = array_pop($stack); + $tmp->setValue(''); + $tmp->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_STOP); + $tokens1[] = $tmp; + + ++$index; + + continue; + } + + // trim white-space + if ($this->formula[$index] == self::WHITESPACE) { + if (strlen($value) > 0) { + $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND); + $value = ''; + } + $tokens1[] = new FormulaToken('', FormulaToken::TOKEN_TYPE_WHITESPACE); + ++$index; + while (($this->formula[$index] == self::WHITESPACE) && ($index < $formulaLength)) { + ++$index; + } + + continue; + } + + // multi-character comparators + if (($index + 2) <= $formulaLength) { + if (in_array(substr($this->formula, $index, 2), $COMPARATORS_MULTI)) { + if (strlen($value) > 0) { + $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND); + $value = ''; + } + $tokens1[] = new FormulaToken(substr($this->formula, $index, 2), FormulaToken::TOKEN_TYPE_OPERATORINFIX, FormulaToken::TOKEN_SUBTYPE_LOGICAL); + $index += 2; + + continue; + } + } + + // standard infix operators + if (strpos(self::OPERATORS_INFIX, $this->formula[$index]) !== false) { + if (strlen($value) > 0) { + $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND); + $value = ''; + } + $tokens1[] = new FormulaToken($this->formula[$index], FormulaToken::TOKEN_TYPE_OPERATORINFIX); + ++$index; + + continue; + } + + // standard postfix operators (only one) + if (strpos(self::OPERATORS_POSTFIX, $this->formula[$index]) !== false) { + if (strlen($value) > 0) { + $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND); + $value = ''; + } + $tokens1[] = new FormulaToken($this->formula[$index], FormulaToken::TOKEN_TYPE_OPERATORPOSTFIX); + ++$index; + + continue; + } + + // start subexpression or function + if ($this->formula[$index] == self::PAREN_OPEN) { + if (strlen($value) > 0) { + $tmp = new FormulaToken($value, FormulaToken::TOKEN_TYPE_FUNCTION, FormulaToken::TOKEN_SUBTYPE_START); + $tokens1[] = $tmp; + $stack[] = clone $tmp; + $value = ''; + } else { + $tmp = new FormulaToken('', FormulaToken::TOKEN_TYPE_SUBEXPRESSION, FormulaToken::TOKEN_SUBTYPE_START); + $tokens1[] = $tmp; + $stack[] = clone $tmp; + } + ++$index; + + continue; + } + + // function, subexpression, or array parameters, or operand unions + if ($this->formula[$index] == self::COMMA) { + if (strlen($value) > 0) { + $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND); + $value = ''; + } + + $tmp = array_pop($stack); + $tmp->setValue(''); + $tmp->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_STOP); + $stack[] = $tmp; + + if ($tmp->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION) { + $tokens1[] = new FormulaToken(',', FormulaToken::TOKEN_TYPE_OPERATORINFIX, FormulaToken::TOKEN_SUBTYPE_UNION); + } else { + $tokens1[] = new FormulaToken(',', FormulaToken::TOKEN_TYPE_ARGUMENT); + } + ++$index; + + continue; + } + + // stop subexpression + if ($this->formula[$index] == self::PAREN_CLOSE) { + if (strlen($value) > 0) { + $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND); + $value = ''; + } + + $tmp = array_pop($stack); + $tmp->setValue(''); + $tmp->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_STOP); + $tokens1[] = $tmp; + + ++$index; + + continue; + } + + // token accumulation + $value .= $this->formula[$index]; + ++$index; + } + + // dump remaining accumulation + if (strlen($value) > 0) { + $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND); + } + + // move tokenList to new set, excluding unnecessary white-space tokens and converting necessary ones to intersections + $tokenCount = count($tokens1); + for ($i = 0; $i < $tokenCount; ++$i) { + $token = $tokens1[$i]; + if (isset($tokens1[$i - 1])) { + $previousToken = $tokens1[$i - 1]; + } else { + $previousToken = null; + } + if (isset($tokens1[$i + 1])) { + $nextToken = $tokens1[$i + 1]; + } else { + $nextToken = null; + } + + if ($token === null) { + continue; + } + + if ($token->getTokenType() != FormulaToken::TOKEN_TYPE_WHITESPACE) { + $tokens2[] = $token; + + continue; + } + + if ($previousToken === null) { + continue; + } + + if ( + !( + (($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION) && ($previousToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP)) || + (($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_SUBEXPRESSION) && ($previousToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP)) || + ($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_OPERAND) + ) + ) { + continue; + } + + if ($nextToken === null) { + continue; + } + + if ( + !( + (($nextToken->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION) && ($nextToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_START)) || + (($nextToken->getTokenType() == FormulaToken::TOKEN_TYPE_SUBEXPRESSION) && ($nextToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_START)) || + ($nextToken->getTokenType() == FormulaToken::TOKEN_TYPE_OPERAND) + ) + ) { + continue; + } + + $tokens2[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERATORINFIX, FormulaToken::TOKEN_SUBTYPE_INTERSECTION); + } + + // move tokens to final list, switching infix "-" operators to prefix when appropriate, switching infix "+" operators + // to noop when appropriate, identifying operand and infix-operator subtypes, and pulling "@" from function names + $this->tokens = []; + + $tokenCount = count($tokens2); + for ($i = 0; $i < $tokenCount; ++$i) { + $token = $tokens2[$i]; + if (isset($tokens2[$i - 1])) { + $previousToken = $tokens2[$i - 1]; + } else { + $previousToken = null; + } + if (isset($tokens2[$i + 1])) { + $nextToken = $tokens2[$i + 1]; + } else { + $nextToken = null; + } + + if ($token === null) { + continue; + } + + if ($token->getTokenType() == FormulaToken::TOKEN_TYPE_OPERATORINFIX && $token->getValue() == '-') { + if ($i == 0) { + $token->setTokenType(FormulaToken::TOKEN_TYPE_OPERATORPREFIX); + } elseif ( + (($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION) && + ($previousToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP)) || + (($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_SUBEXPRESSION) && + ($previousToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP)) || + ($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_OPERATORPOSTFIX) || + ($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_OPERAND) + ) { + $token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_MATH); + } else { + $token->setTokenType(FormulaToken::TOKEN_TYPE_OPERATORPREFIX); + } + + $this->tokens[] = $token; + + continue; + } + + if ($token->getTokenType() == FormulaToken::TOKEN_TYPE_OPERATORINFIX && $token->getValue() == '+') { + if ($i == 0) { + continue; + } elseif ( + (($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION) && + ($previousToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP)) || + (($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_SUBEXPRESSION) && + ($previousToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP)) || + ($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_OPERATORPOSTFIX) || + ($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_OPERAND) + ) { + $token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_MATH); + } else { + continue; + } + + $this->tokens[] = $token; + + continue; + } + + if ( + $token->getTokenType() == FormulaToken::TOKEN_TYPE_OPERATORINFIX && + $token->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_NOTHING + ) { + if (strpos('<>=', substr($token->getValue(), 0, 1)) !== false) { + $token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_LOGICAL); + } elseif ($token->getValue() == '&') { + $token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_CONCATENATION); + } else { + $token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_MATH); + } + + $this->tokens[] = $token; + + continue; + } + + if ( + $token->getTokenType() == FormulaToken::TOKEN_TYPE_OPERAND && + $token->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_NOTHING + ) { + if (!is_numeric($token->getValue())) { + if (strtoupper($token->getValue()) == 'TRUE' || strtoupper($token->getValue()) == 'FALSE') { + $token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_LOGICAL); + } else { + $token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_RANGE); + } + } else { + $token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_NUMBER); + } + + $this->tokens[] = $token; + + continue; + } + + if ($token->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION) { + if (strlen($token->getValue()) > 0) { + if (substr($token->getValue(), 0, 1) == '@') { + $token->setValue(substr($token->getValue(), 1)); + } + } + } + + $this->tokens[] = $token; + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/FormulaToken.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/FormulaToken.php new file mode 100644 index 0000000..68e5eea --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/FormulaToken.php @@ -0,0 +1,150 @@ +value = $value; + $this->tokenType = $tokenType; + $this->tokenSubType = $tokenSubType; + } + + /** + * Get Value. + * + * @return string + */ + public function getValue() + { + return $this->value; + } + + /** + * Set Value. + * + * @param string $value + */ + public function setValue($value): void + { + $this->value = $value; + } + + /** + * Get Token Type (represented by TOKEN_TYPE_*). + * + * @return string + */ + public function getTokenType() + { + return $this->tokenType; + } + + /** + * Set Token Type (represented by TOKEN_TYPE_*). + * + * @param string $value + */ + public function setTokenType($value): void + { + $this->tokenType = $value; + } + + /** + * Get Token SubType (represented by TOKEN_SUBTYPE_*). + * + * @return string + */ + public function getTokenSubType() + { + return $this->tokenSubType; + } + + /** + * Set Token SubType (represented by TOKEN_SUBTYPE_*). + * + * @param string $value + */ + public function setTokenSubType($value): void + { + $this->tokenSubType = $value; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Functions.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Functions.php new file mode 100644 index 0000000..75d4582 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Functions.php @@ -0,0 +1,711 @@ + '#NULL!', + 'divisionbyzero' => '#DIV/0!', + 'value' => '#VALUE!', + 'reference' => '#REF!', + 'name' => '#NAME?', + 'num' => '#NUM!', + 'na' => '#N/A', + 'gettingdata' => '#GETTING_DATA', + ]; + + /** + * Set the Compatibility Mode. + * + * @param string $compatibilityMode Compatibility Mode + * Permitted values are: + * Functions::COMPATIBILITY_EXCEL 'Excel' + * Functions::COMPATIBILITY_GNUMERIC 'Gnumeric' + * Functions::COMPATIBILITY_OPENOFFICE 'OpenOfficeCalc' + * + * @return bool (Success or Failure) + */ + public static function setCompatibilityMode($compatibilityMode) + { + if ( + ($compatibilityMode == self::COMPATIBILITY_EXCEL) || + ($compatibilityMode == self::COMPATIBILITY_GNUMERIC) || + ($compatibilityMode == self::COMPATIBILITY_OPENOFFICE) + ) { + self::$compatibilityMode = $compatibilityMode; + + return true; + } + + return false; + } + + /** + * Return the current Compatibility Mode. + * + * @return string Compatibility Mode + * Possible Return values are: + * Functions::COMPATIBILITY_EXCEL 'Excel' + * Functions::COMPATIBILITY_GNUMERIC 'Gnumeric' + * Functions::COMPATIBILITY_OPENOFFICE 'OpenOfficeCalc' + */ + public static function getCompatibilityMode() + { + return self::$compatibilityMode; + } + + /** + * Set the Return Date Format used by functions that return a date/time (Excel, PHP Serialized Numeric or PHP Object). + * + * @param string $returnDateType Return Date Format + * Permitted values are: + * Functions::RETURNDATE_UNIX_TIMESTAMP 'P' + * Functions::RETURNDATE_PHP_DATETIME_OBJECT 'O' + * Functions::RETURNDATE_EXCEL 'E' + * + * @return bool Success or failure + */ + public static function setReturnDateType($returnDateType) + { + if ( + ($returnDateType == self::RETURNDATE_UNIX_TIMESTAMP) || + ($returnDateType == self::RETURNDATE_PHP_DATETIME_OBJECT) || + ($returnDateType == self::RETURNDATE_EXCEL) + ) { + self::$returnDateType = $returnDateType; + + return true; + } + + return false; + } + + /** + * Return the current Return Date Format for functions that return a date/time (Excel, PHP Serialized Numeric or PHP Object). + * + * @return string Return Date Format + * Possible Return values are: + * Functions::RETURNDATE_UNIX_TIMESTAMP 'P' + * Functions::RETURNDATE_PHP_DATETIME_OBJECT 'O' + * Functions::RETURNDATE_EXCEL 'E' + */ + public static function getReturnDateType() + { + return self::$returnDateType; + } + + /** + * DUMMY. + * + * @return string #Not Yet Implemented + */ + public static function DUMMY() + { + return '#Not Yet Implemented'; + } + + /** + * DIV0. + * + * @return string #Not Yet Implemented + */ + public static function DIV0() + { + return self::$errorCodes['divisionbyzero']; + } + + /** + * NA. + * + * Excel Function: + * =NA() + * + * Returns the error value #N/A + * #N/A is the error value that means "no value is available." + * + * @return string #N/A! + */ + public static function NA() + { + return self::$errorCodes['na']; + } + + /** + * NaN. + * + * Returns the error value #NUM! + * + * @return string #NUM! + */ + public static function NAN() + { + return self::$errorCodes['num']; + } + + /** + * NAME. + * + * Returns the error value #NAME? + * + * @return string #NAME? + */ + public static function NAME() + { + return self::$errorCodes['name']; + } + + /** + * REF. + * + * Returns the error value #REF! + * + * @return string #REF! + */ + public static function REF() + { + return self::$errorCodes['reference']; + } + + /** + * NULL. + * + * Returns the error value #NULL! + * + * @return string #NULL! + */ + public static function null() + { + return self::$errorCodes['null']; + } + + /** + * VALUE. + * + * Returns the error value #VALUE! + * + * @return string #VALUE! + */ + public static function VALUE() + { + return self::$errorCodes['value']; + } + + public static function isMatrixValue($idx) + { + return (substr_count($idx, '.') <= 1) || (preg_match('/\.[A-Z]/', $idx) > 0); + } + + public static function isValue($idx) + { + return substr_count($idx, '.') == 0; + } + + public static function isCellValue($idx) + { + return substr_count($idx, '.') > 1; + } + + public static function ifCondition($condition) + { + $condition = self::flattenSingleValue($condition); + + if ($condition === '') { + return '=""'; + } + if (!is_string($condition) || !in_array($condition[0], ['>', '<', '='])) { + $condition = self::operandSpecialHandling($condition); + if (is_bool($condition)) { + return '=' . ($condition ? 'TRUE' : 'FALSE'); + } elseif (!is_numeric($condition)) { + $condition = Calculation::wrapResult(strtoupper($condition)); + } + + return str_replace('""""', '""', '=' . $condition); + } + preg_match('/(=|<[>=]?|>=?)(.*)/', $condition, $matches); + [, $operator, $operand] = $matches; + + $operand = self::operandSpecialHandling($operand); + if (is_numeric(trim($operand, '"'))) { + $operand = trim($operand, '"'); + } elseif (!is_numeric($operand) && $operand !== 'FALSE' && $operand !== 'TRUE') { + $operand = str_replace('"', '""', $operand); + $operand = Calculation::wrapResult(strtoupper($operand)); + } + + return str_replace('""""', '""', $operator . $operand); + } + + private static function operandSpecialHandling($operand) + { + if (is_numeric($operand) || is_bool($operand)) { + return $operand; + } elseif (strtoupper($operand) === Calculation::getTRUE() || strtoupper($operand) === Calculation::getFALSE()) { + return strtoupper($operand); + } + + // Check for percentage + if (preg_match('/^\-?\d*\.?\d*\s?\%$/', $operand)) { + return ((float) rtrim($operand, '%')) / 100; + } + + // Check for dates + if (($dateValueOperand = Date::stringToExcel($operand)) !== false) { + return $dateValueOperand; + } + + return $operand; + } + + /** + * ERROR_TYPE. + * + * @param mixed $value Value to check + * + * @return int|string + */ + public static function errorType($value = '') + { + $value = self::flattenSingleValue($value); + + $i = 1; + foreach (self::$errorCodes as $errorCode) { + if ($value === $errorCode) { + return $i; + } + ++$i; + } + + return self::NA(); + } + + /** + * IS_BLANK. + * + * @param mixed $value Value to check + * + * @return bool + */ + public static function isBlank($value = null) + { + if ($value !== null) { + $value = self::flattenSingleValue($value); + } + + return $value === null; + } + + /** + * IS_ERR. + * + * @param mixed $value Value to check + * + * @return bool + */ + public static function isErr($value = '') + { + $value = self::flattenSingleValue($value); + + return self::isError($value) && (!self::isNa(($value))); + } + + /** + * IS_ERROR. + * + * @param mixed $value Value to check + * + * @return bool + */ + public static function isError($value = '') + { + $value = self::flattenSingleValue($value); + + if (!is_string($value)) { + return false; + } + + return in_array($value, self::$errorCodes); + } + + /** + * IS_NA. + * + * @param mixed $value Value to check + * + * @return bool + */ + public static function isNa($value = '') + { + $value = self::flattenSingleValue($value); + + return $value === self::NA(); + } + + /** + * IS_EVEN. + * + * @param mixed $value Value to check + * + * @return bool|string + */ + public static function isEven($value = null) + { + $value = self::flattenSingleValue($value); + + if ($value === null) { + return self::NAME(); + } elseif ((is_bool($value)) || ((is_string($value)) && (!is_numeric($value)))) { + return self::VALUE(); + } + + return $value % 2 == 0; + } + + /** + * IS_ODD. + * + * @param mixed $value Value to check + * + * @return bool|string + */ + public static function isOdd($value = null) + { + $value = self::flattenSingleValue($value); + + if ($value === null) { + return self::NAME(); + } elseif ((is_bool($value)) || ((is_string($value)) && (!is_numeric($value)))) { + return self::VALUE(); + } + + return abs($value) % 2 == 1; + } + + /** + * IS_NUMBER. + * + * @param mixed $value Value to check + * + * @return bool + */ + public static function isNumber($value = null) + { + $value = self::flattenSingleValue($value); + + if (is_string($value)) { + return false; + } + + return is_numeric($value); + } + + /** + * IS_LOGICAL. + * + * @param mixed $value Value to check + * + * @return bool + */ + public static function isLogical($value = null) + { + $value = self::flattenSingleValue($value); + + return is_bool($value); + } + + /** + * IS_TEXT. + * + * @param mixed $value Value to check + * + * @return bool + */ + public static function isText($value = null) + { + $value = self::flattenSingleValue($value); + + return is_string($value) && !self::isError($value); + } + + /** + * IS_NONTEXT. + * + * @param mixed $value Value to check + * + * @return bool + */ + public static function isNonText($value = null) + { + return !self::isText($value); + } + + /** + * N. + * + * Returns a value converted to a number + * + * @param null|mixed $value The value you want converted + * + * @return number N converts values listed in the following table + * If value is or refers to N returns + * A number That number + * A date The serial number of that date + * TRUE 1 + * FALSE 0 + * An error value The error value + * Anything else 0 + */ + public static function n($value = null) + { + while (is_array($value)) { + $value = array_shift($value); + } + + switch (gettype($value)) { + case 'double': + case 'float': + case 'integer': + return $value; + case 'boolean': + return (int) $value; + case 'string': + // Errors + if ((strlen($value) > 0) && ($value[0] == '#')) { + return $value; + } + + break; + } + + return 0; + } + + /** + * TYPE. + * + * Returns a number that identifies the type of a value + * + * @param null|mixed $value The value you want tested + * + * @return number N converts values listed in the following table + * If value is or refers to N returns + * A number 1 + * Text 2 + * Logical Value 4 + * An error value 16 + * Array or Matrix 64 + */ + public static function TYPE($value = null) + { + $value = self::flattenArrayIndexed($value); + if (is_array($value) && (count($value) > 1)) { + end($value); + $a = key($value); + // Range of cells is an error + if (self::isCellValue($a)) { + return 16; + // Test for Matrix + } elseif (self::isMatrixValue($a)) { + return 64; + } + } elseif (empty($value)) { + // Empty Cell + return 1; + } + $value = self::flattenSingleValue($value); + + if (($value === null) || (is_float($value)) || (is_int($value))) { + return 1; + } elseif (is_bool($value)) { + return 4; + } elseif (is_array($value)) { + return 64; + } elseif (is_string($value)) { + // Errors + if ((strlen($value) > 0) && ($value[0] == '#')) { + return 16; + } + + return 2; + } + + return 0; + } + + /** + * Convert a multi-dimensional array to a simple 1-dimensional array. + * + * @param array|mixed $array Array to be flattened + * + * @return array Flattened array + */ + public static function flattenArray($array) + { + if (!is_array($array)) { + return (array) $array; + } + + $arrayValues = []; + foreach ($array as $value) { + if (is_array($value)) { + foreach ($value as $val) { + if (is_array($val)) { + foreach ($val as $v) { + $arrayValues[] = $v; + } + } else { + $arrayValues[] = $val; + } + } + } else { + $arrayValues[] = $value; + } + } + + return $arrayValues; + } + + /** + * Convert a multi-dimensional array to a simple 1-dimensional array, but retain an element of indexing. + * + * @param array|mixed $array Array to be flattened + * + * @return array Flattened array + */ + public static function flattenArrayIndexed($array) + { + if (!is_array($array)) { + return (array) $array; + } + + $arrayValues = []; + foreach ($array as $k1 => $value) { + if (is_array($value)) { + foreach ($value as $k2 => $val) { + if (is_array($val)) { + foreach ($val as $k3 => $v) { + $arrayValues[$k1 . '.' . $k2 . '.' . $k3] = $v; + } + } else { + $arrayValues[$k1 . '.' . $k2] = $val; + } + } + } else { + $arrayValues[$k1] = $value; + } + } + + return $arrayValues; + } + + /** + * Convert an array to a single scalar value by extracting the first element. + * + * @param mixed $value Array or scalar value + * + * @return mixed + */ + public static function flattenSingleValue($value = '') + { + while (is_array($value)) { + $value = array_shift($value); + } + + return $value; + } + + /** + * ISFORMULA. + * + * @param mixed $cellReference The cell to check + * @param ?Cell $cell The current cell (containing this formula) + * + * @return bool|string + */ + public static function isFormula($cellReference = '', ?Cell $cell = null) + { + if ($cell === null) { + return self::REF(); + } + $cellReference = self::expandDefinedName((string) $cellReference, $cell); + $cellReference = self::trimTrailingRange($cellReference); + + preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $cellReference, $matches); + + $cellReference = $matches[6] . $matches[7]; + $worksheetName = str_replace("''", "'", trim($matches[2], "'")); + + $worksheet = (!empty($worksheetName)) + ? $cell->getWorksheet()->getParent()->getSheetByName($worksheetName) + : $cell->getWorksheet(); + + return $worksheet->getCell($cellReference)->isFormula(); + } + + public static function expandDefinedName(string $coordinate, Cell $cell): string + { + $worksheet = $cell->getWorksheet(); + $spreadsheet = $worksheet->getParent(); + // Uppercase coordinate + $pCoordinatex = strtoupper($coordinate); + // Eliminate leading equal sign + $pCoordinatex = Worksheet::pregReplace('/^=/', '', $pCoordinatex); + $defined = $spreadsheet->getDefinedName($pCoordinatex, $worksheet); + if ($defined !== null) { + $worksheet2 = $defined->getWorkSheet(); + if (!$defined->isFormula() && $worksheet2 !== null) { + $coordinate = "'" . $worksheet2->getTitle() . "'!" . Worksheet::pregReplace('/^=/', '', $defined->getValue()); + } + } + + return $coordinate; + } + + public static function trimTrailingRange(string $coordinate): string + { + return Worksheet::pregReplace('/:[\\w\$]+$/', '', $coordinate); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Internal/MakeMatrix.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Internal/MakeMatrix.php new file mode 100644 index 0000000..8b53464 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Internal/MakeMatrix.php @@ -0,0 +1,11 @@ + 0) { + $targetValue = Functions::flattenSingleValue($arguments[0]); + $argc = count($arguments) - 1; + $switchCount = floor($argc / 2); + $hasDefaultClause = $argc % 2 !== 0; + $defaultClause = $argc % 2 === 0 ? null : $arguments[$argc]; + + $switchSatisfied = false; + if ($switchCount > 0) { + for ($index = 0; $index < $switchCount; ++$index) { + if ($targetValue == $arguments[$index * 2 + 1]) { + $result = $arguments[$index * 2 + 2]; + $switchSatisfied = true; + + break; + } + } + } + + if ($switchSatisfied !== true) { + $result = $hasDefaultClause ? $defaultClause : Functions::NA(); + } + } + + return $result; + } + + /** + * IFERROR. + * + * Excel Function: + * =IFERROR(testValue,errorpart) + * + * @param mixed $testValue Value to check, is also the value returned when no error + * @param mixed $errorpart Value to return when testValue is an error condition + * + * @return mixed The value of errorpart or testValue determined by error condition + */ + public static function IFERROR($testValue = '', $errorpart = '') + { + $testValue = ($testValue === null) ? '' : Functions::flattenSingleValue($testValue); + $errorpart = ($errorpart === null) ? '' : Functions::flattenSingleValue($errorpart); + + return self::statementIf(Functions::isError($testValue), $errorpart, $testValue); + } + + /** + * IFNA. + * + * Excel Function: + * =IFNA(testValue,napart) + * + * @param mixed $testValue Value to check, is also the value returned when not an NA + * @param mixed $napart Value to return when testValue is an NA condition + * + * @return mixed The value of errorpart or testValue determined by error condition + */ + public static function IFNA($testValue = '', $napart = '') + { + $testValue = ($testValue === null) ? '' : Functions::flattenSingleValue($testValue); + $napart = ($napart === null) ? '' : Functions::flattenSingleValue($napart); + + return self::statementIf(Functions::isNa($testValue), $napart, $testValue); + } + + /** + * IFS. + * + * Excel Function: + * =IFS(testValue1;returnIfTrue1;testValue2;returnIfTrue2;...;testValue_n;returnIfTrue_n) + * + * testValue1 ... testValue_n + * Conditions to Evaluate + * returnIfTrue1 ... returnIfTrue_n + * Value returned if corresponding testValue (nth) was true + * + * @param mixed ...$arguments Statement arguments + * + * @return mixed|string The value of returnIfTrue_n, if testValue_n was true. #N/A if none of testValues was true + */ + public static function IFS(...$arguments) + { + $argumentCount = count($arguments); + + if ($argumentCount % 2 != 0) { + return Functions::NA(); + } + // We use instance of Exception as a falseValue in order to prevent string collision with value in cell + $falseValueException = new Exception(); + for ($i = 0; $i < $argumentCount; $i += 2) { + $testValue = ($arguments[$i] === null) ? '' : Functions::flattenSingleValue($arguments[$i]); + $returnIfTrue = ($arguments[$i + 1] === null) ? '' : Functions::flattenSingleValue($arguments[$i + 1]); + $result = self::statementIf($testValue, $returnIfTrue, $falseValueException); + + if ($result !== $falseValueException) { + return $result; + } + } + + return Functions::NA(); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Logical/Operations.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Logical/Operations.php new file mode 100644 index 0000000..6bfb6a5 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Logical/Operations.php @@ -0,0 +1,198 @@ + 0) && ($returnValue == $argCount); + } + + /** + * LOGICAL_OR. + * + * Returns boolean TRUE if any argument is TRUE; returns FALSE if all arguments are FALSE. + * + * Excel Function: + * =OR(logical1[,logical2[, ...]]) + * + * The arguments must evaluate to logical values such as TRUE or FALSE, or the arguments must be arrays + * or references that contain logical values. + * + * Boolean arguments are treated as True or False as appropriate + * Integer or floating point arguments are treated as True, except for 0 or 0.0 which are False + * If any argument value is a string, or a Null, the function returns a #VALUE! error, unless the string + * holds the value TRUE or FALSE, in which case it is evaluated as the corresponding boolean value + * + * @param mixed $args Data values + * + * @return bool|string the logical OR of the arguments + */ + public static function logicalOr(...$args) + { + $args = Functions::flattenArray($args); + + if (count($args) == 0) { + return Functions::VALUE(); + } + + $args = array_filter($args, function ($value) { + return $value !== null || (is_string($value) && trim($value) == ''); + }); + + $returnValue = self::countTrueValues($args); + if (is_string($returnValue)) { + return $returnValue; + } + + return $returnValue > 0; + } + + /** + * LOGICAL_XOR. + * + * Returns the Exclusive Or logical operation for one or more supplied conditions. + * i.e. the Xor function returns TRUE if an odd number of the supplied conditions evaluate to TRUE, + * and FALSE otherwise. + * + * Excel Function: + * =XOR(logical1[,logical2[, ...]]) + * + * The arguments must evaluate to logical values such as TRUE or FALSE, or the arguments must be arrays + * or references that contain logical values. + * + * Boolean arguments are treated as True or False as appropriate + * Integer or floating point arguments are treated as True, except for 0 or 0.0 which are False + * If any argument value is a string, or a Null, the function returns a #VALUE! error, unless the string + * holds the value TRUE or FALSE, in which case it is evaluated as the corresponding boolean value + * + * @param mixed $args Data values + * + * @return bool|string the logical XOR of the arguments + */ + public static function logicalXor(...$args) + { + $args = Functions::flattenArray($args); + + if (count($args) == 0) { + return Functions::VALUE(); + } + + $args = array_filter($args, function ($value) { + return $value !== null || (is_string($value) && trim($value) == ''); + }); + + $returnValue = self::countTrueValues($args); + if (is_string($returnValue)) { + return $returnValue; + } + + return $returnValue % 2 == 1; + } + + /** + * NOT. + * + * Returns the boolean inverse of the argument. + * + * Excel Function: + * =NOT(logical) + * + * The argument must evaluate to a logical value such as TRUE or FALSE + * + * Boolean arguments are treated as True or False as appropriate + * Integer or floating point arguments are treated as True, except for 0 or 0.0 which are False + * If any argument value is a string, or a Null, the function returns a #VALUE! error, unless the string + * holds the value TRUE or FALSE, in which case it is evaluated as the corresponding boolean value + * + * @param mixed $logical A value or expression that can be evaluated to TRUE or FALSE + * + * @return bool|string the boolean inverse of the argument + */ + public static function NOT($logical = false) + { + $logical = Functions::flattenSingleValue($logical); + + if (is_string($logical)) { + $logical = mb_strtoupper($logical, 'UTF-8'); + if (($logical == 'TRUE') || ($logical == Calculation::getTRUE())) { + return false; + } elseif (($logical == 'FALSE') || ($logical == Calculation::getFALSE())) { + return true; + } + + return Functions::VALUE(); + } + + return !$logical; + } + + /** + * @return int|string + */ + private static function countTrueValues(array $args) + { + $trueValueCount = 0; + + foreach ($args as $arg) { + // Is it a boolean value? + if (is_bool($arg)) { + $trueValueCount += $arg; + } elseif ((is_numeric($arg)) && (!is_string($arg))) { + $trueValueCount += ((int) $arg != 0); + } elseif (is_string($arg)) { + $arg = mb_strtoupper($arg, 'UTF-8'); + if (($arg == 'TRUE') || ($arg == Calculation::getTRUE())) { + $arg = true; + } elseif (($arg == 'FALSE') || ($arg == Calculation::getFALSE())) { + $arg = false; + } else { + return Functions::VALUE(); + } + $trueValueCount += ($arg != 0); + } + } + + return $trueValueCount; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef.php new file mode 100644 index 0000000..6765048 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef.php @@ -0,0 +1,416 @@ + '') { + if (strpos($sheetName, ' ') !== false || strpos($sheetName, '[') !== false) { + $sheetName = "'{$sheetName}'"; + } + $sheetName .= '!'; + } + + return $sheetName; + } + + private static function formatAsA1(int $row, int $column, int $relativity, string $sheetName): string + { + $rowRelative = $columnRelative = '$'; + if (($relativity == self::ADDRESS_COLUMN_RELATIVE) || ($relativity == self::ADDRESS_RELATIVE)) { + $columnRelative = ''; + } + if (($relativity == self::ADDRESS_ROW_RELATIVE) || ($relativity == self::ADDRESS_RELATIVE)) { + $rowRelative = ''; + } + $column = Coordinate::stringFromColumnIndex($column); + + return "{$sheetName}{$columnRelative}{$column}{$rowRelative}{$row}"; + } + + private static function formatAsR1C1(int $row, int $column, int $relativity, string $sheetName): string + { + if (($relativity == self::ADDRESS_COLUMN_RELATIVE) || ($relativity == self::ADDRESS_RELATIVE)) { + $column = "[{$column}]"; + } + if (($relativity == self::ADDRESS_ROW_RELATIVE) || ($relativity == self::ADDRESS_RELATIVE)) { + $row = "[{$row}]"; + } + + return "{$sheetName}R{$row}C{$column}"; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php new file mode 100644 index 0000000..71358bf --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php @@ -0,0 +1,198 @@ +getMessage(); + } + + // MATCH() is not case sensitive, so we convert lookup value to be lower cased if it's a string type. + if (is_string($lookupValue)) { + $lookupValue = StringHelper::strToLower($lookupValue); + } + + $valueKey = null; + switch ($matchType) { + case self::MATCHTYPE_LARGEST_VALUE: + $valueKey = self::matchLargestValue($lookupArray, $lookupValue, $keySet); + + break; + case self::MATCHTYPE_FIRST_VALUE: + $valueKey = self::matchFirstValue($lookupArray, $lookupValue); + + break; + case self::MATCHTYPE_SMALLEST_VALUE: + default: + $valueKey = self::matchSmallestValue($lookupArray, $lookupValue); + } + + if ($valueKey !== null) { + return ++$valueKey; + } + + // Unsuccessful in finding a match, return #N/A error value + return Functions::NA(); + } + + private static function matchFirstValue($lookupArray, $lookupValue) + { + $wildcardLookup = ((bool) preg_match('/([\?\*])/', $lookupValue)); + $wildcard = WildcardMatch::wildcard($lookupValue); + + foreach ($lookupArray as $i => $lookupArrayValue) { + $typeMatch = ((gettype($lookupValue) === gettype($lookupArrayValue)) || + (is_numeric($lookupValue) && is_numeric($lookupArrayValue))); + + if ( + $typeMatch && is_string($lookupValue) && + $wildcardLookup && WildcardMatch::compare($lookupArrayValue, $wildcard) + ) { + // wildcard match + return $i; + } elseif ($lookupArrayValue === $lookupValue) { + // exact match + return $i; + } + } + + return null; + } + + private static function matchLargestValue($lookupArray, $lookupValue, $keySet) + { + foreach ($lookupArray as $i => $lookupArrayValue) { + $typeMatch = ((gettype($lookupValue) === gettype($lookupArrayValue)) || + (is_numeric($lookupValue) && is_numeric($lookupArrayValue))); + + if ($typeMatch && ($lookupArrayValue <= $lookupValue)) { + return array_search($i, $keySet); + } + } + + return null; + } + + private static function matchSmallestValue($lookupArray, $lookupValue) + { + $valueKey = null; + + // The basic algorithm is: + // Iterate and keep the highest match until the next element is smaller than the searched value. + // Return immediately if perfect match is found + foreach ($lookupArray as $i => $lookupArrayValue) { + $typeMatch = gettype($lookupValue) === gettype($lookupArrayValue); + + if ($lookupArrayValue === $lookupValue) { + // Another "special" case. If a perfect match is found, + // the algorithm gives up immediately + return $i; + } elseif ($typeMatch && $lookupArrayValue >= $lookupValue) { + $valueKey = $i; + } elseif ($typeMatch && $lookupArrayValue < $lookupValue) { + //Excel algorithm gives up immediately if the first element is smaller than the searched value + break; + } + } + + return $valueKey; + } + + private static function validateLookupValue($lookupValue): void + { + // Lookup_value type has to be number, text, or logical values + if ((!is_numeric($lookupValue)) && (!is_string($lookupValue)) && (!is_bool($lookupValue))) { + throw new Exception(Functions::NA()); + } + } + + private static function validateMatchType($matchType): void + { + // Match_type is 0, 1 or -1 + if ( + ($matchType !== self::MATCHTYPE_FIRST_VALUE) && + ($matchType !== self::MATCHTYPE_LARGEST_VALUE) && ($matchType !== self::MATCHTYPE_SMALLEST_VALUE) + ) { + throw new Exception(Functions::NA()); + } + } + + private static function validateLookupArray($lookupArray): void + { + // Lookup_array should not be empty + $lookupArraySize = count($lookupArray); + if ($lookupArraySize <= 0) { + throw new Exception(Functions::NA()); + } + } + + private static function prepareLookupArray($lookupArray, $matchType) + { + // Lookup_array should contain only number, text, or logical values, or empty (null) cells + foreach ($lookupArray as $i => $value) { + // check the type of the value + if ((!is_numeric($value)) && (!is_string($value)) && (!is_bool($value)) && ($value !== null)) { + throw new Exception(Functions::NA()); + } + // Convert strings to lowercase for case-insensitive testing + if (is_string($value)) { + $lookupArray[$i] = StringHelper::strToLower($value); + } + if ( + ($value === null) && + (($matchType == self::MATCHTYPE_LARGEST_VALUE) || ($matchType == self::MATCHTYPE_SMALLEST_VALUE)) + ) { + unset($lookupArray[$i]); + } + } + + return $lookupArray; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Formula.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Formula.php new file mode 100644 index 0000000..f3e6c41 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Formula.php @@ -0,0 +1,43 @@ +getWorksheet()->getParent()->getSheetByName($worksheetName) + : $cell->getWorksheet(); + + if ( + $worksheet === null || + !$worksheet->cellExists($cellReference) || + !$worksheet->getCell($cellReference)->isFormula() + ) { + return Functions::NA(); + } + + return $worksheet->getCell($cellReference)->getValue(); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/HLookup.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/HLookup.php new file mode 100644 index 0000000..7db2780 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/HLookup.php @@ -0,0 +1,116 @@ +getMessage(); + } + + $f = array_keys($lookupArray); + $firstRow = reset($f); + if ((!is_array($lookupArray[$firstRow])) || ($indexNumber > count($lookupArray))) { + return Functions::REF(); + } + + $firstkey = $f[0] - 1; + $returnColumn = $firstkey + $indexNumber; + $firstColumn = array_shift($f); + $rowNumber = self::hLookupSearch($lookupValue, $lookupArray, $firstColumn, $notExactMatch); + + if ($rowNumber !== null) { + // otherwise return the appropriate value + return $lookupArray[$returnColumn][Coordinate::stringFromColumnIndex($rowNumber)]; + } + + return Functions::NA(); + } + + /** + * @param mixed $lookupValue The value that you want to match in lookup_array + * @param mixed $column The column to look up + * @param mixed $notExactMatch determines if you are looking for an exact match based on lookup_value + */ + private static function hLookupSearch($lookupValue, array $lookupArray, $column, $notExactMatch): ?int + { + $lookupLower = StringHelper::strToLower($lookupValue); + + $rowNumber = null; + foreach ($lookupArray[$column] as $rowKey => $rowData) { + // break if we have passed possible keys + $bothNumeric = is_numeric($lookupValue) && is_numeric($rowData); + $bothNotNumeric = !is_numeric($lookupValue) && !is_numeric($rowData); + $cellDataLower = StringHelper::strToLower($rowData); + + if ( + $notExactMatch && + (($bothNumeric && $rowData > $lookupValue) || ($bothNotNumeric && $cellDataLower > $lookupLower)) + ) { + break; + } + + $rowNumber = self::checkMatch( + $bothNumeric, + $bothNotNumeric, + $notExactMatch, + Coordinate::columnIndexFromString($rowKey), + $cellDataLower, + $lookupLower, + $rowNumber + ); + } + + return $rowNumber; + } + + private static function convertLiteralArray(array $lookupArray): array + { + if (array_key_exists(0, $lookupArray)) { + $lookupArray2 = []; + $row = 0; + foreach ($lookupArray as $arrayVal) { + ++$row; + if (!is_array($arrayVal)) { + $arrayVal = [$arrayVal]; + } + $arrayVal2 = []; + foreach ($arrayVal as $key2 => $val2) { + $index = Coordinate::stringFromColumnIndex($key2 + 1); + $arrayVal2[$index] = $val2; + } + $lookupArray2[$row] = $arrayVal2; + } + $lookupArray = $lookupArray2; + } + + return $lookupArray; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Helpers.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Helpers.php new file mode 100644 index 0000000..28e8df8 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Helpers.php @@ -0,0 +1,74 @@ +getWorkSheet(); + $sheetTitle = ($workSheet === null) ? '' : $workSheet->getTitle(); + $value = preg_replace('/^=/', '', $namedRange->getValue()); + self::adjustSheetTitle($sheetTitle, $value); + $cellAddress1 = $sheetTitle . $value; + $cellAddress = $cellAddress1; + $a1 = self::CELLADDRESS_USE_A1; + } + if (strpos($cellAddress, ':') !== false) { + [$cellAddress1, $cellAddress2] = explode(':', $cellAddress); + } + $cellAddress = self::convertR1C1($cellAddress1, $cellAddress2, $a1); + + return [$cellAddress1, $cellAddress2, $cellAddress]; + } + + public static function extractWorksheet(string $cellAddress, Cell $cell): array + { + $sheetName = ''; + if (strpos($cellAddress, '!') !== false) { + [$sheetName, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true); + $sheetName = trim($sheetName, "'"); + } + + $worksheet = ($sheetName !== '') + ? $cell->getWorksheet()->getParent()->getSheetByName($sheetName) + : $cell->getWorksheet(); + + return [$cellAddress, $worksheet, $sheetName]; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Hyperlink.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Hyperlink.php new file mode 100644 index 0000000..1448c08 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Hyperlink.php @@ -0,0 +1,40 @@ +getHyperlink()->setUrl($linkURL); + $cell->getHyperlink()->setTooltip($displayName); + + return $displayName; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Indirect.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Indirect.php new file mode 100644 index 0000000..b5a3541 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Indirect.php @@ -0,0 +1,97 @@ +getMessage(); + } + + [$cellAddress, $worksheet, $sheetName] = Helpers::extractWorksheet($cellAddress, $cell); + + [$cellAddress1, $cellAddress2, $cellAddress] = Helpers::extractCellAddresses($cellAddress, $a1, $cell->getWorkSheet(), $sheetName); + + if ( + (!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $cellAddress1, $matches)) || + (($cellAddress2 !== null) && (!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $cellAddress2, $matches))) + ) { + return Functions::REF(); + } + + return self::extractRequiredCells($worksheet, $cellAddress); + } + + /** + * Extract range values. + * + * @return mixed Array of values in range if range contains more than one element. + * Otherwise, a single value is returned. + */ + private static function extractRequiredCells(?Worksheet $worksheet, string $cellAddress) + { + return Calculation::getInstance($worksheet !== null ? $worksheet->getParent() : null) + ->extractCellRange($cellAddress, $worksheet, false); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Lookup.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Lookup.php new file mode 100644 index 0000000..e21d35d --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Lookup.php @@ -0,0 +1,105 @@ + 1) || (!$hasResultVector && $lookupRows === 2 && $lookupColumns !== 2)) { + $lookupVector = LookupRef\Matrix::transpose($lookupVector); + $lookupRows = self::rowCount($lookupVector); + $lookupColumns = self::columnCount($lookupVector); + } + + $resultVector = self::verifyResultVector($lookupVector, $resultVector); + + if ($lookupRows === 2 && !$hasResultVector) { + $resultVector = array_pop($lookupVector); + $lookupVector = array_shift($lookupVector); + } + + if ($lookupColumns !== 2) { + $lookupVector = self::verifyLookupValues($lookupVector, $resultVector); + } + + return VLookup::lookup($lookupValue, $lookupVector, 2); + } + + private static function verifyLookupValues(array $lookupVector, array $resultVector): array + { + foreach ($lookupVector as &$value) { + if (is_array($value)) { + $k = array_keys($value); + $key1 = $key2 = array_shift($k); + ++$key2; + $dataValue1 = $value[$key1]; + } else { + $key1 = 0; + $key2 = 1; + $dataValue1 = $value; + } + + $dataValue2 = array_shift($resultVector); + if (is_array($dataValue2)) { + $dataValue2 = array_shift($dataValue2); + } + $value = [$key1 => $dataValue1, $key2 => $dataValue2]; + } + unset($value); + + return $lookupVector; + } + + private static function verifyResultVector(array $lookupVector, $resultVector) + { + if ($resultVector === null) { + $resultVector = $lookupVector; + } + + $resultRows = self::rowCount($resultVector); + $resultColumns = self::columnCount($resultVector); + + // we correctly orient our results + if ($resultRows === 1 && $resultColumns > 1) { + $resultVector = LookupRef\Matrix::transpose($resultVector); + } + + return $resultVector; + } + + private static function rowCount(array $dataArray): int + { + return count($dataArray); + } + + private static function columnCount(array $dataArray): int + { + $rowKeys = array_keys($dataArray); + $row = array_shift($rowKeys); + + return count($dataArray[$row]); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/LookupBase.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/LookupBase.php new file mode 100644 index 0000000..80fc99a --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/LookupBase.php @@ -0,0 +1,48 @@ +getMessage(); + } + + if (!is_array($matrix) || ($rowNum > count($matrix))) { + return Functions::REF(); + } + + $rowKeys = array_keys($matrix); + $columnKeys = @array_keys($matrix[$rowKeys[0]]); + + if ($columnNum > count($columnKeys)) { + return Functions::REF(); + } + + if ($columnNum === 0) { + return self::extractRowValue($matrix, $rowKeys, $rowNum); + } + + $columnNum = $columnKeys[--$columnNum]; + if ($rowNum === 0) { + return array_map( + function ($value) { + return [$value]; + }, + array_column($matrix, $columnNum) + ); + } + $rowNum = $rowKeys[--$rowNum]; + + return $matrix[$rowNum][$columnNum]; + } + + private static function extractRowValue(array $matrix, array $rowKeys, int $rowNum) + { + if ($rowNum === 0) { + return $matrix; + } + + $rowNum = $rowKeys[--$rowNum]; + $row = $matrix[$rowNum]; + if (is_array($row)) { + return [$rowNum => $row]; + } + + return $row; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Offset.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Offset.php new file mode 100644 index 0000000..7e1e287 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Offset.php @@ -0,0 +1,136 @@ +getParent() : null) + ->extractCellRange($cellAddress, $worksheet, false); + } + + private static function extractWorksheet($cellAddress, Cell $cell): array + { + $sheetName = ''; + if (strpos($cellAddress, '!') !== false) { + [$sheetName, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true); + $sheetName = trim($sheetName, "'"); + } + + $worksheet = ($sheetName !== '') + ? $cell->getWorksheet()->getParent()->getSheetByName($sheetName) + : $cell->getWorksheet(); + + return [$cellAddress, $worksheet]; + } + + private static function adjustEndCellColumnForWidth(string $endCellColumn, $width, int $startCellColumn, $columns) + { + $endCellColumn = Coordinate::columnIndexFromString($endCellColumn) - 1; + if (($width !== null) && (!is_object($width))) { + $endCellColumn = $startCellColumn + (int) $width - 1; + } else { + $endCellColumn += (int) $columns; + } + + return $endCellColumn; + } + + private static function adustEndCellRowForHeight($height, int $startCellRow, $rows, $endCellRow): int + { + if (($height !== null) && (!is_object($height))) { + $endCellRow = $startCellRow + (int) $height - 1; + } else { + $endCellRow += (int) $rows; + } + + return $endCellRow; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php new file mode 100644 index 0000000..9752d67 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php @@ -0,0 +1,209 @@ +getColumn()) : 1; + } + + /** + * COLUMN. + * + * Returns the column number of the given cell reference + * If the cell reference is a range of cells, COLUMN returns the column numbers of each column + * in the reference as a horizontal array. + * If cell reference is omitted, and the function is being called through the calculation engine, + * then it is assumed to be the reference of the cell in which the COLUMN function appears; + * otherwise this function returns 1. + * + * Excel Function: + * =COLUMN([cellAddress]) + * + * @param null|array|string $cellAddress A reference to a range of cells for which you want the column numbers + * + * @return int|int[] + */ + public static function COLUMN($cellAddress = null, ?Cell $cell = null) + { + if (self::cellAddressNullOrWhitespace($cellAddress)) { + return self::cellColumn($cell); + } + + if (is_array($cellAddress)) { + foreach ($cellAddress as $columnKey => $value) { + $columnKey = preg_replace('/[^a-z]/i', '', $columnKey); + + return (int) Coordinate::columnIndexFromString($columnKey); + } + + return self::cellColumn($cell); + } + + $cellAddress = $cellAddress ?? ''; + if ($cell != null) { + [,, $sheetName] = Helpers::extractWorksheet($cellAddress, $cell); + [,, $cellAddress] = Helpers::extractCellAddresses($cellAddress, true, $cell->getWorksheet(), $sheetName); + } + [, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true); + if (strpos($cellAddress, ':') !== false) { + [$startAddress, $endAddress] = explode(':', $cellAddress); + $startAddress = preg_replace('/[^a-z]/i', '', $startAddress); + $endAddress = preg_replace('/[^a-z]/i', '', $endAddress); + + return range( + (int) Coordinate::columnIndexFromString($startAddress), + (int) Coordinate::columnIndexFromString($endAddress) + ); + } + + $cellAddress = preg_replace('/[^a-z]/i', '', $cellAddress); + + return (int) Coordinate::columnIndexFromString($cellAddress); + } + + /** + * COLUMNS. + * + * Returns the number of columns in an array or reference. + * + * Excel Function: + * =COLUMNS(cellAddress) + * + * @param null|array|string $cellAddress An array or array formula, or a reference to a range of cells + * for which you want the number of columns + * + * @return int|string The number of columns in cellAddress, or a string if arguments are invalid + */ + public static function COLUMNS($cellAddress = null) + { + if (self::cellAddressNullOrWhitespace($cellAddress)) { + return 1; + } + if (!is_array($cellAddress)) { + return Functions::VALUE(); + } + + reset($cellAddress); + $isMatrix = (is_numeric(key($cellAddress))); + [$columns, $rows] = Calculation::getMatrixDimensions($cellAddress); + + if ($isMatrix) { + return $rows; + } + + return $columns; + } + + private static function cellRow(?Cell $cell): int + { + return ($cell !== null) ? $cell->getRow() : 1; + } + + /** + * ROW. + * + * Returns the row number of the given cell reference + * If the cell reference is a range of cells, ROW returns the row numbers of each row in the reference + * as a vertical array. + * If cell reference is omitted, and the function is being called through the calculation engine, + * then it is assumed to be the reference of the cell in which the ROW function appears; + * otherwise this function returns 1. + * + * Excel Function: + * =ROW([cellAddress]) + * + * @param null|array|string $cellAddress A reference to a range of cells for which you want the row numbers + * + * @return int|mixed[]|string + */ + public static function ROW($cellAddress = null, ?Cell $cell = null) + { + if (self::cellAddressNullOrWhitespace($cellAddress)) { + return self::cellRow($cell); + } + + if (is_array($cellAddress)) { + foreach ($cellAddress as $rowKey => $rowValue) { + foreach ($rowValue as $columnKey => $cellValue) { + return (int) preg_replace('/\D/', '', $rowKey); + } + } + + return self::cellRow($cell); + } + + $cellAddress = $cellAddress ?? ''; + if ($cell !== null) { + [,, $sheetName] = Helpers::extractWorksheet($cellAddress, $cell); + [,, $cellAddress] = Helpers::extractCellAddresses($cellAddress, true, $cell->getWorksheet(), $sheetName); + } + [, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true); + if (strpos($cellAddress, ':') !== false) { + [$startAddress, $endAddress] = explode(':', $cellAddress); + $startAddress = preg_replace('/\D/', '', $startAddress); + $endAddress = preg_replace('/\D/', '', $endAddress); + + return array_map( + function ($value) { + return [$value]; + }, + range($startAddress, $endAddress) + ); + } + [$cellAddress] = explode(':', $cellAddress); + + return (int) preg_replace('/\D/', '', $cellAddress); + } + + /** + * ROWS. + * + * Returns the number of rows in an array or reference. + * + * Excel Function: + * =ROWS(cellAddress) + * + * @param null|array|string $cellAddress An array or array formula, or a reference to a range of cells + * for which you want the number of rows + * + * @return int|string The number of rows in cellAddress, or a string if arguments are invalid + */ + public static function ROWS($cellAddress = null) + { + if (self::cellAddressNullOrWhitespace($cellAddress)) { + return 1; + } + if (!is_array($cellAddress)) { + return Functions::VALUE(); + } + + reset($cellAddress); + $isMatrix = (is_numeric(key($cellAddress))); + [$columns, $rows] = Calculation::getMatrixDimensions($cellAddress); + + if ($isMatrix) { + return $columns; + } + + return $rows; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Selection.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Selection.php new file mode 100644 index 0000000..6c18d73 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Selection.php @@ -0,0 +1,46 @@ + $entryCount)) { + return Functions::VALUE(); + } + + if (is_array($chooseArgs[$chosenEntry])) { + return Functions::flattenArray($chooseArgs[$chosenEntry]); + } + + return $chooseArgs[$chosenEntry]; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php new file mode 100644 index 0000000..ddd5d9e --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php @@ -0,0 +1,105 @@ +getMessage(); + } + + $f = array_keys($lookupArray); + $firstRow = array_pop($f); + if ((!is_array($lookupArray[$firstRow])) || ($indexNumber > count($lookupArray[$firstRow]))) { + return Functions::REF(); + } + $columnKeys = array_keys($lookupArray[$firstRow]); + $returnColumn = $columnKeys[--$indexNumber]; + $firstColumn = array_shift($columnKeys); + + if (!$notExactMatch) { + uasort($lookupArray, ['self', 'vlookupSort']); + } + + $rowNumber = self::vLookupSearch($lookupValue, $lookupArray, $firstColumn, $notExactMatch); + + if ($rowNumber !== null) { + // return the appropriate value + return $lookupArray[$rowNumber][$returnColumn]; + } + + return Functions::NA(); + } + + private static function vlookupSort($a, $b) + { + reset($a); + $firstColumn = key($a); + $aLower = StringHelper::strToLower($a[$firstColumn]); + $bLower = StringHelper::strToLower($b[$firstColumn]); + + if ($aLower == $bLower) { + return 0; + } + + return ($aLower < $bLower) ? -1 : 1; + } + + private static function vLookupSearch($lookupValue, $lookupArray, $column, $notExactMatch) + { + $lookupLower = StringHelper::strToLower($lookupValue); + + $rowNumber = null; + foreach ($lookupArray as $rowKey => $rowData) { + $bothNumeric = is_numeric($lookupValue) && is_numeric($rowData[$column]); + $bothNotNumeric = !is_numeric($lookupValue) && !is_numeric($rowData[$column]); + $cellDataLower = StringHelper::strToLower($rowData[$column]); + + // break if we have passed possible keys + if ( + $notExactMatch && + (($bothNumeric && ($rowData[$column] > $lookupValue)) || + ($bothNotNumeric && ($cellDataLower > $lookupLower))) + ) { + break; + } + + $rowNumber = self::checkMatch( + $bothNumeric, + $bothNotNumeric, + $notExactMatch, + $rowKey, + $cellDataLower, + $lookupLower, + $rowNumber + ); + } + + return $rowNumber; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig.php new file mode 100644 index 0000000..ec251f6 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig.php @@ -0,0 +1,1520 @@ +getMessage(); + } + + return abs($number); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Angle.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Angle.php new file mode 100644 index 0000000..3062481 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Angle.php @@ -0,0 +1,48 @@ +getMessage(); + } + + return rad2deg($number); + } + + /** + * RADIANS. + * + * Returns the result of builtin function deg2rad after validating args. + * + * @param mixed $number Should be numeric + * + * @return float|string Rounded number + */ + public static function toRadians($number) + { + try { + $number = Helpers::validateNumericNullBool($number); + } catch (Exception $e) { + return $e->getMessage(); + } + + return deg2rad($number); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Arabic.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Arabic.php new file mode 100644 index 0000000..b852eea --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Arabic.php @@ -0,0 +1,103 @@ + 1000, + 'D' => 500, + 'C' => 100, + 'L' => 50, + 'X' => 10, + 'V' => 5, + 'I' => 1, + ]; + + /** + * Recursively calculate the arabic value of a roman numeral. + * + * @param int $sum + * @param int $subtract + * + * @return int + */ + private static function calculateArabic(array $roman, &$sum = 0, $subtract = 0) + { + $numeral = array_shift($roman); + if (!isset(self::ROMAN_LOOKUP[$numeral])) { + throw new Exception('Invalid character detected'); + } + + $arabic = self::ROMAN_LOOKUP[$numeral]; + if (count($roman) > 0 && isset(self::ROMAN_LOOKUP[$roman[0]]) && $arabic < self::ROMAN_LOOKUP[$roman[0]]) { + $subtract += $arabic; + } else { + $sum += ($arabic - $subtract); + $subtract = 0; + } + + if (count($roman) > 0) { + self::calculateArabic($roman, $sum, $subtract); + } + + return $sum; + } + + /** + * @param mixed $value + */ + private static function mollifyScrutinizer($value): array + { + return is_array($value) ? $value : []; + } + + private static function strSplit(string $roman): array + { + $rslt = str_split($roman); + + return self::mollifyScrutinizer($rslt); + } + + /** + * ARABIC. + * + * Converts a Roman numeral to an Arabic numeral. + * + * Excel Function: + * ARABIC(text) + * + * @param string $roman + * + * @return int|string the arabic numberal contrived from the roman numeral + */ + public static function evaluate($roman) + { + // An empty string should return 0 + $roman = substr(trim(strtoupper((string) Functions::flattenSingleValue($roman))), 0, 255); + if ($roman === '') { + return 0; + } + + // Convert the roman numeral to an arabic number + $negativeNumber = $roman[0] === '-'; + if ($negativeNumber) { + $roman = substr($roman, 1); + } + + try { + $arabic = self::calculateArabic(self::strSplit($roman)); + } catch (Exception $e) { + return Functions::VALUE(); // Invalid character detected + } + + if ($negativeNumber) { + $arabic *= -1; // The number should be negative + } + + return $arabic; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Base.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Base.php new file mode 100644 index 0000000..4be7b7c --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Base.php @@ -0,0 +1,49 @@ +getMessage(); + } + $minLength = Functions::flattenSingleValue($minLength); + + if ($minLength === null || is_numeric($minLength)) { + if ($number < 0 || $number >= 2 ** 53 || $radix < 2 || $radix > 36) { + return Functions::NAN(); // Numeric range constraints + } + + $outcome = strtoupper((string) base_convert("$number", 10, $radix)); + if ($minLength !== null) { + $outcome = str_pad($outcome, (int) $minLength, '0', STR_PAD_LEFT); // String padding + } + + return $outcome; + } + + return Functions::VALUE(); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Ceiling.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Ceiling.php new file mode 100644 index 0000000..73f54a5 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Ceiling.php @@ -0,0 +1,138 @@ +getMessage(); + } + + return self::argumentsOk((float) $number, (float) $significance); + } + + /** + * CEILING.MATH. + * + * Round a number down to the nearest integer or to the nearest multiple of significance. + * + * Excel Function: + * CEILING.MATH(number[,significance[,mode]]) + * + * @param mixed $number Number to round + * @param mixed $significance Significance + * @param int $mode direction to round negative numbers + * + * @return float|string Rounded Number, or a string containing an error + */ + public static function math($number, $significance = null, $mode = 0) + { + try { + $number = Helpers::validateNumericNullBool($number); + $significance = Helpers::validateNumericNullSubstitution($significance, ($number < 0) ? -1 : 1); + $mode = Helpers::validateNumericNullSubstitution($mode, null); + } catch (Exception $e) { + return $e->getMessage(); + } + + if (empty($significance * $number)) { + return 0.0; + } + if (self::ceilingMathTest((float) $significance, (float) $number, (int) $mode)) { + return floor($number / $significance) * $significance; + } + + return ceil($number / $significance) * $significance; + } + + /** + * CEILING.PRECISE. + * + * Rounds number up, away from zero, to the nearest multiple of significance. + * + * Excel Function: + * CEILING.PRECISE(number[,significance]) + * + * @param mixed $number the number you want to round + * @param float $significance the multiple to which you want to round + * + * @return float|string Rounded Number, or a string containing an error + */ + public static function precise($number, $significance = 1) + { + try { + $number = Helpers::validateNumericNullBool($number); + $significance = Helpers::validateNumericNullSubstitution($significance, null); + } catch (Exception $e) { + return $e->getMessage(); + } + + if (!$significance) { + return 0.0; + } + $result = $number / abs($significance); + + return ceil($result) * $significance * (($significance < 0) ? -1 : 1); + } + + /** + * Let CEILINGMATH complexity pass Scrutinizer. + */ + private static function ceilingMathTest(float $significance, float $number, int $mode): bool + { + return ((float) $significance < 0) || ((float) $number < 0 && !empty($mode)); + } + + /** + * Avoid Scrutinizer problems concerning complexity. + * + * @return float|string + */ + private static function argumentsOk(float $number, float $significance) + { + if (empty($number * $significance)) { + return 0.0; + } + if (Helpers::returnSign($number) == Helpers::returnSign($significance)) { + return ceil($number / $significance) * $significance; + } + + return Functions::NAN(); + } + + private static function floorCheck1Arg(): void + { + $compatibility = Functions::getCompatibilityMode(); + if ($compatibility === Functions::COMPATIBILITY_EXCEL) { + throw new Exception('Excel requires 2 arguments for CEILING'); + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Combinations.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Combinations.php new file mode 100644 index 0000000..97508bb --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Combinations.php @@ -0,0 +1,74 @@ +getMessage(); + } + + return round(Factorial::fact($numObjs) / Factorial::fact($numObjs - $numInSet)) / Factorial::fact($numInSet); + } + + /** + * COMBIN. + * + * Returns the number of combinations for a given number of items. Use COMBIN to + * determine the total possible number of groups for a given number of items. + * + * Excel Function: + * COMBIN(numObjs,numInSet) + * + * @param mixed $numObjs Number of different objects + * @param mixed $numInSet Number of objects in each combination + * + * @return float|int|string Number of combinations, or a string containing an error + */ + public static function withRepetition($numObjs, $numInSet) + { + try { + $numObjs = Helpers::validateNumericNullSubstitution($numObjs, null); + $numInSet = Helpers::validateNumericNullSubstitution($numInSet, null); + Helpers::validateNotNegative($numInSet); + Helpers::validateNotNegative($numObjs); + $numObjs = (int) $numObjs; + $numInSet = (int) $numInSet; + // Microsoft documentation says following is true, but Excel + // does not enforce this restriction. + //Helpers::validateNotNegative($numObjs - $numInSet); + if ($numObjs === 0) { + Helpers::validateNotNegative(-$numInSet); + + return 1; + } + } catch (Exception $e) { + return $e->getMessage(); + } + + return round(Factorial::fact($numObjs + $numInSet - 1) / Factorial::fact($numObjs - 1)) / Factorial::fact($numInSet); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Exp.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Exp.php new file mode 100644 index 0000000..ce930a8 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Exp.php @@ -0,0 +1,28 @@ +getMessage(); + } + + return exp($number); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Factorial.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Factorial.php new file mode 100644 index 0000000..f443f8e --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Factorial.php @@ -0,0 +1,110 @@ +getMessage(); + } + + $factLoop = floor($factVal); + if ($factVal > $factLoop) { + if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) { + return Statistical\Distributions\Gamma::gammaValue($factVal + 1); + } + } + + $factorial = 1; + while ($factLoop > 1) { + $factorial *= $factLoop--; + } + + return $factorial; + } + + /** + * FACTDOUBLE. + * + * Returns the double factorial of a number. + * + * Excel Function: + * FACTDOUBLE(factVal) + * + * @param float $factVal Factorial Value + * + * @return float|int|string Double Factorial, or a string containing an error + */ + public static function factDouble($factVal) + { + try { + $factVal = Helpers::validateNumericNullSubstitution($factVal, 0); + Helpers::validateNotNegative($factVal); + } catch (Exception $e) { + return $e->getMessage(); + } + + $factLoop = floor($factVal); + $factorial = 1; + while ($factLoop > 1) { + $factorial *= $factLoop; + $factLoop -= 2; + } + + return $factorial; + } + + /** + * MULTINOMIAL. + * + * Returns the ratio of the factorial of a sum of values to the product of factorials. + * + * @param mixed[] $args An array of mixed values for the Data Series + * + * @return float|string The result, or a string containing an error + */ + public static function multinomial(...$args) + { + $summer = 0; + $divisor = 1; + + try { + // Loop through arguments + foreach (Functions::flattenArray($args) as $argx) { + $arg = Helpers::validateNumericNullSubstitution($argx, null); + Helpers::validateNotNegative($arg); + $arg = (int) $arg; + $summer += $arg; + $divisor *= self::fact($arg); + } + } catch (Exception $e) { + return $e->getMessage(); + } + + $summer = self::fact($summer); + + return $summer / $divisor; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Floor.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Floor.php new file mode 100644 index 0000000..04e1220 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Floor.php @@ -0,0 +1,166 @@ +getMessage(); + } + + return self::argumentsOk((float) $number, (float) $significance); + } + + /** + * FLOOR.MATH. + * + * Round a number down to the nearest integer or to the nearest multiple of significance. + * + * Excel Function: + * FLOOR.MATH(number[,significance[,mode]]) + * + * @param mixed $number Number to round + * @param mixed $significance Significance + * @param mixed $mode direction to round negative numbers + * + * @return float|string Rounded Number, or a string containing an error + */ + public static function math($number, $significance = null, $mode = 0) + { + try { + $number = Helpers::validateNumericNullBool($number); + $significance = Helpers::validateNumericNullSubstitution($significance, ($number < 0) ? -1 : 1); + $mode = Helpers::validateNumericNullSubstitution($mode, null); + } catch (Exception $e) { + return $e->getMessage(); + } + + return self::argsOk((float) $number, (float) $significance, (int) $mode); + } + + /** + * FLOOR.PRECISE. + * + * Rounds number down, toward zero, to the nearest multiple of significance. + * + * Excel Function: + * FLOOR.PRECISE(number[,significance]) + * + * @param float $number Number to round + * @param float $significance Significance + * + * @return float|string Rounded Number, or a string containing an error + */ + public static function precise($number, $significance = 1) + { + try { + $number = Helpers::validateNumericNullBool($number); + $significance = Helpers::validateNumericNullSubstitution($significance, null); + } catch (Exception $e) { + return $e->getMessage(); + } + + return self::argumentsOkPrecise((float) $number, (float) $significance); + } + + /** + * Avoid Scrutinizer problems concerning complexity. + * + * @return float|string + */ + private static function argumentsOkPrecise(float $number, float $significance) + { + if ($significance == 0.0) { + return Functions::DIV0(); + } + if ($number == 0.0) { + return 0.0; + } + + return floor($number / abs($significance)) * abs($significance); + } + + /** + * Avoid Scrutinizer complexity problems. + * + * @return float|string Rounded Number, or a string containing an error + */ + private static function argsOk(float $number, float $significance, int $mode) + { + if (!$significance) { + return Functions::DIV0(); + } + if (!$number) { + return 0.0; + } + if (self::floorMathTest($number, $significance, $mode)) { + return ceil($number / $significance) * $significance; + } + + return floor($number / $significance) * $significance; + } + + /** + * Let FLOORMATH complexity pass Scrutinizer. + */ + private static function floorMathTest(float $number, float $significance, int $mode): bool + { + return Helpers::returnSign($significance) == -1 || (Helpers::returnSign($number) == -1 && !empty($mode)); + } + + /** + * Avoid Scrutinizer problems concerning complexity. + * + * @return float|string + */ + private static function argumentsOk(float $number, float $significance) + { + if ($significance == 0.0) { + return Functions::DIV0(); + } + if ($number == 0.0) { + return 0.0; + } + if (Helpers::returnSign($significance) == 1) { + return floor($number / $significance) * $significance; + } + if (Helpers::returnSign($number) == -1 && Helpers::returnSign($significance) == -1) { + return floor($number / $significance) * $significance; + } + + return Functions::NAN(); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Gcd.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Gcd.php new file mode 100644 index 0000000..1dd52fa --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Gcd.php @@ -0,0 +1,69 @@ +getMessage(); + } + + if (count($arrayArgs) <= 0) { + return Functions::VALUE(); + } + $gcd = (int) array_pop($arrayArgs); + do { + $gcd = self::evaluateGCD($gcd, (int) array_pop($arrayArgs)); + } while (!empty($arrayArgs)); + + return $gcd; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Helpers.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Helpers.php new file mode 100644 index 0000000..b89644a --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Helpers.php @@ -0,0 +1,129 @@ += 0. + * + * @param float|int $number + */ + public static function validateNotNegative($number, ?string $except = null): void + { + if ($number >= 0) { + return; + } + + throw new Exception($except ?? Functions::NAN()); + } + + /** + * Confirm number > 0. + * + * @param float|int $number + */ + public static function validatePositive($number, ?string $except = null): void + { + if ($number > 0) { + return; + } + + throw new Exception($except ?? Functions::NAN()); + } + + /** + * Confirm number != 0. + * + * @param float|int $number + */ + public static function validateNotZero($number): void + { + if ($number) { + return; + } + + throw new Exception(Functions::DIV0()); + } + + public static function returnSign(float $number): int + { + return $number ? (($number > 0) ? 1 : -1) : 0; + } + + public static function getEven(float $number): float + { + $significance = 2 * self::returnSign($number); + + return $significance ? (ceil($number / $significance) * $significance) : 0; + } + + /** + * Return NAN or value depending on argument. + * + * @param float $result Number + * + * @return float|string + */ + public static function numberOrNan($result) + { + return is_nan($result) ? Functions::NAN() : $result; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/IntClass.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/IntClass.php new file mode 100644 index 0000000..7aa3d06 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/IntClass.php @@ -0,0 +1,31 @@ +getMessage(); + } + + return (int) floor($number); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Lcm.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Lcm.php new file mode 100644 index 0000000..46e9816 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Lcm.php @@ -0,0 +1,110 @@ + 1; --$i) { + if (($value % $i) == 0) { + $factorArray = array_merge($factorArray, self::factors($value / $i)); + $factorArray = array_merge($factorArray, self::factors($i)); + if ($i <= sqrt($value)) { + break; + } + } + } + if (!empty($factorArray)) { + rsort($factorArray); + + return $factorArray; + } + + return [(int) $value]; + } + + /** + * LCM. + * + * Returns the lowest common multiplier of a series of numbers + * The least common multiple is the smallest positive integer that is a multiple + * of all integer arguments number1, number2, and so on. Use LCM to add fractions + * with different denominators. + * + * Excel Function: + * LCM(number1[,number2[, ...]]) + * + * @param mixed ...$args Data values + * + * @return int|string Lowest Common Multiplier, or a string containing an error + */ + public static function evaluate(...$args) + { + try { + $arrayArgs = []; + $anyZeros = 0; + $anyNonNulls = 0; + foreach (Functions::flattenArray($args) as $value1) { + $anyNonNulls += (int) ($value1 !== null); + $value = Helpers::validateNumericNullSubstitution($value1, 1); + Helpers::validateNotNegative($value); + $arrayArgs[] = (int) $value; + $anyZeros += (int) !((bool) $value); + } + self::testNonNulls($anyNonNulls); + if ($anyZeros) { + return 0; + } + } catch (Exception $e) { + return $e->getMessage(); + } + + $returnValue = 1; + $allPoweredFactors = []; + // Loop through arguments + foreach ($arrayArgs as $value) { + $myFactors = self::factors(floor($value)); + $myCountedFactors = array_count_values($myFactors); + $myPoweredFactors = []; + foreach ($myCountedFactors as $myCountedFactor => $myCountedPower) { + $myPoweredFactors[$myCountedFactor] = $myCountedFactor ** $myCountedPower; + } + self::processPoweredFactors($allPoweredFactors, $myPoweredFactors); + } + foreach ($allPoweredFactors as $allPoweredFactor) { + $returnValue *= (int) $allPoweredFactor; + } + + return $returnValue; + } + + private static function processPoweredFactors(array &$allPoweredFactors, array &$myPoweredFactors): void + { + foreach ($myPoweredFactors as $myPoweredValue => $myPoweredFactor) { + if (isset($allPoweredFactors[$myPoweredValue])) { + if ($allPoweredFactors[$myPoweredValue] < $myPoweredFactor) { + $allPoweredFactors[$myPoweredValue] = $myPoweredFactor; + } + } else { + $allPoweredFactors[$myPoweredValue] = $myPoweredFactor; + } + } + } + + private static function testNonNulls(int $anyNonNulls): void + { + if (!$anyNonNulls) { + throw new Exception(Functions::VALUE()); + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Logarithms.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Logarithms.php new file mode 100644 index 0000000..d6878d8 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Logarithms.php @@ -0,0 +1,77 @@ +getMessage(); + } + + return log($number, $base); + } + + /** + * LOG10. + * + * Returns the result of builtin function log after validating args. + * + * @param mixed $number Should be numeric + * + * @return float|string Rounded number + */ + public static function base10($number) + { + try { + $number = Helpers::validateNumericNullBool($number); + Helpers::validatePositive($number); + } catch (Exception $e) { + return $e->getMessage(); + } + + return log10($number); + } + + /** + * LN. + * + * Returns the result of builtin function log after validating args. + * + * @param mixed $number Should be numeric + * + * @return float|string Rounded number + */ + public static function natural($number) + { + try { + $number = Helpers::validateNumericNullBool($number); + Helpers::validatePositive($number); + } catch (Exception $e) { + return $e->getMessage(); + } + + return log($number); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/MatrixFunctions.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/MatrixFunctions.php new file mode 100644 index 0000000..92e1ff8 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/MatrixFunctions.php @@ -0,0 +1,138 @@ +determinant(); + } catch (MatrixException $ex) { + return Functions::VALUE(); + } catch (Exception $e) { + return $e->getMessage(); + } + } + + /** + * MINVERSE. + * + * Returns the inverse matrix for the matrix stored in an array. + * + * Excel Function: + * MINVERSE(array) + * + * @param mixed $matrixValues A matrix of values + * + * @return array|string The result, or a string containing an error + */ + public static function inverse($matrixValues) + { + try { + $matrix = self::getMatrix($matrixValues); + + return $matrix->inverse()->toArray(); + } catch (MatrixDiv0Exception $e) { + return Functions::NAN(); + } catch (MatrixException $e) { + return Functions::VALUE(); + } catch (Exception $e) { + return $e->getMessage(); + } + } + + /** + * MMULT. + * + * @param mixed $matrixData1 A matrix of values + * @param mixed $matrixData2 A matrix of values + * + * @return array|string The result, or a string containing an error + */ + public static function multiply($matrixData1, $matrixData2) + { + try { + $matrixA = self::getMatrix($matrixData1); + $matrixB = self::getMatrix($matrixData2); + + return $matrixA->multiply($matrixB)->toArray(); + } catch (MatrixException $ex) { + return Functions::VALUE(); + } catch (Exception $e) { + return $e->getMessage(); + } + } + + /** + * MUnit. + * + * @param mixed $dimension Number of rows and columns + * + * @return array|string The result, or a string containing an error + */ + public static function identity($dimension) + { + try { + $dimension = (int) Helpers::validateNumericNullBool($dimension); + Helpers::validatePositive($dimension, Functions::VALUE()); + $matrix = Builder::createIdentityMatrix($dimension, 0)->toArray(); + + return $matrix; + } catch (Exception $e) { + return $e->getMessage(); + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Operations.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Operations.php new file mode 100644 index 0000000..595c7fd --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Operations.php @@ -0,0 +1,136 @@ +getMessage(); + } + + if (($dividend < 0.0) && ($divisor > 0.0)) { + return $divisor - fmod(abs($dividend), $divisor); + } + if (($dividend > 0.0) && ($divisor < 0.0)) { + return $divisor + fmod($dividend, abs($divisor)); + } + + return fmod($dividend, $divisor); + } + + /** + * POWER. + * + * Computes x raised to the power y. + * + * @param float|int $x + * @param float|int $y + * + * @return float|int|string The result, or a string containing an error + */ + public static function power($x, $y) + { + try { + $x = Helpers::validateNumericNullBool($x); + $y = Helpers::validateNumericNullBool($y); + } catch (Exception $e) { + return $e->getMessage(); + } + + // Validate parameters + if (!$x && !$y) { + return Functions::NAN(); + } + if (!$x && $y < 0.0) { + return Functions::DIV0(); + } + + // Return + $result = $x ** $y; + + return Helpers::numberOrNan($result); + } + + /** + * PRODUCT. + * + * PRODUCT returns the product of all the values and cells referenced in the argument list. + * + * Excel Function: + * PRODUCT(value1[,value2[, ...]]) + * + * @param mixed ...$args Data values + * + * @return float|string + */ + public static function product(...$args) + { + // Return value + $returnValue = null; + + // Loop through arguments + foreach (Functions::flattenArray($args) as $arg) { + // Is it a numeric value? + if (is_numeric($arg)) { + if ($returnValue === null) { + $returnValue = $arg; + } else { + $returnValue *= $arg; + } + } else { + return Functions::VALUE(); + } + } + + // Return + if ($returnValue === null) { + return 0; + } + + return $returnValue; + } + + /** + * QUOTIENT. + * + * QUOTIENT function returns the integer portion of a division. Numerator is the divided number + * and denominator is the divisor. + * + * Excel Function: + * QUOTIENT(value1,value2) + * + * @param mixed $numerator Expect float|int + * @param mixed $denominator Expect float|int + * + * @return int|string + */ + public static function quotient($numerator, $denominator) + { + try { + $numerator = Helpers::validateNumericNullSubstitution($numerator, 0); + $denominator = Helpers::validateNumericNullSubstitution($denominator, 0); + Helpers::validateNotZero($denominator); + } catch (Exception $e) { + return $e->getMessage(); + } + + return (int) ($numerator / $denominator); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Random.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Random.php new file mode 100644 index 0000000..963a789 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Random.php @@ -0,0 +1,39 @@ +getMessage(); + } + + return mt_rand($min, $max); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Roman.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Roman.php new file mode 100644 index 0000000..71a6df3 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Roman.php @@ -0,0 +1,835 @@ + ['VL'], + 46 => ['VLI'], + 47 => ['VLII'], + 48 => ['VLIII'], + 49 => ['VLIV', 'IL'], + 95 => ['VC'], + 96 => ['VCI'], + 97 => ['VCII'], + 98 => ['VCIII'], + 99 => ['VCIV', 'IC'], + 145 => ['CVL'], + 146 => ['CVLI'], + 147 => ['CVLII'], + 148 => ['CVLIII'], + 149 => ['CVLIV', 'CIL'], + 195 => ['CVC'], + 196 => ['CVCI'], + 197 => ['CVCII'], + 198 => ['CVCIII'], + 199 => ['CVCIV', 'CIC'], + 245 => ['CCVL'], + 246 => ['CCVLI'], + 247 => ['CCVLII'], + 248 => ['CCVLIII'], + 249 => ['CCVLIV', 'CCIL'], + 295 => ['CCVC'], + 296 => ['CCVCI'], + 297 => ['CCVCII'], + 298 => ['CCVCIII'], + 299 => ['CCVCIV', 'CCIC'], + 345 => ['CCCVL'], + 346 => ['CCCVLI'], + 347 => ['CCCVLII'], + 348 => ['CCCVLIII'], + 349 => ['CCCVLIV', 'CCCIL'], + 395 => ['CCCVC'], + 396 => ['CCCVCI'], + 397 => ['CCCVCII'], + 398 => ['CCCVCIII'], + 399 => ['CCCVCIV', 'CCCIC'], + 445 => ['CDVL'], + 446 => ['CDVLI'], + 447 => ['CDVLII'], + 448 => ['CDVLIII'], + 449 => ['CDVLIV', 'CDIL'], + 450 => ['LD'], + 451 => ['LDI'], + 452 => ['LDII'], + 453 => ['LDIII'], + 454 => ['LDIV'], + 455 => ['LDV'], + 456 => ['LDVI'], + 457 => ['LDVII'], + 458 => ['LDVIII'], + 459 => ['LDIX'], + 460 => ['LDX'], + 461 => ['LDXI'], + 462 => ['LDXII'], + 463 => ['LDXIII'], + 464 => ['LDXIV'], + 465 => ['LDXV'], + 466 => ['LDXVI'], + 467 => ['LDXVII'], + 468 => ['LDXVIII'], + 469 => ['LDXIX'], + 470 => ['LDXX'], + 471 => ['LDXXI'], + 472 => ['LDXXII'], + 473 => ['LDXXIII'], + 474 => ['LDXXIV'], + 475 => ['LDXXV'], + 476 => ['LDXXVI'], + 477 => ['LDXXVII'], + 478 => ['LDXXVIII'], + 479 => ['LDXXIX'], + 480 => ['LDXXX'], + 481 => ['LDXXXI'], + 482 => ['LDXXXII'], + 483 => ['LDXXXIII'], + 484 => ['LDXXXIV'], + 485 => ['LDXXXV'], + 486 => ['LDXXXVI'], + 487 => ['LDXXXVII'], + 488 => ['LDXXXVIII'], + 489 => ['LDXXXIX'], + 490 => ['LDXL', 'XD'], + 491 => ['LDXLI', 'XDI'], + 492 => ['LDXLII', 'XDII'], + 493 => ['LDXLIII', 'XDIII'], + 494 => ['LDXLIV', 'XDIV'], + 495 => ['LDVL', 'XDV', 'VD'], + 496 => ['LDVLI', 'XDVI', 'VDI'], + 497 => ['LDVLII', 'XDVII', 'VDII'], + 498 => ['LDVLIII', 'XDVIII', 'VDIII'], + 499 => ['LDVLIV', 'XDIX', 'VDIV', 'ID'], + 545 => ['DVL'], + 546 => ['DVLI'], + 547 => ['DVLII'], + 548 => ['DVLIII'], + 549 => ['DVLIV', 'DIL'], + 595 => ['DVC'], + 596 => ['DVCI'], + 597 => ['DVCII'], + 598 => ['DVCIII'], + 599 => ['DVCIV', 'DIC'], + 645 => ['DCVL'], + 646 => ['DCVLI'], + 647 => ['DCVLII'], + 648 => ['DCVLIII'], + 649 => ['DCVLIV', 'DCIL'], + 695 => ['DCVC'], + 696 => ['DCVCI'], + 697 => ['DCVCII'], + 698 => ['DCVCIII'], + 699 => ['DCVCIV', 'DCIC'], + 745 => ['DCCVL'], + 746 => ['DCCVLI'], + 747 => ['DCCVLII'], + 748 => ['DCCVLIII'], + 749 => ['DCCVLIV', 'DCCIL'], + 795 => ['DCCVC'], + 796 => ['DCCVCI'], + 797 => ['DCCVCII'], + 798 => ['DCCVCIII'], + 799 => ['DCCVCIV', 'DCCIC'], + 845 => ['DCCCVL'], + 846 => ['DCCCVLI'], + 847 => ['DCCCVLII'], + 848 => ['DCCCVLIII'], + 849 => ['DCCCVLIV', 'DCCCIL'], + 895 => ['DCCCVC'], + 896 => ['DCCCVCI'], + 897 => ['DCCCVCII'], + 898 => ['DCCCVCIII'], + 899 => ['DCCCVCIV', 'DCCCIC'], + 945 => ['CMVL'], + 946 => ['CMVLI'], + 947 => ['CMVLII'], + 948 => ['CMVLIII'], + 949 => ['CMVLIV', 'CMIL'], + 950 => ['LM'], + 951 => ['LMI'], + 952 => ['LMII'], + 953 => ['LMIII'], + 954 => ['LMIV'], + 955 => ['LMV'], + 956 => ['LMVI'], + 957 => ['LMVII'], + 958 => ['LMVIII'], + 959 => ['LMIX'], + 960 => ['LMX'], + 961 => ['LMXI'], + 962 => ['LMXII'], + 963 => ['LMXIII'], + 964 => ['LMXIV'], + 965 => ['LMXV'], + 966 => ['LMXVI'], + 967 => ['LMXVII'], + 968 => ['LMXVIII'], + 969 => ['LMXIX'], + 970 => ['LMXX'], + 971 => ['LMXXI'], + 972 => ['LMXXII'], + 973 => ['LMXXIII'], + 974 => ['LMXXIV'], + 975 => ['LMXXV'], + 976 => ['LMXXVI'], + 977 => ['LMXXVII'], + 978 => ['LMXXVIII'], + 979 => ['LMXXIX'], + 980 => ['LMXXX'], + 981 => ['LMXXXI'], + 982 => ['LMXXXII'], + 983 => ['LMXXXIII'], + 984 => ['LMXXXIV'], + 985 => ['LMXXXV'], + 986 => ['LMXXXVI'], + 987 => ['LMXXXVII'], + 988 => ['LMXXXVIII'], + 989 => ['LMXXXIX'], + 990 => ['LMXL', 'XM'], + 991 => ['LMXLI', 'XMI'], + 992 => ['LMXLII', 'XMII'], + 993 => ['LMXLIII', 'XMIII'], + 994 => ['LMXLIV', 'XMIV'], + 995 => ['LMVL', 'XMV', 'VM'], + 996 => ['LMVLI', 'XMVI', 'VMI'], + 997 => ['LMVLII', 'XMVII', 'VMII'], + 998 => ['LMVLIII', 'XMVIII', 'VMIII'], + 999 => ['LMVLIV', 'XMIX', 'VMIV', 'IM'], + 1045 => ['MVL'], + 1046 => ['MVLI'], + 1047 => ['MVLII'], + 1048 => ['MVLIII'], + 1049 => ['MVLIV', 'MIL'], + 1095 => ['MVC'], + 1096 => ['MVCI'], + 1097 => ['MVCII'], + 1098 => ['MVCIII'], + 1099 => ['MVCIV', 'MIC'], + 1145 => ['MCVL'], + 1146 => ['MCVLI'], + 1147 => ['MCVLII'], + 1148 => ['MCVLIII'], + 1149 => ['MCVLIV', 'MCIL'], + 1195 => ['MCVC'], + 1196 => ['MCVCI'], + 1197 => ['MCVCII'], + 1198 => ['MCVCIII'], + 1199 => ['MCVCIV', 'MCIC'], + 1245 => ['MCCVL'], + 1246 => ['MCCVLI'], + 1247 => ['MCCVLII'], + 1248 => ['MCCVLIII'], + 1249 => ['MCCVLIV', 'MCCIL'], + 1295 => ['MCCVC'], + 1296 => ['MCCVCI'], + 1297 => ['MCCVCII'], + 1298 => ['MCCVCIII'], + 1299 => ['MCCVCIV', 'MCCIC'], + 1345 => ['MCCCVL'], + 1346 => ['MCCCVLI'], + 1347 => ['MCCCVLII'], + 1348 => ['MCCCVLIII'], + 1349 => ['MCCCVLIV', 'MCCCIL'], + 1395 => ['MCCCVC'], + 1396 => ['MCCCVCI'], + 1397 => ['MCCCVCII'], + 1398 => ['MCCCVCIII'], + 1399 => ['MCCCVCIV', 'MCCCIC'], + 1445 => ['MCDVL'], + 1446 => ['MCDVLI'], + 1447 => ['MCDVLII'], + 1448 => ['MCDVLIII'], + 1449 => ['MCDVLIV', 'MCDIL'], + 1450 => ['MLD'], + 1451 => ['MLDI'], + 1452 => ['MLDII'], + 1453 => ['MLDIII'], + 1454 => ['MLDIV'], + 1455 => ['MLDV'], + 1456 => ['MLDVI'], + 1457 => ['MLDVII'], + 1458 => ['MLDVIII'], + 1459 => ['MLDIX'], + 1460 => ['MLDX'], + 1461 => ['MLDXI'], + 1462 => ['MLDXII'], + 1463 => ['MLDXIII'], + 1464 => ['MLDXIV'], + 1465 => ['MLDXV'], + 1466 => ['MLDXVI'], + 1467 => ['MLDXVII'], + 1468 => ['MLDXVIII'], + 1469 => ['MLDXIX'], + 1470 => ['MLDXX'], + 1471 => ['MLDXXI'], + 1472 => ['MLDXXII'], + 1473 => ['MLDXXIII'], + 1474 => ['MLDXXIV'], + 1475 => ['MLDXXV'], + 1476 => ['MLDXXVI'], + 1477 => ['MLDXXVII'], + 1478 => ['MLDXXVIII'], + 1479 => ['MLDXXIX'], + 1480 => ['MLDXXX'], + 1481 => ['MLDXXXI'], + 1482 => ['MLDXXXII'], + 1483 => ['MLDXXXIII'], + 1484 => ['MLDXXXIV'], + 1485 => ['MLDXXXV'], + 1486 => ['MLDXXXVI'], + 1487 => ['MLDXXXVII'], + 1488 => ['MLDXXXVIII'], + 1489 => ['MLDXXXIX'], + 1490 => ['MLDXL', 'MXD'], + 1491 => ['MLDXLI', 'MXDI'], + 1492 => ['MLDXLII', 'MXDII'], + 1493 => ['MLDXLIII', 'MXDIII'], + 1494 => ['MLDXLIV', 'MXDIV'], + 1495 => ['MLDVL', 'MXDV', 'MVD'], + 1496 => ['MLDVLI', 'MXDVI', 'MVDI'], + 1497 => ['MLDVLII', 'MXDVII', 'MVDII'], + 1498 => ['MLDVLIII', 'MXDVIII', 'MVDIII'], + 1499 => ['MLDVLIV', 'MXDIX', 'MVDIV', 'MID'], + 1545 => ['MDVL'], + 1546 => ['MDVLI'], + 1547 => ['MDVLII'], + 1548 => ['MDVLIII'], + 1549 => ['MDVLIV', 'MDIL'], + 1595 => ['MDVC'], + 1596 => ['MDVCI'], + 1597 => ['MDVCII'], + 1598 => ['MDVCIII'], + 1599 => ['MDVCIV', 'MDIC'], + 1645 => ['MDCVL'], + 1646 => ['MDCVLI'], + 1647 => ['MDCVLII'], + 1648 => ['MDCVLIII'], + 1649 => ['MDCVLIV', 'MDCIL'], + 1695 => ['MDCVC'], + 1696 => ['MDCVCI'], + 1697 => ['MDCVCII'], + 1698 => ['MDCVCIII'], + 1699 => ['MDCVCIV', 'MDCIC'], + 1745 => ['MDCCVL'], + 1746 => ['MDCCVLI'], + 1747 => ['MDCCVLII'], + 1748 => ['MDCCVLIII'], + 1749 => ['MDCCVLIV', 'MDCCIL'], + 1795 => ['MDCCVC'], + 1796 => ['MDCCVCI'], + 1797 => ['MDCCVCII'], + 1798 => ['MDCCVCIII'], + 1799 => ['MDCCVCIV', 'MDCCIC'], + 1845 => ['MDCCCVL'], + 1846 => ['MDCCCVLI'], + 1847 => ['MDCCCVLII'], + 1848 => ['MDCCCVLIII'], + 1849 => ['MDCCCVLIV', 'MDCCCIL'], + 1895 => ['MDCCCVC'], + 1896 => ['MDCCCVCI'], + 1897 => ['MDCCCVCII'], + 1898 => ['MDCCCVCIII'], + 1899 => ['MDCCCVCIV', 'MDCCCIC'], + 1945 => ['MCMVL'], + 1946 => ['MCMVLI'], + 1947 => ['MCMVLII'], + 1948 => ['MCMVLIII'], + 1949 => ['MCMVLIV', 'MCMIL'], + 1950 => ['MLM'], + 1951 => ['MLMI'], + 1952 => ['MLMII'], + 1953 => ['MLMIII'], + 1954 => ['MLMIV'], + 1955 => ['MLMV'], + 1956 => ['MLMVI'], + 1957 => ['MLMVII'], + 1958 => ['MLMVIII'], + 1959 => ['MLMIX'], + 1960 => ['MLMX'], + 1961 => ['MLMXI'], + 1962 => ['MLMXII'], + 1963 => ['MLMXIII'], + 1964 => ['MLMXIV'], + 1965 => ['MLMXV'], + 1966 => ['MLMXVI'], + 1967 => ['MLMXVII'], + 1968 => ['MLMXVIII'], + 1969 => ['MLMXIX'], + 1970 => ['MLMXX'], + 1971 => ['MLMXXI'], + 1972 => ['MLMXXII'], + 1973 => ['MLMXXIII'], + 1974 => ['MLMXXIV'], + 1975 => ['MLMXXV'], + 1976 => ['MLMXXVI'], + 1977 => ['MLMXXVII'], + 1978 => ['MLMXXVIII'], + 1979 => ['MLMXXIX'], + 1980 => ['MLMXXX'], + 1981 => ['MLMXXXI'], + 1982 => ['MLMXXXII'], + 1983 => ['MLMXXXIII'], + 1984 => ['MLMXXXIV'], + 1985 => ['MLMXXXV'], + 1986 => ['MLMXXXVI'], + 1987 => ['MLMXXXVII'], + 1988 => ['MLMXXXVIII'], + 1989 => ['MLMXXXIX'], + 1990 => ['MLMXL', 'MXM'], + 1991 => ['MLMXLI', 'MXMI'], + 1992 => ['MLMXLII', 'MXMII'], + 1993 => ['MLMXLIII', 'MXMIII'], + 1994 => ['MLMXLIV', 'MXMIV'], + 1995 => ['MLMVL', 'MXMV', 'MVM'], + 1996 => ['MLMVLI', 'MXMVI', 'MVMI'], + 1997 => ['MLMVLII', 'MXMVII', 'MVMII'], + 1998 => ['MLMVLIII', 'MXMVIII', 'MVMIII'], + 1999 => ['MLMVLIV', 'MXMIX', 'MVMIV', 'MIM'], + 2045 => ['MMVL'], + 2046 => ['MMVLI'], + 2047 => ['MMVLII'], + 2048 => ['MMVLIII'], + 2049 => ['MMVLIV', 'MMIL'], + 2095 => ['MMVC'], + 2096 => ['MMVCI'], + 2097 => ['MMVCII'], + 2098 => ['MMVCIII'], + 2099 => ['MMVCIV', 'MMIC'], + 2145 => ['MMCVL'], + 2146 => ['MMCVLI'], + 2147 => ['MMCVLII'], + 2148 => ['MMCVLIII'], + 2149 => ['MMCVLIV', 'MMCIL'], + 2195 => ['MMCVC'], + 2196 => ['MMCVCI'], + 2197 => ['MMCVCII'], + 2198 => ['MMCVCIII'], + 2199 => ['MMCVCIV', 'MMCIC'], + 2245 => ['MMCCVL'], + 2246 => ['MMCCVLI'], + 2247 => ['MMCCVLII'], + 2248 => ['MMCCVLIII'], + 2249 => ['MMCCVLIV', 'MMCCIL'], + 2295 => ['MMCCVC'], + 2296 => ['MMCCVCI'], + 2297 => ['MMCCVCII'], + 2298 => ['MMCCVCIII'], + 2299 => ['MMCCVCIV', 'MMCCIC'], + 2345 => ['MMCCCVL'], + 2346 => ['MMCCCVLI'], + 2347 => ['MMCCCVLII'], + 2348 => ['MMCCCVLIII'], + 2349 => ['MMCCCVLIV', 'MMCCCIL'], + 2395 => ['MMCCCVC'], + 2396 => ['MMCCCVCI'], + 2397 => ['MMCCCVCII'], + 2398 => ['MMCCCVCIII'], + 2399 => ['MMCCCVCIV', 'MMCCCIC'], + 2445 => ['MMCDVL'], + 2446 => ['MMCDVLI'], + 2447 => ['MMCDVLII'], + 2448 => ['MMCDVLIII'], + 2449 => ['MMCDVLIV', 'MMCDIL'], + 2450 => ['MMLD'], + 2451 => ['MMLDI'], + 2452 => ['MMLDII'], + 2453 => ['MMLDIII'], + 2454 => ['MMLDIV'], + 2455 => ['MMLDV'], + 2456 => ['MMLDVI'], + 2457 => ['MMLDVII'], + 2458 => ['MMLDVIII'], + 2459 => ['MMLDIX'], + 2460 => ['MMLDX'], + 2461 => ['MMLDXI'], + 2462 => ['MMLDXII'], + 2463 => ['MMLDXIII'], + 2464 => ['MMLDXIV'], + 2465 => ['MMLDXV'], + 2466 => ['MMLDXVI'], + 2467 => ['MMLDXVII'], + 2468 => ['MMLDXVIII'], + 2469 => ['MMLDXIX'], + 2470 => ['MMLDXX'], + 2471 => ['MMLDXXI'], + 2472 => ['MMLDXXII'], + 2473 => ['MMLDXXIII'], + 2474 => ['MMLDXXIV'], + 2475 => ['MMLDXXV'], + 2476 => ['MMLDXXVI'], + 2477 => ['MMLDXXVII'], + 2478 => ['MMLDXXVIII'], + 2479 => ['MMLDXXIX'], + 2480 => ['MMLDXXX'], + 2481 => ['MMLDXXXI'], + 2482 => ['MMLDXXXII'], + 2483 => ['MMLDXXXIII'], + 2484 => ['MMLDXXXIV'], + 2485 => ['MMLDXXXV'], + 2486 => ['MMLDXXXVI'], + 2487 => ['MMLDXXXVII'], + 2488 => ['MMLDXXXVIII'], + 2489 => ['MMLDXXXIX'], + 2490 => ['MMLDXL', 'MMXD'], + 2491 => ['MMLDXLI', 'MMXDI'], + 2492 => ['MMLDXLII', 'MMXDII'], + 2493 => ['MMLDXLIII', 'MMXDIII'], + 2494 => ['MMLDXLIV', 'MMXDIV'], + 2495 => ['MMLDVL', 'MMXDV', 'MMVD'], + 2496 => ['MMLDVLI', 'MMXDVI', 'MMVDI'], + 2497 => ['MMLDVLII', 'MMXDVII', 'MMVDII'], + 2498 => ['MMLDVLIII', 'MMXDVIII', 'MMVDIII'], + 2499 => ['MMLDVLIV', 'MMXDIX', 'MMVDIV', 'MMID'], + 2545 => ['MMDVL'], + 2546 => ['MMDVLI'], + 2547 => ['MMDVLII'], + 2548 => ['MMDVLIII'], + 2549 => ['MMDVLIV', 'MMDIL'], + 2595 => ['MMDVC'], + 2596 => ['MMDVCI'], + 2597 => ['MMDVCII'], + 2598 => ['MMDVCIII'], + 2599 => ['MMDVCIV', 'MMDIC'], + 2645 => ['MMDCVL'], + 2646 => ['MMDCVLI'], + 2647 => ['MMDCVLII'], + 2648 => ['MMDCVLIII'], + 2649 => ['MMDCVLIV', 'MMDCIL'], + 2695 => ['MMDCVC'], + 2696 => ['MMDCVCI'], + 2697 => ['MMDCVCII'], + 2698 => ['MMDCVCIII'], + 2699 => ['MMDCVCIV', 'MMDCIC'], + 2745 => ['MMDCCVL'], + 2746 => ['MMDCCVLI'], + 2747 => ['MMDCCVLII'], + 2748 => ['MMDCCVLIII'], + 2749 => ['MMDCCVLIV', 'MMDCCIL'], + 2795 => ['MMDCCVC'], + 2796 => ['MMDCCVCI'], + 2797 => ['MMDCCVCII'], + 2798 => ['MMDCCVCIII'], + 2799 => ['MMDCCVCIV', 'MMDCCIC'], + 2845 => ['MMDCCCVL'], + 2846 => ['MMDCCCVLI'], + 2847 => ['MMDCCCVLII'], + 2848 => ['MMDCCCVLIII'], + 2849 => ['MMDCCCVLIV', 'MMDCCCIL'], + 2895 => ['MMDCCCVC'], + 2896 => ['MMDCCCVCI'], + 2897 => ['MMDCCCVCII'], + 2898 => ['MMDCCCVCIII'], + 2899 => ['MMDCCCVCIV', 'MMDCCCIC'], + 2945 => ['MMCMVL'], + 2946 => ['MMCMVLI'], + 2947 => ['MMCMVLII'], + 2948 => ['MMCMVLIII'], + 2949 => ['MMCMVLIV', 'MMCMIL'], + 2950 => ['MMLM'], + 2951 => ['MMLMI'], + 2952 => ['MMLMII'], + 2953 => ['MMLMIII'], + 2954 => ['MMLMIV'], + 2955 => ['MMLMV'], + 2956 => ['MMLMVI'], + 2957 => ['MMLMVII'], + 2958 => ['MMLMVIII'], + 2959 => ['MMLMIX'], + 2960 => ['MMLMX'], + 2961 => ['MMLMXI'], + 2962 => ['MMLMXII'], + 2963 => ['MMLMXIII'], + 2964 => ['MMLMXIV'], + 2965 => ['MMLMXV'], + 2966 => ['MMLMXVI'], + 2967 => ['MMLMXVII'], + 2968 => ['MMLMXVIII'], + 2969 => ['MMLMXIX'], + 2970 => ['MMLMXX'], + 2971 => ['MMLMXXI'], + 2972 => ['MMLMXXII'], + 2973 => ['MMLMXXIII'], + 2974 => ['MMLMXXIV'], + 2975 => ['MMLMXXV'], + 2976 => ['MMLMXXVI'], + 2977 => ['MMLMXXVII'], + 2978 => ['MMLMXXVIII'], + 2979 => ['MMLMXXIX'], + 2980 => ['MMLMXXX'], + 2981 => ['MMLMXXXI'], + 2982 => ['MMLMXXXII'], + 2983 => ['MMLMXXXIII'], + 2984 => ['MMLMXXXIV'], + 2985 => ['MMLMXXXV'], + 2986 => ['MMLMXXXVI'], + 2987 => ['MMLMXXXVII'], + 2988 => ['MMLMXXXVIII'], + 2989 => ['MMLMXXXIX'], + 2990 => ['MMLMXL', 'MMXM'], + 2991 => ['MMLMXLI', 'MMXMI'], + 2992 => ['MMLMXLII', 'MMXMII'], + 2993 => ['MMLMXLIII', 'MMXMIII'], + 2994 => ['MMLMXLIV', 'MMXMIV'], + 2995 => ['MMLMVL', 'MMXMV', 'MMVM'], + 2996 => ['MMLMVLI', 'MMXMVI', 'MMVMI'], + 2997 => ['MMLMVLII', 'MMXMVII', 'MMVMII'], + 2998 => ['MMLMVLIII', 'MMXMVIII', 'MMVMIII'], + 2999 => ['MMLMVLIV', 'MMXMIX', 'MMVMIV', 'MMIM'], + 3045 => ['MMMVL'], + 3046 => ['MMMVLI'], + 3047 => ['MMMVLII'], + 3048 => ['MMMVLIII'], + 3049 => ['MMMVLIV', 'MMMIL'], + 3095 => ['MMMVC'], + 3096 => ['MMMVCI'], + 3097 => ['MMMVCII'], + 3098 => ['MMMVCIII'], + 3099 => ['MMMVCIV', 'MMMIC'], + 3145 => ['MMMCVL'], + 3146 => ['MMMCVLI'], + 3147 => ['MMMCVLII'], + 3148 => ['MMMCVLIII'], + 3149 => ['MMMCVLIV', 'MMMCIL'], + 3195 => ['MMMCVC'], + 3196 => ['MMMCVCI'], + 3197 => ['MMMCVCII'], + 3198 => ['MMMCVCIII'], + 3199 => ['MMMCVCIV', 'MMMCIC'], + 3245 => ['MMMCCVL'], + 3246 => ['MMMCCVLI'], + 3247 => ['MMMCCVLII'], + 3248 => ['MMMCCVLIII'], + 3249 => ['MMMCCVLIV', 'MMMCCIL'], + 3295 => ['MMMCCVC'], + 3296 => ['MMMCCVCI'], + 3297 => ['MMMCCVCII'], + 3298 => ['MMMCCVCIII'], + 3299 => ['MMMCCVCIV', 'MMMCCIC'], + 3345 => ['MMMCCCVL'], + 3346 => ['MMMCCCVLI'], + 3347 => ['MMMCCCVLII'], + 3348 => ['MMMCCCVLIII'], + 3349 => ['MMMCCCVLIV', 'MMMCCCIL'], + 3395 => ['MMMCCCVC'], + 3396 => ['MMMCCCVCI'], + 3397 => ['MMMCCCVCII'], + 3398 => ['MMMCCCVCIII'], + 3399 => ['MMMCCCVCIV', 'MMMCCCIC'], + 3445 => ['MMMCDVL'], + 3446 => ['MMMCDVLI'], + 3447 => ['MMMCDVLII'], + 3448 => ['MMMCDVLIII'], + 3449 => ['MMMCDVLIV', 'MMMCDIL'], + 3450 => ['MMMLD'], + 3451 => ['MMMLDI'], + 3452 => ['MMMLDII'], + 3453 => ['MMMLDIII'], + 3454 => ['MMMLDIV'], + 3455 => ['MMMLDV'], + 3456 => ['MMMLDVI'], + 3457 => ['MMMLDVII'], + 3458 => ['MMMLDVIII'], + 3459 => ['MMMLDIX'], + 3460 => ['MMMLDX'], + 3461 => ['MMMLDXI'], + 3462 => ['MMMLDXII'], + 3463 => ['MMMLDXIII'], + 3464 => ['MMMLDXIV'], + 3465 => ['MMMLDXV'], + 3466 => ['MMMLDXVI'], + 3467 => ['MMMLDXVII'], + 3468 => ['MMMLDXVIII'], + 3469 => ['MMMLDXIX'], + 3470 => ['MMMLDXX'], + 3471 => ['MMMLDXXI'], + 3472 => ['MMMLDXXII'], + 3473 => ['MMMLDXXIII'], + 3474 => ['MMMLDXXIV'], + 3475 => ['MMMLDXXV'], + 3476 => ['MMMLDXXVI'], + 3477 => ['MMMLDXXVII'], + 3478 => ['MMMLDXXVIII'], + 3479 => ['MMMLDXXIX'], + 3480 => ['MMMLDXXX'], + 3481 => ['MMMLDXXXI'], + 3482 => ['MMMLDXXXII'], + 3483 => ['MMMLDXXXIII'], + 3484 => ['MMMLDXXXIV'], + 3485 => ['MMMLDXXXV'], + 3486 => ['MMMLDXXXVI'], + 3487 => ['MMMLDXXXVII'], + 3488 => ['MMMLDXXXVIII'], + 3489 => ['MMMLDXXXIX'], + 3490 => ['MMMLDXL', 'MMMXD'], + 3491 => ['MMMLDXLI', 'MMMXDI'], + 3492 => ['MMMLDXLII', 'MMMXDII'], + 3493 => ['MMMLDXLIII', 'MMMXDIII'], + 3494 => ['MMMLDXLIV', 'MMMXDIV'], + 3495 => ['MMMLDVL', 'MMMXDV', 'MMMVD'], + 3496 => ['MMMLDVLI', 'MMMXDVI', 'MMMVDI'], + 3497 => ['MMMLDVLII', 'MMMXDVII', 'MMMVDII'], + 3498 => ['MMMLDVLIII', 'MMMXDVIII', 'MMMVDIII'], + 3499 => ['MMMLDVLIV', 'MMMXDIX', 'MMMVDIV', 'MMMID'], + 3545 => ['MMMDVL'], + 3546 => ['MMMDVLI'], + 3547 => ['MMMDVLII'], + 3548 => ['MMMDVLIII'], + 3549 => ['MMMDVLIV', 'MMMDIL'], + 3595 => ['MMMDVC'], + 3596 => ['MMMDVCI'], + 3597 => ['MMMDVCII'], + 3598 => ['MMMDVCIII'], + 3599 => ['MMMDVCIV', 'MMMDIC'], + 3645 => ['MMMDCVL'], + 3646 => ['MMMDCVLI'], + 3647 => ['MMMDCVLII'], + 3648 => ['MMMDCVLIII'], + 3649 => ['MMMDCVLIV', 'MMMDCIL'], + 3695 => ['MMMDCVC'], + 3696 => ['MMMDCVCI'], + 3697 => ['MMMDCVCII'], + 3698 => ['MMMDCVCIII'], + 3699 => ['MMMDCVCIV', 'MMMDCIC'], + 3745 => ['MMMDCCVL'], + 3746 => ['MMMDCCVLI'], + 3747 => ['MMMDCCVLII'], + 3748 => ['MMMDCCVLIII'], + 3749 => ['MMMDCCVLIV', 'MMMDCCIL'], + 3795 => ['MMMDCCVC'], + 3796 => ['MMMDCCVCI'], + 3797 => ['MMMDCCVCII'], + 3798 => ['MMMDCCVCIII'], + 3799 => ['MMMDCCVCIV', 'MMMDCCIC'], + 3845 => ['MMMDCCCVL'], + 3846 => ['MMMDCCCVLI'], + 3847 => ['MMMDCCCVLII'], + 3848 => ['MMMDCCCVLIII'], + 3849 => ['MMMDCCCVLIV', 'MMMDCCCIL'], + 3895 => ['MMMDCCCVC'], + 3896 => ['MMMDCCCVCI'], + 3897 => ['MMMDCCCVCII'], + 3898 => ['MMMDCCCVCIII'], + 3899 => ['MMMDCCCVCIV', 'MMMDCCCIC'], + 3945 => ['MMMCMVL'], + 3946 => ['MMMCMVLI'], + 3947 => ['MMMCMVLII'], + 3948 => ['MMMCMVLIII'], + 3949 => ['MMMCMVLIV', 'MMMCMIL'], + 3950 => ['MMMLM'], + 3951 => ['MMMLMI'], + 3952 => ['MMMLMII'], + 3953 => ['MMMLMIII'], + 3954 => ['MMMLMIV'], + 3955 => ['MMMLMV'], + 3956 => ['MMMLMVI'], + 3957 => ['MMMLMVII'], + 3958 => ['MMMLMVIII'], + 3959 => ['MMMLMIX'], + 3960 => ['MMMLMX'], + 3961 => ['MMMLMXI'], + 3962 => ['MMMLMXII'], + 3963 => ['MMMLMXIII'], + 3964 => ['MMMLMXIV'], + 3965 => ['MMMLMXV'], + 3966 => ['MMMLMXVI'], + 3967 => ['MMMLMXVII'], + 3968 => ['MMMLMXVIII'], + 3969 => ['MMMLMXIX'], + 3970 => ['MMMLMXX'], + 3971 => ['MMMLMXXI'], + 3972 => ['MMMLMXXII'], + 3973 => ['MMMLMXXIII'], + 3974 => ['MMMLMXXIV'], + 3975 => ['MMMLMXXV'], + 3976 => ['MMMLMXXVI'], + 3977 => ['MMMLMXXVII'], + 3978 => ['MMMLMXXVIII'], + 3979 => ['MMMLMXXIX'], + 3980 => ['MMMLMXXX'], + 3981 => ['MMMLMXXXI'], + 3982 => ['MMMLMXXXII'], + 3983 => ['MMMLMXXXIII'], + 3984 => ['MMMLMXXXIV'], + 3985 => ['MMMLMXXXV'], + 3986 => ['MMMLMXXXVI'], + 3987 => ['MMMLMXXXVII'], + 3988 => ['MMMLMXXXVIII'], + 3989 => ['MMMLMXXXIX'], + 3990 => ['MMMLMXL', 'MMMXM'], + 3991 => ['MMMLMXLI', 'MMMXMI'], + 3992 => ['MMMLMXLII', 'MMMXMII'], + 3993 => ['MMMLMXLIII', 'MMMXMIII'], + 3994 => ['MMMLMXLIV', 'MMMXMIV'], + 3995 => ['MMMLMVL', 'MMMXMV', 'MMMVM'], + 3996 => ['MMMLMVLI', 'MMMXMVI', 'MMMVMI'], + 3997 => ['MMMLMVLII', 'MMMXMVII', 'MMMVMII'], + 3998 => ['MMMLMVLIII', 'MMMXMVIII', 'MMMVMIII'], + 3999 => ['MMMLMVLIV', 'MMMXMIX', 'MMMVMIV', 'MMMIM'], + ]; + + private const THOUSANDS = ['', 'M', 'MM', 'MMM']; + private const HUNDREDS = ['', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM']; + private const TENS = ['', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC']; + private const ONES = ['', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX']; + const MAX_ROMAN_VALUE = 3999; + const MAX_ROMAN_STYLE = 4; + + private static function valueOk(int $aValue, int $style): string + { + $origValue = $aValue; + $m = \intdiv($aValue, 1000); + $aValue %= 1000; + $c = \intdiv($aValue, 100); + $aValue %= 100; + $t = \intdiv($aValue, 10); + $aValue %= 10; + $result = self::THOUSANDS[$m] . self::HUNDREDS[$c] . self::TENS[$t] . self::ONES[$aValue]; + if ($style > 0) { + if (array_key_exists($origValue, self::VALUES)) { + $arr = self::VALUES[$origValue]; + $idx = min($style, count($arr)) - 1; + $result = $arr[$idx]; + } + } + + return $result; + } + + private static function styleOk(int $aValue, int $style): string + { + return ($aValue < 0 || $aValue > self::MAX_ROMAN_VALUE) ? Functions::VALUE() : self::valueOk($aValue, $style); + } + + public static function calculateRoman(int $aValue, int $style): string + { + return ($style < 0 || $style > self::MAX_ROMAN_STYLE) ? Functions::VALUE() : self::styleOk($aValue, $style); + } + + /** + * ROMAN. + * + * Converts a number to Roman numeral + * + * @param mixed $aValue Number to convert + * @param mixed $style Number indicating one of five possible forms + * + * @return string Roman numeral, or a string containing an error + */ + public static function evaluate($aValue, $style = 0) + { + try { + $aValue = Helpers::validateNumericNullBool($aValue); + if (is_bool($style)) { + $style = $style ? 0 : 4; + } + $style = Helpers::validateNumericNullSubstitution($style, null); + } catch (Exception $e) { + return $e->getMessage(); + } + + return self::calculateRoman((int) $aValue, (int) $style); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Round.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Round.php new file mode 100644 index 0000000..2ddde90 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Round.php @@ -0,0 +1,179 @@ +getMessage(); + } + + return round($number, (int) $precision); + } + + /** + * ROUNDUP. + * + * Rounds a number up to a specified number of decimal places + * + * @param float $number Number to round + * @param int $digits Number of digits to which you want to round $number + * + * @return float|string Rounded Number, or a string containing an error + */ + public static function up($number, $digits) + { + try { + $number = Helpers::validateNumericNullBool($number); + $digits = (int) Helpers::validateNumericNullSubstitution($digits, null); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($number == 0.0) { + return 0.0; + } + + if ($number < 0.0) { + return round($number - 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_DOWN); + } + + return round($number + 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_DOWN); + } + + /** + * ROUNDDOWN. + * + * Rounds a number down to a specified number of decimal places + * + * @param float $number Number to round + * @param int $digits Number of digits to which you want to round $number + * + * @return float|string Rounded Number, or a string containing an error + */ + public static function down($number, $digits) + { + try { + $number = Helpers::validateNumericNullBool($number); + $digits = (int) Helpers::validateNumericNullSubstitution($digits, null); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($number == 0.0) { + return 0.0; + } + + if ($number < 0.0) { + return round($number + 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_UP); + } + + return round($number - 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_UP); + } + + /** + * MROUND. + * + * Rounds a number to the nearest multiple of a specified value + * + * @param mixed $number Expect float. Number to round. + * @param mixed $multiple Expect int. Multiple to which you want to round. + * + * @return float|string Rounded Number, or a string containing an error + */ + public static function multiple($number, $multiple) + { + try { + $number = Helpers::validateNumericNullSubstitution($number, 0); + $multiple = Helpers::validateNumericNullSubstitution($multiple, null); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($number == 0 || $multiple == 0) { + return 0; + } + if ((Helpers::returnSign($number)) == (Helpers::returnSign($multiple))) { + $multiplier = 1 / $multiple; + + return round($number * $multiplier) / $multiplier; + } + + return Functions::NAN(); + } + + /** + * EVEN. + * + * Returns number rounded up to the nearest even integer. + * You can use this function for processing items that come in twos. For example, + * a packing crate accepts rows of one or two items. The crate is full when + * the number of items, rounded up to the nearest two, matches the crate's + * capacity. + * + * Excel Function: + * EVEN(number) + * + * @param float $number Number to round + * + * @return float|string Rounded Number, or a string containing an error + */ + public static function even($number) + { + try { + $number = Helpers::validateNumericNullBool($number); + } catch (Exception $e) { + return $e->getMessage(); + } + + return Helpers::getEven($number); + } + + /** + * ODD. + * + * Returns number rounded up to the nearest odd integer. + * + * @param float $number Number to round + * + * @return float|string Rounded Number, or a string containing an error + */ + public static function odd($number) + { + try { + $number = Helpers::validateNumericNullBool($number); + } catch (Exception $e) { + return $e->getMessage(); + } + + $significance = Helpers::returnSign($number); + if ($significance == 0) { + return 1; + } + + $result = ceil($number / $significance) * $significance; + if ($result == Helpers::getEven($result)) { + $result += $significance; + } + + return $result; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/SeriesSum.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/SeriesSum.php new file mode 100644 index 0000000..2ada9df --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/SeriesSum.php @@ -0,0 +1,46 @@ +getMessage(); + } + + return $returnValue; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Sign.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Sign.php new file mode 100644 index 0000000..a48cf0f --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Sign.php @@ -0,0 +1,29 @@ +getMessage(); + } + + return Helpers::returnSign($number); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Sqrt.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Sqrt.php new file mode 100644 index 0000000..8ead578 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Sqrt.php @@ -0,0 +1,49 @@ +getMessage(); + } + + return Helpers::numberOrNan(sqrt($number)); + } + + /** + * SQRTPI. + * + * Returns the square root of (number * pi). + * + * @param float $number Number + * + * @return float|string Square Root of Number * Pi, or a string containing an error + */ + public static function pi($number) + { + try { + $number = Helpers::validateNumericNullSubstitution($number, 0); + Helpers::validateNotNegative($number); + } catch (Exception $e) { + return $e->getMessage(); + } + + return sqrt($number * M_PI); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php new file mode 100644 index 0000000..2edb86f --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php @@ -0,0 +1,111 @@ +getWorksheet()->getRowDimension($row)->getVisible(); + }, + ARRAY_FILTER_USE_KEY + ); + } + + /** + * @param mixed $cellReference + * @param mixed $args + */ + protected static function filterFormulaArgs($cellReference, $args): array + { + return array_filter( + $args, + function ($index) use ($cellReference) { + [, $row, $column] = explode('.', $index); + $retVal = true; + if ($cellReference->getWorksheet()->cellExists($column . $row)) { + //take this cell out if it contains the SUBTOTAL or AGGREGATE functions in a formula + $isFormula = $cellReference->getWorksheet()->getCell($column . $row)->isFormula(); + $cellFormula = !preg_match('/^=.*\b(SUBTOTAL|AGGREGATE)\s*\(/i', $cellReference->getWorksheet()->getCell($column . $row)->getValue()); + + $retVal = !$isFormula || $cellFormula; + } + + return $retVal; + }, + ARRAY_FILTER_USE_KEY + ); + } + + /** @var callable[] */ + private const CALL_FUNCTIONS = [ + 1 => [Statistical\Averages::class, 'average'], + [Statistical\Counts::class, 'COUNT'], // 2 + [Statistical\Counts::class, 'COUNTA'], // 3 + [Statistical\Maximum::class, 'max'], // 4 + [Statistical\Minimum::class, 'min'], // 5 + [Operations::class, 'product'], // 6 + [Statistical\StandardDeviations::class, 'STDEV'], // 7 + [Statistical\StandardDeviations::class, 'STDEVP'], // 8 + [Sum::class, 'sumIgnoringStrings'], // 9 + [Statistical\Variances::class, 'VAR'], // 10 + [Statistical\Variances::class, 'VARP'], // 11 + ]; + + /** + * SUBTOTAL. + * + * Returns a subtotal in a list or database. + * + * @param mixed $functionType + * A number 1 to 11 that specifies which function to + * use in calculating subtotals within a range + * list + * Numbers 101 to 111 shadow the functions of 1 to 11 + * but ignore any values in the range that are + * in hidden rows + * @param mixed[] $args A mixed data series of values + * + * @return float|string + */ + public static function evaluate($functionType, ...$args) + { + $cellReference = array_pop($args); + $aArgs = Functions::flattenArrayIndexed($args); + + try { + $subtotal = (int) Helpers::validateNumericNullBool($functionType); + } catch (Exception $e) { + return $e->getMessage(); + } + + // Calculate + if ($subtotal > 100) { + $aArgs = self::filterHiddenArgs($cellReference, $aArgs); + $subtotal -= 100; + } + + $aArgs = self::filterFormulaArgs($cellReference, $aArgs); + if (array_key_exists($subtotal, self::CALL_FUNCTIONS)) { + /** @var callable */ + $call = self::CALL_FUNCTIONS[$subtotal]; + + return call_user_func_array($call, $aArgs); + } + + return Functions::VALUE(); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Sum.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Sum.php new file mode 100644 index 0000000..741734d --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Sum.php @@ -0,0 +1,115 @@ + $arg) { + // Is it a numeric value? + if (is_numeric($arg) || empty($arg)) { + if (is_string($arg)) { + $arg = (int) $arg; + } + $returnValue += $arg; + } elseif (is_bool($arg)) { + $returnValue += (int) $arg; + } elseif (Functions::isError($arg)) { + return $arg; + // ignore non-numerics from cell, but fail as literals (except null) + } elseif ($arg !== null && !Functions::isCellValue($k)) { + return Functions::VALUE(); + } + } + + return $returnValue; + } + + /** + * SUMPRODUCT. + * + * Excel Function: + * SUMPRODUCT(value1[,value2[, ...]]) + * + * @param mixed ...$args Data values + * + * @return float|string The result, or a string containing an error + */ + public static function product(...$args) + { + $arrayList = $args; + + $wrkArray = Functions::flattenArray(array_shift($arrayList)); + $wrkCellCount = count($wrkArray); + + for ($i = 0; $i < $wrkCellCount; ++$i) { + if ((!is_numeric($wrkArray[$i])) || (is_string($wrkArray[$i]))) { + $wrkArray[$i] = 0; + } + } + + foreach ($arrayList as $matrixData) { + $array2 = Functions::flattenArray($matrixData); + $count = count($array2); + if ($wrkCellCount != $count) { + return Functions::VALUE(); + } + + foreach ($array2 as $i => $val) { + if ((!is_numeric($val)) || (is_string($val))) { + $val = 0; + } + $wrkArray[$i] *= $val; + } + } + + return array_sum($wrkArray); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/SumSquares.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/SumSquares.php new file mode 100644 index 0000000..49fa638 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/SumSquares.php @@ -0,0 +1,142 @@ +getMessage(); + } + + return $returnValue; + } + + private static function getCount(array $array1, array $array2): int + { + $count = count($array1); + if ($count !== count($array2)) { + throw new Exception(Functions::NA()); + } + + return $count; + } + + /** + * These functions accept only numeric arguments, not even strings which are numeric. + * + * @param mixed $item + */ + private static function numericNotString($item): bool + { + return is_numeric($item) && !is_string($item); + } + + /** + * SUMX2MY2. + * + * @param mixed[] $matrixData1 Matrix #1 + * @param mixed[] $matrixData2 Matrix #2 + * + * @return float|string + */ + public static function sumXSquaredMinusYSquared($matrixData1, $matrixData2) + { + try { + $array1 = Functions::flattenArray($matrixData1); + $array2 = Functions::flattenArray($matrixData2); + $count = self::getCount($array1, $array2); + + $result = 0; + for ($i = 0; $i < $count; ++$i) { + if (self::numericNotString($array1[$i]) && self::numericNotString($array2[$i])) { + $result += ($array1[$i] * $array1[$i]) - ($array2[$i] * $array2[$i]); + } + } + } catch (Exception $e) { + return $e->getMessage(); + } + + return $result; + } + + /** + * SUMX2PY2. + * + * @param mixed[] $matrixData1 Matrix #1 + * @param mixed[] $matrixData2 Matrix #2 + * + * @return float|string + */ + public static function sumXSquaredPlusYSquared($matrixData1, $matrixData2) + { + try { + $array1 = Functions::flattenArray($matrixData1); + $array2 = Functions::flattenArray($matrixData2); + $count = self::getCount($array1, $array2); + + $result = 0; + for ($i = 0; $i < $count; ++$i) { + if (self::numericNotString($array1[$i]) && self::numericNotString($array2[$i])) { + $result += ($array1[$i] * $array1[$i]) + ($array2[$i] * $array2[$i]); + } + } + } catch (Exception $e) { + return $e->getMessage(); + } + + return $result; + } + + /** + * SUMXMY2. + * + * @param mixed[] $matrixData1 Matrix #1 + * @param mixed[] $matrixData2 Matrix #2 + * + * @return float|string + */ + public static function sumXMinusYSquared($matrixData1, $matrixData2) + { + try { + $array1 = Functions::flattenArray($matrixData1); + $array2 = Functions::flattenArray($matrixData2); + $count = self::getCount($array1, $array2); + + $result = 0; + for ($i = 0; $i < $count; ++$i) { + if (self::numericNotString($array1[$i]) && self::numericNotString($array2[$i])) { + $result += ($array1[$i] - $array2[$i]) * ($array1[$i] - $array2[$i]); + } + } + } catch (Exception $e) { + return $e->getMessage(); + } + + return $result; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Cosecant.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Cosecant.php new file mode 100644 index 0000000..3038e6c --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Cosecant.php @@ -0,0 +1,49 @@ +getMessage(); + } + + return Helpers::verySmallDenominator(1.0, sin($angle)); + } + + /** + * CSCH. + * + * Returns the hyperbolic cosecant of an angle. + * + * @param float $angle Number + * + * @return float|string The hyperbolic cosecant of the angle + */ + public static function csch($angle) + { + try { + $angle = Helpers::validateNumericNullBool($angle); + } catch (Exception $e) { + return $e->getMessage(); + } + + return Helpers::verySmallDenominator(1.0, sinh($angle)); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Cosine.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Cosine.php new file mode 100644 index 0000000..6c69e12 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Cosine.php @@ -0,0 +1,89 @@ +getMessage(); + } + + return cos($number); + } + + /** + * COSH. + * + * Returns the result of builtin function cosh after validating args. + * + * @param mixed $number Should be numeric + * + * @return float|string hyperbolic cosine + */ + public static function cosh($number) + { + try { + $number = Helpers::validateNumericNullBool($number); + } catch (Exception $e) { + return $e->getMessage(); + } + + return cosh($number); + } + + /** + * ACOS. + * + * Returns the arccosine of a number. + * + * @param float $number Number + * + * @return float|string The arccosine of the number + */ + public static function acos($number) + { + try { + $number = Helpers::validateNumericNullBool($number); + } catch (Exception $e) { + return $e->getMessage(); + } + + return Helpers::numberOrNan(acos($number)); + } + + /** + * ACOSH. + * + * Returns the arc inverse hyperbolic cosine of a number. + * + * @param float $number Number + * + * @return float|string The inverse hyperbolic cosine of the number, or an error string + */ + public static function acosh($number) + { + try { + $number = Helpers::validateNumericNullBool($number); + } catch (Exception $e) { + return $e->getMessage(); + } + + return Helpers::numberOrNan(acosh($number)); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Cotangent.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Cotangent.php new file mode 100644 index 0000000..1b796f5 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Cotangent.php @@ -0,0 +1,91 @@ +getMessage(); + } + + return Helpers::verySmallDenominator(cos($angle), sin($angle)); + } + + /** + * COTH. + * + * Returns the hyperbolic cotangent of an angle. + * + * @param float $angle Number + * + * @return float|string The hyperbolic cotangent of the angle + */ + public static function coth($angle) + { + try { + $angle = Helpers::validateNumericNullBool($angle); + } catch (Exception $e) { + return $e->getMessage(); + } + + return Helpers::verySmallDenominator(1.0, tanh($angle)); + } + + /** + * ACOT. + * + * Returns the arccotangent of a number. + * + * @param float $number Number + * + * @return float|string The arccotangent of the number + */ + public static function acot($number) + { + try { + $number = Helpers::validateNumericNullBool($number); + } catch (Exception $e) { + return $e->getMessage(); + } + + return (M_PI / 2) - atan($number); + } + + /** + * ACOTH. + * + * Returns the hyperbolic arccotangent of a number. + * + * @param float $number Number + * + * @return float|string The hyperbolic arccotangent of the number + */ + public static function acoth($number) + { + try { + $number = Helpers::validateNumericNullBool($number); + } catch (Exception $e) { + return $e->getMessage(); + } + + $result = ($number === 1) ? NAN : (log(($number + 1) / ($number - 1)) / 2); + + return Helpers::numberOrNan($result); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Secant.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Secant.php new file mode 100644 index 0000000..70299cb --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Secant.php @@ -0,0 +1,49 @@ +getMessage(); + } + + return Helpers::verySmallDenominator(1.0, cos($angle)); + } + + /** + * SECH. + * + * Returns the hyperbolic secant of an angle. + * + * @param float $angle Number + * + * @return float|string The hyperbolic secant of the angle + */ + public static function sech($angle) + { + try { + $angle = Helpers::validateNumericNullBool($angle); + } catch (Exception $e) { + return $e->getMessage(); + } + + return Helpers::verySmallDenominator(1.0, cosh($angle)); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Sine.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Sine.php new file mode 100644 index 0000000..2c6a8a0 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Sine.php @@ -0,0 +1,89 @@ +getMessage(); + } + + return sin($angle); + } + + /** + * SINH. + * + * Returns the result of builtin function sinh after validating args. + * + * @param mixed $angle Should be numeric + * + * @return float|string hyperbolic sine + */ + public static function sinh($angle) + { + try { + $angle = Helpers::validateNumericNullBool($angle); + } catch (Exception $e) { + return $e->getMessage(); + } + + return sinh($angle); + } + + /** + * ASIN. + * + * Returns the arcsine of a number. + * + * @param float $number Number + * + * @return float|string The arcsine of the number + */ + public static function asin($number) + { + try { + $number = Helpers::validateNumericNullBool($number); + } catch (Exception $e) { + return $e->getMessage(); + } + + return Helpers::numberOrNan(asin($number)); + } + + /** + * ASINH. + * + * Returns the inverse hyperbolic sine of a number. + * + * @param float $number Number + * + * @return float|string The inverse hyperbolic sine of the number + */ + public static function asinh($number) + { + try { + $number = Helpers::validateNumericNullBool($number); + } catch (Exception $e) { + return $e->getMessage(); + } + + return Helpers::numberOrNan(asinh($number)); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Tangent.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Tangent.php new file mode 100644 index 0000000..6cd235f --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Tangent.php @@ -0,0 +1,127 @@ +getMessage(); + } + + return Helpers::verySmallDenominator(sin($angle), cos($angle)); + } + + /** + * TANH. + * + * Returns the result of builtin function sinh after validating args. + * + * @param mixed $angle Should be numeric + * + * @return float|string hyperbolic tangent + */ + public static function tanh($angle) + { + try { + $angle = Helpers::validateNumericNullBool($angle); + } catch (Exception $e) { + return $e->getMessage(); + } + + return tanh($angle); + } + + /** + * ATAN. + * + * Returns the arctangent of a number. + * + * @param float $number Number + * + * @return float|string The arctangent of the number + */ + public static function atan($number) + { + try { + $number = Helpers::validateNumericNullBool($number); + } catch (Exception $e) { + return $e->getMessage(); + } + + return Helpers::numberOrNan(atan($number)); + } + + /** + * ATANH. + * + * Returns the inverse hyperbolic tangent of a number. + * + * @param float $number Number + * + * @return float|string The inverse hyperbolic tangent of the number + */ + public static function atanh($number) + { + try { + $number = Helpers::validateNumericNullBool($number); + } catch (Exception $e) { + return $e->getMessage(); + } + + return Helpers::numberOrNan(atanh($number)); + } + + /** + * ATAN2. + * + * This function calculates the arc tangent of the two variables x and y. It is similar to + * calculating the arc tangent of y ÷ x, except that the signs of both arguments are used + * to determine the quadrant of the result. + * The arctangent is the angle from the x-axis to a line containing the origin (0, 0) and a + * point with coordinates (xCoordinate, yCoordinate). The angle is given in radians between + * -pi and pi, excluding -pi. + * + * Note that the Excel ATAN2() function accepts its arguments in the reverse order to the standard + * PHP atan2() function, so we need to reverse them here before calling the PHP atan() function. + * + * Excel Function: + * ATAN2(xCoordinate,yCoordinate) + * + * @param mixed $xCoordinate should be float, the x-coordinate of the point + * @param mixed $yCoordinate should be float, the y-coordinate of the point + * + * @return float|string the inverse tangent of the specified x- and y-coordinates, or a string containing an error + */ + public static function atan2($xCoordinate, $yCoordinate) + { + try { + $xCoordinate = Helpers::validateNumericNullBool($xCoordinate); + $yCoordinate = Helpers::validateNumericNullBool($yCoordinate); + } catch (Exception $e) { + return $e->getMessage(); + } + + if (($xCoordinate == 0) && ($yCoordinate == 0)) { + return Functions::DIV0(); + } + + return atan2($yCoordinate, $xCoordinate); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trunc.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trunc.php new file mode 100644 index 0000000..4b3670f --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trunc.php @@ -0,0 +1,39 @@ +getMessage(); + } + + $digits = floor($digits); + + // Truncate + $adjust = 10 ** $digits; + + if (($digits > 0) && (rtrim((string) (int) ((abs($value) - abs((int) $value)) * $adjust), '0') < $adjust / 10)) { + return $value; + } + + return ((int) ($value * $adjust)) / $adjust; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical.php new file mode 100644 index 0000000..d43a85f --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical.php @@ -0,0 +1,1820 @@ + $arg) { + $arg = self::testAcceptedBoolean($arg, $k); + // Is it a numeric value? + // Strings containing numeric values are only counted if they are string literals (not cell values) + // and then only in MS Excel and in Open Office, not in Gnumeric + if ((is_string($arg)) && (!is_numeric($arg)) && (!Functions::isCellValue($k))) { + return Functions::VALUE(); + } + if (self::isAcceptedCountable($arg, $k)) { + $returnValue += abs($arg - $aMean); + ++$aCount; + } + } + + // Return + if ($aCount === 0) { + return Functions::DIV0(); + } + + return $returnValue / $aCount; + } + + /** + * AVERAGE. + * + * Returns the average (arithmetic mean) of the arguments + * + * Excel Function: + * AVERAGE(value1[,value2[, ...]]) + * + * @param mixed ...$args Data values + * + * @return float|string (string if result is an error) + */ + public static function average(...$args) + { + $returnValue = $aCount = 0; + + // Loop through arguments + foreach (Functions::flattenArrayIndexed($args) as $k => $arg) { + $arg = self::testAcceptedBoolean($arg, $k); + // Is it a numeric value? + // Strings containing numeric values are only counted if they are string literals (not cell values) + // and then only in MS Excel and in Open Office, not in Gnumeric + if ((is_string($arg)) && (!is_numeric($arg)) && (!Functions::isCellValue($k))) { + return Functions::VALUE(); + } + if (self::isAcceptedCountable($arg, $k)) { + $returnValue += $arg; + ++$aCount; + } + } + + // Return + if ($aCount > 0) { + return $returnValue / $aCount; + } + + return Functions::DIV0(); + } + + /** + * AVERAGEA. + * + * Returns the average of its arguments, including numbers, text, and logical values + * + * Excel Function: + * AVERAGEA(value1[,value2[, ...]]) + * + * @param mixed ...$args Data values + * + * @return float|string (string if result is an error) + */ + public static function averageA(...$args) + { + $returnValue = null; + + $aCount = 0; + // Loop through arguments + foreach (Functions::flattenArrayIndexed($args) as $k => $arg) { + if ((is_bool($arg)) && (!Functions::isMatrixValue($k))) { + } else { + if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) && ($arg != '')))) { + if (is_bool($arg)) { + $arg = (int) $arg; + } elseif (is_string($arg)) { + $arg = 0; + } + $returnValue += $arg; + ++$aCount; + } + } + } + + if ($aCount > 0) { + return $returnValue / $aCount; + } + + return Functions::DIV0(); + } + + /** + * MEDIAN. + * + * Returns the median of the given numbers. The median is the number in the middle of a set of numbers. + * + * Excel Function: + * MEDIAN(value1[,value2[, ...]]) + * + * @param mixed ...$args Data values + * + * @return float|string The result, or a string containing an error + */ + public static function median(...$args) + { + $aArgs = Functions::flattenArray($args); + + $returnValue = Functions::NAN(); + + $aArgs = self::filterArguments($aArgs); + $valueCount = count($aArgs); + if ($valueCount > 0) { + sort($aArgs, SORT_NUMERIC); + $valueCount = $valueCount / 2; + if ($valueCount == floor($valueCount)) { + $returnValue = ($aArgs[$valueCount--] + $aArgs[$valueCount]) / 2; + } else { + $valueCount = floor($valueCount); + $returnValue = $aArgs[$valueCount]; + } + } + + return $returnValue; + } + + /** + * MODE. + * + * Returns the most frequently occurring, or repetitive, value in an array or range of data + * + * Excel Function: + * MODE(value1[,value2[, ...]]) + * + * @param mixed ...$args Data values + * + * @return float|string The result, or a string containing an error + */ + public static function mode(...$args) + { + $returnValue = Functions::NA(); + + // Loop through arguments + $aArgs = Functions::flattenArray($args); + $aArgs = self::filterArguments($aArgs); + + if (!empty($aArgs)) { + return self::modeCalc($aArgs); + } + + return $returnValue; + } + + protected static function filterArguments($args) + { + return array_filter( + $args, + function ($value) { + // Is it a numeric value? + return (is_numeric($value)) && (!is_string($value)); + } + ); + } + + // + // Special variant of array_count_values that isn't limited to strings and integers, + // but can work with floating point numbers as values + // + private static function modeCalc($data) + { + $frequencyArray = []; + $index = 0; + $maxfreq = 0; + $maxfreqkey = ''; + $maxfreqdatum = ''; + foreach ($data as $datum) { + $found = false; + ++$index; + foreach ($frequencyArray as $key => $value) { + if ((string) $value['value'] == (string) $datum) { + ++$frequencyArray[$key]['frequency']; + $freq = $frequencyArray[$key]['frequency']; + if ($freq > $maxfreq) { + $maxfreq = $freq; + $maxfreqkey = $key; + $maxfreqdatum = $datum; + } elseif ($freq == $maxfreq) { + if ($frequencyArray[$key]['index'] < $frequencyArray[$maxfreqkey]['index']) { + $maxfreqkey = $key; + $maxfreqdatum = $datum; + } + } + $found = true; + + break; + } + } + + if ($found === false) { + $frequencyArray[] = [ + 'value' => $datum, + 'frequency' => 1, + 'index' => $index, + ]; + } + } + + if ($maxfreq <= 1) { + return Functions::NA(); + } + + return $maxfreqdatum; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Averages/Mean.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Averages/Mean.php new file mode 100644 index 0000000..d4d0c7e --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Averages/Mean.php @@ -0,0 +1,131 @@ + 0)) { + $aCount = Counts::COUNT($aArgs); + if (Minimum::min($aArgs) > 0) { + return $aMean ** (1 / $aCount); + } + } + + return Functions::NAN(); + } + + /** + * HARMEAN. + * + * Returns the harmonic mean of a data set. The harmonic mean is the reciprocal of the + * arithmetic mean of reciprocals. + * + * Excel Function: + * HARMEAN(value1[,value2[, ...]]) + * + * @param mixed ...$args Data values + * + * @return float|string + */ + public static function harmonic(...$args) + { + // Loop through arguments + $aArgs = Functions::flattenArray($args); + if (Minimum::min($aArgs) < 0) { + return Functions::NAN(); + } + + $returnValue = 0; + $aCount = 0; + foreach ($aArgs as $arg) { + // Is it a numeric value? + if ((is_numeric($arg)) && (!is_string($arg))) { + if ($arg <= 0) { + return Functions::NAN(); + } + $returnValue += (1 / $arg); + ++$aCount; + } + } + + // Return + if ($aCount > 0) { + return 1 / ($returnValue / $aCount); + } + + return Functions::NA(); + } + + /** + * TRIMMEAN. + * + * Returns the mean of the interior of a data set. TRIMMEAN calculates the mean + * taken by excluding a percentage of data points from the top and bottom tails + * of a data set. + * + * Excel Function: + * TRIMEAN(value1[,value2[, ...]], $discard) + * + * @param mixed $args Data values + * + * @return float|string + */ + public static function trim(...$args) + { + $aArgs = Functions::flattenArray($args); + + // Calculate + $percent = array_pop($aArgs); + + if ((is_numeric($percent)) && (!is_string($percent))) { + if (($percent < 0) || ($percent > 1)) { + return Functions::NAN(); + } + + $mArgs = []; + foreach ($aArgs as $arg) { + // Is it a numeric value? + if ((is_numeric($arg)) && (!is_string($arg))) { + $mArgs[] = $arg; + } + } + + $discard = floor(Counts::COUNT($mArgs) * $percent / 2); + sort($mArgs); + + for ($i = 0; $i < $discard; ++$i) { + array_pop($mArgs); + array_shift($mArgs); + } + + return Averages::average($mArgs); + } + + return Functions::VALUE(); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Conditional.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Conditional.php new file mode 100644 index 0000000..51e6b00 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Conditional.php @@ -0,0 +1,304 @@ +getMessage(); + } + + if (($alpha <= 0) || ($alpha >= 1) || ($stdDev <= 0) || ($size < 1)) { + return Functions::NAN(); + } + + return Distributions\StandardNormal::inverse(1 - $alpha / 2) * $stdDev / sqrt($size); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Counts.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Counts.php new file mode 100644 index 0000000..13e7af7 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Counts.php @@ -0,0 +1,95 @@ + $arg) { + $arg = self::testAcceptedBoolean($arg, $k); + // Is it a numeric value? + // Strings containing numeric values are only counted if they are string literals (not cell values) + // and then only in MS Excel and in Open Office, not in Gnumeric + if (self::isAcceptedCountable($arg, $k)) { + ++$returnValue; + } + } + + return $returnValue; + } + + /** + * COUNTA. + * + * Counts the number of cells that are not empty within the list of arguments + * + * Excel Function: + * COUNTA(value1[,value2[, ...]]) + * + * @param mixed ...$args Data values + * + * @return int + */ + public static function COUNTA(...$args) + { + $returnValue = 0; + + // Loop through arguments + $aArgs = Functions::flattenArrayIndexed($args); + foreach ($aArgs as $k => $arg) { + // Nulls are counted if literals, but not if cell values + if ($arg !== null || (!Functions::isCellValue($k))) { + ++$returnValue; + } + } + + return $returnValue; + } + + /** + * COUNTBLANK. + * + * Counts the number of empty cells within the list of arguments + * + * Excel Function: + * COUNTBLANK(value1[,value2[, ...]]) + * + * @param mixed ...$args Data values + * + * @return int + */ + public static function COUNTBLANK(...$args) + { + $returnValue = 0; + + // Loop through arguments + $aArgs = Functions::flattenArray($args); + foreach ($aArgs as $arg) { + // Is it a blank cell? + if (($arg === null) || ((is_string($arg)) && ($arg == ''))) { + ++$returnValue; + } + } + + return $returnValue; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Deviations.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Deviations.php new file mode 100644 index 0000000..55da9f1 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Deviations.php @@ -0,0 +1,141 @@ + $arg) { + // Is it a numeric value? + if ( + (is_bool($arg)) && + ((!Functions::isCellValue($k)) || + (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE)) + ) { + $arg = (int) $arg; + } + if ((is_numeric($arg)) && (!is_string($arg))) { + $returnValue += ($arg - $aMean) ** 2; + ++$aCount; + } + } + + return $aCount === 0 ? Functions::VALUE() : $returnValue; + } + + /** + * KURT. + * + * Returns the kurtosis of a data set. Kurtosis characterizes the relative peakedness + * or flatness of a distribution compared with the normal distribution. Positive + * kurtosis indicates a relatively peaked distribution. Negative kurtosis indicates a + * relatively flat distribution. + * + * @param array ...$args Data Series + * + * @return float|string + */ + public static function kurtosis(...$args) + { + $aArgs = Functions::flattenArrayIndexed($args); + $mean = Averages::average($aArgs); + if (!is_numeric($mean)) { + return Functions::DIV0(); + } + $stdDev = StandardDeviations::STDEV($aArgs); + + if ($stdDev > 0) { + $count = $summer = 0; + + foreach ($aArgs as $k => $arg) { + if ((is_bool($arg)) && (!Functions::isMatrixValue($k))) { + } else { + // Is it a numeric value? + if ((is_numeric($arg)) && (!is_string($arg))) { + $summer += (($arg - $mean) / $stdDev) ** 4; + ++$count; + } + } + } + + if ($count > 3) { + return $summer * ($count * ($count + 1) / + (($count - 1) * ($count - 2) * ($count - 3))) - (3 * ($count - 1) ** 2 / + (($count - 2) * ($count - 3))); + } + } + + return Functions::DIV0(); + } + + /** + * SKEW. + * + * Returns the skewness of a distribution. Skewness characterizes the degree of asymmetry + * of a distribution around its mean. Positive skewness indicates a distribution with an + * asymmetric tail extending toward more positive values. Negative skewness indicates a + * distribution with an asymmetric tail extending toward more negative values. + * + * @param array ...$args Data Series + * + * @return float|int|string The result, or a string containing an error + */ + public static function skew(...$args) + { + $aArgs = Functions::flattenArrayIndexed($args); + $mean = Averages::average($aArgs); + if (!is_numeric($mean)) { + return Functions::DIV0(); + } + $stdDev = StandardDeviations::STDEV($aArgs); + if ($stdDev === 0.0 || is_string($stdDev)) { + return Functions::DIV0(); + } + + $count = $summer = 0; + // Loop through arguments + foreach ($aArgs as $k => $arg) { + if ((is_bool($arg)) && (!Functions::isMatrixValue($k))) { + } elseif (!is_numeric($arg)) { + return Functions::VALUE(); + } else { + // Is it a numeric value? + if ((is_numeric($arg)) && (!is_string($arg))) { + $summer += (($arg - $mean) / $stdDev) ** 3; + ++$count; + } + } + } + + if ($count > 2) { + return $summer * ($count / (($count - 1) * ($count - 2))); + } + + return Functions::DIV0(); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Beta.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Beta.php new file mode 100644 index 0000000..63e6eb4 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Beta.php @@ -0,0 +1,260 @@ +getMessage(); + } + + if ($rMin > $rMax) { + $tmp = $rMin; + $rMin = $rMax; + $rMax = $tmp; + } + if (($value < $rMin) || ($value > $rMax) || ($alpha <= 0) || ($beta <= 0) || ($rMin == $rMax)) { + return Functions::NAN(); + } + + $value -= $rMin; + $value /= ($rMax - $rMin); + + return self::incompleteBeta($value, $alpha, $beta); + } + + /** + * BETAINV. + * + * Returns the inverse of the Beta distribution. + * + * @param mixed $probability Float probability at which you want to evaluate the distribution + * @param mixed $alpha Parameter to the distribution as a float + * @param mixed $beta Parameter to the distribution as a float + * @param mixed $rMin Minimum value as a float + * @param mixed $rMax Maximum value as a float + * + * @return float|string + */ + public static function inverse($probability, $alpha, $beta, $rMin = 0.0, $rMax = 1.0) + { + $probability = Functions::flattenSingleValue($probability); + $alpha = Functions::flattenSingleValue($alpha); + $beta = Functions::flattenSingleValue($beta); + $rMin = ($rMin === null) ? 0.0 : Functions::flattenSingleValue($rMin); + $rMax = ($rMax === null) ? 1.0 : Functions::flattenSingleValue($rMax); + + try { + $probability = DistributionValidations::validateProbability($probability); + $alpha = DistributionValidations::validateFloat($alpha); + $beta = DistributionValidations::validateFloat($beta); + $rMax = DistributionValidations::validateFloat($rMax); + $rMin = DistributionValidations::validateFloat($rMin); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($rMin > $rMax) { + $tmp = $rMin; + $rMin = $rMax; + $rMax = $tmp; + } + if (($alpha <= 0) || ($beta <= 0) || ($rMin == $rMax) || ($probability <= 0.0)) { + return Functions::NAN(); + } + + return self::calculateInverse($probability, $alpha, $beta, $rMin, $rMax); + } + + /** + * @return float|string + */ + private static function calculateInverse(float $probability, float $alpha, float $beta, float $rMin, float $rMax) + { + $a = 0; + $b = 2; + + $i = 0; + while ((($b - $a) > Functions::PRECISION) && (++$i <= self::MAX_ITERATIONS)) { + $guess = ($a + $b) / 2; + $result = self::distribution($guess, $alpha, $beta); + if (($result === $probability) || ($result === 0.0)) { + $b = $a; + } elseif ($result > $probability) { + $b = $guess; + } else { + $a = $guess; + } + } + + if ($i === self::MAX_ITERATIONS) { + return Functions::NA(); + } + + return round($rMin + $guess * ($rMax - $rMin), 12); + } + + /** + * Incomplete beta function. + * + * @author Jaco van Kooten + * @author Paul Meagher + * + * The computation is based on formulas from Numerical Recipes, Chapter 6.4 (W.H. Press et al, 1992). + * + * @param float $x require 0<=x<=1 + * @param float $p require p>0 + * @param float $q require q>0 + * + * @return float 0 if x<0, p<=0, q<=0 or p+q>2.55E305 and 1 if x>1 to avoid errors and over/underflow + */ + public static function incompleteBeta(float $x, float $p, float $q): float + { + if ($x <= 0.0) { + return 0.0; + } elseif ($x >= 1.0) { + return 1.0; + } elseif (($p <= 0.0) || ($q <= 0.0) || (($p + $q) > self::LOG_GAMMA_X_MAX_VALUE)) { + return 0.0; + } + + $beta_gam = exp((0 - self::logBeta($p, $q)) + $p * log($x) + $q * log(1.0 - $x)); + if ($x < ($p + 1.0) / ($p + $q + 2.0)) { + return $beta_gam * self::betaFraction($x, $p, $q) / $p; + } + + return 1.0 - ($beta_gam * self::betaFraction(1 - $x, $q, $p) / $q); + } + + // Function cache for logBeta function + private static $logBetaCacheP = 0.0; + + private static $logBetaCacheQ = 0.0; + + private static $logBetaCacheResult = 0.0; + + /** + * The natural logarithm of the beta function. + * + * @param float $p require p>0 + * @param float $q require q>0 + * + * @return float 0 if p<=0, q<=0 or p+q>2.55E305 to avoid errors and over/underflow + * + * @author Jaco van Kooten + */ + private static function logBeta(float $p, float $q): float + { + if ($p != self::$logBetaCacheP || $q != self::$logBetaCacheQ) { + self::$logBetaCacheP = $p; + self::$logBetaCacheQ = $q; + if (($p <= 0.0) || ($q <= 0.0) || (($p + $q) > self::LOG_GAMMA_X_MAX_VALUE)) { + self::$logBetaCacheResult = 0.0; + } else { + self::$logBetaCacheResult = Gamma::logGamma($p) + Gamma::logGamma($q) - Gamma::logGamma($p + $q); + } + } + + return self::$logBetaCacheResult; + } + + /** + * Evaluates of continued fraction part of incomplete beta function. + * Based on an idea from Numerical Recipes (W.H. Press et al, 1992). + * + * @author Jaco van Kooten + */ + private static function betaFraction(float $x, float $p, float $q): float + { + $c = 1.0; + $sum_pq = $p + $q; + $p_plus = $p + 1.0; + $p_minus = $p - 1.0; + $h = 1.0 - $sum_pq * $x / $p_plus; + if (abs($h) < self::XMININ) { + $h = self::XMININ; + } + $h = 1.0 / $h; + $frac = $h; + $m = 1; + $delta = 0.0; + while ($m <= self::MAX_ITERATIONS && abs($delta - 1.0) > Functions::PRECISION) { + $m2 = 2 * $m; + // even index for d + $d = $m * ($q - $m) * $x / (($p_minus + $m2) * ($p + $m2)); + $h = 1.0 + $d * $h; + if (abs($h) < self::XMININ) { + $h = self::XMININ; + } + $h = 1.0 / $h; + $c = 1.0 + $d / $c; + if (abs($c) < self::XMININ) { + $c = self::XMININ; + } + $frac *= $h * $c; + // odd index for d + $d = -($p + $m) * ($sum_pq + $m) * $x / (($p + $m2) * ($p_plus + $m2)); + $h = 1.0 + $d * $h; + if (abs($h) < self::XMININ) { + $h = self::XMININ; + } + $h = 1.0 / $h; + $c = 1.0 + $d / $c; + if (abs($c) < self::XMININ) { + $c = self::XMININ; + } + $delta = $h * $c; + $frac *= $delta; + ++$m; + } + + return $frac; + } + + private static function betaValue(float $a, float $b): float + { + return (Gamma::gammaValue($a) * Gamma::gammaValue($b)) / + Gamma::gammaValue($a + $b); + } + + private static function regularizedIncompleteBeta(float $value, float $a, float $b): float + { + return self::incompleteBeta($value, $a, $b) / self::betaValue($a, $b); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Binomial.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Binomial.php new file mode 100644 index 0000000..9631236 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Binomial.php @@ -0,0 +1,202 @@ +getMessage(); + } + + if (($value < 0) || ($value > $trials)) { + return Functions::NAN(); + } + + if ($cumulative) { + return self::calculateCumulativeBinomial($value, $trials, $probability); + } + + return Combinations::withoutRepetition($trials, $value) * $probability ** $value + * (1 - $probability) ** ($trials - $value); + } + + /** + * BINOM.DIST.RANGE. + * + * Returns returns the Binomial Distribution probability for the number of successes from a specified number + * of trials falling into a specified range. + * + * @param mixed $trials Integer number of trials + * @param mixed $probability Probability of success on each trial as a float + * @param mixed $successes The integer number of successes in trials + * @param mixed $limit Upper limit for successes in trials as null, or an integer + * If null, then this will indicate the same as the number of Successes + * + * @return float|string + */ + public static function range($trials, $probability, $successes, $limit = null) + { + $trials = Functions::flattenSingleValue($trials); + $probability = Functions::flattenSingleValue($probability); + $successes = Functions::flattenSingleValue($successes); + $limit = ($limit === null) ? $successes : Functions::flattenSingleValue($limit); + + try { + $trials = DistributionValidations::validateInt($trials); + $probability = DistributionValidations::validateProbability($probability); + $successes = DistributionValidations::validateInt($successes); + $limit = DistributionValidations::validateInt($limit); + } catch (Exception $e) { + return $e->getMessage(); + } + + if (($successes < 0) || ($successes > $trials)) { + return Functions::NAN(); + } + if (($limit < 0) || ($limit > $trials) || $limit < $successes) { + return Functions::NAN(); + } + + $summer = 0; + for ($i = $successes; $i <= $limit; ++$i) { + $summer += Combinations::withoutRepetition($trials, $i) * $probability ** $i + * (1 - $probability) ** ($trials - $i); + } + + return $summer; + } + + /** + * NEGBINOMDIST. + * + * Returns the negative binomial distribution. NEGBINOMDIST returns the probability that + * there will be number_f failures before the number_s-th success, when the constant + * probability of a success is probability_s. This function is similar to the binomial + * distribution, except that the number of successes is fixed, and the number of trials is + * variable. Like the binomial, trials are assumed to be independent. + * + * @param mixed $failures Number of Failures as an integer + * @param mixed $successes Threshold number of Successes as an integer + * @param mixed $probability Probability of success on each trial as a float + * + * @return float|string The result, or a string containing an error + * + * TODO Add support for the cumulative flag not present for NEGBINOMDIST, but introduced for NEGBINOM.DIST + * The cumulative default should be false to reflect the behaviour of NEGBINOMDIST + */ + public static function negative($failures, $successes, $probability) + { + $failures = Functions::flattenSingleValue($failures); + $successes = Functions::flattenSingleValue($successes); + $probability = Functions::flattenSingleValue($probability); + + try { + $failures = DistributionValidations::validateInt($failures); + $successes = DistributionValidations::validateInt($successes); + $probability = DistributionValidations::validateProbability($probability); + } catch (Exception $e) { + return $e->getMessage(); + } + + if (($failures < 0) || ($successes < 1)) { + return Functions::NAN(); + } + if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) { + if (($failures + $successes - 1) <= 0) { + return Functions::NAN(); + } + } + + return (Combinations::withoutRepetition($failures + $successes - 1, $successes - 1)) + * ($probability ** $successes) * ((1 - $probability) ** $failures); + } + + /** + * CRITBINOM. + * + * Returns the smallest value for which the cumulative binomial distribution is greater + * than or equal to a criterion value + * + * @param mixed $trials number of Bernoulli trials as an integer + * @param mixed $probability probability of a success on each trial as a float + * @param mixed $alpha criterion value as a float + * + * @return int|string + */ + public static function inverse($trials, $probability, $alpha) + { + $trials = Functions::flattenSingleValue($trials); + $probability = Functions::flattenSingleValue($probability); + $alpha = Functions::flattenSingleValue($alpha); + + try { + $trials = DistributionValidations::validateInt($trials); + $probability = DistributionValidations::validateProbability($probability); + $alpha = DistributionValidations::validateFloat($alpha); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($trials < 0) { + return Functions::NAN(); + } elseif (($alpha < 0.0) || ($alpha > 1.0)) { + return Functions::NAN(); + } + + $successes = 0; + while ($successes <= $trials) { + $result = self::calculateCumulativeBinomial($successes, $trials, $probability); + if ($result >= $alpha) { + break; + } + ++$successes; + } + + return $successes; + } + + /** + * @return float|int + */ + private static function calculateCumulativeBinomial(int $value, int $trials, float $probability) + { + $summer = 0; + for ($i = 0; $i <= $value; ++$i) { + $summer += Combinations::withoutRepetition($trials, $i) * $probability ** $i + * (1 - $probability) ** ($trials - $i); + } + + return $summer; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php new file mode 100644 index 0000000..5165d63 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php @@ -0,0 +1,311 @@ +getMessage(); + } + + if ($degrees < 1) { + return Functions::NAN(); + } + if ($value < 0) { + if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) { + return 1; + } + + return Functions::NAN(); + } + + return 1 - (Gamma::incompleteGamma($degrees / 2, $value / 2) / Gamma::gammaValue($degrees / 2)); + } + + /** + * CHIDIST. + * + * Returns the one-tailed probability of the chi-squared distribution. + * + * @param mixed $value Float value for which we want the probability + * @param mixed $degrees Integer degrees of freedom + * @param mixed $cumulative Boolean value indicating if we want the cdf (true) or the pdf (false) + * + * @return float|string + */ + public static function distributionLeftTail($value, $degrees, $cumulative) + { + $value = Functions::flattenSingleValue($value); + $degrees = Functions::flattenSingleValue($degrees); + $cumulative = Functions::flattenSingleValue($cumulative); + + try { + $value = DistributionValidations::validateFloat($value); + $degrees = DistributionValidations::validateInt($degrees); + $cumulative = DistributionValidations::validateBool($cumulative); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($degrees < 1) { + return Functions::NAN(); + } + if ($value < 0) { + if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) { + return 1; + } + + return Functions::NAN(); + } + + if ($cumulative === true) { + return 1 - self::distributionRightTail($value, $degrees); + } + + return (($value ** (($degrees / 2) - 1) * exp(-$value / 2))) / + ((2 ** ($degrees / 2)) * Gamma::gammaValue($degrees / 2)); + } + + /** + * CHIINV. + * + * Returns the inverse of the right-tailed probability of the chi-squared distribution. + * + * @param mixed $probability Float probability at which you want to evaluate the distribution + * @param mixed $degrees Integer degrees of freedom + * + * @return float|string + */ + public static function inverseRightTail($probability, $degrees) + { + $probability = Functions::flattenSingleValue($probability); + $degrees = Functions::flattenSingleValue($degrees); + + try { + $probability = DistributionValidations::validateProbability($probability); + $degrees = DistributionValidations::validateInt($degrees); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($degrees < 1) { + return Functions::NAN(); + } + + $callback = function ($value) use ($degrees) { + return 1 - (Gamma::incompleteGamma($degrees / 2, $value / 2) + / Gamma::gammaValue($degrees / 2)); + }; + + $newtonRaphson = new NewtonRaphson($callback); + + return $newtonRaphson->execute($probability); + } + + /** + * CHIINV. + * + * Returns the inverse of the left-tailed probability of the chi-squared distribution. + * + * @param mixed $probability Float probability at which you want to evaluate the distribution + * @param mixed $degrees Integer degrees of freedom + * + * @return float|string + */ + public static function inverseLeftTail($probability, $degrees) + { + $probability = Functions::flattenSingleValue($probability); + $degrees = Functions::flattenSingleValue($degrees); + + try { + $probability = DistributionValidations::validateProbability($probability); + $degrees = DistributionValidations::validateInt($degrees); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($degrees < 1) { + return Functions::NAN(); + } + + return self::inverseLeftTailCalculation($probability, $degrees); + } + + /** + * CHITEST. + * + * Uses the chi-square test to calculate the probability that the differences between two supplied data sets + * (of observed and expected frequencies), are likely to be simply due to sampling error, + * or if they are likely to be real. + * + * @param mixed $actual an array of observed frequencies + * @param mixed $expected an array of expected frequencies + * + * @return float|string + */ + public static function test($actual, $expected) + { + $rows = count($actual); + $actual = Functions::flattenArray($actual); + $expected = Functions::flattenArray($expected); + $columns = count($actual) / $rows; + + $countActuals = count($actual); + $countExpected = count($expected); + if ($countActuals !== $countExpected || $countActuals === 1) { + return Functions::NAN(); + } + + $result = 0.0; + for ($i = 0; $i < $countActuals; ++$i) { + if ($expected[$i] == 0.0) { + return Functions::DIV0(); + } elseif ($expected[$i] < 0.0) { + return Functions::NAN(); + } + $result += (($actual[$i] - $expected[$i]) ** 2) / $expected[$i]; + } + + $degrees = self::degrees($rows, $columns); + + $result = self::distributionRightTail($result, $degrees); + + return $result; + } + + protected static function degrees(int $rows, int $columns): int + { + if ($rows === 1) { + return $columns - 1; + } elseif ($columns === 1) { + return $rows - 1; + } + + return ($columns - 1) * ($rows - 1); + } + + private static function inverseLeftTailCalculation(float $probability, int $degrees): float + { + // bracket the root + $min = 0; + $sd = sqrt(2.0 * $degrees); + $max = 2 * $sd; + $s = -1; + + while ($s * self::pchisq($max, $degrees) > $probability * $s) { + $min = $max; + $max += 2 * $sd; + } + + // Find root using bisection + $chi2 = 0.5 * ($min + $max); + + while (($max - $min) > self::EPS * $chi2) { + if ($s * self::pchisq($chi2, $degrees) > $probability * $s) { + $min = $chi2; + } else { + $max = $chi2; + } + $chi2 = 0.5 * ($min + $max); + } + + return $chi2; + } + + private static function pchisq($chi2, $degrees) + { + return self::gammp($degrees, 0.5 * $chi2); + } + + private static function gammp($n, $x) + { + if ($x < 0.5 * $n + 1) { + return self::gser($n, $x); + } + + return 1 - self::gcf($n, $x); + } + + // Return the incomplete gamma function P(n/2,x) evaluated by + // series representation. Algorithm from numerical recipe. + // Assume that n is a positive integer and x>0, won't check arguments. + // Relative error controlled by the eps parameter + private static function gser($n, $x) + { + $gln = Gamma::ln($n / 2); + $a = 0.5 * $n; + $ap = $a; + $sum = 1.0 / $a; + $del = $sum; + for ($i = 1; $i < 101; ++$i) { + ++$ap; + $del = $del * $x / $ap; + $sum += $del; + if ($del < $sum * self::EPS) { + break; + } + } + + return $sum * exp(-$x + $a * log($x) - $gln); + } + + // Return the incomplete gamma function Q(n/2,x) evaluated by + // its continued fraction representation. Algorithm from numerical recipe. + // Assume that n is a postive integer and x>0, won't check arguments. + // Relative error controlled by the eps parameter + private static function gcf($n, $x) + { + $gln = Gamma::ln($n / 2); + $a = 0.5 * $n; + $b = $x + 1 - $a; + $fpmin = 1.e-300; + $c = 1 / $fpmin; + $d = 1 / $b; + $h = $d; + for ($i = 1; $i < 101; ++$i) { + $an = -$i * ($i - $a); + $b += 2; + $d = $an * $d + $b; + if (abs($d) < $fpmin) { + $d = $fpmin; + } + $c = $b + $an / $c; + if (abs($c) < $fpmin) { + $c = $fpmin; + } + $d = 1 / $d; + $del = $d * $c; + $h = $h * $del; + if (abs($del - 1) < self::EPS) { + break; + } + } + + return $h * exp(-$x + $a * log($x) - $gln); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/DistributionValidations.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/DistributionValidations.php new file mode 100644 index 0000000..57ef00a --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/DistributionValidations.php @@ -0,0 +1,24 @@ + 1.0) { + throw new Exception(Functions::NAN()); + } + + return $probability; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Exponential.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Exponential.php new file mode 100644 index 0000000..b3fd946 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Exponential.php @@ -0,0 +1,47 @@ +getMessage(); + } + + if (($value < 0) || ($lambda < 0)) { + return Functions::NAN(); + } + + if ($cumulative === true) { + return 1 - exp(0 - $value * $lambda); + } + + return $lambda * exp(0 - $value * $lambda); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/F.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/F.php new file mode 100644 index 0000000..54b1950 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/F.php @@ -0,0 +1,56 @@ +getMessage(); + } + + if ($value < 0 || $u < 1 || $v < 1) { + return Functions::NAN(); + } + + if ($cumulative) { + $adjustedValue = ($u * $value) / ($u * $value + $v); + + return Beta::incompleteBeta($adjustedValue, $u / 2, $v / 2); + } + + return (Gamma::gammaValue(($v + $u) / 2) / + (Gamma::gammaValue($u / 2) * Gamma::gammaValue($v / 2))) * + (($u / $v) ** ($u / 2)) * + (($value ** (($u - 2) / 2)) / ((1 + ($u / $v) * $value) ** (($u + $v) / 2))); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Fisher.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Fisher.php new file mode 100644 index 0000000..923bf02 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Fisher.php @@ -0,0 +1,61 @@ +getMessage(); + } + + if (($value <= -1) || ($value >= 1)) { + return Functions::NAN(); + } + + return 0.5 * log((1 + $value) / (1 - $value)); + } + + /** + * FISHERINV. + * + * Returns the inverse of the Fisher transformation. Use this transformation when + * analyzing correlations between ranges or arrays of data. If y = FISHER(x), then + * FISHERINV(y) = x. + * + * @param mixed $probability Float probability at which you want to evaluate the distribution + * + * @return float|string + */ + public static function inverse($probability) + { + $probability = Functions::flattenSingleValue($probability); + + try { + DistributionValidations::validateFloat($probability); + } catch (Exception $e) { + return $e->getMessage(); + } + + return (exp(2 * $probability) - 1) / (exp(2 * $probability) + 1); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Gamma.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Gamma.php new file mode 100644 index 0000000..2c6ed67 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Gamma.php @@ -0,0 +1,127 @@ +getMessage(); + } + + if ((((int) $value) == ((float) $value)) && $value <= 0.0) { + return Functions::NAN(); + } + + return self::gammaValue($value); + } + + /** + * GAMMADIST. + * + * Returns the gamma distribution. + * + * @param mixed $value Float Value at which you want to evaluate the distribution + * @param mixed $a Parameter to the distribution as a float + * @param mixed $b Parameter to the distribution as a float + * @param mixed $cumulative Boolean value indicating if we want the cdf (true) or the pdf (false) + * + * @return float|string + */ + public static function distribution($value, $a, $b, $cumulative) + { + $value = Functions::flattenSingleValue($value); + $a = Functions::flattenSingleValue($a); + $b = Functions::flattenSingleValue($b); + + try { + $value = DistributionValidations::validateFloat($value); + $a = DistributionValidations::validateFloat($a); + $b = DistributionValidations::validateFloat($b); + $cumulative = DistributionValidations::validateBool($cumulative); + } catch (Exception $e) { + return $e->getMessage(); + } + + if (($value < 0) || ($a <= 0) || ($b <= 0)) { + return Functions::NAN(); + } + + return self::calculateDistribution($value, $a, $b, $cumulative); + } + + /** + * GAMMAINV. + * + * Returns the inverse of the Gamma distribution. + * + * @param mixed $probability Float probability at which you want to evaluate the distribution + * @param mixed $alpha Parameter to the distribution as a float + * @param mixed $beta Parameter to the distribution as a float + * + * @return float|string + */ + public static function inverse($probability, $alpha, $beta) + { + $probability = Functions::flattenSingleValue($probability); + $alpha = Functions::flattenSingleValue($alpha); + $beta = Functions::flattenSingleValue($beta); + + try { + $probability = DistributionValidations::validateProbability($probability); + $alpha = DistributionValidations::validateFloat($alpha); + $beta = DistributionValidations::validateFloat($beta); + } catch (Exception $e) { + return $e->getMessage(); + } + + if (($alpha <= 0.0) || ($beta <= 0.0)) { + return Functions::NAN(); + } + + return self::calculateInverse($probability, $alpha, $beta); + } + + /** + * GAMMALN. + * + * Returns the natural logarithm of the gamma function. + * + * @param mixed $value Float Value at which you want to evaluate the distribution + * + * @return float|string + */ + public static function ln($value) + { + $value = Functions::flattenSingleValue($value); + + try { + $value = DistributionValidations::validateFloat($value); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($value <= 0) { + return Functions::NAN(); + } + + return log(self::gammaValue($value)); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/GammaBase.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/GammaBase.php new file mode 100644 index 0000000..89170f7 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/GammaBase.php @@ -0,0 +1,381 @@ + Functions::PRECISION) && (++$i <= self::MAX_ITERATIONS)) { + // Apply Newton-Raphson step + $result = self::calculateDistribution($x, $alpha, $beta, true); + $error = $result - $probability; + + if ($error == 0.0) { + $dx = 0; + } elseif ($error < 0.0) { + $xLo = $x; + } else { + $xHi = $x; + } + + $pdf = self::calculateDistribution($x, $alpha, $beta, false); + // Avoid division by zero + if ($pdf !== 0.0) { + $dx = $error / $pdf; + $xNew = $x - $dx; + } + + // If the NR fails to converge (which for example may be the + // case if the initial guess is too rough) we apply a bisection + // step to determine a more narrow interval around the root. + if (($xNew < $xLo) || ($xNew > $xHi) || ($pdf == 0.0)) { + $xNew = ($xLo + $xHi) / 2; + $dx = $xNew - $x; + } + $x = $xNew; + } + + if ($i === self::MAX_ITERATIONS) { + return Functions::NA(); + } + + return $x; + } + + // + // Implementation of the incomplete Gamma function + // + public static function incompleteGamma(float $a, float $x): float + { + static $max = 32; + $summer = 0; + for ($n = 0; $n <= $max; ++$n) { + $divisor = $a; + for ($i = 1; $i <= $n; ++$i) { + $divisor *= ($a + $i); + } + $summer += ($x ** $n / $divisor); + } + + return $x ** $a * exp(0 - $x) * $summer; + } + + // + // Implementation of the Gamma function + // + public static function gammaValue(float $value): float + { + if ($value == 0.0) { + return 0; + } + + static $p0 = 1.000000000190015; + static $p = [ + 1 => 76.18009172947146, + 2 => -86.50532032941677, + 3 => 24.01409824083091, + 4 => -1.231739572450155, + 5 => 1.208650973866179e-3, + 6 => -5.395239384953e-6, + ]; + + $y = $x = $value; + $tmp = $x + 5.5; + $tmp -= ($x + 0.5) * log($tmp); + + $summer = $p0; + for ($j = 1; $j <= 6; ++$j) { + $summer += ($p[$j] / ++$y); + } + + return exp(0 - $tmp + log(self::SQRT2PI * $summer / $x)); + } + + /** + * logGamma function. + * + * @version 1.1 + * + * @author Jaco van Kooten + * + * Original author was Jaco van Kooten. Ported to PHP by Paul Meagher. + * + * The natural logarithm of the gamma function.
        + * Based on public domain NETLIB (Fortran) code by W. J. Cody and L. Stoltz
        + * Applied Mathematics Division
        + * Argonne National Laboratory
        + * Argonne, IL 60439
        + *

        + * References: + *

          + *
        1. W. J. Cody and K. E. Hillstrom, 'Chebyshev Approximations for the Natural + * Logarithm of the Gamma Function,' Math. Comp. 21, 1967, pp. 198-203.
        2. + *
        3. K. E. Hillstrom, ANL/AMD Program ANLC366S, DGAMMA/DLGAMA, May, 1969.
        4. + *
        5. Hart, Et. Al., Computer Approximations, Wiley and sons, New York, 1968.
        6. + *
        + *

        + *

        + * From the original documentation: + *

        + *

        + * This routine calculates the LOG(GAMMA) function for a positive real argument X. + * Computation is based on an algorithm outlined in references 1 and 2. + * The program uses rational functions that theoretically approximate LOG(GAMMA) + * to at least 18 significant decimal digits. The approximation for X > 12 is from + * reference 3, while approximations for X < 12.0 are similar to those in reference + * 1, but are unpublished. The accuracy achieved depends on the arithmetic system, + * the compiler, the intrinsic functions, and proper selection of the + * machine-dependent constants. + *

        + *

        + * Error returns:
        + * The program returns the value XINF for X .LE. 0.0 or when overflow would occur. + * The computation is believed to be free of underflow and overflow. + *

        + * + * @return float MAX_VALUE for x < 0.0 or when overflow would occur, i.e. x > 2.55E305 + */ + + // Log Gamma related constants + private const LG_D1 = -0.5772156649015328605195174; + + private const LG_D2 = 0.4227843350984671393993777; + + private const LG_D4 = 1.791759469228055000094023; + + private const LG_P1 = [ + 4.945235359296727046734888, + 201.8112620856775083915565, + 2290.838373831346393026739, + 11319.67205903380828685045, + 28557.24635671635335736389, + 38484.96228443793359990269, + 26377.48787624195437963534, + 7225.813979700288197698961, + ]; + + private const LG_P2 = [ + 4.974607845568932035012064, + 542.4138599891070494101986, + 15506.93864978364947665077, + 184793.2904445632425417223, + 1088204.76946882876749847, + 3338152.967987029735917223, + 5106661.678927352456275255, + 3074109.054850539556250927, + ]; + + private const LG_P4 = [ + 14745.02166059939948905062, + 2426813.369486704502836312, + 121475557.4045093227939592, + 2663432449.630976949898078, + 29403789566.34553899906876, + 170266573776.5398868392998, + 492612579337.743088758812, + 560625185622.3951465078242, + ]; + + private const LG_Q1 = [ + 67.48212550303777196073036, + 1113.332393857199323513008, + 7738.757056935398733233834, + 27639.87074403340708898585, + 54993.10206226157329794414, + 61611.22180066002127833352, + 36351.27591501940507276287, + 8785.536302431013170870835, + ]; + + private const LG_Q2 = [ + 183.0328399370592604055942, + 7765.049321445005871323047, + 133190.3827966074194402448, + 1136705.821321969608938755, + 5267964.117437946917577538, + 13467014.54311101692290052, + 17827365.30353274213975932, + 9533095.591844353613395747, + ]; + + private const LG_Q4 = [ + 2690.530175870899333379843, + 639388.5654300092398984238, + 41355999.30241388052042842, + 1120872109.61614794137657, + 14886137286.78813811542398, + 101680358627.2438228077304, + 341747634550.7377132798597, + 446315818741.9713286462081, + ]; + + private const LG_C = [ + -0.001910444077728, + 8.4171387781295e-4, + -5.952379913043012e-4, + 7.93650793500350248e-4, + -0.002777777777777681622553, + 0.08333333333333333331554247, + 0.0057083835261, + ]; + + // Rough estimate of the fourth root of logGamma_xBig + private const LG_FRTBIG = 2.25e76; + + private const PNT68 = 0.6796875; + + // Function cache for logGamma + private static $logGammaCacheResult = 0.0; + + private static $logGammaCacheX = 0.0; + + public static function logGamma(float $x): float + { + if ($x == self::$logGammaCacheX) { + return self::$logGammaCacheResult; + } + + $y = $x; + if ($y > 0.0 && $y <= self::LOG_GAMMA_X_MAX_VALUE) { + if ($y <= self::EPS) { + $res = -log($y); + } elseif ($y <= 1.5) { + $res = self::logGamma1($y); + } elseif ($y <= 4.0) { + $res = self::logGamma2($y); + } elseif ($y <= 12.0) { + $res = self::logGamma3($y); + } else { + $res = self::logGamma4($y); + } + } else { + // -------------------------- + // Return for bad arguments + // -------------------------- + $res = self::MAX_VALUE; + } + + // ------------------------------ + // Final adjustments and return + // ------------------------------ + self::$logGammaCacheX = $x; + self::$logGammaCacheResult = $res; + + return $res; + } + + private static function logGamma1(float $y) + { + // --------------------- + // EPS .LT. X .LE. 1.5 + // --------------------- + if ($y < self::PNT68) { + $corr = -log($y); + $xm1 = $y; + } else { + $corr = 0.0; + $xm1 = $y - 1.0; + } + + $xden = 1.0; + $xnum = 0.0; + if ($y <= 0.5 || $y >= self::PNT68) { + for ($i = 0; $i < 8; ++$i) { + $xnum = $xnum * $xm1 + self::LG_P1[$i]; + $xden = $xden * $xm1 + self::LG_Q1[$i]; + } + + return $corr + $xm1 * (self::LG_D1 + $xm1 * ($xnum / $xden)); + } + + $xm2 = $y - 1.0; + for ($i = 0; $i < 8; ++$i) { + $xnum = $xnum * $xm2 + self::LG_P2[$i]; + $xden = $xden * $xm2 + self::LG_Q2[$i]; + } + + return $corr + $xm2 * (self::LG_D2 + $xm2 * ($xnum / $xden)); + } + + private static function logGamma2(float $y) + { + // --------------------- + // 1.5 .LT. X .LE. 4.0 + // --------------------- + $xm2 = $y - 2.0; + $xden = 1.0; + $xnum = 0.0; + for ($i = 0; $i < 8; ++$i) { + $xnum = $xnum * $xm2 + self::LG_P2[$i]; + $xden = $xden * $xm2 + self::LG_Q2[$i]; + } + + return $xm2 * (self::LG_D2 + $xm2 * ($xnum / $xden)); + } + + protected static function logGamma3(float $y) + { + // ---------------------- + // 4.0 .LT. X .LE. 12.0 + // ---------------------- + $xm4 = $y - 4.0; + $xden = -1.0; + $xnum = 0.0; + for ($i = 0; $i < 8; ++$i) { + $xnum = $xnum * $xm4 + self::LG_P4[$i]; + $xden = $xden * $xm4 + self::LG_Q4[$i]; + } + + return self::LG_D4 + $xm4 * ($xnum / $xden); + } + + protected static function logGamma4(float $y) + { + // --------------------------------- + // Evaluate for argument .GE. 12.0 + // --------------------------------- + $res = 0.0; + if ($y <= self::LG_FRTBIG) { + $res = self::LG_C[6]; + $ysq = $y * $y; + for ($i = 0; $i < 6; ++$i) { + $res = $res / $ysq + self::LG_C[$i]; + } + $res /= $y; + $corr = log($y); + $res = $res + log(self::SQRT2PI) - 0.5 * $corr; + $res += $y * ($corr - 1.0); + } + + return $res; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/HyperGeometric.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/HyperGeometric.php new file mode 100644 index 0000000..fe30c08 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/HyperGeometric.php @@ -0,0 +1,59 @@ +getMessage(); + } + + if (($sampleSuccesses < 0) || ($sampleSuccesses > $sampleNumber) || ($sampleSuccesses > $populationSuccesses)) { + return Functions::NAN(); + } + if (($sampleNumber <= 0) || ($sampleNumber > $populationNumber)) { + return Functions::NAN(); + } + if (($populationSuccesses <= 0) || ($populationSuccesses > $populationNumber)) { + return Functions::NAN(); + } + + $successesPopulationAndSample = (float) Combinations::withoutRepetition($populationSuccesses, $sampleSuccesses); + $numbersPopulationAndSample = (float) Combinations::withoutRepetition($populationNumber, $sampleNumber); + $adjustedPopulationAndSample = (float) Combinations::withoutRepetition( + $populationNumber - $populationSuccesses, + $sampleNumber - $sampleSuccesses + ); + + return $successesPopulationAndSample * $adjustedPopulationAndSample / $numbersPopulationAndSample; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/LogNormal.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/LogNormal.php new file mode 100644 index 0000000..e152377 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/LogNormal.php @@ -0,0 +1,119 @@ +getMessage(); + } + + if (($value <= 0) || ($stdDev <= 0)) { + return Functions::NAN(); + } + + return StandardNormal::cumulative((log($value) - $mean) / $stdDev); + } + + /** + * LOGNORM.DIST. + * + * Returns the lognormal distribution of x, where ln(x) is normally distributed + * with parameters mean and standard_dev. + * + * @param mixed $value Float value for which we want the probability + * @param mixed $mean Mean value as a float + * @param mixed $stdDev Standard Deviation as a float + * @param mixed $cumulative Boolean value indicating if we want the cdf (true) or the pdf (false) + * + * @return float|string The result, or a string containing an error + */ + public static function distribution($value, $mean, $stdDev, $cumulative = false) + { + $value = Functions::flattenSingleValue($value); + $mean = Functions::flattenSingleValue($mean); + $stdDev = Functions::flattenSingleValue($stdDev); + $cumulative = Functions::flattenSingleValue($cumulative); + + try { + $value = DistributionValidations::validateFloat($value); + $mean = DistributionValidations::validateFloat($mean); + $stdDev = DistributionValidations::validateFloat($stdDev); + $cumulative = DistributionValidations::validateBool($cumulative); + } catch (Exception $e) { + return $e->getMessage(); + } + + if (($value <= 0) || ($stdDev <= 0)) { + return Functions::NAN(); + } + + if ($cumulative === true) { + return StandardNormal::distribution((log($value) - $mean) / $stdDev, true); + } + + return (1 / (sqrt(2 * M_PI) * $stdDev * $value)) * + exp(0 - ((log($value) - $mean) ** 2 / (2 * $stdDev ** 2))); + } + + /** + * LOGINV. + * + * Returns the inverse of the lognormal cumulative distribution + * + * @param mixed $probability Float probability for which we want the value + * @param mixed $mean Mean Value as a float + * @param mixed $stdDev Standard Deviation as a float + * + * @return float|string The result, or a string containing an error + * + * @TODO Try implementing P J Acklam's refinement algorithm for greater + * accuracy if I can get my head round the mathematics + * (as described at) http://home.online.no/~pjacklam/notes/invnorm/ + */ + public static function inverse($probability, $mean, $stdDev) + { + $probability = Functions::flattenSingleValue($probability); + $mean = Functions::flattenSingleValue($mean); + $stdDev = Functions::flattenSingleValue($stdDev); + + try { + $probability = DistributionValidations::validateProbability($probability); + $mean = DistributionValidations::validateFloat($mean); + $stdDev = DistributionValidations::validateFloat($stdDev); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($stdDev <= 0) { + return Functions::NAN(); + } + + return exp($mean + $stdDev * StandardNormal::inverse($probability)); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/NewtonRaphson.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/NewtonRaphson.php new file mode 100644 index 0000000..2621167 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/NewtonRaphson.php @@ -0,0 +1,62 @@ +callback = $callback; + } + + public function execute(float $probability) + { + $xLo = 100; + $xHi = 0; + + $dx = 1; + $x = $xNew = 1; + $i = 0; + + while ((abs($dx) > Functions::PRECISION) && ($i++ < self::MAX_ITERATIONS)) { + // Apply Newton-Raphson step + $result = call_user_func($this->callback, $x); + $error = $result - $probability; + + if ($error == 0.0) { + $dx = 0; + } elseif ($error < 0.0) { + $xLo = $x; + } else { + $xHi = $x; + } + + // Avoid division by zero + if ($result != 0.0) { + $dx = $error / $result; + $xNew = $x - $dx; + } + + // If the NR fails to converge (which for example may be the + // case if the initial guess is too rough) we apply a bisection + // step to determine a more narrow interval around the root. + if (($xNew < $xLo) || ($xNew > $xHi) || ($result == 0.0)) { + $xNew = ($xLo + $xHi) / 2; + $dx = $xNew - $x; + } + $x = $xNew; + } + + if ($i == self::MAX_ITERATIONS) { + return Functions::NA(); + } + + return $x; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Normal.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Normal.php new file mode 100644 index 0000000..4d158b8 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Normal.php @@ -0,0 +1,166 @@ +getMessage(); + } + + if ($stdDev < 0) { + return Functions::NAN(); + } + + if ($cumulative) { + return 0.5 * (1 + Engineering\Erf::erfValue(($value - $mean) / ($stdDev * sqrt(2)))); + } + + return (1 / (self::SQRT2PI * $stdDev)) * exp(0 - (($value - $mean) ** 2 / (2 * ($stdDev * $stdDev)))); + } + + /** + * NORMINV. + * + * Returns the inverse of the normal cumulative distribution for the specified mean and standard deviation. + * + * @param mixed $probability Float probability for which we want the value + * @param mixed $mean Mean Value as a float + * @param mixed $stdDev Standard Deviation as a float + * + * @return float|string The result, or a string containing an error + */ + public static function inverse($probability, $mean, $stdDev) + { + $probability = Functions::flattenSingleValue($probability); + $mean = Functions::flattenSingleValue($mean); + $stdDev = Functions::flattenSingleValue($stdDev); + + try { + $probability = DistributionValidations::validateProbability($probability); + $mean = DistributionValidations::validateFloat($mean); + $stdDev = DistributionValidations::validateFloat($stdDev); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($stdDev < 0) { + return Functions::NAN(); + } + + return (self::inverseNcdf($probability) * $stdDev) + $mean; + } + + /* + * inverse_ncdf.php + * ------------------- + * begin : Friday, January 16, 2004 + * copyright : (C) 2004 Michael Nickerson + * email : nickersonm@yahoo.com + * + */ + private static function inverseNcdf($p) + { + // Inverse ncdf approximation by Peter J. Acklam, implementation adapted to + // PHP by Michael Nickerson, using Dr. Thomas Ziegler's C implementation as + // a guide. http://home.online.no/~pjacklam/notes/invnorm/index.html + // I have not checked the accuracy of this implementation. Be aware that PHP + // will truncate the coeficcients to 14 digits. + + // You have permission to use and distribute this function freely for + // whatever purpose you want, but please show common courtesy and give credit + // where credit is due. + + // Input paramater is $p - probability - where 0 < p < 1. + + // Coefficients in rational approximations + static $a = [ + 1 => -3.969683028665376e+01, + 2 => 2.209460984245205e+02, + 3 => -2.759285104469687e+02, + 4 => 1.383577518672690e+02, + 5 => -3.066479806614716e+01, + 6 => 2.506628277459239e+00, + ]; + + static $b = [ + 1 => -5.447609879822406e+01, + 2 => 1.615858368580409e+02, + 3 => -1.556989798598866e+02, + 4 => 6.680131188771972e+01, + 5 => -1.328068155288572e+01, + ]; + + static $c = [ + 1 => -7.784894002430293e-03, + 2 => -3.223964580411365e-01, + 3 => -2.400758277161838e+00, + 4 => -2.549732539343734e+00, + 5 => 4.374664141464968e+00, + 6 => 2.938163982698783e+00, + ]; + + static $d = [ + 1 => 7.784695709041462e-03, + 2 => 3.224671290700398e-01, + 3 => 2.445134137142996e+00, + 4 => 3.754408661907416e+00, + ]; + + // Define lower and upper region break-points. + $p_low = 0.02425; //Use lower region approx. below this + $p_high = 1 - $p_low; //Use upper region approx. above this + + if (0 < $p && $p < $p_low) { + // Rational approximation for lower region. + $q = sqrt(-2 * log($p)); + + return ((((($c[1] * $q + $c[2]) * $q + $c[3]) * $q + $c[4]) * $q + $c[5]) * $q + $c[6]) / + (((($d[1] * $q + $d[2]) * $q + $d[3]) * $q + $d[4]) * $q + 1); + } elseif ($p_high < $p && $p < 1) { + // Rational approximation for upper region. + $q = sqrt(-2 * log(1 - $p)); + + return -((((($c[1] * $q + $c[2]) * $q + $c[3]) * $q + $c[4]) * $q + $c[5]) * $q + $c[6]) / + (((($d[1] * $q + $d[2]) * $q + $d[3]) * $q + $d[4]) * $q + 1); + } + + // Rational approximation for central region. + $q = $p - 0.5; + $r = $q * $q; + + return ((((($a[1] * $r + $a[2]) * $r + $a[3]) * $r + $a[4]) * $r + $a[5]) * $r + $a[6]) * $q / + ((((($b[1] * $r + $b[2]) * $r + $b[3]) * $r + $b[4]) * $r + $b[5]) * $r + 1); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Poisson.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Poisson.php new file mode 100644 index 0000000..e7252e0 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Poisson.php @@ -0,0 +1,53 @@ +getMessage(); + } + + if (($value < 0) || ($mean < 0)) { + return Functions::NAN(); + } + + if ($cumulative) { + $summer = 0; + $floor = floor($value); + for ($i = 0; $i <= $floor; ++$i) { + $summer += $mean ** $i / MathTrig\Factorial::fact($i); + } + + return exp(0 - $mean) * $summer; + } + + return (exp(0 - $mean) * $mean ** $value) / MathTrig\Factorial::fact($value); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/StandardNormal.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/StandardNormal.php new file mode 100644 index 0000000..d10f02a --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/StandardNormal.php @@ -0,0 +1,110 @@ +getMessage(); + } + + if (($value < 0) || ($degrees < 1) || ($tails < 1) || ($tails > 2)) { + return Functions::NAN(); + } + + return self::calculateDistribution($value, $degrees, $tails); + } + + /** + * TINV. + * + * Returns the one-tailed probability of the chi-squared distribution. + * + * @param mixed $probability Float probability for the function + * @param mixed $degrees Integer value for degrees of freedom + * + * @return float|string The result, or a string containing an error + */ + public static function inverse($probability, $degrees) + { + $probability = Functions::flattenSingleValue($probability); + $degrees = Functions::flattenSingleValue($degrees); + + try { + $probability = DistributionValidations::validateProbability($probability); + $degrees = DistributionValidations::validateInt($degrees); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($degrees <= 0) { + return Functions::NAN(); + } + + $callback = function ($value) use ($degrees) { + return self::distribution($value, $degrees, 2); + }; + + $newtonRaphson = new NewtonRaphson($callback); + + return $newtonRaphson->execute($probability); + } + + /** + * @return float + */ + private static function calculateDistribution(float $value, int $degrees, int $tails) + { + // tdist, which finds the probability that corresponds to a given value + // of t with k degrees of freedom. This algorithm is translated from a + // pascal function on p81 of "Statistical Computing in Pascal" by D + // Cooke, A H Craven & G M Clark (1985: Edward Arnold (Pubs.) Ltd: + // London). The above Pascal algorithm is itself a translation of the + // fortran algoritm "AS 3" by B E Cooper of the Atlas Computer + // Laboratory as reported in (among other places) "Applied Statistics + // Algorithms", editied by P Griffiths and I D Hill (1985; Ellis + // Horwood Ltd.; W. Sussex, England). + $tterm = $degrees; + $ttheta = atan2($value, sqrt($tterm)); + $tc = cos($ttheta); + $ts = sin($ttheta); + + if (($degrees % 2) === 1) { + $ti = 3; + $tterm = $tc; + } else { + $ti = 2; + $tterm = 1; + } + + $tsum = $tterm; + while ($ti < $degrees) { + $tterm *= $tc * $tc * ($ti - 1) / $ti; + $tsum += $tterm; + $ti += 2; + } + + $tsum *= $ts; + if (($degrees % 2) == 1) { + $tsum = Functions::M_2DIVPI * ($tsum + $ttheta); + } + + $tValue = 0.5 * (1 + $tsum); + if ($tails == 1) { + return 1 - abs($tValue); + } + + return 1 - abs((1 - $tValue) - $tValue); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Weibull.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Weibull.php new file mode 100644 index 0000000..ecec8a8 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Weibull.php @@ -0,0 +1,49 @@ +getMessage(); + } + + if (($value < 0) || ($alpha <= 0) || ($beta <= 0)) { + return Functions::NAN(); + } + + if ($cumulative) { + return 1 - exp(0 - ($value / $beta) ** $alpha); + } + + return ($alpha / $beta ** $alpha) * $value ** ($alpha - 1) * exp(0 - ($value / $beta) ** $alpha); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/MaxMinBase.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/MaxMinBase.php new file mode 100644 index 0000000..bd17b06 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/MaxMinBase.php @@ -0,0 +1,17 @@ + $returnValue)) { + $returnValue = $arg; + } + } + } + + if ($returnValue === null) { + return 0; + } + + return $returnValue; + } + + /** + * MAXA. + * + * Returns the greatest value in a list of arguments, including numbers, text, and logical values + * + * Excel Function: + * MAXA(value1[,value2[, ...]]) + * + * @param mixed ...$args Data values + * + * @return float + */ + public static function maxA(...$args) + { + $returnValue = null; + + // Loop through arguments + $aArgs = Functions::flattenArray($args); + foreach ($aArgs as $arg) { + // Is it a numeric value? + if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) && ($arg != '')))) { + $arg = self::datatypeAdjustmentAllowStrings($arg); + if (($returnValue === null) || ($arg > $returnValue)) { + $returnValue = $arg; + } + } + } + + if ($returnValue === null) { + return 0; + } + + return $returnValue; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Minimum.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Minimum.php new file mode 100644 index 0000000..596aad7 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Minimum.php @@ -0,0 +1,78 @@ +getMessage(); + } + + if (($entry < 0) || ($entry > 1)) { + return Functions::NAN(); + } + + $mArgs = self::percentileFilterValues($aArgs); + $mValueCount = count($mArgs); + if ($mValueCount > 0) { + sort($mArgs); + $count = Counts::COUNT($mArgs); + $index = $entry * ($count - 1); + $iBase = floor($index); + if ($index == $iBase) { + return $mArgs[$index]; + } + $iNext = $iBase + 1; + $iProportion = $index - $iBase; + + return $mArgs[$iBase] + (($mArgs[$iNext] - $mArgs[$iBase]) * $iProportion); + } + + return Functions::NAN(); + } + + /** + * PERCENTRANK. + * + * Returns the rank of a value in a data set as a percentage of the data set. + * Note that the returned rank is simply rounded to the appropriate significant digits, + * rather than floored (as MS Excel), so value 3 for a value set of 1, 2, 3, 4 will return + * 0.667 rather than 0.666 + * + * @param mixed $valueSet An array of (float) values, or a reference to, a list of numbers + * @param mixed $value The number whose rank you want to find + * @param mixed $significance The (integer) number of significant digits for the returned percentage value + * + * @return float|string (string if result is an error) + */ + public static function PERCENTRANK($valueSet, $value, $significance = 3) + { + $valueSet = Functions::flattenArray($valueSet); + $value = Functions::flattenSingleValue($value); + $significance = ($significance === null) ? 3 : Functions::flattenSingleValue($significance); + + try { + $value = StatisticalValidations::validateFloat($value); + $significance = StatisticalValidations::validateInt($significance); + } catch (Exception $e) { + return $e->getMessage(); + } + + $valueSet = self::rankFilterValues($valueSet); + $valueCount = count($valueSet); + if ($valueCount == 0) { + return Functions::NA(); + } + sort($valueSet, SORT_NUMERIC); + + $valueAdjustor = $valueCount - 1; + if (($value < $valueSet[0]) || ($value > $valueSet[$valueAdjustor])) { + return Functions::NA(); + } + + $pos = array_search($value, $valueSet); + if ($pos === false) { + $pos = 0; + $testValue = $valueSet[0]; + while ($testValue < $value) { + $testValue = $valueSet[++$pos]; + } + --$pos; + $pos += (($value - $valueSet[$pos]) / ($testValue - $valueSet[$pos])); + } + + return round($pos / $valueAdjustor, $significance); + } + + /** + * QUARTILE. + * + * Returns the quartile of a data set. + * + * Excel Function: + * QUARTILE(value1[,value2[, ...]],entry) + * + * @param mixed $args Data values + * + * @return float|string The result, or a string containing an error + */ + public static function QUARTILE(...$args) + { + $aArgs = Functions::flattenArray($args); + $entry = array_pop($aArgs); + + try { + $entry = StatisticalValidations::validateFloat($entry); + } catch (Exception $e) { + return $e->getMessage(); + } + + $entry = floor($entry); + $entry /= 4; + if (($entry < 0) || ($entry > 1)) { + return Functions::NAN(); + } + + return self::PERCENTILE($aArgs, $entry); + } + + /** + * RANK. + * + * Returns the rank of a number in a list of numbers. + * + * @param mixed $value The number whose rank you want to find + * @param mixed $valueSet An array of float values, or a reference to, a list of numbers + * @param mixed $order Order to sort the values in the value set + * + * @return float|string The result, or a string containing an error (0 = Descending, 1 = Ascending) + */ + public static function RANK($value, $valueSet, $order = self::RANK_SORT_DESCENDING) + { + $value = Functions::flattenSingleValue($value); + $valueSet = Functions::flattenArray($valueSet); + $order = ($order === null) ? self::RANK_SORT_DESCENDING : Functions::flattenSingleValue($order); + + try { + $value = StatisticalValidations::validateFloat($value); + $order = StatisticalValidations::validateInt($order); + } catch (Exception $e) { + return $e->getMessage(); + } + + $valueSet = self::rankFilterValues($valueSet); + if ($order === self::RANK_SORT_DESCENDING) { + rsort($valueSet, SORT_NUMERIC); + } else { + sort($valueSet, SORT_NUMERIC); + } + + $pos = array_search($value, $valueSet); + if ($pos === false) { + return Functions::NA(); + } + + return ++$pos; + } + + protected static function percentileFilterValues(array $dataSet) + { + return array_filter( + $dataSet, + function ($value): bool { + return is_numeric($value) && !is_string($value); + } + ); + } + + protected static function rankFilterValues(array $dataSet) + { + return array_filter( + $dataSet, + function ($value): bool { + return is_numeric($value); + } + ); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Permutations.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Permutations.php new file mode 100644 index 0000000..272a8a5 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Permutations.php @@ -0,0 +1,77 @@ +getMessage(); + } + + if ($numObjs < $numInSet) { + return Functions::NAN(); + } + $result = round(MathTrig\Factorial::fact($numObjs) / MathTrig\Factorial::fact($numObjs - $numInSet)); + + return IntOrFloat::evaluate($result); + } + + /** + * PERMUTATIONA. + * + * Returns the number of permutations for a given number of objects (with repetitions) + * that can be selected from the total objects. + * + * @param mixed $numObjs Integer number of different objects + * @param mixed $numInSet Integer number of objects in each permutation + * + * @return float|int|string Number of permutations, or a string containing an error + */ + public static function PERMUTATIONA($numObjs, $numInSet) + { + $numObjs = Functions::flattenSingleValue($numObjs); + $numInSet = Functions::flattenSingleValue($numInSet); + + try { + $numObjs = StatisticalValidations::validateInt($numObjs); + $numInSet = StatisticalValidations::validateInt($numInSet); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($numObjs < 0 || $numInSet < 0) { + return Functions::NAN(); + } + + $result = $numObjs ** $numInSet; + + return IntOrFloat::evaluate($result); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Size.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Size.php new file mode 100644 index 0000000..de4b6d6 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Size.php @@ -0,0 +1,96 @@ += $count) || ($count == 0)) { + return Functions::NAN(); + } + rsort($mArgs); + + return $mArgs[$entry]; + } + + return Functions::VALUE(); + } + + /** + * SMALL. + * + * Returns the nth smallest value in a data set. You can use this function to + * select a value based on its relative standing. + * + * Excel Function: + * SMALL(value1[,value2[, ...]],entry) + * + * @param mixed $args Data values + * + * @return float|string The result, or a string containing an error + */ + public static function small(...$args) + { + $aArgs = Functions::flattenArray($args); + + $entry = array_pop($aArgs); + + if ((is_numeric($entry)) && (!is_string($entry))) { + $entry = (int) floor($entry); + + $mArgs = self::filter($aArgs); + $count = Counts::COUNT($mArgs); + --$entry; + if (($entry < 0) || ($entry >= $count) || ($count == 0)) { + return Functions::NAN(); + } + sort($mArgs); + + return $mArgs[$entry]; + } + + return Functions::VALUE(); + } + + /** + * @param mixed[] $args Data values + */ + protected static function filter(array $args): array + { + $mArgs = []; + + foreach ($args as $arg) { + // Is it a numeric value? + if ((is_numeric($arg)) && (!is_string($arg))) { + $mArgs[] = $arg; + } + } + + return $mArgs; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/StandardDeviations.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/StandardDeviations.php new file mode 100644 index 0000000..af27120 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/StandardDeviations.php @@ -0,0 +1,95 @@ +getMessage(); + } + + if ($stdDev <= 0) { + return Functions::NAN(); + } + + return ($value - $mean) / $stdDev; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/StatisticalValidations.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/StatisticalValidations.php new file mode 100644 index 0000000..5b315da --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/StatisticalValidations.php @@ -0,0 +1,45 @@ + $value) { + if ((is_bool($value)) || (is_string($value)) || ($value === null)) { + unset($array1[$key], $array2[$key]); + } + } + } + + private static function checkTrendArrays(&$array1, &$array2): void + { + if (!is_array($array1)) { + $array1 = [$array1]; + } + if (!is_array($array2)) { + $array2 = [$array2]; + } + + $array1 = Functions::flattenArray($array1); + $array2 = Functions::flattenArray($array2); + + self::filterTrendValues($array1, $array2); + self::filterTrendValues($array2, $array1); + + // Reset the array indexes + $array1 = array_merge($array1); + $array2 = array_merge($array2); + } + + protected static function validateTrendArrays(array $yValues, array $xValues): void + { + $yValueCount = count($yValues); + $xValueCount = count($xValues); + + if (($yValueCount === 0) || ($yValueCount !== $xValueCount)) { + throw new Exception(Functions::NA()); + } elseif ($yValueCount === 1) { + throw new Exception(Functions::DIV0()); + } + } + + /** + * CORREL. + * + * Returns covariance, the average of the products of deviations for each data point pair. + * + * @param mixed $yValues array of mixed Data Series Y + * @param null|mixed $xValues array of mixed Data Series X + * + * @return float|string + */ + public static function CORREL($yValues, $xValues = null) + { + if (($xValues === null) || (!is_array($yValues)) || (!is_array($xValues))) { + return Functions::VALUE(); + } + + try { + self::checkTrendArrays($yValues, $xValues); + self::validateTrendArrays($yValues, $xValues); + } catch (Exception $e) { + return $e->getMessage(); + } + + $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); + + return $bestFitLinear->getCorrelation(); + } + + /** + * COVAR. + * + * Returns covariance, the average of the products of deviations for each data point pair. + * + * @param mixed $yValues array of mixed Data Series Y + * @param mixed $xValues array of mixed Data Series X + * + * @return float|string + */ + public static function COVAR($yValues, $xValues) + { + try { + self::checkTrendArrays($yValues, $xValues); + self::validateTrendArrays($yValues, $xValues); + } catch (Exception $e) { + return $e->getMessage(); + } + + $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); + + return $bestFitLinear->getCovariance(); + } + + /** + * FORECAST. + * + * Calculates, or predicts, a future value by using existing values. + * The predicted value is a y-value for a given x-value. + * + * @param mixed $xValue Float value of X for which we want to find Y + * @param mixed $yValues array of mixed Data Series Y + * @param mixed $xValues of mixed Data Series X + * + * @return bool|float|string + */ + public static function FORECAST($xValue, $yValues, $xValues) + { + $xValue = Functions::flattenSingleValue($xValue); + + try { + $xValue = StatisticalValidations::validateFloat($xValue); + self::checkTrendArrays($yValues, $xValues); + self::validateTrendArrays($yValues, $xValues); + } catch (Exception $e) { + return $e->getMessage(); + } + + $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); + + return $bestFitLinear->getValueOfYForX($xValue); + } + + /** + * GROWTH. + * + * Returns values along a predicted exponential Trend + * + * @param mixed[] $yValues Data Series Y + * @param mixed[] $xValues Data Series X + * @param mixed[] $newValues Values of X for which we want to find Y + * @param mixed $const A logical (boolean) value specifying whether to force the intersect to equal 0 or not + * + * @return float[] + */ + public static function GROWTH($yValues, $xValues = [], $newValues = [], $const = true) + { + $yValues = Functions::flattenArray($yValues); + $xValues = Functions::flattenArray($xValues); + $newValues = Functions::flattenArray($newValues); + $const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const); + + $bestFitExponential = Trend::calculate(Trend::TREND_EXPONENTIAL, $yValues, $xValues, $const); + if (empty($newValues)) { + $newValues = $bestFitExponential->getXValues(); + } + + $returnArray = []; + foreach ($newValues as $xValue) { + $returnArray[0][] = [$bestFitExponential->getValueOfYForX($xValue)]; + } + + return $returnArray; + } + + /** + * INTERCEPT. + * + * Calculates the point at which a line will intersect the y-axis by using existing x-values and y-values. + * + * @param mixed[] $yValues Data Series Y + * @param mixed[] $xValues Data Series X + * + * @return float|string + */ + public static function INTERCEPT($yValues, $xValues) + { + try { + self::checkTrendArrays($yValues, $xValues); + self::validateTrendArrays($yValues, $xValues); + } catch (Exception $e) { + return $e->getMessage(); + } + + $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); + + return $bestFitLinear->getIntersect(); + } + + /** + * LINEST. + * + * Calculates the statistics for a line by using the "least squares" method to calculate a straight line + * that best fits your data, and then returns an array that describes the line. + * + * @param mixed[] $yValues Data Series Y + * @param null|mixed[] $xValues Data Series X + * @param mixed $const A logical (boolean) value specifying whether to force the intersect to equal 0 or not + * @param mixed $stats A logical (boolean) value specifying whether to return additional regression statistics + * + * @return array|int|string The result, or a string containing an error + */ + public static function LINEST($yValues, $xValues = null, $const = true, $stats = false) + { + $const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const); + $stats = ($stats === null) ? false : (bool) Functions::flattenSingleValue($stats); + if ($xValues === null) { + $xValues = $yValues; + } + + try { + self::checkTrendArrays($yValues, $xValues); + self::validateTrendArrays($yValues, $xValues); + } catch (Exception $e) { + return $e->getMessage(); + } + + $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues, $const); + + if ($stats === true) { + return [ + [ + $bestFitLinear->getSlope(), + $bestFitLinear->getIntersect(), + ], + [ + $bestFitLinear->getSlopeSE(), + ($const === false) ? Functions::NA() : $bestFitLinear->getIntersectSE(), + ], + [ + $bestFitLinear->getGoodnessOfFit(), + $bestFitLinear->getStdevOfResiduals(), + ], + [ + $bestFitLinear->getF(), + $bestFitLinear->getDFResiduals(), + ], + [ + $bestFitLinear->getSSRegression(), + $bestFitLinear->getSSResiduals(), + ], + ]; + } + + return [ + $bestFitLinear->getSlope(), + $bestFitLinear->getIntersect(), + ]; + } + + /** + * LOGEST. + * + * Calculates an exponential curve that best fits the X and Y data series, + * and then returns an array that describes the line. + * + * @param mixed[] $yValues Data Series Y + * @param null|mixed[] $xValues Data Series X + * @param mixed $const A logical (boolean) value specifying whether to force the intersect to equal 0 or not + * @param mixed $stats A logical (boolean) value specifying whether to return additional regression statistics + * + * @return array|int|string The result, or a string containing an error + */ + public static function LOGEST($yValues, $xValues = null, $const = true, $stats = false) + { + $const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const); + $stats = ($stats === null) ? false : (bool) Functions::flattenSingleValue($stats); + if ($xValues === null) { + $xValues = $yValues; + } + + try { + self::checkTrendArrays($yValues, $xValues); + self::validateTrendArrays($yValues, $xValues); + } catch (Exception $e) { + return $e->getMessage(); + } + + foreach ($yValues as $value) { + if ($value < 0.0) { + return Functions::NAN(); + } + } + + $bestFitExponential = Trend::calculate(Trend::TREND_EXPONENTIAL, $yValues, $xValues, $const); + + if ($stats === true) { + return [ + [ + $bestFitExponential->getSlope(), + $bestFitExponential->getIntersect(), + ], + [ + $bestFitExponential->getSlopeSE(), + ($const === false) ? Functions::NA() : $bestFitExponential->getIntersectSE(), + ], + [ + $bestFitExponential->getGoodnessOfFit(), + $bestFitExponential->getStdevOfResiduals(), + ], + [ + $bestFitExponential->getF(), + $bestFitExponential->getDFResiduals(), + ], + [ + $bestFitExponential->getSSRegression(), + $bestFitExponential->getSSResiduals(), + ], + ]; + } + + return [ + $bestFitExponential->getSlope(), + $bestFitExponential->getIntersect(), + ]; + } + + /** + * RSQ. + * + * Returns the square of the Pearson product moment correlation coefficient through data points + * in known_y's and known_x's. + * + * @param mixed[] $yValues Data Series Y + * @param mixed[] $xValues Data Series X + * + * @return float|string The result, or a string containing an error + */ + public static function RSQ($yValues, $xValues) + { + try { + self::checkTrendArrays($yValues, $xValues); + self::validateTrendArrays($yValues, $xValues); + } catch (Exception $e) { + return $e->getMessage(); + } + + $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); + + return $bestFitLinear->getGoodnessOfFit(); + } + + /** + * SLOPE. + * + * Returns the slope of the linear regression line through data points in known_y's and known_x's. + * + * @param mixed[] $yValues Data Series Y + * @param mixed[] $xValues Data Series X + * + * @return float|string The result, or a string containing an error + */ + public static function SLOPE($yValues, $xValues) + { + try { + self::checkTrendArrays($yValues, $xValues); + self::validateTrendArrays($yValues, $xValues); + } catch (Exception $e) { + return $e->getMessage(); + } + + $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); + + return $bestFitLinear->getSlope(); + } + + /** + * STEYX. + * + * Returns the standard error of the predicted y-value for each x in the regression. + * + * @param mixed[] $yValues Data Series Y + * @param mixed[] $xValues Data Series X + * + * @return float|string + */ + public static function STEYX($yValues, $xValues) + { + try { + self::checkTrendArrays($yValues, $xValues); + self::validateTrendArrays($yValues, $xValues); + } catch (Exception $e) { + return $e->getMessage(); + } + + $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); + + return $bestFitLinear->getStdevOfResiduals(); + } + + /** + * TREND. + * + * Returns values along a linear Trend + * + * @param mixed[] $yValues Data Series Y + * @param mixed[] $xValues Data Series X + * @param mixed[] $newValues Values of X for which we want to find Y + * @param mixed $const A logical (boolean) value specifying whether to force the intersect to equal 0 or not + * + * @return float[] + */ + public static function TREND($yValues, $xValues = [], $newValues = [], $const = true) + { + $yValues = Functions::flattenArray($yValues); + $xValues = Functions::flattenArray($xValues); + $newValues = Functions::flattenArray($newValues); + $const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const); + + $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues, $const); + if (empty($newValues)) { + $newValues = $bestFitLinear->getXValues(); + } + + $returnArray = []; + foreach ($newValues as $xValue) { + $returnArray[0][] = [$bestFitLinear->getValueOfYForX($xValue)]; + } + + return $returnArray; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/VarianceBase.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/VarianceBase.php new file mode 100644 index 0000000..e533467 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/VarianceBase.php @@ -0,0 +1,28 @@ + 1) { + $summerA *= $aCount; + $summerB *= $summerB; + + return ($summerA - $summerB) / ($aCount * ($aCount - 1)); + } + + return $returnValue; + } + + /** + * VARA. + * + * Estimates variance based on a sample, including numbers, text, and logical values + * + * Excel Function: + * VARA(value1[,value2[, ...]]) + * + * @param mixed ...$args Data values + * + * @return float|string (string if result is an error) + */ + public static function VARA(...$args) + { + $returnValue = Functions::DIV0(); + + $summerA = $summerB = 0.0; + + // Loop through arguments + $aArgs = Functions::flattenArrayIndexed($args); + $aCount = 0; + foreach ($aArgs as $k => $arg) { + if ((is_string($arg)) && (Functions::isValue($k))) { + return Functions::VALUE(); + } elseif ((is_string($arg)) && (!Functions::isMatrixValue($k))) { + } else { + // Is it a numeric value? + if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) & ($arg != '')))) { + $arg = self::datatypeAdjustmentAllowStrings($arg); + $summerA += ($arg * $arg); + $summerB += $arg; + ++$aCount; + } + } + } + + if ($aCount > 1) { + $summerA *= $aCount; + $summerB *= $summerB; + + return ($summerA - $summerB) / ($aCount * ($aCount - 1)); + } + + return $returnValue; + } + + /** + * VARP. + * + * Calculates variance based on the entire population + * + * Excel Function: + * VARP(value1[,value2[, ...]]) + * + * @param mixed ...$args Data values + * + * @return float|string (string if result is an error) + */ + public static function VARP(...$args) + { + // Return value + $returnValue = Functions::DIV0(); + + $summerA = $summerB = 0.0; + + // Loop through arguments + $aArgs = Functions::flattenArray($args); + $aCount = 0; + foreach ($aArgs as $arg) { + $arg = self::datatypeAdjustmentBooleans($arg); + + // Is it a numeric value? + if ((is_numeric($arg)) && (!is_string($arg))) { + $summerA += ($arg * $arg); + $summerB += $arg; + ++$aCount; + } + } + + if ($aCount > 0) { + $summerA *= $aCount; + $summerB *= $summerB; + + return ($summerA - $summerB) / ($aCount * $aCount); + } + + return $returnValue; + } + + /** + * VARPA. + * + * Calculates variance based on the entire population, including numbers, text, and logical values + * + * Excel Function: + * VARPA(value1[,value2[, ...]]) + * + * @param mixed ...$args Data values + * + * @return float|string (string if result is an error) + */ + public static function VARPA(...$args) + { + $returnValue = Functions::DIV0(); + + $summerA = $summerB = 0.0; + + // Loop through arguments + $aArgs = Functions::flattenArrayIndexed($args); + $aCount = 0; + foreach ($aArgs as $k => $arg) { + if ((is_string($arg)) && (Functions::isValue($k))) { + return Functions::VALUE(); + } elseif ((is_string($arg)) && (!Functions::isMatrixValue($k))) { + } else { + // Is it a numeric value? + if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) & ($arg != '')))) { + $arg = self::datatypeAdjustmentAllowStrings($arg); + $summerA += ($arg * $arg); + $summerB += $arg; + ++$aCount; + } + } + } + + if ($aCount > 0) { + $summerA *= $aCount; + $summerB *= $summerB; + + return ($summerA - $summerB) / ($aCount * $aCount); + } + + return $returnValue; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData.php new file mode 100644 index 0000000..0bde3b7 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData.php @@ -0,0 +1,448 @@ + 255) { + return Functions::VALUE(); + } + $result = iconv('UCS-4LE', 'UTF-8', pack('V', $character)); + + return ($result === false) ? '' : $result; + } + + /** + * CODE. + * + * @param mixed $characters String character to convert to its ASCII value + * + * @return int|string A string if arguments are invalid + */ + public static function code($characters) + { + $characters = Helpers::extractString($characters); + if ($characters === '') { + return Functions::VALUE(); + } + + $character = $characters; + if (mb_strlen($characters, 'UTF-8') > 1) { + $character = mb_substr($characters, 0, 1, 'UTF-8'); + } + + return self::unicodeToOrd($character); + } + + private static function unicodeToOrd(string $character): int + { + $retVal = 0; + $iconv = iconv('UTF-8', 'UCS-4LE', $character); + if ($iconv !== false) { + $result = unpack('V', $iconv); + if (is_array($result) && isset($result[1])) { + $retVal = $result[1]; + } + } + + return $retVal; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Concatenate.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Concatenate.php new file mode 100644 index 0000000..d53fc82 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Concatenate.php @@ -0,0 +1,71 @@ + &$arg) { + if ($ignoreEmpty === true && is_string($arg) && trim($arg) === '') { + unset($aArgs[$key]); + } elseif (is_bool($arg)) { + $arg = Helpers::convertBooleanValue($arg); + } + } + + return implode($delimiter, $aArgs); + } + + /** + * REPT. + * + * Returns the result of builtin function round after validating args. + * + * @param mixed $stringValue The value to repeat + * @param mixed $repeatCount The number of times the string value should be repeated + */ + public static function builtinREPT($stringValue, $repeatCount): string + { + $repeatCount = Functions::flattenSingleValue($repeatCount); + $stringValue = Helpers::extractString($stringValue); + + if (!is_numeric($repeatCount) || $repeatCount < 0) { + return Functions::VALUE(); + } + + return str_repeat($stringValue, (int) $repeatCount); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Extract.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Extract.php new file mode 100644 index 0000000..7f18e0c --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Extract.php @@ -0,0 +1,64 @@ +getMessage(); + } + + return mb_substr($value ?? '', 0, $chars, 'UTF-8'); + } + + /** + * MID. + * + * @param mixed $value String value from which to extract characters + * @param mixed $start Integer offset of the first character that we want to extract + * @param mixed $chars The number of characters to extract (as an integer) + */ + public static function mid($value, $start, $chars): string + { + try { + $value = Helpers::extractString($value); + $start = Helpers::extractInt($start, 1); + $chars = Helpers::extractInt($chars, 0); + } catch (CalcExp $e) { + return $e->getMessage(); + } + + return mb_substr($value ?? '', --$start, $chars, 'UTF-8'); + } + + /** + * RIGHT. + * + * @param mixed $value String value from which to extract characters + * @param mixed $chars The number of characters to extract (as an integer) + */ + public static function right($value, $chars = 1): string + { + try { + $value = Helpers::extractString($value); + $chars = Helpers::extractInt($chars, 0, 1); + } catch (CalcExp $e) { + return $e->getMessage(); + } + + return mb_substr($value ?? '', mb_strlen($value ?? '', 'UTF-8') - $chars, $chars, 'UTF-8'); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Format.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Format.php new file mode 100644 index 0000000..3286de0 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Format.php @@ -0,0 +1,236 @@ +getMessage(); + } + + $mask = '$#,##0'; + if ($decimals > 0) { + $mask .= '.' . str_repeat('0', $decimals); + } else { + $round = 10 ** abs($decimals); + if ($value < 0) { + $round = 0 - $round; + } + $value = MathTrig\Round::multiple($value, $round); + } + $mask = "{$mask};-{$mask}"; + + return NumberFormat::toFormattedString($value, $mask); + } + + /** + * FIXED. + * + * @param mixed $value The value to format + * @param mixed $decimals Integer value for the number of decimal places that should be formatted + * @param mixed $noCommas Boolean value indicating whether the value should have thousands separators or not + */ + public static function FIXEDFORMAT($value, $decimals = 2, $noCommas = false): string + { + try { + $value = Helpers::extractFloat($value); + $decimals = Helpers::extractInt($decimals, -100, 0, true); + $noCommas = Functions::flattenSingleValue($noCommas); + } catch (CalcExp $e) { + return $e->getMessage(); + } + + $valueResult = round($value, $decimals); + if ($decimals < 0) { + $decimals = 0; + } + if ($noCommas === false) { + $valueResult = number_format( + $valueResult, + $decimals, + StringHelper::getDecimalSeparator(), + StringHelper::getThousandsSeparator() + ); + } + + return (string) $valueResult; + } + + /** + * TEXT. + * + * @param mixed $value The value to format + * @param mixed $format A string with the Format mask that should be used + */ + public static function TEXTFORMAT($value, $format): string + { + $value = Helpers::extractString($value); + $format = Helpers::extractString($format); + + if (!is_numeric($value) && Date::isDateTimeFormatCode($format)) { + $value = DateTimeExcel\DateValue::fromString($value); + } + + return (string) NumberFormat::toFormattedString($value, $format); + } + + /** + * @param mixed $value Value to check + * + * @return mixed + */ + private static function convertValue($value) + { + $value = ($value === null) ? 0 : Functions::flattenSingleValue($value); + if (is_bool($value)) { + if (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE) { + $value = (int) $value; + } else { + throw new CalcExp(Functions::VALUE()); + } + } + + return $value; + } + + /** + * VALUE. + * + * @param mixed $value Value to check + * + * @return DateTimeInterface|float|int|string A string if arguments are invalid + */ + public static function VALUE($value = '') + { + try { + $value = self::convertValue($value); + } catch (CalcExp $e) { + return $e->getMessage(); + } + if (!is_numeric($value)) { + $numberValue = str_replace( + StringHelper::getThousandsSeparator(), + '', + trim($value, " \t\n\r\0\x0B" . StringHelper::getCurrencyCode()) + ); + if (is_numeric($numberValue)) { + return (float) $numberValue; + } + + $dateSetting = Functions::getReturnDateType(); + Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); + + if (strpos($value, ':') !== false) { + $timeValue = DateTimeExcel\TimeValue::fromString($value); + if ($timeValue !== Functions::VALUE()) { + Functions::setReturnDateType($dateSetting); + + return $timeValue; + } + } + $dateValue = DateTimeExcel\DateValue::fromString($value); + if ($dateValue !== Functions::VALUE()) { + Functions::setReturnDateType($dateSetting); + + return $dateValue; + } + Functions::setReturnDateType($dateSetting); + + return Functions::VALUE(); + } + + return (float) $value; + } + + /** + * @param mixed $decimalSeparator + */ + private static function getDecimalSeparator($decimalSeparator): string + { + $decimalSeparator = Functions::flattenSingleValue($decimalSeparator); + + return empty($decimalSeparator) ? StringHelper::getDecimalSeparator() : (string) $decimalSeparator; + } + + /** + * @param mixed $groupSeparator + */ + private static function getGroupSeparator($groupSeparator): string + { + $groupSeparator = Functions::flattenSingleValue($groupSeparator); + + return empty($groupSeparator) ? StringHelper::getThousandsSeparator() : (string) $groupSeparator; + } + + /** + * NUMBERVALUE. + * + * @param mixed $value The value to format + * @param mixed $decimalSeparator A string with the decimal separator to use, defaults to locale defined value + * @param mixed $groupSeparator A string with the group/thousands separator to use, defaults to locale defined value + * + * @return float|string + */ + public static function NUMBERVALUE($value = '', $decimalSeparator = null, $groupSeparator = null) + { + try { + $value = self::convertValue($value); + $decimalSeparator = self::getDecimalSeparator($decimalSeparator); + $groupSeparator = self::getGroupSeparator($groupSeparator); + } catch (CalcExp $e) { + return $e->getMessage(); + } + + if (!is_numeric($value)) { + $decimalPositions = preg_match_all('/' . preg_quote($decimalSeparator) . '/', $value, $matches, PREG_OFFSET_CAPTURE); + if ($decimalPositions > 1) { + return Functions::VALUE(); + } + $decimalOffset = array_pop($matches[0])[1]; + if (strpos($value, $groupSeparator, $decimalOffset) !== false) { + return Functions::VALUE(); + } + + $value = str_replace([$groupSeparator, $decimalSeparator], ['', '.'], $value); + + // Handle the special case of trailing % signs + $percentageString = rtrim($value, '%'); + if (!is_numeric($percentageString)) { + return Functions::VALUE(); + } + + $percentageAdjustment = strlen($value) - strlen($percentageString); + if ($percentageAdjustment) { + $value = (float) $percentageString; + $value /= 10 ** ($percentageAdjustment * 2); + } + } + + return is_array($value) ? Functions::VALUE() : (float) $value; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Helpers.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Helpers.php new file mode 100644 index 0000000..423b6d3 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Helpers.php @@ -0,0 +1,90 @@ +getMessage(); + } + + return $left . $newText . $right; + } + + /** + * SUBSTITUTE. + * + * @param mixed $text The text string value to modify + * @param mixed $fromText The string value that we want to replace in $text + * @param mixed $toText The string value that we want to replace with in $text + * @param mixed $instance Integer instance Number for the occurrence of frmText to change + */ + public static function substitute($text = '', $fromText = '', $toText = '', $instance = null): string + { + try { + $text = Helpers::extractString($text); + $fromText = Helpers::extractString($fromText); + $toText = Helpers::extractString($toText); + $instance = Functions::flattenSingleValue($instance); + if ($instance === null) { + return str_replace($fromText, $toText, $text); + } + if (is_bool($instance)) { + if ($instance === false || Functions::getCompatibilityMode() !== Functions::COMPATIBILITY_OPENOFFICE) { + return Functions::Value(); + } + $instance = 1; + } + $instance = Helpers::extractInt($instance, 1, 0, true); + } catch (CalcExp $e) { + return $e->getMessage(); + } + + $pos = -1; + while ($instance > 0) { + $pos = mb_strpos($text, $fromText, $pos + 1, 'UTF-8'); + if ($pos === false) { + break; + } + --$instance; + } + + if ($pos !== false) { + return self::REPLACE($text, ++$pos, mb_strlen($fromText, 'UTF-8'), $toText); + } + + return $text; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Search.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Search.php new file mode 100644 index 0000000..c9eed2e --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Search.php @@ -0,0 +1,76 @@ +getMessage(); + } + + if (StringHelper::countCharacters($haystack) >= $offset) { + if (StringHelper::countCharacters($needle) === 0) { + return $offset; + } + + $pos = mb_strpos($haystack, $needle, --$offset, 'UTF-8'); + if ($pos !== false) { + return ++$pos; + } + } + + return Functions::VALUE(); + } + + /** + * SEARCH (case insensitive search). + * + * @param mixed $needle The string to look for + * @param mixed $haystack The string in which to look + * @param mixed $offset Integer offset within $haystack to start searching from + * + * @return int|string + */ + public static function insensitive($needle, $haystack, $offset = 1) + { + try { + $needle = Helpers::extractString($needle); + $haystack = Helpers::extractString($haystack); + $offset = Helpers::extractInt($offset, 1, 0, true); + } catch (CalcExp $e) { + return $e->getMessage(); + } + + if (StringHelper::countCharacters($haystack) >= $offset) { + if (StringHelper::countCharacters($needle) === 0) { + return $offset; + } + + $pos = mb_stripos($haystack, $needle, --$offset, 'UTF-8'); + if ($pos !== false) { + return ++$pos; + } + } + + return Functions::VALUE(); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Text.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Text.php new file mode 100644 index 0000000..6f8253e --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Text.php @@ -0,0 +1,54 @@ +count; + } + + /** + * Push a new entry onto the stack. + * + * @param mixed $type + * @param mixed $value + * @param mixed $reference + * @param null|string $storeKey will store the result under this alias + * @param null|string $onlyIf will only run computation if the matching + * store key is true + * @param null|string $onlyIfNot will only run computation if the matching + * store key is false + */ + public function push( + $type, + $value, + $reference = null, + $storeKey = null, + $onlyIf = null, + $onlyIfNot = null + ): void { + $stackItem = $this->getStackItem($type, $value, $reference, $storeKey, $onlyIf, $onlyIfNot); + + $this->stack[$this->count++] = $stackItem; + + if ($type == 'Function') { + $localeFunction = Calculation::localeFunc($value); + if ($localeFunction != $value) { + $this->stack[($this->count - 1)]['localeValue'] = $localeFunction; + } + } + } + + public function getStackItem( + $type, + $value, + $reference = null, + $storeKey = null, + $onlyIf = null, + $onlyIfNot = null + ) { + $stackItem = [ + 'type' => $type, + 'value' => $value, + 'reference' => $reference, + ]; + + if (isset($storeKey)) { + $stackItem['storeKey'] = $storeKey; + } + + if (isset($onlyIf)) { + $stackItem['onlyIf'] = $onlyIf; + } + + if (isset($onlyIfNot)) { + $stackItem['onlyIfNot'] = $onlyIfNot; + } + + return $stackItem; + } + + /** + * Pop the last entry from the stack. + * + * @return mixed + */ + public function pop() + { + if ($this->count > 0) { + return $this->stack[--$this->count]; + } + + return null; + } + + /** + * Return an entry from the stack without removing it. + * + * @param int $n number indicating how far back in the stack we want to look + * + * @return mixed + */ + public function last($n = 1) + { + if ($this->count - $n < 0) { + return null; + } + + return $this->stack[$this->count - $n]; + } + + /** + * Clear the stack. + */ + public function clear(): void + { + $this->stack = []; + $this->count = 0; + } + + public function __toString() + { + $str = 'Stack: '; + foreach ($this->stack as $index => $item) { + if ($index > $this->count - 1) { + break; + } + $value = $item['value'] ?? 'no value'; + while (is_array($value)) { + $value = array_pop($value); + } + $str .= $value . ' |> '; + } + + return $str; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Web.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Web.php new file mode 100644 index 0000000..3f3d945 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Web.php @@ -0,0 +1,27 @@ + 2048) { + return Functions::VALUE(); // Invalid URL length + } + + if (!preg_match('/^http[s]?:\/\//', $url)) { + return Functions::VALUE(); // Invalid protocol + } + + // Get results from the the webservice + $client = Settings::getHttpClient(); + $requestFactory = Settings::getRequestFactory(); + $request = $requestFactory->createRequest('GET', $url); + + try { + $response = $client->sendRequest($request); + } catch (ClientExceptionInterface $e) { + return Functions::VALUE(); // cURL error + } + + if ($response->getStatusCode() != 200) { + return Functions::VALUE(); // cURL error + } + + $output = $response->getBody()->getContents(); + if (strlen($output) > 32767) { + return Functions::VALUE(); // Output not a string or too long + } + + return $output; + } + + /** + * URLENCODE. + * + * Returns data from a web service on the Internet or Intranet. + * + * Excel Function: + * urlEncode(text) + * + * @param mixed $text + * + * @return string the url encoded output + */ + public static function urlEncode($text) + { + if (!is_string($text)) { + return Functions::VALUE(); + } + + return str_replace('+', '%20', urlencode($text)); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/functionlist.txt b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/functionlist.txt new file mode 100644 index 0000000..270715c --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/functionlist.txt @@ -0,0 +1,400 @@ +ABS +ACCRINT +ACCRINTM +ACOS +ACOSH +ACOT +ACOTH +ADDRESS +AMORDEGRC +AMORLINC +AND +ARABIC +AREAS +ASC +ASIN +ASINH +ATAN +ATAN2 +ATANH +AVEDEV +AVERAGE +AVERAGEA +AVERAGEIF +AVERAGEIFS +BAHTTEXT +BASE +BESSELI +BESSELJ +BESSELK +BESSELY +BETADIST +BETAINV +BIN2DEC +BIN2HEX +BIN2OCT +BINOMDIST +BITAND +BITLSHIFT +BITOR +BITRSHIFT +BITXOR +CEILING +CEILING.MATH +CEILING.PRECISE +CELL +CHAR +CHIDIST +CHIINV +CHITEST +CHOOSE +CLEAN +CODE +COLUMN +COLUMNS +COMBIN +COMBINA +COMPLEX +CONCAT +CONCATENATE +CONFIDENCE +CONVERT +CORREL +COS +COSH +COT +COTH +COUNT +COUNTA +COUNTBLANK +COUNTIF +COUNTIFS +COUPDAYBS +COUPDAYBS +COUPDAYSNC +COUPNCD +COUPNUM +COUPPCD +COVAR +CRITBINOM +CSC +CSCH +CUBEKPIMEMBER +CUBEMEMBER +CUBEMEMBERPROPERTY +CUBERANKEDMEMBER +CUBESET +CUBESETCOUNT +CUBEVALUE +CUMIPMT +CUMPRINC +DATE +DATEDIF +DATEVALUE +DAVERAGE +DAY +DAYS +DAYS360 +DB +DCOUNT +DCOUNTA +DDB +DEC2BIN +DEC2HEX +DEC2OCT +DEGREES +DELTA +DEVSQ +DGET +DISC +DMAX +DMIN +DOLLAR +DOLLARDE +DOLLARFR +DPRODUCT +DSTDEV +DSTDEVP +DSUM +DURATION +DVAR +DVARP +EDATE +EFFECT +EOMONTH +ERF +ERF.PRECISE +ERFC +ERFC.PRECISE +ERROR.TYPE +EVEN +EXACT +EXP +EXPONDIST +FACT +FACTDOUBLE +FALSE +FDIST +FIND +FINDB +FINV +FISHER +FISHERINV +FIXED +FLOOR +FLOOR.MATH +FLOOR.PRECISE +FORECAST +FREQUENCY +FTEST +FV +FVSCHEDULE +GAMAMDIST +GAMMAINV +GAMMALN +GCD +GEOMEAN +GESTEP +GETPIVOTDATA +GROWTH +HARMEAN +HEX2BIN +HEX2OCT +HLOOKUP +HOUR +HYPERLINK +HYPGEOMDIST +IF +IFERROR +IFS +IMABS +IMAGINARY +IMARGUMENT +IMCONJUGATE +IMCOS +IMCOSH +IMCOT +IMCSC +IMCSCH +IMEXP +IMLN +IMLOG10 +IMLOG2 +IMPOWER +IMPRODUCT +IMREAL +IMSEC +IMSECH +IMSIN +IMSINH +IMSQRT +IMSUB +IMSUM +IMTAN +INDEX +INDIRECT +INFO +INT +INTERCEPT +INTRATE +IPMT +IRR +ISBLANK +ISERR +ISERROR +ISEVEN +ISLOGICAL +ISNA +ISNONTEXT +ISNUMBER +ISODD +ISOWEEKNUM +ISPMT +ISREF +ISTEXT +JIS +KURT +LARGE +LCM +LEFT +LEFTB +LEN +LENB +LINEST +LN +LOG +LOG10 +LOGEST +LOGINV +LOGNORMDIST +LOOKUP +LOWER +MATCH +MAX +MAXA +MAXIFS +MDETERM +MDURATION +MEDIAN +MID +MIDB +MIN +MINA +MINIFS +MINUTE +MINVERSE +MIRR +MMULT +MOD +MODE +MONTH +MROUND +MULTINOMIAL +MUNIT +N +NA +NEGBINOMDIST +NETWORKDAYS +NOMINAL +NORMDIST +NORMINV +NORMSDIST +NORMSINV +NOT +NOW +NPER +NPV +NUMBERVALUE +OCT2BIN +OCT2DEC +OCT2HEX +ODD +ODDFPRICE +ODDFYIELD +ODDLPRICE +ODDLYIELD +OFFSET +OR +PDURATION +PEARSON +PERCENTILE +PERCENTRANK +PERMUT +PERMUTATIONA +PHONETIC +PI +PMT +POISSON +POWER +PPMT +PRICE +PRICEDISC +PRICEMAT +PROB +PRODUCT +PROPER +PV +QUARTILE +QUOTIENT +RADIANS +RAND +RANDBETWEEN +RANK +RATE +RECEIVED +REPLACE +REPLACEB +REPT +RIGHT +RIGHTB +ROMAN +ROUND +ROUNDDOWN +ROUNDUP +ROW +ROWS +RRI +RSQ +RTD +SEARCH +SEARCHB +SEC +SECH +SECOND +SERIESSUM +SHEET +SHEETS +SIGN +SIN +SINH +SKEW +SLN +SLOPE +SMALL +SQRT +SQRTPI +STANDARDIZE +STDEV +STDEV.A +STDEV.P +STDEVA +STDEVP +STDEVPA +STEYX +SUBSTITUTE +SUBTOTAL +SUM +SUMIF +SUMIFS +SUMPRODUCT +SUMSQ +SUMX2MY2 +SUMX2PY2 +SUMXMY2 +SWITCH +SYD +T +TAN +TANH +TBILLEQ +TBILLPRICE +TBILLYIELD +TDIST +TEXT +TEXTJOIN +TIME +TIMEVALUE +TINV +TODAY +TRANSPOSE +TREND +TRIM +TRIMMEAN +TRUE +TRUNC +TTEST +TYPE +UNICHAR +UNIORD +UPPER +USDOLLAR +VALUE +VAR +VARA +VARP +VARPA +VDB +VLOOKUP +WEEKDAY +WEEKNUM +WEIBULL +WORKDAY +XIRR +XNPV +XOR +YEAR +YEARFRAC +YIELD +YIELDDISC +YIELDMAT +ZTEST diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/Translations.xlsx b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/Translations.xlsx new file mode 100644 index 0000000..518176a Binary files /dev/null and b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/Translations.xlsx differ diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/bg/config b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/bg/config new file mode 100644 index 0000000..86f94d3 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/bg/config @@ -0,0 +1,27 @@ +## +## PhpSpreadsheet +## +## +## + + +ArgumentSeparator = ; + + +## +## (For future use) +## +currencySymbol = лв + + +## +## Excel Error Codes (For future use) + +## +NULL = #ПРАЗНО! +DIV0 = #ДЕЛ/0! +VALUE = #СТОЙНОСТ! +REF = #РЕФ! +NAME = #ИМЕ? +NUM = #ЧИСЛО! +NA = #Н/Д diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/bg/functions b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/bg/functions new file mode 100644 index 0000000..4bc1574 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/bg/functions @@ -0,0 +1,417 @@ +## +## PhpSpreadsheet +## +## +## Data in this file derived from information provided by web-junior (http://www.web-junior.net/) +## +## + + +## +## Add-in and Automation functions Функции надстроек и автоматизации +## +GETPIVOTDATA = ПОЛУЧИТЬ.ДАННЫЕ.СВОДНОЙ.ТАБЛИЦЫ ## Возвращает данные, хранящиеся в отчете сводной таблицы. + + +## +## Cube functions Функции Куб +## +CUBEKPIMEMBER = КУБЭЛЕМЕНТКИП ## Возвращает свойство ключевого индикатора производительности «(КИП)» и отображает имя «КИП» в ячейке. «КИП» представляет собой количественную величину, такую как ежемесячная валовая прибыль или ежеквартальная текучесть кадров, используемой для контроля эффективности работы организации. +CUBEMEMBER = КУБЭЛЕМЕНТ ## Возвращает элемент или кортеж из куба. Используется для проверки существования элемента или кортежа в кубе. +CUBEMEMBERPROPERTY = КУБСВОЙСТВОЭЛЕМЕНТА ## Возвращает значение свойства элемента из куба. Используется для проверки существования имени элемента в кубе и возвращает указанное свойство для этого элемента. +CUBERANKEDMEMBER = КУБПОРЭЛЕМЕНТ ## Возвращает n-ый или ранжированный элемент в множество. Используется для возвращения одного или нескольких элементов в множество, например, лучшего продавца или 10 лучших студентов. +CUBESET = КУБМНОЖ ## Определяет вычислительное множество элементов или кортежей, отправляя на сервер выражение, которое создает множество, а затем возвращает его в Microsoft Office Excel. +CUBESETCOUNT = КУБЧИСЛОЭЛМНОЖ ## Возвращает число элементов множества. +CUBEVALUE = КУБЗНАЧЕНИЕ ## Возвращает обобщенное значение из куба. + + +## +## Database functions Функции для работы с базами данных +## +DAVERAGE = ДСРЗНАЧ ## Возвращает среднее значение выбранных записей базы данных. +DCOUNT = БСЧЁТ ## Подсчитывает количество числовых ячеек в базе данных. +DCOUNTA = БСЧЁТА ## Подсчитывает количество непустых ячеек в базе данных. +DGET = БИЗВЛЕЧЬ ## Извлекает из базы данных одну запись, удовлетворяющую заданному условию. +DMAX = ДМАКС ## Возвращает максимальное значение среди выделенных записей базы данных. +DMIN = ДМИН ## Возвращает минимальное значение среди выделенных записей базы данных. +DPRODUCT = БДПРОИЗВЕД ## Перемножает значения определенного поля в записях базы данных, удовлетворяющих условию. +DSTDEV = ДСТАНДОТКЛ ## Оценивает стандартное отклонение по выборке для выделенных записей базы данных. +DSTDEVP = ДСТАНДОТКЛП ## Вычисляет стандартное отклонение по генеральной совокупности для выделенных записей базы данных +DSUM = БДСУММ ## Суммирует числа в поле для записей базы данных, удовлетворяющих условию. +DVAR = БДДИСП ## Оценивает дисперсию по выборке из выделенных записей базы данных +DVARP = БДДИСПП ## Вычисляет дисперсию по генеральной совокупности для выделенных записей базы данных + + +## +## Date and time functions Функции даты и времени +## +DATE = ДАТА ## Возвращает заданную дату в числовом формате. +DATEVALUE = ДАТАЗНАЧ ## Преобразует дату из текстового формата в числовой формат. +DAY = ДЕНЬ ## Преобразует дату в числовом формате в день месяца. +DAYS360 = ДНЕЙ360 ## Вычисляет количество дней между двумя датами на основе 360-дневного года. +EDATE = ДАТАМЕС ## Возвращает дату в числовом формате, отстоящую на заданное число месяцев вперед или назад от начальной даты. +EOMONTH = КОНМЕСЯЦА ## Возвращает дату в числовом формате для последнего дня месяца, отстоящего вперед или назад на заданное число месяцев. +HOUR = ЧАС ## Преобразует дату в числовом формате в часы. +MINUTE = МИНУТЫ ## Преобразует дату в числовом формате в минуты. +MONTH = МЕСЯЦ ## Преобразует дату в числовом формате в месяцы. +NETWORKDAYS = ЧИСТРАБДНИ ## Возвращает количество рабочих дней между двумя датами. +NOW = ТДАТА ## Возвращает текущую дату и время в числовом формате. +SECOND = СЕКУНДЫ ## Преобразует дату в числовом формате в секунды. +TIME = ВРЕМЯ ## Возвращает заданное время в числовом формате. +TIMEVALUE = ВРЕМЗНАЧ ## Преобразует время из текстового формата в числовой формат. +TODAY = СЕГОДНЯ ## Возвращает текущую дату в числовом формате. +WEEKDAY = ДЕНЬНЕД ## Преобразует дату в числовом формате в день недели. +WEEKNUM = НОМНЕДЕЛИ ## Преобразует числовое представление в число, которое указывает, на какую неделю года приходится указанная дата. +WORKDAY = РАБДЕНЬ ## Возвращает дату в числовом формате, отстоящую вперед или назад на заданное количество рабочих дней. +YEAR = ГОД ## Преобразует дату в числовом формате в год. +YEARFRAC = ДОЛЯГОДА ## Возвращает долю года, которую составляет количество дней между начальной и конечной датами. + + +## +## Engineering functions Инженерные функции +## +BESSELI = БЕССЕЛЬ.I ## Возвращает модифицированную функцию Бесселя In(x). +BESSELJ = БЕССЕЛЬ.J ## Возвращает функцию Бесселя Jn(x). +BESSELK = БЕССЕЛЬ.K ## Возвращает модифицированную функцию Бесселя Kn(x). +BESSELY = БЕССЕЛЬ.Y ## Возвращает функцию Бесселя Yn(x). +BIN2DEC = ДВ.В.ДЕС ## Преобразует двоичное число в десятичное. +BIN2HEX = ДВ.В.ШЕСТН ## Преобразует двоичное число в шестнадцатеричное. +BIN2OCT = ДВ.В.ВОСЬМ ## Преобразует двоичное число в восьмеричное. +COMPLEX = КОМПЛЕКСН ## Преобразует коэффициенты при вещественной и мнимой частях комплексного числа в комплексное число. +CONVERT = ПРЕОБР ## Преобразует число из одной системы единиц измерения в другую. +DEC2BIN = ДЕС.В.ДВ ## Преобразует десятичное число в двоичное. +DEC2HEX = ДЕС.В.ШЕСТН ## Преобразует десятичное число в шестнадцатеричное. +DEC2OCT = ДЕС.В.ВОСЬМ ## Преобразует десятичное число в восьмеричное. +DELTA = ДЕЛЬТА ## Проверяет равенство двух значений. +ERF = ФОШ ## Возвращает функцию ошибки. +ERFC = ДФОШ ## Возвращает дополнительную функцию ошибки. +GESTEP = ПОРОГ ## Проверяет, не превышает ли данное число порогового значения. +HEX2BIN = ШЕСТН.В.ДВ ## Преобразует шестнадцатеричное число в двоичное. +HEX2DEC = ШЕСТН.В.ДЕС ## Преобразует шестнадцатеричное число в десятичное. +HEX2OCT = ШЕСТН.В.ВОСЬМ ## Преобразует шестнадцатеричное число в восьмеричное. +IMABS = МНИМ.ABS ## Возвращает абсолютную величину (модуль) комплексного числа. +IMAGINARY = МНИМ.ЧАСТЬ ## Возвращает коэффициент при мнимой части комплексного числа. +IMARGUMENT = МНИМ.АРГУМЕНТ ## Возвращает значение аргумента комплексного числа (тета) — угол, выраженный в радианах. +IMCONJUGATE = МНИМ.СОПРЯЖ ## Возвращает комплексно-сопряженное комплексное число. +IMCOS = МНИМ.COS ## Возвращает косинус комплексного числа. +IMDIV = МНИМ.ДЕЛ ## Возвращает частное от деления двух комплексных чисел. +IMEXP = МНИМ.EXP ## Возвращает экспоненту комплексного числа. +IMLN = МНИМ.LN ## Возвращает натуральный логарифм комплексного числа. +IMLOG10 = МНИМ.LOG10 ## Возвращает обычный (десятичный) логарифм комплексного числа. +IMLOG2 = МНИМ.LOG2 ## Возвращает двоичный логарифм комплексного числа. +IMPOWER = МНИМ.СТЕПЕНЬ ## Возвращает комплексное число, возведенное в целую степень. +IMPRODUCT = МНИМ.ПРОИЗВЕД ## Возвращает произведение от 2 до 29 комплексных чисел. +IMREAL = МНИМ.ВЕЩ ## Возвращает коэффициент при вещественной части комплексного числа. +IMSIN = МНИМ.SIN ## Возвращает синус комплексного числа. +IMSQRT = МНИМ.КОРЕНЬ ## Возвращает значение квадратного корня из комплексного числа. +IMSUB = МНИМ.РАЗН ## Возвращает разность двух комплексных чисел. +IMSUM = МНИМ.СУММ ## Возвращает сумму комплексных чисел. +OCT2BIN = ВОСЬМ.В.ДВ ## Преобразует восьмеричное число в двоичное. +OCT2DEC = ВОСЬМ.В.ДЕС ## Преобразует восьмеричное число в десятичное. +OCT2HEX = ВОСЬМ.В.ШЕСТН ## Преобразует восьмеричное число в шестнадцатеричное. + + +## +## Financial functions Финансовые функции +## +ACCRINT = НАКОПДОХОД ## Возвращает накопленный процент по ценным бумагам с периодической выплатой процентов. +ACCRINTM = НАКОПДОХОДПОГАШ ## Возвращает накопленный процент по ценным бумагам, проценты по которым выплачиваются в срок погашения. +AMORDEGRC = АМОРУМ ## Возвращает величину амортизации для каждого периода, используя коэффициент амортизации. +AMORLINC = АМОРУВ ## Возвращает величину амортизации для каждого периода. +COUPDAYBS = ДНЕЙКУПОНДО ## Возвращает количество дней от начала действия купона до даты соглашения. +COUPDAYS = ДНЕЙКУПОН ## Возвращает число дней в периоде купона, содержащем дату соглашения. +COUPDAYSNC = ДНЕЙКУПОНПОСЛЕ ## Возвращает число дней от даты соглашения до срока следующего купона. +COUPNCD = ДАТАКУПОНПОСЛЕ ## Возвращает следующую дату купона после даты соглашения. +COUPNUM = ЧИСЛКУПОН ## Возвращает количество купонов, которые могут быть оплачены между датой соглашения и сроком вступления в силу. +COUPPCD = ДАТАКУПОНДО ## Возвращает предыдущую дату купона перед датой соглашения. +CUMIPMT = ОБЩПЛАТ ## Возвращает общую выплату, произведенную между двумя периодическими выплатами. +CUMPRINC = ОБЩДОХОД ## Возвращает общую выплату по займу между двумя периодами. +DB = ФУО ## Возвращает величину амортизации актива для заданного периода, рассчитанную методом фиксированного уменьшения остатка. +DDB = ДДОБ ## Возвращает величину амортизации актива за данный период, используя метод двойного уменьшения остатка или иной явно указанный метод. +DISC = СКИДКА ## Возвращает норму скидки для ценных бумаг. +DOLLARDE = РУБЛЬ.ДЕС ## Преобразует цену в рублях, выраженную в виде дроби, в цену в рублях, выраженную десятичным числом. +DOLLARFR = РУБЛЬ.ДРОБЬ ## Преобразует цену в рублях, выраженную десятичным числом, в цену в рублях, выраженную в виде дроби. +DURATION = ДЛИТ ## Возвращает ежегодную продолжительность действия ценных бумаг с периодическими выплатами по процентам. +EFFECT = ЭФФЕКТ ## Возвращает действующие ежегодные процентные ставки. +FV = БС ## Возвращает будущую стоимость инвестиции. +FVSCHEDULE = БЗРАСПИС ## Возвращает будущую стоимость первоначальной основной суммы после начисления ряда сложных процентов. +INTRATE = ИНОРМА ## Возвращает процентную ставку для полностью инвестированных ценных бумаг. +IPMT = ПРПЛТ ## Возвращает величину выплаты прибыли на вложения за данный период. +IRR = ВСД ## Возвращает внутреннюю ставку доходности для ряда потоков денежных средств. +ISPMT = ПРОЦПЛАТ ## Вычисляет выплаты за указанный период инвестиции. +MDURATION = МДЛИТ ## Возвращает модифицированную длительность Маколея для ценных бумаг с предполагаемой номинальной стоимостью 100 рублей. +MIRR = МВСД ## Возвращает внутреннюю ставку доходности, при которой положительные и отрицательные денежные потоки имеют разные значения ставки. +NOMINAL = НОМИНАЛ ## Возвращает номинальную годовую процентную ставку. +NPER = КПЕР ## Возвращает общее количество периодов выплаты для данного вклада. +NPV = ЧПС ## Возвращает чистую приведенную стоимость инвестиции, основанной на серии периодических денежных потоков и ставке дисконтирования. +ODDFPRICE = ЦЕНАПЕРВНЕРЕГ ## Возвращает цену за 100 рублей нарицательной стоимости ценных бумаг с нерегулярным первым периодом. +ODDFYIELD = ДОХОДПЕРВНЕРЕГ ## Возвращает доход по ценным бумагам с нерегулярным первым периодом. +ODDLPRICE = ЦЕНАПОСЛНЕРЕГ ## Возвращает цену за 100 рублей нарицательной стоимости ценных бумаг с нерегулярным последним периодом. +ODDLYIELD = ДОХОДПОСЛНЕРЕГ ## Возвращает доход по ценным бумагам с нерегулярным последним периодом. +PMT = ПЛТ ## Возвращает величину выплаты за один период аннуитета. +PPMT = ОСПЛТ ## Возвращает величину выплат в погашение основной суммы по инвестиции за заданный период. +PRICE = ЦЕНА ## Возвращает цену за 100 рублей нарицательной стоимости ценных бумаг, по которым производится периодическая выплата процентов. +PRICEDISC = ЦЕНАСКИДКА ## Возвращает цену за 100 рублей номинальной стоимости ценных бумаг, на которые сделана скидка. +PRICEMAT = ЦЕНАПОГАШ ## Возвращает цену за 100 рублей номинальной стоимости ценных бумаг, проценты по которым выплачиваются в срок погашения. +PV = ПС ## Возвращает приведенную (к текущему моменту) стоимость инвестиции. +RATE = СТАВКА ## Возвращает процентную ставку по аннуитету за один период. +RECEIVED = ПОЛУЧЕНО ## Возвращает сумму, полученную к сроку погашения полностью обеспеченных ценных бумаг. +SLN = АПЛ ## Возвращает величину линейной амортизации актива за один период. +SYD = АСЧ ## Возвращает величину амортизации актива за данный период, рассчитанную методом суммы годовых чисел. +TBILLEQ = РАВНОКЧЕК ## Возвращает эквивалентный облигации доход по казначейскому чеку. +TBILLPRICE = ЦЕНАКЧЕК ## Возвращает цену за 100 рублей нарицательной стоимости для казначейского чека. +TBILLYIELD = ДОХОДКЧЕК ## Возвращает доход по казначейскому чеку. +VDB = ПУО ## Возвращает величину амортизации актива для указанного или частичного периода при использовании метода сокращающегося баланса. +XIRR = ЧИСТВНДОХ ## Возвращает внутреннюю ставку доходности для графика денежных потоков, которые не обязательно носят периодический характер. +XNPV = ЧИСТНЗ ## Возвращает чистую приведенную стоимость для денежных потоков, которые не обязательно являются периодическими. +YIELD = ДОХОД ## Возвращает доход от ценных бумаг, по которым производятся периодические выплаты процентов. +YIELDDISC = ДОХОДСКИДКА ## Возвращает годовой доход по ценным бумагам, на которые сделана скидка (пример — казначейские чеки). +YIELDMAT = ДОХОДПОГАШ ## Возвращает годовой доход от ценных бумаг, проценты по которым выплачиваются в срок погашения. + + +## +## Information functions Информационные функции +## +CELL = ЯЧЕЙКА ## Возвращает информацию о формате, расположении или содержимом ячейки. +ERROR.TYPE = ТИП.ОШИБКИ ## Возвращает числовой код, соответствующий типу ошибки. +INFO = ИНФОРМ ## Возвращает информацию о текущей операционной среде. +ISBLANK = ЕПУСТО ## Возвращает значение ИСТИНА, если аргумент является ссылкой на пустую ячейку. +ISERR = ЕОШ ## Возвращает значение ИСТИНА, если аргумент ссылается на любое значение ошибки, кроме #Н/Д. +ISERROR = ЕОШИБКА ## Возвращает значение ИСТИНА, если аргумент ссылается на любое значение ошибки. +ISEVEN = ЕЧЁТН ## Возвращает значение ИСТИНА, если значение аргумента является четным числом. +ISLOGICAL = ЕЛОГИЧ ## Возвращает значение ИСТИНА, если аргумент ссылается на логическое значение. +ISNA = ЕНД ## Возвращает значение ИСТИНА, если аргумент ссылается на значение ошибки #Н/Д. +ISNONTEXT = ЕНЕТЕКСТ ## Возвращает значение ИСТИНА, если значение аргумента не является текстом. +ISNUMBER = ЕЧИСЛО ## Возвращает значение ИСТИНА, если аргумент ссылается на число. +ISODD = ЕНЕЧЁТ ## Возвращает значение ИСТИНА, если значение аргумента является нечетным числом. +ISREF = ЕССЫЛКА ## Возвращает значение ИСТИНА, если значение аргумента является ссылкой. +ISTEXT = ЕТЕКСТ ## Возвращает значение ИСТИНА, если значение аргумента является текстом. +N = Ч ## Возвращает значение, преобразованное в число. +NA = НД ## Возвращает значение ошибки #Н/Д. +TYPE = ТИП ## Возвращает число, обозначающее тип данных значения. + + +## +## Logical functions Логические функции +## +AND = И ## Renvoie VRAI si tous ses arguments sont VRAI. +FALSE = ЛОЖЬ ## Возвращает логическое значение ЛОЖЬ. +IF = ЕСЛИ ## Выполняет проверку условия. +IFERROR = ЕСЛИОШИБКА ## Возвращает введённое значение, если вычисление по формуле вызывает ошибку; в противном случае функция возвращает результат вычисления. +NOT = НЕ ## Меняет логическое значение своего аргумента на противоположное. +OR = ИЛИ ## Возвращает значение ИСТИНА, если хотя бы один аргумент имеет значение ИСТИНА. +TRUE = ИСТИНА ## Возвращает логическое значение ИСТИНА. + + +## +## Lookup and reference functions Функции ссылки и поиска +## +ADDRESS = АДРЕС ## Возвращает ссылку на отдельную ячейку листа в виде текста. +AREAS = ОБЛАСТИ ## Возвращает количество областей в ссылке. +CHOOSE = ВЫБОР ## Выбирает значение из списка значений по индексу. +COLUMN = СТОЛБЕЦ ## Возвращает номер столбца, на который указывает ссылка. +COLUMNS = ЧИСЛСТОЛБ ## Возвращает количество столбцов в ссылке. +HLOOKUP = ГПР ## Ищет в первой строке массива и возвращает значение отмеченной ячейки +HYPERLINK = ГИПЕРССЫЛКА ## Создает ссылку, открывающую документ, который находится на сервере сети, в интрасети или в Интернете. +INDEX = ИНДЕКС ## Использует индекс для выбора значения из ссылки или массива. +INDIRECT = ДВССЫЛ ## Возвращает ссылку, заданную текстовым значением. +LOOKUP = ПРОСМОТР ## Ищет значения в векторе или массиве. +MATCH = ПОИСКПОЗ ## Ищет значения в ссылке или массиве. +OFFSET = СМЕЩ ## Возвращает смещение ссылки относительно заданной ссылки. +ROW = СТРОКА ## Возвращает номер строки, определяемой ссылкой. +ROWS = ЧСТРОК ## Возвращает количество строк в ссылке. +RTD = ДРВ ## Извлекает данные реального времени из программ, поддерживающих автоматизацию COM (Программирование объектов. Стандартное средство для работы с объектами некоторого приложения из другого приложения или средства разработки. Программирование объектов (ранее называемое программированием OLE) является функцией модели COM (Component Object Model, модель компонентных объектов).). +TRANSPOSE = ТРАНСП ## Возвращает транспонированный массив. +VLOOKUP = ВПР ## Ищет значение в первом столбце массива и возвращает значение из ячейки в найденной строке и указанном столбце. + + +## +## Math and trigonometry functions Математические и тригонометрические функции +## +ABS = ABS ## Возвращает модуль (абсолютную величину) числа. +ACOS = ACOS ## Возвращает арккосинус числа. +ACOSH = ACOSH ## Возвращает гиперболический арккосинус числа. +ASIN = ASIN ## Возвращает арксинус числа. +ASINH = ASINH ## Возвращает гиперболический арксинус числа. +ATAN = ATAN ## Возвращает арктангенс числа. +ATAN2 = ATAN2 ## Возвращает арктангенс для заданных координат x и y. +ATANH = ATANH ## Возвращает гиперболический арктангенс числа. +CEILING = ОКРВВЕРХ ## Округляет число до ближайшего целого или до ближайшего кратного указанному значению. +COMBIN = ЧИСЛКОМБ ## Возвращает количество комбинаций для заданного числа объектов. +COS = COS ## Возвращает косинус числа. +COSH = COSH ## Возвращает гиперболический косинус числа. +DEGREES = ГРАДУСЫ ## Преобразует радианы в градусы. +EVEN = ЧЁТН ## Округляет число до ближайшего четного целого. +EXP = EXP ## Возвращает число e, возведенное в указанную степень. +FACT = ФАКТР ## Возвращает факториал числа. +FACTDOUBLE = ДВФАКТР ## Возвращает двойной факториал числа. +FLOOR = ОКРВНИЗ ## Округляет число до ближайшего меньшего по модулю значения. +GCD = НОД ## Возвращает наибольший общий делитель. +INT = ЦЕЛОЕ ## Округляет число до ближайшего меньшего целого. +LCM = НОК ## Возвращает наименьшее общее кратное. +LN = LN ## Возвращает натуральный логарифм числа. +LOG = LOG ## Возвращает логарифм числа по заданному основанию. +LOG10 = LOG10 ## Возвращает десятичный логарифм числа. +MDETERM = МОПРЕД ## Возвращает определитель матрицы массива. +MINVERSE = МОБР ## Возвращает обратную матрицу массива. +MMULT = МУМНОЖ ## Возвращает произведение матриц двух массивов. +MOD = ОСТАТ ## Возвращает остаток от деления. +MROUND = ОКРУГЛТ ## Возвращает число, округленное с требуемой точностью. +MULTINOMIAL = МУЛЬТИНОМ ## Возвращает мультиномиальный коэффициент множества чисел. +ODD = НЕЧЁТ ## Округляет число до ближайшего нечетного целого. +PI = ПИ ## Возвращает число пи. +POWER = СТЕПЕНЬ ## Возвращает результат возведения числа в степень. +PRODUCT = ПРОИЗВЕД ## Возвращает произведение аргументов. +QUOTIENT = ЧАСТНОЕ ## Возвращает целую часть частного при делении. +RADIANS = РАДИАНЫ ## Преобразует градусы в радианы. +RAND = СЛЧИС ## Возвращает случайное число в интервале от 0 до 1. +RANDBETWEEN = СЛУЧМЕЖДУ ## Возвращает случайное число в интервале между двумя заданными числами. +ROMAN = РИМСКОЕ ## Преобразует арабские цифры в римские в виде текста. +ROUND = ОКРУГЛ ## Округляет число до указанного количества десятичных разрядов. +ROUNDDOWN = ОКРУГЛВНИЗ ## Округляет число до ближайшего меньшего по модулю значения. +ROUNDUP = ОКРУГЛВВЕРХ ## Округляет число до ближайшего большего по модулю значения. +SERIESSUM = РЯД.СУММ ## Возвращает сумму степенного ряда, вычисленную по формуле. +SIGN = ЗНАК ## Возвращает знак числа. +SIN = SIN ## Возвращает синус заданного угла. +SINH = SINH ## Возвращает гиперболический синус числа. +SQRT = КОРЕНЬ ## Возвращает положительное значение квадратного корня. +SQRTPI = КОРЕНЬПИ ## Возвращает квадратный корень из значения выражения (число * ПИ). +SUBTOTAL = ПРОМЕЖУТОЧНЫЕ.ИТОГИ ## Возвращает промежуточный итог в списке или базе данных. +SUM = СУММ ## Суммирует аргументы. +SUMIF = СУММЕСЛИ ## Суммирует ячейки, удовлетворяющие заданному условию. +SUMIFS = СУММЕСЛИМН ## Суммирует диапазон ячеек, удовлетворяющих нескольким условиям. +SUMPRODUCT = СУММПРОИЗВ ## Возвращает сумму произведений соответствующих элементов массивов. +SUMSQ = СУММКВ ## Возвращает сумму квадратов аргументов. +SUMX2MY2 = СУММРАЗНКВ ## Возвращает сумму разностей квадратов соответствующих значений в двух массивах. +SUMX2PY2 = СУММСУММКВ ## Возвращает сумму сумм квадратов соответствующих элементов двух массивов. +SUMXMY2 = СУММКВРАЗН ## Возвращает сумму квадратов разностей соответствующих значений в двух массивах. +TAN = TAN ## Возвращает тангенс числа. +TANH = TANH ## Возвращает гиперболический тангенс числа. +TRUNC = ОТБР ## Отбрасывает дробную часть числа. + + +## +## Statistical functions Статистические функции +## +AVEDEV = СРОТКЛ ## Возвращает среднее арифметическое абсолютных значений отклонений точек данных от среднего. +AVERAGE = СРЗНАЧ ## Возвращает среднее арифметическое аргументов. +AVERAGEA = СРЗНАЧА ## Возвращает среднее арифметическое аргументов, включая числа, текст и логические значения. +AVERAGEIF = СРЗНАЧЕСЛИ ## Возвращает среднее значение (среднее арифметическое) всех ячеек в диапазоне, которые удовлетворяют данному условию. +AVERAGEIFS = СРЗНАЧЕСЛИМН ## Возвращает среднее значение (среднее арифметическое) всех ячеек, которые удовлетворяют нескольким условиям. +BETADIST = БЕТАРАСП ## Возвращает интегральную функцию бета-распределения. +BETAINV = БЕТАОБР ## Возвращает обратную интегральную функцию указанного бета-распределения. +BINOMDIST = БИНОМРАСП ## Возвращает отдельное значение биномиального распределения. +CHIDIST = ХИ2РАСП ## Возвращает одностороннюю вероятность распределения хи-квадрат. +CHIINV = ХИ2ОБР ## Возвращает обратное значение односторонней вероятности распределения хи-квадрат. +CHITEST = ХИ2ТЕСТ ## Возвращает тест на независимость. +CONFIDENCE = ДОВЕРИТ ## Возвращает доверительный интервал для среднего значения по генеральной совокупности. +CORREL = КОРРЕЛ ## Возвращает коэффициент корреляции между двумя множествами данных. +COUNT = СЧЁТ ## Подсчитывает количество чисел в списке аргументов. +COUNTA = СЧЁТЗ ## Подсчитывает количество значений в списке аргументов. +COUNTBLANK = СЧИТАТЬПУСТОТЫ ## Подсчитывает количество пустых ячеек в диапазоне +COUNTIF = СЧЁТЕСЛИ ## Подсчитывает количество ячеек в диапазоне, удовлетворяющих заданному условию +COUNTIFS = СЧЁТЕСЛИМН ## Подсчитывает количество ячеек внутри диапазона, удовлетворяющих нескольким условиям. +COVAR = КОВАР ## Возвращает ковариацию, среднее произведений парных отклонений +CRITBINOM = КРИТБИНОМ ## Возвращает наименьшее значение, для которого интегральное биномиальное распределение меньше или равно заданному критерию. +DEVSQ = КВАДРОТКЛ ## Возвращает сумму квадратов отклонений. +EXPONDIST = ЭКСПРАСП ## Возвращает экспоненциальное распределение. +FDIST = FРАСП ## Возвращает F-распределение вероятности. +FINV = FРАСПОБР ## Возвращает обратное значение для F-распределения вероятности. +FISHER = ФИШЕР ## Возвращает преобразование Фишера. +FISHERINV = ФИШЕРОБР ## Возвращает обратное преобразование Фишера. +FORECAST = ПРЕДСКАЗ ## Возвращает значение линейного тренда. +FREQUENCY = ЧАСТОТА ## Возвращает распределение частот в виде вертикального массива. +FTEST = ФТЕСТ ## Возвращает результат F-теста. +GAMMADIST = ГАММАРАСП ## Возвращает гамма-распределение. +GAMMAINV = ГАММАОБР ## Возвращает обратное гамма-распределение. +GAMMALN = ГАММАНЛОГ ## Возвращает натуральный логарифм гамма функции, Γ(x). +GEOMEAN = СРГЕОМ ## Возвращает среднее геометрическое. +GROWTH = РОСТ ## Возвращает значения в соответствии с экспоненциальным трендом. +HARMEAN = СРГАРМ ## Возвращает среднее гармоническое. +HYPGEOMDIST = ГИПЕРГЕОМЕТ ## Возвращает гипергеометрическое распределение. +INTERCEPT = ОТРЕЗОК ## Возвращает отрезок, отсекаемый на оси линией линейной регрессии. +KURT = ЭКСЦЕСС ## Возвращает эксцесс множества данных. +LARGE = НАИБОЛЬШИЙ ## Возвращает k-ое наибольшее значение в множестве данных. +LINEST = ЛИНЕЙН ## Возвращает параметры линейного тренда. +LOGEST = ЛГРФПРИБЛ ## Возвращает параметры экспоненциального тренда. +LOGINV = ЛОГНОРМОБР ## Возвращает обратное логарифмическое нормальное распределение. +LOGNORMDIST = ЛОГНОРМРАСП ## Возвращает интегральное логарифмическое нормальное распределение. +MAX = МАКС ## Возвращает наибольшее значение в списке аргументов. +MAXA = МАКСА ## Возвращает наибольшее значение в списке аргументов, включая числа, текст и логические значения. +MEDIAN = МЕДИАНА ## Возвращает медиану заданных чисел. +MIN = МИН ## Возвращает наименьшее значение в списке аргументов. +MINA = МИНА ## Возвращает наименьшее значение в списке аргументов, включая числа, текст и логические значения. +MODE = МОДА ## Возвращает значение моды множества данных. +NEGBINOMDIST = ОТРБИНОМРАСП ## Возвращает отрицательное биномиальное распределение. +NORMDIST = НОРМРАСП ## Возвращает нормальную функцию распределения. +NORMINV = НОРМОБР ## Возвращает обратное нормальное распределение. +NORMSDIST = НОРМСТРАСП ## Возвращает стандартное нормальное интегральное распределение. +NORMSINV = НОРМСТОБР ## Возвращает обратное значение стандартного нормального распределения. +PEARSON = ПИРСОН ## Возвращает коэффициент корреляции Пирсона. +PERCENTILE = ПЕРСЕНТИЛЬ ## Возвращает k-ую персентиль для значений диапазона. +PERCENTRANK = ПРОЦЕНТРАНГ ## Возвращает процентную норму значения в множестве данных. +PERMUT = ПЕРЕСТ ## Возвращает количество перестановок для заданного числа объектов. +POISSON = ПУАССОН ## Возвращает распределение Пуассона. +PROB = ВЕРОЯТНОСТЬ ## Возвращает вероятность того, что значение из диапазона находится внутри заданных пределов. +QUARTILE = КВАРТИЛЬ ## Возвращает квартиль множества данных. +RANK = РАНГ ## Возвращает ранг числа в списке чисел. +RSQ = КВПИРСОН ## Возвращает квадрат коэффициента корреляции Пирсона. +SKEW = СКОС ## Возвращает асимметрию распределения. +SLOPE = НАКЛОН ## Возвращает наклон линии линейной регрессии. +SMALL = НАИМЕНЬШИЙ ## Возвращает k-ое наименьшее значение в множестве данных. +STANDARDIZE = НОРМАЛИЗАЦИЯ ## Возвращает нормализованное значение. +STDEV = СТАНДОТКЛОН ## Оценивает стандартное отклонение по выборке. +STDEVA = СТАНДОТКЛОНА ## Оценивает стандартное отклонение по выборке, включая числа, текст и логические значения. +STDEVP = СТАНДОТКЛОНП ## Вычисляет стандартное отклонение по генеральной совокупности. +STDEVPA = СТАНДОТКЛОНПА ## Вычисляет стандартное отклонение по генеральной совокупности, включая числа, текст и логические значения. +STEYX = СТОШYX ## Возвращает стандартную ошибку предсказанных значений y для каждого значения x в регрессии. +TDIST = СТЬЮДРАСП ## Возвращает t-распределение Стьюдента. +TINV = СТЬЮДРАСПОБР ## Возвращает обратное t-распределение Стьюдента. +TREND = ТЕНДЕНЦИЯ ## Возвращает значения в соответствии с линейным трендом. +TRIMMEAN = УРЕЗСРЕДНЕЕ ## Возвращает среднее внутренности множества данных. +TTEST = ТТЕСТ ## Возвращает вероятность, соответствующую критерию Стьюдента. +VAR = ДИСП ## Оценивает дисперсию по выборке. +VARA = ДИСПА ## Оценивает дисперсию по выборке, включая числа, текст и логические значения. +VARP = ДИСПР ## Вычисляет дисперсию для генеральной совокупности. +VARPA = ДИСПРА ## Вычисляет дисперсию для генеральной совокупности, включая числа, текст и логические значения. +WEIBULL = ВЕЙБУЛЛ ## Возвращает распределение Вейбулла. +ZTEST = ZТЕСТ ## Возвращает двустороннее P-значение z-теста. + + +## +## Text functions Текстовые функции +## +ASC = ASC ## Для языков с двухбайтовыми наборами знаков (например, катакана) преобразует полноширинные (двухбайтовые) знаки в полуширинные (однобайтовые). +BAHTTEXT = БАТТЕКСТ ## Преобразует число в текст, используя денежный формат ß (БАТ). +CHAR = СИМВОЛ ## Возвращает знак с заданным кодом. +CLEAN = ПЕЧСИМВ ## Удаляет все непечатаемые знаки из текста. +CODE = КОДСИМВ ## Возвращает числовой код первого знака в текстовой строке. +CONCATENATE = СЦЕПИТЬ ## Объединяет несколько текстовых элементов в один. +DOLLAR = РУБЛЬ ## Преобразует число в текст, используя денежный формат. +EXACT = СОВПАД ## Проверяет идентичность двух текстовых значений. +FIND = НАЙТИ ## Ищет вхождения одного текстового значения в другом (с учетом регистра). +FINDB = НАЙТИБ ## Ищет вхождения одного текстового значения в другом (с учетом регистра). +FIXED = ФИКСИРОВАННЫЙ ## Форматирует число и преобразует его в текст с заданным числом десятичных знаков. +JIS = JIS ## Для языков с двухбайтовыми наборами знаков (например, катакана) преобразует полуширинные (однобайтовые) знаки в текстовой строке в полноширинные (двухбайтовые). +LEFT = ЛЕВСИМВ ## Возвращает крайние слева знаки текстового значения. +LEFTB = ЛЕВБ ## Возвращает крайние слева знаки текстового значения. +LEN = ДЛСТР ## Возвращает количество знаков в текстовой строке. +LENB = ДЛИНБ ## Возвращает количество знаков в текстовой строке. +LOWER = СТРОЧН ## Преобразует все буквы текста в строчные. +MID = ПСТР ## Возвращает заданное число знаков из строки текста, начиная с указанной позиции. +MIDB = ПСТРБ ## Возвращает заданное число знаков из строки текста, начиная с указанной позиции. +PHONETIC = PHONETIC ## Извлекает фонетические (фуригана) знаки из текстовой строки. +PROPER = ПРОПНАЧ ## Преобразует первую букву в каждом слове текста в прописную. +REPLACE = ЗАМЕНИТЬ ## Заменяет знаки в тексте. +REPLACEB = ЗАМЕНИТЬБ ## Заменяет знаки в тексте. +REPT = ПОВТОР ## Повторяет текст заданное число раз. +RIGHT = ПРАВСИМВ ## Возвращает крайние справа знаки текстовой строки. +RIGHTB = ПРАВБ ## Возвращает крайние справа знаки текстовой строки. +SEARCH = ПОИСК ## Ищет вхождения одного текстового значения в другом (без учета регистра). +SEARCHB = ПОИСКБ ## Ищет вхождения одного текстового значения в другом (без учета регистра). +SUBSTITUTE = ПОДСТАВИТЬ ## Заменяет в текстовой строке старый текст новым. +T = Т ## Преобразует аргументы в текст. +TEXT = ТЕКСТ ## Форматирует число и преобразует его в текст. +TRIM = СЖПРОБЕЛЫ ## Удаляет из текста пробелы. +UPPER = ПРОПИСН ## Преобразует все буквы текста в прописные. +VALUE = ЗНАЧЕН ## Преобразует текстовый аргумент в число. diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/cs/config b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/cs/config new file mode 100644 index 0000000..49f40fc --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/cs/config @@ -0,0 +1,20 @@ +############################################################ +## +## PhpSpreadsheet - locale settings +## +## Ceština (Czech) +## +############################################################ + +ArgumentSeparator = ; + +## +## Error Codes +## +NULL +DIV0 = #DĚLENÍ_NULOU! +VALUE = #HODNOTA! +REF = #ODKAZ! +NAME = #NÁZEV? +NUM = #ČÍSLO! +NA = #NENÍ_K_DISPOZICI diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/cs/functions b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/cs/functions new file mode 100644 index 0000000..49c4945 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/cs/functions @@ -0,0 +1,520 @@ +############################################################ +## +## PhpSpreadsheet - function name translations +## +## Ceština (Czech) +## +############################################################ + + +## +## Funkce pro práci s datovými krychlemi (Cube Functions) +## +CUBEKPIMEMBER = CUBEKPIMEMBER +CUBEMEMBER = CUBEMEMBER +CUBEMEMBERPROPERTY = CUBEMEMBERPROPERTY +CUBERANKEDMEMBER = CUBERANKEDMEMBER +CUBESET = CUBESET +CUBESETCOUNT = CUBESETCOUNT +CUBEVALUE = CUBEVALUE + +## +## Funkce databáze (Database Functions) +## +DAVERAGE = DPRŮMĚR +DCOUNT = DPOČET +DCOUNTA = DPOČET2 +DGET = DZÍSKAT +DMAX = DMAX +DMIN = DMIN +DPRODUCT = DSOUČIN +DSTDEV = DSMODCH.VÝBĚR +DSTDEVP = DSMODCH +DSUM = DSUMA +DVAR = DVAR.VÝBĚR +DVARP = DVAR + +## +## Funkce data a času (Date & Time Functions) +## +DATE = DATUM +DATEVALUE = DATUMHODN +DAY = DEN +DAYS = DAYS +DAYS360 = ROK360 +EDATE = EDATE +EOMONTH = EOMONTH +HOUR = HODINA +ISOWEEKNUM = ISOWEEKNUM +MINUTE = MINUTA +MONTH = MĚSÍC +NETWORKDAYS = NETWORKDAYS +NETWORKDAYS.INTL = NETWORKDAYS.INTL +NOW = NYNÍ +SECOND = SEKUNDA +TIME = ČAS +TIMEVALUE = ČASHODN +TODAY = DNES +WEEKDAY = DENTÝDNE +WEEKNUM = WEEKNUM +WORKDAY = WORKDAY +WORKDAY.INTL = WORKDAY.INTL +YEAR = ROK +YEARFRAC = YEARFRAC + +## +## Inženýrské funkce (Engineering Functions) +## +BESSELI = BESSELI +BESSELJ = BESSELJ +BESSELK = BESSELK +BESSELY = BESSELY +BIN2DEC = BIN2DEC +BIN2HEX = BIN2HEX +BIN2OCT = BIN2OCT +BITAND = BITAND +BITLSHIFT = BITLSHIFT +BITOR = BITOR +BITRSHIFT = BITRSHIFT +BITXOR = BITXOR +COMPLEX = COMPLEX +CONVERT = CONVERT +DEC2BIN = DEC2BIN +DEC2HEX = DEC2HEX +DEC2OCT = DEC2OCT +DELTA = DELTA +ERF = ERF +ERF.PRECISE = ERF.PRECISE +ERFC = ERFC +ERFC.PRECISE = ERFC.PRECISE +GESTEP = GESTEP +HEX2BIN = HEX2BIN +HEX2DEC = HEX2DEC +HEX2OCT = HEX2OCT +IMABS = IMABS +IMAGINARY = IMAGINARY +IMARGUMENT = IMARGUMENT +IMCONJUGATE = IMCONJUGATE +IMCOS = IMCOS +IMCOSH = IMCOSH +IMCOT = IMCOT +IMCSC = IMCSC +IMCSCH = IMCSCH +IMDIV = IMDIV +IMEXP = IMEXP +IMLN = IMLN +IMLOG10 = IMLOG10 +IMLOG2 = IMLOG2 +IMPOWER = IMPOWER +IMPRODUCT = IMPRODUCT +IMREAL = IMREAL +IMSEC = IMSEC +IMSECH = IMSECH +IMSIN = IMSIN +IMSINH = IMSINH +IMSQRT = IMSQRT +IMSUB = IMSUB +IMSUM = IMSUM +IMTAN = IMTAN +OCT2BIN = OCT2BIN +OCT2DEC = OCT2DEC +OCT2HEX = OCT2HEX + +## +## Finanční funkce (Financial Functions) +## +ACCRINT = ACCRINT +ACCRINTM = ACCRINTM +AMORDEGRC = AMORDEGRC +AMORLINC = AMORLINC +COUPDAYBS = COUPDAYBS +COUPDAYS = COUPDAYS +COUPDAYSNC = COUPDAYSNC +COUPNCD = COUPNCD +COUPNUM = COUPNUM +COUPPCD = COUPPCD +CUMIPMT = CUMIPMT +CUMPRINC = CUMPRINC +DB = ODPIS.ZRYCH +DDB = ODPIS.ZRYCH2 +DISC = DISC +DOLLARDE = DOLLARDE +DOLLARFR = DOLLARFR +DURATION = DURATION +EFFECT = EFFECT +FV = BUDHODNOTA +FVSCHEDULE = FVSCHEDULE +INTRATE = INTRATE +IPMT = PLATBA.ÚROK +IRR = MÍRA.VÝNOSNOSTI +ISPMT = ISPMT +MDURATION = MDURATION +MIRR = MOD.MÍRA.VÝNOSNOSTI +NOMINAL = NOMINAL +NPER = POČET.OBDOBÍ +NPV = ČISTÁ.SOUČHODNOTA +ODDFPRICE = ODDFPRICE +ODDFYIELD = ODDFYIELD +ODDLPRICE = ODDLPRICE +ODDLYIELD = ODDLYIELD +PDURATION = PDURATION +PMT = PLATBA +PPMT = PLATBA.ZÁKLAD +PRICE = PRICE +PRICEDISC = PRICEDISC +PRICEMAT = PRICEMAT +PV = SOUČHODNOTA +RATE = ÚROKOVÁ.MÍRA +RECEIVED = RECEIVED +RRI = RRI +SLN = ODPIS.LIN +SYD = ODPIS.NELIN +TBILLEQ = TBILLEQ +TBILLPRICE = TBILLPRICE +TBILLYIELD = TBILLYIELD +VDB = ODPIS.ZA.INT +XIRR = XIRR +XNPV = XNPV +YIELD = YIELD +YIELDDISC = YIELDDISC +YIELDMAT = YIELDMAT + +## +## Informační funkce (Information Functions) +## +CELL = POLÍČKO +ERROR.TYPE = CHYBA.TYP +INFO = O.PROSTŘEDÍ +ISBLANK = JE.PRÁZDNÉ +ISERR = JE.CHYBA +ISERROR = JE.CHYBHODN +ISEVEN = ISEVEN +ISFORMULA = ISFORMULA +ISLOGICAL = JE.LOGHODN +ISNA = JE.NEDEF +ISNONTEXT = JE.NETEXT +ISNUMBER = JE.ČISLO +ISODD = ISODD +ISREF = JE.ODKAZ +ISTEXT = JE.TEXT +N = N +NA = NEDEF +SHEET = SHEET +SHEETS = SHEETS +TYPE = TYP + +## +## Logické funkce (Logical Functions) +## +AND = A +FALSE = NEPRAVDA +IF = KDYŽ +IFERROR = IFERROR +IFNA = IFNA +IFS = IFS +NOT = NE +OR = NEBO +SWITCH = SWITCH +TRUE = PRAVDA +XOR = XOR + +## +## Vyhledávací funkce a funkce pro odkazy (Lookup & Reference Functions) +## +ADDRESS = ODKAZ +AREAS = POČET.BLOKŮ +CHOOSE = ZVOLIT +COLUMN = SLOUPEC +COLUMNS = SLOUPCE +FORMULATEXT = FORMULATEXT +GETPIVOTDATA = ZÍSKATKONTDATA +HLOOKUP = VVYHLEDAT +HYPERLINK = HYPERTEXTOVÝ.ODKAZ +INDEX = INDEX +INDIRECT = NEPŘÍMÝ.ODKAZ +LOOKUP = VYHLEDAT +MATCH = POZVYHLEDAT +OFFSET = POSUN +ROW = ŘÁDEK +ROWS = ŘÁDKY +RTD = RTD +TRANSPOSE = TRANSPOZICE +VLOOKUP = SVYHLEDAT + +## +## Matematické a trigonometrické funkce (Math & Trig Functions) +## +ABS = ABS +ACOS = ARCCOS +ACOSH = ARCCOSH +ACOT = ACOT +ACOTH = ACOTH +AGGREGATE = AGGREGATE +ARABIC = ARABIC +ASIN = ARCSIN +ASINH = ARCSINH +ATAN = ARCTG +ATAN2 = ARCTG2 +ATANH = ARCTGH +BASE = BASE +CEILING.MATH = CEILING.MATH +COMBIN = KOMBINACE +COMBINA = COMBINA +COS = COS +COSH = COSH +COT = COT +COTH = COTH +CSC = CSC +CSCH = CSCH +DECIMAL = DECIMAL +DEGREES = DEGREES +EVEN = ZAOKROUHLIT.NA.SUDÉ +EXP = EXP +FACT = FAKTORIÁL +FACTDOUBLE = FACTDOUBLE +FLOOR.MATH = FLOOR.MATH +GCD = GCD +INT = CELÁ.ČÁST +LCM = LCM +LN = LN +LOG = LOGZ +LOG10 = LOG +MDETERM = DETERMINANT +MINVERSE = INVERZE +MMULT = SOUČIN.MATIC +MOD = MOD +MROUND = MROUND +MULTINOMIAL = MULTINOMIAL +MUNIT = MUNIT +ODD = ZAOKROUHLIT.NA.LICHÉ +PI = PI +POWER = POWER +PRODUCT = SOUČIN +QUOTIENT = QUOTIENT +RADIANS = RADIANS +RAND = NÁHČÍSLO +RANDBETWEEN = RANDBETWEEN +ROMAN = ROMAN +ROUND = ZAOKROUHLIT +ROUNDDOWN = ROUNDDOWN +ROUNDUP = ROUNDUP +SEC = SEC +SECH = SECH +SERIESSUM = SERIESSUM +SIGN = SIGN +SIN = SIN +SINH = SINH +SQRT = ODMOCNINA +SQRTPI = SQRTPI +SUBTOTAL = SUBTOTAL +SUM = SUMA +SUMIF = SUMIF +SUMIFS = SUMIFS +SUMPRODUCT = SOUČIN.SKALÁRNÍ +SUMSQ = SUMA.ČTVERCŮ +SUMX2MY2 = SUMX2MY2 +SUMX2PY2 = SUMX2PY2 +SUMXMY2 = SUMXMY2 +TAN = TG +TANH = TGH +TRUNC = USEKNOUT + +## +## Statistické funkce (Statistical Functions) +## +AVEDEV = PRŮMODCHYLKA +AVERAGE = PRŮMĚR +AVERAGEA = AVERAGEA +AVERAGEIF = AVERAGEIF +AVERAGEIFS = AVERAGEIFS +BETA.DIST = BETA.DIST +BETA.INV = BETA.INV +BINOM.DIST = BINOM.DIST +BINOM.DIST.RANGE = BINOM.DIST.RANGE +BINOM.INV = BINOM.INV +CHISQ.DIST = CHISQ.DIST +CHISQ.DIST.RT = CHISQ.DIST.RT +CHISQ.INV = CHISQ.INV +CHISQ.INV.RT = CHISQ.INV.RT +CHISQ.TEST = CHISQ.TEST +CONFIDENCE.NORM = CONFIDENCE.NORM +CONFIDENCE.T = CONFIDENCE.T +CORREL = CORREL +COUNT = POČET +COUNTA = POČET2 +COUNTBLANK = COUNTBLANK +COUNTIF = COUNTIF +COUNTIFS = COUNTIFS +COVARIANCE.P = COVARIANCE.P +COVARIANCE.S = COVARIANCE.S +DEVSQ = DEVSQ +EXPON.DIST = EXPON.DIST +F.DIST = F.DIST +F.DIST.RT = F.DIST.RT +F.INV = F.INV +F.INV.RT = F.INV.RT +F.TEST = F.TEST +FISHER = FISHER +FISHERINV = FISHERINV +FORECAST.ETS = FORECAST.ETS +FORECAST.ETS.CONFINT = FORECAST.ETS.CONFINT +FORECAST.ETS.SEASONALITY = FORECAST.ETS.SEASONALITY +FORECAST.ETS.STAT = FORECAST.ETS.STAT +FORECAST.LINEAR = FORECAST.LINEAR +FREQUENCY = ČETNOSTI +GAMMA = GAMMA +GAMMA.DIST = GAMMA.DIST +GAMMA.INV = GAMMA.INV +GAMMALN = GAMMALN +GAMMALN.PRECISE = GAMMALN.PRECISE +GAUSS = GAUSS +GEOMEAN = GEOMEAN +GROWTH = LOGLINTREND +HARMEAN = HARMEAN +HYPGEOM.DIST = HYPGEOM.DIST +INTERCEPT = INTERCEPT +KURT = KURT +LARGE = LARGE +LINEST = LINREGRESE +LOGEST = LOGLINREGRESE +LOGNORM.DIST = LOGNORM.DIST +LOGNORM.INV = LOGNORM.INV +MAX = MAX +MAXA = MAXA +MAXIFS = MAXIFS +MEDIAN = MEDIAN +MIN = MIN +MINA = MINA +MINIFS = MINIFS +MODE.MULT = MODE.MULT +MODE.SNGL = MODE.SNGL +NEGBINOM.DIST = NEGBINOM.DIST +NORM.DIST = NORM.DIST +NORM.INV = NORM.INV +NORM.S.DIST = NORM.S.DIST +NORM.S.INV = NORM.S.INV +PEARSON = PEARSON +PERCENTILE.EXC = PERCENTIL.EXC +PERCENTILE.INC = PERCENTIL.INC +PERCENTRANK.EXC = PERCENTRANK.EXC +PERCENTRANK.INC = PERCENTRANK.INC +PERMUT = PERMUTACE +PERMUTATIONA = PERMUTATIONA +PHI = PHI +POISSON.DIST = POISSON.DIST +PROB = PROB +QUARTILE.EXC = QUARTIL.EXC +QUARTILE.INC = QUARTIL.INC +RANK.AVG = RANK.AVG +RANK.EQ = RANK.EQ +RSQ = RKQ +SKEW = SKEW +SKEW.P = SKEW.P +SLOPE = SLOPE +SMALL = SMALL +STANDARDIZE = STANDARDIZE +STDEV.P = SMODCH.P +STDEV.S = SMODCH.VÝBĚR.S +STDEVA = STDEVA +STDEVPA = STDEVPA +STEYX = STEYX +T.DIST = T.DIST +T.DIST.2T = T.DIST.2T +T.DIST.RT = T.DIST.RT +T.INV = T.INV +T.INV.2T = T.INV.2T +T.TEST = T.TEST +TREND = LINTREND +TRIMMEAN = TRIMMEAN +VAR.P = VAR.P +VAR.S = VAR.S +VARA = VARA +VARPA = VARPA +WEIBULL.DIST = WEIBULL.DIST +Z.TEST = Z.TEST + +## +## Textové funkce (Text Functions) +## +BAHTTEXT = BAHTTEXT +CHAR = ZNAK +CLEAN = VYČISTIT +CODE = KÓD +CONCAT = CONCAT +DOLLAR = KČ +EXACT = STEJNÉ +FIND = NAJÍT +FIXED = ZAOKROUHLIT.NA.TEXT +LEFT = ZLEVA +LEN = DÉLKA +LOWER = MALÁ +MID = ČÁST +NUMBERVALUE = NUMBERVALUE +PHONETIC = ZVUKOVÉ +PROPER = VELKÁ2 +REPLACE = NAHRADIT +REPT = OPAKOVAT +RIGHT = ZPRAVA +SEARCH = HLEDAT +SUBSTITUTE = DOSADIT +T = T +TEXT = HODNOTA.NA.TEXT +TEXTJOIN = TEXTJOIN +TRIM = PROČISTIT +UNICHAR = UNICHAR +UNICODE = UNICODE +UPPER = VELKÁ +VALUE = HODNOTA + +## +## Webové funkce (Web Functions) +## +ENCODEURL = ENCODEURL +FILTERXML = FILTERXML +WEBSERVICE = WEBSERVICE + +## +## Funkce pro kompatibilitu (Compatibility Functions) +## +BETADIST = BETADIST +BETAINV = BETAINV +BINOMDIST = BINOMDIST +CEILING = ZAOKR.NAHORU +CHIDIST = CHIDIST +CHIINV = CHIINV +CHITEST = CHITEST +CONCATENATE = CONCATENATE +CONFIDENCE = CONFIDENCE +COVAR = COVAR +CRITBINOM = CRITBINOM +EXPONDIST = EXPONDIST +FDIST = FDIST +FINV = FINV +FLOOR = ZAOKR.DOLŮ +FORECAST = FORECAST +FTEST = FTEST +GAMMADIST = GAMMADIST +GAMMAINV = GAMMAINV +HYPGEOMDIST = HYPGEOMDIST +LOGINV = LOGINV +LOGNORMDIST = LOGNORMDIST +MODE = MODE +NEGBINOMDIST = NEGBINOMDIST +NORMDIST = NORMDIST +NORMINV = NORMINV +NORMSDIST = NORMSDIST +NORMSINV = NORMSINV +PERCENTILE = PERCENTIL +PERCENTRANK = PERCENTRANK +POISSON = POISSON +QUARTILE = QUARTIL +RANK = RANK +STDEV = SMODCH.VÝBĚR +STDEVP = SMODCH +TDIST = TDIST +TINV = TINV +TTEST = TTEST +VAR = VAR.VÝBĚR +VARP = VAR +WEIBULL = WEIBULL +ZTEST = ZTEST diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/da/config b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/da/config new file mode 100644 index 0000000..284b249 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/da/config @@ -0,0 +1,20 @@ +############################################################ +## +## PhpSpreadsheet - locale settings +## +## Dansk (Danish) +## +############################################################ + +ArgumentSeparator = ; + +## +## Error Codes +## +NULL = #NUL! +DIV0 +VALUE = #VÆRDI! +REF = #REFERENCE! +NAME = #NAVN? +NUM +NA = #I/T diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/da/functions b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/da/functions new file mode 100644 index 0000000..6260760 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/da/functions @@ -0,0 +1,537 @@ +############################################################ +## +## PhpSpreadsheet - function name translations +## +## Dansk (Danish) +## +############################################################ + + +## +## Kubefunktioner (Cube Functions) +## +CUBEKPIMEMBER = KUBE.KPI.MEDLEM +CUBEMEMBER = KUBEMEDLEM +CUBEMEMBERPROPERTY = KUBEMEDLEM.EGENSKAB +CUBERANKEDMEMBER = KUBERANGERET.MEDLEM +CUBESET = KUBESÆT +CUBESETCOUNT = KUBESÆT.ANTAL +CUBEVALUE = KUBEVÆRDI + +## +## Databasefunktioner (Database Functions) +## +DAVERAGE = DMIDDEL +DCOUNT = DTÆL +DCOUNTA = DTÆLV +DGET = DHENT +DMAX = DMAKS +DMIN = DMIN +DPRODUCT = DPRODUKT +DSTDEV = DSTDAFV +DSTDEVP = DSTDAFVP +DSUM = DSUM +DVAR = DVARIANS +DVARP = DVARIANSP + +## +## Dato- og klokkeslætfunktioner (Date & Time Functions) +## +DATE = DATO +DATEDIF = DATO.FORSKEL +DATESTRING = DATOSTRENG +DATEVALUE = DATOVÆRDI +DAY = DAG +DAYS = DAGE +DAYS360 = DAGE360 +EDATE = EDATO +EOMONTH = SLUT.PÅ.MÅNED +HOUR = TIME +ISOWEEKNUM = ISOUGE.NR +MINUTE = MINUT +MONTH = MÅNED +NETWORKDAYS = ANTAL.ARBEJDSDAGE +NETWORKDAYS.INTL = ANTAL.ARBEJDSDAGE.INTL +NOW = NU +SECOND = SEKUND +THAIDAYOFWEEK = THAILANDSKUGEDAG +THAIMONTHOFYEAR = THAILANDSKMÅNED +THAIYEAR = THAILANDSKÅR +TIME = TID +TIMEVALUE = TIDSVÆRDI +TODAY = IDAG +WEEKDAY = UGEDAG +WEEKNUM = UGE.NR +WORKDAY = ARBEJDSDAG +WORKDAY.INTL = ARBEJDSDAG.INTL +YEAR = ÅR +YEARFRAC = ÅR.BRØK + +## +## Tekniske funktioner (Engineering Functions) +## +BESSELI = BESSELI +BESSELJ = BESSELJ +BESSELK = BESSELK +BESSELY = BESSELY +BIN2DEC = BIN.TIL.DEC +BIN2HEX = BIN.TIL.HEX +BIN2OCT = BIN.TIL.OKT +BITAND = BITOG +BITLSHIFT = BITLSKIFT +BITOR = BITELLER +BITRSHIFT = BITRSKIFT +BITXOR = BITXELLER +COMPLEX = KOMPLEKS +CONVERT = KONVERTER +DEC2BIN = DEC.TIL.BIN +DEC2HEX = DEC.TIL.HEX +DEC2OCT = DEC.TIL.OKT +DELTA = DELTA +ERF = FEJLFUNK +ERF.PRECISE = ERF.PRECISE +ERFC = FEJLFUNK.KOMP +ERFC.PRECISE = ERFC.PRECISE +GESTEP = GETRIN +HEX2BIN = HEX.TIL.BIN +HEX2DEC = HEX.TIL.DEC +HEX2OCT = HEX.TIL.OKT +IMABS = IMAGABS +IMAGINARY = IMAGINÆR +IMARGUMENT = IMAGARGUMENT +IMCONJUGATE = IMAGKONJUGERE +IMCOS = IMAGCOS +IMCOSH = IMAGCOSH +IMCOT = IMAGCOT +IMCSC = IMAGCSC +IMCSCH = IMAGCSCH +IMDIV = IMAGDIV +IMEXP = IMAGEKSP +IMLN = IMAGLN +IMLOG10 = IMAGLOG10 +IMLOG2 = IMAGLOG2 +IMPOWER = IMAGPOTENS +IMPRODUCT = IMAGPRODUKT +IMREAL = IMAGREELT +IMSEC = IMAGSEC +IMSECH = IMAGSECH +IMSIN = IMAGSIN +IMSINH = IMAGSINH +IMSQRT = IMAGKVROD +IMSUB = IMAGSUB +IMSUM = IMAGSUM +IMTAN = IMAGTAN +OCT2BIN = OKT.TIL.BIN +OCT2DEC = OKT.TIL.DEC +OCT2HEX = OKT.TIL.HEX + +## +## Finansielle funktioner (Financial Functions) +## +ACCRINT = PÅLØBRENTE +ACCRINTM = PÅLØBRENTE.UDLØB +AMORDEGRC = AMORDEGRC +AMORLINC = AMORLINC +COUPDAYBS = KUPONDAGE.SA +COUPDAYS = KUPONDAGE.A +COUPDAYSNC = KUPONDAGE.ANK +COUPNCD = KUPONDAG.NÆSTE +COUPNUM = KUPONBETALINGER +COUPPCD = KUPONDAG.FORRIGE +CUMIPMT = AKKUM.RENTE +CUMPRINC = AKKUM.HOVEDSTOL +DB = DB +DDB = DSA +DISC = DISKONTO +DOLLARDE = KR.DECIMAL +DOLLARFR = KR.BRØK +DURATION = VARIGHED +EFFECT = EFFEKTIV.RENTE +FV = FV +FVSCHEDULE = FVTABEL +INTRATE = RENTEFOD +IPMT = R.YDELSE +IRR = IA +ISPMT = ISPMT +MDURATION = MVARIGHED +MIRR = MIA +NOMINAL = NOMINEL +NPER = NPER +NPV = NUTIDSVÆRDI +ODDFPRICE = ULIGE.KURS.PÅLYDENDE +ODDFYIELD = ULIGE.FØRSTE.AFKAST +ODDLPRICE = ULIGE.SIDSTE.KURS +ODDLYIELD = ULIGE.SIDSTE.AFKAST +PDURATION = PVARIGHED +PMT = YDELSE +PPMT = H.YDELSE +PRICE = KURS +PRICEDISC = KURS.DISKONTO +PRICEMAT = KURS.UDLØB +PV = NV +RATE = RENTE +RECEIVED = MODTAGET.VED.UDLØB +RRI = RRI +SLN = LA +SYD = ÅRSAFSKRIVNING +TBILLEQ = STATSOBLIGATION +TBILLPRICE = STATSOBLIGATION.KURS +TBILLYIELD = STATSOBLIGATION.AFKAST +VDB = VSA +XIRR = INTERN.RENTE +XNPV = NETTO.NUTIDSVÆRDI +YIELD = AFKAST +YIELDDISC = AFKAST.DISKONTO +YIELDMAT = AFKAST.UDLØBSDATO + +## +## Informationsfunktioner (Information Functions) +## +CELL = CELLE +ERROR.TYPE = FEJLTYPE +INFO = INFO +ISBLANK = ER.TOM +ISERR = ER.FJL +ISERROR = ER.FEJL +ISEVEN = ER.LIGE +ISFORMULA = ER.FORMEL +ISLOGICAL = ER.LOGISK +ISNA = ER.IKKE.TILGÆNGELIG +ISNONTEXT = ER.IKKE.TEKST +ISNUMBER = ER.TAL +ISODD = ER.ULIGE +ISREF = ER.REFERENCE +ISTEXT = ER.TEKST +N = TAL +NA = IKKE.TILGÆNGELIG +SHEET = ARK +SHEETS = ARK.FLERE +TYPE = VÆRDITYPE + +## +## Logiske funktioner (Logical Functions) +## +AND = OG +FALSE = FALSK +IF = HVIS +IFERROR = HVIS.FEJL +IFNA = HVISIT +IFS = HVISER +NOT = IKKE +OR = ELLER +SWITCH = SKIFT +TRUE = SAND +XOR = XELLER + +## +## Opslags- og referencefunktioner (Lookup & Reference Functions) +## +ADDRESS = ADRESSE +AREAS = OMRÅDER +CHOOSE = VÆLG +COLUMN = KOLONNE +COLUMNS = KOLONNER +FORMULATEXT = FORMELTEKST +GETPIVOTDATA = GETPIVOTDATA +HLOOKUP = VOPSLAG +HYPERLINK = HYPERLINK +INDEX = INDEKS +INDIRECT = INDIREKTE +LOOKUP = SLÅ.OP +MATCH = SAMMENLIGN +OFFSET = FORSKYDNING +ROW = RÆKKE +ROWS = RÆKKER +RTD = RTD +TRANSPOSE = TRANSPONER +VLOOKUP = LOPSLAG + +## +## Matematiske og trigonometriske funktioner (Math & Trig Functions) +## +ABS = ABS +ACOS = ARCCOS +ACOSH = ARCCOSH +ACOT = ARCCOT +ACOTH = ARCCOTH +AGGREGATE = SAMLING +ARABIC = ARABISK +ASIN = ARCSIN +ASINH = ARCSINH +ATAN = ARCTAN +ATAN2 = ARCTAN2 +ATANH = ARCTANH +BASE = BASIS +CEILING.MATH = LOFT.MAT +CEILING.PRECISE = LOFT.PRECISE +COMBIN = KOMBIN +COMBINA = KOMBINA +COS = COS +COSH = COSH +COT = COT +COTH = COTH +CSC = CSC +CSCH = CSCH +DECIMAL = DECIMAL +DEGREES = GRADER +ECMA.CEILING = ECMA.LOFT +EVEN = LIGE +EXP = EKSP +FACT = FAKULTET +FACTDOUBLE = DOBBELT.FAKULTET +FLOOR.MATH = AFRUND.BUND.MAT +FLOOR.PRECISE = AFRUND.GULV.PRECISE +GCD = STØRSTE.FÆLLES.DIVISOR +INT = HELTAL +ISO.CEILING = ISO.LOFT +LCM = MINDSTE.FÆLLES.MULTIPLUM +LN = LN +LOG = LOG +LOG10 = LOG10 +MDETERM = MDETERM +MINVERSE = MINVERT +MMULT = MPRODUKT +MOD = REST +MROUND = MAFRUND +MULTINOMIAL = MULTINOMIAL +MUNIT = MENHED +ODD = ULIGE +PI = PI +POWER = POTENS +PRODUCT = PRODUKT +QUOTIENT = KVOTIENT +RADIANS = RADIANER +RAND = SLUMP +RANDBETWEEN = SLUMPMELLEM +ROMAN = ROMERTAL +ROUND = AFRUND +ROUNDBAHTDOWN = RUNDBAHTNED +ROUNDBAHTUP = RUNDBAHTOP +ROUNDDOWN = RUND.NED +ROUNDUP = RUND.OP +SEC = SEC +SECH = SECH +SERIESSUM = SERIESUM +SIGN = FORTEGN +SIN = SIN +SINH = SINH +SQRT = KVROD +SQRTPI = KVRODPI +SUBTOTAL = SUBTOTAL +SUM = SUM +SUMIF = SUM.HVIS +SUMIFS = SUM.HVISER +SUMPRODUCT = SUMPRODUKT +SUMSQ = SUMKV +SUMX2MY2 = SUMX2MY2 +SUMX2PY2 = SUMX2PY2 +SUMXMY2 = SUMXMY2 +TAN = TAN +TANH = TANH +TRUNC = AFKORT + +## +## Statistiske funktioner (Statistical Functions) +## +AVEDEV = MAD +AVERAGE = MIDDEL +AVERAGEA = MIDDELV +AVERAGEIF = MIDDEL.HVIS +AVERAGEIFS = MIDDEL.HVISER +BETA.DIST = BETA.FORDELING +BETA.INV = BETA.INV +BINOM.DIST = BINOMIAL.FORDELING +BINOM.DIST.RANGE = BINOMIAL.DIST.INTERVAL +BINOM.INV = BINOMIAL.INV +CHISQ.DIST = CHI2.FORDELING +CHISQ.DIST.RT = CHI2.FORD.RT +CHISQ.INV = CHI2.INV +CHISQ.INV.RT = CHI2.INV.RT +CHISQ.TEST = CHI2.TEST +CONFIDENCE.NORM = KONFIDENS.NORM +CONFIDENCE.T = KONFIDENST +CORREL = KORRELATION +COUNT = TÆL +COUNTA = TÆLV +COUNTBLANK = ANTAL.BLANKE +COUNTIF = TÆL.HVIS +COUNTIFS = TÆL.HVISER +COVARIANCE.P = KOVARIANS.P +COVARIANCE.S = KOVARIANS.S +DEVSQ = SAK +EXPON.DIST = EKSP.FORDELING +F.DIST = F.FORDELING +F.DIST.RT = F.FORDELING.RT +F.INV = F.INV +F.INV.RT = F.INV.RT +F.TEST = F.TEST +FISHER = FISHER +FISHERINV = FISHERINV +FORECAST.ETS = PROGNOSE.ETS +FORECAST.ETS.CONFINT = PROGNOSE.ETS.CONFINT +FORECAST.ETS.SEASONALITY = PROGNOSE.ETS.SÆSONUDSVING +FORECAST.ETS.STAT = PROGNOSE.ETS.STAT +FORECAST.LINEAR = PROGNOSE.LINEÆR +FREQUENCY = FREKVENS +GAMMA = GAMMA +GAMMA.DIST = GAMMA.FORDELING +GAMMA.INV = GAMMA.INV +GAMMALN = GAMMALN +GAMMALN.PRECISE = GAMMALN.PRECISE +GAUSS = GAUSS +GEOMEAN = GEOMIDDELVÆRDI +GROWTH = FORØGELSE +HARMEAN = HARMIDDELVÆRDI +HYPGEOM.DIST = HYPGEO.FORDELING +INTERCEPT = SKÆRING +KURT = TOPSTEJL +LARGE = STØRSTE +LINEST = LINREGR +LOGEST = LOGREGR +LOGNORM.DIST = LOGNORM.FORDELING +LOGNORM.INV = LOGNORM.INV +MAX = MAKS +MAXA = MAKSV +MAXIFS = MAKSHVISER +MEDIAN = MEDIAN +MIN = MIN +MINA = MINV +MINIFS = MINHVISER +MODE.MULT = HYPPIGST.FLERE +MODE.SNGL = HYPPIGST.ENKELT +NEGBINOM.DIST = NEGBINOM.FORDELING +NORM.DIST = NORMAL.FORDELING +NORM.INV = NORM.INV +NORM.S.DIST = STANDARD.NORM.FORDELING +NORM.S.INV = STANDARD.NORM.INV +PEARSON = PEARSON +PERCENTILE.EXC = FRAKTIL.UDELAD +PERCENTILE.INC = FRAKTIL.MEDTAG +PERCENTRANK.EXC = PROCENTPLADS.UDELAD +PERCENTRANK.INC = PROCENTPLADS.MEDTAG +PERMUT = PERMUT +PERMUTATIONA = PERMUTATIONA +PHI = PHI +POISSON.DIST = POISSON.FORDELING +PROB = SANDSYNLIGHED +QUARTILE.EXC = KVARTIL.UDELAD +QUARTILE.INC = KVARTIL.MEDTAG +RANK.AVG = PLADS.GNSN +RANK.EQ = PLADS.LIGE +RSQ = FORKLARINGSGRAD +SKEW = SKÆVHED +SKEW.P = SKÆVHED.P +SLOPE = STIGNING +SMALL = MINDSTE +STANDARDIZE = STANDARDISER +STDEV.P = STDAFV.P +STDEV.S = STDAFV.S +STDEVA = STDAFVV +STDEVPA = STDAFVPV +STEYX = STFYX +T.DIST = T.FORDELING +T.DIST.2T = T.FORDELING.2T +T.DIST.RT = T.FORDELING.RT +T.INV = T.INV +T.INV.2T = T.INV.2T +T.TEST = T.TEST +TREND = TENDENS +TRIMMEAN = TRIMMIDDELVÆRDI +VAR.P = VARIANS.P +VAR.S = VARIANS.S +VARA = VARIANSV +VARPA = VARIANSPV +WEIBULL.DIST = WEIBULL.FORDELING +Z.TEST = Z.TEST + +## +## Tekstfunktioner (Text Functions) +## +BAHTTEXT = BAHTTEKST +CHAR = TEGN +CLEAN = RENS +CODE = KODE +CONCAT = CONCAT +DOLLAR = KR +EXACT = EKSAKT +FIND = FIND +FIXED = FAST +ISTHAIDIGIT = ERTHAILANDSKCIFFER +LEFT = VENSTRE +LEN = LÆNGDE +LOWER = SMÅ.BOGSTAVER +MID = MIDT +NUMBERSTRING = TALSTRENG +NUMBERVALUE = TALVÆRDI +PHONETIC = FONETISK +PROPER = STORT.FORBOGSTAV +REPLACE = ERSTAT +REPT = GENTAG +RIGHT = HØJRE +SEARCH = SØG +SUBSTITUTE = UDSKIFT +T = T +TEXT = TEKST +TEXTJOIN = TEKST.KOMBINER +THAIDIGIT = THAILANDSKCIFFER +THAINUMSOUND = THAILANDSKNUMLYD +THAINUMSTRING = THAILANDSKNUMSTRENG +THAISTRINGLENGTH = THAILANDSKSTRENGLÆNGDE +TRIM = FJERN.OVERFLØDIGE.BLANKE +UNICHAR = UNICHAR +UNICODE = UNICODE +UPPER = STORE.BOGSTAVER +VALUE = VÆRDI + +## +## Webfunktioner (Web Functions) +## +ENCODEURL = KODNINGSURL +FILTERXML = FILTRERXML +WEBSERVICE = WEBTJENESTE + +## +## Kompatibilitetsfunktioner (Compatibility Functions) +## +BETADIST = BETAFORDELING +BETAINV = BETAINV +BINOMDIST = BINOMIALFORDELING +CEILING = AFRUND.LOFT +CHIDIST = CHIFORDELING +CHIINV = CHIINV +CHITEST = CHITEST +CONCATENATE = SAMMENKÆDNING +CONFIDENCE = KONFIDENSINTERVAL +COVAR = KOVARIANS +CRITBINOM = KRITBINOM +EXPONDIST = EKSPFORDELING +FDIST = FFORDELING +FINV = FINV +FLOOR = AFRUND.GULV +FORECAST = PROGNOSE +FTEST = FTEST +GAMMADIST = GAMMAFORDELING +GAMMAINV = GAMMAINV +HYPGEOMDIST = HYPGEOFORDELING +LOGINV = LOGINV +LOGNORMDIST = LOGNORMFORDELING +MODE = HYPPIGST +NEGBINOMDIST = NEGBINOMFORDELING +NORMDIST = NORMFORDELING +NORMINV = NORMINV +NORMSDIST = STANDARDNORMFORDELING +NORMSINV = STANDARDNORMINV +PERCENTILE = FRAKTIL +PERCENTRANK = PROCENTPLADS +POISSON = POISSON +QUARTILE = KVARTIL +RANK = PLADS +STDEV = STDAFV +STDEVP = STDAFVP +TDIST = TFORDELING +TINV = TINV +TTEST = TTEST +VAR = VARIANS +VARP = VARIANSP +WEIBULL = WEIBULL +ZTEST = ZTEST diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/de/config b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/de/config new file mode 100644 index 0000000..4ca2b82 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/de/config @@ -0,0 +1,20 @@ +############################################################ +## +## PhpSpreadsheet - locale settings +## +## Deutsch (German) +## +############################################################ + +ArgumentSeparator = ; + +## +## Error Codes +## +NULL +DIV0 +VALUE = #WERT! +REF = #BEZUG! +NAME +NUM = #ZAHL! +NA = #NV diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/de/functions b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/de/functions new file mode 100644 index 0000000..331232f --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/de/functions @@ -0,0 +1,533 @@ +############################################################ +## +## PhpSpreadsheet - function name translations +## +## Deutsch (German) +## +############################################################ + + +## +## Cubefunktionen (Cube Functions) +## +CUBEKPIMEMBER = CUBEKPIELEMENT +CUBEMEMBER = CUBEELEMENT +CUBEMEMBERPROPERTY = CUBEELEMENTEIGENSCHAFT +CUBERANKEDMEMBER = CUBERANGELEMENT +CUBESET = CUBEMENGE +CUBESETCOUNT = CUBEMENGENANZAHL +CUBEVALUE = CUBEWERT + +## +## Datenbankfunktionen (Database Functions) +## +DAVERAGE = DBMITTELWERT +DCOUNT = DBANZAHL +DCOUNTA = DBANZAHL2 +DGET = DBAUSZUG +DMAX = DBMAX +DMIN = DBMIN +DPRODUCT = DBPRODUKT +DSTDEV = DBSTDABW +DSTDEVP = DBSTDABWN +DSUM = DBSUMME +DVAR = DBVARIANZ +DVARP = DBVARIANZEN + +## +## Datums- und Uhrzeitfunktionen (Date & Time Functions) +## +DATE = DATUM +DATEVALUE = DATWERT +DAY = TAG +DAYS = TAGE +DAYS360 = TAGE360 +EDATE = EDATUM +EOMONTH = MONATSENDE +HOUR = STUNDE +ISOWEEKNUM = ISOKALENDERWOCHE +MINUTE = MINUTE +MONTH = MONAT +NETWORKDAYS = NETTOARBEITSTAGE +NETWORKDAYS.INTL = NETTOARBEITSTAGE.INTL +NOW = JETZT +SECOND = SEKUNDE +THAIDAYOFWEEK = THAIWOCHENTAG +THAIMONTHOFYEAR = THAIMONATDESJAHRES +THAIYEAR = THAIJAHR +TIME = ZEIT +TIMEVALUE = ZEITWERT +TODAY = HEUTE +WEEKDAY = WOCHENTAG +WEEKNUM = KALENDERWOCHE +WORKDAY = ARBEITSTAG +WORKDAY.INTL = ARBEITSTAG.INTL +YEAR = JAHR +YEARFRAC = BRTEILJAHRE + +## +## Technische Funktionen (Engineering Functions) +## +BESSELI = BESSELI +BESSELJ = BESSELJ +BESSELK = BESSELK +BESSELY = BESSELY +BIN2DEC = BININDEZ +BIN2HEX = BININHEX +BIN2OCT = BININOKT +BITAND = BITUND +BITLSHIFT = BITLVERSCHIEB +BITOR = BITODER +BITRSHIFT = BITRVERSCHIEB +BITXOR = BITXODER +COMPLEX = KOMPLEXE +CONVERT = UMWANDELN +DEC2BIN = DEZINBIN +DEC2HEX = DEZINHEX +DEC2OCT = DEZINOKT +DELTA = DELTA +ERF = GAUSSFEHLER +ERF.PRECISE = GAUSSF.GENAU +ERFC = GAUSSFKOMPL +ERFC.PRECISE = GAUSSFKOMPL.GENAU +GESTEP = GGANZZAHL +HEX2BIN = HEXINBIN +HEX2DEC = HEXINDEZ +HEX2OCT = HEXINOKT +IMABS = IMABS +IMAGINARY = IMAGINÄRTEIL +IMARGUMENT = IMARGUMENT +IMCONJUGATE = IMKONJUGIERTE +IMCOS = IMCOS +IMCOSH = IMCOSHYP +IMCOT = IMCOT +IMCSC = IMCOSEC +IMCSCH = IMCOSECHYP +IMDIV = IMDIV +IMEXP = IMEXP +IMLN = IMLN +IMLOG10 = IMLOG10 +IMLOG2 = IMLOG2 +IMPOWER = IMAPOTENZ +IMPRODUCT = IMPRODUKT +IMREAL = IMREALTEIL +IMSEC = IMSEC +IMSECH = IMSECHYP +IMSIN = IMSIN +IMSINH = IMSINHYP +IMSQRT = IMWURZEL +IMSUB = IMSUB +IMSUM = IMSUMME +IMTAN = IMTAN +OCT2BIN = OKTINBIN +OCT2DEC = OKTINDEZ +OCT2HEX = OKTINHEX + +## +## Finanzmathematische Funktionen (Financial Functions) +## +ACCRINT = AUFGELZINS +ACCRINTM = AUFGELZINSF +AMORDEGRC = AMORDEGRK +AMORLINC = AMORLINEARK +COUPDAYBS = ZINSTERMTAGVA +COUPDAYS = ZINSTERMTAGE +COUPDAYSNC = ZINSTERMTAGNZ +COUPNCD = ZINSTERMNZ +COUPNUM = ZINSTERMZAHL +COUPPCD = ZINSTERMVZ +CUMIPMT = KUMZINSZ +CUMPRINC = KUMKAPITAL +DB = GDA2 +DDB = GDA +DISC = DISAGIO +DOLLARDE = NOTIERUNGDEZ +DOLLARFR = NOTIERUNGBRU +DURATION = DURATION +EFFECT = EFFEKTIV +FV = ZW +FVSCHEDULE = ZW2 +INTRATE = ZINSSATZ +IPMT = ZINSZ +IRR = IKV +ISPMT = ISPMT +MDURATION = MDURATION +MIRR = QIKV +NOMINAL = NOMINAL +NPER = ZZR +NPV = NBW +ODDFPRICE = UNREGER.KURS +ODDFYIELD = UNREGER.REND +ODDLPRICE = UNREGLE.KURS +ODDLYIELD = UNREGLE.REND +PDURATION = PDURATION +PMT = RMZ +PPMT = KAPZ +PRICE = KURS +PRICEDISC = KURSDISAGIO +PRICEMAT = KURSFÄLLIG +PV = BW +RATE = ZINS +RECEIVED = AUSZAHLUNG +RRI = ZSATZINVEST +SLN = LIA +SYD = DIA +TBILLEQ = TBILLÄQUIV +TBILLPRICE = TBILLKURS +TBILLYIELD = TBILLRENDITE +VDB = VDB +XIRR = XINTZINSFUSS +XNPV = XKAPITALWERT +YIELD = RENDITE +YIELDDISC = RENDITEDIS +YIELDMAT = RENDITEFÄLL + +## +## Informationsfunktionen (Information Functions) +## +CELL = ZELLE +ERROR.TYPE = FEHLER.TYP +INFO = INFO +ISBLANK = ISTLEER +ISERR = ISTFEHL +ISERROR = ISTFEHLER +ISEVEN = ISTGERADE +ISFORMULA = ISTFORMEL +ISLOGICAL = ISTLOG +ISNA = ISTNV +ISNONTEXT = ISTKTEXT +ISNUMBER = ISTZAHL +ISODD = ISTUNGERADE +ISREF = ISTBEZUG +ISTEXT = ISTTEXT +N = N +NA = NV +SHEET = BLATT +SHEETS = BLÄTTER +TYPE = TYP + +## +## Logische Funktionen (Logical Functions) +## +AND = UND +FALSE = FALSCH +IF = WENN +IFERROR = WENNFEHLER +IFNA = WENNNV +IFS = WENNS +NOT = NICHT +OR = ODER +SWITCH = ERSTERWERT +TRUE = WAHR +XOR = XODER + +## +## Nachschlage- und Verweisfunktionen (Lookup & Reference Functions) +## +ADDRESS = ADRESSE +AREAS = BEREICHE +CHOOSE = WAHL +COLUMN = SPALTE +COLUMNS = SPALTEN +FORMULATEXT = FORMELTEXT +GETPIVOTDATA = PIVOTDATENZUORDNEN +HLOOKUP = WVERWEIS +HYPERLINK = HYPERLINK +INDEX = INDEX +INDIRECT = INDIREKT +LOOKUP = VERWEIS +MATCH = VERGLEICH +OFFSET = BEREICH.VERSCHIEBEN +ROW = ZEILE +ROWS = ZEILEN +RTD = RTD +TRANSPOSE = MTRANS +VLOOKUP = SVERWEIS + +## +## Mathematische und trigonometrische Funktionen (Math & Trig Functions) +## +ABS = ABS +ACOS = ARCCOS +ACOSH = ARCCOSHYP +ACOT = ARCCOT +ACOTH = ARCCOTHYP +AGGREGATE = AGGREGAT +ARABIC = ARABISCH +ASIN = ARCSIN +ASINH = ARCSINHYP +ATAN = ARCTAN +ATAN2 = ARCTAN2 +ATANH = ARCTANHYP +BASE = BASIS +CEILING.MATH = OBERGRENZE.MATHEMATIK +CEILING.PRECISE = OBERGRENZE.GENAU +COMBIN = KOMBINATIONEN +COMBINA = KOMBINATIONEN2 +COS = COS +COSH = COSHYP +COT = COT +COTH = COTHYP +CSC = COSEC +CSCH = COSECHYP +DECIMAL = DEZIMAL +DEGREES = GRAD +ECMA.CEILING = ECMA.OBERGRENZE +EVEN = GERADE +EXP = EXP +FACT = FAKULTÄT +FACTDOUBLE = ZWEIFAKULTÄT +FLOOR.MATH = UNTERGRENZE.MATHEMATIK +FLOOR.PRECISE = UNTERGRENZE.GENAU +GCD = GGT +INT = GANZZAHL +ISO.CEILING = ISO.OBERGRENZE +LCM = KGV +LN = LN +LOG = LOG +LOG10 = LOG10 +MDETERM = MDET +MINVERSE = MINV +MMULT = MMULT +MOD = REST +MROUND = VRUNDEN +MULTINOMIAL = POLYNOMIAL +MUNIT = MEINHEIT +ODD = UNGERADE +PI = PI +POWER = POTENZ +PRODUCT = PRODUKT +QUOTIENT = QUOTIENT +RADIANS = BOGENMASS +RAND = ZUFALLSZAHL +RANDBETWEEN = ZUFALLSBEREICH +ROMAN = RÖMISCH +ROUND = RUNDEN +ROUNDBAHTDOWN = RUNDBAHTNED +ROUNDBAHTUP = BAHTAUFRUNDEN +ROUNDDOWN = ABRUNDEN +ROUNDUP = AUFRUNDEN +SEC = SEC +SECH = SECHYP +SERIESSUM = POTENZREIHE +SIGN = VORZEICHEN +SIN = SIN +SINH = SINHYP +SQRT = WURZEL +SQRTPI = WURZELPI +SUBTOTAL = TEILERGEBNIS +SUM = SUMME +SUMIF = SUMMEWENN +SUMIFS = SUMMEWENNS +SUMPRODUCT = SUMMENPRODUKT +SUMSQ = QUADRATESUMME +SUMX2MY2 = SUMMEX2MY2 +SUMX2PY2 = SUMMEX2PY2 +SUMXMY2 = SUMMEXMY2 +TAN = TAN +TANH = TANHYP +TRUNC = KÜRZEN + +## +## Statistische Funktionen (Statistical Functions) +## +AVEDEV = MITTELABW +AVERAGE = MITTELWERT +AVERAGEA = MITTELWERTA +AVERAGEIF = MITTELWERTWENN +AVERAGEIFS = MITTELWERTWENNS +BETA.DIST = BETA.VERT +BETA.INV = BETA.INV +BINOM.DIST = BINOM.VERT +BINOM.DIST.RANGE = BINOM.VERT.BEREICH +BINOM.INV = BINOM.INV +CHISQ.DIST = CHIQU.VERT +CHISQ.DIST.RT = CHIQU.VERT.RE +CHISQ.INV = CHIQU.INV +CHISQ.INV.RT = CHIQU.INV.RE +CHISQ.TEST = CHIQU.TEST +CONFIDENCE.NORM = KONFIDENZ.NORM +CONFIDENCE.T = KONFIDENZ.T +CORREL = KORREL +COUNT = ANZAHL +COUNTA = ANZAHL2 +COUNTBLANK = ANZAHLLEEREZELLEN +COUNTIF = ZÄHLENWENN +COUNTIFS = ZÄHLENWENNS +COVARIANCE.P = KOVARIANZ.P +COVARIANCE.S = KOVARIANZ.S +DEVSQ = SUMQUADABW +EXPON.DIST = EXPON.VERT +F.DIST = F.VERT +F.DIST.RT = F.VERT.RE +F.INV = F.INV +F.INV.RT = F.INV.RE +F.TEST = F.TEST +FISHER = FISHER +FISHERINV = FISHERINV +FORECAST.ETS = PROGNOSE.ETS +FORECAST.ETS.CONFINT = PROGNOSE.ETS.KONFINT +FORECAST.ETS.SEASONALITY = PROGNOSE.ETS.SAISONALITÄT +FORECAST.ETS.STAT = PROGNOSE.ETS.STAT +FORECAST.LINEAR = PROGNOSE.LINEAR +FREQUENCY = HÄUFIGKEIT +GAMMA = GAMMA +GAMMA.DIST = GAMMA.VERT +GAMMA.INV = GAMMA.INV +GAMMALN = GAMMALN +GAMMALN.PRECISE = GAMMALN.GENAU +GAUSS = GAUSS +GEOMEAN = GEOMITTEL +GROWTH = VARIATION +HARMEAN = HARMITTEL +HYPGEOM.DIST = HYPGEOM.VERT +INTERCEPT = ACHSENABSCHNITT +KURT = KURT +LARGE = KGRÖSSTE +LINEST = RGP +LOGEST = RKP +LOGNORM.DIST = LOGNORM.VERT +LOGNORM.INV = LOGNORM.INV +MAX = MAX +MAXA = MAXA +MAXIFS = MAXWENNS +MEDIAN = MEDIAN +MIN = MIN +MINA = MINA +MINIFS = MINWENNS +MODE.MULT = MODUS.VIELF +MODE.SNGL = MODUS.EINF +NEGBINOM.DIST = NEGBINOM.VERT +NORM.DIST = NORM.VERT +NORM.INV = NORM.INV +NORM.S.DIST = NORM.S.VERT +NORM.S.INV = NORM.S.INV +PEARSON = PEARSON +PERCENTILE.EXC = QUANTIL.EXKL +PERCENTILE.INC = QUANTIL.INKL +PERCENTRANK.EXC = QUANTILSRANG.EXKL +PERCENTRANK.INC = QUANTILSRANG.INKL +PERMUT = VARIATIONEN +PERMUTATIONA = VARIATIONEN2 +PHI = PHI +POISSON.DIST = POISSON.VERT +PROB = WAHRSCHBEREICH +QUARTILE.EXC = QUARTILE.EXKL +QUARTILE.INC = QUARTILE.INKL +RANK.AVG = RANG.MITTELW +RANK.EQ = RANG.GLEICH +RSQ = BESTIMMTHEITSMASS +SKEW = SCHIEFE +SKEW.P = SCHIEFE.P +SLOPE = STEIGUNG +SMALL = KKLEINSTE +STANDARDIZE = STANDARDISIERUNG +STDEV.P = STABW.N +STDEV.S = STABW.S +STDEVA = STABWA +STDEVPA = STABWNA +STEYX = STFEHLERYX +T.DIST = T.VERT +T.DIST.2T = T.VERT.2S +T.DIST.RT = T.VERT.RE +T.INV = T.INV +T.INV.2T = T.INV.2S +T.TEST = T.TEST +TREND = TREND +TRIMMEAN = GESTUTZTMITTEL +VAR.P = VAR.P +VAR.S = VAR.S +VARA = VARIANZA +VARPA = VARIANZENA +WEIBULL.DIST = WEIBULL.VERT +Z.TEST = G.TEST + +## +## Textfunktionen (Text Functions) +## +BAHTTEXT = BAHTTEXT +CHAR = ZEICHEN +CLEAN = SÄUBERN +CODE = CODE +CONCAT = TEXTKETTE +DOLLAR = DM +EXACT = IDENTISCH +FIND = FINDEN +FIXED = FEST +ISTHAIDIGIT = ISTTHAIZAHLENWORT +LEFT = LINKS +LEN = LÄNGE +LOWER = KLEIN +MID = TEIL +NUMBERVALUE = ZAHLENWERT +PROPER = GROSS2 +REPLACE = ERSETZEN +REPT = WIEDERHOLEN +RIGHT = RECHTS +SEARCH = SUCHEN +SUBSTITUTE = WECHSELN +T = T +TEXT = TEXT +TEXTJOIN = TEXTVERKETTEN +THAIDIGIT = THAIZAHLENWORT +THAINUMSOUND = THAIZAHLSOUND +THAINUMSTRING = THAILANDSKNUMSTRENG +THAISTRINGLENGTH = THAIZEICHENFOLGENLÄNGE +TRIM = GLÄTTEN +UNICHAR = UNIZEICHEN +UNICODE = UNICODE +UPPER = GROSS +VALUE = WERT + +## +## Webfunktionen (Web Functions) +## +ENCODEURL = URLCODIEREN +FILTERXML = XMLFILTERN +WEBSERVICE = WEBDIENST + +## +## Kompatibilitätsfunktionen (Compatibility Functions) +## +BETADIST = BETAVERT +BETAINV = BETAINV +BINOMDIST = BINOMVERT +CEILING = OBERGRENZE +CHIDIST = CHIVERT +CHIINV = CHIINV +CHITEST = CHITEST +CONCATENATE = VERKETTEN +CONFIDENCE = KONFIDENZ +COVAR = KOVAR +CRITBINOM = KRITBINOM +EXPONDIST = EXPONVERT +FDIST = FVERT +FINV = FINV +FLOOR = UNTERGRENZE +FORECAST = SCHÄTZER +FTEST = FTEST +GAMMADIST = GAMMAVERT +GAMMAINV = GAMMAINV +HYPGEOMDIST = HYPGEOMVERT +LOGINV = LOGINV +LOGNORMDIST = LOGNORMVERT +MODE = MODALWERT +NEGBINOMDIST = NEGBINOMVERT +NORMDIST = NORMVERT +NORMINV = NORMINV +NORMSDIST = STANDNORMVERT +NORMSINV = STANDNORMINV +PERCENTILE = QUANTIL +PERCENTRANK = QUANTILSRANG +POISSON = POISSON +QUARTILE = QUARTILE +RANK = RANG +STDEV = STABW +STDEVP = STABWN +TDIST = TVERT +TINV = TINV +TTEST = TTEST +VAR = VARIANZ +VARP = VARIANZEN +WEIBULL = WEIBULL +ZTEST = GTEST diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/en/uk/config b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/en/uk/config new file mode 100644 index 0000000..859e4be --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/en/uk/config @@ -0,0 +1,8 @@ +## +## PhpSpreadsheet +## + +## +## (For future use) +## +currencySymbol = £ diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/es/config b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/es/config new file mode 100644 index 0000000..fe044ef --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/es/config @@ -0,0 +1,20 @@ +############################################################ +## +## PhpSpreadsheet - locale settings +## +## Español (Spanish) +## +############################################################ + +ArgumentSeparator = ; + +## +## Error Codes +## +NULL = #¡NULO! +DIV0 = #¡DIV/0! +VALUE = #¡VALOR! +REF = #¡REF! +NAME = #¿NOMBRE? +NUM = #¡NUM! +NA diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/es/functions b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/es/functions new file mode 100644 index 0000000..1f9f289 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/es/functions @@ -0,0 +1,537 @@ +############################################################ +## +## PhpSpreadsheet - function name translations +## +## Español (Spanish) +## +############################################################ + + +## +## Funciones de cubo (Cube Functions) +## +CUBEKPIMEMBER = MIEMBROKPICUBO +CUBEMEMBER = MIEMBROCUBO +CUBEMEMBERPROPERTY = PROPIEDADMIEMBROCUBO +CUBERANKEDMEMBER = MIEMBRORANGOCUBO +CUBESET = CONJUNTOCUBO +CUBESETCOUNT = RECUENTOCONJUNTOCUBO +CUBEVALUE = VALORCUBO + +## +## Funciones de base de datos (Database Functions) +## +DAVERAGE = BDPROMEDIO +DCOUNT = BDCONTAR +DCOUNTA = BDCONTARA +DGET = BDEXTRAER +DMAX = BDMAX +DMIN = BDMIN +DPRODUCT = BDPRODUCTO +DSTDEV = BDDESVEST +DSTDEVP = BDDESVESTP +DSUM = BDSUMA +DVAR = BDVAR +DVARP = BDVARP + +## +## Funciones de fecha y hora (Date & Time Functions) +## +DATE = FECHA +DATEDIF = SIFECHA +DATESTRING = CADENA.FECHA +DATEVALUE = FECHANUMERO +DAY = DIA +DAYS = DIAS +DAYS360 = DIAS360 +EDATE = FECHA.MES +EOMONTH = FIN.MES +HOUR = HORA +ISOWEEKNUM = ISO.NUM.DE.SEMANA +MINUTE = MINUTO +MONTH = MES +NETWORKDAYS = DIAS.LAB +NETWORKDAYS.INTL = DIAS.LAB.INTL +NOW = AHORA +SECOND = SEGUNDO +THAIDAYOFWEEK = DIASEMTAI +THAIMONTHOFYEAR = MESAÑOTAI +THAIYEAR = AÑOTAI +TIME = NSHORA +TIMEVALUE = HORANUMERO +TODAY = HOY +WEEKDAY = DIASEM +WEEKNUM = NUM.DE.SEMANA +WORKDAY = DIA.LAB +WORKDAY.INTL = DIA.LAB.INTL +YEAR = AÑO +YEARFRAC = FRAC.AÑO + +## +## Funciones de ingeniería (Engineering Functions) +## +BESSELI = BESSELI +BESSELJ = BESSELJ +BESSELK = BESSELK +BESSELY = BESSELY +BIN2DEC = BIN.A.DEC +BIN2HEX = BIN.A.HEX +BIN2OCT = BIN.A.OCT +BITAND = BIT.Y +BITLSHIFT = BIT.DESPLIZQDA +BITOR = BIT.O +BITRSHIFT = BIT.DESPLDCHA +BITXOR = BIT.XO +COMPLEX = COMPLEJO +CONVERT = CONVERTIR +DEC2BIN = DEC.A.BIN +DEC2HEX = DEC.A.HEX +DEC2OCT = DEC.A.OCT +DELTA = DELTA +ERF = FUN.ERROR +ERF.PRECISE = FUN.ERROR.EXACTO +ERFC = FUN.ERROR.COMPL +ERFC.PRECISE = FUN.ERROR.COMPL.EXACTO +GESTEP = MAYOR.O.IGUAL +HEX2BIN = HEX.A.BIN +HEX2DEC = HEX.A.DEC +HEX2OCT = HEX.A.OCT +IMABS = IM.ABS +IMAGINARY = IMAGINARIO +IMARGUMENT = IM.ANGULO +IMCONJUGATE = IM.CONJUGADA +IMCOS = IM.COS +IMCOSH = IM.COSH +IMCOT = IM.COT +IMCSC = IM.CSC +IMCSCH = IM.CSCH +IMDIV = IM.DIV +IMEXP = IM.EXP +IMLN = IM.LN +IMLOG10 = IM.LOG10 +IMLOG2 = IM.LOG2 +IMPOWER = IM.POT +IMPRODUCT = IM.PRODUCT +IMREAL = IM.REAL +IMSEC = IM.SEC +IMSECH = IM.SECH +IMSIN = IM.SENO +IMSINH = IM.SENOH +IMSQRT = IM.RAIZ2 +IMSUB = IM.SUSTR +IMSUM = IM.SUM +IMTAN = IM.TAN +OCT2BIN = OCT.A.BIN +OCT2DEC = OCT.A.DEC +OCT2HEX = OCT.A.HEX + +## +## Funciones financieras (Financial Functions) +## +ACCRINT = INT.ACUM +ACCRINTM = INT.ACUM.V +AMORDEGRC = AMORTIZ.PROGRE +AMORLINC = AMORTIZ.LIN +COUPDAYBS = CUPON.DIAS.L1 +COUPDAYS = CUPON.DIAS +COUPDAYSNC = CUPON.DIAS.L2 +COUPNCD = CUPON.FECHA.L2 +COUPNUM = CUPON.NUM +COUPPCD = CUPON.FECHA.L1 +CUMIPMT = PAGO.INT.ENTRE +CUMPRINC = PAGO.PRINC.ENTRE +DB = DB +DDB = DDB +DISC = TASA.DESC +DOLLARDE = MONEDA.DEC +DOLLARFR = MONEDA.FRAC +DURATION = DURACION +EFFECT = INT.EFECTIVO +FV = VF +FVSCHEDULE = VF.PLAN +INTRATE = TASA.INT +IPMT = PAGOINT +IRR = TIR +ISPMT = INT.PAGO.DIR +MDURATION = DURACION.MODIF +MIRR = TIRM +NOMINAL = TASA.NOMINAL +NPER = NPER +NPV = VNA +ODDFPRICE = PRECIO.PER.IRREGULAR.1 +ODDFYIELD = RENDTO.PER.IRREGULAR.1 +ODDLPRICE = PRECIO.PER.IRREGULAR.2 +ODDLYIELD = RENDTO.PER.IRREGULAR.2 +PDURATION = P.DURACION +PMT = PAGO +PPMT = PAGOPRIN +PRICE = PRECIO +PRICEDISC = PRECIO.DESCUENTO +PRICEMAT = PRECIO.VENCIMIENTO +PV = VA +RATE = TASA +RECEIVED = CANTIDAD.RECIBIDA +RRI = RRI +SLN = SLN +SYD = SYD +TBILLEQ = LETRA.DE.TEST.EQV.A.BONO +TBILLPRICE = LETRA.DE.TES.PRECIO +TBILLYIELD = LETRA.DE.TES.RENDTO +VDB = DVS +XIRR = TIR.NO.PER +XNPV = VNA.NO.PER +YIELD = RENDTO +YIELDDISC = RENDTO.DESC +YIELDMAT = RENDTO.VENCTO + +## +## Funciones de información (Information Functions) +## +CELL = CELDA +ERROR.TYPE = TIPO.DE.ERROR +INFO = INFO +ISBLANK = ESBLANCO +ISERR = ESERR +ISERROR = ESERROR +ISEVEN = ES.PAR +ISFORMULA = ESFORMULA +ISLOGICAL = ESLOGICO +ISNA = ESNOD +ISNONTEXT = ESNOTEXTO +ISNUMBER = ESNUMERO +ISODD = ES.IMPAR +ISREF = ESREF +ISTEXT = ESTEXTO +N = N +NA = NOD +SHEET = HOJA +SHEETS = HOJAS +TYPE = TIPO + +## +## Funciones lógicas (Logical Functions) +## +AND = Y +FALSE = FALSO +IF = SI +IFERROR = SI.ERROR +IFNA = SI.ND +IFS = SI.CONJUNTO +NOT = NO +OR = O +SWITCH = CAMBIAR +TRUE = VERDADERO +XOR = XO + +## +## Funciones de búsqueda y referencia (Lookup & Reference Functions) +## +ADDRESS = DIRECCION +AREAS = AREAS +CHOOSE = ELEGIR +COLUMN = COLUMNA +COLUMNS = COLUMNAS +FORMULATEXT = FORMULATEXTO +GETPIVOTDATA = IMPORTARDATOSDINAMICOS +HLOOKUP = BUSCARH +HYPERLINK = HIPERVINCULO +INDEX = INDICE +INDIRECT = INDIRECTO +LOOKUP = BUSCAR +MATCH = COINCIDIR +OFFSET = DESREF +ROW = FILA +ROWS = FILAS +RTD = RDTR +TRANSPOSE = TRANSPONER +VLOOKUP = BUSCARV + +## +## Funciones matemáticas y trigonométricas (Math & Trig Functions) +## +ABS = ABS +ACOS = ACOS +ACOSH = ACOSH +ACOT = ACOT +ACOTH = ACOTH +AGGREGATE = AGREGAR +ARABIC = NUMERO.ARABE +ASIN = ASENO +ASINH = ASENOH +ATAN = ATAN +ATAN2 = ATAN2 +ATANH = ATANH +BASE = BASE +CEILING.MATH = MULTIPLO.SUPERIOR.MAT +CEILING.PRECISE = MULTIPLO.SUPERIOR.EXACTO +COMBIN = COMBINAT +COMBINA = COMBINA +COS = COS +COSH = COSH +COT = COT +COTH = COTH +CSC = CSC +CSCH = CSCH +DECIMAL = CONV.DECIMAL +DEGREES = GRADOS +ECMA.CEILING = MULTIPLO.SUPERIOR.ECMA +EVEN = REDONDEA.PAR +EXP = EXP +FACT = FACT +FACTDOUBLE = FACT.DOBLE +FLOOR.MATH = MULTIPLO.INFERIOR.MAT +FLOOR.PRECISE = MULTIPLO.INFERIOR.EXACTO +GCD = M.C.D +INT = ENTERO +ISO.CEILING = MULTIPLO.SUPERIOR.ISO +LCM = M.C.M +LN = LN +LOG = LOG +LOG10 = LOG10 +MDETERM = MDETERM +MINVERSE = MINVERSA +MMULT = MMULT +MOD = RESIDUO +MROUND = REDOND.MULT +MULTINOMIAL = MULTINOMIAL +MUNIT = M.UNIDAD +ODD = REDONDEA.IMPAR +PI = PI +POWER = POTENCIA +PRODUCT = PRODUCTO +QUOTIENT = COCIENTE +RADIANS = RADIANES +RAND = ALEATORIO +RANDBETWEEN = ALEATORIO.ENTRE +ROMAN = NUMERO.ROMANO +ROUND = REDONDEAR +ROUNDBAHTDOWN = REDONDEAR.BAHT.MAS +ROUNDBAHTUP = REDONDEAR.BAHT.MENOS +ROUNDDOWN = REDONDEAR.MENOS +ROUNDUP = REDONDEAR.MAS +SEC = SEC +SECH = SECH +SERIESSUM = SUMA.SERIES +SIGN = SIGNO +SIN = SENO +SINH = SENOH +SQRT = RAIZ +SQRTPI = RAIZ2PI +SUBTOTAL = SUBTOTALES +SUM = SUMA +SUMIF = SUMAR.SI +SUMIFS = SUMAR.SI.CONJUNTO +SUMPRODUCT = SUMAPRODUCTO +SUMSQ = SUMA.CUADRADOS +SUMX2MY2 = SUMAX2MENOSY2 +SUMX2PY2 = SUMAX2MASY2 +SUMXMY2 = SUMAXMENOSY2 +TAN = TAN +TANH = TANH +TRUNC = TRUNCAR + +## +## Funciones estadísticas (Statistical Functions) +## +AVEDEV = DESVPROM +AVERAGE = PROMEDIO +AVERAGEA = PROMEDIOA +AVERAGEIF = PROMEDIO.SI +AVERAGEIFS = PROMEDIO.SI.CONJUNTO +BETA.DIST = DISTR.BETA.N +BETA.INV = INV.BETA.N +BINOM.DIST = DISTR.BINOM.N +BINOM.DIST.RANGE = DISTR.BINOM.SERIE +BINOM.INV = INV.BINOM +CHISQ.DIST = DISTR.CHICUAD +CHISQ.DIST.RT = DISTR.CHICUAD.CD +CHISQ.INV = INV.CHICUAD +CHISQ.INV.RT = INV.CHICUAD.CD +CHISQ.TEST = PRUEBA.CHICUAD +CONFIDENCE.NORM = INTERVALO.CONFIANZA.NORM +CONFIDENCE.T = INTERVALO.CONFIANZA.T +CORREL = COEF.DE.CORREL +COUNT = CONTAR +COUNTA = CONTARA +COUNTBLANK = CONTAR.BLANCO +COUNTIF = CONTAR.SI +COUNTIFS = CONTAR.SI.CONJUNTO +COVARIANCE.P = COVARIANCE.P +COVARIANCE.S = COVARIANZA.M +DEVSQ = DESVIA2 +EXPON.DIST = DISTR.EXP.N +F.DIST = DISTR.F.N +F.DIST.RT = DISTR.F.CD +F.INV = INV.F +F.INV.RT = INV.F.CD +F.TEST = PRUEBA.F.N +FISHER = FISHER +FISHERINV = PRUEBA.FISHER.INV +FORECAST.ETS = PRONOSTICO.ETS +FORECAST.ETS.CONFINT = PRONOSTICO.ETS.CONFINT +FORECAST.ETS.SEASONALITY = PRONOSTICO.ETS.ESTACIONALIDAD +FORECAST.ETS.STAT = PRONOSTICO.ETS.STAT +FORECAST.LINEAR = PRONOSTICO.LINEAL +FREQUENCY = FRECUENCIA +GAMMA = GAMMA +GAMMA.DIST = DISTR.GAMMA.N +GAMMA.INV = INV.GAMMA +GAMMALN = GAMMA.LN +GAMMALN.PRECISE = GAMMA.LN.EXACTO +GAUSS = GAUSS +GEOMEAN = MEDIA.GEOM +GROWTH = CRECIMIENTO +HARMEAN = MEDIA.ARMO +HYPGEOM.DIST = DISTR.HIPERGEOM.N +INTERCEPT = INTERSECCION.EJE +KURT = CURTOSIS +LARGE = K.ESIMO.MAYOR +LINEST = ESTIMACION.LINEAL +LOGEST = ESTIMACION.LOGARITMICA +LOGNORM.DIST = DISTR.LOGNORM +LOGNORM.INV = INV.LOGNORM +MAX = MAX +MAXA = MAXA +MAXIFS = MAX.SI.CONJUNTO +MEDIAN = MEDIANA +MIN = MIN +MINA = MINA +MINIFS = MIN.SI.CONJUNTO +MODE.MULT = MODA.VARIOS +MODE.SNGL = MODA.UNO +NEGBINOM.DIST = NEGBINOM.DIST +NORM.DIST = DISTR.NORM.N +NORM.INV = INV.NORM +NORM.S.DIST = DISTR.NORM.ESTAND.N +NORM.S.INV = INV.NORM.ESTAND +PEARSON = PEARSON +PERCENTILE.EXC = PERCENTIL.EXC +PERCENTILE.INC = PERCENTIL.INC +PERCENTRANK.EXC = RANGO.PERCENTIL.EXC +PERCENTRANK.INC = RANGO.PERCENTIL.INC +PERMUT = PERMUTACIONES +PERMUTATIONA = PERMUTACIONES.A +PHI = FI +POISSON.DIST = POISSON.DIST +PROB = PROBABILIDAD +QUARTILE.EXC = CUARTIL.EXC +QUARTILE.INC = CUARTIL.INC +RANK.AVG = JERARQUIA.MEDIA +RANK.EQ = JERARQUIA.EQV +RSQ = COEFICIENTE.R2 +SKEW = COEFICIENTE.ASIMETRIA +SKEW.P = COEFICIENTE.ASIMETRIA.P +SLOPE = PENDIENTE +SMALL = K.ESIMO.MENOR +STANDARDIZE = NORMALIZACION +STDEV.P = DESVEST.P +STDEV.S = DESVEST.M +STDEVA = DESVESTA +STDEVPA = DESVESTPA +STEYX = ERROR.TIPICO.XY +T.DIST = DISTR.T.N +T.DIST.2T = DISTR.T.2C +T.DIST.RT = DISTR.T.CD +T.INV = INV.T +T.INV.2T = INV.T.2C +T.TEST = PRUEBA.T.N +TREND = TENDENCIA +TRIMMEAN = MEDIA.ACOTADA +VAR.P = VAR.P +VAR.S = VAR.S +VARA = VARA +VARPA = VARPA +WEIBULL.DIST = DISTR.WEIBULL +Z.TEST = PRUEBA.Z.N + +## +## Funciones de texto (Text Functions) +## +BAHTTEXT = TEXTOBAHT +CHAR = CARACTER +CLEAN = LIMPIAR +CODE = CODIGO +CONCAT = CONCAT +DOLLAR = MONEDA +EXACT = IGUAL +FIND = ENCONTRAR +FIXED = DECIMAL +ISTHAIDIGIT = ESDIGITOTAI +LEFT = IZQUIERDA +LEN = LARGO +LOWER = MINUSC +MID = EXTRAE +NUMBERSTRING = CADENA.NUMERO +NUMBERVALUE = VALOR.NUMERO +PHONETIC = FONETICO +PROPER = NOMPROPIO +REPLACE = REEMPLAZAR +REPT = REPETIR +RIGHT = DERECHA +SEARCH = HALLAR +SUBSTITUTE = SUSTITUIR +T = T +TEXT = TEXTO +TEXTJOIN = UNIRCADENAS +THAIDIGIT = DIGITOTAI +THAINUMSOUND = SONNUMTAI +THAINUMSTRING = CADENANUMTAI +THAISTRINGLENGTH = LONGCADENATAI +TRIM = ESPACIOS +UNICHAR = UNICAR +UNICODE = UNICODE +UPPER = MAYUSC +VALUE = VALOR + +## +## Funciones web (Web Functions) +## +ENCODEURL = URLCODIF +FILTERXML = XMLFILTRO +WEBSERVICE = SERVICIOWEB + +## +## Funciones de compatibilidad (Compatibility Functions) +## +BETADIST = DISTR.BETA +BETAINV = DISTR.BETA.INV +BINOMDIST = DISTR.BINOM +CEILING = MULTIPLO.SUPERIOR +CHIDIST = DISTR.CHI +CHIINV = PRUEBA.CHI.INV +CHITEST = PRUEBA.CHI +CONCATENATE = CONCATENAR +CONFIDENCE = INTERVALO.CONFIANZA +COVAR = COVAR +CRITBINOM = BINOM.CRIT +EXPONDIST = DISTR.EXP +FDIST = DISTR.F +FINV = DISTR.F.INV +FLOOR = MULTIPLO.INFERIOR +FORECAST = PRONOSTICO +FTEST = PRUEBA.F +GAMMADIST = DISTR.GAMMA +GAMMAINV = DISTR.GAMMA.INV +HYPGEOMDIST = DISTR.HIPERGEOM +LOGINV = DISTR.LOG.INV +LOGNORMDIST = DISTR.LOG.NORM +MODE = MODA +NEGBINOMDIST = NEGBINOMDIST +NORMDIST = DISTR.NORM +NORMINV = DISTR.NORM.INV +NORMSDIST = DISTR.NORM.ESTAND +NORMSINV = DISTR.NORM.ESTAND.INV +PERCENTILE = PERCENTIL +PERCENTRANK = RANGO.PERCENTIL +POISSON = POISSON +QUARTILE = CUARTIL +RANK = JERARQUIA +STDEV = DESVEST +STDEVP = DESVESTP +TDIST = DISTR.T +TINV = DISTR.T.INV +TTEST = PRUEBA.T +VAR = VAR +VARP = VARP +WEIBULL = DIST.WEIBULL +ZTEST = PRUEBA.Z diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fi/config b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fi/config new file mode 100644 index 0000000..5388f93 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fi/config @@ -0,0 +1,20 @@ +############################################################ +## +## PhpSpreadsheet - locale settings +## +## Suomi (Finnish) +## +############################################################ + +ArgumentSeparator = ; + +## +## Error Codes +## +NULL = #TYHJÄ! +DIV0 = #JAKO/0! +VALUE = #ARVO! +REF = #VIITTAUS! +NAME = #NIMI? +NUM = #LUKU! +NA = #PUUTTUU! diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fi/functions b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fi/functions new file mode 100644 index 0000000..33068d9 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fi/functions @@ -0,0 +1,537 @@ +############################################################ +## +## PhpSpreadsheet - function name translations +## +## Suomi (Finnish) +## +############################################################ + + +## +## Kuutiofunktiot (Cube Functions) +## +CUBEKPIMEMBER = KUUTIOKPIJÄSEN +CUBEMEMBER = KUUTIONJÄSEN +CUBEMEMBERPROPERTY = KUUTIONJÄSENENOMINAISUUS +CUBERANKEDMEMBER = KUUTIONLUOKITELTUJÄSEN +CUBESET = KUUTIOJOUKKO +CUBESETCOUNT = KUUTIOJOUKKOJENMÄÄRÄ +CUBEVALUE = KUUTIONARVO + +## +## Tietokantafunktiot (Database Functions) +## +DAVERAGE = TKESKIARVO +DCOUNT = TLASKE +DCOUNTA = TLASKEA +DGET = TNOUDA +DMAX = TMAKS +DMIN = TMIN +DPRODUCT = TTULO +DSTDEV = TKESKIHAJONTA +DSTDEVP = TKESKIHAJONTAP +DSUM = TSUMMA +DVAR = TVARIANSSI +DVARP = TVARIANSSIP + +## +## Päivämäärä- ja aikafunktiot (Date & Time Functions) +## +DATE = PÄIVÄYS +DATEDIF = PVMERO +DATESTRING = PVMMERKKIJONO +DATEVALUE = PÄIVÄYSARVO +DAY = PÄIVÄ +DAYS = PÄIVÄT +DAYS360 = PÄIVÄT360 +EDATE = PÄIVÄ.KUUKAUSI +EOMONTH = KUUKAUSI.LOPPU +HOUR = TUNNIT +ISOWEEKNUM = VIIKKO.ISO.NRO +MINUTE = MINUUTIT +MONTH = KUUKAUSI +NETWORKDAYS = TYÖPÄIVÄT +NETWORKDAYS.INTL = TYÖPÄIVÄT.KANSVÄL +NOW = NYT +SECOND = SEKUNNIT +THAIDAYOFWEEK = THAI.VIIKONPÄIVÄ +THAIMONTHOFYEAR = THAI.KUUKAUSI +THAIYEAR = THAI.VUOSI +TIME = AIKA +TIMEVALUE = AIKA_ARVO +TODAY = TÄMÄ.PÄIVÄ +WEEKDAY = VIIKONPÄIVÄ +WEEKNUM = VIIKKO.NRO +WORKDAY = TYÖPÄIVÄ +WORKDAY.INTL = TYÖPÄIVÄ.KANSVÄL +YEAR = VUOSI +YEARFRAC = VUOSI.OSA + +## +## Tekniset funktiot (Engineering Functions) +## +BESSELI = BESSELI +BESSELJ = BESSELJ +BESSELK = BESSELK +BESSELY = BESSELY +BIN2DEC = BINDES +BIN2HEX = BINHEKSA +BIN2OCT = BINOKT +BITAND = BITTI.JA +BITLSHIFT = BITTI.SIIRTO.V +BITOR = BITTI.TAI +BITRSHIFT = BITTI.SIIRTO.O +BITXOR = BITTI.EHDOTON.TAI +COMPLEX = KOMPLEKSI +CONVERT = MUUNNA +DEC2BIN = DESBIN +DEC2HEX = DESHEKSA +DEC2OCT = DESOKT +DELTA = SAMA.ARVO +ERF = VIRHEFUNKTIO +ERF.PRECISE = VIRHEFUNKTIO.TARKKA +ERFC = VIRHEFUNKTIO.KOMPLEMENTTI +ERFC.PRECISE = VIRHEFUNKTIO.KOMPLEMENTTI.TARKKA +GESTEP = RAJA +HEX2BIN = HEKSABIN +HEX2DEC = HEKSADES +HEX2OCT = HEKSAOKT +IMABS = KOMPLEKSI.ABS +IMAGINARY = KOMPLEKSI.IMAG +IMARGUMENT = KOMPLEKSI.ARG +IMCONJUGATE = KOMPLEKSI.KONJ +IMCOS = KOMPLEKSI.COS +IMCOSH = IMCOSH +IMCOT = KOMPLEKSI.COT +IMCSC = KOMPLEKSI.KOSEK +IMCSCH = KOMPLEKSI.KOSEKH +IMDIV = KOMPLEKSI.OSAM +IMEXP = KOMPLEKSI.EKSP +IMLN = KOMPLEKSI.LN +IMLOG10 = KOMPLEKSI.LOG10 +IMLOG2 = KOMPLEKSI.LOG2 +IMPOWER = KOMPLEKSI.POT +IMPRODUCT = KOMPLEKSI.TULO +IMREAL = KOMPLEKSI.REAALI +IMSEC = KOMPLEKSI.SEK +IMSECH = KOMPLEKSI.SEKH +IMSIN = KOMPLEKSI.SIN +IMSINH = KOMPLEKSI.SINH +IMSQRT = KOMPLEKSI.NELIÖJ +IMSUB = KOMPLEKSI.EROTUS +IMSUM = KOMPLEKSI.SUM +IMTAN = KOMPLEKSI.TAN +OCT2BIN = OKTBIN +OCT2DEC = OKTDES +OCT2HEX = OKTHEKSA + +## +## Rahoitusfunktiot (Financial Functions) +## +ACCRINT = KERTYNYT.KORKO +ACCRINTM = KERTYNYT.KORKO.LOPUSSA +AMORDEGRC = AMORDEGRC +AMORLINC = AMORLINC +COUPDAYBS = KORKOPÄIVÄT.ALUSTA +COUPDAYS = KORKOPÄIVÄT +COUPDAYSNC = KORKOPÄIVÄT.SEURAAVA +COUPNCD = KORKOPÄIVÄ.SEURAAVA +COUPNUM = KORKOPÄIVÄ.JAKSOT +COUPPCD = KORKOPÄIVÄ.EDELLINEN +CUMIPMT = MAKSETTU.KORKO +CUMPRINC = MAKSETTU.LYHENNYS +DB = DB +DDB = DDB +DISC = DISKONTTOKORKO +DOLLARDE = VALUUTTA.DES +DOLLARFR = VALUUTTA.MURTO +DURATION = KESTO +EFFECT = KORKO.EFEKT +FV = TULEVA.ARVO +FVSCHEDULE = TULEVA.ARVO.ERIKORKO +INTRATE = KORKO.ARVOPAPERI +IPMT = IPMT +IRR = SISÄINEN.KORKO +ISPMT = ISPMT +MDURATION = KESTO.MUUNN +MIRR = MSISÄINEN +NOMINAL = KORKO.VUOSI +NPER = NJAKSO +NPV = NNA +ODDFPRICE = PARITON.ENS.NIMELLISARVO +ODDFYIELD = PARITON.ENS.TUOTTO +ODDLPRICE = PARITON.VIIM.NIMELLISARVO +ODDLYIELD = PARITON.VIIM.TUOTTO +PDURATION = KESTO.JAKSO +PMT = MAKSU +PPMT = PPMT +PRICE = HINTA +PRICEDISC = HINTA.DISK +PRICEMAT = HINTA.LUNASTUS +PV = NA +RATE = KORKO +RECEIVED = SAATU.HINTA +RRI = TOT.ROI +SLN = STP +SYD = VUOSIPOISTO +TBILLEQ = OBLIG.TUOTTOPROS +TBILLPRICE = OBLIG.HINTA +TBILLYIELD = OBLIG.TUOTTO +VDB = VDB +XIRR = SISÄINEN.KORKO.JAKSOTON +XNPV = NNA.JAKSOTON +YIELD = TUOTTO +YIELDDISC = TUOTTO.DISK +YIELDMAT = TUOTTO.ERÄP + +## +## Tietofunktiot (Information Functions) +## +CELL = SOLU +ERROR.TYPE = VIRHEEN.LAJI +INFO = KUVAUS +ISBLANK = ONTYHJÄ +ISERR = ONVIRH +ISERROR = ONVIRHE +ISEVEN = ONPARILLINEN +ISFORMULA = ONKAAVA +ISLOGICAL = ONTOTUUS +ISNA = ONPUUTTUU +ISNONTEXT = ONEI_TEKSTI +ISNUMBER = ONLUKU +ISODD = ONPARITON +ISREF = ONVIITT +ISTEXT = ONTEKSTI +N = N +NA = PUUTTUU +SHEET = TAULUKKO +SHEETS = TAULUKOT +TYPE = TYYPPI + +## +## Loogiset funktiot (Logical Functions) +## +AND = JA +FALSE = EPÄTOSI +IF = JOS +IFERROR = JOSVIRHE +IFNA = JOSPUUTTUU +IFS = JOSS +NOT = EI +OR = TAI +SWITCH = MUUTA +TRUE = TOSI +XOR = EHDOTON.TAI + +## +## Haku- ja viitefunktiot (Lookup & Reference Functions) +## +ADDRESS = OSOITE +AREAS = ALUEET +CHOOSE = VALITSE.INDEKSI +COLUMN = SARAKE +COLUMNS = SARAKKEET +FORMULATEXT = KAAVA.TEKSTI +GETPIVOTDATA = NOUDA.PIVOT.TIEDOT +HLOOKUP = VHAKU +HYPERLINK = HYPERLINKKI +INDEX = INDEKSI +INDIRECT = EPÄSUORA +LOOKUP = HAKU +MATCH = VASTINE +OFFSET = SIIRTYMÄ +ROW = RIVI +ROWS = RIVIT +RTD = RTD +TRANSPOSE = TRANSPONOI +VLOOKUP = PHAKU + +## +## Matemaattiset ja trigonometriset funktiot (Math & Trig Functions) +## +ABS = ITSEISARVO +ACOS = ACOS +ACOSH = ACOSH +ACOT = ACOT +ACOTH = ACOTH +AGGREGATE = KOOSTE +ARABIC = ARABIA +ASIN = ASIN +ASINH = ASINH +ATAN = ATAN +ATAN2 = ATAN2 +ATANH = ATANH +BASE = PERUS +CEILING.MATH = PYÖRISTÄ.KERR.YLÖS.MATEMAATTINEN +CEILING.PRECISE = PYÖRISTÄ.KERR.YLÖS.TARKKA +COMBIN = KOMBINAATIO +COMBINA = KOMBINAATIOA +COS = COS +COSH = COSH +COT = COT +COTH = COTH +CSC = KOSEK +CSCH = KOSEKH +DECIMAL = DESIMAALI +DEGREES = ASTEET +ECMA.CEILING = ECMA.PYÖRISTÄ.KERR.YLÖS +EVEN = PARILLINEN +EXP = EKSPONENTTI +FACT = KERTOMA +FACTDOUBLE = KERTOMA.OSA +FLOOR.MATH = PYÖRISTÄ.KERR.ALAS.MATEMAATTINEN +FLOOR.PRECISE = PYÖRISTÄ.KERR.ALAS.TARKKA +GCD = SUURIN.YHT.TEKIJÄ +INT = KOKONAISLUKU +ISO.CEILING = ISO.PYÖRISTÄ.KERR.YLÖS +LCM = PIENIN.YHT.JAETTAVA +LN = LUONNLOG +LOG = LOG +LOG10 = LOG10 +MDETERM = MDETERM +MINVERSE = MKÄÄNTEINEN +MMULT = MKERRO +MOD = JAKOJ +MROUND = PYÖRISTÄ.KERR +MULTINOMIAL = MULTINOMI +MUNIT = YKSIKKÖM +ODD = PARITON +PI = PII +POWER = POTENSSI +PRODUCT = TULO +QUOTIENT = OSAMÄÄRÄ +RADIANS = RADIAANIT +RAND = SATUNNAISLUKU +RANDBETWEEN = SATUNNAISLUKU.VÄLILTÄ +ROMAN = ROMAN +ROUND = PYÖRISTÄ +ROUNDBAHTDOWN = PYÖRISTÄ.BAHT.ALAS +ROUNDBAHTUP = PYÖRISTÄ.BAHT.YLÖS +ROUNDDOWN = PYÖRISTÄ.DES.ALAS +ROUNDUP = PYÖRISTÄ.DES.YLÖS +SEC = SEK +SECH = SEKH +SERIESSUM = SARJA.SUMMA +SIGN = ETUMERKKI +SIN = SIN +SINH = SINH +SQRT = NELIÖJUURI +SQRTPI = NELIÖJUURI.PII +SUBTOTAL = VÄLISUMMA +SUM = SUMMA +SUMIF = SUMMA.JOS +SUMIFS = SUMMA.JOS.JOUKKO +SUMPRODUCT = TULOJEN.SUMMA +SUMSQ = NELIÖSUMMA +SUMX2MY2 = NELIÖSUMMIEN.EROTUS +SUMX2PY2 = NELIÖSUMMIEN.SUMMA +SUMXMY2 = EROTUSTEN.NELIÖSUMMA +TAN = TAN +TANH = TANH +TRUNC = KATKAISE + +## +## Tilastolliset funktiot (Statistical Functions) +## +AVEDEV = KESKIPOIKKEAMA +AVERAGE = KESKIARVO +AVERAGEA = KESKIARVOA +AVERAGEIF = KESKIARVO.JOS +AVERAGEIFS = KESKIARVO.JOS.JOUKKO +BETA.DIST = BEETA.JAKAUMA +BETA.INV = BEETA.KÄÄNT +BINOM.DIST = BINOMI.JAKAUMA +BINOM.DIST.RANGE = BINOMI.JAKAUMA.ALUE +BINOM.INV = BINOMIJAKAUMA.KÄÄNT +CHISQ.DIST = CHINELIÖ.JAKAUMA +CHISQ.DIST.RT = CHINELIÖ.JAKAUMA.OH +CHISQ.INV = CHINELIÖ.KÄÄNT +CHISQ.INV.RT = CHINELIÖ.KÄÄNT.OH +CHISQ.TEST = CHINELIÖ.TESTI +CONFIDENCE.NORM = LUOTTAMUSVÄLI.NORM +CONFIDENCE.T = LUOTTAMUSVÄLI.T +CORREL = KORRELAATIO +COUNT = LASKE +COUNTA = LASKE.A +COUNTBLANK = LASKE.TYHJÄT +COUNTIF = LASKE.JOS +COUNTIFS = LASKE.JOS.JOUKKO +COVARIANCE.P = KOVARIANSSI.P +COVARIANCE.S = KOVARIANSSI.S +DEVSQ = OIKAISTU.NELIÖSUMMA +EXPON.DIST = EKSPONENTIAALI.JAKAUMA +F.DIST = F.JAKAUMA +F.DIST.RT = F.JAKAUMA.OH +F.INV = F.KÄÄNT +F.INV.RT = F.KÄÄNT.OH +F.TEST = F.TESTI +FISHER = FISHER +FISHERINV = FISHER.KÄÄNT +FORECAST.ETS = ENNUSTE.ETS +FORECAST.ETS.CONFINT = ENNUSTE.ETS.CONFINT +FORECAST.ETS.SEASONALITY = ENNUSTE.ETS.KAUSIVAIHTELU +FORECAST.ETS.STAT = ENNUSTE.ETS.STAT +FORECAST.LINEAR = ENNUSTE.LINEAARINEN +FREQUENCY = TAAJUUS +GAMMA = GAMMA +GAMMA.DIST = GAMMA.JAKAUMA +GAMMA.INV = GAMMA.JAKAUMA.KÄÄNT +GAMMALN = GAMMALN +GAMMALN.PRECISE = GAMMALN.TARKKA +GAUSS = GAUSS +GEOMEAN = KESKIARVO.GEOM +GROWTH = KASVU +HARMEAN = KESKIARVO.HARM +HYPGEOM.DIST = HYPERGEOM_JAKAUMA +INTERCEPT = LEIKKAUSPISTE +KURT = KURT +LARGE = SUURI +LINEST = LINREGR +LOGEST = LOGREGR +LOGNORM.DIST = LOGNORM_JAKAUMA +LOGNORM.INV = LOGNORM.KÄÄNT +MAX = MAKS +MAXA = MAKSA +MAXIFS = MAKS.JOS +MEDIAN = MEDIAANI +MIN = MIN +MINA = MINA +MINIFS = MIN.JOS +MODE.MULT = MOODI.USEA +MODE.SNGL = MOODI.YKSI +NEGBINOM.DIST = BINOMI.JAKAUMA.NEG +NORM.DIST = NORMAALI.JAKAUMA +NORM.INV = NORMAALI.JAKAUMA.KÄÄNT +NORM.S.DIST = NORM_JAKAUMA.NORMIT +NORM.S.INV = NORM_JAKAUMA.KÄÄNT +PEARSON = PEARSON +PERCENTILE.EXC = PROSENTTIPISTE.ULK +PERCENTILE.INC = PROSENTTIPISTE.SIS +PERCENTRANK.EXC = PROSENTTIJÄRJESTYS.ULK +PERCENTRANK.INC = PROSENTTIJÄRJESTYS.SIS +PERMUT = PERMUTAATIO +PERMUTATIONA = PERMUTAATIOA +PHI = FII +POISSON.DIST = POISSON.JAKAUMA +PROB = TODENNÄKÖISYYS +QUARTILE.EXC = NELJÄNNES.ULK +QUARTILE.INC = NELJÄNNES.SIS +RANK.AVG = ARVON.MUKAAN.KESKIARVO +RANK.EQ = ARVON.MUKAAN.TASAN +RSQ = PEARSON.NELIÖ +SKEW = JAKAUMAN.VINOUS +SKEW.P = JAKAUMAN.VINOUS.POP +SLOPE = KULMAKERROIN +SMALL = PIENI +STANDARDIZE = NORMITA +STDEV.P = KESKIHAJONTA.P +STDEV.S = KESKIHAJONTA.S +STDEVA = KESKIHAJONTAA +STDEVPA = KESKIHAJONTAPA +STEYX = KESKIVIRHE +T.DIST = T.JAKAUMA +T.DIST.2T = T.JAKAUMA.2S +T.DIST.RT = T.JAKAUMA.OH +T.INV = T.KÄÄNT +T.INV.2T = T.KÄÄNT.2S +T.TEST = T.TESTI +TREND = SUUNTAUS +TRIMMEAN = KESKIARVO.TASATTU +VAR.P = VAR.P +VAR.S = VAR.S +VARA = VARA +VARPA = VARPA +WEIBULL.DIST = WEIBULL.JAKAUMA +Z.TEST = Z.TESTI + +## +## Tekstifunktiot (Text Functions) +## +BAHTTEXT = BAHTTEKSTI +CHAR = MERKKI +CLEAN = SIIVOA +CODE = KOODI +CONCAT = YHDISTÄ +DOLLAR = VALUUTTA +EXACT = VERTAA +FIND = ETSI +FIXED = KIINTEÄ +ISTHAIDIGIT = ON.THAI.NUMERO +LEFT = VASEN +LEN = PITUUS +LOWER = PIENET +MID = POIMI.TEKSTI +NUMBERSTRING = NROMERKKIJONO +NUMBERVALUE = NROARVO +PHONETIC = FONEETTINEN +PROPER = ERISNIMI +REPLACE = KORVAA +REPT = TOISTA +RIGHT = OIKEA +SEARCH = KÄY.LÄPI +SUBSTITUTE = VAIHDA +T = T +TEXT = TEKSTI +TEXTJOIN = TEKSTI.YHDISTÄ +THAIDIGIT = THAI.NUMERO +THAINUMSOUND = THAI.LUKU.ÄÄNI +THAINUMSTRING = THAI.LUKU.MERKKIJONO +THAISTRINGLENGTH = THAI.MERKKIJONON.PITUUS +TRIM = POISTA.VÄLIT +UNICHAR = UNICODEMERKKI +UNICODE = UNICODE +UPPER = ISOT +VALUE = ARVO + +## +## Verkkofunktiot (Web Functions) +## +ENCODEURL = URLKOODAUS +FILTERXML = SUODATA.XML +WEBSERVICE = VERKKOPALVELU + +## +## Yhteensopivuusfunktiot (Compatibility Functions) +## +BETADIST = BEETAJAKAUMA +BETAINV = BEETAJAKAUMA.KÄÄNT +BINOMDIST = BINOMIJAKAUMA +CEILING = PYÖRISTÄ.KERR.YLÖS +CHIDIST = CHIJAKAUMA +CHIINV = CHIJAKAUMA.KÄÄNT +CHITEST = CHITESTI +CONCATENATE = KETJUTA +CONFIDENCE = LUOTTAMUSVÄLI +COVAR = KOVARIANSSI +CRITBINOM = BINOMIJAKAUMA.KRIT +EXPONDIST = EKSPONENTIAALIJAKAUMA +FDIST = FJAKAUMA +FINV = FJAKAUMA.KÄÄNT +FLOOR = PYÖRISTÄ.KERR.ALAS +FORECAST = ENNUSTE +FTEST = FTESTI +GAMMADIST = GAMMAJAKAUMA +GAMMAINV = GAMMAJAKAUMA.KÄÄNT +HYPGEOMDIST = HYPERGEOM.JAKAUMA +LOGINV = LOGNORM.JAKAUMA.KÄÄNT +LOGNORMDIST = LOGNORM.JAKAUMA +MODE = MOODI +NEGBINOMDIST = BINOMIJAKAUMA.NEG +NORMDIST = NORM.JAKAUMA +NORMINV = NORM.JAKAUMA.KÄÄNT +NORMSDIST = NORM.JAKAUMA.NORMIT +NORMSINV = NORM.JAKAUMA.NORMIT.KÄÄNT +PERCENTILE = PROSENTTIPISTE +PERCENTRANK = PROSENTTIJÄRJESTYS +POISSON = POISSON +QUARTILE = NELJÄNNES +RANK = ARVON.MUKAAN +STDEV = KESKIHAJONTA +STDEVP = KESKIHAJONTAP +TDIST = TJAKAUMA +TINV = TJAKAUMA.KÄÄNT +TTEST = TTESTI +VAR = VAR +VARP = VARP +WEIBULL = WEIBULL +ZTEST = ZTESTI diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fr/config b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fr/config new file mode 100644 index 0000000..bdac412 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fr/config @@ -0,0 +1,20 @@ +############################################################ +## +## PhpSpreadsheet - locale settings +## +## Français (French) +## +############################################################ + +ArgumentSeparator = ; + +## +## Error Codes +## +NULL = #NUL! +DIV0 +VALUE = #VALEUR! +REF +NAME = #NOM? +NUM = #NOMBRE! +NA diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fr/functions b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fr/functions new file mode 100644 index 0000000..78b603e --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fr/functions @@ -0,0 +1,524 @@ +############################################################ +## +## PhpSpreadsheet - function name translations +## +## Français (French) +## +############################################################ + + +## +## Fonctions Cube (Cube Functions) +## +CUBEKPIMEMBER = MEMBREKPICUBE +CUBEMEMBER = MEMBRECUBE +CUBEMEMBERPROPERTY = PROPRIETEMEMBRECUBE +CUBERANKEDMEMBER = RANGMEMBRECUBE +CUBESET = JEUCUBE +CUBESETCOUNT = NBJEUCUBE +CUBEVALUE = VALEURCUBE + +## +## Fonctions de base de données (Database Functions) +## +DAVERAGE = BDMOYENNE +DCOUNT = BDNB +DCOUNTA = BDNBVAL +DGET = BDLIRE +DMAX = BDMAX +DMIN = BDMIN +DPRODUCT = BDPRODUIT +DSTDEV = BDECARTYPE +DSTDEVP = BDECARTYPEP +DSUM = BDSOMME +DVAR = BDVAR +DVARP = BDVARP + +## +## Fonctions de date et d’heure (Date & Time Functions) +## +DATE = DATE +DATEVALUE = DATEVAL +DAY = JOUR +DAYS = JOURS +DAYS360 = JOURS360 +EDATE = MOIS.DECALER +EOMONTH = FIN.MOIS +HOUR = HEURE +ISOWEEKNUM = NO.SEMAINE.ISO +MINUTE = MINUTE +MONTH = MOIS +NETWORKDAYS = NB.JOURS.OUVRES +NETWORKDAYS.INTL = NB.JOURS.OUVRES.INTL +NOW = MAINTENANT +SECOND = SECONDE +TIME = TEMPS +TIMEVALUE = TEMPSVAL +TODAY = AUJOURDHUI +WEEKDAY = JOURSEM +WEEKNUM = NO.SEMAINE +WORKDAY = SERIE.JOUR.OUVRE +WORKDAY.INTL = SERIE.JOUR.OUVRE.INTL +YEAR = ANNEE +YEARFRAC = FRACTION.ANNEE + +## +## Fonctions d’ingénierie (Engineering Functions) +## +BESSELI = BESSELI +BESSELJ = BESSELJ +BESSELK = BESSELK +BESSELY = BESSELY +BIN2DEC = BINDEC +BIN2HEX = BINHEX +BIN2OCT = BINOCT +BITAND = BITET +BITLSHIFT = BITDECALG +BITOR = BITOU +BITRSHIFT = BITDECALD +BITXOR = BITOUEXCLUSIF +COMPLEX = COMPLEXE +CONVERT = CONVERT +DEC2BIN = DECBIN +DEC2HEX = DECHEX +DEC2OCT = DECOCT +DELTA = DELTA +ERF = ERF +ERF.PRECISE = ERF.PRECIS +ERFC = ERFC +ERFC.PRECISE = ERFC.PRECIS +GESTEP = SUP.SEUIL +HEX2BIN = HEXBIN +HEX2DEC = HEXDEC +HEX2OCT = HEXOCT +IMABS = COMPLEXE.MODULE +IMAGINARY = COMPLEXE.IMAGINAIRE +IMARGUMENT = COMPLEXE.ARGUMENT +IMCONJUGATE = COMPLEXE.CONJUGUE +IMCOS = COMPLEXE.COS +IMCOSH = COMPLEXE.COSH +IMCOT = COMPLEXE.COT +IMCSC = COMPLEXE.CSC +IMCSCH = COMPLEXE.CSCH +IMDIV = COMPLEXE.DIV +IMEXP = COMPLEXE.EXP +IMLN = COMPLEXE.LN +IMLOG10 = COMPLEXE.LOG10 +IMLOG2 = COMPLEXE.LOG2 +IMPOWER = COMPLEXE.PUISSANCE +IMPRODUCT = COMPLEXE.PRODUIT +IMREAL = COMPLEXE.REEL +IMSEC = COMPLEXE.SEC +IMSECH = COMPLEXE.SECH +IMSIN = COMPLEXE.SIN +IMSINH = COMPLEXE.SINH +IMSQRT = COMPLEXE.RACINE +IMSUB = COMPLEXE.DIFFERENCE +IMSUM = COMPLEXE.SOMME +IMTAN = COMPLEXE.TAN +OCT2BIN = OCTBIN +OCT2DEC = OCTDEC +OCT2HEX = OCTHEX + +## +## Fonctions financières (Financial Functions) +## +ACCRINT = INTERET.ACC +ACCRINTM = INTERET.ACC.MAT +AMORDEGRC = AMORDEGRC +AMORLINC = AMORLINC +COUPDAYBS = NB.JOURS.COUPON.PREC +COUPDAYS = NB.JOURS.COUPONS +COUPDAYSNC = NB.JOURS.COUPON.SUIV +COUPNCD = DATE.COUPON.SUIV +COUPNUM = NB.COUPONS +COUPPCD = DATE.COUPON.PREC +CUMIPMT = CUMUL.INTER +CUMPRINC = CUMUL.PRINCPER +DB = DB +DDB = DDB +DISC = TAUX.ESCOMPTE +DOLLARDE = PRIX.DEC +DOLLARFR = PRIX.FRAC +DURATION = DUREE +EFFECT = TAUX.EFFECTIF +FV = VC +FVSCHEDULE = VC.PAIEMENTS +INTRATE = TAUX.INTERET +IPMT = INTPER +IRR = TRI +ISPMT = ISPMT +MDURATION = DUREE.MODIFIEE +MIRR = TRIM +NOMINAL = TAUX.NOMINAL +NPER = NPM +NPV = VAN +ODDFPRICE = PRIX.PCOUPON.IRREG +ODDFYIELD = REND.PCOUPON.IRREG +ODDLPRICE = PRIX.DCOUPON.IRREG +ODDLYIELD = REND.DCOUPON.IRREG +PDURATION = PDUREE +PMT = VPM +PPMT = PRINCPER +PRICE = PRIX.TITRE +PRICEDISC = VALEUR.ENCAISSEMENT +PRICEMAT = PRIX.TITRE.ECHEANCE +PV = VA +RATE = TAUX +RECEIVED = VALEUR.NOMINALE +RRI = TAUX.INT.EQUIV +SLN = AMORLIN +SYD = SYD +TBILLEQ = TAUX.ESCOMPTE.R +TBILLPRICE = PRIX.BON.TRESOR +TBILLYIELD = RENDEMENT.BON.TRESOR +VDB = VDB +XIRR = TRI.PAIEMENTS +XNPV = VAN.PAIEMENTS +YIELD = RENDEMENT.TITRE +YIELDDISC = RENDEMENT.SIMPLE +YIELDMAT = RENDEMENT.TITRE.ECHEANCE + +## +## Fonctions d’information (Information Functions) +## +CELL = CELLULE +ERROR.TYPE = TYPE.ERREUR +INFO = INFORMATIONS +ISBLANK = ESTVIDE +ISERR = ESTERR +ISERROR = ESTERREUR +ISEVEN = EST.PAIR +ISFORMULA = ESTFORMULE +ISLOGICAL = ESTLOGIQUE +ISNA = ESTNA +ISNONTEXT = ESTNONTEXTE +ISNUMBER = ESTNUM +ISODD = EST.IMPAIR +ISREF = ESTREF +ISTEXT = ESTTEXTE +N = N +NA = NA +SHEET = FEUILLE +SHEETS = FEUILLES +TYPE = TYPE + +## +## Fonctions logiques (Logical Functions) +## +AND = ET +FALSE = FAUX +IF = SI +IFERROR = SIERREUR +IFNA = SI.NON.DISP +IFS = SI.CONDITIONS +NOT = NON +OR = OU +SWITCH = SI.MULTIPLE +TRUE = VRAI +XOR = OUX + +## +## Fonctions de recherche et de référence (Lookup & Reference Functions) +## +ADDRESS = ADRESSE +AREAS = ZONES +CHOOSE = CHOISIR +COLUMN = COLONNE +COLUMNS = COLONNES +FORMULATEXT = FORMULETEXTE +GETPIVOTDATA = LIREDONNEESTABCROISDYNAMIQUE +HLOOKUP = RECHERCHEH +HYPERLINK = LIEN_HYPERTEXTE +INDEX = INDEX +INDIRECT = INDIRECT +LOOKUP = RECHERCHE +MATCH = EQUIV +OFFSET = DECALER +ROW = LIGNE +ROWS = LIGNES +RTD = RTD +TRANSPOSE = TRANSPOSE +VLOOKUP = RECHERCHEV + +## +## Fonctions mathématiques et trigonométriques (Math & Trig Functions) +## +ABS = ABS +ACOS = ACOS +ACOSH = ACOSH +ACOT = ACOT +ACOTH = ACOTH +AGGREGATE = AGREGAT +ARABIC = CHIFFRE.ARABE +ASIN = ASIN +ASINH = ASINH +ATAN = ATAN +ATAN2 = ATAN2 +ATANH = ATANH +BASE = BASE +CEILING.MATH = PLAFOND.MATH +CEILING.PRECISE = PLAFOND.PRECIS +COMBIN = COMBIN +COMBINA = COMBINA +COS = COS +COSH = COSH +COT = COT +COTH = COTH +CSC = CSC +CSCH = CSCH +DECIMAL = DECIMAL +DEGREES = DEGRES +ECMA.CEILING = ECMA.PLAFOND +EVEN = PAIR +EXP = EXP +FACT = FACT +FACTDOUBLE = FACTDOUBLE +FLOOR.MATH = PLANCHER.MATH +FLOOR.PRECISE = PLANCHER.PRECIS +GCD = PGCD +INT = ENT +ISO.CEILING = ISO.PLAFOND +LCM = PPCM +LN = LN +LOG = LOG +LOG10 = LOG10 +MDETERM = DETERMAT +MINVERSE = INVERSEMAT +MMULT = PRODUITMAT +MOD = MOD +MROUND = ARRONDI.AU.MULTIPLE +MULTINOMIAL = MULTINOMIALE +MUNIT = MATRICE.UNITAIRE +ODD = IMPAIR +PI = PI +POWER = PUISSANCE +PRODUCT = PRODUIT +QUOTIENT = QUOTIENT +RADIANS = RADIANS +RAND = ALEA +RANDBETWEEN = ALEA.ENTRE.BORNES +ROMAN = ROMAIN +ROUND = ARRONDI +ROUNDDOWN = ARRONDI.INF +ROUNDUP = ARRONDI.SUP +SEC = SEC +SECH = SECH +SERIESSUM = SOMME.SERIES +SIGN = SIGNE +SIN = SIN +SINH = SINH +SQRT = RACINE +SQRTPI = RACINE.PI +SUBTOTAL = SOUS.TOTAL +SUM = SOMME +SUMIF = SOMME.SI +SUMIFS = SOMME.SI.ENS +SUMPRODUCT = SOMMEPROD +SUMSQ = SOMME.CARRES +SUMX2MY2 = SOMME.X2MY2 +SUMX2PY2 = SOMME.X2PY2 +SUMXMY2 = SOMME.XMY2 +TAN = TAN +TANH = TANH +TRUNC = TRONQUE + +## +## Fonctions statistiques (Statistical Functions) +## +AVEDEV = ECART.MOYEN +AVERAGE = MOYENNE +AVERAGEA = AVERAGEA +AVERAGEIF = MOYENNE.SI +AVERAGEIFS = MOYENNE.SI.ENS +BETA.DIST = LOI.BETA.N +BETA.INV = BETA.INVERSE.N +BINOM.DIST = LOI.BINOMIALE.N +BINOM.DIST.RANGE = LOI.BINOMIALE.SERIE +BINOM.INV = LOI.BINOMIALE.INVERSE +CHISQ.DIST = LOI.KHIDEUX.N +CHISQ.DIST.RT = LOI.KHIDEUX.DROITE +CHISQ.INV = LOI.KHIDEUX.INVERSE +CHISQ.INV.RT = LOI.KHIDEUX.INVERSE.DROITE +CHISQ.TEST = CHISQ.TEST +CONFIDENCE.NORM = INTERVALLE.CONFIANCE.NORMAL +CONFIDENCE.T = INTERVALLE.CONFIANCE.STUDENT +CORREL = COEFFICIENT.CORRELATION +COUNT = NB +COUNTA = NBVAL +COUNTBLANK = NB.VIDE +COUNTIF = NB.SI +COUNTIFS = NB.SI.ENS +COVARIANCE.P = COVARIANCE.PEARSON +COVARIANCE.S = COVARIANCE.STANDARD +DEVSQ = SOMME.CARRES.ECARTS +EXPON.DIST = LOI.EXPONENTIELLE.N +F.DIST = LOI.F.N +F.DIST.RT = LOI.F.DROITE +F.INV = INVERSE.LOI.F.N +F.INV.RT = INVERSE.LOI.F.DROITE +F.TEST = F.TEST +FISHER = FISHER +FISHERINV = FISHER.INVERSE +FORECAST.ETS = PREVISION.ETS +FORECAST.ETS.CONFINT = PREVISION.ETS.CONFINT +FORECAST.ETS.SEASONALITY = PREVISION.ETS.CARACTERESAISONNIER +FORECAST.ETS.STAT = PREVISION.ETS.STAT +FORECAST.LINEAR = PREVISION.LINEAIRE +FREQUENCY = FREQUENCE +GAMMA = GAMMA +GAMMA.DIST = LOI.GAMMA.N +GAMMA.INV = LOI.GAMMA.INVERSE.N +GAMMALN = LNGAMMA +GAMMALN.PRECISE = LNGAMMA.PRECIS +GAUSS = GAUSS +GEOMEAN = MOYENNE.GEOMETRIQUE +GROWTH = CROISSANCE +HARMEAN = MOYENNE.HARMONIQUE +HYPGEOM.DIST = LOI.HYPERGEOMETRIQUE.N +INTERCEPT = ORDONNEE.ORIGINE +KURT = KURTOSIS +LARGE = GRANDE.VALEUR +LINEST = DROITEREG +LOGEST = LOGREG +LOGNORM.DIST = LOI.LOGNORMALE.N +LOGNORM.INV = LOI.LOGNORMALE.INVERSE.N +MAX = MAX +MAXA = MAXA +MAXIFS = MAX.SI +MEDIAN = MEDIANE +MIN = MIN +MINA = MINA +MINIFS = MIN.SI +MODE.MULT = MODE.MULTIPLE +MODE.SNGL = MODE.SIMPLE +NEGBINOM.DIST = LOI.BINOMIALE.NEG.N +NORM.DIST = LOI.NORMALE.N +NORM.INV = LOI.NORMALE.INVERSE.N +NORM.S.DIST = LOI.NORMALE.STANDARD.N +NORM.S.INV = LOI.NORMALE.STANDARD.INVERSE.N +PEARSON = PEARSON +PERCENTILE.EXC = CENTILE.EXCLURE +PERCENTILE.INC = CENTILE.INCLURE +PERCENTRANK.EXC = RANG.POURCENTAGE.EXCLURE +PERCENTRANK.INC = RANG.POURCENTAGE.INCLURE +PERMUT = PERMUTATION +PERMUTATIONA = PERMUTATIONA +PHI = PHI +POISSON.DIST = LOI.POISSON.N +PROB = PROBABILITE +QUARTILE.EXC = QUARTILE.EXCLURE +QUARTILE.INC = QUARTILE.INCLURE +RANK.AVG = MOYENNE.RANG +RANK.EQ = EQUATION.RANG +RSQ = COEFFICIENT.DETERMINATION +SKEW = COEFFICIENT.ASYMETRIE +SKEW.P = COEFFICIENT.ASYMETRIE.P +SLOPE = PENTE +SMALL = PETITE.VALEUR +STANDARDIZE = CENTREE.REDUITE +STDEV.P = ECARTYPE.PEARSON +STDEV.S = ECARTYPE.STANDARD +STDEVA = STDEVA +STDEVPA = STDEVPA +STEYX = ERREUR.TYPE.XY +T.DIST = LOI.STUDENT.N +T.DIST.2T = LOI.STUDENT.BILATERALE +T.DIST.RT = LOI.STUDENT.DROITE +T.INV = LOI.STUDENT.INVERSE.N +T.INV.2T = LOI.STUDENT.INVERSE.BILATERALE +T.TEST = T.TEST +TREND = TENDANCE +TRIMMEAN = MOYENNE.REDUITE +VAR.P = VAR.P.N +VAR.S = VAR.S +VARA = VARA +VARPA = VARPA +WEIBULL.DIST = LOI.WEIBULL.N +Z.TEST = Z.TEST + +## +## Fonctions de texte (Text Functions) +## +BAHTTEXT = BAHTTEXT +CHAR = CAR +CLEAN = EPURAGE +CODE = CODE +CONCAT = CONCAT +DOLLAR = DEVISE +EXACT = EXACT +FIND = TROUVE +FIXED = CTXT +LEFT = GAUCHE +LEN = NBCAR +LOWER = MINUSCULE +MID = STXT +NUMBERVALUE = VALEURNOMBRE +PHONETIC = PHONETIQUE +PROPER = NOMPROPRE +REPLACE = REMPLACER +REPT = REPT +RIGHT = DROITE +SEARCH = CHERCHE +SUBSTITUTE = SUBSTITUE +T = T +TEXT = TEXTE +TEXTJOIN = JOINDRE.TEXTE +TRIM = SUPPRESPACE +UNICHAR = UNICAR +UNICODE = UNICODE +UPPER = MAJUSCULE +VALUE = CNUM + +## +## Fonctions web (Web Functions) +## +ENCODEURL = URLENCODAGE +FILTERXML = FILTRE.XML +WEBSERVICE = SERVICEWEB + +## +## Fonctions de compatibilité (Compatibility Functions) +## +BETADIST = LOI.BETA +BETAINV = BETA.INVERSE +BINOMDIST = LOI.BINOMIALE +CEILING = PLAFOND +CHIDIST = LOI.KHIDEUX +CHIINV = KHIDEUX.INVERSE +CHITEST = TEST.KHIDEUX +CONCATENATE = CONCATENER +CONFIDENCE = INTERVALLE.CONFIANCE +COVAR = COVARIANCE +CRITBINOM = CRITERE.LOI.BINOMIALE +EXPONDIST = LOI.EXPONENTIELLE +FDIST = LOI.F +FINV = INVERSE.LOI.F +FLOOR = PLANCHER +FORECAST = PREVISION +FTEST = TEST.F +GAMMADIST = LOI.GAMMA +GAMMAINV = LOI.GAMMA.INVERSE +HYPGEOMDIST = LOI.HYPERGEOMETRIQUE +LOGINV = LOI.LOGNORMALE.INVERSE +LOGNORMDIST = LOI.LOGNORMALE +MODE = MODE +NEGBINOMDIST = LOI.BINOMIALE.NEG +NORMDIST = LOI.NORMALE +NORMINV = LOI.NORMALE.INVERSE +NORMSDIST = LOI.NORMALE.STANDARD +NORMSINV = LOI.NORMALE.STANDARD.INVERSE +PERCENTILE = CENTILE +PERCENTRANK = RANG.POURCENTAGE +POISSON = LOI.POISSON +QUARTILE = QUARTILE +RANK = RANG +STDEV = ECARTYPE +STDEVP = ECARTYPEP +TDIST = LOI.STUDENT +TINV = LOI.STUDENT.INVERSE +TTEST = TEST.STUDENT +VAR = VAR +VARP = VAR.P +WEIBULL = LOI.WEIBULL +ZTEST = TEST.Z diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/hu/config b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/hu/config new file mode 100644 index 0000000..dc585d7 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/hu/config @@ -0,0 +1,20 @@ +############################################################ +## +## PhpSpreadsheet - locale settings +## +## Magyar (Hungarian) +## +############################################################ + +ArgumentSeparator = ; + +## +## Error Codes +## +NULL = #NULLA! +DIV0 = #ZÉRÓOSZTÓ! +VALUE = #ÉRTÉK! +REF = #HIV! +NAME = #NÉV? +NUM = #SZÁM! +NA = #HIÁNYZIK diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/hu/functions b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/hu/functions new file mode 100644 index 0000000..46b3012 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/hu/functions @@ -0,0 +1,537 @@ +############################################################ +## +## PhpSpreadsheet - function name translations +## +## Magyar (Hungarian) +## +############################################################ + + +## +## Kockafüggvények (Cube Functions) +## +CUBEKPIMEMBER = KOCKA.FŐTELJMUT +CUBEMEMBER = KOCKA.TAG +CUBEMEMBERPROPERTY = KOCKA.TAG.TUL +CUBERANKEDMEMBER = KOCKA.HALM.ELEM +CUBESET = KOCKA.HALM +CUBESETCOUNT = KOCKA.HALM.DB +CUBEVALUE = KOCKA.ÉRTÉK + +## +## Adatbázis-kezelő függvények (Database Functions) +## +DAVERAGE = AB.ÁTLAG +DCOUNT = AB.DARAB +DCOUNTA = AB.DARAB2 +DGET = AB.MEZŐ +DMAX = AB.MAX +DMIN = AB.MIN +DPRODUCT = AB.SZORZAT +DSTDEV = AB.SZÓRÁS +DSTDEVP = AB.SZÓRÁS2 +DSUM = AB.SZUM +DVAR = AB.VAR +DVARP = AB.VAR2 + +## +## Dátumfüggvények (Date & Time Functions) +## +DATE = DÁTUM +DATEDIF = DÁTUMTÓLIG +DATESTRING = DÁTUMSZÖVEG +DATEVALUE = DÁTUMÉRTÉK +DAY = NAP +DAYS = NAPOK +DAYS360 = NAP360 +EDATE = KALK.DÁTUM +EOMONTH = HÓNAP.UTOLSÓ.NAP +HOUR = ÓRA +ISOWEEKNUM = ISO.HÉT.SZÁMA +MINUTE = PERCEK +MONTH = HÓNAP +NETWORKDAYS = ÖSSZ.MUNKANAP +NETWORKDAYS.INTL = ÖSSZ.MUNKANAP.INTL +NOW = MOST +SECOND = MPERC +THAIDAYOFWEEK = THAIHÉTNAPJA +THAIMONTHOFYEAR = THAIHÓNAP +THAIYEAR = THAIÉV +TIME = IDŐ +TIMEVALUE = IDŐÉRTÉK +TODAY = MA +WEEKDAY = HÉT.NAPJA +WEEKNUM = HÉT.SZÁMA +WORKDAY = KALK.MUNKANAP +WORKDAY.INTL = KALK.MUNKANAP.INTL +YEAR = ÉV +YEARFRAC = TÖRTÉV + +## +## Mérnöki függvények (Engineering Functions) +## +BESSELI = BESSELI +BESSELJ = BESSELJ +BESSELK = BESSELK +BESSELY = BESSELY +BIN2DEC = BIN.DEC +BIN2HEX = BIN.HEX +BIN2OCT = BIN.OKT +BITAND = BIT.ÉS +BITLSHIFT = BIT.BAL.ELTOL +BITOR = BIT.VAGY +BITRSHIFT = BIT.JOBB.ELTOL +BITXOR = BIT.XVAGY +COMPLEX = KOMPLEX +CONVERT = KONVERTÁLÁS +DEC2BIN = DEC.BIN +DEC2HEX = DEC.HEX +DEC2OCT = DEC.OKT +DELTA = DELTA +ERF = HIBAF +ERF.PRECISE = HIBAF.PONTOS +ERFC = HIBAF.KOMPLEMENTER +ERFC.PRECISE = HIBAFKOMPLEMENTER.PONTOS +GESTEP = KÜSZÖBNÉL.NAGYOBB +HEX2BIN = HEX.BIN +HEX2DEC = HEX.DEC +HEX2OCT = HEX.OKT +IMABS = KÉPZ.ABSZ +IMAGINARY = KÉPZETES +IMARGUMENT = KÉPZ.ARGUMENT +IMCONJUGATE = KÉPZ.KONJUGÁLT +IMCOS = KÉPZ.COS +IMCOSH = KÉPZ.COSH +IMCOT = KÉPZ.COT +IMCSC = KÉPZ.CSC +IMCSCH = KÉPZ.CSCH +IMDIV = KÉPZ.HÁNYAD +IMEXP = KÉPZ.EXP +IMLN = KÉPZ.LN +IMLOG10 = KÉPZ.LOG10 +IMLOG2 = KÉPZ.LOG2 +IMPOWER = KÉPZ.HATV +IMPRODUCT = KÉPZ.SZORZAT +IMREAL = KÉPZ.VALÓS +IMSEC = KÉPZ.SEC +IMSECH = KÉPZ.SECH +IMSIN = KÉPZ.SIN +IMSINH = KÉPZ.SINH +IMSQRT = KÉPZ.GYÖK +IMSUB = KÉPZ.KÜL +IMSUM = KÉPZ.ÖSSZEG +IMTAN = KÉPZ.TAN +OCT2BIN = OKT.BIN +OCT2DEC = OKT.DEC +OCT2HEX = OKT.HEX + +## +## Pénzügyi függvények (Financial Functions) +## +ACCRINT = IDŐSZAKI.KAMAT +ACCRINTM = LEJÁRATI.KAMAT +AMORDEGRC = ÉRTÉKCSÖKK.TÉNYEZŐVEL +AMORLINC = ÉRTÉKCSÖKK +COUPDAYBS = SZELVÉNYIDŐ.KEZDETTŐL +COUPDAYS = SZELVÉNYIDŐ +COUPDAYSNC = SZELVÉNYIDŐ.KIFIZETÉSTŐL +COUPNCD = ELSŐ.SZELVÉNYDÁTUM +COUPNUM = SZELVÉNYSZÁM +COUPPCD = UTOLSÓ.SZELVÉNYDÁTUM +CUMIPMT = ÖSSZES.KAMAT +CUMPRINC = ÖSSZES.TŐKERÉSZ +DB = KCS2 +DDB = KCSA +DISC = LESZÁM +DOLLARDE = FORINT.DEC +DOLLARFR = FORINT.TÖRT +DURATION = KAMATÉRZ +EFFECT = TÉNYLEGES +FV = JBÉ +FVSCHEDULE = KJÉ +INTRATE = KAMATRÁTA +IPMT = RRÉSZLET +IRR = BMR +ISPMT = LRÉSZLETKAMAT +MDURATION = MKAMATÉRZ +MIRR = MEGTÉRÜLÉS +NOMINAL = NÉVLEGES +NPER = PER.SZÁM +NPV = NMÉ +ODDFPRICE = ELTÉRŐ.EÁR +ODDFYIELD = ELTÉRŐ.EHOZAM +ODDLPRICE = ELTÉRŐ.UÁR +ODDLYIELD = ELTÉRŐ.UHOZAM +PDURATION = KAMATÉRZ.PER +PMT = RÉSZLET +PPMT = PRÉSZLET +PRICE = ÁR +PRICEDISC = ÁR.LESZÁM +PRICEMAT = ÁR.LEJÁRAT +PV = MÉ +RATE = RÁTA +RECEIVED = KAPOTT +RRI = MR +SLN = LCSA +SYD = ÉSZÖ +TBILLEQ = KJEGY.EGYENÉRT +TBILLPRICE = KJEGY.ÁR +TBILLYIELD = KJEGY.HOZAM +VDB = ÉCSRI +XIRR = XBMR +XNPV = XNJÉ +YIELD = HOZAM +YIELDDISC = HOZAM.LESZÁM +YIELDMAT = HOZAM.LEJÁRAT + +## +## Információs függvények (Information Functions) +## +CELL = CELLA +ERROR.TYPE = HIBA.TÍPUS +INFO = INFÓ +ISBLANK = ÜRES +ISERR = HIBA.E +ISERROR = HIBÁS +ISEVEN = PÁROSE +ISFORMULA = KÉPLET +ISLOGICAL = LOGIKAI +ISNA = NINCS +ISNONTEXT = NEM.SZÖVEG +ISNUMBER = SZÁM +ISODD = PÁRATLANE +ISREF = HIVATKOZÁS +ISTEXT = SZÖVEG.E +N = S +NA = HIÁNYZIK +SHEET = LAP +SHEETS = LAPOK +TYPE = TÍPUS + +## +## Logikai függvények (Logical Functions) +## +AND = ÉS +FALSE = HAMIS +IF = HA +IFERROR = HAHIBA +IFNA = HAHIÁNYZIK +IFS = HAELSŐIGAZ +NOT = NEM +OR = VAGY +SWITCH = ÁTVÁLT +TRUE = IGAZ +XOR = XVAGY + +## +## Keresési és hivatkozási függvények (Lookup & Reference Functions) +## +ADDRESS = CÍM +AREAS = TERÜLET +CHOOSE = VÁLASZT +COLUMN = OSZLOP +COLUMNS = OSZLOPOK +FORMULATEXT = KÉPLETSZÖVEG +GETPIVOTDATA = KIMUTATÁSADATOT.VESZ +HLOOKUP = VKERES +HYPERLINK = HIPERHIVATKOZÁS +INDEX = INDEX +INDIRECT = INDIREKT +LOOKUP = KERES +MATCH = HOL.VAN +OFFSET = ELTOLÁS +ROW = SOR +ROWS = SOROK +RTD = VIA +TRANSPOSE = TRANSZPONÁLÁS +VLOOKUP = FKERES + +## +## Matematikai és trigonometrikus függvények (Math & Trig Functions) +## +ABS = ABS +ACOS = ARCCOS +ACOSH = ACOSH +ACOT = ARCCOT +ACOTH = ARCCOTH +AGGREGATE = ÖSSZESÍT +ARABIC = ARAB +ASIN = ARCSIN +ASINH = ASINH +ATAN = ARCTAN +ATAN2 = ARCTAN2 +ATANH = ATANH +BASE = ALAP +CEILING.MATH = PLAFON.MAT +CEILING.PRECISE = PLAFON.PONTOS +COMBIN = KOMBINÁCIÓK +COMBINA = KOMBINÁCIÓK.ISM +COS = COS +COSH = COSH +COT = COT +COTH = COTH +CSC = CSC +CSCH = CSCH +DECIMAL = TIZEDES +DEGREES = FOK +ECMA.CEILING = ECMA.PLAFON +EVEN = PÁROS +EXP = KITEVŐ +FACT = FAKT +FACTDOUBLE = FAKTDUPLA +FLOOR.MATH = PADLÓ.MAT +FLOOR.PRECISE = PADLÓ.PONTOS +GCD = LKO +INT = INT +ISO.CEILING = ISO.PLAFON +LCM = LKT +LN = LN +LOG = LOG +LOG10 = LOG10 +MDETERM = MDETERM +MINVERSE = INVERZ.MÁTRIX +MMULT = MSZORZAT +MOD = MARADÉK +MROUND = TÖBBSZ.KEREKÍT +MULTINOMIAL = SZORHÁNYFAKT +MUNIT = MMÁTRIX +ODD = PÁRATLAN +PI = PI +POWER = HATVÁNY +PRODUCT = SZORZAT +QUOTIENT = KVÓCIENS +RADIANS = RADIÁN +RAND = VÉL +RANDBETWEEN = VÉLETLEN.KÖZÖTT +ROMAN = RÓMAI +ROUND = KEREKÍTÉS +ROUNDBAHTDOWN = BAHTKEREK.LE +ROUNDBAHTUP = BAHTKEREK.FEL +ROUNDDOWN = KEREK.LE +ROUNDUP = KEREK.FEL +SEC = SEC +SECH = SECH +SERIESSUM = SORÖSSZEG +SIGN = ELŐJEL +SIN = SIN +SINH = SINH +SQRT = GYÖK +SQRTPI = GYÖKPI +SUBTOTAL = RÉSZÖSSZEG +SUM = SZUM +SUMIF = SZUMHA +SUMIFS = SZUMHATÖBB +SUMPRODUCT = SZORZATÖSSZEG +SUMSQ = NÉGYZETÖSSZEG +SUMX2MY2 = SZUMX2BŐLY2 +SUMX2PY2 = SZUMX2MEGY2 +SUMXMY2 = SZUMXBŐLY2 +TAN = TAN +TANH = TANH +TRUNC = CSONK + +## +## Statisztikai függvények (Statistical Functions) +## +AVEDEV = ÁTL.ELTÉRÉS +AVERAGE = ÁTLAG +AVERAGEA = ÁTLAGA +AVERAGEIF = ÁTLAGHA +AVERAGEIFS = ÁTLAGHATÖBB +BETA.DIST = BÉTA.ELOSZL +BETA.INV = BÉTA.INVERZ +BINOM.DIST = BINOM.ELOSZL +BINOM.DIST.RANGE = BINOM.ELOSZL.TART +BINOM.INV = BINOM.INVERZ +CHISQ.DIST = KHINÉGYZET.ELOSZLÁS +CHISQ.DIST.RT = KHINÉGYZET.ELOSZLÁS.JOBB +CHISQ.INV = KHINÉGYZET.INVERZ +CHISQ.INV.RT = KHINÉGYZET.INVERZ.JOBB +CHISQ.TEST = KHINÉGYZET.PRÓBA +CONFIDENCE.NORM = MEGBÍZHATÓSÁG.NORM +CONFIDENCE.T = MEGBÍZHATÓSÁG.T +CORREL = KORREL +COUNT = DARAB +COUNTA = DARAB2 +COUNTBLANK = DARABÜRES +COUNTIF = DARABTELI +COUNTIFS = DARABHATÖBB +COVARIANCE.P = KOVARIANCIA.S +COVARIANCE.S = KOVARIANCIA.M +DEVSQ = SQ +EXPON.DIST = EXP.ELOSZL +F.DIST = F.ELOSZL +F.DIST.RT = F.ELOSZLÁS.JOBB +F.INV = F.INVERZ +F.INV.RT = F.INVERZ.JOBB +F.TEST = F.PRÓB +FISHER = FISHER +FISHERINV = INVERZ.FISHER +FORECAST.ETS = ELŐREJELZÉS.ESIM +FORECAST.ETS.CONFINT = ELŐREJELZÉS.ESIM.KONFINT +FORECAST.ETS.SEASONALITY = ELŐREJELZÉS.ESIM.SZEZONALITÁS +FORECAST.ETS.STAT = ELŐREJELZÉS.ESIM.STAT +FORECAST.LINEAR = ELŐREJELZÉS.LINEÁRIS +FREQUENCY = GYAKORISÁG +GAMMA = GAMMA +GAMMA.DIST = GAMMA.ELOSZL +GAMMA.INV = GAMMA.INVERZ +GAMMALN = GAMMALN +GAMMALN.PRECISE = GAMMALN.PONTOS +GAUSS = GAUSS +GEOMEAN = MÉRTANI.KÖZÉP +GROWTH = NÖV +HARMEAN = HARM.KÖZÉP +HYPGEOM.DIST = HIPGEOM.ELOSZLÁS +INTERCEPT = METSZ +KURT = CSÚCSOSSÁG +LARGE = NAGY +LINEST = LIN.ILL +LOGEST = LOG.ILL +LOGNORM.DIST = LOGNORM.ELOSZLÁS +LOGNORM.INV = LOGNORM.INVERZ +MAX = MAX +MAXA = MAXA +MAXIFS = MAXHA +MEDIAN = MEDIÁN +MIN = MIN +MINA = MIN2 +MINIFS = MINHA +MODE.MULT = MÓDUSZ.TÖBB +MODE.SNGL = MÓDUSZ.EGY +NEGBINOM.DIST = NEGBINOM.ELOSZLÁS +NORM.DIST = NORM.ELOSZLÁS +NORM.INV = NORM.INVERZ +NORM.S.DIST = NORM.S.ELOSZLÁS +NORM.S.INV = NORM.S.INVERZ +PEARSON = PEARSON +PERCENTILE.EXC = PERCENTILIS.KIZÁR +PERCENTILE.INC = PERCENTILIS.TARTALMAZ +PERCENTRANK.EXC = SZÁZALÉKRANG.KIZÁR +PERCENTRANK.INC = SZÁZALÉKRANG.TARTALMAZ +PERMUT = VARIÁCIÓK +PERMUTATIONA = VARIÁCIÓK.ISM +PHI = FI +POISSON.DIST = POISSON.ELOSZLÁS +PROB = VALÓSZÍNŰSÉG +QUARTILE.EXC = KVARTILIS.KIZÁR +QUARTILE.INC = KVARTILIS.TARTALMAZ +RANK.AVG = RANG.ÁTL +RANK.EQ = RANG.EGY +RSQ = RNÉGYZET +SKEW = FERDESÉG +SKEW.P = FERDESÉG.P +SLOPE = MEREDEKSÉG +SMALL = KICSI +STANDARDIZE = NORMALIZÁLÁS +STDEV.P = SZÓR.S +STDEV.S = SZÓR.M +STDEVA = SZÓRÁSA +STDEVPA = SZÓRÁSPA +STEYX = STHIBAYX +T.DIST = T.ELOSZL +T.DIST.2T = T.ELOSZLÁS.2SZ +T.DIST.RT = T.ELOSZLÁS.JOBB +T.INV = T.INVERZ +T.INV.2T = T.INVERZ.2SZ +T.TEST = T.PRÓB +TREND = TREND +TRIMMEAN = RÉSZÁTLAG +VAR.P = VAR.S +VAR.S = VAR.M +VARA = VARA +VARPA = VARPA +WEIBULL.DIST = WEIBULL.ELOSZLÁS +Z.TEST = Z.PRÓB + +## +## Szövegműveletekhez használható függvények (Text Functions) +## +BAHTTEXT = BAHTSZÖVEG +CHAR = KARAKTER +CLEAN = TISZTÍT +CODE = KÓD +CONCAT = FŰZ +DOLLAR = FORINT +EXACT = AZONOS +FIND = SZÖVEG.TALÁL +FIXED = FIX +ISTHAIDIGIT = ON.THAI.NUMERO +LEFT = BAL +LEN = HOSSZ +LOWER = KISBETŰ +MID = KÖZÉP +NUMBERSTRING = SZÁM.BETŰVEL +NUMBERVALUE = SZÁMÉRTÉK +PHONETIC = FONETIKUS +PROPER = TNÉV +REPLACE = CSERE +REPT = SOKSZOR +RIGHT = JOBB +SEARCH = SZÖVEG.KERES +SUBSTITUTE = HELYETTE +T = T +TEXT = SZÖVEG +TEXTJOIN = SZÖVEGÖSSZEFŰZÉS +THAIDIGIT = THAISZÁM +THAINUMSOUND = THAISZÁMHANG +THAINUMSTRING = THAISZÁMKAR +THAISTRINGLENGTH = THAIKARHOSSZ +TRIM = KIMETSZ +UNICHAR = UNIKARAKTER +UNICODE = UNICODE +UPPER = NAGYBETŰS +VALUE = ÉRTÉK + +## +## Webes függvények (Web Functions) +## +ENCODEURL = URL.KÓDOL +FILTERXML = XMLSZŰRÉS +WEBSERVICE = WEBSZOLGÁLTATÁS + +## +## Kompatibilitási függvények (Compatibility Functions) +## +BETADIST = BÉTA.ELOSZLÁS +BETAINV = INVERZ.BÉTA +BINOMDIST = BINOM.ELOSZLÁS +CEILING = PLAFON +CHIDIST = KHI.ELOSZLÁS +CHIINV = INVERZ.KHI +CHITEST = KHI.PRÓBA +CONCATENATE = ÖSSZEFŰZ +CONFIDENCE = MEGBÍZHATÓSÁG +COVAR = KOVAR +CRITBINOM = KRITBINOM +EXPONDIST = EXP.ELOSZLÁS +FDIST = F.ELOSZLÁS +FINV = INVERZ.F +FLOOR = PADLÓ +FORECAST = ELŐREJELZÉS +FTEST = F.PRÓBA +GAMMADIST = GAMMA.ELOSZLÁS +GAMMAINV = INVERZ.GAMMA +HYPGEOMDIST = HIPERGEOM.ELOSZLÁS +LOGINV = INVERZ.LOG.ELOSZLÁS +LOGNORMDIST = LOG.ELOSZLÁS +MODE = MÓDUSZ +NEGBINOMDIST = NEGBINOM.ELOSZL +NORMDIST = NORM.ELOSZL +NORMINV = INVERZ.NORM +NORMSDIST = STNORMELOSZL +NORMSINV = INVERZ.STNORM +PERCENTILE = PERCENTILIS +PERCENTRANK = SZÁZALÉKRANG +POISSON = POISSON +QUARTILE = KVARTILIS +RANK = SORSZÁM +STDEV = SZÓRÁS +STDEVP = SZÓRÁSP +TDIST = T.ELOSZLÁS +TINV = INVERZ.T +TTEST = T.PRÓBA +VAR = VAR +VARP = VARP +WEIBULL = WEIBULL +ZTEST = Z.PRÓBA diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/it/config b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/it/config new file mode 100644 index 0000000..5c1e495 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/it/config @@ -0,0 +1,20 @@ +############################################################ +## +## PhpSpreadsheet - locale settings +## +## Italiano (Italian) +## +############################################################ + +ArgumentSeparator = ; + +## +## Error Codes +## +NULL +DIV0 +VALUE = #VALORE! +REF = #RIF! +NAME = #NOME? +NUM +NA = #N/D diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/it/functions b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/it/functions new file mode 100644 index 0000000..c14ed85 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/it/functions @@ -0,0 +1,537 @@ +############################################################ +## +## PhpSpreadsheet - function name translations +## +## Italiano (Italian) +## +############################################################ + + +## +## Funzioni cubo (Cube Functions) +## +CUBEKPIMEMBER = MEMBRO.KPI.CUBO +CUBEMEMBER = MEMBRO.CUBO +CUBEMEMBERPROPERTY = PROPRIETÀ.MEMBRO.CUBO +CUBERANKEDMEMBER = MEMBRO.CUBO.CON.RANGO +CUBESET = SET.CUBO +CUBESETCOUNT = CONTA.SET.CUBO +CUBEVALUE = VALORE.CUBO + +## +## Funzioni di database (Database Functions) +## +DAVERAGE = DB.MEDIA +DCOUNT = DB.CONTA.NUMERI +DCOUNTA = DB.CONTA.VALORI +DGET = DB.VALORI +DMAX = DB.MAX +DMIN = DB.MIN +DPRODUCT = DB.PRODOTTO +DSTDEV = DB.DEV.ST +DSTDEVP = DB.DEV.ST.POP +DSUM = DB.SOMMA +DVAR = DB.VAR +DVARP = DB.VAR.POP + +## +## Funzioni data e ora (Date & Time Functions) +## +DATE = DATA +DATEDIF = DATA.DIFF +DATESTRING = DATA.STRINGA +DATEVALUE = DATA.VALORE +DAY = GIORNO +DAYS = GIORNI +DAYS360 = GIORNO360 +EDATE = DATA.MESE +EOMONTH = FINE.MESE +HOUR = ORA +ISOWEEKNUM = NUM.SETTIMANA.ISO +MINUTE = MINUTO +MONTH = MESE +NETWORKDAYS = GIORNI.LAVORATIVI.TOT +NETWORKDAYS.INTL = GIORNI.LAVORATIVI.TOT.INTL +NOW = ADESSO +SECOND = SECONDO +THAIDAYOFWEEK = THAIGIORNODELLASETTIMANA +THAIMONTHOFYEAR = THAIMESEDELLANNO +THAIYEAR = THAIANNO +TIME = ORARIO +TIMEVALUE = ORARIO.VALORE +TODAY = OGGI +WEEKDAY = GIORNO.SETTIMANA +WEEKNUM = NUM.SETTIMANA +WORKDAY = GIORNO.LAVORATIVO +WORKDAY.INTL = GIORNO.LAVORATIVO.INTL +YEAR = ANNO +YEARFRAC = FRAZIONE.ANNO + +## +## Funzioni ingegneristiche (Engineering Functions) +## +BESSELI = BESSEL.I +BESSELJ = BESSEL.J +BESSELK = BESSEL.K +BESSELY = BESSEL.Y +BIN2DEC = BINARIO.DECIMALE +BIN2HEX = BINARIO.HEX +BIN2OCT = BINARIO.OCT +BITAND = BITAND +BITLSHIFT = BIT.SPOSTA.SX +BITOR = BITOR +BITRSHIFT = BIT.SPOSTA.DX +BITXOR = BITXOR +COMPLEX = COMPLESSO +CONVERT = CONVERTI +DEC2BIN = DECIMALE.BINARIO +DEC2HEX = DECIMALE.HEX +DEC2OCT = DECIMALE.OCT +DELTA = DELTA +ERF = FUNZ.ERRORE +ERF.PRECISE = FUNZ.ERRORE.PRECISA +ERFC = FUNZ.ERRORE.COMP +ERFC.PRECISE = FUNZ.ERRORE.COMP.PRECISA +GESTEP = SOGLIA +HEX2BIN = HEX.BINARIO +HEX2DEC = HEX.DECIMALE +HEX2OCT = HEX.OCT +IMABS = COMP.MODULO +IMAGINARY = COMP.IMMAGINARIO +IMARGUMENT = COMP.ARGOMENTO +IMCONJUGATE = COMP.CONIUGATO +IMCOS = COMP.COS +IMCOSH = COMP.COSH +IMCOT = COMP.COT +IMCSC = COMP.CSC +IMCSCH = COMP.CSCH +IMDIV = COMP.DIV +IMEXP = COMP.EXP +IMLN = COMP.LN +IMLOG10 = COMP.LOG10 +IMLOG2 = COMP.LOG2 +IMPOWER = COMP.POTENZA +IMPRODUCT = COMP.PRODOTTO +IMREAL = COMP.PARTE.REALE +IMSEC = COMP.SEC +IMSECH = COMP.SECH +IMSIN = COMP.SEN +IMSINH = COMP.SENH +IMSQRT = COMP.RADQ +IMSUB = COMP.DIFF +IMSUM = COMP.SOMMA +IMTAN = COMP.TAN +OCT2BIN = OCT.BINARIO +OCT2DEC = OCT.DECIMALE +OCT2HEX = OCT.HEX + +## +## Funzioni finanziarie (Financial Functions) +## +ACCRINT = INT.MATURATO.PER +ACCRINTM = INT.MATURATO.SCAD +AMORDEGRC = AMMORT.DEGR +AMORLINC = AMMORT.PER +COUPDAYBS = GIORNI.CED.INIZ.LIQ +COUPDAYS = GIORNI.CED +COUPDAYSNC = GIORNI.CED.NUOVA +COUPNCD = DATA.CED.SUCC +COUPNUM = NUM.CED +COUPPCD = DATA.CED.PREC +CUMIPMT = INT.CUMUL +CUMPRINC = CAP.CUM +DB = AMMORT.FISSO +DDB = AMMORT +DISC = TASSO.SCONTO +DOLLARDE = VALUTA.DEC +DOLLARFR = VALUTA.FRAZ +DURATION = DURATA +EFFECT = EFFETTIVO +FV = VAL.FUT +FVSCHEDULE = VAL.FUT.CAPITALE +INTRATE = TASSO.INT +IPMT = INTERESSI +IRR = TIR.COST +ISPMT = INTERESSE.RATA +MDURATION = DURATA.M +MIRR = TIR.VAR +NOMINAL = NOMINALE +NPER = NUM.RATE +NPV = VAN +ODDFPRICE = PREZZO.PRIMO.IRR +ODDFYIELD = REND.PRIMO.IRR +ODDLPRICE = PREZZO.ULTIMO.IRR +ODDLYIELD = REND.ULTIMO.IRR +PDURATION = DURATA.P +PMT = RATA +PPMT = P.RATA +PRICE = PREZZO +PRICEDISC = PREZZO.SCONT +PRICEMAT = PREZZO.SCAD +PV = VA +RATE = TASSO +RECEIVED = RICEV.SCAD +RRI = RIT.INVEST.EFFETT +SLN = AMMORT.COST +SYD = AMMORT.ANNUO +TBILLEQ = BOT.EQUIV +TBILLPRICE = BOT.PREZZO +TBILLYIELD = BOT.REND +VDB = AMMORT.VAR +XIRR = TIR.X +XNPV = VAN.X +YIELD = REND +YIELDDISC = REND.TITOLI.SCONT +YIELDMAT = REND.SCAD + +## +## Funzioni relative alle informazioni (Information Functions) +## +CELL = CELLA +ERROR.TYPE = ERRORE.TIPO +INFO = AMBIENTE.INFO +ISBLANK = VAL.VUOTO +ISERR = VAL.ERR +ISERROR = VAL.ERRORE +ISEVEN = VAL.PARI +ISFORMULA = VAL.FORMULA +ISLOGICAL = VAL.LOGICO +ISNA = VAL.NON.DISP +ISNONTEXT = VAL.NON.TESTO +ISNUMBER = VAL.NUMERO +ISODD = VAL.DISPARI +ISREF = VAL.RIF +ISTEXT = VAL.TESTO +N = NUM +NA = NON.DISP +SHEET = FOGLIO +SHEETS = FOGLI +TYPE = TIPO + +## +## Funzioni logiche (Logical Functions) +## +AND = E +FALSE = FALSO +IF = SE +IFERROR = SE.ERRORE +IFNA = SE.NON.DISP. +IFS = PIÙ.SE +NOT = NON +OR = O +SWITCH = SWITCH +TRUE = VERO +XOR = XOR + +## +## Funzioni di ricerca e di riferimento (Lookup & Reference Functions) +## +ADDRESS = INDIRIZZO +AREAS = AREE +CHOOSE = SCEGLI +COLUMN = RIF.COLONNA +COLUMNS = COLONNE +FORMULATEXT = TESTO.FORMULA +GETPIVOTDATA = INFO.DATI.TAB.PIVOT +HLOOKUP = CERCA.ORIZZ +HYPERLINK = COLLEG.IPERTESTUALE +INDEX = INDICE +INDIRECT = INDIRETTO +LOOKUP = CERCA +MATCH = CONFRONTA +OFFSET = SCARTO +ROW = RIF.RIGA +ROWS = RIGHE +RTD = DATITEMPOREALE +TRANSPOSE = MATR.TRASPOSTA +VLOOKUP = CERCA.VERT + +## +## Funzioni matematiche e trigonometriche (Math & Trig Functions) +## +ABS = ASS +ACOS = ARCCOS +ACOSH = ARCCOSH +ACOT = ARCCOT +ACOTH = ARCCOTH +AGGREGATE = AGGREGA +ARABIC = ARABO +ASIN = ARCSEN +ASINH = ARCSENH +ATAN = ARCTAN +ATAN2 = ARCTAN.2 +ATANH = ARCTANH +BASE = BASE +CEILING.MATH = ARROTONDA.ECCESSO.MAT +CEILING.PRECISE = ARROTONDA.ECCESSO.PRECISA +COMBIN = COMBINAZIONE +COMBINA = COMBINAZIONE.VALORI +COS = COS +COSH = COSH +COT = COT +COTH = COTH +CSC = CSC +CSCH = CSCH +DECIMAL = DECIMALE +DEGREES = GRADI +ECMA.CEILING = ECMA.ARROTONDA.ECCESSO +EVEN = PARI +EXP = EXP +FACT = FATTORIALE +FACTDOUBLE = FATT.DOPPIO +FLOOR.MATH = ARROTONDA.DIFETTO.MAT +FLOOR.PRECISE = ARROTONDA.DIFETTO.PRECISA +GCD = MCD +INT = INT +ISO.CEILING = ISO.ARROTONDA.ECCESSO +LCM = MCM +LN = LN +LOG = LOG +LOG10 = LOG10 +MDETERM = MATR.DETERM +MINVERSE = MATR.INVERSA +MMULT = MATR.PRODOTTO +MOD = RESTO +MROUND = ARROTONDA.MULTIPLO +MULTINOMIAL = MULTINOMIALE +MUNIT = MATR.UNIT +ODD = DISPARI +PI = PI.GRECO +POWER = POTENZA +PRODUCT = PRODOTTO +QUOTIENT = QUOZIENTE +RADIANS = RADIANTI +RAND = CASUALE +RANDBETWEEN = CASUALE.TRA +ROMAN = ROMANO +ROUND = ARROTONDA +ROUNDBAHTDOWN = ARROTBAHTGIU +ROUNDBAHTUP = ARROTBAHTSU +ROUNDDOWN = ARROTONDA.PER.DIF +ROUNDUP = ARROTONDA.PER.ECC +SEC = SEC +SECH = SECH +SERIESSUM = SOMMA.SERIE +SIGN = SEGNO +SIN = SEN +SINH = SENH +SQRT = RADQ +SQRTPI = RADQ.PI.GRECO +SUBTOTAL = SUBTOTALE +SUM = SOMMA +SUMIF = SOMMA.SE +SUMIFS = SOMMA.PIÙ.SE +SUMPRODUCT = MATR.SOMMA.PRODOTTO +SUMSQ = SOMMA.Q +SUMX2MY2 = SOMMA.DIFF.Q +SUMX2PY2 = SOMMA.SOMMA.Q +SUMXMY2 = SOMMA.Q.DIFF +TAN = TAN +TANH = TANH +TRUNC = TRONCA + +## +## Funzioni statistiche (Statistical Functions) +## +AVEDEV = MEDIA.DEV +AVERAGE = MEDIA +AVERAGEA = MEDIA.VALORI +AVERAGEIF = MEDIA.SE +AVERAGEIFS = MEDIA.PIÙ.SE +BETA.DIST = DISTRIB.BETA.N +BETA.INV = INV.BETA.N +BINOM.DIST = DISTRIB.BINOM.N +BINOM.DIST.RANGE = INTERVALLO.DISTRIB.BINOM.N. +BINOM.INV = INV.BINOM +CHISQ.DIST = DISTRIB.CHI.QUAD +CHISQ.DIST.RT = DISTRIB.CHI.QUAD.DS +CHISQ.INV = INV.CHI.QUAD +CHISQ.INV.RT = INV.CHI.QUAD.DS +CHISQ.TEST = TEST.CHI.QUAD +CONFIDENCE.NORM = CONFIDENZA.NORM +CONFIDENCE.T = CONFIDENZA.T +CORREL = CORRELAZIONE +COUNT = CONTA.NUMERI +COUNTA = CONTA.VALORI +COUNTBLANK = CONTA.VUOTE +COUNTIF = CONTA.SE +COUNTIFS = CONTA.PIÙ.SE +COVARIANCE.P = COVARIANZA.P +COVARIANCE.S = COVARIANZA.C +DEVSQ = DEV.Q +EXPON.DIST = DISTRIB.EXP.N +F.DIST = DISTRIBF +F.DIST.RT = DISTRIB.F.DS +F.INV = INVF +F.INV.RT = INV.F.DS +F.TEST = TESTF +FISHER = FISHER +FISHERINV = INV.FISHER +FORECAST.ETS = PREVISIONE.ETS +FORECAST.ETS.CONFINT = PREVISIONE.ETS.INTCONF +FORECAST.ETS.SEASONALITY = PREVISIONE.ETS.STAGIONALITÀ +FORECAST.ETS.STAT = PREVISIONE.ETS.STAT +FORECAST.LINEAR = PREVISIONE.LINEARE +FREQUENCY = FREQUENZA +GAMMA = GAMMA +GAMMA.DIST = DISTRIB.GAMMA.N +GAMMA.INV = INV.GAMMA.N +GAMMALN = LN.GAMMA +GAMMALN.PRECISE = LN.GAMMA.PRECISA +GAUSS = GAUSS +GEOMEAN = MEDIA.GEOMETRICA +GROWTH = CRESCITA +HARMEAN = MEDIA.ARMONICA +HYPGEOM.DIST = DISTRIB.IPERGEOM.N +INTERCEPT = INTERCETTA +KURT = CURTOSI +LARGE = GRANDE +LINEST = REGR.LIN +LOGEST = REGR.LOG +LOGNORM.DIST = DISTRIB.LOGNORM.N +LOGNORM.INV = INV.LOGNORM.N +MAX = MAX +MAXA = MAX.VALORI +MAXIFS = MAX.PIÙ.SE +MEDIAN = MEDIANA +MIN = MIN +MINA = MIN.VALORI +MINIFS = MIN.PIÙ.SE +MODE.MULT = MODA.MULT +MODE.SNGL = MODA.SNGL +NEGBINOM.DIST = DISTRIB.BINOM.NEG.N +NORM.DIST = DISTRIB.NORM.N +NORM.INV = INV.NORM.N +NORM.S.DIST = DISTRIB.NORM.ST.N +NORM.S.INV = INV.NORM.S +PEARSON = PEARSON +PERCENTILE.EXC = ESC.PERCENTILE +PERCENTILE.INC = INC.PERCENTILE +PERCENTRANK.EXC = ESC.PERCENT.RANGO +PERCENTRANK.INC = INC.PERCENT.RANGO +PERMUT = PERMUTAZIONE +PERMUTATIONA = PERMUTAZIONE.VALORI +PHI = PHI +POISSON.DIST = DISTRIB.POISSON +PROB = PROBABILITÀ +QUARTILE.EXC = ESC.QUARTILE +QUARTILE.INC = INC.QUARTILE +RANK.AVG = RANGO.MEDIA +RANK.EQ = RANGO.UG +RSQ = RQ +SKEW = ASIMMETRIA +SKEW.P = ASIMMETRIA.P +SLOPE = PENDENZA +SMALL = PICCOLO +STANDARDIZE = NORMALIZZA +STDEV.P = DEV.ST.P +STDEV.S = DEV.ST.C +STDEVA = DEV.ST.VALORI +STDEVPA = DEV.ST.POP.VALORI +STEYX = ERR.STD.YX +T.DIST = DISTRIB.T.N +T.DIST.2T = DISTRIB.T.2T +T.DIST.RT = DISTRIB.T.DS +T.INV = INVT +T.INV.2T = INV.T.2T +T.TEST = TESTT +TREND = TENDENZA +TRIMMEAN = MEDIA.TRONCATA +VAR.P = VAR.P +VAR.S = VAR.C +VARA = VAR.VALORI +VARPA = VAR.POP.VALORI +WEIBULL.DIST = DISTRIB.WEIBULL +Z.TEST = TESTZ + +## +## Funzioni di testo (Text Functions) +## +BAHTTEXT = BAHTTESTO +CHAR = CODICE.CARATT +CLEAN = LIBERA +CODE = CODICE +CONCAT = CONCAT +DOLLAR = VALUTA +EXACT = IDENTICO +FIND = TROVA +FIXED = FISSO +ISTHAIDIGIT = ÈTHAICIFRA +LEFT = SINISTRA +LEN = LUNGHEZZA +LOWER = MINUSC +MID = STRINGA.ESTRAI +NUMBERSTRING = NUMERO.STRINGA +NUMBERVALUE = NUMERO.VALORE +PHONETIC = FURIGANA +PROPER = MAIUSC.INIZ +REPLACE = RIMPIAZZA +REPT = RIPETI +RIGHT = DESTRA +SEARCH = RICERCA +SUBSTITUTE = SOSTITUISCI +T = T +TEXT = TESTO +TEXTJOIN = TESTO.UNISCI +THAIDIGIT = THAICIFRA +THAINUMSOUND = THAINUMSUONO +THAINUMSTRING = THAISZÁMKAR +THAISTRINGLENGTH = THAILUNGSTRINGA +TRIM = ANNULLA.SPAZI +UNICHAR = CARATT.UNI +UNICODE = UNICODE +UPPER = MAIUSC +VALUE = VALORE + +## +## Funzioni Web (Web Functions) +## +ENCODEURL = CODIFICA.URL +FILTERXML = FILTRO.XML +WEBSERVICE = SERVIZIO.WEB + +## +## Funzioni di compatibilità (Compatibility Functions) +## +BETADIST = DISTRIB.BETA +BETAINV = INV.BETA +BINOMDIST = DISTRIB.BINOM +CEILING = ARROTONDA.ECCESSO +CHIDIST = DISTRIB.CHI +CHIINV = INV.CHI +CHITEST = TEST.CHI +CONCATENATE = CONCATENA +CONFIDENCE = CONFIDENZA +COVAR = COVARIANZA +CRITBINOM = CRIT.BINOM +EXPONDIST = DISTRIB.EXP +FDIST = DISTRIB.F +FINV = INV.F +FLOOR = ARROTONDA.DIFETTO +FORECAST = PREVISIONE +FTEST = TEST.F +GAMMADIST = DISTRIB.GAMMA +GAMMAINV = INV.GAMMA +HYPGEOMDIST = DISTRIB.IPERGEOM +LOGINV = INV.LOGNORM +LOGNORMDIST = DISTRIB.LOGNORM +MODE = MODA +NEGBINOMDIST = DISTRIB.BINOM.NEG +NORMDIST = DISTRIB.NORM +NORMINV = INV.NORM +NORMSDIST = DISTRIB.NORM.ST +NORMSINV = INV.NORM.ST +PERCENTILE = PERCENTILE +PERCENTRANK = PERCENT.RANGO +POISSON = POISSON +QUARTILE = QUARTILE +RANK = RANGO +STDEV = DEV.ST +STDEVP = DEV.ST.POP +TDIST = DISTRIB.T +TINV = INV.T +TTEST = TEST.T +VAR = VAR +VARP = VAR.POP +WEIBULL = WEIBULL +ZTEST = TEST.Z diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nb/config b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nb/config new file mode 100644 index 0000000..a7f3be1 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nb/config @@ -0,0 +1,20 @@ +############################################################ +## +## PhpSpreadsheet - locale settings +## +## Norsk Bokmål (Norwegian Bokmål) +## +############################################################ + +ArgumentSeparator = ; + +## +## Error Codes +## +NULL +DIV0 +VALUE = #VERDI! +REF +NAME = #NAVN? +NUM +NA = #N/D diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nb/functions b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nb/functions new file mode 100644 index 0000000..b0a0f94 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nb/functions @@ -0,0 +1,538 @@ +############################################################ +## +## PhpSpreadsheet - function name translations +## +## Norsk Bokmål (Norwegian Bokmål) +## +############################################################ + + +## +## Kubefunksjoner (Cube Functions) +## +CUBEKPIMEMBER = KUBEKPIMEDLEM +CUBEMEMBER = KUBEMEDLEM +CUBEMEMBERPROPERTY = KUBEMEDLEMEGENSKAP +CUBERANKEDMEMBER = KUBERANGERTMEDLEM +CUBESET = KUBESETT +CUBESETCOUNT = KUBESETTANTALL +CUBEVALUE = KUBEVERDI + +## +## Databasefunksjoner (Database Functions) +## +DAVERAGE = DGJENNOMSNITT +DCOUNT = DANTALL +DCOUNTA = DANTALLA +DGET = DHENT +DMAX = DMAKS +DMIN = DMIN +DPRODUCT = DPRODUKT +DSTDEV = DSTDAV +DSTDEVP = DSTDAVP +DSUM = DSUMMER +DVAR = DVARIANS +DVARP = DVARIANSP + +## +## Dato- og tidsfunksjoner (Date & Time Functions) +## +DATE = DATO +DATEDIF = DATODIFF +DATESTRING = DATOSTRENG +DATEVALUE = DATOVERDI +DAY = DAG +DAYS = DAGER +DAYS360 = DAGER360 +EDATE = DAG.ETTER +EOMONTH = MÅNEDSSLUTT +HOUR = TIME +ISOWEEKNUM = ISOUKENR +MINUTE = MINUTT +MONTH = MÅNED +NETWORKDAYS = NETT.ARBEIDSDAGER +NETWORKDAYS.INTL = NETT.ARBEIDSDAGER.INTL +NOW = NÅ +SECOND = SEKUND +THAIDAYOFWEEK = THAIUKEDAG +THAIMONTHOFYEAR = THAIMÅNED +THAIYEAR = THAIÅR +TIME = TID +TIMEVALUE = TIDSVERDI +TODAY = IDAG +WEEKDAY = UKEDAG +WEEKNUM = UKENR +WORKDAY = ARBEIDSDAG +WORKDAY.INTL = ARBEIDSDAG.INTL +YEAR = ÅR +YEARFRAC = ÅRDEL + +## +## Tekniske funksjoner (Engineering Functions) +## +BESSELI = BESSELI +BESSELJ = BESSELJ +BESSELK = BESSELK +BESSELY = BESSELY +BIN2DEC = BINTILDES +BIN2HEX = BINTILHEKS +BIN2OCT = BINTILOKT +BITAND = BITOG +BITLSHIFT = BITVFORSKYV +BITOR = BITELLER +BITRSHIFT = BITHFORSKYV +BITXOR = BITEKSKLUSIVELLER +COMPLEX = KOMPLEKS +CONVERT = KONVERTER +DEC2BIN = DESTILBIN +DEC2HEX = DESTILHEKS +DEC2OCT = DESTILOKT +DELTA = DELTA +ERF = FEILF +ERF.PRECISE = FEILF.PRESIS +ERFC = FEILFK +ERFC.PRECISE = FEILFK.PRESIS +GESTEP = GRENSEVERDI +HEX2BIN = HEKSTILBIN +HEX2DEC = HEKSTILDES +HEX2OCT = HEKSTILOKT +IMABS = IMABS +IMAGINARY = IMAGINÆR +IMARGUMENT = IMARGUMENT +IMCONJUGATE = IMKONJUGERT +IMCOS = IMCOS +IMCOSH = IMCOSH +IMCOT = IMCOT +IMCSC = IMCSC +IMCSCH = IMCSCH +IMDIV = IMDIV +IMEXP = IMEKSP +IMLN = IMLN +IMLOG10 = IMLOG10 +IMLOG2 = IMLOG2 +IMPOWER = IMOPPHØY +IMPRODUCT = IMPRODUKT +IMREAL = IMREELL +IMSEC = IMSEC +IMSECH = IMSECH +IMSIN = IMSIN +IMSINH = IMSINH +IMSQRT = IMROT +IMSUB = IMSUB +IMSUM = IMSUMMER +IMTAN = IMTAN +OCT2BIN = OKTTILBIN +OCT2DEC = OKTTILDES +OCT2HEX = OKTTILHEKS + +## +## Økonomiske funksjoner (Financial Functions) +## +ACCRINT = PÅLØPT.PERIODISK.RENTE +ACCRINTM = PÅLØPT.FORFALLSRENTE +AMORDEGRC = AMORDEGRC +AMORLINC = AMORLINC +COUPDAYBS = OBLIG.DAGER.FF +COUPDAYS = OBLIG.DAGER +COUPDAYSNC = OBLIG.DAGER.NF +COUPNCD = OBLIG.DAGER.EF +COUPNUM = OBLIG.ANTALL +COUPPCD = OBLIG.DAG.FORRIGE +CUMIPMT = SAMLET.RENTE +CUMPRINC = SAMLET.HOVEDSTOL +DB = DAVSKR +DDB = DEGRAVS +DISC = DISKONTERT +DOLLARDE = DOLLARDE +DOLLARFR = DOLLARBR +DURATION = VARIGHET +EFFECT = EFFEKTIV.RENTE +FV = SLUTTVERDI +FVSCHEDULE = SVPLAN +INTRATE = RENTESATS +IPMT = RAVDRAG +IRR = IR +ISPMT = ER.AVDRAG +MDURATION = MVARIGHET +MIRR = MODIR +NOMINAL = NOMINELL +NPER = PERIODER +NPV = NNV +ODDFPRICE = AVVIKFP.PRIS +ODDFYIELD = AVVIKFP.AVKASTNING +ODDLPRICE = AVVIKSP.PRIS +ODDLYIELD = AVVIKSP.AVKASTNING +PDURATION = PVARIGHET +PMT = AVDRAG +PPMT = AMORT +PRICE = PRIS +PRICEDISC = PRIS.DISKONTERT +PRICEMAT = PRIS.FORFALL +PV = NÅVERDI +RATE = RENTE +RECEIVED = MOTTATT.AVKAST +RRI = REALISERT.AVKASTNING +SLN = LINAVS +SYD = ÅRSAVS +TBILLEQ = TBILLEKV +TBILLPRICE = TBILLPRIS +TBILLYIELD = TBILLAVKASTNING +VDB = VERDIAVS +XIRR = XIR +XNPV = XNNV +YIELD = AVKAST +YIELDDISC = AVKAST.DISKONTERT +YIELDMAT = AVKAST.FORFALL + +## +## Informasjonsfunksjoner (Information Functions) +## +CELL = CELLE +ERROR.TYPE = FEIL.TYPE +INFO = INFO +ISBLANK = ERTOM +ISERR = ERF +ISERROR = ERFEIL +ISEVEN = ERPARTALL +ISFORMULA = ERFORMEL +ISLOGICAL = ERLOGISK +ISNA = ERIT +ISNONTEXT = ERIKKETEKST +ISNUMBER = ERTALL +ISODD = ERODDE +ISREF = ERREF +ISTEXT = ERTEKST +N = N +NA = IT +SHEET = ARK +SHEETS = ANTALL.ARK +TYPE = VERDITYPE + +## +## Logiske funksjoner (Logical Functions) +## +AND = OG +FALSE = USANN +IF = HVIS +IFERROR = HVISFEIL +IFNA = HVIS.IT +IFS = HVIS.SETT +NOT = IKKE +OR = ELLER +SWITCH = BRYTER +TRUE = SANN +XOR = EKSKLUSIVELLER + +## +## Oppslag- og referansefunksjoner (Lookup & Reference Functions) +## +ADDRESS = ADRESSE +AREAS = OMRÅDER +CHOOSE = VELG +COLUMN = KOLONNE +COLUMNS = KOLONNER +FORMULATEXT = FORMELTEKST +GETPIVOTDATA = HENTPIVOTDATA +HLOOKUP = FINN.KOLONNE +HYPERLINK = HYPERKOBLING +INDEX = INDEKS +INDIRECT = INDIREKTE +LOOKUP = SLÅ.OPP +MATCH = SAMMENLIGNE +OFFSET = FORSKYVNING +ROW = RAD +ROWS = RADER +RTD = RTD +TRANSPOSE = TRANSPONER +VLOOKUP = FINN.RAD + +## +## Matematikk- og trigonometrifunksjoner (Math & Trig Functions) +## +ABS = ABS +ACOS = ARCCOS +ACOSH = ARCCOSH +ACOT = ACOT +ACOTH = ACOTH +AGGREGATE = MENGDE +ARABIC = ARABISK +ASIN = ARCSIN +ASINH = ARCSINH +ATAN = ARCTAN +ATAN2 = ARCTAN2 +ATANH = ARCTANH +BASE = GRUNNTALL +CEILING.MATH = AVRUND.GJELDENDE.MULTIPLUM.OPP.MATEMATISK +CEILING.PRECISE = AVRUND.GJELDENDE.MULTIPLUM.PRESIS +COMBIN = KOMBINASJON +COMBINA = KOMBINASJONA +COS = COS +COSH = COSH +COT = COT +COTH = COTH +CSC = CSC +CSCH = CSCH +DECIMAL = DESIMAL +DEGREES = GRADER +ECMA.CEILING = ECMA.AVRUND.GJELDENDE.MULTIPLUM +EVEN = AVRUND.TIL.PARTALL +EXP = EKSP +FACT = FAKULTET +FACTDOUBLE = DOBBELFAKT +FLOOR.MATH = AVRUND.GJELDENDE.MULTIPLUM.NED.MATEMATISK +FLOOR.PRECISE = AVRUND.GJELDENDE.MULTIPLUM.NED.PRESIS +GCD = SFF +INT = HELTALL +ISO.CEILING = ISO.AVRUND.GJELDENDE.MULTIPLUM +LCM = MFM +LN = LN +LOG = LOG +LOG10 = LOG10 +MDETERM = MDETERM +MINVERSE = MINVERS +MMULT = MMULT +MOD = REST +MROUND = MRUND +MULTINOMIAL = MULTINOMINELL +MUNIT = MENHET +ODD = AVRUND.TIL.ODDETALL +PI = PI +POWER = OPPHØYD.I +PRODUCT = PRODUKT +QUOTIENT = KVOTIENT +RADIANS = RADIANER +RAND = TILFELDIG +RANDBETWEEN = TILFELDIGMELLOM +ROMAN = ROMERTALL +ROUND = AVRUND +ROUNDBAHTDOWN = RUNDAVBAHTNEDOVER +ROUNDBAHTUP = RUNDAVBAHTOPPOVER +ROUNDDOWN = AVRUND.NED +ROUNDUP = AVRUND.OPP +SEC = SEC +SECH = SECH +SERIESSUM = SUMMER.REKKE +SIGN = FORTEGN +SIN = SIN +SINH = SINH +SQRT = ROT +SQRTPI = ROTPI +SUBTOTAL = DELSUM +SUM = SUMMER +SUMIF = SUMMERHVIS +SUMIFS = SUMMER.HVIS.SETT +SUMPRODUCT = SUMMERPRODUKT +SUMSQ = SUMMERKVADRAT +SUMX2MY2 = SUMMERX2MY2 +SUMX2PY2 = SUMMERX2PY2 +SUMXMY2 = SUMMERXMY2 +TAN = TAN +TANH = TANH +TRUNC = AVKORT + +## +## Statistiske funksjoner (Statistical Functions) +## +AVEDEV = GJENNOMSNITTSAVVIK +AVERAGE = GJENNOMSNITT +AVERAGEA = GJENNOMSNITTA +AVERAGEIF = GJENNOMSNITTHVIS +AVERAGEIFS = GJENNOMSNITT.HVIS.SETT +BETA.DIST = BETA.FORDELING.N +BETA.INV = BETA.INV +BINOM.DIST = BINOM.FORDELING.N +BINOM.DIST.RANGE = BINOM.FORDELING.OMRÅDE +BINOM.INV = BINOM.INV +CHISQ.DIST = KJIKVADRAT.FORDELING +CHISQ.DIST.RT = KJIKVADRAT.FORDELING.H +CHISQ.INV = KJIKVADRAT.INV +CHISQ.INV.RT = KJIKVADRAT.INV.H +CHISQ.TEST = KJIKVADRAT.TEST +CONFIDENCE.NORM = KONFIDENS.NORM +CONFIDENCE.T = KONFIDENS.T +CORREL = KORRELASJON +COUNT = ANTALL +COUNTA = ANTALLA +COUNTBLANK = TELLBLANKE +COUNTIF = ANTALL.HVIS +COUNTIFS = ANTALL.HVIS.SETT +COVARIANCE.P = KOVARIANS.P +COVARIANCE.S = KOVARIANS.S +DEVSQ = AVVIK.KVADRERT +EXPON.DIST = EKSP.FORDELING.N +F.DIST = F.FORDELING +F.DIST.RT = F.FORDELING.H +F.INV = F.INV +F.INV.RT = F.INV.H +F.TEST = F.TEST +FISHER = FISHER +FISHERINV = FISHERINV +FORECAST.ETS = PROGNOSE.ETS +FORECAST.ETS.CONFINT = PROGNOSE.ETS.CONFINT +FORECAST.ETS.SEASONALITY = PROGNOSE.ETS.SESONGAVHENGIGHET +FORECAST.ETS.STAT = PROGNOSE.ETS.STAT +FORECAST.LINEAR = PROGNOSE.LINEÆR +FREQUENCY = FREKVENS +GAMMA = GAMMA +GAMMA.DIST = GAMMA.FORDELING +GAMMA.INV = GAMMA.INV +GAMMALN = GAMMALN +GAMMALN.PRECISE = GAMMALN.PRESIS +GAUSS = GAUSS +GEOMEAN = GJENNOMSNITT.GEOMETRISK +GROWTH = VEKST +HARMEAN = GJENNOMSNITT.HARMONISK +HYPGEOM.DIST = HYPGEOM.FORDELING.N +INTERCEPT = SKJÆRINGSPUNKT +KURT = KURT +LARGE = N.STØRST +LINEST = RETTLINJE +LOGEST = KURVE +LOGNORM.DIST = LOGNORM.FORDELING +LOGNORM.INV = LOGNORM.INV +MAX = STØRST +MAXA = MAKSA +MAXIFS = MAKS.HVIS.SETT +MEDIAN = MEDIAN +MIN = MIN +MINA = MINA +MINIFS = MIN.HVIS.SETT +MODE.MULT = MODUS.MULT +MODE.SNGL = MODUS.SNGL +NEGBINOM.DIST = NEGBINOM.FORDELING.N +NORM.DIST = NORM.FORDELING +NORM.INV = NORM.INV +NORM.S.DIST = NORM.S.FORDELING +NORM.S.INV = NORM.S.INV +PEARSON = PEARSON +PERCENTILE.EXC = PERSENTIL.EKS +PERCENTILE.INC = PERSENTIL.INK +PERCENTRANK.EXC = PROSENTDEL.EKS +PERCENTRANK.INC = PROSENTDEL.INK +PERMUT = PERMUTER +PERMUTATIONA = PERMUTASJONA +PHI = PHI +POISSON.DIST = POISSON.FORDELING +PROB = SANNSYNLIG +QUARTILE.EXC = KVARTIL.EKS +QUARTILE.INC = KVARTIL.INK +RANK.AVG = RANG.GJSN +RANK.EQ = RANG.EKV +RSQ = RKVADRAT +SKEW = SKJEVFORDELING +SKEW.P = SKJEVFORDELING.P +SLOPE = STIGNINGSTALL +SMALL = N.MINST +STANDARDIZE = NORMALISER +STDEV.P = STDAV.P +STDEV.S = STDAV.S +STDEVA = STDAVVIKA +STDEVPA = STDAVVIKPA +STEYX = STANDARDFEIL +T.DIST = T.FORDELING +T.DIST.2T = T.FORDELING.2T +T.DIST.RT = T.FORDELING.H +T.INV = T.INV +T.INV.2T = T.INV.2T +T.TEST = T.TEST +TREND = TREND +TRIMMEAN = TRIMMET.GJENNOMSNITT +VAR.P = VARIANS.P +VAR.S = VARIANS.S +VARA = VARIANSA +VARPA = VARIANSPA +WEIBULL.DIST = WEIBULL.DIST.N +Z.TEST = Z.TEST + +## +## Tekstfunksjoner (Text Functions) +## +ASC = STIGENDE +BAHTTEXT = BAHTTEKST +CHAR = TEGNKODE +CLEAN = RENSK +CODE = KODE +CONCAT = KJED.SAMMEN +DOLLAR = VALUTA +EXACT = EKSAKT +FIND = FINN +FIXED = FASTSATT +ISTHAIDIGIT = ERTHAISIFFER +LEFT = VENSTRE +LEN = LENGDE +LOWER = SMÅ +MID = DELTEKST +NUMBERSTRING = TALLSTRENG +NUMBERVALUE = TALLVERDI +PHONETIC = FURIGANA +PROPER = STOR.FORBOKSTAV +REPLACE = ERSTATT +REPT = GJENTA +RIGHT = HØYRE +SEARCH = SØK +SUBSTITUTE = BYTT.UT +T = T +TEXT = TEKST +TEXTJOIN = TEKST.KOMBINER +THAIDIGIT = THAISIFFER +THAINUMSOUND = THAINUMLYD +THAINUMSTRING = THAINUMSTRENG +THAISTRINGLENGTH = THAISTRENGLENGDE +TRIM = TRIMME +UNICHAR = UNICODETEGN +UNICODE = UNICODE +UPPER = STORE +VALUE = VERDI + +## +## Nettfunksjoner (Web Functions) +## +ENCODEURL = URL.KODE +FILTERXML = FILTRERXML +WEBSERVICE = NETTJENESTE + +## +## Kompatibilitetsfunksjoner (Compatibility Functions) +## +BETADIST = BETA.FORDELING +BETAINV = INVERS.BETA.FORDELING +BINOMDIST = BINOM.FORDELING +CEILING = AVRUND.GJELDENDE.MULTIPLUM +CHIDIST = KJI.FORDELING +CHIINV = INVERS.KJI.FORDELING +CHITEST = KJI.TEST +CONCATENATE = KJEDE.SAMMEN +CONFIDENCE = KONFIDENS +COVAR = KOVARIANS +CRITBINOM = GRENSE.BINOM +EXPONDIST = EKSP.FORDELING +FDIST = FFORDELING +FINV = FFORDELING.INVERS +FLOOR = AVRUND.GJELDENDE.MULTIPLUM.NED +FORECAST = PROGNOSE +FTEST = FTEST +GAMMADIST = GAMMAFORDELING +GAMMAINV = GAMMAINV +HYPGEOMDIST = HYPGEOM.FORDELING +LOGINV = LOGINV +LOGNORMDIST = LOGNORMFORD +MODE = MODUS +NEGBINOMDIST = NEGBINOM.FORDELING +NORMDIST = NORMALFORDELING +NORMINV = NORMINV +NORMSDIST = NORMSFORDELING +NORMSINV = NORMSINV +PERCENTILE = PERSENTIL +PERCENTRANK = PROSENTDEL +POISSON = POISSON +QUARTILE = KVARTIL +RANK = RANG +STDEV = STDAV +STDEVP = STDAVP +TDIST = TFORDELING +TINV = TINV +TTEST = TTEST +VAR = VARIANS +VARP = VARIANSP +WEIBULL = WEIBULL.FORDELING +ZTEST = ZTEST diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nl/config b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nl/config new file mode 100644 index 0000000..370567a --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nl/config @@ -0,0 +1,20 @@ +############################################################ +## +## PhpSpreadsheet - locale settings +## +## Nederlands (Dutch) +## +############################################################ + +ArgumentSeparator = ; + +## +## Error Codes +## +NULL = #LEEG! +DIV0 = #DEEL/0! +VALUE = #WAARDE! +REF = #VERW! +NAME = #NAAM? +NUM = #GETAL! +NA = #N/B diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nl/functions b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nl/functions new file mode 100644 index 0000000..0e4f159 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nl/functions @@ -0,0 +1,536 @@ +############################################################ +## +## PhpSpreadsheet - function name translations +## +## Nederlands (Dutch) +## +############################################################ + + +## +## Kubusfuncties (Cube Functions) +## +CUBEKPIMEMBER = KUBUSKPILID +CUBEMEMBER = KUBUSLID +CUBEMEMBERPROPERTY = KUBUSLIDEIGENSCHAP +CUBERANKEDMEMBER = KUBUSGERANGSCHIKTLID +CUBESET = KUBUSSET +CUBESETCOUNT = KUBUSSETAANTAL +CUBEVALUE = KUBUSWAARDE + +## +## Databasefuncties (Database Functions) +## +DAVERAGE = DBGEMIDDELDE +DCOUNT = DBAANTAL +DCOUNTA = DBAANTALC +DGET = DBLEZEN +DMAX = DBMAX +DMIN = DBMIN +DPRODUCT = DBPRODUCT +DSTDEV = DBSTDEV +DSTDEVP = DBSTDEVP +DSUM = DBSOM +DVAR = DBVAR +DVARP = DBVARP + +## +## Datum- en tijdfuncties (Date & Time Functions) +## +DATE = DATUM +DATESTRING = DATUMNOTATIE +DATEVALUE = DATUMWAARDE +DAY = DAG +DAYS = DAGEN +DAYS360 = DAGEN360 +EDATE = ZELFDE.DAG +EOMONTH = LAATSTE.DAG +HOUR = UUR +ISOWEEKNUM = ISO.WEEKNUMMER +MINUTE = MINUUT +MONTH = MAAND +NETWORKDAYS = NETTO.WERKDAGEN +NETWORKDAYS.INTL = NETWERKDAGEN.INTL +NOW = NU +SECOND = SECONDE +THAIDAYOFWEEK = THAIS.WEEKDAG +THAIMONTHOFYEAR = THAIS.MAAND.VAN.JAAR +THAIYEAR = THAIS.JAAR +TIME = TIJD +TIMEVALUE = TIJDWAARDE +TODAY = VANDAAG +WEEKDAY = WEEKDAG +WEEKNUM = WEEKNUMMER +WORKDAY = WERKDAG +WORKDAY.INTL = WERKDAG.INTL +YEAR = JAAR +YEARFRAC = JAAR.DEEL + +## +## Technische functies (Engineering Functions) +## +BESSELI = BESSEL.I +BESSELJ = BESSEL.J +BESSELK = BESSEL.K +BESSELY = BESSEL.Y +BIN2DEC = BIN.N.DEC +BIN2HEX = BIN.N.HEX +BIN2OCT = BIN.N.OCT +BITAND = BIT.EN +BITLSHIFT = BIT.VERSCHUIF.LINKS +BITOR = BIT.OF +BITRSHIFT = BIT.VERSCHUIF.RECHTS +BITXOR = BIT.EX.OF +COMPLEX = COMPLEX +CONVERT = CONVERTEREN +DEC2BIN = DEC.N.BIN +DEC2HEX = DEC.N.HEX +DEC2OCT = DEC.N.OCT +DELTA = DELTA +ERF = FOUTFUNCTIE +ERF.PRECISE = FOUTFUNCTIE.NAUWKEURIG +ERFC = FOUT.COMPLEMENT +ERFC.PRECISE = FOUT.COMPLEMENT.NAUWKEURIG +GESTEP = GROTER.DAN +HEX2BIN = HEX.N.BIN +HEX2DEC = HEX.N.DEC +HEX2OCT = HEX.N.OCT +IMABS = C.ABS +IMAGINARY = C.IM.DEEL +IMARGUMENT = C.ARGUMENT +IMCONJUGATE = C.TOEGEVOEGD +IMCOS = C.COS +IMCOSH = C.COSH +IMCOT = C.COT +IMCSC = C.COSEC +IMCSCH = C.COSECH +IMDIV = C.QUOTIENT +IMEXP = C.EXP +IMLN = C.LN +IMLOG10 = C.LOG10 +IMLOG2 = C.LOG2 +IMPOWER = C.MACHT +IMPRODUCT = C.PRODUCT +IMREAL = C.REEEL.DEEL +IMSEC = C.SEC +IMSECH = C.SECH +IMSIN = C.SIN +IMSINH = C.SINH +IMSQRT = C.WORTEL +IMSUB = C.VERSCHIL +IMSUM = C.SOM +IMTAN = C.TAN +OCT2BIN = OCT.N.BIN +OCT2DEC = OCT.N.DEC +OCT2HEX = OCT.N.HEX + +## +## Financiële functies (Financial Functions) +## +ACCRINT = SAMENG.RENTE +ACCRINTM = SAMENG.RENTE.V +AMORDEGRC = AMORDEGRC +AMORLINC = AMORLINC +COUPDAYBS = COUP.DAGEN.BB +COUPDAYS = COUP.DAGEN +COUPDAYSNC = COUP.DAGEN.VV +COUPNCD = COUP.DATUM.NB +COUPNUM = COUP.AANTAL +COUPPCD = COUP.DATUM.VB +CUMIPMT = CUM.RENTE +CUMPRINC = CUM.HOOFDSOM +DB = DB +DDB = DDB +DISC = DISCONTO +DOLLARDE = EURO.DE +DOLLARFR = EURO.BR +DURATION = DUUR +EFFECT = EFFECT.RENTE +FV = TW +FVSCHEDULE = TOEK.WAARDE2 +INTRATE = RENTEPERCENTAGE +IPMT = IBET +IRR = IR +ISPMT = ISBET +MDURATION = AANG.DUUR +MIRR = GIR +NOMINAL = NOMINALE.RENTE +NPER = NPER +NPV = NHW +ODDFPRICE = AFW.ET.PRIJS +ODDFYIELD = AFW.ET.REND +ODDLPRICE = AFW.LT.PRIJS +ODDLYIELD = AFW.LT.REND +PDURATION = PDUUR +PMT = BET +PPMT = PBET +PRICE = PRIJS.NOM +PRICEDISC = PRIJS.DISCONTO +PRICEMAT = PRIJS.VERVALDAG +PV = HW +RATE = RENTE +RECEIVED = OPBRENGST +RRI = RRI +SLN = LIN.AFSCHR +SYD = SYD +TBILLEQ = SCHATK.OBL +TBILLPRICE = SCHATK.PRIJS +TBILLYIELD = SCHATK.REND +VDB = VDB +XIRR = IR.SCHEMA +XNPV = NHW2 +YIELD = RENDEMENT +YIELDDISC = REND.DISCONTO +YIELDMAT = REND.VERVAL + +## +## Informatiefuncties (Information Functions) +## +CELL = CEL +ERROR.TYPE = TYPE.FOUT +INFO = INFO +ISBLANK = ISLEEG +ISERR = ISFOUT2 +ISERROR = ISFOUT +ISEVEN = IS.EVEN +ISFORMULA = ISFORMULE +ISLOGICAL = ISLOGISCH +ISNA = ISNB +ISNONTEXT = ISGEENTEKST +ISNUMBER = ISGETAL +ISODD = IS.ONEVEN +ISREF = ISVERWIJZING +ISTEXT = ISTEKST +N = N +NA = NB +SHEET = BLAD +SHEETS = BLADEN +TYPE = TYPE + +## +## Logische functies (Logical Functions) +## +AND = EN +FALSE = ONWAAR +IF = ALS +IFERROR = ALS.FOUT +IFNA = ALS.NB +IFS = ALS.VOORWAARDEN +NOT = NIET +OR = OF +SWITCH = SCHAKELEN +TRUE = WAAR +XOR = EX.OF + +## +## Zoek- en verwijzingsfuncties (Lookup & Reference Functions) +## +ADDRESS = ADRES +AREAS = BEREIKEN +CHOOSE = KIEZEN +COLUMN = KOLOM +COLUMNS = KOLOMMEN +FORMULATEXT = FORMULETEKST +GETPIVOTDATA = DRAAITABEL.OPHALEN +HLOOKUP = HORIZ.ZOEKEN +HYPERLINK = HYPERLINK +INDEX = INDEX +INDIRECT = INDIRECT +LOOKUP = ZOEKEN +MATCH = VERGELIJKEN +OFFSET = VERSCHUIVING +ROW = RIJ +ROWS = RIJEN +RTD = RTG +TRANSPOSE = TRANSPONEREN +VLOOKUP = VERT.ZOEKEN + +## +## Wiskundige en trigonometrische functies (Math & Trig Functions) +## +ABS = ABS +ACOS = BOOGCOS +ACOSH = BOOGCOSH +ACOT = BOOGCOT +ACOTH = BOOGCOTH +AGGREGATE = AGGREGAAT +ARABIC = ARABISCH +ASIN = BOOGSIN +ASINH = BOOGSINH +ATAN = BOOGTAN +ATAN2 = BOOGTAN2 +ATANH = BOOGTANH +BASE = BASIS +CEILING.MATH = AFRONDEN.BOVEN.WISK +CEILING.PRECISE = AFRONDEN.BOVEN.NAUWKEURIG +COMBIN = COMBINATIES +COMBINA = COMBIN.A +COS = COS +COSH = COSH +COT = COT +COTH = COTH +CSC = COSEC +CSCH = COSECH +DECIMAL = DECIMAAL +DEGREES = GRADEN +ECMA.CEILING = ECMA.AFRONDEN.BOVEN +EVEN = EVEN +EXP = EXP +FACT = FACULTEIT +FACTDOUBLE = DUBBELE.FACULTEIT +FLOOR.MATH = AFRONDEN.BENEDEN.WISK +FLOOR.PRECISE = AFRONDEN.BENEDEN.NAUWKEURIG +GCD = GGD +INT = INTEGER +ISO.CEILING = ISO.AFRONDEN.BOVEN +LCM = KGV +LN = LN +LOG = LOG +LOG10 = LOG10 +MDETERM = DETERMINANTMAT +MINVERSE = INVERSEMAT +MMULT = PRODUCTMAT +MOD = REST +MROUND = AFRONDEN.N.VEELVOUD +MULTINOMIAL = MULTINOMIAAL +MUNIT = EENHEIDMAT +ODD = ONEVEN +PI = PI +POWER = MACHT +PRODUCT = PRODUCT +QUOTIENT = QUOTIENT +RADIANS = RADIALEN +RAND = ASELECT +RANDBETWEEN = ASELECTTUSSEN +ROMAN = ROMEINS +ROUND = AFRONDEN +ROUNDBAHTDOWN = BAHT.AFR.NAAR.BENEDEN +ROUNDBAHTUP = BAHT.AFR.NAAR.BOVEN +ROUNDDOWN = AFRONDEN.NAAR.BENEDEN +ROUNDUP = AFRONDEN.NAAR.BOVEN +SEC = SEC +SECH = SECH +SERIESSUM = SOM.MACHTREEKS +SIGN = POS.NEG +SIN = SIN +SINH = SINH +SQRT = WORTEL +SQRTPI = WORTEL.PI +SUBTOTAL = SUBTOTAAL +SUM = SOM +SUMIF = SOM.ALS +SUMIFS = SOMMEN.ALS +SUMPRODUCT = SOMPRODUCT +SUMSQ = KWADRATENSOM +SUMX2MY2 = SOM.X2MINY2 +SUMX2PY2 = SOM.X2PLUSY2 +SUMXMY2 = SOM.XMINY.2 +TAN = TAN +TANH = TANH +TRUNC = GEHEEL + +## +## Statistische functies (Statistical Functions) +## +AVEDEV = GEM.DEVIATIE +AVERAGE = GEMIDDELDE +AVERAGEA = GEMIDDELDEA +AVERAGEIF = GEMIDDELDE.ALS +AVERAGEIFS = GEMIDDELDEN.ALS +BETA.DIST = BETA.VERD +BETA.INV = BETA.INV +BINOM.DIST = BINOM.VERD +BINOM.DIST.RANGE = BINOM.VERD.BEREIK +BINOM.INV = BINOMIALE.INV +CHISQ.DIST = CHIKW.VERD +CHISQ.DIST.RT = CHIKW.VERD.RECHTS +CHISQ.INV = CHIKW.INV +CHISQ.INV.RT = CHIKW.INV.RECHTS +CHISQ.TEST = CHIKW.TEST +CONFIDENCE.NORM = VERTROUWELIJKHEID.NORM +CONFIDENCE.T = VERTROUWELIJKHEID.T +CORREL = CORRELATIE +COUNT = AANTAL +COUNTA = AANTALARG +COUNTBLANK = AANTAL.LEGE.CELLEN +COUNTIF = AANTAL.ALS +COUNTIFS = AANTALLEN.ALS +COVARIANCE.P = COVARIANTIE.P +COVARIANCE.S = COVARIANTIE.S +DEVSQ = DEV.KWAD +EXPON.DIST = EXPON.VERD.N +F.DIST = F.VERD +F.DIST.RT = F.VERD.RECHTS +F.INV = F.INV +F.INV.RT = F.INV.RECHTS +F.TEST = F.TEST +FISHER = FISHER +FISHERINV = FISHER.INV +FORECAST.ETS = VOORSPELLEN.ETS +FORECAST.ETS.CONFINT = VOORSPELLEN.ETS.CONFINT +FORECAST.ETS.SEASONALITY = VOORSPELLEN.ETS.SEASONALITY +FORECAST.ETS.STAT = FORECAST.ETS.STAT +FORECAST.LINEAR = VOORSPELLEN.LINEAR +FREQUENCY = INTERVAL +GAMMA = GAMMA +GAMMA.DIST = GAMMA.VERD.N +GAMMA.INV = GAMMA.INV.N +GAMMALN = GAMMA.LN +GAMMALN.PRECISE = GAMMA.LN.NAUWKEURIG +GAUSS = GAUSS +GEOMEAN = MEETK.GEM +GROWTH = GROEI +HARMEAN = HARM.GEM +HYPGEOM.DIST = HYPGEOM.VERD +INTERCEPT = SNIJPUNT +KURT = KURTOSIS +LARGE = GROOTSTE +LINEST = LIJNSCH +LOGEST = LOGSCH +LOGNORM.DIST = LOGNORM.VERD +LOGNORM.INV = LOGNORM.INV +MAX = MAX +MAXA = MAXA +MAXIFS = MAX.ALS.VOORWAARDEN +MEDIAN = MEDIAAN +MIN = MIN +MINA = MINA +MINIFS = MIN.ALS.VOORWAARDEN +MODE.MULT = MODUS.MEERV +MODE.SNGL = MODUS.ENKELV +NEGBINOM.DIST = NEGBINOM.VERD +NORM.DIST = NORM.VERD.N +NORM.INV = NORM.INV.N +NORM.S.DIST = NORM.S.VERD +NORM.S.INV = NORM.S.INV +PEARSON = PEARSON +PERCENTILE.EXC = PERCENTIEL.EXC +PERCENTILE.INC = PERCENTIEL.INC +PERCENTRANK.EXC = PROCENTRANG.EXC +PERCENTRANK.INC = PROCENTRANG.INC +PERMUT = PERMUTATIES +PERMUTATIONA = PERMUTATIE.A +PHI = PHI +POISSON.DIST = POISSON.VERD +PROB = KANS +QUARTILE.EXC = KWARTIEL.EXC +QUARTILE.INC = KWARTIEL.INC +RANK.AVG = RANG.GEMIDDELDE +RANK.EQ = RANG.GELIJK +RSQ = R.KWADRAAT +SKEW = SCHEEFHEID +SKEW.P = SCHEEFHEID.P +SLOPE = RICHTING +SMALL = KLEINSTE +STANDARDIZE = NORMALISEREN +STDEV.P = STDEV.P +STDEV.S = STDEV.S +STDEVA = STDEVA +STDEVPA = STDEVPA +STEYX = STAND.FOUT.YX +T.DIST = T.DIST +T.DIST.2T = T.VERD.2T +T.DIST.RT = T.VERD.RECHTS +T.INV = T.INV +T.INV.2T = T.INV.2T +T.TEST = T.TEST +TREND = TREND +TRIMMEAN = GETRIMD.GEM +VAR.P = VAR.P +VAR.S = VAR.S +VARA = VARA +VARPA = VARPA +WEIBULL.DIST = WEIBULL.VERD +Z.TEST = Z.TEST + +## +## Tekstfuncties (Text Functions) +## +BAHTTEXT = BAHT.TEKST +CHAR = TEKEN +CLEAN = WISSEN.CONTROL +CODE = CODE +CONCAT = TEKST.SAMENV +DOLLAR = EURO +EXACT = GELIJK +FIND = VIND.ALLES +FIXED = VAST +ISTHAIDIGIT = IS.THAIS.CIJFER +LEFT = LINKS +LEN = LENGTE +LOWER = KLEINE.LETTERS +MID = DEEL +NUMBERSTRING = GETALNOTATIE +NUMBERVALUE = NUMERIEKE.WAARDE +PHONETIC = FONETISCH +PROPER = BEGINLETTERS +REPLACE = VERVANGEN +REPT = HERHALING +RIGHT = RECHTS +SEARCH = VIND.SPEC +SUBSTITUTE = SUBSTITUEREN +T = T +TEXT = TEKST +TEXTJOIN = TEKST.COMBINEREN +THAIDIGIT = THAIS.CIJFER +THAINUMSOUND = THAIS.GETAL.GELUID +THAINUMSTRING = THAIS.GETAL.REEKS +THAISTRINGLENGTH = THAIS.REEKS.LENGTE +TRIM = SPATIES.WISSEN +UNICHAR = UNITEKEN +UNICODE = UNICODE +UPPER = HOOFDLETTERS +VALUE = WAARDE + +## +## Webfuncties (Web Functions) +## +ENCODEURL = URL.CODEREN +FILTERXML = XML.FILTEREN +WEBSERVICE = WEBSERVICE + +## +## Compatibiliteitsfuncties (Compatibility Functions) +## +BETADIST = BETAVERD +BETAINV = BETAINV +BINOMDIST = BINOMIALE.VERD +CEILING = AFRONDEN.BOVEN +CHIDIST = CHI.KWADRAAT +CHIINV = CHI.KWADRAAT.INV +CHITEST = CHI.TOETS +CONCATENATE = TEKST.SAMENVOEGEN +CONFIDENCE = BETROUWBAARHEID +COVAR = COVARIANTIE +CRITBINOM = CRIT.BINOM +EXPONDIST = EXPON.VERD +FDIST = F.VERDELING +FINV = F.INVERSE +FLOOR = AFRONDEN.BENEDEN +FORECAST = VOORSPELLEN +FTEST = F.TOETS +GAMMADIST = GAMMA.VERD +GAMMAINV = GAMMA.INV +HYPGEOMDIST = HYPERGEO.VERD +LOGINV = LOG.NORM.INV +LOGNORMDIST = LOG.NORM.VERD +MODE = MODUS +NEGBINOMDIST = NEG.BINOM.VERD +NORMDIST = NORM.VERD +NORMINV = NORM.INV +NORMSDIST = STAND.NORM.VERD +NORMSINV = STAND.NORM.INV +PERCENTILE = PERCENTIEL +PERCENTRANK = PERCENT.RANG +POISSON = POISSON +QUARTILE = KWARTIEL +RANK = RANG +STDEV = STDEV +STDEVP = STDEVP +TDIST = T.VERD +TINV = TINV +TTEST = T.TOETS +VAR = VAR +VARP = VARP +WEIBULL = WEIBULL +ZTEST = Z.TOETS diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pl/config b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pl/config new file mode 100644 index 0000000..1d8468b --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pl/config @@ -0,0 +1,20 @@ +############################################################ +## +## PhpSpreadsheet - locale settings +## +## Jezyk polski (Polish) +## +############################################################ + +ArgumentSeparator = ; + +## +## Error Codes +## +NULL = #ZERO! +DIV0 = #DZIEL/0! +VALUE = #ARG! +REF = #ADR! +NAME = #NAZWA? +NUM = #LICZBA! +NA = #N/D! diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pl/functions b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pl/functions new file mode 100644 index 0000000..d1b43b2 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pl/functions @@ -0,0 +1,536 @@ +############################################################ +## +## PhpSpreadsheet - function name translations +## +## Jezyk polski (Polish) +## +############################################################ + + +## +## Funkcje baz danych (Cube Functions) +## +CUBEKPIMEMBER = ELEMENT.KPI.MODUŁU +CUBEMEMBER = ELEMENT.MODUŁU +CUBEMEMBERPROPERTY = WŁAŚCIWOŚĆ.ELEMENTU.MODUŁU +CUBERANKEDMEMBER = USZEREGOWANY.ELEMENT.MODUŁU +CUBESET = ZESTAW.MODUŁÓW +CUBESETCOUNT = LICZNIK.MODUŁÓW.ZESTAWU +CUBEVALUE = WARTOŚĆ.MODUŁU + +## +## Funkcje baz danych (Database Functions) +## +DAVERAGE = BD.ŚREDNIA +DCOUNT = BD.ILE.REKORDÓW +DCOUNTA = BD.ILE.REKORDÓW.A +DGET = BD.POLE +DMAX = BD.MAX +DMIN = BD.MIN +DPRODUCT = BD.ILOCZYN +DSTDEV = BD.ODCH.STANDARD +DSTDEVP = BD.ODCH.STANDARD.POPUL +DSUM = BD.SUMA +DVAR = BD.WARIANCJA +DVARP = BD.WARIANCJA.POPUL + +## +## Funkcje daty i godziny (Date & Time Functions) +## +DATE = DATA +DATEDIF = DATA.RÓŻNICA +DATESTRING = DATA.CIĄG.ZNAK +DATEVALUE = DATA.WARTOŚĆ +DAY = DZIEŃ +DAYS = DNI +DAYS360 = DNI.360 +EDATE = NR.SER.DATY +EOMONTH = NR.SER.OST.DN.MIES +HOUR = GODZINA +ISOWEEKNUM = ISO.NUM.TYG +MINUTE = MINUTA +MONTH = MIESIĄC +NETWORKDAYS = DNI.ROBOCZE +NETWORKDAYS.INTL = DNI.ROBOCZE.NIESTAND +NOW = TERAZ +SECOND = SEKUNDA +THAIDAYOFWEEK = TAJ.DZIEŃ.TYGODNIA +THAIMONTHOFYEAR = TAJ.MIESIĄC.ROKU +THAIYEAR = TAJ.ROK +TIME = CZAS +TIMEVALUE = CZAS.WARTOŚĆ +TODAY = DZIŚ +WEEKDAY = DZIEŃ.TYG +WEEKNUM = NUM.TYG +WORKDAY = DZIEŃ.ROBOCZY +WORKDAY.INTL = DZIEŃ.ROBOCZY.NIESTAND +YEAR = ROK +YEARFRAC = CZĘŚĆ.ROKU + +## +## Funkcje inżynierskie (Engineering Functions) +## +BESSELI = BESSEL.I +BESSELJ = BESSEL.J +BESSELK = BESSEL.K +BESSELY = BESSEL.Y +BIN2DEC = DWÓJK.NA.DZIES +BIN2HEX = DWÓJK.NA.SZESN +BIN2OCT = DWÓJK.NA.ÓSM +BITAND = BITAND +BITLSHIFT = BIT.PRZESUNIĘCIE.W.LEWO +BITOR = BITOR +BITRSHIFT = BIT.PRZESUNIĘCIE.W.PRAWO +BITXOR = BITXOR +COMPLEX = LICZBA.ZESP +CONVERT = KONWERTUJ +DEC2BIN = DZIES.NA.DWÓJK +DEC2HEX = DZIES.NA.SZESN +DEC2OCT = DZIES.NA.ÓSM +DELTA = CZY.RÓWNE +ERF = FUNKCJA.BŁ +ERF.PRECISE = FUNKCJA.BŁ.DOKŁ +ERFC = KOMP.FUNKCJA.BŁ +ERFC.PRECISE = KOMP.FUNKCJA.BŁ.DOKŁ +GESTEP = SPRAWDŹ.PRÓG +HEX2BIN = SZESN.NA.DWÓJK +HEX2DEC = SZESN.NA.DZIES +HEX2OCT = SZESN.NA.ÓSM +IMABS = MODUŁ.LICZBY.ZESP +IMAGINARY = CZ.UROJ.LICZBY.ZESP +IMARGUMENT = ARG.LICZBY.ZESP +IMCONJUGATE = SPRZĘŻ.LICZBY.ZESP +IMCOS = COS.LICZBY.ZESP +IMCOSH = COSH.LICZBY.ZESP +IMCOT = COT.LICZBY.ZESP +IMCSC = CSC.LICZBY.ZESP +IMCSCH = CSCH.LICZBY.ZESP +IMDIV = ILORAZ.LICZB.ZESP +IMEXP = EXP.LICZBY.ZESP +IMLN = LN.LICZBY.ZESP +IMLOG10 = LOG10.LICZBY.ZESP +IMLOG2 = LOG2.LICZBY.ZESP +IMPOWER = POTĘGA.LICZBY.ZESP +IMPRODUCT = ILOCZYN.LICZB.ZESP +IMREAL = CZ.RZECZ.LICZBY.ZESP +IMSEC = SEC.LICZBY.ZESP +IMSECH = SECH.LICZBY.ZESP +IMSIN = SIN.LICZBY.ZESP +IMSINH = SINH.LICZBY.ZESP +IMSQRT = PIERWIASTEK.LICZBY.ZESP +IMSUB = RÓŻN.LICZB.ZESP +IMSUM = SUMA.LICZB.ZESP +IMTAN = TAN.LICZBY.ZESP +OCT2BIN = ÓSM.NA.DWÓJK +OCT2DEC = ÓSM.NA.DZIES +OCT2HEX = ÓSM.NA.SZESN + +## +## Funkcje finansowe (Financial Functions) +## +ACCRINT = NAL.ODS +ACCRINTM = NAL.ODS.WYKUP +AMORDEGRC = AMORT.NIELIN +AMORLINC = AMORT.LIN +COUPDAYBS = WYPŁ.DNI.OD.POCZ +COUPDAYS = WYPŁ.DNI +COUPDAYSNC = WYPŁ.DNI.NAST +COUPNCD = WYPŁ.DATA.NAST +COUPNUM = WYPŁ.LICZBA +COUPPCD = WYPŁ.DATA.POPRZ +CUMIPMT = SPŁAC.ODS +CUMPRINC = SPŁAC.KAPIT +DB = DB +DDB = DDB +DISC = STOPA.DYSK +DOLLARDE = CENA.DZIES +DOLLARFR = CENA.UŁAM +DURATION = ROCZ.PRZYCH +EFFECT = EFEKTYWNA +FV = FV +FVSCHEDULE = WART.PRZYSZŁ.KAP +INTRATE = STOPA.PROC +IPMT = IPMT +IRR = IRR +ISPMT = ISPMT +MDURATION = ROCZ.PRZYCH.M +MIRR = MIRR +NOMINAL = NOMINALNA +NPER = NPER +NPV = NPV +ODDFPRICE = CENA.PIERW.OKR +ODDFYIELD = RENT.PIERW.OKR +ODDLPRICE = CENA.OST.OKR +ODDLYIELD = RENT.OST.OKR +PDURATION = O.CZAS.TRWANIA +PMT = PMT +PPMT = PPMT +PRICE = CENA +PRICEDISC = CENA.DYSK +PRICEMAT = CENA.WYKUP +PV = PV +RATE = RATE +RECEIVED = KWOTA.WYKUP +RRI = RÓWNOW.STOPA.PROC +SLN = SLN +SYD = SYD +TBILLEQ = RENT.EKW.BS +TBILLPRICE = CENA.BS +TBILLYIELD = RENT.BS +VDB = VDB +XIRR = XIRR +XNPV = XNPV +YIELD = RENTOWNOŚĆ +YIELDDISC = RENT.DYSK +YIELDMAT = RENT.WYKUP + +## +## Funkcje informacyjne (Information Functions) +## +CELL = KOMÓRKA +ERROR.TYPE = NR.BŁĘDU +INFO = INFO +ISBLANK = CZY.PUSTA +ISERR = CZY.BŁ +ISERROR = CZY.BŁĄD +ISEVEN = CZY.PARZYSTE +ISFORMULA = CZY.FORMUŁA +ISLOGICAL = CZY.LOGICZNA +ISNA = CZY.BRAK +ISNONTEXT = CZY.NIE.TEKST +ISNUMBER = CZY.LICZBA +ISODD = CZY.NIEPARZYSTE +ISREF = CZY.ADR +ISTEXT = CZY.TEKST +N = N +NA = BRAK +SHEET = ARKUSZ +SHEETS = ARKUSZE +TYPE = TYP + +## +## Funkcje logiczne (Logical Functions) +## +AND = ORAZ +FALSE = FAŁSZ +IF = JEŻELI +IFERROR = JEŻELI.BŁĄD +IFNA = JEŻELI.ND +IFS = WARUNKI +NOT = NIE +OR = LUB +SWITCH = PRZEŁĄCZ +TRUE = PRAWDA +XOR = XOR + +## +## Funkcje wyszukiwania i odwołań (Lookup & Reference Functions) +## +ADDRESS = ADRES +AREAS = OBSZARY +CHOOSE = WYBIERZ +COLUMN = NR.KOLUMNY +COLUMNS = LICZBA.KOLUMN +FORMULATEXT = FORMUŁA.TEKST +GETPIVOTDATA = WEŹDANETABELI +HLOOKUP = WYSZUKAJ.POZIOMO +HYPERLINK = HIPERŁĄCZE +INDEX = INDEKS +INDIRECT = ADR.POŚR +LOOKUP = WYSZUKAJ +MATCH = PODAJ.POZYCJĘ +OFFSET = PRZESUNIĘCIE +ROW = WIERSZ +ROWS = ILE.WIERSZY +RTD = DANE.CZASU.RZECZ +TRANSPOSE = TRANSPONUJ +VLOOKUP = WYSZUKAJ.PIONOWO + +## +## Funkcje matematyczne i trygonometryczne (Math & Trig Functions) +## +ABS = MODUŁ.LICZBY +ACOS = ACOS +ACOSH = ACOSH +ACOT = ACOT +ACOTH = ACOTH +AGGREGATE = AGREGUJ +ARABIC = ARABSKIE +ASIN = ASIN +ASINH = ASINH +ATAN = ATAN +ATAN2 = ATAN2 +ATANH = ATANH +BASE = PODSTAWA +CEILING.MATH = ZAOKR.W.GÓRĘ.MATEMATYCZNE +CEILING.PRECISE = ZAOKR.W.GÓRĘ.DOKŁ +COMBIN = KOMBINACJE +COMBINA = KOMBINACJE.A +COS = COS +COSH = COSH +COT = COT +COTH = COTH +CSC = CSC +CSCH = CSCH +DECIMAL = DZIESIĘTNA +DEGREES = STOPNIE +ECMA.CEILING = ECMA.ZAOKR.W.GÓRĘ +EVEN = ZAOKR.DO.PARZ +EXP = EXP +FACT = SILNIA +FACTDOUBLE = SILNIA.DWUKR +FLOOR.MATH = ZAOKR.W.DÓŁ.MATEMATYCZNE +FLOOR.PRECISE = ZAOKR.W.DÓŁ.DOKŁ +GCD = NAJW.WSP.DZIEL +INT = ZAOKR.DO.CAŁK +ISO.CEILING = ISO.ZAOKR.W.GÓRĘ +LCM = NAJMN.WSP.WIEL +LN = LN +LOG = LOG +LOG10 = LOG10 +MDETERM = WYZNACZNIK.MACIERZY +MINVERSE = MACIERZ.ODW +MMULT = MACIERZ.ILOCZYN +MOD = MOD +MROUND = ZAOKR.DO.WIELOKR +MULTINOMIAL = WIELOMIAN +MUNIT = MACIERZ.JEDNOSTKOWA +ODD = ZAOKR.DO.NPARZ +PI = PI +POWER = POTĘGA +PRODUCT = ILOCZYN +QUOTIENT = CZ.CAŁK.DZIELENIA +RADIANS = RADIANY +RAND = LOS +RANDBETWEEN = LOS.ZAKR +ROMAN = RZYMSKIE +ROUND = ZAOKR +ROUNDBAHTDOWN = ZAOKR.DÓŁ.BAT +ROUNDBAHTUP = ZAOKR.GÓRA.BAT +ROUNDDOWN = ZAOKR.DÓŁ +ROUNDUP = ZAOKR.GÓRA +SEC = SEC +SECH = SECH +SERIESSUM = SUMA.SZER.POT +SIGN = ZNAK.LICZBY +SIN = SIN +SINH = SINH +SQRT = PIERWIASTEK +SQRTPI = PIERW.PI +SUBTOTAL = SUMY.CZĘŚCIOWE +SUM = SUMA +SUMIF = SUMA.JEŻELI +SUMIFS = SUMA.WARUNKÓW +SUMPRODUCT = SUMA.ILOCZYNÓW +SUMSQ = SUMA.KWADRATÓW +SUMX2MY2 = SUMA.X2.M.Y2 +SUMX2PY2 = SUMA.X2.P.Y2 +SUMXMY2 = SUMA.XMY.2 +TAN = TAN +TANH = TANH +TRUNC = LICZBA.CAŁK + +## +## Funkcje statystyczne (Statistical Functions) +## +AVEDEV = ODCH.ŚREDNIE +AVERAGE = ŚREDNIA +AVERAGEA = ŚREDNIA.A +AVERAGEIF = ŚREDNIA.JEŻELI +AVERAGEIFS = ŚREDNIA.WARUNKÓW +BETA.DIST = ROZKŁ.BETA +BETA.INV = ROZKŁ.BETA.ODWR +BINOM.DIST = ROZKŁ.DWUM +BINOM.DIST.RANGE = ROZKŁ.DWUM.ZAKRES +BINOM.INV = ROZKŁ.DWUM.ODWR +CHISQ.DIST = ROZKŁ.CHI +CHISQ.DIST.RT = ROZKŁ.CHI.PS +CHISQ.INV = ROZKŁ.CHI.ODWR +CHISQ.INV.RT = ROZKŁ.CHI.ODWR.PS +CHISQ.TEST = CHI.TEST +CONFIDENCE.NORM = UFNOŚĆ.NORM +CONFIDENCE.T = UFNOŚĆ.T +CORREL = WSP.KORELACJI +COUNT = ILE.LICZB +COUNTA = ILE.NIEPUSTYCH +COUNTBLANK = LICZ.PUSTE +COUNTIF = LICZ.JEŻELI +COUNTIFS = LICZ.WARUNKI +COVARIANCE.P = KOWARIANCJA.POPUL +COVARIANCE.S = KOWARIANCJA.PRÓBKI +DEVSQ = ODCH.KWADRATOWE +EXPON.DIST = ROZKŁ.EXP +F.DIST = ROZKŁ.F +F.DIST.RT = ROZKŁ.F.PS +F.INV = ROZKŁ.F.ODWR +F.INV.RT = ROZKŁ.F.ODWR.PS +F.TEST = F.TEST +FISHER = ROZKŁAD.FISHER +FISHERINV = ROZKŁAD.FISHER.ODW +FORECAST.ETS = REGLINX.ETS +FORECAST.ETS.CONFINT = REGLINX.ETS.CONFINT +FORECAST.ETS.SEASONALITY = REGLINX.ETS.SEZONOWOŚĆ +FORECAST.ETS.STAT = REGLINX.ETS.STATYSTYKA +FORECAST.LINEAR = REGLINX.LINIOWA +FREQUENCY = CZĘSTOŚĆ +GAMMA = GAMMA +GAMMA.DIST = ROZKŁ.GAMMA +GAMMA.INV = ROZKŁ.GAMMA.ODWR +GAMMALN = ROZKŁAD.LIN.GAMMA +GAMMALN.PRECISE = ROZKŁAD.LIN.GAMMA.DOKŁ +GAUSS = GAUSS +GEOMEAN = ŚREDNIA.GEOMETRYCZNA +GROWTH = REGEXPW +HARMEAN = ŚREDNIA.HARMONICZNA +HYPGEOM.DIST = ROZKŁ.HIPERGEOM +INTERCEPT = ODCIĘTA +KURT = KURTOZA +LARGE = MAX.K +LINEST = REGLINP +LOGEST = REGEXPP +LOGNORM.DIST = ROZKŁ.LOG +LOGNORM.INV = ROZKŁ.LOG.ODWR +MAX = MAX +MAXA = MAX.A +MAXIFS = MAKS.WARUNKÓW +MEDIAN = MEDIANA +MIN = MIN +MINA = MIN.A +MINIFS = MIN.WARUNKÓW +MODE.MULT = WYST.NAJCZĘŚCIEJ.TABL +MODE.SNGL = WYST.NAJCZĘŚCIEJ.WART +NEGBINOM.DIST = ROZKŁ.DWUM.PRZEC +NORM.DIST = ROZKŁ.NORMALNY +NORM.INV = ROZKŁ.NORMALNY.ODWR +NORM.S.DIST = ROZKŁ.NORMALNY.S +NORM.S.INV = ROZKŁ.NORMALNY.S.ODWR +PEARSON = PEARSON +PERCENTILE.EXC = PERCENTYL.PRZEDZ.OTW +PERCENTILE.INC = PERCENTYL.PRZEDZ.ZAMK +PERCENTRANK.EXC = PROC.POZ.PRZEDZ.OTW +PERCENTRANK.INC = PROC.POZ.PRZEDZ.ZAMK +PERMUT = PERMUTACJE +PERMUTATIONA = PERMUTACJE.A +PHI = PHI +POISSON.DIST = ROZKŁ.POISSON +PROB = PRAWDPD +QUARTILE.EXC = KWARTYL.PRZEDZ.OTW +QUARTILE.INC = KWARTYL.PRZEDZ.ZAMK +RANK.AVG = POZYCJA.ŚR +RANK.EQ = POZYCJA.NAJW +RSQ = R.KWADRAT +SKEW = SKOŚNOŚĆ +SKEW.P = SKOŚNOŚĆ.P +SLOPE = NACHYLENIE +SMALL = MIN.K +STANDARDIZE = NORMALIZUJ +STDEV.P = ODCH.STAND.POPUL +STDEV.S = ODCH.STANDARD.PRÓBKI +STDEVA = ODCH.STANDARDOWE.A +STDEVPA = ODCH.STANDARD.POPUL.A +STEYX = REGBŁSTD +T.DIST = ROZKŁ.T +T.DIST.2T = ROZKŁ.T.DS +T.DIST.RT = ROZKŁ.T.PS +T.INV = ROZKŁ.T.ODWR +T.INV.2T = ROZKŁ.T.ODWR.DS +T.TEST = T.TEST +TREND = REGLINW +TRIMMEAN = ŚREDNIA.WEWN +VAR.P = WARIANCJA.POP +VAR.S = WARIANCJA.PRÓBKI +VARA = WARIANCJA.A +VARPA = WARIANCJA.POPUL.A +WEIBULL.DIST = ROZKŁ.WEIBULL +Z.TEST = Z.TEST + +## +## Funkcje tekstowe (Text Functions) +## +BAHTTEXT = BAT.TEKST +CHAR = ZNAK +CLEAN = OCZYŚĆ +CODE = KOD +CONCAT = ZŁĄCZ.TEKST +DOLLAR = KWOTA +EXACT = PORÓWNAJ +FIND = ZNAJDŹ +FIXED = ZAOKR.DO.TEKST +ISTHAIDIGIT = CZY.CYFRA.TAJ +LEFT = LEWY +LEN = DŁ +LOWER = LITERY.MAŁE +MID = FRAGMENT.TEKSTU +NUMBERSTRING = LICZBA.CIĄG.ZNAK +NUMBERVALUE = WARTOŚĆ.LICZBOWA +PROPER = Z.WIELKIEJ.LITERY +REPLACE = ZASTĄP +REPT = POWT +RIGHT = PRAWY +SEARCH = SZUKAJ.TEKST +SUBSTITUTE = PODSTAW +T = T +TEXT = TEKST +TEXTJOIN = POŁĄCZ.TEKSTY +THAIDIGIT = TAJ.CYFRA +THAINUMSOUND = TAJ.DŹWIĘK.NUM +THAINUMSTRING = TAJ.CIĄG.NUM +THAISTRINGLENGTH = TAJ.DŁUGOŚĆ.CIĄGU +TRIM = USUŃ.ZBĘDNE.ODSTĘPY +UNICHAR = ZNAK.UNICODE +UNICODE = UNICODE +UPPER = LITERY.WIELKIE +VALUE = WARTOŚĆ + +## +## Funkcje sieci Web (Web Functions) +## +ENCODEURL = ENCODEURL +FILTERXML = FILTERXML +WEBSERVICE = WEBSERVICE + +## +## Funkcje zgodności (Compatibility Functions) +## +BETADIST = ROZKŁAD.BETA +BETAINV = ROZKŁAD.BETA.ODW +BINOMDIST = ROZKŁAD.DWUM +CEILING = ZAOKR.W.GÓRĘ +CHIDIST = ROZKŁAD.CHI +CHIINV = ROZKŁAD.CHI.ODW +CHITEST = TEST.CHI +CONCATENATE = ZŁĄCZ.TEKSTY +CONFIDENCE = UFNOŚĆ +COVAR = KOWARIANCJA +CRITBINOM = PRÓG.ROZKŁAD.DWUM +EXPONDIST = ROZKŁAD.EXP +FDIST = ROZKŁAD.F +FINV = ROZKŁAD.F.ODW +FLOOR = ZAOKR.W.DÓŁ +FORECAST = REGLINX +FTEST = TEST.F +GAMMADIST = ROZKŁAD.GAMMA +GAMMAINV = ROZKŁAD.GAMMA.ODW +HYPGEOMDIST = ROZKŁAD.HIPERGEOM +LOGINV = ROZKŁAD.LOG.ODW +LOGNORMDIST = ROZKŁAD.LOG +MODE = WYST.NAJCZĘŚCIEJ +NEGBINOMDIST = ROZKŁAD.DWUM.PRZEC +NORMDIST = ROZKŁAD.NORMALNY +NORMINV = ROZKŁAD.NORMALNY.ODW +NORMSDIST = ROZKŁAD.NORMALNY.S +NORMSINV = ROZKŁAD.NORMALNY.S.ODW +PERCENTILE = PERCENTYL +PERCENTRANK = PROCENT.POZYCJA +POISSON = ROZKŁAD.POISSON +QUARTILE = KWARTYL +RANK = POZYCJA +STDEV = ODCH.STANDARDOWE +STDEVP = ODCH.STANDARD.POPUL +TDIST = ROZKŁAD.T +TINV = ROZKŁAD.T.ODW +TTEST = TEST.T +VAR = WARIANCJA +VARP = WARIANCJA.POPUL +WEIBULL = ROZKŁAD.WEIBULL +ZTEST = TEST.Z diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/br/config b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/br/config new file mode 100644 index 0000000..c39057c --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/br/config @@ -0,0 +1,20 @@ +############################################################ +## +## PhpSpreadsheet - locale settings +## +## Português Brasileiro (Brazilian Portuguese) +## +############################################################ + +ArgumentSeparator = ; + +## +## Error Codes +## +NULL = #NULO! +DIV0 +VALUE = #VALOR! +REF +NAME = #NOME? +NUM = #NÚM! +NA = #N/D diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/br/functions b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/br/functions new file mode 100644 index 0000000..feba30d --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/br/functions @@ -0,0 +1,527 @@ +############################################################ +## +## PhpSpreadsheet - function name translations +## +## Português Brasileiro (Brazilian Portuguese) +## +############################################################ + + +## +## Funções de cubo (Cube Functions) +## +CUBEKPIMEMBER = MEMBROKPICUBO +CUBEMEMBER = MEMBROCUBO +CUBEMEMBERPROPERTY = PROPRIEDADEMEMBROCUBO +CUBERANKEDMEMBER = MEMBROCLASSIFICADOCUBO +CUBESET = CONJUNTOCUBO +CUBESETCOUNT = CONTAGEMCONJUNTOCUBO +CUBEVALUE = VALORCUBO + +## +## Funções de banco de dados (Database Functions) +## +DAVERAGE = BDMÉDIA +DCOUNT = BDCONTAR +DCOUNTA = BDCONTARA +DGET = BDEXTRAIR +DMAX = BDMÁX +DMIN = BDMÍN +DPRODUCT = BDMULTIPL +DSTDEV = BDEST +DSTDEVP = BDDESVPA +DSUM = BDSOMA +DVAR = BDVAREST +DVARP = BDVARP + +## +## Funções de data e hora (Date & Time Functions) +## +DATE = DATA +DATEDIF = DATADIF +DATESTRING = DATA.SÉRIE +DATEVALUE = DATA.VALOR +DAY = DIA +DAYS = DIAS +DAYS360 = DIAS360 +EDATE = DATAM +EOMONTH = FIMMÊS +HOUR = HORA +ISOWEEKNUM = NÚMSEMANAISO +MINUTE = MINUTO +MONTH = MÊS +NETWORKDAYS = DIATRABALHOTOTAL +NETWORKDAYS.INTL = DIATRABALHOTOTAL.INTL +NOW = AGORA +SECOND = SEGUNDO +TIME = TEMPO +TIMEVALUE = VALOR.TEMPO +TODAY = HOJE +WEEKDAY = DIA.DA.SEMANA +WEEKNUM = NÚMSEMANA +WORKDAY = DIATRABALHO +WORKDAY.INTL = DIATRABALHO.INTL +YEAR = ANO +YEARFRAC = FRAÇÃOANO + +## +## Funções de engenharia (Engineering Functions) +## +BESSELI = BESSELI +BESSELJ = BESSELJ +BESSELK = BESSELK +BESSELY = BESSELY +BIN2DEC = BINADEC +BIN2HEX = BINAHEX +BIN2OCT = BINAOCT +BITAND = BITAND +BITLSHIFT = DESLOCESQBIT +BITOR = BITOR +BITRSHIFT = DESLOCDIRBIT +BITXOR = BITXOR +COMPLEX = COMPLEXO +CONVERT = CONVERTER +DEC2BIN = DECABIN +DEC2HEX = DECAHEX +DEC2OCT = DECAOCT +DELTA = DELTA +ERF = FUNERRO +ERF.PRECISE = FUNERRO.PRECISO +ERFC = FUNERROCOMPL +ERFC.PRECISE = FUNERROCOMPL.PRECISO +GESTEP = DEGRAU +HEX2BIN = HEXABIN +HEX2DEC = HEXADEC +HEX2OCT = HEXAOCT +IMABS = IMABS +IMAGINARY = IMAGINÁRIO +IMARGUMENT = IMARG +IMCONJUGATE = IMCONJ +IMCOS = IMCOS +IMCOSH = IMCOSH +IMCOT = IMCOT +IMCSC = IMCOSEC +IMCSCH = IMCOSECH +IMDIV = IMDIV +IMEXP = IMEXP +IMLN = IMLN +IMLOG10 = IMLOG10 +IMLOG2 = IMLOG2 +IMPOWER = IMPOT +IMPRODUCT = IMPROD +IMREAL = IMREAL +IMSEC = IMSEC +IMSECH = IMSECH +IMSIN = IMSENO +IMSINH = IMSENH +IMSQRT = IMRAIZ +IMSUB = IMSUBTR +IMSUM = IMSOMA +IMTAN = IMTAN +OCT2BIN = OCTABIN +OCT2DEC = OCTADEC +OCT2HEX = OCTAHEX + +## +## Funções financeiras (Financial Functions) +## +ACCRINT = JUROSACUM +ACCRINTM = JUROSACUMV +AMORDEGRC = AMORDEGRC +AMORLINC = AMORLINC +COUPDAYBS = CUPDIASINLIQ +COUPDAYS = CUPDIAS +COUPDAYSNC = CUPDIASPRÓX +COUPNCD = CUPDATAPRÓX +COUPNUM = CUPNÚM +COUPPCD = CUPDATAANT +CUMIPMT = PGTOJURACUM +CUMPRINC = PGTOCAPACUM +DB = BD +DDB = BDD +DISC = DESC +DOLLARDE = MOEDADEC +DOLLARFR = MOEDAFRA +DURATION = DURAÇÃO +EFFECT = EFETIVA +FV = VF +FVSCHEDULE = VFPLANO +INTRATE = TAXAJUROS +IPMT = IPGTO +IRR = TIR +ISPMT = ÉPGTO +MDURATION = MDURAÇÃO +MIRR = MTIR +NOMINAL = NOMINAL +NPER = NPER +NPV = VPL +ODDFPRICE = PREÇOPRIMINC +ODDFYIELD = LUCROPRIMINC +ODDLPRICE = PREÇOÚLTINC +ODDLYIELD = LUCROÚLTINC +PDURATION = DURAÇÃOP +PMT = PGTO +PPMT = PPGTO +PRICE = PREÇO +PRICEDISC = PREÇODESC +PRICEMAT = PREÇOVENC +PV = VP +RATE = TAXA +RECEIVED = RECEBER +RRI = TAXAJURO +SLN = DPD +SYD = SDA +TBILLEQ = OTN +TBILLPRICE = OTNVALOR +TBILLYIELD = OTNLUCRO +VDB = BDV +XIRR = XTIR +XNPV = XVPL +YIELD = LUCRO +YIELDDISC = LUCRODESC +YIELDMAT = LUCROVENC + +## +## Funções de informação (Information Functions) +## +CELL = CÉL +ERROR.TYPE = TIPO.ERRO +INFO = INFORMAÇÃO +ISBLANK = ÉCÉL.VAZIA +ISERR = ÉERRO +ISERROR = ÉERROS +ISEVEN = ÉPAR +ISFORMULA = ÉFÓRMULA +ISLOGICAL = ÉLÓGICO +ISNA = É.NÃO.DISP +ISNONTEXT = É.NÃO.TEXTO +ISNUMBER = ÉNÚM +ISODD = ÉIMPAR +ISREF = ÉREF +ISTEXT = ÉTEXTO +N = N +NA = NÃO.DISP +SHEET = PLAN +SHEETS = PLANS +TYPE = TIPO + +## +## Funções lógicas (Logical Functions) +## +AND = E +FALSE = FALSO +IF = SE +IFERROR = SEERRO +IFNA = SENÃODISP +IFS = SES +NOT = NÃO +OR = OU +SWITCH = PARÂMETRO +TRUE = VERDADEIRO +XOR = XOR + +## +## Funções de pesquisa e referência (Lookup & Reference Functions) +## +ADDRESS = ENDEREÇO +AREAS = ÁREAS +CHOOSE = ESCOLHER +COLUMN = COL +COLUMNS = COLS +FORMULATEXT = FÓRMULATEXTO +GETPIVOTDATA = INFODADOSTABELADINÂMICA +HLOOKUP = PROCH +HYPERLINK = HIPERLINK +INDEX = ÍNDICE +INDIRECT = INDIRETO +LOOKUP = PROC +MATCH = CORRESP +OFFSET = DESLOC +ROW = LIN +ROWS = LINS +RTD = RTD +TRANSPOSE = TRANSPOR +VLOOKUP = PROCV + +## +## Funções matemáticas e trigonométricas (Math & Trig Functions) +## +ABS = ABS +ACOS = ACOS +ACOSH = ACOSH +ACOT = ACOT +ACOTH = ACOTH +AGGREGATE = AGREGAR +ARABIC = ARÁBICO +ASIN = ASEN +ASINH = ASENH +ATAN = ATAN +ATAN2 = ATAN2 +ATANH = ATANH +BASE = BASE +CEILING.MATH = TETO.MAT +CEILING.PRECISE = TETO.PRECISO +COMBIN = COMBIN +COMBINA = COMBINA +COS = COS +COSH = COSH +COT = COT +COTH = COTH +CSC = COSEC +CSCH = COSECH +DECIMAL = DECIMAL +DEGREES = GRAUS +ECMA.CEILING = ECMA.TETO +EVEN = PAR +EXP = EXP +FACT = FATORIAL +FACTDOUBLE = FATDUPLO +FLOOR.MATH = ARREDMULTB.MAT +FLOOR.PRECISE = ARREDMULTB.PRECISO +GCD = MDC +INT = INT +ISO.CEILING = ISO.TETO +LCM = MMC +LN = LN +LOG = LOG +LOG10 = LOG10 +MDETERM = MATRIZ.DETERM +MINVERSE = MATRIZ.INVERSO +MMULT = MATRIZ.MULT +MOD = MOD +MROUND = MARRED +MULTINOMIAL = MULTINOMIAL +MUNIT = MUNIT +ODD = ÍMPAR +PI = PI +POWER = POTÊNCIA +PRODUCT = MULT +QUOTIENT = QUOCIENTE +RADIANS = RADIANOS +RAND = ALEATÓRIO +RANDBETWEEN = ALEATÓRIOENTRE +ROMAN = ROMANO +ROUND = ARRED +ROUNDDOWN = ARREDONDAR.PARA.BAIXO +ROUNDUP = ARREDONDAR.PARA.CIMA +SEC = SEC +SECH = SECH +SERIESSUM = SOMASEQÜÊNCIA +SIGN = SINAL +SIN = SEN +SINH = SENH +SQRT = RAIZ +SQRTPI = RAIZPI +SUBTOTAL = SUBTOTAL +SUM = SOMA +SUMIF = SOMASE +SUMIFS = SOMASES +SUMPRODUCT = SOMARPRODUTO +SUMSQ = SOMAQUAD +SUMX2MY2 = SOMAX2DY2 +SUMX2PY2 = SOMAX2SY2 +SUMXMY2 = SOMAXMY2 +TAN = TAN +TANH = TANH +TRUNC = TRUNCAR + +## +## Funções estatísticas (Statistical Functions) +## +AVEDEV = DESV.MÉDIO +AVERAGE = MÉDIA +AVERAGEA = MÉDIAA +AVERAGEIF = MÉDIASE +AVERAGEIFS = MÉDIASES +BETA.DIST = DIST.BETA +BETA.INV = INV.BETA +BINOM.DIST = DISTR.BINOM +BINOM.DIST.RANGE = INTERV.DISTR.BINOM +BINOM.INV = INV.BINOM +CHISQ.DIST = DIST.QUIQUA +CHISQ.DIST.RT = DIST.QUIQUA.CD +CHISQ.INV = INV.QUIQUA +CHISQ.INV.RT = INV.QUIQUA.CD +CHISQ.TEST = TESTE.QUIQUA +CONFIDENCE.NORM = INT.CONFIANÇA.NORM +CONFIDENCE.T = INT.CONFIANÇA.T +CORREL = CORREL +COUNT = CONT.NÚM +COUNTA = CONT.VALORES +COUNTBLANK = CONTAR.VAZIO +COUNTIF = CONT.SE +COUNTIFS = CONT.SES +COVARIANCE.P = COVARIAÇÃO.P +COVARIANCE.S = COVARIAÇÃO.S +DEVSQ = DESVQ +EXPON.DIST = DISTR.EXPON +F.DIST = DIST.F +F.DIST.RT = DIST.F.CD +F.INV = INV.F +F.INV.RT = INV.F.CD +F.TEST = TESTE.F +FISHER = FISHER +FISHERINV = FISHERINV +FORECAST.ETS = PREVISÃO.ETS +FORECAST.ETS.CONFINT = PREVISÃO.ETS.CONFINT +FORECAST.ETS.SEASONALITY = PREVISÃO.ETS.SAZONALIDADE +FORECAST.ETS.STAT = PREVISÃO.ETS.STAT +FORECAST.LINEAR = PREVISÃO.LINEAR +FREQUENCY = FREQÜÊNCIA +GAMMA = GAMA +GAMMA.DIST = DIST.GAMA +GAMMA.INV = INV.GAMA +GAMMALN = LNGAMA +GAMMALN.PRECISE = LNGAMA.PRECISO +GAUSS = GAUSS +GEOMEAN = MÉDIA.GEOMÉTRICA +GROWTH = CRESCIMENTO +HARMEAN = MÉDIA.HARMÔNICA +HYPGEOM.DIST = DIST.HIPERGEOM.N +INTERCEPT = INTERCEPÇÃO +KURT = CURT +LARGE = MAIOR +LINEST = PROJ.LIN +LOGEST = PROJ.LOG +LOGNORM.DIST = DIST.LOGNORMAL.N +LOGNORM.INV = INV.LOGNORMAL +MAX = MÁXIMO +MAXA = MÁXIMOA +MAXIFS = MÁXIMOSES +MEDIAN = MED +MIN = MÍNIMO +MINA = MÍNIMOA +MINIFS = MÍNIMOSES +MODE.MULT = MODO.MULT +MODE.SNGL = MODO.ÚNICO +NEGBINOM.DIST = DIST.BIN.NEG.N +NORM.DIST = DIST.NORM.N +NORM.INV = INV.NORM.N +NORM.S.DIST = DIST.NORMP.N +NORM.S.INV = INV.NORMP.N +PEARSON = PEARSON +PERCENTILE.EXC = PERCENTIL.EXC +PERCENTILE.INC = PERCENTIL.INC +PERCENTRANK.EXC = ORDEM.PORCENTUAL.EXC +PERCENTRANK.INC = ORDEM.PORCENTUAL.INC +PERMUT = PERMUT +PERMUTATIONA = PERMUTAS +PHI = PHI +POISSON.DIST = DIST.POISSON +PROB = PROB +QUARTILE.EXC = QUARTIL.EXC +QUARTILE.INC = QUARTIL.INC +RANK.AVG = ORDEM.MÉD +RANK.EQ = ORDEM.EQ +RSQ = RQUAD +SKEW = DISTORÇÃO +SKEW.P = DISTORÇÃO.P +SLOPE = INCLINAÇÃO +SMALL = MENOR +STANDARDIZE = PADRONIZAR +STDEV.P = DESVPAD.P +STDEV.S = DESVPAD.A +STDEVA = DESVPADA +STDEVPA = DESVPADPA +STEYX = EPADYX +T.DIST = DIST.T +T.DIST.2T = DIST.T.BC +T.DIST.RT = DIST.T.CD +T.INV = INV.T +T.INV.2T = INV.T.BC +T.TEST = TESTE.T +TREND = TENDÊNCIA +TRIMMEAN = MÉDIA.INTERNA +VAR.P = VAR.P +VAR.S = VAR.A +VARA = VARA +VARPA = VARPA +WEIBULL.DIST = DIST.WEIBULL +Z.TEST = TESTE.Z + +## +## Funções de texto (Text Functions) +## +BAHTTEXT = BAHTTEXT +CHAR = CARACT +CLEAN = TIRAR +CODE = CÓDIGO +CONCAT = CONCAT +DOLLAR = MOEDA +EXACT = EXATO +FIND = PROCURAR +FIXED = DEF.NÚM.DEC +LEFT = ESQUERDA +LEN = NÚM.CARACT +LOWER = MINÚSCULA +MID = EXT.TEXTO +NUMBERSTRING = SEQÜÊNCIA.NÚMERO +NUMBERVALUE = VALORNUMÉRICO +PHONETIC = FONÉTICA +PROPER = PRI.MAIÚSCULA +REPLACE = MUDAR +REPT = REPT +RIGHT = DIREITA +SEARCH = LOCALIZAR +SUBSTITUTE = SUBSTITUIR +T = T +TEXT = TEXTO +TEXTJOIN = UNIRTEXTO +TRIM = ARRUMAR +UNICHAR = CARACTUNICODE +UNICODE = UNICODE +UPPER = MAIÚSCULA +VALUE = VALOR + +## +## Funções da Web (Web Functions) +## +ENCODEURL = CODIFURL +FILTERXML = FILTROXML +WEBSERVICE = SERVIÇOWEB + +## +## Funções de compatibilidade (Compatibility Functions) +## +BETADIST = DISTBETA +BETAINV = BETA.ACUM.INV +BINOMDIST = DISTRBINOM +CEILING = TETO +CHIDIST = DIST.QUI +CHIINV = INV.QUI +CHITEST = TESTE.QUI +CONCATENATE = CONCATENAR +CONFIDENCE = INT.CONFIANÇA +COVAR = COVAR +CRITBINOM = CRIT.BINOM +EXPONDIST = DISTEXPON +FDIST = DISTF +FINV = INVF +FLOOR = ARREDMULTB +FORECAST = PREVISÃO +FTEST = TESTEF +GAMMADIST = DISTGAMA +GAMMAINV = INVGAMA +HYPGEOMDIST = DIST.HIPERGEOM +LOGINV = INVLOG +LOGNORMDIST = DIST.LOGNORMAL +MODE = MODO +NEGBINOMDIST = DIST.BIN.NEG +NORMDIST = DISTNORM +NORMINV = INV.NORM +NORMSDIST = DISTNORMP +NORMSINV = INV.NORMP +PERCENTILE = PERCENTIL +PERCENTRANK = ORDEM.PORCENTUAL +POISSON = POISSON +QUARTILE = QUARTIL +RANK = ORDEM +STDEV = DESVPAD +STDEVP = DESVPADP +TDIST = DISTT +TINV = INVT +TTEST = TESTET +VAR = VAR +VARP = VARP +WEIBULL = WEIBULL +ZTEST = TESTEZ diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/config b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/config new file mode 100644 index 0000000..e661830 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/config @@ -0,0 +1,20 @@ +############################################################ +## +## PhpSpreadsheet - locale settings +## +## Português (Portuguese) +## +############################################################ + +ArgumentSeparator = ; + +## +## Error Codes +## +NULL = #NULO! +DIV0 +VALUE = #VALOR! +REF +NAME = #NOME? +NUM = #NÚM! +NA = #N/D diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/functions b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/functions new file mode 100644 index 0000000..8a94d82 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/functions @@ -0,0 +1,537 @@ +############################################################ +## +## PhpSpreadsheet - function name translations +## +## Português (Portuguese) +## +############################################################ + + +## +## Funções de cubo (Cube Functions) +## +CUBEKPIMEMBER = MEMBROKPICUBO +CUBEMEMBER = MEMBROCUBO +CUBEMEMBERPROPERTY = PROPRIEDADEMEMBROCUBO +CUBERANKEDMEMBER = MEMBROCLASSIFICADOCUBO +CUBESET = CONJUNTOCUBO +CUBESETCOUNT = CONTARCONJUNTOCUBO +CUBEVALUE = VALORCUBO + +## +## Funções de base de dados (Database Functions) +## +DAVERAGE = BDMÉDIA +DCOUNT = BDCONTAR +DCOUNTA = BDCONTAR.VAL +DGET = BDOBTER +DMAX = BDMÁX +DMIN = BDMÍN +DPRODUCT = BDMULTIPL +DSTDEV = BDDESVPAD +DSTDEVP = BDDESVPADP +DSUM = BDSOMA +DVAR = BDVAR +DVARP = BDVARP + +## +## Funções de data e hora (Date & Time Functions) +## +DATE = DATA +DATEDIF = DATADIF +DATESTRING = DATA.CADEIA +DATEVALUE = DATA.VALOR +DAY = DIA +DAYS = DIAS +DAYS360 = DIAS360 +EDATE = DATAM +EOMONTH = FIMMÊS +HOUR = HORA +ISOWEEKNUM = NUMSEMANAISO +MINUTE = MINUTO +MONTH = MÊS +NETWORKDAYS = DIATRABALHOTOTAL +NETWORKDAYS.INTL = DIATRABALHOTOTAL.INTL +NOW = AGORA +SECOND = SEGUNDO +THAIDAYOFWEEK = DIA.DA.SEMANA.TAILANDÊS +THAIMONTHOFYEAR = MÊS.DO.ANO.TAILANDÊS +THAIYEAR = ANO.TAILANDÊS +TIME = TEMPO +TIMEVALUE = VALOR.TEMPO +TODAY = HOJE +WEEKDAY = DIA.SEMANA +WEEKNUM = NÚMSEMANA +WORKDAY = DIATRABALHO +WORKDAY.INTL = DIATRABALHO.INTL +YEAR = ANO +YEARFRAC = FRAÇÃOANO + +## +## Funções de engenharia (Engineering Functions) +## +BESSELI = BESSELI +BESSELJ = BESSELJ +BESSELK = BESSELK +BESSELY = BESSELY +BIN2DEC = BINADEC +BIN2HEX = BINAHEX +BIN2OCT = BINAOCT +BITAND = BIT.E +BITLSHIFT = BITDESL.ESQ +BITOR = BIT.OU +BITRSHIFT = BITDESL.DIR +BITXOR = BIT.XOU +COMPLEX = COMPLEXO +CONVERT = CONVERTER +DEC2BIN = DECABIN +DEC2HEX = DECAHEX +DEC2OCT = DECAOCT +DELTA = DELTA +ERF = FUNCERRO +ERF.PRECISE = FUNCERRO.PRECISO +ERFC = FUNCERROCOMPL +ERFC.PRECISE = FUNCERROCOMPL.PRECISO +GESTEP = DEGRAU +HEX2BIN = HEXABIN +HEX2DEC = HEXADEC +HEX2OCT = HEXAOCT +IMABS = IMABS +IMAGINARY = IMAGINÁRIO +IMARGUMENT = IMARG +IMCONJUGATE = IMCONJ +IMCOS = IMCOS +IMCOSH = IMCOSH +IMCOT = IMCOT +IMCSC = IMCSC +IMCSCH = IMCSCH +IMDIV = IMDIV +IMEXP = IMEXP +IMLN = IMLN +IMLOG10 = IMLOG10 +IMLOG2 = IMLOG2 +IMPOWER = IMPOT +IMPRODUCT = IMPROD +IMREAL = IMREAL +IMSEC = IMSEC +IMSECH = IMSECH +IMSIN = IMSENO +IMSINH = IMSENOH +IMSQRT = IMRAIZ +IMSUB = IMSUBTR +IMSUM = IMSOMA +IMTAN = IMTAN +OCT2BIN = OCTABIN +OCT2DEC = OCTADEC +OCT2HEX = OCTAHEX + +## +## Funções financeiras (Financial Functions) +## +ACCRINT = JUROSACUM +ACCRINTM = JUROSACUMV +AMORDEGRC = AMORDEGRC +AMORLINC = AMORLINC +COUPDAYBS = CUPDIASINLIQ +COUPDAYS = CUPDIAS +COUPDAYSNC = CUPDIASPRÓX +COUPNCD = CUPDATAPRÓX +COUPNUM = CUPNÚM +COUPPCD = CUPDATAANT +CUMIPMT = PGTOJURACUM +CUMPRINC = PGTOCAPACUM +DB = BD +DDB = BDD +DISC = DESC +DOLLARDE = MOEDADEC +DOLLARFR = MOEDAFRA +DURATION = DURAÇÃO +EFFECT = EFETIVA +FV = VF +FVSCHEDULE = VFPLANO +INTRATE = TAXAJUROS +IPMT = IPGTO +IRR = TIR +ISPMT = É.PGTO +MDURATION = MDURAÇÃO +MIRR = MTIR +NOMINAL = NOMINAL +NPER = NPER +NPV = VAL +ODDFPRICE = PREÇOPRIMINC +ODDFYIELD = LUCROPRIMINC +ODDLPRICE = PREÇOÚLTINC +ODDLYIELD = LUCROÚLTINC +PDURATION = PDURAÇÃO +PMT = PGTO +PPMT = PPGTO +PRICE = PREÇO +PRICEDISC = PREÇODESC +PRICEMAT = PREÇOVENC +PV = VA +RATE = TAXA +RECEIVED = RECEBER +RRI = DEVOLVERTAXAJUROS +SLN = AMORT +SYD = AMORTD +TBILLEQ = OTN +TBILLPRICE = OTNVALOR +TBILLYIELD = OTNLUCRO +VDB = BDV +XIRR = XTIR +XNPV = XVAL +YIELD = LUCRO +YIELDDISC = LUCRODESC +YIELDMAT = LUCROVENC + +## +## Funções de informação (Information Functions) +## +CELL = CÉL +ERROR.TYPE = TIPO.ERRO +INFO = INFORMAÇÃO +ISBLANK = É.CÉL.VAZIA +ISERR = É.ERROS +ISERROR = É.ERRO +ISEVEN = ÉPAR +ISFORMULA = É.FORMULA +ISLOGICAL = É.LÓGICO +ISNA = É.NÃO.DISP +ISNONTEXT = É.NÃO.TEXTO +ISNUMBER = É.NÚM +ISODD = ÉÍMPAR +ISREF = É.REF +ISTEXT = É.TEXTO +N = N +NA = NÃO.DISP +SHEET = FOLHA +SHEETS = FOLHAS +TYPE = TIPO + +## +## Funções lógicas (Logical Functions) +## +AND = E +FALSE = FALSO +IF = SE +IFERROR = SE.ERRO +IFNA = SEND +IFS = SE.S +NOT = NÃO +OR = OU +SWITCH = PARÂMETRO +TRUE = VERDADEIRO +XOR = XOU + +## +## Funções de pesquisa e referência (Lookup & Reference Functions) +## +ADDRESS = ENDEREÇO +AREAS = ÁREAS +CHOOSE = SELECIONAR +COLUMN = COL +COLUMNS = COLS +FORMULATEXT = FÓRMULA.TEXTO +GETPIVOTDATA = OBTERDADOSDIN +HLOOKUP = PROCH +HYPERLINK = HIPERLIGAÇÃO +INDEX = ÍNDICE +INDIRECT = INDIRETO +LOOKUP = PROC +MATCH = CORRESP +OFFSET = DESLOCAMENTO +ROW = LIN +ROWS = LINS +RTD = RTD +TRANSPOSE = TRANSPOR +VLOOKUP = PROCV + +## +## Funções matemáticas e trigonométricas (Math & Trig Functions) +## +ABS = ABS +ACOS = ACOS +ACOSH = ACOSH +ACOT = ACOT +ACOTH = ACOTH +AGGREGATE = AGREGAR +ARABIC = ÁRABE +ASIN = ASEN +ASINH = ASENH +ATAN = ATAN +ATAN2 = ATAN2 +ATANH = ATANH +BASE = BASE +CEILING.MATH = ARRED.EXCESSO.MAT +CEILING.PRECISE = ARRED.EXCESSO.PRECISO +COMBIN = COMBIN +COMBINA = COMBIN.R +COS = COS +COSH = COSH +COT = COT +COTH = COTH +CSC = CSC +CSCH = CSCH +DECIMAL = DECIMAL +DEGREES = GRAUS +ECMA.CEILING = ARRED.EXCESSO.ECMA +EVEN = PAR +EXP = EXP +FACT = FATORIAL +FACTDOUBLE = FATDUPLO +FLOOR.MATH = ARRED.DEFEITO.MAT +FLOOR.PRECISE = ARRED.DEFEITO.PRECISO +GCD = MDC +INT = INT +ISO.CEILING = ARRED.EXCESSO.ISO +LCM = MMC +LN = LN +LOG = LOG +LOG10 = LOG10 +MDETERM = MATRIZ.DETERM +MINVERSE = MATRIZ.INVERSA +MMULT = MATRIZ.MULT +MOD = RESTO +MROUND = MARRED +MULTINOMIAL = POLINOMIAL +MUNIT = UNIDM +ODD = ÍMPAR +PI = PI +POWER = POTÊNCIA +PRODUCT = PRODUTO +QUOTIENT = QUOCIENTE +RADIANS = RADIANOS +RAND = ALEATÓRIO +RANDBETWEEN = ALEATÓRIOENTRE +ROMAN = ROMANO +ROUND = ARRED +ROUNDBAHTDOWN = ARREDOND.BAHT.BAIXO +ROUNDBAHTUP = ARREDOND.BAHT.CIMA +ROUNDDOWN = ARRED.PARA.BAIXO +ROUNDUP = ARRED.PARA.CIMA +SEC = SEC +SECH = SECH +SERIESSUM = SOMASÉRIE +SIGN = SINAL +SIN = SEN +SINH = SENH +SQRT = RAIZQ +SQRTPI = RAIZPI +SUBTOTAL = SUBTOTAL +SUM = SOMA +SUMIF = SOMA.SE +SUMIFS = SOMA.SE.S +SUMPRODUCT = SOMARPRODUTO +SUMSQ = SOMARQUAD +SUMX2MY2 = SOMAX2DY2 +SUMX2PY2 = SOMAX2SY2 +SUMXMY2 = SOMAXMY2 +TAN = TAN +TANH = TANH +TRUNC = TRUNCAR + +## +## Funções estatísticas (Statistical Functions) +## +AVEDEV = DESV.MÉDIO +AVERAGE = MÉDIA +AVERAGEA = MÉDIAA +AVERAGEIF = MÉDIA.SE +AVERAGEIFS = MÉDIA.SE.S +BETA.DIST = DIST.BETA +BETA.INV = INV.BETA +BINOM.DIST = DISTR.BINOM +BINOM.DIST.RANGE = DIST.BINOM.INTERVALO +BINOM.INV = INV.BINOM +CHISQ.DIST = DIST.CHIQ +CHISQ.DIST.RT = DIST.CHIQ.DIR +CHISQ.INV = INV.CHIQ +CHISQ.INV.RT = INV.CHIQ.DIR +CHISQ.TEST = TESTE.CHIQ +CONFIDENCE.NORM = INT.CONFIANÇA.NORM +CONFIDENCE.T = INT.CONFIANÇA.T +CORREL = CORREL +COUNT = CONTAR +COUNTA = CONTAR.VAL +COUNTBLANK = CONTAR.VAZIO +COUNTIF = CONTAR.SE +COUNTIFS = CONTAR.SE.S +COVARIANCE.P = COVARIÂNCIA.P +COVARIANCE.S = COVARIÂNCIA.S +DEVSQ = DESVQ +EXPON.DIST = DIST.EXPON +F.DIST = DIST.F +F.DIST.RT = DIST.F.DIR +F.INV = INV.F +F.INV.RT = INV.F.DIR +F.TEST = TESTE.F +FISHER = FISHER +FISHERINV = FISHERINV +FORECAST.ETS = PREVISÃO.ETS +FORECAST.ETS.CONFINT = PREVISÃO.ETS.CONFINT +FORECAST.ETS.SEASONALITY = PREVISÃO.ETS.SAZONALIDADE +FORECAST.ETS.STAT = PREVISÃO.ETS.ESTATÍSTICA +FORECAST.LINEAR = PREVISÃO.LINEAR +FREQUENCY = FREQUÊNCIA +GAMMA = GAMA +GAMMA.DIST = DIST.GAMA +GAMMA.INV = INV.GAMA +GAMMALN = LNGAMA +GAMMALN.PRECISE = LNGAMA.PRECISO +GAUSS = GAUSS +GEOMEAN = MÉDIA.GEOMÉTRICA +GROWTH = CRESCIMENTO +HARMEAN = MÉDIA.HARMÓNICA +HYPGEOM.DIST = DIST.HIPGEOM +INTERCEPT = INTERCETAR +KURT = CURT +LARGE = MAIOR +LINEST = PROJ.LIN +LOGEST = PROJ.LOG +LOGNORM.DIST = DIST.NORMLOG +LOGNORM.INV = INV.NORMALLOG +MAX = MÁXIMO +MAXA = MÁXIMOA +MAXIFS = MÁXIMO.SE.S +MEDIAN = MED +MIN = MÍNIMO +MINA = MÍNIMOA +MINIFS = MÍNIMO.SE.S +MODE.MULT = MODO.MÚLT +MODE.SNGL = MODO.SIMPLES +NEGBINOM.DIST = DIST.BINOM.NEG +NORM.DIST = DIST.NORMAL +NORM.INV = INV.NORMAL +NORM.S.DIST = DIST.S.NORM +NORM.S.INV = INV.S.NORM +PEARSON = PEARSON +PERCENTILE.EXC = PERCENTIL.EXC +PERCENTILE.INC = PERCENTIL.INC +PERCENTRANK.EXC = ORDEM.PERCENTUAL.EXC +PERCENTRANK.INC = ORDEM.PERCENTUAL.INC +PERMUT = PERMUTAR +PERMUTATIONA = PERMUTAR.R +PHI = PHI +POISSON.DIST = DIST.POISSON +PROB = PROB +QUARTILE.EXC = QUARTIL.EXC +QUARTILE.INC = QUARTIL.INC +RANK.AVG = ORDEM.MÉD +RANK.EQ = ORDEM.EQ +RSQ = RQUAD +SKEW = DISTORÇÃO +SKEW.P = DISTORÇÃO.P +SLOPE = DECLIVE +SMALL = MENOR +STANDARDIZE = NORMALIZAR +STDEV.P = DESVPAD.P +STDEV.S = DESVPAD.S +STDEVA = DESVPADA +STDEVPA = DESVPADPA +STEYX = EPADYX +T.DIST = DIST.T +T.DIST.2T = DIST.T.2C +T.DIST.RT = DIST.T.DIR +T.INV = INV.T +T.INV.2T = INV.T.2C +T.TEST = TESTE.T +TREND = TENDÊNCIA +TRIMMEAN = MÉDIA.INTERNA +VAR.P = VAR.P +VAR.S = VAR.S +VARA = VARA +VARPA = VARPA +WEIBULL.DIST = DIST.WEIBULL +Z.TEST = TESTE.Z + +## +## Funções de texto (Text Functions) +## +BAHTTEXT = TEXTO.BAHT +CHAR = CARÁT +CLEAN = LIMPARB +CODE = CÓDIGO +CONCAT = CONCAT +DOLLAR = MOEDA +EXACT = EXATO +FIND = LOCALIZAR +FIXED = FIXA +ISTHAIDIGIT = É.DÍGITO.TAILANDÊS +LEFT = ESQUERDA +LEN = NÚM.CARAT +LOWER = MINÚSCULAS +MID = SEG.TEXTO +NUMBERSTRING = NÚMERO.CADEIA +NUMBERVALUE = VALOR.NÚMERO +PHONETIC = FONÉTICA +PROPER = INICIAL.MAIÚSCULA +REPLACE = SUBSTITUIR +REPT = REPETIR +RIGHT = DIREITA +SEARCH = PROCURAR +SUBSTITUTE = SUBST +T = T +TEXT = TEXTO +TEXTJOIN = UNIRTEXTO +THAIDIGIT = DÍGITO.TAILANDÊS +THAINUMSOUND = SOM.NÚM.TAILANDÊS +THAINUMSTRING = CADEIA.NÚM.TAILANDÊS +THAISTRINGLENGTH = COMP.CADEIA.TAILANDÊS +TRIM = COMPACTAR +UNICHAR = UNICARÁT +UNICODE = UNICODE +UPPER = MAIÚSCULAS +VALUE = VALOR + +## +## Funções da Web (Web Functions) +## +ENCODEURL = CODIFICAÇÃOURL +FILTERXML = FILTRARXML +WEBSERVICE = SERVIÇOWEB + +## +## Funções de compatibilidade (Compatibility Functions) +## +BETADIST = DISTBETA +BETAINV = BETA.ACUM.INV +BINOMDIST = DISTRBINOM +CEILING = ARRED.EXCESSO +CHIDIST = DIST.CHI +CHIINV = INV.CHI +CHITEST = TESTE.CHI +CONCATENATE = CONCATENAR +CONFIDENCE = INT.CONFIANÇA +COVAR = COVAR +CRITBINOM = CRIT.BINOM +EXPONDIST = DISTEXPON +FDIST = DISTF +FINV = INVF +FLOOR = ARRED.DEFEITO +FORECAST = PREVISÃO +FTEST = TESTEF +GAMMADIST = DISTGAMA +GAMMAINV = INVGAMA +HYPGEOMDIST = DIST.HIPERGEOM +LOGINV = INVLOG +LOGNORMDIST = DIST.NORMALLOG +MODE = MODA +NEGBINOMDIST = DIST.BIN.NEG +NORMDIST = DIST.NORM +NORMINV = INV.NORM +NORMSDIST = DIST.NORMP +NORMSINV = INV.NORMP +PERCENTILE = PERCENTIL +PERCENTRANK = ORDEM.PERCENTUAL +POISSON = POISSON +QUARTILE = QUARTIL +RANK = ORDEM +STDEV = DESVPAD +STDEVP = DESVPADP +TDIST = DISTT +TINV = INVT +TTEST = TESTET +VAR = VAR +VARP = VARP +WEIBULL = WEIBULL +ZTEST = TESTEZ diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/ru/config b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/ru/config new file mode 100644 index 0000000..2a5a0db --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/ru/config @@ -0,0 +1,20 @@ +############################################################ +## +## PhpSpreadsheet - locale settings +## +## русский язык (Russian) +## +############################################################ + +ArgumentSeparator = ; + +## +## Error Codes +## +NULL = #ПУСТО! +DIV0 = #ДЕЛ/0! +VALUE = #ЗНАЧ! +REF = #ССЫЛКА! +NAME = #ИМЯ? +NUM = #ЧИСЛО! +NA = #Н/Д diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/ru/functions b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/ru/functions new file mode 100644 index 0000000..7f9ce78 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/ru/functions @@ -0,0 +1,536 @@ +############################################################ +## +## PhpSpreadsheet - function name translations +## +## русский язык (Russian) +## +############################################################ + + +## +## Функции кубов (Cube Functions) +## +CUBEKPIMEMBER = КУБЭЛЕМЕНТКИП +CUBEMEMBER = КУБЭЛЕМЕНТ +CUBEMEMBERPROPERTY = КУБСВОЙСТВОЭЛЕМЕНТА +CUBERANKEDMEMBER = КУБПОРЭЛЕМЕНТ +CUBESET = КУБМНОЖ +CUBESETCOUNT = КУБЧИСЛОЭЛМНОЖ +CUBEVALUE = КУБЗНАЧЕНИЕ + +## +## Функции для работы с базами данных (Database Functions) +## +DAVERAGE = ДСРЗНАЧ +DCOUNT = БСЧЁТ +DCOUNTA = БСЧЁТА +DGET = БИЗВЛЕЧЬ +DMAX = ДМАКС +DMIN = ДМИН +DPRODUCT = БДПРОИЗВЕД +DSTDEV = ДСТАНДОТКЛ +DSTDEVP = ДСТАНДОТКЛП +DSUM = БДСУММ +DVAR = БДДИСП +DVARP = БДДИСПП + +## +## Функции даты и времени (Date & Time Functions) +## +DATE = ДАТА +DATEDIF = РАЗНДАТ +DATESTRING = СТРОКАДАННЫХ +DATEVALUE = ДАТАЗНАЧ +DAY = ДЕНЬ +DAYS = ДНИ +DAYS360 = ДНЕЙ360 +EDATE = ДАТАМЕС +EOMONTH = КОНМЕСЯЦА +HOUR = ЧАС +ISOWEEKNUM = НОМНЕДЕЛИ.ISO +MINUTE = МИНУТЫ +MONTH = МЕСЯЦ +NETWORKDAYS = ЧИСТРАБДНИ +NETWORKDAYS.INTL = ЧИСТРАБДНИ.МЕЖД +NOW = ТДАТА +SECOND = СЕКУНДЫ +THAIDAYOFWEEK = ТАЙДЕНЬНЕД +THAIMONTHOFYEAR = ТАЙМЕСЯЦ +THAIYEAR = ТАЙГОД +TIME = ВРЕМЯ +TIMEVALUE = ВРЕМЗНАЧ +TODAY = СЕГОДНЯ +WEEKDAY = ДЕНЬНЕД +WEEKNUM = НОМНЕДЕЛИ +WORKDAY = РАБДЕНЬ +WORKDAY.INTL = РАБДЕНЬ.МЕЖД +YEAR = ГОД +YEARFRAC = ДОЛЯГОДА + +## +## Инженерные функции (Engineering Functions) +## +BESSELI = БЕССЕЛЬ.I +BESSELJ = БЕССЕЛЬ.J +BESSELK = БЕССЕЛЬ.K +BESSELY = БЕССЕЛЬ.Y +BIN2DEC = ДВ.В.ДЕС +BIN2HEX = ДВ.В.ШЕСТН +BIN2OCT = ДВ.В.ВОСЬМ +BITAND = БИТ.И +BITLSHIFT = БИТ.СДВИГЛ +BITOR = БИТ.ИЛИ +BITRSHIFT = БИТ.СДВИГП +BITXOR = БИТ.ИСКЛИЛИ +COMPLEX = КОМПЛЕКСН +CONVERT = ПРЕОБР +DEC2BIN = ДЕС.В.ДВ +DEC2HEX = ДЕС.В.ШЕСТН +DEC2OCT = ДЕС.В.ВОСЬМ +DELTA = ДЕЛЬТА +ERF = ФОШ +ERF.PRECISE = ФОШ.ТОЧН +ERFC = ДФОШ +ERFC.PRECISE = ДФОШ.ТОЧН +GESTEP = ПОРОГ +HEX2BIN = ШЕСТН.В.ДВ +HEX2DEC = ШЕСТН.В.ДЕС +HEX2OCT = ШЕСТН.В.ВОСЬМ +IMABS = МНИМ.ABS +IMAGINARY = МНИМ.ЧАСТЬ +IMARGUMENT = МНИМ.АРГУМЕНТ +IMCONJUGATE = МНИМ.СОПРЯЖ +IMCOS = МНИМ.COS +IMCOSH = МНИМ.COSH +IMCOT = МНИМ.COT +IMCSC = МНИМ.CSC +IMCSCH = МНИМ.CSCH +IMDIV = МНИМ.ДЕЛ +IMEXP = МНИМ.EXP +IMLN = МНИМ.LN +IMLOG10 = МНИМ.LOG10 +IMLOG2 = МНИМ.LOG2 +IMPOWER = МНИМ.СТЕПЕНЬ +IMPRODUCT = МНИМ.ПРОИЗВЕД +IMREAL = МНИМ.ВЕЩ +IMSEC = МНИМ.SEC +IMSECH = МНИМ.SECH +IMSIN = МНИМ.SIN +IMSINH = МНИМ.SINH +IMSQRT = МНИМ.КОРЕНЬ +IMSUB = МНИМ.РАЗН +IMSUM = МНИМ.СУММ +IMTAN = МНИМ.TAN +OCT2BIN = ВОСЬМ.В.ДВ +OCT2DEC = ВОСЬМ.В.ДЕС +OCT2HEX = ВОСЬМ.В.ШЕСТН + +## +## Финансовые функции (Financial Functions) +## +ACCRINT = НАКОПДОХОД +ACCRINTM = НАКОПДОХОДПОГАШ +AMORDEGRC = АМОРУМ +AMORLINC = АМОРУВ +COUPDAYBS = ДНЕЙКУПОНДО +COUPDAYS = ДНЕЙКУПОН +COUPDAYSNC = ДНЕЙКУПОНПОСЛЕ +COUPNCD = ДАТАКУПОНПОСЛЕ +COUPNUM = ЧИСЛКУПОН +COUPPCD = ДАТАКУПОНДО +CUMIPMT = ОБЩПЛАТ +CUMPRINC = ОБЩДОХОД +DB = ФУО +DDB = ДДОБ +DISC = СКИДКА +DOLLARDE = РУБЛЬ.ДЕС +DOLLARFR = РУБЛЬ.ДРОБЬ +DURATION = ДЛИТ +EFFECT = ЭФФЕКТ +FV = БС +FVSCHEDULE = БЗРАСПИС +INTRATE = ИНОРМА +IPMT = ПРПЛТ +IRR = ВСД +ISPMT = ПРОЦПЛАТ +MDURATION = МДЛИТ +MIRR = МВСД +NOMINAL = НОМИНАЛ +NPER = КПЕР +NPV = ЧПС +ODDFPRICE = ЦЕНАПЕРВНЕРЕГ +ODDFYIELD = ДОХОДПЕРВНЕРЕГ +ODDLPRICE = ЦЕНАПОСЛНЕРЕГ +ODDLYIELD = ДОХОДПОСЛНЕРЕГ +PDURATION = ПДЛИТ +PMT = ПЛТ +PPMT = ОСПЛТ +PRICE = ЦЕНА +PRICEDISC = ЦЕНАСКИДКА +PRICEMAT = ЦЕНАПОГАШ +PV = ПС +RATE = СТАВКА +RECEIVED = ПОЛУЧЕНО +RRI = ЭКВ.СТАВКА +SLN = АПЛ +SYD = АСЧ +TBILLEQ = РАВНОКЧЕК +TBILLPRICE = ЦЕНАКЧЕК +TBILLYIELD = ДОХОДКЧЕК +VDB = ПУО +XIRR = ЧИСТВНДОХ +XNPV = ЧИСТНЗ +YIELD = ДОХОД +YIELDDISC = ДОХОДСКИДКА +YIELDMAT = ДОХОДПОГАШ + +## +## Информационные функции (Information Functions) +## +CELL = ЯЧЕЙКА +ERROR.TYPE = ТИП.ОШИБКИ +INFO = ИНФОРМ +ISBLANK = ЕПУСТО +ISERR = ЕОШ +ISERROR = ЕОШИБКА +ISEVEN = ЕЧЁТН +ISFORMULA = ЕФОРМУЛА +ISLOGICAL = ЕЛОГИЧ +ISNA = ЕНД +ISNONTEXT = ЕНЕТЕКСТ +ISNUMBER = ЕЧИСЛО +ISODD = ЕНЕЧЁТ +ISREF = ЕССЫЛКА +ISTEXT = ЕТЕКСТ +N = Ч +NA = НД +SHEET = ЛИСТ +SHEETS = ЛИСТЫ +TYPE = ТИП + +## +## Логические функции (Logical Functions) +## +AND = И +FALSE = ЛОЖЬ +IF = ЕСЛИ +IFERROR = ЕСЛИОШИБКА +IFNA = ЕСНД +IFS = УСЛОВИЯ +NOT = НЕ +OR = ИЛИ +SWITCH = ПЕРЕКЛЮЧ +TRUE = ИСТИНА +XOR = ИСКЛИЛИ + +## +## Функции ссылки и поиска (Lookup & Reference Functions) +## +ADDRESS = АДРЕС +AREAS = ОБЛАСТИ +CHOOSE = ВЫБОР +COLUMN = СТОЛБЕЦ +COLUMNS = ЧИСЛСТОЛБ +FORMULATEXT = Ф.ТЕКСТ +GETPIVOTDATA = ПОЛУЧИТЬ.ДАННЫЕ.СВОДНОЙ.ТАБЛИЦЫ +HLOOKUP = ГПР +HYPERLINK = ГИПЕРССЫЛКА +INDEX = ИНДЕКС +INDIRECT = ДВССЫЛ +LOOKUP = ПРОСМОТР +MATCH = ПОИСКПОЗ +OFFSET = СМЕЩ +ROW = СТРОКА +ROWS = ЧСТРОК +RTD = ДРВ +TRANSPOSE = ТРАНСП +VLOOKUP = ВПР + +## +## Математические и тригонометрические функции (Math & Trig Functions) +## +ABS = ABS +ACOS = ACOS +ACOSH = ACOSH +ACOT = ACOT +ACOTH = ACOTH +AGGREGATE = АГРЕГАТ +ARABIC = АРАБСКОЕ +ASIN = ASIN +ASINH = ASINH +ATAN = ATAN +ATAN2 = ATAN2 +ATANH = ATANH +BASE = ОСНОВАНИЕ +CEILING.MATH = ОКРВВЕРХ.МАТ +CEILING.PRECISE = ОКРВВЕРХ.ТОЧН +COMBIN = ЧИСЛКОМБ +COMBINA = ЧИСЛКОМБА +COS = COS +COSH = COSH +COT = COT +COTH = COTH +CSC = CSC +CSCH = CSCH +DECIMAL = ДЕС +DEGREES = ГРАДУСЫ +ECMA.CEILING = ECMA.ОКРВВЕРХ +EVEN = ЧЁТН +EXP = EXP +FACT = ФАКТР +FACTDOUBLE = ДВФАКТР +FLOOR.MATH = ОКРВНИЗ.МАТ +FLOOR.PRECISE = ОКРВНИЗ.ТОЧН +GCD = НОД +INT = ЦЕЛОЕ +ISO.CEILING = ISO.ОКРВВЕРХ +LCM = НОК +LN = LN +LOG = LOG +LOG10 = LOG10 +MDETERM = МОПРЕД +MINVERSE = МОБР +MMULT = МУМНОЖ +MOD = ОСТАТ +MROUND = ОКРУГЛТ +MULTINOMIAL = МУЛЬТИНОМ +MUNIT = МЕДИН +ODD = НЕЧЁТ +PI = ПИ +POWER = СТЕПЕНЬ +PRODUCT = ПРОИЗВЕД +QUOTIENT = ЧАСТНОЕ +RADIANS = РАДИАНЫ +RAND = СЛЧИС +RANDBETWEEN = СЛУЧМЕЖДУ +ROMAN = РИМСКОЕ +ROUND = ОКРУГЛ +ROUNDBAHTDOWN = ОКРУГЛБАТВНИЗ +ROUNDBAHTUP = ОКРУГЛБАТВВЕРХ +ROUNDDOWN = ОКРУГЛВНИЗ +ROUNDUP = ОКРУГЛВВЕРХ +SEC = SEC +SECH = SECH +SERIESSUM = РЯД.СУММ +SIGN = ЗНАК +SIN = SIN +SINH = SINH +SQRT = КОРЕНЬ +SQRTPI = КОРЕНЬПИ +SUBTOTAL = ПРОМЕЖУТОЧНЫЕ.ИТОГИ +SUM = СУММ +SUMIF = СУММЕСЛИ +SUMIFS = СУММЕСЛИМН +SUMPRODUCT = СУММПРОИЗВ +SUMSQ = СУММКВ +SUMX2MY2 = СУММРАЗНКВ +SUMX2PY2 = СУММСУММКВ +SUMXMY2 = СУММКВРАЗН +TAN = TAN +TANH = TANH +TRUNC = ОТБР + +## +## Статистические функции (Statistical Functions) +## +AVEDEV = СРОТКЛ +AVERAGE = СРЗНАЧ +AVERAGEA = СРЗНАЧА +AVERAGEIF = СРЗНАЧЕСЛИ +AVERAGEIFS = СРЗНАЧЕСЛИМН +BETA.DIST = БЕТА.РАСП +BETA.INV = БЕТА.ОБР +BINOM.DIST = БИНОМ.РАСП +BINOM.DIST.RANGE = БИНОМ.РАСП.ДИАП +BINOM.INV = БИНОМ.ОБР +CHISQ.DIST = ХИ2.РАСП +CHISQ.DIST.RT = ХИ2.РАСП.ПХ +CHISQ.INV = ХИ2.ОБР +CHISQ.INV.RT = ХИ2.ОБР.ПХ +CHISQ.TEST = ХИ2.ТЕСТ +CONFIDENCE.NORM = ДОВЕРИТ.НОРМ +CONFIDENCE.T = ДОВЕРИТ.СТЬЮДЕНТ +CORREL = КОРРЕЛ +COUNT = СЧЁТ +COUNTA = СЧЁТЗ +COUNTBLANK = СЧИТАТЬПУСТОТЫ +COUNTIF = СЧЁТЕСЛИ +COUNTIFS = СЧЁТЕСЛИМН +COVARIANCE.P = КОВАРИАЦИЯ.Г +COVARIANCE.S = КОВАРИАЦИЯ.В +DEVSQ = КВАДРОТКЛ +EXPON.DIST = ЭКСП.РАСП +F.DIST = F.РАСП +F.DIST.RT = F.РАСП.ПХ +F.INV = F.ОБР +F.INV.RT = F.ОБР.ПХ +F.TEST = F.ТЕСТ +FISHER = ФИШЕР +FISHERINV = ФИШЕРОБР +FORECAST.ETS = ПРЕДСКАЗ.ETS +FORECAST.ETS.CONFINT = ПРЕДСКАЗ.ЕTS.ДОВИНТЕРВАЛ +FORECAST.ETS.SEASONALITY = ПРЕДСКАЗ.ETS.СЕЗОННОСТЬ +FORECAST.ETS.STAT = ПРЕДСКАЗ.ETS.СТАТ +FORECAST.LINEAR = ПРЕДСКАЗ.ЛИНЕЙН +FREQUENCY = ЧАСТОТА +GAMMA = ГАММА +GAMMA.DIST = ГАММА.РАСП +GAMMA.INV = ГАММА.ОБР +GAMMALN = ГАММАНЛОГ +GAMMALN.PRECISE = ГАММАНЛОГ.ТОЧН +GAUSS = ГАУСС +GEOMEAN = СРГЕОМ +GROWTH = РОСТ +HARMEAN = СРГАРМ +HYPGEOM.DIST = ГИПЕРГЕОМ.РАСП +INTERCEPT = ОТРЕЗОК +KURT = ЭКСЦЕСС +LARGE = НАИБОЛЬШИЙ +LINEST = ЛИНЕЙН +LOGEST = ЛГРФПРИБЛ +LOGNORM.DIST = ЛОГНОРМ.РАСП +LOGNORM.INV = ЛОГНОРМ.ОБР +MAX = МАКС +MAXA = МАКСА +MAXIFS = МАКСЕСЛИ +MEDIAN = МЕДИАНА +MIN = МИН +MINA = МИНА +MINIFS = МИНЕСЛИ +MODE.MULT = МОДА.НСК +MODE.SNGL = МОДА.ОДН +NEGBINOM.DIST = ОТРБИНОМ.РАСП +NORM.DIST = НОРМ.РАСП +NORM.INV = НОРМ.ОБР +NORM.S.DIST = НОРМ.СТ.РАСП +NORM.S.INV = НОРМ.СТ.ОБР +PEARSON = PEARSON +PERCENTILE.EXC = ПРОЦЕНТИЛЬ.ИСКЛ +PERCENTILE.INC = ПРОЦЕНТИЛЬ.ВКЛ +PERCENTRANK.EXC = ПРОЦЕНТРАНГ.ИСКЛ +PERCENTRANK.INC = ПРОЦЕНТРАНГ.ВКЛ +PERMUT = ПЕРЕСТ +PERMUTATIONA = ПЕРЕСТА +PHI = ФИ +POISSON.DIST = ПУАССОН.РАСП +PROB = ВЕРОЯТНОСТЬ +QUARTILE.EXC = КВАРТИЛЬ.ИСКЛ +QUARTILE.INC = КВАРТИЛЬ.ВКЛ +RANK.AVG = РАНГ.СР +RANK.EQ = РАНГ.РВ +RSQ = КВПИРСОН +SKEW = СКОС +SKEW.P = СКОС.Г +SLOPE = НАКЛОН +SMALL = НАИМЕНЬШИЙ +STANDARDIZE = НОРМАЛИЗАЦИЯ +STDEV.P = СТАНДОТКЛОН.Г +STDEV.S = СТАНДОТКЛОН.В +STDEVA = СТАНДОТКЛОНА +STDEVPA = СТАНДОТКЛОНПА +STEYX = СТОШYX +T.DIST = СТЬЮДЕНТ.РАСП +T.DIST.2T = СТЬЮДЕНТ.РАСП.2Х +T.DIST.RT = СТЬЮДЕНТ.РАСП.ПХ +T.INV = СТЬЮДЕНТ.ОБР +T.INV.2T = СТЬЮДЕНТ.ОБР.2Х +T.TEST = СТЬЮДЕНТ.ТЕСТ +TREND = ТЕНДЕНЦИЯ +TRIMMEAN = УРЕЗСРЕДНЕЕ +VAR.P = ДИСП.Г +VAR.S = ДИСП.В +VARA = ДИСПА +VARPA = ДИСПРА +WEIBULL.DIST = ВЕЙБУЛЛ.РАСП +Z.TEST = Z.ТЕСТ + +## +## Текстовые функции (Text Functions) +## +BAHTTEXT = БАТТЕКСТ +CHAR = СИМВОЛ +CLEAN = ПЕЧСИМВ +CODE = КОДСИМВ +CONCAT = СЦЕП +DOLLAR = РУБЛЬ +EXACT = СОВПАД +FIND = НАЙТИ +FIXED = ФИКСИРОВАННЫЙ +ISTHAIDIGIT = TAYRAKAMIYSA +LEFT = ЛЕВСИМВ +LEN = ДЛСТР +LOWER = СТРОЧН +MID = ПСТР +NUMBERSTRING = СТРОКАЧИСЕЛ +NUMBERVALUE = ЧЗНАЧ +PROPER = ПРОПНАЧ +REPLACE = ЗАМЕНИТЬ +REPT = ПОВТОР +RIGHT = ПРАВСИМВ +SEARCH = ПОИСК +SUBSTITUTE = ПОДСТАВИТЬ +T = Т +TEXT = ТЕКСТ +TEXTJOIN = ОБЪЕДИНИТЬ +THAIDIGIT = ТАЙЦИФРА +THAINUMSOUND = ТАЙЧИСЛОВЗВУК +THAINUMSTRING = ТАЙЧИСЛОВСТРОКУ +THAISTRINGLENGTH = ТАЙДЛИНАСТРОКИ +TRIM = СЖПРОБЕЛЫ +UNICHAR = ЮНИСИМВ +UNICODE = UNICODE +UPPER = ПРОПИСН +VALUE = ЗНАЧЕН + +## +## Веб-функции (Web Functions) +## +ENCODEURL = КОДИР.URL +FILTERXML = ФИЛЬТР.XML +WEBSERVICE = ВЕБСЛУЖБА + +## +## Функции совместимости (Compatibility Functions) +## +BETADIST = БЕТАРАСП +BETAINV = БЕТАОБР +BINOMDIST = БИНОМРАСП +CEILING = ОКРВВЕРХ +CHIDIST = ХИ2РАСП +CHIINV = ХИ2ОБР +CHITEST = ХИ2ТЕСТ +CONCATENATE = СЦЕПИТЬ +CONFIDENCE = ДОВЕРИТ +COVAR = КОВАР +CRITBINOM = КРИТБИНОМ +EXPONDIST = ЭКСПРАСП +FDIST = FРАСП +FINV = FРАСПОБР +FLOOR = ОКРВНИЗ +FORECAST = ПРЕДСКАЗ +FTEST = ФТЕСТ +GAMMADIST = ГАММАРАСП +GAMMAINV = ГАММАОБР +HYPGEOMDIST = ГИПЕРГЕОМЕТ +LOGINV = ЛОГНОРМОБР +LOGNORMDIST = ЛОГНОРМРАСП +MODE = МОДА +NEGBINOMDIST = ОТРБИНОМРАСП +NORMDIST = НОРМРАСП +NORMINV = НОРМОБР +NORMSDIST = НОРМСТРАСП +NORMSINV = НОРМСТОБР +PERCENTILE = ПЕРСЕНТИЛЬ +PERCENTRANK = ПРОЦЕНТРАНГ +POISSON = ПУАССОН +QUARTILE = КВАРТИЛЬ +RANK = РАНГ +STDEV = СТАНДОТКЛОН +STDEVP = СТАНДОТКЛОНП +TDIST = СТЬЮДРАСП +TINV = СТЬЮДРАСПОБР +TTEST = ТТЕСТ +VAR = ДИСП +VARP = ДИСПР +WEIBULL = ВЕЙБУЛЛ +ZTEST = ZТЕСТ diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/sv/config b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/sv/config new file mode 100644 index 0000000..c7440f7 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/sv/config @@ -0,0 +1,20 @@ +############################################################ +## +## PhpSpreadsheet - locale settings +## +## Svenska (Swedish) +## +############################################################ + +ArgumentSeparator = ; + +## +## Error Codes +## +NULL = #SKÄRNING! +DIV0 = #DIVISION/0! +VALUE = #VÄRDEFEL! +REF = #REFERENS! +NAME = #NAMN? +NUM = #OGILTIGT! +NA = #SAKNAS! diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/sv/functions b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/sv/functions new file mode 100644 index 0000000..2531b4c --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/sv/functions @@ -0,0 +1,532 @@ +############################################################ +## +## PhpSpreadsheet - function name translations +## +## Svenska (Swedish) +## +############################################################ + + +## +## Kubfunktioner (Cube Functions) +## +CUBEKPIMEMBER = KUBKPIMEDLEM +CUBEMEMBER = KUBMEDLEM +CUBEMEMBERPROPERTY = KUBMEDLEMSEGENSKAP +CUBERANKEDMEMBER = KUBRANGORDNADMEDLEM +CUBESET = KUBUPPSÄTTNING +CUBESETCOUNT = KUBUPPSÄTTNINGANTAL +CUBEVALUE = KUBVÄRDE + +## +## Databasfunktioner (Database Functions) +## +DAVERAGE = DMEDEL +DCOUNT = DANTAL +DCOUNTA = DANTALV +DGET = DHÄMTA +DMAX = DMAX +DMIN = DMIN +DPRODUCT = DPRODUKT +DSTDEV = DSTDAV +DSTDEVP = DSTDAVP +DSUM = DSUMMA +DVAR = DVARIANS +DVARP = DVARIANSP + +## +## Tid- och datumfunktioner (Date & Time Functions) +## +DATE = DATUM +DATEVALUE = DATUMVÄRDE +DAY = DAG +DAYS = DAGAR +DAYS360 = DAGAR360 +EDATE = EDATUM +EOMONTH = SLUTMÅNAD +HOUR = TIMME +ISOWEEKNUM = ISOVECKONR +MINUTE = MINUT +MONTH = MÅNAD +NETWORKDAYS = NETTOARBETSDAGAR +NETWORKDAYS.INTL = NETTOARBETSDAGAR.INT +NOW = NU +SECOND = SEKUND +THAIDAYOFWEEK = THAIVECKODAG +THAIMONTHOFYEAR = THAIMÅNAD +THAIYEAR = THAIÅR +TIME = KLOCKSLAG +TIMEVALUE = TIDVÄRDE +TODAY = IDAG +WEEKDAY = VECKODAG +WEEKNUM = VECKONR +WORKDAY = ARBETSDAGAR +WORKDAY.INTL = ARBETSDAGAR.INT +YEAR = ÅR +YEARFRAC = ÅRDEL + +## +## Tekniska funktioner (Engineering Functions) +## +BESSELI = BESSELI +BESSELJ = BESSELJ +BESSELK = BESSELK +BESSELY = BESSELY +BIN2DEC = BIN.TILL.DEC +BIN2HEX = BIN.TILL.HEX +BIN2OCT = BIN.TILL.OKT +BITAND = BITOCH +BITLSHIFT = BITVSKIFT +BITOR = BITELLER +BITRSHIFT = BITHSKIFT +BITXOR = BITXELLER +COMPLEX = KOMPLEX +CONVERT = KONVERTERA +DEC2BIN = DEC.TILL.BIN +DEC2HEX = DEC.TILL.HEX +DEC2OCT = DEC.TILL.OKT +DELTA = DELTA +ERF = FELF +ERF.PRECISE = FELF.EXAKT +ERFC = FELFK +ERFC.PRECISE = FELFK.EXAKT +GESTEP = SLSTEG +HEX2BIN = HEX.TILL.BIN +HEX2DEC = HEX.TILL.DEC +HEX2OCT = HEX.TILL.OKT +IMABS = IMABS +IMAGINARY = IMAGINÄR +IMARGUMENT = IMARGUMENT +IMCONJUGATE = IMKONJUGAT +IMCOS = IMCOS +IMCOSH = IMCOSH +IMCOT = IMCOT +IMCSC = IMCSC +IMCSCH = IMCSCH +IMDIV = IMDIV +IMEXP = IMEUPPHÖJT +IMLN = IMLN +IMLOG10 = IMLOG10 +IMLOG2 = IMLOG2 +IMPOWER = IMUPPHÖJT +IMPRODUCT = IMPRODUKT +IMREAL = IMREAL +IMSEC = IMSEK +IMSECH = IMSEKH +IMSIN = IMSIN +IMSINH = IMSINH +IMSQRT = IMROT +IMSUB = IMDIFF +IMSUM = IMSUM +IMTAN = IMTAN +OCT2BIN = OKT.TILL.BIN +OCT2DEC = OKT.TILL.DEC +OCT2HEX = OKT.TILL.HEX + +## +## Finansiella funktioner (Financial Functions) +## +ACCRINT = UPPLRÄNTA +ACCRINTM = UPPLOBLRÄNTA +AMORDEGRC = AMORDEGRC +AMORLINC = AMORLINC +COUPDAYBS = KUPDAGBB +COUPDAYS = KUPDAGB +COUPDAYSNC = KUPDAGNK +COUPNCD = KUPNKD +COUPNUM = KUPANT +COUPPCD = KUPFKD +CUMIPMT = KUMRÄNTA +CUMPRINC = KUMPRIS +DB = DB +DDB = DEGAVSKR +DISC = DISK +DOLLARDE = DECTAL +DOLLARFR = BRÅK +DURATION = LÖPTID +EFFECT = EFFRÄNTA +FV = SLUTVÄRDE +FVSCHEDULE = FÖRRÄNTNING +INTRATE = ÅRSRÄNTA +IPMT = RBETALNING +IRR = IR +ISPMT = RALÅN +MDURATION = MLÖPTID +MIRR = MODIR +NOMINAL = NOMRÄNTA +NPER = PERIODER +NPV = NETNUVÄRDE +ODDFPRICE = UDDAFPRIS +ODDFYIELD = UDDAFAVKASTNING +ODDLPRICE = UDDASPRIS +ODDLYIELD = UDDASAVKASTNING +PDURATION = PLÖPTID +PMT = BETALNING +PPMT = AMORT +PRICE = PRIS +PRICEDISC = PRISDISK +PRICEMAT = PRISFÖRF +PV = NUVÄRDE +RATE = RÄNTA +RECEIVED = BELOPP +RRI = AVKPÅINVEST +SLN = LINAVSKR +SYD = ÅRSAVSKR +TBILLEQ = SSVXEKV +TBILLPRICE = SSVXPRIS +TBILLYIELD = SSVXRÄNTA +VDB = VDEGRAVSKR +XIRR = XIRR +XNPV = XNUVÄRDE +YIELD = NOMAVK +YIELDDISC = NOMAVKDISK +YIELDMAT = NOMAVKFÖRF + +## +## Informationsfunktioner (Information Functions) +## +CELL = CELL +ERROR.TYPE = FEL.TYP +INFO = INFO +ISBLANK = ÄRTOM +ISERR = ÄRF +ISERROR = ÄRFEL +ISEVEN = ÄRJÄMN +ISFORMULA = ÄRFORMEL +ISLOGICAL = ÄRLOGISK +ISNA = ÄRSAKNAD +ISNONTEXT = ÄREJTEXT +ISNUMBER = ÄRTAL +ISODD = ÄRUDDA +ISREF = ÄRREF +ISTEXT = ÄRTEXT +N = N +NA = SAKNAS +SHEET = BLAD +SHEETS = ANTALBLAD +TYPE = VÄRDETYP + +## +## Logiska funktioner (Logical Functions) +## +AND = OCH +FALSE = FALSKT +IF = OM +IFERROR = OMFEL +IFNA = OMSAKNAS +IFS = IFS +NOT = ICKE +OR = ELLER +SWITCH = VÄXLA +TRUE = SANT +XOR = XELLER + +## +## Sök- och referensfunktioner (Lookup & Reference Functions) +## +ADDRESS = ADRESS +AREAS = OMRÅDEN +CHOOSE = VÄLJ +COLUMN = KOLUMN +COLUMNS = KOLUMNER +FORMULATEXT = FORMELTEXT +GETPIVOTDATA = HÄMTA.PIVOTDATA +HLOOKUP = LETAKOLUMN +HYPERLINK = HYPERLÄNK +INDEX = INDEX +INDIRECT = INDIREKT +LOOKUP = LETAUPP +MATCH = PASSA +OFFSET = FÖRSKJUTNING +ROW = RAD +ROWS = RADER +RTD = RTD +TRANSPOSE = TRANSPONERA +VLOOKUP = LETARAD + +## +## Matematiska och trigonometriska funktioner (Math & Trig Functions) +## +ABS = ABS +ACOS = ARCCOS +ACOSH = ARCCOSH +ACOT = ARCCOT +ACOTH = ARCCOTH +AGGREGATE = MÄNGD +ARABIC = ARABISKA +ASIN = ARCSIN +ASINH = ARCSINH +ATAN = ARCTAN +ATAN2 = ARCTAN2 +ATANH = ARCTANH +BASE = BAS +CEILING.MATH = RUNDA.UPP.MATEMATISKT +CEILING.PRECISE = RUNDA.UPP.EXAKT +COMBIN = KOMBIN +COMBINA = KOMBINA +COS = COS +COSH = COSH +COT = COT +COTH = COTH +CSC = CSC +CSCH = CSCH +DECIMAL = DECIMAL +DEGREES = GRADER +ECMA.CEILING = ECMA.RUNDA.UPP +EVEN = JÄMN +EXP = EXP +FACT = FAKULTET +FACTDOUBLE = DUBBELFAKULTET +FLOOR.MATH = RUNDA.NER.MATEMATISKT +FLOOR.PRECISE = RUNDA.NER.EXAKT +GCD = SGD +INT = HELTAL +ISO.CEILING = ISO.RUNDA.UPP +LCM = MGM +LN = LN +LOG = LOG +LOG10 = LOG10 +MDETERM = MDETERM +MINVERSE = MINVERT +MMULT = MMULT +MOD = REST +MROUND = MAVRUNDA +MULTINOMIAL = MULTINOMIAL +MUNIT = MENHET +ODD = UDDA +PI = PI +POWER = UPPHÖJT.TILL +PRODUCT = PRODUKT +QUOTIENT = KVOT +RADIANS = RADIANER +RAND = SLUMP +RANDBETWEEN = SLUMP.MELLAN +ROMAN = ROMERSK +ROUND = AVRUNDA +ROUNDBAHTDOWN = AVRUNDABAHTNEDÅT +ROUNDBAHTUP = AVRUNDABAHTUPPÅT +ROUNDDOWN = AVRUNDA.NEDÅT +ROUNDUP = AVRUNDA.UPPÅT +SEC = SEK +SECH = SEKH +SERIESSUM = SERIESUMMA +SIGN = TECKEN +SIN = SIN +SINH = SINH +SQRT = ROT +SQRTPI = ROTPI +SUBTOTAL = DELSUMMA +SUM = SUMMA +SUMIF = SUMMA.OM +SUMIFS = SUMMA.OMF +SUMPRODUCT = PRODUKTSUMMA +SUMSQ = KVADRATSUMMA +SUMX2MY2 = SUMMAX2MY2 +SUMX2PY2 = SUMMAX2PY2 +SUMXMY2 = SUMMAXMY2 +TAN = TAN +TANH = TANH +TRUNC = AVKORTA + +## +## Statistiska funktioner (Statistical Functions) +## +AVEDEV = MEDELAVV +AVERAGE = MEDEL +AVERAGEA = AVERAGEA +AVERAGEIF = MEDEL.OM +AVERAGEIFS = MEDEL.OMF +BETA.DIST = BETA.FÖRD +BETA.INV = BETA.INV +BINOM.DIST = BINOM.FÖRD +BINOM.DIST.RANGE = BINOM.FÖRD.INTERVALL +BINOM.INV = BINOM.INV +CHISQ.DIST = CHI2.FÖRD +CHISQ.DIST.RT = CHI2.FÖRD.RT +CHISQ.INV = CHI2.INV +CHISQ.INV.RT = CHI2.INV.RT +CHISQ.TEST = CHI2.TEST +CONFIDENCE.NORM = KONFIDENS.NORM +CONFIDENCE.T = KONFIDENS.T +CORREL = KORREL +COUNT = ANTAL +COUNTA = ANTALV +COUNTBLANK = ANTAL.TOMMA +COUNTIF = ANTAL.OM +COUNTIFS = ANTAL.OMF +COVARIANCE.P = KOVARIANS.P +COVARIANCE.S = KOVARIANS.S +DEVSQ = KVADAVV +EXPON.DIST = EXPON.FÖRD +F.DIST = F.FÖRD +F.DIST.RT = F.FÖRD.RT +F.INV = F.INV +F.INV.RT = F.INV.RT +F.TEST = F.TEST +FISHER = FISHER +FISHERINV = FISHERINV +FORECAST.ETS = PROGNOS.ETS +FORECAST.ETS.CONFINT = PROGNOS.ETS.KONFINT +FORECAST.ETS.SEASONALITY = PROGNOS.ETS.SÄSONGSBEROENDE +FORECAST.ETS.STAT = PROGNOS.ETS.STAT +FORECAST.LINEAR = PROGNOS.LINJÄR +FREQUENCY = FREKVENS +GAMMA = GAMMA +GAMMA.DIST = GAMMA.FÖRD +GAMMA.INV = GAMMA.INV +GAMMALN = GAMMALN +GAMMALN.PRECISE = GAMMALN.EXAKT +GAUSS = GAUSS +GEOMEAN = GEOMEDEL +GROWTH = EXPTREND +HARMEAN = HARMMEDEL +HYPGEOM.DIST = HYPGEOM.FÖRD +INTERCEPT = SKÄRNINGSPUNKT +KURT = TOPPIGHET +LARGE = STÖRSTA +LINEST = REGR +LOGEST = EXPREGR +LOGNORM.DIST = LOGNORM.FÖRD +LOGNORM.INV = LOGNORM.INV +MAX = MAX +MAXA = MAXA +MAXIFS = MAXIFS +MEDIAN = MEDIAN +MIN = MIN +MINA = MINA +MINIFS = MINIFS +MODE.MULT = TYPVÄRDE.FLERA +MODE.SNGL = TYPVÄRDE.ETT +NEGBINOM.DIST = NEGBINOM.FÖRD +NORM.DIST = NORM.FÖRD +NORM.INV = NORM.INV +NORM.S.DIST = NORM.S.FÖRD +NORM.S.INV = NORM.S.INV +PEARSON = PEARSON +PERCENTILE.EXC = PERCENTIL.EXK +PERCENTILE.INC = PERCENTIL.INK +PERCENTRANK.EXC = PROCENTRANG.EXK +PERCENTRANK.INC = PROCENTRANG.INK +PERMUT = PERMUT +PERMUTATIONA = PERMUTATIONA +PHI = PHI +POISSON.DIST = POISSON.FÖRD +PROB = SANNOLIKHET +QUARTILE.EXC = KVARTIL.EXK +QUARTILE.INC = KVARTIL.INK +RANK.AVG = RANG.MED +RANK.EQ = RANG.EKV +RSQ = RKV +SKEW = SNEDHET +SKEW.P = SNEDHET.P +SLOPE = LUTNING +SMALL = MINSTA +STANDARDIZE = STANDARDISERA +STDEV.P = STDAV.P +STDEV.S = STDAV.S +STDEVA = STDEVA +STDEVPA = STDEVPA +STEYX = STDFELYX +T.DIST = T.FÖRD +T.DIST.2T = T.FÖRD.2T +T.DIST.RT = T.FÖRD.RT +T.INV = T.INV +T.INV.2T = T.INV.2T +T.TEST = T.TEST +TREND = TREND +TRIMMEAN = TRIMMEDEL +VAR.P = VARIANS.P +VAR.S = VARIANS.S +VARA = VARA +VARPA = VARPA +WEIBULL.DIST = WEIBULL.FÖRD +Z.TEST = Z.TEST + +## +## Textfunktioner (Text Functions) +## +BAHTTEXT = BAHTTEXT +CHAR = TECKENKOD +CLEAN = STÄDA +CODE = KOD +CONCAT = SAMMAN +DOLLAR = VALUTA +EXACT = EXAKT +FIND = HITTA +FIXED = FASTTAL +LEFT = VÄNSTER +LEN = LÄNGD +LOWER = GEMENER +MID = EXTEXT +NUMBERVALUE = TALVÄRDE +PROPER = INITIAL +REPLACE = ERSÄTT +REPT = REP +RIGHT = HÖGER +SEARCH = SÖK +SUBSTITUTE = BYT.UT +T = T +TEXT = TEXT +TEXTJOIN = TEXTJOIN +THAIDIGIT = THAISIFFRA +THAINUMSOUND = THAITALLJUD +THAINUMSTRING = THAITALSTRÄNG +THAISTRINGLENGTH = THAISTRÄNGLÄNGD +TRIM = RENSA +UNICHAR = UNITECKENKOD +UNICODE = UNICODE +UPPER = VERSALER +VALUE = TEXTNUM + +## +## Webbfunktioner (Web Functions) +## +ENCODEURL = KODAWEBBADRESS +FILTERXML = FILTRERAXML +WEBSERVICE = WEBBTJÄNST + +## +## Kompatibilitetsfunktioner (Compatibility Functions) +## +BETADIST = BETAFÖRD +BETAINV = BETAINV +BINOMDIST = BINOMFÖRD +CEILING = RUNDA.UPP +CHIDIST = CHI2FÖRD +CHIINV = CHI2INV +CHITEST = CHI2TEST +CONCATENATE = SAMMANFOGA +CONFIDENCE = KONFIDENS +COVAR = KOVAR +CRITBINOM = KRITBINOM +EXPONDIST = EXPONFÖRD +FDIST = FFÖRD +FINV = FINV +FLOOR = RUNDA.NER +FORECAST = PREDIKTION +FTEST = FTEST +GAMMADIST = GAMMAFÖRD +GAMMAINV = GAMMAINV +HYPGEOMDIST = HYPGEOMFÖRD +LOGINV = LOGINV +LOGNORMDIST = LOGNORMFÖRD +MODE = TYPVÄRDE +NEGBINOMDIST = NEGBINOMFÖRD +NORMDIST = NORMFÖRD +NORMINV = NORMINV +NORMSDIST = NORMSFÖRD +NORMSINV = NORMSINV +PERCENTILE = PERCENTIL +PERCENTRANK = PROCENTRANG +POISSON = POISSON +QUARTILE = KVARTIL +RANK = RANG +STDEV = STDAV +STDEVP = STDAVP +TDIST = TFÖRD +TINV = TINV +TTEST = TTEST +VAR = VARIANS +VARP = VARIANSP +WEIBULL = WEIBULL +ZTEST = ZTEST diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/tr/config b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/tr/config new file mode 100644 index 0000000..63d22fd --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/tr/config @@ -0,0 +1,20 @@ +############################################################ +## +## PhpSpreadsheet - locale settings +## +## Türkçe (Turkish) +## +############################################################ + +ArgumentSeparator = ; + +## +## Error Codes +## +NULL = #BOŞ! +DIV0 = #SAYI/0! +VALUE = #DEĞER! +REF = #BAŞV! +NAME = #AD? +NUM = #SAYI! +NA = #YOK diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/tr/functions b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/tr/functions new file mode 100644 index 0000000..f872274 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/tr/functions @@ -0,0 +1,537 @@ +############################################################ +## +## PhpSpreadsheet - function name translations +## +## Türkçe (Turkish) +## +############################################################ + + +## +## Küp işlevleri (Cube Functions) +## +CUBEKPIMEMBER = KÜPKPIÜYESİ +CUBEMEMBER = KÜPÜYESİ +CUBEMEMBERPROPERTY = KÜPÜYEÖZELLİĞİ +CUBERANKEDMEMBER = DERECELİKÜPÜYESİ +CUBESET = KÜPKÜMESİ +CUBESETCOUNT = KÜPKÜMESAYISI +CUBEVALUE = KÜPDEĞERİ + +## +## Veritabanı işlevleri (Database Functions) +## +DAVERAGE = VSEÇORT +DCOUNT = VSEÇSAY +DCOUNTA = VSEÇSAYDOLU +DGET = VAL +DMAX = VSEÇMAK +DMIN = VSEÇMİN +DPRODUCT = VSEÇÇARP +DSTDEV = VSEÇSTDSAPMA +DSTDEVP = VSEÇSTDSAPMAS +DSUM = VSEÇTOPLA +DVAR = VSEÇVAR +DVARP = VSEÇVARS + +## +## Tarih ve saat işlevleri (Date & Time Functions) +## +DATE = TARİH +DATEDIF = ETARİHLİ +DATESTRING = TARİHDİZİ +DATEVALUE = TARİHSAYISI +DAY = GÜN +DAYS = GÜNSAY +DAYS360 = GÜN360 +EDATE = SERİTARİH +EOMONTH = SERİAY +HOUR = SAAT +ISOWEEKNUM = ISOHAFTASAY +MINUTE = DAKİKA +MONTH = AY +NETWORKDAYS = TAMİŞGÜNÜ +NETWORKDAYS.INTL = TAMİŞGÜNÜ.ULUSL +NOW = ŞİMDİ +SECOND = SANİYE +THAIDAYOFWEEK = TAYHAFTANINGÜNÜ +THAIMONTHOFYEAR = TAYYILINAYI +THAIYEAR = TAYYILI +TIME = ZAMAN +TIMEVALUE = ZAMANSAYISI +TODAY = BUGÜN +WEEKDAY = HAFTANINGÜNÜ +WEEKNUM = HAFTASAY +WORKDAY = İŞGÜNÜ +WORKDAY.INTL = İŞGÜNÜ.ULUSL +YEAR = YIL +YEARFRAC = YILORAN + +## +## Mühendislik işlevleri (Engineering Functions) +## +BESSELI = BESSELI +BESSELJ = BESSELJ +BESSELK = BESSELK +BESSELY = BESSELY +BIN2DEC = BIN2DEC +BIN2HEX = BIN2HEX +BIN2OCT = BIN2OCT +BITAND = BİTVE +BITLSHIFT = BİTSOLAKAYDIR +BITOR = BİTVEYA +BITRSHIFT = BİTSAĞAKAYDIR +BITXOR = BİTÖZELVEYA +COMPLEX = KARMAŞIK +CONVERT = ÇEVİR +DEC2BIN = DEC2BIN +DEC2HEX = DEC2HEX +DEC2OCT = DEC2OCT +DELTA = DELTA +ERF = HATAİŞLEV +ERF.PRECISE = HATAİŞLEV.DUYARLI +ERFC = TÜMHATAİŞLEV +ERFC.PRECISE = TÜMHATAİŞLEV.DUYARLI +GESTEP = BESINIR +HEX2BIN = HEX2BIN +HEX2DEC = HEX2DEC +HEX2OCT = HEX2OCT +IMABS = SANMUTLAK +IMAGINARY = SANAL +IMARGUMENT = SANBAĞ_DEĞİŞKEN +IMCONJUGATE = SANEŞLENEK +IMCOS = SANCOS +IMCOSH = SANCOSH +IMCOT = SANCOT +IMCSC = SANCSC +IMCSCH = SANCSCH +IMDIV = SANBÖL +IMEXP = SANÜS +IMLN = SANLN +IMLOG10 = SANLOG10 +IMLOG2 = SANLOG2 +IMPOWER = SANKUVVET +IMPRODUCT = SANÇARP +IMREAL = SANGERÇEK +IMSEC = SANSEC +IMSECH = SANSECH +IMSIN = SANSIN +IMSINH = SANSINH +IMSQRT = SANKAREKÖK +IMSUB = SANTOPLA +IMSUM = SANÇIKAR +IMTAN = SANTAN +OCT2BIN = OCT2BIN +OCT2DEC = OCT2DEC +OCT2HEX = OCT2HEX + +## +## Finansal işlevler (Financial Functions) +## +ACCRINT = GERÇEKFAİZ +ACCRINTM = GERÇEKFAİZV +AMORDEGRC = AMORDEGRC +AMORLINC = AMORLINC +COUPDAYBS = KUPONGÜNBD +COUPDAYS = KUPONGÜN +COUPDAYSNC = KUPONGÜNDSK +COUPNCD = KUPONGÜNSKT +COUPNUM = KUPONSAYI +COUPPCD = KUPONGÜNÖKT +CUMIPMT = TOPÖDENENFAİZ +CUMPRINC = TOPANAPARA +DB = AZALANBAKİYE +DDB = ÇİFTAZALANBAKİYE +DISC = İNDİRİM +DOLLARDE = LİRAON +DOLLARFR = LİRAKES +DURATION = SÜRE +EFFECT = ETKİN +FV = GD +FVSCHEDULE = GDPROGRAM +INTRATE = FAİZORANI +IPMT = FAİZTUTARI +IRR = İÇ_VERİM_ORANI +ISPMT = ISPMT +MDURATION = MSÜRE +MIRR = D_İÇ_VERİM_ORANI +NOMINAL = NOMİNAL +NPER = TAKSİT_SAYISI +NPV = NBD +ODDFPRICE = TEKYDEĞER +ODDFYIELD = TEKYÖDEME +ODDLPRICE = TEKSDEĞER +ODDLYIELD = TEKSÖDEME +PDURATION = PSÜRE +PMT = DEVRESEL_ÖDEME +PPMT = ANA_PARA_ÖDEMESİ +PRICE = DEĞER +PRICEDISC = DEĞERİND +PRICEMAT = DEĞERVADE +PV = BD +RATE = FAİZ_ORANI +RECEIVED = GETİRİ +RRI = GERÇEKLEŞENYATIRIMGETİRİSİ +SLN = DA +SYD = YAT +TBILLEQ = HTAHEŞ +TBILLPRICE = HTAHDEĞER +TBILLYIELD = HTAHÖDEME +VDB = DAB +XIRR = AİÇVERİMORANI +XNPV = ANBD +YIELD = ÖDEME +YIELDDISC = ÖDEMEİND +YIELDMAT = ÖDEMEVADE + +## +## Bilgi işlevleri (Information Functions) +## +CELL = HÜCRE +ERROR.TYPE = HATA.TİPİ +INFO = BİLGİ +ISBLANK = EBOŞSA +ISERR = EHATA +ISERROR = EHATALIYSA +ISEVEN = ÇİFTMİ +ISFORMULA = EFORMÜLSE +ISLOGICAL = EMANTIKSALSA +ISNA = EYOKSA +ISNONTEXT = EMETİNDEĞİLSE +ISNUMBER = ESAYIYSA +ISODD = TEKMİ +ISREF = EREFSE +ISTEXT = EMETİNSE +N = S +NA = YOKSAY +SHEET = SAYFA +SHEETS = SAYFALAR +TYPE = TÜR + +## +## Mantıksal işlevler (Logical Functions) +## +AND = VE +FALSE = YANLIŞ +IF = EĞER +IFERROR = EĞERHATA +IFNA = EĞERYOKSA +IFS = ÇOKEĞER +NOT = DEĞİL +OR = YADA +SWITCH = İLKEŞLEŞEN +TRUE = DOĞRU +XOR = ÖZELVEYA + +## +## Arama ve başvuru işlevleri (Lookup & Reference Functions) +## +ADDRESS = ADRES +AREAS = ALANSAY +CHOOSE = ELEMAN +COLUMN = SÜTUN +COLUMNS = SÜTUNSAY +FORMULATEXT = FORMÜLMETNİ +GETPIVOTDATA = ÖZETVERİAL +HLOOKUP = YATAYARA +HYPERLINK = KÖPRÜ +INDEX = İNDİS +INDIRECT = DOLAYLI +LOOKUP = ARA +MATCH = KAÇINCI +OFFSET = KAYDIR +ROW = SATIR +ROWS = SATIRSAY +RTD = GZV +TRANSPOSE = DEVRİK_DÖNÜŞÜM +VLOOKUP = DÜŞEYARA + +## +## Matematik ve trigonometri işlevleri (Math & Trig Functions) +## +ABS = MUTLAK +ACOS = ACOS +ACOSH = ACOSH +ACOT = ACOT +ACOTH = ACOTH +AGGREGATE = TOPLAMA +ARABIC = ARAP +ASIN = ASİN +ASINH = ASİNH +ATAN = ATAN +ATAN2 = ATAN2 +ATANH = ATANH +BASE = TABAN +CEILING.MATH = TAVANAYUVARLA.MATEMATİK +CEILING.PRECISE = TAVANAYUVARLA.DUYARLI +COMBIN = KOMBİNASYON +COMBINA = KOMBİNASYONA +COS = COS +COSH = COSH +COT = COT +COTH = COTH +CSC = CSC +CSCH = CSCH +DECIMAL = ONDALIK +DEGREES = DERECE +ECMA.CEILING = ECMA.TAVAN +EVEN = ÇİFT +EXP = ÜS +FACT = ÇARPINIM +FACTDOUBLE = ÇİFTFAKTÖR +FLOOR.MATH = TABANAYUVARLA.MATEMATİK +FLOOR.PRECISE = TABANAYUVARLA.DUYARLI +GCD = OBEB +INT = TAMSAYI +ISO.CEILING = ISO.TAVAN +LCM = OKEK +LN = LN +LOG = LOG +LOG10 = LOG10 +MDETERM = DETERMİNANT +MINVERSE = DİZEY_TERS +MMULT = DÇARP +MOD = MOD +MROUND = KYUVARLA +MULTINOMIAL = ÇOKTERİMLİ +MUNIT = BİRİMMATRİS +ODD = TEK +PI = Pİ +POWER = KUVVET +PRODUCT = ÇARPIM +QUOTIENT = BÖLÜM +RADIANS = RADYAN +RAND = S_SAYI_ÜRET +RANDBETWEEN = RASTGELEARADA +ROMAN = ROMEN +ROUND = YUVARLA +ROUNDBAHTDOWN = BAHTAŞAĞIYUVARLA +ROUNDBAHTUP = BAHTYUKARIYUVARLA +ROUNDDOWN = AŞAĞIYUVARLA +ROUNDUP = YUKARIYUVARLA +SEC = SEC +SECH = SECH +SERIESSUM = SERİTOPLA +SIGN = İŞARET +SIN = SİN +SINH = SİNH +SQRT = KAREKÖK +SQRTPI = KAREKÖKPİ +SUBTOTAL = ALTTOPLAM +SUM = TOPLA +SUMIF = ETOPLA +SUMIFS = ÇOKETOPLA +SUMPRODUCT = TOPLA.ÇARPIM +SUMSQ = TOPKARE +SUMX2MY2 = TOPX2EY2 +SUMX2PY2 = TOPX2AY2 +SUMXMY2 = TOPXEY2 +TAN = TAN +TANH = TANH +TRUNC = NSAT + +## +## İstatistik işlevleri (Statistical Functions) +## +AVEDEV = ORTSAP +AVERAGE = ORTALAMA +AVERAGEA = ORTALAMAA +AVERAGEIF = EĞERORTALAMA +AVERAGEIFS = ÇOKEĞERORTALAMA +BETA.DIST = BETA.DAĞ +BETA.INV = BETA.TERS +BINOM.DIST = BİNOM.DAĞ +BINOM.DIST.RANGE = BİNOM.DAĞ.ARALIK +BINOM.INV = BİNOM.TERS +CHISQ.DIST = KİKARE.DAĞ +CHISQ.DIST.RT = KİKARE.DAĞ.SAĞK +CHISQ.INV = KİKARE.TERS +CHISQ.INV.RT = KİKARE.TERS.SAĞK +CHISQ.TEST = KİKARE.TEST +CONFIDENCE.NORM = GÜVENİLİRLİK.NORM +CONFIDENCE.T = GÜVENİLİRLİK.T +CORREL = KORELASYON +COUNT = BAĞ_DEĞ_SAY +COUNTA = BAĞ_DEĞ_DOLU_SAY +COUNTBLANK = BOŞLUKSAY +COUNTIF = EĞERSAY +COUNTIFS = ÇOKEĞERSAY +COVARIANCE.P = KOVARYANS.P +COVARIANCE.S = KOVARYANS.S +DEVSQ = SAPKARE +EXPON.DIST = ÜSTEL.DAĞ +F.DIST = F.DAĞ +F.DIST.RT = F.DAĞ.SAĞK +F.INV = F.TERS +F.INV.RT = F.TERS.SAĞK +F.TEST = F.TEST +FISHER = FISHER +FISHERINV = FISHERTERS +FORECAST.ETS = TAHMİN.ETS +FORECAST.ETS.CONFINT = TAHMİN.ETS.GVNARAL +FORECAST.ETS.SEASONALITY = TAHMİN.ETS.MEVSİMSELLİK +FORECAST.ETS.STAT = TAHMİN.ETS.İSTAT +FORECAST.LINEAR = TAHMİN.DOĞRUSAL +FREQUENCY = SIKLIK +GAMMA = GAMA +GAMMA.DIST = GAMA.DAĞ +GAMMA.INV = GAMA.TERS +GAMMALN = GAMALN +GAMMALN.PRECISE = GAMALN.DUYARLI +GAUSS = GAUSS +GEOMEAN = GEOORT +GROWTH = BÜYÜME +HARMEAN = HARORT +HYPGEOM.DIST = HİPERGEOM.DAĞ +INTERCEPT = KESMENOKTASI +KURT = BASIKLIK +LARGE = BÜYÜK +LINEST = DOT +LOGEST = LOT +LOGNORM.DIST = LOGNORM.DAĞ +LOGNORM.INV = LOGNORM.TERS +MAX = MAK +MAXA = MAKA +MAXIFS = ÇOKEĞERMAK +MEDIAN = ORTANCA +MIN = MİN +MINA = MİNA +MINIFS = ÇOKEĞERMİN +MODE.MULT = ENÇOK_OLAN.ÇOK +MODE.SNGL = ENÇOK_OLAN.TEK +NEGBINOM.DIST = NEGBİNOM.DAĞ +NORM.DIST = NORM.DAĞ +NORM.INV = NORM.TERS +NORM.S.DIST = NORM.S.DAĞ +NORM.S.INV = NORM.S.TERS +PEARSON = PEARSON +PERCENTILE.EXC = YÜZDEBİRLİK.HRC +PERCENTILE.INC = YÜZDEBİRLİK.DHL +PERCENTRANK.EXC = YÜZDERANK.HRC +PERCENTRANK.INC = YÜZDERANK.DHL +PERMUT = PERMÜTASYON +PERMUTATIONA = PERMÜTASYONA +PHI = PHI +POISSON.DIST = POISSON.DAĞ +PROB = OLASILIK +QUARTILE.EXC = DÖRTTEBİRLİK.HRC +QUARTILE.INC = DÖRTTEBİRLİK.DHL +RANK.AVG = RANK.ORT +RANK.EQ = RANK.EŞİT +RSQ = RKARE +SKEW = ÇARPIKLIK +SKEW.P = ÇARPIKLIK.P +SLOPE = EĞİM +SMALL = KÜÇÜK +STANDARDIZE = STANDARTLAŞTIRMA +STDEV.P = STDSAPMA.P +STDEV.S = STDSAPMA.S +STDEVA = STDSAPMAA +STDEVPA = STDSAPMASA +STEYX = STHYX +T.DIST = T.DAĞ +T.DIST.2T = T.DAĞ.2K +T.DIST.RT = T.DAĞ.SAĞK +T.INV = T.TERS +T.INV.2T = T.TERS.2K +T.TEST = T.TEST +TREND = EĞİLİM +TRIMMEAN = KIRPORTALAMA +VAR.P = VAR.P +VAR.S = VAR.S +VARA = VARA +VARPA = VARSA +WEIBULL.DIST = WEIBULL.DAĞ +Z.TEST = Z.TEST + +## +## Metin işlevleri (Text Functions) +## +BAHTTEXT = BAHTMETİN +CHAR = DAMGA +CLEAN = TEMİZ +CODE = KOD +CONCAT = ARALIKBİRLEŞTİR +DOLLAR = LİRA +EXACT = ÖZDEŞ +FIND = BUL +FIXED = SAYIDÜZENLE +ISTHAIDIGIT = TAYRAKAMIYSA +LEFT = SOLDAN +LEN = UZUNLUK +LOWER = KÜÇÜKHARF +MID = PARÇAAL +NUMBERSTRING = SAYIDİZİ +NUMBERVALUE = SAYIDEĞERİ +PHONETIC = SES +PROPER = YAZIM.DÜZENİ +REPLACE = DEĞİŞTİR +REPT = YİNELE +RIGHT = SAĞDAN +SEARCH = MBUL +SUBSTITUTE = YERİNEKOY +T = M +TEXT = METNEÇEVİR +TEXTJOIN = METİNBİRLEŞTİR +THAIDIGIT = TAYRAKAM +THAINUMSOUND = TAYSAYISES +THAINUMSTRING = TAYSAYIDİZE +THAISTRINGLENGTH = TAYDİZEUZUNLUĞU +TRIM = KIRP +UNICHAR = UNICODEKARAKTERİ +UNICODE = UNICODE +UPPER = BÜYÜKHARF +VALUE = SAYIYAÇEVİR + +## +## Metin işlevleri (Web Functions) +## +ENCODEURL = URLKODLA +FILTERXML = XMLFİLTRELE +WEBSERVICE = WEBHİZMETİ + +## +## Uyumluluk işlevleri (Compatibility Functions) +## +BETADIST = BETADAĞ +BETAINV = BETATERS +BINOMDIST = BİNOMDAĞ +CEILING = TAVANAYUVARLA +CHIDIST = KİKAREDAĞ +CHIINV = KİKARETERS +CHITEST = KİKARETEST +CONCATENATE = BİRLEŞTİR +CONFIDENCE = GÜVENİRLİK +COVAR = KOVARYANS +CRITBINOM = KRİTİKBİNOM +EXPONDIST = ÜSTELDAĞ +FDIST = FDAĞ +FINV = FTERS +FLOOR = TABANAYUVARLA +FORECAST = TAHMİN +FTEST = FTEST +GAMMADIST = GAMADAĞ +GAMMAINV = GAMATERS +HYPGEOMDIST = HİPERGEOMDAĞ +LOGINV = LOGTERS +LOGNORMDIST = LOGNORMDAĞ +MODE = ENÇOK_OLAN +NEGBINOMDIST = NEGBİNOMDAĞ +NORMDIST = NORMDAĞ +NORMINV = NORMTERS +NORMSDIST = NORMSDAĞ +NORMSINV = NORMSTERS +PERCENTILE = YÜZDEBİRLİK +PERCENTRANK = YÜZDERANK +POISSON = POISSON +QUARTILE = DÖRTTEBİRLİK +RANK = RANK +STDEV = STDSAPMA +STDEVP = STDSAPMAS +TDIST = TDAĞ +TINV = TTERS +TTEST = TTEST +VAR = VAR +VARP = VARS +WEIBULL = WEIBULL +ZTEST = ZTEST diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/AddressHelper.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/AddressHelper.php new file mode 100644 index 0000000..499d248 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/AddressHelper.php @@ -0,0 +1,153 @@ +setValueExplicit(true, DataType::TYPE_BOOL); + + return true; + } elseif ($value == Calculation::getFALSE()) { + $cell->setValueExplicit(false, DataType::TYPE_BOOL); + + return true; + } + + // Check for fractions + if (preg_match('/^([+-]?)\s*(\d+)\s?\/\s*(\d+)$/', $value, $matches)) { + return $this->setProperFraction($matches, $cell); + } elseif (preg_match('/^([+-]?)(\d*) +(\d*)\s?\/\s*(\d*)$/', $value, $matches)) { + return $this->setImproperFraction($matches, $cell); + } + + // Check for percentage + if (preg_match('/^\-?\d*\.?\d*\s?\%$/', $value)) { + return $this->setPercentage($value, $cell); + } + + // Check for currency + $currencyCode = StringHelper::getCurrencyCode(); + $decimalSeparator = StringHelper::getDecimalSeparator(); + $thousandsSeparator = StringHelper::getThousandsSeparator(); + if (preg_match('/^' . preg_quote($currencyCode, '/') . ' *(\d{1,3}(' . preg_quote($thousandsSeparator, '/') . '\d{3})*|(\d+))(' . preg_quote($decimalSeparator, '/') . '\d{2})?$/', $value)) { + // Convert value to number + $value = (float) trim(str_replace([$currencyCode, $thousandsSeparator, $decimalSeparator], ['', '', '.'], $value)); + $cell->setValueExplicit($value, DataType::TYPE_NUMERIC); + // Set style + $cell->getWorksheet()->getStyle($cell->getCoordinate()) + ->getNumberFormat()->setFormatCode( + str_replace('$', $currencyCode, NumberFormat::FORMAT_CURRENCY_USD_SIMPLE) + ); + + return true; + } elseif (preg_match('/^\$ *(\d{1,3}(\,\d{3})*|(\d+))(\.\d{2})?$/', $value)) { + // Convert value to number + $value = (float) trim(str_replace(['$', ','], '', $value)); + $cell->setValueExplicit($value, DataType::TYPE_NUMERIC); + // Set style + $cell->getWorksheet()->getStyle($cell->getCoordinate()) + ->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_CURRENCY_USD_SIMPLE); + + return true; + } + + // Check for time without seconds e.g. '9:45', '09:45' + if (preg_match('/^(\d|[0-1]\d|2[0-3]):[0-5]\d$/', $value)) { + return $this->setTimeHoursMinutes($value, $cell); + } + + // Check for time with seconds '9:45:59', '09:45:59' + if (preg_match('/^(\d|[0-1]\d|2[0-3]):[0-5]\d:[0-5]\d$/', $value)) { + return $this->setTimeHoursMinutesSeconds($value, $cell); + } + + // Check for datetime, e.g. '2008-12-31', '2008-12-31 15:59', '2008-12-31 15:59:10' + if (($d = Date::stringToExcel($value)) !== false) { + // Convert value to number + $cell->setValueExplicit($d, DataType::TYPE_NUMERIC); + // Determine style. Either there is a time part or not. Look for ':' + if (strpos($value, ':') !== false) { + $formatCode = 'yyyy-mm-dd h:mm'; + } else { + $formatCode = 'yyyy-mm-dd'; + } + $cell->getWorksheet()->getStyle($cell->getCoordinate()) + ->getNumberFormat()->setFormatCode($formatCode); + + return true; + } + + // Check for newline character "\n" + if (strpos($value, "\n") !== false) { + $cell->setValueExplicit($value, DataType::TYPE_STRING); + // Set style + $cell->getWorksheet()->getStyle($cell->getCoordinate()) + ->getAlignment()->setWrapText(true); + + return true; + } + } + + // Not bound yet? Use parent... + return parent::bindValue($cell, $value); + } + + protected function setImproperFraction(array $matches, Cell $cell): bool + { + // Convert value to number + $value = $matches[2] + ($matches[3] / $matches[4]); + if ($matches[1] === '-') { + $value = 0 - $value; + } + $cell->setValueExplicit((float) $value, DataType::TYPE_NUMERIC); + + // Build the number format mask based on the size of the matched values + $dividend = str_repeat('?', strlen($matches[3])); + $divisor = str_repeat('?', strlen($matches[4])); + $fractionMask = "# {$dividend}/{$divisor}"; + // Set style + $cell->getWorksheet()->getStyle($cell->getCoordinate()) + ->getNumberFormat()->setFormatCode($fractionMask); + + return true; + } + + protected function setProperFraction(array $matches, Cell $cell): bool + { + // Convert value to number + $value = $matches[2] / $matches[3]; + if ($matches[1] === '-') { + $value = 0 - $value; + } + $cell->setValueExplicit((float) $value, DataType::TYPE_NUMERIC); + + // Build the number format mask based on the size of the matched values + $dividend = str_repeat('?', strlen($matches[2])); + $divisor = str_repeat('?', strlen($matches[3])); + $fractionMask = "{$dividend}/{$divisor}"; + // Set style + $cell->getWorksheet()->getStyle($cell->getCoordinate()) + ->getNumberFormat()->setFormatCode($fractionMask); + + return true; + } + + protected function setPercentage(string $value, Cell $cell): bool + { + // Convert value to number + $value = ((float) str_replace('%', '', $value)) / 100; + $cell->setValueExplicit($value, DataType::TYPE_NUMERIC); + + // Set style + $cell->getWorksheet()->getStyle($cell->getCoordinate()) + ->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_PERCENTAGE_00); + + return true; + } + + protected function setTimeHoursMinutes(string $value, Cell $cell): bool + { + // Convert value to number + [$hours, $minutes] = explode(':', $value); + $days = ($hours / 24) + ($minutes / 1440); + $cell->setValueExplicit($days, DataType::TYPE_NUMERIC); + + // Set style + $cell->getWorksheet()->getStyle($cell->getCoordinate()) + ->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_TIME3); + + return true; + } + + protected function setTimeHoursMinutesSeconds(string $value, Cell $cell): bool + { + // Convert value to number + [$hours, $minutes, $seconds] = explode(':', $value); + $days = ($hours / 24) + ($minutes / 1440) + ($seconds / 86400); + $cell->setValueExplicit($days, DataType::TYPE_NUMERIC); + + // Set style + $cell->getWorksheet()->getStyle($cell->getCoordinate()) + ->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_TIME4); + + return true; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Cell.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Cell.php new file mode 100644 index 0000000..5a9def3 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Cell.php @@ -0,0 +1,697 @@ +parent->update($this); + + return $this; + } + + public function detach(): void + { + // @phpstan-ignore-next-line + $this->parent = null; + } + + public function attach(Cells $parent): void + { + $this->parent = $parent; + } + + /** + * Create a new Cell. + * + * @param mixed $value + * @param string $dataType + */ + public function __construct($value, $dataType, Worksheet $worksheet) + { + // Initialise cell value + $this->value = $value; + + // Set worksheet cache + $this->parent = $worksheet->getCellCollection(); + + // Set datatype? + if ($dataType !== null) { + if ($dataType == DataType::TYPE_STRING2) { + $dataType = DataType::TYPE_STRING; + } + $this->dataType = $dataType; + } elseif (!self::getValueBinder()->bindValue($this, $value)) { + throw new Exception('Value could not be bound to cell.'); + } + } + + /** + * Get cell coordinate column. + * + * @return string + */ + public function getColumn() + { + return $this->parent->getCurrentColumn(); + } + + /** + * Get cell coordinate row. + * + * @return int + */ + public function getRow() + { + return $this->parent->getCurrentRow(); + } + + /** + * Get cell coordinate. + * + * @return string + */ + public function getCoordinate() + { + try { + $coordinate = $this->parent->getCurrentCoordinate(); + } catch (Throwable $e) { + $coordinate = null; + } + if ($coordinate === null) { + throw new Exception('Coordinate no longer exists'); + } + + return $coordinate; + } + + /** + * Get cell value. + * + * @return mixed + */ + public function getValue() + { + return $this->value; + } + + /** + * Get cell value with formatting. + * + * @return string + */ + public function getFormattedValue() + { + return (string) NumberFormat::toFormattedString( + $this->getCalculatedValue(), + $this->getStyle() + ->getNumberFormat()->getFormatCode() + ); + } + + /** + * Set cell value. + * + * Sets the value for a cell, automatically determining the datatype using the value binder + * + * @param mixed $value Value + * + * @return $this + */ + public function setValue($value) + { + if (!self::getValueBinder()->bindValue($this, $value)) { + throw new Exception('Value could not be bound to cell.'); + } + + return $this; + } + + /** + * Set the value for a cell, with the explicit data type passed to the method (bypassing any use of the value binder). + * + * @param mixed $value Value + * @param string $dataType Explicit data type, see DataType::TYPE_* + * + * @return Cell + */ + public function setValueExplicit($value, $dataType) + { + // set the value according to data type + switch ($dataType) { + case DataType::TYPE_NULL: + $this->value = $value; + + break; + case DataType::TYPE_STRING2: + $dataType = DataType::TYPE_STRING; + // no break + case DataType::TYPE_STRING: + // Synonym for string + case DataType::TYPE_INLINE: + // Rich text + $this->value = DataType::checkString($value); + + break; + case DataType::TYPE_NUMERIC: + if (is_string($value) && !is_numeric($value)) { + throw new Exception('Invalid numeric value for datatype Numeric'); + } + $this->value = 0 + $value; + + break; + case DataType::TYPE_FORMULA: + $this->value = (string) $value; + + break; + case DataType::TYPE_BOOL: + $this->value = (bool) $value; + + break; + case DataType::TYPE_ERROR: + $this->value = DataType::checkErrorCode($value); + + break; + default: + throw new Exception('Invalid datatype: ' . $dataType); + + break; + } + + // set the datatype + $this->dataType = $dataType; + + return $this->updateInCollection(); + } + + /** + * Get calculated cell value. + * + * @param bool $resetLog Whether the calculation engine logger should be reset or not + * + * @return mixed + */ + public function getCalculatedValue($resetLog = true) + { + if ($this->dataType == DataType::TYPE_FORMULA) { + try { + $index = $this->getWorksheet()->getParent()->getActiveSheetIndex(); + $selected = $this->getWorksheet()->getSelectedCells(); + $result = Calculation::getInstance( + $this->getWorksheet()->getParent() + )->calculateCellValue($this, $resetLog); + $this->getWorksheet()->setSelectedCells($selected); + $this->getWorksheet()->getParent()->setActiveSheetIndex($index); + // We don't yet handle array returns + if (is_array($result)) { + while (is_array($result)) { + $result = array_shift($result); + } + } + } catch (Exception $ex) { + if (($ex->getMessage() === 'Unable to access External Workbook') && ($this->calculatedValue !== null)) { + return $this->calculatedValue; // Fallback for calculations referencing external files. + } elseif (preg_match('/[Uu]ndefined (name|offset: 2|array key 2)/', $ex->getMessage()) === 1) { + return \PhpOffice\PhpSpreadsheet\Calculation\Functions::NAME(); + } + + throw new \PhpOffice\PhpSpreadsheet\Calculation\Exception( + $this->getWorksheet()->getTitle() . '!' . $this->getCoordinate() . ' -> ' . $ex->getMessage() + ); + } + + if ($result === '#Not Yet Implemented') { + return $this->calculatedValue; // Fallback if calculation engine does not support the formula. + } + + return $result; + } elseif ($this->value instanceof RichText) { + return $this->value->getPlainText(); + } + + return $this->value; + } + + /** + * Set old calculated value (cached). + * + * @param mixed $originalValue Value + * + * @return Cell + */ + public function setCalculatedValue($originalValue) + { + if ($originalValue !== null) { + $this->calculatedValue = (is_numeric($originalValue)) ? (float) $originalValue : $originalValue; + } + + return $this->updateInCollection(); + } + + /** + * Get old calculated value (cached) + * This returns the value last calculated by MS Excel or whichever spreadsheet program was used to + * create the original spreadsheet file. + * Note that this value is not guaranteed to reflect the actual calculated value because it is + * possible that auto-calculation was disabled in the original spreadsheet, and underlying data + * values used by the formula have changed since it was last calculated. + * + * @return mixed + */ + public function getOldCalculatedValue() + { + return $this->calculatedValue; + } + + /** + * Get cell data type. + * + * @return string + */ + public function getDataType() + { + return $this->dataType; + } + + /** + * Set cell data type. + * + * @param string $dataType see DataType::TYPE_* + * + * @return Cell + */ + public function setDataType($dataType) + { + if ($dataType == DataType::TYPE_STRING2) { + $dataType = DataType::TYPE_STRING; + } + $this->dataType = $dataType; + + return $this->updateInCollection(); + } + + /** + * Identify if the cell contains a formula. + * + * @return bool + */ + public function isFormula() + { + return $this->dataType == DataType::TYPE_FORMULA; + } + + /** + * Does this cell contain Data validation rules? + * + * @return bool + */ + public function hasDataValidation() + { + if (!isset($this->parent)) { + throw new Exception('Cannot check for data validation when cell is not bound to a worksheet'); + } + + return $this->getWorksheet()->dataValidationExists($this->getCoordinate()); + } + + /** + * Get Data validation rules. + * + * @return DataValidation + */ + public function getDataValidation() + { + if (!isset($this->parent)) { + throw new Exception('Cannot get data validation for cell that is not bound to a worksheet'); + } + + return $this->getWorksheet()->getDataValidation($this->getCoordinate()); + } + + /** + * Set Data validation rules. + */ + public function setDataValidation(?DataValidation $dataValidation = null): self + { + if (!isset($this->parent)) { + throw new Exception('Cannot set data validation for cell that is not bound to a worksheet'); + } + + $this->getWorksheet()->setDataValidation($this->getCoordinate(), $dataValidation); + + return $this->updateInCollection(); + } + + /** + * Does this cell contain valid value? + * + * @return bool + */ + public function hasValidValue() + { + $validator = new DataValidator(); + + return $validator->isValid($this); + } + + /** + * Does this cell contain a Hyperlink? + * + * @return bool + */ + public function hasHyperlink() + { + if (!isset($this->parent)) { + throw new Exception('Cannot check for hyperlink when cell is not bound to a worksheet'); + } + + return $this->getWorksheet()->hyperlinkExists($this->getCoordinate()); + } + + /** + * Get Hyperlink. + * + * @return Hyperlink + */ + public function getHyperlink() + { + if (!isset($this->parent)) { + throw new Exception('Cannot get hyperlink for cell that is not bound to a worksheet'); + } + + return $this->getWorksheet()->getHyperlink($this->getCoordinate()); + } + + /** + * Set Hyperlink. + * + * @return Cell + */ + public function setHyperlink(?Hyperlink $hyperlink = null) + { + if (!isset($this->parent)) { + throw new Exception('Cannot set hyperlink for cell that is not bound to a worksheet'); + } + + $this->getWorksheet()->setHyperlink($this->getCoordinate(), $hyperlink); + + return $this->updateInCollection(); + } + + /** + * Get cell collection. + * + * @return Cells + */ + public function getParent() + { + return $this->parent; + } + + /** + * Get parent worksheet. + * + * @return Worksheet + */ + public function getWorksheet() + { + try { + $worksheet = $this->parent->getParent(); + } catch (Throwable $e) { + $worksheet = null; + } + + if ($worksheet === null) { + throw new Exception('Worksheet no longer exists'); + } + + return $worksheet; + } + + /** + * Is this cell in a merge range. + * + * @return bool + */ + public function isInMergeRange() + { + return (bool) $this->getMergeRange(); + } + + /** + * Is this cell the master (top left cell) in a merge range (that holds the actual data value). + * + * @return bool + */ + public function isMergeRangeValueCell() + { + if ($mergeRange = $this->getMergeRange()) { + $mergeRange = Coordinate::splitRange($mergeRange); + [$startCell] = $mergeRange[0]; + if ($this->getCoordinate() === $startCell) { + return true; + } + } + + return false; + } + + /** + * If this cell is in a merge range, then return the range. + * + * @return false|string + */ + public function getMergeRange() + { + foreach ($this->getWorksheet()->getMergeCells() as $mergeRange) { + if ($this->isInRange($mergeRange)) { + return $mergeRange; + } + } + + return false; + } + + /** + * Get cell style. + * + * @return Style + */ + public function getStyle() + { + return $this->getWorksheet()->getStyle($this->getCoordinate()); + } + + /** + * Re-bind parent. + * + * @return Cell + */ + public function rebindParent(Worksheet $parent) + { + $this->parent = $parent->getCellCollection(); + + return $this->updateInCollection(); + } + + /** + * Is cell in a specific range? + * + * @param string $range Cell range (e.g. A1:A1) + * + * @return bool + */ + public function isInRange($range) + { + [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($range); + + // Translate properties + $myColumn = Coordinate::columnIndexFromString($this->getColumn()); + $myRow = $this->getRow(); + + // Verify if cell is in range + return ($rangeStart[0] <= $myColumn) && ($rangeEnd[0] >= $myColumn) && + ($rangeStart[1] <= $myRow) && ($rangeEnd[1] >= $myRow); + } + + /** + * Compare 2 cells. + * + * @param Cell $a Cell a + * @param Cell $b Cell b + * + * @return int Result of comparison (always -1 or 1, never zero!) + */ + public static function compareCells(self $a, self $b) + { + if ($a->getRow() < $b->getRow()) { + return -1; + } elseif ($a->getRow() > $b->getRow()) { + return 1; + } elseif (Coordinate::columnIndexFromString($a->getColumn()) < Coordinate::columnIndexFromString($b->getColumn())) { + return -1; + } + + return 1; + } + + /** + * Get value binder to use. + * + * @return IValueBinder + */ + public static function getValueBinder() + { + if (self::$valueBinder === null) { + self::$valueBinder = new DefaultValueBinder(); + } + + return self::$valueBinder; + } + + /** + * Set value binder to use. + */ + public static function setValueBinder(IValueBinder $binder): void + { + self::$valueBinder = $binder; + } + + /** + * Implement PHP __clone to create a deep clone, not just a shallow copy. + */ + public function __clone() + { + $vars = get_object_vars($this); + foreach ($vars as $key => $value) { + if ((is_object($value)) && ($key != 'parent')) { + $this->$key = clone $value; + } else { + $this->$key = $value; + } + } + } + + /** + * Get index to cellXf. + * + * @return int + */ + public function getXfIndex() + { + return $this->xfIndex; + } + + /** + * Set index to cellXf. + * + * @param int $indexValue + * + * @return Cell + */ + public function setXfIndex($indexValue) + { + $this->xfIndex = $indexValue; + + return $this->updateInCollection(); + } + + /** + * Set the formula attributes. + * + * @param mixed $attributes + * + * @return $this + */ + public function setFormulaAttributes($attributes) + { + $this->formulaAttributes = $attributes; + + return $this; + } + + /** + * Get the formula attributes. + */ + public function getFormulaAttributes() + { + return $this->formulaAttributes; + } + + /** + * Convert to string. + * + * @return string + */ + public function __toString() + { + return (string) $this->getValue(); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Coordinate.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Coordinate.php new file mode 100644 index 0000000..b4b76c5 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Coordinate.php @@ -0,0 +1,570 @@ +\$?)(?[A-Z]{1,3})(?\$?)(?\d{1,7})$/i'; + + /** + * Default range variable constant. + * + * @var string + */ + const DEFAULT_RANGE = 'A1:A1'; + + /** + * Coordinate from string. + * + * @param string $cellAddress eg: 'A1' + * + * @return array{0: string, 1: string} Array containing column and row (indexes 0 and 1) + */ + public static function coordinateFromString($cellAddress) + { + if (preg_match(self::A1_COORDINATE_REGEX, $cellAddress, $matches)) { + return [$matches['absolute_col'] . $matches['col_ref'], $matches['absolute_row'] . $matches['row_ref']]; + } elseif (self::coordinateIsRange($cellAddress)) { + throw new Exception('Cell coordinate string can not be a range of cells'); + } elseif ($cellAddress == '') { + throw new Exception('Cell coordinate can not be zero-length string'); + } + + throw new Exception('Invalid cell coordinate ' . $cellAddress); + } + + /** + * Get indexes from a string coordinates. + * + * @param string $coordinates eg: 'A1', '$B$12' + * + * @return array{0: int, 1: int} Array containing column index and row index (indexes 0 and 1) + */ + public static function indexesFromString(string $coordinates): array + { + [$col, $row] = self::coordinateFromString($coordinates); + + return [ + self::columnIndexFromString(ltrim($col, '$')), + (int) ltrim($row, '$'), + ]; + } + + /** + * Checks if a Cell Address represents a range of cells. + * + * @param string $cellAddress eg: 'A1' or 'A1:A2' or 'A1:A2,C1:C2' + * + * @return bool Whether the coordinate represents a range of cells + */ + public static function coordinateIsRange($cellAddress) + { + return (strpos($cellAddress, ':') !== false) || (strpos($cellAddress, ',') !== false); + } + + /** + * Make string row, column or cell coordinate absolute. + * + * @param string $cellAddress e.g. 'A' or '1' or 'A1' + * Note that this value can be a row or column reference as well as a cell reference + * + * @return string Absolute coordinate e.g. '$A' or '$1' or '$A$1' + */ + public static function absoluteReference($cellAddress) + { + if (self::coordinateIsRange($cellAddress)) { + throw new Exception('Cell coordinate string can not be a range of cells'); + } + + // Split out any worksheet name from the reference + [$worksheet, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true); + if ($worksheet > '') { + $worksheet .= '!'; + } + + // Create absolute coordinate + if (ctype_digit($cellAddress)) { + return $worksheet . '$' . $cellAddress; + } elseif (ctype_alpha($cellAddress)) { + return $worksheet . '$' . strtoupper($cellAddress); + } + + return $worksheet . self::absoluteCoordinate($cellAddress); + } + + /** + * Make string coordinate absolute. + * + * @param string $cellAddress e.g. 'A1' + * + * @return string Absolute coordinate e.g. '$A$1' + */ + public static function absoluteCoordinate($cellAddress) + { + if (self::coordinateIsRange($cellAddress)) { + throw new Exception('Cell coordinate string can not be a range of cells'); + } + + // Split out any worksheet name from the coordinate + [$worksheet, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true); + if ($worksheet > '') { + $worksheet .= '!'; + } + + // Create absolute coordinate + [$column, $row] = self::coordinateFromString($cellAddress); + $column = ltrim($column, '$'); + $row = ltrim($row, '$'); + + return $worksheet . '$' . $column . '$' . $row; + } + + /** + * Split range into coordinate strings. + * + * @param string $range e.g. 'B4:D9' or 'B4:D9,H2:O11' or 'B4' + * + * @return array Array containing one or more arrays containing one or two coordinate strings + * e.g. ['B4','D9'] or [['B4','D9'], ['H2','O11']] + * or ['B4'] + */ + public static function splitRange($range) + { + // Ensure $pRange is a valid range + if (empty($range)) { + $range = self::DEFAULT_RANGE; + } + + $exploded = explode(',', $range); + $counter = count($exploded); + for ($i = 0; $i < $counter; ++$i) { + $exploded[$i] = explode(':', $exploded[$i]); + } + + return $exploded; + } + + /** + * Build range from coordinate strings. + * + * @param array $range Array containing one or more arrays containing one or two coordinate strings + * + * @return string String representation of $pRange + */ + public static function buildRange(array $range) + { + // Verify range + if (empty($range) || !is_array($range[0])) { + throw new Exception('Range does not contain any information'); + } + + // Build range + $counter = count($range); + for ($i = 0; $i < $counter; ++$i) { + $range[$i] = implode(':', $range[$i]); + } + + return implode(',', $range); + } + + /** + * Calculate range boundaries. + * + * @param string $range Cell range (e.g. A1:A1) + * + * @return array Range coordinates [Start Cell, End Cell] + * where Start Cell and End Cell are arrays (Column Number, Row Number) + */ + public static function rangeBoundaries($range) + { + // Ensure $pRange is a valid range + if (empty($range)) { + $range = self::DEFAULT_RANGE; + } + + // Uppercase coordinate + $range = strtoupper($range); + + // Extract range + if (strpos($range, ':') === false) { + $rangeA = $rangeB = $range; + } else { + [$rangeA, $rangeB] = explode(':', $range); + } + + // Calculate range outer borders + $rangeStart = self::coordinateFromString($rangeA); + $rangeEnd = self::coordinateFromString($rangeB); + + // Translate column into index + $rangeStart[0] = self::columnIndexFromString($rangeStart[0]); + $rangeEnd[0] = self::columnIndexFromString($rangeEnd[0]); + + return [$rangeStart, $rangeEnd]; + } + + /** + * Calculate range dimension. + * + * @param string $range Cell range (e.g. A1:A1) + * + * @return array Range dimension (width, height) + */ + public static function rangeDimension($range) + { + // Calculate range outer borders + [$rangeStart, $rangeEnd] = self::rangeBoundaries($range); + + return [($rangeEnd[0] - $rangeStart[0] + 1), ($rangeEnd[1] - $rangeStart[1] + 1)]; + } + + /** + * Calculate range boundaries. + * + * @param string $range Cell range (e.g. A1:A1) + * + * @return array Range coordinates [Start Cell, End Cell] + * where Start Cell and End Cell are arrays [Column ID, Row Number] + */ + public static function getRangeBoundaries($range) + { + // Ensure $pRange is a valid range + if (empty($range)) { + $range = self::DEFAULT_RANGE; + } + + // Uppercase coordinate + $range = strtoupper($range); + + // Extract range + if (strpos($range, ':') === false) { + $rangeA = $rangeB = $range; + } else { + [$rangeA, $rangeB] = explode(':', $range); + } + + return [self::coordinateFromString($rangeA), self::coordinateFromString($rangeB)]; + } + + /** + * Column index from string. + * + * @param string $columnAddress eg 'A' + * + * @return int Column index (A = 1) + */ + public static function columnIndexFromString($columnAddress) + { + // Using a lookup cache adds a slight memory overhead, but boosts speed + // caching using a static within the method is faster than a class static, + // though it's additional memory overhead + static $indexCache = []; + + if (isset($indexCache[$columnAddress])) { + return $indexCache[$columnAddress]; + } + // It's surprising how costly the strtoupper() and ord() calls actually are, so we use a lookup array rather than use ord() + // and make it case insensitive to get rid of the strtoupper() as well. Because it's a static, there's no significant + // memory overhead either + static $columnLookup = [ + 'A' => 1, 'B' => 2, 'C' => 3, 'D' => 4, 'E' => 5, 'F' => 6, 'G' => 7, 'H' => 8, 'I' => 9, 'J' => 10, 'K' => 11, 'L' => 12, 'M' => 13, + 'N' => 14, 'O' => 15, 'P' => 16, 'Q' => 17, 'R' => 18, 'S' => 19, 'T' => 20, 'U' => 21, 'V' => 22, 'W' => 23, 'X' => 24, 'Y' => 25, 'Z' => 26, + 'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5, 'f' => 6, 'g' => 7, 'h' => 8, 'i' => 9, 'j' => 10, 'k' => 11, 'l' => 12, 'm' => 13, + 'n' => 14, 'o' => 15, 'p' => 16, 'q' => 17, 'r' => 18, 's' => 19, 't' => 20, 'u' => 21, 'v' => 22, 'w' => 23, 'x' => 24, 'y' => 25, 'z' => 26, + ]; + + // We also use the language construct isset() rather than the more costly strlen() function to match the length of $columnAddress + // for improved performance + if (isset($columnAddress[0])) { + if (!isset($columnAddress[1])) { + $indexCache[$columnAddress] = $columnLookup[$columnAddress]; + + return $indexCache[$columnAddress]; + } elseif (!isset($columnAddress[2])) { + $indexCache[$columnAddress] = $columnLookup[$columnAddress[0]] * 26 + $columnLookup[$columnAddress[1]]; + + return $indexCache[$columnAddress]; + } elseif (!isset($columnAddress[3])) { + $indexCache[$columnAddress] = $columnLookup[$columnAddress[0]] * 676 + $columnLookup[$columnAddress[1]] * 26 + $columnLookup[$columnAddress[2]]; + + return $indexCache[$columnAddress]; + } + } + + throw new Exception('Column string index can not be ' . ((isset($columnAddress[0])) ? 'longer than 3 characters' : 'empty')); + } + + /** + * String from column index. + * + * @param int $columnIndex Column index (A = 1) + * + * @return string + */ + public static function stringFromColumnIndex($columnIndex) + { + static $indexCache = []; + + if (!isset($indexCache[$columnIndex])) { + $indexValue = $columnIndex; + $base26 = null; + do { + $characterValue = ($indexValue % 26) ?: 26; + $indexValue = ($indexValue - $characterValue) / 26; + $base26 = chr($characterValue + 64) . ($base26 ?: ''); + } while ($indexValue > 0); + $indexCache[$columnIndex] = $base26; + } + + return $indexCache[$columnIndex]; + } + + /** + * Extract all cell references in range, which may be comprised of multiple cell ranges. + * + * @param string $cellRange Range: e.g. 'A1' or 'A1:C10' or 'A1:E10,A20:E25' or 'A1:E5 C3:G7' or 'A1:C1,A3:C3 B1:C3' + * + * @return array Array containing single cell references + */ + public static function extractAllCellReferencesInRange($cellRange): array + { + [$ranges, $operators] = self::getCellBlocksFromRangeString($cellRange); + + $cells = []; + foreach ($ranges as $range) { + $cells[] = self::getReferencesForCellBlock($range); + } + + $cells = self::processRangeSetOperators($operators, $cells); + + if (empty($cells)) { + return []; + } + + $cellList = array_merge(...$cells); + $cellList = self::sortCellReferenceArray($cellList); + + return $cellList; + } + + private static function processRangeSetOperators(array $operators, array $cells): array + { + $operatorCount = count($operators); + for ($offset = 0; $offset < $operatorCount; ++$offset) { + $operator = $operators[$offset]; + if ($operator !== ' ') { + continue; + } + + $cells[$offset] = array_intersect($cells[$offset], $cells[$offset + 1]); + unset($operators[$offset], $cells[$offset + 1]); + $operators = array_values($operators); + $cells = array_values($cells); + --$offset; + --$operatorCount; + } + + return $cells; + } + + private static function sortCellReferenceArray(array $cellList): array + { + // Sort the result by column and row + $sortKeys = []; + foreach ($cellList as $coord) { + [$column, $row] = sscanf($coord, '%[A-Z]%d'); + $sortKeys[sprintf('%3s%09d', $column, $row)] = $coord; + } + ksort($sortKeys); + + return array_values($sortKeys); + } + + /** + * Get all cell references for an individual cell block. + * + * @param string $cellBlock A cell range e.g. A4:B5 + * + * @return array All individual cells in that range + */ + private static function getReferencesForCellBlock($cellBlock) + { + $returnValue = []; + + // Single cell? + if (!self::coordinateIsRange($cellBlock)) { + return (array) $cellBlock; + } + + // Range... + $ranges = self::splitRange($cellBlock); + foreach ($ranges as $range) { + // Single cell? + if (!isset($range[1])) { + $returnValue[] = $range[0]; + + continue; + } + + // Range... + [$rangeStart, $rangeEnd] = $range; + [$startColumn, $startRow] = self::coordinateFromString($rangeStart); + [$endColumn, $endRow] = self::coordinateFromString($rangeEnd); + $startColumnIndex = self::columnIndexFromString($startColumn); + $endColumnIndex = self::columnIndexFromString($endColumn); + ++$endColumnIndex; + + // Current data + $currentColumnIndex = $startColumnIndex; + $currentRow = $startRow; + + self::validateRange($cellBlock, $startColumnIndex, $endColumnIndex, $currentRow, $endRow); + + // Loop cells + while ($currentColumnIndex < $endColumnIndex) { + while ($currentRow <= $endRow) { + $returnValue[] = self::stringFromColumnIndex($currentColumnIndex) . $currentRow; + ++$currentRow; + } + ++$currentColumnIndex; + $currentRow = $startRow; + } + } + + return $returnValue; + } + + /** + * Convert an associative array of single cell coordinates to values to an associative array + * of cell ranges to values. Only adjacent cell coordinates with the same + * value will be merged. If the value is an object, it must implement the method getHashCode(). + * + * For example, this function converts: + * + * [ 'A1' => 'x', 'A2' => 'x', 'A3' => 'x', 'A4' => 'y' ] + * + * to: + * + * [ 'A1:A3' => 'x', 'A4' => 'y' ] + * + * @param array $coordinateCollection associative array mapping coordinates to values + * + * @return array associative array mapping coordinate ranges to valuea + */ + public static function mergeRangesInCollection(array $coordinateCollection) + { + $hashedValues = []; + $mergedCoordCollection = []; + + foreach ($coordinateCollection as $coord => $value) { + if (self::coordinateIsRange($coord)) { + $mergedCoordCollection[$coord] = $value; + + continue; + } + + [$column, $row] = self::coordinateFromString($coord); + $row = (int) (ltrim($row, '$')); + $hashCode = $column . '-' . (is_object($value) ? $value->getHashCode() : $value); + + if (!isset($hashedValues[$hashCode])) { + $hashedValues[$hashCode] = (object) [ + 'value' => $value, + 'col' => $column, + 'rows' => [$row], + ]; + } else { + $hashedValues[$hashCode]->rows[] = $row; + } + } + + ksort($hashedValues); + + foreach ($hashedValues as $hashedValue) { + sort($hashedValue->rows); + $rowStart = null; + $rowEnd = null; + $ranges = []; + + foreach ($hashedValue->rows as $row) { + if ($rowStart === null) { + $rowStart = $row; + $rowEnd = $row; + } elseif ($rowEnd === $row - 1) { + $rowEnd = $row; + } else { + if ($rowStart == $rowEnd) { + $ranges[] = $hashedValue->col . $rowStart; + } else { + $ranges[] = $hashedValue->col . $rowStart . ':' . $hashedValue->col . $rowEnd; + } + + $rowStart = $row; + $rowEnd = $row; + } + } + + if ($rowStart !== null) { + if ($rowStart == $rowEnd) { + $ranges[] = $hashedValue->col . $rowStart; + } else { + $ranges[] = $hashedValue->col . $rowStart . ':' . $hashedValue->col . $rowEnd; + } + } + + foreach ($ranges as $range) { + $mergedCoordCollection[$range] = $hashedValue->value; + } + } + + return $mergedCoordCollection; + } + + /** + * Get the individual cell blocks from a range string, removing any $ characters. + * then splitting by operators and returning an array with ranges and operators. + * + * @param string $rangeString + * + * @return array[] + */ + private static function getCellBlocksFromRangeString($rangeString) + { + $rangeString = str_replace('$', '', strtoupper($rangeString)); + + // split range sets on intersection (space) or union (,) operators + $tokens = preg_split('/([ ,])/', $rangeString, -1, PREG_SPLIT_DELIM_CAPTURE); + // separate the range sets and the operators into arrays + $split = array_chunk($tokens, 2); + $ranges = array_column($split, 0); + $operators = array_column($split, 1); + + return [$ranges, $operators]; + } + + /** + * Check that the given range is valid, i.e. that the start column and row are not greater than the end column and + * row. + * + * @param string $cellBlock The original range, for displaying a meaningful error message + * @param int $startColumnIndex + * @param int $endColumnIndex + * @param int $currentRow + * @param int $endRow + */ + private static function validateRange($cellBlock, $startColumnIndex, $endColumnIndex, $currentRow, $endRow): void + { + if ($startColumnIndex >= $endColumnIndex || $currentRow > $endRow) { + throw new Exception('Invalid range: "' . $cellBlock . '"'); + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataType.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataType.php new file mode 100644 index 0000000..cee3e1e --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataType.php @@ -0,0 +1,85 @@ + 0, + '#DIV/0!' => 1, + '#VALUE!' => 2, + '#REF!' => 3, + '#NAME?' => 4, + '#NUM!' => 5, + '#N/A' => 6, + ]; + + /** + * Get list of error codes. + * + * @return array + */ + public static function getErrorCodes() + { + return self::$errorCodes; + } + + /** + * Check a string that it satisfies Excel requirements. + * + * @param null|RichText|string $textValue Value to sanitize to an Excel string + * + * @return null|RichText|string Sanitized value + */ + public static function checkString($textValue) + { + if ($textValue instanceof RichText) { + // TODO: Sanitize Rich-Text string (max. character count is 32,767) + return $textValue; + } + + // string must never be longer than 32,767 characters, truncate if necessary + $textValue = StringHelper::substring($textValue, 0, 32767); + + // we require that newline is represented as "\n" in core, not as "\r\n" or "\r" + $textValue = str_replace(["\r\n", "\r"], "\n", $textValue); + + return $textValue; + } + + /** + * Check a value that it is a valid error code. + * + * @param mixed $value Value to sanitize to an Excel error code + * + * @return string Sanitized value + */ + public static function checkErrorCode($value) + { + $value = (string) $value; + + if (!isset(self::$errorCodes[$value])) { + $value = '#NULL!'; + } + + return $value; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataValidation.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataValidation.php new file mode 100644 index 0000000..7ee53ea --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataValidation.php @@ -0,0 +1,497 @@ +formula1; + } + + /** + * Set Formula 1. + * + * @param string $formula + * + * @return $this + */ + public function setFormula1($formula) + { + $this->formula1 = $formula; + + return $this; + } + + /** + * Get Formula 2. + * + * @return string + */ + public function getFormula2() + { + return $this->formula2; + } + + /** + * Set Formula 2. + * + * @param string $formula + * + * @return $this + */ + public function setFormula2($formula) + { + $this->formula2 = $formula; + + return $this; + } + + /** + * Get Type. + * + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * Set Type. + * + * @param string $type + * + * @return $this + */ + public function setType($type) + { + $this->type = $type; + + return $this; + } + + /** + * Get Error style. + * + * @return string + */ + public function getErrorStyle() + { + return $this->errorStyle; + } + + /** + * Set Error style. + * + * @param string $errorStyle see self::STYLE_* + * + * @return $this + */ + public function setErrorStyle($errorStyle) + { + $this->errorStyle = $errorStyle; + + return $this; + } + + /** + * Get Operator. + * + * @return string + */ + public function getOperator() + { + return $this->operator; + } + + /** + * Set Operator. + * + * @param string $operator + * + * @return $this + */ + public function setOperator($operator) + { + $this->operator = $operator; + + return $this; + } + + /** + * Get Allow Blank. + * + * @return bool + */ + public function getAllowBlank() + { + return $this->allowBlank; + } + + /** + * Set Allow Blank. + * + * @param bool $allowBlank + * + * @return $this + */ + public function setAllowBlank($allowBlank) + { + $this->allowBlank = $allowBlank; + + return $this; + } + + /** + * Get Show DropDown. + * + * @return bool + */ + public function getShowDropDown() + { + return $this->showDropDown; + } + + /** + * Set Show DropDown. + * + * @param bool $showDropDown + * + * @return $this + */ + public function setShowDropDown($showDropDown) + { + $this->showDropDown = $showDropDown; + + return $this; + } + + /** + * Get Show InputMessage. + * + * @return bool + */ + public function getShowInputMessage() + { + return $this->showInputMessage; + } + + /** + * Set Show InputMessage. + * + * @param bool $showInputMessage + * + * @return $this + */ + public function setShowInputMessage($showInputMessage) + { + $this->showInputMessage = $showInputMessage; + + return $this; + } + + /** + * Get Show ErrorMessage. + * + * @return bool + */ + public function getShowErrorMessage() + { + return $this->showErrorMessage; + } + + /** + * Set Show ErrorMessage. + * + * @param bool $showErrorMessage + * + * @return $this + */ + public function setShowErrorMessage($showErrorMessage) + { + $this->showErrorMessage = $showErrorMessage; + + return $this; + } + + /** + * Get Error title. + * + * @return string + */ + public function getErrorTitle() + { + return $this->errorTitle; + } + + /** + * Set Error title. + * + * @param string $errorTitle + * + * @return $this + */ + public function setErrorTitle($errorTitle) + { + $this->errorTitle = $errorTitle; + + return $this; + } + + /** + * Get Error. + * + * @return string + */ + public function getError() + { + return $this->error; + } + + /** + * Set Error. + * + * @param string $error + * + * @return $this + */ + public function setError($error) + { + $this->error = $error; + + return $this; + } + + /** + * Get Prompt title. + * + * @return string + */ + public function getPromptTitle() + { + return $this->promptTitle; + } + + /** + * Set Prompt title. + * + * @param string $promptTitle + * + * @return $this + */ + public function setPromptTitle($promptTitle) + { + $this->promptTitle = $promptTitle; + + return $this; + } + + /** + * Get Prompt. + * + * @return string + */ + public function getPrompt() + { + return $this->prompt; + } + + /** + * Set Prompt. + * + * @param string $prompt + * + * @return $this + */ + public function setPrompt($prompt) + { + $this->prompt = $prompt; + + return $this; + } + + /** + * Get hash code. + * + * @return string Hash code + */ + public function getHashCode() + { + return md5( + $this->formula1 . + $this->formula2 . + $this->type . + $this->errorStyle . + $this->operator . + ($this->allowBlank ? 't' : 'f') . + ($this->showDropDown ? 't' : 'f') . + ($this->showInputMessage ? 't' : 'f') . + ($this->showErrorMessage ? 't' : 'f') . + $this->errorTitle . + $this->error . + $this->promptTitle . + $this->prompt . + $this->sqref . + __CLASS__ + ); + } + + /** + * Implement PHP __clone to create a deep clone, not just a shallow copy. + */ + public function __clone() + { + $vars = get_object_vars($this); + foreach ($vars as $key => $value) { + if (is_object($value)) { + $this->$key = clone $value; + } else { + $this->$key = $value; + } + } + } + + /** @var ?string */ + private $sqref; + + public function getSqref(): ?string + { + return $this->sqref; + } + + public function setSqref(?string $str): self + { + $this->sqref = $str; + + return $this; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataValidator.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataValidator.php new file mode 100644 index 0000000..430d81b --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataValidator.php @@ -0,0 +1,77 @@ +hasDataValidation()) { + return true; + } + + $cellValue = $cell->getValue(); + $dataValidation = $cell->getDataValidation(); + + if (!$dataValidation->getAllowBlank() && ($cellValue === null || $cellValue === '')) { + return false; + } + + // TODO: write check on all cases + switch ($dataValidation->getType()) { + case DataValidation::TYPE_LIST: + return $this->isValueInList($cell); + } + + return false; + } + + /** + * Does this cell contain valid value, based on list? + * + * @param Cell $cell Cell to check the value + * + * @return bool + */ + private function isValueInList(Cell $cell) + { + $cellValue = $cell->getValue(); + $dataValidation = $cell->getDataValidation(); + + $formula1 = $dataValidation->getFormula1(); + if (!empty($formula1)) { + // inline values list + if ($formula1[0] === '"') { + return in_array(strtolower($cellValue), explode(',', strtolower(trim($formula1, '"'))), true); + } elseif (strpos($formula1, ':') > 0) { + // values list cells + $matchFormula = '=MATCH(' . $cell->getCoordinate() . ', ' . $formula1 . ', 0)'; + $calculation = Calculation::getInstance($cell->getWorksheet()->getParent()); + + try { + $result = $calculation->calculateFormula($matchFormula, $cell->getCoordinate(), $cell); + + return $result !== Functions::NA(); + } catch (Exception $ex) { + return false; + } + } + } + + return true; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DefaultValueBinder.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DefaultValueBinder.php new file mode 100644 index 0000000..4f2cdf7 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DefaultValueBinder.php @@ -0,0 +1,83 @@ +format('Y-m-d H:i:s'); + } elseif (!($value instanceof RichText)) { + // Attempt to cast any unexpected objects to string + $value = (string) $value; + } + } + + // Set value explicit + $cell->setValueExplicit($value, static::dataTypeForValue($value)); + + // Done! + return true; + } + + /** + * DataType for value. + * + * @param mixed $value + * + * @return string + */ + public static function dataTypeForValue($value) + { + // Match the value against a few data types + if ($value === null) { + return DataType::TYPE_NULL; + } elseif (is_float($value) || is_int($value)) { + return DataType::TYPE_NUMERIC; + } elseif (is_bool($value)) { + return DataType::TYPE_BOOL; + } elseif ($value === '') { + return DataType::TYPE_STRING; + } elseif ($value instanceof RichText) { + return DataType::TYPE_INLINE; + } elseif (is_string($value) && strlen($value) > 1 && $value[0] === '=') { + return DataType::TYPE_FORMULA; + } elseif (preg_match('/^[\+\-]?(\d+\\.?\d*|\d*\\.?\d+)([Ee][\-\+]?[0-2]?\d{1,3})?$/', $value)) { + $tValue = ltrim($value, '+-'); + if (is_string($value) && strlen($tValue) > 1 && $tValue[0] === '0' && $tValue[1] !== '.') { + return DataType::TYPE_STRING; + } elseif ((strpos($value, '.') === false) && ($value > PHP_INT_MAX)) { + return DataType::TYPE_STRING; + } elseif (!is_numeric($value)) { + return DataType::TYPE_STRING; + } + + return DataType::TYPE_NUMERIC; + } elseif (is_string($value)) { + $errorCodes = DataType::getErrorCodes(); + if (isset($errorCodes[$value])) { + return DataType::TYPE_ERROR; + } + } + + return DataType::TYPE_STRING; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Hyperlink.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Hyperlink.php new file mode 100644 index 0000000..ffdcbac --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Hyperlink.php @@ -0,0 +1,113 @@ +url = $url; + $this->tooltip = $tooltip; + } + + /** + * Get URL. + * + * @return string + */ + public function getUrl() + { + return $this->url; + } + + /** + * Set URL. + * + * @param string $url + * + * @return $this + */ + public function setUrl($url) + { + $this->url = $url; + + return $this; + } + + /** + * Get tooltip. + * + * @return string + */ + public function getTooltip() + { + return $this->tooltip; + } + + /** + * Set tooltip. + * + * @param string $tooltip + * + * @return $this + */ + public function setTooltip($tooltip) + { + $this->tooltip = $tooltip; + + return $this; + } + + /** + * Is this hyperlink internal? (to another worksheet). + * + * @return bool + */ + public function isInternal() + { + return strpos($this->url, 'sheet://') !== false; + } + + /** + * @return string + */ + public function getTypeHyperlink() + { + return $this->isInternal() ? '' : 'External'; + } + + /** + * Get hash code. + * + * @return string Hash code + */ + public function getHashCode() + { + return md5( + $this->url . + $this->tooltip . + __CLASS__ + ); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/IValueBinder.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/IValueBinder.php new file mode 100644 index 0000000..5af9f5f --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/IValueBinder.php @@ -0,0 +1,16 @@ +convertNull = $suppressConversion; + + return $this; + } + + public function setBooleanConversion(bool $suppressConversion = false): self + { + $this->convertBoolean = $suppressConversion; + + return $this; + } + + public function getBooleanConversion(): bool + { + return $this->convertBoolean; + } + + public function setNumericConversion(bool $suppressConversion = false): self + { + $this->convertNumeric = $suppressConversion; + + return $this; + } + + public function setFormulaConversion(bool $suppressConversion = false): self + { + $this->convertFormula = $suppressConversion; + + return $this; + } + + public function setConversionForAllValueTypes(bool $suppressConversion = false): self + { + $this->convertNull = $suppressConversion; + $this->convertBoolean = $suppressConversion; + $this->convertNumeric = $suppressConversion; + $this->convertFormula = $suppressConversion; + + return $this; + } + + /** + * Bind value to a cell. + * + * @param Cell $cell Cell to bind value to + * @param mixed $value Value to bind in cell + */ + public function bindValue(Cell $cell, $value) + { + if (is_object($value)) { + return $this->bindObjectValue($cell, $value); + } + + // sanitize UTF-8 strings + if (is_string($value)) { + $value = StringHelper::sanitizeUTF8($value); + } + + if ($value === null && $this->convertNull === false) { + $cell->setValueExplicit($value, DataType::TYPE_NULL); + } elseif (is_bool($value) && $this->convertBoolean === false) { + $cell->setValueExplicit($value, DataType::TYPE_BOOL); + } elseif ((is_int($value) || is_float($value)) && $this->convertNumeric === false) { + $cell->setValueExplicit($value, DataType::TYPE_NUMERIC); + } elseif (is_string($value) && strlen($value) > 1 && $value[0] === '=' && $this->convertFormula === false) { + $cell->setValueExplicit($value, DataType::TYPE_FORMULA); + } else { + if (is_string($value) && strlen($value) > 1 && $value[0] === '=') { + $cell->getStyle()->setQuotePrefix(true); + } + $cell->setValueExplicit((string) $value, DataType::TYPE_STRING); + } + + return true; + } + + protected function bindObjectValue(Cell $cell, object $value): bool + { + // Handle any objects that might be injected + if ($value instanceof DateTimeInterface) { + $value = $value->format('Y-m-d H:i:s'); + } elseif ($value instanceof RichText) { + $cell->setValueExplicit($value, DataType::TYPE_INLINE); + + return true; + } + + $cell->setValueExplicit((string) $value, DataType::TYPE_STRING); + + return true; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Axis.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Axis.php new file mode 100644 index 0000000..eeed326 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Axis.php @@ -0,0 +1,554 @@ + self::FORMAT_CODE_GENERAL, + 'source_linked' => 1, + ]; + + /** + * Axis Options. + * + * @var mixed[] + */ + private $axisOptions = [ + 'minimum' => null, + 'maximum' => null, + 'major_unit' => null, + 'minor_unit' => null, + 'orientation' => self::ORIENTATION_NORMAL, + 'minor_tick_mark' => self::TICK_MARK_NONE, + 'major_tick_mark' => self::TICK_MARK_NONE, + 'axis_labels' => self::AXIS_LABELS_NEXT_TO, + 'horizontal_crosses' => self::HORIZONTAL_CROSSES_AUTOZERO, + 'horizontal_crosses_value' => null, + ]; + + /** + * Fill Properties. + * + * @var mixed[] + */ + private $fillProperties = [ + 'type' => self::EXCEL_COLOR_TYPE_ARGB, + 'value' => null, + 'alpha' => 0, + ]; + + /** + * Line Properties. + * + * @var mixed[] + */ + private $lineProperties = [ + 'type' => self::EXCEL_COLOR_TYPE_ARGB, + 'value' => null, + 'alpha' => 0, + ]; + + /** + * Line Style Properties. + * + * @var mixed[] + */ + private $lineStyleProperties = [ + 'width' => '9525', + 'compound' => self::LINE_STYLE_COMPOUND_SIMPLE, + 'dash' => self::LINE_STYLE_DASH_SOLID, + 'cap' => self::LINE_STYLE_CAP_FLAT, + 'join' => self::LINE_STYLE_JOIN_BEVEL, + 'arrow' => [ + 'head' => [ + 'type' => self::LINE_STYLE_ARROW_TYPE_NOARROW, + 'size' => self::LINE_STYLE_ARROW_SIZE_5, + ], + 'end' => [ + 'type' => self::LINE_STYLE_ARROW_TYPE_NOARROW, + 'size' => self::LINE_STYLE_ARROW_SIZE_8, + ], + ], + ]; + + /** + * Shadow Properties. + * + * @var mixed[] + */ + private $shadowProperties = [ + 'presets' => self::SHADOW_PRESETS_NOSHADOW, + 'effect' => null, + 'color' => [ + 'type' => self::EXCEL_COLOR_TYPE_STANDARD, + 'value' => 'black', + 'alpha' => 40, + ], + 'size' => [ + 'sx' => null, + 'sy' => null, + 'kx' => null, + ], + 'blur' => null, + 'direction' => null, + 'distance' => null, + 'algn' => null, + 'rotWithShape' => null, + ]; + + /** + * Glow Properties. + * + * @var mixed[] + */ + private $glowProperties = [ + 'size' => null, + 'color' => [ + 'type' => self::EXCEL_COLOR_TYPE_STANDARD, + 'value' => 'black', + 'alpha' => 40, + ], + ]; + + /** + * Soft Edge Properties. + * + * @var mixed[] + */ + private $softEdges = [ + 'size' => null, + ]; + + /** + * Get Series Data Type. + * + * @param mixed $format_code + */ + public function setAxisNumberProperties($format_code): void + { + $this->axisNumber['format'] = (string) $format_code; + $this->axisNumber['source_linked'] = 0; + } + + /** + * Get Axis Number Format Data Type. + * + * @return string + */ + public function getAxisNumberFormat() + { + return $this->axisNumber['format']; + } + + /** + * Get Axis Number Source Linked. + * + * @return string + */ + public function getAxisNumberSourceLinked() + { + return (string) $this->axisNumber['source_linked']; + } + + /** + * Set Axis Options Properties. + * + * @param string $axisLabels + * @param string $horizontalCrossesValue + * @param string $horizontalCrosses + * @param string $axisOrientation + * @param string $majorTmt + * @param string $minorTmt + * @param string $minimum + * @param string $maximum + * @param string $majorUnit + * @param string $minorUnit + */ + public function setAxisOptionsProperties($axisLabels, $horizontalCrossesValue = null, $horizontalCrosses = null, $axisOrientation = null, $majorTmt = null, $minorTmt = null, $minimum = null, $maximum = null, $majorUnit = null, $minorUnit = null): void + { + $this->axisOptions['axis_labels'] = (string) $axisLabels; + ($horizontalCrossesValue !== null) ? $this->axisOptions['horizontal_crosses_value'] = (string) $horizontalCrossesValue : null; + ($horizontalCrosses !== null) ? $this->axisOptions['horizontal_crosses'] = (string) $horizontalCrosses : null; + ($axisOrientation !== null) ? $this->axisOptions['orientation'] = (string) $axisOrientation : null; + ($majorTmt !== null) ? $this->axisOptions['major_tick_mark'] = (string) $majorTmt : null; + ($minorTmt !== null) ? $this->axisOptions['minor_tick_mark'] = (string) $minorTmt : null; + ($minorTmt !== null) ? $this->axisOptions['minor_tick_mark'] = (string) $minorTmt : null; + ($minimum !== null) ? $this->axisOptions['minimum'] = (string) $minimum : null; + ($maximum !== null) ? $this->axisOptions['maximum'] = (string) $maximum : null; + ($majorUnit !== null) ? $this->axisOptions['major_unit'] = (string) $majorUnit : null; + ($minorUnit !== null) ? $this->axisOptions['minor_unit'] = (string) $minorUnit : null; + } + + /** + * Get Axis Options Property. + * + * @param string $property + * + * @return string + */ + public function getAxisOptionsProperty($property) + { + return $this->axisOptions[$property]; + } + + /** + * Set Axis Orientation Property. + * + * @param string $orientation + */ + public function setAxisOrientation($orientation): void + { + $this->axisOptions['orientation'] = (string) $orientation; + } + + /** + * Set Fill Property. + * + * @param string $color + * @param int $alpha + * @param string $AlphaType + */ + public function setFillParameters($color, $alpha = 0, $AlphaType = self::EXCEL_COLOR_TYPE_ARGB): void + { + $this->fillProperties = $this->setColorProperties($color, $alpha, $AlphaType); + } + + /** + * Set Line Property. + * + * @param string $color + * @param int $alpha + * @param string $alphaType + */ + public function setLineParameters($color, $alpha = 0, $alphaType = self::EXCEL_COLOR_TYPE_ARGB): void + { + $this->lineProperties = $this->setColorProperties($color, $alpha, $alphaType); + } + + /** + * Get Fill Property. + * + * @param string $property + * + * @return string + */ + public function getFillProperty($property) + { + return $this->fillProperties[$property]; + } + + /** + * Get Line Property. + * + * @param string $property + * + * @return string + */ + public function getLineProperty($property) + { + return $this->lineProperties[$property]; + } + + /** + * Set Line Style Properties. + * + * @param float $lineWidth + * @param string $compoundType + * @param string $dashType + * @param string $capType + * @param string $joinType + * @param string $headArrowType + * @param string $headArrowSize + * @param string $endArrowType + * @param string $endArrowSize + */ + public function setLineStyleProperties($lineWidth = null, $compoundType = null, $dashType = null, $capType = null, $joinType = null, $headArrowType = null, $headArrowSize = null, $endArrowType = null, $endArrowSize = null): void + { + ($lineWidth !== null) ? $this->lineStyleProperties['width'] = $this->getExcelPointsWidth((float) $lineWidth) : null; + ($compoundType !== null) ? $this->lineStyleProperties['compound'] = (string) $compoundType : null; + ($dashType !== null) ? $this->lineStyleProperties['dash'] = (string) $dashType : null; + ($capType !== null) ? $this->lineStyleProperties['cap'] = (string) $capType : null; + ($joinType !== null) ? $this->lineStyleProperties['join'] = (string) $joinType : null; + ($headArrowType !== null) ? $this->lineStyleProperties['arrow']['head']['type'] = (string) $headArrowType : null; + ($headArrowSize !== null) ? $this->lineStyleProperties['arrow']['head']['size'] = (string) $headArrowSize : null; + ($endArrowType !== null) ? $this->lineStyleProperties['arrow']['end']['type'] = (string) $endArrowType : null; + ($endArrowSize !== null) ? $this->lineStyleProperties['arrow']['end']['size'] = (string) $endArrowSize : null; + } + + /** + * Get Line Style Property. + * + * @param array|string $elements + * + * @return string + */ + public function getLineStyleProperty($elements) + { + return $this->getArrayElementsValue($this->lineStyleProperties, $elements); + } + + /** + * Get Line Style Arrow Excel Width. + * + * @param string $arrow + * + * @return string + */ + public function getLineStyleArrowWidth($arrow) + { + return $this->getLineStyleArrowSize($this->lineStyleProperties['arrow'][$arrow]['size'], 'w'); + } + + /** + * Get Line Style Arrow Excel Length. + * + * @param string $arrow + * + * @return string + */ + public function getLineStyleArrowLength($arrow) + { + return $this->getLineStyleArrowSize($this->lineStyleProperties['arrow'][$arrow]['size'], 'len'); + } + + /** + * Set Shadow Properties. + * + * @param int $shadowPresets + * @param string $colorValue + * @param string $colorType + * @param string $colorAlpha + * @param float $blur + * @param int $angle + * @param float $distance + */ + public function setShadowProperties($shadowPresets, $colorValue = null, $colorType = null, $colorAlpha = null, $blur = null, $angle = null, $distance = null): void + { + $this->setShadowPresetsProperties((int) $shadowPresets) + ->setShadowColor( + $colorValue ?? $this->shadowProperties['color']['value'], + $colorAlpha ?? (int) $this->shadowProperties['color']['alpha'], + $colorType ?? $this->shadowProperties['color']['type'] + ) + ->setShadowBlur($blur) + ->setShadowAngle($angle) + ->setShadowDistance($distance); + } + + /** + * Set Shadow Color. + * + * @param int $presets + * + * @return $this + */ + private function setShadowPresetsProperties($presets) + { + $this->shadowProperties['presets'] = $presets; + $this->setShadowPropertiesMapValues($this->getShadowPresetsMap($presets)); + + return $this; + } + + /** + * Set Shadow Properties from Mapped Values. + * + * @param mixed $reference + * + * @return $this + */ + private function setShadowPropertiesMapValues(array $propertiesMap, &$reference = null) + { + $base_reference = $reference; + foreach ($propertiesMap as $property_key => $property_val) { + if (is_array($property_val)) { + if ($reference === null) { + $reference = &$this->shadowProperties[$property_key]; + } else { + $reference = &$reference[$property_key]; + } + $this->setShadowPropertiesMapValues($property_val, $reference); + } else { + if ($base_reference === null) { + $this->shadowProperties[$property_key] = $property_val; + } else { + $reference[$property_key] = $property_val; + } + } + } + + return $this; + } + + /** + * Set Shadow Color. + * + * @param string $color + * @param int $alpha + * @param string $alphaType + * + * @return $this + */ + private function setShadowColor($color, $alpha, $alphaType) + { + $this->shadowProperties['color'] = $this->setColorProperties($color, $alpha, $alphaType); + + return $this; + } + + /** + * Set Shadow Blur. + * + * @param float $blur + * + * @return $this + */ + private function setShadowBlur($blur) + { + if ($blur !== null) { + $this->shadowProperties['blur'] = (string) $this->getExcelPointsWidth($blur); + } + + return $this; + } + + /** + * Set Shadow Angle. + * + * @param int $angle + * + * @return $this + */ + private function setShadowAngle($angle) + { + if ($angle !== null) { + $this->shadowProperties['direction'] = (string) $this->getExcelPointsAngle($angle); + } + + return $this; + } + + /** + * Set Shadow Distance. + * + * @param float $distance + * + * @return $this + */ + private function setShadowDistance($distance) + { + if ($distance !== null) { + $this->shadowProperties['distance'] = (string) $this->getExcelPointsWidth($distance); + } + + return $this; + } + + /** + * Get Shadow Property. + * + * @param string|string[] $elements + * + * @return null|array|int|string + */ + public function getShadowProperty($elements) + { + return $this->getArrayElementsValue($this->shadowProperties, $elements); + } + + /** + * Set Glow Properties. + * + * @param float $size + * @param string $colorValue + * @param int $colorAlpha + * @param string $colorType + */ + public function setGlowProperties($size, $colorValue = null, $colorAlpha = null, $colorType = null): void + { + $this->setGlowSize($size) + ->setGlowColor( + $colorValue ?? $this->glowProperties['color']['value'], + $colorAlpha ?? (int) $this->glowProperties['color']['alpha'], + $colorType ?? $this->glowProperties['color']['type'] + ); + } + + /** + * Get Glow Property. + * + * @param array|string $property + * + * @return string + */ + public function getGlowProperty($property) + { + return $this->getArrayElementsValue($this->glowProperties, $property); + } + + /** + * Set Glow Color. + * + * @param float $size + * + * @return $this + */ + private function setGlowSize($size) + { + if ($size !== null) { + $this->glowProperties['size'] = $this->getExcelPointsWidth($size); + } + + return $this; + } + + /** + * Set Glow Color. + * + * @param string $color + * @param int $alpha + * @param string $colorType + * + * @return $this + */ + private function setGlowColor($color, $alpha, $colorType) + { + $this->glowProperties['color'] = $this->setColorProperties($color, $alpha, $colorType); + + return $this; + } + + /** + * Set Soft Edges Size. + * + * @param float $size + */ + public function setSoftEdges($size): void + { + if ($size !== null) { + $softEdges['size'] = (string) $this->getExcelPointsWidth($size); + } + } + + /** + * Get Soft Edges Size. + * + * @return string + */ + public function getSoftEdgesSize() + { + return $this->softEdges['size']; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Chart.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Chart.php new file mode 100644 index 0000000..bed8946 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Chart.php @@ -0,0 +1,661 @@ +name = $name; + $this->title = $title; + $this->legend = $legend; + $this->xAxisLabel = $xAxisLabel; + $this->yAxisLabel = $yAxisLabel; + $this->plotArea = $plotArea; + $this->plotVisibleOnly = $plotVisibleOnly; + $this->displayBlanksAs = $displayBlanksAs; + $this->xAxis = $xAxis; + $this->yAxis = $yAxis; + $this->majorGridlines = $majorGridlines; + $this->minorGridlines = $minorGridlines; + } + + /** + * Get Name. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Get Worksheet. + * + * @return Worksheet + */ + public function getWorksheet() + { + return $this->worksheet; + } + + /** + * Set Worksheet. + * + * @return $this + */ + public function setWorksheet(?Worksheet $worksheet = null) + { + $this->worksheet = $worksheet; + + return $this; + } + + /** + * Get Title. + * + * @return Title + */ + public function getTitle() + { + return $this->title; + } + + /** + * Set Title. + * + * @return $this + */ + public function setTitle(Title $title) + { + $this->title = $title; + + return $this; + } + + /** + * Get Legend. + * + * @return Legend + */ + public function getLegend() + { + return $this->legend; + } + + /** + * Set Legend. + * + * @return $this + */ + public function setLegend(Legend $legend) + { + $this->legend = $legend; + + return $this; + } + + /** + * Get X-Axis Label. + * + * @return Title + */ + public function getXAxisLabel() + { + return $this->xAxisLabel; + } + + /** + * Set X-Axis Label. + * + * @return $this + */ + public function setXAxisLabel(Title $label) + { + $this->xAxisLabel = $label; + + return $this; + } + + /** + * Get Y-Axis Label. + * + * @return Title + */ + public function getYAxisLabel() + { + return $this->yAxisLabel; + } + + /** + * Set Y-Axis Label. + * + * @return $this + */ + public function setYAxisLabel(Title $label) + { + $this->yAxisLabel = $label; + + return $this; + } + + /** + * Get Plot Area. + * + * @return PlotArea + */ + public function getPlotArea() + { + return $this->plotArea; + } + + /** + * Get Plot Visible Only. + * + * @return bool + */ + public function getPlotVisibleOnly() + { + return $this->plotVisibleOnly; + } + + /** + * Set Plot Visible Only. + * + * @param bool $plotVisibleOnly + * + * @return $this + */ + public function setPlotVisibleOnly($plotVisibleOnly) + { + $this->plotVisibleOnly = $plotVisibleOnly; + + return $this; + } + + /** + * Get Display Blanks as. + * + * @return string + */ + public function getDisplayBlanksAs() + { + return $this->displayBlanksAs; + } + + /** + * Set Display Blanks as. + * + * @param string $displayBlanksAs + * + * @return $this + */ + public function setDisplayBlanksAs($displayBlanksAs) + { + $this->displayBlanksAs = $displayBlanksAs; + + return $this; + } + + /** + * Get yAxis. + * + * @return Axis + */ + public function getChartAxisY() + { + if ($this->yAxis !== null) { + return $this->yAxis; + } + + return new Axis(); + } + + /** + * Get xAxis. + * + * @return Axis + */ + public function getChartAxisX() + { + if ($this->xAxis !== null) { + return $this->xAxis; + } + + return new Axis(); + } + + /** + * Get Major Gridlines. + * + * @return GridLines + */ + public function getMajorGridlines() + { + if ($this->majorGridlines !== null) { + return $this->majorGridlines; + } + + return new GridLines(); + } + + /** + * Get Minor Gridlines. + * + * @return GridLines + */ + public function getMinorGridlines() + { + if ($this->minorGridlines !== null) { + return $this->minorGridlines; + } + + return new GridLines(); + } + + /** + * Set the Top Left position for the chart. + * + * @param string $cell + * @param int $xOffset + * @param int $yOffset + * + * @return $this + */ + public function setTopLeftPosition($cell, $xOffset = null, $yOffset = null) + { + $this->topLeftCellRef = $cell; + if ($xOffset !== null) { + $this->setTopLeftXOffset($xOffset); + } + if ($yOffset !== null) { + $this->setTopLeftYOffset($yOffset); + } + + return $this; + } + + /** + * Get the top left position of the chart. + * + * @return array{cell: string, xOffset: int, yOffset: int} an associative array containing the cell address, X-Offset and Y-Offset from the top left of that cell + */ + public function getTopLeftPosition() + { + return [ + 'cell' => $this->topLeftCellRef, + 'xOffset' => $this->topLeftXOffset, + 'yOffset' => $this->topLeftYOffset, + ]; + } + + /** + * Get the cell address where the top left of the chart is fixed. + * + * @return string + */ + public function getTopLeftCell() + { + return $this->topLeftCellRef; + } + + /** + * Set the Top Left cell position for the chart. + * + * @param string $cell + * + * @return $this + */ + public function setTopLeftCell($cell) + { + $this->topLeftCellRef = $cell; + + return $this; + } + + /** + * Set the offset position within the Top Left cell for the chart. + * + * @param int $xOffset + * @param int $yOffset + * + * @return $this + */ + public function setTopLeftOffset($xOffset, $yOffset) + { + if ($xOffset !== null) { + $this->setTopLeftXOffset($xOffset); + } + + if ($yOffset !== null) { + $this->setTopLeftYOffset($yOffset); + } + + return $this; + } + + /** + * Get the offset position within the Top Left cell for the chart. + * + * @return int[] + */ + public function getTopLeftOffset() + { + return [ + 'X' => $this->topLeftXOffset, + 'Y' => $this->topLeftYOffset, + ]; + } + + public function setTopLeftXOffset($xOffset) + { + $this->topLeftXOffset = $xOffset; + + return $this; + } + + public function getTopLeftXOffset() + { + return $this->topLeftXOffset; + } + + public function setTopLeftYOffset($yOffset) + { + $this->topLeftYOffset = $yOffset; + + return $this; + } + + public function getTopLeftYOffset() + { + return $this->topLeftYOffset; + } + + /** + * Set the Bottom Right position of the chart. + * + * @param string $cell + * @param int $xOffset + * @param int $yOffset + * + * @return $this + */ + public function setBottomRightPosition($cell, $xOffset = null, $yOffset = null) + { + $this->bottomRightCellRef = $cell; + if ($xOffset !== null) { + $this->setBottomRightXOffset($xOffset); + } + if ($yOffset !== null) { + $this->setBottomRightYOffset($yOffset); + } + + return $this; + } + + /** + * Get the bottom right position of the chart. + * + * @return array an associative array containing the cell address, X-Offset and Y-Offset from the top left of that cell + */ + public function getBottomRightPosition() + { + return [ + 'cell' => $this->bottomRightCellRef, + 'xOffset' => $this->bottomRightXOffset, + 'yOffset' => $this->bottomRightYOffset, + ]; + } + + public function setBottomRightCell($cell) + { + $this->bottomRightCellRef = $cell; + + return $this; + } + + /** + * Get the cell address where the bottom right of the chart is fixed. + * + * @return string + */ + public function getBottomRightCell() + { + return $this->bottomRightCellRef; + } + + /** + * Set the offset position within the Bottom Right cell for the chart. + * + * @param int $xOffset + * @param int $yOffset + * + * @return $this + */ + public function setBottomRightOffset($xOffset, $yOffset) + { + if ($xOffset !== null) { + $this->setBottomRightXOffset($xOffset); + } + + if ($yOffset !== null) { + $this->setBottomRightYOffset($yOffset); + } + + return $this; + } + + /** + * Get the offset position within the Bottom Right cell for the chart. + * + * @return int[] + */ + public function getBottomRightOffset() + { + return [ + 'X' => $this->bottomRightXOffset, + 'Y' => $this->bottomRightYOffset, + ]; + } + + public function setBottomRightXOffset($xOffset) + { + $this->bottomRightXOffset = $xOffset; + + return $this; + } + + public function getBottomRightXOffset() + { + return $this->bottomRightXOffset; + } + + public function setBottomRightYOffset($yOffset) + { + $this->bottomRightYOffset = $yOffset; + + return $this; + } + + public function getBottomRightYOffset() + { + return $this->bottomRightYOffset; + } + + public function refresh(): void + { + if ($this->worksheet !== null) { + $this->plotArea->refresh($this->worksheet); + } + } + + /** + * Render the chart to given file (or stream). + * + * @param string $outputDestination Name of the file render to + * + * @return bool true on success + */ + public function render($outputDestination = null) + { + if ($outputDestination == 'php://output') { + $outputDestination = null; + } + + $libraryName = Settings::getChartRenderer(); + if ($libraryName === null) { + return false; + } + + // Ensure that data series values are up-to-date before we render + $this->refresh(); + + $renderer = new $libraryName($this); + + return $renderer->render($outputDestination); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/DataSeries.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/DataSeries.php new file mode 100644 index 0000000..067d30e --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/DataSeries.php @@ -0,0 +1,394 @@ +plotType = $plotType; + $this->plotGrouping = $plotGrouping; + $this->plotOrder = $plotOrder; + $keys = array_keys($plotValues); + $this->plotValues = $plotValues; + if ((count($plotLabel) == 0) || ($plotLabel[$keys[0]] === null)) { + $plotLabel[$keys[0]] = new DataSeriesValues(); + } + $this->plotLabel = $plotLabel; + + if ((count($plotCategory) == 0) || ($plotCategory[$keys[0]] === null)) { + $plotCategory[$keys[0]] = new DataSeriesValues(); + } + $this->plotCategory = $plotCategory; + + $this->smoothLine = $smoothLine; + $this->plotStyle = $plotStyle; + + if ($plotDirection === null) { + $plotDirection = self::DIRECTION_COL; + } + $this->plotDirection = $plotDirection; + } + + /** + * Get Plot Type. + * + * @return string + */ + public function getPlotType() + { + return $this->plotType; + } + + /** + * Set Plot Type. + * + * @param string $plotType + * + * @return $this + */ + public function setPlotType($plotType) + { + $this->plotType = $plotType; + + return $this; + } + + /** + * Get Plot Grouping Type. + * + * @return string + */ + public function getPlotGrouping() + { + return $this->plotGrouping; + } + + /** + * Set Plot Grouping Type. + * + * @param string $groupingType + * + * @return $this + */ + public function setPlotGrouping($groupingType) + { + $this->plotGrouping = $groupingType; + + return $this; + } + + /** + * Get Plot Direction. + * + * @return string + */ + public function getPlotDirection() + { + return $this->plotDirection; + } + + /** + * Set Plot Direction. + * + * @param string $plotDirection + * + * @return $this + */ + public function setPlotDirection($plotDirection) + { + $this->plotDirection = $plotDirection; + + return $this; + } + + /** + * Get Plot Order. + * + * @return int[] + */ + public function getPlotOrder() + { + return $this->plotOrder; + } + + /** + * Get Plot Labels. + * + * @return DataSeriesValues[] + */ + public function getPlotLabels() + { + return $this->plotLabel; + } + + /** + * Get Plot Label by Index. + * + * @param mixed $index + * + * @return DataSeriesValues|false + */ + public function getPlotLabelByIndex($index) + { + $keys = array_keys($this->plotLabel); + if (in_array($index, $keys)) { + return $this->plotLabel[$index]; + } elseif (isset($keys[$index])) { + return $this->plotLabel[$keys[$index]]; + } + + return false; + } + + /** + * Get Plot Categories. + * + * @return DataSeriesValues[] + */ + public function getPlotCategories() + { + return $this->plotCategory; + } + + /** + * Get Plot Category by Index. + * + * @param mixed $index + * + * @return DataSeriesValues|false + */ + public function getPlotCategoryByIndex($index) + { + $keys = array_keys($this->plotCategory); + if (in_array($index, $keys)) { + return $this->plotCategory[$index]; + } elseif (isset($keys[$index])) { + return $this->plotCategory[$keys[$index]]; + } + + return false; + } + + /** + * Get Plot Style. + * + * @return null|string + */ + public function getPlotStyle() + { + return $this->plotStyle; + } + + /** + * Set Plot Style. + * + * @param null|string $plotStyle + * + * @return $this + */ + public function setPlotStyle($plotStyle) + { + $this->plotStyle = $plotStyle; + + return $this; + } + + /** + * Get Plot Values. + * + * @return DataSeriesValues[] + */ + public function getPlotValues() + { + return $this->plotValues; + } + + /** + * Get Plot Values by Index. + * + * @param mixed $index + * + * @return DataSeriesValues|false + */ + public function getPlotValuesByIndex($index) + { + $keys = array_keys($this->plotValues); + if (in_array($index, $keys)) { + return $this->plotValues[$index]; + } elseif (isset($keys[$index])) { + return $this->plotValues[$keys[$index]]; + } + + return false; + } + + /** + * Get Number of Plot Series. + * + * @return int + */ + public function getPlotSeriesCount() + { + return count($this->plotValues); + } + + /** + * Get Smooth Line. + * + * @return bool + */ + public function getSmoothLine() + { + return $this->smoothLine; + } + + /** + * Set Smooth Line. + * + * @param bool $smoothLine + * + * @return $this + */ + public function setSmoothLine($smoothLine) + { + $this->smoothLine = $smoothLine; + + return $this; + } + + public function refresh(Worksheet $worksheet): void + { + foreach ($this->plotValues as $plotValues) { + if ($plotValues !== null) { + $plotValues->refresh($worksheet, true); + } + } + foreach ($this->plotLabel as $plotValues) { + if ($plotValues !== null) { + $plotValues->refresh($worksheet, true); + } + } + foreach ($this->plotCategory as $plotValues) { + if ($plotValues !== null) { + $plotValues->refresh($worksheet, false); + } + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/DataSeriesValues.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/DataSeriesValues.php new file mode 100644 index 0000000..8806333 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/DataSeriesValues.php @@ -0,0 +1,397 @@ +setDataType($dataType); + $this->dataSource = $dataSource; + $this->formatCode = $formatCode; + $this->pointCount = $pointCount; + $this->dataValues = $dataValues; + $this->pointMarker = $marker; + $this->fillColor = $fillColor; + } + + /** + * Get Series Data Type. + * + * @return string + */ + public function getDataType() + { + return $this->dataType; + } + + /** + * Set Series Data Type. + * + * @param string $dataType Datatype of this data series + * Typical values are: + * DataSeriesValues::DATASERIES_TYPE_STRING + * Normally used for axis point values + * DataSeriesValues::DATASERIES_TYPE_NUMBER + * Normally used for chart data values + * + * @return $this + */ + public function setDataType($dataType) + { + if (!in_array($dataType, self::$dataTypeValues)) { + throw new Exception('Invalid datatype for chart data series values'); + } + $this->dataType = $dataType; + + return $this; + } + + /** + * Get Series Data Source (formula). + * + * @return string + */ + public function getDataSource() + { + return $this->dataSource; + } + + /** + * Set Series Data Source (formula). + * + * @param string $dataSource + * + * @return $this + */ + public function setDataSource($dataSource) + { + $this->dataSource = $dataSource; + + return $this; + } + + /** + * Get Point Marker. + * + * @return string + */ + public function getPointMarker() + { + return $this->pointMarker; + } + + /** + * Set Point Marker. + * + * @param string $marker + * + * @return $this + */ + public function setPointMarker($marker) + { + $this->pointMarker = $marker; + + return $this; + } + + /** + * Get Series Format Code. + * + * @return string + */ + public function getFormatCode() + { + return $this->formatCode; + } + + /** + * Set Series Format Code. + * + * @param string $formatCode + * + * @return $this + */ + public function setFormatCode($formatCode) + { + $this->formatCode = $formatCode; + + return $this; + } + + /** + * Get Series Point Count. + * + * @return int + */ + public function getPointCount() + { + return $this->pointCount; + } + + /** + * Get fill color. + * + * @return string|string[] HEX color or array with HEX colors + */ + public function getFillColor() + { + return $this->fillColor; + } + + /** + * Set fill color for series. + * + * @param string|string[] $color HEX color or array with HEX colors + * + * @return DataSeriesValues + */ + public function setFillColor($color) + { + if (is_array($color)) { + foreach ($color as $colorValue) { + $this->validateColor($colorValue); + } + } else { + $this->validateColor($color); + } + $this->fillColor = $color; + + return $this; + } + + /** + * Method for validating hex color. + * + * @param string $color value for color + * + * @return bool true if validation was successful + */ + private function validateColor($color) + { + if (!preg_match('/^[a-f0-9]{6}$/i', $color)) { + throw new Exception(sprintf('Invalid hex color for chart series (color: "%s")', $color)); + } + + return true; + } + + /** + * Get line width for series. + * + * @return int + */ + public function getLineWidth() + { + return $this->lineWidth; + } + + /** + * Set line width for the series. + * + * @param int $width + * + * @return $this + */ + public function setLineWidth($width) + { + $minWidth = 12700; + $this->lineWidth = max($minWidth, $width); + + return $this; + } + + /** + * Identify if the Data Series is a multi-level or a simple series. + * + * @return null|bool + */ + public function isMultiLevelSeries() + { + if (count($this->dataValues) > 0) { + return is_array(array_values($this->dataValues)[0]); + } + + return null; + } + + /** + * Return the level count of a multi-level Data Series. + * + * @return int + */ + public function multiLevelCount() + { + $levelCount = 0; + foreach ($this->dataValues as $dataValueSet) { + $levelCount = max($levelCount, count($dataValueSet)); + } + + return $levelCount; + } + + /** + * Get Series Data Values. + * + * @return mixed[] + */ + public function getDataValues() + { + return $this->dataValues; + } + + /** + * Get the first Series Data value. + * + * @return mixed + */ + public function getDataValue() + { + $count = count($this->dataValues); + if ($count == 0) { + return null; + } elseif ($count == 1) { + return $this->dataValues[0]; + } + + return $this->dataValues; + } + + /** + * Set Series Data Values. + * + * @param array $dataValues + * + * @return $this + */ + public function setDataValues($dataValues) + { + $this->dataValues = Functions::flattenArray($dataValues); + $this->pointCount = count($dataValues); + + return $this; + } + + public function refresh(Worksheet $worksheet, $flatten = true): void + { + if ($this->dataSource !== null) { + $calcEngine = Calculation::getInstance($worksheet->getParent()); + $newDataValues = Calculation::unwrapResult( + $calcEngine->_calculateFormulaValue( + '=' . $this->dataSource, + null, + $worksheet->getCell('A1') + ) + ); + if ($flatten) { + $this->dataValues = Functions::flattenArray($newDataValues); + foreach ($this->dataValues as &$dataValue) { + if (is_string($dataValue) && !empty($dataValue) && $dataValue[0] == '#') { + $dataValue = 0.0; + } + } + unset($dataValue); + } else { + [$worksheet, $cellRange] = Worksheet::extractSheetTitle($this->dataSource, true); + $dimensions = Coordinate::rangeDimension(str_replace('$', '', $cellRange)); + if (($dimensions[0] == 1) || ($dimensions[1] == 1)) { + $this->dataValues = Functions::flattenArray($newDataValues); + } else { + $newArray = array_values(array_shift($newDataValues)); + foreach ($newArray as $i => $newDataSet) { + $newArray[$i] = [$newDataSet]; + } + + foreach ($newDataValues as $newDataSet) { + $i = 0; + foreach ($newDataSet as $newDataVal) { + array_unshift($newArray[$i++], $newDataVal); + } + } + $this->dataValues = $newArray; + } + } + $this->pointCount = count($this->dataValues); + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Exception.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Exception.php new file mode 100644 index 0000000..3f95b59 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Exception.php @@ -0,0 +1,9 @@ + [ + 'type' => self::EXCEL_COLOR_TYPE_STANDARD, + 'value' => null, + 'alpha' => 0, + ], + 'style' => [ + 'width' => '9525', + 'compound' => self::LINE_STYLE_COMPOUND_SIMPLE, + 'dash' => self::LINE_STYLE_DASH_SOLID, + 'cap' => self::LINE_STYLE_CAP_FLAT, + 'join' => self::LINE_STYLE_JOIN_BEVEL, + 'arrow' => [ + 'head' => [ + 'type' => self::LINE_STYLE_ARROW_TYPE_NOARROW, + 'size' => self::LINE_STYLE_ARROW_SIZE_5, + ], + 'end' => [ + 'type' => self::LINE_STYLE_ARROW_TYPE_NOARROW, + 'size' => self::LINE_STYLE_ARROW_SIZE_8, + ], + ], + ], + ]; + + private $shadowProperties = [ + 'presets' => self::SHADOW_PRESETS_NOSHADOW, + 'effect' => null, + 'color' => [ + 'type' => self::EXCEL_COLOR_TYPE_STANDARD, + 'value' => 'black', + 'alpha' => 85, + ], + 'size' => [ + 'sx' => null, + 'sy' => null, + 'kx' => null, + ], + 'blur' => null, + 'direction' => null, + 'distance' => null, + 'algn' => null, + 'rotWithShape' => null, + ]; + + private $glowProperties = [ + 'size' => null, + 'color' => [ + 'type' => self::EXCEL_COLOR_TYPE_STANDARD, + 'value' => 'black', + 'alpha' => 40, + ], + ]; + + private $softEdges = [ + 'size' => null, + ]; + + /** + * Get Object State. + * + * @return bool + */ + public function getObjectState() + { + return $this->objectState; + } + + /** + * Change Object State to True. + * + * @return $this + */ + private function activateObject() + { + $this->objectState = true; + + return $this; + } + + /** + * Set Line Color Properties. + * + * @param string $value + * @param int $alpha + * @param string $colorType + */ + public function setLineColorProperties($value, $alpha = 0, $colorType = self::EXCEL_COLOR_TYPE_STANDARD): void + { + $this->activateObject() + ->lineProperties['color'] = $this->setColorProperties( + $value, + $alpha, + $colorType + ); + } + + /** + * Set Line Color Properties. + * + * @param float $lineWidth + * @param string $compoundType + * @param string $dashType + * @param string $capType + * @param string $joinType + * @param string $headArrowType + * @param string $headArrowSize + * @param string $endArrowType + * @param string $endArrowSize + */ + public function setLineStyleProperties($lineWidth = null, $compoundType = null, $dashType = null, $capType = null, $joinType = null, $headArrowType = null, $headArrowSize = null, $endArrowType = null, $endArrowSize = null): void + { + $this->activateObject(); + ($lineWidth !== null) + ? $this->lineProperties['style']['width'] = $this->getExcelPointsWidth((float) $lineWidth) + : null; + ($compoundType !== null) + ? $this->lineProperties['style']['compound'] = (string) $compoundType + : null; + ($dashType !== null) + ? $this->lineProperties['style']['dash'] = (string) $dashType + : null; + ($capType !== null) + ? $this->lineProperties['style']['cap'] = (string) $capType + : null; + ($joinType !== null) + ? $this->lineProperties['style']['join'] = (string) $joinType + : null; + ($headArrowType !== null) + ? $this->lineProperties['style']['arrow']['head']['type'] = (string) $headArrowType + : null; + ($headArrowSize !== null) + ? $this->lineProperties['style']['arrow']['head']['size'] = (string) $headArrowSize + : null; + ($endArrowType !== null) + ? $this->lineProperties['style']['arrow']['end']['type'] = (string) $endArrowType + : null; + ($endArrowSize !== null) + ? $this->lineProperties['style']['arrow']['end']['size'] = (string) $endArrowSize + : null; + } + + /** + * Get Line Color Property. + * + * @param string $propertyName + * + * @return string + */ + public function getLineColorProperty($propertyName) + { + return $this->lineProperties['color'][$propertyName]; + } + + /** + * Get Line Style Property. + * + * @param array|string $elements + * + * @return string + */ + public function getLineStyleProperty($elements) + { + return $this->getArrayElementsValue($this->lineProperties['style'], $elements); + } + + /** + * Set Glow Properties. + * + * @param float $size + * @param string $colorValue + * @param int $colorAlpha + * @param string $colorType + */ + public function setGlowProperties($size, $colorValue = null, $colorAlpha = null, $colorType = null): void + { + $this + ->activateObject() + ->setGlowSize($size) + ->setGlowColor($colorValue, $colorAlpha, $colorType); + } + + /** + * Get Glow Color Property. + * + * @param string $propertyName + * + * @return string + */ + public function getGlowColor($propertyName) + { + return $this->glowProperties['color'][$propertyName]; + } + + /** + * Get Glow Size. + * + * @return string + */ + public function getGlowSize() + { + return $this->glowProperties['size']; + } + + /** + * Set Glow Size. + * + * @param float $size + * + * @return $this + */ + private function setGlowSize($size) + { + $this->glowProperties['size'] = $this->getExcelPointsWidth((float) $size); + + return $this; + } + + /** + * Set Glow Color. + * + * @param string $color + * @param int $alpha + * @param string $colorType + * + * @return $this + */ + private function setGlowColor($color, $alpha, $colorType) + { + if ($color !== null) { + $this->glowProperties['color']['value'] = (string) $color; + } + if ($alpha !== null) { + $this->glowProperties['color']['alpha'] = $this->getTrueAlpha((int) $alpha); + } + if ($colorType !== null) { + $this->glowProperties['color']['type'] = (string) $colorType; + } + + return $this; + } + + /** + * Get Line Style Arrow Parameters. + * + * @param string $arrowSelector + * @param string $propertySelector + * + * @return string + */ + public function getLineStyleArrowParameters($arrowSelector, $propertySelector) + { + return $this->getLineStyleArrowSize($this->lineProperties['style']['arrow'][$arrowSelector]['size'], $propertySelector); + } + + /** + * Set Shadow Properties. + * + * @param int $presets + * @param string $colorValue + * @param string $colorType + * @param string $colorAlpha + * @param string $blur + * @param int $angle + * @param float $distance + */ + public function setShadowProperties($presets, $colorValue = null, $colorType = null, $colorAlpha = null, $blur = null, $angle = null, $distance = null): void + { + $this->activateObject() + ->setShadowPresetsProperties((int) $presets) + ->setShadowColor( + $colorValue ?? $this->shadowProperties['color']['value'], + $colorAlpha === null ? (int) $this->shadowProperties['color']['alpha'] : $this->getTrueAlpha($colorAlpha), + $colorType ?? $this->shadowProperties['color']['type'] + ) + ->setShadowBlur((float) $blur) + ->setShadowAngle($angle) + ->setShadowDistance($distance); + } + + /** + * Set Shadow Presets Properties. + * + * @param int $presets + * + * @return $this + */ + private function setShadowPresetsProperties($presets) + { + $this->shadowProperties['presets'] = $presets; + $this->setShadowPropertiesMapValues($this->getShadowPresetsMap($presets)); + + return $this; + } + + /** + * Set Shadow Properties Values. + * + * @param mixed $reference + * + * @return $this + */ + private function setShadowPropertiesMapValues(array $propertiesMap, &$reference = null) + { + $base_reference = $reference; + foreach ($propertiesMap as $property_key => $property_val) { + if (is_array($property_val)) { + if ($reference === null) { + $reference = &$this->shadowProperties[$property_key]; + } else { + $reference = &$reference[$property_key]; + } + $this->setShadowPropertiesMapValues($property_val, $reference); + } else { + if ($base_reference === null) { + $this->shadowProperties[$property_key] = $property_val; + } else { + $reference[$property_key] = $property_val; + } + } + } + + return $this; + } + + /** + * Set Shadow Color. + * + * @param string $color + * @param int $alpha + * @param string $colorType + * + * @return $this + */ + private function setShadowColor($color, $alpha, $colorType) + { + if ($color !== null) { + $this->shadowProperties['color']['value'] = (string) $color; + } + if ($alpha !== null) { + $this->shadowProperties['color']['alpha'] = $this->getTrueAlpha((int) $alpha); + } + if ($colorType !== null) { + $this->shadowProperties['color']['type'] = (string) $colorType; + } + + return $this; + } + + /** + * Set Shadow Blur. + * + * @param float $blur + * + * @return $this + */ + private function setShadowBlur($blur) + { + if ($blur !== null) { + $this->shadowProperties['blur'] = (string) $this->getExcelPointsWidth($blur); + } + + return $this; + } + + /** + * Set Shadow Angle. + * + * @param int $angle + * + * @return $this + */ + private function setShadowAngle($angle) + { + if ($angle !== null) { + $this->shadowProperties['direction'] = (string) $this->getExcelPointsAngle($angle); + } + + return $this; + } + + /** + * Set Shadow Distance. + * + * @param float $distance + * + * @return $this + */ + private function setShadowDistance($distance) + { + if ($distance !== null) { + $this->shadowProperties['distance'] = (string) $this->getExcelPointsWidth($distance); + } + + return $this; + } + + /** + * Get Shadow Property. + * + * @param string|string[] $elements + * + * @return string + */ + public function getShadowProperty($elements) + { + return $this->getArrayElementsValue($this->shadowProperties, $elements); + } + + /** + * Set Soft Edges Size. + * + * @param float $size + */ + public function setSoftEdgesSize($size): void + { + if ($size !== null) { + $this->activateObject(); + $this->softEdges['size'] = (string) $this->getExcelPointsWidth($size); + } + } + + /** + * Get Soft Edges Size. + * + * @return string + */ + public function getSoftEdgesSize() + { + return $this->softEdges['size']; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Layout.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Layout.php new file mode 100644 index 0000000..cea9655 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Layout.php @@ -0,0 +1,481 @@ +layoutTarget = $layout['layoutTarget']; + } + if (isset($layout['xMode'])) { + $this->xMode = $layout['xMode']; + } + if (isset($layout['yMode'])) { + $this->yMode = $layout['yMode']; + } + if (isset($layout['x'])) { + $this->xPos = (float) $layout['x']; + } + if (isset($layout['y'])) { + $this->yPos = (float) $layout['y']; + } + if (isset($layout['w'])) { + $this->width = (float) $layout['w']; + } + if (isset($layout['h'])) { + $this->height = (float) $layout['h']; + } + } + + /** + * Get Layout Target. + * + * @return string + */ + public function getLayoutTarget() + { + return $this->layoutTarget; + } + + /** + * Set Layout Target. + * + * @param string $target + * + * @return $this + */ + public function setLayoutTarget($target) + { + $this->layoutTarget = $target; + + return $this; + } + + /** + * Get X-Mode. + * + * @return string + */ + public function getXMode() + { + return $this->xMode; + } + + /** + * Set X-Mode. + * + * @param string $mode + * + * @return $this + */ + public function setXMode($mode) + { + $this->xMode = (string) $mode; + + return $this; + } + + /** + * Get Y-Mode. + * + * @return string + */ + public function getYMode() + { + return $this->yMode; + } + + /** + * Set Y-Mode. + * + * @param string $mode + * + * @return $this + */ + public function setYMode($mode) + { + $this->yMode = (string) $mode; + + return $this; + } + + /** + * Get X-Position. + * + * @return number + */ + public function getXPosition() + { + return $this->xPos; + } + + /** + * Set X-Position. + * + * @param float $position + * + * @return $this + */ + public function setXPosition($position) + { + $this->xPos = (float) $position; + + return $this; + } + + /** + * Get Y-Position. + * + * @return number + */ + public function getYPosition() + { + return $this->yPos; + } + + /** + * Set Y-Position. + * + * @param float $position + * + * @return $this + */ + public function setYPosition($position) + { + $this->yPos = (float) $position; + + return $this; + } + + /** + * Get Width. + * + * @return number + */ + public function getWidth() + { + return $this->width; + } + + /** + * Set Width. + * + * @param float $width + * + * @return $this + */ + public function setWidth($width) + { + $this->width = $width; + + return $this; + } + + /** + * Get Height. + * + * @return number + */ + public function getHeight() + { + return $this->height; + } + + /** + * Set Height. + * + * @param float $height + * + * @return $this + */ + public function setHeight($height) + { + $this->height = $height; + + return $this; + } + + /** + * Get show legend key. + * + * @return bool + */ + public function getShowLegendKey() + { + return $this->showLegendKey; + } + + /** + * Set show legend key + * Specifies that legend keys should be shown in data labels. + * + * @param bool $showLegendKey Show legend key + * + * @return $this + */ + public function setShowLegendKey($showLegendKey) + { + $this->showLegendKey = $showLegendKey; + + return $this; + } + + /** + * Get show value. + * + * @return bool + */ + public function getShowVal() + { + return $this->showVal; + } + + /** + * Set show val + * Specifies that the value should be shown in data labels. + * + * @param bool $showDataLabelValues Show val + * + * @return $this + */ + public function setShowVal($showDataLabelValues) + { + $this->showVal = $showDataLabelValues; + + return $this; + } + + /** + * Get show category name. + * + * @return bool + */ + public function getShowCatName() + { + return $this->showCatName; + } + + /** + * Set show cat name + * Specifies that the category name should be shown in data labels. + * + * @param bool $showCategoryName Show cat name + * + * @return $this + */ + public function setShowCatName($showCategoryName) + { + $this->showCatName = $showCategoryName; + + return $this; + } + + /** + * Get show data series name. + * + * @return bool + */ + public function getShowSerName() + { + return $this->showSerName; + } + + /** + * Set show ser name + * Specifies that the series name should be shown in data labels. + * + * @param bool $showSeriesName Show series name + * + * @return $this + */ + public function setShowSerName($showSeriesName) + { + $this->showSerName = $showSeriesName; + + return $this; + } + + /** + * Get show percentage. + * + * @return bool + */ + public function getShowPercent() + { + return $this->showPercent; + } + + /** + * Set show percentage + * Specifies that the percentage should be shown in data labels. + * + * @param bool $showPercentage Show percentage + * + * @return $this + */ + public function setShowPercent($showPercentage) + { + $this->showPercent = $showPercentage; + + return $this; + } + + /** + * Get show bubble size. + * + * @return bool + */ + public function getShowBubbleSize() + { + return $this->showBubbleSize; + } + + /** + * Set show bubble size + * Specifies that the bubble size should be shown in data labels. + * + * @param bool $showBubbleSize Show bubble size + * + * @return $this + */ + public function setShowBubbleSize($showBubbleSize) + { + $this->showBubbleSize = $showBubbleSize; + + return $this; + } + + /** + * Get show leader lines. + * + * @return bool + */ + public function getShowLeaderLines() + { + return $this->showLeaderLines; + } + + /** + * Set show leader lines + * Specifies that leader lines should be shown in data labels. + * + * @param bool $showLeaderLines Show leader lines + * + * @return $this + */ + public function setShowLeaderLines($showLeaderLines) + { + $this->showLeaderLines = $showLeaderLines; + + return $this; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Legend.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Legend.php new file mode 100644 index 0000000..2f003cd --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Legend.php @@ -0,0 +1,149 @@ + self::POSITION_BOTTOM, + self::XL_LEGEND_POSITION_CORNER => self::POSITION_TOPRIGHT, + self::XL_LEGEND_POSITION_CUSTOM => '??', + self::XL_LEGEND_POSITION_LEFT => self::POSITION_LEFT, + self::XL_LEGEND_POSITION_RIGHT => self::POSITION_RIGHT, + self::XL_LEGEND_POSITION_TOP => self::POSITION_TOP, + ]; + + /** + * Legend position. + * + * @var string + */ + private $position = self::POSITION_RIGHT; + + /** + * Allow overlay of other elements? + * + * @var bool + */ + private $overlay = true; + + /** + * Legend Layout. + * + * @var Layout + */ + private $layout; + + /** + * Create a new Legend. + * + * @param string $position + * @param bool $overlay + */ + public function __construct($position = self::POSITION_RIGHT, ?Layout $layout = null, $overlay = false) + { + $this->setPosition($position); + $this->layout = $layout; + $this->setOverlay($overlay); + } + + /** + * Get legend position as an excel string value. + * + * @return string + */ + public function getPosition() + { + return $this->position; + } + + /** + * Get legend position using an excel string value. + * + * @param string $position see self::POSITION_* + * + * @return bool + */ + public function setPosition($position) + { + if (!in_array($position, self::$positionXLref)) { + return false; + } + + $this->position = $position; + + return true; + } + + /** + * Get legend position as an Excel internal numeric value. + * + * @return int + */ + public function getPositionXL() + { + return array_search($this->position, self::$positionXLref); + } + + /** + * Set legend position using an Excel internal numeric value. + * + * @param int $positionXL see self::XL_LEGEND_POSITION_* + * + * @return bool + */ + public function setPositionXL($positionXL) + { + if (!isset(self::$positionXLref[$positionXL])) { + return false; + } + + $this->position = self::$positionXLref[$positionXL]; + + return true; + } + + /** + * Get allow overlay of other elements? + * + * @return bool + */ + public function getOverlay() + { + return $this->overlay; + } + + /** + * Set allow overlay of other elements? + * + * @param bool $overlay + */ + public function setOverlay($overlay): void + { + $this->overlay = $overlay; + } + + /** + * Get Layout. + * + * @return Layout + */ + public function getLayout() + { + return $this->layout; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/PlotArea.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/PlotArea.php new file mode 100644 index 0000000..ecb7b6c --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/PlotArea.php @@ -0,0 +1,109 @@ +layout = $layout; + $this->plotSeries = $plotSeries; + } + + /** + * Get Layout. + * + * @return Layout + */ + public function getLayout() + { + return $this->layout; + } + + /** + * Get Number of Plot Groups. + */ + public function getPlotGroupCount(): int + { + return count($this->plotSeries); + } + + /** + * Get Number of Plot Series. + * + * @return int + */ + public function getPlotSeriesCount() + { + $seriesCount = 0; + foreach ($this->plotSeries as $plot) { + $seriesCount += $plot->getPlotSeriesCount(); + } + + return $seriesCount; + } + + /** + * Get Plot Series. + * + * @return DataSeries[] + */ + public function getPlotGroup() + { + return $this->plotSeries; + } + + /** + * Get Plot Series by Index. + * + * @param mixed $index + * + * @return DataSeries + */ + public function getPlotGroupByIndex($index) + { + return $this->plotSeries[$index]; + } + + /** + * Set Plot Series. + * + * @param DataSeries[] $plotSeries + * + * @return $this + */ + public function setPlotSeries(array $plotSeries) + { + $this->plotSeries = $plotSeries; + + return $this; + } + + public function refresh(Worksheet $worksheet): void + { + foreach ($this->plotSeries as $plotSeries) { + $plotSeries->refresh($worksheet); + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Properties.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Properties.php new file mode 100644 index 0000000..ef22fb5 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Properties.php @@ -0,0 +1,369 @@ + (string) $colorType, + 'value' => (string) $color, + 'alpha' => (string) $this->getTrueAlpha($alpha), + ]; + } + + protected function getLineStyleArrowSize($arraySelector, $arrayKaySelector) + { + $sizes = [ + 1 => ['w' => 'sm', 'len' => 'sm'], + 2 => ['w' => 'sm', 'len' => 'med'], + 3 => ['w' => 'sm', 'len' => 'lg'], + 4 => ['w' => 'med', 'len' => 'sm'], + 5 => ['w' => 'med', 'len' => 'med'], + 6 => ['w' => 'med', 'len' => 'lg'], + 7 => ['w' => 'lg', 'len' => 'sm'], + 8 => ['w' => 'lg', 'len' => 'med'], + 9 => ['w' => 'lg', 'len' => 'lg'], + ]; + + return $sizes[$arraySelector][$arrayKaySelector]; + } + + protected function getShadowPresetsMap($presetsOption) + { + $presets_options = [ + //OUTER + 1 => [ + 'effect' => 'outerShdw', + 'blur' => '50800', + 'distance' => '38100', + 'direction' => '2700000', + 'algn' => 'tl', + 'rotWithShape' => '0', + ], + 2 => [ + 'effect' => 'outerShdw', + 'blur' => '50800', + 'distance' => '38100', + 'direction' => '5400000', + 'algn' => 't', + 'rotWithShape' => '0', + ], + 3 => [ + 'effect' => 'outerShdw', + 'blur' => '50800', + 'distance' => '38100', + 'direction' => '8100000', + 'algn' => 'tr', + 'rotWithShape' => '0', + ], + 4 => [ + 'effect' => 'outerShdw', + 'blur' => '50800', + 'distance' => '38100', + 'algn' => 'l', + 'rotWithShape' => '0', + ], + 5 => [ + 'effect' => 'outerShdw', + 'size' => [ + 'sx' => '102000', + 'sy' => '102000', + ], + 'blur' => '63500', + 'distance' => '38100', + 'algn' => 'ctr', + 'rotWithShape' => '0', + ], + 6 => [ + 'effect' => 'outerShdw', + 'blur' => '50800', + 'distance' => '38100', + 'direction' => '10800000', + 'algn' => 'r', + 'rotWithShape' => '0', + ], + 7 => [ + 'effect' => 'outerShdw', + 'blur' => '50800', + 'distance' => '38100', + 'direction' => '18900000', + 'algn' => 'bl', + 'rotWithShape' => '0', + ], + 8 => [ + 'effect' => 'outerShdw', + 'blur' => '50800', + 'distance' => '38100', + 'direction' => '16200000', + 'rotWithShape' => '0', + ], + 9 => [ + 'effect' => 'outerShdw', + 'blur' => '50800', + 'distance' => '38100', + 'direction' => '13500000', + 'algn' => 'br', + 'rotWithShape' => '0', + ], + //INNER + 10 => [ + 'effect' => 'innerShdw', + 'blur' => '63500', + 'distance' => '50800', + 'direction' => '2700000', + ], + 11 => [ + 'effect' => 'innerShdw', + 'blur' => '63500', + 'distance' => '50800', + 'direction' => '5400000', + ], + 12 => [ + 'effect' => 'innerShdw', + 'blur' => '63500', + 'distance' => '50800', + 'direction' => '8100000', + ], + 13 => [ + 'effect' => 'innerShdw', + 'blur' => '63500', + 'distance' => '50800', + ], + 14 => [ + 'effect' => 'innerShdw', + 'blur' => '114300', + ], + 15 => [ + 'effect' => 'innerShdw', + 'blur' => '63500', + 'distance' => '50800', + 'direction' => '10800000', + ], + 16 => [ + 'effect' => 'innerShdw', + 'blur' => '63500', + 'distance' => '50800', + 'direction' => '18900000', + ], + 17 => [ + 'effect' => 'innerShdw', + 'blur' => '63500', + 'distance' => '50800', + 'direction' => '16200000', + ], + 18 => [ + 'effect' => 'innerShdw', + 'blur' => '63500', + 'distance' => '50800', + 'direction' => '13500000', + ], + //perspective + 19 => [ + 'effect' => 'outerShdw', + 'blur' => '152400', + 'distance' => '317500', + 'size' => [ + 'sx' => '90000', + 'sy' => '-19000', + ], + 'direction' => '5400000', + 'rotWithShape' => '0', + ], + 20 => [ + 'effect' => 'outerShdw', + 'blur' => '76200', + 'direction' => '18900000', + 'size' => [ + 'sy' => '23000', + 'kx' => '-1200000', + ], + 'algn' => 'bl', + 'rotWithShape' => '0', + ], + 21 => [ + 'effect' => 'outerShdw', + 'blur' => '76200', + 'direction' => '13500000', + 'size' => [ + 'sy' => '23000', + 'kx' => '1200000', + ], + 'algn' => 'br', + 'rotWithShape' => '0', + ], + 22 => [ + 'effect' => 'outerShdw', + 'blur' => '76200', + 'distance' => '12700', + 'direction' => '2700000', + 'size' => [ + 'sy' => '-23000', + 'kx' => '-800400', + ], + 'algn' => 'bl', + 'rotWithShape' => '0', + ], + 23 => [ + 'effect' => 'outerShdw', + 'blur' => '76200', + 'distance' => '12700', + 'direction' => '8100000', + 'size' => [ + 'sy' => '-23000', + 'kx' => '800400', + ], + 'algn' => 'br', + 'rotWithShape' => '0', + ], + ]; + + return $presets_options[$presetsOption]; + } + + protected function getArrayElementsValue($properties, $elements) + { + $reference = &$properties; + if (!is_array($elements)) { + return $reference[$elements]; + } + + foreach ($elements as $keys) { + $reference = &$reference[$keys]; + } + + return $reference; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/IRenderer.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/IRenderer.php new file mode 100644 index 0000000..3032f6b --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/IRenderer.php @@ -0,0 +1,22 @@ +graph = null; + $this->chart = $chart; + } + + private static function init(): void + { + static $loaded = false; + if ($loaded) { + return; + } + + \JpGraph\JpGraph::load(); + \JpGraph\JpGraph::module('bar'); + \JpGraph\JpGraph::module('contour'); + \JpGraph\JpGraph::module('line'); + \JpGraph\JpGraph::module('pie'); + \JpGraph\JpGraph::module('pie3d'); + \JpGraph\JpGraph::module('radar'); + \JpGraph\JpGraph::module('regstat'); + \JpGraph\JpGraph::module('scatter'); + \JpGraph\JpGraph::module('stock'); + + self::$markSet = [ + 'diamond' => MARK_DIAMOND, + 'square' => MARK_SQUARE, + 'triangle' => MARK_UTRIANGLE, + 'x' => MARK_X, + 'star' => MARK_STAR, + 'dot' => MARK_FILLEDCIRCLE, + 'dash' => MARK_DTRIANGLE, + 'circle' => MARK_CIRCLE, + 'plus' => MARK_CROSS, + ]; + + $loaded = true; + } + + private function formatPointMarker($seriesPlot, $markerID) + { + $plotMarkKeys = array_keys(self::$markSet); + if ($markerID === null) { + // Use default plot marker (next marker in the series) + self::$plotMark %= count(self::$markSet); + $seriesPlot->mark->SetType(self::$markSet[$plotMarkKeys[self::$plotMark++]]); + } elseif ($markerID !== 'none') { + // Use specified plot marker (if it exists) + if (isset(self::$markSet[$markerID])) { + $seriesPlot->mark->SetType(self::$markSet[$markerID]); + } else { + // If the specified plot marker doesn't exist, use default plot marker (next marker in the series) + self::$plotMark %= count(self::$markSet); + $seriesPlot->mark->SetType(self::$markSet[$plotMarkKeys[self::$plotMark++]]); + } + } else { + // Hide plot marker + $seriesPlot->mark->Hide(); + } + $seriesPlot->mark->SetColor(self::$colourSet[self::$plotColour]); + $seriesPlot->mark->SetFillColor(self::$colourSet[self::$plotColour]); + $seriesPlot->SetColor(self::$colourSet[self::$plotColour++]); + + return $seriesPlot; + } + + private function formatDataSetLabels($groupID, $datasetLabels, $labelCount, $rotation = '') + { + $datasetLabelFormatCode = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getFormatCode(); + if ($datasetLabelFormatCode !== null) { + // Retrieve any label formatting code + $datasetLabelFormatCode = stripslashes($datasetLabelFormatCode); + } + + $testCurrentIndex = 0; + foreach ($datasetLabels as $i => $datasetLabel) { + if (is_array($datasetLabel)) { + if ($rotation == 'bar') { + $datasetLabels[$i] = implode(' ', $datasetLabel); + } else { + $datasetLabel = array_reverse($datasetLabel); + $datasetLabels[$i] = implode("\n", $datasetLabel); + } + } else { + // Format labels according to any formatting code + if ($datasetLabelFormatCode !== null) { + $datasetLabels[$i] = NumberFormat::toFormattedString($datasetLabel, $datasetLabelFormatCode); + } + } + ++$testCurrentIndex; + } + + return $datasetLabels; + } + + private function percentageSumCalculation($groupID, $seriesCount) + { + $sumValues = []; + // Adjust our values to a percentage value across all series in the group + for ($i = 0; $i < $seriesCount; ++$i) { + if ($i == 0) { + $sumValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues(); + } else { + $nextValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues(); + foreach ($nextValues as $k => $value) { + if (isset($sumValues[$k])) { + $sumValues[$k] += $value; + } else { + $sumValues[$k] = $value; + } + } + } + } + + return $sumValues; + } + + private function percentageAdjustValues($dataValues, $sumValues) + { + foreach ($dataValues as $k => $dataValue) { + $dataValues[$k] = $dataValue / $sumValues[$k] * 100; + } + + return $dataValues; + } + + private function getCaption($captionElement) + { + // Read any caption + $caption = ($captionElement !== null) ? $captionElement->getCaption() : null; + // Test if we have a title caption to display + if ($caption !== null) { + // If we do, it could be a plain string or an array + if (is_array($caption)) { + // Implode an array to a plain string + $caption = implode('', $caption); + } + } + + return $caption; + } + + private function renderTitle(): void + { + $title = $this->getCaption($this->chart->getTitle()); + if ($title !== null) { + $this->graph->title->Set($title); + } + } + + private function renderLegend(): void + { + $legend = $this->chart->getLegend(); + if ($legend !== null) { + $legendPosition = $legend->getPosition(); + switch ($legendPosition) { + case 'r': + $this->graph->legend->SetPos(0.01, 0.5, 'right', 'center'); // right + $this->graph->legend->SetColumns(1); + + break; + case 'l': + $this->graph->legend->SetPos(0.01, 0.5, 'left', 'center'); // left + $this->graph->legend->SetColumns(1); + + break; + case 't': + $this->graph->legend->SetPos(0.5, 0.01, 'center', 'top'); // top + + break; + case 'b': + $this->graph->legend->SetPos(0.5, 0.99, 'center', 'bottom'); // bottom + + break; + default: + $this->graph->legend->SetPos(0.01, 0.01, 'right', 'top'); // top-right + $this->graph->legend->SetColumns(1); + + break; + } + } else { + $this->graph->legend->Hide(); + } + } + + private function renderCartesianPlotArea($type = 'textlin'): void + { + $this->graph = new Graph(self::$width, self::$height); + $this->graph->SetScale($type); + + $this->renderTitle(); + + // Rotate for bar rather than column chart + $rotation = $this->chart->getPlotArea()->getPlotGroupByIndex(0)->getPlotDirection(); + $reverse = $rotation == 'bar'; + + $xAxisLabel = $this->chart->getXAxisLabel(); + if ($xAxisLabel !== null) { + $title = $this->getCaption($xAxisLabel); + if ($title !== null) { + $this->graph->xaxis->SetTitle($title, 'center'); + $this->graph->xaxis->title->SetMargin(35); + if ($reverse) { + $this->graph->xaxis->title->SetAngle(90); + $this->graph->xaxis->title->SetMargin(90); + } + } + } + + $yAxisLabel = $this->chart->getYAxisLabel(); + if ($yAxisLabel !== null) { + $title = $this->getCaption($yAxisLabel); + if ($title !== null) { + $this->graph->yaxis->SetTitle($title, 'center'); + if ($reverse) { + $this->graph->yaxis->title->SetAngle(0); + $this->graph->yaxis->title->SetMargin(-55); + } + } + } + } + + private function renderPiePlotArea(): void + { + $this->graph = new PieGraph(self::$width, self::$height); + + $this->renderTitle(); + } + + private function renderRadarPlotArea(): void + { + $this->graph = new RadarGraph(self::$width, self::$height); + $this->graph->SetScale('lin'); + + $this->renderTitle(); + } + + private function renderPlotLine($groupID, $filled = false, $combination = false, $dimensions = '2d'): void + { + $grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping(); + + $labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex(0)->getPointCount(); + if ($labelCount > 0) { + $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues(); + $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels, $labelCount); + $this->graph->xaxis->SetTickLabels($datasetLabels); + } + + $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); + $seriesPlots = []; + if ($grouping == 'percentStacked') { + $sumValues = $this->percentageSumCalculation($groupID, $seriesCount); + } else { + $sumValues = []; + } + + // Loop through each data series in turn + for ($i = 0; $i < $seriesCount; ++$i) { + $dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues(); + $marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getPointMarker(); + + if ($grouping == 'percentStacked') { + $dataValues = $this->percentageAdjustValues($dataValues, $sumValues); + } + + // Fill in any missing values in the $dataValues array + $testCurrentIndex = 0; + foreach ($dataValues as $k => $dataValue) { + while ($k != $testCurrentIndex) { + $dataValues[$testCurrentIndex] = null; + ++$testCurrentIndex; + } + ++$testCurrentIndex; + } + + $seriesPlot = new LinePlot($dataValues); + if ($combination) { + $seriesPlot->SetBarCenter(); + } + + if ($filled) { + $seriesPlot->SetFilled(true); + $seriesPlot->SetColor('black'); + $seriesPlot->SetFillColor(self::$colourSet[self::$plotColour++]); + } else { + // Set the appropriate plot marker + $this->formatPointMarker($seriesPlot, $marker); + } + $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($i)->getDataValue(); + $seriesPlot->SetLegend($dataLabel); + + $seriesPlots[] = $seriesPlot; + } + + if ($grouping == 'standard') { + $groupPlot = $seriesPlots; + } else { + $groupPlot = new AccLinePlot($seriesPlots); + } + $this->graph->Add($groupPlot); + } + + private function renderPlotBar($groupID, $dimensions = '2d'): void + { + $rotation = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotDirection(); + // Rotate for bar rather than column chart + if (($groupID == 0) && ($rotation == 'bar')) { + $this->graph->Set90AndMargin(); + } + $grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping(); + + $labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex(0)->getPointCount(); + if ($labelCount > 0) { + $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues(); + $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels, $labelCount, $rotation); + // Rotate for bar rather than column chart + if ($rotation == 'bar') { + $datasetLabels = array_reverse($datasetLabels); + $this->graph->yaxis->SetPos('max'); + $this->graph->yaxis->SetLabelAlign('center', 'top'); + $this->graph->yaxis->SetLabelSide(SIDE_RIGHT); + } + $this->graph->xaxis->SetTickLabels($datasetLabels); + } + + $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); + $seriesPlots = []; + if ($grouping == 'percentStacked') { + $sumValues = $this->percentageSumCalculation($groupID, $seriesCount); + } else { + $sumValues = []; + } + + // Loop through each data series in turn + for ($j = 0; $j < $seriesCount; ++$j) { + $dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($j)->getDataValues(); + if ($grouping == 'percentStacked') { + $dataValues = $this->percentageAdjustValues($dataValues, $sumValues); + } + + // Fill in any missing values in the $dataValues array + $testCurrentIndex = 0; + foreach ($dataValues as $k => $dataValue) { + while ($k != $testCurrentIndex) { + $dataValues[$testCurrentIndex] = null; + ++$testCurrentIndex; + } + ++$testCurrentIndex; + } + + // Reverse the $dataValues order for bar rather than column chart + if ($rotation == 'bar') { + $dataValues = array_reverse($dataValues); + } + $seriesPlot = new BarPlot($dataValues); + $seriesPlot->SetColor('black'); + $seriesPlot->SetFillColor(self::$colourSet[self::$plotColour++]); + if ($dimensions == '3d') { + $seriesPlot->SetShadow(); + } + if (!$this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($j)) { + $dataLabel = ''; + } else { + $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($j)->getDataValue(); + } + $seriesPlot->SetLegend($dataLabel); + + $seriesPlots[] = $seriesPlot; + } + // Reverse the plot order for bar rather than column chart + if (($rotation == 'bar') && ($grouping != 'percentStacked')) { + $seriesPlots = array_reverse($seriesPlots); + } + + if ($grouping == 'clustered') { + $groupPlot = new GroupBarPlot($seriesPlots); + } elseif ($grouping == 'standard') { + $groupPlot = new GroupBarPlot($seriesPlots); + } else { + $groupPlot = new AccBarPlot($seriesPlots); + if ($dimensions == '3d') { + $groupPlot->SetShadow(); + } + } + + $this->graph->Add($groupPlot); + } + + private function renderPlotScatter($groupID, $bubble): void + { + $grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping(); + $scatterStyle = $bubbleSize = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle(); + + $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); + $seriesPlots = []; + + // Loop through each data series in turn + for ($i = 0; $i < $seriesCount; ++$i) { + $dataValuesY = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex($i)->getDataValues(); + $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues(); + + foreach ($dataValuesY as $k => $dataValueY) { + $dataValuesY[$k] = $k; + } + + $seriesPlot = new ScatterPlot($dataValuesX, $dataValuesY); + if ($scatterStyle == 'lineMarker') { + $seriesPlot->SetLinkPoints(); + $seriesPlot->link->SetColor(self::$colourSet[self::$plotColour]); + } elseif ($scatterStyle == 'smoothMarker') { + $spline = new Spline($dataValuesY, $dataValuesX); + [$splineDataY, $splineDataX] = $spline->Get(count($dataValuesX) * self::$width / 20); + $lplot = new LinePlot($splineDataX, $splineDataY); + $lplot->SetColor(self::$colourSet[self::$plotColour]); + + $this->graph->Add($lplot); + } + + if ($bubble) { + $this->formatPointMarker($seriesPlot, 'dot'); + $seriesPlot->mark->SetColor('black'); + $seriesPlot->mark->SetSize($bubbleSize); + } else { + $marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getPointMarker(); + $this->formatPointMarker($seriesPlot, $marker); + } + $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($i)->getDataValue(); + $seriesPlot->SetLegend($dataLabel); + + $this->graph->Add($seriesPlot); + } + } + + private function renderPlotRadar($groupID): void + { + $radarStyle = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle(); + + $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); + $seriesPlots = []; + + // Loop through each data series in turn + for ($i = 0; $i < $seriesCount; ++$i) { + $dataValuesY = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex($i)->getDataValues(); + $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues(); + $marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getPointMarker(); + + $dataValues = []; + foreach ($dataValuesY as $k => $dataValueY) { + $dataValues[$k] = implode(' ', array_reverse($dataValueY)); + } + $tmp = array_shift($dataValues); + $dataValues[] = $tmp; + $tmp = array_shift($dataValuesX); + $dataValuesX[] = $tmp; + + $this->graph->SetTitles(array_reverse($dataValues)); + + $seriesPlot = new RadarPlot(array_reverse($dataValuesX)); + + $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($i)->getDataValue(); + $seriesPlot->SetColor(self::$colourSet[self::$plotColour++]); + if ($radarStyle == 'filled') { + $seriesPlot->SetFillColor(self::$colourSet[self::$plotColour]); + } + $this->formatPointMarker($seriesPlot, $marker); + $seriesPlot->SetLegend($dataLabel); + + $this->graph->Add($seriesPlot); + } + } + + private function renderPlotContour($groupID): void + { + $contourStyle = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle(); + + $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); + $seriesPlots = []; + + $dataValues = []; + // Loop through each data series in turn + for ($i = 0; $i < $seriesCount; ++$i) { + $dataValuesY = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex($i)->getDataValues(); + $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues(); + + $dataValues[$i] = $dataValuesX; + } + $seriesPlot = new ContourPlot($dataValues); + + $this->graph->Add($seriesPlot); + } + + private function renderPlotStock($groupID): void + { + $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); + $plotOrder = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotOrder(); + + $dataValues = []; + // Loop through each data series in turn and build the plot arrays + foreach ($plotOrder as $i => $v) { + $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($v)->getDataValues(); + foreach ($dataValuesX as $j => $dataValueX) { + $dataValues[$plotOrder[$i]][$j] = $dataValueX; + } + } + if (empty($dataValues)) { + return; + } + + $dataValuesPlot = []; + // Flatten the plot arrays to a single dimensional array to work with jpgraph + $jMax = count($dataValues[0]); + for ($j = 0; $j < $jMax; ++$j) { + for ($i = 0; $i < $seriesCount; ++$i) { + $dataValuesPlot[] = $dataValues[$i][$j]; + } + } + + // Set the x-axis labels + $labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex(0)->getPointCount(); + if ($labelCount > 0) { + $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues(); + $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels, $labelCount); + $this->graph->xaxis->SetTickLabels($datasetLabels); + } + + $seriesPlot = new StockPlot($dataValuesPlot); + $seriesPlot->SetWidth(20); + + $this->graph->Add($seriesPlot); + } + + private function renderAreaChart($groupCount, $dimensions = '2d'): void + { + $this->renderCartesianPlotArea(); + + for ($i = 0; $i < $groupCount; ++$i) { + $this->renderPlotLine($i, true, false, $dimensions); + } + } + + private function renderLineChart($groupCount, $dimensions = '2d'): void + { + $this->renderCartesianPlotArea(); + + for ($i = 0; $i < $groupCount; ++$i) { + $this->renderPlotLine($i, false, false, $dimensions); + } + } + + private function renderBarChart($groupCount, $dimensions = '2d'): void + { + $this->renderCartesianPlotArea(); + + for ($i = 0; $i < $groupCount; ++$i) { + $this->renderPlotBar($i, $dimensions); + } + } + + private function renderScatterChart($groupCount): void + { + $this->renderCartesianPlotArea('linlin'); + + for ($i = 0; $i < $groupCount; ++$i) { + $this->renderPlotScatter($i, false); + } + } + + private function renderBubbleChart($groupCount): void + { + $this->renderCartesianPlotArea('linlin'); + + for ($i = 0; $i < $groupCount; ++$i) { + $this->renderPlotScatter($i, true); + } + } + + private function renderPieChart($groupCount, $dimensions = '2d', $doughnut = false, $multiplePlots = false): void + { + $this->renderPiePlotArea(); + + $iLimit = ($multiplePlots) ? $groupCount : 1; + for ($groupID = 0; $groupID < $iLimit; ++$groupID) { + $grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping(); + $exploded = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle(); + $datasetLabels = []; + if ($groupID == 0) { + $labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex(0)->getPointCount(); + if ($labelCount > 0) { + $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues(); + $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels, $labelCount); + } + } + + $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); + $seriesPlots = []; + // For pie charts, we only display the first series: doughnut charts generally display all series + $jLimit = ($multiplePlots) ? $seriesCount : 1; + // Loop through each data series in turn + for ($j = 0; $j < $jLimit; ++$j) { + $dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($j)->getDataValues(); + + // Fill in any missing values in the $dataValues array + $testCurrentIndex = 0; + foreach ($dataValues as $k => $dataValue) { + while ($k != $testCurrentIndex) { + $dataValues[$testCurrentIndex] = null; + ++$testCurrentIndex; + } + ++$testCurrentIndex; + } + + if ($dimensions == '3d') { + $seriesPlot = new PiePlot3D($dataValues); + } else { + if ($doughnut) { + $seriesPlot = new PiePlotC($dataValues); + } else { + $seriesPlot = new PiePlot($dataValues); + } + } + + if ($multiplePlots) { + $seriesPlot->SetSize(($jLimit - $j) / ($jLimit * 4)); + } + + if ($doughnut) { + $seriesPlot->SetMidColor('white'); + } + + $seriesPlot->SetColor(self::$colourSet[self::$plotColour++]); + if (count($datasetLabels) > 0) { + $seriesPlot->SetLabels(array_fill(0, count($datasetLabels), '')); + } + if ($dimensions != '3d') { + $seriesPlot->SetGuideLines(false); + } + if ($j == 0) { + if ($exploded) { + $seriesPlot->ExplodeAll(); + } + $seriesPlot->SetLegends($datasetLabels); + } + + $this->graph->Add($seriesPlot); + } + } + } + + private function renderRadarChart($groupCount): void + { + $this->renderRadarPlotArea(); + + for ($groupID = 0; $groupID < $groupCount; ++$groupID) { + $this->renderPlotRadar($groupID); + } + } + + private function renderStockChart($groupCount): void + { + $this->renderCartesianPlotArea('intint'); + + for ($groupID = 0; $groupID < $groupCount; ++$groupID) { + $this->renderPlotStock($groupID); + } + } + + private function renderContourChart($groupCount, $dimensions): void + { + $this->renderCartesianPlotArea('intint'); + + for ($i = 0; $i < $groupCount; ++$i) { + $this->renderPlotContour($i); + } + } + + private function renderCombinationChart($groupCount, $dimensions, $outputDestination) + { + $this->renderCartesianPlotArea(); + + for ($i = 0; $i < $groupCount; ++$i) { + $dimensions = null; + $chartType = $this->chart->getPlotArea()->getPlotGroupByIndex($i)->getPlotType(); + switch ($chartType) { + case 'area3DChart': + $dimensions = '3d'; + // no break + case 'areaChart': + $this->renderPlotLine($i, true, true, $dimensions); + + break; + case 'bar3DChart': + $dimensions = '3d'; + // no break + case 'barChart': + $this->renderPlotBar($i, $dimensions); + + break; + case 'line3DChart': + $dimensions = '3d'; + // no break + case 'lineChart': + $this->renderPlotLine($i, false, true, $dimensions); + + break; + case 'scatterChart': + $this->renderPlotScatter($i, false); + + break; + case 'bubbleChart': + $this->renderPlotScatter($i, true); + + break; + default: + $this->graph = null; + + return false; + } + } + + $this->renderLegend(); + + $this->graph->Stroke($outputDestination); + + return true; + } + + public function render($outputDestination) + { + self::$plotColour = 0; + + $groupCount = $this->chart->getPlotArea()->getPlotGroupCount(); + + $dimensions = null; + if ($groupCount == 1) { + $chartType = $this->chart->getPlotArea()->getPlotGroupByIndex(0)->getPlotType(); + } else { + $chartTypes = []; + for ($i = 0; $i < $groupCount; ++$i) { + $chartTypes[] = $this->chart->getPlotArea()->getPlotGroupByIndex($i)->getPlotType(); + } + $chartTypes = array_unique($chartTypes); + if (count($chartTypes) == 1) { + $chartType = array_pop($chartTypes); + } elseif (count($chartTypes) == 0) { + echo 'Chart is not yet implemented
        '; + + return false; + } else { + return $this->renderCombinationChart($groupCount, $dimensions, $outputDestination); + } + } + + switch ($chartType) { + case 'area3DChart': + $dimensions = '3d'; + // no break + case 'areaChart': + $this->renderAreaChart($groupCount, $dimensions); + + break; + case 'bar3DChart': + $dimensions = '3d'; + // no break + case 'barChart': + $this->renderBarChart($groupCount, $dimensions); + + break; + case 'line3DChart': + $dimensions = '3d'; + // no break + case 'lineChart': + $this->renderLineChart($groupCount, $dimensions); + + break; + case 'pie3DChart': + $dimensions = '3d'; + // no break + case 'pieChart': + $this->renderPieChart($groupCount, $dimensions, false, false); + + break; + case 'doughnut3DChart': + $dimensions = '3d'; + // no break + case 'doughnutChart': + $this->renderPieChart($groupCount, $dimensions, true, true); + + break; + case 'scatterChart': + $this->renderScatterChart($groupCount); + + break; + case 'bubbleChart': + $this->renderBubbleChart($groupCount); + + break; + case 'radarChart': + $this->renderRadarChart($groupCount); + + break; + case 'surface3DChart': + $dimensions = '3d'; + // no break + case 'surfaceChart': + $this->renderContourChart($groupCount, $dimensions); + + break; + case 'stockChart': + $this->renderStockChart($groupCount); + + break; + default: + echo $chartType . ' is not yet implemented
        '; + + return false; + } + $this->renderLegend(); + + $this->graph->Stroke($outputDestination); + + return true; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/PHP Charting Libraries.txt b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/PHP Charting Libraries.txt new file mode 100644 index 0000000..4abab7a --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/PHP Charting Libraries.txt @@ -0,0 +1,20 @@ +ChartDirector + https://www.advsofteng.com/cdphp.html + +GraPHPite + http://graphpite.sourceforge.net/ + +JpGraph + http://www.aditus.nu/jpgraph/ + +LibChart + https://naku.dohcrew.com/libchart/pages/introduction/ + +pChart + http://pchart.sourceforge.net/ + +TeeChart + https://www.steema.com/ + +PHPGraphLib + http://www.ebrueggeman.com/phpgraphlib \ No newline at end of file diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Title.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Title.php new file mode 100644 index 0000000..090c4f3 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Title.php @@ -0,0 +1,90 @@ +caption = $caption; + $this->layout = $layout; + } + + /** + * Get caption. + * + * @return array|RichText|string + */ + public function getCaption() + { + return $this->caption; + } + + public function getCaptionText(): string + { + $caption = $this->caption; + if (is_string($caption)) { + return $caption; + } + if ($caption instanceof RichText) { + return $caption->getPlainText(); + } + $retVal = ''; + foreach ($caption as $textx) { + /** @var RichText|string */ + $text = $textx; + if ($text instanceof RichText) { + $retVal .= $text->getPlainText(); + } else { + $retVal .= $text; + } + } + + return $retVal; + } + + /** + * Set caption. + * + * @param array|RichText|string $caption + * + * @return $this + */ + public function setCaption($caption) + { + $this->caption = $caption; + + return $this; + } + + /** + * Get Layout. + * + * @return Layout + */ + public function getLayout() + { + return $this->layout; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/Cells.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/Cells.php new file mode 100644 index 0000000..e3d81cb --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/Cells.php @@ -0,0 +1,499 @@ +parent = $parent; + $this->cache = $cache; + $this->cachePrefix = $this->getUniqueID(); + } + + /** + * Return the parent worksheet for this cell collection. + * + * @return null|Worksheet + */ + public function getParent() + { + return $this->parent; + } + + /** + * Whether the collection holds a cell for the given coordinate. + * + * @param string $cellCoordinate Coordinate of the cell to check + * + * @return bool + */ + public function has($cellCoordinate) + { + if ($cellCoordinate === $this->currentCoordinate) { + return true; + } + + // Check if the requested entry exists in the index + return isset($this->index[$cellCoordinate]); + } + + /** + * Add or update a cell in the collection. + * + * @param Cell $cell Cell to update + * + * @return Cell + */ + public function update(Cell $cell) + { + return $this->add($cell->getCoordinate(), $cell); + } + + /** + * Delete a cell in cache identified by coordinate. + * + * @param string $cellCoordinate Coordinate of the cell to delete + */ + public function delete($cellCoordinate): void + { + if ($cellCoordinate === $this->currentCoordinate && $this->currentCell !== null) { + $this->currentCell->detach(); + $this->currentCoordinate = null; + $this->currentCell = null; + $this->currentCellIsDirty = false; + } + + unset($this->index[$cellCoordinate]); + + // Delete the entry from cache + $this->cache->delete($this->cachePrefix . $cellCoordinate); + } + + /** + * Get a list of all cell coordinates currently held in the collection. + * + * @return string[] + */ + public function getCoordinates() + { + return array_keys($this->index); + } + + /** + * Get a sorted list of all cell coordinates currently held in the collection by row and column. + * + * @return string[] + */ + public function getSortedCoordinates() + { + $sortKeys = []; + foreach ($this->getCoordinates() as $coord) { + $column = ''; + $row = 0; + sscanf($coord, '%[A-Z]%d', $column, $row); + $sortKeys[sprintf('%09d%3s', $row, $column)] = $coord; + } + ksort($sortKeys); + + return array_values($sortKeys); + } + + /** + * Get highest worksheet column and highest row that have cell records. + * + * @return array Highest column name and highest row number + */ + public function getHighestRowAndColumn() + { + // Lookup highest column and highest row + $col = ['A' => '1A']; + $row = [1]; + foreach ($this->getCoordinates() as $coord) { + $c = ''; + $r = 0; + sscanf($coord, '%[A-Z]%d', $c, $r); + $row[$r] = $r; + $col[$c] = strlen($c) . $c; + } + + // Determine highest column and row + $highestRow = max($row); + $highestColumn = substr((string) @max($col), 1); + + return [ + 'row' => $highestRow, + 'column' => $highestColumn, + ]; + } + + /** + * Return the cell coordinate of the currently active cell object. + * + * @return null|string + */ + public function getCurrentCoordinate() + { + return $this->currentCoordinate; + } + + /** + * Return the column coordinate of the currently active cell object. + * + * @return string + */ + public function getCurrentColumn() + { + $column = ''; + $row = 0; + + sscanf($this->currentCoordinate ?? '', '%[A-Z]%d', $column, $row); + + return $column; + } + + /** + * Return the row coordinate of the currently active cell object. + * + * @return int + */ + public function getCurrentRow() + { + $column = ''; + $row = 0; + + sscanf($this->currentCoordinate ?? '', '%[A-Z]%d', $column, $row); + + return (int) $row; + } + + /** + * Get highest worksheet column. + * + * @param null|int|string $row Return the highest column for the specified row, + * or the highest column of any row if no row number is passed + * + * @return string Highest column name + */ + public function getHighestColumn($row = null) + { + if ($row === null) { + $colRow = $this->getHighestRowAndColumn(); + + return $colRow['column']; + } + + $columnList = [1]; + foreach ($this->getCoordinates() as $coord) { + $c = ''; + $r = 0; + + sscanf($coord, '%[A-Z]%d', $c, $r); + if ($r != $row) { + continue; + } + $columnList[] = Coordinate::columnIndexFromString($c); + } + + return Coordinate::stringFromColumnIndex((int) @max($columnList)); + } + + /** + * Get highest worksheet row. + * + * @param null|string $column Return the highest row for the specified column, + * or the highest row of any column if no column letter is passed + * + * @return int Highest row number + */ + public function getHighestRow($column = null) + { + if ($column === null) { + $colRow = $this->getHighestRowAndColumn(); + + return $colRow['row']; + } + + $rowList = [0]; + foreach ($this->getCoordinates() as $coord) { + $c = ''; + $r = 0; + + sscanf($coord, '%[A-Z]%d', $c, $r); + if ($c != $column) { + continue; + } + $rowList[] = $r; + } + + return max($rowList); + } + + /** + * Generate a unique ID for cache referencing. + * + * @return string Unique Reference + */ + private function getUniqueID() + { + return uniqid('phpspreadsheet.', true) . '.'; + } + + /** + * Clone the cell collection. + * + * @return self + */ + public function cloneCellCollection(Worksheet $worksheet) + { + $this->storeCurrentCell(); + $newCollection = clone $this; + + $newCollection->parent = $worksheet; + if (is_object($newCollection->currentCell)) { + $newCollection->currentCell->attach($this); + } + + // Get old values + $oldKeys = $newCollection->getAllCacheKeys(); + $oldValues = $newCollection->cache->getMultiple($oldKeys); + $newValues = []; + $oldCachePrefix = $newCollection->cachePrefix; + + // Change prefix + $newCollection->cachePrefix = $newCollection->getUniqueID(); + foreach ($oldValues as $oldKey => $value) { + /** @var string $newKey */ + $newKey = str_replace($oldCachePrefix, $newCollection->cachePrefix, $oldKey); + $newValues[$newKey] = clone $value; + } + + // Store new values + $stored = $newCollection->cache->setMultiple($newValues); + $this->destructIfNeeded($stored, $newCollection, 'Failed to copy cells in cache'); + + return $newCollection; + } + + /** + * Remove a row, deleting all cells in that row. + * + * @param string $row Row number to remove + */ + public function removeRow($row): void + { + foreach ($this->getCoordinates() as $coord) { + $c = ''; + $r = 0; + + sscanf($coord, '%[A-Z]%d', $c, $r); + if ($r == $row) { + $this->delete($coord); + } + } + } + + /** + * Remove a column, deleting all cells in that column. + * + * @param string $column Column ID to remove + */ + public function removeColumn($column): void + { + foreach ($this->getCoordinates() as $coord) { + $c = ''; + $r = 0; + + sscanf($coord, '%[A-Z]%d', $c, $r); + if ($c == $column) { + $this->delete($coord); + } + } + } + + /** + * Store cell data in cache for the current cell object if it's "dirty", + * and the 'nullify' the current cell object. + */ + private function storeCurrentCell(): void + { + if ($this->currentCellIsDirty && isset($this->currentCoordinate, $this->currentCell)) { + $this->currentCell->detach(); + + $stored = $this->cache->set($this->cachePrefix . $this->currentCoordinate, $this->currentCell); + $this->destructIfNeeded($stored, $this, "Failed to store cell {$this->currentCoordinate} in cache"); + $this->currentCellIsDirty = false; + } + + $this->currentCoordinate = null; + $this->currentCell = null; + } + + private function destructIfNeeded(bool $stored, self $cells, string $message): void + { + if (!$stored) { + $cells->__destruct(); + + throw new PhpSpreadsheetException($message); + } + } + + /** + * Add or update a cell identified by its coordinate into the collection. + * + * @param string $cellCoordinate Coordinate of the cell to update + * @param Cell $cell Cell to update + * + * @return Cell + */ + public function add($cellCoordinate, Cell $cell) + { + if ($cellCoordinate !== $this->currentCoordinate) { + $this->storeCurrentCell(); + } + $this->index[$cellCoordinate] = true; + + $this->currentCoordinate = $cellCoordinate; + $this->currentCell = $cell; + $this->currentCellIsDirty = true; + + return $cell; + } + + /** + * Get cell at a specific coordinate. + * + * @param string $cellCoordinate Coordinate of the cell + * + * @return null|Cell Cell that was found, or null if not found + */ + public function get($cellCoordinate) + { + if ($cellCoordinate === $this->currentCoordinate) { + return $this->currentCell; + } + $this->storeCurrentCell(); + + // Return null if requested entry doesn't exist in collection + if (!$this->has($cellCoordinate)) { + return null; + } + + // Check if the entry that has been requested actually exists + $cell = $this->cache->get($this->cachePrefix . $cellCoordinate); + if ($cell === null) { + throw new PhpSpreadsheetException("Cell entry {$cellCoordinate} no longer exists in cache. This probably means that the cache was cleared by someone else."); + } + + // Set current entry to the requested entry + $this->currentCoordinate = $cellCoordinate; + $this->currentCell = $cell; + // Re-attach this as the cell's parent + $this->currentCell->attach($this); + + // Return requested entry + return $this->currentCell; + } + + /** + * Clear the cell collection and disconnect from our parent. + */ + public function unsetWorksheetCells(): void + { + if ($this->currentCell !== null) { + $this->currentCell->detach(); + $this->currentCell = null; + $this->currentCoordinate = null; + } + + // Flush the cache + $this->__destruct(); + + $this->index = []; + + // detach ourself from the worksheet, so that it can then delete this object successfully + $this->parent = null; + } + + /** + * Destroy this cell collection. + */ + public function __destruct() + { + $this->cache->deleteMultiple($this->getAllCacheKeys()); + } + + /** + * Returns all known cache keys. + * + * @return Generator|string[] + */ + private function getAllCacheKeys() + { + foreach ($this->getCoordinates() as $coordinate) { + yield $this->cachePrefix . $coordinate; + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/CellsFactory.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/CellsFactory.php new file mode 100644 index 0000000..26f18df --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/CellsFactory.php @@ -0,0 +1,21 @@ +cache = []; + + return true; + } + + public function delete($key) + { + unset($this->cache[$key]); + + return true; + } + + public function deleteMultiple($keys) + { + foreach ($keys as $key) { + $this->delete($key); + } + + return true; + } + + public function get($key, $default = null) + { + if ($this->has($key)) { + return $this->cache[$key]; + } + + return $default; + } + + public function getMultiple($keys, $default = null) + { + $results = []; + foreach ($keys as $key) { + $results[$key] = $this->get($key, $default); + } + + return $results; + } + + public function has($key) + { + return array_key_exists($key, $this->cache); + } + + public function set($key, $value, $ttl = null) + { + $this->cache[$key] = $value; + + return true; + } + + public function setMultiple($values, $ttl = null) + { + foreach ($values as $key => $value) { + $this->set($key, $value); + } + + return true; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Comment.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Comment.php new file mode 100644 index 0000000..7aaf975 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Comment.php @@ -0,0 +1,302 @@ +author = 'Author'; + $this->text = new RichText(); + $this->fillColor = new Color('FFFFFFE1'); + $this->alignment = Alignment::HORIZONTAL_GENERAL; + } + + /** + * Get Author. + */ + public function getAuthor(): string + { + return $this->author; + } + + /** + * Set Author. + */ + public function setAuthor(string $author): self + { + $this->author = $author; + + return $this; + } + + /** + * Get Rich text comment. + */ + public function getText(): RichText + { + return $this->text; + } + + /** + * Set Rich text comment. + */ + public function setText(RichText $text): self + { + $this->text = $text; + + return $this; + } + + /** + * Get comment width (CSS style, i.e. XXpx or YYpt). + */ + public function getWidth(): string + { + return $this->width; + } + + /** + * Set comment width (CSS style, i.e. XXpx or YYpt). Default unit is pt. + */ + public function setWidth(string $width): self + { + $width = new Size($width); + if ($width->valid()) { + $this->width = (string) $width; + } + + return $this; + } + + /** + * Get comment height (CSS style, i.e. XXpx or YYpt). + */ + public function getHeight(): string + { + return $this->height; + } + + /** + * Set comment height (CSS style, i.e. XXpx or YYpt). Default unit is pt. + */ + public function setHeight(string $height): self + { + $height = new Size($height); + if ($height->valid()) { + $this->height = (string) $height; + } + + return $this; + } + + /** + * Get left margin (CSS style, i.e. XXpx or YYpt). + */ + public function getMarginLeft(): string + { + return $this->marginLeft; + } + + /** + * Set left margin (CSS style, i.e. XXpx or YYpt). Default unit is pt. + */ + public function setMarginLeft(string $margin): self + { + $margin = new Size($margin); + if ($margin->valid()) { + $this->marginLeft = (string) $margin; + } + + return $this; + } + + /** + * Get top margin (CSS style, i.e. XXpx or YYpt). + */ + public function getMarginTop(): string + { + return $this->marginTop; + } + + /** + * Set top margin (CSS style, i.e. XXpx or YYpt). Default unit is pt. + */ + public function setMarginTop(string $margin): self + { + $margin = new Size($margin); + if ($margin->valid()) { + $this->marginTop = (string) $margin; + } + + return $this; + } + + /** + * Is the comment visible by default? + */ + public function getVisible(): bool + { + return $this->visible; + } + + /** + * Set comment default visibility. + */ + public function setVisible(bool $visibility): self + { + $this->visible = $visibility; + + return $this; + } + + /** + * Set fill color. + */ + public function setFillColor(Color $color): self + { + $this->fillColor = $color; + + return $this; + } + + /** + * Get fill color. + */ + public function getFillColor(): Color + { + return $this->fillColor; + } + + /** + * Set Alignment. + */ + public function setAlignment(string $alignment): self + { + $this->alignment = $alignment; + + return $this; + } + + /** + * Get Alignment. + */ + public function getAlignment(): string + { + return $this->alignment; + } + + /** + * Get hash code. + */ + public function getHashCode(): string + { + return md5( + $this->author . + $this->text->getHashCode() . + $this->width . + $this->height . + $this->marginLeft . + $this->marginTop . + ($this->visible ? 1 : 0) . + $this->fillColor->getHashCode() . + $this->alignment . + __CLASS__ + ); + } + + /** + * Implement PHP __clone to create a deep clone, not just a shallow copy. + */ + public function __clone() + { + $vars = get_object_vars($this); + foreach ($vars as $key => $value) { + if (is_object($value)) { + $this->$key = clone $value; + } else { + $this->$key = $value; + } + } + } + + /** + * Convert to string. + */ + public function __toString(): string + { + return $this->text->getPlainText(); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/DefinedName.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/DefinedName.php new file mode 100644 index 0000000..3b874b4 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/DefinedName.php @@ -0,0 +1,272 @@ +worksheet). + * + * @var bool + */ + protected $localOnly; + + /** + * Scope. + * + * @var Worksheet + */ + protected $scope; + + /** + * Whether this is a named range or a named formula. + * + * @var bool + */ + protected $isFormula; + + /** + * Create a new Defined Name. + */ + public function __construct( + string $name, + ?Worksheet $worksheet = null, + ?string $value = null, + bool $localOnly = false, + ?Worksheet $scope = null + ) { + if ($worksheet === null) { + $worksheet = $scope; + } + + // Set local members + $this->name = $name; + $this->worksheet = $worksheet; + $this->value = (string) $value; + $this->localOnly = $localOnly; + // If local only, then the scope will be set to worksheet unless a scope is explicitly set + $this->scope = ($localOnly === true) ? (($scope === null) ? $worksheet : $scope) : null; + // If the range string contains characters that aren't associated with the range definition (A-Z,1-9 + // for cell references, and $, or the range operators (colon comma or space), quotes and ! for + // worksheet names + // then this is treated as a named formula, and not a named range + $this->isFormula = self::testIfFormula($this->value); + } + + /** + * Create a new defined name, either a range or a formula. + */ + public static function createInstance( + string $name, + ?Worksheet $worksheet = null, + ?string $value = null, + bool $localOnly = false, + ?Worksheet $scope = null + ): self { + $value = (string) $value; + $isFormula = self::testIfFormula($value); + if ($isFormula) { + return new NamedFormula($name, $worksheet, $value, $localOnly, $scope); + } + + return new NamedRange($name, $worksheet, $value, $localOnly, $scope); + } + + public static function testIfFormula(string $value): bool + { + if (substr($value, 0, 1) === '=') { + $value = substr($value, 1); + } + + if (is_numeric($value)) { + return true; + } + + $segMatcher = false; + foreach (explode("'", $value) as $subVal) { + // Only test in alternate array entries (the non-quoted blocks) + if ( + ($segMatcher = !$segMatcher) && + (preg_match('/' . self::REGEXP_IDENTIFY_FORMULA . '/miu', $subVal)) + ) { + return true; + } + } + + return false; + } + + /** + * Get name. + */ + public function getName(): string + { + return $this->name; + } + + /** + * Set name. + */ + public function setName(string $name): self + { + if (!empty($name)) { + // Old title + $oldTitle = $this->name; + + // Re-attach + if ($this->worksheet !== null) { + $this->worksheet->getParent()->removeNamedRange($this->name, $this->worksheet); + } + $this->name = $name; + + if ($this->worksheet !== null) { + $this->worksheet->getParent()->addNamedRange($this); + } + + // New title + $newTitle = $this->name; + ReferenceHelper::getInstance()->updateNamedFormulas($this->worksheet->getParent(), $oldTitle, $newTitle); + } + + return $this; + } + + /** + * Get worksheet. + */ + public function getWorksheet(): ?Worksheet + { + return $this->worksheet; + } + + /** + * Set worksheet. + */ + public function setWorksheet(?Worksheet $worksheet): self + { + $this->worksheet = $worksheet; + + return $this; + } + + /** + * Get range or formula value. + */ + public function getValue(): string + { + return $this->value; + } + + /** + * Set range or formula value. + */ + public function setValue(string $value): self + { + $this->value = $value; + + return $this; + } + + /** + * Get localOnly. + */ + public function getLocalOnly(): bool + { + return $this->localOnly; + } + + /** + * Set localOnly. + */ + public function setLocalOnly(bool $localScope): self + { + $this->localOnly = $localScope; + $this->scope = $localScope ? $this->worksheet : null; + + return $this; + } + + /** + * Get scope. + */ + public function getScope(): ?Worksheet + { + return $this->scope; + } + + /** + * Set scope. + */ + public function setScope(?Worksheet $worksheet): self + { + $this->scope = $worksheet; + $this->localOnly = $worksheet !== null; + + return $this; + } + + /** + * Identify whether this is a named range or a named formula. + */ + public function isFormula(): bool + { + return $this->isFormula; + } + + /** + * Resolve a named range to a regular cell range or formula. + */ + public static function resolveName(string $definedName, Worksheet $worksheet, string $sheetName = ''): ?self + { + if ($sheetName === '') { + $worksheet2 = $worksheet; + } else { + $worksheet2 = $worksheet->getParent()->getSheetByName($sheetName); + if ($worksheet2 === null) { + return null; + } + } + + return $worksheet->getParent()->getDefinedName($definedName, $worksheet2); + } + + /** + * Implement PHP __clone to create a deep clone, not just a shallow copy. + */ + public function __clone() + { + $vars = get_object_vars($this); + foreach ($vars as $key => $value) { + if (is_object($value)) { + $this->$key = clone $value; + } else { + $this->$key = $value; + } + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Document/Properties.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Document/Properties.php new file mode 100644 index 0000000..3be5a67 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Document/Properties.php @@ -0,0 +1,537 @@ +lastModifiedBy = $this->creator; + $this->created = self::intOrFloatTimestamp(null); + $this->modified = self::intOrFloatTimestamp(null); + } + + /** + * Get Creator. + */ + public function getCreator(): string + { + return $this->creator; + } + + /** + * Set Creator. + * + * @return $this + */ + public function setCreator(string $creator): self + { + $this->creator = $creator; + + return $this; + } + + /** + * Get Last Modified By. + */ + public function getLastModifiedBy(): string + { + return $this->lastModifiedBy; + } + + /** + * Set Last Modified By. + * + * @return $this + */ + public function setLastModifiedBy(string $modifiedBy): self + { + $this->lastModifiedBy = $modifiedBy; + + return $this; + } + + /** + * @param null|float|int|string $timestamp + * + * @return float|int + */ + private static function intOrFloatTimestamp($timestamp) + { + if ($timestamp === null) { + $timestamp = (float) (new DateTime())->format('U'); + } elseif (is_string($timestamp)) { + if (is_numeric($timestamp)) { + $timestamp = (float) $timestamp; + } else { + $timestamp = preg_replace('/[.][0-9]*$/', '', $timestamp) ?? ''; + $timestamp = preg_replace('/^(\\d{4})- (\\d)/', '$1-0$2', $timestamp) ?? ''; + $timestamp = preg_replace('/^(\\d{4}-\\d{2})- (\\d)/', '$1-0$2', $timestamp) ?? ''; + $timestamp = (float) (new DateTime($timestamp))->format('U'); + } + } + + return IntOrFloat::evaluate($timestamp); + } + + /** + * Get Created. + * + * @return float|int + */ + public function getCreated() + { + return $this->created; + } + + /** + * Set Created. + * + * @param null|float|int|string $timestamp + * + * @return $this + */ + public function setCreated($timestamp): self + { + $this->created = self::intOrFloatTimestamp($timestamp); + + return $this; + } + + /** + * Get Modified. + * + * @return float|int + */ + public function getModified() + { + return $this->modified; + } + + /** + * Set Modified. + * + * @param null|float|int|string $timestamp + * + * @return $this + */ + public function setModified($timestamp): self + { + $this->modified = self::intOrFloatTimestamp($timestamp); + + return $this; + } + + /** + * Get Title. + */ + public function getTitle(): string + { + return $this->title; + } + + /** + * Set Title. + * + * @return $this + */ + public function setTitle(string $title): self + { + $this->title = $title; + + return $this; + } + + /** + * Get Description. + */ + public function getDescription(): string + { + return $this->description; + } + + /** + * Set Description. + * + * @return $this + */ + public function setDescription(string $description): self + { + $this->description = $description; + + return $this; + } + + /** + * Get Subject. + */ + public function getSubject(): string + { + return $this->subject; + } + + /** + * Set Subject. + * + * @return $this + */ + public function setSubject(string $subject): self + { + $this->subject = $subject; + + return $this; + } + + /** + * Get Keywords. + */ + public function getKeywords(): string + { + return $this->keywords; + } + + /** + * Set Keywords. + * + * @return $this + */ + public function setKeywords(string $keywords): self + { + $this->keywords = $keywords; + + return $this; + } + + /** + * Get Category. + */ + public function getCategory(): string + { + return $this->category; + } + + /** + * Set Category. + * + * @return $this + */ + public function setCategory(string $category): self + { + $this->category = $category; + + return $this; + } + + /** + * Get Company. + */ + public function getCompany(): string + { + return $this->company; + } + + /** + * Set Company. + * + * @return $this + */ + public function setCompany(string $company): self + { + $this->company = $company; + + return $this; + } + + /** + * Get Manager. + */ + public function getManager(): string + { + return $this->manager; + } + + /** + * Set Manager. + * + * @return $this + */ + public function setManager(string $manager): self + { + $this->manager = $manager; + + return $this; + } + + /** + * Get a List of Custom Property Names. + * + * @return string[] + */ + public function getCustomProperties(): array + { + return array_keys($this->customProperties); + } + + /** + * Check if a Custom Property is defined. + */ + public function isCustomPropertySet(string $propertyName): bool + { + return array_key_exists($propertyName, $this->customProperties); + } + + /** + * Get a Custom Property Value. + * + * @return mixed + */ + public function getCustomPropertyValue(string $propertyName) + { + if (isset($this->customProperties[$propertyName])) { + return $this->customProperties[$propertyName]['value']; + } + + return null; + } + + /** + * Get a Custom Property Type. + * + * @return null|string + */ + public function getCustomPropertyType(string $propertyName) + { + return $this->customProperties[$propertyName]['type'] ?? null; + } + + /** + * @param mixed $propertyValue + */ + private function identifyPropertyType($propertyValue): string + { + if (is_float($propertyValue)) { + return self::PROPERTY_TYPE_FLOAT; + } + if (is_int($propertyValue)) { + return self::PROPERTY_TYPE_INTEGER; + } + if (is_bool($propertyValue)) { + return self::PROPERTY_TYPE_BOOLEAN; + } + + return self::PROPERTY_TYPE_STRING; + } + + /** + * Set a Custom Property. + * + * @param mixed $propertyValue + * @param string $propertyType + * 'i' : Integer + * 'f' : Floating Point + * 's' : String + * 'd' : Date/Time + * 'b' : Boolean + * + * @return $this + */ + public function setCustomProperty(string $propertyName, $propertyValue = '', $propertyType = null): self + { + if (($propertyType === null) || (!in_array($propertyType, self::VALID_PROPERTY_TYPE_LIST))) { + $propertyType = $this->identifyPropertyType($propertyValue); + } + + if (!is_object($propertyValue)) { + $this->customProperties[$propertyName] = [ + 'value' => self::convertProperty($propertyValue, $propertyType), + 'type' => $propertyType, + ]; + } + + return $this; + } + + private const PROPERTY_TYPE_ARRAY = [ + 'i' => self::PROPERTY_TYPE_INTEGER, // Integer + 'i1' => self::PROPERTY_TYPE_INTEGER, // 1-Byte Signed Integer + 'i2' => self::PROPERTY_TYPE_INTEGER, // 2-Byte Signed Integer + 'i4' => self::PROPERTY_TYPE_INTEGER, // 4-Byte Signed Integer + 'i8' => self::PROPERTY_TYPE_INTEGER, // 8-Byte Signed Integer + 'int' => self::PROPERTY_TYPE_INTEGER, // Integer + 'ui1' => self::PROPERTY_TYPE_INTEGER, // 1-Byte Unsigned Integer + 'ui2' => self::PROPERTY_TYPE_INTEGER, // 2-Byte Unsigned Integer + 'ui4' => self::PROPERTY_TYPE_INTEGER, // 4-Byte Unsigned Integer + 'ui8' => self::PROPERTY_TYPE_INTEGER, // 8-Byte Unsigned Integer + 'uint' => self::PROPERTY_TYPE_INTEGER, // Unsigned Integer + 'f' => self::PROPERTY_TYPE_FLOAT, // Real Number + 'r4' => self::PROPERTY_TYPE_FLOAT, // 4-Byte Real Number + 'r8' => self::PROPERTY_TYPE_FLOAT, // 8-Byte Real Number + 'decimal' => self::PROPERTY_TYPE_FLOAT, // Decimal + 's' => self::PROPERTY_TYPE_STRING, // String + 'empty' => self::PROPERTY_TYPE_STRING, // Empty + 'null' => self::PROPERTY_TYPE_STRING, // Null + 'lpstr' => self::PROPERTY_TYPE_STRING, // LPSTR + 'lpwstr' => self::PROPERTY_TYPE_STRING, // LPWSTR + 'bstr' => self::PROPERTY_TYPE_STRING, // Basic String + 'd' => self::PROPERTY_TYPE_DATE, // Date and Time + 'date' => self::PROPERTY_TYPE_DATE, // Date and Time + 'filetime' => self::PROPERTY_TYPE_DATE, // File Time + 'b' => self::PROPERTY_TYPE_BOOLEAN, // Boolean + 'bool' => self::PROPERTY_TYPE_BOOLEAN, // Boolean + ]; + + private const SPECIAL_TYPES = [ + 'empty' => '', + 'null' => null, + ]; + + /** + * Convert property to form desired by Excel. + * + * @param mixed $propertyValue + * + * @return mixed + */ + public static function convertProperty($propertyValue, string $propertyType) + { + return self::SPECIAL_TYPES[$propertyType] ?? self::convertProperty2($propertyValue, $propertyType); + } + + /** + * Convert property to form desired by Excel. + * + * @param mixed $propertyValue + * + * @return mixed + */ + private static function convertProperty2($propertyValue, string $type) + { + $propertyType = self::convertPropertyType($type); + switch ($propertyType) { + case self::PROPERTY_TYPE_INTEGER: + $intValue = (int) $propertyValue; + + return ($type[0] === 'u') ? abs($intValue) : $intValue; + case self::PROPERTY_TYPE_FLOAT: + return (float) $propertyValue; + case self::PROPERTY_TYPE_DATE: + return self::intOrFloatTimestamp($propertyValue); + case self::PROPERTY_TYPE_BOOLEAN: + return is_bool($propertyValue) ? $propertyValue : ($propertyValue === 'true'); + default: // includes string + return $propertyValue; + } + } + + public static function convertPropertyType(string $propertyType): string + { + return self::PROPERTY_TYPE_ARRAY[$propertyType] ?? self::PROPERTY_TYPE_UNKNOWN; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Document/Security.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Document/Security.php new file mode 100644 index 0000000..279aed4 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Document/Security.php @@ -0,0 +1,152 @@ +lockRevision || + $this->lockStructure || + $this->lockWindows; + } + + public function getLockRevision(): bool + { + return $this->lockRevision; + } + + public function setLockRevision(?bool $locked): self + { + if ($locked !== null) { + $this->lockRevision = $locked; + } + + return $this; + } + + public function getLockStructure(): bool + { + return $this->lockStructure; + } + + public function setLockStructure(?bool $locked): self + { + if ($locked !== null) { + $this->lockStructure = $locked; + } + + return $this; + } + + public function getLockWindows(): bool + { + return $this->lockWindows; + } + + public function setLockWindows(?bool $locked): self + { + if ($locked !== null) { + $this->lockWindows = $locked; + } + + return $this; + } + + public function getRevisionsPassword(): string + { + return $this->revisionsPassword; + } + + /** + * Set RevisionsPassword. + * + * @param string $password + * @param bool $alreadyHashed If the password has already been hashed, set this to true + * + * @return $this + */ + public function setRevisionsPassword(?string $password, bool $alreadyHashed = false) + { + if ($password !== null) { + if (!$alreadyHashed) { + $password = PasswordHasher::hashPassword($password); + } + $this->revisionsPassword = $password; + } + + return $this; + } + + public function getWorkbookPassword(): string + { + return $this->workbookPassword; + } + + /** + * Set WorkbookPassword. + * + * @param string $password + * @param bool $alreadyHashed If the password has already been hashed, set this to true + * + * @return $this + */ + public function setWorkbookPassword(?string $password, bool $alreadyHashed = false) + { + if ($password !== null) { + if (!$alreadyHashed) { + $password = PasswordHasher::hashPassword($password); + } + $this->workbookPassword = $password; + } + + return $this; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Exception.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Exception.php new file mode 100644 index 0000000..9c5ab30 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Exception.php @@ -0,0 +1,7 @@ + + */ + protected $items = []; + + /** + * HashTable key map. + * + * @var array + */ + protected $keyMap = []; + + /** + * Create a new HashTable. + * + * @param T[] $source Optional source array to create HashTable from + */ + public function __construct($source = null) + { + if ($source !== null) { + // Create HashTable + $this->addFromSource($source); + } + } + + /** + * Add HashTable items from source. + * + * @param T[] $source Source array to create HashTable from + */ + public function addFromSource(?array $source = null): void + { + // Check if an array was passed + if ($source === null) { + return; + } + + foreach ($source as $item) { + $this->add($item); + } + } + + /** + * Add HashTable item. + * + * @param T $source Item to add + */ + public function add(IComparable $source): void + { + $hash = $source->getHashCode(); + if (!isset($this->items[$hash])) { + $this->items[$hash] = $source; + $this->keyMap[count($this->items) - 1] = $hash; + } + } + + /** + * Remove HashTable item. + * + * @param T $source Item to remove + */ + public function remove(IComparable $source): void + { + $hash = $source->getHashCode(); + if (isset($this->items[$hash])) { + unset($this->items[$hash]); + + $deleteKey = -1; + foreach ($this->keyMap as $key => $value) { + if ($deleteKey >= 0) { + $this->keyMap[$key - 1] = $value; + } + + if ($value == $hash) { + $deleteKey = $key; + } + } + unset($this->keyMap[count($this->keyMap) - 1]); + } + } + + /** + * Clear HashTable. + */ + public function clear(): void + { + $this->items = []; + $this->keyMap = []; + } + + /** + * Count. + * + * @return int + */ + public function count() + { + return count($this->items); + } + + /** + * Get index for hash code. + * + * @return false|int Index + */ + public function getIndexForHashCode(string $hashCode) + { + return array_search($hashCode, $this->keyMap, true); + } + + /** + * Get by index. + * + * @return null|T + */ + public function getByIndex(int $index) + { + if (isset($this->keyMap[$index])) { + return $this->getByHashCode($this->keyMap[$index]); + } + + return null; + } + + /** + * Get by hashcode. + * + * @return null|T + */ + public function getByHashCode(string $hashCode) + { + if (isset($this->items[$hashCode])) { + return $this->items[$hashCode]; + } + + return null; + } + + /** + * HashTable to array. + * + * @return T[] + */ + public function toArray() + { + return $this->items; + } + + /** + * Implement PHP __clone to create a deep clone, not just a shallow copy. + */ + public function __clone() + { + $vars = get_object_vars($this); + foreach ($vars as $key => $value) { + // each member of this class is an array + if (is_array($value)) { + $array1 = $value; + foreach ($array1 as $key1 => $value1) { + if (is_object($value1)) { + $array1[$key1] = clone $value1; + } + } + $this->$key = $array1; + } + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Dimension.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Dimension.php new file mode 100644 index 0000000..136ffd7 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Dimension.php @@ -0,0 +1,103 @@ + 96.0 / 2.54, + self::UOM_MILLIMETERS => 96.0 / 25.4, + self::UOM_INCHES => 96.0, + self::UOM_PIXELS => 1.0, + self::UOM_POINTS => 96.0 / 72, + self::UOM_PICA => 96.0 * 12 / 72, + ]; + + /** + * Based on a standard column width of 8.54 units in MS Excel. + */ + const RELATIVE_UNITS = [ + 'em' => 10.0 / 8.54, + 'ex' => 10.0 / 8.54, + 'ch' => 10.0 / 8.54, + 'rem' => 10.0 / 8.54, + 'vw' => 8.54, + 'vh' => 8.54, + 'vmin' => 8.54, + 'vmax' => 8.54, + '%' => 8.54 / 100, + ]; + + /** + * @var float|int If this is a width, then size is measured in pixels (if is set) + * or in Excel's default column width units if $unit is null. + * If this is a height, then size is measured in pixels () + * or in points () if $unit is null. + */ + protected $size; + + /** + * @var null|string + */ + protected $unit; + + public function __construct(string $dimension) + { + [$size, $unit] = sscanf($dimension, '%[1234567890.]%s'); + $unit = strtolower(trim($unit)); + + // If a UoM is specified, then convert the size to pixels for internal storage + if (isset(self::ABSOLUTE_UNITS[$unit])) { + $size *= self::ABSOLUTE_UNITS[$unit]; + $this->unit = self::UOM_PIXELS; + } elseif (isset(self::RELATIVE_UNITS[$unit])) { + $size *= self::RELATIVE_UNITS[$unit]; + $size = round($size, 4); + } + + $this->size = $size; + } + + public function width(): float + { + return (float) ($this->unit === null) + ? $this->size + : round(Drawing::pixelsToCellDimension((int) $this->size, new Font(false)), 4); + } + + public function height(): float + { + return (float) ($this->unit === null) + ? $this->size + : $this->toUnit(self::UOM_POINTS); + } + + public function toUnit(string $unitOfMeasure): float + { + $unitOfMeasure = strtolower($unitOfMeasure); + if (!array_key_exists($unitOfMeasure, self::ABSOLUTE_UNITS)) { + throw new Exception("{$unitOfMeasure} is not a vaid unit of measure"); + } + + $size = $this->size; + if ($this->unit === null) { + $size = Drawing::cellDimensionToPixels($size, new Font(false)); + } + + return $size / self::ABSOLUTE_UNITS[$unitOfMeasure]; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Html.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Html.php new file mode 100644 index 0000000..73a3308 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Html.php @@ -0,0 +1,839 @@ + 'f0f8ff', + 'antiquewhite' => 'faebd7', + 'antiquewhite1' => 'ffefdb', + 'antiquewhite2' => 'eedfcc', + 'antiquewhite3' => 'cdc0b0', + 'antiquewhite4' => '8b8378', + 'aqua' => '00ffff', + 'aquamarine1' => '7fffd4', + 'aquamarine2' => '76eec6', + 'aquamarine4' => '458b74', + 'azure1' => 'f0ffff', + 'azure2' => 'e0eeee', + 'azure3' => 'c1cdcd', + 'azure4' => '838b8b', + 'beige' => 'f5f5dc', + 'bisque1' => 'ffe4c4', + 'bisque2' => 'eed5b7', + 'bisque3' => 'cdb79e', + 'bisque4' => '8b7d6b', + 'black' => '000000', + 'blanchedalmond' => 'ffebcd', + 'blue' => '0000ff', + 'blue1' => '0000ff', + 'blue2' => '0000ee', + 'blue4' => '00008b', + 'blueviolet' => '8a2be2', + 'brown' => 'a52a2a', + 'brown1' => 'ff4040', + 'brown2' => 'ee3b3b', + 'brown3' => 'cd3333', + 'brown4' => '8b2323', + 'burlywood' => 'deb887', + 'burlywood1' => 'ffd39b', + 'burlywood2' => 'eec591', + 'burlywood3' => 'cdaa7d', + 'burlywood4' => '8b7355', + 'cadetblue' => '5f9ea0', + 'cadetblue1' => '98f5ff', + 'cadetblue2' => '8ee5ee', + 'cadetblue3' => '7ac5cd', + 'cadetblue4' => '53868b', + 'chartreuse1' => '7fff00', + 'chartreuse2' => '76ee00', + 'chartreuse3' => '66cd00', + 'chartreuse4' => '458b00', + 'chocolate' => 'd2691e', + 'chocolate1' => 'ff7f24', + 'chocolate2' => 'ee7621', + 'chocolate3' => 'cd661d', + 'coral' => 'ff7f50', + 'coral1' => 'ff7256', + 'coral2' => 'ee6a50', + 'coral3' => 'cd5b45', + 'coral4' => '8b3e2f', + 'cornflowerblue' => '6495ed', + 'cornsilk1' => 'fff8dc', + 'cornsilk2' => 'eee8cd', + 'cornsilk3' => 'cdc8b1', + 'cornsilk4' => '8b8878', + 'cyan1' => '00ffff', + 'cyan2' => '00eeee', + 'cyan3' => '00cdcd', + 'cyan4' => '008b8b', + 'darkgoldenrod' => 'b8860b', + 'darkgoldenrod1' => 'ffb90f', + 'darkgoldenrod2' => 'eead0e', + 'darkgoldenrod3' => 'cd950c', + 'darkgoldenrod4' => '8b6508', + 'darkgreen' => '006400', + 'darkkhaki' => 'bdb76b', + 'darkolivegreen' => '556b2f', + 'darkolivegreen1' => 'caff70', + 'darkolivegreen2' => 'bcee68', + 'darkolivegreen3' => 'a2cd5a', + 'darkolivegreen4' => '6e8b3d', + 'darkorange' => 'ff8c00', + 'darkorange1' => 'ff7f00', + 'darkorange2' => 'ee7600', + 'darkorange3' => 'cd6600', + 'darkorange4' => '8b4500', + 'darkorchid' => '9932cc', + 'darkorchid1' => 'bf3eff', + 'darkorchid2' => 'b23aee', + 'darkorchid3' => '9a32cd', + 'darkorchid4' => '68228b', + 'darksalmon' => 'e9967a', + 'darkseagreen' => '8fbc8f', + 'darkseagreen1' => 'c1ffc1', + 'darkseagreen2' => 'b4eeb4', + 'darkseagreen3' => '9bcd9b', + 'darkseagreen4' => '698b69', + 'darkslateblue' => '483d8b', + 'darkslategray' => '2f4f4f', + 'darkslategray1' => '97ffff', + 'darkslategray2' => '8deeee', + 'darkslategray3' => '79cdcd', + 'darkslategray4' => '528b8b', + 'darkturquoise' => '00ced1', + 'darkviolet' => '9400d3', + 'deeppink1' => 'ff1493', + 'deeppink2' => 'ee1289', + 'deeppink3' => 'cd1076', + 'deeppink4' => '8b0a50', + 'deepskyblue1' => '00bfff', + 'deepskyblue2' => '00b2ee', + 'deepskyblue3' => '009acd', + 'deepskyblue4' => '00688b', + 'dimgray' => '696969', + 'dodgerblue1' => '1e90ff', + 'dodgerblue2' => '1c86ee', + 'dodgerblue3' => '1874cd', + 'dodgerblue4' => '104e8b', + 'firebrick' => 'b22222', + 'firebrick1' => 'ff3030', + 'firebrick2' => 'ee2c2c', + 'firebrick3' => 'cd2626', + 'firebrick4' => '8b1a1a', + 'floralwhite' => 'fffaf0', + 'forestgreen' => '228b22', + 'fuchsia' => 'ff00ff', + 'gainsboro' => 'dcdcdc', + 'ghostwhite' => 'f8f8ff', + 'gold1' => 'ffd700', + 'gold2' => 'eec900', + 'gold3' => 'cdad00', + 'gold4' => '8b7500', + 'goldenrod' => 'daa520', + 'goldenrod1' => 'ffc125', + 'goldenrod2' => 'eeb422', + 'goldenrod3' => 'cd9b1d', + 'goldenrod4' => '8b6914', + 'gray' => 'bebebe', + 'gray1' => '030303', + 'gray10' => '1a1a1a', + 'gray11' => '1c1c1c', + 'gray12' => '1f1f1f', + 'gray13' => '212121', + 'gray14' => '242424', + 'gray15' => '262626', + 'gray16' => '292929', + 'gray17' => '2b2b2b', + 'gray18' => '2e2e2e', + 'gray19' => '303030', + 'gray2' => '050505', + 'gray20' => '333333', + 'gray21' => '363636', + 'gray22' => '383838', + 'gray23' => '3b3b3b', + 'gray24' => '3d3d3d', + 'gray25' => '404040', + 'gray26' => '424242', + 'gray27' => '454545', + 'gray28' => '474747', + 'gray29' => '4a4a4a', + 'gray3' => '080808', + 'gray30' => '4d4d4d', + 'gray31' => '4f4f4f', + 'gray32' => '525252', + 'gray33' => '545454', + 'gray34' => '575757', + 'gray35' => '595959', + 'gray36' => '5c5c5c', + 'gray37' => '5e5e5e', + 'gray38' => '616161', + 'gray39' => '636363', + 'gray4' => '0a0a0a', + 'gray40' => '666666', + 'gray41' => '696969', + 'gray42' => '6b6b6b', + 'gray43' => '6e6e6e', + 'gray44' => '707070', + 'gray45' => '737373', + 'gray46' => '757575', + 'gray47' => '787878', + 'gray48' => '7a7a7a', + 'gray49' => '7d7d7d', + 'gray5' => '0d0d0d', + 'gray50' => '7f7f7f', + 'gray51' => '828282', + 'gray52' => '858585', + 'gray53' => '878787', + 'gray54' => '8a8a8a', + 'gray55' => '8c8c8c', + 'gray56' => '8f8f8f', + 'gray57' => '919191', + 'gray58' => '949494', + 'gray59' => '969696', + 'gray6' => '0f0f0f', + 'gray60' => '999999', + 'gray61' => '9c9c9c', + 'gray62' => '9e9e9e', + 'gray63' => 'a1a1a1', + 'gray64' => 'a3a3a3', + 'gray65' => 'a6a6a6', + 'gray66' => 'a8a8a8', + 'gray67' => 'ababab', + 'gray68' => 'adadad', + 'gray69' => 'b0b0b0', + 'gray7' => '121212', + 'gray70' => 'b3b3b3', + 'gray71' => 'b5b5b5', + 'gray72' => 'b8b8b8', + 'gray73' => 'bababa', + 'gray74' => 'bdbdbd', + 'gray75' => 'bfbfbf', + 'gray76' => 'c2c2c2', + 'gray77' => 'c4c4c4', + 'gray78' => 'c7c7c7', + 'gray79' => 'c9c9c9', + 'gray8' => '141414', + 'gray80' => 'cccccc', + 'gray81' => 'cfcfcf', + 'gray82' => 'd1d1d1', + 'gray83' => 'd4d4d4', + 'gray84' => 'd6d6d6', + 'gray85' => 'd9d9d9', + 'gray86' => 'dbdbdb', + 'gray87' => 'dedede', + 'gray88' => 'e0e0e0', + 'gray89' => 'e3e3e3', + 'gray9' => '171717', + 'gray90' => 'e5e5e5', + 'gray91' => 'e8e8e8', + 'gray92' => 'ebebeb', + 'gray93' => 'ededed', + 'gray94' => 'f0f0f0', + 'gray95' => 'f2f2f2', + 'gray97' => 'f7f7f7', + 'gray98' => 'fafafa', + 'gray99' => 'fcfcfc', + 'green' => '00ff00', + 'green1' => '00ff00', + 'green2' => '00ee00', + 'green3' => '00cd00', + 'green4' => '008b00', + 'greenyellow' => 'adff2f', + 'honeydew1' => 'f0fff0', + 'honeydew2' => 'e0eee0', + 'honeydew3' => 'c1cdc1', + 'honeydew4' => '838b83', + 'hotpink' => 'ff69b4', + 'hotpink1' => 'ff6eb4', + 'hotpink2' => 'ee6aa7', + 'hotpink3' => 'cd6090', + 'hotpink4' => '8b3a62', + 'indianred' => 'cd5c5c', + 'indianred1' => 'ff6a6a', + 'indianred2' => 'ee6363', + 'indianred3' => 'cd5555', + 'indianred4' => '8b3a3a', + 'ivory1' => 'fffff0', + 'ivory2' => 'eeeee0', + 'ivory3' => 'cdcdc1', + 'ivory4' => '8b8b83', + 'khaki' => 'f0e68c', + 'khaki1' => 'fff68f', + 'khaki2' => 'eee685', + 'khaki3' => 'cdc673', + 'khaki4' => '8b864e', + 'lavender' => 'e6e6fa', + 'lavenderblush1' => 'fff0f5', + 'lavenderblush2' => 'eee0e5', + 'lavenderblush3' => 'cdc1c5', + 'lavenderblush4' => '8b8386', + 'lawngreen' => '7cfc00', + 'lemonchiffon1' => 'fffacd', + 'lemonchiffon2' => 'eee9bf', + 'lemonchiffon3' => 'cdc9a5', + 'lemonchiffon4' => '8b8970', + 'light' => 'eedd82', + 'lightblue' => 'add8e6', + 'lightblue1' => 'bfefff', + 'lightblue2' => 'b2dfee', + 'lightblue3' => '9ac0cd', + 'lightblue4' => '68838b', + 'lightcoral' => 'f08080', + 'lightcyan1' => 'e0ffff', + 'lightcyan2' => 'd1eeee', + 'lightcyan3' => 'b4cdcd', + 'lightcyan4' => '7a8b8b', + 'lightgoldenrod1' => 'ffec8b', + 'lightgoldenrod2' => 'eedc82', + 'lightgoldenrod3' => 'cdbe70', + 'lightgoldenrod4' => '8b814c', + 'lightgoldenrodyellow' => 'fafad2', + 'lightgray' => 'd3d3d3', + 'lightpink' => 'ffb6c1', + 'lightpink1' => 'ffaeb9', + 'lightpink2' => 'eea2ad', + 'lightpink3' => 'cd8c95', + 'lightpink4' => '8b5f65', + 'lightsalmon1' => 'ffa07a', + 'lightsalmon2' => 'ee9572', + 'lightsalmon3' => 'cd8162', + 'lightsalmon4' => '8b5742', + 'lightseagreen' => '20b2aa', + 'lightskyblue' => '87cefa', + 'lightskyblue1' => 'b0e2ff', + 'lightskyblue2' => 'a4d3ee', + 'lightskyblue3' => '8db6cd', + 'lightskyblue4' => '607b8b', + 'lightslateblue' => '8470ff', + 'lightslategray' => '778899', + 'lightsteelblue' => 'b0c4de', + 'lightsteelblue1' => 'cae1ff', + 'lightsteelblue2' => 'bcd2ee', + 'lightsteelblue3' => 'a2b5cd', + 'lightsteelblue4' => '6e7b8b', + 'lightyellow1' => 'ffffe0', + 'lightyellow2' => 'eeeed1', + 'lightyellow3' => 'cdcdb4', + 'lightyellow4' => '8b8b7a', + 'lime' => '00ff00', + 'limegreen' => '32cd32', + 'linen' => 'faf0e6', + 'magenta' => 'ff00ff', + 'magenta2' => 'ee00ee', + 'magenta3' => 'cd00cd', + 'magenta4' => '8b008b', + 'maroon' => 'b03060', + 'maroon1' => 'ff34b3', + 'maroon2' => 'ee30a7', + 'maroon3' => 'cd2990', + 'maroon4' => '8b1c62', + 'medium' => '66cdaa', + 'mediumaquamarine' => '66cdaa', + 'mediumblue' => '0000cd', + 'mediumorchid' => 'ba55d3', + 'mediumorchid1' => 'e066ff', + 'mediumorchid2' => 'd15fee', + 'mediumorchid3' => 'b452cd', + 'mediumorchid4' => '7a378b', + 'mediumpurple' => '9370db', + 'mediumpurple1' => 'ab82ff', + 'mediumpurple2' => '9f79ee', + 'mediumpurple3' => '8968cd', + 'mediumpurple4' => '5d478b', + 'mediumseagreen' => '3cb371', + 'mediumslateblue' => '7b68ee', + 'mediumspringgreen' => '00fa9a', + 'mediumturquoise' => '48d1cc', + 'mediumvioletred' => 'c71585', + 'midnightblue' => '191970', + 'mintcream' => 'f5fffa', + 'mistyrose1' => 'ffe4e1', + 'mistyrose2' => 'eed5d2', + 'mistyrose3' => 'cdb7b5', + 'mistyrose4' => '8b7d7b', + 'moccasin' => 'ffe4b5', + 'navajowhite1' => 'ffdead', + 'navajowhite2' => 'eecfa1', + 'navajowhite3' => 'cdb38b', + 'navajowhite4' => '8b795e', + 'navy' => '000080', + 'navyblue' => '000080', + 'oldlace' => 'fdf5e6', + 'olive' => '808000', + 'olivedrab' => '6b8e23', + 'olivedrab1' => 'c0ff3e', + 'olivedrab2' => 'b3ee3a', + 'olivedrab4' => '698b22', + 'orange' => 'ffa500', + 'orange1' => 'ffa500', + 'orange2' => 'ee9a00', + 'orange3' => 'cd8500', + 'orange4' => '8b5a00', + 'orangered1' => 'ff4500', + 'orangered2' => 'ee4000', + 'orangered3' => 'cd3700', + 'orangered4' => '8b2500', + 'orchid' => 'da70d6', + 'orchid1' => 'ff83fa', + 'orchid2' => 'ee7ae9', + 'orchid3' => 'cd69c9', + 'orchid4' => '8b4789', + 'pale' => 'db7093', + 'palegoldenrod' => 'eee8aa', + 'palegreen' => '98fb98', + 'palegreen1' => '9aff9a', + 'palegreen2' => '90ee90', + 'palegreen3' => '7ccd7c', + 'palegreen4' => '548b54', + 'paleturquoise' => 'afeeee', + 'paleturquoise1' => 'bbffff', + 'paleturquoise2' => 'aeeeee', + 'paleturquoise3' => '96cdcd', + 'paleturquoise4' => '668b8b', + 'palevioletred' => 'db7093', + 'palevioletred1' => 'ff82ab', + 'palevioletred2' => 'ee799f', + 'palevioletred3' => 'cd6889', + 'palevioletred4' => '8b475d', + 'papayawhip' => 'ffefd5', + 'peachpuff1' => 'ffdab9', + 'peachpuff2' => 'eecbad', + 'peachpuff3' => 'cdaf95', + 'peachpuff4' => '8b7765', + 'pink' => 'ffc0cb', + 'pink1' => 'ffb5c5', + 'pink2' => 'eea9b8', + 'pink3' => 'cd919e', + 'pink4' => '8b636c', + 'plum' => 'dda0dd', + 'plum1' => 'ffbbff', + 'plum2' => 'eeaeee', + 'plum3' => 'cd96cd', + 'plum4' => '8b668b', + 'powderblue' => 'b0e0e6', + 'purple' => 'a020f0', + 'rebeccapurple' => '663399', + 'purple1' => '9b30ff', + 'purple2' => '912cee', + 'purple3' => '7d26cd', + 'purple4' => '551a8b', + 'red' => 'ff0000', + 'red1' => 'ff0000', + 'red2' => 'ee0000', + 'red3' => 'cd0000', + 'red4' => '8b0000', + 'rosybrown' => 'bc8f8f', + 'rosybrown1' => 'ffc1c1', + 'rosybrown2' => 'eeb4b4', + 'rosybrown3' => 'cd9b9b', + 'rosybrown4' => '8b6969', + 'royalblue' => '4169e1', + 'royalblue1' => '4876ff', + 'royalblue2' => '436eee', + 'royalblue3' => '3a5fcd', + 'royalblue4' => '27408b', + 'saddlebrown' => '8b4513', + 'salmon' => 'fa8072', + 'salmon1' => 'ff8c69', + 'salmon2' => 'ee8262', + 'salmon3' => 'cd7054', + 'salmon4' => '8b4c39', + 'sandybrown' => 'f4a460', + 'seagreen1' => '54ff9f', + 'seagreen2' => '4eee94', + 'seagreen3' => '43cd80', + 'seagreen4' => '2e8b57', + 'seashell1' => 'fff5ee', + 'seashell2' => 'eee5de', + 'seashell3' => 'cdc5bf', + 'seashell4' => '8b8682', + 'sienna' => 'a0522d', + 'sienna1' => 'ff8247', + 'sienna2' => 'ee7942', + 'sienna3' => 'cd6839', + 'sienna4' => '8b4726', + 'silver' => 'c0c0c0', + 'skyblue' => '87ceeb', + 'skyblue1' => '87ceff', + 'skyblue2' => '7ec0ee', + 'skyblue3' => '6ca6cd', + 'skyblue4' => '4a708b', + 'slateblue' => '6a5acd', + 'slateblue1' => '836fff', + 'slateblue2' => '7a67ee', + 'slateblue3' => '6959cd', + 'slateblue4' => '473c8b', + 'slategray' => '708090', + 'slategray1' => 'c6e2ff', + 'slategray2' => 'b9d3ee', + 'slategray3' => '9fb6cd', + 'slategray4' => '6c7b8b', + 'snow1' => 'fffafa', + 'snow2' => 'eee9e9', + 'snow3' => 'cdc9c9', + 'snow4' => '8b8989', + 'springgreen1' => '00ff7f', + 'springgreen2' => '00ee76', + 'springgreen3' => '00cd66', + 'springgreen4' => '008b45', + 'steelblue' => '4682b4', + 'steelblue1' => '63b8ff', + 'steelblue2' => '5cacee', + 'steelblue3' => '4f94cd', + 'steelblue4' => '36648b', + 'tan' => 'd2b48c', + 'tan1' => 'ffa54f', + 'tan2' => 'ee9a49', + 'tan3' => 'cd853f', + 'tan4' => '8b5a2b', + 'teal' => '008080', + 'thistle' => 'd8bfd8', + 'thistle1' => 'ffe1ff', + 'thistle2' => 'eed2ee', + 'thistle3' => 'cdb5cd', + 'thistle4' => '8b7b8b', + 'tomato1' => 'ff6347', + 'tomato2' => 'ee5c42', + 'tomato3' => 'cd4f39', + 'tomato4' => '8b3626', + 'turquoise' => '40e0d0', + 'turquoise1' => '00f5ff', + 'turquoise2' => '00e5ee', + 'turquoise3' => '00c5cd', + 'turquoise4' => '00868b', + 'violet' => 'ee82ee', + 'violetred' => 'd02090', + 'violetred1' => 'ff3e96', + 'violetred2' => 'ee3a8c', + 'violetred3' => 'cd3278', + 'violetred4' => '8b2252', + 'wheat' => 'f5deb3', + 'wheat1' => 'ffe7ba', + 'wheat2' => 'eed8ae', + 'wheat3' => 'cdba96', + 'wheat4' => '8b7e66', + 'white' => 'ffffff', + 'whitesmoke' => 'f5f5f5', + 'yellow' => 'ffff00', + 'yellow1' => 'ffff00', + 'yellow2' => 'eeee00', + 'yellow3' => 'cdcd00', + 'yellow4' => '8b8b00', + 'yellowgreen' => '9acd32', + ]; + + protected $face; + + protected $size; + + protected $color; + + protected $bold = false; + + protected $italic = false; + + protected $underline = false; + + protected $superscript = false; + + protected $subscript = false; + + protected $strikethrough = false; + + protected $startTagCallbacks = [ + 'font' => 'startFontTag', + 'b' => 'startBoldTag', + 'strong' => 'startBoldTag', + 'i' => 'startItalicTag', + 'em' => 'startItalicTag', + 'u' => 'startUnderlineTag', + 'ins' => 'startUnderlineTag', + 'del' => 'startStrikethruTag', + 'sup' => 'startSuperscriptTag', + 'sub' => 'startSubscriptTag', + ]; + + protected $endTagCallbacks = [ + 'font' => 'endFontTag', + 'b' => 'endBoldTag', + 'strong' => 'endBoldTag', + 'i' => 'endItalicTag', + 'em' => 'endItalicTag', + 'u' => 'endUnderlineTag', + 'ins' => 'endUnderlineTag', + 'del' => 'endStrikethruTag', + 'sup' => 'endSuperscriptTag', + 'sub' => 'endSubscriptTag', + 'br' => 'breakTag', + 'p' => 'breakTag', + 'h1' => 'breakTag', + 'h2' => 'breakTag', + 'h3' => 'breakTag', + 'h4' => 'breakTag', + 'h5' => 'breakTag', + 'h6' => 'breakTag', + ]; + + protected $stack = []; + + protected $stringData = ''; + + /** + * @var RichText + */ + protected $richTextObject; + + protected function initialise(): void + { + $this->face = $this->size = $this->color = null; + $this->bold = $this->italic = $this->underline = $this->superscript = $this->subscript = $this->strikethrough = false; + + $this->stack = []; + + $this->stringData = ''; + } + + /** + * Parse HTML formatting and return the resulting RichText. + * + * @param string $html + * + * @return RichText + */ + public function toRichTextObject($html) + { + $this->initialise(); + + // Create a new DOM object + $dom = new DOMDocument(); + // Load the HTML file into the DOM object + // Note the use of error suppression, because typically this will be an html fragment, so not fully valid markup + $prefix = ''; + @$dom->loadHTML($prefix . $html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); + // Discard excess white space + $dom->preserveWhiteSpace = false; + + $this->richTextObject = new RichText(); + $this->parseElements($dom); + + // Clean any further spurious whitespace + $this->cleanWhitespace(); + + return $this->richTextObject; + } + + protected function cleanWhitespace(): void + { + foreach ($this->richTextObject->getRichTextElements() as $key => $element) { + $text = $element->getText(); + // Trim any leading spaces on the first run + if ($key == 0) { + $text = ltrim($text); + } + // Trim any spaces immediately after a line break + $text = preg_replace('/\n */mu', "\n", $text); + $element->setText($text); + } + } + + protected function buildTextRun(): void + { + $text = $this->stringData; + if (trim($text) === '') { + return; + } + + $richtextRun = $this->richTextObject->createTextRun($this->stringData); + if ($this->face) { + $richtextRun->getFont()->setName($this->face); + } + if ($this->size) { + $richtextRun->getFont()->setSize($this->size); + } + if ($this->color) { + $richtextRun->getFont()->setColor(new Color('ff' . $this->color)); + } + if ($this->bold) { + $richtextRun->getFont()->setBold(true); + } + if ($this->italic) { + $richtextRun->getFont()->setItalic(true); + } + if ($this->underline) { + $richtextRun->getFont()->setUnderline(Font::UNDERLINE_SINGLE); + } + if ($this->superscript) { + $richtextRun->getFont()->setSuperscript(true); + } + if ($this->subscript) { + $richtextRun->getFont()->setSubscript(true); + } + if ($this->strikethrough) { + $richtextRun->getFont()->setStrikethrough(true); + } + $this->stringData = ''; + } + + protected function rgbToColour(string $rgbValue): string + { + preg_match_all('/\d+/', $rgbValue, $values); + foreach ($values[0] as &$value) { + $value = str_pad(dechex($value), 2, '0', STR_PAD_LEFT); + } + + return implode('', $values[0]); + } + + public static function colourNameLookup(string $colorName): string + { + return self::$colourMap[$colorName] ?? ''; + } + + protected function startFontTag($tag): void + { + foreach ($tag->attributes as $attribute) { + $attributeName = strtolower($attribute->name); + $attributeValue = $attribute->value; + + if ($attributeName == 'color') { + if (preg_match('/rgb\s*\(/', $attributeValue)) { + $this->$attributeName = $this->rgbToColour($attributeValue); + } elseif (strpos(trim($attributeValue), '#') === 0) { + $this->$attributeName = ltrim($attributeValue, '#'); + } else { + $this->$attributeName = static::colourNameLookup($attributeValue); + } + } else { + $this->$attributeName = $attributeValue; + } + } + } + + protected function endFontTag(): void + { + $this->face = $this->size = $this->color = null; + } + + protected function startBoldTag(): void + { + $this->bold = true; + } + + protected function endBoldTag(): void + { + $this->bold = false; + } + + protected function startItalicTag(): void + { + $this->italic = true; + } + + protected function endItalicTag(): void + { + $this->italic = false; + } + + protected function startUnderlineTag(): void + { + $this->underline = true; + } + + protected function endUnderlineTag(): void + { + $this->underline = false; + } + + protected function startSubscriptTag(): void + { + $this->subscript = true; + } + + protected function endSubscriptTag(): void + { + $this->subscript = false; + } + + protected function startSuperscriptTag(): void + { + $this->superscript = true; + } + + protected function endSuperscriptTag(): void + { + $this->superscript = false; + } + + protected function startStrikethruTag(): void + { + $this->strikethrough = true; + } + + protected function endStrikethruTag(): void + { + $this->strikethrough = false; + } + + protected function breakTag(): void + { + $this->stringData .= "\n"; + } + + protected function parseTextNode(DOMText $textNode): void + { + $domText = preg_replace( + '/\s+/u', + ' ', + str_replace(["\r", "\n"], ' ', $textNode->nodeValue) + ); + $this->stringData .= $domText; + $this->buildTextRun(); + } + + /** + * @param string $callbackTag + */ + protected function handleCallback(DOMElement $element, $callbackTag, array $callbacks): void + { + if (isset($callbacks[$callbackTag])) { + $elementHandler = $callbacks[$callbackTag]; + if (method_exists($this, $elementHandler)) { + call_user_func([$this, $elementHandler], $element); + } + } + } + + protected function parseElementNode(DOMElement $element): void + { + $callbackTag = strtolower($element->nodeName); + $this->stack[] = $callbackTag; + + $this->handleCallback($element, $callbackTag, $this->startTagCallbacks); + + $this->parseElements($element); + array_pop($this->stack); + + $this->handleCallback($element, $callbackTag, $this->endTagCallbacks); + } + + protected function parseElements(DOMNode $element): void + { + foreach ($element->childNodes as $child) { + if ($child instanceof DOMText) { + $this->parseTextNode($child); + } elseif ($child instanceof DOMElement) { + $this->parseElementNode($child); + } + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Sample.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Sample.php new file mode 100644 index 0000000..257a02a --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Sample.php @@ -0,0 +1,226 @@ +getScriptFilename() === 'index'; + } + + /** + * Return the page title. + * + * @return string + */ + public function getPageTitle() + { + return $this->isIndex() ? 'PHPSpreadsheet' : $this->getScriptFilename(); + } + + /** + * Return the page heading. + * + * @return string + */ + public function getPageHeading() + { + return $this->isIndex() ? '' : '

        ' . str_replace('_', ' ', $this->getScriptFilename()) . '

        '; + } + + /** + * Returns an array of all known samples. + * + * @return string[][] [$name => $path] + */ + public function getSamples() + { + // Populate samples + $baseDir = realpath(__DIR__ . '/../../../samples'); + $directory = new RecursiveDirectoryIterator($baseDir); + $iterator = new RecursiveIteratorIterator($directory); + $regex = new RegexIterator($iterator, '/^.+\.php$/', RecursiveRegexIterator::GET_MATCH); + + $files = []; + foreach ($regex as $file) { + $file = str_replace(str_replace('\\', '/', $baseDir) . '/', '', str_replace('\\', '/', $file[0])); + $info = pathinfo($file); + $category = str_replace('_', ' ', $info['dirname']); + $name = str_replace('_', ' ', preg_replace('/(|\.php)/', '', $info['filename'])); + if (!in_array($category, ['.', 'boostrap', 'templates'])) { + if (!isset($files[$category])) { + $files[$category] = []; + } + $files[$category][$name] = $file; + } + } + + // Sort everything + ksort($files); + foreach ($files as &$f) { + asort($f); + } + + return $files; + } + + /** + * Write documents. + * + * @param string $filename + * @param string[] $writers + */ + public function write(Spreadsheet $spreadsheet, $filename, array $writers = ['Xlsx', 'Xls']): void + { + // Set active sheet index to the first sheet, so Excel opens this as the first sheet + $spreadsheet->setActiveSheetIndex(0); + + // Write documents + foreach ($writers as $writerType) { + $path = $this->getFilename($filename, mb_strtolower($writerType)); + $writer = IOFactory::createWriter($spreadsheet, $writerType); + $callStartTime = microtime(true); + $writer->save($path); + $this->logWrite($writer, $path, $callStartTime); + } + + $this->logEndingNotes(); + } + + protected function isDirOrMkdir(string $folder): bool + { + return \is_dir($folder) || \mkdir($folder); + } + + /** + * Returns the temporary directory and make sure it exists. + * + * @return string + */ + private function getTemporaryFolder() + { + $tempFolder = sys_get_temp_dir() . '/phpspreadsheet'; + if (!$this->isDirOrMkdir($tempFolder)) { + throw new RuntimeException(sprintf('Directory "%s" was not created', $tempFolder)); + } + + return $tempFolder; + } + + /** + * Returns the filename that should be used for sample output. + * + * @param string $filename + * @param string $extension + * + * @return string + */ + public function getFilename($filename, $extension = 'xlsx') + { + $originalExtension = pathinfo($filename, PATHINFO_EXTENSION); + + return $this->getTemporaryFolder() . '/' . str_replace('.' . $originalExtension, '.' . $extension, basename($filename)); + } + + /** + * Return a random temporary file name. + * + * @param string $extension + * + * @return string + */ + public function getTemporaryFilename($extension = 'xlsx') + { + $temporaryFilename = tempnam($this->getTemporaryFolder(), 'phpspreadsheet-'); + unlink($temporaryFilename); + + return $temporaryFilename . '.' . $extension; + } + + public function log($message): void + { + $eol = $this->isCli() ? PHP_EOL : '
        '; + echo date('H:i:s ') . $message . $eol; + } + + /** + * Log ending notes. + */ + public function logEndingNotes(): void + { + // Do not show execution time for index + $this->log('Peak memory usage: ' . (memory_get_peak_usage(true) / 1024 / 1024) . 'MB'); + } + + /** + * Log a line about the write operation. + * + * @param string $path + * @param float $callStartTime + */ + public function logWrite(IWriter $writer, $path, $callStartTime): void + { + $callEndTime = microtime(true); + $callTime = $callEndTime - $callStartTime; + $reflection = new ReflectionClass($writer); + $format = $reflection->getShortName(); + $message = "Write {$format} format to {$path} in " . sprintf('%.4f', $callTime) . ' seconds'; + + $this->log($message); + } + + /** + * Log a line about the read operation. + * + * @param string $format + * @param string $path + * @param float $callStartTime + */ + public function logRead($format, $path, $callStartTime): void + { + $callEndTime = microtime(true); + $callTime = $callEndTime - $callStartTime; + $message = "Read {$format} format from {$path} in " . sprintf('%.4f', $callTime) . ' seconds'; + + $this->log($message); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Size.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Size.php new file mode 100644 index 0000000..12ba4ef --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Size.php @@ -0,0 +1,52 @@ +\d*\.?\d+)(?Ppt|px|em)?$/i'; + + /** + * @var bool + */ + protected $valid; + + /** + * @var string + */ + protected $size = ''; + + /** + * @var string + */ + protected $unit = ''; + + public function __construct(string $size) + { + $this->valid = (bool) preg_match(self::REGEXP_SIZE_VALIDATION, $size, $matches); + if ($this->valid) { + $this->size = $matches['size']; + $this->unit = $matches['unit'] ?? 'pt'; + } + } + + public function valid(): bool + { + return $this->valid; + } + + public function size(): string + { + return $this->size; + } + + public function unit(): string + { + return $this->unit; + } + + public function __toString() + { + return $this->size . $this->unit; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/IComparable.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/IComparable.php new file mode 100644 index 0000000..c215847 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/IComparable.php @@ -0,0 +1,13 @@ + Reader\Xlsx::class, + 'Xls' => Reader\Xls::class, + 'Xml' => Reader\Xml::class, + 'Ods' => Reader\Ods::class, + 'Slk' => Reader\Slk::class, + 'Gnumeric' => Reader\Gnumeric::class, + 'Html' => Reader\Html::class, + 'Csv' => Reader\Csv::class, + ]; + + private static $writers = [ + 'Xls' => Writer\Xls::class, + 'Xlsx' => Writer\Xlsx::class, + 'Ods' => Writer\Ods::class, + 'Csv' => Writer\Csv::class, + 'Html' => Writer\Html::class, + 'Tcpdf' => Writer\Pdf\Tcpdf::class, + 'Dompdf' => Writer\Pdf\Dompdf::class, + 'Mpdf' => Writer\Pdf\Mpdf::class, + ]; + + /** + * Create Writer\IWriter. + */ + public static function createWriter(Spreadsheet $spreadsheet, string $writerType): IWriter + { + if (!isset(self::$writers[$writerType])) { + throw new Writer\Exception("No writer found for type $writerType"); + } + + // Instantiate writer + $className = self::$writers[$writerType]; + + return new $className($spreadsheet); + } + + /** + * Create IReader. + */ + public static function createReader(string $readerType): IReader + { + if (!isset(self::$readers[$readerType])) { + throw new Reader\Exception("No reader found for type $readerType"); + } + + // Instantiate reader + $className = self::$readers[$readerType]; + + return new $className(); + } + + /** + * Loads Spreadsheet from file using automatic Reader\IReader resolution. + * + * @param string $filename The name of the spreadsheet file + */ + public static function load(string $filename, int $flags = 0): Spreadsheet + { + $reader = self::createReaderForFile($filename); + + return $reader->load($filename, $flags); + } + + /** + * Identify file type using automatic IReader resolution. + */ + public static function identify(string $filename): string + { + $reader = self::createReaderForFile($filename); + $className = get_class($reader); + $classType = explode('\\', $className); + unset($reader); + + return array_pop($classType); + } + + /** + * Create Reader\IReader for file using automatic IReader resolution. + */ + public static function createReaderForFile(string $filename): IReader + { + File::assertFile($filename); + + // First, lucky guess by inspecting file extension + $guessedReader = self::getReaderTypeFromExtension($filename); + if ($guessedReader !== null) { + $reader = self::createReader($guessedReader); + + // Let's see if we are lucky + if ($reader->canRead($filename)) { + return $reader; + } + } + + // If we reach here then "lucky guess" didn't give any result + // Try walking through all the options in self::$autoResolveClasses + foreach (self::$readers as $type => $class) { + // Ignore our original guess, we know that won't work + if ($type !== $guessedReader) { + $reader = self::createReader($type); + if ($reader->canRead($filename)) { + return $reader; + } + } + } + + throw new Reader\Exception('Unable to identify a reader for this file'); + } + + /** + * Guess a reader type from the file extension, if any. + */ + private static function getReaderTypeFromExtension(string $filename): ?string + { + $pathinfo = pathinfo($filename); + if (!isset($pathinfo['extension'])) { + return null; + } + + switch (strtolower($pathinfo['extension'])) { + case 'xlsx': // Excel (OfficeOpenXML) Spreadsheet + case 'xlsm': // Excel (OfficeOpenXML) Macro Spreadsheet (macros will be discarded) + case 'xltx': // Excel (OfficeOpenXML) Template + case 'xltm': // Excel (OfficeOpenXML) Macro Template (macros will be discarded) + return 'Xlsx'; + case 'xls': // Excel (BIFF) Spreadsheet + case 'xlt': // Excel (BIFF) Template + return 'Xls'; + case 'ods': // Open/Libre Offic Calc + case 'ots': // Open/Libre Offic Calc Template + return 'Ods'; + case 'slk': + return 'Slk'; + case 'xml': // Excel 2003 SpreadSheetML + return 'Xml'; + case 'gnumeric': + return 'Gnumeric'; + case 'htm': + case 'html': + return 'Html'; + case 'csv': + // Do nothing + // We must not try to use CSV reader since it loads + // all files including Excel files etc. + return null; + default: + return null; + } + } + + /** + * Register a writer with its type and class name. + */ + public static function registerWriter(string $writerType, string $writerClass): void + { + if (!is_a($writerClass, IWriter::class, true)) { + throw new Writer\Exception('Registered writers must implement ' . IWriter::class); + } + + self::$writers[$writerType] = $writerClass; + } + + /** + * Register a reader with its type and class name. + */ + public static function registerReader(string $readerType, string $readerClass): void + { + if (!is_a($readerClass, IReader::class, true)) { + throw new Reader\Exception('Registered readers must implement ' . IReader::class); + } + + self::$readers[$readerType] = $readerClass; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/NamedFormula.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/NamedFormula.php new file mode 100644 index 0000000..500151f --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/NamedFormula.php @@ -0,0 +1,45 @@ +value; + } + + /** + * Set the formula value. + */ + public function setFormula(string $formula): self + { + if (!empty($formula)) { + $this->value = $formula; + } + + return $this; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/NamedRange.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/NamedRange.php new file mode 100644 index 0000000..db9c5f1 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/NamedRange.php @@ -0,0 +1,55 @@ +value; + } + + /** + * Set the range value. + */ + public function setRange(string $range): self + { + if (!empty($range)) { + $this->value = $range; + } + + return $this; + } + + public function getCellsInRange(): array + { + $range = $this->value; + if (substr($range, 0, 1) === '=') { + $range = substr($range, 1); + } + + return Coordinate::extractAllCellReferencesInRange($range); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/BaseReader.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/BaseReader.php new file mode 100644 index 0000000..2ad8e6b --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/BaseReader.php @@ -0,0 +1,168 @@ +readFilter = new DefaultReadFilter(); + } + + public function getReadDataOnly() + { + return $this->readDataOnly; + } + + public function setReadDataOnly($readCellValuesOnly) + { + $this->readDataOnly = (bool) $readCellValuesOnly; + + return $this; + } + + public function getReadEmptyCells() + { + return $this->readEmptyCells; + } + + public function setReadEmptyCells($readEmptyCells) + { + $this->readEmptyCells = (bool) $readEmptyCells; + + return $this; + } + + public function getIncludeCharts() + { + return $this->includeCharts; + } + + public function setIncludeCharts($includeCharts) + { + $this->includeCharts = (bool) $includeCharts; + + return $this; + } + + public function getLoadSheetsOnly() + { + return $this->loadSheetsOnly; + } + + public function setLoadSheetsOnly($sheetList) + { + if ($sheetList === null) { + return $this->setLoadAllSheets(); + } + + $this->loadSheetsOnly = is_array($sheetList) ? $sheetList : [$sheetList]; + + return $this; + } + + public function setLoadAllSheets() + { + $this->loadSheetsOnly = null; + + return $this; + } + + public function getReadFilter() + { + return $this->readFilter; + } + + public function setReadFilter(IReadFilter $readFilter) + { + $this->readFilter = $readFilter; + + return $this; + } + + public function getSecurityScanner() + { + return $this->securityScanner; + } + + protected function processFlags(int $flags): void + { + if (((bool) ($flags & self::LOAD_WITH_CHARTS)) === true) { + $this->setIncludeCharts(true); + } + } + + /** + * Open file for reading. + * + * @param string $filename + */ + protected function openFile($filename): void + { + if ($filename) { + File::assertFile($filename); + + // Open file + $fileHandle = fopen($filename, 'rb'); + } else { + $fileHandle = false; + } + if ($fileHandle !== false) { + $this->fileHandle = $fileHandle; + } else { + throw new ReaderException('Could not open file ' . $filename . ' for reading.'); + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Csv.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Csv.php new file mode 100644 index 0000000..185f064 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Csv.php @@ -0,0 +1,523 @@ +inputEncoding = $encoding; + + return $this; + } + + public function getInputEncoding(): string + { + return $this->inputEncoding; + } + + public function setFallbackEncoding(string $fallbackEncoding): self + { + $this->fallbackEncoding = $fallbackEncoding; + + return $this; + } + + public function getFallbackEncoding(): string + { + return $this->fallbackEncoding; + } + + /** + * Move filepointer past any BOM marker. + */ + protected function skipBOM(): void + { + rewind($this->fileHandle); + + if (fgets($this->fileHandle, self::UTF8_BOM_LEN + 1) !== self::UTF8_BOM) { + rewind($this->fileHandle); + } + } + + /** + * Identify any separator that is explicitly set in the file. + */ + protected function checkSeparator(): void + { + $line = fgets($this->fileHandle); + if ($line === false) { + return; + } + + if ((strlen(trim($line, "\r\n")) == 5) && (stripos($line, 'sep=') === 0)) { + $this->delimiter = substr($line, 4, 1); + + return; + } + + $this->skipBOM(); + } + + /** + * Infer the separator if it isn't explicitly set in the file or specified by the user. + */ + protected function inferSeparator(): void + { + if ($this->delimiter !== null) { + return; + } + + $inferenceEngine = new Delimiter($this->fileHandle, $this->escapeCharacter, $this->enclosure); + + // If number of lines is 0, nothing to infer : fall back to the default + if ($inferenceEngine->linesCounted() === 0) { + $this->delimiter = $inferenceEngine->getDefaultDelimiter(); + $this->skipBOM(); + + return; + } + + $this->delimiter = $inferenceEngine->infer(); + + // If no delimiter could be detected, fall back to the default + if ($this->delimiter === null) { + $this->delimiter = $inferenceEngine->getDefaultDelimiter(); + } + + $this->skipBOM(); + } + + /** + * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns). + */ + public function listWorksheetInfo(string $filename): array + { + // Open file + $this->openFileOrMemory($filename); + $fileHandle = $this->fileHandle; + + // Skip BOM, if any + $this->skipBOM(); + $this->checkSeparator(); + $this->inferSeparator(); + + $worksheetInfo = []; + $worksheetInfo[0]['worksheetName'] = 'Worksheet'; + $worksheetInfo[0]['lastColumnLetter'] = 'A'; + $worksheetInfo[0]['lastColumnIndex'] = 0; + $worksheetInfo[0]['totalRows'] = 0; + $worksheetInfo[0]['totalColumns'] = 0; + + // Loop through each line of the file in turn + $rowData = fgetcsv($fileHandle, 0, $this->delimiter ?? '', $this->enclosure, $this->escapeCharacter); + while (is_array($rowData)) { + ++$worksheetInfo[0]['totalRows']; + $worksheetInfo[0]['lastColumnIndex'] = max($worksheetInfo[0]['lastColumnIndex'], count($rowData) - 1); + $rowData = fgetcsv($fileHandle, 0, $this->delimiter ?? '', $this->enclosure, $this->escapeCharacter); + } + + $worksheetInfo[0]['lastColumnLetter'] = Coordinate::stringFromColumnIndex($worksheetInfo[0]['lastColumnIndex'] + 1); + $worksheetInfo[0]['totalColumns'] = $worksheetInfo[0]['lastColumnIndex'] + 1; + + // Close file + fclose($fileHandle); + + return $worksheetInfo; + } + + /** + * Loads Spreadsheet from file. + * + * @return Spreadsheet + */ + public function load(string $filename, int $flags = 0) + { + $this->processFlags($flags); + + // Create new Spreadsheet + $spreadsheet = new Spreadsheet(); + + // Load into this instance + return $this->loadIntoExisting($filename, $spreadsheet); + } + + private function openFileOrMemory(string $filename): void + { + // Open file + $fhandle = $this->canRead($filename); + if (!$fhandle) { + throw new Exception($filename . ' is an Invalid Spreadsheet file.'); + } + if ($this->inputEncoding === self::GUESS_ENCODING) { + $this->inputEncoding = self::guessEncoding($filename, $this->fallbackEncoding); + } + $this->openFile($filename); + if ($this->inputEncoding !== 'UTF-8') { + fclose($this->fileHandle); + $entireFile = file_get_contents($filename); + $this->fileHandle = fopen('php://memory', 'r+b'); + if ($this->fileHandle !== false && $entireFile !== false) { + $data = StringHelper::convertEncoding($entireFile, 'UTF-8', $this->inputEncoding); + fwrite($this->fileHandle, $data); + $this->skipBOM(); + } + } + } + + private static function setAutoDetect(?string $value): ?string + { + $retVal = null; + if ($value !== null) { + $retVal2 = @ini_set('auto_detect_line_endings', $value); + if (is_string($retVal2)) { + $retVal = $retVal2; + } + } + + return $retVal; + } + + /** + * Loads PhpSpreadsheet from file into PhpSpreadsheet instance. + */ + public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet): Spreadsheet + { + // Deprecated in Php8.1 + $iniset = self::setAutoDetect('1'); + + // Open file + $this->openFileOrMemory($filename); + $fileHandle = $this->fileHandle; + + // Skip BOM, if any + $this->skipBOM(); + $this->checkSeparator(); + $this->inferSeparator(); + + // Create new PhpSpreadsheet object + while ($spreadsheet->getSheetCount() <= $this->sheetIndex) { + $spreadsheet->createSheet(); + } + $sheet = $spreadsheet->setActiveSheetIndex($this->sheetIndex); + + // Set our starting row based on whether we're in contiguous mode or not + $currentRow = 1; + $outRow = 0; + + // Loop through each line of the file in turn + $rowData = fgetcsv($fileHandle, 0, $this->delimiter ?? '', $this->enclosure, $this->escapeCharacter); + $valueBinder = Cell::getValueBinder(); + $preserveBooleanString = method_exists($valueBinder, 'getBooleanConversion') && $valueBinder->getBooleanConversion(); + while (is_array($rowData)) { + $noOutputYet = true; + $columnLetter = 'A'; + foreach ($rowData as $rowDatum) { + $this->convertBoolean($rowDatum, $preserveBooleanString); + if ($rowDatum !== '' && $this->readFilter->readCell($columnLetter, $currentRow)) { + if ($this->contiguous) { + if ($noOutputYet) { + $noOutputYet = false; + ++$outRow; + } + } else { + $outRow = $currentRow; + } + // Set cell value + $sheet->getCell($columnLetter . $outRow)->setValue($rowDatum); + } + ++$columnLetter; + } + $rowData = fgetcsv($fileHandle, 0, $this->delimiter ?? '', $this->enclosure, $this->escapeCharacter); + ++$currentRow; + } + + // Close file + fclose($fileHandle); + + self::setAutoDetect($iniset); + + // Return + return $spreadsheet; + } + + /** + * Convert string true/false to boolean, and null to null-string. + * + * @param mixed $rowDatum + */ + private function convertBoolean(&$rowDatum, bool $preserveBooleanString): void + { + if (is_string($rowDatum) && !$preserveBooleanString) { + if (strcasecmp('true', $rowDatum) === 0) { + $rowDatum = true; + } elseif (strcasecmp('false', $rowDatum) === 0) { + $rowDatum = false; + } + } elseif ($rowDatum === null) { + $rowDatum = ''; + } + } + + public function getDelimiter(): ?string + { + return $this->delimiter; + } + + public function setDelimiter(?string $delimiter): self + { + $this->delimiter = $delimiter; + + return $this; + } + + public function getEnclosure(): string + { + return $this->enclosure; + } + + public function setEnclosure(string $enclosure): self + { + if ($enclosure == '') { + $enclosure = '"'; + } + $this->enclosure = $enclosure; + + return $this; + } + + public function getSheetIndex(): int + { + return $this->sheetIndex; + } + + public function setSheetIndex(int $indexValue): self + { + $this->sheetIndex = $indexValue; + + return $this; + } + + public function setContiguous(bool $contiguous): self + { + $this->contiguous = $contiguous; + + return $this; + } + + public function getContiguous(): bool + { + return $this->contiguous; + } + + public function setEscapeCharacter(string $escapeCharacter): self + { + $this->escapeCharacter = $escapeCharacter; + + return $this; + } + + public function getEscapeCharacter(): string + { + return $this->escapeCharacter; + } + + /** + * Can the current IReader read the file? + */ + public function canRead(string $filename): bool + { + // Check if file exists + try { + $this->openFile($filename); + } catch (ReaderException $e) { + return false; + } + + fclose($this->fileHandle); + + // Trust file extension if any + $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); + if (in_array($extension, ['csv', 'tsv'])) { + return true; + } + + // Attempt to guess mimetype + $type = mime_content_type($filename); + $supportedTypes = [ + 'application/csv', + 'text/csv', + 'text/plain', + 'inode/x-empty', + ]; + + return in_array($type, $supportedTypes, true); + } + + private static function guessEncodingTestNoBom(string &$encoding, string &$contents, string $compare, string $setEncoding): void + { + if ($encoding === '') { + $pos = strpos($contents, $compare); + if ($pos !== false && $pos % strlen($compare) === 0) { + $encoding = $setEncoding; + } + } + } + + private static function guessEncodingNoBom(string $filename): string + { + $encoding = ''; + $contents = file_get_contents($filename); + self::guessEncodingTestNoBom($encoding, $contents, self::UTF32BE_LF, 'UTF-32BE'); + self::guessEncodingTestNoBom($encoding, $contents, self::UTF32LE_LF, 'UTF-32LE'); + self::guessEncodingTestNoBom($encoding, $contents, self::UTF16BE_LF, 'UTF-16BE'); + self::guessEncodingTestNoBom($encoding, $contents, self::UTF16LE_LF, 'UTF-16LE'); + if ($encoding === '' && preg_match('//u', $contents) === 1) { + $encoding = 'UTF-8'; + } + + return $encoding; + } + + private static function guessEncodingTestBom(string &$encoding, string $first4, string $compare, string $setEncoding): void + { + if ($encoding === '') { + if ($compare === substr($first4, 0, strlen($compare))) { + $encoding = $setEncoding; + } + } + } + + private static function guessEncodingBom(string $filename): string + { + $encoding = ''; + $first4 = file_get_contents($filename, false, null, 0, 4); + if ($first4 !== false) { + self::guessEncodingTestBom($encoding, $first4, self::UTF8_BOM, 'UTF-8'); + self::guessEncodingTestBom($encoding, $first4, self::UTF16BE_BOM, 'UTF-16BE'); + self::guessEncodingTestBom($encoding, $first4, self::UTF32BE_BOM, 'UTF-32BE'); + self::guessEncodingTestBom($encoding, $first4, self::UTF32LE_BOM, 'UTF-32LE'); + self::guessEncodingTestBom($encoding, $first4, self::UTF16LE_BOM, 'UTF-16LE'); + } + + return $encoding; + } + + public static function guessEncoding(string $filename, string $dflt = self::DEFAULT_FALLBACK_ENCODING): string + { + $encoding = self::guessEncodingBom($filename); + if ($encoding === '') { + $encoding = self::guessEncodingNoBom($filename); + } + + return ($encoding === '') ? $dflt : $encoding; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Csv/Delimiter.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Csv/Delimiter.php new file mode 100644 index 0000000..fc29895 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Csv/Delimiter.php @@ -0,0 +1,151 @@ +fileHandle = $fileHandle; + $this->escapeCharacter = $escapeCharacter; + $this->enclosure = $enclosure; + + $this->countPotentialDelimiters(); + } + + public function getDefaultDelimiter(): string + { + return self::POTENTIAL_DELIMETERS[0]; + } + + public function linesCounted(): int + { + return $this->numberLines; + } + + protected function countPotentialDelimiters(): void + { + $this->counts = array_fill_keys(self::POTENTIAL_DELIMETERS, []); + $delimiterKeys = array_flip(self::POTENTIAL_DELIMETERS); + + // Count how many times each of the potential delimiters appears in each line + $this->numberLines = 0; + while (($line = $this->getNextLine()) !== false && (++$this->numberLines < 1000)) { + $this->countDelimiterValues($line, $delimiterKeys); + } + } + + protected function countDelimiterValues(string $line, array $delimiterKeys): void + { + $splitString = str_split($line, 1); + if (is_array($splitString)) { + $distribution = array_count_values($splitString); + $countLine = array_intersect_key($distribution, $delimiterKeys); + + foreach (self::POTENTIAL_DELIMETERS as $delimiter) { + $this->counts[$delimiter][] = $countLine[$delimiter] ?? 0; + } + } + } + + public function infer(): ?string + { + // Calculate the mean square deviations for each delimiter + // (ignoring delimiters that haven't been found consistently) + $meanSquareDeviations = []; + $middleIdx = floor(($this->numberLines - 1) / 2); + + foreach (self::POTENTIAL_DELIMETERS as $delimiter) { + $series = $this->counts[$delimiter]; + sort($series); + + $median = ($this->numberLines % 2) + ? $series[$middleIdx] + : ($series[$middleIdx] + $series[$middleIdx + 1]) / 2; + + if ($median === 0) { + continue; + } + + $meanSquareDeviations[$delimiter] = array_reduce( + $series, + function ($sum, $value) use ($median) { + return $sum + ($value - $median) ** 2; + } + ) / count($series); + } + + // ... and pick the delimiter with the smallest mean square deviation + // (in case of ties, the order in potentialDelimiters is respected) + $min = INF; + foreach (self::POTENTIAL_DELIMETERS as $delimiter) { + if (!isset($meanSquareDeviations[$delimiter])) { + continue; + } + + if ($meanSquareDeviations[$delimiter] < $min) { + $min = $meanSquareDeviations[$delimiter]; + $this->delimiter = $delimiter; + } + } + + return $this->delimiter; + } + + /** + * Get the next full line from the file. + * + * @return false|string + */ + public function getNextLine() + { + $line = ''; + $enclosure = ($this->escapeCharacter === '' ? '' + : ('(?escapeCharacter, '/') . ')')) + . preg_quote($this->enclosure, '/'); + + do { + // Get the next line in the file + $newLine = fgets($this->fileHandle); + + // Return false if there is no next line + if ($newLine === false) { + return false; + } + + // Add the new line to the line passed in + $line = $line . $newLine; + + // Drop everything that is enclosed to avoid counting false positives in enclosures + $line = preg_replace('/(' . $enclosure . '.*' . $enclosure . ')/Us', '', $line); + + // See if we have any enclosures left in the line + // if we still have an enclosure then we need to read the next line as well + } while (preg_match('/(' . $enclosure . ')/', $line ?? '') > 0); + + return $line ?? false; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/DefaultReadFilter.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/DefaultReadFilter.php new file mode 100644 index 0000000..8fdb162 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/DefaultReadFilter.php @@ -0,0 +1,20 @@ + [ + '10' => DataType::TYPE_NULL, + '20' => DataType::TYPE_BOOL, + '30' => DataType::TYPE_NUMERIC, // Integer doesn't exist in Excel + '40' => DataType::TYPE_NUMERIC, // Float + '50' => DataType::TYPE_ERROR, + '60' => DataType::TYPE_STRING, + //'70': // Cell Range + //'80': // Array + ], + ]; + + /** + * Create a new Gnumeric. + */ + public function __construct() + { + parent::__construct(); + $this->referenceHelper = ReferenceHelper::getInstance(); + $this->securityScanner = XmlScanner::getInstance($this); + } + + /** + * Can the current IReader read the file? + */ + public function canRead(string $filename): bool + { + // Check if gzlib functions are available + if (File::testFileNoThrow($filename) && function_exists('gzread')) { + // Read signature data (first 3 bytes) + $fh = fopen($filename, 'rb'); + if ($fh !== false) { + $data = fread($fh, 2); + fclose($fh); + } + } + + return isset($data) && $data === chr(0x1F) . chr(0x8B); + } + + private static function matchXml(XMLReader $xml, string $expectedLocalName): bool + { + return $xml->namespaceURI === self::NAMESPACE_GNM + && $xml->localName === $expectedLocalName + && $xml->nodeType === XMLReader::ELEMENT; + } + + /** + * Reads names of the worksheets from a file, without parsing the whole file to a Spreadsheet object. + * + * @param string $filename + * + * @return array + */ + public function listWorksheetNames($filename) + { + File::assertFile($filename); + + $xml = new XMLReader(); + $xml->xml($this->securityScanner->scanFile('compress.zlib://' . realpath($filename)), null, Settings::getLibXmlLoaderOptions()); + $xml->setParserProperty(2, true); + + $worksheetNames = []; + while ($xml->read()) { + if (self::matchXml($xml, 'SheetName')) { + $xml->read(); // Move onto the value node + $worksheetNames[] = (string) $xml->value; + } elseif (self::matchXml($xml, 'Sheets')) { + // break out of the loop once we've got our sheet names rather than parse the entire file + break; + } + } + + return $worksheetNames; + } + + /** + * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns). + * + * @param string $filename + * + * @return array + */ + public function listWorksheetInfo($filename) + { + File::assertFile($filename); + + $xml = new XMLReader(); + $xml->xml($this->securityScanner->scanFile('compress.zlib://' . realpath($filename)), null, Settings::getLibXmlLoaderOptions()); + $xml->setParserProperty(2, true); + + $worksheetInfo = []; + while ($xml->read()) { + if (self::matchXml($xml, 'Sheet')) { + $tmpInfo = [ + 'worksheetName' => '', + 'lastColumnLetter' => 'A', + 'lastColumnIndex' => 0, + 'totalRows' => 0, + 'totalColumns' => 0, + ]; + + while ($xml->read()) { + if (self::matchXml($xml, 'Name')) { + $xml->read(); // Move onto the value node + $tmpInfo['worksheetName'] = (string) $xml->value; + } elseif (self::matchXml($xml, 'MaxCol')) { + $xml->read(); // Move onto the value node + $tmpInfo['lastColumnIndex'] = (int) $xml->value; + $tmpInfo['totalColumns'] = (int) $xml->value + 1; + } elseif (self::matchXml($xml, 'MaxRow')) { + $xml->read(); // Move onto the value node + $tmpInfo['totalRows'] = (int) $xml->value + 1; + + break; + } + } + $tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1); + $worksheetInfo[] = $tmpInfo; + } + } + + return $worksheetInfo; + } + + /** + * @param string $filename + * + * @return string + */ + private function gzfileGetContents($filename) + { + $file = @gzopen($filename, 'rb'); + $data = ''; + if ($file !== false) { + while (!gzeof($file)) { + $data .= gzread($file, 1024); + } + gzclose($file); + } + + return $data; + } + + public static function gnumericMappings(): array + { + return array_merge(self::$mappings, Styles::$mappings); + } + + private function processComments(SimpleXMLElement $sheet): void + { + if ((!$this->readDataOnly) && (isset($sheet->Objects))) { + foreach ($sheet->Objects->children(self::NAMESPACE_GNM) as $key => $comment) { + $commentAttributes = $comment->attributes(); + // Only comment objects are handled at the moment + if ($commentAttributes && $commentAttributes->Text) { + $this->spreadsheet->getActiveSheet()->getComment((string) $commentAttributes->ObjectBound) + ->setAuthor((string) $commentAttributes->Author) + ->setText($this->parseRichText((string) $commentAttributes->Text)); + } + } + } + } + + /** + * @param mixed $value + */ + private static function testSimpleXml($value): SimpleXMLElement + { + return ($value instanceof SimpleXMLElement) ? $value : new SimpleXMLElement(''); + } + + /** + * Loads Spreadsheet from file. + * + * @return Spreadsheet + */ + public function load(string $filename, int $flags = 0) + { + $this->processFlags($flags); + + // Create new Spreadsheet + $spreadsheet = new Spreadsheet(); + $spreadsheet->removeSheetByIndex(0); + + // Load into this instance + return $this->loadIntoExisting($filename, $spreadsheet); + } + + /** + * Loads from file into Spreadsheet instance. + */ + public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet): Spreadsheet + { + $this->spreadsheet = $spreadsheet; + File::assertFile($filename); + + $gFileData = $this->gzfileGetContents($filename); + + $xml2 = simplexml_load_string($this->securityScanner->scan($gFileData), 'SimpleXMLElement', Settings::getLibXmlLoaderOptions()); + $xml = self::testSimpleXml($xml2); + + $gnmXML = $xml->children(self::NAMESPACE_GNM); + (new Properties($this->spreadsheet))->readProperties($xml, $gnmXML); + + $worksheetID = 0; + foreach ($gnmXML->Sheets->Sheet as $sheetOrNull) { + $sheet = self::testSimpleXml($sheetOrNull); + $worksheetName = (string) $sheet->Name; + if (is_array($this->loadSheetsOnly) && !in_array($worksheetName, $this->loadSheetsOnly, true)) { + continue; + } + + $maxRow = $maxCol = 0; + + // Create new Worksheet + $this->spreadsheet->createSheet(); + $this->spreadsheet->setActiveSheetIndex($worksheetID); + // Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in formula + // cells... during the load, all formulae should be correct, and we're simply bringing the worksheet + // name in line with the formula, not the reverse + $this->spreadsheet->getActiveSheet()->setTitle($worksheetName, false, false); + + if (!$this->readDataOnly) { + (new PageSetup($this->spreadsheet)) + ->printInformation($sheet) + ->sheetMargins($sheet); + } + + foreach ($sheet->Cells->Cell as $cellOrNull) { + $cell = self::testSimpleXml($cellOrNull); + $cellAttributes = self::testSimpleXml($cell->attributes()); + $row = (int) $cellAttributes->Row + 1; + $column = (int) $cellAttributes->Col; + + if ($row > $maxRow) { + $maxRow = $row; + } + if ($column > $maxCol) { + $maxCol = $column; + } + + $column = Coordinate::stringFromColumnIndex($column + 1); + + // Read cell? + if ($this->getReadFilter() !== null) { + if (!$this->getReadFilter()->readCell($column, $row, $worksheetName)) { + continue; + } + } + + $ValueType = $cellAttributes->ValueType; + $ExprID = (string) $cellAttributes->ExprID; + $type = DataType::TYPE_FORMULA; + if ($ExprID > '') { + if (((string) $cell) > '') { + $this->expressions[$ExprID] = [ + 'column' => $cellAttributes->Col, + 'row' => $cellAttributes->Row, + 'formula' => (string) $cell, + ]; + } else { + $expression = $this->expressions[$ExprID]; + + $cell = $this->referenceHelper->updateFormulaReferences( + $expression['formula'], + 'A1', + $cellAttributes->Col - $expression['column'], + $cellAttributes->Row - $expression['row'], + $worksheetName + ); + } + $type = DataType::TYPE_FORMULA; + } else { + $vtype = (string) $ValueType; + if (array_key_exists($vtype, self::$mappings['dataType'])) { + $type = self::$mappings['dataType'][$vtype]; + } + if ($vtype === '20') { // Boolean + $cell = $cell == 'TRUE'; + } + } + $this->spreadsheet->getActiveSheet()->getCell($column . $row)->setValueExplicit((string) $cell, $type); + } + + if ($sheet->Styles !== null) { + (new Styles($this->spreadsheet, $this->readDataOnly))->read($sheet, $maxRow, $maxCol); + } + + $this->processComments($sheet); + $this->processColumnWidths($sheet, $maxCol); + $this->processRowHeights($sheet, $maxRow); + $this->processMergedCells($sheet); + $this->processAutofilter($sheet); + + ++$worksheetID; + } + + $this->processDefinedNames($gnmXML); + + // Return + return $this->spreadsheet; + } + + private function processMergedCells(?SimpleXMLElement $sheet): void + { + // Handle Merged Cells in this worksheet + if ($sheet !== null && isset($sheet->MergedRegions)) { + foreach ($sheet->MergedRegions->Merge as $mergeCells) { + if (strpos((string) $mergeCells, ':') !== false) { + $this->spreadsheet->getActiveSheet()->mergeCells($mergeCells); + } + } + } + } + + private function processAutofilter(?SimpleXMLElement $sheet): void + { + if ($sheet !== null && isset($sheet->Filters)) { + foreach ($sheet->Filters->Filter as $autofilter) { + if ($autofilter !== null) { + $attributes = $autofilter->attributes(); + if (isset($attributes['Area'])) { + $this->spreadsheet->getActiveSheet()->setAutoFilter((string) $attributes['Area']); + } + } + } + } + } + + private function setColumnWidth(int $whichColumn, float $defaultWidth): void + { + $columnDimension = $this->spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($whichColumn + 1)); + if ($columnDimension !== null) { + $columnDimension->setWidth($defaultWidth); + } + } + + private function setColumnInvisible(int $whichColumn): void + { + $columnDimension = $this->spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($whichColumn + 1)); + if ($columnDimension !== null) { + $columnDimension->setVisible(false); + } + } + + private function processColumnLoop(int $whichColumn, int $maxCol, ?SimpleXMLElement $columnOverride, float $defaultWidth): int + { + $columnOverride = self::testSimpleXml($columnOverride); + $columnAttributes = self::testSimpleXml($columnOverride->attributes()); + $column = $columnAttributes['No']; + $columnWidth = ((float) $columnAttributes['Unit']) / 5.4; + $hidden = (isset($columnAttributes['Hidden'])) && ((string) $columnAttributes['Hidden'] == '1'); + $columnCount = (int) ($columnAttributes['Count'] ?? 1); + while ($whichColumn < $column) { + $this->setColumnWidth($whichColumn, $defaultWidth); + ++$whichColumn; + } + while (($whichColumn < ($column + $columnCount)) && ($whichColumn <= $maxCol)) { + $this->setColumnWidth($whichColumn, $columnWidth); + if ($hidden) { + $this->setColumnInvisible($whichColumn); + } + ++$whichColumn; + } + + return $whichColumn; + } + + private function processColumnWidths(?SimpleXMLElement $sheet, int $maxCol): void + { + if ((!$this->readDataOnly) && $sheet !== null && (isset($sheet->Cols))) { + // Column Widths + $defaultWidth = 0; + $columnAttributes = $sheet->Cols->attributes(); + if ($columnAttributes !== null) { + $defaultWidth = $columnAttributes['DefaultSizePts'] / 5.4; + } + $whichColumn = 0; + foreach ($sheet->Cols->ColInfo as $columnOverride) { + $whichColumn = $this->processColumnLoop($whichColumn, $maxCol, $columnOverride, $defaultWidth); + } + while ($whichColumn <= $maxCol) { + $this->setColumnWidth($whichColumn, $defaultWidth); + ++$whichColumn; + } + } + } + + private function setRowHeight(int $whichRow, float $defaultHeight): void + { + $rowDimension = $this->spreadsheet->getActiveSheet()->getRowDimension($whichRow); + if ($rowDimension !== null) { + $rowDimension->setRowHeight($defaultHeight); + } + } + + private function setRowInvisible(int $whichRow): void + { + $rowDimension = $this->spreadsheet->getActiveSheet()->getRowDimension($whichRow); + if ($rowDimension !== null) { + $rowDimension->setVisible(false); + } + } + + private function processRowLoop(int $whichRow, int $maxRow, ?SimpleXMLElement $rowOverride, float $defaultHeight): int + { + $rowOverride = self::testSimpleXml($rowOverride); + $rowAttributes = self::testSimpleXml($rowOverride->attributes()); + $row = $rowAttributes['No']; + $rowHeight = (float) $rowAttributes['Unit']; + $hidden = (isset($rowAttributes['Hidden'])) && ((string) $rowAttributes['Hidden'] == '1'); + $rowCount = (int) ($rowAttributes['Count'] ?? 1); + while ($whichRow < $row) { + ++$whichRow; + $this->setRowHeight($whichRow, $defaultHeight); + } + while (($whichRow < ($row + $rowCount)) && ($whichRow < $maxRow)) { + ++$whichRow; + $this->setRowHeight($whichRow, $rowHeight); + if ($hidden) { + $this->setRowInvisible($whichRow); + } + } + + return $whichRow; + } + + private function processRowHeights(?SimpleXMLElement $sheet, int $maxRow): void + { + if ((!$this->readDataOnly) && $sheet !== null && (isset($sheet->Rows))) { + // Row Heights + $defaultHeight = 0; + $rowAttributes = $sheet->Rows->attributes(); + if ($rowAttributes !== null) { + $defaultHeight = (float) $rowAttributes['DefaultSizePts']; + } + $whichRow = 0; + + foreach ($sheet->Rows->RowInfo as $rowOverride) { + $whichRow = $this->processRowLoop($whichRow, $maxRow, $rowOverride, $defaultHeight); + } + // never executed, I can't figure out any circumstances + // under which it would be executed, and, even if + // such exist, I'm not convinced this is needed. + //while ($whichRow < $maxRow) { + // ++$whichRow; + // $this->spreadsheet->getActiveSheet()->getRowDimension($whichRow)->setRowHeight($defaultHeight); + //} + } + } + + private function processDefinedNames(?SimpleXMLElement $gnmXML): void + { + // Loop through definedNames (global named ranges) + if ($gnmXML !== null && isset($gnmXML->Names)) { + foreach ($gnmXML->Names->Name as $definedName) { + $name = (string) $definedName->name; + $value = (string) $definedName->value; + if (stripos($value, '#REF!') !== false) { + continue; + } + + [$worksheetName] = Worksheet::extractSheetTitle($value, true); + $worksheetName = trim($worksheetName, "'"); + $worksheet = $this->spreadsheet->getSheetByName($worksheetName); + // Worksheet might still be null if we're only loading selected sheets rather than the full spreadsheet + if ($worksheet !== null) { + $this->spreadsheet->addDefinedName(DefinedName::createInstance($name, $worksheet, $value)); + } + } + } + } + + private function parseRichText(string $is): RichText + { + $value = new RichText(); + $value->createText($is); + + return $value; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric/PageSetup.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric/PageSetup.php new file mode 100644 index 0000000..5b501e0 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric/PageSetup.php @@ -0,0 +1,142 @@ +spreadsheet = $spreadsheet; + } + + public function printInformation(SimpleXMLElement $sheet): self + { + if (isset($sheet->PrintInformation)) { + $printInformation = $sheet->PrintInformation[0]; + if (!$printInformation) { + return $this; + } + + $scale = (string) $printInformation->Scale->attributes()['percentage']; + $pageOrder = (string) $printInformation->order; + $orientation = (string) $printInformation->orientation; + $horizontalCentered = (string) $printInformation->hcenter->attributes()['value']; + $verticalCentered = (string) $printInformation->vcenter->attributes()['value']; + + $this->spreadsheet->getActiveSheet()->getPageSetup() + ->setPageOrder($pageOrder === 'r_then_d' ? WorksheetPageSetup::PAGEORDER_OVER_THEN_DOWN : WorksheetPageSetup::PAGEORDER_DOWN_THEN_OVER) + ->setScale((int) $scale) + ->setOrientation($orientation ?? WorksheetPageSetup::ORIENTATION_DEFAULT) + ->setHorizontalCentered((bool) $horizontalCentered) + ->setVerticalCentered((bool) $verticalCentered); + } + + return $this; + } + + public function sheetMargins(SimpleXMLElement $sheet): self + { + if (isset($sheet->PrintInformation, $sheet->PrintInformation->Margins)) { + $marginSet = [ + // Default Settings + 'top' => 0.75, + 'header' => 0.3, + 'left' => 0.7, + 'right' => 0.7, + 'bottom' => 0.75, + 'footer' => 0.3, + ]; + + $marginSet = $this->buildMarginSet($sheet, $marginSet); + $this->adjustMargins($marginSet); + } + + return $this; + } + + private function buildMarginSet(SimpleXMLElement $sheet, array $marginSet): array + { + foreach ($sheet->PrintInformation->Margins->children(Gnumeric::NAMESPACE_GNM) as $key => $margin) { + $marginAttributes = $margin->attributes(); + $marginSize = ($marginAttributes['Points']) ?? 72; // Default is 72pt + // Convert value in points to inches + $marginSize = PageMargins::fromPoints((float) $marginSize); + $marginSet[$key] = $marginSize; + } + + return $marginSet; + } + + private function adjustMargins(array $marginSet): void + { + foreach ($marginSet as $key => $marginSize) { + // Gnumeric is quirky in the way it displays the header/footer values: + // header is actually the sum of top and header; footer is actually the sum of bottom and footer + // then top is actually the header value, and bottom is actually the footer value + switch ($key) { + case 'left': + case 'right': + $this->sheetMargin($key, $marginSize); + + break; + case 'top': + $this->sheetMargin($key, $marginSet['header'] ?? 0); + + break; + case 'bottom': + $this->sheetMargin($key, $marginSet['footer'] ?? 0); + + break; + case 'header': + $this->sheetMargin($key, ($marginSet['top'] ?? 0) - $marginSize); + + break; + case 'footer': + $this->sheetMargin($key, ($marginSet['bottom'] ?? 0) - $marginSize); + + break; + } + } + } + + private function sheetMargin(string $key, float $marginSize): void + { + switch ($key) { + case 'top': + $this->spreadsheet->getActiveSheet()->getPageMargins()->setTop($marginSize); + + break; + case 'bottom': + $this->spreadsheet->getActiveSheet()->getPageMargins()->setBottom($marginSize); + + break; + case 'left': + $this->spreadsheet->getActiveSheet()->getPageMargins()->setLeft($marginSize); + + break; + case 'right': + $this->spreadsheet->getActiveSheet()->getPageMargins()->setRight($marginSize); + + break; + case 'header': + $this->spreadsheet->getActiveSheet()->getPageMargins()->setHeader($marginSize); + + break; + case 'footer': + $this->spreadsheet->getActiveSheet()->getPageMargins()->setFooter($marginSize); + + break; + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric/Properties.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric/Properties.php new file mode 100644 index 0000000..23d4067 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric/Properties.php @@ -0,0 +1,164 @@ +spreadsheet = $spreadsheet; + } + + private function docPropertiesOld(SimpleXMLElement $gnmXML): void + { + $docProps = $this->spreadsheet->getProperties(); + foreach ($gnmXML->Summary->Item as $summaryItem) { + $propertyName = $summaryItem->name; + $propertyValue = $summaryItem->{'val-string'}; + switch ($propertyName) { + case 'title': + $docProps->setTitle(trim($propertyValue)); + + break; + case 'comments': + $docProps->setDescription(trim($propertyValue)); + + break; + case 'keywords': + $docProps->setKeywords(trim($propertyValue)); + + break; + case 'category': + $docProps->setCategory(trim($propertyValue)); + + break; + case 'manager': + $docProps->setManager(trim($propertyValue)); + + break; + case 'author': + $docProps->setCreator(trim($propertyValue)); + $docProps->setLastModifiedBy(trim($propertyValue)); + + break; + case 'company': + $docProps->setCompany(trim($propertyValue)); + + break; + } + } + } + + private function docPropertiesDC(SimpleXMLElement $officePropertyDC): void + { + $docProps = $this->spreadsheet->getProperties(); + foreach ($officePropertyDC as $propertyName => $propertyValue) { + $propertyValue = trim((string) $propertyValue); + switch ($propertyName) { + case 'title': + $docProps->setTitle($propertyValue); + + break; + case 'subject': + $docProps->setSubject($propertyValue); + + break; + case 'creator': + $docProps->setCreator($propertyValue); + $docProps->setLastModifiedBy($propertyValue); + + break; + case 'date': + $creationDate = $propertyValue; + $docProps->setModified($creationDate); + + break; + case 'description': + $docProps->setDescription($propertyValue); + + break; + } + } + } + + private function docPropertiesMeta(SimpleXMLElement $officePropertyMeta): void + { + $docProps = $this->spreadsheet->getProperties(); + foreach ($officePropertyMeta as $propertyName => $propertyValue) { + if ($propertyValue !== null) { + $attributes = $propertyValue->attributes(Gnumeric::NAMESPACE_META); + $propertyValue = trim((string) $propertyValue); + switch ($propertyName) { + case 'keyword': + $docProps->setKeywords($propertyValue); + + break; + case 'initial-creator': + $docProps->setCreator($propertyValue); + $docProps->setLastModifiedBy($propertyValue); + + break; + case 'creation-date': + $creationDate = $propertyValue; + $docProps->setCreated($creationDate); + + break; + case 'user-defined': + if ($attributes) { + [, $attrName] = explode(':', (string) $attributes['name']); + $this->userDefinedProperties($attrName, $propertyValue); + } + + break; + } + } + } + } + + private function userDefinedProperties(string $attrName, string $propertyValue): void + { + $docProps = $this->spreadsheet->getProperties(); + switch ($attrName) { + case 'publisher': + $docProps->setCompany($propertyValue); + + break; + case 'category': + $docProps->setCategory($propertyValue); + + break; + case 'manager': + $docProps->setManager($propertyValue); + + break; + } + } + + public function readProperties(SimpleXMLElement $xml, SimpleXMLElement $gnmXML): void + { + $officeXML = $xml->children(Gnumeric::NAMESPACE_OFFICE); + if (!empty($officeXML)) { + $officeDocXML = $officeXML->{'document-meta'}; + $officeDocMetaXML = $officeDocXML->meta; + + foreach ($officeDocMetaXML as $officePropertyData) { + $officePropertyDC = $officePropertyData->children(Gnumeric::NAMESPACE_DC); + $this->docPropertiesDC($officePropertyDC); + + $officePropertyMeta = $officePropertyData->children(Gnumeric::NAMESPACE_META); + $this->docPropertiesMeta($officePropertyMeta); + } + } elseif (isset($gnmXML->Summary)) { + $this->docPropertiesOld($gnmXML); + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric/Styles.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric/Styles.php new file mode 100644 index 0000000..e2e9d56 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric/Styles.php @@ -0,0 +1,278 @@ + [ + '0' => Border::BORDER_NONE, + '1' => Border::BORDER_THIN, + '2' => Border::BORDER_MEDIUM, + '3' => Border::BORDER_SLANTDASHDOT, + '4' => Border::BORDER_DASHED, + '5' => Border::BORDER_THICK, + '6' => Border::BORDER_DOUBLE, + '7' => Border::BORDER_DOTTED, + '8' => Border::BORDER_MEDIUMDASHED, + '9' => Border::BORDER_DASHDOT, + '10' => Border::BORDER_MEDIUMDASHDOT, + '11' => Border::BORDER_DASHDOTDOT, + '12' => Border::BORDER_MEDIUMDASHDOTDOT, + '13' => Border::BORDER_MEDIUMDASHDOTDOT, + ], + 'fillType' => [ + '1' => Fill::FILL_SOLID, + '2' => Fill::FILL_PATTERN_DARKGRAY, + '3' => Fill::FILL_PATTERN_MEDIUMGRAY, + '4' => Fill::FILL_PATTERN_LIGHTGRAY, + '5' => Fill::FILL_PATTERN_GRAY125, + '6' => Fill::FILL_PATTERN_GRAY0625, + '7' => Fill::FILL_PATTERN_DARKHORIZONTAL, // horizontal stripe + '8' => Fill::FILL_PATTERN_DARKVERTICAL, // vertical stripe + '9' => Fill::FILL_PATTERN_DARKDOWN, // diagonal stripe + '10' => Fill::FILL_PATTERN_DARKUP, // reverse diagonal stripe + '11' => Fill::FILL_PATTERN_DARKGRID, // diagoanl crosshatch + '12' => Fill::FILL_PATTERN_DARKTRELLIS, // thick diagonal crosshatch + '13' => Fill::FILL_PATTERN_LIGHTHORIZONTAL, + '14' => Fill::FILL_PATTERN_LIGHTVERTICAL, + '15' => Fill::FILL_PATTERN_LIGHTUP, + '16' => Fill::FILL_PATTERN_LIGHTDOWN, + '17' => Fill::FILL_PATTERN_LIGHTGRID, // thin horizontal crosshatch + '18' => Fill::FILL_PATTERN_LIGHTTRELLIS, // thin diagonal crosshatch + ], + 'horizontal' => [ + '1' => Alignment::HORIZONTAL_GENERAL, + '2' => Alignment::HORIZONTAL_LEFT, + '4' => Alignment::HORIZONTAL_RIGHT, + '8' => Alignment::HORIZONTAL_CENTER, + '16' => Alignment::HORIZONTAL_CENTER_CONTINUOUS, + '32' => Alignment::HORIZONTAL_JUSTIFY, + '64' => Alignment::HORIZONTAL_CENTER_CONTINUOUS, + ], + 'underline' => [ + '1' => Font::UNDERLINE_SINGLE, + '2' => Font::UNDERLINE_DOUBLE, + '3' => Font::UNDERLINE_SINGLEACCOUNTING, + '4' => Font::UNDERLINE_DOUBLEACCOUNTING, + ], + 'vertical' => [ + '1' => Alignment::VERTICAL_TOP, + '2' => Alignment::VERTICAL_BOTTOM, + '4' => Alignment::VERTICAL_CENTER, + '8' => Alignment::VERTICAL_JUSTIFY, + ], + ]; + + public function __construct(Spreadsheet $spreadsheet, bool $readDataOnly) + { + $this->spreadsheet = $spreadsheet; + $this->readDataOnly = $readDataOnly; + } + + public function read(SimpleXMLElement $sheet, int $maxRow, int $maxCol): void + { + if ($sheet->Styles->StyleRegion !== null) { + $this->readStyles($sheet->Styles->StyleRegion, $maxRow, $maxCol); + } + } + + private function readStyles(SimpleXMLElement $styleRegion, int $maxRow, int $maxCol): void + { + foreach ($styleRegion as $style) { + $styleAttributes = $style->attributes(); + if ($styleAttributes !== null && ($styleAttributes['startRow'] <= $maxRow) && ($styleAttributes['startCol'] <= $maxCol)) { + $cellRange = $this->readStyleRange($styleAttributes, $maxCol, $maxRow); + + $styleAttributes = $style->Style->attributes(); + + $styleArray = []; + // We still set the number format mask for date/time values, even if readDataOnly is true + // so that we can identify whether a float is a float or a date value + $formatCode = $styleAttributes ? (string) $styleAttributes['Format'] : null; + if ($formatCode && Date::isDateTimeFormatCode($formatCode)) { + $styleArray['numberFormat']['formatCode'] = $formatCode; + } + if ($this->readDataOnly === false && $styleAttributes !== null) { + // If readDataOnly is false, we set all formatting information + $styleArray['numberFormat']['formatCode'] = $formatCode; + $styleArray = $this->readStyle($styleArray, $styleAttributes, $style); + } + $this->spreadsheet->getActiveSheet()->getStyle($cellRange)->applyFromArray($styleArray); + } + } + } + + private function addBorderDiagonal(SimpleXMLElement $srssb, array &$styleArray): void + { + if (isset($srssb->Diagonal, $srssb->{'Rev-Diagonal'})) { + $styleArray['borders']['diagonal'] = self::parseBorderAttributes($srssb->Diagonal->attributes()); + $styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_BOTH; + } elseif (isset($srssb->Diagonal)) { + $styleArray['borders']['diagonal'] = self::parseBorderAttributes($srssb->Diagonal->attributes()); + $styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_UP; + } elseif (isset($srssb->{'Rev-Diagonal'})) { + $styleArray['borders']['diagonal'] = self::parseBorderAttributes($srssb->{'Rev-Diagonal'}->attributes()); + $styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_DOWN; + } + } + + private function addBorderStyle(SimpleXMLElement $srssb, array &$styleArray, string $direction): void + { + $ucDirection = ucfirst($direction); + if (isset($srssb->$ucDirection)) { + $styleArray['borders'][$direction] = self::parseBorderAttributes($srssb->$ucDirection->attributes()); + } + } + + private function calcRotation(SimpleXMLElement $styleAttributes): int + { + $rotation = (int) $styleAttributes->Rotation; + if ($rotation >= 270 && $rotation <= 360) { + $rotation -= 360; + } + $rotation = (abs($rotation) > 90) ? 0 : $rotation; + + return $rotation; + } + + private static function addStyle(array &$styleArray, string $key, string $value): void + { + if (array_key_exists($value, self::$mappings[$key])) { + $styleArray[$key] = self::$mappings[$key][$value]; + } + } + + private static function addStyle2(array &$styleArray, string $key1, string $key, string $value): void + { + if (array_key_exists($value, self::$mappings[$key])) { + $styleArray[$key1][$key] = self::$mappings[$key][$value]; + } + } + + private static function parseBorderAttributes(?SimpleXMLElement $borderAttributes): array + { + $styleArray = []; + if ($borderAttributes !== null) { + if (isset($borderAttributes['Color'])) { + $styleArray['color']['rgb'] = self::parseGnumericColour($borderAttributes['Color']); + } + + self::addStyle($styleArray, 'borderStyle', (string) $borderAttributes['Style']); + } + + return $styleArray; + } + + private static function parseGnumericColour(string $gnmColour): string + { + [$gnmR, $gnmG, $gnmB] = explode(':', $gnmColour); + $gnmR = substr(str_pad($gnmR, 4, '0', STR_PAD_RIGHT), 0, 2); + $gnmG = substr(str_pad($gnmG, 4, '0', STR_PAD_RIGHT), 0, 2); + $gnmB = substr(str_pad($gnmB, 4, '0', STR_PAD_RIGHT), 0, 2); + + return $gnmR . $gnmG . $gnmB; + } + + private function addColors(array &$styleArray, SimpleXMLElement $styleAttributes): void + { + $RGB = self::parseGnumericColour((string) $styleAttributes['Fore']); + $styleArray['font']['color']['rgb'] = $RGB; + $RGB = self::parseGnumericColour((string) $styleAttributes['Back']); + $shade = (string) $styleAttributes['Shade']; + if (($RGB !== '000000') || ($shade !== '0')) { + $RGB2 = self::parseGnumericColour((string) $styleAttributes['PatternColor']); + if ($shade === '1') { + $styleArray['fill']['startColor']['rgb'] = $RGB; + $styleArray['fill']['endColor']['rgb'] = $RGB2; + } else { + $styleArray['fill']['endColor']['rgb'] = $RGB; + $styleArray['fill']['startColor']['rgb'] = $RGB2; + } + self::addStyle2($styleArray, 'fill', 'fillType', $shade); + } + } + + private function readStyleRange(SimpleXMLElement $styleAttributes, int $maxCol, int $maxRow): string + { + $startColumn = Coordinate::stringFromColumnIndex((int) $styleAttributes['startCol'] + 1); + $startRow = $styleAttributes['startRow'] + 1; + + $endColumn = ($styleAttributes['endCol'] > $maxCol) ? $maxCol : (int) $styleAttributes['endCol']; + $endColumn = Coordinate::stringFromColumnIndex($endColumn + 1); + + $endRow = 1 + (($styleAttributes['endRow'] > $maxRow) ? $maxRow : (int) $styleAttributes['endRow']); + $cellRange = $startColumn . $startRow . ':' . $endColumn . $endRow; + + return $cellRange; + } + + private function readStyle(array $styleArray, SimpleXMLElement $styleAttributes, SimpleXMLElement $style): array + { + self::addStyle2($styleArray, 'alignment', 'horizontal', (string) $styleAttributes['HAlign']); + self::addStyle2($styleArray, 'alignment', 'vertical', (string) $styleAttributes['VAlign']); + $styleArray['alignment']['wrapText'] = $styleAttributes['WrapText'] == '1'; + $styleArray['alignment']['textRotation'] = $this->calcRotation($styleAttributes); + $styleArray['alignment']['shrinkToFit'] = $styleAttributes['ShrinkToFit'] == '1'; + $styleArray['alignment']['indent'] = ((int) ($styleAttributes['Indent']) > 0) ? $styleAttributes['indent'] : 0; + + $this->addColors($styleArray, $styleAttributes); + + $fontAttributes = $style->Style->Font->attributes(); + if ($fontAttributes !== null) { + $styleArray['font']['name'] = (string) $style->Style->Font; + $styleArray['font']['size'] = (int) ($fontAttributes['Unit']); + $styleArray['font']['bold'] = $fontAttributes['Bold'] == '1'; + $styleArray['font']['italic'] = $fontAttributes['Italic'] == '1'; + $styleArray['font']['strikethrough'] = $fontAttributes['StrikeThrough'] == '1'; + self::addStyle2($styleArray, 'font', 'underline', (string) $fontAttributes['Underline']); + + switch ($fontAttributes['Script']) { + case '1': + $styleArray['font']['superscript'] = true; + + break; + case '-1': + $styleArray['font']['subscript'] = true; + + break; + } + } + + if (isset($style->Style->StyleBorder)) { + $srssb = $style->Style->StyleBorder; + $this->addBorderStyle($srssb, $styleArray, 'top'); + $this->addBorderStyle($srssb, $styleArray, 'bottom'); + $this->addBorderStyle($srssb, $styleArray, 'left'); + $this->addBorderStyle($srssb, $styleArray, 'right'); + $this->addBorderDiagonal($srssb, $styleArray); + } + if (isset($style->Style->HyperLink)) { + // TO DO + $hyperlink = $style->Style->HyperLink->attributes(); + } + + return $styleArray; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Html.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Html.php new file mode 100644 index 0000000..c37f1c1 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Html.php @@ -0,0 +1,1057 @@ + [ + 'font' => [ + 'bold' => true, + 'size' => 24, + ], + ], // Bold, 24pt + 'h2' => [ + 'font' => [ + 'bold' => true, + 'size' => 18, + ], + ], // Bold, 18pt + 'h3' => [ + 'font' => [ + 'bold' => true, + 'size' => 13.5, + ], + ], // Bold, 13.5pt + 'h4' => [ + 'font' => [ + 'bold' => true, + 'size' => 12, + ], + ], // Bold, 12pt + 'h5' => [ + 'font' => [ + 'bold' => true, + 'size' => 10, + ], + ], // Bold, 10pt + 'h6' => [ + 'font' => [ + 'bold' => true, + 'size' => 7.5, + ], + ], // Bold, 7.5pt + 'a' => [ + 'font' => [ + 'underline' => true, + 'color' => [ + 'argb' => Color::COLOR_BLUE, + ], + ], + ], // Blue underlined + 'hr' => [ + 'borders' => [ + 'bottom' => [ + 'borderStyle' => Border::BORDER_THIN, + 'color' => [ + Color::COLOR_BLACK, + ], + ], + ], + ], // Bottom border + 'strong' => [ + 'font' => [ + 'bold' => true, + ], + ], // Bold + 'b' => [ + 'font' => [ + 'bold' => true, + ], + ], // Bold + 'i' => [ + 'font' => [ + 'italic' => true, + ], + ], // Italic + 'em' => [ + 'font' => [ + 'italic' => true, + ], + ], // Italic + ]; + + /** @var array */ + protected $rowspan = []; + + /** + * Create a new HTML Reader instance. + */ + public function __construct() + { + parent::__construct(); + $this->securityScanner = XmlScanner::getInstance($this); + } + + /** + * Validate that the current file is an HTML file. + */ + public function canRead(string $filename): bool + { + // Check if file exists + try { + $this->openFile($filename); + } catch (Exception $e) { + return false; + } + + $beginning = $this->readBeginning(); + $startWithTag = self::startsWithTag($beginning); + $containsTags = self::containsTags($beginning); + $endsWithTag = self::endsWithTag($this->readEnding()); + + fclose($this->fileHandle); + + return $startWithTag && $containsTags && $endsWithTag; + } + + private function readBeginning(): string + { + fseek($this->fileHandle, 0); + + return (string) fread($this->fileHandle, self::TEST_SAMPLE_SIZE); + } + + private function readEnding(): string + { + $meta = stream_get_meta_data($this->fileHandle); + $filename = $meta['uri']; + + $size = (int) filesize($filename); + if ($size === 0) { + return ''; + } + + $blockSize = self::TEST_SAMPLE_SIZE; + if ($size < $blockSize) { + $blockSize = $size; + } + + fseek($this->fileHandle, $size - $blockSize); + + return (string) fread($this->fileHandle, $blockSize); + } + + private static function startsWithTag(string $data): bool + { + return '<' === substr(trim($data), 0, 1); + } + + private static function endsWithTag(string $data): bool + { + return '>' === substr(trim($data), -1, 1); + } + + private static function containsTags(string $data): bool + { + return strlen($data) !== strlen(strip_tags($data)); + } + + /** + * Loads Spreadsheet from file. + * + * @return Spreadsheet + */ + public function load(string $filename, int $flags = 0) + { + $this->processFlags($flags); + + // Create new Spreadsheet + $spreadsheet = new Spreadsheet(); + + // Load into this instance + return $this->loadIntoExisting($filename, $spreadsheet); + } + + /** + * Set input encoding. + * + * @param string $inputEncoding Input encoding, eg: 'ANSI' + * + * @return $this + * + * @codeCoverageIgnore + * + * @deprecated no use is made of this property + */ + public function setInputEncoding($inputEncoding) + { + $this->inputEncoding = $inputEncoding; + + return $this; + } + + /** + * Get input encoding. + * + * @return string + * + * @codeCoverageIgnore + * + * @deprecated no use is made of this property + */ + public function getInputEncoding() + { + return $this->inputEncoding; + } + + // Data Array used for testing only, should write to Spreadsheet object on completion of tests + + /** @var array */ + protected $dataArray = []; + + /** @var int */ + protected $tableLevel = 0; + + /** @var array */ + protected $nestedColumn = ['A']; + + protected function setTableStartColumn(string $column): string + { + if ($this->tableLevel == 0) { + $column = 'A'; + } + ++$this->tableLevel; + $this->nestedColumn[$this->tableLevel] = $column; + + return $this->nestedColumn[$this->tableLevel]; + } + + protected function getTableStartColumn(): string + { + return $this->nestedColumn[$this->tableLevel]; + } + + protected function releaseTableStartColumn(): string + { + --$this->tableLevel; + + return array_pop($this->nestedColumn); + } + + /** + * Flush cell. + * + * @param string $column + * @param int|string $row + * @param mixed $cellContent + */ + protected function flushCell(Worksheet $sheet, $column, $row, &$cellContent): void + { + if (is_string($cellContent)) { + // Simple String content + if (trim($cellContent) > '') { + // Only actually write it if there's content in the string + // Write to worksheet to be done here... + // ... we return the cell so we can mess about with styles more easily + $sheet->setCellValue($column . $row, $cellContent); + $this->dataArray[$row][$column] = $cellContent; + } + } else { + // We have a Rich Text run + // TODO + $this->dataArray[$row][$column] = 'RICH TEXT: ' . $cellContent; + } + $cellContent = (string) ''; + } + + private function processDomElementBody(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child): void + { + $attributeArray = []; + foreach (($child->attributes ?? []) as $attribute) { + $attributeArray[$attribute->name] = $attribute->value; + } + + if ($child->nodeName === 'body') { + $row = 1; + $column = 'A'; + $cellContent = ''; + $this->tableLevel = 0; + $this->processDomElement($child, $sheet, $row, $column, $cellContent); + } else { + $this->processDomElementTitle($sheet, $row, $column, $cellContent, $child, $attributeArray); + } + } + + private function processDomElementTitle(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void + { + if ($child->nodeName === 'title') { + $this->processDomElement($child, $sheet, $row, $column, $cellContent); + $sheet->setTitle($cellContent, true, true); + $cellContent = ''; + } else { + $this->processDomElementSpanEtc($sheet, $row, $column, $cellContent, $child, $attributeArray); + } + } + + private const SPAN_ETC = ['span', 'div', 'font', 'i', 'em', 'strong', 'b']; + + private function processDomElementSpanEtc(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void + { + if (in_array((string) $child->nodeName, self::SPAN_ETC, true)) { + if (isset($attributeArray['class']) && $attributeArray['class'] === 'comment') { + $sheet->getComment($column . $row) + ->getText() + ->createTextRun($child->textContent); + } else { + $this->processDomElement($child, $sheet, $row, $column, $cellContent); + } + + if (isset($this->formats[$child->nodeName])) { + $sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]); + } + } else { + $this->processDomElementHr($sheet, $row, $column, $cellContent, $child, $attributeArray); + } + } + + private function processDomElementHr(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void + { + if ($child->nodeName === 'hr') { + $this->flushCell($sheet, $column, $row, $cellContent); + ++$row; + if (isset($this->formats[$child->nodeName])) { + $sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]); + } + ++$row; + } + // fall through to br + $this->processDomElementBr($sheet, $row, $column, $cellContent, $child, $attributeArray); + } + + private function processDomElementBr(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void + { + if ($child->nodeName === 'br' || $child->nodeName === 'hr') { + if ($this->tableLevel > 0) { + // If we're inside a table, replace with a \n and set the cell to wrap + $cellContent .= "\n"; + $sheet->getStyle($column . $row)->getAlignment()->setWrapText(true); + } else { + // Otherwise flush our existing content and move the row cursor on + $this->flushCell($sheet, $column, $row, $cellContent); + ++$row; + } + } else { + $this->processDomElementA($sheet, $row, $column, $cellContent, $child, $attributeArray); + } + } + + private function processDomElementA(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void + { + if ($child->nodeName === 'a') { + foreach ($attributeArray as $attributeName => $attributeValue) { + switch ($attributeName) { + case 'href': + $sheet->getCell($column . $row)->getHyperlink()->setUrl($attributeValue); + if (isset($this->formats[$child->nodeName])) { + $sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]); + } + + break; + case 'class': + if ($attributeValue === 'comment-indicator') { + break; // Ignore - it's just a red square. + } + } + } + // no idea why this should be needed + //$cellContent .= ' '; + $this->processDomElement($child, $sheet, $row, $column, $cellContent); + } else { + $this->processDomElementH1Etc($sheet, $row, $column, $cellContent, $child, $attributeArray); + } + } + + private const H1_ETC = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ol', 'ul', 'p']; + + private function processDomElementH1Etc(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void + { + if (in_array((string) $child->nodeName, self::H1_ETC, true)) { + if ($this->tableLevel > 0) { + // If we're inside a table, replace with a \n + $cellContent .= $cellContent ? "\n" : ''; + $sheet->getStyle($column . $row)->getAlignment()->setWrapText(true); + $this->processDomElement($child, $sheet, $row, $column, $cellContent); + } else { + if ($cellContent > '') { + $this->flushCell($sheet, $column, $row, $cellContent); + ++$row; + } + $this->processDomElement($child, $sheet, $row, $column, $cellContent); + $this->flushCell($sheet, $column, $row, $cellContent); + + if (isset($this->formats[$child->nodeName])) { + $sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]); + } + + ++$row; + $column = 'A'; + } + } else { + $this->processDomElementLi($sheet, $row, $column, $cellContent, $child, $attributeArray); + } + } + + private function processDomElementLi(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void + { + if ($child->nodeName === 'li') { + if ($this->tableLevel > 0) { + // If we're inside a table, replace with a \n + $cellContent .= $cellContent ? "\n" : ''; + $this->processDomElement($child, $sheet, $row, $column, $cellContent); + } else { + if ($cellContent > '') { + $this->flushCell($sheet, $column, $row, $cellContent); + } + ++$row; + $this->processDomElement($child, $sheet, $row, $column, $cellContent); + $this->flushCell($sheet, $column, $row, $cellContent); + $column = 'A'; + } + } else { + $this->processDomElementImg($sheet, $row, $column, $cellContent, $child, $attributeArray); + } + } + + private function processDomElementImg(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void + { + if ($child->nodeName === 'img') { + $this->insertImage($sheet, $column, $row, $attributeArray); + } else { + $this->processDomElementTable($sheet, $row, $column, $cellContent, $child, $attributeArray); + } + } + + private function processDomElementTable(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void + { + if ($child->nodeName === 'table') { + $this->flushCell($sheet, $column, $row, $cellContent); + $column = $this->setTableStartColumn($column); + if ($this->tableLevel > 1 && $row > 1) { + --$row; + } + $this->processDomElement($child, $sheet, $row, $column, $cellContent); + $column = $this->releaseTableStartColumn(); + if ($this->tableLevel > 1) { + ++$column; + } else { + ++$row; + } + } else { + $this->processDomElementTr($sheet, $row, $column, $cellContent, $child, $attributeArray); + } + } + + private function processDomElementTr(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void + { + if ($child->nodeName === 'tr') { + $column = $this->getTableStartColumn(); + $cellContent = ''; + $this->processDomElement($child, $sheet, $row, $column, $cellContent); + + if (isset($attributeArray['height'])) { + $sheet->getRowDimension($row)->setRowHeight($attributeArray['height']); + } + + ++$row; + } else { + $this->processDomElementThTdOther($sheet, $row, $column, $cellContent, $child, $attributeArray); + } + } + + private function processDomElementThTdOther(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void + { + if ($child->nodeName !== 'td' && $child->nodeName !== 'th') { + $this->processDomElement($child, $sheet, $row, $column, $cellContent); + } else { + $this->processDomElementThTd($sheet, $row, $column, $cellContent, $child, $attributeArray); + } + } + + private function processDomElementBgcolor(Worksheet $sheet, int $row, string $column, array $attributeArray): void + { + if (isset($attributeArray['bgcolor'])) { + $sheet->getStyle("$column$row")->applyFromArray( + [ + 'fill' => [ + 'fillType' => Fill::FILL_SOLID, + 'color' => ['rgb' => $this->getStyleColor($attributeArray['bgcolor'])], + ], + ] + ); + } + } + + private function processDomElementWidth(Worksheet $sheet, string $column, array $attributeArray): void + { + if (isset($attributeArray['width'])) { + $sheet->getColumnDimension($column)->setWidth((new CssDimension($attributeArray['width']))->width()); + } + } + + private function processDomElementHeight(Worksheet $sheet, int $row, array $attributeArray): void + { + if (isset($attributeArray['height'])) { + $sheet->getRowDimension($row)->setRowHeight((new CssDimension($attributeArray['height']))->height()); + } + } + + private function processDomElementAlign(Worksheet $sheet, int $row, string $column, array $attributeArray): void + { + if (isset($attributeArray['align'])) { + $sheet->getStyle($column . $row)->getAlignment()->setHorizontal($attributeArray['align']); + } + } + + private function processDomElementVAlign(Worksheet $sheet, int $row, string $column, array $attributeArray): void + { + if (isset($attributeArray['valign'])) { + $sheet->getStyle($column . $row)->getAlignment()->setVertical($attributeArray['valign']); + } + } + + private function processDomElementDataFormat(Worksheet $sheet, int $row, string $column, array $attributeArray): void + { + if (isset($attributeArray['data-format'])) { + $sheet->getStyle($column . $row)->getNumberFormat()->setFormatCode($attributeArray['data-format']); + } + } + + private function processDomElementThTd(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void + { + while (isset($this->rowspan[$column . $row])) { + ++$column; + } + $this->processDomElement($child, $sheet, $row, $column, $cellContent); + + // apply inline style + $this->applyInlineStyle($sheet, $row, $column, $attributeArray); + + $this->flushCell($sheet, $column, $row, $cellContent); + + $this->processDomElementBgcolor($sheet, $row, $column, $attributeArray); + $this->processDomElementWidth($sheet, $column, $attributeArray); + $this->processDomElementHeight($sheet, $row, $attributeArray); + $this->processDomElementAlign($sheet, $row, $column, $attributeArray); + $this->processDomElementVAlign($sheet, $row, $column, $attributeArray); + $this->processDomElementDataFormat($sheet, $row, $column, $attributeArray); + + if (isset($attributeArray['rowspan'], $attributeArray['colspan'])) { + //create merging rowspan and colspan + $columnTo = $column; + for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) { + ++$columnTo; + } + $range = $column . $row . ':' . $columnTo . ($row + (int) $attributeArray['rowspan'] - 1); + foreach (Coordinate::extractAllCellReferencesInRange($range) as $value) { + $this->rowspan[$value] = true; + } + $sheet->mergeCells($range); + $column = $columnTo; + } elseif (isset($attributeArray['rowspan'])) { + //create merging rowspan + $range = $column . $row . ':' . $column . ($row + (int) $attributeArray['rowspan'] - 1); + foreach (Coordinate::extractAllCellReferencesInRange($range) as $value) { + $this->rowspan[$value] = true; + } + $sheet->mergeCells($range); + } elseif (isset($attributeArray['colspan'])) { + //create merging colspan + $columnTo = $column; + for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) { + ++$columnTo; + } + $sheet->mergeCells($column . $row . ':' . $columnTo . $row); + $column = $columnTo; + } + + ++$column; + } + + protected function processDomElement(DOMNode $element, Worksheet $sheet, int &$row, string &$column, string &$cellContent): void + { + foreach ($element->childNodes as $child) { + if ($child instanceof DOMText) { + $domText = preg_replace('/\s+/u', ' ', trim($child->nodeValue)); + if (is_string($cellContent)) { + // simply append the text if the cell content is a plain text string + $cellContent .= $domText; + } + // but if we have a rich text run instead, we need to append it correctly + // TODO + } elseif ($child instanceof DOMElement) { + $this->processDomElementBody($sheet, $row, $column, $cellContent, $child); + } + } + } + + /** + * Make sure mb_convert_encoding returns string. + * + * @param mixed $result + */ + private static function ensureString($result): string + { + return is_string($result) ? $result : ''; + } + + /** + * Loads PhpSpreadsheet from file into PhpSpreadsheet instance. + * + * @param string $filename + * + * @return Spreadsheet + */ + public function loadIntoExisting($filename, Spreadsheet $spreadsheet) + { + // Validate + if (!$this->canRead($filename)) { + throw new Exception($filename . ' is an Invalid HTML file.'); + } + + // Create a new DOM object + $dom = new DOMDocument(); + // Reload the HTML file into the DOM object + try { + $convert = mb_convert_encoding($this->securityScanner->scanFile($filename), 'HTML-ENTITIES', 'UTF-8'); + $loaded = $dom->loadHTML(self::ensureString($convert)); + } catch (Throwable $e) { + $loaded = false; + } + if ($loaded === false) { + throw new Exception('Failed to load ' . $filename . ' as a DOM Document', 0, $e ?? null); + } + + return $this->loadDocument($dom, $spreadsheet); + } + + /** + * Spreadsheet from content. + * + * @param string $content + */ + public function loadFromString($content, ?Spreadsheet $spreadsheet = null): Spreadsheet + { + // Create a new DOM object + $dom = new DOMDocument(); + // Reload the HTML file into the DOM object + try { + $convert = mb_convert_encoding($this->securityScanner->scan($content), 'HTML-ENTITIES', 'UTF-8'); + $loaded = $dom->loadHTML(self::ensureString($convert)); + } catch (Throwable $e) { + $loaded = false; + } + if ($loaded === false) { + throw new Exception('Failed to load content as a DOM Document', 0, $e ?? null); + } + + return $this->loadDocument($dom, $spreadsheet ?? new Spreadsheet()); + } + + /** + * Loads PhpSpreadsheet from DOMDocument into PhpSpreadsheet instance. + */ + private function loadDocument(DOMDocument $document, Spreadsheet $spreadsheet): Spreadsheet + { + while ($spreadsheet->getSheetCount() <= $this->sheetIndex) { + $spreadsheet->createSheet(); + } + $spreadsheet->setActiveSheetIndex($this->sheetIndex); + + // Discard white space + $document->preserveWhiteSpace = false; + + $row = 0; + $column = 'A'; + $content = ''; + $this->rowspan = []; + $this->processDomElement($document, $spreadsheet->getActiveSheet(), $row, $column, $content); + + // Return + return $spreadsheet; + } + + /** + * Get sheet index. + * + * @return int + */ + public function getSheetIndex() + { + return $this->sheetIndex; + } + + /** + * Set sheet index. + * + * @param int $sheetIndex Sheet index + * + * @return $this + */ + public function setSheetIndex($sheetIndex) + { + $this->sheetIndex = $sheetIndex; + + return $this; + } + + /** + * Apply inline css inline style. + * + * NOTES : + * Currently only intended for td & th element, + * and only takes 'background-color' and 'color'; property with HEX color + * + * TODO : + * - Implement to other propertie, such as border + * + * @param int $row + * @param string $column + * @param array $attributeArray + */ + private function applyInlineStyle(Worksheet &$sheet, $row, $column, $attributeArray): void + { + if (!isset($attributeArray['style'])) { + return; + } + + if (isset($attributeArray['rowspan'], $attributeArray['colspan'])) { + $columnTo = $column; + for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) { + ++$columnTo; + } + $range = $column . $row . ':' . $columnTo . ($row + (int) $attributeArray['rowspan'] - 1); + $cellStyle = $sheet->getStyle($range); + } elseif (isset($attributeArray['rowspan'])) { + $range = $column . $row . ':' . $column . ($row + (int) $attributeArray['rowspan'] - 1); + $cellStyle = $sheet->getStyle($range); + } elseif (isset($attributeArray['colspan'])) { + $columnTo = $column; + for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) { + ++$columnTo; + } + $range = $column . $row . ':' . $columnTo . $row; + $cellStyle = $sheet->getStyle($range); + } else { + $cellStyle = $sheet->getStyle($column . $row); + } + + // add color styles (background & text) from dom element,currently support : td & th, using ONLY inline css style with RGB color + $styles = explode(';', $attributeArray['style']); + foreach ($styles as $st) { + $value = explode(':', $st); + $styleName = isset($value[0]) ? trim($value[0]) : null; + $styleValue = isset($value[1]) ? trim($value[1]) : null; + $styleValueString = (string) $styleValue; + + if (!$styleName) { + continue; + } + + switch ($styleName) { + case 'background': + case 'background-color': + $styleColor = $this->getStyleColor($styleValueString); + + if (!$styleColor) { + continue 2; + } + + $cellStyle->applyFromArray(['fill' => ['fillType' => Fill::FILL_SOLID, 'color' => ['rgb' => $styleColor]]]); + + break; + case 'color': + $styleColor = $this->getStyleColor($styleValueString); + + if (!$styleColor) { + continue 2; + } + + $cellStyle->applyFromArray(['font' => ['color' => ['rgb' => $styleColor]]]); + + break; + + case 'border': + $this->setBorderStyle($cellStyle, $styleValueString, 'allBorders'); + + break; + + case 'border-top': + $this->setBorderStyle($cellStyle, $styleValueString, 'top'); + + break; + + case 'border-bottom': + $this->setBorderStyle($cellStyle, $styleValueString, 'bottom'); + + break; + + case 'border-left': + $this->setBorderStyle($cellStyle, $styleValueString, 'left'); + + break; + + case 'border-right': + $this->setBorderStyle($cellStyle, $styleValueString, 'right'); + + break; + + case 'font-size': + $cellStyle->getFont()->setSize( + (float) $styleValue + ); + + break; + + case 'font-weight': + if ($styleValue === 'bold' || $styleValue >= 500) { + $cellStyle->getFont()->setBold(true); + } + + break; + + case 'font-style': + if ($styleValue === 'italic') { + $cellStyle->getFont()->setItalic(true); + } + + break; + + case 'font-family': + $cellStyle->getFont()->setName(str_replace('\'', '', $styleValueString)); + + break; + + case 'text-decoration': + switch ($styleValue) { + case 'underline': + $cellStyle->getFont()->setUnderline(Font::UNDERLINE_SINGLE); + + break; + case 'line-through': + $cellStyle->getFont()->setStrikethrough(true); + + break; + } + + break; + + case 'text-align': + $cellStyle->getAlignment()->setHorizontal($styleValueString); + + break; + + case 'vertical-align': + $cellStyle->getAlignment()->setVertical($styleValueString); + + break; + + case 'width': + $sheet->getColumnDimension($column)->setWidth( + (new CssDimension($styleValue ?? ''))->width() + ); + + break; + + case 'height': + $sheet->getRowDimension($row)->setRowHeight( + (new CssDimension($styleValue ?? ''))->height() + ); + + break; + + case 'word-wrap': + $cellStyle->getAlignment()->setWrapText( + $styleValue === 'break-word' + ); + + break; + + case 'text-indent': + $cellStyle->getAlignment()->setIndent( + (int) str_replace(['px'], '', $styleValueString) + ); + + break; + } + } + } + + /** + * Check if has #, so we can get clean hex. + * + * @param mixed $value + * + * @return null|string + */ + public function getStyleColor($value) + { + $value = (string) $value; + if (strpos($value ?? '', '#') === 0) { + return substr($value, 1); + } + + return \PhpOffice\PhpSpreadsheet\Helper\Html::colourNameLookup($value); + } + + /** + * @param string $column + * @param int $row + */ + private function insertImage(Worksheet $sheet, $column, $row, array $attributes): void + { + if (!isset($attributes['src'])) { + return; + } + + $src = urldecode($attributes['src']); + $width = isset($attributes['width']) ? (float) $attributes['width'] : null; + $height = isset($attributes['height']) ? (float) $attributes['height'] : null; + $name = $attributes['alt'] ?? null; + + $drawing = new Drawing(); + $drawing->setPath($src); + $drawing->setWorksheet($sheet); + $drawing->setCoordinates($column . $row); + $drawing->setOffsetX(0); + $drawing->setOffsetY(10); + $drawing->setResizeProportional(true); + + if ($name) { + $drawing->setName($name); + } + + if ($width) { + $drawing->setWidth((int) $width); + } + + if ($height) { + $drawing->setHeight((int) $height); + } + + $sheet->getColumnDimension($column)->setWidth( + $drawing->getWidth() / 6 + ); + + $sheet->getRowDimension($row)->setRowHeight( + $drawing->getHeight() * 0.9 + ); + } + + private const BORDER_MAPPINGS = [ + 'dash-dot' => Border::BORDER_DASHDOT, + 'dash-dot-dot' => Border::BORDER_DASHDOTDOT, + 'dashed' => Border::BORDER_DASHED, + 'dotted' => Border::BORDER_DOTTED, + 'double' => Border::BORDER_DOUBLE, + 'hair' => Border::BORDER_HAIR, + 'medium' => Border::BORDER_MEDIUM, + 'medium-dashed' => Border::BORDER_MEDIUMDASHED, + 'medium-dash-dot' => Border::BORDER_MEDIUMDASHDOT, + 'medium-dash-dot-dot' => Border::BORDER_MEDIUMDASHDOTDOT, + 'none' => Border::BORDER_NONE, + 'slant-dash-dot' => Border::BORDER_SLANTDASHDOT, + 'solid' => Border::BORDER_THIN, + 'thick' => Border::BORDER_THICK, + ]; + + public static function getBorderMappings(): array + { + return self::BORDER_MAPPINGS; + } + + /** + * Map html border style to PhpSpreadsheet border style. + * + * @param string $style + * + * @return null|string + */ + public function getBorderStyle($style) + { + return self::BORDER_MAPPINGS[$style] ?? null; + } + + /** + * @param string $styleValue + * @param string $type + */ + private function setBorderStyle(Style $cellStyle, $styleValue, $type): void + { + if (trim($styleValue) === Border::BORDER_NONE) { + $borderStyle = Border::BORDER_NONE; + $color = null; + } else { + $borderArray = explode(' ', $styleValue); + $borderCount = count($borderArray); + if ($borderCount >= 3) { + $borderStyle = $borderArray[1]; + $color = $borderArray[2]; + } else { + $borderStyle = $borderArray[0]; + $color = $borderArray[1] ?? null; + } + } + + $cellStyle->applyFromArray([ + 'borders' => [ + $type => [ + 'borderStyle' => $this->getBorderStyle($borderStyle), + 'color' => ['rgb' => $this->getStyleColor($color)], + ], + ], + ]); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/IReadFilter.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/IReadFilter.php new file mode 100644 index 0000000..9f68a7f --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/IReadFilter.php @@ -0,0 +1,17 @@ +securityScanner = XmlScanner::getInstance($this); + } + + /** + * Can the current IReader read the file? + */ + public function canRead(string $filename): bool + { + $mimeType = 'UNKNOWN'; + + // Load file + + if (File::testFileNoThrow($filename, '')) { + $zip = new ZipArchive(); + if ($zip->open($filename) === true) { + // check if it is an OOXML archive + $stat = $zip->statName('mimetype'); + if ($stat && ($stat['size'] <= 255)) { + $mimeType = $zip->getFromName($stat['name']); + } elseif ($zip->statName('META-INF/manifest.xml')) { + $xml = simplexml_load_string( + $this->securityScanner->scan($zip->getFromName('META-INF/manifest.xml')), + 'SimpleXMLElement', + Settings::getLibXmlLoaderOptions() + ); + $namespacesContent = $xml->getNamespaces(true); + if (isset($namespacesContent['manifest'])) { + $manifest = $xml->children($namespacesContent['manifest']); + foreach ($manifest as $manifestDataSet) { + $manifestAttributes = $manifestDataSet->attributes($namespacesContent['manifest']); + if ($manifestAttributes && $manifestAttributes->{'full-path'} == '/') { + $mimeType = (string) $manifestAttributes->{'media-type'}; + + break; + } + } + } + } + + $zip->close(); + } + } + + return $mimeType === 'application/vnd.oasis.opendocument.spreadsheet'; + } + + /** + * Reads names of the worksheets from a file, without parsing the whole file to a PhpSpreadsheet object. + * + * @param string $filename + * + * @return string[] + */ + public function listWorksheetNames($filename) + { + File::assertFile($filename, self::INITIAL_FILE); + + $worksheetNames = []; + + $xml = new XMLReader(); + $xml->xml( + $this->securityScanner->scanFile('zip://' . realpath($filename) . '#' . self::INITIAL_FILE), + null, + Settings::getLibXmlLoaderOptions() + ); + $xml->setParserProperty(2, true); + + // Step into the first level of content of the XML + $xml->read(); + while ($xml->read()) { + // Quickly jump through to the office:body node + while ($xml->name !== 'office:body') { + if ($xml->isEmptyElement) { + $xml->read(); + } else { + $xml->next(); + } + } + // Now read each node until we find our first table:table node + while ($xml->read()) { + if ($xml->name == 'table:table' && $xml->nodeType == XMLReader::ELEMENT) { + // Loop through each table:table node reading the table:name attribute for each worksheet name + do { + $worksheetNames[] = $xml->getAttribute('table:name'); + $xml->next(); + } while ($xml->name == 'table:table' && $xml->nodeType == XMLReader::ELEMENT); + } + } + } + + return $worksheetNames; + } + + /** + * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns). + * + * @param string $filename + * + * @return array + */ + public function listWorksheetInfo($filename) + { + File::assertFile($filename, self::INITIAL_FILE); + + $worksheetInfo = []; + + $xml = new XMLReader(); + $xml->xml( + $this->securityScanner->scanFile('zip://' . realpath($filename) . '#' . self::INITIAL_FILE), + null, + Settings::getLibXmlLoaderOptions() + ); + $xml->setParserProperty(2, true); + + // Step into the first level of content of the XML + $xml->read(); + while ($xml->read()) { + // Quickly jump through to the office:body node + while ($xml->name !== 'office:body') { + if ($xml->isEmptyElement) { + $xml->read(); + } else { + $xml->next(); + } + } + // Now read each node until we find our first table:table node + while ($xml->read()) { + if ($xml->name == 'table:table' && $xml->nodeType == XMLReader::ELEMENT) { + $worksheetNames[] = $xml->getAttribute('table:name'); + + $tmpInfo = [ + 'worksheetName' => $xml->getAttribute('table:name'), + 'lastColumnLetter' => 'A', + 'lastColumnIndex' => 0, + 'totalRows' => 0, + 'totalColumns' => 0, + ]; + + // Loop through each child node of the table:table element reading + $currCells = 0; + do { + $xml->read(); + if ($xml->name == 'table:table-row' && $xml->nodeType == XMLReader::ELEMENT) { + $rowspan = $xml->getAttribute('table:number-rows-repeated'); + $rowspan = empty($rowspan) ? 1 : $rowspan; + $tmpInfo['totalRows'] += $rowspan; + $tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells); + $currCells = 0; + // Step into the row + $xml->read(); + do { + $doread = true; + if ($xml->name == 'table:table-cell' && $xml->nodeType == XMLReader::ELEMENT) { + if (!$xml->isEmptyElement) { + ++$currCells; + $xml->next(); + $doread = false; + } + } elseif ($xml->name == 'table:covered-table-cell' && $xml->nodeType == XMLReader::ELEMENT) { + $mergeSize = $xml->getAttribute('table:number-columns-repeated'); + $currCells += (int) $mergeSize; + } + if ($doread) { + $xml->read(); + } + } while ($xml->name != 'table:table-row'); + } + } while ($xml->name != 'table:table'); + + $tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells); + $tmpInfo['lastColumnIndex'] = $tmpInfo['totalColumns'] - 1; + $tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1); + $worksheetInfo[] = $tmpInfo; + } + } + } + + return $worksheetInfo; + } + + /** + * Loads PhpSpreadsheet from file. + * + * @return Spreadsheet + */ + public function load(string $filename, int $flags = 0) + { + $this->processFlags($flags); + + // Create new Spreadsheet + $spreadsheet = new Spreadsheet(); + + // Load into this instance + return $this->loadIntoExisting($filename, $spreadsheet); + } + + /** + * Loads PhpSpreadsheet from file into PhpSpreadsheet instance. + * + * @param string $filename + * + * @return Spreadsheet + */ + public function loadIntoExisting($filename, Spreadsheet $spreadsheet) + { + File::assertFile($filename, self::INITIAL_FILE); + + $zip = new ZipArchive(); + $zip->open($filename); + + // Meta + + $xml = @simplexml_load_string( + $this->securityScanner->scan($zip->getFromName('meta.xml')), + 'SimpleXMLElement', + Settings::getLibXmlLoaderOptions() + ); + if ($xml === false) { + throw new Exception('Unable to read data from {$pFilename}'); + } + + $namespacesMeta = $xml->getNamespaces(true); + + (new DocumentProperties($spreadsheet))->load($xml, $namespacesMeta); + + // Styles + + $dom = new DOMDocument('1.01', 'UTF-8'); + $dom->loadXML( + $this->securityScanner->scan($zip->getFromName('styles.xml')), + Settings::getLibXmlLoaderOptions() + ); + + $pageSettings = new PageSettings($dom); + + // Main Content + + $dom = new DOMDocument('1.01', 'UTF-8'); + $dom->loadXML( + $this->securityScanner->scan($zip->getFromName(self::INITIAL_FILE)), + Settings::getLibXmlLoaderOptions() + ); + + $officeNs = $dom->lookupNamespaceUri('office'); + $tableNs = $dom->lookupNamespaceUri('table'); + $textNs = $dom->lookupNamespaceUri('text'); + $xlinkNs = $dom->lookupNamespaceUri('xlink'); + + $pageSettings->readStyleCrossReferences($dom); + + $autoFilterReader = new AutoFilter($spreadsheet, $tableNs); + $definedNameReader = new DefinedNames($spreadsheet, $tableNs); + + // Content + $spreadsheets = $dom->getElementsByTagNameNS($officeNs, 'body') + ->item(0) + ->getElementsByTagNameNS($officeNs, 'spreadsheet'); + + foreach ($spreadsheets as $workbookData) { + /** @var DOMElement $workbookData */ + $tables = $workbookData->getElementsByTagNameNS($tableNs, 'table'); + + $worksheetID = 0; + foreach ($tables as $worksheetDataSet) { + /** @var DOMElement $worksheetDataSet */ + $worksheetName = $worksheetDataSet->getAttributeNS($tableNs, 'name'); + + // Check loadSheetsOnly + if ( + isset($this->loadSheetsOnly) + && $worksheetName + && !in_array($worksheetName, $this->loadSheetsOnly) + ) { + continue; + } + + $worksheetStyleName = $worksheetDataSet->getAttributeNS($tableNs, 'style-name'); + + // Create sheet + if ($worksheetID > 0) { + $spreadsheet->createSheet(); // First sheet is added by default + } + $spreadsheet->setActiveSheetIndex($worksheetID); + + if ($worksheetName) { + // Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in + // formula cells... during the load, all formulae should be correct, and we're simply + // bringing the worksheet name in line with the formula, not the reverse + $spreadsheet->getActiveSheet()->setTitle((string) $worksheetName, false, false); + } + + // Go through every child of table element + $rowID = 1; + foreach ($worksheetDataSet->childNodes as $childNode) { + /** @var DOMElement $childNode */ + + // Filter elements which are not under the "table" ns + if ($childNode->namespaceURI != $tableNs) { + continue; + } + + $key = $childNode->nodeName; + + // Remove ns from node name + if (strpos($key, ':') !== false) { + $keyChunks = explode(':', $key); + $key = array_pop($keyChunks); + } + + switch ($key) { + case 'table-header-rows': + /// TODO :: Figure this out. This is only a partial implementation I guess. + // ($rowData it's not used at all and I'm not sure that PHPExcel + // has an API for this) + +// foreach ($rowData as $keyRowData => $cellData) { +// $rowData = $cellData; +// break; +// } + break; + case 'table-row': + if ($childNode->hasAttributeNS($tableNs, 'number-rows-repeated')) { + $rowRepeats = (int) $childNode->getAttributeNS($tableNs, 'number-rows-repeated'); + } else { + $rowRepeats = 1; + } + + $columnID = 'A'; + /** @var DOMElement $cellData */ + foreach ($childNode->childNodes as $cellData) { + if ($this->getReadFilter() !== null) { + if (!$this->getReadFilter()->readCell($columnID, $rowID, $worksheetName)) { + ++$columnID; + + continue; + } + } + + // Initialize variables + $formatting = $hyperlink = null; + $hasCalculatedValue = false; + $cellDataFormula = ''; + + if ($cellData->hasAttributeNS($tableNs, 'formula')) { + $cellDataFormula = $cellData->getAttributeNS($tableNs, 'formula'); + $hasCalculatedValue = true; + } + + // Annotations + $annotation = $cellData->getElementsByTagNameNS($officeNs, 'annotation'); + + if ($annotation->length > 0) { + $textNode = $annotation->item(0)->getElementsByTagNameNS($textNs, 'p'); + + if ($textNode->length > 0) { + $text = $this->scanElementForText($textNode->item(0)); + + $spreadsheet->getActiveSheet() + ->getComment($columnID . $rowID) + ->setText($this->parseRichText($text)); +// ->setAuthor( $author ) + } + } + + // Content + + /** @var DOMElement[] $paragraphs */ + $paragraphs = []; + + foreach ($cellData->childNodes as $item) { + /** @var DOMElement $item */ + + // Filter text:p elements + if ($item->nodeName == 'text:p') { + $paragraphs[] = $item; + } + } + + if (count($paragraphs) > 0) { + // Consolidate if there are multiple p records (maybe with spans as well) + $dataArray = []; + + // Text can have multiple text:p and within those, multiple text:span. + // text:p newlines, but text:span does not. + // Also, here we assume there is no text data is span fields are specified, since + // we have no way of knowing proper positioning anyway. + + foreach ($paragraphs as $pData) { + $dataArray[] = $this->scanElementForText($pData); + } + $allCellDataText = implode("\n", $dataArray); + + $type = $cellData->getAttributeNS($officeNs, 'value-type'); + + switch ($type) { + case 'string': + $type = DataType::TYPE_STRING; + $dataValue = $allCellDataText; + + foreach ($paragraphs as $paragraph) { + $link = $paragraph->getElementsByTagNameNS($textNs, 'a'); + if ($link->length > 0) { + $hyperlink = $link->item(0)->getAttributeNS($xlinkNs, 'href'); + } + } + + break; + case 'boolean': + $type = DataType::TYPE_BOOL; + $dataValue = ($allCellDataText == 'TRUE') ? true : false; + + break; + case 'percentage': + $type = DataType::TYPE_NUMERIC; + $dataValue = (float) $cellData->getAttributeNS($officeNs, 'value'); + + // percentage should always be float + //if (floor($dataValue) == $dataValue) { + // $dataValue = (int) $dataValue; + //} + $formatting = NumberFormat::FORMAT_PERCENTAGE_00; + + break; + case 'currency': + $type = DataType::TYPE_NUMERIC; + $dataValue = (float) $cellData->getAttributeNS($officeNs, 'value'); + + if (floor($dataValue) == $dataValue) { + $dataValue = (int) $dataValue; + } + $formatting = NumberFormat::FORMAT_CURRENCY_USD_SIMPLE; + + break; + case 'float': + $type = DataType::TYPE_NUMERIC; + $dataValue = (float) $cellData->getAttributeNS($officeNs, 'value'); + + if (floor($dataValue) == $dataValue) { + if ($dataValue == (int) $dataValue) { + $dataValue = (int) $dataValue; + } + } + + break; + case 'date': + $type = DataType::TYPE_NUMERIC; + $value = $cellData->getAttributeNS($officeNs, 'date-value'); + + $dateObj = new DateTime($value); + [$year, $month, $day, $hour, $minute, $second] = explode( + ' ', + $dateObj->format('Y m d H i s') + ); + + $dataValue = Date::formattedPHPToExcel( + (int) $year, + (int) $month, + (int) $day, + (int) $hour, + (int) $minute, + (int) $second + ); + + if ($dataValue != floor($dataValue)) { + $formatting = NumberFormat::FORMAT_DATE_XLSX15 + . ' ' + . NumberFormat::FORMAT_DATE_TIME4; + } else { + $formatting = NumberFormat::FORMAT_DATE_XLSX15; + } + + break; + case 'time': + $type = DataType::TYPE_NUMERIC; + + $timeValue = $cellData->getAttributeNS($officeNs, 'time-value'); + + $dataValue = Date::PHPToExcel( + strtotime( + '01-01-1970 ' . implode(':', sscanf($timeValue, 'PT%dH%dM%dS') ?? []) + ) + ); + $formatting = NumberFormat::FORMAT_DATE_TIME4; + + break; + default: + $dataValue = null; + } + } else { + $type = DataType::TYPE_NULL; + $dataValue = null; + } + + if ($hasCalculatedValue) { + $type = DataType::TYPE_FORMULA; + $cellDataFormula = substr($cellDataFormula, strpos($cellDataFormula, ':=') + 1); + $cellDataFormula = $this->convertToExcelFormulaValue($cellDataFormula); + } + + if ($cellData->hasAttributeNS($tableNs, 'number-columns-repeated')) { + $colRepeats = (int) $cellData->getAttributeNS($tableNs, 'number-columns-repeated'); + } else { + $colRepeats = 1; + } + + if ($type !== null) { + for ($i = 0; $i < $colRepeats; ++$i) { + if ($i > 0) { + ++$columnID; + } + + if ($type !== DataType::TYPE_NULL) { + for ($rowAdjust = 0; $rowAdjust < $rowRepeats; ++$rowAdjust) { + $rID = $rowID + $rowAdjust; + + $cell = $spreadsheet->getActiveSheet() + ->getCell($columnID . $rID); + + // Set value + if ($hasCalculatedValue) { + $cell->setValueExplicit($cellDataFormula, $type); + } else { + $cell->setValueExplicit($dataValue, $type); + } + + if ($hasCalculatedValue) { + $cell->setCalculatedValue($dataValue); + } + + // Set other properties + if ($formatting !== null) { + $spreadsheet->getActiveSheet() + ->getStyle($columnID . $rID) + ->getNumberFormat() + ->setFormatCode($formatting); + } else { + $spreadsheet->getActiveSheet() + ->getStyle($columnID . $rID) + ->getNumberFormat() + ->setFormatCode(NumberFormat::FORMAT_GENERAL); + } + + if ($hyperlink !== null) { + $cell->getHyperlink() + ->setUrl($hyperlink); + } + } + } + } + } + + // Merged cells + if ( + $cellData->hasAttributeNS($tableNs, 'number-columns-spanned') + || $cellData->hasAttributeNS($tableNs, 'number-rows-spanned') + ) { + if (($type !== DataType::TYPE_NULL) || (!$this->readDataOnly)) { + $columnTo = $columnID; + + if ($cellData->hasAttributeNS($tableNs, 'number-columns-spanned')) { + $columnIndex = Coordinate::columnIndexFromString($columnID); + $columnIndex += (int) $cellData->getAttributeNS($tableNs, 'number-columns-spanned'); + $columnIndex -= 2; + + $columnTo = Coordinate::stringFromColumnIndex($columnIndex + 1); + } + + $rowTo = $rowID; + + if ($cellData->hasAttributeNS($tableNs, 'number-rows-spanned')) { + $rowTo = $rowTo + (int) $cellData->getAttributeNS($tableNs, 'number-rows-spanned') - 1; + } + + $cellRange = $columnID . $rowID . ':' . $columnTo . $rowTo; + $spreadsheet->getActiveSheet()->mergeCells($cellRange); + } + } + + ++$columnID; + } + $rowID += $rowRepeats; + + break; + } + } + $pageSettings->setPrintSettingsForWorksheet($spreadsheet->getActiveSheet(), $worksheetStyleName); + ++$worksheetID; + } + + $autoFilterReader->read($workbookData); + $definedNameReader->read($workbookData); + } + $spreadsheet->setActiveSheetIndex(0); + + if ($zip->locateName('settings.xml') !== false) { + $this->processSettings($zip, $spreadsheet); + } + + // Return + return $spreadsheet; + } + + private function processSettings(ZipArchive $zip, Spreadsheet $spreadsheet): void + { + $dom = new DOMDocument('1.01', 'UTF-8'); + $dom->loadXML( + $this->securityScanner->scan($zip->getFromName('settings.xml')), + Settings::getLibXmlLoaderOptions() + ); + //$xlinkNs = $dom->lookupNamespaceUri('xlink'); + $configNs = $dom->lookupNamespaceUri('config'); + //$oooNs = $dom->lookupNamespaceUri('ooo'); + $officeNs = $dom->lookupNamespaceUri('office'); + $settings = $dom->getElementsByTagNameNS($officeNs, 'settings') + ->item(0); + $this->lookForActiveSheet($settings, $spreadsheet, $configNs); + $this->lookForSelectedCells($settings, $spreadsheet, $configNs); + } + + private function lookForActiveSheet(DOMElement $settings, Spreadsheet $spreadsheet, string $configNs): void + { + /** @var DOMElement $t */ + foreach ($settings->getElementsByTagNameNS($configNs, 'config-item') as $t) { + if ($t->getAttributeNs($configNs, 'name') === 'ActiveTable') { + try { + $spreadsheet->setActiveSheetIndexByName($t->nodeValue); + } catch (Throwable $e) { + // do nothing + } + + break; + } + } + } + + private function lookForSelectedCells(DOMElement $settings, Spreadsheet $spreadsheet, string $configNs): void + { + /** @var DOMElement $t */ + foreach ($settings->getElementsByTagNameNS($configNs, 'config-item-map-named') as $t) { + if ($t->getAttributeNs($configNs, 'name') === 'Tables') { + foreach ($t->getElementsByTagNameNS($configNs, 'config-item-map-entry') as $ws) { + $setRow = $setCol = ''; + $wsname = $ws->getAttributeNs($configNs, 'name'); + foreach ($ws->getElementsByTagNameNS($configNs, 'config-item') as $configItem) { + $attrName = $configItem->getAttributeNs($configNs, 'name'); + if ($attrName === 'CursorPositionX') { + $setCol = $configItem->nodeValue; + } + if ($attrName === 'CursorPositionY') { + $setRow = $configItem->nodeValue; + } + } + $this->setSelected($spreadsheet, $wsname, $setCol, $setRow); + } + + break; + } + } + } + + private function setSelected(Spreadsheet $spreadsheet, string $wsname, string $setCol, string $setRow): void + { + if (is_numeric($setCol) && is_numeric($setRow)) { + try { + $spreadsheet->getSheetByName($wsname)->setSelectedCellByColumnAndRow($setCol + 1, $setRow + 1); + } catch (Throwable $e) { + // do nothing + } + } + } + + /** + * Recursively scan element. + * + * @return string + */ + protected function scanElementForText(DOMNode $element) + { + $str = ''; + foreach ($element->childNodes as $child) { + /** @var DOMNode $child */ + if ($child->nodeType == XML_TEXT_NODE) { + $str .= $child->nodeValue; + } elseif ($child->nodeType == XML_ELEMENT_NODE && $child->nodeName == 'text:s') { + // It's a space + + // Multiple spaces? + /** @var DOMAttr $cAttr */ + $cAttr = $child->attributes->getNamedItem('c'); + if ($cAttr) { + $multiplier = (int) $cAttr->nodeValue; + } else { + $multiplier = 1; + } + + $str .= str_repeat(' ', $multiplier); + } + + if ($child->hasChildNodes()) { + $str .= $this->scanElementForText($child); + } + } + + return $str; + } + + /** + * @param string $is + * + * @return RichText + */ + private function parseRichText($is) + { + $value = new RichText(); + $value->createText($is); + + return $value; + } + + private function convertToExcelFormulaValue(string $openOfficeFormula): string + { + $temp = explode('"', $openOfficeFormula); + $tKey = false; + foreach ($temp as &$value) { + // Only replace in alternate array entries (i.e. non-quoted blocks) + if ($tKey = !$tKey) { + // Cell range reference in another sheet + $value = preg_replace('/\[\$?([^\.]+)\.([^\.]+):\.([^\.]+)\]/miu', '$1!$2:$3', $value); + // Cell reference in another sheet + $value = preg_replace('/\[\$?([^\.]+)\.([^\.]+)\]/miu', '$1!$2', $value ?? ''); + // Cell range reference + $value = preg_replace('/\[\.([^\.]+):\.([^\.]+)\]/miu', '$1:$2', $value ?? ''); + // Simple cell reference + $value = preg_replace('/\[\.([^\.]+)\]/miu', '$1', $value ?? ''); + // Convert references to defined names/formulae + $value = str_replace('$$', '', $value ?? ''); + + $value = Calculation::translateSeparator(';', ',', $value, $inBraces); + } + } + + // Then rebuild the formula string + $excelFormula = implode('"', $temp); + + return $excelFormula; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/AutoFilter.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/AutoFilter.php new file mode 100644 index 0000000..bdc8b3f --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/AutoFilter.php @@ -0,0 +1,45 @@ +readAutoFilters($workbookData); + } + + protected function readAutoFilters(DOMElement $workbookData): void + { + $databases = $workbookData->getElementsByTagNameNS($this->tableNs, 'database-ranges'); + + foreach ($databases as $autofilters) { + foreach ($autofilters->childNodes as $autofilter) { + $autofilterRange = $this->getAttributeValue($autofilter, 'target-range-address'); + if ($autofilterRange !== null) { + $baseAddress = $this->convertToExcelAddressValue($autofilterRange); + $this->spreadsheet->getActiveSheet()->setAutoFilter($baseAddress); + } + } + } + } + + protected function getAttributeValue(?DOMNode $node, string $attributeName): ?string + { + if ($node !== null && $node->attributes !== null) { + $attribute = $node->attributes->getNamedItemNS( + $this->tableNs, + $attributeName + ); + + if ($attribute !== null) { + return $attribute->nodeValue; + } + } + + return null; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/BaseReader.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/BaseReader.php new file mode 100644 index 0000000..17e2d4d --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/BaseReader.php @@ -0,0 +1,77 @@ +spreadsheet = $spreadsheet; + $this->tableNs = $tableNs; + } + + abstract public function read(DOMElement $workbookData): void; + + protected function convertToExcelAddressValue(string $openOfficeAddress): string + { + $excelAddress = $openOfficeAddress; + + // Cell range 3-d reference + // As we don't support 3-d ranges, we're just going to take a quick and dirty approach + // and assume that the second worksheet reference is the same as the first + $excelAddress = preg_replace('/\$?([^\.]+)\.([^\.]+):\$?([^\.]+)\.([^\.]+)/miu', '$1!$2:$4', $excelAddress); + // Cell range reference in another sheet + $excelAddress = preg_replace('/\$?([^\.]+)\.([^\.]+):\.([^\.]+)/miu', '$1!$2:$3', $excelAddress ?? ''); + // Cell reference in another sheet + $excelAddress = preg_replace('/\$?([^\.]+)\.([^\.]+)/miu', '$1!$2', $excelAddress ?? ''); + // Cell range reference + $excelAddress = preg_replace('/\.([^\.]+):\.([^\.]+)/miu', '$1:$2', $excelAddress ?? ''); + // Simple cell reference + $excelAddress = preg_replace('/\.([^\.]+)/miu', '$1', $excelAddress ?? ''); + + return $excelAddress ?? ''; + } + + protected function convertToExcelFormulaValue(string $openOfficeFormula): string + { + $temp = explode('"', $openOfficeFormula); + $tKey = false; + foreach ($temp as &$value) { + // @var string $value + // Only replace in alternate array entries (i.e. non-quoted blocks) + if ($tKey = !$tKey) { + // Cell range reference in another sheet + $value = preg_replace('/\[\$?([^\.]+)\.([^\.]+):\.([^\.]+)\]/miu', '$1!$2:$3', $value); + // Cell reference in another sheet + $value = preg_replace('/\[\$?([^\.]+)\.([^\.]+)\]/miu', '$1!$2', $value ?? ''); + // Cell range reference + $value = preg_replace('/\[\.([^\.]+):\.([^\.]+)\]/miu', '$1:$2', $value ?? ''); + // Simple cell reference + $value = preg_replace('/\[\.([^\.]+)\]/miu', '$1', $value ?? ''); + // Convert references to defined names/formulae + $value = str_replace('$$', '', $value ?? ''); + + $value = Calculation::translateSeparator(';', ',', $value, $inBraces); + } + } + + // Then rebuild the formula string + $excelFormula = implode('"', $temp); + + return $excelFormula; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/DefinedNames.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/DefinedNames.php new file mode 100644 index 0000000..6810a3c --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/DefinedNames.php @@ -0,0 +1,66 @@ +readDefinedRanges($workbookData); + $this->readDefinedExpressions($workbookData); + } + + /** + * Read any Named Ranges that are defined in this spreadsheet. + */ + protected function readDefinedRanges(DOMElement $workbookData): void + { + $namedRanges = $workbookData->getElementsByTagNameNS($this->tableNs, 'named-range'); + foreach ($namedRanges as $definedNameElement) { + $definedName = $definedNameElement->getAttributeNS($this->tableNs, 'name'); + $baseAddress = $definedNameElement->getAttributeNS($this->tableNs, 'base-cell-address'); + $range = $definedNameElement->getAttributeNS($this->tableNs, 'cell-range-address'); + + $baseAddress = $this->convertToExcelAddressValue($baseAddress); + $range = $this->convertToExcelAddressValue($range); + + $this->addDefinedName($baseAddress, $definedName, $range); + } + } + + /** + * Read any Named Formulae that are defined in this spreadsheet. + */ + protected function readDefinedExpressions(DOMElement $workbookData): void + { + $namedExpressions = $workbookData->getElementsByTagNameNS($this->tableNs, 'named-expression'); + foreach ($namedExpressions as $definedNameElement) { + $definedName = $definedNameElement->getAttributeNS($this->tableNs, 'name'); + $baseAddress = $definedNameElement->getAttributeNS($this->tableNs, 'base-cell-address'); + $expression = $definedNameElement->getAttributeNS($this->tableNs, 'expression'); + + $baseAddress = $this->convertToExcelAddressValue($baseAddress); + $expression = substr($expression, strpos($expression, ':=') + 1); + $expression = $this->convertToExcelFormulaValue($expression); + + $this->addDefinedName($baseAddress, $definedName, $expression); + } + } + + /** + * Assess scope and store the Defined Name. + */ + private function addDefinedName(string $baseAddress, string $definedName, string $value): void + { + [$sheetReference] = Worksheet::extractSheetTitle($baseAddress, true); + $worksheet = $this->spreadsheet->getSheetByName($sheetReference); + // Worksheet might still be null if we're only loading selected sheets rather than the full spreadsheet + if ($worksheet !== null) { + $this->spreadsheet->addDefinedName(DefinedName::createInstance((string) $definedName, $worksheet, $value)); + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/PageSettings.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/PageSettings.php new file mode 100644 index 0000000..8d24fd0 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/PageSettings.php @@ -0,0 +1,139 @@ +setDomNameSpaces($styleDom); + $this->readPageSettingStyles($styleDom); + $this->readStyleMasterLookup($styleDom); + } + + private function setDomNameSpaces(DOMDocument $styleDom): void + { + $this->officeNs = $styleDom->lookupNamespaceUri('office'); + $this->stylesNs = $styleDom->lookupNamespaceUri('style'); + $this->stylesFo = $styleDom->lookupNamespaceUri('fo'); + } + + private function readPageSettingStyles(DOMDocument $styleDom): void + { + $styles = $styleDom->getElementsByTagNameNS($this->officeNs, 'automatic-styles') + ->item(0) + ->getElementsByTagNameNS($this->stylesNs, 'page-layout'); + + foreach ($styles as $styleSet) { + $styleName = $styleSet->getAttributeNS($this->stylesNs, 'name'); + $pageLayoutProperties = $styleSet->getElementsByTagNameNS($this->stylesNs, 'page-layout-properties')[0]; + $styleOrientation = $pageLayoutProperties->getAttributeNS($this->stylesNs, 'print-orientation'); + $styleScale = $pageLayoutProperties->getAttributeNS($this->stylesNs, 'scale-to'); + $stylePrintOrder = $pageLayoutProperties->getAttributeNS($this->stylesNs, 'print-page-order'); + $centered = $pageLayoutProperties->getAttributeNS($this->stylesNs, 'table-centering'); + + $marginLeft = $pageLayoutProperties->getAttributeNS($this->stylesFo, 'margin-left'); + $marginRight = $pageLayoutProperties->getAttributeNS($this->stylesFo, 'margin-right'); + $marginTop = $pageLayoutProperties->getAttributeNS($this->stylesFo, 'margin-top'); + $marginBottom = $pageLayoutProperties->getAttributeNS($this->stylesFo, 'margin-bottom'); + $header = $styleSet->getElementsByTagNameNS($this->stylesNs, 'header-style')[0]; + $headerProperties = $header->getElementsByTagNameNS($this->stylesNs, 'header-footer-properties')[0]; + $marginHeader = isset($headerProperties) ? $headerProperties->getAttributeNS($this->stylesFo, 'min-height') : null; + $footer = $styleSet->getElementsByTagNameNS($this->stylesNs, 'footer-style')[0]; + $footerProperties = $footer->getElementsByTagNameNS($this->stylesNs, 'header-footer-properties')[0]; + $marginFooter = isset($footerProperties) ? $footerProperties->getAttributeNS($this->stylesFo, 'min-height') : null; + + $this->pageLayoutStyles[$styleName] = (object) [ + 'orientation' => $styleOrientation ?: PageSetup::ORIENTATION_DEFAULT, + 'scale' => $styleScale ?: 100, + 'printOrder' => $stylePrintOrder, + 'horizontalCentered' => $centered === 'horizontal' || $centered === 'both', + 'verticalCentered' => $centered === 'vertical' || $centered === 'both', + // margin size is already stored in inches, so no UOM conversion is required + 'marginLeft' => (float) $marginLeft ?? 0.7, + 'marginRight' => (float) $marginRight ?? 0.7, + 'marginTop' => (float) $marginTop ?? 0.3, + 'marginBottom' => (float) $marginBottom ?? 0.3, + 'marginHeader' => (float) $marginHeader ?? 0.45, + 'marginFooter' => (float) $marginFooter ?? 0.45, + ]; + } + } + + private function readStyleMasterLookup(DOMDocument $styleDom): void + { + $styleMasterLookup = $styleDom->getElementsByTagNameNS($this->officeNs, 'master-styles') + ->item(0) + ->getElementsByTagNameNS($this->stylesNs, 'master-page'); + + foreach ($styleMasterLookup as $styleMasterSet) { + $styleMasterName = $styleMasterSet->getAttributeNS($this->stylesNs, 'name'); + $pageLayoutName = $styleMasterSet->getAttributeNS($this->stylesNs, 'page-layout-name'); + $this->masterPrintStylesCrossReference[$styleMasterName] = $pageLayoutName; + } + } + + public function readStyleCrossReferences(DOMDocument $contentDom): void + { + $styleXReferences = $contentDom->getElementsByTagNameNS($this->officeNs, 'automatic-styles') + ->item(0) + ->getElementsByTagNameNS($this->stylesNs, 'style'); + + foreach ($styleXReferences as $styleXreferenceSet) { + $styleXRefName = $styleXreferenceSet->getAttributeNS($this->stylesNs, 'name'); + $stylePageLayoutName = $styleXreferenceSet->getAttributeNS($this->stylesNs, 'master-page-name'); + if (!empty($stylePageLayoutName)) { + $this->masterStylesCrossReference[$styleXRefName] = $stylePageLayoutName; + } + } + } + + public function setPrintSettingsForWorksheet(Worksheet $worksheet, string $styleName): void + { + if (!array_key_exists($styleName, $this->masterStylesCrossReference)) { + return; + } + $masterStyleName = $this->masterStylesCrossReference[$styleName]; + + if (!array_key_exists($masterStyleName, $this->masterPrintStylesCrossReference)) { + return; + } + $printSettingsIndex = $this->masterPrintStylesCrossReference[$masterStyleName]; + + if (!array_key_exists($printSettingsIndex, $this->pageLayoutStyles)) { + return; + } + $printSettings = $this->pageLayoutStyles[$printSettingsIndex]; + + $worksheet->getPageSetup() + ->setOrientation($printSettings->orientation ?? PageSetup::ORIENTATION_DEFAULT) + ->setPageOrder($printSettings->printOrder === 'ltr' ? PageSetup::PAGEORDER_OVER_THEN_DOWN : PageSetup::PAGEORDER_DOWN_THEN_OVER) + ->setScale((int) trim($printSettings->scale, '%')) + ->setHorizontalCentered($printSettings->horizontalCentered) + ->setVerticalCentered($printSettings->verticalCentered); + + $worksheet->getPageMargins() + ->setLeft($printSettings->marginLeft) + ->setRight($printSettings->marginRight) + ->setTop($printSettings->marginTop) + ->setBottom($printSettings->marginBottom) + ->setHeader($printSettings->marginHeader) + ->setFooter($printSettings->marginFooter); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/Properties.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/Properties.php new file mode 100644 index 0000000..fc78936 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/Properties.php @@ -0,0 +1,129 @@ +spreadsheet = $spreadsheet; + } + + public function load(SimpleXMLElement $xml, $namespacesMeta): void + { + $docProps = $this->spreadsheet->getProperties(); + $officeProperty = $xml->children($namespacesMeta['office']); + foreach ($officeProperty as $officePropertyData) { + // @var \SimpleXMLElement $officePropertyData + if (isset($namespacesMeta['dc'])) { + $officePropertiesDC = $officePropertyData->children($namespacesMeta['dc']); + $this->setCoreProperties($docProps, $officePropertiesDC); + } + + $officePropertyMeta = []; + if (isset($namespacesMeta['dc'])) { + $officePropertyMeta = $officePropertyData->children($namespacesMeta['meta']); + } + foreach ($officePropertyMeta as $propertyName => $propertyValue) { + $this->setMetaProperties($namespacesMeta, $propertyValue, $propertyName, $docProps); + } + } + } + + private function setCoreProperties(DocumentProperties $docProps, SimpleXMLElement $officePropertyDC): void + { + foreach ($officePropertyDC as $propertyName => $propertyValue) { + $propertyValue = (string) $propertyValue; + switch ($propertyName) { + case 'title': + $docProps->setTitle($propertyValue); + + break; + case 'subject': + $docProps->setSubject($propertyValue); + + break; + case 'creator': + $docProps->setCreator($propertyValue); + $docProps->setLastModifiedBy($propertyValue); + + break; + case 'date': + $docProps->setModified($propertyValue); + + break; + case 'description': + $docProps->setDescription($propertyValue); + + break; + } + } + } + + private function setMetaProperties( + $namespacesMeta, + SimpleXMLElement $propertyValue, + $propertyName, + DocumentProperties $docProps + ): void { + $propertyValueAttributes = $propertyValue->attributes($namespacesMeta['meta']); + $propertyValue = (string) $propertyValue; + switch ($propertyName) { + case 'initial-creator': + $docProps->setCreator($propertyValue); + + break; + case 'keyword': + $docProps->setKeywords($propertyValue); + + break; + case 'creation-date': + $docProps->setCreated($propertyValue); + + break; + case 'user-defined': + $this->setUserDefinedProperty($propertyValueAttributes, $propertyValue, $docProps); + + break; + } + } + + private function setUserDefinedProperty($propertyValueAttributes, $propertyValue, DocumentProperties $docProps): void + { + $propertyValueName = ''; + $propertyValueType = DocumentProperties::PROPERTY_TYPE_STRING; + foreach ($propertyValueAttributes as $key => $value) { + if ($key == 'name') { + $propertyValueName = (string) $value; + } elseif ($key == 'value-type') { + switch ($value) { + case 'date': + $propertyValue = DocumentProperties::convertProperty($propertyValue, 'date'); + $propertyValueType = DocumentProperties::PROPERTY_TYPE_DATE; + + break; + case 'boolean': + $propertyValue = DocumentProperties::convertProperty($propertyValue, 'bool'); + $propertyValueType = DocumentProperties::PROPERTY_TYPE_BOOLEAN; + + break; + case 'float': + $propertyValue = DocumentProperties::convertProperty($propertyValue, 'r4'); + $propertyValueType = DocumentProperties::PROPERTY_TYPE_FLOAT; + + break; + default: + $propertyValueType = DocumentProperties::PROPERTY_TYPE_STRING; + } + } + } + + $docProps->setCustomProperty($propertyValueName, $propertyValue, $propertyValueType); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Security/XmlScanner.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Security/XmlScanner.php new file mode 100644 index 0000000..8155b83 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Security/XmlScanner.php @@ -0,0 +1,157 @@ +pattern = $pattern; + + $this->disableEntityLoaderCheck(); + + // A fatal error will bypass the destructor, so we register a shutdown here + if (!self::$shutdownRegistered) { + self::$shutdownRegistered = true; + register_shutdown_function([__CLASS__, 'shutdown']); + } + } + + public static function getInstance(Reader\IReader $reader) + { + switch (true) { + case $reader instanceof Reader\Html: + return new self('= 1; + case 1: + return PHP_RELEASE_VERSION >= 13; + case 0: + return PHP_RELEASE_VERSION >= 27; + } + + return true; + } + + return false; + } + + private function disableEntityLoaderCheck(): void + { + if (\PHP_VERSION_ID < 80000) { + $libxmlDisableEntityLoaderValue = libxml_disable_entity_loader(true); + + if (self::$libxmlDisableEntityLoaderValue === null) { + self::$libxmlDisableEntityLoaderValue = $libxmlDisableEntityLoaderValue; + } + } + } + + public static function shutdown(): void + { + if (self::$libxmlDisableEntityLoaderValue !== null && \PHP_VERSION_ID < 80000) { + libxml_disable_entity_loader(self::$libxmlDisableEntityLoaderValue); + self::$libxmlDisableEntityLoaderValue = null; + } + } + + public function __destruct() + { + self::shutdown(); + } + + public function setAdditionalCallback(callable $callback): void + { + $this->callback = $callback; + } + + private function toUtf8($xml) + { + $pattern = '/encoding="(.*?)"/'; + $result = preg_match($pattern, $xml, $matches); + $charset = strtoupper($result ? $matches[1] : 'UTF-8'); + + if ($charset !== 'UTF-8') { + $xml = mb_convert_encoding($xml, 'UTF-8', $charset); + + $result = preg_match($pattern, $xml, $matches); + $charset = strtoupper($result ? $matches[1] : 'UTF-8'); + if ($charset !== 'UTF-8') { + throw new Reader\Exception('Suspicious Double-encoded XML, spreadsheet file load() aborted to prevent XXE/XEE attacks'); + } + } + + return $xml; + } + + /** + * Scan the XML for use of disableEntityLoaderCheck(); + + $xml = $this->toUtf8($xml); + + // Don't rely purely on libxml_disable_entity_loader() + $pattern = '/\\0?' . implode('\\0?', str_split($this->pattern)) . '\\0?/'; + + if (preg_match($pattern, $xml)) { + throw new Reader\Exception('Detected use of ENTITY in XML, spreadsheet file load() aborted to prevent XXE/XEE attacks'); + } + + if ($this->callback !== null && is_callable($this->callback)) { + $xml = call_user_func($this->callback, $xml); + } + + return $xml; + } + + /** + * Scan theXML for use of scan(file_get_contents($filestream)); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Slk.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Slk.php new file mode 100644 index 0000000..de3c6ce --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Slk.php @@ -0,0 +1,596 @@ +openFile($filename); + } catch (ReaderException $e) { + return false; + } + + // Read sample data (first 2 KB will do) + $data = (string) fread($this->fileHandle, 2048); + + // Count delimiters in file + $delimiterCount = substr_count($data, ';'); + $hasDelimiter = $delimiterCount > 0; + + // Analyze first line looking for ID; signature + $lines = explode("\n", $data); + $hasId = substr($lines[0], 0, 4) === 'ID;P'; + + fclose($this->fileHandle); + + return $hasDelimiter && $hasId; + } + + private function canReadOrBust(string $filename): void + { + if (!$this->canRead($filename)) { + throw new ReaderException($filename . ' is an Invalid SYLK file.'); + } + $this->openFile($filename); + } + + /** + * Set input encoding. + * + * @deprecated no use is made of this property + * + * @param string $inputEncoding Input encoding, eg: 'ANSI' + * + * @return $this + * + * @codeCoverageIgnore + */ + public function setInputEncoding($inputEncoding) + { + $this->inputEncoding = $inputEncoding; + + return $this; + } + + /** + * Get input encoding. + * + * @deprecated no use is made of this property + * + * @return string + * + * @codeCoverageIgnore + */ + public function getInputEncoding() + { + return $this->inputEncoding; + } + + /** + * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns). + * + * @param string $filename + * + * @return array + */ + public function listWorksheetInfo($filename) + { + // Open file + $this->canReadOrBust($filename); + $fileHandle = $this->fileHandle; + rewind($fileHandle); + + $worksheetInfo = []; + $worksheetInfo[0]['worksheetName'] = basename($filename, '.slk'); + + // loop through one row (line) at a time in the file + $rowIndex = 0; + $columnIndex = 0; + while (($rowData = fgets($fileHandle)) !== false) { + $columnIndex = 0; + + // convert SYLK encoded $rowData to UTF-8 + $rowData = StringHelper::SYLKtoUTF8($rowData); + + // explode each row at semicolons while taking into account that literal semicolon (;) + // is escaped like this (;;) + $rowData = explode("\t", str_replace('¤', ';', str_replace(';', "\t", str_replace(';;', '¤', rtrim($rowData))))); + + $dataType = array_shift($rowData); + if ($dataType == 'B') { + foreach ($rowData as $rowDatum) { + switch ($rowDatum[0]) { + case 'X': + $columnIndex = (int) substr($rowDatum, 1) - 1; + + break; + case 'Y': + $rowIndex = substr($rowDatum, 1); + + break; + } + } + + break; + } + } + + $worksheetInfo[0]['lastColumnIndex'] = $columnIndex; + $worksheetInfo[0]['totalRows'] = $rowIndex; + $worksheetInfo[0]['lastColumnLetter'] = Coordinate::stringFromColumnIndex($worksheetInfo[0]['lastColumnIndex'] + 1); + $worksheetInfo[0]['totalColumns'] = $worksheetInfo[0]['lastColumnIndex'] + 1; + + // Close file + fclose($fileHandle); + + return $worksheetInfo; + } + + /** + * Loads PhpSpreadsheet from file. + * + * @return Spreadsheet + */ + public function load(string $filename, int $flags = 0) + { + $this->processFlags($flags); + + // Create new Spreadsheet + $spreadsheet = new Spreadsheet(); + + // Load into this instance + return $this->loadIntoExisting($filename, $spreadsheet); + } + + private const COLOR_ARRAY = [ + 'FF00FFFF', // 0 - cyan + 'FF000000', // 1 - black + 'FFFFFFFF', // 2 - white + 'FFFF0000', // 3 - red + 'FF00FF00', // 4 - green + 'FF0000FF', // 5 - blue + 'FFFFFF00', // 6 - yellow + 'FFFF00FF', // 7 - magenta + ]; + + private const FONT_STYLE_MAPPINGS = [ + 'B' => 'bold', + 'I' => 'italic', + 'U' => 'underline', + ]; + + private function processFormula(string $rowDatum, bool &$hasCalculatedValue, string &$cellDataFormula, string $row, string $column): void + { + $cellDataFormula = '=' . substr($rowDatum, 1); + // Convert R1C1 style references to A1 style references (but only when not quoted) + $temp = explode('"', $cellDataFormula); + $key = false; + foreach ($temp as &$value) { + // Only count/replace in alternate array entries + $key = !$key; + if ($key) { + preg_match_all('/(R(\[?-?\d*\]?))(C(\[?-?\d*\]?))/', $value, $cellReferences, PREG_SET_ORDER + PREG_OFFSET_CAPTURE); + // Reverse the matches array, otherwise all our offsets will become incorrect if we modify our way + // through the formula from left to right. Reversing means that we work right to left.through + // the formula + $cellReferences = array_reverse($cellReferences); + // Loop through each R1C1 style reference in turn, converting it to its A1 style equivalent, + // then modify the formula to use that new reference + foreach ($cellReferences as $cellReference) { + $rowReference = $cellReference[2][0]; + // Empty R reference is the current row + if ($rowReference == '') { + $rowReference = $row; + } + // Bracketed R references are relative to the current row + if ($rowReference[0] == '[') { + $rowReference = (int) $row + (int) trim($rowReference, '[]'); + } + $columnReference = $cellReference[4][0]; + // Empty C reference is the current column + if ($columnReference == '') { + $columnReference = $column; + } + // Bracketed C references are relative to the current column + if ($columnReference[0] == '[') { + $columnReference = (int) $column + (int) trim($columnReference, '[]'); + } + $A1CellReference = Coordinate::stringFromColumnIndex($columnReference) . $rowReference; + + $value = substr_replace($value, $A1CellReference, $cellReference[0][1], strlen($cellReference[0][0])); + } + } + } + unset($value); + // Then rebuild the formula string + $cellDataFormula = implode('"', $temp); + $hasCalculatedValue = true; + } + + private function processCRecord(array $rowData, Spreadsheet &$spreadsheet, string &$row, string &$column): void + { + // Read cell value data + $hasCalculatedValue = false; + $cellDataFormula = $cellData = ''; + foreach ($rowData as $rowDatum) { + switch ($rowDatum[0]) { + case 'C': + case 'X': + $column = substr($rowDatum, 1); + + break; + case 'R': + case 'Y': + $row = substr($rowDatum, 1); + + break; + case 'K': + $cellData = substr($rowDatum, 1); + + break; + case 'E': + $this->processFormula($rowDatum, $hasCalculatedValue, $cellDataFormula, $row, $column); + + break; + case 'A': + $comment = substr($rowDatum, 1); + $columnLetter = Coordinate::stringFromColumnIndex((int) $column); + $spreadsheet->getActiveSheet() + ->getComment("$columnLetter$row") + ->getText() + ->createText($comment); + + break; + } + } + $columnLetter = Coordinate::stringFromColumnIndex((int) $column); + $cellData = Calculation::unwrapResult($cellData); + + // Set cell value + $this->processCFinal($spreadsheet, $hasCalculatedValue, $cellDataFormula, $cellData, "$columnLetter$row"); + } + + private function processCFinal(Spreadsheet &$spreadsheet, bool $hasCalculatedValue, string $cellDataFormula, string $cellData, string $coordinate): void + { + // Set cell value + $spreadsheet->getActiveSheet()->getCell($coordinate)->setValue(($hasCalculatedValue) ? $cellDataFormula : $cellData); + if ($hasCalculatedValue) { + $cellData = Calculation::unwrapResult($cellData); + $spreadsheet->getActiveSheet()->getCell($coordinate)->setCalculatedValue($cellData); + } + } + + private function processFRecord(array $rowData, Spreadsheet &$spreadsheet, string &$row, string &$column): void + { + // Read cell formatting + $formatStyle = $columnWidth = ''; + $startCol = $endCol = ''; + $fontStyle = ''; + $styleData = []; + foreach ($rowData as $rowDatum) { + switch ($rowDatum[0]) { + case 'C': + case 'X': + $column = substr($rowDatum, 1); + + break; + case 'R': + case 'Y': + $row = substr($rowDatum, 1); + + break; + case 'P': + $formatStyle = $rowDatum; + + break; + case 'W': + [$startCol, $endCol, $columnWidth] = explode(' ', substr($rowDatum, 1)); + + break; + case 'S': + $this->styleSettings($rowDatum, $styleData, $fontStyle); + + break; + } + } + $this->addFormats($spreadsheet, $formatStyle, $row, $column); + $this->addFonts($spreadsheet, $fontStyle, $row, $column); + $this->addStyle($spreadsheet, $styleData, $row, $column); + $this->addWidth($spreadsheet, $columnWidth, $startCol, $endCol); + } + + private const STYLE_SETTINGS_FONT = ['D' => 'bold', 'I' => 'italic']; + + private const STYLE_SETTINGS_BORDER = [ + 'B' => 'bottom', + 'L' => 'left', + 'R' => 'right', + 'T' => 'top', + ]; + + private function styleSettings(string $rowDatum, array &$styleData, string &$fontStyle): void + { + $styleSettings = substr($rowDatum, 1); + $iMax = strlen($styleSettings); + for ($i = 0; $i < $iMax; ++$i) { + $char = $styleSettings[$i]; + if (array_key_exists($char, self::STYLE_SETTINGS_FONT)) { + $styleData['font'][self::STYLE_SETTINGS_FONT[$char]] = true; + } elseif (array_key_exists($char, self::STYLE_SETTINGS_BORDER)) { + $styleData['borders'][self::STYLE_SETTINGS_BORDER[$char]]['borderStyle'] = Border::BORDER_THIN; + } elseif ($char == 'S') { + $styleData['fill']['fillType'] = \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_PATTERN_GRAY125; + } elseif ($char == 'M') { + if (preg_match('/M([1-9]\\d*)/', $styleSettings, $matches)) { + $fontStyle = $matches[1]; + } + } + } + } + + private function addFormats(Spreadsheet &$spreadsheet, string $formatStyle, string $row, string $column): void + { + if ($formatStyle && $column > '' && $row > '') { + $columnLetter = Coordinate::stringFromColumnIndex((int) $column); + if (isset($this->formats[$formatStyle])) { + $spreadsheet->getActiveSheet()->getStyle($columnLetter . $row)->applyFromArray($this->formats[$formatStyle]); + } + } + } + + private function addFonts(Spreadsheet &$spreadsheet, string $fontStyle, string $row, string $column): void + { + if ($fontStyle && $column > '' && $row > '') { + $columnLetter = Coordinate::stringFromColumnIndex((int) $column); + if (isset($this->fonts[$fontStyle])) { + $spreadsheet->getActiveSheet()->getStyle($columnLetter . $row)->applyFromArray($this->fonts[$fontStyle]); + } + } + } + + private function addStyle(Spreadsheet &$spreadsheet, array $styleData, string $row, string $column): void + { + if ((!empty($styleData)) && $column > '' && $row > '') { + $columnLetter = Coordinate::stringFromColumnIndex((int) $column); + $spreadsheet->getActiveSheet()->getStyle($columnLetter . $row)->applyFromArray($styleData); + } + } + + private function addWidth(Spreadsheet $spreadsheet, string $columnWidth, string $startCol, string $endCol): void + { + if ($columnWidth > '') { + if ($startCol == $endCol) { + $startCol = Coordinate::stringFromColumnIndex((int) $startCol); + $spreadsheet->getActiveSheet()->getColumnDimension($startCol)->setWidth((float) $columnWidth); + } else { + $startCol = Coordinate::stringFromColumnIndex((int) $startCol); + $endCol = Coordinate::stringFromColumnIndex((int) $endCol); + $spreadsheet->getActiveSheet()->getColumnDimension($startCol)->setWidth((float) $columnWidth); + do { + $spreadsheet->getActiveSheet()->getColumnDimension(++$startCol)->setWidth((float) $columnWidth); + } while ($startCol !== $endCol); + } + } + } + + private function processPRecord(array $rowData, Spreadsheet &$spreadsheet): void + { + // Read shared styles + $formatArray = []; + $fromFormats = ['\-', '\ ']; + $toFormats = ['-', ' ']; + foreach ($rowData as $rowDatum) { + switch ($rowDatum[0]) { + case 'P': + $formatArray['numberFormat']['formatCode'] = str_replace($fromFormats, $toFormats, substr($rowDatum, 1)); + + break; + case 'E': + case 'F': + $formatArray['font']['name'] = substr($rowDatum, 1); + + break; + case 'M': + $formatArray['font']['size'] = substr($rowDatum, 1) / 20; + + break; + case 'L': + $this->processPColors($rowDatum, $formatArray); + + break; + case 'S': + $this->processPFontStyles($rowDatum, $formatArray); + + break; + } + } + $this->processPFinal($spreadsheet, $formatArray); + } + + private function processPColors(string $rowDatum, array &$formatArray): void + { + if (preg_match('/L([1-9]\\d*)/', $rowDatum, $matches)) { + $fontColor = $matches[1] % 8; + $formatArray['font']['color']['argb'] = self::COLOR_ARRAY[$fontColor]; + } + } + + private function processPFontStyles(string $rowDatum, array &$formatArray): void + { + $styleSettings = substr($rowDatum, 1); + $iMax = strlen($styleSettings); + for ($i = 0; $i < $iMax; ++$i) { + if (array_key_exists($styleSettings[$i], self::FONT_STYLE_MAPPINGS)) { + $formatArray['font'][self::FONT_STYLE_MAPPINGS[$styleSettings[$i]]] = true; + } + } + } + + private function processPFinal(Spreadsheet &$spreadsheet, array $formatArray): void + { + if (array_key_exists('numberFormat', $formatArray)) { + $this->formats['P' . $this->format] = $formatArray; + ++$this->format; + } elseif (array_key_exists('font', $formatArray)) { + ++$this->fontcount; + $this->fonts[$this->fontcount] = $formatArray; + if ($this->fontcount === 1) { + $spreadsheet->getDefaultStyle()->applyFromArray($formatArray); + } + } + } + + /** + * Loads PhpSpreadsheet from file into PhpSpreadsheet instance. + * + * @param string $filename + * + * @return Spreadsheet + */ + public function loadIntoExisting($filename, Spreadsheet $spreadsheet) + { + // Open file + $this->canReadOrBust($filename); + $fileHandle = $this->fileHandle; + rewind($fileHandle); + + // Create new Worksheets + while ($spreadsheet->getSheetCount() <= $this->sheetIndex) { + $spreadsheet->createSheet(); + } + $spreadsheet->setActiveSheetIndex($this->sheetIndex); + $spreadsheet->getActiveSheet()->setTitle(substr(basename($filename, '.slk'), 0, Worksheet::SHEET_TITLE_MAXIMUM_LENGTH)); + + // Loop through file + $column = $row = ''; + + // loop through one row (line) at a time in the file + while (($rowDataTxt = fgets($fileHandle)) !== false) { + // convert SYLK encoded $rowData to UTF-8 + $rowDataTxt = StringHelper::SYLKtoUTF8($rowDataTxt); + + // explode each row at semicolons while taking into account that literal semicolon (;) + // is escaped like this (;;) + $rowData = explode("\t", str_replace('¤', ';', str_replace(';', "\t", str_replace(';;', '¤', rtrim($rowDataTxt))))); + + $dataType = array_shift($rowData); + if ($dataType == 'P') { + // Read shared styles + $this->processPRecord($rowData, $spreadsheet); + } elseif ($dataType == 'C') { + // Read cell value data + $this->processCRecord($rowData, $spreadsheet, $row, $column); + } elseif ($dataType == 'F') { + // Read cell formatting + $this->processFRecord($rowData, $spreadsheet, $row, $column); + } else { + $this->columnRowFromRowData($rowData, $column, $row); + } + } + + // Close file + fclose($fileHandle); + + // Return + return $spreadsheet; + } + + private function columnRowFromRowData(array $rowData, string &$column, string &$row): void + { + foreach ($rowData as $rowDatum) { + $char0 = $rowDatum[0]; + if ($char0 === 'X' || $char0 == 'C') { + $column = substr($rowDatum, 1); + } elseif ($char0 === 'Y' || $char0 == 'R') { + $row = substr($rowDatum, 1); + } + } + } + + /** + * Get sheet index. + * + * @return int + */ + public function getSheetIndex() + { + return $this->sheetIndex; + } + + /** + * Set sheet index. + * + * @param int $sheetIndex Sheet index + * + * @return $this + */ + public function setSheetIndex($sheetIndex) + { + $this->sheetIndex = $sheetIndex; + + return $this; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls.php new file mode 100644 index 0000000..11d5874 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls.php @@ -0,0 +1,7915 @@ +data. + * + * @var int + */ + private $dataSize; + + /** + * Current position in stream. + * + * @var int + */ + private $pos; + + /** + * Workbook to be returned by the reader. + * + * @var Spreadsheet + */ + private $spreadsheet; + + /** + * Worksheet that is currently being built by the reader. + * + * @var Worksheet + */ + private $phpSheet; + + /** + * BIFF version. + * + * @var int + */ + private $version; + + /** + * Codepage set in the Excel file being read. Only important for BIFF5 (Excel 5.0 - Excel 95) + * For BIFF8 (Excel 97 - Excel 2003) this will always have the value 'UTF-16LE'. + * + * @var string + */ + private $codepage; + + /** + * Shared formats. + * + * @var array + */ + private $formats; + + /** + * Shared fonts. + * + * @var Font[] + */ + private $objFonts; + + /** + * Color palette. + * + * @var array + */ + private $palette; + + /** + * Worksheets. + * + * @var array + */ + private $sheets; + + /** + * External books. + * + * @var array + */ + private $externalBooks; + + /** + * REF structures. Only applies to BIFF8. + * + * @var array + */ + private $ref; + + /** + * External names. + * + * @var array + */ + private $externalNames; + + /** + * Defined names. + * + * @var array + */ + private $definedname; + + /** + * Shared strings. Only applies to BIFF8. + * + * @var array + */ + private $sst; + + /** + * Panes are frozen? (in sheet currently being read). See WINDOW2 record. + * + * @var bool + */ + private $frozen; + + /** + * Fit printout to number of pages? (in sheet currently being read). See SHEETPR record. + * + * @var bool + */ + private $isFitToPages; + + /** + * Objects. One OBJ record contributes with one entry. + * + * @var array + */ + private $objs; + + /** + * Text Objects. One TXO record corresponds with one entry. + * + * @var array + */ + private $textObjects; + + /** + * Cell Annotations (BIFF8). + * + * @var array + */ + private $cellNotes; + + /** + * The combined MSODRAWINGGROUP data. + * + * @var string + */ + private $drawingGroupData; + + /** + * The combined MSODRAWING data (per sheet). + * + * @var string + */ + private $drawingData; + + /** + * Keep track of XF index. + * + * @var int + */ + private $xfIndex; + + /** + * Mapping of XF index (that is a cell XF) to final index in cellXf collection. + * + * @var array + */ + private $mapCellXfIndex; + + /** + * Mapping of XF index (that is a style XF) to final index in cellStyleXf collection. + * + * @var array + */ + private $mapCellStyleXfIndex; + + /** + * The shared formulas in a sheet. One SHAREDFMLA record contributes with one value. + * + * @var array + */ + private $sharedFormulas; + + /** + * The shared formula parts in a sheet. One FORMULA record contributes with one value if it + * refers to a shared formula. + * + * @var array + */ + private $sharedFormulaParts; + + /** + * The type of encryption in use. + * + * @var int + */ + private $encryption = 0; + + /** + * The position in the stream after which contents are encrypted. + * + * @var int + */ + private $encryptionStartPos = 0; + + /** + * The current RC4 decryption object. + * + * @var Xls\RC4 + */ + private $rc4Key; + + /** + * The position in the stream that the RC4 decryption object was left at. + * + * @var int + */ + private $rc4Pos = 0; + + /** + * The current MD5 context state. + * + * @var string + */ + private $md5Ctxt; + + /** + * @var int + */ + private $textObjRef; + + /** + * @var string + */ + private $baseCell; + + /** + * Create a new Xls Reader instance. + */ + public function __construct() + { + parent::__construct(); + } + + /** + * Can the current IReader read the file? + */ + public function canRead(string $filename): bool + { + if (!File::testFileNoThrow($filename)) { + return false; + } + + try { + // Use ParseXL for the hard work. + $ole = new OLERead(); + + // get excel data + $ole->read($filename); + + return true; + } catch (PhpSpreadsheetException $e) { + return false; + } + } + + public function setCodepage(string $codepage): void + { + if (!CodePage::validate($codepage)) { + throw new PhpSpreadsheetException('Unknown codepage: ' . $codepage); + } + + $this->codepage = $codepage; + } + + /** + * Reads names of the worksheets from a file, without parsing the whole file to a PhpSpreadsheet object. + * + * @param string $filename + * + * @return array + */ + public function listWorksheetNames($filename) + { + File::assertFile($filename); + + $worksheetNames = []; + + // Read the OLE file + $this->loadOLE($filename); + + // total byte size of Excel data (workbook global substream + sheet substreams) + $this->dataSize = strlen($this->data); + + $this->pos = 0; + $this->sheets = []; + + // Parse Workbook Global Substream + while ($this->pos < $this->dataSize) { + $code = self::getUInt2d($this->data, $this->pos); + + switch ($code) { + case self::XLS_TYPE_BOF: + $this->readBof(); + + break; + case self::XLS_TYPE_SHEET: + $this->readSheet(); + + break; + case self::XLS_TYPE_EOF: + $this->readDefault(); + + break 2; + default: + $this->readDefault(); + + break; + } + } + + foreach ($this->sheets as $sheet) { + if ($sheet['sheetType'] != 0x00) { + // 0x00: Worksheet, 0x02: Chart, 0x06: Visual Basic module + continue; + } + + $worksheetNames[] = $sheet['name']; + } + + return $worksheetNames; + } + + /** + * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns). + * + * @param string $filename + * + * @return array + */ + public function listWorksheetInfo($filename) + { + File::assertFile($filename); + + $worksheetInfo = []; + + // Read the OLE file + $this->loadOLE($filename); + + // total byte size of Excel data (workbook global substream + sheet substreams) + $this->dataSize = strlen($this->data); + + // initialize + $this->pos = 0; + $this->sheets = []; + + // Parse Workbook Global Substream + while ($this->pos < $this->dataSize) { + $code = self::getUInt2d($this->data, $this->pos); + + switch ($code) { + case self::XLS_TYPE_BOF: + $this->readBof(); + + break; + case self::XLS_TYPE_SHEET: + $this->readSheet(); + + break; + case self::XLS_TYPE_EOF: + $this->readDefault(); + + break 2; + default: + $this->readDefault(); + + break; + } + } + + // Parse the individual sheets + foreach ($this->sheets as $sheet) { + if ($sheet['sheetType'] != 0x00) { + // 0x00: Worksheet + // 0x02: Chart + // 0x06: Visual Basic module + continue; + } + + $tmpInfo = []; + $tmpInfo['worksheetName'] = $sheet['name']; + $tmpInfo['lastColumnLetter'] = 'A'; + $tmpInfo['lastColumnIndex'] = 0; + $tmpInfo['totalRows'] = 0; + $tmpInfo['totalColumns'] = 0; + + $this->pos = $sheet['offset']; + + while ($this->pos <= $this->dataSize - 4) { + $code = self::getUInt2d($this->data, $this->pos); + + switch ($code) { + case self::XLS_TYPE_RK: + case self::XLS_TYPE_LABELSST: + case self::XLS_TYPE_NUMBER: + case self::XLS_TYPE_FORMULA: + case self::XLS_TYPE_BOOLERR: + case self::XLS_TYPE_LABEL: + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + $rowIndex = self::getUInt2d($recordData, 0) + 1; + $columnIndex = self::getUInt2d($recordData, 2); + + $tmpInfo['totalRows'] = max($tmpInfo['totalRows'], $rowIndex); + $tmpInfo['lastColumnIndex'] = max($tmpInfo['lastColumnIndex'], $columnIndex); + + break; + case self::XLS_TYPE_BOF: + $this->readBof(); + + break; + case self::XLS_TYPE_EOF: + $this->readDefault(); + + break 2; + default: + $this->readDefault(); + + break; + } + } + + $tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1); + $tmpInfo['totalColumns'] = $tmpInfo['lastColumnIndex'] + 1; + + $worksheetInfo[] = $tmpInfo; + } + + return $worksheetInfo; + } + + /** + * Loads PhpSpreadsheet from file. + * + * @return Spreadsheet + */ + public function load(string $filename, int $flags = 0) + { + $this->processFlags($flags); + + // Read the OLE file + $this->loadOLE($filename); + + // Initialisations + $this->spreadsheet = new Spreadsheet(); + $this->spreadsheet->removeSheetByIndex(0); // remove 1st sheet + if (!$this->readDataOnly) { + $this->spreadsheet->removeCellStyleXfByIndex(0); // remove the default style + $this->spreadsheet->removeCellXfByIndex(0); // remove the default style + } + + // Read the summary information stream (containing meta data) + $this->readSummaryInformation(); + + // Read the Additional document summary information stream (containing application-specific meta data) + $this->readDocumentSummaryInformation(); + + // total byte size of Excel data (workbook global substream + sheet substreams) + $this->dataSize = strlen($this->data); + + // initialize + $this->pos = 0; + $this->codepage = $this->codepage ?: CodePage::DEFAULT_CODE_PAGE; + $this->formats = []; + $this->objFonts = []; + $this->palette = []; + $this->sheets = []; + $this->externalBooks = []; + $this->ref = []; + $this->definedname = []; + $this->sst = []; + $this->drawingGroupData = ''; + $this->xfIndex = 0; + $this->mapCellXfIndex = []; + $this->mapCellStyleXfIndex = []; + + // Parse Workbook Global Substream + while ($this->pos < $this->dataSize) { + $code = self::getUInt2d($this->data, $this->pos); + + switch ($code) { + case self::XLS_TYPE_BOF: + $this->readBof(); + + break; + case self::XLS_TYPE_FILEPASS: + $this->readFilepass(); + + break; + case self::XLS_TYPE_CODEPAGE: + $this->readCodepage(); + + break; + case self::XLS_TYPE_DATEMODE: + $this->readDateMode(); + + break; + case self::XLS_TYPE_FONT: + $this->readFont(); + + break; + case self::XLS_TYPE_FORMAT: + $this->readFormat(); + + break; + case self::XLS_TYPE_XF: + $this->readXf(); + + break; + case self::XLS_TYPE_XFEXT: + $this->readXfExt(); + + break; + case self::XLS_TYPE_STYLE: + $this->readStyle(); + + break; + case self::XLS_TYPE_PALETTE: + $this->readPalette(); + + break; + case self::XLS_TYPE_SHEET: + $this->readSheet(); + + break; + case self::XLS_TYPE_EXTERNALBOOK: + $this->readExternalBook(); + + break; + case self::XLS_TYPE_EXTERNNAME: + $this->readExternName(); + + break; + case self::XLS_TYPE_EXTERNSHEET: + $this->readExternSheet(); + + break; + case self::XLS_TYPE_DEFINEDNAME: + $this->readDefinedName(); + + break; + case self::XLS_TYPE_MSODRAWINGGROUP: + $this->readMsoDrawingGroup(); + + break; + case self::XLS_TYPE_SST: + $this->readSst(); + + break; + case self::XLS_TYPE_EOF: + $this->readDefault(); + + break 2; + default: + $this->readDefault(); + + break; + } + } + + // Resolve indexed colors for font, fill, and border colors + // Cannot be resolved already in XF record, because PALETTE record comes afterwards + if (!$this->readDataOnly) { + foreach ($this->objFonts as $objFont) { + if (isset($objFont->colorIndex)) { + $color = Xls\Color::map($objFont->colorIndex, $this->palette, $this->version); + $objFont->getColor()->setRGB($color['rgb']); + } + } + + foreach ($this->spreadsheet->getCellXfCollection() as $objStyle) { + // fill start and end color + $fill = $objStyle->getFill(); + + if (isset($fill->startcolorIndex)) { + $startColor = Xls\Color::map($fill->startcolorIndex, $this->palette, $this->version); + $fill->getStartColor()->setRGB($startColor['rgb']); + } + if (isset($fill->endcolorIndex)) { + $endColor = Xls\Color::map($fill->endcolorIndex, $this->palette, $this->version); + $fill->getEndColor()->setRGB($endColor['rgb']); + } + + // border colors + $top = $objStyle->getBorders()->getTop(); + $right = $objStyle->getBorders()->getRight(); + $bottom = $objStyle->getBorders()->getBottom(); + $left = $objStyle->getBorders()->getLeft(); + $diagonal = $objStyle->getBorders()->getDiagonal(); + + if (isset($top->colorIndex)) { + $borderTopColor = Xls\Color::map($top->colorIndex, $this->palette, $this->version); + $top->getColor()->setRGB($borderTopColor['rgb']); + } + if (isset($right->colorIndex)) { + $borderRightColor = Xls\Color::map($right->colorIndex, $this->palette, $this->version); + $right->getColor()->setRGB($borderRightColor['rgb']); + } + if (isset($bottom->colorIndex)) { + $borderBottomColor = Xls\Color::map($bottom->colorIndex, $this->palette, $this->version); + $bottom->getColor()->setRGB($borderBottomColor['rgb']); + } + if (isset($left->colorIndex)) { + $borderLeftColor = Xls\Color::map($left->colorIndex, $this->palette, $this->version); + $left->getColor()->setRGB($borderLeftColor['rgb']); + } + if (isset($diagonal->colorIndex)) { + $borderDiagonalColor = Xls\Color::map($diagonal->colorIndex, $this->palette, $this->version); + $diagonal->getColor()->setRGB($borderDiagonalColor['rgb']); + } + } + } + + // treat MSODRAWINGGROUP records, workbook-level Escher + $escherWorkbook = null; + if (!$this->readDataOnly && $this->drawingGroupData) { + $escher = new Escher(); + $reader = new Xls\Escher($escher); + $escherWorkbook = $reader->load($this->drawingGroupData); + } + + // Parse the individual sheets + foreach ($this->sheets as $sheet) { + if ($sheet['sheetType'] != 0x00) { + // 0x00: Worksheet, 0x02: Chart, 0x06: Visual Basic module + continue; + } + + // check if sheet should be skipped + if (isset($this->loadSheetsOnly) && !in_array($sheet['name'], $this->loadSheetsOnly)) { + continue; + } + + // add sheet to PhpSpreadsheet object + $this->phpSheet = $this->spreadsheet->createSheet(); + // Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in formula + // cells... during the load, all formulae should be correct, and we're simply bringing the worksheet + // name in line with the formula, not the reverse + $this->phpSheet->setTitle($sheet['name'], false, false); + $this->phpSheet->setSheetState($sheet['sheetState']); + + $this->pos = $sheet['offset']; + + // Initialize isFitToPages. May change after reading SHEETPR record. + $this->isFitToPages = false; + + // Initialize drawingData + $this->drawingData = ''; + + // Initialize objs + $this->objs = []; + + // Initialize shared formula parts + $this->sharedFormulaParts = []; + + // Initialize shared formulas + $this->sharedFormulas = []; + + // Initialize text objs + $this->textObjects = []; + + // Initialize cell annotations + $this->cellNotes = []; + $this->textObjRef = -1; + + while ($this->pos <= $this->dataSize - 4) { + $code = self::getUInt2d($this->data, $this->pos); + + switch ($code) { + case self::XLS_TYPE_BOF: + $this->readBof(); + + break; + case self::XLS_TYPE_PRINTGRIDLINES: + $this->readPrintGridlines(); + + break; + case self::XLS_TYPE_DEFAULTROWHEIGHT: + $this->readDefaultRowHeight(); + + break; + case self::XLS_TYPE_SHEETPR: + $this->readSheetPr(); + + break; + case self::XLS_TYPE_HORIZONTALPAGEBREAKS: + $this->readHorizontalPageBreaks(); + + break; + case self::XLS_TYPE_VERTICALPAGEBREAKS: + $this->readVerticalPageBreaks(); + + break; + case self::XLS_TYPE_HEADER: + $this->readHeader(); + + break; + case self::XLS_TYPE_FOOTER: + $this->readFooter(); + + break; + case self::XLS_TYPE_HCENTER: + $this->readHcenter(); + + break; + case self::XLS_TYPE_VCENTER: + $this->readVcenter(); + + break; + case self::XLS_TYPE_LEFTMARGIN: + $this->readLeftMargin(); + + break; + case self::XLS_TYPE_RIGHTMARGIN: + $this->readRightMargin(); + + break; + case self::XLS_TYPE_TOPMARGIN: + $this->readTopMargin(); + + break; + case self::XLS_TYPE_BOTTOMMARGIN: + $this->readBottomMargin(); + + break; + case self::XLS_TYPE_PAGESETUP: + $this->readPageSetup(); + + break; + case self::XLS_TYPE_PROTECT: + $this->readProtect(); + + break; + case self::XLS_TYPE_SCENPROTECT: + $this->readScenProtect(); + + break; + case self::XLS_TYPE_OBJECTPROTECT: + $this->readObjectProtect(); + + break; + case self::XLS_TYPE_PASSWORD: + $this->readPassword(); + + break; + case self::XLS_TYPE_DEFCOLWIDTH: + $this->readDefColWidth(); + + break; + case self::XLS_TYPE_COLINFO: + $this->readColInfo(); + + break; + case self::XLS_TYPE_DIMENSION: + $this->readDefault(); + + break; + case self::XLS_TYPE_ROW: + $this->readRow(); + + break; + case self::XLS_TYPE_DBCELL: + $this->readDefault(); + + break; + case self::XLS_TYPE_RK: + $this->readRk(); + + break; + case self::XLS_TYPE_LABELSST: + $this->readLabelSst(); + + break; + case self::XLS_TYPE_MULRK: + $this->readMulRk(); + + break; + case self::XLS_TYPE_NUMBER: + $this->readNumber(); + + break; + case self::XLS_TYPE_FORMULA: + $this->readFormula(); + + break; + case self::XLS_TYPE_SHAREDFMLA: + $this->readSharedFmla(); + + break; + case self::XLS_TYPE_BOOLERR: + $this->readBoolErr(); + + break; + case self::XLS_TYPE_MULBLANK: + $this->readMulBlank(); + + break; + case self::XLS_TYPE_LABEL: + $this->readLabel(); + + break; + case self::XLS_TYPE_BLANK: + $this->readBlank(); + + break; + case self::XLS_TYPE_MSODRAWING: + $this->readMsoDrawing(); + + break; + case self::XLS_TYPE_OBJ: + $this->readObj(); + + break; + case self::XLS_TYPE_WINDOW2: + $this->readWindow2(); + + break; + case self::XLS_TYPE_PAGELAYOUTVIEW: + $this->readPageLayoutView(); + + break; + case self::XLS_TYPE_SCL: + $this->readScl(); + + break; + case self::XLS_TYPE_PANE: + $this->readPane(); + + break; + case self::XLS_TYPE_SELECTION: + $this->readSelection(); + + break; + case self::XLS_TYPE_MERGEDCELLS: + $this->readMergedCells(); + + break; + case self::XLS_TYPE_HYPERLINK: + $this->readHyperLink(); + + break; + case self::XLS_TYPE_DATAVALIDATIONS: + $this->readDataValidations(); + + break; + case self::XLS_TYPE_DATAVALIDATION: + $this->readDataValidation(); + + break; + case self::XLS_TYPE_SHEETLAYOUT: + $this->readSheetLayout(); + + break; + case self::XLS_TYPE_SHEETPROTECTION: + $this->readSheetProtection(); + + break; + case self::XLS_TYPE_RANGEPROTECTION: + $this->readRangeProtection(); + + break; + case self::XLS_TYPE_NOTE: + $this->readNote(); + + break; + case self::XLS_TYPE_TXO: + $this->readTextObject(); + + break; + case self::XLS_TYPE_CONTINUE: + $this->readContinue(); + + break; + case self::XLS_TYPE_EOF: + $this->readDefault(); + + break 2; + default: + $this->readDefault(); + + break; + } + } + + // treat MSODRAWING records, sheet-level Escher + if (!$this->readDataOnly && $this->drawingData) { + $escherWorksheet = new Escher(); + $reader = new Xls\Escher($escherWorksheet); + $escherWorksheet = $reader->load($this->drawingData); + + // get all spContainers in one long array, so they can be mapped to OBJ records + $allSpContainers = $escherWorksheet->getDgContainer()->getSpgrContainer()->getAllSpContainers(); + } + + // treat OBJ records + foreach ($this->objs as $n => $obj) { + // the first shape container never has a corresponding OBJ record, hence $n + 1 + if (isset($allSpContainers[$n + 1]) && is_object($allSpContainers[$n + 1])) { + $spContainer = $allSpContainers[$n + 1]; + + // we skip all spContainers that are a part of a group shape since we cannot yet handle those + if ($spContainer->getNestingLevel() > 1) { + continue; + } + + // calculate the width and height of the shape + [$startColumn, $startRow] = Coordinate::coordinateFromString($spContainer->getStartCoordinates()); + [$endColumn, $endRow] = Coordinate::coordinateFromString($spContainer->getEndCoordinates()); + + $startOffsetX = $spContainer->getStartOffsetX(); + $startOffsetY = $spContainer->getStartOffsetY(); + $endOffsetX = $spContainer->getEndOffsetX(); + $endOffsetY = $spContainer->getEndOffsetY(); + + $width = SharedXls::getDistanceX($this->phpSheet, $startColumn, $startOffsetX, $endColumn, $endOffsetX); + $height = SharedXls::getDistanceY($this->phpSheet, $startRow, $startOffsetY, $endRow, $endOffsetY); + + // calculate offsetX and offsetY of the shape + $offsetX = $startOffsetX * SharedXls::sizeCol($this->phpSheet, $startColumn) / 1024; + $offsetY = $startOffsetY * SharedXls::sizeRow($this->phpSheet, $startRow) / 256; + + switch ($obj['otObjType']) { + case 0x19: + // Note + if (isset($this->cellNotes[$obj['idObjID']])) { + $cellNote = $this->cellNotes[$obj['idObjID']]; + + if (isset($this->textObjects[$obj['idObjID']])) { + $textObject = $this->textObjects[$obj['idObjID']]; + $this->cellNotes[$obj['idObjID']]['objTextData'] = $textObject; + } + } + + break; + case 0x08: + // picture + // get index to BSE entry (1-based) + $BSEindex = $spContainer->getOPT(0x0104); + + // If there is no BSE Index, we will fail here and other fields are not read. + // Fix by checking here. + // TODO: Why is there no BSE Index? Is this a new Office Version? Password protected field? + // More likely : a uncompatible picture + if (!$BSEindex) { + continue 2; + } + + if ($escherWorkbook) { + $BSECollection = $escherWorkbook->getDggContainer()->getBstoreContainer()->getBSECollection(); + $BSE = $BSECollection[$BSEindex - 1]; + $blipType = $BSE->getBlipType(); + + // need check because some blip types are not supported by Escher reader such as EMF + if ($blip = $BSE->getBlip()) { + $ih = imagecreatefromstring($blip->getData()); + if ($ih !== false) { + $drawing = new MemoryDrawing(); + $drawing->setImageResource($ih); + + // width, height, offsetX, offsetY + $drawing->setResizeProportional(false); + $drawing->setWidth($width); + $drawing->setHeight($height); + $drawing->setOffsetX($offsetX); + $drawing->setOffsetY($offsetY); + + switch ($blipType) { + case BSE::BLIPTYPE_JPEG: + $drawing->setRenderingFunction(MemoryDrawing::RENDERING_JPEG); + $drawing->setMimeType(MemoryDrawing::MIMETYPE_JPEG); + + break; + case BSE::BLIPTYPE_PNG: + imagealphablending($ih, false); + imagesavealpha($ih, true); + $drawing->setRenderingFunction(MemoryDrawing::RENDERING_PNG); + $drawing->setMimeType(MemoryDrawing::MIMETYPE_PNG); + + break; + } + + $drawing->setWorksheet($this->phpSheet); + $drawing->setCoordinates($spContainer->getStartCoordinates()); + } + } + } + + break; + default: + // other object type + break; + } + } + } + + // treat SHAREDFMLA records + if ($this->version == self::XLS_BIFF8) { + foreach ($this->sharedFormulaParts as $cell => $baseCell) { + [$column, $row] = Coordinate::coordinateFromString($cell); + if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($column, $row, $this->phpSheet->getTitle())) { + $formula = $this->getFormulaFromStructure($this->sharedFormulas[$baseCell], $cell); + $this->phpSheet->getCell($cell)->setValueExplicit('=' . $formula, DataType::TYPE_FORMULA); + } + } + } + + if (!empty($this->cellNotes)) { + foreach ($this->cellNotes as $note => $noteDetails) { + if (!isset($noteDetails['objTextData'])) { + if (isset($this->textObjects[$note])) { + $textObject = $this->textObjects[$note]; + $noteDetails['objTextData'] = $textObject; + } else { + $noteDetails['objTextData']['text'] = ''; + } + } + $cellAddress = str_replace('$', '', $noteDetails['cellRef']); + $this->phpSheet->getComment($cellAddress)->setAuthor($noteDetails['author'])->setText($this->parseRichText($noteDetails['objTextData']['text'])); + } + } + } + + // add the named ranges (defined names) + foreach ($this->definedname as $definedName) { + if ($definedName['isBuiltInName']) { + switch ($definedName['name']) { + case pack('C', 0x06): + // print area + // in general, formula looks like this: Foo!$C$7:$J$66,Bar!$A$1:$IV$2 + $ranges = explode(',', $definedName['formula']); // FIXME: what if sheetname contains comma? + + $extractedRanges = []; + foreach ($ranges as $range) { + // $range should look like one of these + // Foo!$C$7:$J$66 + // Bar!$A$1:$IV$2 + $explodes = Worksheet::extractSheetTitle($range, true); + $sheetName = trim($explodes[0], "'"); + if (count($explodes) == 2) { + if (strpos($explodes[1], ':') === false) { + $explodes[1] = $explodes[1] . ':' . $explodes[1]; + } + $extractedRanges[] = str_replace('$', '', $explodes[1]); // C7:J66 + } + } + if ($docSheet = $this->spreadsheet->getSheetByName($sheetName)) { + $docSheet->getPageSetup()->setPrintArea(implode(',', $extractedRanges)); // C7:J66,A1:IV2 + } + + break; + case pack('C', 0x07): + // print titles (repeating rows) + // Assuming BIFF8, there are 3 cases + // 1. repeating rows + // formula looks like this: Sheet!$A$1:$IV$2 + // rows 1-2 repeat + // 2. repeating columns + // formula looks like this: Sheet!$A$1:$B$65536 + // columns A-B repeat + // 3. both repeating rows and repeating columns + // formula looks like this: Sheet!$A$1:$B$65536,Sheet!$A$1:$IV$2 + $ranges = explode(',', $definedName['formula']); // FIXME: what if sheetname contains comma? + foreach ($ranges as $range) { + // $range should look like this one of these + // Sheet!$A$1:$B$65536 + // Sheet!$A$1:$IV$2 + if (strpos($range, '!') !== false) { + $explodes = Worksheet::extractSheetTitle($range, true); + if ($docSheet = $this->spreadsheet->getSheetByName($explodes[0])) { + $extractedRange = $explodes[1]; + $extractedRange = str_replace('$', '', $extractedRange); + + $coordinateStrings = explode(':', $extractedRange); + if (count($coordinateStrings) == 2) { + [$firstColumn, $firstRow] = Coordinate::coordinateFromString($coordinateStrings[0]); + [$lastColumn, $lastRow] = Coordinate::coordinateFromString($coordinateStrings[1]); + + if ($firstColumn == 'A' && $lastColumn == 'IV') { + // then we have repeating rows + $docSheet->getPageSetup()->setRowsToRepeatAtTop([$firstRow, $lastRow]); + } elseif ($firstRow == 1 && $lastRow == 65536) { + // then we have repeating columns + $docSheet->getPageSetup()->setColumnsToRepeatAtLeft([$firstColumn, $lastColumn]); + } + } + } + } + } + + break; + } + } else { + // Extract range + if (strpos($definedName['formula'], '!') !== false) { + $explodes = Worksheet::extractSheetTitle($definedName['formula'], true); + if ( + ($docSheet = $this->spreadsheet->getSheetByName($explodes[0])) || + ($docSheet = $this->spreadsheet->getSheetByName(trim($explodes[0], "'"))) + ) { + $extractedRange = $explodes[1]; + $extractedRange = str_replace('$', '', $extractedRange); + + $localOnly = ($definedName['scope'] == 0) ? false : true; + + $scope = ($definedName['scope'] == 0) ? null : $this->spreadsheet->getSheetByName($this->sheets[$definedName['scope'] - 1]['name']); + + $this->spreadsheet->addNamedRange(new NamedRange((string) $definedName['name'], $docSheet, $extractedRange, $localOnly, $scope)); + } + } + // Named Value + // TODO Provide support for named values + } + } + $this->data = ''; + + return $this->spreadsheet; + } + + /** + * Read record data from stream, decrypting as required. + * + * @param string $data Data stream to read from + * @param int $pos Position to start reading from + * @param int $len Record data length + * + * @return string Record data + */ + private function readRecordData($data, $pos, $len) + { + $data = substr($data, $pos, $len); + + // File not encrypted, or record before encryption start point + if ($this->encryption == self::MS_BIFF_CRYPTO_NONE || $pos < $this->encryptionStartPos) { + return $data; + } + + $recordData = ''; + if ($this->encryption == self::MS_BIFF_CRYPTO_RC4) { + $oldBlock = floor($this->rc4Pos / self::REKEY_BLOCK); + $block = floor($pos / self::REKEY_BLOCK); + $endBlock = floor(($pos + $len) / self::REKEY_BLOCK); + + // Spin an RC4 decryptor to the right spot. If we have a decryptor sitting + // at a point earlier in the current block, re-use it as we can save some time. + if ($block != $oldBlock || $pos < $this->rc4Pos || !$this->rc4Key) { + $this->rc4Key = $this->makeKey($block, $this->md5Ctxt); + $step = $pos % self::REKEY_BLOCK; + } else { + $step = $pos - $this->rc4Pos; + } + $this->rc4Key->RC4(str_repeat("\0", $step)); + + // Decrypt record data (re-keying at the end of every block) + while ($block != $endBlock) { + $step = self::REKEY_BLOCK - ($pos % self::REKEY_BLOCK); + $recordData .= $this->rc4Key->RC4(substr($data, 0, $step)); + $data = substr($data, $step); + $pos += $step; + $len -= $step; + ++$block; + $this->rc4Key = $this->makeKey($block, $this->md5Ctxt); + } + $recordData .= $this->rc4Key->RC4(substr($data, 0, $len)); + + // Keep track of the position of this decryptor. + // We'll try and re-use it later if we can to speed things up + $this->rc4Pos = $pos + $len; + } elseif ($this->encryption == self::MS_BIFF_CRYPTO_XOR) { + throw new Exception('XOr encryption not supported'); + } + + return $recordData; + } + + /** + * Use OLE reader to extract the relevant data streams from the OLE file. + * + * @param string $filename + */ + private function loadOLE($filename): void + { + // OLE reader + $ole = new OLERead(); + // get excel data, + $ole->read($filename); + // Get workbook data: workbook stream + sheet streams + $this->data = $ole->getStream($ole->wrkbook); + // Get summary information data + $this->summaryInformation = $ole->getStream($ole->summaryInformation); + // Get additional document summary information data + $this->documentSummaryInformation = $ole->getStream($ole->documentSummaryInformation); + } + + /** + * Read summary information. + */ + private function readSummaryInformation(): void + { + if (!isset($this->summaryInformation)) { + return; + } + + // offset: 0; size: 2; must be 0xFE 0xFF (UTF-16 LE byte order mark) + // offset: 2; size: 2; + // offset: 4; size: 2; OS version + // offset: 6; size: 2; OS indicator + // offset: 8; size: 16 + // offset: 24; size: 4; section count + $secCount = self::getInt4d($this->summaryInformation, 24); + + // offset: 28; size: 16; first section's class id: e0 85 9f f2 f9 4f 68 10 ab 91 08 00 2b 27 b3 d9 + // offset: 44; size: 4 + $secOffset = self::getInt4d($this->summaryInformation, 44); + + // section header + // offset: $secOffset; size: 4; section length + $secLength = self::getInt4d($this->summaryInformation, $secOffset); + + // offset: $secOffset+4; size: 4; property count + $countProperties = self::getInt4d($this->summaryInformation, $secOffset + 4); + + // initialize code page (used to resolve string values) + $codePage = 'CP1252'; + + // offset: ($secOffset+8); size: var + // loop through property decarations and properties + for ($i = 0; $i < $countProperties; ++$i) { + // offset: ($secOffset+8) + (8 * $i); size: 4; property ID + $id = self::getInt4d($this->summaryInformation, ($secOffset + 8) + (8 * $i)); + + // Use value of property id as appropriate + // offset: ($secOffset+12) + (8 * $i); size: 4; offset from beginning of section (48) + $offset = self::getInt4d($this->summaryInformation, ($secOffset + 12) + (8 * $i)); + + $type = self::getInt4d($this->summaryInformation, $secOffset + $offset); + + // initialize property value + $value = null; + + // extract property value based on property type + switch ($type) { + case 0x02: // 2 byte signed integer + $value = self::getUInt2d($this->summaryInformation, $secOffset + 4 + $offset); + + break; + case 0x03: // 4 byte signed integer + $value = self::getInt4d($this->summaryInformation, $secOffset + 4 + $offset); + + break; + case 0x13: // 4 byte unsigned integer + // not needed yet, fix later if necessary + break; + case 0x1E: // null-terminated string prepended by dword string length + $byteLength = self::getInt4d($this->summaryInformation, $secOffset + 4 + $offset); + $value = substr($this->summaryInformation, $secOffset + 8 + $offset, $byteLength); + $value = StringHelper::convertEncoding($value, 'UTF-8', $codePage); + $value = rtrim($value); + + break; + case 0x40: // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601) + // PHP-time + $value = OLE::OLE2LocalDate(substr($this->summaryInformation, $secOffset + 4 + $offset, 8)); + + break; + case 0x47: // Clipboard format + // not needed yet, fix later if necessary + break; + } + + switch ($id) { + case 0x01: // Code Page + $codePage = CodePage::numberToName((int) $value); + + break; + case 0x02: // Title + $this->spreadsheet->getProperties()->setTitle("$value"); + + break; + case 0x03: // Subject + $this->spreadsheet->getProperties()->setSubject("$value"); + + break; + case 0x04: // Author (Creator) + $this->spreadsheet->getProperties()->setCreator("$value"); + + break; + case 0x05: // Keywords + $this->spreadsheet->getProperties()->setKeywords("$value"); + + break; + case 0x06: // Comments (Description) + $this->spreadsheet->getProperties()->setDescription("$value"); + + break; + case 0x07: // Template + // Not supported by PhpSpreadsheet + break; + case 0x08: // Last Saved By (LastModifiedBy) + $this->spreadsheet->getProperties()->setLastModifiedBy("$value"); + + break; + case 0x09: // Revision + // Not supported by PhpSpreadsheet + break; + case 0x0A: // Total Editing Time + // Not supported by PhpSpreadsheet + break; + case 0x0B: // Last Printed + // Not supported by PhpSpreadsheet + break; + case 0x0C: // Created Date/Time + $this->spreadsheet->getProperties()->setCreated($value); + + break; + case 0x0D: // Modified Date/Time + $this->spreadsheet->getProperties()->setModified($value); + + break; + case 0x0E: // Number of Pages + // Not supported by PhpSpreadsheet + break; + case 0x0F: // Number of Words + // Not supported by PhpSpreadsheet + break; + case 0x10: // Number of Characters + // Not supported by PhpSpreadsheet + break; + case 0x11: // Thumbnail + // Not supported by PhpSpreadsheet + break; + case 0x12: // Name of creating application + // Not supported by PhpSpreadsheet + break; + case 0x13: // Security + // Not supported by PhpSpreadsheet + break; + } + } + } + + /** + * Read additional document summary information. + */ + private function readDocumentSummaryInformation(): void + { + if (!isset($this->documentSummaryInformation)) { + return; + } + + // offset: 0; size: 2; must be 0xFE 0xFF (UTF-16 LE byte order mark) + // offset: 2; size: 2; + // offset: 4; size: 2; OS version + // offset: 6; size: 2; OS indicator + // offset: 8; size: 16 + // offset: 24; size: 4; section count + $secCount = self::getInt4d($this->documentSummaryInformation, 24); + + // offset: 28; size: 16; first section's class id: 02 d5 cd d5 9c 2e 1b 10 93 97 08 00 2b 2c f9 ae + // offset: 44; size: 4; first section offset + $secOffset = self::getInt4d($this->documentSummaryInformation, 44); + + // section header + // offset: $secOffset; size: 4; section length + $secLength = self::getInt4d($this->documentSummaryInformation, $secOffset); + + // offset: $secOffset+4; size: 4; property count + $countProperties = self::getInt4d($this->documentSummaryInformation, $secOffset + 4); + + // initialize code page (used to resolve string values) + $codePage = 'CP1252'; + + // offset: ($secOffset+8); size: var + // loop through property decarations and properties + for ($i = 0; $i < $countProperties; ++$i) { + // offset: ($secOffset+8) + (8 * $i); size: 4; property ID + $id = self::getInt4d($this->documentSummaryInformation, ($secOffset + 8) + (8 * $i)); + + // Use value of property id as appropriate + // offset: 60 + 8 * $i; size: 4; offset from beginning of section (48) + $offset = self::getInt4d($this->documentSummaryInformation, ($secOffset + 12) + (8 * $i)); + + $type = self::getInt4d($this->documentSummaryInformation, $secOffset + $offset); + + // initialize property value + $value = null; + + // extract property value based on property type + switch ($type) { + case 0x02: // 2 byte signed integer + $value = self::getUInt2d($this->documentSummaryInformation, $secOffset + 4 + $offset); + + break; + case 0x03: // 4 byte signed integer + $value = self::getInt4d($this->documentSummaryInformation, $secOffset + 4 + $offset); + + break; + case 0x0B: // Boolean + $value = self::getUInt2d($this->documentSummaryInformation, $secOffset + 4 + $offset); + $value = ($value == 0 ? false : true); + + break; + case 0x13: // 4 byte unsigned integer + // not needed yet, fix later if necessary + break; + case 0x1E: // null-terminated string prepended by dword string length + $byteLength = self::getInt4d($this->documentSummaryInformation, $secOffset + 4 + $offset); + $value = substr($this->documentSummaryInformation, $secOffset + 8 + $offset, $byteLength); + $value = StringHelper::convertEncoding($value, 'UTF-8', $codePage); + $value = rtrim($value); + + break; + case 0x40: // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601) + // PHP-Time + $value = OLE::OLE2LocalDate(substr($this->documentSummaryInformation, $secOffset + 4 + $offset, 8)); + + break; + case 0x47: // Clipboard format + // not needed yet, fix later if necessary + break; + } + + switch ($id) { + case 0x01: // Code Page + $codePage = CodePage::numberToName((int) $value); + + break; + case 0x02: // Category + $this->spreadsheet->getProperties()->setCategory("$value"); + + break; + case 0x03: // Presentation Target + // Not supported by PhpSpreadsheet + break; + case 0x04: // Bytes + // Not supported by PhpSpreadsheet + break; + case 0x05: // Lines + // Not supported by PhpSpreadsheet + break; + case 0x06: // Paragraphs + // Not supported by PhpSpreadsheet + break; + case 0x07: // Slides + // Not supported by PhpSpreadsheet + break; + case 0x08: // Notes + // Not supported by PhpSpreadsheet + break; + case 0x09: // Hidden Slides + // Not supported by PhpSpreadsheet + break; + case 0x0A: // MM Clips + // Not supported by PhpSpreadsheet + break; + case 0x0B: // Scale Crop + // Not supported by PhpSpreadsheet + break; + case 0x0C: // Heading Pairs + // Not supported by PhpSpreadsheet + break; + case 0x0D: // Titles of Parts + // Not supported by PhpSpreadsheet + break; + case 0x0E: // Manager + $this->spreadsheet->getProperties()->setManager("$value"); + + break; + case 0x0F: // Company + $this->spreadsheet->getProperties()->setCompany("$value"); + + break; + case 0x10: // Links up-to-date + // Not supported by PhpSpreadsheet + break; + } + } + } + + /** + * Reads a general type of BIFF record. Does nothing except for moving stream pointer forward to next record. + */ + private function readDefault(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + + // move stream pointer to next record + $this->pos += 4 + $length; + } + + /** + * The NOTE record specifies a comment associated with a particular cell. In Excel 95 (BIFF7) and earlier versions, + * this record stores a note (cell note). This feature was significantly enhanced in Excel 97. + */ + private function readNote(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + if ($this->readDataOnly) { + return; + } + + $cellAddress = $this->readBIFF8CellAddress(substr($recordData, 0, 4)); + if ($this->version == self::XLS_BIFF8) { + $noteObjID = self::getUInt2d($recordData, 6); + $noteAuthor = self::readUnicodeStringLong(substr($recordData, 8)); + $noteAuthor = $noteAuthor['value']; + $this->cellNotes[$noteObjID] = [ + 'cellRef' => $cellAddress, + 'objectID' => $noteObjID, + 'author' => $noteAuthor, + ]; + } else { + $extension = false; + if ($cellAddress == '$B$65536') { + // If the address row is -1 and the column is 0, (which translates as $B$65536) then this is a continuation + // note from the previous cell annotation. We're not yet handling this, so annotations longer than the + // max 2048 bytes will probably throw a wobbly. + $row = self::getUInt2d($recordData, 0); + $extension = true; + $arrayKeys = array_keys($this->phpSheet->getComments()); + $cellAddress = array_pop($arrayKeys); + } + + $cellAddress = str_replace('$', '', $cellAddress); + $noteLength = self::getUInt2d($recordData, 4); + $noteText = trim(substr($recordData, 6)); + + if ($extension) { + // Concatenate this extension with the currently set comment for the cell + $comment = $this->phpSheet->getComment($cellAddress); + $commentText = $comment->getText()->getPlainText(); + $comment->setText($this->parseRichText($commentText . $noteText)); + } else { + // Set comment for the cell + $this->phpSheet->getComment($cellAddress)->setText($this->parseRichText($noteText)); +// ->setAuthor($author) + } + } + } + + /** + * The TEXT Object record contains the text associated with a cell annotation. + */ + private function readTextObject(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + if ($this->readDataOnly) { + return; + } + + // recordData consists of an array of subrecords looking like this: + // grbit: 2 bytes; Option Flags + // rot: 2 bytes; rotation + // cchText: 2 bytes; length of the text (in the first continue record) + // cbRuns: 2 bytes; length of the formatting (in the second continue record) + // followed by the continuation records containing the actual text and formatting + $grbitOpts = self::getUInt2d($recordData, 0); + $rot = self::getUInt2d($recordData, 2); + $cchText = self::getUInt2d($recordData, 10); + $cbRuns = self::getUInt2d($recordData, 12); + $text = $this->getSplicedRecordData(); + + $textByte = $text['spliceOffsets'][1] - $text['spliceOffsets'][0] - 1; + $textStr = substr($text['recordData'], $text['spliceOffsets'][0] + 1, $textByte); + // get 1 byte + $is16Bit = ord($text['recordData'][0]); + // it is possible to use a compressed format, + // which omits the high bytes of all characters, if they are all zero + if (($is16Bit & 0x01) === 0) { + $textStr = StringHelper::ConvertEncoding($textStr, 'UTF-8', 'ISO-8859-1'); + } else { + $textStr = $this->decodeCodepage($textStr); + } + + $this->textObjects[$this->textObjRef] = [ + 'text' => $textStr, + 'format' => substr($text['recordData'], $text['spliceOffsets'][1], $cbRuns), + 'alignment' => $grbitOpts, + 'rotation' => $rot, + ]; + } + + /** + * Read BOF. + */ + private function readBof(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = substr($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + // offset: 2; size: 2; type of the following data + $substreamType = self::getUInt2d($recordData, 2); + + switch ($substreamType) { + case self::XLS_WORKBOOKGLOBALS: + $version = self::getUInt2d($recordData, 0); + if (($version != self::XLS_BIFF8) && ($version != self::XLS_BIFF7)) { + throw new Exception('Cannot read this Excel file. Version is too old.'); + } + $this->version = $version; + + break; + case self::XLS_WORKSHEET: + // do not use this version information for anything + // it is unreliable (OpenOffice doc, 5.8), use only version information from the global stream + break; + default: + // substream, e.g. chart + // just skip the entire substream + do { + $code = self::getUInt2d($this->data, $this->pos); + $this->readDefault(); + } while ($code != self::XLS_TYPE_EOF && $this->pos < $this->dataSize); + + break; + } + } + + /** + * FILEPASS. + * + * This record is part of the File Protection Block. It + * contains information about the read/write password of the + * file. All record contents following this record will be + * encrypted. + * + * -- "OpenOffice.org's Documentation of the Microsoft + * Excel File Format" + * + * The decryption functions and objects used from here on in + * are based on the source of Spreadsheet-ParseExcel: + * https://metacpan.org/release/Spreadsheet-ParseExcel + */ + private function readFilepass(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + + if ($length != 54) { + throw new Exception('Unexpected file pass record length'); + } + + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + if (!$this->verifyPassword('VelvetSweatshop', substr($recordData, 6, 16), substr($recordData, 22, 16), substr($recordData, 38, 16), $this->md5Ctxt)) { + throw new Exception('Decryption password incorrect'); + } + + $this->encryption = self::MS_BIFF_CRYPTO_RC4; + + // Decryption required from the record after next onwards + $this->encryptionStartPos = $this->pos + self::getUInt2d($this->data, $this->pos + 2); + } + + /** + * Make an RC4 decryptor for the given block. + * + * @param int $block Block for which to create decrypto + * @param string $valContext MD5 context state + * + * @return Xls\RC4 + */ + private function makeKey($block, $valContext) + { + $pwarray = str_repeat("\0", 64); + + for ($i = 0; $i < 5; ++$i) { + $pwarray[$i] = $valContext[$i]; + } + + $pwarray[5] = chr($block & 0xff); + $pwarray[6] = chr(($block >> 8) & 0xff); + $pwarray[7] = chr(($block >> 16) & 0xff); + $pwarray[8] = chr(($block >> 24) & 0xff); + + $pwarray[9] = "\x80"; + $pwarray[56] = "\x48"; + + $md5 = new Xls\MD5(); + $md5->add($pwarray); + + $s = $md5->getContext(); + + return new Xls\RC4($s); + } + + /** + * Verify RC4 file password. + * + * @param string $password Password to check + * @param string $docid Document id + * @param string $salt_data Salt data + * @param string $hashedsalt_data Hashed salt data + * @param string $valContext Set to the MD5 context of the value + * + * @return bool Success + */ + private function verifyPassword($password, $docid, $salt_data, $hashedsalt_data, &$valContext) + { + $pwarray = str_repeat("\0", 64); + + $iMax = strlen($password); + for ($i = 0; $i < $iMax; ++$i) { + $o = ord(substr($password, $i, 1)); + $pwarray[2 * $i] = chr($o & 0xff); + $pwarray[2 * $i + 1] = chr(($o >> 8) & 0xff); + } + $pwarray[2 * $i] = chr(0x80); + $pwarray[56] = chr(($i << 4) & 0xff); + + $md5 = new Xls\MD5(); + $md5->add($pwarray); + + $mdContext1 = $md5->getContext(); + + $offset = 0; + $keyoffset = 0; + $tocopy = 5; + + $md5->reset(); + + while ($offset != 16) { + if ((64 - $offset) < 5) { + $tocopy = 64 - $offset; + } + for ($i = 0; $i <= $tocopy; ++$i) { + $pwarray[$offset + $i] = $mdContext1[$keyoffset + $i]; + } + $offset += $tocopy; + + if ($offset == 64) { + $md5->add($pwarray); + $keyoffset = $tocopy; + $tocopy = 5 - $tocopy; + $offset = 0; + + continue; + } + + $keyoffset = 0; + $tocopy = 5; + for ($i = 0; $i < 16; ++$i) { + $pwarray[$offset + $i] = $docid[$i]; + } + $offset += 16; + } + + $pwarray[16] = "\x80"; + for ($i = 0; $i < 47; ++$i) { + $pwarray[17 + $i] = "\0"; + } + $pwarray[56] = "\x80"; + $pwarray[57] = "\x0a"; + + $md5->add($pwarray); + $valContext = $md5->getContext(); + + $key = $this->makeKey(0, $valContext); + + $salt = $key->RC4($salt_data); + $hashedsalt = $key->RC4($hashedsalt_data); + + $salt .= "\x80" . str_repeat("\0", 47); + $salt[56] = "\x80"; + + $md5->reset(); + $md5->add($salt); + $mdContext2 = $md5->getContext(); + + return $mdContext2 == $hashedsalt; + } + + /** + * CODEPAGE. + * + * This record stores the text encoding used to write byte + * strings, stored as MS Windows code page identifier. + * + * -- "OpenOffice.org's Documentation of the Microsoft + * Excel File Format" + */ + private function readCodepage(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + // offset: 0; size: 2; code page identifier + $codepage = self::getUInt2d($recordData, 0); + + $this->codepage = CodePage::numberToName($codepage); + } + + /** + * DATEMODE. + * + * This record specifies the base date for displaying date + * values. All dates are stored as count of days past this + * base date. In BIFF2-BIFF4 this record is part of the + * Calculation Settings Block. In BIFF5-BIFF8 it is + * stored in the Workbook Globals Substream. + * + * -- "OpenOffice.org's Documentation of the Microsoft + * Excel File Format" + */ + private function readDateMode(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + // offset: 0; size: 2; 0 = base 1900, 1 = base 1904 + Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); + if (ord($recordData[0]) == 1) { + Date::setExcelCalendar(Date::CALENDAR_MAC_1904); + } + } + + /** + * Read a FONT record. + */ + private function readFont(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + if (!$this->readDataOnly) { + $objFont = new Font(); + + // offset: 0; size: 2; height of the font (in twips = 1/20 of a point) + $size = self::getUInt2d($recordData, 0); + $objFont->setSize($size / 20); + + // offset: 2; size: 2; option flags + // bit: 0; mask 0x0001; bold (redundant in BIFF5-BIFF8) + // bit: 1; mask 0x0002; italic + $isItalic = (0x0002 & self::getUInt2d($recordData, 2)) >> 1; + if ($isItalic) { + $objFont->setItalic(true); + } + + // bit: 2; mask 0x0004; underlined (redundant in BIFF5-BIFF8) + // bit: 3; mask 0x0008; strikethrough + $isStrike = (0x0008 & self::getUInt2d($recordData, 2)) >> 3; + if ($isStrike) { + $objFont->setStrikethrough(true); + } + + // offset: 4; size: 2; colour index + $colorIndex = self::getUInt2d($recordData, 4); + $objFont->colorIndex = $colorIndex; + + // offset: 6; size: 2; font weight + $weight = self::getUInt2d($recordData, 6); + switch ($weight) { + case 0x02BC: + $objFont->setBold(true); + + break; + } + + // offset: 8; size: 2; escapement type + $escapement = self::getUInt2d($recordData, 8); + CellFont::escapement($objFont, $escapement); + + // offset: 10; size: 1; underline type + $underlineType = ord($recordData[10]); + CellFont::underline($objFont, $underlineType); + + // offset: 11; size: 1; font family + // offset: 12; size: 1; character set + // offset: 13; size: 1; not used + // offset: 14; size: var; font name + if ($this->version == self::XLS_BIFF8) { + $string = self::readUnicodeStringShort(substr($recordData, 14)); + } else { + $string = $this->readByteStringShort(substr($recordData, 14)); + } + $objFont->setName($string['value']); + + $this->objFonts[] = $objFont; + } + } + + /** + * FORMAT. + * + * This record contains information about a number format. + * All FORMAT records occur together in a sequential list. + * + * In BIFF2-BIFF4 other records referencing a FORMAT record + * contain a zero-based index into this list. From BIFF5 on + * the FORMAT record contains the index itself that will be + * used by other records. + * + * -- "OpenOffice.org's Documentation of the Microsoft + * Excel File Format" + */ + private function readFormat(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + if (!$this->readDataOnly) { + $indexCode = self::getUInt2d($recordData, 0); + + if ($this->version == self::XLS_BIFF8) { + $string = self::readUnicodeStringLong(substr($recordData, 2)); + } else { + // BIFF7 + $string = $this->readByteStringShort(substr($recordData, 2)); + } + + $formatString = $string['value']; + // Apache Open Office sets wrong case writing to xls - issue 2239 + if ($formatString === 'GENERAL') { + $formatString = NumberFormat::FORMAT_GENERAL; + } + $this->formats[$indexCode] = $formatString; + } + } + + /** + * XF - Extended Format. + * + * This record contains formatting information for cells, rows, columns or styles. + * According to https://support.microsoft.com/en-us/help/147732 there are always at least 15 cell style XF + * and 1 cell XF. + * Inspection of Excel files generated by MS Office Excel shows that XF records 0-14 are cell style XF + * and XF record 15 is a cell XF + * We only read the first cell style XF and skip the remaining cell style XF records + * We read all cell XF records. + * + * -- "OpenOffice.org's Documentation of the Microsoft + * Excel File Format" + */ + private function readXf(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + $objStyle = new Style(); + + if (!$this->readDataOnly) { + // offset: 0; size: 2; Index to FONT record + if (self::getUInt2d($recordData, 0) < 4) { + $fontIndex = self::getUInt2d($recordData, 0); + } else { + // this has to do with that index 4 is omitted in all BIFF versions for some strange reason + // check the OpenOffice documentation of the FONT record + $fontIndex = self::getUInt2d($recordData, 0) - 1; + } + $objStyle->setFont($this->objFonts[$fontIndex]); + + // offset: 2; size: 2; Index to FORMAT record + $numberFormatIndex = self::getUInt2d($recordData, 2); + if (isset($this->formats[$numberFormatIndex])) { + // then we have user-defined format code + $numberFormat = ['formatCode' => $this->formats[$numberFormatIndex]]; + } elseif (($code = NumberFormat::builtInFormatCode($numberFormatIndex)) !== '') { + // then we have built-in format code + $numberFormat = ['formatCode' => $code]; + } else { + // we set the general format code + $numberFormat = ['formatCode' => NumberFormat::FORMAT_GENERAL]; + } + $objStyle->getNumberFormat()->setFormatCode($numberFormat['formatCode']); + + // offset: 4; size: 2; XF type, cell protection, and parent style XF + // bit 2-0; mask 0x0007; XF_TYPE_PROT + $xfTypeProt = self::getUInt2d($recordData, 4); + // bit 0; mask 0x01; 1 = cell is locked + $isLocked = (0x01 & $xfTypeProt) >> 0; + $objStyle->getProtection()->setLocked($isLocked ? Protection::PROTECTION_INHERIT : Protection::PROTECTION_UNPROTECTED); + + // bit 1; mask 0x02; 1 = Formula is hidden + $isHidden = (0x02 & $xfTypeProt) >> 1; + $objStyle->getProtection()->setHidden($isHidden ? Protection::PROTECTION_PROTECTED : Protection::PROTECTION_UNPROTECTED); + + // bit 2; mask 0x04; 0 = Cell XF, 1 = Cell Style XF + $isCellStyleXf = (0x04 & $xfTypeProt) >> 2; + + // offset: 6; size: 1; Alignment and text break + // bit 2-0, mask 0x07; horizontal alignment + $horAlign = (0x07 & ord($recordData[6])) >> 0; + Xls\Style\CellAlignment::horizontal($objStyle->getAlignment(), $horAlign); + + // bit 3, mask 0x08; wrap text + $wrapText = (0x08 & ord($recordData[6])) >> 3; + Xls\Style\CellAlignment::wrap($objStyle->getAlignment(), $wrapText); + + // bit 6-4, mask 0x70; vertical alignment + $vertAlign = (0x70 & ord($recordData[6])) >> 4; + Xls\Style\CellAlignment::vertical($objStyle->getAlignment(), $vertAlign); + + if ($this->version == self::XLS_BIFF8) { + // offset: 7; size: 1; XF_ROTATION: Text rotation angle + $angle = ord($recordData[7]); + $rotation = 0; + if ($angle <= 90) { + $rotation = $angle; + } elseif ($angle <= 180) { + $rotation = 90 - $angle; + } elseif ($angle == Alignment::TEXTROTATION_STACK_EXCEL) { + $rotation = Alignment::TEXTROTATION_STACK_PHPSPREADSHEET; + } + $objStyle->getAlignment()->setTextRotation($rotation); + + // offset: 8; size: 1; Indentation, shrink to cell size, and text direction + // bit: 3-0; mask: 0x0F; indent level + $indent = (0x0F & ord($recordData[8])) >> 0; + $objStyle->getAlignment()->setIndent($indent); + + // bit: 4; mask: 0x10; 1 = shrink content to fit into cell + $shrinkToFit = (0x10 & ord($recordData[8])) >> 4; + switch ($shrinkToFit) { + case 0: + $objStyle->getAlignment()->setShrinkToFit(false); + + break; + case 1: + $objStyle->getAlignment()->setShrinkToFit(true); + + break; + } + + // offset: 9; size: 1; Flags used for attribute groups + + // offset: 10; size: 4; Cell border lines and background area + // bit: 3-0; mask: 0x0000000F; left style + if ($bordersLeftStyle = Xls\Style\Border::lookup((0x0000000F & self::getInt4d($recordData, 10)) >> 0)) { + $objStyle->getBorders()->getLeft()->setBorderStyle($bordersLeftStyle); + } + // bit: 7-4; mask: 0x000000F0; right style + if ($bordersRightStyle = Xls\Style\Border::lookup((0x000000F0 & self::getInt4d($recordData, 10)) >> 4)) { + $objStyle->getBorders()->getRight()->setBorderStyle($bordersRightStyle); + } + // bit: 11-8; mask: 0x00000F00; top style + if ($bordersTopStyle = Xls\Style\Border::lookup((0x00000F00 & self::getInt4d($recordData, 10)) >> 8)) { + $objStyle->getBorders()->getTop()->setBorderStyle($bordersTopStyle); + } + // bit: 15-12; mask: 0x0000F000; bottom style + if ($bordersBottomStyle = Xls\Style\Border::lookup((0x0000F000 & self::getInt4d($recordData, 10)) >> 12)) { + $objStyle->getBorders()->getBottom()->setBorderStyle($bordersBottomStyle); + } + // bit: 22-16; mask: 0x007F0000; left color + $objStyle->getBorders()->getLeft()->colorIndex = (0x007F0000 & self::getInt4d($recordData, 10)) >> 16; + + // bit: 29-23; mask: 0x3F800000; right color + $objStyle->getBorders()->getRight()->colorIndex = (0x3F800000 & self::getInt4d($recordData, 10)) >> 23; + + // bit: 30; mask: 0x40000000; 1 = diagonal line from top left to right bottom + $diagonalDown = (0x40000000 & self::getInt4d($recordData, 10)) >> 30 ? true : false; + + // bit: 31; mask: 0x80000000; 1 = diagonal line from bottom left to top right + $diagonalUp = (0x80000000 & self::getInt4d($recordData, 10)) >> 31 ? true : false; + + if ($diagonalUp == false && $diagonalDown == false) { + $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_NONE); + } elseif ($diagonalUp == true && $diagonalDown == false) { + $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_UP); + } elseif ($diagonalUp == false && $diagonalDown == true) { + $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_DOWN); + } elseif ($diagonalUp == true && $diagonalDown == true) { + $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_BOTH); + } + + // offset: 14; size: 4; + // bit: 6-0; mask: 0x0000007F; top color + $objStyle->getBorders()->getTop()->colorIndex = (0x0000007F & self::getInt4d($recordData, 14)) >> 0; + + // bit: 13-7; mask: 0x00003F80; bottom color + $objStyle->getBorders()->getBottom()->colorIndex = (0x00003F80 & self::getInt4d($recordData, 14)) >> 7; + + // bit: 20-14; mask: 0x001FC000; diagonal color + $objStyle->getBorders()->getDiagonal()->colorIndex = (0x001FC000 & self::getInt4d($recordData, 14)) >> 14; + + // bit: 24-21; mask: 0x01E00000; diagonal style + if ($bordersDiagonalStyle = Xls\Style\Border::lookup((0x01E00000 & self::getInt4d($recordData, 14)) >> 21)) { + $objStyle->getBorders()->getDiagonal()->setBorderStyle($bordersDiagonalStyle); + } + + // bit: 31-26; mask: 0xFC000000 fill pattern + if ($fillType = Xls\Style\FillPattern::lookup((0xFC000000 & self::getInt4d($recordData, 14)) >> 26)) { + $objStyle->getFill()->setFillType($fillType); + } + // offset: 18; size: 2; pattern and background colour + // bit: 6-0; mask: 0x007F; color index for pattern color + $objStyle->getFill()->startcolorIndex = (0x007F & self::getUInt2d($recordData, 18)) >> 0; + + // bit: 13-7; mask: 0x3F80; color index for pattern background + $objStyle->getFill()->endcolorIndex = (0x3F80 & self::getUInt2d($recordData, 18)) >> 7; + } else { + // BIFF5 + + // offset: 7; size: 1; Text orientation and flags + $orientationAndFlags = ord($recordData[7]); + + // bit: 1-0; mask: 0x03; XF_ORIENTATION: Text orientation + $xfOrientation = (0x03 & $orientationAndFlags) >> 0; + switch ($xfOrientation) { + case 0: + $objStyle->getAlignment()->setTextRotation(0); + + break; + case 1: + $objStyle->getAlignment()->setTextRotation(Alignment::TEXTROTATION_STACK_PHPSPREADSHEET); + + break; + case 2: + $objStyle->getAlignment()->setTextRotation(90); + + break; + case 3: + $objStyle->getAlignment()->setTextRotation(-90); + + break; + } + + // offset: 8; size: 4; cell border lines and background area + $borderAndBackground = self::getInt4d($recordData, 8); + + // bit: 6-0; mask: 0x0000007F; color index for pattern color + $objStyle->getFill()->startcolorIndex = (0x0000007F & $borderAndBackground) >> 0; + + // bit: 13-7; mask: 0x00003F80; color index for pattern background + $objStyle->getFill()->endcolorIndex = (0x00003F80 & $borderAndBackground) >> 7; + + // bit: 21-16; mask: 0x003F0000; fill pattern + $objStyle->getFill()->setFillType(Xls\Style\FillPattern::lookup((0x003F0000 & $borderAndBackground) >> 16)); + + // bit: 24-22; mask: 0x01C00000; bottom line style + $objStyle->getBorders()->getBottom()->setBorderStyle(Xls\Style\Border::lookup((0x01C00000 & $borderAndBackground) >> 22)); + + // bit: 31-25; mask: 0xFE000000; bottom line color + $objStyle->getBorders()->getBottom()->colorIndex = (0xFE000000 & $borderAndBackground) >> 25; + + // offset: 12; size: 4; cell border lines + $borderLines = self::getInt4d($recordData, 12); + + // bit: 2-0; mask: 0x00000007; top line style + $objStyle->getBorders()->getTop()->setBorderStyle(Xls\Style\Border::lookup((0x00000007 & $borderLines) >> 0)); + + // bit: 5-3; mask: 0x00000038; left line style + $objStyle->getBorders()->getLeft()->setBorderStyle(Xls\Style\Border::lookup((0x00000038 & $borderLines) >> 3)); + + // bit: 8-6; mask: 0x000001C0; right line style + $objStyle->getBorders()->getRight()->setBorderStyle(Xls\Style\Border::lookup((0x000001C0 & $borderLines) >> 6)); + + // bit: 15-9; mask: 0x0000FE00; top line color index + $objStyle->getBorders()->getTop()->colorIndex = (0x0000FE00 & $borderLines) >> 9; + + // bit: 22-16; mask: 0x007F0000; left line color index + $objStyle->getBorders()->getLeft()->colorIndex = (0x007F0000 & $borderLines) >> 16; + + // bit: 29-23; mask: 0x3F800000; right line color index + $objStyle->getBorders()->getRight()->colorIndex = (0x3F800000 & $borderLines) >> 23; + } + + // add cellStyleXf or cellXf and update mapping + if ($isCellStyleXf) { + // we only read one style XF record which is always the first + if ($this->xfIndex == 0) { + $this->spreadsheet->addCellStyleXf($objStyle); + $this->mapCellStyleXfIndex[$this->xfIndex] = 0; + } + } else { + // we read all cell XF records + $this->spreadsheet->addCellXf($objStyle); + $this->mapCellXfIndex[$this->xfIndex] = count($this->spreadsheet->getCellXfCollection()) - 1; + } + + // update XF index for when we read next record + ++$this->xfIndex; + } + } + + private function readXfExt(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + if (!$this->readDataOnly) { + // offset: 0; size: 2; 0x087D = repeated header + + // offset: 2; size: 2 + + // offset: 4; size: 8; not used + + // offset: 12; size: 2; record version + + // offset: 14; size: 2; index to XF record which this record modifies + $ixfe = self::getUInt2d($recordData, 14); + + // offset: 16; size: 2; not used + + // offset: 18; size: 2; number of extension properties that follow + $cexts = self::getUInt2d($recordData, 18); + + // start reading the actual extension data + $offset = 20; + while ($offset < $length) { + // extension type + $extType = self::getUInt2d($recordData, $offset); + + // extension length + $cb = self::getUInt2d($recordData, $offset + 2); + + // extension data + $extData = substr($recordData, $offset + 4, $cb); + + switch ($extType) { + case 4: // fill start color + $xclfType = self::getUInt2d($extData, 0); // color type + $xclrValue = substr($extData, 4, 4); // color value (value based on color type) + + if ($xclfType == 2) { + $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2])); + + // modify the relevant style property + if (isset($this->mapCellXfIndex[$ixfe])) { + $fill = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getFill(); + $fill->getStartColor()->setRGB($rgb); + $fill->startcolorIndex = null; // normal color index does not apply, discard + } + } + + break; + case 5: // fill end color + $xclfType = self::getUInt2d($extData, 0); // color type + $xclrValue = substr($extData, 4, 4); // color value (value based on color type) + + if ($xclfType == 2) { + $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2])); + + // modify the relevant style property + if (isset($this->mapCellXfIndex[$ixfe])) { + $fill = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getFill(); + $fill->getEndColor()->setRGB($rgb); + $fill->endcolorIndex = null; // normal color index does not apply, discard + } + } + + break; + case 7: // border color top + $xclfType = self::getUInt2d($extData, 0); // color type + $xclrValue = substr($extData, 4, 4); // color value (value based on color type) + + if ($xclfType == 2) { + $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2])); + + // modify the relevant style property + if (isset($this->mapCellXfIndex[$ixfe])) { + $top = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getTop(); + $top->getColor()->setRGB($rgb); + $top->colorIndex = null; // normal color index does not apply, discard + } + } + + break; + case 8: // border color bottom + $xclfType = self::getUInt2d($extData, 0); // color type + $xclrValue = substr($extData, 4, 4); // color value (value based on color type) + + if ($xclfType == 2) { + $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2])); + + // modify the relevant style property + if (isset($this->mapCellXfIndex[$ixfe])) { + $bottom = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getBottom(); + $bottom->getColor()->setRGB($rgb); + $bottom->colorIndex = null; // normal color index does not apply, discard + } + } + + break; + case 9: // border color left + $xclfType = self::getUInt2d($extData, 0); // color type + $xclrValue = substr($extData, 4, 4); // color value (value based on color type) + + if ($xclfType == 2) { + $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2])); + + // modify the relevant style property + if (isset($this->mapCellXfIndex[$ixfe])) { + $left = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getLeft(); + $left->getColor()->setRGB($rgb); + $left->colorIndex = null; // normal color index does not apply, discard + } + } + + break; + case 10: // border color right + $xclfType = self::getUInt2d($extData, 0); // color type + $xclrValue = substr($extData, 4, 4); // color value (value based on color type) + + if ($xclfType == 2) { + $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2])); + + // modify the relevant style property + if (isset($this->mapCellXfIndex[$ixfe])) { + $right = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getRight(); + $right->getColor()->setRGB($rgb); + $right->colorIndex = null; // normal color index does not apply, discard + } + } + + break; + case 11: // border color diagonal + $xclfType = self::getUInt2d($extData, 0); // color type + $xclrValue = substr($extData, 4, 4); // color value (value based on color type) + + if ($xclfType == 2) { + $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2])); + + // modify the relevant style property + if (isset($this->mapCellXfIndex[$ixfe])) { + $diagonal = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getDiagonal(); + $diagonal->getColor()->setRGB($rgb); + $diagonal->colorIndex = null; // normal color index does not apply, discard + } + } + + break; + case 13: // font color + $xclfType = self::getUInt2d($extData, 0); // color type + $xclrValue = substr($extData, 4, 4); // color value (value based on color type) + + if ($xclfType == 2) { + $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2])); + + // modify the relevant style property + if (isset($this->mapCellXfIndex[$ixfe])) { + $font = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getFont(); + $font->getColor()->setRGB($rgb); + $font->colorIndex = null; // normal color index does not apply, discard + } + } + + break; + } + + $offset += $cb; + } + } + } + + /** + * Read STYLE record. + */ + private function readStyle(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + if (!$this->readDataOnly) { + // offset: 0; size: 2; index to XF record and flag for built-in style + $ixfe = self::getUInt2d($recordData, 0); + + // bit: 11-0; mask 0x0FFF; index to XF record + $xfIndex = (0x0FFF & $ixfe) >> 0; + + // bit: 15; mask 0x8000; 0 = user-defined style, 1 = built-in style + $isBuiltIn = (bool) ((0x8000 & $ixfe) >> 15); + + if ($isBuiltIn) { + // offset: 2; size: 1; identifier for built-in style + $builtInId = ord($recordData[2]); + + switch ($builtInId) { + case 0x00: + // currently, we are not using this for anything + break; + default: + break; + } + } + // user-defined; not supported by PhpSpreadsheet + } + } + + /** + * Read PALETTE record. + */ + private function readPalette(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + if (!$this->readDataOnly) { + // offset: 0; size: 2; number of following colors + $nm = self::getUInt2d($recordData, 0); + + // list of RGB colors + for ($i = 0; $i < $nm; ++$i) { + $rgb = substr($recordData, 2 + 4 * $i, 4); + $this->palette[] = self::readRGB($rgb); + } + } + } + + /** + * SHEET. + * + * This record is located in the Workbook Globals + * Substream and represents a sheet inside the workbook. + * One SHEET record is written for each sheet. It stores the + * sheet name and a stream offset to the BOF record of the + * respective Sheet Substream within the Workbook Stream. + * + * -- "OpenOffice.org's Documentation of the Microsoft + * Excel File Format" + */ + private function readSheet(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // offset: 0; size: 4; absolute stream position of the BOF record of the sheet + // NOTE: not encrypted + $rec_offset = self::getInt4d($this->data, $this->pos + 4); + + // move stream pointer to next record + $this->pos += 4 + $length; + + // offset: 4; size: 1; sheet state + switch (ord($recordData[4])) { + case 0x00: + $sheetState = Worksheet::SHEETSTATE_VISIBLE; + + break; + case 0x01: + $sheetState = Worksheet::SHEETSTATE_HIDDEN; + + break; + case 0x02: + $sheetState = Worksheet::SHEETSTATE_VERYHIDDEN; + + break; + default: + $sheetState = Worksheet::SHEETSTATE_VISIBLE; + + break; + } + + // offset: 5; size: 1; sheet type + $sheetType = ord($recordData[5]); + + // offset: 6; size: var; sheet name + $rec_name = null; + if ($this->version == self::XLS_BIFF8) { + $string = self::readUnicodeStringShort(substr($recordData, 6)); + $rec_name = $string['value']; + } elseif ($this->version == self::XLS_BIFF7) { + $string = $this->readByteStringShort(substr($recordData, 6)); + $rec_name = $string['value']; + } + + $this->sheets[] = [ + 'name' => $rec_name, + 'offset' => $rec_offset, + 'sheetState' => $sheetState, + 'sheetType' => $sheetType, + ]; + } + + /** + * Read EXTERNALBOOK record. + */ + private function readExternalBook(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + // offset within record data + $offset = 0; + + // there are 4 types of records + if (strlen($recordData) > 4) { + // external reference + // offset: 0; size: 2; number of sheet names ($nm) + $nm = self::getUInt2d($recordData, 0); + $offset += 2; + + // offset: 2; size: var; encoded URL without sheet name (Unicode string, 16-bit length) + $encodedUrlString = self::readUnicodeStringLong(substr($recordData, 2)); + $offset += $encodedUrlString['size']; + + // offset: var; size: var; list of $nm sheet names (Unicode strings, 16-bit length) + $externalSheetNames = []; + for ($i = 0; $i < $nm; ++$i) { + $externalSheetNameString = self::readUnicodeStringLong(substr($recordData, $offset)); + $externalSheetNames[] = $externalSheetNameString['value']; + $offset += $externalSheetNameString['size']; + } + + // store the record data + $this->externalBooks[] = [ + 'type' => 'external', + 'encodedUrl' => $encodedUrlString['value'], + 'externalSheetNames' => $externalSheetNames, + ]; + } elseif (substr($recordData, 2, 2) == pack('CC', 0x01, 0x04)) { + // internal reference + // offset: 0; size: 2; number of sheet in this document + // offset: 2; size: 2; 0x01 0x04 + $this->externalBooks[] = [ + 'type' => 'internal', + ]; + } elseif (substr($recordData, 0, 4) == pack('vCC', 0x0001, 0x01, 0x3A)) { + // add-in function + // offset: 0; size: 2; 0x0001 + $this->externalBooks[] = [ + 'type' => 'addInFunction', + ]; + } elseif (substr($recordData, 0, 2) == pack('v', 0x0000)) { + // DDE links, OLE links + // offset: 0; size: 2; 0x0000 + // offset: 2; size: var; encoded source document name + $this->externalBooks[] = [ + 'type' => 'DDEorOLE', + ]; + } + } + + /** + * Read EXTERNNAME record. + */ + private function readExternName(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + // external sheet references provided for named cells + if ($this->version == self::XLS_BIFF8) { + // offset: 0; size: 2; options + $options = self::getUInt2d($recordData, 0); + + // offset: 2; size: 2; + + // offset: 4; size: 2; not used + + // offset: 6; size: var + $nameString = self::readUnicodeStringShort(substr($recordData, 6)); + + // offset: var; size: var; formula data + $offset = 6 + $nameString['size']; + $formula = $this->getFormulaFromStructure(substr($recordData, $offset)); + + $this->externalNames[] = [ + 'name' => $nameString['value'], + 'formula' => $formula, + ]; + } + } + + /** + * Read EXTERNSHEET record. + */ + private function readExternSheet(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + // external sheet references provided for named cells + if ($this->version == self::XLS_BIFF8) { + // offset: 0; size: 2; number of following ref structures + $nm = self::getUInt2d($recordData, 0); + for ($i = 0; $i < $nm; ++$i) { + $this->ref[] = [ + // offset: 2 + 6 * $i; index to EXTERNALBOOK record + 'externalBookIndex' => self::getUInt2d($recordData, 2 + 6 * $i), + // offset: 4 + 6 * $i; index to first sheet in EXTERNALBOOK record + 'firstSheetIndex' => self::getUInt2d($recordData, 4 + 6 * $i), + // offset: 6 + 6 * $i; index to last sheet in EXTERNALBOOK record + 'lastSheetIndex' => self::getUInt2d($recordData, 6 + 6 * $i), + ]; + } + } + } + + /** + * DEFINEDNAME. + * + * This record is part of a Link Table. It contains the name + * and the token array of an internal defined name. Token + * arrays of defined names contain tokens with aberrant + * token classes. + * + * -- "OpenOffice.org's Documentation of the Microsoft + * Excel File Format" + */ + private function readDefinedName(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + if ($this->version == self::XLS_BIFF8) { + // retrieves named cells + + // offset: 0; size: 2; option flags + $opts = self::getUInt2d($recordData, 0); + + // bit: 5; mask: 0x0020; 0 = user-defined name, 1 = built-in-name + $isBuiltInName = (0x0020 & $opts) >> 5; + + // offset: 2; size: 1; keyboard shortcut + + // offset: 3; size: 1; length of the name (character count) + $nlen = ord($recordData[3]); + + // offset: 4; size: 2; size of the formula data (it can happen that this is zero) + // note: there can also be additional data, this is not included in $flen + $flen = self::getUInt2d($recordData, 4); + + // offset: 8; size: 2; 0=Global name, otherwise index to sheet (1-based) + $scope = self::getUInt2d($recordData, 8); + + // offset: 14; size: var; Name (Unicode string without length field) + $string = self::readUnicodeString(substr($recordData, 14), $nlen); + + // offset: var; size: $flen; formula data + $offset = 14 + $string['size']; + $formulaStructure = pack('v', $flen) . substr($recordData, $offset); + + try { + $formula = $this->getFormulaFromStructure($formulaStructure); + } catch (PhpSpreadsheetException $e) { + $formula = ''; + } + + $this->definedname[] = [ + 'isBuiltInName' => $isBuiltInName, + 'name' => $string['value'], + 'formula' => $formula, + 'scope' => $scope, + ]; + } + } + + /** + * Read MSODRAWINGGROUP record. + */ + private function readMsoDrawingGroup(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + + // get spliced record data + $splicedRecordData = $this->getSplicedRecordData(); + $recordData = $splicedRecordData['recordData']; + + $this->drawingGroupData .= $recordData; + } + + /** + * SST - Shared String Table. + * + * This record contains a list of all strings used anywhere + * in the workbook. Each string occurs only once. The + * workbook uses indexes into the list to reference the + * strings. + * + * -- "OpenOffice.org's Documentation of the Microsoft + * Excel File Format" + */ + private function readSst(): void + { + // offset within (spliced) record data + $pos = 0; + + // Limit global SST position, further control for bad SST Length in BIFF8 data + $limitposSST = 0; + + // get spliced record data + $splicedRecordData = $this->getSplicedRecordData(); + + $recordData = $splicedRecordData['recordData']; + $spliceOffsets = $splicedRecordData['spliceOffsets']; + + // offset: 0; size: 4; total number of strings in the workbook + $pos += 4; + + // offset: 4; size: 4; number of following strings ($nm) + $nm = self::getInt4d($recordData, 4); + $pos += 4; + + // look up limit position + foreach ($spliceOffsets as $spliceOffset) { + // it can happen that the string is empty, therefore we need + // <= and not just < + if ($pos <= $spliceOffset) { + $limitposSST = $spliceOffset; + } + } + + // loop through the Unicode strings (16-bit length) + for ($i = 0; $i < $nm && $pos < $limitposSST; ++$i) { + // number of characters in the Unicode string + $numChars = self::getUInt2d($recordData, $pos); + $pos += 2; + + // option flags + $optionFlags = ord($recordData[$pos]); + ++$pos; + + // bit: 0; mask: 0x01; 0 = compressed; 1 = uncompressed + $isCompressed = (($optionFlags & 0x01) == 0); + + // bit: 2; mask: 0x02; 0 = ordinary; 1 = Asian phonetic + $hasAsian = (($optionFlags & 0x04) != 0); + + // bit: 3; mask: 0x03; 0 = ordinary; 1 = Rich-Text + $hasRichText = (($optionFlags & 0x08) != 0); + + $formattingRuns = 0; + if ($hasRichText) { + // number of Rich-Text formatting runs + $formattingRuns = self::getUInt2d($recordData, $pos); + $pos += 2; + } + + $extendedRunLength = 0; + if ($hasAsian) { + // size of Asian phonetic setting + $extendedRunLength = self::getInt4d($recordData, $pos); + $pos += 4; + } + + // expected byte length of character array if not split + $len = ($isCompressed) ? $numChars : $numChars * 2; + + // look up limit position - Check it again to be sure that no error occurs when parsing SST structure + $limitpos = null; + foreach ($spliceOffsets as $spliceOffset) { + // it can happen that the string is empty, therefore we need + // <= and not just < + if ($pos <= $spliceOffset) { + $limitpos = $spliceOffset; + + break; + } + } + + if ($pos + $len <= $limitpos) { + // character array is not split between records + + $retstr = substr($recordData, $pos, $len); + $pos += $len; + } else { + // character array is split between records + + // first part of character array + $retstr = substr($recordData, $pos, $limitpos - $pos); + + $bytesRead = $limitpos - $pos; + + // remaining characters in Unicode string + $charsLeft = $numChars - (($isCompressed) ? $bytesRead : ($bytesRead / 2)); + + $pos = $limitpos; + + // keep reading the characters + while ($charsLeft > 0) { + // look up next limit position, in case the string span more than one continue record + foreach ($spliceOffsets as $spliceOffset) { + if ($pos < $spliceOffset) { + $limitpos = $spliceOffset; + + break; + } + } + + // repeated option flags + // OpenOffice.org documentation 5.21 + $option = ord($recordData[$pos]); + ++$pos; + + if ($isCompressed && ($option == 0)) { + // 1st fragment compressed + // this fragment compressed + $len = min($charsLeft, $limitpos - $pos); + $retstr .= substr($recordData, $pos, $len); + $charsLeft -= $len; + $isCompressed = true; + } elseif (!$isCompressed && ($option != 0)) { + // 1st fragment uncompressed + // this fragment uncompressed + $len = min($charsLeft * 2, $limitpos - $pos); + $retstr .= substr($recordData, $pos, $len); + $charsLeft -= $len / 2; + $isCompressed = false; + } elseif (!$isCompressed && ($option == 0)) { + // 1st fragment uncompressed + // this fragment compressed + $len = min($charsLeft, $limitpos - $pos); + for ($j = 0; $j < $len; ++$j) { + $retstr .= $recordData[$pos + $j] + . chr(0); + } + $charsLeft -= $len; + $isCompressed = false; + } else { + // 1st fragment compressed + // this fragment uncompressed + $newstr = ''; + $jMax = strlen($retstr); + for ($j = 0; $j < $jMax; ++$j) { + $newstr .= $retstr[$j] . chr(0); + } + $retstr = $newstr; + $len = min($charsLeft * 2, $limitpos - $pos); + $retstr .= substr($recordData, $pos, $len); + $charsLeft -= $len / 2; + $isCompressed = false; + } + + $pos += $len; + } + } + + // convert to UTF-8 + $retstr = self::encodeUTF16($retstr, $isCompressed); + + // read additional Rich-Text information, if any + $fmtRuns = []; + if ($hasRichText) { + // list of formatting runs + for ($j = 0; $j < $formattingRuns; ++$j) { + // first formatted character; zero-based + $charPos = self::getUInt2d($recordData, $pos + $j * 4); + + // index to font record + $fontIndex = self::getUInt2d($recordData, $pos + 2 + $j * 4); + + $fmtRuns[] = [ + 'charPos' => $charPos, + 'fontIndex' => $fontIndex, + ]; + } + $pos += 4 * $formattingRuns; + } + + // read additional Asian phonetics information, if any + if ($hasAsian) { + // For Asian phonetic settings, we skip the extended string data + $pos += $extendedRunLength; + } + + // store the shared sting + $this->sst[] = [ + 'value' => $retstr, + 'fmtRuns' => $fmtRuns, + ]; + } + + // getSplicedRecordData() takes care of moving current position in data stream + } + + /** + * Read PRINTGRIDLINES record. + */ + private function readPrintGridlines(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) { + // offset: 0; size: 2; 0 = do not print sheet grid lines; 1 = print sheet gridlines + $printGridlines = (bool) self::getUInt2d($recordData, 0); + $this->phpSheet->setPrintGridlines($printGridlines); + } + } + + /** + * Read DEFAULTROWHEIGHT record. + */ + private function readDefaultRowHeight(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + // offset: 0; size: 2; option flags + // offset: 2; size: 2; default height for unused rows, (twips 1/20 point) + $height = self::getUInt2d($recordData, 2); + $this->phpSheet->getDefaultRowDimension()->setRowHeight($height / 20); + } + + /** + * Read SHEETPR record. + */ + private function readSheetPr(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + // offset: 0; size: 2 + + // bit: 6; mask: 0x0040; 0 = outline buttons above outline group + $isSummaryBelow = (0x0040 & self::getUInt2d($recordData, 0)) >> 6; + $this->phpSheet->setShowSummaryBelow($isSummaryBelow); + + // bit: 7; mask: 0x0080; 0 = outline buttons left of outline group + $isSummaryRight = (0x0080 & self::getUInt2d($recordData, 0)) >> 7; + $this->phpSheet->setShowSummaryRight($isSummaryRight); + + // bit: 8; mask: 0x100; 0 = scale printout in percent, 1 = fit printout to number of pages + // this corresponds to radio button setting in page setup dialog in Excel + $this->isFitToPages = (bool) ((0x0100 & self::getUInt2d($recordData, 0)) >> 8); + } + + /** + * Read HORIZONTALPAGEBREAKS record. + */ + private function readHorizontalPageBreaks(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) { + // offset: 0; size: 2; number of the following row index structures + $nm = self::getUInt2d($recordData, 0); + + // offset: 2; size: 6 * $nm; list of $nm row index structures + for ($i = 0; $i < $nm; ++$i) { + $r = self::getUInt2d($recordData, 2 + 6 * $i); + $cf = self::getUInt2d($recordData, 2 + 6 * $i + 2); + $cl = self::getUInt2d($recordData, 2 + 6 * $i + 4); + + // not sure why two column indexes are necessary? + $this->phpSheet->setBreakByColumnAndRow($cf + 1, $r, Worksheet::BREAK_ROW); + } + } + } + + /** + * Read VERTICALPAGEBREAKS record. + */ + private function readVerticalPageBreaks(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) { + // offset: 0; size: 2; number of the following column index structures + $nm = self::getUInt2d($recordData, 0); + + // offset: 2; size: 6 * $nm; list of $nm row index structures + for ($i = 0; $i < $nm; ++$i) { + $c = self::getUInt2d($recordData, 2 + 6 * $i); + $rf = self::getUInt2d($recordData, 2 + 6 * $i + 2); + $rl = self::getUInt2d($recordData, 2 + 6 * $i + 4); + + // not sure why two row indexes are necessary? + $this->phpSheet->setBreakByColumnAndRow($c + 1, $rf, Worksheet::BREAK_COLUMN); + } + } + } + + /** + * Read HEADER record. + */ + private function readHeader(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + if (!$this->readDataOnly) { + // offset: 0; size: var + // realized that $recordData can be empty even when record exists + if ($recordData) { + if ($this->version == self::XLS_BIFF8) { + $string = self::readUnicodeStringLong($recordData); + } else { + $string = $this->readByteStringShort($recordData); + } + + $this->phpSheet->getHeaderFooter()->setOddHeader($string['value']); + $this->phpSheet->getHeaderFooter()->setEvenHeader($string['value']); + } + } + } + + /** + * Read FOOTER record. + */ + private function readFooter(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + if (!$this->readDataOnly) { + // offset: 0; size: var + // realized that $recordData can be empty even when record exists + if ($recordData) { + if ($this->version == self::XLS_BIFF8) { + $string = self::readUnicodeStringLong($recordData); + } else { + $string = $this->readByteStringShort($recordData); + } + $this->phpSheet->getHeaderFooter()->setOddFooter($string['value']); + $this->phpSheet->getHeaderFooter()->setEvenFooter($string['value']); + } + } + } + + /** + * Read HCENTER record. + */ + private function readHcenter(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + if (!$this->readDataOnly) { + // offset: 0; size: 2; 0 = print sheet left aligned, 1 = print sheet centered horizontally + $isHorizontalCentered = (bool) self::getUInt2d($recordData, 0); + + $this->phpSheet->getPageSetup()->setHorizontalCentered($isHorizontalCentered); + } + } + + /** + * Read VCENTER record. + */ + private function readVcenter(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + if (!$this->readDataOnly) { + // offset: 0; size: 2; 0 = print sheet aligned at top page border, 1 = print sheet vertically centered + $isVerticalCentered = (bool) self::getUInt2d($recordData, 0); + + $this->phpSheet->getPageSetup()->setVerticalCentered($isVerticalCentered); + } + } + + /** + * Read LEFTMARGIN record. + */ + private function readLeftMargin(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + if (!$this->readDataOnly) { + // offset: 0; size: 8 + $this->phpSheet->getPageMargins()->setLeft(self::extractNumber($recordData)); + } + } + + /** + * Read RIGHTMARGIN record. + */ + private function readRightMargin(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + if (!$this->readDataOnly) { + // offset: 0; size: 8 + $this->phpSheet->getPageMargins()->setRight(self::extractNumber($recordData)); + } + } + + /** + * Read TOPMARGIN record. + */ + private function readTopMargin(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + if (!$this->readDataOnly) { + // offset: 0; size: 8 + $this->phpSheet->getPageMargins()->setTop(self::extractNumber($recordData)); + } + } + + /** + * Read BOTTOMMARGIN record. + */ + private function readBottomMargin(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + if (!$this->readDataOnly) { + // offset: 0; size: 8 + $this->phpSheet->getPageMargins()->setBottom(self::extractNumber($recordData)); + } + } + + /** + * Read PAGESETUP record. + */ + private function readPageSetup(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + if (!$this->readDataOnly) { + // offset: 0; size: 2; paper size + $paperSize = self::getUInt2d($recordData, 0); + + // offset: 2; size: 2; scaling factor + $scale = self::getUInt2d($recordData, 2); + + // offset: 6; size: 2; fit worksheet width to this number of pages, 0 = use as many as needed + $fitToWidth = self::getUInt2d($recordData, 6); + + // offset: 8; size: 2; fit worksheet height to this number of pages, 0 = use as many as needed + $fitToHeight = self::getUInt2d($recordData, 8); + + // offset: 10; size: 2; option flags + + // bit: 0; mask: 0x0001; 0=down then over, 1=over then down + $isOverThenDown = (0x0001 & self::getUInt2d($recordData, 10)); + + // bit: 1; mask: 0x0002; 0=landscape, 1=portrait + $isPortrait = (0x0002 & self::getUInt2d($recordData, 10)) >> 1; + + // bit: 2; mask: 0x0004; 1= paper size, scaling factor, paper orient. not init + // when this bit is set, do not use flags for those properties + $isNotInit = (0x0004 & self::getUInt2d($recordData, 10)) >> 2; + + if (!$isNotInit) { + $this->phpSheet->getPageSetup()->setPaperSize($paperSize); + $this->phpSheet->getPageSetup()->setPageOrder(((bool) $isOverThenDown) ? PageSetup::PAGEORDER_OVER_THEN_DOWN : PageSetup::PAGEORDER_DOWN_THEN_OVER); + $this->phpSheet->getPageSetup()->setOrientation(((bool) $isPortrait) ? PageSetup::ORIENTATION_PORTRAIT : PageSetup::ORIENTATION_LANDSCAPE); + + $this->phpSheet->getPageSetup()->setScale($scale, false); + $this->phpSheet->getPageSetup()->setFitToPage((bool) $this->isFitToPages); + $this->phpSheet->getPageSetup()->setFitToWidth($fitToWidth, false); + $this->phpSheet->getPageSetup()->setFitToHeight($fitToHeight, false); + } + + // offset: 16; size: 8; header margin (IEEE 754 floating-point value) + $marginHeader = self::extractNumber(substr($recordData, 16, 8)); + $this->phpSheet->getPageMargins()->setHeader($marginHeader); + + // offset: 24; size: 8; footer margin (IEEE 754 floating-point value) + $marginFooter = self::extractNumber(substr($recordData, 24, 8)); + $this->phpSheet->getPageMargins()->setFooter($marginFooter); + } + } + + /** + * PROTECT - Sheet protection (BIFF2 through BIFF8) + * if this record is omitted, then it also means no sheet protection. + */ + private function readProtect(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + if ($this->readDataOnly) { + return; + } + + // offset: 0; size: 2; + + // bit 0, mask 0x01; 1 = sheet is protected + $bool = (0x01 & self::getUInt2d($recordData, 0)) >> 0; + $this->phpSheet->getProtection()->setSheet((bool) $bool); + } + + /** + * SCENPROTECT. + */ + private function readScenProtect(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + if ($this->readDataOnly) { + return; + } + + // offset: 0; size: 2; + + // bit: 0, mask 0x01; 1 = scenarios are protected + $bool = (0x01 & self::getUInt2d($recordData, 0)) >> 0; + + $this->phpSheet->getProtection()->setScenarios((bool) $bool); + } + + /** + * OBJECTPROTECT. + */ + private function readObjectProtect(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + if ($this->readDataOnly) { + return; + } + + // offset: 0; size: 2; + + // bit: 0, mask 0x01; 1 = objects are protected + $bool = (0x01 & self::getUInt2d($recordData, 0)) >> 0; + + $this->phpSheet->getProtection()->setObjects((bool) $bool); + } + + /** + * PASSWORD - Sheet protection (hashed) password (BIFF2 through BIFF8). + */ + private function readPassword(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + if (!$this->readDataOnly) { + // offset: 0; size: 2; 16-bit hash value of password + $password = strtoupper(dechex(self::getUInt2d($recordData, 0))); // the hashed password + $this->phpSheet->getProtection()->setPassword($password, true); + } + } + + /** + * Read DEFCOLWIDTH record. + */ + private function readDefColWidth(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + // offset: 0; size: 2; default column width + $width = self::getUInt2d($recordData, 0); + if ($width != 8) { + $this->phpSheet->getDefaultColumnDimension()->setWidth($width); + } + } + + /** + * Read COLINFO record. + */ + private function readColInfo(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + if (!$this->readDataOnly) { + // offset: 0; size: 2; index to first column in range + $firstColumnIndex = self::getUInt2d($recordData, 0); + + // offset: 2; size: 2; index to last column in range + $lastColumnIndex = self::getUInt2d($recordData, 2); + + // offset: 4; size: 2; width of the column in 1/256 of the width of the zero character + $width = self::getUInt2d($recordData, 4); + + // offset: 6; size: 2; index to XF record for default column formatting + $xfIndex = self::getUInt2d($recordData, 6); + + // offset: 8; size: 2; option flags + // bit: 0; mask: 0x0001; 1= columns are hidden + $isHidden = (0x0001 & self::getUInt2d($recordData, 8)) >> 0; + + // bit: 10-8; mask: 0x0700; outline level of the columns (0 = no outline) + $level = (0x0700 & self::getUInt2d($recordData, 8)) >> 8; + + // bit: 12; mask: 0x1000; 1 = collapsed + $isCollapsed = (bool) ((0x1000 & self::getUInt2d($recordData, 8)) >> 12); + + // offset: 10; size: 2; not used + + for ($i = $firstColumnIndex + 1; $i <= $lastColumnIndex + 1; ++$i) { + if ($lastColumnIndex == 255 || $lastColumnIndex == 256) { + $this->phpSheet->getDefaultColumnDimension()->setWidth($width / 256); + + break; + } + $this->phpSheet->getColumnDimensionByColumn($i)->setWidth($width / 256); + $this->phpSheet->getColumnDimensionByColumn($i)->setVisible(!$isHidden); + $this->phpSheet->getColumnDimensionByColumn($i)->setOutlineLevel($level); + $this->phpSheet->getColumnDimensionByColumn($i)->setCollapsed($isCollapsed); + if (isset($this->mapCellXfIndex[$xfIndex])) { + $this->phpSheet->getColumnDimensionByColumn($i)->setXfIndex($this->mapCellXfIndex[$xfIndex]); + } + } + } + } + + /** + * ROW. + * + * This record contains the properties of a single row in a + * sheet. Rows and cells in a sheet are divided into blocks + * of 32 rows. + * + * -- "OpenOffice.org's Documentation of the Microsoft + * Excel File Format" + */ + private function readRow(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + if (!$this->readDataOnly) { + // offset: 0; size: 2; index of this row + $r = self::getUInt2d($recordData, 0); + + // offset: 2; size: 2; index to column of the first cell which is described by a cell record + + // offset: 4; size: 2; index to column of the last cell which is described by a cell record, increased by 1 + + // offset: 6; size: 2; + + // bit: 14-0; mask: 0x7FFF; height of the row, in twips = 1/20 of a point + $height = (0x7FFF & self::getUInt2d($recordData, 6)) >> 0; + + // bit: 15: mask: 0x8000; 0 = row has custom height; 1= row has default height + $useDefaultHeight = (0x8000 & self::getUInt2d($recordData, 6)) >> 15; + + if (!$useDefaultHeight) { + $this->phpSheet->getRowDimension($r + 1)->setRowHeight($height / 20); + } + + // offset: 8; size: 2; not used + + // offset: 10; size: 2; not used in BIFF5-BIFF8 + + // offset: 12; size: 4; option flags and default row formatting + + // bit: 2-0: mask: 0x00000007; outline level of the row + $level = (0x00000007 & self::getInt4d($recordData, 12)) >> 0; + $this->phpSheet->getRowDimension($r + 1)->setOutlineLevel($level); + + // bit: 4; mask: 0x00000010; 1 = outline group start or ends here... and is collapsed + $isCollapsed = (bool) ((0x00000010 & self::getInt4d($recordData, 12)) >> 4); + $this->phpSheet->getRowDimension($r + 1)->setCollapsed($isCollapsed); + + // bit: 5; mask: 0x00000020; 1 = row is hidden + $isHidden = (0x00000020 & self::getInt4d($recordData, 12)) >> 5; + $this->phpSheet->getRowDimension($r + 1)->setVisible(!$isHidden); + + // bit: 7; mask: 0x00000080; 1 = row has explicit format + $hasExplicitFormat = (0x00000080 & self::getInt4d($recordData, 12)) >> 7; + + // bit: 27-16; mask: 0x0FFF0000; only applies when hasExplicitFormat = 1; index to XF record + $xfIndex = (0x0FFF0000 & self::getInt4d($recordData, 12)) >> 16; + + if ($hasExplicitFormat && isset($this->mapCellXfIndex[$xfIndex])) { + $this->phpSheet->getRowDimension($r + 1)->setXfIndex($this->mapCellXfIndex[$xfIndex]); + } + } + } + + /** + * Read RK record + * This record represents a cell that contains an RK value + * (encoded integer or floating-point value). If a + * floating-point value cannot be encoded to an RK value, + * a NUMBER record will be written. This record replaces the + * record INTEGER written in BIFF2. + * + * -- "OpenOffice.org's Documentation of the Microsoft + * Excel File Format" + */ + private function readRk(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + // offset: 0; size: 2; index to row + $row = self::getUInt2d($recordData, 0); + + // offset: 2; size: 2; index to column + $column = self::getUInt2d($recordData, 2); + $columnString = Coordinate::stringFromColumnIndex($column + 1); + + // Read cell? + if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) { + // offset: 4; size: 2; index to XF record + $xfIndex = self::getUInt2d($recordData, 4); + + // offset: 6; size: 4; RK value + $rknum = self::getInt4d($recordData, 6); + $numValue = self::getIEEE754($rknum); + + $cell = $this->phpSheet->getCell($columnString . ($row + 1)); + if (!$this->readDataOnly && isset($this->mapCellXfIndex[$xfIndex])) { + // add style information + $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]); + } + + // add cell + $cell->setValueExplicit($numValue, DataType::TYPE_NUMERIC); + } + } + + /** + * Read LABELSST record + * This record represents a cell that contains a string. It + * replaces the LABEL record and RSTRING record used in + * BIFF2-BIFF5. + * + * -- "OpenOffice.org's Documentation of the Microsoft + * Excel File Format" + */ + private function readLabelSst(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + // offset: 0; size: 2; index to row + $row = self::getUInt2d($recordData, 0); + + // offset: 2; size: 2; index to column + $column = self::getUInt2d($recordData, 2); + $columnString = Coordinate::stringFromColumnIndex($column + 1); + + $emptyCell = true; + // Read cell? + if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) { + // offset: 4; size: 2; index to XF record + $xfIndex = self::getUInt2d($recordData, 4); + + // offset: 6; size: 4; index to SST record + $index = self::getInt4d($recordData, 6); + + // add cell + if (($fmtRuns = $this->sst[$index]['fmtRuns']) && !$this->readDataOnly) { + // then we should treat as rich text + $richText = new RichText(); + $charPos = 0; + $sstCount = count($this->sst[$index]['fmtRuns']); + for ($i = 0; $i <= $sstCount; ++$i) { + if (isset($fmtRuns[$i])) { + $text = StringHelper::substring($this->sst[$index]['value'], $charPos, $fmtRuns[$i]['charPos'] - $charPos); + $charPos = $fmtRuns[$i]['charPos']; + } else { + $text = StringHelper::substring($this->sst[$index]['value'], $charPos, StringHelper::countCharacters($this->sst[$index]['value'])); + } + + if (StringHelper::countCharacters($text) > 0) { + if ($i == 0) { // first text run, no style + $richText->createText($text); + } else { + $textRun = $richText->createTextRun($text); + if (isset($fmtRuns[$i - 1])) { + $fontIndex = $fmtRuns[$i - 1]['fontIndex']; + + if (array_key_exists($fontIndex, $this->objFonts) === false) { + $fontIndex = count($this->objFonts) - 1; + } + $textRun->setFont(clone $this->objFonts[$fontIndex]); + } + } + } + } + if ($this->readEmptyCells || trim($richText->getPlainText()) !== '') { + $cell = $this->phpSheet->getCell($columnString . ($row + 1)); + $cell->setValueExplicit($richText, DataType::TYPE_STRING); + $emptyCell = false; + } + } else { + if ($this->readEmptyCells || trim($this->sst[$index]['value']) !== '') { + $cell = $this->phpSheet->getCell($columnString . ($row + 1)); + $cell->setValueExplicit($this->sst[$index]['value'], DataType::TYPE_STRING); + $emptyCell = false; + } + } + + if (!$this->readDataOnly && !$emptyCell && isset($this->mapCellXfIndex[$xfIndex])) { + // add style information + $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]); + } + } + } + + /** + * Read MULRK record + * This record represents a cell range containing RK value + * cells. All cells are located in the same row. + * + * -- "OpenOffice.org's Documentation of the Microsoft + * Excel File Format" + */ + private function readMulRk(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + // offset: 0; size: 2; index to row + $row = self::getUInt2d($recordData, 0); + + // offset: 2; size: 2; index to first column + $colFirst = self::getUInt2d($recordData, 2); + + // offset: var; size: 2; index to last column + $colLast = self::getUInt2d($recordData, $length - 2); + $columns = $colLast - $colFirst + 1; + + // offset within record data + $offset = 4; + + for ($i = 1; $i <= $columns; ++$i) { + $columnString = Coordinate::stringFromColumnIndex($colFirst + $i); + + // Read cell? + if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) { + // offset: var; size: 2; index to XF record + $xfIndex = self::getUInt2d($recordData, $offset); + + // offset: var; size: 4; RK value + $numValue = self::getIEEE754(self::getInt4d($recordData, $offset + 2)); + $cell = $this->phpSheet->getCell($columnString . ($row + 1)); + if (!$this->readDataOnly && isset($this->mapCellXfIndex[$xfIndex])) { + // add style + $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]); + } + + // add cell value + $cell->setValueExplicit($numValue, DataType::TYPE_NUMERIC); + } + + $offset += 6; + } + } + + /** + * Read NUMBER record + * This record represents a cell that contains a + * floating-point value. + * + * -- "OpenOffice.org's Documentation of the Microsoft + * Excel File Format" + */ + private function readNumber(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + // offset: 0; size: 2; index to row + $row = self::getUInt2d($recordData, 0); + + // offset: 2; size 2; index to column + $column = self::getUInt2d($recordData, 2); + $columnString = Coordinate::stringFromColumnIndex($column + 1); + + // Read cell? + if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) { + // offset 4; size: 2; index to XF record + $xfIndex = self::getUInt2d($recordData, 4); + + $numValue = self::extractNumber(substr($recordData, 6, 8)); + + $cell = $this->phpSheet->getCell($columnString . ($row + 1)); + if (!$this->readDataOnly && isset($this->mapCellXfIndex[$xfIndex])) { + // add cell style + $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]); + } + + // add cell value + $cell->setValueExplicit($numValue, DataType::TYPE_NUMERIC); + } + } + + /** + * Read FORMULA record + perhaps a following STRING record if formula result is a string + * This record contains the token array and the result of a + * formula cell. + * + * -- "OpenOffice.org's Documentation of the Microsoft + * Excel File Format" + */ + private function readFormula(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + // offset: 0; size: 2; row index + $row = self::getUInt2d($recordData, 0); + + // offset: 2; size: 2; col index + $column = self::getUInt2d($recordData, 2); + $columnString = Coordinate::stringFromColumnIndex($column + 1); + + // offset: 20: size: variable; formula structure + $formulaStructure = substr($recordData, 20); + + // offset: 14: size: 2; option flags, recalculate always, recalculate on open etc. + $options = self::getUInt2d($recordData, 14); + + // bit: 0; mask: 0x0001; 1 = recalculate always + // bit: 1; mask: 0x0002; 1 = calculate on open + // bit: 2; mask: 0x0008; 1 = part of a shared formula + $isPartOfSharedFormula = (bool) (0x0008 & $options); + + // WARNING: + // We can apparently not rely on $isPartOfSharedFormula. Even when $isPartOfSharedFormula = true + // the formula data may be ordinary formula data, therefore we need to check + // explicitly for the tExp token (0x01) + $isPartOfSharedFormula = $isPartOfSharedFormula && ord($formulaStructure[2]) == 0x01; + + if ($isPartOfSharedFormula) { + // part of shared formula which means there will be a formula with a tExp token and nothing else + // get the base cell, grab tExp token + $baseRow = self::getUInt2d($formulaStructure, 3); + $baseCol = self::getUInt2d($formulaStructure, 5); + $this->baseCell = Coordinate::stringFromColumnIndex($baseCol + 1) . ($baseRow + 1); + } + + // Read cell? + if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) { + if ($isPartOfSharedFormula) { + // formula is added to this cell after the sheet has been read + $this->sharedFormulaParts[$columnString . ($row + 1)] = $this->baseCell; + } + + // offset: 16: size: 4; not used + + // offset: 4; size: 2; XF index + $xfIndex = self::getUInt2d($recordData, 4); + + // offset: 6; size: 8; result of the formula + if ((ord($recordData[6]) == 0) && (ord($recordData[12]) == 255) && (ord($recordData[13]) == 255)) { + // String formula. Result follows in appended STRING record + $dataType = DataType::TYPE_STRING; + + // read possible SHAREDFMLA record + $code = self::getUInt2d($this->data, $this->pos); + if ($code == self::XLS_TYPE_SHAREDFMLA) { + $this->readSharedFmla(); + } + + // read STRING record + $value = $this->readString(); + } elseif ( + (ord($recordData[6]) == 1) + && (ord($recordData[12]) == 255) + && (ord($recordData[13]) == 255) + ) { + // Boolean formula. Result is in +2; 0=false, 1=true + $dataType = DataType::TYPE_BOOL; + $value = (bool) ord($recordData[8]); + } elseif ( + (ord($recordData[6]) == 2) + && (ord($recordData[12]) == 255) + && (ord($recordData[13]) == 255) + ) { + // Error formula. Error code is in +2 + $dataType = DataType::TYPE_ERROR; + $value = Xls\ErrorCode::lookup(ord($recordData[8])); + } elseif ( + (ord($recordData[6]) == 3) + && (ord($recordData[12]) == 255) + && (ord($recordData[13]) == 255) + ) { + // Formula result is a null string + $dataType = DataType::TYPE_NULL; + $value = ''; + } else { + // forumla result is a number, first 14 bytes like _NUMBER record + $dataType = DataType::TYPE_NUMERIC; + $value = self::extractNumber(substr($recordData, 6, 8)); + } + + $cell = $this->phpSheet->getCell($columnString . ($row + 1)); + if (!$this->readDataOnly && isset($this->mapCellXfIndex[$xfIndex])) { + // add cell style + $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]); + } + + // store the formula + if (!$isPartOfSharedFormula) { + // not part of shared formula + // add cell value. If we can read formula, populate with formula, otherwise just used cached value + try { + if ($this->version != self::XLS_BIFF8) { + throw new Exception('Not BIFF8. Can only read BIFF8 formulas'); + } + $formula = $this->getFormulaFromStructure($formulaStructure); // get formula in human language + $cell->setValueExplicit('=' . $formula, DataType::TYPE_FORMULA); + } catch (PhpSpreadsheetException $e) { + $cell->setValueExplicit($value, $dataType); + } + } else { + if ($this->version == self::XLS_BIFF8) { + // do nothing at this point, formula id added later in the code + } else { + $cell->setValueExplicit($value, $dataType); + } + } + + // store the cached calculated value + $cell->setCalculatedValue($value); + } + } + + /** + * Read a SHAREDFMLA record. This function just stores the binary shared formula in the reader, + * which usually contains relative references. + * These will be used to construct the formula in each shared formula part after the sheet is read. + */ + private function readSharedFmla(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + // offset: 0, size: 6; cell range address of the area used by the shared formula, not used for anything + $cellRange = substr($recordData, 0, 6); + $cellRange = $this->readBIFF5CellRangeAddressFixed($cellRange); // note: even BIFF8 uses BIFF5 syntax + + // offset: 6, size: 1; not used + + // offset: 7, size: 1; number of existing FORMULA records for this shared formula + $no = ord($recordData[7]); + + // offset: 8, size: var; Binary token array of the shared formula + $formula = substr($recordData, 8); + + // at this point we only store the shared formula for later use + $this->sharedFormulas[$this->baseCell] = $formula; + } + + /** + * Read a STRING record from current stream position and advance the stream pointer to next record + * This record is used for storing result from FORMULA record when it is a string, and + * it occurs directly after the FORMULA record. + * + * @return string The string contents as UTF-8 + */ + private function readString() + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + if ($this->version == self::XLS_BIFF8) { + $string = self::readUnicodeStringLong($recordData); + $value = $string['value']; + } else { + $string = $this->readByteStringLong($recordData); + $value = $string['value']; + } + + return $value; + } + + /** + * Read BOOLERR record + * This record represents a Boolean value or error value + * cell. + * + * -- "OpenOffice.org's Documentation of the Microsoft + * Excel File Format" + */ + private function readBoolErr(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + // offset: 0; size: 2; row index + $row = self::getUInt2d($recordData, 0); + + // offset: 2; size: 2; column index + $column = self::getUInt2d($recordData, 2); + $columnString = Coordinate::stringFromColumnIndex($column + 1); + + // Read cell? + if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) { + // offset: 4; size: 2; index to XF record + $xfIndex = self::getUInt2d($recordData, 4); + + // offset: 6; size: 1; the boolean value or error value + $boolErr = ord($recordData[6]); + + // offset: 7; size: 1; 0=boolean; 1=error + $isError = ord($recordData[7]); + + $cell = $this->phpSheet->getCell($columnString . ($row + 1)); + switch ($isError) { + case 0: // boolean + $value = (bool) $boolErr; + + // add cell value + $cell->setValueExplicit($value, DataType::TYPE_BOOL); + + break; + case 1: // error type + $value = Xls\ErrorCode::lookup($boolErr); + + // add cell value + $cell->setValueExplicit($value, DataType::TYPE_ERROR); + + break; + } + + if (!$this->readDataOnly && isset($this->mapCellXfIndex[$xfIndex])) { + // add cell style + $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]); + } + } + } + + /** + * Read MULBLANK record + * This record represents a cell range of empty cells. All + * cells are located in the same row. + * + * -- "OpenOffice.org's Documentation of the Microsoft + * Excel File Format" + */ + private function readMulBlank(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + // offset: 0; size: 2; index to row + $row = self::getUInt2d($recordData, 0); + + // offset: 2; size: 2; index to first column + $fc = self::getUInt2d($recordData, 2); + + // offset: 4; size: 2 x nc; list of indexes to XF records + // add style information + if (!$this->readDataOnly && $this->readEmptyCells) { + for ($i = 0; $i < $length / 2 - 3; ++$i) { + $columnString = Coordinate::stringFromColumnIndex($fc + $i + 1); + + // Read cell? + if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) { + $xfIndex = self::getUInt2d($recordData, 4 + 2 * $i); + if (isset($this->mapCellXfIndex[$xfIndex])) { + $this->phpSheet->getCell($columnString . ($row + 1))->setXfIndex($this->mapCellXfIndex[$xfIndex]); + } + } + } + } + + // offset: 6; size 2; index to last column (not needed) + } + + /** + * Read LABEL record + * This record represents a cell that contains a string. In + * BIFF8 it is usually replaced by the LABELSST record. + * Excel still uses this record, if it copies unformatted + * text cells to the clipboard. + * + * -- "OpenOffice.org's Documentation of the Microsoft + * Excel File Format" + */ + private function readLabel(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + // offset: 0; size: 2; index to row + $row = self::getUInt2d($recordData, 0); + + // offset: 2; size: 2; index to column + $column = self::getUInt2d($recordData, 2); + $columnString = Coordinate::stringFromColumnIndex($column + 1); + + // Read cell? + if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) { + // offset: 4; size: 2; XF index + $xfIndex = self::getUInt2d($recordData, 4); + + // add cell value + // todo: what if string is very long? continue record + if ($this->version == self::XLS_BIFF8) { + $string = self::readUnicodeStringLong(substr($recordData, 6)); + $value = $string['value']; + } else { + $string = $this->readByteStringLong(substr($recordData, 6)); + $value = $string['value']; + } + if ($this->readEmptyCells || trim($value) !== '') { + $cell = $this->phpSheet->getCell($columnString . ($row + 1)); + $cell->setValueExplicit($value, DataType::TYPE_STRING); + + if (!$this->readDataOnly && isset($this->mapCellXfIndex[$xfIndex])) { + // add cell style + $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]); + } + } + } + } + + /** + * Read BLANK record. + */ + private function readBlank(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + // offset: 0; size: 2; row index + $row = self::getUInt2d($recordData, 0); + + // offset: 2; size: 2; col index + $col = self::getUInt2d($recordData, 2); + $columnString = Coordinate::stringFromColumnIndex($col + 1); + + // Read cell? + if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) { + // offset: 4; size: 2; XF index + $xfIndex = self::getUInt2d($recordData, 4); + + // add style information + if (!$this->readDataOnly && $this->readEmptyCells && isset($this->mapCellXfIndex[$xfIndex])) { + $this->phpSheet->getCell($columnString . ($row + 1))->setXfIndex($this->mapCellXfIndex[$xfIndex]); + } + } + } + + /** + * Read MSODRAWING record. + */ + private function readMsoDrawing(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + + // get spliced record data + $splicedRecordData = $this->getSplicedRecordData(); + $recordData = $splicedRecordData['recordData']; + + $this->drawingData .= $recordData; + } + + /** + * Read OBJ record. + */ + private function readObj(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + if ($this->readDataOnly || $this->version != self::XLS_BIFF8) { + return; + } + + // recordData consists of an array of subrecords looking like this: + // ft: 2 bytes; ftCmo type (0x15) + // cb: 2 bytes; size in bytes of ftCmo data + // ot: 2 bytes; Object Type + // id: 2 bytes; Object id number + // grbit: 2 bytes; Option Flags + // data: var; subrecord data + + // for now, we are just interested in the second subrecord containing the object type + $ftCmoType = self::getUInt2d($recordData, 0); + $cbCmoSize = self::getUInt2d($recordData, 2); + $otObjType = self::getUInt2d($recordData, 4); + $idObjID = self::getUInt2d($recordData, 6); + $grbitOpts = self::getUInt2d($recordData, 6); + + $this->objs[] = [ + 'ftCmoType' => $ftCmoType, + 'cbCmoSize' => $cbCmoSize, + 'otObjType' => $otObjType, + 'idObjID' => $idObjID, + 'grbitOpts' => $grbitOpts, + ]; + $this->textObjRef = $idObjID; + } + + /** + * Read WINDOW2 record. + */ + private function readWindow2(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + // offset: 0; size: 2; option flags + $options = self::getUInt2d($recordData, 0); + + // offset: 2; size: 2; index to first visible row + $firstVisibleRow = self::getUInt2d($recordData, 2); + + // offset: 4; size: 2; index to first visible colum + $firstVisibleColumn = self::getUInt2d($recordData, 4); + $zoomscaleInPageBreakPreview = 0; + $zoomscaleInNormalView = 0; + if ($this->version === self::XLS_BIFF8) { + // offset: 8; size: 2; not used + // offset: 10; size: 2; cached magnification factor in page break preview (in percent); 0 = Default (60%) + // offset: 12; size: 2; cached magnification factor in normal view (in percent); 0 = Default (100%) + // offset: 14; size: 4; not used + if (!isset($recordData[10])) { + $zoomscaleInPageBreakPreview = 0; + } else { + $zoomscaleInPageBreakPreview = self::getUInt2d($recordData, 10); + } + + if ($zoomscaleInPageBreakPreview === 0) { + $zoomscaleInPageBreakPreview = 60; + } + + if (!isset($recordData[12])) { + $zoomscaleInNormalView = 0; + } else { + $zoomscaleInNormalView = self::getUInt2d($recordData, 12); + } + + if ($zoomscaleInNormalView === 0) { + $zoomscaleInNormalView = 100; + } + } + + // bit: 1; mask: 0x0002; 0 = do not show gridlines, 1 = show gridlines + $showGridlines = (bool) ((0x0002 & $options) >> 1); + $this->phpSheet->setShowGridlines($showGridlines); + + // bit: 2; mask: 0x0004; 0 = do not show headers, 1 = show headers + $showRowColHeaders = (bool) ((0x0004 & $options) >> 2); + $this->phpSheet->setShowRowColHeaders($showRowColHeaders); + + // bit: 3; mask: 0x0008; 0 = panes are not frozen, 1 = panes are frozen + $this->frozen = (bool) ((0x0008 & $options) >> 3); + + // bit: 6; mask: 0x0040; 0 = columns from left to right, 1 = columns from right to left + $this->phpSheet->setRightToLeft((bool) ((0x0040 & $options) >> 6)); + + // bit: 10; mask: 0x0400; 0 = sheet not active, 1 = sheet active + $isActive = (bool) ((0x0400 & $options) >> 10); + if ($isActive) { + $this->spreadsheet->setActiveSheetIndex($this->spreadsheet->getIndex($this->phpSheet)); + } + + // bit: 11; mask: 0x0800; 0 = normal view, 1 = page break view + $isPageBreakPreview = (bool) ((0x0800 & $options) >> 11); + + //FIXME: set $firstVisibleRow and $firstVisibleColumn + + if ($this->phpSheet->getSheetView()->getView() !== SheetView::SHEETVIEW_PAGE_LAYOUT) { + //NOTE: this setting is inferior to page layout view(Excel2007-) + $view = $isPageBreakPreview ? SheetView::SHEETVIEW_PAGE_BREAK_PREVIEW : SheetView::SHEETVIEW_NORMAL; + $this->phpSheet->getSheetView()->setView($view); + if ($this->version === self::XLS_BIFF8) { + $zoomScale = $isPageBreakPreview ? $zoomscaleInPageBreakPreview : $zoomscaleInNormalView; + $this->phpSheet->getSheetView()->setZoomScale($zoomScale); + $this->phpSheet->getSheetView()->setZoomScaleNormal($zoomscaleInNormalView); + } + } + } + + /** + * Read PLV Record(Created by Excel2007 or upper). + */ + private function readPageLayoutView(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + // offset: 0; size: 2; rt + //->ignore + $rt = self::getUInt2d($recordData, 0); + // offset: 2; size: 2; grbitfr + //->ignore + $grbitFrt = self::getUInt2d($recordData, 2); + // offset: 4; size: 8; reserved + //->ignore + + // offset: 12; size 2; zoom scale + $wScalePLV = self::getUInt2d($recordData, 12); + // offset: 14; size 2; grbit + $grbit = self::getUInt2d($recordData, 14); + + // decomprise grbit + $fPageLayoutView = $grbit & 0x01; + $fRulerVisible = ($grbit >> 1) & 0x01; //no support + $fWhitespaceHidden = ($grbit >> 3) & 0x01; //no support + + if ($fPageLayoutView === 1) { + $this->phpSheet->getSheetView()->setView(SheetView::SHEETVIEW_PAGE_LAYOUT); + $this->phpSheet->getSheetView()->setZoomScale($wScalePLV); //set by Excel2007 only if SHEETVIEW_PAGE_LAYOUT + } + //otherwise, we cannot know whether SHEETVIEW_PAGE_LAYOUT or SHEETVIEW_PAGE_BREAK_PREVIEW. + } + + /** + * Read SCL record. + */ + private function readScl(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + // offset: 0; size: 2; numerator of the view magnification + $numerator = self::getUInt2d($recordData, 0); + + // offset: 2; size: 2; numerator of the view magnification + $denumerator = self::getUInt2d($recordData, 2); + + // set the zoom scale (in percent) + $this->phpSheet->getSheetView()->setZoomScale($numerator * 100 / $denumerator); + } + + /** + * Read PANE record. + */ + private function readPane(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + if (!$this->readDataOnly) { + // offset: 0; size: 2; position of vertical split + $px = self::getUInt2d($recordData, 0); + + // offset: 2; size: 2; position of horizontal split + $py = self::getUInt2d($recordData, 2); + + // offset: 4; size: 2; top most visible row in the bottom pane + $rwTop = self::getUInt2d($recordData, 4); + + // offset: 6; size: 2; first visible left column in the right pane + $colLeft = self::getUInt2d($recordData, 6); + + if ($this->frozen) { + // frozen panes + $cell = Coordinate::stringFromColumnIndex($px + 1) . ($py + 1); + $topLeftCell = Coordinate::stringFromColumnIndex($colLeft + 1) . ($rwTop + 1); + $this->phpSheet->freezePane($cell, $topLeftCell); + } + // unfrozen panes; split windows; not supported by PhpSpreadsheet core + } + } + + /** + * Read SELECTION record. There is one such record for each pane in the sheet. + */ + private function readSelection(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + if (!$this->readDataOnly) { + // offset: 0; size: 1; pane identifier + $paneId = ord($recordData[0]); + + // offset: 1; size: 2; index to row of the active cell + $r = self::getUInt2d($recordData, 1); + + // offset: 3; size: 2; index to column of the active cell + $c = self::getUInt2d($recordData, 3); + + // offset: 5; size: 2; index into the following cell range list to the + // entry that contains the active cell + $index = self::getUInt2d($recordData, 5); + + // offset: 7; size: var; cell range address list containing all selected cell ranges + $data = substr($recordData, 7); + $cellRangeAddressList = $this->readBIFF5CellRangeAddressList($data); // note: also BIFF8 uses BIFF5 syntax + + $selectedCells = $cellRangeAddressList['cellRangeAddresses'][0]; + + // first row '1' + last row '16384' indicates that full column is selected (apparently also in BIFF8!) + if (preg_match('/^([A-Z]+1\:[A-Z]+)16384$/', $selectedCells)) { + $selectedCells = preg_replace('/^([A-Z]+1\:[A-Z]+)16384$/', '${1}1048576', $selectedCells); + } + + // first row '1' + last row '65536' indicates that full column is selected + if (preg_match('/^([A-Z]+1\:[A-Z]+)65536$/', $selectedCells)) { + $selectedCells = preg_replace('/^([A-Z]+1\:[A-Z]+)65536$/', '${1}1048576', $selectedCells); + } + + // first column 'A' + last column 'IV' indicates that full row is selected + if (preg_match('/^(A\d+\:)IV(\d+)$/', $selectedCells)) { + $selectedCells = preg_replace('/^(A\d+\:)IV(\d+)$/', '${1}XFD${2}', $selectedCells); + } + + $this->phpSheet->setSelectedCells($selectedCells); + } + } + + private function includeCellRangeFiltered($cellRangeAddress) + { + $includeCellRange = true; + if ($this->getReadFilter() !== null) { + $includeCellRange = false; + $rangeBoundaries = Coordinate::getRangeBoundaries($cellRangeAddress); + ++$rangeBoundaries[1][0]; + for ($row = $rangeBoundaries[0][1]; $row <= $rangeBoundaries[1][1]; ++$row) { + for ($column = $rangeBoundaries[0][0]; $column != $rangeBoundaries[1][0]; ++$column) { + if ($this->getReadFilter()->readCell($column, $row, $this->phpSheet->getTitle())) { + $includeCellRange = true; + + break 2; + } + } + } + } + + return $includeCellRange; + } + + /** + * MERGEDCELLS. + * + * This record contains the addresses of merged cell ranges + * in the current sheet. + * + * -- "OpenOffice.org's Documentation of the Microsoft + * Excel File Format" + */ + private function readMergedCells(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) { + $cellRangeAddressList = $this->readBIFF8CellRangeAddressList($recordData); + foreach ($cellRangeAddressList['cellRangeAddresses'] as $cellRangeAddress) { + if ( + (strpos($cellRangeAddress, ':') !== false) && + ($this->includeCellRangeFiltered($cellRangeAddress)) + ) { + $this->phpSheet->mergeCells($cellRangeAddress); + } + } + } + } + + /** + * Read HYPERLINK record. + */ + private function readHyperLink(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer forward to next record + $this->pos += 4 + $length; + + if (!$this->readDataOnly) { + // offset: 0; size: 8; cell range address of all cells containing this hyperlink + try { + $cellRange = $this->readBIFF8CellRangeAddressFixed($recordData); + } catch (PhpSpreadsheetException $e) { + return; + } + + // offset: 8, size: 16; GUID of StdLink + + // offset: 24, size: 4; unknown value + + // offset: 28, size: 4; option flags + // bit: 0; mask: 0x00000001; 0 = no link or extant, 1 = file link or URL + $isFileLinkOrUrl = (0x00000001 & self::getUInt2d($recordData, 28)) >> 0; + + // bit: 1; mask: 0x00000002; 0 = relative path, 1 = absolute path or URL + $isAbsPathOrUrl = (0x00000001 & self::getUInt2d($recordData, 28)) >> 1; + + // bit: 2 (and 4); mask: 0x00000014; 0 = no description + $hasDesc = (0x00000014 & self::getUInt2d($recordData, 28)) >> 2; + + // bit: 3; mask: 0x00000008; 0 = no text, 1 = has text + $hasText = (0x00000008 & self::getUInt2d($recordData, 28)) >> 3; + + // bit: 7; mask: 0x00000080; 0 = no target frame, 1 = has target frame + $hasFrame = (0x00000080 & self::getUInt2d($recordData, 28)) >> 7; + + // bit: 8; mask: 0x00000100; 0 = file link or URL, 1 = UNC path (inc. server name) + $isUNC = (0x00000100 & self::getUInt2d($recordData, 28)) >> 8; + + // offset within record data + $offset = 32; + + if ($hasDesc) { + // offset: 32; size: var; character count of description text + $dl = self::getInt4d($recordData, 32); + // offset: 36; size: var; character array of description text, no Unicode string header, always 16-bit characters, zero terminated + $desc = self::encodeUTF16(substr($recordData, 36, 2 * ($dl - 1)), false); + $offset += 4 + 2 * $dl; + } + if ($hasFrame) { + $fl = self::getInt4d($recordData, $offset); + $offset += 4 + 2 * $fl; + } + + // detect type of hyperlink (there are 4 types) + $hyperlinkType = null; + + if ($isUNC) { + $hyperlinkType = 'UNC'; + } elseif (!$isFileLinkOrUrl) { + $hyperlinkType = 'workbook'; + } elseif (ord($recordData[$offset]) == 0x03) { + $hyperlinkType = 'local'; + } elseif (ord($recordData[$offset]) == 0xE0) { + $hyperlinkType = 'URL'; + } + + switch ($hyperlinkType) { + case 'URL': + // section 5.58.2: Hyperlink containing a URL + // e.g. http://example.org/index.php + + // offset: var; size: 16; GUID of URL Moniker + $offset += 16; + // offset: var; size: 4; size (in bytes) of character array of the URL including trailing zero word + $us = self::getInt4d($recordData, $offset); + $offset += 4; + // offset: var; size: $us; character array of the URL, no Unicode string header, always 16-bit characters, zero-terminated + $url = self::encodeUTF16(substr($recordData, $offset, $us - 2), false); + $nullOffset = strpos($url, chr(0x00)); + if ($nullOffset) { + $url = substr($url, 0, $nullOffset); + } + $url .= $hasText ? '#' : ''; + $offset += $us; + + break; + case 'local': + // section 5.58.3: Hyperlink to local file + // examples: + // mydoc.txt + // ../../somedoc.xls#Sheet!A1 + + // offset: var; size: 16; GUI of File Moniker + $offset += 16; + + // offset: var; size: 2; directory up-level count. + $upLevelCount = self::getUInt2d($recordData, $offset); + $offset += 2; + + // offset: var; size: 4; character count of the shortened file path and name, including trailing zero word + $sl = self::getInt4d($recordData, $offset); + $offset += 4; + + // offset: var; size: sl; character array of the shortened file path and name in 8.3-DOS-format (compressed Unicode string) + $shortenedFilePath = substr($recordData, $offset, $sl); + $shortenedFilePath = self::encodeUTF16($shortenedFilePath, true); + $shortenedFilePath = substr($shortenedFilePath, 0, -1); // remove trailing zero + + $offset += $sl; + + // offset: var; size: 24; unknown sequence + $offset += 24; + + // extended file path + // offset: var; size: 4; size of the following file link field including string lenth mark + $sz = self::getInt4d($recordData, $offset); + $offset += 4; + + // only present if $sz > 0 + if ($sz > 0) { + // offset: var; size: 4; size of the character array of the extended file path and name + $xl = self::getInt4d($recordData, $offset); + $offset += 4; + + // offset: var; size 2; unknown + $offset += 2; + + // offset: var; size $xl; character array of the extended file path and name. + $extendedFilePath = substr($recordData, $offset, $xl); + $extendedFilePath = self::encodeUTF16($extendedFilePath, false); + $offset += $xl; + } + + // construct the path + $url = str_repeat('..\\', $upLevelCount); + $url .= ($sz > 0) ? $extendedFilePath : $shortenedFilePath; // use extended path if available + $url .= $hasText ? '#' : ''; + + break; + case 'UNC': + // section 5.58.4: Hyperlink to a File with UNC (Universal Naming Convention) Path + // todo: implement + return; + case 'workbook': + // section 5.58.5: Hyperlink to the Current Workbook + // e.g. Sheet2!B1:C2, stored in text mark field + $url = 'sheet://'; + + break; + default: + return; + } + + if ($hasText) { + // offset: var; size: 4; character count of text mark including trailing zero word + $tl = self::getInt4d($recordData, $offset); + $offset += 4; + // offset: var; size: var; character array of the text mark without the # sign, no Unicode header, always 16-bit characters, zero-terminated + $text = self::encodeUTF16(substr($recordData, $offset, 2 * ($tl - 1)), false); + $url .= $text; + } + + // apply the hyperlink to all the relevant cells + foreach (Coordinate::extractAllCellReferencesInRange($cellRange) as $coordinate) { + $this->phpSheet->getCell($coordinate)->getHyperLink()->setUrl($url); + } + } + } + + /** + * Read DATAVALIDATIONS record. + */ + private function readDataValidations(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer forward to next record + $this->pos += 4 + $length; + } + + /** + * Read DATAVALIDATION record. + */ + private function readDataValidation(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer forward to next record + $this->pos += 4 + $length; + + if ($this->readDataOnly) { + return; + } + + // offset: 0; size: 4; Options + $options = self::getInt4d($recordData, 0); + + // bit: 0-3; mask: 0x0000000F; type + $type = (0x0000000F & $options) >> 0; + switch ($type) { + case 0x00: + $type = DataValidation::TYPE_NONE; + + break; + case 0x01: + $type = DataValidation::TYPE_WHOLE; + + break; + case 0x02: + $type = DataValidation::TYPE_DECIMAL; + + break; + case 0x03: + $type = DataValidation::TYPE_LIST; + + break; + case 0x04: + $type = DataValidation::TYPE_DATE; + + break; + case 0x05: + $type = DataValidation::TYPE_TIME; + + break; + case 0x06: + $type = DataValidation::TYPE_TEXTLENGTH; + + break; + case 0x07: + $type = DataValidation::TYPE_CUSTOM; + + break; + } + + // bit: 4-6; mask: 0x00000070; error type + $errorStyle = (0x00000070 & $options) >> 4; + switch ($errorStyle) { + case 0x00: + $errorStyle = DataValidation::STYLE_STOP; + + break; + case 0x01: + $errorStyle = DataValidation::STYLE_WARNING; + + break; + case 0x02: + $errorStyle = DataValidation::STYLE_INFORMATION; + + break; + } + + // bit: 7; mask: 0x00000080; 1= formula is explicit (only applies to list) + // I have only seen cases where this is 1 + $explicitFormula = (0x00000080 & $options) >> 7; + + // bit: 8; mask: 0x00000100; 1= empty cells allowed + $allowBlank = (0x00000100 & $options) >> 8; + + // bit: 9; mask: 0x00000200; 1= suppress drop down arrow in list type validity + $suppressDropDown = (0x00000200 & $options) >> 9; + + // bit: 18; mask: 0x00040000; 1= show prompt box if cell selected + $showInputMessage = (0x00040000 & $options) >> 18; + + // bit: 19; mask: 0x00080000; 1= show error box if invalid values entered + $showErrorMessage = (0x00080000 & $options) >> 19; + + // bit: 20-23; mask: 0x00F00000; condition operator + $operator = (0x00F00000 & $options) >> 20; + switch ($operator) { + case 0x00: + $operator = DataValidation::OPERATOR_BETWEEN; + + break; + case 0x01: + $operator = DataValidation::OPERATOR_NOTBETWEEN; + + break; + case 0x02: + $operator = DataValidation::OPERATOR_EQUAL; + + break; + case 0x03: + $operator = DataValidation::OPERATOR_NOTEQUAL; + + break; + case 0x04: + $operator = DataValidation::OPERATOR_GREATERTHAN; + + break; + case 0x05: + $operator = DataValidation::OPERATOR_LESSTHAN; + + break; + case 0x06: + $operator = DataValidation::OPERATOR_GREATERTHANOREQUAL; + + break; + case 0x07: + $operator = DataValidation::OPERATOR_LESSTHANOREQUAL; + + break; + } + + // offset: 4; size: var; title of the prompt box + $offset = 4; + $string = self::readUnicodeStringLong(substr($recordData, $offset)); + $promptTitle = $string['value'] !== chr(0) ? $string['value'] : ''; + $offset += $string['size']; + + // offset: var; size: var; title of the error box + $string = self::readUnicodeStringLong(substr($recordData, $offset)); + $errorTitle = $string['value'] !== chr(0) ? $string['value'] : ''; + $offset += $string['size']; + + // offset: var; size: var; text of the prompt box + $string = self::readUnicodeStringLong(substr($recordData, $offset)); + $prompt = $string['value'] !== chr(0) ? $string['value'] : ''; + $offset += $string['size']; + + // offset: var; size: var; text of the error box + $string = self::readUnicodeStringLong(substr($recordData, $offset)); + $error = $string['value'] !== chr(0) ? $string['value'] : ''; + $offset += $string['size']; + + // offset: var; size: 2; size of the formula data for the first condition + $sz1 = self::getUInt2d($recordData, $offset); + $offset += 2; + + // offset: var; size: 2; not used + $offset += 2; + + // offset: var; size: $sz1; formula data for first condition (without size field) + $formula1 = substr($recordData, $offset, $sz1); + $formula1 = pack('v', $sz1) . $formula1; // prepend the length + + try { + $formula1 = $this->getFormulaFromStructure($formula1); + + // in list type validity, null characters are used as item separators + if ($type == DataValidation::TYPE_LIST) { + $formula1 = str_replace(chr(0), ',', $formula1); + } + } catch (PhpSpreadsheetException $e) { + return; + } + $offset += $sz1; + + // offset: var; size: 2; size of the formula data for the first condition + $sz2 = self::getUInt2d($recordData, $offset); + $offset += 2; + + // offset: var; size: 2; not used + $offset += 2; + + // offset: var; size: $sz2; formula data for second condition (without size field) + $formula2 = substr($recordData, $offset, $sz2); + $formula2 = pack('v', $sz2) . $formula2; // prepend the length + + try { + $formula2 = $this->getFormulaFromStructure($formula2); + } catch (PhpSpreadsheetException $e) { + return; + } + $offset += $sz2; + + // offset: var; size: var; cell range address list with + $cellRangeAddressList = $this->readBIFF8CellRangeAddressList(substr($recordData, $offset)); + $cellRangeAddresses = $cellRangeAddressList['cellRangeAddresses']; + + foreach ($cellRangeAddresses as $cellRange) { + $stRange = $this->phpSheet->shrinkRangeToFit($cellRange); + foreach (Coordinate::extractAllCellReferencesInRange($stRange) as $coordinate) { + $objValidation = $this->phpSheet->getCell($coordinate)->getDataValidation(); + $objValidation->setType($type); + $objValidation->setErrorStyle($errorStyle); + $objValidation->setAllowBlank((bool) $allowBlank); + $objValidation->setShowInputMessage((bool) $showInputMessage); + $objValidation->setShowErrorMessage((bool) $showErrorMessage); + $objValidation->setShowDropDown(!$suppressDropDown); + $objValidation->setOperator($operator); + $objValidation->setErrorTitle($errorTitle); + $objValidation->setError($error); + $objValidation->setPromptTitle($promptTitle); + $objValidation->setPrompt($prompt); + $objValidation->setFormula1($formula1); + $objValidation->setFormula2($formula2); + } + } + } + + /** + * Read SHEETLAYOUT record. Stores sheet tab color information. + */ + private function readSheetLayout(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + // local pointer in record data + $offset = 0; + + if (!$this->readDataOnly) { + // offset: 0; size: 2; repeated record identifier 0x0862 + + // offset: 2; size: 10; not used + + // offset: 12; size: 4; size of record data + // Excel 2003 uses size of 0x14 (documented), Excel 2007 uses size of 0x28 (not documented?) + $sz = self::getInt4d($recordData, 12); + + switch ($sz) { + case 0x14: + // offset: 16; size: 2; color index for sheet tab + $colorIndex = self::getUInt2d($recordData, 16); + $color = Xls\Color::map($colorIndex, $this->palette, $this->version); + $this->phpSheet->getTabColor()->setRGB($color['rgb']); + + break; + case 0x28: + // TODO: Investigate structure for .xls SHEETLAYOUT record as saved by MS Office Excel 2007 + return; + + break; + } + } + } + + /** + * Read SHEETPROTECTION record (FEATHEADR). + */ + private function readSheetProtection(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + if ($this->readDataOnly) { + return; + } + + // offset: 0; size: 2; repeated record header + + // offset: 2; size: 2; FRT cell reference flag (=0 currently) + + // offset: 4; size: 8; Currently not used and set to 0 + + // offset: 12; size: 2; Shared feature type index (2=Enhanced Protetion, 4=SmartTag) + $isf = self::getUInt2d($recordData, 12); + if ($isf != 2) { + return; + } + + // offset: 14; size: 1; =1 since this is a feat header + + // offset: 15; size: 4; size of rgbHdrSData + + // rgbHdrSData, assume "Enhanced Protection" + // offset: 19; size: 2; option flags + $options = self::getUInt2d($recordData, 19); + + // bit: 0; mask 0x0001; 1 = user may edit objects, 0 = users must not edit objects + $bool = (0x0001 & $options) >> 0; + $this->phpSheet->getProtection()->setObjects(!$bool); + + // bit: 1; mask 0x0002; edit scenarios + $bool = (0x0002 & $options) >> 1; + $this->phpSheet->getProtection()->setScenarios(!$bool); + + // bit: 2; mask 0x0004; format cells + $bool = (0x0004 & $options) >> 2; + $this->phpSheet->getProtection()->setFormatCells(!$bool); + + // bit: 3; mask 0x0008; format columns + $bool = (0x0008 & $options) >> 3; + $this->phpSheet->getProtection()->setFormatColumns(!$bool); + + // bit: 4; mask 0x0010; format rows + $bool = (0x0010 & $options) >> 4; + $this->phpSheet->getProtection()->setFormatRows(!$bool); + + // bit: 5; mask 0x0020; insert columns + $bool = (0x0020 & $options) >> 5; + $this->phpSheet->getProtection()->setInsertColumns(!$bool); + + // bit: 6; mask 0x0040; insert rows + $bool = (0x0040 & $options) >> 6; + $this->phpSheet->getProtection()->setInsertRows(!$bool); + + // bit: 7; mask 0x0080; insert hyperlinks + $bool = (0x0080 & $options) >> 7; + $this->phpSheet->getProtection()->setInsertHyperlinks(!$bool); + + // bit: 8; mask 0x0100; delete columns + $bool = (0x0100 & $options) >> 8; + $this->phpSheet->getProtection()->setDeleteColumns(!$bool); + + // bit: 9; mask 0x0200; delete rows + $bool = (0x0200 & $options) >> 9; + $this->phpSheet->getProtection()->setDeleteRows(!$bool); + + // bit: 10; mask 0x0400; select locked cells + $bool = (0x0400 & $options) >> 10; + $this->phpSheet->getProtection()->setSelectLockedCells(!$bool); + + // bit: 11; mask 0x0800; sort cell range + $bool = (0x0800 & $options) >> 11; + $this->phpSheet->getProtection()->setSort(!$bool); + + // bit: 12; mask 0x1000; auto filter + $bool = (0x1000 & $options) >> 12; + $this->phpSheet->getProtection()->setAutoFilter(!$bool); + + // bit: 13; mask 0x2000; pivot tables + $bool = (0x2000 & $options) >> 13; + $this->phpSheet->getProtection()->setPivotTables(!$bool); + + // bit: 14; mask 0x4000; select unlocked cells + $bool = (0x4000 & $options) >> 14; + $this->phpSheet->getProtection()->setSelectUnlockedCells(!$bool); + + // offset: 21; size: 2; not used + } + + /** + * Read RANGEPROTECTION record + * Reading of this record is based on Microsoft Office Excel 97-2000 Binary File Format Specification, + * where it is referred to as FEAT record. + */ + private function readRangeProtection(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer to next record + $this->pos += 4 + $length; + + // local pointer in record data + $offset = 0; + + if (!$this->readDataOnly) { + $offset += 12; + + // offset: 12; size: 2; shared feature type, 2 = enhanced protection, 4 = smart tag + $isf = self::getUInt2d($recordData, 12); + if ($isf != 2) { + // we only read FEAT records of type 2 + return; + } + $offset += 2; + + $offset += 5; + + // offset: 19; size: 2; count of ref ranges this feature is on + $cref = self::getUInt2d($recordData, 19); + $offset += 2; + + $offset += 6; + + // offset: 27; size: 8 * $cref; list of cell ranges (like in hyperlink record) + $cellRanges = []; + for ($i = 0; $i < $cref; ++$i) { + try { + $cellRange = $this->readBIFF8CellRangeAddressFixed(substr($recordData, 27 + 8 * $i, 8)); + } catch (PhpSpreadsheetException $e) { + return; + } + $cellRanges[] = $cellRange; + $offset += 8; + } + + // offset: var; size: var; variable length of feature specific data + $rgbFeat = substr($recordData, $offset); + $offset += 4; + + // offset: var; size: 4; the encrypted password (only 16-bit although field is 32-bit) + $wPassword = self::getInt4d($recordData, $offset); + $offset += 4; + + // Apply range protection to sheet + if ($cellRanges) { + $this->phpSheet->protectCells(implode(' ', $cellRanges), strtoupper(dechex($wPassword)), true); + } + } + } + + /** + * Read a free CONTINUE record. Free CONTINUE record may be a camouflaged MSODRAWING record + * When MSODRAWING data on a sheet exceeds 8224 bytes, CONTINUE records are used instead. Undocumented. + * In this case, we must treat the CONTINUE record as a MSODRAWING record. + */ + private function readContinue(): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // check if we are reading drawing data + // this is in case a free CONTINUE record occurs in other circumstances we are unaware of + if ($this->drawingData == '') { + // move stream pointer to next record + $this->pos += 4 + $length; + + return; + } + + // check if record data is at least 4 bytes long, otherwise there is no chance this is MSODRAWING data + if ($length < 4) { + // move stream pointer to next record + $this->pos += 4 + $length; + + return; + } + + // dirty check to see if CONTINUE record could be a camouflaged MSODRAWING record + // look inside CONTINUE record to see if it looks like a part of an Escher stream + // we know that Escher stream may be split at least at + // 0xF003 MsofbtSpgrContainer + // 0xF004 MsofbtSpContainer + // 0xF00D MsofbtClientTextbox + $validSplitPoints = [0xF003, 0xF004, 0xF00D]; // add identifiers if we find more + + $splitPoint = self::getUInt2d($recordData, 2); + if (in_array($splitPoint, $validSplitPoints)) { + // get spliced record data (and move pointer to next record) + $splicedRecordData = $this->getSplicedRecordData(); + $this->drawingData .= $splicedRecordData['recordData']; + + return; + } + + // move stream pointer to next record + $this->pos += 4 + $length; + } + + /** + * Reads a record from current position in data stream and continues reading data as long as CONTINUE + * records are found. Splices the record data pieces and returns the combined string as if record data + * is in one piece. + * Moves to next current position in data stream to start of next record different from a CONtINUE record. + * + * @return array + */ + private function getSplicedRecordData() + { + $data = ''; + $spliceOffsets = []; + + $i = 0; + $spliceOffsets[0] = 0; + + do { + ++$i; + + // offset: 0; size: 2; identifier + $identifier = self::getUInt2d($this->data, $this->pos); + // offset: 2; size: 2; length + $length = self::getUInt2d($this->data, $this->pos + 2); + $data .= $this->readRecordData($this->data, $this->pos + 4, $length); + + $spliceOffsets[$i] = $spliceOffsets[$i - 1] + $length; + + $this->pos += 4 + $length; + $nextIdentifier = self::getUInt2d($this->data, $this->pos); + } while ($nextIdentifier == self::XLS_TYPE_CONTINUE); + + return [ + 'recordData' => $data, + 'spliceOffsets' => $spliceOffsets, + ]; + } + + /** + * Convert formula structure into human readable Excel formula like 'A3+A5*5'. + * + * @param string $formulaStructure The complete binary data for the formula + * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas + * + * @return string Human readable formula + */ + private function getFormulaFromStructure($formulaStructure, $baseCell = 'A1') + { + // offset: 0; size: 2; size of the following formula data + $sz = self::getUInt2d($formulaStructure, 0); + + // offset: 2; size: sz + $formulaData = substr($formulaStructure, 2, $sz); + + // offset: 2 + sz; size: variable (optional) + if (strlen($formulaStructure) > 2 + $sz) { + $additionalData = substr($formulaStructure, 2 + $sz); + } else { + $additionalData = ''; + } + + return $this->getFormulaFromData($formulaData, $additionalData, $baseCell); + } + + /** + * Take formula data and additional data for formula and return human readable formula. + * + * @param string $formulaData The binary data for the formula itself + * @param string $additionalData Additional binary data going with the formula + * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas + * + * @return string Human readable formula + */ + private function getFormulaFromData($formulaData, $additionalData = '', $baseCell = 'A1') + { + // start parsing the formula data + $tokens = []; + + while (strlen($formulaData) > 0 && $token = $this->getNextToken($formulaData, $baseCell)) { + $tokens[] = $token; + $formulaData = substr($formulaData, $token['size']); + } + + $formulaString = $this->createFormulaFromTokens($tokens, $additionalData); + + return $formulaString; + } + + /** + * Take array of tokens together with additional data for formula and return human readable formula. + * + * @param array $tokens + * @param string $additionalData Additional binary data going with the formula + * + * @return string Human readable formula + */ + private function createFormulaFromTokens($tokens, $additionalData) + { + // empty formula? + if (empty($tokens)) { + return ''; + } + + $formulaStrings = []; + foreach ($tokens as $token) { + // initialize spaces + $space0 = $space0 ?? ''; // spaces before next token, not tParen + $space1 = $space1 ?? ''; // carriage returns before next token, not tParen + $space2 = $space2 ?? ''; // spaces before opening parenthesis + $space3 = $space3 ?? ''; // carriage returns before opening parenthesis + $space4 = $space4 ?? ''; // spaces before closing parenthesis + $space5 = $space5 ?? ''; // carriage returns before closing parenthesis + + switch ($token['name']) { + case 'tAdd': // addition + case 'tConcat': // addition + case 'tDiv': // division + case 'tEQ': // equality + case 'tGE': // greater than or equal + case 'tGT': // greater than + case 'tIsect': // intersection + case 'tLE': // less than or equal + case 'tList': // less than or equal + case 'tLT': // less than + case 'tMul': // multiplication + case 'tNE': // multiplication + case 'tPower': // power + case 'tRange': // range + case 'tSub': // subtraction + $op2 = array_pop($formulaStrings); + $op1 = array_pop($formulaStrings); + $formulaStrings[] = "$op1$space1$space0{$token['data']}$op2"; + unset($space0, $space1); + + break; + case 'tUplus': // unary plus + case 'tUminus': // unary minus + $op = array_pop($formulaStrings); + $formulaStrings[] = "$space1$space0{$token['data']}$op"; + unset($space0, $space1); + + break; + case 'tPercent': // percent sign + $op = array_pop($formulaStrings); + $formulaStrings[] = "$op$space1$space0{$token['data']}"; + unset($space0, $space1); + + break; + case 'tAttrVolatile': // indicates volatile function + case 'tAttrIf': + case 'tAttrSkip': + case 'tAttrChoose': + // token is only important for Excel formula evaluator + // do nothing + break; + case 'tAttrSpace': // space / carriage return + // space will be used when next token arrives, do not alter formulaString stack + switch ($token['data']['spacetype']) { + case 'type0': + $space0 = str_repeat(' ', $token['data']['spacecount']); + + break; + case 'type1': + $space1 = str_repeat("\n", $token['data']['spacecount']); + + break; + case 'type2': + $space2 = str_repeat(' ', $token['data']['spacecount']); + + break; + case 'type3': + $space3 = str_repeat("\n", $token['data']['spacecount']); + + break; + case 'type4': + $space4 = str_repeat(' ', $token['data']['spacecount']); + + break; + case 'type5': + $space5 = str_repeat("\n", $token['data']['spacecount']); + + break; + } + + break; + case 'tAttrSum': // SUM function with one parameter + $op = array_pop($formulaStrings); + $formulaStrings[] = "{$space1}{$space0}SUM($op)"; + unset($space0, $space1); + + break; + case 'tFunc': // function with fixed number of arguments + case 'tFuncV': // function with variable number of arguments + if ($token['data']['function'] != '') { + // normal function + $ops = []; // array of operators + for ($i = 0; $i < $token['data']['args']; ++$i) { + $ops[] = array_pop($formulaStrings); + } + $ops = array_reverse($ops); + $formulaStrings[] = "$space1$space0{$token['data']['function']}(" . implode(',', $ops) . ')'; + unset($space0, $space1); + } else { + // add-in function + $ops = []; // array of operators + for ($i = 0; $i < $token['data']['args'] - 1; ++$i) { + $ops[] = array_pop($formulaStrings); + } + $ops = array_reverse($ops); + $function = array_pop($formulaStrings); + $formulaStrings[] = "$space1$space0$function(" . implode(',', $ops) . ')'; + unset($space0, $space1); + } + + break; + case 'tParen': // parenthesis + $expression = array_pop($formulaStrings); + $formulaStrings[] = "$space3$space2($expression$space5$space4)"; + unset($space2, $space3, $space4, $space5); + + break; + case 'tArray': // array constant + $constantArray = self::readBIFF8ConstantArray($additionalData); + $formulaStrings[] = $space1 . $space0 . $constantArray['value']; + $additionalData = substr($additionalData, $constantArray['size']); // bite of chunk of additional data + unset($space0, $space1); + + break; + case 'tMemArea': + // bite off chunk of additional data + $cellRangeAddressList = $this->readBIFF8CellRangeAddressList($additionalData); + $additionalData = substr($additionalData, $cellRangeAddressList['size']); + $formulaStrings[] = "$space1$space0{$token['data']}"; + unset($space0, $space1); + + break; + case 'tArea': // cell range address + case 'tBool': // boolean + case 'tErr': // error code + case 'tInt': // integer + case 'tMemErr': + case 'tMemFunc': + case 'tMissArg': + case 'tName': + case 'tNameX': + case 'tNum': // number + case 'tRef': // single cell reference + case 'tRef3d': // 3d cell reference + case 'tArea3d': // 3d cell range reference + case 'tRefN': + case 'tAreaN': + case 'tStr': // string + $formulaStrings[] = "$space1$space0{$token['data']}"; + unset($space0, $space1); + + break; + } + } + $formulaString = $formulaStrings[0]; + + return $formulaString; + } + + /** + * Fetch next token from binary formula data. + * + * @param string $formulaData Formula data + * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas + * + * @return array + */ + private function getNextToken($formulaData, $baseCell = 'A1') + { + // offset: 0; size: 1; token id + $id = ord($formulaData[0]); // token id + $name = false; // initialize token name + + switch ($id) { + case 0x03: + $name = 'tAdd'; + $size = 1; + $data = '+'; + + break; + case 0x04: + $name = 'tSub'; + $size = 1; + $data = '-'; + + break; + case 0x05: + $name = 'tMul'; + $size = 1; + $data = '*'; + + break; + case 0x06: + $name = 'tDiv'; + $size = 1; + $data = '/'; + + break; + case 0x07: + $name = 'tPower'; + $size = 1; + $data = '^'; + + break; + case 0x08: + $name = 'tConcat'; + $size = 1; + $data = '&'; + + break; + case 0x09: + $name = 'tLT'; + $size = 1; + $data = '<'; + + break; + case 0x0A: + $name = 'tLE'; + $size = 1; + $data = '<='; + + break; + case 0x0B: + $name = 'tEQ'; + $size = 1; + $data = '='; + + break; + case 0x0C: + $name = 'tGE'; + $size = 1; + $data = '>='; + + break; + case 0x0D: + $name = 'tGT'; + $size = 1; + $data = '>'; + + break; + case 0x0E: + $name = 'tNE'; + $size = 1; + $data = '<>'; + + break; + case 0x0F: + $name = 'tIsect'; + $size = 1; + $data = ' '; + + break; + case 0x10: + $name = 'tList'; + $size = 1; + $data = ','; + + break; + case 0x11: + $name = 'tRange'; + $size = 1; + $data = ':'; + + break; + case 0x12: + $name = 'tUplus'; + $size = 1; + $data = '+'; + + break; + case 0x13: + $name = 'tUminus'; + $size = 1; + $data = '-'; + + break; + case 0x14: + $name = 'tPercent'; + $size = 1; + $data = '%'; + + break; + case 0x15: // parenthesis + $name = 'tParen'; + $size = 1; + $data = null; + + break; + case 0x16: // missing argument + $name = 'tMissArg'; + $size = 1; + $data = ''; + + break; + case 0x17: // string + $name = 'tStr'; + // offset: 1; size: var; Unicode string, 8-bit string length + $string = self::readUnicodeStringShort(substr($formulaData, 1)); + $size = 1 + $string['size']; + $data = self::UTF8toExcelDoubleQuoted($string['value']); + + break; + case 0x19: // Special attribute + // offset: 1; size: 1; attribute type flags: + switch (ord($formulaData[1])) { + case 0x01: + $name = 'tAttrVolatile'; + $size = 4; + $data = null; + + break; + case 0x02: + $name = 'tAttrIf'; + $size = 4; + $data = null; + + break; + case 0x04: + $name = 'tAttrChoose'; + // offset: 2; size: 2; number of choices in the CHOOSE function ($nc, number of parameters decreased by 1) + $nc = self::getUInt2d($formulaData, 2); + // offset: 4; size: 2 * $nc + // offset: 4 + 2 * $nc; size: 2 + $size = 2 * $nc + 6; + $data = null; + + break; + case 0x08: + $name = 'tAttrSkip'; + $size = 4; + $data = null; + + break; + case 0x10: + $name = 'tAttrSum'; + $size = 4; + $data = null; + + break; + case 0x40: + case 0x41: + $name = 'tAttrSpace'; + $size = 4; + // offset: 2; size: 2; space type and position + switch (ord($formulaData[2])) { + case 0x00: + $spacetype = 'type0'; + + break; + case 0x01: + $spacetype = 'type1'; + + break; + case 0x02: + $spacetype = 'type2'; + + break; + case 0x03: + $spacetype = 'type3'; + + break; + case 0x04: + $spacetype = 'type4'; + + break; + case 0x05: + $spacetype = 'type5'; + + break; + default: + throw new Exception('Unrecognized space type in tAttrSpace token'); + + break; + } + // offset: 3; size: 1; number of inserted spaces/carriage returns + $spacecount = ord($formulaData[3]); + + $data = ['spacetype' => $spacetype, 'spacecount' => $spacecount]; + + break; + default: + throw new Exception('Unrecognized attribute flag in tAttr token'); + + break; + } + + break; + case 0x1C: // error code + // offset: 1; size: 1; error code + $name = 'tErr'; + $size = 2; + $data = Xls\ErrorCode::lookup(ord($formulaData[1])); + + break; + case 0x1D: // boolean + // offset: 1; size: 1; 0 = false, 1 = true; + $name = 'tBool'; + $size = 2; + $data = ord($formulaData[1]) ? 'TRUE' : 'FALSE'; + + break; + case 0x1E: // integer + // offset: 1; size: 2; unsigned 16-bit integer + $name = 'tInt'; + $size = 3; + $data = self::getUInt2d($formulaData, 1); + + break; + case 0x1F: // number + // offset: 1; size: 8; + $name = 'tNum'; + $size = 9; + $data = self::extractNumber(substr($formulaData, 1)); + $data = str_replace(',', '.', (string) $data); // in case non-English locale + + break; + case 0x20: // array constant + case 0x40: + case 0x60: + // offset: 1; size: 7; not used + $name = 'tArray'; + $size = 8; + $data = null; + + break; + case 0x21: // function with fixed number of arguments + case 0x41: + case 0x61: + $name = 'tFunc'; + $size = 3; + // offset: 1; size: 2; index to built-in sheet function + switch (self::getUInt2d($formulaData, 1)) { + case 2: + $function = 'ISNA'; + $args = 1; + + break; + case 3: + $function = 'ISERROR'; + $args = 1; + + break; + case 10: + $function = 'NA'; + $args = 0; + + break; + case 15: + $function = 'SIN'; + $args = 1; + + break; + case 16: + $function = 'COS'; + $args = 1; + + break; + case 17: + $function = 'TAN'; + $args = 1; + + break; + case 18: + $function = 'ATAN'; + $args = 1; + + break; + case 19: + $function = 'PI'; + $args = 0; + + break; + case 20: + $function = 'SQRT'; + $args = 1; + + break; + case 21: + $function = 'EXP'; + $args = 1; + + break; + case 22: + $function = 'LN'; + $args = 1; + + break; + case 23: + $function = 'LOG10'; + $args = 1; + + break; + case 24: + $function = 'ABS'; + $args = 1; + + break; + case 25: + $function = 'INT'; + $args = 1; + + break; + case 26: + $function = 'SIGN'; + $args = 1; + + break; + case 27: + $function = 'ROUND'; + $args = 2; + + break; + case 30: + $function = 'REPT'; + $args = 2; + + break; + case 31: + $function = 'MID'; + $args = 3; + + break; + case 32: + $function = 'LEN'; + $args = 1; + + break; + case 33: + $function = 'VALUE'; + $args = 1; + + break; + case 34: + $function = 'TRUE'; + $args = 0; + + break; + case 35: + $function = 'FALSE'; + $args = 0; + + break; + case 38: + $function = 'NOT'; + $args = 1; + + break; + case 39: + $function = 'MOD'; + $args = 2; + + break; + case 40: + $function = 'DCOUNT'; + $args = 3; + + break; + case 41: + $function = 'DSUM'; + $args = 3; + + break; + case 42: + $function = 'DAVERAGE'; + $args = 3; + + break; + case 43: + $function = 'DMIN'; + $args = 3; + + break; + case 44: + $function = 'DMAX'; + $args = 3; + + break; + case 45: + $function = 'DSTDEV'; + $args = 3; + + break; + case 48: + $function = 'TEXT'; + $args = 2; + + break; + case 61: + $function = 'MIRR'; + $args = 3; + + break; + case 63: + $function = 'RAND'; + $args = 0; + + break; + case 65: + $function = 'DATE'; + $args = 3; + + break; + case 66: + $function = 'TIME'; + $args = 3; + + break; + case 67: + $function = 'DAY'; + $args = 1; + + break; + case 68: + $function = 'MONTH'; + $args = 1; + + break; + case 69: + $function = 'YEAR'; + $args = 1; + + break; + case 71: + $function = 'HOUR'; + $args = 1; + + break; + case 72: + $function = 'MINUTE'; + $args = 1; + + break; + case 73: + $function = 'SECOND'; + $args = 1; + + break; + case 74: + $function = 'NOW'; + $args = 0; + + break; + case 75: + $function = 'AREAS'; + $args = 1; + + break; + case 76: + $function = 'ROWS'; + $args = 1; + + break; + case 77: + $function = 'COLUMNS'; + $args = 1; + + break; + case 83: + $function = 'TRANSPOSE'; + $args = 1; + + break; + case 86: + $function = 'TYPE'; + $args = 1; + + break; + case 97: + $function = 'ATAN2'; + $args = 2; + + break; + case 98: + $function = 'ASIN'; + $args = 1; + + break; + case 99: + $function = 'ACOS'; + $args = 1; + + break; + case 105: + $function = 'ISREF'; + $args = 1; + + break; + case 111: + $function = 'CHAR'; + $args = 1; + + break; + case 112: + $function = 'LOWER'; + $args = 1; + + break; + case 113: + $function = 'UPPER'; + $args = 1; + + break; + case 114: + $function = 'PROPER'; + $args = 1; + + break; + case 117: + $function = 'EXACT'; + $args = 2; + + break; + case 118: + $function = 'TRIM'; + $args = 1; + + break; + case 119: + $function = 'REPLACE'; + $args = 4; + + break; + case 121: + $function = 'CODE'; + $args = 1; + + break; + case 126: + $function = 'ISERR'; + $args = 1; + + break; + case 127: + $function = 'ISTEXT'; + $args = 1; + + break; + case 128: + $function = 'ISNUMBER'; + $args = 1; + + break; + case 129: + $function = 'ISBLANK'; + $args = 1; + + break; + case 130: + $function = 'T'; + $args = 1; + + break; + case 131: + $function = 'N'; + $args = 1; + + break; + case 140: + $function = 'DATEVALUE'; + $args = 1; + + break; + case 141: + $function = 'TIMEVALUE'; + $args = 1; + + break; + case 142: + $function = 'SLN'; + $args = 3; + + break; + case 143: + $function = 'SYD'; + $args = 4; + + break; + case 162: + $function = 'CLEAN'; + $args = 1; + + break; + case 163: + $function = 'MDETERM'; + $args = 1; + + break; + case 164: + $function = 'MINVERSE'; + $args = 1; + + break; + case 165: + $function = 'MMULT'; + $args = 2; + + break; + case 184: + $function = 'FACT'; + $args = 1; + + break; + case 189: + $function = 'DPRODUCT'; + $args = 3; + + break; + case 190: + $function = 'ISNONTEXT'; + $args = 1; + + break; + case 195: + $function = 'DSTDEVP'; + $args = 3; + + break; + case 196: + $function = 'DVARP'; + $args = 3; + + break; + case 198: + $function = 'ISLOGICAL'; + $args = 1; + + break; + case 199: + $function = 'DCOUNTA'; + $args = 3; + + break; + case 207: + $function = 'REPLACEB'; + $args = 4; + + break; + case 210: + $function = 'MIDB'; + $args = 3; + + break; + case 211: + $function = 'LENB'; + $args = 1; + + break; + case 212: + $function = 'ROUNDUP'; + $args = 2; + + break; + case 213: + $function = 'ROUNDDOWN'; + $args = 2; + + break; + case 214: + $function = 'ASC'; + $args = 1; + + break; + case 215: + $function = 'DBCS'; + $args = 1; + + break; + case 221: + $function = 'TODAY'; + $args = 0; + + break; + case 229: + $function = 'SINH'; + $args = 1; + + break; + case 230: + $function = 'COSH'; + $args = 1; + + break; + case 231: + $function = 'TANH'; + $args = 1; + + break; + case 232: + $function = 'ASINH'; + $args = 1; + + break; + case 233: + $function = 'ACOSH'; + $args = 1; + + break; + case 234: + $function = 'ATANH'; + $args = 1; + + break; + case 235: + $function = 'DGET'; + $args = 3; + + break; + case 244: + $function = 'INFO'; + $args = 1; + + break; + case 252: + $function = 'FREQUENCY'; + $args = 2; + + break; + case 261: + $function = 'ERROR.TYPE'; + $args = 1; + + break; + case 271: + $function = 'GAMMALN'; + $args = 1; + + break; + case 273: + $function = 'BINOMDIST'; + $args = 4; + + break; + case 274: + $function = 'CHIDIST'; + $args = 2; + + break; + case 275: + $function = 'CHIINV'; + $args = 2; + + break; + case 276: + $function = 'COMBIN'; + $args = 2; + + break; + case 277: + $function = 'CONFIDENCE'; + $args = 3; + + break; + case 278: + $function = 'CRITBINOM'; + $args = 3; + + break; + case 279: + $function = 'EVEN'; + $args = 1; + + break; + case 280: + $function = 'EXPONDIST'; + $args = 3; + + break; + case 281: + $function = 'FDIST'; + $args = 3; + + break; + case 282: + $function = 'FINV'; + $args = 3; + + break; + case 283: + $function = 'FISHER'; + $args = 1; + + break; + case 284: + $function = 'FISHERINV'; + $args = 1; + + break; + case 285: + $function = 'FLOOR'; + $args = 2; + + break; + case 286: + $function = 'GAMMADIST'; + $args = 4; + + break; + case 287: + $function = 'GAMMAINV'; + $args = 3; + + break; + case 288: + $function = 'CEILING'; + $args = 2; + + break; + case 289: + $function = 'HYPGEOMDIST'; + $args = 4; + + break; + case 290: + $function = 'LOGNORMDIST'; + $args = 3; + + break; + case 291: + $function = 'LOGINV'; + $args = 3; + + break; + case 292: + $function = 'NEGBINOMDIST'; + $args = 3; + + break; + case 293: + $function = 'NORMDIST'; + $args = 4; + + break; + case 294: + $function = 'NORMSDIST'; + $args = 1; + + break; + case 295: + $function = 'NORMINV'; + $args = 3; + + break; + case 296: + $function = 'NORMSINV'; + $args = 1; + + break; + case 297: + $function = 'STANDARDIZE'; + $args = 3; + + break; + case 298: + $function = 'ODD'; + $args = 1; + + break; + case 299: + $function = 'PERMUT'; + $args = 2; + + break; + case 300: + $function = 'POISSON'; + $args = 3; + + break; + case 301: + $function = 'TDIST'; + $args = 3; + + break; + case 302: + $function = 'WEIBULL'; + $args = 4; + + break; + case 303: + $function = 'SUMXMY2'; + $args = 2; + + break; + case 304: + $function = 'SUMX2MY2'; + $args = 2; + + break; + case 305: + $function = 'SUMX2PY2'; + $args = 2; + + break; + case 306: + $function = 'CHITEST'; + $args = 2; + + break; + case 307: + $function = 'CORREL'; + $args = 2; + + break; + case 308: + $function = 'COVAR'; + $args = 2; + + break; + case 309: + $function = 'FORECAST'; + $args = 3; + + break; + case 310: + $function = 'FTEST'; + $args = 2; + + break; + case 311: + $function = 'INTERCEPT'; + $args = 2; + + break; + case 312: + $function = 'PEARSON'; + $args = 2; + + break; + case 313: + $function = 'RSQ'; + $args = 2; + + break; + case 314: + $function = 'STEYX'; + $args = 2; + + break; + case 315: + $function = 'SLOPE'; + $args = 2; + + break; + case 316: + $function = 'TTEST'; + $args = 4; + + break; + case 325: + $function = 'LARGE'; + $args = 2; + + break; + case 326: + $function = 'SMALL'; + $args = 2; + + break; + case 327: + $function = 'QUARTILE'; + $args = 2; + + break; + case 328: + $function = 'PERCENTILE'; + $args = 2; + + break; + case 331: + $function = 'TRIMMEAN'; + $args = 2; + + break; + case 332: + $function = 'TINV'; + $args = 2; + + break; + case 337: + $function = 'POWER'; + $args = 2; + + break; + case 342: + $function = 'RADIANS'; + $args = 1; + + break; + case 343: + $function = 'DEGREES'; + $args = 1; + + break; + case 346: + $function = 'COUNTIF'; + $args = 2; + + break; + case 347: + $function = 'COUNTBLANK'; + $args = 1; + + break; + case 350: + $function = 'ISPMT'; + $args = 4; + + break; + case 351: + $function = 'DATEDIF'; + $args = 3; + + break; + case 352: + $function = 'DATESTRING'; + $args = 1; + + break; + case 353: + $function = 'NUMBERSTRING'; + $args = 2; + + break; + case 360: + $function = 'PHONETIC'; + $args = 1; + + break; + case 368: + $function = 'BAHTTEXT'; + $args = 1; + + break; + default: + throw new Exception('Unrecognized function in formula'); + + break; + } + $data = ['function' => $function, 'args' => $args]; + + break; + case 0x22: // function with variable number of arguments + case 0x42: + case 0x62: + $name = 'tFuncV'; + $size = 4; + // offset: 1; size: 1; number of arguments + $args = ord($formulaData[1]); + // offset: 2: size: 2; index to built-in sheet function + $index = self::getUInt2d($formulaData, 2); + switch ($index) { + case 0: + $function = 'COUNT'; + + break; + case 1: + $function = 'IF'; + + break; + case 4: + $function = 'SUM'; + + break; + case 5: + $function = 'AVERAGE'; + + break; + case 6: + $function = 'MIN'; + + break; + case 7: + $function = 'MAX'; + + break; + case 8: + $function = 'ROW'; + + break; + case 9: + $function = 'COLUMN'; + + break; + case 11: + $function = 'NPV'; + + break; + case 12: + $function = 'STDEV'; + + break; + case 13: + $function = 'DOLLAR'; + + break; + case 14: + $function = 'FIXED'; + + break; + case 28: + $function = 'LOOKUP'; + + break; + case 29: + $function = 'INDEX'; + + break; + case 36: + $function = 'AND'; + + break; + case 37: + $function = 'OR'; + + break; + case 46: + $function = 'VAR'; + + break; + case 49: + $function = 'LINEST'; + + break; + case 50: + $function = 'TREND'; + + break; + case 51: + $function = 'LOGEST'; + + break; + case 52: + $function = 'GROWTH'; + + break; + case 56: + $function = 'PV'; + + break; + case 57: + $function = 'FV'; + + break; + case 58: + $function = 'NPER'; + + break; + case 59: + $function = 'PMT'; + + break; + case 60: + $function = 'RATE'; + + break; + case 62: + $function = 'IRR'; + + break; + case 64: + $function = 'MATCH'; + + break; + case 70: + $function = 'WEEKDAY'; + + break; + case 78: + $function = 'OFFSET'; + + break; + case 82: + $function = 'SEARCH'; + + break; + case 100: + $function = 'CHOOSE'; + + break; + case 101: + $function = 'HLOOKUP'; + + break; + case 102: + $function = 'VLOOKUP'; + + break; + case 109: + $function = 'LOG'; + + break; + case 115: + $function = 'LEFT'; + + break; + case 116: + $function = 'RIGHT'; + + break; + case 120: + $function = 'SUBSTITUTE'; + + break; + case 124: + $function = 'FIND'; + + break; + case 125: + $function = 'CELL'; + + break; + case 144: + $function = 'DDB'; + + break; + case 148: + $function = 'INDIRECT'; + + break; + case 167: + $function = 'IPMT'; + + break; + case 168: + $function = 'PPMT'; + + break; + case 169: + $function = 'COUNTA'; + + break; + case 183: + $function = 'PRODUCT'; + + break; + case 193: + $function = 'STDEVP'; + + break; + case 194: + $function = 'VARP'; + + break; + case 197: + $function = 'TRUNC'; + + break; + case 204: + $function = 'USDOLLAR'; + + break; + case 205: + $function = 'FINDB'; + + break; + case 206: + $function = 'SEARCHB'; + + break; + case 208: + $function = 'LEFTB'; + + break; + case 209: + $function = 'RIGHTB'; + + break; + case 216: + $function = 'RANK'; + + break; + case 219: + $function = 'ADDRESS'; + + break; + case 220: + $function = 'DAYS360'; + + break; + case 222: + $function = 'VDB'; + + break; + case 227: + $function = 'MEDIAN'; + + break; + case 228: + $function = 'SUMPRODUCT'; + + break; + case 247: + $function = 'DB'; + + break; + case 255: + $function = ''; + + break; + case 269: + $function = 'AVEDEV'; + + break; + case 270: + $function = 'BETADIST'; + + break; + case 272: + $function = 'BETAINV'; + + break; + case 317: + $function = 'PROB'; + + break; + case 318: + $function = 'DEVSQ'; + + break; + case 319: + $function = 'GEOMEAN'; + + break; + case 320: + $function = 'HARMEAN'; + + break; + case 321: + $function = 'SUMSQ'; + + break; + case 322: + $function = 'KURT'; + + break; + case 323: + $function = 'SKEW'; + + break; + case 324: + $function = 'ZTEST'; + + break; + case 329: + $function = 'PERCENTRANK'; + + break; + case 330: + $function = 'MODE'; + + break; + case 336: + $function = 'CONCATENATE'; + + break; + case 344: + $function = 'SUBTOTAL'; + + break; + case 345: + $function = 'SUMIF'; + + break; + case 354: + $function = 'ROMAN'; + + break; + case 358: + $function = 'GETPIVOTDATA'; + + break; + case 359: + $function = 'HYPERLINK'; + + break; + case 361: + $function = 'AVERAGEA'; + + break; + case 362: + $function = 'MAXA'; + + break; + case 363: + $function = 'MINA'; + + break; + case 364: + $function = 'STDEVPA'; + + break; + case 365: + $function = 'VARPA'; + + break; + case 366: + $function = 'STDEVA'; + + break; + case 367: + $function = 'VARA'; + + break; + default: + throw new Exception('Unrecognized function in formula'); + + break; + } + $data = ['function' => $function, 'args' => $args]; + + break; + case 0x23: // index to defined name + case 0x43: + case 0x63: + $name = 'tName'; + $size = 5; + // offset: 1; size: 2; one-based index to definedname record + $definedNameIndex = self::getUInt2d($formulaData, 1) - 1; + // offset: 2; size: 2; not used + $data = $this->definedname[$definedNameIndex]['name']; + + break; + case 0x24: // single cell reference e.g. A5 + case 0x44: + case 0x64: + $name = 'tRef'; + $size = 5; + $data = $this->readBIFF8CellAddress(substr($formulaData, 1, 4)); + + break; + case 0x25: // cell range reference to cells in the same sheet (2d) + case 0x45: + case 0x65: + $name = 'tArea'; + $size = 9; + $data = $this->readBIFF8CellRangeAddress(substr($formulaData, 1, 8)); + + break; + case 0x26: // Constant reference sub-expression + case 0x46: + case 0x66: + $name = 'tMemArea'; + // offset: 1; size: 4; not used + // offset: 5; size: 2; size of the following subexpression + $subSize = self::getUInt2d($formulaData, 5); + $size = 7 + $subSize; + $data = $this->getFormulaFromData(substr($formulaData, 7, $subSize)); + + break; + case 0x27: // Deleted constant reference sub-expression + case 0x47: + case 0x67: + $name = 'tMemErr'; + // offset: 1; size: 4; not used + // offset: 5; size: 2; size of the following subexpression + $subSize = self::getUInt2d($formulaData, 5); + $size = 7 + $subSize; + $data = $this->getFormulaFromData(substr($formulaData, 7, $subSize)); + + break; + case 0x29: // Variable reference sub-expression + case 0x49: + case 0x69: + $name = 'tMemFunc'; + // offset: 1; size: 2; size of the following sub-expression + $subSize = self::getUInt2d($formulaData, 1); + $size = 3 + $subSize; + $data = $this->getFormulaFromData(substr($formulaData, 3, $subSize)); + + break; + case 0x2C: // Relative 2d cell reference reference, used in shared formulas and some other places + case 0x4C: + case 0x6C: + $name = 'tRefN'; + $size = 5; + $data = $this->readBIFF8CellAddressB(substr($formulaData, 1, 4), $baseCell); + + break; + case 0x2D: // Relative 2d range reference + case 0x4D: + case 0x6D: + $name = 'tAreaN'; + $size = 9; + $data = $this->readBIFF8CellRangeAddressB(substr($formulaData, 1, 8), $baseCell); + + break; + case 0x39: // External name + case 0x59: + case 0x79: + $name = 'tNameX'; + $size = 7; + // offset: 1; size: 2; index to REF entry in EXTERNSHEET record + // offset: 3; size: 2; one-based index to DEFINEDNAME or EXTERNNAME record + $index = self::getUInt2d($formulaData, 3); + // assume index is to EXTERNNAME record + $data = $this->externalNames[$index - 1]['name'] ?? ''; + // offset: 5; size: 2; not used + break; + case 0x3A: // 3d reference to cell + case 0x5A: + case 0x7A: + $name = 'tRef3d'; + $size = 7; + + try { + // offset: 1; size: 2; index to REF entry + $sheetRange = $this->readSheetRangeByRefIndex(self::getUInt2d($formulaData, 1)); + // offset: 3; size: 4; cell address + $cellAddress = $this->readBIFF8CellAddress(substr($formulaData, 3, 4)); + + $data = "$sheetRange!$cellAddress"; + } catch (PhpSpreadsheetException $e) { + // deleted sheet reference + $data = '#REF!'; + } + + break; + case 0x3B: // 3d reference to cell range + case 0x5B: + case 0x7B: + $name = 'tArea3d'; + $size = 11; + + try { + // offset: 1; size: 2; index to REF entry + $sheetRange = $this->readSheetRangeByRefIndex(self::getUInt2d($formulaData, 1)); + // offset: 3; size: 8; cell address + $cellRangeAddress = $this->readBIFF8CellRangeAddress(substr($formulaData, 3, 8)); + + $data = "$sheetRange!$cellRangeAddress"; + } catch (PhpSpreadsheetException $e) { + // deleted sheet reference + $data = '#REF!'; + } + + break; + // Unknown cases // don't know how to deal with + default: + throw new Exception('Unrecognized token ' . sprintf('%02X', $id) . ' in formula'); + + break; + } + + return [ + 'id' => $id, + 'name' => $name, + 'size' => $size, + 'data' => $data, + ]; + } + + /** + * Reads a cell address in BIFF8 e.g. 'A2' or '$A$2' + * section 3.3.4. + * + * @param string $cellAddressStructure + * + * @return string + */ + private function readBIFF8CellAddress($cellAddressStructure) + { + // offset: 0; size: 2; index to row (0... 65535) (or offset (-32768... 32767)) + $row = self::getUInt2d($cellAddressStructure, 0) + 1; + + // offset: 2; size: 2; index to column or column offset + relative flags + // bit: 7-0; mask 0x00FF; column index + $column = Coordinate::stringFromColumnIndex((0x00FF & self::getUInt2d($cellAddressStructure, 2)) + 1); + + // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index) + if (!(0x4000 & self::getUInt2d($cellAddressStructure, 2))) { + $column = '$' . $column; + } + // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index) + if (!(0x8000 & self::getUInt2d($cellAddressStructure, 2))) { + $row = '$' . $row; + } + + return $column . $row; + } + + /** + * Reads a cell address in BIFF8 for shared formulas. Uses positive and negative values for row and column + * to indicate offsets from a base cell + * section 3.3.4. + * + * @param string $cellAddressStructure + * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas + * + * @return string + */ + private function readBIFF8CellAddressB($cellAddressStructure, $baseCell = 'A1') + { + [$baseCol, $baseRow] = Coordinate::coordinateFromString($baseCell); + $baseCol = Coordinate::columnIndexFromString($baseCol) - 1; + $baseRow = (int) $baseRow; + + // offset: 0; size: 2; index to row (0... 65535) (or offset (-32768... 32767)) + $rowIndex = self::getUInt2d($cellAddressStructure, 0); + $row = self::getUInt2d($cellAddressStructure, 0) + 1; + + // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index) + if (!(0x4000 & self::getUInt2d($cellAddressStructure, 2))) { + // offset: 2; size: 2; index to column or column offset + relative flags + // bit: 7-0; mask 0x00FF; column index + $colIndex = 0x00FF & self::getUInt2d($cellAddressStructure, 2); + + $column = Coordinate::stringFromColumnIndex($colIndex + 1); + $column = '$' . $column; + } else { + // offset: 2; size: 2; index to column or column offset + relative flags + // bit: 7-0; mask 0x00FF; column index + $relativeColIndex = 0x00FF & self::getInt2d($cellAddressStructure, 2); + $colIndex = $baseCol + $relativeColIndex; + $colIndex = ($colIndex < 256) ? $colIndex : $colIndex - 256; + $colIndex = ($colIndex >= 0) ? $colIndex : $colIndex + 256; + $column = Coordinate::stringFromColumnIndex($colIndex + 1); + } + + // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index) + if (!(0x8000 & self::getUInt2d($cellAddressStructure, 2))) { + $row = '$' . $row; + } else { + $rowIndex = ($rowIndex <= 32767) ? $rowIndex : $rowIndex - 65536; + $row = $baseRow + $rowIndex; + } + + return $column . $row; + } + + /** + * Reads a cell range address in BIFF5 e.g. 'A2:B6' or 'A1' + * always fixed range + * section 2.5.14. + * + * @param string $subData + * + * @return string + */ + private function readBIFF5CellRangeAddressFixed($subData) + { + // offset: 0; size: 2; index to first row + $fr = self::getUInt2d($subData, 0) + 1; + + // offset: 2; size: 2; index to last row + $lr = self::getUInt2d($subData, 2) + 1; + + // offset: 4; size: 1; index to first column + $fc = ord($subData[4]); + + // offset: 5; size: 1; index to last column + $lc = ord($subData[5]); + + // check values + if ($fr > $lr || $fc > $lc) { + throw new Exception('Not a cell range address'); + } + + // column index to letter + $fc = Coordinate::stringFromColumnIndex($fc + 1); + $lc = Coordinate::stringFromColumnIndex($lc + 1); + + if ($fr == $lr && $fc == $lc) { + return "$fc$fr"; + } + + return "$fc$fr:$lc$lr"; + } + + /** + * Reads a cell range address in BIFF8 e.g. 'A2:B6' or 'A1' + * always fixed range + * section 2.5.14. + * + * @param string $subData + * + * @return string + */ + private function readBIFF8CellRangeAddressFixed($subData) + { + // offset: 0; size: 2; index to first row + $fr = self::getUInt2d($subData, 0) + 1; + + // offset: 2; size: 2; index to last row + $lr = self::getUInt2d($subData, 2) + 1; + + // offset: 4; size: 2; index to first column + $fc = self::getUInt2d($subData, 4); + + // offset: 6; size: 2; index to last column + $lc = self::getUInt2d($subData, 6); + + // check values + if ($fr > $lr || $fc > $lc) { + throw new Exception('Not a cell range address'); + } + + // column index to letter + $fc = Coordinate::stringFromColumnIndex($fc + 1); + $lc = Coordinate::stringFromColumnIndex($lc + 1); + + if ($fr == $lr && $fc == $lc) { + return "$fc$fr"; + } + + return "$fc$fr:$lc$lr"; + } + + /** + * Reads a cell range address in BIFF8 e.g. 'A2:B6' or '$A$2:$B$6' + * there are flags indicating whether column/row index is relative + * section 3.3.4. + * + * @param string $subData + * + * @return string + */ + private function readBIFF8CellRangeAddress($subData) + { + // todo: if cell range is just a single cell, should this funciton + // not just return e.g. 'A1' and not 'A1:A1' ? + + // offset: 0; size: 2; index to first row (0... 65535) (or offset (-32768... 32767)) + $fr = self::getUInt2d($subData, 0) + 1; + + // offset: 2; size: 2; index to last row (0... 65535) (or offset (-32768... 32767)) + $lr = self::getUInt2d($subData, 2) + 1; + + // offset: 4; size: 2; index to first column or column offset + relative flags + + // bit: 7-0; mask 0x00FF; column index + $fc = Coordinate::stringFromColumnIndex((0x00FF & self::getUInt2d($subData, 4)) + 1); + + // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index) + if (!(0x4000 & self::getUInt2d($subData, 4))) { + $fc = '$' . $fc; + } + + // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index) + if (!(0x8000 & self::getUInt2d($subData, 4))) { + $fr = '$' . $fr; + } + + // offset: 6; size: 2; index to last column or column offset + relative flags + + // bit: 7-0; mask 0x00FF; column index + $lc = Coordinate::stringFromColumnIndex((0x00FF & self::getUInt2d($subData, 6)) + 1); + + // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index) + if (!(0x4000 & self::getUInt2d($subData, 6))) { + $lc = '$' . $lc; + } + + // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index) + if (!(0x8000 & self::getUInt2d($subData, 6))) { + $lr = '$' . $lr; + } + + return "$fc$fr:$lc$lr"; + } + + /** + * Reads a cell range address in BIFF8 for shared formulas. Uses positive and negative values for row and column + * to indicate offsets from a base cell + * section 3.3.4. + * + * @param string $subData + * @param string $baseCell Base cell + * + * @return string Cell range address + */ + private function readBIFF8CellRangeAddressB($subData, $baseCell = 'A1') + { + [$baseCol, $baseRow] = Coordinate::indexesFromString($baseCell); + $baseCol = $baseCol - 1; + + // TODO: if cell range is just a single cell, should this funciton + // not just return e.g. 'A1' and not 'A1:A1' ? + + // offset: 0; size: 2; first row + $frIndex = self::getUInt2d($subData, 0); // adjust below + + // offset: 2; size: 2; relative index to first row (0... 65535) should be treated as offset (-32768... 32767) + $lrIndex = self::getUInt2d($subData, 2); // adjust below + + // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index) + if (!(0x4000 & self::getUInt2d($subData, 4))) { + // absolute column index + // offset: 4; size: 2; first column with relative/absolute flags + // bit: 7-0; mask 0x00FF; column index + $fcIndex = 0x00FF & self::getUInt2d($subData, 4); + $fc = Coordinate::stringFromColumnIndex($fcIndex + 1); + $fc = '$' . $fc; + } else { + // column offset + // offset: 4; size: 2; first column with relative/absolute flags + // bit: 7-0; mask 0x00FF; column index + $relativeFcIndex = 0x00FF & self::getInt2d($subData, 4); + $fcIndex = $baseCol + $relativeFcIndex; + $fcIndex = ($fcIndex < 256) ? $fcIndex : $fcIndex - 256; + $fcIndex = ($fcIndex >= 0) ? $fcIndex : $fcIndex + 256; + $fc = Coordinate::stringFromColumnIndex($fcIndex + 1); + } + + // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index) + if (!(0x8000 & self::getUInt2d($subData, 4))) { + // absolute row index + $fr = $frIndex + 1; + $fr = '$' . $fr; + } else { + // row offset + $frIndex = ($frIndex <= 32767) ? $frIndex : $frIndex - 65536; + $fr = $baseRow + $frIndex; + } + + // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index) + if (!(0x4000 & self::getUInt2d($subData, 6))) { + // absolute column index + // offset: 6; size: 2; last column with relative/absolute flags + // bit: 7-0; mask 0x00FF; column index + $lcIndex = 0x00FF & self::getUInt2d($subData, 6); + $lc = Coordinate::stringFromColumnIndex($lcIndex + 1); + $lc = '$' . $lc; + } else { + // column offset + // offset: 4; size: 2; first column with relative/absolute flags + // bit: 7-0; mask 0x00FF; column index + $relativeLcIndex = 0x00FF & self::getInt2d($subData, 4); + $lcIndex = $baseCol + $relativeLcIndex; + $lcIndex = ($lcIndex < 256) ? $lcIndex : $lcIndex - 256; + $lcIndex = ($lcIndex >= 0) ? $lcIndex : $lcIndex + 256; + $lc = Coordinate::stringFromColumnIndex($lcIndex + 1); + } + + // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index) + if (!(0x8000 & self::getUInt2d($subData, 6))) { + // absolute row index + $lr = $lrIndex + 1; + $lr = '$' . $lr; + } else { + // row offset + $lrIndex = ($lrIndex <= 32767) ? $lrIndex : $lrIndex - 65536; + $lr = $baseRow + $lrIndex; + } + + return "$fc$fr:$lc$lr"; + } + + /** + * Read BIFF8 cell range address list + * section 2.5.15. + * + * @param string $subData + * + * @return array + */ + private function readBIFF8CellRangeAddressList($subData) + { + $cellRangeAddresses = []; + + // offset: 0; size: 2; number of the following cell range addresses + $nm = self::getUInt2d($subData, 0); + + $offset = 2; + // offset: 2; size: 8 * $nm; list of $nm (fixed) cell range addresses + for ($i = 0; $i < $nm; ++$i) { + $cellRangeAddresses[] = $this->readBIFF8CellRangeAddressFixed(substr($subData, $offset, 8)); + $offset += 8; + } + + return [ + 'size' => 2 + 8 * $nm, + 'cellRangeAddresses' => $cellRangeAddresses, + ]; + } + + /** + * Read BIFF5 cell range address list + * section 2.5.15. + * + * @param string $subData + * + * @return array + */ + private function readBIFF5CellRangeAddressList($subData) + { + $cellRangeAddresses = []; + + // offset: 0; size: 2; number of the following cell range addresses + $nm = self::getUInt2d($subData, 0); + + $offset = 2; + // offset: 2; size: 6 * $nm; list of $nm (fixed) cell range addresses + for ($i = 0; $i < $nm; ++$i) { + $cellRangeAddresses[] = $this->readBIFF5CellRangeAddressFixed(substr($subData, $offset, 6)); + $offset += 6; + } + + return [ + 'size' => 2 + 6 * $nm, + 'cellRangeAddresses' => $cellRangeAddresses, + ]; + } + + /** + * Get a sheet range like Sheet1:Sheet3 from REF index + * Note: If there is only one sheet in the range, one gets e.g Sheet1 + * It can also happen that the REF structure uses the -1 (FFFF) code to indicate deleted sheets, + * in which case an Exception is thrown. + * + * @param int $index + * + * @return false|string + */ + private function readSheetRangeByRefIndex($index) + { + if (isset($this->ref[$index])) { + $type = $this->externalBooks[$this->ref[$index]['externalBookIndex']]['type']; + + switch ($type) { + case 'internal': + // check if we have a deleted 3d reference + if ($this->ref[$index]['firstSheetIndex'] == 0xFFFF || $this->ref[$index]['lastSheetIndex'] == 0xFFFF) { + throw new Exception('Deleted sheet reference'); + } + + // we have normal sheet range (collapsed or uncollapsed) + $firstSheetName = $this->sheets[$this->ref[$index]['firstSheetIndex']]['name']; + $lastSheetName = $this->sheets[$this->ref[$index]['lastSheetIndex']]['name']; + + if ($firstSheetName == $lastSheetName) { + // collapsed sheet range + $sheetRange = $firstSheetName; + } else { + $sheetRange = "$firstSheetName:$lastSheetName"; + } + + // escape the single-quotes + $sheetRange = str_replace("'", "''", $sheetRange); + + // if there are special characters, we need to enclose the range in single-quotes + // todo: check if we have identified the whole set of special characters + // it seems that the following characters are not accepted for sheet names + // and we may assume that they are not present: []*/:\? + if (preg_match("/[ !\"@#£$%&{()}<>=+'|^,;-]/u", $sheetRange)) { + $sheetRange = "'$sheetRange'"; + } + + return $sheetRange; + + break; + default: + // TODO: external sheet support + throw new Exception('Xls reader only supports internal sheets in formulas'); + + break; + } + } + + return false; + } + + /** + * read BIFF8 constant value array from array data + * returns e.g. ['value' => '{1,2;3,4}', 'size' => 40] + * section 2.5.8. + * + * @param string $arrayData + * + * @return array + */ + private static function readBIFF8ConstantArray($arrayData) + { + // offset: 0; size: 1; number of columns decreased by 1 + $nc = ord($arrayData[0]); + + // offset: 1; size: 2; number of rows decreased by 1 + $nr = self::getUInt2d($arrayData, 1); + $size = 3; // initialize + $arrayData = substr($arrayData, 3); + + // offset: 3; size: var; list of ($nc + 1) * ($nr + 1) constant values + $matrixChunks = []; + for ($r = 1; $r <= $nr + 1; ++$r) { + $items = []; + for ($c = 1; $c <= $nc + 1; ++$c) { + $constant = self::readBIFF8Constant($arrayData); + $items[] = $constant['value']; + $arrayData = substr($arrayData, $constant['size']); + $size += $constant['size']; + } + $matrixChunks[] = implode(',', $items); // looks like e.g. '1,"hello"' + } + $matrix = '{' . implode(';', $matrixChunks) . '}'; + + return [ + 'value' => $matrix, + 'size' => $size, + ]; + } + + /** + * read BIFF8 constant value which may be 'Empty Value', 'Number', 'String Value', 'Boolean Value', 'Error Value' + * section 2.5.7 + * returns e.g. ['value' => '5', 'size' => 9]. + * + * @param string $valueData + * + * @return array + */ + private static function readBIFF8Constant($valueData) + { + // offset: 0; size: 1; identifier for type of constant + $identifier = ord($valueData[0]); + + switch ($identifier) { + case 0x00: // empty constant (what is this?) + $value = ''; + $size = 9; + + break; + case 0x01: // number + // offset: 1; size: 8; IEEE 754 floating-point value + $value = self::extractNumber(substr($valueData, 1, 8)); + $size = 9; + + break; + case 0x02: // string value + // offset: 1; size: var; Unicode string, 16-bit string length + $string = self::readUnicodeStringLong(substr($valueData, 1)); + $value = '"' . $string['value'] . '"'; + $size = 1 + $string['size']; + + break; + case 0x04: // boolean + // offset: 1; size: 1; 0 = FALSE, 1 = TRUE + if (ord($valueData[1])) { + $value = 'TRUE'; + } else { + $value = 'FALSE'; + } + $size = 9; + + break; + case 0x10: // error code + // offset: 1; size: 1; error code + $value = Xls\ErrorCode::lookup(ord($valueData[1])); + $size = 9; + + break; + default: + throw new PhpSpreadsheetException('Unsupported BIFF8 constant'); + } + + return [ + 'value' => $value, + 'size' => $size, + ]; + } + + /** + * Extract RGB color + * OpenOffice.org's Documentation of the Microsoft Excel File Format, section 2.5.4. + * + * @param string $rgb Encoded RGB value (4 bytes) + * + * @return array + */ + private static function readRGB($rgb) + { + // offset: 0; size 1; Red component + $r = ord($rgb[0]); + + // offset: 1; size: 1; Green component + $g = ord($rgb[1]); + + // offset: 2; size: 1; Blue component + $b = ord($rgb[2]); + + // HEX notation, e.g. 'FF00FC' + $rgb = sprintf('%02X%02X%02X', $r, $g, $b); + + return ['rgb' => $rgb]; + } + + /** + * Read byte string (8-bit string length) + * OpenOffice documentation: 2.5.2. + * + * @param string $subData + * + * @return array + */ + private function readByteStringShort($subData) + { + // offset: 0; size: 1; length of the string (character count) + $ln = ord($subData[0]); + + // offset: 1: size: var; character array (8-bit characters) + $value = $this->decodeCodepage(substr($subData, 1, $ln)); + + return [ + 'value' => $value, + 'size' => 1 + $ln, // size in bytes of data structure + ]; + } + + /** + * Read byte string (16-bit string length) + * OpenOffice documentation: 2.5.2. + * + * @param string $subData + * + * @return array + */ + private function readByteStringLong($subData) + { + // offset: 0; size: 2; length of the string (character count) + $ln = self::getUInt2d($subData, 0); + + // offset: 2: size: var; character array (8-bit characters) + $value = $this->decodeCodepage(substr($subData, 2)); + + //return $string; + return [ + 'value' => $value, + 'size' => 2 + $ln, // size in bytes of data structure + ]; + } + + /** + * Extracts an Excel Unicode short string (8-bit string length) + * OpenOffice documentation: 2.5.3 + * function will automatically find out where the Unicode string ends. + * + * @param string $subData + * + * @return array + */ + private static function readUnicodeStringShort($subData) + { + $value = ''; + + // offset: 0: size: 1; length of the string (character count) + $characterCount = ord($subData[0]); + + $string = self::readUnicodeString(substr($subData, 1), $characterCount); + + // add 1 for the string length + ++$string['size']; + + return $string; + } + + /** + * Extracts an Excel Unicode long string (16-bit string length) + * OpenOffice documentation: 2.5.3 + * this function is under construction, needs to support rich text, and Asian phonetic settings. + * + * @param string $subData + * + * @return array + */ + private static function readUnicodeStringLong($subData) + { + $value = ''; + + // offset: 0: size: 2; length of the string (character count) + $characterCount = self::getUInt2d($subData, 0); + + $string = self::readUnicodeString(substr($subData, 2), $characterCount); + + // add 2 for the string length + $string['size'] += 2; + + return $string; + } + + /** + * Read Unicode string with no string length field, but with known character count + * this function is under construction, needs to support rich text, and Asian phonetic settings + * OpenOffice.org's Documentation of the Microsoft Excel File Format, section 2.5.3. + * + * @param string $subData + * @param int $characterCount + * + * @return array + */ + private static function readUnicodeString($subData, $characterCount) + { + $value = ''; + + // offset: 0: size: 1; option flags + // bit: 0; mask: 0x01; character compression (0 = compressed 8-bit, 1 = uncompressed 16-bit) + $isCompressed = !((0x01 & ord($subData[0])) >> 0); + + // bit: 2; mask: 0x04; Asian phonetic settings + $hasAsian = (0x04) & ord($subData[0]) >> 2; + + // bit: 3; mask: 0x08; Rich-Text settings + $hasRichText = (0x08) & ord($subData[0]) >> 3; + + // offset: 1: size: var; character array + // this offset assumes richtext and Asian phonetic settings are off which is generally wrong + // needs to be fixed + $value = self::encodeUTF16(substr($subData, 1, $isCompressed ? $characterCount : 2 * $characterCount), $isCompressed); + + return [ + 'value' => $value, + 'size' => $isCompressed ? 1 + $characterCount : 1 + 2 * $characterCount, // the size in bytes including the option flags + ]; + } + + /** + * Convert UTF-8 string to string surounded by double quotes. Used for explicit string tokens in formulas. + * Example: hello"world --> "hello""world". + * + * @param string $value UTF-8 encoded string + * + * @return string + */ + private static function UTF8toExcelDoubleQuoted($value) + { + return '"' . str_replace('"', '""', $value) . '"'; + } + + /** + * Reads first 8 bytes of a string and return IEEE 754 float. + * + * @param string $data Binary string that is at least 8 bytes long + * + * @return float + */ + private static function extractNumber($data) + { + $rknumhigh = self::getInt4d($data, 4); + $rknumlow = self::getInt4d($data, 0); + $sign = ($rknumhigh & 0x80000000) >> 31; + $exp = (($rknumhigh & 0x7ff00000) >> 20) - 1023; + $mantissa = (0x100000 | ($rknumhigh & 0x000fffff)); + $mantissalow1 = ($rknumlow & 0x80000000) >> 31; + $mantissalow2 = ($rknumlow & 0x7fffffff); + $value = $mantissa / 2 ** (20 - $exp); + + if ($mantissalow1 != 0) { + $value += 1 / 2 ** (21 - $exp); + } + + $value += $mantissalow2 / 2 ** (52 - $exp); + if ($sign) { + $value *= -1; + } + + return $value; + } + + /** + * @param int $rknum + * + * @return float + */ + private static function getIEEE754($rknum) + { + if (($rknum & 0x02) != 0) { + $value = $rknum >> 2; + } else { + // changes by mmp, info on IEEE754 encoding from + // research.microsoft.com/~hollasch/cgindex/coding/ieeefloat.html + // The RK format calls for using only the most significant 30 bits + // of the 64 bit floating point value. The other 34 bits are assumed + // to be 0 so we use the upper 30 bits of $rknum as follows... + $sign = ($rknum & 0x80000000) >> 31; + $exp = ($rknum & 0x7ff00000) >> 20; + $mantissa = (0x100000 | ($rknum & 0x000ffffc)); + $value = $mantissa / 2 ** (20 - ($exp - 1023)); + if ($sign) { + $value = -1 * $value; + } + //end of changes by mmp + } + if (($rknum & 0x01) != 0) { + $value /= 100; + } + + return $value; + } + + /** + * Get UTF-8 string from (compressed or uncompressed) UTF-16 string. + * + * @param string $string + * @param bool $compressed + * + * @return string + */ + private static function encodeUTF16($string, $compressed = false) + { + if ($compressed) { + $string = self::uncompressByteString($string); + } + + return StringHelper::convertEncoding($string, 'UTF-8', 'UTF-16LE'); + } + + /** + * Convert UTF-16 string in compressed notation to uncompressed form. Only used for BIFF8. + * + * @param string $string + * + * @return string + */ + private static function uncompressByteString($string) + { + $uncompressedString = ''; + $strLen = strlen($string); + for ($i = 0; $i < $strLen; ++$i) { + $uncompressedString .= $string[$i] . "\0"; + } + + return $uncompressedString; + } + + /** + * Convert string to UTF-8. Only used for BIFF5. + * + * @param string $string + * + * @return string + */ + private function decodeCodepage($string) + { + return StringHelper::convertEncoding($string, 'UTF-8', $this->codepage); + } + + /** + * Read 16-bit unsigned integer. + * + * @param string $data + * @param int $pos + * + * @return int + */ + public static function getUInt2d($data, $pos) + { + return ord($data[$pos]) | (ord($data[$pos + 1]) << 8); + } + + /** + * Read 16-bit signed integer. + * + * @param string $data + * @param int $pos + * + * @return int + */ + public static function getInt2d($data, $pos) + { + return unpack('s', $data[$pos] . $data[$pos + 1])[1]; + } + + /** + * Read 32-bit signed integer. + * + * @param string $data + * @param int $pos + * + * @return int + */ + public static function getInt4d($data, $pos) + { + // FIX: represent numbers correctly on 64-bit system + // http://sourceforge.net/tracker/index.php?func=detail&aid=1487372&group_id=99160&atid=623334 + // Changed by Andreas Rehm 2006 to ensure correct result of the <<24 block on 32 and 64bit systems + $_or_24 = ord($data[$pos + 3]); + if ($_or_24 >= 128) { + // negative number + $_ord_24 = -abs((256 - $_or_24) << 24); + } else { + $_ord_24 = ($_or_24 & 127) << 24; + } + + return ord($data[$pos]) | (ord($data[$pos + 1]) << 8) | (ord($data[$pos + 2]) << 16) | $_ord_24; + } + + private function parseRichText($is) + { + $value = new RichText(); + $value->createText($is); + + return $value; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color.php new file mode 100644 index 0000000..06c2d0b --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color.php @@ -0,0 +1,36 @@ + 'FF0000'] + */ + public static function map($color, $palette, $version) + { + if ($color <= 0x07 || $color >= 0x40) { + // special built-in color + return Color\BuiltIn::lookup($color); + } elseif (isset($palette[$color - 8])) { + // palette color, color index 0x08 maps to pallete index 0 + return $palette[$color - 8]; + } + + // default color table + if ($version == Xls::XLS_BIFF8) { + return Color\BIFF8::lookup($color); + } + + // BIFF5 + return Color\BIFF5::lookup($color); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color/BIFF5.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color/BIFF5.php new file mode 100644 index 0000000..743d938 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color/BIFF5.php @@ -0,0 +1,81 @@ + '000000', + 0x09 => 'FFFFFF', + 0x0A => 'FF0000', + 0x0B => '00FF00', + 0x0C => '0000FF', + 0x0D => 'FFFF00', + 0x0E => 'FF00FF', + 0x0F => '00FFFF', + 0x10 => '800000', + 0x11 => '008000', + 0x12 => '000080', + 0x13 => '808000', + 0x14 => '800080', + 0x15 => '008080', + 0x16 => 'C0C0C0', + 0x17 => '808080', + 0x18 => '8080FF', + 0x19 => '802060', + 0x1A => 'FFFFC0', + 0x1B => 'A0E0F0', + 0x1C => '600080', + 0x1D => 'FF8080', + 0x1E => '0080C0', + 0x1F => 'C0C0FF', + 0x20 => '000080', + 0x21 => 'FF00FF', + 0x22 => 'FFFF00', + 0x23 => '00FFFF', + 0x24 => '800080', + 0x25 => '800000', + 0x26 => '008080', + 0x27 => '0000FF', + 0x28 => '00CFFF', + 0x29 => '69FFFF', + 0x2A => 'E0FFE0', + 0x2B => 'FFFF80', + 0x2C => 'A6CAF0', + 0x2D => 'DD9CB3', + 0x2E => 'B38FEE', + 0x2F => 'E3E3E3', + 0x30 => '2A6FF9', + 0x31 => '3FB8CD', + 0x32 => '488436', + 0x33 => '958C41', + 0x34 => '8E5E42', + 0x35 => 'A0627A', + 0x36 => '624FAC', + 0x37 => '969696', + 0x38 => '1D2FBE', + 0x39 => '286676', + 0x3A => '004500', + 0x3B => '453E01', + 0x3C => '6A2813', + 0x3D => '85396A', + 0x3E => '4A3285', + 0x3F => '424242', + ]; + + /** + * Map color array from BIFF5 built-in color index. + * + * @param int $color + * + * @return array + */ + public static function lookup($color) + { + if (isset(self::$map[$color])) { + return ['rgb' => self::$map[$color]]; + } + + return ['rgb' => '000000']; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color/BIFF8.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color/BIFF8.php new file mode 100644 index 0000000..5c109fb --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color/BIFF8.php @@ -0,0 +1,81 @@ + '000000', + 0x09 => 'FFFFFF', + 0x0A => 'FF0000', + 0x0B => '00FF00', + 0x0C => '0000FF', + 0x0D => 'FFFF00', + 0x0E => 'FF00FF', + 0x0F => '00FFFF', + 0x10 => '800000', + 0x11 => '008000', + 0x12 => '000080', + 0x13 => '808000', + 0x14 => '800080', + 0x15 => '008080', + 0x16 => 'C0C0C0', + 0x17 => '808080', + 0x18 => '9999FF', + 0x19 => '993366', + 0x1A => 'FFFFCC', + 0x1B => 'CCFFFF', + 0x1C => '660066', + 0x1D => 'FF8080', + 0x1E => '0066CC', + 0x1F => 'CCCCFF', + 0x20 => '000080', + 0x21 => 'FF00FF', + 0x22 => 'FFFF00', + 0x23 => '00FFFF', + 0x24 => '800080', + 0x25 => '800000', + 0x26 => '008080', + 0x27 => '0000FF', + 0x28 => '00CCFF', + 0x29 => 'CCFFFF', + 0x2A => 'CCFFCC', + 0x2B => 'FFFF99', + 0x2C => '99CCFF', + 0x2D => 'FF99CC', + 0x2E => 'CC99FF', + 0x2F => 'FFCC99', + 0x30 => '3366FF', + 0x31 => '33CCCC', + 0x32 => '99CC00', + 0x33 => 'FFCC00', + 0x34 => 'FF9900', + 0x35 => 'FF6600', + 0x36 => '666699', + 0x37 => '969696', + 0x38 => '003366', + 0x39 => '339966', + 0x3A => '003300', + 0x3B => '333300', + 0x3C => '993300', + 0x3D => '993366', + 0x3E => '333399', + 0x3F => '333333', + ]; + + /** + * Map color array from BIFF8 built-in color index. + * + * @param int $color + * + * @return array + */ + public static function lookup($color) + { + if (isset(self::$map[$color])) { + return ['rgb' => self::$map[$color]]; + } + + return ['rgb' => '000000']; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color/BuiltIn.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color/BuiltIn.php new file mode 100644 index 0000000..90d50e3 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color/BuiltIn.php @@ -0,0 +1,35 @@ + '000000', + 0x01 => 'FFFFFF', + 0x02 => 'FF0000', + 0x03 => '00FF00', + 0x04 => '0000FF', + 0x05 => 'FFFF00', + 0x06 => 'FF00FF', + 0x07 => '00FFFF', + 0x40 => '000000', // system window text color + 0x41 => 'FFFFFF', // system window background color + ]; + + /** + * Map built-in color to RGB value. + * + * @param int $color Indexed color + * + * @return array + */ + public static function lookup($color) + { + if (isset(self::$map[$color])) { + return ['rgb' => self::$map[$color]]; + } + + return ['rgb' => '000000']; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/ErrorCode.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/ErrorCode.php new file mode 100644 index 0000000..7daf723 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/ErrorCode.php @@ -0,0 +1,32 @@ + '#NULL!', + 0x07 => '#DIV/0!', + 0x0F => '#VALUE!', + 0x17 => '#REF!', + 0x1D => '#NAME?', + 0x24 => '#NUM!', + 0x2A => '#N/A', + ]; + + /** + * Map error code, e.g. '#N/A'. + * + * @param int $code + * + * @return bool|string + */ + public static function lookup($code) + { + if (isset(self::$map[$code])) { + return self::$map[$code]; + } + + return false; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Escher.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Escher.php new file mode 100644 index 0000000..e9c95c8 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Escher.php @@ -0,0 +1,624 @@ +object = $object; + } + + private const WHICH_ROUTINE = [ + self::DGGCONTAINER => 'readDggContainer', + self::DGG => 'readDgg', + self::BSTORECONTAINER => 'readBstoreContainer', + self::BSE => 'readBSE', + self::BLIPJPEG => 'readBlipJPEG', + self::BLIPPNG => 'readBlipPNG', + self::OPT => 'readOPT', + self::TERTIARYOPT => 'readTertiaryOPT', + self::SPLITMENUCOLORS => 'readSplitMenuColors', + self::DGCONTAINER => 'readDgContainer', + self::DG => 'readDg', + self::SPGRCONTAINER => 'readSpgrContainer', + self::SPCONTAINER => 'readSpContainer', + self::SPGR => 'readSpgr', + self::SP => 'readSp', + self::CLIENTTEXTBOX => 'readClientTextbox', + self::CLIENTANCHOR => 'readClientAnchor', + self::CLIENTDATA => 'readClientData', + ]; + + /** + * Load Escher stream data. May be a partial Escher stream. + * + * @param string $data + * + * @return BSE|BstoreContainer|DgContainer|DggContainer|\PhpOffice\PhpSpreadsheet\Shared\Escher|SpContainer|SpgrContainer + */ + public function load($data) + { + $this->data = $data; + + // total byte size of Excel data (workbook global substream + sheet substreams) + $this->dataSize = strlen($this->data); + + $this->pos = 0; + + // Parse Escher stream + while ($this->pos < $this->dataSize) { + // offset: 2; size: 2: Record Type + $fbt = Xls::getUInt2d($this->data, $this->pos + 2); + $routine = self::WHICH_ROUTINE[$fbt] ?? 'readDefault'; + if (method_exists($this, $routine)) { + $this->$routine(); + } + } + + return $this->object; + } + + /** + * Read a generic record. + */ + private function readDefault(): void + { + // offset 0; size: 2; recVer and recInstance + //$verInstance = Xls::getUInt2d($this->data, $this->pos); + + // offset: 2; size: 2: Record Type + //$fbt = Xls::getUInt2d($this->data, $this->pos + 2); + + // bit: 0-3; mask: 0x000F; recVer + //$recVer = (0x000F & $verInstance) >> 0; + + $length = Xls::getInt4d($this->data, $this->pos + 4); + //$recordData = substr($this->data, $this->pos + 8, $length); + + // move stream pointer to next record + $this->pos += 8 + $length; + } + + /** + * Read DggContainer record (Drawing Group Container). + */ + private function readDggContainer(): void + { + $length = Xls::getInt4d($this->data, $this->pos + 4); + $recordData = substr($this->data, $this->pos + 8, $length); + + // move stream pointer to next record + $this->pos += 8 + $length; + + // record is a container, read contents + $dggContainer = new DggContainer(); + $this->applyAttribute('setDggContainer', $dggContainer); + $reader = new self($dggContainer); + $reader->load($recordData); + } + + /** + * Read Dgg record (Drawing Group). + */ + private function readDgg(): void + { + $length = Xls::getInt4d($this->data, $this->pos + 4); + //$recordData = substr($this->data, $this->pos + 8, $length); + + // move stream pointer to next record + $this->pos += 8 + $length; + } + + /** + * Read BstoreContainer record (Blip Store Container). + */ + private function readBstoreContainer(): void + { + $length = Xls::getInt4d($this->data, $this->pos + 4); + $recordData = substr($this->data, $this->pos + 8, $length); + + // move stream pointer to next record + $this->pos += 8 + $length; + + // record is a container, read contents + $bstoreContainer = new BstoreContainer(); + $this->applyAttribute('setBstoreContainer', $bstoreContainer); + $reader = new self($bstoreContainer); + $reader->load($recordData); + } + + /** + * Read BSE record. + */ + private function readBSE(): void + { + // offset: 0; size: 2; recVer and recInstance + + // bit: 4-15; mask: 0xFFF0; recInstance + $recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4; + + $length = Xls::getInt4d($this->data, $this->pos + 4); + $recordData = substr($this->data, $this->pos + 8, $length); + + // move stream pointer to next record + $this->pos += 8 + $length; + + // add BSE to BstoreContainer + $BSE = new BSE(); + $this->applyAttribute('addBSE', $BSE); + + $BSE->setBLIPType($recInstance); + + // offset: 0; size: 1; btWin32 (MSOBLIPTYPE) + //$btWin32 = ord($recordData[0]); + + // offset: 1; size: 1; btWin32 (MSOBLIPTYPE) + //$btMacOS = ord($recordData[1]); + + // offset: 2; size: 16; MD4 digest + //$rgbUid = substr($recordData, 2, 16); + + // offset: 18; size: 2; tag + //$tag = Xls::getUInt2d($recordData, 18); + + // offset: 20; size: 4; size of BLIP in bytes + //$size = Xls::getInt4d($recordData, 20); + + // offset: 24; size: 4; number of references to this BLIP + //$cRef = Xls::getInt4d($recordData, 24); + + // offset: 28; size: 4; MSOFO file offset + //$foDelay = Xls::getInt4d($recordData, 28); + + // offset: 32; size: 1; unused1 + //$unused1 = ord($recordData[32]); + + // offset: 33; size: 1; size of nameData in bytes (including null terminator) + $cbName = ord($recordData[33]); + + // offset: 34; size: 1; unused2 + //$unused2 = ord($recordData[34]); + + // offset: 35; size: 1; unused3 + //$unused3 = ord($recordData[35]); + + // offset: 36; size: $cbName; nameData + //$nameData = substr($recordData, 36, $cbName); + + // offset: 36 + $cbName, size: var; the BLIP data + $blipData = substr($recordData, 36 + $cbName); + + // record is a container, read contents + $reader = new self($BSE); + $reader->load($blipData); + } + + /** + * Read BlipJPEG record. Holds raw JPEG image data. + */ + private function readBlipJPEG(): void + { + // offset: 0; size: 2; recVer and recInstance + + // bit: 4-15; mask: 0xFFF0; recInstance + $recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4; + + $length = Xls::getInt4d($this->data, $this->pos + 4); + $recordData = substr($this->data, $this->pos + 8, $length); + + // move stream pointer to next record + $this->pos += 8 + $length; + + $pos = 0; + + // offset: 0; size: 16; rgbUid1 (MD4 digest of) + //$rgbUid1 = substr($recordData, 0, 16); + $pos += 16; + + // offset: 16; size: 16; rgbUid2 (MD4 digest), only if $recInstance = 0x46B or 0x6E3 + if (in_array($recInstance, [0x046B, 0x06E3])) { + //$rgbUid2 = substr($recordData, 16, 16); + $pos += 16; + } + + // offset: var; size: 1; tag + //$tag = ord($recordData[$pos]); + ++$pos; + + // offset: var; size: var; the raw image data + $data = substr($recordData, $pos); + + $blip = new Blip(); + $blip->setData($data); + + $this->applyAttribute('setBlip', $blip); + } + + /** + * Read BlipPNG record. Holds raw PNG image data. + */ + private function readBlipPNG(): void + { + // offset: 0; size: 2; recVer and recInstance + + // bit: 4-15; mask: 0xFFF0; recInstance + $recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4; + + $length = Xls::getInt4d($this->data, $this->pos + 4); + $recordData = substr($this->data, $this->pos + 8, $length); + + // move stream pointer to next record + $this->pos += 8 + $length; + + $pos = 0; + + // offset: 0; size: 16; rgbUid1 (MD4 digest of) + //$rgbUid1 = substr($recordData, 0, 16); + $pos += 16; + + // offset: 16; size: 16; rgbUid2 (MD4 digest), only if $recInstance = 0x46B or 0x6E3 + if ($recInstance == 0x06E1) { + //$rgbUid2 = substr($recordData, 16, 16); + $pos += 16; + } + + // offset: var; size: 1; tag + //$tag = ord($recordData[$pos]); + ++$pos; + + // offset: var; size: var; the raw image data + $data = substr($recordData, $pos); + + $blip = new Blip(); + $blip->setData($data); + + $this->applyAttribute('setBlip', $blip); + } + + /** + * Read OPT record. This record may occur within DggContainer record or SpContainer. + */ + private function readOPT(): void + { + // offset: 0; size: 2; recVer and recInstance + + // bit: 4-15; mask: 0xFFF0; recInstance + $recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4; + + $length = Xls::getInt4d($this->data, $this->pos + 4); + $recordData = substr($this->data, $this->pos + 8, $length); + + // move stream pointer to next record + $this->pos += 8 + $length; + + $this->readOfficeArtRGFOPTE($recordData, $recInstance); + } + + /** + * Read TertiaryOPT record. + */ + private function readTertiaryOPT(): void + { + // offset: 0; size: 2; recVer and recInstance + + // bit: 4-15; mask: 0xFFF0; recInstance + //$recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4; + + $length = Xls::getInt4d($this->data, $this->pos + 4); + //$recordData = substr($this->data, $this->pos + 8, $length); + + // move stream pointer to next record + $this->pos += 8 + $length; + } + + /** + * Read SplitMenuColors record. + */ + private function readSplitMenuColors(): void + { + $length = Xls::getInt4d($this->data, $this->pos + 4); + //$recordData = substr($this->data, $this->pos + 8, $length); + + // move stream pointer to next record + $this->pos += 8 + $length; + } + + /** + * Read DgContainer record (Drawing Container). + */ + private function readDgContainer(): void + { + $length = Xls::getInt4d($this->data, $this->pos + 4); + $recordData = substr($this->data, $this->pos + 8, $length); + + // move stream pointer to next record + $this->pos += 8 + $length; + + // record is a container, read contents + $dgContainer = new DgContainer(); + $this->applyAttribute('setDgContainer', $dgContainer); + $reader = new self($dgContainer); + $reader->load($recordData); + } + + /** + * Read Dg record (Drawing). + */ + private function readDg(): void + { + $length = Xls::getInt4d($this->data, $this->pos + 4); + //$recordData = substr($this->data, $this->pos + 8, $length); + + // move stream pointer to next record + $this->pos += 8 + $length; + } + + /** + * Read SpgrContainer record (Shape Group Container). + */ + private function readSpgrContainer(): void + { + // context is either context DgContainer or SpgrContainer + + $length = Xls::getInt4d($this->data, $this->pos + 4); + $recordData = substr($this->data, $this->pos + 8, $length); + + // move stream pointer to next record + $this->pos += 8 + $length; + + // record is a container, read contents + $spgrContainer = new SpgrContainer(); + + if ($this->object instanceof DgContainer) { + // DgContainer + $this->object->setSpgrContainer($spgrContainer); + } elseif ($this->object instanceof SpgrContainer) { + // SpgrContainer + $this->object->addChild($spgrContainer); + } + + $reader = new self($spgrContainer); + $reader->load($recordData); + } + + /** + * Read SpContainer record (Shape Container). + */ + private function readSpContainer(): void + { + $length = Xls::getInt4d($this->data, $this->pos + 4); + $recordData = substr($this->data, $this->pos + 8, $length); + + // add spContainer to spgrContainer + $spContainer = new SpContainer(); + $this->applyAttribute('addChild', $spContainer); + + // move stream pointer to next record + $this->pos += 8 + $length; + + // record is a container, read contents + $reader = new self($spContainer); + $reader->load($recordData); + } + + /** + * Read Spgr record (Shape Group). + */ + private function readSpgr(): void + { + $length = Xls::getInt4d($this->data, $this->pos + 4); + //$recordData = substr($this->data, $this->pos + 8, $length); + + // move stream pointer to next record + $this->pos += 8 + $length; + } + + /** + * Read Sp record (Shape). + */ + private function readSp(): void + { + // offset: 0; size: 2; recVer and recInstance + + // bit: 4-15; mask: 0xFFF0; recInstance + //$recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4; + + $length = Xls::getInt4d($this->data, $this->pos + 4); + //$recordData = substr($this->data, $this->pos + 8, $length); + + // move stream pointer to next record + $this->pos += 8 + $length; + } + + /** + * Read ClientTextbox record. + */ + private function readClientTextbox(): void + { + // offset: 0; size: 2; recVer and recInstance + + // bit: 4-15; mask: 0xFFF0; recInstance + //$recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4; + + $length = Xls::getInt4d($this->data, $this->pos + 4); + //$recordData = substr($this->data, $this->pos + 8, $length); + + // move stream pointer to next record + $this->pos += 8 + $length; + } + + /** + * Read ClientAnchor record. This record holds information about where the shape is anchored in worksheet. + */ + private function readClientAnchor(): void + { + $length = Xls::getInt4d($this->data, $this->pos + 4); + $recordData = substr($this->data, $this->pos + 8, $length); + + // move stream pointer to next record + $this->pos += 8 + $length; + + // offset: 2; size: 2; upper-left corner column index (0-based) + $c1 = Xls::getUInt2d($recordData, 2); + + // offset: 4; size: 2; upper-left corner horizontal offset in 1/1024 of column width + $startOffsetX = Xls::getUInt2d($recordData, 4); + + // offset: 6; size: 2; upper-left corner row index (0-based) + $r1 = Xls::getUInt2d($recordData, 6); + + // offset: 8; size: 2; upper-left corner vertical offset in 1/256 of row height + $startOffsetY = Xls::getUInt2d($recordData, 8); + + // offset: 10; size: 2; bottom-right corner column index (0-based) + $c2 = Xls::getUInt2d($recordData, 10); + + // offset: 12; size: 2; bottom-right corner horizontal offset in 1/1024 of column width + $endOffsetX = Xls::getUInt2d($recordData, 12); + + // offset: 14; size: 2; bottom-right corner row index (0-based) + $r2 = Xls::getUInt2d($recordData, 14); + + // offset: 16; size: 2; bottom-right corner vertical offset in 1/256 of row height + $endOffsetY = Xls::getUInt2d($recordData, 16); + + $this->applyAttribute('setStartCoordinates', Coordinate::stringFromColumnIndex($c1 + 1) . ($r1 + 1)); + $this->applyAttribute('setStartOffsetX', $startOffsetX); + $this->applyAttribute('setStartOffsetY', $startOffsetY); + $this->applyAttribute('setEndCoordinates', Coordinate::stringFromColumnIndex($c2 + 1) . ($r2 + 1)); + $this->applyAttribute('setEndOffsetX', $endOffsetX); + $this->applyAttribute('setEndOffsetY', $endOffsetY); + } + + /** + * @param mixed $value + */ + private function applyAttribute(string $name, $value): void + { + if (method_exists($this->object, $name)) { + $this->object->$name($value); + } + } + + /** + * Read ClientData record. + */ + private function readClientData(): void + { + $length = Xls::getInt4d($this->data, $this->pos + 4); + //$recordData = substr($this->data, $this->pos + 8, $length); + + // move stream pointer to next record + $this->pos += 8 + $length; + } + + /** + * Read OfficeArtRGFOPTE table of property-value pairs. + * + * @param string $data Binary data + * @param int $n Number of properties + */ + private function readOfficeArtRGFOPTE($data, $n): void + { + $splicedComplexData = substr($data, 6 * $n); + + // loop through property-value pairs + for ($i = 0; $i < $n; ++$i) { + // read 6 bytes at a time + $fopte = substr($data, 6 * $i, 6); + + // offset: 0; size: 2; opid + $opid = Xls::getUInt2d($fopte, 0); + + // bit: 0-13; mask: 0x3FFF; opid.opid + $opidOpid = (0x3FFF & $opid) >> 0; + + // bit: 14; mask 0x4000; 1 = value in op field is BLIP identifier + //$opidFBid = (0x4000 & $opid) >> 14; + + // bit: 15; mask 0x8000; 1 = this is a complex property, op field specifies size of complex data + $opidFComplex = (0x8000 & $opid) >> 15; + + // offset: 2; size: 4; the value for this property + $op = Xls::getInt4d($fopte, 2); + + if ($opidFComplex) { + $complexData = substr($splicedComplexData, 0, $op); + $splicedComplexData = substr($splicedComplexData, $op); + + // we store string value with complex data + $value = $complexData; + } else { + // we store integer value + $value = $op; + } + + if (method_exists($this->object, 'setOPT')) { + $this->object->setOPT($opidOpid, $value); + } + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/MD5.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/MD5.php new file mode 100644 index 0000000..3e15f64 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/MD5.php @@ -0,0 +1,197 @@ +reset(); + } + + /** + * Reset the MD5 stream context. + */ + public function reset(): void + { + $this->a = 0x67452301; + $this->b = 0xEFCDAB89; + $this->c = 0x98BADCFE; + $this->d = 0x10325476; + } + + /** + * Get MD5 stream context. + * + * @return string + */ + public function getContext() + { + $s = ''; + foreach (['a', 'b', 'c', 'd'] as $i) { + $v = $this->{$i}; + $s .= chr($v & 0xff); + $s .= chr(($v >> 8) & 0xff); + $s .= chr(($v >> 16) & 0xff); + $s .= chr(($v >> 24) & 0xff); + } + + return $s; + } + + /** + * Add data to context. + * + * @param string $data Data to add + */ + public function add(string $data): void + { + $words = array_values(unpack('V16', $data)); + + $A = $this->a; + $B = $this->b; + $C = $this->c; + $D = $this->d; + + $F = ['self', 'f']; + $G = ['self', 'g']; + $H = ['self', 'h']; + $I = ['self', 'i']; + + // ROUND 1 + self::step($F, $A, $B, $C, $D, $words[0], 7, 0xd76aa478); + self::step($F, $D, $A, $B, $C, $words[1], 12, 0xe8c7b756); + self::step($F, $C, $D, $A, $B, $words[2], 17, 0x242070db); + self::step($F, $B, $C, $D, $A, $words[3], 22, 0xc1bdceee); + self::step($F, $A, $B, $C, $D, $words[4], 7, 0xf57c0faf); + self::step($F, $D, $A, $B, $C, $words[5], 12, 0x4787c62a); + self::step($F, $C, $D, $A, $B, $words[6], 17, 0xa8304613); + self::step($F, $B, $C, $D, $A, $words[7], 22, 0xfd469501); + self::step($F, $A, $B, $C, $D, $words[8], 7, 0x698098d8); + self::step($F, $D, $A, $B, $C, $words[9], 12, 0x8b44f7af); + self::step($F, $C, $D, $A, $B, $words[10], 17, 0xffff5bb1); + self::step($F, $B, $C, $D, $A, $words[11], 22, 0x895cd7be); + self::step($F, $A, $B, $C, $D, $words[12], 7, 0x6b901122); + self::step($F, $D, $A, $B, $C, $words[13], 12, 0xfd987193); + self::step($F, $C, $D, $A, $B, $words[14], 17, 0xa679438e); + self::step($F, $B, $C, $D, $A, $words[15], 22, 0x49b40821); + + // ROUND 2 + self::step($G, $A, $B, $C, $D, $words[1], 5, 0xf61e2562); + self::step($G, $D, $A, $B, $C, $words[6], 9, 0xc040b340); + self::step($G, $C, $D, $A, $B, $words[11], 14, 0x265e5a51); + self::step($G, $B, $C, $D, $A, $words[0], 20, 0xe9b6c7aa); + self::step($G, $A, $B, $C, $D, $words[5], 5, 0xd62f105d); + self::step($G, $D, $A, $B, $C, $words[10], 9, 0x02441453); + self::step($G, $C, $D, $A, $B, $words[15], 14, 0xd8a1e681); + self::step($G, $B, $C, $D, $A, $words[4], 20, 0xe7d3fbc8); + self::step($G, $A, $B, $C, $D, $words[9], 5, 0x21e1cde6); + self::step($G, $D, $A, $B, $C, $words[14], 9, 0xc33707d6); + self::step($G, $C, $D, $A, $B, $words[3], 14, 0xf4d50d87); + self::step($G, $B, $C, $D, $A, $words[8], 20, 0x455a14ed); + self::step($G, $A, $B, $C, $D, $words[13], 5, 0xa9e3e905); + self::step($G, $D, $A, $B, $C, $words[2], 9, 0xfcefa3f8); + self::step($G, $C, $D, $A, $B, $words[7], 14, 0x676f02d9); + self::step($G, $B, $C, $D, $A, $words[12], 20, 0x8d2a4c8a); + + // ROUND 3 + self::step($H, $A, $B, $C, $D, $words[5], 4, 0xfffa3942); + self::step($H, $D, $A, $B, $C, $words[8], 11, 0x8771f681); + self::step($H, $C, $D, $A, $B, $words[11], 16, 0x6d9d6122); + self::step($H, $B, $C, $D, $A, $words[14], 23, 0xfde5380c); + self::step($H, $A, $B, $C, $D, $words[1], 4, 0xa4beea44); + self::step($H, $D, $A, $B, $C, $words[4], 11, 0x4bdecfa9); + self::step($H, $C, $D, $A, $B, $words[7], 16, 0xf6bb4b60); + self::step($H, $B, $C, $D, $A, $words[10], 23, 0xbebfbc70); + self::step($H, $A, $B, $C, $D, $words[13], 4, 0x289b7ec6); + self::step($H, $D, $A, $B, $C, $words[0], 11, 0xeaa127fa); + self::step($H, $C, $D, $A, $B, $words[3], 16, 0xd4ef3085); + self::step($H, $B, $C, $D, $A, $words[6], 23, 0x04881d05); + self::step($H, $A, $B, $C, $D, $words[9], 4, 0xd9d4d039); + self::step($H, $D, $A, $B, $C, $words[12], 11, 0xe6db99e5); + self::step($H, $C, $D, $A, $B, $words[15], 16, 0x1fa27cf8); + self::step($H, $B, $C, $D, $A, $words[2], 23, 0xc4ac5665); + + // ROUND 4 + self::step($I, $A, $B, $C, $D, $words[0], 6, 0xf4292244); + self::step($I, $D, $A, $B, $C, $words[7], 10, 0x432aff97); + self::step($I, $C, $D, $A, $B, $words[14], 15, 0xab9423a7); + self::step($I, $B, $C, $D, $A, $words[5], 21, 0xfc93a039); + self::step($I, $A, $B, $C, $D, $words[12], 6, 0x655b59c3); + self::step($I, $D, $A, $B, $C, $words[3], 10, 0x8f0ccc92); + self::step($I, $C, $D, $A, $B, $words[10], 15, 0xffeff47d); + self::step($I, $B, $C, $D, $A, $words[1], 21, 0x85845dd1); + self::step($I, $A, $B, $C, $D, $words[8], 6, 0x6fa87e4f); + self::step($I, $D, $A, $B, $C, $words[15], 10, 0xfe2ce6e0); + self::step($I, $C, $D, $A, $B, $words[6], 15, 0xa3014314); + self::step($I, $B, $C, $D, $A, $words[13], 21, 0x4e0811a1); + self::step($I, $A, $B, $C, $D, $words[4], 6, 0xf7537e82); + self::step($I, $D, $A, $B, $C, $words[11], 10, 0xbd3af235); + self::step($I, $C, $D, $A, $B, $words[2], 15, 0x2ad7d2bb); + self::step($I, $B, $C, $D, $A, $words[9], 21, 0xeb86d391); + + $this->a = ($this->a + $A) & 0xffffffff; + $this->b = ($this->b + $B) & 0xffffffff; + $this->c = ($this->c + $C) & 0xffffffff; + $this->d = ($this->d + $D) & 0xffffffff; + } + + private static function f(int $X, int $Y, int $Z) + { + return ($X & $Y) | ((~$X) & $Z); // X AND Y OR NOT X AND Z + } + + private static function g(int $X, int $Y, int $Z) + { + return ($X & $Z) | ($Y & (~$Z)); // X AND Z OR Y AND NOT Z + } + + private static function h(int $X, int $Y, int $Z) + { + return $X ^ $Y ^ $Z; // X XOR Y XOR Z + } + + private static function i(int $X, int $Y, int $Z) + { + return $Y ^ ($X | (~$Z)); // Y XOR (X OR NOT Z) + } + + private static function step($func, int &$A, int $B, int $C, int $D, int $M, int $s, int $t): void + { + $A = ($A + call_user_func($func, $B, $C, $D) + $M + $t) & 0xffffffff; + $A = self::rotate($A, $s); + $A = ($B + $A) & 0xffffffff; + } + + private static function rotate(int $decimal, int $bits) + { + $binary = str_pad(decbin($decimal), 32, '0', STR_PAD_LEFT); + + return bindec(substr($binary, $bits) . substr($binary, 0, $bits)); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/RC4.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/RC4.php new file mode 100644 index 0000000..691aca7 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/RC4.php @@ -0,0 +1,61 @@ +i = 0; $this->i < 256; ++$this->i) { + $this->s[$this->i] = $this->i; + } + + $this->j = 0; + for ($this->i = 0; $this->i < 256; ++$this->i) { + $this->j = ($this->j + $this->s[$this->i] + ord($key[$this->i % $len])) % 256; + $t = $this->s[$this->i]; + $this->s[$this->i] = $this->s[$this->j]; + $this->s[$this->j] = $t; + } + $this->i = $this->j = 0; + } + + /** + * Symmetric decryption/encryption function. + * + * @param string $data Data to encrypt/decrypt + * + * @return string + */ + public function RC4($data) + { + $len = strlen($data); + for ($c = 0; $c < $len; ++$c) { + $this->i = ($this->i + 1) % 256; + $this->j = ($this->j + $this->s[$this->i]) % 256; + $t = $this->s[$this->i]; + $this->s[$this->i] = $this->s[$this->j]; + $this->s[$this->j] = $t; + + $t = ($this->s[$this->i] + $this->s[$this->j]) % 256; + + $data[$c] = chr(ord($data[$c]) ^ $this->s[$t]); + } + + return $data; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Style/Border.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Style/Border.php new file mode 100644 index 0000000..d832eb0 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Style/Border.php @@ -0,0 +1,37 @@ + + */ + protected static $borderStyleMap = [ + 0x00 => StyleBorder::BORDER_NONE, + 0x01 => StyleBorder::BORDER_THIN, + 0x02 => StyleBorder::BORDER_MEDIUM, + 0x03 => StyleBorder::BORDER_DASHED, + 0x04 => StyleBorder::BORDER_DOTTED, + 0x05 => StyleBorder::BORDER_THICK, + 0x06 => StyleBorder::BORDER_DOUBLE, + 0x07 => StyleBorder::BORDER_HAIR, + 0x08 => StyleBorder::BORDER_MEDIUMDASHED, + 0x09 => StyleBorder::BORDER_DASHDOT, + 0x0A => StyleBorder::BORDER_MEDIUMDASHDOT, + 0x0B => StyleBorder::BORDER_DASHDOTDOT, + 0x0C => StyleBorder::BORDER_MEDIUMDASHDOTDOT, + 0x0D => StyleBorder::BORDER_SLANTDASHDOT, + ]; + + public static function lookup(int $index): string + { + if (isset(self::$borderStyleMap[$index])) { + return self::$borderStyleMap[$index]; + } + + return StyleBorder::BORDER_NONE; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Style/CellAlignment.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Style/CellAlignment.php new file mode 100644 index 0000000..f03d47f --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Style/CellAlignment.php @@ -0,0 +1,50 @@ + + */ + protected static $horizontalAlignmentMap = [ + 0 => Alignment::HORIZONTAL_GENERAL, + 1 => Alignment::HORIZONTAL_LEFT, + 2 => Alignment::HORIZONTAL_CENTER, + 3 => Alignment::HORIZONTAL_RIGHT, + 4 => Alignment::HORIZONTAL_FILL, + 5 => Alignment::HORIZONTAL_JUSTIFY, + 6 => Alignment::HORIZONTAL_CENTER_CONTINUOUS, + ]; + + /** + * @var array + */ + protected static $verticalAlignmentMap = [ + 0 => Alignment::VERTICAL_TOP, + 1 => Alignment::VERTICAL_CENTER, + 2 => Alignment::VERTICAL_BOTTOM, + 3 => Alignment::VERTICAL_JUSTIFY, + ]; + + public static function horizontal(Alignment $alignment, int $horizontal): void + { + if (array_key_exists($horizontal, self::$horizontalAlignmentMap)) { + $alignment->setHorizontal(self::$horizontalAlignmentMap[$horizontal]); + } + } + + public static function vertical(Alignment $alignment, int $vertical): void + { + if (array_key_exists($vertical, self::$verticalAlignmentMap)) { + $alignment->setVertical(self::$verticalAlignmentMap[$vertical]); + } + } + + public static function wrap(Alignment $alignment, int $wrap): void + { + $alignment->setWrapText((bool) $wrap); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Style/CellFont.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Style/CellFont.php new file mode 100644 index 0000000..e975be4 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Style/CellFont.php @@ -0,0 +1,39 @@ +setSuperscript(true); + + break; + case 0x0002: + $font->setSubscript(true); + + break; + } + } + + /** + * @var array + */ + protected static $underlineMap = [ + 0x01 => Font::UNDERLINE_SINGLE, + 0x02 => Font::UNDERLINE_DOUBLE, + 0x21 => Font::UNDERLINE_SINGLEACCOUNTING, + 0x22 => Font::UNDERLINE_DOUBLEACCOUNTING, + ]; + + public static function underline(Font $font, int $underline): void + { + if (array_key_exists($underline, self::$underlineMap)) { + $font->setUnderline(self::$underlineMap[$underline]); + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Style/FillPattern.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Style/FillPattern.php new file mode 100644 index 0000000..32ab5c8 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Style/FillPattern.php @@ -0,0 +1,50 @@ + + */ + protected static $fillPatternMap = [ + 0x00 => Fill::FILL_NONE, + 0x01 => Fill::FILL_SOLID, + 0x02 => Fill::FILL_PATTERN_MEDIUMGRAY, + 0x03 => Fill::FILL_PATTERN_DARKGRAY, + 0x04 => Fill::FILL_PATTERN_LIGHTGRAY, + 0x05 => Fill::FILL_PATTERN_DARKHORIZONTAL, + 0x06 => Fill::FILL_PATTERN_DARKVERTICAL, + 0x07 => Fill::FILL_PATTERN_DARKDOWN, + 0x08 => Fill::FILL_PATTERN_DARKUP, + 0x09 => Fill::FILL_PATTERN_DARKGRID, + 0x0A => Fill::FILL_PATTERN_DARKTRELLIS, + 0x0B => Fill::FILL_PATTERN_LIGHTHORIZONTAL, + 0x0C => Fill::FILL_PATTERN_LIGHTVERTICAL, + 0x0D => Fill::FILL_PATTERN_LIGHTDOWN, + 0x0E => Fill::FILL_PATTERN_LIGHTUP, + 0x0F => Fill::FILL_PATTERN_LIGHTGRID, + 0x10 => Fill::FILL_PATTERN_LIGHTTRELLIS, + 0x11 => Fill::FILL_PATTERN_GRAY125, + 0x12 => Fill::FILL_PATTERN_GRAY0625, + ]; + + /** + * Get fill pattern from index + * OpenOffice documentation: 2.5.12. + * + * @param int $index + * + * @return string + */ + public static function lookup($index) + { + if (isset(self::$fillPatternMap[$index])) { + return self::$fillPatternMap[$index]; + } + + return Fill::FILL_NONE; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx.php new file mode 100644 index 0000000..c8b9126 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx.php @@ -0,0 +1,2076 @@ +referenceHelper = ReferenceHelper::getInstance(); + $this->securityScanner = XmlScanner::getInstance($this); + } + + /** + * Can the current IReader read the file? + */ + public function canRead(string $filename): bool + { + if (!File::testFileNoThrow($filename, self::INITIAL_FILE)) { + return false; + } + + $result = false; + $this->zip = $zip = new ZipArchive(); + + if ($zip->open($filename) === true) { + [$workbookBasename] = $this->getWorkbookBaseName(); + $result = !empty($workbookBasename); + + $zip->close(); + } + + return $result; + } + + /** + * @param mixed $value + */ + public static function testSimpleXml($value): SimpleXMLElement + { + return ($value instanceof SimpleXMLElement) ? $value : new SimpleXMLElement(''); + } + + public static function getAttributes(?SimpleXMLElement $value, string $ns = ''): SimpleXMLElement + { + return self::testSimpleXml($value === null ? $value : $value->attributes($ns)); + } + + // Phpstan thinks, correctly, that xpath can return false. + // Scrutinizer thinks it can't. + // Sigh. + private static function xpathNoFalse(SimpleXmlElement $sxml, string $path): array + { + return self::falseToArray($sxml->xpath($path)); + } + + /** + * @param mixed $value + */ + public static function falseToArray($value): array + { + return is_array($value) ? $value : []; + } + + private function loadZip(string $filename, string $ns = ''): SimpleXMLElement + { + $contents = $this->getFromZipArchive($this->zip, $filename); + $rels = simplexml_load_string( + $this->securityScanner->scan($contents), + 'SimpleXMLElement', + Settings::getLibXmlLoaderOptions(), + $ns + ); + + return self::testSimpleXml($rels); + } + + // This function is just to identify cases where I'm not sure + // why empty namespace is required. + private function loadZipNonamespace(string $filename, string $ns): SimpleXMLElement + { + $contents = $this->getFromZipArchive($this->zip, $filename); + $rels = simplexml_load_string( + $this->securityScanner->scan($contents), + 'SimpleXMLElement', + Settings::getLibXmlLoaderOptions(), + ($ns === '' ? $ns : '') + ); + + return self::testSimpleXml($rels); + } + + private const REL_TO_MAIN = [ + Namespaces::PURL_OFFICE_DOCUMENT => Namespaces::PURL_MAIN, + ]; + + private const REL_TO_DRAWING = [ + Namespaces::PURL_RELATIONSHIPS => Namespaces::PURL_DRAWING, + ]; + + /** + * Reads names of the worksheets from a file, without parsing the whole file to a Spreadsheet object. + * + * @param string $filename + * + * @return array + */ + public function listWorksheetNames($filename) + { + File::assertFile($filename, self::INITIAL_FILE); + + $worksheetNames = []; + + $this->zip = $zip = new ZipArchive(); + $zip->open($filename); + + // The files we're looking at here are small enough that simpleXML is more efficient than XMLReader + $rels = $this->loadZip(self::INITIAL_FILE, Namespaces::RELATIONSHIPS); + foreach ($rels->Relationship as $relx) { + $rel = self::getAttributes($relx); + $relType = (string) $rel['Type']; + $mainNS = self::REL_TO_MAIN[$relType] ?? Namespaces::MAIN; + if ($mainNS !== '') { + $xmlWorkbook = $this->loadZip((string) $rel['Target'], $mainNS); + + if ($xmlWorkbook->sheets) { + foreach ($xmlWorkbook->sheets->sheet as $eleSheet) { + // Check if sheet should be skipped + $worksheetNames[] = (string) self::getAttributes($eleSheet)['name']; + } + } + } + } + + $zip->close(); + + return $worksheetNames; + } + + /** + * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns). + * + * @param string $filename + * + * @return array + */ + public function listWorksheetInfo($filename) + { + File::assertFile($filename, self::INITIAL_FILE); + + $worksheetInfo = []; + + $this->zip = $zip = new ZipArchive(); + $zip->open($filename); + + $rels = $this->loadZip(self::INITIAL_FILE, Namespaces::RELATIONSHIPS); + foreach ($rels->Relationship as $relx) { + $rel = self::getAttributes($relx); + $relType = (string) $rel['Type']; + $mainNS = self::REL_TO_MAIN[$relType] ?? Namespaces::MAIN; + if ($mainNS !== '') { + $relTarget = (string) $rel['Target']; + $dir = dirname($relTarget); + $namespace = dirname($relType); + $relsWorkbook = $this->loadZip("$dir/_rels/" . basename($relTarget) . '.rels', ''); + + $worksheets = []; + foreach ($relsWorkbook->Relationship as $elex) { + $ele = self::getAttributes($elex); + if ((string) $ele['Type'] === "$namespace/worksheet") { + $worksheets[(string) $ele['Id']] = $ele['Target']; + } + } + + $xmlWorkbook = $this->loadZip($relTarget, $mainNS); + if ($xmlWorkbook->sheets) { + $dir = dirname($relTarget); + /** @var SimpleXMLElement $eleSheet */ + foreach ($xmlWorkbook->sheets->sheet as $eleSheet) { + $tmpInfo = [ + 'worksheetName' => (string) self::getAttributes($eleSheet)['name'], + 'lastColumnLetter' => 'A', + 'lastColumnIndex' => 0, + 'totalRows' => 0, + 'totalColumns' => 0, + ]; + + $fileWorksheet = (string) $worksheets[(string) self::getArrayItem(self::getAttributes($eleSheet, $namespace), 'id')]; + $fileWorksheetPath = strpos($fileWorksheet, '/') === 0 ? substr($fileWorksheet, 1) : "$dir/$fileWorksheet"; + + $xml = new XMLReader(); + $xml->xml( + $this->securityScanner->scanFile( + 'zip://' . File::realpath($filename) . '#' . $fileWorksheetPath + ), + null, + Settings::getLibXmlLoaderOptions() + ); + $xml->setParserProperty(2, true); + + $currCells = 0; + while ($xml->read()) { + if ($xml->localName == 'row' && $xml->nodeType == XMLReader::ELEMENT && $xml->namespaceURI === $mainNS) { + $row = $xml->getAttribute('r'); + $tmpInfo['totalRows'] = $row; + $tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells); + $currCells = 0; + } elseif ($xml->localName == 'c' && $xml->nodeType == XMLReader::ELEMENT && $xml->namespaceURI === $mainNS) { + $cell = $xml->getAttribute('r'); + $currCells = $cell ? max($currCells, Coordinate::indexesFromString($cell)[0]) : ($currCells + 1); + } + } + $tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells); + $xml->close(); + + $tmpInfo['lastColumnIndex'] = $tmpInfo['totalColumns'] - 1; + $tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1); + + $worksheetInfo[] = $tmpInfo; + } + } + } + } + + $zip->close(); + + return $worksheetInfo; + } + + private static function castToBoolean($c) + { + $value = isset($c->v) ? (string) $c->v : null; + if ($value == '0') { + return false; + } elseif ($value == '1') { + return true; + } + + return (bool) $c->v; + } + + private static function castToError($c) + { + return isset($c->v) ? (string) $c->v : null; + } + + private static function castToString($c) + { + return isset($c->v) ? (string) $c->v : null; + } + + private function castToFormula($c, $r, &$cellDataType, &$value, &$calculatedValue, &$sharedFormulas, $castBaseType): void + { + $attr = $c->f->attributes(); + $cellDataType = 'f'; + $value = "={$c->f}"; + $calculatedValue = self::$castBaseType($c); + + // Shared formula? + if (isset($attr['t']) && strtolower((string) $attr['t']) == 'shared') { + $instance = (string) $attr['si']; + + if (!isset($sharedFormulas[(string) $attr['si']])) { + $sharedFormulas[$instance] = ['master' => $r, 'formula' => $value]; + } else { + $master = Coordinate::indexesFromString($sharedFormulas[$instance]['master']); + $current = Coordinate::indexesFromString($r); + + $difference = [0, 0]; + $difference[0] = $current[0] - $master[0]; + $difference[1] = $current[1] - $master[1]; + + $value = $this->referenceHelper->updateFormulaReferences($sharedFormulas[$instance]['formula'], 'A1', $difference[0], $difference[1]); + } + } + } + + /** + * @param string $fileName + */ + private function fileExistsInArchive(ZipArchive $archive, $fileName = ''): bool + { + // Root-relative paths + if (strpos($fileName, '//') !== false) { + $fileName = substr($fileName, strpos($fileName, '//') + 1); + } + $fileName = File::realpath($fileName); + + // Sadly, some 3rd party xlsx generators don't use consistent case for filenaming + // so we need to load case-insensitively from the zip file + + // Apache POI fixes + $contents = $archive->locateName($fileName, ZipArchive::FL_NOCASE); + if ($contents === false) { + $contents = $archive->locateName(substr($fileName, 1), ZipArchive::FL_NOCASE); + } + + return $contents !== false; + } + + /** + * @param string $fileName + * + * @return string + */ + private function getFromZipArchive(ZipArchive $archive, $fileName = '') + { + // Root-relative paths + if (strpos($fileName, '//') !== false) { + $fileName = substr($fileName, strpos($fileName, '//') + 1); + } + $fileName = File::realpath($fileName); + + // Sadly, some 3rd party xlsx generators don't use consistent case for filenaming + // so we need to load case-insensitively from the zip file + + // Apache POI fixes + $contents = $archive->getFromName($fileName, 0, ZipArchive::FL_NOCASE); + if ($contents === false) { + $contents = $archive->getFromName(substr($fileName, 1), 0, ZipArchive::FL_NOCASE); + } + + return $contents; + } + + /** + * Loads Spreadsheet from file. + */ + public function load(string $filename, int $flags = 0): Spreadsheet + { + File::assertFile($filename, self::INITIAL_FILE); + $this->processFlags($flags); + + // Initialisations + $excel = new Spreadsheet(); + $excel->removeSheetByIndex(0); + $addingFirstCellStyleXf = true; + $addingFirstCellXf = true; + + $unparsedLoadedData = []; + + $this->zip = $zip = new ZipArchive(); + $zip->open($filename); + + // Read the theme first, because we need the colour scheme when reading the styles + [$workbookBasename, $xmlNamespaceBase] = $this->getWorkbookBaseName(); + $wbRels = $this->loadZip("xl/_rels/${workbookBasename}.rels", Namespaces::RELATIONSHIPS); + foreach ($wbRels->Relationship as $relx) { + $rel = self::getAttributes($relx); + $relTarget = (string) $rel['Target']; + switch ($rel['Type']) { + case "$xmlNamespaceBase/theme": + $themeOrderArray = ['lt1', 'dk1', 'lt2', 'dk2']; + $themeOrderAdditional = count($themeOrderArray); + $drawingNS = self::REL_TO_DRAWING[$xmlNamespaceBase] ?? Namespaces::DRAWINGML; + + $xmlTheme = $this->loadZip("xl/{$relTarget}", $drawingNS); + $xmlThemeName = self::getAttributes($xmlTheme); + $xmlTheme = $xmlTheme->children($drawingNS); + $themeName = (string) $xmlThemeName['name']; + + $colourScheme = self::getAttributes($xmlTheme->themeElements->clrScheme); + $colourSchemeName = (string) $colourScheme['name']; + $colourScheme = $xmlTheme->themeElements->clrScheme->children($drawingNS); + + $themeColours = []; + foreach ($colourScheme as $k => $xmlColour) { + $themePos = array_search($k, $themeOrderArray); + if ($themePos === false) { + $themePos = $themeOrderAdditional++; + } + if (isset($xmlColour->sysClr)) { + $xmlColourData = self::getAttributes($xmlColour->sysClr); + $themeColours[$themePos] = (string) $xmlColourData['lastClr']; + } elseif (isset($xmlColour->srgbClr)) { + $xmlColourData = self::getAttributes($xmlColour->srgbClr); + $themeColours[$themePos] = (string) $xmlColourData['val']; + } + } + self::$theme = new Xlsx\Theme($themeName, $colourSchemeName, $themeColours); + + break; + } + } + + $rels = $this->loadZip(self::INITIAL_FILE, Namespaces::RELATIONSHIPS); + + $propertyReader = new PropertyReader($this->securityScanner, $excel->getProperties()); + foreach ($rels->Relationship as $relx) { + $rel = self::getAttributes($relx); + $relTarget = (string) $rel['Target']; + $relType = (string) $rel['Type']; + $mainNS = self::REL_TO_MAIN[$relType] ?? Namespaces::MAIN; + switch ($relType) { + case Namespaces::CORE_PROPERTIES: + $propertyReader->readCoreProperties($this->getFromZipArchive($zip, $relTarget)); + + break; + case "$xmlNamespaceBase/extended-properties": + $propertyReader->readExtendedProperties($this->getFromZipArchive($zip, $relTarget)); + + break; + case "$xmlNamespaceBase/custom-properties": + $propertyReader->readCustomProperties($this->getFromZipArchive($zip, $relTarget)); + + break; + //Ribbon + case Namespaces::EXTENSIBILITY: + $customUI = $relTarget; + if ($customUI) { + $this->readRibbon($excel, $customUI, $zip); + } + + break; + case "$xmlNamespaceBase/officeDocument": + $dir = dirname($relTarget); + + // Do not specify namespace in next stmt - do it in Xpath + $relsWorkbook = $this->loadZip("$dir/_rels/" . basename($relTarget) . '.rels', ''); + $relsWorkbook->registerXPathNamespace('rel', Namespaces::RELATIONSHIPS); + + $sharedStrings = []; + $relType = "rel:Relationship[@Type='" + //. Namespaces::SHARED_STRINGS + . "$xmlNamespaceBase/sharedStrings" + . "']"; + $xpath = self::getArrayItem($relsWorkbook->xpath($relType)); + + if ($xpath) { + $xmlStrings = $this->loadZip("$dir/$xpath[Target]", $mainNS); + if (isset($xmlStrings->si)) { + foreach ($xmlStrings->si as $val) { + if (isset($val->t)) { + $sharedStrings[] = StringHelper::controlCharacterOOXML2PHP((string) $val->t); + } elseif (isset($val->r)) { + $sharedStrings[] = $this->parseRichText($val); + } + } + } + } + + $worksheets = []; + $macros = $customUI = null; + foreach ($relsWorkbook->Relationship as $elex) { + $ele = self::getAttributes($elex); + switch ($ele['Type']) { + case Namespaces::WORKSHEET: + case Namespaces::PURL_WORKSHEET: + $worksheets[(string) $ele['Id']] = $ele['Target']; + + break; + // a vbaProject ? (: some macros) + case Namespaces::VBA: + $macros = $ele['Target']; + + break; + } + } + + if ($macros !== null) { + $macrosCode = $this->getFromZipArchive($zip, 'xl/vbaProject.bin'); //vbaProject.bin always in 'xl' dir and always named vbaProject.bin + if ($macrosCode !== false) { + $excel->setMacrosCode($macrosCode); + $excel->setHasMacros(true); + //short-circuit : not reading vbaProject.bin.rel to get Signature =>allways vbaProjectSignature.bin in 'xl' dir + $Certificate = $this->getFromZipArchive($zip, 'xl/vbaProjectSignature.bin'); + if ($Certificate !== false) { + $excel->setMacrosCertificate($Certificate); + } + } + } + + $relType = "rel:Relationship[@Type='" + . "$xmlNamespaceBase/styles" + . "']"; + $xpath = self::getArrayItem(self::xpathNoFalse($relsWorkbook, $relType)); + + if ($xpath === null) { + $xmlStyles = self::testSimpleXml(null); + } else { + // I think Nonamespace is okay because I'm using xpath. + $xmlStyles = $this->loadZipNonamespace("$dir/$xpath[Target]", $mainNS); + } + + $xmlStyles->registerXPathNamespace('smm', Namespaces::MAIN); + $fills = self::xpathNoFalse($xmlStyles, 'smm:fills/smm:fill'); + $fonts = self::xpathNoFalse($xmlStyles, 'smm:fonts/smm:font'); + $borders = self::xpathNoFalse($xmlStyles, 'smm:borders/smm:border'); + $xfTags = self::xpathNoFalse($xmlStyles, 'smm:cellXfs/smm:xf'); + $cellXfTags = self::xpathNoFalse($xmlStyles, 'smm:cellStyleXfs/smm:xf'); + + $styles = []; + $cellStyles = []; + $numFmts = null; + if (/*$xmlStyles && */ $xmlStyles->numFmts[0]) { + $numFmts = $xmlStyles->numFmts[0]; + } + if (isset($numFmts) && ($numFmts !== null)) { + $numFmts->registerXPathNamespace('sml', $mainNS); + } + if (!$this->readDataOnly/* && $xmlStyles*/) { + foreach ($xfTags as $xfTag) { + $xf = self::getAttributes($xfTag); + $numFmt = null; + + if ($xf['numFmtId']) { + if (isset($numFmts)) { + $tmpNumFmt = self::getArrayItem($numFmts->xpath("sml:numFmt[@numFmtId=$xf[numFmtId]]")); + + if (isset($tmpNumFmt['formatCode'])) { + $numFmt = (string) $tmpNumFmt['formatCode']; + } + } + + // We shouldn't override any of the built-in MS Excel values (values below id 164) + // But there's a lot of naughty homebrew xlsx writers that do use "reserved" id values that aren't actually used + // So we make allowance for them rather than lose formatting masks + if ( + $numFmt === null && + (int) $xf['numFmtId'] < 164 && + NumberFormat::builtInFormatCode((int) $xf['numFmtId']) !== '' + ) { + $numFmt = NumberFormat::builtInFormatCode((int) $xf['numFmtId']); + } + } + $quotePrefix = (bool) ($xf['quotePrefix'] ?? false); + + $style = (object) [ + 'numFmt' => $numFmt ?? NumberFormat::FORMAT_GENERAL, + 'font' => $fonts[(int) ($xf['fontId'])], + 'fill' => $fills[(int) ($xf['fillId'])], + 'border' => $borders[(int) ($xf['borderId'])], + 'alignment' => $xfTag->alignment, + 'protection' => $xfTag->protection, + 'quotePrefix' => $quotePrefix, + ]; + $styles[] = $style; + + // add style to cellXf collection + $objStyle = new Style(); + self::readStyle($objStyle, $style); + if ($addingFirstCellXf) { + $excel->removeCellXfByIndex(0); // remove the default style + $addingFirstCellXf = false; + } + $excel->addCellXf($objStyle); + } + + foreach ($cellXfTags as $xfTag) { + $xf = self::getAttributes($xfTag); + $numFmt = NumberFormat::FORMAT_GENERAL; + if ($numFmts && $xf['numFmtId']) { + $tmpNumFmt = self::getArrayItem($numFmts->xpath("sml:numFmt[@numFmtId=$xf[numFmtId]]")); + if (isset($tmpNumFmt['formatCode'])) { + $numFmt = (string) $tmpNumFmt['formatCode']; + } elseif ((int) $xf['numFmtId'] < 165) { + $numFmt = NumberFormat::builtInFormatCode((int) $xf['numFmtId']); + } + } + + $quotePrefix = (bool) ($xf['quotePrefix'] ?? false); + + $cellStyle = (object) [ + 'numFmt' => $numFmt, + 'font' => $fonts[(int) ($xf['fontId'])], + 'fill' => $fills[((int) $xf['fillId'])], + 'border' => $borders[(int) ($xf['borderId'])], + 'alignment' => $xfTag->alignment, + 'protection' => $xfTag->protection, + 'quotePrefix' => $quotePrefix, + ]; + $cellStyles[] = $cellStyle; + + // add style to cellStyleXf collection + $objStyle = new Style(); + self::readStyle($objStyle, $cellStyle); + if ($addingFirstCellStyleXf) { + $excel->removeCellStyleXfByIndex(0); // remove the default style + $addingFirstCellStyleXf = false; + } + $excel->addCellStyleXf($objStyle); + } + } + $styleReader = new Styles($xmlStyles); + $styleReader->setStyleBaseData(self::$theme, $styles, $cellStyles); + $dxfs = $styleReader->dxfs($this->readDataOnly); + $styles = $styleReader->styles(); + + $xmlWorkbook = $this->loadZipNoNamespace($relTarget, $mainNS); + $xmlWorkbookNS = $this->loadZip($relTarget, $mainNS); + + // Set base date + if ($xmlWorkbookNS->workbookPr) { + Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); + $attrs1904 = self::getAttributes($xmlWorkbookNS->workbookPr); + if (isset($attrs1904['date1904'])) { + if (self::boolean((string) $attrs1904['date1904'])) { + Date::setExcelCalendar(Date::CALENDAR_MAC_1904); + } + } + } + + // Set protection + $this->readProtection($excel, $xmlWorkbook); + + $sheetId = 0; // keep track of new sheet id in final workbook + $oldSheetId = -1; // keep track of old sheet id in final workbook + $countSkippedSheets = 0; // keep track of number of skipped sheets + $mapSheetId = []; // mapping of sheet ids from old to new + + $charts = $chartDetails = []; + + if ($xmlWorkbookNS->sheets) { + /** @var SimpleXMLElement $eleSheet */ + foreach ($xmlWorkbookNS->sheets->sheet as $eleSheet) { + $eleSheetAttr = self::getAttributes($eleSheet); + ++$oldSheetId; + + // Check if sheet should be skipped + if (is_array($this->loadSheetsOnly) && !in_array((string) $eleSheetAttr['name'], $this->loadSheetsOnly)) { + ++$countSkippedSheets; + $mapSheetId[$oldSheetId] = null; + + continue; + } + + // Map old sheet id in original workbook to new sheet id. + // They will differ if loadSheetsOnly() is being used + $mapSheetId[$oldSheetId] = $oldSheetId - $countSkippedSheets; + + // Load sheet + $docSheet = $excel->createSheet(); + // Use false for $updateFormulaCellReferences to prevent adjustment of worksheet + // references in formula cells... during the load, all formulae should be correct, + // and we're simply bringing the worksheet name in line with the formula, not the + // reverse + $docSheet->setTitle((string) $eleSheetAttr['name'], false, false); + $fileWorksheet = (string) $worksheets[(string) self::getArrayItem(self::getAttributes($eleSheet, $xmlNamespaceBase), 'id')]; + $xmlSheet = $this->loadZipNoNamespace("$dir/$fileWorksheet", $mainNS); + $xmlSheetNS = $this->loadZip("$dir/$fileWorksheet", $mainNS); + + $sharedFormulas = []; + + if (isset($eleSheetAttr['state']) && (string) $eleSheetAttr['state'] != '') { + $docSheet->setSheetState((string) $eleSheetAttr['state']); + } + if ($xmlSheetNS) { + $xmlSheetMain = $xmlSheetNS->children($mainNS); + // Setting Conditional Styles adjusts selected cells, so we need to execute this + // before reading the sheet view data to get the actual selected cells + if (!$this->readDataOnly && $xmlSheet->conditionalFormatting) { + (new ConditionalStyles($docSheet, $xmlSheet, $dxfs))->load(); + } + if (isset($xmlSheetMain->sheetViews, $xmlSheetMain->sheetViews->sheetView)) { + $sheetViews = new SheetViews($xmlSheetMain->sheetViews->sheetView, $docSheet); + $sheetViews->load(); + } + + $sheetViewOptions = new SheetViewOptions($docSheet, $xmlSheet); + $sheetViewOptions->load($this->getReadDataOnly()); + + (new ColumnAndRowAttributes($docSheet, $xmlSheet)) + ->load($this->getReadFilter(), $this->getReadDataOnly()); + } + + if ($xmlSheetNS && $xmlSheetNS->sheetData && $xmlSheetNS->sheetData->row) { + $cIndex = 1; // Cell Start from 1 + foreach ($xmlSheetNS->sheetData->row as $row) { + $rowIndex = 1; + foreach ($row->c as $c) { + $cAttr = self::getAttributes($c); + $r = (string) $cAttr['r']; + if ($r == '') { + $r = Coordinate::stringFromColumnIndex($rowIndex) . $cIndex; + } + $cellDataType = (string) $cAttr['t']; + $value = null; + $calculatedValue = null; + + // Read cell? + if ($this->getReadFilter() !== null) { + $coordinates = Coordinate::coordinateFromString($r); + + if (!$this->getReadFilter()->readCell($coordinates[0], (int) $coordinates[1], $docSheet->getTitle())) { + if (isset($cAttr->f)) { + $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToError'); + } + ++$rowIndex; + + continue; + } + } + + // Read cell! + switch ($cellDataType) { + case 's': + if ((string) $c->v != '') { + $value = $sharedStrings[(int) ($c->v)]; + + if ($value instanceof RichText) { + $value = clone $value; + } + } else { + $value = ''; + } + + break; + case 'b': + if (!isset($c->f)) { + $value = self::castToBoolean($c); + } else { + // Formula + $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToBoolean'); + if (isset($c->f['t'])) { + $att = $c->f; + $docSheet->getCell($r)->setFormulaAttributes($att); + } + } + + break; + case 'inlineStr': + if (isset($c->f)) { + $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToError'); + } else { + $value = $this->parseRichText($c->is); + } + + break; + case 'e': + if (!isset($c->f)) { + $value = self::castToError($c); + } else { + // Formula + $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToError'); + } + + break; + default: + if (!isset($c->f)) { + $value = self::castToString($c); + } else { + // Formula + $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToString'); + if (isset($c->f['t'])) { + $attributes = $c->f['t']; + $docSheet->getCell($r)->setFormulaAttributes(['t' => (string) $attributes]); + } + } + + break; + } + + // read empty cells or the cells are not empty + if ($this->readEmptyCells || ($value !== null && $value !== '')) { + // Rich text? + if ($value instanceof RichText && $this->readDataOnly) { + $value = $value->getPlainText(); + } + + $cell = $docSheet->getCell($r); + // Assign value + if ($cellDataType != '') { + // it is possible, that datatype is numeric but with an empty string, which result in an error + if ($cellDataType === DataType::TYPE_NUMERIC && $value === '') { + $cellDataType = DataType::TYPE_STRING; + } + $cell->setValueExplicit($value, $cellDataType); + } else { + $cell->setValue($value); + } + if ($calculatedValue !== null) { + $cell->setCalculatedValue($calculatedValue); + } + + // Style information? + if ($cAttr['s'] && !$this->readDataOnly) { + // no style index means 0, it seems + $cell->setXfIndex(isset($styles[(int) ($cAttr['s'])]) ? + (int) ($cAttr['s']) : 0); + } + } + ++$rowIndex; + } + ++$cIndex; + } + } + + $aKeys = ['sheet', 'objects', 'scenarios', 'formatCells', 'formatColumns', 'formatRows', 'insertColumns', 'insertRows', 'insertHyperlinks', 'deleteColumns', 'deleteRows', 'selectLockedCells', 'sort', 'autoFilter', 'pivotTables', 'selectUnlockedCells']; + if (!$this->readDataOnly && $xmlSheet && $xmlSheet->sheetProtection) { + foreach ($aKeys as $key) { + $method = 'set' . ucfirst($key); + $docSheet->getProtection()->$method(self::boolean((string) $xmlSheet->sheetProtection[$key])); + } + } + + if ($xmlSheet) { + $this->readSheetProtection($docSheet, $xmlSheet); + } + + if ($this->readDataOnly === false) { + $this->readAutoFilterTables($xmlSheet, $docSheet, $dir, $fileWorksheet, $zip); + } + + if ($xmlSheet && $xmlSheet->mergeCells && $xmlSheet->mergeCells->mergeCell && !$this->readDataOnly) { + foreach ($xmlSheet->mergeCells->mergeCell as $mergeCell) { + $mergeRef = (string) $mergeCell['ref']; + if (strpos($mergeRef, ':') !== false) { + $docSheet->mergeCells((string) $mergeCell['ref']); + } + } + } + + if ($xmlSheet && !$this->readDataOnly) { + $unparsedLoadedData = (new PageSetup($docSheet, $xmlSheet))->load($unparsedLoadedData); + } + + if ($xmlSheet !== false && isset($xmlSheet->extLst, $xmlSheet->extLst->ext, $xmlSheet->extLst->ext['uri']) && ($xmlSheet->extLst->ext['uri'] == '{CCE6A557-97BC-4b89-ADB6-D9C93CAAB3DF}')) { + // Create dataValidations node if does not exists, maybe is better inside the foreach ? + if (!$xmlSheet->dataValidations) { + $xmlSheet->addChild('dataValidations'); + } + + foreach ($xmlSheet->extLst->ext->children('x14', true)->dataValidations->dataValidation as $item) { + $node = $xmlSheet->dataValidations->addChild('dataValidation'); + foreach ($item->attributes() ?? [] as $attr) { + $node->addAttribute($attr->getName(), $attr); + } + $node->addAttribute('sqref', $item->children('xm', true)->sqref); + $node->addChild('formula1', $item->formula1->children('xm', true)->f); + } + } + + if ($xmlSheet && $xmlSheet->dataValidations && !$this->readDataOnly) { + (new DataValidations($docSheet, $xmlSheet))->load(); + } + + // unparsed sheet AlternateContent + if ($xmlSheet && !$this->readDataOnly) { + $mc = $xmlSheet->children(Namespaces::COMPATIBILITY); + if ($mc->AlternateContent) { + foreach ($mc->AlternateContent as $alternateContent) { + $alternateContent = self::testSimpleXml($alternateContent); + $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['AlternateContents'][] = $alternateContent->asXML(); + } + } + } + + // Add hyperlinks + if (!$this->readDataOnly) { + $hyperlinkReader = new Hyperlinks($docSheet); + // Locate hyperlink relations + $relationsFileName = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels'; + if ($zip->locateName($relationsFileName)) { + $relsWorksheet = $this->loadZip($relationsFileName, Namespaces::RELATIONSHIPS); + $hyperlinkReader->readHyperlinks($relsWorksheet); + } + + // Loop through hyperlinks + if ($xmlSheetNS && $xmlSheetNS->children($mainNS)->hyperlinks) { + $hyperlinkReader->setHyperlinks($xmlSheetNS->children($mainNS)->hyperlinks); + } + } + + // Add comments + $comments = []; + $vmlComments = []; + if (!$this->readDataOnly) { + // Locate comment relations + $commentRelations = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels'; + if ($zip->locateName($commentRelations)) { + $relsWorksheet = $this->loadZip($commentRelations, Namespaces::RELATIONSHIPS); + foreach ($relsWorksheet->Relationship as $elex) { + $ele = self::getAttributes($elex); + if ($ele['Type'] == Namespaces::COMMENTS) { + $comments[(string) $ele['Id']] = (string) $ele['Target']; + } + if ($ele['Type'] == Namespaces::VML) { + $vmlComments[(string) $ele['Id']] = (string) $ele['Target']; + } + } + } + + // Loop through comments + foreach ($comments as $relName => $relPath) { + // Load comments file + $relPath = File::realpath(dirname("$dir/$fileWorksheet") . '/' . $relPath); + // okay to ignore namespace - using xpath + $commentsFile = $this->loadZip($relPath, ''); + + // Utility variables + $authors = []; + $commentsFile->registerXpathNamespace('com', $mainNS); + $authorPath = self::xpathNoFalse($commentsFile, 'com:authors/com:author'); + foreach ($authorPath as $author) { + $authors[] = (string) $author; + } + + // Loop through contents + $contentPath = self::xpathNoFalse($commentsFile, 'com:commentList/com:comment'); + foreach ($contentPath as $comment) { + $commentx = $comment->attributes(); + $commentModel = $docSheet->getComment((string) $commentx['ref']); + if (isset($commentx['authorId'])) { + $commentModel->setAuthor($authors[(int) $commentx['authorId']]); + } + $commentModel->setText($this->parseRichText($comment->children($mainNS)->text)); + } + } + + // later we will remove from it real vmlComments + $unparsedVmlDrawings = $vmlComments; + + // Loop through VML comments + foreach ($vmlComments as $relName => $relPath) { + // Load VML comments file + $relPath = File::realpath(dirname("$dir/$fileWorksheet") . '/' . $relPath); + + try { + // no namespace okay - processed with Xpath + $vmlCommentsFile = $this->loadZip($relPath, ''); + $vmlCommentsFile->registerXPathNamespace('v', Namespaces::URN_VML); + } catch (Throwable $ex) { + //Ignore unparsable vmlDrawings. Later they will be moved from $unparsedVmlDrawings to $unparsedLoadedData + continue; + } + + $shapes = self::xpathNoFalse($vmlCommentsFile, '//v:shape'); + foreach ($shapes as $shape) { + $shape->registerXPathNamespace('v', Namespaces::URN_VML); + + if (isset($shape['style'])) { + $style = (string) $shape['style']; + $fillColor = strtoupper(substr((string) $shape['fillcolor'], 1)); + $column = null; + $row = null; + + $clientData = $shape->xpath('.//x:ClientData'); + if (is_array($clientData) && !empty($clientData)) { + $clientData = $clientData[0]; + + if (isset($clientData['ObjectType']) && (string) $clientData['ObjectType'] == 'Note') { + $temp = $clientData->xpath('.//x:Row'); + if (is_array($temp)) { + $row = $temp[0]; + } + + $temp = $clientData->xpath('.//x:Column'); + if (is_array($temp)) { + $column = $temp[0]; + } + } + } + + if (($column !== null) && ($row !== null)) { + // Set comment properties + $comment = $docSheet->getCommentByColumnAndRow($column + 1, $row + 1); + $comment->getFillColor()->setRGB($fillColor); + + // Parse style + $styleArray = explode(';', str_replace(' ', '', $style)); + foreach ($styleArray as $stylePair) { + $stylePair = explode(':', $stylePair); + + if ($stylePair[0] == 'margin-left') { + $comment->setMarginLeft($stylePair[1]); + } + if ($stylePair[0] == 'margin-top') { + $comment->setMarginTop($stylePair[1]); + } + if ($stylePair[0] == 'width') { + $comment->setWidth($stylePair[1]); + } + if ($stylePair[0] == 'height') { + $comment->setHeight($stylePair[1]); + } + if ($stylePair[0] == 'visibility') { + $comment->setVisible($stylePair[1] == 'visible'); + } + } + + unset($unparsedVmlDrawings[$relName]); + } + } + } + } + + // unparsed vmlDrawing + if ($unparsedVmlDrawings) { + foreach ($unparsedVmlDrawings as $rId => $relPath) { + $rId = substr($rId, 3); // rIdXXX + $unparsedVmlDrawing = &$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['vmlDrawings']; + $unparsedVmlDrawing[$rId] = []; + $unparsedVmlDrawing[$rId]['filePath'] = self::dirAdd("$dir/$fileWorksheet", $relPath); + $unparsedVmlDrawing[$rId]['relFilePath'] = $relPath; + $unparsedVmlDrawing[$rId]['content'] = $this->securityScanner->scan($this->getFromZipArchive($zip, $unparsedVmlDrawing[$rId]['filePath'])); + unset($unparsedVmlDrawing); + } + } + + // Header/footer images + if ($xmlSheet && $xmlSheet->legacyDrawingHF && !$this->readDataOnly) { + if ($zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) { + $relsWorksheet = $this->loadZipNoNamespace(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels', Namespaces::RELATIONSHIPS); + $vmlRelationship = ''; + + foreach ($relsWorksheet->Relationship as $ele) { + if ($ele['Type'] == Namespaces::VML) { + $vmlRelationship = self::dirAdd("$dir/$fileWorksheet", $ele['Target']); + } + } + + if ($vmlRelationship != '') { + // Fetch linked images + $relsVML = $this->loadZipNoNamespace(dirname($vmlRelationship) . '/_rels/' . basename($vmlRelationship) . '.rels', Namespaces::RELATIONSHIPS); + $drawings = []; + if (isset($relsVML->Relationship)) { + foreach ($relsVML->Relationship as $ele) { + if ($ele['Type'] == Namespaces::IMAGE) { + $drawings[(string) $ele['Id']] = self::dirAdd($vmlRelationship, $ele['Target']); + } + } + } + // Fetch VML document + $vmlDrawing = $this->loadZipNoNamespace($vmlRelationship, ''); + $vmlDrawing->registerXPathNamespace('v', Namespaces::URN_VML); + + $hfImages = []; + + $shapes = self::xpathNoFalse($vmlDrawing, '//v:shape'); + foreach ($shapes as $idx => $shape) { + $shape->registerXPathNamespace('v', Namespaces::URN_VML); + $imageData = $shape->xpath('//v:imagedata'); + + if (empty($imageData)) { + continue; + } + + $imageData = $imageData[$idx]; + + $imageData = self::getAttributes($imageData, Namespaces::URN_MSOFFICE); + $style = self::toCSSArray((string) $shape['style']); + + $hfImages[(string) $shape['id']] = new HeaderFooterDrawing(); + if (isset($imageData['title'])) { + $hfImages[(string) $shape['id']]->setName((string) $imageData['title']); + } + + $hfImages[(string) $shape['id']]->setPath('zip://' . File::realpath($filename) . '#' . $drawings[(string) $imageData['relid']], false); + $hfImages[(string) $shape['id']]->setResizeProportional(false); + $hfImages[(string) $shape['id']]->setWidth($style['width']); + $hfImages[(string) $shape['id']]->setHeight($style['height']); + if (isset($style['margin-left'])) { + $hfImages[(string) $shape['id']]->setOffsetX($style['margin-left']); + } + $hfImages[(string) $shape['id']]->setOffsetY($style['margin-top']); + $hfImages[(string) $shape['id']]->setResizeProportional(true); + } + + $docSheet->getHeaderFooter()->setImages($hfImages); + } + } + } + } + + // TODO: Autoshapes from twoCellAnchors! + $drawingFilename = dirname("$dir/$fileWorksheet") + . '/_rels/' + . basename($fileWorksheet) + . '.rels'; + if ($zip->locateName($drawingFilename)) { + $relsWorksheet = $this->loadZipNoNamespace($drawingFilename, Namespaces::RELATIONSHIPS); + $drawings = []; + foreach ($relsWorksheet->Relationship as $ele) { + if ((string) $ele['Type'] === "$xmlNamespaceBase/drawing") { + $drawings[(string) $ele['Id']] = self::dirAdd("$dir/$fileWorksheet", $ele['Target']); + } + } + if ($xmlSheet->drawing && !$this->readDataOnly) { + $unparsedDrawings = []; + $fileDrawing = null; + foreach ($xmlSheet->drawing as $drawing) { + $drawingRelId = (string) self::getArrayItem(self::getAttributes($drawing, $xmlNamespaceBase), 'id'); + $fileDrawing = $drawings[$drawingRelId]; + $drawingFilename = dirname($fileDrawing) . '/_rels/' . basename($fileDrawing) . '.rels'; + $relsDrawing = $this->loadZipNoNamespace($drawingFilename, $xmlNamespaceBase); + $images = []; + $hyperlinks = []; + if ($relsDrawing && $relsDrawing->Relationship) { + foreach ($relsDrawing->Relationship as $ele) { + $eleType = (string) $ele['Type']; + if ($eleType === Namespaces::HYPERLINK) { + $hyperlinks[(string) $ele['Id']] = (string) $ele['Target']; + } + if ($eleType === "$xmlNamespaceBase/image") { + $images[(string) $ele['Id']] = self::dirAdd($fileDrawing, $ele['Target']); + } elseif ($eleType === "$xmlNamespaceBase/chart") { + if ($this->includeCharts) { + $charts[self::dirAdd($fileDrawing, $ele['Target'])] = [ + 'id' => (string) $ele['Id'], + 'sheet' => $docSheet->getTitle(), + ]; + } + } + } + } + $xmlDrawing = $this->loadZipNoNamespace($fileDrawing, ''); + $xmlDrawingChildren = $xmlDrawing->children(Namespaces::SPREADSHEET_DRAWING); + + if ($xmlDrawingChildren->oneCellAnchor) { + foreach ($xmlDrawingChildren->oneCellAnchor as $oneCellAnchor) { + if ($oneCellAnchor->pic->blipFill) { + /** @var SimpleXMLElement $blip */ + $blip = $oneCellAnchor->pic->blipFill->children(Namespaces::DRAWINGML)->blip; + /** @var SimpleXMLElement $xfrm */ + $xfrm = $oneCellAnchor->pic->spPr->children(Namespaces::DRAWINGML)->xfrm; + /** @var SimpleXMLElement $outerShdw */ + $outerShdw = $oneCellAnchor->pic->spPr->children(Namespaces::DRAWINGML)->effectLst->outerShdw; + /** @var SimpleXMLElement $hlinkClick */ + $hlinkClick = $oneCellAnchor->pic->nvPicPr->cNvPr->children(Namespaces::DRAWINGML)->hlinkClick; + + $objDrawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing(); + $objDrawing->setName((string) self::getArrayItem(self::getAttributes($oneCellAnchor->pic->nvPicPr->cNvPr), 'name')); + $objDrawing->setDescription((string) self::getArrayItem(self::getAttributes($oneCellAnchor->pic->nvPicPr->cNvPr), 'descr')); + $embedImageKey = (string) self::getArrayItem( + self::getAttributes($blip, $xmlNamespaceBase), + 'embed' + ); + if (isset($images[$embedImageKey])) { + $objDrawing->setPath( + 'zip://' . File::realpath($filename) . '#' . + $images[$embedImageKey], + false + ); + } else { + $linkImageKey = (string) self::getArrayItem( + $blip->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), + 'link' + ); + if (isset($images[$linkImageKey])) { + $url = str_replace('xl/drawings/', '', $images[$linkImageKey]); + $objDrawing->setPath($url); + } + } + $objDrawing->setCoordinates(Coordinate::stringFromColumnIndex(((int) $oneCellAnchor->from->col) + 1) . ($oneCellAnchor->from->row + 1)); + + $objDrawing->setOffsetX((int) Drawing::EMUToPixels($oneCellAnchor->from->colOff)); + $objDrawing->setOffsetY(Drawing::EMUToPixels($oneCellAnchor->from->rowOff)); + $objDrawing->setResizeProportional(false); + $objDrawing->setWidth(Drawing::EMUToPixels(self::getArrayItem((int) self::getAttributes($oneCellAnchor->ext), 'cx'))); + $objDrawing->setHeight(Drawing::EMUToPixels(self::getArrayItem((int) self::getAttributes($oneCellAnchor->ext), 'cy'))); + if ($xfrm) { + $objDrawing->setRotation((int) Drawing::angleToDegrees(self::getArrayItem(self::getAttributes($xfrm), 'rot'))); + } + if ($outerShdw) { + $shadow = $objDrawing->getShadow(); + $shadow->setVisible(true); + $shadow->setBlurRadius(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($outerShdw), 'blurRad'))); + $shadow->setDistance(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($outerShdw), 'dist'))); + $shadow->setDirection(Drawing::angleToDegrees(self::getArrayItem(self::getAttributes($outerShdw), 'dir'))); + $shadow->setAlignment((string) self::getArrayItem(self::getAttributes($outerShdw), 'algn')); + $clr = $outerShdw->srgbClr ?? $outerShdw->prstClr; + $shadow->getColor()->setRGB(self::getArrayItem(self::getAttributes($clr), 'val')); + $shadow->setAlpha(self::getArrayItem(self::getAttributes($clr->alpha), 'val') / 1000); + } + + $this->readHyperLinkDrawing($objDrawing, $oneCellAnchor, $hyperlinks); + + $objDrawing->setWorksheet($docSheet); + } elseif ($this->includeCharts && $oneCellAnchor->graphicFrame) { + // Exported XLSX from Google Sheets positions charts with a oneCellAnchor + $coordinates = Coordinate::stringFromColumnIndex(((int) $oneCellAnchor->from->col) + 1) . ($oneCellAnchor->from->row + 1); + $offsetX = Drawing::EMUToPixels($oneCellAnchor->from->colOff); + $offsetY = Drawing::EMUToPixels($oneCellAnchor->from->rowOff); + $width = Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($oneCellAnchor->ext), 'cx')); + $height = Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($oneCellAnchor->ext), 'cy')); + + $graphic = $oneCellAnchor->graphicFrame->children(Namespaces::DRAWINGML)->graphic; + /** @var SimpleXMLElement $chartRef */ + $chartRef = $graphic->graphicData->children(Namespaces::CHART)->chart; + $thisChart = (string) self::getAttributes($chartRef, $xmlNamespaceBase); + + $chartDetails[$docSheet->getTitle() . '!' . $thisChart] = [ + 'fromCoordinate' => $coordinates, + 'fromOffsetX' => $offsetX, + 'fromOffsetY' => $offsetY, + 'width' => $width, + 'height' => $height, + 'worksheetTitle' => $docSheet->getTitle(), + ]; + } + } + } + if ($xmlDrawingChildren->twoCellAnchor) { + foreach ($xmlDrawingChildren->twoCellAnchor as $twoCellAnchor) { + if ($twoCellAnchor->pic->blipFill) { + $blip = $twoCellAnchor->pic->blipFill->children(Namespaces::DRAWINGML)->blip; + $xfrm = $twoCellAnchor->pic->spPr->children(Namespaces::DRAWINGML)->xfrm; + $outerShdw = $twoCellAnchor->pic->spPr->children(Namespaces::DRAWINGML)->effectLst->outerShdw; + $hlinkClick = $twoCellAnchor->pic->nvPicPr->cNvPr->children(Namespaces::DRAWINGML)->hlinkClick; + $objDrawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing(); + $objDrawing->setName((string) self::getArrayItem(self::getAttributes($twoCellAnchor->pic->nvPicPr->cNvPr), 'name')); + $objDrawing->setDescription((string) self::getArrayItem(self::getAttributes($twoCellAnchor->pic->nvPicPr->cNvPr), 'descr')); + $embedImageKey = (string) self::getArrayItem( + self::getAttributes($blip, $xmlNamespaceBase), + 'embed' + ); + if (isset($images[$embedImageKey])) { + $objDrawing->setPath( + 'zip://' . File::realpath($filename) . '#' . + $images[$embedImageKey], + false + ); + } else { + $linkImageKey = (string) self::getArrayItem( + $blip->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), + 'link' + ); + if (isset($images[$linkImageKey])) { + $url = str_replace('xl/drawings/', '', $images[$linkImageKey]); + $objDrawing->setPath($url); + } + } + $objDrawing->setCoordinates(Coordinate::stringFromColumnIndex(((int) $twoCellAnchor->from->col) + 1) . ($twoCellAnchor->from->row + 1)); + + $objDrawing->setOffsetX(Drawing::EMUToPixels($twoCellAnchor->from->colOff)); + $objDrawing->setOffsetY(Drawing::EMUToPixels($twoCellAnchor->from->rowOff)); + $objDrawing->setResizeProportional(false); + + if ($xfrm) { + $objDrawing->setWidth(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($xfrm->ext), 'cx'))); + $objDrawing->setHeight(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($xfrm->ext), 'cy'))); + $objDrawing->setRotation(Drawing::angleToDegrees(self::getArrayItem(self::getAttributes($xfrm), 'rot'))); + } + if ($outerShdw) { + $shadow = $objDrawing->getShadow(); + $shadow->setVisible(true); + $shadow->setBlurRadius(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($outerShdw), 'blurRad'))); + $shadow->setDistance(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($outerShdw), 'dist'))); + $shadow->setDirection(Drawing::angleToDegrees(self::getArrayItem(self::getAttributes($outerShdw), 'dir'))); + $shadow->setAlignment((string) self::getArrayItem(self::getAttributes($outerShdw), 'algn')); + $clr = $outerShdw->srgbClr ?? $outerShdw->prstClr; + $shadow->getColor()->setRGB(self::getArrayItem(self::getAttributes($clr), 'val')); + $shadow->setAlpha(self::getArrayItem(self::getAttributes($clr->alpha), 'val') / 1000); + } + + $this->readHyperLinkDrawing($objDrawing, $twoCellAnchor, $hyperlinks); + + $objDrawing->setWorksheet($docSheet); + } elseif (($this->includeCharts) && ($twoCellAnchor->graphicFrame)) { + $fromCoordinate = Coordinate::stringFromColumnIndex(((int) $twoCellAnchor->from->col) + 1) . ($twoCellAnchor->from->row + 1); + $fromOffsetX = Drawing::EMUToPixels($twoCellAnchor->from->colOff); + $fromOffsetY = Drawing::EMUToPixels($twoCellAnchor->from->rowOff); + $toCoordinate = Coordinate::stringFromColumnIndex(((int) $twoCellAnchor->to->col) + 1) . ($twoCellAnchor->to->row + 1); + $toOffsetX = Drawing::EMUToPixels($twoCellAnchor->to->colOff); + $toOffsetY = Drawing::EMUToPixels($twoCellAnchor->to->rowOff); + $graphic = $twoCellAnchor->graphicFrame->children(Namespaces::DRAWINGML)->graphic; + /** @var SimpleXMLElement $chartRef */ + $chartRef = $graphic->graphicData->children(Namespaces::CHART)->chart; + $thisChart = (string) self::getAttributes($chartRef, $xmlNamespaceBase); + + $chartDetails[$docSheet->getTitle() . '!' . $thisChart] = [ + 'fromCoordinate' => $fromCoordinate, + 'fromOffsetX' => $fromOffsetX, + 'fromOffsetY' => $fromOffsetY, + 'toCoordinate' => $toCoordinate, + 'toOffsetX' => $toOffsetX, + 'toOffsetY' => $toOffsetY, + 'worksheetTitle' => $docSheet->getTitle(), + ]; + } + } + } + if (empty($relsDrawing) && $xmlDrawing->count() == 0) { + // Save Drawing without rels and children as unparsed + $unparsedDrawings[$drawingRelId] = $xmlDrawing->asXML(); + } + } + + // store original rId of drawing files + $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingOriginalIds'] = []; + foreach ($relsWorksheet->Relationship as $ele) { + if ((string) $ele['Type'] === "$xmlNamespaceBase/drawing") { + $drawingRelId = (string) $ele['Id']; + $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingOriginalIds'][(string) $ele['Target']] = $drawingRelId; + if (isset($unparsedDrawings[$drawingRelId])) { + $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['Drawings'][$drawingRelId] = $unparsedDrawings[$drawingRelId]; + } + } + } + + // unparsed drawing AlternateContent + $xmlAltDrawing = $this->loadZip($fileDrawing, Namespaces::COMPATIBILITY); + + if ($xmlAltDrawing->AlternateContent) { + foreach ($xmlAltDrawing->AlternateContent as $alternateContent) { + $alternateContent = self::testSimpleXml($alternateContent); + $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingAlternateContents'][] = $alternateContent->asXML(); + } + } + } + } + + $this->readFormControlProperties($excel, $dir, $fileWorksheet, $docSheet, $unparsedLoadedData); + $this->readPrinterSettings($excel, $dir, $fileWorksheet, $docSheet, $unparsedLoadedData); + + // Loop through definedNames + if ($xmlWorkbook->definedNames) { + foreach ($xmlWorkbook->definedNames->definedName as $definedName) { + // Extract range + $extractedRange = (string) $definedName; + if (($spos = strpos($extractedRange, '!')) !== false) { + $extractedRange = substr($extractedRange, 0, $spos) . str_replace('$', '', substr($extractedRange, $spos)); + } else { + $extractedRange = str_replace('$', '', $extractedRange); + } + + // Valid range? + if ($extractedRange == '') { + continue; + } + + // Some definedNames are only applicable if we are on the same sheet... + if ((string) $definedName['localSheetId'] != '' && (string) $definedName['localSheetId'] == $oldSheetId) { + // Switch on type + switch ((string) $definedName['name']) { + case '_xlnm._FilterDatabase': + if ((string) $definedName['hidden'] !== '1') { + $extractedRange = explode(',', $extractedRange); + foreach ($extractedRange as $range) { + $autoFilterRange = $range; + if (strpos($autoFilterRange, ':') !== false) { + $docSheet->getAutoFilter()->setRange($autoFilterRange); + } + } + } + + break; + case '_xlnm.Print_Titles': + // Split $extractedRange + $extractedRange = explode(',', $extractedRange); + + // Set print titles + foreach ($extractedRange as $range) { + $matches = []; + $range = str_replace('$', '', $range); + + // check for repeating columns, e g. 'A:A' or 'A:D' + if (preg_match('/!?([A-Z]+)\:([A-Z]+)$/', $range, $matches)) { + $docSheet->getPageSetup()->setColumnsToRepeatAtLeft([$matches[1], $matches[2]]); + } elseif (preg_match('/!?(\d+)\:(\d+)$/', $range, $matches)) { + // check for repeating rows, e.g. '1:1' or '1:5' + $docSheet->getPageSetup()->setRowsToRepeatAtTop([$matches[1], $matches[2]]); + } + } + + break; + case '_xlnm.Print_Area': + $rangeSets = preg_split("/('?(?:.*?)'?(?:![A-Z0-9]+:[A-Z0-9]+)),?/", $extractedRange, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); + $newRangeSets = []; + foreach ($rangeSets as $rangeSet) { + [$sheetName, $rangeSet] = Worksheet::extractSheetTitle($rangeSet, true); + if (strpos($rangeSet, ':') === false) { + $rangeSet = $rangeSet . ':' . $rangeSet; + } + $newRangeSets[] = str_replace('$', '', $rangeSet); + } + $docSheet->getPageSetup()->setPrintArea(implode(',', $newRangeSets)); + + break; + default: + break; + } + } + } + } + + // Next sheet id + ++$sheetId; + } + + // Loop through definedNames + if ($xmlWorkbook->definedNames) { + foreach ($xmlWorkbook->definedNames->definedName as $definedName) { + // Extract range + $extractedRange = (string) $definedName; + + // Valid range? + if ($extractedRange == '') { + continue; + } + + // Some definedNames are only applicable if we are on the same sheet... + if ((string) $definedName['localSheetId'] != '') { + // Local defined name + // Switch on type + switch ((string) $definedName['name']) { + case '_xlnm._FilterDatabase': + case '_xlnm.Print_Titles': + case '_xlnm.Print_Area': + break; + default: + if ($mapSheetId[(int) $definedName['localSheetId']] !== null) { + $range = Worksheet::extractSheetTitle((string) $definedName, true); + $scope = $excel->getSheet($mapSheetId[(int) $definedName['localSheetId']]); + if (strpos((string) $definedName, '!') !== false) { + $range[0] = str_replace("''", "'", $range[0]); + $range[0] = str_replace("'", '', $range[0]); + if ($worksheet = $excel->getSheetByName($range[0])) { + $excel->addDefinedName(DefinedName::createInstance((string) $definedName['name'], $worksheet, $extractedRange, true, $scope)); + } else { + $excel->addDefinedName(DefinedName::createInstance((string) $definedName['name'], $scope, $extractedRange, true, $scope)); + } + } else { + $excel->addDefinedName(DefinedName::createInstance((string) $definedName['name'], $scope, $extractedRange, true)); + } + } + + break; + } + } elseif (!isset($definedName['localSheetId'])) { + $definedRange = (string) $definedName; + // "Global" definedNames + $locatedSheet = null; + if (strpos((string) $definedName, '!') !== false) { + // Modify range, and extract the first worksheet reference + // Need to split on a comma or a space if not in quotes, and extract the first part. + $definedNameValueParts = preg_split("/[ ,](?=([^']*'[^']*')*[^']*$)/miuU", $definedRange); + // Extract sheet name + [$extractedSheetName] = Worksheet::extractSheetTitle((string) $definedNameValueParts[0], true); + $extractedSheetName = trim($extractedSheetName, "'"); + + // Locate sheet + $locatedSheet = $excel->getSheetByName($extractedSheetName); + } + + if ($locatedSheet === null && !DefinedName::testIfFormula($definedRange)) { + $definedRange = '#REF!'; + } + $excel->addDefinedName(DefinedName::createInstance((string) $definedName['name'], $locatedSheet, $definedRange, false)); + } + } + } + } + + $workbookView = $xmlWorkbook->children($mainNS)->bookViews->workbookView; + if ((!$this->readDataOnly || !empty($this->loadSheetsOnly)) && !empty($workbookView)) { + $workbookViewAttributes = self::testSimpleXml(self::getAttributes($workbookView)); + // active sheet index + $activeTab = (int) $workbookViewAttributes->activeTab; // refers to old sheet index + + // keep active sheet index if sheet is still loaded, else first sheet is set as the active + if (isset($mapSheetId[$activeTab]) && $mapSheetId[$activeTab] !== null) { + $excel->setActiveSheetIndex($mapSheetId[$activeTab]); + } else { + if ($excel->getSheetCount() == 0) { + $excel->createSheet(); + } + $excel->setActiveSheetIndex(0); + } + + if (isset($workbookViewAttributes->showHorizontalScroll)) { + $showHorizontalScroll = (string) $workbookViewAttributes->showHorizontalScroll; + $excel->setShowHorizontalScroll($this->castXsdBooleanToBool($showHorizontalScroll)); + } + + if (isset($workbookViewAttributes->showVerticalScroll)) { + $showVerticalScroll = (string) $workbookViewAttributes->showVerticalScroll; + $excel->setShowVerticalScroll($this->castXsdBooleanToBool($showVerticalScroll)); + } + + if (isset($workbookViewAttributes->showSheetTabs)) { + $showSheetTabs = (string) $workbookViewAttributes->showSheetTabs; + $excel->setShowSheetTabs($this->castXsdBooleanToBool($showSheetTabs)); + } + + if (isset($workbookViewAttributes->minimized)) { + $minimized = (string) $workbookViewAttributes->minimized; + $excel->setMinimized($this->castXsdBooleanToBool($minimized)); + } + + if (isset($workbookViewAttributes->autoFilterDateGrouping)) { + $autoFilterDateGrouping = (string) $workbookViewAttributes->autoFilterDateGrouping; + $excel->setAutoFilterDateGrouping($this->castXsdBooleanToBool($autoFilterDateGrouping)); + } + + if (isset($workbookViewAttributes->firstSheet)) { + $firstSheet = (string) $workbookViewAttributes->firstSheet; + $excel->setFirstSheetIndex((int) $firstSheet); + } + + if (isset($workbookViewAttributes->visibility)) { + $visibility = (string) $workbookViewAttributes->visibility; + $excel->setVisibility($visibility); + } + + if (isset($workbookViewAttributes->tabRatio)) { + $tabRatio = (string) $workbookViewAttributes->tabRatio; + $excel->setTabRatio((int) $tabRatio); + } + } + + break; + } + } + + if (!$this->readDataOnly) { + $contentTypes = $this->loadZip('[Content_Types].xml'); + + // Default content types + foreach ($contentTypes->Default as $contentType) { + switch ($contentType['ContentType']) { + case 'application/vnd.openxmlformats-officedocument.spreadsheetml.printerSettings': + $unparsedLoadedData['default_content_types'][(string) $contentType['Extension']] = (string) $contentType['ContentType']; + + break; + } + } + + // Override content types + foreach ($contentTypes->Override as $contentType) { + switch ($contentType['ContentType']) { + case 'application/vnd.openxmlformats-officedocument.drawingml.chart+xml': + if ($this->includeCharts) { + $chartEntryRef = ltrim((string) $contentType['PartName'], '/'); + $chartElements = $this->loadZip($chartEntryRef); + $objChart = Chart::readChart($chartElements, basename($chartEntryRef, '.xml')); + + if (isset($charts[$chartEntryRef])) { + $chartPositionRef = $charts[$chartEntryRef]['sheet'] . '!' . $charts[$chartEntryRef]['id']; + if (isset($chartDetails[$chartPositionRef])) { + $excel->getSheetByName($charts[$chartEntryRef]['sheet'])->addChart($objChart); + $objChart->setWorksheet($excel->getSheetByName($charts[$chartEntryRef]['sheet'])); + $objChart->setTopLeftPosition($chartDetails[$chartPositionRef]['fromCoordinate'], $chartDetails[$chartPositionRef]['fromOffsetX'], $chartDetails[$chartPositionRef]['fromOffsetY']); + if (array_key_exists('toCoordinate', $chartDetails[$chartPositionRef])) { + // For oneCellAnchor positioned charts, toCoordinate is not in the data. Does it need to be calculated? + $objChart->setBottomRightPosition($chartDetails[$chartPositionRef]['toCoordinate'], $chartDetails[$chartPositionRef]['toOffsetX'], $chartDetails[$chartPositionRef]['toOffsetY']); + } + } + } + } + + break; + + // unparsed + case 'application/vnd.ms-excel.controlproperties+xml': + $unparsedLoadedData['override_content_types'][(string) $contentType['PartName']] = (string) $contentType['ContentType']; + + break; + } + } + } + + $excel->setUnparsedLoadedData($unparsedLoadedData); + + $zip->close(); + + return $excel; + } + + /** + * @param SimpleXMLElement|stdClass $style + */ + private static function readStyle(Style $docStyle, $style): void + { + $docStyle->getNumberFormat()->setFormatCode($style->numFmt); + + // font + if (isset($style->font)) { + Styles::readFontStyle($docStyle->getFont(), $style->font); + } + + // fill + if (isset($style->fill)) { + Styles::readFillStyle($docStyle->getFill(), $style->fill); + } + + // border + if (isset($style->border)) { + Styles::readBorderStyle($docStyle->getBorders(), $style->border); + } + + // alignment + if (isset($style->alignment)) { + Styles::readAlignmentStyle($docStyle->getAlignment(), $style->alignment); + } + + // protection + if (isset($style->protection)) { + Styles::readProtectionLocked($docStyle, $style); + Styles::readProtectionHidden($docStyle, $style); + } + + // top-level style settings + if (isset($style->quotePrefix)) { + $docStyle->setQuotePrefix((bool) $style->quotePrefix); + } + } + + /** + * @return RichText + */ + private function parseRichText(?SimpleXMLElement $is) + { + $value = new RichText(); + + if (isset($is->t)) { + $value->createText(StringHelper::controlCharacterOOXML2PHP((string) $is->t)); + } else { + if (is_object($is->r)) { + + /** @var SimpleXMLElement $run */ + foreach ($is->r as $run) { + if (!isset($run->rPr)) { + $value->createText(StringHelper::controlCharacterOOXML2PHP((string) $run->t)); + } else { + $objText = $value->createTextRun(StringHelper::controlCharacterOOXML2PHP((string) $run->t)); + + $attr = $run->rPr->rFont->attributes(); + if (isset($attr['val'])) { + $objText->getFont()->setName((string) $attr['val']); + } + $attr = $run->rPr->sz->attributes(); + if (isset($attr['val'])) { + $objText->getFont()->setSize((float) $attr['val']); + } + if (isset($run->rPr->color)) { + $objText->getFont()->setColor(new Color(Styles::readColor($run->rPr->color))); + } + if (isset($run->rPr->b)) { + $attr = $run->rPr->b->attributes(); + if ( + (isset($attr['val']) && self::boolean((string) $attr['val'])) || + (!isset($attr['val'])) + ) { + $objText->getFont()->setBold(true); + } + } + if (isset($run->rPr->i)) { + $attr = $run->rPr->i->attributes(); + if ( + (isset($attr['val']) && self::boolean((string) $attr['val'])) || + (!isset($attr['val'])) + ) { + $objText->getFont()->setItalic(true); + } + } + if (isset($run->rPr->vertAlign)) { + $attr = $run->rPr->vertAlign->attributes(); + if (isset($attr['val'])) { + $vertAlign = strtolower((string) $attr['val']); + if ($vertAlign == 'superscript') { + $objText->getFont()->setSuperscript(true); + } + if ($vertAlign == 'subscript') { + $objText->getFont()->setSubscript(true); + } + } + } + if (isset($run->rPr->u)) { + $attr = $run->rPr->u->attributes(); + if (!isset($attr['val'])) { + $objText->getFont()->setUnderline(\PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_SINGLE); + } else { + $objText->getFont()->setUnderline((string) $attr['val']); + } + } + if (isset($run->rPr->strike)) { + $attr = $run->rPr->strike->attributes(); + if ( + (isset($attr['val']) && self::boolean((string) $attr['val'])) || + (!isset($attr['val'])) + ) { + $objText->getFont()->setStrikethrough(true); + } + } + } + } + } + } + + return $value; + } + + private function readRibbon(Spreadsheet $excel, string $customUITarget, ZipArchive $zip): void + { + $baseDir = dirname($customUITarget); + $nameCustomUI = basename($customUITarget); + // get the xml file (ribbon) + $localRibbon = $this->getFromZipArchive($zip, $customUITarget); + $customUIImagesNames = []; + $customUIImagesBinaries = []; + // something like customUI/_rels/customUI.xml.rels + $pathRels = $baseDir . '/_rels/' . $nameCustomUI . '.rels'; + $dataRels = $this->getFromZipArchive($zip, $pathRels); + if ($dataRels) { + // exists and not empty if the ribbon have some pictures (other than internal MSO) + $UIRels = simplexml_load_string( + $this->securityScanner->scan($dataRels), + 'SimpleXMLElement', + Settings::getLibXmlLoaderOptions() + ); + if (false !== $UIRels) { + // we need to save id and target to avoid parsing customUI.xml and "guess" if it's a pseudo callback who load the image + foreach ($UIRels->Relationship as $ele) { + if ((string) $ele['Type'] === Namespaces::SCHEMA_OFFICE_DOCUMENT . '/image') { + // an image ? + $customUIImagesNames[(string) $ele['Id']] = (string) $ele['Target']; + $customUIImagesBinaries[(string) $ele['Target']] = $this->getFromZipArchive($zip, $baseDir . '/' . (string) $ele['Target']); + } + } + } + } + if ($localRibbon) { + $excel->setRibbonXMLData($customUITarget, $localRibbon); + if (count($customUIImagesNames) > 0 && count($customUIImagesBinaries) > 0) { + $excel->setRibbonBinObjects($customUIImagesNames, $customUIImagesBinaries); + } else { + $excel->setRibbonBinObjects(null, null); + } + } else { + $excel->setRibbonXMLData(null, null); + $excel->setRibbonBinObjects(null, null); + } + } + + private static function getArrayItem($array, $key = 0) + { + return $array[$key] ?? null; + } + + private static function dirAdd($base, $add) + { + return preg_replace('~[^/]+/\.\./~', '', dirname($base) . "/$add"); + } + + private static function toCSSArray($style) + { + $style = self::stripWhiteSpaceFromStyleString($style); + + $temp = explode(';', $style); + $style = []; + foreach ($temp as $item) { + $item = explode(':', $item); + + if (strpos($item[1], 'px') !== false) { + $item[1] = str_replace('px', '', $item[1]); + } + if (strpos($item[1], 'pt') !== false) { + $item[1] = str_replace('pt', '', $item[1]); + $item[1] = Font::fontSizeToPixels($item[1]); + } + if (strpos($item[1], 'in') !== false) { + $item[1] = str_replace('in', '', $item[1]); + $item[1] = Font::inchSizeToPixels($item[1]); + } + if (strpos($item[1], 'cm') !== false) { + $item[1] = str_replace('cm', '', $item[1]); + $item[1] = Font::centimeterSizeToPixels($item[1]); + } + + $style[$item[0]] = $item[1]; + } + + return $style; + } + + public static function stripWhiteSpaceFromStyleString($string) + { + return trim(str_replace(["\r", "\n", ' '], '', $string), ';'); + } + + private static function boolean($value) + { + if (is_object($value)) { + $value = (string) $value; + } + if (is_numeric($value)) { + return (bool) $value; + } + + return $value === 'true' || $value === 'TRUE'; + } + + /** + * @param array $hyperlinks + */ + private function readHyperLinkDrawing(\PhpOffice\PhpSpreadsheet\Worksheet\Drawing $objDrawing, SimpleXMLElement $cellAnchor, $hyperlinks): void + { + $hlinkClick = $cellAnchor->pic->nvPicPr->cNvPr->children(Namespaces::DRAWINGML)->hlinkClick; + + if ($hlinkClick->count() === 0) { + return; + } + + $hlinkId = (string) self::getAttributes($hlinkClick, Namespaces::SCHEMA_OFFICE_DOCUMENT)['id']; + $hyperlink = new Hyperlink( + $hyperlinks[$hlinkId], + (string) self::getArrayItem(self::getAttributes($cellAnchor->pic->nvPicPr->cNvPr), 'name') + ); + $objDrawing->setHyperlink($hyperlink); + } + + private function readProtection(Spreadsheet $excel, SimpleXMLElement $xmlWorkbook): void + { + if (!$xmlWorkbook->workbookProtection) { + return; + } + + $excel->getSecurity()->setLockRevision(self::getLockValue($xmlWorkbook->workbookProtection, 'lockRevision')); + $excel->getSecurity()->setLockStructure(self::getLockValue($xmlWorkbook->workbookProtection, 'lockStructure')); + $excel->getSecurity()->setLockWindows(self::getLockValue($xmlWorkbook->workbookProtection, 'lockWindows')); + + if ($xmlWorkbook->workbookProtection['revisionsPassword']) { + $excel->getSecurity()->setRevisionsPassword( + (string) $xmlWorkbook->workbookProtection['revisionsPassword'], + true + ); + } + + if ($xmlWorkbook->workbookProtection['workbookPassword']) { + $excel->getSecurity()->setWorkbookPassword( + (string) $xmlWorkbook->workbookProtection['workbookPassword'], + true + ); + } + } + + private static function getLockValue(SimpleXmlElement $protection, string $key): ?bool + { + $returnValue = null; + $protectKey = $protection[$key]; + if (!empty($protectKey)) { + $protectKey = (string) $protectKey; + $returnValue = $protectKey !== 'false' && (bool) $protectKey; + } + + return $returnValue; + } + + private function readFormControlProperties(Spreadsheet $excel, $dir, $fileWorksheet, $docSheet, array &$unparsedLoadedData): void + { + $zip = $this->zip; + if (!$zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) { + return; + } + + $filename = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels'; + $relsWorksheet = $this->loadZipNoNamespace($filename, Namespaces::RELATIONSHIPS); + $ctrlProps = []; + foreach ($relsWorksheet->Relationship as $ele) { + if ((string) $ele['Type'] === Namespaces::SCHEMA_OFFICE_DOCUMENT . '/ctrlProp') { + $ctrlProps[(string) $ele['Id']] = $ele; + } + } + + $unparsedCtrlProps = &$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['ctrlProps']; + foreach ($ctrlProps as $rId => $ctrlProp) { + $rId = substr($rId, 3); // rIdXXX + $unparsedCtrlProps[$rId] = []; + $unparsedCtrlProps[$rId]['filePath'] = self::dirAdd("$dir/$fileWorksheet", $ctrlProp['Target']); + $unparsedCtrlProps[$rId]['relFilePath'] = (string) $ctrlProp['Target']; + $unparsedCtrlProps[$rId]['content'] = $this->securityScanner->scan($this->getFromZipArchive($zip, $unparsedCtrlProps[$rId]['filePath'])); + } + unset($unparsedCtrlProps); + } + + private function readPrinterSettings(Spreadsheet $excel, $dir, $fileWorksheet, $docSheet, array &$unparsedLoadedData): void + { + $zip = $this->zip; + if (!$zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) { + return; + } + + $filename = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels'; + $relsWorksheet = $this->loadZipNoNamespace($filename, Namespaces::RELATIONSHIPS); + $sheetPrinterSettings = []; + foreach ($relsWorksheet->Relationship as $ele) { + if ((string) $ele['Type'] === Namespaces::SCHEMA_OFFICE_DOCUMENT . '/printerSettings') { + $sheetPrinterSettings[(string) $ele['Id']] = $ele; + } + } + + $unparsedPrinterSettings = &$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['printerSettings']; + foreach ($sheetPrinterSettings as $rId => $printerSettings) { + $rId = substr($rId, 3) . 'ps'; // rIdXXX, add 'ps' suffix to avoid identical resource identifier collision with unparsed vmlDrawing + $unparsedPrinterSettings[$rId] = []; + $unparsedPrinterSettings[$rId]['filePath'] = self::dirAdd("$dir/$fileWorksheet", $printerSettings['Target']); + $unparsedPrinterSettings[$rId]['relFilePath'] = (string) $printerSettings['Target']; + $unparsedPrinterSettings[$rId]['content'] = $this->securityScanner->scan($this->getFromZipArchive($zip, $unparsedPrinterSettings[$rId]['filePath'])); + } + unset($unparsedPrinterSettings); + } + + /** + * Convert an 'xsd:boolean' XML value to a PHP boolean value. + * A valid 'xsd:boolean' XML value can be one of the following + * four values: 'true', 'false', '1', '0'. It is case sensitive. + * + * Note that just doing '(bool) $xsdBoolean' is not safe, + * since '(bool) "false"' returns true. + * + * @see https://www.w3.org/TR/xmlschema11-2/#boolean + * + * @param string $xsdBoolean An XML string value of type 'xsd:boolean' + * + * @return bool Boolean value + */ + private function castXsdBooleanToBool($xsdBoolean) + { + if ($xsdBoolean === 'false') { + return false; + } + + return (bool) $xsdBoolean; + } + + private function getWorkbookBaseName(): array + { + $workbookBasename = ''; + $xmlNamespaceBase = ''; + + // check if it is an OOXML archive + $rels = $this->loadZip(self::INITIAL_FILE); + foreach ($rels->children(Namespaces::RELATIONSHIPS)->Relationship as $rel) { + $rel = self::getAttributes($rel); + $type = (string) $rel['Type']; + switch ($type) { + case Namespaces::OFFICE_DOCUMENT: + case Namespaces::PURL_OFFICE_DOCUMENT: + $basename = basename((string) $rel['Target']); + $xmlNamespaceBase = dirname($type); + if (preg_match('/workbook.*\.xml/', $basename)) { + $workbookBasename = $basename; + } + + break; + } + } + + return [$workbookBasename, $xmlNamespaceBase]; + } + + private function readSheetProtection(Worksheet $docSheet, SimpleXMLElement $xmlSheet): void + { + if ($this->readDataOnly || !$xmlSheet->sheetProtection) { + return; + } + + $algorithmName = (string) $xmlSheet->sheetProtection['algorithmName']; + $protection = $docSheet->getProtection(); + $protection->setAlgorithm($algorithmName); + + if ($algorithmName) { + $protection->setPassword((string) $xmlSheet->sheetProtection['hashValue'], true); + $protection->setSalt((string) $xmlSheet->sheetProtection['saltValue']); + $protection->setSpinCount((int) $xmlSheet->sheetProtection['spinCount']); + } else { + $protection->setPassword((string) $xmlSheet->sheetProtection['password'], true); + } + + if ($xmlSheet->protectedRanges->protectedRange) { + foreach ($xmlSheet->protectedRanges->protectedRange as $protectedRange) { + $docSheet->protectCells((string) $protectedRange['sqref'], (string) $protectedRange['password'], true); + } + } + } + + private function readAutoFilterTables( + SimpleXMLElement $xmlSheet, + Worksheet $docSheet, + string $dir, + string $fileWorksheet, + ZipArchive $zip + ): void { + if ($xmlSheet && $xmlSheet->autoFilter) { + // In older files, autofilter structure is defined in the worksheet file + (new AutoFilter($docSheet, $xmlSheet))->load(); + } elseif ($xmlSheet && $xmlSheet->tableParts && $xmlSheet->tableParts['count'] > 0) { + // But for Office365, MS decided to make it all just a bit more complicated + $this->readAutoFilterTablesInTablesFile($xmlSheet, $dir, $fileWorksheet, $zip, $docSheet); + } + } + + private function readAutoFilterTablesInTablesFile( + SimpleXMLElement $xmlSheet, + string $dir, + string $fileWorksheet, + ZipArchive $zip, + Worksheet $docSheet + ): void { + foreach ($xmlSheet->tableParts->tablePart as $tablePart) { + $relation = self::getAttributes($tablePart, Namespaces::SCHEMA_OFFICE_DOCUMENT); + $tablePartRel = (string) $relation['id']; + $relationsFileName = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels'; + + if ($zip->locateName($relationsFileName)) { + $relsTableReferences = $this->loadZip($relationsFileName, Namespaces::RELATIONSHIPS); + foreach ($relsTableReferences->Relationship as $relationship) { + $relationshipAttributes = self::getAttributes($relationship, ''); + + if ((string) $relationshipAttributes['Id'] === $tablePartRel) { + $relationshipFileName = (string) $relationshipAttributes['Target']; + $relationshipFilePath = dirname("$dir/$fileWorksheet") . '/' . $relationshipFileName; + $relationshipFilePath = File::realpath($relationshipFilePath); + + if ($this->fileExistsInArchive($this->zip, $relationshipFilePath)) { + $autoFilter = $this->loadZip($relationshipFilePath); + (new AutoFilter($docSheet, $autoFilter))->load(); + } + } + } + } + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php new file mode 100644 index 0000000..155ed73 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php @@ -0,0 +1,148 @@ +worksheet = $workSheet; + $this->worksheetXml = $worksheetXml; + } + + public function load(): void + { + // Remove all "$" in the auto filter range + $autoFilterRange = preg_replace('/\$/', '', $this->worksheetXml->autoFilter['ref']); + if (strpos($autoFilterRange, ':') !== false) { + $this->readAutoFilter($autoFilterRange, $this->worksheetXml); + } + } + + private function readAutoFilter($autoFilterRange, $xmlSheet): void + { + $autoFilter = $this->worksheet->getAutoFilter(); + $autoFilter->setRange($autoFilterRange); + + foreach ($xmlSheet->autoFilter->filterColumn as $filterColumn) { + $column = $autoFilter->getColumnByOffset((int) $filterColumn['colId']); + // Check for standard filters + if ($filterColumn->filters) { + $column->setFilterType(Column::AUTOFILTER_FILTERTYPE_FILTER); + $filters = $filterColumn->filters; + if ((isset($filters['blank'])) && ($filters['blank'] == 1)) { + // Operator is undefined, but always treated as EQUAL + $column->createRule()->setRule(null, '')->setRuleType(Rule::AUTOFILTER_RULETYPE_FILTER); + } + // Standard filters are always an OR join, so no join rule needs to be set + // Entries can be either filter elements + foreach ($filters->filter as $filterRule) { + // Operator is undefined, but always treated as EQUAL + $column->createRule()->setRule(null, (string) $filterRule['val'])->setRuleType(Rule::AUTOFILTER_RULETYPE_FILTER); + } + + // Or Date Group elements + $this->readDateRangeAutoFilter($filters, $column); + } + + // Check for custom filters + $this->readCustomAutoFilter($filterColumn, $column); + // Check for dynamic filters + $this->readDynamicAutoFilter($filterColumn, $column); + // Check for dynamic filters + $this->readTopTenAutoFilter($filterColumn, $column); + } + } + + private function readDateRangeAutoFilter(SimpleXMLElement $filters, Column $column): void + { + foreach ($filters->dateGroupItem as $dateGroupItem) { + // Operator is undefined, but always treated as EQUAL + $column->createRule()->setRule( + null, + [ + 'year' => (string) $dateGroupItem['year'], + 'month' => (string) $dateGroupItem['month'], + 'day' => (string) $dateGroupItem['day'], + 'hour' => (string) $dateGroupItem['hour'], + 'minute' => (string) $dateGroupItem['minute'], + 'second' => (string) $dateGroupItem['second'], + ], + (string) $dateGroupItem['dateTimeGrouping'] + )->setRuleType(Rule::AUTOFILTER_RULETYPE_DATEGROUP); + } + } + + private function readCustomAutoFilter(SimpleXMLElement $filterColumn, Column $column): void + { + if ($filterColumn->customFilters) { + $column->setFilterType(Column::AUTOFILTER_FILTERTYPE_CUSTOMFILTER); + $customFilters = $filterColumn->customFilters; + // Custom filters can an AND or an OR join; + // and there should only ever be one or two entries + if ((isset($customFilters['and'])) && ((string) $customFilters['and'] === '1')) { + $column->setJoin(Column::AUTOFILTER_COLUMN_JOIN_AND); + } + foreach ($customFilters->customFilter as $filterRule) { + $column->createRule()->setRule( + (string) $filterRule['operator'], + (string) $filterRule['val'] + )->setRuleType(Rule::AUTOFILTER_RULETYPE_CUSTOMFILTER); + } + } + } + + private function readDynamicAutoFilter(SimpleXMLElement $filterColumn, Column $column): void + { + if ($filterColumn->dynamicFilter) { + $column->setFilterType(Column::AUTOFILTER_FILTERTYPE_DYNAMICFILTER); + // We should only ever have one dynamic filter + foreach ($filterColumn->dynamicFilter as $filterRule) { + // Operator is undefined, but always treated as EQUAL + $column->createRule()->setRule( + null, + (string) $filterRule['val'], + (string) $filterRule['type'] + )->setRuleType(Rule::AUTOFILTER_RULETYPE_DYNAMICFILTER); + if (isset($filterRule['val'])) { + $column->setAttribute('val', (string) $filterRule['val']); + } + if (isset($filterRule['maxVal'])) { + $column->setAttribute('maxVal', (string) $filterRule['maxVal']); + } + } + } + } + + private function readTopTenAutoFilter(SimpleXMLElement $filterColumn, Column $column): void + { + if ($filterColumn->top10) { + $column->setFilterType(Column::AUTOFILTER_FILTERTYPE_TOPTENFILTER); + // We should only ever have one top10 filter + foreach ($filterColumn->top10 as $filterRule) { + $column->createRule()->setRule( + ( + ((isset($filterRule['percent'])) && ((string) $filterRule['percent'] === '1')) + ? Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT + : Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_BY_VALUE + ), + (string) $filterRule['val'], + ( + ((isset($filterRule['top'])) && ((string) $filterRule['top'] === '1')) + ? Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP + : Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_BOTTOM + ) + )->setRuleType(Rule::AUTOFILTER_RULETYPE_TOPTENFILTER); + } + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/BaseParserClass.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/BaseParserClass.php new file mode 100644 index 0000000..1679f01 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/BaseParserClass.php @@ -0,0 +1,19 @@ +attributes(); + if (isset($attributes[$name])) { + if ($format == 'string') { + return (string) $attributes[$name]; + } elseif ($format == 'integer') { + return (int) $attributes[$name]; + } elseif ($format == 'boolean') { + $value = (string) $attributes[$name]; + + return $value === 'true' || $value === '1'; + } + + return (float) $attributes[$name]; + } + + return null; + } + + private static function readColor($color, $background = false) + { + if (isset($color['rgb'])) { + return (string) $color['rgb']; + } elseif (isset($color['indexed'])) { + return Color::indexedColor($color['indexed'] - 7, $background)->getARGB(); + } + } + + /** + * @param string $chartName + * + * @return \PhpOffice\PhpSpreadsheet\Chart\Chart + */ + public static function readChart(SimpleXMLElement $chartElements, $chartName) + { + $namespacesChartMeta = $chartElements->getNamespaces(true); + $chartElementsC = $chartElements->children($namespacesChartMeta['c']); + + $XaxisLabel = $YaxisLabel = $legend = $title = null; + $dispBlanksAs = $plotVisOnly = null; + $plotArea = null; + foreach ($chartElementsC as $chartElementKey => $chartElement) { + switch ($chartElementKey) { + case 'chart': + foreach ($chartElement as $chartDetailsKey => $chartDetails) { + $chartDetailsC = $chartDetails->children($namespacesChartMeta['c']); + switch ($chartDetailsKey) { + case 'plotArea': + $plotAreaLayout = $XaxisLable = $YaxisLable = null; + $plotSeries = $plotAttributes = []; + foreach ($chartDetails as $chartDetailKey => $chartDetail) { + switch ($chartDetailKey) { + case 'layout': + $plotAreaLayout = self::chartLayoutDetails($chartDetail, $namespacesChartMeta); + + break; + case 'catAx': + if (isset($chartDetail->title)) { + $XaxisLabel = self::chartTitle($chartDetail->title->children($namespacesChartMeta['c']), $namespacesChartMeta); + } + + break; + case 'dateAx': + if (isset($chartDetail->title)) { + $XaxisLabel = self::chartTitle($chartDetail->title->children($namespacesChartMeta['c']), $namespacesChartMeta); + } + + break; + case 'valAx': + if (isset($chartDetail->title, $chartDetail->axPos)) { + $axisLabel = self::chartTitle($chartDetail->title->children($namespacesChartMeta['c']), $namespacesChartMeta); + $axPos = self::getAttribute($chartDetail->axPos, 'val', 'string'); + + switch ($axPos) { + case 't': + case 'b': + $XaxisLabel = $axisLabel; + + break; + case 'r': + case 'l': + $YaxisLabel = $axisLabel; + + break; + } + } + + break; + case 'barChart': + case 'bar3DChart': + $barDirection = self::getAttribute($chartDetail->barDir, 'val', 'string'); + $plotSer = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey); + $plotSer->setPlotDirection($barDirection); + $plotSeries[] = $plotSer; + $plotAttributes = self::readChartAttributes($chartDetail); + + break; + case 'lineChart': + case 'line3DChart': + $plotSeries[] = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey); + $plotAttributes = self::readChartAttributes($chartDetail); + + break; + case 'areaChart': + case 'area3DChart': + $plotSeries[] = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey); + $plotAttributes = self::readChartAttributes($chartDetail); + + break; + case 'doughnutChart': + case 'pieChart': + case 'pie3DChart': + $explosion = isset($chartDetail->ser->explosion); + $plotSer = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey); + $plotSer->setPlotStyle($explosion); + $plotSeries[] = $plotSer; + $plotAttributes = self::readChartAttributes($chartDetail); + + break; + case 'scatterChart': + $scatterStyle = self::getAttribute($chartDetail->scatterStyle, 'val', 'string'); + $plotSer = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey); + $plotSer->setPlotStyle($scatterStyle); + $plotSeries[] = $plotSer; + $plotAttributes = self::readChartAttributes($chartDetail); + + break; + case 'bubbleChart': + $bubbleScale = self::getAttribute($chartDetail->bubbleScale, 'val', 'integer'); + $plotSer = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey); + $plotSer->setPlotStyle($bubbleScale); + $plotSeries[] = $plotSer; + $plotAttributes = self::readChartAttributes($chartDetail); + + break; + case 'radarChart': + $radarStyle = self::getAttribute($chartDetail->radarStyle, 'val', 'string'); + $plotSer = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey); + $plotSer->setPlotStyle($radarStyle); + $plotSeries[] = $plotSer; + $plotAttributes = self::readChartAttributes($chartDetail); + + break; + case 'surfaceChart': + case 'surface3DChart': + $wireFrame = self::getAttribute($chartDetail->wireframe, 'val', 'boolean'); + $plotSer = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey); + $plotSer->setPlotStyle($wireFrame); + $plotSeries[] = $plotSer; + $plotAttributes = self::readChartAttributes($chartDetail); + + break; + case 'stockChart': + $plotSeries[] = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey); + $plotAttributes = self::readChartAttributes($plotAreaLayout); + + break; + } + } + if ($plotAreaLayout == null) { + $plotAreaLayout = new Layout(); + } + $plotArea = new PlotArea($plotAreaLayout, $plotSeries); + self::setChartAttributes($plotAreaLayout, $plotAttributes); + + break; + case 'plotVisOnly': + $plotVisOnly = self::getAttribute($chartDetails, 'val', 'string'); + + break; + case 'dispBlanksAs': + $dispBlanksAs = self::getAttribute($chartDetails, 'val', 'string'); + + break; + case 'title': + $title = self::chartTitle($chartDetails, $namespacesChartMeta); + + break; + case 'legend': + $legendPos = 'r'; + $legendLayout = null; + $legendOverlay = false; + foreach ($chartDetails as $chartDetailKey => $chartDetail) { + switch ($chartDetailKey) { + case 'legendPos': + $legendPos = self::getAttribute($chartDetail, 'val', 'string'); + + break; + case 'overlay': + $legendOverlay = self::getAttribute($chartDetail, 'val', 'boolean'); + + break; + case 'layout': + $legendLayout = self::chartLayoutDetails($chartDetail, $namespacesChartMeta); + + break; + } + } + $legend = new Legend($legendPos, $legendLayout, $legendOverlay); + + break; + } + } + } + } + $chart = new \PhpOffice\PhpSpreadsheet\Chart\Chart($chartName, $title, $legend, $plotArea, $plotVisOnly, $dispBlanksAs, $XaxisLabel, $YaxisLabel); + + return $chart; + } + + private static function chartTitle(SimpleXMLElement $titleDetails, array $namespacesChartMeta) + { + $caption = []; + $titleLayout = null; + foreach ($titleDetails as $titleDetailKey => $chartDetail) { + switch ($titleDetailKey) { + case 'tx': + $titleDetails = $chartDetail->rich->children($namespacesChartMeta['a']); + foreach ($titleDetails as $titleKey => $titleDetail) { + switch ($titleKey) { + case 'p': + $titleDetailPart = $titleDetail->children($namespacesChartMeta['a']); + $caption[] = self::parseRichText($titleDetailPart); + } + } + + break; + case 'layout': + $titleLayout = self::chartLayoutDetails($chartDetail, $namespacesChartMeta); + + break; + } + } + + return new Title($caption, $titleLayout); + } + + private static function chartLayoutDetails($chartDetail, $namespacesChartMeta) + { + if (!isset($chartDetail->manualLayout)) { + return null; + } + $details = $chartDetail->manualLayout->children($namespacesChartMeta['c']); + if ($details === null) { + return null; + } + $layout = []; + foreach ($details as $detailKey => $detail) { + $layout[$detailKey] = self::getAttribute($detail, 'val', 'string'); + } + + return new Layout($layout); + } + + private static function chartDataSeries($chartDetail, $namespacesChartMeta, $plotType) + { + $multiSeriesType = null; + $smoothLine = false; + $seriesLabel = $seriesCategory = $seriesValues = $plotOrder = []; + + $seriesDetailSet = $chartDetail->children($namespacesChartMeta['c']); + foreach ($seriesDetailSet as $seriesDetailKey => $seriesDetails) { + switch ($seriesDetailKey) { + case 'grouping': + $multiSeriesType = self::getAttribute($chartDetail->grouping, 'val', 'string'); + + break; + case 'ser': + $marker = null; + $seriesIndex = ''; + foreach ($seriesDetails as $seriesKey => $seriesDetail) { + switch ($seriesKey) { + case 'idx': + $seriesIndex = self::getAttribute($seriesDetail, 'val', 'integer'); + + break; + case 'order': + $seriesOrder = self::getAttribute($seriesDetail, 'val', 'integer'); + $plotOrder[$seriesIndex] = $seriesOrder; + + break; + case 'tx': + $seriesLabel[$seriesIndex] = self::chartDataSeriesValueSet($seriesDetail, $namespacesChartMeta); + + break; + case 'marker': + $marker = self::getAttribute($seriesDetail->symbol, 'val', 'string'); + + break; + case 'smooth': + $smoothLine = self::getAttribute($seriesDetail, 'val', 'boolean'); + + break; + case 'cat': + $seriesCategory[$seriesIndex] = self::chartDataSeriesValueSet($seriesDetail, $namespacesChartMeta); + + break; + case 'val': + $seriesValues[$seriesIndex] = self::chartDataSeriesValueSet($seriesDetail, $namespacesChartMeta, $marker); + + break; + case 'xVal': + $seriesCategory[$seriesIndex] = self::chartDataSeriesValueSet($seriesDetail, $namespacesChartMeta, $marker); + + break; + case 'yVal': + $seriesValues[$seriesIndex] = self::chartDataSeriesValueSet($seriesDetail, $namespacesChartMeta, $marker); + + break; + } + } + } + } + + return new DataSeries($plotType, $multiSeriesType, $plotOrder, $seriesLabel, $seriesCategory, $seriesValues, $smoothLine); + } + + private static function chartDataSeriesValueSet($seriesDetail, $namespacesChartMeta, $marker = null) + { + if (isset($seriesDetail->strRef)) { + $seriesSource = (string) $seriesDetail->strRef->f; + $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, null, null, $marker); + + if (isset($seriesDetail->strRef->strCache)) { + $seriesData = self::chartDataSeriesValues($seriesDetail->strRef->strCache->children($namespacesChartMeta['c']), 's'); + $seriesValues + ->setFormatCode($seriesData['formatCode']) + ->setDataValues($seriesData['dataValues']); + } + + return $seriesValues; + } elseif (isset($seriesDetail->numRef)) { + $seriesSource = (string) $seriesDetail->numRef->f; + $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, $seriesSource, null, null, null, $marker); + if (isset($seriesDetail->numRef->numCache)) { + $seriesData = self::chartDataSeriesValues($seriesDetail->numRef->numCache->children($namespacesChartMeta['c'])); + $seriesValues + ->setFormatCode($seriesData['formatCode']) + ->setDataValues($seriesData['dataValues']); + } + + return $seriesValues; + } elseif (isset($seriesDetail->multiLvlStrRef)) { + $seriesSource = (string) $seriesDetail->multiLvlStrRef->f; + $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, null, null, $marker); + + if (isset($seriesDetail->multiLvlStrRef->multiLvlStrCache)) { + $seriesData = self::chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlStrRef->multiLvlStrCache->children($namespacesChartMeta['c']), 's'); + $seriesValues + ->setFormatCode($seriesData['formatCode']) + ->setDataValues($seriesData['dataValues']); + } + + return $seriesValues; + } elseif (isset($seriesDetail->multiLvlNumRef)) { + $seriesSource = (string) $seriesDetail->multiLvlNumRef->f; + $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, null, null, $marker); + + if (isset($seriesDetail->multiLvlNumRef->multiLvlNumCache)) { + $seriesData = self::chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlNumRef->multiLvlNumCache->children($namespacesChartMeta['c']), 's'); + $seriesValues + ->setFormatCode($seriesData['formatCode']) + ->setDataValues($seriesData['dataValues']); + } + + return $seriesValues; + } + + return null; + } + + private static function chartDataSeriesValues($seriesValueSet, $dataType = 'n') + { + $seriesVal = []; + $formatCode = ''; + $pointCount = 0; + + foreach ($seriesValueSet as $seriesValueIdx => $seriesValue) { + switch ($seriesValueIdx) { + case 'ptCount': + $pointCount = self::getAttribute($seriesValue, 'val', 'integer'); + + break; + case 'formatCode': + $formatCode = (string) $seriesValue; + + break; + case 'pt': + $pointVal = self::getAttribute($seriesValue, 'idx', 'integer'); + if ($dataType == 's') { + $seriesVal[$pointVal] = (string) $seriesValue->v; + } elseif ($seriesValue->v === Functions::NA()) { + $seriesVal[$pointVal] = null; + } else { + $seriesVal[$pointVal] = (float) $seriesValue->v; + } + + break; + } + } + + return [ + 'formatCode' => $formatCode, + 'pointCount' => $pointCount, + 'dataValues' => $seriesVal, + ]; + } + + private static function chartDataSeriesValuesMultiLevel($seriesValueSet, $dataType = 'n') + { + $seriesVal = []; + $formatCode = ''; + $pointCount = 0; + + foreach ($seriesValueSet->lvl as $seriesLevelIdx => $seriesLevel) { + foreach ($seriesLevel as $seriesValueIdx => $seriesValue) { + switch ($seriesValueIdx) { + case 'ptCount': + $pointCount = self::getAttribute($seriesValue, 'val', 'integer'); + + break; + case 'formatCode': + $formatCode = (string) $seriesValue; + + break; + case 'pt': + $pointVal = self::getAttribute($seriesValue, 'idx', 'integer'); + if ($dataType == 's') { + $seriesVal[$pointVal][] = (string) $seriesValue->v; + } elseif ($seriesValue->v === Functions::NA()) { + $seriesVal[$pointVal] = null; + } else { + $seriesVal[$pointVal][] = (float) $seriesValue->v; + } + + break; + } + } + } + + return [ + 'formatCode' => $formatCode, + 'pointCount' => $pointCount, + 'dataValues' => $seriesVal, + ]; + } + + private static function parseRichText(SimpleXMLElement $titleDetailPart) + { + $value = new RichText(); + $objText = null; + foreach ($titleDetailPart as $titleDetailElementKey => $titleDetailElement) { + if (isset($titleDetailElement->t)) { + $objText = $value->createTextRun((string) $titleDetailElement->t); + } + if (isset($titleDetailElement->rPr)) { + if (isset($titleDetailElement->rPr->rFont['val'])) { + $objText->getFont()->setName((string) $titleDetailElement->rPr->rFont['val']); + } + + $fontSize = (self::getAttribute($titleDetailElement->rPr, 'sz', 'integer')); + if (is_int($fontSize)) { + $objText->getFont()->setSize(floor($fontSize / 100)); + } + + $fontColor = (self::getAttribute($titleDetailElement->rPr, 'color', 'string')); + if ($fontColor !== null) { + $objText->getFont()->setColor(new Color(self::readColor($fontColor))); + } + + $bold = self::getAttribute($titleDetailElement->rPr, 'b', 'boolean'); + if ($bold !== null) { + $objText->getFont()->setBold($bold); + } + + $italic = self::getAttribute($titleDetailElement->rPr, 'i', 'boolean'); + if ($italic !== null) { + $objText->getFont()->setItalic($italic); + } + + $baseline = self::getAttribute($titleDetailElement->rPr, 'baseline', 'integer'); + if ($baseline !== null) { + if ($baseline > 0) { + $objText->getFont()->setSuperscript(true); + } elseif ($baseline < 0) { + $objText->getFont()->setSubscript(true); + } + } + + $underscore = (self::getAttribute($titleDetailElement->rPr, 'u', 'string')); + if ($underscore !== null) { + if ($underscore == 'sng') { + $objText->getFont()->setUnderline(Font::UNDERLINE_SINGLE); + } elseif ($underscore == 'dbl') { + $objText->getFont()->setUnderline(Font::UNDERLINE_DOUBLE); + } else { + $objText->getFont()->setUnderline(Font::UNDERLINE_NONE); + } + } + + $strikethrough = (self::getAttribute($titleDetailElement->rPr, 's', 'string')); + if ($strikethrough !== null) { + if ($strikethrough == 'noStrike') { + $objText->getFont()->setStrikethrough(false); + } else { + $objText->getFont()->setStrikethrough(true); + } + } + } + } + + return $value; + } + + private static function readChartAttributes($chartDetail) + { + $plotAttributes = []; + if (isset($chartDetail->dLbls)) { + if (isset($chartDetail->dLbls->showLegendKey)) { + $plotAttributes['showLegendKey'] = self::getAttribute($chartDetail->dLbls->showLegendKey, 'val', 'string'); + } + if (isset($chartDetail->dLbls->showVal)) { + $plotAttributes['showVal'] = self::getAttribute($chartDetail->dLbls->showVal, 'val', 'string'); + } + if (isset($chartDetail->dLbls->showCatName)) { + $plotAttributes['showCatName'] = self::getAttribute($chartDetail->dLbls->showCatName, 'val', 'string'); + } + if (isset($chartDetail->dLbls->showSerName)) { + $plotAttributes['showSerName'] = self::getAttribute($chartDetail->dLbls->showSerName, 'val', 'string'); + } + if (isset($chartDetail->dLbls->showPercent)) { + $plotAttributes['showPercent'] = self::getAttribute($chartDetail->dLbls->showPercent, 'val', 'string'); + } + if (isset($chartDetail->dLbls->showBubbleSize)) { + $plotAttributes['showBubbleSize'] = self::getAttribute($chartDetail->dLbls->showBubbleSize, 'val', 'string'); + } + if (isset($chartDetail->dLbls->showLeaderLines)) { + $plotAttributes['showLeaderLines'] = self::getAttribute($chartDetail->dLbls->showLeaderLines, 'val', 'string'); + } + } + + return $plotAttributes; + } + + /** + * @param mixed $plotAttributes + */ + private static function setChartAttributes(Layout $plotArea, $plotAttributes): void + { + foreach ($plotAttributes as $plotAttributeKey => $plotAttributeValue) { + switch ($plotAttributeKey) { + case 'showLegendKey': + $plotArea->setShowLegendKey($plotAttributeValue); + + break; + case 'showVal': + $plotArea->setShowVal($plotAttributeValue); + + break; + case 'showCatName': + $plotArea->setShowCatName($plotAttributeValue); + + break; + case 'showSerName': + $plotArea->setShowSerName($plotAttributeValue); + + break; + case 'showPercent': + $plotArea->setShowPercent($plotAttributeValue); + + break; + case 'showBubbleSize': + $plotArea->setShowBubbleSize($plotAttributeValue); + + break; + case 'showLeaderLines': + $plotArea->setShowLeaderLines($plotAttributeValue); + + break; + } + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php new file mode 100644 index 0000000..2a1e2af --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php @@ -0,0 +1,210 @@ +worksheet = $workSheet; + $this->worksheetXml = $worksheetXml; + } + + /** + * Set Worksheet column attributes by attributes array passed. + * + * @param string $columnAddress A, B, ... DX, ... + * @param array $columnAttributes array of attributes (indexes are attribute name, values are value) + * 'xfIndex', 'visible', 'collapsed', 'outlineLevel', 'width', ... ? + */ + private function setColumnAttributes($columnAddress, array $columnAttributes): void + { + if (isset($columnAttributes['xfIndex'])) { + $this->worksheet->getColumnDimension($columnAddress)->setXfIndex($columnAttributes['xfIndex']); + } + if (isset($columnAttributes['visible'])) { + $this->worksheet->getColumnDimension($columnAddress)->setVisible($columnAttributes['visible']); + } + if (isset($columnAttributes['collapsed'])) { + $this->worksheet->getColumnDimension($columnAddress)->setCollapsed($columnAttributes['collapsed']); + } + if (isset($columnAttributes['outlineLevel'])) { + $this->worksheet->getColumnDimension($columnAddress)->setOutlineLevel($columnAttributes['outlineLevel']); + } + if (isset($columnAttributes['width'])) { + $this->worksheet->getColumnDimension($columnAddress)->setWidth($columnAttributes['width']); + } + } + + /** + * Set Worksheet row attributes by attributes array passed. + * + * @param int $rowNumber 1, 2, 3, ... 99, ... + * @param array $rowAttributes array of attributes (indexes are attribute name, values are value) + * 'xfIndex', 'visible', 'collapsed', 'outlineLevel', 'rowHeight', ... ? + */ + private function setRowAttributes($rowNumber, array $rowAttributes): void + { + if (isset($rowAttributes['xfIndex'])) { + $this->worksheet->getRowDimension($rowNumber)->setXfIndex($rowAttributes['xfIndex']); + } + if (isset($rowAttributes['visible'])) { + $this->worksheet->getRowDimension($rowNumber)->setVisible($rowAttributes['visible']); + } + if (isset($rowAttributes['collapsed'])) { + $this->worksheet->getRowDimension($rowNumber)->setCollapsed($rowAttributes['collapsed']); + } + if (isset($rowAttributes['outlineLevel'])) { + $this->worksheet->getRowDimension($rowNumber)->setOutlineLevel($rowAttributes['outlineLevel']); + } + if (isset($rowAttributes['rowHeight'])) { + $this->worksheet->getRowDimension($rowNumber)->setRowHeight($rowAttributes['rowHeight']); + } + } + + public function load(?IReadFilter $readFilter = null, bool $readDataOnly = false): void + { + if ($this->worksheetXml === null) { + return; + } + + $columnsAttributes = []; + $rowsAttributes = []; + if (isset($this->worksheetXml->cols)) { + $columnsAttributes = $this->readColumnAttributes($this->worksheetXml->cols, $readDataOnly); + } + + if ($this->worksheetXml->sheetData && $this->worksheetXml->sheetData->row) { + $rowsAttributes = $this->readRowAttributes($this->worksheetXml->sheetData->row, $readDataOnly); + } + + if ($readFilter !== null && get_class($readFilter) === DefaultReadFilter::class) { + $readFilter = null; + } + + // set columns/rows attributes + $columnsAttributesAreSet = []; + foreach ($columnsAttributes as $columnCoordinate => $columnAttributes) { + if ( + $readFilter === null || + !$this->isFilteredColumn($readFilter, $columnCoordinate, $rowsAttributes) + ) { + if (!isset($columnsAttributesAreSet[$columnCoordinate])) { + $this->setColumnAttributes($columnCoordinate, $columnAttributes); + $columnsAttributesAreSet[$columnCoordinate] = true; + } + } + } + + $rowsAttributesAreSet = []; + foreach ($rowsAttributes as $rowCoordinate => $rowAttributes) { + if ( + $readFilter === null || + !$this->isFilteredRow($readFilter, $rowCoordinate, $columnsAttributes) + ) { + if (!isset($rowsAttributesAreSet[$rowCoordinate])) { + $this->setRowAttributes($rowCoordinate, $rowAttributes); + $rowsAttributesAreSet[$rowCoordinate] = true; + } + } + } + } + + private function isFilteredColumn(IReadFilter $readFilter, $columnCoordinate, array $rowsAttributes) + { + foreach ($rowsAttributes as $rowCoordinate => $rowAttributes) { + if (!$readFilter->readCell($columnCoordinate, $rowCoordinate, $this->worksheet->getTitle())) { + return true; + } + } + + return false; + } + + private function readColumnAttributes(SimpleXMLElement $worksheetCols, $readDataOnly) + { + $columnAttributes = []; + + foreach ($worksheetCols->col as $column) { + $startColumn = Coordinate::stringFromColumnIndex((int) $column['min']); + $endColumn = Coordinate::stringFromColumnIndex((int) $column['max']); + ++$endColumn; + for ($columnAddress = $startColumn; $columnAddress !== $endColumn; ++$columnAddress) { + $columnAttributes[$columnAddress] = $this->readColumnRangeAttributes($column, $readDataOnly); + + if ((int) ($column['max']) == 16384) { + break; + } + } + } + + return $columnAttributes; + } + + private function readColumnRangeAttributes(SimpleXMLElement $column, $readDataOnly) + { + $columnAttributes = []; + + if ($column['style'] && !$readDataOnly) { + $columnAttributes['xfIndex'] = (int) $column['style']; + } + if (self::boolean($column['hidden'])) { + $columnAttributes['visible'] = false; + } + if (self::boolean($column['collapsed'])) { + $columnAttributes['collapsed'] = true; + } + if (((int) $column['outlineLevel']) > 0) { + $columnAttributes['outlineLevel'] = (int) $column['outlineLevel']; + } + $columnAttributes['width'] = (float) $column['width']; + + return $columnAttributes; + } + + private function isFilteredRow(IReadFilter $readFilter, $rowCoordinate, array $columnsAttributes) + { + foreach ($columnsAttributes as $columnCoordinate => $columnAttributes) { + if (!$readFilter->readCell($columnCoordinate, $rowCoordinate, $this->worksheet->getTitle())) { + return true; + } + } + + return false; + } + + private function readRowAttributes(SimpleXMLElement $worksheetRow, $readDataOnly) + { + $rowAttributes = []; + + foreach ($worksheetRow as $row) { + if ($row['ht'] && !$readDataOnly) { + $rowAttributes[(int) $row['r']]['rowHeight'] = (float) $row['ht']; + } + if (self::boolean($row['hidden'])) { + $rowAttributes[(int) $row['r']]['visible'] = false; + } + if (self::boolean($row['collapsed'])) { + $rowAttributes[(int) $row['r']]['collapsed'] = true; + } + if ((int) $row['outlineLevel'] > 0) { + $rowAttributes[(int) $row['r']]['outlineLevel'] = (int) $row['outlineLevel']; + } + if ($row['s'] && !$readDataOnly) { + $rowAttributes[(int) $row['r']]['xfIndex'] = (int) $row['s']; + } + } + + return $rowAttributes; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php new file mode 100644 index 0000000..dcd7ad1 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php @@ -0,0 +1,149 @@ +worksheet = $workSheet; + $this->worksheetXml = $worksheetXml; + $this->dxfs = $dxfs; + } + + public function load(): void + { + $this->setConditionalStyles( + $this->worksheet, + $this->readConditionalStyles($this->worksheetXml), + $this->worksheetXml->extLst + ); + } + + private function readConditionalStyles($xmlSheet) + { + $conditionals = []; + foreach ($xmlSheet->conditionalFormatting as $conditional) { + foreach ($conditional->cfRule as $cfRule) { + if (Conditional::isValidConditionType((string) $cfRule['type']) && isset($this->dxfs[(int) ($cfRule['dxfId'])])) { + $conditionals[(string) $conditional['sqref']][(int) ($cfRule['priority'])] = $cfRule; + } elseif ((string) $cfRule['type'] == Conditional::CONDITION_DATABAR) { + $conditionals[(string) $conditional['sqref']][(int) ($cfRule['priority'])] = $cfRule; + } + } + } + + return $conditionals; + } + + private function setConditionalStyles(Worksheet $worksheet, array $conditionals, $xmlExtLst): void + { + foreach ($conditionals as $ref => $cfRules) { + ksort($cfRules); + $conditionalStyles = $this->readStyleRules($cfRules, $xmlExtLst); + + // Extract all cell references in $ref + $cellBlocks = explode(' ', str_replace('$', '', strtoupper($ref))); + foreach ($cellBlocks as $cellBlock) { + $worksheet->getStyle($cellBlock)->setConditionalStyles($conditionalStyles); + } + } + } + + private function readStyleRules($cfRules, $extLst) + { + $conditionalFormattingRuleExtensions = ConditionalFormattingRuleExtension::parseExtLstXml($extLst); + $conditionalStyles = []; + foreach ($cfRules as $cfRule) { + $objConditional = new Conditional(); + $objConditional->setConditionType((string) $cfRule['type']); + $objConditional->setOperatorType((string) $cfRule['operator']); + + if ((string) $cfRule['text'] != '') { + $objConditional->setText((string) $cfRule['text']); + } + + if (isset($cfRule['stopIfTrue']) && (int) $cfRule['stopIfTrue'] === 1) { + $objConditional->setStopIfTrue(true); + } + + if (count($cfRule->formula) > 1) { + foreach ($cfRule->formula as $formula) { + $objConditional->addCondition((string) $formula); + } + } else { + $objConditional->addCondition((string) $cfRule->formula); + } + + if (isset($cfRule->dataBar)) { + $objConditional->setDataBar( + $this->readDataBarOfConditionalRule($cfRule, $conditionalFormattingRuleExtensions) + ); + } else { + $objConditional->setStyle(clone $this->dxfs[(int) ($cfRule['dxfId'])]); + } + + $conditionalStyles[] = $objConditional; + } + + return $conditionalStyles; + } + + private function readDataBarOfConditionalRule($cfRule, $conditionalFormattingRuleExtensions): ConditionalDataBar + { + $dataBar = new ConditionalDataBar(); + //dataBar attribute + if (isset($cfRule->dataBar['showValue'])) { + $dataBar->setShowValue((bool) $cfRule->dataBar['showValue']); + } + + //dataBar children + //conditionalFormatValueObjects + $cfvoXml = $cfRule->dataBar->cfvo; + $cfvoIndex = 0; + foreach ((count($cfvoXml) > 1 ? $cfvoXml : [$cfvoXml]) as $cfvo) { + if ($cfvoIndex === 0) { + $dataBar->setMinimumConditionalFormatValueObject(new ConditionalFormatValueObject((string) $cfvo['type'], (string) $cfvo['val'])); + } + if ($cfvoIndex === 1) { + $dataBar->setMaximumConditionalFormatValueObject(new ConditionalFormatValueObject((string) $cfvo['type'], (string) $cfvo['val'])); + } + ++$cfvoIndex; + } + + //color + if (isset($cfRule->dataBar->color)) { + $dataBar->setColor((string) $cfRule->dataBar->color['rgb']); + } + //extLst + $this->readDataBarExtLstOfConditionalRule($dataBar, $cfRule, $conditionalFormattingRuleExtensions); + + return $dataBar; + } + + private function readDataBarExtLstOfConditionalRule(ConditionalDataBar $dataBar, $cfRule, $conditionalFormattingRuleExtensions): void + { + if (isset($cfRule->extLst)) { + $ns = $cfRule->extLst->getNamespaces(true); + foreach ((count($cfRule->extLst) > 0 ? $cfRule->extLst->ext : [$cfRule->extLst->ext]) as $ext) { + $extId = (string) $ext->children($ns['x14'])->id; + if (isset($conditionalFormattingRuleExtensions[$extId]) && (string) $ext['uri'] === '{B025F937-C7B1-47D3-B67F-A62EFF666E3E}') { + $dataBar->setConditionalFormattingRuleExt($conditionalFormattingRuleExtensions[$extId]); + } + } + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php new file mode 100644 index 0000000..b699cb5 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php @@ -0,0 +1,53 @@ +worksheet = $workSheet; + $this->worksheetXml = $worksheetXml; + } + + public function load(): void + { + foreach ($this->worksheetXml->dataValidations->dataValidation as $dataValidation) { + // Uppercase coordinate + $range = strtoupper($dataValidation['sqref']); + $rangeSet = explode(' ', $range); + foreach ($rangeSet as $range) { + $stRange = $this->worksheet->shrinkRangeToFit($range); + + // Extract all cell references in $range + foreach (Coordinate::extractAllCellReferencesInRange($stRange) as $reference) { + // Create validation + $docValidation = $this->worksheet->getCell($reference)->getDataValidation(); + $docValidation->setType((string) $dataValidation['type']); + $docValidation->setErrorStyle((string) $dataValidation['errorStyle']); + $docValidation->setOperator((string) $dataValidation['operator']); + $docValidation->setAllowBlank(filter_var($dataValidation['allowBlank'], FILTER_VALIDATE_BOOLEAN)); + // showDropDown is inverted (works as hideDropDown if true) + $docValidation->setShowDropDown(!filter_var($dataValidation['showDropDown'], FILTER_VALIDATE_BOOLEAN)); + $docValidation->setShowInputMessage(filter_var($dataValidation['showInputMessage'], FILTER_VALIDATE_BOOLEAN)); + $docValidation->setShowErrorMessage(filter_var($dataValidation['showErrorMessage'], FILTER_VALIDATE_BOOLEAN)); + $docValidation->setErrorTitle((string) $dataValidation['errorTitle']); + $docValidation->setError((string) $dataValidation['error']); + $docValidation->setPromptTitle((string) $dataValidation['promptTitle']); + $docValidation->setPrompt((string) $dataValidation['prompt']); + $docValidation->setFormula1((string) $dataValidation->formula1); + $docValidation->setFormula2((string) $dataValidation->formula2); + $docValidation->setSqref($range); + } + } + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php new file mode 100644 index 0000000..8488499 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php @@ -0,0 +1,64 @@ +worksheet = $workSheet; + } + + public function readHyperlinks(SimpleXMLElement $relsWorksheet): void + { + foreach ($relsWorksheet->children(Namespaces::RELATIONSHIPS)->Relationship as $elementx) { + $element = Xlsx::getAttributes($elementx); + if ($element->Type == Namespaces::HYPERLINK) { + $this->hyperlinks[(string) $element->Id] = (string) $element->Target; + } + } + } + + public function setHyperlinks(SimpleXMLElement $worksheetXml): void + { + foreach ($worksheetXml->children(Namespaces::MAIN)->hyperlink as $hyperlink) { + if ($hyperlink !== null) { + $this->setHyperlink($hyperlink, $this->worksheet); + } + } + } + + private function setHyperlink(SimpleXMLElement $hyperlink, Worksheet $worksheet): void + { + // Link url + $linkRel = Xlsx::getAttributes($hyperlink, Namespaces::SCHEMA_OFFICE_DOCUMENT); + + $attributes = Xlsx::getAttributes($hyperlink); + foreach (Coordinate::extractAllCellReferencesInRange($attributes->ref) as $cellReference) { + $cell = $worksheet->getCell($cellReference); + if (isset($linkRel['id'])) { + $hyperlinkUrl = $this->hyperlinks[(string) $linkRel['id']] ?? null; + if (isset($attributes['location'])) { + $hyperlinkUrl .= '#' . (string) $attributes['location']; + } + $cell->getHyperlink()->setUrl($hyperlinkUrl); + } elseif (isset($attributes['location'])) { + $cell->getHyperlink()->setUrl('sheet://' . (string) $attributes['location']); + } + + // Tooltip + if (isset($attributes['tooltip'])) { + $cell->getHyperlink()->setTooltip((string) $attributes['tooltip']); + } + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Namespaces.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Namespaces.php new file mode 100644 index 0000000..54f56d7 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Namespaces.php @@ -0,0 +1,76 @@ +worksheet = $workSheet; + $this->worksheetXml = $worksheetXml; + } + + public function load(array $unparsedLoadedData) + { + if (!$this->worksheetXml) { + return $unparsedLoadedData; + } + + $this->margins($this->worksheetXml, $this->worksheet); + $unparsedLoadedData = $this->pageSetup($this->worksheetXml, $this->worksheet, $unparsedLoadedData); + $this->headerFooter($this->worksheetXml, $this->worksheet); + $this->pageBreaks($this->worksheetXml, $this->worksheet); + + return $unparsedLoadedData; + } + + private function margins(SimpleXMLElement $xmlSheet, Worksheet $worksheet): void + { + if ($xmlSheet->pageMargins) { + $docPageMargins = $worksheet->getPageMargins(); + $docPageMargins->setLeft((float) ($xmlSheet->pageMargins['left'])); + $docPageMargins->setRight((float) ($xmlSheet->pageMargins['right'])); + $docPageMargins->setTop((float) ($xmlSheet->pageMargins['top'])); + $docPageMargins->setBottom((float) ($xmlSheet->pageMargins['bottom'])); + $docPageMargins->setHeader((float) ($xmlSheet->pageMargins['header'])); + $docPageMargins->setFooter((float) ($xmlSheet->pageMargins['footer'])); + } + } + + private function pageSetup(SimpleXMLElement $xmlSheet, Worksheet $worksheet, array $unparsedLoadedData) + { + if ($xmlSheet->pageSetup) { + $docPageSetup = $worksheet->getPageSetup(); + + if (isset($xmlSheet->pageSetup['orientation'])) { + $docPageSetup->setOrientation((string) $xmlSheet->pageSetup['orientation']); + } + if (isset($xmlSheet->pageSetup['paperSize'])) { + $docPageSetup->setPaperSize((int) ($xmlSheet->pageSetup['paperSize'])); + } + if (isset($xmlSheet->pageSetup['scale'])) { + $docPageSetup->setScale((int) ($xmlSheet->pageSetup['scale']), false); + } + if (isset($xmlSheet->pageSetup['fitToHeight']) && (int) ($xmlSheet->pageSetup['fitToHeight']) >= 0) { + $docPageSetup->setFitToHeight((int) ($xmlSheet->pageSetup['fitToHeight']), false); + } + if (isset($xmlSheet->pageSetup['fitToWidth']) && (int) ($xmlSheet->pageSetup['fitToWidth']) >= 0) { + $docPageSetup->setFitToWidth((int) ($xmlSheet->pageSetup['fitToWidth']), false); + } + if ( + isset($xmlSheet->pageSetup['firstPageNumber'], $xmlSheet->pageSetup['useFirstPageNumber']) && + self::boolean((string) $xmlSheet->pageSetup['useFirstPageNumber']) + ) { + $docPageSetup->setFirstPageNumber((int) ($xmlSheet->pageSetup['firstPageNumber'])); + } + if (isset($xmlSheet->pageSetup['pageOrder'])) { + $docPageSetup->setPageOrder((string) $xmlSheet->pageSetup['pageOrder']); + } + + $relAttributes = $xmlSheet->pageSetup->attributes(Namespaces::SCHEMA_OFFICE_DOCUMENT); + if (isset($relAttributes['id'])) { + $unparsedLoadedData['sheets'][$worksheet->getCodeName()]['pageSetupRelId'] = (string) $relAttributes['id']; + } + } + + return $unparsedLoadedData; + } + + private function headerFooter(SimpleXMLElement $xmlSheet, Worksheet $worksheet): void + { + if ($xmlSheet->headerFooter) { + $docHeaderFooter = $worksheet->getHeaderFooter(); + + if ( + isset($xmlSheet->headerFooter['differentOddEven']) && + self::boolean((string) $xmlSheet->headerFooter['differentOddEven']) + ) { + $docHeaderFooter->setDifferentOddEven(true); + } else { + $docHeaderFooter->setDifferentOddEven(false); + } + if ( + isset($xmlSheet->headerFooter['differentFirst']) && + self::boolean((string) $xmlSheet->headerFooter['differentFirst']) + ) { + $docHeaderFooter->setDifferentFirst(true); + } else { + $docHeaderFooter->setDifferentFirst(false); + } + if ( + isset($xmlSheet->headerFooter['scaleWithDoc']) && + !self::boolean((string) $xmlSheet->headerFooter['scaleWithDoc']) + ) { + $docHeaderFooter->setScaleWithDocument(false); + } else { + $docHeaderFooter->setScaleWithDocument(true); + } + if ( + isset($xmlSheet->headerFooter['alignWithMargins']) && + !self::boolean((string) $xmlSheet->headerFooter['alignWithMargins']) + ) { + $docHeaderFooter->setAlignWithMargins(false); + } else { + $docHeaderFooter->setAlignWithMargins(true); + } + + $docHeaderFooter->setOddHeader((string) $xmlSheet->headerFooter->oddHeader); + $docHeaderFooter->setOddFooter((string) $xmlSheet->headerFooter->oddFooter); + $docHeaderFooter->setEvenHeader((string) $xmlSheet->headerFooter->evenHeader); + $docHeaderFooter->setEvenFooter((string) $xmlSheet->headerFooter->evenFooter); + $docHeaderFooter->setFirstHeader((string) $xmlSheet->headerFooter->firstHeader); + $docHeaderFooter->setFirstFooter((string) $xmlSheet->headerFooter->firstFooter); + } + } + + private function pageBreaks(SimpleXMLElement $xmlSheet, Worksheet $worksheet): void + { + if ($xmlSheet->rowBreaks && $xmlSheet->rowBreaks->brk) { + $this->rowBreaks($xmlSheet, $worksheet); + } + if ($xmlSheet->colBreaks && $xmlSheet->colBreaks->brk) { + $this->columnBreaks($xmlSheet, $worksheet); + } + } + + private function rowBreaks(SimpleXMLElement $xmlSheet, Worksheet $worksheet): void + { + foreach ($xmlSheet->rowBreaks->brk as $brk) { + if ($brk['man']) { + $worksheet->setBreak("A{$brk['id']}", Worksheet::BREAK_ROW); + } + } + } + + private function columnBreaks(SimpleXMLElement $xmlSheet, Worksheet $worksheet): void + { + foreach ($xmlSheet->colBreaks->brk as $brk) { + if ($brk['man']) { + $worksheet->setBreak( + Coordinate::stringFromColumnIndex(((int) $brk['id']) + 1) . '1', + Worksheet::BREAK_COLUMN + ); + } + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Properties.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Properties.php new file mode 100644 index 0000000..82b5172 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Properties.php @@ -0,0 +1,109 @@ +securityScanner = $securityScanner; + $this->docProps = $docProps; + } + + /** + * @param mixed $obj + */ + private static function nullOrSimple($obj): ?SimpleXMLElement + { + return ($obj instanceof SimpleXMLElement) ? $obj : null; + } + + private function extractPropertyData(string $propertyData): ?SimpleXMLElement + { + // okay to omit namespace because everything will be processed by xpath + $obj = simplexml_load_string( + $this->securityScanner->scan($propertyData), + 'SimpleXMLElement', + Settings::getLibXmlLoaderOptions() + ); + + return self::nullOrSimple($obj); + } + + public function readCoreProperties(string $propertyData): void + { + $xmlCore = $this->extractPropertyData($propertyData); + + if (is_object($xmlCore)) { + $xmlCore->registerXPathNamespace('dc', Namespaces::DC_ELEMENTS); + $xmlCore->registerXPathNamespace('dcterms', Namespaces::DC_TERMS); + $xmlCore->registerXPathNamespace('cp', Namespaces::CORE_PROPERTIES2); + + $this->docProps->setCreator((string) self::getArrayItem($xmlCore->xpath('dc:creator'))); + $this->docProps->setLastModifiedBy((string) self::getArrayItem($xmlCore->xpath('cp:lastModifiedBy'))); + $this->docProps->setCreated((string) self::getArrayItem($xmlCore->xpath('dcterms:created'))); //! respect xsi:type + $this->docProps->setModified((string) self::getArrayItem($xmlCore->xpath('dcterms:modified'))); //! respect xsi:type + $this->docProps->setTitle((string) self::getArrayItem($xmlCore->xpath('dc:title'))); + $this->docProps->setDescription((string) self::getArrayItem($xmlCore->xpath('dc:description'))); + $this->docProps->setSubject((string) self::getArrayItem($xmlCore->xpath('dc:subject'))); + $this->docProps->setKeywords((string) self::getArrayItem($xmlCore->xpath('cp:keywords'))); + $this->docProps->setCategory((string) self::getArrayItem($xmlCore->xpath('cp:category'))); + } + } + + public function readExtendedProperties(string $propertyData): void + { + $xmlCore = $this->extractPropertyData($propertyData); + + if (is_object($xmlCore)) { + if (isset($xmlCore->Company)) { + $this->docProps->setCompany((string) $xmlCore->Company); + } + if (isset($xmlCore->Manager)) { + $this->docProps->setManager((string) $xmlCore->Manager); + } + } + } + + public function readCustomProperties(string $propertyData): void + { + $xmlCore = $this->extractPropertyData($propertyData); + + if (is_object($xmlCore)) { + foreach ($xmlCore as $xmlProperty) { + /** @var SimpleXMLElement $xmlProperty */ + $cellDataOfficeAttributes = $xmlProperty->attributes(); + if (isset($cellDataOfficeAttributes['name'])) { + $propertyName = (string) $cellDataOfficeAttributes['name']; + $cellDataOfficeChildren = $xmlProperty->children('http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes'); + + $attributeType = $cellDataOfficeChildren->getName(); + $attributeValue = (string) $cellDataOfficeChildren->{$attributeType}; + $attributeValue = DocumentProperties::convertProperty($attributeValue, $attributeType); + $attributeType = DocumentProperties::convertPropertyType($attributeType); + $this->docProps->setCustomProperty($propertyName, $attributeValue, $attributeType); + } + } + } + } + + /** + * @param array|false $array + * @param mixed $key + */ + private static function getArrayItem($array, $key = 0): ?SimpleXMLElement + { + return is_array($array) ? ($array[$key] ?? null) : null; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php new file mode 100644 index 0000000..1235881 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php @@ -0,0 +1,135 @@ +worksheet = $workSheet; + $this->worksheetXml = $worksheetXml; + } + + /** + * @param bool $readDataOnly + */ + public function load($readDataOnly = false): void + { + if ($this->worksheetXml === null) { + return; + } + + if (isset($this->worksheetXml->sheetPr)) { + $this->tabColor($this->worksheetXml->sheetPr); + $this->codeName($this->worksheetXml->sheetPr); + $this->outlines($this->worksheetXml->sheetPr); + $this->pageSetup($this->worksheetXml->sheetPr); + } + + if (isset($this->worksheetXml->sheetFormatPr)) { + $this->sheetFormat($this->worksheetXml->sheetFormatPr); + } + + if (!$readDataOnly && isset($this->worksheetXml->printOptions)) { + $this->printOptions($this->worksheetXml->printOptions); + } + } + + private function tabColor(SimpleXMLElement $sheetPr): void + { + if (isset($sheetPr->tabColor, $sheetPr->tabColor['rgb'])) { + $this->worksheet->getTabColor()->setARGB((string) $sheetPr->tabColor['rgb']); + } + } + + private function codeName(SimpleXMLElement $sheetPr): void + { + if (isset($sheetPr['codeName'])) { + $this->worksheet->setCodeName((string) $sheetPr['codeName'], false); + } + } + + private function outlines(SimpleXMLElement $sheetPr): void + { + if (isset($sheetPr->outlinePr)) { + if ( + isset($sheetPr->outlinePr['summaryRight']) && + !self::boolean((string) $sheetPr->outlinePr['summaryRight']) + ) { + $this->worksheet->setShowSummaryRight(false); + } else { + $this->worksheet->setShowSummaryRight(true); + } + + if ( + isset($sheetPr->outlinePr['summaryBelow']) && + !self::boolean((string) $sheetPr->outlinePr['summaryBelow']) + ) { + $this->worksheet->setShowSummaryBelow(false); + } else { + $this->worksheet->setShowSummaryBelow(true); + } + } + } + + private function pageSetup(SimpleXMLElement $sheetPr): void + { + if (isset($sheetPr->pageSetUpPr)) { + if ( + isset($sheetPr->pageSetUpPr['fitToPage']) && + !self::boolean((string) $sheetPr->pageSetUpPr['fitToPage']) + ) { + $this->worksheet->getPageSetup()->setFitToPage(false); + } else { + $this->worksheet->getPageSetup()->setFitToPage(true); + } + } + } + + private function sheetFormat(SimpleXMLElement $sheetFormatPr): void + { + if ( + isset($sheetFormatPr['customHeight']) && + self::boolean((string) $sheetFormatPr['customHeight']) && + isset($sheetFormatPr['defaultRowHeight']) + ) { + $this->worksheet->getDefaultRowDimension() + ->setRowHeight((float) $sheetFormatPr['defaultRowHeight']); + } + + if (isset($sheetFormatPr['defaultColWidth'])) { + $this->worksheet->getDefaultColumnDimension() + ->setWidth((float) $sheetFormatPr['defaultColWidth']); + } + + if ( + isset($sheetFormatPr['zeroHeight']) && + ((string) $sheetFormatPr['zeroHeight'] === '1') + ) { + $this->worksheet->getDefaultRowDimension()->setZeroHeight(true); + } + } + + private function printOptions(SimpleXMLElement $printOptions): void + { + if (self::boolean((string) $printOptions['gridLinesSet'])) { + $this->worksheet->setShowGridlines(true); + } + if (self::boolean((string) $printOptions['gridLines'])) { + $this->worksheet->setPrintGridlines(true); + } + if (self::boolean((string) $printOptions['horizontalCentered'])) { + $this->worksheet->getPageSetup()->setHorizontalCentered(true); + } + if (self::boolean((string) $printOptions['verticalCentered'])) { + $this->worksheet->getPageSetup()->setVerticalCentered(true); + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SheetViews.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SheetViews.php new file mode 100644 index 0000000..b2bc99f --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SheetViews.php @@ -0,0 +1,156 @@ +sheetViewXml = $sheetViewXml; + $this->sheetViewAttributes = Xlsx::testSimpleXml($sheetViewXml->attributes()); + $this->worksheet = $workSheet; + } + + public function load(): void + { + $this->topLeft(); + $this->zoomScale(); + $this->view(); + $this->gridLines(); + $this->headers(); + $this->direction(); + $this->showZeros(); + + if (isset($this->sheetViewXml->pane)) { + $this->pane(); + } + if (isset($this->sheetViewXml->selection, $this->sheetViewXml->selection->attributes()->sqref)) { + $this->selection(); + } + } + + private function zoomScale(): void + { + if (isset($this->sheetViewAttributes->zoomScale)) { + $zoomScale = (int) ($this->sheetViewAttributes->zoomScale); + if ($zoomScale <= 0) { + // setZoomScale will throw an Exception if the scale is less than or equals 0 + // that is OK when manually creating documents, but we should be able to read all documents + $zoomScale = 100; + } + + $this->worksheet->getSheetView()->setZoomScale($zoomScale); + } + + if (isset($this->sheetViewAttributes->zoomScaleNormal)) { + $zoomScaleNormal = (int) ($this->sheetViewAttributes->zoomScaleNormal); + if ($zoomScaleNormal <= 0) { + // setZoomScaleNormal will throw an Exception if the scale is less than or equals 0 + // that is OK when manually creating documents, but we should be able to read all documents + $zoomScaleNormal = 100; + } + + $this->worksheet->getSheetView()->setZoomScaleNormal($zoomScaleNormal); + } + } + + private function view(): void + { + if (isset($this->sheetViewAttributes->view)) { + $this->worksheet->getSheetView()->setView((string) $this->sheetViewAttributes->view); + } + } + + private function topLeft(): void + { + if (isset($this->sheetViewAttributes->topLeftCell)) { + $this->worksheet->setTopLeftCell($this->sheetViewAttributes->topLeftCell); + } + } + + private function gridLines(): void + { + if (isset($this->sheetViewAttributes->showGridLines)) { + $this->worksheet->setShowGridLines( + self::boolean((string) $this->sheetViewAttributes->showGridLines) + ); + } + } + + private function headers(): void + { + if (isset($this->sheetViewAttributes->showRowColHeaders)) { + $this->worksheet->setShowRowColHeaders( + self::boolean((string) $this->sheetViewAttributes->showRowColHeaders) + ); + } + } + + private function direction(): void + { + if (isset($this->sheetViewAttributes->rightToLeft)) { + $this->worksheet->setRightToLeft( + self::boolean((string) $this->sheetViewAttributes->rightToLeft) + ); + } + } + + private function showZeros(): void + { + if (isset($this->sheetViewAttributes->showZeros)) { + $this->worksheet->getSheetView()->setShowZeros( + self::boolean((string) $this->sheetViewAttributes->showZeros) + ); + } + } + + private function pane(): void + { + $xSplit = 0; + $ySplit = 0; + $topLeftCell = null; + $paneAttributes = $this->sheetViewXml->pane->attributes(); + + if (isset($paneAttributes->xSplit)) { + $xSplit = (int) ($paneAttributes->xSplit); + } + + if (isset($paneAttributes->ySplit)) { + $ySplit = (int) ($paneAttributes->ySplit); + } + + if (isset($paneAttributes->topLeftCell)) { + $topLeftCell = (string) $paneAttributes->topLeftCell; + } + + $this->worksheet->freezePane( + Coordinate::stringFromColumnIndex($xSplit + 1) . ($ySplit + 1), + $topLeftCell + ); + } + + private function selection(): void + { + $attributes = $this->sheetViewXml->selection->attributes(); + if ($attributes !== null) { + $sqref = (string) $attributes->sqref; + $sqref = explode(' ', $sqref); + $sqref = $sqref[0]; + $this->worksheet->setSelectedCells($sqref); + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Styles.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Styles.php new file mode 100644 index 0000000..72e66a9 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Styles.php @@ -0,0 +1,297 @@ +styleXml = $styleXml; + } + + public function setStyleBaseData(?Theme $theme = null, $styles = [], $cellStyles = []): void + { + self::$theme = $theme; + $this->styles = $styles; + $this->cellStyles = $cellStyles; + } + + public static function readFontStyle(Font $fontStyle, SimpleXMLElement $fontStyleXml): void + { + if (isset($fontStyleXml->name, $fontStyleXml->name['val'])) { + $fontStyle->setName((string) $fontStyleXml->name['val']); + } + if (isset($fontStyleXml->sz, $fontStyleXml->sz['val'])) { + $fontStyle->setSize((float) $fontStyleXml->sz['val']); + } + if (isset($fontStyleXml->b)) { + $fontStyle->setBold(!isset($fontStyleXml->b['val']) || self::boolean((string) $fontStyleXml->b['val'])); + } + if (isset($fontStyleXml->i)) { + $fontStyle->setItalic(!isset($fontStyleXml->i['val']) || self::boolean((string) $fontStyleXml->i['val'])); + } + if (isset($fontStyleXml->strike)) { + $fontStyle->setStrikethrough( + !isset($fontStyleXml->strike['val']) || self::boolean((string) $fontStyleXml->strike['val']) + ); + } + $fontStyle->getColor()->setARGB(self::readColor($fontStyleXml->color)); + + if (isset($fontStyleXml->u) && !isset($fontStyleXml->u['val'])) { + $fontStyle->setUnderline(Font::UNDERLINE_SINGLE); + } elseif (isset($fontStyleXml->u, $fontStyleXml->u['val'])) { + $fontStyle->setUnderline((string) $fontStyleXml->u['val']); + } + + if (isset($fontStyleXml->vertAlign, $fontStyleXml->vertAlign['val'])) { + $verticalAlign = strtolower((string) $fontStyleXml->vertAlign['val']); + if ($verticalAlign === 'superscript') { + $fontStyle->setSuperscript(true); + } elseif ($verticalAlign === 'subscript') { + $fontStyle->setSubscript(true); + } + } + } + + private static function readNumberFormat(NumberFormat $numfmtStyle, SimpleXMLElement $numfmtStyleXml): void + { + if ($numfmtStyleXml->count() === 0) { + return; + } + $numfmt = Xlsx::getAttributes($numfmtStyleXml); + if ($numfmt->count() > 0 && isset($numfmt['formatCode'])) { + $numfmtStyle->setFormatCode((string) $numfmt['formatCode']); + } + } + + public static function readFillStyle(Fill $fillStyle, SimpleXMLElement $fillStyleXml): void + { + if ($fillStyleXml->gradientFill) { + /** @var SimpleXMLElement $gradientFill */ + $gradientFill = $fillStyleXml->gradientFill[0]; + if (!empty($gradientFill['type'])) { + $fillStyle->setFillType((string) $gradientFill['type']); + } + $fillStyle->setRotation((float) ($gradientFill['degree'])); + $gradientFill->registerXPathNamespace('sml', Namespaces::MAIN); + $fillStyle->getStartColor()->setARGB(self::readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=0]'))->color)); + $fillStyle->getEndColor()->setARGB(self::readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=1]'))->color)); + } elseif ($fillStyleXml->patternFill) { + $defaultFillStyle = Fill::FILL_NONE; + if ($fillStyleXml->patternFill->fgColor) { + $fillStyle->getStartColor()->setARGB(self::readColor($fillStyleXml->patternFill->fgColor, true)); + $defaultFillStyle = Fill::FILL_SOLID; + } + if ($fillStyleXml->patternFill->bgColor) { + $fillStyle->getEndColor()->setARGB(self::readColor($fillStyleXml->patternFill->bgColor, true)); + $defaultFillStyle = Fill::FILL_SOLID; + } + + $patternType = (string) $fillStyleXml->patternFill['patternType'] != '' + ? (string) $fillStyleXml->patternFill['patternType'] + : $defaultFillStyle; + + $fillStyle->setFillType($patternType); + } + } + + public static function readBorderStyle(Borders $borderStyle, SimpleXMLElement $borderStyleXml): void + { + $diagonalUp = self::boolean((string) $borderStyleXml['diagonalUp']); + $diagonalDown = self::boolean((string) $borderStyleXml['diagonalDown']); + if (!$diagonalUp && !$diagonalDown) { + $borderStyle->setDiagonalDirection(Borders::DIAGONAL_NONE); + } elseif ($diagonalUp && !$diagonalDown) { + $borderStyle->setDiagonalDirection(Borders::DIAGONAL_UP); + } elseif (!$diagonalUp && $diagonalDown) { + $borderStyle->setDiagonalDirection(Borders::DIAGONAL_DOWN); + } else { + $borderStyle->setDiagonalDirection(Borders::DIAGONAL_BOTH); + } + + self::readBorder($borderStyle->getLeft(), $borderStyleXml->left); + self::readBorder($borderStyle->getRight(), $borderStyleXml->right); + self::readBorder($borderStyle->getTop(), $borderStyleXml->top); + self::readBorder($borderStyle->getBottom(), $borderStyleXml->bottom); + self::readBorder($borderStyle->getDiagonal(), $borderStyleXml->diagonal); + } + + private static function readBorder(Border $border, SimpleXMLElement $borderXml): void + { + if (isset($borderXml['style'])) { + $border->setBorderStyle((string) $borderXml['style']); + } + if (isset($borderXml->color)) { + $border->getColor()->setARGB(self::readColor($borderXml->color)); + } + } + + public static function readAlignmentStyle(Alignment $alignment, SimpleXMLElement $alignmentXml): void + { + $alignment->setHorizontal((string) $alignmentXml['horizontal']); + $alignment->setVertical((string) $alignmentXml['vertical']); + + $textRotation = 0; + if ((int) $alignmentXml['textRotation'] <= 90) { + $textRotation = (int) $alignmentXml['textRotation']; + } elseif ((int) $alignmentXml['textRotation'] > 90) { + $textRotation = 90 - (int) $alignmentXml['textRotation']; + } + + $alignment->setTextRotation((int) $textRotation); + $alignment->setWrapText(self::boolean((string) $alignmentXml['wrapText'])); + $alignment->setShrinkToFit(self::boolean((string) $alignmentXml['shrinkToFit'])); + $alignment->setIndent( + (int) ((string) $alignmentXml['indent']) > 0 ? (int) ((string) $alignmentXml['indent']) : 0 + ); + $alignment->setReadOrder( + (int) ((string) $alignmentXml['readingOrder']) > 0 ? (int) ((string) $alignmentXml['readingOrder']) : 0 + ); + } + + private function readStyle(Style $docStyle, $style): void + { + if ($style->numFmt instanceof SimpleXMLElement) { + self::readNumberFormat($docStyle->getNumberFormat(), $style->numFmt); + } else { + $docStyle->getNumberFormat()->setFormatCode($style->numFmt); + } + + if (isset($style->font)) { + self::readFontStyle($docStyle->getFont(), $style->font); + } + + if (isset($style->fill)) { + self::readFillStyle($docStyle->getFill(), $style->fill); + } + + if (isset($style->border)) { + self::readBorderStyle($docStyle->getBorders(), $style->border); + } + + if (isset($style->alignment->alignment)) { + self::readAlignmentStyle($docStyle->getAlignment(), $style->alignment); + } + + // protection + if (isset($style->protection)) { + self::readProtectionLocked($docStyle, $style); + self::readProtectionHidden($docStyle, $style); + } + + // top-level style settings + if (isset($style->quotePrefix)) { + $docStyle->setQuotePrefix(true); + } + } + + public static function readProtectionLocked(Style $docStyle, $style): void + { + if (isset($style->protection['locked'])) { + if (self::boolean((string) $style->protection['locked'])) { + $docStyle->getProtection()->setLocked(Protection::PROTECTION_PROTECTED); + } else { + $docStyle->getProtection()->setLocked(Protection::PROTECTION_UNPROTECTED); + } + } + } + + public static function readProtectionHidden(Style $docStyle, $style): void + { + if (isset($style->protection['hidden'])) { + if (self::boolean((string) $style->protection['hidden'])) { + $docStyle->getProtection()->setHidden(Protection::PROTECTION_PROTECTED); + } else { + $docStyle->getProtection()->setHidden(Protection::PROTECTION_UNPROTECTED); + } + } + } + + public static function readColor($color, $background = false) + { + if (isset($color['rgb'])) { + return (string) $color['rgb']; + } elseif (isset($color['indexed'])) { + return Color::indexedColor($color['indexed'] - 7, $background)->getARGB(); + } elseif (isset($color['theme'])) { + if (self::$theme !== null) { + $returnColour = self::$theme->getColourByIndex((int) $color['theme']); + if (isset($color['tint'])) { + $tintAdjust = (float) $color['tint']; + $returnColour = Color::changeBrightness($returnColour, $tintAdjust); + } + + return 'FF' . $returnColour; + } + } + + return ($background) ? 'FFFFFFFF' : 'FF000000'; + } + + public function dxfs($readDataOnly = false) + { + $dxfs = []; + if (!$readDataOnly && $this->styleXml) { + // Conditional Styles + if ($this->styleXml->dxfs) { + foreach ($this->styleXml->dxfs->dxf as $dxf) { + $style = new Style(false, true); + $this->readStyle($style, $dxf); + $dxfs[] = $style; + } + } + // Cell Styles + if ($this->styleXml->cellStyles) { + foreach ($this->styleXml->cellStyles->cellStyle as $cellStylex) { + $cellStyle = Xlsx::getAttributes($cellStylex); + if ((int) ($cellStyle['builtinId']) == 0) { + if (isset($this->cellStyles[(int) ($cellStyle['xfId'])])) { + // Set default style + $style = new Style(); + $this->readStyle($style, $this->cellStyles[(int) ($cellStyle['xfId'])]); + + // normal style, currently not using it for anything + } + } + } + } + } + + return $dxfs; + } + + public function styles() + { + return $this->styles; + } + + private static function getArrayItem($array, $key = 0) + { + return $array[$key] ?? null; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Theme.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Theme.php new file mode 100644 index 0000000..1f2b863 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Theme.php @@ -0,0 +1,93 @@ +themeName = $themeName; + $this->colourSchemeName = $colourSchemeName; + $this->colourMap = $colourMap; + } + + /** + * Get Theme Name. + * + * @return string + */ + public function getThemeName() + { + return $this->themeName; + } + + /** + * Get colour Scheme Name. + * + * @return string + */ + public function getColourSchemeName() + { + return $this->colourSchemeName; + } + + /** + * Get colour Map Value by Position. + * + * @param int $index + * + * @return null|string + */ + public function getColourByIndex($index) + { + if (isset($this->colourMap[$index])) { + return $this->colourMap[$index]; + } + + return null; + } + + /** + * Implement PHP __clone to create a deep clone, not just a shallow copy. + */ + public function __clone() + { + $vars = get_object_vars($this); + foreach ($vars as $key => $value) { + if ((is_object($value)) && ($key != '_parent')) { + $this->$key = clone $value; + } else { + $this->$key = $value; + } + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml.php new file mode 100644 index 0000000..8552509 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml.php @@ -0,0 +1,536 @@ +securityScanner = XmlScanner::getInstance($this); + } + + private $fileContents = ''; + + public static function xmlMappings(): array + { + return array_merge( + Style\Fill::FILL_MAPPINGS, + Style\Border::BORDER_MAPPINGS + ); + } + + /** + * Can the current IReader read the file? + */ + public function canRead(string $filename): bool + { + // Office xmlns:o="urn:schemas-microsoft-com:office:office" + // Excel xmlns:x="urn:schemas-microsoft-com:office:excel" + // XML Spreadsheet xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet" + // Spreadsheet component xmlns:c="urn:schemas-microsoft-com:office:component:spreadsheet" + // XML schema xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882" + // XML data type xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882" + // MS-persist recordset xmlns:rs="urn:schemas-microsoft-com:rowset" + // Rowset xmlns:z="#RowsetSchema" + // + + $signature = [ + '/m', $data, $matches)) { + $charSet = strtoupper($matches[1]); + if (1 == preg_match('/^ISO-8859-\d[\dL]?$/i', $charSet)) { + $data = StringHelper::convertEncoding($data, 'UTF-8', $charSet); + $data = preg_replace('/()/um', '$1' . 'UTF-8' . '$2', $data, 1); + } + } + $this->fileContents = $data; + + return $valid; + } + + /** + * Check if the file is a valid SimpleXML. + * + * @param string $filename + * + * @return false|SimpleXMLElement + */ + public function trySimpleXMLLoadString($filename) + { + try { + $xml = simplexml_load_string( + $this->securityScanner->scan($this->fileContents ?: file_get_contents($filename)), + 'SimpleXMLElement', + Settings::getLibXmlLoaderOptions() + ); + } catch (\Exception $e) { + throw new Exception('Cannot load invalid XML file: ' . $filename, 0, $e); + } + $this->fileContents = ''; + + return $xml; + } + + /** + * Reads names of the worksheets from a file, without parsing the whole file to a Spreadsheet object. + * + * @param string $filename + * + * @return array + */ + public function listWorksheetNames($filename) + { + File::assertFile($filename); + if (!$this->canRead($filename)) { + throw new Exception($filename . ' is an Invalid Spreadsheet file.'); + } + + $worksheetNames = []; + + $xml = $this->trySimpleXMLLoadString($filename); + if ($xml === false) { + throw new Exception("Problem reading {$filename}"); + } + + $namespaces = $xml->getNamespaces(true); + + $xml_ss = $xml->children($namespaces['ss']); + foreach ($xml_ss->Worksheet as $worksheet) { + $worksheet_ss = self::getAttributes($worksheet, $namespaces['ss']); + $worksheetNames[] = (string) $worksheet_ss['Name']; + } + + return $worksheetNames; + } + + /** + * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns). + * + * @param string $filename + * + * @return array + */ + public function listWorksheetInfo($filename) + { + File::assertFile($filename); + if (!$this->canRead($filename)) { + throw new Exception($filename . ' is an Invalid Spreadsheet file.'); + } + + $worksheetInfo = []; + + $xml = $this->trySimpleXMLLoadString($filename); + if ($xml === false) { + throw new Exception("Problem reading {$filename}"); + } + + $namespaces = $xml->getNamespaces(true); + + $worksheetID = 1; + $xml_ss = $xml->children($namespaces['ss']); + foreach ($xml_ss->Worksheet as $worksheet) { + $worksheet_ss = self::getAttributes($worksheet, $namespaces['ss']); + + $tmpInfo = []; + $tmpInfo['worksheetName'] = ''; + $tmpInfo['lastColumnLetter'] = 'A'; + $tmpInfo['lastColumnIndex'] = 0; + $tmpInfo['totalRows'] = 0; + $tmpInfo['totalColumns'] = 0; + + $tmpInfo['worksheetName'] = "Worksheet_{$worksheetID}"; + if (isset($worksheet_ss['Name'])) { + $tmpInfo['worksheetName'] = (string) $worksheet_ss['Name']; + } + + if (isset($worksheet->Table->Row)) { + $rowIndex = 0; + + foreach ($worksheet->Table->Row as $rowData) { + $columnIndex = 0; + $rowHasData = false; + + foreach ($rowData->Cell as $cell) { + if (isset($cell->Data)) { + $tmpInfo['lastColumnIndex'] = max($tmpInfo['lastColumnIndex'], $columnIndex); + $rowHasData = true; + } + + ++$columnIndex; + } + + ++$rowIndex; + + if ($rowHasData) { + $tmpInfo['totalRows'] = max($tmpInfo['totalRows'], $rowIndex); + } + } + } + + $tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1); + $tmpInfo['totalColumns'] = $tmpInfo['lastColumnIndex'] + 1; + + $worksheetInfo[] = $tmpInfo; + ++$worksheetID; + } + + return $worksheetInfo; + } + + /** + * Loads Spreadsheet from file. + * + * @return Spreadsheet + */ + public function load(string $filename, int $flags = 0) + { + $this->processFlags($flags); + + // Create new Spreadsheet + $spreadsheet = new Spreadsheet(); + $spreadsheet->removeSheetByIndex(0); + + // Load into this instance + return $this->loadIntoExisting($filename, $spreadsheet); + } + + /** + * Loads from file into Spreadsheet instance. + * + * @param string $filename + * + * @return Spreadsheet + */ + public function loadIntoExisting($filename, Spreadsheet $spreadsheet) + { + File::assertFile($filename); + if (!$this->canRead($filename)) { + throw new Exception($filename . ' is an Invalid Spreadsheet file.'); + } + + $xml = $this->trySimpleXMLLoadString($filename); + if ($xml === false) { + throw new Exception("Problem reading {$filename}"); + } + + $namespaces = $xml->getNamespaces(true); + + (new Properties($spreadsheet))->readProperties($xml, $namespaces); + + $this->styles = (new Style())->parseStyles($xml, $namespaces); + + $worksheetID = 0; + $xml_ss = $xml->children($namespaces['ss']); + + /** @var null|SimpleXMLElement $worksheetx */ + foreach ($xml_ss->Worksheet as $worksheetx) { + $worksheet = $worksheetx ?? new SimpleXMLElement(''); + $worksheet_ss = self::getAttributes($worksheet, $namespaces['ss']); + + if ( + isset($this->loadSheetsOnly, $worksheet_ss['Name']) && + (!in_array($worksheet_ss['Name'], $this->loadSheetsOnly)) + ) { + continue; + } + + // Create new Worksheet + $spreadsheet->createSheet(); + $spreadsheet->setActiveSheetIndex($worksheetID); + $worksheetName = ''; + if (isset($worksheet_ss['Name'])) { + $worksheetName = (string) $worksheet_ss['Name']; + // Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in + // formula cells... during the load, all formulae should be correct, and we're simply bringing + // the worksheet name in line with the formula, not the reverse + $spreadsheet->getActiveSheet()->setTitle($worksheetName, false, false); + } + + // locally scoped defined names + if (isset($worksheet->Names[0])) { + foreach ($worksheet->Names[0] as $definedName) { + $definedName_ss = self::getAttributes($definedName, $namespaces['ss']); + $name = (string) $definedName_ss['Name']; + $definedValue = (string) $definedName_ss['RefersTo']; + $convertedValue = AddressHelper::convertFormulaToA1($definedValue); + if ($convertedValue[0] === '=') { + $convertedValue = substr($convertedValue, 1); + } + $spreadsheet->addDefinedName(DefinedName::createInstance($name, $spreadsheet->getActiveSheet(), $convertedValue, true)); + } + } + + $columnID = 'A'; + if (isset($worksheet->Table->Column)) { + foreach ($worksheet->Table->Column as $columnData) { + $columnData_ss = self::getAttributes($columnData, $namespaces['ss']); + if (isset($columnData_ss['Index'])) { + $columnID = Coordinate::stringFromColumnIndex((int) $columnData_ss['Index']); + } + if (isset($columnData_ss['Width'])) { + $columnWidth = $columnData_ss['Width']; + $spreadsheet->getActiveSheet()->getColumnDimension($columnID)->setWidth($columnWidth / 5.4); + } + ++$columnID; + } + } + + $rowID = 1; + if (isset($worksheet->Table->Row)) { + $additionalMergedCells = 0; + foreach ($worksheet->Table->Row as $rowData) { + $rowHasData = false; + $row_ss = self::getAttributes($rowData, $namespaces['ss']); + if (isset($row_ss['Index'])) { + $rowID = (int) $row_ss['Index']; + } + + $columnID = 'A'; + foreach ($rowData->Cell as $cell) { + $cell_ss = self::getAttributes($cell, $namespaces['ss']); + if (isset($cell_ss['Index'])) { + $columnID = Coordinate::stringFromColumnIndex((int) $cell_ss['Index']); + } + $cellRange = $columnID . $rowID; + + if ($this->getReadFilter() !== null) { + if (!$this->getReadFilter()->readCell($columnID, $rowID, $worksheetName)) { + ++$columnID; + + continue; + } + } + + if (isset($cell_ss['HRef'])) { + $spreadsheet->getActiveSheet()->getCell($cellRange)->getHyperlink()->setUrl((string) $cell_ss['HRef']); + } + + if ((isset($cell_ss['MergeAcross'])) || (isset($cell_ss['MergeDown']))) { + $columnTo = $columnID; + if (isset($cell_ss['MergeAcross'])) { + $additionalMergedCells += (int) $cell_ss['MergeAcross']; + $columnTo = Coordinate::stringFromColumnIndex((int) (Coordinate::columnIndexFromString($columnID) + $cell_ss['MergeAcross'])); + } + $rowTo = $rowID; + if (isset($cell_ss['MergeDown'])) { + $rowTo = $rowTo + $cell_ss['MergeDown']; + } + $cellRange .= ':' . $columnTo . $rowTo; + $spreadsheet->getActiveSheet()->mergeCells($cellRange); + } + + $hasCalculatedValue = false; + $cellDataFormula = ''; + if (isset($cell_ss['Formula'])) { + $cellDataFormula = $cell_ss['Formula']; + $hasCalculatedValue = true; + } + if (isset($cell->Data)) { + $cellData = $cell->Data; + $cellValue = (string) $cellData; + $type = DataType::TYPE_NULL; + $cellData_ss = self::getAttributes($cellData, $namespaces['ss']); + if (isset($cellData_ss['Type'])) { + $cellDataType = $cellData_ss['Type']; + switch ($cellDataType) { + /* + const TYPE_STRING = 's'; + const TYPE_FORMULA = 'f'; + const TYPE_NUMERIC = 'n'; + const TYPE_BOOL = 'b'; + const TYPE_NULL = 'null'; + const TYPE_INLINE = 'inlineStr'; + const TYPE_ERROR = 'e'; + */ + case 'String': + $type = DataType::TYPE_STRING; + + break; + case 'Number': + $type = DataType::TYPE_NUMERIC; + $cellValue = (float) $cellValue; + if (floor($cellValue) == $cellValue) { + $cellValue = (int) $cellValue; + } + + break; + case 'Boolean': + $type = DataType::TYPE_BOOL; + $cellValue = ($cellValue != 0); + + break; + case 'DateTime': + $type = DataType::TYPE_NUMERIC; + $dateTime = new DateTime($cellValue, new DateTimeZone('UTC')); + $cellValue = Date::PHPToExcel($dateTime); + + break; + case 'Error': + $type = DataType::TYPE_ERROR; + $hasCalculatedValue = false; + + break; + } + } + + if ($hasCalculatedValue) { + $type = DataType::TYPE_FORMULA; + $columnNumber = Coordinate::columnIndexFromString($columnID); + $cellDataFormula = AddressHelper::convertFormulaToA1($cellDataFormula, $rowID, $columnNumber); + } + + $spreadsheet->getActiveSheet()->getCell($columnID . $rowID)->setValueExplicit((($hasCalculatedValue) ? $cellDataFormula : $cellValue), $type); + if ($hasCalculatedValue) { + $spreadsheet->getActiveSheet()->getCell($columnID . $rowID)->setCalculatedValue($cellValue); + } + $rowHasData = true; + } + + if (isset($cell->Comment)) { + $this->parseCellComment($cell->Comment, $namespaces, $spreadsheet, $columnID, $rowID); + } + + if (isset($cell_ss['StyleID'])) { + $style = (string) $cell_ss['StyleID']; + if ((isset($this->styles[$style])) && (!empty($this->styles[$style]))) { + //if (!$spreadsheet->getActiveSheet()->cellExists($columnID . $rowID)) { + // $spreadsheet->getActiveSheet()->getCell($columnID . $rowID)->setValue(null); + //} + $spreadsheet->getActiveSheet()->getStyle($cellRange) + ->applyFromArray($this->styles[$style]); + } + } + ++$columnID; + while ($additionalMergedCells > 0) { + ++$columnID; + --$additionalMergedCells; + } + } + + if ($rowHasData) { + if (isset($row_ss['Height'])) { + $rowHeight = $row_ss['Height']; + $spreadsheet->getActiveSheet()->getRowDimension($rowID)->setRowHeight((float) $rowHeight); + } + } + + ++$rowID; + } + + if (isset($namespaces['x'])) { + $xmlX = $worksheet->children($namespaces['x']); + if (isset($xmlX->WorksheetOptions)) { + (new PageSettings($xmlX, $namespaces))->loadPageSettings($spreadsheet); + } + } + } + ++$worksheetID; + } + + // Globally scoped defined names + $activeWorksheet = $spreadsheet->setActiveSheetIndex(0); + if (isset($xml->Names[0])) { + foreach ($xml->Names[0] as $definedName) { + $definedName_ss = self::getAttributes($definedName, $namespaces['ss']); + $name = (string) $definedName_ss['Name']; + $definedValue = (string) $definedName_ss['RefersTo']; + $convertedValue = AddressHelper::convertFormulaToA1($definedValue); + if ($convertedValue[0] === '=') { + $convertedValue = substr($convertedValue, 1); + } + $spreadsheet->addDefinedName(DefinedName::createInstance($name, $activeWorksheet, $convertedValue)); + } + } + + // Return + return $spreadsheet; + } + + protected function parseCellComment( + SimpleXMLElement $comment, + array $namespaces, + Spreadsheet $spreadsheet, + string $columnID, + int $rowID + ): void { + $commentAttributes = $comment->attributes($namespaces['ss']); + $author = 'unknown'; + if (isset($commentAttributes->Author)) { + $author = (string) $commentAttributes->Author; + } + + $node = $comment->Data->asXML(); + $annotation = strip_tags((string) $node); + $spreadsheet->getActiveSheet()->getComment($columnID . $rowID) + ->setAuthor($author) + ->setText($this->parseRichText($annotation)); + } + + protected function parseRichText(string $annotation): RichText + { + $value = new RichText(); + + $value->createText($annotation); + + return $value; + } + + private static function getAttributes(?SimpleXMLElement $simple, string $node): SimpleXMLElement + { + return ($simple === null) + ? new SimpleXMLElement('') + : ($simple->attributes($node) ?? new SimpleXMLElement('')); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/PageSettings.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/PageSettings.php new file mode 100644 index 0000000..c0caca3 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/PageSettings.php @@ -0,0 +1,134 @@ +pageSetup($xmlX, $namespaces, $this->getPrintDefaults()); + $this->printSettings = $this->printSetup($xmlX, $printSettings); + } + + public function loadPageSettings(Spreadsheet $spreadsheet): void + { + $spreadsheet->getActiveSheet()->getPageSetup() + ->setPaperSize($this->printSettings->paperSize) + ->setOrientation($this->printSettings->orientation) + ->setScale($this->printSettings->scale) + ->setVerticalCentered($this->printSettings->verticalCentered) + ->setHorizontalCentered($this->printSettings->horizontalCentered) + ->setPageOrder($this->printSettings->printOrder); + $spreadsheet->getActiveSheet()->getPageMargins() + ->setTop($this->printSettings->topMargin) + ->setHeader($this->printSettings->headerMargin) + ->setLeft($this->printSettings->leftMargin) + ->setRight($this->printSettings->rightMargin) + ->setBottom($this->printSettings->bottomMargin) + ->setFooter($this->printSettings->footerMargin); + } + + private function getPrintDefaults(): stdClass + { + return (object) [ + 'paperSize' => 9, + 'orientation' => PageSetup::ORIENTATION_DEFAULT, + 'scale' => 100, + 'horizontalCentered' => false, + 'verticalCentered' => false, + 'printOrder' => PageSetup::PAGEORDER_DOWN_THEN_OVER, + 'topMargin' => 0.75, + 'headerMargin' => 0.3, + 'leftMargin' => 0.7, + 'rightMargin' => 0.7, + 'bottomMargin' => 0.75, + 'footerMargin' => 0.3, + ]; + } + + private function pageSetup(SimpleXMLElement $xmlX, array $namespaces, stdClass $printDefaults): stdClass + { + if (isset($xmlX->WorksheetOptions->PageSetup)) { + foreach ($xmlX->WorksheetOptions->PageSetup as $pageSetupData) { + foreach ($pageSetupData as $pageSetupKey => $pageSetupValue) { + $pageSetupAttributes = $pageSetupValue->attributes($namespaces['x']); + if (!$pageSetupAttributes) { + continue; + } + + switch ($pageSetupKey) { + case 'Layout': + $this->setLayout($printDefaults, $pageSetupAttributes); + + break; + case 'Header': + $printDefaults->headerMargin = (float) $pageSetupAttributes->Margin ?: 1.0; + + break; + case 'Footer': + $printDefaults->footerMargin = (float) $pageSetupAttributes->Margin ?: 1.0; + + break; + case 'PageMargins': + $this->setMargins($printDefaults, $pageSetupAttributes); + + break; + } + } + } + } + + return $printDefaults; + } + + private function printSetup(SimpleXMLElement $xmlX, stdClass $printDefaults): stdClass + { + if (isset($xmlX->WorksheetOptions->Print)) { + foreach ($xmlX->WorksheetOptions->Print as $printData) { + foreach ($printData as $printKey => $printValue) { + switch ($printKey) { + case 'LeftToRight': + $printDefaults->printOrder = PageSetup::PAGEORDER_OVER_THEN_DOWN; + + break; + case 'PaperSizeIndex': + $printDefaults->paperSize = (int) $printValue ?: 9; + + break; + case 'Scale': + $printDefaults->scale = (int) $printValue ?: 100; + + break; + } + } + } + } + + return $printDefaults; + } + + private function setLayout(stdClass $printDefaults, SimpleXMLElement $pageSetupAttributes): void + { + $printDefaults->orientation = (string) strtolower($pageSetupAttributes->Orientation ?? '') ?: PageSetup::ORIENTATION_PORTRAIT; + $printDefaults->horizontalCentered = (bool) $pageSetupAttributes->CenterHorizontal ?: false; + $printDefaults->verticalCentered = (bool) $pageSetupAttributes->CenterVertical ?: false; + } + + private function setMargins(stdClass $printDefaults, SimpleXMLElement $pageSetupAttributes): void + { + $printDefaults->leftMargin = (float) $pageSetupAttributes->Left ?: 1.0; + $printDefaults->rightMargin = (float) $pageSetupAttributes->Right ?: 1.0; + $printDefaults->topMargin = (float) $pageSetupAttributes->Top ?: 1.0; + $printDefaults->bottomMargin = (float) $pageSetupAttributes->Bottom ?: 1.0; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Properties.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Properties.php new file mode 100644 index 0000000..1c3e421 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Properties.php @@ -0,0 +1,157 @@ +spreadsheet = $spreadsheet; + } + + public function readProperties(SimpleXMLElement $xml, array $namespaces): void + { + $this->readStandardProperties($xml); + $this->readCustomProperties($xml, $namespaces); + } + + protected function readStandardProperties(SimpleXMLElement $xml): void + { + if (isset($xml->DocumentProperties[0])) { + $docProps = $this->spreadsheet->getProperties(); + + foreach ($xml->DocumentProperties[0] as $propertyName => $propertyValue) { + $propertyValue = (string) $propertyValue; + + $this->processStandardProperty($docProps, $propertyName, $propertyValue); + } + } + } + + protected function readCustomProperties(SimpleXMLElement $xml, array $namespaces): void + { + if (isset($xml->CustomDocumentProperties)) { + $docProps = $this->spreadsheet->getProperties(); + + foreach ($xml->CustomDocumentProperties[0] as $propertyName => $propertyValue) { + $propertyAttributes = self::getAttributes($propertyValue, $namespaces['dt']); + $propertyName = preg_replace_callback('/_x([0-9a-f]{4})_/i', [$this, 'hex2str'], $propertyName); + + $this->processCustomProperty($docProps, $propertyName, $propertyValue, $propertyAttributes); + } + } + } + + protected function processStandardProperty( + DocumentProperties $docProps, + string $propertyName, + string $stringValue + ): void { + switch ($propertyName) { + case 'Title': + $docProps->setTitle($stringValue); + + break; + case 'Subject': + $docProps->setSubject($stringValue); + + break; + case 'Author': + $docProps->setCreator($stringValue); + + break; + case 'Created': + $docProps->setCreated($stringValue); + + break; + case 'LastAuthor': + $docProps->setLastModifiedBy($stringValue); + + break; + case 'LastSaved': + $docProps->setModified($stringValue); + + break; + case 'Company': + $docProps->setCompany($stringValue); + + break; + case 'Category': + $docProps->setCategory($stringValue); + + break; + case 'Manager': + $docProps->setManager($stringValue); + + break; + case 'Keywords': + $docProps->setKeywords($stringValue); + + break; + case 'Description': + $docProps->setDescription($stringValue); + + break; + } + } + + protected function processCustomProperty( + DocumentProperties $docProps, + string $propertyName, + ?SimpleXMLElement $propertyValue, + SimpleXMLElement $propertyAttributes + ): void { + $propertyType = DocumentProperties::PROPERTY_TYPE_UNKNOWN; + + switch ((string) $propertyAttributes) { + case 'string': + $propertyType = DocumentProperties::PROPERTY_TYPE_STRING; + $propertyValue = trim((string) $propertyValue); + + break; + case 'boolean': + $propertyType = DocumentProperties::PROPERTY_TYPE_BOOLEAN; + $propertyValue = (bool) $propertyValue; + + break; + case 'integer': + $propertyType = DocumentProperties::PROPERTY_TYPE_INTEGER; + $propertyValue = (int) $propertyValue; + + break; + case 'float': + $propertyType = DocumentProperties::PROPERTY_TYPE_FLOAT; + $propertyValue = (float) $propertyValue; + + break; + case 'dateTime.tz': + $propertyType = DocumentProperties::PROPERTY_TYPE_DATE; + $propertyValue = trim((string) $propertyValue); + + break; + } + + $docProps->setCustomProperty($propertyName, $propertyValue, $propertyType); + } + + protected function hex2str(array $hex): string + { + return mb_chr((int) hexdec($hex[1]), 'UTF-8'); + } + + private static function getAttributes(?SimpleXMLElement $simple, string $node): SimpleXMLElement + { + return ($simple === null) + ? new SimpleXMLElement('') + : ($simple->attributes($node) ?? new SimpleXMLElement('')); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style.php new file mode 100644 index 0000000..0e3cd16 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style.php @@ -0,0 +1,83 @@ +Styles)) { + return []; + } + + $alignmentStyleParser = new Style\Alignment(); + $borderStyleParser = new Style\Border(); + $fontStyleParser = new Style\Font(); + $fillStyleParser = new Style\Fill(); + $numberFormatStyleParser = new Style\NumberFormat(); + + foreach ($xml->Styles[0] as $style) { + $style_ss = self::getAttributes($style, $namespaces['ss']); + $styleID = (string) $style_ss['ID']; + $this->styles[$styleID] = $this->styles['Default'] ?? []; + + $alignment = $border = $font = $fill = $numberFormat = []; + + foreach ($style as $styleType => $styleDatax) { + $styleData = $styleDatax ?? new SimpleXMLElement(''); + $styleAttributes = $styleData->attributes($namespaces['ss']); + + switch ($styleType) { + case 'Alignment': + if ($styleAttributes) { + $alignment = $alignmentStyleParser->parseStyle($styleAttributes); + } + + break; + case 'Borders': + $border = $borderStyleParser->parseStyle($styleData, $namespaces); + + break; + case 'Font': + if ($styleAttributes) { + $font = $fontStyleParser->parseStyle($styleAttributes); + } + + break; + case 'Interior': + if ($styleAttributes) { + $fill = $fillStyleParser->parseStyle($styleAttributes); + } + + break; + case 'NumberFormat': + if ($styleAttributes) { + $numberFormat = $numberFormatStyleParser->parseStyle($styleAttributes); + } + + break; + } + } + + $this->styles[$styleID] = array_merge($alignment, $border, $font, $fill, $numberFormat); + } + + return $this->styles; + } + + protected static function getAttributes(?SimpleXMLElement $simple, string $node): SimpleXMLElement + { + return ($simple === null) + ? new SimpleXMLElement('') + : ($simple->attributes($node) ?? new SimpleXMLElement('')); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Alignment.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Alignment.php new file mode 100644 index 0000000..d136354 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Alignment.php @@ -0,0 +1,58 @@ + $styleAttributeValue) { + $styleAttributeValue = (string) $styleAttributeValue; + switch ($styleAttributeKey) { + case 'Vertical': + if (self::identifyFixedStyleValue(self::VERTICAL_ALIGNMENT_STYLES, $styleAttributeValue)) { + $style['alignment']['vertical'] = $styleAttributeValue; + } + + break; + case 'Horizontal': + if (self::identifyFixedStyleValue(self::HORIZONTAL_ALIGNMENT_STYLES, $styleAttributeValue)) { + $style['alignment']['horizontal'] = $styleAttributeValue; + } + + break; + case 'WrapText': + $style['alignment']['wrapText'] = true; + + break; + case 'Rotate': + $style['alignment']['textRotation'] = $styleAttributeValue; + + break; + } + } + + return $style; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Border.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Border.php new file mode 100644 index 0000000..8aefd9c --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Border.php @@ -0,0 +1,98 @@ + [ + '1continuous' => BorderStyle::BORDER_THIN, + '1dash' => BorderStyle::BORDER_DASHED, + '1dashdot' => BorderStyle::BORDER_DASHDOT, + '1dashdotdot' => BorderStyle::BORDER_DASHDOTDOT, + '1dot' => BorderStyle::BORDER_DOTTED, + '1double' => BorderStyle::BORDER_DOUBLE, + '2continuous' => BorderStyle::BORDER_MEDIUM, + '2dash' => BorderStyle::BORDER_MEDIUMDASHED, + '2dashdot' => BorderStyle::BORDER_MEDIUMDASHDOT, + '2dashdotdot' => BorderStyle::BORDER_MEDIUMDASHDOTDOT, + '2dot' => BorderStyle::BORDER_DOTTED, + '2double' => BorderStyle::BORDER_DOUBLE, + '3continuous' => BorderStyle::BORDER_THICK, + '3dash' => BorderStyle::BORDER_MEDIUMDASHED, + '3dashdot' => BorderStyle::BORDER_MEDIUMDASHDOT, + '3dashdotdot' => BorderStyle::BORDER_MEDIUMDASHDOTDOT, + '3dot' => BorderStyle::BORDER_DOTTED, + '3double' => BorderStyle::BORDER_DOUBLE, + ], + ]; + + public function parseStyle(SimpleXMLElement $styleData, array $namespaces): array + { + $style = []; + + $diagonalDirection = ''; + $borderPosition = ''; + foreach ($styleData->Border as $borderStyle) { + $borderAttributes = self::getAttributes($borderStyle, $namespaces['ss']); + $thisBorder = []; + $styleType = (string) $borderAttributes->Weight; + $styleType .= strtolower((string) $borderAttributes->LineStyle); + $thisBorder['borderStyle'] = self::BORDER_MAPPINGS['borderStyle'][$styleType] ?? BorderStyle::BORDER_NONE; + + foreach ($borderAttributes as $borderStyleKey => $borderStyleValuex) { + $borderStyleValue = (string) $borderStyleValuex; + switch ($borderStyleKey) { + case 'Position': + [$borderPosition, $diagonalDirection] = + $this->parsePosition($borderStyleValue, $diagonalDirection); + + break; + case 'Color': + $borderColour = substr($borderStyleValue, 1); + $thisBorder['color']['rgb'] = $borderColour; + + break; + } + } + + if ($borderPosition) { + $style['borders'][$borderPosition] = $thisBorder; + } elseif ($diagonalDirection) { + $style['borders']['diagonalDirection'] = $diagonalDirection; + $style['borders']['diagonal'] = $thisBorder; + } + } + + return $style; + } + + protected function parsePosition(string $borderStyleValue, string $diagonalDirection): array + { + $borderStyleValue = strtolower($borderStyleValue); + + if (in_array($borderStyleValue, self::BORDER_POSITIONS)) { + $borderPosition = $borderStyleValue; + } elseif ($borderStyleValue === 'diagonalleft') { + $diagonalDirection = $diagonalDirection ? Borders::DIAGONAL_BOTH : Borders::DIAGONAL_DOWN; + } elseif ($borderStyleValue === 'diagonalright') { + $diagonalDirection = $diagonalDirection ? Borders::DIAGONAL_BOTH : Borders::DIAGONAL_UP; + } + + return [$borderPosition ?? null, $diagonalDirection]; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Fill.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Fill.php new file mode 100644 index 0000000..9a61215 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Fill.php @@ -0,0 +1,63 @@ + [ + 'solid' => FillStyles::FILL_SOLID, + 'gray75' => FillStyles::FILL_PATTERN_DARKGRAY, + 'gray50' => FillStyles::FILL_PATTERN_MEDIUMGRAY, + 'gray25' => FillStyles::FILL_PATTERN_LIGHTGRAY, + 'gray125' => FillStyles::FILL_PATTERN_GRAY125, + 'gray0625' => FillStyles::FILL_PATTERN_GRAY0625, + 'horzstripe' => FillStyles::FILL_PATTERN_DARKHORIZONTAL, // horizontal stripe + 'vertstripe' => FillStyles::FILL_PATTERN_DARKVERTICAL, // vertical stripe + 'reversediagstripe' => FillStyles::FILL_PATTERN_DARKUP, // reverse diagonal stripe + 'diagstripe' => FillStyles::FILL_PATTERN_DARKDOWN, // diagonal stripe + 'diagcross' => FillStyles::FILL_PATTERN_DARKGRID, // diagoanl crosshatch + 'thickdiagcross' => FillStyles::FILL_PATTERN_DARKTRELLIS, // thick diagonal crosshatch + 'thinhorzstripe' => FillStyles::FILL_PATTERN_LIGHTHORIZONTAL, + 'thinvertstripe' => FillStyles::FILL_PATTERN_LIGHTVERTICAL, + 'thinreversediagstripe' => FillStyles::FILL_PATTERN_LIGHTUP, + 'thindiagstripe' => FillStyles::FILL_PATTERN_LIGHTDOWN, + 'thinhorzcross' => FillStyles::FILL_PATTERN_LIGHTGRID, // thin horizontal crosshatch + 'thindiagcross' => FillStyles::FILL_PATTERN_LIGHTTRELLIS, // thin diagonal crosshatch + ], + ]; + + public function parseStyle(SimpleXMLElement $styleAttributes): array + { + $style = []; + + foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValuex) { + $styleAttributeValue = (string) $styleAttributeValuex; + switch ($styleAttributeKey) { + case 'Color': + $style['fill']['endColor']['rgb'] = substr($styleAttributeValue, 1); + $style['fill']['startColor']['rgb'] = substr($styleAttributeValue, 1); + + break; + case 'PatternColor': + $style['fill']['startColor']['rgb'] = substr($styleAttributeValue, 1); + + break; + case 'Pattern': + $lcStyleAttributeValue = strtolower((string) $styleAttributeValue); + $style['fill']['fillType'] + = self::FILL_MAPPINGS['fillType'][$lcStyleAttributeValue] ?? FillStyles::FILL_NONE; + + break; + } + } + + return $style; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Font.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Font.php new file mode 100644 index 0000000..16ab44d --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Font.php @@ -0,0 +1,79 @@ + $styleAttributeValue) { + $styleAttributeValue = (string) $styleAttributeValue; + switch ($styleAttributeKey) { + case 'FontName': + $style['font']['name'] = $styleAttributeValue; + + break; + case 'Size': + $style['font']['size'] = $styleAttributeValue; + + break; + case 'Color': + $style['font']['color']['rgb'] = substr($styleAttributeValue, 1); + + break; + case 'Bold': + $style['font']['bold'] = true; + + break; + case 'Italic': + $style['font']['italic'] = true; + + break; + case 'Underline': + $style = $this->parseUnderline($style, $styleAttributeValue); + + break; + case 'VerticalAlign': + $style = $this->parseVerticalAlign($style, $styleAttributeValue); + + break; + } + } + + return $style; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/NumberFormat.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/NumberFormat.php new file mode 100644 index 0000000..a31aa9e --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/NumberFormat.php @@ -0,0 +1,33 @@ + $styleAttributeValue) { + $styleAttributeValue = str_replace($fromFormats, $toFormats, $styleAttributeValue); + + switch ($styleAttributeValue) { + case 'Short Date': + $styleAttributeValue = 'dd/mm/yyyy'; + + break; + } + + if ($styleAttributeValue > '') { + $style['numberFormat']['formatCode'] = $styleAttributeValue; + } + } + + return $style; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/StyleBase.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/StyleBase.php new file mode 100644 index 0000000..fc9ace8 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/StyleBase.php @@ -0,0 +1,32 @@ +
        ') + : ($simple->attributes($node) ?? new SimpleXMLElement('')); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/ReferenceHelper.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/ReferenceHelper.php new file mode 100644 index 0000000..ba4eefb --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/ReferenceHelper.php @@ -0,0 +1,1028 @@ += ($beforeRow + $numberOfRows)) && + ($cellRow < $beforeRow) + ) { + return true; + } elseif ( + $numberOfCols < 0 && + ($cellColumnIndex >= ($beforeColumnIndex + $numberOfCols)) && + ($cellColumnIndex < $beforeColumnIndex) + ) { + return true; + } + + return false; + } + + /** + * Update page breaks when inserting/deleting rows/columns. + * + * @param Worksheet $worksheet The worksheet that we're editing + * @param string $beforeCellAddress Insert/Delete before this cell address (e.g. 'A1') + * @param int $beforeColumnIndex Index number of the column we're inserting/deleting before + * @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion) + * @param int $beforeRow Number of the row we're inserting/deleting before + * @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion) + */ + protected function adjustPageBreaks(Worksheet $worksheet, $beforeCellAddress, $beforeColumnIndex, $numberOfColumns, $beforeRow, $numberOfRows): void + { + $aBreaks = $worksheet->getBreaks(); + ($numberOfColumns > 0 || $numberOfRows > 0) ? + uksort($aBreaks, ['self', 'cellReverseSort']) : uksort($aBreaks, ['self', 'cellSort']); + + foreach ($aBreaks as $key => $value) { + if (self::cellAddressInDeleteRange($key, $beforeRow, $numberOfRows, $beforeColumnIndex, $numberOfColumns)) { + // If we're deleting, then clear any defined breaks that are within the range + // of rows/columns that we're deleting + $worksheet->setBreak($key, Worksheet::BREAK_NONE); + } else { + // Otherwise update any affected breaks by inserting a new break at the appropriate point + // and removing the old affected break + $newReference = $this->updateCellReference($key, $beforeCellAddress, $numberOfColumns, $numberOfRows); + if ($key != $newReference) { + $worksheet->setBreak($newReference, $value) + ->setBreak($key, Worksheet::BREAK_NONE); + } + } + } + } + + /** + * Update cell comments when inserting/deleting rows/columns. + * + * @param Worksheet $worksheet The worksheet that we're editing + * @param string $beforeCellAddress Insert/Delete before this cell address (e.g. 'A1') + * @param int $beforeColumnIndex Index number of the column we're inserting/deleting before + * @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion) + * @param int $beforeRow Number of the row we're inserting/deleting before + * @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion) + */ + protected function adjustComments($worksheet, $beforeCellAddress, $beforeColumnIndex, $numberOfColumns, $beforeRow, $numberOfRows): void + { + $aComments = $worksheet->getComments(); + $aNewComments = []; // the new array of all comments + + foreach ($aComments as $key => &$value) { + // Any comments inside a deleted range will be ignored + if (!self::cellAddressInDeleteRange($key, $beforeRow, $numberOfRows, $beforeColumnIndex, $numberOfColumns)) { + // Otherwise build a new array of comments indexed by the adjusted cell reference + $newReference = $this->updateCellReference($key, $beforeCellAddress, $numberOfColumns, $numberOfRows); + $aNewComments[$newReference] = $value; + } + } + // Replace the comments array with the new set of comments + $worksheet->setComments($aNewComments); + } + + /** + * Update hyperlinks when inserting/deleting rows/columns. + * + * @param Worksheet $worksheet The worksheet that we're editing + * @param string $beforeCellAddress Insert/Delete before this cell address (e.g. 'A1') + * @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion) + * @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion) + */ + protected function adjustHyperlinks($worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows): void + { + $aHyperlinkCollection = $worksheet->getHyperlinkCollection(); + ($numberOfColumns > 0 || $numberOfRows > 0) ? + uksort($aHyperlinkCollection, ['self', 'cellReverseSort']) : uksort($aHyperlinkCollection, ['self', 'cellSort']); + + foreach ($aHyperlinkCollection as $key => $value) { + $newReference = $this->updateCellReference($key, $beforeCellAddress, $numberOfColumns, $numberOfRows); + if ($key != $newReference) { + $worksheet->setHyperlink($newReference, $value); + $worksheet->setHyperlink($key, null); + } + } + } + + /** + * Update data validations when inserting/deleting rows/columns. + * + * @param Worksheet $worksheet The worksheet that we're editing + * @param string $before Insert/Delete before this cell address (e.g. 'A1') + * @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion) + * @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion) + */ + protected function adjustDataValidations(Worksheet $worksheet, $before, $numberOfColumns, $numberOfRows): void + { + $aDataValidationCollection = $worksheet->getDataValidationCollection(); + ($numberOfColumns > 0 || $numberOfRows > 0) ? + uksort($aDataValidationCollection, ['self', 'cellReverseSort']) : uksort($aDataValidationCollection, ['self', 'cellSort']); + + foreach ($aDataValidationCollection as $key => $value) { + $newReference = $this->updateCellReference($key, $before, $numberOfColumns, $numberOfRows); + if ($key != $newReference) { + $worksheet->setDataValidation($newReference, $value); + $worksheet->setDataValidation($key, null); + } + } + } + + /** + * Update merged cells when inserting/deleting rows/columns. + * + * @param Worksheet $worksheet The worksheet that we're editing + * @param string $beforeCellAddress Insert/Delete before this cell address (e.g. 'A1') + * @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion) + * @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion) + */ + protected function adjustMergeCells(Worksheet $worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows): void + { + $aMergeCells = $worksheet->getMergeCells(); + $aNewMergeCells = []; // the new array of all merge cells + foreach ($aMergeCells as $key => &$value) { + $newReference = $this->updateCellReference($key, $beforeCellAddress, $numberOfColumns, $numberOfRows); + $aNewMergeCells[$newReference] = $newReference; + } + $worksheet->setMergeCells($aNewMergeCells); // replace the merge cells array + } + + /** + * Update protected cells when inserting/deleting rows/columns. + * + * @param Worksheet $worksheet The worksheet that we're editing + * @param string $beforeCellAddress Insert/Delete before this cell address (e.g. 'A1') + * @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion) + * @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion) + */ + protected function adjustProtectedCells(Worksheet $worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows): void + { + $aProtectedCells = $worksheet->getProtectedCells(); + ($numberOfColumns > 0 || $numberOfRows > 0) ? + uksort($aProtectedCells, ['self', 'cellReverseSort']) : uksort($aProtectedCells, ['self', 'cellSort']); + foreach ($aProtectedCells as $key => $value) { + $newReference = $this->updateCellReference($key, $beforeCellAddress, $numberOfColumns, $numberOfRows); + if ($key != $newReference) { + $worksheet->protectCells($newReference, $value, true); + $worksheet->unprotectCells($key); + } + } + } + + /** + * Update column dimensions when inserting/deleting rows/columns. + * + * @param Worksheet $worksheet The worksheet that we're editing + * @param string $beforeCellAddress Insert/Delete before this cell address (e.g. 'A1') + * @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion) + * @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion) + */ + protected function adjustColumnDimensions(Worksheet $worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows): void + { + $aColumnDimensions = array_reverse($worksheet->getColumnDimensions(), true); + if (!empty($aColumnDimensions)) { + foreach ($aColumnDimensions as $objColumnDimension) { + $newReference = $this->updateCellReference($objColumnDimension->getColumnIndex() . '1', $beforeCellAddress, $numberOfColumns, $numberOfRows); + [$newReference] = Coordinate::coordinateFromString($newReference); + if ($objColumnDimension->getColumnIndex() != $newReference) { + $objColumnDimension->setColumnIndex($newReference); + } + } + $worksheet->refreshColumnDimensions(); + } + } + + /** + * Update row dimensions when inserting/deleting rows/columns. + * + * @param Worksheet $worksheet The worksheet that we're editing + * @param string $beforeCellAddress Insert/Delete before this cell address (e.g. 'A1') + * @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion) + * @param int $beforeRow Number of the row we're inserting/deleting before + * @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion) + */ + protected function adjustRowDimensions(Worksheet $worksheet, $beforeCellAddress, $numberOfColumns, $beforeRow, $numberOfRows): void + { + $aRowDimensions = array_reverse($worksheet->getRowDimensions(), true); + if (!empty($aRowDimensions)) { + foreach ($aRowDimensions as $objRowDimension) { + $newReference = $this->updateCellReference('A' . $objRowDimension->getRowIndex(), $beforeCellAddress, $numberOfColumns, $numberOfRows); + [, $newReference] = Coordinate::coordinateFromString($newReference); + if ($objRowDimension->getRowIndex() != $newReference) { + $objRowDimension->setRowIndex($newReference); + } + } + $worksheet->refreshRowDimensions(); + + $copyDimension = $worksheet->getRowDimension($beforeRow - 1); + for ($i = $beforeRow; $i <= $beforeRow - 1 + $numberOfRows; ++$i) { + $newDimension = $worksheet->getRowDimension($i); + $newDimension->setRowHeight($copyDimension->getRowHeight()); + $newDimension->setVisible($copyDimension->getVisible()); + $newDimension->setOutlineLevel($copyDimension->getOutlineLevel()); + $newDimension->setCollapsed($copyDimension->getCollapsed()); + } + } + } + + /** + * Insert a new column or row, updating all possible related data. + * + * @param string $beforeCellAddress Insert before this cell address (e.g. 'A1') + * @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion) + * @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion) + * @param Worksheet $worksheet The worksheet that we're editing + */ + public function insertNewBefore($beforeCellAddress, $numberOfColumns, $numberOfRows, Worksheet $worksheet): void + { + $remove = ($numberOfColumns < 0 || $numberOfRows < 0); + $allCoordinates = $worksheet->getCoordinates(); + + // Get coordinate of $beforeCellAddress + [$beforeColumn, $beforeRow] = Coordinate::indexesFromString($beforeCellAddress); + + // Clear cells if we are removing columns or rows + $highestColumn = $worksheet->getHighestColumn(); + $highestRow = $worksheet->getHighestRow(); + + // 1. Clear column strips if we are removing columns + if ($numberOfColumns < 0 && $beforeColumn - 2 + $numberOfColumns > 0) { + for ($i = 1; $i <= $highestRow - 1; ++$i) { + for ($j = $beforeColumn - 1 + $numberOfColumns; $j <= $beforeColumn - 2; ++$j) { + $coordinate = Coordinate::stringFromColumnIndex($j + 1) . $i; + $worksheet->removeConditionalStyles($coordinate); + if ($worksheet->cellExists($coordinate)) { + $worksheet->getCell($coordinate)->setValueExplicit('', DataType::TYPE_NULL); + $worksheet->getCell($coordinate)->setXfIndex(0); + } + } + } + } + + // 2. Clear row strips if we are removing rows + if ($numberOfRows < 0 && $beforeRow - 1 + $numberOfRows > 0) { + for ($i = $beforeColumn - 1; $i <= Coordinate::columnIndexFromString($highestColumn) - 1; ++$i) { + for ($j = $beforeRow + $numberOfRows; $j <= $beforeRow - 1; ++$j) { + $coordinate = Coordinate::stringFromColumnIndex($i + 1) . $j; + $worksheet->removeConditionalStyles($coordinate); + if ($worksheet->cellExists($coordinate)) { + $worksheet->getCell($coordinate)->setValueExplicit('', DataType::TYPE_NULL); + $worksheet->getCell($coordinate)->setXfIndex(0); + } + } + } + } + + // Loop through cells, bottom-up, and change cell coordinate + if ($remove) { + // It's faster to reverse and pop than to use unshift, especially with large cell collections + $allCoordinates = array_reverse($allCoordinates); + } + while ($coordinate = array_pop($allCoordinates)) { + $cell = $worksheet->getCell($coordinate); + $cellIndex = Coordinate::columnIndexFromString($cell->getColumn()); + + if ($cellIndex - 1 + $numberOfColumns < 0) { + continue; + } + + // New coordinate + $newCoordinate = Coordinate::stringFromColumnIndex($cellIndex + $numberOfColumns) . ($cell->getRow() + $numberOfRows); + + // Should the cell be updated? Move value and cellXf index from one cell to another. + if (($cellIndex >= $beforeColumn) && ($cell->getRow() >= $beforeRow)) { + // Update cell styles + $worksheet->getCell($newCoordinate)->setXfIndex($cell->getXfIndex()); + + // Insert this cell at its new location + if ($cell->getDataType() == DataType::TYPE_FORMULA) { + // Formula should be adjusted + $worksheet->getCell($newCoordinate) + ->setValue($this->updateFormulaReferences($cell->getValue(), $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle())); + } else { + // Formula should not be adjusted + $worksheet->getCell($newCoordinate)->setValue($cell->getValue()); + } + + // Clear the original cell + $worksheet->getCellCollection()->delete($coordinate); + } else { + /* We don't need to update styles for rows/columns before our insertion position, + but we do still need to adjust any formulae in those cells */ + if ($cell->getDataType() == DataType::TYPE_FORMULA) { + // Formula should be adjusted + $cell->setValue($this->updateFormulaReferences($cell->getValue(), $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle())); + } + } + } + + // Duplicate styles for the newly inserted cells + $highestColumn = $worksheet->getHighestColumn(); + $highestRow = $worksheet->getHighestRow(); + + if ($numberOfColumns > 0 && $beforeColumn - 2 > 0) { + for ($i = $beforeRow; $i <= $highestRow - 1; ++$i) { + // Style + $coordinate = Coordinate::stringFromColumnIndex($beforeColumn - 1) . $i; + if ($worksheet->cellExists($coordinate)) { + $xfIndex = $worksheet->getCell($coordinate)->getXfIndex(); + $conditionalStyles = $worksheet->conditionalStylesExists($coordinate) ? + $worksheet->getConditionalStyles($coordinate) : false; + for ($j = $beforeColumn; $j <= $beforeColumn - 1 + $numberOfColumns; ++$j) { + $worksheet->getCellByColumnAndRow($j, $i)->setXfIndex($xfIndex); + if ($conditionalStyles) { + $cloned = []; + foreach ($conditionalStyles as $conditionalStyle) { + $cloned[] = clone $conditionalStyle; + } + $worksheet->setConditionalStyles(Coordinate::stringFromColumnIndex($j) . $i, $cloned); + } + } + } + } + } + + if ($numberOfRows > 0 && $beforeRow - 1 > 0) { + for ($i = $beforeColumn; $i <= Coordinate::columnIndexFromString($highestColumn); ++$i) { + // Style + $coordinate = Coordinate::stringFromColumnIndex($i) . ($beforeRow - 1); + if ($worksheet->cellExists($coordinate)) { + $xfIndex = $worksheet->getCell($coordinate)->getXfIndex(); + $conditionalStyles = $worksheet->conditionalStylesExists($coordinate) ? + $worksheet->getConditionalStyles($coordinate) : false; + for ($j = $beforeRow; $j <= $beforeRow - 1 + $numberOfRows; ++$j) { + $worksheet->getCell(Coordinate::stringFromColumnIndex($i) . $j)->setXfIndex($xfIndex); + if ($conditionalStyles) { + $cloned = []; + foreach ($conditionalStyles as $conditionalStyle) { + $cloned[] = clone $conditionalStyle; + } + $worksheet->setConditionalStyles(Coordinate::stringFromColumnIndex($i) . $j, $cloned); + } + } + } + } + } + + // Update worksheet: column dimensions + $this->adjustColumnDimensions($worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows); + + // Update worksheet: row dimensions + $this->adjustRowDimensions($worksheet, $beforeCellAddress, $numberOfColumns, $beforeRow, $numberOfRows); + + // Update worksheet: page breaks + $this->adjustPageBreaks($worksheet, $beforeCellAddress, $beforeColumn, $numberOfColumns, $beforeRow, $numberOfRows); + + // Update worksheet: comments + $this->adjustComments($worksheet, $beforeCellAddress, $beforeColumn, $numberOfColumns, $beforeRow, $numberOfRows); + + // Update worksheet: hyperlinks + $this->adjustHyperlinks($worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows); + + // Update worksheet: data validations + $this->adjustDataValidations($worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows); + + // Update worksheet: merge cells + $this->adjustMergeCells($worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows); + + // Update worksheet: protected cells + $this->adjustProtectedCells($worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows); + + // Update worksheet: autofilter + $autoFilter = $worksheet->getAutoFilter(); + $autoFilterRange = $autoFilter->getRange(); + if (!empty($autoFilterRange)) { + if ($numberOfColumns != 0) { + $autoFilterColumns = $autoFilter->getColumns(); + if (count($autoFilterColumns) > 0) { + $column = ''; + $row = 0; + sscanf($beforeCellAddress, '%[A-Z]%d', $column, $row); + $columnIndex = Coordinate::columnIndexFromString($column); + [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($autoFilterRange); + if ($columnIndex <= $rangeEnd[0]) { + if ($numberOfColumns < 0) { + // If we're actually deleting any columns that fall within the autofilter range, + // then we delete any rules for those columns + $deleteColumn = $columnIndex + $numberOfColumns - 1; + $deleteCount = abs($numberOfColumns); + for ($i = 1; $i <= $deleteCount; ++$i) { + if (isset($autoFilterColumns[Coordinate::stringFromColumnIndex($deleteColumn + 1)])) { + $autoFilter->clearColumn(Coordinate::stringFromColumnIndex($deleteColumn + 1)); + } + ++$deleteColumn; + } + } + $startCol = ($columnIndex > $rangeStart[0]) ? $columnIndex : $rangeStart[0]; + + // Shuffle columns in autofilter range + if ($numberOfColumns > 0) { + $startColRef = $startCol; + $endColRef = $rangeEnd[0]; + $toColRef = $rangeEnd[0] + $numberOfColumns; + + do { + $autoFilter->shiftColumn(Coordinate::stringFromColumnIndex($endColRef), Coordinate::stringFromColumnIndex($toColRef)); + --$endColRef; + --$toColRef; + } while ($startColRef <= $endColRef); + } else { + // For delete, we shuffle from beginning to end to avoid overwriting + $startColID = Coordinate::stringFromColumnIndex($startCol); + $toColID = Coordinate::stringFromColumnIndex($startCol + $numberOfColumns); + $endColID = Coordinate::stringFromColumnIndex($rangeEnd[0] + 1); + do { + $autoFilter->shiftColumn($startColID, $toColID); + ++$startColID; + ++$toColID; + } while ($startColID != $endColID); + } + } + } + } + $worksheet->setAutoFilter($this->updateCellReference($autoFilterRange, $beforeCellAddress, $numberOfColumns, $numberOfRows)); + } + + // Update worksheet: freeze pane + if ($worksheet->getFreezePane()) { + $splitCell = $worksheet->getFreezePane() ?? ''; + $topLeftCell = $worksheet->getTopLeftCell() ?? ''; + + $splitCell = $this->updateCellReference($splitCell, $beforeCellAddress, $numberOfColumns, $numberOfRows); + $topLeftCell = $this->updateCellReference($topLeftCell, $beforeCellAddress, $numberOfColumns, $numberOfRows); + + $worksheet->freezePane($splitCell, $topLeftCell); + } + + // Page setup + if ($worksheet->getPageSetup()->isPrintAreaSet()) { + $worksheet->getPageSetup()->setPrintArea($this->updateCellReference($worksheet->getPageSetup()->getPrintArea(), $beforeCellAddress, $numberOfColumns, $numberOfRows)); + } + + // Update worksheet: drawings + $aDrawings = $worksheet->getDrawingCollection(); + foreach ($aDrawings as $objDrawing) { + $newReference = $this->updateCellReference($objDrawing->getCoordinates(), $beforeCellAddress, $numberOfColumns, $numberOfRows); + if ($objDrawing->getCoordinates() != $newReference) { + $objDrawing->setCoordinates($newReference); + } + } + + // Update workbook: define names + if (count($worksheet->getParent()->getDefinedNames()) > 0) { + foreach ($worksheet->getParent()->getDefinedNames() as $definedName) { + if ($definedName->getWorksheet() !== null && $definedName->getWorksheet()->getHashCode() === $worksheet->getHashCode()) { + $definedName->setValue($this->updateCellReference($definedName->getValue(), $beforeCellAddress, $numberOfColumns, $numberOfRows)); + } + } + } + + // Garbage collect + $worksheet->garbageCollect(); + } + + /** + * Update references within formulas. + * + * @param string $formula Formula to update + * @param string $beforeCellAddress Insert before this one + * @param int $numberOfColumns Number of columns to insert + * @param int $numberOfRows Number of rows to insert + * @param string $worksheetName Worksheet name/title + * + * @return string Updated formula + */ + public function updateFormulaReferences($formula = '', $beforeCellAddress = 'A1', $numberOfColumns = 0, $numberOfRows = 0, $worksheetName = '') + { + // Update cell references in the formula + $formulaBlocks = explode('"', $formula); + $i = false; + foreach ($formulaBlocks as &$formulaBlock) { + // Ignore blocks that were enclosed in quotes (alternating entries in the $formulaBlocks array after the explode) + if ($i = !$i) { + $adjustCount = 0; + $newCellTokens = $cellTokens = []; + // Search for row ranges (e.g. 'Sheet1'!3:5 or 3:5) with or without $ absolutes (e.g. $3:5) + $matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_ROWRANGE . '/i', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER); + if ($matchCount > 0) { + foreach ($matches as $match) { + $fromString = ($match[2] > '') ? $match[2] . '!' : ''; + $fromString .= $match[3] . ':' . $match[4]; + $modified3 = substr($this->updateCellReference('$A' . $match[3], $beforeCellAddress, $numberOfColumns, $numberOfRows), 2); + $modified4 = substr($this->updateCellReference('$A' . $match[4], $beforeCellAddress, $numberOfColumns, $numberOfRows), 2); + + if ($match[3] . ':' . $match[4] !== $modified3 . ':' . $modified4) { + if (($match[2] == '') || (trim($match[2], "'") == $worksheetName)) { + $toString = ($match[2] > '') ? $match[2] . '!' : ''; + $toString .= $modified3 . ':' . $modified4; + // Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more + $column = 100000; + $row = 10000000 + (int) trim($match[3], '$'); + $cellIndex = $column . $row; + + $newCellTokens[$cellIndex] = preg_quote($toString, '/'); + $cellTokens[$cellIndex] = '/(? 0) { + foreach ($matches as $match) { + $fromString = ($match[2] > '') ? $match[2] . '!' : ''; + $fromString .= $match[3] . ':' . $match[4]; + $modified3 = substr($this->updateCellReference($match[3] . '$1', $beforeCellAddress, $numberOfColumns, $numberOfRows), 0, -2); + $modified4 = substr($this->updateCellReference($match[4] . '$1', $beforeCellAddress, $numberOfColumns, $numberOfRows), 0, -2); + + if ($match[3] . ':' . $match[4] !== $modified3 . ':' . $modified4) { + if (($match[2] == '') || (trim($match[2], "'") == $worksheetName)) { + $toString = ($match[2] > '') ? $match[2] . '!' : ''; + $toString .= $modified3 . ':' . $modified4; + // Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more + $column = Coordinate::columnIndexFromString(trim($match[3], '$')) + 100000; + $row = 10000000; + $cellIndex = $column . $row; + + $newCellTokens[$cellIndex] = preg_quote($toString, '/'); + $cellTokens[$cellIndex] = '/(? 0) { + foreach ($matches as $match) { + $fromString = ($match[2] > '') ? $match[2] . '!' : ''; + $fromString .= $match[3] . ':' . $match[4]; + $modified3 = $this->updateCellReference($match[3], $beforeCellAddress, $numberOfColumns, $numberOfRows); + $modified4 = $this->updateCellReference($match[4], $beforeCellAddress, $numberOfColumns, $numberOfRows); + + if ($match[3] . $match[4] !== $modified3 . $modified4) { + if (($match[2] == '') || (trim($match[2], "'") == $worksheetName)) { + $toString = ($match[2] > '') ? $match[2] . '!' : ''; + $toString .= $modified3 . ':' . $modified4; + [$column, $row] = Coordinate::coordinateFromString($match[3]); + // Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more + $column = Coordinate::columnIndexFromString(trim($column, '$')) + 100000; + $row = (int) trim($row, '$') + 10000000; + $cellIndex = $column . $row; + + $newCellTokens[$cellIndex] = preg_quote($toString, '/'); + $cellTokens[$cellIndex] = '/(? 0) { + foreach ($matches as $match) { + $fromString = ($match[2] > '') ? $match[2] . '!' : ''; + $fromString .= $match[3]; + + $modified3 = $this->updateCellReference($match[3], $beforeCellAddress, $numberOfColumns, $numberOfRows); + if ($match[3] !== $modified3) { + if (($match[2] == '') || (trim($match[2], "'") == $worksheetName)) { + $toString = ($match[2] > '') ? $match[2] . '!' : ''; + $toString .= $modified3; + [$column, $row] = Coordinate::coordinateFromString($match[3]); + $columnAdditionalIndex = $column[0] === '$' ? 1 : 0; + $rowAdditionalIndex = $row[0] === '$' ? 1 : 0; + // Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more + $column = Coordinate::columnIndexFromString(trim($column, '$')) + 100000; + $row = (int) trim($row, '$') + 10000000; + $cellIndex = $row . $rowAdditionalIndex . $column . $columnAdditionalIndex; + + $newCellTokens[$cellIndex] = preg_quote($toString, '/'); + $cellTokens[$cellIndex] = '/(? 0) { + if ($numberOfColumns > 0 || $numberOfRows > 0) { + krsort($cellTokens); + krsort($newCellTokens); + } else { + ksort($cellTokens); + ksort($newCellTokens); + } // Update cell references in the formula + $formulaBlock = str_replace('\\', '', preg_replace($cellTokens, $newCellTokens, $formulaBlock)); + } + } + } + unset($formulaBlock); + + // Then rebuild the formula string + return implode('"', $formulaBlocks); + } + + /** + * Update all cell references within a formula, irrespective of worksheet. + */ + public function updateFormulaReferencesAnyWorksheet(string $formula = '', int $numberOfColumns = 0, int $numberOfRows = 0): string + { + $formula = $this->updateCellReferencesAllWorksheets($formula, $numberOfColumns, $numberOfRows); + + if ($numberOfColumns !== 0) { + $formula = $this->updateColumnRangesAllWorksheets($formula, $numberOfColumns); + } + + if ($numberOfRows !== 0) { + $formula = $this->updateRowRangesAllWorksheets($formula, $numberOfRows); + } + + return $formula; + } + + private function updateCellReferencesAllWorksheets(string $formula, int $numberOfColumns, int $numberOfRows): string + { + $splitCount = preg_match_all( + '/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/mui', + $formula, + $splitRanges, + PREG_OFFSET_CAPTURE + ); + + $columnLengths = array_map('strlen', array_column($splitRanges[6], 0)); + $rowLengths = array_map('strlen', array_column($splitRanges[7], 0)); + $columnOffsets = array_column($splitRanges[6], 1); + $rowOffsets = array_column($splitRanges[7], 1); + + $columns = $splitRanges[6]; + $rows = $splitRanges[7]; + + while ($splitCount > 0) { + --$splitCount; + $columnLength = $columnLengths[$splitCount]; + $rowLength = $rowLengths[$splitCount]; + $columnOffset = $columnOffsets[$splitCount]; + $rowOffset = $rowOffsets[$splitCount]; + $column = $columns[$splitCount][0]; + $row = $rows[$splitCount][0]; + + if (!empty($column) && $column[0] !== '$') { + $column = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($column) + $numberOfColumns); + $formula = substr($formula, 0, $columnOffset) . $column . substr($formula, $columnOffset + $columnLength); + } + if (!empty($row) && $row[0] !== '$') { + $row += $numberOfRows; + $formula = substr($formula, 0, $rowOffset) . $row . substr($formula, $rowOffset + $rowLength); + } + } + + return $formula; + } + + private function updateColumnRangesAllWorksheets(string $formula, int $numberOfColumns): string + { + $splitCount = preg_match_all( + '/' . Calculation::CALCULATION_REGEXP_COLUMNRANGE_RELATIVE . '/mui', + $formula, + $splitRanges, + PREG_OFFSET_CAPTURE + ); + + $fromColumnLengths = array_map('strlen', array_column($splitRanges[1], 0)); + $fromColumnOffsets = array_column($splitRanges[1], 1); + $toColumnLengths = array_map('strlen', array_column($splitRanges[2], 0)); + $toColumnOffsets = array_column($splitRanges[2], 1); + + $fromColumns = $splitRanges[1]; + $toColumns = $splitRanges[2]; + + while ($splitCount > 0) { + --$splitCount; + $fromColumnLength = $fromColumnLengths[$splitCount]; + $toColumnLength = $toColumnLengths[$splitCount]; + $fromColumnOffset = $fromColumnOffsets[$splitCount]; + $toColumnOffset = $toColumnOffsets[$splitCount]; + $fromColumn = $fromColumns[$splitCount][0]; + $toColumn = $toColumns[$splitCount][0]; + + if (!empty($fromColumn) && $fromColumn[0] !== '$') { + $fromColumn = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($fromColumn) + $numberOfColumns); + $formula = substr($formula, 0, $fromColumnOffset) . $fromColumn . substr($formula, $fromColumnOffset + $fromColumnLength); + } + if (!empty($toColumn) && $toColumn[0] !== '$') { + $toColumn = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($toColumn) + $numberOfColumns); + $formula = substr($formula, 0, $toColumnOffset) . $toColumn . substr($formula, $toColumnOffset + $toColumnLength); + } + } + + return $formula; + } + + private function updateRowRangesAllWorksheets(string $formula, int $numberOfRows): string + { + $splitCount = preg_match_all( + '/' . Calculation::CALCULATION_REGEXP_ROWRANGE_RELATIVE . '/mui', + $formula, + $splitRanges, + PREG_OFFSET_CAPTURE + ); + + $fromRowLengths = array_map('strlen', array_column($splitRanges[1], 0)); + $fromRowOffsets = array_column($splitRanges[1], 1); + $toRowLengths = array_map('strlen', array_column($splitRanges[2], 0)); + $toRowOffsets = array_column($splitRanges[2], 1); + + $fromRows = $splitRanges[1]; + $toRows = $splitRanges[2]; + + while ($splitCount > 0) { + --$splitCount; + $fromRowLength = $fromRowLengths[$splitCount]; + $toRowLength = $toRowLengths[$splitCount]; + $fromRowOffset = $fromRowOffsets[$splitCount]; + $toRowOffset = $toRowOffsets[$splitCount]; + $fromRow = $fromRows[$splitCount][0]; + $toRow = $toRows[$splitCount][0]; + + if (!empty($fromRow) && $fromRow[0] !== '$') { + $fromRow += $numberOfRows; + $formula = substr($formula, 0, $fromRowOffset) . $fromRow . substr($formula, $fromRowOffset + $fromRowLength); + } + if (!empty($toRow) && $toRow[0] !== '$') { + $toRow += $numberOfRows; + $formula = substr($formula, 0, $toRowOffset) . $toRow . substr($formula, $toRowOffset + $toRowLength); + } + } + + return $formula; + } + + /** + * Update cell reference. + * + * @param string $cellReference Cell address or range of addresses + * @param string $beforeCellAddress Insert before this one + * @param int $numberOfColumns Number of columns to increment + * @param int $numberOfRows Number of rows to increment + * + * @return string Updated cell range + */ + public function updateCellReference($cellReference = 'A1', $beforeCellAddress = 'A1', $numberOfColumns = 0, $numberOfRows = 0) + { + // Is it in another worksheet? Will not have to update anything. + if (strpos($cellReference, '!') !== false) { + return $cellReference; + // Is it a range or a single cell? + } elseif (!Coordinate::coordinateIsRange($cellReference)) { + // Single cell + return $this->updateSingleCellReference($cellReference, $beforeCellAddress, $numberOfColumns, $numberOfRows); + } elseif (Coordinate::coordinateIsRange($cellReference)) { + // Range + return $this->updateCellRange($cellReference, $beforeCellAddress, $numberOfColumns, $numberOfRows); + } + + // Return original + return $cellReference; + } + + /** + * Update named formulas (i.e. containing worksheet references / named ranges). + * + * @param Spreadsheet $spreadsheet Object to update + * @param string $oldName Old name (name to replace) + * @param string $newName New name + */ + public function updateNamedFormulas(Spreadsheet $spreadsheet, $oldName = '', $newName = ''): void + { + if ($oldName == '') { + return; + } + + foreach ($spreadsheet->getWorksheetIterator() as $sheet) { + foreach ($sheet->getCoordinates(false) as $coordinate) { + $cell = $sheet->getCell($coordinate); + if (($cell !== null) && ($cell->getDataType() == DataType::TYPE_FORMULA)) { + $formula = $cell->getValue(); + if (strpos($formula, $oldName) !== false) { + $formula = str_replace("'" . $oldName . "'!", "'" . $newName . "'!", $formula); + $formula = str_replace($oldName . '!', $newName . '!', $formula); + $cell->setValueExplicit($formula, DataType::TYPE_FORMULA); + } + } + } + } + } + + /** + * Update cell range. + * + * @param string $cellRange Cell range (e.g. 'B2:D4', 'B:C' or '2:3') + * @param string $beforeCellAddress Insert before this one + * @param int $numberOfColumns Number of columns to increment + * @param int $numberOfRows Number of rows to increment + * + * @return string Updated cell range + */ + private function updateCellRange($cellRange = 'A1:A1', $beforeCellAddress = 'A1', $numberOfColumns = 0, $numberOfRows = 0) + { + if (!Coordinate::coordinateIsRange($cellRange)) { + throw new Exception('Only cell ranges may be passed to this method.'); + } + + // Update range + $range = Coordinate::splitRange($cellRange); + $ic = count($range); + for ($i = 0; $i < $ic; ++$i) { + $jc = count($range[$i]); + for ($j = 0; $j < $jc; ++$j) { + if (ctype_alpha($range[$i][$j])) { + $r = Coordinate::coordinateFromString($this->updateSingleCellReference($range[$i][$j] . '1', $beforeCellAddress, $numberOfColumns, $numberOfRows)); + $range[$i][$j] = $r[0]; + } elseif (ctype_digit($range[$i][$j])) { + $r = Coordinate::coordinateFromString($this->updateSingleCellReference('A' . $range[$i][$j], $beforeCellAddress, $numberOfColumns, $numberOfRows)); + $range[$i][$j] = $r[1]; + } else { + $range[$i][$j] = $this->updateSingleCellReference($range[$i][$j], $beforeCellAddress, $numberOfColumns, $numberOfRows); + } + } + } + + // Recreate range string + return Coordinate::buildRange($range); + } + + /** + * Update single cell reference. + * + * @param string $cellReference Single cell reference + * @param string $beforeCellAddress Insert before this one + * @param int $numberOfColumns Number of columns to increment + * @param int $numberOfRows Number of rows to increment + * + * @return string Updated cell reference + */ + private function updateSingleCellReference($cellReference = 'A1', $beforeCellAddress = 'A1', $numberOfColumns = 0, $numberOfRows = 0) + { + if (Coordinate::coordinateIsRange($cellReference)) { + throw new Exception('Only single cell references may be passed to this method.'); + } + + // Get coordinate of $beforeCellAddress + [$beforeColumn, $beforeRow] = Coordinate::coordinateFromString($beforeCellAddress); + + // Get coordinate of $cellReference + [$newColumn, $newRow] = Coordinate::coordinateFromString($cellReference); + + // Verify which parts should be updated + $updateColumn = (($newColumn[0] != '$') && ($beforeColumn[0] != '$') && (Coordinate::columnIndexFromString($newColumn) >= Coordinate::columnIndexFromString($beforeColumn))); + $updateRow = (($newRow[0] != '$') && ($beforeRow[0] != '$') && $newRow >= $beforeRow); + + // Create new column reference + if ($updateColumn) { + $newColumn = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($newColumn) + $numberOfColumns); + } + + // Create new row reference + if ($updateRow) { + $newRow = (int) $newRow + $numberOfRows; + } + + // Return new reference + return $newColumn . $newRow; + } + + /** + * __clone implementation. Cloning should not be allowed in a Singleton! + */ + final public function __clone() + { + throw new Exception('Cloning a Singleton is not allowed!'); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/ITextElement.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/ITextElement.php new file mode 100644 index 0000000..39b70c8 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/ITextElement.php @@ -0,0 +1,36 @@ +richTextElements = []; + + // Rich-Text string attached to cell? + if ($cell !== null) { + // Add cell text and style + if ($cell->getValue() != '') { + $objRun = new Run($cell->getValue()); + $objRun->setFont(clone $cell->getWorksheet()->getStyle($cell->getCoordinate())->getFont()); + $this->addText($objRun); + } + + // Set parent value + $cell->setValueExplicit($this, DataType::TYPE_STRING); + } + } + + /** + * Add text. + * + * @param ITextElement $text Rich text element + * + * @return $this + */ + public function addText(ITextElement $text) + { + $this->richTextElements[] = $text; + + return $this; + } + + /** + * Create text. + * + * @param string $text Text + * + * @return TextElement + */ + public function createText($text) + { + $objText = new TextElement($text); + $this->addText($objText); + + return $objText; + } + + /** + * Create text run. + * + * @param string $text Text + * + * @return Run + */ + public function createTextRun($text) + { + $objText = new Run($text); + $this->addText($objText); + + return $objText; + } + + /** + * Get plain text. + * + * @return string + */ + public function getPlainText() + { + // Return value + $returnValue = ''; + + // Loop through all ITextElements + foreach ($this->richTextElements as $text) { + $returnValue .= $text->getText(); + } + + return $returnValue; + } + + /** + * Convert to string. + * + * @return string + */ + public function __toString() + { + return $this->getPlainText(); + } + + /** + * Get Rich Text elements. + * + * @return ITextElement[] + */ + public function getRichTextElements() + { + return $this->richTextElements; + } + + /** + * Set Rich Text elements. + * + * @param ITextElement[] $textElements Array of elements + * + * @return $this + */ + public function setRichTextElements(array $textElements) + { + $this->richTextElements = $textElements; + + return $this; + } + + /** + * Get hash code. + * + * @return string Hash code + */ + public function getHashCode() + { + $hashElements = ''; + foreach ($this->richTextElements as $element) { + $hashElements .= $element->getHashCode(); + } + + return md5( + $hashElements . + __CLASS__ + ); + } + + /** + * Implement PHP __clone to create a deep clone, not just a shallow copy. + */ + public function __clone() + { + $vars = get_object_vars($this); + foreach ($vars as $key => $value) { + if (is_object($value)) { + $this->$key = clone $value; + } else { + $this->$key = $value; + } + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/Run.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/Run.php new file mode 100644 index 0000000..9c9f807 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/Run.php @@ -0,0 +1,65 @@ +font = new Font(); + } + + /** + * Get font. + * + * @return null|\PhpOffice\PhpSpreadsheet\Style\Font + */ + public function getFont() + { + return $this->font; + } + + /** + * Set font. + * + * @param Font $font Font + * + * @return $this + */ + public function setFont(?Font $font = null) + { + $this->font = $font; + + return $this; + } + + /** + * Get hash code. + * + * @return string Hash code + */ + public function getHashCode() + { + return md5( + $this->getText() . + $this->font->getHashCode() . + __CLASS__ + ); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/TextElement.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/TextElement.php new file mode 100644 index 0000000..6bec005 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/TextElement.php @@ -0,0 +1,86 @@ +text = $text; + } + + /** + * Get text. + * + * @return string Text + */ + public function getText() + { + return $this->text; + } + + /** + * Set text. + * + * @param string $text Text + * + * @return $this + */ + public function setText($text) + { + $this->text = $text; + + return $this; + } + + /** + * Get font. + * + * @return null|\PhpOffice\PhpSpreadsheet\Style\Font + */ + public function getFont() + { + return null; + } + + /** + * Get hash code. + * + * @return string Hash code + */ + public function getHashCode() + { + return md5( + $this->text . + __CLASS__ + ); + } + + /** + * Implement PHP __clone to create a deep clone, not just a shallow copy. + */ + public function __clone() + { + $vars = get_object_vars($this); + foreach ($vars as $key => $value) { + if (is_object($value)) { + $this->$key = clone $value; + } else { + $this->$key = $value; + } + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Settings.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Settings.php new file mode 100644 index 0000000..5fbbadb --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Settings.php @@ -0,0 +1,214 @@ +setLocale($locale); + } + + public static function getLocale(): string + { + return Calculation::getInstance()->getLocale(); + } + + /** + * Identify to PhpSpreadsheet the external library to use for rendering charts. + * + * @param string $rendererClassName Class name of the chart renderer + * eg: PhpOffice\PhpSpreadsheet\Chart\Renderer\JpGraph + */ + public static function setChartRenderer(string $rendererClassName): void + { + if (!is_a($rendererClassName, IRenderer::class, true)) { + throw new Exception('Chart renderer must implement ' . IRenderer::class); + } + + self::$chartRenderer = $rendererClassName; + } + + /** + * Return the Chart Rendering Library that PhpSpreadsheet is currently configured to use. + * + * @return null|string Class name of the chart renderer + * eg: PhpOffice\PhpSpreadsheet\Chart\Renderer\JpGraph + */ + public static function getChartRenderer(): ?string + { + return self::$chartRenderer; + } + + public static function htmlEntityFlags(): int + { + return \ENT_COMPAT; + } + + /** + * Set default options for libxml loader. + * + * @param int $options Default options for libxml loader + */ + public static function setLibXmlLoaderOptions($options): void + { + if ($options === null && defined('LIBXML_DTDLOAD')) { + $options = LIBXML_DTDLOAD | LIBXML_DTDATTR; + } + self::$libXmlLoaderOptions = $options; + } + + /** + * Get default options for libxml loader. + * Defaults to LIBXML_DTDLOAD | LIBXML_DTDATTR when not set explicitly. + * + * @return int Default options for libxml loader + */ + public static function getLibXmlLoaderOptions(): int + { + if (self::$libXmlLoaderOptions === null && defined('LIBXML_DTDLOAD')) { + self::setLibXmlLoaderOptions(LIBXML_DTDLOAD | LIBXML_DTDATTR); + } elseif (self::$libXmlLoaderOptions === null) { + self::$libXmlLoaderOptions = 0; + } + + return self::$libXmlLoaderOptions; + } + + /** + * Deprecated, has no effect. + * + * @param bool $state + * + * @deprecated will be removed without replacement as it is no longer necessary on PHP 7.3.0+ + */ + public static function setLibXmlDisableEntityLoader($state): void + { + // noop + } + + /** + * Deprecated, has no effect. + * + * @return bool $state + * + * @deprecated will be removed without replacement as it is no longer necessary on PHP 7.3.0+ + */ + public static function getLibXmlDisableEntityLoader(): bool + { + return true; + } + + /** + * Sets the implementation of cache that should be used for cell collection. + */ + public static function setCache(CacheInterface $cache): void + { + self::$cache = $cache; + } + + /** + * Gets the implementation of cache that is being used for cell collection. + */ + public static function getCache(): CacheInterface + { + if (!self::$cache) { + self::$cache = new Memory(); + } + + return self::$cache; + } + + /** + * Set the HTTP client implementation to be used for network request. + */ + public static function setHttpClient(ClientInterface $httpClient, RequestFactoryInterface $requestFactory): void + { + self::$httpClient = $httpClient; + self::$requestFactory = $requestFactory; + } + + /** + * Unset the HTTP client configuration. + */ + public static function unsetHttpClient(): void + { + self::$httpClient = null; + self::$requestFactory = null; + } + + /** + * Get the HTTP client implementation to be used for network request. + */ + public static function getHttpClient(): ClientInterface + { + self::assertHttpClient(); + + return self::$httpClient; + } + + /** + * Get the HTTP request factory. + */ + public static function getRequestFactory(): RequestFactoryInterface + { + self::assertHttpClient(); + + return self::$requestFactory; + } + + private static function assertHttpClient(): void + { + if (!self::$httpClient || !self::$requestFactory) { + throw new Exception('HTTP client must be configured via Settings::setHttpClient() to be able to use WEBSERVICE function.'); + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/CodePage.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/CodePage.php new file mode 100644 index 0000000..8718a61 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/CodePage.php @@ -0,0 +1,114 @@ + 'CP1252', // CodePage is not always correctly set when the xls file was saved by Apple's Numbers program + 367 => 'ASCII', // ASCII + 437 => 'CP437', // OEM US + //720 => 'notsupported', // OEM Arabic + 737 => 'CP737', // OEM Greek + 775 => 'CP775', // OEM Baltic + 850 => 'CP850', // OEM Latin I + 852 => 'CP852', // OEM Latin II (Central European) + 855 => 'CP855', // OEM Cyrillic + 857 => 'CP857', // OEM Turkish + 858 => 'CP858', // OEM Multilingual Latin I with Euro + 860 => 'CP860', // OEM Portugese + 861 => 'CP861', // OEM Icelandic + 862 => 'CP862', // OEM Hebrew + 863 => 'CP863', // OEM Canadian (French) + 864 => 'CP864', // OEM Arabic + 865 => 'CP865', // OEM Nordic + 866 => 'CP866', // OEM Cyrillic (Russian) + 869 => 'CP869', // OEM Greek (Modern) + 874 => 'CP874', // ANSI Thai + 932 => 'CP932', // ANSI Japanese Shift-JIS + 936 => 'CP936', // ANSI Chinese Simplified GBK + 949 => 'CP949', // ANSI Korean (Wansung) + 950 => 'CP950', // ANSI Chinese Traditional BIG5 + 1200 => 'UTF-16LE', // UTF-16 (BIFF8) + 1250 => 'CP1250', // ANSI Latin II (Central European) + 1251 => 'CP1251', // ANSI Cyrillic + 1252 => 'CP1252', // ANSI Latin I (BIFF4-BIFF7) + 1253 => 'CP1253', // ANSI Greek + 1254 => 'CP1254', // ANSI Turkish + 1255 => 'CP1255', // ANSI Hebrew + 1256 => 'CP1256', // ANSI Arabic + 1257 => 'CP1257', // ANSI Baltic + 1258 => 'CP1258', // ANSI Vietnamese + 1361 => 'CP1361', // ANSI Korean (Johab) + 10000 => 'MAC', // Apple Roman + 10001 => 'CP932', // Macintosh Japanese + 10002 => 'CP950', // Macintosh Chinese Traditional + 10003 => 'CP1361', // Macintosh Korean + 10004 => 'MACARABIC', // Apple Arabic + 10005 => 'MACHEBREW', // Apple Hebrew + 10006 => 'MACGREEK', // Macintosh Greek + 10007 => 'MACCYRILLIC', // Macintosh Cyrillic + 10008 => 'CP936', // Macintosh - Simplified Chinese (GB 2312) + 10010 => 'MACROMANIA', // Macintosh Romania + 10017 => 'MACUKRAINE', // Macintosh Ukraine + 10021 => 'MACTHAI', // Macintosh Thai + 10029 => ['MACCENTRALEUROPE', 'MAC-CENTRALEUROPE'], // Macintosh Central Europe + 10079 => 'MACICELAND', // Macintosh Icelandic + 10081 => 'MACTURKISH', // Macintosh Turkish + 10082 => 'MACCROATIAN', // Macintosh Croatian + 21010 => 'UTF-16LE', // UTF-16 (BIFF8) This isn't correct, but some Excel writer libraries erroneously use Codepage 21010 for UTF-16LE + 32768 => 'MAC', // Apple Roman + //32769 => 'unsupported', // ANSI Latin I (BIFF2-BIFF3) + 65000 => 'UTF-7', // Unicode (UTF-7) + 65001 => 'UTF-8', // Unicode (UTF-8) + 99999 => ['unsupported'], // Unicode (UTF-8) + ]; + + public static function validate(string $codePage): bool + { + return in_array($codePage, self::$pageArray, true); + } + + /** + * Convert Microsoft Code Page Identifier to Code Page Name which iconv + * and mbstring understands. + * + * @param int $codePage Microsoft Code Page Indentifier + * + * @return string Code Page Name + */ + public static function numberToName(int $codePage): string + { + if (array_key_exists($codePage, self::$pageArray)) { + $value = self::$pageArray[$codePage]; + if (is_array($value)) { + foreach ($value as $encoding) { + if (@iconv('UTF-8', $encoding, ' ') !== false) { + self::$pageArray[$codePage] = $encoding; + + return $encoding; + } + } + + throw new PhpSpreadsheetException("Code page $codePage not implemented on this system."); + } else { + return $value; + } + } + if ($codePage == 720 || $codePage == 32769) { + throw new PhpSpreadsheetException("Code page $codePage not supported."); // OEM Arabic + } + + throw new PhpSpreadsheetException('Unknown codepage: ' . $codePage); + } + + public static function getEncodings(): array + { + return self::$pageArray; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Date.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Date.php new file mode 100644 index 0000000..5b0a290 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Date.php @@ -0,0 +1,523 @@ + 'January', + 'Feb' => 'February', + 'Mar' => 'March', + 'Apr' => 'April', + 'May' => 'May', + 'Jun' => 'June', + 'Jul' => 'July', + 'Aug' => 'August', + 'Sep' => 'September', + 'Oct' => 'October', + 'Nov' => 'November', + 'Dec' => 'December', + ]; + + /** + * @var string[] + */ + public static $numberSuffixes = [ + 'st', + 'nd', + 'rd', + 'th', + ]; + + /** + * Base calendar year to use for calculations + * Value is either CALENDAR_WINDOWS_1900 (1900) or CALENDAR_MAC_1904 (1904). + * + * @var int + */ + protected static $excelCalendar = self::CALENDAR_WINDOWS_1900; + + /** + * Default timezone to use for DateTime objects. + * + * @var null|DateTimeZone + */ + protected static $defaultTimeZone; + + /** + * Set the Excel calendar (Windows 1900 or Mac 1904). + * + * @param int $baseYear Excel base date (1900 or 1904) + * + * @return bool Success or failure + */ + public static function setExcelCalendar($baseYear) + { + if ( + ($baseYear == self::CALENDAR_WINDOWS_1900) || + ($baseYear == self::CALENDAR_MAC_1904) + ) { + self::$excelCalendar = $baseYear; + + return true; + } + + return false; + } + + /** + * Return the Excel calendar (Windows 1900 or Mac 1904). + * + * @return int Excel base date (1900 or 1904) + */ + public static function getExcelCalendar() + { + return self::$excelCalendar; + } + + /** + * Set the Default timezone to use for dates. + * + * @param null|DateTimeZone|string $timeZone The timezone to set for all Excel datetimestamp to PHP DateTime Object conversions + * + * @return bool Success or failure + */ + public static function setDefaultTimezone($timeZone) + { + try { + $timeZone = self::validateTimeZone($timeZone); + self::$defaultTimeZone = $timeZone; + $retval = true; + } catch (PhpSpreadsheetException $e) { + $retval = false; + } + + return $retval; + } + + /** + * Return the Default timezone, or UTC if default not set. + */ + public static function getDefaultTimezone(): DateTimeZone + { + return self::$defaultTimeZone ?? new DateTimeZone('UTC'); + } + + /** + * Return the Default timezone, or local timezone if default is not set. + */ + public static function getDefaultOrLocalTimezone(): DateTimeZone + { + return self::$defaultTimeZone ?? new DateTimeZone(date_default_timezone_get()); + } + + /** + * Return the Default timezone even if null. + */ + public static function getDefaultTimezoneOrNull(): ?DateTimeZone + { + return self::$defaultTimeZone; + } + + /** + * Validate a timezone. + * + * @param null|DateTimeZone|string $timeZone The timezone to validate, either as a timezone string or object + * + * @return ?DateTimeZone The timezone as a timezone object + */ + private static function validateTimeZone($timeZone) + { + if ($timeZone instanceof DateTimeZone || $timeZone === null) { + return $timeZone; + } + if (in_array($timeZone, DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC))) { + return new DateTimeZone($timeZone); + } + + throw new PhpSpreadsheetException('Invalid timezone'); + } + + /** + * Convert a MS serialized datetime value from Excel to a PHP Date/Time object. + * + * @param float|int $excelTimestamp MS Excel serialized date/time value + * @param null|DateTimeZone|string $timeZone The timezone to assume for the Excel timestamp, + * if you don't want to treat it as a UTC value + * Use the default (UTC) unless you absolutely need a conversion + * + * @return DateTime PHP date/time object + */ + public static function excelToDateTimeObject($excelTimestamp, $timeZone = null) + { + $timeZone = ($timeZone === null) ? self::getDefaultTimezone() : self::validateTimeZone($timeZone); + if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_EXCEL) { + if ($excelTimestamp < 1 && self::$excelCalendar === self::CALENDAR_WINDOWS_1900) { + // Unix timestamp base date + $baseDate = new DateTime('1970-01-01', $timeZone); + } else { + // MS Excel calendar base dates + if (self::$excelCalendar == self::CALENDAR_WINDOWS_1900) { + // Allow adjustment for 1900 Leap Year in MS Excel + $baseDate = ($excelTimestamp < 60) ? new DateTime('1899-12-31', $timeZone) : new DateTime('1899-12-30', $timeZone); + } else { + $baseDate = new DateTime('1904-01-01', $timeZone); + } + } + } else { + $baseDate = new DateTime('1899-12-30', $timeZone); + } + + $days = floor($excelTimestamp); + $partDay = $excelTimestamp - $days; + $hours = floor($partDay * 24); + $partDay = $partDay * 24 - $hours; + $minutes = floor($partDay * 60); + $partDay = $partDay * 60 - $minutes; + $seconds = round($partDay * 60); + + if ($days >= 0) { + $days = '+' . $days; + } + $interval = $days . ' days'; + + return $baseDate->modify($interval) + ->setTime((int) $hours, (int) $minutes, (int) $seconds); + } + + /** + * Convert a MS serialized datetime value from Excel to a unix timestamp. + * The use of Unix timestamps, and therefore this function, is discouraged. + * They are not Y2038-safe on a 32-bit system, and have no timezone info. + * + * @param float|int $excelTimestamp MS Excel serialized date/time value + * @param null|DateTimeZone|string $timeZone The timezone to assume for the Excel timestamp, + * if you don't want to treat it as a UTC value + * Use the default (UTC) unless you absolutely need a conversion + * + * @return int Unix timetamp for this date/time + */ + public static function excelToTimestamp($excelTimestamp, $timeZone = null) + { + return (int) self::excelToDateTimeObject($excelTimestamp, $timeZone) + ->format('U'); + } + + /** + * Convert a date from PHP to an MS Excel serialized date/time value. + * + * @param mixed $dateValue PHP DateTime object or a string - Unix timestamp is also permitted, but discouraged; + * not Y2038-safe on a 32-bit system, and no timezone info + * + * @return bool|float Excel date/time value + * or boolean FALSE on failure + */ + public static function PHPToExcel($dateValue) + { + if ((is_object($dateValue)) && ($dateValue instanceof DateTimeInterface)) { + return self::dateTimeToExcel($dateValue); + } elseif (is_numeric($dateValue)) { + return self::timestampToExcel($dateValue); + } elseif (is_string($dateValue)) { + return self::stringToExcel($dateValue); + } + + return false; + } + + /** + * Convert a PHP DateTime object to an MS Excel serialized date/time value. + * + * @param DateTimeInterface $dateValue PHP DateTime object + * + * @return float MS Excel serialized date/time value + */ + public static function dateTimeToExcel(DateTimeInterface $dateValue) + { + return self::formattedPHPToExcel( + (int) $dateValue->format('Y'), + (int) $dateValue->format('m'), + (int) $dateValue->format('d'), + (int) $dateValue->format('H'), + (int) $dateValue->format('i'), + (int) $dateValue->format('s') + ); + } + + /** + * Convert a Unix timestamp to an MS Excel serialized date/time value. + * The use of Unix timestamps, and therefore this function, is discouraged. + * They are not Y2038-safe on a 32-bit system, and have no timezone info. + * + * @param int $unixTimestamp Unix Timestamp + * + * @return false|float MS Excel serialized date/time value + */ + public static function timestampToExcel($unixTimestamp) + { + if (!is_numeric($unixTimestamp)) { + return false; + } + + return self::dateTimeToExcel(new DateTime('@' . $unixTimestamp)); + } + + /** + * formattedPHPToExcel. + * + * @param int $year + * @param int $month + * @param int $day + * @param int $hours + * @param int $minutes + * @param int $seconds + * + * @return float Excel date/time value + */ + public static function formattedPHPToExcel($year, $month, $day, $hours = 0, $minutes = 0, $seconds = 0) + { + if (self::$excelCalendar == self::CALENDAR_WINDOWS_1900) { + // + // Fudge factor for the erroneous fact that the year 1900 is treated as a Leap Year in MS Excel + // This affects every date following 28th February 1900 + // + $excel1900isLeapYear = true; + if (($year == 1900) && ($month <= 2)) { + $excel1900isLeapYear = false; + } + $myexcelBaseDate = 2415020; + } else { + $myexcelBaseDate = 2416481; + $excel1900isLeapYear = false; + } + + // Julian base date Adjustment + if ($month > 2) { + $month -= 3; + } else { + $month += 9; + --$year; + } + + // Calculate the Julian Date, then subtract the Excel base date (JD 2415020 = 31-Dec-1899 Giving Excel Date of 0) + $century = (int) substr($year, 0, 2); + $decade = (int) substr($year, 2, 2); + $excelDate = floor((146097 * $century) / 4) + floor((1461 * $decade) / 4) + floor((153 * $month + 2) / 5) + $day + 1721119 - $myexcelBaseDate + $excel1900isLeapYear; + + $excelTime = (($hours * 3600) + ($minutes * 60) + $seconds) / 86400; + + return (float) $excelDate + $excelTime; + } + + /** + * Is a given cell a date/time? + * + * @return bool + */ + public static function isDateTime(Cell $cell) + { + return is_numeric($cell->getCalculatedValue()) && + self::isDateTimeFormat( + $cell->getWorksheet()->getStyle( + $cell->getCoordinate() + )->getNumberFormat() + ); + } + + /** + * Is a given number format a date/time? + * + * @return bool + */ + public static function isDateTimeFormat(NumberFormat $excelFormatCode) + { + return self::isDateTimeFormatCode($excelFormatCode->getFormatCode()); + } + + private static $possibleDateFormatCharacters = 'eymdHs'; + + /** + * Is a given number format code a date/time? + * + * @param string $excelFormatCode + * + * @return bool + */ + public static function isDateTimeFormatCode($excelFormatCode) + { + if (strtolower($excelFormatCode) === strtolower(NumberFormat::FORMAT_GENERAL)) { + // "General" contains an epoch letter 'e', so we trap for it explicitly here (case-insensitive check) + return false; + } + if (preg_match('/[0#]E[+-]0/i', $excelFormatCode)) { + // Scientific format + return false; + } + + // Switch on formatcode + switch ($excelFormatCode) { + // Explicitly defined date formats + case NumberFormat::FORMAT_DATE_YYYYMMDD: + case NumberFormat::FORMAT_DATE_YYYYMMDD2: + case NumberFormat::FORMAT_DATE_DDMMYYYY: + case NumberFormat::FORMAT_DATE_DMYSLASH: + case NumberFormat::FORMAT_DATE_DMYMINUS: + case NumberFormat::FORMAT_DATE_DMMINUS: + case NumberFormat::FORMAT_DATE_MYMINUS: + case NumberFormat::FORMAT_DATE_DATETIME: + case NumberFormat::FORMAT_DATE_TIME1: + case NumberFormat::FORMAT_DATE_TIME2: + case NumberFormat::FORMAT_DATE_TIME3: + case NumberFormat::FORMAT_DATE_TIME4: + case NumberFormat::FORMAT_DATE_TIME5: + case NumberFormat::FORMAT_DATE_TIME6: + case NumberFormat::FORMAT_DATE_TIME7: + case NumberFormat::FORMAT_DATE_TIME8: + case NumberFormat::FORMAT_DATE_YYYYMMDDSLASH: + case NumberFormat::FORMAT_DATE_XLSX14: + case NumberFormat::FORMAT_DATE_XLSX15: + case NumberFormat::FORMAT_DATE_XLSX16: + case NumberFormat::FORMAT_DATE_XLSX17: + case NumberFormat::FORMAT_DATE_XLSX22: + return true; + } + + // Typically number, currency or accounting (or occasionally fraction) formats + if ((substr($excelFormatCode, 0, 1) == '_') || (substr($excelFormatCode, 0, 2) == '0 ')) { + return false; + } + // Some "special formats" provided in German Excel versions were detected as date time value, + // so filter them out here - "\C\H\-00000" (Switzerland) and "\D-00000" (Germany). + if (\strpos($excelFormatCode, '-00000') !== false) { + return false; + } + // Try checking for any of the date formatting characters that don't appear within square braces + if (preg_match('/(^|\])[^\[]*[' . self::$possibleDateFormatCharacters . ']/i', $excelFormatCode)) { + // We might also have a format mask containing quoted strings... + // we don't want to test for any of our characters within the quoted blocks + if (strpos($excelFormatCode, '"') !== false) { + $segMatcher = false; + foreach (explode('"', $excelFormatCode) as $subVal) { + // Only test in alternate array entries (the non-quoted blocks) + if ( + ($segMatcher = !$segMatcher) && + (preg_match('/(^|\])[^\[]*[' . self::$possibleDateFormatCharacters . ']/i', $subVal)) + ) { + return true; + } + } + + return false; + } + + return true; + } + + // No date... + return false; + } + + /** + * Convert a date/time string to Excel time. + * + * @param string $dateValue Examples: '2009-12-31', '2009-12-31 15:59', '2009-12-31 15:59:10' + * + * @return false|float Excel date/time serial value + */ + public static function stringToExcel($dateValue) + { + if (strlen($dateValue) < 2) { + return false; + } + if (!preg_match('/^(\d{1,4}[ \.\/\-][A-Z]{3,9}([ \.\/\-]\d{1,4})?|[A-Z]{3,9}[ \.\/\-]\d{1,4}([ \.\/\-]\d{1,4})?|\d{1,4}[ \.\/\-]\d{1,4}([ \.\/\-]\d{1,4})?)( \d{1,2}:\d{1,2}(:\d{1,2})?)?$/iu', $dateValue)) { + return false; + } + + $dateValueNew = DateTimeExcel\DateValue::fromString($dateValue); + + if ($dateValueNew === Functions::VALUE()) { + return false; + } + + if (strpos($dateValue, ':') !== false) { + $timeValue = DateTimeExcel\TimeValue::fromString($dateValue); + if ($timeValue === Functions::VALUE()) { + return false; + } + $dateValueNew += $timeValue; + } + + return $dateValueNew; + } + + /** + * Converts a month name (either a long or a short name) to a month number. + * + * @param string $monthName Month name or abbreviation + * + * @return int|string Month number (1 - 12), or the original string argument if it isn't a valid month name + */ + public static function monthStringToNumber($monthName) + { + $monthIndex = 1; + foreach (self::$monthNames as $shortMonthName => $longMonthName) { + if (($monthName === $longMonthName) || ($monthName === $shortMonthName)) { + return $monthIndex; + } + ++$monthIndex; + } + + return $monthName; + } + + /** + * Strips an ordinal from a numeric value. + * + * @param string $day Day number with an ordinal + * + * @return int|string The integer value with any ordinal stripped, or the original string argument if it isn't a valid numeric + */ + public static function dayStringToNumber($day) + { + $strippedDayValue = (str_replace(self::$numberSuffixes, '', $day)); + if (is_numeric($strippedDayValue)) { + return (int) $strippedDayValue; + } + + return $day; + } + + public static function dateTimeFromTimestamp(string $date, ?DateTimeZone $timeZone = null): DateTime + { + $dtobj = DateTime::createFromFormat('U', $date) ?: new DateTime(); + $dtobj->setTimeZone($timeZone ?? self::getDefaultOrLocalTimezone()); + + return $dtobj; + } + + public static function formattedDateTimeFromTimestamp(string $date, string $format, ?DateTimeZone $timeZone = null): string + { + $dtobj = self::dateTimeFromTimestamp($date, $timeZone); + + return $dtobj->format($format); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Drawing.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Drawing.php new file mode 100644 index 0000000..0d8ad61 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Drawing.php @@ -0,0 +1,257 @@ +getName(); + $size = $defaultFont->getSize(); + + if (isset(Font::$defaultColumnWidths[$name][$size])) { + // Exact width can be determined + return $pixelValue * Font::$defaultColumnWidths[$name][$size]['width'] + / Font::$defaultColumnWidths[$name][$size]['px']; + } + + // We don't have data for this particular font and size, use approximation by + // extrapolating from Calibri 11 + return $pixelValue * 11 * Font::$defaultColumnWidths['Calibri'][11]['width'] + / Font::$defaultColumnWidths['Calibri'][11]['px'] / $size; + } + + /** + * Convert column width from (intrinsic) Excel units to pixels. + * + * @param float $cellWidth Value in cell dimension + * @param \PhpOffice\PhpSpreadsheet\Style\Font $defaultFont Default font of the workbook + * + * @return int Value in pixels + */ + public static function cellDimensionToPixels($cellWidth, \PhpOffice\PhpSpreadsheet\Style\Font $defaultFont) + { + // Font name and size + $name = $defaultFont->getName(); + $size = $defaultFont->getSize(); + + if (isset(Font::$defaultColumnWidths[$name][$size])) { + // Exact width can be determined + $colWidth = $cellWidth * Font::$defaultColumnWidths[$name][$size]['px'] + / Font::$defaultColumnWidths[$name][$size]['width']; + } else { + // We don't have data for this particular font and size, use approximation by + // extrapolating from Calibri 11 + $colWidth = $cellWidth * $size * Font::$defaultColumnWidths['Calibri'][11]['px'] + / Font::$defaultColumnWidths['Calibri'][11]['width'] / 11; + } + + // Round pixels to closest integer + $colWidth = (int) round($colWidth); + + return $colWidth; + } + + /** + * Convert pixels to points. + * + * @param int $pixelValue Value in pixels + * + * @return float Value in points + */ + public static function pixelsToPoints($pixelValue) + { + return $pixelValue * 0.75; + } + + /** + * Convert points to pixels. + * + * @param int $pointValue Value in points + * + * @return int Value in pixels + */ + public static function pointsToPixels($pointValue) + { + if ($pointValue != 0) { + return (int) ceil($pointValue / 0.75); + } + + return 0; + } + + /** + * Convert degrees to angle. + * + * @param int $degrees Degrees + * + * @return int Angle + */ + public static function degreesToAngle($degrees) + { + return (int) round($degrees * 60000); + } + + /** + * Convert angle to degrees. + * + * @param int|SimpleXMLElement $angle Angle + * + * @return int Degrees + */ + public static function angleToDegrees($angle) + { + $angle = (int) $angle; + if ($angle != 0) { + return (int) round($angle / 60000); + } + + return 0; + } + + /** + * Create a new image from file. By alexander at alexauto dot nl. + * + * @see http://www.php.net/manual/en/function.imagecreatefromwbmp.php#86214 + * + * @param string $bmpFilename Path to Windows DIB (BMP) image + * + * @return GdImage|resource + */ + public static function imagecreatefrombmp($bmpFilename) + { + // Load the image into a string + $file = fopen($bmpFilename, 'rb'); + $read = fread($file, 10); + while (!feof($file) && ($read != '')) { + $read .= fread($file, 1024); + } + + $temp = unpack('H*', $read); + $hex = $temp[1]; + $header = substr($hex, 0, 108); + + // Process the header + // Structure: http://www.fastgraph.com/help/bmp_header_format.html + $width = 0; + $height = 0; + if (substr($header, 0, 4) == '424d') { + // Cut it in parts of 2 bytes + $header_parts = str_split($header, 2); + + // Get the width 4 bytes + $width = hexdec($header_parts[19] . $header_parts[18]); + + // Get the height 4 bytes + $height = hexdec($header_parts[23] . $header_parts[22]); + + // Unset the header params + unset($header_parts); + } + + // Define starting X and Y + $x = 0; + $y = 1; + + // Create newimage + $image = imagecreatetruecolor($width, $height); + + // Grab the body from the image + $body = substr($hex, 108); + + // Calculate if padding at the end-line is needed + // Divided by two to keep overview. + // 1 byte = 2 HEX-chars + $body_size = (strlen($body) / 2); + $header_size = ($width * $height); + + // Use end-line padding? Only when needed + $usePadding = ($body_size > ($header_size * 3) + 4); + + // Using a for-loop with index-calculation instaid of str_split to avoid large memory consumption + // Calculate the next DWORD-position in the body + for ($i = 0; $i < $body_size; $i += 3) { + // Calculate line-ending and padding + if ($x >= $width) { + // If padding needed, ignore image-padding + // Shift i to the ending of the current 32-bit-block + if ($usePadding) { + $i += $width % 4; + } + + // Reset horizontal position + $x = 0; + + // Raise the height-position (bottom-up) + ++$y; + + // Reached the image-height? Break the for-loop + if ($y > $height) { + break; + } + } + + // Calculation of the RGB-pixel (defined as BGR in image-data) + // Define $i_pos as absolute position in the body + $i_pos = $i * 2; + $r = hexdec($body[$i_pos + 4] . $body[$i_pos + 5]); + $g = hexdec($body[$i_pos + 2] . $body[$i_pos + 3]); + $b = hexdec($body[$i_pos] . $body[$i_pos + 1]); + + // Calculate and draw the pixel + $color = imagecolorallocate($image, $r, $g, $b); + imagesetpixel($image, $x, $height - $y, $color); + + // Raise the horizontal position + ++$x; + } + + // Unset the body / free the memory + unset($body); + + // Return image-object + return $image; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher.php new file mode 100644 index 0000000..c6d0a6f --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher.php @@ -0,0 +1,64 @@ +dggContainer; + } + + /** + * Set Drawing Group Container. + * + * @param Escher\DggContainer $dggContainer + * + * @return Escher\DggContainer + */ + public function setDggContainer($dggContainer) + { + return $this->dggContainer = $dggContainer; + } + + /** + * Get Drawing Container. + * + * @return Escher\DgContainer + */ + public function getDgContainer() + { + return $this->dgContainer; + } + + /** + * Set Drawing Container. + * + * @param Escher\DgContainer $dgContainer + * + * @return Escher\DgContainer + */ + public function setDgContainer($dgContainer) + { + return $this->dgContainer = $dgContainer; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer.php new file mode 100644 index 0000000..b0d75d7 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer.php @@ -0,0 +1,52 @@ +dgId; + } + + public function setDgId($value): void + { + $this->dgId = $value; + } + + public function getLastSpId() + { + return $this->lastSpId; + } + + public function setLastSpId($value): void + { + $this->lastSpId = $value; + } + + public function getSpgrContainer() + { + return $this->spgrContainer; + } + + public function setSpgrContainer($spgrContainer) + { + return $this->spgrContainer = $spgrContainer; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer.php new file mode 100644 index 0000000..6bdc8f7 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer.php @@ -0,0 +1,75 @@ +parent = $parent; + } + + /** + * Get the parent Shape Group Container if any. + */ + public function getParent(): ?self + { + return $this->parent; + } + + /** + * Add a child. This will be either spgrContainer or spContainer. + * + * @param mixed $child + */ + public function addChild($child): void + { + $this->children[] = $child; + $child->setParent($this); + } + + /** + * Get collection of Shape Containers. + */ + public function getChildren() + { + return $this->children; + } + + /** + * Recursively get all spContainers within this spgrContainer. + * + * @return SpgrContainer\SpContainer[] + */ + public function getAllSpContainers() + { + $allSpContainers = []; + + foreach ($this->children as $child) { + if ($child instanceof self) { + $allSpContainers = array_merge($allSpContainers, $child->getAllSpContainers()); + } else { + $allSpContainers[] = $child; + } + } + + return $allSpContainers; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer/SpContainer.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer/SpContainer.php new file mode 100644 index 0000000..8a81ff5 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer/SpContainer.php @@ -0,0 +1,369 @@ +parent = $parent; + } + + /** + * Get the parent Shape Group Container. + * + * @return SpgrContainer + */ + public function getParent() + { + return $this->parent; + } + + /** + * Set whether this is a group shape. + * + * @param bool $value + */ + public function setSpgr($value): void + { + $this->spgr = $value; + } + + /** + * Get whether this is a group shape. + * + * @return bool + */ + public function getSpgr() + { + return $this->spgr; + } + + /** + * Set the shape type. + * + * @param int $value + */ + public function setSpType($value): void + { + $this->spType = $value; + } + + /** + * Get the shape type. + * + * @return int + */ + public function getSpType() + { + return $this->spType; + } + + /** + * Set the shape flag. + * + * @param int $value + */ + public function setSpFlag($value): void + { + $this->spFlag = $value; + } + + /** + * Get the shape flag. + * + * @return int + */ + public function getSpFlag() + { + return $this->spFlag; + } + + /** + * Set the shape index. + * + * @param int $value + */ + public function setSpId($value): void + { + $this->spId = $value; + } + + /** + * Get the shape index. + * + * @return int + */ + public function getSpId() + { + return $this->spId; + } + + /** + * Set an option for the Shape Group Container. + * + * @param int $property The number specifies the option + * @param mixed $value + */ + public function setOPT($property, $value): void + { + $this->OPT[$property] = $value; + } + + /** + * Get an option for the Shape Group Container. + * + * @param int $property The number specifies the option + * + * @return mixed + */ + public function getOPT($property) + { + if (isset($this->OPT[$property])) { + return $this->OPT[$property]; + } + + return null; + } + + /** + * Get the collection of options. + * + * @return array + */ + public function getOPTCollection() + { + return $this->OPT; + } + + /** + * Set cell coordinates of upper-left corner of shape. + * + * @param string $value eg: 'A1' + */ + public function setStartCoordinates($value): void + { + $this->startCoordinates = $value; + } + + /** + * Get cell coordinates of upper-left corner of shape. + * + * @return string + */ + public function getStartCoordinates() + { + return $this->startCoordinates; + } + + /** + * Set offset in x-direction of upper-left corner of shape measured in 1/1024 of column width. + * + * @param int $startOffsetX + */ + public function setStartOffsetX($startOffsetX): void + { + $this->startOffsetX = $startOffsetX; + } + + /** + * Get offset in x-direction of upper-left corner of shape measured in 1/1024 of column width. + * + * @return int + */ + public function getStartOffsetX() + { + return $this->startOffsetX; + } + + /** + * Set offset in y-direction of upper-left corner of shape measured in 1/256 of row height. + * + * @param int $startOffsetY + */ + public function setStartOffsetY($startOffsetY): void + { + $this->startOffsetY = $startOffsetY; + } + + /** + * Get offset in y-direction of upper-left corner of shape measured in 1/256 of row height. + * + * @return int + */ + public function getStartOffsetY() + { + return $this->startOffsetY; + } + + /** + * Set cell coordinates of bottom-right corner of shape. + * + * @param string $value eg: 'A1' + */ + public function setEndCoordinates($value): void + { + $this->endCoordinates = $value; + } + + /** + * Get cell coordinates of bottom-right corner of shape. + * + * @return string + */ + public function getEndCoordinates() + { + return $this->endCoordinates; + } + + /** + * Set offset in x-direction of bottom-right corner of shape measured in 1/1024 of column width. + * + * @param int $endOffsetX + */ + public function setEndOffsetX($endOffsetX): void + { + $this->endOffsetX = $endOffsetX; + } + + /** + * Get offset in x-direction of bottom-right corner of shape measured in 1/1024 of column width. + * + * @return int + */ + public function getEndOffsetX() + { + return $this->endOffsetX; + } + + /** + * Set offset in y-direction of bottom-right corner of shape measured in 1/256 of row height. + * + * @param int $endOffsetY + */ + public function setEndOffsetY($endOffsetY): void + { + $this->endOffsetY = $endOffsetY; + } + + /** + * Get offset in y-direction of bottom-right corner of shape measured in 1/256 of row height. + * + * @return int + */ + public function getEndOffsetY() + { + return $this->endOffsetY; + } + + /** + * Get the nesting level of this spContainer. This is the number of spgrContainers between this spContainer and + * the dgContainer. A value of 1 = immediately within first spgrContainer + * Higher nesting level occurs if and only if spContainer is part of a shape group. + * + * @return int Nesting level + */ + public function getNestingLevel() + { + $nestingLevel = 0; + + $parent = $this->getParent(); + while ($parent instanceof SpgrContainer) { + ++$nestingLevel; + $parent = $parent->getParent(); + } + + return $nestingLevel; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer.php new file mode 100644 index 0000000..36806aa --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer.php @@ -0,0 +1,175 @@ +spIdMax; + } + + /** + * Set maximum shape index of all shapes in all drawings (plus one). + * + * @param int $value + */ + public function setSpIdMax($value): void + { + $this->spIdMax = $value; + } + + /** + * Get total number of drawings saved. + * + * @return int + */ + public function getCDgSaved() + { + return $this->cDgSaved; + } + + /** + * Set total number of drawings saved. + * + * @param int $value + */ + public function setCDgSaved($value): void + { + $this->cDgSaved = $value; + } + + /** + * Get total number of shapes saved (including group shapes). + * + * @return int + */ + public function getCSpSaved() + { + return $this->cSpSaved; + } + + /** + * Set total number of shapes saved (including group shapes). + * + * @param int $value + */ + public function setCSpSaved($value): void + { + $this->cSpSaved = $value; + } + + /** + * Get BLIP Store Container. + * + * @return DggContainer\BstoreContainer + */ + public function getBstoreContainer() + { + return $this->bstoreContainer; + } + + /** + * Set BLIP Store Container. + * + * @param DggContainer\BstoreContainer $bstoreContainer + */ + public function setBstoreContainer($bstoreContainer): void + { + $this->bstoreContainer = $bstoreContainer; + } + + /** + * Set an option for the drawing group. + * + * @param int $property The number specifies the option + * @param mixed $value + */ + public function setOPT($property, $value): void + { + $this->OPT[$property] = $value; + } + + /** + * Get an option for the drawing group. + * + * @param int $property The number specifies the option + * + * @return mixed + */ + public function getOPT($property) + { + if (isset($this->OPT[$property])) { + return $this->OPT[$property]; + } + + return null; + } + + /** + * Get identifier clusters. + * + * @return array + */ + public function getIDCLs() + { + return $this->IDCLs; + } + + /** + * Set identifier clusters. [ => , ...]. + * + * @param array $IDCLs + */ + public function setIDCLs($IDCLs): void + { + $this->IDCLs = $IDCLs; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer.php new file mode 100644 index 0000000..7203b66 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer.php @@ -0,0 +1,32 @@ +BSECollection[] = $BSE; + $BSE->setParent($this); + } + + /** + * Get the collection of BLIP Store Entries. + * + * @return BstoreContainer\BSE[] + */ + public function getBSECollection() + { + return $this->BSECollection; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE.php new file mode 100644 index 0000000..d24af3f --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE.php @@ -0,0 +1,87 @@ +parent = $parent; + } + + /** + * Get the BLIP. + * + * @return BSE\Blip + */ + public function getBlip() + { + return $this->blip; + } + + /** + * Set the BLIP. + */ + public function setBlip(BSE\Blip $blip): void + { + $this->blip = $blip; + $blip->setParent($this); + } + + /** + * Get the BLIP type. + * + * @return int + */ + public function getBlipType() + { + return $this->blipType; + } + + /** + * Set the BLIP type. + * + * @param int $blipType + */ + public function setBlipType($blipType): void + { + $this->blipType = $blipType; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE/Blip.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE/Blip.php new file mode 100644 index 0000000..03b261f --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE/Blip.php @@ -0,0 +1,58 @@ +data; + } + + /** + * Set the raw image data. + * + * @param string $data + */ + public function setData($data): void + { + $this->data = $data; + } + + /** + * Set parent BSE. + */ + public function setParent(BSE $parent): void + { + $this->parent = $parent; + } + + /** + * Get parent BSE. + */ + public function getParent(): BSE + { + return $this->parent; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/File.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/File.php new file mode 100644 index 0000000..293b15c --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/File.php @@ -0,0 +1,185 @@ +open($zipFile); + if ($res === true) { + $returnValue = ($zip->getFromName($archiveFile) !== false); + $zip->close(); + + return $returnValue; + } + } + + return false; + } + + return file_exists($filename); + } + + /** + * Returns canonicalized absolute pathname, also for ZIP archives. + */ + public static function realpath(string $filename): string + { + // Returnvalue + $returnValue = ''; + + // Try using realpath() + if (file_exists($filename)) { + $returnValue = realpath($filename) ?: ''; + } + + // Found something? + if ($returnValue === '') { + $pathArray = explode('/', $filename); + while (in_array('..', $pathArray) && $pathArray[0] != '..') { + $iMax = count($pathArray); + for ($i = 0; $i < $iMax; ++$i) { + if ($pathArray[$i] == '..' && $i > 0) { + unset($pathArray[$i], $pathArray[$i - 1]); + + break; + } + } + } + $returnValue = implode('/', $pathArray); + } + + // Return + return $returnValue; + } + + /** + * Get the systems temporary directory. + */ + public static function sysGetTempDir(): string + { + $path = sys_get_temp_dir(); + if (self::$useUploadTempDirectory) { + // use upload-directory when defined to allow running on environments having very restricted + // open_basedir configs + if (ini_get('upload_tmp_dir') !== false) { + if ($temp = ini_get('upload_tmp_dir')) { + if (file_exists($temp)) { + $path = $temp; + } + } + } + } + + return realpath($path) ?: ''; + } + + public static function temporaryFilename(): string + { + $filename = tempnam(self::sysGetTempDir(), 'phpspreadsheet'); + if ($filename === false) { + throw new Exception('Could not create temporary file'); + } + + return $filename; + } + + /** + * Assert that given path is an existing file and is readable, otherwise throw exception. + */ + public static function assertFile(string $filename, string $zipMember = ''): void + { + if (!is_file($filename)) { + throw new ReaderException('File "' . $filename . '" does not exist.'); + } + + if (!is_readable($filename)) { + throw new ReaderException('Could not open "' . $filename . '" for reading.'); + } + + if ($zipMember !== '') { + $zipfile = "zip://$filename#$zipMember"; + if (!self::fileExists($zipfile)) { + throw new ReaderException("Could not find zip member $zipfile"); + } + } + } + + /** + * Same as assertFile, except return true/false and don't throw Exception. + */ + public static function testFileNoThrow(string $filename, ?string $zipMember = null): bool + { + if (!is_file($filename)) { + return false; + } + if (!is_readable($filename)) { + return false; + } + if ($zipMember === null) { + return true; + } + // validate zip, but don't check specific member + if ($zipMember === '') { + return self::validateZipFirst4($filename); + } + + return self::fileExists("zip://$filename#$zipMember"); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Font.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Font.php new file mode 100644 index 0000000..9a74bef --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Font.php @@ -0,0 +1,758 @@ + [ + 1 => ['px' => 24, 'width' => 12.00000000], + 2 => ['px' => 24, 'width' => 12.00000000], + 3 => ['px' => 32, 'width' => 10.66406250], + 4 => ['px' => 32, 'width' => 10.66406250], + 5 => ['px' => 40, 'width' => 10.00000000], + 6 => ['px' => 48, 'width' => 9.59765625], + 7 => ['px' => 48, 'width' => 9.59765625], + 8 => ['px' => 56, 'width' => 9.33203125], + 9 => ['px' => 64, 'width' => 9.14062500], + 10 => ['px' => 64, 'width' => 9.14062500], + ], + 'Calibri' => [ + 1 => ['px' => 24, 'width' => 12.00000000], + 2 => ['px' => 24, 'width' => 12.00000000], + 3 => ['px' => 32, 'width' => 10.66406250], + 4 => ['px' => 32, 'width' => 10.66406250], + 5 => ['px' => 40, 'width' => 10.00000000], + 6 => ['px' => 48, 'width' => 9.59765625], + 7 => ['px' => 48, 'width' => 9.59765625], + 8 => ['px' => 56, 'width' => 9.33203125], + 9 => ['px' => 56, 'width' => 9.33203125], + 10 => ['px' => 64, 'width' => 9.14062500], + 11 => ['px' => 64, 'width' => 9.14062500], + ], + 'Verdana' => [ + 1 => ['px' => 24, 'width' => 12.00000000], + 2 => ['px' => 24, 'width' => 12.00000000], + 3 => ['px' => 32, 'width' => 10.66406250], + 4 => ['px' => 32, 'width' => 10.66406250], + 5 => ['px' => 40, 'width' => 10.00000000], + 6 => ['px' => 48, 'width' => 9.59765625], + 7 => ['px' => 48, 'width' => 9.59765625], + 8 => ['px' => 64, 'width' => 9.14062500], + 9 => ['px' => 72, 'width' => 9.00000000], + 10 => ['px' => 72, 'width' => 9.00000000], + ], + ]; + + /** + * Set autoSize method. + * + * @param string $method see self::AUTOSIZE_METHOD_* + * + * @return bool Success or failure + */ + public static function setAutoSizeMethod($method) + { + if (!in_array($method, self::$autoSizeMethods)) { + return false; + } + self::$autoSizeMethod = $method; + + return true; + } + + /** + * Get autoSize method. + * + * @return string + */ + public static function getAutoSizeMethod() + { + return self::$autoSizeMethod; + } + + /** + * Set the path to the folder containing .ttf files. There should be a trailing slash. + * Typical locations on variout some platforms: + *
          + *
        • C:/Windows/Fonts/
        • + *
        • /usr/share/fonts/truetype/
        • + *
        • ~/.fonts/
        • + *
        . + * + * @param string $folderPath + */ + public static function setTrueTypeFontPath($folderPath): void + { + self::$trueTypeFontPath = $folderPath; + } + + /** + * Get the path to the folder containing .ttf files. + * + * @return string + */ + public static function getTrueTypeFontPath() + { + return self::$trueTypeFontPath; + } + + /** + * Calculate an (approximate) OpenXML column width, based on font size and text contained. + * + * @param FontStyle $font Font object + * @param RichText|string $cellText Text to calculate width + * @param int $rotation Rotation angle + * @param null|FontStyle $defaultFont Font object + * + * @return int Column width + */ + public static function calculateColumnWidth(FontStyle $font, $cellText = '', $rotation = 0, ?FontStyle $defaultFont = null) + { + // If it is rich text, use plain text + if ($cellText instanceof RichText) { + $cellText = $cellText->getPlainText(); + } + + // Special case if there are one or more newline characters ("\n") + if (strpos($cellText ?? '', "\n") !== false) { + $lineTexts = explode("\n", $cellText); + $lineWidths = []; + foreach ($lineTexts as $lineText) { + $lineWidths[] = self::calculateColumnWidth($font, $lineText, $rotation = 0, $defaultFont); + } + + return max($lineWidths); // width of longest line in cell + } + + // Try to get the exact text width in pixels + $approximate = self::$autoSizeMethod == self::AUTOSIZE_METHOD_APPROX; + $columnWidth = 0; + if (!$approximate) { + $columnWidthAdjust = ceil(self::getTextWidthPixelsExact('n', $font, 0) * 1.07); + + try { + // Width of text in pixels excl. padding + // and addition because Excel adds some padding, just use approx width of 'n' glyph + $columnWidth = self::getTextWidthPixelsExact($cellText, $font, $rotation) + $columnWidthAdjust; + } catch (PhpSpreadsheetException $e) { + $approximate = true; + } + } + + if ($approximate) { + $columnWidthAdjust = self::getTextWidthPixelsApprox('n', $font, 0); + // Width of text in pixels excl. padding, approximation + // and addition because Excel adds some padding, just use approx width of 'n' glyph + $columnWidth = self::getTextWidthPixelsApprox($cellText, $font, $rotation) + $columnWidthAdjust; + } + + // Convert from pixel width to column width + $columnWidth = Drawing::pixelsToCellDimension((int) $columnWidth, $defaultFont); + + // Return + return (int) round($columnWidth, 6); + } + + /** + * Get GD text width in pixels for a string of text in a certain font at a certain rotation angle. + */ + public static function getTextWidthPixelsExact(string $text, FontStyle $font, int $rotation = 0): int + { + if (!function_exists('imagettfbbox')) { + throw new PhpSpreadsheetException('GD library needs to be enabled'); + } + + // font size should really be supplied in pixels in GD2, + // but since GD2 seems to assume 72dpi, pixels and points are the same + $fontFile = self::getTrueTypeFontFileFromFont($font); + $textBox = imagettfbbox($font->getSize(), $rotation, $fontFile, $text); + + // Get corners positions + $lowerLeftCornerX = $textBox[0]; + $lowerRightCornerX = $textBox[2]; + $upperRightCornerX = $textBox[4]; + $upperLeftCornerX = $textBox[6]; + + // Consider the rotation when calculating the width + return max($lowerRightCornerX - $upperLeftCornerX, $upperRightCornerX - $lowerLeftCornerX); + } + + /** + * Get approximate width in pixels for a string of text in a certain font at a certain rotation angle. + * + * @param string $columnText + * @param int $rotation + * + * @return int Text width in pixels (no padding added) + */ + public static function getTextWidthPixelsApprox($columnText, FontStyle $font, $rotation = 0) + { + $fontName = $font->getName(); + $fontSize = $font->getSize(); + + // Calculate column width in pixels. We assume fixed glyph width. Result varies with font name and size. + switch ($fontName) { + case 'Calibri': + // value 8.26 was found via interpolation by inspecting real Excel files with Calibri 11 font. + $columnWidth = (int) (8.26 * StringHelper::countCharacters($columnText)); + $columnWidth = $columnWidth * $fontSize / 11; // extrapolate from font size + + break; + case 'Arial': + // value 8 was set because of experience in different exports at Arial 10 font. + $columnWidth = (int) (8 * StringHelper::countCharacters($columnText)); + $columnWidth = $columnWidth * $fontSize / 10; // extrapolate from font size + + break; + case 'Verdana': + // value 8 was found via interpolation by inspecting real Excel files with Verdana 10 font. + $columnWidth = (int) (8 * StringHelper::countCharacters($columnText)); + $columnWidth = $columnWidth * $fontSize / 10; // extrapolate from font size + + break; + default: + // just assume Calibri + $columnWidth = (int) (8.26 * StringHelper::countCharacters($columnText)); + $columnWidth = $columnWidth * $fontSize / 11; // extrapolate from font size + + break; + } + + // Calculate approximate rotated column width + if ($rotation !== 0) { + if ($rotation == Alignment::TEXTROTATION_STACK_PHPSPREADSHEET) { + // stacked text + $columnWidth = 4; // approximation + } else { + // rotated text + $columnWidth = $columnWidth * cos(deg2rad($rotation)) + + $fontSize * abs(sin(deg2rad($rotation))) / 5; // approximation + } + } + + // pixel width is an integer + return (int) $columnWidth; + } + + /** + * Calculate an (approximate) pixel size, based on a font points size. + * + * @param int $fontSizeInPoints Font size (in points) + * + * @return int Font size (in pixels) + */ + public static function fontSizeToPixels($fontSizeInPoints) + { + return (int) ((4 / 3) * $fontSizeInPoints); + } + + /** + * Calculate an (approximate) pixel size, based on inch size. + * + * @param int $sizeInInch Font size (in inch) + * + * @return int Size (in pixels) + */ + public static function inchSizeToPixels($sizeInInch) + { + return $sizeInInch * 96; + } + + /** + * Calculate an (approximate) pixel size, based on centimeter size. + * + * @param int $sizeInCm Font size (in centimeters) + * + * @return float Size (in pixels) + */ + public static function centimeterSizeToPixels($sizeInCm) + { + return $sizeInCm * 37.795275591; + } + + /** + * Returns the font path given the font. + * + * @return string Path to TrueType font file + */ + public static function getTrueTypeFontFileFromFont(FontStyle $font) + { + if (!file_exists(self::$trueTypeFontPath) || !is_dir(self::$trueTypeFontPath)) { + throw new PhpSpreadsheetException('Valid directory to TrueType Font files not specified'); + } + + $name = $font->getName(); + $bold = $font->getBold(); + $italic = $font->getItalic(); + + // Check if we can map font to true type font file + switch ($name) { + case 'Arial': + $fontFile = ( + $bold ? ($italic ? self::ARIAL_BOLD_ITALIC : self::ARIAL_BOLD) + : ($italic ? self::ARIAL_ITALIC : self::ARIAL) + ); + + break; + case 'Calibri': + $fontFile = ( + $bold ? ($italic ? self::CALIBRI_BOLD_ITALIC : self::CALIBRI_BOLD) + : ($italic ? self::CALIBRI_ITALIC : self::CALIBRI) + ); + + break; + case 'Courier New': + $fontFile = ( + $bold ? ($italic ? self::COURIER_NEW_BOLD_ITALIC : self::COURIER_NEW_BOLD) + : ($italic ? self::COURIER_NEW_ITALIC : self::COURIER_NEW) + ); + + break; + case 'Comic Sans MS': + $fontFile = ( + $bold ? self::COMIC_SANS_MS_BOLD : self::COMIC_SANS_MS + ); + + break; + case 'Georgia': + $fontFile = ( + $bold ? ($italic ? self::GEORGIA_BOLD_ITALIC : self::GEORGIA_BOLD) + : ($italic ? self::GEORGIA_ITALIC : self::GEORGIA) + ); + + break; + case 'Impact': + $fontFile = self::IMPACT; + + break; + case 'Liberation Sans': + $fontFile = ( + $bold ? ($italic ? self::LIBERATION_SANS_BOLD_ITALIC : self::LIBERATION_SANS_BOLD) + : ($italic ? self::LIBERATION_SANS_ITALIC : self::LIBERATION_SANS) + ); + + break; + case 'Lucida Console': + $fontFile = self::LUCIDA_CONSOLE; + + break; + case 'Lucida Sans Unicode': + $fontFile = self::LUCIDA_SANS_UNICODE; + + break; + case 'Microsoft Sans Serif': + $fontFile = self::MICROSOFT_SANS_SERIF; + + break; + case 'Palatino Linotype': + $fontFile = ( + $bold ? ($italic ? self::PALATINO_LINOTYPE_BOLD_ITALIC : self::PALATINO_LINOTYPE_BOLD) + : ($italic ? self::PALATINO_LINOTYPE_ITALIC : self::PALATINO_LINOTYPE) + ); + + break; + case 'Symbol': + $fontFile = self::SYMBOL; + + break; + case 'Tahoma': + $fontFile = ( + $bold ? self::TAHOMA_BOLD : self::TAHOMA + ); + + break; + case 'Times New Roman': + $fontFile = ( + $bold ? ($italic ? self::TIMES_NEW_ROMAN_BOLD_ITALIC : self::TIMES_NEW_ROMAN_BOLD) + : ($italic ? self::TIMES_NEW_ROMAN_ITALIC : self::TIMES_NEW_ROMAN) + ); + + break; + case 'Trebuchet MS': + $fontFile = ( + $bold ? ($italic ? self::TREBUCHET_MS_BOLD_ITALIC : self::TREBUCHET_MS_BOLD) + : ($italic ? self::TREBUCHET_MS_ITALIC : self::TREBUCHET_MS) + ); + + break; + case 'Verdana': + $fontFile = ( + $bold ? ($italic ? self::VERDANA_BOLD_ITALIC : self::VERDANA_BOLD) + : ($italic ? self::VERDANA_ITALIC : self::VERDANA) + ); + + break; + default: + throw new PhpSpreadsheetException('Unknown font name "' . $name . '". Cannot map to TrueType font file'); + + break; + } + + $fontFile = self::$trueTypeFontPath . $fontFile; + + // Check if file actually exists + if (!file_exists($fontFile)) { + throw new PhpSpreadsheetException('TrueType Font file not found'); + } + + return $fontFile; + } + + /** + * Returns the associated charset for the font name. + * + * @param string $fontName Font name + * + * @return int Character set code + */ + public static function getCharsetFromFontName($fontName) + { + switch ($fontName) { + // Add more cases. Check FONT records in real Excel files. + case 'EucrosiaUPC': + return self::CHARSET_ANSI_THAI; + case 'Wingdings': + return self::CHARSET_SYMBOL; + case 'Wingdings 2': + return self::CHARSET_SYMBOL; + case 'Wingdings 3': + return self::CHARSET_SYMBOL; + default: + return self::CHARSET_ANSI_LATIN; + } + } + + /** + * Get the effective column width for columns without a column dimension or column with width -1 + * For example, for Calibri 11 this is 9.140625 (64 px). + * + * @param FontStyle $font The workbooks default font + * @param bool $returnAsPixels true = return column width in pixels, false = return in OOXML units + * + * @return mixed Column width + */ + public static function getDefaultColumnWidthByFont(FontStyle $font, $returnAsPixels = false) + { + if (isset(self::$defaultColumnWidths[$font->getName()][$font->getSize()])) { + // Exact width can be determined + $columnWidth = $returnAsPixels ? + self::$defaultColumnWidths[$font->getName()][$font->getSize()]['px'] + : self::$defaultColumnWidths[$font->getName()][$font->getSize()]['width']; + } else { + // We don't have data for this particular font and size, use approximation by + // extrapolating from Calibri 11 + $columnWidth = $returnAsPixels ? + self::$defaultColumnWidths['Calibri'][11]['px'] + : self::$defaultColumnWidths['Calibri'][11]['width']; + $columnWidth = $columnWidth * $font->getSize() / 11; + + // Round pixels to closest integer + if ($returnAsPixels) { + $columnWidth = (int) round($columnWidth); + } + } + + return $columnWidth; + } + + /** + * Get the effective row height for rows without a row dimension or rows with height -1 + * For example, for Calibri 11 this is 15 points. + * + * @param FontStyle $font The workbooks default font + * + * @return float Row height in points + */ + public static function getDefaultRowHeightByFont(FontStyle $font) + { + switch ($font->getName()) { + case 'Arial': + switch ($font->getSize()) { + case 10: + // inspection of Arial 10 workbook says 12.75pt ~17px + $rowHeight = 12.75; + + break; + case 9: + // inspection of Arial 9 workbook says 12.00pt ~16px + $rowHeight = 12; + + break; + case 8: + // inspection of Arial 8 workbook says 11.25pt ~15px + $rowHeight = 11.25; + + break; + case 7: + // inspection of Arial 7 workbook says 9.00pt ~12px + $rowHeight = 9; + + break; + case 6: + case 5: + // inspection of Arial 5,6 workbook says 8.25pt ~11px + $rowHeight = 8.25; + + break; + case 4: + // inspection of Arial 4 workbook says 6.75pt ~9px + $rowHeight = 6.75; + + break; + case 3: + // inspection of Arial 3 workbook says 6.00pt ~8px + $rowHeight = 6; + + break; + case 2: + case 1: + // inspection of Arial 1,2 workbook says 5.25pt ~7px + $rowHeight = 5.25; + + break; + default: + // use Arial 10 workbook as an approximation, extrapolation + $rowHeight = 12.75 * $font->getSize() / 10; + + break; + } + + break; + case 'Calibri': + switch ($font->getSize()) { + case 11: + // inspection of Calibri 11 workbook says 15.00pt ~20px + $rowHeight = 15; + + break; + case 10: + // inspection of Calibri 10 workbook says 12.75pt ~17px + $rowHeight = 12.75; + + break; + case 9: + // inspection of Calibri 9 workbook says 12.00pt ~16px + $rowHeight = 12; + + break; + case 8: + // inspection of Calibri 8 workbook says 11.25pt ~15px + $rowHeight = 11.25; + + break; + case 7: + // inspection of Calibri 7 workbook says 9.00pt ~12px + $rowHeight = 9; + + break; + case 6: + case 5: + // inspection of Calibri 5,6 workbook says 8.25pt ~11px + $rowHeight = 8.25; + + break; + case 4: + // inspection of Calibri 4 workbook says 6.75pt ~9px + $rowHeight = 6.75; + + break; + case 3: + // inspection of Calibri 3 workbook says 6.00pt ~8px + $rowHeight = 6.00; + + break; + case 2: + case 1: + // inspection of Calibri 1,2 workbook says 5.25pt ~7px + $rowHeight = 5.25; + + break; + default: + // use Calibri 11 workbook as an approximation, extrapolation + $rowHeight = 15 * $font->getSize() / 11; + + break; + } + + break; + case 'Verdana': + switch ($font->getSize()) { + case 10: + // inspection of Verdana 10 workbook says 12.75pt ~17px + $rowHeight = 12.75; + + break; + case 9: + // inspection of Verdana 9 workbook says 11.25pt ~15px + $rowHeight = 11.25; + + break; + case 8: + // inspection of Verdana 8 workbook says 10.50pt ~14px + $rowHeight = 10.50; + + break; + case 7: + // inspection of Verdana 7 workbook says 9.00pt ~12px + $rowHeight = 9.00; + + break; + case 6: + case 5: + // inspection of Verdana 5,6 workbook says 8.25pt ~11px + $rowHeight = 8.25; + + break; + case 4: + // inspection of Verdana 4 workbook says 6.75pt ~9px + $rowHeight = 6.75; + + break; + case 3: + // inspection of Verdana 3 workbook says 6.00pt ~8px + $rowHeight = 6; + + break; + case 2: + case 1: + // inspection of Verdana 1,2 workbook says 5.25pt ~7px + $rowHeight = 5.25; + + break; + default: + // use Verdana 10 workbook as an approximation, extrapolation + $rowHeight = 12.75 * $font->getSize() / 10; + + break; + } + + break; + default: + // just use Calibri as an approximation + $rowHeight = 15 * $font->getSize() / 11; + + break; + } + + return $rowHeight; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/IntOrFloat.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/IntOrFloat.php new file mode 100644 index 0000000..060f09c --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/IntOrFloat.php @@ -0,0 +1,21 @@ +L = $A->getArray(); + $this->m = $A->getRowDimension(); + + for ($i = 0; $i < $this->m; ++$i) { + for ($j = $i; $j < $this->m; ++$j) { + for ($sum = $this->L[$i][$j], $k = $i - 1; $k >= 0; --$k) { + $sum -= $this->L[$i][$k] * $this->L[$j][$k]; + } + if ($i == $j) { + if ($sum >= 0) { + $this->L[$i][$i] = sqrt($sum); + } else { + $this->isspd = false; + } + } else { + if ($this->L[$i][$i] != 0) { + $this->L[$j][$i] = $sum / $this->L[$i][$i]; + } + } + } + + for ($k = $i + 1; $k < $this->m; ++$k) { + $this->L[$i][$k] = 0.0; + } + } + } + + /** + * Is the matrix symmetric and positive definite? + * + * @return bool + */ + public function isSPD() + { + return $this->isspd; + } + + /** + * getL. + * + * Return triangular factor. + * + * @return Matrix Lower triangular matrix + */ + public function getL() + { + return new Matrix($this->L); + } + + /** + * Solve A*X = B. + * + * @param Matrix $B Row-equal matrix + * + * @return Matrix L * L' * X = B + */ + public function solve(Matrix $B) + { + if ($B->getRowDimension() == $this->m) { + if ($this->isspd) { + $X = $B->getArray(); + $nx = $B->getColumnDimension(); + + for ($k = 0; $k < $this->m; ++$k) { + for ($i = $k + 1; $i < $this->m; ++$i) { + for ($j = 0; $j < $nx; ++$j) { + $X[$i][$j] -= $X[$k][$j] * $this->L[$i][$k]; + } + } + for ($j = 0; $j < $nx; ++$j) { + $X[$k][$j] /= $this->L[$k][$k]; + } + } + + for ($k = $this->m - 1; $k >= 0; --$k) { + for ($j = 0; $j < $nx; ++$j) { + $X[$k][$j] /= $this->L[$k][$k]; + } + for ($i = 0; $i < $k; ++$i) { + for ($j = 0; $j < $nx; ++$j) { + $X[$i][$j] -= $X[$k][$j] * $this->L[$k][$i]; + } + } + } + + return new Matrix($X, $this->m, $nx); + } + + throw new CalculationException(Matrix::MATRIX_SPD_EXCEPTION); + } + + throw new CalculationException(Matrix::MATRIX_DIMENSION_EXCEPTION); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/EigenvalueDecomposition.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/EigenvalueDecomposition.php new file mode 100644 index 0000000..5c6ccfd --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/EigenvalueDecomposition.php @@ -0,0 +1,870 @@ +d = $this->V[$this->n - 1]; + $j = 0; + // Householder reduction to tridiagonal form. + for ($i = $this->n - 1; $i > 0; --$i) { + $i_ = $i - 1; + // Scale to avoid under/overflow. + $h = $scale = 0.0; + $scale += array_sum(array_map('abs', $this->d)); + if ($scale == 0.0) { + $this->e[$i] = $this->d[$i_]; + $this->d = array_slice($this->V[$i_], 0, $i_); + for ($j = 0; $j < $i; ++$j) { + $this->V[$j][$i] = $this->V[$i][$j] = 0.0; + } + } else { + // Generate Householder vector. + for ($k = 0; $k < $i; ++$k) { + $this->d[$k] /= $scale; + $h += $this->d[$k] ** 2; + } + $f = $this->d[$i_]; + $g = sqrt($h); + if ($f > 0) { + $g = -$g; + } + $this->e[$i] = $scale * $g; + $h = $h - $f * $g; + $this->d[$i_] = $f - $g; + for ($j = 0; $j < $i; ++$j) { + $this->e[$j] = 0.0; + } + // Apply similarity transformation to remaining columns. + for ($j = 0; $j < $i; ++$j) { + $f = $this->d[$j]; + $this->V[$j][$i] = $f; + $g = $this->e[$j] + $this->V[$j][$j] * $f; + for ($k = $j + 1; $k <= $i_; ++$k) { + $g += $this->V[$k][$j] * $this->d[$k]; + $this->e[$k] += $this->V[$k][$j] * $f; + } + $this->e[$j] = $g; + } + $f = 0.0; + for ($j = 0; $j < $i; ++$j) { + $this->e[$j] /= $h; + $f += $this->e[$j] * $this->d[$j]; + } + $hh = $f / (2 * $h); + for ($j = 0; $j < $i; ++$j) { + $this->e[$j] -= $hh * $this->d[$j]; + } + for ($j = 0; $j < $i; ++$j) { + $f = $this->d[$j]; + $g = $this->e[$j]; + for ($k = $j; $k <= $i_; ++$k) { + $this->V[$k][$j] -= ($f * $this->e[$k] + $g * $this->d[$k]); + } + $this->d[$j] = $this->V[$i - 1][$j]; + $this->V[$i][$j] = 0.0; + } + } + $this->d[$i] = $h; + } + + // Accumulate transformations. + for ($i = 0; $i < $this->n - 1; ++$i) { + $this->V[$this->n - 1][$i] = $this->V[$i][$i]; + $this->V[$i][$i] = 1.0; + $h = $this->d[$i + 1]; + if ($h != 0.0) { + for ($k = 0; $k <= $i; ++$k) { + $this->d[$k] = $this->V[$k][$i + 1] / $h; + } + for ($j = 0; $j <= $i; ++$j) { + $g = 0.0; + for ($k = 0; $k <= $i; ++$k) { + $g += $this->V[$k][$i + 1] * $this->V[$k][$j]; + } + for ($k = 0; $k <= $i; ++$k) { + $this->V[$k][$j] -= $g * $this->d[$k]; + } + } + } + for ($k = 0; $k <= $i; ++$k) { + $this->V[$k][$i + 1] = 0.0; + } + } + + $this->d = $this->V[$this->n - 1]; + $this->V[$this->n - 1] = array_fill(0, $j, 0.0); + $this->V[$this->n - 1][$this->n - 1] = 1.0; + $this->e[0] = 0.0; + } + + /** + * Symmetric tridiagonal QL algorithm. + * + * This is derived from the Algol procedures tql2, by + * Bowdler, Martin, Reinsch, and Wilkinson, Handbook for + * Auto. Comp., Vol.ii-Linear Algebra, and the corresponding + * Fortran subroutine in EISPACK. + */ + private function tql2(): void + { + for ($i = 1; $i < $this->n; ++$i) { + $this->e[$i - 1] = $this->e[$i]; + } + $this->e[$this->n - 1] = 0.0; + $f = 0.0; + $tst1 = 0.0; + $eps = 2.0 ** (-52.0); + + for ($l = 0; $l < $this->n; ++$l) { + // Find small subdiagonal element + $tst1 = max($tst1, abs($this->d[$l]) + abs($this->e[$l])); + $m = $l; + while ($m < $this->n) { + if (abs($this->e[$m]) <= $eps * $tst1) { + break; + } + ++$m; + } + // If m == l, $this->d[l] is an eigenvalue, + // otherwise, iterate. + if ($m > $l) { + $iter = 0; + do { + // Could check iteration count here. + ++$iter; + // Compute implicit shift + $g = $this->d[$l]; + $p = ($this->d[$l + 1] - $g) / (2.0 * $this->e[$l]); + $r = hypo($p, 1.0); + if ($p < 0) { + $r *= -1; + } + $this->d[$l] = $this->e[$l] / ($p + $r); + $this->d[$l + 1] = $this->e[$l] * ($p + $r); + $dl1 = $this->d[$l + 1]; + $h = $g - $this->d[$l]; + for ($i = $l + 2; $i < $this->n; ++$i) { + $this->d[$i] -= $h; + } + $f += $h; + // Implicit QL transformation. + $p = $this->d[$m]; + $c = 1.0; + $c2 = $c3 = $c; + $el1 = $this->e[$l + 1]; + $s = $s2 = 0.0; + for ($i = $m - 1; $i >= $l; --$i) { + $c3 = $c2; + $c2 = $c; + $s2 = $s; + $g = $c * $this->e[$i]; + $h = $c * $p; + $r = hypo($p, $this->e[$i]); + $this->e[$i + 1] = $s * $r; + $s = $this->e[$i] / $r; + $c = $p / $r; + $p = $c * $this->d[$i] - $s * $g; + $this->d[$i + 1] = $h + $s * ($c * $g + $s * $this->d[$i]); + // Accumulate transformation. + for ($k = 0; $k < $this->n; ++$k) { + $h = $this->V[$k][$i + 1]; + $this->V[$k][$i + 1] = $s * $this->V[$k][$i] + $c * $h; + $this->V[$k][$i] = $c * $this->V[$k][$i] - $s * $h; + } + } + $p = -$s * $s2 * $c3 * $el1 * $this->e[$l] / $dl1; + $this->e[$l] = $s * $p; + $this->d[$l] = $c * $p; + // Check for convergence. + } while (abs($this->e[$l]) > $eps * $tst1); + } + $this->d[$l] = $this->d[$l] + $f; + $this->e[$l] = 0.0; + } + + // Sort eigenvalues and corresponding vectors. + for ($i = 0; $i < $this->n - 1; ++$i) { + $k = $i; + $p = $this->d[$i]; + for ($j = $i + 1; $j < $this->n; ++$j) { + if ($this->d[$j] < $p) { + $k = $j; + $p = $this->d[$j]; + } + } + if ($k != $i) { + $this->d[$k] = $this->d[$i]; + $this->d[$i] = $p; + for ($j = 0; $j < $this->n; ++$j) { + $p = $this->V[$j][$i]; + $this->V[$j][$i] = $this->V[$j][$k]; + $this->V[$j][$k] = $p; + } + } + } + } + + /** + * Nonsymmetric reduction to Hessenberg form. + * + * This is derived from the Algol procedures orthes and ortran, + * by Martin and Wilkinson, Handbook for Auto. Comp., + * Vol.ii-Linear Algebra, and the corresponding + * Fortran subroutines in EISPACK. + */ + private function orthes(): void + { + $low = 0; + $high = $this->n - 1; + + for ($m = $low + 1; $m <= $high - 1; ++$m) { + // Scale column. + $scale = 0.0; + for ($i = $m; $i <= $high; ++$i) { + $scale = $scale + abs($this->H[$i][$m - 1]); + } + if ($scale != 0.0) { + // Compute Householder transformation. + $h = 0.0; + for ($i = $high; $i >= $m; --$i) { + $this->ort[$i] = $this->H[$i][$m - 1] / $scale; + $h += $this->ort[$i] * $this->ort[$i]; + } + $g = sqrt($h); + if ($this->ort[$m] > 0) { + $g *= -1; + } + $h -= $this->ort[$m] * $g; + $this->ort[$m] -= $g; + // Apply Householder similarity transformation + // H = (I -u * u' / h) * H * (I -u * u') / h) + for ($j = $m; $j < $this->n; ++$j) { + $f = 0.0; + for ($i = $high; $i >= $m; --$i) { + $f += $this->ort[$i] * $this->H[$i][$j]; + } + $f /= $h; + for ($i = $m; $i <= $high; ++$i) { + $this->H[$i][$j] -= $f * $this->ort[$i]; + } + } + for ($i = 0; $i <= $high; ++$i) { + $f = 0.0; + for ($j = $high; $j >= $m; --$j) { + $f += $this->ort[$j] * $this->H[$i][$j]; + } + $f = $f / $h; + for ($j = $m; $j <= $high; ++$j) { + $this->H[$i][$j] -= $f * $this->ort[$j]; + } + } + $this->ort[$m] = $scale * $this->ort[$m]; + $this->H[$m][$m - 1] = $scale * $g; + } + } + + // Accumulate transformations (Algol's ortran). + for ($i = 0; $i < $this->n; ++$i) { + for ($j = 0; $j < $this->n; ++$j) { + $this->V[$i][$j] = ($i == $j ? 1.0 : 0.0); + } + } + for ($m = $high - 1; $m >= $low + 1; --$m) { + if ($this->H[$m][$m - 1] != 0.0) { + for ($i = $m + 1; $i <= $high; ++$i) { + $this->ort[$i] = $this->H[$i][$m - 1]; + } + for ($j = $m; $j <= $high; ++$j) { + $g = 0.0; + for ($i = $m; $i <= $high; ++$i) { + $g += $this->ort[$i] * $this->V[$i][$j]; + } + // Double division avoids possible underflow + $g = ($g / $this->ort[$m]) / $this->H[$m][$m - 1]; + for ($i = $m; $i <= $high; ++$i) { + $this->V[$i][$j] += $g * $this->ort[$i]; + } + } + } + } + } + + /** + * Performs complex division. + * + * @param mixed $xr + * @param mixed $xi + * @param mixed $yr + * @param mixed $yi + */ + private function cdiv($xr, $xi, $yr, $yi): void + { + if (abs($yr) > abs($yi)) { + $r = $yi / $yr; + $d = $yr + $r * $yi; + $this->cdivr = ($xr + $r * $xi) / $d; + $this->cdivi = ($xi - $r * $xr) / $d; + } else { + $r = $yr / $yi; + $d = $yi + $r * $yr; + $this->cdivr = ($r * $xr + $xi) / $d; + $this->cdivi = ($r * $xi - $xr) / $d; + } + } + + /** + * Nonsymmetric reduction from Hessenberg to real Schur form. + * + * Code is derived from the Algol procedure hqr2, + * by Martin and Wilkinson, Handbook for Auto. Comp., + * Vol.ii-Linear Algebra, and the corresponding + * Fortran subroutine in EISPACK. + */ + private function hqr2(): void + { + // Initialize + $nn = $this->n; + $n = $nn - 1; + $low = 0; + $high = $nn - 1; + $eps = 2.0 ** (-52.0); + $exshift = 0.0; + $p = $q = $r = $s = $z = 0; + // Store roots isolated by balanc and compute matrix norm + $norm = 0.0; + + for ($i = 0; $i < $nn; ++$i) { + if (($i < $low) || ($i > $high)) { + $this->d[$i] = $this->H[$i][$i]; + $this->e[$i] = 0.0; + } + for ($j = max($i - 1, 0); $j < $nn; ++$j) { + $norm = $norm + abs($this->H[$i][$j]); + } + } + + // Outer loop over eigenvalue index + $iter = 0; + while ($n >= $low) { + // Look for single small sub-diagonal element + $l = $n; + while ($l > $low) { + $s = abs($this->H[$l - 1][$l - 1]) + abs($this->H[$l][$l]); + if ($s == 0.0) { + $s = $norm; + } + if (abs($this->H[$l][$l - 1]) < $eps * $s) { + break; + } + --$l; + } + // Check for convergence + // One root found + if ($l == $n) { + $this->H[$n][$n] = $this->H[$n][$n] + $exshift; + $this->d[$n] = $this->H[$n][$n]; + $this->e[$n] = 0.0; + --$n; + $iter = 0; + // Two roots found + } elseif ($l == $n - 1) { + $w = $this->H[$n][$n - 1] * $this->H[$n - 1][$n]; + $p = ($this->H[$n - 1][$n - 1] - $this->H[$n][$n]) / 2.0; + $q = $p * $p + $w; + $z = sqrt(abs($q)); + $this->H[$n][$n] = $this->H[$n][$n] + $exshift; + $this->H[$n - 1][$n - 1] = $this->H[$n - 1][$n - 1] + $exshift; + $x = $this->H[$n][$n]; + // Real pair + if ($q >= 0) { + if ($p >= 0) { + $z = $p + $z; + } else { + $z = $p - $z; + } + $this->d[$n - 1] = $x + $z; + $this->d[$n] = $this->d[$n - 1]; + if ($z != 0.0) { + $this->d[$n] = $x - $w / $z; + } + $this->e[$n - 1] = 0.0; + $this->e[$n] = 0.0; + $x = $this->H[$n][$n - 1]; + $s = abs($x) + abs($z); + $p = $x / $s; + $q = $z / $s; + $r = sqrt($p * $p + $q * $q); + $p = $p / $r; + $q = $q / $r; + // Row modification + for ($j = $n - 1; $j < $nn; ++$j) { + $z = $this->H[$n - 1][$j]; + $this->H[$n - 1][$j] = $q * $z + $p * $this->H[$n][$j]; + $this->H[$n][$j] = $q * $this->H[$n][$j] - $p * $z; + } + // Column modification + for ($i = 0; $i <= $n; ++$i) { + $z = $this->H[$i][$n - 1]; + $this->H[$i][$n - 1] = $q * $z + $p * $this->H[$i][$n]; + $this->H[$i][$n] = $q * $this->H[$i][$n] - $p * $z; + } + // Accumulate transformations + for ($i = $low; $i <= $high; ++$i) { + $z = $this->V[$i][$n - 1]; + $this->V[$i][$n - 1] = $q * $z + $p * $this->V[$i][$n]; + $this->V[$i][$n] = $q * $this->V[$i][$n] - $p * $z; + } + // Complex pair + } else { + $this->d[$n - 1] = $x + $p; + $this->d[$n] = $x + $p; + $this->e[$n - 1] = $z; + $this->e[$n] = -$z; + } + $n = $n - 2; + $iter = 0; + // No convergence yet + } else { + // Form shift + $x = $this->H[$n][$n]; + $y = 0.0; + $w = 0.0; + if ($l < $n) { + $y = $this->H[$n - 1][$n - 1]; + $w = $this->H[$n][$n - 1] * $this->H[$n - 1][$n]; + } + // Wilkinson's original ad hoc shift + if ($iter == 10) { + $exshift += $x; + for ($i = $low; $i <= $n; ++$i) { + $this->H[$i][$i] -= $x; + } + $s = abs($this->H[$n][$n - 1]) + abs($this->H[$n - 1][$n - 2]); + $x = $y = 0.75 * $s; + $w = -0.4375 * $s * $s; + } + // MATLAB's new ad hoc shift + if ($iter == 30) { + $s = ($y - $x) / 2.0; + $s = $s * $s + $w; + if ($s > 0) { + $s = sqrt($s); + if ($y < $x) { + $s = -$s; + } + $s = $x - $w / (($y - $x) / 2.0 + $s); + for ($i = $low; $i <= $n; ++$i) { + $this->H[$i][$i] -= $s; + } + $exshift += $s; + $x = $y = $w = 0.964; + } + } + // Could check iteration count here. + $iter = $iter + 1; + // Look for two consecutive small sub-diagonal elements + $m = $n - 2; + while ($m >= $l) { + $z = $this->H[$m][$m]; + $r = $x - $z; + $s = $y - $z; + $p = ($r * $s - $w) / $this->H[$m + 1][$m] + $this->H[$m][$m + 1]; + $q = $this->H[$m + 1][$m + 1] - $z - $r - $s; + $r = $this->H[$m + 2][$m + 1]; + $s = abs($p) + abs($q) + abs($r); + $p = $p / $s; + $q = $q / $s; + $r = $r / $s; + if ($m == $l) { + break; + } + if ( + abs($this->H[$m][$m - 1]) * (abs($q) + abs($r)) < + $eps * (abs($p) * (abs($this->H[$m - 1][$m - 1]) + abs($z) + abs($this->H[$m + 1][$m + 1]))) + ) { + break; + } + --$m; + } + for ($i = $m + 2; $i <= $n; ++$i) { + $this->H[$i][$i - 2] = 0.0; + if ($i > $m + 2) { + $this->H[$i][$i - 3] = 0.0; + } + } + // Double QR step involving rows l:n and columns m:n + for ($k = $m; $k <= $n - 1; ++$k) { + $notlast = ($k != $n - 1); + if ($k != $m) { + $p = $this->H[$k][$k - 1]; + $q = $this->H[$k + 1][$k - 1]; + $r = ($notlast ? $this->H[$k + 2][$k - 1] : 0.0); + $x = abs($p) + abs($q) + abs($r); + if ($x != 0.0) { + $p = $p / $x; + $q = $q / $x; + $r = $r / $x; + } + } + if ($x == 0.0) { + break; + } + $s = sqrt($p * $p + $q * $q + $r * $r); + if ($p < 0) { + $s = -$s; + } + if ($s != 0) { + if ($k != $m) { + $this->H[$k][$k - 1] = -$s * $x; + } elseif ($l != $m) { + $this->H[$k][$k - 1] = -$this->H[$k][$k - 1]; + } + $p = $p + $s; + $x = $p / $s; + $y = $q / $s; + $z = $r / $s; + $q = $q / $p; + $r = $r / $p; + // Row modification + for ($j = $k; $j < $nn; ++$j) { + $p = $this->H[$k][$j] + $q * $this->H[$k + 1][$j]; + if ($notlast) { + $p = $p + $r * $this->H[$k + 2][$j]; + $this->H[$k + 2][$j] = $this->H[$k + 2][$j] - $p * $z; + } + $this->H[$k][$j] = $this->H[$k][$j] - $p * $x; + $this->H[$k + 1][$j] = $this->H[$k + 1][$j] - $p * $y; + } + // Column modification + $iMax = min($n, $k + 3); + for ($i = 0; $i <= $iMax; ++$i) { + $p = $x * $this->H[$i][$k] + $y * $this->H[$i][$k + 1]; + if ($notlast) { + $p = $p + $z * $this->H[$i][$k + 2]; + $this->H[$i][$k + 2] = $this->H[$i][$k + 2] - $p * $r; + } + $this->H[$i][$k] = $this->H[$i][$k] - $p; + $this->H[$i][$k + 1] = $this->H[$i][$k + 1] - $p * $q; + } + // Accumulate transformations + for ($i = $low; $i <= $high; ++$i) { + $p = $x * $this->V[$i][$k] + $y * $this->V[$i][$k + 1]; + if ($notlast) { + $p = $p + $z * $this->V[$i][$k + 2]; + $this->V[$i][$k + 2] = $this->V[$i][$k + 2] - $p * $r; + } + $this->V[$i][$k] = $this->V[$i][$k] - $p; + $this->V[$i][$k + 1] = $this->V[$i][$k + 1] - $p * $q; + } + } // ($s != 0) + } // k loop + } // check convergence + } // while ($n >= $low) + + // Backsubstitute to find vectors of upper triangular form + if ($norm == 0.0) { + return; + } + + for ($n = $nn - 1; $n >= 0; --$n) { + $p = $this->d[$n]; + $q = $this->e[$n]; + // Real vector + if ($q == 0) { + $l = $n; + $this->H[$n][$n] = 1.0; + for ($i = $n - 1; $i >= 0; --$i) { + $w = $this->H[$i][$i] - $p; + $r = 0.0; + for ($j = $l; $j <= $n; ++$j) { + $r = $r + $this->H[$i][$j] * $this->H[$j][$n]; + } + if ($this->e[$i] < 0.0) { + $z = $w; + $s = $r; + } else { + $l = $i; + if ($this->e[$i] == 0.0) { + if ($w != 0.0) { + $this->H[$i][$n] = -$r / $w; + } else { + $this->H[$i][$n] = -$r / ($eps * $norm); + } + // Solve real equations + } else { + $x = $this->H[$i][$i + 1]; + $y = $this->H[$i + 1][$i]; + $q = ($this->d[$i] - $p) * ($this->d[$i] - $p) + $this->e[$i] * $this->e[$i]; + $t = ($x * $s - $z * $r) / $q; + $this->H[$i][$n] = $t; + if (abs($x) > abs($z)) { + $this->H[$i + 1][$n] = (-$r - $w * $t) / $x; + } else { + $this->H[$i + 1][$n] = (-$s - $y * $t) / $z; + } + } + // Overflow control + $t = abs($this->H[$i][$n]); + if (($eps * $t) * $t > 1) { + for ($j = $i; $j <= $n; ++$j) { + $this->H[$j][$n] = $this->H[$j][$n] / $t; + } + } + } + } + // Complex vector + } elseif ($q < 0) { + $l = $n - 1; + // Last vector component imaginary so matrix is triangular + if (abs($this->H[$n][$n - 1]) > abs($this->H[$n - 1][$n])) { + $this->H[$n - 1][$n - 1] = $q / $this->H[$n][$n - 1]; + $this->H[$n - 1][$n] = -($this->H[$n][$n] - $p) / $this->H[$n][$n - 1]; + } else { + $this->cdiv(0.0, -$this->H[$n - 1][$n], $this->H[$n - 1][$n - 1] - $p, $q); + $this->H[$n - 1][$n - 1] = $this->cdivr; + $this->H[$n - 1][$n] = $this->cdivi; + } + $this->H[$n][$n - 1] = 0.0; + $this->H[$n][$n] = 1.0; + for ($i = $n - 2; $i >= 0; --$i) { + // double ra,sa,vr,vi; + $ra = 0.0; + $sa = 0.0; + for ($j = $l; $j <= $n; ++$j) { + $ra = $ra + $this->H[$i][$j] * $this->H[$j][$n - 1]; + $sa = $sa + $this->H[$i][$j] * $this->H[$j][$n]; + } + $w = $this->H[$i][$i] - $p; + if ($this->e[$i] < 0.0) { + $z = $w; + $r = $ra; + $s = $sa; + } else { + $l = $i; + if ($this->e[$i] == 0) { + $this->cdiv(-$ra, -$sa, $w, $q); + $this->H[$i][$n - 1] = $this->cdivr; + $this->H[$i][$n] = $this->cdivi; + } else { + // Solve complex equations + $x = $this->H[$i][$i + 1]; + $y = $this->H[$i + 1][$i]; + $vr = ($this->d[$i] - $p) * ($this->d[$i] - $p) + $this->e[$i] * $this->e[$i] - $q * $q; + $vi = ($this->d[$i] - $p) * 2.0 * $q; + if ($vr == 0.0 & $vi == 0.0) { + $vr = $eps * $norm * (abs($w) + abs($q) + abs($x) + abs($y) + abs($z)); + } + $this->cdiv($x * $r - $z * $ra + $q * $sa, $x * $s - $z * $sa - $q * $ra, $vr, $vi); + $this->H[$i][$n - 1] = $this->cdivr; + $this->H[$i][$n] = $this->cdivi; + if (abs($x) > (abs($z) + abs($q))) { + $this->H[$i + 1][$n - 1] = (-$ra - $w * $this->H[$i][$n - 1] + $q * $this->H[$i][$n]) / $x; + $this->H[$i + 1][$n] = (-$sa - $w * $this->H[$i][$n] - $q * $this->H[$i][$n - 1]) / $x; + } else { + $this->cdiv(-$r - $y * $this->H[$i][$n - 1], -$s - $y * $this->H[$i][$n], $z, $q); + $this->H[$i + 1][$n - 1] = $this->cdivr; + $this->H[$i + 1][$n] = $this->cdivi; + } + } + // Overflow control + $t = max(abs($this->H[$i][$n - 1]), abs($this->H[$i][$n])); + if (($eps * $t) * $t > 1) { + for ($j = $i; $j <= $n; ++$j) { + $this->H[$j][$n - 1] = $this->H[$j][$n - 1] / $t; + $this->H[$j][$n] = $this->H[$j][$n] / $t; + } + } + } // end else + } // end for + } // end else for complex case + } // end for + + // Vectors of isolated roots + for ($i = 0; $i < $nn; ++$i) { + if ($i < $low | $i > $high) { + for ($j = $i; $j < $nn; ++$j) { + $this->V[$i][$j] = $this->H[$i][$j]; + } + } + } + + // Back transformation to get eigenvectors of original matrix + for ($j = $nn - 1; $j >= $low; --$j) { + for ($i = $low; $i <= $high; ++$i) { + $z = 0.0; + $kMax = min($j, $high); + for ($k = $low; $k <= $kMax; ++$k) { + $z = $z + $this->V[$i][$k] * $this->H[$k][$j]; + } + $this->V[$i][$j] = $z; + } + } + } + + // end hqr2 + + /** + * Constructor: Check for symmetry, then construct the eigenvalue decomposition. + * + * @param Matrix $Arg A Square matrix + */ + public function __construct(Matrix $Arg) + { + $this->A = $Arg->getArray(); + $this->n = $Arg->getColumnDimension(); + + $issymmetric = true; + for ($j = 0; ($j < $this->n) & $issymmetric; ++$j) { + for ($i = 0; ($i < $this->n) & $issymmetric; ++$i) { + $issymmetric = ($this->A[$i][$j] == $this->A[$j][$i]); + } + } + + if ($issymmetric) { + $this->V = $this->A; + // Tridiagonalize. + $this->tred2(); + // Diagonalize. + $this->tql2(); + } else { + $this->H = $this->A; + $this->ort = []; + // Reduce to Hessenberg form. + $this->orthes(); + // Reduce Hessenberg to real Schur form. + $this->hqr2(); + } + } + + /** + * Return the eigenvector matrix. + * + * @return Matrix V + */ + public function getV() + { + return new Matrix($this->V, $this->n, $this->n); + } + + /** + * Return the real parts of the eigenvalues. + * + * @return array real(diag(D)) + */ + public function getRealEigenvalues() + { + return $this->d; + } + + /** + * Return the imaginary parts of the eigenvalues. + * + * @return array imag(diag(D)) + */ + public function getImagEigenvalues() + { + return $this->e; + } + + /** + * Return the block diagonal eigenvalue matrix. + * + * @return Matrix D + */ + public function getD() + { + $D = []; + for ($i = 0; $i < $this->n; ++$i) { + $D[$i] = array_fill(0, $this->n, 0.0); + $D[$i][$i] = $this->d[$i]; + if ($this->e[$i] == 0) { + continue; + } + $o = ($this->e[$i] > 0) ? $i + 1 : $i - 1; + $D[$i][$o] = $this->e[$i]; + } + + return new Matrix($D); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/LUDecomposition.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/LUDecomposition.php new file mode 100644 index 0000000..ecfe42b --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/LUDecomposition.php @@ -0,0 +1,284 @@ += n, the LU decomposition is an m-by-n + * unit lower triangular matrix L, an n-by-n upper triangular matrix U, + * and a permutation vector piv of length m so that A(piv,:) = L*U. + * If m < n, then L is m-by-m and U is m-by-n. + * + * The LU decompostion with pivoting always exists, even if the matrix is + * singular, so the constructor will never fail. The primary use of the + * LU decomposition is in the solution of square systems of simultaneous + * linear equations. This will fail if isNonsingular() returns false. + * + * @author Paul Meagher + * @author Bartosz Matosiuk + * @author Michael Bommarito + * + * @version 1.1 + */ +class LUDecomposition +{ + const MATRIX_SINGULAR_EXCEPTION = 'Can only perform operation on singular matrix.'; + const MATRIX_SQUARE_EXCEPTION = 'Mismatched Row dimension'; + + /** + * Decomposition storage. + * + * @var array + */ + private $LU = []; + + /** + * Row dimension. + * + * @var int + */ + private $m; + + /** + * Column dimension. + * + * @var int + */ + private $n; + + /** + * Pivot sign. + * + * @var int + */ + private $pivsign; + + /** + * Internal storage of pivot vector. + * + * @var array + */ + private $piv = []; + + /** + * LU Decomposition constructor. + * + * @param Matrix $A Rectangular matrix + */ + public function __construct($A) + { + if ($A instanceof Matrix) { + // Use a "left-looking", dot-product, Crout/Doolittle algorithm. + $this->LU = $A->getArray(); + $this->m = $A->getRowDimension(); + $this->n = $A->getColumnDimension(); + for ($i = 0; $i < $this->m; ++$i) { + $this->piv[$i] = $i; + } + $this->pivsign = 1; + $LUrowi = $LUcolj = []; + + // Outer loop. + for ($j = 0; $j < $this->n; ++$j) { + // Make a copy of the j-th column to localize references. + for ($i = 0; $i < $this->m; ++$i) { + $LUcolj[$i] = &$this->LU[$i][$j]; + } + // Apply previous transformations. + for ($i = 0; $i < $this->m; ++$i) { + $LUrowi = $this->LU[$i]; + // Most of the time is spent in the following dot product. + $kmax = min($i, $j); + $s = 0.0; + for ($k = 0; $k < $kmax; ++$k) { + $s += $LUrowi[$k] * $LUcolj[$k]; + } + $LUrowi[$j] = $LUcolj[$i] -= $s; + } + // Find pivot and exchange if necessary. + $p = $j; + for ($i = $j + 1; $i < $this->m; ++$i) { + if (abs($LUcolj[$i]) > abs($LUcolj[$p])) { + $p = $i; + } + } + if ($p != $j) { + for ($k = 0; $k < $this->n; ++$k) { + $t = $this->LU[$p][$k]; + $this->LU[$p][$k] = $this->LU[$j][$k]; + $this->LU[$j][$k] = $t; + } + $k = $this->piv[$p]; + $this->piv[$p] = $this->piv[$j]; + $this->piv[$j] = $k; + $this->pivsign = $this->pivsign * -1; + } + // Compute multipliers. + if (($j < $this->m) && ($this->LU[$j][$j] != 0.0)) { + for ($i = $j + 1; $i < $this->m; ++$i) { + $this->LU[$i][$j] /= $this->LU[$j][$j]; + } + } + } + } else { + throw new CalculationException(Matrix::ARGUMENT_TYPE_EXCEPTION); + } + } + + // function __construct() + + /** + * Get lower triangular factor. + * + * @return Matrix Lower triangular factor + */ + public function getL() + { + $L = []; + for ($i = 0; $i < $this->m; ++$i) { + for ($j = 0; $j < $this->n; ++$j) { + if ($i > $j) { + $L[$i][$j] = $this->LU[$i][$j]; + } elseif ($i == $j) { + $L[$i][$j] = 1.0; + } else { + $L[$i][$j] = 0.0; + } + } + } + + return new Matrix($L); + } + + // function getL() + + /** + * Get upper triangular factor. + * + * @return Matrix Upper triangular factor + */ + public function getU() + { + $U = []; + for ($i = 0; $i < $this->n; ++$i) { + for ($j = 0; $j < $this->n; ++$j) { + if ($i <= $j) { + $U[$i][$j] = $this->LU[$i][$j]; + } else { + $U[$i][$j] = 0.0; + } + } + } + + return new Matrix($U); + } + + // function getU() + + /** + * Return pivot permutation vector. + * + * @return array Pivot vector + */ + public function getPivot() + { + return $this->piv; + } + + // function getPivot() + + /** + * Alias for getPivot. + * + * @see getPivot + */ + public function getDoublePivot() + { + return $this->getPivot(); + } + + // function getDoublePivot() + + /** + * Is the matrix nonsingular? + * + * @return bool true if U, and hence A, is nonsingular + */ + public function isNonsingular() + { + for ($j = 0; $j < $this->n; ++$j) { + if ($this->LU[$j][$j] == 0) { + return false; + } + } + + return true; + } + + // function isNonsingular() + + /** + * Count determinants. + * + * @return float + */ + public function det() + { + if ($this->m == $this->n) { + $d = $this->pivsign; + for ($j = 0; $j < $this->n; ++$j) { + $d *= $this->LU[$j][$j]; + } + + return $d; + } + + throw new CalculationException(Matrix::MATRIX_DIMENSION_EXCEPTION); + } + + // function det() + + /** + * Solve A*X = B. + * + * @param Matrix $B a Matrix with as many rows as A and any number of columns + * + * @return Matrix X so that L*U*X = B(piv,:) + */ + public function solve(Matrix $B) + { + if ($B->getRowDimension() == $this->m) { + if ($this->isNonsingular()) { + // Copy right hand side with pivoting + $nx = $B->getColumnDimension(); + $X = $B->getMatrix($this->piv, 0, $nx - 1); + // Solve L*Y = B(piv,:) + for ($k = 0; $k < $this->n; ++$k) { + for ($i = $k + 1; $i < $this->n; ++$i) { + for ($j = 0; $j < $nx; ++$j) { + $X->A[$i][$j] -= $X->A[$k][$j] * $this->LU[$i][$k]; + } + } + } + // Solve U*X = Y; + for ($k = $this->n - 1; $k >= 0; --$k) { + for ($j = 0; $j < $nx; ++$j) { + $X->A[$k][$j] /= $this->LU[$k][$k]; + } + for ($i = 0; $i < $k; ++$i) { + for ($j = 0; $j < $nx; ++$j) { + $X->A[$i][$j] -= $X->A[$k][$j] * $this->LU[$i][$k]; + } + } + } + + return $X; + } + + throw new CalculationException(self::MATRIX_SINGULAR_EXCEPTION); + } + + throw new CalculationException(self::MATRIX_SQUARE_EXCEPTION); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/Matrix.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/Matrix.php new file mode 100644 index 0000000..adf399a --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/Matrix.php @@ -0,0 +1,1189 @@ + 0) { + $match = implode(',', array_map('gettype', $args)); + + switch ($match) { + //Rectangular matrix - m x n initialized from 2D array + case 'array': + $this->m = count($args[0]); + $this->n = count($args[0][0]); + $this->A = $args[0]; + + break; + //Square matrix - n x n + case 'integer': + $this->m = $args[0]; + $this->n = $args[0]; + $this->A = array_fill(0, $this->m, array_fill(0, $this->n, 0)); + + break; + //Rectangular matrix - m x n + case 'integer,integer': + $this->m = $args[0]; + $this->n = $args[1]; + $this->A = array_fill(0, $this->m, array_fill(0, $this->n, 0)); + + break; + //Rectangular matrix - m x n initialized from packed array + case 'array,integer': + $this->m = $args[1]; + if ($this->m != 0) { + $this->n = count($args[0]) / $this->m; + } else { + $this->n = 0; + } + if (($this->m * $this->n) == count($args[0])) { + for ($i = 0; $i < $this->m; ++$i) { + for ($j = 0; $j < $this->n; ++$j) { + $this->A[$i][$j] = $args[0][$i + $j * $this->m]; + } + } + } else { + throw new CalculationException(self::ARRAY_LENGTH_EXCEPTION); + } + + break; + default: + throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); + + break; + } + } else { + throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); + } + } + + /** + * getArray. + * + * @return array Matrix array + */ + public function getArray() + { + return $this->A; + } + + /** + * getRowDimension. + * + * @return int Row dimension + */ + public function getRowDimension() + { + return $this->m; + } + + /** + * getColumnDimension. + * + * @return int Column dimension + */ + public function getColumnDimension() + { + return $this->n; + } + + /** + * get. + * + * Get the i,j-th element of the matrix. + * + * @param int $i Row position + * @param int $j Column position + * + * @return float|int + */ + public function get($i = null, $j = null) + { + return $this->A[$i][$j]; + } + + /** + * getMatrix. + * + * Get a submatrix + * + * @return Matrix Submatrix + */ + public function getMatrix(...$args) + { + if (count($args) > 0) { + $match = implode(',', array_map('gettype', $args)); + + switch ($match) { + //A($i0...; $j0...) + case 'integer,integer': + [$i0, $j0] = $args; + if ($i0 >= 0) { + $m = $this->m - $i0; + } else { + throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION); + } + if ($j0 >= 0) { + $n = $this->n - $j0; + } else { + throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION); + } + $R = new self($m, $n); + for ($i = $i0; $i < $this->m; ++$i) { + for ($j = $j0; $j < $this->n; ++$j) { + $R->set($i, $j, $this->A[$i][$j]); + } + } + + return $R; + + break; + //A($i0...$iF; $j0...$jF) + case 'integer,integer,integer,integer': + [$i0, $iF, $j0, $jF] = $args; + if (($iF > $i0) && ($this->m >= $iF) && ($i0 >= 0)) { + $m = $iF - $i0; + } else { + throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION); + } + if (($jF > $j0) && ($this->n >= $jF) && ($j0 >= 0)) { + $n = $jF - $j0; + } else { + throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION); + } + $R = new self($m + 1, $n + 1); + for ($i = $i0; $i <= $iF; ++$i) { + for ($j = $j0; $j <= $jF; ++$j) { + $R->set($i - $i0, $j - $j0, $this->A[$i][$j]); + } + } + + return $R; + + break; + //$R = array of row indices; $C = array of column indices + case 'array,array': + [$RL, $CL] = $args; + if (count($RL) > 0) { + $m = count($RL); + } else { + throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION); + } + if (count($CL) > 0) { + $n = count($CL); + } else { + throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION); + } + $R = new self($m, $n); + for ($i = 0; $i < $m; ++$i) { + for ($j = 0; $j < $n; ++$j) { + $R->set($i, $j, $this->A[$RL[$i]][$CL[$j]]); + } + } + + return $R; + + break; + //A($i0...$iF); $CL = array of column indices + case 'integer,integer,array': + [$i0, $iF, $CL] = $args; + if (($iF > $i0) && ($this->m >= $iF) && ($i0 >= 0)) { + $m = $iF - $i0; + } else { + throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION); + } + if (count($CL) > 0) { + $n = count($CL); + } else { + throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION); + } + $R = new self($m, $n); + for ($i = $i0; $i < $iF; ++$i) { + for ($j = 0; $j < $n; ++$j) { + $R->set($i - $i0, $j, $this->A[$i][$CL[$j]]); + } + } + + return $R; + + break; + //$RL = array of row indices + case 'array,integer,integer': + [$RL, $j0, $jF] = $args; + if (count($RL) > 0) { + $m = count($RL); + } else { + throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION); + } + if (($jF >= $j0) && ($this->n >= $jF) && ($j0 >= 0)) { + $n = $jF - $j0; + } else { + throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION); + } + $R = new self($m, $n + 1); + for ($i = 0; $i < $m; ++$i) { + for ($j = $j0; $j <= $jF; ++$j) { + $R->set($i, $j - $j0, $this->A[$RL[$i]][$j]); + } + } + + return $R; + + break; + default: + throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); + + break; + } + } else { + throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); + } + } + + /** + * checkMatrixDimensions. + * + * Is matrix B the same size? + * + * @param Matrix $B Matrix B + * + * @return bool + */ + public function checkMatrixDimensions($B = null) + { + if ($B instanceof self) { + if (($this->m == $B->getRowDimension()) && ($this->n == $B->getColumnDimension())) { + return true; + } + + throw new CalculationException(self::MATRIX_DIMENSION_EXCEPTION); + } + + throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); + } + + // function checkMatrixDimensions() + + /** + * set. + * + * Set the i,j-th element of the matrix. + * + * @param int $i Row position + * @param int $j Column position + * @param float|int $c value + */ + public function set($i = null, $j = null, $c = null): void + { + // Optimized set version just has this + $this->A[$i][$j] = $c; + } + + // function set() + + /** + * identity. + * + * Generate an identity matrix. + * + * @param int $m Row dimension + * @param int $n Column dimension + * + * @return Matrix Identity matrix + */ + public function identity($m = null, $n = null) + { + return $this->diagonal($m, $n, 1); + } + + /** + * diagonal. + * + * Generate a diagonal matrix + * + * @param int $m Row dimension + * @param int $n Column dimension + * @param mixed $c Diagonal value + * + * @return Matrix Diagonal matrix + */ + public function diagonal($m = null, $n = null, $c = 1) + { + $R = new self($m, $n); + for ($i = 0; $i < $m; ++$i) { + $R->set($i, $i, $c); + } + + return $R; + } + + /** + * getMatrixByRow. + * + * Get a submatrix by row index/range + * + * @param int $i0 Initial row index + * @param int $iF Final row index + * + * @return Matrix Submatrix + */ + public function getMatrixByRow($i0 = null, $iF = null) + { + if (is_int($i0)) { + if (is_int($iF)) { + return $this->getMatrix($i0, 0, $iF + 1, $this->n); + } + + return $this->getMatrix($i0, 0, $i0 + 1, $this->n); + } + + throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); + } + + /** + * getMatrixByCol. + * + * Get a submatrix by column index/range + * + * @param int $j0 Initial column index + * @param int $jF Final column index + * + * @return Matrix Submatrix + */ + public function getMatrixByCol($j0 = null, $jF = null) + { + if (is_int($j0)) { + if (is_int($jF)) { + return $this->getMatrix(0, $j0, $this->m, $jF + 1); + } + + return $this->getMatrix(0, $j0, $this->m, $j0 + 1); + } + + throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); + } + + /** + * transpose. + * + * Tranpose matrix + * + * @return Matrix Transposed matrix + */ + public function transpose() + { + $R = new self($this->n, $this->m); + for ($i = 0; $i < $this->m; ++$i) { + for ($j = 0; $j < $this->n; ++$j) { + $R->set($j, $i, $this->A[$i][$j]); + } + } + + return $R; + } + + // function transpose() + + /** + * trace. + * + * Sum of diagonal elements + * + * @return float Sum of diagonal elements + */ + public function trace() + { + $s = 0; + $n = min($this->m, $this->n); + for ($i = 0; $i < $n; ++$i) { + $s += $this->A[$i][$i]; + } + + return $s; + } + + /** + * plus. + * + * A + B + * + * @return Matrix Sum + */ + public function plus(...$args) + { + if (count($args) > 0) { + $match = implode(',', array_map('gettype', $args)); + + switch ($match) { + case 'object': + if ($args[0] instanceof self) { + $M = $args[0]; + } else { + throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); + } + + break; + case 'array': + $M = new self($args[0]); + + break; + default: + throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); + + break; + } + $this->checkMatrixDimensions($M); + for ($i = 0; $i < $this->m; ++$i) { + for ($j = 0; $j < $this->n; ++$j) { + $M->set($i, $j, $M->get($i, $j) + $this->A[$i][$j]); + } + } + + return $M; + } + + throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); + } + + /** + * plusEquals. + * + * A = A + B + * + * @return $this + */ + public function plusEquals(...$args) + { + if (count($args) > 0) { + $match = implode(',', array_map('gettype', $args)); + + switch ($match) { + case 'object': + if ($args[0] instanceof self) { + $M = $args[0]; + } else { + throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); + } + + break; + case 'array': + $M = new self($args[0]); + + break; + default: + throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); + + break; + } + $this->checkMatrixDimensions($M); + for ($i = 0; $i < $this->m; ++$i) { + for ($j = 0; $j < $this->n; ++$j) { + $validValues = true; + $value = $M->get($i, $j); + if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) { + $this->A[$i][$j] = trim($this->A[$i][$j], '"'); + $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]); + } + if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) { + $value = trim($value, '"'); + $validValues &= StringHelper::convertToNumberIfFraction($value); + } + if ($validValues) { + $this->A[$i][$j] += $value; + } else { + $this->A[$i][$j] = Functions::NAN(); + } + } + } + + return $this; + } + + throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); + } + + /** + * minus. + * + * A - B + * + * @return Matrix Sum + */ + public function minus(...$args) + { + if (count($args) > 0) { + $match = implode(',', array_map('gettype', $args)); + + switch ($match) { + case 'object': + if ($args[0] instanceof self) { + $M = $args[0]; + } else { + throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); + } + + break; + case 'array': + $M = new self($args[0]); + + break; + default: + throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); + + break; + } + $this->checkMatrixDimensions($M); + for ($i = 0; $i < $this->m; ++$i) { + for ($j = 0; $j < $this->n; ++$j) { + $M->set($i, $j, $M->get($i, $j) - $this->A[$i][$j]); + } + } + + return $M; + } + + throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); + } + + /** + * minusEquals. + * + * A = A - B + * + * @return $this + */ + public function minusEquals(...$args) + { + if (count($args) > 0) { + $match = implode(',', array_map('gettype', $args)); + + switch ($match) { + case 'object': + if ($args[0] instanceof self) { + $M = $args[0]; + } else { + throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); + } + + break; + case 'array': + $M = new self($args[0]); + + break; + default: + throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); + + break; + } + $this->checkMatrixDimensions($M); + for ($i = 0; $i < $this->m; ++$i) { + for ($j = 0; $j < $this->n; ++$j) { + $validValues = true; + $value = $M->get($i, $j); + if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) { + $this->A[$i][$j] = trim($this->A[$i][$j], '"'); + $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]); + } + if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) { + $value = trim($value, '"'); + $validValues &= StringHelper::convertToNumberIfFraction($value); + } + if ($validValues) { + $this->A[$i][$j] -= $value; + } else { + $this->A[$i][$j] = Functions::NAN(); + } + } + } + + return $this; + } + + throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); + } + + /** + * arrayTimes. + * + * Element-by-element multiplication + * Cij = Aij * Bij + * + * @return Matrix Matrix Cij + */ + public function arrayTimes(...$args) + { + if (count($args) > 0) { + $match = implode(',', array_map('gettype', $args)); + + switch ($match) { + case 'object': + if ($args[0] instanceof self) { + $M = $args[0]; + } else { + throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); + } + + break; + case 'array': + $M = new self($args[0]); + + break; + default: + throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); + + break; + } + $this->checkMatrixDimensions($M); + for ($i = 0; $i < $this->m; ++$i) { + for ($j = 0; $j < $this->n; ++$j) { + $M->set($i, $j, $M->get($i, $j) * $this->A[$i][$j]); + } + } + + return $M; + } + + throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); + } + + /** + * arrayTimesEquals. + * + * Element-by-element multiplication + * Aij = Aij * Bij + * + * @return $this + */ + public function arrayTimesEquals(...$args) + { + if (count($args) > 0) { + $match = implode(',', array_map('gettype', $args)); + + switch ($match) { + case 'object': + if ($args[0] instanceof self) { + $M = $args[0]; + } else { + throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); + } + + break; + case 'array': + $M = new self($args[0]); + + break; + default: + throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); + + break; + } + $this->checkMatrixDimensions($M); + for ($i = 0; $i < $this->m; ++$i) { + for ($j = 0; $j < $this->n; ++$j) { + $validValues = true; + $value = $M->get($i, $j); + if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) { + $this->A[$i][$j] = trim($this->A[$i][$j], '"'); + $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]); + } + if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) { + $value = trim($value, '"'); + $validValues &= StringHelper::convertToNumberIfFraction($value); + } + if ($validValues) { + $this->A[$i][$j] *= $value; + } else { + $this->A[$i][$j] = Functions::NAN(); + } + } + } + + return $this; + } + + throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); + } + + /** + * arrayRightDivide. + * + * Element-by-element right division + * A / B + * + * @return Matrix Division result + */ + public function arrayRightDivide(...$args) + { + if (count($args) > 0) { + $match = implode(',', array_map('gettype', $args)); + + switch ($match) { + case 'object': + if ($args[0] instanceof self) { + $M = $args[0]; + } else { + throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); + } + + break; + case 'array': + $M = new self($args[0]); + + break; + default: + throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); + + break; + } + $this->checkMatrixDimensions($M); + for ($i = 0; $i < $this->m; ++$i) { + for ($j = 0; $j < $this->n; ++$j) { + $validValues = true; + $value = $M->get($i, $j); + if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) { + $this->A[$i][$j] = trim($this->A[$i][$j], '"'); + $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]); + } + if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) { + $value = trim($value, '"'); + $validValues &= StringHelper::convertToNumberIfFraction($value); + } + if ($validValues) { + if ($value == 0) { + // Trap for Divide by Zero error + $M->set($i, $j, '#DIV/0!'); + } else { + $M->set($i, $j, $this->A[$i][$j] / $value); + } + } else { + $M->set($i, $j, Functions::NAN()); + } + } + } + + return $M; + } + + throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); + } + + /** + * arrayRightDivideEquals. + * + * Element-by-element right division + * Aij = Aij / Bij + * + * @return Matrix Matrix Aij + */ + public function arrayRightDivideEquals(...$args) + { + if (count($args) > 0) { + $match = implode(',', array_map('gettype', $args)); + + switch ($match) { + case 'object': + if ($args[0] instanceof self) { + $M = $args[0]; + } else { + throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); + } + + break; + case 'array': + $M = new self($args[0]); + + break; + default: + throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); + + break; + } + $this->checkMatrixDimensions($M); + for ($i = 0; $i < $this->m; ++$i) { + for ($j = 0; $j < $this->n; ++$j) { + $this->A[$i][$j] = $this->A[$i][$j] / $M->get($i, $j); + } + } + + return $M; + } + + throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); + } + + /** + * arrayLeftDivide. + * + * Element-by-element Left division + * A / B + * + * @return Matrix Division result + */ + public function arrayLeftDivide(...$args) + { + if (count($args) > 0) { + $match = implode(',', array_map('gettype', $args)); + + switch ($match) { + case 'object': + if ($args[0] instanceof self) { + $M = $args[0]; + } else { + throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); + } + + break; + case 'array': + $M = new self($args[0]); + + break; + default: + throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); + + break; + } + $this->checkMatrixDimensions($M); + for ($i = 0; $i < $this->m; ++$i) { + for ($j = 0; $j < $this->n; ++$j) { + $M->set($i, $j, $M->get($i, $j) / $this->A[$i][$j]); + } + } + + return $M; + } + + throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); + } + + /** + * arrayLeftDivideEquals. + * + * Element-by-element Left division + * Aij = Aij / Bij + * + * @return Matrix Matrix Aij + */ + public function arrayLeftDivideEquals(...$args) + { + if (count($args) > 0) { + $match = implode(',', array_map('gettype', $args)); + + switch ($match) { + case 'object': + if ($args[0] instanceof self) { + $M = $args[0]; + } else { + throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); + } + + break; + case 'array': + $M = new self($args[0]); + + break; + default: + throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); + + break; + } + $this->checkMatrixDimensions($M); + for ($i = 0; $i < $this->m; ++$i) { + for ($j = 0; $j < $this->n; ++$j) { + $this->A[$i][$j] = $M->get($i, $j) / $this->A[$i][$j]; + } + } + + return $M; + } + + throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); + } + + /** + * times. + * + * Matrix multiplication + * + * @return Matrix Product + */ + public function times(...$args) + { + if (count($args) > 0) { + $match = implode(',', array_map('gettype', $args)); + + switch ($match) { + case 'object': + if ($args[0] instanceof self) { + $B = $args[0]; + } else { + throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); + } + if ($this->n == $B->m) { + $C = new self($this->m, $B->n); + for ($j = 0; $j < $B->n; ++$j) { + $Bcolj = []; + for ($k = 0; $k < $this->n; ++$k) { + $Bcolj[$k] = $B->A[$k][$j]; + } + for ($i = 0; $i < $this->m; ++$i) { + $Arowi = $this->A[$i]; + $s = 0; + for ($k = 0; $k < $this->n; ++$k) { + $s += $Arowi[$k] * $Bcolj[$k]; + } + $C->A[$i][$j] = $s; + } + } + + return $C; + } + + throw new CalculationException(self::MATRIX_DIMENSION_EXCEPTION); + case 'array': + $B = new self($args[0]); + if ($this->n == $B->m) { + $C = new self($this->m, $B->n); + for ($i = 0; $i < $C->m; ++$i) { + for ($j = 0; $j < $C->n; ++$j) { + $s = '0'; + for ($k = 0; $k < $C->n; ++$k) { + $s += $this->A[$i][$k] * $B->A[$k][$j]; + } + $C->A[$i][$j] = $s; + } + } + + return $C; + } + + throw new CalculationException(self::MATRIX_DIMENSION_EXCEPTION); + case 'integer': + $C = new self($this->A); + for ($i = 0; $i < $C->m; ++$i) { + for ($j = 0; $j < $C->n; ++$j) { + $C->A[$i][$j] *= $args[0]; + } + } + + return $C; + case 'double': + $C = new self($this->m, $this->n); + for ($i = 0; $i < $C->m; ++$i) { + for ($j = 0; $j < $C->n; ++$j) { + $C->A[$i][$j] = $args[0] * $this->A[$i][$j]; + } + } + + return $C; + case 'float': + $C = new self($this->A); + for ($i = 0; $i < $C->m; ++$i) { + for ($j = 0; $j < $C->n; ++$j) { + $C->A[$i][$j] *= $args[0]; + } + } + + return $C; + default: + throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); + } + } else { + throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); + } + } + + /** + * power. + * + * A = A ^ B + * + * @return $this + */ + public function power(...$args) + { + if (count($args) > 0) { + $match = implode(',', array_map('gettype', $args)); + + switch ($match) { + case 'object': + if ($args[0] instanceof self) { + $M = $args[0]; + } else { + throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); + } + + break; + case 'array': + $M = new self($args[0]); + + break; + default: + throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); + + break; + } + $this->checkMatrixDimensions($M); + for ($i = 0; $i < $this->m; ++$i) { + for ($j = 0; $j < $this->n; ++$j) { + $validValues = true; + $value = $M->get($i, $j); + if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) { + $this->A[$i][$j] = trim($this->A[$i][$j], '"'); + $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]); + } + if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) { + $value = trim($value, '"'); + $validValues &= StringHelper::convertToNumberIfFraction($value); + } + if ($validValues) { + $this->A[$i][$j] = $this->A[$i][$j] ** $value; + } else { + $this->A[$i][$j] = Functions::NAN(); + } + } + } + + return $this; + } + + throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); + } + + /** + * concat. + * + * A = A & B + * + * @return $this + */ + public function concat(...$args) + { + if (count($args) > 0) { + $match = implode(',', array_map('gettype', $args)); + + switch ($match) { + case 'object': + if ($args[0] instanceof self) { + $M = $args[0]; + } else { + throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); + } + + break; + case 'array': + $M = new self($args[0]); + + break; + default: + throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); + + break; + } + $this->checkMatrixDimensions($M); + for ($i = 0; $i < $this->m; ++$i) { + for ($j = 0; $j < $this->n; ++$j) { + $this->A[$i][$j] = trim($this->A[$i][$j], '"') . trim($M->get($i, $j), '"'); + } + } + + return $this; + } + + throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); + } + + /** + * Solve A*X = B. + * + * @param Matrix $B Right hand side + * + * @return Matrix ... Solution if A is square, least squares solution otherwise + */ + public function solve(self $B) + { + if ($this->m == $this->n) { + $LU = new LUDecomposition($this); + + return $LU->solve($B); + } + $QR = new QRDecomposition($this); + + return $QR->solve($B); + } + + /** + * Matrix inverse or pseudoinverse. + * + * @return Matrix ... Inverse(A) if A is square, pseudoinverse otherwise. + */ + public function inverse() + { + return $this->solve($this->identity($this->m, $this->m)); + } + + /** + * det. + * + * Calculate determinant + * + * @return float Determinant + */ + public function det() + { + $L = new LUDecomposition($this); + + return $L->det(); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/QRDecomposition.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/QRDecomposition.php new file mode 100644 index 0000000..9b51f41 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/QRDecomposition.php @@ -0,0 +1,245 @@ += n, the QR decomposition is an m-by-n + * orthogonal matrix Q and an n-by-n upper triangular matrix R so that + * A = Q*R. + * + * The QR decompostion always exists, even if the matrix does not have + * full rank, so the constructor will never fail. The primary use of the + * QR decomposition is in the least squares solution of nonsquare systems + * of simultaneous linear equations. This will fail if isFullRank() + * returns false. + * + * @author Paul Meagher + * + * @version 1.1 + */ +class QRDecomposition +{ + const MATRIX_RANK_EXCEPTION = 'Can only perform operation on full-rank matrix.'; + + /** + * Array for internal storage of decomposition. + * + * @var array + */ + private $QR = []; + + /** + * Row dimension. + * + * @var int + */ + private $m; + + /** + * Column dimension. + * + * @var int + */ + private $n; + + /** + * Array for internal storage of diagonal of R. + * + * @var array + */ + private $Rdiag = []; + + /** + * QR Decomposition computed by Householder reflections. + * + * @param Matrix $A Rectangular matrix + */ + public function __construct(Matrix $A) + { + // Initialize. + $this->QR = $A->getArray(); + $this->m = $A->getRowDimension(); + $this->n = $A->getColumnDimension(); + // Main loop. + for ($k = 0; $k < $this->n; ++$k) { + // Compute 2-norm of k-th column without under/overflow. + $nrm = 0.0; + for ($i = $k; $i < $this->m; ++$i) { + $nrm = hypo($nrm, $this->QR[$i][$k]); + } + if ($nrm != 0.0) { + // Form k-th Householder vector. + if ($this->QR[$k][$k] < 0) { + $nrm = -$nrm; + } + for ($i = $k; $i < $this->m; ++$i) { + $this->QR[$i][$k] /= $nrm; + } + $this->QR[$k][$k] += 1.0; + // Apply transformation to remaining columns. + for ($j = $k + 1; $j < $this->n; ++$j) { + $s = 0.0; + for ($i = $k; $i < $this->m; ++$i) { + $s += $this->QR[$i][$k] * $this->QR[$i][$j]; + } + $s = -$s / $this->QR[$k][$k]; + for ($i = $k; $i < $this->m; ++$i) { + $this->QR[$i][$j] += $s * $this->QR[$i][$k]; + } + } + } + $this->Rdiag[$k] = -$nrm; + } + } + + // function __construct() + + /** + * Is the matrix full rank? + * + * @return bool true if R, and hence A, has full rank, else false + */ + public function isFullRank() + { + for ($j = 0; $j < $this->n; ++$j) { + if ($this->Rdiag[$j] == 0) { + return false; + } + } + + return true; + } + + // function isFullRank() + + /** + * Return the Householder vectors. + * + * @return Matrix Lower trapezoidal matrix whose columns define the reflections + */ + public function getH() + { + $H = []; + for ($i = 0; $i < $this->m; ++$i) { + for ($j = 0; $j < $this->n; ++$j) { + if ($i >= $j) { + $H[$i][$j] = $this->QR[$i][$j]; + } else { + $H[$i][$j] = 0.0; + } + } + } + + return new Matrix($H); + } + + // function getH() + + /** + * Return the upper triangular factor. + * + * @return Matrix upper triangular factor + */ + public function getR() + { + $R = []; + for ($i = 0; $i < $this->n; ++$i) { + for ($j = 0; $j < $this->n; ++$j) { + if ($i < $j) { + $R[$i][$j] = $this->QR[$i][$j]; + } elseif ($i == $j) { + $R[$i][$j] = $this->Rdiag[$i]; + } else { + $R[$i][$j] = 0.0; + } + } + } + + return new Matrix($R); + } + + // function getR() + + /** + * Generate and return the (economy-sized) orthogonal factor. + * + * @return Matrix orthogonal factor + */ + public function getQ() + { + $Q = []; + for ($k = $this->n - 1; $k >= 0; --$k) { + for ($i = 0; $i < $this->m; ++$i) { + $Q[$i][$k] = 0.0; + } + $Q[$k][$k] = 1.0; + for ($j = $k; $j < $this->n; ++$j) { + if ($this->QR[$k][$k] != 0) { + $s = 0.0; + for ($i = $k; $i < $this->m; ++$i) { + $s += $this->QR[$i][$k] * $Q[$i][$j]; + } + $s = -$s / $this->QR[$k][$k]; + for ($i = $k; $i < $this->m; ++$i) { + $Q[$i][$j] += $s * $this->QR[$i][$k]; + } + } + } + } + + return new Matrix($Q); + } + + // function getQ() + + /** + * Least squares solution of A*X = B. + * + * @param Matrix $B a Matrix with as many rows as A and any number of columns + * + * @return Matrix matrix that minimizes the two norm of Q*R*X-B + */ + public function solve(Matrix $B) + { + if ($B->getRowDimension() == $this->m) { + if ($this->isFullRank()) { + // Copy right hand side + $nx = $B->getColumnDimension(); + $X = $B->getArray(); + // Compute Y = transpose(Q)*B + for ($k = 0; $k < $this->n; ++$k) { + for ($j = 0; $j < $nx; ++$j) { + $s = 0.0; + for ($i = $k; $i < $this->m; ++$i) { + $s += $this->QR[$i][$k] * $X[$i][$j]; + } + $s = -$s / $this->QR[$k][$k]; + for ($i = $k; $i < $this->m; ++$i) { + $X[$i][$j] += $s * $this->QR[$i][$k]; + } + } + } + // Solve R*X = Y; + for ($k = $this->n - 1; $k >= 0; --$k) { + for ($j = 0; $j < $nx; ++$j) { + $X[$k][$j] /= $this->Rdiag[$k]; + } + for ($i = 0; $i < $k; ++$i) { + for ($j = 0; $j < $nx; ++$j) { + $X[$i][$j] -= $X[$k][$j] * $this->QR[$i][$k]; + } + } + } + $X = new Matrix($X); + + return $X->getMatrix(0, $this->n - 1, 0, $nx); + } + + throw new CalculationException(self::MATRIX_RANK_EXCEPTION); + } + + throw new CalculationException(Matrix::MATRIX_DIMENSION_EXCEPTION); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/SingularValueDecomposition.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/SingularValueDecomposition.php new file mode 100644 index 0000000..6c8999d --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/SingularValueDecomposition.php @@ -0,0 +1,529 @@ += n, the singular value decomposition is + * an m-by-n orthogonal matrix U, an n-by-n diagonal matrix S, and + * an n-by-n orthogonal matrix V so that A = U*S*V'. + * + * The singular values, sigma[$k] = S[$k][$k], are ordered so that + * sigma[0] >= sigma[1] >= ... >= sigma[n-1]. + * + * The singular value decompostion always exists, so the constructor will + * never fail. The matrix condition number and the effective numerical + * rank can be computed from this decomposition. + * + * @author Paul Meagher + * + * @version 1.1 + */ +class SingularValueDecomposition +{ + /** + * Internal storage of U. + * + * @var array + */ + private $U = []; + + /** + * Internal storage of V. + * + * @var array + */ + private $V = []; + + /** + * Internal storage of singular values. + * + * @var array + */ + private $s = []; + + /** + * Row dimension. + * + * @var int + */ + private $m; + + /** + * Column dimension. + * + * @var int + */ + private $n; + + /** + * Construct the singular value decomposition. + * + * Derived from LINPACK code. + * + * @param mixed $Arg Rectangular matrix + */ + public function __construct($Arg) + { + // Initialize. + $A = $Arg->getArray(); + $this->m = $Arg->getRowDimension(); + $this->n = $Arg->getColumnDimension(); + $nu = min($this->m, $this->n); + $e = []; + $work = []; + $wantu = true; + $wantv = true; + $nct = min($this->m - 1, $this->n); + $nrt = max(0, min($this->n - 2, $this->m)); + + // Reduce A to bidiagonal form, storing the diagonal elements + // in s and the super-diagonal elements in e. + $kMax = max($nct, $nrt); + for ($k = 0; $k < $kMax; ++$k) { + if ($k < $nct) { + // Compute the transformation for the k-th column and + // place the k-th diagonal in s[$k]. + // Compute 2-norm of k-th column without under/overflow. + $this->s[$k] = 0; + for ($i = $k; $i < $this->m; ++$i) { + $this->s[$k] = hypo($this->s[$k], $A[$i][$k]); + } + if ($this->s[$k] != 0.0) { + if ($A[$k][$k] < 0.0) { + $this->s[$k] = -$this->s[$k]; + } + for ($i = $k; $i < $this->m; ++$i) { + $A[$i][$k] /= $this->s[$k]; + } + $A[$k][$k] += 1.0; + } + $this->s[$k] = -$this->s[$k]; + } + + for ($j = $k + 1; $j < $this->n; ++$j) { + if (($k < $nct) & ($this->s[$k] != 0.0)) { + // Apply the transformation. + $t = 0; + for ($i = $k; $i < $this->m; ++$i) { + $t += $A[$i][$k] * $A[$i][$j]; + } + $t = -$t / $A[$k][$k]; + for ($i = $k; $i < $this->m; ++$i) { + $A[$i][$j] += $t * $A[$i][$k]; + } + // Place the k-th row of A into e for the + // subsequent calculation of the row transformation. + $e[$j] = $A[$k][$j]; + } + } + + if ($wantu && ($k < $nct)) { + // Place the transformation in U for subsequent back + // multiplication. + for ($i = $k; $i < $this->m; ++$i) { + $this->U[$i][$k] = $A[$i][$k]; + } + } + + if ($k < $nrt) { + // Compute the k-th row transformation and place the + // k-th super-diagonal in e[$k]. + // Compute 2-norm without under/overflow. + $e[$k] = 0; + for ($i = $k + 1; $i < $this->n; ++$i) { + $e[$k] = hypo($e[$k], $e[$i]); + } + if ($e[$k] != 0.0) { + if ($e[$k + 1] < 0.0) { + $e[$k] = -$e[$k]; + } + for ($i = $k + 1; $i < $this->n; ++$i) { + $e[$i] /= $e[$k]; + } + $e[$k + 1] += 1.0; + } + $e[$k] = -$e[$k]; + if (($k + 1 < $this->m) && ($e[$k] != 0.0)) { + // Apply the transformation. + for ($i = $k + 1; $i < $this->m; ++$i) { + $work[$i] = 0.0; + } + for ($j = $k + 1; $j < $this->n; ++$j) { + for ($i = $k + 1; $i < $this->m; ++$i) { + $work[$i] += $e[$j] * $A[$i][$j]; + } + } + for ($j = $k + 1; $j < $this->n; ++$j) { + $t = -$e[$j] / $e[$k + 1]; + for ($i = $k + 1; $i < $this->m; ++$i) { + $A[$i][$j] += $t * $work[$i]; + } + } + } + if ($wantv) { + // Place the transformation in V for subsequent + // back multiplication. + for ($i = $k + 1; $i < $this->n; ++$i) { + $this->V[$i][$k] = $e[$i]; + } + } + } + } + + // Set up the final bidiagonal matrix or order p. + $p = min($this->n, $this->m + 1); + if ($nct < $this->n) { + $this->s[$nct] = $A[$nct][$nct]; + } + if ($this->m < $p) { + $this->s[$p - 1] = 0.0; + } + if ($nrt + 1 < $p) { + $e[$nrt] = $A[$nrt][$p - 1]; + } + $e[$p - 1] = 0.0; + // If required, generate U. + if ($wantu) { + for ($j = $nct; $j < $nu; ++$j) { + for ($i = 0; $i < $this->m; ++$i) { + $this->U[$i][$j] = 0.0; + } + $this->U[$j][$j] = 1.0; + } + for ($k = $nct - 1; $k >= 0; --$k) { + if ($this->s[$k] != 0.0) { + for ($j = $k + 1; $j < $nu; ++$j) { + $t = 0; + for ($i = $k; $i < $this->m; ++$i) { + $t += $this->U[$i][$k] * $this->U[$i][$j]; + } + $t = -$t / $this->U[$k][$k]; + for ($i = $k; $i < $this->m; ++$i) { + $this->U[$i][$j] += $t * $this->U[$i][$k]; + } + } + for ($i = $k; $i < $this->m; ++$i) { + $this->U[$i][$k] = -$this->U[$i][$k]; + } + $this->U[$k][$k] = 1.0 + $this->U[$k][$k]; + for ($i = 0; $i < $k - 1; ++$i) { + $this->U[$i][$k] = 0.0; + } + } else { + for ($i = 0; $i < $this->m; ++$i) { + $this->U[$i][$k] = 0.0; + } + $this->U[$k][$k] = 1.0; + } + } + } + + // If required, generate V. + if ($wantv) { + for ($k = $this->n - 1; $k >= 0; --$k) { + if (($k < $nrt) && ($e[$k] != 0.0)) { + for ($j = $k + 1; $j < $nu; ++$j) { + $t = 0; + for ($i = $k + 1; $i < $this->n; ++$i) { + $t += $this->V[$i][$k] * $this->V[$i][$j]; + } + $t = -$t / $this->V[$k + 1][$k]; + for ($i = $k + 1; $i < $this->n; ++$i) { + $this->V[$i][$j] += $t * $this->V[$i][$k]; + } + } + } + for ($i = 0; $i < $this->n; ++$i) { + $this->V[$i][$k] = 0.0; + } + $this->V[$k][$k] = 1.0; + } + } + + // Main iteration loop for the singular values. + $pp = $p - 1; + $iter = 0; + $eps = 2.0 ** (-52.0); + + while ($p > 0) { + // Here is where a test for too many iterations would go. + // This section of the program inspects for negligible + // elements in the s and e arrays. On completion the + // variables kase and k are set as follows: + // kase = 1 if s(p) and e[k-1] are negligible and k

        = -1; --$k) { + if ($k == -1) { + break; + } + if (abs($e[$k]) <= $eps * (abs($this->s[$k]) + abs($this->s[$k + 1]))) { + $e[$k] = 0.0; + + break; + } + } + if ($k == $p - 2) { + $kase = 4; + } else { + for ($ks = $p - 1; $ks >= $k; --$ks) { + if ($ks == $k) { + break; + } + $t = ($ks != $p ? abs($e[$ks]) : 0.) + ($ks != $k + 1 ? abs($e[$ks - 1]) : 0.); + if (abs($this->s[$ks]) <= $eps * $t) { + $this->s[$ks] = 0.0; + + break; + } + } + if ($ks == $k) { + $kase = 3; + } elseif ($ks == $p - 1) { + $kase = 1; + } else { + $kase = 2; + $k = $ks; + } + } + ++$k; + + // Perform the task indicated by kase. + switch ($kase) { + // Deflate negligible s(p). + case 1: + $f = $e[$p - 2]; + $e[$p - 2] = 0.0; + for ($j = $p - 2; $j >= $k; --$j) { + $t = hypo($this->s[$j], $f); + $cs = $this->s[$j] / $t; + $sn = $f / $t; + $this->s[$j] = $t; + if ($j != $k) { + $f = -$sn * $e[$j - 1]; + $e[$j - 1] = $cs * $e[$j - 1]; + } + if ($wantv) { + for ($i = 0; $i < $this->n; ++$i) { + $t = $cs * $this->V[$i][$j] + $sn * $this->V[$i][$p - 1]; + $this->V[$i][$p - 1] = -$sn * $this->V[$i][$j] + $cs * $this->V[$i][$p - 1]; + $this->V[$i][$j] = $t; + } + } + } + + break; + // Split at negligible s(k). + case 2: + $f = $e[$k - 1]; + $e[$k - 1] = 0.0; + for ($j = $k; $j < $p; ++$j) { + $t = hypo($this->s[$j], $f); + $cs = $this->s[$j] / $t; + $sn = $f / $t; + $this->s[$j] = $t; + $f = -$sn * $e[$j]; + $e[$j] = $cs * $e[$j]; + if ($wantu) { + for ($i = 0; $i < $this->m; ++$i) { + $t = $cs * $this->U[$i][$j] + $sn * $this->U[$i][$k - 1]; + $this->U[$i][$k - 1] = -$sn * $this->U[$i][$j] + $cs * $this->U[$i][$k - 1]; + $this->U[$i][$j] = $t; + } + } + } + + break; + // Perform one qr step. + case 3: + // Calculate the shift. + $scale = max(max(max(max(abs($this->s[$p - 1]), abs($this->s[$p - 2])), abs($e[$p - 2])), abs($this->s[$k])), abs($e[$k])); + $sp = $this->s[$p - 1] / $scale; + $spm1 = $this->s[$p - 2] / $scale; + $epm1 = $e[$p - 2] / $scale; + $sk = $this->s[$k] / $scale; + $ek = $e[$k] / $scale; + $b = (($spm1 + $sp) * ($spm1 - $sp) + $epm1 * $epm1) / 2.0; + $c = ($sp * $epm1) * ($sp * $epm1); + $shift = 0.0; + if (($b != 0.0) || ($c != 0.0)) { + $shift = sqrt($b * $b + $c); + if ($b < 0.0) { + $shift = -$shift; + } + $shift = $c / ($b + $shift); + } + $f = ($sk + $sp) * ($sk - $sp) + $shift; + $g = $sk * $ek; + // Chase zeros. + for ($j = $k; $j < $p - 1; ++$j) { + $t = hypo($f, $g); + $cs = $f / $t; + $sn = $g / $t; + if ($j != $k) { + $e[$j - 1] = $t; + } + $f = $cs * $this->s[$j] + $sn * $e[$j]; + $e[$j] = $cs * $e[$j] - $sn * $this->s[$j]; + $g = $sn * $this->s[$j + 1]; + $this->s[$j + 1] = $cs * $this->s[$j + 1]; + if ($wantv) { + for ($i = 0; $i < $this->n; ++$i) { + $t = $cs * $this->V[$i][$j] + $sn * $this->V[$i][$j + 1]; + $this->V[$i][$j + 1] = -$sn * $this->V[$i][$j] + $cs * $this->V[$i][$j + 1]; + $this->V[$i][$j] = $t; + } + } + $t = hypo($f, $g); + $cs = $f / $t; + $sn = $g / $t; + $this->s[$j] = $t; + $f = $cs * $e[$j] + $sn * $this->s[$j + 1]; + $this->s[$j + 1] = -$sn * $e[$j] + $cs * $this->s[$j + 1]; + $g = $sn * $e[$j + 1]; + $e[$j + 1] = $cs * $e[$j + 1]; + if ($wantu && ($j < $this->m - 1)) { + for ($i = 0; $i < $this->m; ++$i) { + $t = $cs * $this->U[$i][$j] + $sn * $this->U[$i][$j + 1]; + $this->U[$i][$j + 1] = -$sn * $this->U[$i][$j] + $cs * $this->U[$i][$j + 1]; + $this->U[$i][$j] = $t; + } + } + } + $e[$p - 2] = $f; + $iter = $iter + 1; + + break; + // Convergence. + case 4: + // Make the singular values positive. + if ($this->s[$k] <= 0.0) { + $this->s[$k] = ($this->s[$k] < 0.0 ? -$this->s[$k] : 0.0); + if ($wantv) { + for ($i = 0; $i <= $pp; ++$i) { + $this->V[$i][$k] = -$this->V[$i][$k]; + } + } + } + // Order the singular values. + while ($k < $pp) { + if ($this->s[$k] >= $this->s[$k + 1]) { + break; + } + $t = $this->s[$k]; + $this->s[$k] = $this->s[$k + 1]; + $this->s[$k + 1] = $t; + if ($wantv && ($k < $this->n - 1)) { + for ($i = 0; $i < $this->n; ++$i) { + $t = $this->V[$i][$k + 1]; + $this->V[$i][$k + 1] = $this->V[$i][$k]; + $this->V[$i][$k] = $t; + } + } + if ($wantu && ($k < $this->m - 1)) { + for ($i = 0; $i < $this->m; ++$i) { + $t = $this->U[$i][$k + 1]; + $this->U[$i][$k + 1] = $this->U[$i][$k]; + $this->U[$i][$k] = $t; + } + } + ++$k; + } + $iter = 0; + --$p; + + break; + } // end switch + } // end while + } + + /** + * Return the left singular vectors. + * + * @return Matrix U + */ + public function getU() + { + return new Matrix($this->U, $this->m, min($this->m + 1, $this->n)); + } + + /** + * Return the right singular vectors. + * + * @return Matrix V + */ + public function getV() + { + return new Matrix($this->V); + } + + /** + * Return the one-dimensional array of singular values. + * + * @return array diagonal of S + */ + public function getSingularValues() + { + return $this->s; + } + + /** + * Return the diagonal matrix of singular values. + * + * @return Matrix S + */ + public function getS() + { + $S = []; + for ($i = 0; $i < $this->n; ++$i) { + for ($j = 0; $j < $this->n; ++$j) { + $S[$i][$j] = 0.0; + } + $S[$i][$i] = $this->s[$i]; + } + + return new Matrix($S); + } + + /** + * Two norm. + * + * @return float max(S) + */ + public function norm2() + { + return $this->s[0]; + } + + /** + * Two norm condition number. + * + * @return float max(S)/min(S) + */ + public function cond() + { + return $this->s[0] / $this->s[min($this->m, $this->n) - 1]; + } + + /** + * Effective numerical matrix rank. + * + * @return int Number of nonnegligible singular values + */ + public function rank() + { + $eps = 2.0 ** (-52.0); + $tol = max($this->m, $this->n) * $this->s[0] * $eps; + $r = 0; + $iMax = count($this->s); + for ($i = 0; $i < $iMax; ++$i) { + if ($this->s[$i] > $tol) { + ++$r; + } + } + + return $r; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/utils/Maths.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/utils/Maths.php new file mode 100644 index 0000000..49877b2 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/utils/Maths.php @@ -0,0 +1,31 @@ + abs($b)) { + $r = $b / $a; + $r = abs($a) * sqrt(1 + $r * $r); + } elseif ($b != 0) { + $r = $a / $b; + $r = abs($b) * sqrt(1 + $r * $r); + } else { + $r = 0.0; + } + + return $r; +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE.php new file mode 100644 index 0000000..ca97573 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE.php @@ -0,0 +1,556 @@ + | +// | Based on OLE::Storage_Lite by Kawai, Takanori | +// +----------------------------------------------------------------------+ +// + +use PhpOffice\PhpSpreadsheet\Exception; +use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException; +use PhpOffice\PhpSpreadsheet\Shared\OLE\ChainedBlockStream; +use PhpOffice\PhpSpreadsheet\Shared\OLE\PPS\Root; + +/* + * Array for storing OLE instances that are accessed from + * OLE_ChainedBlockStream::stream_open(). + * + * @var array + */ +$GLOBALS['_OLE_INSTANCES'] = []; + +/** + * OLE package base class. + * + * @author Xavier Noguer + * @author Christian Schmidt + */ +class OLE +{ + const OLE_PPS_TYPE_ROOT = 5; + const OLE_PPS_TYPE_DIR = 1; + const OLE_PPS_TYPE_FILE = 2; + const OLE_DATA_SIZE_SMALL = 0x1000; + const OLE_LONG_INT_SIZE = 4; + const OLE_PPS_SIZE = 0x80; + + /** + * The file handle for reading an OLE container. + * + * @var resource + */ + public $_file_handle; + + /** + * Array of PPS's found on the OLE container. + * + * @var array + */ + public $_list = []; + + /** + * Root directory of OLE container. + * + * @var Root + */ + public $root; + + /** + * Big Block Allocation Table. + * + * @var array (blockId => nextBlockId) + */ + public $bbat; + + /** + * Short Block Allocation Table. + * + * @var array (blockId => nextBlockId) + */ + public $sbat; + + /** + * Size of big blocks. This is usually 512. + * + * @var int number of octets per block + */ + public $bigBlockSize; + + /** + * Size of small blocks. This is usually 64. + * + * @var int number of octets per block + */ + public $smallBlockSize; + + /** + * Threshold for big blocks. + * + * @var int + */ + public $bigBlockThreshold; + + /** + * Reads an OLE container from the contents of the file given. + * + * @acces public + * + * @param string $filename + * + * @return bool true on success, PEAR_Error on failure + */ + public function read($filename) + { + $fh = fopen($filename, 'rb'); + if (!$fh) { + throw new ReaderException("Can't open file $filename"); + } + $this->_file_handle = $fh; + + $signature = fread($fh, 8); + if ("\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1" != $signature) { + throw new ReaderException("File doesn't seem to be an OLE container."); + } + fseek($fh, 28); + if (fread($fh, 2) != "\xFE\xFF") { + // This shouldn't be a problem in practice + throw new ReaderException('Only Little-Endian encoding is supported.'); + } + // Size of blocks and short blocks in bytes + $this->bigBlockSize = 2 ** self::readInt2($fh); + $this->smallBlockSize = 2 ** self::readInt2($fh); + + // Skip UID, revision number and version number + fseek($fh, 44); + // Number of blocks in Big Block Allocation Table + $bbatBlockCount = self::readInt4($fh); + + // Root chain 1st block + $directoryFirstBlockId = self::readInt4($fh); + + // Skip unused bytes + fseek($fh, 56); + // Streams shorter than this are stored using small blocks + $this->bigBlockThreshold = self::readInt4($fh); + // Block id of first sector in Short Block Allocation Table + $sbatFirstBlockId = self::readInt4($fh); + // Number of blocks in Short Block Allocation Table + $sbbatBlockCount = self::readInt4($fh); + // Block id of first sector in Master Block Allocation Table + $mbatFirstBlockId = self::readInt4($fh); + // Number of blocks in Master Block Allocation Table + $mbbatBlockCount = self::readInt4($fh); + $this->bbat = []; + + // Remaining 4 * 109 bytes of current block is beginning of Master + // Block Allocation Table + $mbatBlocks = []; + for ($i = 0; $i < 109; ++$i) { + $mbatBlocks[] = self::readInt4($fh); + } + + // Read rest of Master Block Allocation Table (if any is left) + $pos = $this->getBlockOffset($mbatFirstBlockId); + for ($i = 0; $i < $mbbatBlockCount; ++$i) { + fseek($fh, $pos); + for ($j = 0; $j < $this->bigBlockSize / 4 - 1; ++$j) { + $mbatBlocks[] = self::readInt4($fh); + } + // Last block id in each block points to next block + $pos = $this->getBlockOffset(self::readInt4($fh)); + } + + // Read Big Block Allocation Table according to chain specified by $mbatBlocks + for ($i = 0; $i < $bbatBlockCount; ++$i) { + $pos = $this->getBlockOffset($mbatBlocks[$i]); + fseek($fh, $pos); + for ($j = 0; $j < $this->bigBlockSize / 4; ++$j) { + $this->bbat[] = self::readInt4($fh); + } + } + + // Read short block allocation table (SBAT) + $this->sbat = []; + $shortBlockCount = $sbbatBlockCount * $this->bigBlockSize / 4; + $sbatFh = $this->getStream($sbatFirstBlockId); + for ($blockId = 0; $blockId < $shortBlockCount; ++$blockId) { + $this->sbat[$blockId] = self::readInt4($sbatFh); + } + fclose($sbatFh); + + $this->readPpsWks($directoryFirstBlockId); + + return true; + } + + /** + * @param int $blockId byte offset from beginning of file + * + * @return int + */ + public function getBlockOffset($blockId) + { + return 512 + $blockId * $this->bigBlockSize; + } + + /** + * Returns a stream for use with fread() etc. External callers should + * use \PhpOffice\PhpSpreadsheet\Shared\OLE\PPS\File::getStream(). + * + * @param int|OLE\PPS $blockIdOrPps block id or PPS + * + * @return resource read-only stream + */ + public function getStream($blockIdOrPps) + { + static $isRegistered = false; + if (!$isRegistered) { + stream_wrapper_register('ole-chainedblockstream', ChainedBlockStream::class); + $isRegistered = true; + } + + // Store current instance in global array, so that it can be accessed + // in OLE_ChainedBlockStream::stream_open(). + // Object is removed from self::$instances in OLE_Stream::close(). + $GLOBALS['_OLE_INSTANCES'][] = $this; + $keys = array_keys($GLOBALS['_OLE_INSTANCES']); + $instanceId = end($keys); + + $path = 'ole-chainedblockstream://oleInstanceId=' . $instanceId; + if ($blockIdOrPps instanceof OLE\PPS) { + $path .= '&blockId=' . $blockIdOrPps->startBlock; + $path .= '&size=' . $blockIdOrPps->Size; + } else { + $path .= '&blockId=' . $blockIdOrPps; + } + + return fopen($path, 'rb'); + } + + /** + * Reads a signed char. + * + * @param resource $fileHandle file handle + * + * @return int + */ + private static function readInt1($fileHandle) + { + [, $tmp] = unpack('c', fread($fileHandle, 1)); + + return $tmp; + } + + /** + * Reads an unsigned short (2 octets). + * + * @param resource $fileHandle file handle + * + * @return int + */ + private static function readInt2($fileHandle) + { + [, $tmp] = unpack('v', fread($fileHandle, 2)); + + return $tmp; + } + + /** + * Reads an unsigned long (4 octets). + * + * @param resource $fileHandle file handle + * + * @return int + */ + private static function readInt4($fileHandle) + { + [, $tmp] = unpack('V', fread($fileHandle, 4)); + + return $tmp; + } + + /** + * Gets information about all PPS's on the OLE container from the PPS WK's + * creates an OLE_PPS object for each one. + * + * @param int $blockId the block id of the first block + * + * @return bool true on success, PEAR_Error on failure + */ + public function readPpsWks($blockId) + { + $fh = $this->getStream($blockId); + for ($pos = 0; true; $pos += 128) { + fseek($fh, $pos, SEEK_SET); + $nameUtf16 = fread($fh, 64); + $nameLength = self::readInt2($fh); + $nameUtf16 = substr($nameUtf16, 0, $nameLength - 2); + // Simple conversion from UTF-16LE to ISO-8859-1 + $name = str_replace("\x00", '', $nameUtf16); + $type = self::readInt1($fh); + switch ($type) { + case self::OLE_PPS_TYPE_ROOT: + $pps = new OLE\PPS\Root(null, null, []); + $this->root = $pps; + + break; + case self::OLE_PPS_TYPE_DIR: + $pps = new OLE\PPS(null, null, null, null, null, null, null, null, null, []); + + break; + case self::OLE_PPS_TYPE_FILE: + $pps = new OLE\PPS\File($name); + + break; + default: + throw new Exception('Unsupported PPS type'); + } + fseek($fh, 1, SEEK_CUR); + $pps->Type = $type; + $pps->Name = $name; + $pps->PrevPps = self::readInt4($fh); + $pps->NextPps = self::readInt4($fh); + $pps->DirPps = self::readInt4($fh); + fseek($fh, 20, SEEK_CUR); + $pps->Time1st = self::OLE2LocalDate(fread($fh, 8)); + $pps->Time2nd = self::OLE2LocalDate(fread($fh, 8)); + $pps->startBlock = self::readInt4($fh); + $pps->Size = self::readInt4($fh); + $pps->No = count($this->_list); + $this->_list[] = $pps; + + // check if the PPS tree (starting from root) is complete + if (isset($this->root) && $this->ppsTreeComplete($this->root->No)) { + break; + } + } + fclose($fh); + + // Initialize $pps->children on directories + foreach ($this->_list as $pps) { + if ($pps->Type == self::OLE_PPS_TYPE_DIR || $pps->Type == self::OLE_PPS_TYPE_ROOT) { + $nos = [$pps->DirPps]; + $pps->children = []; + while ($nos) { + $no = array_pop($nos); + if ($no != -1) { + $childPps = $this->_list[$no]; + $nos[] = $childPps->PrevPps; + $nos[] = $childPps->NextPps; + $pps->children[] = $childPps; + } + } + } + } + + return true; + } + + /** + * It checks whether the PPS tree is complete (all PPS's read) + * starting with the given PPS (not necessarily root). + * + * @param int $index The index of the PPS from which we are checking + * + * @return bool Whether the PPS tree for the given PPS is complete + */ + private function ppsTreeComplete($index) + { + return isset($this->_list[$index]) && + ($pps = $this->_list[$index]) && + ($pps->PrevPps == -1 || + $this->ppsTreeComplete($pps->PrevPps)) && + ($pps->NextPps == -1 || + $this->ppsTreeComplete($pps->NextPps)) && + ($pps->DirPps == -1 || + $this->ppsTreeComplete($pps->DirPps)); + } + + /** + * Checks whether a PPS is a File PPS or not. + * If there is no PPS for the index given, it will return false. + * + * @param int $index The index for the PPS + * + * @return bool true if it's a File PPS, false otherwise + */ + public function isFile($index) + { + if (isset($this->_list[$index])) { + return $this->_list[$index]->Type == self::OLE_PPS_TYPE_FILE; + } + + return false; + } + + /** + * Checks whether a PPS is a Root PPS or not. + * If there is no PPS for the index given, it will return false. + * + * @param int $index the index for the PPS + * + * @return bool true if it's a Root PPS, false otherwise + */ + public function isRoot($index) + { + if (isset($this->_list[$index])) { + return $this->_list[$index]->Type == self::OLE_PPS_TYPE_ROOT; + } + + return false; + } + + /** + * Gives the total number of PPS's found in the OLE container. + * + * @return int The total number of PPS's found in the OLE container + */ + public function ppsTotal() + { + return count($this->_list); + } + + /** + * Gets data from a PPS + * If there is no PPS for the index given, it will return an empty string. + * + * @param int $index The index for the PPS + * @param int $position The position from which to start reading + * (relative to the PPS) + * @param int $length The amount of bytes to read (at most) + * + * @return string The binary string containing the data requested + * + * @see OLE_PPS_File::getStream() + */ + public function getData($index, $position, $length) + { + // if position is not valid return empty string + if (!isset($this->_list[$index]) || ($position >= $this->_list[$index]->Size) || ($position < 0)) { + return ''; + } + $fh = $this->getStream($this->_list[$index]); + $data = stream_get_contents($fh, $length, $position); + fclose($fh); + + return $data; + } + + /** + * Gets the data length from a PPS + * If there is no PPS for the index given, it will return 0. + * + * @param int $index The index for the PPS + * + * @return int The amount of bytes in data the PPS has + */ + public function getDataLength($index) + { + if (isset($this->_list[$index])) { + return $this->_list[$index]->Size; + } + + return 0; + } + + /** + * Utility function to transform ASCII text to Unicode. + * + * @param string $ascii The ASCII string to transform + * + * @return string The string in Unicode + */ + public static function ascToUcs($ascii) + { + $rawname = ''; + $iMax = strlen($ascii); + for ($i = 0; $i < $iMax; ++$i) { + $rawname .= $ascii[$i] + . "\x00"; + } + + return $rawname; + } + + /** + * Utility function + * Returns a string for the OLE container with the date given. + * + * @param float|int $date A timestamp + * + * @return string The string for the OLE container + */ + public static function localDateToOLE($date) + { + if (!$date) { + return "\x00\x00\x00\x00\x00\x00\x00\x00"; + } + $dateTime = Date::dateTimeFromTimestamp("$date"); + + // days from 1-1-1601 until the beggining of UNIX era + $days = 134774; + // calculate seconds + $big_date = $days * 24 * 3600 + (float) $dateTime->format('U'); + // multiply just to make MS happy + $big_date *= 10000000; + + // Make HEX string + $res = ''; + + $factor = 2 ** 56; + while ($factor >= 1) { + $hex = (int) floor($big_date / $factor); + $res = pack('c', $hex) . $res; + $big_date = fmod($big_date, $factor); + $factor /= 256; + } + + return $res; + } + + /** + * Returns a timestamp from an OLE container's date. + * + * @param string $oleTimestamp A binary string with the encoded date + * + * @return float|int The Unix timestamp corresponding to the string + */ + public static function OLE2LocalDate($oleTimestamp) + { + if (strlen($oleTimestamp) != 8) { + throw new ReaderException('Expecting 8 byte string'); + } + + // convert to units of 100 ns since 1601: + $unpackedTimestamp = unpack('v4', $oleTimestamp); + $timestampHigh = (float) $unpackedTimestamp[4] * 65536 + (float) $unpackedTimestamp[3]; + $timestampLow = (float) $unpackedTimestamp[2] * 65536 + (float) $unpackedTimestamp[1]; + + // translate to seconds since 1601: + $timestampHigh /= 10000000; + $timestampLow /= 10000000; + + // days from 1601 to 1970: + $days = 134774; + + // translate to seconds since 1970: + $unixTimestamp = floor(65536.0 * 65536.0 * $timestampHigh + $timestampLow - $days * 24 * 3600 + 0.5); + + return IntOrFloat::evaluate($unixTimestamp); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/ChainedBlockStream.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/ChainedBlockStream.php new file mode 100644 index 0000000..43e4804 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/ChainedBlockStream.php @@ -0,0 +1,196 @@ +params); + if (!isset($this->params['oleInstanceId'], $this->params['blockId'], $GLOBALS['_OLE_INSTANCES'][$this->params['oleInstanceId']])) { + if ($options & STREAM_REPORT_ERRORS) { + trigger_error('OLE stream not found', E_USER_WARNING); + } + + return false; + } + $this->ole = $GLOBALS['_OLE_INSTANCES'][$this->params['oleInstanceId']]; + + $blockId = $this->params['blockId']; + $this->data = ''; + if (isset($this->params['size']) && $this->params['size'] < $this->ole->bigBlockThreshold && $blockId != $this->ole->root->startBlock) { + // Block id refers to small blocks + $rootPos = $this->ole->getBlockOffset($this->ole->root->startBlock); + while ($blockId != -2) { + $pos = $rootPos + $blockId * $this->ole->bigBlockSize; + $blockId = $this->ole->sbat[$blockId]; + fseek($this->ole->_file_handle, $pos); + $this->data .= fread($this->ole->_file_handle, $this->ole->bigBlockSize); + } + } else { + // Block id refers to big blocks + while ($blockId != -2) { + $pos = $this->ole->getBlockOffset($blockId); + fseek($this->ole->_file_handle, $pos); + $this->data .= fread($this->ole->_file_handle, $this->ole->bigBlockSize); + $blockId = $this->ole->bbat[$blockId]; + } + } + if (isset($this->params['size'])) { + $this->data = substr($this->data, 0, $this->params['size']); + } + + if ($options & STREAM_USE_PATH) { + $openedPath = $path; + } + + return true; + } + + /** + * Implements support for fclose(). + */ + public function stream_close(): void // @codingStandardsIgnoreLine + { + $this->ole = null; + unset($GLOBALS['_OLE_INSTANCES']); + } + + /** + * Implements support for fread(), fgets() etc. + * + * @param int $count maximum number of bytes to read + * + * @return false|string + */ + public function stream_read($count) // @codingStandardsIgnoreLine + { + if ($this->stream_eof()) { + return false; + } + $s = substr($this->data, $this->pos, $count); + $this->pos += $count; + + return $s; + } + + /** + * Implements support for feof(). + * + * @return bool TRUE if the file pointer is at EOF; otherwise FALSE + */ + public function stream_eof() // @codingStandardsIgnoreLine + { + return $this->pos >= strlen($this->data); + } + + /** + * Returns the position of the file pointer, i.e. its offset into the file + * stream. Implements support for ftell(). + * + * @return int + */ + public function stream_tell() // @codingStandardsIgnoreLine + { + return $this->pos; + } + + /** + * Implements support for fseek(). + * + * @param int $offset byte offset + * @param int $whence SEEK_SET, SEEK_CUR or SEEK_END + * + * @return bool + */ + public function stream_seek($offset, $whence) // @codingStandardsIgnoreLine + { + if ($whence == SEEK_SET && $offset >= 0) { + $this->pos = $offset; + } elseif ($whence == SEEK_CUR && -$offset <= $this->pos) { + $this->pos += $offset; + } elseif ($whence == SEEK_END && -$offset <= count($this->data)) { + $this->pos = strlen($this->data) + $offset; + } else { + return false; + } + + return true; + } + + /** + * Implements support for fstat(). Currently the only supported field is + * "size". + * + * @return array + */ + public function stream_stat() // @codingStandardsIgnoreLine + { + return [ + 'size' => strlen($this->data), + ]; + } + + // Methods used by stream_wrapper_register() that are not implemented: + // bool stream_flush ( void ) + // int stream_write ( string data ) + // bool rename ( string path_from, string path_to ) + // bool mkdir ( string path, int mode, int options ) + // bool rmdir ( string path, int options ) + // bool dir_opendir ( string path, int options ) + // array url_stat ( string path, int flags ) + // string dir_readdir ( void ) + // bool dir_rewinddir ( void ) + // bool dir_closedir ( void ) +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS.php new file mode 100644 index 0000000..8b9e92a --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS.php @@ -0,0 +1,237 @@ + | +// | Based on OLE::Storage_Lite by Kawai, Takanori | +// +----------------------------------------------------------------------+ +// +use PhpOffice\PhpSpreadsheet\Shared\OLE; + +/** + * Class for creating PPS's for OLE containers. + * + * @author Xavier Noguer + */ +class PPS +{ + /** + * The PPS index. + * + * @var int + */ + public $No; + + /** + * The PPS name (in Unicode). + * + * @var string + */ + public $Name; + + /** + * The PPS type. Dir, Root or File. + * + * @var int + */ + public $Type; + + /** + * The index of the previous PPS. + * + * @var int + */ + public $PrevPps; + + /** + * The index of the next PPS. + * + * @var int + */ + public $NextPps; + + /** + * The index of it's first child if this is a Dir or Root PPS. + * + * @var int + */ + public $DirPps; + + /** + * A timestamp. + * + * @var float|int + */ + public $Time1st; + + /** + * A timestamp. + * + * @var float|int + */ + public $Time2nd; + + /** + * Starting block (small or big) for this PPS's data inside the container. + * + * @var int + */ + public $startBlock; + + /** + * The size of the PPS's data (in bytes). + * + * @var int + */ + public $Size; + + /** + * The PPS's data (only used if it's not using a temporary file). + * + * @var string + */ + public $_data; + + /** + * Array of child PPS's (only used by Root and Dir PPS's). + * + * @var array + */ + public $children = []; + + /** + * Pointer to OLE container. + * + * @var OLE + */ + public $ole; + + /** + * The constructor. + * + * @param int $No The PPS index + * @param string $name The PPS name + * @param int $type The PPS type. Dir, Root or File + * @param int $prev The index of the previous PPS + * @param int $next The index of the next PPS + * @param int $dir The index of it's first child if this is a Dir or Root PPS + * @param null|float|int $time_1st A timestamp + * @param null|float|int $time_2nd A timestamp + * @param string $data The (usually binary) source data of the PPS + * @param array $children Array containing children PPS for this PPS + */ + public function __construct($No, $name, $type, $prev, $next, $dir, $time_1st, $time_2nd, $data, $children) + { + $this->No = $No; + $this->Name = $name; + $this->Type = $type; + $this->PrevPps = $prev; + $this->NextPps = $next; + $this->DirPps = $dir; + $this->Time1st = $time_1st ?? 0; + $this->Time2nd = $time_2nd ?? 0; + $this->_data = $data; + $this->children = $children; + if ($data != '') { + $this->Size = strlen($data); + } else { + $this->Size = 0; + } + } + + /** + * Returns the amount of data saved for this PPS. + * + * @return int The amount of data (in bytes) + */ + public function getDataLen() + { + if (!isset($this->_data)) { + return 0; + } + + return strlen($this->_data); + } + + /** + * Returns a string with the PPS's WK (What is a WK?). + * + * @return string The binary string + */ + public function getPpsWk() + { + $ret = str_pad($this->Name, 64, "\x00"); + + $ret .= pack('v', strlen($this->Name) + 2) // 66 + . pack('c', $this->Type) // 67 + . pack('c', 0x00) //UK // 68 + . pack('V', $this->PrevPps) //Prev // 72 + . pack('V', $this->NextPps) //Next // 76 + . pack('V', $this->DirPps) //Dir // 80 + . "\x00\x09\x02\x00" // 84 + . "\x00\x00\x00\x00" // 88 + . "\xc0\x00\x00\x00" // 92 + . "\x00\x00\x00\x46" // 96 // Seems to be ok only for Root + . "\x00\x00\x00\x00" // 100 + . OLE::localDateToOLE($this->Time1st) // 108 + . OLE::localDateToOLE($this->Time2nd) // 116 + . pack('V', $this->startBlock ?? 0) // 120 + . pack('V', $this->Size) // 124 + . pack('V', 0); // 128 + + return $ret; + } + + /** + * Updates index and pointers to previous, next and children PPS's for this + * PPS. I don't think it'll work with Dir PPS's. + * + * @param array $raList Reference to the array of PPS's for the whole OLE + * container + * @param mixed $to_save + * @param mixed $depth + * + * @return int The index for this PPS + */ + public static function savePpsSetPnt(&$raList, $to_save, $depth = 0) + { + if (!is_array($to_save) || (empty($to_save))) { + return 0xFFFFFFFF; + } elseif (count($to_save) == 1) { + $cnt = count($raList); + // If the first entry, it's the root... Don't clone it! + $raList[$cnt] = ($depth == 0) ? $to_save[0] : clone $to_save[0]; + $raList[$cnt]->No = $cnt; + $raList[$cnt]->PrevPps = 0xFFFFFFFF; + $raList[$cnt]->NextPps = 0xFFFFFFFF; + $raList[$cnt]->DirPps = self::savePpsSetPnt($raList, @$raList[$cnt]->children, $depth++); + } else { + $iPos = floor(count($to_save) / 2); + $aPrev = array_slice($to_save, 0, $iPos); + $aNext = array_slice($to_save, $iPos + 1); + $cnt = count($raList); + // If the first entry, it's the root... Don't clone it! + $raList[$cnt] = ($depth == 0) ? $to_save[$iPos] : clone $to_save[$iPos]; + $raList[$cnt]->No = $cnt; + $raList[$cnt]->PrevPps = self::savePpsSetPnt($raList, $aPrev, $depth++); + $raList[$cnt]->NextPps = self::savePpsSetPnt($raList, $aNext, $depth++); + $raList[$cnt]->DirPps = self::savePpsSetPnt($raList, @$raList[$cnt]->children, $depth++); + } + + return $cnt; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS/File.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS/File.php new file mode 100644 index 0000000..dd1cda2 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS/File.php @@ -0,0 +1,64 @@ + | +// | Based on OLE::Storage_Lite by Kawai, Takanori | +// +----------------------------------------------------------------------+ +// +use PhpOffice\PhpSpreadsheet\Shared\OLE; +use PhpOffice\PhpSpreadsheet\Shared\OLE\PPS; + +/** + * Class for creating File PPS's for OLE containers. + * + * @author Xavier Noguer + */ +class File extends PPS +{ + /** + * The constructor. + * + * @param string $name The name of the file (in Unicode) + * + * @see OLE::ascToUcs() + */ + public function __construct($name) + { + parent::__construct(null, $name, OLE::OLE_PPS_TYPE_FILE, null, null, null, null, null, '', []); + } + + /** + * Initialization method. Has to be called right after OLE_PPS_File(). + * + * @return mixed true on success + */ + public function init() + { + return true; + } + + /** + * Append data to PPS. + * + * @param string $data The data to append + */ + public function append($data): void + { + $this->_data .= $data; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS/Root.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS/Root.php new file mode 100644 index 0000000..7a31d77 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS/Root.php @@ -0,0 +1,426 @@ + | +// | Based on OLE::Storage_Lite by Kawai, Takanori | +// +----------------------------------------------------------------------+ +// +use PhpOffice\PhpSpreadsheet\Shared\OLE; +use PhpOffice\PhpSpreadsheet\Shared\OLE\PPS; + +/** + * Class for creating Root PPS's for OLE containers. + * + * @author Xavier Noguer + */ +class Root extends PPS +{ + /** + * @var resource + */ + private $fileHandle; + + /** + * @var int + */ + private $smallBlockSize; + + /** + * @var int + */ + private $bigBlockSize; + + /** + * @param null|float|int $time_1st A timestamp + * @param null|float|int $time_2nd A timestamp + * @param File[] $raChild + */ + public function __construct($time_1st, $time_2nd, $raChild) + { + parent::__construct(null, OLE::ascToUcs('Root Entry'), OLE::OLE_PPS_TYPE_ROOT, null, null, null, $time_1st, $time_2nd, null, $raChild); + } + + /** + * Method for saving the whole OLE container (including files). + * In fact, if called with an empty argument (or '-'), it saves to a + * temporary file and then outputs it's contents to stdout. + * If a resource pointer to a stream created by fopen() is passed + * it will be used, but you have to close such stream by yourself. + * + * @param resource $fileHandle the name of the file or stream where to save the OLE container + * + * @return bool true on success + */ + public function save($fileHandle) + { + $this->fileHandle = $fileHandle; + + // Initial Setting for saving + $this->bigBlockSize = 2 ** ( + (isset($this->bigBlockSize)) ? self::adjust2($this->bigBlockSize) : 9 + ); + $this->smallBlockSize = 2 ** ( + (isset($this->smallBlockSize)) ? self::adjust2($this->smallBlockSize) : 6 + ); + + // Make an array of PPS's (for Save) + $aList = []; + PPS::savePpsSetPnt($aList, [$this]); + // calculate values for header + [$iSBDcnt, $iBBcnt, $iPPScnt] = $this->calcSize($aList); //, $rhInfo); + // Save Header + $this->saveHeader($iSBDcnt, $iBBcnt, $iPPScnt); + + // Make Small Data string (write SBD) + $this->_data = $this->makeSmallData($aList); + + // Write BB + $this->saveBigData($iSBDcnt, $aList); + // Write PPS + $this->savePps($aList); + // Write Big Block Depot and BDList and Adding Header informations + $this->saveBbd($iSBDcnt, $iBBcnt, $iPPScnt); + + return true; + } + + /** + * Calculate some numbers. + * + * @param array $raList Reference to an array of PPS's + * + * @return float[] The array of numbers + */ + private function calcSize(&$raList) + { + // Calculate Basic Setting + [$iSBDcnt, $iBBcnt, $iPPScnt] = [0, 0, 0]; + $iSmallLen = 0; + $iSBcnt = 0; + $iCount = count($raList); + for ($i = 0; $i < $iCount; ++$i) { + if ($raList[$i]->Type == OLE::OLE_PPS_TYPE_FILE) { + $raList[$i]->Size = $raList[$i]->getDataLen(); + if ($raList[$i]->Size < OLE::OLE_DATA_SIZE_SMALL) { + $iSBcnt += floor($raList[$i]->Size / $this->smallBlockSize) + + (($raList[$i]->Size % $this->smallBlockSize) ? 1 : 0); + } else { + $iBBcnt += (floor($raList[$i]->Size / $this->bigBlockSize) + + (($raList[$i]->Size % $this->bigBlockSize) ? 1 : 0)); + } + } + } + $iSmallLen = $iSBcnt * $this->smallBlockSize; + $iSlCnt = floor($this->bigBlockSize / OLE::OLE_LONG_INT_SIZE); + $iSBDcnt = floor($iSBcnt / $iSlCnt) + (($iSBcnt % $iSlCnt) ? 1 : 0); + $iBBcnt += (floor($iSmallLen / $this->bigBlockSize) + + (($iSmallLen % $this->bigBlockSize) ? 1 : 0)); + $iCnt = count($raList); + $iBdCnt = $this->bigBlockSize / OLE::OLE_PPS_SIZE; + $iPPScnt = (floor($iCnt / $iBdCnt) + (($iCnt % $iBdCnt) ? 1 : 0)); + + return [$iSBDcnt, $iBBcnt, $iPPScnt]; + } + + /** + * Helper function for caculating a magic value for block sizes. + * + * @param int $i2 The argument + * + * @return float + * + * @see save() + */ + private static function adjust2($i2) + { + $iWk = log($i2) / log(2); + + return ($iWk > floor($iWk)) ? floor($iWk) + 1 : $iWk; + } + + /** + * Save OLE header. + * + * @param int $iSBDcnt + * @param int $iBBcnt + * @param int $iPPScnt + */ + private function saveHeader($iSBDcnt, $iBBcnt, $iPPScnt): void + { + $FILE = $this->fileHandle; + + // Calculate Basic Setting + $iBlCnt = $this->bigBlockSize / OLE::OLE_LONG_INT_SIZE; + $i1stBdL = ($this->bigBlockSize - 0x4C) / OLE::OLE_LONG_INT_SIZE; + + $iBdExL = 0; + $iAll = $iBBcnt + $iPPScnt + $iSBDcnt; + $iAllW = $iAll; + $iBdCntW = floor($iAllW / $iBlCnt) + (($iAllW % $iBlCnt) ? 1 : 0); + $iBdCnt = floor(($iAll + $iBdCntW) / $iBlCnt) + ((($iAllW + $iBdCntW) % $iBlCnt) ? 1 : 0); + + // Calculate BD count + if ($iBdCnt > $i1stBdL) { + while (1) { + ++$iBdExL; + ++$iAllW; + $iBdCntW = floor($iAllW / $iBlCnt) + (($iAllW % $iBlCnt) ? 1 : 0); + $iBdCnt = floor(($iAllW + $iBdCntW) / $iBlCnt) + ((($iAllW + $iBdCntW) % $iBlCnt) ? 1 : 0); + if ($iBdCnt <= ($iBdExL * $iBlCnt + $i1stBdL)) { + break; + } + } + } + + // Save Header + fwrite( + $FILE, + "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1" + . "\x00\x00\x00\x00" + . "\x00\x00\x00\x00" + . "\x00\x00\x00\x00" + . "\x00\x00\x00\x00" + . pack('v', 0x3b) + . pack('v', 0x03) + . pack('v', -2) + . pack('v', 9) + . pack('v', 6) + . pack('v', 0) + . "\x00\x00\x00\x00" + . "\x00\x00\x00\x00" + . pack('V', $iBdCnt) + . pack('V', $iBBcnt + $iSBDcnt) //ROOT START + . pack('V', 0) + . pack('V', 0x1000) + . pack('V', $iSBDcnt ? 0 : -2) //Small Block Depot + . pack('V', $iSBDcnt) + ); + // Extra BDList Start, Count + if ($iBdCnt < $i1stBdL) { + fwrite( + $FILE, + pack('V', -2) // Extra BDList Start + . pack('V', 0)// Extra BDList Count + ); + } else { + fwrite($FILE, pack('V', $iAll + $iBdCnt) . pack('V', $iBdExL)); + } + + // BDList + for ($i = 0; $i < $i1stBdL && $i < $iBdCnt; ++$i) { + fwrite($FILE, pack('V', $iAll + $i)); + } + if ($i < $i1stBdL) { + $jB = $i1stBdL - $i; + for ($j = 0; $j < $jB; ++$j) { + fwrite($FILE, (pack('V', -1))); + } + } + } + + /** + * Saving big data (PPS's with data bigger than \PhpOffice\PhpSpreadsheet\Shared\OLE::OLE_DATA_SIZE_SMALL). + * + * @param int $iStBlk + * @param array $raList Reference to array of PPS's + */ + private function saveBigData($iStBlk, &$raList): void + { + $FILE = $this->fileHandle; + + // cycle through PPS's + $iCount = count($raList); + for ($i = 0; $i < $iCount; ++$i) { + if ($raList[$i]->Type != OLE::OLE_PPS_TYPE_DIR) { + $raList[$i]->Size = $raList[$i]->getDataLen(); + if (($raList[$i]->Size >= OLE::OLE_DATA_SIZE_SMALL) || (($raList[$i]->Type == OLE::OLE_PPS_TYPE_ROOT) && isset($raList[$i]->_data))) { + fwrite($FILE, $raList[$i]->_data); + + if ($raList[$i]->Size % $this->bigBlockSize) { + fwrite($FILE, str_repeat("\x00", $this->bigBlockSize - ($raList[$i]->Size % $this->bigBlockSize))); + } + // Set For PPS + $raList[$i]->startBlock = $iStBlk; + $iStBlk += + (floor($raList[$i]->Size / $this->bigBlockSize) + + (($raList[$i]->Size % $this->bigBlockSize) ? 1 : 0)); + } + } + } + } + + /** + * get small data (PPS's with data smaller than \PhpOffice\PhpSpreadsheet\Shared\OLE::OLE_DATA_SIZE_SMALL). + * + * @param array $raList Reference to array of PPS's + * + * @return string + */ + private function makeSmallData(&$raList) + { + $sRes = ''; + $FILE = $this->fileHandle; + $iSmBlk = 0; + + $iCount = count($raList); + for ($i = 0; $i < $iCount; ++$i) { + // Make SBD, small data string + if ($raList[$i]->Type == OLE::OLE_PPS_TYPE_FILE) { + if ($raList[$i]->Size <= 0) { + continue; + } + if ($raList[$i]->Size < OLE::OLE_DATA_SIZE_SMALL) { + $iSmbCnt = floor($raList[$i]->Size / $this->smallBlockSize) + + (($raList[$i]->Size % $this->smallBlockSize) ? 1 : 0); + // Add to SBD + $jB = $iSmbCnt - 1; + for ($j = 0; $j < $jB; ++$j) { + fwrite($FILE, pack('V', $j + $iSmBlk + 1)); + } + fwrite($FILE, pack('V', -2)); + + // Add to Data String(this will be written for RootEntry) + $sRes .= $raList[$i]->_data; + if ($raList[$i]->Size % $this->smallBlockSize) { + $sRes .= str_repeat("\x00", $this->smallBlockSize - ($raList[$i]->Size % $this->smallBlockSize)); + } + // Set for PPS + $raList[$i]->startBlock = $iSmBlk; + $iSmBlk += $iSmbCnt; + } + } + } + $iSbCnt = floor($this->bigBlockSize / OLE::OLE_LONG_INT_SIZE); + if ($iSmBlk % $iSbCnt) { + $iB = $iSbCnt - ($iSmBlk % $iSbCnt); + for ($i = 0; $i < $iB; ++$i) { + fwrite($FILE, pack('V', -1)); + } + } + + return $sRes; + } + + /** + * Saves all the PPS's WKs. + * + * @param array $raList Reference to an array with all PPS's + */ + private function savePps(&$raList): void + { + // Save each PPS WK + $iC = count($raList); + for ($i = 0; $i < $iC; ++$i) { + fwrite($this->fileHandle, $raList[$i]->getPpsWk()); + } + // Adjust for Block + $iCnt = count($raList); + $iBCnt = $this->bigBlockSize / OLE::OLE_PPS_SIZE; + if ($iCnt % $iBCnt) { + fwrite($this->fileHandle, str_repeat("\x00", ($iBCnt - ($iCnt % $iBCnt)) * OLE::OLE_PPS_SIZE)); + } + } + + /** + * Saving Big Block Depot. + * + * @param int $iSbdSize + * @param int $iBsize + * @param int $iPpsCnt + */ + private function saveBbd($iSbdSize, $iBsize, $iPpsCnt): void + { + $FILE = $this->fileHandle; + // Calculate Basic Setting + $iBbCnt = $this->bigBlockSize / OLE::OLE_LONG_INT_SIZE; + $i1stBdL = ($this->bigBlockSize - 0x4C) / OLE::OLE_LONG_INT_SIZE; + + $iBdExL = 0; + $iAll = $iBsize + $iPpsCnt + $iSbdSize; + $iAllW = $iAll; + $iBdCntW = floor($iAllW / $iBbCnt) + (($iAllW % $iBbCnt) ? 1 : 0); + $iBdCnt = floor(($iAll + $iBdCntW) / $iBbCnt) + ((($iAllW + $iBdCntW) % $iBbCnt) ? 1 : 0); + // Calculate BD count + if ($iBdCnt > $i1stBdL) { + while (1) { + ++$iBdExL; + ++$iAllW; + $iBdCntW = floor($iAllW / $iBbCnt) + (($iAllW % $iBbCnt) ? 1 : 0); + $iBdCnt = floor(($iAllW + $iBdCntW) / $iBbCnt) + ((($iAllW + $iBdCntW) % $iBbCnt) ? 1 : 0); + if ($iBdCnt <= ($iBdExL * $iBbCnt + $i1stBdL)) { + break; + } + } + } + + // Making BD + // Set for SBD + if ($iSbdSize > 0) { + for ($i = 0; $i < ($iSbdSize - 1); ++$i) { + fwrite($FILE, pack('V', $i + 1)); + } + fwrite($FILE, pack('V', -2)); + } + // Set for B + for ($i = 0; $i < ($iBsize - 1); ++$i) { + fwrite($FILE, pack('V', $i + $iSbdSize + 1)); + } + fwrite($FILE, pack('V', -2)); + + // Set for PPS + for ($i = 0; $i < ($iPpsCnt - 1); ++$i) { + fwrite($FILE, pack('V', $i + $iSbdSize + $iBsize + 1)); + } + fwrite($FILE, pack('V', -2)); + // Set for BBD itself ( 0xFFFFFFFD : BBD) + for ($i = 0; $i < $iBdCnt; ++$i) { + fwrite($FILE, pack('V', 0xFFFFFFFD)); + } + // Set for ExtraBDList + for ($i = 0; $i < $iBdExL; ++$i) { + fwrite($FILE, pack('V', 0xFFFFFFFC)); + } + // Adjust for Block + if (($iAllW + $iBdCnt) % $iBbCnt) { + $iBlock = ($iBbCnt - (($iAllW + $iBdCnt) % $iBbCnt)); + for ($i = 0; $i < $iBlock; ++$i) { + fwrite($FILE, pack('V', -1)); + } + } + // Extra BDList + if ($iBdCnt > $i1stBdL) { + $iN = 0; + $iNb = 0; + for ($i = $i1stBdL; $i < $iBdCnt; $i++, ++$iN) { + if ($iN >= ($iBbCnt - 1)) { + $iN = 0; + ++$iNb; + fwrite($FILE, pack('V', $iAll + $iBdCnt + $iNb)); + } + fwrite($FILE, pack('V', $iBsize + $iSbdSize + $iPpsCnt + $i)); + } + if (($iBdCnt - $i1stBdL) % ($iBbCnt - 1)) { + $iB = ($iBbCnt - 1) - (($iBdCnt - $i1stBdL) % ($iBbCnt - 1)); + for ($i = 0; $i < $iB; ++$i) { + fwrite($FILE, pack('V', -1)); + } + } + fwrite($FILE, pack('V', -2)); + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLERead.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLERead.php new file mode 100644 index 0000000..91f1d04 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLERead.php @@ -0,0 +1,347 @@ +data = file_get_contents($filename, false, null, 0, 8); + + // Check OLE identifier + $identifierOle = pack('CCCCCCCC', 0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1); + if ($this->data != $identifierOle) { + throw new ReaderException('The filename ' . $filename . ' is not recognised as an OLE file'); + } + + // Get the file data + $this->data = file_get_contents($filename); + + // Total number of sectors used for the SAT + $this->numBigBlockDepotBlocks = self::getInt4d($this->data, self::NUM_BIG_BLOCK_DEPOT_BLOCKS_POS); + + // SecID of the first sector of the directory stream + $this->rootStartBlock = self::getInt4d($this->data, self::ROOT_START_BLOCK_POS); + + // SecID of the first sector of the SSAT (or -2 if not extant) + $this->sbdStartBlock = self::getInt4d($this->data, self::SMALL_BLOCK_DEPOT_BLOCK_POS); + + // SecID of the first sector of the MSAT (or -2 if no additional sectors are used) + $this->extensionBlock = self::getInt4d($this->data, self::EXTENSION_BLOCK_POS); + + // Total number of sectors used by MSAT + $this->numExtensionBlocks = self::getInt4d($this->data, self::NUM_EXTENSION_BLOCK_POS); + + $bigBlockDepotBlocks = []; + $pos = self::BIG_BLOCK_DEPOT_BLOCKS_POS; + + $bbdBlocks = $this->numBigBlockDepotBlocks; + + if ($this->numExtensionBlocks != 0) { + $bbdBlocks = (self::BIG_BLOCK_SIZE - self::BIG_BLOCK_DEPOT_BLOCKS_POS) / 4; + } + + for ($i = 0; $i < $bbdBlocks; ++$i) { + $bigBlockDepotBlocks[$i] = self::getInt4d($this->data, $pos); + $pos += 4; + } + + for ($j = 0; $j < $this->numExtensionBlocks; ++$j) { + $pos = ($this->extensionBlock + 1) * self::BIG_BLOCK_SIZE; + $blocksToRead = min($this->numBigBlockDepotBlocks - $bbdBlocks, self::BIG_BLOCK_SIZE / 4 - 1); + + for ($i = $bbdBlocks; $i < $bbdBlocks + $blocksToRead; ++$i) { + $bigBlockDepotBlocks[$i] = self::getInt4d($this->data, $pos); + $pos += 4; + } + + $bbdBlocks += $blocksToRead; + if ($bbdBlocks < $this->numBigBlockDepotBlocks) { + $this->extensionBlock = self::getInt4d($this->data, $pos); + } + } + + $pos = 0; + $this->bigBlockChain = ''; + $bbs = self::BIG_BLOCK_SIZE / 4; + for ($i = 0; $i < $this->numBigBlockDepotBlocks; ++$i) { + $pos = ($bigBlockDepotBlocks[$i] + 1) * self::BIG_BLOCK_SIZE; + + $this->bigBlockChain .= substr($this->data, $pos, 4 * $bbs); + $pos += 4 * $bbs; + } + + $pos = 0; + $sbdBlock = $this->sbdStartBlock; + $this->smallBlockChain = ''; + while ($sbdBlock != -2) { + $pos = ($sbdBlock + 1) * self::BIG_BLOCK_SIZE; + + $this->smallBlockChain .= substr($this->data, $pos, 4 * $bbs); + $pos += 4 * $bbs; + + $sbdBlock = self::getInt4d($this->bigBlockChain, $sbdBlock * 4); + } + + // read the directory stream + $block = $this->rootStartBlock; + $this->entry = $this->readData($block); + + $this->readPropertySets(); + } + + /** + * Extract binary stream data. + * + * @param int $stream + * + * @return null|string + */ + public function getStream($stream) + { + if ($stream === null) { + return null; + } + + $streamData = ''; + + if ($this->props[$stream]['size'] < self::SMALL_BLOCK_THRESHOLD) { + $rootdata = $this->readData($this->props[$this->rootentry]['startBlock']); + + $block = $this->props[$stream]['startBlock']; + + while ($block != -2) { + $pos = $block * self::SMALL_BLOCK_SIZE; + $streamData .= substr($rootdata, $pos, self::SMALL_BLOCK_SIZE); + + $block = self::getInt4d($this->smallBlockChain, $block * 4); + } + + return $streamData; + } + $numBlocks = $this->props[$stream]['size'] / self::BIG_BLOCK_SIZE; + if ($this->props[$stream]['size'] % self::BIG_BLOCK_SIZE != 0) { + ++$numBlocks; + } + + if ($numBlocks == 0) { + return ''; + } + + $block = $this->props[$stream]['startBlock']; + + while ($block != -2) { + $pos = ($block + 1) * self::BIG_BLOCK_SIZE; + $streamData .= substr($this->data, $pos, self::BIG_BLOCK_SIZE); + $block = self::getInt4d($this->bigBlockChain, $block * 4); + } + + return $streamData; + } + + /** + * Read a standard stream (by joining sectors using information from SAT). + * + * @param int $block Sector ID where the stream starts + * + * @return string Data for standard stream + */ + private function readData($block) + { + $data = ''; + + while ($block != -2) { + $pos = ($block + 1) * self::BIG_BLOCK_SIZE; + $data .= substr($this->data, $pos, self::BIG_BLOCK_SIZE); + $block = self::getInt4d($this->bigBlockChain, $block * 4); + } + + return $data; + } + + /** + * Read entries in the directory stream. + */ + private function readPropertySets(): void + { + $offset = 0; + + // loop through entires, each entry is 128 bytes + $entryLen = strlen($this->entry); + while ($offset < $entryLen) { + // entry data (128 bytes) + $d = substr($this->entry, $offset, self::PROPERTY_STORAGE_BLOCK_SIZE); + + // size in bytes of name + $nameSize = ord($d[self::SIZE_OF_NAME_POS]) | (ord($d[self::SIZE_OF_NAME_POS + 1]) << 8); + + // type of entry + $type = ord($d[self::TYPE_POS]); + + // sectorID of first sector or short sector, if this entry refers to a stream (the case with workbook) + // sectorID of first sector of the short-stream container stream, if this entry is root entry + $startBlock = self::getInt4d($d, self::START_BLOCK_POS); + + $size = self::getInt4d($d, self::SIZE_POS); + + $name = str_replace("\x00", '', substr($d, 0, $nameSize)); + + $this->props[] = [ + 'name' => $name, + 'type' => $type, + 'startBlock' => $startBlock, + 'size' => $size, + ]; + + // tmp helper to simplify checks + $upName = strtoupper($name); + + // Workbook directory entry (BIFF5 uses Book, BIFF8 uses Workbook) + if (($upName === 'WORKBOOK') || ($upName === 'BOOK')) { + $this->wrkbook = count($this->props) - 1; + } elseif ($upName === 'ROOT ENTRY' || $upName === 'R') { + // Root entry + $this->rootentry = count($this->props) - 1; + } + + // Summary information + if ($name == chr(5) . 'SummaryInformation') { + $this->summaryInformation = count($this->props) - 1; + } + + // Additional Document Summary information + if ($name == chr(5) . 'DocumentSummaryInformation') { + $this->documentSummaryInformation = count($this->props) - 1; + } + + $offset += self::PROPERTY_STORAGE_BLOCK_SIZE; + } + } + + /** + * Read 4 bytes of data at specified position. + * + * @param string $data + * @param int $pos + * + * @return int + */ + private static function getInt4d($data, $pos) + { + if ($pos < 0) { + // Invalid position + throw new ReaderException('Parameter pos=' . $pos . ' is invalid.'); + } + + $len = strlen($data); + if ($len < $pos + 4) { + $data .= str_repeat("\0", $pos + 4 - $len); + } + + // FIX: represent numbers correctly on 64-bit system + // http://sourceforge.net/tracker/index.php?func=detail&aid=1487372&group_id=99160&atid=623334 + // Changed by Andreas Rehm 2006 to ensure correct result of the <<24 block on 32 and 64bit systems + $_or_24 = ord($data[$pos + 3]); + if ($_or_24 >= 128) { + // negative number + $_ord_24 = -abs((256 - $_or_24) << 24); + } else { + $_ord_24 = ($_or_24 & 127) << 24; + } + + return ord($data[$pos]) | (ord($data[$pos + 1]) << 8) | (ord($data[$pos + 2]) << 16) | $_ord_24; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/PasswordHasher.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/PasswordHasher.php new file mode 100644 index 0000000..0d58a86 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/PasswordHasher.php @@ -0,0 +1,109 @@ + 'md2', + Protection::ALGORITHM_MD4 => 'md4', + Protection::ALGORITHM_MD5 => 'md5', + Protection::ALGORITHM_SHA_1 => 'sha1', + Protection::ALGORITHM_SHA_256 => 'sha256', + Protection::ALGORITHM_SHA_384 => 'sha384', + Protection::ALGORITHM_SHA_512 => 'sha512', + Protection::ALGORITHM_RIPEMD_128 => 'ripemd128', + Protection::ALGORITHM_RIPEMD_160 => 'ripemd160', + Protection::ALGORITHM_WHIRLPOOL => 'whirlpool', + ]; + + if (array_key_exists($algorithmName, $mapping)) { + return $mapping[$algorithmName]; + } + + throw new SpException('Unsupported password algorithm: ' . $algorithmName); + } + + /** + * Create a password hash from a given string. + * + * This method is based on the spec at: + * https://interoperability.blob.core.windows.net/files/MS-OFFCRYPTO/[MS-OFFCRYPTO].pdf + * 2.3.7.1 Binary Document Password Verifier Derivation Method 1 + * + * It replaces a method based on the algorithm provided by + * Daniel Rentz of OpenOffice and the PEAR package + * Spreadsheet_Excel_Writer by Xavier Noguer . + * + * Scrutinizer will squawk at the use of bitwise operations here, + * but it should ultimately pass. + * + * @param string $password Password to hash + */ + private static function defaultHashPassword(string $password): string + { + $verifier = 0; + $pwlen = strlen($password); + $passwordArray = pack('c', $pwlen) . $password; + for ($i = $pwlen; $i >= 0; --$i) { + $intermediate1 = (($verifier & 0x4000) === 0) ? 0 : 1; + $intermediate2 = 2 * $verifier; + $intermediate2 = $intermediate2 & 0x7fff; + $intermediate3 = $intermediate1 | $intermediate2; + $verifier = $intermediate3 ^ ord($passwordArray[$i]); + } + $verifier ^= 0xCE4B; + + return strtoupper(dechex($verifier)); + } + + /** + * Create a password hash from a given string by a specific algorithm. + * + * 2.4.2.4 ISO Write Protection Method + * + * @see https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-offcrypto/1357ea58-646e-4483-92ef-95d718079d6f + * + * @param string $password Password to hash + * @param string $algorithm Hash algorithm used to compute the password hash value + * @param string $salt Pseudorandom string + * @param int $spinCount Number of times to iterate on a hash of a password + * + * @return string Hashed password + */ + public static function hashPassword(string $password, string $algorithm = '', string $salt = '', int $spinCount = 10000): string + { + if (strlen($password) > self::MAX_PASSWORD_LENGTH) { + throw new SpException('Password exceeds ' . self::MAX_PASSWORD_LENGTH . ' characters'); + } + $phpAlgorithm = self::getAlgorithm($algorithm); + if (!$phpAlgorithm) { + return self::defaultHashPassword($password); + } + + $saltValue = base64_decode($salt); + $encodedPassword = mb_convert_encoding($password, 'UCS-2LE', 'UTF-8'); + + $hashValue = hash($phpAlgorithm, $saltValue . $encodedPassword, true); + for ($i = 0; $i < $spinCount; ++$i) { + $hashValue = hash($phpAlgorithm, $hashValue . pack('L', $i), true); + } + + return base64_encode($hashValue); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/StringHelper.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/StringHelper.php new file mode 100644 index 0000000..435d0e4 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/StringHelper.php @@ -0,0 +1,722 @@ + chr(0), + "\x1B 1" => chr(1), + "\x1B 2" => chr(2), + "\x1B 3" => chr(3), + "\x1B 4" => chr(4), + "\x1B 5" => chr(5), + "\x1B 6" => chr(6), + "\x1B 7" => chr(7), + "\x1B 8" => chr(8), + "\x1B 9" => chr(9), + "\x1B :" => chr(10), + "\x1B ;" => chr(11), + "\x1B <" => chr(12), + "\x1B =" => chr(13), + "\x1B >" => chr(14), + "\x1B ?" => chr(15), + "\x1B!0" => chr(16), + "\x1B!1" => chr(17), + "\x1B!2" => chr(18), + "\x1B!3" => chr(19), + "\x1B!4" => chr(20), + "\x1B!5" => chr(21), + "\x1B!6" => chr(22), + "\x1B!7" => chr(23), + "\x1B!8" => chr(24), + "\x1B!9" => chr(25), + "\x1B!:" => chr(26), + "\x1B!;" => chr(27), + "\x1B!<" => chr(28), + "\x1B!=" => chr(29), + "\x1B!>" => chr(30), + "\x1B!?" => chr(31), + "\x1B'?" => chr(127), + "\x1B(0" => '€', // 128 in CP1252 + "\x1B(2" => '‚', // 130 in CP1252 + "\x1B(3" => 'ƒ', // 131 in CP1252 + "\x1B(4" => '„', // 132 in CP1252 + "\x1B(5" => '…', // 133 in CP1252 + "\x1B(6" => '†', // 134 in CP1252 + "\x1B(7" => '‡', // 135 in CP1252 + "\x1B(8" => 'ˆ', // 136 in CP1252 + "\x1B(9" => '‰', // 137 in CP1252 + "\x1B(:" => 'Š', // 138 in CP1252 + "\x1B(;" => '‹', // 139 in CP1252 + "\x1BNj" => 'Œ', // 140 in CP1252 + "\x1B(>" => 'Ž', // 142 in CP1252 + "\x1B)1" => '‘', // 145 in CP1252 + "\x1B)2" => '’', // 146 in CP1252 + "\x1B)3" => '“', // 147 in CP1252 + "\x1B)4" => '”', // 148 in CP1252 + "\x1B)5" => '•', // 149 in CP1252 + "\x1B)6" => '–', // 150 in CP1252 + "\x1B)7" => '—', // 151 in CP1252 + "\x1B)8" => '˜', // 152 in CP1252 + "\x1B)9" => '™', // 153 in CP1252 + "\x1B):" => 'š', // 154 in CP1252 + "\x1B);" => '›', // 155 in CP1252 + "\x1BNz" => 'œ', // 156 in CP1252 + "\x1B)>" => 'ž', // 158 in CP1252 + "\x1B)?" => 'Ÿ', // 159 in CP1252 + "\x1B*0" => ' ', // 160 in CP1252 + "\x1BN!" => '¡', // 161 in CP1252 + "\x1BN\"" => '¢', // 162 in CP1252 + "\x1BN#" => '£', // 163 in CP1252 + "\x1BN(" => '¤', // 164 in CP1252 + "\x1BN%" => '¥', // 165 in CP1252 + "\x1B*6" => '¦', // 166 in CP1252 + "\x1BN'" => '§', // 167 in CP1252 + "\x1BNH " => '¨', // 168 in CP1252 + "\x1BNS" => '©', // 169 in CP1252 + "\x1BNc" => 'ª', // 170 in CP1252 + "\x1BN+" => '«', // 171 in CP1252 + "\x1B*<" => '¬', // 172 in CP1252 + "\x1B*=" => '­', // 173 in CP1252 + "\x1BNR" => '®', // 174 in CP1252 + "\x1B*?" => '¯', // 175 in CP1252 + "\x1BN0" => '°', // 176 in CP1252 + "\x1BN1" => '±', // 177 in CP1252 + "\x1BN2" => '²', // 178 in CP1252 + "\x1BN3" => '³', // 179 in CP1252 + "\x1BNB " => '´', // 180 in CP1252 + "\x1BN5" => 'µ', // 181 in CP1252 + "\x1BN6" => '¶', // 182 in CP1252 + "\x1BN7" => '·', // 183 in CP1252 + "\x1B+8" => '¸', // 184 in CP1252 + "\x1BNQ" => '¹', // 185 in CP1252 + "\x1BNk" => 'º', // 186 in CP1252 + "\x1BN;" => '»', // 187 in CP1252 + "\x1BN<" => '¼', // 188 in CP1252 + "\x1BN=" => '½', // 189 in CP1252 + "\x1BN>" => '¾', // 190 in CP1252 + "\x1BN?" => '¿', // 191 in CP1252 + "\x1BNAA" => 'À', // 192 in CP1252 + "\x1BNBA" => 'Á', // 193 in CP1252 + "\x1BNCA" => 'Â', // 194 in CP1252 + "\x1BNDA" => 'Ã', // 195 in CP1252 + "\x1BNHA" => 'Ä', // 196 in CP1252 + "\x1BNJA" => 'Å', // 197 in CP1252 + "\x1BNa" => 'Æ', // 198 in CP1252 + "\x1BNKC" => 'Ç', // 199 in CP1252 + "\x1BNAE" => 'È', // 200 in CP1252 + "\x1BNBE" => 'É', // 201 in CP1252 + "\x1BNCE" => 'Ê', // 202 in CP1252 + "\x1BNHE" => 'Ë', // 203 in CP1252 + "\x1BNAI" => 'Ì', // 204 in CP1252 + "\x1BNBI" => 'Í', // 205 in CP1252 + "\x1BNCI" => 'Î', // 206 in CP1252 + "\x1BNHI" => 'Ï', // 207 in CP1252 + "\x1BNb" => 'Ð', // 208 in CP1252 + "\x1BNDN" => 'Ñ', // 209 in CP1252 + "\x1BNAO" => 'Ò', // 210 in CP1252 + "\x1BNBO" => 'Ó', // 211 in CP1252 + "\x1BNCO" => 'Ô', // 212 in CP1252 + "\x1BNDO" => 'Õ', // 213 in CP1252 + "\x1BNHO" => 'Ö', // 214 in CP1252 + "\x1B-7" => '×', // 215 in CP1252 + "\x1BNi" => 'Ø', // 216 in CP1252 + "\x1BNAU" => 'Ù', // 217 in CP1252 + "\x1BNBU" => 'Ú', // 218 in CP1252 + "\x1BNCU" => 'Û', // 219 in CP1252 + "\x1BNHU" => 'Ü', // 220 in CP1252 + "\x1B-=" => 'Ý', // 221 in CP1252 + "\x1BNl" => 'Þ', // 222 in CP1252 + "\x1BN{" => 'ß', // 223 in CP1252 + "\x1BNAa" => 'à', // 224 in CP1252 + "\x1BNBa" => 'á', // 225 in CP1252 + "\x1BNCa" => 'â', // 226 in CP1252 + "\x1BNDa" => 'ã', // 227 in CP1252 + "\x1BNHa" => 'ä', // 228 in CP1252 + "\x1BNJa" => 'å', // 229 in CP1252 + "\x1BNq" => 'æ', // 230 in CP1252 + "\x1BNKc" => 'ç', // 231 in CP1252 + "\x1BNAe" => 'è', // 232 in CP1252 + "\x1BNBe" => 'é', // 233 in CP1252 + "\x1BNCe" => 'ê', // 234 in CP1252 + "\x1BNHe" => 'ë', // 235 in CP1252 + "\x1BNAi" => 'ì', // 236 in CP1252 + "\x1BNBi" => 'í', // 237 in CP1252 + "\x1BNCi" => 'î', // 238 in CP1252 + "\x1BNHi" => 'ï', // 239 in CP1252 + "\x1BNs" => 'ð', // 240 in CP1252 + "\x1BNDn" => 'ñ', // 241 in CP1252 + "\x1BNAo" => 'ò', // 242 in CP1252 + "\x1BNBo" => 'ó', // 243 in CP1252 + "\x1BNCo" => 'ô', // 244 in CP1252 + "\x1BNDo" => 'õ', // 245 in CP1252 + "\x1BNHo" => 'ö', // 246 in CP1252 + "\x1B/7" => '÷', // 247 in CP1252 + "\x1BNy" => 'ø', // 248 in CP1252 + "\x1BNAu" => 'ù', // 249 in CP1252 + "\x1BNBu" => 'ú', // 250 in CP1252 + "\x1BNCu" => 'û', // 251 in CP1252 + "\x1BNHu" => 'ü', // 252 in CP1252 + "\x1B/=" => 'ý', // 253 in CP1252 + "\x1BN|" => 'þ', // 254 in CP1252 + "\x1BNHy" => 'ÿ', // 255 in CP1252 + ]; + } + + /** + * Get whether iconv extension is available. + * + * @return bool + */ + public static function getIsIconvEnabled() + { + if (isset(self::$isIconvEnabled)) { + return self::$isIconvEnabled; + } + + // Assume no problems with iconv + self::$isIconvEnabled = true; + + // Fail if iconv doesn't exist + if (!function_exists('iconv')) { + self::$isIconvEnabled = false; + } elseif (!@iconv('UTF-8', 'UTF-16LE', 'x')) { + // Sometimes iconv is not working, and e.g. iconv('UTF-8', 'UTF-16LE', 'x') just returns false, + self::$isIconvEnabled = false; + } elseif (defined('PHP_OS') && @stristr(PHP_OS, 'AIX') && defined('ICONV_IMPL') && (@strcasecmp(ICONV_IMPL, 'unknown') == 0) && defined('ICONV_VERSION') && (@strcasecmp(ICONV_VERSION, 'unknown') == 0)) { + // CUSTOM: IBM AIX iconv() does not work + self::$isIconvEnabled = false; + } + + // Deactivate iconv default options if they fail (as seen on IMB i) + if (self::$isIconvEnabled && !@iconv('UTF-8', 'UTF-16LE' . self::$iconvOptions, 'x')) { + self::$iconvOptions = ''; + } + + return self::$isIconvEnabled; + } + + private static function buildCharacterSets(): void + { + if (empty(self::$controlCharacters)) { + self::buildControlCharacters(); + } + + if (empty(self::$SYLKCharacters)) { + self::buildSYLKCharacters(); + } + } + + /** + * Convert from OpenXML escaped control character to PHP control character. + * + * Excel 2007 team: + * ---------------- + * That's correct, control characters are stored directly in the shared-strings table. + * We do encode characters that cannot be represented in XML using the following escape sequence: + * _xHHHH_ where H represents a hexadecimal character in the character's value... + * So you could end up with something like _x0008_ in a string (either in a cell value () + * element or in the shared string element. + * + * @param string $textValue Value to unescape + * + * @return string + */ + public static function controlCharacterOOXML2PHP($textValue) + { + self::buildCharacterSets(); + + return str_replace(array_keys(self::$controlCharacters), array_values(self::$controlCharacters), $textValue); + } + + /** + * Convert from PHP control character to OpenXML escaped control character. + * + * Excel 2007 team: + * ---------------- + * That's correct, control characters are stored directly in the shared-strings table. + * We do encode characters that cannot be represented in XML using the following escape sequence: + * _xHHHH_ where H represents a hexadecimal character in the character's value... + * So you could end up with something like _x0008_ in a string (either in a cell value () + * element or in the shared string element. + * + * @param string $textValue Value to escape + * + * @return string + */ + public static function controlCharacterPHP2OOXML($textValue) + { + self::buildCharacterSets(); + + return str_replace(array_values(self::$controlCharacters), array_keys(self::$controlCharacters), $textValue); + } + + /** + * Try to sanitize UTF8, stripping invalid byte sequences. Not perfect. Does not surrogate characters. + * + * @param string $textValue + * + * @return string + */ + public static function sanitizeUTF8($textValue) + { + if (self::getIsIconvEnabled()) { + $textValue = @iconv('UTF-8', 'UTF-8', $textValue); + + return $textValue; + } + + $textValue = mb_convert_encoding($textValue, 'UTF-8', 'UTF-8'); + + return $textValue; + } + + /** + * Check if a string contains UTF8 data. + * + * @param string $textValue + * + * @return bool + */ + public static function isUTF8($textValue) + { + return $textValue === '' || preg_match('/^./su', $textValue) === 1; + } + + /** + * Formats a numeric value as a string for output in various output writers forcing + * point as decimal separator in case locale is other than English. + * + * @param mixed $numericValue + * + * @return string + */ + public static function formatNumber($numericValue) + { + if (is_float($numericValue)) { + return str_replace(',', '.', $numericValue); + } + + return (string) $numericValue; + } + + /** + * Converts a UTF-8 string into BIFF8 Unicode string data (8-bit string length) + * Writes the string using uncompressed notation, no rich text, no Asian phonetics + * If mbstring extension is not available, ASCII is assumed, and compressed notation is used + * although this will give wrong results for non-ASCII strings + * see OpenOffice.org's Documentation of the Microsoft Excel File Format, sect. 2.5.3. + * + * @param string $textValue UTF-8 encoded string + * @param mixed[] $arrcRuns Details of rich text runs in $value + * + * @return string + */ + public static function UTF8toBIFF8UnicodeShort($textValue, $arrcRuns = []) + { + // character count + $ln = self::countCharacters($textValue, 'UTF-8'); + // option flags + if (empty($arrcRuns)) { + $data = pack('CC', $ln, 0x0001); + // characters + $data .= self::convertEncoding($textValue, 'UTF-16LE', 'UTF-8'); + } else { + $data = pack('vC', $ln, 0x09); + $data .= pack('v', count($arrcRuns)); + // characters + $data .= self::convertEncoding($textValue, 'UTF-16LE', 'UTF-8'); + foreach ($arrcRuns as $cRun) { + $data .= pack('v', $cRun['strlen']); + $data .= pack('v', $cRun['fontidx']); + } + } + + return $data; + } + + /** + * Converts a UTF-8 string into BIFF8 Unicode string data (16-bit string length) + * Writes the string using uncompressed notation, no rich text, no Asian phonetics + * If mbstring extension is not available, ASCII is assumed, and compressed notation is used + * although this will give wrong results for non-ASCII strings + * see OpenOffice.org's Documentation of the Microsoft Excel File Format, sect. 2.5.3. + * + * @param string $textValue UTF-8 encoded string + * + * @return string + */ + public static function UTF8toBIFF8UnicodeLong($textValue) + { + // character count + $ln = self::countCharacters($textValue, 'UTF-8'); + + // characters + $chars = self::convertEncoding($textValue, 'UTF-16LE', 'UTF-8'); + + return pack('vC', $ln, 0x0001) . $chars; + } + + /** + * Convert string from one encoding to another. + * + * @param string $textValue + * @param string $to Encoding to convert to, e.g. 'UTF-8' + * @param string $from Encoding to convert from, e.g. 'UTF-16LE' + * + * @return string + */ + public static function convertEncoding($textValue, $to, $from) + { + if (self::getIsIconvEnabled()) { + $result = iconv($from, $to . self::$iconvOptions, $textValue); + if (false !== $result) { + return $result; + } + } + + return mb_convert_encoding($textValue, $to, $from); + } + + /** + * Get character count. + * + * @param string $textValue + * @param string $encoding Encoding + * + * @return int Character count + */ + public static function countCharacters($textValue, $encoding = 'UTF-8') + { + return mb_strlen($textValue ?? '', $encoding); + } + + /** + * Get a substring of a UTF-8 encoded string. + * + * @param string $textValue UTF-8 encoded string + * @param int $offset Start offset + * @param int $length Maximum number of characters in substring + * + * @return string + */ + public static function substring($textValue, $offset, $length = 0) + { + return mb_substr($textValue, $offset, $length, 'UTF-8'); + } + + /** + * Convert a UTF-8 encoded string to upper case. + * + * @param string $textValue UTF-8 encoded string + * + * @return string + */ + public static function strToUpper($textValue) + { + return mb_convert_case($textValue ?? '', MB_CASE_UPPER, 'UTF-8'); + } + + /** + * Convert a UTF-8 encoded string to lower case. + * + * @param string $textValue UTF-8 encoded string + * + * @return string + */ + public static function strToLower($textValue) + { + return mb_convert_case($textValue ?? '', MB_CASE_LOWER, 'UTF-8'); + } + + /** + * Convert a UTF-8 encoded string to title/proper case + * (uppercase every first character in each word, lower case all other characters). + * + * @param string $textValue UTF-8 encoded string + * + * @return string + */ + public static function strToTitle($textValue) + { + return mb_convert_case($textValue, MB_CASE_TITLE, 'UTF-8'); + } + + public static function mbIsUpper($character) + { + return mb_strtolower($character, 'UTF-8') != $character; + } + + public static function mbStrSplit($string) + { + // Split at all position not after the start: ^ + // and not before the end: $ + return preg_split('/(?_calculateFormulaValue($fractionFormula); + + return true; + } + + return false; + } + + // function convertToNumberIfFraction() + + /** + * Get the decimal separator. If it has not yet been set explicitly, try to obtain number + * formatting information from locale. + * + * @return string + */ + public static function getDecimalSeparator() + { + if (!isset(self::$decimalSeparator)) { + $localeconv = localeconv(); + self::$decimalSeparator = ($localeconv['decimal_point'] != '') + ? $localeconv['decimal_point'] : $localeconv['mon_decimal_point']; + + if (self::$decimalSeparator == '') { + // Default to . + self::$decimalSeparator = '.'; + } + } + + return self::$decimalSeparator; + } + + /** + * Set the decimal separator. Only used by NumberFormat::toFormattedString() + * to format output by \PhpOffice\PhpSpreadsheet\Writer\Html and \PhpOffice\PhpSpreadsheet\Writer\Pdf. + * + * @param string $separator Character for decimal separator + */ + public static function setDecimalSeparator($separator): void + { + self::$decimalSeparator = $separator; + } + + /** + * Get the thousands separator. If it has not yet been set explicitly, try to obtain number + * formatting information from locale. + * + * @return string + */ + public static function getThousandsSeparator() + { + if (!isset(self::$thousandsSeparator)) { + $localeconv = localeconv(); + self::$thousandsSeparator = ($localeconv['thousands_sep'] != '') + ? $localeconv['thousands_sep'] : $localeconv['mon_thousands_sep']; + + if (self::$thousandsSeparator == '') { + // Default to . + self::$thousandsSeparator = ','; + } + } + + return self::$thousandsSeparator; + } + + /** + * Set the thousands separator. Only used by NumberFormat::toFormattedString() + * to format output by \PhpOffice\PhpSpreadsheet\Writer\Html and \PhpOffice\PhpSpreadsheet\Writer\Pdf. + * + * @param string $separator Character for thousands separator + */ + public static function setThousandsSeparator($separator): void + { + self::$thousandsSeparator = $separator; + } + + /** + * Get the currency code. If it has not yet been set explicitly, try to obtain the + * symbol information from locale. + * + * @return string + */ + public static function getCurrencyCode() + { + if (!empty(self::$currencyCode)) { + return self::$currencyCode; + } + self::$currencyCode = '$'; + $localeconv = localeconv(); + if (!empty($localeconv['currency_symbol'])) { + self::$currencyCode = $localeconv['currency_symbol']; + + return self::$currencyCode; + } + if (!empty($localeconv['int_curr_symbol'])) { + self::$currencyCode = $localeconv['int_curr_symbol']; + + return self::$currencyCode; + } + + return self::$currencyCode; + } + + /** + * Set the currency code. Only used by NumberFormat::toFormattedString() + * to format output by \PhpOffice\PhpSpreadsheet\Writer\Html and \PhpOffice\PhpSpreadsheet\Writer\Pdf. + * + * @param string $currencyCode Character for currency code + */ + public static function setCurrencyCode($currencyCode): void + { + self::$currencyCode = $currencyCode; + } + + /** + * Convert SYLK encoded string to UTF-8. + * + * @param string $textValue + * + * @return string UTF-8 encoded string + */ + public static function SYLKtoUTF8($textValue) + { + self::buildCharacterSets(); + + // If there is no escape character in the string there is nothing to do + if (strpos($textValue, '') === false) { + return $textValue; + } + + foreach (self::$SYLKCharacters as $k => $v) { + $textValue = str_replace($k, $v, $textValue); + } + + return $textValue; + } + + /** + * Retrieve any leading numeric part of a string, or return the full string if no leading numeric + * (handles basic integer or float, but not exponent or non decimal). + * + * @param string $textValue + * + * @return mixed string or only the leading numeric part of the string + */ + public static function testStringAsNumeric($textValue) + { + if (is_numeric($textValue)) { + return $textValue; + } + $v = (float) $textValue; + + return (is_numeric(substr($textValue, 0, strlen($v)))) ? $v : $textValue; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/TimeZone.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/TimeZone.php new file mode 100644 index 0000000..dabb88f --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/TimeZone.php @@ -0,0 +1,77 @@ +setTimeZone(new DateTimeZone($timezoneName)); + + return $dtobj->getOffset(); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/BestFit.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/BestFit.php new file mode 100644 index 0000000..7df4895 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/BestFit.php @@ -0,0 +1,461 @@ +error; + } + + public function getBestFitType() + { + return $this->bestFitType; + } + + /** + * Return the Y-Value for a specified value of X. + * + * @param float $xValue X-Value + * + * @return float Y-Value + */ + abstract public function getValueOfYForX($xValue); + + /** + * Return the X-Value for a specified value of Y. + * + * @param float $yValue Y-Value + * + * @return float X-Value + */ + abstract public function getValueOfXForY($yValue); + + /** + * Return the original set of X-Values. + * + * @return float[] X-Values + */ + public function getXValues() + { + return $this->xValues; + } + + /** + * Return the Equation of the best-fit line. + * + * @param int $dp Number of places of decimal precision to display + * + * @return string + */ + abstract public function getEquation($dp = 0); + + /** + * Return the Slope of the line. + * + * @param int $dp Number of places of decimal precision to display + * + * @return float + */ + public function getSlope($dp = 0) + { + if ($dp != 0) { + return round($this->slope, $dp); + } + + return $this->slope; + } + + /** + * Return the standard error of the Slope. + * + * @param int $dp Number of places of decimal precision to display + * + * @return float + */ + public function getSlopeSE($dp = 0) + { + if ($dp != 0) { + return round($this->slopeSE, $dp); + } + + return $this->slopeSE; + } + + /** + * Return the Value of X where it intersects Y = 0. + * + * @param int $dp Number of places of decimal precision to display + * + * @return float + */ + public function getIntersect($dp = 0) + { + if ($dp != 0) { + return round($this->intersect, $dp); + } + + return $this->intersect; + } + + /** + * Return the standard error of the Intersect. + * + * @param int $dp Number of places of decimal precision to display + * + * @return float + */ + public function getIntersectSE($dp = 0) + { + if ($dp != 0) { + return round($this->intersectSE, $dp); + } + + return $this->intersectSE; + } + + /** + * Return the goodness of fit for this regression. + * + * @param int $dp Number of places of decimal precision to return + * + * @return float + */ + public function getGoodnessOfFit($dp = 0) + { + if ($dp != 0) { + return round($this->goodnessOfFit, $dp); + } + + return $this->goodnessOfFit; + } + + /** + * Return the goodness of fit for this regression. + * + * @param int $dp Number of places of decimal precision to return + * + * @return float + */ + public function getGoodnessOfFitPercent($dp = 0) + { + if ($dp != 0) { + return round($this->goodnessOfFit * 100, $dp); + } + + return $this->goodnessOfFit * 100; + } + + /** + * Return the standard deviation of the residuals for this regression. + * + * @param int $dp Number of places of decimal precision to return + * + * @return float + */ + public function getStdevOfResiduals($dp = 0) + { + if ($dp != 0) { + return round($this->stdevOfResiduals, $dp); + } + + return $this->stdevOfResiduals; + } + + /** + * @param int $dp Number of places of decimal precision to return + * + * @return float + */ + public function getSSRegression($dp = 0) + { + if ($dp != 0) { + return round($this->SSRegression, $dp); + } + + return $this->SSRegression; + } + + /** + * @param int $dp Number of places of decimal precision to return + * + * @return float + */ + public function getSSResiduals($dp = 0) + { + if ($dp != 0) { + return round($this->SSResiduals, $dp); + } + + return $this->SSResiduals; + } + + /** + * @param int $dp Number of places of decimal precision to return + * + * @return float + */ + public function getDFResiduals($dp = 0) + { + if ($dp != 0) { + return round($this->DFResiduals, $dp); + } + + return $this->DFResiduals; + } + + /** + * @param int $dp Number of places of decimal precision to return + * + * @return float + */ + public function getF($dp = 0) + { + if ($dp != 0) { + return round($this->f, $dp); + } + + return $this->f; + } + + /** + * @param int $dp Number of places of decimal precision to return + * + * @return float + */ + public function getCovariance($dp = 0) + { + if ($dp != 0) { + return round($this->covariance, $dp); + } + + return $this->covariance; + } + + /** + * @param int $dp Number of places of decimal precision to return + * + * @return float + */ + public function getCorrelation($dp = 0) + { + if ($dp != 0) { + return round($this->correlation, $dp); + } + + return $this->correlation; + } + + /** + * @return float[] + */ + public function getYBestFitValues() + { + return $this->yBestFitValues; + } + + protected function calculateGoodnessOfFit($sumX, $sumY, $sumX2, $sumY2, $sumXY, $meanX, $meanY, $const): void + { + $SSres = $SScov = $SScor = $SStot = $SSsex = 0.0; + foreach ($this->xValues as $xKey => $xValue) { + $bestFitY = $this->yBestFitValues[$xKey] = $this->getValueOfYForX($xValue); + + $SSres += ($this->yValues[$xKey] - $bestFitY) * ($this->yValues[$xKey] - $bestFitY); + if ($const === true) { + $SStot += ($this->yValues[$xKey] - $meanY) * ($this->yValues[$xKey] - $meanY); + } else { + $SStot += $this->yValues[$xKey] * $this->yValues[$xKey]; + } + $SScov += ($this->xValues[$xKey] - $meanX) * ($this->yValues[$xKey] - $meanY); + if ($const === true) { + $SSsex += ($this->xValues[$xKey] - $meanX) * ($this->xValues[$xKey] - $meanX); + } else { + $SSsex += $this->xValues[$xKey] * $this->xValues[$xKey]; + } + } + + $this->SSResiduals = $SSres; + $this->DFResiduals = $this->valueCount - 1 - ($const === true ? 1 : 0); + + if ($this->DFResiduals == 0.0) { + $this->stdevOfResiduals = 0.0; + } else { + $this->stdevOfResiduals = sqrt($SSres / $this->DFResiduals); + } + if (($SStot == 0.0) || ($SSres == $SStot)) { + $this->goodnessOfFit = 1; + } else { + $this->goodnessOfFit = 1 - ($SSres / $SStot); + } + + $this->SSRegression = $this->goodnessOfFit * $SStot; + $this->covariance = $SScov / $this->valueCount; + $this->correlation = ($this->valueCount * $sumXY - $sumX * $sumY) / sqrt(($this->valueCount * $sumX2 - $sumX ** 2) * ($this->valueCount * $sumY2 - $sumY ** 2)); + $this->slopeSE = $this->stdevOfResiduals / sqrt($SSsex); + $this->intersectSE = $this->stdevOfResiduals * sqrt(1 / ($this->valueCount - ($sumX * $sumX) / $sumX2)); + if ($this->SSResiduals != 0.0) { + if ($this->DFResiduals == 0.0) { + $this->f = 0.0; + } else { + $this->f = $this->SSRegression / ($this->SSResiduals / $this->DFResiduals); + } + } else { + if ($this->DFResiduals == 0.0) { + $this->f = 0.0; + } else { + $this->f = $this->SSRegression / $this->DFResiduals; + } + } + } + + private function sumSquares(array $values) + { + return array_sum( + array_map( + function ($value) { + return $value ** 2; + }, + $values + ) + ); + } + + /** + * @param float[] $yValues + * @param float[] $xValues + */ + protected function leastSquareFit(array $yValues, array $xValues, bool $const): void + { + // calculate sums + $sumValuesX = array_sum($xValues); + $sumValuesY = array_sum($yValues); + $meanValueX = $sumValuesX / $this->valueCount; + $meanValueY = $sumValuesY / $this->valueCount; + $sumSquaresX = $this->sumSquares($xValues); + $sumSquaresY = $this->sumSquares($yValues); + $mBase = $mDivisor = 0.0; + $xy_sum = 0.0; + for ($i = 0; $i < $this->valueCount; ++$i) { + $xy_sum += $xValues[$i] * $yValues[$i]; + + if ($const === true) { + $mBase += ($xValues[$i] - $meanValueX) * ($yValues[$i] - $meanValueY); + $mDivisor += ($xValues[$i] - $meanValueX) * ($xValues[$i] - $meanValueX); + } else { + $mBase += $xValues[$i] * $yValues[$i]; + $mDivisor += $xValues[$i] * $xValues[$i]; + } + } + + // calculate slope + $this->slope = $mBase / $mDivisor; + + // calculate intersect + $this->intersect = ($const === true) ? $meanValueY - ($this->slope * $meanValueX) : 0.0; + + $this->calculateGoodnessOfFit($sumValuesX, $sumValuesY, $sumSquaresX, $sumSquaresY, $xy_sum, $meanValueX, $meanValueY, $const); + } + + /** + * Define the regression. + * + * @param float[] $yValues The set of Y-values for this regression + * @param float[] $xValues The set of X-values for this regression + */ + public function __construct($yValues, $xValues = []) + { + // Calculate number of points + $yValueCount = count($yValues); + $xValueCount = count($xValues); + + // Define X Values if necessary + if ($xValueCount === 0) { + $xValues = range(1, $yValueCount); + } elseif ($yValueCount !== $xValueCount) { + // Ensure both arrays of points are the same size + $this->error = true; + } + + $this->valueCount = $yValueCount; + $this->xValues = $xValues; + $this->yValues = $yValues; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/ExponentialBestFit.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/ExponentialBestFit.php new file mode 100644 index 0000000..eb8cd74 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/ExponentialBestFit.php @@ -0,0 +1,119 @@ +getIntersect() * $this->getSlope() ** ($xValue - $this->xOffset); + } + + /** + * Return the X-Value for a specified value of Y. + * + * @param float $yValue Y-Value + * + * @return float X-Value + */ + public function getValueOfXForY($yValue) + { + return log(($yValue + $this->yOffset) / $this->getIntersect()) / log($this->getSlope()); + } + + /** + * Return the Equation of the best-fit line. + * + * @param int $dp Number of places of decimal precision to display + * + * @return string + */ + public function getEquation($dp = 0) + { + $slope = $this->getSlope($dp); + $intersect = $this->getIntersect($dp); + + return 'Y = ' . $intersect . ' * ' . $slope . '^X'; + } + + /** + * Return the Slope of the line. + * + * @param int $dp Number of places of decimal precision to display + * + * @return float + */ + public function getSlope($dp = 0) + { + if ($dp != 0) { + return round(exp($this->slope), $dp); + } + + return exp($this->slope); + } + + /** + * Return the Value of X where it intersects Y = 0. + * + * @param int $dp Number of places of decimal precision to display + * + * @return float + */ + public function getIntersect($dp = 0) + { + if ($dp != 0) { + return round(exp($this->intersect), $dp); + } + + return exp($this->intersect); + } + + /** + * Execute the regression and calculate the goodness of fit for a set of X and Y data values. + * + * @param float[] $yValues The set of Y-values for this regression + * @param float[] $xValues The set of X-values for this regression + */ + private function exponentialRegression(array $yValues, array $xValues, bool $const): void + { + $adjustedYValues = array_map( + function ($value) { + return ($value < 0.0) ? 0 - log(abs($value)) : log($value); + }, + $yValues + ); + + $this->leastSquareFit($adjustedYValues, $xValues, $const); + } + + /** + * Define the regression and calculate the goodness of fit for a set of X and Y data values. + * + * @param float[] $yValues The set of Y-values for this regression + * @param float[] $xValues The set of X-values for this regression + * @param bool $const + */ + public function __construct($yValues, $xValues = [], $const = true) + { + parent::__construct($yValues, $xValues); + + if (!$this->error) { + $this->exponentialRegression($yValues, $xValues, (bool) $const); + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/LinearBestFit.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/LinearBestFit.php new file mode 100644 index 0000000..65d6b4f --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/LinearBestFit.php @@ -0,0 +1,80 @@ +getIntersect() + $this->getSlope() * $xValue; + } + + /** + * Return the X-Value for a specified value of Y. + * + * @param float $yValue Y-Value + * + * @return float X-Value + */ + public function getValueOfXForY($yValue) + { + return ($yValue - $this->getIntersect()) / $this->getSlope(); + } + + /** + * Return the Equation of the best-fit line. + * + * @param int $dp Number of places of decimal precision to display + * + * @return string + */ + public function getEquation($dp = 0) + { + $slope = $this->getSlope($dp); + $intersect = $this->getIntersect($dp); + + return 'Y = ' . $intersect . ' + ' . $slope . ' * X'; + } + + /** + * Execute the regression and calculate the goodness of fit for a set of X and Y data values. + * + * @param float[] $yValues The set of Y-values for this regression + * @param float[] $xValues The set of X-values for this regression + */ + private function linearRegression(array $yValues, array $xValues, bool $const): void + { + $this->leastSquareFit($yValues, $xValues, $const); + } + + /** + * Define the regression and calculate the goodness of fit for a set of X and Y data values. + * + * @param float[] $yValues The set of Y-values for this regression + * @param float[] $xValues The set of X-values for this regression + * @param bool $const + */ + public function __construct($yValues, $xValues = [], $const = true) + { + parent::__construct($yValues, $xValues); + + if (!$this->error) { + $this->linearRegression($yValues, $xValues, (bool) $const); + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/LogarithmicBestFit.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/LogarithmicBestFit.php new file mode 100644 index 0000000..2366dc6 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/LogarithmicBestFit.php @@ -0,0 +1,87 @@ +getIntersect() + $this->getSlope() * log($xValue - $this->xOffset); + } + + /** + * Return the X-Value for a specified value of Y. + * + * @param float $yValue Y-Value + * + * @return float X-Value + */ + public function getValueOfXForY($yValue) + { + return exp(($yValue - $this->getIntersect()) / $this->getSlope()); + } + + /** + * Return the Equation of the best-fit line. + * + * @param int $dp Number of places of decimal precision to display + * + * @return string + */ + public function getEquation($dp = 0) + { + $slope = $this->getSlope($dp); + $intersect = $this->getIntersect($dp); + + return 'Y = ' . $slope . ' * log(' . $intersect . ' * X)'; + } + + /** + * Execute the regression and calculate the goodness of fit for a set of X and Y data values. + * + * @param float[] $yValues The set of Y-values for this regression + * @param float[] $xValues The set of X-values for this regression + */ + private function logarithmicRegression(array $yValues, array $xValues, bool $const): void + { + $adjustedYValues = array_map( + function ($value) { + return ($value < 0.0) ? 0 - log(abs($value)) : log($value); + }, + $yValues + ); + + $this->leastSquareFit($adjustedYValues, $xValues, $const); + } + + /** + * Define the regression and calculate the goodness of fit for a set of X and Y data values. + * + * @param float[] $yValues The set of Y-values for this regression + * @param float[] $xValues The set of X-values for this regression + * @param bool $const + */ + public function __construct($yValues, $xValues = [], $const = true) + { + parent::__construct($yValues, $xValues); + + if (!$this->error) { + $this->logarithmicRegression($yValues, $xValues, (bool) $const); + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php new file mode 100644 index 0000000..2c8eea5 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php @@ -0,0 +1,202 @@ +order; + } + + /** + * Return the Y-Value for a specified value of X. + * + * @param float $xValue X-Value + * + * @return float Y-Value + */ + public function getValueOfYForX($xValue) + { + $retVal = $this->getIntersect(); + $slope = $this->getSlope(); + // @phpstan-ignore-next-line + foreach ($slope as $key => $value) { + if ($value != 0.0) { + $retVal += $value * $xValue ** ($key + 1); + } + } + + return $retVal; + } + + /** + * Return the X-Value for a specified value of Y. + * + * @param float $yValue Y-Value + * + * @return float X-Value + */ + public function getValueOfXForY($yValue) + { + return ($yValue - $this->getIntersect()) / $this->getSlope(); + } + + /** + * Return the Equation of the best-fit line. + * + * @param int $dp Number of places of decimal precision to display + * + * @return string + */ + public function getEquation($dp = 0) + { + $slope = $this->getSlope($dp); + $intersect = $this->getIntersect($dp); + + $equation = 'Y = ' . $intersect; + // @phpstan-ignore-next-line + foreach ($slope as $key => $value) { + if ($value != 0.0) { + $equation .= ' + ' . $value . ' * X'; + if ($key > 0) { + $equation .= '^' . ($key + 1); + } + } + } + + return $equation; + } + + /** + * Return the Slope of the line. + * + * @param int $dp Number of places of decimal precision to display + * + * @return float + */ + public function getSlope($dp = 0) + { + if ($dp != 0) { + $coefficients = []; + foreach ($this->slope as $coefficient) { + $coefficients[] = round($coefficient, $dp); + } + + // @phpstan-ignore-next-line + return $coefficients; + } + + return $this->slope; + } + + public function getCoefficients($dp = 0) + { + return array_merge([$this->getIntersect($dp)], $this->getSlope($dp)); + } + + /** + * Execute the regression and calculate the goodness of fit for a set of X and Y data values. + * + * @param int $order Order of Polynomial for this regression + * @param float[] $yValues The set of Y-values for this regression + * @param float[] $xValues The set of X-values for this regression + */ + private function polynomialRegression($order, $yValues, $xValues): void + { + // calculate sums + $x_sum = array_sum($xValues); + $y_sum = array_sum($yValues); + $xx_sum = $xy_sum = $yy_sum = 0; + for ($i = 0; $i < $this->valueCount; ++$i) { + $xy_sum += $xValues[$i] * $yValues[$i]; + $xx_sum += $xValues[$i] * $xValues[$i]; + $yy_sum += $yValues[$i] * $yValues[$i]; + } + /* + * This routine uses logic from the PHP port of polyfit version 0.1 + * written by Michael Bommarito and Paul Meagher + * + * The function fits a polynomial function of order $order through + * a series of x-y data points using least squares. + * + */ + $A = []; + $B = []; + for ($i = 0; $i < $this->valueCount; ++$i) { + for ($j = 0; $j <= $order; ++$j) { + $A[$i][$j] = $xValues[$i] ** $j; + } + } + for ($i = 0; $i < $this->valueCount; ++$i) { + $B[$i] = [$yValues[$i]]; + } + $matrixA = new Matrix($A); + $matrixB = new Matrix($B); + $C = $matrixA->solve($matrixB); + + $coefficients = []; + for ($i = 0; $i < $C->getRowDimension(); ++$i) { + $r = $C->get($i, 0); + if (abs($r) <= 10 ** (-9)) { + $r = 0; + } + $coefficients[] = $r; + } + + $this->intersect = array_shift($coefficients); + $this->slope = $coefficients; + + $this->calculateGoodnessOfFit($x_sum, $y_sum, $xx_sum, $yy_sum, $xy_sum, 0, 0, 0); + foreach ($this->xValues as $xKey => $xValue) { + $this->yBestFitValues[$xKey] = $this->getValueOfYForX($xValue); + } + } + + /** + * Define the regression and calculate the goodness of fit for a set of X and Y data values. + * + * @param int $order Order of Polynomial for this regression + * @param float[] $yValues The set of Y-values for this regression + * @param float[] $xValues The set of X-values for this regression + */ + public function __construct($order, $yValues, $xValues = []) + { + parent::__construct($yValues, $xValues); + + if (!$this->error) { + if ($order < $this->valueCount) { + $this->bestFitType .= '_' . $order; + $this->order = $order; + $this->polynomialRegression($order, $yValues, $xValues); + if (($this->getGoodnessOfFit() < 0.0) || ($this->getGoodnessOfFit() > 1.0)) { + $this->error = true; + } + } else { + $this->error = true; + } + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/PowerBestFit.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/PowerBestFit.php new file mode 100644 index 0000000..cafd011 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/PowerBestFit.php @@ -0,0 +1,109 @@ +getIntersect() * ($xValue - $this->xOffset) ** $this->getSlope(); + } + + /** + * Return the X-Value for a specified value of Y. + * + * @param float $yValue Y-Value + * + * @return float X-Value + */ + public function getValueOfXForY($yValue) + { + return (($yValue + $this->yOffset) / $this->getIntersect()) ** (1 / $this->getSlope()); + } + + /** + * Return the Equation of the best-fit line. + * + * @param int $dp Number of places of decimal precision to display + * + * @return string + */ + public function getEquation($dp = 0) + { + $slope = $this->getSlope($dp); + $intersect = $this->getIntersect($dp); + + return 'Y = ' . $intersect . ' * X^' . $slope; + } + + /** + * Return the Value of X where it intersects Y = 0. + * + * @param int $dp Number of places of decimal precision to display + * + * @return float + */ + public function getIntersect($dp = 0) + { + if ($dp != 0) { + return round(exp($this->intersect), $dp); + } + + return exp($this->intersect); + } + + /** + * Execute the regression and calculate the goodness of fit for a set of X and Y data values. + * + * @param float[] $yValues The set of Y-values for this regression + * @param float[] $xValues The set of X-values for this regression + */ + private function powerRegression(array $yValues, array $xValues, bool $const): void + { + $adjustedYValues = array_map( + function ($value) { + return ($value < 0.0) ? 0 - log(abs($value)) : log($value); + }, + $yValues + ); + $adjustedXValues = array_map( + function ($value) { + return ($value < 0.0) ? 0 - log(abs($value)) : log($value); + }, + $xValues + ); + + $this->leastSquareFit($adjustedYValues, $adjustedXValues, $const); + } + + /** + * Define the regression and calculate the goodness of fit for a set of X and Y data values. + * + * @param float[] $yValues The set of Y-values for this regression + * @param float[] $xValues The set of X-values for this regression + * @param bool $const + */ + public function __construct($yValues, $xValues = [], $const = true) + { + parent::__construct($yValues, $xValues); + + if (!$this->error) { + $this->powerRegression($yValues, $xValues, (bool) $const); + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/Trend.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/Trend.php new file mode 100644 index 0000000..61d1183 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/Trend.php @@ -0,0 +1,121 @@ +getGoodnessOfFit(); + } + if ($trendType != self::TREND_BEST_FIT_NO_POLY) { + foreach (self::$trendTypePolynomialOrders as $trendMethod) { + $order = substr($trendMethod, -1); + $bestFit[$trendMethod] = new PolynomialBestFit($order, $yValues, $xValues); + if ($bestFit[$trendMethod]->getError()) { + unset($bestFit[$trendMethod]); + } else { + $bestFitValue[$trendMethod] = $bestFit[$trendMethod]->getGoodnessOfFit(); + } + } + } + // Determine which of our Trend lines is the best fit, and then we return the instance of that Trend class + arsort($bestFitValue); + $bestFitType = key($bestFitValue); + + return $bestFit[$bestFitType]; + default: + return false; + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/XMLWriter.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/XMLWriter.php new file mode 100644 index 0000000..84ad8a8 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/XMLWriter.php @@ -0,0 +1,92 @@ +openMemory(); + } else { + // Create temporary filename + if ($temporaryStorageFolder === null) { + $temporaryStorageFolder = File::sysGetTempDir(); + } + $this->tempFileName = @tempnam($temporaryStorageFolder, 'xml'); + + // Open storage + if ($this->openUri($this->tempFileName) === false) { + // Fallback to memory... + $this->openMemory(); + } + } + + // Set default values + if (self::$debugEnabled) { + $this->setIndent(true); + } + } + + /** + * Destructor. + */ + public function __destruct() + { + // Unlink temporary files + if ($this->tempFileName != '') { + @unlink($this->tempFileName); + } + } + + /** + * Get written data. + * + * @return string + */ + public function getData() + { + if ($this->tempFileName == '') { + return $this->outputMemory(true); + } + $this->flush(); + + return file_get_contents($this->tempFileName); + } + + /** + * Wrapper method for writeRaw. + * + * @param null|string|string[] $rawTextData + * + * @return bool + */ + public function writeRawData($rawTextData) + { + if (is_array($rawTextData)) { + $rawTextData = implode("\n", $rawTextData); + } + + return $this->writeRaw(htmlspecialchars($rawTextData ?? '')); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Xls.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Xls.php new file mode 100644 index 0000000..2c3198b --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Xls.php @@ -0,0 +1,277 @@ +getParent()->getDefaultStyle()->getFont(); + + $columnDimensions = $worksheet->getColumnDimensions(); + + // first find the true column width in pixels (uncollapsed and unhidden) + if (isset($columnDimensions[$col]) && $columnDimensions[$col]->getWidth() != -1) { + // then we have column dimension with explicit width + $columnDimension = $columnDimensions[$col]; + $width = $columnDimension->getWidth(); + $pixelWidth = Drawing::cellDimensionToPixels($width, $font); + } elseif ($worksheet->getDefaultColumnDimension()->getWidth() != -1) { + // then we have default column dimension with explicit width + $defaultColumnDimension = $worksheet->getDefaultColumnDimension(); + $width = $defaultColumnDimension->getWidth(); + $pixelWidth = Drawing::cellDimensionToPixels($width, $font); + } else { + // we don't even have any default column dimension. Width depends on default font + $pixelWidth = Font::getDefaultColumnWidthByFont($font, true); + } + + // now find the effective column width in pixels + if (isset($columnDimensions[$col]) && !$columnDimensions[$col]->getVisible()) { + $effectivePixelWidth = 0; + } else { + $effectivePixelWidth = $pixelWidth; + } + + return $effectivePixelWidth; + } + + /** + * Convert the height of a cell from user's units to pixels. By interpolation + * the relationship is: y = 4/3x. If the height hasn't been set by the user we + * use the default value. If the row is hidden we use a value of zero. + * + * @param Worksheet $worksheet The sheet + * @param int $row The row index (1-based) + * + * @return int The width in pixels + */ + public static function sizeRow(Worksheet $worksheet, $row = 1) + { + // default font of the workbook + $font = $worksheet->getParent()->getDefaultStyle()->getFont(); + + $rowDimensions = $worksheet->getRowDimensions(); + + // first find the true row height in pixels (uncollapsed and unhidden) + if (isset($rowDimensions[$row]) && $rowDimensions[$row]->getRowHeight() != -1) { + // then we have a row dimension + $rowDimension = $rowDimensions[$row]; + $rowHeight = $rowDimension->getRowHeight(); + $pixelRowHeight = (int) ceil(4 * $rowHeight / 3); // here we assume Arial 10 + } elseif ($worksheet->getDefaultRowDimension()->getRowHeight() != -1) { + // then we have a default row dimension with explicit height + $defaultRowDimension = $worksheet->getDefaultRowDimension(); + $pixelRowHeight = $defaultRowDimension->getRowHeight(Dimension::UOM_PIXELS); + } else { + // we don't even have any default row dimension. Height depends on default font + $pointRowHeight = Font::getDefaultRowHeightByFont($font); + $pixelRowHeight = Font::fontSizeToPixels((int) $pointRowHeight); + } + + // now find the effective row height in pixels + if (isset($rowDimensions[$row]) && !$rowDimensions[$row]->getVisible()) { + $effectivePixelRowHeight = 0; + } else { + $effectivePixelRowHeight = $pixelRowHeight; + } + + return (int) $effectivePixelRowHeight; + } + + /** + * Get the horizontal distance in pixels between two anchors + * The distanceX is found as sum of all the spanning columns widths minus correction for the two offsets. + * + * @param string $startColumn + * @param int $startOffsetX Offset within start cell measured in 1/1024 of the cell width + * @param string $endColumn + * @param int $endOffsetX Offset within end cell measured in 1/1024 of the cell width + * + * @return int Horizontal measured in pixels + */ + public static function getDistanceX(Worksheet $worksheet, $startColumn = 'A', $startOffsetX = 0, $endColumn = 'A', $endOffsetX = 0) + { + $distanceX = 0; + + // add the widths of the spanning columns + $startColumnIndex = Coordinate::columnIndexFromString($startColumn); + $endColumnIndex = Coordinate::columnIndexFromString($endColumn); + for ($i = $startColumnIndex; $i <= $endColumnIndex; ++$i) { + $distanceX += self::sizeCol($worksheet, Coordinate::stringFromColumnIndex($i)); + } + + // correct for offsetX in startcell + $distanceX -= (int) floor(self::sizeCol($worksheet, $startColumn) * $startOffsetX / 1024); + + // correct for offsetX in endcell + $distanceX -= (int) floor(self::sizeCol($worksheet, $endColumn) * (1 - $endOffsetX / 1024)); + + return $distanceX; + } + + /** + * Get the vertical distance in pixels between two anchors + * The distanceY is found as sum of all the spanning rows minus two offsets. + * + * @param int $startRow (1-based) + * @param int $startOffsetY Offset within start cell measured in 1/256 of the cell height + * @param int $endRow (1-based) + * @param int $endOffsetY Offset within end cell measured in 1/256 of the cell height + * + * @return int Vertical distance measured in pixels + */ + public static function getDistanceY(Worksheet $worksheet, $startRow = 1, $startOffsetY = 0, $endRow = 1, $endOffsetY = 0) + { + $distanceY = 0; + + // add the widths of the spanning rows + for ($row = $startRow; $row <= $endRow; ++$row) { + $distanceY += self::sizeRow($worksheet, $row); + } + + // correct for offsetX in startcell + $distanceY -= (int) floor(self::sizeRow($worksheet, $startRow) * $startOffsetY / 256); + + // correct for offsetX in endcell + $distanceY -= (int) floor(self::sizeRow($worksheet, $endRow) * (1 - $endOffsetY / 256)); + + return $distanceY; + } + + /** + * Convert 1-cell anchor coordinates to 2-cell anchor coordinates + * This function is ported from PEAR Spreadsheet_Writer_Excel with small modifications. + * + * Calculate the vertices that define the position of the image as required by + * the OBJ record. + * + * +------------+------------+ + * | A | B | + * +-----+------------+------------+ + * | |(x1,y1) | | + * | 1 |(A1)._______|______ | + * | | | | | + * | | | | | + * +-----+----| BITMAP |-----+ + * | | | | | + * | 2 | |______________. | + * | | | (B2)| + * | | | (x2,y2)| + * +---- +------------+------------+ + * + * Example of a bitmap that covers some of the area from cell A1 to cell B2. + * + * Based on the width and height of the bitmap we need to calculate 8 vars: + * $col_start, $row_start, $col_end, $row_end, $x1, $y1, $x2, $y2. + * The width and height of the cells are also variable and have to be taken into + * account. + * The values of $col_start and $row_start are passed in from the calling + * function. The values of $col_end and $row_end are calculated by subtracting + * the width and height of the bitmap from the width and height of the + * underlying cells. + * The vertices are expressed as a percentage of the underlying cell width as + * follows (rhs values are in pixels): + * + * x1 = X / W *1024 + * y1 = Y / H *256 + * x2 = (X-1) / W *1024 + * y2 = (Y-1) / H *256 + * + * Where: X is distance from the left side of the underlying cell + * Y is distance from the top of the underlying cell + * W is the width of the cell + * H is the height of the cell + * + * @param string $coordinates E.g. 'A1' + * @param int $offsetX Horizontal offset in pixels + * @param int $offsetY Vertical offset in pixels + * @param int $width Width in pixels + * @param int $height Height in pixels + * + * @return null|array + */ + public static function oneAnchor2twoAnchor(Worksheet $worksheet, $coordinates, $offsetX, $offsetY, $width, $height) + { + [$col_start, $row] = Coordinate::indexesFromString($coordinates); + $row_start = $row - 1; + + $x1 = $offsetX; + $y1 = $offsetY; + + // Initialise end cell to the same as the start cell + $col_end = $col_start; // Col containing lower right corner of object + $row_end = $row_start; // Row containing bottom right corner of object + + // Zero the specified offset if greater than the cell dimensions + if ($x1 >= self::sizeCol($worksheet, Coordinate::stringFromColumnIndex($col_start))) { + $x1 = 0; + } + if ($y1 >= self::sizeRow($worksheet, $row_start + 1)) { + $y1 = 0; + } + + $width = $width + $x1 - 1; + $height = $height + $y1 - 1; + + // Subtract the underlying cell widths to find the end cell of the image + while ($width >= self::sizeCol($worksheet, Coordinate::stringFromColumnIndex($col_end))) { + $width -= self::sizeCol($worksheet, Coordinate::stringFromColumnIndex($col_end)); + ++$col_end; + } + + // Subtract the underlying cell heights to find the end cell of the image + while ($height >= self::sizeRow($worksheet, $row_end + 1)) { + $height -= self::sizeRow($worksheet, $row_end + 1); + ++$row_end; + } + + // Bitmap isn't allowed to start or finish in a hidden cell, i.e. a cell + // with zero height or width. + if (self::sizeCol($worksheet, Coordinate::stringFromColumnIndex($col_start)) == 0) { + return null; + } + if (self::sizeCol($worksheet, Coordinate::stringFromColumnIndex($col_end)) == 0) { + return null; + } + if (self::sizeRow($worksheet, $row_start + 1) == 0) { + return null; + } + if (self::sizeRow($worksheet, $row_end + 1) == 0) { + return null; + } + + // Convert the pixel values to the percentage value expected by Excel + $x1 = $x1 / self::sizeCol($worksheet, Coordinate::stringFromColumnIndex($col_start)) * 1024; + $y1 = $y1 / self::sizeRow($worksheet, $row_start + 1) * 256; + $x2 = ($width + 1) / self::sizeCol($worksheet, Coordinate::stringFromColumnIndex($col_end)) * 1024; // Distance to right side of object + $y2 = ($height + 1) / self::sizeRow($worksheet, $row_end + 1) * 256; // Distance to bottom of object + + $startCoordinates = Coordinate::stringFromColumnIndex($col_start) . ($row_start + 1); + $endCoordinates = Coordinate::stringFromColumnIndex($col_end) . ($row_end + 1); + + return [ + 'startCoordinates' => $startCoordinates, + 'startOffsetX' => $x1, + 'startOffsetY' => $y1, + 'endCoordinates' => $endCoordinates, + 'endOffsetX' => $x2, + 'endOffsetY' => $y2, + ]; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Spreadsheet.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Spreadsheet.php new file mode 100644 index 0000000..9990a68 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Spreadsheet.php @@ -0,0 +1,1592 @@ +hasMacros; + } + + /** + * Define if a workbook has macros. + * + * @param bool $hasMacros true|false + */ + public function setHasMacros($hasMacros): void + { + $this->hasMacros = (bool) $hasMacros; + } + + /** + * Set the macros code. + * + * @param string $macroCode string|null + */ + public function setMacrosCode($macroCode): void + { + $this->macrosCode = $macroCode; + $this->setHasMacros($macroCode !== null); + } + + /** + * Return the macros code. + * + * @return null|string + */ + public function getMacrosCode() + { + return $this->macrosCode; + } + + /** + * Set the macros certificate. + * + * @param null|string $certificate + */ + public function setMacrosCertificate($certificate): void + { + $this->macrosCertificate = $certificate; + } + + /** + * Is the project signed ? + * + * @return bool true|false + */ + public function hasMacrosCertificate() + { + return $this->macrosCertificate !== null; + } + + /** + * Return the macros certificate. + * + * @return null|string + */ + public function getMacrosCertificate() + { + return $this->macrosCertificate; + } + + /** + * Remove all macros, certificate from spreadsheet. + */ + public function discardMacros(): void + { + $this->hasMacros = false; + $this->macrosCode = null; + $this->macrosCertificate = null; + } + + /** + * set ribbon XML data. + * + * @param null|mixed $target + * @param null|mixed $xmlData + */ + public function setRibbonXMLData($target, $xmlData): void + { + if ($target !== null && $xmlData !== null) { + $this->ribbonXMLData = ['target' => $target, 'data' => $xmlData]; + } else { + $this->ribbonXMLData = null; + } + } + + /** + * retrieve ribbon XML Data. + * + * @param string $what + * + * @return null|array|string + */ + public function getRibbonXMLData($what = 'all') //we need some constants here... + { + $returnData = null; + $what = strtolower($what); + switch ($what) { + case 'all': + $returnData = $this->ribbonXMLData; + + break; + case 'target': + case 'data': + if (is_array($this->ribbonXMLData) && isset($this->ribbonXMLData[$what])) { + $returnData = $this->ribbonXMLData[$what]; + } + + break; + } + + return $returnData; + } + + /** + * store binaries ribbon objects (pictures). + * + * @param null|mixed $BinObjectsNames + * @param null|mixed $BinObjectsData + */ + public function setRibbonBinObjects($BinObjectsNames, $BinObjectsData): void + { + if ($BinObjectsNames !== null && $BinObjectsData !== null) { + $this->ribbonBinObjects = ['names' => $BinObjectsNames, 'data' => $BinObjectsData]; + } else { + $this->ribbonBinObjects = null; + } + } + + /** + * List of unparsed loaded data for export to same format with better compatibility. + * It has to be minimized when the library start to support currently unparsed data. + * + * @internal + * + * @return array + */ + public function getUnparsedLoadedData() + { + return $this->unparsedLoadedData; + } + + /** + * List of unparsed loaded data for export to same format with better compatibility. + * It has to be minimized when the library start to support currently unparsed data. + * + * @internal + */ + public function setUnparsedLoadedData(array $unparsedLoadedData): void + { + $this->unparsedLoadedData = $unparsedLoadedData; + } + + /** + * return the extension of a filename. Internal use for a array_map callback (php<5.3 don't like lambda function). + * + * @param mixed $path + * + * @return string + */ + private function getExtensionOnly($path) + { + $extension = pathinfo($path, PATHINFO_EXTENSION); + + return is_array($extension) ? '' : $extension; + } + + /** + * retrieve Binaries Ribbon Objects. + * + * @param string $what + * + * @return null|array + */ + public function getRibbonBinObjects($what = 'all') + { + $ReturnData = null; + $what = strtolower($what); + switch ($what) { + case 'all': + return $this->ribbonBinObjects; + + break; + case 'names': + case 'data': + if (is_array($this->ribbonBinObjects) && isset($this->ribbonBinObjects[$what])) { + $ReturnData = $this->ribbonBinObjects[$what]; + } + + break; + case 'types': + if ( + is_array($this->ribbonBinObjects) && + isset($this->ribbonBinObjects['data']) && is_array($this->ribbonBinObjects['data']) + ) { + $tmpTypes = array_keys($this->ribbonBinObjects['data']); + $ReturnData = array_unique(array_map([$this, 'getExtensionOnly'], $tmpTypes)); + } else { + $ReturnData = []; // the caller want an array... not null if empty + } + + break; + } + + return $ReturnData; + } + + /** + * This workbook have a custom UI ? + * + * @return bool + */ + public function hasRibbon() + { + return $this->ribbonXMLData !== null; + } + + /** + * This workbook have additionnal object for the ribbon ? + * + * @return bool + */ + public function hasRibbonBinObjects() + { + return $this->ribbonBinObjects !== null; + } + + /** + * Check if a sheet with a specified code name already exists. + * + * @param string $codeName Name of the worksheet to check + * + * @return bool + */ + public function sheetCodeNameExists($codeName) + { + return $this->getSheetByCodeName($codeName) !== null; + } + + /** + * Get sheet by code name. Warning : sheet don't have always a code name ! + * + * @param string $codeName Sheet name + * + * @return null|Worksheet + */ + public function getSheetByCodeName($codeName) + { + $worksheetCount = count($this->workSheetCollection); + for ($i = 0; $i < $worksheetCount; ++$i) { + if ($this->workSheetCollection[$i]->getCodeName() == $codeName) { + return $this->workSheetCollection[$i]; + } + } + + return null; + } + + /** + * Create a new PhpSpreadsheet with one Worksheet. + */ + public function __construct() + { + $this->uniqueID = uniqid('', true); + $this->calculationEngine = new Calculation($this); + + // Initialise worksheet collection and add one worksheet + $this->workSheetCollection = []; + $this->workSheetCollection[] = new Worksheet($this); + $this->activeSheetIndex = 0; + + // Create document properties + $this->properties = new Document\Properties(); + + // Create document security + $this->security = new Document\Security(); + + // Set defined names + $this->definedNames = []; + + // Create the cellXf supervisor + $this->cellXfSupervisor = new Style(true); + $this->cellXfSupervisor->bindParent($this); + + // Create the default style + $this->addCellXf(new Style()); + $this->addCellStyleXf(new Style()); + } + + /** + * Code to execute when this worksheet is unset(). + */ + public function __destruct() + { + $this->disconnectWorksheets(); + $this->calculationEngine = null; + $this->cellXfCollection = []; + $this->cellStyleXfCollection = []; + } + + /** + * Disconnect all worksheets from this PhpSpreadsheet workbook object, + * typically so that the PhpSpreadsheet object can be unset. + */ + public function disconnectWorksheets(): void + { + foreach ($this->workSheetCollection as $worksheet) { + $worksheet->disconnectCells(); + unset($worksheet); + } + $this->workSheetCollection = []; + } + + /** + * Return the calculation engine for this worksheet. + * + * @return null|Calculation + */ + public function getCalculationEngine() + { + return $this->calculationEngine; + } + + /** + * Get properties. + * + * @return Document\Properties + */ + public function getProperties() + { + return $this->properties; + } + + /** + * Set properties. + */ + public function setProperties(Document\Properties $documentProperties): void + { + $this->properties = $documentProperties; + } + + /** + * Get security. + * + * @return Document\Security + */ + public function getSecurity() + { + return $this->security; + } + + /** + * Set security. + */ + public function setSecurity(Document\Security $documentSecurity): void + { + $this->security = $documentSecurity; + } + + /** + * Get active sheet. + * + * @return Worksheet + */ + public function getActiveSheet() + { + return $this->getSheet($this->activeSheetIndex); + } + + /** + * Create sheet and add it to this workbook. + * + * @param null|int $sheetIndex Index where sheet should go (0,1,..., or null for last) + * + * @return Worksheet + */ + public function createSheet($sheetIndex = null) + { + $newSheet = new Worksheet($this); + $this->addSheet($newSheet, $sheetIndex); + + return $newSheet; + } + + /** + * Check if a sheet with a specified name already exists. + * + * @param string $worksheetName Name of the worksheet to check + * + * @return bool + */ + public function sheetNameExists($worksheetName) + { + return $this->getSheetByName($worksheetName) !== null; + } + + /** + * Add sheet. + * + * @param Worksheet $worksheet The worskeet to add + * @param null|int $sheetIndex Index where sheet should go (0,1,..., or null for last) + * + * @return Worksheet + */ + public function addSheet(Worksheet $worksheet, $sheetIndex = null) + { + if ($this->sheetNameExists($worksheet->getTitle())) { + throw new Exception( + "Workbook already contains a worksheet named '{$worksheet->getTitle()}'. Rename this worksheet first." + ); + } + + if ($sheetIndex === null) { + if ($this->activeSheetIndex < 0) { + $this->activeSheetIndex = 0; + } + $this->workSheetCollection[] = $worksheet; + } else { + // Insert the sheet at the requested index + array_splice( + $this->workSheetCollection, + $sheetIndex, + 0, + [$worksheet] + ); + + // Adjust active sheet index if necessary + if ($this->activeSheetIndex >= $sheetIndex) { + ++$this->activeSheetIndex; + } + } + + if ($worksheet->getParent() === null) { + $worksheet->rebindParent($this); + } + + return $worksheet; + } + + /** + * Remove sheet by index. + * + * @param int $sheetIndex Index position of the worksheet to remove + */ + public function removeSheetByIndex($sheetIndex): void + { + $numSheets = count($this->workSheetCollection); + if ($sheetIndex > $numSheets - 1) { + throw new Exception( + "You tried to remove a sheet by the out of bounds index: {$sheetIndex}. The actual number of sheets is {$numSheets}." + ); + } + array_splice($this->workSheetCollection, $sheetIndex, 1); + + // Adjust active sheet index if necessary + if ( + ($this->activeSheetIndex >= $sheetIndex) && + ($this->activeSheetIndex > 0 || $numSheets <= 1) + ) { + --$this->activeSheetIndex; + } + } + + /** + * Get sheet by index. + * + * @param int $sheetIndex Sheet index + * + * @return Worksheet + */ + public function getSheet($sheetIndex) + { + if (!isset($this->workSheetCollection[$sheetIndex])) { + $numSheets = $this->getSheetCount(); + + throw new Exception( + "Your requested sheet index: {$sheetIndex} is out of bounds. The actual number of sheets is {$numSheets}." + ); + } + + return $this->workSheetCollection[$sheetIndex]; + } + + /** + * Get all sheets. + * + * @return Worksheet[] + */ + public function getAllSheets() + { + return $this->workSheetCollection; + } + + /** + * Get sheet by name. + * + * @param string $worksheetName Sheet name + * + * @return null|Worksheet + */ + public function getSheetByName($worksheetName) + { + $worksheetCount = count($this->workSheetCollection); + for ($i = 0; $i < $worksheetCount; ++$i) { + if ($this->workSheetCollection[$i]->getTitle() === trim($worksheetName, "'")) { + return $this->workSheetCollection[$i]; + } + } + + return null; + } + + /** + * Get index for sheet. + * + * @return int index + */ + public function getIndex(Worksheet $worksheet) + { + foreach ($this->workSheetCollection as $key => $value) { + if ($value->getHashCode() === $worksheet->getHashCode()) { + return $key; + } + } + + throw new Exception('Sheet does not exist.'); + } + + /** + * Set index for sheet by sheet name. + * + * @param string $worksheetName Sheet name to modify index for + * @param int $newIndexPosition New index for the sheet + * + * @return int New sheet index + */ + public function setIndexByName($worksheetName, $newIndexPosition) + { + $oldIndex = $this->getIndex($this->getSheetByName($worksheetName)); + $worksheet = array_splice( + $this->workSheetCollection, + $oldIndex, + 1 + ); + array_splice( + $this->workSheetCollection, + $newIndexPosition, + 0, + $worksheet + ); + + return $newIndexPosition; + } + + /** + * Get sheet count. + * + * @return int + */ + public function getSheetCount() + { + return count($this->workSheetCollection); + } + + /** + * Get active sheet index. + * + * @return int Active sheet index + */ + public function getActiveSheetIndex() + { + return $this->activeSheetIndex; + } + + /** + * Set active sheet index. + * + * @param int $worksheetIndex Active sheet index + * + * @return Worksheet + */ + public function setActiveSheetIndex($worksheetIndex) + { + $numSheets = count($this->workSheetCollection); + + if ($worksheetIndex > $numSheets - 1) { + throw new Exception( + "You tried to set a sheet active by the out of bounds index: {$worksheetIndex}. The actual number of sheets is {$numSheets}." + ); + } + $this->activeSheetIndex = $worksheetIndex; + + return $this->getActiveSheet(); + } + + /** + * Set active sheet index by name. + * + * @param string $worksheetName Sheet title + * + * @return Worksheet + */ + public function setActiveSheetIndexByName($worksheetName) + { + if (($worksheet = $this->getSheetByName($worksheetName)) instanceof Worksheet) { + $this->setActiveSheetIndex($this->getIndex($worksheet)); + + return $worksheet; + } + + throw new Exception('Workbook does not contain sheet:' . $worksheetName); + } + + /** + * Get sheet names. + * + * @return string[] + */ + public function getSheetNames() + { + $returnValue = []; + $worksheetCount = $this->getSheetCount(); + for ($i = 0; $i < $worksheetCount; ++$i) { + $returnValue[] = $this->getSheet($i)->getTitle(); + } + + return $returnValue; + } + + /** + * Add external sheet. + * + * @param Worksheet $worksheet External sheet to add + * @param null|int $sheetIndex Index where sheet should go (0,1,..., or null for last) + * + * @return Worksheet + */ + public function addExternalSheet(Worksheet $worksheet, $sheetIndex = null) + { + if ($this->sheetNameExists($worksheet->getTitle())) { + throw new Exception("Workbook already contains a worksheet named '{$worksheet->getTitle()}'. Rename the external sheet first."); + } + + // count how many cellXfs there are in this workbook currently, we will need this below + $countCellXfs = count($this->cellXfCollection); + + // copy all the shared cellXfs from the external workbook and append them to the current + foreach ($worksheet->getParent()->getCellXfCollection() as $cellXf) { + $this->addCellXf(clone $cellXf); + } + + // move sheet to this workbook + $worksheet->rebindParent($this); + + // update the cellXfs + foreach ($worksheet->getCoordinates(false) as $coordinate) { + $cell = $worksheet->getCell($coordinate); + $cell->setXfIndex($cell->getXfIndex() + $countCellXfs); + } + + return $this->addSheet($worksheet, $sheetIndex); + } + + /** + * Get an array of all Named Ranges. + * + * @return DefinedName[] + */ + public function getNamedRanges(): array + { + return array_filter( + $this->definedNames, + function (DefinedName $definedName) { + return $definedName->isFormula() === self::DEFINED_NAME_IS_RANGE; + } + ); + } + + /** + * Get an array of all Named Formulae. + * + * @return DefinedName[] + */ + public function getNamedFormulae(): array + { + return array_filter( + $this->definedNames, + function (DefinedName $definedName) { + return $definedName->isFormula() === self::DEFINED_NAME_IS_FORMULA; + } + ); + } + + /** + * Get an array of all Defined Names (both named ranges and named formulae). + * + * @return DefinedName[] + */ + public function getDefinedNames(): array + { + return $this->definedNames; + } + + /** + * Add a named range. + * If a named range with this name already exists, then this will replace the existing value. + */ + public function addNamedRange(NamedRange $namedRange): void + { + $this->addDefinedName($namedRange); + } + + /** + * Add a named formula. + * If a named formula with this name already exists, then this will replace the existing value. + */ + public function addNamedFormula(NamedFormula $namedFormula): void + { + $this->addDefinedName($namedFormula); + } + + /** + * Add a defined name (either a named range or a named formula). + * If a defined named with this name already exists, then this will replace the existing value. + */ + public function addDefinedName(DefinedName $definedName): void + { + $upperCaseName = StringHelper::strToUpper($definedName->getName()); + if ($definedName->getScope() == null) { + // global scope + $this->definedNames[$upperCaseName] = $definedName; + } else { + // local scope + $this->definedNames[$definedName->getScope()->getTitle() . '!' . $upperCaseName] = $definedName; + } + } + + /** + * Get named range. + * + * @param null|Worksheet $worksheet Scope. Use null for global scope + */ + public function getNamedRange(string $namedRange, ?Worksheet $worksheet = null): ?NamedRange + { + $returnValue = null; + + if ($namedRange !== '') { + $namedRange = StringHelper::strToUpper($namedRange); + // first look for global named range + $returnValue = $this->getGlobalDefinedNameByType($namedRange, self::DEFINED_NAME_IS_RANGE); + // then look for local named range (has priority over global named range if both names exist) + $returnValue = $this->getLocalDefinedNameByType($namedRange, self::DEFINED_NAME_IS_RANGE, $worksheet) ?: $returnValue; + } + + return $returnValue instanceof NamedRange ? $returnValue : null; + } + + /** + * Get named formula. + * + * @param null|Worksheet $worksheet Scope. Use null for global scope + */ + public function getNamedFormula(string $namedFormula, ?Worksheet $worksheet = null): ?NamedFormula + { + $returnValue = null; + + if ($namedFormula !== '') { + $namedFormula = StringHelper::strToUpper($namedFormula); + // first look for global named formula + $returnValue = $this->getGlobalDefinedNameByType($namedFormula, self::DEFINED_NAME_IS_FORMULA); + // then look for local named formula (has priority over global named formula if both names exist) + $returnValue = $this->getLocalDefinedNameByType($namedFormula, self::DEFINED_NAME_IS_FORMULA, $worksheet) ?: $returnValue; + } + + return $returnValue instanceof NamedFormula ? $returnValue : null; + } + + private function getGlobalDefinedNameByType(string $name, bool $type): ?DefinedName + { + if (isset($this->definedNames[$name]) && $this->definedNames[$name]->isFormula() === $type) { + return $this->definedNames[$name]; + } + + return null; + } + + private function getLocalDefinedNameByType(string $name, bool $type, ?Worksheet $worksheet = null): ?DefinedName + { + if ( + ($worksheet !== null) && isset($this->definedNames[$worksheet->getTitle() . '!' . $name]) + && $this->definedNames[$worksheet->getTitle() . '!' . $name]->isFormula() === $type + ) { + return $this->definedNames[$worksheet->getTitle() . '!' . $name]; + } + + return null; + } + + /** + * Get named range. + * + * @param null|Worksheet $worksheet Scope. Use null for global scope + */ + public function getDefinedName(string $definedName, ?Worksheet $worksheet = null): ?DefinedName + { + $returnValue = null; + + if ($definedName !== '') { + $definedName = StringHelper::strToUpper($definedName); + // first look for global defined name + if (isset($this->definedNames[$definedName])) { + $returnValue = $this->definedNames[$definedName]; + } + + // then look for local defined name (has priority over global defined name if both names exist) + if (($worksheet !== null) && isset($this->definedNames[$worksheet->getTitle() . '!' . $definedName])) { + $returnValue = $this->definedNames[$worksheet->getTitle() . '!' . $definedName]; + } + } + + return $returnValue; + } + + /** + * Remove named range. + * + * @param null|Worksheet $worksheet scope: use null for global scope + * + * @return $this + */ + public function removeNamedRange(string $namedRange, ?Worksheet $worksheet = null): self + { + if ($this->getNamedRange($namedRange, $worksheet) === null) { + return $this; + } + + return $this->removeDefinedName($namedRange, $worksheet); + } + + /** + * Remove named formula. + * + * @param null|Worksheet $worksheet scope: use null for global scope + * + * @return $this + */ + public function removeNamedFormula(string $namedFormula, ?Worksheet $worksheet = null): self + { + if ($this->getNamedFormula($namedFormula, $worksheet) === null) { + return $this; + } + + return $this->removeDefinedName($namedFormula, $worksheet); + } + + /** + * Remove defined name. + * + * @param null|Worksheet $worksheet scope: use null for global scope + * + * @return $this + */ + public function removeDefinedName(string $definedName, ?Worksheet $worksheet = null): self + { + $definedName = StringHelper::strToUpper($definedName); + + if ($worksheet === null) { + if (isset($this->definedNames[$definedName])) { + unset($this->definedNames[$definedName]); + } + } else { + if (isset($this->definedNames[$worksheet->getTitle() . '!' . $definedName])) { + unset($this->definedNames[$worksheet->getTitle() . '!' . $definedName]); + } elseif (isset($this->definedNames[$definedName])) { + unset($this->definedNames[$definedName]); + } + } + + return $this; + } + + /** + * Get worksheet iterator. + * + * @return Iterator + */ + public function getWorksheetIterator() + { + return new Iterator($this); + } + + /** + * Copy workbook (!= clone!). + * + * @return Spreadsheet + */ + public function copy() + { + $copied = clone $this; + + $worksheetCount = count($this->workSheetCollection); + for ($i = 0; $i < $worksheetCount; ++$i) { + $this->workSheetCollection[$i] = $this->workSheetCollection[$i]->copy(); + $this->workSheetCollection[$i]->rebindParent($this); + } + + return $copied; + } + + /** + * Implement PHP __clone to create a deep clone, not just a shallow copy. + */ + public function __clone() + { + // @phpstan-ignore-next-line + foreach ($this as $key => $val) { + if (is_object($val) || (is_array($val))) { + $this->{$key} = unserialize(serialize($val)); + } + } + } + + /** + * Get the workbook collection of cellXfs. + * + * @return Style[] + */ + public function getCellXfCollection() + { + return $this->cellXfCollection; + } + + /** + * Get cellXf by index. + * + * @param int $cellStyleIndex + * + * @return Style + */ + public function getCellXfByIndex($cellStyleIndex) + { + return $this->cellXfCollection[$cellStyleIndex]; + } + + /** + * Get cellXf by hash code. + * + * @param string $hashcode + * + * @return false|Style + */ + public function getCellXfByHashCode($hashcode) + { + foreach ($this->cellXfCollection as $cellXf) { + if ($cellXf->getHashCode() === $hashcode) { + return $cellXf; + } + } + + return false; + } + + /** + * Check if style exists in style collection. + * + * @return bool + */ + public function cellXfExists(Style $cellStyleIndex) + { + return in_array($cellStyleIndex, $this->cellXfCollection, true); + } + + /** + * Get default style. + * + * @return Style + */ + public function getDefaultStyle() + { + if (isset($this->cellXfCollection[0])) { + return $this->cellXfCollection[0]; + } + + throw new Exception('No default style found for this workbook'); + } + + /** + * Add a cellXf to the workbook. + */ + public function addCellXf(Style $style): void + { + $this->cellXfCollection[] = $style; + $style->setIndex(count($this->cellXfCollection) - 1); + } + + /** + * Remove cellXf by index. It is ensured that all cells get their xf index updated. + * + * @param int $cellStyleIndex Index to cellXf + */ + public function removeCellXfByIndex($cellStyleIndex): void + { + if ($cellStyleIndex > count($this->cellXfCollection) - 1) { + throw new Exception('CellXf index is out of bounds.'); + } + + // first remove the cellXf + array_splice($this->cellXfCollection, $cellStyleIndex, 1); + + // then update cellXf indexes for cells + foreach ($this->workSheetCollection as $worksheet) { + foreach ($worksheet->getCoordinates(false) as $coordinate) { + $cell = $worksheet->getCell($coordinate); + $xfIndex = $cell->getXfIndex(); + if ($xfIndex > $cellStyleIndex) { + // decrease xf index by 1 + $cell->setXfIndex($xfIndex - 1); + } elseif ($xfIndex == $cellStyleIndex) { + // set to default xf index 0 + $cell->setXfIndex(0); + } + } + } + } + + /** + * Get the cellXf supervisor. + * + * @return Style + */ + public function getCellXfSupervisor() + { + return $this->cellXfSupervisor; + } + + /** + * Get the workbook collection of cellStyleXfs. + * + * @return Style[] + */ + public function getCellStyleXfCollection() + { + return $this->cellStyleXfCollection; + } + + /** + * Get cellStyleXf by index. + * + * @param int $cellStyleIndex Index to cellXf + * + * @return Style + */ + public function getCellStyleXfByIndex($cellStyleIndex) + { + return $this->cellStyleXfCollection[$cellStyleIndex]; + } + + /** + * Get cellStyleXf by hash code. + * + * @param string $hashcode + * + * @return false|Style + */ + public function getCellStyleXfByHashCode($hashcode) + { + foreach ($this->cellStyleXfCollection as $cellStyleXf) { + if ($cellStyleXf->getHashCode() === $hashcode) { + return $cellStyleXf; + } + } + + return false; + } + + /** + * Add a cellStyleXf to the workbook. + */ + public function addCellStyleXf(Style $style): void + { + $this->cellStyleXfCollection[] = $style; + $style->setIndex(count($this->cellStyleXfCollection) - 1); + } + + /** + * Remove cellStyleXf by index. + * + * @param int $cellStyleIndex Index to cellXf + */ + public function removeCellStyleXfByIndex($cellStyleIndex): void + { + if ($cellStyleIndex > count($this->cellStyleXfCollection) - 1) { + throw new Exception('CellStyleXf index is out of bounds.'); + } + array_splice($this->cellStyleXfCollection, $cellStyleIndex, 1); + } + + /** + * Eliminate all unneeded cellXf and afterwards update the xfIndex for all cells + * and columns in the workbook. + */ + public function garbageCollect(): void + { + // how many references are there to each cellXf ? + $countReferencesCellXf = []; + foreach ($this->cellXfCollection as $index => $cellXf) { + $countReferencesCellXf[$index] = 0; + } + + foreach ($this->getWorksheetIterator() as $sheet) { + // from cells + foreach ($sheet->getCoordinates(false) as $coordinate) { + $cell = $sheet->getCell($coordinate); + ++$countReferencesCellXf[$cell->getXfIndex()]; + } + + // from row dimensions + foreach ($sheet->getRowDimensions() as $rowDimension) { + if ($rowDimension->getXfIndex() !== null) { + ++$countReferencesCellXf[$rowDimension->getXfIndex()]; + } + } + + // from column dimensions + foreach ($sheet->getColumnDimensions() as $columnDimension) { + ++$countReferencesCellXf[$columnDimension->getXfIndex()]; + } + } + + // remove cellXfs without references and create mapping so we can update xfIndex + // for all cells and columns + $countNeededCellXfs = 0; + $map = []; + foreach ($this->cellXfCollection as $index => $cellXf) { + if ($countReferencesCellXf[$index] > 0 || $index == 0) { // we must never remove the first cellXf + ++$countNeededCellXfs; + } else { + unset($this->cellXfCollection[$index]); + } + $map[$index] = $countNeededCellXfs - 1; + } + $this->cellXfCollection = array_values($this->cellXfCollection); + + // update the index for all cellXfs + foreach ($this->cellXfCollection as $i => $cellXf) { + $cellXf->setIndex($i); + } + + // make sure there is always at least one cellXf (there should be) + if (empty($this->cellXfCollection)) { + $this->cellXfCollection[] = new Style(); + } + + // update the xfIndex for all cells, row dimensions, column dimensions + foreach ($this->getWorksheetIterator() as $sheet) { + // for all cells + foreach ($sheet->getCoordinates(false) as $coordinate) { + $cell = $sheet->getCell($coordinate); + $cell->setXfIndex($map[$cell->getXfIndex()]); + } + + // for all row dimensions + foreach ($sheet->getRowDimensions() as $rowDimension) { + if ($rowDimension->getXfIndex() !== null) { + $rowDimension->setXfIndex($map[$rowDimension->getXfIndex()]); + } + } + + // for all column dimensions + foreach ($sheet->getColumnDimensions() as $columnDimension) { + $columnDimension->setXfIndex($map[$columnDimension->getXfIndex()]); + } + + // also do garbage collection for all the sheets + $sheet->garbageCollect(); + } + } + + /** + * Return the unique ID value assigned to this spreadsheet workbook. + * + * @return string + */ + public function getID() + { + return $this->uniqueID; + } + + /** + * Get the visibility of the horizonal scroll bar in the application. + * + * @return bool True if horizonal scroll bar is visible + */ + public function getShowHorizontalScroll() + { + return $this->showHorizontalScroll; + } + + /** + * Set the visibility of the horizonal scroll bar in the application. + * + * @param bool $showHorizontalScroll True if horizonal scroll bar is visible + */ + public function setShowHorizontalScroll($showHorizontalScroll): void + { + $this->showHorizontalScroll = (bool) $showHorizontalScroll; + } + + /** + * Get the visibility of the vertical scroll bar in the application. + * + * @return bool True if vertical scroll bar is visible + */ + public function getShowVerticalScroll() + { + return $this->showVerticalScroll; + } + + /** + * Set the visibility of the vertical scroll bar in the application. + * + * @param bool $showVerticalScroll True if vertical scroll bar is visible + */ + public function setShowVerticalScroll($showVerticalScroll): void + { + $this->showVerticalScroll = (bool) $showVerticalScroll; + } + + /** + * Get the visibility of the sheet tabs in the application. + * + * @return bool True if the sheet tabs are visible + */ + public function getShowSheetTabs() + { + return $this->showSheetTabs; + } + + /** + * Set the visibility of the sheet tabs in the application. + * + * @param bool $showSheetTabs True if sheet tabs are visible + */ + public function setShowSheetTabs($showSheetTabs): void + { + $this->showSheetTabs = (bool) $showSheetTabs; + } + + /** + * Return whether the workbook window is minimized. + * + * @return bool true if workbook window is minimized + */ + public function getMinimized() + { + return $this->minimized; + } + + /** + * Set whether the workbook window is minimized. + * + * @param bool $minimized true if workbook window is minimized + */ + public function setMinimized($minimized): void + { + $this->minimized = (bool) $minimized; + } + + /** + * Return whether to group dates when presenting the user with + * filtering optiomd in the user interface. + * + * @return bool true if workbook window is minimized + */ + public function getAutoFilterDateGrouping() + { + return $this->autoFilterDateGrouping; + } + + /** + * Set whether to group dates when presenting the user with + * filtering optiomd in the user interface. + * + * @param bool $autoFilterDateGrouping true if workbook window is minimized + */ + public function setAutoFilterDateGrouping($autoFilterDateGrouping): void + { + $this->autoFilterDateGrouping = (bool) $autoFilterDateGrouping; + } + + /** + * Return the first sheet in the book view. + * + * @return int First sheet in book view + */ + public function getFirstSheetIndex() + { + return $this->firstSheetIndex; + } + + /** + * Set the first sheet in the book view. + * + * @param int $firstSheetIndex First sheet in book view + */ + public function setFirstSheetIndex($firstSheetIndex): void + { + if ($firstSheetIndex >= 0) { + $this->firstSheetIndex = (int) $firstSheetIndex; + } else { + throw new Exception('First sheet index must be a positive integer.'); + } + } + + /** + * Return the visibility status of the workbook. + * + * This may be one of the following three values: + * - visibile + * + * @return string Visible status + */ + public function getVisibility() + { + return $this->visibility; + } + + /** + * Set the visibility status of the workbook. + * + * Valid values are: + * - 'visible' (self::VISIBILITY_VISIBLE): + * Workbook window is visible + * - 'hidden' (self::VISIBILITY_HIDDEN): + * Workbook window is hidden, but can be shown by the user + * via the user interface + * - 'veryHidden' (self::VISIBILITY_VERY_HIDDEN): + * Workbook window is hidden and cannot be shown in the + * user interface. + * + * @param string $visibility visibility status of the workbook + */ + public function setVisibility($visibility): void + { + if ($visibility === null) { + $visibility = self::VISIBILITY_VISIBLE; + } + + if (in_array($visibility, self::$workbookViewVisibilityValues)) { + $this->visibility = $visibility; + } else { + throw new Exception('Invalid visibility value.'); + } + } + + /** + * Get the ratio between the workbook tabs bar and the horizontal scroll bar. + * TabRatio is assumed to be out of 1000 of the horizontal window width. + * + * @return int Ratio between the workbook tabs bar and the horizontal scroll bar + */ + public function getTabRatio() + { + return $this->tabRatio; + } + + /** + * Set the ratio between the workbook tabs bar and the horizontal scroll bar + * TabRatio is assumed to be out of 1000 of the horizontal window width. + * + * @param int $tabRatio Ratio between the tabs bar and the horizontal scroll bar + */ + public function setTabRatio($tabRatio): void + { + if ($tabRatio >= 0 || $tabRatio <= 1000) { + $this->tabRatio = (int) $tabRatio; + } else { + throw new Exception('Tab ratio must be between 0 and 1000.'); + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Alignment.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Alignment.php new file mode 100644 index 0000000..1b3ed6a --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Alignment.php @@ -0,0 +1,483 @@ +horizontal = null; + $this->vertical = null; + $this->textRotation = null; + } + } + + /** + * Get the shared style component for the currently active cell in currently active sheet. + * Only used for style supervisor. + * + * @return Alignment + */ + public function getSharedComponent() + { + return $this->parent->getSharedComponent()->getAlignment(); + } + + /** + * Build style array from subcomponents. + * + * @param array $array + * + * @return array + */ + public function getStyleArray($array) + { + return ['alignment' => $array]; + } + + /** + * Apply styles from array. + * + * + * $spreadsheet->getActiveSheet()->getStyle('B2')->getAlignment()->applyFromArray( + * [ + * 'horizontal' => \PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER, + * 'vertical' => \PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_CENTER, + * 'textRotation' => 0, + * 'wrapText' => TRUE + * ] + * ); + * + * + * @param array $styleArray Array containing style information + * + * @return $this + */ + public function applyFromArray(array $styleArray) + { + if ($this->isSupervisor) { + $this->getActiveSheet()->getStyle($this->getSelectedCells()) + ->applyFromArray($this->getStyleArray($styleArray)); + } else { + if (isset($styleArray['horizontal'])) { + $this->setHorizontal($styleArray['horizontal']); + } + if (isset($styleArray['vertical'])) { + $this->setVertical($styleArray['vertical']); + } + if (isset($styleArray['textRotation'])) { + $this->setTextRotation($styleArray['textRotation']); + } + if (isset($styleArray['wrapText'])) { + $this->setWrapText($styleArray['wrapText']); + } + if (isset($styleArray['shrinkToFit'])) { + $this->setShrinkToFit($styleArray['shrinkToFit']); + } + if (isset($styleArray['indent'])) { + $this->setIndent($styleArray['indent']); + } + if (isset($styleArray['readOrder'])) { + $this->setReadOrder($styleArray['readOrder']); + } + } + + return $this; + } + + /** + * Get Horizontal. + * + * @return null|string + */ + public function getHorizontal() + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->getHorizontal(); + } + + return $this->horizontal; + } + + /** + * Set Horizontal. + * + * @param string $horizontalAlignment see self::HORIZONTAL_* + * + * @return $this + */ + public function setHorizontal(string $horizontalAlignment) + { + if ($horizontalAlignment == '') { + $horizontalAlignment = self::HORIZONTAL_GENERAL; + } + + if ($this->isSupervisor) { + $styleArray = $this->getStyleArray(['horizontal' => $horizontalAlignment]); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); + } else { + $this->horizontal = $horizontalAlignment; + } + + return $this; + } + + /** + * Get Vertical. + * + * @return null|string + */ + public function getVertical() + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->getVertical(); + } + + return $this->vertical; + } + + /** + * Set Vertical. + * + * @param string $verticalAlignment see self::VERTICAL_* + * + * @return $this + */ + public function setVertical($verticalAlignment) + { + if ($verticalAlignment == '') { + $verticalAlignment = self::VERTICAL_BOTTOM; + } + + if ($this->isSupervisor) { + $styleArray = $this->getStyleArray(['vertical' => $verticalAlignment]); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); + } else { + $this->vertical = $verticalAlignment; + } + + return $this; + } + + /** + * Get TextRotation. + * + * @return null|int + */ + public function getTextRotation() + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->getTextRotation(); + } + + return $this->textRotation; + } + + /** + * Set TextRotation. + * + * @param int $angleInDegrees + * + * @return $this + */ + public function setTextRotation($angleInDegrees) + { + // Excel2007 value 255 => PhpSpreadsheet value -165 + if ($angleInDegrees == self::TEXTROTATION_STACK_EXCEL) { + $angleInDegrees = self::TEXTROTATION_STACK_PHPSPREADSHEET; + } + + // Set rotation + if (($angleInDegrees >= -90 && $angleInDegrees <= 90) || $angleInDegrees == self::TEXTROTATION_STACK_PHPSPREADSHEET) { + if ($this->isSupervisor) { + $styleArray = $this->getStyleArray(['textRotation' => $angleInDegrees]); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); + } else { + $this->textRotation = $angleInDegrees; + } + } else { + throw new PhpSpreadsheetException('Text rotation should be a value between -90 and 90.'); + } + + return $this; + } + + /** + * Get Wrap Text. + * + * @return bool + */ + public function getWrapText() + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->getWrapText(); + } + + return $this->wrapText; + } + + /** + * Set Wrap Text. + * + * @param bool $wrapped + * + * @return $this + */ + public function setWrapText($wrapped) + { + if ($wrapped == '') { + $wrapped = false; + } + if ($this->isSupervisor) { + $styleArray = $this->getStyleArray(['wrapText' => $wrapped]); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); + } else { + $this->wrapText = $wrapped; + } + + return $this; + } + + /** + * Get Shrink to fit. + * + * @return bool + */ + public function getShrinkToFit() + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->getShrinkToFit(); + } + + return $this->shrinkToFit; + } + + /** + * Set Shrink to fit. + * + * @param bool $shrink + * + * @return $this + */ + public function setShrinkToFit($shrink) + { + if ($shrink == '') { + $shrink = false; + } + if ($this->isSupervisor) { + $styleArray = $this->getStyleArray(['shrinkToFit' => $shrink]); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); + } else { + $this->shrinkToFit = $shrink; + } + + return $this; + } + + /** + * Get indent. + * + * @return int + */ + public function getIndent() + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->getIndent(); + } + + return $this->indent; + } + + /** + * Set indent. + * + * @param int $indent + * + * @return $this + */ + public function setIndent($indent) + { + if ($indent > 0) { + if ( + $this->getHorizontal() != self::HORIZONTAL_GENERAL && + $this->getHorizontal() != self::HORIZONTAL_LEFT && + $this->getHorizontal() != self::HORIZONTAL_RIGHT && + $this->getHorizontal() != self::HORIZONTAL_DISTRIBUTED + ) { + $indent = 0; // indent not supported + } + } + if ($this->isSupervisor) { + $styleArray = $this->getStyleArray(['indent' => $indent]); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); + } else { + $this->indent = $indent; + } + + return $this; + } + + /** + * Get read order. + * + * @return int + */ + public function getReadOrder() + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->getReadOrder(); + } + + return $this->readOrder; + } + + /** + * Set read order. + * + * @param int $readOrder + * + * @return $this + */ + public function setReadOrder($readOrder) + { + if ($readOrder < 0 || $readOrder > 2) { + $readOrder = 0; + } + if ($this->isSupervisor) { + $styleArray = $this->getStyleArray(['readOrder' => $readOrder]); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); + } else { + $this->readOrder = $readOrder; + } + + return $this; + } + + /** + * Get hash code. + * + * @return string Hash code + */ + public function getHashCode() + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->getHashCode(); + } + + return md5( + $this->horizontal . + $this->vertical . + $this->textRotation . + ($this->wrapText ? 't' : 'f') . + ($this->shrinkToFit ? 't' : 'f') . + $this->indent . + $this->readOrder . + __CLASS__ + ); + } + + protected function exportArray1(): array + { + $exportedArray = []; + $this->exportArray2($exportedArray, 'horizontal', $this->getHorizontal()); + $this->exportArray2($exportedArray, 'indent', $this->getIndent()); + $this->exportArray2($exportedArray, 'readOrder', $this->getReadOrder()); + $this->exportArray2($exportedArray, 'shrinkToFit', $this->getShrinkToFit()); + $this->exportArray2($exportedArray, 'textRotation', $this->getTextRotation()); + $this->exportArray2($exportedArray, 'vertical', $this->getVertical()); + $this->exportArray2($exportedArray, 'wrapText', $this->getWrapText()); + + return $exportedArray; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Border.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Border.php new file mode 100644 index 0000000..9fabf36 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Border.php @@ -0,0 +1,234 @@ +color = new Color(Color::COLOR_BLACK, $isSupervisor); + + // bind parent if we are a supervisor + if ($isSupervisor) { + $this->color->bindParent($this, 'color'); + } + } + + /** + * Get the shared style component for the currently active cell in currently active sheet. + * Only used for style supervisor. + * + * @return Border + */ + public function getSharedComponent() + { + /** @var Borders $sharedComponent */ + $sharedComponent = $this->parent->getSharedComponent(); + switch ($this->parentPropertyName) { + case 'bottom': + return $sharedComponent->getBottom(); + case 'diagonal': + return $sharedComponent->getDiagonal(); + case 'left': + return $sharedComponent->getLeft(); + case 'right': + return $sharedComponent->getRight(); + case 'top': + return $sharedComponent->getTop(); + } + + throw new PhpSpreadsheetException('Cannot get shared component for a pseudo-border.'); + } + + /** + * Build style array from subcomponents. + * + * @param array $array + * + * @return array + */ + public function getStyleArray($array) + { + return $this->parent->getStyleArray([$this->parentPropertyName => $array]); + } + + /** + * Apply styles from array. + * + * + * $spreadsheet->getActiveSheet()->getStyle('B2')->getBorders()->getTop()->applyFromArray( + * [ + * 'borderStyle' => Border::BORDER_DASHDOT, + * 'color' => [ + * 'rgb' => '808080' + * ] + * ] + * ); + * + * + * @param array $styleArray Array containing style information + * + * @return $this + */ + public function applyFromArray(array $styleArray) + { + if ($this->isSupervisor) { + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($styleArray)); + } else { + if (isset($styleArray['borderStyle'])) { + $this->setBorderStyle($styleArray['borderStyle']); + } + if (isset($styleArray['color'])) { + $this->getColor()->applyFromArray($styleArray['color']); + } + } + + return $this; + } + + /** + * Get Border style. + * + * @return string + */ + public function getBorderStyle() + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->getBorderStyle(); + } + + return $this->borderStyle; + } + + /** + * Set Border style. + * + * @param bool|string $style + * When passing a boolean, FALSE equates Border::BORDER_NONE + * and TRUE to Border::BORDER_MEDIUM + * + * @return $this + */ + public function setBorderStyle($style) + { + if (empty($style)) { + $style = self::BORDER_NONE; + } elseif (is_bool($style)) { + $style = self::BORDER_MEDIUM; + } + + if ($this->isSupervisor) { + $styleArray = $this->getStyleArray(['borderStyle' => $style]); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); + } else { + $this->borderStyle = $style; + } + + return $this; + } + + /** + * Get Border Color. + * + * @return Color + */ + public function getColor() + { + return $this->color; + } + + /** + * Set Border Color. + * + * @return $this + */ + public function setColor(Color $color) + { + // make sure parameter is a real color and not a supervisor + $color = $color->getIsSupervisor() ? $color->getSharedComponent() : $color; + + if ($this->isSupervisor) { + $styleArray = $this->getColor()->getStyleArray(['argb' => $color->getARGB()]); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); + } else { + $this->color = $color; + } + + return $this; + } + + /** + * Get hash code. + * + * @return string Hash code + */ + public function getHashCode() + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->getHashCode(); + } + + return md5( + $this->borderStyle . + $this->color->getHashCode() . + __CLASS__ + ); + } + + protected function exportArray1(): array + { + $exportedArray = []; + $this->exportArray2($exportedArray, 'borderStyle', $this->getBorderStyle()); + $this->exportArray2($exportedArray, 'color', $this->getColor()); + + return $exportedArray; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Borders.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Borders.php new file mode 100644 index 0000000..5d92f93 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Borders.php @@ -0,0 +1,421 @@ +left = new Border($isSupervisor); + $this->right = new Border($isSupervisor); + $this->top = new Border($isSupervisor); + $this->bottom = new Border($isSupervisor); + $this->diagonal = new Border($isSupervisor); + $this->diagonalDirection = self::DIAGONAL_NONE; + + // Specially for supervisor + if ($isSupervisor) { + // Initialize pseudo-borders + $this->allBorders = new Border(true); + $this->outline = new Border(true); + $this->inside = new Border(true); + $this->vertical = new Border(true); + $this->horizontal = new Border(true); + + // bind parent if we are a supervisor + $this->left->bindParent($this, 'left'); + $this->right->bindParent($this, 'right'); + $this->top->bindParent($this, 'top'); + $this->bottom->bindParent($this, 'bottom'); + $this->diagonal->bindParent($this, 'diagonal'); + $this->allBorders->bindParent($this, 'allBorders'); + $this->outline->bindParent($this, 'outline'); + $this->inside->bindParent($this, 'inside'); + $this->vertical->bindParent($this, 'vertical'); + $this->horizontal->bindParent($this, 'horizontal'); + } + } + + /** + * Get the shared style component for the currently active cell in currently active sheet. + * Only used for style supervisor. + * + * @return Borders + */ + public function getSharedComponent() + { + return $this->parent->getSharedComponent()->getBorders(); + } + + /** + * Build style array from subcomponents. + * + * @param array $array + * + * @return array + */ + public function getStyleArray($array) + { + return ['borders' => $array]; + } + + /** + * Apply styles from array. + * + * + * $spreadsheet->getActiveSheet()->getStyle('B2')->getBorders()->applyFromArray( + * [ + * 'bottom' => [ + * 'borderStyle' => Border::BORDER_DASHDOT, + * 'color' => [ + * 'rgb' => '808080' + * ] + * ], + * 'top' => [ + * 'borderStyle' => Border::BORDER_DASHDOT, + * 'color' => [ + * 'rgb' => '808080' + * ] + * ] + * ] + * ); + * + * + * + * $spreadsheet->getActiveSheet()->getStyle('B2')->getBorders()->applyFromArray( + * [ + * 'allBorders' => [ + * 'borderStyle' => Border::BORDER_DASHDOT, + * 'color' => [ + * 'rgb' => '808080' + * ] + * ] + * ] + * ); + * + * + * @param array $styleArray Array containing style information + * + * @return $this + */ + public function applyFromArray(array $styleArray) + { + if ($this->isSupervisor) { + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($styleArray)); + } else { + if (isset($styleArray['left'])) { + $this->getLeft()->applyFromArray($styleArray['left']); + } + if (isset($styleArray['right'])) { + $this->getRight()->applyFromArray($styleArray['right']); + } + if (isset($styleArray['top'])) { + $this->getTop()->applyFromArray($styleArray['top']); + } + if (isset($styleArray['bottom'])) { + $this->getBottom()->applyFromArray($styleArray['bottom']); + } + if (isset($styleArray['diagonal'])) { + $this->getDiagonal()->applyFromArray($styleArray['diagonal']); + } + if (isset($styleArray['diagonalDirection'])) { + $this->setDiagonalDirection($styleArray['diagonalDirection']); + } + if (isset($styleArray['allBorders'])) { + $this->getLeft()->applyFromArray($styleArray['allBorders']); + $this->getRight()->applyFromArray($styleArray['allBorders']); + $this->getTop()->applyFromArray($styleArray['allBorders']); + $this->getBottom()->applyFromArray($styleArray['allBorders']); + } + } + + return $this; + } + + /** + * Get Left. + * + * @return Border + */ + public function getLeft() + { + return $this->left; + } + + /** + * Get Right. + * + * @return Border + */ + public function getRight() + { + return $this->right; + } + + /** + * Get Top. + * + * @return Border + */ + public function getTop() + { + return $this->top; + } + + /** + * Get Bottom. + * + * @return Border + */ + public function getBottom() + { + return $this->bottom; + } + + /** + * Get Diagonal. + * + * @return Border + */ + public function getDiagonal() + { + return $this->diagonal; + } + + /** + * Get AllBorders (pseudo-border). Only applies to supervisor. + * + * @return Border + */ + public function getAllBorders() + { + if (!$this->isSupervisor) { + throw new PhpSpreadsheetException('Can only get pseudo-border for supervisor.'); + } + + return $this->allBorders; + } + + /** + * Get Outline (pseudo-border). Only applies to supervisor. + * + * @return Border + */ + public function getOutline() + { + if (!$this->isSupervisor) { + throw new PhpSpreadsheetException('Can only get pseudo-border for supervisor.'); + } + + return $this->outline; + } + + /** + * Get Inside (pseudo-border). Only applies to supervisor. + * + * @return Border + */ + public function getInside() + { + if (!$this->isSupervisor) { + throw new PhpSpreadsheetException('Can only get pseudo-border for supervisor.'); + } + + return $this->inside; + } + + /** + * Get Vertical (pseudo-border). Only applies to supervisor. + * + * @return Border + */ + public function getVertical() + { + if (!$this->isSupervisor) { + throw new PhpSpreadsheetException('Can only get pseudo-border for supervisor.'); + } + + return $this->vertical; + } + + /** + * Get Horizontal (pseudo-border). Only applies to supervisor. + * + * @return Border + */ + public function getHorizontal() + { + if (!$this->isSupervisor) { + throw new PhpSpreadsheetException('Can only get pseudo-border for supervisor.'); + } + + return $this->horizontal; + } + + /** + * Get DiagonalDirection. + * + * @return int + */ + public function getDiagonalDirection() + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->getDiagonalDirection(); + } + + return $this->diagonalDirection; + } + + /** + * Set DiagonalDirection. + * + * @param int $direction see self::DIAGONAL_* + * + * @return $this + */ + public function setDiagonalDirection($direction) + { + if ($direction == '') { + $direction = self::DIAGONAL_NONE; + } + if ($this->isSupervisor) { + $styleArray = $this->getStyleArray(['diagonalDirection' => $direction]); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); + } else { + $this->diagonalDirection = $direction; + } + + return $this; + } + + /** + * Get hash code. + * + * @return string Hash code + */ + public function getHashCode() + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->getHashcode(); + } + + return md5( + $this->getLeft()->getHashCode() . + $this->getRight()->getHashCode() . + $this->getTop()->getHashCode() . + $this->getBottom()->getHashCode() . + $this->getDiagonal()->getHashCode() . + $this->getDiagonalDirection() . + __CLASS__ + ); + } + + protected function exportArray1(): array + { + $exportedArray = []; + $this->exportArray2($exportedArray, 'bottom', $this->getBottom()); + $this->exportArray2($exportedArray, 'diagonal', $this->getDiagonal()); + $this->exportArray2($exportedArray, 'diagonalDirection', $this->getDiagonalDirection()); + $this->exportArray2($exportedArray, 'left', $this->getLeft()); + $this->exportArray2($exportedArray, 'right', $this->getRight()); + $this->exportArray2($exportedArray, 'top', $this->getTop()); + + return $exportedArray; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Color.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Color.php new file mode 100644 index 0000000..d9b9830 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Color.php @@ -0,0 +1,413 @@ +argb = $this->validateColor($colorValue, self::VALIDATE_ARGB_SIZE) ? $colorValue : self::COLOR_BLACK; + } + } + + /** + * Get the shared style component for the currently active cell in currently active sheet. + * Only used for style supervisor. + * + * @return Color + */ + public function getSharedComponent() + { + /** @var Border|Fill $sharedComponent */ + $sharedComponent = $this->parent->getSharedComponent(); + if ($this->parentPropertyName === 'endColor') { + return $sharedComponent->getEndColor(); + } + if ($this->parentPropertyName === 'startColor') { + return $sharedComponent->getStartColor(); + } + + return $sharedComponent->getColor(); + } + + /** + * Build style array from subcomponents. + * + * @param array $array + * + * @return array + */ + public function getStyleArray($array) + { + return $this->parent->getStyleArray([$this->parentPropertyName => $array]); + } + + /** + * Apply styles from array. + * + * + * $spreadsheet->getActiveSheet()->getStyle('B2')->getFont()->getColor()->applyFromArray(['rgb' => '808080']); + * + * + * @param array $styleArray Array containing style information + * + * @return $this + */ + public function applyFromArray(array $styleArray) + { + if ($this->isSupervisor) { + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($styleArray)); + } else { + if (isset($styleArray['rgb'])) { + $this->setRGB($styleArray['rgb']); + } + if (isset($styleArray['argb'])) { + $this->setARGB($styleArray['argb']); + } + } + + return $this; + } + + private function validateColor(string $colorValue, int $size): bool + { + return in_array(ucfirst(strtolower($colorValue)), self::NAMED_COLORS) || + preg_match(sprintf(self::VALIDATE_COLOR_VALUE, $size), $colorValue); + } + + /** + * Get ARGB. + */ + public function getARGB(): ?string + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->getARGB(); + } + + return $this->argb; + } + + /** + * Set ARGB. + * + * @param string $colorValue ARGB value, or a named color + * + * @return $this + */ + public function setARGB(?string $colorValue = self::COLOR_BLACK) + { + if ($colorValue === '' || $colorValue === null) { + $colorValue = self::COLOR_BLACK; + } elseif (!$this->validateColor($colorValue, self::VALIDATE_ARGB_SIZE)) { + return $this; + } + + if ($this->isSupervisor) { + $styleArray = $this->getStyleArray(['argb' => $colorValue]); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); + } else { + $this->argb = $colorValue; + } + + return $this; + } + + /** + * Get RGB. + */ + public function getRGB(): string + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->getRGB(); + } + + return substr($this->argb ?? '', 2); + } + + /** + * Set RGB. + * + * @param string $colorValue RGB value, or a named color + * + * @return $this + */ + public function setRGB(?string $colorValue = self::COLOR_BLACK) + { + if ($colorValue === '' || $colorValue === null) { + $colorValue = '000000'; + } elseif (!$this->validateColor($colorValue, self::VALIDATE_RGB_SIZE)) { + return $this; + } + + if ($this->isSupervisor) { + $styleArray = $this->getStyleArray(['argb' => 'FF' . $colorValue]); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); + } else { + $this->argb = 'FF' . $colorValue; + } + + return $this; + } + + /** + * Get a specified colour component of an RGB value. + * + * @param string $rgbValue The colour as an RGB value (e.g. FF00CCCC or CCDDEE + * @param int $offset Position within the RGB value to extract + * @param bool $hex Flag indicating whether the component should be returned as a hex or a + * decimal value + * + * @return int|string The extracted colour component + */ + private static function getColourComponent($rgbValue, $offset, $hex = true) + { + $colour = substr($rgbValue, $offset, 2); + + return ($hex) ? $colour : hexdec($colour); + } + + /** + * Get the red colour component of an RGB value. + * + * @param string $rgbValue The colour as an RGB value (e.g. FF00CCCC or CCDDEE + * @param bool $hex Flag indicating whether the component should be returned as a hex or a + * decimal value + * + * @return int|string The red colour component + */ + public static function getRed($rgbValue, $hex = true) + { + return self::getColourComponent($rgbValue, strlen($rgbValue) - 6, $hex); + } + + /** + * Get the green colour component of an RGB value. + * + * @param string $rgbValue The colour as an RGB value (e.g. FF00CCCC or CCDDEE + * @param bool $hex Flag indicating whether the component should be returned as a hex or a + * decimal value + * + * @return int|string The green colour component + */ + public static function getGreen($rgbValue, $hex = true) + { + return self::getColourComponent($rgbValue, strlen($rgbValue) - 4, $hex); + } + + /** + * Get the blue colour component of an RGB value. + * + * @param string $rgbValue The colour as an RGB value (e.g. FF00CCCC or CCDDEE + * @param bool $hex Flag indicating whether the component should be returned as a hex or a + * decimal value + * + * @return int|string The blue colour component + */ + public static function getBlue($rgbValue, $hex = true) + { + return self::getColourComponent($rgbValue, strlen($rgbValue) - 2, $hex); + } + + /** + * Adjust the brightness of a color. + * + * @param string $hexColourValue The colour as an RGBA or RGB value (e.g. FF00CCCC or CCDDEE) + * @param float $adjustPercentage The percentage by which to adjust the colour as a float from -1 to 1 + * + * @return string The adjusted colour as an RGBA or RGB value (e.g. FF00CCCC or CCDDEE) + */ + public static function changeBrightness($hexColourValue, $adjustPercentage) + { + $rgba = (strlen($hexColourValue) === 8); + $adjustPercentage = max(-1.0, min(1.0, $adjustPercentage)); + + /** @var int $red */ + $red = self::getRed($hexColourValue, false); + /** @var int $green */ + $green = self::getGreen($hexColourValue, false); + /** @var int $blue */ + $blue = self::getBlue($hexColourValue, false); + if ($adjustPercentage > 0) { + $red += (255 - $red) * $adjustPercentage; + $green += (255 - $green) * $adjustPercentage; + $blue += (255 - $blue) * $adjustPercentage; + } else { + $red += $red * $adjustPercentage; + $green += $green * $adjustPercentage; + $blue += $blue * $adjustPercentage; + } + + $rgb = strtoupper( + str_pad(dechex((int) $red), 2, '0', 0) . + str_pad(dechex((int) $green), 2, '0', 0) . + str_pad(dechex((int) $blue), 2, '0', 0) + ); + + return (($rgba) ? 'FF' : '') . $rgb; + } + + /** + * Get indexed color. + * + * @param int $colorIndex Index entry point into the colour array + * @param bool $background Flag to indicate whether default background or foreground colour + * should be returned if the indexed colour doesn't exist + * + * @return Color + */ + public static function indexedColor($colorIndex, $background = false): self + { + // Clean parameter + $colorIndex = (int) $colorIndex; + + // Indexed colors + if (self::$indexedColors === null) { + self::$indexedColors = [ + 1 => 'FF000000', // System Colour #1 - Black + 2 => 'FFFFFFFF', // System Colour #2 - White + 3 => 'FFFF0000', // System Colour #3 - Red + 4 => 'FF00FF00', // System Colour #4 - Green + 5 => 'FF0000FF', // System Colour #5 - Blue + 6 => 'FFFFFF00', // System Colour #6 - Yellow + 7 => 'FFFF00FF', // System Colour #7- Magenta + 8 => 'FF00FFFF', // System Colour #8- Cyan + 9 => 'FF800000', // Standard Colour #9 + 10 => 'FF008000', // Standard Colour #10 + 11 => 'FF000080', // Standard Colour #11 + 12 => 'FF808000', // Standard Colour #12 + 13 => 'FF800080', // Standard Colour #13 + 14 => 'FF008080', // Standard Colour #14 + 15 => 'FFC0C0C0', // Standard Colour #15 + 16 => 'FF808080', // Standard Colour #16 + 17 => 'FF9999FF', // Chart Fill Colour #17 + 18 => 'FF993366', // Chart Fill Colour #18 + 19 => 'FFFFFFCC', // Chart Fill Colour #19 + 20 => 'FFCCFFFF', // Chart Fill Colour #20 + 21 => 'FF660066', // Chart Fill Colour #21 + 22 => 'FFFF8080', // Chart Fill Colour #22 + 23 => 'FF0066CC', // Chart Fill Colour #23 + 24 => 'FFCCCCFF', // Chart Fill Colour #24 + 25 => 'FF000080', // Chart Line Colour #25 + 26 => 'FFFF00FF', // Chart Line Colour #26 + 27 => 'FFFFFF00', // Chart Line Colour #27 + 28 => 'FF00FFFF', // Chart Line Colour #28 + 29 => 'FF800080', // Chart Line Colour #29 + 30 => 'FF800000', // Chart Line Colour #30 + 31 => 'FF008080', // Chart Line Colour #31 + 32 => 'FF0000FF', // Chart Line Colour #32 + 33 => 'FF00CCFF', // Standard Colour #33 + 34 => 'FFCCFFFF', // Standard Colour #34 + 35 => 'FFCCFFCC', // Standard Colour #35 + 36 => 'FFFFFF99', // Standard Colour #36 + 37 => 'FF99CCFF', // Standard Colour #37 + 38 => 'FFFF99CC', // Standard Colour #38 + 39 => 'FFCC99FF', // Standard Colour #39 + 40 => 'FFFFCC99', // Standard Colour #40 + 41 => 'FF3366FF', // Standard Colour #41 + 42 => 'FF33CCCC', // Standard Colour #42 + 43 => 'FF99CC00', // Standard Colour #43 + 44 => 'FFFFCC00', // Standard Colour #44 + 45 => 'FFFF9900', // Standard Colour #45 + 46 => 'FFFF6600', // Standard Colour #46 + 47 => 'FF666699', // Standard Colour #47 + 48 => 'FF969696', // Standard Colour #48 + 49 => 'FF003366', // Standard Colour #49 + 50 => 'FF339966', // Standard Colour #50 + 51 => 'FF003300', // Standard Colour #51 + 52 => 'FF333300', // Standard Colour #52 + 53 => 'FF993300', // Standard Colour #53 + 54 => 'FF993366', // Standard Colour #54 + 55 => 'FF333399', // Standard Colour #55 + 56 => 'FF333333', // Standard Colour #56 + ]; + } + + if (isset(self::$indexedColors[$colorIndex])) { + return new self(self::$indexedColors[$colorIndex]); + } + + return ($background) ? new self(self::COLOR_WHITE) : new self(self::COLOR_BLACK); + } + + /** + * Get hash code. + * + * @return string Hash code + */ + public function getHashCode(): string + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->getHashCode(); + } + + return md5( + $this->argb . + __CLASS__ + ); + } + + protected function exportArray1(): array + { + $exportedArray = []; + $this->exportArray2($exportedArray, 'argb', $this->getARGB()); + + return $exportedArray; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Conditional.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Conditional.php new file mode 100644 index 0000000..e148ee8 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Conditional.php @@ -0,0 +1,321 @@ +style = new Style(false, true); + } + + /** + * Get Condition type. + * + * @return string + */ + public function getConditionType() + { + return $this->conditionType; + } + + /** + * Set Condition type. + * + * @param string $type Condition type, see self::CONDITION_* + * + * @return $this + */ + public function setConditionType($type) + { + $this->conditionType = $type; + + return $this; + } + + /** + * Get Operator type. + * + * @return string + */ + public function getOperatorType() + { + return $this->operatorType; + } + + /** + * Set Operator type. + * + * @param string $type Conditional operator type, see self::OPERATOR_* + * + * @return $this + */ + public function setOperatorType($type) + { + $this->operatorType = $type; + + return $this; + } + + /** + * Get text. + * + * @return string + */ + public function getText() + { + return $this->text; + } + + /** + * Set text. + * + * @param string $text + * + * @return $this + */ + public function setText($text) + { + $this->text = $text; + + return $this; + } + + /** + * Get StopIfTrue. + * + * @return bool + */ + public function getStopIfTrue() + { + return $this->stopIfTrue; + } + + /** + * Set StopIfTrue. + * + * @param bool $stopIfTrue + * + * @return $this + */ + public function setStopIfTrue($stopIfTrue) + { + $this->stopIfTrue = $stopIfTrue; + + return $this; + } + + /** + * Get Conditions. + * + * @return string[] + */ + public function getConditions() + { + return $this->condition; + } + + /** + * Set Conditions. + * + * @param bool|float|int|string|string[] $conditions Condition + * + * @return $this + */ + public function setConditions($conditions) + { + if (!is_array($conditions)) { + $conditions = [$conditions]; + } + $this->condition = $conditions; + + return $this; + } + + /** + * Add Condition. + * + * @param string $condition Condition + * + * @return $this + */ + public function addCondition($condition) + { + $this->condition[] = $condition; + + return $this; + } + + /** + * Get Style. + * + * @return Style + */ + public function getStyle() + { + return $this->style; + } + + /** + * Set Style. + * + * @return $this + */ + public function setStyle(?Style $style = null) + { + $this->style = $style; + + return $this; + } + + /** + * get DataBar. + * + * @return null|ConditionalDataBar + */ + public function getDataBar() + { + return $this->dataBar; + } + + /** + * set DataBar. + * + * @return $this + */ + public function setDataBar(ConditionalDataBar $dataBar) + { + $this->dataBar = $dataBar; + + return $this; + } + + /** + * Get hash code. + * + * @return string Hash code + */ + public function getHashCode() + { + return md5( + $this->conditionType . + $this->operatorType . + implode(';', $this->condition) . + $this->style->getHashCode() . + __CLASS__ + ); + } + + /** + * Implement PHP __clone to create a deep clone, not just a shallow copy. + */ + public function __clone() + { + $vars = get_object_vars($this); + foreach ($vars as $key => $value) { + if (is_object($value)) { + $this->$key = clone $value; + } else { + $this->$key = $value; + } + } + } + + /** + * Verify if param is valid condition type. + */ + public static function isValidConditionType(string $type): bool + { + return in_array($type, self::CONDITION_TYPES); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBar.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBar.php new file mode 100644 index 0000000..5451367 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBar.php @@ -0,0 +1,102 @@ + attribute */ + + /** @var null|bool */ + private $showValue; + + /** children */ + + /** @var ConditionalFormatValueObject */ + private $minimumConditionalFormatValueObject; + + /** @var ConditionalFormatValueObject */ + private $maximumConditionalFormatValueObject; + + /** @var string */ + private $color; + + /** */ + + /** @var ConditionalFormattingRuleExtension */ + private $conditionalFormattingRuleExt; + + /** + * @return null|bool + */ + public function getShowValue() + { + return $this->showValue; + } + + /** + * @param bool $showValue + */ + public function setShowValue($showValue) + { + $this->showValue = $showValue; + + return $this; + } + + /** + * @return ConditionalFormatValueObject + */ + public function getMinimumConditionalFormatValueObject() + { + return $this->minimumConditionalFormatValueObject; + } + + public function setMinimumConditionalFormatValueObject(ConditionalFormatValueObject $minimumConditionalFormatValueObject) + { + $this->minimumConditionalFormatValueObject = $minimumConditionalFormatValueObject; + + return $this; + } + + /** + * @return ConditionalFormatValueObject + */ + public function getMaximumConditionalFormatValueObject() + { + return $this->maximumConditionalFormatValueObject; + } + + public function setMaximumConditionalFormatValueObject(ConditionalFormatValueObject $maximumConditionalFormatValueObject) + { + $this->maximumConditionalFormatValueObject = $maximumConditionalFormatValueObject; + + return $this; + } + + public function getColor(): string + { + return $this->color; + } + + public function setColor(string $color): self + { + $this->color = $color; + + return $this; + } + + /** + * @return ConditionalFormattingRuleExtension + */ + public function getConditionalFormattingRuleExt() + { + return $this->conditionalFormattingRuleExt; + } + + public function setConditionalFormattingRuleExt(ConditionalFormattingRuleExtension $conditionalFormattingRuleExt) + { + $this->conditionalFormattingRuleExt = $conditionalFormattingRuleExt; + + return $this; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBarExtension.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBarExtension.php new file mode 100644 index 0000000..c709cf3 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBarExtension.php @@ -0,0 +1,290 @@ + attributes */ + + /** @var int */ + private $minLength; + + /** @var int */ + private $maxLength; + + /** @var null|bool */ + private $border; + + /** @var null|bool */ + private $gradient; + + /** @var string */ + private $direction; + + /** @var null|bool */ + private $negativeBarBorderColorSameAsPositive; + + /** @var string */ + private $axisPosition; + + // children + + /** @var ConditionalFormatValueObject */ + private $maximumConditionalFormatValueObject; + + /** @var ConditionalFormatValueObject */ + private $minimumConditionalFormatValueObject; + + /** @var string */ + private $borderColor; + + /** @var string */ + private $negativeFillColor; + + /** @var string */ + private $negativeBorderColor; + + /** @var array */ + private $axisColor = [ + 'rgb' => null, + 'theme' => null, + 'tint' => null, + ]; + + public function getXmlAttributes() + { + $ret = []; + foreach (['minLength', 'maxLength', 'direction', 'axisPosition'] as $attrKey) { + if (null !== $this->{$attrKey}) { + $ret[$attrKey] = $this->{$attrKey}; + } + } + foreach (['border', 'gradient', 'negativeBarBorderColorSameAsPositive'] as $attrKey) { + if (null !== $this->{$attrKey}) { + $ret[$attrKey] = $this->{$attrKey} ? '1' : '0'; + } + } + + return $ret; + } + + public function getXmlElements() + { + $ret = []; + $elms = ['borderColor', 'negativeFillColor', 'negativeBorderColor']; + foreach ($elms as $elmKey) { + if (null !== $this->{$elmKey}) { + $ret[$elmKey] = ['rgb' => $this->{$elmKey}]; + } + } + foreach (array_filter($this->axisColor) as $attrKey => $axisColorAttr) { + if (!isset($ret['axisColor'])) { + $ret['axisColor'] = []; + } + $ret['axisColor'][$attrKey] = $axisColorAttr; + } + + return $ret; + } + + /** + * @return int + */ + public function getMinLength() + { + return $this->minLength; + } + + public function setMinLength(int $minLength): self + { + $this->minLength = $minLength; + + return $this; + } + + /** + * @return int + */ + public function getMaxLength() + { + return $this->maxLength; + } + + public function setMaxLength(int $maxLength): self + { + $this->maxLength = $maxLength; + + return $this; + } + + /** + * @return null|bool + */ + public function getBorder() + { + return $this->border; + } + + public function setBorder(bool $border): self + { + $this->border = $border; + + return $this; + } + + /** + * @return null|bool + */ + public function getGradient() + { + return $this->gradient; + } + + public function setGradient(bool $gradient): self + { + $this->gradient = $gradient; + + return $this; + } + + /** + * @return string + */ + public function getDirection() + { + return $this->direction; + } + + public function setDirection(string $direction): self + { + $this->direction = $direction; + + return $this; + } + + /** + * @return null|bool + */ + public function getNegativeBarBorderColorSameAsPositive() + { + return $this->negativeBarBorderColorSameAsPositive; + } + + public function setNegativeBarBorderColorSameAsPositive(bool $negativeBarBorderColorSameAsPositive): self + { + $this->negativeBarBorderColorSameAsPositive = $negativeBarBorderColorSameAsPositive; + + return $this; + } + + /** + * @return string + */ + public function getAxisPosition() + { + return $this->axisPosition; + } + + public function setAxisPosition(string $axisPosition): self + { + $this->axisPosition = $axisPosition; + + return $this; + } + + /** + * @return ConditionalFormatValueObject + */ + public function getMaximumConditionalFormatValueObject() + { + return $this->maximumConditionalFormatValueObject; + } + + public function setMaximumConditionalFormatValueObject(ConditionalFormatValueObject $maximumConditionalFormatValueObject) + { + $this->maximumConditionalFormatValueObject = $maximumConditionalFormatValueObject; + + return $this; + } + + /** + * @return ConditionalFormatValueObject + */ + public function getMinimumConditionalFormatValueObject() + { + return $this->minimumConditionalFormatValueObject; + } + + public function setMinimumConditionalFormatValueObject(ConditionalFormatValueObject $minimumConditionalFormatValueObject) + { + $this->minimumConditionalFormatValueObject = $minimumConditionalFormatValueObject; + + return $this; + } + + /** + * @return string + */ + public function getBorderColor() + { + return $this->borderColor; + } + + public function setBorderColor(string $borderColor): self + { + $this->borderColor = $borderColor; + + return $this; + } + + /** + * @return string + */ + public function getNegativeFillColor() + { + return $this->negativeFillColor; + } + + public function setNegativeFillColor(string $negativeFillColor): self + { + $this->negativeFillColor = $negativeFillColor; + + return $this; + } + + /** + * @return string + */ + public function getNegativeBorderColor() + { + return $this->negativeBorderColor; + } + + public function setNegativeBorderColor(string $negativeBorderColor): self + { + $this->negativeBorderColor = $negativeBorderColor; + + return $this; + } + + public function getAxisColor(): array + { + return $this->axisColor; + } + + /** + * @param mixed $rgb + * @param null|mixed $theme + * @param null|mixed $tint + */ + public function setAxisColor($rgb, $theme = null, $tint = null): self + { + $this->axisColor = [ + 'rgb' => $rgb, + 'theme' => $theme, + 'tint' => $tint, + ]; + + return $this; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormatValueObject.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormatValueObject.php new file mode 100644 index 0000000..107969b --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormatValueObject.php @@ -0,0 +1,78 @@ +type = $type; + $this->value = $value; + $this->cellFormula = $cellFormula; + } + + /** + * @return mixed + */ + public function getType() + { + return $this->type; + } + + /** + * @param mixed $type + */ + public function setType($type) + { + $this->type = $type; + + return $this; + } + + /** + * @return mixed + */ + public function getValue() + { + return $this->value; + } + + /** + * @param mixed $value + */ + public function setValue($value) + { + $this->value = $value; + + return $this; + } + + /** + * @return mixed + */ + public function getCellFormula() + { + return $this->cellFormula; + } + + /** + * @param mixed $cellFormula + */ + public function setCellFormula($cellFormula) + { + $this->cellFormula = $cellFormula; + + return $this; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php new file mode 100644 index 0000000..d8ef990 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php @@ -0,0 +1,208 @@ + attributes */ + private $id; + + /** @var string Conditional Formatting Rule */ + private $cfRule; + + /** children */ + + /** @var ConditionalDataBarExtension */ + private $dataBar; + + /** @var string Sequence of References */ + private $sqref; + + /** + * ConditionalFormattingRuleExtension constructor. + */ + public function __construct($id = null, string $cfRule = self::CONDITION_EXTENSION_DATABAR) + { + if (null === $id) { + $this->id = '{' . $this->generateUuid() . '}'; + } else { + $this->id = $id; + } + $this->cfRule = $cfRule; + } + + private function generateUuid() + { + $chars = str_split('xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'); + + foreach ($chars as $i => $char) { + if ($char === 'x') { + $chars[$i] = dechex(random_int(0, 15)); + } elseif ($char === 'y') { + $chars[$i] = dechex(random_int(8, 11)); + } + } + + return implode('', $chars); + } + + public static function parseExtLstXml($extLstXml) + { + $conditionalFormattingRuleExtensions = []; + $conditionalFormattingRuleExtensionXml = null; + if ($extLstXml instanceof SimpleXMLElement) { + foreach ((count($extLstXml) > 0 ? $extLstXml : [$extLstXml]) as $extLst) { + //this uri is conditionalFormattings + //https://docs.microsoft.com/en-us/openspecs/office_standards/ms-xlsx/07d607af-5618-4ca2-b683-6a78dc0d9627 + if (isset($extLst->ext['uri']) && (string) $extLst->ext['uri'] === '{78C0D931-6437-407d-A8EE-F0AAD7539E65}') { + $conditionalFormattingRuleExtensionXml = $extLst->ext; + } + } + + if ($conditionalFormattingRuleExtensionXml) { + $ns = $conditionalFormattingRuleExtensionXml->getNamespaces(true); + $extFormattingsXml = $conditionalFormattingRuleExtensionXml->children($ns['x14']); + + foreach ($extFormattingsXml->children($ns['x14']) as $extFormattingXml) { + $extCfRuleXml = $extFormattingXml->cfRule; + $attributes = $extCfRuleXml->attributes(); + if (!$attributes || ((string) $attributes->type) !== Conditional::CONDITION_DATABAR) { + continue; + } + + $extFormattingRuleObj = new self((string) $attributes->id); + $extFormattingRuleObj->setSqref((string) $extFormattingXml->children($ns['xm'])->sqref); + $conditionalFormattingRuleExtensions[$extFormattingRuleObj->getId()] = $extFormattingRuleObj; + + $extDataBarObj = new ConditionalDataBarExtension(); + $extFormattingRuleObj->setDataBarExt($extDataBarObj); + $dataBarXml = $extCfRuleXml->dataBar; + self::parseExtDataBarAttributesFromXml($extDataBarObj, $dataBarXml); + self::parseExtDataBarElementChildrenFromXml($extDataBarObj, $dataBarXml, $ns); + } + } + } + + return $conditionalFormattingRuleExtensions; + } + + private static function parseExtDataBarAttributesFromXml( + ConditionalDataBarExtension $extDataBarObj, + SimpleXMLElement $dataBarXml + ): void { + $dataBarAttribute = $dataBarXml->attributes(); + if ($dataBarAttribute->minLength) { + $extDataBarObj->setMinLength((int) $dataBarAttribute->minLength); + } + if ($dataBarAttribute->maxLength) { + $extDataBarObj->setMaxLength((int) $dataBarAttribute->maxLength); + } + if ($dataBarAttribute->border) { + $extDataBarObj->setBorder((bool) (string) $dataBarAttribute->border); + } + if ($dataBarAttribute->gradient) { + $extDataBarObj->setGradient((bool) (string) $dataBarAttribute->gradient); + } + if ($dataBarAttribute->direction) { + $extDataBarObj->setDirection((string) $dataBarAttribute->direction); + } + if ($dataBarAttribute->negativeBarBorderColorSameAsPositive) { + $extDataBarObj->setNegativeBarBorderColorSameAsPositive((bool) (string) $dataBarAttribute->negativeBarBorderColorSameAsPositive); + } + if ($dataBarAttribute->axisPosition) { + $extDataBarObj->setAxisPosition((string) $dataBarAttribute->axisPosition); + } + } + + private static function parseExtDataBarElementChildrenFromXml(ConditionalDataBarExtension $extDataBarObj, SimpleXMLElement $dataBarXml, $ns): void + { + if ($dataBarXml->borderColor) { + $extDataBarObj->setBorderColor((string) $dataBarXml->borderColor->attributes()['rgb']); + } + if ($dataBarXml->negativeFillColor) { + $extDataBarObj->setNegativeFillColor((string) $dataBarXml->negativeFillColor->attributes()['rgb']); + } + if ($dataBarXml->negativeBorderColor) { + $extDataBarObj->setNegativeBorderColor((string) $dataBarXml->negativeBorderColor->attributes()['rgb']); + } + if ($dataBarXml->axisColor) { + $axisColorAttr = $dataBarXml->axisColor->attributes(); + $extDataBarObj->setAxisColor((string) $axisColorAttr['rgb'], (string) $axisColorAttr['theme'], (string) $axisColorAttr['tint']); + } + $cfvoIndex = 0; + foreach ($dataBarXml->cfvo as $cfvo) { + $f = (string) $cfvo->children($ns['xm'])->f; + $attributes = $cfvo->attributes(); + if (!($attributes)) { + continue; + } + + if ($cfvoIndex === 0) { + $extDataBarObj->setMinimumConditionalFormatValueObject(new ConditionalFormatValueObject((string) $attributes['type'], null, (empty($f) ? null : $f))); + } + if ($cfvoIndex === 1) { + $extDataBarObj->setMaximumConditionalFormatValueObject(new ConditionalFormatValueObject((string) $attributes['type'], null, (empty($f) ? null : $f))); + } + ++$cfvoIndex; + } + } + + /** + * @return mixed + */ + public function getId() + { + return $this->id; + } + + /** + * @param mixed $id + */ + public function setId($id): self + { + $this->id = $id; + + return $this; + } + + public function getCfRule(): string + { + return $this->cfRule; + } + + public function setCfRule(string $cfRule): self + { + $this->cfRule = $cfRule; + + return $this; + } + + public function getDataBarExt(): ConditionalDataBarExtension + { + return $this->dataBar; + } + + public function setDataBarExt(ConditionalDataBarExtension $dataBar): self + { + $this->dataBar = $dataBar; + + return $this; + } + + public function getSqref(): string + { + return $this->sqref; + } + + public function setSqref(string $sqref): self + { + $this->sqref = $sqref; + + return $this; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Fill.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Fill.php new file mode 100644 index 0000000..1a7331e --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Fill.php @@ -0,0 +1,325 @@ +fillType = null; + } + $this->startColor = new Color(Color::COLOR_WHITE, $isSupervisor, $isConditional); + $this->endColor = new Color(Color::COLOR_BLACK, $isSupervisor, $isConditional); + + // bind parent if we are a supervisor + if ($isSupervisor) { + $this->startColor->bindParent($this, 'startColor'); + $this->endColor->bindParent($this, 'endColor'); + } + } + + /** + * Get the shared style component for the currently active cell in currently active sheet. + * Only used for style supervisor. + * + * @return Fill + */ + public function getSharedComponent() + { + return $this->parent->getSharedComponent()->getFill(); + } + + /** + * Build style array from subcomponents. + * + * @param array $array + * + * @return array + */ + public function getStyleArray($array) + { + return ['fill' => $array]; + } + + /** + * Apply styles from array. + * + * + * $spreadsheet->getActiveSheet()->getStyle('B2')->getFill()->applyFromArray( + * [ + * 'fillType' => Fill::FILL_GRADIENT_LINEAR, + * 'rotation' => 0, + * 'startColor' => [ + * 'rgb' => '000000' + * ], + * 'endColor' => [ + * 'argb' => 'FFFFFFFF' + * ] + * ] + * ); + * + * + * @param array $styleArray Array containing style information + * + * @return $this + */ + public function applyFromArray(array $styleArray) + { + if ($this->isSupervisor) { + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($styleArray)); + } else { + if (isset($styleArray['fillType'])) { + $this->setFillType($styleArray['fillType']); + } + if (isset($styleArray['rotation'])) { + $this->setRotation($styleArray['rotation']); + } + if (isset($styleArray['startColor'])) { + $this->getStartColor()->applyFromArray($styleArray['startColor']); + } + if (isset($styleArray['endColor'])) { + $this->getEndColor()->applyFromArray($styleArray['endColor']); + } + if (isset($styleArray['color'])) { + $this->getStartColor()->applyFromArray($styleArray['color']); + $this->getEndColor()->applyFromArray($styleArray['color']); + } + } + + return $this; + } + + /** + * Get Fill Type. + * + * @return null|string + */ + public function getFillType() + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->getFillType(); + } + + return $this->fillType; + } + + /** + * Set Fill Type. + * + * @param string $fillType Fill type, see self::FILL_* + * + * @return $this + */ + public function setFillType($fillType) + { + if ($this->isSupervisor) { + $styleArray = $this->getStyleArray(['fillType' => $fillType]); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); + } else { + $this->fillType = $fillType; + } + + return $this; + } + + /** + * Get Rotation. + * + * @return float + */ + public function getRotation() + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->getRotation(); + } + + return $this->rotation; + } + + /** + * Set Rotation. + * + * @param float $angleInDegrees + * + * @return $this + */ + public function setRotation($angleInDegrees) + { + if ($this->isSupervisor) { + $styleArray = $this->getStyleArray(['rotation' => $angleInDegrees]); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); + } else { + $this->rotation = $angleInDegrees; + } + + return $this; + } + + /** + * Get Start Color. + * + * @return Color + */ + public function getStartColor() + { + return $this->startColor; + } + + /** + * Set Start Color. + * + * @return $this + */ + public function setStartColor(Color $color) + { + // make sure parameter is a real color and not a supervisor + $color = $color->getIsSupervisor() ? $color->getSharedComponent() : $color; + + if ($this->isSupervisor) { + $styleArray = $this->getStartColor()->getStyleArray(['argb' => $color->getARGB()]); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); + } else { + $this->startColor = $color; + } + + return $this; + } + + /** + * Get End Color. + * + * @return Color + */ + public function getEndColor() + { + return $this->endColor; + } + + /** + * Set End Color. + * + * @return $this + */ + public function setEndColor(Color $color) + { + // make sure parameter is a real color and not a supervisor + $color = $color->getIsSupervisor() ? $color->getSharedComponent() : $color; + + if ($this->isSupervisor) { + $styleArray = $this->getEndColor()->getStyleArray(['argb' => $color->getARGB()]); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); + } else { + $this->endColor = $color; + } + + return $this; + } + + /** + * Get hash code. + * + * @return string Hash code + */ + public function getHashCode() + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->getHashCode(); + } + // Note that we don't care about colours for fill type NONE, but could have duplicate NONEs with + // different hashes if we don't explicitly prevent this + return md5( + $this->getFillType() . + $this->getRotation() . + ($this->getFillType() !== self::FILL_NONE ? $this->getStartColor()->getHashCode() : '') . + ($this->getFillType() !== self::FILL_NONE ? $this->getEndColor()->getHashCode() : '') . + __CLASS__ + ); + } + + protected function exportArray1(): array + { + $exportedArray = []; + $this->exportArray2($exportedArray, 'endColor', $this->getEndColor()); + $this->exportArray2($exportedArray, 'fillType', $this->getFillType()); + $this->exportArray2($exportedArray, 'rotation', $this->getRotation()); + $this->exportArray2($exportedArray, 'startColor', $this->getStartColor()); + + return $exportedArray; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Font.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Font.php new file mode 100644 index 0000000..63a75cc --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Font.php @@ -0,0 +1,565 @@ +name = null; + $this->size = null; + $this->bold = null; + $this->italic = null; + $this->superscript = null; + $this->subscript = null; + $this->underline = null; + $this->strikethrough = null; + $this->color = new Color(Color::COLOR_BLACK, $isSupervisor, $isConditional); + } else { + $this->color = new Color(Color::COLOR_BLACK, $isSupervisor); + } + // bind parent if we are a supervisor + if ($isSupervisor) { + $this->color->bindParent($this, 'color'); + } + } + + /** + * Get the shared style component for the currently active cell in currently active sheet. + * Only used for style supervisor. + * + * @return Font + */ + public function getSharedComponent() + { + return $this->parent->getSharedComponent()->getFont(); + } + + /** + * Build style array from subcomponents. + * + * @param array $array + * + * @return array + */ + public function getStyleArray($array) + { + return ['font' => $array]; + } + + /** + * Apply styles from array. + * + * + * $spreadsheet->getActiveSheet()->getStyle('B2')->getFont()->applyFromArray( + * [ + * 'name' => 'Arial', + * 'bold' => TRUE, + * 'italic' => FALSE, + * 'underline' => \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_DOUBLE, + * 'strikethrough' => FALSE, + * 'color' => [ + * 'rgb' => '808080' + * ] + * ] + * ); + * + * + * @param array $styleArray Array containing style information + * + * @return $this + */ + public function applyFromArray(array $styleArray) + { + if ($this->isSupervisor) { + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($styleArray)); + } else { + if (isset($styleArray['name'])) { + $this->setName($styleArray['name']); + } + if (isset($styleArray['bold'])) { + $this->setBold($styleArray['bold']); + } + if (isset($styleArray['italic'])) { + $this->setItalic($styleArray['italic']); + } + if (isset($styleArray['superscript'])) { + $this->setSuperscript($styleArray['superscript']); + } + if (isset($styleArray['subscript'])) { + $this->setSubscript($styleArray['subscript']); + } + if (isset($styleArray['underline'])) { + $this->setUnderline($styleArray['underline']); + } + if (isset($styleArray['strikethrough'])) { + $this->setStrikethrough($styleArray['strikethrough']); + } + if (isset($styleArray['color'])) { + $this->getColor()->applyFromArray($styleArray['color']); + } + if (isset($styleArray['size'])) { + $this->setSize($styleArray['size']); + } + } + + return $this; + } + + /** + * Get Name. + * + * @return null|string + */ + public function getName() + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->getName(); + } + + return $this->name; + } + + /** + * Set Name. + * + * @param string $fontname + * + * @return $this + */ + public function setName($fontname) + { + if ($fontname == '') { + $fontname = 'Calibri'; + } + if ($this->isSupervisor) { + $styleArray = $this->getStyleArray(['name' => $fontname]); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); + } else { + $this->name = $fontname; + } + + return $this; + } + + /** + * Get Size. + * + * @return null|float + */ + public function getSize() + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->getSize(); + } + + return $this->size; + } + + /** + * Set Size. + * + * @param mixed $sizeInPoints A float representing the value of a positive measurement in points (1/72 of an inch) + * + * @return $this + */ + public function setSize($sizeInPoints) + { + if (is_string($sizeInPoints) || is_int($sizeInPoints)) { + $sizeInPoints = (float) $sizeInPoints; // $pValue = 0 if given string is not numeric + } + + // Size must be a positive floating point number + // ECMA-376-1:2016, part 1, chapter 18.4.11 sz (Font Size), p. 1536 + if (!is_float($sizeInPoints) || !($sizeInPoints > 0)) { + $sizeInPoints = 10.0; + } + + if ($this->isSupervisor) { + $styleArray = $this->getStyleArray(['size' => $sizeInPoints]); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); + } else { + $this->size = $sizeInPoints; + } + + return $this; + } + + /** + * Get Bold. + * + * @return null|bool + */ + public function getBold() + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->getBold(); + } + + return $this->bold; + } + + /** + * Set Bold. + * + * @param bool $bold + * + * @return $this + */ + public function setBold($bold) + { + if ($bold == '') { + $bold = false; + } + if ($this->isSupervisor) { + $styleArray = $this->getStyleArray(['bold' => $bold]); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); + } else { + $this->bold = $bold; + } + + return $this; + } + + /** + * Get Italic. + * + * @return null|bool + */ + public function getItalic() + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->getItalic(); + } + + return $this->italic; + } + + /** + * Set Italic. + * + * @param bool $italic + * + * @return $this + */ + public function setItalic($italic) + { + if ($italic == '') { + $italic = false; + } + if ($this->isSupervisor) { + $styleArray = $this->getStyleArray(['italic' => $italic]); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); + } else { + $this->italic = $italic; + } + + return $this; + } + + /** + * Get Superscript. + * + * @return null|bool + */ + public function getSuperscript() + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->getSuperscript(); + } + + return $this->superscript; + } + + /** + * Set Superscript. + * + * @return $this + */ + public function setSuperscript(bool $superscript) + { + if ($this->isSupervisor) { + $styleArray = $this->getStyleArray(['superscript' => $superscript]); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); + } else { + $this->superscript = $superscript; + if ($this->superscript) { + $this->subscript = false; + } + } + + return $this; + } + + /** + * Get Subscript. + * + * @return null|bool + */ + public function getSubscript() + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->getSubscript(); + } + + return $this->subscript; + } + + /** + * Set Subscript. + * + * @return $this + */ + public function setSubscript(bool $subscript) + { + if ($this->isSupervisor) { + $styleArray = $this->getStyleArray(['subscript' => $subscript]); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); + } else { + $this->subscript = $subscript; + if ($this->subscript) { + $this->superscript = false; + } + } + + return $this; + } + + /** + * Get Underline. + * + * @return null|string + */ + public function getUnderline() + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->getUnderline(); + } + + return $this->underline; + } + + /** + * Set Underline. + * + * @param bool|string $underlineStyle \PhpOffice\PhpSpreadsheet\Style\Font underline type + * If a boolean is passed, then TRUE equates to UNDERLINE_SINGLE, + * false equates to UNDERLINE_NONE + * + * @return $this + */ + public function setUnderline($underlineStyle) + { + if (is_bool($underlineStyle)) { + $underlineStyle = ($underlineStyle) ? self::UNDERLINE_SINGLE : self::UNDERLINE_NONE; + } elseif ($underlineStyle == '') { + $underlineStyle = self::UNDERLINE_NONE; + } + if ($this->isSupervisor) { + $styleArray = $this->getStyleArray(['underline' => $underlineStyle]); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); + } else { + $this->underline = $underlineStyle; + } + + return $this; + } + + /** + * Get Strikethrough. + * + * @return null|bool + */ + public function getStrikethrough() + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->getStrikethrough(); + } + + return $this->strikethrough; + } + + /** + * Set Strikethrough. + * + * @param bool $strikethru + * + * @return $this + */ + public function setStrikethrough($strikethru) + { + if ($strikethru == '') { + $strikethru = false; + } + + if ($this->isSupervisor) { + $styleArray = $this->getStyleArray(['strikethrough' => $strikethru]); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); + } else { + $this->strikethrough = $strikethru; + } + + return $this; + } + + /** + * Get Color. + * + * @return Color + */ + public function getColor() + { + return $this->color; + } + + /** + * Set Color. + * + * @return $this + */ + public function setColor(Color $color) + { + // make sure parameter is a real color and not a supervisor + $color = $color->getIsSupervisor() ? $color->getSharedComponent() : $color; + + if ($this->isSupervisor) { + $styleArray = $this->getColor()->getStyleArray(['argb' => $color->getARGB()]); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); + } else { + $this->color = $color; + } + + return $this; + } + + /** + * Get hash code. + * + * @return string Hash code + */ + public function getHashCode() + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->getHashCode(); + } + + return md5( + $this->name . + $this->size . + ($this->bold ? 't' : 'f') . + ($this->italic ? 't' : 'f') . + ($this->superscript ? 't' : 'f') . + ($this->subscript ? 't' : 'f') . + $this->underline . + ($this->strikethrough ? 't' : 'f') . + $this->color->getHashCode() . + __CLASS__ + ); + } + + protected function exportArray1(): array + { + $exportedArray = []; + $this->exportArray2($exportedArray, 'bold', $this->getBold()); + $this->exportArray2($exportedArray, 'color', $this->getColor()); + $this->exportArray2($exportedArray, 'italic', $this->getItalic()); + $this->exportArray2($exportedArray, 'name', $this->getName()); + $this->exportArray2($exportedArray, 'size', $this->getSize()); + $this->exportArray2($exportedArray, 'strikethrough', $this->getStrikethrough()); + $this->exportArray2($exportedArray, 'subscript', $this->getSubscript()); + $this->exportArray2($exportedArray, 'superscript', $this->getSuperscript()); + $this->exportArray2($exportedArray, 'underline', $this->getUnderline()); + + return $exportedArray; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat.php new file mode 100644 index 0000000..a456928 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat.php @@ -0,0 +1,409 @@ +formatCode = null; + $this->builtInFormatCode = false; + } + } + + /** + * Get the shared style component for the currently active cell in currently active sheet. + * Only used for style supervisor. + * + * @return NumberFormat + */ + public function getSharedComponent() + { + return $this->parent->getSharedComponent()->getNumberFormat(); + } + + /** + * Build style array from subcomponents. + * + * @param array $array + * + * @return array + */ + public function getStyleArray($array) + { + return ['numberFormat' => $array]; + } + + /** + * Apply styles from array. + * + * + * $spreadsheet->getActiveSheet()->getStyle('B2')->getNumberFormat()->applyFromArray( + * [ + * 'formatCode' => NumberFormat::FORMAT_CURRENCY_EUR_SIMPLE + * ] + * ); + * + * + * @param array $styleArray Array containing style information + * + * @return $this + */ + public function applyFromArray(array $styleArray) + { + if ($this->isSupervisor) { + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($styleArray)); + } else { + if (isset($styleArray['formatCode'])) { + $this->setFormatCode($styleArray['formatCode']); + } + } + + return $this; + } + + /** + * Get Format Code. + * + * @return null|string + */ + public function getFormatCode() + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->getFormatCode(); + } + if ($this->builtInFormatCode !== false) { + return self::builtInFormatCode($this->builtInFormatCode); + } + + return $this->formatCode; + } + + /** + * Set Format Code. + * + * @param string $formatCode see self::FORMAT_* + * + * @return $this + */ + public function setFormatCode($formatCode) + { + if ($formatCode == '') { + $formatCode = self::FORMAT_GENERAL; + } + if ($this->isSupervisor) { + $styleArray = $this->getStyleArray(['formatCode' => $formatCode]); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); + } else { + $this->formatCode = $formatCode; + $this->builtInFormatCode = self::builtInFormatCodeIndex($formatCode); + } + + return $this; + } + + /** + * Get Built-In Format Code. + * + * @return false|int + */ + public function getBuiltInFormatCode() + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->getBuiltInFormatCode(); + } + + return $this->builtInFormatCode; + } + + /** + * Set Built-In Format Code. + * + * @param int $formatCodeIndex + * + * @return $this + */ + public function setBuiltInFormatCode($formatCodeIndex) + { + if ($this->isSupervisor) { + $styleArray = $this->getStyleArray(['formatCode' => self::builtInFormatCode($formatCodeIndex)]); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); + } else { + $this->builtInFormatCode = $formatCodeIndex; + $this->formatCode = self::builtInFormatCode($formatCodeIndex); + } + + return $this; + } + + /** + * Fill built-in format codes. + */ + private static function fillBuiltInFormatCodes(): void + { + // [MS-OI29500: Microsoft Office Implementation Information for ISO/IEC-29500 Standard Compliance] + // 18.8.30. numFmt (Number Format) + // + // The ECMA standard defines built-in format IDs + // 14: "mm-dd-yy" + // 22: "m/d/yy h:mm" + // 37: "#,##0 ;(#,##0)" + // 38: "#,##0 ;[Red](#,##0)" + // 39: "#,##0.00;(#,##0.00)" + // 40: "#,##0.00;[Red](#,##0.00)" + // 47: "mmss.0" + // KOR fmt 55: "yyyy-mm-dd" + // Excel defines built-in format IDs + // 14: "m/d/yyyy" + // 22: "m/d/yyyy h:mm" + // 37: "#,##0_);(#,##0)" + // 38: "#,##0_);[Red](#,##0)" + // 39: "#,##0.00_);(#,##0.00)" + // 40: "#,##0.00_);[Red](#,##0.00)" + // 47: "mm:ss.0" + // KOR fmt 55: "yyyy/mm/dd" + + // Built-in format codes + if (self::$builtInFormats === null) { + self::$builtInFormats = []; + + // General + self::$builtInFormats[0] = self::FORMAT_GENERAL; + self::$builtInFormats[1] = '0'; + self::$builtInFormats[2] = '0.00'; + self::$builtInFormats[3] = '#,##0'; + self::$builtInFormats[4] = '#,##0.00'; + + self::$builtInFormats[9] = '0%'; + self::$builtInFormats[10] = '0.00%'; + self::$builtInFormats[11] = '0.00E+00'; + self::$builtInFormats[12] = '# ?/?'; + self::$builtInFormats[13] = '# ??/??'; + self::$builtInFormats[14] = 'm/d/yyyy'; // Despite ECMA 'mm-dd-yy'; + self::$builtInFormats[15] = 'd-mmm-yy'; + self::$builtInFormats[16] = 'd-mmm'; + self::$builtInFormats[17] = 'mmm-yy'; + self::$builtInFormats[18] = 'h:mm AM/PM'; + self::$builtInFormats[19] = 'h:mm:ss AM/PM'; + self::$builtInFormats[20] = 'h:mm'; + self::$builtInFormats[21] = 'h:mm:ss'; + self::$builtInFormats[22] = 'm/d/yyyy h:mm'; // Despite ECMA 'm/d/yy h:mm'; + + self::$builtInFormats[37] = '#,##0_);(#,##0)'; // Despite ECMA '#,##0 ;(#,##0)'; + self::$builtInFormats[38] = '#,##0_);[Red](#,##0)'; // Despite ECMA '#,##0 ;[Red](#,##0)'; + self::$builtInFormats[39] = '#,##0.00_);(#,##0.00)'; // Despite ECMA '#,##0.00;(#,##0.00)'; + self::$builtInFormats[40] = '#,##0.00_);[Red](#,##0.00)'; // Despite ECMA '#,##0.00;[Red](#,##0.00)'; + + self::$builtInFormats[44] = '_("$"* #,##0.00_);_("$"* \(#,##0.00\);_("$"* "-"??_);_(@_)'; + self::$builtInFormats[45] = 'mm:ss'; + self::$builtInFormats[46] = '[h]:mm:ss'; + self::$builtInFormats[47] = 'mm:ss.0'; // Despite ECMA 'mmss.0'; + self::$builtInFormats[48] = '##0.0E+0'; + self::$builtInFormats[49] = '@'; + + // CHT + self::$builtInFormats[27] = '[$-404]e/m/d'; + self::$builtInFormats[30] = 'm/d/yy'; + self::$builtInFormats[36] = '[$-404]e/m/d'; + self::$builtInFormats[50] = '[$-404]e/m/d'; + self::$builtInFormats[57] = '[$-404]e/m/d'; + + // THA + self::$builtInFormats[59] = 't0'; + self::$builtInFormats[60] = 't0.00'; + self::$builtInFormats[61] = 't#,##0'; + self::$builtInFormats[62] = 't#,##0.00'; + self::$builtInFormats[67] = 't0%'; + self::$builtInFormats[68] = 't0.00%'; + self::$builtInFormats[69] = 't# ?/?'; + self::$builtInFormats[70] = 't# ??/??'; + + // JPN + self::$builtInFormats[28] = '[$-411]ggge"年"m"月"d"日"'; + self::$builtInFormats[29] = '[$-411]ggge"年"m"月"d"日"'; + self::$builtInFormats[31] = 'yyyy"年"m"月"d"日"'; + self::$builtInFormats[32] = 'h"時"mm"分"'; + self::$builtInFormats[33] = 'h"時"mm"分"ss"秒"'; + self::$builtInFormats[34] = 'yyyy"年"m"月"'; + self::$builtInFormats[35] = 'm"月"d"日"'; + self::$builtInFormats[51] = '[$-411]ggge"年"m"月"d"日"'; + self::$builtInFormats[52] = 'yyyy"年"m"月"'; + self::$builtInFormats[53] = 'm"月"d"日"'; + self::$builtInFormats[54] = '[$-411]ggge"年"m"月"d"日"'; + self::$builtInFormats[55] = 'yyyy"年"m"月"'; + self::$builtInFormats[56] = 'm"月"d"日"'; + self::$builtInFormats[58] = '[$-411]ggge"年"m"月"d"日"'; + + // Flip array (for faster lookups) + self::$flippedBuiltInFormats = array_flip(self::$builtInFormats); + } + } + + /** + * Get built-in format code. + * + * @param int $index + * + * @return string + */ + public static function builtInFormatCode($index) + { + // Clean parameter + $index = (int) $index; + + // Ensure built-in format codes are available + self::fillBuiltInFormatCodes(); + + // Lookup format code + if (isset(self::$builtInFormats[$index])) { + return self::$builtInFormats[$index]; + } + + return ''; + } + + /** + * Get built-in format code index. + * + * @param string $formatCodeIndex + * + * @return bool|int + */ + public static function builtInFormatCodeIndex($formatCodeIndex) + { + // Ensure built-in format codes are available + self::fillBuiltInFormatCodes(); + + // Lookup format code + if (array_key_exists($formatCodeIndex, self::$flippedBuiltInFormats)) { + return self::$flippedBuiltInFormats[$formatCodeIndex]; + } + + return false; + } + + /** + * Get hash code. + * + * @return string Hash code + */ + public function getHashCode() + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->getHashCode(); + } + + return md5( + $this->formatCode . + $this->builtInFormatCode . + __CLASS__ + ); + } + + /** + * Convert a value in a pre-defined format to a PHP string. + * + * @param mixed $value Value to format + * @param string $format Format code, see = self::FORMAT_* + * @param array $callBack Callback function for additional formatting of string + * + * @return string Formatted string + */ + public static function toFormattedString($value, $format, $callBack = null) + { + return NumberFormat\Formatter::toFormattedString($value, $format, $callBack); + } + + protected function exportArray1(): array + { + $exportedArray = []; + $this->exportArray2($exportedArray, 'formatCode', $this->getFormatCode()); + + return $exportedArray; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/BaseFormatter.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/BaseFormatter.php new file mode 100644 index 0000000..7988143 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/BaseFormatter.php @@ -0,0 +1,12 @@ + '', + // 12-hour suffix + 'am/pm' => 'A', + // 4-digit year + 'e' => 'Y', + 'yyyy' => 'Y', + // 2-digit year + 'yy' => 'y', + // first letter of month - no php equivalent + 'mmmmm' => 'M', + // full month name + 'mmmm' => 'F', + // short month name + 'mmm' => 'M', + // mm is minutes if time, but can also be month w/leading zero + // so we try to identify times be the inclusion of a : separator in the mask + // It isn't perfect, but the best way I know how + ':mm' => ':i', + 'mm:' => 'i:', + // month leading zero + 'mm' => 'm', + // month no leading zero + 'm' => 'n', + // full day of week name + 'dddd' => 'l', + // short day of week name + 'ddd' => 'D', + // days leading zero + 'dd' => 'd', + // days no leading zero + 'd' => 'j', + // seconds + 'ss' => 's', + // fractional seconds - no php equivalent + '.s' => '', + ]; + + /** + * Search/replace values to convert Excel date/time format masks hours to PHP format masks (24 hr clock). + * + * @var array + */ + private static $dateFormatReplacements24 = [ + 'hh' => 'H', + 'h' => 'G', + ]; + + /** + * Search/replace values to convert Excel date/time format masks hours to PHP format masks (12 hr clock). + * + * @var array + */ + private static $dateFormatReplacements12 = [ + 'hh' => 'h', + 'h' => 'g', + ]; + + public static function format($value, string $format): string + { + // strip off first part containing e.g. [$-F800] or [$USD-409] + // general syntax: [$-] + // language info is in hexadecimal + // strip off chinese part like [DBNum1][$-804] + $format = preg_replace('/^(\[DBNum\d\])*(\[\$[^\]]*\])/i', '', $format); + + // OpenOffice.org uses upper-case number formats, e.g. 'YYYY', convert to lower-case; + // but we don't want to change any quoted strings + $format = preg_replace_callback('/(?:^|")([^"]*)(?:$|")/', ['self', 'setLowercaseCallback'], $format); + + // Only process the non-quoted blocks for date format characters + $blocks = explode('"', $format); + foreach ($blocks as $key => &$block) { + if ($key % 2 == 0) { + $block = strtr($block, self::$dateFormatReplacements); + if (!strpos($block, 'A')) { + // 24-hour time format + // when [h]:mm format, the [h] should replace to the hours of the value * 24 + if (false !== strpos($block, '[h]')) { + $hours = (int) ($value * 24); + $block = str_replace('[h]', $hours, $block); + + continue; + } + $block = strtr($block, self::$dateFormatReplacements24); + } else { + // 12-hour time format + $block = strtr($block, self::$dateFormatReplacements12); + } + } + } + $format = implode('"', $blocks); + + // escape any quoted characters so that DateTime format() will render them correctly + $format = preg_replace_callback('/"(.*)"/U', ['self', 'escapeQuotesCallback'], $format); + + $dateObj = Date::excelToDateTimeObject($value); + // If the colon preceding minute had been quoted, as happens in + // Excel 2003 XML formats, m will not have been changed to i above. + // Change it now. + $format = \preg_replace('/\\\\:m/', ':i', $format); + + return $dateObj->format($format); + } + + private static function setLowercaseCallback($matches): string + { + return mb_strtolower($matches[0]); + } + + private static function escapeQuotesCallback($matches): string + { + return '\\' . implode('\\', str_split($matches[1])); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Formatter.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Formatter.php new file mode 100644 index 0000000..01407e6 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Formatter.php @@ -0,0 +1,162 @@ +': + return $value > $val; + + case '<': + return $value < $val; + + case '<=': + return $value <= $val; + + case '<>': + return $value != $val; + + case '=': + return $value == $val; + } + + return $value >= $val; + } + + private static function splitFormat($sections, $value) + { + // Extract the relevant section depending on whether number is positive, negative, or zero? + // Text not supported yet. + // Here is how the sections apply to various values in Excel: + // 1 section: [POSITIVE/NEGATIVE/ZERO/TEXT] + // 2 sections: [POSITIVE/ZERO/TEXT] [NEGATIVE] + // 3 sections: [POSITIVE/TEXT] [NEGATIVE] [ZERO] + // 4 sections: [POSITIVE] [NEGATIVE] [ZERO] [TEXT] + $cnt = count($sections); + $color_regex = '/\\[(' . implode('|', Color::NAMED_COLORS) . ')\\]/mui'; + $cond_regex = '/\\[(>|>=|<|<=|=|<>)([+-]?\\d+([.]\\d+)?)\\]/'; + $colors = ['', '', '', '', '']; + $condops = ['', '', '', '', '']; + $condvals = [0, 0, 0, 0, 0]; + for ($idx = 0; $idx < $cnt; ++$idx) { + if (preg_match($color_regex, $sections[$idx], $matches)) { + $colors[$idx] = $matches[0]; + $sections[$idx] = preg_replace($color_regex, '', $sections[$idx]); + } + if (preg_match($cond_regex, $sections[$idx], $matches)) { + $condops[$idx] = $matches[1]; + $condvals[$idx] = $matches[2]; + $sections[$idx] = preg_replace($cond_regex, '', $sections[$idx]); + } + } + $color = $colors[0]; + $format = $sections[0]; + $absval = $value; + switch ($cnt) { + case 2: + $absval = abs($value); + if (!self::splitFormatCompare($value, $condops[0], $condvals[0], '>=', 0)) { + $color = $colors[1]; + $format = $sections[1]; + } + + break; + case 3: + case 4: + $absval = abs($value); + if (!self::splitFormatCompare($value, $condops[0], $condvals[0], '>', 0)) { + if (self::splitFormatCompare($value, $condops[1], $condvals[1], '<', 0)) { + $color = $colors[1]; + $format = $sections[1]; + } else { + $color = $colors[2]; + $format = $sections[2]; + } + } + + break; + } + + return [$color, $format, $absval]; + } + + /** + * Convert a value in a pre-defined format to a PHP string. + * + * @param mixed $value Value to format + * @param string $format Format code, see = NumberFormat::FORMAT_* + * @param array $callBack Callback function for additional formatting of string + * + * @return string Formatted string + */ + public static function toFormattedString($value, $format, $callBack = null) + { + // For now we do not treat strings although section 4 of a format code affects strings + if (!is_numeric($value)) { + return $value; + } + + // For 'General' format code, we just pass the value although this is not entirely the way Excel does it, + // it seems to round numbers to a total of 10 digits. + if (($format === NumberFormat::FORMAT_GENERAL) || ($format === NumberFormat::FORMAT_TEXT)) { + return $value; + } + + $format = preg_replace_callback( + '/(["])(?:(?=(\\\\?))\\2.)*?\\1/u', + function ($matches) { + return str_replace('.', chr(0x00), $matches[0]); + }, + $format + ); + + // Convert any other escaped characters to quoted strings, e.g. (\T to "T") + $format = preg_replace('/(\\\(((.)(?!((AM\/PM)|(A\/P))))|([^ ])))(?=(?:[^"]|"[^"]*")*$)/ui', '"${2}"', $format); + + // Get the sections, there can be up to four sections, separated with a semi-colon (but only if not a quoted literal) + $sections = preg_split('/(;)(?=(?:[^"]|"[^"]*")*$)/u', $format); + + [$colors, $format, $value] = self::splitFormat($sections, $value); + + // In Excel formats, "_" is used to add spacing, + // The following character indicates the size of the spacing, which we can't do in HTML, so we just use a standard space + $format = preg_replace('/_.?/ui', ' ', $format); + + // Let's begin inspecting the format and converting the value to a formatted string + + // Check for date/time characters (not inside quotes) + if (preg_match('/(\[\$[A-Z]*-[0-9A-F]*\])*[hmsdy](?=(?:[^"]|"[^"]*")*$)/miu', $format, $matches)) { + // datetime format + $value = DateFormatter::format($value, $format); + } else { + if (substr($format, 0, 1) === '"' && substr($format, -1, 1) === '"' && substr_count($format, '"') === 2) { + $value = substr($format, 1, -1); + } elseif (preg_match('/[0#, ]%/', $format)) { + // % number format + $value = PercentageFormatter::format($value, $format); + } else { + $value = NumberFormatter::format($value, $format); + } + } + + // Additional formatting provided by callback function + if ($callBack !== null) { + [$writerInstance, $function] = $callBack; + $value = $writerInstance->$function($value, $colors); + } + + $value = str_replace(chr(0x00), '.', $value); + + return $value; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/FractionFormatter.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/FractionFormatter.php new file mode 100644 index 0000000..46f27cc --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/FractionFormatter.php @@ -0,0 +1,64 @@ + 0); + + return [ + implode('.', $masks), + implode('.', array_reverse($postDecimalMasks)), + ]; + } + + /** + * @param mixed $number + */ + private static function processComplexNumberFormatMask($number, string $mask): string + { + $result = $number; + $maskingBlockCount = preg_match_all('/0+/', $mask, $maskingBlocks, PREG_OFFSET_CAPTURE); + + if ($maskingBlockCount > 1) { + $maskingBlocks = array_reverse($maskingBlocks[0]); + + $offset = 0; + foreach ($maskingBlocks as $block) { + $size = strlen($block[0]); + $divisor = 10 ** $size; + $offset = $block[1]; + + $blockValue = sprintf("%0{$size}d", fmod($number, $divisor)); + $number = floor($number / $divisor); + $mask = substr_replace($mask, $blockValue, $offset, $size); + } + if ($number > 0) { + $mask = substr_replace($mask, $number, $offset, 0); + } + $result = $mask; + } + + return self::makeString($result); + } + + /** + * @param mixed $number + */ + private static function complexNumberFormatMask($number, string $mask, bool $splitOnPoint = true): string + { + $sign = ($number < 0.0) ? '-' : ''; + $number = (string) abs($number); + + if ($splitOnPoint && strpos($mask, '.') !== false && strpos($number, '.') !== false) { + $numbers = explode('.', $number); + $masks = explode('.', $mask); + if (count($masks) > 2) { + $masks = self::mergeComplexNumberFormatMasks($numbers, $masks); + } + $integerPart = self::complexNumberFormatMask($numbers[0], $masks[0], false); + $decimalPart = strrev(self::complexNumberFormatMask(strrev($numbers[1]), strrev($masks[1]), false)); + + return "{$sign}{$integerPart}.{$decimalPart}"; + } + + $result = self::processComplexNumberFormatMask($number, $mask); + + return "{$sign}{$result}"; + } + + /** + * @param mixed $value + */ + private static function formatStraightNumericValue($value, string $format, array $matches, bool $useThousands): string + { + $left = $matches[1]; + $dec = $matches[2]; + $right = $matches[3]; + + // minimun width of formatted number (including dot) + $minWidth = strlen($left) + strlen($dec) + strlen($right); + if ($useThousands) { + $value = number_format( + $value, + strlen($right), + StringHelper::getDecimalSeparator(), + StringHelper::getThousandsSeparator() + ); + + return self::pregReplace(self::NUMBER_REGEX, $value, $format); + } + + if (preg_match('/[0#]E[+-]0/i', $format)) { + // Scientific format + return sprintf('%5.2E', $value); + } elseif (preg_match('/0([^\d\.]+)0/', $format) || substr_count($format, '.') > 1) { + if ($value == (int) $value && substr_count($format, '.') === 1) { + $value *= 10 ** strlen(explode('.', $format)[1]); + } + + return self::complexNumberFormatMask($value, $format); + } + + $sprintf_pattern = "%0$minWidth." . strlen($right) . 'f'; + $value = sprintf($sprintf_pattern, $value); + + return self::pregReplace(self::NUMBER_REGEX, $value, $format); + } + + /** + * @param mixed $value + */ + public static function format($value, string $format): string + { + // The "_" in this string has already been stripped out, + // so this test is never true. Furthermore, testing + // on Excel shows this format uses Euro symbol, not "EUR". + //if ($format === NumberFormat::FORMAT_CURRENCY_EUR_SIMPLE) { + // return 'EUR ' . sprintf('%1.2f', $value); + //} + + // Some non-number strings are quoted, so we'll get rid of the quotes, likewise any positional * symbols + $format = self::makeString(str_replace(['"', '*'], '', $format)); + + // Find out if we need thousands separator + // This is indicated by a comma enclosed by a digit placeholder: + // #,# or 0,0 + $useThousands = (bool) preg_match('/(#,#|0,0)/', $format); + if ($useThousands) { + $format = self::pregReplace('/0,0/', '00', $format); + $format = self::pregReplace('/#,#/', '##', $format); + } + + // Scale thousands, millions,... + // This is indicated by a number of commas after a digit placeholder: + // #, or 0.0,, + $scale = 1; // same as no scale + $matches = []; + if (preg_match('/(#|0)(,+)/', $format, $matches)) { + $scale = 1000 ** strlen($matches[2]); + + // strip the commas + $format = self::pregReplace('/0,+/', '0', $format); + $format = self::pregReplace('/#,+/', '#', $format); + } + if (preg_match('/#?.*\?\/\?/', $format, $m)) { + $value = FractionFormatter::format($value, $format); + } else { + // Handle the number itself + + // scale number + $value = $value / $scale; + // Strip # + $format = self::pregReplace('/\\#/', '0', $format); + // Remove locale code [$-###] + $format = self::pregReplace('/\[\$\-.*\]/', '', $format); + + $n = '/\\[[^\\]]+\\]/'; + $m = self::pregReplace($n, '', $format); + if (preg_match(self::NUMBER_REGEX, $m, $matches)) { + // There are placeholders for digits, so inject digits from the value into the mask + $value = self::formatStraightNumericValue($value, $format, $matches, $useThousands); + } elseif ($format !== NumberFormat::FORMAT_GENERAL) { + // Yes, I know that this is basically just a hack; + // if there's no placeholders for digits, just return the format mask "as is" + $value = self::makeString(str_replace('?', '', $format)); + } + } + + if (preg_match('/\[\$(.*)\]/u', $format, $m)) { + // Currency or Accounting + $currencyCode = $m[1]; + [$currencyCode] = explode('-', $currencyCode); + if ($currencyCode == '') { + $currencyCode = StringHelper::getCurrencyCode(); + } + $value = self::pregReplace('/\[\$([^\]]*)\]/u', $currencyCode, (string) $value); + } + + return (string) $value; + } + + /** + * @param mixed $value + */ + private static function makeString($value): string + { + return is_array($value) ? '' : (string) $value; + } + + private static function pregReplace(string $pattern, string $replacement, string $subject): string + { + return self::makeString(preg_replace($pattern, $replacement, $subject)); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/PercentageFormatter.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/PercentageFormatter.php new file mode 100644 index 0000000..cf1731e --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/PercentageFormatter.php @@ -0,0 +1,42 @@ +locked = self::PROTECTION_INHERIT; + $this->hidden = self::PROTECTION_INHERIT; + } + } + + /** + * Get the shared style component for the currently active cell in currently active sheet. + * Only used for style supervisor. + * + * @return Protection + */ + public function getSharedComponent() + { + return $this->parent->getSharedComponent()->getProtection(); + } + + /** + * Build style array from subcomponents. + * + * @param array $array + * + * @return array + */ + public function getStyleArray($array) + { + return ['protection' => $array]; + } + + /** + * Apply styles from array. + * + * + * $spreadsheet->getActiveSheet()->getStyle('B2')->getLocked()->applyFromArray( + * [ + * 'locked' => TRUE, + * 'hidden' => FALSE + * ] + * ); + * + * + * @param array $styleArray Array containing style information + * + * @return $this + */ + public function applyFromArray(array $styleArray) + { + if ($this->isSupervisor) { + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($styleArray)); + } else { + if (isset($styleArray['locked'])) { + $this->setLocked($styleArray['locked']); + } + if (isset($styleArray['hidden'])) { + $this->setHidden($styleArray['hidden']); + } + } + + return $this; + } + + /** + * Get locked. + * + * @return string + */ + public function getLocked() + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->getLocked(); + } + + return $this->locked; + } + + /** + * Set locked. + * + * @param string $lockType see self::PROTECTION_* + * + * @return $this + */ + public function setLocked($lockType) + { + if ($this->isSupervisor) { + $styleArray = $this->getStyleArray(['locked' => $lockType]); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); + } else { + $this->locked = $lockType; + } + + return $this; + } + + /** + * Get hidden. + * + * @return string + */ + public function getHidden() + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->getHidden(); + } + + return $this->hidden; + } + + /** + * Set hidden. + * + * @param string $hiddenType see self::PROTECTION_* + * + * @return $this + */ + public function setHidden($hiddenType) + { + if ($this->isSupervisor) { + $styleArray = $this->getStyleArray(['hidden' => $hiddenType]); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); + } else { + $this->hidden = $hiddenType; + } + + return $this; + } + + /** + * Get hash code. + * + * @return string Hash code + */ + public function getHashCode() + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->getHashCode(); + } + + return md5( + $this->locked . + $this->hidden . + __CLASS__ + ); + } + + protected function exportArray1(): array + { + $exportedArray = []; + $this->exportArray2($exportedArray, 'locked', $this->getLocked()); + $this->exportArray2($exportedArray, 'hidden', $this->getHidden()); + + return $exportedArray; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Style.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Style.php new file mode 100644 index 0000000..5823929 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Style.php @@ -0,0 +1,737 @@ +, hashByObjId: array} + * + * @var array + */ + private static $cachedStyles; + + /** + * Create a new Style. + * + * @param bool $isSupervisor Flag indicating if this is a supervisor or not + * Leave this value at default unless you understand exactly what + * its ramifications are + * @param bool $isConditional Flag indicating if this is a conditional style or not + * Leave this value at default unless you understand exactly what + * its ramifications are + */ + public function __construct($isSupervisor = false, $isConditional = false) + { + parent::__construct($isSupervisor); + + // Initialise values + $this->font = new Font($isSupervisor, $isConditional); + $this->fill = new Fill($isSupervisor, $isConditional); + $this->borders = new Borders($isSupervisor); + $this->alignment = new Alignment($isSupervisor, $isConditional); + $this->numberFormat = new NumberFormat($isSupervisor, $isConditional); + $this->protection = new Protection($isSupervisor, $isConditional); + + // bind parent if we are a supervisor + if ($isSupervisor) { + $this->font->bindParent($this); + $this->fill->bindParent($this); + $this->borders->bindParent($this); + $this->alignment->bindParent($this); + $this->numberFormat->bindParent($this); + $this->protection->bindParent($this); + } + } + + /** + * Get the shared style component for the currently active cell in currently active sheet. + * Only used for style supervisor. + */ + public function getSharedComponent(): self + { + $activeSheet = $this->getActiveSheet(); + $selectedCell = $this->getActiveCell(); // e.g. 'A1' + + if ($activeSheet->cellExists($selectedCell)) { + $xfIndex = $activeSheet->getCell($selectedCell)->getXfIndex(); + } else { + $xfIndex = 0; + } + + return $this->parent->getCellXfByIndex($xfIndex); + } + + /** + * Get parent. Only used for style supervisor. + * + * @return Spreadsheet + */ + public function getParent() + { + return $this->parent; + } + + /** + * Build style array from subcomponents. + * + * @param array $array + * + * @return array + */ + public function getStyleArray($array) + { + return ['quotePrefix' => $array]; + } + + /** + * Apply styles from array. + * + * + * $spreadsheet->getActiveSheet()->getStyle('B2')->applyFromArray( + * [ + * 'font' => [ + * 'name' => 'Arial', + * 'bold' => true, + * 'italic' => false, + * 'underline' => Font::UNDERLINE_DOUBLE, + * 'strikethrough' => false, + * 'color' => [ + * 'rgb' => '808080' + * ] + * ], + * 'borders' => [ + * 'bottom' => [ + * 'borderStyle' => Border::BORDER_DASHDOT, + * 'color' => [ + * 'rgb' => '808080' + * ] + * ], + * 'top' => [ + * 'borderStyle' => Border::BORDER_DASHDOT, + * 'color' => [ + * 'rgb' => '808080' + * ] + * ] + * ], + * 'alignment' => [ + * 'horizontal' => Alignment::HORIZONTAL_CENTER, + * 'vertical' => Alignment::VERTICAL_CENTER, + * 'wrapText' => true, + * ], + * 'quotePrefix' => true + * ] + * ); + * + * + * @param array $styleArray Array containing style information + * @param bool $advancedBorders advanced mode for setting borders + * + * @return $this + */ + public function applyFromArray(array $styleArray, $advancedBorders = true) + { + if ($this->isSupervisor) { + $pRange = $this->getSelectedCells(); + + // Uppercase coordinate + $pRange = strtoupper($pRange); + + // Is it a cell range or a single cell? + if (strpos($pRange, ':') === false) { + $rangeA = $pRange; + $rangeB = $pRange; + } else { + [$rangeA, $rangeB] = explode(':', $pRange); + } + + // Calculate range outer borders + $rangeStart = Coordinate::coordinateFromString($rangeA); + $rangeEnd = Coordinate::coordinateFromString($rangeB); + $rangeStartIndexes = Coordinate::indexesFromString($rangeA); + $rangeEndIndexes = Coordinate::indexesFromString($rangeB); + + $columnStart = $rangeStart[0]; + $columnEnd = $rangeEnd[0]; + + // Make sure we can loop upwards on rows and columns + if ($rangeStartIndexes[0] > $rangeEndIndexes[0] && $rangeStartIndexes[1] > $rangeEndIndexes[1]) { + $tmp = $rangeStartIndexes; + $rangeStartIndexes = $rangeEndIndexes; + $rangeEndIndexes = $tmp; + } + + // ADVANCED MODE: + if ($advancedBorders && isset($styleArray['borders'])) { + // 'allBorders' is a shorthand property for 'outline' and 'inside' and + // it applies to components that have not been set explicitly + if (isset($styleArray['borders']['allBorders'])) { + foreach (['outline', 'inside'] as $component) { + if (!isset($styleArray['borders'][$component])) { + $styleArray['borders'][$component] = $styleArray['borders']['allBorders']; + } + } + unset($styleArray['borders']['allBorders']); // not needed any more + } + // 'outline' is a shorthand property for 'top', 'right', 'bottom', 'left' + // it applies to components that have not been set explicitly + if (isset($styleArray['borders']['outline'])) { + foreach (['top', 'right', 'bottom', 'left'] as $component) { + if (!isset($styleArray['borders'][$component])) { + $styleArray['borders'][$component] = $styleArray['borders']['outline']; + } + } + unset($styleArray['borders']['outline']); // not needed any more + } + // 'inside' is a shorthand property for 'vertical' and 'horizontal' + // it applies to components that have not been set explicitly + if (isset($styleArray['borders']['inside'])) { + foreach (['vertical', 'horizontal'] as $component) { + if (!isset($styleArray['borders'][$component])) { + $styleArray['borders'][$component] = $styleArray['borders']['inside']; + } + } + unset($styleArray['borders']['inside']); // not needed any more + } + // width and height characteristics of selection, 1, 2, or 3 (for 3 or more) + $xMax = min($rangeEndIndexes[0] - $rangeStartIndexes[0] + 1, 3); + $yMax = min($rangeEndIndexes[1] - $rangeStartIndexes[1] + 1, 3); + + // loop through up to 3 x 3 = 9 regions + for ($x = 1; $x <= $xMax; ++$x) { + // start column index for region + $colStart = ($x == 3) ? + Coordinate::stringFromColumnIndex($rangeEndIndexes[0]) + : Coordinate::stringFromColumnIndex($rangeStartIndexes[0] + $x - 1); + // end column index for region + $colEnd = ($x == 1) ? + Coordinate::stringFromColumnIndex($rangeStartIndexes[0]) + : Coordinate::stringFromColumnIndex($rangeEndIndexes[0] - $xMax + $x); + + for ($y = 1; $y <= $yMax; ++$y) { + // which edges are touching the region + $edges = []; + if ($x == 1) { + // are we at left edge + $edges[] = 'left'; + } + if ($x == $xMax) { + // are we at right edge + $edges[] = 'right'; + } + if ($y == 1) { + // are we at top edge? + $edges[] = 'top'; + } + if ($y == $yMax) { + // are we at bottom edge? + $edges[] = 'bottom'; + } + + // start row index for region + $rowStart = ($y == 3) ? + $rangeEndIndexes[1] : $rangeStartIndexes[1] + $y - 1; + + // end row index for region + $rowEnd = ($y == 1) ? + $rangeStartIndexes[1] : $rangeEndIndexes[1] - $yMax + $y; + + // build range for region + $range = $colStart . $rowStart . ':' . $colEnd . $rowEnd; + + // retrieve relevant style array for region + $regionStyles = $styleArray; + unset($regionStyles['borders']['inside']); + + // what are the inner edges of the region when looking at the selection + $innerEdges = array_diff(['top', 'right', 'bottom', 'left'], $edges); + + // inner edges that are not touching the region should take the 'inside' border properties if they have been set + foreach ($innerEdges as $innerEdge) { + switch ($innerEdge) { + case 'top': + case 'bottom': + // should pick up 'horizontal' border property if set + if (isset($styleArray['borders']['horizontal'])) { + $regionStyles['borders'][$innerEdge] = $styleArray['borders']['horizontal']; + } else { + unset($regionStyles['borders'][$innerEdge]); + } + + break; + case 'left': + case 'right': + // should pick up 'vertical' border property if set + if (isset($styleArray['borders']['vertical'])) { + $regionStyles['borders'][$innerEdge] = $styleArray['borders']['vertical']; + } else { + unset($regionStyles['borders'][$innerEdge]); + } + + break; + } + } + + // apply region style to region by calling applyFromArray() in simple mode + $this->getActiveSheet()->getStyle($range)->applyFromArray($regionStyles, false); + } + } + + // restore initial cell selection range + $this->getActiveSheet()->getStyle($pRange); + + return $this; + } + + // SIMPLE MODE: + // Selection type, inspect + if (preg_match('/^[A-Z]+1:[A-Z]+1048576$/', $pRange)) { + $selectionType = 'COLUMN'; + + // Enable caching of styles + self::$cachedStyles = ['hashByObjId' => [], 'styleByHash' => []]; + } elseif (preg_match('/^A\d+:XFD\d+$/', $pRange)) { + $selectionType = 'ROW'; + + // Enable caching of styles + self::$cachedStyles = ['hashByObjId' => [], 'styleByHash' => []]; + } else { + $selectionType = 'CELL'; + } + + // First loop through columns, rows, or cells to find out which styles are affected by this operation + $oldXfIndexes = $this->getOldXfIndexes($selectionType, $rangeStartIndexes, $rangeEndIndexes, $columnStart, $columnEnd, $styleArray); + + // clone each of the affected styles, apply the style array, and add the new styles to the workbook + $workbook = $this->getActiveSheet()->getParent(); + $newXfIndexes = []; + foreach ($oldXfIndexes as $oldXfIndex => $dummy) { + $style = $workbook->getCellXfByIndex($oldXfIndex); + + // $cachedStyles is set when applying style for a range of cells, either column or row + if (self::$cachedStyles === null) { + // Clone the old style and apply style-array + $newStyle = clone $style; + $newStyle->applyFromArray($styleArray); + + // Look for existing style we can use instead (reduce memory usage) + $existingStyle = $workbook->getCellXfByHashCode($newStyle->getHashCode()); + } else { + // Style cache is stored by Style::getHashCode(). But calling this method is + // expensive. So we cache the php obj id -> hash. + $objId = spl_object_id($style); + + // Look for the original HashCode + $styleHash = self::$cachedStyles['hashByObjId'][$objId] ?? null; + if ($styleHash === null) { + // This object_id is not cached, store the hashcode in case encounter again + $styleHash = self::$cachedStyles['hashByObjId'][$objId] = $style->getHashCode(); + } + + // Find existing style by hash. + $existingStyle = self::$cachedStyles['styleByHash'][$styleHash] ?? null; + + if (!$existingStyle) { + // The old style combined with the new style array is not cached, so we create it now + $newStyle = clone $style; + $newStyle->applyFromArray($styleArray); + + // Look for similar style in workbook to reduce memory usage + $existingStyle = $workbook->getCellXfByHashCode($newStyle->getHashCode()); + + // Cache the new style by original hashcode + self::$cachedStyles['styleByHash'][$styleHash] = $existingStyle instanceof self ? $existingStyle : $newStyle; + } + } + + if ($existingStyle) { + // there is already such cell Xf in our collection + $newXfIndexes[$oldXfIndex] = $existingStyle->getIndex(); + } else { + if (!isset($newStyle)) { + // Handle bug in PHPStan, see https://github.com/phpstan/phpstan/issues/5805 + // $newStyle should always be defined. + // This block might not be needed in the future + $newStyle = clone $style; + $newStyle->applyFromArray($styleArray); + } + + // we don't have such a cell Xf, need to add + $workbook->addCellXf($newStyle); + $newXfIndexes[$oldXfIndex] = $newStyle->getIndex(); + } + } + + // Loop through columns, rows, or cells again and update the XF index + switch ($selectionType) { + case 'COLUMN': + for ($col = $rangeStartIndexes[0]; $col <= $rangeEndIndexes[0]; ++$col) { + $columnDimension = $this->getActiveSheet()->getColumnDimensionByColumn($col); + $oldXfIndex = $columnDimension->getXfIndex(); + $columnDimension->setXfIndex($newXfIndexes[$oldXfIndex]); + } + + // Disable caching of styles + self::$cachedStyles = null; + + break; + case 'ROW': + for ($row = $rangeStartIndexes[1]; $row <= $rangeEndIndexes[1]; ++$row) { + $rowDimension = $this->getActiveSheet()->getRowDimension($row); + // row without explicit style should be formatted based on default style + $oldXfIndex = $rowDimension->getXfIndex() ?? 0; + $rowDimension->setXfIndex($newXfIndexes[$oldXfIndex]); + } + + // Disable caching of styles + self::$cachedStyles = null; + + break; + case 'CELL': + for ($col = $rangeStartIndexes[0]; $col <= $rangeEndIndexes[0]; ++$col) { + for ($row = $rangeStartIndexes[1]; $row <= $rangeEndIndexes[1]; ++$row) { + $cell = $this->getActiveSheet()->getCellByColumnAndRow($col, $row); + $oldXfIndex = $cell->getXfIndex(); + $cell->setXfIndex($newXfIndexes[$oldXfIndex]); + } + } + + break; + } + } else { + // not a supervisor, just apply the style array directly on style object + if (isset($styleArray['fill'])) { + $this->getFill()->applyFromArray($styleArray['fill']); + } + if (isset($styleArray['font'])) { + $this->getFont()->applyFromArray($styleArray['font']); + } + if (isset($styleArray['borders'])) { + $this->getBorders()->applyFromArray($styleArray['borders']); + } + if (isset($styleArray['alignment'])) { + $this->getAlignment()->applyFromArray($styleArray['alignment']); + } + if (isset($styleArray['numberFormat'])) { + $this->getNumberFormat()->applyFromArray($styleArray['numberFormat']); + } + if (isset($styleArray['protection'])) { + $this->getProtection()->applyFromArray($styleArray['protection']); + } + if (isset($styleArray['quotePrefix'])) { + $this->quotePrefix = $styleArray['quotePrefix']; + } + } + + return $this; + } + + private function getOldXfIndexes(string $selectionType, array $rangeStart, array $rangeEnd, string $columnStart, string $columnEnd, array $styleArray): array + { + $oldXfIndexes = []; + switch ($selectionType) { + case 'COLUMN': + for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) { + $oldXfIndexes[$this->getActiveSheet()->getColumnDimensionByColumn($col)->getXfIndex()] = true; + } + foreach ($this->getActiveSheet()->getColumnIterator($columnStart, $columnEnd) as $columnIterator) { + $cellIterator = $columnIterator->getCellIterator(); + $cellIterator->setIterateOnlyExistingCells(true); + foreach ($cellIterator as $columnCell) { + if ($columnCell !== null) { + $columnCell->getStyle()->applyFromArray($styleArray); + } + } + } + + break; + case 'ROW': + for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) { + if ($this->getActiveSheet()->getRowDimension($row)->getXfIndex() === null) { + $oldXfIndexes[0] = true; // row without explicit style should be formatted based on default style + } else { + $oldXfIndexes[$this->getActiveSheet()->getRowDimension($row)->getXfIndex()] = true; + } + } + foreach ($this->getActiveSheet()->getRowIterator((int) $rangeStart[1], (int) $rangeEnd[1]) as $rowIterator) { + $cellIterator = $rowIterator->getCellIterator(); + $cellIterator->setIterateOnlyExistingCells(true); + foreach ($cellIterator as $rowCell) { + if ($rowCell !== null) { + $rowCell->getStyle()->applyFromArray($styleArray); + } + } + } + + break; + case 'CELL': + for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) { + for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) { + $oldXfIndexes[$this->getActiveSheet()->getCellByColumnAndRow($col, $row)->getXfIndex()] = true; + } + } + + break; + } + + return $oldXfIndexes; + } + + /** + * Get Fill. + * + * @return Fill + */ + public function getFill() + { + return $this->fill; + } + + /** + * Get Font. + * + * @return Font + */ + public function getFont() + { + return $this->font; + } + + /** + * Set font. + * + * @return $this + */ + public function setFont(Font $font) + { + $this->font = $font; + + return $this; + } + + /** + * Get Borders. + * + * @return Borders + */ + public function getBorders() + { + return $this->borders; + } + + /** + * Get Alignment. + * + * @return Alignment + */ + public function getAlignment() + { + return $this->alignment; + } + + /** + * Get Number Format. + * + * @return NumberFormat + */ + public function getNumberFormat() + { + return $this->numberFormat; + } + + /** + * Get Conditional Styles. Only used on supervisor. + * + * @return Conditional[] + */ + public function getConditionalStyles() + { + return $this->getActiveSheet()->getConditionalStyles($this->getActiveCell()); + } + + /** + * Set Conditional Styles. Only used on supervisor. + * + * @param Conditional[] $conditionalStyleArray Array of conditional styles + * + * @return $this + */ + public function setConditionalStyles(array $conditionalStyleArray) + { + $this->getActiveSheet()->setConditionalStyles($this->getSelectedCells(), $conditionalStyleArray); + + return $this; + } + + /** + * Get Protection. + * + * @return Protection + */ + public function getProtection() + { + return $this->protection; + } + + /** + * Get quote prefix. + * + * @return bool + */ + public function getQuotePrefix() + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->getQuotePrefix(); + } + + return $this->quotePrefix; + } + + /** + * Set quote prefix. + * + * @param bool $quotePrefix + * + * @return $this + */ + public function setQuotePrefix($quotePrefix) + { + if ($quotePrefix == '') { + $quotePrefix = false; + } + if ($this->isSupervisor) { + $styleArray = ['quotePrefix' => $quotePrefix]; + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); + } else { + $this->quotePrefix = (bool) $quotePrefix; + } + + return $this; + } + + /** + * Get hash code. + * + * @return string Hash code + */ + public function getHashCode() + { + return md5( + $this->fill->getHashCode() . + $this->font->getHashCode() . + $this->borders->getHashCode() . + $this->alignment->getHashCode() . + $this->numberFormat->getHashCode() . + $this->protection->getHashCode() . + ($this->quotePrefix ? 't' : 'f') . + __CLASS__ + ); + } + + /** + * Get own index in style collection. + * + * @return int + */ + public function getIndex() + { + return $this->index; + } + + /** + * Set own index in style collection. + * + * @param int $index + */ + public function setIndex($index): void + { + $this->index = $index; + } + + protected function exportArray1(): array + { + $exportedArray = []; + $this->exportArray2($exportedArray, 'alignment', $this->getAlignment()); + $this->exportArray2($exportedArray, 'borders', $this->getBorders()); + $this->exportArray2($exportedArray, 'fill', $this->getFill()); + $this->exportArray2($exportedArray, 'font', $this->getFont()); + $this->exportArray2($exportedArray, 'numberFormat', $this->getNumberFormat()); + $this->exportArray2($exportedArray, 'protection', $this->getProtection()); + $this->exportArray2($exportedArray, 'quotePrefx', $this->getQuotePrefix()); + + return $exportedArray; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Supervisor.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Supervisor.php new file mode 100644 index 0000000..7f655be --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Supervisor.php @@ -0,0 +1,158 @@ +isSupervisor = $isSupervisor; + } + + /** + * Bind parent. Only used for supervisor. + * + * @param Spreadsheet|Style $parent + * @param null|string $parentPropertyName + * + * @return $this + */ + public function bindParent($parent, $parentPropertyName = null) + { + $this->parent = $parent; + $this->parentPropertyName = $parentPropertyName; + + return $this; + } + + /** + * Is this a supervisor or a cell style component? + * + * @return bool + */ + public function getIsSupervisor() + { + return $this->isSupervisor; + } + + /** + * Get the currently active sheet. Only used for supervisor. + * + * @return Worksheet + */ + public function getActiveSheet() + { + return $this->parent->getActiveSheet(); + } + + /** + * Get the currently active cell coordinate in currently active sheet. + * Only used for supervisor. + * + * @return string E.g. 'A1' + */ + public function getSelectedCells() + { + return $this->getActiveSheet()->getSelectedCells(); + } + + /** + * Get the currently active cell coordinate in currently active sheet. + * Only used for supervisor. + * + * @return string E.g. 'A1' + */ + public function getActiveCell() + { + return $this->getActiveSheet()->getActiveCell(); + } + + /** + * Implement PHP __clone to create a deep clone, not just a shallow copy. + */ + public function __clone() + { + $vars = get_object_vars($this); + foreach ($vars as $key => $value) { + if ((is_object($value)) && ($key != 'parent')) { + $this->$key = clone $value; + } else { + $this->$key = $value; + } + } + } + + /** + * Export style as array. + * + * Available to anything which extends this class: + * Alignment, Border, Borders, Color, Fill, Font, + * NumberFormat, Protection, and Style. + */ + final public function exportArray(): array + { + return $this->exportArray1(); + } + + /** + * Abstract method to be implemented in anything which + * extends this class. + * + * This method invokes exportArray2 with the names and values + * of all properties to be included in output array, + * returning that array to exportArray, then to caller. + */ + abstract protected function exportArray1(): array; + + /** + * Populate array from exportArray1. + * This method is available to anything which extends this class. + * The parameter index is the key to be added to the array. + * The parameter objOrValue is either a primitive type, + * which is the value added to the array, + * or a Style object to be recursively added via exportArray. + * + * @param mixed $objOrValue + */ + final protected function exportArray2(array &$exportedArray, string $index, $objOrValue): void + { + if ($objOrValue instanceof self) { + $exportedArray[$index] = $objOrValue->exportArray(); + } else { + $exportedArray[$index] = $objOrValue; + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter.php new file mode 100644 index 0000000..01dd837 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter.php @@ -0,0 +1,1045 @@ +range = $range; + $this->workSheet = $worksheet; + } + + /** + * Get AutoFilter Parent Worksheet. + * + * @return null|Worksheet + */ + public function getParent() + { + return $this->workSheet; + } + + /** + * Set AutoFilter Parent Worksheet. + * + * @return $this + */ + public function setParent(?Worksheet $worksheet = null) + { + $this->workSheet = $worksheet; + + return $this; + } + + /** + * Get AutoFilter Range. + * + * @return string + */ + public function getRange() + { + return $this->range; + } + + /** + * Set AutoFilter Range. + * + * @param string $range Cell range (i.e. A1:E10) + * + * @return $this + */ + public function setRange($range) + { + // extract coordinate + [$worksheet, $range] = Worksheet::extractSheetTitle($range, true); + if (empty($range)) { + // Discard all column rules + $this->columns = []; + $this->range = ''; + + return $this; + } + + if (strpos($range, ':') === false) { + throw new PhpSpreadsheetException('Autofilter must be set on a range of cells.'); + } + + $this->range = $range; + // Discard any column rules that are no longer valid within this range + [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range); + foreach ($this->columns as $key => $value) { + $colIndex = Coordinate::columnIndexFromString($key); + if (($rangeStart[0] > $colIndex) || ($rangeEnd[0] < $colIndex)) { + unset($this->columns[$key]); + } + } + + return $this; + } + + /** + * Get all AutoFilter Columns. + * + * @return AutoFilter\Column[] + */ + public function getColumns() + { + return $this->columns; + } + + /** + * Validate that the specified column is in the AutoFilter range. + * + * @param string $column Column name (e.g. A) + * + * @return int The column offset within the autofilter range + */ + public function testColumnInRange($column) + { + if (empty($this->range)) { + throw new PhpSpreadsheetException('No autofilter range is defined.'); + } + + $columnIndex = Coordinate::columnIndexFromString($column); + [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range); + if (($rangeStart[0] > $columnIndex) || ($rangeEnd[0] < $columnIndex)) { + throw new PhpSpreadsheetException('Column is outside of current autofilter range.'); + } + + return $columnIndex - $rangeStart[0]; + } + + /** + * Get a specified AutoFilter Column Offset within the defined AutoFilter range. + * + * @param string $column Column name (e.g. A) + * + * @return int The offset of the specified column within the autofilter range + */ + public function getColumnOffset($column) + { + return $this->testColumnInRange($column); + } + + /** + * Get a specified AutoFilter Column. + * + * @param string $column Column name (e.g. A) + * + * @return AutoFilter\Column + */ + public function getColumn($column) + { + $this->testColumnInRange($column); + + if (!isset($this->columns[$column])) { + $this->columns[$column] = new AutoFilter\Column($column, $this); + } + + return $this->columns[$column]; + } + + /** + * Get a specified AutoFilter Column by it's offset. + * + * @param int $columnOffset Column offset within range (starting from 0) + * + * @return AutoFilter\Column + */ + public function getColumnByOffset($columnOffset) + { + [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range); + $pColumn = Coordinate::stringFromColumnIndex($rangeStart[0] + $columnOffset); + + return $this->getColumn($pColumn); + } + + /** + * Set AutoFilter. + * + * @param AutoFilter\Column|string $columnObjectOrString + * A simple string containing a Column ID like 'A' is permitted + * + * @return $this + */ + public function setColumn($columnObjectOrString) + { + if ((is_string($columnObjectOrString)) && (!empty($columnObjectOrString))) { + $column = $columnObjectOrString; + } elseif (is_object($columnObjectOrString) && ($columnObjectOrString instanceof AutoFilter\Column)) { + $column = $columnObjectOrString->getColumnIndex(); + } else { + throw new PhpSpreadsheetException('Column is not within the autofilter range.'); + } + $this->testColumnInRange($column); + + if (is_string($columnObjectOrString)) { + $this->columns[$columnObjectOrString] = new AutoFilter\Column($columnObjectOrString, $this); + } else { + $columnObjectOrString->setParent($this); + $this->columns[$column] = $columnObjectOrString; + } + ksort($this->columns); + + return $this; + } + + /** + * Clear a specified AutoFilter Column. + * + * @param string $column Column name (e.g. A) + * + * @return $this + */ + public function clearColumn($column) + { + $this->testColumnInRange($column); + + if (isset($this->columns[$column])) { + unset($this->columns[$column]); + } + + return $this; + } + + /** + * Shift an AutoFilter Column Rule to a different column. + * + * Note: This method bypasses validation of the destination column to ensure it is within this AutoFilter range. + * Nor does it verify whether any column rule already exists at $toColumn, but will simply override any existing value. + * Use with caution. + * + * @param string $fromColumn Column name (e.g. A) + * @param string $toColumn Column name (e.g. B) + * + * @return $this + */ + public function shiftColumn($fromColumn, $toColumn) + { + $fromColumn = strtoupper($fromColumn); + $toColumn = strtoupper($toColumn); + + if (($fromColumn !== null) && (isset($this->columns[$fromColumn])) && ($toColumn !== null)) { + $this->columns[$fromColumn]->setParent(); + $this->columns[$fromColumn]->setColumnIndex($toColumn); + $this->columns[$toColumn] = $this->columns[$fromColumn]; + $this->columns[$toColumn]->setParent($this); + unset($this->columns[$fromColumn]); + + ksort($this->columns); + } + + return $this; + } + + /** + * Test if cell value is in the defined set of values. + * + * @param mixed $cellValue + * @param mixed[] $dataSet + * + * @return bool + */ + private static function filterTestInSimpleDataSet($cellValue, $dataSet) + { + $dataSetValues = $dataSet['filterValues']; + $blanks = $dataSet['blanks']; + if (($cellValue == '') || ($cellValue === null)) { + return $blanks; + } + + return in_array($cellValue, $dataSetValues); + } + + /** + * Test if cell value is in the defined set of Excel date values. + * + * @param mixed $cellValue + * @param mixed[] $dataSet + * + * @return bool + */ + private static function filterTestInDateGroupSet($cellValue, $dataSet) + { + $dateSet = $dataSet['filterValues']; + $blanks = $dataSet['blanks']; + if (($cellValue == '') || ($cellValue === null)) { + return $blanks; + } + $timeZone = new DateTimeZone('UTC'); + + if (is_numeric($cellValue)) { + $dateTime = Date::excelToDateTimeObject((float) $cellValue, $timeZone); + $cellValue = (float) $cellValue; + if ($cellValue < 1) { + // Just the time part + $dtVal = $dateTime->format('His'); + $dateSet = $dateSet['time']; + } elseif ($cellValue == floor($cellValue)) { + // Just the date part + $dtVal = $dateTime->format('Ymd'); + $dateSet = $dateSet['date']; + } else { + // date and time parts + $dtVal = $dateTime->format('YmdHis'); + $dateSet = $dateSet['dateTime']; + } + foreach ($dateSet as $dateValue) { + // Use of substr to extract value at the appropriate group level + if (substr($dtVal, 0, strlen($dateValue)) == $dateValue) { + return true; + } + } + } + + return false; + } + + /** + * Test if cell value is within a set of values defined by a ruleset. + * + * @param mixed $cellValue + * @param mixed[] $ruleSet + * + * @return bool + */ + private static function filterTestInCustomDataSet($cellValue, $ruleSet) + { + /** @var array[] */ + $dataSet = $ruleSet['filterRules']; + $join = $ruleSet['join']; + $customRuleForBlanks = $ruleSet['customRuleForBlanks'] ?? false; + + if (!$customRuleForBlanks) { + // Blank cells are always ignored, so return a FALSE + if (($cellValue == '') || ($cellValue === null)) { + return false; + } + } + $returnVal = ($join == AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_AND); + foreach ($dataSet as $rule) { + /** @var string */ + $ruleValue = $rule['value']; + /** @var string */ + $ruleOperator = $rule['operator']; + /** @var string */ + $cellValueString = $cellValue; + $retVal = false; + + if (is_numeric($ruleValue)) { + // Numeric values are tested using the appropriate operator + $numericTest = is_numeric($cellValue); + switch ($ruleOperator) { + case Rule::AUTOFILTER_COLUMN_RULE_EQUAL: + $retVal = $numericTest && ($cellValue == $ruleValue); + + break; + case Rule::AUTOFILTER_COLUMN_RULE_NOTEQUAL: + $retVal = !$numericTest || ($cellValue != $ruleValue); + + break; + case Rule::AUTOFILTER_COLUMN_RULE_GREATERTHAN: + $retVal = $numericTest && ($cellValue > $ruleValue); + + break; + case Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL: + $retVal = $numericTest && ($cellValue >= $ruleValue); + + break; + case Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN: + $retVal = $numericTest && ($cellValue < $ruleValue); + + break; + case Rule::AUTOFILTER_COLUMN_RULE_LESSTHANOREQUAL: + $retVal = $numericTest && ($cellValue <= $ruleValue); + + break; + } + } elseif ($ruleValue == '') { + switch ($ruleOperator) { + case Rule::AUTOFILTER_COLUMN_RULE_EQUAL: + $retVal = (($cellValue == '') || ($cellValue === null)); + + break; + case Rule::AUTOFILTER_COLUMN_RULE_NOTEQUAL: + $retVal = (($cellValue != '') && ($cellValue !== null)); + + break; + default: + $retVal = true; + + break; + } + } else { + // String values are always tested for equality, factoring in for wildcards (hence a regexp test) + switch ($ruleOperator) { + case Rule::AUTOFILTER_COLUMN_RULE_EQUAL: + $retVal = (bool) preg_match('/^' . $ruleValue . '$/i', $cellValueString); + + break; + case Rule::AUTOFILTER_COLUMN_RULE_NOTEQUAL: + $retVal = !((bool) preg_match('/^' . $ruleValue . '$/i', $cellValueString)); + + break; + case Rule::AUTOFILTER_COLUMN_RULE_GREATERTHAN: + $retVal = strcasecmp($cellValueString, $ruleValue) > 0; + + break; + case Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL: + $retVal = strcasecmp($cellValueString, $ruleValue) >= 0; + + break; + case Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN: + $retVal = strcasecmp($cellValueString, $ruleValue) < 0; + + break; + case Rule::AUTOFILTER_COLUMN_RULE_LESSTHANOREQUAL: + $retVal = strcasecmp($cellValueString, $ruleValue) <= 0; + + break; + } + } + // If there are multiple conditions, then we need to test both using the appropriate join operator + switch ($join) { + case AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_OR: + $returnVal = $returnVal || $retVal; + // Break as soon as we have a TRUE match for OR joins, + // to avoid unnecessary additional code execution + if ($returnVal) { + return $returnVal; + } + + break; + case AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_AND: + $returnVal = $returnVal && $retVal; + + break; + } + } + + return $returnVal; + } + + /** + * Test if cell date value is matches a set of values defined by a set of months. + * + * @param mixed $cellValue + * @param mixed[] $monthSet + * + * @return bool + */ + private static function filterTestInPeriodDateSet($cellValue, $monthSet) + { + // Blank cells are always ignored, so return a FALSE + if (($cellValue == '') || ($cellValue === null)) { + return false; + } + + if (is_numeric($cellValue)) { + $dateObject = Date::excelToDateTimeObject((float) $cellValue, new DateTimeZone('UTC')); + $dateValue = (int) $dateObject->format('m'); + if (in_array($dateValue, $monthSet)) { + return true; + } + } + + return false; + } + + private static function makeDateObject(int $year, int $month, int $day, int $hour = 0, int $minute = 0, int $second = 0): DateTime + { + $baseDate = new DateTime(); + $baseDate->setDate($year, $month, $day); + $baseDate->setTime($hour, $minute, $second); + + return $baseDate; + } + + private const DATE_FUNCTIONS = [ + Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTMONTH => 'dynamicLastMonth', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTQUARTER => 'dynamicLastQuarter', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTWEEK => 'dynamicLastWeek', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTYEAR => 'dynamicLastYear', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTMONTH => 'dynamicNextMonth', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTQUARTER => 'dynamicNextQuarter', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTWEEK => 'dynamicNextWeek', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTYEAR => 'dynamicNextYear', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISMONTH => 'dynamicThisMonth', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISQUARTER => 'dynamicThisQuarter', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISWEEK => 'dynamicThisWeek', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISYEAR => 'dynamicThisYear', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_TODAY => 'dynamicToday', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_TOMORROW => 'dynamicTomorrow', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_YEARTODATE => 'dynamicYearToDate', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_YESTERDAY => 'dynamicYesterday', + ]; + + private static function dynamicLastMonth(): array + { + $maxval = new DateTime(); + $year = (int) $maxval->format('Y'); + $month = (int) $maxval->format('m'); + $maxval->setDate($year, $month, 1); + $maxval->setTime(0, 0, 0); + $val = clone $maxval; + $val->modify('-1 month'); + + return [$val, $maxval]; + } + + private static function firstDayOfQuarter(): DateTime + { + $val = new DateTime(); + $year = (int) $val->format('Y'); + $month = (int) $val->format('m'); + $month = 3 * intdiv($month - 1, 3) + 1; + $val->setDate($year, $month, 1); + $val->setTime(0, 0, 0); + + return $val; + } + + private static function dynamicLastQuarter(): array + { + $maxval = self::firstDayOfQuarter(); + $val = clone $maxval; + $val->modify('-3 months'); + + return [$val, $maxval]; + } + + private static function dynamicLastWeek(): array + { + $val = new DateTime(); + $val->setTime(0, 0, 0); + $dayOfWeek = (int) $val->format('w'); // Sunday is 0 + $subtract = $dayOfWeek + 7; // revert to prior Sunday + $val->modify("-$subtract days"); + $maxval = clone $val; + $maxval->modify('+7 days'); + + return [$val, $maxval]; + } + + private static function dynamicLastYear(): array + { + $val = new DateTime(); + $year = (int) $val->format('Y'); + $val = self::makeDateObject($year - 1, 1, 1); + $maxval = self::makeDateObject($year, 1, 1); + + return [$val, $maxval]; + } + + private static function dynamicNextMonth(): array + { + $val = new DateTime(); + $year = (int) $val->format('Y'); + $month = (int) $val->format('m'); + $val->setDate($year, $month, 1); + $val->setTime(0, 0, 0); + $val->modify('+1 month'); + $maxval = clone $val; + $maxval->modify('+1 month'); + + return [$val, $maxval]; + } + + private static function dynamicNextQuarter(): array + { + $val = self::firstDayOfQuarter(); + $val->modify('+3 months'); + $maxval = clone $val; + $maxval->modify('+3 months'); + + return [$val, $maxval]; + } + + private static function dynamicNextWeek(): array + { + $val = new DateTime(); + $val->setTime(0, 0, 0); + $dayOfWeek = (int) $val->format('w'); // Sunday is 0 + $add = 7 - $dayOfWeek; // move to next Sunday + $val->modify("+$add days"); + $maxval = clone $val; + $maxval->modify('+7 days'); + + return [$val, $maxval]; + } + + private static function dynamicNextYear(): array + { + $val = new DateTime(); + $year = (int) $val->format('Y'); + $val = self::makeDateObject($year + 1, 1, 1); + $maxval = self::makeDateObject($year + 2, 1, 1); + + return [$val, $maxval]; + } + + private static function dynamicThisMonth(): array + { + $baseDate = new DateTime(); + $baseDate->setTime(0, 0, 0); + $year = (int) $baseDate->format('Y'); + $month = (int) $baseDate->format('m'); + $val = self::makeDateObject($year, $month, 1); + $maxval = clone $val; + $maxval->modify('+1 month'); + + return [$val, $maxval]; + } + + private static function dynamicThisQuarter(): array + { + $val = self::firstDayOfQuarter(); + $maxval = clone $val; + $maxval->modify('+3 months'); + + return [$val, $maxval]; + } + + private static function dynamicThisWeek(): array + { + $val = new DateTime(); + $val->setTime(0, 0, 0); + $dayOfWeek = (int) $val->format('w'); // Sunday is 0 + $subtract = $dayOfWeek; // revert to Sunday + $val->modify("-$subtract days"); + $maxval = clone $val; + $maxval->modify('+7 days'); + + return [$val, $maxval]; + } + + private static function dynamicThisYear(): array + { + $val = new DateTime(); + $year = (int) $val->format('Y'); + $val = self::makeDateObject($year, 1, 1); + $maxval = self::makeDateObject($year + 1, 1, 1); + + return [$val, $maxval]; + } + + private static function dynamicToday(): array + { + $val = new DateTime(); + $val->setTime(0, 0, 0); + $maxval = clone $val; + $maxval->modify('+1 day'); + + return [$val, $maxval]; + } + + private static function dynamicTomorrow(): array + { + $val = new DateTime(); + $val->setTime(0, 0, 0); + $val->modify('+1 day'); + $maxval = clone $val; + $maxval->modify('+1 day'); + + return [$val, $maxval]; + } + + private static function dynamicYearToDate(): array + { + $maxval = new DateTime(); + $maxval->setTime(0, 0, 0); + $val = self::makeDateObject((int) $maxval->format('Y'), 1, 1); + $maxval->modify('+1 day'); + + return [$val, $maxval]; + } + + private static function dynamicYesterday(): array + { + $maxval = new DateTime(); + $maxval->setTime(0, 0, 0); + $val = clone $maxval; + $val->modify('-1 day'); + + return [$val, $maxval]; + } + + /** + * Convert a dynamic rule daterange to a custom filter range expression for ease of calculation. + * + * @param string $dynamicRuleType + * + * @return mixed[] + */ + private function dynamicFilterDateRange($dynamicRuleType, AutoFilter\Column &$filterColumn) + { + $ruleValues = []; + $callBack = [__CLASS__, self::DATE_FUNCTIONS[$dynamicRuleType]]; // What if not found? + // Calculate start/end dates for the required date range based on current date + // Val is lowest permitted value. + // Maxval is greater than highest permitted value + $val = $maxval = 0; + if (is_callable($callBack)) { + [$val, $maxval] = $callBack(); + } + $val = Date::dateTimeToExcel($val); + $maxval = Date::dateTimeToExcel($maxval); + + // Set the filter column rule attributes ready for writing + $filterColumn->setAttributes(['val' => $val, 'maxVal' => $maxval]); + + // Set the rules for identifying rows for hide/show + $ruleValues[] = ['operator' => Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL, 'value' => $val]; + $ruleValues[] = ['operator' => Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN, 'value' => $maxval]; + + return ['method' => 'filterTestInCustomDataSet', 'arguments' => ['filterRules' => $ruleValues, 'join' => AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_AND]]; + } + + /** + * Apply the AutoFilter rules to the AutoFilter Range. + * + * @param string $columnID + * @param int $startRow + * @param int $endRow + * @param ?string $ruleType + * @param mixed $ruleValue + * + * @return mixed + */ + private function calculateTopTenValue($columnID, $startRow, $endRow, $ruleType, $ruleValue) + { + $range = $columnID . $startRow . ':' . $columnID . $endRow; + $retVal = null; + if ($this->workSheet !== null) { + $dataValues = Functions::flattenArray($this->workSheet->rangeToArray($range, null, true, false)); + $dataValues = array_filter($dataValues); + + if ($ruleType == Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP) { + rsort($dataValues); + } else { + sort($dataValues); + } + + $slice = array_slice($dataValues, 0, $ruleValue); + + $retVal = array_pop($slice); + } + + return $retVal; + } + + /** + * Apply the AutoFilter rules to the AutoFilter Range. + * + * @return $this + */ + public function showHideRows() + { + if ($this->workSheet === null) { + return $this; + } + [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range); + + // The heading row should always be visible + $this->workSheet->getRowDimension($rangeStart[1])->setVisible(true); + + $columnFilterTests = []; + foreach ($this->columns as $columnID => $filterColumn) { + $rules = $filterColumn->getRules(); + switch ($filterColumn->getFilterType()) { + case AutoFilter\Column::AUTOFILTER_FILTERTYPE_FILTER: + $ruleType = null; + $ruleValues = []; + // Build a list of the filter value selections + foreach ($rules as $rule) { + $ruleType = $rule->getRuleType(); + $ruleValues[] = $rule->getValue(); + } + // Test if we want to include blanks in our filter criteria + $blanks = false; + $ruleDataSet = array_filter($ruleValues); + if (count($ruleValues) != count($ruleDataSet)) { + $blanks = true; + } + if ($ruleType == Rule::AUTOFILTER_RULETYPE_FILTER) { + // Filter on absolute values + $columnFilterTests[$columnID] = [ + 'method' => 'filterTestInSimpleDataSet', + 'arguments' => ['filterValues' => $ruleDataSet, 'blanks' => $blanks], + ]; + } else { + // Filter on date group values + $arguments = [ + 'date' => [], + 'time' => [], + 'dateTime' => [], + ]; + foreach ($ruleDataSet as $ruleValue) { + if (!is_array($ruleValue)) { + continue; + } + $date = $time = ''; + if ( + (isset($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_YEAR])) && + ($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_YEAR] !== '') + ) { + $date .= sprintf('%04d', $ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_YEAR]); + } + if ( + (isset($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_MONTH])) && + ($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_MONTH] != '') + ) { + $date .= sprintf('%02d', $ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_MONTH]); + } + if ( + (isset($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_DAY])) && + ($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_DAY] !== '') + ) { + $date .= sprintf('%02d', $ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_DAY]); + } + if ( + (isset($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_HOUR])) && + ($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_HOUR] !== '') + ) { + $time .= sprintf('%02d', $ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_HOUR]); + } + if ( + (isset($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_MINUTE])) && + ($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_MINUTE] !== '') + ) { + $time .= sprintf('%02d', $ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_MINUTE]); + } + if ( + (isset($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_SECOND])) && + ($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_SECOND] !== '') + ) { + $time .= sprintf('%02d', $ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_SECOND]); + } + $dateTime = $date . $time; + $arguments['date'][] = $date; + $arguments['time'][] = $time; + $arguments['dateTime'][] = $dateTime; + } + // Remove empty elements + $arguments['date'] = array_filter($arguments['date']); + $arguments['time'] = array_filter($arguments['time']); + $arguments['dateTime'] = array_filter($arguments['dateTime']); + $columnFilterTests[$columnID] = [ + 'method' => 'filterTestInDateGroupSet', + 'arguments' => ['filterValues' => $arguments, 'blanks' => $blanks], + ]; + } + + break; + case AutoFilter\Column::AUTOFILTER_FILTERTYPE_CUSTOMFILTER: + $customRuleForBlanks = true; + $ruleValues = []; + // Build a list of the filter value selections + foreach ($rules as $rule) { + $ruleValue = $rule->getValue(); + if (!is_array($ruleValue) && !is_numeric($ruleValue)) { + // Convert to a regexp allowing for regexp reserved characters, wildcards and escaped wildcards + $ruleValue = WildcardMatch::wildcard($ruleValue); + if (trim($ruleValue) == '') { + $customRuleForBlanks = true; + $ruleValue = trim($ruleValue); + } + } + $ruleValues[] = ['operator' => $rule->getOperator(), 'value' => $ruleValue]; + } + $join = $filterColumn->getJoin(); + $columnFilterTests[$columnID] = [ + 'method' => 'filterTestInCustomDataSet', + 'arguments' => ['filterRules' => $ruleValues, 'join' => $join, 'customRuleForBlanks' => $customRuleForBlanks], + ]; + + break; + case AutoFilter\Column::AUTOFILTER_FILTERTYPE_DYNAMICFILTER: + $ruleValues = []; + foreach ($rules as $rule) { + // We should only ever have one Dynamic Filter Rule anyway + $dynamicRuleType = $rule->getGrouping(); + if ( + ($dynamicRuleType == Rule::AUTOFILTER_RULETYPE_DYNAMIC_ABOVEAVERAGE) || + ($dynamicRuleType == Rule::AUTOFILTER_RULETYPE_DYNAMIC_BELOWAVERAGE) + ) { + // Number (Average) based + // Calculate the average + $averageFormula = '=AVERAGE(' . $columnID . ($rangeStart[1] + 1) . ':' . $columnID . $rangeEnd[1] . ')'; + $spreadsheet = ($this->workSheet === null) ? null : $this->workSheet->getParent(); + $average = Calculation::getInstance($spreadsheet)->calculateFormula($averageFormula, null, $this->workSheet->getCell('A1')); + // Set above/below rule based on greaterThan or LessTan + $operator = ($dynamicRuleType === Rule::AUTOFILTER_RULETYPE_DYNAMIC_ABOVEAVERAGE) + ? Rule::AUTOFILTER_COLUMN_RULE_GREATERTHAN + : Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN; + $ruleValues[] = [ + 'operator' => $operator, + 'value' => $average, + ]; + $columnFilterTests[$columnID] = [ + 'method' => 'filterTestInCustomDataSet', + 'arguments' => ['filterRules' => $ruleValues, 'join' => AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_OR], + ]; + } else { + // Date based + if ($dynamicRuleType[0] == 'M' || $dynamicRuleType[0] == 'Q') { + $periodType = ''; + $period = 0; + // Month or Quarter + sscanf($dynamicRuleType, '%[A-Z]%d', $periodType, $period); + if ($periodType == 'M') { + $ruleValues = [$period]; + } else { + --$period; + $periodEnd = (1 + $period) * 3; + $periodStart = 1 + $period * 3; + $ruleValues = range($periodStart, $periodEnd); + } + $columnFilterTests[$columnID] = [ + 'method' => 'filterTestInPeriodDateSet', + 'arguments' => $ruleValues, + ]; + $filterColumn->setAttributes([]); + } else { + // Date Range + $columnFilterTests[$columnID] = $this->dynamicFilterDateRange($dynamicRuleType, $filterColumn); + + break; + } + } + } + + break; + case AutoFilter\Column::AUTOFILTER_FILTERTYPE_TOPTENFILTER: + $ruleValues = []; + $dataRowCount = $rangeEnd[1] - $rangeStart[1]; + $toptenRuleType = null; + $ruleValue = 0; + $ruleOperator = null; + foreach ($rules as $rule) { + // We should only ever have one Dynamic Filter Rule anyway + $toptenRuleType = $rule->getGrouping(); + $ruleValue = $rule->getValue(); + $ruleOperator = $rule->getOperator(); + } + if (is_numeric($ruleValue) && $ruleOperator === Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT) { + $ruleValue = floor((float) $ruleValue * ($dataRowCount / 100)); + } + if (!is_array($ruleValue) && $ruleValue < 1) { + $ruleValue = 1; + } + if (!is_array($ruleValue) && $ruleValue > 500) { + $ruleValue = 500; + } + + $maxVal = $this->calculateTopTenValue($columnID, $rangeStart[1] + 1, (int) $rangeEnd[1], $toptenRuleType, $ruleValue); + + $operator = ($toptenRuleType == Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP) + ? Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL + : Rule::AUTOFILTER_COLUMN_RULE_LESSTHANOREQUAL; + $ruleValues[] = ['operator' => $operator, 'value' => $maxVal]; + $columnFilterTests[$columnID] = [ + 'method' => 'filterTestInCustomDataSet', + 'arguments' => ['filterRules' => $ruleValues, 'join' => AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_OR], + ]; + $filterColumn->setAttributes(['maxVal' => $maxVal]); + + break; + } + } + + // Execute the column tests for each row in the autoFilter range to determine show/hide, + for ($row = $rangeStart[1] + 1; $row <= $rangeEnd[1]; ++$row) { + $result = true; + foreach ($columnFilterTests as $columnID => $columnFilterTest) { + $cellValue = $this->workSheet->getCell($columnID . $row)->getCalculatedValue(); + // Execute the filter test + $result = // $result && // phpstan says $result is always true here + // @phpstan-ignore-next-line + call_user_func_array([self::class, $columnFilterTest['method']], [$cellValue, $columnFilterTest['arguments']]); + // If filter test has resulted in FALSE, exit the loop straightaway rather than running any more tests + if (!$result) { + break; + } + } + // Set show/hide for the row based on the result of the autoFilter result + $this->workSheet->getRowDimension((int) $row)->setVisible($result); + } + + return $this; + } + + /** + * Implement PHP __clone to create a deep clone, not just a shallow copy. + */ + public function __clone() + { + $vars = get_object_vars($this); + foreach ($vars as $key => $value) { + if (is_object($value)) { + if ($key === 'workSheet') { + // Detach from worksheet + $this->{$key} = null; + } else { + $this->{$key} = clone $value; + } + } elseif ((is_array($value)) && ($key == 'columns')) { + // The columns array of \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\AutoFilter objects + $this->{$key} = []; + foreach ($value as $k => $v) { + $this->{$key}[$k] = clone $v; + // attach the new cloned Column to this new cloned Autofilter object + $this->{$key}[$k]->setParent($this); + } + } else { + $this->{$key} = $value; + } + } + } + + /** + * toString method replicates previous behavior by returning the range if object is + * referenced as a property of its parent. + */ + public function __toString() + { + return (string) $this->range; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter/Column.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter/Column.php new file mode 100644 index 0000000..ef741ee --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter/Column.php @@ -0,0 +1,387 @@ +columnIndex = $column; + $this->parent = $parent; + } + + /** + * Get AutoFilter column index as string eg: 'A'. + * + * @return string + */ + public function getColumnIndex() + { + return $this->columnIndex; + } + + /** + * Set AutoFilter column index as string eg: 'A'. + * + * @param string $column Column (e.g. A) + * + * @return $this + */ + public function setColumnIndex($column) + { + // Uppercase coordinate + $column = strtoupper($column); + if ($this->parent !== null) { + $this->parent->testColumnInRange($column); + } + + $this->columnIndex = $column; + + return $this; + } + + /** + * Get this Column's AutoFilter Parent. + * + * @return null|AutoFilter + */ + public function getParent() + { + return $this->parent; + } + + /** + * Set this Column's AutoFilter Parent. + * + * @return $this + */ + public function setParent(?AutoFilter $parent = null) + { + $this->parent = $parent; + + return $this; + } + + /** + * Get AutoFilter Type. + * + * @return string + */ + public function getFilterType() + { + return $this->filterType; + } + + /** + * Set AutoFilter Type. + * + * @param string $filterType + * + * @return $this + */ + public function setFilterType($filterType) + { + if (!in_array($filterType, self::$filterTypes)) { + throw new PhpSpreadsheetException('Invalid filter type for column AutoFilter.'); + } + if ($filterType === self::AUTOFILTER_FILTERTYPE_CUSTOMFILTER && count($this->ruleset) > 2) { + throw new PhpSpreadsheetException('No more than 2 rules are allowed in a Custom Filter'); + } + + $this->filterType = $filterType; + + return $this; + } + + /** + * Get AutoFilter Multiple Rules And/Or Join. + * + * @return string + */ + public function getJoin() + { + return $this->join; + } + + /** + * Set AutoFilter Multiple Rules And/Or. + * + * @param string $join And/Or + * + * @return $this + */ + public function setJoin($join) + { + // Lowercase And/Or + $join = strtolower($join); + if (!in_array($join, self::$ruleJoins)) { + throw new PhpSpreadsheetException('Invalid rule connection for column AutoFilter.'); + } + + $this->join = $join; + + return $this; + } + + /** + * Set AutoFilter Attributes. + * + * @param mixed[] $attributes + * + * @return $this + */ + public function setAttributes($attributes) + { + $this->attributes = $attributes; + + return $this; + } + + /** + * Set An AutoFilter Attribute. + * + * @param string $name Attribute Name + * @param string $value Attribute Value + * + * @return $this + */ + public function setAttribute($name, $value) + { + $this->attributes[$name] = $value; + + return $this; + } + + /** + * Get AutoFilter Column Attributes. + * + * @return int[]|string[] + */ + public function getAttributes() + { + return $this->attributes; + } + + /** + * Get specific AutoFilter Column Attribute. + * + * @param string $name Attribute Name + * + * @return null|int|string + */ + public function getAttribute($name) + { + if (isset($this->attributes[$name])) { + return $this->attributes[$name]; + } + + return null; + } + + public function ruleCount(): int + { + return count($this->ruleset); + } + + /** + * Get all AutoFilter Column Rules. + * + * @return Column\Rule[] + */ + public function getRules() + { + return $this->ruleset; + } + + /** + * Get a specified AutoFilter Column Rule. + * + * @param int $index Rule index in the ruleset array + * + * @return Column\Rule + */ + public function getRule($index) + { + if (!isset($this->ruleset[$index])) { + $this->ruleset[$index] = new Column\Rule($this); + } + + return $this->ruleset[$index]; + } + + /** + * Create a new AutoFilter Column Rule in the ruleset. + * + * @return Column\Rule + */ + public function createRule() + { + if ($this->filterType === self::AUTOFILTER_FILTERTYPE_CUSTOMFILTER && count($this->ruleset) >= 2) { + throw new PhpSpreadsheetException('No more than 2 rules are allowed in a Custom Filter'); + } + $this->ruleset[] = new Column\Rule($this); + + return end($this->ruleset); + } + + /** + * Add a new AutoFilter Column Rule to the ruleset. + * + * @return $this + */ + public function addRule(Column\Rule $rule) + { + $rule->setParent($this); + $this->ruleset[] = $rule; + + return $this; + } + + /** + * Delete a specified AutoFilter Column Rule + * If the number of rules is reduced to 1, then we reset And/Or logic to Or. + * + * @param int $index Rule index in the ruleset array + * + * @return $this + */ + public function deleteRule($index) + { + if (isset($this->ruleset[$index])) { + unset($this->ruleset[$index]); + // If we've just deleted down to a single rule, then reset And/Or joining to Or + if (count($this->ruleset) <= 1) { + $this->setJoin(self::AUTOFILTER_COLUMN_JOIN_OR); + } + } + + return $this; + } + + /** + * Delete all AutoFilter Column Rules. + * + * @return $this + */ + public function clearRules() + { + $this->ruleset = []; + $this->setJoin(self::AUTOFILTER_COLUMN_JOIN_OR); + + return $this; + } + + /** + * Implement PHP __clone to create a deep clone, not just a shallow copy. + */ + public function __clone() + { + $vars = get_object_vars($this); + foreach ($vars as $key => $value) { + if ($key === 'parent') { + // Detach from autofilter parent + $this->parent = null; + } elseif ($key === 'ruleset') { + // The columns array of \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\AutoFilter objects + $this->ruleset = []; + foreach ($value as $k => $v) { + $cloned = clone $v; + $cloned->setParent($this); // attach the new cloned Rule to this new cloned Autofilter Cloned object + $this->ruleset[$k] = $cloned; + } + } else { + $this->$key = $value; + } + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter/Column/Rule.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter/Column/Rule.php new file mode 100644 index 0000000..525b06f --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter/Column/Rule.php @@ -0,0 +1,413 @@ +parent = $parent; + } + + /** + * Get AutoFilter Rule Type. + * + * @return string + */ + public function getRuleType() + { + return $this->ruleType; + } + + /** + * Set AutoFilter Rule Type. + * + * @param string $ruleType see self::AUTOFILTER_RULETYPE_* + * + * @return $this + */ + public function setRuleType($ruleType) + { + if (!in_array($ruleType, self::RULE_TYPES)) { + throw new PhpSpreadsheetException('Invalid rule type for column AutoFilter Rule.'); + } + + $this->ruleType = $ruleType; + + return $this; + } + + /** + * Get AutoFilter Rule Value. + * + * @return int|int[]|string|string[] + */ + public function getValue() + { + return $this->value; + } + + /** + * Set AutoFilter Rule Value. + * + * @param int|int[]|string|string[] $value + * + * @return $this + */ + public function setValue($value) + { + if (is_array($value)) { + $grouping = -1; + foreach ($value as $key => $v) { + // Validate array entries + if (!in_array($key, self::DATE_TIME_GROUPS)) { + // Remove any invalid entries from the value array + unset($value[$key]); + } else { + // Work out what the dateTime grouping will be + $grouping = max($grouping, array_search($key, self::DATE_TIME_GROUPS)); + } + } + if (count($value) == 0) { + throw new PhpSpreadsheetException('Invalid rule value for column AutoFilter Rule.'); + } + // Set the dateTime grouping that we've anticipated + $this->setGrouping(self::DATE_TIME_GROUPS[$grouping]); + } + $this->value = $value; + + return $this; + } + + /** + * Get AutoFilter Rule Operator. + * + * @return string + */ + public function getOperator() + { + return $this->operator; + } + + /** + * Set AutoFilter Rule Operator. + * + * @param string $operator see self::AUTOFILTER_COLUMN_RULE_* + * + * @return $this + */ + public function setOperator($operator) + { + if (empty($operator)) { + $operator = self::AUTOFILTER_COLUMN_RULE_EQUAL; + } + if ( + (!in_array($operator, self::OPERATORS)) && + (!in_array($operator, self::TOP_TEN_VALUE)) + ) { + throw new PhpSpreadsheetException('Invalid operator for column AutoFilter Rule.'); + } + $this->operator = $operator; + + return $this; + } + + /** + * Get AutoFilter Rule Grouping. + * + * @return string + */ + public function getGrouping() + { + return $this->grouping; + } + + /** + * Set AutoFilter Rule Grouping. + * + * @param string $grouping + * + * @return $this + */ + public function setGrouping($grouping) + { + if ( + ($grouping !== null) && + (!in_array($grouping, self::DATE_TIME_GROUPS)) && + (!in_array($grouping, self::DYNAMIC_TYPES)) && + (!in_array($grouping, self::TOP_TEN_TYPE)) + ) { + throw new PhpSpreadsheetException('Invalid grouping for column AutoFilter Rule.'); + } + $this->grouping = $grouping; + + return $this; + } + + /** + * Set AutoFilter Rule. + * + * @param string $operator see self::AUTOFILTER_COLUMN_RULE_* + * @param int|int[]|string|string[] $value + * @param string $grouping + * + * @return $this + */ + public function setRule($operator, $value, $grouping = null) + { + $this->setOperator($operator); + $this->setValue($value); + // Only set grouping if it's been passed in as a user-supplied argument, + // otherwise we're calculating it when we setValue() and don't want to overwrite that + // If the user supplies an argumnet for grouping, then on their own head be it + if ($grouping !== null) { + $this->setGrouping($grouping); + } + + return $this; + } + + /** + * Get this Rule's AutoFilter Column Parent. + * + * @return ?Column + */ + public function getParent() + { + return $this->parent; + } + + /** + * Set this Rule's AutoFilter Column Parent. + * + * @return $this + */ + public function setParent(?Column $parent = null) + { + $this->parent = $parent; + + return $this; + } + + /** + * Implement PHP __clone to create a deep clone, not just a shallow copy. + */ + public function __clone() + { + $vars = get_object_vars($this); + foreach ($vars as $key => $value) { + if (is_object($value)) { + if ($key == 'parent') { // this is only object + // Detach from autofilter column parent + $this->$key = null; + } + } else { + $this->$key = $value; + } + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/BaseDrawing.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/BaseDrawing.php new file mode 100644 index 0000000..b8cfb01 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/BaseDrawing.php @@ -0,0 +1,529 @@ +name = ''; + $this->description = ''; + $this->worksheet = null; + $this->coordinates = 'A1'; + $this->offsetX = 0; + $this->offsetY = 0; + $this->width = 0; + $this->height = 0; + $this->resizeProportional = true; + $this->rotation = 0; + $this->shadow = new Drawing\Shadow(); + + // Set image index + ++self::$imageCounter; + $this->imageIndex = self::$imageCounter; + } + + /** + * Get image index. + * + * @return int + */ + public function getImageIndex() + { + return $this->imageIndex; + } + + /** + * Get Name. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Set Name. + * + * @param string $name + * + * @return $this + */ + public function setName($name) + { + $this->name = $name; + + return $this; + } + + /** + * Get Description. + * + * @return string + */ + public function getDescription() + { + return $this->description; + } + + /** + * Set Description. + * + * @param string $description + * + * @return $this + */ + public function setDescription($description) + { + $this->description = $description; + + return $this; + } + + /** + * Get Worksheet. + * + * @return null|Worksheet + */ + public function getWorksheet() + { + return $this->worksheet; + } + + /** + * Set Worksheet. + * + * @param bool $overrideOld If a Worksheet has already been assigned, overwrite it and remove image from old Worksheet? + * + * @return $this + */ + public function setWorksheet(?Worksheet $worksheet = null, $overrideOld = false) + { + if ($this->worksheet === null) { + // Add drawing to \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet + $this->worksheet = $worksheet; + $this->worksheet->getCell($this->coordinates); + $this->worksheet->getDrawingCollection()->append($this); + } else { + if ($overrideOld) { + // Remove drawing from old \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet + $iterator = $this->worksheet->getDrawingCollection()->getIterator(); + + while ($iterator->valid()) { + if ($iterator->current()->getHashCode() === $this->getHashCode()) { + $this->worksheet->getDrawingCollection()->offsetUnset($iterator->key()); + $this->worksheet = null; + + break; + } + } + + // Set new \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet + $this->setWorksheet($worksheet); + } else { + throw new PhpSpreadsheetException('A Worksheet has already been assigned. Drawings can only exist on one \\PhpOffice\\PhpSpreadsheet\\Worksheet.'); + } + } + + return $this; + } + + /** + * Get Coordinates. + * + * @return string + */ + public function getCoordinates() + { + return $this->coordinates; + } + + /** + * Set Coordinates. + * + * @param string $coordinates eg: 'A1' + * + * @return $this + */ + public function setCoordinates($coordinates) + { + $this->coordinates = $coordinates; + + return $this; + } + + /** + * Get OffsetX. + * + * @return int + */ + public function getOffsetX() + { + return $this->offsetX; + } + + /** + * Set OffsetX. + * + * @param int $offsetX + * + * @return $this + */ + public function setOffsetX($offsetX) + { + $this->offsetX = $offsetX; + + return $this; + } + + /** + * Get OffsetY. + * + * @return int + */ + public function getOffsetY() + { + return $this->offsetY; + } + + /** + * Set OffsetY. + * + * @param int $offsetY + * + * @return $this + */ + public function setOffsetY($offsetY) + { + $this->offsetY = $offsetY; + + return $this; + } + + /** + * Get Width. + * + * @return int + */ + public function getWidth() + { + return $this->width; + } + + /** + * Set Width. + * + * @param int $width + * + * @return $this + */ + public function setWidth($width) + { + // Resize proportional? + if ($this->resizeProportional && $width != 0) { + $ratio = $this->height / ($this->width != 0 ? $this->width : 1); + $this->height = (int) round($ratio * $width); + } + + // Set width + $this->width = $width; + + return $this; + } + + /** + * Get Height. + * + * @return int + */ + public function getHeight() + { + return $this->height; + } + + /** + * Set Height. + * + * @param int $height + * + * @return $this + */ + public function setHeight($height) + { + // Resize proportional? + if ($this->resizeProportional && $height != 0) { + $ratio = $this->width / ($this->height != 0 ? $this->height : 1); + $this->width = (int) round($ratio * $height); + } + + // Set height + $this->height = $height; + + return $this; + } + + /** + * Set width and height with proportional resize. + * + * Example: + * + * $objDrawing->setResizeProportional(true); + * $objDrawing->setWidthAndHeight(160,120); + * + * + * @param int $width + * @param int $height + * + * @return $this + * + * @author Vincent@luo MSN:kele_100@hotmail.com + */ + public function setWidthAndHeight($width, $height) + { + $xratio = $width / ($this->width != 0 ? $this->width : 1); + $yratio = $height / ($this->height != 0 ? $this->height : 1); + if ($this->resizeProportional && !($width == 0 || $height == 0)) { + if (($xratio * $this->height) < $height) { + $this->height = (int) ceil($xratio * $this->height); + $this->width = $width; + } else { + $this->width = (int) ceil($yratio * $this->width); + $this->height = $height; + } + } else { + $this->width = $width; + $this->height = $height; + } + + return $this; + } + + /** + * Get ResizeProportional. + * + * @return bool + */ + public function getResizeProportional() + { + return $this->resizeProportional; + } + + /** + * Set ResizeProportional. + * + * @param bool $resizeProportional + * + * @return $this + */ + public function setResizeProportional($resizeProportional) + { + $this->resizeProportional = $resizeProportional; + + return $this; + } + + /** + * Get Rotation. + * + * @return int + */ + public function getRotation() + { + return $this->rotation; + } + + /** + * Set Rotation. + * + * @param int $rotation + * + * @return $this + */ + public function setRotation($rotation) + { + $this->rotation = $rotation; + + return $this; + } + + /** + * Get Shadow. + * + * @return Drawing\Shadow + */ + public function getShadow() + { + return $this->shadow; + } + + /** + * Set Shadow. + * + * @return $this + */ + public function setShadow(?Drawing\Shadow $shadow = null) + { + $this->shadow = $shadow; + + return $this; + } + + /** + * Get hash code. + * + * @return string Hash code + */ + public function getHashCode() + { + return md5( + $this->name . + $this->description . + $this->worksheet->getHashCode() . + $this->coordinates . + $this->offsetX . + $this->offsetY . + $this->width . + $this->height . + $this->rotation . + $this->shadow->getHashCode() . + __CLASS__ + ); + } + + /** + * Implement PHP __clone to create a deep clone, not just a shallow copy. + */ + public function __clone() + { + $vars = get_object_vars($this); + foreach ($vars as $key => $value) { + if ($key == 'worksheet') { + $this->worksheet = null; + } elseif (is_object($value)) { + $this->$key = clone $value; + } else { + $this->$key = $value; + } + } + } + + public function setHyperlink(?Hyperlink $hyperlink = null): void + { + $this->hyperlink = $hyperlink; + } + + /** + * @return null|Hyperlink + */ + public function getHyperlink() + { + return $this->hyperlink; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/CellIterator.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/CellIterator.php new file mode 100644 index 0000000..444e3b1 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/CellIterator.php @@ -0,0 +1,59 @@ + + */ +abstract class CellIterator implements Iterator +{ + /** + * Worksheet to iterate. + * + * @var Worksheet + */ + protected $worksheet; + + /** + * Iterate only existing cells. + * + * @var bool + */ + protected $onlyExistingCells = false; + + /** + * Destructor. + */ + public function __destruct() + { + // @phpstan-ignore-next-line + $this->worksheet = null; + } + + /** + * Get loop only existing cells. + */ + public function getIterateOnlyExistingCells(): bool + { + return $this->onlyExistingCells; + } + + /** + * Validate start/end values for "IterateOnlyExistingCells" mode, and adjust if necessary. + */ + abstract protected function adjustForExistingOnlyRange(); + + /** + * Set the iterator to loop only existing cells. + */ + public function setIterateOnlyExistingCells(bool $value): void + { + $this->onlyExistingCells = (bool) $value; + + $this->adjustForExistingOnlyRange(); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Column.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Column.php new file mode 100644 index 0000000..b6f30f1 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Column.php @@ -0,0 +1,62 @@ +parent = $parent; + $this->columnIndex = $columnIndex; + } + + /** + * Destructor. + */ + public function __destruct() + { + // @phpstan-ignore-next-line + $this->parent = null; + } + + /** + * Get column index as string eg: 'A'. + */ + public function getColumnIndex(): string + { + return $this->columnIndex; + } + + /** + * Get cell iterator. + * + * @param int $startRow The row number at which to start iterating + * @param int $endRow Optionally, the row number at which to stop iterating + * + * @return ColumnCellIterator + */ + public function getCellIterator($startRow = 1, $endRow = null) + { + return new ColumnCellIterator($this->parent, $this->columnIndex, $startRow, $endRow); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnCellIterator.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnCellIterator.php new file mode 100644 index 0000000..9d0be5b --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnCellIterator.php @@ -0,0 +1,190 @@ + + */ +class ColumnCellIterator extends CellIterator +{ + /** + * Current iterator position. + * + * @var int + */ + private $currentRow; + + /** + * Column index. + * + * @var int + */ + private $columnIndex; + + /** + * Start position. + * + * @var int + */ + private $startRow = 1; + + /** + * End position. + * + * @var int + */ + private $endRow = 1; + + /** + * Create a new row iterator. + * + * @param Worksheet $subject The worksheet to iterate over + * @param string $columnIndex The column that we want to iterate + * @param int $startRow The row number at which to start iterating + * @param int $endRow Optionally, the row number at which to stop iterating + */ + public function __construct(Worksheet $subject, $columnIndex = 'A', $startRow = 1, $endRow = null) + { + // Set subject + $this->worksheet = $subject; + $this->columnIndex = Coordinate::columnIndexFromString($columnIndex); + $this->resetEnd($endRow); + $this->resetStart($startRow); + } + + /** + * (Re)Set the start row and the current row pointer. + * + * @param int $startRow The row number at which to start iterating + * + * @return $this + */ + public function resetStart(int $startRow = 1) + { + $this->startRow = $startRow; + $this->adjustForExistingOnlyRange(); + $this->seek($startRow); + + return $this; + } + + /** + * (Re)Set the end row. + * + * @param int $endRow The row number at which to stop iterating + * + * @return $this + */ + public function resetEnd($endRow = null) + { + $this->endRow = $endRow ?: $this->worksheet->getHighestRow(); + $this->adjustForExistingOnlyRange(); + + return $this; + } + + /** + * Set the row pointer to the selected row. + * + * @param int $row The row number to set the current pointer at + * + * @return $this + */ + public function seek(int $row = 1) + { + if ($this->onlyExistingCells && !($this->worksheet->cellExistsByColumnAndRow($this->columnIndex, $row))) { + throw new PhpSpreadsheetException('In "IterateOnlyExistingCells" mode and Cell does not exist'); + } + if (($row < $this->startRow) || ($row > $this->endRow)) { + throw new PhpSpreadsheetException("Row $row is out of range ({$this->startRow} - {$this->endRow})"); + } + $this->currentRow = $row; + + return $this; + } + + /** + * Rewind the iterator to the starting row. + */ + public function rewind(): void + { + $this->currentRow = $this->startRow; + } + + /** + * Return the current cell in this worksheet column. + */ + public function current(): ?Cell + { + return $this->worksheet->getCellByColumnAndRow($this->columnIndex, $this->currentRow); + } + + /** + * Return the current iterator key. + */ + public function key(): int + { + return $this->currentRow; + } + + /** + * Set the iterator to its next value. + */ + public function next(): void + { + do { + ++$this->currentRow; + } while ( + ($this->onlyExistingCells) && + (!$this->worksheet->cellExistsByColumnAndRow($this->columnIndex, $this->currentRow)) && + ($this->currentRow <= $this->endRow) + ); + } + + /** + * Set the iterator to its previous value. + */ + public function prev(): void + { + do { + --$this->currentRow; + } while ( + ($this->onlyExistingCells) && + (!$this->worksheet->cellExistsByColumnAndRow($this->columnIndex, $this->currentRow)) && + ($this->currentRow >= $this->startRow) + ); + } + + /** + * Indicate if more rows exist in the worksheet range of rows that we're iterating. + */ + public function valid(): bool + { + return $this->currentRow <= $this->endRow && $this->currentRow >= $this->startRow; + } + + /** + * Validate start/end values for "IterateOnlyExistingCells" mode, and adjust if necessary. + */ + protected function adjustForExistingOnlyRange(): void + { + if ($this->onlyExistingCells) { + while ( + (!$this->worksheet->cellExistsByColumnAndRow($this->columnIndex, $this->startRow)) && + ($this->startRow <= $this->endRow) + ) { + ++$this->startRow; + } + while ( + (!$this->worksheet->cellExistsByColumnAndRow($this->columnIndex, $this->endRow)) && + ($this->endRow >= $this->startRow) + ) { + --$this->endRow; + } + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnDimension.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnDimension.php new file mode 100644 index 0000000..8a48f47 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnDimension.php @@ -0,0 +1,117 @@ +columnIndex = $index; + + // set dimension as unformatted by default + parent::__construct(0); + } + + /** + * Get column index as string eg: 'A'. + */ + public function getColumnIndex(): string + { + return $this->columnIndex; + } + + /** + * Set column index as string eg: 'A'. + * + * @return $this + */ + public function setColumnIndex(string $index) + { + $this->columnIndex = $index; + + return $this; + } + + /** + * Get Width. + * + * Each unit of column width is equal to the width of one character in the default font size. + * By default, this will be the return value; but this method also accepts a unit of measure argument and will + * return the value converted to the specified UoM using an approximation method. + */ + public function getWidth(?string $unitOfMeasure = null): float + { + return ($unitOfMeasure === null || $this->width < 0) + ? $this->width + : (new CssDimension((string) $this->width))->toUnit($unitOfMeasure); + } + + /** + * Set Width. + * + * Each unit of column width is equal to the width of one character in the default font size. + * By default, this will be the unit of measure for the passed value; but this method accepts a unit of measure + * argument, and will convert the value from the specified UoM using an approximation method. + * + * @return $this + */ + public function setWidth(float $width, ?string $unitOfMeasure = null) + { + $this->width = ($unitOfMeasure === null || $width < 0) + ? $width + : (new CssDimension("{$width}{$unitOfMeasure}"))->width(); + + return $this; + } + + /** + * Get Auto Size. + */ + public function getAutoSize(): bool + { + return $this->autoSize; + } + + /** + * Set Auto Size. + * + * @return $this + */ + public function setAutoSize(bool $autosizeEnabled) + { + $this->autoSize = $autosizeEnabled; + + return $this; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnIterator.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnIterator.php new file mode 100644 index 0000000..b64ae5a --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnIterator.php @@ -0,0 +1,174 @@ + + */ +class ColumnIterator implements Iterator +{ + /** + * Worksheet to iterate. + * + * @var Worksheet + */ + private $worksheet; + + /** + * Current iterator position. + * + * @var int + */ + private $currentColumnIndex = 1; + + /** + * Start position. + * + * @var int + */ + private $startColumnIndex = 1; + + /** + * End position. + * + * @var int + */ + private $endColumnIndex = 1; + + /** + * Create a new column iterator. + * + * @param Worksheet $worksheet The worksheet to iterate over + * @param string $startColumn The column address at which to start iterating + * @param string $endColumn Optionally, the column address at which to stop iterating + */ + public function __construct(Worksheet $worksheet, $startColumn = 'A', $endColumn = null) + { + // Set subject + $this->worksheet = $worksheet; + $this->resetEnd($endColumn); + $this->resetStart($startColumn); + } + + /** + * Destructor. + */ + public function __destruct() + { + // @phpstan-ignore-next-line + $this->worksheet = null; + } + + /** + * (Re)Set the start column and the current column pointer. + * + * @param string $startColumn The column address at which to start iterating + * + * @return $this + */ + public function resetStart(string $startColumn = 'A') + { + $startColumnIndex = Coordinate::columnIndexFromString($startColumn); + if ($startColumnIndex > Coordinate::columnIndexFromString($this->worksheet->getHighestColumn())) { + throw new Exception( + "Start column ({$startColumn}) is beyond highest column ({$this->worksheet->getHighestColumn()})" + ); + } + + $this->startColumnIndex = $startColumnIndex; + if ($this->endColumnIndex < $this->startColumnIndex) { + $this->endColumnIndex = $this->startColumnIndex; + } + $this->seek($startColumn); + + return $this; + } + + /** + * (Re)Set the end column. + * + * @param string $endColumn The column address at which to stop iterating + * + * @return $this + */ + public function resetEnd($endColumn = null) + { + $endColumn = $endColumn ?: $this->worksheet->getHighestColumn(); + $this->endColumnIndex = Coordinate::columnIndexFromString($endColumn); + + return $this; + } + + /** + * Set the column pointer to the selected column. + * + * @param string $column The column address to set the current pointer at + * + * @return $this + */ + public function seek(string $column = 'A') + { + $column = Coordinate::columnIndexFromString($column); + if (($column < $this->startColumnIndex) || ($column > $this->endColumnIndex)) { + throw new PhpSpreadsheetException( + "Column $column is out of range ({$this->startColumnIndex} - {$this->endColumnIndex})" + ); + } + $this->currentColumnIndex = $column; + + return $this; + } + + /** + * Rewind the iterator to the starting column. + */ + public function rewind(): void + { + $this->currentColumnIndex = $this->startColumnIndex; + } + + /** + * Return the current column in this worksheet. + */ + public function current(): Column + { + return new Column($this->worksheet, Coordinate::stringFromColumnIndex($this->currentColumnIndex)); + } + + /** + * Return the current iterator key. + */ + public function key(): string + { + return Coordinate::stringFromColumnIndex($this->currentColumnIndex); + } + + /** + * Set the iterator to its next value. + */ + public function next(): void + { + ++$this->currentColumnIndex; + } + + /** + * Set the iterator to its previous value. + */ + public function prev(): void + { + --$this->currentColumnIndex; + } + + /** + * Indicate if more columns exist in the worksheet range of columns that we're iterating. + */ + public function valid(): bool + { + return $this->currentColumnIndex <= $this->endColumnIndex && $this->currentColumnIndex >= $this->startColumnIndex; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Dimension.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Dimension.php new file mode 100644 index 0000000..894ac19 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Dimension.php @@ -0,0 +1,149 @@ +xfIndex = $initialValue; + } + + /** + * Get Visible. + */ + public function getVisible(): bool + { + return $this->visible; + } + + /** + * Set Visible. + * + * @return $this + */ + public function setVisible(bool $visible) + { + $this->visible = $visible; + + return $this; + } + + /** + * Get Outline Level. + */ + public function getOutlineLevel(): int + { + return $this->outlineLevel; + } + + /** + * Set Outline Level. + * Value must be between 0 and 7. + * + * @return $this + */ + public function setOutlineLevel(int $level) + { + if ($level < 0 || $level > 7) { + throw new PhpSpreadsheetException('Outline level must range between 0 and 7.'); + } + + $this->outlineLevel = $level; + + return $this; + } + + /** + * Get Collapsed. + */ + public function getCollapsed(): bool + { + return $this->collapsed; + } + + /** + * Set Collapsed. + * + * @return $this + */ + public function setCollapsed(bool $collapsed) + { + $this->collapsed = $collapsed; + + return $this; + } + + /** + * Get index to cellXf. + * + * @return int + */ + public function getXfIndex(): ?int + { + return $this->xfIndex; + } + + /** + * Set index to cellXf. + * + * @return $this + */ + public function setXfIndex(int $XfIndex) + { + $this->xfIndex = $XfIndex; + + return $this; + } + + /** + * Implement PHP __clone to create a deep clone, not just a shallow copy. + */ + public function __clone() + { + $vars = get_object_vars($this); + foreach ($vars as $key => $value) { + if (is_object($value)) { + $this->$key = clone $value; + } else { + $this->$key = $value; + } + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Drawing.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Drawing.php new file mode 100644 index 0000000..bbb5acf --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Drawing.php @@ -0,0 +1,156 @@ +path = ''; + $this->isUrl = false; + + // Initialize parent + parent::__construct(); + } + + /** + * Get Filename. + * + * @return string + */ + public function getFilename() + { + return basename($this->path); + } + + /** + * Get indexed filename (using image index). + */ + public function getIndexedFilename(): string + { + $fileName = $this->getFilename(); + $fileName = str_replace(' ', '_', $fileName); + + return str_replace('.' . $this->getExtension(), '', $fileName) . $this->getImageIndex() . '.' . $this->getExtension(); + } + + /** + * Get Extension. + * + * @return string + */ + public function getExtension() + { + $exploded = explode('.', basename($this->path)); + + return $exploded[count($exploded) - 1]; + } + + /** + * Get Path. + * + * @return string + */ + public function getPath() + { + return $this->path; + } + + /** + * Set Path. + * + * @param string $path File path + * @param bool $verifyFile Verify file + * + * @return $this + */ + public function setPath($path, $verifyFile = true) + { + if ($verifyFile) { + // Check if a URL has been passed. https://stackoverflow.com/a/2058596/1252979 + if (filter_var($path, FILTER_VALIDATE_URL)) { + $this->path = $path; + // Implicit that it is a URL, rather store info than running check above on value in other places. + $this->isUrl = true; + $imageContents = file_get_contents($path); + $filePath = tempnam(sys_get_temp_dir(), 'Drawing'); + if ($filePath) { + file_put_contents($filePath, $imageContents); + if (file_exists($filePath)) { + if ($this->width == 0 && $this->height == 0) { + // Get width/height + [$this->width, $this->height] = getimagesize($filePath); + } + unlink($filePath); + } + } + } elseif (file_exists($path)) { + $this->path = $path; + if ($this->width == 0 && $this->height == 0) { + // Get width/height + [$this->width, $this->height] = getimagesize($path); + } + } else { + throw new PhpSpreadsheetException("File $path not found!"); + } + } else { + $this->path = $path; + } + + return $this; + } + + /** + * Get isURL. + */ + public function getIsURL(): bool + { + return $this->isUrl; + } + + /** + * Set isURL. + * + * @return $this + */ + public function setIsURL(bool $isUrl): self + { + $this->isUrl = $isUrl; + + return $this; + } + + /** + * Get hash code. + * + * @return string Hash code + */ + public function getHashCode() + { + return md5( + $this->path . + parent::getHashCode() . + __CLASS__ + ); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Drawing/Shadow.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Drawing/Shadow.php new file mode 100644 index 0000000..a461a51 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Drawing/Shadow.php @@ -0,0 +1,287 @@ +visible = false; + $this->blurRadius = 6; + $this->distance = 2; + $this->direction = 0; + $this->alignment = self::SHADOW_BOTTOM_RIGHT; + $this->color = new Color(Color::COLOR_BLACK); + $this->alpha = 50; + } + + /** + * Get Visible. + * + * @return bool + */ + public function getVisible() + { + return $this->visible; + } + + /** + * Set Visible. + * + * @param bool $visible + * + * @return $this + */ + public function setVisible($visible) + { + $this->visible = $visible; + + return $this; + } + + /** + * Get Blur radius. + * + * @return int + */ + public function getBlurRadius() + { + return $this->blurRadius; + } + + /** + * Set Blur radius. + * + * @param int $blurRadius + * + * @return $this + */ + public function setBlurRadius($blurRadius) + { + $this->blurRadius = $blurRadius; + + return $this; + } + + /** + * Get Shadow distance. + * + * @return int + */ + public function getDistance() + { + return $this->distance; + } + + /** + * Set Shadow distance. + * + * @param int $distance + * + * @return $this + */ + public function setDistance($distance) + { + $this->distance = $distance; + + return $this; + } + + /** + * Get Shadow direction (in degrees). + * + * @return int + */ + public function getDirection() + { + return $this->direction; + } + + /** + * Set Shadow direction (in degrees). + * + * @param int $direction + * + * @return $this + */ + public function setDirection($direction) + { + $this->direction = $direction; + + return $this; + } + + /** + * Get Shadow alignment. + * + * @return string + */ + public function getAlignment() + { + return $this->alignment; + } + + /** + * Set Shadow alignment. + * + * @param string $alignment + * + * @return $this + */ + public function setAlignment($alignment) + { + $this->alignment = $alignment; + + return $this; + } + + /** + * Get Color. + * + * @return Color + */ + public function getColor() + { + return $this->color; + } + + /** + * Set Color. + * + * @return $this + */ + public function setColor(?Color $color = null) + { + $this->color = $color; + + return $this; + } + + /** + * Get Alpha. + * + * @return int + */ + public function getAlpha() + { + return $this->alpha; + } + + /** + * Set Alpha. + * + * @param int $alpha + * + * @return $this + */ + public function setAlpha($alpha) + { + $this->alpha = $alpha; + + return $this; + } + + /** + * Get hash code. + * + * @return string Hash code + */ + public function getHashCode() + { + return md5( + ($this->visible ? 't' : 'f') . + $this->blurRadius . + $this->distance . + $this->direction . + $this->alignment . + $this->color->getHashCode() . + $this->alpha . + __CLASS__ + ); + } + + /** + * Implement PHP __clone to create a deep clone, not just a shallow copy. + */ + public function __clone() + { + $vars = get_object_vars($this); + foreach ($vars as $key => $value) { + if (is_object($value)) { + $this->$key = clone $value; + } else { + $this->$key = $value; + } + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/HeaderFooter.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/HeaderFooter.php new file mode 100644 index 0000000..c3504d8 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/HeaderFooter.php @@ -0,0 +1,490 @@ + + * Header/Footer Formatting Syntax taken from Office Open XML Part 4 - Markup Language Reference, page 1970:. + * + * There are a number of formatting codes that can be written inline with the actual header / footer text, which + * affect the formatting in the header or footer. + * + * Example: This example shows the text "Center Bold Header" on the first line (center section), and the date on + * the second line (center section). + * &CCenter &"-,Bold"Bold&"-,Regular"Header_x000A_&D + * + * General Rules: + * There is no required order in which these codes must appear. + * + * The first occurrence of the following codes turns the formatting ON, the second occurrence turns it OFF again: + * - strikethrough + * - superscript + * - subscript + * Superscript and subscript cannot both be ON at same time. Whichever comes first wins and the other is ignored, + * while the first is ON. + * &L - code for "left section" (there are three header / footer locations, "left", "center", and "right"). When + * two or more occurrences of this section marker exist, the contents from all markers are concatenated, in the + * order of appearance, and placed into the left section. + * &P - code for "current page #" + * &N - code for "total pages" + * &font size - code for "text font size", where font size is a font size in points. + * &K - code for "text font color" + * RGB Color is specified as RRGGBB + * Theme Color is specifed as TTSNN where TT is the theme color Id, S is either "+" or "-" of the tint/shade + * value, NN is the tint/shade value. + * &S - code for "text strikethrough" on / off + * &X - code for "text super script" on / off + * &Y - code for "text subscript" on / off + * &C - code for "center section". When two or more occurrences of this section marker exist, the contents + * from all markers are concatenated, in the order of appearance, and placed into the center section. + * + * &D - code for "date" + * &T - code for "time" + * &G - code for "picture as background" + * &U - code for "text single underline" + * &E - code for "double underline" + * &R - code for "right section". When two or more occurrences of this section marker exist, the contents + * from all markers are concatenated, in the order of appearance, and placed into the right section. + * &Z - code for "this workbook's file path" + * &F - code for "this workbook's file name" + * &A - code for "sheet tab name" + * &+ - code for add to page #. + * &- - code for subtract from page #. + * &"font name,font type" - code for "text font name" and "text font type", where font name and font type + * are strings specifying the name and type of the font, separated by a comma. When a hyphen appears in font + * name, it means "none specified". Both of font name and font type can be localized values. + * &"-,Bold" - code for "bold font style" + * &B - also means "bold font style". + * &"-,Regular" - code for "regular font style" + * &"-,Italic" - code for "italic font style" + * &I - also means "italic font style" + * &"-,Bold Italic" code for "bold italic font style" + * &O - code for "outline style" + * &H - code for "shadow style" + * + */ +class HeaderFooter +{ + // Header/footer image location + const IMAGE_HEADER_LEFT = 'LH'; + const IMAGE_HEADER_CENTER = 'CH'; + const IMAGE_HEADER_RIGHT = 'RH'; + const IMAGE_FOOTER_LEFT = 'LF'; + const IMAGE_FOOTER_CENTER = 'CF'; + const IMAGE_FOOTER_RIGHT = 'RF'; + + /** + * OddHeader. + * + * @var string + */ + private $oddHeader = ''; + + /** + * OddFooter. + * + * @var string + */ + private $oddFooter = ''; + + /** + * EvenHeader. + * + * @var string + */ + private $evenHeader = ''; + + /** + * EvenFooter. + * + * @var string + */ + private $evenFooter = ''; + + /** + * FirstHeader. + * + * @var string + */ + private $firstHeader = ''; + + /** + * FirstFooter. + * + * @var string + */ + private $firstFooter = ''; + + /** + * Different header for Odd/Even, defaults to false. + * + * @var bool + */ + private $differentOddEven = false; + + /** + * Different header for first page, defaults to false. + * + * @var bool + */ + private $differentFirst = false; + + /** + * Scale with document, defaults to true. + * + * @var bool + */ + private $scaleWithDocument = true; + + /** + * Align with margins, defaults to true. + * + * @var bool + */ + private $alignWithMargins = true; + + /** + * Header/footer images. + * + * @var HeaderFooterDrawing[] + */ + private $headerFooterImages = []; + + /** + * Create a new HeaderFooter. + */ + public function __construct() + { + } + + /** + * Get OddHeader. + * + * @return string + */ + public function getOddHeader() + { + return $this->oddHeader; + } + + /** + * Set OddHeader. + * + * @param string $oddHeader + * + * @return $this + */ + public function setOddHeader($oddHeader) + { + $this->oddHeader = $oddHeader; + + return $this; + } + + /** + * Get OddFooter. + * + * @return string + */ + public function getOddFooter() + { + return $this->oddFooter; + } + + /** + * Set OddFooter. + * + * @param string $oddFooter + * + * @return $this + */ + public function setOddFooter($oddFooter) + { + $this->oddFooter = $oddFooter; + + return $this; + } + + /** + * Get EvenHeader. + * + * @return string + */ + public function getEvenHeader() + { + return $this->evenHeader; + } + + /** + * Set EvenHeader. + * + * @param string $eventHeader + * + * @return $this + */ + public function setEvenHeader($eventHeader) + { + $this->evenHeader = $eventHeader; + + return $this; + } + + /** + * Get EvenFooter. + * + * @return string + */ + public function getEvenFooter() + { + return $this->evenFooter; + } + + /** + * Set EvenFooter. + * + * @param string $evenFooter + * + * @return $this + */ + public function setEvenFooter($evenFooter) + { + $this->evenFooter = $evenFooter; + + return $this; + } + + /** + * Get FirstHeader. + * + * @return string + */ + public function getFirstHeader() + { + return $this->firstHeader; + } + + /** + * Set FirstHeader. + * + * @param string $firstHeader + * + * @return $this + */ + public function setFirstHeader($firstHeader) + { + $this->firstHeader = $firstHeader; + + return $this; + } + + /** + * Get FirstFooter. + * + * @return string + */ + public function getFirstFooter() + { + return $this->firstFooter; + } + + /** + * Set FirstFooter. + * + * @param string $firstFooter + * + * @return $this + */ + public function setFirstFooter($firstFooter) + { + $this->firstFooter = $firstFooter; + + return $this; + } + + /** + * Get DifferentOddEven. + * + * @return bool + */ + public function getDifferentOddEven() + { + return $this->differentOddEven; + } + + /** + * Set DifferentOddEven. + * + * @param bool $differentOddEvent + * + * @return $this + */ + public function setDifferentOddEven($differentOddEvent) + { + $this->differentOddEven = $differentOddEvent; + + return $this; + } + + /** + * Get DifferentFirst. + * + * @return bool + */ + public function getDifferentFirst() + { + return $this->differentFirst; + } + + /** + * Set DifferentFirst. + * + * @param bool $differentFirst + * + * @return $this + */ + public function setDifferentFirst($differentFirst) + { + $this->differentFirst = $differentFirst; + + return $this; + } + + /** + * Get ScaleWithDocument. + * + * @return bool + */ + public function getScaleWithDocument() + { + return $this->scaleWithDocument; + } + + /** + * Set ScaleWithDocument. + * + * @param bool $scaleWithDocument + * + * @return $this + */ + public function setScaleWithDocument($scaleWithDocument) + { + $this->scaleWithDocument = $scaleWithDocument; + + return $this; + } + + /** + * Get AlignWithMargins. + * + * @return bool + */ + public function getAlignWithMargins() + { + return $this->alignWithMargins; + } + + /** + * Set AlignWithMargins. + * + * @param bool $alignWithMargins + * + * @return $this + */ + public function setAlignWithMargins($alignWithMargins) + { + $this->alignWithMargins = $alignWithMargins; + + return $this; + } + + /** + * Add header/footer image. + * + * @param string $location + * + * @return $this + */ + public function addImage(HeaderFooterDrawing $image, $location = self::IMAGE_HEADER_LEFT) + { + $this->headerFooterImages[$location] = $image; + + return $this; + } + + /** + * Remove header/footer image. + * + * @param string $location + * + * @return $this + */ + public function removeImage($location = self::IMAGE_HEADER_LEFT) + { + if (isset($this->headerFooterImages[$location])) { + unset($this->headerFooterImages[$location]); + } + + return $this; + } + + /** + * Set header/footer images. + * + * @param HeaderFooterDrawing[] $images + * + * @return $this + */ + public function setImages(array $images) + { + $this->headerFooterImages = $images; + + return $this; + } + + /** + * Get header/footer images. + * + * @return HeaderFooterDrawing[] + */ + public function getImages() + { + // Sort array + $images = []; + if (isset($this->headerFooterImages[self::IMAGE_HEADER_LEFT])) { + $images[self::IMAGE_HEADER_LEFT] = $this->headerFooterImages[self::IMAGE_HEADER_LEFT]; + } + if (isset($this->headerFooterImages[self::IMAGE_HEADER_CENTER])) { + $images[self::IMAGE_HEADER_CENTER] = $this->headerFooterImages[self::IMAGE_HEADER_CENTER]; + } + if (isset($this->headerFooterImages[self::IMAGE_HEADER_RIGHT])) { + $images[self::IMAGE_HEADER_RIGHT] = $this->headerFooterImages[self::IMAGE_HEADER_RIGHT]; + } + if (isset($this->headerFooterImages[self::IMAGE_FOOTER_LEFT])) { + $images[self::IMAGE_FOOTER_LEFT] = $this->headerFooterImages[self::IMAGE_FOOTER_LEFT]; + } + if (isset($this->headerFooterImages[self::IMAGE_FOOTER_CENTER])) { + $images[self::IMAGE_FOOTER_CENTER] = $this->headerFooterImages[self::IMAGE_FOOTER_CENTER]; + } + if (isset($this->headerFooterImages[self::IMAGE_FOOTER_RIGHT])) { + $images[self::IMAGE_FOOTER_RIGHT] = $this->headerFooterImages[self::IMAGE_FOOTER_RIGHT]; + } + $this->headerFooterImages = $images; + + return $this->headerFooterImages; + } + + /** + * Implement PHP __clone to create a deep clone, not just a shallow copy. + */ + public function __clone() + { + $vars = get_object_vars($this); + foreach ($vars as $key => $value) { + if (is_object($value)) { + $this->$key = clone $value; + } else { + $this->$key = $value; + } + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/HeaderFooterDrawing.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/HeaderFooterDrawing.php new file mode 100644 index 0000000..b42c732 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/HeaderFooterDrawing.php @@ -0,0 +1,24 @@ +getPath() . + $this->name . + $this->offsetX . + $this->offsetY . + $this->width . + $this->height . + __CLASS__ + ); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Iterator.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Iterator.php new file mode 100644 index 0000000..dc08204 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Iterator.php @@ -0,0 +1,74 @@ + + */ +class Iterator implements \Iterator +{ + /** + * Spreadsheet to iterate. + * + * @var Spreadsheet + */ + private $subject; + + /** + * Current iterator position. + * + * @var int + */ + private $position = 0; + + /** + * Create a new worksheet iterator. + */ + public function __construct(Spreadsheet $subject) + { + // Set subject + $this->subject = $subject; + } + + /** + * Rewind iterator. + */ + public function rewind(): void + { + $this->position = 0; + } + + /** + * Current Worksheet. + */ + public function current(): Worksheet + { + return $this->subject->getSheet($this->position); + } + + /** + * Current key. + */ + public function key(): int + { + return $this->position; + } + + /** + * Next value. + */ + public function next(): void + { + ++$this->position; + } + + /** + * Are there more Worksheet instances available? + */ + public function valid(): bool + { + return $this->position < $this->subject->getSheetCount() && $this->position >= 0; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/MemoryDrawing.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/MemoryDrawing.php new file mode 100644 index 0000000..19a3d1e --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/MemoryDrawing.php @@ -0,0 +1,230 @@ +renderingFunction = self::RENDERING_DEFAULT; + $this->mimeType = self::MIMETYPE_DEFAULT; + $this->uniqueName = md5(mt_rand(0, 9999) . time() . mt_rand(0, 9999)); + + // Initialize parent + parent::__construct(); + } + + public function __destruct() + { + if ($this->imageResource) { + imagedestroy($this->imageResource); + $this->imageResource = null; + } + } + + public function __clone() + { + parent::__clone(); + $this->cloneResource(); + } + + private function cloneResource(): void + { + if (!$this->imageResource) { + return; + } + + $width = imagesx($this->imageResource); + $height = imagesy($this->imageResource); + + if (imageistruecolor($this->imageResource)) { + $clone = imagecreatetruecolor($width, $height); + if (!$clone) { + throw new Exception('Could not clone image resource'); + } + + imagealphablending($clone, false); + imagesavealpha($clone, true); + } else { + $clone = imagecreate($width, $height); + if (!$clone) { + throw new Exception('Could not clone image resource'); + } + + // If the image has transparency... + $transparent = imagecolortransparent($this->imageResource); + if ($transparent >= 0) { + $rgb = imagecolorsforindex($this->imageResource, $transparent); + if ($rgb === false) { + throw new Exception('Could not get image colors'); + } + + imagesavealpha($clone, true); + $color = imagecolorallocatealpha($clone, $rgb['red'], $rgb['green'], $rgb['blue'], $rgb['alpha']); + if ($color === false) { + throw new Exception('Could not get image alpha color'); + } + + imagefill($clone, 0, 0, $color); + } + } + + //Create the Clone!! + imagecopy($clone, $this->imageResource, 0, 0, 0, 0, $width, $height); + + $this->imageResource = $clone; + } + + /** + * Get image resource. + * + * @return null|GdImage|resource + */ + public function getImageResource() + { + return $this->imageResource; + } + + /** + * Set image resource. + * + * @param GdImage|resource $value + * + * @return $this + */ + public function setImageResource($value) + { + $this->imageResource = $value; + + if ($this->imageResource !== null) { + // Get width/height + $this->width = imagesx($this->imageResource); + $this->height = imagesy($this->imageResource); + } + + return $this; + } + + /** + * Get rendering function. + * + * @return string + */ + public function getRenderingFunction() + { + return $this->renderingFunction; + } + + /** + * Set rendering function. + * + * @param string $value see self::RENDERING_* + * + * @return $this + */ + public function setRenderingFunction($value) + { + $this->renderingFunction = $value; + + return $this; + } + + /** + * Get mime type. + * + * @return string + */ + public function getMimeType() + { + return $this->mimeType; + } + + /** + * Set mime type. + * + * @param string $value see self::MIMETYPE_* + * + * @return $this + */ + public function setMimeType($value) + { + $this->mimeType = $value; + + return $this; + } + + /** + * Get indexed filename (using image index). + */ + public function getIndexedFilename(): string + { + $extension = strtolower($this->getMimeType()); + $extension = explode('/', $extension); + $extension = $extension[1]; + + return $this->uniqueName . $this->getImageIndex() . '.' . $extension; + } + + /** + * Get hash code. + * + * @return string Hash code + */ + public function getHashCode() + { + return md5( + $this->renderingFunction . + $this->mimeType . + $this->uniqueName . + parent::getHashCode() . + __CLASS__ + ); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageMargins.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageMargins.php new file mode 100644 index 0000000..34e1145 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageMargins.php @@ -0,0 +1,244 @@ +left; + } + + /** + * Set Left. + * + * @param float $left + * + * @return $this + */ + public function setLeft($left) + { + $this->left = $left; + + return $this; + } + + /** + * Get Right. + * + * @return float + */ + public function getRight() + { + return $this->right; + } + + /** + * Set Right. + * + * @param float $right + * + * @return $this + */ + public function setRight($right) + { + $this->right = $right; + + return $this; + } + + /** + * Get Top. + * + * @return float + */ + public function getTop() + { + return $this->top; + } + + /** + * Set Top. + * + * @param float $top + * + * @return $this + */ + public function setTop($top) + { + $this->top = $top; + + return $this; + } + + /** + * Get Bottom. + * + * @return float + */ + public function getBottom() + { + return $this->bottom; + } + + /** + * Set Bottom. + * + * @param float $bottom + * + * @return $this + */ + public function setBottom($bottom) + { + $this->bottom = $bottom; + + return $this; + } + + /** + * Get Header. + * + * @return float + */ + public function getHeader() + { + return $this->header; + } + + /** + * Set Header. + * + * @param float $header + * + * @return $this + */ + public function setHeader($header) + { + $this->header = $header; + + return $this; + } + + /** + * Get Footer. + * + * @return float + */ + public function getFooter() + { + return $this->footer; + } + + /** + * Set Footer. + * + * @param float $footer + * + * @return $this + */ + public function setFooter($footer) + { + $this->footer = $footer; + + return $this; + } + + /** + * Implement PHP __clone to create a deep clone, not just a shallow copy. + */ + public function __clone() + { + $vars = get_object_vars($this); + foreach ($vars as $key => $value) { + if (is_object($value)) { + $this->$key = clone $value; + } else { + $this->$key = $value; + } + } + } + + public static function fromCentimeters(float $value): float + { + return $value / 2.54; + } + + public static function toCentimeters(float $value): float + { + return $value * 2.54; + } + + public static function fromMillimeters(float $value): float + { + return $value / 25.4; + } + + public static function toMillimeters(float $value): float + { + return $value * 25.4; + } + + public static function fromPoints(float $value): float + { + return $value / 72; + } + + public static function toPoints(float $value): float + { + return $value * 72; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageSetup.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageSetup.php new file mode 100644 index 0000000..ec276b6 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageSetup.php @@ -0,0 +1,857 @@ + + * Paper size taken from Office Open XML Part 4 - Markup Language Reference, page 1988:. + * + * 1 = Letter paper (8.5 in. by 11 in.) + * 2 = Letter small paper (8.5 in. by 11 in.) + * 3 = Tabloid paper (11 in. by 17 in.) + * 4 = Ledger paper (17 in. by 11 in.) + * 5 = Legal paper (8.5 in. by 14 in.) + * 6 = Statement paper (5.5 in. by 8.5 in.) + * 7 = Executive paper (7.25 in. by 10.5 in.) + * 8 = A3 paper (297 mm by 420 mm) + * 9 = A4 paper (210 mm by 297 mm) + * 10 = A4 small paper (210 mm by 297 mm) + * 11 = A5 paper (148 mm by 210 mm) + * 12 = B4 paper (250 mm by 353 mm) + * 13 = B5 paper (176 mm by 250 mm) + * 14 = Folio paper (8.5 in. by 13 in.) + * 15 = Quarto paper (215 mm by 275 mm) + * 16 = Standard paper (10 in. by 14 in.) + * 17 = Standard paper (11 in. by 17 in.) + * 18 = Note paper (8.5 in. by 11 in.) + * 19 = #9 envelope (3.875 in. by 8.875 in.) + * 20 = #10 envelope (4.125 in. by 9.5 in.) + * 21 = #11 envelope (4.5 in. by 10.375 in.) + * 22 = #12 envelope (4.75 in. by 11 in.) + * 23 = #14 envelope (5 in. by 11.5 in.) + * 24 = C paper (17 in. by 22 in.) + * 25 = D paper (22 in. by 34 in.) + * 26 = E paper (34 in. by 44 in.) + * 27 = DL envelope (110 mm by 220 mm) + * 28 = C5 envelope (162 mm by 229 mm) + * 29 = C3 envelope (324 mm by 458 mm) + * 30 = C4 envelope (229 mm by 324 mm) + * 31 = C6 envelope (114 mm by 162 mm) + * 32 = C65 envelope (114 mm by 229 mm) + * 33 = B4 envelope (250 mm by 353 mm) + * 34 = B5 envelope (176 mm by 250 mm) + * 35 = B6 envelope (176 mm by 125 mm) + * 36 = Italy envelope (110 mm by 230 mm) + * 37 = Monarch envelope (3.875 in. by 7.5 in.). + * 38 = 6 3/4 envelope (3.625 in. by 6.5 in.) + * 39 = US standard fanfold (14.875 in. by 11 in.) + * 40 = German standard fanfold (8.5 in. by 12 in.) + * 41 = German legal fanfold (8.5 in. by 13 in.) + * 42 = ISO B4 (250 mm by 353 mm) + * 43 = Japanese double postcard (200 mm by 148 mm) + * 44 = Standard paper (9 in. by 11 in.) + * 45 = Standard paper (10 in. by 11 in.) + * 46 = Standard paper (15 in. by 11 in.) + * 47 = Invite envelope (220 mm by 220 mm) + * 50 = Letter extra paper (9.275 in. by 12 in.) + * 51 = Legal extra paper (9.275 in. by 15 in.) + * 52 = Tabloid extra paper (11.69 in. by 18 in.) + * 53 = A4 extra paper (236 mm by 322 mm) + * 54 = Letter transverse paper (8.275 in. by 11 in.) + * 55 = A4 transverse paper (210 mm by 297 mm) + * 56 = Letter extra transverse paper (9.275 in. by 12 in.) + * 57 = SuperA/SuperA/A4 paper (227 mm by 356 mm) + * 58 = SuperB/SuperB/A3 paper (305 mm by 487 mm) + * 59 = Letter plus paper (8.5 in. by 12.69 in.) + * 60 = A4 plus paper (210 mm by 330 mm) + * 61 = A5 transverse paper (148 mm by 210 mm) + * 62 = JIS B5 transverse paper (182 mm by 257 mm) + * 63 = A3 extra paper (322 mm by 445 mm) + * 64 = A5 extra paper (174 mm by 235 mm) + * 65 = ISO B5 extra paper (201 mm by 276 mm) + * 66 = A2 paper (420 mm by 594 mm) + * 67 = A3 transverse paper (297 mm by 420 mm) + * 68 = A3 extra transverse paper (322 mm by 445 mm) + * + */ +class PageSetup +{ + // Paper size + const PAPERSIZE_LETTER = 1; + const PAPERSIZE_LETTER_SMALL = 2; + const PAPERSIZE_TABLOID = 3; + const PAPERSIZE_LEDGER = 4; + const PAPERSIZE_LEGAL = 5; + const PAPERSIZE_STATEMENT = 6; + const PAPERSIZE_EXECUTIVE = 7; + const PAPERSIZE_A3 = 8; + const PAPERSIZE_A4 = 9; + const PAPERSIZE_A4_SMALL = 10; + const PAPERSIZE_A5 = 11; + const PAPERSIZE_B4 = 12; + const PAPERSIZE_B5 = 13; + const PAPERSIZE_FOLIO = 14; + const PAPERSIZE_QUARTO = 15; + const PAPERSIZE_STANDARD_1 = 16; + const PAPERSIZE_STANDARD_2 = 17; + const PAPERSIZE_NOTE = 18; + const PAPERSIZE_NO9_ENVELOPE = 19; + const PAPERSIZE_NO10_ENVELOPE = 20; + const PAPERSIZE_NO11_ENVELOPE = 21; + const PAPERSIZE_NO12_ENVELOPE = 22; + const PAPERSIZE_NO14_ENVELOPE = 23; + const PAPERSIZE_C = 24; + const PAPERSIZE_D = 25; + const PAPERSIZE_E = 26; + const PAPERSIZE_DL_ENVELOPE = 27; + const PAPERSIZE_C5_ENVELOPE = 28; + const PAPERSIZE_C3_ENVELOPE = 29; + const PAPERSIZE_C4_ENVELOPE = 30; + const PAPERSIZE_C6_ENVELOPE = 31; + const PAPERSIZE_C65_ENVELOPE = 32; + const PAPERSIZE_B4_ENVELOPE = 33; + const PAPERSIZE_B5_ENVELOPE = 34; + const PAPERSIZE_B6_ENVELOPE = 35; + const PAPERSIZE_ITALY_ENVELOPE = 36; + const PAPERSIZE_MONARCH_ENVELOPE = 37; + const PAPERSIZE_6_3_4_ENVELOPE = 38; + const PAPERSIZE_US_STANDARD_FANFOLD = 39; + const PAPERSIZE_GERMAN_STANDARD_FANFOLD = 40; + const PAPERSIZE_GERMAN_LEGAL_FANFOLD = 41; + const PAPERSIZE_ISO_B4 = 42; + const PAPERSIZE_JAPANESE_DOUBLE_POSTCARD = 43; + const PAPERSIZE_STANDARD_PAPER_1 = 44; + const PAPERSIZE_STANDARD_PAPER_2 = 45; + const PAPERSIZE_STANDARD_PAPER_3 = 46; + const PAPERSIZE_INVITE_ENVELOPE = 47; + const PAPERSIZE_LETTER_EXTRA_PAPER = 48; + const PAPERSIZE_LEGAL_EXTRA_PAPER = 49; + const PAPERSIZE_TABLOID_EXTRA_PAPER = 50; + const PAPERSIZE_A4_EXTRA_PAPER = 51; + const PAPERSIZE_LETTER_TRANSVERSE_PAPER = 52; + const PAPERSIZE_A4_TRANSVERSE_PAPER = 53; + const PAPERSIZE_LETTER_EXTRA_TRANSVERSE_PAPER = 54; + const PAPERSIZE_SUPERA_SUPERA_A4_PAPER = 55; + const PAPERSIZE_SUPERB_SUPERB_A3_PAPER = 56; + const PAPERSIZE_LETTER_PLUS_PAPER = 57; + const PAPERSIZE_A4_PLUS_PAPER = 58; + const PAPERSIZE_A5_TRANSVERSE_PAPER = 59; + const PAPERSIZE_JIS_B5_TRANSVERSE_PAPER = 60; + const PAPERSIZE_A3_EXTRA_PAPER = 61; + const PAPERSIZE_A5_EXTRA_PAPER = 62; + const PAPERSIZE_ISO_B5_EXTRA_PAPER = 63; + const PAPERSIZE_A2_PAPER = 64; + const PAPERSIZE_A3_TRANSVERSE_PAPER = 65; + const PAPERSIZE_A3_EXTRA_TRANSVERSE_PAPER = 66; + + // Page orientation + const ORIENTATION_DEFAULT = 'default'; + const ORIENTATION_LANDSCAPE = 'landscape'; + const ORIENTATION_PORTRAIT = 'portrait'; + + // Print Range Set Method + const SETPRINTRANGE_OVERWRITE = 'O'; + const SETPRINTRANGE_INSERT = 'I'; + + const PAGEORDER_OVER_THEN_DOWN = 'overThenDown'; + const PAGEORDER_DOWN_THEN_OVER = 'downThenOver'; + + /** + * Paper size. + * + * @var int + */ + private $paperSize = self::PAPERSIZE_LETTER; + + /** + * Orientation. + * + * @var string + */ + private $orientation = self::ORIENTATION_DEFAULT; + + /** + * Scale (Print Scale). + * + * Print scaling. Valid values range from 10 to 400 + * This setting is overridden when fitToWidth and/or fitToHeight are in use + * + * @var null|int + */ + private $scale = 100; + + /** + * Fit To Page + * Whether scale or fitToWith / fitToHeight applies. + * + * @var bool + */ + private $fitToPage = false; + + /** + * Fit To Height + * Number of vertical pages to fit on. + * + * @var null|int + */ + private $fitToHeight = 1; + + /** + * Fit To Width + * Number of horizontal pages to fit on. + * + * @var null|int + */ + private $fitToWidth = 1; + + /** + * Columns to repeat at left. + * + * @var array Containing start column and end column, empty array if option unset + */ + private $columnsToRepeatAtLeft = ['', '']; + + /** + * Rows to repeat at top. + * + * @var array Containing start row number and end row number, empty array if option unset + */ + private $rowsToRepeatAtTop = [0, 0]; + + /** + * Center page horizontally. + * + * @var bool + */ + private $horizontalCentered = false; + + /** + * Center page vertically. + * + * @var bool + */ + private $verticalCentered = false; + + /** + * Print area. + * + * @var null|string + */ + private $printArea; + + /** + * First page number. + * + * @var int + */ + private $firstPageNumber; + + private $pageOrder = self::PAGEORDER_DOWN_THEN_OVER; + + /** + * Create a new PageSetup. + */ + public function __construct() + { + } + + /** + * Get Paper Size. + * + * @return int + */ + public function getPaperSize() + { + return $this->paperSize; + } + + /** + * Set Paper Size. + * + * @param int $paperSize see self::PAPERSIZE_* + * + * @return $this + */ + public function setPaperSize($paperSize) + { + $this->paperSize = $paperSize; + + return $this; + } + + /** + * Get Orientation. + * + * @return string + */ + public function getOrientation() + { + return $this->orientation; + } + + /** + * Set Orientation. + * + * @param string $orientation see self::ORIENTATION_* + * + * @return $this + */ + public function setOrientation($orientation) + { + $this->orientation = $orientation; + + return $this; + } + + /** + * Get Scale. + * + * @return null|int + */ + public function getScale() + { + return $this->scale; + } + + /** + * Set Scale. + * Print scaling. Valid values range from 10 to 400 + * This setting is overridden when fitToWidth and/or fitToHeight are in use. + * + * @param null|int $scale + * @param bool $update Update fitToPage so scaling applies rather than fitToHeight / fitToWidth + * + * @return $this + */ + public function setScale($scale, $update = true) + { + // Microsoft Office Excel 2007 only allows setting a scale between 10 and 400 via the user interface, + // but it is apparently still able to handle any scale >= 0, where 0 results in 100 + if (($scale >= 0) || $scale === null) { + $this->scale = $scale; + if ($update) { + $this->fitToPage = false; + } + } else { + throw new PhpSpreadsheetException('Scale must not be negative'); + } + + return $this; + } + + /** + * Get Fit To Page. + * + * @return bool + */ + public function getFitToPage() + { + return $this->fitToPage; + } + + /** + * Set Fit To Page. + * + * @param bool $fitToPage + * + * @return $this + */ + public function setFitToPage($fitToPage) + { + $this->fitToPage = $fitToPage; + + return $this; + } + + /** + * Get Fit To Height. + * + * @return null|int + */ + public function getFitToHeight() + { + return $this->fitToHeight; + } + + /** + * Set Fit To Height. + * + * @param null|int $fitToHeight + * @param bool $update Update fitToPage so it applies rather than scaling + * + * @return $this + */ + public function setFitToHeight($fitToHeight, $update = true) + { + $this->fitToHeight = $fitToHeight; + if ($update) { + $this->fitToPage = true; + } + + return $this; + } + + /** + * Get Fit To Width. + * + * @return null|int + */ + public function getFitToWidth() + { + return $this->fitToWidth; + } + + /** + * Set Fit To Width. + * + * @param null|int $value + * @param bool $update Update fitToPage so it applies rather than scaling + * + * @return $this + */ + public function setFitToWidth($value, $update = true) + { + $this->fitToWidth = $value; + if ($update) { + $this->fitToPage = true; + } + + return $this; + } + + /** + * Is Columns to repeat at left set? + * + * @return bool + */ + public function isColumnsToRepeatAtLeftSet() + { + if (is_array($this->columnsToRepeatAtLeft)) { + if ($this->columnsToRepeatAtLeft[0] != '' && $this->columnsToRepeatAtLeft[1] != '') { + return true; + } + } + + return false; + } + + /** + * Get Columns to repeat at left. + * + * @return array Containing start column and end column, empty array if option unset + */ + public function getColumnsToRepeatAtLeft() + { + return $this->columnsToRepeatAtLeft; + } + + /** + * Set Columns to repeat at left. + * + * @param array $columnsToRepeatAtLeft Containing start column and end column, empty array if option unset + * + * @return $this + */ + public function setColumnsToRepeatAtLeft(array $columnsToRepeatAtLeft) + { + $this->columnsToRepeatAtLeft = $columnsToRepeatAtLeft; + + return $this; + } + + /** + * Set Columns to repeat at left by start and end. + * + * @param string $start eg: 'A' + * @param string $end eg: 'B' + * + * @return $this + */ + public function setColumnsToRepeatAtLeftByStartAndEnd($start, $end) + { + $this->columnsToRepeatAtLeft = [$start, $end]; + + return $this; + } + + /** + * Is Rows to repeat at top set? + * + * @return bool + */ + public function isRowsToRepeatAtTopSet() + { + if (is_array($this->rowsToRepeatAtTop)) { + if ($this->rowsToRepeatAtTop[0] != 0 && $this->rowsToRepeatAtTop[1] != 0) { + return true; + } + } + + return false; + } + + /** + * Get Rows to repeat at top. + * + * @return array Containing start column and end column, empty array if option unset + */ + public function getRowsToRepeatAtTop() + { + return $this->rowsToRepeatAtTop; + } + + /** + * Set Rows to repeat at top. + * + * @param array $rowsToRepeatAtTop Containing start column and end column, empty array if option unset + * + * @return $this + */ + public function setRowsToRepeatAtTop(array $rowsToRepeatAtTop) + { + $this->rowsToRepeatAtTop = $rowsToRepeatAtTop; + + return $this; + } + + /** + * Set Rows to repeat at top by start and end. + * + * @param int $start eg: 1 + * @param int $end eg: 1 + * + * @return $this + */ + public function setRowsToRepeatAtTopByStartAndEnd($start, $end) + { + $this->rowsToRepeatAtTop = [$start, $end]; + + return $this; + } + + /** + * Get center page horizontally. + * + * @return bool + */ + public function getHorizontalCentered() + { + return $this->horizontalCentered; + } + + /** + * Set center page horizontally. + * + * @param bool $value + * + * @return $this + */ + public function setHorizontalCentered($value) + { + $this->horizontalCentered = $value; + + return $this; + } + + /** + * Get center page vertically. + * + * @return bool + */ + public function getVerticalCentered() + { + return $this->verticalCentered; + } + + /** + * Set center page vertically. + * + * @param bool $value + * + * @return $this + */ + public function setVerticalCentered($value) + { + $this->verticalCentered = $value; + + return $this; + } + + /** + * Get print area. + * + * @param int $index Identifier for a specific print area range if several ranges have been set + * Default behaviour, or a index value of 0, will return all ranges as a comma-separated string + * Otherwise, the specific range identified by the value of $index will be returned + * Print areas are numbered from 1 + * + * @return string + */ + public function getPrintArea($index = 0) + { + if ($index == 0) { + return $this->printArea; + } + $printAreas = explode(',', $this->printArea); + if (isset($printAreas[$index - 1])) { + return $printAreas[$index - 1]; + } + + throw new PhpSpreadsheetException('Requested Print Area does not exist'); + } + + /** + * Is print area set? + * + * @param int $index Identifier for a specific print area range if several ranges have been set + * Default behaviour, or an index value of 0, will identify whether any print range is set + * Otherwise, existence of the range identified by the value of $index will be returned + * Print areas are numbered from 1 + * + * @return bool + */ + public function isPrintAreaSet($index = 0) + { + if ($index == 0) { + return $this->printArea !== null; + } + $printAreas = explode(',', $this->printArea); + + return isset($printAreas[$index - 1]); + } + + /** + * Clear a print area. + * + * @param int $index Identifier for a specific print area range if several ranges have been set + * Default behaviour, or an index value of 0, will clear all print ranges that are set + * Otherwise, the range identified by the value of $index will be removed from the series + * Print areas are numbered from 1 + * + * @return $this + */ + public function clearPrintArea($index = 0) + { + if ($index == 0) { + $this->printArea = null; + } else { + $printAreas = explode(',', $this->printArea); + if (isset($printAreas[$index - 1])) { + unset($printAreas[$index - 1]); + $this->printArea = implode(',', $printAreas); + } + } + + return $this; + } + + /** + * Set print area. e.g. 'A1:D10' or 'A1:D10,G5:M20'. + * + * @param string $value + * @param int $index Identifier for a specific print area range allowing several ranges to be set + * When the method is "O"verwrite, then a positive integer index will overwrite that indexed + * entry in the print areas list; a negative index value will identify which entry to + * overwrite working bacward through the print area to the list, with the last entry as -1. + * Specifying an index value of 0, will overwrite all existing print ranges. + * When the method is "I"nsert, then a positive index will insert after that indexed entry in + * the print areas list, while a negative index will insert before the indexed entry. + * Specifying an index value of 0, will always append the new print range at the end of the + * list. + * Print areas are numbered from 1 + * @param string $method Determines the method used when setting multiple print areas + * Default behaviour, or the "O" method, overwrites existing print area + * The "I" method, inserts the new print area before any specified index, or at the end of the list + * + * @return $this + */ + public function setPrintArea($value, $index = 0, $method = self::SETPRINTRANGE_OVERWRITE) + { + if (strpos($value, '!') !== false) { + throw new PhpSpreadsheetException('Cell coordinate must not specify a worksheet.'); + } elseif (strpos($value, ':') === false) { + throw new PhpSpreadsheetException('Cell coordinate must be a range of cells.'); + } elseif (strpos($value, '$') !== false) { + throw new PhpSpreadsheetException('Cell coordinate must not be absolute.'); + } + $value = strtoupper($value); + if (!$this->printArea) { + $index = 0; + } + + if ($method == self::SETPRINTRANGE_OVERWRITE) { + if ($index == 0) { + $this->printArea = $value; + } else { + $printAreas = explode(',', $this->printArea); + if ($index < 0) { + $index = count($printAreas) - abs($index) + 1; + } + if (($index <= 0) || ($index > count($printAreas))) { + throw new PhpSpreadsheetException('Invalid index for setting print range.'); + } + $printAreas[$index - 1] = $value; + $this->printArea = implode(',', $printAreas); + } + } elseif ($method == self::SETPRINTRANGE_INSERT) { + if ($index == 0) { + $this->printArea = $this->printArea ? ($this->printArea . ',' . $value) : $value; + } else { + $printAreas = explode(',', $this->printArea); + if ($index < 0) { + $index = abs($index) - 1; + } + if ($index > count($printAreas)) { + throw new PhpSpreadsheetException('Invalid index for setting print range.'); + } + $printAreas = array_merge(array_slice($printAreas, 0, $index), [$value], array_slice($printAreas, $index)); + $this->printArea = implode(',', $printAreas); + } + } else { + throw new PhpSpreadsheetException('Invalid method for setting print range.'); + } + + return $this; + } + + /** + * Add a new print area (e.g. 'A1:D10' or 'A1:D10,G5:M20') to the list of print areas. + * + * @param string $value + * @param int $index Identifier for a specific print area range allowing several ranges to be set + * A positive index will insert after that indexed entry in the print areas list, while a + * negative index will insert before the indexed entry. + * Specifying an index value of 0, will always append the new print range at the end of the + * list. + * Print areas are numbered from 1 + * + * @return $this + */ + public function addPrintArea($value, $index = -1) + { + return $this->setPrintArea($value, $index, self::SETPRINTRANGE_INSERT); + } + + /** + * Set print area. + * + * @param int $column1 Column 1 + * @param int $row1 Row 1 + * @param int $column2 Column 2 + * @param int $row2 Row 2 + * @param int $index Identifier for a specific print area range allowing several ranges to be set + * When the method is "O"verwrite, then a positive integer index will overwrite that indexed + * entry in the print areas list; a negative index value will identify which entry to + * overwrite working backward through the print area to the list, with the last entry as -1. + * Specifying an index value of 0, will overwrite all existing print ranges. + * When the method is "I"nsert, then a positive index will insert after that indexed entry in + * the print areas list, while a negative index will insert before the indexed entry. + * Specifying an index value of 0, will always append the new print range at the end of the + * list. + * Print areas are numbered from 1 + * @param string $method Determines the method used when setting multiple print areas + * Default behaviour, or the "O" method, overwrites existing print area + * The "I" method, inserts the new print area before any specified index, or at the end of the list + * + * @return $this + */ + public function setPrintAreaByColumnAndRow($column1, $row1, $column2, $row2, $index = 0, $method = self::SETPRINTRANGE_OVERWRITE) + { + return $this->setPrintArea( + Coordinate::stringFromColumnIndex($column1) . $row1 . ':' . Coordinate::stringFromColumnIndex($column2) . $row2, + $index, + $method + ); + } + + /** + * Add a new print area to the list of print areas. + * + * @param int $column1 Start Column for the print area + * @param int $row1 Start Row for the print area + * @param int $column2 End Column for the print area + * @param int $row2 End Row for the print area + * @param int $index Identifier for a specific print area range allowing several ranges to be set + * A positive index will insert after that indexed entry in the print areas list, while a + * negative index will insert before the indexed entry. + * Specifying an index value of 0, will always append the new print range at the end of the + * list. + * Print areas are numbered from 1 + * + * @return $this + */ + public function addPrintAreaByColumnAndRow($column1, $row1, $column2, $row2, $index = -1) + { + return $this->setPrintArea( + Coordinate::stringFromColumnIndex($column1) . $row1 . ':' . Coordinate::stringFromColumnIndex($column2) . $row2, + $index, + self::SETPRINTRANGE_INSERT + ); + } + + /** + * Get first page number. + * + * @return int + */ + public function getFirstPageNumber() + { + return $this->firstPageNumber; + } + + /** + * Set first page number. + * + * @param int $value + * + * @return $this + */ + public function setFirstPageNumber($value) + { + $this->firstPageNumber = $value; + + return $this; + } + + /** + * Reset first page number. + * + * @return $this + */ + public function resetFirstPageNumber() + { + return $this->setFirstPageNumber(null); + } + + public function getPageOrder(): string + { + return $this->pageOrder; + } + + public function setPageOrder(?string $pageOrder): self + { + if ($pageOrder === null || $pageOrder === self::PAGEORDER_DOWN_THEN_OVER || $pageOrder === self::PAGEORDER_OVER_THEN_DOWN) { + $this->pageOrder = $pageOrder ?? self::PAGEORDER_DOWN_THEN_OVER; + } + + return $this; + } + + /** + * Implement PHP __clone to create a deep clone, not just a shallow copy. + */ + public function __clone() + { + $vars = get_object_vars($this); + foreach ($vars as $key => $value) { + if (is_object($value)) { + $this->$key = clone $value; + } else { + $this->$key = $value; + } + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Protection.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Protection.php new file mode 100644 index 0000000..4d44d8e --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Protection.php @@ -0,0 +1,691 @@ +sheet || + $this->objects || + $this->scenarios || + $this->formatCells || + $this->formatColumns || + $this->formatRows || + $this->insertColumns || + $this->insertRows || + $this->insertHyperlinks || + $this->deleteColumns || + $this->deleteRows || + $this->selectLockedCells || + $this->sort || + $this->autoFilter || + $this->pivotTables || + $this->selectUnlockedCells; + } + + /** + * Get Sheet. + * + * @return bool + */ + public function getSheet() + { + return $this->sheet; + } + + /** + * Set Sheet. + * + * @param bool $sheet + * + * @return $this + */ + public function setSheet($sheet) + { + $this->sheet = $sheet; + + return $this; + } + + /** + * Get Objects. + * + * @return bool + */ + public function getObjects() + { + return $this->objects; + } + + /** + * Set Objects. + * + * @param bool $objects + * + * @return $this + */ + public function setObjects($objects) + { + $this->objects = $objects; + + return $this; + } + + /** + * Get Scenarios. + * + * @return bool + */ + public function getScenarios() + { + return $this->scenarios; + } + + /** + * Set Scenarios. + * + * @param bool $scenarios + * + * @return $this + */ + public function setScenarios($scenarios) + { + $this->scenarios = $scenarios; + + return $this; + } + + /** + * Get FormatCells. + * + * @return bool + */ + public function getFormatCells() + { + return $this->formatCells; + } + + /** + * Set FormatCells. + * + * @param bool $formatCells + * + * @return $this + */ + public function setFormatCells($formatCells) + { + $this->formatCells = $formatCells; + + return $this; + } + + /** + * Get FormatColumns. + * + * @return bool + */ + public function getFormatColumns() + { + return $this->formatColumns; + } + + /** + * Set FormatColumns. + * + * @param bool $formatColumns + * + * @return $this + */ + public function setFormatColumns($formatColumns) + { + $this->formatColumns = $formatColumns; + + return $this; + } + + /** + * Get FormatRows. + * + * @return bool + */ + public function getFormatRows() + { + return $this->formatRows; + } + + /** + * Set FormatRows. + * + * @param bool $formatRows + * + * @return $this + */ + public function setFormatRows($formatRows) + { + $this->formatRows = $formatRows; + + return $this; + } + + /** + * Get InsertColumns. + * + * @return bool + */ + public function getInsertColumns() + { + return $this->insertColumns; + } + + /** + * Set InsertColumns. + * + * @param bool $insertColumns + * + * @return $this + */ + public function setInsertColumns($insertColumns) + { + $this->insertColumns = $insertColumns; + + return $this; + } + + /** + * Get InsertRows. + * + * @return bool + */ + public function getInsertRows() + { + return $this->insertRows; + } + + /** + * Set InsertRows. + * + * @param bool $insertRows + * + * @return $this + */ + public function setInsertRows($insertRows) + { + $this->insertRows = $insertRows; + + return $this; + } + + /** + * Get InsertHyperlinks. + * + * @return bool + */ + public function getInsertHyperlinks() + { + return $this->insertHyperlinks; + } + + /** + * Set InsertHyperlinks. + * + * @param bool $insertHyperLinks + * + * @return $this + */ + public function setInsertHyperlinks($insertHyperLinks) + { + $this->insertHyperlinks = $insertHyperLinks; + + return $this; + } + + /** + * Get DeleteColumns. + * + * @return bool + */ + public function getDeleteColumns() + { + return $this->deleteColumns; + } + + /** + * Set DeleteColumns. + * + * @param bool $deleteColumns + * + * @return $this + */ + public function setDeleteColumns($deleteColumns) + { + $this->deleteColumns = $deleteColumns; + + return $this; + } + + /** + * Get DeleteRows. + * + * @return bool + */ + public function getDeleteRows() + { + return $this->deleteRows; + } + + /** + * Set DeleteRows. + * + * @param bool $deleteRows + * + * @return $this + */ + public function setDeleteRows($deleteRows) + { + $this->deleteRows = $deleteRows; + + return $this; + } + + /** + * Get SelectLockedCells. + * + * @return bool + */ + public function getSelectLockedCells() + { + return $this->selectLockedCells; + } + + /** + * Set SelectLockedCells. + * + * @param bool $selectLockedCells + * + * @return $this + */ + public function setSelectLockedCells($selectLockedCells) + { + $this->selectLockedCells = $selectLockedCells; + + return $this; + } + + /** + * Get Sort. + * + * @return bool + */ + public function getSort() + { + return $this->sort; + } + + /** + * Set Sort. + * + * @param bool $sort + * + * @return $this + */ + public function setSort($sort) + { + $this->sort = $sort; + + return $this; + } + + /** + * Get AutoFilter. + * + * @return bool + */ + public function getAutoFilter() + { + return $this->autoFilter; + } + + /** + * Set AutoFilter. + * + * @param bool $autoFilter + * + * @return $this + */ + public function setAutoFilter($autoFilter) + { + $this->autoFilter = $autoFilter; + + return $this; + } + + /** + * Get PivotTables. + * + * @return bool + */ + public function getPivotTables() + { + return $this->pivotTables; + } + + /** + * Set PivotTables. + * + * @param bool $pivotTables + * + * @return $this + */ + public function setPivotTables($pivotTables) + { + $this->pivotTables = $pivotTables; + + return $this; + } + + /** + * Get SelectUnlockedCells. + * + * @return bool + */ + public function getSelectUnlockedCells() + { + return $this->selectUnlockedCells; + } + + /** + * Set SelectUnlockedCells. + * + * @param bool $selectUnlockedCells + * + * @return $this + */ + public function setSelectUnlockedCells($selectUnlockedCells) + { + $this->selectUnlockedCells = $selectUnlockedCells; + + return $this; + } + + /** + * Get hashed password. + * + * @return string + */ + public function getPassword() + { + return $this->password; + } + + /** + * Set Password. + * + * @param string $password + * @param bool $alreadyHashed If the password has already been hashed, set this to true + * + * @return $this + */ + public function setPassword($password, $alreadyHashed = false) + { + if (!$alreadyHashed) { + $salt = $this->generateSalt(); + $this->setSalt($salt); + $password = PasswordHasher::hashPassword($password, $this->getAlgorithm(), $this->getSalt(), $this->getSpinCount()); + } + + $this->password = $password; + + return $this; + } + + /** + * Create a pseudorandom string. + */ + private function generateSalt(): string + { + return base64_encode(random_bytes(16)); + } + + /** + * Get algorithm name. + */ + public function getAlgorithm(): string + { + return $this->algorithm; + } + + /** + * Set algorithm name. + */ + public function setAlgorithm(string $algorithm): void + { + $this->algorithm = $algorithm; + } + + /** + * Get salt value. + */ + public function getSalt(): string + { + return $this->salt; + } + + /** + * Set salt value. + */ + public function setSalt(string $salt): void + { + $this->salt = $salt; + } + + /** + * Get spin count. + */ + public function getSpinCount(): int + { + return $this->spinCount; + } + + /** + * Set spin count. + */ + public function setSpinCount(int $spinCount): void + { + $this->spinCount = $spinCount; + } + + /** + * Verify that the given non-hashed password can "unlock" the protection. + */ + public function verify(string $password): bool + { + if (!$this->isProtectionEnabled()) { + return true; + } + + $hash = PasswordHasher::hashPassword($password, $this->getAlgorithm(), $this->getSalt(), $this->getSpinCount()); + + return $this->getPassword() === $hash; + } + + /** + * Implement PHP __clone to create a deep clone, not just a shallow copy. + */ + public function __clone() + { + $vars = get_object_vars($this); + foreach ($vars as $key => $value) { + if (is_object($value)) { + $this->$key = clone $value; + } else { + $this->$key = $value; + } + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Row.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Row.php new file mode 100644 index 0000000..b593335 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Row.php @@ -0,0 +1,70 @@ +worksheet = $worksheet; + $this->rowIndex = $rowIndex; + } + + /** + * Destructor. + */ + public function __destruct() + { + // @phpstan-ignore-next-line + $this->worksheet = null; + } + + /** + * Get row index. + */ + public function getRowIndex(): int + { + return $this->rowIndex; + } + + /** + * Get cell iterator. + * + * @param string $startColumn The column address at which to start iterating + * @param string $endColumn Optionally, the column address at which to stop iterating + * + * @return RowCellIterator + */ + public function getCellIterator($startColumn = 'A', $endColumn = null) + { + return new RowCellIterator($this->worksheet, $this->rowIndex, $startColumn, $endColumn); + } + + /** + * Returns bound worksheet. + */ + public function getWorksheet(): Worksheet + { + return $this->worksheet; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowCellIterator.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowCellIterator.php new file mode 100644 index 0000000..a78765b --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowCellIterator.php @@ -0,0 +1,187 @@ + + */ +class RowCellIterator extends CellIterator +{ + /** + * Current iterator position. + * + * @var int + */ + private $currentColumnIndex; + + /** + * Row index. + * + * @var int + */ + private $rowIndex = 1; + + /** + * Start position. + * + * @var int + */ + private $startColumnIndex = 1; + + /** + * End position. + * + * @var int + */ + private $endColumnIndex = 1; + + /** + * Create a new column iterator. + * + * @param Worksheet $worksheet The worksheet to iterate over + * @param int $rowIndex The row that we want to iterate + * @param string $startColumn The column address at which to start iterating + * @param string $endColumn Optionally, the column address at which to stop iterating + */ + public function __construct(Worksheet $worksheet, $rowIndex = 1, $startColumn = 'A', $endColumn = null) + { + // Set subject and row index + $this->worksheet = $worksheet; + $this->rowIndex = $rowIndex; + $this->resetEnd($endColumn); + $this->resetStart($startColumn); + } + + /** + * (Re)Set the start column and the current column pointer. + * + * @param string $startColumn The column address at which to start iterating + * + * @return $this + */ + public function resetStart(string $startColumn = 'A') + { + $this->startColumnIndex = Coordinate::columnIndexFromString($startColumn); + $this->adjustForExistingOnlyRange(); + $this->seek(Coordinate::stringFromColumnIndex($this->startColumnIndex)); + + return $this; + } + + /** + * (Re)Set the end column. + * + * @param string $endColumn The column address at which to stop iterating + * + * @return $this + */ + public function resetEnd($endColumn = null) + { + $endColumn = $endColumn ?: $this->worksheet->getHighestColumn(); + $this->endColumnIndex = Coordinate::columnIndexFromString($endColumn); + $this->adjustForExistingOnlyRange(); + + return $this; + } + + /** + * Set the column pointer to the selected column. + * + * @param string $column The column address to set the current pointer at + * + * @return $this + */ + public function seek(string $column = 'A') + { + $columnx = $column; + $column = Coordinate::columnIndexFromString($column); + if ($this->onlyExistingCells && !($this->worksheet->cellExistsByColumnAndRow($column, $this->rowIndex))) { + throw new PhpSpreadsheetException('In "IterateOnlyExistingCells" mode and Cell does not exist'); + } + if (($column < $this->startColumnIndex) || ($column > $this->endColumnIndex)) { + throw new PhpSpreadsheetException("Column $columnx is out of range ({$this->startColumnIndex} - {$this->endColumnIndex})"); + } + $this->currentColumnIndex = $column; + + return $this; + } + + /** + * Rewind the iterator to the starting column. + */ + public function rewind(): void + { + $this->currentColumnIndex = $this->startColumnIndex; + } + + /** + * Return the current cell in this worksheet row. + */ + public function current(): ?Cell + { + return $this->worksheet->getCellByColumnAndRow($this->currentColumnIndex, $this->rowIndex); + } + + /** + * Return the current iterator key. + */ + public function key(): string + { + return Coordinate::stringFromColumnIndex($this->currentColumnIndex); + } + + /** + * Set the iterator to its next value. + */ + public function next(): void + { + do { + ++$this->currentColumnIndex; + } while (($this->onlyExistingCells) && (!$this->worksheet->cellExistsByColumnAndRow($this->currentColumnIndex, $this->rowIndex)) && ($this->currentColumnIndex <= $this->endColumnIndex)); + } + + /** + * Set the iterator to its previous value. + */ + public function prev(): void + { + do { + --$this->currentColumnIndex; + } while (($this->onlyExistingCells) && (!$this->worksheet->cellExistsByColumnAndRow($this->currentColumnIndex, $this->rowIndex)) && ($this->currentColumnIndex >= $this->startColumnIndex)); + } + + /** + * Indicate if more columns exist in the worksheet range of columns that we're iterating. + */ + public function valid(): bool + { + return $this->currentColumnIndex <= $this->endColumnIndex && $this->currentColumnIndex >= $this->startColumnIndex; + } + + /** + * Return the current iterator position. + */ + public function getCurrentColumnIndex(): int + { + return $this->currentColumnIndex; + } + + /** + * Validate start/end values for "IterateOnlyExistingCells" mode, and adjust if necessary. + */ + protected function adjustForExistingOnlyRange(): void + { + if ($this->onlyExistingCells) { + while ((!$this->worksheet->cellExistsByColumnAndRow($this->startColumnIndex, $this->rowIndex)) && ($this->startColumnIndex <= $this->endColumnIndex)) { + ++$this->startColumnIndex; + } + while ((!$this->worksheet->cellExistsByColumnAndRow($this->endColumnIndex, $this->rowIndex)) && ($this->endColumnIndex >= $this->startColumnIndex)) { + --$this->endColumnIndex; + } + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowDimension.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowDimension.php new file mode 100644 index 0000000..1d8aada --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowDimension.php @@ -0,0 +1,117 @@ +rowIndex = $index; + + // set dimension as unformatted by default + parent::__construct(null); + } + + /** + * Get Row Index. + */ + public function getRowIndex(): int + { + return $this->rowIndex; + } + + /** + * Set Row Index. + * + * @return $this + */ + public function setRowIndex(int $index) + { + $this->rowIndex = $index; + + return $this; + } + + /** + * Get Row Height. + * By default, this will be in points; but this method accepts a unit of measure + * argument, and will convert the value to the specified UoM. + * + * @return float + */ + public function getRowHeight(?string $unitOfMeasure = null) + { + return ($unitOfMeasure === null || $this->height < 0) + ? $this->height + : (new CssDimension($this->height . CssDimension::UOM_POINTS))->toUnit($unitOfMeasure); + } + + /** + * Set Row Height. + * + * @param float $height in points + * By default, this will be the passed argument value; but this method accepts a unit of measure + * argument, and will convert the passed argument value to points from the specified UoM + * + * @return $this + */ + public function setRowHeight($height, ?string $unitOfMeasure = null) + { + $this->height = ($unitOfMeasure === null || $height < 0) + ? $height + : (new CssDimension("{$height}{$unitOfMeasure}"))->height(); + + return $this; + } + + /** + * Get ZeroHeight. + */ + public function getZeroHeight(): bool + { + return $this->zeroHeight; + } + + /** + * Set ZeroHeight. + * + * @return $this + */ + public function setZeroHeight(bool $zeroHeight) + { + $this->zeroHeight = $zeroHeight; + + return $this; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowIterator.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowIterator.php new file mode 100644 index 0000000..5c51bfe --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowIterator.php @@ -0,0 +1,158 @@ + + */ +class RowIterator implements Iterator +{ + /** + * Worksheet to iterate. + * + * @var Worksheet + */ + private $subject; + + /** + * Current iterator position. + * + * @var int + */ + private $position = 1; + + /** + * Start position. + * + * @var int + */ + private $startRow = 1; + + /** + * End position. + * + * @var int + */ + private $endRow = 1; + + /** + * Create a new row iterator. + * + * @param Worksheet $subject The worksheet to iterate over + * @param int $startRow The row number at which to start iterating + * @param int $endRow Optionally, the row number at which to stop iterating + */ + public function __construct(Worksheet $subject, $startRow = 1, $endRow = null) + { + // Set subject + $this->subject = $subject; + $this->resetEnd($endRow); + $this->resetStart($startRow); + } + + /** + * (Re)Set the start row and the current row pointer. + * + * @param int $startRow The row number at which to start iterating + * + * @return $this + */ + public function resetStart(int $startRow = 1) + { + if ($startRow > $this->subject->getHighestRow()) { + throw new PhpSpreadsheetException( + "Start row ({$startRow}) is beyond highest row ({$this->subject->getHighestRow()})" + ); + } + + $this->startRow = $startRow; + if ($this->endRow < $this->startRow) { + $this->endRow = $this->startRow; + } + $this->seek($startRow); + + return $this; + } + + /** + * (Re)Set the end row. + * + * @param int $endRow The row number at which to stop iterating + * + * @return $this + */ + public function resetEnd($endRow = null) + { + $this->endRow = $endRow ?: $this->subject->getHighestRow(); + + return $this; + } + + /** + * Set the row pointer to the selected row. + * + * @param int $row The row number to set the current pointer at + * + * @return $this + */ + public function seek(int $row = 1) + { + if (($row < $this->startRow) || ($row > $this->endRow)) { + throw new PhpSpreadsheetException("Row $row is out of range ({$this->startRow} - {$this->endRow})"); + } + $this->position = $row; + + return $this; + } + + /** + * Rewind the iterator to the starting row. + */ + public function rewind(): void + { + $this->position = $this->startRow; + } + + /** + * Return the current row in this worksheet. + */ + public function current(): Row + { + return new Row($this->subject, $this->position); + } + + /** + * Return the current iterator key. + */ + public function key(): int + { + return $this->position; + } + + /** + * Set the iterator to its next value. + */ + public function next(): void + { + ++$this->position; + } + + /** + * Set the iterator to its previous value. + */ + public function prev(): void + { + --$this->position; + } + + /** + * Indicate if more rows exist in the worksheet range of rows that we're iterating. + */ + public function valid(): bool + { + return $this->position <= $this->endRow && $this->position >= $this->startRow; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/SheetView.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/SheetView.php new file mode 100644 index 0000000..80c4433 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/SheetView.php @@ -0,0 +1,193 @@ +zoomScale; + } + + /** + * Set ZoomScale. + * Valid values range from 10 to 400. + * + * @param int $zoomScale + * + * @return $this + */ + public function setZoomScale($zoomScale) + { + // Microsoft Office Excel 2007 only allows setting a scale between 10 and 400 via the user interface, + // but it is apparently still able to handle any scale >= 1 + if (($zoomScale >= 1) || $zoomScale === null) { + $this->zoomScale = $zoomScale; + } else { + throw new PhpSpreadsheetException('Scale must be greater than or equal to 1.'); + } + + return $this; + } + + /** + * Get ZoomScaleNormal. + * + * @return int + */ + public function getZoomScaleNormal() + { + return $this->zoomScaleNormal; + } + + /** + * Set ZoomScale. + * Valid values range from 10 to 400. + * + * @param int $zoomScaleNormal + * + * @return $this + */ + public function setZoomScaleNormal($zoomScaleNormal) + { + if (($zoomScaleNormal >= 1) || $zoomScaleNormal === null) { + $this->zoomScaleNormal = $zoomScaleNormal; + } else { + throw new PhpSpreadsheetException('Scale must be greater than or equal to 1.'); + } + + return $this; + } + + /** + * Set ShowZeroes setting. + * + * @param bool $showZeros + */ + public function setShowZeros($showZeros): void + { + $this->showZeros = $showZeros; + } + + /** + * @return bool + */ + public function getShowZeros() + { + return $this->showZeros; + } + + /** + * Get View. + * + * @return string + */ + public function getView() + { + return $this->sheetviewType; + } + + /** + * Set View. + * + * Valid values are + * 'normal' self::SHEETVIEW_NORMAL + * 'pageLayout' self::SHEETVIEW_PAGE_LAYOUT + * 'pageBreakPreview' self::SHEETVIEW_PAGE_BREAK_PREVIEW + * + * @param string $sheetViewType + * + * @return $this + */ + public function setView($sheetViewType) + { + // MS Excel 2007 allows setting the view to 'normal', 'pageLayout' or 'pageBreakPreview' via the user interface + if ($sheetViewType === null) { + $sheetViewType = self::SHEETVIEW_NORMAL; + } + if (in_array($sheetViewType, self::$sheetViewTypes)) { + $this->sheetviewType = $sheetViewType; + } else { + throw new PhpSpreadsheetException('Invalid sheetview layout type.'); + } + + return $this; + } + + /** + * Implement PHP __clone to create a deep clone, not just a shallow copy. + */ + public function __clone() + { + $vars = get_object_vars($this); + foreach ($vars as $key => $value) { + if (is_object($value)) { + $this->$key = clone $value; + } else { + $this->$key = $value; + } + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Worksheet.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Worksheet.php new file mode 100644 index 0000000..24da07a --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Worksheet.php @@ -0,0 +1,3090 @@ + + */ + private $drawingCollection; + + /** + * Collection of Chart objects. + * + * @var ArrayObject + */ + private $chartCollection; + + /** + * Worksheet title. + * + * @var string + */ + private $title; + + /** + * Sheet state. + * + * @var string + */ + private $sheetState; + + /** + * Page setup. + * + * @var PageSetup + */ + private $pageSetup; + + /** + * Page margins. + * + * @var PageMargins + */ + private $pageMargins; + + /** + * Page header/footer. + * + * @var HeaderFooter + */ + private $headerFooter; + + /** + * Sheet view. + * + * @var SheetView + */ + private $sheetView; + + /** + * Protection. + * + * @var Protection + */ + private $protection; + + /** + * Collection of styles. + * + * @var Style[] + */ + private $styles = []; + + /** + * Conditional styles. Indexed by cell coordinate, e.g. 'A1'. + * + * @var array + */ + private $conditionalStylesCollection = []; + + /** + * Is the current cell collection sorted already? + * + * @var bool + */ + private $cellCollectionIsSorted = false; + + /** + * Collection of breaks. + * + * @var int[] + */ + private $breaks = []; + + /** + * Collection of merged cell ranges. + * + * @var string[] + */ + private $mergeCells = []; + + /** + * Collection of protected cell ranges. + * + * @var string[] + */ + private $protectedCells = []; + + /** + * Autofilter Range and selection. + * + * @var AutoFilter + */ + private $autoFilter; + + /** + * Freeze pane. + * + * @var null|string + */ + private $freezePane; + + /** + * Default position of the right bottom pane. + * + * @var null|string + */ + private $topLeftCell; + + /** + * Show gridlines? + * + * @var bool + */ + private $showGridlines = true; + + /** + * Print gridlines? + * + * @var bool + */ + private $printGridlines = false; + + /** + * Show row and column headers? + * + * @var bool + */ + private $showRowColHeaders = true; + + /** + * Show summary below? (Row/Column outline). + * + * @var bool + */ + private $showSummaryBelow = true; + + /** + * Show summary right? (Row/Column outline). + * + * @var bool + */ + private $showSummaryRight = true; + + /** + * Collection of comments. + * + * @var Comment[] + */ + private $comments = []; + + /** + * Active cell. (Only one!). + * + * @var string + */ + private $activeCell = 'A1'; + + /** + * Selected cells. + * + * @var string + */ + private $selectedCells = 'A1'; + + /** + * Cached highest column. + * + * @var int + */ + private $cachedHighestColumn = 1; + + /** + * Cached highest row. + * + * @var int + */ + private $cachedHighestRow = 1; + + /** + * Right-to-left? + * + * @var bool + */ + private $rightToLeft = false; + + /** + * Hyperlinks. Indexed by cell coordinate, e.g. 'A1'. + * + * @var array + */ + private $hyperlinkCollection = []; + + /** + * Data validation objects. Indexed by cell coordinate, e.g. 'A1'. + * + * @var array + */ + private $dataValidationCollection = []; + + /** + * Tab color. + * + * @var null|Color + */ + private $tabColor; + + /** + * Dirty flag. + * + * @var bool + */ + private $dirty = true; + + /** + * Hash. + * + * @var string + */ + private $hash; + + /** + * CodeName. + * + * @var string + */ + private $codeName; + + /** + * Create a new worksheet. + * + * @param string $title + */ + public function __construct(?Spreadsheet $parent = null, $title = 'Worksheet') + { + // Set parent and title + $this->parent = $parent; + $this->setTitle($title, false); + // setTitle can change $pTitle + $this->setCodeName($this->getTitle()); + $this->setSheetState(self::SHEETSTATE_VISIBLE); + + $this->cellCollection = CellsFactory::getInstance($this); + // Set page setup + $this->pageSetup = new PageSetup(); + // Set page margins + $this->pageMargins = new PageMargins(); + // Set page header/footer + $this->headerFooter = new HeaderFooter(); + // Set sheet view + $this->sheetView = new SheetView(); + // Drawing collection + $this->drawingCollection = new ArrayObject(); + // Chart collection + $this->chartCollection = new ArrayObject(); + // Protection + $this->protection = new Protection(); + // Default row dimension + $this->defaultRowDimension = new RowDimension(null); + // Default column dimension + $this->defaultColumnDimension = new ColumnDimension(null); + $this->autoFilter = new AutoFilter(null, $this); + } + + /** + * Disconnect all cells from this Worksheet object, + * typically so that the worksheet object can be unset. + */ + public function disconnectCells(): void + { + if ($this->cellCollection !== null) { + $this->cellCollection->unsetWorksheetCells(); + // @phpstan-ignore-next-line + $this->cellCollection = null; + } + // detach ourself from the workbook, so that it can then delete this worksheet successfully + // @phpstan-ignore-next-line + $this->parent = null; + } + + /** + * Code to execute when this worksheet is unset(). + */ + public function __destruct() + { + Calculation::getInstance($this->parent)->clearCalculationCacheForWorksheet($this->title); + + $this->disconnectCells(); + $this->rowDimensions = []; + } + + /** + * Return the cell collection. + * + * @return Cells + */ + public function getCellCollection() + { + return $this->cellCollection; + } + + /** + * Get array of invalid characters for sheet title. + * + * @return array + */ + public static function getInvalidCharacters() + { + return self::$invalidCharacters; + } + + /** + * Check sheet code name for valid Excel syntax. + * + * @param string $sheetCodeName The string to check + * + * @return string The valid string + */ + private static function checkSheetCodeName($sheetCodeName) + { + $charCount = Shared\StringHelper::countCharacters($sheetCodeName); + if ($charCount == 0) { + throw new Exception('Sheet code name cannot be empty.'); + } + // Some of the printable ASCII characters are invalid: * : / \ ? [ ] and first and last characters cannot be a "'" + if ( + (str_replace(self::$invalidCharacters, '', $sheetCodeName) !== $sheetCodeName) || + (Shared\StringHelper::substring($sheetCodeName, -1, 1) == '\'') || + (Shared\StringHelper::substring($sheetCodeName, 0, 1) == '\'') + ) { + throw new Exception('Invalid character found in sheet code name'); + } + + // Enforce maximum characters allowed for sheet title + if ($charCount > self::SHEET_TITLE_MAXIMUM_LENGTH) { + throw new Exception('Maximum ' . self::SHEET_TITLE_MAXIMUM_LENGTH . ' characters allowed in sheet code name.'); + } + + return $sheetCodeName; + } + + /** + * Check sheet title for valid Excel syntax. + * + * @param string $sheetTitle The string to check + * + * @return string The valid string + */ + private static function checkSheetTitle($sheetTitle) + { + // Some of the printable ASCII characters are invalid: * : / \ ? [ ] + if (str_replace(self::$invalidCharacters, '', $sheetTitle) !== $sheetTitle) { + throw new Exception('Invalid character found in sheet title'); + } + + // Enforce maximum characters allowed for sheet title + if (Shared\StringHelper::countCharacters($sheetTitle) > self::SHEET_TITLE_MAXIMUM_LENGTH) { + throw new Exception('Maximum ' . self::SHEET_TITLE_MAXIMUM_LENGTH . ' characters allowed in sheet title.'); + } + + return $sheetTitle; + } + + /** + * Get a sorted list of all cell coordinates currently held in the collection by row and column. + * + * @param bool $sorted Also sort the cell collection? + * + * @return string[] + */ + public function getCoordinates($sorted = true) + { + if ($this->cellCollection == null) { + return []; + } + + if ($sorted) { + return $this->cellCollection->getSortedCoordinates(); + } + + return $this->cellCollection->getCoordinates(); + } + + /** + * Get collection of row dimensions. + * + * @return RowDimension[] + */ + public function getRowDimensions() + { + return $this->rowDimensions; + } + + /** + * Get default row dimension. + * + * @return RowDimension + */ + public function getDefaultRowDimension() + { + return $this->defaultRowDimension; + } + + /** + * Get collection of column dimensions. + * + * @return ColumnDimension[] + */ + public function getColumnDimensions() + { + return $this->columnDimensions; + } + + /** + * Get default column dimension. + * + * @return ColumnDimension + */ + public function getDefaultColumnDimension() + { + return $this->defaultColumnDimension; + } + + /** + * Get collection of drawings. + * + * @return ArrayObject + */ + public function getDrawingCollection() + { + return $this->drawingCollection; + } + + /** + * Get collection of charts. + * + * @return ArrayObject + */ + public function getChartCollection() + { + return $this->chartCollection; + } + + /** + * Add chart. + * + * @param null|int $chartIndex Index where chart should go (0,1,..., or null for last) + * + * @return Chart + */ + public function addChart(Chart $chart, $chartIndex = null) + { + $chart->setWorksheet($this); + if ($chartIndex === null) { + $this->chartCollection[] = $chart; + } else { + // Insert the chart at the requested index + array_splice($this->chartCollection, $chartIndex, 0, [$chart]); + } + + return $chart; + } + + /** + * Return the count of charts on this worksheet. + * + * @return int The number of charts + */ + public function getChartCount() + { + return count($this->chartCollection); + } + + /** + * Get a chart by its index position. + * + * @param string $index Chart index position + * + * @return Chart|false + */ + public function getChartByIndex($index) + { + $chartCount = count($this->chartCollection); + if ($chartCount == 0) { + return false; + } + if ($index === null) { + $index = --$chartCount; + } + if (!isset($this->chartCollection[$index])) { + return false; + } + + return $this->chartCollection[$index]; + } + + /** + * Return an array of the names of charts on this worksheet. + * + * @return string[] The names of charts + */ + public function getChartNames() + { + $chartNames = []; + foreach ($this->chartCollection as $chart) { + $chartNames[] = $chart->getName(); + } + + return $chartNames; + } + + /** + * Get a chart by name. + * + * @param string $chartName Chart name + * + * @return Chart|false + */ + public function getChartByName($chartName) + { + $chartCount = count($this->chartCollection); + if ($chartCount == 0) { + return false; + } + foreach ($this->chartCollection as $index => $chart) { + if ($chart->getName() == $chartName) { + return $this->chartCollection[$index]; + } + } + + return false; + } + + /** + * Refresh column dimensions. + * + * @return $this + */ + public function refreshColumnDimensions() + { + $currentColumnDimensions = $this->getColumnDimensions(); + $newColumnDimensions = []; + + foreach ($currentColumnDimensions as $objColumnDimension) { + $newColumnDimensions[$objColumnDimension->getColumnIndex()] = $objColumnDimension; + } + + $this->columnDimensions = $newColumnDimensions; + + return $this; + } + + /** + * Refresh row dimensions. + * + * @return $this + */ + public function refreshRowDimensions() + { + $currentRowDimensions = $this->getRowDimensions(); + $newRowDimensions = []; + + foreach ($currentRowDimensions as $objRowDimension) { + $newRowDimensions[$objRowDimension->getRowIndex()] = $objRowDimension; + } + + $this->rowDimensions = $newRowDimensions; + + return $this; + } + + /** + * Calculate worksheet dimension. + * + * @return string String containing the dimension of this worksheet + */ + public function calculateWorksheetDimension() + { + // Return + return 'A1:' . $this->getHighestColumn() . $this->getHighestRow(); + } + + /** + * Calculate worksheet data dimension. + * + * @return string String containing the dimension of this worksheet that actually contain data + */ + public function calculateWorksheetDataDimension() + { + // Return + return 'A1:' . $this->getHighestDataColumn() . $this->getHighestDataRow(); + } + + /** + * Calculate widths for auto-size columns. + * + * @return $this + */ + public function calculateColumnWidths() + { + // initialize $autoSizes array + $autoSizes = []; + foreach ($this->getColumnDimensions() as $colDimension) { + if ($colDimension->getAutoSize()) { + $autoSizes[$colDimension->getColumnIndex()] = -1; + } + } + + // There is only something to do if there are some auto-size columns + if (!empty($autoSizes)) { + // build list of cells references that participate in a merge + $isMergeCell = []; + foreach ($this->getMergeCells() as $cells) { + foreach (Coordinate::extractAllCellReferencesInRange($cells) as $cellReference) { + $isMergeCell[$cellReference] = true; + } + } + + // loop through all cells in the worksheet + foreach ($this->getCoordinates(false) as $coordinate) { + $cell = $this->getCellOrNull($coordinate); + if ($cell !== null && isset($autoSizes[$this->cellCollection->getCurrentColumn()])) { + //Determine if cell is in merge range + $isMerged = isset($isMergeCell[$this->cellCollection->getCurrentCoordinate()]); + + //By default merged cells should be ignored + $isMergedButProceed = false; + + //The only exception is if it's a merge range value cell of a 'vertical' randge (1 column wide) + if ($isMerged && $cell->isMergeRangeValueCell()) { + $range = $cell->getMergeRange(); + $rangeBoundaries = Coordinate::rangeDimension($range); + if ($rangeBoundaries[0] == 1) { + $isMergedButProceed = true; + } + } + + // Determine width if cell does not participate in a merge or does and is a value cell of 1-column wide range + if (!$isMerged || $isMergedButProceed) { + // Calculated value + // To formatted string + $cellValue = NumberFormat::toFormattedString( + $cell->getCalculatedValue(), + $this->getParent()->getCellXfByIndex($cell->getXfIndex())->getNumberFormat()->getFormatCode() + ); + + if ($cellValue !== null && $cellValue !== '') { + $autoSizes[$this->cellCollection->getCurrentColumn()] = max( + (float) $autoSizes[$this->cellCollection->getCurrentColumn()], + (float) Shared\Font::calculateColumnWidth( + $this->getParent()->getCellXfByIndex($cell->getXfIndex())->getFont(), + $cellValue, + $this->getParent()->getCellXfByIndex($cell->getXfIndex())->getAlignment()->getTextRotation(), + $this->getParent()->getDefaultStyle()->getFont() + ) + ); + } + } + } + } + + // adjust column widths + foreach ($autoSizes as $columnIndex => $width) { + if ($width == -1) { + $width = $this->getDefaultColumnDimension()->getWidth(); + } + $this->getColumnDimension($columnIndex)->setWidth($width); + } + } + + return $this; + } + + /** + * Get parent. + * + * @return Spreadsheet + */ + public function getParent() + { + return $this->parent; + } + + /** + * Re-bind parent. + * + * @return $this + */ + public function rebindParent(Spreadsheet $parent) + { + if ($this->parent !== null) { + $definedNames = $this->parent->getDefinedNames(); + foreach ($definedNames as $definedName) { + $parent->addDefinedName($definedName); + } + + $this->parent->removeSheetByIndex( + $this->parent->getIndex($this) + ); + } + $this->parent = $parent; + + return $this; + } + + /** + * Get title. + * + * @return string + */ + public function getTitle() + { + return $this->title; + } + + /** + * Set title. + * + * @param string $title String containing the dimension of this worksheet + * @param bool $updateFormulaCellReferences Flag indicating whether cell references in formulae should + * be updated to reflect the new sheet name. + * This should be left as the default true, unless you are + * certain that no formula cells on any worksheet contain + * references to this worksheet + * @param bool $validate False to skip validation of new title. WARNING: This should only be set + * at parse time (by Readers), where titles can be assumed to be valid. + * + * @return $this + */ + public function setTitle($title, $updateFormulaCellReferences = true, $validate = true) + { + // Is this a 'rename' or not? + if ($this->getTitle() == $title) { + return $this; + } + + // Old title + $oldTitle = $this->getTitle(); + + if ($validate) { + // Syntax check + self::checkSheetTitle($title); + + if ($this->parent) { + // Is there already such sheet name? + if ($this->parent->sheetNameExists($title)) { + // Use name, but append with lowest possible integer + + if (Shared\StringHelper::countCharacters($title) > 29) { + $title = Shared\StringHelper::substring($title, 0, 29); + } + $i = 1; + while ($this->parent->sheetNameExists($title . ' ' . $i)) { + ++$i; + if ($i == 10) { + if (Shared\StringHelper::countCharacters($title) > 28) { + $title = Shared\StringHelper::substring($title, 0, 28); + } + } elseif ($i == 100) { + if (Shared\StringHelper::countCharacters($title) > 27) { + $title = Shared\StringHelper::substring($title, 0, 27); + } + } + } + + $title .= " $i"; + } + } + } + + // Set title + $this->title = $title; + $this->dirty = true; + + if ($this->parent && $this->parent->getCalculationEngine()) { + // New title + $newTitle = $this->getTitle(); + $this->parent->getCalculationEngine() + ->renameCalculationCacheForWorksheet($oldTitle, $newTitle); + if ($updateFormulaCellReferences) { + ReferenceHelper::getInstance()->updateNamedFormulas($this->parent, $oldTitle, $newTitle); + } + } + + return $this; + } + + /** + * Get sheet state. + * + * @return string Sheet state (visible, hidden, veryHidden) + */ + public function getSheetState() + { + return $this->sheetState; + } + + /** + * Set sheet state. + * + * @param string $value Sheet state (visible, hidden, veryHidden) + * + * @return $this + */ + public function setSheetState($value) + { + $this->sheetState = $value; + + return $this; + } + + /** + * Get page setup. + * + * @return PageSetup + */ + public function getPageSetup() + { + return $this->pageSetup; + } + + /** + * Set page setup. + * + * @return $this + */ + public function setPageSetup(PageSetup $pageSetup) + { + $this->pageSetup = $pageSetup; + + return $this; + } + + /** + * Get page margins. + * + * @return PageMargins + */ + public function getPageMargins() + { + return $this->pageMargins; + } + + /** + * Set page margins. + * + * @return $this + */ + public function setPageMargins(PageMargins $pageMargins) + { + $this->pageMargins = $pageMargins; + + return $this; + } + + /** + * Get page header/footer. + * + * @return HeaderFooter + */ + public function getHeaderFooter() + { + return $this->headerFooter; + } + + /** + * Set page header/footer. + * + * @return $this + */ + public function setHeaderFooter(HeaderFooter $headerFooter) + { + $this->headerFooter = $headerFooter; + + return $this; + } + + /** + * Get sheet view. + * + * @return SheetView + */ + public function getSheetView() + { + return $this->sheetView; + } + + /** + * Set sheet view. + * + * @return $this + */ + public function setSheetView(SheetView $sheetView) + { + $this->sheetView = $sheetView; + + return $this; + } + + /** + * Get Protection. + * + * @return Protection + */ + public function getProtection() + { + return $this->protection; + } + + /** + * Set Protection. + * + * @return $this + */ + public function setProtection(Protection $protection) + { + $this->protection = $protection; + $this->dirty = true; + + return $this; + } + + /** + * Get highest worksheet column. + * + * @param null|int|string $row Return the data highest column for the specified row, + * or the highest column of any row if no row number is passed + * + * @return string Highest column name + */ + public function getHighestColumn($row = null) + { + if (empty($row)) { + return Coordinate::stringFromColumnIndex($this->cachedHighestColumn); + } + + return $this->getHighestDataColumn($row); + } + + /** + * Get highest worksheet column that contains data. + * + * @param null|int|string $row Return the highest data column for the specified row, + * or the highest data column of any row if no row number is passed + * + * @return string Highest column name that contains data + */ + public function getHighestDataColumn($row = null) + { + return $this->cellCollection->getHighestColumn($row); + } + + /** + * Get highest worksheet row. + * + * @param null|string $column Return the highest data row for the specified column, + * or the highest row of any column if no column letter is passed + * + * @return int Highest row number + */ + public function getHighestRow($column = null) + { + if ($column == null) { + return $this->cachedHighestRow; + } + + return $this->getHighestDataRow($column); + } + + /** + * Get highest worksheet row that contains data. + * + * @param null|string $column Return the highest data row for the specified column, + * or the highest data row of any column if no column letter is passed + * + * @return int Highest row number that contains data + */ + public function getHighestDataRow($column = null) + { + return $this->cellCollection->getHighestRow($column); + } + + /** + * Get highest worksheet column and highest row that have cell records. + * + * @return array Highest column name and highest row number + */ + public function getHighestRowAndColumn() + { + return $this->cellCollection->getHighestRowAndColumn(); + } + + /** + * Set a cell value. + * + * @param string $coordinate Coordinate of the cell, eg: 'A1' + * @param mixed $value Value of the cell + * + * @return $this + */ + public function setCellValue($coordinate, $value) + { + $this->getCell($coordinate)->setValue($value); + + return $this; + } + + /** + * Set a cell value by using numeric cell coordinates. + * + * @param int $columnIndex Numeric column coordinate of the cell + * @param int $row Numeric row coordinate of the cell + * @param mixed $value Value of the cell + * + * @return $this + */ + public function setCellValueByColumnAndRow($columnIndex, $row, $value) + { + $this->getCellByColumnAndRow($columnIndex, $row)->setValue($value); + + return $this; + } + + /** + * Set a cell value. + * + * @param string $coordinate Coordinate of the cell, eg: 'A1' + * @param mixed $value Value of the cell + * @param string $dataType Explicit data type, see DataType::TYPE_* + * + * @return $this + */ + public function setCellValueExplicit($coordinate, $value, $dataType) + { + // Set value + $this->getCell($coordinate)->setValueExplicit($value, $dataType); + + return $this; + } + + /** + * Set a cell value by using numeric cell coordinates. + * + * @param int $columnIndex Numeric column coordinate of the cell + * @param int $row Numeric row coordinate of the cell + * @param mixed $value Value of the cell + * @param string $dataType Explicit data type, see DataType::TYPE_* + * + * @return $this + */ + public function setCellValueExplicitByColumnAndRow($columnIndex, $row, $value, $dataType) + { + $this->getCellByColumnAndRow($columnIndex, $row)->setValueExplicit($value, $dataType); + + return $this; + } + + /** + * Get cell at a specific coordinate. + * + * @param string $coordinate Coordinate of the cell, eg: 'A1' + * + * @return Cell Cell that was found or created + */ + public function getCell(string $coordinate): Cell + { + // Shortcut for increased performance for the vast majority of simple cases + if ($this->cellCollection->has($coordinate)) { + /** @var Cell $cell */ + $cell = $this->cellCollection->get($coordinate); + + return $cell; + } + + /** @var Worksheet $sheet */ + [$sheet, $finalCoordinate] = $this->getWorksheetAndCoordinate($coordinate); + $cell = $sheet->cellCollection->get($finalCoordinate); + + return $cell ?? $sheet->createNewCell($finalCoordinate); + } + + /** + * Get the correct Worksheet and coordinate from a coordinate that may + * contains reference to another sheet or a named range. + * + * @return array{0: Worksheet, 1: string} + */ + private function getWorksheetAndCoordinate(string $coordinate): array + { + $sheet = null; + $finalCoordinate = null; + + // Worksheet reference? + if (strpos($coordinate, '!') !== false) { + $worksheetReference = self::extractSheetTitle($coordinate, true); + + $sheet = $this->parent->getSheetByName($worksheetReference[0]); + $finalCoordinate = strtoupper($worksheetReference[1]); + + if (!$sheet) { + throw new Exception('Sheet not found for name: ' . $worksheetReference[0]); + } + } elseif ( + !preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $coordinate) && + preg_match('/^' . Calculation::CALCULATION_REGEXP_DEFINEDNAME . '$/i', $coordinate) + ) { + // Named range? + $namedRange = $this->validateNamedRange($coordinate, true); + if ($namedRange !== null) { + $sheet = $namedRange->getWorksheet(); + if (!$sheet) { + throw new Exception('Sheet not found for named range: ' . $namedRange->getName()); + } + + $cellCoordinate = ltrim(substr($namedRange->getValue(), strrpos($namedRange->getValue(), '!')), '!'); + $finalCoordinate = str_replace('$', '', $cellCoordinate); + } + } + + if (!$sheet || !$finalCoordinate) { + $sheet = $this; + $finalCoordinate = strtoupper($coordinate); + } + + if (Coordinate::coordinateIsRange($finalCoordinate)) { + throw new Exception('Cell coordinate string can not be a range of cells.'); + } elseif (strpos($finalCoordinate, '$') !== false) { + throw new Exception('Cell coordinate must not be absolute.'); + } + + return [$sheet, $finalCoordinate]; + } + + /** + * Get an existing cell at a specific coordinate, or null. + * + * @param string $coordinate Coordinate of the cell, eg: 'A1' + * + * @return null|Cell Cell that was found or null + */ + private function getCellOrNull($coordinate): ?Cell + { + // Check cell collection + if ($this->cellCollection->has($coordinate)) { + return $this->cellCollection->get($coordinate); + } + + return null; + } + + /** + * Get cell at a specific coordinate by using numeric cell coordinates. + * + * @param int $columnIndex Numeric column coordinate of the cell + * @param int $row Numeric row coordinate of the cell + * + * @return Cell Cell that was found/created or null + */ + public function getCellByColumnAndRow($columnIndex, $row): Cell + { + $columnLetter = Coordinate::stringFromColumnIndex($columnIndex); + $coordinate = $columnLetter . $row; + + if ($this->cellCollection->has($coordinate)) { + /** @var Cell $cell */ + $cell = $this->cellCollection->get($coordinate); + + return $cell; + } + + // Create new cell object, if required + return $this->createNewCell($coordinate); + } + + /** + * Create a new cell at the specified coordinate. + * + * @param string $coordinate Coordinate of the cell + * + * @return Cell Cell that was created + */ + private function createNewCell($coordinate) + { + $cell = new Cell(null, DataType::TYPE_NULL, $this); + $this->cellCollection->add($coordinate, $cell); + $this->cellCollectionIsSorted = false; + + // Coordinates + [$column, $row] = Coordinate::coordinateFromString($coordinate); + $aIndexes = Coordinate::indexesFromString($coordinate); + if ($this->cachedHighestColumn < $aIndexes[0]) { + $this->cachedHighestColumn = $aIndexes[0]; + } + if ($aIndexes[1] > $this->cachedHighestRow) { + $this->cachedHighestRow = $aIndexes[1]; + } + + // Cell needs appropriate xfIndex from dimensions records + // but don't create dimension records if they don't already exist + $rowDimension = $this->rowDimensions[$row] ?? null; + $columnDimension = $this->columnDimensions[$column] ?? null; + + if ($rowDimension !== null && $rowDimension->getXfIndex() > 0) { + // then there is a row dimension with explicit style, assign it to the cell + $cell->setXfIndex($rowDimension->getXfIndex()); + } elseif ($columnDimension !== null && $columnDimension->getXfIndex() > 0) { + // then there is a column dimension, assign it to the cell + $cell->setXfIndex($columnDimension->getXfIndex()); + } + + return $cell; + } + + /** + * Does the cell at a specific coordinate exist? + * + * @param string $coordinate Coordinate of the cell eg: 'A1' + * + * @return bool + */ + public function cellExists($coordinate) + { + /** @var Worksheet $sheet */ + [$sheet, $finalCoordinate] = $this->getWorksheetAndCoordinate($coordinate); + + return $sheet->cellCollection->has($finalCoordinate); + } + + /** + * Cell at a specific coordinate by using numeric cell coordinates exists? + * + * @param int $columnIndex Numeric column coordinate of the cell + * @param int $row Numeric row coordinate of the cell + * + * @return bool + */ + public function cellExistsByColumnAndRow($columnIndex, $row) + { + return $this->cellExists(Coordinate::stringFromColumnIndex($columnIndex) . $row); + } + + /** + * Get row dimension at a specific row. + * + * @param int $row Numeric index of the row + */ + public function getRowDimension(int $row): RowDimension + { + // Get row dimension + if (!isset($this->rowDimensions[$row])) { + $this->rowDimensions[$row] = new RowDimension($row); + + $this->cachedHighestRow = max($this->cachedHighestRow, $row); + } + + return $this->rowDimensions[$row]; + } + + /** + * Get column dimension at a specific column. + * + * @param string $column String index of the column eg: 'A' + */ + public function getColumnDimension(string $column): ColumnDimension + { + // Uppercase coordinate + $column = strtoupper($column); + + // Fetch dimensions + if (!isset($this->columnDimensions[$column])) { + $this->columnDimensions[$column] = new ColumnDimension($column); + + $columnIndex = Coordinate::columnIndexFromString($column); + if ($this->cachedHighestColumn < $columnIndex) { + $this->cachedHighestColumn = $columnIndex; + } + } + + return $this->columnDimensions[$column]; + } + + /** + * Get column dimension at a specific column by using numeric cell coordinates. + * + * @param int $columnIndex Numeric column coordinate of the cell + */ + public function getColumnDimensionByColumn(int $columnIndex): ColumnDimension + { + return $this->getColumnDimension(Coordinate::stringFromColumnIndex($columnIndex)); + } + + /** + * Get styles. + * + * @return Style[] + */ + public function getStyles() + { + return $this->styles; + } + + /** + * Get style for cell. + * + * @param string $cellCoordinate Cell coordinate (or range) to get style for, eg: 'A1' + * + * @return Style + */ + public function getStyle($cellCoordinate) + { + // set this sheet as active + $this->parent->setActiveSheetIndex($this->parent->getIndex($this)); + + // set cell coordinate as active + $this->setSelectedCells($cellCoordinate); + + return $this->parent->getCellXfSupervisor(); + } + + /** + * Get conditional styles for a cell. + * + * @param string $coordinate eg: 'A1' + * + * @return Conditional[] + */ + public function getConditionalStyles($coordinate) + { + $coordinate = strtoupper($coordinate); + if (!isset($this->conditionalStylesCollection[$coordinate])) { + $this->conditionalStylesCollection[$coordinate] = []; + } + + return $this->conditionalStylesCollection[$coordinate]; + } + + /** + * Do conditional styles exist for this cell? + * + * @param string $coordinate eg: 'A1' + * + * @return bool + */ + public function conditionalStylesExists($coordinate) + { + return isset($this->conditionalStylesCollection[strtoupper($coordinate)]); + } + + /** + * Removes conditional styles for a cell. + * + * @param string $coordinate eg: 'A1' + * + * @return $this + */ + public function removeConditionalStyles($coordinate) + { + unset($this->conditionalStylesCollection[strtoupper($coordinate)]); + + return $this; + } + + /** + * Get collection of conditional styles. + * + * @return array + */ + public function getConditionalStylesCollection() + { + return $this->conditionalStylesCollection; + } + + /** + * Set conditional styles. + * + * @param string $coordinate eg: 'A1' + * @param Conditional[] $styles + * + * @return $this + */ + public function setConditionalStyles($coordinate, $styles) + { + $this->conditionalStylesCollection[strtoupper($coordinate)] = $styles; + + return $this; + } + + /** + * Get style for cell by using numeric cell coordinates. + * + * @param int $columnIndex1 Numeric column coordinate of the cell + * @param int $row1 Numeric row coordinate of the cell + * @param null|int $columnIndex2 Numeric column coordinate of the range cell + * @param null|int $row2 Numeric row coordinate of the range cell + * + * @return Style + */ + public function getStyleByColumnAndRow($columnIndex1, $row1, $columnIndex2 = null, $row2 = null) + { + if ($columnIndex2 !== null && $row2 !== null) { + $cellRange = Coordinate::stringFromColumnIndex($columnIndex1) . $row1 . ':' . Coordinate::stringFromColumnIndex($columnIndex2) . $row2; + + return $this->getStyle($cellRange); + } + + return $this->getStyle(Coordinate::stringFromColumnIndex($columnIndex1) . $row1); + } + + /** + * Duplicate cell style to a range of cells. + * + * Please note that this will overwrite existing cell styles for cells in range! + * + * @param Style $style Cell style to duplicate + * @param string $range Range of cells (i.e. "A1:B10"), or just one cell (i.e. "A1") + * + * @return $this + */ + public function duplicateStyle(Style $style, $range) + { + // Add the style to the workbook if necessary + $workbook = $this->parent; + if ($existingStyle = $this->parent->getCellXfByHashCode($style->getHashCode())) { + // there is already such cell Xf in our collection + $xfIndex = $existingStyle->getIndex(); + } else { + // we don't have such a cell Xf, need to add + $workbook->addCellXf($style); + $xfIndex = $style->getIndex(); + } + + // Calculate range outer borders + [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($range . ':' . $range); + + // Make sure we can loop upwards on rows and columns + if ($rangeStart[0] > $rangeEnd[0] && $rangeStart[1] > $rangeEnd[1]) { + $tmp = $rangeStart; + $rangeStart = $rangeEnd; + $rangeEnd = $tmp; + } + + // Loop through cells and apply styles + for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) { + for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) { + $this->getCell(Coordinate::stringFromColumnIndex($col) . $row)->setXfIndex($xfIndex); + } + } + + return $this; + } + + /** + * Duplicate conditional style to a range of cells. + * + * Please note that this will overwrite existing cell styles for cells in range! + * + * @param Conditional[] $styles Cell style to duplicate + * @param string $range Range of cells (i.e. "A1:B10"), or just one cell (i.e. "A1") + * + * @return $this + */ + public function duplicateConditionalStyle(array $styles, $range = '') + { + foreach ($styles as $cellStyle) { + if (!($cellStyle instanceof Conditional)) { + throw new Exception('Style is not a conditional style'); + } + } + + // Calculate range outer borders + [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($range . ':' . $range); + + // Make sure we can loop upwards on rows and columns + if ($rangeStart[0] > $rangeEnd[0] && $rangeStart[1] > $rangeEnd[1]) { + $tmp = $rangeStart; + $rangeStart = $rangeEnd; + $rangeEnd = $tmp; + } + + // Loop through cells and apply styles + for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) { + for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) { + $this->setConditionalStyles(Coordinate::stringFromColumnIndex($col) . $row, $styles); + } + } + + return $this; + } + + /** + * Set break on a cell. + * + * @param string $coordinate Cell coordinate (e.g. A1) + * @param int $break Break type (type of Worksheet::BREAK_*) + * + * @return $this + */ + public function setBreak($coordinate, $break) + { + // Uppercase coordinate + $coordinate = strtoupper($coordinate); + + if ($coordinate != '') { + if ($break == self::BREAK_NONE) { + if (isset($this->breaks[$coordinate])) { + unset($this->breaks[$coordinate]); + } + } else { + $this->breaks[$coordinate] = $break; + } + } else { + throw new Exception('No cell coordinate specified.'); + } + + return $this; + } + + /** + * Set break on a cell by using numeric cell coordinates. + * + * @param int $columnIndex Numeric column coordinate of the cell + * @param int $row Numeric row coordinate of the cell + * @param int $break Break type (type of Worksheet::BREAK_*) + * + * @return $this + */ + public function setBreakByColumnAndRow($columnIndex, $row, $break) + { + return $this->setBreak(Coordinate::stringFromColumnIndex($columnIndex) . $row, $break); + } + + /** + * Get breaks. + * + * @return int[] + */ + public function getBreaks() + { + return $this->breaks; + } + + /** + * Set merge on a cell range. + * + * @param string $range Cell range (e.g. A1:E1) + * + * @return $this + */ + public function mergeCells($range) + { + // Uppercase coordinate + $range = strtoupper($range); + + if (strpos($range, ':') !== false) { + $this->mergeCells[$range] = $range; + + // make sure cells are created + + // get the cells in the range + $aReferences = Coordinate::extractAllCellReferencesInRange($range); + + // create upper left cell if it does not already exist + $upperLeft = $aReferences[0]; + if (!$this->cellExists($upperLeft)) { + $this->getCell($upperLeft)->setValueExplicit(null, DataType::TYPE_NULL); + } + + // Blank out the rest of the cells in the range (if they exist) + $count = count($aReferences); + for ($i = 1; $i < $count; ++$i) { + if ($this->cellExists($aReferences[$i])) { + $this->getCell($aReferences[$i])->setValueExplicit(null, DataType::TYPE_NULL); + } + } + } else { + throw new Exception('Merge must be set on a range of cells.'); + } + + return $this; + } + + /** + * Set merge on a cell range by using numeric cell coordinates. + * + * @param int $columnIndex1 Numeric column coordinate of the first cell + * @param int $row1 Numeric row coordinate of the first cell + * @param int $columnIndex2 Numeric column coordinate of the last cell + * @param int $row2 Numeric row coordinate of the last cell + * + * @return $this + */ + public function mergeCellsByColumnAndRow($columnIndex1, $row1, $columnIndex2, $row2) + { + $cellRange = Coordinate::stringFromColumnIndex($columnIndex1) . $row1 . ':' . Coordinate::stringFromColumnIndex($columnIndex2) . $row2; + + return $this->mergeCells($cellRange); + } + + /** + * Remove merge on a cell range. + * + * @param string $range Cell range (e.g. A1:E1) + * + * @return $this + */ + public function unmergeCells($range) + { + // Uppercase coordinate + $range = strtoupper($range); + + if (strpos($range, ':') !== false) { + if (isset($this->mergeCells[$range])) { + unset($this->mergeCells[$range]); + } else { + throw new Exception('Cell range ' . $range . ' not known as merged.'); + } + } else { + throw new Exception('Merge can only be removed from a range of cells.'); + } + + return $this; + } + + /** + * Remove merge on a cell range by using numeric cell coordinates. + * + * @param int $columnIndex1 Numeric column coordinate of the first cell + * @param int $row1 Numeric row coordinate of the first cell + * @param int $columnIndex2 Numeric column coordinate of the last cell + * @param int $row2 Numeric row coordinate of the last cell + * + * @return $this + */ + public function unmergeCellsByColumnAndRow($columnIndex1, $row1, $columnIndex2, $row2) + { + $cellRange = Coordinate::stringFromColumnIndex($columnIndex1) . $row1 . ':' . Coordinate::stringFromColumnIndex($columnIndex2) . $row2; + + return $this->unmergeCells($cellRange); + } + + /** + * Get merge cells array. + * + * @return string[] + */ + public function getMergeCells() + { + return $this->mergeCells; + } + + /** + * Set merge cells array for the entire sheet. Use instead mergeCells() to merge + * a single cell range. + * + * @param string[] $mergeCells + * + * @return $this + */ + public function setMergeCells(array $mergeCells) + { + $this->mergeCells = $mergeCells; + + return $this; + } + + /** + * Set protection on a cell range. + * + * @param string $range Cell (e.g. A1) or cell range (e.g. A1:E1) + * @param string $password Password to unlock the protection + * @param bool $alreadyHashed If the password has already been hashed, set this to true + * + * @return $this + */ + public function protectCells($range, $password, $alreadyHashed = false) + { + // Uppercase coordinate + $range = strtoupper($range); + + if (!$alreadyHashed) { + $password = Shared\PasswordHasher::hashPassword($password); + } + $this->protectedCells[$range] = $password; + + return $this; + } + + /** + * Set protection on a cell range by using numeric cell coordinates. + * + * @param int $columnIndex1 Numeric column coordinate of the first cell + * @param int $row1 Numeric row coordinate of the first cell + * @param int $columnIndex2 Numeric column coordinate of the last cell + * @param int $row2 Numeric row coordinate of the last cell + * @param string $password Password to unlock the protection + * @param bool $alreadyHashed If the password has already been hashed, set this to true + * + * @return $this + */ + public function protectCellsByColumnAndRow($columnIndex1, $row1, $columnIndex2, $row2, $password, $alreadyHashed = false) + { + $cellRange = Coordinate::stringFromColumnIndex($columnIndex1) . $row1 . ':' . Coordinate::stringFromColumnIndex($columnIndex2) . $row2; + + return $this->protectCells($cellRange, $password, $alreadyHashed); + } + + /** + * Remove protection on a cell range. + * + * @param string $range Cell (e.g. A1) or cell range (e.g. A1:E1) + * + * @return $this + */ + public function unprotectCells($range) + { + // Uppercase coordinate + $range = strtoupper($range); + + if (isset($this->protectedCells[$range])) { + unset($this->protectedCells[$range]); + } else { + throw new Exception('Cell range ' . $range . ' not known as protected.'); + } + + return $this; + } + + /** + * Remove protection on a cell range by using numeric cell coordinates. + * + * @param int $columnIndex1 Numeric column coordinate of the first cell + * @param int $row1 Numeric row coordinate of the first cell + * @param int $columnIndex2 Numeric column coordinate of the last cell + * @param int $row2 Numeric row coordinate of the last cell + * + * @return $this + */ + public function unprotectCellsByColumnAndRow($columnIndex1, $row1, $columnIndex2, $row2) + { + $cellRange = Coordinate::stringFromColumnIndex($columnIndex1) . $row1 . ':' . Coordinate::stringFromColumnIndex($columnIndex2) . $row2; + + return $this->unprotectCells($cellRange); + } + + /** + * Get protected cells. + * + * @return string[] + */ + public function getProtectedCells() + { + return $this->protectedCells; + } + + /** + * Get Autofilter. + * + * @return AutoFilter + */ + public function getAutoFilter() + { + return $this->autoFilter; + } + + /** + * Set AutoFilter. + * + * @param AutoFilter|string $autoFilterOrRange + * A simple string containing a Cell range like 'A1:E10' is permitted for backward compatibility + * + * @return $this + */ + public function setAutoFilter($autoFilterOrRange) + { + if (is_string($autoFilterOrRange)) { + $this->autoFilter->setRange($autoFilterOrRange); + } elseif (is_object($autoFilterOrRange) && ($autoFilterOrRange instanceof AutoFilter)) { + $this->autoFilter = $autoFilterOrRange; + } + + return $this; + } + + /** + * Set Autofilter Range by using numeric cell coordinates. + * + * @param int $columnIndex1 Numeric column coordinate of the first cell + * @param int $row1 Numeric row coordinate of the first cell + * @param int $columnIndex2 Numeric column coordinate of the second cell + * @param int $row2 Numeric row coordinate of the second cell + * + * @return $this + */ + public function setAutoFilterByColumnAndRow($columnIndex1, $row1, $columnIndex2, $row2) + { + return $this->setAutoFilter( + Coordinate::stringFromColumnIndex($columnIndex1) . $row1 + . ':' . + Coordinate::stringFromColumnIndex($columnIndex2) . $row2 + ); + } + + /** + * Remove autofilter. + * + * @return $this + */ + public function removeAutoFilter() + { + $this->autoFilter->setRange(null); + + return $this; + } + + /** + * Get Freeze Pane. + * + * @return null|string + */ + public function getFreezePane() + { + return $this->freezePane; + } + + /** + * Freeze Pane. + * + * Examples: + * + * - A2 will freeze the rows above cell A2 (i.e row 1) + * - B1 will freeze the columns to the left of cell B1 (i.e column A) + * - B2 will freeze the rows above and to the left of cell B2 (i.e row 1 and column A) + * + * @param null|string $cell Position of the split + * @param null|string $topLeftCell default position of the right bottom pane + * + * @return $this + */ + public function freezePane($cell, $topLeftCell = null) + { + if (is_string($cell) && Coordinate::coordinateIsRange($cell)) { + throw new Exception('Freeze pane can not be set on a range of cells.'); + } + + if ($cell !== null && $topLeftCell === null) { + $coordinate = Coordinate::coordinateFromString($cell); + $topLeftCell = $coordinate[0] . $coordinate[1]; + } + + $this->freezePane = $cell; + $this->topLeftCell = $topLeftCell; + + return $this; + } + + public function setTopLeftCell(string $topLeftCell): self + { + $this->topLeftCell = $topLeftCell; + + return $this; + } + + /** + * Freeze Pane by using numeric cell coordinates. + * + * @param int $columnIndex Numeric column coordinate of the cell + * @param int $row Numeric row coordinate of the cell + * + * @return $this + */ + public function freezePaneByColumnAndRow($columnIndex, $row) + { + return $this->freezePane(Coordinate::stringFromColumnIndex($columnIndex) . $row); + } + + /** + * Unfreeze Pane. + * + * @return $this + */ + public function unfreezePane() + { + return $this->freezePane(null); + } + + /** + * Get the default position of the right bottom pane. + * + * @return null|string + */ + public function getTopLeftCell() + { + return $this->topLeftCell; + } + + /** + * Insert a new row, updating all possible related data. + * + * @param int $before Insert before this one + * @param int $numberOfRows Number of rows to insert + * + * @return $this + */ + public function insertNewRowBefore($before, $numberOfRows = 1) + { + if ($before >= 1) { + $objReferenceHelper = ReferenceHelper::getInstance(); + $objReferenceHelper->insertNewBefore('A' . $before, 0, $numberOfRows, $this); + } else { + throw new Exception('Rows can only be inserted before at least row 1.'); + } + + return $this; + } + + /** + * Insert a new column, updating all possible related data. + * + * @param string $before Insert before this one, eg: 'A' + * @param int $numberOfColumns Number of columns to insert + * + * @return $this + */ + public function insertNewColumnBefore($before, $numberOfColumns = 1) + { + if (!is_numeric($before)) { + $objReferenceHelper = ReferenceHelper::getInstance(); + $objReferenceHelper->insertNewBefore($before . '1', $numberOfColumns, 0, $this); + } else { + throw new Exception('Column references should not be numeric.'); + } + + return $this; + } + + /** + * Insert a new column, updating all possible related data. + * + * @param int $beforeColumnIndex Insert before this one (numeric column coordinate of the cell) + * @param int $numberOfColumns Number of columns to insert + * + * @return $this + */ + public function insertNewColumnBeforeByIndex($beforeColumnIndex, $numberOfColumns = 1) + { + if ($beforeColumnIndex >= 1) { + return $this->insertNewColumnBefore(Coordinate::stringFromColumnIndex($beforeColumnIndex), $numberOfColumns); + } + + throw new Exception('Columns can only be inserted before at least column A (1).'); + } + + /** + * Delete a row, updating all possible related data. + * + * @param int $row Remove starting with this one + * @param int $numberOfRows Number of rows to remove + * + * @return $this + */ + public function removeRow($row, $numberOfRows = 1) + { + if ($row < 1) { + throw new Exception('Rows to be deleted should at least start from row 1.'); + } + + $highestRow = $this->getHighestDataRow(); + $removedRowsCounter = 0; + + for ($r = 0; $r < $numberOfRows; ++$r) { + if ($row + $r <= $highestRow) { + $this->getCellCollection()->removeRow($row + $r); + ++$removedRowsCounter; + } + } + + $objReferenceHelper = ReferenceHelper::getInstance(); + $objReferenceHelper->insertNewBefore('A' . ($row + $numberOfRows), 0, -$numberOfRows, $this); + for ($r = 0; $r < $removedRowsCounter; ++$r) { + $this->getCellCollection()->removeRow($highestRow); + --$highestRow; + } + + return $this; + } + + /** + * Remove a column, updating all possible related data. + * + * @param string $column Remove starting with this one, eg: 'A' + * @param int $numberOfColumns Number of columns to remove + * + * @return $this + */ + public function removeColumn($column, $numberOfColumns = 1) + { + if (is_numeric($column)) { + throw new Exception('Column references should not be numeric.'); + } + + $highestColumn = $this->getHighestDataColumn(); + $highestColumnIndex = Coordinate::columnIndexFromString($highestColumn); + $pColumnIndex = Coordinate::columnIndexFromString($column); + + if ($pColumnIndex > $highestColumnIndex) { + return $this; + } + + $column = Coordinate::stringFromColumnIndex($pColumnIndex + $numberOfColumns); + $objReferenceHelper = ReferenceHelper::getInstance(); + $objReferenceHelper->insertNewBefore($column . '1', -$numberOfColumns, 0, $this); + + $maxPossibleColumnsToBeRemoved = $highestColumnIndex - $pColumnIndex + 1; + + for ($c = 0, $n = min($maxPossibleColumnsToBeRemoved, $numberOfColumns); $c < $n; ++$c) { + $this->getCellCollection()->removeColumn($highestColumn); + $highestColumn = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($highestColumn) - 1); + } + + $this->garbageCollect(); + + return $this; + } + + /** + * Remove a column, updating all possible related data. + * + * @param int $columnIndex Remove starting with this one (numeric column coordinate of the cell) + * @param int $numColumns Number of columns to remove + * + * @return $this + */ + public function removeColumnByIndex($columnIndex, $numColumns = 1) + { + if ($columnIndex >= 1) { + return $this->removeColumn(Coordinate::stringFromColumnIndex($columnIndex), $numColumns); + } + + throw new Exception('Columns to be deleted should at least start from column A (1)'); + } + + /** + * Show gridlines? + * + * @return bool + */ + public function getShowGridlines() + { + return $this->showGridlines; + } + + /** + * Set show gridlines. + * + * @param bool $showGridLines Show gridlines (true/false) + * + * @return $this + */ + public function setShowGridlines($showGridLines) + { + $this->showGridlines = $showGridLines; + + return $this; + } + + /** + * Print gridlines? + * + * @return bool + */ + public function getPrintGridlines() + { + return $this->printGridlines; + } + + /** + * Set print gridlines. + * + * @param bool $printGridLines Print gridlines (true/false) + * + * @return $this + */ + public function setPrintGridlines($printGridLines) + { + $this->printGridlines = $printGridLines; + + return $this; + } + + /** + * Show row and column headers? + * + * @return bool + */ + public function getShowRowColHeaders() + { + return $this->showRowColHeaders; + } + + /** + * Set show row and column headers. + * + * @param bool $showRowColHeaders Show row and column headers (true/false) + * + * @return $this + */ + public function setShowRowColHeaders($showRowColHeaders) + { + $this->showRowColHeaders = $showRowColHeaders; + + return $this; + } + + /** + * Show summary below? (Row/Column outlining). + * + * @return bool + */ + public function getShowSummaryBelow() + { + return $this->showSummaryBelow; + } + + /** + * Set show summary below. + * + * @param bool $showSummaryBelow Show summary below (true/false) + * + * @return $this + */ + public function setShowSummaryBelow($showSummaryBelow) + { + $this->showSummaryBelow = $showSummaryBelow; + + return $this; + } + + /** + * Show summary right? (Row/Column outlining). + * + * @return bool + */ + public function getShowSummaryRight() + { + return $this->showSummaryRight; + } + + /** + * Set show summary right. + * + * @param bool $showSummaryRight Show summary right (true/false) + * + * @return $this + */ + public function setShowSummaryRight($showSummaryRight) + { + $this->showSummaryRight = $showSummaryRight; + + return $this; + } + + /** + * Get comments. + * + * @return Comment[] + */ + public function getComments() + { + return $this->comments; + } + + /** + * Set comments array for the entire sheet. + * + * @param Comment[] $comments + * + * @return $this + */ + public function setComments(array $comments) + { + $this->comments = $comments; + + return $this; + } + + /** + * Get comment for cell. + * + * @param string $cellCoordinate Cell coordinate to get comment for, eg: 'A1' + * + * @return Comment + */ + public function getComment($cellCoordinate) + { + // Uppercase coordinate + $cellCoordinate = strtoupper($cellCoordinate); + + if (Coordinate::coordinateIsRange($cellCoordinate)) { + throw new Exception('Cell coordinate string can not be a range of cells.'); + } elseif (strpos($cellCoordinate, '$') !== false) { + throw new Exception('Cell coordinate string must not be absolute.'); + } elseif ($cellCoordinate == '') { + throw new Exception('Cell coordinate can not be zero-length string.'); + } + + // Check if we already have a comment for this cell. + if (isset($this->comments[$cellCoordinate])) { + return $this->comments[$cellCoordinate]; + } + + // If not, create a new comment. + $newComment = new Comment(); + $this->comments[$cellCoordinate] = $newComment; + + return $newComment; + } + + /** + * Get comment for cell by using numeric cell coordinates. + * + * @param int $columnIndex Numeric column coordinate of the cell + * @param int $row Numeric row coordinate of the cell + * + * @return Comment + */ + public function getCommentByColumnAndRow($columnIndex, $row) + { + return $this->getComment(Coordinate::stringFromColumnIndex($columnIndex) . $row); + } + + /** + * Get active cell. + * + * @return string Example: 'A1' + */ + public function getActiveCell() + { + return $this->activeCell; + } + + /** + * Get selected cells. + * + * @return string + */ + public function getSelectedCells() + { + return $this->selectedCells; + } + + /** + * Selected cell. + * + * @param string $coordinate Cell (i.e. A1) + * + * @return $this + */ + public function setSelectedCell($coordinate) + { + return $this->setSelectedCells($coordinate); + } + + /** + * Sigh - Phpstan thinks, correctly, that preg_replace can return null. + * But Scrutinizer doesn't. Try to satisfy both. + * + * @param mixed $str + */ + private static function ensureString($str): string + { + return is_string($str) ? $str : ''; + } + + public static function pregReplace(string $pattern, string $replacement, string $subject): string + { + return self::ensureString(preg_replace($pattern, $replacement, $subject)); + } + + private function tryDefinedName(string $coordinate): string + { + // Uppercase coordinate + $coordinate = strtoupper($coordinate); + // Eliminate leading equal sign + $coordinate = self::pregReplace('/^=/', '', $coordinate); + $defined = $this->parent->getDefinedName($coordinate, $this); + if ($defined !== null) { + if ($defined->getWorksheet() === $this && !$defined->isFormula()) { + $coordinate = self::pregReplace('/^=/', '', $defined->getValue()); + } + } + + return $coordinate; + } + + /** + * Select a range of cells. + * + * @param string $coordinate Cell range, examples: 'A1', 'B2:G5', 'A:C', '3:6' + * + * @return $this + */ + public function setSelectedCells($coordinate) + { + $originalCoordinate = $coordinate; + $coordinate = $this->tryDefinedName($coordinate); + + // Convert 'A' to 'A:A' + $coordinate = self::pregReplace('/^([A-Z]+)$/', '${1}:${1}', $coordinate); + + // Convert '1' to '1:1' + $coordinate = self::pregReplace('/^(\d+)$/', '${1}:${1}', $coordinate); + + // Convert 'A:C' to 'A1:C1048576' + $coordinate = self::pregReplace('/^([A-Z]+):([A-Z]+)$/', '${1}1:${2}1048576', $coordinate); + + // Convert '1:3' to 'A1:XFD3' + $coordinate = self::pregReplace('/^(\d+):(\d+)$/', 'A${1}:XFD${2}', $coordinate); + if (preg_match('/^\\$?[A-Z]{1,3}\\$?\d{1,7}(:\\$?[A-Z]{1,3}\\$?\d{1,7})?$/', $coordinate) !== 1) { + throw new Exception("Invalid setSelectedCells $originalCoordinate $coordinate"); + } + + if (Coordinate::coordinateIsRange($coordinate)) { + [$first] = Coordinate::splitRange($coordinate); + $this->activeCell = $first[0]; + } else { + $this->activeCell = $coordinate; + } + $this->selectedCells = $coordinate; + + return $this; + } + + /** + * Selected cell by using numeric cell coordinates. + * + * @param int $columnIndex Numeric column coordinate of the cell + * @param int $row Numeric row coordinate of the cell + * + * @return $this + */ + public function setSelectedCellByColumnAndRow($columnIndex, $row) + { + return $this->setSelectedCells(Coordinate::stringFromColumnIndex($columnIndex) . $row); + } + + /** + * Get right-to-left. + * + * @return bool + */ + public function getRightToLeft() + { + return $this->rightToLeft; + } + + /** + * Set right-to-left. + * + * @param bool $value Right-to-left true/false + * + * @return $this + */ + public function setRightToLeft($value) + { + $this->rightToLeft = $value; + + return $this; + } + + /** + * Fill worksheet from values in array. + * + * @param array $source Source array + * @param mixed $nullValue Value in source array that stands for blank cell + * @param string $startCell Insert array starting from this cell address as the top left coordinate + * @param bool $strictNullComparison Apply strict comparison when testing for null values in the array + * + * @return $this + */ + public function fromArray(array $source, $nullValue = null, $startCell = 'A1', $strictNullComparison = false) + { + // Convert a 1-D array to 2-D (for ease of looping) + if (!is_array(end($source))) { + $source = [$source]; + } + + // start coordinate + [$startColumn, $startRow] = Coordinate::coordinateFromString($startCell); + + // Loop through $source + foreach ($source as $rowData) { + $currentColumn = $startColumn; + foreach ($rowData as $cellValue) { + if ($strictNullComparison) { + if ($cellValue !== $nullValue) { + // Set cell value + $this->getCell($currentColumn . $startRow)->setValue($cellValue); + } + } else { + if ($cellValue != $nullValue) { + // Set cell value + $this->getCell($currentColumn . $startRow)->setValue($cellValue); + } + } + ++$currentColumn; + } + ++$startRow; + } + + return $this; + } + + /** + * Create array from a range of cells. + * + * @param string $range Range of cells (i.e. "A1:B10"), or just one cell (i.e. "A1") + * @param mixed $nullValue Value returned in the array entry if a cell doesn't exist + * @param bool $calculateFormulas Should formulas be calculated? + * @param bool $formatData Should formatting be applied to cell values? + * @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero + * True - Return rows and columns indexed by their actual row and column IDs + * + * @return array + */ + public function rangeToArray($range, $nullValue = null, $calculateFormulas = true, $formatData = true, $returnCellRef = false) + { + // Returnvalue + $returnValue = []; + // Identify the range that we need to extract from the worksheet + [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($range); + $minCol = Coordinate::stringFromColumnIndex($rangeStart[0]); + $minRow = $rangeStart[1]; + $maxCol = Coordinate::stringFromColumnIndex($rangeEnd[0]); + $maxRow = $rangeEnd[1]; + + ++$maxCol; + // Loop through rows + $r = -1; + for ($row = $minRow; $row <= $maxRow; ++$row) { + $rRef = $returnCellRef ? $row : ++$r; + $c = -1; + // Loop through columns in the current row + for ($col = $minCol; $col != $maxCol; ++$col) { + $cRef = $returnCellRef ? $col : ++$c; + // Using getCell() will create a new cell if it doesn't already exist. We don't want that to happen + // so we test and retrieve directly against cellCollection + if ($this->cellCollection->has($col . $row)) { + // Cell exists + $cell = $this->cellCollection->get($col . $row); + if ($cell->getValue() !== null) { + if ($cell->getValue() instanceof RichText) { + $returnValue[$rRef][$cRef] = $cell->getValue()->getPlainText(); + } else { + if ($calculateFormulas) { + $returnValue[$rRef][$cRef] = $cell->getCalculatedValue(); + } else { + $returnValue[$rRef][$cRef] = $cell->getValue(); + } + } + + if ($formatData) { + $style = $this->parent->getCellXfByIndex($cell->getXfIndex()); + $returnValue[$rRef][$cRef] = NumberFormat::toFormattedString( + $returnValue[$rRef][$cRef], + ($style && $style->getNumberFormat()) ? $style->getNumberFormat()->getFormatCode() : NumberFormat::FORMAT_GENERAL + ); + } + } else { + // Cell holds a NULL + $returnValue[$rRef][$cRef] = $nullValue; + } + } else { + // Cell doesn't exist + $returnValue[$rRef][$cRef] = $nullValue; + } + } + } + + // Return + return $returnValue; + } + + private function validateNamedRange(string $definedName, bool $returnNullIfInvalid = false): ?DefinedName + { + $namedRange = DefinedName::resolveName($definedName, $this); + if ($namedRange === null) { + if ($returnNullIfInvalid) { + return null; + } + + throw new Exception('Named Range ' . $definedName . ' does not exist.'); + } + + if ($namedRange->isFormula()) { + if ($returnNullIfInvalid) { + return null; + } + + throw new Exception('Defined Named ' . $definedName . ' is a formula, not a range or cell.'); + } + + if ($namedRange->getLocalOnly() && $this->getHashCode() !== $namedRange->getWorksheet()->getHashCode()) { + if ($returnNullIfInvalid) { + return null; + } + + throw new Exception( + 'Named range ' . $definedName . ' is not accessible from within sheet ' . $this->getTitle() + ); + } + + return $namedRange; + } + + /** + * Create array from a range of cells. + * + * @param string $definedName The Named Range that should be returned + * @param mixed $nullValue Value returned in the array entry if a cell doesn't exist + * @param bool $calculateFormulas Should formulas be calculated? + * @param bool $formatData Should formatting be applied to cell values? + * @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero + * True - Return rows and columns indexed by their actual row and column IDs + * + * @return array + */ + public function namedRangeToArray(string $definedName, $nullValue = null, $calculateFormulas = true, $formatData = true, $returnCellRef = false) + { + $namedRange = $this->validateNamedRange($definedName); + $workSheet = $namedRange->getWorksheet(); + $cellRange = ltrim(substr($namedRange->getValue(), strrpos($namedRange->getValue(), '!')), '!'); + $cellRange = str_replace('$', '', $cellRange); + + return $workSheet->rangeToArray($cellRange, $nullValue, $calculateFormulas, $formatData, $returnCellRef); + } + + /** + * Create array from worksheet. + * + * @param mixed $nullValue Value returned in the array entry if a cell doesn't exist + * @param bool $calculateFormulas Should formulas be calculated? + * @param bool $formatData Should formatting be applied to cell values? + * @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero + * True - Return rows and columns indexed by their actual row and column IDs + * + * @return array + */ + public function toArray($nullValue = null, $calculateFormulas = true, $formatData = true, $returnCellRef = false) + { + // Garbage collect... + $this->garbageCollect(); + + // Identify the range that we need to extract from the worksheet + $maxCol = $this->getHighestColumn(); + $maxRow = $this->getHighestRow(); + + // Return + return $this->rangeToArray('A1:' . $maxCol . $maxRow, $nullValue, $calculateFormulas, $formatData, $returnCellRef); + } + + /** + * Get row iterator. + * + * @param int $startRow The row number at which to start iterating + * @param int $endRow The row number at which to stop iterating + * + * @return RowIterator + */ + public function getRowIterator($startRow = 1, $endRow = null) + { + return new RowIterator($this, $startRow, $endRow); + } + + /** + * Get column iterator. + * + * @param string $startColumn The column address at which to start iterating + * @param string $endColumn The column address at which to stop iterating + * + * @return ColumnIterator + */ + public function getColumnIterator($startColumn = 'A', $endColumn = null) + { + return new ColumnIterator($this, $startColumn, $endColumn); + } + + /** + * Run PhpSpreadsheet garbage collector. + * + * @return $this + */ + public function garbageCollect() + { + // Flush cache + $this->cellCollection->get('A1'); + + // Lookup highest column and highest row if cells are cleaned + $colRow = $this->cellCollection->getHighestRowAndColumn(); + $highestRow = $colRow['row']; + $highestColumn = Coordinate::columnIndexFromString($colRow['column']); + + // Loop through column dimensions + foreach ($this->columnDimensions as $dimension) { + $highestColumn = max($highestColumn, Coordinate::columnIndexFromString($dimension->getColumnIndex())); + } + + // Loop through row dimensions + foreach ($this->rowDimensions as $dimension) { + $highestRow = max($highestRow, $dimension->getRowIndex()); + } + + // Cache values + if ($highestColumn < 1) { + $this->cachedHighestColumn = 1; + } else { + $this->cachedHighestColumn = $highestColumn; + } + $this->cachedHighestRow = $highestRow; + + // Return + return $this; + } + + /** + * Get hash code. + * + * @return string Hash code + */ + public function getHashCode() + { + if ($this->dirty) { + $this->hash = md5($this->title . $this->autoFilter . ($this->protection->isProtectionEnabled() ? 't' : 'f') . __CLASS__); + $this->dirty = false; + } + + return $this->hash; + } + + /** + * Extract worksheet title from range. + * + * Example: extractSheetTitle("testSheet!A1") ==> 'A1' + * Example: extractSheetTitle("'testSheet 1'!A1", true) ==> ['testSheet 1', 'A1']; + * + * @param string $range Range to extract title from + * @param bool $returnRange Return range? (see example) + * + * @return mixed + */ + public static function extractSheetTitle($range, $returnRange = false) + { + // Sheet title included? + if (($sep = strrpos($range, '!')) === false) { + return $returnRange ? ['', $range] : ''; + } + + if ($returnRange) { + return [substr($range, 0, $sep), substr($range, $sep + 1)]; + } + + return substr($range, $sep + 1); + } + + /** + * Get hyperlink. + * + * @param string $cellCoordinate Cell coordinate to get hyperlink for, eg: 'A1' + * + * @return Hyperlink + */ + public function getHyperlink($cellCoordinate) + { + // return hyperlink if we already have one + if (isset($this->hyperlinkCollection[$cellCoordinate])) { + return $this->hyperlinkCollection[$cellCoordinate]; + } + + // else create hyperlink + $this->hyperlinkCollection[$cellCoordinate] = new Hyperlink(); + + return $this->hyperlinkCollection[$cellCoordinate]; + } + + /** + * Set hyperlink. + * + * @param string $cellCoordinate Cell coordinate to insert hyperlink, eg: 'A1' + * + * @return $this + */ + public function setHyperlink($cellCoordinate, ?Hyperlink $hyperlink = null) + { + if ($hyperlink === null) { + unset($this->hyperlinkCollection[$cellCoordinate]); + } else { + $this->hyperlinkCollection[$cellCoordinate] = $hyperlink; + } + + return $this; + } + + /** + * Hyperlink at a specific coordinate exists? + * + * @param string $coordinate eg: 'A1' + * + * @return bool + */ + public function hyperlinkExists($coordinate) + { + return isset($this->hyperlinkCollection[$coordinate]); + } + + /** + * Get collection of hyperlinks. + * + * @return Hyperlink[] + */ + public function getHyperlinkCollection() + { + return $this->hyperlinkCollection; + } + + /** + * Get data validation. + * + * @param string $cellCoordinate Cell coordinate to get data validation for, eg: 'A1' + * + * @return DataValidation + */ + public function getDataValidation($cellCoordinate) + { + // return data validation if we already have one + if (isset($this->dataValidationCollection[$cellCoordinate])) { + return $this->dataValidationCollection[$cellCoordinate]; + } + + // else create data validation + $this->dataValidationCollection[$cellCoordinate] = new DataValidation(); + + return $this->dataValidationCollection[$cellCoordinate]; + } + + /** + * Set data validation. + * + * @param string $cellCoordinate Cell coordinate to insert data validation, eg: 'A1' + * + * @return $this + */ + public function setDataValidation($cellCoordinate, ?DataValidation $dataValidation = null) + { + if ($dataValidation === null) { + unset($this->dataValidationCollection[$cellCoordinate]); + } else { + $this->dataValidationCollection[$cellCoordinate] = $dataValidation; + } + + return $this; + } + + /** + * Data validation at a specific coordinate exists? + * + * @param string $coordinate eg: 'A1' + * + * @return bool + */ + public function dataValidationExists($coordinate) + { + return isset($this->dataValidationCollection[$coordinate]); + } + + /** + * Get collection of data validations. + * + * @return DataValidation[] + */ + public function getDataValidationCollection() + { + return $this->dataValidationCollection; + } + + /** + * Accepts a range, returning it as a range that falls within the current highest row and column of the worksheet. + * + * @param string $range + * + * @return string Adjusted range value + */ + public function shrinkRangeToFit($range) + { + $maxCol = $this->getHighestColumn(); + $maxRow = $this->getHighestRow(); + $maxCol = Coordinate::columnIndexFromString($maxCol); + + $rangeBlocks = explode(' ', $range); + foreach ($rangeBlocks as &$rangeSet) { + $rangeBoundaries = Coordinate::getRangeBoundaries($rangeSet); + + if (Coordinate::columnIndexFromString($rangeBoundaries[0][0]) > $maxCol) { + $rangeBoundaries[0][0] = Coordinate::stringFromColumnIndex($maxCol); + } + if ($rangeBoundaries[0][1] > $maxRow) { + $rangeBoundaries[0][1] = $maxRow; + } + if (Coordinate::columnIndexFromString($rangeBoundaries[1][0]) > $maxCol) { + $rangeBoundaries[1][0] = Coordinate::stringFromColumnIndex($maxCol); + } + if ($rangeBoundaries[1][1] > $maxRow) { + $rangeBoundaries[1][1] = $maxRow; + } + $rangeSet = $rangeBoundaries[0][0] . $rangeBoundaries[0][1] . ':' . $rangeBoundaries[1][0] . $rangeBoundaries[1][1]; + } + unset($rangeSet); + + return implode(' ', $rangeBlocks); + } + + /** + * Get tab color. + * + * @return Color + */ + public function getTabColor() + { + if ($this->tabColor === null) { + $this->tabColor = new Color(); + } + + return $this->tabColor; + } + + /** + * Reset tab color. + * + * @return $this + */ + public function resetTabColor() + { + $this->tabColor = null; + + return $this; + } + + /** + * Tab color set? + * + * @return bool + */ + public function isTabColorSet() + { + return $this->tabColor !== null; + } + + /** + * Copy worksheet (!= clone!). + * + * @return static + */ + public function copy() + { + return clone $this; + } + + /** + * Implement PHP __clone to create a deep clone, not just a shallow copy. + */ + public function __clone() + { + // @phpstan-ignore-next-line + foreach ($this as $key => $val) { + if ($key == 'parent') { + continue; + } + + if (is_object($val) || (is_array($val))) { + if ($key == 'cellCollection') { + $newCollection = $this->cellCollection->cloneCellCollection($this); + $this->cellCollection = $newCollection; + } elseif ($key == 'drawingCollection') { + $currentCollection = $this->drawingCollection; + $this->drawingCollection = new ArrayObject(); + foreach ($currentCollection as $item) { + if (is_object($item)) { + $newDrawing = clone $item; + $newDrawing->setWorksheet($this); + } + } + } elseif (($key == 'autoFilter') && ($this->autoFilter instanceof AutoFilter)) { + $newAutoFilter = clone $this->autoFilter; + $this->autoFilter = $newAutoFilter; + $this->autoFilter->setParent($this); + } else { + $this->{$key} = unserialize(serialize($val)); + } + } + } + } + + /** + * Define the code name of the sheet. + * + * @param string $codeName Same rule as Title minus space not allowed (but, like Excel, change + * silently space to underscore) + * @param bool $validate False to skip validation of new title. WARNING: This should only be set + * at parse time (by Readers), where titles can be assumed to be valid. + * + * @return $this + */ + public function setCodeName($codeName, $validate = true) + { + // Is this a 'rename' or not? + if ($this->getCodeName() == $codeName) { + return $this; + } + + if ($validate) { + $codeName = str_replace(' ', '_', $codeName); //Excel does this automatically without flinching, we are doing the same + + // Syntax check + // throw an exception if not valid + self::checkSheetCodeName($codeName); + + // We use the same code that setTitle to find a valid codeName else not using a space (Excel don't like) but a '_' + + if ($this->getParent()) { + // Is there already such sheet name? + if ($this->getParent()->sheetCodeNameExists($codeName)) { + // Use name, but append with lowest possible integer + + if (Shared\StringHelper::countCharacters($codeName) > 29) { + $codeName = Shared\StringHelper::substring($codeName, 0, 29); + } + $i = 1; + while ($this->getParent()->sheetCodeNameExists($codeName . '_' . $i)) { + ++$i; + if ($i == 10) { + if (Shared\StringHelper::countCharacters($codeName) > 28) { + $codeName = Shared\StringHelper::substring($codeName, 0, 28); + } + } elseif ($i == 100) { + if (Shared\StringHelper::countCharacters($codeName) > 27) { + $codeName = Shared\StringHelper::substring($codeName, 0, 27); + } + } + } + + $codeName .= '_' . $i; // ok, we have a valid name + } + } + } + + $this->codeName = $codeName; + + return $this; + } + + /** + * Return the code name of the sheet. + * + * @return null|string + */ + public function getCodeName() + { + return $this->codeName; + } + + /** + * Sheet has a code name ? + * + * @return bool + */ + public function hasCodeName() + { + return $this->codeName !== null; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/BaseWriter.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/BaseWriter.php new file mode 100644 index 0000000..7811a2a --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/BaseWriter.php @@ -0,0 +1,143 @@ +includeCharts; + } + + public function setIncludeCharts($includeCharts) + { + $this->includeCharts = (bool) $includeCharts; + + return $this; + } + + public function getPreCalculateFormulas() + { + return $this->preCalculateFormulas; + } + + public function setPreCalculateFormulas($precalculateFormulas) + { + $this->preCalculateFormulas = (bool) $precalculateFormulas; + + return $this; + } + + public function getUseDiskCaching() + { + return $this->useDiskCaching; + } + + public function setUseDiskCaching($useDiskCache, $cacheDirectory = null) + { + $this->useDiskCaching = $useDiskCache; + + if ($cacheDirectory !== null) { + if (is_dir($cacheDirectory)) { + $this->diskCachingDirectory = $cacheDirectory; + } else { + throw new Exception("Directory does not exist: $cacheDirectory"); + } + } + + return $this; + } + + public function getDiskCachingDirectory() + { + return $this->diskCachingDirectory; + } + + protected function processFlags(int $flags): void + { + if (((bool) ($flags & self::SAVE_WITH_CHARTS)) === true) { + $this->setIncludeCharts(true); + } + } + + /** + * Open file handle. + * + * @param resource|string $filename + */ + public function openFileHandle($filename): void + { + if (is_resource($filename)) { + $this->fileHandle = $filename; + $this->shouldCloseFile = false; + + return; + } + + $mode = 'wb+'; + $scheme = parse_url($filename, PHP_URL_SCHEME); + if ($scheme === 's3') { + $mode = 'w'; + } + $fileHandle = $filename ? fopen($filename, $mode) : false; + if ($fileHandle === false) { + throw new Exception('Could not open file "' . $filename . '" for writing.'); + } + + $this->fileHandle = $fileHandle; + $this->shouldCloseFile = true; + } + + /** + * Close file handle only if we opened it ourselves. + */ + protected function maybeCloseFileHandle(): void + { + if ($this->shouldCloseFile) { + if (!fclose($this->fileHandle)) { + throw new Exception('Could not close file after writing.'); + } + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Csv.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Csv.php new file mode 100644 index 0000000..0f385de --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Csv.php @@ -0,0 +1,404 @@ +spreadsheet = $spreadsheet; + } + + /** + * Save PhpSpreadsheet to file. + * + * @param resource|string $filename + */ + public function save($filename, int $flags = 0): void + { + $this->processFlags($flags); + + // Fetch sheet + $sheet = $this->spreadsheet->getSheet($this->sheetIndex); + + $saveDebugLog = Calculation::getInstance($this->spreadsheet)->getDebugLog()->getWriteDebugLog(); + Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog(false); + $saveArrayReturnType = Calculation::getArrayReturnType(); + Calculation::setArrayReturnType(Calculation::RETURN_ARRAY_AS_VALUE); + + // Open file + $this->openFileHandle($filename); + + if ($this->excelCompatibility) { + $this->setUseBOM(true); // Enforce UTF-8 BOM Header + $this->setIncludeSeparatorLine(true); // Set separator line + $this->setEnclosure('"'); // Set enclosure to " + $this->setDelimiter(';'); // Set delimiter to a semi-colon + $this->setLineEnding("\r\n"); + } + + if ($this->useBOM) { + // Write the UTF-8 BOM code if required + fwrite($this->fileHandle, "\xEF\xBB\xBF"); + } + + if ($this->includeSeparatorLine) { + // Write the separator line if required + fwrite($this->fileHandle, 'sep=' . $this->getDelimiter() . $this->lineEnding); + } + + // Identify the range that we need to extract from the worksheet + $maxCol = $sheet->getHighestDataColumn(); + $maxRow = $sheet->getHighestDataRow(); + + // Write rows to file + for ($row = 1; $row <= $maxRow; ++$row) { + // Convert the row to an array... + $cellsArray = $sheet->rangeToArray('A' . $row . ':' . $maxCol . $row, '', $this->preCalculateFormulas); + // ... and write to the file + $this->writeLine($this->fileHandle, $cellsArray[0]); + } + + $this->maybeCloseFileHandle(); + Calculation::setArrayReturnType($saveArrayReturnType); + Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog($saveDebugLog); + } + + /** + * Get delimiter. + * + * @return string + */ + public function getDelimiter() + { + return $this->delimiter; + } + + /** + * Set delimiter. + * + * @param string $delimiter Delimiter, defaults to ',' + * + * @return $this + */ + public function setDelimiter($delimiter) + { + $this->delimiter = $delimiter; + + return $this; + } + + /** + * Get enclosure. + * + * @return string + */ + public function getEnclosure() + { + return $this->enclosure; + } + + /** + * Set enclosure. + * + * @param string $enclosure Enclosure, defaults to " + * + * @return $this + */ + public function setEnclosure($enclosure = '"') + { + $this->enclosure = $enclosure; + + return $this; + } + + /** + * Get line ending. + * + * @return string + */ + public function getLineEnding() + { + return $this->lineEnding; + } + + /** + * Set line ending. + * + * @param string $lineEnding Line ending, defaults to OS line ending (PHP_EOL) + * + * @return $this + */ + public function setLineEnding($lineEnding) + { + $this->lineEnding = $lineEnding; + + return $this; + } + + /** + * Get whether BOM should be used. + * + * @return bool + */ + public function getUseBOM() + { + return $this->useBOM; + } + + /** + * Set whether BOM should be used. + * + * @param bool $useBOM Use UTF-8 byte-order mark? Defaults to false + * + * @return $this + */ + public function setUseBOM($useBOM) + { + $this->useBOM = $useBOM; + + return $this; + } + + /** + * Get whether a separator line should be included. + * + * @return bool + */ + public function getIncludeSeparatorLine() + { + return $this->includeSeparatorLine; + } + + /** + * Set whether a separator line should be included as the first line of the file. + * + * @param bool $includeSeparatorLine Use separator line? Defaults to false + * + * @return $this + */ + public function setIncludeSeparatorLine($includeSeparatorLine) + { + $this->includeSeparatorLine = $includeSeparatorLine; + + return $this; + } + + /** + * Get whether the file should be saved with full Excel Compatibility. + * + * @return bool + */ + public function getExcelCompatibility() + { + return $this->excelCompatibility; + } + + /** + * Set whether the file should be saved with full Excel Compatibility. + * + * @param bool $excelCompatibility Set the file to be written as a fully Excel compatible csv file + * Note that this overrides other settings such as useBOM, enclosure and delimiter + * + * @return $this + */ + public function setExcelCompatibility($excelCompatibility) + { + $this->excelCompatibility = $excelCompatibility; + + return $this; + } + + /** + * Get sheet index. + * + * @return int + */ + public function getSheetIndex() + { + return $this->sheetIndex; + } + + /** + * Set sheet index. + * + * @param int $sheetIndex Sheet index + * + * @return $this + */ + public function setSheetIndex($sheetIndex) + { + $this->sheetIndex = $sheetIndex; + + return $this; + } + + /** + * Get output encoding. + * + * @return string + */ + public function getOutputEncoding() + { + return $this->outputEncoding; + } + + /** + * Set output encoding. + * + * @param string $outputEnconding Output encoding + * + * @return $this + */ + public function setOutputEncoding($outputEnconding) + { + $this->outputEncoding = $outputEnconding; + + return $this; + } + + /** @var bool */ + private $enclosureRequired = true; + + public function setEnclosureRequired(bool $value): self + { + $this->enclosureRequired = $value; + + return $this; + } + + public function getEnclosureRequired(): bool + { + return $this->enclosureRequired; + } + + /** + * Convert boolean to TRUE/FALSE; otherwise return element cast to string. + * + * @param mixed $element + */ + private static function elementToString($element): string + { + if (is_bool($element)) { + return $element ? 'TRUE' : 'FALSE'; + } + + return (string) $element; + } + + /** + * Write line to CSV file. + * + * @param resource $fileHandle PHP filehandle + * @param array $values Array containing values in a row + */ + private function writeLine($fileHandle, array $values): void + { + // No leading delimiter + $delimiter = ''; + + // Build the line + $line = ''; + + foreach ($values as $element) { + $element = self::elementToString($element); + // Add delimiter + $line .= $delimiter; + $delimiter = $this->delimiter; + // Escape enclosures + $enclosure = $this->enclosure; + if ($enclosure) { + // If enclosure is not required, use enclosure only if + // element contains newline, delimiter, or enclosure. + if (!$this->enclosureRequired && strpbrk($element, "$delimiter$enclosure\n") === false) { + $enclosure = ''; + } else { + $element = str_replace($enclosure, $enclosure . $enclosure, $element); + } + } + // Add enclosed string + $line .= $enclosure . $element . $enclosure; + } + + // Add line ending + $line .= $this->lineEnding; + + // Write to file + if ($this->outputEncoding != '') { + $line = mb_convert_encoding($line, $this->outputEncoding); + } + fwrite($fileHandle, $line); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Exception.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Exception.php new file mode 100644 index 0000000..92e6f5f --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Exception.php @@ -0,0 +1,9 @@ +spreadsheet = $spreadsheet; + $this->defaultFont = $this->spreadsheet->getDefaultStyle()->getFont(); + } + + /** + * Save Spreadsheet to file. + * + * @param resource|string $filename + */ + public function save($filename, int $flags = 0): void + { + $this->processFlags($flags); + + // Open file + $this->openFileHandle($filename); + + // Write html + fwrite($this->fileHandle, $this->generateHTMLAll()); + + // Close file + $this->maybeCloseFileHandle(); + } + + /** + * Save Spreadsheet as html to variable. + * + * @return string + */ + public function generateHtmlAll() + { + // garbage collect + $this->spreadsheet->garbageCollect(); + + $saveDebugLog = Calculation::getInstance($this->spreadsheet)->getDebugLog()->getWriteDebugLog(); + Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog(false); + $saveArrayReturnType = Calculation::getArrayReturnType(); + Calculation::setArrayReturnType(Calculation::RETURN_ARRAY_AS_VALUE); + + // Build CSS + $this->buildCSS(!$this->useInlineCss); + + $html = ''; + + // Write headers + $html .= $this->generateHTMLHeader(!$this->useInlineCss); + + // Write navigation (tabs) + if ((!$this->isPdf) && ($this->generateSheetNavigationBlock)) { + $html .= $this->generateNavigation(); + } + + // Write data + $html .= $this->generateSheetData(); + + // Write footer + $html .= $this->generateHTMLFooter(); + $callback = $this->editHtmlCallback; + if ($callback) { + $html = $callback($html); + } + + Calculation::setArrayReturnType($saveArrayReturnType); + Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog($saveDebugLog); + + return $html; + } + + /** + * Set a callback to edit the entire HTML. + * + * The callback must accept the HTML as string as first parameter, + * and it must return the edited HTML as string. + */ + public function setEditHtmlCallback(?callable $callback): void + { + $this->editHtmlCallback = $callback; + } + + const VALIGN_ARR = [ + Alignment::VERTICAL_BOTTOM => 'bottom', + Alignment::VERTICAL_TOP => 'top', + Alignment::VERTICAL_CENTER => 'middle', + Alignment::VERTICAL_JUSTIFY => 'middle', + ]; + + /** + * Map VAlign. + * + * @param string $vAlign Vertical alignment + * + * @return string + */ + private function mapVAlign($vAlign) + { + return array_key_exists($vAlign, self::VALIGN_ARR) ? self::VALIGN_ARR[$vAlign] : 'baseline'; + } + + const HALIGN_ARR = [ + Alignment::HORIZONTAL_LEFT => 'left', + Alignment::HORIZONTAL_RIGHT => 'right', + Alignment::HORIZONTAL_CENTER => 'center', + Alignment::HORIZONTAL_CENTER_CONTINUOUS => 'center', + Alignment::HORIZONTAL_JUSTIFY => 'justify', + ]; + + /** + * Map HAlign. + * + * @param string $hAlign Horizontal alignment + * + * @return string + */ + private function mapHAlign($hAlign) + { + return array_key_exists($hAlign, self::HALIGN_ARR) ? self::HALIGN_ARR[$hAlign] : ''; + } + + const BORDER_ARR = [ + Border::BORDER_NONE => 'none', + Border::BORDER_DASHDOT => '1px dashed', + Border::BORDER_DASHDOTDOT => '1px dotted', + Border::BORDER_DASHED => '1px dashed', + Border::BORDER_DOTTED => '1px dotted', + Border::BORDER_DOUBLE => '3px double', + Border::BORDER_HAIR => '1px solid', + Border::BORDER_MEDIUM => '2px solid', + Border::BORDER_MEDIUMDASHDOT => '2px dashed', + Border::BORDER_MEDIUMDASHDOTDOT => '2px dotted', + Border::BORDER_SLANTDASHDOT => '2px dashed', + Border::BORDER_THICK => '3px solid', + ]; + + /** + * Map border style. + * + * @param int $borderStyle Sheet index + * + * @return string + */ + private function mapBorderStyle($borderStyle) + { + return array_key_exists($borderStyle, self::BORDER_ARR) ? self::BORDER_ARR[$borderStyle] : '1px solid'; + } + + /** + * Get sheet index. + * + * @return int + */ + public function getSheetIndex() + { + return $this->sheetIndex; + } + + /** + * Set sheet index. + * + * @param int $sheetIndex Sheet index + * + * @return $this + */ + public function setSheetIndex($sheetIndex) + { + $this->sheetIndex = $sheetIndex; + + return $this; + } + + /** + * Get sheet index. + * + * @return bool + */ + public function getGenerateSheetNavigationBlock() + { + return $this->generateSheetNavigationBlock; + } + + /** + * Set sheet index. + * + * @param bool $generateSheetNavigationBlock Flag indicating whether the sheet navigation block should be generated or not + * + * @return $this + */ + public function setGenerateSheetNavigationBlock($generateSheetNavigationBlock) + { + $this->generateSheetNavigationBlock = (bool) $generateSheetNavigationBlock; + + return $this; + } + + /** + * Write all sheets (resets sheetIndex to NULL). + * + * @return $this + */ + public function writeAllSheets() + { + $this->sheetIndex = null; + + return $this; + } + + private static function generateMeta($val, $desc) + { + return $val + ? (' ' . PHP_EOL) + : ''; + } + + /** + * Generate HTML header. + * + * @param bool $includeStyles Include styles? + * + * @return string + */ + public function generateHTMLHeader($includeStyles = false) + { + // Construct HTML + $properties = $this->spreadsheet->getProperties(); + $html = '' . PHP_EOL; + $html .= '' . PHP_EOL; + $html .= ' ' . PHP_EOL; + $html .= ' ' . PHP_EOL; + $html .= ' ' . PHP_EOL; + $html .= ' ' . htmlspecialchars($properties->getTitle(), Settings::htmlEntityFlags()) . '' . PHP_EOL; + $html .= self::generateMeta($properties->getCreator(), 'author'); + $html .= self::generateMeta($properties->getTitle(), 'title'); + $html .= self::generateMeta($properties->getDescription(), 'description'); + $html .= self::generateMeta($properties->getSubject(), 'subject'); + $html .= self::generateMeta($properties->getKeywords(), 'keywords'); + $html .= self::generateMeta($properties->getCategory(), 'category'); + $html .= self::generateMeta($properties->getCompany(), 'company'); + $html .= self::generateMeta($properties->getManager(), 'manager'); + + $html .= $includeStyles ? $this->generateStyles(true) : $this->generatePageDeclarations(true); + + $html .= ' ' . PHP_EOL; + $html .= '' . PHP_EOL; + $html .= ' ' . PHP_EOL; + + return $html; + } + + private function generateSheetPrep() + { + // Ensure that Spans have been calculated? + $this->calculateSpans(); + + // Fetch sheets + if ($this->sheetIndex === null) { + $sheets = $this->spreadsheet->getAllSheets(); + } else { + $sheets = [$this->spreadsheet->getSheet($this->sheetIndex)]; + } + + return $sheets; + } + + private function generateSheetStarts($sheet, $rowMin) + { + // calculate start of , + $tbodyStart = $rowMin; + $theadStart = $theadEnd = 0; // default: no no + if ($sheet->getPageSetup()->isRowsToRepeatAtTopSet()) { + $rowsToRepeatAtTop = $sheet->getPageSetup()->getRowsToRepeatAtTop(); + + // we can only support repeating rows that start at top row + if ($rowsToRepeatAtTop[0] == 1) { + $theadStart = $rowsToRepeatAtTop[0]; + $theadEnd = $rowsToRepeatAtTop[1]; + $tbodyStart = $rowsToRepeatAtTop[1] + 1; + } + } + + return [$theadStart, $theadEnd, $tbodyStart]; + } + + private function generateSheetTags($row, $theadStart, $theadEnd, $tbodyStart) + { + // ? + $startTag = ($row == $theadStart) ? (' ' . PHP_EOL) : ''; + if (!$startTag) { + $startTag = ($row == $tbodyStart) ? (' ' . PHP_EOL) : ''; + } + $endTag = ($row == $theadEnd) ? (' ' . PHP_EOL) : ''; + $cellType = ($row >= $tbodyStart) ? 'td' : 'th'; + + return [$cellType, $startTag, $endTag]; + } + + /** + * Generate sheet data. + * + * @return string + */ + public function generateSheetData() + { + $sheets = $this->generateSheetPrep(); + + // Construct HTML + $html = ''; + + // Loop all sheets + $sheetId = 0; + foreach ($sheets as $sheet) { + // Write table header + $html .= $this->generateTableHeader($sheet); + + // Get worksheet dimension + [$min, $max] = explode(':', $sheet->calculateWorksheetDataDimension()); + [$minCol, $minRow] = Coordinate::indexesFromString($min); + [$maxCol, $maxRow] = Coordinate::indexesFromString($max); + + [$theadStart, $theadEnd, $tbodyStart] = $this->generateSheetStarts($sheet, $minRow); + + // Loop through cells + $row = $minRow - 1; + while ($row++ < $maxRow) { + [$cellType, $startTag, $endTag] = $this->generateSheetTags($row, $theadStart, $theadEnd, $tbodyStart); + $html .= $startTag; + + // Write row if there are HTML table cells in it + if (!isset($this->isSpannedRow[$sheet->getParent()->getIndex($sheet)][$row])) { + // Start a new rowData + $rowData = []; + // Loop through columns + $column = $minCol; + while ($column <= $maxCol) { + // Cell exists? + if ($sheet->cellExistsByColumnAndRow($column, $row)) { + $rowData[$column] = Coordinate::stringFromColumnIndex($column) . $row; + } else { + $rowData[$column] = ''; + } + ++$column; + } + $html .= $this->generateRow($sheet, $rowData, $row - 1, $cellType); + } + + $html .= $endTag; + } + $html .= $this->extendRowsForChartsAndImages($sheet, $row); + + // Write table footer + $html .= $this->generateTableFooter(); + // Writing PDF? + if ($this->isPdf && $this->useInlineCss) { + if ($this->sheetIndex === null && $sheetId + 1 < $this->spreadsheet->getSheetCount()) { + $html .= '

        '; + } + } + + // Next sheet + ++$sheetId; + } + + return $html; + } + + /** + * Generate sheet tabs. + * + * @return string + */ + public function generateNavigation() + { + // Fetch sheets + $sheets = []; + if ($this->sheetIndex === null) { + $sheets = $this->spreadsheet->getAllSheets(); + } else { + $sheets[] = $this->spreadsheet->getSheet($this->sheetIndex); + } + + // Construct HTML + $html = ''; + + // Only if there are more than 1 sheets + if (count($sheets) > 1) { + // Loop all sheets + $sheetId = 0; + + $html .= '' . PHP_EOL; + } + + return $html; + } + + /** + * Extend Row if chart is placed after nominal end of row. + * This code should be exercised by sample: + * Chart/32_Chart_read_write_PDF.php. + * However, that test is suppressed due to out-of-date + * Jpgraph code issuing warnings. So, don't measure + * code coverage for this function till that is fixed. + * + * @param int $row Row to check for charts + * + * @return array + * + * @codeCoverageIgnore + */ + private function extendRowsForCharts(Worksheet $worksheet, int $row) + { + $rowMax = $row; + $colMax = 'A'; + $anyfound = false; + if ($this->includeCharts) { + foreach ($worksheet->getChartCollection() as $chart) { + if ($chart instanceof Chart) { + $anyfound = true; + $chartCoordinates = $chart->getTopLeftPosition(); + $chartTL = Coordinate::coordinateFromString($chartCoordinates['cell']); + $chartCol = Coordinate::columnIndexFromString($chartTL[0]); + if ($chartTL[1] > $rowMax) { + $rowMax = $chartTL[1]; + if ($chartCol > Coordinate::columnIndexFromString($colMax)) { + $colMax = $chartTL[0]; + } + } + } + } + } + + return [$rowMax, $colMax, $anyfound]; + } + + private function extendRowsForChartsAndImages(Worksheet $worksheet, int $row): string + { + [$rowMax, $colMax, $anyfound] = $this->extendRowsForCharts($worksheet, $row); + + foreach ($worksheet->getDrawingCollection() as $drawing) { + $anyfound = true; + $imageTL = Coordinate::coordinateFromString($drawing->getCoordinates()); + $imageCol = Coordinate::columnIndexFromString($imageTL[0]); + if ($imageTL[1] > $rowMax) { + $rowMax = $imageTL[1]; + if ($imageCol > Coordinate::columnIndexFromString($colMax)) { + $colMax = $imageTL[0]; + } + } + } + + // Don't extend rows if not needed + if ($row === $rowMax || !$anyfound) { + return ''; + } + + $html = ''; + ++$colMax; + ++$row; + while ($row <= $rowMax) { + $html .= ''; + for ($col = 'A'; $col != $colMax; ++$col) { + $htmlx = $this->writeImageInCell($worksheet, $col . $row); + $htmlx .= $this->includeCharts ? $this->writeChartInCell($worksheet, $col . $row) : ''; + if ($htmlx) { + $html .= "$htmlx"; + } else { + $html .= ""; + } + } + ++$row; + $html .= '' . PHP_EOL; + } + + return $html; + } + + /** + * Convert Windows file name to file protocol URL. + * + * @param string $filename file name on local system + * + * @return string + */ + public static function winFileToUrl($filename) + { + // Windows filename + if (substr($filename, 1, 2) === ':\\') { + $filename = 'file:///' . str_replace('\\', '/', $filename); + } + + return $filename; + } + + /** + * Generate image tag in cell. + * + * @param Worksheet $worksheet \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet + * @param string $coordinates Cell coordinates + * + * @return string + */ + private function writeImageInCell(Worksheet $worksheet, $coordinates) + { + // Construct HTML + $html = ''; + + // Write images + foreach ($worksheet->getDrawingCollection() as $drawing) { + if ($drawing->getCoordinates() != $coordinates) { + continue; + } + $filedesc = $drawing->getDescription(); + $filedesc = $filedesc ? htmlspecialchars($filedesc, ENT_QUOTES) : 'Embedded image'; + if ($drawing instanceof Drawing) { + $filename = $drawing->getPath(); + + // Strip off eventual '.' + $filename = preg_replace('/^[.]/', '', $filename); + + // Prepend images root + $filename = $this->getImagesRoot() . $filename; + + // Strip off eventual '.' if followed by non-/ + $filename = preg_replace('@^[.]([^/])@', '$1', $filename); + + // Convert UTF8 data to PCDATA + $filename = htmlspecialchars($filename, Settings::htmlEntityFlags()); + + $html .= PHP_EOL; + $imageData = self::winFileToUrl($filename); + + if ($this->embedImages && !$this->isPdf) { + $picture = @file_get_contents($filename); + if ($picture !== false) { + $imageDetails = getimagesize($filename); + // base64 encode the binary data + $base64 = base64_encode($picture); + $imageData = 'data:' . $imageDetails['mime'] . ';base64,' . $base64; + } + } + + $html .= '' . $filedesc . ''; + } elseif ($drawing instanceof MemoryDrawing) { + $imageResource = $drawing->getImageResource(); + if ($imageResource) { + ob_start(); // Let's start output buffering. + imagepng($imageResource); // This will normally output the image, but because of ob_start(), it won't. + $contents = ob_get_contents(); // Instead, output above is saved to $contents + ob_end_clean(); // End the output buffer. + + $dataUri = 'data:image/jpeg;base64,' . base64_encode($contents); + + // Because of the nature of tables, width is more important than height. + // max-width: 100% ensures that image doesnt overflow containing cell + // width: X sets width of supplied image. + // As a result, images bigger than cell will be contained and images smaller will not get stretched + $html .= '' . $filedesc . ''; + } + } + } + + return $html; + } + + /** + * Generate chart tag in cell. + * This code should be exercised by sample: + * Chart/32_Chart_read_write_PDF.php. + * However, that test is suppressed due to out-of-date + * Jpgraph code issuing warnings. So, don't measure + * code coverage for this function till that is fixed. + * + * @codeCoverageIgnore + */ + private function writeChartInCell(Worksheet $worksheet, string $coordinates): string + { + // Construct HTML + $html = ''; + + // Write charts + foreach ($worksheet->getChartCollection() as $chart) { + if ($chart instanceof Chart) { + $chartCoordinates = $chart->getTopLeftPosition(); + if ($chartCoordinates['cell'] == $coordinates) { + $chartFileName = File::sysGetTempDir() . '/' . uniqid('', true) . '.png'; + if (!$chart->render($chartFileName)) { + return ''; + } + + $html .= PHP_EOL; + $imageDetails = getimagesize($chartFileName); + $filedesc = $chart->getTitle(); + $filedesc = $filedesc ? $filedesc->getCaptionText() : ''; + $filedesc = $filedesc ? htmlspecialchars($filedesc, ENT_QUOTES) : 'Embedded chart'; + if ($fp = fopen($chartFileName, 'rb', 0)) { + $picture = fread($fp, filesize($chartFileName)); + fclose($fp); + // base64 encode the binary data + $base64 = base64_encode($picture); + $imageData = 'data:' . $imageDetails['mime'] . ';base64,' . $base64; + + $html .= '' . $filedesc . '' . PHP_EOL; + + unlink($chartFileName); + } + } + } + } + + // Return + return $html; + } + + /** + * Generate CSS styles. + * + * @param bool $generateSurroundingHTML Generate surrounding HTML tags? (<style> and </style>) + * + * @return string + */ + public function generateStyles($generateSurroundingHTML = true) + { + // Build CSS + $css = $this->buildCSS($generateSurroundingHTML); + + // Construct HTML + $html = ''; + + // Start styles + if ($generateSurroundingHTML) { + $html .= ' ' . PHP_EOL; + } + + // Return + return $html; + } + + private function buildCssRowHeights(Worksheet $sheet, array &$css, int $sheetIndex): void + { + // Calculate row heights + foreach ($sheet->getRowDimensions() as $rowDimension) { + $row = $rowDimension->getRowIndex() - 1; + + // table.sheetN tr.rowYYYYYY { } + $css['table.sheet' . $sheetIndex . ' tr.row' . $row] = []; + + if ($rowDimension->getRowHeight() != -1) { + $pt_height = $rowDimension->getRowHeight(); + $css['table.sheet' . $sheetIndex . ' tr.row' . $row]['height'] = $pt_height . 'pt'; + } + if ($rowDimension->getVisible() === false) { + $css['table.sheet' . $sheetIndex . ' tr.row' . $row]['display'] = 'none'; + $css['table.sheet' . $sheetIndex . ' tr.row' . $row]['visibility'] = 'hidden'; + } + } + } + + private function buildCssPerSheet(Worksheet $sheet, array &$css): void + { + // Calculate hash code + $sheetIndex = $sheet->getParent()->getIndex($sheet); + + // Build styles + // Calculate column widths + $sheet->calculateColumnWidths(); + + // col elements, initialize + $highestColumnIndex = Coordinate::columnIndexFromString($sheet->getHighestColumn()) - 1; + $column = -1; + while ($column++ < $highestColumnIndex) { + $this->columnWidths[$sheetIndex][$column] = 42; // approximation + $css['table.sheet' . $sheetIndex . ' col.col' . $column]['width'] = '42pt'; + } + + // col elements, loop through columnDimensions and set width + foreach ($sheet->getColumnDimensions() as $columnDimension) { + $column = Coordinate::columnIndexFromString($columnDimension->getColumnIndex()) - 1; + $width = SharedDrawing::cellDimensionToPixels($columnDimension->getWidth(), $this->defaultFont); + $width = SharedDrawing::pixelsToPoints($width); + if ($columnDimension->getVisible() === false) { + $css['table.sheet' . $sheetIndex . ' .column' . $column]['display'] = 'none'; + } + if ($width >= 0) { + $this->columnWidths[$sheetIndex][$column] = $width; + $css['table.sheet' . $sheetIndex . ' col.col' . $column]['width'] = $width . 'pt'; + } + } + + // Default row height + $rowDimension = $sheet->getDefaultRowDimension(); + + // table.sheetN tr { } + $css['table.sheet' . $sheetIndex . ' tr'] = []; + + if ($rowDimension->getRowHeight() == -1) { + $pt_height = SharedFont::getDefaultRowHeightByFont($this->spreadsheet->getDefaultStyle()->getFont()); + } else { + $pt_height = $rowDimension->getRowHeight(); + } + $css['table.sheet' . $sheetIndex . ' tr']['height'] = $pt_height . 'pt'; + if ($rowDimension->getVisible() === false) { + $css['table.sheet' . $sheetIndex . ' tr']['display'] = 'none'; + $css['table.sheet' . $sheetIndex . ' tr']['visibility'] = 'hidden'; + } + + $this->buildCssRowHeights($sheet, $css, $sheetIndex); + } + + /** + * Build CSS styles. + * + * @param bool $generateSurroundingHTML Generate surrounding HTML style? (html { }) + * + * @return array + */ + public function buildCSS($generateSurroundingHTML = true) + { + // Cached? + if ($this->cssStyles !== null) { + return $this->cssStyles; + } + + // Ensure that spans have been calculated + $this->calculateSpans(); + + // Construct CSS + $css = []; + + // Start styles + if ($generateSurroundingHTML) { + // html { } + $css['html']['font-family'] = 'Calibri, Arial, Helvetica, sans-serif'; + $css['html']['font-size'] = '11pt'; + $css['html']['background-color'] = 'white'; + } + + // CSS for comments as found in LibreOffice + $css['a.comment-indicator:hover + div.comment'] = [ + 'background' => '#ffd', + 'position' => 'absolute', + 'display' => 'block', + 'border' => '1px solid black', + 'padding' => '0.5em', + ]; + + $css['a.comment-indicator'] = [ + 'background' => 'red', + 'display' => 'inline-block', + 'border' => '1px solid black', + 'width' => '0.5em', + 'height' => '0.5em', + ]; + + $css['div.comment']['display'] = 'none'; + + // table { } + $css['table']['border-collapse'] = 'collapse'; + + // .b {} + $css['.b']['text-align'] = 'center'; // BOOL + + // .e {} + $css['.e']['text-align'] = 'center'; // ERROR + + // .f {} + $css['.f']['text-align'] = 'right'; // FORMULA + + // .inlineStr {} + $css['.inlineStr']['text-align'] = 'left'; // INLINE + + // .n {} + $css['.n']['text-align'] = 'right'; // NUMERIC + + // .s {} + $css['.s']['text-align'] = 'left'; // STRING + + // Calculate cell style hashes + foreach ($this->spreadsheet->getCellXfCollection() as $index => $style) { + $css['td.style' . $index] = $this->createCSSStyle($style); + $css['th.style' . $index] = $this->createCSSStyle($style); + } + + // Fetch sheets + $sheets = []; + if ($this->sheetIndex === null) { + $sheets = $this->spreadsheet->getAllSheets(); + } else { + $sheets[] = $this->spreadsheet->getSheet($this->sheetIndex); + } + + // Build styles per sheet + foreach ($sheets as $sheet) { + $this->buildCssPerSheet($sheet, $css); + } + + // Cache + if ($this->cssStyles === null) { + $this->cssStyles = $css; + } + + // Return + return $css; + } + + /** + * Create CSS style. + * + * @return array + */ + private function createCSSStyle(Style $style) + { + // Create CSS + return array_merge( + $this->createCSSStyleAlignment($style->getAlignment()), + $this->createCSSStyleBorders($style->getBorders()), + $this->createCSSStyleFont($style->getFont()), + $this->createCSSStyleFill($style->getFill()) + ); + } + + /** + * Create CSS style. + * + * @return array + */ + private function createCSSStyleAlignment(Alignment $alignment) + { + // Construct CSS + $css = []; + + // Create CSS + $css['vertical-align'] = $this->mapVAlign($alignment->getVertical()); + $textAlign = $this->mapHAlign($alignment->getHorizontal()); + if ($textAlign) { + $css['text-align'] = $textAlign; + if (in_array($textAlign, ['left', 'right'])) { + $css['padding-' . $textAlign] = (string) ((int) $alignment->getIndent() * 9) . 'px'; + } + } + + return $css; + } + + /** + * Create CSS style. + * + * @return array + */ + private function createCSSStyleFont(Font $font) + { + // Construct CSS + $css = []; + + // Create CSS + if ($font->getBold()) { + $css['font-weight'] = 'bold'; + } + if ($font->getUnderline() != Font::UNDERLINE_NONE && $font->getStrikethrough()) { + $css['text-decoration'] = 'underline line-through'; + } elseif ($font->getUnderline() != Font::UNDERLINE_NONE) { + $css['text-decoration'] = 'underline'; + } elseif ($font->getStrikethrough()) { + $css['text-decoration'] = 'line-through'; + } + if ($font->getItalic()) { + $css['font-style'] = 'italic'; + } + + $css['color'] = '#' . $font->getColor()->getRGB(); + $css['font-family'] = '\'' . $font->getName() . '\''; + $css['font-size'] = $font->getSize() . 'pt'; + + return $css; + } + + /** + * Create CSS style. + * + * @param Borders $borders Borders + * + * @return array + */ + private function createCSSStyleBorders(Borders $borders) + { + // Construct CSS + $css = []; + + // Create CSS + $css['border-bottom'] = $this->createCSSStyleBorder($borders->getBottom()); + $css['border-top'] = $this->createCSSStyleBorder($borders->getTop()); + $css['border-left'] = $this->createCSSStyleBorder($borders->getLeft()); + $css['border-right'] = $this->createCSSStyleBorder($borders->getRight()); + + return $css; + } + + /** + * Create CSS style. + * + * @param Border $border Border + * + * @return string + */ + private function createCSSStyleBorder(Border $border) + { + // Create CSS - add !important to non-none border styles for merged cells + $borderStyle = $this->mapBorderStyle($border->getBorderStyle()); + + return $borderStyle . ' #' . $border->getColor()->getRGB() . (($borderStyle == 'none') ? '' : ' !important'); + } + + /** + * Create CSS style (Fill). + * + * @param Fill $fill Fill + * + * @return array + */ + private function createCSSStyleFill(Fill $fill) + { + // Construct HTML + $css = []; + + // Create CSS + $value = $fill->getFillType() == Fill::FILL_NONE ? + 'white' : '#' . $fill->getStartColor()->getRGB(); + $css['background-color'] = $value; + + return $css; + } + + /** + * Generate HTML footer. + */ + public function generateHTMLFooter() + { + // Construct HTML + $html = ''; + $html .= ' ' . PHP_EOL; + $html .= '' . PHP_EOL; + + return $html; + } + + private function generateTableTagInline(Worksheet $worksheet, $id) + { + $style = isset($this->cssStyles['table']) ? + $this->assembleCSS($this->cssStyles['table']) : ''; + + $prntgrid = $worksheet->getPrintGridlines(); + $viewgrid = $this->isPdf ? $prntgrid : $worksheet->getShowGridlines(); + if ($viewgrid && $prntgrid) { + $html = " " . PHP_EOL; + } elseif ($viewgrid) { + $html = "
        " . PHP_EOL; + } elseif ($prntgrid) { + $html = "
        " . PHP_EOL; + } else { + $html = "
        " . PHP_EOL; + } + + return $html; + } + + private function generateTableTag(Worksheet $worksheet, $id, &$html, $sheetIndex): void + { + if (!$this->useInlineCss) { + $gridlines = $worksheet->getShowGridlines() ? ' gridlines' : ''; + $gridlinesp = $worksheet->getPrintGridlines() ? ' gridlinesp' : ''; + $html .= "
        " . PHP_EOL; + } else { + $html .= $this->generateTableTagInline($worksheet, $id); + } + } + + /** + * Generate table header. + * + * @param Worksheet $worksheet The worksheet for the table we are writing + * @param bool $showid whether or not to add id to table tag + * + * @return string + */ + private function generateTableHeader(Worksheet $worksheet, $showid = true) + { + $sheetIndex = $worksheet->getParent()->getIndex($worksheet); + + // Construct HTML + $html = ''; + $id = $showid ? "id='sheet$sheetIndex'" : ''; + if ($showid) { + $html .= "
        \n"; + } else { + $html .= "
        \n"; + } + + $this->generateTableTag($worksheet, $id, $html, $sheetIndex); + + // Write
        elements + $highestColumnIndex = Coordinate::columnIndexFromString($worksheet->getHighestColumn()) - 1; + $i = -1; + while ($i++ < $highestColumnIndex) { + if (!$this->useInlineCss) { + $html .= ' ' . PHP_EOL; + } else { + $style = isset($this->cssStyles['table.sheet' . $sheetIndex . ' col.col' . $i]) ? + $this->assembleCSS($this->cssStyles['table.sheet' . $sheetIndex . ' col.col' . $i]) : ''; + $html .= ' ' . PHP_EOL; + } + } + + return $html; + } + + /** + * Generate table footer. + */ + private function generateTableFooter() + { + return '
        ' . PHP_EOL . '' . PHP_EOL; + } + + /** + * Generate row start. + * + * @param int $sheetIndex Sheet index (0-based) + * @param int $row row number + * + * @return string + */ + private function generateRowStart(Worksheet $worksheet, $sheetIndex, $row) + { + $html = ''; + if (count($worksheet->getBreaks()) > 0) { + $breaks = $worksheet->getBreaks(); + + // check if a break is needed before this row + if (isset($breaks['A' . $row])) { + // close table: + $html .= $this->generateTableFooter(); + if ($this->isPdf && $this->useInlineCss) { + $html .= '
        '; + } + + // open table again: + etc. + $html .= $this->generateTableHeader($worksheet, false); + $html .= '' . PHP_EOL; + } + } + + // Write row start + if (!$this->useInlineCss) { + $html .= ' ' . PHP_EOL; + } else { + $style = isset($this->cssStyles['table.sheet' . $sheetIndex . ' tr.row' . $row]) + ? $this->assembleCSS($this->cssStyles['table.sheet' . $sheetIndex . ' tr.row' . $row]) : ''; + + $html .= ' ' . PHP_EOL; + } + + return $html; + } + + private function generateRowCellCss(Worksheet $worksheet, $cellAddress, $row, $columnNumber) + { + $cell = ($cellAddress > '') ? $worksheet->getCell($cellAddress) : ''; + $coordinate = Coordinate::stringFromColumnIndex($columnNumber + 1) . ($row + 1); + if (!$this->useInlineCss) { + $cssClass = 'column' . $columnNumber; + } else { + $cssClass = []; + // The statements below do nothing. + // Commenting out the code rather than deleting it + // in case someone can figure out what their intent was. + //if ($cellType == 'th') { + // if (isset($this->cssStyles['table.sheet' . $sheetIndex . ' th.column' . $colNum])) { + // $this->cssStyles['table.sheet' . $sheetIndex . ' th.column' . $colNum]; + // } + //} else { + // if (isset($this->cssStyles['table.sheet' . $sheetIndex . ' td.column' . $colNum])) { + // $this->cssStyles['table.sheet' . $sheetIndex . ' td.column' . $colNum]; + // } + //} + // End of mystery statements. + } + + return [$cell, $cssClass, $coordinate]; + } + + private function generateRowCellDataValueRich($cell, &$cellData): void + { + // Loop through rich text elements + $elements = $cell->getValue()->getRichTextElements(); + foreach ($elements as $element) { + // Rich text start? + if ($element instanceof Run) { + $cellData .= ''; + + $cellEnd = ''; + if ($element->getFont()->getSuperscript()) { + $cellData .= ''; + $cellEnd = ''; + } elseif ($element->getFont()->getSubscript()) { + $cellData .= ''; + $cellEnd = ''; + } + + // Convert UTF8 data to PCDATA + $cellText = $element->getText(); + $cellData .= htmlspecialchars($cellText, Settings::htmlEntityFlags()); + + $cellData .= $cellEnd; + + $cellData .= ''; + } else { + // Convert UTF8 data to PCDATA + $cellText = $element->getText(); + $cellData .= htmlspecialchars($cellText, Settings::htmlEntityFlags()); + } + } + } + + private function generateRowCellDataValue(Worksheet $worksheet, $cell, &$cellData): void + { + if ($cell->getValue() instanceof RichText) { + $this->generateRowCellDataValueRich($cell, $cellData); + } else { + $origData = $this->preCalculateFormulas ? $cell->getCalculatedValue() : $cell->getValue(); + $formatCode = $worksheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getNumberFormat()->getFormatCode(); + if ($formatCode !== null) { + $cellData = NumberFormat::toFormattedString( + $origData, + $formatCode, + [$this, 'formatColor'] + ); + } + + if ($cellData === $origData) { + $cellData = htmlspecialchars($cellData ?? '', Settings::htmlEntityFlags()); + } + if ($worksheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getFont()->getSuperscript()) { + $cellData = '' . $cellData . ''; + } elseif ($worksheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getFont()->getSubscript()) { + $cellData = '' . $cellData . ''; + } + } + } + + private function generateRowCellData(Worksheet $worksheet, $cell, &$cssClass, $cellType) + { + $cellData = ' '; + if ($cell instanceof Cell) { + $cellData = ''; + // Don't know what this does, and no test cases. + //if ($cell->getParent() === null) { + // $cell->attach($worksheet); + //} + // Value + $this->generateRowCellDataValue($worksheet, $cell, $cellData); + + // Converts the cell content so that spaces occuring at beginning of each new line are replaced by   + // Example: " Hello\n to the world" is converted to "  Hello\n to the world" + $cellData = preg_replace('/(?m)(?:^|\\G) /', ' ', $cellData); + + // convert newline "\n" to '
        ' + $cellData = nl2br($cellData); + + // Extend CSS class? + if (!$this->useInlineCss) { + $cssClass .= ' style' . $cell->getXfIndex(); + $cssClass .= ' ' . $cell->getDataType(); + } else { + if ($cellType == 'th') { + if (isset($this->cssStyles['th.style' . $cell->getXfIndex()])) { + $cssClass = array_merge($cssClass, $this->cssStyles['th.style' . $cell->getXfIndex()]); + } + } else { + if (isset($this->cssStyles['td.style' . $cell->getXfIndex()])) { + $cssClass = array_merge($cssClass, $this->cssStyles['td.style' . $cell->getXfIndex()]); + } + } + + // General horizontal alignment: Actual horizontal alignment depends on dataType + $sharedStyle = $worksheet->getParent()->getCellXfByIndex($cell->getXfIndex()); + if ( + $sharedStyle->getAlignment()->getHorizontal() == Alignment::HORIZONTAL_GENERAL + && isset($this->cssStyles['.' . $cell->getDataType()]['text-align']) + ) { + $cssClass['text-align'] = $this->cssStyles['.' . $cell->getDataType()]['text-align']; + } + } + } else { + // Use default borders for empty cell + if (is_string($cssClass)) { + $cssClass .= ' style0'; + } + } + + return $cellData; + } + + private function generateRowIncludeCharts(Worksheet $worksheet, $coordinate) + { + return $this->includeCharts ? $this->writeChartInCell($worksheet, $coordinate) : ''; + } + + private function generateRowSpans($html, $rowSpan, $colSpan) + { + $html .= ($colSpan > 1) ? (' colspan="' . $colSpan . '"') : ''; + $html .= ($rowSpan > 1) ? (' rowspan="' . $rowSpan . '"') : ''; + + return $html; + } + + private function generateRowWriteCell(&$html, Worksheet $worksheet, $coordinate, $cellType, $cellData, $colSpan, $rowSpan, $cssClass, $colNum, $sheetIndex, $row): void + { + // Image? + $htmlx = $this->writeImageInCell($worksheet, $coordinate); + // Chart? + $htmlx .= $this->generateRowIncludeCharts($worksheet, $coordinate); + // Column start + $html .= ' <' . $cellType; + if (!$this->useInlineCss && !$this->isPdf) { + $html .= ' class="' . $cssClass . '"'; + if ($htmlx) { + $html .= " style='position: relative;'"; + } + } else { + //** Necessary redundant code for the sake of \PhpOffice\PhpSpreadsheet\Writer\Pdf ** + // We must explicitly write the width of the + if ($this->useInlineCss) { + $xcssClass = $cssClass; + } else { + $html .= ' class="' . $cssClass . '"'; + $xcssClass = []; + } + $width = 0; + $i = $colNum - 1; + $e = $colNum + $colSpan - 1; + while ($i++ < $e) { + if (isset($this->columnWidths[$sheetIndex][$i])) { + $width += $this->columnWidths[$sheetIndex][$i]; + } + } + $xcssClass['width'] = $width . 'pt'; + + // We must also explicitly write the height of the + if (isset($this->cssStyles['table.sheet' . $sheetIndex . ' tr.row' . $row]['height'])) { + $height = $this->cssStyles['table.sheet' . $sheetIndex . ' tr.row' . $row]['height']; + $xcssClass['height'] = $height; + } + //** end of redundant code ** + + if ($htmlx) { + $xcssClass['position'] = 'relative'; + } + $html .= ' style="' . $this->assembleCSS($xcssClass) . '"'; + } + $html = $this->generateRowSpans($html, $rowSpan, $colSpan); + + $html .= '>'; + $html .= $htmlx; + + $html .= $this->writeComment($worksheet, $coordinate); + + // Cell data + $html .= $cellData; + + // Column end + $html .= '' . PHP_EOL; + } + + /** + * Generate row. + * + * @param array $values Array containing cells in a row + * @param int $row Row number (0-based) + * @param string $cellType eg: 'td' + * + * @return string + */ + private function generateRow(Worksheet $worksheet, array $values, $row, $cellType) + { + // Sheet index + $sheetIndex = $worksheet->getParent()->getIndex($worksheet); + $html = $this->generateRowStart($worksheet, $sheetIndex, $row); + + // Write cells + $colNum = 0; + foreach ($values as $cellAddress) { + [$cell, $cssClass, $coordinate] = $this->generateRowCellCss($worksheet, $cellAddress, $row, $colNum); + + $colSpan = 1; + $rowSpan = 1; + + // Cell Data + $cellData = $this->generateRowCellData($worksheet, $cell, $cssClass, $cellType); + + // Hyperlink? + if ($worksheet->hyperlinkExists($coordinate) && !$worksheet->getHyperlink($coordinate)->isInternal()) { + $cellData = '' . $cellData . ''; + } + + // Should the cell be written or is it swallowed by a rowspan or colspan? + $writeCell = !(isset($this->isSpannedCell[$worksheet->getParent()->getIndex($worksheet)][$row + 1][$colNum]) + && $this->isSpannedCell[$worksheet->getParent()->getIndex($worksheet)][$row + 1][$colNum]); + + // Colspan and Rowspan + $colspan = 1; + $rowspan = 1; + if (isset($this->isBaseCell[$worksheet->getParent()->getIndex($worksheet)][$row + 1][$colNum])) { + $spans = $this->isBaseCell[$worksheet->getParent()->getIndex($worksheet)][$row + 1][$colNum]; + $rowSpan = $spans['rowspan']; + $colSpan = $spans['colspan']; + + // Also apply style from last cell in merge to fix borders - + // relies on !important for non-none border declarations in createCSSStyleBorder + $endCellCoord = Coordinate::stringFromColumnIndex($colNum + $colSpan) . ($row + $rowSpan); + if (!$this->useInlineCss) { + $cssClass .= ' style' . $worksheet->getCell($endCellCoord)->getXfIndex(); + } + } + + // Write + if ($writeCell) { + $this->generateRowWriteCell($html, $worksheet, $coordinate, $cellType, $cellData, $colSpan, $rowSpan, $cssClass, $colNum, $sheetIndex, $row); + } + + // Next column + ++$colNum; + } + + // Write row end + $html .= ' ' . PHP_EOL; + + // Return + return $html; + } + + /** + * Takes array where of CSS properties / values and converts to CSS string. + * + * @return string + */ + private function assembleCSS(array $values = []) + { + $pairs = []; + foreach ($values as $property => $value) { + $pairs[] = $property . ':' . $value; + } + $string = implode('; ', $pairs); + + return $string; + } + + /** + * Get images root. + * + * @return string + */ + public function getImagesRoot() + { + return $this->imagesRoot; + } + + /** + * Set images root. + * + * @param string $imagesRoot + * + * @return $this + */ + public function setImagesRoot($imagesRoot) + { + $this->imagesRoot = $imagesRoot; + + return $this; + } + + /** + * Get embed images. + * + * @return bool + */ + public function getEmbedImages() + { + return $this->embedImages; + } + + /** + * Set embed images. + * + * @param bool $embedImages + * + * @return $this + */ + public function setEmbedImages($embedImages) + { + $this->embedImages = $embedImages; + + return $this; + } + + /** + * Get use inline CSS? + * + * @return bool + */ + public function getUseInlineCss() + { + return $this->useInlineCss; + } + + /** + * Set use inline CSS? + * + * @param bool $useInlineCss + * + * @return $this + */ + public function setUseInlineCss($useInlineCss) + { + $this->useInlineCss = $useInlineCss; + + return $this; + } + + /** + * Get use embedded CSS? + * + * @return bool + * + * @codeCoverageIgnore + * + * @deprecated no longer used + */ + public function getUseEmbeddedCSS() + { + return $this->useEmbeddedCSS; + } + + /** + * Set use embedded CSS? + * + * @param bool $useEmbeddedCSS + * + * @return $this + * + * @codeCoverageIgnore + * + * @deprecated no longer used + */ + public function setUseEmbeddedCSS($useEmbeddedCSS) + { + $this->useEmbeddedCSS = $useEmbeddedCSS; + + return $this; + } + + /** + * Add color to formatted string as inline style. + * + * @param string $value Plain formatted value without color + * @param string $format Format code + * + * @return string + */ + public function formatColor($value, $format) + { + // Color information, e.g. [Red] is always at the beginning + $color = null; // initialize + $matches = []; + + $color_regex = '/^\\[[a-zA-Z]+\\]/'; + if (preg_match($color_regex, $format, $matches)) { + $color = str_replace(['[', ']'], '', $matches[0]); + $color = strtolower($color); + } + + // convert to PCDATA + $result = htmlspecialchars($value, Settings::htmlEntityFlags()); + + // color span tag + if ($color !== null) { + $result = '' . $result . ''; + } + + return $result; + } + + /** + * Calculate information about HTML colspan and rowspan which is not always the same as Excel's. + */ + private function calculateSpans(): void + { + if ($this->spansAreCalculated) { + return; + } + // Identify all cells that should be omitted in HTML due to cell merge. + // In HTML only the upper-left cell should be written and it should have + // appropriate rowspan / colspan attribute + $sheetIndexes = $this->sheetIndex !== null ? + [$this->sheetIndex] : range(0, $this->spreadsheet->getSheetCount() - 1); + + foreach ($sheetIndexes as $sheetIndex) { + $sheet = $this->spreadsheet->getSheet($sheetIndex); + + $candidateSpannedRow = []; + + // loop through all Excel merged cells + foreach ($sheet->getMergeCells() as $cells) { + [$cells] = Coordinate::splitRange($cells); + $first = $cells[0]; + $last = $cells[1]; + + [$fc, $fr] = Coordinate::indexesFromString($first); + $fc = $fc - 1; + + [$lc, $lr] = Coordinate::indexesFromString($last); + $lc = $lc - 1; + + // loop through the individual cells in the individual merge + $r = $fr - 1; + while ($r++ < $lr) { + // also, flag this row as a HTML row that is candidate to be omitted + $candidateSpannedRow[$r] = $r; + + $c = $fc - 1; + while ($c++ < $lc) { + if (!($c == $fc && $r == $fr)) { + // not the upper-left cell (should not be written in HTML) + $this->isSpannedCell[$sheetIndex][$r][$c] = [ + 'baseCell' => [$fr, $fc], + ]; + } else { + // upper-left is the base cell that should hold the colspan/rowspan attribute + $this->isBaseCell[$sheetIndex][$r][$c] = [ + 'xlrowspan' => $lr - $fr + 1, // Excel rowspan + 'rowspan' => $lr - $fr + 1, // HTML rowspan, value may change + 'xlcolspan' => $lc - $fc + 1, // Excel colspan + 'colspan' => $lc - $fc + 1, // HTML colspan, value may change + ]; + } + } + } + } + + $this->calculateSpansOmitRows($sheet, $sheetIndex, $candidateSpannedRow); + + // TODO: Same for columns + } + + // We have calculated the spans + $this->spansAreCalculated = true; + } + + private function calculateSpansOmitRows($sheet, $sheetIndex, $candidateSpannedRow): void + { + // Identify which rows should be omitted in HTML. These are the rows where all the cells + // participate in a merge and the where base cells are somewhere above. + $countColumns = Coordinate::columnIndexFromString($sheet->getHighestColumn()); + foreach ($candidateSpannedRow as $rowIndex) { + if (isset($this->isSpannedCell[$sheetIndex][$rowIndex])) { + if (count($this->isSpannedCell[$sheetIndex][$rowIndex]) == $countColumns) { + $this->isSpannedRow[$sheetIndex][$rowIndex] = $rowIndex; + } + } + } + + // For each of the omitted rows we found above, the affected rowspans should be subtracted by 1 + if (isset($this->isSpannedRow[$sheetIndex])) { + foreach ($this->isSpannedRow[$sheetIndex] as $rowIndex) { + $adjustedBaseCells = []; + $c = -1; + $e = $countColumns - 1; + while ($c++ < $e) { + $baseCell = $this->isSpannedCell[$sheetIndex][$rowIndex][$c]['baseCell']; + + if (!in_array($baseCell, $adjustedBaseCells)) { + // subtract rowspan by 1 + --$this->isBaseCell[$sheetIndex][$baseCell[0]][$baseCell[1]]['rowspan']; + $adjustedBaseCells[] = $baseCell; + } + } + } + } + } + + /** + * Write a comment in the same format as LibreOffice. + * + * @see https://github.com/LibreOffice/core/blob/9fc9bf3240f8c62ad7859947ab8a033ac1fe93fa/sc/source/filter/html/htmlexp.cxx#L1073-L1092 + * + * @param string $coordinate + * + * @return string + */ + private function writeComment(Worksheet $worksheet, $coordinate) + { + $result = ''; + if (!$this->isPdf && isset($worksheet->getComments()[$coordinate])) { + $sanitizer = new HTMLPurifier(); + $cachePath = File::sysGetTempDir() . '/phpsppur'; + if (is_dir($cachePath) || mkdir($cachePath)) { + $sanitizer->config->set('Cache.SerializerPath', $cachePath); + } + $sanitizedString = $sanitizer->purify($worksheet->getComment($coordinate)->getText()->getPlainText()); + if ($sanitizedString !== '') { + $result .= ''; + $result .= '
        ' . nl2br($sanitizedString) . '
        '; + $result .= PHP_EOL; + } + } + + return $result; + } + + /** + * Generate @page declarations. + * + * @param bool $generateSurroundingHTML + * + * @return string + */ + private function generatePageDeclarations($generateSurroundingHTML) + { + // Ensure that Spans have been calculated? + $this->calculateSpans(); + + // Fetch sheets + $sheets = []; + if ($this->sheetIndex === null) { + $sheets = $this->spreadsheet->getAllSheets(); + } else { + $sheets[] = $this->spreadsheet->getSheet($this->sheetIndex); + } + + // Construct HTML + $htmlPage = $generateSurroundingHTML ? ('' . PHP_EOL) : ''; + + return $htmlPage; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/IWriter.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/IWriter.php new file mode 100644 index 0000000..b0a6272 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/IWriter.php @@ -0,0 +1,91 @@ +setSpreadsheet($spreadsheet); + + $this->writerPartContent = new Content($this); + $this->writerPartMeta = new Meta($this); + $this->writerPartMetaInf = new MetaInf($this); + $this->writerPartMimetype = new Mimetype($this); + $this->writerPartSettings = new Settings($this); + $this->writerPartStyles = new Styles($this); + $this->writerPartThumbnails = new Thumbnails($this); + } + + public function getWriterPartContent(): Content + { + return $this->writerPartContent; + } + + public function getWriterPartMeta(): Meta + { + return $this->writerPartMeta; + } + + public function getWriterPartMetaInf(): MetaInf + { + return $this->writerPartMetaInf; + } + + public function getWriterPartMimetype(): Mimetype + { + return $this->writerPartMimetype; + } + + public function getWriterPartSettings(): Settings + { + return $this->writerPartSettings; + } + + public function getWriterPartStyles(): Styles + { + return $this->writerPartStyles; + } + + public function getWriterPartThumbnails(): Thumbnails + { + return $this->writerPartThumbnails; + } + + /** + * Save PhpSpreadsheet to file. + * + * @param resource|string $filename + */ + public function save($filename, int $flags = 0): void + { + if (!$this->spreadSheet) { + throw new WriterException('PhpSpreadsheet object unassigned.'); + } + + $this->processFlags($flags); + + // garbage collect + $this->spreadSheet->garbageCollect(); + + $this->openFileHandle($filename); + + $zip = $this->createZip(); + + $zip->addFile('META-INF/manifest.xml', $this->getWriterPartMetaInf()->write()); + $zip->addFile('Thumbnails/thumbnail.png', $this->getWriterPartthumbnails()->write()); + $zip->addFile('content.xml', $this->getWriterPartcontent()->write()); + $zip->addFile('meta.xml', $this->getWriterPartmeta()->write()); + $zip->addFile('mimetype', $this->getWriterPartmimetype()->write()); + $zip->addFile('settings.xml', $this->getWriterPartsettings()->write()); + $zip->addFile('styles.xml', $this->getWriterPartstyles()->write()); + + // Close file + try { + $zip->finish(); + } catch (OverflowException $e) { + throw new WriterException('Could not close resource.'); + } + + $this->maybeCloseFileHandle(); + } + + /** + * Create zip object. + * + * @return ZipStream + */ + private function createZip() + { + // Try opening the ZIP file + if (!is_resource($this->fileHandle)) { + throw new WriterException('Could not open resource for writing.'); + } + + // Create new ZIP stream + $options = new Archive(); + $options->setEnableZip64(false); + $options->setOutputStream($this->fileHandle); + + return new ZipStream(null, $options); + } + + /** + * Get Spreadsheet object. + * + * @return Spreadsheet + */ + public function getSpreadsheet() + { + if ($this->spreadSheet !== null) { + return $this->spreadSheet; + } + + throw new WriterException('No PhpSpreadsheet assigned.'); + } + + /** + * Set Spreadsheet object. + * + * @param Spreadsheet $spreadsheet PhpSpreadsheet object + * + * @return $this + */ + public function setSpreadsheet(Spreadsheet $spreadsheet) + { + $this->spreadSheet = $spreadsheet; + + return $this; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/AutoFilters.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/AutoFilters.php new file mode 100644 index 0000000..cf0450f --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/AutoFilters.php @@ -0,0 +1,63 @@ +objWriter = $objWriter; + $this->spreadsheet = $spreadsheet; + } + + public function write(): void + { + $wrapperWritten = false; + $sheetCount = $this->spreadsheet->getSheetCount(); + for ($i = 0; $i < $sheetCount; ++$i) { + $worksheet = $this->spreadsheet->getSheet($i); + $autofilter = $worksheet->getAutoFilter(); + if ($autofilter !== null && !empty($autofilter->getRange())) { + if ($wrapperWritten === false) { + $this->objWriter->startElement('table:database-ranges'); + $wrapperWritten = true; + } + $this->objWriter->startElement('table:database-range'); + $this->objWriter->writeAttribute('table:orientation', 'column'); + $this->objWriter->writeAttribute('table:display-filter-buttons', 'true'); + $this->objWriter->writeAttribute( + 'table:target-range-address', + $this->formatRange($worksheet, $autofilter) + ); + $this->objWriter->endElement(); + } + } + + if ($wrapperWritten === true) { + $this->objWriter->endElement(); + } + } + + protected function formatRange(Worksheet $worksheet, Autofilter $autofilter): string + { + $title = $worksheet->getTitle(); + $range = $autofilter->getRange(); + + return "'{$title}'.{$range}"; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Cell/Comment.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Cell/Comment.php new file mode 100644 index 0000000..b0829bf --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Cell/Comment.php @@ -0,0 +1,30 @@ + + */ +class Comment +{ + public static function write(XMLWriter $objWriter, Cell $cell): void + { + $comments = $cell->getWorksheet()->getComments(); + if (!isset($comments[$cell->getCoordinate()])) { + return; + } + $comment = $comments[$cell->getCoordinate()]; + + $objWriter->startElement('office:annotation'); + $objWriter->writeAttribute('svg:width', $comment->getWidth()); + $objWriter->writeAttribute('svg:height', $comment->getHeight()); + $objWriter->writeAttribute('svg:x', $comment->getMarginLeft()); + $objWriter->writeAttribute('svg:y', $comment->getMarginTop()); + $objWriter->writeElement('dc:creator', $comment->getAuthor()); + $objWriter->writeElement('text:p', $comment->getText()->getPlainText()); + $objWriter->endElement(); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Cell/Style.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Cell/Style.php new file mode 100644 index 0000000..f8aae20 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Cell/Style.php @@ -0,0 +1,178 @@ +writer = $writer; + } + + private function mapHorizontalAlignment(string $horizontalAlignment): string + { + switch ($horizontalAlignment) { + case Alignment::HORIZONTAL_CENTER: + case Alignment::HORIZONTAL_CENTER_CONTINUOUS: + case Alignment::HORIZONTAL_DISTRIBUTED: + return 'center'; + case Alignment::HORIZONTAL_RIGHT: + return 'end'; + case Alignment::HORIZONTAL_FILL: + case Alignment::HORIZONTAL_JUSTIFY: + return 'justify'; + } + + return 'start'; + } + + private function mapVerticalAlignment(string $verticalAlignment): string + { + switch ($verticalAlignment) { + case Alignment::VERTICAL_TOP: + return 'top'; + case Alignment::VERTICAL_CENTER: + return 'middle'; + case Alignment::VERTICAL_DISTRIBUTED: + case Alignment::VERTICAL_JUSTIFY: + return 'automatic'; + } + + return 'bottom'; + } + + private function writeFillStyle(Fill $fill): void + { + switch ($fill->getFillType()) { + case Fill::FILL_SOLID: + $this->writer->writeAttribute('fo:background-color', sprintf( + '#%s', + strtolower($fill->getStartColor()->getRGB()) + )); + + break; + case Fill::FILL_GRADIENT_LINEAR: + case Fill::FILL_GRADIENT_PATH: + /// TODO :: To be implemented + break; + case Fill::FILL_NONE: + default: + } + } + + private function writeCellProperties(CellStyle $style): void + { + // Align + $hAlign = $style->getAlignment()->getHorizontal(); + $vAlign = $style->getAlignment()->getVertical(); + $wrap = $style->getAlignment()->getWrapText(); + + $this->writer->startElement('style:table-cell-properties'); + if (!empty($vAlign) || $wrap) { + if (!empty($vAlign)) { + $vAlign = $this->mapVerticalAlignment($vAlign); + $this->writer->writeAttribute('style:vertical-align', $vAlign); + } + if ($wrap) { + $this->writer->writeAttribute('fo:wrap-option', 'wrap'); + } + } + $this->writer->writeAttribute('style:rotation-align', 'none'); + + // Fill + if ($fill = $style->getFill()) { + $this->writeFillStyle($fill); + } + + $this->writer->endElement(); + + if (!empty($hAlign)) { + $hAlign = $this->mapHorizontalAlignment($hAlign); + $this->writer->startElement('style:paragraph-properties'); + $this->writer->writeAttribute('fo:text-align', $hAlign); + $this->writer->endElement(); + } + } + + protected function mapUnderlineStyle(Font $font): string + { + switch ($font->getUnderline()) { + case Font::UNDERLINE_DOUBLE: + case Font::UNDERLINE_DOUBLEACCOUNTING: + return'double'; + case Font::UNDERLINE_SINGLE: + case Font::UNDERLINE_SINGLEACCOUNTING: + return'single'; + } + + return 'none'; + } + + protected function writeTextProperties(CellStyle $style): void + { + // Font + $this->writer->startElement('style:text-properties'); + + $font = $style->getFont(); + + if ($font->getBold()) { + $this->writer->writeAttribute('fo:font-weight', 'bold'); + $this->writer->writeAttribute('style:font-weight-complex', 'bold'); + $this->writer->writeAttribute('style:font-weight-asian', 'bold'); + } + + if ($font->getItalic()) { + $this->writer->writeAttribute('fo:font-style', 'italic'); + } + + if ($color = $font->getColor()) { + $this->writer->writeAttribute('fo:color', sprintf('#%s', $color->getRGB())); + } + + if ($family = $font->getName()) { + $this->writer->writeAttribute('fo:font-family', $family); + } + + if ($size = $font->getSize()) { + $this->writer->writeAttribute('fo:font-size', sprintf('%.1Fpt', $size)); + } + + if ($font->getUnderline() && $font->getUnderline() !== Font::UNDERLINE_NONE) { + $this->writer->writeAttribute('style:text-underline-style', 'solid'); + $this->writer->writeAttribute('style:text-underline-width', 'auto'); + $this->writer->writeAttribute('style:text-underline-color', 'font-color'); + + $underline = $this->mapUnderlineStyle($font); + $this->writer->writeAttribute('style:text-underline-type', $underline); + } + + $this->writer->endElement(); // Close style:text-properties + } + + public function write(CellStyle $style): void + { + $this->writer->startElement('style:style'); + $this->writer->writeAttribute('style:name', self::CELL_STYLE_PREFIX . $style->getIndex()); + $this->writer->writeAttribute('style:family', 'table-cell'); + $this->writer->writeAttribute('style:parent-style-name', 'Default'); + + // Alignment, fill colour, etc + $this->writeCellProperties($style); + + // style:text-properties + $this->writeTextProperties($style); + + // End + $this->writer->endElement(); // Close style:style + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Content.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Content.php new file mode 100644 index 0000000..a589e54 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Content.php @@ -0,0 +1,302 @@ + + */ +class Content extends WriterPart +{ + const NUMBER_COLS_REPEATED_MAX = 1024; + const NUMBER_ROWS_REPEATED_MAX = 1048576; + + private $formulaConvertor; + + /** + * Set parent Ods writer. + */ + public function __construct(Ods $writer) + { + parent::__construct($writer); + + $this->formulaConvertor = new Formula($this->getParentWriter()->getSpreadsheet()->getDefinedNames()); + } + + /** + * Write content.xml to XML format. + * + * @return string XML Output + */ + public function write(): string + { + $objWriter = null; + if ($this->getParentWriter()->getUseDiskCaching()) { + $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory()); + } else { + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + } + + // XML header + $objWriter->startDocument('1.0', 'UTF-8'); + + // Content + $objWriter->startElement('office:document-content'); + $objWriter->writeAttribute('xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'); + $objWriter->writeAttribute('xmlns:style', 'urn:oasis:names:tc:opendocument:xmlns:style:1.0'); + $objWriter->writeAttribute('xmlns:text', 'urn:oasis:names:tc:opendocument:xmlns:text:1.0'); + $objWriter->writeAttribute('xmlns:table', 'urn:oasis:names:tc:opendocument:xmlns:table:1.0'); + $objWriter->writeAttribute('xmlns:draw', 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0'); + $objWriter->writeAttribute('xmlns:fo', 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0'); + $objWriter->writeAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink'); + $objWriter->writeAttribute('xmlns:dc', 'http://purl.org/dc/elements/1.1/'); + $objWriter->writeAttribute('xmlns:meta', 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0'); + $objWriter->writeAttribute('xmlns:number', 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0'); + $objWriter->writeAttribute('xmlns:presentation', 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0'); + $objWriter->writeAttribute('xmlns:svg', 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0'); + $objWriter->writeAttribute('xmlns:chart', 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0'); + $objWriter->writeAttribute('xmlns:dr3d', 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0'); + $objWriter->writeAttribute('xmlns:math', 'http://www.w3.org/1998/Math/MathML'); + $objWriter->writeAttribute('xmlns:form', 'urn:oasis:names:tc:opendocument:xmlns:form:1.0'); + $objWriter->writeAttribute('xmlns:script', 'urn:oasis:names:tc:opendocument:xmlns:script:1.0'); + $objWriter->writeAttribute('xmlns:ooo', 'http://openoffice.org/2004/office'); + $objWriter->writeAttribute('xmlns:ooow', 'http://openoffice.org/2004/writer'); + $objWriter->writeAttribute('xmlns:oooc', 'http://openoffice.org/2004/calc'); + $objWriter->writeAttribute('xmlns:dom', 'http://www.w3.org/2001/xml-events'); + $objWriter->writeAttribute('xmlns:xforms', 'http://www.w3.org/2002/xforms'); + $objWriter->writeAttribute('xmlns:xsd', 'http://www.w3.org/2001/XMLSchema'); + $objWriter->writeAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'); + $objWriter->writeAttribute('xmlns:rpt', 'http://openoffice.org/2005/report'); + $objWriter->writeAttribute('xmlns:of', 'urn:oasis:names:tc:opendocument:xmlns:of:1.2'); + $objWriter->writeAttribute('xmlns:xhtml', 'http://www.w3.org/1999/xhtml'); + $objWriter->writeAttribute('xmlns:grddl', 'http://www.w3.org/2003/g/data-view#'); + $objWriter->writeAttribute('xmlns:tableooo', 'http://openoffice.org/2009/table'); + $objWriter->writeAttribute('xmlns:field', 'urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0'); + $objWriter->writeAttribute('xmlns:formx', 'urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0'); + $objWriter->writeAttribute('xmlns:css3t', 'http://www.w3.org/TR/css3-text/'); + $objWriter->writeAttribute('office:version', '1.2'); + + $objWriter->writeElement('office:scripts'); + $objWriter->writeElement('office:font-face-decls'); + + // Styles XF + $objWriter->startElement('office:automatic-styles'); + $this->writeXfStyles($objWriter, $this->getParentWriter()->getSpreadsheet()); + $objWriter->endElement(); + + $objWriter->startElement('office:body'); + $objWriter->startElement('office:spreadsheet'); + $objWriter->writeElement('table:calculation-settings'); + + $this->writeSheets($objWriter); + + (new AutoFilters($objWriter, $this->getParentWriter()->getSpreadsheet()))->write(); + // Defined names (ranges and formulae) + (new NamedExpressions($objWriter, $this->getParentWriter()->getSpreadsheet(), $this->formulaConvertor))->write(); + + $objWriter->endElement(); + $objWriter->endElement(); + $objWriter->endElement(); + + return $objWriter->getData(); + } + + /** + * Write sheets. + */ + private function writeSheets(XMLWriter $objWriter): void + { + $spreadsheet = $this->getParentWriter()->getSpreadsheet(); /** @var Spreadsheet $spreadsheet */ + $sheetCount = $spreadsheet->getSheetCount(); + for ($i = 0; $i < $sheetCount; ++$i) { + $objWriter->startElement('table:table'); + $objWriter->writeAttribute('table:name', $spreadsheet->getSheet($i)->getTitle()); + $objWriter->writeElement('office:forms'); + $objWriter->startElement('table:table-column'); + $objWriter->writeAttribute('table:number-columns-repeated', self::NUMBER_COLS_REPEATED_MAX); + $objWriter->endElement(); + $this->writeRows($objWriter, $spreadsheet->getSheet($i)); + $objWriter->endElement(); + } + } + + /** + * Write rows of the specified sheet. + */ + private function writeRows(XMLWriter $objWriter, Worksheet $sheet): void + { + $numberRowsRepeated = self::NUMBER_ROWS_REPEATED_MAX; + $span_row = 0; + $rows = $sheet->getRowIterator(); + while ($rows->valid()) { + --$numberRowsRepeated; + $row = $rows->current(); + if ($row->getCellIterator()->valid()) { + if ($span_row) { + $objWriter->startElement('table:table-row'); + if ($span_row > 1) { + $objWriter->writeAttribute('table:number-rows-repeated', $span_row); + } + $objWriter->startElement('table:table-cell'); + $objWriter->writeAttribute('table:number-columns-repeated', self::NUMBER_COLS_REPEATED_MAX); + $objWriter->endElement(); + $objWriter->endElement(); + $span_row = 0; + } + $objWriter->startElement('table:table-row'); + $this->writeCells($objWriter, $row); + $objWriter->endElement(); + } else { + ++$span_row; + } + $rows->next(); + } + } + + /** + * Write cells of the specified row. + */ + private function writeCells(XMLWriter $objWriter, Row $row): void + { + $numberColsRepeated = self::NUMBER_COLS_REPEATED_MAX; + $prevColumn = -1; + $cells = $row->getCellIterator(); + while ($cells->valid()) { + /** @var \PhpOffice\PhpSpreadsheet\Cell\Cell $cell */ + $cell = $cells->current(); + $column = Coordinate::columnIndexFromString($cell->getColumn()) - 1; + + $this->writeCellSpan($objWriter, $column, $prevColumn); + $objWriter->startElement('table:table-cell'); + $this->writeCellMerge($objWriter, $cell); + + // Style XF + $style = $cell->getXfIndex(); + if ($style !== null) { + $objWriter->writeAttribute('table:style-name', Style::CELL_STYLE_PREFIX . $style); + } + + switch ($cell->getDataType()) { + case DataType::TYPE_BOOL: + $objWriter->writeAttribute('office:value-type', 'boolean'); + $objWriter->writeAttribute('office:value', $cell->getValue()); + $objWriter->writeElement('text:p', $cell->getValue()); + + break; + case DataType::TYPE_ERROR: + $objWriter->writeAttribute('table:formula', 'of:=#NULL!'); + $objWriter->writeAttribute('office:value-type', 'string'); + $objWriter->writeAttribute('office:string-value', ''); + $objWriter->writeElement('text:p', '#NULL!'); + + break; + case DataType::TYPE_FORMULA: + $formulaValue = $cell->getValue(); + if ($this->getParentWriter()->getPreCalculateFormulas()) { + try { + $formulaValue = $cell->getCalculatedValue(); + } catch (Exception $e) { + // don't do anything + } + } + $objWriter->writeAttribute('table:formula', $this->formulaConvertor->convertFormula($cell->getValue())); + if (is_numeric($formulaValue)) { + $objWriter->writeAttribute('office:value-type', 'float'); + } else { + $objWriter->writeAttribute('office:value-type', 'string'); + } + $objWriter->writeAttribute('office:value', $formulaValue); + $objWriter->writeElement('text:p', $formulaValue); + + break; + case DataType::TYPE_NUMERIC: + $objWriter->writeAttribute('office:value-type', 'float'); + $objWriter->writeAttribute('office:value', $cell->getValue()); + $objWriter->writeElement('text:p', $cell->getValue()); + + break; + case DataType::TYPE_INLINE: + // break intentionally omitted + case DataType::TYPE_STRING: + $objWriter->writeAttribute('office:value-type', 'string'); + $objWriter->writeElement('text:p', $cell->getValue()); + + break; + } + Comment::write($objWriter, $cell); + $objWriter->endElement(); + $prevColumn = $column; + $cells->next(); + } + $numberColsRepeated = $numberColsRepeated - $prevColumn - 1; + if ($numberColsRepeated > 0) { + if ($numberColsRepeated > 1) { + $objWriter->startElement('table:table-cell'); + $objWriter->writeAttribute('table:number-columns-repeated', $numberColsRepeated); + $objWriter->endElement(); + } else { + $objWriter->writeElement('table:table-cell'); + } + } + } + + /** + * Write span. + * + * @param int $curColumn + * @param int $prevColumn + */ + private function writeCellSpan(XMLWriter $objWriter, $curColumn, $prevColumn): void + { + $diff = $curColumn - $prevColumn - 1; + if (1 === $diff) { + $objWriter->writeElement('table:table-cell'); + } elseif ($diff > 1) { + $objWriter->startElement('table:table-cell'); + $objWriter->writeAttribute('table:number-columns-repeated', $diff); + $objWriter->endElement(); + } + } + + /** + * Write XF cell styles. + */ + private function writeXfStyles(XMLWriter $writer, Spreadsheet $spreadsheet): void + { + $styleWriter = new Style($writer); + foreach ($spreadsheet->getCellXfCollection() as $style) { + $styleWriter->write($style); + } + } + + /** + * Write attributes for merged cell. + */ + private function writeCellMerge(XMLWriter $objWriter, Cell $cell): void + { + if (!$cell->isMergeRangeValueCell()) { + return; + } + + $mergeRange = Coordinate::splitRange($cell->getMergeRange()); + [$startCell, $endCell] = $mergeRange[0]; + $start = Coordinate::coordinateFromString($startCell); + $end = Coordinate::coordinateFromString($endCell); + $columnSpan = Coordinate::columnIndexFromString($end[0]) - Coordinate::columnIndexFromString($start[0]) + 1; + $rowSpan = ((int) $end[1]) - ((int) $start[1]) + 1; + + $objWriter->writeAttribute('table:number-columns-spanned', $columnSpan); + $objWriter->writeAttribute('table:number-rows-spanned', $rowSpan); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Formula.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Formula.php new file mode 100644 index 0000000..db766fb --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Formula.php @@ -0,0 +1,119 @@ +definedNames[] = $definedName->getName(); + } + } + + public function convertFormula(string $formula, string $worksheetName = ''): string + { + $formula = $this->convertCellReferences($formula, $worksheetName); + $formula = $this->convertDefinedNames($formula); + + if (substr($formula, 0, 1) !== '=') { + $formula = '=' . $formula; + } + + return 'of:' . $formula; + } + + private function convertDefinedNames(string $formula): string + { + $splitCount = preg_match_all( + '/' . Calculation::CALCULATION_REGEXP_DEFINEDNAME . '/mui', + $formula, + $splitRanges, + PREG_OFFSET_CAPTURE + ); + + $lengths = array_map('strlen', array_column($splitRanges[0], 0)); + $offsets = array_column($splitRanges[0], 1); + $values = array_column($splitRanges[0], 0); + + while ($splitCount > 0) { + --$splitCount; + $length = $lengths[$splitCount]; + $offset = $offsets[$splitCount]; + $value = $values[$splitCount]; + + if (in_array($value, $this->definedNames, true)) { + $formula = substr($formula, 0, $offset) . '$$' . $value . substr($formula, $offset + $length); + } + } + + return $formula; + } + + private function convertCellReferences(string $formula, string $worksheetName): string + { + $splitCount = preg_match_all( + '/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/mui', + $formula, + $splitRanges, + PREG_OFFSET_CAPTURE + ); + + $lengths = array_map('strlen', array_column($splitRanges[0], 0)); + $offsets = array_column($splitRanges[0], 1); + + $worksheets = $splitRanges[2]; + $columns = $splitRanges[6]; + $rows = $splitRanges[7]; + + // Replace any commas in the formula with semi-colons for Ods + // If by chance there are commas in worksheet names, then they will be "fixed" again in the loop + // because we've already extracted worksheet names with our preg_match_all() + $formula = str_replace(',', ';', $formula); + while ($splitCount > 0) { + --$splitCount; + $length = $lengths[$splitCount]; + $offset = $offsets[$splitCount]; + $worksheet = $worksheets[$splitCount][0]; + $column = $columns[$splitCount][0]; + $row = $rows[$splitCount][0]; + + $newRange = ''; + if (empty($worksheet)) { + if (($offset === 0) || ($formula[$offset - 1] !== ':')) { + // We need a worksheet + $worksheet = $worksheetName; + } + } else { + $worksheet = str_replace("''", "'", trim($worksheet, "'")); + } + if (!empty($worksheet)) { + $newRange = "['" . str_replace("'", "''", $worksheet) . "'"; + } elseif (substr($formula, $offset - 1, 1) !== ':') { + $newRange = '['; + } + $newRange .= '.'; + + if (!empty($column)) { + $newRange .= $column; + } + if (!empty($row)) { + $newRange .= $row; + } + // close the wrapping [] unless this is the first part of a range + $newRange .= substr($formula, $offset + $length, 1) !== ':' ? ']' : ''; + + $formula = substr($formula, 0, $offset) . $newRange . substr($formula, $offset + $length); + } + + return $formula; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Meta.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Meta.php new file mode 100644 index 0000000..16f7c8b --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Meta.php @@ -0,0 +1,122 @@ +getParentWriter()->getSpreadsheet(); + + $objWriter = null; + if ($this->getParentWriter()->getUseDiskCaching()) { + $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory()); + } else { + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + } + + // XML header + $objWriter->startDocument('1.0', 'UTF-8'); + + // Meta + $objWriter->startElement('office:document-meta'); + + $objWriter->writeAttribute('xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'); + $objWriter->writeAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink'); + $objWriter->writeAttribute('xmlns:dc', 'http://purl.org/dc/elements/1.1/'); + $objWriter->writeAttribute('xmlns:meta', 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0'); + $objWriter->writeAttribute('xmlns:ooo', 'http://openoffice.org/2004/office'); + $objWriter->writeAttribute('xmlns:grddl', 'http://www.w3.org/2003/g/data-view#'); + $objWriter->writeAttribute('office:version', '1.2'); + + $objWriter->startElement('office:meta'); + + $objWriter->writeElement('meta:initial-creator', $spreadsheet->getProperties()->getCreator()); + $objWriter->writeElement('dc:creator', $spreadsheet->getProperties()->getCreator()); + $created = $spreadsheet->getProperties()->getCreated(); + $date = Date::dateTimeFromTimestamp("$created"); + $date->setTimeZone(Date::getDefaultOrLocalTimeZone()); + $objWriter->writeElement('meta:creation-date', $date->format(DATE_W3C)); + $created = $spreadsheet->getProperties()->getModified(); + $date = Date::dateTimeFromTimestamp("$created"); + $date->setTimeZone(Date::getDefaultOrLocalTimeZone()); + $objWriter->writeElement('dc:date', $date->format(DATE_W3C)); + $objWriter->writeElement('dc:title', $spreadsheet->getProperties()->getTitle()); + $objWriter->writeElement('dc:description', $spreadsheet->getProperties()->getDescription()); + $objWriter->writeElement('dc:subject', $spreadsheet->getProperties()->getSubject()); + $objWriter->writeElement('meta:keyword', $spreadsheet->getProperties()->getKeywords()); + // Don't know if this changed over time, but the keywords are all + // in a single declaration now. + //$keywords = explode(' ', $spreadsheet->getProperties()->getKeywords()); + //foreach ($keywords as $keyword) { + // $objWriter->writeElement('meta:keyword', $keyword); + //} + + // + $objWriter->startElement('meta:user-defined'); + $objWriter->writeAttribute('meta:name', 'Company'); + $objWriter->writeRaw($spreadsheet->getProperties()->getCompany()); + $objWriter->endElement(); + + $objWriter->startElement('meta:user-defined'); + $objWriter->writeAttribute('meta:name', 'category'); + $objWriter->writeRaw($spreadsheet->getProperties()->getCategory()); + $objWriter->endElement(); + + self::writeDocPropsCustom($objWriter, $spreadsheet); + + $objWriter->endElement(); + + $objWriter->endElement(); + + return $objWriter->getData(); + } + + private static function writeDocPropsCustom(XMLWriter $objWriter, Spreadsheet $spreadsheet): void + { + $customPropertyList = $spreadsheet->getProperties()->getCustomProperties(); + foreach ($customPropertyList as $key => $customProperty) { + $propertyValue = $spreadsheet->getProperties()->getCustomPropertyValue($customProperty); + $propertyType = $spreadsheet->getProperties()->getCustomPropertyType($customProperty); + + $objWriter->startElement('meta:user-defined'); + $objWriter->writeAttribute('meta:name', $customProperty); + + switch ($propertyType) { + case Properties::PROPERTY_TYPE_INTEGER: + case Properties::PROPERTY_TYPE_FLOAT: + $objWriter->writeAttribute('meta:value-type', 'float'); + $objWriter->writeRawData($propertyValue); + + break; + case Properties::PROPERTY_TYPE_BOOLEAN: + $objWriter->writeAttribute('meta:value-type', 'boolean'); + $objWriter->writeRawData($propertyValue ? 'true' : 'false'); + + break; + case Properties::PROPERTY_TYPE_DATE: + $objWriter->writeAttribute('meta:value-type', 'date'); + $dtobj = Date::dateTimeFromTimestamp($propertyValue ?? 0); + $objWriter->writeRawData($dtobj->format(DATE_W3C)); + + break; + default: + $objWriter->writeRawData($propertyValue); + + break; + } + + $objWriter->endElement(); + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/MetaInf.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/MetaInf.php new file mode 100644 index 0000000..f3f0d5f --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/MetaInf.php @@ -0,0 +1,60 @@ +getParentWriter()->getUseDiskCaching()) { + $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory()); + } else { + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + } + + // XML header + $objWriter->startDocument('1.0', 'UTF-8'); + + // Manifest + $objWriter->startElement('manifest:manifest'); + $objWriter->writeAttribute('xmlns:manifest', 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0'); + $objWriter->writeAttribute('manifest:version', '1.2'); + + $objWriter->startElement('manifest:file-entry'); + $objWriter->writeAttribute('manifest:full-path', '/'); + $objWriter->writeAttribute('manifest:version', '1.2'); + $objWriter->writeAttribute('manifest:media-type', 'application/vnd.oasis.opendocument.spreadsheet'); + $objWriter->endElement(); + $objWriter->startElement('manifest:file-entry'); + $objWriter->writeAttribute('manifest:full-path', 'meta.xml'); + $objWriter->writeAttribute('manifest:media-type', 'text/xml'); + $objWriter->endElement(); + $objWriter->startElement('manifest:file-entry'); + $objWriter->writeAttribute('manifest:full-path', 'settings.xml'); + $objWriter->writeAttribute('manifest:media-type', 'text/xml'); + $objWriter->endElement(); + $objWriter->startElement('manifest:file-entry'); + $objWriter->writeAttribute('manifest:full-path', 'content.xml'); + $objWriter->writeAttribute('manifest:media-type', 'text/xml'); + $objWriter->endElement(); + $objWriter->startElement('manifest:file-entry'); + $objWriter->writeAttribute('manifest:full-path', 'Thumbnails/thumbnail.png'); + $objWriter->writeAttribute('manifest:media-type', 'image/png'); + $objWriter->endElement(); + $objWriter->startElement('manifest:file-entry'); + $objWriter->writeAttribute('manifest:full-path', 'styles.xml'); + $objWriter->writeAttribute('manifest:media-type', 'text/xml'); + $objWriter->endElement(); + $objWriter->endElement(); + + return $objWriter->getData(); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Mimetype.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Mimetype.php new file mode 100644 index 0000000..e109e6e --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Mimetype.php @@ -0,0 +1,16 @@ +objWriter = $objWriter; + $this->spreadsheet = $spreadsheet; + $this->formulaConvertor = $formulaConvertor; + } + + public function write(): string + { + $this->objWriter->startElement('table:named-expressions'); + $this->writeExpressions(); + $this->objWriter->endElement(); + + return ''; + } + + private function writeExpressions(): void + { + $definedNames = $this->spreadsheet->getDefinedNames(); + + foreach ($definedNames as $definedName) { + if ($definedName->isFormula()) { + $this->objWriter->startElement('table:named-expression'); + $this->writeNamedFormula($definedName, $this->spreadsheet->getActiveSheet()); + } else { + $this->objWriter->startElement('table:named-range'); + $this->writeNamedRange($definedName); + } + + $this->objWriter->endElement(); + } + } + + private function writeNamedFormula(DefinedName $definedName, Worksheet $defaultWorksheet): void + { + $title = ($definedName->getWorksheet() !== null) ? $definedName->getWorksheet()->getTitle() : $defaultWorksheet->getTitle(); + $this->objWriter->writeAttribute('table:name', $definedName->getName()); + $this->objWriter->writeAttribute( + 'table:expression', + $this->formulaConvertor->convertFormula($definedName->getValue(), $title) + ); + $this->objWriter->writeAttribute('table:base-cell-address', $this->convertAddress( + $definedName, + "'" . $title . "'!\$A\$1" + )); + } + + private function writeNamedRange(DefinedName $definedName): void + { + $baseCell = '$A$1'; + $ws = $definedName->getWorksheet(); + if ($ws !== null) { + $baseCell = "'" . $ws->getTitle() . "'!$baseCell"; + } + $this->objWriter->writeAttribute('table:name', $definedName->getName()); + $this->objWriter->writeAttribute('table:base-cell-address', $this->convertAddress( + $definedName, + $baseCell + )); + $this->objWriter->writeAttribute('table:cell-range-address', $this->convertAddress($definedName, $definedName->getValue())); + } + + private function convertAddress(DefinedName $definedName, string $address): string + { + $splitCount = preg_match_all( + '/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/mui', + $address, + $splitRanges, + PREG_OFFSET_CAPTURE + ); + + $lengths = array_map('strlen', array_column($splitRanges[0], 0)); + $offsets = array_column($splitRanges[0], 1); + + $worksheets = $splitRanges[2]; + $columns = $splitRanges[6]; + $rows = $splitRanges[7]; + + while ($splitCount > 0) { + --$splitCount; + $length = $lengths[$splitCount]; + $offset = $offsets[$splitCount]; + $worksheet = $worksheets[$splitCount][0]; + $column = $columns[$splitCount][0]; + $row = $rows[$splitCount][0]; + + $newRange = ''; + if (empty($worksheet)) { + if (($offset === 0) || ($address[$offset - 1] !== ':')) { + // We need a worksheet + $ws = $definedName->getWorksheet(); + if ($ws !== null) { + $worksheet = $ws->getTitle(); + } + } + } else { + $worksheet = str_replace("''", "'", trim($worksheet, "'")); + } + if (!empty($worksheet)) { + $newRange = "'" . str_replace("'", "''", $worksheet) . "'."; + } + + if (!empty($column)) { + $newRange .= $column; + } + if (!empty($row)) { + $newRange .= $row; + } + + $address = substr($address, 0, $offset) . $newRange . substr($address, $offset + $length); + } + + if (substr($address, 0, 1) === '=') { + $address = substr($address, 1); + } + + return $address; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Settings.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Settings.php new file mode 100644 index 0000000..047bd41 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Settings.php @@ -0,0 +1,88 @@ +getParentWriter()->getUseDiskCaching()) { + $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory()); + } else { + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + } + + // XML header + $objWriter->startDocument('1.0', 'UTF-8'); + + // Settings + $objWriter->startElement('office:document-settings'); + $objWriter->writeAttribute('xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'); + $objWriter->writeAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink'); + $objWriter->writeAttribute('xmlns:config', 'urn:oasis:names:tc:opendocument:xmlns:config:1.0'); + $objWriter->writeAttribute('xmlns:ooo', 'http://openoffice.org/2004/office'); + $objWriter->writeAttribute('office:version', '1.2'); + + $objWriter->startElement('office:settings'); + $objWriter->startElement('config:config-item-set'); + $objWriter->writeAttribute('config:name', 'ooo:view-settings'); + $objWriter->startElement('config:config-item-map-indexed'); + $objWriter->writeAttribute('config:name', 'Views'); + $objWriter->startElement('config:config-item-map-entry'); + $spreadsheet = $this->getParentWriter()->getSpreadsheet(); + + $objWriter->startElement('config:config-item'); + $objWriter->writeAttribute('config:name', 'ViewId'); + $objWriter->writeAttribute('config:type', 'string'); + $objWriter->text('view1'); + $objWriter->endElement(); // ViewId + $objWriter->startElement('config:config-item-map-named'); + $objWriter->writeAttribute('config:name', 'Tables'); + foreach ($spreadsheet->getWorksheetIterator() as $ws) { + $objWriter->startElement('config:config-item-map-entry'); + $objWriter->writeAttribute('config:name', $ws->getTitle()); + $selected = $ws->getSelectedCells(); + if (preg_match('/^([a-z]+)([0-9]+)/i', $selected, $matches) === 1) { + $colSel = Coordinate::columnIndexFromString($matches[1]) - 1; + $rowSel = (int) $matches[2] - 1; + $objWriter->startElement('config:config-item'); + $objWriter->writeAttribute('config:name', 'CursorPositionX'); + $objWriter->writeAttribute('config:type', 'int'); + $objWriter->text($colSel); + $objWriter->endElement(); + $objWriter->startElement('config:config-item'); + $objWriter->writeAttribute('config:name', 'CursorPositionY'); + $objWriter->writeAttribute('config:type', 'int'); + $objWriter->text($rowSel); + $objWriter->endElement(); + } + $objWriter->endElement(); // config:config-item-map-entry + } + $objWriter->endElement(); // config:config-item-map-named + $wstitle = $spreadsheet->getActiveSheet()->getTitle(); + $objWriter->startElement('config:config-item'); + $objWriter->writeAttribute('config:name', 'ActiveTable'); + $objWriter->writeAttribute('config:type', 'string'); + $objWriter->text($wstitle); + $objWriter->endElement(); // config:config-item ActiveTable + + $objWriter->endElement(); // config:config-item-map-entry + $objWriter->endElement(); // config:config-item-map-indexed Views + $objWriter->endElement(); // config:config-item-set ooo:view-settings + $objWriter->startElement('config:config-item-set'); + $objWriter->writeAttribute('config:name', 'ooo:configuration-settings'); + $objWriter->endElement(); // config:config-item-set ooo:configuration-settings + $objWriter->endElement(); // office:settings + $objWriter->endElement(); // office:document-settings + + return $objWriter->getData(); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Styles.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Styles.php new file mode 100644 index 0000000..448b1ef --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Styles.php @@ -0,0 +1,65 @@ +getParentWriter()->getUseDiskCaching()) { + $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory()); + } else { + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + } + + // XML header + $objWriter->startDocument('1.0', 'UTF-8'); + + // Content + $objWriter->startElement('office:document-styles'); + $objWriter->writeAttribute('xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'); + $objWriter->writeAttribute('xmlns:style', 'urn:oasis:names:tc:opendocument:xmlns:style:1.0'); + $objWriter->writeAttribute('xmlns:text', 'urn:oasis:names:tc:opendocument:xmlns:text:1.0'); + $objWriter->writeAttribute('xmlns:table', 'urn:oasis:names:tc:opendocument:xmlns:table:1.0'); + $objWriter->writeAttribute('xmlns:draw', 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0'); + $objWriter->writeAttribute('xmlns:fo', 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0'); + $objWriter->writeAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink'); + $objWriter->writeAttribute('xmlns:dc', 'http://purl.org/dc/elements/1.1/'); + $objWriter->writeAttribute('xmlns:meta', 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0'); + $objWriter->writeAttribute('xmlns:number', 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0'); + $objWriter->writeAttribute('xmlns:presentation', 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0'); + $objWriter->writeAttribute('xmlns:svg', 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0'); + $objWriter->writeAttribute('xmlns:chart', 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0'); + $objWriter->writeAttribute('xmlns:dr3d', 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0'); + $objWriter->writeAttribute('xmlns:math', 'http://www.w3.org/1998/Math/MathML'); + $objWriter->writeAttribute('xmlns:form', 'urn:oasis:names:tc:opendocument:xmlns:form:1.0'); + $objWriter->writeAttribute('xmlns:script', 'urn:oasis:names:tc:opendocument:xmlns:script:1.0'); + $objWriter->writeAttribute('xmlns:ooo', 'http://openoffice.org/2004/office'); + $objWriter->writeAttribute('xmlns:ooow', 'http://openoffice.org/2004/writer'); + $objWriter->writeAttribute('xmlns:oooc', 'http://openoffice.org/2004/calc'); + $objWriter->writeAttribute('xmlns:dom', 'http://www.w3.org/2001/xml-events'); + $objWriter->writeAttribute('xmlns:rpt', 'http://openoffice.org/2005/report'); + $objWriter->writeAttribute('xmlns:of', 'urn:oasis:names:tc:opendocument:xmlns:of:1.2'); + $objWriter->writeAttribute('xmlns:xhtml', 'http://www.w3.org/1999/xhtml'); + $objWriter->writeAttribute('xmlns:grddl', 'http://www.w3.org/2003/g/data-view#'); + $objWriter->writeAttribute('xmlns:tableooo', 'http://openoffice.org/2009/table'); + $objWriter->writeAttribute('xmlns:css3t', 'http://www.w3.org/TR/css3-text/'); + $objWriter->writeAttribute('office:version', '1.2'); + + $objWriter->writeElement('office:font-face-decls'); + $objWriter->writeElement('office:styles'); + $objWriter->writeElement('office:automatic-styles'); + $objWriter->writeElement('office:master-styles'); + $objWriter->endElement(); + + return $objWriter->getData(); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Thumbnails.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Thumbnails.php new file mode 100644 index 0000000..db9579d --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Thumbnails.php @@ -0,0 +1,16 @@ +parentWriter; + } + + /** + * Set parent Ods writer. + */ + public function __construct(Ods $writer) + { + $this->parentWriter = $writer; + } + + abstract public function write(): string; +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf.php new file mode 100644 index 0000000..c419e1e --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf.php @@ -0,0 +1,253 @@ + 'LETTER', // (8.5 in. by 11 in.) + PageSetup::PAPERSIZE_LETTER_SMALL => 'LETTER', // (8.5 in. by 11 in.) + PageSetup::PAPERSIZE_TABLOID => [792.00, 1224.00], // (11 in. by 17 in.) + PageSetup::PAPERSIZE_LEDGER => [1224.00, 792.00], // (17 in. by 11 in.) + PageSetup::PAPERSIZE_LEGAL => 'LEGAL', // (8.5 in. by 14 in.) + PageSetup::PAPERSIZE_STATEMENT => [396.00, 612.00], // (5.5 in. by 8.5 in.) + PageSetup::PAPERSIZE_EXECUTIVE => 'EXECUTIVE', // (7.25 in. by 10.5 in.) + PageSetup::PAPERSIZE_A3 => 'A3', // (297 mm by 420 mm) + PageSetup::PAPERSIZE_A4 => 'A4', // (210 mm by 297 mm) + PageSetup::PAPERSIZE_A4_SMALL => 'A4', // (210 mm by 297 mm) + PageSetup::PAPERSIZE_A5 => 'A5', // (148 mm by 210 mm) + PageSetup::PAPERSIZE_B4 => 'B4', // (250 mm by 353 mm) + PageSetup::PAPERSIZE_B5 => 'B5', // (176 mm by 250 mm) + PageSetup::PAPERSIZE_FOLIO => 'FOLIO', // (8.5 in. by 13 in.) + PageSetup::PAPERSIZE_QUARTO => [609.45, 779.53], // (215 mm by 275 mm) + PageSetup::PAPERSIZE_STANDARD_1 => [720.00, 1008.00], // (10 in. by 14 in.) + PageSetup::PAPERSIZE_STANDARD_2 => [792.00, 1224.00], // (11 in. by 17 in.) + PageSetup::PAPERSIZE_NOTE => 'LETTER', // (8.5 in. by 11 in.) + PageSetup::PAPERSIZE_NO9_ENVELOPE => [279.00, 639.00], // (3.875 in. by 8.875 in.) + PageSetup::PAPERSIZE_NO10_ENVELOPE => [297.00, 684.00], // (4.125 in. by 9.5 in.) + PageSetup::PAPERSIZE_NO11_ENVELOPE => [324.00, 747.00], // (4.5 in. by 10.375 in.) + PageSetup::PAPERSIZE_NO12_ENVELOPE => [342.00, 792.00], // (4.75 in. by 11 in.) + PageSetup::PAPERSIZE_NO14_ENVELOPE => [360.00, 828.00], // (5 in. by 11.5 in.) + PageSetup::PAPERSIZE_C => [1224.00, 1584.00], // (17 in. by 22 in.) + PageSetup::PAPERSIZE_D => [1584.00, 2448.00], // (22 in. by 34 in.) + PageSetup::PAPERSIZE_E => [2448.00, 3168.00], // (34 in. by 44 in.) + PageSetup::PAPERSIZE_DL_ENVELOPE => [311.81, 623.62], // (110 mm by 220 mm) + PageSetup::PAPERSIZE_C5_ENVELOPE => 'C5', // (162 mm by 229 mm) + PageSetup::PAPERSIZE_C3_ENVELOPE => 'C3', // (324 mm by 458 mm) + PageSetup::PAPERSIZE_C4_ENVELOPE => 'C4', // (229 mm by 324 mm) + PageSetup::PAPERSIZE_C6_ENVELOPE => 'C6', // (114 mm by 162 mm) + PageSetup::PAPERSIZE_C65_ENVELOPE => [323.15, 649.13], // (114 mm by 229 mm) + PageSetup::PAPERSIZE_B4_ENVELOPE => 'B4', // (250 mm by 353 mm) + PageSetup::PAPERSIZE_B5_ENVELOPE => 'B5', // (176 mm by 250 mm) + PageSetup::PAPERSIZE_B6_ENVELOPE => [498.90, 354.33], // (176 mm by 125 mm) + PageSetup::PAPERSIZE_ITALY_ENVELOPE => [311.81, 651.97], // (110 mm by 230 mm) + PageSetup::PAPERSIZE_MONARCH_ENVELOPE => [279.00, 540.00], // (3.875 in. by 7.5 in.) + PageSetup::PAPERSIZE_6_3_4_ENVELOPE => [261.00, 468.00], // (3.625 in. by 6.5 in.) + PageSetup::PAPERSIZE_US_STANDARD_FANFOLD => [1071.00, 792.00], // (14.875 in. by 11 in.) + PageSetup::PAPERSIZE_GERMAN_STANDARD_FANFOLD => [612.00, 864.00], // (8.5 in. by 12 in.) + PageSetup::PAPERSIZE_GERMAN_LEGAL_FANFOLD => 'FOLIO', // (8.5 in. by 13 in.) + PageSetup::PAPERSIZE_ISO_B4 => 'B4', // (250 mm by 353 mm) + PageSetup::PAPERSIZE_JAPANESE_DOUBLE_POSTCARD => [566.93, 419.53], // (200 mm by 148 mm) + PageSetup::PAPERSIZE_STANDARD_PAPER_1 => [648.00, 792.00], // (9 in. by 11 in.) + PageSetup::PAPERSIZE_STANDARD_PAPER_2 => [720.00, 792.00], // (10 in. by 11 in.) + PageSetup::PAPERSIZE_STANDARD_PAPER_3 => [1080.00, 792.00], // (15 in. by 11 in.) + PageSetup::PAPERSIZE_INVITE_ENVELOPE => [623.62, 623.62], // (220 mm by 220 mm) + PageSetup::PAPERSIZE_LETTER_EXTRA_PAPER => [667.80, 864.00], // (9.275 in. by 12 in.) + PageSetup::PAPERSIZE_LEGAL_EXTRA_PAPER => [667.80, 1080.00], // (9.275 in. by 15 in.) + PageSetup::PAPERSIZE_TABLOID_EXTRA_PAPER => [841.68, 1296.00], // (11.69 in. by 18 in.) + PageSetup::PAPERSIZE_A4_EXTRA_PAPER => [668.98, 912.76], // (236 mm by 322 mm) + PageSetup::PAPERSIZE_LETTER_TRANSVERSE_PAPER => [595.80, 792.00], // (8.275 in. by 11 in.) + PageSetup::PAPERSIZE_A4_TRANSVERSE_PAPER => 'A4', // (210 mm by 297 mm) + PageSetup::PAPERSIZE_LETTER_EXTRA_TRANSVERSE_PAPER => [667.80, 864.00], // (9.275 in. by 12 in.) + PageSetup::PAPERSIZE_SUPERA_SUPERA_A4_PAPER => [643.46, 1009.13], // (227 mm by 356 mm) + PageSetup::PAPERSIZE_SUPERB_SUPERB_A3_PAPER => [864.57, 1380.47], // (305 mm by 487 mm) + PageSetup::PAPERSIZE_LETTER_PLUS_PAPER => [612.00, 913.68], // (8.5 in. by 12.69 in.) + PageSetup::PAPERSIZE_A4_PLUS_PAPER => [595.28, 935.43], // (210 mm by 330 mm) + PageSetup::PAPERSIZE_A5_TRANSVERSE_PAPER => 'A5', // (148 mm by 210 mm) + PageSetup::PAPERSIZE_JIS_B5_TRANSVERSE_PAPER => [515.91, 728.50], // (182 mm by 257 mm) + PageSetup::PAPERSIZE_A3_EXTRA_PAPER => [912.76, 1261.42], // (322 mm by 445 mm) + PageSetup::PAPERSIZE_A5_EXTRA_PAPER => [493.23, 666.14], // (174 mm by 235 mm) + PageSetup::PAPERSIZE_ISO_B5_EXTRA_PAPER => [569.76, 782.36], // (201 mm by 276 mm) + PageSetup::PAPERSIZE_A2_PAPER => 'A2', // (420 mm by 594 mm) + PageSetup::PAPERSIZE_A3_TRANSVERSE_PAPER => 'A3', // (297 mm by 420 mm) + PageSetup::PAPERSIZE_A3_EXTRA_TRANSVERSE_PAPER => [912.76, 1261.42], // (322 mm by 445 mm) + ]; + + /** + * Create a new PDF Writer instance. + * + * @param Spreadsheet $spreadsheet Spreadsheet object + */ + public function __construct(Spreadsheet $spreadsheet) + { + parent::__construct($spreadsheet); + //$this->setUseInlineCss(true); + $this->tempDir = File::sysGetTempDir() . '/phpsppdf'; + $this->isPdf = true; + } + + /** + * Get Font. + * + * @return string + */ + public function getFont() + { + return $this->font; + } + + /** + * Set font. Examples: + * 'arialunicid0-chinese-simplified' + * 'arialunicid0-chinese-traditional' + * 'arialunicid0-korean' + * 'arialunicid0-japanese'. + * + * @param string $fontName + * + * @return $this + */ + public function setFont($fontName) + { + $this->font = $fontName; + + return $this; + } + + /** + * Get Paper Size. + * + * @return int + */ + public function getPaperSize() + { + return $this->paperSize; + } + + /** + * Set Paper Size. + * + * @param int $paperSize Paper size see PageSetup::PAPERSIZE_* + * + * @return self + */ + public function setPaperSize($paperSize) + { + $this->paperSize = $paperSize; + + return $this; + } + + /** + * Get Orientation. + * + * @return string + */ + public function getOrientation() + { + return $this->orientation; + } + + /** + * Set Orientation. + * + * @param string $orientation Page orientation see PageSetup::ORIENTATION_* + * + * @return self + */ + public function setOrientation($orientation) + { + $this->orientation = $orientation; + + return $this; + } + + /** + * Get temporary storage directory. + * + * @return string + */ + public function getTempDir() + { + return $this->tempDir; + } + + /** + * Set temporary storage directory. + * + * @param string $temporaryDirectory Temporary storage directory + * + * @return self + */ + public function setTempDir($temporaryDirectory) + { + if (is_dir($temporaryDirectory)) { + $this->tempDir = $temporaryDirectory; + } else { + throw new WriterException("Directory does not exist: $temporaryDirectory"); + } + + return $this; + } + + /** + * Save Spreadsheet to PDF file, pre-save. + * + * @param string $filename Name of the file to save as + * + * @return resource + */ + protected function prepareForSave($filename) + { + // Open file + $this->openFileHandle($filename); + + return $this->fileHandle; + } + + /** + * Save PhpSpreadsheet to PDF file, post-save. + */ + protected function restoreStateAfterSave(): void + { + $this->maybeCloseFileHandle(); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Dompdf.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Dompdf.php new file mode 100644 index 0000000..e49c22c --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Dompdf.php @@ -0,0 +1,72 @@ +getSheetIndex() === null) { + $orientation = ($this->spreadsheet->getSheet(0)->getPageSetup()->getOrientation() + == PageSetup::ORIENTATION_LANDSCAPE) ? 'L' : 'P'; + $printPaperSize = $this->spreadsheet->getSheet(0)->getPageSetup()->getPaperSize(); + } else { + $orientation = ($this->spreadsheet->getSheet($this->getSheetIndex())->getPageSetup()->getOrientation() + == PageSetup::ORIENTATION_LANDSCAPE) ? 'L' : 'P'; + $printPaperSize = $this->spreadsheet->getSheet($this->getSheetIndex())->getPageSetup()->getPaperSize(); + } + + $orientation = ($orientation == 'L') ? 'landscape' : 'portrait'; + + // Override Page Orientation + if ($this->getOrientation() !== null) { + $orientation = ($this->getOrientation() == PageSetup::ORIENTATION_DEFAULT) + ? PageSetup::ORIENTATION_PORTRAIT + : $this->getOrientation(); + } + // Override Paper Size + if ($this->getPaperSize() !== null) { + $printPaperSize = $this->getPaperSize(); + } + + if (isset(self::$paperSizes[$printPaperSize])) { + $paperSize = self::$paperSizes[$printPaperSize]; + } + + // Create PDF + $pdf = $this->createExternalWriterInstance(); + $pdf->setPaper($paperSize, $orientation); + + $pdf->loadHtml($this->generateHTMLAll()); + $pdf->render(); + + // Write to file + fwrite($fileHandle, $pdf->output()); + + parent::restoreStateAfterSave(); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Mpdf.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Mpdf.php new file mode 100644 index 0000000..8d0eda9 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Mpdf.php @@ -0,0 +1,106 @@ +getSheetIndex()) { + $orientation = ($this->spreadsheet->getSheet(0)->getPageSetup()->getOrientation() + == PageSetup::ORIENTATION_LANDSCAPE) ? 'L' : 'P'; + $printPaperSize = $this->spreadsheet->getSheet(0)->getPageSetup()->getPaperSize(); + } else { + $orientation = ($this->spreadsheet->getSheet($this->getSheetIndex())->getPageSetup()->getOrientation() + == PageSetup::ORIENTATION_LANDSCAPE) ? 'L' : 'P'; + $printPaperSize = $this->spreadsheet->getSheet($this->getSheetIndex())->getPageSetup()->getPaperSize(); + } + $this->setOrientation($orientation); + + // Override Page Orientation + if (null !== $this->getOrientation()) { + $orientation = ($this->getOrientation() == PageSetup::ORIENTATION_DEFAULT) + ? PageSetup::ORIENTATION_PORTRAIT + : $this->getOrientation(); + } + $orientation = strtoupper($orientation); + + // Override Paper Size + if (null !== $this->getPaperSize()) { + $printPaperSize = $this->getPaperSize(); + } + + if (isset(self::$paperSizes[$printPaperSize])) { + $paperSize = self::$paperSizes[$printPaperSize]; + } + + // Create PDF + $config = ['tempDir' => $this->tempDir . '/mpdf']; + $pdf = $this->createExternalWriterInstance($config); + $ortmp = $orientation; + $pdf->_setPageSize($paperSize, $ortmp); + $pdf->DefOrientation = $orientation; + $pdf->AddPageByArray([ + 'orientation' => $orientation, + 'margin-left' => $this->inchesToMm($this->spreadsheet->getActiveSheet()->getPageMargins()->getLeft()), + 'margin-right' => $this->inchesToMm($this->spreadsheet->getActiveSheet()->getPageMargins()->getRight()), + 'margin-top' => $this->inchesToMm($this->spreadsheet->getActiveSheet()->getPageMargins()->getTop()), + 'margin-bottom' => $this->inchesToMm($this->spreadsheet->getActiveSheet()->getPageMargins()->getBottom()), + ]); + + // Document info + $pdf->SetTitle($this->spreadsheet->getProperties()->getTitle()); + $pdf->SetAuthor($this->spreadsheet->getProperties()->getCreator()); + $pdf->SetSubject($this->spreadsheet->getProperties()->getSubject()); + $pdf->SetKeywords($this->spreadsheet->getProperties()->getKeywords()); + $pdf->SetCreator($this->spreadsheet->getProperties()->getCreator()); + + $html = $this->generateHTMLAll(); + foreach (\array_chunk(\explode(PHP_EOL, $html), 1000) as $lines) { + $pdf->WriteHTML(\implode(PHP_EOL, $lines)); + } + + // Write to file + fwrite($fileHandle, $pdf->Output('', 'S')); + + parent::restoreStateAfterSave(); + } + + /** + * Convert inches to mm. + * + * @param float $inches + * + * @return float + */ + private function inchesToMm($inches) + { + return $inches * 25.4; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Tcpdf.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Tcpdf.php new file mode 100644 index 0000000..6ed16a5 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Tcpdf.php @@ -0,0 +1,104 @@ +setUseInlineCss(true); + } + + /** + * Gets the implementation of external PDF library that should be used. + * + * @param string $orientation Page orientation + * @param string $unit Unit measure + * @param array|string $paperSize Paper size + * + * @return \TCPDF implementation + */ + protected function createExternalWriterInstance($orientation, $unit, $paperSize) + { + return new \TCPDF($orientation, $unit, $paperSize); + } + + /** + * Save Spreadsheet to file. + * + * @param string $filename Name of the file to save as + */ + public function save($filename, int $flags = 0): void + { + $fileHandle = parent::prepareForSave($filename); + + // Default PDF paper size + $paperSize = 'LETTER'; // Letter (8.5 in. by 11 in.) + + // Check for paper size and page orientation + if ($this->getSheetIndex() === null) { + $orientation = ($this->spreadsheet->getSheet(0)->getPageSetup()->getOrientation() + == PageSetup::ORIENTATION_LANDSCAPE) ? 'L' : 'P'; + $printPaperSize = $this->spreadsheet->getSheet(0)->getPageSetup()->getPaperSize(); + $printMargins = $this->spreadsheet->getSheet(0)->getPageMargins(); + } else { + $orientation = ($this->spreadsheet->getSheet($this->getSheetIndex())->getPageSetup()->getOrientation() + == PageSetup::ORIENTATION_LANDSCAPE) ? 'L' : 'P'; + $printPaperSize = $this->spreadsheet->getSheet($this->getSheetIndex())->getPageSetup()->getPaperSize(); + $printMargins = $this->spreadsheet->getSheet($this->getSheetIndex())->getPageMargins(); + } + + // Override Page Orientation + if ($this->getOrientation() !== null) { + $orientation = ($this->getOrientation() == PageSetup::ORIENTATION_LANDSCAPE) + ? 'L' + : 'P'; + } + // Override Paper Size + if ($this->getPaperSize() !== null) { + $printPaperSize = $this->getPaperSize(); + } + + if (isset(self::$paperSizes[$printPaperSize])) { + $paperSize = self::$paperSizes[$printPaperSize]; + } + + // Create PDF + $pdf = $this->createExternalWriterInstance($orientation, 'pt', $paperSize); + $pdf->setFontSubsetting(false); + // Set margins, converting inches to points (using 72 dpi) + $pdf->SetMargins($printMargins->getLeft() * 72, $printMargins->getTop() * 72, $printMargins->getRight() * 72); + $pdf->SetAutoPageBreak(true, $printMargins->getBottom() * 72); + + $pdf->setPrintHeader(false); + $pdf->setPrintFooter(false); + + $pdf->AddPage(); + + // Set the appropriate font + $pdf->SetFont($this->getFont()); + $pdf->writeHTML($this->generateHTMLAll()); + + // Document info + $pdf->SetTitle($this->spreadsheet->getProperties()->getTitle()); + $pdf->SetAuthor($this->spreadsheet->getProperties()->getCreator()); + $pdf->SetSubject($this->spreadsheet->getProperties()->getSubject()); + $pdf->SetKeywords($this->spreadsheet->getProperties()->getKeywords()); + $pdf->SetCreator($this->spreadsheet->getProperties()->getCreator()); + + // Write to file + fwrite($fileHandle, $pdf->output($filename, 'S')); + + parent::restoreStateAfterSave(); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls.php new file mode 100644 index 0000000..d134d69 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls.php @@ -0,0 +1,911 @@ +spreadsheet = $spreadsheet; + + $this->parser = new Xls\Parser($spreadsheet); + } + + /** + * Save Spreadsheet to file. + * + * @param resource|string $filename + */ + public function save($filename, int $flags = 0): void + { + $this->processFlags($flags); + + // garbage collect + $this->spreadsheet->garbageCollect(); + + $saveDebugLog = Calculation::getInstance($this->spreadsheet)->getDebugLog()->getWriteDebugLog(); + Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog(false); + $saveDateReturnType = Functions::getReturnDateType(); + Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); + + // initialize colors array + $this->colors = []; + + // Initialise workbook writer + $this->writerWorkbook = new Xls\Workbook($this->spreadsheet, $this->strTotal, $this->strUnique, $this->strTable, $this->colors, $this->parser); + + // Initialise worksheet writers + $countSheets = $this->spreadsheet->getSheetCount(); + for ($i = 0; $i < $countSheets; ++$i) { + $this->writerWorksheets[$i] = new Xls\Worksheet($this->strTotal, $this->strUnique, $this->strTable, $this->colors, $this->parser, $this->preCalculateFormulas, $this->spreadsheet->getSheet($i)); + } + + // build Escher objects. Escher objects for workbooks needs to be build before Escher object for workbook. + $this->buildWorksheetEschers(); + $this->buildWorkbookEscher(); + + // add 15 identical cell style Xfs + // for now, we use the first cellXf instead of cellStyleXf + $cellXfCollection = $this->spreadsheet->getCellXfCollection(); + for ($i = 0; $i < 15; ++$i) { + $this->writerWorkbook->addXfWriter($cellXfCollection[0], true); + } + + // add all the cell Xfs + foreach ($this->spreadsheet->getCellXfCollection() as $style) { + $this->writerWorkbook->addXfWriter($style, false); + } + + // add fonts from rich text eleemnts + for ($i = 0; $i < $countSheets; ++$i) { + foreach ($this->writerWorksheets[$i]->phpSheet->getCoordinates() as $coordinate) { + $cell = $this->writerWorksheets[$i]->phpSheet->getCell($coordinate); + $cVal = $cell->getValue(); + if ($cVal instanceof RichText) { + $elements = $cVal->getRichTextElements(); + foreach ($elements as $element) { + if ($element instanceof Run) { + $font = $element->getFont(); + $this->writerWorksheets[$i]->fontHashIndex[$font->getHashCode()] = $this->writerWorkbook->addFont($font); + } + } + } + } + } + + // initialize OLE file + $workbookStreamName = 'Workbook'; + $OLE = new File(OLE::ascToUcs($workbookStreamName)); + + // Write the worksheet streams before the global workbook stream, + // because the byte sizes of these are needed in the global workbook stream + $worksheetSizes = []; + for ($i = 0; $i < $countSheets; ++$i) { + $this->writerWorksheets[$i]->close(); + $worksheetSizes[] = $this->writerWorksheets[$i]->_datasize; + } + + // add binary data for global workbook stream + $OLE->append($this->writerWorkbook->writeWorkbook($worksheetSizes)); + + // add binary data for sheet streams + for ($i = 0; $i < $countSheets; ++$i) { + $OLE->append($this->writerWorksheets[$i]->getData()); + } + + $this->documentSummaryInformation = $this->writeDocumentSummaryInformation(); + // initialize OLE Document Summary Information + if (isset($this->documentSummaryInformation) && !empty($this->documentSummaryInformation)) { + $OLE_DocumentSummaryInformation = new File(OLE::ascToUcs(chr(5) . 'DocumentSummaryInformation')); + $OLE_DocumentSummaryInformation->append($this->documentSummaryInformation); + } + + $this->summaryInformation = $this->writeSummaryInformation(); + // initialize OLE Summary Information + if (isset($this->summaryInformation) && !empty($this->summaryInformation)) { + $OLE_SummaryInformation = new File(OLE::ascToUcs(chr(5) . 'SummaryInformation')); + $OLE_SummaryInformation->append($this->summaryInformation); + } + + // define OLE Parts + $arrRootData = [$OLE]; + // initialize OLE Properties file + if (isset($OLE_SummaryInformation)) { + $arrRootData[] = $OLE_SummaryInformation; + } + // initialize OLE Extended Properties file + if (isset($OLE_DocumentSummaryInformation)) { + $arrRootData[] = $OLE_DocumentSummaryInformation; + } + + $time = $this->spreadsheet->getProperties()->getModified(); + $root = new Root($time, $time, $arrRootData); + // save the OLE file + $this->openFileHandle($filename); + $root->save($this->fileHandle); + $this->maybeCloseFileHandle(); + + Functions::setReturnDateType($saveDateReturnType); + Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog($saveDebugLog); + } + + /** + * Build the Worksheet Escher objects. + */ + private function buildWorksheetEschers(): void + { + // 1-based index to BstoreContainer + $blipIndex = 0; + $lastReducedSpId = 0; + $lastSpId = 0; + + foreach ($this->spreadsheet->getAllsheets() as $sheet) { + // sheet index + $sheetIndex = $sheet->getParent()->getIndex($sheet); + + // check if there are any shapes for this sheet + $filterRange = $sheet->getAutoFilter()->getRange(); + if (count($sheet->getDrawingCollection()) == 0 && empty($filterRange)) { + continue; + } + + // create intermediate Escher object + $escher = new Escher(); + + // dgContainer + $dgContainer = new DgContainer(); + + // set the drawing index (we use sheet index + 1) + $dgId = $sheet->getParent()->getIndex($sheet) + 1; + $dgContainer->setDgId($dgId); + $escher->setDgContainer($dgContainer); + + // spgrContainer + $spgrContainer = new SpgrContainer(); + $dgContainer->setSpgrContainer($spgrContainer); + + // add one shape which is the group shape + $spContainer = new SpContainer(); + $spContainer->setSpgr(true); + $spContainer->setSpType(0); + $spContainer->setSpId(($sheet->getParent()->getIndex($sheet) + 1) << 10); + $spgrContainer->addChild($spContainer); + + // add the shapes + + $countShapes[$sheetIndex] = 0; // count number of shapes (minus group shape), in sheet + + foreach ($sheet->getDrawingCollection() as $drawing) { + ++$blipIndex; + + ++$countShapes[$sheetIndex]; + + // add the shape + $spContainer = new SpContainer(); + + // set the shape type + $spContainer->setSpType(0x004B); + // set the shape flag + $spContainer->setSpFlag(0x02); + + // set the shape index (we combine 1-based sheet index and $countShapes to create unique shape index) + $reducedSpId = $countShapes[$sheetIndex]; + $spId = $reducedSpId | ($sheet->getParent()->getIndex($sheet) + 1) << 10; + $spContainer->setSpId($spId); + + // keep track of last reducedSpId + $lastReducedSpId = $reducedSpId; + + // keep track of last spId + $lastSpId = $spId; + + // set the BLIP index + $spContainer->setOPT(0x4104, $blipIndex); + + // set coordinates and offsets, client anchor + $coordinates = $drawing->getCoordinates(); + $offsetX = $drawing->getOffsetX(); + $offsetY = $drawing->getOffsetY(); + $width = $drawing->getWidth(); + $height = $drawing->getHeight(); + + $twoAnchor = \PhpOffice\PhpSpreadsheet\Shared\Xls::oneAnchor2twoAnchor($sheet, $coordinates, $offsetX, $offsetY, $width, $height); + + $spContainer->setStartCoordinates($twoAnchor['startCoordinates']); + $spContainer->setStartOffsetX($twoAnchor['startOffsetX']); + $spContainer->setStartOffsetY($twoAnchor['startOffsetY']); + $spContainer->setEndCoordinates($twoAnchor['endCoordinates']); + $spContainer->setEndOffsetX($twoAnchor['endOffsetX']); + $spContainer->setEndOffsetY($twoAnchor['endOffsetY']); + + $spgrContainer->addChild($spContainer); + } + + // AutoFilters + if (!empty($filterRange)) { + $rangeBounds = Coordinate::rangeBoundaries($filterRange); + $iNumColStart = $rangeBounds[0][0]; + $iNumColEnd = $rangeBounds[1][0]; + + $iInc = $iNumColStart; + while ($iInc <= $iNumColEnd) { + ++$countShapes[$sheetIndex]; + + // create an Drawing Object for the dropdown + $oDrawing = new BaseDrawing(); + // get the coordinates of drawing + $cDrawing = Coordinate::stringFromColumnIndex($iInc) . $rangeBounds[0][1]; + $oDrawing->setCoordinates($cDrawing); + $oDrawing->setWorksheet($sheet); + + // add the shape + $spContainer = new SpContainer(); + // set the shape type + $spContainer->setSpType(0x00C9); + // set the shape flag + $spContainer->setSpFlag(0x01); + + // set the shape index (we combine 1-based sheet index and $countShapes to create unique shape index) + $reducedSpId = $countShapes[$sheetIndex]; + $spId = $reducedSpId | ($sheet->getParent()->getIndex($sheet) + 1) << 10; + $spContainer->setSpId($spId); + + // keep track of last reducedSpId + $lastReducedSpId = $reducedSpId; + + // keep track of last spId + $lastSpId = $spId; + + $spContainer->setOPT(0x007F, 0x01040104); // Protection -> fLockAgainstGrouping + $spContainer->setOPT(0x00BF, 0x00080008); // Text -> fFitTextToShape + $spContainer->setOPT(0x01BF, 0x00010000); // Fill Style -> fNoFillHitTest + $spContainer->setOPT(0x01FF, 0x00080000); // Line Style -> fNoLineDrawDash + $spContainer->setOPT(0x03BF, 0x000A0000); // Group Shape -> fPrint + + // set coordinates and offsets, client anchor + $endCoordinates = Coordinate::stringFromColumnIndex($iInc); + $endCoordinates .= $rangeBounds[0][1] + 1; + + $spContainer->setStartCoordinates($cDrawing); + $spContainer->setStartOffsetX(0); + $spContainer->setStartOffsetY(0); + $spContainer->setEndCoordinates($endCoordinates); + $spContainer->setEndOffsetX(0); + $spContainer->setEndOffsetY(0); + + $spgrContainer->addChild($spContainer); + ++$iInc; + } + } + + // identifier clusters, used for workbook Escher object + $this->IDCLs[$dgId] = $lastReducedSpId; + + // set last shape index + $dgContainer->setLastSpId($lastSpId); + + // set the Escher object + $this->writerWorksheets[$sheetIndex]->setEscher($escher); + } + } + + private function processMemoryDrawing(BstoreContainer &$bstoreContainer, MemoryDrawing $drawing, string $renderingFunctionx): void + { + switch ($renderingFunctionx) { + case MemoryDrawing::RENDERING_JPEG: + $blipType = BSE::BLIPTYPE_JPEG; + $renderingFunction = 'imagejpeg'; + + break; + default: + $blipType = BSE::BLIPTYPE_PNG; + $renderingFunction = 'imagepng'; + + break; + } + + ob_start(); + call_user_func($renderingFunction, $drawing->getImageResource()); + $blipData = ob_get_contents(); + ob_end_clean(); + + $blip = new Blip(); + $blip->setData($blipData); + + $BSE = new BSE(); + $BSE->setBlipType($blipType); + $BSE->setBlip($blip); + + $bstoreContainer->addBSE($BSE); + } + + private function processDrawing(BstoreContainer &$bstoreContainer, Drawing $drawing): void + { + $blipType = null; + $blipData = ''; + $filename = $drawing->getPath(); + + [$imagesx, $imagesy, $imageFormat] = getimagesize($filename); + + switch ($imageFormat) { + case 1: // GIF, not supported by BIFF8, we convert to PNG + $blipType = BSE::BLIPTYPE_PNG; + ob_start(); + imagepng(imagecreatefromgif($filename)); + $blipData = ob_get_contents(); + ob_end_clean(); + + break; + case 2: // JPEG + $blipType = BSE::BLIPTYPE_JPEG; + $blipData = file_get_contents($filename); + + break; + case 3: // PNG + $blipType = BSE::BLIPTYPE_PNG; + $blipData = file_get_contents($filename); + + break; + case 6: // Windows DIB (BMP), we convert to PNG + $blipType = BSE::BLIPTYPE_PNG; + ob_start(); + imagepng(SharedDrawing::imagecreatefrombmp($filename)); + $blipData = ob_get_contents(); + ob_end_clean(); + + break; + } + if ($blipData) { + $blip = new Blip(); + $blip->setData($blipData); + + $BSE = new BSE(); + $BSE->setBlipType($blipType); + $BSE->setBlip($blip); + + $bstoreContainer->addBSE($BSE); + } + } + + private function processBaseDrawing(BstoreContainer &$bstoreContainer, BaseDrawing $drawing): void + { + if ($drawing instanceof Drawing) { + $this->processDrawing($bstoreContainer, $drawing); + } elseif ($drawing instanceof MemoryDrawing) { + $this->processMemoryDrawing($bstoreContainer, $drawing, $drawing->getRenderingFunction()); + } + } + + private function checkForDrawings(): bool + { + // any drawings in this workbook? + $found = false; + foreach ($this->spreadsheet->getAllSheets() as $sheet) { + if (count($sheet->getDrawingCollection()) > 0) { + $found = true; + + break; + } + } + + return $found; + } + + /** + * Build the Escher object corresponding to the MSODRAWINGGROUP record. + */ + private function buildWorkbookEscher(): void + { + // nothing to do if there are no drawings + if (!$this->checkForDrawings()) { + return; + } + + // if we reach here, then there are drawings in the workbook + $escher = new Escher(); + + // dggContainer + $dggContainer = new DggContainer(); + $escher->setDggContainer($dggContainer); + + // set IDCLs (identifier clusters) + $dggContainer->setIDCLs($this->IDCLs); + + // this loop is for determining maximum shape identifier of all drawing + $spIdMax = 0; + $totalCountShapes = 0; + $countDrawings = 0; + + foreach ($this->spreadsheet->getAllsheets() as $sheet) { + $sheetCountShapes = 0; // count number of shapes (minus group shape), in sheet + + $addCount = 0; + foreach ($sheet->getDrawingCollection() as $drawing) { + $addCount = 1; + ++$sheetCountShapes; + ++$totalCountShapes; + + $spId = $sheetCountShapes | ($this->spreadsheet->getIndex($sheet) + 1) << 10; + $spIdMax = max($spId, $spIdMax); + } + $countDrawings += $addCount; + } + + $dggContainer->setSpIdMax($spIdMax + 1); + $dggContainer->setCDgSaved($countDrawings); + $dggContainer->setCSpSaved($totalCountShapes + $countDrawings); // total number of shapes incl. one group shapes per drawing + + // bstoreContainer + $bstoreContainer = new BstoreContainer(); + $dggContainer->setBstoreContainer($bstoreContainer); + + // the BSE's (all the images) + foreach ($this->spreadsheet->getAllsheets() as $sheet) { + foreach ($sheet->getDrawingCollection() as $drawing) { + $this->processBaseDrawing($bstoreContainer, $drawing); + } + } + + // Set the Escher object + $this->writerWorkbook->setEscher($escher); + } + + /** + * Build the OLE Part for DocumentSummary Information. + * + * @return string + */ + private function writeDocumentSummaryInformation() + { + // offset: 0; size: 2; must be 0xFE 0xFF (UTF-16 LE byte order mark) + $data = pack('v', 0xFFFE); + // offset: 2; size: 2; + $data .= pack('v', 0x0000); + // offset: 4; size: 2; OS version + $data .= pack('v', 0x0106); + // offset: 6; size: 2; OS indicator + $data .= pack('v', 0x0002); + // offset: 8; size: 16 + $data .= pack('VVVV', 0x00, 0x00, 0x00, 0x00); + // offset: 24; size: 4; section count + $data .= pack('V', 0x0001); + + // offset: 28; size: 16; first section's class id: 02 d5 cd d5 9c 2e 1b 10 93 97 08 00 2b 2c f9 ae + $data .= pack('vvvvvvvv', 0xD502, 0xD5CD, 0x2E9C, 0x101B, 0x9793, 0x0008, 0x2C2B, 0xAEF9); + // offset: 44; size: 4; offset of the start + $data .= pack('V', 0x30); + + // SECTION + $dataSection = []; + $dataSection_NumProps = 0; + $dataSection_Summary = ''; + $dataSection_Content = ''; + + // GKPIDDSI_CODEPAGE: CodePage + $dataSection[] = [ + 'summary' => ['pack' => 'V', 'data' => 0x01], + 'offset' => ['pack' => 'V'], + 'type' => ['pack' => 'V', 'data' => 0x02], // 2 byte signed integer + 'data' => ['data' => 1252], + ]; + ++$dataSection_NumProps; + + // GKPIDDSI_CATEGORY : Category + $dataProp = $this->spreadsheet->getProperties()->getCategory(); + if ($dataProp) { + $dataSection[] = [ + 'summary' => ['pack' => 'V', 'data' => 0x02], + 'offset' => ['pack' => 'V'], + 'type' => ['pack' => 'V', 'data' => 0x1E], + 'data' => ['data' => $dataProp, 'length' => strlen($dataProp)], + ]; + ++$dataSection_NumProps; + } + // GKPIDDSI_VERSION :Version of the application that wrote the property storage + $dataSection[] = [ + 'summary' => ['pack' => 'V', 'data' => 0x17], + 'offset' => ['pack' => 'V'], + 'type' => ['pack' => 'V', 'data' => 0x03], + 'data' => ['pack' => 'V', 'data' => 0x000C0000], + ]; + ++$dataSection_NumProps; + // GKPIDDSI_SCALE : FALSE + $dataSection[] = [ + 'summary' => ['pack' => 'V', 'data' => 0x0B], + 'offset' => ['pack' => 'V'], + 'type' => ['pack' => 'V', 'data' => 0x0B], + 'data' => ['data' => false], + ]; + ++$dataSection_NumProps; + // GKPIDDSI_LINKSDIRTY : True if any of the values for the linked properties have changed outside of the application + $dataSection[] = [ + 'summary' => ['pack' => 'V', 'data' => 0x10], + 'offset' => ['pack' => 'V'], + 'type' => ['pack' => 'V', 'data' => 0x0B], + 'data' => ['data' => false], + ]; + ++$dataSection_NumProps; + // GKPIDDSI_SHAREDOC : FALSE + $dataSection[] = [ + 'summary' => ['pack' => 'V', 'data' => 0x13], + 'offset' => ['pack' => 'V'], + 'type' => ['pack' => 'V', 'data' => 0x0B], + 'data' => ['data' => false], + ]; + ++$dataSection_NumProps; + // GKPIDDSI_HYPERLINKSCHANGED : True if any of the values for the _PID_LINKS (hyperlink text) have changed outside of the application + $dataSection[] = [ + 'summary' => ['pack' => 'V', 'data' => 0x16], + 'offset' => ['pack' => 'V'], + 'type' => ['pack' => 'V', 'data' => 0x0B], + 'data' => ['data' => false], + ]; + ++$dataSection_NumProps; + + // GKPIDDSI_DOCSPARTS + // MS-OSHARED p75 (2.3.3.2.2.1) + // Structure is VtVecUnalignedLpstrValue (2.3.3.1.9) + // cElements + $dataProp = pack('v', 0x0001); + $dataProp .= pack('v', 0x0000); + // array of UnalignedLpstr + // cch + $dataProp .= pack('v', 0x000A); + $dataProp .= pack('v', 0x0000); + // value + $dataProp .= 'Worksheet' . chr(0); + + $dataSection[] = [ + 'summary' => ['pack' => 'V', 'data' => 0x0D], + 'offset' => ['pack' => 'V'], + 'type' => ['pack' => 'V', 'data' => 0x101E], + 'data' => ['data' => $dataProp, 'length' => strlen($dataProp)], + ]; + ++$dataSection_NumProps; + + // GKPIDDSI_HEADINGPAIR + // VtVecHeadingPairValue + // cElements + $dataProp = pack('v', 0x0002); + $dataProp .= pack('v', 0x0000); + // Array of vtHeadingPair + // vtUnalignedString - headingString + // stringType + $dataProp .= pack('v', 0x001E); + // padding + $dataProp .= pack('v', 0x0000); + // UnalignedLpstr + // cch + $dataProp .= pack('v', 0x0013); + $dataProp .= pack('v', 0x0000); + // value + $dataProp .= 'Feuilles de calcul'; + // vtUnalignedString - headingParts + // wType : 0x0003 = 32 bit signed integer + $dataProp .= pack('v', 0x0300); + // padding + $dataProp .= pack('v', 0x0000); + // value + $dataProp .= pack('v', 0x0100); + $dataProp .= pack('v', 0x0000); + $dataProp .= pack('v', 0x0000); + $dataProp .= pack('v', 0x0000); + + $dataSection[] = [ + 'summary' => ['pack' => 'V', 'data' => 0x0C], + 'offset' => ['pack' => 'V'], + 'type' => ['pack' => 'V', 'data' => 0x100C], + 'data' => ['data' => $dataProp, 'length' => strlen($dataProp)], + ]; + ++$dataSection_NumProps; + + // 4 Section Length + // 4 Property count + // 8 * $dataSection_NumProps (8 = ID (4) + OffSet(4)) + $dataSection_Content_Offset = 8 + $dataSection_NumProps * 8; + foreach ($dataSection as $dataProp) { + // Summary + $dataSection_Summary .= pack($dataProp['summary']['pack'], $dataProp['summary']['data']); + // Offset + $dataSection_Summary .= pack($dataProp['offset']['pack'], $dataSection_Content_Offset); + // DataType + $dataSection_Content .= pack($dataProp['type']['pack'], $dataProp['type']['data']); + // Data + if ($dataProp['type']['data'] == 0x02) { // 2 byte signed integer + $dataSection_Content .= pack('V', $dataProp['data']['data']); + + $dataSection_Content_Offset += 4 + 4; + } elseif ($dataProp['type']['data'] == 0x03) { // 4 byte signed integer + $dataSection_Content .= pack('V', $dataProp['data']['data']); + + $dataSection_Content_Offset += 4 + 4; + } elseif ($dataProp['type']['data'] == 0x0B) { // Boolean + $dataSection_Content .= pack('V', (int) $dataProp['data']['data']); + $dataSection_Content_Offset += 4 + 4; + } elseif ($dataProp['type']['data'] == 0x1E) { // null-terminated string prepended by dword string length + // Null-terminated string + $dataProp['data']['data'] .= chr(0); + // @phpstan-ignore-next-line + ++$dataProp['data']['length']; + // Complete the string with null string for being a %4 + $dataProp['data']['length'] = $dataProp['data']['length'] + ((4 - $dataProp['data']['length'] % 4) == 4 ? 0 : (4 - $dataProp['data']['length'] % 4)); + $dataProp['data']['data'] = str_pad($dataProp['data']['data'], $dataProp['data']['length'], chr(0), STR_PAD_RIGHT); + + $dataSection_Content .= pack('V', $dataProp['data']['length']); + $dataSection_Content .= $dataProp['data']['data']; + + $dataSection_Content_Offset += 4 + 4 + strlen($dataProp['data']['data']); + // Condition below can never be true + //} elseif ($dataProp['type']['data'] == 0x40) { // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601) + // $dataSection_Content .= $dataProp['data']['data']; + + // $dataSection_Content_Offset += 4 + 8; + } else { + $dataSection_Content .= $dataProp['data']['data']; + + // @phpstan-ignore-next-line + $dataSection_Content_Offset += 4 + $dataProp['data']['length']; + } + } + // Now $dataSection_Content_Offset contains the size of the content + + // section header + // offset: $secOffset; size: 4; section length + // + x Size of the content (summary + content) + $data .= pack('V', $dataSection_Content_Offset); + // offset: $secOffset+4; size: 4; property count + $data .= pack('V', $dataSection_NumProps); + // Section Summary + $data .= $dataSection_Summary; + // Section Content + $data .= $dataSection_Content; + + return $data; + } + + /** + * @param float|int $dataProp + */ + private function writeSummaryPropOle($dataProp, int &$dataSection_NumProps, array &$dataSection, int $sumdata, int $typdata): void + { + if ($dataProp) { + $dataSection[] = [ + 'summary' => ['pack' => 'V', 'data' => $sumdata], + 'offset' => ['pack' => 'V'], + 'type' => ['pack' => 'V', 'data' => $typdata], // null-terminated string prepended by dword string length + 'data' => ['data' => OLE::localDateToOLE($dataProp)], + ]; + ++$dataSection_NumProps; + } + } + + private function writeSummaryProp(string $dataProp, int &$dataSection_NumProps, array &$dataSection, int $sumdata, int $typdata): void + { + if ($dataProp) { + $dataSection[] = [ + 'summary' => ['pack' => 'V', 'data' => $sumdata], + 'offset' => ['pack' => 'V'], + 'type' => ['pack' => 'V', 'data' => $typdata], // null-terminated string prepended by dword string length + 'data' => ['data' => $dataProp, 'length' => strlen($dataProp)], + ]; + ++$dataSection_NumProps; + } + } + + /** + * Build the OLE Part for Summary Information. + * + * @return string + */ + private function writeSummaryInformation() + { + // offset: 0; size: 2; must be 0xFE 0xFF (UTF-16 LE byte order mark) + $data = pack('v', 0xFFFE); + // offset: 2; size: 2; + $data .= pack('v', 0x0000); + // offset: 4; size: 2; OS version + $data .= pack('v', 0x0106); + // offset: 6; size: 2; OS indicator + $data .= pack('v', 0x0002); + // offset: 8; size: 16 + $data .= pack('VVVV', 0x00, 0x00, 0x00, 0x00); + // offset: 24; size: 4; section count + $data .= pack('V', 0x0001); + + // offset: 28; size: 16; first section's class id: e0 85 9f f2 f9 4f 68 10 ab 91 08 00 2b 27 b3 d9 + $data .= pack('vvvvvvvv', 0x85E0, 0xF29F, 0x4FF9, 0x1068, 0x91AB, 0x0008, 0x272B, 0xD9B3); + // offset: 44; size: 4; offset of the start + $data .= pack('V', 0x30); + + // SECTION + $dataSection = []; + $dataSection_NumProps = 0; + $dataSection_Summary = ''; + $dataSection_Content = ''; + + // CodePage : CP-1252 + $dataSection[] = [ + 'summary' => ['pack' => 'V', 'data' => 0x01], + 'offset' => ['pack' => 'V'], + 'type' => ['pack' => 'V', 'data' => 0x02], // 2 byte signed integer + 'data' => ['data' => 1252], + ]; + ++$dataSection_NumProps; + + $props = $this->spreadsheet->getProperties(); + $this->writeSummaryProp($props->getTitle(), $dataSection_NumProps, $dataSection, 0x02, 0x1e); + $this->writeSummaryProp($props->getSubject(), $dataSection_NumProps, $dataSection, 0x03, 0x1e); + $this->writeSummaryProp($props->getCreator(), $dataSection_NumProps, $dataSection, 0x04, 0x1e); + $this->writeSummaryProp($props->getKeywords(), $dataSection_NumProps, $dataSection, 0x05, 0x1e); + $this->writeSummaryProp($props->getDescription(), $dataSection_NumProps, $dataSection, 0x06, 0x1e); + $this->writeSummaryProp($props->getLastModifiedBy(), $dataSection_NumProps, $dataSection, 0x08, 0x1e); + $this->writeSummaryPropOle($props->getCreated(), $dataSection_NumProps, $dataSection, 0x0c, 0x40); + $this->writeSummaryPropOle($props->getModified(), $dataSection_NumProps, $dataSection, 0x0d, 0x40); + + // Security + $dataSection[] = [ + 'summary' => ['pack' => 'V', 'data' => 0x13], + 'offset' => ['pack' => 'V'], + 'type' => ['pack' => 'V', 'data' => 0x03], // 4 byte signed integer + 'data' => ['data' => 0x00], + ]; + ++$dataSection_NumProps; + + // 4 Section Length + // 4 Property count + // 8 * $dataSection_NumProps (8 = ID (4) + OffSet(4)) + $dataSection_Content_Offset = 8 + $dataSection_NumProps * 8; + foreach ($dataSection as $dataProp) { + // Summary + $dataSection_Summary .= pack($dataProp['summary']['pack'], $dataProp['summary']['data']); + // Offset + $dataSection_Summary .= pack($dataProp['offset']['pack'], $dataSection_Content_Offset); + // DataType + $dataSection_Content .= pack($dataProp['type']['pack'], $dataProp['type']['data']); + // Data + if ($dataProp['type']['data'] == 0x02) { // 2 byte signed integer + $dataSection_Content .= pack('V', $dataProp['data']['data']); + + $dataSection_Content_Offset += 4 + 4; + } elseif ($dataProp['type']['data'] == 0x03) { // 4 byte signed integer + $dataSection_Content .= pack('V', $dataProp['data']['data']); + + $dataSection_Content_Offset += 4 + 4; + } elseif ($dataProp['type']['data'] == 0x1E) { // null-terminated string prepended by dword string length + // Null-terminated string + $dataProp['data']['data'] .= chr(0); + ++$dataProp['data']['length']; + // Complete the string with null string for being a %4 + $dataProp['data']['length'] = $dataProp['data']['length'] + ((4 - $dataProp['data']['length'] % 4) == 4 ? 0 : (4 - $dataProp['data']['length'] % 4)); + $dataProp['data']['data'] = str_pad($dataProp['data']['data'], $dataProp['data']['length'], chr(0), STR_PAD_RIGHT); + + $dataSection_Content .= pack('V', $dataProp['data']['length']); + $dataSection_Content .= $dataProp['data']['data']; + + $dataSection_Content_Offset += 4 + 4 + strlen($dataProp['data']['data']); + } elseif ($dataProp['type']['data'] == 0x40) { // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601) + $dataSection_Content .= $dataProp['data']['data']; + + $dataSection_Content_Offset += 4 + 8; + } + // Data Type Not Used at the moment + } + // Now $dataSection_Content_Offset contains the size of the content + + // section header + // offset: $secOffset; size: 4; section length + // + x Size of the content (summary + content) + $data .= pack('V', $dataSection_Content_Offset); + // offset: $secOffset+4; size: 4; property count + $data .= pack('V', $dataSection_NumProps); + // Section Summary + $data .= $dataSection_Summary; + // Section Content + $data .= $dataSection_Content; + + return $data; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/BIFFwriter.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/BIFFwriter.php new file mode 100644 index 0000000..4a7e065 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/BIFFwriter.php @@ -0,0 +1,224 @@ + +// * +// * The majority of this is _NOT_ my code. I simply ported it from the +// * PERL Spreadsheet::WriteExcel module. +// * +// * The author of the Spreadsheet::WriteExcel module is John McNamara +// * +// * +// * I _DO_ maintain this code, and John McNamara has nothing to do with the +// * porting of this code to PHP. Any questions directly related to this +// * class library should be directed to me. +// * +// * License Information: +// * +// * Spreadsheet_Excel_Writer: A library for generating Excel Spreadsheets +// * Copyright (c) 2002-2003 Xavier Noguer xnoguer@rezebra.com +// * +// * This library is free software; you can redistribute it and/or +// * modify it under the terms of the GNU Lesser General Public +// * License as published by the Free Software Foundation; either +// * version 2.1 of the License, or (at your option) any later version. +// * +// * This library is distributed in the hope that it will be useful, +// * but WITHOUT ANY WARRANTY; without even the implied warranty of +// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// * Lesser General Public License for more details. +// * +// * You should have received a copy of the GNU Lesser General Public +// * License along with this library; if not, write to the Free Software +// * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// */ +class BIFFwriter +{ + /** + * The byte order of this architecture. 0 => little endian, 1 => big endian. + * + * @var int + */ + private static $byteOrder; + + /** + * The string containing the data of the BIFF stream. + * + * @var null|string + */ + public $_data; + + /** + * The size of the data in bytes. Should be the same as strlen($this->_data). + * + * @var int + */ + public $_datasize; + + /** + * The maximum length for a BIFF record (excluding record header and length field). See addContinue(). + * + * @var int + * + * @see addContinue() + */ + private $limit = 8224; + + /** + * Constructor. + */ + public function __construct() + { + $this->_data = ''; + $this->_datasize = 0; + } + + /** + * Determine the byte order and store it as class data to avoid + * recalculating it for each call to new(). + * + * @return int + */ + public static function getByteOrder() + { + if (!isset(self::$byteOrder)) { + // Check if "pack" gives the required IEEE 64bit float + $teststr = pack('d', 1.2345); + $number = pack('C8', 0x8D, 0x97, 0x6E, 0x12, 0x83, 0xC0, 0xF3, 0x3F); + if ($number == $teststr) { + $byte_order = 0; // Little Endian + } elseif ($number == strrev($teststr)) { + $byte_order = 1; // Big Endian + } else { + // Give up. I'll fix this in a later version. + throw new WriterException('Required floating point format not supported on this platform.'); + } + self::$byteOrder = $byte_order; + } + + return self::$byteOrder; + } + + /** + * General storage function. + * + * @param string $data binary data to append + */ + protected function append($data): void + { + if (strlen($data) - 4 > $this->limit) { + $data = $this->addContinue($data); + } + $this->_data .= $data; + $this->_datasize += strlen($data); + } + + /** + * General storage function like append, but returns string instead of modifying $this->_data. + * + * @param string $data binary data to write + * + * @return string + */ + public function writeData($data) + { + if (strlen($data) - 4 > $this->limit) { + $data = $this->addContinue($data); + } + $this->_datasize += strlen($data); + + return $data; + } + + /** + * Writes Excel BOF record to indicate the beginning of a stream or + * sub-stream in the BIFF file. + * + * @param int $type type of BIFF file to write: 0x0005 Workbook, + * 0x0010 Worksheet + */ + protected function storeBof($type): void + { + $record = 0x0809; // Record identifier (BIFF5-BIFF8) + $length = 0x0010; + + // by inspection of real files, MS Office Excel 2007 writes the following + $unknown = pack('VV', 0x000100D1, 0x00000406); + + $build = 0x0DBB; // Excel 97 + $year = 0x07CC; // Excel 97 + + $version = 0x0600; // BIFF8 + + $header = pack('vv', $record, $length); + $data = pack('vvvv', $version, $type, $build, $year); + $this->append($header . $data . $unknown); + } + + /** + * Writes Excel EOF record to indicate the end of a BIFF stream. + */ + protected function storeEof(): void + { + $record = 0x000A; // Record identifier + $length = 0x0000; // Number of bytes to follow + + $header = pack('vv', $record, $length); + $this->append($header); + } + + /** + * Writes Excel EOF record to indicate the end of a BIFF stream. + */ + public function writeEof() + { + $record = 0x000A; // Record identifier + $length = 0x0000; // Number of bytes to follow + $header = pack('vv', $record, $length); + + return $this->writeData($header); + } + + /** + * Excel limits the size of BIFF records. In Excel 5 the limit is 2084 bytes. In + * Excel 97 the limit is 8228 bytes. Records that are longer than these limits + * must be split up into CONTINUE blocks. + * + * This function takes a long BIFF record and inserts CONTINUE records as + * necessary. + * + * @param string $data The original binary data to be written + * + * @return string A very convenient string of continue blocks + */ + private function addContinue($data) + { + $limit = $this->limit; + $record = 0x003C; // Record identifier + + // The first 2080/8224 bytes remain intact. However, we have to change + // the length field of the record. + $tmp = substr($data, 0, 2) . pack('v', $limit) . substr($data, 4, $limit); + + $header = pack('vv', $record, $limit); // Headers for continue records + + // Retrieve chunks of 2080/8224 bytes +4 for the header. + $data_length = strlen($data); + for ($i = $limit + 4; $i < ($data_length - $limit); $i += $limit) { + $tmp .= $header; + $tmp .= substr($data, $i, $limit); + } + + // Retrieve the last chunk of data + $header = pack('vv', $record, strlen($data) - $i); + $tmp .= $header; + $tmp .= substr($data, $i); + + return $tmp; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/CellDataValidation.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/CellDataValidation.php new file mode 100644 index 0000000..7e9b3cf --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/CellDataValidation.php @@ -0,0 +1,78 @@ + + */ + protected static $validationTypeMap = [ + DataValidation::TYPE_NONE => 0x00, + DataValidation::TYPE_WHOLE => 0x01, + DataValidation::TYPE_DECIMAL => 0x02, + DataValidation::TYPE_LIST => 0x03, + DataValidation::TYPE_DATE => 0x04, + DataValidation::TYPE_TIME => 0x05, + DataValidation::TYPE_TEXTLENGTH => 0x06, + DataValidation::TYPE_CUSTOM => 0x07, + ]; + + /** + * @var array + */ + protected static $errorStyleMap = [ + DataValidation::STYLE_STOP => 0x00, + DataValidation::STYLE_WARNING => 0x01, + DataValidation::STYLE_INFORMATION => 0x02, + ]; + + /** + * @var array + */ + protected static $operatorMap = [ + DataValidation::OPERATOR_BETWEEN => 0x00, + DataValidation::OPERATOR_NOTBETWEEN => 0x01, + DataValidation::OPERATOR_EQUAL => 0x02, + DataValidation::OPERATOR_NOTEQUAL => 0x03, + DataValidation::OPERATOR_GREATERTHAN => 0x04, + DataValidation::OPERATOR_LESSTHAN => 0x05, + DataValidation::OPERATOR_GREATERTHANOREQUAL => 0x06, + DataValidation::OPERATOR_LESSTHANOREQUAL => 0x07, + ]; + + public static function type(DataValidation $dataValidation): int + { + $validationType = $dataValidation->getType(); + + if (is_string($validationType) && array_key_exists($validationType, self::$validationTypeMap)) { + return self::$validationTypeMap[$validationType]; + } + + return self::$validationTypeMap[DataValidation::TYPE_NONE]; + } + + public static function errorStyle(DataValidation $dataValidation): int + { + $errorStyle = $dataValidation->getErrorStyle(); + + if (is_string($errorStyle) && array_key_exists($errorStyle, self::$errorStyleMap)) { + return self::$errorStyleMap[$errorStyle]; + } + + return self::$errorStyleMap[DataValidation::STYLE_STOP]; + } + + public static function operator(DataValidation $dataValidation): int + { + $operator = $dataValidation->getOperator(); + + if (is_string($operator) && array_key_exists($operator, self::$operatorMap)) { + return self::$operatorMap[$operator]; + } + + return self::$operatorMap[DataValidation::OPERATOR_BETWEEN]; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/ErrorCode.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/ErrorCode.php new file mode 100644 index 0000000..7a864f5 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/ErrorCode.php @@ -0,0 +1,28 @@ + + */ + protected static $errorCodeMap = [ + '#NULL!' => 0x00, + '#DIV/0!' => 0x07, + '#VALUE!' => 0x0F, + '#REF!' => 0x17, + '#NAME?' => 0x1D, + '#NUM!' => 0x24, + '#N/A' => 0x2A, + ]; + + public static function error(string $errorCode): int + { + if (array_key_exists($errorCode, self::$errorCodeMap)) { + return self::$errorCodeMap[$errorCode]; + } + + return 0; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Escher.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Escher.php new file mode 100644 index 0000000..e42139b --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Escher.php @@ -0,0 +1,510 @@ +object = $object; + } + + /** + * Process the object to be written. + * + * @return string + */ + public function close() + { + // initialize + $this->data = ''; + + switch (get_class($this->object)) { + case \PhpOffice\PhpSpreadsheet\Shared\Escher::class: + if ($dggContainer = $this->object->getDggContainer()) { + $writer = new self($dggContainer); + $this->data = $writer->close(); + } elseif ($dgContainer = $this->object->getDgContainer()) { + $writer = new self($dgContainer); + $this->data = $writer->close(); + $this->spOffsets = $writer->getSpOffsets(); + $this->spTypes = $writer->getSpTypes(); + } + + break; + case DggContainer::class: + // this is a container record + + // initialize + $innerData = ''; + + // write the dgg + $recVer = 0x0; + $recInstance = 0x0000; + $recType = 0xF006; + + $recVerInstance = $recVer; + $recVerInstance |= $recInstance << 4; + + // dgg data + $dggData = + pack( + 'VVVV', + $this->object->getSpIdMax(), // maximum shape identifier increased by one + $this->object->getCDgSaved() + 1, // number of file identifier clusters increased by one + $this->object->getCSpSaved(), + $this->object->getCDgSaved() // count total number of drawings saved + ); + + // add file identifier clusters (one per drawing) + $IDCLs = $this->object->getIDCLs(); + + foreach ($IDCLs as $dgId => $maxReducedSpId) { + $dggData .= pack('VV', $dgId, $maxReducedSpId + 1); + } + + $header = pack('vvV', $recVerInstance, $recType, strlen($dggData)); + $innerData .= $header . $dggData; + + // write the bstoreContainer + if ($bstoreContainer = $this->object->getBstoreContainer()) { + $writer = new self($bstoreContainer); + $innerData .= $writer->close(); + } + + // write the record + $recVer = 0xF; + $recInstance = 0x0000; + $recType = 0xF000; + $length = strlen($innerData); + + $recVerInstance = $recVer; + $recVerInstance |= $recInstance << 4; + + $header = pack('vvV', $recVerInstance, $recType, $length); + + $this->data = $header . $innerData; + + break; + case BstoreContainer::class: + // this is a container record + + // initialize + $innerData = ''; + + // treat the inner data + if ($BSECollection = $this->object->getBSECollection()) { + foreach ($BSECollection as $BSE) { + $writer = new self($BSE); + $innerData .= $writer->close(); + } + } + + // write the record + $recVer = 0xF; + $recInstance = count($this->object->getBSECollection()); + $recType = 0xF001; + $length = strlen($innerData); + + $recVerInstance = $recVer; + $recVerInstance |= $recInstance << 4; + + $header = pack('vvV', $recVerInstance, $recType, $length); + + $this->data = $header . $innerData; + + break; + case BSE::class: + // this is a semi-container record + + // initialize + $innerData = ''; + + // here we treat the inner data + if ($blip = $this->object->getBlip()) { + $writer = new self($blip); + $innerData .= $writer->close(); + } + + // initialize + $data = ''; + + $btWin32 = $this->object->getBlipType(); + $btMacOS = $this->object->getBlipType(); + $data .= pack('CC', $btWin32, $btMacOS); + + $rgbUid = pack('VVVV', 0, 0, 0, 0); // todo + $data .= $rgbUid; + + $tag = 0; + $size = strlen($innerData); + $cRef = 1; + $foDelay = 0; //todo + $unused1 = 0x0; + $cbName = 0x0; + $unused2 = 0x0; + $unused3 = 0x0; + $data .= pack('vVVVCCCC', $tag, $size, $cRef, $foDelay, $unused1, $cbName, $unused2, $unused3); + + $data .= $innerData; + + // write the record + $recVer = 0x2; + $recInstance = $this->object->getBlipType(); + $recType = 0xF007; + $length = strlen($data); + + $recVerInstance = $recVer; + $recVerInstance |= $recInstance << 4; + + $header = pack('vvV', $recVerInstance, $recType, $length); + + $this->data = $header; + + $this->data .= $data; + + break; + case Blip::class: + // this is an atom record + + // write the record + switch ($this->object->getParent()->getBlipType()) { + case BSE::BLIPTYPE_JPEG: + // initialize + $innerData = ''; + + $rgbUid1 = pack('VVVV', 0, 0, 0, 0); // todo + $innerData .= $rgbUid1; + + $tag = 0xFF; // todo + $innerData .= pack('C', $tag); + + $innerData .= $this->object->getData(); + + $recVer = 0x0; + $recInstance = 0x46A; + $recType = 0xF01D; + $length = strlen($innerData); + + $recVerInstance = $recVer; + $recVerInstance |= $recInstance << 4; + + $header = pack('vvV', $recVerInstance, $recType, $length); + + $this->data = $header; + + $this->data .= $innerData; + + break; + case BSE::BLIPTYPE_PNG: + // initialize + $innerData = ''; + + $rgbUid1 = pack('VVVV', 0, 0, 0, 0); // todo + $innerData .= $rgbUid1; + + $tag = 0xFF; // todo + $innerData .= pack('C', $tag); + + $innerData .= $this->object->getData(); + + $recVer = 0x0; + $recInstance = 0x6E0; + $recType = 0xF01E; + $length = strlen($innerData); + + $recVerInstance = $recVer; + $recVerInstance |= $recInstance << 4; + + $header = pack('vvV', $recVerInstance, $recType, $length); + + $this->data = $header; + + $this->data .= $innerData; + + break; + } + + break; + case DgContainer::class: + // this is a container record + + // initialize + $innerData = ''; + + // write the dg + $recVer = 0x0; + $recInstance = $this->object->getDgId(); + $recType = 0xF008; + $length = 8; + + $recVerInstance = $recVer; + $recVerInstance |= $recInstance << 4; + + $header = pack('vvV', $recVerInstance, $recType, $length); + + // number of shapes in this drawing (including group shape) + $countShapes = count($this->object->getSpgrContainer()->getChildren()); + $innerData .= $header . pack('VV', $countShapes, $this->object->getLastSpId()); + + // write the spgrContainer + if ($spgrContainer = $this->object->getSpgrContainer()) { + $writer = new self($spgrContainer); + $innerData .= $writer->close(); + + // get the shape offsets relative to the spgrContainer record + $spOffsets = $writer->getSpOffsets(); + $spTypes = $writer->getSpTypes(); + + // save the shape offsets relative to dgContainer + foreach ($spOffsets as &$spOffset) { + $spOffset += 24; // add length of dgContainer header data (8 bytes) plus dg data (16 bytes) + } + + $this->spOffsets = $spOffsets; + $this->spTypes = $spTypes; + } + + // write the record + $recVer = 0xF; + $recInstance = 0x0000; + $recType = 0xF002; + $length = strlen($innerData); + + $recVerInstance = $recVer; + $recVerInstance |= $recInstance << 4; + + $header = pack('vvV', $recVerInstance, $recType, $length); + + $this->data = $header . $innerData; + + break; + case SpgrContainer::class: + // this is a container record + + // initialize + $innerData = ''; + + // initialize spape offsets + $totalSize = 8; + $spOffsets = []; + $spTypes = []; + + // treat the inner data + foreach ($this->object->getChildren() as $spContainer) { + $writer = new self($spContainer); + $spData = $writer->close(); + $innerData .= $spData; + + // save the shape offsets (where new shape records begin) + $totalSize += strlen($spData); + $spOffsets[] = $totalSize; + + $spTypes = array_merge($spTypes, $writer->getSpTypes()); + } + + // write the record + $recVer = 0xF; + $recInstance = 0x0000; + $recType = 0xF003; + $length = strlen($innerData); + + $recVerInstance = $recVer; + $recVerInstance |= $recInstance << 4; + + $header = pack('vvV', $recVerInstance, $recType, $length); + + $this->data = $header . $innerData; + $this->spOffsets = $spOffsets; + $this->spTypes = $spTypes; + + break; + case SpContainer::class: + // initialize + $data = ''; + + // build the data + + // write group shape record, if necessary? + if ($this->object->getSpgr()) { + $recVer = 0x1; + $recInstance = 0x0000; + $recType = 0xF009; + $length = 0x00000010; + + $recVerInstance = $recVer; + $recVerInstance |= $recInstance << 4; + + $header = pack('vvV', $recVerInstance, $recType, $length); + + $data .= $header . pack('VVVV', 0, 0, 0, 0); + } + $this->spTypes[] = ($this->object->getSpType()); + + // write the shape record + $recVer = 0x2; + $recInstance = $this->object->getSpType(); // shape type + $recType = 0xF00A; + $length = 0x00000008; + + $recVerInstance = $recVer; + $recVerInstance |= $recInstance << 4; + + $header = pack('vvV', $recVerInstance, $recType, $length); + + $data .= $header . pack('VV', $this->object->getSpId(), $this->object->getSpgr() ? 0x0005 : 0x0A00); + + // the options + if ($this->object->getOPTCollection()) { + $optData = ''; + + $recVer = 0x3; + $recInstance = count($this->object->getOPTCollection()); + $recType = 0xF00B; + foreach ($this->object->getOPTCollection() as $property => $value) { + $optData .= pack('vV', $property, $value); + } + $length = strlen($optData); + + $recVerInstance = $recVer; + $recVerInstance |= $recInstance << 4; + + $header = pack('vvV', $recVerInstance, $recType, $length); + $data .= $header . $optData; + } + + // the client anchor + if ($this->object->getStartCoordinates()) { + $clientAnchorData = ''; + + $recVer = 0x0; + $recInstance = 0x0; + $recType = 0xF010; + + // start coordinates + [$column, $row] = Coordinate::indexesFromString($this->object->getStartCoordinates()); + $c1 = $column - 1; + $r1 = $row - 1; + + // start offsetX + $startOffsetX = $this->object->getStartOffsetX(); + + // start offsetY + $startOffsetY = $this->object->getStartOffsetY(); + + // end coordinates + [$column, $row] = Coordinate::indexesFromString($this->object->getEndCoordinates()); + $c2 = $column - 1; + $r2 = $row - 1; + + // end offsetX + $endOffsetX = $this->object->getEndOffsetX(); + + // end offsetY + $endOffsetY = $this->object->getEndOffsetY(); + + $clientAnchorData = pack('vvvvvvvvv', $this->object->getSpFlag(), $c1, $startOffsetX, $r1, $startOffsetY, $c2, $endOffsetX, $r2, $endOffsetY); + + $length = strlen($clientAnchorData); + + $recVerInstance = $recVer; + $recVerInstance |= $recInstance << 4; + + $header = pack('vvV', $recVerInstance, $recType, $length); + $data .= $header . $clientAnchorData; + } + + // the client data, just empty for now + if (!$this->object->getSpgr()) { + $clientDataData = ''; + + $recVer = 0x0; + $recInstance = 0x0; + $recType = 0xF011; + + $length = strlen($clientDataData); + + $recVerInstance = $recVer; + $recVerInstance |= $recInstance << 4; + + $header = pack('vvV', $recVerInstance, $recType, $length); + $data .= $header . $clientDataData; + } + + // write the record + $recVer = 0xF; + $recInstance = 0x0000; + $recType = 0xF004; + $length = strlen($data); + + $recVerInstance = $recVer; + $recVerInstance |= $recInstance << 4; + + $header = pack('vvV', $recVerInstance, $recType, $length); + + $this->data = $header . $data; + + break; + } + + return $this->data; + } + + /** + * Gets the shape offsets. + * + * @return array + */ + public function getSpOffsets() + { + return $this->spOffsets; + } + + /** + * Gets the shape types. + * + * @return array + */ + public function getSpTypes() + { + return $this->spTypes; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Font.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Font.php new file mode 100644 index 0000000..1266cf2 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Font.php @@ -0,0 +1,147 @@ +colorIndex = 0x7FFF; + $this->font = $font; + } + + /** + * Set the color index. + * + * @param int $colorIndex + */ + public function setColorIndex($colorIndex): void + { + $this->colorIndex = $colorIndex; + } + + /** + * Get font record data. + * + * @return string + */ + public function writeFont() + { + $font_outline = 0; + $font_shadow = 0; + + $icv = $this->colorIndex; // Index to color palette + if ($this->font->getSuperscript()) { + $sss = 1; + } elseif ($this->font->getSubscript()) { + $sss = 2; + } else { + $sss = 0; + } + $bFamily = 0; // Font family + $bCharSet = \PhpOffice\PhpSpreadsheet\Shared\Font::getCharsetFromFontName($this->font->getName()); // Character set + + $record = 0x31; // Record identifier + $reserved = 0x00; // Reserved + $grbit = 0x00; // Font attributes + if ($this->font->getItalic()) { + $grbit |= 0x02; + } + if ($this->font->getStrikethrough()) { + $grbit |= 0x08; + } + if ($font_outline) { + $grbit |= 0x10; + } + if ($font_shadow) { + $grbit |= 0x20; + } + + $data = pack( + 'vvvvvCCCC', + // Fontsize (in twips) + $this->font->getSize() * 20, + $grbit, + // Colour + $icv, + // Font weight + self::mapBold($this->font->getBold()), + // Superscript/Subscript + $sss, + self::mapUnderline($this->font->getUnderline()), + $bFamily, + $bCharSet, + $reserved + ); + $data .= StringHelper::UTF8toBIFF8UnicodeShort($this->font->getName()); + + $length = strlen($data); + $header = pack('vv', $record, $length); + + return $header . $data; + } + + /** + * Map to BIFF5-BIFF8 codes for bold. + * + * @param bool $bold + * + * @return int + */ + private static function mapBold($bold) + { + if ($bold) { + return 0x2BC; // 700 = Bold font weight + } + + return 0x190; // 400 = Normal font weight + } + + /** + * Map of BIFF2-BIFF8 codes for underline styles. + * + * @var int[] + */ + private static $mapUnderline = [ + \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_NONE => 0x00, + \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_SINGLE => 0x01, + \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_DOUBLE => 0x02, + \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_SINGLEACCOUNTING => 0x21, + \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_DOUBLEACCOUNTING => 0x22, + ]; + + /** + * Map underline. + * + * @param string $underline + * + * @return int + */ + private static function mapUnderline($underline) + { + if (isset(self::$mapUnderline[$underline])) { + return self::$mapUnderline[$underline]; + } + + return 0x00; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Parser.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Parser.php new file mode 100644 index 0000000..4033fd5 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Parser.php @@ -0,0 +1,1486 @@ +=,;#()"{} + const REGEX_SHEET_TITLE_UNQUOTED = '[^\*\:\/\\\\\?\[\]\+\-\% \\\'\^\&\<\>\=\,\;\#\(\)\"\{\}]+'; + + // Sheet title in quoted form (without surrounding quotes) + // Invalid sheet title characters cannot occur in the sheet title: + // *:/\?[] (usual invalid sheet title characters) + // Single quote is represented as a pair '' + const REGEX_SHEET_TITLE_QUOTED = '(([^\*\:\/\\\\\?\[\]\\\'])+|(\\\'\\\')+)+'; + + /** + * The index of the character we are currently looking at. + * + * @var int + */ + public $currentCharacter; + + /** + * The token we are working on. + * + * @var string + */ + public $currentToken; + + /** + * The formula to parse. + * + * @var string + */ + private $formula; + + /** + * The character ahead of the current char. + * + * @var string + */ + public $lookAhead; + + /** + * The parse tree to be generated. + * + * @var string + */ + public $parseTree; + + /** + * Array of external sheets. + * + * @var array + */ + private $externalSheets; + + /** + * Array of sheet references in the form of REF structures. + * + * @var array + */ + public $references; + + /** + * The Excel ptg indices. + * + * @var array + */ + private $ptg = [ + 'ptgExp' => 0x01, + 'ptgTbl' => 0x02, + 'ptgAdd' => 0x03, + 'ptgSub' => 0x04, + 'ptgMul' => 0x05, + 'ptgDiv' => 0x06, + 'ptgPower' => 0x07, + 'ptgConcat' => 0x08, + 'ptgLT' => 0x09, + 'ptgLE' => 0x0A, + 'ptgEQ' => 0x0B, + 'ptgGE' => 0x0C, + 'ptgGT' => 0x0D, + 'ptgNE' => 0x0E, + 'ptgIsect' => 0x0F, + 'ptgUnion' => 0x10, + 'ptgRange' => 0x11, + 'ptgUplus' => 0x12, + 'ptgUminus' => 0x13, + 'ptgPercent' => 0x14, + 'ptgParen' => 0x15, + 'ptgMissArg' => 0x16, + 'ptgStr' => 0x17, + 'ptgAttr' => 0x19, + 'ptgSheet' => 0x1A, + 'ptgEndSheet' => 0x1B, + 'ptgErr' => 0x1C, + 'ptgBool' => 0x1D, + 'ptgInt' => 0x1E, + 'ptgNum' => 0x1F, + 'ptgArray' => 0x20, + 'ptgFunc' => 0x21, + 'ptgFuncVar' => 0x22, + 'ptgName' => 0x23, + 'ptgRef' => 0x24, + 'ptgArea' => 0x25, + 'ptgMemArea' => 0x26, + 'ptgMemErr' => 0x27, + 'ptgMemNoMem' => 0x28, + 'ptgMemFunc' => 0x29, + 'ptgRefErr' => 0x2A, + 'ptgAreaErr' => 0x2B, + 'ptgRefN' => 0x2C, + 'ptgAreaN' => 0x2D, + 'ptgMemAreaN' => 0x2E, + 'ptgMemNoMemN' => 0x2F, + 'ptgNameX' => 0x39, + 'ptgRef3d' => 0x3A, + 'ptgArea3d' => 0x3B, + 'ptgRefErr3d' => 0x3C, + 'ptgAreaErr3d' => 0x3D, + 'ptgArrayV' => 0x40, + 'ptgFuncV' => 0x41, + 'ptgFuncVarV' => 0x42, + 'ptgNameV' => 0x43, + 'ptgRefV' => 0x44, + 'ptgAreaV' => 0x45, + 'ptgMemAreaV' => 0x46, + 'ptgMemErrV' => 0x47, + 'ptgMemNoMemV' => 0x48, + 'ptgMemFuncV' => 0x49, + 'ptgRefErrV' => 0x4A, + 'ptgAreaErrV' => 0x4B, + 'ptgRefNV' => 0x4C, + 'ptgAreaNV' => 0x4D, + 'ptgMemAreaNV' => 0x4E, + 'ptgMemNoMemNV' => 0x4F, + 'ptgFuncCEV' => 0x58, + 'ptgNameXV' => 0x59, + 'ptgRef3dV' => 0x5A, + 'ptgArea3dV' => 0x5B, + 'ptgRefErr3dV' => 0x5C, + 'ptgAreaErr3dV' => 0x5D, + 'ptgArrayA' => 0x60, + 'ptgFuncA' => 0x61, + 'ptgFuncVarA' => 0x62, + 'ptgNameA' => 0x63, + 'ptgRefA' => 0x64, + 'ptgAreaA' => 0x65, + 'ptgMemAreaA' => 0x66, + 'ptgMemErrA' => 0x67, + 'ptgMemNoMemA' => 0x68, + 'ptgMemFuncA' => 0x69, + 'ptgRefErrA' => 0x6A, + 'ptgAreaErrA' => 0x6B, + 'ptgRefNA' => 0x6C, + 'ptgAreaNA' => 0x6D, + 'ptgMemAreaNA' => 0x6E, + 'ptgMemNoMemNA' => 0x6F, + 'ptgFuncCEA' => 0x78, + 'ptgNameXA' => 0x79, + 'ptgRef3dA' => 0x7A, + 'ptgArea3dA' => 0x7B, + 'ptgRefErr3dA' => 0x7C, + 'ptgAreaErr3dA' => 0x7D, + ]; + + /** + * Thanks to Michael Meeks and Gnumeric for the initial arg values. + * + * The following hash was generated by "function_locale.pl" in the distro. + * Refer to function_locale.pl for non-English function names. + * + * The array elements are as follow: + * ptg: The Excel function ptg code. + * args: The number of arguments that the function takes: + * >=0 is a fixed number of arguments. + * -1 is a variable number of arguments. + * class: The reference, value or array class of the function args. + * vol: The function is volatile. + * + * @var array + */ + private $functions = [ + // function ptg args class vol + 'COUNT' => [0, -1, 0, 0], + 'IF' => [1, -1, 1, 0], + 'ISNA' => [2, 1, 1, 0], + 'ISERROR' => [3, 1, 1, 0], + 'SUM' => [4, -1, 0, 0], + 'AVERAGE' => [5, -1, 0, 0], + 'MIN' => [6, -1, 0, 0], + 'MAX' => [7, -1, 0, 0], + 'ROW' => [8, -1, 0, 0], + 'COLUMN' => [9, -1, 0, 0], + 'NA' => [10, 0, 0, 0], + 'NPV' => [11, -1, 1, 0], + 'STDEV' => [12, -1, 0, 0], + 'DOLLAR' => [13, -1, 1, 0], + 'FIXED' => [14, -1, 1, 0], + 'SIN' => [15, 1, 1, 0], + 'COS' => [16, 1, 1, 0], + 'TAN' => [17, 1, 1, 0], + 'ATAN' => [18, 1, 1, 0], + 'PI' => [19, 0, 1, 0], + 'SQRT' => [20, 1, 1, 0], + 'EXP' => [21, 1, 1, 0], + 'LN' => [22, 1, 1, 0], + 'LOG10' => [23, 1, 1, 0], + 'ABS' => [24, 1, 1, 0], + 'INT' => [25, 1, 1, 0], + 'SIGN' => [26, 1, 1, 0], + 'ROUND' => [27, 2, 1, 0], + 'LOOKUP' => [28, -1, 0, 0], + 'INDEX' => [29, -1, 0, 1], + 'REPT' => [30, 2, 1, 0], + 'MID' => [31, 3, 1, 0], + 'LEN' => [32, 1, 1, 0], + 'VALUE' => [33, 1, 1, 0], + 'TRUE' => [34, 0, 1, 0], + 'FALSE' => [35, 0, 1, 0], + 'AND' => [36, -1, 0, 0], + 'OR' => [37, -1, 0, 0], + 'NOT' => [38, 1, 1, 0], + 'MOD' => [39, 2, 1, 0], + 'DCOUNT' => [40, 3, 0, 0], + 'DSUM' => [41, 3, 0, 0], + 'DAVERAGE' => [42, 3, 0, 0], + 'DMIN' => [43, 3, 0, 0], + 'DMAX' => [44, 3, 0, 0], + 'DSTDEV' => [45, 3, 0, 0], + 'VAR' => [46, -1, 0, 0], + 'DVAR' => [47, 3, 0, 0], + 'TEXT' => [48, 2, 1, 0], + 'LINEST' => [49, -1, 0, 0], + 'TREND' => [50, -1, 0, 0], + 'LOGEST' => [51, -1, 0, 0], + 'GROWTH' => [52, -1, 0, 0], + 'PV' => [56, -1, 1, 0], + 'FV' => [57, -1, 1, 0], + 'NPER' => [58, -1, 1, 0], + 'PMT' => [59, -1, 1, 0], + 'RATE' => [60, -1, 1, 0], + 'MIRR' => [61, 3, 0, 0], + 'IRR' => [62, -1, 0, 0], + 'RAND' => [63, 0, 1, 1], + 'MATCH' => [64, -1, 0, 0], + 'DATE' => [65, 3, 1, 0], + 'TIME' => [66, 3, 1, 0], + 'DAY' => [67, 1, 1, 0], + 'MONTH' => [68, 1, 1, 0], + 'YEAR' => [69, 1, 1, 0], + 'WEEKDAY' => [70, -1, 1, 0], + 'HOUR' => [71, 1, 1, 0], + 'MINUTE' => [72, 1, 1, 0], + 'SECOND' => [73, 1, 1, 0], + 'NOW' => [74, 0, 1, 1], + 'AREAS' => [75, 1, 0, 1], + 'ROWS' => [76, 1, 0, 1], + 'COLUMNS' => [77, 1, 0, 1], + 'OFFSET' => [78, -1, 0, 1], + 'SEARCH' => [82, -1, 1, 0], + 'TRANSPOSE' => [83, 1, 1, 0], + 'TYPE' => [86, 1, 1, 0], + 'ATAN2' => [97, 2, 1, 0], + 'ASIN' => [98, 1, 1, 0], + 'ACOS' => [99, 1, 1, 0], + 'CHOOSE' => [100, -1, 1, 0], + 'HLOOKUP' => [101, -1, 0, 0], + 'VLOOKUP' => [102, -1, 0, 0], + 'ISREF' => [105, 1, 0, 0], + 'LOG' => [109, -1, 1, 0], + 'CHAR' => [111, 1, 1, 0], + 'LOWER' => [112, 1, 1, 0], + 'UPPER' => [113, 1, 1, 0], + 'PROPER' => [114, 1, 1, 0], + 'LEFT' => [115, -1, 1, 0], + 'RIGHT' => [116, -1, 1, 0], + 'EXACT' => [117, 2, 1, 0], + 'TRIM' => [118, 1, 1, 0], + 'REPLACE' => [119, 4, 1, 0], + 'SUBSTITUTE' => [120, -1, 1, 0], + 'CODE' => [121, 1, 1, 0], + 'FIND' => [124, -1, 1, 0], + 'CELL' => [125, -1, 0, 1], + 'ISERR' => [126, 1, 1, 0], + 'ISTEXT' => [127, 1, 1, 0], + 'ISNUMBER' => [128, 1, 1, 0], + 'ISBLANK' => [129, 1, 1, 0], + 'T' => [130, 1, 0, 0], + 'N' => [131, 1, 0, 0], + 'DATEVALUE' => [140, 1, 1, 0], + 'TIMEVALUE' => [141, 1, 1, 0], + 'SLN' => [142, 3, 1, 0], + 'SYD' => [143, 4, 1, 0], + 'DDB' => [144, -1, 1, 0], + 'INDIRECT' => [148, -1, 1, 1], + 'CALL' => [150, -1, 1, 0], + 'CLEAN' => [162, 1, 1, 0], + 'MDETERM' => [163, 1, 2, 0], + 'MINVERSE' => [164, 1, 2, 0], + 'MMULT' => [165, 2, 2, 0], + 'IPMT' => [167, -1, 1, 0], + 'PPMT' => [168, -1, 1, 0], + 'COUNTA' => [169, -1, 0, 0], + 'PRODUCT' => [183, -1, 0, 0], + 'FACT' => [184, 1, 1, 0], + 'DPRODUCT' => [189, 3, 0, 0], + 'ISNONTEXT' => [190, 1, 1, 0], + 'STDEVP' => [193, -1, 0, 0], + 'VARP' => [194, -1, 0, 0], + 'DSTDEVP' => [195, 3, 0, 0], + 'DVARP' => [196, 3, 0, 0], + 'TRUNC' => [197, -1, 1, 0], + 'ISLOGICAL' => [198, 1, 1, 0], + 'DCOUNTA' => [199, 3, 0, 0], + 'USDOLLAR' => [204, -1, 1, 0], + 'FINDB' => [205, -1, 1, 0], + 'SEARCHB' => [206, -1, 1, 0], + 'REPLACEB' => [207, 4, 1, 0], + 'LEFTB' => [208, -1, 1, 0], + 'RIGHTB' => [209, -1, 1, 0], + 'MIDB' => [210, 3, 1, 0], + 'LENB' => [211, 1, 1, 0], + 'ROUNDUP' => [212, 2, 1, 0], + 'ROUNDDOWN' => [213, 2, 1, 0], + 'ASC' => [214, 1, 1, 0], + 'DBCS' => [215, 1, 1, 0], + 'RANK' => [216, -1, 0, 0], + 'ADDRESS' => [219, -1, 1, 0], + 'DAYS360' => [220, -1, 1, 0], + 'TODAY' => [221, 0, 1, 1], + 'VDB' => [222, -1, 1, 0], + 'MEDIAN' => [227, -1, 0, 0], + 'SUMPRODUCT' => [228, -1, 2, 0], + 'SINH' => [229, 1, 1, 0], + 'COSH' => [230, 1, 1, 0], + 'TANH' => [231, 1, 1, 0], + 'ASINH' => [232, 1, 1, 0], + 'ACOSH' => [233, 1, 1, 0], + 'ATANH' => [234, 1, 1, 0], + 'DGET' => [235, 3, 0, 0], + 'INFO' => [244, 1, 1, 1], + 'DB' => [247, -1, 1, 0], + 'FREQUENCY' => [252, 2, 0, 0], + 'ERROR.TYPE' => [261, 1, 1, 0], + 'REGISTER.ID' => [267, -1, 1, 0], + 'AVEDEV' => [269, -1, 0, 0], + 'BETADIST' => [270, -1, 1, 0], + 'GAMMALN' => [271, 1, 1, 0], + 'BETAINV' => [272, -1, 1, 0], + 'BINOMDIST' => [273, 4, 1, 0], + 'CHIDIST' => [274, 2, 1, 0], + 'CHIINV' => [275, 2, 1, 0], + 'COMBIN' => [276, 2, 1, 0], + 'CONFIDENCE' => [277, 3, 1, 0], + 'CRITBINOM' => [278, 3, 1, 0], + 'EVEN' => [279, 1, 1, 0], + 'EXPONDIST' => [280, 3, 1, 0], + 'FDIST' => [281, 3, 1, 0], + 'FINV' => [282, 3, 1, 0], + 'FISHER' => [283, 1, 1, 0], + 'FISHERINV' => [284, 1, 1, 0], + 'FLOOR' => [285, 2, 1, 0], + 'GAMMADIST' => [286, 4, 1, 0], + 'GAMMAINV' => [287, 3, 1, 0], + 'CEILING' => [288, 2, 1, 0], + 'HYPGEOMDIST' => [289, 4, 1, 0], + 'LOGNORMDIST' => [290, 3, 1, 0], + 'LOGINV' => [291, 3, 1, 0], + 'NEGBINOMDIST' => [292, 3, 1, 0], + 'NORMDIST' => [293, 4, 1, 0], + 'NORMSDIST' => [294, 1, 1, 0], + 'NORMINV' => [295, 3, 1, 0], + 'NORMSINV' => [296, 1, 1, 0], + 'STANDARDIZE' => [297, 3, 1, 0], + 'ODD' => [298, 1, 1, 0], + 'PERMUT' => [299, 2, 1, 0], + 'POISSON' => [300, 3, 1, 0], + 'TDIST' => [301, 3, 1, 0], + 'WEIBULL' => [302, 4, 1, 0], + 'SUMXMY2' => [303, 2, 2, 0], + 'SUMX2MY2' => [304, 2, 2, 0], + 'SUMX2PY2' => [305, 2, 2, 0], + 'CHITEST' => [306, 2, 2, 0], + 'CORREL' => [307, 2, 2, 0], + 'COVAR' => [308, 2, 2, 0], + 'FORECAST' => [309, 3, 2, 0], + 'FTEST' => [310, 2, 2, 0], + 'INTERCEPT' => [311, 2, 2, 0], + 'PEARSON' => [312, 2, 2, 0], + 'RSQ' => [313, 2, 2, 0], + 'STEYX' => [314, 2, 2, 0], + 'SLOPE' => [315, 2, 2, 0], + 'TTEST' => [316, 4, 2, 0], + 'PROB' => [317, -1, 2, 0], + 'DEVSQ' => [318, -1, 0, 0], + 'GEOMEAN' => [319, -1, 0, 0], + 'HARMEAN' => [320, -1, 0, 0], + 'SUMSQ' => [321, -1, 0, 0], + 'KURT' => [322, -1, 0, 0], + 'SKEW' => [323, -1, 0, 0], + 'ZTEST' => [324, -1, 0, 0], + 'LARGE' => [325, 2, 0, 0], + 'SMALL' => [326, 2, 0, 0], + 'QUARTILE' => [327, 2, 0, 0], + 'PERCENTILE' => [328, 2, 0, 0], + 'PERCENTRANK' => [329, -1, 0, 0], + 'MODE' => [330, -1, 2, 0], + 'TRIMMEAN' => [331, 2, 0, 0], + 'TINV' => [332, 2, 1, 0], + 'CONCATENATE' => [336, -1, 1, 0], + 'POWER' => [337, 2, 1, 0], + 'RADIANS' => [342, 1, 1, 0], + 'DEGREES' => [343, 1, 1, 0], + 'SUBTOTAL' => [344, -1, 0, 0], + 'SUMIF' => [345, -1, 0, 0], + 'COUNTIF' => [346, 2, 0, 0], + 'COUNTBLANK' => [347, 1, 0, 0], + 'ISPMT' => [350, 4, 1, 0], + 'DATEDIF' => [351, 3, 1, 0], + 'DATESTRING' => [352, 1, 1, 0], + 'NUMBERSTRING' => [353, 2, 1, 0], + 'ROMAN' => [354, -1, 1, 0], + 'GETPIVOTDATA' => [358, -1, 0, 0], + 'HYPERLINK' => [359, -1, 1, 0], + 'PHONETIC' => [360, 1, 0, 0], + 'AVERAGEA' => [361, -1, 0, 0], + 'MAXA' => [362, -1, 0, 0], + 'MINA' => [363, -1, 0, 0], + 'STDEVPA' => [364, -1, 0, 0], + 'VARPA' => [365, -1, 0, 0], + 'STDEVA' => [366, -1, 0, 0], + 'VARA' => [367, -1, 0, 0], + 'BAHTTEXT' => [368, 1, 0, 0], + ]; + + private $spreadsheet; + + /** + * The class constructor. + */ + public function __construct(Spreadsheet $spreadsheet) + { + $this->spreadsheet = $spreadsheet; + + $this->currentCharacter = 0; + $this->currentToken = ''; // The token we are working on. + $this->formula = ''; // The formula to parse. + $this->lookAhead = ''; // The character ahead of the current char. + $this->parseTree = ''; // The parse tree to be generated. + $this->externalSheets = []; + $this->references = []; + } + + /** + * Convert a token to the proper ptg value. + * + * @param mixed $token the token to convert + * + * @return mixed the converted token on success + */ + private function convert($token) + { + if (preg_match('/"([^"]|""){0,255}"/', $token)) { + return $this->convertString($token); + } elseif (is_numeric($token)) { + return $this->convertNumber($token); + // match references like A1 or $A$1 + } elseif (preg_match('/^\$?([A-Ia-i]?[A-Za-z])\$?(\d+)$/', $token)) { + return $this->convertRef2d($token); + // match external references like Sheet1!A1 or Sheet1:Sheet2!A1 or Sheet1!$A$1 or Sheet1:Sheet2!$A$1 + } elseif (preg_match('/^' . self::REGEX_SHEET_TITLE_UNQUOTED . '(\\:' . self::REGEX_SHEET_TITLE_UNQUOTED . ')?\\!\$?[A-Ia-i]?[A-Za-z]\$?(\\d+)$/u', $token)) { + return $this->convertRef3d($token); + // match external references like 'Sheet1'!A1 or 'Sheet1:Sheet2'!A1 or 'Sheet1'!$A$1 or 'Sheet1:Sheet2'!$A$1 + } elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . '(\\:' . self::REGEX_SHEET_TITLE_QUOTED . ")?'\\!\\$?[A-Ia-i]?[A-Za-z]\\$?(\\d+)$/u", $token)) { + return $this->convertRef3d($token); + // match ranges like A1:B2 or $A$1:$B$2 + } elseif (preg_match('/^(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)\:(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)$/', $token)) { + return $this->convertRange2d($token); + // match external ranges like Sheet1!A1:B2 or Sheet1:Sheet2!A1:B2 or Sheet1!$A$1:$B$2 or Sheet1:Sheet2!$A$1:$B$2 + } elseif (preg_match('/^' . self::REGEX_SHEET_TITLE_UNQUOTED . '(\\:' . self::REGEX_SHEET_TITLE_UNQUOTED . ')?\\!\$?([A-Ia-i]?[A-Za-z])?\$?(\\d+)\\:\$?([A-Ia-i]?[A-Za-z])?\$?(\\d+)$/u', $token)) { + return $this->convertRange3d($token); + // match external ranges like 'Sheet1'!A1:B2 or 'Sheet1:Sheet2'!A1:B2 or 'Sheet1'!$A$1:$B$2 or 'Sheet1:Sheet2'!$A$1:$B$2 + } elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . '(\\:' . self::REGEX_SHEET_TITLE_QUOTED . ")?'\\!\\$?([A-Ia-i]?[A-Za-z])?\\$?(\\d+)\\:\\$?([A-Ia-i]?[A-Za-z])?\\$?(\\d+)$/u", $token)) { + return $this->convertRange3d($token); + // operators (including parentheses) + } elseif (isset($this->ptg[$token])) { + return pack('C', $this->ptg[$token]); + // match error codes + } elseif (preg_match('/^#[A-Z0\\/]{3,5}[!?]{1}$/', $token) || $token == '#N/A') { + return $this->convertError($token); + } elseif (preg_match('/^' . Calculation::CALCULATION_REGEXP_DEFINEDNAME . '$/mui', $token) && $this->spreadsheet->getDefinedName($token) !== null) { + return $this->convertDefinedName($token); + // commented so argument number can be processed correctly. See toReversePolish(). + /*elseif (preg_match("/[A-Z0-9\xc0-\xdc\.]+/", $token)) + { + return($this->convertFunction($token, $this->_func_args)); + }*/ + // if it's an argument, ignore the token (the argument remains) + } elseif ($token == 'arg') { + return ''; + } + + // TODO: use real error codes + throw new WriterException("Unknown token $token"); + } + + /** + * Convert a number token to ptgInt or ptgNum. + * + * @param mixed $num an integer or double for conversion to its ptg value + * + * @return string + */ + private function convertNumber($num) + { + // Integer in the range 0..2**16-1 + if ((preg_match('/^\\d+$/', $num)) && ($num <= 65535)) { + return pack('Cv', $this->ptg['ptgInt'], $num); + } + + // A float + if (BIFFwriter::getByteOrder()) { // if it's Big Endian + $num = strrev($num); + } + + return pack('Cd', $this->ptg['ptgNum'], $num); + } + + /** + * Convert a string token to ptgStr. + * + * @param string $string a string for conversion to its ptg value + * + * @return mixed the converted token on success + */ + private function convertString($string) + { + // chop away beggining and ending quotes + $string = substr($string, 1, -1); + if (strlen($string) > 255) { + throw new WriterException('String is too long'); + } + + return pack('C', $this->ptg['ptgStr']) . StringHelper::UTF8toBIFF8UnicodeShort($string); + } + + /** + * Convert a function to a ptgFunc or ptgFuncVarV depending on the number of + * args that it takes. + * + * @param string $token the name of the function for convertion to ptg value + * @param int $num_args the number of arguments the function receives + * + * @return string The packed ptg for the function + */ + private function convertFunction($token, $num_args) + { + $args = $this->functions[$token][1]; + + // Fixed number of args eg. TIME($i, $j, $k). + if ($args >= 0) { + return pack('Cv', $this->ptg['ptgFuncV'], $this->functions[$token][0]); + } + + // Variable number of args eg. SUM($i, $j, $k, ..). + return pack('CCv', $this->ptg['ptgFuncVarV'], $num_args, $this->functions[$token][0]); + } + + /** + * Convert an Excel range such as A1:D4 to a ptgRefV. + * + * @param string $range An Excel range in the A1:A2 + * @param int $class + * + * @return string + */ + private function convertRange2d($range, $class = 0) + { + // TODO: possible class value 0,1,2 check Formula.pm + // Split the range into 2 cell refs + if (preg_match('/^(\$)?([A-Ia-i]?[A-Za-z])(\$)?(\d+)\:(\$)?([A-Ia-i]?[A-Za-z])(\$)?(\d+)$/', $range)) { + [$cell1, $cell2] = explode(':', $range); + } else { + // TODO: use real error codes + throw new WriterException('Unknown range separator'); + } + + // Convert the cell references + [$row1, $col1] = $this->cellToPackedRowcol($cell1); + [$row2, $col2] = $this->cellToPackedRowcol($cell2); + + // The ptg value depends on the class of the ptg. + if ($class == 0) { + $ptgArea = pack('C', $this->ptg['ptgArea']); + } elseif ($class == 1) { + $ptgArea = pack('C', $this->ptg['ptgAreaV']); + } elseif ($class == 2) { + $ptgArea = pack('C', $this->ptg['ptgAreaA']); + } else { + // TODO: use real error codes + throw new WriterException("Unknown class $class"); + } + + return $ptgArea . $row1 . $row2 . $col1 . $col2; + } + + /** + * Convert an Excel 3d range such as "Sheet1!A1:D4" or "Sheet1:Sheet2!A1:D4" to + * a ptgArea3d. + * + * @param string $token an Excel range in the Sheet1!A1:A2 format + * + * @return mixed the packed ptgArea3d token on success + */ + private function convertRange3d($token) + { + // Split the ref at the ! symbol + [$ext_ref, $range] = PhpspreadsheetWorksheet::extractSheetTitle($token, true); + + // Convert the external reference part (different for BIFF8) + $ext_ref = $this->getRefIndex($ext_ref); + + // Split the range into 2 cell refs + [$cell1, $cell2] = explode(':', $range); + + // Convert the cell references + if (preg_match('/^(\$)?[A-Ia-i]?[A-Za-z](\$)?(\\d+)$/', $cell1)) { + [$row1, $col1] = $this->cellToPackedRowcol($cell1); + [$row2, $col2] = $this->cellToPackedRowcol($cell2); + } else { // It's a rows range (like 26:27) + [$row1, $col1, $row2, $col2] = $this->rangeToPackedRange($cell1 . ':' . $cell2); + } + + // The ptg value depends on the class of the ptg. + $ptgArea = pack('C', $this->ptg['ptgArea3d']); + + return $ptgArea . $ext_ref . $row1 . $row2 . $col1 . $col2; + } + + /** + * Convert an Excel reference such as A1, $B2, C$3 or $D$4 to a ptgRefV. + * + * @param string $cell An Excel cell reference + * + * @return string The cell in packed() format with the corresponding ptg + */ + private function convertRef2d($cell) + { + // Convert the cell reference + $cell_array = $this->cellToPackedRowcol($cell); + [$row, $col] = $cell_array; + + // The ptg value depends on the class of the ptg. + $ptgRef = pack('C', $this->ptg['ptgRefA']); + + return $ptgRef . $row . $col; + } + + /** + * Convert an Excel 3d reference such as "Sheet1!A1" or "Sheet1:Sheet2!A1" to a + * ptgRef3d. + * + * @param string $cell An Excel cell reference + * + * @return mixed the packed ptgRef3d token on success + */ + private function convertRef3d($cell) + { + // Split the ref at the ! symbol + [$ext_ref, $cell] = PhpspreadsheetWorksheet::extractSheetTitle($cell, true); + + // Convert the external reference part (different for BIFF8) + $ext_ref = $this->getRefIndex($ext_ref); + + // Convert the cell reference part + [$row, $col] = $this->cellToPackedRowcol($cell); + + // The ptg value depends on the class of the ptg. + $ptgRef = pack('C', $this->ptg['ptgRef3dA']); + + return $ptgRef . $ext_ref . $row . $col; + } + + /** + * Convert an error code to a ptgErr. + * + * @param string $errorCode The error code for conversion to its ptg value + * + * @return string The error code ptgErr + */ + private function convertError($errorCode) + { + switch ($errorCode) { + case '#NULL!': + return pack('C', 0x00); + case '#DIV/0!': + return pack('C', 0x07); + case '#VALUE!': + return pack('C', 0x0F); + case '#REF!': + return pack('C', 0x17); + case '#NAME?': + return pack('C', 0x1D); + case '#NUM!': + return pack('C', 0x24); + case '#N/A': + return pack('C', 0x2A); + } + + return pack('C', 0xFF); + } + + private function convertDefinedName(string $name): string + { + if (strlen($name) > 255) { + throw new WriterException('Defined Name is too long'); + } + + $nameReference = 1; + foreach ($this->spreadsheet->getDefinedNames() as $definedName) { + if ($name === $definedName->getName()) { + break; + } + ++$nameReference; + } + + $ptgRef = pack('Cvxx', $this->ptg['ptgName'], $nameReference); + + throw new WriterException('Cannot yet write formulae with defined names to Xls'); + + return $ptgRef; + } + + /** + * Look up the REF index that corresponds to an external sheet name + * (or range). If it doesn't exist yet add it to the workbook's references + * array. It assumes all sheet names given must exist. + * + * @param string $ext_ref The name of the external reference + * + * @return mixed The reference index in packed() format on success + */ + private function getRefIndex($ext_ref) + { + $ext_ref = preg_replace("/^'/", '', $ext_ref); // Remove leading ' if any. + $ext_ref = preg_replace("/'$/", '', $ext_ref); // Remove trailing ' if any. + $ext_ref = str_replace('\'\'', '\'', $ext_ref); // Replace escaped '' with ' + + // Check if there is a sheet range eg., Sheet1:Sheet2. + if (preg_match('/:/', $ext_ref)) { + [$sheet_name1, $sheet_name2] = explode(':', $ext_ref); + + $sheet1 = $this->getSheetIndex($sheet_name1); + if ($sheet1 == -1) { + throw new WriterException("Unknown sheet name $sheet_name1 in formula"); + } + $sheet2 = $this->getSheetIndex($sheet_name2); + if ($sheet2 == -1) { + throw new WriterException("Unknown sheet name $sheet_name2 in formula"); + } + + // Reverse max and min sheet numbers if necessary + if ($sheet1 > $sheet2) { + [$sheet1, $sheet2] = [$sheet2, $sheet1]; + } + } else { // Single sheet name only. + $sheet1 = $this->getSheetIndex($ext_ref); + if ($sheet1 == -1) { + throw new WriterException("Unknown sheet name $ext_ref in formula"); + } + $sheet2 = $sheet1; + } + + // assume all references belong to this document + $supbook_index = 0x00; + $ref = pack('vvv', $supbook_index, $sheet1, $sheet2); + $totalreferences = count($this->references); + $index = -1; + for ($i = 0; $i < $totalreferences; ++$i) { + if ($ref == $this->references[$i]) { + $index = $i; + + break; + } + } + // if REF was not found add it to references array + if ($index == -1) { + $this->references[$totalreferences] = $ref; + $index = $totalreferences; + } + + return pack('v', $index); + } + + /** + * Look up the index that corresponds to an external sheet name. The hash of + * sheet names is updated by the addworksheet() method of the + * \PhpOffice\PhpSpreadsheet\Writer\Xls\Workbook class. + * + * @param string $sheet_name Sheet name + * + * @return int The sheet index, -1 if the sheet was not found + */ + private function getSheetIndex($sheet_name) + { + if (!isset($this->externalSheets[$sheet_name])) { + return -1; + } + + return $this->externalSheets[$sheet_name]; + } + + /** + * This method is used to update the array of sheet names. It is + * called by the addWorksheet() method of the + * \PhpOffice\PhpSpreadsheet\Writer\Xls\Workbook class. + * + * @param string $name The name of the worksheet being added + * @param int $index The index of the worksheet being added + * + * @see \PhpOffice\PhpSpreadsheet\Writer\Xls\Workbook::addWorksheet() + */ + public function setExtSheet($name, $index): void + { + $this->externalSheets[$name] = $index; + } + + /** + * pack() row and column into the required 3 or 4 byte format. + * + * @param string $cell The Excel cell reference to be packed + * + * @return array Array containing the row and column in packed() format + */ + private function cellToPackedRowcol($cell) + { + $cell = strtoupper($cell); + [$row, $col, $row_rel, $col_rel] = $this->cellToRowcol($cell); + if ($col >= 256) { + throw new WriterException("Column in: $cell greater than 255"); + } + if ($row >= 65536) { + throw new WriterException("Row in: $cell greater than 65536 "); + } + + // Set the high bits to indicate if row or col are relative. + $col |= $col_rel << 14; + $col |= $row_rel << 15; + $col = pack('v', $col); + + $row = pack('v', $row); + + return [$row, $col]; + } + + /** + * pack() row range into the required 3 or 4 byte format. + * Just using maximum col/rows, which is probably not the correct solution. + * + * @param string $range The Excel range to be packed + * + * @return array Array containing (row1,col1,row2,col2) in packed() format + */ + private function rangeToPackedRange($range) + { + preg_match('/(\$)?(\d+)\:(\$)?(\d+)/', $range, $match); + // return absolute rows if there is a $ in the ref + $row1_rel = empty($match[1]) ? 1 : 0; + $row1 = $match[2]; + $row2_rel = empty($match[3]) ? 1 : 0; + $row2 = $match[4]; + // Convert 1-index to zero-index + --$row1; + --$row2; + // Trick poor inocent Excel + $col1 = 0; + $col2 = 65535; // FIXME: maximum possible value for Excel 5 (change this!!!) + + // FIXME: this changes for BIFF8 + if (($row1 >= 65536) || ($row2 >= 65536)) { + throw new WriterException("Row in: $range greater than 65536 "); + } + + // Set the high bits to indicate if rows are relative. + $col1 |= $row1_rel << 15; + $col2 |= $row2_rel << 15; + $col1 = pack('v', $col1); + $col2 = pack('v', $col2); + + $row1 = pack('v', $row1); + $row2 = pack('v', $row2); + + return [$row1, $col1, $row2, $col2]; + } + + /** + * Convert an Excel cell reference such as A1 or $B2 or C$3 or $D$4 to a zero + * indexed row and column number. Also returns two (0,1) values to indicate + * whether the row or column are relative references. + * + * @param string $cell the Excel cell reference in A1 format + * + * @return array + */ + private function cellToRowcol($cell) + { + preg_match('/(\$)?([A-I]?[A-Z])(\$)?(\d+)/', $cell, $match); + // return absolute column if there is a $ in the ref + $col_rel = empty($match[1]) ? 1 : 0; + $col_ref = $match[2]; + $row_rel = empty($match[3]) ? 1 : 0; + $row = $match[4]; + + // Convert base26 column string to a number. + $expn = strlen($col_ref) - 1; + $col = 0; + $col_ref_length = strlen($col_ref); + for ($i = 0; $i < $col_ref_length; ++$i) { + $col += (ord($col_ref[$i]) - 64) * 26 ** $expn; + --$expn; + } + + // Convert 1-index to zero-index + --$row; + --$col; + + return [$row, $col, $row_rel, $col_rel]; + } + + /** + * Advance to the next valid token. + */ + private function advance() + { + $token = ''; + $i = $this->currentCharacter; + $formula_length = strlen($this->formula); + // eat up white spaces + if ($i < $formula_length) { + while ($this->formula[$i] == ' ') { + ++$i; + } + + if ($i < ($formula_length - 1)) { + $this->lookAhead = $this->formula[$i + 1]; + } + $token = ''; + } + + while ($i < $formula_length) { + $token .= $this->formula[$i]; + + if ($i < ($formula_length - 1)) { + $this->lookAhead = $this->formula[$i + 1]; + } else { + $this->lookAhead = ''; + } + + if ($this->match($token) != '') { + $this->currentCharacter = $i + 1; + $this->currentToken = $token; + + return 1; + } + + if ($i < ($formula_length - 2)) { + $this->lookAhead = $this->formula[$i + 2]; + } else { // if we run out of characters lookAhead becomes empty + $this->lookAhead = ''; + } + ++$i; + } + //die("Lexical error ".$this->currentCharacter); + } + + /** + * Checks if it's a valid token. + * + * @param mixed $token the token to check + * + * @return mixed The checked token or false on failure + */ + private function match($token) + { + switch ($token) { + case '+': + case '-': + case '*': + case '/': + case '(': + case ')': + case ',': + case ';': + case '>=': + case '<=': + case '=': + case '<>': + case '^': + case '&': + case '%': + return $token; + + break; + case '>': + if ($this->lookAhead === '=') { // it's a GE token + break; + } + + return $token; + + break; + case '<': + // it's a LE or a NE token + if (($this->lookAhead === '=') || ($this->lookAhead === '>')) { + break; + } + + return $token; + + break; + default: + // if it's a reference A1 or $A$1 or $A1 or A$1 + if (preg_match('/^\$?[A-Ia-i]?[A-Za-z]\$?\d+$/', $token) && !preg_match('/\d/', $this->lookAhead) && ($this->lookAhead !== ':') && ($this->lookAhead !== '.') && ($this->lookAhead !== '!')) { + return $token; + } elseif (preg_match('/^' . self::REGEX_SHEET_TITLE_UNQUOTED . '(\\:' . self::REGEX_SHEET_TITLE_UNQUOTED . ')?\\!\$?[A-Ia-i]?[A-Za-z]\$?\\d+$/u', $token) && !preg_match('/\d/', $this->lookAhead) && ($this->lookAhead !== ':') && ($this->lookAhead !== '.')) { + // If it's an external reference (Sheet1!A1 or Sheet1:Sheet2!A1 or Sheet1!$A$1 or Sheet1:Sheet2!$A$1) + return $token; + } elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . '(\\:' . self::REGEX_SHEET_TITLE_QUOTED . ")?'\\!\\$?[A-Ia-i]?[A-Za-z]\\$?\\d+$/u", $token) && !preg_match('/\d/', $this->lookAhead) && ($this->lookAhead !== ':') && ($this->lookAhead !== '.')) { + // If it's an external reference ('Sheet1'!A1 or 'Sheet1:Sheet2'!A1 or 'Sheet1'!$A$1 or 'Sheet1:Sheet2'!$A$1) + return $token; + } elseif (preg_match('/^(\$)?[A-Ia-i]?[A-Za-z](\$)?\d+:(\$)?[A-Ia-i]?[A-Za-z](\$)?\d+$/', $token) && !preg_match('/\d/', $this->lookAhead)) { + // if it's a range A1:A2 or $A$1:$A$2 + return $token; + } elseif (preg_match('/^' . self::REGEX_SHEET_TITLE_UNQUOTED . '(\\:' . self::REGEX_SHEET_TITLE_UNQUOTED . ')?\\!\$?([A-Ia-i]?[A-Za-z])?\$?\\d+:\$?([A-Ia-i]?[A-Za-z])?\$?\\d+$/u', $token) && !preg_match('/\d/', $this->lookAhead)) { + // If it's an external range like Sheet1!A1:B2 or Sheet1:Sheet2!A1:B2 or Sheet1!$A$1:$B$2 or Sheet1:Sheet2!$A$1:$B$2 + return $token; + } elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . '(\\:' . self::REGEX_SHEET_TITLE_QUOTED . ")?'\\!\\$?([A-Ia-i]?[A-Za-z])?\\$?\\d+:\\$?([A-Ia-i]?[A-Za-z])?\\$?\\d+$/u", $token) && !preg_match('/\d/', $this->lookAhead)) { + // If it's an external range like 'Sheet1'!A1:B2 or 'Sheet1:Sheet2'!A1:B2 or 'Sheet1'!$A$1:$B$2 or 'Sheet1:Sheet2'!$A$1:$B$2 + return $token; + } elseif (is_numeric($token) && (!is_numeric($token . $this->lookAhead) || ($this->lookAhead == '')) && ($this->lookAhead !== '!') && ($this->lookAhead !== ':')) { + // If it's a number (check that it's not a sheet name or range) + return $token; + } elseif (preg_match('/"([^"]|""){0,255}"/', $token) && $this->lookAhead !== '"' && (substr_count($token, '"') % 2 == 0)) { + // If it's a string (of maximum 255 characters) + return $token; + } elseif (preg_match('/^#[A-Z0\\/]{3,5}[!?]{1}$/', $token) || $token === '#N/A') { + // If it's an error code + return $token; + } elseif (preg_match("/^[A-Z0-9\xc0-\xdc\\.]+$/i", $token) && ($this->lookAhead === '(')) { + // if it's a function call + return $token; + } elseif (preg_match('/^' . Calculation::CALCULATION_REGEXP_DEFINEDNAME . '$/miu', $token) && $this->spreadsheet->getDefinedName($token) !== null) { + return $token; + } elseif (substr($token, -1) === ')') { + // It's an argument of some description (e.g. a named range), + // precise nature yet to be determined + return $token; + } + + return ''; + } + } + + /** + * The parsing method. It parses a formula. + * + * @param string $formula the formula to parse, without the initial equal + * sign (=) + * + * @return mixed true on success + */ + public function parse($formula) + { + $this->currentCharacter = 0; + $this->formula = (string) $formula; + $this->lookAhead = $formula[1] ?? ''; + $this->advance(); + $this->parseTree = $this->condition(); + + return true; + } + + /** + * It parses a condition. It assumes the following rule: + * Cond -> Expr [(">" | "<") Expr]. + * + * @return mixed The parsed ptg'd tree on success + */ + private function condition() + { + $result = $this->expression(); + if ($this->currentToken == '<') { + $this->advance(); + $result2 = $this->expression(); + $result = $this->createTree('ptgLT', $result, $result2); + } elseif ($this->currentToken == '>') { + $this->advance(); + $result2 = $this->expression(); + $result = $this->createTree('ptgGT', $result, $result2); + } elseif ($this->currentToken == '<=') { + $this->advance(); + $result2 = $this->expression(); + $result = $this->createTree('ptgLE', $result, $result2); + } elseif ($this->currentToken == '>=') { + $this->advance(); + $result2 = $this->expression(); + $result = $this->createTree('ptgGE', $result, $result2); + } elseif ($this->currentToken == '=') { + $this->advance(); + $result2 = $this->expression(); + $result = $this->createTree('ptgEQ', $result, $result2); + } elseif ($this->currentToken == '<>') { + $this->advance(); + $result2 = $this->expression(); + $result = $this->createTree('ptgNE', $result, $result2); + } + + return $result; + } + + /** + * It parses a expression. It assumes the following rule: + * Expr -> Term [("+" | "-") Term] + * -> "string" + * -> "-" Term : Negative value + * -> "+" Term : Positive value + * -> Error code. + * + * @return mixed The parsed ptg'd tree on success + */ + private function expression() + { + // If it's a string return a string node + if (preg_match('/"([^"]|""){0,255}"/', $this->currentToken)) { + $tmp = str_replace('""', '"', $this->currentToken); + if (($tmp == '"') || ($tmp == '')) { + // Trap for "" that has been used for an empty string + $tmp = '""'; + } + $result = $this->createTree($tmp, '', ''); + $this->advance(); + + return $result; + // If it's an error code + } elseif (preg_match('/^#[A-Z0\\/]{3,5}[!?]{1}$/', $this->currentToken) || $this->currentToken == '#N/A') { + $result = $this->createTree($this->currentToken, 'ptgErr', ''); + $this->advance(); + + return $result; + // If it's a negative value + } elseif ($this->currentToken == '-') { + // catch "-" Term + $this->advance(); + $result2 = $this->expression(); + + return $this->createTree('ptgUminus', $result2, ''); + // If it's a positive value + } elseif ($this->currentToken == '+') { + // catch "+" Term + $this->advance(); + $result2 = $this->expression(); + + return $this->createTree('ptgUplus', $result2, ''); + } + $result = $this->term(); + while ($this->currentToken === '&') { + $this->advance(); + $result2 = $this->expression(); + $result = $this->createTree('ptgConcat', $result, $result2); + } + while ( + ($this->currentToken == '+') || + ($this->currentToken == '-') || + ($this->currentToken == '^') + ) { + if ($this->currentToken == '+') { + $this->advance(); + $result2 = $this->term(); + $result = $this->createTree('ptgAdd', $result, $result2); + } elseif ($this->currentToken == '-') { + $this->advance(); + $result2 = $this->term(); + $result = $this->createTree('ptgSub', $result, $result2); + } else { + $this->advance(); + $result2 = $this->term(); + $result = $this->createTree('ptgPower', $result, $result2); + } + } + + return $result; + } + + /** + * This function just introduces a ptgParen element in the tree, so that Excel + * doesn't get confused when working with a parenthesized formula afterwards. + * + * @return array The parsed ptg'd tree + * + * @see fact() + */ + private function parenthesizedExpression() + { + return $this->createTree('ptgParen', $this->expression(), ''); + } + + /** + * It parses a term. It assumes the following rule: + * Term -> Fact [("*" | "/") Fact]. + * + * @return mixed The parsed ptg'd tree on success + */ + private function term() + { + $result = $this->fact(); + while ( + ($this->currentToken == '*') || + ($this->currentToken == '/') + ) { + if ($this->currentToken == '*') { + $this->advance(); + $result2 = $this->fact(); + $result = $this->createTree('ptgMul', $result, $result2); + } else { + $this->advance(); + $result2 = $this->fact(); + $result = $this->createTree('ptgDiv', $result, $result2); + } + } + + return $result; + } + + /** + * It parses a factor. It assumes the following rule: + * Fact -> ( Expr ) + * | CellRef + * | CellRange + * | Number + * | Function. + * + * @return mixed The parsed ptg'd tree on success + */ + private function fact() + { + if ($this->currentToken === '(') { + $this->advance(); // eat the "(" + $result = $this->parenthesizedExpression(); + if ($this->currentToken !== ')') { + throw new WriterException("')' token expected."); + } + $this->advance(); // eat the ")" + + return $result; + } + // if it's a reference + if (preg_match('/^\$?[A-Ia-i]?[A-Za-z]\$?\d+$/', $this->currentToken)) { + $result = $this->createTree($this->currentToken, '', ''); + $this->advance(); + + return $result; + } elseif (preg_match('/^' . self::REGEX_SHEET_TITLE_UNQUOTED . '(\\:' . self::REGEX_SHEET_TITLE_UNQUOTED . ')?\\!\$?[A-Ia-i]?[A-Za-z]\$?\\d+$/u', $this->currentToken)) { + // If it's an external reference (Sheet1!A1 or Sheet1:Sheet2!A1 or Sheet1!$A$1 or Sheet1:Sheet2!$A$1) + $result = $this->createTree($this->currentToken, '', ''); + $this->advance(); + + return $result; + } elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . '(\\:' . self::REGEX_SHEET_TITLE_QUOTED . ")?'\\!\\$?[A-Ia-i]?[A-Za-z]\\$?\\d+$/u", $this->currentToken)) { + // If it's an external reference ('Sheet1'!A1 or 'Sheet1:Sheet2'!A1 or 'Sheet1'!$A$1 or 'Sheet1:Sheet2'!$A$1) + $result = $this->createTree($this->currentToken, '', ''); + $this->advance(); + + return $result; + } elseif ( + preg_match('/^(\$)?[A-Ia-i]?[A-Za-z](\$)?\d+:(\$)?[A-Ia-i]?[A-Za-z](\$)?\d+$/', $this->currentToken) || + preg_match('/^(\$)?[A-Ia-i]?[A-Za-z](\$)?\d+\.\.(\$)?[A-Ia-i]?[A-Za-z](\$)?\d+$/', $this->currentToken) + ) { + // if it's a range A1:B2 or $A$1:$B$2 + // must be an error? + $result = $this->createTree($this->currentToken, '', ''); + $this->advance(); + + return $result; + } elseif (preg_match('/^' . self::REGEX_SHEET_TITLE_UNQUOTED . '(\\:' . self::REGEX_SHEET_TITLE_UNQUOTED . ')?\\!\$?([A-Ia-i]?[A-Za-z])?\$?\\d+:\$?([A-Ia-i]?[A-Za-z])?\$?\\d+$/u', $this->currentToken)) { + // If it's an external range (Sheet1!A1:B2 or Sheet1:Sheet2!A1:B2 or Sheet1!$A$1:$B$2 or Sheet1:Sheet2!$A$1:$B$2) + // must be an error? + $result = $this->createTree($this->currentToken, '', ''); + $this->advance(); + + return $result; + } elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . '(\\:' . self::REGEX_SHEET_TITLE_QUOTED . ")?'\\!\\$?([A-Ia-i]?[A-Za-z])?\\$?\\d+:\\$?([A-Ia-i]?[A-Za-z])?\\$?\\d+$/u", $this->currentToken)) { + // If it's an external range ('Sheet1'!A1:B2 or 'Sheet1'!A1:B2 or 'Sheet1'!$A$1:$B$2 or 'Sheet1'!$A$1:$B$2) + // must be an error? + $result = $this->createTree($this->currentToken, '', ''); + $this->advance(); + + return $result; + } elseif (is_numeric($this->currentToken)) { + // If it's a number or a percent + if ($this->lookAhead === '%') { + $result = $this->createTree('ptgPercent', $this->currentToken, ''); + $this->advance(); // Skip the percentage operator once we've pre-built that tree + } else { + $result = $this->createTree($this->currentToken, '', ''); + } + $this->advance(); + + return $result; + } elseif (preg_match("/^[A-Z0-9\xc0-\xdc\\.]+$/i", $this->currentToken) && ($this->lookAhead === '(')) { + // if it's a function call + return $this->func(); + } elseif (preg_match('/^' . Calculation::CALCULATION_REGEXP_DEFINEDNAME . '$/miu', $this->currentToken) && $this->spreadsheet->getDefinedName($this->currentToken) !== null) { + $result = $this->createTree('ptgName', $this->currentToken, ''); + $this->advance(); + + return $result; + } + + throw new WriterException('Syntax error: ' . $this->currentToken . ', lookahead: ' . $this->lookAhead . ', current char: ' . $this->currentCharacter); + } + + /** + * It parses a function call. It assumes the following rule: + * Func -> ( Expr [,Expr]* ). + * + * @return mixed The parsed ptg'd tree on success + */ + private function func() + { + $num_args = 0; // number of arguments received + $function = strtoupper($this->currentToken); + $result = ''; // initialize result + $this->advance(); + $this->advance(); // eat the "(" + while ($this->currentToken !== ')') { + if ($num_args > 0) { + if ($this->currentToken === ',' || $this->currentToken === ';') { + $this->advance(); // eat the "," or ";" + } else { + throw new WriterException("Syntax error: comma expected in function $function, arg #{$num_args}"); + } + $result2 = $this->condition(); + $result = $this->createTree('arg', $result, $result2); + } else { // first argument + $result2 = $this->condition(); + $result = $this->createTree('arg', '', $result2); + } + ++$num_args; + } + if (!isset($this->functions[$function])) { + throw new WriterException("Function $function() doesn't exist"); + } + $args = $this->functions[$function][1]; + // If fixed number of args eg. TIME($i, $j, $k). Check that the number of args is valid. + if (($args >= 0) && ($args != $num_args)) { + throw new WriterException("Incorrect number of arguments in function $function() "); + } + + $result = $this->createTree($function, $result, $num_args); + $this->advance(); // eat the ")" + + return $result; + } + + /** + * Creates a tree. In fact an array which may have one or two arrays (sub-trees) + * as elements. + * + * @param mixed $value the value of this node + * @param mixed $left the left array (sub-tree) or a final node + * @param mixed $right the right array (sub-tree) or a final node + * + * @return array A tree + */ + private function createTree($value, $left, $right) + { + return ['value' => $value, 'left' => $left, 'right' => $right]; + } + + /** + * Builds a string containing the tree in reverse polish notation (What you + * would use in a HP calculator stack). + * The following tree:. + * + * + + * / \ + * 2 3 + * + * produces: "23+" + * + * The following tree: + * + * + + * / \ + * 3 * + * / \ + * 6 A1 + * + * produces: "36A1*+" + * + * In fact all operands, functions, references, etc... are written as ptg's + * + * @param array $tree the optional tree to convert + * + * @return string The tree in reverse polish notation + */ + public function toReversePolish($tree = []) + { + $polish = ''; // the string we are going to return + if (empty($tree)) { // If it's the first call use parseTree + $tree = $this->parseTree; + } + + if (is_array($tree['left'])) { + $converted_tree = $this->toReversePolish($tree['left']); + $polish .= $converted_tree; + } elseif ($tree['left'] != '') { // It's a final node + $converted_tree = $this->convert($tree['left']); + $polish .= $converted_tree; + } + if (is_array($tree['right'])) { + $converted_tree = $this->toReversePolish($tree['right']); + $polish .= $converted_tree; + } elseif ($tree['right'] != '') { // It's a final node + $converted_tree = $this->convert($tree['right']); + $polish .= $converted_tree; + } + // if it's a function convert it here (so we can set it's arguments) + if ( + preg_match("/^[A-Z0-9\xc0-\xdc\\.]+$/", $tree['value']) && + !preg_match('/^([A-Ia-i]?[A-Za-z])(\d+)$/', $tree['value']) && + !preg_match('/^[A-Ia-i]?[A-Za-z](\\d+)\\.\\.[A-Ia-i]?[A-Za-z](\\d+)$/', $tree['value']) && + !is_numeric($tree['value']) && + !isset($this->ptg[$tree['value']]) + ) { + // left subtree for a function is always an array. + if ($tree['left'] != '') { + $left_tree = $this->toReversePolish($tree['left']); + } else { + $left_tree = ''; + } + + // add it's left subtree and return. + return $left_tree . $this->convertFunction($tree['value'], $tree['right']); + } + $converted_tree = $this->convert($tree['value']); + + return $polish . $converted_tree; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Style/CellAlignment.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Style/CellAlignment.php new file mode 100644 index 0000000..711d88d --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Style/CellAlignment.php @@ -0,0 +1,59 @@ + + */ + private static $horizontalMap = [ + Alignment::HORIZONTAL_GENERAL => 0, + Alignment::HORIZONTAL_LEFT => 1, + Alignment::HORIZONTAL_RIGHT => 3, + Alignment::HORIZONTAL_CENTER => 2, + Alignment::HORIZONTAL_CENTER_CONTINUOUS => 6, + Alignment::HORIZONTAL_JUSTIFY => 5, + ]; + + /** + * @var array + */ + private static $verticalMap = [ + Alignment::VERTICAL_BOTTOM => 2, + Alignment::VERTICAL_TOP => 0, + Alignment::VERTICAL_CENTER => 1, + Alignment::VERTICAL_JUSTIFY => 3, + ]; + + public static function horizontal(Alignment $alignment): int + { + $horizontalAlignment = $alignment->getHorizontal(); + + if (is_string($horizontalAlignment) && array_key_exists($horizontalAlignment, self::$horizontalMap)) { + return self::$horizontalMap[$horizontalAlignment]; + } + + return self::$horizontalMap[Alignment::HORIZONTAL_GENERAL]; + } + + public static function wrap(Alignment $alignment): int + { + $wrap = $alignment->getWrapText(); + + return ($wrap === true) ? 1 : 0; + } + + public static function vertical(Alignment $alignment): int + { + $verticalAlignment = $alignment->getVertical(); + + if (is_string($verticalAlignment) && array_key_exists($verticalAlignment, self::$verticalMap)) { + return self::$verticalMap[$verticalAlignment]; + } + + return self::$verticalMap[Alignment::VERTICAL_BOTTOM]; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Style/CellBorder.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Style/CellBorder.php new file mode 100644 index 0000000..8d47d6a --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Style/CellBorder.php @@ -0,0 +1,39 @@ + + */ + protected static $styleMap = [ + Border::BORDER_NONE => 0x00, + Border::BORDER_THIN => 0x01, + Border::BORDER_MEDIUM => 0x02, + Border::BORDER_DASHED => 0x03, + Border::BORDER_DOTTED => 0x04, + Border::BORDER_THICK => 0x05, + Border::BORDER_DOUBLE => 0x06, + Border::BORDER_HAIR => 0x07, + Border::BORDER_MEDIUMDASHED => 0x08, + Border::BORDER_DASHDOT => 0x09, + Border::BORDER_MEDIUMDASHDOT => 0x0A, + Border::BORDER_DASHDOTDOT => 0x0B, + Border::BORDER_MEDIUMDASHDOTDOT => 0x0C, + Border::BORDER_SLANTDASHDOT => 0x0D, + ]; + + public static function style(Border $border): int + { + $borderStyle = $border->getBorderStyle(); + + if (is_string($borderStyle) && array_key_exists($borderStyle, self::$styleMap)) { + return self::$styleMap[$borderStyle]; + } + + return self::$styleMap[Border::BORDER_NONE]; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Style/CellFill.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Style/CellFill.php new file mode 100644 index 0000000..f5a8c47 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Style/CellFill.php @@ -0,0 +1,46 @@ + + */ + protected static $fillStyleMap = [ + Fill::FILL_NONE => 0x00, + Fill::FILL_SOLID => 0x01, + Fill::FILL_PATTERN_MEDIUMGRAY => 0x02, + Fill::FILL_PATTERN_DARKGRAY => 0x03, + Fill::FILL_PATTERN_LIGHTGRAY => 0x04, + Fill::FILL_PATTERN_DARKHORIZONTAL => 0x05, + Fill::FILL_PATTERN_DARKVERTICAL => 0x06, + Fill::FILL_PATTERN_DARKDOWN => 0x07, + Fill::FILL_PATTERN_DARKUP => 0x08, + Fill::FILL_PATTERN_DARKGRID => 0x09, + Fill::FILL_PATTERN_DARKTRELLIS => 0x0A, + Fill::FILL_PATTERN_LIGHTHORIZONTAL => 0x0B, + Fill::FILL_PATTERN_LIGHTVERTICAL => 0x0C, + Fill::FILL_PATTERN_LIGHTDOWN => 0x0D, + Fill::FILL_PATTERN_LIGHTUP => 0x0E, + Fill::FILL_PATTERN_LIGHTGRID => 0x0F, + Fill::FILL_PATTERN_LIGHTTRELLIS => 0x10, + Fill::FILL_PATTERN_GRAY125 => 0x11, + Fill::FILL_PATTERN_GRAY0625 => 0x12, + Fill::FILL_GRADIENT_LINEAR => 0x00, // does not exist in BIFF8 + Fill::FILL_GRADIENT_PATH => 0x00, // does not exist in BIFF8 + ]; + + public static function style(Fill $fill): int + { + $fillStyle = $fill->getFillType(); + + if (is_string($fillStyle) && array_key_exists($fillStyle, self::$fillStyleMap)) { + return self::$fillStyleMap[$fillStyle]; + } + + return self::$fillStyleMap[Fill::FILL_NONE]; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Style/ColorMap.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Style/ColorMap.php new file mode 100644 index 0000000..caf85c0 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Style/ColorMap.php @@ -0,0 +1,90 @@ + + */ + private static $colorMap = [ + '#000000' => 0x08, + '#FFFFFF' => 0x09, + '#FF0000' => 0x0A, + '#00FF00' => 0x0B, + '#0000FF' => 0x0C, + '#FFFF00' => 0x0D, + '#FF00FF' => 0x0E, + '#00FFFF' => 0x0F, + '#800000' => 0x10, + '#008000' => 0x11, + '#000080' => 0x12, + '#808000' => 0x13, + '#800080' => 0x14, + '#008080' => 0x15, + '#C0C0C0' => 0x16, + '#808080' => 0x17, + '#9999FF' => 0x18, + '#993366' => 0x19, + '#FFFFCC' => 0x1A, + '#CCFFFF' => 0x1B, + '#660066' => 0x1C, + '#FF8080' => 0x1D, + '#0066CC' => 0x1E, + '#CCCCFF' => 0x1F, + // '#000080' => 0x20, + // '#FF00FF' => 0x21, + // '#FFFF00' => 0x22, + // '#00FFFF' => 0x23, + // '#800080' => 0x24, + // '#800000' => 0x25, + // '#008080' => 0x26, + // '#0000FF' => 0x27, + '#00CCFF' => 0x28, + // '#CCFFFF' => 0x29, + '#CCFFCC' => 0x2A, + '#FFFF99' => 0x2B, + '#99CCFF' => 0x2C, + '#FF99CC' => 0x2D, + '#CC99FF' => 0x2E, + '#FFCC99' => 0x2F, + '#3366FF' => 0x30, + '#33CCCC' => 0x31, + '#99CC00' => 0x32, + '#FFCC00' => 0x33, + '#FF9900' => 0x34, + '#FF6600' => 0x35, + '#666699' => 0x36, + '#969696' => 0x37, + '#003366' => 0x38, + '#339966' => 0x39, + '#003300' => 0x3A, + '#333300' => 0x3B, + '#993300' => 0x3C, + // '#993366' => 0x3D, + '#333399' => 0x3E, + '#333333' => 0x3F, + ]; + + public static function lookup(Color $color, int $defaultIndex = 0x00): int + { + $colorRgb = $color->getRGB(); + if (is_string($colorRgb) && array_key_exists("#{$colorRgb}", self::$colorMap)) { + return self::$colorMap["#{$colorRgb}"]; + } + +// TODO Try and map RGB value to nearest colour within the define pallette +// $red = Color::getRed($colorRgb, false); +// $green = Color::getGreen($colorRgb, false); +// $blue = Color::getBlue($colorRgb, false); + +// $paletteSpace = 3; +// $newColor = ($red * $paletteSpace / 256) * ($paletteSpace * $paletteSpace) + +// ($green * $paletteSpace / 256) * $paletteSpace + +// ($blue * $paletteSpace / 256); + + return $defaultIndex; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Workbook.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Workbook.php new file mode 100644 index 0000000..ccffa18 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Workbook.php @@ -0,0 +1,1190 @@ + +// * +// * The majority of this is _NOT_ my code. I simply ported it from the +// * PERL Spreadsheet::WriteExcel module. +// * +// * The author of the Spreadsheet::WriteExcel module is John McNamara +// * +// * +// * I _DO_ maintain this code, and John McNamara has nothing to do with the +// * porting of this code to PHP. Any questions directly related to this +// * class library should be directed to me. +// * +// * License Information: +// * +// * Spreadsheet_Excel_Writer: A library for generating Excel Spreadsheets +// * Copyright (c) 2002-2003 Xavier Noguer xnoguer@rezebra.com +// * +// * This library is free software; you can redistribute it and/or +// * modify it under the terms of the GNU Lesser General Public +// * License as published by the Free Software Foundation; either +// * version 2.1 of the License, or (at your option) any later version. +// * +// * This library is distributed in the hope that it will be useful, +// * but WITHOUT ANY WARRANTY; without even the implied warranty of +// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// * Lesser General Public License for more details. +// * +// * You should have received a copy of the GNU Lesser General Public +// * License along with this library; if not, write to the Free Software +// * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// */ +class Workbook extends BIFFwriter +{ + /** + * Formula parser. + * + * @var \PhpOffice\PhpSpreadsheet\Writer\Xls\Parser + */ + private $parser; + + /** + * The BIFF file size for the workbook. + * + * @var int + * + * @see calcSheetOffsets() + */ + private $biffSize; + + /** + * XF Writers. + * + * @var \PhpOffice\PhpSpreadsheet\Writer\Xls\Xf[] + */ + private $xfWriters = []; + + /** + * Array containing the colour palette. + * + * @var array + */ + private $palette; + + /** + * The codepage indicates the text encoding used for strings. + * + * @var int + */ + private $codepage; + + /** + * The country code used for localization. + * + * @var int + */ + private $countryCode; + + /** + * Workbook. + * + * @var Spreadsheet + */ + private $spreadsheet; + + /** + * Fonts writers. + * + * @var Font[] + */ + private $fontWriters = []; + + /** + * Added fonts. Maps from font's hash => index in workbook. + * + * @var array + */ + private $addedFonts = []; + + /** + * Shared number formats. + * + * @var array + */ + private $numberFormats = []; + + /** + * Added number formats. Maps from numberFormat's hash => index in workbook. + * + * @var array + */ + private $addedNumberFormats = []; + + /** + * Sizes of the binary worksheet streams. + * + * @var array + */ + private $worksheetSizes = []; + + /** + * Offsets of the binary worksheet streams relative to the start of the global workbook stream. + * + * @var array + */ + private $worksheetOffsets = []; + + /** + * Total number of shared strings in workbook. + * + * @var int + */ + private $stringTotal; + + /** + * Number of unique shared strings in workbook. + * + * @var int + */ + private $stringUnique; + + /** + * Array of unique shared strings in workbook. + * + * @var array + */ + private $stringTable; + + /** + * Color cache. + */ + private $colors; + + /** + * Escher object corresponding to MSODRAWINGGROUP. + * + * @var null|\PhpOffice\PhpSpreadsheet\Shared\Escher + */ + private $escher; + + /** + * Class constructor. + * + * @param Spreadsheet $spreadsheet The Workbook + * @param int $str_total Total number of strings + * @param int $str_unique Total number of unique strings + * @param array $str_table String Table + * @param array $colors Colour Table + * @param Parser $parser The formula parser created for the Workbook + */ + public function __construct(Spreadsheet $spreadsheet, &$str_total, &$str_unique, &$str_table, &$colors, Parser $parser) + { + // It needs to call its parent's constructor explicitly + parent::__construct(); + + $this->parser = $parser; + $this->biffSize = 0; + $this->palette = []; + $this->countryCode = -1; + + $this->stringTotal = &$str_total; + $this->stringUnique = &$str_unique; + $this->stringTable = &$str_table; + $this->colors = &$colors; + $this->setPaletteXl97(); + + $this->spreadsheet = $spreadsheet; + + $this->codepage = 0x04B0; + + // Add empty sheets and Build color cache + $countSheets = $spreadsheet->getSheetCount(); + for ($i = 0; $i < $countSheets; ++$i) { + $phpSheet = $spreadsheet->getSheet($i); + + $this->parser->setExtSheet($phpSheet->getTitle(), $i); // Register worksheet name with parser + + $supbook_index = 0x00; + $ref = pack('vvv', $supbook_index, $i, $i); + $this->parser->references[] = $ref; // Register reference with parser + + // Sheet tab colors? + if ($phpSheet->isTabColorSet()) { + $this->addColor($phpSheet->getTabColor()->getRGB()); + } + } + } + + /** + * Add a new XF writer. + * + * @param bool $isStyleXf Is it a style XF? + * + * @return int Index to XF record + */ + public function addXfWriter(Style $style, $isStyleXf = false) + { + $xfWriter = new Xf($style); + $xfWriter->setIsStyleXf($isStyleXf); + + // Add the font if not already added + $fontIndex = $this->addFont($style->getFont()); + + // Assign the font index to the xf record + $xfWriter->setFontIndex($fontIndex); + + // Background colors, best to treat these after the font so black will come after white in custom palette + $xfWriter->setFgColor($this->addColor($style->getFill()->getStartColor()->getRGB())); + $xfWriter->setBgColor($this->addColor($style->getFill()->getEndColor()->getRGB())); + $xfWriter->setBottomColor($this->addColor($style->getBorders()->getBottom()->getColor()->getRGB())); + $xfWriter->setTopColor($this->addColor($style->getBorders()->getTop()->getColor()->getRGB())); + $xfWriter->setRightColor($this->addColor($style->getBorders()->getRight()->getColor()->getRGB())); + $xfWriter->setLeftColor($this->addColor($style->getBorders()->getLeft()->getColor()->getRGB())); + $xfWriter->setDiagColor($this->addColor($style->getBorders()->getDiagonal()->getColor()->getRGB())); + + // Add the number format if it is not a built-in one and not already added + if ($style->getNumberFormat()->getBuiltInFormatCode() === false) { + $numberFormatHashCode = $style->getNumberFormat()->getHashCode(); + + if (isset($this->addedNumberFormats[$numberFormatHashCode])) { + $numberFormatIndex = $this->addedNumberFormats[$numberFormatHashCode]; + } else { + $numberFormatIndex = 164 + count($this->numberFormats); + $this->numberFormats[$numberFormatIndex] = $style->getNumberFormat(); + $this->addedNumberFormats[$numberFormatHashCode] = $numberFormatIndex; + } + } else { + $numberFormatIndex = (int) $style->getNumberFormat()->getBuiltInFormatCode(); + } + + // Assign the number format index to xf record + $xfWriter->setNumberFormatIndex($numberFormatIndex); + + $this->xfWriters[] = $xfWriter; + + return count($this->xfWriters) - 1; + } + + /** + * Add a font to added fonts. + * + * @return int Index to FONT record + */ + public function addFont(\PhpOffice\PhpSpreadsheet\Style\Font $font) + { + $fontHashCode = $font->getHashCode(); + if (isset($this->addedFonts[$fontHashCode])) { + $fontIndex = $this->addedFonts[$fontHashCode]; + } else { + $countFonts = count($this->fontWriters); + $fontIndex = ($countFonts < 4) ? $countFonts : $countFonts + 1; + + $fontWriter = new Font($font); + $fontWriter->setColorIndex($this->addColor($font->getColor()->getRGB())); + $this->fontWriters[] = $fontWriter; + + $this->addedFonts[$fontHashCode] = $fontIndex; + } + + return $fontIndex; + } + + /** + * Alter color palette adding a custom color. + * + * @param string $rgb E.g. 'FF00AA' + * + * @return int Color index + */ + private function addColor($rgb) + { + if (!isset($this->colors[$rgb])) { + $color = + [ + hexdec(substr($rgb, 0, 2)), + hexdec(substr($rgb, 2, 2)), + hexdec(substr($rgb, 4)), + 0, + ]; + $colorIndex = array_search($color, $this->palette); + if ($colorIndex) { + $this->colors[$rgb] = $colorIndex; + } else { + if (count($this->colors) === 0) { + $lastColor = 7; + } else { + $lastColor = end($this->colors); + } + if ($lastColor < 57) { + // then we add a custom color altering the palette + $colorIndex = $lastColor + 1; + $this->palette[$colorIndex] = $color; + $this->colors[$rgb] = $colorIndex; + } else { + // no room for more custom colors, just map to black + $colorIndex = 0; + } + } + } else { + // fetch already added custom color + $colorIndex = $this->colors[$rgb]; + } + + return $colorIndex; + } + + /** + * Sets the colour palette to the Excel 97+ default. + */ + private function setPaletteXl97(): void + { + $this->palette = [ + 0x08 => [0x00, 0x00, 0x00, 0x00], + 0x09 => [0xff, 0xff, 0xff, 0x00], + 0x0A => [0xff, 0x00, 0x00, 0x00], + 0x0B => [0x00, 0xff, 0x00, 0x00], + 0x0C => [0x00, 0x00, 0xff, 0x00], + 0x0D => [0xff, 0xff, 0x00, 0x00], + 0x0E => [0xff, 0x00, 0xff, 0x00], + 0x0F => [0x00, 0xff, 0xff, 0x00], + 0x10 => [0x80, 0x00, 0x00, 0x00], + 0x11 => [0x00, 0x80, 0x00, 0x00], + 0x12 => [0x00, 0x00, 0x80, 0x00], + 0x13 => [0x80, 0x80, 0x00, 0x00], + 0x14 => [0x80, 0x00, 0x80, 0x00], + 0x15 => [0x00, 0x80, 0x80, 0x00], + 0x16 => [0xc0, 0xc0, 0xc0, 0x00], + 0x17 => [0x80, 0x80, 0x80, 0x00], + 0x18 => [0x99, 0x99, 0xff, 0x00], + 0x19 => [0x99, 0x33, 0x66, 0x00], + 0x1A => [0xff, 0xff, 0xcc, 0x00], + 0x1B => [0xcc, 0xff, 0xff, 0x00], + 0x1C => [0x66, 0x00, 0x66, 0x00], + 0x1D => [0xff, 0x80, 0x80, 0x00], + 0x1E => [0x00, 0x66, 0xcc, 0x00], + 0x1F => [0xcc, 0xcc, 0xff, 0x00], + 0x20 => [0x00, 0x00, 0x80, 0x00], + 0x21 => [0xff, 0x00, 0xff, 0x00], + 0x22 => [0xff, 0xff, 0x00, 0x00], + 0x23 => [0x00, 0xff, 0xff, 0x00], + 0x24 => [0x80, 0x00, 0x80, 0x00], + 0x25 => [0x80, 0x00, 0x00, 0x00], + 0x26 => [0x00, 0x80, 0x80, 0x00], + 0x27 => [0x00, 0x00, 0xff, 0x00], + 0x28 => [0x00, 0xcc, 0xff, 0x00], + 0x29 => [0xcc, 0xff, 0xff, 0x00], + 0x2A => [0xcc, 0xff, 0xcc, 0x00], + 0x2B => [0xff, 0xff, 0x99, 0x00], + 0x2C => [0x99, 0xcc, 0xff, 0x00], + 0x2D => [0xff, 0x99, 0xcc, 0x00], + 0x2E => [0xcc, 0x99, 0xff, 0x00], + 0x2F => [0xff, 0xcc, 0x99, 0x00], + 0x30 => [0x33, 0x66, 0xff, 0x00], + 0x31 => [0x33, 0xcc, 0xcc, 0x00], + 0x32 => [0x99, 0xcc, 0x00, 0x00], + 0x33 => [0xff, 0xcc, 0x00, 0x00], + 0x34 => [0xff, 0x99, 0x00, 0x00], + 0x35 => [0xff, 0x66, 0x00, 0x00], + 0x36 => [0x66, 0x66, 0x99, 0x00], + 0x37 => [0x96, 0x96, 0x96, 0x00], + 0x38 => [0x00, 0x33, 0x66, 0x00], + 0x39 => [0x33, 0x99, 0x66, 0x00], + 0x3A => [0x00, 0x33, 0x00, 0x00], + 0x3B => [0x33, 0x33, 0x00, 0x00], + 0x3C => [0x99, 0x33, 0x00, 0x00], + 0x3D => [0x99, 0x33, 0x66, 0x00], + 0x3E => [0x33, 0x33, 0x99, 0x00], + 0x3F => [0x33, 0x33, 0x33, 0x00], + ]; + } + + /** + * Assemble worksheets into a workbook and send the BIFF data to an OLE + * storage. + * + * @param array $worksheetSizes The sizes in bytes of the binary worksheet streams + * + * @return string Binary data for workbook stream + */ + public function writeWorkbook(array $worksheetSizes) + { + $this->worksheetSizes = $worksheetSizes; + + // Calculate the number of selected worksheet tabs and call the finalization + // methods for each worksheet + $total_worksheets = $this->spreadsheet->getSheetCount(); + + // Add part 1 of the Workbook globals, what goes before the SHEET records + $this->storeBof(0x0005); + $this->writeCodepage(); + $this->writeWindow1(); + + $this->writeDateMode(); + $this->writeAllFonts(); + $this->writeAllNumberFormats(); + $this->writeAllXfs(); + $this->writeAllStyles(); + $this->writePalette(); + + // Prepare part 3 of the workbook global stream, what goes after the SHEET records + $part3 = ''; + if ($this->countryCode !== -1) { + $part3 .= $this->writeCountry(); + } + $part3 .= $this->writeRecalcId(); + + $part3 .= $this->writeSupbookInternal(); + /* TODO: store external SUPBOOK records and XCT and CRN records + in case of external references for BIFF8 */ + $part3 .= $this->writeExternalsheetBiff8(); + $part3 .= $this->writeAllDefinedNamesBiff8(); + $part3 .= $this->writeMsoDrawingGroup(); + $part3 .= $this->writeSharedStringsTable(); + + $part3 .= $this->writeEof(); + + // Add part 2 of the Workbook globals, the SHEET records + $this->calcSheetOffsets(); + for ($i = 0; $i < $total_worksheets; ++$i) { + $this->writeBoundSheet($this->spreadsheet->getSheet($i), $this->worksheetOffsets[$i]); + } + + // Add part 3 of the Workbook globals + $this->_data .= $part3; + + return $this->_data; + } + + /** + * Calculate offsets for Worksheet BOF records. + */ + private function calcSheetOffsets(): void + { + $boundsheet_length = 10; // fixed length for a BOUNDSHEET record + + // size of Workbook globals part 1 + 3 + $offset = $this->_datasize; + + // add size of Workbook globals part 2, the length of the SHEET records + $total_worksheets = count($this->spreadsheet->getAllSheets()); + foreach ($this->spreadsheet->getWorksheetIterator() as $sheet) { + $offset += $boundsheet_length + strlen(StringHelper::UTF8toBIFF8UnicodeShort($sheet->getTitle())); + } + + // add the sizes of each of the Sheet substreams, respectively + for ($i = 0; $i < $total_worksheets; ++$i) { + $this->worksheetOffsets[$i] = $offset; + $offset += $this->worksheetSizes[$i]; + } + $this->biffSize = $offset; + } + + /** + * Store the Excel FONT records. + */ + private function writeAllFonts(): void + { + foreach ($this->fontWriters as $fontWriter) { + $this->append($fontWriter->writeFont()); + } + } + + /** + * Store user defined numerical formats i.e. FORMAT records. + */ + private function writeAllNumberFormats(): void + { + foreach ($this->numberFormats as $numberFormatIndex => $numberFormat) { + $this->writeNumberFormat($numberFormat->getFormatCode(), $numberFormatIndex); + } + } + + /** + * Write all XF records. + */ + private function writeAllXfs(): void + { + foreach ($this->xfWriters as $xfWriter) { + $this->append($xfWriter->writeXf()); + } + } + + /** + * Write all STYLE records. + */ + private function writeAllStyles(): void + { + $this->writeStyle(); + } + + private function parseDefinedNameValue(DefinedName $definedName): string + { + $definedRange = $definedName->getValue(); + $splitCount = preg_match_all( + '/' . Calculation::CALCULATION_REGEXP_CELLREF . '/mui', + $definedRange, + $splitRanges, + PREG_OFFSET_CAPTURE + ); + + $lengths = array_map('strlen', array_column($splitRanges[0], 0)); + $offsets = array_column($splitRanges[0], 1); + + $worksheets = $splitRanges[2]; + $columns = $splitRanges[6]; + $rows = $splitRanges[7]; + + while ($splitCount > 0) { + --$splitCount; + $length = $lengths[$splitCount]; + $offset = $offsets[$splitCount]; + $worksheet = $worksheets[$splitCount][0]; + $column = $columns[$splitCount][0]; + $row = $rows[$splitCount][0]; + + $newRange = ''; + if (empty($worksheet)) { + if (($offset === 0) || ($definedRange[$offset - 1] !== ':')) { + // We should have a worksheet + $worksheet = $definedName->getWorksheet() ? $definedName->getWorksheet()->getTitle() : null; + } + } else { + $worksheet = str_replace("''", "'", trim($worksheet, "'")); + } + if (!empty($worksheet)) { + $newRange = "'" . str_replace("'", "''", $worksheet) . "'!"; + } + + if (!empty($column)) { + $newRange .= "\${$column}"; + } + if (!empty($row)) { + $newRange .= "\${$row}"; + } + + $definedRange = substr($definedRange, 0, $offset) . $newRange . substr($definedRange, $offset + $length); + } + + return $definedRange; + } + + /** + * Writes all the DEFINEDNAME records (BIFF8). + * So far this is only used for repeating rows/columns (print titles) and print areas. + */ + private function writeAllDefinedNamesBiff8() + { + $chunk = ''; + + // Named ranges + $definedNames = $this->spreadsheet->getDefinedNames(); + if (count($definedNames) > 0) { + // Loop named ranges + foreach ($definedNames as $definedName) { + $range = $this->parseDefinedNameValue($definedName); + + // parse formula + try { + $error = $this->parser->parse($range); + $formulaData = $this->parser->toReversePolish(); + + // make sure tRef3d is of type tRef3dR (0x3A) + if (isset($formulaData[0]) && ($formulaData[0] == "\x7A" || $formulaData[0] == "\x5A")) { + $formulaData = "\x3A" . substr($formulaData, 1); + } + + if ($definedName->getLocalOnly()) { + /** + * local scope. + * + * @phpstan-ignore-next-line + */ + $scope = $this->spreadsheet->getIndex($definedName->getScope()) + 1; + } else { + // global scope + $scope = 0; + } + $chunk .= $this->writeData($this->writeDefinedNameBiff8($definedName->getName(), $formulaData, $scope, false)); + } catch (PhpSpreadsheetException $e) { + // do nothing + } + } + } + + // total number of sheets + $total_worksheets = $this->spreadsheet->getSheetCount(); + + // write the print titles (repeating rows, columns), if any + for ($i = 0; $i < $total_worksheets; ++$i) { + $sheetSetup = $this->spreadsheet->getSheet($i)->getPageSetup(); + // simultaneous repeatColumns repeatRows + if ($sheetSetup->isColumnsToRepeatAtLeftSet() && $sheetSetup->isRowsToRepeatAtTopSet()) { + $repeat = $sheetSetup->getColumnsToRepeatAtLeft(); + $colmin = Coordinate::columnIndexFromString($repeat[0]) - 1; + $colmax = Coordinate::columnIndexFromString($repeat[1]) - 1; + + $repeat = $sheetSetup->getRowsToRepeatAtTop(); + $rowmin = $repeat[0] - 1; + $rowmax = $repeat[1] - 1; + + // construct formula data manually + $formulaData = pack('Cv', 0x29, 0x17); // tMemFunc + $formulaData .= pack('Cvvvvv', 0x3B, $i, 0, 65535, $colmin, $colmax); // tArea3d + $formulaData .= pack('Cvvvvv', 0x3B, $i, $rowmin, $rowmax, 0, 255); // tArea3d + $formulaData .= pack('C', 0x10); // tList + + // store the DEFINEDNAME record + $chunk .= $this->writeData($this->writeDefinedNameBiff8(pack('C', 0x07), $formulaData, $i + 1, true)); + + // (exclusive) either repeatColumns or repeatRows + } elseif ($sheetSetup->isColumnsToRepeatAtLeftSet() || $sheetSetup->isRowsToRepeatAtTopSet()) { + // Columns to repeat + if ($sheetSetup->isColumnsToRepeatAtLeftSet()) { + $repeat = $sheetSetup->getColumnsToRepeatAtLeft(); + $colmin = Coordinate::columnIndexFromString($repeat[0]) - 1; + $colmax = Coordinate::columnIndexFromString($repeat[1]) - 1; + } else { + $colmin = 0; + $colmax = 255; + } + // Rows to repeat + if ($sheetSetup->isRowsToRepeatAtTopSet()) { + $repeat = $sheetSetup->getRowsToRepeatAtTop(); + $rowmin = $repeat[0] - 1; + $rowmax = $repeat[1] - 1; + } else { + $rowmin = 0; + $rowmax = 65535; + } + + // construct formula data manually because parser does not recognize absolute 3d cell references + $formulaData = pack('Cvvvvv', 0x3B, $i, $rowmin, $rowmax, $colmin, $colmax); + + // store the DEFINEDNAME record + $chunk .= $this->writeData($this->writeDefinedNameBiff8(pack('C', 0x07), $formulaData, $i + 1, true)); + } + } + + // write the print areas, if any + for ($i = 0; $i < $total_worksheets; ++$i) { + $sheetSetup = $this->spreadsheet->getSheet($i)->getPageSetup(); + if ($sheetSetup->isPrintAreaSet()) { + // Print area, e.g. A3:J6,H1:X20 + $printArea = Coordinate::splitRange($sheetSetup->getPrintArea()); + $countPrintArea = count($printArea); + + $formulaData = ''; + for ($j = 0; $j < $countPrintArea; ++$j) { + $printAreaRect = $printArea[$j]; // e.g. A3:J6 + $printAreaRect[0] = Coordinate::indexesFromString($printAreaRect[0]); + $printAreaRect[1] = Coordinate::indexesFromString($printAreaRect[1]); + + $print_rowmin = $printAreaRect[0][1] - 1; + $print_rowmax = $printAreaRect[1][1] - 1; + $print_colmin = $printAreaRect[0][0] - 1; + $print_colmax = $printAreaRect[1][0] - 1; + + // construct formula data manually because parser does not recognize absolute 3d cell references + $formulaData .= pack('Cvvvvv', 0x3B, $i, $print_rowmin, $print_rowmax, $print_colmin, $print_colmax); + + if ($j > 0) { + $formulaData .= pack('C', 0x10); // list operator token ',' + } + } + + // store the DEFINEDNAME record + $chunk .= $this->writeData($this->writeDefinedNameBiff8(pack('C', 0x06), $formulaData, $i + 1, true)); + } + } + + // write autofilters, if any + for ($i = 0; $i < $total_worksheets; ++$i) { + $sheetAutoFilter = $this->spreadsheet->getSheet($i)->getAutoFilter(); + $autoFilterRange = $sheetAutoFilter->getRange(); + if (!empty($autoFilterRange)) { + $rangeBounds = Coordinate::rangeBoundaries($autoFilterRange); + + //Autofilter built in name + $name = pack('C', 0x0D); + + $chunk .= $this->writeData($this->writeShortNameBiff8($name, $i + 1, $rangeBounds, true)); + } + } + + return $chunk; + } + + /** + * Write a DEFINEDNAME record for BIFF8 using explicit binary formula data. + * + * @param string $name The name in UTF-8 + * @param string $formulaData The binary formula data + * @param int $sheetIndex 1-based sheet index the defined name applies to. 0 = global + * @param bool $isBuiltIn Built-in name? + * + * @return string Complete binary record data + */ + private function writeDefinedNameBiff8($name, $formulaData, $sheetIndex = 0, $isBuiltIn = false) + { + $record = 0x0018; + + // option flags + $options = $isBuiltIn ? 0x20 : 0x00; + + // length of the name, character count + $nlen = StringHelper::countCharacters($name); + + // name with stripped length field + $name = substr(StringHelper::UTF8toBIFF8UnicodeLong($name), 2); + + // size of the formula (in bytes) + $sz = strlen($formulaData); + + // combine the parts + $data = pack('vCCvvvCCCC', $options, 0, $nlen, $sz, 0, $sheetIndex, 0, 0, 0, 0) + . $name . $formulaData; + $length = strlen($data); + + $header = pack('vv', $record, $length); + + return $header . $data; + } + + /** + * Write a short NAME record. + * + * @param string $name + * @param int $sheetIndex 1-based sheet index the defined name applies to. 0 = global + * @param int[][] $rangeBounds range boundaries + * @param bool $isHidden + * + * @return string Complete binary record data + * */ + private function writeShortNameBiff8($name, $sheetIndex, $rangeBounds, $isHidden = false) + { + $record = 0x0018; + + // option flags + $options = ($isHidden ? 0x21 : 0x00); + + $extra = pack( + 'Cvvvvv', + 0x3B, + $sheetIndex - 1, + $rangeBounds[0][1] - 1, + $rangeBounds[1][1] - 1, + $rangeBounds[0][0] - 1, + $rangeBounds[1][0] - 1 + ); + + // size of the formula (in bytes) + $sz = strlen($extra); + + // combine the parts + $data = pack('vCCvvvCCCCC', $options, 0, 1, $sz, 0, $sheetIndex, 0, 0, 0, 0, 0) + . $name . $extra; + $length = strlen($data); + + $header = pack('vv', $record, $length); + + return $header . $data; + } + + /** + * Stores the CODEPAGE biff record. + */ + private function writeCodepage(): void + { + $record = 0x0042; // Record identifier + $length = 0x0002; // Number of bytes to follow + $cv = $this->codepage; // The code page + + $header = pack('vv', $record, $length); + $data = pack('v', $cv); + + $this->append($header . $data); + } + + /** + * Write Excel BIFF WINDOW1 record. + */ + private function writeWindow1(): void + { + $record = 0x003D; // Record identifier + $length = 0x0012; // Number of bytes to follow + + $xWn = 0x0000; // Horizontal position of window + $yWn = 0x0000; // Vertical position of window + $dxWn = 0x25BC; // Width of window + $dyWn = 0x1572; // Height of window + + $grbit = 0x0038; // Option flags + + // not supported by PhpSpreadsheet, so there is only one selected sheet, the active + $ctabsel = 1; // Number of workbook tabs selected + + $wTabRatio = 0x0258; // Tab to scrollbar ratio + + // not supported by PhpSpreadsheet, set to 0 + $itabFirst = 0; // 1st displayed worksheet + $itabCur = $this->spreadsheet->getActiveSheetIndex(); // Active worksheet + + $header = pack('vv', $record, $length); + $data = pack('vvvvvvvvv', $xWn, $yWn, $dxWn, $dyWn, $grbit, $itabCur, $itabFirst, $ctabsel, $wTabRatio); + $this->append($header . $data); + } + + /** + * Writes Excel BIFF BOUNDSHEET record. + * + * @param int $offset Location of worksheet BOF + */ + private function writeBoundSheet(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $sheet, $offset): void + { + $sheetname = $sheet->getTitle(); + $record = 0x0085; // Record identifier + + // sheet state + switch ($sheet->getSheetState()) { + case \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::SHEETSTATE_VISIBLE: + $ss = 0x00; + + break; + case \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::SHEETSTATE_HIDDEN: + $ss = 0x01; + + break; + case \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::SHEETSTATE_VERYHIDDEN: + $ss = 0x02; + + break; + default: + $ss = 0x00; + + break; + } + + // sheet type + $st = 0x00; + + $grbit = 0x0000; // Visibility and sheet type + + $data = pack('VCC', $offset, $ss, $st); + $data .= StringHelper::UTF8toBIFF8UnicodeShort($sheetname); + + $length = strlen($data); + $header = pack('vv', $record, $length); + $this->append($header . $data); + } + + /** + * Write Internal SUPBOOK record. + */ + private function writeSupbookInternal() + { + $record = 0x01AE; // Record identifier + $length = 0x0004; // Bytes to follow + + $header = pack('vv', $record, $length); + $data = pack('vv', $this->spreadsheet->getSheetCount(), 0x0401); + + return $this->writeData($header . $data); + } + + /** + * Writes the Excel BIFF EXTERNSHEET record. These references are used by + * formulas. + */ + private function writeExternalsheetBiff8() + { + $totalReferences = count($this->parser->references); + $record = 0x0017; // Record identifier + $length = 2 + 6 * $totalReferences; // Number of bytes to follow + + $supbook_index = 0; // FIXME: only using internal SUPBOOK record + $header = pack('vv', $record, $length); + $data = pack('v', $totalReferences); + for ($i = 0; $i < $totalReferences; ++$i) { + $data .= $this->parser->references[$i]; + } + + return $this->writeData($header . $data); + } + + /** + * Write Excel BIFF STYLE records. + */ + private function writeStyle(): void + { + $record = 0x0293; // Record identifier + $length = 0x0004; // Bytes to follow + + $ixfe = 0x8000; // Index to cell style XF + $BuiltIn = 0x00; // Built-in style + $iLevel = 0xff; // Outline style level + + $header = pack('vv', $record, $length); + $data = pack('vCC', $ixfe, $BuiltIn, $iLevel); + $this->append($header . $data); + } + + /** + * Writes Excel FORMAT record for non "built-in" numerical formats. + * + * @param string $format Custom format string + * @param int $ifmt Format index code + */ + private function writeNumberFormat($format, $ifmt): void + { + $record = 0x041E; // Record identifier + + $numberFormatString = StringHelper::UTF8toBIFF8UnicodeLong($format); + $length = 2 + strlen($numberFormatString); // Number of bytes to follow + + $header = pack('vv', $record, $length); + $data = pack('v', $ifmt) . $numberFormatString; + $this->append($header . $data); + } + + /** + * Write DATEMODE record to indicate the date system in use (1904 or 1900). + */ + private function writeDateMode(): void + { + $record = 0x0022; // Record identifier + $length = 0x0002; // Bytes to follow + + $f1904 = (Date::getExcelCalendar() === Date::CALENDAR_MAC_1904) + ? 1 + : 0; // Flag for 1904 date system + + $header = pack('vv', $record, $length); + $data = pack('v', $f1904); + $this->append($header . $data); + } + + /** + * Stores the COUNTRY record for localization. + * + * @return string + */ + private function writeCountry() + { + $record = 0x008C; // Record identifier + $length = 4; // Number of bytes to follow + + $header = pack('vv', $record, $length); + // using the same country code always for simplicity + $data = pack('vv', $this->countryCode, $this->countryCode); + + return $this->writeData($header . $data); + } + + /** + * Write the RECALCID record. + * + * @return string + */ + private function writeRecalcId() + { + $record = 0x01C1; // Record identifier + $length = 8; // Number of bytes to follow + + $header = pack('vv', $record, $length); + + // by inspection of real Excel files, MS Office Excel 2007 writes this + $data = pack('VV', 0x000001C1, 0x00001E667); + + return $this->writeData($header . $data); + } + + /** + * Stores the PALETTE biff record. + */ + private function writePalette(): void + { + $aref = $this->palette; + + $record = 0x0092; // Record identifier + $length = 2 + 4 * count($aref); // Number of bytes to follow + $ccv = count($aref); // Number of RGB values to follow + $data = ''; // The RGB data + + // Pack the RGB data + foreach ($aref as $color) { + foreach ($color as $byte) { + $data .= pack('C', $byte); + } + } + + $header = pack('vvv', $record, $length, $ccv); + $this->append($header . $data); + } + + /** + * Handling of the SST continue blocks is complicated by the need to include an + * additional continuation byte depending on whether the string is split between + * blocks or whether it starts at the beginning of the block. (There are also + * additional complications that will arise later when/if Rich Strings are + * supported). + * + * The Excel documentation says that the SST record should be followed by an + * EXTSST record. The EXTSST record is a hash table that is used to optimise + * access to SST. However, despite the documentation it doesn't seem to be + * required so we will ignore it. + * + * @return string Binary data + */ + private function writeSharedStringsTable() + { + // maximum size of record data (excluding record header) + $continue_limit = 8224; + + // initialize array of record data blocks + $recordDatas = []; + + // start SST record data block with total number of strings, total number of unique strings + $recordData = pack('VV', $this->stringTotal, $this->stringUnique); + + // loop through all (unique) strings in shared strings table + foreach (array_keys($this->stringTable) as $string) { + // here $string is a BIFF8 encoded string + + // length = character count + $headerinfo = unpack('vlength/Cencoding', $string); + + // currently, this is always 1 = uncompressed + $encoding = $headerinfo['encoding']; + + // initialize finished writing current $string + $finished = false; + + while ($finished === false) { + // normally, there will be only one cycle, but if string cannot immediately be written as is + // there will be need for more than one cylcle, if string longer than one record data block, there + // may be need for even more cycles + + if (strlen($recordData) + strlen($string) <= $continue_limit) { + // then we can write the string (or remainder of string) without any problems + $recordData .= $string; + + if (strlen($recordData) + strlen($string) == $continue_limit) { + // we close the record data block, and initialize a new one + $recordDatas[] = $recordData; + $recordData = ''; + } + + // we are finished writing this string + $finished = true; + } else { + // special treatment writing the string (or remainder of the string) + // If the string is very long it may need to be written in more than one CONTINUE record. + + // check how many bytes more there is room for in the current record + $space_remaining = $continue_limit - strlen($recordData); + + // minimum space needed + // uncompressed: 2 byte string length length field + 1 byte option flags + 2 byte character + // compressed: 2 byte string length length field + 1 byte option flags + 1 byte character + $min_space_needed = ($encoding == 1) ? 5 : 4; + + // We have two cases + // 1. space remaining is less than minimum space needed + // here we must waste the space remaining and move to next record data block + // 2. space remaining is greater than or equal to minimum space needed + // here we write as much as we can in the current block, then move to next record data block + + // 1. space remaining is less than minimum space needed + if ($space_remaining < $min_space_needed) { + // we close the block, store the block data + $recordDatas[] = $recordData; + + // and start new record data block where we start writing the string + $recordData = ''; + + // 2. space remaining is greater than or equal to minimum space needed + } else { + // initialize effective remaining space, for Unicode strings this may need to be reduced by 1, see below + $effective_space_remaining = $space_remaining; + + // for uncompressed strings, sometimes effective space remaining is reduced by 1 + if ($encoding == 1 && (strlen($string) - $space_remaining) % 2 == 1) { + --$effective_space_remaining; + } + + // one block fininshed, store the block data + $recordData .= substr($string, 0, $effective_space_remaining); + + $string = substr($string, $effective_space_remaining); // for next cycle in while loop + $recordDatas[] = $recordData; + + // start new record data block with the repeated option flags + $recordData = pack('C', $encoding); + } + } + } + } + + // Store the last record data block unless it is empty + // if there was no need for any continue records, this will be the for SST record data block itself + if (strlen($recordData) > 0) { + $recordDatas[] = $recordData; + } + + // combine into one chunk with all the blocks SST, CONTINUE,... + $chunk = ''; + foreach ($recordDatas as $i => $recordData) { + // first block should have the SST record header, remaing should have CONTINUE header + $record = ($i == 0) ? 0x00FC : 0x003C; + + $header = pack('vv', $record, strlen($recordData)); + $data = $header . $recordData; + + $chunk .= $this->writeData($data); + } + + return $chunk; + } + + /** + * Writes the MSODRAWINGGROUP record if needed. Possibly split using CONTINUE records. + */ + private function writeMsoDrawingGroup() + { + // write the Escher stream if necessary + if (isset($this->escher)) { + $writer = new Escher($this->escher); + $data = $writer->close(); + + $record = 0x00EB; + $length = strlen($data); + $header = pack('vv', $record, $length); + + return $this->writeData($header . $data); + } + + return ''; + } + + /** + * Get Escher object. + */ + public function getEscher(): ?\PhpOffice\PhpSpreadsheet\Shared\Escher + { + return $this->escher; + } + + /** + * Set Escher object. + */ + public function setEscher(?\PhpOffice\PhpSpreadsheet\Shared\Escher $escher): void + { + $this->escher = $escher; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Worksheet.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Worksheet.php new file mode 100644 index 0000000..77ec5de --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Worksheet.php @@ -0,0 +1,3184 @@ + +// * +// * The majority of this is _NOT_ my code. I simply ported it from the +// * PERL Spreadsheet::WriteExcel module. +// * +// * The author of the Spreadsheet::WriteExcel module is John McNamara +// * +// * +// * I _DO_ maintain this code, and John McNamara has nothing to do with the +// * porting of this code to PHP. Any questions directly related to this +// * class library should be directed to me. +// * +// * License Information: +// * +// * Spreadsheet_Excel_Writer: A library for generating Excel Spreadsheets +// * Copyright (c) 2002-2003 Xavier Noguer xnoguer@rezebra.com +// * +// * This library is free software; you can redistribute it and/or +// * modify it under the terms of the GNU Lesser General Public +// * License as published by the Free Software Foundation; either +// * version 2.1 of the License, or (at your option) any later version. +// * +// * This library is distributed in the hope that it will be useful, +// * but WITHOUT ANY WARRANTY; without even the implied warranty of +// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// * Lesser General Public License for more details. +// * +// * You should have received a copy of the GNU Lesser General Public +// * License along with this library; if not, write to the Free Software +// * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// */ +class Worksheet extends BIFFwriter +{ + /** + * Formula parser. + * + * @var \PhpOffice\PhpSpreadsheet\Writer\Xls\Parser + */ + private $parser; + + /** + * Maximum number of characters for a string (LABEL record in BIFF5). + * + * @var int + */ + private $xlsStringMaxLength; + + /** + * Array containing format information for columns. + * + * @var array + */ + private $columnInfo; + + /** + * Array containing the selected area for the worksheet. + * + * @var array + */ + private $selection; + + /** + * The active pane for the worksheet. + * + * @var int + */ + private $activePane; + + /** + * Whether to use outline. + * + * @var bool + */ + private $outlineOn; + + /** + * Auto outline styles. + * + * @var bool + */ + private $outlineStyle; + + /** + * Whether to have outline summary below. + * + * @var bool + */ + private $outlineBelow; + + /** + * Whether to have outline summary at the right. + * + * @var bool + */ + private $outlineRight; + + /** + * Reference to the total number of strings in the workbook. + * + * @var int + */ + private $stringTotal; + + /** + * Reference to the number of unique strings in the workbook. + * + * @var int + */ + private $stringUnique; + + /** + * Reference to the array containing all the unique strings in the workbook. + * + * @var array + */ + private $stringTable; + + /** + * Color cache. + */ + private $colors; + + /** + * Index of first used row (at least 0). + * + * @var int + */ + private $firstRowIndex; + + /** + * Index of last used row. (no used rows means -1). + * + * @var int + */ + private $lastRowIndex; + + /** + * Index of first used column (at least 0). + * + * @var int + */ + private $firstColumnIndex; + + /** + * Index of last used column (no used columns means -1). + * + * @var int + */ + private $lastColumnIndex; + + /** + * Sheet object. + * + * @var \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet + */ + public $phpSheet; + + /** + * Count cell style Xfs. + * + * @var int + */ + private $countCellStyleXfs; + + /** + * Escher object corresponding to MSODRAWING. + * + * @var null|\PhpOffice\PhpSpreadsheet\Shared\Escher + */ + private $escher; + + /** + * Array of font hashes associated to FONT records index. + * + * @var array + */ + public $fontHashIndex; + + /** + * @var bool + */ + private $preCalculateFormulas; + + /** + * @var int + */ + private $printHeaders; + + /** + * Constructor. + * + * @param int $str_total Total number of strings + * @param int $str_unique Total number of unique strings + * @param array $str_table String Table + * @param array $colors Colour Table + * @param Parser $parser The formula parser created for the Workbook + * @param bool $preCalculateFormulas Flag indicating whether formulas should be calculated or just written + * @param \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $phpSheet The worksheet to write + */ + public function __construct(&$str_total, &$str_unique, &$str_table, &$colors, Parser $parser, $preCalculateFormulas, \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $phpSheet) + { + // It needs to call its parent's constructor explicitly + parent::__construct(); + + $this->preCalculateFormulas = $preCalculateFormulas; + $this->stringTotal = &$str_total; + $this->stringUnique = &$str_unique; + $this->stringTable = &$str_table; + $this->colors = &$colors; + $this->parser = $parser; + + $this->phpSheet = $phpSheet; + + $this->xlsStringMaxLength = 255; + $this->columnInfo = []; + $this->selection = [0, 0, 0, 0]; + $this->activePane = 3; + + $this->printHeaders = 0; + + $this->outlineStyle = false; + $this->outlineBelow = true; + $this->outlineRight = true; + $this->outlineOn = true; + + $this->fontHashIndex = []; + + // calculate values for DIMENSIONS record + $minR = 1; + $minC = 'A'; + + $maxR = $this->phpSheet->getHighestRow(); + $maxC = $this->phpSheet->getHighestColumn(); + + // Determine lowest and highest column and row + $this->firstRowIndex = $minR; + $this->lastRowIndex = ($maxR > 65535) ? 65535 : $maxR; + + $this->firstColumnIndex = Coordinate::columnIndexFromString($minC); + $this->lastColumnIndex = Coordinate::columnIndexFromString($maxC); + + if ($this->lastColumnIndex > 255) { + $this->lastColumnIndex = 255; + } + + $this->countCellStyleXfs = count($phpSheet->getParent()->getCellStyleXfCollection()); + } + + /** + * Add data to the beginning of the workbook (note the reverse order) + * and to the end of the workbook. + * + * @see \PhpOffice\PhpSpreadsheet\Writer\Xls\Workbook::storeWorkbook() + */ + public function close(): void + { + $phpSheet = $this->phpSheet; + + // Storing selected cells and active sheet because it changes while parsing cells with formulas. + $selectedCells = $this->phpSheet->getSelectedCells(); + $activeSheetIndex = $this->phpSheet->getParent()->getActiveSheetIndex(); + + // Write BOF record + $this->storeBof(0x0010); + + // Write PRINTHEADERS + $this->writePrintHeaders(); + + // Write PRINTGRIDLINES + $this->writePrintGridlines(); + + // Write GRIDSET + $this->writeGridset(); + + // Calculate column widths + $phpSheet->calculateColumnWidths(); + + // Column dimensions + if (($defaultWidth = $phpSheet->getDefaultColumnDimension()->getWidth()) < 0) { + $defaultWidth = \PhpOffice\PhpSpreadsheet\Shared\Font::getDefaultColumnWidthByFont($phpSheet->getParent()->getDefaultStyle()->getFont()); + } + + $columnDimensions = $phpSheet->getColumnDimensions(); + $maxCol = $this->lastColumnIndex - 1; + for ($i = 0; $i <= $maxCol; ++$i) { + $hidden = 0; + $level = 0; + $xfIndex = 15; // there are 15 cell style Xfs + + $width = $defaultWidth; + + $columnLetter = Coordinate::stringFromColumnIndex($i + 1); + if (isset($columnDimensions[$columnLetter])) { + $columnDimension = $columnDimensions[$columnLetter]; + if ($columnDimension->getWidth() >= 0) { + $width = $columnDimension->getWidth(); + } + $hidden = $columnDimension->getVisible() ? 0 : 1; + $level = $columnDimension->getOutlineLevel(); + $xfIndex = $columnDimension->getXfIndex() + 15; // there are 15 cell style Xfs + } + + // Components of columnInfo: + // $firstcol first column on the range + // $lastcol last column on the range + // $width width to set + // $xfIndex The optional cell style Xf index to apply to the columns + // $hidden The optional hidden atribute + // $level The optional outline level + $this->columnInfo[] = [$i, $i, $width, $xfIndex, $hidden, $level]; + } + + // Write GUTS + $this->writeGuts(); + + // Write DEFAULTROWHEIGHT + $this->writeDefaultRowHeight(); + // Write WSBOOL + $this->writeWsbool(); + // Write horizontal and vertical page breaks + $this->writeBreaks(); + // Write page header + $this->writeHeader(); + // Write page footer + $this->writeFooter(); + // Write page horizontal centering + $this->writeHcenter(); + // Write page vertical centering + $this->writeVcenter(); + // Write left margin + $this->writeMarginLeft(); + // Write right margin + $this->writeMarginRight(); + // Write top margin + $this->writeMarginTop(); + // Write bottom margin + $this->writeMarginBottom(); + // Write page setup + $this->writeSetup(); + // Write sheet protection + $this->writeProtect(); + // Write SCENPROTECT + $this->writeScenProtect(); + // Write OBJECTPROTECT + $this->writeObjectProtect(); + // Write sheet password + $this->writePassword(); + // Write DEFCOLWIDTH record + $this->writeDefcol(); + + // Write the COLINFO records if they exist + if (!empty($this->columnInfo)) { + $colcount = count($this->columnInfo); + for ($i = 0; $i < $colcount; ++$i) { + $this->writeColinfo($this->columnInfo[$i]); + } + } + $autoFilterRange = $phpSheet->getAutoFilter()->getRange(); + if (!empty($autoFilterRange)) { + // Write AUTOFILTERINFO + $this->writeAutoFilterInfo(); + } + + // Write sheet dimensions + $this->writeDimensions(); + + // Row dimensions + foreach ($phpSheet->getRowDimensions() as $rowDimension) { + $xfIndex = $rowDimension->getXfIndex() + 15; // there are 15 cellXfs + $this->writeRow( + $rowDimension->getRowIndex() - 1, + (int) $rowDimension->getRowHeight(), + $xfIndex, + !$rowDimension->getVisible(), + $rowDimension->getOutlineLevel() + ); + } + + // Write Cells + foreach ($phpSheet->getCoordinates() as $coordinate) { + $cell = $phpSheet->getCell($coordinate); + $row = $cell->getRow() - 1; + $column = Coordinate::columnIndexFromString($cell->getColumn()) - 1; + + // Don't break Excel break the code! + if ($row > 65535 || $column > 255) { + throw new WriterException('Rows or columns overflow! Excel5 has limit to 65535 rows and 255 columns. Use XLSX instead.'); + } + + // Write cell value + $xfIndex = $cell->getXfIndex() + 15; // there are 15 cell style Xfs + + $cVal = $cell->getValue(); + if ($cVal instanceof RichText) { + $arrcRun = []; + $str_pos = 0; + $elements = $cVal->getRichTextElements(); + foreach ($elements as $element) { + // FONT Index + if ($element instanceof Run) { + $str_fontidx = $this->fontHashIndex[$element->getFont()->getHashCode()]; + } else { + $str_fontidx = 0; + } + $arrcRun[] = ['strlen' => $str_pos, 'fontidx' => $str_fontidx]; + // Position FROM + $str_pos += StringHelper::countCharacters($element->getText(), 'UTF-8'); + } + $this->writeRichTextString($row, $column, $cVal->getPlainText(), $xfIndex, $arrcRun); + } else { + switch ($cell->getDatatype()) { + case DataType::TYPE_STRING: + case DataType::TYPE_NULL: + if ($cVal === '' || $cVal === null) { + $this->writeBlank($row, $column, $xfIndex); + } else { + $this->writeString($row, $column, $cVal, $xfIndex); + } + + break; + case DataType::TYPE_NUMERIC: + $this->writeNumber($row, $column, $cVal, $xfIndex); + + break; + case DataType::TYPE_FORMULA: + $calculatedValue = $this->preCalculateFormulas ? + $cell->getCalculatedValue() : null; + if (self::WRITE_FORMULA_EXCEPTION == $this->writeFormula($row, $column, $cVal, $xfIndex, $calculatedValue)) { + if ($calculatedValue === null) { + $calculatedValue = $cell->getCalculatedValue(); + } + $calctype = gettype($calculatedValue); + switch ($calctype) { + case 'integer': + case 'double': + $this->writeNumber($row, $column, $calculatedValue, $xfIndex); + + break; + case 'string': + $this->writeString($row, $column, $calculatedValue, $xfIndex); + + break; + case 'boolean': + $this->writeBoolErr($row, $column, $calculatedValue, 0, $xfIndex); + + break; + default: + $this->writeString($row, $column, $cVal, $xfIndex); + } + } + + break; + case DataType::TYPE_BOOL: + $this->writeBoolErr($row, $column, $cVal, 0, $xfIndex); + + break; + case DataType::TYPE_ERROR: + $this->writeBoolErr($row, $column, ErrorCode::error($cVal), 1, $xfIndex); + + break; + } + } + } + + // Append + $this->writeMsoDrawing(); + + // Restoring active sheet. + $this->phpSheet->getParent()->setActiveSheetIndex($activeSheetIndex); + + // Write WINDOW2 record + $this->writeWindow2(); + + // Write PLV record + $this->writePageLayoutView(); + + // Write ZOOM record + $this->writeZoom(); + if ($phpSheet->getFreezePane()) { + $this->writePanes(); + } + + // Restoring selected cells. + $this->phpSheet->setSelectedCells($selectedCells); + + // Write SELECTION record + $this->writeSelection(); + + // Write MergedCellsTable Record + $this->writeMergedCells(); + + // Hyperlinks + foreach ($phpSheet->getHyperLinkCollection() as $coordinate => $hyperlink) { + [$column, $row] = Coordinate::indexesFromString($coordinate); + + $url = $hyperlink->getUrl(); + + if (strpos($url, 'sheet://') !== false) { + // internal to current workbook + $url = str_replace('sheet://', 'internal:', $url); + } elseif (preg_match('/^(http:|https:|ftp:|mailto:)/', $url)) { + // URL + } else { + // external (local file) + $url = 'external:' . $url; + } + + $this->writeUrl($row - 1, $column - 1, $url); + } + + $this->writeDataValidity(); + $this->writeSheetLayout(); + + // Write SHEETPROTECTION record + $this->writeSheetProtection(); + $this->writeRangeProtection(); + + $arrConditionalStyles = $phpSheet->getConditionalStylesCollection(); + if (!empty($arrConditionalStyles)) { + $arrConditional = []; + + $cfHeaderWritten = false; + // Write ConditionalFormattingTable records + foreach ($arrConditionalStyles as $cellCoordinate => $conditionalStyles) { + foreach ($conditionalStyles as $conditional) { + /** @var Conditional $conditional */ + if ( + $conditional->getConditionType() == Conditional::CONDITION_EXPRESSION || + $conditional->getConditionType() == Conditional::CONDITION_CELLIS + ) { + // Write CFHEADER record (only if there are Conditional Styles that we are able to write) + if ($cfHeaderWritten === false) { + $this->writeCFHeader(); + $cfHeaderWritten = true; + } + if (!isset($arrConditional[$conditional->getHashCode()])) { + // This hash code has been handled + $arrConditional[$conditional->getHashCode()] = true; + + // Write CFRULE record + $this->writeCFRule($conditional); + } + } + } + } + } + + $this->storeEof(); + } + + /** + * Write a cell range address in BIFF8 + * always fixed range + * See section 2.5.14 in OpenOffice.org's Documentation of the Microsoft Excel File Format. + * + * @param string $range E.g. 'A1' or 'A1:B6' + * + * @return string Binary data + */ + private function writeBIFF8CellRangeAddressFixed($range) + { + $explodes = explode(':', $range); + + // extract first cell, e.g. 'A1' + $firstCell = $explodes[0]; + + // extract last cell, e.g. 'B6' + if (count($explodes) == 1) { + $lastCell = $firstCell; + } else { + $lastCell = $explodes[1]; + } + + $firstCellCoordinates = Coordinate::indexesFromString($firstCell); // e.g. [0, 1] + $lastCellCoordinates = Coordinate::indexesFromString($lastCell); // e.g. [1, 6] + + return pack('vvvv', $firstCellCoordinates[1] - 1, $lastCellCoordinates[1] - 1, $firstCellCoordinates[0] - 1, $lastCellCoordinates[0] - 1); + } + + /** + * Retrieves data from memory in one chunk, or from disk + * sized chunks. + * + * @return string The data + */ + public function getData() + { + // Return data stored in memory + if (isset($this->_data)) { + $tmp = $this->_data; + $this->_data = null; + + return $tmp; + } + + // No data to return + return ''; + } + + /** + * Set the option to print the row and column headers on the printed page. + * + * @param int $print Whether to print the headers or not. Defaults to 1 (print). + */ + public function printRowColHeaders($print = 1): void + { + $this->printHeaders = $print; + } + + /** + * This method sets the properties for outlining and grouping. The defaults + * correspond to Excel's defaults. + * + * @param bool $visible + * @param bool $symbols_below + * @param bool $symbols_right + * @param bool $auto_style + */ + public function setOutline($visible = true, $symbols_below = true, $symbols_right = true, $auto_style = false): void + { + $this->outlineOn = $visible; + $this->outlineBelow = $symbols_below; + $this->outlineRight = $symbols_right; + $this->outlineStyle = $auto_style; + } + + /** + * Write a double to the specified row and column (zero indexed). + * An integer can be written as a double. Excel will display an + * integer. $format is optional. + * + * Returns 0 : normal termination + * -2 : row or column out of range + * + * @param int $row Zero indexed row + * @param int $col Zero indexed column + * @param float $num The number to write + * @param mixed $xfIndex The optional XF format + * + * @return int + */ + private function writeNumber($row, $col, $num, $xfIndex) + { + $record = 0x0203; // Record identifier + $length = 0x000E; // Number of bytes to follow + + $header = pack('vv', $record, $length); + $data = pack('vvv', $row, $col, $xfIndex); + $xl_double = pack('d', $num); + if (self::getByteOrder()) { // if it's Big Endian + $xl_double = strrev($xl_double); + } + + $this->append($header . $data . $xl_double); + + return 0; + } + + /** + * Write a LABELSST record or a LABEL record. Which one depends on BIFF version. + * + * @param int $row Row index (0-based) + * @param int $col Column index (0-based) + * @param string $str The string + * @param int $xfIndex Index to XF record + */ + private function writeString($row, $col, $str, $xfIndex): void + { + $this->writeLabelSst($row, $col, $str, $xfIndex); + } + + /** + * Write a LABELSST record or a LABEL record. Which one depends on BIFF version + * It differs from writeString by the writing of rich text strings. + * + * @param int $row Row index (0-based) + * @param int $col Column index (0-based) + * @param string $str The string + * @param int $xfIndex The XF format index for the cell + * @param array $arrcRun Index to Font record and characters beginning + */ + private function writeRichTextString($row, $col, $str, $xfIndex, $arrcRun): void + { + $record = 0x00FD; // Record identifier + $length = 0x000A; // Bytes to follow + $str = StringHelper::UTF8toBIFF8UnicodeShort($str, $arrcRun); + + // check if string is already present + if (!isset($this->stringTable[$str])) { + $this->stringTable[$str] = $this->stringUnique++; + } + ++$this->stringTotal; + + $header = pack('vv', $record, $length); + $data = pack('vvvV', $row, $col, $xfIndex, $this->stringTable[$str]); + $this->append($header . $data); + } + + /** + * Write a string to the specified row and column (zero indexed). + * This is the BIFF8 version (no 255 chars limit). + * $format is optional. + * + * @param int $row Zero indexed row + * @param int $col Zero indexed column + * @param string $str The string to write + * @param mixed $xfIndex The XF format index for the cell + */ + private function writeLabelSst($row, $col, $str, $xfIndex): void + { + $record = 0x00FD; // Record identifier + $length = 0x000A; // Bytes to follow + + $str = StringHelper::UTF8toBIFF8UnicodeLong($str); + + // check if string is already present + if (!isset($this->stringTable[$str])) { + $this->stringTable[$str] = $this->stringUnique++; + } + ++$this->stringTotal; + + $header = pack('vv', $record, $length); + $data = pack('vvvV', $row, $col, $xfIndex, $this->stringTable[$str]); + $this->append($header . $data); + } + + /** + * Write a blank cell to the specified row and column (zero indexed). + * A blank cell is used to specify formatting without adding a string + * or a number. + * + * A blank cell without a format serves no purpose. Therefore, we don't write + * a BLANK record unless a format is specified. + * + * Returns 0 : normal termination (including no format) + * -1 : insufficient number of arguments + * -2 : row or column out of range + * + * @param int $row Zero indexed row + * @param int $col Zero indexed column + * @param mixed $xfIndex The XF format index + * + * @return int + */ + public function writeBlank($row, $col, $xfIndex) + { + $record = 0x0201; // Record identifier + $length = 0x0006; // Number of bytes to follow + + $header = pack('vv', $record, $length); + $data = pack('vvv', $row, $col, $xfIndex); + $this->append($header . $data); + + return 0; + } + + /** + * Write a boolean or an error type to the specified row and column (zero indexed). + * + * @param int $row Row index (0-based) + * @param int $col Column index (0-based) + * @param int $value + * @param bool $isError Error or Boolean? + * @param int $xfIndex + * + * @return int + */ + private function writeBoolErr($row, $col, $value, $isError, $xfIndex) + { + $record = 0x0205; + $length = 8; + + $header = pack('vv', $record, $length); + $data = pack('vvvCC', $row, $col, $xfIndex, $value, $isError); + $this->append($header . $data); + + return 0; + } + + const WRITE_FORMULA_NORMAL = 0; + const WRITE_FORMULA_ERRORS = -1; + const WRITE_FORMULA_RANGE = -2; + const WRITE_FORMULA_EXCEPTION = -3; + + /** + * Write a formula to the specified row and column (zero indexed). + * The textual representation of the formula is passed to the parser in + * Parser.php which returns a packed binary string. + * + * Returns 0 : WRITE_FORMULA_NORMAL normal termination + * -1 : WRITE_FORMULA_ERRORS formula errors (bad formula) + * -2 : WRITE_FORMULA_RANGE row or column out of range + * -3 : WRITE_FORMULA_EXCEPTION parse raised exception, probably due to definedname + * + * @param int $row Zero indexed row + * @param int $col Zero indexed column + * @param string $formula The formula text string + * @param mixed $xfIndex The XF format index + * @param mixed $calculatedValue Calculated value + * + * @return int + */ + private function writeFormula($row, $col, $formula, $xfIndex, $calculatedValue) + { + $record = 0x0006; // Record identifier + // Initialize possible additional value for STRING record that should be written after the FORMULA record? + $stringValue = null; + + // calculated value + if (isset($calculatedValue)) { + // Since we can't yet get the data type of the calculated value, + // we use best effort to determine data type + if (is_bool($calculatedValue)) { + // Boolean value + $num = pack('CCCvCv', 0x01, 0x00, (int) $calculatedValue, 0x00, 0x00, 0xFFFF); + } elseif (is_int($calculatedValue) || is_float($calculatedValue)) { + // Numeric value + $num = pack('d', $calculatedValue); + } elseif (is_string($calculatedValue)) { + $errorCodes = DataType::getErrorCodes(); + if (isset($errorCodes[$calculatedValue])) { + // Error value + $num = pack('CCCvCv', 0x02, 0x00, ErrorCode::error($calculatedValue), 0x00, 0x00, 0xFFFF); + } elseif ($calculatedValue === '') { + // Empty string (and BIFF8) + $num = pack('CCCvCv', 0x03, 0x00, 0x00, 0x00, 0x00, 0xFFFF); + } else { + // Non-empty string value (or empty string BIFF5) + $stringValue = $calculatedValue; + $num = pack('CCCvCv', 0x00, 0x00, 0x00, 0x00, 0x00, 0xFFFF); + } + } else { + // We are really not supposed to reach here + $num = pack('d', 0x00); + } + } else { + $num = pack('d', 0x00); + } + + $grbit = 0x03; // Option flags + $unknown = 0x0000; // Must be zero + + // Strip the '=' or '@' sign at the beginning of the formula string + if ($formula[0] == '=') { + $formula = substr($formula, 1); + } else { + // Error handling + $this->writeString($row, $col, 'Unrecognised character for formula', 0); + + return self::WRITE_FORMULA_ERRORS; + } + + // Parse the formula using the parser in Parser.php + try { + $this->parser->parse($formula); + $formula = $this->parser->toReversePolish(); + + $formlen = strlen($formula); // Length of the binary string + $length = 0x16 + $formlen; // Length of the record data + + $header = pack('vv', $record, $length); + + $data = pack('vvv', $row, $col, $xfIndex) + . $num + . pack('vVv', $grbit, $unknown, $formlen); + $this->append($header . $data . $formula); + + // Append also a STRING record if necessary + if ($stringValue !== null) { + $this->writeStringRecord($stringValue); + } + + return self::WRITE_FORMULA_NORMAL; + } catch (PhpSpreadsheetException $e) { + return self::WRITE_FORMULA_EXCEPTION; + } + } + + /** + * Write a STRING record. This. + * + * @param string $stringValue + */ + private function writeStringRecord($stringValue): void + { + $record = 0x0207; // Record identifier + $data = StringHelper::UTF8toBIFF8UnicodeLong($stringValue); + + $length = strlen($data); + $header = pack('vv', $record, $length); + + $this->append($header . $data); + } + + /** + * Write a hyperlink. + * This is comprised of two elements: the visible label and + * the invisible link. The visible label is the same as the link unless an + * alternative string is specified. The label is written using the + * writeString() method. Therefore the 255 characters string limit applies. + * $string and $format are optional. + * + * The hyperlink can be to a http, ftp, mail, internal sheet (not yet), or external + * directory url. + * + * @param int $row Row + * @param int $col Column + * @param string $url URL string + */ + private function writeUrl($row, $col, $url): void + { + // Add start row and col to arg list + $this->writeUrlRange($row, $col, $row, $col, $url); + } + + /** + * This is the more general form of writeUrl(). It allows a hyperlink to be + * written to a range of cells. This function also decides the type of hyperlink + * to be written. These are either, Web (http, ftp, mailto), Internal + * (Sheet1!A1) or external ('c:\temp\foo.xls#Sheet1!A1'). + * + * @param int $row1 Start row + * @param int $col1 Start column + * @param int $row2 End row + * @param int $col2 End column + * @param string $url URL string + * + * @see writeUrl() + */ + private function writeUrlRange($row1, $col1, $row2, $col2, $url): void + { + // Check for internal/external sheet links or default to web link + if (preg_match('[^internal:]', $url)) { + $this->writeUrlInternal($row1, $col1, $row2, $col2, $url); + } + if (preg_match('[^external:]', $url)) { + $this->writeUrlExternal($row1, $col1, $row2, $col2, $url); + } + + $this->writeUrlWeb($row1, $col1, $row2, $col2, $url); + } + + /** + * Used to write http, ftp and mailto hyperlinks. + * The link type ($options) is 0x03 is the same as absolute dir ref without + * sheet. However it is differentiated by the $unknown2 data stream. + * + * @param int $row1 Start row + * @param int $col1 Start column + * @param int $row2 End row + * @param int $col2 End column + * @param string $url URL string + * + * @see writeUrl() + */ + public function writeUrlWeb($row1, $col1, $row2, $col2, $url): void + { + $record = 0x01B8; // Record identifier + + // Pack the undocumented parts of the hyperlink stream + $unknown1 = pack('H*', 'D0C9EA79F9BACE118C8200AA004BA90B02000000'); + $unknown2 = pack('H*', 'E0C9EA79F9BACE118C8200AA004BA90B'); + + // Pack the option flags + $options = pack('V', 0x03); + + // Convert URL to a null terminated wchar string + $url = implode("\0", preg_split("''", $url, -1, PREG_SPLIT_NO_EMPTY)); + $url = $url . "\0\0\0"; + + // Pack the length of the URL + $url_len = pack('V', strlen($url)); + + // Calculate the data length + $length = 0x34 + strlen($url); + + // Pack the header data + $header = pack('vv', $record, $length); + $data = pack('vvvv', $row1, $row2, $col1, $col2); + + // Write the packed data + $this->append($header . $data . $unknown1 . $options . $unknown2 . $url_len . $url); + } + + /** + * Used to write internal reference hyperlinks such as "Sheet1!A1". + * + * @param int $row1 Start row + * @param int $col1 Start column + * @param int $row2 End row + * @param int $col2 End column + * @param string $url URL string + * + * @see writeUrl() + */ + private function writeUrlInternal($row1, $col1, $row2, $col2, $url): void + { + $record = 0x01B8; // Record identifier + + // Strip URL type + $url = preg_replace('/^internal:/', '', $url); + + // Pack the undocumented parts of the hyperlink stream + $unknown1 = pack('H*', 'D0C9EA79F9BACE118C8200AA004BA90B02000000'); + + // Pack the option flags + $options = pack('V', 0x08); + + // Convert the URL type and to a null terminated wchar string + $url .= "\0"; + + // character count + $url_len = StringHelper::countCharacters($url); + $url_len = pack('V', $url_len); + + $url = StringHelper::convertEncoding($url, 'UTF-16LE', 'UTF-8'); + + // Calculate the data length + $length = 0x24 + strlen($url); + + // Pack the header data + $header = pack('vv', $record, $length); + $data = pack('vvvv', $row1, $row2, $col1, $col2); + + // Write the packed data + $this->append($header . $data . $unknown1 . $options . $url_len . $url); + } + + /** + * Write links to external directory names such as 'c:\foo.xls', + * c:\foo.xls#Sheet1!A1', '../../foo.xls'. and '../../foo.xls#Sheet1!A1'. + * + * Note: Excel writes some relative links with the $dir_long string. We ignore + * these cases for the sake of simpler code. + * + * @param int $row1 Start row + * @param int $col1 Start column + * @param int $row2 End row + * @param int $col2 End column + * @param string $url URL string + * + * @see writeUrl() + */ + private function writeUrlExternal($row1, $col1, $row2, $col2, $url): void + { + // Network drives are different. We will handle them separately + // MS/Novell network drives and shares start with \\ + if (preg_match('[^external:\\\\]', $url)) { + return; + } + + $record = 0x01B8; // Record identifier + $length = 0x00000; // Bytes to follow + + // Strip URL type and change Unix dir separator to Dos style (if needed) + // + $url = preg_replace('/^external:/', '', $url); + $url = preg_replace('/\//', '\\', $url); + + // Determine if the link is relative or absolute: + // relative if link contains no dir separator, "somefile.xls" + // relative if link starts with up-dir, "..\..\somefile.xls" + // otherwise, absolute + + $absolute = 0x00; // relative path + if (preg_match('/^[A-Z]:/', $url)) { + $absolute = 0x02; // absolute path on Windows, e.g. C:\... + } + $link_type = 0x01 | $absolute; + + // Determine if the link contains a sheet reference and change some of the + // parameters accordingly. + // Split the dir name and sheet name (if it exists) + $dir_long = $url; + if (preg_match('/\\#/', $url)) { + $link_type |= 0x08; + } + + // Pack the link type + $link_type = pack('V', $link_type); + + // Calculate the up-level dir count e.g.. (..\..\..\ == 3) + $up_count = preg_match_all('/\\.\\.\\\\/', $dir_long, $useless); + $up_count = pack('v', $up_count); + + // Store the short dos dir name (null terminated) + $dir_short = preg_replace('/\\.\\.\\\\/', '', $dir_long) . "\0"; + + // Store the long dir name as a wchar string (non-null terminated) + $dir_long = $dir_long . "\0"; + + // Pack the lengths of the dir strings + $dir_short_len = pack('V', strlen($dir_short)); + $dir_long_len = pack('V', strlen($dir_long)); + $stream_len = pack('V', 0); //strlen($dir_long) + 0x06); + + // Pack the undocumented parts of the hyperlink stream + $unknown1 = pack('H*', 'D0C9EA79F9BACE118C8200AA004BA90B02000000'); + $unknown2 = pack('H*', '0303000000000000C000000000000046'); + $unknown3 = pack('H*', 'FFFFADDE000000000000000000000000000000000000000'); + $unknown4 = pack('v', 0x03); + + // Pack the main data stream + $data = pack('vvvv', $row1, $row2, $col1, $col2) . + $unknown1 . + $link_type . + $unknown2 . + $up_count . + $dir_short_len . + $dir_short . + $unknown3 . + $stream_len; /*. + $dir_long_len . + $unknown4 . + $dir_long . + $sheet_len . + $sheet ;*/ + + // Pack the header data + $length = strlen($data); + $header = pack('vv', $record, $length); + + // Write the packed data + $this->append($header . $data); + } + + /** + * This method is used to set the height and format for a row. + * + * @param int $row The row to set + * @param int $height Height we are giving to the row. + * Use null to set XF without setting height + * @param int $xfIndex The optional cell style Xf index to apply to the columns + * @param bool $hidden The optional hidden attribute + * @param int $level The optional outline level for row, in range [0,7] + */ + private function writeRow($row, $height, $xfIndex, $hidden = false, $level = 0): void + { + $record = 0x0208; // Record identifier + $length = 0x0010; // Number of bytes to follow + + $colMic = 0x0000; // First defined column + $colMac = 0x0000; // Last defined column + $irwMac = 0x0000; // Used by Excel to optimise loading + $reserved = 0x0000; // Reserved + $grbit = 0x0000; // Option flags + $ixfe = $xfIndex; + + if ($height < 0) { + $height = null; + } + + // Use writeRow($row, null, $XF) to set XF format without setting height + if ($height !== null) { + $miyRw = $height * 20; // row height + } else { + $miyRw = 0xff; // default row height is 256 + } + + // Set the options flags. fUnsynced is used to show that the font and row + // heights are not compatible. This is usually the case for WriteExcel. + // The collapsed flag 0x10 doesn't seem to be used to indicate that a row + // is collapsed. Instead it is used to indicate that the previous row is + // collapsed. The zero height flag, 0x20, is used to collapse a row. + + $grbit |= $level; + if ($hidden === true) { + $grbit |= 0x0030; + } + if ($height !== null) { + $grbit |= 0x0040; // fUnsynced + } + if ($xfIndex !== 0xF) { + $grbit |= 0x0080; + } + $grbit |= 0x0100; + + $header = pack('vv', $record, $length); + $data = pack('vvvvvvvv', $row, $colMic, $colMac, $miyRw, $irwMac, $reserved, $grbit, $ixfe); + $this->append($header . $data); + } + + /** + * Writes Excel DIMENSIONS to define the area in which there is data. + */ + private function writeDimensions(): void + { + $record = 0x0200; // Record identifier + + $length = 0x000E; + $data = pack('VVvvv', $this->firstRowIndex, $this->lastRowIndex + 1, $this->firstColumnIndex, $this->lastColumnIndex + 1, 0x0000); // reserved + + $header = pack('vv', $record, $length); + $this->append($header . $data); + } + + /** + * Write BIFF record Window2. + */ + private function writeWindow2(): void + { + $record = 0x023E; // Record identifier + $length = 0x0012; + + $grbit = 0x00B6; // Option flags + $rwTop = 0x0000; // Top row visible in window + $colLeft = 0x0000; // Leftmost column visible in window + + // The options flags that comprise $grbit + $fDspFmla = 0; // 0 - bit + $fDspGrid = $this->phpSheet->getShowGridlines() ? 1 : 0; // 1 + $fDspRwCol = $this->phpSheet->getShowRowColHeaders() ? 1 : 0; // 2 + $fFrozen = $this->phpSheet->getFreezePane() ? 1 : 0; // 3 + $fDspZeros = 1; // 4 + $fDefaultHdr = 1; // 5 + $fArabic = $this->phpSheet->getRightToLeft() ? 1 : 0; // 6 + $fDspGuts = $this->outlineOn; // 7 + $fFrozenNoSplit = 0; // 0 - bit + // no support in PhpSpreadsheet for selected sheet, therefore sheet is only selected if it is the active sheet + $fSelected = ($this->phpSheet === $this->phpSheet->getParent()->getActiveSheet()) ? 1 : 0; + $fPageBreakPreview = $this->phpSheet->getSheetView()->getView() === SheetView::SHEETVIEW_PAGE_BREAK_PREVIEW; + + $grbit = $fDspFmla; + $grbit |= $fDspGrid << 1; + $grbit |= $fDspRwCol << 2; + $grbit |= $fFrozen << 3; + $grbit |= $fDspZeros << 4; + $grbit |= $fDefaultHdr << 5; + $grbit |= $fArabic << 6; + $grbit |= $fDspGuts << 7; + $grbit |= $fFrozenNoSplit << 8; + $grbit |= $fSelected << 9; // Selected sheets. + $grbit |= $fSelected << 10; // Active sheet. + $grbit |= $fPageBreakPreview << 11; + + $header = pack('vv', $record, $length); + $data = pack('vvv', $grbit, $rwTop, $colLeft); + + // FIXME !!! + $rgbHdr = 0x0040; // Row/column heading and gridline color index + $zoom_factor_page_break = ($fPageBreakPreview ? $this->phpSheet->getSheetView()->getZoomScale() : 0x0000); + $zoom_factor_normal = $this->phpSheet->getSheetView()->getZoomScaleNormal(); + + $data .= pack('vvvvV', $rgbHdr, 0x0000, $zoom_factor_page_break, $zoom_factor_normal, 0x00000000); + + $this->append($header . $data); + } + + /** + * Write BIFF record DEFAULTROWHEIGHT. + */ + private function writeDefaultRowHeight(): void + { + $defaultRowHeight = $this->phpSheet->getDefaultRowDimension()->getRowHeight(); + + if ($defaultRowHeight < 0) { + return; + } + + // convert to twips + $defaultRowHeight = (int) 20 * $defaultRowHeight; + + $record = 0x0225; // Record identifier + $length = 0x0004; // Number of bytes to follow + + $header = pack('vv', $record, $length); + $data = pack('vv', 1, $defaultRowHeight); + $this->append($header . $data); + } + + /** + * Write BIFF record DEFCOLWIDTH if COLINFO records are in use. + */ + private function writeDefcol(): void + { + $defaultColWidth = 8; + + $record = 0x0055; // Record identifier + $length = 0x0002; // Number of bytes to follow + + $header = pack('vv', $record, $length); + $data = pack('v', $defaultColWidth); + $this->append($header . $data); + } + + /** + * Write BIFF record COLINFO to define column widths. + * + * Note: The SDK says the record length is 0x0B but Excel writes a 0x0C + * length record. + * + * @param array $col_array This is the only parameter received and is composed of the following: + * 0 => First formatted column, + * 1 => Last formatted column, + * 2 => Col width (8.43 is Excel default), + * 3 => The optional XF format of the column, + * 4 => Option flags. + * 5 => Optional outline level + */ + private function writeColinfo($col_array): void + { + $colFirst = $col_array[0] ?? null; + $colLast = $col_array[1] ?? null; + $coldx = $col_array[2] ?? 8.43; + $xfIndex = $col_array[3] ?? 15; + $grbit = $col_array[4] ?? 0; + $level = $col_array[5] ?? 0; + + $record = 0x007D; // Record identifier + $length = 0x000C; // Number of bytes to follow + + $coldx *= 256; // Convert to units of 1/256 of a char + + $ixfe = $xfIndex; + $reserved = 0x0000; // Reserved + + $level = max(0, min($level, 7)); + $grbit |= $level << 8; + + $header = pack('vv', $record, $length); + $data = pack('vvvvvv', $colFirst, $colLast, $coldx, $ixfe, $grbit, $reserved); + $this->append($header . $data); + } + + /** + * Write BIFF record SELECTION. + */ + private function writeSelection(): void + { + // look up the selected cell range + $selectedCells = Coordinate::splitRange($this->phpSheet->getSelectedCells()); + $selectedCells = $selectedCells[0]; + if (count($selectedCells) == 2) { + [$first, $last] = $selectedCells; + } else { + $first = $selectedCells[0]; + $last = $selectedCells[0]; + } + + [$colFirst, $rwFirst] = Coordinate::coordinateFromString($first); + $colFirst = Coordinate::columnIndexFromString($colFirst) - 1; // base 0 column index + --$rwFirst; // base 0 row index + + [$colLast, $rwLast] = Coordinate::coordinateFromString($last); + $colLast = Coordinate::columnIndexFromString($colLast) - 1; // base 0 column index + --$rwLast; // base 0 row index + + // make sure we are not out of bounds + $colFirst = min($colFirst, 255); + $colLast = min($colLast, 255); + + $rwFirst = min($rwFirst, 65535); + $rwLast = min($rwLast, 65535); + + $record = 0x001D; // Record identifier + $length = 0x000F; // Number of bytes to follow + + $pnn = $this->activePane; // Pane position + $rwAct = $rwFirst; // Active row + $colAct = $colFirst; // Active column + $irefAct = 0; // Active cell ref + $cref = 1; // Number of refs + + // Swap last row/col for first row/col as necessary + if ($rwFirst > $rwLast) { + [$rwFirst, $rwLast] = [$rwLast, $rwFirst]; + } + + if ($colFirst > $colLast) { + [$colFirst, $colLast] = [$colLast, $colFirst]; + } + + $header = pack('vv', $record, $length); + $data = pack('CvvvvvvCC', $pnn, $rwAct, $colAct, $irefAct, $cref, $rwFirst, $rwLast, $colFirst, $colLast); + $this->append($header . $data); + } + + /** + * Store the MERGEDCELLS records for all ranges of merged cells. + */ + private function writeMergedCells(): void + { + $mergeCells = $this->phpSheet->getMergeCells(); + $countMergeCells = count($mergeCells); + + if ($countMergeCells == 0) { + return; + } + + // maximum allowed number of merged cells per record + $maxCountMergeCellsPerRecord = 1027; + + // record identifier + $record = 0x00E5; + + // counter for total number of merged cells treated so far by the writer + $i = 0; + + // counter for number of merged cells written in record currently being written + $j = 0; + + // initialize record data + $recordData = ''; + + // loop through the merged cells + foreach ($mergeCells as $mergeCell) { + ++$i; + ++$j; + + // extract the row and column indexes + $range = Coordinate::splitRange($mergeCell); + [$first, $last] = $range[0]; + [$firstColumn, $firstRow] = Coordinate::indexesFromString($first); + [$lastColumn, $lastRow] = Coordinate::indexesFromString($last); + + $recordData .= pack('vvvv', $firstRow - 1, $lastRow - 1, $firstColumn - 1, $lastColumn - 1); + + // flush record if we have reached limit for number of merged cells, or reached final merged cell + if ($j == $maxCountMergeCellsPerRecord || $i == $countMergeCells) { + $recordData = pack('v', $j) . $recordData; + $length = strlen($recordData); + $header = pack('vv', $record, $length); + $this->append($header . $recordData); + + // initialize for next record, if any + $recordData = ''; + $j = 0; + } + } + } + + /** + * Write SHEETLAYOUT record. + */ + private function writeSheetLayout(): void + { + if (!$this->phpSheet->isTabColorSet()) { + return; + } + + $recordData = pack( + 'vvVVVvv', + 0x0862, + 0x0000, // unused + 0x00000000, // unused + 0x00000000, // unused + 0x00000014, // size of record data + $this->colors[$this->phpSheet->getTabColor()->getRGB()], // color index + 0x0000 // unused + ); + + $length = strlen($recordData); + + $record = 0x0862; // Record identifier + $header = pack('vv', $record, $length); + $this->append($header . $recordData); + } + + /** + * Write SHEETPROTECTION. + */ + private function writeSheetProtection(): void + { + // record identifier + $record = 0x0867; + + // prepare options + $options = (int) !$this->phpSheet->getProtection()->getObjects() + | (int) !$this->phpSheet->getProtection()->getScenarios() << 1 + | (int) !$this->phpSheet->getProtection()->getFormatCells() << 2 + | (int) !$this->phpSheet->getProtection()->getFormatColumns() << 3 + | (int) !$this->phpSheet->getProtection()->getFormatRows() << 4 + | (int) !$this->phpSheet->getProtection()->getInsertColumns() << 5 + | (int) !$this->phpSheet->getProtection()->getInsertRows() << 6 + | (int) !$this->phpSheet->getProtection()->getInsertHyperlinks() << 7 + | (int) !$this->phpSheet->getProtection()->getDeleteColumns() << 8 + | (int) !$this->phpSheet->getProtection()->getDeleteRows() << 9 + | (int) !$this->phpSheet->getProtection()->getSelectLockedCells() << 10 + | (int) !$this->phpSheet->getProtection()->getSort() << 11 + | (int) !$this->phpSheet->getProtection()->getAutoFilter() << 12 + | (int) !$this->phpSheet->getProtection()->getPivotTables() << 13 + | (int) !$this->phpSheet->getProtection()->getSelectUnlockedCells() << 14; + + // record data + $recordData = pack( + 'vVVCVVvv', + 0x0867, // repeated record identifier + 0x0000, // not used + 0x0000, // not used + 0x00, // not used + 0x01000200, // unknown data + 0xFFFFFFFF, // unknown data + $options, // options + 0x0000 // not used + ); + + $length = strlen($recordData); + $header = pack('vv', $record, $length); + + $this->append($header . $recordData); + } + + /** + * Write BIFF record RANGEPROTECTION. + * + * Openoffice.org's Documentation of the Microsoft Excel File Format uses term RANGEPROTECTION for these records + * Microsoft Office Excel 97-2007 Binary File Format Specification uses term FEAT for these records + */ + private function writeRangeProtection(): void + { + foreach ($this->phpSheet->getProtectedCells() as $range => $password) { + // number of ranges, e.g. 'A1:B3 C20:D25' + $cellRanges = explode(' ', $range); + $cref = count($cellRanges); + + $recordData = pack( + 'vvVVvCVvVv', + 0x0868, + 0x00, + 0x0000, + 0x0000, + 0x02, + 0x0, + 0x0000, + $cref, + 0x0000, + 0x00 + ); + + foreach ($cellRanges as $cellRange) { + $recordData .= $this->writeBIFF8CellRangeAddressFixed($cellRange); + } + + // the rgbFeat structure + $recordData .= pack( + 'VV', + 0x0000, + hexdec($password) + ); + + $recordData .= StringHelper::UTF8toBIFF8UnicodeLong('p' . md5($recordData)); + + $length = strlen($recordData); + + $record = 0x0868; // Record identifier + $header = pack('vv', $record, $length); + $this->append($header . $recordData); + } + } + + /** + * Writes the Excel BIFF PANE record. + * The panes can either be frozen or thawed (unfrozen). + * Frozen panes are specified in terms of an integer number of rows and columns. + * Thawed panes are specified in terms of Excel's units for rows and columns. + */ + private function writePanes(): void + { + if (!$this->phpSheet->getFreezePane()) { + // thaw panes + return; + } + + [$column, $row] = Coordinate::indexesFromString($this->phpSheet->getFreezePane() ?? ''); + $x = $column - 1; + $y = $row - 1; + + [$leftMostColumn, $topRow] = Coordinate::indexesFromString($this->phpSheet->getTopLeftCell()); + //Coordinates are zero-based in xls files + $rwTop = $topRow - 1; + $colLeft = $leftMostColumn - 1; + + $record = 0x0041; // Record identifier + $length = 0x000A; // Number of bytes to follow + + // Determine which pane should be active. There is also the undocumented + // option to override this should it be necessary: may be removed later. + $pnnAct = null; + if ($x != 0 && $y != 0) { + $pnnAct = 0; // Bottom right + } + if ($x != 0 && $y == 0) { + $pnnAct = 1; // Top right + } + if ($x == 0 && $y != 0) { + $pnnAct = 2; // Bottom left + } + if ($x == 0 && $y == 0) { + $pnnAct = 3; // Top left + } + + $this->activePane = $pnnAct; // Used in writeSelection + + $header = pack('vv', $record, $length); + $data = pack('vvvvv', $x, $y, $rwTop, $colLeft, $pnnAct); + $this->append($header . $data); + } + + /** + * Store the page setup SETUP BIFF record. + */ + private function writeSetup(): void + { + $record = 0x00A1; // Record identifier + $length = 0x0022; // Number of bytes to follow + + $iPaperSize = $this->phpSheet->getPageSetup()->getPaperSize(); // Paper size + $iScale = $this->phpSheet->getPageSetup()->getScale() ?: 100; // Print scaling factor + + $iPageStart = 0x01; // Starting page number + $iFitWidth = (int) $this->phpSheet->getPageSetup()->getFitToWidth(); // Fit to number of pages wide + $iFitHeight = (int) $this->phpSheet->getPageSetup()->getFitToHeight(); // Fit to number of pages high + $grbit = 0x00; // Option flags + $iRes = 0x0258; // Print resolution + $iVRes = 0x0258; // Vertical print resolution + + $numHdr = $this->phpSheet->getPageMargins()->getHeader(); // Header Margin + + $numFtr = $this->phpSheet->getPageMargins()->getFooter(); // Footer Margin + $iCopies = 0x01; // Number of copies + + // Order of printing pages + $fLeftToRight = $this->phpSheet->getPageSetup()->getPageOrder() === PageSetup::PAGEORDER_DOWN_THEN_OVER + ? 0x1 : 0x0; + // Page orientation + $fLandscape = ($this->phpSheet->getPageSetup()->getOrientation() == PageSetup::ORIENTATION_LANDSCAPE) + ? 0x0 : 0x1; + + $fNoPls = 0x0; // Setup not read from printer + $fNoColor = 0x0; // Print black and white + $fDraft = 0x0; // Print draft quality + $fNotes = 0x0; // Print notes + $fNoOrient = 0x0; // Orientation not set + $fUsePage = 0x0; // Use custom starting page + + $grbit = $fLeftToRight; + $grbit |= $fLandscape << 1; + $grbit |= $fNoPls << 2; + $grbit |= $fNoColor << 3; + $grbit |= $fDraft << 4; + $grbit |= $fNotes << 5; + $grbit |= $fNoOrient << 6; + $grbit |= $fUsePage << 7; + + $numHdr = pack('d', $numHdr); + $numFtr = pack('d', $numFtr); + if (self::getByteOrder()) { // if it's Big Endian + $numHdr = strrev($numHdr); + $numFtr = strrev($numFtr); + } + + $header = pack('vv', $record, $length); + $data1 = pack('vvvvvvvv', $iPaperSize, $iScale, $iPageStart, $iFitWidth, $iFitHeight, $grbit, $iRes, $iVRes); + $data2 = $numHdr . $numFtr; + $data3 = pack('v', $iCopies); + $this->append($header . $data1 . $data2 . $data3); + } + + /** + * Store the header caption BIFF record. + */ + private function writeHeader(): void + { + $record = 0x0014; // Record identifier + + /* removing for now + // need to fix character count (multibyte!) + if (strlen($this->phpSheet->getHeaderFooter()->getOddHeader()) <= 255) { + $str = $this->phpSheet->getHeaderFooter()->getOddHeader(); // header string + } else { + $str = ''; + } + */ + + $recordData = StringHelper::UTF8toBIFF8UnicodeLong($this->phpSheet->getHeaderFooter()->getOddHeader()); + $length = strlen($recordData); + + $header = pack('vv', $record, $length); + + $this->append($header . $recordData); + } + + /** + * Store the footer caption BIFF record. + */ + private function writeFooter(): void + { + $record = 0x0015; // Record identifier + + /* removing for now + // need to fix character count (multibyte!) + if (strlen($this->phpSheet->getHeaderFooter()->getOddFooter()) <= 255) { + $str = $this->phpSheet->getHeaderFooter()->getOddFooter(); + } else { + $str = ''; + } + */ + + $recordData = StringHelper::UTF8toBIFF8UnicodeLong($this->phpSheet->getHeaderFooter()->getOddFooter()); + $length = strlen($recordData); + + $header = pack('vv', $record, $length); + + $this->append($header . $recordData); + } + + /** + * Store the horizontal centering HCENTER BIFF record. + */ + private function writeHcenter(): void + { + $record = 0x0083; // Record identifier + $length = 0x0002; // Bytes to follow + + $fHCenter = $this->phpSheet->getPageSetup()->getHorizontalCentered() ? 1 : 0; // Horizontal centering + + $header = pack('vv', $record, $length); + $data = pack('v', $fHCenter); + + $this->append($header . $data); + } + + /** + * Store the vertical centering VCENTER BIFF record. + */ + private function writeVcenter(): void + { + $record = 0x0084; // Record identifier + $length = 0x0002; // Bytes to follow + + $fVCenter = $this->phpSheet->getPageSetup()->getVerticalCentered() ? 1 : 0; // Horizontal centering + + $header = pack('vv', $record, $length); + $data = pack('v', $fVCenter); + $this->append($header . $data); + } + + /** + * Store the LEFTMARGIN BIFF record. + */ + private function writeMarginLeft(): void + { + $record = 0x0026; // Record identifier + $length = 0x0008; // Bytes to follow + + $margin = $this->phpSheet->getPageMargins()->getLeft(); // Margin in inches + + $header = pack('vv', $record, $length); + $data = pack('d', $margin); + if (self::getByteOrder()) { // if it's Big Endian + $data = strrev($data); + } + + $this->append($header . $data); + } + + /** + * Store the RIGHTMARGIN BIFF record. + */ + private function writeMarginRight(): void + { + $record = 0x0027; // Record identifier + $length = 0x0008; // Bytes to follow + + $margin = $this->phpSheet->getPageMargins()->getRight(); // Margin in inches + + $header = pack('vv', $record, $length); + $data = pack('d', $margin); + if (self::getByteOrder()) { // if it's Big Endian + $data = strrev($data); + } + + $this->append($header . $data); + } + + /** + * Store the TOPMARGIN BIFF record. + */ + private function writeMarginTop(): void + { + $record = 0x0028; // Record identifier + $length = 0x0008; // Bytes to follow + + $margin = $this->phpSheet->getPageMargins()->getTop(); // Margin in inches + + $header = pack('vv', $record, $length); + $data = pack('d', $margin); + if (self::getByteOrder()) { // if it's Big Endian + $data = strrev($data); + } + + $this->append($header . $data); + } + + /** + * Store the BOTTOMMARGIN BIFF record. + */ + private function writeMarginBottom(): void + { + $record = 0x0029; // Record identifier + $length = 0x0008; // Bytes to follow + + $margin = $this->phpSheet->getPageMargins()->getBottom(); // Margin in inches + + $header = pack('vv', $record, $length); + $data = pack('d', $margin); + if (self::getByteOrder()) { // if it's Big Endian + $data = strrev($data); + } + + $this->append($header . $data); + } + + /** + * Write the PRINTHEADERS BIFF record. + */ + private function writePrintHeaders(): void + { + $record = 0x002a; // Record identifier + $length = 0x0002; // Bytes to follow + + $fPrintRwCol = $this->printHeaders; // Boolean flag + + $header = pack('vv', $record, $length); + $data = pack('v', $fPrintRwCol); + $this->append($header . $data); + } + + /** + * Write the PRINTGRIDLINES BIFF record. Must be used in conjunction with the + * GRIDSET record. + */ + private function writePrintGridlines(): void + { + $record = 0x002b; // Record identifier + $length = 0x0002; // Bytes to follow + + $fPrintGrid = $this->phpSheet->getPrintGridlines() ? 1 : 0; // Boolean flag + + $header = pack('vv', $record, $length); + $data = pack('v', $fPrintGrid); + $this->append($header . $data); + } + + /** + * Write the GRIDSET BIFF record. Must be used in conjunction with the + * PRINTGRIDLINES record. + */ + private function writeGridset(): void + { + $record = 0x0082; // Record identifier + $length = 0x0002; // Bytes to follow + + $fGridSet = !$this->phpSheet->getPrintGridlines(); // Boolean flag + + $header = pack('vv', $record, $length); + $data = pack('v', $fGridSet); + $this->append($header . $data); + } + + /** + * Write the AUTOFILTERINFO BIFF record. This is used to configure the number of autofilter select used in the sheet. + */ + private function writeAutoFilterInfo(): void + { + $record = 0x009D; // Record identifier + $length = 0x0002; // Bytes to follow + + $rangeBounds = Coordinate::rangeBoundaries($this->phpSheet->getAutoFilter()->getRange()); + $iNumFilters = 1 + $rangeBounds[1][0] - $rangeBounds[0][0]; + + $header = pack('vv', $record, $length); + $data = pack('v', $iNumFilters); + $this->append($header . $data); + } + + /** + * Write the GUTS BIFF record. This is used to configure the gutter margins + * where Excel outline symbols are displayed. The visibility of the gutters is + * controlled by a flag in WSBOOL. + * + * @see writeWsbool() + */ + private function writeGuts(): void + { + $record = 0x0080; // Record identifier + $length = 0x0008; // Bytes to follow + + $dxRwGut = 0x0000; // Size of row gutter + $dxColGut = 0x0000; // Size of col gutter + + // determine maximum row outline level + $maxRowOutlineLevel = 0; + foreach ($this->phpSheet->getRowDimensions() as $rowDimension) { + $maxRowOutlineLevel = max($maxRowOutlineLevel, $rowDimension->getOutlineLevel()); + } + + $col_level = 0; + + // Calculate the maximum column outline level. The equivalent calculation + // for the row outline level is carried out in writeRow(). + $colcount = count($this->columnInfo); + for ($i = 0; $i < $colcount; ++$i) { + $col_level = max($this->columnInfo[$i][5], $col_level); + } + + // Set the limits for the outline levels (0 <= x <= 7). + $col_level = max(0, min($col_level, 7)); + + // The displayed level is one greater than the max outline levels + if ($maxRowOutlineLevel) { + ++$maxRowOutlineLevel; + } + if ($col_level) { + ++$col_level; + } + + $header = pack('vv', $record, $length); + $data = pack('vvvv', $dxRwGut, $dxColGut, $maxRowOutlineLevel, $col_level); + + $this->append($header . $data); + } + + /** + * Write the WSBOOL BIFF record, mainly for fit-to-page. Used in conjunction + * with the SETUP record. + */ + private function writeWsbool(): void + { + $record = 0x0081; // Record identifier + $length = 0x0002; // Bytes to follow + $grbit = 0x0000; + + // The only option that is of interest is the flag for fit to page. So we + // set all the options in one go. + // + // Set the option flags + $grbit |= 0x0001; // Auto page breaks visible + if ($this->outlineStyle) { + $grbit |= 0x0020; // Auto outline styles + } + if ($this->phpSheet->getShowSummaryBelow()) { + $grbit |= 0x0040; // Outline summary below + } + if ($this->phpSheet->getShowSummaryRight()) { + $grbit |= 0x0080; // Outline summary right + } + if ($this->phpSheet->getPageSetup()->getFitToPage()) { + $grbit |= 0x0100; // Page setup fit to page + } + if ($this->outlineOn) { + $grbit |= 0x0400; // Outline symbols displayed + } + + $header = pack('vv', $record, $length); + $data = pack('v', $grbit); + $this->append($header . $data); + } + + /** + * Write the HORIZONTALPAGEBREAKS and VERTICALPAGEBREAKS BIFF records. + */ + private function writeBreaks(): void + { + // initialize + $vbreaks = []; + $hbreaks = []; + + foreach ($this->phpSheet->getBreaks() as $cell => $breakType) { + // Fetch coordinates + $coordinates = Coordinate::coordinateFromString($cell); + + // Decide what to do by the type of break + switch ($breakType) { + case \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::BREAK_COLUMN: + // Add to list of vertical breaks + $vbreaks[] = Coordinate::columnIndexFromString($coordinates[0]) - 1; + + break; + case \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::BREAK_ROW: + // Add to list of horizontal breaks + $hbreaks[] = $coordinates[1]; + + break; + case \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::BREAK_NONE: + default: + // Nothing to do + break; + } + } + + //horizontal page breaks + if (!empty($hbreaks)) { + // Sort and filter array of page breaks + sort($hbreaks, SORT_NUMERIC); + if ($hbreaks[0] == 0) { // don't use first break if it's 0 + array_shift($hbreaks); + } + + $record = 0x001b; // Record identifier + $cbrk = count($hbreaks); // Number of page breaks + $length = 2 + 6 * $cbrk; // Bytes to follow + + $header = pack('vv', $record, $length); + $data = pack('v', $cbrk); + + // Append each page break + foreach ($hbreaks as $hbreak) { + $data .= pack('vvv', $hbreak, 0x0000, 0x00ff); + } + + $this->append($header . $data); + } + + // vertical page breaks + if (!empty($vbreaks)) { + // 1000 vertical pagebreaks appears to be an internal Excel 5 limit. + // It is slightly higher in Excel 97/200, approx. 1026 + $vbreaks = array_slice($vbreaks, 0, 1000); + + // Sort and filter array of page breaks + sort($vbreaks, SORT_NUMERIC); + if ($vbreaks[0] == 0) { // don't use first break if it's 0 + array_shift($vbreaks); + } + + $record = 0x001a; // Record identifier + $cbrk = count($vbreaks); // Number of page breaks + $length = 2 + 6 * $cbrk; // Bytes to follow + + $header = pack('vv', $record, $length); + $data = pack('v', $cbrk); + + // Append each page break + foreach ($vbreaks as $vbreak) { + $data .= pack('vvv', $vbreak, 0x0000, 0xffff); + } + + $this->append($header . $data); + } + } + + /** + * Set the Biff PROTECT record to indicate that the worksheet is protected. + */ + private function writeProtect(): void + { + // Exit unless sheet protection has been specified + if (!$this->phpSheet->getProtection()->getSheet()) { + return; + } + + $record = 0x0012; // Record identifier + $length = 0x0002; // Bytes to follow + + $fLock = 1; // Worksheet is protected + + $header = pack('vv', $record, $length); + $data = pack('v', $fLock); + + $this->append($header . $data); + } + + /** + * Write SCENPROTECT. + */ + private function writeScenProtect(): void + { + // Exit if sheet protection is not active + if (!$this->phpSheet->getProtection()->getSheet()) { + return; + } + + // Exit if scenarios are not protected + if (!$this->phpSheet->getProtection()->getScenarios()) { + return; + } + + $record = 0x00DD; // Record identifier + $length = 0x0002; // Bytes to follow + + $header = pack('vv', $record, $length); + $data = pack('v', 1); + + $this->append($header . $data); + } + + /** + * Write OBJECTPROTECT. + */ + private function writeObjectProtect(): void + { + // Exit if sheet protection is not active + if (!$this->phpSheet->getProtection()->getSheet()) { + return; + } + + // Exit if objects are not protected + if (!$this->phpSheet->getProtection()->getObjects()) { + return; + } + + $record = 0x0063; // Record identifier + $length = 0x0002; // Bytes to follow + + $header = pack('vv', $record, $length); + $data = pack('v', 1); + + $this->append($header . $data); + } + + /** + * Write the worksheet PASSWORD record. + */ + private function writePassword(): void + { + // Exit unless sheet protection and password have been specified + if (!$this->phpSheet->getProtection()->getSheet() || !$this->phpSheet->getProtection()->getPassword() || $this->phpSheet->getProtection()->getAlgorithm() !== '') { + return; + } + + $record = 0x0013; // Record identifier + $length = 0x0002; // Bytes to follow + + $wPassword = hexdec($this->phpSheet->getProtection()->getPassword()); // Encoded password + + $header = pack('vv', $record, $length); + $data = pack('v', $wPassword); + + $this->append($header . $data); + } + + /** + * Insert a 24bit bitmap image in a worksheet. + * + * @param int $row The row we are going to insert the bitmap into + * @param int $col The column we are going to insert the bitmap into + * @param mixed $bitmap The bitmap filename or GD-image resource + * @param int $x the horizontal position (offset) of the image inside the cell + * @param int $y the vertical position (offset) of the image inside the cell + * @param float $scale_x The horizontal scale + * @param float $scale_y The vertical scale + */ + public function insertBitmap($row, $col, $bitmap, $x = 0, $y = 0, $scale_x = 1, $scale_y = 1): void + { + $bitmap_array = (is_resource($bitmap) || $bitmap instanceof GdImage + ? $this->processBitmapGd($bitmap) + : $this->processBitmap($bitmap)); + [$width, $height, $size, $data] = $bitmap_array; + + // Scale the frame of the image. + $width *= $scale_x; + $height *= $scale_y; + + // Calculate the vertices of the image and write the OBJ record + $this->positionImage($col, $row, $x, $y, $width, $height); + + // Write the IMDATA record to store the bitmap data + $record = 0x007f; + $length = 8 + $size; + $cf = 0x09; + $env = 0x01; + $lcb = $size; + + $header = pack('vvvvV', $record, $length, $cf, $env, $lcb); + $this->append($header . $data); + } + + /** + * Calculate the vertices that define the position of the image as required by + * the OBJ record. + * + * +------------+------------+ + * | A | B | + * +-----+------------+------------+ + * | |(x1,y1) | | + * | 1 |(A1)._______|______ | + * | | | | | + * | | | | | + * +-----+----| BITMAP |-----+ + * | | | | | + * | 2 | |______________. | + * | | | (B2)| + * | | | (x2,y2)| + * +---- +------------+------------+ + * + * Example of a bitmap that covers some of the area from cell A1 to cell B2. + * + * Based on the width and height of the bitmap we need to calculate 8 vars: + * $col_start, $row_start, $col_end, $row_end, $x1, $y1, $x2, $y2. + * The width and height of the cells are also variable and have to be taken into + * account. + * The values of $col_start and $row_start are passed in from the calling + * function. The values of $col_end and $row_end are calculated by subtracting + * the width and height of the bitmap from the width and height of the + * underlying cells. + * The vertices are expressed as a percentage of the underlying cell width as + * follows (rhs values are in pixels): + * + * x1 = X / W *1024 + * y1 = Y / H *256 + * x2 = (X-1) / W *1024 + * y2 = (Y-1) / H *256 + * + * Where: X is distance from the left side of the underlying cell + * Y is distance from the top of the underlying cell + * W is the width of the cell + * H is the height of the cell + * The SDK incorrectly states that the height should be expressed as a + * percentage of 1024. + * + * @param int $col_start Col containing upper left corner of object + * @param int $row_start Row containing top left corner of object + * @param int $x1 Distance to left side of object + * @param int $y1 Distance to top of object + * @param int $width Width of image frame + * @param int $height Height of image frame + */ + public function positionImage($col_start, $row_start, $x1, $y1, $width, $height): void + { + // Initialise end cell to the same as the start cell + $col_end = $col_start; // Col containing lower right corner of object + $row_end = $row_start; // Row containing bottom right corner of object + + // Zero the specified offset if greater than the cell dimensions + if ($x1 >= Xls::sizeCol($this->phpSheet, Coordinate::stringFromColumnIndex($col_start + 1))) { + $x1 = 0; + } + if ($y1 >= Xls::sizeRow($this->phpSheet, $row_start + 1)) { + $y1 = 0; + } + + $width = $width + $x1 - 1; + $height = $height + $y1 - 1; + + // Subtract the underlying cell widths to find the end cell of the image + while ($width >= Xls::sizeCol($this->phpSheet, Coordinate::stringFromColumnIndex($col_end + 1))) { + $width -= Xls::sizeCol($this->phpSheet, Coordinate::stringFromColumnIndex($col_end + 1)); + ++$col_end; + } + + // Subtract the underlying cell heights to find the end cell of the image + while ($height >= Xls::sizeRow($this->phpSheet, $row_end + 1)) { + $height -= Xls::sizeRow($this->phpSheet, $row_end + 1); + ++$row_end; + } + + // Bitmap isn't allowed to start or finish in a hidden cell, i.e. a cell + // with zero eight or width. + // + if (Xls::sizeCol($this->phpSheet, Coordinate::stringFromColumnIndex($col_start + 1)) == 0) { + return; + } + if (Xls::sizeCol($this->phpSheet, Coordinate::stringFromColumnIndex($col_end + 1)) == 0) { + return; + } + if (Xls::sizeRow($this->phpSheet, $row_start + 1) == 0) { + return; + } + if (Xls::sizeRow($this->phpSheet, $row_end + 1) == 0) { + return; + } + + // Convert the pixel values to the percentage value expected by Excel + $x1 = $x1 / Xls::sizeCol($this->phpSheet, Coordinate::stringFromColumnIndex($col_start + 1)) * 1024; + $y1 = $y1 / Xls::sizeRow($this->phpSheet, $row_start + 1) * 256; + $x2 = $width / Xls::sizeCol($this->phpSheet, Coordinate::stringFromColumnIndex($col_end + 1)) * 1024; // Distance to right side of object + $y2 = $height / Xls::sizeRow($this->phpSheet, $row_end + 1) * 256; // Distance to bottom of object + + $this->writeObjPicture($col_start, $x1, $row_start, $y1, $col_end, $x2, $row_end, $y2); + } + + /** + * Store the OBJ record that precedes an IMDATA record. This could be generalise + * to support other Excel objects. + * + * @param int $colL Column containing upper left corner of object + * @param int $dxL Distance from left side of cell + * @param int $rwT Row containing top left corner of object + * @param int $dyT Distance from top of cell + * @param int $colR Column containing lower right corner of object + * @param int $dxR Distance from right of cell + * @param int $rwB Row containing bottom right corner of object + * @param int $dyB Distance from bottom of cell + */ + private function writeObjPicture($colL, $dxL, $rwT, $dyT, $colR, $dxR, $rwB, $dyB): void + { + $record = 0x005d; // Record identifier + $length = 0x003c; // Bytes to follow + + $cObj = 0x0001; // Count of objects in file (set to 1) + $OT = 0x0008; // Object type. 8 = Picture + $id = 0x0001; // Object ID + $grbit = 0x0614; // Option flags + + $cbMacro = 0x0000; // Length of FMLA structure + $Reserved1 = 0x0000; // Reserved + $Reserved2 = 0x0000; // Reserved + + $icvBack = 0x09; // Background colour + $icvFore = 0x09; // Foreground colour + $fls = 0x00; // Fill pattern + $fAuto = 0x00; // Automatic fill + $icv = 0x08; // Line colour + $lns = 0xff; // Line style + $lnw = 0x01; // Line weight + $fAutoB = 0x00; // Automatic border + $frs = 0x0000; // Frame style + $cf = 0x0009; // Image format, 9 = bitmap + $Reserved3 = 0x0000; // Reserved + $cbPictFmla = 0x0000; // Length of FMLA structure + $Reserved4 = 0x0000; // Reserved + $grbit2 = 0x0001; // Option flags + $Reserved5 = 0x0000; // Reserved + + $header = pack('vv', $record, $length); + $data = pack('V', $cObj); + $data .= pack('v', $OT); + $data .= pack('v', $id); + $data .= pack('v', $grbit); + $data .= pack('v', $colL); + $data .= pack('v', $dxL); + $data .= pack('v', $rwT); + $data .= pack('v', $dyT); + $data .= pack('v', $colR); + $data .= pack('v', $dxR); + $data .= pack('v', $rwB); + $data .= pack('v', $dyB); + $data .= pack('v', $cbMacro); + $data .= pack('V', $Reserved1); + $data .= pack('v', $Reserved2); + $data .= pack('C', $icvBack); + $data .= pack('C', $icvFore); + $data .= pack('C', $fls); + $data .= pack('C', $fAuto); + $data .= pack('C', $icv); + $data .= pack('C', $lns); + $data .= pack('C', $lnw); + $data .= pack('C', $fAutoB); + $data .= pack('v', $frs); + $data .= pack('V', $cf); + $data .= pack('v', $Reserved3); + $data .= pack('v', $cbPictFmla); + $data .= pack('v', $Reserved4); + $data .= pack('v', $grbit2); + $data .= pack('V', $Reserved5); + + $this->append($header . $data); + } + + /** + * Convert a GD-image into the internal format. + * + * @param GdImage|resource $image The image to process + * + * @return array Array with data and properties of the bitmap + */ + public function processBitmapGd($image) + { + $width = imagesx($image); + $height = imagesy($image); + + $data = pack('Vvvvv', 0x000c, $width, $height, 0x01, 0x18); + for ($j = $height; --$j;) { + for ($i = 0; $i < $width; ++$i) { + $color = imagecolorsforindex($image, imagecolorat($image, $i, $j)); + foreach (['red', 'green', 'blue'] as $key) { + $color[$key] = $color[$key] + round((255 - $color[$key]) * $color['alpha'] / 127); + } + $data .= chr($color['blue']) . chr($color['green']) . chr($color['red']); + } + if (3 * $width % 4) { + $data .= str_repeat("\x00", 4 - 3 * $width % 4); + } + } + + return [$width, $height, strlen($data), $data]; + } + + /** + * Convert a 24 bit bitmap into the modified internal format used by Windows. + * This is described in BITMAPCOREHEADER and BITMAPCOREINFO structures in the + * MSDN library. + * + * @param string $bitmap The bitmap to process + * + * @return array Array with data and properties of the bitmap + */ + public function processBitmap($bitmap) + { + // Open file. + $bmp_fd = @fopen($bitmap, 'rb'); + if (!$bmp_fd) { + throw new WriterException("Couldn't import $bitmap"); + } + + // Slurp the file into a string. + $data = fread($bmp_fd, filesize($bitmap)); + + // Check that the file is big enough to be a bitmap. + if (strlen($data) <= 0x36) { + throw new WriterException("$bitmap doesn't contain enough data.\n"); + } + + // The first 2 bytes are used to identify the bitmap. + $identity = unpack('A2ident', $data); + if ($identity['ident'] != 'BM') { + throw new WriterException("$bitmap doesn't appear to be a valid bitmap image.\n"); + } + + // Remove bitmap data: ID. + $data = substr($data, 2); + + // Read and remove the bitmap size. This is more reliable than reading + // the data size at offset 0x22. + // + $size_array = unpack('Vsa', substr($data, 0, 4)); + $size = $size_array['sa']; + $data = substr($data, 4); + $size -= 0x36; // Subtract size of bitmap header. + $size += 0x0C; // Add size of BIFF header. + + // Remove bitmap data: reserved, offset, header length. + $data = substr($data, 12); + + // Read and remove the bitmap width and height. Verify the sizes. + $width_and_height = unpack('V2', substr($data, 0, 8)); + $width = $width_and_height[1]; + $height = $width_and_height[2]; + $data = substr($data, 8); + if ($width > 0xFFFF) { + throw new WriterException("$bitmap: largest image width supported is 65k.\n"); + } + if ($height > 0xFFFF) { + throw new WriterException("$bitmap: largest image height supported is 65k.\n"); + } + + // Read and remove the bitmap planes and bpp data. Verify them. + $planes_and_bitcount = unpack('v2', substr($data, 0, 4)); + $data = substr($data, 4); + if ($planes_and_bitcount[2] != 24) { // Bitcount + throw new WriterException("$bitmap isn't a 24bit true color bitmap.\n"); + } + if ($planes_and_bitcount[1] != 1) { + throw new WriterException("$bitmap: only 1 plane supported in bitmap image.\n"); + } + + // Read and remove the bitmap compression. Verify compression. + $compression = unpack('Vcomp', substr($data, 0, 4)); + $data = substr($data, 4); + + if ($compression['comp'] != 0) { + throw new WriterException("$bitmap: compression not supported in bitmap image.\n"); + } + + // Remove bitmap data: data size, hres, vres, colours, imp. colours. + $data = substr($data, 20); + + // Add the BITMAPCOREHEADER data + $header = pack('Vvvvv', 0x000c, $width, $height, 0x01, 0x18); + $data = $header . $data; + + return [$width, $height, $size, $data]; + } + + /** + * Store the window zoom factor. This should be a reduced fraction but for + * simplicity we will store all fractions with a numerator of 100. + */ + private function writeZoom(): void + { + // If scale is 100 we don't need to write a record + if ($this->phpSheet->getSheetView()->getZoomScale() == 100) { + return; + } + + $record = 0x00A0; // Record identifier + $length = 0x0004; // Bytes to follow + + $header = pack('vv', $record, $length); + $data = pack('vv', $this->phpSheet->getSheetView()->getZoomScale(), 100); + $this->append($header . $data); + } + + /** + * Get Escher object. + */ + public function getEscher(): ?\PhpOffice\PhpSpreadsheet\Shared\Escher + { + return $this->escher; + } + + /** + * Set Escher object. + */ + public function setEscher(?\PhpOffice\PhpSpreadsheet\Shared\Escher $escher): void + { + $this->escher = $escher; + } + + /** + * Write MSODRAWING record. + */ + private function writeMsoDrawing(): void + { + // write the Escher stream if necessary + if (isset($this->escher)) { + $writer = new Escher($this->escher); + $data = $writer->close(); + $spOffsets = $writer->getSpOffsets(); + $spTypes = $writer->getSpTypes(); + // write the neccesary MSODRAWING, OBJ records + + // split the Escher stream + $spOffsets[0] = 0; + $nm = count($spOffsets) - 1; // number of shapes excluding first shape + for ($i = 1; $i <= $nm; ++$i) { + // MSODRAWING record + $record = 0x00EC; // Record identifier + + // chunk of Escher stream for one shape + $dataChunk = substr($data, $spOffsets[$i - 1], $spOffsets[$i] - $spOffsets[$i - 1]); + + $length = strlen($dataChunk); + $header = pack('vv', $record, $length); + + $this->append($header . $dataChunk); + + // OBJ record + $record = 0x005D; // record identifier + $objData = ''; + + // ftCmo + if ($spTypes[$i] == 0x00C9) { + // Add ftCmo (common object data) subobject + $objData .= + pack( + 'vvvvvVVV', + 0x0015, // 0x0015 = ftCmo + 0x0012, // length of ftCmo data + 0x0014, // object type, 0x0014 = filter + $i, // object id number, Excel seems to use 1-based index, local for the sheet + 0x2101, // option flags, 0x2001 is what OpenOffice.org uses + 0, // reserved + 0, // reserved + 0 // reserved + ); + + // Add ftSbs Scroll bar subobject + $objData .= pack('vv', 0x00C, 0x0014); + $objData .= pack('H*', '0000000000000000640001000A00000010000100'); + // Add ftLbsData (List box data) subobject + $objData .= pack('vv', 0x0013, 0x1FEE); + $objData .= pack('H*', '00000000010001030000020008005700'); + } else { + // Add ftCmo (common object data) subobject + $objData .= + pack( + 'vvvvvVVV', + 0x0015, // 0x0015 = ftCmo + 0x0012, // length of ftCmo data + 0x0008, // object type, 0x0008 = picture + $i, // object id number, Excel seems to use 1-based index, local for the sheet + 0x6011, // option flags, 0x6011 is what OpenOffice.org uses + 0, // reserved + 0, // reserved + 0 // reserved + ); + } + + // ftEnd + $objData .= + pack( + 'vv', + 0x0000, // 0x0000 = ftEnd + 0x0000 // length of ftEnd data + ); + + $length = strlen($objData); + $header = pack('vv', $record, $length); + $this->append($header . $objData); + } + } + } + + /** + * Store the DATAVALIDATIONS and DATAVALIDATION records. + */ + private function writeDataValidity(): void + { + // Datavalidation collection + $dataValidationCollection = $this->phpSheet->getDataValidationCollection(); + + // Write data validations? + if (!empty($dataValidationCollection)) { + // DATAVALIDATIONS record + $record = 0x01B2; // Record identifier + $length = 0x0012; // Bytes to follow + + $grbit = 0x0000; // Prompt box at cell, no cached validity data at DV records + $horPos = 0x00000000; // Horizontal position of prompt box, if fixed position + $verPos = 0x00000000; // Vertical position of prompt box, if fixed position + $objId = 0xFFFFFFFF; // Object identifier of drop down arrow object, or -1 if not visible + + $header = pack('vv', $record, $length); + $data = pack('vVVVV', $grbit, $horPos, $verPos, $objId, count($dataValidationCollection)); + $this->append($header . $data); + + // DATAVALIDATION records + $record = 0x01BE; // Record identifier + + foreach ($dataValidationCollection as $cellCoordinate => $dataValidation) { + // options + $options = 0x00000000; + + // data type + $type = CellDataValidation::type($dataValidation); + + $options |= $type << 0; + + // error style + $errorStyle = CellDataValidation::errorStyle($dataValidation); + + $options |= $errorStyle << 4; + + // explicit formula? + if ($type == 0x03 && preg_match('/^\".*\"$/', $dataValidation->getFormula1())) { + $options |= 0x01 << 7; + } + + // empty cells allowed + $options |= $dataValidation->getAllowBlank() << 8; + + // show drop down + $options |= (!$dataValidation->getShowDropDown()) << 9; + + // show input message + $options |= $dataValidation->getShowInputMessage() << 18; + + // show error message + $options |= $dataValidation->getShowErrorMessage() << 19; + + // condition operator + $operator = CellDataValidation::operator($dataValidation); + + $options |= $operator << 20; + + $data = pack('V', $options); + + // prompt title + $promptTitle = $dataValidation->getPromptTitle() !== '' ? + $dataValidation->getPromptTitle() : chr(0); + $data .= StringHelper::UTF8toBIFF8UnicodeLong($promptTitle); + + // error title + $errorTitle = $dataValidation->getErrorTitle() !== '' ? + $dataValidation->getErrorTitle() : chr(0); + $data .= StringHelper::UTF8toBIFF8UnicodeLong($errorTitle); + + // prompt text + $prompt = $dataValidation->getPrompt() !== '' ? + $dataValidation->getPrompt() : chr(0); + $data .= StringHelper::UTF8toBIFF8UnicodeLong($prompt); + + // error text + $error = $dataValidation->getError() !== '' ? + $dataValidation->getError() : chr(0); + $data .= StringHelper::UTF8toBIFF8UnicodeLong($error); + + // formula 1 + try { + $formula1 = $dataValidation->getFormula1(); + if ($type == 0x03) { // list type + $formula1 = str_replace(',', chr(0), $formula1); + } + $this->parser->parse($formula1); + $formula1 = $this->parser->toReversePolish(); + $sz1 = strlen($formula1); + } catch (PhpSpreadsheetException $e) { + $sz1 = 0; + $formula1 = ''; + } + $data .= pack('vv', $sz1, 0x0000); + $data .= $formula1; + + // formula 2 + try { + $formula2 = $dataValidation->getFormula2(); + if ($formula2 === '') { + throw new WriterException('No formula2'); + } + $this->parser->parse($formula2); + $formula2 = $this->parser->toReversePolish(); + $sz2 = strlen($formula2); + } catch (PhpSpreadsheetException $e) { + $sz2 = 0; + $formula2 = ''; + } + $data .= pack('vv', $sz2, 0x0000); + $data .= $formula2; + + // cell range address list + $data .= pack('v', 0x0001); + $data .= $this->writeBIFF8CellRangeAddressFixed($cellCoordinate); + + $length = strlen($data); + $header = pack('vv', $record, $length); + + $this->append($header . $data); + } + } + } + + /** + * Write PLV Record. + */ + private function writePageLayoutView(): void + { + $record = 0x088B; // Record identifier + $length = 0x0010; // Bytes to follow + + $rt = 0x088B; // 2 + $grbitFrt = 0x0000; // 2 + $reserved = 0x0000000000000000; // 8 + $wScalvePLV = $this->phpSheet->getSheetView()->getZoomScale(); // 2 + + // The options flags that comprise $grbit + if ($this->phpSheet->getSheetView()->getView() == SheetView::SHEETVIEW_PAGE_LAYOUT) { + $fPageLayoutView = 1; + } else { + $fPageLayoutView = 0; + } + $fRulerVisible = 0; + $fWhitespaceHidden = 0; + + $grbit = $fPageLayoutView; // 2 + $grbit |= $fRulerVisible << 1; + $grbit |= $fWhitespaceHidden << 3; + + $header = pack('vv', $record, $length); + $data = pack('vvVVvv', $rt, $grbitFrt, 0x00000000, 0x00000000, $wScalvePLV, $grbit); + $this->append($header . $data); + } + + /** + * Write CFRule Record. + */ + private function writeCFRule(Conditional $conditional): void + { + $record = 0x01B1; // Record identifier + $type = null; // Type of the CF + $operatorType = null; // Comparison operator + + if ($conditional->getConditionType() == Conditional::CONDITION_EXPRESSION) { + $type = 0x02; + $operatorType = 0x00; + } elseif ($conditional->getConditionType() == Conditional::CONDITION_CELLIS) { + $type = 0x01; + + switch ($conditional->getOperatorType()) { + case Conditional::OPERATOR_NONE: + $operatorType = 0x00; + + break; + case Conditional::OPERATOR_EQUAL: + $operatorType = 0x03; + + break; + case Conditional::OPERATOR_GREATERTHAN: + $operatorType = 0x05; + + break; + case Conditional::OPERATOR_GREATERTHANOREQUAL: + $operatorType = 0x07; + + break; + case Conditional::OPERATOR_LESSTHAN: + $operatorType = 0x06; + + break; + case Conditional::OPERATOR_LESSTHANOREQUAL: + $operatorType = 0x08; + + break; + case Conditional::OPERATOR_NOTEQUAL: + $operatorType = 0x04; + + break; + case Conditional::OPERATOR_BETWEEN: + $operatorType = 0x01; + + break; + // not OPERATOR_NOTBETWEEN 0x02 + } + } + + // $szValue1 : size of the formula data for first value or formula + // $szValue2 : size of the formula data for second value or formula + $arrConditions = $conditional->getConditions(); + $numConditions = count($arrConditions); + if ($numConditions == 1) { + $szValue1 = ($arrConditions[0] <= 65535 ? 3 : 0x0000); + $szValue2 = 0x0000; + $operand1 = pack('Cv', 0x1E, $arrConditions[0]); + $operand2 = null; + } elseif ($numConditions == 2 && ($conditional->getOperatorType() == Conditional::OPERATOR_BETWEEN)) { + $szValue1 = ($arrConditions[0] <= 65535 ? 3 : 0x0000); + $szValue2 = ($arrConditions[1] <= 65535 ? 3 : 0x0000); + $operand1 = pack('Cv', 0x1E, $arrConditions[0]); + $operand2 = pack('Cv', 0x1E, $arrConditions[1]); + } else { + $szValue1 = 0x0000; + $szValue2 = 0x0000; + $operand1 = null; + $operand2 = null; + } + + // $flags : Option flags + // Alignment + $bAlignHz = ($conditional->getStyle()->getAlignment()->getHorizontal() === null ? 1 : 0); + $bAlignVt = ($conditional->getStyle()->getAlignment()->getVertical() === null ? 1 : 0); + $bAlignWrapTx = ($conditional->getStyle()->getAlignment()->getWrapText() === false ? 1 : 0); + $bTxRotation = ($conditional->getStyle()->getAlignment()->getTextRotation() === null ? 1 : 0); + $bIndent = ($conditional->getStyle()->getAlignment()->getIndent() === 0 ? 1 : 0); + $bShrinkToFit = ($conditional->getStyle()->getAlignment()->getShrinkToFit() === false ? 1 : 0); + if ($bAlignHz == 0 || $bAlignVt == 0 || $bAlignWrapTx == 0 || $bTxRotation == 0 || $bIndent == 0 || $bShrinkToFit == 0) { + $bFormatAlign = 1; + } else { + $bFormatAlign = 0; + } + // Protection + $bProtLocked = ($conditional->getStyle()->getProtection()->getLocked() == null ? 1 : 0); + $bProtHidden = ($conditional->getStyle()->getProtection()->getHidden() == null ? 1 : 0); + if ($bProtLocked == 0 || $bProtHidden == 0) { + $bFormatProt = 1; + } else { + $bFormatProt = 0; + } + // Border + $bBorderLeft = ($conditional->getStyle()->getBorders()->getLeft()->getColor()->getARGB() == Color::COLOR_BLACK + && $conditional->getStyle()->getBorders()->getLeft()->getBorderStyle() == Border::BORDER_NONE ? 1 : 0); + $bBorderRight = ($conditional->getStyle()->getBorders()->getRight()->getColor()->getARGB() == Color::COLOR_BLACK + && $conditional->getStyle()->getBorders()->getRight()->getBorderStyle() == Border::BORDER_NONE ? 1 : 0); + $bBorderTop = ($conditional->getStyle()->getBorders()->getTop()->getColor()->getARGB() == Color::COLOR_BLACK + && $conditional->getStyle()->getBorders()->getTop()->getBorderStyle() == Border::BORDER_NONE ? 1 : 0); + $bBorderBottom = ($conditional->getStyle()->getBorders()->getBottom()->getColor()->getARGB() == Color::COLOR_BLACK + && $conditional->getStyle()->getBorders()->getBottom()->getBorderStyle() == Border::BORDER_NONE ? 1 : 0); + if ($bBorderLeft == 0 || $bBorderRight == 0 || $bBorderTop == 0 || $bBorderBottom == 0) { + $bFormatBorder = 1; + } else { + $bFormatBorder = 0; + } + // Pattern + $bFillStyle = ($conditional->getStyle()->getFill()->getFillType() === null ? 0 : 1); + $bFillColor = ($conditional->getStyle()->getFill()->getStartColor()->getARGB() == null ? 0 : 1); + $bFillColorBg = ($conditional->getStyle()->getFill()->getEndColor()->getARGB() == null ? 0 : 1); + if ($bFillStyle == 0 || $bFillColor == 0 || $bFillColorBg == 0) { + $bFormatFill = 1; + } else { + $bFormatFill = 0; + } + // Font + if ( + $conditional->getStyle()->getFont()->getName() !== null + || $conditional->getStyle()->getFont()->getSize() !== null + || $conditional->getStyle()->getFont()->getBold() !== null + || $conditional->getStyle()->getFont()->getItalic() !== null + || $conditional->getStyle()->getFont()->getSuperscript() !== null + || $conditional->getStyle()->getFont()->getSubscript() !== null + || $conditional->getStyle()->getFont()->getUnderline() !== null + || $conditional->getStyle()->getFont()->getStrikethrough() !== null + || $conditional->getStyle()->getFont()->getColor()->getARGB() != null + ) { + $bFormatFont = 1; + } else { + $bFormatFont = 0; + } + // Alignment + $flags = 0; + $flags |= (1 == $bAlignHz ? 0x00000001 : 0); + $flags |= (1 == $bAlignVt ? 0x00000002 : 0); + $flags |= (1 == $bAlignWrapTx ? 0x00000004 : 0); + $flags |= (1 == $bTxRotation ? 0x00000008 : 0); + // Justify last line flag + $flags |= (1 == 1 ? 0x00000010 : 0); + $flags |= (1 == $bIndent ? 0x00000020 : 0); + $flags |= (1 == $bShrinkToFit ? 0x00000040 : 0); + // Default + $flags |= (1 == 1 ? 0x00000080 : 0); + // Protection + $flags |= (1 == $bProtLocked ? 0x00000100 : 0); + $flags |= (1 == $bProtHidden ? 0x00000200 : 0); + // Border + $flags |= (1 == $bBorderLeft ? 0x00000400 : 0); + $flags |= (1 == $bBorderRight ? 0x00000800 : 0); + $flags |= (1 == $bBorderTop ? 0x00001000 : 0); + $flags |= (1 == $bBorderBottom ? 0x00002000 : 0); + $flags |= (1 == 1 ? 0x00004000 : 0); // Top left to Bottom right border + $flags |= (1 == 1 ? 0x00008000 : 0); // Bottom left to Top right border + // Pattern + $flags |= (1 == $bFillStyle ? 0x00010000 : 0); + $flags |= (1 == $bFillColor ? 0x00020000 : 0); + $flags |= (1 == $bFillColorBg ? 0x00040000 : 0); + $flags |= (1 == 1 ? 0x00380000 : 0); + // Font + $flags |= (1 == $bFormatFont ? 0x04000000 : 0); + // Alignment: + $flags |= (1 == $bFormatAlign ? 0x08000000 : 0); + // Border + $flags |= (1 == $bFormatBorder ? 0x10000000 : 0); + // Pattern + $flags |= (1 == $bFormatFill ? 0x20000000 : 0); + // Protection + $flags |= (1 == $bFormatProt ? 0x40000000 : 0); + // Text direction + $flags |= (1 == 0 ? 0x80000000 : 0); + + $dataBlockFont = null; + $dataBlockAlign = null; + $dataBlockBorder = null; + $dataBlockFill = null; + + // Data Blocks + if ($bFormatFont == 1) { + // Font Name + if ($conditional->getStyle()->getFont()->getName() === null) { + $dataBlockFont = pack('VVVVVVVV', 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000); + $dataBlockFont .= pack('VVVVVVVV', 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000); + } else { + $dataBlockFont = StringHelper::UTF8toBIFF8UnicodeLong($conditional->getStyle()->getFont()->getName()); + } + // Font Size + if ($conditional->getStyle()->getFont()->getSize() === null) { + $dataBlockFont .= pack('V', 20 * 11); + } else { + $dataBlockFont .= pack('V', 20 * $conditional->getStyle()->getFont()->getSize()); + } + // Font Options + $dataBlockFont .= pack('V', 0); + // Font weight + if ($conditional->getStyle()->getFont()->getBold() === true) { + $dataBlockFont .= pack('v', 0x02BC); + } else { + $dataBlockFont .= pack('v', 0x0190); + } + // Escapement type + if ($conditional->getStyle()->getFont()->getSubscript() === true) { + $dataBlockFont .= pack('v', 0x02); + $fontEscapement = 0; + } elseif ($conditional->getStyle()->getFont()->getSuperscript() === true) { + $dataBlockFont .= pack('v', 0x01); + $fontEscapement = 0; + } else { + $dataBlockFont .= pack('v', 0x00); + $fontEscapement = 1; + } + // Underline type + switch ($conditional->getStyle()->getFont()->getUnderline()) { + case \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_NONE: + $dataBlockFont .= pack('C', 0x00); + $fontUnderline = 0; + + break; + case \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_DOUBLE: + $dataBlockFont .= pack('C', 0x02); + $fontUnderline = 0; + + break; + case \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_DOUBLEACCOUNTING: + $dataBlockFont .= pack('C', 0x22); + $fontUnderline = 0; + + break; + case \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_SINGLE: + $dataBlockFont .= pack('C', 0x01); + $fontUnderline = 0; + + break; + case \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_SINGLEACCOUNTING: + $dataBlockFont .= pack('C', 0x21); + $fontUnderline = 0; + + break; + default: + $dataBlockFont .= pack('C', 0x00); + $fontUnderline = 1; + + break; + } + // Not used (3) + $dataBlockFont .= pack('vC', 0x0000, 0x00); + // Font color index + $colorIdx = Style\ColorMap::lookup($conditional->getStyle()->getFont()->getColor(), 0x00); + + $dataBlockFont .= pack('V', $colorIdx); + // Not used (4) + $dataBlockFont .= pack('V', 0x00000000); + // Options flags for modified font attributes + $optionsFlags = 0; + $optionsFlagsBold = ($conditional->getStyle()->getFont()->getBold() === null ? 1 : 0); + $optionsFlags |= (1 == $optionsFlagsBold ? 0x00000002 : 0); + $optionsFlags |= (1 == 1 ? 0x00000008 : 0); + $optionsFlags |= (1 == 1 ? 0x00000010 : 0); + $optionsFlags |= (1 == 0 ? 0x00000020 : 0); + $optionsFlags |= (1 == 1 ? 0x00000080 : 0); + $dataBlockFont .= pack('V', $optionsFlags); + // Escapement type + $dataBlockFont .= pack('V', $fontEscapement); + // Underline type + $dataBlockFont .= pack('V', $fontUnderline); + // Always + $dataBlockFont .= pack('V', 0x00000000); + // Always + $dataBlockFont .= pack('V', 0x00000000); + // Not used (8) + $dataBlockFont .= pack('VV', 0x00000000, 0x00000000); + // Always + $dataBlockFont .= pack('v', 0x0001); + } + if ($bFormatAlign === 1) { + // Alignment and text break + $blockAlign = Style\CellAlignment::horizontal($conditional->getStyle()->getAlignment()); + $blockAlign |= Style\CellAlignment::wrap($conditional->getStyle()->getAlignment()) << 3; + $blockAlign |= Style\CellAlignment::vertical($conditional->getStyle()->getAlignment()) << 4; + $blockAlign |= 0 << 7; + + // Text rotation angle + $blockRotation = $conditional->getStyle()->getAlignment()->getTextRotation(); + + // Indentation + $blockIndent = $conditional->getStyle()->getAlignment()->getIndent(); + if ($conditional->getStyle()->getAlignment()->getShrinkToFit() === true) { + $blockIndent |= 1 << 4; + } else { + $blockIndent |= 0 << 4; + } + $blockIndent |= 0 << 6; + + // Relative indentation + $blockIndentRelative = 255; + + $dataBlockAlign = pack('CCvvv', $blockAlign, $blockRotation, $blockIndent, $blockIndentRelative, 0x0000); + } + if ($bFormatBorder === 1) { + $blockLineStyle = Style\CellBorder::style($conditional->getStyle()->getBorders()->getLeft()); + $blockLineStyle |= Style\CellBorder::style($conditional->getStyle()->getBorders()->getRight()) << 4; + $blockLineStyle |= Style\CellBorder::style($conditional->getStyle()->getBorders()->getTop()) << 8; + $blockLineStyle |= Style\CellBorder::style($conditional->getStyle()->getBorders()->getBottom()) << 12; + + // TODO writeCFRule() => $blockLineStyle => Index Color for left line + // TODO writeCFRule() => $blockLineStyle => Index Color for right line + // TODO writeCFRule() => $blockLineStyle => Top-left to bottom-right on/off + // TODO writeCFRule() => $blockLineStyle => Bottom-left to top-right on/off + $blockColor = 0; + // TODO writeCFRule() => $blockColor => Index Color for top line + // TODO writeCFRule() => $blockColor => Index Color for bottom line + // TODO writeCFRule() => $blockColor => Index Color for diagonal line + $blockColor |= Style\CellBorder::style($conditional->getStyle()->getBorders()->getDiagonal()) << 21; + $dataBlockBorder = pack('vv', $blockLineStyle, $blockColor); + } + if ($bFormatFill === 1) { + // Fill Pattern Style + $blockFillPatternStyle = Style\CellFill::style($conditional->getStyle()->getFill()); + // Background Color + $colorIdxBg = Style\ColorMap::lookup($conditional->getStyle()->getFill()->getStartColor(), 0x41); + // Foreground Color + $colorIdxFg = Style\ColorMap::lookup($conditional->getStyle()->getFill()->getEndColor(), 0x40); + + $dataBlockFill = pack('v', $blockFillPatternStyle); + $dataBlockFill .= pack('v', $colorIdxFg | ($colorIdxBg << 7)); + } + + $data = pack('CCvvVv', $type, $operatorType, $szValue1, $szValue2, $flags, 0x0000); + if ($bFormatFont === 1) { // Block Formatting : OK + $data .= $dataBlockFont; + } + if ($bFormatAlign === 1) { + $data .= $dataBlockAlign; + } + if ($bFormatBorder === 1) { + $data .= $dataBlockBorder; + } + if ($bFormatFill === 1) { // Block Formatting : OK + $data .= $dataBlockFill; + } + if ($bFormatProt == 1) { + $data .= $this->getDataBlockProtection($conditional); + } + if ($operand1 !== null) { + $data .= $operand1; + } + if ($operand2 !== null) { + $data .= $operand2; + } + $header = pack('vv', $record, strlen($data)); + $this->append($header . $data); + } + + /** + * Write CFHeader record. + */ + private function writeCFHeader(): void + { + $record = 0x01B0; // Record identifier + $length = 0x0016; // Bytes to follow + + $numColumnMin = null; + $numColumnMax = null; + $numRowMin = null; + $numRowMax = null; + $arrConditional = []; + foreach ($this->phpSheet->getConditionalStylesCollection() as $cellCoordinate => $conditionalStyles) { + foreach ($conditionalStyles as $conditional) { + if ( + $conditional->getConditionType() == Conditional::CONDITION_EXPRESSION || + $conditional->getConditionType() == Conditional::CONDITION_CELLIS + ) { + if (!in_array($conditional->getHashCode(), $arrConditional)) { + $arrConditional[] = $conditional->getHashCode(); + } + // Cells + $rangeCoordinates = Coordinate::rangeBoundaries($cellCoordinate); + if ($numColumnMin === null || ($numColumnMin > $rangeCoordinates[0][0])) { + $numColumnMin = $rangeCoordinates[0][0]; + } + if ($numColumnMax === null || ($numColumnMax < $rangeCoordinates[1][0])) { + $numColumnMax = $rangeCoordinates[1][0]; + } + if ($numRowMin === null || ($numRowMin > $rangeCoordinates[0][1])) { + $numRowMin = (int) $rangeCoordinates[0][1]; + } + if ($numRowMax === null || ($numRowMax < $rangeCoordinates[1][1])) { + $numRowMax = (int) $rangeCoordinates[1][1]; + } + } + } + } + $needRedraw = 1; + $cellRange = pack('vvvv', $numRowMin - 1, $numRowMax - 1, $numColumnMin - 1, $numColumnMax - 1); + + $header = pack('vv', $record, $length); + $data = pack('vv', count($arrConditional), $needRedraw); + $data .= $cellRange; + $data .= pack('v', 0x0001); + $data .= $cellRange; + $this->append($header . $data); + } + + private function getDataBlockProtection(Conditional $conditional): int + { + $dataBlockProtection = 0; + if ($conditional->getStyle()->getProtection()->getLocked() == Protection::PROTECTION_PROTECTED) { + $dataBlockProtection = 1; + } + if ($conditional->getStyle()->getProtection()->getHidden() == Protection::PROTECTION_PROTECTED) { + $dataBlockProtection = 1 << 1; + } + + return $dataBlockProtection; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Xf.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Xf.php new file mode 100644 index 0000000..b2dbc67 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Xf.php @@ -0,0 +1,418 @@ + +// * +// * The majority of this is _NOT_ my code. I simply ported it from the +// * PERL Spreadsheet::WriteExcel module. +// * +// * The author of the Spreadsheet::WriteExcel module is John McNamara +// * +// * +// * I _DO_ maintain this code, and John McNamara has nothing to do with the +// * porting of this code to PHP. Any questions directly related to this +// * class library should be directed to me. +// * +// * License Information: +// * +// * Spreadsheet_Excel_Writer: A library for generating Excel Spreadsheets +// * Copyright (c) 2002-2003 Xavier Noguer xnoguer@rezebra.com +// * +// * This library is free software; you can redistribute it and/or +// * modify it under the terms of the GNU Lesser General Public +// * License as published by the Free Software Foundation; either +// * version 2.1 of the License, or (at your option) any later version. +// * +// * This library is distributed in the hope that it will be useful, +// * but WITHOUT ANY WARRANTY; without even the implied warranty of +// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// * Lesser General Public License for more details. +// * +// * You should have received a copy of the GNU Lesser General Public +// * License along with this library; if not, write to the Free Software +// * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// */ +class Xf +{ + /** + * Style XF or a cell XF ? + * + * @var bool + */ + private $isStyleXf; + + /** + * Index to the FONT record. Index 4 does not exist. + * + * @var int + */ + private $fontIndex; + + /** + * An index (2 bytes) to a FORMAT record (number format). + * + * @var int + */ + private $numberFormatIndex; + + /** + * 1 bit, apparently not used. + * + * @var int + */ + private $textJustLast; + + /** + * The cell's foreground color. + * + * @var int + */ + private $foregroundColor; + + /** + * The cell's background color. + * + * @var int + */ + private $backgroundColor; + + /** + * Color of the bottom border of the cell. + * + * @var int + */ + private $bottomBorderColor; + + /** + * Color of the top border of the cell. + * + * @var int + */ + private $topBorderColor; + + /** + * Color of the left border of the cell. + * + * @var int + */ + private $leftBorderColor; + + /** + * Color of the right border of the cell. + * + * @var int + */ + private $rightBorderColor; + + /** + * @var int + */ + private $diag; + + /** + * @var int + */ + private $diagColor; + + /** + * @var Style + */ + private $style; + + /** + * Constructor. + * + * @param Style $style The XF format + */ + public function __construct(Style $style) + { + $this->isStyleXf = false; + $this->fontIndex = 0; + + $this->numberFormatIndex = 0; + + $this->textJustLast = 0; + + $this->foregroundColor = 0x40; + $this->backgroundColor = 0x41; + + $this->diag = 0; + + $this->bottomBorderColor = 0x40; + $this->topBorderColor = 0x40; + $this->leftBorderColor = 0x40; + $this->rightBorderColor = 0x40; + $this->diagColor = 0x40; + $this->style = $style; + } + + /** + * Generate an Excel BIFF XF record (style or cell). + * + * @return string The XF record + */ + public function writeXf() + { + // Set the type of the XF record and some of the attributes. + if ($this->isStyleXf) { + $style = 0xFFF5; + } else { + $style = self::mapLocked($this->style->getProtection()->getLocked()); + $style |= self::mapHidden($this->style->getProtection()->getHidden()) << 1; + } + + // Flags to indicate if attributes have been set. + $atr_num = ($this->numberFormatIndex != 0) ? 1 : 0; + $atr_fnt = ($this->fontIndex != 0) ? 1 : 0; + $atr_alc = ((int) $this->style->getAlignment()->getWrapText()) ? 1 : 0; + $atr_bdr = (CellBorder::style($this->style->getBorders()->getBottom()) || + CellBorder::style($this->style->getBorders()->getTop()) || + CellBorder::style($this->style->getBorders()->getLeft()) || + CellBorder::style($this->style->getBorders()->getRight())) ? 1 : 0; + $atr_pat = ($this->foregroundColor != 0x40) ? 1 : 0; + $atr_pat = ($this->backgroundColor != 0x41) ? 1 : $atr_pat; + $atr_pat = CellFill::style($this->style->getFill()) ? 1 : $atr_pat; + $atr_prot = self::mapLocked($this->style->getProtection()->getLocked()) + | self::mapHidden($this->style->getProtection()->getHidden()); + + // Zero the default border colour if the border has not been set. + if (CellBorder::style($this->style->getBorders()->getBottom()) == 0) { + $this->bottomBorderColor = 0; + } + if (CellBorder::style($this->style->getBorders()->getTop()) == 0) { + $this->topBorderColor = 0; + } + if (CellBorder::style($this->style->getBorders()->getRight()) == 0) { + $this->rightBorderColor = 0; + } + if (CellBorder::style($this->style->getBorders()->getLeft()) == 0) { + $this->leftBorderColor = 0; + } + if (CellBorder::style($this->style->getBorders()->getDiagonal()) == 0) { + $this->diagColor = 0; + } + + $record = 0x00E0; // Record identifier + $length = 0x0014; // Number of bytes to follow + + $ifnt = $this->fontIndex; // Index to FONT record + $ifmt = $this->numberFormatIndex; // Index to FORMAT record + + // Alignment + $align = CellAlignment::horizontal($this->style->getAlignment()); + $align |= CellAlignment::wrap($this->style->getAlignment()) << 3; + $align |= CellAlignment::vertical($this->style->getAlignment()) << 4; + $align |= $this->textJustLast << 7; + + $used_attrib = $atr_num << 2; + $used_attrib |= $atr_fnt << 3; + $used_attrib |= $atr_alc << 4; + $used_attrib |= $atr_bdr << 5; + $used_attrib |= $atr_pat << 6; + $used_attrib |= $atr_prot << 7; + + $icv = $this->foregroundColor; // fg and bg pattern colors + $icv |= $this->backgroundColor << 7; + + $border1 = CellBorder::style($this->style->getBorders()->getLeft()); // Border line style and color + $border1 |= CellBorder::style($this->style->getBorders()->getRight()) << 4; + $border1 |= CellBorder::style($this->style->getBorders()->getTop()) << 8; + $border1 |= CellBorder::style($this->style->getBorders()->getBottom()) << 12; + $border1 |= $this->leftBorderColor << 16; + $border1 |= $this->rightBorderColor << 23; + + $diagonalDirection = $this->style->getBorders()->getDiagonalDirection(); + $diag_tl_to_rb = $diagonalDirection == Borders::DIAGONAL_BOTH + || $diagonalDirection == Borders::DIAGONAL_DOWN; + $diag_tr_to_lb = $diagonalDirection == Borders::DIAGONAL_BOTH + || $diagonalDirection == Borders::DIAGONAL_UP; + $border1 |= $diag_tl_to_rb << 30; + $border1 |= $diag_tr_to_lb << 31; + + $border2 = $this->topBorderColor; // Border color + $border2 |= $this->bottomBorderColor << 7; + $border2 |= $this->diagColor << 14; + $border2 |= CellBorder::style($this->style->getBorders()->getDiagonal()) << 21; + $border2 |= CellFill::style($this->style->getFill()) << 26; + + $header = pack('vv', $record, $length); + + //BIFF8 options: identation, shrinkToFit and text direction + $biff8_options = $this->style->getAlignment()->getIndent(); + $biff8_options |= (int) $this->style->getAlignment()->getShrinkToFit() << 4; + + $data = pack('vvvC', $ifnt, $ifmt, $style, $align); + $data .= pack('CCC', self::mapTextRotation($this->style->getAlignment()->getTextRotation()), $biff8_options, $used_attrib); + $data .= pack('VVv', $border1, $border2, $icv); + + return $header . $data; + } + + /** + * Is this a style XF ? + * + * @param bool $value + */ + public function setIsStyleXf($value): void + { + $this->isStyleXf = $value; + } + + /** + * Sets the cell's bottom border color. + * + * @param int $colorIndex Color index + */ + public function setBottomColor($colorIndex): void + { + $this->bottomBorderColor = $colorIndex; + } + + /** + * Sets the cell's top border color. + * + * @param int $colorIndex Color index + */ + public function setTopColor($colorIndex): void + { + $this->topBorderColor = $colorIndex; + } + + /** + * Sets the cell's left border color. + * + * @param int $colorIndex Color index + */ + public function setLeftColor($colorIndex): void + { + $this->leftBorderColor = $colorIndex; + } + + /** + * Sets the cell's right border color. + * + * @param int $colorIndex Color index + */ + public function setRightColor($colorIndex): void + { + $this->rightBorderColor = $colorIndex; + } + + /** + * Sets the cell's diagonal border color. + * + * @param int $colorIndex Color index + */ + public function setDiagColor($colorIndex): void + { + $this->diagColor = $colorIndex; + } + + /** + * Sets the cell's foreground color. + * + * @param int $colorIndex Color index + */ + public function setFgColor($colorIndex): void + { + $this->foregroundColor = $colorIndex; + } + + /** + * Sets the cell's background color. + * + * @param int $colorIndex Color index + */ + public function setBgColor($colorIndex): void + { + $this->backgroundColor = $colorIndex; + } + + /** + * Sets the index to the number format record + * It can be date, time, currency, etc... + * + * @param int $numberFormatIndex Index to format record + */ + public function setNumberFormatIndex($numberFormatIndex): void + { + $this->numberFormatIndex = $numberFormatIndex; + } + + /** + * Set the font index. + * + * @param int $value Font index, note that value 4 does not exist + */ + public function setFontIndex($value): void + { + $this->fontIndex = $value; + } + + /** + * Map to BIFF8 codes for text rotation angle. + * + * @param int $textRotation + * + * @return int + */ + private static function mapTextRotation($textRotation) + { + if ($textRotation >= 0) { + return $textRotation; + } + if ($textRotation == Alignment::TEXTROTATION_STACK_PHPSPREADSHEET) { + return Alignment::TEXTROTATION_STACK_EXCEL; + } + + return 90 - $textRotation; + } + + private const LOCK_ARRAY = [ + Protection::PROTECTION_INHERIT => 1, + Protection::PROTECTION_PROTECTED => 1, + Protection::PROTECTION_UNPROTECTED => 0, + ]; + + /** + * Map locked values. + * + * @param string $locked + * + * @return int + */ + private static function mapLocked($locked) + { + return array_key_exists($locked, self::LOCK_ARRAY) ? self::LOCK_ARRAY[$locked] : 1; + } + + private const HIDDEN_ARRAY = [ + Protection::PROTECTION_INHERIT => 0, + Protection::PROTECTION_PROTECTED => 1, + Protection::PROTECTION_UNPROTECTED => 0, + ]; + + /** + * Map hidden. + * + * @param string $hidden + * + * @return int + */ + private static function mapHidden($hidden) + { + return array_key_exists($hidden, self::HIDDEN_ARRAY) ? self::HIDDEN_ARRAY[$hidden] : 0; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx.php new file mode 100644 index 0000000..8bcdc53 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx.php @@ -0,0 +1,670 @@ + + */ + private $stylesConditionalHashTable; + + /** + * Private unique Style HashTable. + * + * @var HashTable<\PhpOffice\PhpSpreadsheet\Style\Style> + */ + private $styleHashTable; + + /** + * Private unique Fill HashTable. + * + * @var HashTable + */ + private $fillHashTable; + + /** + * Private unique \PhpOffice\PhpSpreadsheet\Style\Font HashTable. + * + * @var HashTable + */ + private $fontHashTable; + + /** + * Private unique Borders HashTable. + * + * @var HashTable + */ + private $bordersHashTable; + + /** + * Private unique NumberFormat HashTable. + * + * @var HashTable + */ + private $numFmtHashTable; + + /** + * Private unique \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\BaseDrawing HashTable. + * + * @var HashTable + */ + private $drawingHashTable; + + /** + * Private handle for zip stream. + * + * @var ZipStream + */ + private $zip; + + /** + * @var Chart + */ + private $writerPartChart; + + /** + * @var Comments + */ + private $writerPartComments; + + /** + * @var ContentTypes + */ + private $writerPartContentTypes; + + /** + * @var DocProps + */ + private $writerPartDocProps; + + /** + * @var Drawing + */ + private $writerPartDrawing; + + /** + * @var Rels + */ + private $writerPartRels; + + /** + * @var RelsRibbon + */ + private $writerPartRelsRibbon; + + /** + * @var RelsVBA + */ + private $writerPartRelsVBA; + + /** + * @var StringTable + */ + private $writerPartStringTable; + + /** + * @var Style + */ + private $writerPartStyle; + + /** + * @var Theme + */ + private $writerPartTheme; + + /** + * @var Workbook + */ + private $writerPartWorkbook; + + /** + * @var Worksheet + */ + private $writerPartWorksheet; + + /** + * Create a new Xlsx Writer. + */ + public function __construct(Spreadsheet $spreadsheet) + { + // Assign PhpSpreadsheet + $this->setSpreadsheet($spreadsheet); + + $this->writerPartChart = new Chart($this); + $this->writerPartComments = new Comments($this); + $this->writerPartContentTypes = new ContentTypes($this); + $this->writerPartDocProps = new DocProps($this); + $this->writerPartDrawing = new Drawing($this); + $this->writerPartRels = new Rels($this); + $this->writerPartRelsRibbon = new RelsRibbon($this); + $this->writerPartRelsVBA = new RelsVBA($this); + $this->writerPartStringTable = new StringTable($this); + $this->writerPartStyle = new Style($this); + $this->writerPartTheme = new Theme($this); + $this->writerPartWorkbook = new Workbook($this); + $this->writerPartWorksheet = new Worksheet($this); + + // Set HashTable variables + // @phpstan-ignore-next-line + $this->bordersHashTable = new HashTable(); + // @phpstan-ignore-next-line + $this->drawingHashTable = new HashTable(); + // @phpstan-ignore-next-line + $this->fillHashTable = new HashTable(); + // @phpstan-ignore-next-line + $this->fontHashTable = new HashTable(); + // @phpstan-ignore-next-line + $this->numFmtHashTable = new HashTable(); + // @phpstan-ignore-next-line + $this->styleHashTable = new HashTable(); + // @phpstan-ignore-next-line + $this->stylesConditionalHashTable = new HashTable(); + } + + public function getWriterPartChart(): Chart + { + return $this->writerPartChart; + } + + public function getWriterPartComments(): Comments + { + return $this->writerPartComments; + } + + public function getWriterPartContentTypes(): ContentTypes + { + return $this->writerPartContentTypes; + } + + public function getWriterPartDocProps(): DocProps + { + return $this->writerPartDocProps; + } + + public function getWriterPartDrawing(): Drawing + { + return $this->writerPartDrawing; + } + + public function getWriterPartRels(): Rels + { + return $this->writerPartRels; + } + + public function getWriterPartRelsRibbon(): RelsRibbon + { + return $this->writerPartRelsRibbon; + } + + public function getWriterPartRelsVBA(): RelsVBA + { + return $this->writerPartRelsVBA; + } + + public function getWriterPartStringTable(): StringTable + { + return $this->writerPartStringTable; + } + + public function getWriterPartStyle(): Style + { + return $this->writerPartStyle; + } + + public function getWriterPartTheme(): Theme + { + return $this->writerPartTheme; + } + + public function getWriterPartWorkbook(): Workbook + { + return $this->writerPartWorkbook; + } + + public function getWriterPartWorksheet(): Worksheet + { + return $this->writerPartWorksheet; + } + + /** + * Save PhpSpreadsheet to file. + * + * @param resource|string $filename + */ + public function save($filename, int $flags = 0): void + { + $this->processFlags($flags); + + // garbage collect + $this->pathNames = []; + $this->spreadSheet->garbageCollect(); + + $saveDebugLog = Calculation::getInstance($this->spreadSheet)->getDebugLog()->getWriteDebugLog(); + Calculation::getInstance($this->spreadSheet)->getDebugLog()->setWriteDebugLog(false); + $saveDateReturnType = Functions::getReturnDateType(); + Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); + + // Create string lookup table + $this->stringTable = []; + for ($i = 0; $i < $this->spreadSheet->getSheetCount(); ++$i) { + $this->stringTable = $this->getWriterPartStringTable()->createStringTable($this->spreadSheet->getSheet($i), $this->stringTable); + } + + // Create styles dictionaries + $this->styleHashTable->addFromSource($this->getWriterPartStyle()->allStyles($this->spreadSheet)); + $this->stylesConditionalHashTable->addFromSource($this->getWriterPartStyle()->allConditionalStyles($this->spreadSheet)); + $this->fillHashTable->addFromSource($this->getWriterPartStyle()->allFills($this->spreadSheet)); + $this->fontHashTable->addFromSource($this->getWriterPartStyle()->allFonts($this->spreadSheet)); + $this->bordersHashTable->addFromSource($this->getWriterPartStyle()->allBorders($this->spreadSheet)); + $this->numFmtHashTable->addFromSource($this->getWriterPartStyle()->allNumberFormats($this->spreadSheet)); + + // Create drawing dictionary + $this->drawingHashTable->addFromSource($this->getWriterPartDrawing()->allDrawings($this->spreadSheet)); + + $zipContent = []; + // Add [Content_Types].xml to ZIP file + $zipContent['[Content_Types].xml'] = $this->getWriterPartContentTypes()->writeContentTypes($this->spreadSheet, $this->includeCharts); + + //if hasMacros, add the vbaProject.bin file, Certificate file(if exists) + if ($this->spreadSheet->hasMacros()) { + $macrosCode = $this->spreadSheet->getMacrosCode(); + if ($macrosCode !== null) { + // we have the code ? + $zipContent['xl/vbaProject.bin'] = $macrosCode; //allways in 'xl', allways named vbaProject.bin + if ($this->spreadSheet->hasMacrosCertificate()) { + //signed macros ? + // Yes : add the certificate file and the related rels file + $zipContent['xl/vbaProjectSignature.bin'] = $this->spreadSheet->getMacrosCertificate(); + $zipContent['xl/_rels/vbaProject.bin.rels'] = $this->getWriterPartRelsVBA()->writeVBARelationships($this->spreadSheet); + } + } + } + //a custom UI in this workbook ? add it ("base" xml and additional objects (pictures) and rels) + if ($this->spreadSheet->hasRibbon()) { + $tmpRibbonTarget = $this->spreadSheet->getRibbonXMLData('target'); + $zipContent[$tmpRibbonTarget] = $this->spreadSheet->getRibbonXMLData('data'); + if ($this->spreadSheet->hasRibbonBinObjects()) { + $tmpRootPath = dirname($tmpRibbonTarget) . '/'; + $ribbonBinObjects = $this->spreadSheet->getRibbonBinObjects('data'); //the files to write + foreach ($ribbonBinObjects as $aPath => $aContent) { + $zipContent[$tmpRootPath . $aPath] = $aContent; + } + //the rels for files + $zipContent[$tmpRootPath . '_rels/' . basename($tmpRibbonTarget) . '.rels'] = $this->getWriterPartRelsRibbon()->writeRibbonRelationships($this->spreadSheet); + } + } + + // Add relationships to ZIP file + $zipContent['_rels/.rels'] = $this->getWriterPartRels()->writeRelationships($this->spreadSheet); + $zipContent['xl/_rels/workbook.xml.rels'] = $this->getWriterPartRels()->writeWorkbookRelationships($this->spreadSheet); + + // Add document properties to ZIP file + $zipContent['docProps/app.xml'] = $this->getWriterPartDocProps()->writeDocPropsApp($this->spreadSheet); + $zipContent['docProps/core.xml'] = $this->getWriterPartDocProps()->writeDocPropsCore($this->spreadSheet); + $customPropertiesPart = $this->getWriterPartDocProps()->writeDocPropsCustom($this->spreadSheet); + if ($customPropertiesPart !== null) { + $zipContent['docProps/custom.xml'] = $customPropertiesPart; + } + + // Add theme to ZIP file + $zipContent['xl/theme/theme1.xml'] = $this->getWriterPartTheme()->writeTheme($this->spreadSheet); + + // Add string table to ZIP file + $zipContent['xl/sharedStrings.xml'] = $this->getWriterPartStringTable()->writeStringTable($this->stringTable); + + // Add styles to ZIP file + $zipContent['xl/styles.xml'] = $this->getWriterPartStyle()->writeStyles($this->spreadSheet); + + // Add workbook to ZIP file + $zipContent['xl/workbook.xml'] = $this->getWriterPartWorkbook()->writeWorkbook($this->spreadSheet, $this->preCalculateFormulas); + + $chartCount = 0; + // Add worksheets + for ($i = 0; $i < $this->spreadSheet->getSheetCount(); ++$i) { + $zipContent['xl/worksheets/sheet' . ($i + 1) . '.xml'] = $this->getWriterPartWorksheet()->writeWorksheet($this->spreadSheet->getSheet($i), $this->stringTable, $this->includeCharts); + if ($this->includeCharts) { + $charts = $this->spreadSheet->getSheet($i)->getChartCollection(); + if (count($charts) > 0) { + foreach ($charts as $chart) { + $zipContent['xl/charts/chart' . ($chartCount + 1) . '.xml'] = $this->getWriterPartChart()->writeChart($chart, $this->preCalculateFormulas); + ++$chartCount; + } + } + } + } + + $chartRef1 = 0; + // Add worksheet relationships (drawings, ...) + for ($i = 0; $i < $this->spreadSheet->getSheetCount(); ++$i) { + // Add relationships + $zipContent['xl/worksheets/_rels/sheet' . ($i + 1) . '.xml.rels'] = $this->getWriterPartRels()->writeWorksheetRelationships($this->spreadSheet->getSheet($i), ($i + 1), $this->includeCharts); + + // Add unparsedLoadedData + $sheetCodeName = $this->spreadSheet->getSheet($i)->getCodeName(); + $unparsedLoadedData = $this->spreadSheet->getUnparsedLoadedData(); + if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['ctrlProps'])) { + foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['ctrlProps'] as $ctrlProp) { + $zipContent[$ctrlProp['filePath']] = $ctrlProp['content']; + } + } + if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['printerSettings'])) { + foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['printerSettings'] as $ctrlProp) { + $zipContent[$ctrlProp['filePath']] = $ctrlProp['content']; + } + } + + $drawings = $this->spreadSheet->getSheet($i)->getDrawingCollection(); + $drawingCount = count($drawings); + if ($this->includeCharts) { + $chartCount = $this->spreadSheet->getSheet($i)->getChartCount(); + } + + // Add drawing and image relationship parts + if (($drawingCount > 0) || ($chartCount > 0)) { + // Drawing relationships + $zipContent['xl/drawings/_rels/drawing' . ($i + 1) . '.xml.rels'] = $this->getWriterPartRels()->writeDrawingRelationships($this->spreadSheet->getSheet($i), $chartRef1, $this->includeCharts); + + // Drawings + $zipContent['xl/drawings/drawing' . ($i + 1) . '.xml'] = $this->getWriterPartDrawing()->writeDrawings($this->spreadSheet->getSheet($i), $this->includeCharts); + } elseif (isset($unparsedLoadedData['sheets'][$sheetCodeName]['drawingAlternateContents'])) { + // Drawings + $zipContent['xl/drawings/drawing' . ($i + 1) . '.xml'] = $this->getWriterPartDrawing()->writeDrawings($this->spreadSheet->getSheet($i), $this->includeCharts); + } + + // Add unparsed drawings + if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['Drawings'])) { + foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['Drawings'] as $relId => $drawingXml) { + $drawingFile = array_search($relId, $unparsedLoadedData['sheets'][$sheetCodeName]['drawingOriginalIds']); + if ($drawingFile !== false) { + $drawingFile = ltrim($drawingFile, '.'); + $zipContent['xl' . $drawingFile] = $drawingXml; + } + } + } + + // Add comment relationship parts + if (count($this->spreadSheet->getSheet($i)->getComments()) > 0) { + // VML Comments + $zipContent['xl/drawings/vmlDrawing' . ($i + 1) . '.vml'] = $this->getWriterPartComments()->writeVMLComments($this->spreadSheet->getSheet($i)); + + // Comments + $zipContent['xl/comments' . ($i + 1) . '.xml'] = $this->getWriterPartComments()->writeComments($this->spreadSheet->getSheet($i)); + } + + // Add unparsed relationship parts + if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['vmlDrawings'])) { + foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['vmlDrawings'] as $vmlDrawing) { + $zipContent[$vmlDrawing['filePath']] = $vmlDrawing['content']; + } + } + + // Add header/footer relationship parts + if (count($this->spreadSheet->getSheet($i)->getHeaderFooter()->getImages()) > 0) { + // VML Drawings + $zipContent['xl/drawings/vmlDrawingHF' . ($i + 1) . '.vml'] = $this->getWriterPartDrawing()->writeVMLHeaderFooterImages($this->spreadSheet->getSheet($i)); + + // VML Drawing relationships + $zipContent['xl/drawings/_rels/vmlDrawingHF' . ($i + 1) . '.vml.rels'] = $this->getWriterPartRels()->writeHeaderFooterDrawingRelationships($this->spreadSheet->getSheet($i)); + + // Media + foreach ($this->spreadSheet->getSheet($i)->getHeaderFooter()->getImages() as $image) { + $zipContent['xl/media/' . $image->getIndexedFilename()] = file_get_contents($image->getPath()); + } + } + } + + // Add media + for ($i = 0; $i < $this->getDrawingHashTable()->count(); ++$i) { + if ($this->getDrawingHashTable()->getByIndex($i) instanceof WorksheetDrawing) { + $imageContents = null; + $imagePath = $this->getDrawingHashTable()->getByIndex($i)->getPath(); + if (strpos($imagePath, 'zip://') !== false) { + $imagePath = substr($imagePath, 6); + $imagePathSplitted = explode('#', $imagePath); + + $imageZip = new ZipArchive(); + $imageZip->open($imagePathSplitted[0]); + $imageContents = $imageZip->getFromName($imagePathSplitted[1]); + $imageZip->close(); + unset($imageZip); + } else { + $imageContents = file_get_contents($imagePath); + } + + $zipContent['xl/media/' . str_replace(' ', '_', $this->getDrawingHashTable()->getByIndex($i)->getIndexedFilename())] = $imageContents; + } elseif ($this->getDrawingHashTable()->getByIndex($i) instanceof MemoryDrawing) { + ob_start(); + call_user_func( + $this->getDrawingHashTable()->getByIndex($i)->getRenderingFunction(), + $this->getDrawingHashTable()->getByIndex($i)->getImageResource() + ); + $imageContents = ob_get_contents(); + ob_end_clean(); + + $zipContent['xl/media/' . str_replace(' ', '_', $this->getDrawingHashTable()->getByIndex($i)->getIndexedFilename())] = $imageContents; + } + } + + Functions::setReturnDateType($saveDateReturnType); + Calculation::getInstance($this->spreadSheet)->getDebugLog()->setWriteDebugLog($saveDebugLog); + + $this->openFileHandle($filename); + + $options = new Archive(); + $options->setEnableZip64(false); + $options->setOutputStream($this->fileHandle); + + $this->zip = new ZipStream(null, $options); + + $this->addZipFiles($zipContent); + + // Close file + try { + $this->zip->finish(); + } catch (OverflowException $e) { + throw new WriterException('Could not close resource.'); + } + + $this->maybeCloseFileHandle(); + } + + /** + * Get Spreadsheet object. + * + * @return Spreadsheet + */ + public function getSpreadsheet() + { + return $this->spreadSheet; + } + + /** + * Set Spreadsheet object. + * + * @param Spreadsheet $spreadsheet PhpSpreadsheet object + * + * @return $this + */ + public function setSpreadsheet(Spreadsheet $spreadsheet) + { + $this->spreadSheet = $spreadsheet; + + return $this; + } + + /** + * Get string table. + * + * @return string[] + */ + public function getStringTable() + { + return $this->stringTable; + } + + /** + * Get Style HashTable. + * + * @return HashTable<\PhpOffice\PhpSpreadsheet\Style\Style> + */ + public function getStyleHashTable() + { + return $this->styleHashTable; + } + + /** + * Get Conditional HashTable. + * + * @return HashTable + */ + public function getStylesConditionalHashTable() + { + return $this->stylesConditionalHashTable; + } + + /** + * Get Fill HashTable. + * + * @return HashTable + */ + public function getFillHashTable() + { + return $this->fillHashTable; + } + + /** + * Get \PhpOffice\PhpSpreadsheet\Style\Font HashTable. + * + * @return HashTable + */ + public function getFontHashTable() + { + return $this->fontHashTable; + } + + /** + * Get Borders HashTable. + * + * @return HashTable + */ + public function getBordersHashTable() + { + return $this->bordersHashTable; + } + + /** + * Get NumberFormat HashTable. + * + * @return HashTable + */ + public function getNumFmtHashTable() + { + return $this->numFmtHashTable; + } + + /** + * Get \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\BaseDrawing HashTable. + * + * @return HashTable + */ + public function getDrawingHashTable() + { + return $this->drawingHashTable; + } + + /** + * Get Office2003 compatibility. + * + * @return bool + */ + public function getOffice2003Compatibility() + { + return $this->office2003compatibility; + } + + /** + * Set Office2003 compatibility. + * + * @param bool $office2003compatibility Office2003 compatibility? + * + * @return $this + */ + public function setOffice2003Compatibility($office2003compatibility) + { + $this->office2003compatibility = $office2003compatibility; + + return $this; + } + + private $pathNames = []; + + private function addZipFile(string $path, string $content): void + { + if (!in_array($path, $this->pathNames)) { + $this->pathNames[] = $path; + $this->zip->addFile($path, $content); + } + } + + private function addZipFiles(array $zipContent): void + { + foreach ($zipContent as $path => $content) { + $this->addZipFile($path, $content); + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Chart.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Chart.php new file mode 100644 index 0000000..23b78a2 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Chart.php @@ -0,0 +1,1485 @@ +calculateCellValues = $calculateCellValues; + + // Create XML writer + $objWriter = null; + if ($this->getParentWriter()->getUseDiskCaching()) { + $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory()); + } else { + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + } + // Ensure that data series values are up-to-date before we save + if ($this->calculateCellValues) { + $chart->refresh(); + } + + // XML header + $objWriter->startDocument('1.0', 'UTF-8', 'yes'); + + // c:chartSpace + $objWriter->startElement('c:chartSpace'); + $objWriter->writeAttribute('xmlns:c', 'http://schemas.openxmlformats.org/drawingml/2006/chart'); + $objWriter->writeAttribute('xmlns:a', 'http://schemas.openxmlformats.org/drawingml/2006/main'); + $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + + $objWriter->startElement('c:date1904'); + $objWriter->writeAttribute('val', 0); + $objWriter->endElement(); + $objWriter->startElement('c:lang'); + $objWriter->writeAttribute('val', 'en-GB'); + $objWriter->endElement(); + $objWriter->startElement('c:roundedCorners'); + $objWriter->writeAttribute('val', 0); + $objWriter->endElement(); + + $this->writeAlternateContent($objWriter); + + $objWriter->startElement('c:chart'); + + $this->writeTitle($objWriter, $chart->getTitle()); + + $objWriter->startElement('c:autoTitleDeleted'); + $objWriter->writeAttribute('val', 0); + $objWriter->endElement(); + + $this->writePlotArea($objWriter, $chart->getPlotArea(), $chart->getXAxisLabel(), $chart->getYAxisLabel(), $chart->getChartAxisX(), $chart->getChartAxisY(), $chart->getMajorGridlines(), $chart->getMinorGridlines()); + + $this->writeLegend($objWriter, $chart->getLegend()); + + $objWriter->startElement('c:plotVisOnly'); + $objWriter->writeAttribute('val', (int) $chart->getPlotVisibleOnly()); + $objWriter->endElement(); + + $objWriter->startElement('c:dispBlanksAs'); + $objWriter->writeAttribute('val', $chart->getDisplayBlanksAs()); + $objWriter->endElement(); + + $objWriter->startElement('c:showDLblsOverMax'); + $objWriter->writeAttribute('val', 0); + $objWriter->endElement(); + + $objWriter->endElement(); + + $this->writePrintSettings($objWriter); + + $objWriter->endElement(); + + // Return + return $objWriter->getData(); + } + + /** + * Write Chart Title. + */ + private function writeTitle(XMLWriter $objWriter, ?Title $title = null): void + { + if ($title === null) { + return; + } + + $objWriter->startElement('c:title'); + $objWriter->startElement('c:tx'); + $objWriter->startElement('c:rich'); + + $objWriter->startElement('a:bodyPr'); + $objWriter->endElement(); + + $objWriter->startElement('a:lstStyle'); + $objWriter->endElement(); + + $objWriter->startElement('a:p'); + + $caption = $title->getCaption(); + if ((is_array($caption)) && (count($caption) > 0)) { + $caption = $caption[0]; + } + $this->getParentWriter()->getWriterPartstringtable()->writeRichTextForCharts($objWriter, $caption, 'a'); + + $objWriter->endElement(); + $objWriter->endElement(); + $objWriter->endElement(); + + $this->writeLayout($objWriter, $title->getLayout()); + + $objWriter->startElement('c:overlay'); + $objWriter->writeAttribute('val', 0); + $objWriter->endElement(); + + $objWriter->endElement(); + } + + /** + * Write Chart Legend. + */ + private function writeLegend(XMLWriter $objWriter, ?Legend $legend = null): void + { + if ($legend === null) { + return; + } + + $objWriter->startElement('c:legend'); + + $objWriter->startElement('c:legendPos'); + $objWriter->writeAttribute('val', $legend->getPosition()); + $objWriter->endElement(); + + $this->writeLayout($objWriter, $legend->getLayout()); + + $objWriter->startElement('c:overlay'); + $objWriter->writeAttribute('val', ($legend->getOverlay()) ? '1' : '0'); + $objWriter->endElement(); + + $objWriter->startElement('c:txPr'); + $objWriter->startElement('a:bodyPr'); + $objWriter->endElement(); + + $objWriter->startElement('a:lstStyle'); + $objWriter->endElement(); + + $objWriter->startElement('a:p'); + $objWriter->startElement('a:pPr'); + $objWriter->writeAttribute('rtl', 0); + + $objWriter->startElement('a:defRPr'); + $objWriter->endElement(); + $objWriter->endElement(); + + $objWriter->startElement('a:endParaRPr'); + $objWriter->writeAttribute('lang', 'en-US'); + $objWriter->endElement(); + + $objWriter->endElement(); + $objWriter->endElement(); + + $objWriter->endElement(); + } + + /** + * Write Chart Plot Area. + */ + private function writePlotArea(XMLWriter $objWriter, PlotArea $plotArea, ?Title $xAxisLabel = null, ?Title $yAxisLabel = null, ?Axis $xAxis = null, ?Axis $yAxis = null, ?GridLines $majorGridlines = null, ?GridLines $minorGridlines = null): void + { + if ($plotArea === null) { + return; + } + + $id1 = $id2 = 0; + $this->seriesIndex = 0; + $objWriter->startElement('c:plotArea'); + + $layout = $plotArea->getLayout(); + + $this->writeLayout($objWriter, $layout); + + $chartTypes = self::getChartType($plotArea); + $catIsMultiLevelSeries = $valIsMultiLevelSeries = false; + $plotGroupingType = ''; + $chartType = null; + foreach ($chartTypes as $chartType) { + $objWriter->startElement('c:' . $chartType); + + $groupCount = $plotArea->getPlotGroupCount(); + $plotGroup = null; + for ($i = 0; $i < $groupCount; ++$i) { + $plotGroup = $plotArea->getPlotGroupByIndex($i); + $groupType = $plotGroup->getPlotType(); + if ($groupType == $chartType) { + $plotStyle = $plotGroup->getPlotStyle(); + if ($groupType === DataSeries::TYPE_RADARCHART) { + $objWriter->startElement('c:radarStyle'); + $objWriter->writeAttribute('val', $plotStyle); + $objWriter->endElement(); + } elseif ($groupType === DataSeries::TYPE_SCATTERCHART) { + $objWriter->startElement('c:scatterStyle'); + $objWriter->writeAttribute('val', $plotStyle); + $objWriter->endElement(); + } + + $this->writePlotGroup($plotGroup, $chartType, $objWriter, $catIsMultiLevelSeries, $valIsMultiLevelSeries, $plotGroupingType); + } + } + + $this->writeDataLabels($objWriter, $layout); + + if ($chartType === DataSeries::TYPE_LINECHART && $plotGroup) { + // Line only, Line3D can't be smoothed + $objWriter->startElement('c:smooth'); + $objWriter->writeAttribute('val', (int) $plotGroup->getSmoothLine()); + $objWriter->endElement(); + } elseif (($chartType === DataSeries::TYPE_BARCHART) || ($chartType === DataSeries::TYPE_BARCHART_3D)) { + $objWriter->startElement('c:gapWidth'); + $objWriter->writeAttribute('val', 150); + $objWriter->endElement(); + + if ($plotGroupingType == 'percentStacked' || $plotGroupingType == 'stacked') { + $objWriter->startElement('c:overlap'); + $objWriter->writeAttribute('val', 100); + $objWriter->endElement(); + } + } elseif ($chartType === DataSeries::TYPE_BUBBLECHART) { + $objWriter->startElement('c:bubbleScale'); + $objWriter->writeAttribute('val', 25); + $objWriter->endElement(); + + $objWriter->startElement('c:showNegBubbles'); + $objWriter->writeAttribute('val', 0); + $objWriter->endElement(); + } elseif ($chartType === DataSeries::TYPE_STOCKCHART) { + $objWriter->startElement('c:hiLowLines'); + $objWriter->endElement(); + + $objWriter->startElement('c:upDownBars'); + + $objWriter->startElement('c:gapWidth'); + $objWriter->writeAttribute('val', 300); + $objWriter->endElement(); + + $objWriter->startElement('c:upBars'); + $objWriter->endElement(); + + $objWriter->startElement('c:downBars'); + $objWriter->endElement(); + + $objWriter->endElement(); + } + + // Generate 2 unique numbers to use for axId values + $id1 = '75091328'; + $id2 = '75089408'; + + if (($chartType !== DataSeries::TYPE_PIECHART) && ($chartType !== DataSeries::TYPE_PIECHART_3D) && ($chartType !== DataSeries::TYPE_DONUTCHART)) { + $objWriter->startElement('c:axId'); + $objWriter->writeAttribute('val', $id1); + $objWriter->endElement(); + $objWriter->startElement('c:axId'); + $objWriter->writeAttribute('val', $id2); + $objWriter->endElement(); + } else { + $objWriter->startElement('c:firstSliceAng'); + $objWriter->writeAttribute('val', 0); + $objWriter->endElement(); + + if ($chartType === DataSeries::TYPE_DONUTCHART) { + $objWriter->startElement('c:holeSize'); + $objWriter->writeAttribute('val', 50); + $objWriter->endElement(); + } + } + + $objWriter->endElement(); + } + + if (($chartType !== DataSeries::TYPE_PIECHART) && ($chartType !== DataSeries::TYPE_PIECHART_3D) && ($chartType !== DataSeries::TYPE_DONUTCHART)) { + if ($chartType === DataSeries::TYPE_BUBBLECHART) { + $this->writeValueAxis($objWriter, $xAxisLabel, $chartType, $id1, $id2, $catIsMultiLevelSeries, $xAxis, $majorGridlines, $minorGridlines); + } else { + $this->writeCategoryAxis($objWriter, $xAxisLabel, $id1, $id2, $catIsMultiLevelSeries, $xAxis); + } + + $this->writeValueAxis($objWriter, $yAxisLabel, $chartType, $id1, $id2, $valIsMultiLevelSeries, $yAxis, $majorGridlines, $minorGridlines); + } + + $objWriter->endElement(); + } + + /** + * Write Data Labels. + */ + private function writeDataLabels(XMLWriter $objWriter, ?Layout $chartLayout = null): void + { + $objWriter->startElement('c:dLbls'); + + $objWriter->startElement('c:showLegendKey'); + $showLegendKey = (empty($chartLayout)) ? 0 : $chartLayout->getShowLegendKey(); + $objWriter->writeAttribute('val', ((empty($showLegendKey)) ? 0 : 1)); + $objWriter->endElement(); + + $objWriter->startElement('c:showVal'); + $showVal = (empty($chartLayout)) ? 0 : $chartLayout->getShowVal(); + $objWriter->writeAttribute('val', ((empty($showVal)) ? 0 : 1)); + $objWriter->endElement(); + + $objWriter->startElement('c:showCatName'); + $showCatName = (empty($chartLayout)) ? 0 : $chartLayout->getShowCatName(); + $objWriter->writeAttribute('val', ((empty($showCatName)) ? 0 : 1)); + $objWriter->endElement(); + + $objWriter->startElement('c:showSerName'); + $showSerName = (empty($chartLayout)) ? 0 : $chartLayout->getShowSerName(); + $objWriter->writeAttribute('val', ((empty($showSerName)) ? 0 : 1)); + $objWriter->endElement(); + + $objWriter->startElement('c:showPercent'); + $showPercent = (empty($chartLayout)) ? 0 : $chartLayout->getShowPercent(); + $objWriter->writeAttribute('val', ((empty($showPercent)) ? 0 : 1)); + $objWriter->endElement(); + + $objWriter->startElement('c:showBubbleSize'); + $showBubbleSize = (empty($chartLayout)) ? 0 : $chartLayout->getShowBubbleSize(); + $objWriter->writeAttribute('val', ((empty($showBubbleSize)) ? 0 : 1)); + $objWriter->endElement(); + + $objWriter->startElement('c:showLeaderLines'); + $showLeaderLines = (empty($chartLayout)) ? 1 : $chartLayout->getShowLeaderLines(); + $objWriter->writeAttribute('val', ((empty($showLeaderLines)) ? 0 : 1)); + $objWriter->endElement(); + + $objWriter->endElement(); + } + + /** + * Write Category Axis. + * + * @param string $id1 + * @param string $id2 + * @param bool $isMultiLevelSeries + */ + private function writeCategoryAxis(XMLWriter $objWriter, ?Title $xAxisLabel, $id1, $id2, $isMultiLevelSeries, Axis $yAxis): void + { + $objWriter->startElement('c:catAx'); + + if ($id1 > 0) { + $objWriter->startElement('c:axId'); + $objWriter->writeAttribute('val', $id1); + $objWriter->endElement(); + } + + $objWriter->startElement('c:scaling'); + $objWriter->startElement('c:orientation'); + $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('orientation')); + $objWriter->endElement(); + $objWriter->endElement(); + + $objWriter->startElement('c:delete'); + $objWriter->writeAttribute('val', 0); + $objWriter->endElement(); + + $objWriter->startElement('c:axPos'); + $objWriter->writeAttribute('val', 'b'); + $objWriter->endElement(); + + if ($xAxisLabel !== null) { + $objWriter->startElement('c:title'); + $objWriter->startElement('c:tx'); + $objWriter->startElement('c:rich'); + + $objWriter->startElement('a:bodyPr'); + $objWriter->endElement(); + + $objWriter->startElement('a:lstStyle'); + $objWriter->endElement(); + + $objWriter->startElement('a:p'); + $objWriter->startElement('a:r'); + + $caption = $xAxisLabel->getCaption(); + if (is_array($caption)) { + $caption = $caption[0]; + } + $objWriter->startElement('a:t'); + $objWriter->writeRawData(StringHelper::controlCharacterPHP2OOXML($caption)); + $objWriter->endElement(); + + $objWriter->endElement(); + $objWriter->endElement(); + $objWriter->endElement(); + $objWriter->endElement(); + + $layout = $xAxisLabel->getLayout(); + $this->writeLayout($objWriter, $layout); + + $objWriter->startElement('c:overlay'); + $objWriter->writeAttribute('val', 0); + $objWriter->endElement(); + + $objWriter->endElement(); + } + + $objWriter->startElement('c:numFmt'); + $objWriter->writeAttribute('formatCode', $yAxis->getAxisNumberFormat()); + $objWriter->writeAttribute('sourceLinked', $yAxis->getAxisNumberSourceLinked()); + $objWriter->endElement(); + + $objWriter->startElement('c:majorTickMark'); + $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('major_tick_mark')); + $objWriter->endElement(); + + $objWriter->startElement('c:minorTickMark'); + $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('minor_tick_mark')); + $objWriter->endElement(); + + $objWriter->startElement('c:tickLblPos'); + $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('axis_labels')); + $objWriter->endElement(); + + if ($id2 > 0) { + $objWriter->startElement('c:crossAx'); + $objWriter->writeAttribute('val', $id2); + $objWriter->endElement(); + + $objWriter->startElement('c:crosses'); + $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('horizontal_crosses')); + $objWriter->endElement(); + } + + $objWriter->startElement('c:auto'); + $objWriter->writeAttribute('val', 1); + $objWriter->endElement(); + + $objWriter->startElement('c:lblAlgn'); + $objWriter->writeAttribute('val', 'ctr'); + $objWriter->endElement(); + + $objWriter->startElement('c:lblOffset'); + $objWriter->writeAttribute('val', 100); + $objWriter->endElement(); + + if ($isMultiLevelSeries) { + $objWriter->startElement('c:noMultiLvlLbl'); + $objWriter->writeAttribute('val', 0); + $objWriter->endElement(); + } + $objWriter->endElement(); + } + + /** + * Write Value Axis. + * + * @param null|string $groupType Chart type + * @param string $id1 + * @param string $id2 + * @param bool $isMultiLevelSeries + */ + private function writeValueAxis(XMLWriter $objWriter, ?Title $yAxisLabel, $groupType, $id1, $id2, $isMultiLevelSeries, Axis $xAxis, GridLines $majorGridlines, GridLines $minorGridlines): void + { + $objWriter->startElement('c:valAx'); + + if ($id2 > 0) { + $objWriter->startElement('c:axId'); + $objWriter->writeAttribute('val', $id2); + $objWriter->endElement(); + } + + $objWriter->startElement('c:scaling'); + + if ($xAxis->getAxisOptionsProperty('maximum') !== null) { + $objWriter->startElement('c:max'); + $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('maximum')); + $objWriter->endElement(); + } + + if ($xAxis->getAxisOptionsProperty('minimum') !== null) { + $objWriter->startElement('c:min'); + $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('minimum')); + $objWriter->endElement(); + } + + $objWriter->startElement('c:orientation'); + $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('orientation')); + + $objWriter->endElement(); + $objWriter->endElement(); + + $objWriter->startElement('c:delete'); + $objWriter->writeAttribute('val', 0); + $objWriter->endElement(); + + $objWriter->startElement('c:axPos'); + $objWriter->writeAttribute('val', 'l'); + $objWriter->endElement(); + + $objWriter->startElement('c:majorGridlines'); + $objWriter->startElement('c:spPr'); + + if ($majorGridlines->getLineColorProperty('value') !== null) { + $objWriter->startElement('a:ln'); + $objWriter->writeAttribute('w', $majorGridlines->getLineStyleProperty('width')); + $objWriter->startElement('a:solidFill'); + $objWriter->startElement("a:{$majorGridlines->getLineColorProperty('type')}"); + $objWriter->writeAttribute('val', $majorGridlines->getLineColorProperty('value')); + $objWriter->startElement('a:alpha'); + $objWriter->writeAttribute('val', $majorGridlines->getLineColorProperty('alpha')); + $objWriter->endElement(); //end alpha + $objWriter->endElement(); //end srgbClr + $objWriter->endElement(); //end solidFill + + $objWriter->startElement('a:prstDash'); + $objWriter->writeAttribute('val', $majorGridlines->getLineStyleProperty('dash')); + $objWriter->endElement(); + + if ($majorGridlines->getLineStyleProperty('join') == 'miter') { + $objWriter->startElement('a:miter'); + $objWriter->writeAttribute('lim', '800000'); + $objWriter->endElement(); + } else { + $objWriter->startElement('a:bevel'); + $objWriter->endElement(); + } + + if ($majorGridlines->getLineStyleProperty(['arrow', 'head', 'type']) !== null) { + $objWriter->startElement('a:headEnd'); + $objWriter->writeAttribute('type', $majorGridlines->getLineStyleProperty(['arrow', 'head', 'type'])); + $objWriter->writeAttribute('w', $majorGridlines->getLineStyleArrowParameters('head', 'w')); + $objWriter->writeAttribute('len', $majorGridlines->getLineStyleArrowParameters('head', 'len')); + $objWriter->endElement(); + } + + if ($majorGridlines->getLineStyleProperty(['arrow', 'end', 'type']) !== null) { + $objWriter->startElement('a:tailEnd'); + $objWriter->writeAttribute('type', $majorGridlines->getLineStyleProperty(['arrow', 'end', 'type'])); + $objWriter->writeAttribute('w', $majorGridlines->getLineStyleArrowParameters('end', 'w')); + $objWriter->writeAttribute('len', $majorGridlines->getLineStyleArrowParameters('end', 'len')); + $objWriter->endElement(); + } + $objWriter->endElement(); //end ln + } + $objWriter->startElement('a:effectLst'); + + if ($majorGridlines->getGlowSize() !== null) { + $objWriter->startElement('a:glow'); + $objWriter->writeAttribute('rad', $majorGridlines->getGlowSize()); + $objWriter->startElement("a:{$majorGridlines->getGlowColor('type')}"); + $objWriter->writeAttribute('val', $majorGridlines->getGlowColor('value')); + $objWriter->startElement('a:alpha'); + $objWriter->writeAttribute('val', $majorGridlines->getGlowColor('alpha')); + $objWriter->endElement(); //end alpha + $objWriter->endElement(); //end schemeClr + $objWriter->endElement(); //end glow + } + + if ($majorGridlines->getShadowProperty('presets') !== null) { + $objWriter->startElement("a:{$majorGridlines->getShadowProperty('effect')}"); + if ($majorGridlines->getShadowProperty('blur') !== null) { + $objWriter->writeAttribute('blurRad', $majorGridlines->getShadowProperty('blur')); + } + if ($majorGridlines->getShadowProperty('distance') !== null) { + $objWriter->writeAttribute('dist', $majorGridlines->getShadowProperty('distance')); + } + if ($majorGridlines->getShadowProperty('direction') !== null) { + $objWriter->writeAttribute('dir', $majorGridlines->getShadowProperty('direction')); + } + if ($majorGridlines->getShadowProperty('algn') !== null) { + $objWriter->writeAttribute('algn', $majorGridlines->getShadowProperty('algn')); + } + if ($majorGridlines->getShadowProperty(['size', 'sx']) !== null) { + $objWriter->writeAttribute('sx', $majorGridlines->getShadowProperty(['size', 'sx'])); + } + if ($majorGridlines->getShadowProperty(['size', 'sy']) !== null) { + $objWriter->writeAttribute('sy', $majorGridlines->getShadowProperty(['size', 'sy'])); + } + if ($majorGridlines->getShadowProperty(['size', 'kx']) !== null) { + $objWriter->writeAttribute('kx', $majorGridlines->getShadowProperty(['size', 'kx'])); + } + if ($majorGridlines->getShadowProperty('rotWithShape') !== null) { + $objWriter->writeAttribute('rotWithShape', $majorGridlines->getShadowProperty('rotWithShape')); + } + $objWriter->startElement("a:{$majorGridlines->getShadowProperty(['color', 'type'])}"); + $objWriter->writeAttribute('val', $majorGridlines->getShadowProperty(['color', 'value'])); + + $objWriter->startElement('a:alpha'); + $objWriter->writeAttribute('val', $majorGridlines->getShadowProperty(['color', 'alpha'])); + $objWriter->endElement(); //end alpha + + $objWriter->endElement(); //end color:type + $objWriter->endElement(); //end shadow + } + + if ($majorGridlines->getSoftEdgesSize() !== null) { + $objWriter->startElement('a:softEdge'); + $objWriter->writeAttribute('rad', $majorGridlines->getSoftEdgesSize()); + $objWriter->endElement(); //end softEdge + } + + $objWriter->endElement(); //end effectLst + $objWriter->endElement(); //end spPr + $objWriter->endElement(); //end majorGridLines + + if ($minorGridlines->getObjectState()) { + $objWriter->startElement('c:minorGridlines'); + $objWriter->startElement('c:spPr'); + + if ($minorGridlines->getLineColorProperty('value') !== null) { + $objWriter->startElement('a:ln'); + $objWriter->writeAttribute('w', $minorGridlines->getLineStyleProperty('width')); + $objWriter->startElement('a:solidFill'); + $objWriter->startElement("a:{$minorGridlines->getLineColorProperty('type')}"); + $objWriter->writeAttribute('val', $minorGridlines->getLineColorProperty('value')); + $objWriter->startElement('a:alpha'); + $objWriter->writeAttribute('val', $minorGridlines->getLineColorProperty('alpha')); + $objWriter->endElement(); //end alpha + $objWriter->endElement(); //end srgbClr + $objWriter->endElement(); //end solidFill + + $objWriter->startElement('a:prstDash'); + $objWriter->writeAttribute('val', $minorGridlines->getLineStyleProperty('dash')); + $objWriter->endElement(); + + if ($minorGridlines->getLineStyleProperty('join') == 'miter') { + $objWriter->startElement('a:miter'); + $objWriter->writeAttribute('lim', '800000'); + $objWriter->endElement(); + } else { + $objWriter->startElement('a:bevel'); + $objWriter->endElement(); + } + + if ($minorGridlines->getLineStyleProperty(['arrow', 'head', 'type']) !== null) { + $objWriter->startElement('a:headEnd'); + $objWriter->writeAttribute('type', $minorGridlines->getLineStyleProperty(['arrow', 'head', 'type'])); + $objWriter->writeAttribute('w', $minorGridlines->getLineStyleArrowParameters('head', 'w')); + $objWriter->writeAttribute('len', $minorGridlines->getLineStyleArrowParameters('head', 'len')); + $objWriter->endElement(); + } + + if ($minorGridlines->getLineStyleProperty(['arrow', 'end', 'type']) !== null) { + $objWriter->startElement('a:tailEnd'); + $objWriter->writeAttribute('type', $minorGridlines->getLineStyleProperty(['arrow', 'end', 'type'])); + $objWriter->writeAttribute('w', $minorGridlines->getLineStyleArrowParameters('end', 'w')); + $objWriter->writeAttribute('len', $minorGridlines->getLineStyleArrowParameters('end', 'len')); + $objWriter->endElement(); + } + $objWriter->endElement(); //end ln + } + + $objWriter->startElement('a:effectLst'); + + if ($minorGridlines->getGlowSize() !== null) { + $objWriter->startElement('a:glow'); + $objWriter->writeAttribute('rad', $minorGridlines->getGlowSize()); + $objWriter->startElement("a:{$minorGridlines->getGlowColor('type')}"); + $objWriter->writeAttribute('val', $minorGridlines->getGlowColor('value')); + $objWriter->startElement('a:alpha'); + $objWriter->writeAttribute('val', $minorGridlines->getGlowColor('alpha')); + $objWriter->endElement(); //end alpha + $objWriter->endElement(); //end schemeClr + $objWriter->endElement(); //end glow + } + + if ($minorGridlines->getShadowProperty('presets') !== null) { + $objWriter->startElement("a:{$minorGridlines->getShadowProperty('effect')}"); + if ($minorGridlines->getShadowProperty('blur') !== null) { + $objWriter->writeAttribute('blurRad', $minorGridlines->getShadowProperty('blur')); + } + if ($minorGridlines->getShadowProperty('distance') !== null) { + $objWriter->writeAttribute('dist', $minorGridlines->getShadowProperty('distance')); + } + if ($minorGridlines->getShadowProperty('direction') !== null) { + $objWriter->writeAttribute('dir', $minorGridlines->getShadowProperty('direction')); + } + if ($minorGridlines->getShadowProperty('algn') !== null) { + $objWriter->writeAttribute('algn', $minorGridlines->getShadowProperty('algn')); + } + if ($minorGridlines->getShadowProperty(['size', 'sx']) !== null) { + $objWriter->writeAttribute('sx', $minorGridlines->getShadowProperty(['size', 'sx'])); + } + if ($minorGridlines->getShadowProperty(['size', 'sy']) !== null) { + $objWriter->writeAttribute('sy', $minorGridlines->getShadowProperty(['size', 'sy'])); + } + if ($minorGridlines->getShadowProperty(['size', 'kx']) !== null) { + $objWriter->writeAttribute('kx', $minorGridlines->getShadowProperty(['size', 'kx'])); + } + if ($minorGridlines->getShadowProperty('rotWithShape') !== null) { + $objWriter->writeAttribute('rotWithShape', $minorGridlines->getShadowProperty('rotWithShape')); + } + $objWriter->startElement("a:{$minorGridlines->getShadowProperty(['color', 'type'])}"); + $objWriter->writeAttribute('val', $minorGridlines->getShadowProperty(['color', 'value'])); + $objWriter->startElement('a:alpha'); + $objWriter->writeAttribute('val', $minorGridlines->getShadowProperty(['color', 'alpha'])); + $objWriter->endElement(); //end alpha + $objWriter->endElement(); //end color:type + $objWriter->endElement(); //end shadow + } + + if ($minorGridlines->getSoftEdgesSize() !== null) { + $objWriter->startElement('a:softEdge'); + $objWriter->writeAttribute('rad', $minorGridlines->getSoftEdgesSize()); + $objWriter->endElement(); //end softEdge + } + + $objWriter->endElement(); //end effectLst + $objWriter->endElement(); //end spPr + $objWriter->endElement(); //end minorGridLines + } + + if ($yAxisLabel !== null) { + $objWriter->startElement('c:title'); + $objWriter->startElement('c:tx'); + $objWriter->startElement('c:rich'); + + $objWriter->startElement('a:bodyPr'); + $objWriter->endElement(); + + $objWriter->startElement('a:lstStyle'); + $objWriter->endElement(); + + $objWriter->startElement('a:p'); + $objWriter->startElement('a:r'); + + $caption = $yAxisLabel->getCaption(); + if (is_array($caption)) { + $caption = $caption[0]; + } + + $objWriter->startElement('a:t'); + $objWriter->writeRawData(StringHelper::controlCharacterPHP2OOXML($caption)); + $objWriter->endElement(); + + $objWriter->endElement(); + $objWriter->endElement(); + $objWriter->endElement(); + $objWriter->endElement(); + + if ($groupType !== DataSeries::TYPE_BUBBLECHART) { + $layout = $yAxisLabel->getLayout(); + $this->writeLayout($objWriter, $layout); + } + + $objWriter->startElement('c:overlay'); + $objWriter->writeAttribute('val', 0); + $objWriter->endElement(); + + $objWriter->endElement(); + } + + $objWriter->startElement('c:numFmt'); + $objWriter->writeAttribute('formatCode', $xAxis->getAxisNumberFormat()); + $objWriter->writeAttribute('sourceLinked', $xAxis->getAxisNumberSourceLinked()); + $objWriter->endElement(); + + $objWriter->startElement('c:majorTickMark'); + $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('major_tick_mark')); + $objWriter->endElement(); + + $objWriter->startElement('c:minorTickMark'); + $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('minor_tick_mark')); + $objWriter->endElement(); + + $objWriter->startElement('c:tickLblPos'); + $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('axis_labels')); + $objWriter->endElement(); + + $objWriter->startElement('c:spPr'); + + if ($xAxis->getFillProperty('value') !== null) { + $objWriter->startElement('a:solidFill'); + $objWriter->startElement('a:' . $xAxis->getFillProperty('type')); + $objWriter->writeAttribute('val', $xAxis->getFillProperty('value')); + $objWriter->startElement('a:alpha'); + $objWriter->writeAttribute('val', $xAxis->getFillProperty('alpha')); + $objWriter->endElement(); + $objWriter->endElement(); + $objWriter->endElement(); + } + + $objWriter->startElement('a:ln'); + + $objWriter->writeAttribute('w', $xAxis->getLineStyleProperty('width')); + $objWriter->writeAttribute('cap', $xAxis->getLineStyleProperty('cap')); + $objWriter->writeAttribute('cmpd', $xAxis->getLineStyleProperty('compound')); + + if ($xAxis->getLineProperty('value') !== null) { + $objWriter->startElement('a:solidFill'); + $objWriter->startElement('a:' . $xAxis->getLineProperty('type')); + $objWriter->writeAttribute('val', $xAxis->getLineProperty('value')); + $objWriter->startElement('a:alpha'); + $objWriter->writeAttribute('val', $xAxis->getLineProperty('alpha')); + $objWriter->endElement(); + $objWriter->endElement(); + $objWriter->endElement(); + } + + $objWriter->startElement('a:prstDash'); + $objWriter->writeAttribute('val', $xAxis->getLineStyleProperty('dash')); + $objWriter->endElement(); + + if ($xAxis->getLineStyleProperty('join') == 'miter') { + $objWriter->startElement('a:miter'); + $objWriter->writeAttribute('lim', '800000'); + $objWriter->endElement(); + } else { + $objWriter->startElement('a:bevel'); + $objWriter->endElement(); + } + + if ($xAxis->getLineStyleProperty(['arrow', 'head', 'type']) !== null) { + $objWriter->startElement('a:headEnd'); + $objWriter->writeAttribute('type', $xAxis->getLineStyleProperty(['arrow', 'head', 'type'])); + $objWriter->writeAttribute('w', $xAxis->getLineStyleArrowWidth('head')); + $objWriter->writeAttribute('len', $xAxis->getLineStyleArrowLength('head')); + $objWriter->endElement(); + } + + if ($xAxis->getLineStyleProperty(['arrow', 'end', 'type']) !== null) { + $objWriter->startElement('a:tailEnd'); + $objWriter->writeAttribute('type', $xAxis->getLineStyleProperty(['arrow', 'end', 'type'])); + $objWriter->writeAttribute('w', $xAxis->getLineStyleArrowWidth('end')); + $objWriter->writeAttribute('len', $xAxis->getLineStyleArrowLength('end')); + $objWriter->endElement(); + } + + $objWriter->endElement(); + + $objWriter->startElement('a:effectLst'); + + if ($xAxis->getGlowProperty('size') !== null) { + $objWriter->startElement('a:glow'); + $objWriter->writeAttribute('rad', $xAxis->getGlowProperty('size')); + $objWriter->startElement("a:{$xAxis->getGlowProperty(['color', 'type'])}"); + $objWriter->writeAttribute('val', $xAxis->getGlowProperty(['color', 'value'])); + $objWriter->startElement('a:alpha'); + $objWriter->writeAttribute('val', $xAxis->getGlowProperty(['color', 'alpha'])); + $objWriter->endElement(); + $objWriter->endElement(); + $objWriter->endElement(); + } + + if ($xAxis->getShadowProperty('presets') !== null) { + $objWriter->startElement("a:{$xAxis->getShadowProperty('effect')}"); + + if ($xAxis->getShadowProperty('blur') !== null) { + $objWriter->writeAttribute('blurRad', $xAxis->getShadowProperty('blur')); + } + if ($xAxis->getShadowProperty('distance') !== null) { + $objWriter->writeAttribute('dist', $xAxis->getShadowProperty('distance')); + } + if ($xAxis->getShadowProperty('direction') !== null) { + $objWriter->writeAttribute('dir', $xAxis->getShadowProperty('direction')); + } + if ($xAxis->getShadowProperty('algn') !== null) { + $objWriter->writeAttribute('algn', $xAxis->getShadowProperty('algn')); + } + if ($xAxis->getShadowProperty(['size', 'sx']) !== null) { + $objWriter->writeAttribute('sx', $xAxis->getShadowProperty(['size', 'sx'])); + } + if ($xAxis->getShadowProperty(['size', 'sy']) !== null) { + $objWriter->writeAttribute('sy', $xAxis->getShadowProperty(['size', 'sy'])); + } + if ($xAxis->getShadowProperty(['size', 'kx']) !== null) { + $objWriter->writeAttribute('kx', $xAxis->getShadowProperty(['size', 'kx'])); + } + if ($xAxis->getShadowProperty('rotWithShape') !== null) { + $objWriter->writeAttribute('rotWithShape', $xAxis->getShadowProperty('rotWithShape')); + } + + $objWriter->startElement("a:{$xAxis->getShadowProperty(['color', 'type'])}"); + $objWriter->writeAttribute('val', $xAxis->getShadowProperty(['color', 'value'])); + $objWriter->startElement('a:alpha'); + $objWriter->writeAttribute('val', $xAxis->getShadowProperty(['color', 'alpha'])); + $objWriter->endElement(); + $objWriter->endElement(); + + $objWriter->endElement(); + } + + if ($xAxis->getSoftEdgesSize() !== null) { + $objWriter->startElement('a:softEdge'); + $objWriter->writeAttribute('rad', $xAxis->getSoftEdgesSize()); + $objWriter->endElement(); + } + + $objWriter->endElement(); //effectList + $objWriter->endElement(); //end spPr + + if ($id1 > 0) { + $objWriter->startElement('c:crossAx'); + $objWriter->writeAttribute('val', $id2); + $objWriter->endElement(); + + if ($xAxis->getAxisOptionsProperty('horizontal_crosses_value') !== null) { + $objWriter->startElement('c:crossesAt'); + $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('horizontal_crosses_value')); + $objWriter->endElement(); + } else { + $objWriter->startElement('c:crosses'); + $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('horizontal_crosses')); + $objWriter->endElement(); + } + + $objWriter->startElement('c:crossBetween'); + $objWriter->writeAttribute('val', 'midCat'); + $objWriter->endElement(); + + if ($xAxis->getAxisOptionsProperty('major_unit') !== null) { + $objWriter->startElement('c:majorUnit'); + $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('major_unit')); + $objWriter->endElement(); + } + + if ($xAxis->getAxisOptionsProperty('minor_unit') !== null) { + $objWriter->startElement('c:minorUnit'); + $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('minor_unit')); + $objWriter->endElement(); + } + } + + if ($isMultiLevelSeries) { + if ($groupType !== DataSeries::TYPE_BUBBLECHART) { + $objWriter->startElement('c:noMultiLvlLbl'); + $objWriter->writeAttribute('val', 0); + $objWriter->endElement(); + } + } + + $objWriter->endElement(); + } + + /** + * Get the data series type(s) for a chart plot series. + * + * @return string[] + */ + private static function getChartType(PlotArea $plotArea): array + { + $groupCount = $plotArea->getPlotGroupCount(); + + if ($groupCount == 1) { + $chartType = [$plotArea->getPlotGroupByIndex(0)->getPlotType()]; + } else { + $chartTypes = []; + for ($i = 0; $i < $groupCount; ++$i) { + $chartTypes[] = $plotArea->getPlotGroupByIndex($i)->getPlotType(); + } + $chartType = array_unique($chartTypes); + if (count($chartTypes) == 0) { + throw new WriterException('Chart is not yet implemented'); + } + } + + return $chartType; + } + + /** + * Method writing plot series values. + * + * @param int $val value for idx (default: 3) + * @param string $fillColor hex color (default: FF9900) + */ + private function writePlotSeriesValuesElement(XMLWriter $objWriter, $val = 3, $fillColor = 'FF9900'): void + { + $objWriter->startElement('c:dPt'); + $objWriter->startElement('c:idx'); + $objWriter->writeAttribute('val', $val); + $objWriter->endElement(); + + $objWriter->startElement('c:bubble3D'); + $objWriter->writeAttribute('val', 0); + $objWriter->endElement(); + + $objWriter->startElement('c:spPr'); + $objWriter->startElement('a:solidFill'); + $objWriter->startElement('a:srgbClr'); + $objWriter->writeAttribute('val', $fillColor); + $objWriter->endElement(); + $objWriter->endElement(); + $objWriter->endElement(); + $objWriter->endElement(); + } + + /** + * Write Plot Group (series of related plots). + * + * @param string $groupType Type of plot for dataseries + * @param bool $catIsMultiLevelSeries Is category a multi-series category + * @param bool $valIsMultiLevelSeries Is value set a multi-series set + * @param string $plotGroupingType Type of grouping for multi-series values + */ + private function writePlotGroup(?DataSeries $plotGroup, $groupType, XMLWriter $objWriter, &$catIsMultiLevelSeries, &$valIsMultiLevelSeries, &$plotGroupingType): void + { + if ($plotGroup === null) { + return; + } + + if (($groupType == DataSeries::TYPE_BARCHART) || ($groupType == DataSeries::TYPE_BARCHART_3D)) { + $objWriter->startElement('c:barDir'); + $objWriter->writeAttribute('val', $plotGroup->getPlotDirection()); + $objWriter->endElement(); + } + + if ($plotGroup->getPlotGrouping() !== null) { + $plotGroupingType = $plotGroup->getPlotGrouping(); + $objWriter->startElement('c:grouping'); + $objWriter->writeAttribute('val', $plotGroupingType); + $objWriter->endElement(); + } + + // Get these details before the loop, because we can use the count to check for varyColors + $plotSeriesOrder = $plotGroup->getPlotOrder(); + $plotSeriesCount = count($plotSeriesOrder); + + if (($groupType !== DataSeries::TYPE_RADARCHART) && ($groupType !== DataSeries::TYPE_STOCKCHART)) { + if ($groupType !== DataSeries::TYPE_LINECHART) { + if (($groupType == DataSeries::TYPE_PIECHART) || ($groupType == DataSeries::TYPE_PIECHART_3D) || ($groupType == DataSeries::TYPE_DONUTCHART) || ($plotSeriesCount > 1)) { + $objWriter->startElement('c:varyColors'); + $objWriter->writeAttribute('val', 1); + $objWriter->endElement(); + } else { + $objWriter->startElement('c:varyColors'); + $objWriter->writeAttribute('val', 0); + $objWriter->endElement(); + } + } + } + + $plotSeriesIdx = 0; + foreach ($plotSeriesOrder as $plotSeriesIdx => $plotSeriesRef) { + $objWriter->startElement('c:ser'); + + $plotLabel = $plotGroup->getPlotLabelByIndex($plotSeriesIdx); + if ($plotLabel && $groupType !== DataSeries::TYPE_LINECHART) { + $fillColor = $plotLabel->getFillColor(); + if ($fillColor !== null && !is_array($fillColor)) { + $objWriter->startElement('c:spPr'); + $objWriter->startElement('a:solidFill'); + $objWriter->startElement('a:srgbClr'); + $objWriter->writeAttribute('val', $fillColor); + $objWriter->endElement(); + $objWriter->endElement(); + $objWriter->endElement(); + } + } + + $objWriter->startElement('c:idx'); + $objWriter->writeAttribute('val', $this->seriesIndex + $plotSeriesIdx); + $objWriter->endElement(); + + $objWriter->startElement('c:order'); + $objWriter->writeAttribute('val', $this->seriesIndex + $plotSeriesRef); + $objWriter->endElement(); + + // Values + $plotSeriesValues = $plotGroup->getPlotValuesByIndex($plotSeriesRef); + + if (($groupType == DataSeries::TYPE_PIECHART) || ($groupType == DataSeries::TYPE_PIECHART_3D) || ($groupType == DataSeries::TYPE_DONUTCHART)) { + $fillColorValues = $plotSeriesValues->getFillColor(); + if ($fillColorValues !== null && is_array($fillColorValues)) { + foreach ($plotSeriesValues->getDataValues() as $dataKey => $dataValue) { + $this->writePlotSeriesValuesElement($objWriter, $dataKey, ($fillColorValues[$dataKey] ?? 'FF9900')); + } + } else { + $this->writePlotSeriesValuesElement($objWriter); + } + } + + // Labels + $plotSeriesLabel = $plotGroup->getPlotLabelByIndex($plotSeriesRef); + if ($plotSeriesLabel && ($plotSeriesLabel->getPointCount() > 0)) { + $objWriter->startElement('c:tx'); + $objWriter->startElement('c:strRef'); + $this->writePlotSeriesLabel($plotSeriesLabel, $objWriter); + $objWriter->endElement(); + $objWriter->endElement(); + } + + // Formatting for the points + if (($groupType == DataSeries::TYPE_LINECHART) || ($groupType == DataSeries::TYPE_STOCKCHART)) { + $plotLineWidth = 12700; + if ($plotSeriesValues) { + $plotLineWidth = $plotSeriesValues->getLineWidth(); + } + + $objWriter->startElement('c:spPr'); + $objWriter->startElement('a:ln'); + $objWriter->writeAttribute('w', $plotLineWidth); + if ($groupType == DataSeries::TYPE_STOCKCHART) { + $objWriter->startElement('a:noFill'); + $objWriter->endElement(); + } elseif ($plotLabel) { + $fillColor = $plotLabel->getFillColor(); + if (is_string($fillColor)) { + $objWriter->startElement('a:solidFill'); + $objWriter->startElement('a:srgbClr'); + $objWriter->writeAttribute('val', $fillColor); + $objWriter->endElement(); + $objWriter->endElement(); + } + } + $objWriter->endElement(); + $objWriter->endElement(); + } + + if ($plotSeriesValues) { + $plotSeriesMarker = $plotSeriesValues->getPointMarker(); + if ($plotSeriesMarker) { + $objWriter->startElement('c:marker'); + $objWriter->startElement('c:symbol'); + $objWriter->writeAttribute('val', $plotSeriesMarker); + $objWriter->endElement(); + + if ($plotSeriesMarker !== 'none') { + $objWriter->startElement('c:size'); + $objWriter->writeAttribute('val', 3); + $objWriter->endElement(); + } + + $objWriter->endElement(); + } + } + + if (($groupType === DataSeries::TYPE_BARCHART) || ($groupType === DataSeries::TYPE_BARCHART_3D) || ($groupType === DataSeries::TYPE_BUBBLECHART)) { + $objWriter->startElement('c:invertIfNegative'); + $objWriter->writeAttribute('val', 0); + $objWriter->endElement(); + } + + // Category Labels + $plotSeriesCategory = $plotGroup->getPlotCategoryByIndex($plotSeriesRef); + if ($plotSeriesCategory && ($plotSeriesCategory->getPointCount() > 0)) { + $catIsMultiLevelSeries = $catIsMultiLevelSeries || $plotSeriesCategory->isMultiLevelSeries(); + + if (($groupType == DataSeries::TYPE_PIECHART) || ($groupType == DataSeries::TYPE_PIECHART_3D) || ($groupType == DataSeries::TYPE_DONUTCHART)) { + if ($plotGroup->getPlotStyle() !== null) { + $plotStyle = $plotGroup->getPlotStyle(); + if ($plotStyle) { + $objWriter->startElement('c:explosion'); + $objWriter->writeAttribute('val', 25); + $objWriter->endElement(); + } + } + } + + if (($groupType === DataSeries::TYPE_BUBBLECHART) || ($groupType === DataSeries::TYPE_SCATTERCHART)) { + $objWriter->startElement('c:xVal'); + } else { + $objWriter->startElement('c:cat'); + } + + $this->writePlotSeriesValues($plotSeriesCategory, $objWriter, $groupType, 'str'); + $objWriter->endElement(); + } + + // Values + if ($plotSeriesValues) { + $valIsMultiLevelSeries = $valIsMultiLevelSeries || $plotSeriesValues->isMultiLevelSeries(); + + if (($groupType === DataSeries::TYPE_BUBBLECHART) || ($groupType === DataSeries::TYPE_SCATTERCHART)) { + $objWriter->startElement('c:yVal'); + } else { + $objWriter->startElement('c:val'); + } + + $this->writePlotSeriesValues($plotSeriesValues, $objWriter, $groupType, 'num'); + $objWriter->endElement(); + } + + if ($groupType === DataSeries::TYPE_BUBBLECHART) { + $this->writeBubbles($plotSeriesValues, $objWriter); + } + + $objWriter->endElement(); + } + + $this->seriesIndex += $plotSeriesIdx + 1; + } + + /** + * Write Plot Series Label. + */ + private function writePlotSeriesLabel(?DataSeriesValues $plotSeriesLabel, XMLWriter $objWriter): void + { + if ($plotSeriesLabel === null) { + return; + } + + $objWriter->startElement('c:f'); + $objWriter->writeRawData($plotSeriesLabel->getDataSource()); + $objWriter->endElement(); + + $objWriter->startElement('c:strCache'); + $objWriter->startElement('c:ptCount'); + $objWriter->writeAttribute('val', $plotSeriesLabel->getPointCount()); + $objWriter->endElement(); + + foreach ($plotSeriesLabel->getDataValues() as $plotLabelKey => $plotLabelValue) { + $objWriter->startElement('c:pt'); + $objWriter->writeAttribute('idx', $plotLabelKey); + + $objWriter->startElement('c:v'); + $objWriter->writeRawData($plotLabelValue); + $objWriter->endElement(); + $objWriter->endElement(); + } + $objWriter->endElement(); + } + + /** + * Write Plot Series Values. + * + * @param string $groupType Type of plot for dataseries + * @param string $dataType Datatype of series values + */ + private function writePlotSeriesValues(?DataSeriesValues $plotSeriesValues, XMLWriter $objWriter, $groupType, $dataType = 'str'): void + { + if ($plotSeriesValues === null) { + return; + } + + if ($plotSeriesValues->isMultiLevelSeries()) { + $levelCount = $plotSeriesValues->multiLevelCount(); + + $objWriter->startElement('c:multiLvlStrRef'); + + $objWriter->startElement('c:f'); + $objWriter->writeRawData($plotSeriesValues->getDataSource()); + $objWriter->endElement(); + + $objWriter->startElement('c:multiLvlStrCache'); + + $objWriter->startElement('c:ptCount'); + $objWriter->writeAttribute('val', $plotSeriesValues->getPointCount()); + $objWriter->endElement(); + + for ($level = 0; $level < $levelCount; ++$level) { + $objWriter->startElement('c:lvl'); + + foreach ($plotSeriesValues->getDataValues() as $plotSeriesKey => $plotSeriesValue) { + if (isset($plotSeriesValue[$level])) { + $objWriter->startElement('c:pt'); + $objWriter->writeAttribute('idx', $plotSeriesKey); + + $objWriter->startElement('c:v'); + $objWriter->writeRawData($plotSeriesValue[$level]); + $objWriter->endElement(); + $objWriter->endElement(); + } + } + + $objWriter->endElement(); + } + + $objWriter->endElement(); + + $objWriter->endElement(); + } else { + $objWriter->startElement('c:' . $dataType . 'Ref'); + + $objWriter->startElement('c:f'); + $objWriter->writeRawData($plotSeriesValues->getDataSource()); + $objWriter->endElement(); + + $objWriter->startElement('c:' . $dataType . 'Cache'); + + if (($groupType != DataSeries::TYPE_PIECHART) && ($groupType != DataSeries::TYPE_PIECHART_3D) && ($groupType != DataSeries::TYPE_DONUTCHART)) { + if (($plotSeriesValues->getFormatCode() !== null) && ($plotSeriesValues->getFormatCode() !== '')) { + $objWriter->startElement('c:formatCode'); + $objWriter->writeRawData($plotSeriesValues->getFormatCode()); + $objWriter->endElement(); + } + } + + $objWriter->startElement('c:ptCount'); + $objWriter->writeAttribute('val', $plotSeriesValues->getPointCount()); + $objWriter->endElement(); + + $dataValues = $plotSeriesValues->getDataValues(); + if (!empty($dataValues)) { + if (is_array($dataValues)) { + foreach ($dataValues as $plotSeriesKey => $plotSeriesValue) { + $objWriter->startElement('c:pt'); + $objWriter->writeAttribute('idx', $plotSeriesKey); + + $objWriter->startElement('c:v'); + $objWriter->writeRawData($plotSeriesValue); + $objWriter->endElement(); + $objWriter->endElement(); + } + } + } + + $objWriter->endElement(); + + $objWriter->endElement(); + } + } + + /** + * Write Bubble Chart Details. + */ + private function writeBubbles(?DataSeriesValues $plotSeriesValues, XMLWriter $objWriter): void + { + if ($plotSeriesValues === null) { + return; + } + + $objWriter->startElement('c:bubbleSize'); + $objWriter->startElement('c:numLit'); + + $objWriter->startElement('c:formatCode'); + $objWriter->writeRawData('General'); + $objWriter->endElement(); + + $objWriter->startElement('c:ptCount'); + $objWriter->writeAttribute('val', $plotSeriesValues->getPointCount()); + $objWriter->endElement(); + + $dataValues = $plotSeriesValues->getDataValues(); + if (!empty($dataValues)) { + if (is_array($dataValues)) { + foreach ($dataValues as $plotSeriesKey => $plotSeriesValue) { + $objWriter->startElement('c:pt'); + $objWriter->writeAttribute('idx', $plotSeriesKey); + $objWriter->startElement('c:v'); + $objWriter->writeRawData(1); + $objWriter->endElement(); + $objWriter->endElement(); + } + } + } + + $objWriter->endElement(); + $objWriter->endElement(); + + $objWriter->startElement('c:bubble3D'); + $objWriter->writeAttribute('val', 0); + $objWriter->endElement(); + } + + /** + * Write Layout. + */ + private function writeLayout(XMLWriter $objWriter, ?Layout $layout = null): void + { + $objWriter->startElement('c:layout'); + + if ($layout !== null) { + $objWriter->startElement('c:manualLayout'); + + $layoutTarget = $layout->getLayoutTarget(); + if ($layoutTarget !== null) { + $objWriter->startElement('c:layoutTarget'); + $objWriter->writeAttribute('val', $layoutTarget); + $objWriter->endElement(); + } + + $xMode = $layout->getXMode(); + if ($xMode !== null) { + $objWriter->startElement('c:xMode'); + $objWriter->writeAttribute('val', $xMode); + $objWriter->endElement(); + } + + $yMode = $layout->getYMode(); + if ($yMode !== null) { + $objWriter->startElement('c:yMode'); + $objWriter->writeAttribute('val', $yMode); + $objWriter->endElement(); + } + + $x = $layout->getXPosition(); + if ($x !== null) { + $objWriter->startElement('c:x'); + $objWriter->writeAttribute('val', $x); + $objWriter->endElement(); + } + + $y = $layout->getYPosition(); + if ($y !== null) { + $objWriter->startElement('c:y'); + $objWriter->writeAttribute('val', $y); + $objWriter->endElement(); + } + + $w = $layout->getWidth(); + if ($w !== null) { + $objWriter->startElement('c:w'); + $objWriter->writeAttribute('val', $w); + $objWriter->endElement(); + } + + $h = $layout->getHeight(); + if ($h !== null) { + $objWriter->startElement('c:h'); + $objWriter->writeAttribute('val', $h); + $objWriter->endElement(); + } + + $objWriter->endElement(); + } + + $objWriter->endElement(); + } + + /** + * Write Alternate Content block. + */ + private function writeAlternateContent(XMLWriter $objWriter): void + { + $objWriter->startElement('mc:AlternateContent'); + $objWriter->writeAttribute('xmlns:mc', 'http://schemas.openxmlformats.org/markup-compatibility/2006'); + + $objWriter->startElement('mc:Choice'); + $objWriter->writeAttribute('xmlns:c14', 'http://schemas.microsoft.com/office/drawing/2007/8/2/chart'); + $objWriter->writeAttribute('Requires', 'c14'); + + $objWriter->startElement('c14:style'); + $objWriter->writeAttribute('val', '102'); + $objWriter->endElement(); + $objWriter->endElement(); + + $objWriter->startElement('mc:Fallback'); + $objWriter->startElement('c:style'); + $objWriter->writeAttribute('val', '2'); + $objWriter->endElement(); + $objWriter->endElement(); + + $objWriter->endElement(); + } + + /** + * Write Printer Settings. + */ + private function writePrintSettings(XMLWriter $objWriter): void + { + $objWriter->startElement('c:printSettings'); + + $objWriter->startElement('c:headerFooter'); + $objWriter->endElement(); + + $objWriter->startElement('c:pageMargins'); + $objWriter->writeAttribute('footer', 0.3); + $objWriter->writeAttribute('header', 0.3); + $objWriter->writeAttribute('r', 0.7); + $objWriter->writeAttribute('l', 0.7); + $objWriter->writeAttribute('t', 0.75); + $objWriter->writeAttribute('b', 0.75); + $objWriter->endElement(); + + $objWriter->startElement('c:pageSetup'); + $objWriter->writeAttribute('orientation', 'portrait'); + $objWriter->endElement(); + + $objWriter->endElement(); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Comments.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Comments.php new file mode 100644 index 0000000..62e48f8 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Comments.php @@ -0,0 +1,229 @@ +getParentWriter()->getUseDiskCaching()) { + $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory()); + } else { + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + } + + // XML header + $objWriter->startDocument('1.0', 'UTF-8', 'yes'); + + // Comments cache + $comments = $worksheet->getComments(); + + // Authors cache + $authors = []; + $authorId = 0; + foreach ($comments as $comment) { + if (!isset($authors[$comment->getAuthor()])) { + $authors[$comment->getAuthor()] = $authorId++; + } + } + + // comments + $objWriter->startElement('comments'); + $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'); + + // Loop through authors + $objWriter->startElement('authors'); + foreach ($authors as $author => $index) { + $objWriter->writeElement('author', $author); + } + $objWriter->endElement(); + + // Loop through comments + $objWriter->startElement('commentList'); + foreach ($comments as $key => $value) { + $this->writeComment($objWriter, $key, $value, $authors); + } + $objWriter->endElement(); + + $objWriter->endElement(); + + // Return + return $objWriter->getData(); + } + + /** + * Write comment to XML format. + * + * @param string $cellReference Cell reference + * @param Comment $comment Comment + * @param array $authors Array of authors + */ + private function writeComment(XMLWriter $objWriter, $cellReference, Comment $comment, array $authors): void + { + // comment + $objWriter->startElement('comment'); + $objWriter->writeAttribute('ref', $cellReference); + $objWriter->writeAttribute('authorId', $authors[$comment->getAuthor()]); + + // text + $objWriter->startElement('text'); + $this->getParentWriter()->getWriterPartstringtable()->writeRichText($objWriter, $comment->getText()); + $objWriter->endElement(); + + $objWriter->endElement(); + } + + /** + * Write VML comments to XML format. + * + * @return string XML Output + */ + public function writeVMLComments(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $worksheet) + { + // Create XML writer + $objWriter = null; + if ($this->getParentWriter()->getUseDiskCaching()) { + $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory()); + } else { + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + } + + // XML header + $objWriter->startDocument('1.0', 'UTF-8', 'yes'); + + // Comments cache + $comments = $worksheet->getComments(); + + // xml + $objWriter->startElement('xml'); + $objWriter->writeAttribute('xmlns:v', 'urn:schemas-microsoft-com:vml'); + $objWriter->writeAttribute('xmlns:o', 'urn:schemas-microsoft-com:office:office'); + $objWriter->writeAttribute('xmlns:x', 'urn:schemas-microsoft-com:office:excel'); + + // o:shapelayout + $objWriter->startElement('o:shapelayout'); + $objWriter->writeAttribute('v:ext', 'edit'); + + // o:idmap + $objWriter->startElement('o:idmap'); + $objWriter->writeAttribute('v:ext', 'edit'); + $objWriter->writeAttribute('data', '1'); + $objWriter->endElement(); + + $objWriter->endElement(); + + // v:shapetype + $objWriter->startElement('v:shapetype'); + $objWriter->writeAttribute('id', '_x0000_t202'); + $objWriter->writeAttribute('coordsize', '21600,21600'); + $objWriter->writeAttribute('o:spt', '202'); + $objWriter->writeAttribute('path', 'm,l,21600r21600,l21600,xe'); + + // v:stroke + $objWriter->startElement('v:stroke'); + $objWriter->writeAttribute('joinstyle', 'miter'); + $objWriter->endElement(); + + // v:path + $objWriter->startElement('v:path'); + $objWriter->writeAttribute('gradientshapeok', 't'); + $objWriter->writeAttribute('o:connecttype', 'rect'); + $objWriter->endElement(); + + $objWriter->endElement(); + + // Loop through comments + foreach ($comments as $key => $value) { + $this->writeVMLComment($objWriter, $key, $value); + } + + $objWriter->endElement(); + + // Return + return $objWriter->getData(); + } + + /** + * Write VML comment to XML format. + * + * @param string $cellReference Cell reference, eg: 'A1' + * @param Comment $comment Comment + */ + private function writeVMLComment(XMLWriter $objWriter, $cellReference, Comment $comment): void + { + // Metadata + [$column, $row] = Coordinate::indexesFromString($cellReference); + $id = 1024 + $column + $row; + $id = substr($id, 0, 4); + + // v:shape + $objWriter->startElement('v:shape'); + $objWriter->writeAttribute('id', '_x0000_s' . $id); + $objWriter->writeAttribute('type', '#_x0000_t202'); + $objWriter->writeAttribute('style', 'position:absolute;margin-left:' . $comment->getMarginLeft() . ';margin-top:' . $comment->getMarginTop() . ';width:' . $comment->getWidth() . ';height:' . $comment->getHeight() . ';z-index:1;visibility:' . ($comment->getVisible() ? 'visible' : 'hidden')); + $objWriter->writeAttribute('fillcolor', '#' . $comment->getFillColor()->getRGB()); + $objWriter->writeAttribute('o:insetmode', 'auto'); + + // v:fill + $objWriter->startElement('v:fill'); + $objWriter->writeAttribute('color2', '#' . $comment->getFillColor()->getRGB()); + $objWriter->endElement(); + + // v:shadow + $objWriter->startElement('v:shadow'); + $objWriter->writeAttribute('on', 't'); + $objWriter->writeAttribute('color', 'black'); + $objWriter->writeAttribute('obscured', 't'); + $objWriter->endElement(); + + // v:path + $objWriter->startElement('v:path'); + $objWriter->writeAttribute('o:connecttype', 'none'); + $objWriter->endElement(); + + // v:textbox + $objWriter->startElement('v:textbox'); + $objWriter->writeAttribute('style', 'mso-direction-alt:auto'); + + // div + $objWriter->startElement('div'); + $objWriter->writeAttribute('style', 'text-align:left'); + $objWriter->endElement(); + + $objWriter->endElement(); + + // x:ClientData + $objWriter->startElement('x:ClientData'); + $objWriter->writeAttribute('ObjectType', 'Note'); + + // x:MoveWithCells + $objWriter->writeElement('x:MoveWithCells', ''); + + // x:SizeWithCells + $objWriter->writeElement('x:SizeWithCells', ''); + + // x:AutoFill + $objWriter->writeElement('x:AutoFill', 'False'); + + // x:Row + $objWriter->writeElement('x:Row', ($row - 1)); + + // x:Column + $objWriter->writeElement('x:Column', ($column - 1)); + + $objWriter->endElement(); + + $objWriter->endElement(); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php new file mode 100644 index 0000000..bf0eb08 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php @@ -0,0 +1,238 @@ +getParentWriter()->getUseDiskCaching()) { + $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory()); + } else { + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + } + + // XML header + $objWriter->startDocument('1.0', 'UTF-8', 'yes'); + + // Types + $objWriter->startElement('Types'); + $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/content-types'); + + // Theme + $this->writeOverrideContentType($objWriter, '/xl/theme/theme1.xml', 'application/vnd.openxmlformats-officedocument.theme+xml'); + + // Styles + $this->writeOverrideContentType($objWriter, '/xl/styles.xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml'); + + // Rels + $this->writeDefaultContentType($objWriter, 'rels', 'application/vnd.openxmlformats-package.relationships+xml'); + + // XML + $this->writeDefaultContentType($objWriter, 'xml', 'application/xml'); + + // VML + $this->writeDefaultContentType($objWriter, 'vml', 'application/vnd.openxmlformats-officedocument.vmlDrawing'); + + // Workbook + if ($spreadsheet->hasMacros()) { //Macros in workbook ? + // Yes : not standard content but "macroEnabled" + $this->writeOverrideContentType($objWriter, '/xl/workbook.xml', 'application/vnd.ms-excel.sheet.macroEnabled.main+xml'); + //... and define a new type for the VBA project + // Better use Override, because we can use 'bin' also for xl\printerSettings\printerSettings1.bin + $this->writeOverrideContentType($objWriter, '/xl/vbaProject.bin', 'application/vnd.ms-office.vbaProject'); + if ($spreadsheet->hasMacrosCertificate()) { + // signed macros ? + // Yes : add needed information + $this->writeOverrideContentType($objWriter, '/xl/vbaProjectSignature.bin', 'application/vnd.ms-office.vbaProjectSignature'); + } + } else { + // no macros in workbook, so standard type + $this->writeOverrideContentType($objWriter, '/xl/workbook.xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml'); + } + + // DocProps + $this->writeOverrideContentType($objWriter, '/docProps/app.xml', 'application/vnd.openxmlformats-officedocument.extended-properties+xml'); + + $this->writeOverrideContentType($objWriter, '/docProps/core.xml', 'application/vnd.openxmlformats-package.core-properties+xml'); + + $customPropertyList = $spreadsheet->getProperties()->getCustomProperties(); + if (!empty($customPropertyList)) { + $this->writeOverrideContentType($objWriter, '/docProps/custom.xml', 'application/vnd.openxmlformats-officedocument.custom-properties+xml'); + } + + // Worksheets + $sheetCount = $spreadsheet->getSheetCount(); + for ($i = 0; $i < $sheetCount; ++$i) { + $this->writeOverrideContentType($objWriter, '/xl/worksheets/sheet' . ($i + 1) . '.xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml'); + } + + // Shared strings + $this->writeOverrideContentType($objWriter, '/xl/sharedStrings.xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml'); + + // Add worksheet relationship content types + $unparsedLoadedData = $spreadsheet->getUnparsedLoadedData(); + $chart = 1; + for ($i = 0; $i < $sheetCount; ++$i) { + $drawings = $spreadsheet->getSheet($i)->getDrawingCollection(); + $drawingCount = count($drawings); + $chartCount = ($includeCharts) ? $spreadsheet->getSheet($i)->getChartCount() : 0; + $hasUnparsedDrawing = isset($unparsedLoadedData['sheets'][$spreadsheet->getSheet($i)->getCodeName()]['drawingOriginalIds']); + + // We need a drawing relationship for the worksheet if we have either drawings or charts + if (($drawingCount > 0) || ($chartCount > 0) || $hasUnparsedDrawing) { + $this->writeOverrideContentType($objWriter, '/xl/drawings/drawing' . ($i + 1) . '.xml', 'application/vnd.openxmlformats-officedocument.drawing+xml'); + } + + // If we have charts, then we need a chart relationship for every individual chart + if ($chartCount > 0) { + for ($c = 0; $c < $chartCount; ++$c) { + $this->writeOverrideContentType($objWriter, '/xl/charts/chart' . $chart++ . '.xml', 'application/vnd.openxmlformats-officedocument.drawingml.chart+xml'); + } + } + } + + // Comments + for ($i = 0; $i < $sheetCount; ++$i) { + if (count($spreadsheet->getSheet($i)->getComments()) > 0) { + $this->writeOverrideContentType($objWriter, '/xl/comments' . ($i + 1) . '.xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml'); + } + } + + // Add media content-types + $aMediaContentTypes = []; + $mediaCount = $this->getParentWriter()->getDrawingHashTable()->count(); + for ($i = 0; $i < $mediaCount; ++$i) { + $extension = ''; + $mimeType = ''; + + if ($this->getParentWriter()->getDrawingHashTable()->getByIndex($i) instanceof \PhpOffice\PhpSpreadsheet\Worksheet\Drawing) { + $extension = strtolower($this->getParentWriter()->getDrawingHashTable()->getByIndex($i)->getExtension()); + $mimeType = $this->getImageMimeType($this->getParentWriter()->getDrawingHashTable()->getByIndex($i)->getPath()); + } elseif ($this->getParentWriter()->getDrawingHashTable()->getByIndex($i) instanceof MemoryDrawing) { + $extension = strtolower($this->getParentWriter()->getDrawingHashTable()->getByIndex($i)->getMimeType()); + $extension = explode('/', $extension); + $extension = $extension[1]; + + $mimeType = $this->getParentWriter()->getDrawingHashTable()->getByIndex($i)->getMimeType(); + } + + if (!isset($aMediaContentTypes[$extension])) { + $aMediaContentTypes[$extension] = $mimeType; + + $this->writeDefaultContentType($objWriter, $extension, $mimeType); + } + } + if ($spreadsheet->hasRibbonBinObjects()) { + // Some additional objects in the ribbon ? + // we need to write "Extension" but not already write for media content + $tabRibbonTypes = array_diff($spreadsheet->getRibbonBinObjects('types') ?? [], array_keys($aMediaContentTypes)); + foreach ($tabRibbonTypes as $aRibbonType) { + $mimeType = 'image/.' . $aRibbonType; //we wrote $mimeType like customUI Editor + $this->writeDefaultContentType($objWriter, $aRibbonType, $mimeType); + } + } + $sheetCount = $spreadsheet->getSheetCount(); + for ($i = 0; $i < $sheetCount; ++$i) { + if (count($spreadsheet->getSheet($i)->getHeaderFooter()->getImages()) > 0) { + foreach ($spreadsheet->getSheet($i)->getHeaderFooter()->getImages() as $image) { + if (!isset($aMediaContentTypes[strtolower($image->getExtension())])) { + $aMediaContentTypes[strtolower($image->getExtension())] = $this->getImageMimeType($image->getPath()); + + $this->writeDefaultContentType($objWriter, strtolower($image->getExtension()), $aMediaContentTypes[strtolower($image->getExtension())]); + } + } + } + } + + // unparsed defaults + if (isset($unparsedLoadedData['default_content_types'])) { + foreach ($unparsedLoadedData['default_content_types'] as $extName => $contentType) { + $this->writeDefaultContentType($objWriter, $extName, $contentType); + } + } + + // unparsed overrides + if (isset($unparsedLoadedData['override_content_types'])) { + foreach ($unparsedLoadedData['override_content_types'] as $partName => $overrideType) { + $this->writeOverrideContentType($objWriter, $partName, $overrideType); + } + } + + $objWriter->endElement(); + + // Return + return $objWriter->getData(); + } + + /** + * Get image mime type. + * + * @param string $filename Filename + * + * @return string Mime Type + */ + private function getImageMimeType($filename) + { + if (File::fileExists($filename)) { + $image = getimagesize($filename); + + return image_type_to_mime_type((is_array($image) && count($image) >= 3) ? $image[2] : 0); + } + + throw new WriterException("File $filename does not exist"); + } + + /** + * Write Default content type. + * + * @param string $partName Part name + * @param string $contentType Content type + */ + private function writeDefaultContentType(XMLWriter $objWriter, $partName, $contentType): void + { + if ($partName != '' && $contentType != '') { + // Write content type + $objWriter->startElement('Default'); + $objWriter->writeAttribute('Extension', $partName); + $objWriter->writeAttribute('ContentType', $contentType); + $objWriter->endElement(); + } else { + throw new WriterException('Invalid parameters passed.'); + } + } + + /** + * Write Override content type. + * + * @param string $partName Part name + * @param string $contentType Content type + */ + private function writeOverrideContentType(XMLWriter $objWriter, $partName, $contentType): void + { + if ($partName != '' && $contentType != '') { + // Write content type + $objWriter->startElement('Override'); + $objWriter->writeAttribute('PartName', $partName); + $objWriter->writeAttribute('ContentType', $contentType); + $objWriter->endElement(); + } else { + throw new WriterException('Invalid parameters passed.'); + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/DefinedNames.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/DefinedNames.php new file mode 100644 index 0000000..b8285fc --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/DefinedNames.php @@ -0,0 +1,242 @@ +objWriter = $objWriter; + $this->spreadsheet = $spreadsheet; + } + + public function write(): void + { + // Write defined names + $this->objWriter->startElement('definedNames'); + + // Named ranges + if (count($this->spreadsheet->getDefinedNames()) > 0) { + // Named ranges + $this->writeNamedRangesAndFormulae(); + } + + // Other defined names + $sheetCount = $this->spreadsheet->getSheetCount(); + for ($i = 0; $i < $sheetCount; ++$i) { + // NamedRange for autoFilter + $this->writeNamedRangeForAutofilter($this->spreadsheet->getSheet($i), $i); + + // NamedRange for Print_Titles + $this->writeNamedRangeForPrintTitles($this->spreadsheet->getSheet($i), $i); + + // NamedRange for Print_Area + $this->writeNamedRangeForPrintArea($this->spreadsheet->getSheet($i), $i); + } + + $this->objWriter->endElement(); + } + + /** + * Write defined names. + */ + private function writeNamedRangesAndFormulae(): void + { + // Loop named ranges + $definedNames = $this->spreadsheet->getDefinedNames(); + foreach ($definedNames as $definedName) { + $this->writeDefinedName($definedName); + } + } + + /** + * Write Defined Name for named range. + */ + private function writeDefinedName(DefinedName $definedName): void + { + // definedName for named range + $local = -1; + if ($definedName->getLocalOnly() && $definedName->getScope() !== null) { + try { + $local = $definedName->getScope()->getParent()->getIndex($definedName->getScope()); + } catch (Exception $e) { + // See issue 2266 - deleting sheet which contains + // defined names will cause Exception above. + return; + } + } + $this->objWriter->startElement('definedName'); + $this->objWriter->writeAttribute('name', $definedName->getName()); + if ($local >= 0) { + $this->objWriter->writeAttribute( + 'localSheetId', + "$local" + ); + } + + $definedRange = $this->getDefinedRange($definedName); + + $this->objWriter->writeRawData($definedRange); + + $this->objWriter->endElement(); + } + + /** + * Write Defined Name for autoFilter. + */ + private function writeNamedRangeForAutofilter(Worksheet $worksheet, int $worksheetId = 0): void + { + // NamedRange for autoFilter + $autoFilterRange = $worksheet->getAutoFilter()->getRange(); + if (!empty($autoFilterRange)) { + $this->objWriter->startElement('definedName'); + $this->objWriter->writeAttribute('name', '_xlnm._FilterDatabase'); + $this->objWriter->writeAttribute('localSheetId', "$worksheetId"); + $this->objWriter->writeAttribute('hidden', '1'); + + // Create absolute coordinate and write as raw text + $range = Coordinate::splitRange($autoFilterRange); + $range = $range[0]; + // Strip any worksheet ref so we can make the cell ref absolute + [, $range[0]] = Worksheet::extractSheetTitle($range[0], true); + + $range[0] = Coordinate::absoluteCoordinate($range[0]); + $range[1] = Coordinate::absoluteCoordinate($range[1]); + $range = implode(':', $range); + + $this->objWriter->writeRawData('\'' . str_replace("'", "''", $worksheet->getTitle() ?? '') . '\'!' . $range); + + $this->objWriter->endElement(); + } + } + + /** + * Write Defined Name for PrintTitles. + */ + private function writeNamedRangeForPrintTitles(Worksheet $worksheet, int $worksheetId = 0): void + { + // NamedRange for PrintTitles + if ($worksheet->getPageSetup()->isColumnsToRepeatAtLeftSet() || $worksheet->getPageSetup()->isRowsToRepeatAtTopSet()) { + $this->objWriter->startElement('definedName'); + $this->objWriter->writeAttribute('name', '_xlnm.Print_Titles'); + $this->objWriter->writeAttribute('localSheetId', "$worksheetId"); + + // Setting string + $settingString = ''; + + // Columns to repeat + if ($worksheet->getPageSetup()->isColumnsToRepeatAtLeftSet()) { + $repeat = $worksheet->getPageSetup()->getColumnsToRepeatAtLeft(); + + $settingString .= '\'' . str_replace("'", "''", $worksheet->getTitle()) . '\'!$' . $repeat[0] . ':$' . $repeat[1]; + } + + // Rows to repeat + if ($worksheet->getPageSetup()->isRowsToRepeatAtTopSet()) { + if ($worksheet->getPageSetup()->isColumnsToRepeatAtLeftSet()) { + $settingString .= ','; + } + + $repeat = $worksheet->getPageSetup()->getRowsToRepeatAtTop(); + + $settingString .= '\'' . str_replace("'", "''", $worksheet->getTitle()) . '\'!$' . $repeat[0] . ':$' . $repeat[1]; + } + + $this->objWriter->writeRawData($settingString); + + $this->objWriter->endElement(); + } + } + + /** + * Write Defined Name for PrintTitles. + */ + private function writeNamedRangeForPrintArea(Worksheet $worksheet, int $worksheetId = 0): void + { + // NamedRange for PrintArea + if ($worksheet->getPageSetup()->isPrintAreaSet()) { + $this->objWriter->startElement('definedName'); + $this->objWriter->writeAttribute('name', '_xlnm.Print_Area'); + $this->objWriter->writeAttribute('localSheetId', "$worksheetId"); + + // Print area + $printArea = Coordinate::splitRange($worksheet->getPageSetup()->getPrintArea()); + + $chunks = []; + foreach ($printArea as $printAreaRect) { + $printAreaRect[0] = Coordinate::absoluteReference($printAreaRect[0]); + $printAreaRect[1] = Coordinate::absoluteReference($printAreaRect[1]); + $chunks[] = '\'' . str_replace("'", "''", $worksheet->getTitle()) . '\'!' . implode(':', $printAreaRect); + } + + $this->objWriter->writeRawData(implode(',', $chunks)); + + $this->objWriter->endElement(); + } + } + + private function getDefinedRange(DefinedName $definedName): string + { + $definedRange = $definedName->getValue(); + $splitCount = preg_match_all( + '/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/mui', + $definedRange, + $splitRanges, + PREG_OFFSET_CAPTURE + ); + + $lengths = array_map('strlen', array_column($splitRanges[0], 0)); + $offsets = array_column($splitRanges[0], 1); + + $worksheets = $splitRanges[2]; + $columns = $splitRanges[6]; + $rows = $splitRanges[7]; + + while ($splitCount > 0) { + --$splitCount; + $length = $lengths[$splitCount]; + $offset = $offsets[$splitCount]; + $worksheet = $worksheets[$splitCount][0]; + $column = $columns[$splitCount][0]; + $row = $rows[$splitCount][0]; + + $newRange = ''; + if (empty($worksheet)) { + if (($offset === 0) || ($definedRange[$offset - 1] !== ':')) { + // We should have a worksheet + $ws = $definedName->getWorksheet(); + $worksheet = ($ws === null) ? null : $ws->getTitle(); + } + } else { + $worksheet = str_replace("''", "'", trim($worksheet, "'")); + } + + if (!empty($worksheet)) { + $newRange = "'" . str_replace("'", "''", $worksheet) . "'!"; + } + $newRange = "{$newRange}{$column}{$row}"; + + $definedRange = substr($definedRange, 0, $offset) . $newRange . substr($definedRange, $offset + $length); + } + + if (substr($definedRange, 0, 1) === '=') { + $definedRange = substr($definedRange, 1); + } + + return $definedRange; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/DocProps.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/DocProps.php new file mode 100644 index 0000000..43ce442 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/DocProps.php @@ -0,0 +1,246 @@ +getParentWriter()->getUseDiskCaching()) { + $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory()); + } else { + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + } + + // XML header + $objWriter->startDocument('1.0', 'UTF-8', 'yes'); + + // Properties + $objWriter->startElement('Properties'); + $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/officeDocument/2006/extended-properties'); + $objWriter->writeAttribute('xmlns:vt', 'http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes'); + + // Application + $objWriter->writeElement('Application', 'Microsoft Excel'); + + // DocSecurity + $objWriter->writeElement('DocSecurity', '0'); + + // ScaleCrop + $objWriter->writeElement('ScaleCrop', 'false'); + + // HeadingPairs + $objWriter->startElement('HeadingPairs'); + + // Vector + $objWriter->startElement('vt:vector'); + $objWriter->writeAttribute('size', '2'); + $objWriter->writeAttribute('baseType', 'variant'); + + // Variant + $objWriter->startElement('vt:variant'); + $objWriter->writeElement('vt:lpstr', 'Worksheets'); + $objWriter->endElement(); + + // Variant + $objWriter->startElement('vt:variant'); + $objWriter->writeElement('vt:i4', $spreadsheet->getSheetCount()); + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + // TitlesOfParts + $objWriter->startElement('TitlesOfParts'); + + // Vector + $objWriter->startElement('vt:vector'); + $objWriter->writeAttribute('size', $spreadsheet->getSheetCount()); + $objWriter->writeAttribute('baseType', 'lpstr'); + + $sheetCount = $spreadsheet->getSheetCount(); + for ($i = 0; $i < $sheetCount; ++$i) { + $objWriter->writeElement('vt:lpstr', $spreadsheet->getSheet($i)->getTitle()); + } + + $objWriter->endElement(); + + $objWriter->endElement(); + + // Company + $objWriter->writeElement('Company', $spreadsheet->getProperties()->getCompany()); + + // Company + $objWriter->writeElement('Manager', $spreadsheet->getProperties()->getManager()); + + // LinksUpToDate + $objWriter->writeElement('LinksUpToDate', 'false'); + + // SharedDoc + $objWriter->writeElement('SharedDoc', 'false'); + + // HyperlinksChanged + $objWriter->writeElement('HyperlinksChanged', 'false'); + + // AppVersion + $objWriter->writeElement('AppVersion', '12.0000'); + + $objWriter->endElement(); + + // Return + return $objWriter->getData(); + } + + /** + * Write docProps/core.xml to XML format. + * + * @return string XML Output + */ + public function writeDocPropsCore(Spreadsheet $spreadsheet) + { + // Create XML writer + $objWriter = null; + if ($this->getParentWriter()->getUseDiskCaching()) { + $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory()); + } else { + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + } + + // XML header + $objWriter->startDocument('1.0', 'UTF-8', 'yes'); + + // cp:coreProperties + $objWriter->startElement('cp:coreProperties'); + $objWriter->writeAttribute('xmlns:cp', 'http://schemas.openxmlformats.org/package/2006/metadata/core-properties'); + $objWriter->writeAttribute('xmlns:dc', 'http://purl.org/dc/elements/1.1/'); + $objWriter->writeAttribute('xmlns:dcterms', 'http://purl.org/dc/terms/'); + $objWriter->writeAttribute('xmlns:dcmitype', 'http://purl.org/dc/dcmitype/'); + $objWriter->writeAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'); + + // dc:creator + $objWriter->writeElement('dc:creator', $spreadsheet->getProperties()->getCreator()); + + // cp:lastModifiedBy + $objWriter->writeElement('cp:lastModifiedBy', $spreadsheet->getProperties()->getLastModifiedBy()); + + // dcterms:created + $objWriter->startElement('dcterms:created'); + $objWriter->writeAttribute('xsi:type', 'dcterms:W3CDTF'); + $created = $spreadsheet->getProperties()->getCreated(); + $date = Date::dateTimeFromTimestamp("$created"); + $objWriter->writeRawData($date->format(DATE_W3C)); + $objWriter->endElement(); + + // dcterms:modified + $objWriter->startElement('dcterms:modified'); + $objWriter->writeAttribute('xsi:type', 'dcterms:W3CDTF'); + $created = $spreadsheet->getProperties()->getModified(); + $date = Date::dateTimeFromTimestamp("$created"); + $objWriter->writeRawData($date->format(DATE_W3C)); + $objWriter->endElement(); + + // dc:title + $objWriter->writeElement('dc:title', $spreadsheet->getProperties()->getTitle()); + + // dc:description + $objWriter->writeElement('dc:description', $spreadsheet->getProperties()->getDescription()); + + // dc:subject + $objWriter->writeElement('dc:subject', $spreadsheet->getProperties()->getSubject()); + + // cp:keywords + $objWriter->writeElement('cp:keywords', $spreadsheet->getProperties()->getKeywords()); + + // cp:category + $objWriter->writeElement('cp:category', $spreadsheet->getProperties()->getCategory()); + + $objWriter->endElement(); + + // Return + return $objWriter->getData(); + } + + /** + * Write docProps/custom.xml to XML format. + * + * @return null|string XML Output + */ + public function writeDocPropsCustom(Spreadsheet $spreadsheet) + { + $customPropertyList = $spreadsheet->getProperties()->getCustomProperties(); + if (empty($customPropertyList)) { + return null; + } + + // Create XML writer + $objWriter = null; + if ($this->getParentWriter()->getUseDiskCaching()) { + $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory()); + } else { + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + } + + // XML header + $objWriter->startDocument('1.0', 'UTF-8', 'yes'); + + // cp:coreProperties + $objWriter->startElement('Properties'); + $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/officeDocument/2006/custom-properties'); + $objWriter->writeAttribute('xmlns:vt', 'http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes'); + + foreach ($customPropertyList as $key => $customProperty) { + $propertyValue = $spreadsheet->getProperties()->getCustomPropertyValue($customProperty); + $propertyType = $spreadsheet->getProperties()->getCustomPropertyType($customProperty); + + $objWriter->startElement('property'); + $objWriter->writeAttribute('fmtid', '{D5CDD505-2E9C-101B-9397-08002B2CF9AE}'); + $objWriter->writeAttribute('pid', $key + 2); + $objWriter->writeAttribute('name', $customProperty); + + switch ($propertyType) { + case Properties::PROPERTY_TYPE_INTEGER: + $objWriter->writeElement('vt:i4', $propertyValue); + + break; + case Properties::PROPERTY_TYPE_FLOAT: + $objWriter->writeElement('vt:r8', $propertyValue); + + break; + case Properties::PROPERTY_TYPE_BOOLEAN: + $objWriter->writeElement('vt:bool', ($propertyValue) ? 'true' : 'false'); + + break; + case Properties::PROPERTY_TYPE_DATE: + $objWriter->startElement('vt:filetime'); + $date = Date::dateTimeFromTimestamp("$propertyValue"); + $objWriter->writeRawData($date->format(DATE_W3C)); + $objWriter->endElement(); + + break; + default: + $objWriter->writeElement('vt:lpwstr', $propertyValue); + + break; + } + + $objWriter->endElement(); + } + + $objWriter->endElement(); + + return $objWriter->getData(); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php new file mode 100644 index 0000000..fa77e2d --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php @@ -0,0 +1,500 @@ +getParentWriter()->getUseDiskCaching()) { + $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory()); + } else { + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + } + + // XML header + $objWriter->startDocument('1.0', 'UTF-8', 'yes'); + + // xdr:wsDr + $objWriter->startElement('xdr:wsDr'); + $objWriter->writeAttribute('xmlns:xdr', 'http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing'); + $objWriter->writeAttribute('xmlns:a', 'http://schemas.openxmlformats.org/drawingml/2006/main'); + + // Loop through images and write drawings + $i = 1; + $iterator = $worksheet->getDrawingCollection()->getIterator(); + while ($iterator->valid()) { + /** @var BaseDrawing $pDrawing */ + $pDrawing = $iterator->current(); + $pRelationId = $i; + $hlinkClickId = $pDrawing->getHyperlink() === null ? null : ++$i; + + $this->writeDrawing($objWriter, $pDrawing, $pRelationId, $hlinkClickId); + + $iterator->next(); + ++$i; + } + + if ($includeCharts) { + $chartCount = $worksheet->getChartCount(); + // Loop through charts and write the chart position + if ($chartCount > 0) { + for ($c = 0; $c < $chartCount; ++$c) { + $this->writeChart($objWriter, $worksheet->getChartByIndex($c), $c + $i); + } + } + } + + // unparsed AlternateContent + $unparsedLoadedData = $worksheet->getParent()->getUnparsedLoadedData(); + if (isset($unparsedLoadedData['sheets'][$worksheet->getCodeName()]['drawingAlternateContents'])) { + foreach ($unparsedLoadedData['sheets'][$worksheet->getCodeName()]['drawingAlternateContents'] as $drawingAlternateContent) { + $objWriter->writeRaw($drawingAlternateContent); + } + } + + $objWriter->endElement(); + + // Return + return $objWriter->getData(); + } + + /** + * Write drawings to XML format. + * + * @param int $relationId + */ + public function writeChart(XMLWriter $objWriter, \PhpOffice\PhpSpreadsheet\Chart\Chart $chart, $relationId = -1): void + { + $tl = $chart->getTopLeftPosition(); + $tlColRow = Coordinate::indexesFromString($tl['cell']); + $br = $chart->getBottomRightPosition(); + $brColRow = Coordinate::indexesFromString($br['cell']); + + $objWriter->startElement('xdr:twoCellAnchor'); + + $objWriter->startElement('xdr:from'); + $objWriter->writeElement('xdr:col', $tlColRow[0] - 1); + $objWriter->writeElement('xdr:colOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($tl['xOffset'])); + $objWriter->writeElement('xdr:row', $tlColRow[1] - 1); + $objWriter->writeElement('xdr:rowOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($tl['yOffset'])); + $objWriter->endElement(); + $objWriter->startElement('xdr:to'); + $objWriter->writeElement('xdr:col', $brColRow[0] - 1); + $objWriter->writeElement('xdr:colOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($br['xOffset'])); + $objWriter->writeElement('xdr:row', $brColRow[1] - 1); + $objWriter->writeElement('xdr:rowOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($br['yOffset'])); + $objWriter->endElement(); + + $objWriter->startElement('xdr:graphicFrame'); + $objWriter->writeAttribute('macro', ''); + $objWriter->startElement('xdr:nvGraphicFramePr'); + $objWriter->startElement('xdr:cNvPr'); + $objWriter->writeAttribute('name', 'Chart ' . $relationId); + $objWriter->writeAttribute('id', 1025 * $relationId); + $objWriter->endElement(); + $objWriter->startElement('xdr:cNvGraphicFramePr'); + $objWriter->startElement('a:graphicFrameLocks'); + $objWriter->endElement(); + $objWriter->endElement(); + $objWriter->endElement(); + + $objWriter->startElement('xdr:xfrm'); + $objWriter->startElement('a:off'); + $objWriter->writeAttribute('x', '0'); + $objWriter->writeAttribute('y', '0'); + $objWriter->endElement(); + $objWriter->startElement('a:ext'); + $objWriter->writeAttribute('cx', '0'); + $objWriter->writeAttribute('cy', '0'); + $objWriter->endElement(); + $objWriter->endElement(); + + $objWriter->startElement('a:graphic'); + $objWriter->startElement('a:graphicData'); + $objWriter->writeAttribute('uri', 'http://schemas.openxmlformats.org/drawingml/2006/chart'); + $objWriter->startElement('c:chart'); + $objWriter->writeAttribute('xmlns:c', 'http://schemas.openxmlformats.org/drawingml/2006/chart'); + $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + $objWriter->writeAttribute('r:id', 'rId' . $relationId); + $objWriter->endElement(); + $objWriter->endElement(); + $objWriter->endElement(); + $objWriter->endElement(); + + $objWriter->startElement('xdr:clientData'); + $objWriter->endElement(); + + $objWriter->endElement(); + } + + /** + * Write drawings to XML format. + * + * @param int $relationId + * @param null|int $hlinkClickId + */ + public function writeDrawing(XMLWriter $objWriter, BaseDrawing $drawing, $relationId = -1, $hlinkClickId = null): void + { + if ($relationId >= 0) { + // xdr:oneCellAnchor + $objWriter->startElement('xdr:oneCellAnchor'); + // Image location + $aCoordinates = Coordinate::indexesFromString($drawing->getCoordinates()); + + // xdr:from + $objWriter->startElement('xdr:from'); + $objWriter->writeElement('xdr:col', $aCoordinates[0] - 1); + $objWriter->writeElement('xdr:colOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($drawing->getOffsetX())); + $objWriter->writeElement('xdr:row', $aCoordinates[1] - 1); + $objWriter->writeElement('xdr:rowOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($drawing->getOffsetY())); + $objWriter->endElement(); + + // xdr:ext + $objWriter->startElement('xdr:ext'); + $objWriter->writeAttribute('cx', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($drawing->getWidth())); + $objWriter->writeAttribute('cy', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($drawing->getHeight())); + $objWriter->endElement(); + + // xdr:pic + $objWriter->startElement('xdr:pic'); + + // xdr:nvPicPr + $objWriter->startElement('xdr:nvPicPr'); + + // xdr:cNvPr + $objWriter->startElement('xdr:cNvPr'); + $objWriter->writeAttribute('id', $relationId); + $objWriter->writeAttribute('name', $drawing->getName()); + $objWriter->writeAttribute('descr', $drawing->getDescription()); + + //a:hlinkClick + $this->writeHyperLinkDrawing($objWriter, $hlinkClickId); + + $objWriter->endElement(); + + // xdr:cNvPicPr + $objWriter->startElement('xdr:cNvPicPr'); + + // a:picLocks + $objWriter->startElement('a:picLocks'); + $objWriter->writeAttribute('noChangeAspect', '1'); + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + // xdr:blipFill + $objWriter->startElement('xdr:blipFill'); + + // a:blip + $objWriter->startElement('a:blip'); + $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + $objWriter->writeAttribute('r:embed', 'rId' . $relationId); + $objWriter->endElement(); + + // a:stretch + $objWriter->startElement('a:stretch'); + $objWriter->writeElement('a:fillRect', null); + $objWriter->endElement(); + + $objWriter->endElement(); + + // xdr:spPr + $objWriter->startElement('xdr:spPr'); + + // a:xfrm + $objWriter->startElement('a:xfrm'); + $objWriter->writeAttribute('rot', \PhpOffice\PhpSpreadsheet\Shared\Drawing::degreesToAngle($drawing->getRotation())); + $objWriter->endElement(); + + // a:prstGeom + $objWriter->startElement('a:prstGeom'); + $objWriter->writeAttribute('prst', 'rect'); + + // a:avLst + $objWriter->writeElement('a:avLst', null); + + $objWriter->endElement(); + + if ($drawing->getShadow()->getVisible()) { + // a:effectLst + $objWriter->startElement('a:effectLst'); + + // a:outerShdw + $objWriter->startElement('a:outerShdw'); + $objWriter->writeAttribute('blurRad', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($drawing->getShadow()->getBlurRadius())); + $objWriter->writeAttribute('dist', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($drawing->getShadow()->getDistance())); + $objWriter->writeAttribute('dir', \PhpOffice\PhpSpreadsheet\Shared\Drawing::degreesToAngle($drawing->getShadow()->getDirection())); + $objWriter->writeAttribute('algn', $drawing->getShadow()->getAlignment()); + $objWriter->writeAttribute('rotWithShape', '0'); + + // a:srgbClr + $objWriter->startElement('a:srgbClr'); + $objWriter->writeAttribute('val', $drawing->getShadow()->getColor()->getRGB()); + + // a:alpha + $objWriter->startElement('a:alpha'); + $objWriter->writeAttribute('val', $drawing->getShadow()->getAlpha() * 1000); + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + } + $objWriter->endElement(); + + $objWriter->endElement(); + + // xdr:clientData + $objWriter->writeElement('xdr:clientData', null); + + $objWriter->endElement(); + } else { + throw new WriterException('Invalid parameters passed.'); + } + } + + /** + * Write VML header/footer images to XML format. + * + * @return string XML Output + */ + public function writeVMLHeaderFooterImages(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $worksheet) + { + // Create XML writer + $objWriter = null; + if ($this->getParentWriter()->getUseDiskCaching()) { + $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory()); + } else { + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + } + + // XML header + $objWriter->startDocument('1.0', 'UTF-8', 'yes'); + + // Header/footer images + $images = $worksheet->getHeaderFooter()->getImages(); + + // xml + $objWriter->startElement('xml'); + $objWriter->writeAttribute('xmlns:v', 'urn:schemas-microsoft-com:vml'); + $objWriter->writeAttribute('xmlns:o', 'urn:schemas-microsoft-com:office:office'); + $objWriter->writeAttribute('xmlns:x', 'urn:schemas-microsoft-com:office:excel'); + + // o:shapelayout + $objWriter->startElement('o:shapelayout'); + $objWriter->writeAttribute('v:ext', 'edit'); + + // o:idmap + $objWriter->startElement('o:idmap'); + $objWriter->writeAttribute('v:ext', 'edit'); + $objWriter->writeAttribute('data', '1'); + $objWriter->endElement(); + + $objWriter->endElement(); + + // v:shapetype + $objWriter->startElement('v:shapetype'); + $objWriter->writeAttribute('id', '_x0000_t75'); + $objWriter->writeAttribute('coordsize', '21600,21600'); + $objWriter->writeAttribute('o:spt', '75'); + $objWriter->writeAttribute('o:preferrelative', 't'); + $objWriter->writeAttribute('path', 'm@4@5l@4@11@9@11@9@5xe'); + $objWriter->writeAttribute('filled', 'f'); + $objWriter->writeAttribute('stroked', 'f'); + + // v:stroke + $objWriter->startElement('v:stroke'); + $objWriter->writeAttribute('joinstyle', 'miter'); + $objWriter->endElement(); + + // v:formulas + $objWriter->startElement('v:formulas'); + + // v:f + $objWriter->startElement('v:f'); + $objWriter->writeAttribute('eqn', 'if lineDrawn pixelLineWidth 0'); + $objWriter->endElement(); + + // v:f + $objWriter->startElement('v:f'); + $objWriter->writeAttribute('eqn', 'sum @0 1 0'); + $objWriter->endElement(); + + // v:f + $objWriter->startElement('v:f'); + $objWriter->writeAttribute('eqn', 'sum 0 0 @1'); + $objWriter->endElement(); + + // v:f + $objWriter->startElement('v:f'); + $objWriter->writeAttribute('eqn', 'prod @2 1 2'); + $objWriter->endElement(); + + // v:f + $objWriter->startElement('v:f'); + $objWriter->writeAttribute('eqn', 'prod @3 21600 pixelWidth'); + $objWriter->endElement(); + + // v:f + $objWriter->startElement('v:f'); + $objWriter->writeAttribute('eqn', 'prod @3 21600 pixelHeight'); + $objWriter->endElement(); + + // v:f + $objWriter->startElement('v:f'); + $objWriter->writeAttribute('eqn', 'sum @0 0 1'); + $objWriter->endElement(); + + // v:f + $objWriter->startElement('v:f'); + $objWriter->writeAttribute('eqn', 'prod @6 1 2'); + $objWriter->endElement(); + + // v:f + $objWriter->startElement('v:f'); + $objWriter->writeAttribute('eqn', 'prod @7 21600 pixelWidth'); + $objWriter->endElement(); + + // v:f + $objWriter->startElement('v:f'); + $objWriter->writeAttribute('eqn', 'sum @8 21600 0'); + $objWriter->endElement(); + + // v:f + $objWriter->startElement('v:f'); + $objWriter->writeAttribute('eqn', 'prod @7 21600 pixelHeight'); + $objWriter->endElement(); + + // v:f + $objWriter->startElement('v:f'); + $objWriter->writeAttribute('eqn', 'sum @10 21600 0'); + $objWriter->endElement(); + + $objWriter->endElement(); + + // v:path + $objWriter->startElement('v:path'); + $objWriter->writeAttribute('o:extrusionok', 'f'); + $objWriter->writeAttribute('gradientshapeok', 't'); + $objWriter->writeAttribute('o:connecttype', 'rect'); + $objWriter->endElement(); + + // o:lock + $objWriter->startElement('o:lock'); + $objWriter->writeAttribute('v:ext', 'edit'); + $objWriter->writeAttribute('aspectratio', 't'); + $objWriter->endElement(); + + $objWriter->endElement(); + + // Loop through images + foreach ($images as $key => $value) { + $this->writeVMLHeaderFooterImage($objWriter, $key, $value); + } + + $objWriter->endElement(); + + // Return + return $objWriter->getData(); + } + + /** + * Write VML comment to XML format. + * + * @param string $reference Reference + */ + private function writeVMLHeaderFooterImage(XMLWriter $objWriter, $reference, HeaderFooterDrawing $image): void + { + // Calculate object id + preg_match('{(\d+)}', md5($reference), $m); + $id = 1500 + ((int) substr($m[1], 0, 2) * 1); + + // Calculate offset + $width = $image->getWidth(); + $height = $image->getHeight(); + $marginLeft = $image->getOffsetX(); + $marginTop = $image->getOffsetY(); + + // v:shape + $objWriter->startElement('v:shape'); + $objWriter->writeAttribute('id', $reference); + $objWriter->writeAttribute('o:spid', '_x0000_s' . $id); + $objWriter->writeAttribute('type', '#_x0000_t75'); + $objWriter->writeAttribute('style', "position:absolute;margin-left:{$marginLeft}px;margin-top:{$marginTop}px;width:{$width}px;height:{$height}px;z-index:1"); + + // v:imagedata + $objWriter->startElement('v:imagedata'); + $objWriter->writeAttribute('o:relid', 'rId' . $reference); + $objWriter->writeAttribute('o:title', $image->getName()); + $objWriter->endElement(); + + // o:lock + $objWriter->startElement('o:lock'); + $objWriter->writeAttribute('v:ext', 'edit'); + $objWriter->writeAttribute('textRotation', 't'); + $objWriter->endElement(); + + $objWriter->endElement(); + } + + /** + * Get an array of all drawings. + * + * @return BaseDrawing[] All drawings in PhpSpreadsheet + */ + public function allDrawings(Spreadsheet $spreadsheet) + { + // Get an array of all drawings + $aDrawings = []; + + // Loop through PhpSpreadsheet + $sheetCount = $spreadsheet->getSheetCount(); + for ($i = 0; $i < $sheetCount; ++$i) { + // Loop through images and add to array + $iterator = $spreadsheet->getSheet($i)->getDrawingCollection()->getIterator(); + while ($iterator->valid()) { + $aDrawings[] = $iterator->current(); + + $iterator->next(); + } + } + + return $aDrawings; + } + + /** + * @param null|int $hlinkClickId + */ + private function writeHyperLinkDrawing(XMLWriter $objWriter, $hlinkClickId): void + { + if ($hlinkClickId === null) { + return; + } + + $objWriter->startElement('a:hlinkClick'); + $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + $objWriter->writeAttribute('r:id', 'rId' . $hlinkClickId); + $objWriter->endElement(); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Rels.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Rels.php new file mode 100644 index 0000000..fce54c3 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Rels.php @@ -0,0 +1,443 @@ +getParentWriter()->getUseDiskCaching()) { + $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory()); + } else { + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + } + + // XML header + $objWriter->startDocument('1.0', 'UTF-8', 'yes'); + + // Relationships + $objWriter->startElement('Relationships'); + $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships'); + + $customPropertyList = $spreadsheet->getProperties()->getCustomProperties(); + if (!empty($customPropertyList)) { + // Relationship docProps/app.xml + $this->writeRelationship( + $objWriter, + 4, + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties', + 'docProps/custom.xml' + ); + } + + // Relationship docProps/app.xml + $this->writeRelationship( + $objWriter, + 3, + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties', + 'docProps/app.xml' + ); + + // Relationship docProps/core.xml + $this->writeRelationship( + $objWriter, + 2, + 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties', + 'docProps/core.xml' + ); + + // Relationship xl/workbook.xml + $this->writeRelationship( + $objWriter, + 1, + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument', + 'xl/workbook.xml' + ); + // a custom UI in workbook ? + if ($spreadsheet->hasRibbon()) { + $this->writeRelationShip( + $objWriter, + 5, + 'http://schemas.microsoft.com/office/2006/relationships/ui/extensibility', + $spreadsheet->getRibbonXMLData('target') + ); + } + + $objWriter->endElement(); + + return $objWriter->getData(); + } + + /** + * Write workbook relationships to XML format. + * + * @return string XML Output + */ + public function writeWorkbookRelationships(Spreadsheet $spreadsheet) + { + // Create XML writer + $objWriter = null; + if ($this->getParentWriter()->getUseDiskCaching()) { + $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory()); + } else { + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + } + + // XML header + $objWriter->startDocument('1.0', 'UTF-8', 'yes'); + + // Relationships + $objWriter->startElement('Relationships'); + $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships'); + + // Relationship styles.xml + $this->writeRelationship( + $objWriter, + 1, + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles', + 'styles.xml' + ); + + // Relationship theme/theme1.xml + $this->writeRelationship( + $objWriter, + 2, + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme', + 'theme/theme1.xml' + ); + + // Relationship sharedStrings.xml + $this->writeRelationship( + $objWriter, + 3, + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings', + 'sharedStrings.xml' + ); + + // Relationships with sheets + $sheetCount = $spreadsheet->getSheetCount(); + for ($i = 0; $i < $sheetCount; ++$i) { + $this->writeRelationship( + $objWriter, + ($i + 1 + 3), + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet', + 'worksheets/sheet' . ($i + 1) . '.xml' + ); + } + // Relationships for vbaProject if needed + // id : just after the last sheet + if ($spreadsheet->hasMacros()) { + $this->writeRelationShip( + $objWriter, + ($i + 1 + 3), + 'http://schemas.microsoft.com/office/2006/relationships/vbaProject', + 'vbaProject.bin' + ); + ++$i; //increment i if needed for an another relation + } + + $objWriter->endElement(); + + return $objWriter->getData(); + } + + /** + * Write worksheet relationships to XML format. + * + * Numbering is as follows: + * rId1 - Drawings + * rId_hyperlink_x - Hyperlinks + * + * @param int $worksheetId + * @param bool $includeCharts Flag indicating if we should write charts + * + * @return string XML Output + */ + public function writeWorksheetRelationships(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $worksheet, $worksheetId = 1, $includeCharts = false) + { + // Create XML writer + $objWriter = null; + if ($this->getParentWriter()->getUseDiskCaching()) { + $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory()); + } else { + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + } + + // XML header + $objWriter->startDocument('1.0', 'UTF-8', 'yes'); + + // Relationships + $objWriter->startElement('Relationships'); + $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships'); + + // Write drawing relationships? + $drawingOriginalIds = []; + $unparsedLoadedData = $worksheet->getParent()->getUnparsedLoadedData(); + if (isset($unparsedLoadedData['sheets'][$worksheet->getCodeName()]['drawingOriginalIds'])) { + $drawingOriginalIds = $unparsedLoadedData['sheets'][$worksheet->getCodeName()]['drawingOriginalIds']; + } + + if ($includeCharts) { + $charts = $worksheet->getChartCollection(); + } else { + $charts = []; + } + + if (($worksheet->getDrawingCollection()->count() > 0) || (count($charts) > 0) || $drawingOriginalIds) { + $rId = 1; + + // Use original $relPath to get original $rId. + // Take first. In future can be overwritten. + // (! synchronize with \PhpOffice\PhpSpreadsheet\Writer\Xlsx\Worksheet::writeDrawings) + reset($drawingOriginalIds); + $relPath = key($drawingOriginalIds); + if (isset($drawingOriginalIds[$relPath])) { + $rId = (int) (substr($drawingOriginalIds[$relPath], 3)); + } + + // Generate new $relPath to write drawing relationship + $relPath = '../drawings/drawing' . $worksheetId . '.xml'; + $this->writeRelationship( + $objWriter, + $rId, + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing', + $relPath + ); + } + + // Write hyperlink relationships? + $i = 1; + foreach ($worksheet->getHyperlinkCollection() as $hyperlink) { + if (!$hyperlink->isInternal()) { + $this->writeRelationship( + $objWriter, + '_hyperlink_' . $i, + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink', + $hyperlink->getUrl(), + 'External' + ); + + ++$i; + } + } + + // Write comments relationship? + $i = 1; + if (count($worksheet->getComments()) > 0) { + $this->writeRelationship( + $objWriter, + '_comments_vml' . $i, + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing', + '../drawings/vmlDrawing' . $worksheetId . '.vml' + ); + + $this->writeRelationship( + $objWriter, + '_comments' . $i, + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments', + '../comments' . $worksheetId . '.xml' + ); + } + + // Write header/footer relationship? + $i = 1; + if (count($worksheet->getHeaderFooter()->getImages()) > 0) { + $this->writeRelationship( + $objWriter, + '_headerfooter_vml' . $i, + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing', + '../drawings/vmlDrawingHF' . $worksheetId . '.vml' + ); + } + + $this->writeUnparsedRelationship($worksheet, $objWriter, 'ctrlProps', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/ctrlProp'); + $this->writeUnparsedRelationship($worksheet, $objWriter, 'vmlDrawings', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing'); + $this->writeUnparsedRelationship($worksheet, $objWriter, 'printerSettings', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/printerSettings'); + + $objWriter->endElement(); + + return $objWriter->getData(); + } + + private function writeUnparsedRelationship(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $worksheet, XMLWriter $objWriter, $relationship, $type): void + { + $unparsedLoadedData = $worksheet->getParent()->getUnparsedLoadedData(); + if (!isset($unparsedLoadedData['sheets'][$worksheet->getCodeName()][$relationship])) { + return; + } + + foreach ($unparsedLoadedData['sheets'][$worksheet->getCodeName()][$relationship] as $rId => $value) { + $this->writeRelationship( + $objWriter, + $rId, + $type, + $value['relFilePath'] + ); + } + } + + /** + * Write drawing relationships to XML format. + * + * @param int $chartRef Chart ID + * @param bool $includeCharts Flag indicating if we should write charts + * + * @return string XML Output + */ + public function writeDrawingRelationships(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $worksheet, &$chartRef, $includeCharts = false) + { + // Create XML writer + $objWriter = null; + if ($this->getParentWriter()->getUseDiskCaching()) { + $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory()); + } else { + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + } + + // XML header + $objWriter->startDocument('1.0', 'UTF-8', 'yes'); + + // Relationships + $objWriter->startElement('Relationships'); + $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships'); + + // Loop through images and write relationships + $i = 1; + $iterator = $worksheet->getDrawingCollection()->getIterator(); + while ($iterator->valid()) { + $drawing = $iterator->current(); + if ( + $drawing instanceof \PhpOffice\PhpSpreadsheet\Worksheet\Drawing + || $drawing instanceof MemoryDrawing + ) { + // Write relationship for image drawing + $this->writeRelationship( + $objWriter, + $i, + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image', + '../media/' . str_replace(' ', '', $drawing->getIndexedFilename()) + ); + + $i = $this->writeDrawingHyperLink($objWriter, $drawing, $i); + } + + $iterator->next(); + ++$i; + } + + if ($includeCharts) { + // Loop through charts and write relationships + $chartCount = $worksheet->getChartCount(); + if ($chartCount > 0) { + for ($c = 0; $c < $chartCount; ++$c) { + $this->writeRelationship( + $objWriter, + $i++, + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart', + '../charts/chart' . ++$chartRef . '.xml' + ); + } + } + } + + $objWriter->endElement(); + + return $objWriter->getData(); + } + + /** + * Write header/footer drawing relationships to XML format. + * + * @return string XML Output + */ + public function writeHeaderFooterDrawingRelationships(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $worksheet) + { + // Create XML writer + $objWriter = null; + if ($this->getParentWriter()->getUseDiskCaching()) { + $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory()); + } else { + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + } + + // XML header + $objWriter->startDocument('1.0', 'UTF-8', 'yes'); + + // Relationships + $objWriter->startElement('Relationships'); + $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships'); + + // Loop through images and write relationships + foreach ($worksheet->getHeaderFooter()->getImages() as $key => $value) { + // Write relationship for image drawing + $this->writeRelationship( + $objWriter, + $key, + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image', + '../media/' . $value->getIndexedFilename() + ); + } + + $objWriter->endElement(); + + return $objWriter->getData(); + } + + /** + * Write Override content type. + * + * @param int $id Relationship ID. rId will be prepended! + * @param string $type Relationship type + * @param string $target Relationship target + * @param string $targetMode Relationship target mode + */ + private function writeRelationship(XMLWriter $objWriter, $id, $type, $target, $targetMode = ''): void + { + if ($type != '' && $target != '') { + // Write relationship + $objWriter->startElement('Relationship'); + $objWriter->writeAttribute('Id', 'rId' . $id); + $objWriter->writeAttribute('Type', $type); + $objWriter->writeAttribute('Target', $target); + + if ($targetMode != '') { + $objWriter->writeAttribute('TargetMode', $targetMode); + } + + $objWriter->endElement(); + } else { + throw new WriterException('Invalid parameters passed.'); + } + } + + private function writeDrawingHyperLink(XMLWriter $objWriter, BaseDrawing $drawing, int $i): int + { + if ($drawing->getHyperlink() === null) { + return $i; + } + + ++$i; + $this->writeRelationship( + $objWriter, + $i, + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink', + $drawing->getHyperlink()->getUrl(), + $drawing->getHyperlink()->getTypeHyperlink() + ); + + return $i; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/RelsRibbon.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/RelsRibbon.php new file mode 100644 index 0000000..8005207 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/RelsRibbon.php @@ -0,0 +1,45 @@ +getParentWriter()->getUseDiskCaching()) { + $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory()); + } else { + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + } + + // XML header + $objWriter->startDocument('1.0', 'UTF-8', 'yes'); + + // Relationships + $objWriter->startElement('Relationships'); + $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships'); + $localRels = $spreadsheet->getRibbonBinObjects('names'); + if (is_array($localRels)) { + foreach ($localRels as $aId => $aTarget) { + $objWriter->startElement('Relationship'); + $objWriter->writeAttribute('Id', $aId); + $objWriter->writeAttribute('Type', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image'); + $objWriter->writeAttribute('Target', $aTarget); + $objWriter->endElement(); + } + } + $objWriter->endElement(); + + return $objWriter->getData(); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/RelsVBA.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/RelsVBA.php new file mode 100644 index 0000000..55bcd36 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/RelsVBA.php @@ -0,0 +1,40 @@ +getParentWriter()->getUseDiskCaching()) { + $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory()); + } else { + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + } + + // XML header + $objWriter->startDocument('1.0', 'UTF-8', 'yes'); + + // Relationships + $objWriter->startElement('Relationships'); + $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships'); + $objWriter->startElement('Relationship'); + $objWriter->writeAttribute('Id', 'rId1'); + $objWriter->writeAttribute('Type', 'http://schemas.microsoft.com/office/2006/relationships/vbaProjectSignature'); + $objWriter->writeAttribute('Target', 'vbaProjectSignature.bin'); + $objWriter->endElement(); + $objWriter->endElement(); + + return $objWriter->getData(); + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/StringTable.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/StringTable.php new file mode 100644 index 0000000..f9a2e71 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/StringTable.php @@ -0,0 +1,279 @@ +flipStringTable($aStringTable); + + // Loop through cells + foreach ($worksheet->getCoordinates() as $coordinate) { + $cell = $worksheet->getCell($coordinate); + $cellValue = $cell->getValue(); + if ( + !is_object($cellValue) && + ($cellValue !== null) && + $cellValue !== '' && + ($cell->getDataType() == DataType::TYPE_STRING || $cell->getDataType() == DataType::TYPE_STRING2 || $cell->getDataType() == DataType::TYPE_NULL) && + !isset($aFlippedStringTable[$cellValue]) + ) { + $aStringTable[] = $cellValue; + $aFlippedStringTable[$cellValue] = true; + } elseif ( + $cellValue instanceof RichText && + ($cellValue !== null) && + !isset($aFlippedStringTable[$cellValue->getHashCode()]) + ) { + $aStringTable[] = $cellValue; + $aFlippedStringTable[$cellValue->getHashCode()] = true; + } + } + + return $aStringTable; + } + + /** + * Write string table to XML format. + * + * @param string[] $stringTable + * + * @return string XML Output + */ + public function writeStringTable(array $stringTable) + { + // Create XML writer + $objWriter = null; + if ($this->getParentWriter()->getUseDiskCaching()) { + $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory()); + } else { + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + } + + // XML header + $objWriter->startDocument('1.0', 'UTF-8', 'yes'); + + // String table + $objWriter->startElement('sst'); + $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'); + $objWriter->writeAttribute('uniqueCount', count($stringTable)); + + // Loop through string table + foreach ($stringTable as $textElement) { + $objWriter->startElement('si'); + + if (!$textElement instanceof RichText) { + $textToWrite = StringHelper::controlCharacterPHP2OOXML($textElement); + $objWriter->startElement('t'); + if ($textToWrite !== trim($textToWrite)) { + $objWriter->writeAttribute('xml:space', 'preserve'); + } + $objWriter->writeRawData($textToWrite); + $objWriter->endElement(); + } elseif ($textElement instanceof RichText) { + $this->writeRichText($objWriter, $textElement); + } + + $objWriter->endElement(); + } + + $objWriter->endElement(); + + return $objWriter->getData(); + } + + /** + * Write Rich Text. + * + * @param string $prefix Optional Namespace prefix + */ + public function writeRichText(XMLWriter $objWriter, RichText $richText, $prefix = null): void + { + if ($prefix !== null) { + $prefix .= ':'; + } + + // Loop through rich text elements + $elements = $richText->getRichTextElements(); + foreach ($elements as $element) { + // r + $objWriter->startElement($prefix . 'r'); + + // rPr + if ($element instanceof Run) { + // rPr + $objWriter->startElement($prefix . 'rPr'); + + // rFont + $objWriter->startElement($prefix . 'rFont'); + $objWriter->writeAttribute('val', $element->getFont()->getName()); + $objWriter->endElement(); + + // Bold + $objWriter->startElement($prefix . 'b'); + $objWriter->writeAttribute('val', ($element->getFont()->getBold() ? 'true' : 'false')); + $objWriter->endElement(); + + // Italic + $objWriter->startElement($prefix . 'i'); + $objWriter->writeAttribute('val', ($element->getFont()->getItalic() ? 'true' : 'false')); + $objWriter->endElement(); + + // Superscript / subscript + if ($element->getFont()->getSuperscript() || $element->getFont()->getSubscript()) { + $objWriter->startElement($prefix . 'vertAlign'); + if ($element->getFont()->getSuperscript()) { + $objWriter->writeAttribute('val', 'superscript'); + } elseif ($element->getFont()->getSubscript()) { + $objWriter->writeAttribute('val', 'subscript'); + } + $objWriter->endElement(); + } + + // Strikethrough + $objWriter->startElement($prefix . 'strike'); + $objWriter->writeAttribute('val', ($element->getFont()->getStrikethrough() ? 'true' : 'false')); + $objWriter->endElement(); + + // Color + $objWriter->startElement($prefix . 'color'); + $objWriter->writeAttribute('rgb', $element->getFont()->getColor()->getARGB()); + $objWriter->endElement(); + + // Size + $objWriter->startElement($prefix . 'sz'); + $objWriter->writeAttribute('val', $element->getFont()->getSize()); + $objWriter->endElement(); + + // Underline + $objWriter->startElement($prefix . 'u'); + $objWriter->writeAttribute('val', $element->getFont()->getUnderline()); + $objWriter->endElement(); + + $objWriter->endElement(); + } + + // t + $objWriter->startElement($prefix . 't'); + $objWriter->writeAttribute('xml:space', 'preserve'); + $objWriter->writeRawData(StringHelper::controlCharacterPHP2OOXML($element->getText())); + $objWriter->endElement(); + + $objWriter->endElement(); + } + } + + /** + * Write Rich Text. + * + * @param RichText|string $richText text string or Rich text + * @param string $prefix Optional Namespace prefix + */ + public function writeRichTextForCharts(XMLWriter $objWriter, $richText = null, $prefix = null): void + { + if (!$richText instanceof RichText) { + $textRun = $richText; + $richText = new RichText(); + $richText->createTextRun($textRun); + } + + if ($prefix !== null) { + $prefix .= ':'; + } + + // Loop through rich text elements + $elements = $richText->getRichTextElements(); + foreach ($elements as $element) { + // r + $objWriter->startElement($prefix . 'r'); + + // rPr + $objWriter->startElement($prefix . 'rPr'); + + // Bold + $objWriter->writeAttribute('b', ($element->getFont()->getBold() ? 1 : 0)); + // Italic + $objWriter->writeAttribute('i', ($element->getFont()->getItalic() ? 1 : 0)); + // Underline + $underlineType = $element->getFont()->getUnderline(); + switch ($underlineType) { + case 'single': + $underlineType = 'sng'; + + break; + case 'double': + $underlineType = 'dbl'; + + break; + } + $objWriter->writeAttribute('u', $underlineType); + // Strikethrough + $objWriter->writeAttribute('strike', ($element->getFont()->getStrikethrough() ? 'sngStrike' : 'noStrike')); + + // rFont + $objWriter->startElement($prefix . 'latin'); + $objWriter->writeAttribute('typeface', $element->getFont()->getName()); + $objWriter->endElement(); + + $objWriter->endElement(); + + // t + $objWriter->startElement($prefix . 't'); + $objWriter->writeRawData(StringHelper::controlCharacterPHP2OOXML($element->getText())); + $objWriter->endElement(); + + $objWriter->endElement(); + } + } + + /** + * Flip string table (for index searching). + * + * @param array $stringTable Stringtable + * + * @return array + */ + public function flipStringTable(array $stringTable) + { + // Return value + $returnValue = []; + + // Loop through stringtable and add flipped items to $returnValue + foreach ($stringTable as $key => $value) { + if (!$value instanceof RichText) { + $returnValue[$value] = $key; + } elseif ($value instanceof RichText) { + $returnValue[$value->getHashCode()] = $key; + } + } + + return $returnValue; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Style.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Style.php new file mode 100644 index 0000000..bf306a8 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Style.php @@ -0,0 +1,650 @@ +getParentWriter()->getUseDiskCaching()) { + $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory()); + } else { + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + } + + // XML header + $objWriter->startDocument('1.0', 'UTF-8', 'yes'); + + // styleSheet + $objWriter->startElement('styleSheet'); + $objWriter->writeAttribute('xml:space', 'preserve'); + $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'); + + // numFmts + $objWriter->startElement('numFmts'); + $objWriter->writeAttribute('count', $this->getParentWriter()->getNumFmtHashTable()->count()); + + // numFmt + for ($i = 0; $i < $this->getParentWriter()->getNumFmtHashTable()->count(); ++$i) { + $this->writeNumFmt($objWriter, $this->getParentWriter()->getNumFmtHashTable()->getByIndex($i), $i); + } + + $objWriter->endElement(); + + // fonts + $objWriter->startElement('fonts'); + $objWriter->writeAttribute('count', $this->getParentWriter()->getFontHashTable()->count()); + + // font + for ($i = 0; $i < $this->getParentWriter()->getFontHashTable()->count(); ++$i) { + $this->writeFont($objWriter, $this->getParentWriter()->getFontHashTable()->getByIndex($i)); + } + + $objWriter->endElement(); + + // fills + $objWriter->startElement('fills'); + $objWriter->writeAttribute('count', $this->getParentWriter()->getFillHashTable()->count()); + + // fill + for ($i = 0; $i < $this->getParentWriter()->getFillHashTable()->count(); ++$i) { + $this->writeFill($objWriter, $this->getParentWriter()->getFillHashTable()->getByIndex($i)); + } + + $objWriter->endElement(); + + // borders + $objWriter->startElement('borders'); + $objWriter->writeAttribute('count', $this->getParentWriter()->getBordersHashTable()->count()); + + // border + for ($i = 0; $i < $this->getParentWriter()->getBordersHashTable()->count(); ++$i) { + $this->writeBorder($objWriter, $this->getParentWriter()->getBordersHashTable()->getByIndex($i)); + } + + $objWriter->endElement(); + + // cellStyleXfs + $objWriter->startElement('cellStyleXfs'); + $objWriter->writeAttribute('count', 1); + + // xf + $objWriter->startElement('xf'); + $objWriter->writeAttribute('numFmtId', 0); + $objWriter->writeAttribute('fontId', 0); + $objWriter->writeAttribute('fillId', 0); + $objWriter->writeAttribute('borderId', 0); + $objWriter->endElement(); + + $objWriter->endElement(); + + // cellXfs + $objWriter->startElement('cellXfs'); + $objWriter->writeAttribute('count', count($spreadsheet->getCellXfCollection())); + + // xf + foreach ($spreadsheet->getCellXfCollection() as $cellXf) { + $this->writeCellStyleXf($objWriter, $cellXf, $spreadsheet); + } + + $objWriter->endElement(); + + // cellStyles + $objWriter->startElement('cellStyles'); + $objWriter->writeAttribute('count', 1); + + // cellStyle + $objWriter->startElement('cellStyle'); + $objWriter->writeAttribute('name', 'Normal'); + $objWriter->writeAttribute('xfId', 0); + $objWriter->writeAttribute('builtinId', 0); + $objWriter->endElement(); + + $objWriter->endElement(); + + // dxfs + $objWriter->startElement('dxfs'); + $objWriter->writeAttribute('count', $this->getParentWriter()->getStylesConditionalHashTable()->count()); + + // dxf + for ($i = 0; $i < $this->getParentWriter()->getStylesConditionalHashTable()->count(); ++$i) { + $this->writeCellStyleDxf($objWriter, $this->getParentWriter()->getStylesConditionalHashTable()->getByIndex($i)->getStyle()); + } + + $objWriter->endElement(); + + // tableStyles + $objWriter->startElement('tableStyles'); + $objWriter->writeAttribute('defaultTableStyle', 'TableStyleMedium9'); + $objWriter->writeAttribute('defaultPivotStyle', 'PivotTableStyle1'); + $objWriter->endElement(); + + $objWriter->endElement(); + + // Return + return $objWriter->getData(); + } + + /** + * Write Fill. + */ + private function writeFill(XMLWriter $objWriter, Fill $fill): void + { + // Check if this is a pattern type or gradient type + if ( + $fill->getFillType() === Fill::FILL_GRADIENT_LINEAR || + $fill->getFillType() === Fill::FILL_GRADIENT_PATH + ) { + // Gradient fill + $this->writeGradientFill($objWriter, $fill); + } elseif ($fill->getFillType() !== null) { + // Pattern fill + $this->writePatternFill($objWriter, $fill); + } + } + + /** + * Write Gradient Fill. + */ + private function writeGradientFill(XMLWriter $objWriter, Fill $fill): void + { + // fill + $objWriter->startElement('fill'); + + // gradientFill + $objWriter->startElement('gradientFill'); + $objWriter->writeAttribute('type', $fill->getFillType()); + $objWriter->writeAttribute('degree', $fill->getRotation()); + + // stop + $objWriter->startElement('stop'); + $objWriter->writeAttribute('position', '0'); + + // color + $objWriter->startElement('color'); + $objWriter->writeAttribute('rgb', $fill->getStartColor()->getARGB()); + $objWriter->endElement(); + + $objWriter->endElement(); + + // stop + $objWriter->startElement('stop'); + $objWriter->writeAttribute('position', '1'); + + // color + $objWriter->startElement('color'); + $objWriter->writeAttribute('rgb', $fill->getEndColor()->getARGB()); + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + } + + /** + * Write Pattern Fill. + */ + private function writePatternFill(XMLWriter $objWriter, Fill $fill): void + { + // fill + $objWriter->startElement('fill'); + + // patternFill + $objWriter->startElement('patternFill'); + $objWriter->writeAttribute('patternType', $fill->getFillType()); + + if ($fill->getFillType() !== Fill::FILL_NONE) { + // fgColor + if ($fill->getStartColor()->getARGB()) { + $objWriter->startElement('fgColor'); + $objWriter->writeAttribute('rgb', $fill->getStartColor()->getARGB()); + $objWriter->endElement(); + } + } + if ($fill->getFillType() !== Fill::FILL_NONE) { + // bgColor + if ($fill->getEndColor()->getARGB()) { + $objWriter->startElement('bgColor'); + $objWriter->writeAttribute('rgb', $fill->getEndColor()->getARGB()); + $objWriter->endElement(); + } + } + + $objWriter->endElement(); + + $objWriter->endElement(); + } + + /** + * Write Font. + */ + private function writeFont(XMLWriter $objWriter, Font $font): void + { + // font + $objWriter->startElement('font'); + // Weird! The order of these elements actually makes a difference when opening Xlsx + // files in Excel2003 with the compatibility pack. It's not documented behaviour, + // and makes for a real WTF! + + // Bold. We explicitly write this element also when false (like MS Office Excel 2007 does + // for conditional formatting). Otherwise it will apparently not be picked up in conditional + // formatting style dialog + if ($font->getBold() !== null) { + $objWriter->startElement('b'); + $objWriter->writeAttribute('val', $font->getBold() ? '1' : '0'); + $objWriter->endElement(); + } + + // Italic + if ($font->getItalic() !== null) { + $objWriter->startElement('i'); + $objWriter->writeAttribute('val', $font->getItalic() ? '1' : '0'); + $objWriter->endElement(); + } + + // Strikethrough + if ($font->getStrikethrough() !== null) { + $objWriter->startElement('strike'); + $objWriter->writeAttribute('val', $font->getStrikethrough() ? '1' : '0'); + $objWriter->endElement(); + } + + // Underline + if ($font->getUnderline() !== null) { + $objWriter->startElement('u'); + $objWriter->writeAttribute('val', $font->getUnderline()); + $objWriter->endElement(); + } + + // Superscript / subscript + if ($font->getSuperscript() === true || $font->getSubscript() === true) { + $objWriter->startElement('vertAlign'); + if ($font->getSuperscript() === true) { + $objWriter->writeAttribute('val', 'superscript'); + } elseif ($font->getSubscript() === true) { + $objWriter->writeAttribute('val', 'subscript'); + } + $objWriter->endElement(); + } + + // Size + if ($font->getSize() !== null) { + $objWriter->startElement('sz'); + $objWriter->writeAttribute('val', StringHelper::formatNumber($font->getSize())); + $objWriter->endElement(); + } + + // Foreground color + if ($font->getColor()->getARGB() !== null) { + $objWriter->startElement('color'); + $objWriter->writeAttribute('rgb', $font->getColor()->getARGB()); + $objWriter->endElement(); + } + + // Name + if ($font->getName() !== null) { + $objWriter->startElement('name'); + $objWriter->writeAttribute('val', $font->getName()); + $objWriter->endElement(); + } + + $objWriter->endElement(); + } + + /** + * Write Border. + */ + private function writeBorder(XMLWriter $objWriter, Borders $borders): void + { + // Write border + $objWriter->startElement('border'); + // Diagonal? + switch ($borders->getDiagonalDirection()) { + case Borders::DIAGONAL_UP: + $objWriter->writeAttribute('diagonalUp', 'true'); + $objWriter->writeAttribute('diagonalDown', 'false'); + + break; + case Borders::DIAGONAL_DOWN: + $objWriter->writeAttribute('diagonalUp', 'false'); + $objWriter->writeAttribute('diagonalDown', 'true'); + + break; + case Borders::DIAGONAL_BOTH: + $objWriter->writeAttribute('diagonalUp', 'true'); + $objWriter->writeAttribute('diagonalDown', 'true'); + + break; + } + + // BorderPr + $this->writeBorderPr($objWriter, 'left', $borders->getLeft()); + $this->writeBorderPr($objWriter, 'right', $borders->getRight()); + $this->writeBorderPr($objWriter, 'top', $borders->getTop()); + $this->writeBorderPr($objWriter, 'bottom', $borders->getBottom()); + $this->writeBorderPr($objWriter, 'diagonal', $borders->getDiagonal()); + $objWriter->endElement(); + } + + /** + * Write Cell Style Xf. + */ + private function writeCellStyleXf(XMLWriter $objWriter, \PhpOffice\PhpSpreadsheet\Style\Style $style, Spreadsheet $spreadsheet): void + { + // xf + $objWriter->startElement('xf'); + $objWriter->writeAttribute('xfId', 0); + $objWriter->writeAttribute('fontId', (int) $this->getParentWriter()->getFontHashTable()->getIndexForHashCode($style->getFont()->getHashCode())); + if ($style->getQuotePrefix()) { + $objWriter->writeAttribute('quotePrefix', 1); + } + + if ($style->getNumberFormat()->getBuiltInFormatCode() === false) { + $objWriter->writeAttribute('numFmtId', (int) ($this->getParentWriter()->getNumFmtHashTable()->getIndexForHashCode($style->getNumberFormat()->getHashCode()) + 164)); + } else { + $objWriter->writeAttribute('numFmtId', (int) $style->getNumberFormat()->getBuiltInFormatCode()); + } + + $objWriter->writeAttribute('fillId', (int) $this->getParentWriter()->getFillHashTable()->getIndexForHashCode($style->getFill()->getHashCode())); + $objWriter->writeAttribute('borderId', (int) $this->getParentWriter()->getBordersHashTable()->getIndexForHashCode($style->getBorders()->getHashCode())); + + // Apply styles? + $objWriter->writeAttribute('applyFont', ($spreadsheet->getDefaultStyle()->getFont()->getHashCode() != $style->getFont()->getHashCode()) ? '1' : '0'); + $objWriter->writeAttribute('applyNumberFormat', ($spreadsheet->getDefaultStyle()->getNumberFormat()->getHashCode() != $style->getNumberFormat()->getHashCode()) ? '1' : '0'); + $objWriter->writeAttribute('applyFill', ($spreadsheet->getDefaultStyle()->getFill()->getHashCode() != $style->getFill()->getHashCode()) ? '1' : '0'); + $objWriter->writeAttribute('applyBorder', ($spreadsheet->getDefaultStyle()->getBorders()->getHashCode() != $style->getBorders()->getHashCode()) ? '1' : '0'); + $objWriter->writeAttribute('applyAlignment', ($spreadsheet->getDefaultStyle()->getAlignment()->getHashCode() != $style->getAlignment()->getHashCode()) ? '1' : '0'); + if ($style->getProtection()->getLocked() != Protection::PROTECTION_INHERIT || $style->getProtection()->getHidden() != Protection::PROTECTION_INHERIT) { + $objWriter->writeAttribute('applyProtection', 'true'); + } + + // alignment + $objWriter->startElement('alignment'); + $objWriter->writeAttribute('horizontal', $style->getAlignment()->getHorizontal()); + $objWriter->writeAttribute('vertical', $style->getAlignment()->getVertical()); + + $textRotation = 0; + if ($style->getAlignment()->getTextRotation() >= 0) { + $textRotation = $style->getAlignment()->getTextRotation(); + } elseif ($style->getAlignment()->getTextRotation() < 0) { + $textRotation = 90 - $style->getAlignment()->getTextRotation(); + } + $objWriter->writeAttribute('textRotation', $textRotation); + + $objWriter->writeAttribute('wrapText', ($style->getAlignment()->getWrapText() ? 'true' : 'false')); + $objWriter->writeAttribute('shrinkToFit', ($style->getAlignment()->getShrinkToFit() ? 'true' : 'false')); + + if ($style->getAlignment()->getIndent() > 0) { + $objWriter->writeAttribute('indent', $style->getAlignment()->getIndent()); + } + if ($style->getAlignment()->getReadOrder() > 0) { + $objWriter->writeAttribute('readingOrder', $style->getAlignment()->getReadOrder()); + } + $objWriter->endElement(); + + // protection + if ($style->getProtection()->getLocked() != Protection::PROTECTION_INHERIT || $style->getProtection()->getHidden() != Protection::PROTECTION_INHERIT) { + $objWriter->startElement('protection'); + if ($style->getProtection()->getLocked() != Protection::PROTECTION_INHERIT) { + $objWriter->writeAttribute('locked', ($style->getProtection()->getLocked() == Protection::PROTECTION_PROTECTED ? 'true' : 'false')); + } + if ($style->getProtection()->getHidden() != Protection::PROTECTION_INHERIT) { + $objWriter->writeAttribute('hidden', ($style->getProtection()->getHidden() == Protection::PROTECTION_PROTECTED ? 'true' : 'false')); + } + $objWriter->endElement(); + } + + $objWriter->endElement(); + } + + /** + * Write Cell Style Dxf. + */ + private function writeCellStyleDxf(XMLWriter $objWriter, \PhpOffice\PhpSpreadsheet\Style\Style $style): void + { + // dxf + $objWriter->startElement('dxf'); + + // font + $this->writeFont($objWriter, $style->getFont()); + + // numFmt + $this->writeNumFmt($objWriter, $style->getNumberFormat()); + + // fill + $this->writeFill($objWriter, $style->getFill()); + + // alignment + $objWriter->startElement('alignment'); + if ($style->getAlignment()->getHorizontal() !== null) { + $objWriter->writeAttribute('horizontal', $style->getAlignment()->getHorizontal()); + } + if ($style->getAlignment()->getVertical() !== null) { + $objWriter->writeAttribute('vertical', $style->getAlignment()->getVertical()); + } + + if ($style->getAlignment()->getTextRotation() !== null) { + $textRotation = 0; + if ($style->getAlignment()->getTextRotation() >= 0) { + $textRotation = $style->getAlignment()->getTextRotation(); + } elseif ($style->getAlignment()->getTextRotation() < 0) { + $textRotation = 90 - $style->getAlignment()->getTextRotation(); + } + $objWriter->writeAttribute('textRotation', $textRotation); + } + $objWriter->endElement(); + + // border + $this->writeBorder($objWriter, $style->getBorders()); + + // protection + if (($style->getProtection()->getLocked() !== null) || ($style->getProtection()->getHidden() !== null)) { + if ( + $style->getProtection()->getLocked() !== Protection::PROTECTION_INHERIT || + $style->getProtection()->getHidden() !== Protection::PROTECTION_INHERIT + ) { + $objWriter->startElement('protection'); + if ( + ($style->getProtection()->getLocked() !== null) && + ($style->getProtection()->getLocked() !== Protection::PROTECTION_INHERIT) + ) { + $objWriter->writeAttribute('locked', ($style->getProtection()->getLocked() == Protection::PROTECTION_PROTECTED ? 'true' : 'false')); + } + if ( + ($style->getProtection()->getHidden() !== null) && + ($style->getProtection()->getHidden() !== Protection::PROTECTION_INHERIT) + ) { + $objWriter->writeAttribute('hidden', ($style->getProtection()->getHidden() == Protection::PROTECTION_PROTECTED ? 'true' : 'false')); + } + $objWriter->endElement(); + } + } + + $objWriter->endElement(); + } + + /** + * Write BorderPr. + * + * @param string $name Element name + */ + private function writeBorderPr(XMLWriter $objWriter, $name, Border $border): void + { + // Write BorderPr + if ($border->getBorderStyle() != Border::BORDER_NONE) { + $objWriter->startElement($name); + $objWriter->writeAttribute('style', $border->getBorderStyle()); + + // color + $objWriter->startElement('color'); + $objWriter->writeAttribute('rgb', $border->getColor()->getARGB()); + $objWriter->endElement(); + + $objWriter->endElement(); + } + } + + /** + * Write NumberFormat. + * + * @param int $id Number Format identifier + */ + private function writeNumFmt(XMLWriter $objWriter, NumberFormat $numberFormat, $id = 0): void + { + // Translate formatcode + $formatCode = $numberFormat->getFormatCode(); + + // numFmt + if ($formatCode !== null) { + $objWriter->startElement('numFmt'); + $objWriter->writeAttribute('numFmtId', ($id + 164)); + $objWriter->writeAttribute('formatCode', $formatCode); + $objWriter->endElement(); + } + } + + /** + * Get an array of all styles. + * + * @return \PhpOffice\PhpSpreadsheet\Style\Style[] All styles in PhpSpreadsheet + */ + public function allStyles(Spreadsheet $spreadsheet) + { + return $spreadsheet->getCellXfCollection(); + } + + /** + * Get an array of all conditional styles. + * + * @return Conditional[] All conditional styles in PhpSpreadsheet + */ + public function allConditionalStyles(Spreadsheet $spreadsheet) + { + // Get an array of all styles + $aStyles = []; + + $sheetCount = $spreadsheet->getSheetCount(); + for ($i = 0; $i < $sheetCount; ++$i) { + foreach ($spreadsheet->getSheet($i)->getConditionalStylesCollection() as $conditionalStyles) { + foreach ($conditionalStyles as $conditionalStyle) { + $aStyles[] = $conditionalStyle; + } + } + } + + return $aStyles; + } + + /** + * Get an array of all fills. + * + * @return Fill[] All fills in PhpSpreadsheet + */ + public function allFills(Spreadsheet $spreadsheet) + { + // Get an array of unique fills + $aFills = []; + + // Two first fills are predefined + $fill0 = new Fill(); + $fill0->setFillType(Fill::FILL_NONE); + $aFills[] = $fill0; + + $fill1 = new Fill(); + $fill1->setFillType(Fill::FILL_PATTERN_GRAY125); + $aFills[] = $fill1; + // The remaining fills + $aStyles = $this->allStyles($spreadsheet); + /** @var \PhpOffice\PhpSpreadsheet\Style\Style $style */ + foreach ($aStyles as $style) { + if (!isset($aFills[$style->getFill()->getHashCode()])) { + $aFills[$style->getFill()->getHashCode()] = $style->getFill(); + } + } + + return $aFills; + } + + /** + * Get an array of all fonts. + * + * @return Font[] All fonts in PhpSpreadsheet + */ + public function allFonts(Spreadsheet $spreadsheet) + { + // Get an array of unique fonts + $aFonts = []; + $aStyles = $this->allStyles($spreadsheet); + + /** @var \PhpOffice\PhpSpreadsheet\Style\Style $style */ + foreach ($aStyles as $style) { + if (!isset($aFonts[$style->getFont()->getHashCode()])) { + $aFonts[$style->getFont()->getHashCode()] = $style->getFont(); + } + } + + return $aFonts; + } + + /** + * Get an array of all borders. + * + * @return Borders[] All borders in PhpSpreadsheet + */ + public function allBorders(Spreadsheet $spreadsheet) + { + // Get an array of unique borders + $aBorders = []; + $aStyles = $this->allStyles($spreadsheet); + + /** @var \PhpOffice\PhpSpreadsheet\Style\Style $style */ + foreach ($aStyles as $style) { + if (!isset($aBorders[$style->getBorders()->getHashCode()])) { + $aBorders[$style->getBorders()->getHashCode()] = $style->getBorders(); + } + } + + return $aBorders; + } + + /** + * Get an array of all number formats. + * + * @return NumberFormat[] All number formats in PhpSpreadsheet + */ + public function allNumberFormats(Spreadsheet $spreadsheet) + { + // Get an array of unique number formats + $aNumFmts = []; + $aStyles = $this->allStyles($spreadsheet); + + /** @var \PhpOffice\PhpSpreadsheet\Style\Style $style */ + foreach ($aStyles as $style) { + if ($style->getNumberFormat()->getBuiltInFormatCode() === false && !isset($aNumFmts[$style->getNumberFormat()->getHashCode()])) { + $aNumFmts[$style->getNumberFormat()->getHashCode()] = $style->getNumberFormat(); + } + } + + return $aNumFmts; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Theme.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Theme.php new file mode 100644 index 0000000..9917729 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Theme.php @@ -0,0 +1,829 @@ + 'MS Pゴシック', + 'Hang' => '맑은 고딕', + 'Hans' => '宋体', + 'Hant' => '新細明體', + 'Arab' => 'Times New Roman', + 'Hebr' => 'Times New Roman', + 'Thai' => 'Tahoma', + 'Ethi' => 'Nyala', + 'Beng' => 'Vrinda', + 'Gujr' => 'Shruti', + 'Khmr' => 'MoolBoran', + 'Knda' => 'Tunga', + 'Guru' => 'Raavi', + 'Cans' => 'Euphemia', + 'Cher' => 'Plantagenet Cherokee', + 'Yiii' => 'Microsoft Yi Baiti', + 'Tibt' => 'Microsoft Himalaya', + 'Thaa' => 'MV Boli', + 'Deva' => 'Mangal', + 'Telu' => 'Gautami', + 'Taml' => 'Latha', + 'Syrc' => 'Estrangelo Edessa', + 'Orya' => 'Kalinga', + 'Mlym' => 'Kartika', + 'Laoo' => 'DokChampa', + 'Sinh' => 'Iskoola Pota', + 'Mong' => 'Mongolian Baiti', + 'Viet' => 'Times New Roman', + 'Uigh' => 'Microsoft Uighur', + 'Geor' => 'Sylfaen', + ]; + + /** + * Map of Minor fonts to write. + * + * @var string[] + */ + private static $minorFonts = [ + 'Jpan' => 'MS Pゴシック', + 'Hang' => '맑은 고딕', + 'Hans' => '宋体', + 'Hant' => '新細明體', + 'Arab' => 'Arial', + 'Hebr' => 'Arial', + 'Thai' => 'Tahoma', + 'Ethi' => 'Nyala', + 'Beng' => 'Vrinda', + 'Gujr' => 'Shruti', + 'Khmr' => 'DaunPenh', + 'Knda' => 'Tunga', + 'Guru' => 'Raavi', + 'Cans' => 'Euphemia', + 'Cher' => 'Plantagenet Cherokee', + 'Yiii' => 'Microsoft Yi Baiti', + 'Tibt' => 'Microsoft Himalaya', + 'Thaa' => 'MV Boli', + 'Deva' => 'Mangal', + 'Telu' => 'Gautami', + 'Taml' => 'Latha', + 'Syrc' => 'Estrangelo Edessa', + 'Orya' => 'Kalinga', + 'Mlym' => 'Kartika', + 'Laoo' => 'DokChampa', + 'Sinh' => 'Iskoola Pota', + 'Mong' => 'Mongolian Baiti', + 'Viet' => 'Arial', + 'Uigh' => 'Microsoft Uighur', + 'Geor' => 'Sylfaen', + ]; + + /** + * Map of core colours. + * + * @var string[] + */ + private static $colourScheme = [ + 'dk2' => '1F497D', + 'lt2' => 'EEECE1', + 'accent1' => '4F81BD', + 'accent2' => 'C0504D', + 'accent3' => '9BBB59', + 'accent4' => '8064A2', + 'accent5' => '4BACC6', + 'accent6' => 'F79646', + 'hlink' => '0000FF', + 'folHlink' => '800080', + ]; + + /** + * Write theme to XML format. + * + * @return string XML Output + */ + public function writeTheme(Spreadsheet $spreadsheet) + { + // Create XML writer + $objWriter = null; + if ($this->getParentWriter()->getUseDiskCaching()) { + $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory()); + } else { + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + } + + // XML header + $objWriter->startDocument('1.0', 'UTF-8', 'yes'); + + // a:theme + $objWriter->startElement('a:theme'); + $objWriter->writeAttribute('xmlns:a', 'http://schemas.openxmlformats.org/drawingml/2006/main'); + $objWriter->writeAttribute('name', 'Office Theme'); + + // a:themeElements + $objWriter->startElement('a:themeElements'); + + // a:clrScheme + $objWriter->startElement('a:clrScheme'); + $objWriter->writeAttribute('name', 'Office'); + + // a:dk1 + $objWriter->startElement('a:dk1'); + + // a:sysClr + $objWriter->startElement('a:sysClr'); + $objWriter->writeAttribute('val', 'windowText'); + $objWriter->writeAttribute('lastClr', '000000'); + $objWriter->endElement(); + + $objWriter->endElement(); + + // a:lt1 + $objWriter->startElement('a:lt1'); + + // a:sysClr + $objWriter->startElement('a:sysClr'); + $objWriter->writeAttribute('val', 'window'); + $objWriter->writeAttribute('lastClr', 'FFFFFF'); + $objWriter->endElement(); + + $objWriter->endElement(); + + // a:dk2 + $this->writeColourScheme($objWriter); + + $objWriter->endElement(); + + // a:fontScheme + $objWriter->startElement('a:fontScheme'); + $objWriter->writeAttribute('name', 'Office'); + + // a:majorFont + $objWriter->startElement('a:majorFont'); + $this->writeFonts($objWriter, 'Cambria', self::$majorFonts); + $objWriter->endElement(); + + // a:minorFont + $objWriter->startElement('a:minorFont'); + $this->writeFonts($objWriter, 'Calibri', self::$minorFonts); + $objWriter->endElement(); + + $objWriter->endElement(); + + // a:fmtScheme + $objWriter->startElement('a:fmtScheme'); + $objWriter->writeAttribute('name', 'Office'); + + // a:fillStyleLst + $objWriter->startElement('a:fillStyleLst'); + + // a:solidFill + $objWriter->startElement('a:solidFill'); + + // a:schemeClr + $objWriter->startElement('a:schemeClr'); + $objWriter->writeAttribute('val', 'phClr'); + $objWriter->endElement(); + + $objWriter->endElement(); + + // a:gradFill + $objWriter->startElement('a:gradFill'); + $objWriter->writeAttribute('rotWithShape', '1'); + + // a:gsLst + $objWriter->startElement('a:gsLst'); + + // a:gs + $objWriter->startElement('a:gs'); + $objWriter->writeAttribute('pos', '0'); + + // a:schemeClr + $objWriter->startElement('a:schemeClr'); + $objWriter->writeAttribute('val', 'phClr'); + + // a:tint + $objWriter->startElement('a:tint'); + $objWriter->writeAttribute('val', '50000'); + $objWriter->endElement(); + + // a:satMod + $objWriter->startElement('a:satMod'); + $objWriter->writeAttribute('val', '300000'); + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + // a:gs + $objWriter->startElement('a:gs'); + $objWriter->writeAttribute('pos', '35000'); + + // a:schemeClr + $objWriter->startElement('a:schemeClr'); + $objWriter->writeAttribute('val', 'phClr'); + + // a:tint + $objWriter->startElement('a:tint'); + $objWriter->writeAttribute('val', '37000'); + $objWriter->endElement(); + + // a:satMod + $objWriter->startElement('a:satMod'); + $objWriter->writeAttribute('val', '300000'); + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + // a:gs + $objWriter->startElement('a:gs'); + $objWriter->writeAttribute('pos', '100000'); + + // a:schemeClr + $objWriter->startElement('a:schemeClr'); + $objWriter->writeAttribute('val', 'phClr'); + + // a:tint + $objWriter->startElement('a:tint'); + $objWriter->writeAttribute('val', '15000'); + $objWriter->endElement(); + + // a:satMod + $objWriter->startElement('a:satMod'); + $objWriter->writeAttribute('val', '350000'); + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + // a:lin + $objWriter->startElement('a:lin'); + $objWriter->writeAttribute('ang', '16200000'); + $objWriter->writeAttribute('scaled', '1'); + $objWriter->endElement(); + + $objWriter->endElement(); + + // a:gradFill + $objWriter->startElement('a:gradFill'); + $objWriter->writeAttribute('rotWithShape', '1'); + + // a:gsLst + $objWriter->startElement('a:gsLst'); + + // a:gs + $objWriter->startElement('a:gs'); + $objWriter->writeAttribute('pos', '0'); + + // a:schemeClr + $objWriter->startElement('a:schemeClr'); + $objWriter->writeAttribute('val', 'phClr'); + + // a:shade + $objWriter->startElement('a:shade'); + $objWriter->writeAttribute('val', '51000'); + $objWriter->endElement(); + + // a:satMod + $objWriter->startElement('a:satMod'); + $objWriter->writeAttribute('val', '130000'); + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + // a:gs + $objWriter->startElement('a:gs'); + $objWriter->writeAttribute('pos', '80000'); + + // a:schemeClr + $objWriter->startElement('a:schemeClr'); + $objWriter->writeAttribute('val', 'phClr'); + + // a:shade + $objWriter->startElement('a:shade'); + $objWriter->writeAttribute('val', '93000'); + $objWriter->endElement(); + + // a:satMod + $objWriter->startElement('a:satMod'); + $objWriter->writeAttribute('val', '130000'); + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + // a:gs + $objWriter->startElement('a:gs'); + $objWriter->writeAttribute('pos', '100000'); + + // a:schemeClr + $objWriter->startElement('a:schemeClr'); + $objWriter->writeAttribute('val', 'phClr'); + + // a:shade + $objWriter->startElement('a:shade'); + $objWriter->writeAttribute('val', '94000'); + $objWriter->endElement(); + + // a:satMod + $objWriter->startElement('a:satMod'); + $objWriter->writeAttribute('val', '135000'); + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + // a:lin + $objWriter->startElement('a:lin'); + $objWriter->writeAttribute('ang', '16200000'); + $objWriter->writeAttribute('scaled', '0'); + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + // a:lnStyleLst + $objWriter->startElement('a:lnStyleLst'); + + // a:ln + $objWriter->startElement('a:ln'); + $objWriter->writeAttribute('w', '9525'); + $objWriter->writeAttribute('cap', 'flat'); + $objWriter->writeAttribute('cmpd', 'sng'); + $objWriter->writeAttribute('algn', 'ctr'); + + // a:solidFill + $objWriter->startElement('a:solidFill'); + + // a:schemeClr + $objWriter->startElement('a:schemeClr'); + $objWriter->writeAttribute('val', 'phClr'); + + // a:shade + $objWriter->startElement('a:shade'); + $objWriter->writeAttribute('val', '95000'); + $objWriter->endElement(); + + // a:satMod + $objWriter->startElement('a:satMod'); + $objWriter->writeAttribute('val', '105000'); + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + // a:prstDash + $objWriter->startElement('a:prstDash'); + $objWriter->writeAttribute('val', 'solid'); + $objWriter->endElement(); + + $objWriter->endElement(); + + // a:ln + $objWriter->startElement('a:ln'); + $objWriter->writeAttribute('w', '25400'); + $objWriter->writeAttribute('cap', 'flat'); + $objWriter->writeAttribute('cmpd', 'sng'); + $objWriter->writeAttribute('algn', 'ctr'); + + // a:solidFill + $objWriter->startElement('a:solidFill'); + + // a:schemeClr + $objWriter->startElement('a:schemeClr'); + $objWriter->writeAttribute('val', 'phClr'); + $objWriter->endElement(); + + $objWriter->endElement(); + + // a:prstDash + $objWriter->startElement('a:prstDash'); + $objWriter->writeAttribute('val', 'solid'); + $objWriter->endElement(); + + $objWriter->endElement(); + + // a:ln + $objWriter->startElement('a:ln'); + $objWriter->writeAttribute('w', '38100'); + $objWriter->writeAttribute('cap', 'flat'); + $objWriter->writeAttribute('cmpd', 'sng'); + $objWriter->writeAttribute('algn', 'ctr'); + + // a:solidFill + $objWriter->startElement('a:solidFill'); + + // a:schemeClr + $objWriter->startElement('a:schemeClr'); + $objWriter->writeAttribute('val', 'phClr'); + $objWriter->endElement(); + + $objWriter->endElement(); + + // a:prstDash + $objWriter->startElement('a:prstDash'); + $objWriter->writeAttribute('val', 'solid'); + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + // a:effectStyleLst + $objWriter->startElement('a:effectStyleLst'); + + // a:effectStyle + $objWriter->startElement('a:effectStyle'); + + // a:effectLst + $objWriter->startElement('a:effectLst'); + + // a:outerShdw + $objWriter->startElement('a:outerShdw'); + $objWriter->writeAttribute('blurRad', '40000'); + $objWriter->writeAttribute('dist', '20000'); + $objWriter->writeAttribute('dir', '5400000'); + $objWriter->writeAttribute('rotWithShape', '0'); + + // a:srgbClr + $objWriter->startElement('a:srgbClr'); + $objWriter->writeAttribute('val', '000000'); + + // a:alpha + $objWriter->startElement('a:alpha'); + $objWriter->writeAttribute('val', '38000'); + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + // a:effectStyle + $objWriter->startElement('a:effectStyle'); + + // a:effectLst + $objWriter->startElement('a:effectLst'); + + // a:outerShdw + $objWriter->startElement('a:outerShdw'); + $objWriter->writeAttribute('blurRad', '40000'); + $objWriter->writeAttribute('dist', '23000'); + $objWriter->writeAttribute('dir', '5400000'); + $objWriter->writeAttribute('rotWithShape', '0'); + + // a:srgbClr + $objWriter->startElement('a:srgbClr'); + $objWriter->writeAttribute('val', '000000'); + + // a:alpha + $objWriter->startElement('a:alpha'); + $objWriter->writeAttribute('val', '35000'); + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + // a:effectStyle + $objWriter->startElement('a:effectStyle'); + + // a:effectLst + $objWriter->startElement('a:effectLst'); + + // a:outerShdw + $objWriter->startElement('a:outerShdw'); + $objWriter->writeAttribute('blurRad', '40000'); + $objWriter->writeAttribute('dist', '23000'); + $objWriter->writeAttribute('dir', '5400000'); + $objWriter->writeAttribute('rotWithShape', '0'); + + // a:srgbClr + $objWriter->startElement('a:srgbClr'); + $objWriter->writeAttribute('val', '000000'); + + // a:alpha + $objWriter->startElement('a:alpha'); + $objWriter->writeAttribute('val', '35000'); + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + // a:scene3d + $objWriter->startElement('a:scene3d'); + + // a:camera + $objWriter->startElement('a:camera'); + $objWriter->writeAttribute('prst', 'orthographicFront'); + + // a:rot + $objWriter->startElement('a:rot'); + $objWriter->writeAttribute('lat', '0'); + $objWriter->writeAttribute('lon', '0'); + $objWriter->writeAttribute('rev', '0'); + $objWriter->endElement(); + + $objWriter->endElement(); + + // a:lightRig + $objWriter->startElement('a:lightRig'); + $objWriter->writeAttribute('rig', 'threePt'); + $objWriter->writeAttribute('dir', 't'); + + // a:rot + $objWriter->startElement('a:rot'); + $objWriter->writeAttribute('lat', '0'); + $objWriter->writeAttribute('lon', '0'); + $objWriter->writeAttribute('rev', '1200000'); + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + // a:sp3d + $objWriter->startElement('a:sp3d'); + + // a:bevelT + $objWriter->startElement('a:bevelT'); + $objWriter->writeAttribute('w', '63500'); + $objWriter->writeAttribute('h', '25400'); + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + // a:bgFillStyleLst + $objWriter->startElement('a:bgFillStyleLst'); + + // a:solidFill + $objWriter->startElement('a:solidFill'); + + // a:schemeClr + $objWriter->startElement('a:schemeClr'); + $objWriter->writeAttribute('val', 'phClr'); + $objWriter->endElement(); + + $objWriter->endElement(); + + // a:gradFill + $objWriter->startElement('a:gradFill'); + $objWriter->writeAttribute('rotWithShape', '1'); + + // a:gsLst + $objWriter->startElement('a:gsLst'); + + // a:gs + $objWriter->startElement('a:gs'); + $objWriter->writeAttribute('pos', '0'); + + // a:schemeClr + $objWriter->startElement('a:schemeClr'); + $objWriter->writeAttribute('val', 'phClr'); + + // a:tint + $objWriter->startElement('a:tint'); + $objWriter->writeAttribute('val', '40000'); + $objWriter->endElement(); + + // a:satMod + $objWriter->startElement('a:satMod'); + $objWriter->writeAttribute('val', '350000'); + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + // a:gs + $objWriter->startElement('a:gs'); + $objWriter->writeAttribute('pos', '40000'); + + // a:schemeClr + $objWriter->startElement('a:schemeClr'); + $objWriter->writeAttribute('val', 'phClr'); + + // a:tint + $objWriter->startElement('a:tint'); + $objWriter->writeAttribute('val', '45000'); + $objWriter->endElement(); + + // a:shade + $objWriter->startElement('a:shade'); + $objWriter->writeAttribute('val', '99000'); + $objWriter->endElement(); + + // a:satMod + $objWriter->startElement('a:satMod'); + $objWriter->writeAttribute('val', '350000'); + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + // a:gs + $objWriter->startElement('a:gs'); + $objWriter->writeAttribute('pos', '100000'); + + // a:schemeClr + $objWriter->startElement('a:schemeClr'); + $objWriter->writeAttribute('val', 'phClr'); + + // a:shade + $objWriter->startElement('a:shade'); + $objWriter->writeAttribute('val', '20000'); + $objWriter->endElement(); + + // a:satMod + $objWriter->startElement('a:satMod'); + $objWriter->writeAttribute('val', '255000'); + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + // a:path + $objWriter->startElement('a:path'); + $objWriter->writeAttribute('path', 'circle'); + + // a:fillToRect + $objWriter->startElement('a:fillToRect'); + $objWriter->writeAttribute('l', '50000'); + $objWriter->writeAttribute('t', '-80000'); + $objWriter->writeAttribute('r', '50000'); + $objWriter->writeAttribute('b', '180000'); + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + // a:gradFill + $objWriter->startElement('a:gradFill'); + $objWriter->writeAttribute('rotWithShape', '1'); + + // a:gsLst + $objWriter->startElement('a:gsLst'); + + // a:gs + $objWriter->startElement('a:gs'); + $objWriter->writeAttribute('pos', '0'); + + // a:schemeClr + $objWriter->startElement('a:schemeClr'); + $objWriter->writeAttribute('val', 'phClr'); + + // a:tint + $objWriter->startElement('a:tint'); + $objWriter->writeAttribute('val', '80000'); + $objWriter->endElement(); + + // a:satMod + $objWriter->startElement('a:satMod'); + $objWriter->writeAttribute('val', '300000'); + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + // a:gs + $objWriter->startElement('a:gs'); + $objWriter->writeAttribute('pos', '100000'); + + // a:schemeClr + $objWriter->startElement('a:schemeClr'); + $objWriter->writeAttribute('val', 'phClr'); + + // a:shade + $objWriter->startElement('a:shade'); + $objWriter->writeAttribute('val', '30000'); + $objWriter->endElement(); + + // a:satMod + $objWriter->startElement('a:satMod'); + $objWriter->writeAttribute('val', '200000'); + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + // a:path + $objWriter->startElement('a:path'); + $objWriter->writeAttribute('path', 'circle'); + + // a:fillToRect + $objWriter->startElement('a:fillToRect'); + $objWriter->writeAttribute('l', '50000'); + $objWriter->writeAttribute('t', '50000'); + $objWriter->writeAttribute('r', '50000'); + $objWriter->writeAttribute('b', '50000'); + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + // a:objectDefaults + $objWriter->writeElement('a:objectDefaults', null); + + // a:extraClrSchemeLst + $objWriter->writeElement('a:extraClrSchemeLst', null); + + $objWriter->endElement(); + + // Return + return $objWriter->getData(); + } + + /** + * Write fonts to XML format. + * + * @param string[] $fontSet + */ + private function writeFonts(XMLWriter $objWriter, string $latinFont, array $fontSet): void + { + // a:latin + $objWriter->startElement('a:latin'); + $objWriter->writeAttribute('typeface', $latinFont); + $objWriter->endElement(); + + // a:ea + $objWriter->startElement('a:ea'); + $objWriter->writeAttribute('typeface', ''); + $objWriter->endElement(); + + // a:cs + $objWriter->startElement('a:cs'); + $objWriter->writeAttribute('typeface', ''); + $objWriter->endElement(); + + foreach ($fontSet as $fontScript => $typeface) { + $objWriter->startElement('a:font'); + $objWriter->writeAttribute('script', $fontScript); + $objWriter->writeAttribute('typeface', $typeface); + $objWriter->endElement(); + } + } + + /** + * Write colour scheme to XML format. + */ + private function writeColourScheme(XMLWriter $objWriter): void + { + foreach (self::$colourScheme as $colourName => $colourValue) { + $objWriter->startElement('a:' . $colourName); + + $objWriter->startElement('a:srgbClr'); + $objWriter->writeAttribute('val', $colourValue); + $objWriter->endElement(); + + $objWriter->endElement(); + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Workbook.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Workbook.php new file mode 100644 index 0000000..f9d7197 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Workbook.php @@ -0,0 +1,213 @@ +getParentWriter()->getUseDiskCaching()) { + $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory()); + } else { + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + } + + // XML header + $objWriter->startDocument('1.0', 'UTF-8', 'yes'); + + // workbook + $objWriter->startElement('workbook'); + $objWriter->writeAttribute('xml:space', 'preserve'); + $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'); + $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + + // fileVersion + $this->writeFileVersion($objWriter); + + // workbookPr + $this->writeWorkbookPr($objWriter); + + // workbookProtection + $this->writeWorkbookProtection($objWriter, $spreadsheet); + + // bookViews + if ($this->getParentWriter()->getOffice2003Compatibility() === false) { + $this->writeBookViews($objWriter, $spreadsheet); + } + + // sheets + $this->writeSheets($objWriter, $spreadsheet); + + // definedNames + (new DefinedNamesWriter($objWriter, $spreadsheet))->write(); + + // calcPr + $this->writeCalcPr($objWriter, $recalcRequired); + + $objWriter->endElement(); + + // Return + return $objWriter->getData(); + } + + /** + * Write file version. + */ + private function writeFileVersion(XMLWriter $objWriter): void + { + $objWriter->startElement('fileVersion'); + $objWriter->writeAttribute('appName', 'xl'); + $objWriter->writeAttribute('lastEdited', '4'); + $objWriter->writeAttribute('lowestEdited', '4'); + $objWriter->writeAttribute('rupBuild', '4505'); + $objWriter->endElement(); + } + + /** + * Write WorkbookPr. + */ + private function writeWorkbookPr(XMLWriter $objWriter): void + { + $objWriter->startElement('workbookPr'); + + if (Date::getExcelCalendar() === Date::CALENDAR_MAC_1904) { + $objWriter->writeAttribute('date1904', '1'); + } + + $objWriter->writeAttribute('codeName', 'ThisWorkbook'); + + $objWriter->endElement(); + } + + /** + * Write BookViews. + */ + private function writeBookViews(XMLWriter $objWriter, Spreadsheet $spreadsheet): void + { + // bookViews + $objWriter->startElement('bookViews'); + + // workbookView + $objWriter->startElement('workbookView'); + + $objWriter->writeAttribute('activeTab', $spreadsheet->getActiveSheetIndex()); + $objWriter->writeAttribute('autoFilterDateGrouping', ($spreadsheet->getAutoFilterDateGrouping() ? 'true' : 'false')); + $objWriter->writeAttribute('firstSheet', $spreadsheet->getFirstSheetIndex()); + $objWriter->writeAttribute('minimized', ($spreadsheet->getMinimized() ? 'true' : 'false')); + $objWriter->writeAttribute('showHorizontalScroll', ($spreadsheet->getShowHorizontalScroll() ? 'true' : 'false')); + $objWriter->writeAttribute('showSheetTabs', ($spreadsheet->getShowSheetTabs() ? 'true' : 'false')); + $objWriter->writeAttribute('showVerticalScroll', ($spreadsheet->getShowVerticalScroll() ? 'true' : 'false')); + $objWriter->writeAttribute('tabRatio', $spreadsheet->getTabRatio()); + $objWriter->writeAttribute('visibility', $spreadsheet->getVisibility()); + + $objWriter->endElement(); + + $objWriter->endElement(); + } + + /** + * Write WorkbookProtection. + */ + private function writeWorkbookProtection(XMLWriter $objWriter, Spreadsheet $spreadsheet): void + { + if ($spreadsheet->getSecurity()->isSecurityEnabled()) { + $objWriter->startElement('workbookProtection'); + $objWriter->writeAttribute('lockRevision', ($spreadsheet->getSecurity()->getLockRevision() ? 'true' : 'false')); + $objWriter->writeAttribute('lockStructure', ($spreadsheet->getSecurity()->getLockStructure() ? 'true' : 'false')); + $objWriter->writeAttribute('lockWindows', ($spreadsheet->getSecurity()->getLockWindows() ? 'true' : 'false')); + + if ($spreadsheet->getSecurity()->getRevisionsPassword() != '') { + $objWriter->writeAttribute('revisionsPassword', $spreadsheet->getSecurity()->getRevisionsPassword()); + } + + if ($spreadsheet->getSecurity()->getWorkbookPassword() != '') { + $objWriter->writeAttribute('workbookPassword', $spreadsheet->getSecurity()->getWorkbookPassword()); + } + + $objWriter->endElement(); + } + } + + /** + * Write calcPr. + * + * @param bool $recalcRequired Indicate whether formulas should be recalculated before writing + */ + private function writeCalcPr(XMLWriter $objWriter, $recalcRequired = true): void + { + $objWriter->startElement('calcPr'); + + // Set the calcid to a higher value than Excel itself will use, otherwise Excel will always recalc + // If MS Excel does do a recalc, then users opening a file in MS Excel will be prompted to save on exit + // because the file has changed + $objWriter->writeAttribute('calcId', '999999'); + $objWriter->writeAttribute('calcMode', 'auto'); + // fullCalcOnLoad isn't needed if we've recalculating for the save + $objWriter->writeAttribute('calcCompleted', ($recalcRequired) ? 1 : 0); + $objWriter->writeAttribute('fullCalcOnLoad', ($recalcRequired) ? 0 : 1); + $objWriter->writeAttribute('forceFullCalc', ($recalcRequired) ? 0 : 1); + + $objWriter->endElement(); + } + + /** + * Write sheets. + */ + private function writeSheets(XMLWriter $objWriter, Spreadsheet $spreadsheet): void + { + // Write sheets + $objWriter->startElement('sheets'); + $sheetCount = $spreadsheet->getSheetCount(); + for ($i = 0; $i < $sheetCount; ++$i) { + // sheet + $this->writeSheet( + $objWriter, + $spreadsheet->getSheet($i)->getTitle(), + ($i + 1), + ($i + 1 + 3), + $spreadsheet->getSheet($i)->getSheetState() + ); + } + + $objWriter->endElement(); + } + + /** + * Write sheet. + * + * @param string $worksheetName Sheet name + * @param int $worksheetId Sheet id + * @param int $relId Relationship ID + * @param string $sheetState Sheet state (visible, hidden, veryHidden) + */ + private function writeSheet(XMLWriter $objWriter, $worksheetName, $worksheetId = 1, $relId = 1, $sheetState = 'visible'): void + { + if ($worksheetName != '') { + // Write sheet + $objWriter->startElement('sheet'); + $objWriter->writeAttribute('name', $worksheetName); + $objWriter->writeAttribute('sheetId', $worksheetId); + if ($sheetState !== 'visible' && $sheetState != '') { + $objWriter->writeAttribute('state', $sheetState); + } + $objWriter->writeAttribute('r:id', 'rId' . $relId); + $objWriter->endElement(); + } else { + throw new WriterException('Invalid parameters passed.'); + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php new file mode 100644 index 0000000..3e1b537 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php @@ -0,0 +1,1383 @@ +getParentWriter()->getUseDiskCaching()) { + $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory()); + } else { + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + } + + // XML header + $objWriter->startDocument('1.0', 'UTF-8', 'yes'); + + // Worksheet + $objWriter->startElement('worksheet'); + $objWriter->writeAttribute('xml:space', 'preserve'); + $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'); + $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + + $objWriter->writeAttribute('xmlns:xdr', 'http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing'); + $objWriter->writeAttribute('xmlns:x14', 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/main'); + $objWriter->writeAttribute('xmlns:xm', 'http://schemas.microsoft.com/office/excel/2006/main'); + $objWriter->writeAttribute('xmlns:mc', 'http://schemas.openxmlformats.org/markup-compatibility/2006'); + $objWriter->writeAttribute('mc:Ignorable', 'x14ac'); + $objWriter->writeAttribute('xmlns:x14ac', 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac'); + + // sheetPr + $this->writeSheetPr($objWriter, $worksheet); + + // Dimension + $this->writeDimension($objWriter, $worksheet); + + // sheetViews + $this->writeSheetViews($objWriter, $worksheet); + + // sheetFormatPr + $this->writeSheetFormatPr($objWriter, $worksheet); + + // cols + $this->writeCols($objWriter, $worksheet); + + // sheetData + $this->writeSheetData($objWriter, $worksheet, $stringTable); + + // sheetProtection + $this->writeSheetProtection($objWriter, $worksheet); + + // protectedRanges + $this->writeProtectedRanges($objWriter, $worksheet); + + // autoFilter + $this->writeAutoFilter($objWriter, $worksheet); + + // mergeCells + $this->writeMergeCells($objWriter, $worksheet); + + // conditionalFormatting + $this->writeConditionalFormatting($objWriter, $worksheet); + + // dataValidations + $this->writeDataValidations($objWriter, $worksheet); + + // hyperlinks + $this->writeHyperlinks($objWriter, $worksheet); + + // Print options + $this->writePrintOptions($objWriter, $worksheet); + + // Page margins + $this->writePageMargins($objWriter, $worksheet); + + // Page setup + $this->writePageSetup($objWriter, $worksheet); + + // Header / footer + $this->writeHeaderFooter($objWriter, $worksheet); + + // Breaks + $this->writeBreaks($objWriter, $worksheet); + + // Drawings and/or Charts + $this->writeDrawings($objWriter, $worksheet, $includeCharts); + + // LegacyDrawing + $this->writeLegacyDrawing($objWriter, $worksheet); + + // LegacyDrawingHF + $this->writeLegacyDrawingHF($objWriter, $worksheet); + + // AlternateContent + $this->writeAlternateContent($objWriter, $worksheet); + + // ConditionalFormattingRuleExtensionList + // (Must be inserted last. Not insert last, an Excel parse error will occur) + $this->writeExtLst($objWriter, $worksheet); + + $objWriter->endElement(); + + // Return + return $objWriter->getData(); + } + + /** + * Write SheetPr. + */ + private function writeSheetPr(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void + { + // sheetPr + $objWriter->startElement('sheetPr'); + if ($worksheet->getParent()->hasMacros()) { + //if the workbook have macros, we need to have codeName for the sheet + if (!$worksheet->hasCodeName()) { + $worksheet->setCodeName($worksheet->getTitle()); + } + self::writeAttributeNotNull($objWriter, 'codeName', $worksheet->getCodeName()); + } + $autoFilterRange = $worksheet->getAutoFilter()->getRange(); + if (!empty($autoFilterRange)) { + $objWriter->writeAttribute('filterMode', 1); + $worksheet->getAutoFilter()->showHideRows(); + } + + // tabColor + if ($worksheet->isTabColorSet()) { + $objWriter->startElement('tabColor'); + $objWriter->writeAttribute('rgb', $worksheet->getTabColor()->getARGB()); + $objWriter->endElement(); + } + + // outlinePr + $objWriter->startElement('outlinePr'); + $objWriter->writeAttribute('summaryBelow', ($worksheet->getShowSummaryBelow() ? '1' : '0')); + $objWriter->writeAttribute('summaryRight', ($worksheet->getShowSummaryRight() ? '1' : '0')); + $objWriter->endElement(); + + // pageSetUpPr + if ($worksheet->getPageSetup()->getFitToPage()) { + $objWriter->startElement('pageSetUpPr'); + $objWriter->writeAttribute('fitToPage', '1'); + $objWriter->endElement(); + } + + $objWriter->endElement(); + } + + /** + * Write Dimension. + */ + private function writeDimension(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void + { + // dimension + $objWriter->startElement('dimension'); + $objWriter->writeAttribute('ref', $worksheet->calculateWorksheetDimension()); + $objWriter->endElement(); + } + + /** + * Write SheetViews. + */ + private function writeSheetViews(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void + { + // sheetViews + $objWriter->startElement('sheetViews'); + + // Sheet selected? + $sheetSelected = false; + if ($this->getParentWriter()->getSpreadsheet()->getIndex($worksheet) == $this->getParentWriter()->getSpreadsheet()->getActiveSheetIndex()) { + $sheetSelected = true; + } + + // sheetView + $objWriter->startElement('sheetView'); + $objWriter->writeAttribute('tabSelected', $sheetSelected ? '1' : '0'); + $objWriter->writeAttribute('workbookViewId', '0'); + + // Zoom scales + if ($worksheet->getSheetView()->getZoomScale() != 100) { + $objWriter->writeAttribute('zoomScale', $worksheet->getSheetView()->getZoomScale()); + } + if ($worksheet->getSheetView()->getZoomScaleNormal() != 100) { + $objWriter->writeAttribute('zoomScaleNormal', $worksheet->getSheetView()->getZoomScaleNormal()); + } + + // Show zeros (Excel also writes this attribute only if set to false) + if ($worksheet->getSheetView()->getShowZeros() === false) { + $objWriter->writeAttribute('showZeros', 0); + } + + // View Layout Type + if ($worksheet->getSheetView()->getView() !== SheetView::SHEETVIEW_NORMAL) { + $objWriter->writeAttribute('view', $worksheet->getSheetView()->getView()); + } + + // Gridlines + if ($worksheet->getShowGridlines()) { + $objWriter->writeAttribute('showGridLines', 'true'); + } else { + $objWriter->writeAttribute('showGridLines', 'false'); + } + + // Row and column headers + if ($worksheet->getShowRowColHeaders()) { + $objWriter->writeAttribute('showRowColHeaders', '1'); + } else { + $objWriter->writeAttribute('showRowColHeaders', '0'); + } + + // Right-to-left + if ($worksheet->getRightToLeft()) { + $objWriter->writeAttribute('rightToLeft', 'true'); + } + + $topLeftCell = $worksheet->getTopLeftCell(); + $activeCell = $worksheet->getActiveCell(); + $sqref = $worksheet->getSelectedCells(); + + // Pane + $pane = ''; + if ($worksheet->getFreezePane()) { + [$xSplit, $ySplit] = Coordinate::coordinateFromString($worksheet->getFreezePane() ?? ''); + $xSplit = Coordinate::columnIndexFromString($xSplit); + --$xSplit; + --$ySplit; + + // pane + $pane = 'topRight'; + $objWriter->startElement('pane'); + if ($xSplit > 0) { + $objWriter->writeAttribute('xSplit', $xSplit); + } + if ($ySplit > 0) { + $objWriter->writeAttribute('ySplit', $ySplit); + $pane = ($xSplit > 0) ? 'bottomRight' : 'bottomLeft'; + } + self::writeAttributeNotNull($objWriter, 'topLeftCell', $topLeftCell); + $objWriter->writeAttribute('activePane', $pane); + $objWriter->writeAttribute('state', 'frozen'); + $objWriter->endElement(); + + if (($xSplit > 0) && ($ySplit > 0)) { + // Write additional selections if more than two panes (ie both an X and a Y split) + $objWriter->startElement('selection'); + $objWriter->writeAttribute('pane', 'topRight'); + $objWriter->endElement(); + $objWriter->startElement('selection'); + $objWriter->writeAttribute('pane', 'bottomLeft'); + $objWriter->endElement(); + } + } else { + self::writeAttributeNotNull($objWriter, 'topLeftCell', $topLeftCell); + } + + // Selection + // Only need to write selection element if we have a split pane + // We cheat a little by over-riding the active cell selection, setting it to the split cell + $objWriter->startElement('selection'); + if ($pane != '') { + $objWriter->writeAttribute('pane', $pane); + } + $objWriter->writeAttribute('activeCell', $activeCell); + $objWriter->writeAttribute('sqref', $sqref); + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + } + + /** + * Write SheetFormatPr. + */ + private function writeSheetFormatPr(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void + { + // sheetFormatPr + $objWriter->startElement('sheetFormatPr'); + + // Default row height + if ($worksheet->getDefaultRowDimension()->getRowHeight() >= 0) { + $objWriter->writeAttribute('customHeight', 'true'); + $objWriter->writeAttribute('defaultRowHeight', StringHelper::formatNumber($worksheet->getDefaultRowDimension()->getRowHeight())); + } else { + $objWriter->writeAttribute('defaultRowHeight', '14.4'); + } + + // Set Zero Height row + if ( + (string) $worksheet->getDefaultRowDimension()->getZeroHeight() === '1' || + strtolower((string) $worksheet->getDefaultRowDimension()->getZeroHeight()) == 'true' + ) { + $objWriter->writeAttribute('zeroHeight', '1'); + } + + // Default column width + if ($worksheet->getDefaultColumnDimension()->getWidth() >= 0) { + $objWriter->writeAttribute('defaultColWidth', StringHelper::formatNumber($worksheet->getDefaultColumnDimension()->getWidth())); + } + + // Outline level - row + $outlineLevelRow = 0; + foreach ($worksheet->getRowDimensions() as $dimension) { + if ($dimension->getOutlineLevel() > $outlineLevelRow) { + $outlineLevelRow = $dimension->getOutlineLevel(); + } + } + $objWriter->writeAttribute('outlineLevelRow', (int) $outlineLevelRow); + + // Outline level - column + $outlineLevelCol = 0; + foreach ($worksheet->getColumnDimensions() as $dimension) { + if ($dimension->getOutlineLevel() > $outlineLevelCol) { + $outlineLevelCol = $dimension->getOutlineLevel(); + } + } + $objWriter->writeAttribute('outlineLevelCol', (int) $outlineLevelCol); + + $objWriter->endElement(); + } + + /** + * Write Cols. + */ + private function writeCols(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void + { + // cols + if (count($worksheet->getColumnDimensions()) > 0) { + $objWriter->startElement('cols'); + + $worksheet->calculateColumnWidths(); + + // Loop through column dimensions + foreach ($worksheet->getColumnDimensions() as $colDimension) { + // col + $objWriter->startElement('col'); + $objWriter->writeAttribute('min', Coordinate::columnIndexFromString($colDimension->getColumnIndex())); + $objWriter->writeAttribute('max', Coordinate::columnIndexFromString($colDimension->getColumnIndex())); + + if ($colDimension->getWidth() < 0) { + // No width set, apply default of 10 + $objWriter->writeAttribute('width', '9.10'); + } else { + // Width set + $objWriter->writeAttribute('width', StringHelper::formatNumber($colDimension->getWidth())); + } + + // Column visibility + if ($colDimension->getVisible() === false) { + $objWriter->writeAttribute('hidden', 'true'); + } + + // Auto size? + if ($colDimension->getAutoSize()) { + $objWriter->writeAttribute('bestFit', 'true'); + } + + // Custom width? + if ($colDimension->getWidth() != $worksheet->getDefaultColumnDimension()->getWidth()) { + $objWriter->writeAttribute('customWidth', 'true'); + } + + // Collapsed + if ($colDimension->getCollapsed() === true) { + $objWriter->writeAttribute('collapsed', 'true'); + } + + // Outline level + if ($colDimension->getOutlineLevel() > 0) { + $objWriter->writeAttribute('outlineLevel', $colDimension->getOutlineLevel()); + } + + // Style + $objWriter->writeAttribute('style', $colDimension->getXfIndex()); + + $objWriter->endElement(); + } + + $objWriter->endElement(); + } + } + + /** + * Write SheetProtection. + */ + private function writeSheetProtection(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void + { + // sheetProtection + $objWriter->startElement('sheetProtection'); + + $protection = $worksheet->getProtection(); + + if ($protection->getAlgorithm()) { + $objWriter->writeAttribute('algorithmName', $protection->getAlgorithm()); + $objWriter->writeAttribute('hashValue', $protection->getPassword()); + $objWriter->writeAttribute('saltValue', $protection->getSalt()); + $objWriter->writeAttribute('spinCount', $protection->getSpinCount()); + } elseif ($protection->getPassword() !== '') { + $objWriter->writeAttribute('password', $protection->getPassword()); + } + + $objWriter->writeAttribute('sheet', ($protection->getSheet() ? 'true' : 'false')); + $objWriter->writeAttribute('objects', ($protection->getObjects() ? 'true' : 'false')); + $objWriter->writeAttribute('scenarios', ($protection->getScenarios() ? 'true' : 'false')); + $objWriter->writeAttribute('formatCells', ($protection->getFormatCells() ? 'true' : 'false')); + $objWriter->writeAttribute('formatColumns', ($protection->getFormatColumns() ? 'true' : 'false')); + $objWriter->writeAttribute('formatRows', ($protection->getFormatRows() ? 'true' : 'false')); + $objWriter->writeAttribute('insertColumns', ($protection->getInsertColumns() ? 'true' : 'false')); + $objWriter->writeAttribute('insertRows', ($protection->getInsertRows() ? 'true' : 'false')); + $objWriter->writeAttribute('insertHyperlinks', ($protection->getInsertHyperlinks() ? 'true' : 'false')); + $objWriter->writeAttribute('deleteColumns', ($protection->getDeleteColumns() ? 'true' : 'false')); + $objWriter->writeAttribute('deleteRows', ($protection->getDeleteRows() ? 'true' : 'false')); + $objWriter->writeAttribute('selectLockedCells', ($protection->getSelectLockedCells() ? 'true' : 'false')); + $objWriter->writeAttribute('sort', ($protection->getSort() ? 'true' : 'false')); + $objWriter->writeAttribute('autoFilter', ($protection->getAutoFilter() ? 'true' : 'false')); + $objWriter->writeAttribute('pivotTables', ($protection->getPivotTables() ? 'true' : 'false')); + $objWriter->writeAttribute('selectUnlockedCells', ($protection->getSelectUnlockedCells() ? 'true' : 'false')); + $objWriter->endElement(); + } + + private static function writeAttributeIf(XMLWriter $objWriter, $condition, string $attr, string $val): void + { + if ($condition) { + $objWriter->writeAttribute($attr, $val); + } + } + + private static function writeAttributeNotNull(XMLWriter $objWriter, string $attr, ?string $val): void + { + if ($val !== null) { + $objWriter->writeAttribute($attr, $val); + } + } + + private static function writeElementIf(XMLWriter $objWriter, $condition, string $attr, string $val): void + { + if ($condition) { + $objWriter->writeElement($attr, $val); + } + } + + private static function writeOtherCondElements(XMLWriter $objWriter, Conditional $conditional, string $cellCoordinate): void + { + if ( + $conditional->getConditionType() == Conditional::CONDITION_CELLIS + || $conditional->getConditionType() == Conditional::CONDITION_CONTAINSTEXT + || $conditional->getConditionType() == Conditional::CONDITION_EXPRESSION + ) { + foreach ($conditional->getConditions() as $formula) { + // Formula + $objWriter->writeElement('formula', Xlfn::addXlfn($formula)); + } + } elseif ($conditional->getConditionType() == Conditional::CONDITION_CONTAINSBLANKS) { + // formula copied from ms xlsx xml source file + $objWriter->writeElement('formula', 'LEN(TRIM(' . $cellCoordinate . '))=0'); + } elseif ($conditional->getConditionType() == Conditional::CONDITION_NOTCONTAINSBLANKS) { + // formula copied from ms xlsx xml source file + $objWriter->writeElement('formula', 'LEN(TRIM(' . $cellCoordinate . '))>0'); + } + } + + private static function writeTextCondElements(XMLWriter $objWriter, Conditional $conditional, string $cellCoordinate): void + { + $txt = $conditional->getText(); + if ($txt !== null) { + $objWriter->writeAttribute('text', $txt); + if ($conditional->getOperatorType() == Conditional::OPERATOR_CONTAINSTEXT) { + $objWriter->writeElement('formula', 'NOT(ISERROR(SEARCH("' . $txt . '",' . $cellCoordinate . ')))'); + } elseif ($conditional->getOperatorType() == Conditional::OPERATOR_BEGINSWITH) { + $objWriter->writeElement('formula', 'LEFT(' . $cellCoordinate . ',' . strlen($txt) . ')="' . $txt . '"'); + } elseif ($conditional->getOperatorType() == Conditional::OPERATOR_ENDSWITH) { + $objWriter->writeElement('formula', 'RIGHT(' . $cellCoordinate . ',' . strlen($txt) . ')="' . $txt . '"'); + } elseif ($conditional->getOperatorType() == Conditional::OPERATOR_NOTCONTAINS) { + $objWriter->writeElement('formula', 'ISERROR(SEARCH("' . $txt . '",' . $cellCoordinate . '))'); + } + } + } + + private static function writeExtConditionalFormattingElements(XMLWriter $objWriter, ConditionalFormattingRuleExtension $ruleExtension): void + { + $prefix = 'x14'; + $objWriter->startElementNs($prefix, 'conditionalFormatting', null); + + $objWriter->startElementNs($prefix, 'cfRule', null); + $objWriter->writeAttribute('type', $ruleExtension->getCfRule()); + $objWriter->writeAttribute('id', $ruleExtension->getId()); + $objWriter->startElementNs($prefix, 'dataBar', null); + $dataBar = $ruleExtension->getDataBarExt(); + foreach ($dataBar->getXmlAttributes() as $attrKey => $val) { + $objWriter->writeAttribute($attrKey, $val); + } + $minCfvo = $dataBar->getMinimumConditionalFormatValueObject(); + if ($minCfvo) { + $objWriter->startElementNs($prefix, 'cfvo', null); + $objWriter->writeAttribute('type', $minCfvo->getType()); + if ($minCfvo->getCellFormula()) { + $objWriter->writeElement('xm:f', $minCfvo->getCellFormula()); + } + $objWriter->endElement(); //end cfvo + } + + $maxCfvo = $dataBar->getMaximumConditionalFormatValueObject(); + if ($maxCfvo) { + $objWriter->startElementNs($prefix, 'cfvo', null); + $objWriter->writeAttribute('type', $maxCfvo->getType()); + if ($maxCfvo->getCellFormula()) { + $objWriter->writeElement('xm:f', $maxCfvo->getCellFormula()); + } + $objWriter->endElement(); //end cfvo + } + + foreach ($dataBar->getXmlElements() as $elmKey => $elmAttr) { + $objWriter->startElementNs($prefix, $elmKey, null); + foreach ($elmAttr as $attrKey => $attrVal) { + $objWriter->writeAttribute($attrKey, $attrVal); + } + $objWriter->endElement(); //end elmKey + } + $objWriter->endElement(); //end dataBar + $objWriter->endElement(); //end cfRule + $objWriter->writeElement('xm:sqref', $ruleExtension->getSqref()); + $objWriter->endElement(); //end conditionalFormatting + } + + private static function writeDataBarElements(XMLWriter $objWriter, $dataBar): void + { + /** @var ConditionalDataBar $dataBar */ + if ($dataBar) { + $objWriter->startElement('dataBar'); + self::writeAttributeIf($objWriter, null !== $dataBar->getShowValue(), 'showValue', $dataBar->getShowValue() ? '1' : '0'); + + $minCfvo = $dataBar->getMinimumConditionalFormatValueObject(); + if ($minCfvo) { + $objWriter->startElement('cfvo'); + self::writeAttributeIf($objWriter, $minCfvo->getType(), 'type', (string) $minCfvo->getType()); + self::writeAttributeIf($objWriter, $minCfvo->getValue(), 'val', (string) $minCfvo->getValue()); + $objWriter->endElement(); + } + $maxCfvo = $dataBar->getMaximumConditionalFormatValueObject(); + if ($maxCfvo) { + $objWriter->startElement('cfvo'); + self::writeAttributeIf($objWriter, $maxCfvo->getType(), 'type', (string) $maxCfvo->getType()); + self::writeAttributeIf($objWriter, $maxCfvo->getValue(), 'val', (string) $maxCfvo->getValue()); + $objWriter->endElement(); + } + if ($dataBar->getColor()) { + $objWriter->startElement('color'); + $objWriter->writeAttribute('rgb', $dataBar->getColor()); + $objWriter->endElement(); + } + $objWriter->endElement(); // end dataBar + + if ($dataBar->getConditionalFormattingRuleExt()) { + $objWriter->startElement('extLst'); + $extension = $dataBar->getConditionalFormattingRuleExt(); + $objWriter->startElement('ext'); + $objWriter->writeAttribute('uri', '{B025F937-C7B1-47D3-B67F-A62EFF666E3E}'); + $objWriter->startElementNs('x14', 'id', null); + $objWriter->text($extension->getId()); + $objWriter->endElement(); + $objWriter->endElement(); + $objWriter->endElement(); //end extLst + } + } + } + + /** + * Write ConditionalFormatting. + */ + private function writeConditionalFormatting(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void + { + // Conditional id + $id = 1; + + // Loop through styles in the current worksheet + foreach ($worksheet->getConditionalStylesCollection() as $cellCoordinate => $conditionalStyles) { + foreach ($conditionalStyles as $conditional) { + // WHY was this again? + // if ($this->getParentWriter()->getStylesConditionalHashTable()->getIndexForHashCode($conditional->getHashCode()) == '') { + // continue; + // } + if ($conditional->getConditionType() != Conditional::CONDITION_NONE) { + // conditionalFormatting + $objWriter->startElement('conditionalFormatting'); + $objWriter->writeAttribute('sqref', $cellCoordinate); + + // cfRule + $objWriter->startElement('cfRule'); + $objWriter->writeAttribute('type', $conditional->getConditionType()); + self::writeAttributeIf( + $objWriter, + ($conditional->getConditionType() != Conditional::CONDITION_DATABAR), + 'dxfId', + (string) $this->getParentWriter()->getStylesConditionalHashTable()->getIndexForHashCode($conditional->getHashCode()) + ); + $objWriter->writeAttribute('priority', $id++); + + self::writeAttributeif( + $objWriter, + ( + $conditional->getConditionType() === Conditional::CONDITION_CELLIS + || $conditional->getConditionType() === Conditional::CONDITION_CONTAINSTEXT + || $conditional->getConditionType() === Conditional::CONDITION_NOTCONTAINSTEXT + ) && $conditional->getOperatorType() !== Conditional::OPERATOR_NONE, + 'operator', + $conditional->getOperatorType() + ); + + self::writeAttributeIf($objWriter, $conditional->getStopIfTrue(), 'stopIfTrue', '1'); + + if ( + $conditional->getConditionType() === Conditional::CONDITION_CONTAINSTEXT + || $conditional->getConditionType() === Conditional::CONDITION_NOTCONTAINSTEXT + ) { + self::writeTextCondElements($objWriter, $conditional, $cellCoordinate); + } else { + self::writeOtherCondElements($objWriter, $conditional, $cellCoordinate); + } + + // + self::writeDataBarElements($objWriter, $conditional->getDataBar()); + + $objWriter->endElement(); //end cfRule + + $objWriter->endElement(); + } + } + } + } + + /** + * Write DataValidations. + */ + private function writeDataValidations(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void + { + // Datavalidation collection + $dataValidationCollection = $worksheet->getDataValidationCollection(); + + // Write data validations? + if (!empty($dataValidationCollection)) { + $dataValidationCollection = Coordinate::mergeRangesInCollection($dataValidationCollection); + $objWriter->startElement('dataValidations'); + $objWriter->writeAttribute('count', count($dataValidationCollection)); + + foreach ($dataValidationCollection as $coordinate => $dv) { + $objWriter->startElement('dataValidation'); + + if ($dv->getType() != '') { + $objWriter->writeAttribute('type', $dv->getType()); + } + + if ($dv->getErrorStyle() != '') { + $objWriter->writeAttribute('errorStyle', $dv->getErrorStyle()); + } + + if ($dv->getOperator() != '') { + $objWriter->writeAttribute('operator', $dv->getOperator()); + } + + $objWriter->writeAttribute('allowBlank', ($dv->getAllowBlank() ? '1' : '0')); + $objWriter->writeAttribute('showDropDown', (!$dv->getShowDropDown() ? '1' : '0')); + $objWriter->writeAttribute('showInputMessage', ($dv->getShowInputMessage() ? '1' : '0')); + $objWriter->writeAttribute('showErrorMessage', ($dv->getShowErrorMessage() ? '1' : '0')); + + if ($dv->getErrorTitle() !== '') { + $objWriter->writeAttribute('errorTitle', $dv->getErrorTitle()); + } + if ($dv->getError() !== '') { + $objWriter->writeAttribute('error', $dv->getError()); + } + if ($dv->getPromptTitle() !== '') { + $objWriter->writeAttribute('promptTitle', $dv->getPromptTitle()); + } + if ($dv->getPrompt() !== '') { + $objWriter->writeAttribute('prompt', $dv->getPrompt()); + } + + $objWriter->writeAttribute('sqref', $dv->getSqref() ?? $coordinate); + + if ($dv->getFormula1() !== '') { + $objWriter->writeElement('formula1', $dv->getFormula1()); + } + if ($dv->getFormula2() !== '') { + $objWriter->writeElement('formula2', $dv->getFormula2()); + } + + $objWriter->endElement(); + } + + $objWriter->endElement(); + } + } + + /** + * Write Hyperlinks. + */ + private function writeHyperlinks(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void + { + // Hyperlink collection + $hyperlinkCollection = $worksheet->getHyperlinkCollection(); + + // Relation ID + $relationId = 1; + + // Write hyperlinks? + if (!empty($hyperlinkCollection)) { + $objWriter->startElement('hyperlinks'); + + foreach ($hyperlinkCollection as $coordinate => $hyperlink) { + $objWriter->startElement('hyperlink'); + + $objWriter->writeAttribute('ref', $coordinate); + if (!$hyperlink->isInternal()) { + $objWriter->writeAttribute('r:id', 'rId_hyperlink_' . $relationId); + ++$relationId; + } else { + $objWriter->writeAttribute('location', str_replace('sheet://', '', $hyperlink->getUrl())); + } + + if ($hyperlink->getTooltip() !== '') { + $objWriter->writeAttribute('tooltip', $hyperlink->getTooltip()); + $objWriter->writeAttribute('display', $hyperlink->getTooltip()); + } + + $objWriter->endElement(); + } + + $objWriter->endElement(); + } + } + + /** + * Write ProtectedRanges. + */ + private function writeProtectedRanges(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void + { + if (count($worksheet->getProtectedCells()) > 0) { + // protectedRanges + $objWriter->startElement('protectedRanges'); + + // Loop protectedRanges + foreach ($worksheet->getProtectedCells() as $protectedCell => $passwordHash) { + // protectedRange + $objWriter->startElement('protectedRange'); + $objWriter->writeAttribute('name', 'p' . md5($protectedCell)); + $objWriter->writeAttribute('sqref', $protectedCell); + if (!empty($passwordHash)) { + $objWriter->writeAttribute('password', $passwordHash); + } + $objWriter->endElement(); + } + + $objWriter->endElement(); + } + } + + /** + * Write MergeCells. + */ + private function writeMergeCells(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void + { + if (count($worksheet->getMergeCells()) > 0) { + // mergeCells + $objWriter->startElement('mergeCells'); + + // Loop mergeCells + foreach ($worksheet->getMergeCells() as $mergeCell) { + // mergeCell + $objWriter->startElement('mergeCell'); + $objWriter->writeAttribute('ref', $mergeCell); + $objWriter->endElement(); + } + + $objWriter->endElement(); + } + } + + /** + * Write PrintOptions. + */ + private function writePrintOptions(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void + { + // printOptions + $objWriter->startElement('printOptions'); + + $objWriter->writeAttribute('gridLines', ($worksheet->getPrintGridlines() ? 'true' : 'false')); + $objWriter->writeAttribute('gridLinesSet', 'true'); + + if ($worksheet->getPageSetup()->getHorizontalCentered()) { + $objWriter->writeAttribute('horizontalCentered', 'true'); + } + + if ($worksheet->getPageSetup()->getVerticalCentered()) { + $objWriter->writeAttribute('verticalCentered', 'true'); + } + + $objWriter->endElement(); + } + + /** + * Write PageMargins. + */ + private function writePageMargins(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void + { + // pageMargins + $objWriter->startElement('pageMargins'); + $objWriter->writeAttribute('left', StringHelper::formatNumber($worksheet->getPageMargins()->getLeft())); + $objWriter->writeAttribute('right', StringHelper::formatNumber($worksheet->getPageMargins()->getRight())); + $objWriter->writeAttribute('top', StringHelper::formatNumber($worksheet->getPageMargins()->getTop())); + $objWriter->writeAttribute('bottom', StringHelper::formatNumber($worksheet->getPageMargins()->getBottom())); + $objWriter->writeAttribute('header', StringHelper::formatNumber($worksheet->getPageMargins()->getHeader())); + $objWriter->writeAttribute('footer', StringHelper::formatNumber($worksheet->getPageMargins()->getFooter())); + $objWriter->endElement(); + } + + /** + * Write AutoFilter. + */ + private function writeAutoFilter(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void + { + $autoFilterRange = $worksheet->getAutoFilter()->getRange(); + if (!empty($autoFilterRange)) { + // autoFilter + $objWriter->startElement('autoFilter'); + + // Strip any worksheet reference from the filter coordinates + $range = Coordinate::splitRange($autoFilterRange); + $range = $range[0]; + // Strip any worksheet ref + [$ws, $range[0]] = PhpspreadsheetWorksheet::extractSheetTitle($range[0], true); + $range = implode(':', $range); + + $objWriter->writeAttribute('ref', str_replace('$', '', $range)); + + $columns = $worksheet->getAutoFilter()->getColumns(); + if (count($columns) > 0) { + foreach ($columns as $columnID => $column) { + $rules = $column->getRules(); + if (count($rules) > 0) { + $objWriter->startElement('filterColumn'); + $objWriter->writeAttribute('colId', $worksheet->getAutoFilter()->getColumnOffset($columnID)); + + $objWriter->startElement($column->getFilterType()); + if ($column->getJoin() == Column::AUTOFILTER_COLUMN_JOIN_AND) { + $objWriter->writeAttribute('and', 1); + } + + foreach ($rules as $rule) { + if ( + ($column->getFilterType() === Column::AUTOFILTER_FILTERTYPE_FILTER) && + ($rule->getOperator() === Rule::AUTOFILTER_COLUMN_RULE_EQUAL) && + ($rule->getValue() === '') + ) { + // Filter rule for Blanks + $objWriter->writeAttribute('blank', 1); + } elseif ($rule->getRuleType() === Rule::AUTOFILTER_RULETYPE_DYNAMICFILTER) { + // Dynamic Filter Rule + $objWriter->writeAttribute('type', $rule->getGrouping()); + $val = $column->getAttribute('val'); + if ($val !== null) { + $objWriter->writeAttribute('val', "$val"); + } + $maxVal = $column->getAttribute('maxVal'); + if ($maxVal !== null) { + $objWriter->writeAttribute('maxVal', "$maxVal"); + } + } elseif ($rule->getRuleType() === Rule::AUTOFILTER_RULETYPE_TOPTENFILTER) { + // Top 10 Filter Rule + $ruleValue = $rule->getValue(); + if (!is_array($ruleValue)) { + $objWriter->writeAttribute('val', "$ruleValue"); + } + $objWriter->writeAttribute('percent', (($rule->getOperator() === Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT) ? '1' : '0')); + $objWriter->writeAttribute('top', (($rule->getGrouping() === Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP) ? '1' : '0')); + } else { + // Filter, DateGroupItem or CustomFilter + $objWriter->startElement($rule->getRuleType()); + + if ($rule->getOperator() !== Rule::AUTOFILTER_COLUMN_RULE_EQUAL) { + $objWriter->writeAttribute('operator', $rule->getOperator()); + } + if ($rule->getRuleType() === Rule::AUTOFILTER_RULETYPE_DATEGROUP) { + // Date Group filters + $ruleValue = $rule->getValue(); + if (is_array($ruleValue)) { + foreach ($ruleValue as $key => $value) { + $objWriter->writeAttribute($key, "$value"); + } + } + $objWriter->writeAttribute('dateTimeGrouping', $rule->getGrouping()); + } else { + $ruleValue = $rule->getValue(); + if (!is_array($ruleValue)) { + $objWriter->writeAttribute('val', "$ruleValue"); + } + } + + $objWriter->endElement(); + } + } + + $objWriter->endElement(); + + $objWriter->endElement(); + } + } + } + $objWriter->endElement(); + } + } + + /** + * Write PageSetup. + */ + private function writePageSetup(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void + { + // pageSetup + $objWriter->startElement('pageSetup'); + $objWriter->writeAttribute('paperSize', $worksheet->getPageSetup()->getPaperSize()); + $objWriter->writeAttribute('orientation', $worksheet->getPageSetup()->getOrientation()); + + if ($worksheet->getPageSetup()->getScale() !== null) { + $objWriter->writeAttribute('scale', $worksheet->getPageSetup()->getScale()); + } + if ($worksheet->getPageSetup()->getFitToHeight() !== null) { + $objWriter->writeAttribute('fitToHeight', $worksheet->getPageSetup()->getFitToHeight()); + } else { + $objWriter->writeAttribute('fitToHeight', '0'); + } + if ($worksheet->getPageSetup()->getFitToWidth() !== null) { + $objWriter->writeAttribute('fitToWidth', $worksheet->getPageSetup()->getFitToWidth()); + } else { + $objWriter->writeAttribute('fitToWidth', '0'); + } + if ($worksheet->getPageSetup()->getFirstPageNumber() !== null) { + $objWriter->writeAttribute('firstPageNumber', $worksheet->getPageSetup()->getFirstPageNumber()); + $objWriter->writeAttribute('useFirstPageNumber', '1'); + } + $objWriter->writeAttribute('pageOrder', $worksheet->getPageSetup()->getPageOrder()); + + $getUnparsedLoadedData = $worksheet->getParent()->getUnparsedLoadedData(); + if (isset($getUnparsedLoadedData['sheets'][$worksheet->getCodeName()]['pageSetupRelId'])) { + $objWriter->writeAttribute('r:id', $getUnparsedLoadedData['sheets'][$worksheet->getCodeName()]['pageSetupRelId']); + } + + $objWriter->endElement(); + } + + /** + * Write Header / Footer. + */ + private function writeHeaderFooter(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void + { + // headerFooter + $objWriter->startElement('headerFooter'); + $objWriter->writeAttribute('differentOddEven', ($worksheet->getHeaderFooter()->getDifferentOddEven() ? 'true' : 'false')); + $objWriter->writeAttribute('differentFirst', ($worksheet->getHeaderFooter()->getDifferentFirst() ? 'true' : 'false')); + $objWriter->writeAttribute('scaleWithDoc', ($worksheet->getHeaderFooter()->getScaleWithDocument() ? 'true' : 'false')); + $objWriter->writeAttribute('alignWithMargins', ($worksheet->getHeaderFooter()->getAlignWithMargins() ? 'true' : 'false')); + + $objWriter->writeElement('oddHeader', $worksheet->getHeaderFooter()->getOddHeader()); + $objWriter->writeElement('oddFooter', $worksheet->getHeaderFooter()->getOddFooter()); + $objWriter->writeElement('evenHeader', $worksheet->getHeaderFooter()->getEvenHeader()); + $objWriter->writeElement('evenFooter', $worksheet->getHeaderFooter()->getEvenFooter()); + $objWriter->writeElement('firstHeader', $worksheet->getHeaderFooter()->getFirstHeader()); + $objWriter->writeElement('firstFooter', $worksheet->getHeaderFooter()->getFirstFooter()); + $objWriter->endElement(); + } + + /** + * Write Breaks. + */ + private function writeBreaks(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void + { + // Get row and column breaks + $aRowBreaks = []; + $aColumnBreaks = []; + foreach ($worksheet->getBreaks() as $cell => $breakType) { + if ($breakType == PhpspreadsheetWorksheet::BREAK_ROW) { + $aRowBreaks[] = $cell; + } elseif ($breakType == PhpspreadsheetWorksheet::BREAK_COLUMN) { + $aColumnBreaks[] = $cell; + } + } + + // rowBreaks + if (!empty($aRowBreaks)) { + $objWriter->startElement('rowBreaks'); + $objWriter->writeAttribute('count', count($aRowBreaks)); + $objWriter->writeAttribute('manualBreakCount', count($aRowBreaks)); + + foreach ($aRowBreaks as $cell) { + $coords = Coordinate::coordinateFromString($cell); + + $objWriter->startElement('brk'); + $objWriter->writeAttribute('id', $coords[1]); + $objWriter->writeAttribute('man', '1'); + $objWriter->endElement(); + } + + $objWriter->endElement(); + } + + // Second, write column breaks + if (!empty($aColumnBreaks)) { + $objWriter->startElement('colBreaks'); + $objWriter->writeAttribute('count', count($aColumnBreaks)); + $objWriter->writeAttribute('manualBreakCount', count($aColumnBreaks)); + + foreach ($aColumnBreaks as $cell) { + $coords = Coordinate::coordinateFromString($cell); + + $objWriter->startElement('brk'); + $objWriter->writeAttribute('id', Coordinate::columnIndexFromString($coords[0]) - 1); + $objWriter->writeAttribute('man', '1'); + $objWriter->endElement(); + } + + $objWriter->endElement(); + } + } + + /** + * Write SheetData. + * + * @param string[] $stringTable String table + */ + private function writeSheetData(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet, array $stringTable): void + { + // Flipped stringtable, for faster index searching + $aFlippedStringTable = $this->getParentWriter()->getWriterPartstringtable()->flipStringTable($stringTable); + + // sheetData + $objWriter->startElement('sheetData'); + + // Get column count + $colCount = Coordinate::columnIndexFromString($worksheet->getHighestColumn()); + + // Highest row number + $highestRow = $worksheet->getHighestRow(); + + // Loop through cells + $cellsByRow = []; + foreach ($worksheet->getCoordinates() as $coordinate) { + $cellAddress = Coordinate::coordinateFromString($coordinate); + $cellsByRow[$cellAddress[1]][] = $coordinate; + } + + $currentRow = 0; + while ($currentRow++ < $highestRow) { + // Get row dimension + $rowDimension = $worksheet->getRowDimension($currentRow); + + // Write current row? + $writeCurrentRow = isset($cellsByRow[$currentRow]) || $rowDimension->getRowHeight() >= 0 || $rowDimension->getVisible() == false || $rowDimension->getCollapsed() == true || $rowDimension->getOutlineLevel() > 0 || $rowDimension->getXfIndex() !== null; + + if ($writeCurrentRow) { + // Start a new row + $objWriter->startElement('row'); + $objWriter->writeAttribute('r', $currentRow); + $objWriter->writeAttribute('spans', '1:' . $colCount); + + // Row dimensions + if ($rowDimension->getRowHeight() >= 0) { + $objWriter->writeAttribute('customHeight', '1'); + $objWriter->writeAttribute('ht', StringHelper::formatNumber($rowDimension->getRowHeight())); + } + + // Row visibility + if (!$rowDimension->getVisible() === true) { + $objWriter->writeAttribute('hidden', 'true'); + } + + // Collapsed + if ($rowDimension->getCollapsed() === true) { + $objWriter->writeAttribute('collapsed', 'true'); + } + + // Outline level + if ($rowDimension->getOutlineLevel() > 0) { + $objWriter->writeAttribute('outlineLevel', $rowDimension->getOutlineLevel()); + } + + // Style + if ($rowDimension->getXfIndex() !== null) { + $objWriter->writeAttribute('s', $rowDimension->getXfIndex()); + $objWriter->writeAttribute('customFormat', '1'); + } + + // Write cells + if (isset($cellsByRow[$currentRow])) { + foreach ($cellsByRow[$currentRow] as $cellAddress) { + // Write cell + $this->writeCell($objWriter, $worksheet, $cellAddress, $aFlippedStringTable); + } + } + + // End row + $objWriter->endElement(); + } + } + + $objWriter->endElement(); + } + + /** + * @param RichText|string $cellValue + */ + private function writeCellInlineStr(XMLWriter $objWriter, string $mappedType, $cellValue): void + { + $objWriter->writeAttribute('t', $mappedType); + if (!$cellValue instanceof RichText) { + $objWriter->writeElement( + 't', + StringHelper::controlCharacterPHP2OOXML(htmlspecialchars($cellValue, Settings::htmlEntityFlags())) + ); + } elseif ($cellValue instanceof RichText) { + $objWriter->startElement('is'); + $this->getParentWriter()->getWriterPartstringtable()->writeRichText($objWriter, $cellValue); + $objWriter->endElement(); + } + } + + /** + * @param RichText|string $cellValue + * @param string[] $flippedStringTable + */ + private function writeCellString(XMLWriter $objWriter, string $mappedType, $cellValue, array $flippedStringTable): void + { + $objWriter->writeAttribute('t', $mappedType); + if (!$cellValue instanceof RichText) { + self::writeElementIf($objWriter, isset($flippedStringTable[$cellValue]), 'v', $flippedStringTable[$cellValue] ?? ''); + } else { + $objWriter->writeElement('v', $flippedStringTable[$cellValue->getHashCode()]); + } + } + + /** + * @param float|int $cellValue + */ + private function writeCellNumeric(XMLWriter $objWriter, $cellValue): void + { + //force a decimal to be written if the type is float + if (is_float($cellValue)) { + // force point as decimal separator in case current locale uses comma + $cellValue = str_replace(',', '.', (string) $cellValue); + if (strpos($cellValue, '.') === false) { + $cellValue = $cellValue . '.0'; + } + } + $objWriter->writeElement('v', $cellValue); + } + + private function writeCellBoolean(XMLWriter $objWriter, string $mappedType, bool $cellValue): void + { + $objWriter->writeAttribute('t', $mappedType); + $objWriter->writeElement('v', $cellValue ? '1' : '0'); + } + + private function writeCellError(XMLWriter $objWriter, string $mappedType, string $cellValue, string $formulaerr = '#NULL!'): void + { + $objWriter->writeAttribute('t', $mappedType); + $cellIsFormula = substr($cellValue, 0, 1) === '='; + self::writeElementIf($objWriter, $cellIsFormula, 'f', Xlfn::addXlfnStripEquals($cellValue)); + $objWriter->writeElement('v', $cellIsFormula ? $formulaerr : $cellValue); + } + + private function writeCellFormula(XMLWriter $objWriter, string $cellValue, Cell $cell): void + { + $calculatedValue = $this->getParentWriter()->getPreCalculateFormulas() ? $cell->getCalculatedValue() : $cellValue; + if (is_string($calculatedValue)) { + if (\PhpOffice\PhpSpreadsheet\Calculation\Functions::isError($calculatedValue)) { + $this->writeCellError($objWriter, 'e', $cellValue, $calculatedValue); + + return; + } + $objWriter->writeAttribute('t', 'str'); + $calculatedValue = StringHelper::controlCharacterPHP2OOXML($calculatedValue); + } elseif (is_bool($calculatedValue)) { + $objWriter->writeAttribute('t', 'b'); + $calculatedValue = (int) $calculatedValue; + } + + $attributes = $cell->getFormulaAttributes(); + if (($attributes['t'] ?? null) === 'array') { + $objWriter->startElement('f'); + $objWriter->writeAttribute('t', 'array'); + $objWriter->writeAttribute('ref', $cell->getCoordinate()); + $objWriter->writeAttribute('aca', '1'); + $objWriter->writeAttribute('ca', '1'); + $objWriter->text(substr($cellValue, 1)); + $objWriter->endElement(); + } else { + $objWriter->writeElement('f', Xlfn::addXlfnStripEquals($cellValue)); + self::writeElementIf( + $objWriter, + $this->getParentWriter()->getOffice2003Compatibility() === false, + 'v', + ($this->getParentWriter()->getPreCalculateFormulas() && !is_array($calculatedValue) && substr($calculatedValue, 0, 1) !== '#') + ? StringHelper::formatNumber($calculatedValue) : '0' + ); + } + } + + /** + * Write Cell. + * + * @param string $cellAddress Cell Address + * @param string[] $flippedStringTable String table (flipped), for faster index searching + */ + private function writeCell(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet, string $cellAddress, array $flippedStringTable): void + { + // Cell + $pCell = $worksheet->getCell($cellAddress); + $objWriter->startElement('c'); + $objWriter->writeAttribute('r', $cellAddress); + + // Sheet styles + $xfi = $pCell->getXfIndex(); + self::writeAttributeIf($objWriter, $xfi, 's', $xfi); + + // If cell value is supplied, write cell value + $cellValue = $pCell->getValue(); + if (is_object($cellValue) || $cellValue !== '') { + // Map type + $mappedType = $pCell->getDataType(); + + // Write data depending on its type + switch (strtolower($mappedType)) { + case 'inlinestr': // Inline string + $this->writeCellInlineStr($objWriter, $mappedType, $cellValue); + + break; + case 's': // String + $this->writeCellString($objWriter, $mappedType, $cellValue, $flippedStringTable); + + break; + case 'f': // Formula + $this->writeCellFormula($objWriter, $cellValue, $pCell); + + break; + case 'n': // Numeric + $this->writeCellNumeric($objWriter, $cellValue); + + break; + case 'b': // Boolean + $this->writeCellBoolean($objWriter, $mappedType, $cellValue); + + break; + case 'e': // Error + $this->writeCellError($objWriter, $mappedType, $cellValue); + } + } + + $objWriter->endElement(); + } + + /** + * Write Drawings. + * + * @param bool $includeCharts Flag indicating if we should include drawing details for charts + */ + private function writeDrawings(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet, $includeCharts = false): void + { + $unparsedLoadedData = $worksheet->getParent()->getUnparsedLoadedData(); + $hasUnparsedDrawing = isset($unparsedLoadedData['sheets'][$worksheet->getCodeName()]['drawingOriginalIds']); + $chartCount = ($includeCharts) ? $worksheet->getChartCollection()->count() : 0; + if ($chartCount == 0 && $worksheet->getDrawingCollection()->count() == 0 && !$hasUnparsedDrawing) { + return; + } + + // If sheet contains drawings, add the relationships + $objWriter->startElement('drawing'); + + $rId = 'rId1'; + if (isset($unparsedLoadedData['sheets'][$worksheet->getCodeName()]['drawingOriginalIds'])) { + $drawingOriginalIds = $unparsedLoadedData['sheets'][$worksheet->getCodeName()]['drawingOriginalIds']; + // take first. In future can be overriten + // (! synchronize with \PhpOffice\PhpSpreadsheet\Writer\Xlsx\Rels::writeWorksheetRelationships) + $rId = reset($drawingOriginalIds); + } + + $objWriter->writeAttribute('r:id', $rId); + $objWriter->endElement(); + } + + /** + * Write LegacyDrawing. + */ + private function writeLegacyDrawing(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void + { + // If sheet contains comments, add the relationships + if (count($worksheet->getComments()) > 0) { + $objWriter->startElement('legacyDrawing'); + $objWriter->writeAttribute('r:id', 'rId_comments_vml1'); + $objWriter->endElement(); + } + } + + /** + * Write LegacyDrawingHF. + */ + private function writeLegacyDrawingHF(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void + { + // If sheet contains images, add the relationships + if (count($worksheet->getHeaderFooter()->getImages()) > 0) { + $objWriter->startElement('legacyDrawingHF'); + $objWriter->writeAttribute('r:id', 'rId_headerfooter_vml1'); + $objWriter->endElement(); + } + } + + private function writeAlternateContent(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void + { + if (empty($worksheet->getParent()->getUnparsedLoadedData()['sheets'][$worksheet->getCodeName()]['AlternateContents'])) { + return; + } + + foreach ($worksheet->getParent()->getUnparsedLoadedData()['sheets'][$worksheet->getCodeName()]['AlternateContents'] as $alternateContent) { + $objWriter->writeRaw($alternateContent); + } + } + + /** + * write + * only implementation conditionalFormattings. + * + * @url https://docs.microsoft.com/en-us/openspecs/office_standards/ms-xlsx/07d607af-5618-4ca2-b683-6a78dc0d9627 + */ + private function writeExtLst(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void + { + $conditionalFormattingRuleExtList = []; + foreach ($worksheet->getConditionalStylesCollection() as $cellCoordinate => $conditionalStyles) { + /** @var Conditional $conditional */ + foreach ($conditionalStyles as $conditional) { + $dataBar = $conditional->getDataBar(); + // @phpstan-ignore-next-line + if ($dataBar && $dataBar->getConditionalFormattingRuleExt()) { + $conditionalFormattingRuleExtList[] = $dataBar->getConditionalFormattingRuleExt(); + } + } + } + + if (count($conditionalFormattingRuleExtList) > 0) { + $conditionalFormattingRuleExtNsPrefix = 'x14'; + $objWriter->startElement('extLst'); + $objWriter->startElement('ext'); + $objWriter->writeAttribute('uri', '{78C0D931-6437-407d-A8EE-F0AAD7539E65}'); + $objWriter->startElementNs($conditionalFormattingRuleExtNsPrefix, 'conditionalFormattings', null); + foreach ($conditionalFormattingRuleExtList as $extension) { + self::writeExtConditionalFormattingElements($objWriter, $extension); + } + $objWriter->endElement(); //end conditionalFormattings + $objWriter->endElement(); //end ext + $objWriter->endElement(); //end extLst + } + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/WriterPart.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/WriterPart.php new file mode 100644 index 0000000..0bfb356 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/WriterPart.php @@ -0,0 +1,33 @@ +parentWriter; + } + + /** + * Set parent Xlsx object. + */ + public function __construct(Xlsx $writer) + { + $this->parentWriter = $writer; + } +} diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Xlfn.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Xlfn.php new file mode 100644 index 0000000..c88ef24 --- /dev/null +++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Xlfn.php @@ -0,0 +1,166 @@ +setRules([ + '@Symfony' => true, + '@Symfony:risky' => true, + '@PHPUnit75Migration:risky' => true, + 'php_unit_dedicate_assert' => true, + 'array_syntax' => ['syntax' => 'short'], + 'php_unit_fqcn_annotation' => true, + 'no_unreachable_default_argument_value' => false, + 'braces' => ['allow_single_line_closure' => true], + 'heredoc_to_nowdoc' => false, + 'ordered_imports' => true, + 'phpdoc_types_order' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'], + 'native_function_invocation' => ['include' => ['@compiler_optimized'], 'scope' => 'all'], + ]) + ->setRiskyAllowed(true) + ->setFinder(PhpCsFixer\Finder::create()->in(__DIR__.'/src')) +; diff --git a/vendor/pimple/pimple/CHANGELOG b/vendor/pimple/pimple/CHANGELOG new file mode 100644 index 0000000..08059e5 --- /dev/null +++ b/vendor/pimple/pimple/CHANGELOG @@ -0,0 +1,72 @@ +* 3.4.0 (2021-03-06) + + * Implement version 1.1 of PSR-11 + +* 3.3.1 (2020-11-24) + + * Add support for PHP 8 + +* 3.3.0 (2020-03-03) + + * Drop PHP extension + * Bump min PHP version to 7.2.5 + +* 3.2.3 (2018-01-21) + + * prefixed all function calls with \ for extra speed + +* 3.2.2 (2017-07-23) + + * reverted extending a protected closure throws an exception (deprecated it instead) + +* 3.2.1 (2017-07-17) + + * fixed PHP error + +* 3.2.0 (2017-07-17) + + * added a PSR-11 service locator + * added a PSR-11 wrapper + * added ServiceIterator + * fixed extending a protected closure (now throws InvalidServiceIdentifierException) + +* 3.1.0 (2017-07-03) + + * deprecated the C extension + * added support for PSR-11 exceptions + +* 3.0.2 (2015-09-11) + + * refactored the C extension + * minor non-significant changes + +* 3.0.1 (2015-07-30) + + * simplified some code + * fixed a segfault in the C extension + +* 3.0.0 (2014-07-24) + + * removed the Pimple class alias (use Pimple\Container instead) + +* 2.1.1 (2014-07-24) + + * fixed compiler warnings for the C extension + * fixed code when dealing with circular references + +* 2.1.0 (2014-06-24) + + * moved the Pimple to Pimple\Container (with a BC layer -- Pimple is now a + deprecated alias which will be removed in Pimple 3.0) + * added Pimple\ServiceProviderInterface (and Pimple::register()) + +* 2.0.0 (2014-02-10) + + * changed extend to automatically re-assign the extended service and keep it as shared or factory + (to keep BC, extend still returns the extended service) + * changed services to be shared by default (use factory() for factory + services) + +* 1.0.0 + + * initial version diff --git a/vendor/pimple/pimple/LICENSE b/vendor/pimple/pimple/LICENSE new file mode 100644 index 0000000..3e2a9e1 --- /dev/null +++ b/vendor/pimple/pimple/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2009-2020 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/pimple/pimple/README.rst b/vendor/pimple/pimple/README.rst new file mode 100644 index 0000000..7081839 --- /dev/null +++ b/vendor/pimple/pimple/README.rst @@ -0,0 +1,332 @@ +Pimple +====== + +.. caution:: + + Pimple is now closed for changes. No new features will be added and no + cosmetic changes will be accepted either. The only accepted changes are + compatibility with newer PHP versions and security issue fixes. + +.. caution:: + + This is the documentation for Pimple 3.x. If you are using Pimple 1.x, read + the `Pimple 1.x documentation`_. Reading the Pimple 1.x code is also a good + way to learn more about how to create a simple Dependency Injection + Container (recent versions of Pimple are more focused on performance). + +Pimple is a small Dependency Injection Container for PHP. + +Installation +------------ + +Before using Pimple in your project, add it to your ``composer.json`` file: + +.. code-block:: bash + + $ ./composer.phar require pimple/pimple "^3.0" + +Usage +----- + +Creating a container is a matter of creating a ``Container`` instance: + +.. code-block:: php + + use Pimple\Container; + + $container = new Container(); + +As many other dependency injection containers, Pimple manages two different +kind of data: **services** and **parameters**. + +Defining Services +~~~~~~~~~~~~~~~~~ + +A service is an object that does something as part of a larger system. Examples +of services: a database connection, a templating engine, or a mailer. Almost +any **global** object can be a service. + +Services are defined by **anonymous functions** that return an instance of an +object: + +.. code-block:: php + + // define some services + $container['session_storage'] = function ($c) { + return new SessionStorage('SESSION_ID'); + }; + + $container['session'] = function ($c) { + return new Session($c['session_storage']); + }; + +Notice that the anonymous function has access to the current container +instance, allowing references to other services or parameters. + +As objects are only created when you get them, the order of the definitions +does not matter. + +Using the defined services is also very easy: + +.. code-block:: php + + // get the session object + $session = $container['session']; + + // the above call is roughly equivalent to the following code: + // $storage = new SessionStorage('SESSION_ID'); + // $session = new Session($storage); + +Defining Factory Services +~~~~~~~~~~~~~~~~~~~~~~~~~ + +By default, each time you get a service, Pimple returns the **same instance** +of it. If you want a different instance to be returned for all calls, wrap your +anonymous function with the ``factory()`` method + +.. code-block:: php + + $container['session'] = $container->factory(function ($c) { + return new Session($c['session_storage']); + }); + +Now, each call to ``$container['session']`` returns a new instance of the +session. + +Defining Parameters +~~~~~~~~~~~~~~~~~~~ + +Defining a parameter allows to ease the configuration of your container from +the outside and to store global values: + +.. code-block:: php + + // define some parameters + $container['cookie_name'] = 'SESSION_ID'; + $container['session_storage_class'] = 'SessionStorage'; + +If you change the ``session_storage`` service definition like below: + +.. code-block:: php + + $container['session_storage'] = function ($c) { + return new $c['session_storage_class']($c['cookie_name']); + }; + +You can now easily change the cookie name by overriding the +``cookie_name`` parameter instead of redefining the service +definition. + +Protecting Parameters +~~~~~~~~~~~~~~~~~~~~~ + +Because Pimple sees anonymous functions as service definitions, you need to +wrap anonymous functions with the ``protect()`` method to store them as +parameters: + +.. code-block:: php + + $container['random_func'] = $container->protect(function () { + return rand(); + }); + +Modifying Services after Definition +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In some cases you may want to modify a service definition after it has been +defined. You can use the ``extend()`` method to define additional code to be +run on your service just after it is created: + +.. code-block:: php + + $container['session_storage'] = function ($c) { + return new $c['session_storage_class']($c['cookie_name']); + }; + + $container->extend('session_storage', function ($storage, $c) { + $storage->...(); + + return $storage; + }); + +The first argument is the name of the service to extend, the second a function +that gets access to the object instance and the container. + +Extending a Container +~~~~~~~~~~~~~~~~~~~~~ + +If you use the same libraries over and over, you might want to reuse some +services from one project to the next one; package your services into a +**provider** by implementing ``Pimple\ServiceProviderInterface``: + +.. code-block:: php + + use Pimple\Container; + + class FooProvider implements Pimple\ServiceProviderInterface + { + public function register(Container $pimple) + { + // register some services and parameters + // on $pimple + } + } + +Then, register the provider on a Container: + +.. code-block:: php + + $pimple->register(new FooProvider()); + +Fetching the Service Creation Function +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When you access an object, Pimple automatically calls the anonymous function +that you defined, which creates the service object for you. If you want to get +raw access to this function, you can use the ``raw()`` method: + +.. code-block:: php + + $container['session'] = function ($c) { + return new Session($c['session_storage']); + }; + + $sessionFunction = $container->raw('session'); + +PSR-11 compatibility +-------------------- + +For historical reasons, the ``Container`` class does not implement the PSR-11 +``ContainerInterface``. However, Pimple provides a helper class that will let +you decouple your code from the Pimple container class. + +The PSR-11 container class +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``Pimple\Psr11\Container`` class lets you access the content of an +underlying Pimple container using ``Psr\Container\ContainerInterface`` +methods: + +.. code-block:: php + + use Pimple\Container; + use Pimple\Psr11\Container as PsrContainer; + + $container = new Container(); + $container['service'] = function ($c) { + return new Service(); + }; + $psr11 = new PsrContainer($container); + + $controller = function (PsrContainer $container) { + $service = $container->get('service'); + }; + $controller($psr11); + +Using the PSR-11 ServiceLocator +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Sometimes, a service needs access to several other services without being sure +that all of them will actually be used. In those cases, you may want the +instantiation of the services to be lazy. + +The traditional solution is to inject the entire service container to get only +the services really needed. However, this is not recommended because it gives +services a too broad access to the rest of the application and it hides their +actual dependencies. + +The ``ServiceLocator`` is intended to solve this problem by giving access to a +set of predefined services while instantiating them only when actually needed. + +It also allows you to make your services available under a different name than +the one used to register them. For instance, you may want to use an object +that expects an instance of ``EventDispatcherInterface`` to be available under +the name ``event_dispatcher`` while your event dispatcher has been +registered under the name ``dispatcher``: + +.. code-block:: php + + use Monolog\Logger; + use Pimple\Psr11\ServiceLocator; + use Psr\Container\ContainerInterface; + use Symfony\Component\EventDispatcher\EventDispatcher; + + class MyService + { + /** + * "logger" must be an instance of Psr\Log\LoggerInterface + * "event_dispatcher" must be an instance of Symfony\Component\EventDispatcher\EventDispatcherInterface + */ + private $services; + + public function __construct(ContainerInterface $services) + { + $this->services = $services; + } + } + + $container['logger'] = function ($c) { + return new Monolog\Logger(); + }; + $container['dispatcher'] = function () { + return new EventDispatcher(); + }; + + $container['service'] = function ($c) { + $locator = new ServiceLocator($c, array('logger', 'event_dispatcher' => 'dispatcher')); + + return new MyService($locator); + }; + +Referencing a Collection of Services Lazily +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Passing a collection of services instances in an array may prove inefficient +if the class that consumes the collection only needs to iterate over it at a +later stage, when one of its method is called. It can also lead to problems +if there is a circular dependency between one of the services stored in the +collection and the class that consumes it. + +The ``ServiceIterator`` class helps you solve these issues. It receives a +list of service names during instantiation and will retrieve the services +when iterated over: + +.. code-block:: php + + use Pimple\Container; + use Pimple\ServiceIterator; + + class AuthorizationService + { + private $voters; + + public function __construct($voters) + { + $this->voters = $voters; + } + + public function canAccess($resource) + { + foreach ($this->voters as $voter) { + if (true === $voter->canAccess($resource)) { + return true; + } + } + + return false; + } + } + + $container = new Container(); + + $container['voter1'] = function ($c) { + return new SomeVoter(); + } + $container['voter2'] = function ($c) { + return new SomeOtherVoter($c['auth']); + } + $container['auth'] = function ($c) { + return new AuthorizationService(new ServiceIterator($c, array('voter1', 'voter2')); + } + +.. _Pimple 1.x documentation: https://github.com/silexphp/Pimple/tree/1.1 diff --git a/vendor/pimple/pimple/composer.json b/vendor/pimple/pimple/composer.json new file mode 100644 index 0000000..ca6a581 --- /dev/null +++ b/vendor/pimple/pimple/composer.json @@ -0,0 +1,29 @@ +{ + "name": "pimple/pimple", + "type": "library", + "description": "Pimple, a simple Dependency Injection Container", + "keywords": ["dependency injection", "container"], + "homepage": "https://pimple.symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1 || ^2.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^5.4@dev" + }, + "autoload": { + "psr-0": { "Pimple": "src/" } + }, + "extra": { + "branch-alias": { + "dev-master": "3.4.x-dev" + } + } +} diff --git a/vendor/pimple/pimple/phpunit.xml.dist b/vendor/pimple/pimple/phpunit.xml.dist new file mode 100644 index 0000000..8990202 --- /dev/null +++ b/vendor/pimple/pimple/phpunit.xml.dist @@ -0,0 +1,18 @@ + + + + + + ./src/Pimple/Tests + + + + + + + diff --git a/vendor/pimple/pimple/src/Pimple/Container.php b/vendor/pimple/pimple/src/Pimple/Container.php new file mode 100644 index 0000000..586a0b7 --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/Container.php @@ -0,0 +1,305 @@ +factories = new \SplObjectStorage(); + $this->protected = new \SplObjectStorage(); + + foreach ($values as $key => $value) { + $this->offsetSet($key, $value); + } + } + + /** + * Sets a parameter or an object. + * + * Objects must be defined as Closures. + * + * Allowing any PHP callable leads to difficult to debug problems + * as function names (strings) are callable (creating a function with + * the same name as an existing parameter would break your container). + * + * @param string $id The unique identifier for the parameter or object + * @param mixed $value The value of the parameter or a closure to define an object + * + * @return void + * + * @throws FrozenServiceException Prevent override of a frozen service + */ + #[\ReturnTypeWillChange] + public function offsetSet($id, $value) + { + if (isset($this->frozen[$id])) { + throw new FrozenServiceException($id); + } + + $this->values[$id] = $value; + $this->keys[$id] = true; + } + + /** + * Gets a parameter or an object. + * + * @param string $id The unique identifier for the parameter or object + * + * @return mixed The value of the parameter or an object + * + * @throws UnknownIdentifierException If the identifier is not defined + */ + #[\ReturnTypeWillChange] + public function offsetGet($id) + { + if (!isset($this->keys[$id])) { + throw new UnknownIdentifierException($id); + } + + if ( + isset($this->raw[$id]) + || !\is_object($this->values[$id]) + || isset($this->protected[$this->values[$id]]) + || !\method_exists($this->values[$id], '__invoke') + ) { + return $this->values[$id]; + } + + if (isset($this->factories[$this->values[$id]])) { + return $this->values[$id]($this); + } + + $raw = $this->values[$id]; + $val = $this->values[$id] = $raw($this); + $this->raw[$id] = $raw; + + $this->frozen[$id] = true; + + return $val; + } + + /** + * Checks if a parameter or an object is set. + * + * @param string $id The unique identifier for the parameter or object + * + * @return bool + */ + #[\ReturnTypeWillChange] + public function offsetExists($id) + { + return isset($this->keys[$id]); + } + + /** + * Unsets a parameter or an object. + * + * @param string $id The unique identifier for the parameter or object + * + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetUnset($id) + { + if (isset($this->keys[$id])) { + if (\is_object($this->values[$id])) { + unset($this->factories[$this->values[$id]], $this->protected[$this->values[$id]]); + } + + unset($this->values[$id], $this->frozen[$id], $this->raw[$id], $this->keys[$id]); + } + } + + /** + * Marks a callable as being a factory service. + * + * @param callable $callable A service definition to be used as a factory + * + * @return callable The passed callable + * + * @throws ExpectedInvokableException Service definition has to be a closure or an invokable object + */ + public function factory($callable) + { + if (!\is_object($callable) || !\method_exists($callable, '__invoke')) { + throw new ExpectedInvokableException('Service definition is not a Closure or invokable object.'); + } + + $this->factories->attach($callable); + + return $callable; + } + + /** + * Protects a callable from being interpreted as a service. + * + * This is useful when you want to store a callable as a parameter. + * + * @param callable $callable A callable to protect from being evaluated + * + * @return callable The passed callable + * + * @throws ExpectedInvokableException Service definition has to be a closure or an invokable object + */ + public function protect($callable) + { + if (!\is_object($callable) || !\method_exists($callable, '__invoke')) { + throw new ExpectedInvokableException('Callable is not a Closure or invokable object.'); + } + + $this->protected->attach($callable); + + return $callable; + } + + /** + * Gets a parameter or the closure defining an object. + * + * @param string $id The unique identifier for the parameter or object + * + * @return mixed The value of the parameter or the closure defining an object + * + * @throws UnknownIdentifierException If the identifier is not defined + */ + public function raw($id) + { + if (!isset($this->keys[$id])) { + throw new UnknownIdentifierException($id); + } + + if (isset($this->raw[$id])) { + return $this->raw[$id]; + } + + return $this->values[$id]; + } + + /** + * Extends an object definition. + * + * Useful when you want to extend an existing object definition, + * without necessarily loading that object. + * + * @param string $id The unique identifier for the object + * @param callable $callable A service definition to extend the original + * + * @return callable The wrapped callable + * + * @throws UnknownIdentifierException If the identifier is not defined + * @throws FrozenServiceException If the service is frozen + * @throws InvalidServiceIdentifierException If the identifier belongs to a parameter + * @throws ExpectedInvokableException If the extension callable is not a closure or an invokable object + */ + public function extend($id, $callable) + { + if (!isset($this->keys[$id])) { + throw new UnknownIdentifierException($id); + } + + if (isset($this->frozen[$id])) { + throw new FrozenServiceException($id); + } + + if (!\is_object($this->values[$id]) || !\method_exists($this->values[$id], '__invoke')) { + throw new InvalidServiceIdentifierException($id); + } + + if (isset($this->protected[$this->values[$id]])) { + @\trigger_error(\sprintf('How Pimple behaves when extending protected closures will be fixed in Pimple 4. Are you sure "%s" should be protected?', $id), E_USER_DEPRECATED); + } + + if (!\is_object($callable) || !\method_exists($callable, '__invoke')) { + throw new ExpectedInvokableException('Extension service definition is not a Closure or invokable object.'); + } + + $factory = $this->values[$id]; + + $extended = function ($c) use ($callable, $factory) { + return $callable($factory($c), $c); + }; + + if (isset($this->factories[$factory])) { + $this->factories->detach($factory); + $this->factories->attach($extended); + } + + return $this[$id] = $extended; + } + + /** + * Returns all defined value names. + * + * @return array An array of value names + */ + public function keys() + { + return \array_keys($this->values); + } + + /** + * Registers a service provider. + * + * @param array $values An array of values that customizes the provider + * + * @return static + */ + public function register(ServiceProviderInterface $provider, array $values = []) + { + $provider->register($this); + + foreach ($values as $key => $value) { + $this[$key] = $value; + } + + return $this; + } +} diff --git a/vendor/pimple/pimple/src/Pimple/Exception/ExpectedInvokableException.php b/vendor/pimple/pimple/src/Pimple/Exception/ExpectedInvokableException.php new file mode 100644 index 0000000..7228421 --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/Exception/ExpectedInvokableException.php @@ -0,0 +1,38 @@ + + */ +class ExpectedInvokableException extends \InvalidArgumentException implements ContainerExceptionInterface +{ +} diff --git a/vendor/pimple/pimple/src/Pimple/Exception/FrozenServiceException.php b/vendor/pimple/pimple/src/Pimple/Exception/FrozenServiceException.php new file mode 100644 index 0000000..e4d2f6d --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/Exception/FrozenServiceException.php @@ -0,0 +1,45 @@ + + */ +class FrozenServiceException extends \RuntimeException implements ContainerExceptionInterface +{ + /** + * @param string $id Identifier of the frozen service + */ + public function __construct($id) + { + parent::__construct(\sprintf('Cannot override frozen service "%s".', $id)); + } +} diff --git a/vendor/pimple/pimple/src/Pimple/Exception/InvalidServiceIdentifierException.php b/vendor/pimple/pimple/src/Pimple/Exception/InvalidServiceIdentifierException.php new file mode 100644 index 0000000..91e82f9 --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/Exception/InvalidServiceIdentifierException.php @@ -0,0 +1,45 @@ + + */ +class InvalidServiceIdentifierException extends \InvalidArgumentException implements NotFoundExceptionInterface +{ + /** + * @param string $id The invalid identifier + */ + public function __construct($id) + { + parent::__construct(\sprintf('Identifier "%s" does not contain an object definition.', $id)); + } +} diff --git a/vendor/pimple/pimple/src/Pimple/Exception/UnknownIdentifierException.php b/vendor/pimple/pimple/src/Pimple/Exception/UnknownIdentifierException.php new file mode 100644 index 0000000..fb6b626 --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/Exception/UnknownIdentifierException.php @@ -0,0 +1,45 @@ + + */ +class UnknownIdentifierException extends \InvalidArgumentException implements NotFoundExceptionInterface +{ + /** + * @param string $id The unknown identifier + */ + public function __construct($id) + { + parent::__construct(\sprintf('Identifier "%s" is not defined.', $id)); + } +} diff --git a/vendor/pimple/pimple/src/Pimple/Psr11/Container.php b/vendor/pimple/pimple/src/Pimple/Psr11/Container.php new file mode 100644 index 0000000..e18592e --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/Psr11/Container.php @@ -0,0 +1,55 @@ + + */ +final class Container implements ContainerInterface +{ + private $pimple; + + public function __construct(PimpleContainer $pimple) + { + $this->pimple = $pimple; + } + + public function get(string $id) + { + return $this->pimple[$id]; + } + + public function has(string $id): bool + { + return isset($this->pimple[$id]); + } +} diff --git a/vendor/pimple/pimple/src/Pimple/Psr11/ServiceLocator.php b/vendor/pimple/pimple/src/Pimple/Psr11/ServiceLocator.php new file mode 100644 index 0000000..714b882 --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/Psr11/ServiceLocator.php @@ -0,0 +1,75 @@ + + */ +class ServiceLocator implements ContainerInterface +{ + private $container; + private $aliases = []; + + /** + * @param PimpleContainer $container The Container instance used to locate services + * @param array $ids Array of service ids that can be located. String keys can be used to define aliases + */ + public function __construct(PimpleContainer $container, array $ids) + { + $this->container = $container; + + foreach ($ids as $key => $id) { + $this->aliases[\is_int($key) ? $id : $key] = $id; + } + } + + /** + * {@inheritdoc} + */ + public function get(string $id) + { + if (!isset($this->aliases[$id])) { + throw new UnknownIdentifierException($id); + } + + return $this->container[$this->aliases[$id]]; + } + + /** + * {@inheritdoc} + */ + public function has(string $id): bool + { + return isset($this->aliases[$id]) && isset($this->container[$this->aliases[$id]]); + } +} diff --git a/vendor/pimple/pimple/src/Pimple/ServiceIterator.php b/vendor/pimple/pimple/src/Pimple/ServiceIterator.php new file mode 100644 index 0000000..ebafac1 --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/ServiceIterator.php @@ -0,0 +1,89 @@ + + */ +final class ServiceIterator implements \Iterator +{ + private $container; + private $ids; + + public function __construct(Container $container, array $ids) + { + $this->container = $container; + $this->ids = $ids; + } + + /** + * @return void + */ + #[\ReturnTypeWillChange] + public function rewind() + { + \reset($this->ids); + } + + /** + * @return mixed + */ + #[\ReturnTypeWillChange] + public function current() + { + return $this->container[\current($this->ids)]; + } + + /** + * @return mixed + */ + #[\ReturnTypeWillChange] + public function key() + { + return \current($this->ids); + } + + /** + * @return void + */ + #[\ReturnTypeWillChange] + public function next() + { + \next($this->ids); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function valid() + { + return null !== \key($this->ids); + } +} diff --git a/vendor/pimple/pimple/src/Pimple/ServiceProviderInterface.php b/vendor/pimple/pimple/src/Pimple/ServiceProviderInterface.php new file mode 100644 index 0000000..abf90d8 --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/ServiceProviderInterface.php @@ -0,0 +1,44 @@ +value = $value; + + return $service; + } +} diff --git a/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/NonInvokable.php b/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/NonInvokable.php new file mode 100644 index 0000000..33cd4e5 --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/NonInvokable.php @@ -0,0 +1,34 @@ +factory(function () { + return new Service(); + }); + } +} diff --git a/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/Service.php b/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/Service.php new file mode 100644 index 0000000..d71b184 --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/Service.php @@ -0,0 +1,35 @@ + + */ +class Service +{ + public $value; +} diff --git a/vendor/pimple/pimple/src/Pimple/Tests/PimpleServiceProviderInterfaceTest.php b/vendor/pimple/pimple/src/Pimple/Tests/PimpleServiceProviderInterfaceTest.php new file mode 100644 index 0000000..097a7fd --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/Tests/PimpleServiceProviderInterfaceTest.php @@ -0,0 +1,77 @@ + + */ +class PimpleServiceProviderInterfaceTest extends TestCase +{ + public function testProvider() + { + $pimple = new Container(); + + $pimpleServiceProvider = new Fixtures\PimpleServiceProvider(); + $pimpleServiceProvider->register($pimple); + + $this->assertEquals('value', $pimple['param']); + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $pimple['service']); + + $serviceOne = $pimple['factory']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne); + + $serviceTwo = $pimple['factory']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo); + + $this->assertNotSame($serviceOne, $serviceTwo); + } + + public function testProviderWithRegisterMethod() + { + $pimple = new Container(); + + $pimple->register(new Fixtures\PimpleServiceProvider(), [ + 'anotherParameter' => 'anotherValue', + ]); + + $this->assertEquals('value', $pimple['param']); + $this->assertEquals('anotherValue', $pimple['anotherParameter']); + + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $pimple['service']); + + $serviceOne = $pimple['factory']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne); + + $serviceTwo = $pimple['factory']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo); + + $this->assertNotSame($serviceOne, $serviceTwo); + } +} diff --git a/vendor/pimple/pimple/src/Pimple/Tests/PimpleTest.php b/vendor/pimple/pimple/src/Pimple/Tests/PimpleTest.php new file mode 100644 index 0000000..ffa50a6 --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/Tests/PimpleTest.php @@ -0,0 +1,610 @@ + + */ +class PimpleTest extends TestCase +{ + public function testWithString() + { + $pimple = new Container(); + $pimple['param'] = 'value'; + + $this->assertEquals('value', $pimple['param']); + } + + public function testWithClosure() + { + $pimple = new Container(); + $pimple['service'] = function () { + return new Fixtures\Service(); + }; + + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $pimple['service']); + } + + public function testServicesShouldBeDifferent() + { + $pimple = new Container(); + $pimple['service'] = $pimple->factory(function () { + return new Fixtures\Service(); + }); + + $serviceOne = $pimple['service']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne); + + $serviceTwo = $pimple['service']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo); + + $this->assertNotSame($serviceOne, $serviceTwo); + } + + public function testShouldPassContainerAsParameter() + { + $pimple = new Container(); + $pimple['service'] = function () { + return new Fixtures\Service(); + }; + $pimple['container'] = function ($container) { + return $container; + }; + + $this->assertNotSame($pimple, $pimple['service']); + $this->assertSame($pimple, $pimple['container']); + } + + public function testIsset() + { + $pimple = new Container(); + $pimple['param'] = 'value'; + $pimple['service'] = function () { + return new Fixtures\Service(); + }; + + $pimple['null'] = null; + + $this->assertTrue(isset($pimple['param'])); + $this->assertTrue(isset($pimple['service'])); + $this->assertTrue(isset($pimple['null'])); + $this->assertFalse(isset($pimple['non_existent'])); + } + + public function testConstructorInjection() + { + $params = ['param' => 'value']; + $pimple = new Container($params); + + $this->assertSame($params['param'], $pimple['param']); + } + + public function testOffsetGetValidatesKeyIsPresent() + { + $this->expectException(\Pimple\Exception\UnknownIdentifierException::class); + $this->expectExceptionMessage('Identifier "foo" is not defined.'); + + $pimple = new Container(); + echo $pimple['foo']; + } + + /** + * @group legacy + */ + public function testLegacyOffsetGetValidatesKeyIsPresent() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Identifier "foo" is not defined.'); + + $pimple = new Container(); + echo $pimple['foo']; + } + + public function testOffsetGetHonorsNullValues() + { + $pimple = new Container(); + $pimple['foo'] = null; + $this->assertNull($pimple['foo']); + } + + public function testUnset() + { + $pimple = new Container(); + $pimple['param'] = 'value'; + $pimple['service'] = function () { + return new Fixtures\Service(); + }; + + unset($pimple['param'], $pimple['service']); + $this->assertFalse(isset($pimple['param'])); + $this->assertFalse(isset($pimple['service'])); + } + + /** + * @dataProvider serviceDefinitionProvider + */ + public function testShare($service) + { + $pimple = new Container(); + $pimple['shared_service'] = $service; + + $serviceOne = $pimple['shared_service']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne); + + $serviceTwo = $pimple['shared_service']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo); + + $this->assertSame($serviceOne, $serviceTwo); + } + + /** + * @dataProvider serviceDefinitionProvider + */ + public function testProtect($service) + { + $pimple = new Container(); + $pimple['protected'] = $pimple->protect($service); + + $this->assertSame($service, $pimple['protected']); + } + + public function testGlobalFunctionNameAsParameterValue() + { + $pimple = new Container(); + $pimple['global_function'] = 'strlen'; + $this->assertSame('strlen', $pimple['global_function']); + } + + public function testRaw() + { + $pimple = new Container(); + $pimple['service'] = $definition = $pimple->factory(function () { + return 'foo'; + }); + $this->assertSame($definition, $pimple->raw('service')); + } + + public function testRawHonorsNullValues() + { + $pimple = new Container(); + $pimple['foo'] = null; + $this->assertNull($pimple->raw('foo')); + } + + public function testFluentRegister() + { + $pimple = new Container(); + $this->assertSame($pimple, $pimple->register($this->getMockBuilder('Pimple\ServiceProviderInterface')->getMock())); + } + + public function testRawValidatesKeyIsPresent() + { + $this->expectException(\Pimple\Exception\UnknownIdentifierException::class); + $this->expectExceptionMessage('Identifier "foo" is not defined.'); + + $pimple = new Container(); + $pimple->raw('foo'); + } + + /** + * @group legacy + */ + public function testLegacyRawValidatesKeyIsPresent() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Identifier "foo" is not defined.'); + + $pimple = new Container(); + $pimple->raw('foo'); + } + + /** + * @dataProvider serviceDefinitionProvider + */ + public function testExtend($service) + { + $pimple = new Container(); + $pimple['shared_service'] = function () { + return new Fixtures\Service(); + }; + $pimple['factory_service'] = $pimple->factory(function () { + return new Fixtures\Service(); + }); + + $pimple->extend('shared_service', $service); + $serviceOne = $pimple['shared_service']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne); + $serviceTwo = $pimple['shared_service']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo); + $this->assertSame($serviceOne, $serviceTwo); + $this->assertSame($serviceOne->value, $serviceTwo->value); + + $pimple->extend('factory_service', $service); + $serviceOne = $pimple['factory_service']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne); + $serviceTwo = $pimple['factory_service']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo); + $this->assertNotSame($serviceOne, $serviceTwo); + $this->assertNotSame($serviceOne->value, $serviceTwo->value); + } + + public function testExtendDoesNotLeakWithFactories() + { + if (\extension_loaded('pimple')) { + $this->markTestSkipped('Pimple extension does not support this test'); + } + $pimple = new Container(); + + $pimple['foo'] = $pimple->factory(function () { + return; + }); + $pimple['foo'] = $pimple->extend('foo', function ($foo, $pimple) { + return; + }); + unset($pimple['foo']); + + $p = new \ReflectionProperty($pimple, 'values'); + $p->setAccessible(true); + $this->assertEmpty($p->getValue($pimple)); + + $p = new \ReflectionProperty($pimple, 'factories'); + $p->setAccessible(true); + $this->assertCount(0, $p->getValue($pimple)); + } + + public function testExtendValidatesKeyIsPresent() + { + $this->expectException(\Pimple\Exception\UnknownIdentifierException::class); + $this->expectExceptionMessage('Identifier "foo" is not defined.'); + + $pimple = new Container(); + $pimple->extend('foo', function () { + }); + } + + /** + * @group legacy + */ + public function testLegacyExtendValidatesKeyIsPresent() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Identifier "foo" is not defined.'); + + $pimple = new Container(); + $pimple->extend('foo', function () { + }); + } + + public function testKeys() + { + $pimple = new Container(); + $pimple['foo'] = 123; + $pimple['bar'] = 123; + + $this->assertEquals(['foo', 'bar'], $pimple->keys()); + } + + /** @test */ + public function settingAnInvokableObjectShouldTreatItAsFactory() + { + $pimple = new Container(); + $pimple['invokable'] = new Fixtures\Invokable(); + + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $pimple['invokable']); + } + + /** @test */ + public function settingNonInvokableObjectShouldTreatItAsParameter() + { + $pimple = new Container(); + $pimple['non_invokable'] = new Fixtures\NonInvokable(); + + $this->assertInstanceOf('Pimple\Tests\Fixtures\NonInvokable', $pimple['non_invokable']); + } + + /** + * @dataProvider badServiceDefinitionProvider + */ + public function testFactoryFailsForInvalidServiceDefinitions($service) + { + $this->expectException(\Pimple\Exception\ExpectedInvokableException::class); + $this->expectExceptionMessage('Service definition is not a Closure or invokable object.'); + + $pimple = new Container(); + $pimple->factory($service); + } + + /** + * @group legacy + * @dataProvider badServiceDefinitionProvider + */ + public function testLegacyFactoryFailsForInvalidServiceDefinitions($service) + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Service definition is not a Closure or invokable object.'); + + $pimple = new Container(); + $pimple->factory($service); + } + + /** + * @dataProvider badServiceDefinitionProvider + */ + public function testProtectFailsForInvalidServiceDefinitions($service) + { + $this->expectException(\Pimple\Exception\ExpectedInvokableException::class); + $this->expectExceptionMessage('Callable is not a Closure or invokable object.'); + + $pimple = new Container(); + $pimple->protect($service); + } + + /** + * @group legacy + * @dataProvider badServiceDefinitionProvider + */ + public function testLegacyProtectFailsForInvalidServiceDefinitions($service) + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Callable is not a Closure or invokable object.'); + + $pimple = new Container(); + $pimple->protect($service); + } + + /** + * @dataProvider badServiceDefinitionProvider + */ + public function testExtendFailsForKeysNotContainingServiceDefinitions($service) + { + $this->expectException(\Pimple\Exception\InvalidServiceIdentifierException::class); + $this->expectExceptionMessage('Identifier "foo" does not contain an object definition.'); + + $pimple = new Container(); + $pimple['foo'] = $service; + $pimple->extend('foo', function () { + }); + } + + /** + * @group legacy + * @dataProvider badServiceDefinitionProvider + */ + public function testLegacyExtendFailsForKeysNotContainingServiceDefinitions($service) + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Identifier "foo" does not contain an object definition.'); + + $pimple = new Container(); + $pimple['foo'] = $service; + $pimple->extend('foo', function () { + }); + } + + /** + * @group legacy + * @expectedDeprecation How Pimple behaves when extending protected closures will be fixed in Pimple 4. Are you sure "foo" should be protected? + */ + public function testExtendingProtectedClosureDeprecation() + { + $pimple = new Container(); + $pimple['foo'] = $pimple->protect(function () { + return 'bar'; + }); + + $pimple->extend('foo', function ($value) { + return $value.'-baz'; + }); + + $this->assertSame('bar-baz', $pimple['foo']); + } + + /** + * @dataProvider badServiceDefinitionProvider + */ + public function testExtendFailsForInvalidServiceDefinitions($service) + { + $this->expectException(\Pimple\Exception\ExpectedInvokableException::class); + $this->expectExceptionMessage('Extension service definition is not a Closure or invokable object.'); + + $pimple = new Container(); + $pimple['foo'] = function () { + }; + $pimple->extend('foo', $service); + } + + /** + * @group legacy + * @dataProvider badServiceDefinitionProvider + */ + public function testLegacyExtendFailsForInvalidServiceDefinitions($service) + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Extension service definition is not a Closure or invokable object.'); + + $pimple = new Container(); + $pimple['foo'] = function () { + }; + $pimple->extend('foo', $service); + } + + public function testExtendFailsIfFrozenServiceIsNonInvokable() + { + $this->expectException(\Pimple\Exception\FrozenServiceException::class); + $this->expectExceptionMessage('Cannot override frozen service "foo".'); + + $pimple = new Container(); + $pimple['foo'] = function () { + return new Fixtures\NonInvokable(); + }; + $foo = $pimple['foo']; + + $pimple->extend('foo', function () { + }); + } + + public function testExtendFailsIfFrozenServiceIsInvokable() + { + $this->expectException(\Pimple\Exception\FrozenServiceException::class); + $this->expectExceptionMessage('Cannot override frozen service "foo".'); + + $pimple = new Container(); + $pimple['foo'] = function () { + return new Fixtures\Invokable(); + }; + $foo = $pimple['foo']; + + $pimple->extend('foo', function () { + }); + } + + /** + * Provider for invalid service definitions. + */ + public function badServiceDefinitionProvider() + { + return [ + [123], + [new Fixtures\NonInvokable()], + ]; + } + + /** + * Provider for service definitions. + */ + public function serviceDefinitionProvider() + { + return [ + [function ($value) { + $service = new Fixtures\Service(); + $service->value = $value; + + return $service; + }], + [new Fixtures\Invokable()], + ]; + } + + public function testDefiningNewServiceAfterFreeze() + { + $pimple = new Container(); + $pimple['foo'] = function () { + return 'foo'; + }; + $foo = $pimple['foo']; + + $pimple['bar'] = function () { + return 'bar'; + }; + $this->assertSame('bar', $pimple['bar']); + } + + public function testOverridingServiceAfterFreeze() + { + $this->expectException(\Pimple\Exception\FrozenServiceException::class); + $this->expectExceptionMessage('Cannot override frozen service "foo".'); + + $pimple = new Container(); + $pimple['foo'] = function () { + return 'foo'; + }; + $foo = $pimple['foo']; + + $pimple['foo'] = function () { + return 'bar'; + }; + } + + /** + * @group legacy + */ + public function testLegacyOverridingServiceAfterFreeze() + { + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('Cannot override frozen service "foo".'); + + $pimple = new Container(); + $pimple['foo'] = function () { + return 'foo'; + }; + $foo = $pimple['foo']; + + $pimple['foo'] = function () { + return 'bar'; + }; + } + + public function testRemovingServiceAfterFreeze() + { + $pimple = new Container(); + $pimple['foo'] = function () { + return 'foo'; + }; + $foo = $pimple['foo']; + + unset($pimple['foo']); + $pimple['foo'] = function () { + return 'bar'; + }; + $this->assertSame('bar', $pimple['foo']); + } + + public function testExtendingService() + { + $pimple = new Container(); + $pimple['foo'] = function () { + return 'foo'; + }; + $pimple['foo'] = $pimple->extend('foo', function ($foo, $app) { + return "$foo.bar"; + }); + $pimple['foo'] = $pimple->extend('foo', function ($foo, $app) { + return "$foo.baz"; + }); + $this->assertSame('foo.bar.baz', $pimple['foo']); + } + + public function testExtendingServiceAfterOtherServiceFreeze() + { + $pimple = new Container(); + $pimple['foo'] = function () { + return 'foo'; + }; + $pimple['bar'] = function () { + return 'bar'; + }; + $foo = $pimple['foo']; + + $pimple['bar'] = $pimple->extend('bar', function ($bar, $app) { + return "$bar.baz"; + }); + $this->assertSame('bar.baz', $pimple['bar']); + } +} diff --git a/vendor/pimple/pimple/src/Pimple/Tests/Psr11/ContainerTest.php b/vendor/pimple/pimple/src/Pimple/Tests/Psr11/ContainerTest.php new file mode 100644 index 0000000..d47b9c3 --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/Tests/Psr11/ContainerTest.php @@ -0,0 +1,76 @@ +assertSame($pimple['service'], $psr->get('service')); + } + + public function testGetThrowsExceptionIfServiceIsNotFound() + { + $this->expectException(\Psr\Container\NotFoundExceptionInterface::class); + $this->expectExceptionMessage('Identifier "service" is not defined.'); + + $pimple = new Container(); + $psr = new PsrContainer($pimple); + + $psr->get('service'); + } + + public function testHasReturnsTrueIfServiceExists() + { + $pimple = new Container(); + $pimple['service'] = function () { + return new Service(); + }; + $psr = new PsrContainer($pimple); + + $this->assertTrue($psr->has('service')); + } + + public function testHasReturnsFalseIfServiceDoesNotExist() + { + $pimple = new Container(); + $psr = new PsrContainer($pimple); + + $this->assertFalse($psr->has('service')); + } +} diff --git a/vendor/pimple/pimple/src/Pimple/Tests/Psr11/ServiceLocatorTest.php b/vendor/pimple/pimple/src/Pimple/Tests/Psr11/ServiceLocatorTest.php new file mode 100644 index 0000000..bd2d335 --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/Tests/Psr11/ServiceLocatorTest.php @@ -0,0 +1,131 @@ + + */ +class ServiceLocatorTest extends TestCase +{ + public function testCanAccessServices() + { + $pimple = new Container(); + $pimple['service'] = function () { + return new Fixtures\Service(); + }; + $locator = new ServiceLocator($pimple, ['service']); + + $this->assertSame($pimple['service'], $locator->get('service')); + } + + public function testCanAccessAliasedServices() + { + $pimple = new Container(); + $pimple['service'] = function () { + return new Fixtures\Service(); + }; + $locator = new ServiceLocator($pimple, ['alias' => 'service']); + + $this->assertSame($pimple['service'], $locator->get('alias')); + } + + public function testCannotAccessAliasedServiceUsingRealIdentifier() + { + $this->expectException(\Pimple\Exception\UnknownIdentifierException::class); + $this->expectExceptionMessage('Identifier "service" is not defined.'); + + $pimple = new Container(); + $pimple['service'] = function () { + return new Fixtures\Service(); + }; + $locator = new ServiceLocator($pimple, ['alias' => 'service']); + + $service = $locator->get('service'); + } + + public function testGetValidatesServiceCanBeLocated() + { + $this->expectException(\Pimple\Exception\UnknownIdentifierException::class); + $this->expectExceptionMessage('Identifier "foo" is not defined.'); + + $pimple = new Container(); + $pimple['service'] = function () { + return new Fixtures\Service(); + }; + $locator = new ServiceLocator($pimple, ['alias' => 'service']); + + $service = $locator->get('foo'); + } + + public function testGetValidatesTargetServiceExists() + { + $this->expectException(\Pimple\Exception\UnknownIdentifierException::class); + $this->expectExceptionMessage('Identifier "invalid" is not defined.'); + + $pimple = new Container(); + $pimple['service'] = function () { + return new Fixtures\Service(); + }; + $locator = new ServiceLocator($pimple, ['alias' => 'invalid']); + + $service = $locator->get('alias'); + } + + public function testHasValidatesServiceCanBeLocated() + { + $pimple = new Container(); + $pimple['service1'] = function () { + return new Fixtures\Service(); + }; + $pimple['service2'] = function () { + return new Fixtures\Service(); + }; + $locator = new ServiceLocator($pimple, ['service1']); + + $this->assertTrue($locator->has('service1')); + $this->assertFalse($locator->has('service2')); + } + + public function testHasChecksIfTargetServiceExists() + { + $pimple = new Container(); + $pimple['service'] = function () { + return new Fixtures\Service(); + }; + $locator = new ServiceLocator($pimple, ['foo' => 'service', 'bar' => 'invalid']); + + $this->assertTrue($locator->has('foo')); + $this->assertFalse($locator->has('bar')); + } +} diff --git a/vendor/pimple/pimple/src/Pimple/Tests/ServiceIteratorTest.php b/vendor/pimple/pimple/src/Pimple/Tests/ServiceIteratorTest.php new file mode 100644 index 0000000..2bb935f --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/Tests/ServiceIteratorTest.php @@ -0,0 +1,52 @@ +assertSame(['service1' => $pimple['service1'], 'service2' => $pimple['service2']], iterator_to_array($iterator)); + } +} diff --git a/vendor/psr/cache/CHANGELOG.md b/vendor/psr/cache/CHANGELOG.md new file mode 100644 index 0000000..58ddab0 --- /dev/null +++ b/vendor/psr/cache/CHANGELOG.md @@ -0,0 +1,16 @@ +# Changelog + +All notable changes to this project will be documented in this file, in reverse chronological order by release. + +## 1.0.1 - 2016-08-06 + +### Fixed + +- Make spacing consistent in phpdoc annotations php-fig/cache#9 - chalasr +- Fix grammar in phpdoc annotations php-fig/cache#10 - chalasr +- Be more specific in docblocks that `getItems()` and `deleteItems()` take an array of strings (`string[]`) compared to just `array` php-fig/cache#8 - GrahamCampbell +- For `expiresAt()` and `expiresAfter()` in CacheItemInterface fix docblock to specify null as a valid parameters as well as an implementation of DateTimeInterface php-fig/cache#7 - GrahamCampbell + +## 1.0.0 - 2015-12-11 + +Initial stable release; reflects accepted PSR-6 specification diff --git a/vendor/psr/cache/LICENSE.txt b/vendor/psr/cache/LICENSE.txt new file mode 100644 index 0000000..b1c2c97 --- /dev/null +++ b/vendor/psr/cache/LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (c) 2015 PHP Framework Interoperability Group + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/psr/cache/README.md b/vendor/psr/cache/README.md new file mode 100644 index 0000000..c8706ce --- /dev/null +++ b/vendor/psr/cache/README.md @@ -0,0 +1,9 @@ +PSR Cache +========= + +This repository holds all interfaces defined by +[PSR-6](http://www.php-fig.org/psr/psr-6/). + +Note that this is not a Cache implementation of its own. It is merely an +interface that describes a Cache implementation. See the specification for more +details. diff --git a/vendor/psr/cache/composer.json b/vendor/psr/cache/composer.json new file mode 100644 index 0000000..e828fec --- /dev/null +++ b/vendor/psr/cache/composer.json @@ -0,0 +1,25 @@ +{ + "name": "psr/cache", + "description": "Common interface for caching libraries", + "keywords": ["psr", "psr-6", "cache"], + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "require": { + "php": ">=5.3.0" + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/vendor/psr/cache/src/CacheException.php b/vendor/psr/cache/src/CacheException.php new file mode 100644 index 0000000..e27f22f --- /dev/null +++ b/vendor/psr/cache/src/CacheException.php @@ -0,0 +1,10 @@ +=7.4.0" + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + } +} diff --git a/vendor/psr/container/src/ContainerExceptionInterface.php b/vendor/psr/container/src/ContainerExceptionInterface.php new file mode 100644 index 0000000..0f213f2 --- /dev/null +++ b/vendor/psr/container/src/ContainerExceptionInterface.php @@ -0,0 +1,12 @@ +=7.2.0" + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/vendor/psr/event-dispatcher/src/EventDispatcherInterface.php b/vendor/psr/event-dispatcher/src/EventDispatcherInterface.php new file mode 100644 index 0000000..4306fa9 --- /dev/null +++ b/vendor/psr/event-dispatcher/src/EventDispatcherInterface.php @@ -0,0 +1,21 @@ +=7.0.0", + "psr/http-message": "^1.0" + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/vendor/psr/http-factory/src/RequestFactoryInterface.php b/vendor/psr/http-factory/src/RequestFactoryInterface.php new file mode 100644 index 0000000..cb39a08 --- /dev/null +++ b/vendor/psr/http-factory/src/RequestFactoryInterface.php @@ -0,0 +1,18 @@ +=5.3.0" + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/vendor/psr/http-message/src/MessageInterface.php b/vendor/psr/http-message/src/MessageInterface.php new file mode 100644 index 0000000..dd46e5e --- /dev/null +++ b/vendor/psr/http-message/src/MessageInterface.php @@ -0,0 +1,187 @@ +getHeaders() as $name => $values) { + * echo $name . ": " . implode(", ", $values); + * } + * + * // Emit headers iteratively: + * foreach ($message->getHeaders() as $name => $values) { + * foreach ($values as $value) { + * header(sprintf('%s: %s', $name, $value), false); + * } + * } + * + * While header names are not case-sensitive, getHeaders() will preserve the + * exact case in which headers were originally specified. + * + * @return string[][] Returns an associative array of the message's headers. Each + * key MUST be a header name, and each value MUST be an array of strings + * for that header. + */ + public function getHeaders(); + + /** + * Checks if a header exists by the given case-insensitive name. + * + * @param string $name Case-insensitive header field name. + * @return bool Returns true if any header names match the given header + * name using a case-insensitive string comparison. Returns false if + * no matching header name is found in the message. + */ + public function hasHeader($name); + + /** + * Retrieves a message header value by the given case-insensitive name. + * + * This method returns an array of all the header values of the given + * case-insensitive header name. + * + * If the header does not appear in the message, this method MUST return an + * empty array. + * + * @param string $name Case-insensitive header field name. + * @return string[] An array of string values as provided for the given + * header. If the header does not appear in the message, this method MUST + * return an empty array. + */ + public function getHeader($name); + + /** + * Retrieves a comma-separated string of the values for a single header. + * + * This method returns all of the header values of the given + * case-insensitive header name as a string concatenated together using + * a comma. + * + * NOTE: Not all header values may be appropriately represented using + * comma concatenation. For such headers, use getHeader() instead + * and supply your own delimiter when concatenating. + * + * If the header does not appear in the message, this method MUST return + * an empty string. + * + * @param string $name Case-insensitive header field name. + * @return string A string of values as provided for the given header + * concatenated together using a comma. If the header does not appear in + * the message, this method MUST return an empty string. + */ + public function getHeaderLine($name); + + /** + * Return an instance with the provided value replacing the specified header. + * + * While header names are case-insensitive, the casing of the header will + * be preserved by this function, and returned from getHeaders(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new and/or updated header and value. + * + * @param string $name Case-insensitive header field name. + * @param string|string[] $value Header value(s). + * @return static + * @throws \InvalidArgumentException for invalid header names or values. + */ + public function withHeader($name, $value); + + /** + * Return an instance with the specified header appended with the given value. + * + * Existing values for the specified header will be maintained. The new + * value(s) will be appended to the existing list. If the header did not + * exist previously, it will be added. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new header and/or value. + * + * @param string $name Case-insensitive header field name to add. + * @param string|string[] $value Header value(s). + * @return static + * @throws \InvalidArgumentException for invalid header names or values. + */ + public function withAddedHeader($name, $value); + + /** + * Return an instance without the specified header. + * + * Header resolution MUST be done without case-sensitivity. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that removes + * the named header. + * + * @param string $name Case-insensitive header field name to remove. + * @return static + */ + public function withoutHeader($name); + + /** + * Gets the body of the message. + * + * @return StreamInterface Returns the body as a stream. + */ + public function getBody(); + + /** + * Return an instance with the specified message body. + * + * The body MUST be a StreamInterface object. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return a new instance that has the + * new body stream. + * + * @param StreamInterface $body Body. + * @return static + * @throws \InvalidArgumentException When the body is not valid. + */ + public function withBody(StreamInterface $body); +} diff --git a/vendor/psr/http-message/src/RequestInterface.php b/vendor/psr/http-message/src/RequestInterface.php new file mode 100644 index 0000000..a96d4fd --- /dev/null +++ b/vendor/psr/http-message/src/RequestInterface.php @@ -0,0 +1,129 @@ +getQuery()` + * or from the `QUERY_STRING` server param. + * + * @return array + */ + public function getQueryParams(); + + /** + * Return an instance with the specified query string arguments. + * + * These values SHOULD remain immutable over the course of the incoming + * request. They MAY be injected during instantiation, such as from PHP's + * $_GET superglobal, or MAY be derived from some other value such as the + * URI. In cases where the arguments are parsed from the URI, the data + * MUST be compatible with what PHP's parse_str() would return for + * purposes of how duplicate query parameters are handled, and how nested + * sets are handled. + * + * Setting query string arguments MUST NOT change the URI stored by the + * request, nor the values in the server params. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated query string arguments. + * + * @param array $query Array of query string arguments, typically from + * $_GET. + * @return static + */ + public function withQueryParams(array $query); + + /** + * Retrieve normalized file upload data. + * + * This method returns upload metadata in a normalized tree, with each leaf + * an instance of Psr\Http\Message\UploadedFileInterface. + * + * These values MAY be prepared from $_FILES or the message body during + * instantiation, or MAY be injected via withUploadedFiles(). + * + * @return array An array tree of UploadedFileInterface instances; an empty + * array MUST be returned if no data is present. + */ + public function getUploadedFiles(); + + /** + * Create a new instance with the specified uploaded files. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated body parameters. + * + * @param array $uploadedFiles An array tree of UploadedFileInterface instances. + * @return static + * @throws \InvalidArgumentException if an invalid structure is provided. + */ + public function withUploadedFiles(array $uploadedFiles); + + /** + * Retrieve any parameters provided in the request body. + * + * If the request Content-Type is either application/x-www-form-urlencoded + * or multipart/form-data, and the request method is POST, this method MUST + * return the contents of $_POST. + * + * Otherwise, this method may return any results of deserializing + * the request body content; as parsing returns structured content, the + * potential types MUST be arrays or objects only. A null value indicates + * the absence of body content. + * + * @return null|array|object The deserialized body parameters, if any. + * These will typically be an array or object. + */ + public function getParsedBody(); + + /** + * Return an instance with the specified body parameters. + * + * These MAY be injected during instantiation. + * + * If the request Content-Type is either application/x-www-form-urlencoded + * or multipart/form-data, and the request method is POST, use this method + * ONLY to inject the contents of $_POST. + * + * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of + * deserializing the request body content. Deserialization/parsing returns + * structured data, and, as such, this method ONLY accepts arrays or objects, + * or a null value if nothing was available to parse. + * + * As an example, if content negotiation determines that the request data + * is a JSON payload, this method could be used to create a request + * instance with the deserialized parameters. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated body parameters. + * + * @param null|array|object $data The deserialized body data. This will + * typically be in an array or object. + * @return static + * @throws \InvalidArgumentException if an unsupported argument type is + * provided. + */ + public function withParsedBody($data); + + /** + * Retrieve attributes derived from the request. + * + * The request "attributes" may be used to allow injection of any + * parameters derived from the request: e.g., the results of path + * match operations; the results of decrypting cookies; the results of + * deserializing non-form-encoded message bodies; etc. Attributes + * will be application and request specific, and CAN be mutable. + * + * @return array Attributes derived from the request. + */ + public function getAttributes(); + + /** + * Retrieve a single derived request attribute. + * + * Retrieves a single derived request attribute as described in + * getAttributes(). If the attribute has not been previously set, returns + * the default value as provided. + * + * This method obviates the need for a hasAttribute() method, as it allows + * specifying a default value to return if the attribute is not found. + * + * @see getAttributes() + * @param string $name The attribute name. + * @param mixed $default Default value to return if the attribute does not exist. + * @return mixed + */ + public function getAttribute($name, $default = null); + + /** + * Return an instance with the specified derived request attribute. + * + * This method allows setting a single derived request attribute as + * described in getAttributes(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated attribute. + * + * @see getAttributes() + * @param string $name The attribute name. + * @param mixed $value The value of the attribute. + * @return static + */ + public function withAttribute($name, $value); + + /** + * Return an instance that removes the specified derived request attribute. + * + * This method allows removing a single derived request attribute as + * described in getAttributes(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that removes + * the attribute. + * + * @see getAttributes() + * @param string $name The attribute name. + * @return static + */ + public function withoutAttribute($name); +} diff --git a/vendor/psr/http-message/src/StreamInterface.php b/vendor/psr/http-message/src/StreamInterface.php new file mode 100644 index 0000000..f68f391 --- /dev/null +++ b/vendor/psr/http-message/src/StreamInterface.php @@ -0,0 +1,158 @@ + + * [user-info@]host[:port] + * + * + * If the port component is not set or is the standard port for the current + * scheme, it SHOULD NOT be included. + * + * @see https://tools.ietf.org/html/rfc3986#section-3.2 + * @return string The URI authority, in "[user-info@]host[:port]" format. + */ + public function getAuthority(); + + /** + * Retrieve the user information component of the URI. + * + * If no user information is present, this method MUST return an empty + * string. + * + * If a user is present in the URI, this will return that value; + * additionally, if the password is also present, it will be appended to the + * user value, with a colon (":") separating the values. + * + * The trailing "@" character is not part of the user information and MUST + * NOT be added. + * + * @return string The URI user information, in "username[:password]" format. + */ + public function getUserInfo(); + + /** + * Retrieve the host component of the URI. + * + * If no host is present, this method MUST return an empty string. + * + * The value returned MUST be normalized to lowercase, per RFC 3986 + * Section 3.2.2. + * + * @see http://tools.ietf.org/html/rfc3986#section-3.2.2 + * @return string The URI host. + */ + public function getHost(); + + /** + * Retrieve the port component of the URI. + * + * If a port is present, and it is non-standard for the current scheme, + * this method MUST return it as an integer. If the port is the standard port + * used with the current scheme, this method SHOULD return null. + * + * If no port is present, and no scheme is present, this method MUST return + * a null value. + * + * If no port is present, but a scheme is present, this method MAY return + * the standard port for that scheme, but SHOULD return null. + * + * @return null|int The URI port. + */ + public function getPort(); + + /** + * Retrieve the path component of the URI. + * + * The path can either be empty or absolute (starting with a slash) or + * rootless (not starting with a slash). Implementations MUST support all + * three syntaxes. + * + * Normally, the empty path "" and absolute path "/" are considered equal as + * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically + * do this normalization because in contexts with a trimmed base path, e.g. + * the front controller, this difference becomes significant. It's the task + * of the user to handle both "" and "/". + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.3. + * + * As an example, if the value should include a slash ("/") not intended as + * delimiter between path segments, that value MUST be passed in encoded + * form (e.g., "%2F") to the instance. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.3 + * @return string The URI path. + */ + public function getPath(); + + /** + * Retrieve the query string of the URI. + * + * If no query string is present, this method MUST return an empty string. + * + * The leading "?" character is not part of the query and MUST NOT be + * added. + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.4. + * + * As an example, if a value in a key/value pair of the query string should + * include an ampersand ("&") not intended as a delimiter between values, + * that value MUST be passed in encoded form (e.g., "%26") to the instance. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.4 + * @return string The URI query string. + */ + public function getQuery(); + + /** + * Retrieve the fragment component of the URI. + * + * If no fragment is present, this method MUST return an empty string. + * + * The leading "#" character is not part of the fragment and MUST NOT be + * added. + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.5. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.5 + * @return string The URI fragment. + */ + public function getFragment(); + + /** + * Return an instance with the specified scheme. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified scheme. + * + * Implementations MUST support the schemes "http" and "https" case + * insensitively, and MAY accommodate other schemes if required. + * + * An empty scheme is equivalent to removing the scheme. + * + * @param string $scheme The scheme to use with the new instance. + * @return static A new instance with the specified scheme. + * @throws \InvalidArgumentException for invalid or unsupported schemes. + */ + public function withScheme($scheme); + + /** + * Return an instance with the specified user information. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified user information. + * + * Password is optional, but the user information MUST include the + * user; an empty string for the user is equivalent to removing user + * information. + * + * @param string $user The user name to use for authority. + * @param null|string $password The password associated with $user. + * @return static A new instance with the specified user information. + */ + public function withUserInfo($user, $password = null); + + /** + * Return an instance with the specified host. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified host. + * + * An empty host value is equivalent to removing the host. + * + * @param string $host The hostname to use with the new instance. + * @return static A new instance with the specified host. + * @throws \InvalidArgumentException for invalid hostnames. + */ + public function withHost($host); + + /** + * Return an instance with the specified port. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified port. + * + * Implementations MUST raise an exception for ports outside the + * established TCP and UDP port ranges. + * + * A null value provided for the port is equivalent to removing the port + * information. + * + * @param null|int $port The port to use with the new instance; a null value + * removes the port information. + * @return static A new instance with the specified port. + * @throws \InvalidArgumentException for invalid ports. + */ + public function withPort($port); + + /** + * Return an instance with the specified path. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified path. + * + * The path can either be empty or absolute (starting with a slash) or + * rootless (not starting with a slash). Implementations MUST support all + * three syntaxes. + * + * If the path is intended to be domain-relative rather than path relative then + * it must begin with a slash ("/"). Paths not starting with a slash ("/") + * are assumed to be relative to some base path known to the application or + * consumer. + * + * Users can provide both encoded and decoded path characters. + * Implementations ensure the correct encoding as outlined in getPath(). + * + * @param string $path The path to use with the new instance. + * @return static A new instance with the specified path. + * @throws \InvalidArgumentException for invalid paths. + */ + public function withPath($path); + + /** + * Return an instance with the specified query string. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified query string. + * + * Users can provide both encoded and decoded query characters. + * Implementations ensure the correct encoding as outlined in getQuery(). + * + * An empty query string value is equivalent to removing the query string. + * + * @param string $query The query string to use with the new instance. + * @return static A new instance with the specified query string. + * @throws \InvalidArgumentException for invalid query strings. + */ + public function withQuery($query); + + /** + * Return an instance with the specified URI fragment. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified URI fragment. + * + * Users can provide both encoded and decoded fragment characters. + * Implementations ensure the correct encoding as outlined in getFragment(). + * + * An empty fragment value is equivalent to removing the fragment. + * + * @param string $fragment The fragment to use with the new instance. + * @return static A new instance with the specified fragment. + */ + public function withFragment($fragment); + + /** + * Return the string representation as a URI reference. + * + * Depending on which components of the URI are present, the resulting + * string is either a full URI or relative reference according to RFC 3986, + * Section 4.1. The method concatenates the various components of the URI, + * using the appropriate delimiters: + * + * - If a scheme is present, it MUST be suffixed by ":". + * - If an authority is present, it MUST be prefixed by "//". + * - The path can be concatenated without delimiters. But there are two + * cases where the path has to be adjusted to make the URI reference + * valid as PHP does not allow to throw an exception in __toString(): + * - If the path is rootless and an authority is present, the path MUST + * be prefixed by "/". + * - If the path is starting with more than one "/" and no authority is + * present, the starting slashes MUST be reduced to one. + * - If a query is present, it MUST be prefixed by "?". + * - If a fragment is present, it MUST be prefixed by "#". + * + * @see http://tools.ietf.org/html/rfc3986#section-4.1 + * @return string + */ + public function __toString(); +} diff --git a/vendor/psr/log/LICENSE b/vendor/psr/log/LICENSE new file mode 100644 index 0000000..474c952 --- /dev/null +++ b/vendor/psr/log/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2012 PHP Framework Interoperability Group + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/psr/log/Psr/Log/AbstractLogger.php b/vendor/psr/log/Psr/Log/AbstractLogger.php new file mode 100644 index 0000000..e02f9da --- /dev/null +++ b/vendor/psr/log/Psr/Log/AbstractLogger.php @@ -0,0 +1,128 @@ +log(LogLevel::EMERGENCY, $message, $context); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function alert($message, array $context = array()) + { + $this->log(LogLevel::ALERT, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function critical($message, array $context = array()) + { + $this->log(LogLevel::CRITICAL, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function error($message, array $context = array()) + { + $this->log(LogLevel::ERROR, $message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function warning($message, array $context = array()) + { + $this->log(LogLevel::WARNING, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function notice($message, array $context = array()) + { + $this->log(LogLevel::NOTICE, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function info($message, array $context = array()) + { + $this->log(LogLevel::INFO, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function debug($message, array $context = array()) + { + $this->log(LogLevel::DEBUG, $message, $context); + } +} diff --git a/vendor/psr/log/Psr/Log/InvalidArgumentException.php b/vendor/psr/log/Psr/Log/InvalidArgumentException.php new file mode 100644 index 0000000..67f852d --- /dev/null +++ b/vendor/psr/log/Psr/Log/InvalidArgumentException.php @@ -0,0 +1,7 @@ +logger = $logger; + } +} diff --git a/vendor/psr/log/Psr/Log/LoggerInterface.php b/vendor/psr/log/Psr/Log/LoggerInterface.php new file mode 100644 index 0000000..2206cfd --- /dev/null +++ b/vendor/psr/log/Psr/Log/LoggerInterface.php @@ -0,0 +1,125 @@ +log(LogLevel::EMERGENCY, $message, $context); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function alert($message, array $context = array()) + { + $this->log(LogLevel::ALERT, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function critical($message, array $context = array()) + { + $this->log(LogLevel::CRITICAL, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function error($message, array $context = array()) + { + $this->log(LogLevel::ERROR, $message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function warning($message, array $context = array()) + { + $this->log(LogLevel::WARNING, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function notice($message, array $context = array()) + { + $this->log(LogLevel::NOTICE, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function info($message, array $context = array()) + { + $this->log(LogLevel::INFO, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function debug($message, array $context = array()) + { + $this->log(LogLevel::DEBUG, $message, $context); + } + + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * + * @return void + * + * @throws \Psr\Log\InvalidArgumentException + */ + abstract public function log($level, $message, array $context = array()); +} diff --git a/vendor/psr/log/Psr/Log/NullLogger.php b/vendor/psr/log/Psr/Log/NullLogger.php new file mode 100644 index 0000000..c8f7293 --- /dev/null +++ b/vendor/psr/log/Psr/Log/NullLogger.php @@ -0,0 +1,30 @@ +logger) { }` + * blocks. + */ +class NullLogger extends AbstractLogger +{ + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * + * @return void + * + * @throws \Psr\Log\InvalidArgumentException + */ + public function log($level, $message, array $context = array()) + { + // noop + } +} diff --git a/vendor/psr/log/Psr/Log/Test/DummyTest.php b/vendor/psr/log/Psr/Log/Test/DummyTest.php new file mode 100644 index 0000000..9638c11 --- /dev/null +++ b/vendor/psr/log/Psr/Log/Test/DummyTest.php @@ -0,0 +1,18 @@ + ". + * + * Example ->error('Foo') would yield "error Foo". + * + * @return string[] + */ + abstract public function getLogs(); + + public function testImplements() + { + $this->assertInstanceOf('Psr\Log\LoggerInterface', $this->getLogger()); + } + + /** + * @dataProvider provideLevelsAndMessages + */ + public function testLogsAtAllLevels($level, $message) + { + $logger = $this->getLogger(); + $logger->{$level}($message, array('user' => 'Bob')); + $logger->log($level, $message, array('user' => 'Bob')); + + $expected = array( + $level.' message of level '.$level.' with context: Bob', + $level.' message of level '.$level.' with context: Bob', + ); + $this->assertEquals($expected, $this->getLogs()); + } + + public function provideLevelsAndMessages() + { + return array( + LogLevel::EMERGENCY => array(LogLevel::EMERGENCY, 'message of level emergency with context: {user}'), + LogLevel::ALERT => array(LogLevel::ALERT, 'message of level alert with context: {user}'), + LogLevel::CRITICAL => array(LogLevel::CRITICAL, 'message of level critical with context: {user}'), + LogLevel::ERROR => array(LogLevel::ERROR, 'message of level error with context: {user}'), + LogLevel::WARNING => array(LogLevel::WARNING, 'message of level warning with context: {user}'), + LogLevel::NOTICE => array(LogLevel::NOTICE, 'message of level notice with context: {user}'), + LogLevel::INFO => array(LogLevel::INFO, 'message of level info with context: {user}'), + LogLevel::DEBUG => array(LogLevel::DEBUG, 'message of level debug with context: {user}'), + ); + } + + /** + * @expectedException \Psr\Log\InvalidArgumentException + */ + public function testThrowsOnInvalidLevel() + { + $logger = $this->getLogger(); + $logger->log('invalid level', 'Foo'); + } + + public function testContextReplacement() + { + $logger = $this->getLogger(); + $logger->info('{Message {nothing} {user} {foo.bar} a}', array('user' => 'Bob', 'foo.bar' => 'Bar')); + + $expected = array('info {Message {nothing} Bob Bar a}'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testObjectCastToString() + { + if (method_exists($this, 'createPartialMock')) { + $dummy = $this->createPartialMock('Psr\Log\Test\DummyTest', array('__toString')); + } else { + $dummy = $this->getMock('Psr\Log\Test\DummyTest', array('__toString')); + } + $dummy->expects($this->once()) + ->method('__toString') + ->will($this->returnValue('DUMMY')); + + $this->getLogger()->warning($dummy); + + $expected = array('warning DUMMY'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testContextCanContainAnything() + { + $closed = fopen('php://memory', 'r'); + fclose($closed); + + $context = array( + 'bool' => true, + 'null' => null, + 'string' => 'Foo', + 'int' => 0, + 'float' => 0.5, + 'nested' => array('with object' => new DummyTest), + 'object' => new \DateTime, + 'resource' => fopen('php://memory', 'r'), + 'closed' => $closed, + ); + + $this->getLogger()->warning('Crazy context data', $context); + + $expected = array('warning Crazy context data'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testContextExceptionKeyCanBeExceptionOrOtherValues() + { + $logger = $this->getLogger(); + $logger->warning('Random message', array('exception' => 'oops')); + $logger->critical('Uncaught Exception!', array('exception' => new \LogicException('Fail'))); + + $expected = array( + 'warning Random message', + 'critical Uncaught Exception!' + ); + $this->assertEquals($expected, $this->getLogs()); + } +} diff --git a/vendor/psr/log/Psr/Log/Test/TestLogger.php b/vendor/psr/log/Psr/Log/Test/TestLogger.php new file mode 100644 index 0000000..1be3230 --- /dev/null +++ b/vendor/psr/log/Psr/Log/Test/TestLogger.php @@ -0,0 +1,147 @@ + $level, + 'message' => $message, + 'context' => $context, + ]; + + $this->recordsByLevel[$record['level']][] = $record; + $this->records[] = $record; + } + + public function hasRecords($level) + { + return isset($this->recordsByLevel[$level]); + } + + public function hasRecord($record, $level) + { + if (is_string($record)) { + $record = ['message' => $record]; + } + return $this->hasRecordThatPasses(function ($rec) use ($record) { + if ($rec['message'] !== $record['message']) { + return false; + } + if (isset($record['context']) && $rec['context'] !== $record['context']) { + return false; + } + return true; + }, $level); + } + + public function hasRecordThatContains($message, $level) + { + return $this->hasRecordThatPasses(function ($rec) use ($message) { + return strpos($rec['message'], $message) !== false; + }, $level); + } + + public function hasRecordThatMatches($regex, $level) + { + return $this->hasRecordThatPasses(function ($rec) use ($regex) { + return preg_match($regex, $rec['message']) > 0; + }, $level); + } + + public function hasRecordThatPasses(callable $predicate, $level) + { + if (!isset($this->recordsByLevel[$level])) { + return false; + } + foreach ($this->recordsByLevel[$level] as $i => $rec) { + if (call_user_func($predicate, $rec, $i)) { + return true; + } + } + return false; + } + + public function __call($method, $args) + { + if (preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) { + $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3]; + $level = strtolower($matches[2]); + if (method_exists($this, $genericMethod)) { + $args[] = $level; + return call_user_func_array([$this, $genericMethod], $args); + } + } + throw new \BadMethodCallException('Call to undefined method ' . get_class($this) . '::' . $method . '()'); + } + + public function reset() + { + $this->records = []; + $this->recordsByLevel = []; + } +} diff --git a/vendor/psr/log/README.md b/vendor/psr/log/README.md new file mode 100644 index 0000000..a9f20c4 --- /dev/null +++ b/vendor/psr/log/README.md @@ -0,0 +1,58 @@ +PSR Log +======= + +This repository holds all interfaces/classes/traits related to +[PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md). + +Note that this is not a logger of its own. It is merely an interface that +describes a logger. See the specification for more details. + +Installation +------------ + +```bash +composer require psr/log +``` + +Usage +----- + +If you need a logger, you can use the interface like this: + +```php +logger = $logger; + } + + public function doSomething() + { + if ($this->logger) { + $this->logger->info('Doing work'); + } + + try { + $this->doSomethingElse(); + } catch (Exception $exception) { + $this->logger->error('Oh no!', array('exception' => $exception)); + } + + // do something useful + } +} +``` + +You can then pick one of the implementations of the interface to get a logger. + +If you want to implement the interface, you can require this package and +implement `Psr\Log\LoggerInterface` in your code. Please read the +[specification text](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) +for details. diff --git a/vendor/psr/log/composer.json b/vendor/psr/log/composer.json new file mode 100644 index 0000000..ca05695 --- /dev/null +++ b/vendor/psr/log/composer.json @@ -0,0 +1,26 @@ +{ + "name": "psr/log", + "description": "Common interface for logging libraries", + "keywords": ["psr", "psr-3", "log"], + "homepage": "https://github.com/php-fig/log", + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "require": { + "php": ">=5.3.0" + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + } +} diff --git a/vendor/psr/simple-cache/.editorconfig b/vendor/psr/simple-cache/.editorconfig new file mode 100644 index 0000000..48542cb --- /dev/null +++ b/vendor/psr/simple-cache/.editorconfig @@ -0,0 +1,12 @@ +; This file is for unifying the coding style for different editors and IDEs. +; More information at http://editorconfig.org + +root = true + +[*] +charset = utf-8 +indent_size = 4 +indent_style = space +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/vendor/psr/simple-cache/LICENSE.md b/vendor/psr/simple-cache/LICENSE.md new file mode 100644 index 0000000..e49a7c8 --- /dev/null +++ b/vendor/psr/simple-cache/LICENSE.md @@ -0,0 +1,21 @@ +# The MIT License (MIT) + +Copyright (c) 2016 PHP Framework Interoperability Group + +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. diff --git a/vendor/psr/simple-cache/README.md b/vendor/psr/simple-cache/README.md new file mode 100644 index 0000000..43641d1 --- /dev/null +++ b/vendor/psr/simple-cache/README.md @@ -0,0 +1,8 @@ +PHP FIG Simple Cache PSR +======================== + +This repository holds all interfaces related to PSR-16. + +Note that this is not a cache implementation of its own. It is merely an interface that describes a cache implementation. See [the specification](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-16-simple-cache.md) for more details. + +You can find implementations of the specification by looking for packages providing the [psr/simple-cache-implementation](https://packagist.org/providers/psr/simple-cache-implementation) virtual package. diff --git a/vendor/psr/simple-cache/composer.json b/vendor/psr/simple-cache/composer.json new file mode 100644 index 0000000..2978fa5 --- /dev/null +++ b/vendor/psr/simple-cache/composer.json @@ -0,0 +1,25 @@ +{ + "name": "psr/simple-cache", + "description": "Common interfaces for simple caching", + "keywords": ["psr", "psr-16", "cache", "simple-cache", "caching"], + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "require": { + "php": ">=5.3.0" + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/vendor/psr/simple-cache/src/CacheException.php b/vendor/psr/simple-cache/src/CacheException.php new file mode 100644 index 0000000..eba5381 --- /dev/null +++ b/vendor/psr/simple-cache/src/CacheException.php @@ -0,0 +1,10 @@ + value pairs. Cache keys that do not exist or are stale will have $default as value. + * + * @throws \Psr\SimpleCache\InvalidArgumentException + * MUST be thrown if $keys is neither an array nor a Traversable, + * or if any of the $keys are not a legal value. + */ + public function getMultiple($keys, $default = null); + + /** + * Persists a set of key => value pairs in the cache, with an optional TTL. + * + * @param iterable $values A list of key => value pairs for a multiple-set operation. + * @param null|int|\DateInterval $ttl Optional. The TTL value of this item. If no value is sent and + * the driver supports TTL then the library may set a default value + * for it or let the driver take care of that. + * + * @return bool True on success and false on failure. + * + * @throws \Psr\SimpleCache\InvalidArgumentException + * MUST be thrown if $values is neither an array nor a Traversable, + * or if any of the $values are not a legal value. + */ + public function setMultiple($values, $ttl = null); + + /** + * Deletes multiple cache items in a single operation. + * + * @param iterable $keys A list of string-based keys to be deleted. + * + * @return bool True if the items were successfully removed. False if there was an error. + * + * @throws \Psr\SimpleCache\InvalidArgumentException + * MUST be thrown if $keys is neither an array nor a Traversable, + * or if any of the $keys are not a legal value. + */ + public function deleteMultiple($keys); + + /** + * Determines whether an item is present in the cache. + * + * NOTE: It is recommended that has() is only to be used for cache warming type purposes + * and not to be used within your live applications operations for get/set, as this method + * is subject to a race condition where your has() will return true and immediately after, + * another script can remove it making the state of your app out of date. + * + * @param string $key The cache item key. + * + * @return bool + * + * @throws \Psr\SimpleCache\InvalidArgumentException + * MUST be thrown if the $key string is not a legal value. + */ + public function has($key); +} diff --git a/vendor/psr/simple-cache/src/InvalidArgumentException.php b/vendor/psr/simple-cache/src/InvalidArgumentException.php new file mode 100644 index 0000000..6a9524a --- /dev/null +++ b/vendor/psr/simple-cache/src/InvalidArgumentException.php @@ -0,0 +1,13 @@ += 5.3. + +[![Build Status](https://travis-ci.org/ralouphie/getallheaders.svg?branch=master)](https://travis-ci.org/ralouphie/getallheaders) +[![Coverage Status](https://coveralls.io/repos/ralouphie/getallheaders/badge.png?branch=master)](https://coveralls.io/r/ralouphie/getallheaders?branch=master) +[![Latest Stable Version](https://poser.pugx.org/ralouphie/getallheaders/v/stable.png)](https://packagist.org/packages/ralouphie/getallheaders) +[![Latest Unstable Version](https://poser.pugx.org/ralouphie/getallheaders/v/unstable.png)](https://packagist.org/packages/ralouphie/getallheaders) +[![License](https://poser.pugx.org/ralouphie/getallheaders/license.png)](https://packagist.org/packages/ralouphie/getallheaders) + + +This is a simple polyfill for [`getallheaders()`](http://www.php.net/manual/en/function.getallheaders.php). + +## Install + +For PHP version **`>= 5.6`**: + +``` +composer require ralouphie/getallheaders +``` + +For PHP version **`< 5.6`**: + +``` +composer require ralouphie/getallheaders "^2" +``` diff --git a/vendor/ralouphie/getallheaders/composer.json b/vendor/ralouphie/getallheaders/composer.json new file mode 100644 index 0000000..de8ce62 --- /dev/null +++ b/vendor/ralouphie/getallheaders/composer.json @@ -0,0 +1,26 @@ +{ + "name": "ralouphie/getallheaders", + "description": "A polyfill for getallheaders.", + "license": "MIT", + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "require": { + "php": ">=5.6" + }, + "require-dev": { + "phpunit/phpunit": "^5 || ^6.5", + "php-coveralls/php-coveralls": "^2.1" + }, + "autoload": { + "files": ["src/getallheaders.php"] + }, + "autoload-dev": { + "psr-4": { + "getallheaders\\Tests\\": "tests/" + } + } +} diff --git a/vendor/ralouphie/getallheaders/src/getallheaders.php b/vendor/ralouphie/getallheaders/src/getallheaders.php new file mode 100644 index 0000000..c7285a5 --- /dev/null +++ b/vendor/ralouphie/getallheaders/src/getallheaders.php @@ -0,0 +1,46 @@ + 'Content-Type', + 'CONTENT_LENGTH' => 'Content-Length', + 'CONTENT_MD5' => 'Content-Md5', + ); + + foreach ($_SERVER as $key => $value) { + if (substr($key, 0, 5) === 'HTTP_') { + $key = substr($key, 5); + if (!isset($copy_server[$key]) || !isset($_SERVER[$key])) { + $key = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $key)))); + $headers[$key] = $value; + } + } elseif (isset($copy_server[$key])) { + $headers[$copy_server[$key]] = $value; + } + } + + if (!isset($headers['Authorization'])) { + if (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) { + $headers['Authorization'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION']; + } elseif (isset($_SERVER['PHP_AUTH_USER'])) { + $basic_pass = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : ''; + $headers['Authorization'] = 'Basic ' . base64_encode($_SERVER['PHP_AUTH_USER'] . ':' . $basic_pass); + } elseif (isset($_SERVER['PHP_AUTH_DIGEST'])) { + $headers['Authorization'] = $_SERVER['PHP_AUTH_DIGEST']; + } + } + + return $headers; + } + +} diff --git a/vendor/s1lentium/iptools/.codeclimate.yml b/vendor/s1lentium/iptools/.codeclimate.yml new file mode 100644 index 0000000..e34343a --- /dev/null +++ b/vendor/s1lentium/iptools/.codeclimate.yml @@ -0,0 +1,4 @@ +languages: + PHP: true +exclude_paths: +- "tests/*" diff --git a/vendor/s1lentium/iptools/.gitignore b/vendor/s1lentium/iptools/.gitignore new file mode 100644 index 0000000..4a40460 --- /dev/null +++ b/vendor/s1lentium/iptools/.gitignore @@ -0,0 +1,32 @@ +# phpstorm project files +.idea + +# netbeans project files +nbproject + +# zend studio for eclipse project files +.buildpath +.project +.settings + +# windows thumbnail cache +Thumbs.db + +# Mac DS_Store Files +.DS_Store + +# composer vendor dir +/vendor + +# phpunit itself is not needed +phpunit.phar + +# local phpunit config +phpunit.xml +phpunit.xml.dist + +# coverage report +/coverage + +composer.phar +composer.lock \ No newline at end of file diff --git a/vendor/s1lentium/iptools/.travis.yml b/vendor/s1lentium/iptools/.travis.yml new file mode 100644 index 0000000..287122b --- /dev/null +++ b/vendor/s1lentium/iptools/.travis.yml @@ -0,0 +1,17 @@ +language: php +php: + - 5.4 + - 5.5 + - 5.6 + - 7.0 + +before_script: + - wget http://getcomposer.org/composer.phar + - php composer.phar install --dev --no-interaction + +script: + - mkdir -p build/logs + - phpunit --coverage-clover build/logs/clover.xml + +after_script: + - php vendor/bin/coveralls diff --git a/vendor/s1lentium/iptools/LICENSE b/vendor/s1lentium/iptools/LICENSE new file mode 100644 index 0000000..4611d95 --- /dev/null +++ b/vendor/s1lentium/iptools/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2014 Safarov Alisher + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/s1lentium/iptools/README.md b/vendor/s1lentium/iptools/README.md new file mode 100644 index 0000000..147a192 --- /dev/null +++ b/vendor/s1lentium/iptools/README.md @@ -0,0 +1,206 @@ +# IPTools + +PHP Library for manipulating network addresses (IPv4 and IPv6). + +[![Build Status](https://travis-ci.org/S1lentium/IPTools.svg)](https://travis-ci.org/S1lentium/IPTools) +[![Coverage Status](https://coveralls.io/repos/S1lentium/IPTools/badge.svg?branch=master&service=github)](https://coveralls.io/github/S1lentium/IPTools?branch=master) +[![Code Climate](https://codeclimate.com/github/S1lentium/IPTools/badges/gpa.svg)](https://codeclimate.com/github/S1lentium/IPTools) + +[![PHP 5.4](https://img.shields.io/badge/PHP-5.4-8892BF.svg)](http://php.net) +[![PHP 7.0](https://img.shields.io/badge/PHP-7.0-8892BF.svg)](http://php.net) + +## Installation +Composer: +Run in command line: +``` +composer require s1lentium/iptools +``` +or put in composer.json: +```json +{ + "require": { + "s1lentium/iptools": "*" + } +} +``` + +## Usage + +### IP Operations +```php +$ip = new IP('192.168.1.1'); +echo $ip->version;// IPv4 +``` + +```php +$ip = new IP('fc00::'); +echo $ip->version; // IPv6 +``` + +**Parsing IP from integer, binary and hex:** +```php +echo (string)IP::parse(2130706433); // 127.0.0.1 +echo (string)IP::parse('0b11000000101010000000000100000001') // 192.168.1.1 +echo (string)IP::parse('0x0a000001'); // 10.0.0.1 +``` +or: +```php +echo (string)IP::parseLong(2130706433); // 127.0.0.1 +echo (string)IP::parseBin('11000000101010000000000100000001'); // 192.168.1.1 +echo (string)IP::parseHex('0a000001'); // 10.0.0.1 +``` + +**Converting IP to other formats:** +```php +echo IP::parse('192.168.1.1')->bin // 11000000101010000000000100000001 +echo IP::parse('10.0.0.1')->hex // 0a000001 +echo IP::parse('127.0.0.1')->long // 2130706433 +``` + +#### Other public properties: + +`maxPrefixLength` +The max number of bits in the address representation: 32 for IPv4, 128 for IPv6. + +`octetsCount` +The count of octets in IP address: 4 for IPv4, 16 for IPv6 + +`reversePointer` +The name of the reverse DNS PTR for the address: +```php +echo new IP::parse('192.0.2.5')->reversePointer // 5.2.0.192.in-addr.arpa +echo new IP::parse('2001:db8::567:89ab')->reversePointer // b.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa +``` + +### Network Operations +```php +echo Network::parse('192.0.0.1 255.0.0.0')->CIDR; // 192.0.0.0/8 +echo (string)Network::parse('192.0.0.1/8')->netmask; // 255.0.0.0 +echo (string)Network::parse('192.0.0.1'); // 192.0.0.1/32 +``` + +**Exclude IP from Network:** +```php +$excluded = Network::parse('192.0.0.0/8')->exclude(new IP('192.168.1.1')); +foreach($excluded as $network) { + echo (string)$network . '
        '; +} +``` + 192.0.0.0/9 + 192.128.0.0/11 + 192.160.0.0/13 + 192.168.0.0/24 + 192.168.1.0/32 + 192.168.1.2/31 + ... + 192.192.0.0/10 + +**Exclude Subnet from Network:** +```php +$excluded = Network::parse('192.0.0.0/8')->exclude(new Network('192.168.1.0/24')); +foreach($excluded as $network) { + echo (string)$network . '
        '; +} +``` + 192.0.0.0/9 + 192.128.0.0/11 + 192.160.0.0/13 + 192.168.0.0/24 + 192.168.2.0/23 + ... + 192.192.0.0/10 + +**Split network into equal subnets** +```php +$networks = Network::parse('192.168.0.0/22')->moveTo('24'); +foreach ($networks as $network) { + echo (string)$network . '
        '; +} +``` + 192.168.0.0/24 + 192.168.1.0/24 + 192.168.2.0/24 + 192.168.3.0/24 + +**Iterate over Network IP adresses:** +```php +$network = Network::parse('192.168.1.0/24'); +foreach($network as $ip) { + echo (string)$ip . '
        '; +} +``` + 192.168.1.0 + ... + 192.168.1.255 + +**Get Network hosts adresses as Range:** +```php +$hosts = Network::parse('192.168.1.0/24')->hosts // Range(192.168.1.1, 192.168.1.254); +foreach($hosts as $ip) { + echo (string)$ip . '
        '; +} +``` + 192.168.1.1 + ... + 192.168.1.254 + +**Count Network IP adresses** +```php +echo count(Network::parse('192.168.1.0/24')) // 254 +``` + +### Range Operations +**Define the range in different formats:** +```php +$range = new Range(new IP('192.168.1.0'), new IP('192.168.1.255')); +$range = Range::parse('192.168.1.0-192.168.1.255'); +$range = Range::parse('192.168.1.*'); +$range = Range::parse('192.168.1.0/24'); +``` +**Check if IP is within Range:** +```php +echo Range::parse('192.168.1.1-192.168.1.254')->contains(new IP('192.168.1.5')); // true +echo Range::parse('::1-::ffff')->contains(new IP('::1234')); // true +``` + +**Iterate over Range IP adresses:** +```php +$range = Range::parse('192.168.1.1-192.168.1.254'); +foreach($range as $ip) { + echo (string)$ip . '
        '; +} +``` + 192.168.1.1 + ... + 192.168.1.254 + +**Get Networks that fit into a specified range of IP Adresses:** +```php +$networks = Range::parse('192.168.1.1-192.168.1.254')->getNetworks(); + +foreach($networks as $network) { + echo (string)$network . '
        '; +} +``` + 192.168.1.1/32 + 192.168.1.2/31 + 192.168.1.4/30 + 192.168.1.8/29 + 192.168.1.16/28 + 192.168.1.32/27 + 192.168.1.64/26 + 192.168.1.128/26 + 192.168.1.192/27 + 192.168.1.224/28 + 192.168.1.240/29 + 192.168.1.248/30 + 192.168.1.252/31 + 192.168.1.254/32 + +**Count IP adresses in Range** +```php +echo count(Range::parse('192.168.1.1-192.168.1.254')) // 254 +``` + +# License +The library is released under the [MIT](https://opensource.org/licenses/MIT). diff --git a/vendor/s1lentium/iptools/composer.json b/vendor/s1lentium/iptools/composer.json new file mode 100644 index 0000000..bed5a05 --- /dev/null +++ b/vendor/s1lentium/iptools/composer.json @@ -0,0 +1,25 @@ +{ + "name": "s1lentium/iptools", + "type": "library", + "description": "PHP Library for manipulating network addresses (IPv4 and IPv6)", + "keywords": ["IP-Tools", "network", "subnet", "cidr", "IP", "IPv4", "IPv6"], + "license": "MIT", + "authors": [{ + "name": "Safarov Alisher", + "email": "alisher.safarov@outlook.com", + "homepage": "https://github.com/S1lentium" + }], + "require": { + "php": ">=5.4.0", + "ext-bcmath": "*" + }, + "require-dev": { + "satooshi/php-coveralls": "~1.0", + "phpunit/phpunit": "~4.0" + }, + "autoload": { + "psr-4": { + "IPTools\\": "src/" + } + } +} diff --git a/vendor/s1lentium/iptools/src/IP.php b/vendor/s1lentium/iptools/src/IP.php new file mode 100644 index 0000000..0358dee --- /dev/null +++ b/vendor/s1lentium/iptools/src/IP.php @@ -0,0 +1,286 @@ + + * @link https://github.com/S1lentium/IPTools + */ +class IP +{ + use PropertyTrait; + + const IP_V4 = 'IPv4'; + const IP_V6 = 'IPv6'; + + const IP_V4_MAX_PREFIX_LENGTH = 32; + const IP_V6_MAX_PREFIX_LENGTH = 128; + + const IP_V4_OCTETS = 4; + const IP_V6_OCTETS = 16; + + /** + * @var string + */ + private $in_addr; + + /** + * @param string ip + * @throws \Exception + */ + public function __construct($ip) + { + if (!filter_var($ip, FILTER_VALIDATE_IP)) { + throw new \Exception("Invalid IP address format"); + } + $this->in_addr = inet_pton($ip); + } + + /** + * @return string + */ + public function __toString() + { + return inet_ntop($this->in_addr); + } + + /** + * @param string ip + * @return IP + */ + public static function parse($ip) + { + if (strpos($ip, '0x') === 0) { + $ip = substr($ip, 2); + return self::parseHex($ip); + } + + if (strpos($ip, '0b') === 0) { + $ip = substr($ip, 2); + return self::parseBin($ip); + } + + if (is_numeric($ip)) { + return self::parseLong($ip); + } + + return new self($ip); + } + + /** + * @param string $binIP + * @throws \Exception + * @return IP + */ + public static function parseBin($binIP) + { + if (!preg_match('/^([0-1]{32}|[0-1]{128})$/', $binIP)) { + throw new \Exception("Invalid binary IP address format"); + } + + $in_addr = ''; + foreach (array_map('bindec', str_split($binIP, 8)) as $char) { + $in_addr .= pack('C*', $char); + } + + return new self(inet_ntop($in_addr)); + } + + /** + * @param string $hexIP + * @throws \Exception + * @return IP + */ + public static function parseHex($hexIP) + { + if (!preg_match('/^([0-9a-fA-F]{8}|[0-9a-fA-F]{32})$/', $hexIP)) { + throw new \Exception("Invalid hexadecimal IP address format"); + } + + return new self(inet_ntop(pack('H*', $hexIP))); + } + + /** + * @param string|int $longIP + * @return IP + */ + public static function parseLong($longIP, $version=self::IP_V4) + { + if ($version === self::IP_V4) { + $ip = new self(long2ip($longIP)); + } else { + $binary = array(); + for ($i = 0; $i < self::IP_V6_OCTETS; $i++) { + $binary[] = bcmod($longIP, 256); + $longIP = bcdiv($longIP, 256, 0); + } + $ip = new self(inet_ntop(call_user_func_array('pack', array_merge(array('C*'), array_reverse($binary))))); + } + + return $ip; + } + + /** + * @param string $inAddr + * @return IP + */ + public static function parseInAddr($inAddr) + { + return new self(inet_ntop($inAddr)); + } + + /** + * @return string + */ + public function getVersion() + { + $version = ''; + + if (filter_var(inet_ntop($this->in_addr), FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { + $version = self::IP_V4; + } elseif (filter_var(inet_ntop($this->in_addr), FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { + $version = self::IP_V6; + } + + return $version; + } + + /** + * @return int + */ + public function getMaxPrefixLength() + { + return $this->getVersion() === self::IP_V4 + ? self::IP_V4_MAX_PREFIX_LENGTH + : self::IP_V6_MAX_PREFIX_LENGTH; + } + + /** + * @return int + */ + public function getOctetsCount() + { + return $this->getVersion() === self::IP_V4 + ? self::IP_V4_OCTETS + : self::IP_V6_OCTETS; + } + + /** + * @return string + */ + public function getReversePointer() + { + if ($this->getVersion() === self::IP_V4) { + $reverseOctets = array_reverse(explode('.', $this->__toString())); + $reversePointer = implode('.', $reverseOctets) . '.in-addr.arpa'; + } else { + $unpacked = unpack('H*hex', $this->in_addr); + $reverseOctets = array_reverse(str_split($unpacked['hex'])); + $reversePointer = implode('.', $reverseOctets) . '.ip6.arpa'; + } + + return $reversePointer; + } + + /** + * @return string + */ + public function inAddr() + { + return $this->in_addr; + } + + /** + * @return string + */ + public function toBin() + { + $binary = array(); + foreach (unpack('C*', $this->in_addr) as $char) { + $binary[] = str_pad(decbin($char), 8, '0', STR_PAD_LEFT); + } + + return implode($binary); + } + + /** + * @return string + */ + public function toHex() + { + return bin2hex($this->in_addr); + } + + /** + * @return string + */ + public function toLong() + { + $long = 0; + if ($this->getVersion() === self::IP_V4) { + $long = sprintf('%u', ip2long(inet_ntop($this->in_addr))); + } else { + $octet = self::IP_V6_OCTETS - 1; + foreach ($chars = unpack('C*', $this->in_addr) as $char) { + $long = bcadd($long, bcmul($char, bcpow(256, $octet--))); + } + } + + return $long; + } + + /** + * @param int $to + * @return IP + * @throws \Exception + */ + public function next($to=1) + { + if ($to < 0) { + throw new \Exception("Number must be greater than 0"); + } + + $unpacked = unpack('C*', $this->in_addr); + + for ($i = 0; $i < $to; $i++) { + for ($byte = count($unpacked); $byte >= 0; --$byte) { + if ($unpacked[$byte] < 255) { + $unpacked[$byte]++; + break; + } + + $unpacked[$byte] = 0; + } + } + + return new self(inet_ntop(call_user_func_array('pack', array_merge(array('C*'), $unpacked)))); + } + + /** + * @param int $to + * @return IP + * @throws \Exception + */ + public function prev($to=1) + { + + if ($to < 0) { + throw new \Exception("Number must be greater than 0"); + } + + $unpacked = unpack('C*', $this->in_addr); + + for ($i = 0; $i < $to; $i++) { + for ($byte = count($unpacked); $byte >= 0; --$byte) { + if ($unpacked[$byte] === 0) { + $unpacked[$byte] = 255; + } else { + $unpacked[$byte]--; + break; + } + } + } + + return new self(inet_ntop(call_user_func_array('pack', array_merge(array('C*'), $unpacked)))); + } + +} diff --git a/vendor/s1lentium/iptools/src/Network.php b/vendor/s1lentium/iptools/src/Network.php new file mode 100644 index 0000000..220f09e --- /dev/null +++ b/vendor/s1lentium/iptools/src/Network.php @@ -0,0 +1,365 @@ + + * @link https://github.com/S1lentium/IPTools + */ +class Network implements \Iterator, \Countable +{ + use PropertyTrait; + + /** + * @var IP + */ + private $ip; + /** + * @var IP + */ + private $netmask; + /** + * @var int + */ + private $position = 0; + + /** + * @param IP $ip + * @param IP $netmask + */ + public function __construct(IP $ip, IP $netmask) + { + $this->setIP($ip); + $this->setNetmask($netmask); + } + + /** + * + * @return string + */ + public function __toString() + { + return $this->getCIDR(); + } + + /** + * @param string $data + * @return Network + */ + public static function parse($data) + { + if (preg_match('~^(.+?)/(\d+)$~', $data, $matches)) { + $ip = IP::parse($matches[1]); + $netmask = self::prefix2netmask((int)$matches[2], $ip->getVersion()); + } elseif (strpos($data,' ')) { + list($ip, $netmask) = explode(' ', $data, 2); + $ip = IP::parse($ip); + $netmask = IP::parse($netmask); + } else { + $ip = IP::parse($data); + $netmask = self::prefix2netmask($ip->getMaxPrefixLength(), $ip->getVersion()); + } + + return new self($ip, $netmask); + } + + /** + * @param int $prefixLength + * @param string $version + * @return IP + * @throws \Exception + */ + public static function prefix2netmask($prefixLength, $version) + { + if (!in_array($version, array(IP::IP_V4, IP::IP_V6))) { + throw new \Exception("Wrong IP version"); + } + + $maxPrefixLength = $version === IP::IP_V4 + ? IP::IP_V4_MAX_PREFIX_LENGTH + : IP::IP_V6_MAX_PREFIX_LENGTH; + + if (!is_numeric($prefixLength) + || !($prefixLength >= 0 && $prefixLength <= $maxPrefixLength) + ) { + throw new \Exception('Invalid prefix length'); + } + + $binIP = str_pad(str_pad('', (int)$prefixLength, '1'), $maxPrefixLength, '0'); + + return IP::parseBin($binIP); + } + + /** + * @param IP ip + * @return int + */ + public static function netmask2prefix(IP $ip) + { + return strlen(rtrim($ip->toBin(), 0)); + } + + /** + * @param IP ip + * @throws \Exception + */ + public function setIP(IP $ip) + { + if (isset($this->netmask) && $this->netmask->getVersion() !== $ip->getVersion()) { + throw new \Exception('IP version is not same as Netmask version'); + } + + $this->ip = $ip; + } + + /** + * @param IP ip + * @throws \Exception + */ + public function setNetmask(IP $ip) + { + if (!preg_match('/^1*0*$/',$ip->toBin())) { + throw new \Exception('Invalid Netmask address format'); + } + + if (isset($this->ip) && $ip->getVersion() !== $this->ip->getVersion()) { + throw new \Exception('Netmask version is not same as IP version'); + } + + $this->netmask = $ip; + } + + /** + * @param int $prefixLength + */ + public function setPrefixLength($prefixLength) + { + $this->setNetmask(self::prefix2netmask((int)$prefixLength, $this->ip->getVersion())); + } + + /** + * @return IP + */ + public function getIP() + { + return $this->ip; + } + + /** + * @return IP + */ + public function getNetmask() + { + return $this->netmask; + } + + /** + * @return IP + */ + public function getNetwork() + { + return new IP(inet_ntop($this->getIP()->inAddr() & $this->getNetmask()->inAddr())); + } + + /** + * @return int + */ + public function getPrefixLength() + { + return self::netmask2prefix($this->getNetmask()); + } + + /** + * @return string + */ + public function getCIDR() + { + return sprintf('%s/%s', $this->getNetwork(), $this->getPrefixLength()); + } + + /** + * @return IP + */ + public function getWildcard() + { + return new IP(inet_ntop(~$this->getNetmask()->inAddr())); + } + + /** + * @return IP + */ + public function getBroadcast() + { + return new IP(inet_ntop($this->getNetwork()->inAddr() | ~$this->getNetmask()->inAddr())); + } + + /** + * @return IP + */ + public function getFirstIP() + { + return $this->getNetwork(); + } + + /** + * @return IP + */ + public function getLastIP() + { + return $this->getBroadcast(); + } + + /** + * @return int|string + */ + public function getBlockSize() + { + $maxPrefixLength = $this->ip->getMaxPrefixLength(); + $prefixLength = $this->getPrefixLength(); + + if ($this->ip->getVersion() === IP::IP_V6) { + return bcpow('2', (string)($maxPrefixLength - $prefixLength)); + } + + return pow(2, $maxPrefixLength - $prefixLength); + } + + /** + * @return Range + */ + public function getHosts() + { + $firstHost = $this->getNetwork(); + $lastHost = $this->getBroadcast(); + + if ($this->ip->getVersion() === IP::IP_V4) { + if ($this->getBlockSize() > 2) { + $firstHost = IP::parseBin(substr($firstHost->toBin(), 0, $firstHost->getMaxPrefixLength() - 1) . '1'); + $lastHost = IP::parseBin(substr($lastHost->toBin(), 0, $lastHost->getMaxPrefixLength() - 1) . '0'); + } + } + + return new Range($firstHost, $lastHost); + } + + /** + * @param IP|Network $exclude + * @return Network[] + * @throws \Exception + */ + public function exclude($exclude) + { + $exclude = self::parse($exclude); + + if (strcmp($exclude->getFirstIP()->inAddr() , $this->getLastIP()->inAddr()) > 0 + || strcmp($exclude->getLastIP()->inAddr() , $this->getFirstIP()->inAddr()) < 0 + ) { + throw new \Exception('Exclude subnet not within target network'); + } + + $networks = array(); + + $newPrefixLength = $this->getPrefixLength() + 1; + if ($newPrefixLength > $this->ip->getMaxPrefixLength()) { + return $networks; + } + + $lower = clone $this; + $lower->setPrefixLength($newPrefixLength); + + $upper = clone $lower; + $upper->setIP($lower->getLastIP()->next()); + + while ($newPrefixLength <= $exclude->getPrefixLength()) { + $range = new Range($lower->getFirstIP(), $lower->getLastIP()); + if ($range->contains($exclude)) { + $matched = $lower; + $unmatched = $upper; + } else { + $matched = $upper; + $unmatched = $lower; + } + + $networks[] = clone $unmatched; + + if (++$newPrefixLength > $this->getNetwork()->getMaxPrefixLength()) break; + + $matched->setPrefixLength($newPrefixLength); + $unmatched->setPrefixLength($newPrefixLength); + $unmatched->setIP($matched->getLastIP()->next()); + } + + sort($networks); + + return $networks; + } + + /** + * @param int $prefixLength + * @return Network[] + * @throws \Exception + */ + public function moveTo($prefixLength) + { + $maxPrefixLength = $this->ip->getMaxPrefixLength(); + + if ($prefixLength <= $this->getPrefixLength() || $prefixLength > $maxPrefixLength) { + throw new \Exception('Invalid prefix length '); + } + + $netmask = self::prefix2netmask($prefixLength, $this->ip->getVersion()); + $networks = array(); + + $subnet = clone $this; + $subnet->setPrefixLength($prefixLength); + + while ($subnet->ip->inAddr() <= $this->getLastIP()->inAddr()) { + $networks[] = $subnet; + $subnet = new self($subnet->getLastIP()->next(), $netmask); + } + + return $networks; + } + + /** + * @return IP + */ + public function current() + { + return $this->getFirstIP()->next($this->position); + } + + /** + * @return int + */ + public function key() + { + return $this->position; + } + + public function next() + { + ++$this->position; + } + + public function rewind() + { + $this->position = 0; + } + + /** + * @return bool + */ + public function valid() + { + return strcmp($this->getFirstIP()->next($this->position)->inAddr(), $this->getLastIP()->inAddr()) <= 0; + } + + /** + * @return int + */ + public function count() + { + return (integer)$this->getBlockSize(); + } + +} diff --git a/vendor/s1lentium/iptools/src/PropertyTrait.php b/vendor/s1lentium/iptools/src/PropertyTrait.php new file mode 100644 index 0000000..5763644 --- /dev/null +++ b/vendor/s1lentium/iptools/src/PropertyTrait.php @@ -0,0 +1,45 @@ + + * @link https://github.com/S1lentium/IPTools + */ +trait PropertyTrait +{ + /** + * @param string $name + * @return mixed + */ + public function __get($name) + { + if(method_exists($this, $name)) { + return $this->$name(); + } + + foreach (array('get', 'to') as $prefix) { + $method = $prefix . ucfirst($name); + if(method_exists($this, $method)) { + return $this->$method(); + } + } + + trigger_error('Undefined property'); + return null; + } + + /** + * @param string $name + * @param mixed $value + */ + public function __set($name, $value) + { + $method = 'set'. ucfirst($name); + if (!method_exists($this, $method)) { + trigger_error('Undefined property'); + return; + } + $this->$method($value); + } + +} diff --git a/vendor/s1lentium/iptools/src/Range.php b/vendor/s1lentium/iptools/src/Range.php new file mode 100644 index 0000000..08919fa --- /dev/null +++ b/vendor/s1lentium/iptools/src/Range.php @@ -0,0 +1,228 @@ + + * @link https://github.com/S1lentium/IPTools + */ +class Range implements \Iterator, \Countable +{ + use PropertyTrait; + + /** + * @var IP + */ + private $firstIP; + /** + * @var IP + */ + private $lastIP; + /** + * @var int + */ + private $position = 0; + + /** + * @param IP $firstIP + * @param IP $lastIP + * @throws \Exception + */ + public function __construct(IP $firstIP, IP $lastIP) + { + $this->setFirstIP($firstIP); + $this->setLastIP($lastIP); + } + + /** + * @param string $data + * @return Range + */ + public static function parse($data) + { + if (strpos($data,'/') || strpos($data,' ')) { + $network = Network::parse($data); + $firstIP = $network->getFirstIP(); + $lastIP = $network->getLastIP(); + } elseif (strpos($data, '*') !== false) { + $firstIP = IP::parse(str_replace('*', '0', $data)); + $lastIP = IP::parse(str_replace('*', '255', $data)); + } elseif (strpos($data, '-')) { + list($first, $last) = explode('-', $data, 2); + $firstIP = IP::parse($first); + $lastIP = IP::parse($last); + } else { + $firstIP = IP::parse($data); + $lastIP = clone $firstIP; + } + + return new self($firstIP, $lastIP); + } + + /** + * @param IP|Network|Range $find + * @return bool + * @throws \Exception + */ + public function contains($find) + { + if ($find instanceof IP) { + $within = (strcmp($find->inAddr(), $this->firstIP->inAddr()) >= 0) + && (strcmp($find->inAddr(), $this->lastIP->inAddr()) <= 0); + } elseif ($find instanceof Range || $find instanceof Network) { + /** + * @var Network|Range $find + */ + $within = (strcmp($find->getFirstIP()->inAddr(), $this->firstIP->inAddr()) >= 0) + && (strcmp($find->getLastIP()->inAddr(), $this->lastIP->inAddr()) <= 0); + } else { + throw new \Exception('Invalid type'); + } + + return $within; + } + + /** + * @param IP $ip + * @throws \Exception + */ + public function setFirstIP(IP $ip) + { + if ($this->lastIP && strcmp($ip->inAddr(), $this->lastIP->inAddr()) > 0) { + throw new \Exception('First IP is grater than second'); + } + + $this->firstIP = $ip; + } + + /** + * @param IP $ip + * @throws \Exception + */ + public function setLastIP(IP $ip) + { + if ($this->firstIP && strcmp($ip->inAddr(), $this->firstIP->inAddr()) < 0) { + throw new \Exception('Last IP is less than first'); + } + + $this->lastIP = $ip; + } + + /** + * @return IP + */ + public function getFirstIP() + { + return $this->firstIP; + } + + /** + * @return IP + */ + public function getLastIP() + { + return $this->lastIP; + } + + /** + * @return Network[] + */ + public function getNetworks() + { + $span = $this->getSpanNetwork(); + + $networks = array(); + + if ($span->getFirstIP()->inAddr() === $this->firstIP->inAddr() + && $span->getLastIP()->inAddr() === $this->lastIP->inAddr() + ) { + $networks = array($span); + } else { + if ($span->getFirstIP()->inAddr() !== $this->firstIP->inAddr()) { + $excluded = $span->exclude($this->firstIP->prev()); + foreach ($excluded as $network) { + if (strcmp($network->getFirstIP()->inAddr(), $this->firstIP->inAddr()) >= 0) { + $networks[] = $network; + } + } + } + + if ($span->getLastIP()->inAddr() !== $this->lastIP->inAddr()) { + if (!$networks) { + $excluded = $span->exclude($this->lastIP->next()); + } else { + $excluded = array_pop($networks); + $excluded = $excluded->exclude($this->lastIP->next()); + } + + foreach ($excluded as $network) { + $networks[] = $network; + if ($network->getLastIP()->inAddr() === $this->lastIP->inAddr()) { + break; + } + } + } + + } + + return $networks; + } + + /** + * @return Network + */ + public function getSpanNetwork() + { + $xorIP = IP::parseInAddr($this->getFirstIP()->inAddr() ^ $this->getLastIP()->inAddr()); + + preg_match('/^(0*)/', $xorIP->toBin(), $match); + + $prefixLength = strlen($match[1]); + + $ip = IP::parseBin(str_pad(substr($this->getFirstIP()->toBin(), 0, $prefixLength), $xorIP->getMaxPrefixLength(), '0')); + + return new Network($ip, Network::prefix2netmask($prefixLength, $ip->getVersion())); + } + + /** + * @return IP + */ + public function current() + { + return $this->firstIP->next($this->position); + } + + /** + * @return int + */ + public function key() + { + return $this->position; + } + + public function next() + { + ++$this->position; + } + + public function rewind() + { + $this->position = 0; + } + + /** + * @return bool + */ + public function valid() + { + return strcmp($this->firstIP->next($this->position)->inAddr(), $this->lastIP->inAddr()) <= 0; + } + + /** + * @return int + */ + public function count() + { + return (integer)bcadd(bcsub($this->lastIP->toLong(), $this->firstIP->toLong()), 1); + } + +} diff --git a/vendor/s1lentium/iptools/tests/IPTest.php b/vendor/s1lentium/iptools/tests/IPTest.php new file mode 100644 index 0000000..3557794 --- /dev/null +++ b/vendor/s1lentium/iptools/tests/IPTest.php @@ -0,0 +1,255 @@ +assertEquals(inet_pton($ipv4String), $ipv4->inAddr()); + $this->assertEquals(IP::IP_V4, $ipv4->getVersion()); + $this->assertEquals(IP::IP_V4_MAX_PREFIX_LENGTH, $ipv4->getMaxPrefixLength()); + $this->assertEquals(IP::IP_V4_OCTETS, $ipv4->getOctetsCount()); + + $this->assertEquals(inet_pton($ipv6String), $ipv6->inAddr()); + $this->assertEquals(IP::IP_V6, $ipv6->getVersion()); + $this->assertEquals(IP::IP_V6_MAX_PREFIX_LENGTH, $ipv6->getMaxPrefixLength()); + $this->assertEquals(IP::IP_V6_OCTETS, $ipv6->getOctetsCount()); + } + + /** + * @dataProvider getTestContructorExceptionData + * @expectedException Exception + * @expectedExceptionMessage Invalid IP address format + */ + public function testConstructorException($string) + { + $ip = new IP($string); + } + + public function testProperties() + { + $ip = new IP('127.0.0.1'); + + $this->assertNotEmpty($ip->maxPrefixLength); + $this->assertNotEmpty($ip->octetsCount); + $this->assertNotEmpty($ip->reversePointer); + + $this->assertNotEmpty($ip->bin); + $this->assertNotEmpty($ip->long); + $this->assertNotEmpty($ip->hex); + } + + /** + * @dataProvider getToStringData + */ + public function testToString($actual, $expected) + { + $ip = new IP($actual); + $this->assertEquals($expected, (string)$ip); + + } + + /** + * @dataProvider getTestParseData + */ + public function testParse($ipString, $expected) + { + $ip = IP::parse($ipString); + $this->assertEquals($expected, (string) $ip); + } + + /** + * @dataProvider getParseBinData + */ + public function testParseBin($bin, $expectedString) + { + $ip = IP::parseBin($bin); + + $this->assertEquals($expectedString, (string) $ip); + $this->assertEquals($bin, $ip->toBin()); + } + + /** + * @expectedException Exception + * @expectedExceptionMessage Invalid binary IP address format + */ + public function testParseBinException() + { + IP::parseBin('192.168.1.1'); + } + + public function testParseLong() + { + $ipv4long = '2130706433'; + $ipv4 = IP::parseLong($ipv4long); + + $ipv6Long = '340277174624079928635746076935438991360'; + $ipv6 = IP::parseLong($ipv6Long, IP::IP_V6); + + $this->assertEquals('127.0.0.1', (string)$ipv4); + $this->assertEquals($ipv4long, $ipv4->toLong()); + + $this->assertEquals('ffff::', (string)$ipv6); + $this->assertEquals($ipv6Long, $ipv6->toLong()); + } + + public function testParseHex() + { + $hex = '7f000001'; + $ip = IP::parseHex($hex); + + $this->assertEquals('127.0.0.1', (string)$ip); + $this->assertEquals($hex, $ip->toHex()); + + } + + /** + * @expectedException Exception + * @expectedExceptionMessage Invalid hexadecimal IP address format + */ + public function testParseHexException() + { + IP::parseHex('192.168.1.1'); + } + + public function testParseInAddr() + { + $inAddr = inet_pton('127.0.0.1'); + $ip = IP::parseInAddr($inAddr); + + $this->assertEquals($inAddr, $ip->inAddr()); + + $inAddr = inet_pton('2001::8000:0:0:0'); + $ip = IP::parseInAddr($inAddr); + + $this->assertEquals($inAddr, $ip->inAddr()); + } + + /** + * @dataProvider getTestNextData + */ + public function testNext($ip, $step, $expected) + { + $object = new IP($ip); + $next = $object->next($step); + + $this->assertEquals($expected, (string) $next); + } + + /** + * @dataProvider getTestPrevData + */ + public function testPrev($ip, $step, $expected) + { + $object = new IP($ip); + $prev = $object->prev($step); + + $this->assertEquals($expected, (string) $prev); + } + + /** + * @expectedException Exception + * @expectedExceptionMessage Number must be greater than 0 + */ + public function testPrevException() + { + $object = new IP('192.168.1.1'); + $object->prev(-1); + } + + /** + * @dataProvider getReversePointerData + */ + public function testReversePointer($ip, $expected) + { + $object = new IP($ip); + $reversePointer = $object->getReversePointer(); + $this->assertEquals($expected, $reversePointer); + } + + public function getTestContructorExceptionData() + { + return array( + array('256.0.0.1'), + array('127.-1.0.1'), + array(123.45), + array(-123.45), + array('cake'), + array('12345'), + array('-12345'), + array('0000:0000:0000:ffff:0127:0000:0000:0001:0000'), + ); + } + + public function getToStringData() + { + return array( + array('127.0.0.1', '127.0.0.1'), + array('2001::', '2001::'), + array('2001:0000:0000:0000:0000:0000:0000:0000', '2001::'), + array('2001:0000:0000:0000:8000:0000:0000:0000', '2001::8000:0:0:0') + ); + } + + public function getTestParseData() + { + return array( + array(2130706433, '127.0.0.1'), //long + array('0b01111111000000000000000000000001', '127.0.0.1'), //bin + array('0x7f000001', '127.0.0.1'), //hex, + array('0x20010000000000008000000000000000', '2001::8000:0:0:0'), //hex + array('127.0.0.1', '127.0.0.1'), + array('2001::', '2001::') + ); + } + + public function getParseBinData() + { + return array( + array( + '00100000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + '2001::' + ), + array('01111111000000000000000000000001', '127.0.0.1') + ); + } + + public function getTestNextData() + { + return array( + array('192.168.0.1', 1, '192.168.0.2'), + array('192.168.0.1', 254, '192.168.0.255'), + array('192.168.0.1', 255, '192.168.1.0'), + array('2001::', 1, '2001::1'), + array('2001::', 65535, '2001::ffff'), + array('2001::', 65536, '2001::1:0') + ); + } + + public function getTestPrevData() + { + return array( + array('192.168.1.1', 1, '192.168.1.0'), + array('192.168.1.0', 1, '192.168.0.255'), + array('192.168.1.1', 258, '192.167.255.255'), + array('2001::1', 1, '2001::'), + array('2001::1:0', 1, '2001::ffff'), + array('2001::1:0', 65536, '2001::'), + ); + } + + public function getReversePointerData() + { + return array( + array('192.0.2.5', '5.2.0.192.in-addr.arpa'), + array('2001:db8::567:89ab', 'b.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa'), + ); + } +} diff --git a/vendor/s1lentium/iptools/tests/NetworkTest.php b/vendor/s1lentium/iptools/tests/NetworkTest.php new file mode 100644 index 0000000..0e90b76 --- /dev/null +++ b/vendor/s1lentium/iptools/tests/NetworkTest.php @@ -0,0 +1,305 @@ +assertEquals('127.0.0.0/24', (string)$ipv4Network); + $this->assertEquals('2001::/112', (string)$ipv6Network); + } + + public function testProperties() + { + $network = Network::parse('127.0.0.1/24'); + + $network->ip = new IP('192.0.0.2'); + + $this->assertEquals('192.0.0.2', $network->ip); + $this->assertEquals('192.0.0.0/24', (string)$network); + $this->assertEquals('0.0.0.255', (string)$network->wildcard); + $this->assertEquals('192.0.0.0', (string)$network->firstIP); + $this->assertEquals('192.0.0.255', (string)$network->lastIP); + } + + /** + * @dataProvider getTestParseData + */ + public function testParse($data, $expected) + { + $this->assertEquals($expected, (string)Network::parse($data)); + } + + /** + * @expectedException Exception + * @expectedExceptionMessage Invalid IP address format + */ + public function testParseWrongNetwork() + { + Network::parse('10.0.0.0/24 abc'); + } + + /** + * @dataProvider getPrefixData + */ + public function testPrefix2Mask($prefix, $version, $mask) + { + $this->assertEquals($mask, Network::prefix2netmask($prefix, $version)); + } + + /** + * @expectedException Exception + * @expectedExceptionMessage Wrong IP version + */ + public function testPrefix2MaskWrongIPVersion() + { + Network::prefix2netmask('128', 'ip_version'); + } + + /** + * @dataProvider getInvalidPrefixData + * @expectedException Exception + * @expectedExceptionMessage Invalid prefix length + */ + public function testPrefix2MaskInvalidPrefix($prefix, $version) + { + Network::prefix2netmask($prefix, $version); + } + + /** + * @dataProvider getHostsData + */ + public function testHosts($data, $expected) + { + foreach(Network::parse($data)->getHosts as $ip) { + $result[] = (string)$ip; + } + + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider getExcludeData + */ + public function testExclude($data, $exclude, $expected) + { + $result = array(); + + foreach(Network::parse($data)->exclude($exclude) as $network) { + $result[] = (string)$network; + } + + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider getExcludeExceptionData + * @expectedException Exception + * @expectedExceptionMessage Exclude subnet not within target network + */ + public function testExcludeException($data, $exclude) + { + Network::parse($data)->exclude($exclude); + } + + /** + * @dataProvider getMoveToData + */ + public function testMoveTo($network, $prefixLength, $expected) + { + $result = array(); + + foreach (Network::parse($network)->moveTo($prefixLength) as $network) { + $result[] = (string)$network; + } + + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider getMoveToExceptionData + * @expectedException Exception + * @expectedExceptionMessage Invalid prefix length + */ + public function testMoveToException($network, $prefixLength) + { + Network::parse($network)->moveTo($prefixLength); + } + + /** + * @dataProvider getTestIterationData + */ + public function testNetworkIteration($data, $expected) + { + foreach (Network::parse($data) as $key => $ip) { + $result[] = (string)$ip; + } + + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider getTestCountData + */ + public function testCount($data, $expected) + { + $this->assertEquals($expected, count(Network::parse($data))); + } + + public function getTestParseData() + { + return array( + array('192.168.0.54/24', '192.168.0.0/24'), + array('2001::2001:2001/32', '2001::/32'), + array('127.168.0.1 255.255.255.255', '127.168.0.1/32'), + array('1234::1234', '1234::1234/128'), + ); + } + + public function getPrefixData() + { + return array( + array('24', IP::IP_V4, IP::parse('255.255.255.0')), + array('32', IP::IP_V4, IP::parse('255.255.255.255')), + array('64', IP::IP_V6, IP::parse('ffff:ffff:ffff:ffff::')), + array('128', IP::IP_V6, IP::parse('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff')) + ); + } + + public function getInvalidPrefixData() + { + return array( + array('-1', IP::IP_V4), + array('33', IP::IP_V4), + array('prefix', IP::IP_V4), + array('-1', IP::IP_V6), + array('129', IP::IP_V6), + ); + } + + public function getHostsData() + { + return array( + array('192.0.2.0/29', + array( + '192.0.2.1', + '192.0.2.2', + '192.0.2.3', + '192.0.2.4', + '192.0.2.5', + '192.0.2.6', + ) + ), + ); + } + + public function getExcludeData() + { + return array( + array('192.0.2.0/28', '192.0.2.1/32', + array( + '192.0.2.0/32', + '192.0.2.2/31', + '192.0.2.4/30', + '192.0.2.8/29', + ) + ), + array('192.0.2.2/32', '192.0.2.2/32', array()), + ); + } + + public function getExcludeExceptionData() + { + return array( + array('192.0.2.0/28', '192.0.3.0/24'), + array('192.0.2.2/32', '192.0.2.3/32'), + ); + } + + public function getMoveToData() + { + return array( + array('192.168.0.0/22', '24', + array( + '192.168.0.0/24', + '192.168.1.0/24', + '192.168.2.0/24', + '192.168.3.0/24' + ) + ), + array('192.168.2.0/24', '25', + array( + '192.168.2.0/25', + '192.168.2.128/25' + ) + ), + array('192.168.2.0/30', '32', + array( + '192.168.2.0/32', + '192.168.2.1/32', + '192.168.2.2/32', + '192.168.2.3/32' + ) + ), + ); + } + + public function getMoveToExceptionData() + { + return array( + array('192.168.0.0/22', '22'), + array('192.168.0.0/22', '21'), + array('192.168.0.0/22', '33'), + array('192.168.0.0/22', 'prefixLength') + ); + } + + public function getTestIterationData() + { + return array( + array('192.168.2.0/29', + array( + '192.168.2.0', + '192.168.2.1', + '192.168.2.2', + '192.168.2.3', + '192.168.2.4', + '192.168.2.5', + '192.168.2.6', + '192.168.2.7', + ) + ), + array('2001:db8::/125', + array( + '2001:db8::', + '2001:db8::1', + '2001:db8::2', + '2001:db8::3', + '2001:db8::4', + '2001:db8::5', + '2001:db8::6', + '2001:db8::7', + ) + ), + ); + } + + public function getTestCountData() + { + return array( + array('127.0.0.0/31', 2), + array('2001:db8::/120', 256), + ); + } +} diff --git a/vendor/s1lentium/iptools/tests/RangeTest.php b/vendor/s1lentium/iptools/tests/RangeTest.php new file mode 100644 index 0000000..4f81f8a --- /dev/null +++ b/vendor/s1lentium/iptools/tests/RangeTest.php @@ -0,0 +1,146 @@ +assertEquals($expected[0], $range->firstIP); + $this->assertEquals($expected[1], $range->lastIP); + } + + /** + * @dataProvider getTestNetworksData + */ + public function testGetNetworks($data, $expected) + { + $result = array(); + + foreach (Range::parse($data)->getNetworks() as $network) { + $result[] = (string)$network; + } + + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider getTestContainsData + */ + public function testContains($data, $find, $expected) + { + $this->assertEquals($expected, Range::parse($data)->contains(new IP($find))); + } + + /** + * @dataProvider getTestIterationData + */ + public function testRangeIteration($data, $expected) + { + foreach (Range::parse($data) as $key => $ip) { + $result[] = (string)$ip; + } + + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider getTestCountData + */ + public function testCount($data, $expected) + { + $this->assertEquals($expected, count(Range::parse($data))); + } + + public function getTestParseData() + { + return array( + array('127.0.0.1-127.255.255.255', array('127.0.0.1', '127.255.255.255')), + array('127.0.0.1/24', array('127.0.0.0', '127.0.0.255')), + array('127.*.0.0', array('127.0.0.0', '127.255.0.0')), + array('127.255.255.0', array('127.255.255.0', '127.255.255.0')), + ); + } + + public function getTestNetworksData() + { + return array( + array('192.168.1.*', array('192.168.1.0/24')), + array('192.168.1.208-192.168.1.255', array( + '192.168.1.208/28', + '192.168.1.224/27' + )), + array('192.168.1.0-192.168.1.191', array( + '192.168.1.0/25', + '192.168.1.128/26' + )), + array('192.168.1.125-192.168.1.126', array( + '192.168.1.125/32', + '192.168.1.126/32', + )), + ); + } + + public function getTestContainsData() + { + return array( + array('192.168.*.*', '192.168.245.15', true), + array('192.168.*.*', '192.169.255.255', false), + + /** + * 10.10.45.48 --> 00001010 00001010 00101101 00110000 + * the last 0000 leads error + */ + array('10.10.45.48/28', '10.10.45.58', true), + + array('2001:db8::/64', '2001:db8::ffff', true), + array('2001:db8::/64', '2001:db8:ffff::', false), + ); + } + + public function getTestIterationData() + { + return array( + array('192.168.2.0-192.168.2.7', + array( + '192.168.2.0', + '192.168.2.1', + '192.168.2.2', + '192.168.2.3', + '192.168.2.4', + '192.168.2.5', + '192.168.2.6', + '192.168.2.7', + ) + ), + array('2001:db8::/125', + array( + '2001:db8::', + '2001:db8::1', + '2001:db8::2', + '2001:db8::3', + '2001:db8::4', + '2001:db8::5', + '2001:db8::6', + '2001:db8::7', + ) + ), + ); + } + + public function getTestCountData() + { + return array( + array('127.0.0.0/31', 2), + array('2001:db8::/120', 256), + ); + } + +} diff --git a/vendor/s1lentium/iptools/tests/bootstrap.php b/vendor/s1lentium/iptools/tests/bootstrap.php new file mode 100644 index 0000000..5a9f5ff --- /dev/null +++ b/vendor/s1lentium/iptools/tests/bootstrap.php @@ -0,0 +1,10 @@ + 'tauthz\\TauthzService', + 1 => 'think\\captcha\\CaptchaService', + 2 => 'think\\migration\\Service', + 3 => 'think\\queue\\Service', + 4 => 'think\\trace\\Service', +); \ No newline at end of file diff --git a/vendor/symfony/cache-contracts/.gitignore b/vendor/symfony/cache-contracts/.gitignore new file mode 100644 index 0000000..c49a5d8 --- /dev/null +++ b/vendor/symfony/cache-contracts/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/cache-contracts/CHANGELOG.md b/vendor/symfony/cache-contracts/CHANGELOG.md new file mode 100644 index 0000000..7932e26 --- /dev/null +++ b/vendor/symfony/cache-contracts/CHANGELOG.md @@ -0,0 +1,5 @@ +CHANGELOG +========= + +The changelog is maintained for all Symfony contracts at the following URL: +https://github.com/symfony/contracts/blob/main/CHANGELOG.md diff --git a/vendor/symfony/cache-contracts/CacheInterface.php b/vendor/symfony/cache-contracts/CacheInterface.php new file mode 100644 index 0000000..67e4dfd --- /dev/null +++ b/vendor/symfony/cache-contracts/CacheInterface.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Cache; + +use Psr\Cache\CacheItemInterface; +use Psr\Cache\InvalidArgumentException; + +/** + * Covers most simple to advanced caching needs. + * + * @author Nicolas Grekas + */ +interface CacheInterface +{ + /** + * Fetches a value from the pool or computes it if not found. + * + * On cache misses, a callback is called that should return the missing value. + * This callback is given a PSR-6 CacheItemInterface instance corresponding to the + * requested key, that could be used e.g. for expiration control. It could also + * be an ItemInterface instance when its additional features are needed. + * + * @param string $key The key of the item to retrieve from the cache + * @param callable|CallbackInterface $callback Should return the computed value for the given key/item + * @param float|null $beta A float that, as it grows, controls the likeliness of triggering + * early expiration. 0 disables it, INF forces immediate expiration. + * The default (or providing null) is implementation dependent but should + * typically be 1.0, which should provide optimal stampede protection. + * See https://en.wikipedia.org/wiki/Cache_stampede#Probabilistic_early_expiration + * @param array &$metadata The metadata of the cached item {@see ItemInterface::getMetadata()} + * + * @return mixed + * + * @throws InvalidArgumentException When $key is not valid or when $beta is negative + */ + public function get(string $key, callable $callback, float $beta = null, array &$metadata = null); + + /** + * Removes an item from the pool. + * + * @param string $key The key to delete + * + * @throws InvalidArgumentException When $key is not valid + * + * @return bool True if the item was successfully removed, false if there was any error + */ + public function delete(string $key): bool; +} diff --git a/vendor/symfony/cache-contracts/CacheTrait.php b/vendor/symfony/cache-contracts/CacheTrait.php new file mode 100644 index 0000000..d340e06 --- /dev/null +++ b/vendor/symfony/cache-contracts/CacheTrait.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Cache; + +use Psr\Cache\CacheItemPoolInterface; +use Psr\Cache\InvalidArgumentException; +use Psr\Log\LoggerInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(InvalidArgumentException::class); + +/** + * An implementation of CacheInterface for PSR-6 CacheItemPoolInterface classes. + * + * @author Nicolas Grekas + */ +trait CacheTrait +{ + /** + * {@inheritdoc} + * + * @return mixed + */ + public function get(string $key, callable $callback, float $beta = null, array &$metadata = null) + { + return $this->doGet($this, $key, $callback, $beta, $metadata); + } + + /** + * {@inheritdoc} + */ + public function delete(string $key): bool + { + return $this->deleteItem($key); + } + + private function doGet(CacheItemPoolInterface $pool, string $key, callable $callback, ?float $beta, array &$metadata = null, LoggerInterface $logger = null) + { + if (0 > $beta = $beta ?? 1.0) { + throw new class(sprintf('Argument "$beta" provided to "%s::get()" must be a positive number, %f given.', static::class, $beta)) extends \InvalidArgumentException implements InvalidArgumentException { }; + } + + $item = $pool->getItem($key); + $recompute = !$item->isHit() || \INF === $beta; + $metadata = $item instanceof ItemInterface ? $item->getMetadata() : []; + + if (!$recompute && $metadata) { + $expiry = $metadata[ItemInterface::METADATA_EXPIRY] ?? false; + $ctime = $metadata[ItemInterface::METADATA_CTIME] ?? false; + + if ($recompute = $ctime && $expiry && $expiry <= ($now = microtime(true)) - $ctime / 1000 * $beta * log(random_int(1, \PHP_INT_MAX) / \PHP_INT_MAX)) { + // force applying defaultLifetime to expiry + $item->expiresAt(null); + $logger && $logger->info('Item "{key}" elected for early recomputation {delta}s before its expiration', [ + 'key' => $key, + 'delta' => sprintf('%.1f', $expiry - $now), + ]); + } + } + + if ($recompute) { + $save = true; + $item->set($callback($item, $save)); + if ($save) { + $pool->save($item); + } + } + + return $item->get(); + } +} diff --git a/vendor/symfony/cache-contracts/CallbackInterface.php b/vendor/symfony/cache-contracts/CallbackInterface.php new file mode 100644 index 0000000..7dae2aa --- /dev/null +++ b/vendor/symfony/cache-contracts/CallbackInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Cache; + +use Psr\Cache\CacheItemInterface; + +/** + * Computes and returns the cached value of an item. + * + * @author Nicolas Grekas + */ +interface CallbackInterface +{ + /** + * @param CacheItemInterface|ItemInterface $item The item to compute the value for + * @param bool &$save Should be set to false when the value should not be saved in the pool + * + * @return mixed The computed value for the passed item + */ + public function __invoke(CacheItemInterface $item, bool &$save); +} diff --git a/vendor/symfony/cache-contracts/ItemInterface.php b/vendor/symfony/cache-contracts/ItemInterface.php new file mode 100644 index 0000000..10c0488 --- /dev/null +++ b/vendor/symfony/cache-contracts/ItemInterface.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Cache; + +use Psr\Cache\CacheException; +use Psr\Cache\CacheItemInterface; +use Psr\Cache\InvalidArgumentException; + +/** + * Augments PSR-6's CacheItemInterface with support for tags and metadata. + * + * @author Nicolas Grekas + */ +interface ItemInterface extends CacheItemInterface +{ + /** + * References the Unix timestamp stating when the item will expire. + */ + public const METADATA_EXPIRY = 'expiry'; + + /** + * References the time the item took to be created, in milliseconds. + */ + public const METADATA_CTIME = 'ctime'; + + /** + * References the list of tags that were assigned to the item, as string[]. + */ + public const METADATA_TAGS = 'tags'; + + /** + * Reserved characters that cannot be used in a key or tag. + */ + public const RESERVED_CHARACTERS = '{}()/\@:'; + + /** + * Adds a tag to a cache item. + * + * Tags are strings that follow the same validation rules as keys. + * + * @param string|string[] $tags A tag or array of tags + * + * @return $this + * + * @throws InvalidArgumentException When $tag is not valid + * @throws CacheException When the item comes from a pool that is not tag-aware + */ + public function tag($tags): self; + + /** + * Returns a list of metadata info that were saved alongside with the cached value. + * + * See ItemInterface::METADATA_* consts for keys potentially found in the returned array. + */ + public function getMetadata(): array; +} diff --git a/vendor/symfony/cache-contracts/LICENSE b/vendor/symfony/cache-contracts/LICENSE new file mode 100644 index 0000000..2358414 --- /dev/null +++ b/vendor/symfony/cache-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2021 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/cache-contracts/README.md b/vendor/symfony/cache-contracts/README.md new file mode 100644 index 0000000..7085a69 --- /dev/null +++ b/vendor/symfony/cache-contracts/README.md @@ -0,0 +1,9 @@ +Symfony Cache Contracts +======================= + +A set of abstractions extracted out of the Symfony components. + +Can be used to build on semantics that the Symfony components proved useful - and +that already have battle tested implementations. + +See https://github.com/symfony/contracts/blob/main/README.md for more information. diff --git a/vendor/symfony/cache-contracts/TagAwareCacheInterface.php b/vendor/symfony/cache-contracts/TagAwareCacheInterface.php new file mode 100644 index 0000000..7c4cf11 --- /dev/null +++ b/vendor/symfony/cache-contracts/TagAwareCacheInterface.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Cache; + +use Psr\Cache\InvalidArgumentException; + +/** + * Allows invalidating cached items using tags. + * + * @author Nicolas Grekas + */ +interface TagAwareCacheInterface extends CacheInterface +{ + /** + * Invalidates cached items using tags. + * + * When implemented on a PSR-6 pool, invalidation should not apply + * to deferred items. Instead, they should be committed as usual. + * This allows replacing old tagged values by new ones without + * race conditions. + * + * @param string[] $tags An array of tags to invalidate + * + * @return bool True on success + * + * @throws InvalidArgumentException When $tags is not valid + */ + public function invalidateTags(array $tags); +} diff --git a/vendor/symfony/cache-contracts/composer.json b/vendor/symfony/cache-contracts/composer.json new file mode 100644 index 0000000..9f45e17 --- /dev/null +++ b/vendor/symfony/cache-contracts/composer.json @@ -0,0 +1,38 @@ +{ + "name": "symfony/cache-contracts", + "type": "library", + "description": "Generic abstractions related to caching", + "keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.2.5", + "psr/cache": "^1.0|^2.0|^3.0" + }, + "suggest": { + "symfony/cache-implementation": "" + }, + "autoload": { + "psr-4": { "Symfony\\Contracts\\Cache\\": "" } + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + } +} diff --git a/vendor/symfony/cache/Adapter/AbstractAdapter.php b/vendor/symfony/cache/Adapter/AbstractAdapter.php new file mode 100644 index 0000000..b2dec61 --- /dev/null +++ b/vendor/symfony/cache/Adapter/AbstractAdapter.php @@ -0,0 +1,203 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerInterface; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Cache\Traits\AbstractAdapterTrait; +use Symfony\Component\Cache\Traits\ContractsTrait; +use Symfony\Contracts\Cache\CacheInterface; + +/** + * @author Nicolas Grekas + */ +abstract class AbstractAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface +{ + use AbstractAdapterTrait; + use ContractsTrait; + + /** + * @internal + */ + protected const NS_SEPARATOR = ':'; + + private static $apcuSupported; + private static $phpFilesSupported; + + protected function __construct(string $namespace = '', int $defaultLifetime = 0) + { + $this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).static::NS_SEPARATOR; + $this->defaultLifetime = $defaultLifetime; + if (null !== $this->maxIdLength && \strlen($namespace) > $this->maxIdLength - 24) { + throw new InvalidArgumentException(sprintf('Namespace must be %d chars max, %d given ("%s").', $this->maxIdLength - 24, \strlen($namespace), $namespace)); + } + self::$createCacheItem ?? self::$createCacheItem = \Closure::bind( + static function ($key, $value, $isHit) { + $item = new CacheItem(); + $item->key = $key; + $item->value = $v = $value; + $item->isHit = $isHit; + // Detect wrapped values that encode for their expiry and creation duration + // For compactness, these values are packed in the key of an array using + // magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F + if (\is_array($v) && 1 === \count($v) && 10 === \strlen($k = (string) array_key_first($v)) && "\x9D" === $k[0] && "\0" === $k[5] && "\x5F" === $k[9]) { + $item->value = $v[$k]; + $v = unpack('Ve/Nc', substr($k, 1, -1)); + $item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET; + $item->metadata[CacheItem::METADATA_CTIME] = $v['c']; + } + + return $item; + }, + null, + CacheItem::class + ); + self::$mergeByLifetime ?? self::$mergeByLifetime = \Closure::bind( + static function ($deferred, $namespace, &$expiredIds, $getId, $defaultLifetime) { + $byLifetime = []; + $now = microtime(true); + $expiredIds = []; + + foreach ($deferred as $key => $item) { + $key = (string) $key; + if (null === $item->expiry) { + $ttl = 0 < $defaultLifetime ? $defaultLifetime : 0; + } elseif (0 === $item->expiry) { + $ttl = 0; + } elseif (0 >= $ttl = (int) (0.1 + $item->expiry - $now)) { + $expiredIds[] = $getId($key); + continue; + } + if (isset(($metadata = $item->newMetadata)[CacheItem::METADATA_TAGS])) { + unset($metadata[CacheItem::METADATA_TAGS]); + } + // For compactness, expiry and creation duration are packed in the key of an array, using magic numbers as separators + $byLifetime[$ttl][$getId($key)] = $metadata ? ["\x9D".pack('VN', (int) (0.1 + $metadata[self::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[self::METADATA_CTIME])."\x5F" => $item->value] : $item->value; + } + + return $byLifetime; + }, + null, + CacheItem::class + ); + } + + /** + * Returns the best possible adapter that your runtime supports. + * + * Using ApcuAdapter makes system caches compatible with read-only filesystems. + * + * @return AdapterInterface + */ + public static function createSystemCache(string $namespace, int $defaultLifetime, string $version, string $directory, LoggerInterface $logger = null) + { + $opcache = new PhpFilesAdapter($namespace, $defaultLifetime, $directory, true); + if (null !== $logger) { + $opcache->setLogger($logger); + } + + if (!self::$apcuSupported = self::$apcuSupported ?? ApcuAdapter::isSupported()) { + return $opcache; + } + + if (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) && !filter_var(ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOLEAN)) { + return $opcache; + } + + $apcu = new ApcuAdapter($namespace, intdiv($defaultLifetime, 5), $version); + if (null !== $logger) { + $apcu->setLogger($logger); + } + + return new ChainAdapter([$apcu, $opcache]); + } + + public static function createConnection(string $dsn, array $options = []) + { + if (str_starts_with($dsn, 'redis:') || str_starts_with($dsn, 'rediss:')) { + return RedisAdapter::createConnection($dsn, $options); + } + if (str_starts_with($dsn, 'memcached:')) { + return MemcachedAdapter::createConnection($dsn, $options); + } + if (0 === strpos($dsn, 'couchbase:')) { + if (CouchbaseBucketAdapter::isSupported()) { + return CouchbaseBucketAdapter::createConnection($dsn, $options); + } + + return CouchbaseCollectionAdapter::createConnection($dsn, $options); + } + + throw new InvalidArgumentException(sprintf('Unsupported DSN: "%s".', $dsn)); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function commit() + { + $ok = true; + $byLifetime = (self::$mergeByLifetime)($this->deferred, $this->namespace, $expiredIds, \Closure::fromCallable([$this, 'getId']), $this->defaultLifetime); + $retry = $this->deferred = []; + + if ($expiredIds) { + $this->doDelete($expiredIds); + } + foreach ($byLifetime as $lifetime => $values) { + try { + $e = $this->doSave($values, $lifetime); + } catch (\Exception $e) { + } + if (true === $e || [] === $e) { + continue; + } + if (\is_array($e) || 1 === \count($values)) { + foreach (\is_array($e) ? $e : array_keys($values) as $id) { + $ok = false; + $v = $values[$id]; + $type = get_debug_type($v); + $message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.'); + CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => get_debug_type($this)]); + } + } else { + foreach ($values as $id => $v) { + $retry[$lifetime][] = $id; + } + } + } + + // When bulk-save failed, retry each item individually + foreach ($retry as $lifetime => $ids) { + foreach ($ids as $id) { + try { + $v = $byLifetime[$lifetime][$id]; + $e = $this->doSave([$id => $v], $lifetime); + } catch (\Exception $e) { + } + if (true === $e || [] === $e) { + continue; + } + $ok = false; + $type = get_debug_type($v); + $message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.'); + CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => get_debug_type($this)]); + } + } + + return $ok; + } +} diff --git a/vendor/symfony/cache/Adapter/AbstractTagAwareAdapter.php b/vendor/symfony/cache/Adapter/AbstractTagAwareAdapter.php new file mode 100644 index 0000000..d062a94 --- /dev/null +++ b/vendor/symfony/cache/Adapter/AbstractTagAwareAdapter.php @@ -0,0 +1,323 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Log\LoggerAwareInterface; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Cache\Traits\AbstractAdapterTrait; +use Symfony\Component\Cache\Traits\ContractsTrait; +use Symfony\Contracts\Cache\TagAwareCacheInterface; + +/** + * Abstract for native TagAware adapters. + * + * To keep info on tags, the tags are both serialized as part of cache value and provided as tag ids + * to Adapters on operations when needed for storage to doSave(), doDelete() & doInvalidate(). + * + * @author Nicolas Grekas + * @author André Rømcke + * + * @internal + */ +abstract class AbstractTagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface, LoggerAwareInterface, ResettableInterface +{ + use AbstractAdapterTrait; + use ContractsTrait; + + private const TAGS_PREFIX = "\0tags\0"; + + protected function __construct(string $namespace = '', int $defaultLifetime = 0) + { + $this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).':'; + $this->defaultLifetime = $defaultLifetime; + if (null !== $this->maxIdLength && \strlen($namespace) > $this->maxIdLength - 24) { + throw new InvalidArgumentException(sprintf('Namespace must be %d chars max, %d given ("%s").', $this->maxIdLength - 24, \strlen($namespace), $namespace)); + } + self::$createCacheItem ?? self::$createCacheItem = \Closure::bind( + static function ($key, $value, $isHit) { + $item = new CacheItem(); + $item->key = $key; + $item->isTaggable = true; + // If structure does not match what we expect return item as is (no value and not a hit) + if (!\is_array($value) || !\array_key_exists('value', $value)) { + return $item; + } + $item->isHit = $isHit; + // Extract value, tags and meta data from the cache value + $item->value = $value['value']; + $item->metadata[CacheItem::METADATA_TAGS] = $value['tags'] ?? []; + if (isset($value['meta'])) { + // For compactness these values are packed, & expiry is offset to reduce size + $v = unpack('Ve/Nc', $value['meta']); + $item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET; + $item->metadata[CacheItem::METADATA_CTIME] = $v['c']; + } + + return $item; + }, + null, + CacheItem::class + ); + self::$mergeByLifetime ?? self::$mergeByLifetime = \Closure::bind( + static function ($deferred, &$expiredIds, $getId, $tagPrefix, $defaultLifetime) { + $byLifetime = []; + $now = microtime(true); + $expiredIds = []; + + foreach ($deferred as $key => $item) { + $key = (string) $key; + if (null === $item->expiry) { + $ttl = 0 < $defaultLifetime ? $defaultLifetime : 0; + } elseif (0 === $item->expiry) { + $ttl = 0; + } elseif (0 >= $ttl = (int) (0.1 + $item->expiry - $now)) { + $expiredIds[] = $getId($key); + continue; + } + // Store Value and Tags on the cache value + if (isset(($metadata = $item->newMetadata)[CacheItem::METADATA_TAGS])) { + $value = ['value' => $item->value, 'tags' => $metadata[CacheItem::METADATA_TAGS]]; + unset($metadata[CacheItem::METADATA_TAGS]); + } else { + $value = ['value' => $item->value, 'tags' => []]; + } + + if ($metadata) { + // For compactness, expiry and creation duration are packed, using magic numbers as separators + $value['meta'] = pack('VN', (int) (0.1 + $metadata[self::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[self::METADATA_CTIME]); + } + + // Extract tag changes, these should be removed from values in doSave() + $value['tag-operations'] = ['add' => [], 'remove' => []]; + $oldTags = $item->metadata[CacheItem::METADATA_TAGS] ?? []; + foreach (array_diff($value['tags'], $oldTags) as $addedTag) { + $value['tag-operations']['add'][] = $getId($tagPrefix.$addedTag); + } + foreach (array_diff($oldTags, $value['tags']) as $removedTag) { + $value['tag-operations']['remove'][] = $getId($tagPrefix.$removedTag); + } + + $byLifetime[$ttl][$getId($key)] = $value; + $item->metadata = $item->newMetadata; + } + + return $byLifetime; + }, + null, + CacheItem::class + ); + } + + /** + * Persists several cache items immediately. + * + * @param array $values The values to cache, indexed by their cache identifier + * @param int $lifetime The lifetime of the cached values, 0 for persisting until manual cleaning + * @param array[] $addTagData Hash where key is tag id, and array value is list of cache id's to add to tag + * @param array[] $removeTagData Hash where key is tag id, and array value is list of cache id's to remove to tag + * + * @return array The identifiers that failed to be cached or a boolean stating if caching succeeded or not + */ + abstract protected function doSave(array $values, int $lifetime, array $addTagData = [], array $removeTagData = []): array; + + /** + * Removes multiple items from the pool and their corresponding tags. + * + * @param array $ids An array of identifiers that should be removed from the pool + * + * @return bool + */ + abstract protected function doDelete(array $ids); + + /** + * Removes relations between tags and deleted items. + * + * @param array $tagData Array of tag => key identifiers that should be removed from the pool + */ + abstract protected function doDeleteTagRelations(array $tagData): bool; + + /** + * Invalidates cached items using tags. + * + * @param string[] $tagIds An array of tags to invalidate, key is tag and value is tag id + * + * @return bool + */ + abstract protected function doInvalidate(array $tagIds): bool; + + /** + * Delete items and yields the tags they were bound to. + */ + protected function doDeleteYieldTags(array $ids): iterable + { + foreach ($this->doFetch($ids) as $id => $value) { + yield $id => \is_array($value) && \is_array($value['tags'] ?? null) ? $value['tags'] : []; + } + + $this->doDelete($ids); + } + + /** + * {@inheritdoc} + */ + public function commit(): bool + { + $ok = true; + $byLifetime = (self::$mergeByLifetime)($this->deferred, $expiredIds, \Closure::fromCallable([$this, 'getId']), self::TAGS_PREFIX, $this->defaultLifetime); + $retry = $this->deferred = []; + + if ($expiredIds) { + // Tags are not cleaned up in this case, however that is done on invalidateTags(). + $this->doDelete($expiredIds); + } + foreach ($byLifetime as $lifetime => $values) { + try { + $values = $this->extractTagData($values, $addTagData, $removeTagData); + $e = $this->doSave($values, $lifetime, $addTagData, $removeTagData); + } catch (\Exception $e) { + } + if (true === $e || [] === $e) { + continue; + } + if (\is_array($e) || 1 === \count($values)) { + foreach (\is_array($e) ? $e : array_keys($values) as $id) { + $ok = false; + $v = $values[$id]; + $type = get_debug_type($v); + $message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.'); + CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => get_debug_type($this)]); + } + } else { + foreach ($values as $id => $v) { + $retry[$lifetime][] = $id; + } + } + } + + // When bulk-save failed, retry each item individually + foreach ($retry as $lifetime => $ids) { + foreach ($ids as $id) { + try { + $v = $byLifetime[$lifetime][$id]; + $values = $this->extractTagData([$id => $v], $addTagData, $removeTagData); + $e = $this->doSave($values, $lifetime, $addTagData, $removeTagData); + } catch (\Exception $e) { + } + if (true === $e || [] === $e) { + continue; + } + $ok = false; + $type = get_debug_type($v); + $message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.'); + CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => get_debug_type($this)]); + } + } + + return $ok; + } + + /** + * {@inheritdoc} + */ + public function deleteItems(array $keys): bool + { + if (!$keys) { + return true; + } + + $ok = true; + $ids = []; + $tagData = []; + + foreach ($keys as $key) { + $ids[$key] = $this->getId($key); + unset($this->deferred[$key]); + } + + try { + foreach ($this->doDeleteYieldTags(array_values($ids)) as $id => $tags) { + foreach ($tags as $tag) { + $tagData[$this->getId(self::TAGS_PREFIX.$tag)][] = $id; + } + } + } catch (\Exception $e) { + $ok = false; + } + + try { + if ((!$tagData || $this->doDeleteTagRelations($tagData)) && $ok) { + return true; + } + } catch (\Exception $e) { + } + + // When bulk-delete failed, retry each item individually + foreach ($ids as $key => $id) { + try { + $e = null; + if ($this->doDelete([$id])) { + continue; + } + } catch (\Exception $e) { + } + $message = 'Failed to delete key "{key}"'.($e instanceof \Exception ? ': '.$e->getMessage() : '.'); + CacheItem::log($this->logger, $message, ['key' => $key, 'exception' => $e, 'cache-adapter' => get_debug_type($this)]); + $ok = false; + } + + return $ok; + } + + /** + * {@inheritdoc} + */ + public function invalidateTags(array $tags) + { + if (empty($tags)) { + return false; + } + + $tagIds = []; + foreach (array_unique($tags) as $tag) { + $tagIds[] = $this->getId(self::TAGS_PREFIX.$tag); + } + + if ($this->doInvalidate($tagIds)) { + return true; + } + + return false; + } + + /** + * Extracts tags operation data from $values set in mergeByLifetime, and returns values without it. + */ + private function extractTagData(array $values, ?array &$addTagData, ?array &$removeTagData): array + { + $addTagData = $removeTagData = []; + foreach ($values as $id => $value) { + foreach ($value['tag-operations']['add'] as $tag => $tagId) { + $addTagData[$tagId][] = $id; + } + + foreach ($value['tag-operations']['remove'] as $tag => $tagId) { + $removeTagData[$tagId][] = $id; + } + + unset($values[$id]['tag-operations']); + } + + return $values; + } +} diff --git a/vendor/symfony/cache/Adapter/AdapterInterface.php b/vendor/symfony/cache/Adapter/AdapterInterface.php new file mode 100644 index 0000000..f8dce86 --- /dev/null +++ b/vendor/symfony/cache/Adapter/AdapterInterface.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\CacheItem; + +// Help opcache.preload discover always-needed symbols +class_exists(CacheItem::class); + +/** + * Interface for adapters managing instances of Symfony's CacheItem. + * + * @author Kévin Dunglas + */ +interface AdapterInterface extends CacheItemPoolInterface +{ + /** + * {@inheritdoc} + * + * @return CacheItem + */ + public function getItem($key); + + /** + * {@inheritdoc} + * + * @return \Traversable + */ + public function getItems(array $keys = []); + + /** + * {@inheritdoc} + * + * @return bool + */ + public function clear(string $prefix = ''); +} diff --git a/vendor/symfony/cache/Adapter/ApcuAdapter.php b/vendor/symfony/cache/Adapter/ApcuAdapter.php new file mode 100644 index 0000000..6fda0ef --- /dev/null +++ b/vendor/symfony/cache/Adapter/ApcuAdapter.php @@ -0,0 +1,131 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\CacheException; +use Symfony\Component\Cache\Marshaller\MarshallerInterface; + +/** + * @author Nicolas Grekas + */ +class ApcuAdapter extends AbstractAdapter +{ + private $marshaller; + + /** + * @throws CacheException if APCu is not enabled + */ + public function __construct(string $namespace = '', int $defaultLifetime = 0, string $version = null, MarshallerInterface $marshaller = null) + { + if (!static::isSupported()) { + throw new CacheException('APCu is not enabled.'); + } + if ('cli' === \PHP_SAPI) { + ini_set('apc.use_request_time', 0); + } + parent::__construct($namespace, $defaultLifetime); + + if (null !== $version) { + CacheItem::validateKey($version); + + if (!apcu_exists($version.'@'.$namespace)) { + $this->doClear($namespace); + apcu_add($version.'@'.$namespace, null); + } + } + + $this->marshaller = $marshaller; + } + + public static function isSupported() + { + return \function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), \FILTER_VALIDATE_BOOLEAN); + } + + /** + * {@inheritdoc} + */ + protected function doFetch(array $ids) + { + $unserializeCallbackHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback'); + try { + $values = []; + foreach (apcu_fetch($ids, $ok) ?: [] as $k => $v) { + if (null !== $v || $ok) { + $values[$k] = null !== $this->marshaller ? $this->marshaller->unmarshall($v) : $v; + } + } + + return $values; + } catch (\Error $e) { + throw new \ErrorException($e->getMessage(), $e->getCode(), \E_ERROR, $e->getFile(), $e->getLine()); + } finally { + ini_set('unserialize_callback_func', $unserializeCallbackHandler); + } + } + + /** + * {@inheritdoc} + */ + protected function doHave(string $id) + { + return apcu_exists($id); + } + + /** + * {@inheritdoc} + */ + protected function doClear(string $namespace) + { + return isset($namespace[0]) && class_exists(\APCuIterator::class, false) && ('cli' !== \PHP_SAPI || filter_var(ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOLEAN)) + ? apcu_delete(new \APCuIterator(sprintf('/^%s/', preg_quote($namespace, '/')), \APC_ITER_KEY)) + : apcu_clear_cache(); + } + + /** + * {@inheritdoc} + */ + protected function doDelete(array $ids) + { + foreach ($ids as $id) { + apcu_delete($id); + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doSave(array $values, int $lifetime) + { + if (null !== $this->marshaller && (!$values = $this->marshaller->marshall($values, $failed))) { + return $failed; + } + + try { + if (false === $failures = apcu_store($values, null, $lifetime)) { + $failures = $values; + } + + return array_keys($failures); + } catch (\Throwable $e) { + if (1 === \count($values)) { + // Workaround https://github.com/krakjoe/apcu/issues/170 + apcu_delete(array_key_first($values)); + } + + throw $e; + } + } +} diff --git a/vendor/symfony/cache/Adapter/ArrayAdapter.php b/vendor/symfony/cache/Adapter/ArrayAdapter.php new file mode 100644 index 0000000..0fa78d0 --- /dev/null +++ b/vendor/symfony/cache/Adapter/ArrayAdapter.php @@ -0,0 +1,404 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Cache\CacheItemInterface; +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerAwareTrait; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\ResettableInterface; +use Symfony\Contracts\Cache\CacheInterface; + +/** + * An in-memory cache storage. + * + * Acts as a least-recently-used (LRU) storage when configured with a maximum number of items. + * + * @author Nicolas Grekas + */ +class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface +{ + use LoggerAwareTrait; + + private $storeSerialized; + private $values = []; + private $expiries = []; + private $defaultLifetime; + private $maxLifetime; + private $maxItems; + + private static $createCacheItem; + + /** + * @param bool $storeSerialized Disabling serialization can lead to cache corruptions when storing mutable values but increases performance otherwise + */ + public function __construct(int $defaultLifetime = 0, bool $storeSerialized = true, float $maxLifetime = 0, int $maxItems = 0) + { + if (0 > $maxLifetime) { + throw new InvalidArgumentException(sprintf('Argument $maxLifetime must be positive, %F passed.', $maxLifetime)); + } + + if (0 > $maxItems) { + throw new InvalidArgumentException(sprintf('Argument $maxItems must be a positive integer, %d passed.', $maxItems)); + } + + $this->defaultLifetime = $defaultLifetime; + $this->storeSerialized = $storeSerialized; + $this->maxLifetime = $maxLifetime; + $this->maxItems = $maxItems; + self::$createCacheItem ?? self::$createCacheItem = \Closure::bind( + static function ($key, $value, $isHit) { + $item = new CacheItem(); + $item->key = $key; + $item->value = $value; + $item->isHit = $isHit; + + return $item; + }, + null, + CacheItem::class + ); + } + + /** + * {@inheritdoc} + */ + public function get(string $key, callable $callback, float $beta = null, array &$metadata = null) + { + $item = $this->getItem($key); + $metadata = $item->getMetadata(); + + // ArrayAdapter works in memory, we don't care about stampede protection + if (\INF === $beta || !$item->isHit()) { + $save = true; + $this->save($item->set($callback($item, $save))); + } + + return $item->get(); + } + + /** + * {@inheritdoc} + */ + public function delete(string $key): bool + { + return $this->deleteItem($key); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function hasItem($key) + { + if (\is_string($key) && isset($this->expiries[$key]) && $this->expiries[$key] > microtime(true)) { + if ($this->maxItems) { + // Move the item last in the storage + $value = $this->values[$key]; + unset($this->values[$key]); + $this->values[$key] = $value; + } + + return true; + } + \assert('' !== CacheItem::validateKey($key)); + + return isset($this->expiries[$key]) && !$this->deleteItem($key); + } + + /** + * {@inheritdoc} + */ + public function getItem($key) + { + if (!$isHit = $this->hasItem($key)) { + $value = null; + + if (!$this->maxItems) { + // Track misses in non-LRU mode only + $this->values[$key] = null; + } + } else { + $value = $this->storeSerialized ? $this->unfreeze($key, $isHit) : $this->values[$key]; + } + + return (self::$createCacheItem)($key, $value, $isHit); + } + + /** + * {@inheritdoc} + */ + public function getItems(array $keys = []) + { + \assert(self::validateKeys($keys)); + + return $this->generateItems($keys, microtime(true), self::$createCacheItem); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function deleteItem($key) + { + \assert('' !== CacheItem::validateKey($key)); + unset($this->values[$key], $this->expiries[$key]); + + return true; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function deleteItems(array $keys) + { + foreach ($keys as $key) { + $this->deleteItem($key); + } + + return true; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function save(CacheItemInterface $item) + { + if (!$item instanceof CacheItem) { + return false; + } + $item = (array) $item; + $key = $item["\0*\0key"]; + $value = $item["\0*\0value"]; + $expiry = $item["\0*\0expiry"]; + + $now = microtime(true); + + if (0 === $expiry) { + $expiry = \PHP_INT_MAX; + } + + if (null !== $expiry && $expiry <= $now) { + $this->deleteItem($key); + + return true; + } + if ($this->storeSerialized && null === $value = $this->freeze($value, $key)) { + return false; + } + if (null === $expiry && 0 < $this->defaultLifetime) { + $expiry = $this->defaultLifetime; + $expiry = $now + ($expiry > ($this->maxLifetime ?: $expiry) ? $this->maxLifetime : $expiry); + } elseif ($this->maxLifetime && (null === $expiry || $expiry > $now + $this->maxLifetime)) { + $expiry = $now + $this->maxLifetime; + } + + if ($this->maxItems) { + unset($this->values[$key]); + + // Iterate items and vacuum expired ones while we are at it + foreach ($this->values as $k => $v) { + if ($this->expiries[$k] > $now && \count($this->values) < $this->maxItems) { + break; + } + + unset($this->values[$k], $this->expiries[$k]); + } + } + + $this->values[$key] = $value; + $this->expiries[$key] = $expiry ?? \PHP_INT_MAX; + + return true; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function saveDeferred(CacheItemInterface $item) + { + return $this->save($item); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function commit() + { + return true; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function clear(string $prefix = '') + { + if ('' !== $prefix) { + $now = microtime(true); + + foreach ($this->values as $key => $value) { + if (!isset($this->expiries[$key]) || $this->expiries[$key] <= $now || 0 === strpos($key, $prefix)) { + unset($this->values[$key], $this->expiries[$key]); + } + } + + if ($this->values) { + return true; + } + } + + $this->values = $this->expiries = []; + + return true; + } + + /** + * Returns all cached values, with cache miss as null. + * + * @return array + */ + public function getValues() + { + if (!$this->storeSerialized) { + return $this->values; + } + + $values = $this->values; + foreach ($values as $k => $v) { + if (null === $v || 'N;' === $v) { + continue; + } + if (!\is_string($v) || !isset($v[2]) || ':' !== $v[1]) { + $values[$k] = serialize($v); + } + } + + return $values; + } + + /** + * {@inheritdoc} + */ + public function reset() + { + $this->clear(); + } + + private function generateItems(array $keys, float $now, \Closure $f): \Generator + { + foreach ($keys as $i => $key) { + if (!$isHit = isset($this->expiries[$key]) && ($this->expiries[$key] > $now || !$this->deleteItem($key))) { + $value = null; + + if (!$this->maxItems) { + // Track misses in non-LRU mode only + $this->values[$key] = null; + } + } else { + if ($this->maxItems) { + // Move the item last in the storage + $value = $this->values[$key]; + unset($this->values[$key]); + $this->values[$key] = $value; + } + + $value = $this->storeSerialized ? $this->unfreeze($key, $isHit) : $this->values[$key]; + } + unset($keys[$i]); + + yield $key => $f($key, $value, $isHit); + } + + foreach ($keys as $key) { + yield $key => $f($key, null, false); + } + } + + private function freeze($value, string $key) + { + if (null === $value) { + return 'N;'; + } + if (\is_string($value)) { + // Serialize strings if they could be confused with serialized objects or arrays + if ('N;' === $value || (isset($value[2]) && ':' === $value[1])) { + return serialize($value); + } + } elseif (!is_scalar($value)) { + try { + $serialized = serialize($value); + } catch (\Exception $e) { + unset($this->values[$key]); + $type = get_debug_type($value); + $message = sprintf('Failed to save key "{key}" of type %s: %s', $type, $e->getMessage()); + CacheItem::log($this->logger, $message, ['key' => $key, 'exception' => $e, 'cache-adapter' => get_debug_type($this)]); + + return; + } + // Keep value serialized if it contains any objects or any internal references + if ('C' === $serialized[0] || 'O' === $serialized[0] || preg_match('/;[OCRr]:[1-9]/', $serialized)) { + return $serialized; + } + } + + return $value; + } + + private function unfreeze(string $key, bool &$isHit) + { + if ('N;' === $value = $this->values[$key]) { + return null; + } + if (\is_string($value) && isset($value[2]) && ':' === $value[1]) { + try { + $value = unserialize($value); + } catch (\Exception $e) { + CacheItem::log($this->logger, 'Failed to unserialize key "{key}": '.$e->getMessage(), ['key' => $key, 'exception' => $e, 'cache-adapter' => get_debug_type($this)]); + $value = false; + } + if (false === $value) { + $value = null; + $isHit = false; + + if (!$this->maxItems) { + $this->values[$key] = null; + } + } + } + + return $value; + } + + private function validateKeys(array $keys): bool + { + foreach ($keys as $key) { + if (!\is_string($key) || !isset($this->expiries[$key])) { + CacheItem::validateKey($key); + } + } + + return true; + } +} diff --git a/vendor/symfony/cache/Adapter/ChainAdapter.php b/vendor/symfony/cache/Adapter/ChainAdapter.php new file mode 100644 index 0000000..5680697 --- /dev/null +++ b/vendor/symfony/cache/Adapter/ChainAdapter.php @@ -0,0 +1,333 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Cache\CacheItemInterface; +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Cache\Traits\ContractsTrait; +use Symfony\Contracts\Cache\CacheInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * Chains several adapters together. + * + * Cached items are fetched from the first adapter having them in its data store. + * They are saved and deleted in all adapters at once. + * + * @author Kévin Dunglas + */ +class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface +{ + use ContractsTrait; + + private $adapters = []; + private $adapterCount; + private $defaultLifetime; + + private static $syncItem; + + /** + * @param CacheItemPoolInterface[] $adapters The ordered list of adapters used to fetch cached items + * @param int $defaultLifetime The default lifetime of items propagated from lower adapters to upper ones + */ + public function __construct(array $adapters, int $defaultLifetime = 0) + { + if (!$adapters) { + throw new InvalidArgumentException('At least one adapter must be specified.'); + } + + foreach ($adapters as $adapter) { + if (!$adapter instanceof CacheItemPoolInterface) { + throw new InvalidArgumentException(sprintf('The class "%s" does not implement the "%s" interface.', get_debug_type($adapter), CacheItemPoolInterface::class)); + } + if (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) && $adapter instanceof ApcuAdapter && !filter_var(ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOLEAN)) { + continue; // skip putting APCu in the chain when the backend is disabled + } + + if ($adapter instanceof AdapterInterface) { + $this->adapters[] = $adapter; + } else { + $this->adapters[] = new ProxyAdapter($adapter); + } + } + $this->adapterCount = \count($this->adapters); + $this->defaultLifetime = $defaultLifetime; + + self::$syncItem ?? self::$syncItem = \Closure::bind( + static function ($sourceItem, $item, $defaultLifetime, $sourceMetadata = null) { + $sourceItem->isTaggable = false; + $sourceMetadata = $sourceMetadata ?? $sourceItem->metadata; + unset($sourceMetadata[CacheItem::METADATA_TAGS]); + + $item->value = $sourceItem->value; + $item->isHit = $sourceItem->isHit; + $item->metadata = $item->newMetadata = $sourceItem->metadata = $sourceMetadata; + + if (isset($item->metadata[CacheItem::METADATA_EXPIRY])) { + $item->expiresAt(\DateTime::createFromFormat('U.u', sprintf('%.6F', $item->metadata[CacheItem::METADATA_EXPIRY]))); + } elseif (0 < $defaultLifetime) { + $item->expiresAfter($defaultLifetime); + } + + return $item; + }, + null, + CacheItem::class + ); + } + + /** + * {@inheritdoc} + */ + public function get(string $key, callable $callback, float $beta = null, array &$metadata = null) + { + $lastItem = null; + $i = 0; + $wrap = function (CacheItem $item = null) use ($key, $callback, $beta, &$wrap, &$i, &$lastItem, &$metadata) { + $adapter = $this->adapters[$i]; + if (isset($this->adapters[++$i])) { + $callback = $wrap; + $beta = \INF === $beta ? \INF : 0; + } + if ($adapter instanceof CacheInterface) { + $value = $adapter->get($key, $callback, $beta, $metadata); + } else { + $value = $this->doGet($adapter, $key, $callback, $beta, $metadata); + } + if (null !== $item) { + (self::$syncItem)($lastItem = $lastItem ?? $item, $item, $this->defaultLifetime, $metadata); + } + + return $value; + }; + + return $wrap(); + } + + /** + * {@inheritdoc} + */ + public function getItem($key) + { + $syncItem = self::$syncItem; + $misses = []; + + foreach ($this->adapters as $i => $adapter) { + $item = $adapter->getItem($key); + + if ($item->isHit()) { + while (0 <= --$i) { + $this->adapters[$i]->save($syncItem($item, $misses[$i], $this->defaultLifetime)); + } + + return $item; + } + + $misses[$i] = $item; + } + + return $item; + } + + /** + * {@inheritdoc} + */ + public function getItems(array $keys = []) + { + return $this->generateItems($this->adapters[0]->getItems($keys), 0); + } + + private function generateItems(iterable $items, int $adapterIndex): \Generator + { + $missing = []; + $misses = []; + $nextAdapterIndex = $adapterIndex + 1; + $nextAdapter = $this->adapters[$nextAdapterIndex] ?? null; + + foreach ($items as $k => $item) { + if (!$nextAdapter || $item->isHit()) { + yield $k => $item; + } else { + $missing[] = $k; + $misses[$k] = $item; + } + } + + if ($missing) { + $syncItem = self::$syncItem; + $adapter = $this->adapters[$adapterIndex]; + $items = $this->generateItems($nextAdapter->getItems($missing), $nextAdapterIndex); + + foreach ($items as $k => $item) { + if ($item->isHit()) { + $adapter->save($syncItem($item, $misses[$k], $this->defaultLifetime)); + } + + yield $k => $item; + } + } + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function hasItem($key) + { + foreach ($this->adapters as $adapter) { + if ($adapter->hasItem($key)) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function clear(string $prefix = '') + { + $cleared = true; + $i = $this->adapterCount; + + while ($i--) { + if ($this->adapters[$i] instanceof AdapterInterface) { + $cleared = $this->adapters[$i]->clear($prefix) && $cleared; + } else { + $cleared = $this->adapters[$i]->clear() && $cleared; + } + } + + return $cleared; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function deleteItem($key) + { + $deleted = true; + $i = $this->adapterCount; + + while ($i--) { + $deleted = $this->adapters[$i]->deleteItem($key) && $deleted; + } + + return $deleted; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function deleteItems(array $keys) + { + $deleted = true; + $i = $this->adapterCount; + + while ($i--) { + $deleted = $this->adapters[$i]->deleteItems($keys) && $deleted; + } + + return $deleted; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function save(CacheItemInterface $item) + { + $saved = true; + $i = $this->adapterCount; + + while ($i--) { + $saved = $this->adapters[$i]->save($item) && $saved; + } + + return $saved; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function saveDeferred(CacheItemInterface $item) + { + $saved = true; + $i = $this->adapterCount; + + while ($i--) { + $saved = $this->adapters[$i]->saveDeferred($item) && $saved; + } + + return $saved; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function commit() + { + $committed = true; + $i = $this->adapterCount; + + while ($i--) { + $committed = $this->adapters[$i]->commit() && $committed; + } + + return $committed; + } + + /** + * {@inheritdoc} + */ + public function prune() + { + $pruned = true; + + foreach ($this->adapters as $adapter) { + if ($adapter instanceof PruneableInterface) { + $pruned = $adapter->prune() && $pruned; + } + } + + return $pruned; + } + + /** + * {@inheritdoc} + */ + public function reset() + { + foreach ($this->adapters as $adapter) { + if ($adapter instanceof ResetInterface) { + $adapter->reset(); + } + } + } +} diff --git a/vendor/symfony/cache/Adapter/CouchbaseBucketAdapter.php b/vendor/symfony/cache/Adapter/CouchbaseBucketAdapter.php new file mode 100644 index 0000000..36d5249 --- /dev/null +++ b/vendor/symfony/cache/Adapter/CouchbaseBucketAdapter.php @@ -0,0 +1,252 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Symfony\Component\Cache\Exception\CacheException; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Marshaller\DefaultMarshaller; +use Symfony\Component\Cache\Marshaller\MarshallerInterface; + +/** + * @author Antonio Jose Cerezo Aranda + */ +class CouchbaseBucketAdapter extends AbstractAdapter +{ + private const THIRTY_DAYS_IN_SECONDS = 2592000; + private const MAX_KEY_LENGTH = 250; + private const KEY_NOT_FOUND = 13; + private const VALID_DSN_OPTIONS = [ + 'operationTimeout', + 'configTimeout', + 'configNodeTimeout', + 'n1qlTimeout', + 'httpTimeout', + 'configDelay', + 'htconfigIdleTimeout', + 'durabilityInterval', + 'durabilityTimeout', + ]; + + private $bucket; + private $marshaller; + + public function __construct(\CouchbaseBucket $bucket, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null) + { + if (!static::isSupported()) { + throw new CacheException('Couchbase >= 2.6.0 < 3.0.0 is required.'); + } + + $this->maxIdLength = static::MAX_KEY_LENGTH; + + $this->bucket = $bucket; + + parent::__construct($namespace, $defaultLifetime); + $this->enableVersioning(); + $this->marshaller = $marshaller ?? new DefaultMarshaller(); + } + + /** + * @param array|string $servers + */ + public static function createConnection($servers, array $options = []): \CouchbaseBucket + { + if (\is_string($servers)) { + $servers = [$servers]; + } elseif (!\is_array($servers)) { + throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be array or string, "%s" given.', __METHOD__, get_debug_type($servers))); + } + + if (!static::isSupported()) { + throw new CacheException('Couchbase >= 2.6.0 < 3.0.0 is required.'); + } + + set_error_handler(function ($type, $msg, $file, $line) { throw new \ErrorException($msg, 0, $type, $file, $line); }); + + $dsnPattern = '/^(?couchbase(?:s)?)\:\/\/(?:(?[^\:]+)\:(?[^\@]{6,})@)?' + .'(?[^\:]+(?:\:\d+)?)(?:\/(?[^\?]+))(?:\?(?.*))?$/i'; + + $newServers = []; + $protocol = 'couchbase'; + try { + $options = self::initOptions($options); + $username = $options['username']; + $password = $options['password']; + + foreach ($servers as $dsn) { + if (0 !== strpos($dsn, 'couchbase:')) { + throw new InvalidArgumentException(sprintf('Invalid Couchbase DSN: "%s" does not start with "couchbase:".', $dsn)); + } + + preg_match($dsnPattern, $dsn, $matches); + + $username = $matches['username'] ?: $username; + $password = $matches['password'] ?: $password; + $protocol = $matches['protocol'] ?: $protocol; + + if (isset($matches['options'])) { + $optionsInDsn = self::getOptions($matches['options']); + + foreach ($optionsInDsn as $parameter => $value) { + $options[$parameter] = $value; + } + } + + $newServers[] = $matches['host']; + } + + $connectionString = $protocol.'://'.implode(',', $newServers); + + $client = new \CouchbaseCluster($connectionString); + $client->authenticateAs($username, $password); + + $bucket = $client->openBucket($matches['bucketName']); + + unset($options['username'], $options['password']); + foreach ($options as $option => $value) { + if (!empty($value)) { + $bucket->$option = $value; + } + } + + return $bucket; + } finally { + restore_error_handler(); + } + } + + public static function isSupported(): bool + { + return \extension_loaded('couchbase') && version_compare(phpversion('couchbase'), '2.6.0', '>=') && version_compare(phpversion('couchbase'), '3.0', '<'); + } + + private static function getOptions(string $options): array + { + $results = []; + $optionsInArray = explode('&', $options); + + foreach ($optionsInArray as $option) { + [$key, $value] = explode('=', $option); + + if (\in_array($key, static::VALID_DSN_OPTIONS, true)) { + $results[$key] = $value; + } + } + + return $results; + } + + private static function initOptions(array $options): array + { + $options['username'] = $options['username'] ?? ''; + $options['password'] = $options['password'] ?? ''; + $options['operationTimeout'] = $options['operationTimeout'] ?? 0; + $options['configTimeout'] = $options['configTimeout'] ?? 0; + $options['configNodeTimeout'] = $options['configNodeTimeout'] ?? 0; + $options['n1qlTimeout'] = $options['n1qlTimeout'] ?? 0; + $options['httpTimeout'] = $options['httpTimeout'] ?? 0; + $options['configDelay'] = $options['configDelay'] ?? 0; + $options['htconfigIdleTimeout'] = $options['htconfigIdleTimeout'] ?? 0; + $options['durabilityInterval'] = $options['durabilityInterval'] ?? 0; + $options['durabilityTimeout'] = $options['durabilityTimeout'] ?? 0; + + return $options; + } + + /** + * {@inheritdoc} + */ + protected function doFetch(array $ids) + { + $resultsCouchbase = $this->bucket->get($ids); + + $results = []; + foreach ($resultsCouchbase as $key => $value) { + if (null !== $value->error) { + continue; + } + $results[$key] = $this->marshaller->unmarshall($value->value); + } + + return $results; + } + + /** + * {@inheritdoc} + */ + protected function doHave(string $id): bool + { + return false !== $this->bucket->get($id); + } + + /** + * {@inheritdoc} + */ + protected function doClear(string $namespace): bool + { + if ('' === $namespace) { + $this->bucket->manager()->flush(); + + return true; + } + + return false; + } + + /** + * {@inheritdoc} + */ + protected function doDelete(array $ids): bool + { + $results = $this->bucket->remove(array_values($ids)); + + foreach ($results as $key => $result) { + if (null !== $result->error && static::KEY_NOT_FOUND !== $result->error->getCode()) { + continue; + } + unset($results[$key]); + } + + return 0 === \count($results); + } + + /** + * {@inheritdoc} + */ + protected function doSave(array $values, int $lifetime) + { + if (!$values = $this->marshaller->marshall($values, $failed)) { + return $failed; + } + + $lifetime = $this->normalizeExpiry($lifetime); + + $ko = []; + foreach ($values as $key => $value) { + $result = $this->bucket->upsert($key, $value, ['expiry' => $lifetime]); + + if (null !== $result->error) { + $ko[$key] = $result; + } + } + + return [] === $ko ? true : $ko; + } + + private function normalizeExpiry(int $expiry): int + { + if ($expiry && $expiry > static::THIRTY_DAYS_IN_SECONDS) { + $expiry += time(); + } + + return $expiry; + } +} diff --git a/vendor/symfony/cache/Adapter/CouchbaseCollectionAdapter.php b/vendor/symfony/cache/Adapter/CouchbaseCollectionAdapter.php new file mode 100644 index 0000000..79f6485 --- /dev/null +++ b/vendor/symfony/cache/Adapter/CouchbaseCollectionAdapter.php @@ -0,0 +1,222 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Couchbase\Bucket; +use Couchbase\Cluster; +use Couchbase\ClusterOptions; +use Couchbase\Collection; +use Couchbase\DocumentNotFoundException; +use Couchbase\UpsertOptions; +use Symfony\Component\Cache\Exception\CacheException; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Marshaller\DefaultMarshaller; +use Symfony\Component\Cache\Marshaller\MarshallerInterface; + +/** + * @author Antonio Jose Cerezo Aranda + */ +class CouchbaseCollectionAdapter extends AbstractAdapter +{ + private const MAX_KEY_LENGTH = 250; + + /** @var Collection */ + private $connection; + private $marshaller; + + public function __construct(Collection $connection, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null) + { + if (!static::isSupported()) { + throw new CacheException('Couchbase >= 3.0.0 < 4.0.0 is required.'); + } + + $this->maxIdLength = static::MAX_KEY_LENGTH; + + $this->connection = $connection; + + parent::__construct($namespace, $defaultLifetime); + $this->enableVersioning(); + $this->marshaller = $marshaller ?? new DefaultMarshaller(); + } + + /** + * @param array|string $dsn + * + * @return Bucket|Collection + */ + public static function createConnection($dsn, array $options = []) + { + if (\is_string($dsn)) { + $dsn = [$dsn]; + } elseif (!\is_array($dsn)) { + throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be array or string, "%s" given.', __METHOD__, get_debug_type($dsn))); + } + + if (!static::isSupported()) { + throw new CacheException('Couchbase >= 3.0.0 < 4.0.0 is required.'); + } + + set_error_handler(function ($type, $msg, $file, $line): bool { throw new \ErrorException($msg, 0, $type, $file, $line); }); + + $dsnPattern = '/^(?couchbase(?:s)?)\:\/\/(?:(?[^\:]+)\:(?[^\@]{6,})@)?' + .'(?[^\:]+(?:\:\d+)?)(?:\/(?[^\/\?]+))(?:(?:\/(?[^\/]+))' + .'(?:\/(?[^\/\?]+)))?(?:\/)?(?:\?(?.*))?$/i'; + + $newServers = []; + $protocol = 'couchbase'; + try { + $username = $options['username'] ?? ''; + $password = $options['password'] ?? ''; + + foreach ($dsn as $server) { + if (0 !== strpos($server, 'couchbase:')) { + throw new InvalidArgumentException(sprintf('Invalid Couchbase DSN: "%s" does not start with "couchbase:".', $server)); + } + + preg_match($dsnPattern, $server, $matches); + + $username = $matches['username'] ?: $username; + $password = $matches['password'] ?: $password; + $protocol = $matches['protocol'] ?: $protocol; + + if (isset($matches['options'])) { + $optionsInDsn = self::getOptions($matches['options']); + + foreach ($optionsInDsn as $parameter => $value) { + $options[$parameter] = $value; + } + } + + $newServers[] = $matches['host']; + } + + $option = isset($matches['options']) ? '?'.$matches['options'] : ''; + $connectionString = $protocol.'://'.implode(',', $newServers).$option; + + $clusterOptions = new ClusterOptions(); + $clusterOptions->credentials($username, $password); + + $client = new Cluster($connectionString, $clusterOptions); + + $bucket = $client->bucket($matches['bucketName']); + $collection = $bucket->defaultCollection(); + if (!empty($matches['scopeName'])) { + $scope = $bucket->scope($matches['scopeName']); + $collection = $scope->collection($matches['collectionName']); + } + + return $collection; + } finally { + restore_error_handler(); + } + } + + public static function isSupported(): bool + { + return \extension_loaded('couchbase') && version_compare(phpversion('couchbase'), '3.0.5', '>=') && version_compare(phpversion('couchbase'), '4.0', '<'); + } + + private static function getOptions(string $options): array + { + $results = []; + $optionsInArray = explode('&', $options); + + foreach ($optionsInArray as $option) { + [$key, $value] = explode('=', $option); + + $results[$key] = $value; + } + + return $results; + } + + /** + * {@inheritdoc} + */ + protected function doFetch(array $ids): array + { + $results = []; + foreach ($ids as $id) { + try { + $resultCouchbase = $this->connection->get($id); + } catch (DocumentNotFoundException $exception) { + continue; + } + + $content = $resultCouchbase->value ?? $resultCouchbase->content(); + + $results[$id] = $this->marshaller->unmarshall($content); + } + + return $results; + } + + /** + * {@inheritdoc} + */ + protected function doHave($id): bool + { + return $this->connection->exists($id)->exists(); + } + + /** + * {@inheritdoc} + */ + protected function doClear($namespace): bool + { + return false; + } + + /** + * {@inheritdoc} + */ + protected function doDelete(array $ids): bool + { + $idsErrors = []; + foreach ($ids as $id) { + try { + $result = $this->connection->remove($id); + + if (null === $result->mutationToken()) { + $idsErrors[] = $id; + } + } catch (DocumentNotFoundException $exception) { + } + } + + return 0 === \count($idsErrors); + } + + /** + * {@inheritdoc} + */ + protected function doSave(array $values, $lifetime) + { + if (!$values = $this->marshaller->marshall($values, $failed)) { + return $failed; + } + + $upsertOptions = new UpsertOptions(); + $upsertOptions->expiry($lifetime); + + $ko = []; + foreach ($values as $key => $value) { + try { + $this->connection->upsert($key, $value, $upsertOptions); + } catch (\Exception $exception) { + $ko[$key] = ''; + } + } + + return [] === $ko ? true : $ko; + } +} diff --git a/vendor/symfony/cache/Adapter/DoctrineAdapter.php b/vendor/symfony/cache/Adapter/DoctrineAdapter.php new file mode 100644 index 0000000..efa30c8 --- /dev/null +++ b/vendor/symfony/cache/Adapter/DoctrineAdapter.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Doctrine\Common\Cache\CacheProvider; +use Doctrine\Common\Cache\Psr6\CacheAdapter; + +/** + * @author Nicolas Grekas + * + * @deprecated Since Symfony 5.4, use Doctrine\Common\Cache\Psr6\CacheAdapter instead + */ +class DoctrineAdapter extends AbstractAdapter +{ + private $provider; + + public function __construct(CacheProvider $provider, string $namespace = '', int $defaultLifetime = 0) + { + trigger_deprecation('symfony/cache', '5.4', '"%s" is deprecated, use "%s" instead.', __CLASS__, CacheAdapter::class); + + parent::__construct('', $defaultLifetime); + $this->provider = $provider; + $provider->setNamespace($namespace); + } + + /** + * {@inheritdoc} + */ + public function reset() + { + parent::reset(); + $this->provider->setNamespace($this->provider->getNamespace()); + } + + /** + * {@inheritdoc} + */ + protected function doFetch(array $ids) + { + $unserializeCallbackHandler = ini_set('unserialize_callback_func', parent::class.'::handleUnserializeCallback'); + try { + return $this->provider->fetchMultiple($ids); + } catch (\Error $e) { + $trace = $e->getTrace(); + + if (isset($trace[0]['function']) && !isset($trace[0]['class'])) { + switch ($trace[0]['function']) { + case 'unserialize': + case 'apcu_fetch': + case 'apc_fetch': + throw new \ErrorException($e->getMessage(), $e->getCode(), \E_ERROR, $e->getFile(), $e->getLine()); + } + } + + throw $e; + } finally { + ini_set('unserialize_callback_func', $unserializeCallbackHandler); + } + } + + /** + * {@inheritdoc} + */ + protected function doHave(string $id) + { + return $this->provider->contains($id); + } + + /** + * {@inheritdoc} + */ + protected function doClear(string $namespace) + { + $namespace = $this->provider->getNamespace(); + + return isset($namespace[0]) + ? $this->provider->deleteAll() + : $this->provider->flushAll(); + } + + /** + * {@inheritdoc} + */ + protected function doDelete(array $ids) + { + $ok = true; + foreach ($ids as $id) { + $ok = $this->provider->delete($id) && $ok; + } + + return $ok; + } + + /** + * {@inheritdoc} + */ + protected function doSave(array $values, int $lifetime) + { + return $this->provider->saveMultiple($values, $lifetime); + } +} diff --git a/vendor/symfony/cache/Adapter/DoctrineDbalAdapter.php b/vendor/symfony/cache/Adapter/DoctrineDbalAdapter.php new file mode 100644 index 0000000..73f0ea6 --- /dev/null +++ b/vendor/symfony/cache/Adapter/DoctrineDbalAdapter.php @@ -0,0 +1,397 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Driver\ServerInfoAwareConnection; +use Doctrine\DBAL\DriverManager; +use Doctrine\DBAL\Exception as DBALException; +use Doctrine\DBAL\Exception\TableNotFoundException; +use Doctrine\DBAL\ParameterType; +use Doctrine\DBAL\Schema\Schema; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Marshaller\DefaultMarshaller; +use Symfony\Component\Cache\Marshaller\MarshallerInterface; +use Symfony\Component\Cache\PruneableInterface; + +class DoctrineDbalAdapter extends AbstractAdapter implements PruneableInterface +{ + protected $maxIdLength = 255; + + private $marshaller; + private $conn; + private $platformName; + private $serverVersion; + private $table = 'cache_items'; + private $idCol = 'item_id'; + private $dataCol = 'item_data'; + private $lifetimeCol = 'item_lifetime'; + private $timeCol = 'item_time'; + private $namespace; + + /** + * You can either pass an existing database Doctrine DBAL Connection or + * a DSN string that will be used to connect to the database. + * + * The cache table is created automatically when possible. + * Otherwise, use the createTable() method. + * + * List of available options: + * * db_table: The name of the table [default: cache_items] + * * db_id_col: The column where to store the cache id [default: item_id] + * * db_data_col: The column where to store the cache data [default: item_data] + * * db_lifetime_col: The column where to store the lifetime [default: item_lifetime] + * * db_time_col: The column where to store the timestamp [default: item_time] + * + * @param Connection|string $connOrDsn + * + * @throws InvalidArgumentException When namespace contains invalid characters + */ + public function __construct($connOrDsn, string $namespace = '', int $defaultLifetime = 0, array $options = [], MarshallerInterface $marshaller = null) + { + if (isset($namespace[0]) && preg_match('#[^-+.A-Za-z0-9]#', $namespace, $match)) { + throw new InvalidArgumentException(sprintf('Namespace contains "%s" but only characters in [-+.A-Za-z0-9] are allowed.', $match[0])); + } + + if ($connOrDsn instanceof Connection) { + $this->conn = $connOrDsn; + } elseif (\is_string($connOrDsn)) { + if (!class_exists(DriverManager::class)) { + throw new InvalidArgumentException(sprintf('Failed to parse the DSN "%s". Try running "composer require doctrine/dbal".', $connOrDsn)); + } + $this->conn = DriverManager::getConnection(['url' => $connOrDsn]); + } else { + throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be "%s" or string, "%s" given.', __METHOD__, Connection::class, get_debug_type($connOrDsn))); + } + + $this->table = $options['db_table'] ?? $this->table; + $this->idCol = $options['db_id_col'] ?? $this->idCol; + $this->dataCol = $options['db_data_col'] ?? $this->dataCol; + $this->lifetimeCol = $options['db_lifetime_col'] ?? $this->lifetimeCol; + $this->timeCol = $options['db_time_col'] ?? $this->timeCol; + $this->namespace = $namespace; + $this->marshaller = $marshaller ?? new DefaultMarshaller(); + + parent::__construct($namespace, $defaultLifetime); + } + + /** + * Creates the table to store cache items which can be called once for setup. + * + * Cache ID are saved in a column of maximum length 255. Cache data is + * saved in a BLOB. + * + * @throws DBALException When the table already exists + */ + public function createTable() + { + $schema = new Schema(); + $this->addTableToSchema($schema); + + foreach ($schema->toSql($this->conn->getDatabasePlatform()) as $sql) { + $this->conn->executeStatement($sql); + } + } + + /** + * {@inheritdoc} + */ + public function configureSchema(Schema $schema, Connection $forConnection): void + { + // only update the schema for this connection + if ($forConnection !== $this->conn) { + return; + } + + if ($schema->hasTable($this->table)) { + return; + } + + $this->addTableToSchema($schema); + } + + /** + * {@inheritdoc} + */ + public function prune(): bool + { + $deleteSql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol <= ?"; + $params = [time()]; + $paramTypes = [ParameterType::INTEGER]; + + if ('' !== $this->namespace) { + $deleteSql .= " AND $this->idCol LIKE ?"; + $params[] = sprintf('%s%%', $this->namespace); + $paramTypes[] = ParameterType::STRING; + } + + try { + $this->conn->executeStatement($deleteSql, $params, $paramTypes); + } catch (TableNotFoundException $e) { + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doFetch(array $ids): iterable + { + $now = time(); + $expired = []; + + $sql = "SELECT $this->idCol, CASE WHEN $this->lifetimeCol IS NULL OR $this->lifetimeCol + $this->timeCol > ? THEN $this->dataCol ELSE NULL END FROM $this->table WHERE $this->idCol IN (?)"; + $result = $this->conn->executeQuery($sql, [ + $now, + $ids, + ], [ + ParameterType::INTEGER, + Connection::PARAM_STR_ARRAY, + ])->iterateNumeric(); + + foreach ($result as $row) { + if (null === $row[1]) { + $expired[] = $row[0]; + } else { + yield $row[0] => $this->marshaller->unmarshall(\is_resource($row[1]) ? stream_get_contents($row[1]) : $row[1]); + } + } + + if ($expired) { + $sql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol <= ? AND $this->idCol IN (?)"; + $this->conn->executeStatement($sql, [ + $now, + $expired, + ], [ + ParameterType::INTEGER, + Connection::PARAM_STR_ARRAY, + ]); + } + } + + /** + * {@inheritdoc} + */ + protected function doHave(string $id): bool + { + $sql = "SELECT 1 FROM $this->table WHERE $this->idCol = ? AND ($this->lifetimeCol IS NULL OR $this->lifetimeCol + $this->timeCol > ?)"; + $result = $this->conn->executeQuery($sql, [ + $id, + time(), + ], [ + ParameterType::STRING, + ParameterType::INTEGER, + ]); + + return (bool) $result->fetchOne(); + } + + /** + * {@inheritdoc} + */ + protected function doClear(string $namespace): bool + { + if ('' === $namespace) { + if ('sqlite' === $this->getPlatformName()) { + $sql = "DELETE FROM $this->table"; + } else { + $sql = "TRUNCATE TABLE $this->table"; + } + } else { + $sql = "DELETE FROM $this->table WHERE $this->idCol LIKE '$namespace%'"; + } + + try { + $this->conn->executeStatement($sql); + } catch (TableNotFoundException $e) { + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doDelete(array $ids): bool + { + $sql = "DELETE FROM $this->table WHERE $this->idCol IN (?)"; + try { + $this->conn->executeStatement($sql, [array_values($ids)], [Connection::PARAM_STR_ARRAY]); + } catch (TableNotFoundException $e) { + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doSave(array $values, int $lifetime) + { + if (!$values = $this->marshaller->marshall($values, $failed)) { + return $failed; + } + + $platformName = $this->getPlatformName(); + $insertSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?)"; + + switch (true) { + case 'mysql' === $platformName: + $sql = $insertSql." ON DUPLICATE KEY UPDATE $this->dataCol = VALUES($this->dataCol), $this->lifetimeCol = VALUES($this->lifetimeCol), $this->timeCol = VALUES($this->timeCol)"; + break; + case 'oci' === $platformName: + // DUAL is Oracle specific dummy table + $sql = "MERGE INTO $this->table USING DUAL ON ($this->idCol = ?) ". + "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ". + "WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?"; + break; + case 'sqlsrv' === $platformName && version_compare($this->getServerVersion(), '10', '>='): + // MERGE is only available since SQL Server 2008 and must be terminated by semicolon + // It also requires HOLDLOCK according to http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx + $sql = "MERGE INTO $this->table WITH (HOLDLOCK) USING (SELECT 1 AS dummy) AS src ON ($this->idCol = ?) ". + "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ". + "WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?;"; + break; + case 'sqlite' === $platformName: + $sql = 'INSERT OR REPLACE'.substr($insertSql, 6); + break; + case 'pgsql' === $platformName && version_compare($this->getServerVersion(), '9.5', '>='): + $sql = $insertSql." ON CONFLICT ($this->idCol) DO UPDATE SET ($this->dataCol, $this->lifetimeCol, $this->timeCol) = (EXCLUDED.$this->dataCol, EXCLUDED.$this->lifetimeCol, EXCLUDED.$this->timeCol)"; + break; + default: + $platformName = null; + $sql = "UPDATE $this->table SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ? WHERE $this->idCol = ?"; + break; + } + + $now = time(); + $lifetime = $lifetime ?: null; + try { + $stmt = $this->conn->prepare($sql); + } catch (TableNotFoundException $e) { + if (!$this->conn->isTransactionActive() || \in_array($platformName, ['pgsql', 'sqlite', 'sqlsrv'], true)) { + $this->createTable(); + } + $stmt = $this->conn->prepare($sql); + } + + // $id and $data are defined later in the loop. Binding is done by reference, values are read on execution. + if ('sqlsrv' === $platformName || 'oci' === $platformName) { + $stmt->bindParam(1, $id); + $stmt->bindParam(2, $id); + $stmt->bindParam(3, $data, ParameterType::LARGE_OBJECT); + $stmt->bindValue(4, $lifetime, ParameterType::INTEGER); + $stmt->bindValue(5, $now, ParameterType::INTEGER); + $stmt->bindParam(6, $data, ParameterType::LARGE_OBJECT); + $stmt->bindValue(7, $lifetime, ParameterType::INTEGER); + $stmt->bindValue(8, $now, ParameterType::INTEGER); + } elseif (null !== $platformName) { + $stmt->bindParam(1, $id); + $stmt->bindParam(2, $data, ParameterType::LARGE_OBJECT); + $stmt->bindValue(3, $lifetime, ParameterType::INTEGER); + $stmt->bindValue(4, $now, ParameterType::INTEGER); + } else { + $stmt->bindParam(1, $data, ParameterType::LARGE_OBJECT); + $stmt->bindValue(2, $lifetime, ParameterType::INTEGER); + $stmt->bindValue(3, $now, ParameterType::INTEGER); + $stmt->bindParam(4, $id); + + $insertStmt = $this->conn->prepare($insertSql); + $insertStmt->bindParam(1, $id); + $insertStmt->bindParam(2, $data, ParameterType::LARGE_OBJECT); + $insertStmt->bindValue(3, $lifetime, ParameterType::INTEGER); + $insertStmt->bindValue(4, $now, ParameterType::INTEGER); + } + + foreach ($values as $id => $data) { + try { + $rowCount = $stmt->executeStatement(); + } catch (TableNotFoundException $e) { + if (!$this->conn->isTransactionActive() || \in_array($platformName, ['pgsql', 'sqlite', 'sqlsrv'], true)) { + $this->createTable(); + } + $rowCount = $stmt->executeStatement(); + } + if (null === $platformName && 0 === $rowCount) { + try { + $insertStmt->executeStatement(); + } catch (DBALException $e) { + // A concurrent write won, let it be + } + } + } + + return $failed; + } + + private function getPlatformName(): string + { + if (isset($this->platformName)) { + return $this->platformName; + } + + $platform = $this->conn->getDatabasePlatform(); + + switch (true) { + case $platform instanceof \Doctrine\DBAL\Platforms\MySQLPlatform: + case $platform instanceof \Doctrine\DBAL\Platforms\MySQL57Platform: + return $this->platformName = 'mysql'; + + case $platform instanceof \Doctrine\DBAL\Platforms\SqlitePlatform: + return $this->platformName = 'sqlite'; + + case $platform instanceof \Doctrine\DBAL\Platforms\PostgreSQLPlatform: + case $platform instanceof \Doctrine\DBAL\Platforms\PostgreSQL94Platform: + return $this->platformName = 'pgsql'; + + case $platform instanceof \Doctrine\DBAL\Platforms\OraclePlatform: + return $this->platformName = 'oci'; + + case $platform instanceof \Doctrine\DBAL\Platforms\SQLServerPlatform: + case $platform instanceof \Doctrine\DBAL\Platforms\SQLServer2012Platform: + return $this->platformName = 'sqlsrv'; + + default: + return $this->platformName = \get_class($platform); + } + } + + private function getServerVersion(): string + { + if (isset($this->serverVersion)) { + return $this->serverVersion; + } + + $conn = $this->conn->getWrappedConnection(); + if ($conn instanceof ServerInfoAwareConnection) { + return $this->serverVersion = $conn->getServerVersion(); + } + + return $this->serverVersion = '0'; + } + + private function addTableToSchema(Schema $schema): void + { + $types = [ + 'mysql' => 'binary', + 'sqlite' => 'text', + ]; + + $table = $schema->createTable($this->table); + $table->addColumn($this->idCol, $types[$this->getPlatformName()] ?? 'string', ['length' => 255]); + $table->addColumn($this->dataCol, 'blob', ['length' => 16777215]); + $table->addColumn($this->lifetimeCol, 'integer', ['unsigned' => true, 'notnull' => false]); + $table->addColumn($this->timeCol, 'integer', ['unsigned' => true]); + $table->setPrimaryKey([$this->idCol]); + } +} diff --git a/vendor/symfony/cache/Adapter/FilesystemAdapter.php b/vendor/symfony/cache/Adapter/FilesystemAdapter.php new file mode 100644 index 0000000..7185dd4 --- /dev/null +++ b/vendor/symfony/cache/Adapter/FilesystemAdapter.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Symfony\Component\Cache\Marshaller\DefaultMarshaller; +use Symfony\Component\Cache\Marshaller\MarshallerInterface; +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Cache\Traits\FilesystemTrait; + +class FilesystemAdapter extends AbstractAdapter implements PruneableInterface +{ + use FilesystemTrait; + + public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, MarshallerInterface $marshaller = null) + { + $this->marshaller = $marshaller ?? new DefaultMarshaller(); + parent::__construct('', $defaultLifetime); + $this->init($namespace, $directory); + } +} diff --git a/vendor/symfony/cache/Adapter/FilesystemTagAwareAdapter.php b/vendor/symfony/cache/Adapter/FilesystemTagAwareAdapter.php new file mode 100644 index 0000000..afde843 --- /dev/null +++ b/vendor/symfony/cache/Adapter/FilesystemTagAwareAdapter.php @@ -0,0 +1,239 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Symfony\Component\Cache\Marshaller\MarshallerInterface; +use Symfony\Component\Cache\Marshaller\TagAwareMarshaller; +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Cache\Traits\FilesystemTrait; + +/** + * Stores tag id <> cache id relationship as a symlink, and lookup on invalidation calls. + * + * @author Nicolas Grekas + * @author André Rømcke + */ +class FilesystemTagAwareAdapter extends AbstractTagAwareAdapter implements PruneableInterface +{ + use FilesystemTrait { + doClear as private doClearCache; + doSave as private doSaveCache; + } + + /** + * Folder used for tag symlinks. + */ + private const TAG_FOLDER = 'tags'; + + public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, MarshallerInterface $marshaller = null) + { + $this->marshaller = new TagAwareMarshaller($marshaller); + parent::__construct('', $defaultLifetime); + $this->init($namespace, $directory); + } + + /** + * {@inheritdoc} + */ + protected function doClear(string $namespace) + { + $ok = $this->doClearCache($namespace); + + if ('' !== $namespace) { + return $ok; + } + + set_error_handler(static function () {}); + $chars = '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + + try { + foreach ($this->scanHashDir($this->directory.self::TAG_FOLDER.\DIRECTORY_SEPARATOR) as $dir) { + if (rename($dir, $renamed = substr_replace($dir, bin2hex(random_bytes(4)), -8))) { + $dir = $renamed.\DIRECTORY_SEPARATOR; + } else { + $dir .= \DIRECTORY_SEPARATOR; + $renamed = null; + } + + for ($i = 0; $i < 38; ++$i) { + if (!is_dir($dir.$chars[$i])) { + continue; + } + for ($j = 0; $j < 38; ++$j) { + if (!is_dir($d = $dir.$chars[$i].\DIRECTORY_SEPARATOR.$chars[$j])) { + continue; + } + foreach (scandir($d, \SCANDIR_SORT_NONE) ?: [] as $link) { + if ('.' !== $link && '..' !== $link && (null !== $renamed || !realpath($d.\DIRECTORY_SEPARATOR.$link))) { + unlink($d.\DIRECTORY_SEPARATOR.$link); + } + } + null === $renamed ?: rmdir($d); + } + null === $renamed ?: rmdir($dir.$chars[$i]); + } + null === $renamed ?: rmdir($renamed); + } + } finally { + restore_error_handler(); + } + + return $ok; + } + + /** + * {@inheritdoc} + */ + protected function doSave(array $values, int $lifetime, array $addTagData = [], array $removeTagData = []): array + { + $failed = $this->doSaveCache($values, $lifetime); + + // Add Tags as symlinks + foreach ($addTagData as $tagId => $ids) { + $tagFolder = $this->getTagFolder($tagId); + foreach ($ids as $id) { + if ($failed && \in_array($id, $failed, true)) { + continue; + } + + $file = $this->getFile($id); + + if (!@symlink($file, $tagLink = $this->getFile($id, true, $tagFolder)) && !is_link($tagLink)) { + @unlink($file); + $failed[] = $id; + } + } + } + + // Unlink removed Tags + foreach ($removeTagData as $tagId => $ids) { + $tagFolder = $this->getTagFolder($tagId); + foreach ($ids as $id) { + if ($failed && \in_array($id, $failed, true)) { + continue; + } + + @unlink($this->getFile($id, false, $tagFolder)); + } + } + + return $failed; + } + + /** + * {@inheritdoc} + */ + protected function doDeleteYieldTags(array $ids): iterable + { + foreach ($ids as $id) { + $file = $this->getFile($id); + if (!is_file($file) || !$h = @fopen($file, 'r')) { + continue; + } + + if ((\PHP_VERSION_ID >= 70300 || '\\' !== \DIRECTORY_SEPARATOR) && !@unlink($file)) { + fclose($h); + continue; + } + + $meta = explode("\n", fread($h, 4096), 3)[2] ?? ''; + + // detect the compact format used in marshall() using magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F + if (13 < \strlen($meta) && "\x9D" === $meta[0] && "\0" === $meta[5] && "\x5F" === $meta[9]) { + $meta[9] = "\0"; + $tagLen = unpack('Nlen', $meta, 9)['len']; + $meta = substr($meta, 13, $tagLen); + + if (0 < $tagLen -= \strlen($meta)) { + $meta .= fread($h, $tagLen); + } + + try { + yield $id => '' === $meta ? [] : $this->marshaller->unmarshall($meta); + } catch (\Exception $e) { + yield $id => []; + } + } + + fclose($h); + + if (\PHP_VERSION_ID < 70300 && '\\' === \DIRECTORY_SEPARATOR) { + @unlink($file); + } + } + } + + /** + * {@inheritdoc} + */ + protected function doDeleteTagRelations(array $tagData): bool + { + foreach ($tagData as $tagId => $idList) { + $tagFolder = $this->getTagFolder($tagId); + foreach ($idList as $id) { + @unlink($this->getFile($id, false, $tagFolder)); + } + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doInvalidate(array $tagIds): bool + { + foreach ($tagIds as $tagId) { + if (!is_dir($tagFolder = $this->getTagFolder($tagId))) { + continue; + } + + set_error_handler(static function () {}); + + try { + if (rename($tagFolder, $renamed = substr_replace($tagFolder, bin2hex(random_bytes(4)), -9))) { + $tagFolder = $renamed.\DIRECTORY_SEPARATOR; + } else { + $renamed = null; + } + + foreach ($this->scanHashDir($tagFolder) as $itemLink) { + unlink(realpath($itemLink) ?: $itemLink); + unlink($itemLink); + } + + if (null === $renamed) { + continue; + } + + $chars = '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + + for ($i = 0; $i < 38; ++$i) { + for ($j = 0; $j < 38; ++$j) { + rmdir($tagFolder.$chars[$i].\DIRECTORY_SEPARATOR.$chars[$j]); + } + rmdir($tagFolder.$chars[$i]); + } + rmdir($renamed); + } finally { + restore_error_handler(); + } + } + + return true; + } + + private function getTagFolder(string $tagId): string + { + return $this->getFile($tagId, false, $this->directory.self::TAG_FOLDER.\DIRECTORY_SEPARATOR).\DIRECTORY_SEPARATOR; + } +} diff --git a/vendor/symfony/cache/Adapter/MemcachedAdapter.php b/vendor/symfony/cache/Adapter/MemcachedAdapter.php new file mode 100644 index 0000000..ab4e87c --- /dev/null +++ b/vendor/symfony/cache/Adapter/MemcachedAdapter.php @@ -0,0 +1,354 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Symfony\Component\Cache\Exception\CacheException; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Marshaller\DefaultMarshaller; +use Symfony\Component\Cache\Marshaller\MarshallerInterface; + +/** + * @author Rob Frawley 2nd + * @author Nicolas Grekas + */ +class MemcachedAdapter extends AbstractAdapter +{ + /** + * We are replacing characters that are illegal in Memcached keys with reserved characters from + * {@see \Symfony\Contracts\Cache\ItemInterface::RESERVED_CHARACTERS} that are legal in Memcached. + * Note: don’t use {@see \Symfony\Component\Cache\Adapter\AbstractAdapter::NS_SEPARATOR}. + */ + private const RESERVED_MEMCACHED = " \n\r\t\v\f\0"; + private const RESERVED_PSR6 = '@()\{}/'; + + protected $maxIdLength = 250; + + private const DEFAULT_CLIENT_OPTIONS = [ + 'persistent_id' => null, + 'username' => null, + 'password' => null, + \Memcached::OPT_SERIALIZER => \Memcached::SERIALIZER_PHP, + ]; + + private $marshaller; + private $client; + private $lazyClient; + + /** + * Using a MemcachedAdapter with a TagAwareAdapter for storing tags is discouraged. + * Using a RedisAdapter is recommended instead. If you cannot do otherwise, be aware that: + * - the Memcached::OPT_BINARY_PROTOCOL must be enabled + * (that's the default when using MemcachedAdapter::createConnection()); + * - tags eviction by Memcached's LRU algorithm will break by-tags invalidation; + * your Memcached memory should be large enough to never trigger LRU. + * + * Using a MemcachedAdapter as a pure items store is fine. + */ + public function __construct(\Memcached $client, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null) + { + if (!static::isSupported()) { + throw new CacheException('Memcached >= 2.2.0 is required.'); + } + if ('Memcached' === \get_class($client)) { + $opt = $client->getOption(\Memcached::OPT_SERIALIZER); + if (\Memcached::SERIALIZER_PHP !== $opt && \Memcached::SERIALIZER_IGBINARY !== $opt) { + throw new CacheException('MemcachedAdapter: "serializer" option must be "php" or "igbinary".'); + } + $this->maxIdLength -= \strlen($client->getOption(\Memcached::OPT_PREFIX_KEY)); + $this->client = $client; + } else { + $this->lazyClient = $client; + } + + parent::__construct($namespace, $defaultLifetime); + $this->enableVersioning(); + $this->marshaller = $marshaller ?? new DefaultMarshaller(); + } + + public static function isSupported() + { + return \extension_loaded('memcached') && version_compare(phpversion('memcached'), '2.2.0', '>='); + } + + /** + * Creates a Memcached instance. + * + * By default, the binary protocol, no block, and libketama compatible options are enabled. + * + * Examples for servers: + * - 'memcached://user:pass@localhost?weight=33' + * - [['localhost', 11211, 33]] + * + * @param array[]|string|string[] $servers An array of servers, a DSN, or an array of DSNs + * @param array $options An array of options + * + * @return \Memcached + * + * @throws \ErrorException When invalid options or servers are provided + */ + public static function createConnection($servers, array $options = []) + { + if (\is_string($servers)) { + $servers = [$servers]; + } elseif (!\is_array($servers)) { + throw new InvalidArgumentException(sprintf('MemcachedAdapter::createClient() expects array or string as first argument, "%s" given.', get_debug_type($servers))); + } + if (!static::isSupported()) { + throw new CacheException('Memcached >= 2.2.0 is required.'); + } + set_error_handler(function ($type, $msg, $file, $line) { throw new \ErrorException($msg, 0, $type, $file, $line); }); + try { + $options += static::DEFAULT_CLIENT_OPTIONS; + $client = new \Memcached($options['persistent_id']); + $username = $options['username']; + $password = $options['password']; + + // parse any DSN in $servers + foreach ($servers as $i => $dsn) { + if (\is_array($dsn)) { + continue; + } + if (0 !== strpos($dsn, 'memcached:')) { + throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: "%s" does not start with "memcached:".', $dsn)); + } + $params = preg_replace_callback('#^memcached:(//)?(?:([^@]*+)@)?#', function ($m) use (&$username, &$password) { + if (!empty($m[2])) { + [$username, $password] = explode(':', $m[2], 2) + [1 => null]; + } + + return 'file:'.($m[1] ?? ''); + }, $dsn); + if (false === $params = parse_url($params)) { + throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: "%s".', $dsn)); + } + $query = $hosts = []; + if (isset($params['query'])) { + parse_str($params['query'], $query); + + if (isset($query['host'])) { + if (!\is_array($hosts = $query['host'])) { + throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: "%s".', $dsn)); + } + foreach ($hosts as $host => $weight) { + if (false === $port = strrpos($host, ':')) { + $hosts[$host] = [$host, 11211, (int) $weight]; + } else { + $hosts[$host] = [substr($host, 0, $port), (int) substr($host, 1 + $port), (int) $weight]; + } + } + $hosts = array_values($hosts); + unset($query['host']); + } + if ($hosts && !isset($params['host']) && !isset($params['path'])) { + unset($servers[$i]); + $servers = array_merge($servers, $hosts); + continue; + } + } + if (!isset($params['host']) && !isset($params['path'])) { + throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: "%s".', $dsn)); + } + if (isset($params['path']) && preg_match('#/(\d+)$#', $params['path'], $m)) { + $params['weight'] = $m[1]; + $params['path'] = substr($params['path'], 0, -\strlen($m[0])); + } + $params += [ + 'host' => $params['host'] ?? $params['path'], + 'port' => isset($params['host']) ? 11211 : null, + 'weight' => 0, + ]; + if ($query) { + $params += $query; + $options = $query + $options; + } + + $servers[$i] = [$params['host'], $params['port'], $params['weight']]; + + if ($hosts) { + $servers = array_merge($servers, $hosts); + } + } + + // set client's options + unset($options['persistent_id'], $options['username'], $options['password'], $options['weight'], $options['lazy']); + $options = array_change_key_case($options, \CASE_UPPER); + $client->setOption(\Memcached::OPT_BINARY_PROTOCOL, true); + $client->setOption(\Memcached::OPT_NO_BLOCK, true); + $client->setOption(\Memcached::OPT_TCP_NODELAY, true); + if (!\array_key_exists('LIBKETAMA_COMPATIBLE', $options) && !\array_key_exists(\Memcached::OPT_LIBKETAMA_COMPATIBLE, $options)) { + $client->setOption(\Memcached::OPT_LIBKETAMA_COMPATIBLE, true); + } + foreach ($options as $name => $value) { + if (\is_int($name)) { + continue; + } + if ('HASH' === $name || 'SERIALIZER' === $name || 'DISTRIBUTION' === $name) { + $value = \constant('Memcached::'.$name.'_'.strtoupper($value)); + } + unset($options[$name]); + + if (\defined('Memcached::OPT_'.$name)) { + $options[\constant('Memcached::OPT_'.$name)] = $value; + } + } + $client->setOptions($options); + + // set client's servers, taking care of persistent connections + if (!$client->isPristine()) { + $oldServers = []; + foreach ($client->getServerList() as $server) { + $oldServers[] = [$server['host'], $server['port']]; + } + + $newServers = []; + foreach ($servers as $server) { + if (1 < \count($server)) { + $server = array_values($server); + unset($server[2]); + $server[1] = (int) $server[1]; + } + $newServers[] = $server; + } + + if ($oldServers !== $newServers) { + $client->resetServerList(); + $client->addServers($servers); + } + } else { + $client->addServers($servers); + } + + if (null !== $username || null !== $password) { + if (!method_exists($client, 'setSaslAuthData')) { + trigger_error('Missing SASL support: the memcached extension must be compiled with --enable-memcached-sasl.'); + } + $client->setSaslAuthData($username, $password); + } + + return $client; + } finally { + restore_error_handler(); + } + } + + /** + * {@inheritdoc} + */ + protected function doSave(array $values, int $lifetime) + { + if (!$values = $this->marshaller->marshall($values, $failed)) { + return $failed; + } + + if ($lifetime && $lifetime > 30 * 86400) { + $lifetime += time(); + } + + $encodedValues = []; + foreach ($values as $key => $value) { + $encodedValues[self::encodeKey($key)] = $value; + } + + return $this->checkResultCode($this->getClient()->setMulti($encodedValues, $lifetime)) ? $failed : false; + } + + /** + * {@inheritdoc} + */ + protected function doFetch(array $ids) + { + try { + $encodedIds = array_map('self::encodeKey', $ids); + + $encodedResult = $this->checkResultCode($this->getClient()->getMulti($encodedIds)); + + $result = []; + foreach ($encodedResult as $key => $value) { + $result[self::decodeKey($key)] = $this->marshaller->unmarshall($value); + } + + return $result; + } catch (\Error $e) { + throw new \ErrorException($e->getMessage(), $e->getCode(), \E_ERROR, $e->getFile(), $e->getLine()); + } + } + + /** + * {@inheritdoc} + */ + protected function doHave(string $id) + { + return false !== $this->getClient()->get(self::encodeKey($id)) || $this->checkResultCode(\Memcached::RES_SUCCESS === $this->client->getResultCode()); + } + + /** + * {@inheritdoc} + */ + protected function doDelete(array $ids) + { + $ok = true; + $encodedIds = array_map('self::encodeKey', $ids); + foreach ($this->checkResultCode($this->getClient()->deleteMulti($encodedIds)) as $result) { + if (\Memcached::RES_SUCCESS !== $result && \Memcached::RES_NOTFOUND !== $result) { + $ok = false; + } + } + + return $ok; + } + + /** + * {@inheritdoc} + */ + protected function doClear(string $namespace) + { + return '' === $namespace && $this->getClient()->flush(); + } + + private function checkResultCode($result) + { + $code = $this->client->getResultCode(); + + if (\Memcached::RES_SUCCESS === $code || \Memcached::RES_NOTFOUND === $code) { + return $result; + } + + throw new CacheException('MemcachedAdapter client error: '.strtolower($this->client->getResultMessage())); + } + + private function getClient(): \Memcached + { + if ($this->client) { + return $this->client; + } + + $opt = $this->lazyClient->getOption(\Memcached::OPT_SERIALIZER); + if (\Memcached::SERIALIZER_PHP !== $opt && \Memcached::SERIALIZER_IGBINARY !== $opt) { + throw new CacheException('MemcachedAdapter: "serializer" option must be "php" or "igbinary".'); + } + if ('' !== $prefix = (string) $this->lazyClient->getOption(\Memcached::OPT_PREFIX_KEY)) { + throw new CacheException(sprintf('MemcachedAdapter: "prefix_key" option must be empty when using proxified connections, "%s" given.', $prefix)); + } + + return $this->client = $this->lazyClient; + } + + private static function encodeKey(string $key): string + { + return strtr($key, self::RESERVED_MEMCACHED, self::RESERVED_PSR6); + } + + private static function decodeKey(string $key): string + { + return strtr($key, self::RESERVED_PSR6, self::RESERVED_MEMCACHED); + } +} diff --git a/vendor/symfony/cache/Adapter/NullAdapter.php b/vendor/symfony/cache/Adapter/NullAdapter.php new file mode 100644 index 0000000..15f7f8c --- /dev/null +++ b/vendor/symfony/cache/Adapter/NullAdapter.php @@ -0,0 +1,152 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Cache\CacheItemInterface; +use Symfony\Component\Cache\CacheItem; +use Symfony\Contracts\Cache\CacheInterface; + +/** + * @author Titouan Galopin + */ +class NullAdapter implements AdapterInterface, CacheInterface +{ + private static $createCacheItem; + + public function __construct() + { + self::$createCacheItem ?? self::$createCacheItem = \Closure::bind( + static function ($key) { + $item = new CacheItem(); + $item->key = $key; + $item->isHit = false; + + return $item; + }, + null, + CacheItem::class + ); + } + + /** + * {@inheritdoc} + */ + public function get(string $key, callable $callback, float $beta = null, array &$metadata = null) + { + $save = true; + + return $callback((self::$createCacheItem)($key), $save); + } + + /** + * {@inheritdoc} + */ + public function getItem($key) + { + return (self::$createCacheItem)($key); + } + + /** + * {@inheritdoc} + */ + public function getItems(array $keys = []) + { + return $this->generateItems($keys); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function hasItem($key) + { + return false; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function clear(string $prefix = '') + { + return true; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function deleteItem($key) + { + return true; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function deleteItems(array $keys) + { + return true; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function save(CacheItemInterface $item) + { + return true; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function saveDeferred(CacheItemInterface $item) + { + return true; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function commit() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function delete(string $key): bool + { + return $this->deleteItem($key); + } + + private function generateItems(array $keys): \Generator + { + $f = self::$createCacheItem; + + foreach ($keys as $key) { + yield $key => $f($key); + } + } +} diff --git a/vendor/symfony/cache/Adapter/ParameterNormalizer.php b/vendor/symfony/cache/Adapter/ParameterNormalizer.php new file mode 100644 index 0000000..e33ae9f --- /dev/null +++ b/vendor/symfony/cache/Adapter/ParameterNormalizer.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +/** + * @author Lars Strojny + */ +final class ParameterNormalizer +{ + public static function normalizeDuration(string $duration): int + { + if (is_numeric($duration)) { + return $duration; + } + + if (false !== $time = strtotime($duration, 0)) { + return $time; + } + + try { + return \DateTime::createFromFormat('U', 0)->add(new \DateInterval($duration))->getTimestamp(); + } catch (\Exception $e) { + throw new \InvalidArgumentException(sprintf('Cannot parse date interval "%s".', $duration), 0, $e); + } + } +} diff --git a/vendor/symfony/cache/Adapter/PdoAdapter.php b/vendor/symfony/cache/Adapter/PdoAdapter.php new file mode 100644 index 0000000..5d10724 --- /dev/null +++ b/vendor/symfony/cache/Adapter/PdoAdapter.php @@ -0,0 +1,583 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Schema\Schema; +use Psr\Cache\CacheItemInterface; +use Psr\Log\LoggerInterface; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Marshaller\DefaultMarshaller; +use Symfony\Component\Cache\Marshaller\MarshallerInterface; +use Symfony\Component\Cache\PruneableInterface; + +class PdoAdapter extends AbstractAdapter implements PruneableInterface +{ + protected $maxIdLength = 255; + + private $marshaller; + private $conn; + private $dsn; + private $driver; + private $serverVersion; + private $table = 'cache_items'; + private $idCol = 'item_id'; + private $dataCol = 'item_data'; + private $lifetimeCol = 'item_lifetime'; + private $timeCol = 'item_time'; + private $username = ''; + private $password = ''; + private $connectionOptions = []; + private $namespace; + + private $dbalAdapter; + + /** + * You can either pass an existing database connection as PDO instance or + * a DSN string that will be used to lazy-connect to the database when the + * cache is actually used. + * + * List of available options: + * * db_table: The name of the table [default: cache_items] + * * db_id_col: The column where to store the cache id [default: item_id] + * * db_data_col: The column where to store the cache data [default: item_data] + * * db_lifetime_col: The column where to store the lifetime [default: item_lifetime] + * * db_time_col: The column where to store the timestamp [default: item_time] + * * db_username: The username when lazy-connect [default: ''] + * * db_password: The password when lazy-connect [default: ''] + * * db_connection_options: An array of driver-specific connection options [default: []] + * + * @param \PDO|string $connOrDsn + * + * @throws InvalidArgumentException When first argument is not PDO nor Connection nor string + * @throws InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION + * @throws InvalidArgumentException When namespace contains invalid characters + */ + public function __construct($connOrDsn, string $namespace = '', int $defaultLifetime = 0, array $options = [], MarshallerInterface $marshaller = null) + { + if ($connOrDsn instanceof Connection || (\is_string($connOrDsn) && str_contains($connOrDsn, '://'))) { + trigger_deprecation('symfony/cache', '5.4', 'Usage of a DBAL Connection with "%s" is deprecated and will be removed in symfony 6.0. Use "%s" instead.', __CLASS__, DoctrineDbalAdapter::class); + $this->dbalAdapter = new DoctrineDbalAdapter($connOrDsn, $namespace, $defaultLifetime, $options, $marshaller); + + return; + } + + if (isset($namespace[0]) && preg_match('#[^-+.A-Za-z0-9]#', $namespace, $match)) { + throw new InvalidArgumentException(sprintf('Namespace contains "%s" but only characters in [-+.A-Za-z0-9] are allowed.', $match[0])); + } + + if ($connOrDsn instanceof \PDO) { + if (\PDO::ERRMODE_EXCEPTION !== $connOrDsn->getAttribute(\PDO::ATTR_ERRMODE)) { + throw new InvalidArgumentException(sprintf('"%s" requires PDO error mode attribute be set to throw Exceptions (i.e. $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION)).', __CLASS__)); + } + + $this->conn = $connOrDsn; + } elseif (\is_string($connOrDsn)) { + $this->dsn = $connOrDsn; + } else { + throw new InvalidArgumentException(sprintf('"%s" requires PDO or Doctrine\DBAL\Connection instance or DSN string as first argument, "%s" given.', __CLASS__, get_debug_type($connOrDsn))); + } + + $this->table = $options['db_table'] ?? $this->table; + $this->idCol = $options['db_id_col'] ?? $this->idCol; + $this->dataCol = $options['db_data_col'] ?? $this->dataCol; + $this->lifetimeCol = $options['db_lifetime_col'] ?? $this->lifetimeCol; + $this->timeCol = $options['db_time_col'] ?? $this->timeCol; + $this->username = $options['db_username'] ?? $this->username; + $this->password = $options['db_password'] ?? $this->password; + $this->connectionOptions = $options['db_connection_options'] ?? $this->connectionOptions; + $this->namespace = $namespace; + $this->marshaller = $marshaller ?? new DefaultMarshaller(); + + parent::__construct($namespace, $defaultLifetime); + } + + /** + * {@inheritDoc} + */ + public function getItem($key) + { + if (isset($this->dbalAdapter)) { + return $this->dbalAdapter->getItem($key); + } + + return parent::getItem($key); + } + + /** + * {@inheritDoc} + */ + public function getItems(array $keys = []) + { + if (isset($this->dbalAdapter)) { + return $this->dbalAdapter->getItems($keys); + } + + return parent::getItems($keys); + } + + /** + * {@inheritDoc} + */ + public function hasItem($key) + { + if (isset($this->dbalAdapter)) { + return $this->dbalAdapter->hasItem($key); + } + + return parent::hasItem($key); + } + + /** + * {@inheritDoc} + */ + public function deleteItem($key) + { + if (isset($this->dbalAdapter)) { + return $this->dbalAdapter->deleteItem($key); + } + + return parent::deleteItem($key); + } + + /** + * {@inheritDoc} + */ + public function deleteItems(array $keys) + { + if (isset($this->dbalAdapter)) { + return $this->dbalAdapter->deleteItems($keys); + } + + return parent::deleteItems($keys); + } + + /** + * {@inheritDoc} + */ + public function clear(string $prefix = '') + { + if (isset($this->dbalAdapter)) { + return $this->dbalAdapter->clear($prefix); + } + + return parent::clear($prefix); + } + + /** + * {@inheritDoc} + */ + public function get(string $key, callable $callback, float $beta = null, array &$metadata = null) + { + if (isset($this->dbalAdapter)) { + return $this->dbalAdapter->get($key, $callback, $beta, $metadata); + } + + return parent::get($key, $callback, $beta, $metadata); + } + + /** + * {@inheritDoc} + */ + public function delete(string $key): bool + { + if (isset($this->dbalAdapter)) { + return $this->dbalAdapter->delete($key); + } + + return parent::delete($key); + } + + /** + * {@inheritDoc} + */ + public function save(CacheItemInterface $item) + { + if (isset($this->dbalAdapter)) { + return $this->dbalAdapter->save($item); + } + + return parent::save($item); + } + + /** + * {@inheritDoc} + */ + public function saveDeferred(CacheItemInterface $item) + { + if (isset($this->dbalAdapter)) { + return $this->dbalAdapter->saveDeferred($item); + } + + return parent::saveDeferred($item); + } + + /** + * {@inheritDoc} + */ + public function setLogger(LoggerInterface $logger): void + { + if (isset($this->dbalAdapter)) { + $this->dbalAdapter->setLogger($logger); + + return; + } + + parent::setLogger($logger); + } + + /** + * {@inheritDoc} + */ + public function commit() + { + if (isset($this->dbalAdapter)) { + return $this->dbalAdapter->commit(); + } + + return parent::commit(); + } + + /** + * {@inheritDoc} + */ + public function reset() + { + if (isset($this->dbalAdapter)) { + $this->dbalAdapter->reset(); + + return; + } + + parent::reset(); + } + + /** + * Creates the table to store cache items which can be called once for setup. + * + * Cache ID are saved in a column of maximum length 255. Cache data is + * saved in a BLOB. + * + * @throws \PDOException When the table already exists + * @throws \DomainException When an unsupported PDO driver is used + */ + public function createTable() + { + if (isset($this->dbalAdapter)) { + $this->dbalAdapter->createTable(); + + return; + } + + // connect if we are not yet + $conn = $this->getConnection(); + + switch ($this->driver) { + case 'mysql': + // We use varbinary for the ID column because it prevents unwanted conversions: + // - character set conversions between server and client + // - trailing space removal + // - case-insensitivity + // - language processing like é == e + $sql = "CREATE TABLE $this->table ($this->idCol VARBINARY(255) NOT NULL PRIMARY KEY, $this->dataCol MEDIUMBLOB NOT NULL, $this->lifetimeCol INTEGER UNSIGNED, $this->timeCol INTEGER UNSIGNED NOT NULL) COLLATE utf8mb4_bin, ENGINE = InnoDB"; + break; + case 'sqlite': + $sql = "CREATE TABLE $this->table ($this->idCol TEXT NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)"; + break; + case 'pgsql': + $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(255) NOT NULL PRIMARY KEY, $this->dataCol BYTEA NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)"; + break; + case 'oci': + $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR2(255) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)"; + break; + case 'sqlsrv': + $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(255) NOT NULL PRIMARY KEY, $this->dataCol VARBINARY(MAX) NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)"; + break; + default: + throw new \DomainException(sprintf('Creating the cache table is currently not implemented for PDO driver "%s".', $this->driver)); + } + + $conn->exec($sql); + } + + /** + * Adds the Table to the Schema if the adapter uses this Connection. + * + * @deprecated since symfony/cache 5.4 use DoctrineDbalAdapter instead + */ + public function configureSchema(Schema $schema, Connection $forConnection): void + { + if (isset($this->dbalAdapter)) { + $this->dbalAdapter->configureSchema($schema, $forConnection); + } + } + + /** + * {@inheritdoc} + */ + public function prune() + { + if (isset($this->dbalAdapter)) { + return $this->dbalAdapter->prune(); + } + + $deleteSql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol <= :time"; + + if ('' !== $this->namespace) { + $deleteSql .= " AND $this->idCol LIKE :namespace"; + } + + $connection = $this->getConnection(); + + try { + $delete = $connection->prepare($deleteSql); + } catch (\PDOException $e) { + return true; + } + $delete->bindValue(':time', time(), \PDO::PARAM_INT); + + if ('' !== $this->namespace) { + $delete->bindValue(':namespace', sprintf('%s%%', $this->namespace), \PDO::PARAM_STR); + } + try { + return $delete->execute(); + } catch (\PDOException $e) { + return true; + } + } + + /** + * {@inheritdoc} + */ + protected function doFetch(array $ids) + { + $connection = $this->getConnection(); + + $now = time(); + $expired = []; + + $sql = str_pad('', (\count($ids) << 1) - 1, '?,'); + $sql = "SELECT $this->idCol, CASE WHEN $this->lifetimeCol IS NULL OR $this->lifetimeCol + $this->timeCol > ? THEN $this->dataCol ELSE NULL END FROM $this->table WHERE $this->idCol IN ($sql)"; + $stmt = $connection->prepare($sql); + $stmt->bindValue($i = 1, $now, \PDO::PARAM_INT); + foreach ($ids as $id) { + $stmt->bindValue(++$i, $id); + } + $result = $stmt->execute(); + + if (\is_object($result)) { + $result = $result->iterateNumeric(); + } else { + $stmt->setFetchMode(\PDO::FETCH_NUM); + $result = $stmt; + } + + foreach ($result as $row) { + if (null === $row[1]) { + $expired[] = $row[0]; + } else { + yield $row[0] => $this->marshaller->unmarshall(\is_resource($row[1]) ? stream_get_contents($row[1]) : $row[1]); + } + } + + if ($expired) { + $sql = str_pad('', (\count($expired) << 1) - 1, '?,'); + $sql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol <= ? AND $this->idCol IN ($sql)"; + $stmt = $connection->prepare($sql); + $stmt->bindValue($i = 1, $now, \PDO::PARAM_INT); + foreach ($expired as $id) { + $stmt->bindValue(++$i, $id); + } + $stmt->execute(); + } + } + + /** + * {@inheritdoc} + */ + protected function doHave(string $id) + { + $connection = $this->getConnection(); + + $sql = "SELECT 1 FROM $this->table WHERE $this->idCol = :id AND ($this->lifetimeCol IS NULL OR $this->lifetimeCol + $this->timeCol > :time)"; + $stmt = $connection->prepare($sql); + + $stmt->bindValue(':id', $id); + $stmt->bindValue(':time', time(), \PDO::PARAM_INT); + $stmt->execute(); + + return (bool) $stmt->fetchColumn(); + } + + /** + * {@inheritdoc} + */ + protected function doClear(string $namespace) + { + $conn = $this->getConnection(); + + if ('' === $namespace) { + if ('sqlite' === $this->driver) { + $sql = "DELETE FROM $this->table"; + } else { + $sql = "TRUNCATE TABLE $this->table"; + } + } else { + $sql = "DELETE FROM $this->table WHERE $this->idCol LIKE '$namespace%'"; + } + + try { + $conn->exec($sql); + } catch (\PDOException $e) { + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doDelete(array $ids) + { + $sql = str_pad('', (\count($ids) << 1) - 1, '?,'); + $sql = "DELETE FROM $this->table WHERE $this->idCol IN ($sql)"; + try { + $stmt = $this->getConnection()->prepare($sql); + $stmt->execute(array_values($ids)); + } catch (\PDOException $e) { + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doSave(array $values, int $lifetime) + { + if (!$values = $this->marshaller->marshall($values, $failed)) { + return $failed; + } + + $conn = $this->getConnection(); + + $driver = $this->driver; + $insertSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time)"; + + switch (true) { + case 'mysql' === $driver: + $sql = $insertSql." ON DUPLICATE KEY UPDATE $this->dataCol = VALUES($this->dataCol), $this->lifetimeCol = VALUES($this->lifetimeCol), $this->timeCol = VALUES($this->timeCol)"; + break; + case 'oci' === $driver: + // DUAL is Oracle specific dummy table + $sql = "MERGE INTO $this->table USING DUAL ON ($this->idCol = ?) ". + "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ". + "WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?"; + break; + case 'sqlsrv' === $driver && version_compare($this->getServerVersion(), '10', '>='): + // MERGE is only available since SQL Server 2008 and must be terminated by semicolon + // It also requires HOLDLOCK according to http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx + $sql = "MERGE INTO $this->table WITH (HOLDLOCK) USING (SELECT 1 AS dummy) AS src ON ($this->idCol = ?) ". + "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ". + "WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?;"; + break; + case 'sqlite' === $driver: + $sql = 'INSERT OR REPLACE'.substr($insertSql, 6); + break; + case 'pgsql' === $driver && version_compare($this->getServerVersion(), '9.5', '>='): + $sql = $insertSql." ON CONFLICT ($this->idCol) DO UPDATE SET ($this->dataCol, $this->lifetimeCol, $this->timeCol) = (EXCLUDED.$this->dataCol, EXCLUDED.$this->lifetimeCol, EXCLUDED.$this->timeCol)"; + break; + default: + $driver = null; + $sql = "UPDATE $this->table SET $this->dataCol = :data, $this->lifetimeCol = :lifetime, $this->timeCol = :time WHERE $this->idCol = :id"; + break; + } + + $now = time(); + $lifetime = $lifetime ?: null; + try { + $stmt = $conn->prepare($sql); + } catch (\PDOException $e) { + if (!$conn->inTransaction() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true)) { + $this->createTable(); + } + $stmt = $conn->prepare($sql); + } + + // $id and $data are defined later in the loop. Binding is done by reference, values are read on execution. + if ('sqlsrv' === $driver || 'oci' === $driver) { + $stmt->bindParam(1, $id); + $stmt->bindParam(2, $id); + $stmt->bindParam(3, $data, \PDO::PARAM_LOB); + $stmt->bindValue(4, $lifetime, \PDO::PARAM_INT); + $stmt->bindValue(5, $now, \PDO::PARAM_INT); + $stmt->bindParam(6, $data, \PDO::PARAM_LOB); + $stmt->bindValue(7, $lifetime, \PDO::PARAM_INT); + $stmt->bindValue(8, $now, \PDO::PARAM_INT); + } else { + $stmt->bindParam(':id', $id); + $stmt->bindParam(':data', $data, \PDO::PARAM_LOB); + $stmt->bindValue(':lifetime', $lifetime, \PDO::PARAM_INT); + $stmt->bindValue(':time', $now, \PDO::PARAM_INT); + } + if (null === $driver) { + $insertStmt = $conn->prepare($insertSql); + + $insertStmt->bindParam(':id', $id); + $insertStmt->bindParam(':data', $data, \PDO::PARAM_LOB); + $insertStmt->bindValue(':lifetime', $lifetime, \PDO::PARAM_INT); + $insertStmt->bindValue(':time', $now, \PDO::PARAM_INT); + } + + foreach ($values as $id => $data) { + try { + $stmt->execute(); + } catch (\PDOException $e) { + if (!$conn->inTransaction() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true)) { + $this->createTable(); + } + $stmt->execute(); + } + if (null === $driver && !$stmt->rowCount()) { + try { + $insertStmt->execute(); + } catch (\PDOException $e) { + // A concurrent write won, let it be + } + } + } + + return $failed; + } + + private function getConnection(): \PDO + { + if (null === $this->conn) { + $this->conn = new \PDO($this->dsn, $this->username, $this->password, $this->connectionOptions); + $this->conn->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + } + if (null === $this->driver) { + $this->driver = $this->conn->getAttribute(\PDO::ATTR_DRIVER_NAME); + } + + return $this->conn; + } + + private function getServerVersion(): string + { + if (null === $this->serverVersion) { + $this->serverVersion = $this->conn->getAttribute(\PDO::ATTR_SERVER_VERSION); + } + + return $this->serverVersion; + } +} diff --git a/vendor/symfony/cache/Adapter/PhpArrayAdapter.php b/vendor/symfony/cache/Adapter/PhpArrayAdapter.php new file mode 100644 index 0000000..7e9b6b3 --- /dev/null +++ b/vendor/symfony/cache/Adapter/PhpArrayAdapter.php @@ -0,0 +1,435 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Cache\CacheItemInterface; +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Cache\Traits\ContractsTrait; +use Symfony\Component\Cache\Traits\ProxyTrait; +use Symfony\Component\VarExporter\VarExporter; +use Symfony\Contracts\Cache\CacheInterface; + +/** + * Caches items at warm up time using a PHP array that is stored in shared memory by OPCache since PHP 7.0. + * Warmed up items are read-only and run-time discovered items are cached using a fallback adapter. + * + * @author Titouan Galopin + * @author Nicolas Grekas + */ +class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface +{ + use ContractsTrait; + use ProxyTrait; + + private $file; + private $keys; + private $values; + + private static $createCacheItem; + private static $valuesCache = []; + + /** + * @param string $file The PHP file were values are cached + * @param AdapterInterface $fallbackPool A pool to fallback on when an item is not hit + */ + public function __construct(string $file, AdapterInterface $fallbackPool) + { + $this->file = $file; + $this->pool = $fallbackPool; + self::$createCacheItem ?? self::$createCacheItem = \Closure::bind( + static function ($key, $value, $isHit) { + $item = new CacheItem(); + $item->key = $key; + $item->value = $value; + $item->isHit = $isHit; + + return $item; + }, + null, + CacheItem::class + ); + } + + /** + * This adapter takes advantage of how PHP stores arrays in its latest versions. + * + * @param string $file The PHP file were values are cached + * @param CacheItemPoolInterface $fallbackPool A pool to fallback on when an item is not hit + * + * @return CacheItemPoolInterface + */ + public static function create(string $file, CacheItemPoolInterface $fallbackPool) + { + if (!$fallbackPool instanceof AdapterInterface) { + $fallbackPool = new ProxyAdapter($fallbackPool); + } + + return new static($file, $fallbackPool); + } + + /** + * {@inheritdoc} + */ + public function get(string $key, callable $callback, float $beta = null, array &$metadata = null) + { + if (null === $this->values) { + $this->initialize(); + } + if (!isset($this->keys[$key])) { + get_from_pool: + if ($this->pool instanceof CacheInterface) { + return $this->pool->get($key, $callback, $beta, $metadata); + } + + return $this->doGet($this->pool, $key, $callback, $beta, $metadata); + } + $value = $this->values[$this->keys[$key]]; + + if ('N;' === $value) { + return null; + } + try { + if ($value instanceof \Closure) { + return $value(); + } + } catch (\Throwable $e) { + unset($this->keys[$key]); + goto get_from_pool; + } + + return $value; + } + + /** + * {@inheritdoc} + */ + public function getItem($key) + { + if (!\is_string($key)) { + throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', get_debug_type($key))); + } + if (null === $this->values) { + $this->initialize(); + } + if (!isset($this->keys[$key])) { + return $this->pool->getItem($key); + } + + $value = $this->values[$this->keys[$key]]; + $isHit = true; + + if ('N;' === $value) { + $value = null; + } elseif ($value instanceof \Closure) { + try { + $value = $value(); + } catch (\Throwable $e) { + $value = null; + $isHit = false; + } + } + + return (self::$createCacheItem)($key, $value, $isHit); + } + + /** + * {@inheritdoc} + */ + public function getItems(array $keys = []) + { + foreach ($keys as $key) { + if (!\is_string($key)) { + throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', get_debug_type($key))); + } + } + if (null === $this->values) { + $this->initialize(); + } + + return $this->generateItems($keys); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function hasItem($key) + { + if (!\is_string($key)) { + throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', get_debug_type($key))); + } + if (null === $this->values) { + $this->initialize(); + } + + return isset($this->keys[$key]) || $this->pool->hasItem($key); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function deleteItem($key) + { + if (!\is_string($key)) { + throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', get_debug_type($key))); + } + if (null === $this->values) { + $this->initialize(); + } + + return !isset($this->keys[$key]) && $this->pool->deleteItem($key); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function deleteItems(array $keys) + { + $deleted = true; + $fallbackKeys = []; + + foreach ($keys as $key) { + if (!\is_string($key)) { + throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', get_debug_type($key))); + } + + if (isset($this->keys[$key])) { + $deleted = false; + } else { + $fallbackKeys[] = $key; + } + } + if (null === $this->values) { + $this->initialize(); + } + + if ($fallbackKeys) { + $deleted = $this->pool->deleteItems($fallbackKeys) && $deleted; + } + + return $deleted; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function save(CacheItemInterface $item) + { + if (null === $this->values) { + $this->initialize(); + } + + return !isset($this->keys[$item->getKey()]) && $this->pool->save($item); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function saveDeferred(CacheItemInterface $item) + { + if (null === $this->values) { + $this->initialize(); + } + + return !isset($this->keys[$item->getKey()]) && $this->pool->saveDeferred($item); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function commit() + { + return $this->pool->commit(); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function clear(string $prefix = '') + { + $this->keys = $this->values = []; + + $cleared = @unlink($this->file) || !file_exists($this->file); + unset(self::$valuesCache[$this->file]); + + if ($this->pool instanceof AdapterInterface) { + return $this->pool->clear($prefix) && $cleared; + } + + return $this->pool->clear() && $cleared; + } + + /** + * Store an array of cached values. + * + * @param array $values The cached values + * + * @return string[] A list of classes to preload on PHP 7.4+ + */ + public function warmUp(array $values) + { + if (file_exists($this->file)) { + if (!is_file($this->file)) { + throw new InvalidArgumentException(sprintf('Cache path exists and is not a file: "%s".', $this->file)); + } + + if (!is_writable($this->file)) { + throw new InvalidArgumentException(sprintf('Cache file is not writable: "%s".', $this->file)); + } + } else { + $directory = \dirname($this->file); + + if (!is_dir($directory) && !@mkdir($directory, 0777, true)) { + throw new InvalidArgumentException(sprintf('Cache directory does not exist and cannot be created: "%s".', $directory)); + } + + if (!is_writable($directory)) { + throw new InvalidArgumentException(sprintf('Cache directory is not writable: "%s".', $directory)); + } + } + + $preload = []; + $dumpedValues = ''; + $dumpedMap = []; + $dump = <<<'EOF' + $value) { + CacheItem::validateKey(\is_int($key) ? (string) $key : $key); + $isStaticValue = true; + + if (null === $value) { + $value = "'N;'"; + } elseif (\is_object($value) || \is_array($value)) { + try { + $value = VarExporter::export($value, $isStaticValue, $preload); + } catch (\Exception $e) { + throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable "%s" value.', $key, get_debug_type($value)), 0, $e); + } + } elseif (\is_string($value)) { + // Wrap "N;" in a closure to not confuse it with an encoded `null` + if ('N;' === $value) { + $isStaticValue = false; + } + $value = var_export($value, true); + } elseif (!is_scalar($value)) { + throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable "%s" value.', $key, get_debug_type($value))); + } else { + $value = var_export($value, true); + } + + if (!$isStaticValue) { + $value = str_replace("\n", "\n ", $value); + $value = "static function () {\n return {$value};\n}"; + } + $hash = hash('md5', $value); + + if (null === $id = $dumpedMap[$hash] ?? null) { + $id = $dumpedMap[$hash] = \count($dumpedMap); + $dumpedValues .= "{$id} => {$value},\n"; + } + + $dump .= var_export($key, true)." => {$id},\n"; + } + + $dump .= "\n], [\n\n{$dumpedValues}\n]];\n"; + + $tmpFile = uniqid($this->file, true); + + file_put_contents($tmpFile, $dump); + @chmod($tmpFile, 0666 & ~umask()); + unset($serialized, $value, $dump); + + @rename($tmpFile, $this->file); + unset(self::$valuesCache[$this->file]); + + $this->initialize(); + + return $preload; + } + + /** + * Load the cache file. + */ + private function initialize() + { + if (isset(self::$valuesCache[$this->file])) { + $values = self::$valuesCache[$this->file]; + } elseif (!is_file($this->file)) { + $this->keys = $this->values = []; + + return; + } else { + $values = self::$valuesCache[$this->file] = (include $this->file) ?: [[], []]; + } + + if (2 !== \count($values) || !isset($values[0], $values[1])) { + $this->keys = $this->values = []; + } else { + [$this->keys, $this->values] = $values; + } + } + + private function generateItems(array $keys): \Generator + { + $f = self::$createCacheItem; + $fallbackKeys = []; + + foreach ($keys as $key) { + if (isset($this->keys[$key])) { + $value = $this->values[$this->keys[$key]]; + + if ('N;' === $value) { + yield $key => $f($key, null, true); + } elseif ($value instanceof \Closure) { + try { + yield $key => $f($key, $value(), true); + } catch (\Throwable $e) { + yield $key => $f($key, null, false); + } + } else { + yield $key => $f($key, $value, true); + } + } else { + $fallbackKeys[] = $key; + } + } + + if ($fallbackKeys) { + yield from $this->pool->getItems($fallbackKeys); + } + } +} diff --git a/vendor/symfony/cache/Adapter/PhpFilesAdapter.php b/vendor/symfony/cache/Adapter/PhpFilesAdapter.php new file mode 100644 index 0000000..f5766c3 --- /dev/null +++ b/vendor/symfony/cache/Adapter/PhpFilesAdapter.php @@ -0,0 +1,330 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Symfony\Component\Cache\Exception\CacheException; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Cache\Traits\FilesystemCommonTrait; +use Symfony\Component\VarExporter\VarExporter; + +/** + * @author Piotr Stankowski + * @author Nicolas Grekas + * @author Rob Frawley 2nd + */ +class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface +{ + use FilesystemCommonTrait { + doClear as private doCommonClear; + doDelete as private doCommonDelete; + } + + private $includeHandler; + private $appendOnly; + private $values = []; + private $files = []; + + private static $startTime; + private static $valuesCache = []; + + /** + * @param $appendOnly Set to `true` to gain extra performance when the items stored in this pool never expire. + * Doing so is encouraged because it fits perfectly OPcache's memory model. + * + * @throws CacheException if OPcache is not enabled + */ + public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, bool $appendOnly = false) + { + $this->appendOnly = $appendOnly; + self::$startTime = self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? time(); + parent::__construct('', $defaultLifetime); + $this->init($namespace, $directory); + $this->includeHandler = static function ($type, $msg, $file, $line) { + throw new \ErrorException($msg, 0, $type, $file, $line); + }; + } + + public static function isSupported() + { + self::$startTime = self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? time(); + + return \function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN) && (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) || filter_var(ini_get('opcache.enable_cli'), \FILTER_VALIDATE_BOOLEAN)); + } + + /** + * @return bool + */ + public function prune() + { + $time = time(); + $pruned = true; + $getExpiry = true; + + set_error_handler($this->includeHandler); + try { + foreach ($this->scanHashDir($this->directory) as $file) { + try { + if (\is_array($expiresAt = include $file)) { + $expiresAt = $expiresAt[0]; + } + } catch (\ErrorException $e) { + $expiresAt = $time; + } + + if ($time >= $expiresAt) { + $pruned = $this->doUnlink($file) && !file_exists($file) && $pruned; + } + } + } finally { + restore_error_handler(); + } + + return $pruned; + } + + /** + * {@inheritdoc} + */ + protected function doFetch(array $ids) + { + if ($this->appendOnly) { + $now = 0; + $missingIds = []; + } else { + $now = time(); + $missingIds = $ids; + $ids = []; + } + $values = []; + + begin: + $getExpiry = false; + + foreach ($ids as $id) { + if (null === $value = $this->values[$id] ?? null) { + $missingIds[] = $id; + } elseif ('N;' === $value) { + $values[$id] = null; + } elseif (!\is_object($value)) { + $values[$id] = $value; + } elseif (!$value instanceof LazyValue) { + $values[$id] = $value(); + } elseif (false === $values[$id] = include $value->file) { + unset($values[$id], $this->values[$id]); + $missingIds[] = $id; + } + if (!$this->appendOnly) { + unset($this->values[$id]); + } + } + + if (!$missingIds) { + return $values; + } + + set_error_handler($this->includeHandler); + try { + $getExpiry = true; + + foreach ($missingIds as $k => $id) { + try { + $file = $this->files[$id] ?? $this->files[$id] = $this->getFile($id); + + if (isset(self::$valuesCache[$file])) { + [$expiresAt, $this->values[$id]] = self::$valuesCache[$file]; + } elseif (\is_array($expiresAt = include $file)) { + if ($this->appendOnly) { + self::$valuesCache[$file] = $expiresAt; + } + + [$expiresAt, $this->values[$id]] = $expiresAt; + } elseif ($now < $expiresAt) { + $this->values[$id] = new LazyValue($file); + } + + if ($now >= $expiresAt) { + unset($this->values[$id], $missingIds[$k], self::$valuesCache[$file]); + } + } catch (\ErrorException $e) { + unset($missingIds[$k]); + } + } + } finally { + restore_error_handler(); + } + + $ids = $missingIds; + $missingIds = []; + goto begin; + } + + /** + * {@inheritdoc} + */ + protected function doHave(string $id) + { + if ($this->appendOnly && isset($this->values[$id])) { + return true; + } + + set_error_handler($this->includeHandler); + try { + $file = $this->files[$id] ?? $this->files[$id] = $this->getFile($id); + $getExpiry = true; + + if (isset(self::$valuesCache[$file])) { + [$expiresAt, $value] = self::$valuesCache[$file]; + } elseif (\is_array($expiresAt = include $file)) { + if ($this->appendOnly) { + self::$valuesCache[$file] = $expiresAt; + } + + [$expiresAt, $value] = $expiresAt; + } elseif ($this->appendOnly) { + $value = new LazyValue($file); + } + } catch (\ErrorException $e) { + return false; + } finally { + restore_error_handler(); + } + if ($this->appendOnly) { + $now = 0; + $this->values[$id] = $value; + } else { + $now = time(); + } + + return $now < $expiresAt; + } + + /** + * {@inheritdoc} + */ + protected function doSave(array $values, int $lifetime) + { + $ok = true; + $expiry = $lifetime ? time() + $lifetime : 'PHP_INT_MAX'; + $allowCompile = self::isSupported(); + + foreach ($values as $key => $value) { + unset($this->values[$key]); + $isStaticValue = true; + if (null === $value) { + $value = "'N;'"; + } elseif (\is_object($value) || \is_array($value)) { + try { + $value = VarExporter::export($value, $isStaticValue); + } catch (\Exception $e) { + throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable "%s" value.', $key, get_debug_type($value)), 0, $e); + } + } elseif (\is_string($value)) { + // Wrap "N;" in a closure to not confuse it with an encoded `null` + if ('N;' === $value) { + $isStaticValue = false; + } + $value = var_export($value, true); + } elseif (!is_scalar($value)) { + throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable "%s" value.', $key, get_debug_type($value))); + } else { + $value = var_export($value, true); + } + + $encodedKey = rawurlencode($key); + + if ($isStaticValue) { + $value = "return [{$expiry}, {$value}];"; + } elseif ($this->appendOnly) { + $value = "return [{$expiry}, static function () { return {$value}; }];"; + } else { + // We cannot use a closure here because of https://bugs.php.net/76982 + $value = str_replace('\Symfony\Component\VarExporter\Internal\\', '', $value); + $value = "namespace Symfony\Component\VarExporter\Internal;\n\nreturn \$getExpiry ? {$expiry} : {$value};"; + } + + $file = $this->files[$key] = $this->getFile($key, true); + // Since OPcache only compiles files older than the script execution start, set the file's mtime in the past + $ok = $this->write($file, "directory)) { + throw new CacheException(sprintf('Cache directory is not writable (%s).', $this->directory)); + } + + return $ok; + } + + /** + * {@inheritdoc} + */ + protected function doClear(string $namespace) + { + $this->values = []; + + return $this->doCommonClear($namespace); + } + + /** + * {@inheritdoc} + */ + protected function doDelete(array $ids) + { + foreach ($ids as $id) { + unset($this->values[$id]); + } + + return $this->doCommonDelete($ids); + } + + protected function doUnlink(string $file) + { + unset(self::$valuesCache[$file]); + + if (self::isSupported()) { + @opcache_invalidate($file, true); + } + + return @unlink($file); + } + + private function getFileKey(string $file): string + { + if (!$h = @fopen($file, 'r')) { + return ''; + } + + $encodedKey = substr(fgets($h), 8); + fclose($h); + + return rawurldecode(rtrim($encodedKey)); + } +} + +/** + * @internal + */ +class LazyValue +{ + public $file; + + public function __construct(string $file) + { + $this->file = $file; + } +} diff --git a/vendor/symfony/cache/Adapter/ProxyAdapter.php b/vendor/symfony/cache/Adapter/ProxyAdapter.php new file mode 100644 index 0000000..7291a7e --- /dev/null +++ b/vendor/symfony/cache/Adapter/ProxyAdapter.php @@ -0,0 +1,268 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Cache\CacheItemInterface; +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Cache\Traits\ContractsTrait; +use Symfony\Component\Cache\Traits\ProxyTrait; +use Symfony\Contracts\Cache\CacheInterface; + +/** + * @author Nicolas Grekas + */ +class ProxyAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface +{ + use ContractsTrait; + use ProxyTrait; + + private $namespace = ''; + private $namespaceLen; + private $poolHash; + private $defaultLifetime; + + private static $createCacheItem; + private static $setInnerItem; + + public function __construct(CacheItemPoolInterface $pool, string $namespace = '', int $defaultLifetime = 0) + { + $this->pool = $pool; + $this->poolHash = $poolHash = spl_object_hash($pool); + if ('' !== $namespace) { + \assert('' !== CacheItem::validateKey($namespace)); + $this->namespace = $namespace; + } + $this->namespaceLen = \strlen($namespace); + $this->defaultLifetime = $defaultLifetime; + self::$createCacheItem ?? self::$createCacheItem = \Closure::bind( + static function ($key, $innerItem, $poolHash) { + $item = new CacheItem(); + $item->key = $key; + + if (null === $innerItem) { + return $item; + } + + $item->value = $v = $innerItem->get(); + $item->isHit = $innerItem->isHit(); + $item->innerItem = $innerItem; + $item->poolHash = $poolHash; + + // Detect wrapped values that encode for their expiry and creation duration + // For compactness, these values are packed in the key of an array using + // magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F + if (\is_array($v) && 1 === \count($v) && 10 === \strlen($k = (string) array_key_first($v)) && "\x9D" === $k[0] && "\0" === $k[5] && "\x5F" === $k[9]) { + $item->value = $v[$k]; + $v = unpack('Ve/Nc', substr($k, 1, -1)); + $item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET; + $item->metadata[CacheItem::METADATA_CTIME] = $v['c']; + } elseif ($innerItem instanceof CacheItem) { + $item->metadata = $innerItem->metadata; + } + $innerItem->set(null); + + return $item; + }, + null, + CacheItem::class + ); + self::$setInnerItem ?? self::$setInnerItem = \Closure::bind( + /** + * @param array $item A CacheItem cast to (array); accessing protected properties requires adding the "\0*\0" PHP prefix + */ + static function (CacheItemInterface $innerItem, array $item) { + // Tags are stored separately, no need to account for them when considering this item's newly set metadata + if (isset(($metadata = $item["\0*\0newMetadata"])[CacheItem::METADATA_TAGS])) { + unset($metadata[CacheItem::METADATA_TAGS]); + } + if ($metadata) { + // For compactness, expiry and creation duration are packed in the key of an array, using magic numbers as separators + $item["\0*\0value"] = ["\x9D".pack('VN', (int) (0.1 + $metadata[self::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[self::METADATA_CTIME])."\x5F" => $item["\0*\0value"]]; + } + $innerItem->set($item["\0*\0value"]); + $innerItem->expiresAt(null !== $item["\0*\0expiry"] ? \DateTime::createFromFormat('U.u', sprintf('%.6F', 0 === $item["\0*\0expiry"] ? \PHP_INT_MAX : $item["\0*\0expiry"])) : null); + }, + null, + CacheItem::class + ); + } + + /** + * {@inheritdoc} + */ + public function get(string $key, callable $callback, float $beta = null, array &$metadata = null) + { + if (!$this->pool instanceof CacheInterface) { + return $this->doGet($this, $key, $callback, $beta, $metadata); + } + + return $this->pool->get($this->getId($key), function ($innerItem, bool &$save) use ($key, $callback) { + $item = (self::$createCacheItem)($key, $innerItem, $this->poolHash); + $item->set($value = $callback($item, $save)); + (self::$setInnerItem)($innerItem, (array) $item); + + return $value; + }, $beta, $metadata); + } + + /** + * {@inheritdoc} + */ + public function getItem($key) + { + $item = $this->pool->getItem($this->getId($key)); + + return (self::$createCacheItem)($key, $item, $this->poolHash); + } + + /** + * {@inheritdoc} + */ + public function getItems(array $keys = []) + { + if ($this->namespaceLen) { + foreach ($keys as $i => $key) { + $keys[$i] = $this->getId($key); + } + } + + return $this->generateItems($this->pool->getItems($keys)); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function hasItem($key) + { + return $this->pool->hasItem($this->getId($key)); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function clear(string $prefix = '') + { + if ($this->pool instanceof AdapterInterface) { + return $this->pool->clear($this->namespace.$prefix); + } + + return $this->pool->clear(); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function deleteItem($key) + { + return $this->pool->deleteItem($this->getId($key)); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function deleteItems(array $keys) + { + if ($this->namespaceLen) { + foreach ($keys as $i => $key) { + $keys[$i] = $this->getId($key); + } + } + + return $this->pool->deleteItems($keys); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function save(CacheItemInterface $item) + { + return $this->doSave($item, __FUNCTION__); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function saveDeferred(CacheItemInterface $item) + { + return $this->doSave($item, __FUNCTION__); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function commit() + { + return $this->pool->commit(); + } + + private function doSave(CacheItemInterface $item, string $method) + { + if (!$item instanceof CacheItem) { + return false; + } + $item = (array) $item; + if (null === $item["\0*\0expiry"] && 0 < $this->defaultLifetime) { + $item["\0*\0expiry"] = microtime(true) + $this->defaultLifetime; + } + + if ($item["\0*\0poolHash"] === $this->poolHash && $item["\0*\0innerItem"]) { + $innerItem = $item["\0*\0innerItem"]; + } elseif ($this->pool instanceof AdapterInterface) { + // this is an optimization specific for AdapterInterface implementations + // so we can save a round-trip to the backend by just creating a new item + $innerItem = (self::$createCacheItem)($this->namespace.$item["\0*\0key"], null, $this->poolHash); + } else { + $innerItem = $this->pool->getItem($this->namespace.$item["\0*\0key"]); + } + + (self::$setInnerItem)($innerItem, $item); + + return $this->pool->$method($innerItem); + } + + private function generateItems(iterable $items): \Generator + { + $f = self::$createCacheItem; + + foreach ($items as $key => $item) { + if ($this->namespaceLen) { + $key = substr($key, $this->namespaceLen); + } + + yield $key => $f($key, $item, $this->poolHash); + } + } + + private function getId($key): string + { + \assert('' !== CacheItem::validateKey($key)); + + return $this->namespace.$key; + } +} diff --git a/vendor/symfony/cache/Adapter/Psr16Adapter.php b/vendor/symfony/cache/Adapter/Psr16Adapter.php new file mode 100644 index 0000000..a56aa39 --- /dev/null +++ b/vendor/symfony/cache/Adapter/Psr16Adapter.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\SimpleCache\CacheInterface; +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Cache\Traits\ProxyTrait; + +/** + * Turns a PSR-16 cache into a PSR-6 one. + * + * @author Nicolas Grekas + */ +class Psr16Adapter extends AbstractAdapter implements PruneableInterface, ResettableInterface +{ + use ProxyTrait; + + /** + * @internal + */ + protected const NS_SEPARATOR = '_'; + + private $miss; + + public function __construct(CacheInterface $pool, string $namespace = '', int $defaultLifetime = 0) + { + parent::__construct($namespace, $defaultLifetime); + + $this->pool = $pool; + $this->miss = new \stdClass(); + } + + /** + * {@inheritdoc} + */ + protected function doFetch(array $ids) + { + foreach ($this->pool->getMultiple($ids, $this->miss) as $key => $value) { + if ($this->miss !== $value) { + yield $key => $value; + } + } + } + + /** + * {@inheritdoc} + */ + protected function doHave(string $id) + { + return $this->pool->has($id); + } + + /** + * {@inheritdoc} + */ + protected function doClear(string $namespace) + { + return $this->pool->clear(); + } + + /** + * {@inheritdoc} + */ + protected function doDelete(array $ids) + { + return $this->pool->deleteMultiple($ids); + } + + /** + * {@inheritdoc} + */ + protected function doSave(array $values, int $lifetime) + { + return $this->pool->setMultiple($values, 0 === $lifetime ? null : $lifetime); + } +} diff --git a/vendor/symfony/cache/Adapter/RedisAdapter.php b/vendor/symfony/cache/Adapter/RedisAdapter.php new file mode 100644 index 0000000..eb5950e --- /dev/null +++ b/vendor/symfony/cache/Adapter/RedisAdapter.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Symfony\Component\Cache\Marshaller\MarshallerInterface; +use Symfony\Component\Cache\Traits\RedisClusterProxy; +use Symfony\Component\Cache\Traits\RedisProxy; +use Symfony\Component\Cache\Traits\RedisTrait; + +class RedisAdapter extends AbstractAdapter +{ + use RedisTrait; + + /** + * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|RedisProxy|RedisClusterProxy $redis The redis client + * @param string $namespace The default namespace + * @param int $defaultLifetime The default lifetime + */ + public function __construct($redis, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null) + { + $this->init($redis, $namespace, $defaultLifetime, $marshaller); + } +} diff --git a/vendor/symfony/cache/Adapter/RedisTagAwareAdapter.php b/vendor/symfony/cache/Adapter/RedisTagAwareAdapter.php new file mode 100644 index 0000000..58456e6 --- /dev/null +++ b/vendor/symfony/cache/Adapter/RedisTagAwareAdapter.php @@ -0,0 +1,317 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Predis\Connection\Aggregate\ClusterInterface; +use Predis\Connection\Aggregate\PredisCluster; +use Predis\Connection\Aggregate\ReplicationInterface; +use Predis\Response\Status; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Exception\LogicException; +use Symfony\Component\Cache\Marshaller\DeflateMarshaller; +use Symfony\Component\Cache\Marshaller\MarshallerInterface; +use Symfony\Component\Cache\Marshaller\TagAwareMarshaller; +use Symfony\Component\Cache\Traits\RedisClusterProxy; +use Symfony\Component\Cache\Traits\RedisProxy; +use Symfony\Component\Cache\Traits\RedisTrait; + +/** + * Stores tag id <> cache id relationship as a Redis Set. + * + * Set (tag relation info) is stored without expiry (non-volatile), while cache always gets an expiry (volatile) even + * if not set by caller. Thus if you configure redis with the right eviction policy you can be safe this tag <> cache + * relationship survives eviction (cache cleanup when Redis runs out of memory). + * + * Redis server 2.8+ with any `volatile-*` eviction policy, OR `noeviction` if you're sure memory will NEVER fill up + * + * Design limitations: + * - Max 4 billion cache keys per cache tag as limited by Redis Set datatype. + * E.g. If you use a "all" items tag for expiry instead of clear(), that limits you to 4 billion cache items also. + * + * @see https://redis.io/topics/lru-cache#eviction-policies Documentation for Redis eviction policies. + * @see https://redis.io/topics/data-types#sets Documentation for Redis Set datatype. + * + * @author Nicolas Grekas + * @author André Rømcke + */ +class RedisTagAwareAdapter extends AbstractTagAwareAdapter +{ + use RedisTrait; + + /** + * On cache items without a lifetime set, we set it to 100 days. This is to make sure cache items are + * preferred to be evicted over tag Sets, if eviction policy is configured according to requirements. + */ + private const DEFAULT_CACHE_TTL = 8640000; + + /** + * @var string|null detected eviction policy used on Redis server + */ + private $redisEvictionPolicy; + + /** + * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|RedisProxy|RedisClusterProxy $redis The redis client + * @param string $namespace The default namespace + * @param int $defaultLifetime The default lifetime + */ + public function __construct($redis, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null) + { + if ($redis instanceof \Predis\ClientInterface && $redis->getConnection() instanceof ClusterInterface && !$redis->getConnection() instanceof PredisCluster) { + throw new InvalidArgumentException(sprintf('Unsupported Predis cluster connection: only "%s" is, "%s" given.', PredisCluster::class, get_debug_type($redis->getConnection()))); + } + + if (\defined('Redis::OPT_COMPRESSION') && ($redis instanceof \Redis || $redis instanceof \RedisArray || $redis instanceof \RedisCluster)) { + $compression = $redis->getOption(\Redis::OPT_COMPRESSION); + + foreach (\is_array($compression) ? $compression : [$compression] as $c) { + if (\Redis::COMPRESSION_NONE !== $c) { + throw new InvalidArgumentException(sprintf('phpredis compression must be disabled when using "%s", use "%s" instead.', static::class, DeflateMarshaller::class)); + } + } + } + + $this->init($redis, $namespace, $defaultLifetime, new TagAwareMarshaller($marshaller)); + } + + /** + * {@inheritdoc} + */ + protected function doSave(array $values, int $lifetime, array $addTagData = [], array $delTagData = []): array + { + $eviction = $this->getRedisEvictionPolicy(); + if ('noeviction' !== $eviction && !str_starts_with($eviction, 'volatile-')) { + throw new LogicException(sprintf('Redis maxmemory-policy setting "%s" is *not* supported by RedisTagAwareAdapter, use "noeviction" or "volatile-*" eviction policies.', $eviction)); + } + + // serialize values + if (!$serialized = $this->marshaller->marshall($values, $failed)) { + return $failed; + } + + // While pipeline isn't supported on RedisCluster, other setups will at least benefit from doing this in one op + $results = $this->pipeline(static function () use ($serialized, $lifetime, $addTagData, $delTagData, $failed) { + // Store cache items, force a ttl if none is set, as there is no MSETEX we need to set each one + foreach ($serialized as $id => $value) { + yield 'setEx' => [ + $id, + 0 >= $lifetime ? self::DEFAULT_CACHE_TTL : $lifetime, + $value, + ]; + } + + // Add and Remove Tags + foreach ($addTagData as $tagId => $ids) { + if (!$failed || $ids = array_diff($ids, $failed)) { + yield 'sAdd' => array_merge([$tagId], $ids); + } + } + + foreach ($delTagData as $tagId => $ids) { + if (!$failed || $ids = array_diff($ids, $failed)) { + yield 'sRem' => array_merge([$tagId], $ids); + } + } + }); + + foreach ($results as $id => $result) { + // Skip results of SADD/SREM operations, they'll be 1 or 0 depending on if set value already existed or not + if (is_numeric($result)) { + continue; + } + // setEx results + if (true !== $result && (!$result instanceof Status || Status::get('OK') !== $result)) { + $failed[] = $id; + } + } + + return $failed; + } + + /** + * {@inheritdoc} + */ + protected function doDeleteYieldTags(array $ids): iterable + { + $lua = <<<'EOLUA' + local v = redis.call('GET', KEYS[1]) + local e = redis.pcall('UNLINK', KEYS[1]) + + if type(e) ~= 'number' then + redis.call('DEL', KEYS[1]) + end + + if not v or v:len() <= 13 or v:byte(1) ~= 0x9D or v:byte(6) ~= 0 or v:byte(10) ~= 0x5F then + return '' + end + + return v:sub(14, 13 + v:byte(13) + v:byte(12) * 256 + v:byte(11) * 65536) +EOLUA; + + $results = $this->pipeline(function () use ($ids, $lua) { + foreach ($ids as $id) { + yield 'eval' => $this->redis instanceof \Predis\ClientInterface ? [$lua, 1, $id] : [$lua, [$id], 1]; + } + }); + + foreach ($results as $id => $result) { + if ($result instanceof \RedisException) { + CacheItem::log($this->logger, 'Failed to delete key "{key}": '.$result->getMessage(), ['key' => substr($id, \strlen($this->namespace)), 'exception' => $result]); + + continue; + } + + try { + yield $id => !\is_string($result) || '' === $result ? [] : $this->marshaller->unmarshall($result); + } catch (\Exception $e) { + yield $id => []; + } + } + } + + /** + * {@inheritdoc} + */ + protected function doDeleteTagRelations(array $tagData): bool + { + $results = $this->pipeline(static function () use ($tagData) { + foreach ($tagData as $tagId => $idList) { + array_unshift($idList, $tagId); + yield 'sRem' => $idList; + } + }); + foreach ($results as $result) { + // no-op + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doInvalidate(array $tagIds): bool + { + // This script scans the set of items linked to tag: it empties the set + // and removes the linked items. When the set is still not empty after + // the scan, it means we're in cluster mode and that the linked items + // are on other nodes: we move the links to a temporary set and we + // gargage collect that set from the client side. + + $lua = <<<'EOLUA' + redis.replicate_commands() + + local cursor = '0' + local id = KEYS[1] + repeat + local result = redis.call('SSCAN', id, cursor, 'COUNT', 5000); + cursor = result[1]; + local rems = {} + + for _, v in ipairs(result[2]) do + local ok, _ = pcall(redis.call, 'DEL', ARGV[1]..v) + if ok then + table.insert(rems, v) + end + end + if 0 < #rems then + redis.call('SREM', id, unpack(rems)) + end + until '0' == cursor; + + redis.call('SUNIONSTORE', '{'..id..'}'..id, id) + redis.call('DEL', id) + + return redis.call('SSCAN', '{'..id..'}'..id, '0', 'COUNT', 5000) +EOLUA; + + $results = $this->pipeline(function () use ($tagIds, $lua) { + if ($this->redis instanceof \Predis\ClientInterface) { + $prefix = $this->redis->getOptions()->prefix ? $this->redis->getOptions()->prefix->getPrefix() : ''; + } elseif (\is_array($prefix = $this->redis->getOption(\Redis::OPT_PREFIX) ?? '')) { + $prefix = current($prefix); + } + + foreach ($tagIds as $id) { + yield 'eval' => $this->redis instanceof \Predis\ClientInterface ? [$lua, 1, $id, $prefix] : [$lua, [$id, $prefix], 1]; + } + }); + + $lua = <<<'EOLUA' + redis.replicate_commands() + + local id = KEYS[1] + local cursor = table.remove(ARGV) + redis.call('SREM', '{'..id..'}'..id, unpack(ARGV)) + + return redis.call('SSCAN', '{'..id..'}'..id, cursor, 'COUNT', 5000) +EOLUA; + + $success = true; + foreach ($results as $id => $values) { + if ($values instanceof \RedisException) { + CacheItem::log($this->logger, 'Failed to invalidate key "{key}": '.$values->getMessage(), ['key' => substr($id, \strlen($this->namespace)), 'exception' => $values]); + $success = false; + + continue; + } + + [$cursor, $ids] = $values; + + while ($ids || '0' !== $cursor) { + $this->doDelete($ids); + + $evalArgs = [$id, $cursor]; + array_splice($evalArgs, 1, 0, $ids); + + if ($this->redis instanceof \Predis\ClientInterface) { + array_unshift($evalArgs, $lua, 1); + } else { + $evalArgs = [$lua, $evalArgs, 1]; + } + + $results = $this->pipeline(function () use ($evalArgs) { + yield 'eval' => $evalArgs; + }); + + foreach ($results as [$cursor, $ids]) { + // no-op + } + } + } + + return $success; + } + + private function getRedisEvictionPolicy(): string + { + if (null !== $this->redisEvictionPolicy) { + return $this->redisEvictionPolicy; + } + + $hosts = $this->getHosts(); + $host = reset($hosts); + if ($host instanceof \Predis\Client && $host->getConnection() instanceof ReplicationInterface) { + // Predis supports info command only on the master in replication environments + $hosts = [$host->getClientFor('master')]; + } + + foreach ($hosts as $host) { + $info = $host->info('Memory'); + $info = $info['Memory'] ?? $info; + + return $this->redisEvictionPolicy = $info['maxmemory_policy']; + } + + return $this->redisEvictionPolicy = ''; + } +} diff --git a/vendor/symfony/cache/Adapter/TagAwareAdapter.php b/vendor/symfony/cache/Adapter/TagAwareAdapter.php new file mode 100644 index 0000000..ff22e5a --- /dev/null +++ b/vendor/symfony/cache/Adapter/TagAwareAdapter.php @@ -0,0 +1,428 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Cache\CacheItemInterface; +use Psr\Cache\InvalidArgumentException; +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerAwareTrait; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Cache\Traits\ContractsTrait; +use Symfony\Component\Cache\Traits\ProxyTrait; +use Symfony\Contracts\Cache\TagAwareCacheInterface; + +/** + * @author Nicolas Grekas + */ +class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface, PruneableInterface, ResettableInterface, LoggerAwareInterface +{ + use ContractsTrait; + use LoggerAwareTrait; + use ProxyTrait; + + public const TAGS_PREFIX = "\0tags\0"; + + private $deferred = []; + private $tags; + private $knownTagVersions = []; + private $knownTagVersionsTtl; + + private static $createCacheItem; + private static $setCacheItemTags; + private static $getTagsByKey; + private static $saveTags; + + public function __construct(AdapterInterface $itemsPool, AdapterInterface $tagsPool = null, float $knownTagVersionsTtl = 0.15) + { + $this->pool = $itemsPool; + $this->tags = $tagsPool ?: $itemsPool; + $this->knownTagVersionsTtl = $knownTagVersionsTtl; + self::$createCacheItem ?? self::$createCacheItem = \Closure::bind( + static function ($key, $value, CacheItem $protoItem) { + $item = new CacheItem(); + $item->key = $key; + $item->value = $value; + $item->expiry = $protoItem->expiry; + $item->poolHash = $protoItem->poolHash; + + return $item; + }, + null, + CacheItem::class + ); + self::$setCacheItemTags ?? self::$setCacheItemTags = \Closure::bind( + static function (CacheItem $item, $key, array &$itemTags) { + $item->isTaggable = true; + if (!$item->isHit) { + return $item; + } + if (isset($itemTags[$key])) { + foreach ($itemTags[$key] as $tag => $version) { + $item->metadata[CacheItem::METADATA_TAGS][$tag] = $tag; + } + unset($itemTags[$key]); + } else { + $item->value = null; + $item->isHit = false; + } + + return $item; + }, + null, + CacheItem::class + ); + self::$getTagsByKey ?? self::$getTagsByKey = \Closure::bind( + static function ($deferred) { + $tagsByKey = []; + foreach ($deferred as $key => $item) { + $tagsByKey[$key] = $item->newMetadata[CacheItem::METADATA_TAGS] ?? []; + $item->metadata = $item->newMetadata; + } + + return $tagsByKey; + }, + null, + CacheItem::class + ); + self::$saveTags ?? self::$saveTags = \Closure::bind( + static function (AdapterInterface $tagsAdapter, array $tags) { + ksort($tags); + + foreach ($tags as $v) { + $v->expiry = 0; + $tagsAdapter->saveDeferred($v); + } + + return $tagsAdapter->commit(); + }, + null, + CacheItem::class + ); + } + + /** + * {@inheritdoc} + */ + public function invalidateTags(array $tags) + { + $ids = []; + foreach ($tags as $tag) { + \assert('' !== CacheItem::validateKey($tag)); + unset($this->knownTagVersions[$tag]); + $ids[] = $tag.static::TAGS_PREFIX; + } + + return !$tags || $this->tags->deleteItems($ids); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function hasItem($key) + { + if (\is_string($key) && isset($this->deferred[$key])) { + $this->commit(); + } + + if (!$this->pool->hasItem($key)) { + return false; + } + + $itemTags = $this->pool->getItem(static::TAGS_PREFIX.$key); + + if (!$itemTags->isHit()) { + return false; + } + + if (!$itemTags = $itemTags->get()) { + return true; + } + + foreach ($this->getTagVersions([$itemTags]) as $tag => $version) { + if ($itemTags[$tag] !== $version) { + return false; + } + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function getItem($key) + { + foreach ($this->getItems([$key]) as $item) { + return $item; + } + + return null; + } + + /** + * {@inheritdoc} + */ + public function getItems(array $keys = []) + { + $tagKeys = []; + $commit = false; + + foreach ($keys as $key) { + if ('' !== $key && \is_string($key)) { + $commit = $commit || isset($this->deferred[$key]); + $key = static::TAGS_PREFIX.$key; + $tagKeys[$key] = $key; + } + } + + if ($commit) { + $this->commit(); + } + + try { + $items = $this->pool->getItems($tagKeys + $keys); + } catch (InvalidArgumentException $e) { + $this->pool->getItems($keys); // Should throw an exception + + throw $e; + } + + return $this->generateItems($items, $tagKeys); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function clear(string $prefix = '') + { + if ('' !== $prefix) { + foreach ($this->deferred as $key => $item) { + if (str_starts_with($key, $prefix)) { + unset($this->deferred[$key]); + } + } + } else { + $this->deferred = []; + } + + if ($this->pool instanceof AdapterInterface) { + return $this->pool->clear($prefix); + } + + return $this->pool->clear(); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function deleteItem($key) + { + return $this->deleteItems([$key]); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function deleteItems(array $keys) + { + foreach ($keys as $key) { + if ('' !== $key && \is_string($key)) { + $keys[] = static::TAGS_PREFIX.$key; + } + } + + return $this->pool->deleteItems($keys); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function save(CacheItemInterface $item) + { + if (!$item instanceof CacheItem) { + return false; + } + $this->deferred[$item->getKey()] = $item; + + return $this->commit(); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function saveDeferred(CacheItemInterface $item) + { + if (!$item instanceof CacheItem) { + return false; + } + $this->deferred[$item->getKey()] = $item; + + return true; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function commit() + { + if (!$this->deferred) { + return true; + } + + $ok = true; + foreach ($this->deferred as $key => $item) { + if (!$this->pool->saveDeferred($item)) { + unset($this->deferred[$key]); + $ok = false; + } + } + + $items = $this->deferred; + $tagsByKey = (self::$getTagsByKey)($items); + $this->deferred = []; + + $tagVersions = $this->getTagVersions($tagsByKey); + $f = self::$createCacheItem; + + foreach ($tagsByKey as $key => $tags) { + $this->pool->saveDeferred($f(static::TAGS_PREFIX.$key, array_intersect_key($tagVersions, $tags), $items[$key])); + } + + return $this->pool->commit() && $ok; + } + + /** + * @return array + */ + public function __sleep() + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + $this->commit(); + } + + private function generateItems(iterable $items, array $tagKeys): \Generator + { + $bufferedItems = $itemTags = []; + $f = self::$setCacheItemTags; + + foreach ($items as $key => $item) { + if (!$tagKeys) { + yield $key => $f($item, static::TAGS_PREFIX.$key, $itemTags); + continue; + } + if (!isset($tagKeys[$key])) { + $bufferedItems[$key] = $item; + continue; + } + + unset($tagKeys[$key]); + + if ($item->isHit()) { + $itemTags[$key] = $item->get() ?: []; + } + + if (!$tagKeys) { + $tagVersions = $this->getTagVersions($itemTags); + + foreach ($itemTags as $key => $tags) { + foreach ($tags as $tag => $version) { + if ($tagVersions[$tag] !== $version) { + unset($itemTags[$key]); + continue 2; + } + } + } + $tagVersions = $tagKeys = null; + + foreach ($bufferedItems as $key => $item) { + yield $key => $f($item, static::TAGS_PREFIX.$key, $itemTags); + } + $bufferedItems = null; + } + } + } + + private function getTagVersions(array $tagsByKey) + { + $tagVersions = []; + $fetchTagVersions = false; + + foreach ($tagsByKey as $tags) { + $tagVersions += $tags; + + foreach ($tags as $tag => $version) { + if ($tagVersions[$tag] !== $version) { + unset($this->knownTagVersions[$tag]); + } + } + } + + if (!$tagVersions) { + return []; + } + + $now = microtime(true); + $tags = []; + foreach ($tagVersions as $tag => $version) { + $tags[$tag.static::TAGS_PREFIX] = $tag; + if ($fetchTagVersions || ($this->knownTagVersions[$tag][1] ?? null) !== $version || $now - $this->knownTagVersions[$tag][0] >= $this->knownTagVersionsTtl) { + // reuse previously fetched tag versions up to the ttl + $fetchTagVersions = true; + } + } + + if (!$fetchTagVersions) { + return $tagVersions; + } + + $newTags = []; + $newVersion = null; + foreach ($this->tags->getItems(array_keys($tags)) as $tag => $version) { + if (!$version->isHit()) { + $newTags[$tag] = $version->set($newVersion ?? $newVersion = random_int(\PHP_INT_MIN, \PHP_INT_MAX)); + } + $tagVersions[$tag = $tags[$tag]] = $version->get(); + $this->knownTagVersions[$tag] = [$now, $tagVersions[$tag]]; + } + + if ($newTags) { + (self::$saveTags)($this->tags, $newTags); + } + + return $tagVersions; + } +} diff --git a/vendor/symfony/cache/Adapter/TagAwareAdapterInterface.php b/vendor/symfony/cache/Adapter/TagAwareAdapterInterface.php new file mode 100644 index 0000000..afa18d3 --- /dev/null +++ b/vendor/symfony/cache/Adapter/TagAwareAdapterInterface.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Cache\InvalidArgumentException; + +/** + * Interface for invalidating cached items using tags. + * + * @author Nicolas Grekas + */ +interface TagAwareAdapterInterface extends AdapterInterface +{ + /** + * Invalidates cached items using tags. + * + * @param string[] $tags An array of tags to invalidate + * + * @return bool + * + * @throws InvalidArgumentException When $tags is not valid + */ + public function invalidateTags(array $tags); +} diff --git a/vendor/symfony/cache/Adapter/TraceableAdapter.php b/vendor/symfony/cache/Adapter/TraceableAdapter.php new file mode 100644 index 0000000..4b06557 --- /dev/null +++ b/vendor/symfony/cache/Adapter/TraceableAdapter.php @@ -0,0 +1,295 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\Cache\CacheItemInterface; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\Cache\ResettableInterface; +use Symfony\Contracts\Cache\CacheInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * An adapter that collects data about all cache calls. + * + * @author Aaron Scherer + * @author Tobias Nyholm + * @author Nicolas Grekas + */ +class TraceableAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface +{ + protected $pool; + private $calls = []; + + public function __construct(AdapterInterface $pool) + { + $this->pool = $pool; + } + + /** + * {@inheritdoc} + */ + public function get(string $key, callable $callback, float $beta = null, array &$metadata = null) + { + if (!$this->pool instanceof CacheInterface) { + throw new \BadMethodCallException(sprintf('Cannot call "%s::get()": this class doesn\'t implement "%s".', get_debug_type($this->pool), CacheInterface::class)); + } + + $isHit = true; + $callback = function (CacheItem $item, bool &$save) use ($callback, &$isHit) { + $isHit = $item->isHit(); + + return $callback($item, $save); + }; + + $event = $this->start(__FUNCTION__); + try { + $value = $this->pool->get($key, $callback, $beta, $metadata); + $event->result[$key] = get_debug_type($value); + } finally { + $event->end = microtime(true); + } + if ($isHit) { + ++$event->hits; + } else { + ++$event->misses; + } + + return $value; + } + + /** + * {@inheritdoc} + */ + public function getItem($key) + { + $event = $this->start(__FUNCTION__); + try { + $item = $this->pool->getItem($key); + } finally { + $event->end = microtime(true); + } + if ($event->result[$key] = $item->isHit()) { + ++$event->hits; + } else { + ++$event->misses; + } + + return $item; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function hasItem($key) + { + $event = $this->start(__FUNCTION__); + try { + return $event->result[$key] = $this->pool->hasItem($key); + } finally { + $event->end = microtime(true); + } + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function deleteItem($key) + { + $event = $this->start(__FUNCTION__); + try { + return $event->result[$key] = $this->pool->deleteItem($key); + } finally { + $event->end = microtime(true); + } + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function save(CacheItemInterface $item) + { + $event = $this->start(__FUNCTION__); + try { + return $event->result[$item->getKey()] = $this->pool->save($item); + } finally { + $event->end = microtime(true); + } + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function saveDeferred(CacheItemInterface $item) + { + $event = $this->start(__FUNCTION__); + try { + return $event->result[$item->getKey()] = $this->pool->saveDeferred($item); + } finally { + $event->end = microtime(true); + } + } + + /** + * {@inheritdoc} + */ + public function getItems(array $keys = []) + { + $event = $this->start(__FUNCTION__); + try { + $result = $this->pool->getItems($keys); + } finally { + $event->end = microtime(true); + } + $f = function () use ($result, $event) { + $event->result = []; + foreach ($result as $key => $item) { + if ($event->result[$key] = $item->isHit()) { + ++$event->hits; + } else { + ++$event->misses; + } + yield $key => $item; + } + }; + + return $f(); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function clear(string $prefix = '') + { + $event = $this->start(__FUNCTION__); + try { + if ($this->pool instanceof AdapterInterface) { + return $event->result = $this->pool->clear($prefix); + } + + return $event->result = $this->pool->clear(); + } finally { + $event->end = microtime(true); + } + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function deleteItems(array $keys) + { + $event = $this->start(__FUNCTION__); + $event->result['keys'] = $keys; + try { + return $event->result['result'] = $this->pool->deleteItems($keys); + } finally { + $event->end = microtime(true); + } + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function commit() + { + $event = $this->start(__FUNCTION__); + try { + return $event->result = $this->pool->commit(); + } finally { + $event->end = microtime(true); + } + } + + /** + * {@inheritdoc} + */ + public function prune() + { + if (!$this->pool instanceof PruneableInterface) { + return false; + } + $event = $this->start(__FUNCTION__); + try { + return $event->result = $this->pool->prune(); + } finally { + $event->end = microtime(true); + } + } + + /** + * {@inheritdoc} + */ + public function reset() + { + if ($this->pool instanceof ResetInterface) { + $this->pool->reset(); + } + + $this->clearCalls(); + } + + /** + * {@inheritdoc} + */ + public function delete(string $key): bool + { + $event = $this->start(__FUNCTION__); + try { + return $event->result[$key] = $this->pool->deleteItem($key); + } finally { + $event->end = microtime(true); + } + } + + public function getCalls() + { + return $this->calls; + } + + public function clearCalls() + { + $this->calls = []; + } + + protected function start(string $name) + { + $this->calls[] = $event = new TraceableAdapterEvent(); + $event->name = $name; + $event->start = microtime(true); + + return $event; + } +} + +class TraceableAdapterEvent +{ + public $name; + public $start; + public $end; + public $result; + public $hits = 0; + public $misses = 0; +} diff --git a/vendor/symfony/cache/Adapter/TraceableTagAwareAdapter.php b/vendor/symfony/cache/Adapter/TraceableTagAwareAdapter.php new file mode 100644 index 0000000..69461b8 --- /dev/null +++ b/vendor/symfony/cache/Adapter/TraceableTagAwareAdapter.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Symfony\Contracts\Cache\TagAwareCacheInterface; + +/** + * @author Robin Chalas + */ +class TraceableTagAwareAdapter extends TraceableAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface +{ + public function __construct(TagAwareAdapterInterface $pool) + { + parent::__construct($pool); + } + + /** + * {@inheritdoc} + */ + public function invalidateTags(array $tags) + { + $event = $this->start(__FUNCTION__); + try { + return $event->result = $this->pool->invalidateTags($tags); + } finally { + $event->end = microtime(true); + } + } +} diff --git a/vendor/symfony/cache/CHANGELOG.md b/vendor/symfony/cache/CHANGELOG.md new file mode 100644 index 0000000..0654b03 --- /dev/null +++ b/vendor/symfony/cache/CHANGELOG.md @@ -0,0 +1,109 @@ +CHANGELOG +========= + +5.4 +--- + + * Make `LockRegistry` use semaphores when possible + * Deprecate `DoctrineProvider` and `DoctrineAdapter` because these classes have been added to the `doctrine/cache` package + * Add `DoctrineDbalAdapter` identical to `PdoAdapter` for `Doctrine\DBAL\Connection` or DBAL URL + * Deprecate usage of `PdoAdapter` with `Doctrine\DBAL\Connection` or DBAL URL + +5.3 +--- + + * added support for connecting to Redis Sentinel clusters when using the Redis PHP extension + * add support for a custom serializer to the `ApcuAdapter` class + +5.2.0 +----- + + * added integration with Messenger to allow computing cached values in a worker + * allow ISO 8601 time intervals to specify default lifetime + +5.1.0 +----- + + * added max-items + LRU + max-lifetime capabilities to `ArrayCache` + * added `CouchbaseBucketAdapter` + * added context `cache-adapter` to log messages + +5.0.0 +----- + + * removed all PSR-16 implementations in the `Simple` namespace + * removed `SimpleCacheAdapter` + * removed `AbstractAdapter::unserialize()` + * removed `CacheItem::getPreviousTags()` + +4.4.0 +----- + + * added support for connecting to Redis Sentinel clusters + * added argument `$prefix` to `AdapterInterface::clear()` + * improved `RedisTagAwareAdapter` to support Redis server >= 2.8 and up to 4B items per tag + * added `TagAwareMarshaller` for optimized data storage when using `AbstractTagAwareAdapter` + * added `DeflateMarshaller` to compress serialized values + * removed support for phpredis 4 `compression` + * [BC BREAK] `RedisTagAwareAdapter` is not compatible with `RedisCluster` from `Predis` anymore, use `phpredis` instead + * Marked the `CacheDataCollector` class as `@final`. + * added `SodiumMarshaller` to encrypt/decrypt values using libsodium + +4.3.0 +----- + + * removed `psr/simple-cache` dependency, run `composer require psr/simple-cache` if you need it + * deprecated all PSR-16 adapters, use `Psr16Cache` or `Symfony\Contracts\Cache\CacheInterface` implementations instead + * deprecated `SimpleCacheAdapter`, use `Psr16Adapter` instead + +4.2.0 +----- + + * added support for connecting to Redis clusters via DSN + * added support for configuring multiple Memcached servers via DSN + * added `MarshallerInterface` and `DefaultMarshaller` to allow changing the serializer and provide one that automatically uses igbinary when available + * implemented `CacheInterface`, which provides stampede protection via probabilistic early expiration and should become the preferred way to use a cache + * added sub-second expiry accuracy for backends that support it + * added support for phpredis 4 `compression` and `tcp_keepalive` options + * added automatic table creation when using Doctrine DBAL with PDO-based backends + * throw `LogicException` when `CacheItem::tag()` is called on an item coming from a non tag-aware pool + * deprecated `CacheItem::getPreviousTags()`, use `CacheItem::getMetadata()` instead + * deprecated the `AbstractAdapter::unserialize()` and `AbstractCache::unserialize()` methods + * added `CacheCollectorPass` (originally in `FrameworkBundle`) + * added `CachePoolClearerPass` (originally in `FrameworkBundle`) + * added `CachePoolPass` (originally in `FrameworkBundle`) + * added `CachePoolPrunerPass` (originally in `FrameworkBundle`) + +3.4.0 +----- + + * added using options from Memcached DSN + * added PruneableInterface so PSR-6 or PSR-16 cache implementations can declare support for manual stale cache pruning + * added prune logic to FilesystemTrait, PhpFilesTrait, PdoTrait, TagAwareAdapter and ChainTrait + * now FilesystemAdapter, PhpFilesAdapter, FilesystemCache, PhpFilesCache, PdoAdapter, PdoCache, ChainAdapter, and + ChainCache implement PruneableInterface and support manual stale cache pruning + +3.3.0 +----- + + * added CacheItem::getPreviousTags() to get bound tags coming from the pool storage if any + * added PSR-16 "Simple Cache" implementations for all existing PSR-6 adapters + * added Psr6Cache and SimpleCacheAdapter for bidirectional interoperability between PSR-6 and PSR-16 + * added MemcachedAdapter (PSR-6) and MemcachedCache (PSR-16) + * added TraceableAdapter (PSR-6) and TraceableCache (PSR-16) + +3.2.0 +----- + + * added TagAwareAdapter for tags-based invalidation + * added PdoAdapter with PDO and Doctrine DBAL support + * added PhpArrayAdapter and PhpFilesAdapter for OPcache-backed shared memory storage (PHP 7+ only) + * added NullAdapter + +3.1.0 +----- + + * added the component with strict PSR-6 implementations + * added ApcuAdapter, ArrayAdapter, FilesystemAdapter and RedisAdapter + * added AbstractAdapter, ChainAdapter and ProxyAdapter + * added DoctrineAdapter and DoctrineProvider for bidirectional interoperability with Doctrine Cache diff --git a/vendor/symfony/cache/CacheItem.php b/vendor/symfony/cache/CacheItem.php new file mode 100644 index 0000000..cf39b4c --- /dev/null +++ b/vendor/symfony/cache/CacheItem.php @@ -0,0 +1,192 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache; + +use Psr\Log\LoggerInterface; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Exception\LogicException; +use Symfony\Contracts\Cache\ItemInterface; + +/** + * @author Nicolas Grekas + */ +final class CacheItem implements ItemInterface +{ + private const METADATA_EXPIRY_OFFSET = 1527506807; + + protected $key; + protected $value; + protected $isHit = false; + protected $expiry; + protected $metadata = []; + protected $newMetadata = []; + protected $innerItem; + protected $poolHash; + protected $isTaggable = false; + + /** + * {@inheritdoc} + */ + public function getKey(): string + { + return $this->key; + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function get() + { + return $this->value; + } + + /** + * {@inheritdoc} + */ + public function isHit(): bool + { + return $this->isHit; + } + + /** + * {@inheritdoc} + * + * @return $this + */ + public function set($value): self + { + $this->value = $value; + + return $this; + } + + /** + * {@inheritdoc} + * + * @return $this + */ + public function expiresAt($expiration): self + { + if (null === $expiration) { + $this->expiry = null; + } elseif ($expiration instanceof \DateTimeInterface) { + $this->expiry = (float) $expiration->format('U.u'); + } else { + throw new InvalidArgumentException(sprintf('Expiration date must implement DateTimeInterface or be null, "%s" given.', get_debug_type($expiration))); + } + + return $this; + } + + /** + * {@inheritdoc} + * + * @return $this + */ + public function expiresAfter($time): self + { + if (null === $time) { + $this->expiry = null; + } elseif ($time instanceof \DateInterval) { + $this->expiry = microtime(true) + \DateTime::createFromFormat('U', 0)->add($time)->format('U.u'); + } elseif (\is_int($time)) { + $this->expiry = $time + microtime(true); + } else { + throw new InvalidArgumentException(sprintf('Expiration date must be an integer, a DateInterval or null, "%s" given.', get_debug_type($time))); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function tag($tags): ItemInterface + { + if (!$this->isTaggable) { + throw new LogicException(sprintf('Cache item "%s" comes from a non tag-aware pool: you cannot tag it.', $this->key)); + } + if (!is_iterable($tags)) { + $tags = [$tags]; + } + foreach ($tags as $tag) { + if (!\is_string($tag) && !(\is_object($tag) && method_exists($tag, '__toString'))) { + throw new InvalidArgumentException(sprintf('Cache tag must be string or object that implements __toString(), "%s" given.', \is_object($tag) ? \get_class($tag) : \gettype($tag))); + } + $tag = (string) $tag; + if (isset($this->newMetadata[self::METADATA_TAGS][$tag])) { + continue; + } + if ('' === $tag) { + throw new InvalidArgumentException('Cache tag length must be greater than zero.'); + } + if (false !== strpbrk($tag, self::RESERVED_CHARACTERS)) { + throw new InvalidArgumentException(sprintf('Cache tag "%s" contains reserved characters "%s".', $tag, self::RESERVED_CHARACTERS)); + } + $this->newMetadata[self::METADATA_TAGS][$tag] = $tag; + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getMetadata(): array + { + return $this->metadata; + } + + /** + * Validates a cache key according to PSR-6. + * + * @param mixed $key The key to validate + * + * @throws InvalidArgumentException When $key is not valid + */ + public static function validateKey($key): string + { + if (!\is_string($key)) { + throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', get_debug_type($key))); + } + if ('' === $key) { + throw new InvalidArgumentException('Cache key length must be greater than zero.'); + } + if (false !== strpbrk($key, self::RESERVED_CHARACTERS)) { + throw new InvalidArgumentException(sprintf('Cache key "%s" contains reserved characters "%s".', $key, self::RESERVED_CHARACTERS)); + } + + return $key; + } + + /** + * Internal logging helper. + * + * @internal + */ + public static function log(?LoggerInterface $logger, string $message, array $context = []) + { + if ($logger) { + $logger->warning($message, $context); + } else { + $replace = []; + foreach ($context as $k => $v) { + if (is_scalar($v)) { + $replace['{'.$k.'}'] = $v; + } + } + @trigger_error(strtr($message, $replace), \E_USER_WARNING); + } + } +} diff --git a/vendor/symfony/cache/DataCollector/CacheDataCollector.php b/vendor/symfony/cache/DataCollector/CacheDataCollector.php new file mode 100644 index 0000000..9590436 --- /dev/null +++ b/vendor/symfony/cache/DataCollector/CacheDataCollector.php @@ -0,0 +1,185 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\DataCollector; + +use Symfony\Component\Cache\Adapter\TraceableAdapter; +use Symfony\Component\Cache\Adapter\TraceableAdapterEvent; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\DataCollector\DataCollector; +use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface; + +/** + * @author Aaron Scherer + * @author Tobias Nyholm + * + * @final + */ +class CacheDataCollector extends DataCollector implements LateDataCollectorInterface +{ + /** + * @var TraceableAdapter[] + */ + private $instances = []; + + public function addInstance(string $name, TraceableAdapter $instance) + { + $this->instances[$name] = $instance; + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Throwable $exception = null) + { + $empty = ['calls' => [], 'config' => [], 'options' => [], 'statistics' => []]; + $this->data = ['instances' => $empty, 'total' => $empty]; + foreach ($this->instances as $name => $instance) { + $this->data['instances']['calls'][$name] = $instance->getCalls(); + } + + $this->data['instances']['statistics'] = $this->calculateStatistics(); + $this->data['total']['statistics'] = $this->calculateTotalStatistics(); + } + + public function reset() + { + $this->data = []; + foreach ($this->instances as $instance) { + $instance->clearCalls(); + } + } + + public function lateCollect() + { + $this->data['instances']['calls'] = $this->cloneVar($this->data['instances']['calls']); + } + + /** + * {@inheritdoc} + */ + public function getName(): string + { + return 'cache'; + } + + /** + * Method returns amount of logged Cache reads: "get" calls. + */ + public function getStatistics(): array + { + return $this->data['instances']['statistics']; + } + + /** + * Method returns the statistic totals. + */ + public function getTotals(): array + { + return $this->data['total']['statistics']; + } + + /** + * Method returns all logged Cache call objects. + * + * @return mixed + */ + public function getCalls() + { + return $this->data['instances']['calls']; + } + + private function calculateStatistics(): array + { + $statistics = []; + foreach ($this->data['instances']['calls'] as $name => $calls) { + $statistics[$name] = [ + 'calls' => 0, + 'time' => 0, + 'reads' => 0, + 'writes' => 0, + 'deletes' => 0, + 'hits' => 0, + 'misses' => 0, + ]; + /** @var TraceableAdapterEvent $call */ + foreach ($calls as $call) { + ++$statistics[$name]['calls']; + $statistics[$name]['time'] += $call->end - $call->start; + if ('get' === $call->name) { + ++$statistics[$name]['reads']; + if ($call->hits) { + ++$statistics[$name]['hits']; + } else { + ++$statistics[$name]['misses']; + ++$statistics[$name]['writes']; + } + } elseif ('getItem' === $call->name) { + ++$statistics[$name]['reads']; + if ($call->hits) { + ++$statistics[$name]['hits']; + } else { + ++$statistics[$name]['misses']; + } + } elseif ('getItems' === $call->name) { + $statistics[$name]['reads'] += $call->hits + $call->misses; + $statistics[$name]['hits'] += $call->hits; + $statistics[$name]['misses'] += $call->misses; + } elseif ('hasItem' === $call->name) { + ++$statistics[$name]['reads']; + if (false === $call->result) { + ++$statistics[$name]['misses']; + } else { + ++$statistics[$name]['hits']; + } + } elseif ('save' === $call->name) { + ++$statistics[$name]['writes']; + } elseif ('deleteItem' === $call->name) { + ++$statistics[$name]['deletes']; + } + } + if ($statistics[$name]['reads']) { + $statistics[$name]['hit_read_ratio'] = round(100 * $statistics[$name]['hits'] / $statistics[$name]['reads'], 2); + } else { + $statistics[$name]['hit_read_ratio'] = null; + } + } + + return $statistics; + } + + private function calculateTotalStatistics(): array + { + $statistics = $this->getStatistics(); + $totals = [ + 'calls' => 0, + 'time' => 0, + 'reads' => 0, + 'writes' => 0, + 'deletes' => 0, + 'hits' => 0, + 'misses' => 0, + ]; + foreach ($statistics as $name => $values) { + foreach ($totals as $key => $value) { + $totals[$key] += $statistics[$name][$key]; + } + } + if ($totals['reads']) { + $totals['hit_read_ratio'] = round(100 * $totals['hits'] / $totals['reads'], 2); + } else { + $totals['hit_read_ratio'] = null; + } + + return $totals; + } +} diff --git a/vendor/symfony/cache/DependencyInjection/CacheCollectorPass.php b/vendor/symfony/cache/DependencyInjection/CacheCollectorPass.php new file mode 100644 index 0000000..843232e --- /dev/null +++ b/vendor/symfony/cache/DependencyInjection/CacheCollectorPass.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\DependencyInjection; + +use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface; +use Symfony\Component\Cache\Adapter\TraceableAdapter; +use Symfony\Component\Cache\Adapter\TraceableTagAwareAdapter; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Inject a data collector to all the cache services to be able to get detailed statistics. + * + * @author Tobias Nyholm + */ +class CacheCollectorPass implements CompilerPassInterface +{ + private $dataCollectorCacheId; + private $cachePoolTag; + private $cachePoolRecorderInnerSuffix; + + public function __construct(string $dataCollectorCacheId = 'data_collector.cache', string $cachePoolTag = 'cache.pool', string $cachePoolRecorderInnerSuffix = '.recorder_inner') + { + if (0 < \func_num_args()) { + trigger_deprecation('symfony/cache', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); + } + + $this->dataCollectorCacheId = $dataCollectorCacheId; + $this->cachePoolTag = $cachePoolTag; + $this->cachePoolRecorderInnerSuffix = $cachePoolRecorderInnerSuffix; + } + + /** + * {@inheritdoc} + */ + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition($this->dataCollectorCacheId)) { + return; + } + + foreach ($container->findTaggedServiceIds($this->cachePoolTag) as $id => $attributes) { + $poolName = $attributes[0]['name'] ?? $id; + + $this->addToCollector($id, $poolName, $container); + } + } + + private function addToCollector(string $id, string $name, ContainerBuilder $container) + { + $definition = $container->getDefinition($id); + if ($definition->isAbstract()) { + return; + } + + $collectorDefinition = $container->getDefinition($this->dataCollectorCacheId); + $recorder = new Definition(is_subclass_of($definition->getClass(), TagAwareAdapterInterface::class) ? TraceableTagAwareAdapter::class : TraceableAdapter::class); + $recorder->setTags($definition->getTags()); + if (!$definition->isPublic() || !$definition->isPrivate()) { + $recorder->setPublic($definition->isPublic()); + } + $recorder->setArguments([new Reference($innerId = $id.$this->cachePoolRecorderInnerSuffix)]); + + foreach ($definition->getMethodCalls() as [$method, $args]) { + if ('setCallbackWrapper' !== $method || !$args[0] instanceof Definition || !($args[0]->getArguments()[2] ?? null) instanceof Definition) { + continue; + } + if ([new Reference($id), 'setCallbackWrapper'] == $args[0]->getArguments()[2]->getFactory()) { + $args[0]->getArguments()[2]->setFactory([new Reference($innerId), 'setCallbackWrapper']); + } + } + + $definition->setTags([]); + $definition->setPublic(false); + + $container->setDefinition($innerId, $definition); + $container->setDefinition($id, $recorder); + + // Tell the collector to add the new instance + $collectorDefinition->addMethodCall('addInstance', [$name, new Reference($id)]); + $collectorDefinition->setPublic(false); + } +} diff --git a/vendor/symfony/cache/DependencyInjection/CachePoolClearerPass.php b/vendor/symfony/cache/DependencyInjection/CachePoolClearerPass.php new file mode 100644 index 0000000..c9b04ad --- /dev/null +++ b/vendor/symfony/cache/DependencyInjection/CachePoolClearerPass.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * @author Nicolas Grekas + */ +class CachePoolClearerPass implements CompilerPassInterface +{ + private $cachePoolClearerTag; + + public function __construct(string $cachePoolClearerTag = 'cache.pool.clearer') + { + if (0 < \func_num_args()) { + trigger_deprecation('symfony/cache', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); + } + + $this->cachePoolClearerTag = $cachePoolClearerTag; + } + + /** + * {@inheritdoc} + */ + public function process(ContainerBuilder $container) + { + $container->getParameterBag()->remove('cache.prefix.seed'); + + foreach ($container->findTaggedServiceIds($this->cachePoolClearerTag) as $id => $attr) { + $clearer = $container->getDefinition($id); + $pools = []; + foreach ($clearer->getArgument(0) as $name => $ref) { + if ($container->hasDefinition($ref)) { + $pools[$name] = new Reference($ref); + } + } + $clearer->replaceArgument(0, $pools); + } + } +} diff --git a/vendor/symfony/cache/DependencyInjection/CachePoolPass.php b/vendor/symfony/cache/DependencyInjection/CachePoolPass.php new file mode 100644 index 0000000..14ac2bd --- /dev/null +++ b/vendor/symfony/cache/DependencyInjection/CachePoolPass.php @@ -0,0 +1,274 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\DependencyInjection; + +use Symfony\Component\Cache\Adapter\AbstractAdapter; +use Symfony\Component\Cache\Adapter\ArrayAdapter; +use Symfony\Component\Cache\Adapter\ChainAdapter; +use Symfony\Component\Cache\Adapter\NullAdapter; +use Symfony\Component\Cache\Adapter\ParameterNormalizer; +use Symfony\Component\Cache\Messenger\EarlyExpirationDispatcher; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Reference; + +/** + * @author Nicolas Grekas + */ +class CachePoolPass implements CompilerPassInterface +{ + private $cachePoolTag; + private $kernelResetTag; + private $cacheClearerId; + private $cachePoolClearerTag; + private $cacheSystemClearerId; + private $cacheSystemClearerTag; + private $reverseContainerId; + private $reversibleTag; + private $messageHandlerId; + + public function __construct(string $cachePoolTag = 'cache.pool', string $kernelResetTag = 'kernel.reset', string $cacheClearerId = 'cache.global_clearer', string $cachePoolClearerTag = 'cache.pool.clearer', string $cacheSystemClearerId = 'cache.system_clearer', string $cacheSystemClearerTag = 'kernel.cache_clearer', string $reverseContainerId = 'reverse_container', string $reversibleTag = 'container.reversible', string $messageHandlerId = 'cache.early_expiration_handler') + { + if (0 < \func_num_args()) { + trigger_deprecation('symfony/cache', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); + } + + $this->cachePoolTag = $cachePoolTag; + $this->kernelResetTag = $kernelResetTag; + $this->cacheClearerId = $cacheClearerId; + $this->cachePoolClearerTag = $cachePoolClearerTag; + $this->cacheSystemClearerId = $cacheSystemClearerId; + $this->cacheSystemClearerTag = $cacheSystemClearerTag; + $this->reverseContainerId = $reverseContainerId; + $this->reversibleTag = $reversibleTag; + $this->messageHandlerId = $messageHandlerId; + } + + /** + * {@inheritdoc} + */ + public function process(ContainerBuilder $container) + { + if ($container->hasParameter('cache.prefix.seed')) { + $seed = $container->getParameterBag()->resolveValue($container->getParameter('cache.prefix.seed')); + } else { + $seed = '_'.$container->getParameter('kernel.project_dir'); + $seed .= '.'.$container->getParameter('kernel.container_class'); + } + + $needsMessageHandler = false; + $allPools = []; + $clearers = []; + $attributes = [ + 'provider', + 'name', + 'namespace', + 'default_lifetime', + 'early_expiration_message_bus', + 'reset', + ]; + foreach ($container->findTaggedServiceIds($this->cachePoolTag) as $id => $tags) { + $adapter = $pool = $container->getDefinition($id); + if ($pool->isAbstract()) { + continue; + } + $class = $adapter->getClass(); + while ($adapter instanceof ChildDefinition) { + $adapter = $container->findDefinition($adapter->getParent()); + $class = $class ?: $adapter->getClass(); + if ($t = $adapter->getTag($this->cachePoolTag)) { + $tags[0] += $t[0]; + } + } + $name = $tags[0]['name'] ?? $id; + if (!isset($tags[0]['namespace'])) { + $namespaceSeed = $seed; + if (null !== $class) { + $namespaceSeed .= '.'.$class; + } + + $tags[0]['namespace'] = $this->getNamespace($namespaceSeed, $name); + } + if (isset($tags[0]['clearer'])) { + $clearer = $tags[0]['clearer']; + while ($container->hasAlias($clearer)) { + $clearer = (string) $container->getAlias($clearer); + } + } else { + $clearer = null; + } + unset($tags[0]['clearer'], $tags[0]['name']); + + if (isset($tags[0]['provider'])) { + $tags[0]['provider'] = new Reference(static::getServiceProvider($container, $tags[0]['provider'])); + } + + if (ChainAdapter::class === $class) { + $adapters = []; + foreach ($adapter->getArgument(0) as $provider => $adapter) { + if ($adapter instanceof ChildDefinition) { + $chainedPool = $adapter; + } else { + $chainedPool = $adapter = new ChildDefinition($adapter); + } + + $chainedTags = [\is_int($provider) ? [] : ['provider' => $provider]]; + $chainedClass = ''; + + while ($adapter instanceof ChildDefinition) { + $adapter = $container->findDefinition($adapter->getParent()); + $chainedClass = $chainedClass ?: $adapter->getClass(); + if ($t = $adapter->getTag($this->cachePoolTag)) { + $chainedTags[0] += $t[0]; + } + } + + if (ChainAdapter::class === $chainedClass) { + throw new InvalidArgumentException(sprintf('Invalid service "%s": chain of adapters cannot reference another chain, found "%s".', $id, $chainedPool->getParent())); + } + + $i = 0; + + if (isset($chainedTags[0]['provider'])) { + $chainedPool->replaceArgument($i++, new Reference(static::getServiceProvider($container, $chainedTags[0]['provider']))); + } + + if (isset($tags[0]['namespace']) && !\in_array($adapter->getClass(), [ArrayAdapter::class, NullAdapter::class], true)) { + $chainedPool->replaceArgument($i++, $tags[0]['namespace']); + } + + if (isset($tags[0]['default_lifetime'])) { + $chainedPool->replaceArgument($i++, $tags[0]['default_lifetime']); + } + + $adapters[] = $chainedPool; + } + + $pool->replaceArgument(0, $adapters); + unset($tags[0]['provider'], $tags[0]['namespace']); + $i = 1; + } else { + $i = 0; + } + + foreach ($attributes as $attr) { + if (!isset($tags[0][$attr])) { + // no-op + } elseif ('reset' === $attr) { + if ($tags[0][$attr]) { + $pool->addTag($this->kernelResetTag, ['method' => $tags[0][$attr]]); + } + } elseif ('early_expiration_message_bus' === $attr) { + $needsMessageHandler = true; + $pool->addMethodCall('setCallbackWrapper', [(new Definition(EarlyExpirationDispatcher::class)) + ->addArgument(new Reference($tags[0]['early_expiration_message_bus'])) + ->addArgument(new Reference($this->reverseContainerId)) + ->addArgument((new Definition('callable')) + ->setFactory([new Reference($id), 'setCallbackWrapper']) + ->addArgument(null) + ), + ]); + $pool->addTag($this->reversibleTag); + } elseif ('namespace' !== $attr || !\in_array($class, [ArrayAdapter::class, NullAdapter::class], true)) { + $argument = $tags[0][$attr]; + + if ('default_lifetime' === $attr && !is_numeric($argument)) { + $argument = (new Definition('int', [$argument])) + ->setFactory([ParameterNormalizer::class, 'normalizeDuration']); + } + + $pool->replaceArgument($i++, $argument); + } + unset($tags[0][$attr]); + } + if (!empty($tags[0])) { + throw new InvalidArgumentException(sprintf('Invalid "%s" tag for service "%s": accepted attributes are "clearer", "provider", "name", "namespace", "default_lifetime", "early_expiration_message_bus" and "reset", found "%s".', $this->cachePoolTag, $id, implode('", "', array_keys($tags[0])))); + } + + if (null !== $clearer) { + $clearers[$clearer][$name] = new Reference($id, $container::IGNORE_ON_UNINITIALIZED_REFERENCE); + } + + $allPools[$name] = new Reference($id, $container::IGNORE_ON_UNINITIALIZED_REFERENCE); + } + + if (!$needsMessageHandler) { + $container->removeDefinition($this->messageHandlerId); + } + + $notAliasedCacheClearerId = $this->cacheClearerId; + while ($container->hasAlias($this->cacheClearerId)) { + $this->cacheClearerId = (string) $container->getAlias($this->cacheClearerId); + } + if ($container->hasDefinition($this->cacheClearerId)) { + $clearers[$notAliasedCacheClearerId] = $allPools; + } + + foreach ($clearers as $id => $pools) { + $clearer = $container->getDefinition($id); + if ($clearer instanceof ChildDefinition) { + $clearer->replaceArgument(0, $pools); + } else { + $clearer->setArgument(0, $pools); + } + $clearer->addTag($this->cachePoolClearerTag); + + if ($this->cacheSystemClearerId === $id) { + $clearer->addTag($this->cacheSystemClearerTag); + } + } + + $allPoolsKeys = array_keys($allPools); + + if ($container->hasDefinition('console.command.cache_pool_list')) { + $container->getDefinition('console.command.cache_pool_list')->replaceArgument(0, $allPoolsKeys); + } + + if ($container->hasDefinition('console.command.cache_pool_clear')) { + $container->getDefinition('console.command.cache_pool_clear')->addArgument($allPoolsKeys); + } + + if ($container->hasDefinition('console.command.cache_pool_delete')) { + $container->getDefinition('console.command.cache_pool_delete')->addArgument($allPoolsKeys); + } + } + + private function getNamespace(string $seed, string $id) + { + return substr(str_replace('/', '-', base64_encode(hash('sha256', $id.$seed, true))), 0, 10); + } + + /** + * @internal + */ + public static function getServiceProvider(ContainerBuilder $container, string $name) + { + $container->resolveEnvPlaceholders($name, null, $usedEnvs); + + if ($usedEnvs || preg_match('#^[a-z]++:#', $name)) { + $dsn = $name; + + if (!$container->hasDefinition($name = '.cache_connection.'.ContainerBuilder::hash($dsn))) { + $definition = new Definition(AbstractAdapter::class); + $definition->setPublic(false); + $definition->setFactory([AbstractAdapter::class, 'createConnection']); + $definition->setArguments([$dsn, ['lazy' => true]]); + $container->setDefinition($name, $definition); + } + } + + return $name; + } +} diff --git a/vendor/symfony/cache/DependencyInjection/CachePoolPrunerPass.php b/vendor/symfony/cache/DependencyInjection/CachePoolPrunerPass.php new file mode 100644 index 0000000..86a1906 --- /dev/null +++ b/vendor/symfony/cache/DependencyInjection/CachePoolPrunerPass.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\DependencyInjection; + +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Reference; + +/** + * @author Rob Frawley 2nd + */ +class CachePoolPrunerPass implements CompilerPassInterface +{ + private $cacheCommandServiceId; + private $cachePoolTag; + + public function __construct(string $cacheCommandServiceId = 'console.command.cache_pool_prune', string $cachePoolTag = 'cache.pool') + { + if (0 < \func_num_args()) { + trigger_deprecation('symfony/cache', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); + } + + $this->cacheCommandServiceId = $cacheCommandServiceId; + $this->cachePoolTag = $cachePoolTag; + } + + /** + * {@inheritdoc} + */ + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition($this->cacheCommandServiceId)) { + return; + } + + $services = []; + + foreach ($container->findTaggedServiceIds($this->cachePoolTag) as $id => $tags) { + $class = $container->getParameterBag()->resolveValue($container->getDefinition($id)->getClass()); + + if (!$reflection = $container->getReflectionClass($class)) { + throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); + } + + if ($reflection->implementsInterface(PruneableInterface::class)) { + $services[$id] = new Reference($id); + } + } + + $container->getDefinition($this->cacheCommandServiceId)->replaceArgument(0, new IteratorArgument($services)); + } +} diff --git a/vendor/symfony/cache/DoctrineProvider.php b/vendor/symfony/cache/DoctrineProvider.php new file mode 100644 index 0000000..2c9d757 --- /dev/null +++ b/vendor/symfony/cache/DoctrineProvider.php @@ -0,0 +1,120 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache; + +use Doctrine\Common\Cache\CacheProvider; +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * @author Nicolas Grekas + * + * @deprecated Use Doctrine\Common\Cache\Psr6\DoctrineProvider instead + */ +class DoctrineProvider extends CacheProvider implements PruneableInterface, ResettableInterface +{ + private $pool; + + public function __construct(CacheItemPoolInterface $pool) + { + trigger_deprecation('symfony/cache', '5.4', '"%s" is deprecated, use "Doctrine\Common\Cache\Psr6\DoctrineProvider" instead.', __CLASS__); + + $this->pool = $pool; + } + + /** + * {@inheritdoc} + */ + public function prune() + { + return $this->pool instanceof PruneableInterface && $this->pool->prune(); + } + + /** + * {@inheritdoc} + */ + public function reset() + { + if ($this->pool instanceof ResetInterface) { + $this->pool->reset(); + } + $this->setNamespace($this->getNamespace()); + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + protected function doFetch($id) + { + $item = $this->pool->getItem(rawurlencode($id)); + + return $item->isHit() ? $item->get() : false; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + protected function doContains($id) + { + return $this->pool->hasItem(rawurlencode($id)); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + protected function doSave($id, $data, $lifeTime = 0) + { + $item = $this->pool->getItem(rawurlencode($id)); + + if (0 < $lifeTime) { + $item->expiresAfter($lifeTime); + } + + return $this->pool->save($item->set($data)); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + protected function doDelete($id) + { + return $this->pool->deleteItem(rawurlencode($id)); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + protected function doFlush() + { + return $this->pool->clear(); + } + + /** + * {@inheritdoc} + * + * @return array|null + */ + protected function doGetStats() + { + return null; + } +} diff --git a/vendor/symfony/cache/Exception/CacheException.php b/vendor/symfony/cache/Exception/CacheException.php new file mode 100644 index 0000000..d2e975b --- /dev/null +++ b/vendor/symfony/cache/Exception/CacheException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Exception; + +use Psr\Cache\CacheException as Psr6CacheInterface; +use Psr\SimpleCache\CacheException as SimpleCacheInterface; + +if (interface_exists(SimpleCacheInterface::class)) { + class CacheException extends \Exception implements Psr6CacheInterface, SimpleCacheInterface + { + } +} else { + class CacheException extends \Exception implements Psr6CacheInterface + { + } +} diff --git a/vendor/symfony/cache/Exception/InvalidArgumentException.php b/vendor/symfony/cache/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..7f9584a --- /dev/null +++ b/vendor/symfony/cache/Exception/InvalidArgumentException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Exception; + +use Psr\Cache\InvalidArgumentException as Psr6CacheInterface; +use Psr\SimpleCache\InvalidArgumentException as SimpleCacheInterface; + +if (interface_exists(SimpleCacheInterface::class)) { + class InvalidArgumentException extends \InvalidArgumentException implements Psr6CacheInterface, SimpleCacheInterface + { + } +} else { + class InvalidArgumentException extends \InvalidArgumentException implements Psr6CacheInterface + { + } +} diff --git a/vendor/symfony/cache/Exception/LogicException.php b/vendor/symfony/cache/Exception/LogicException.php new file mode 100644 index 0000000..9ffa7ed --- /dev/null +++ b/vendor/symfony/cache/Exception/LogicException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Exception; + +use Psr\Cache\CacheException as Psr6CacheInterface; +use Psr\SimpleCache\CacheException as SimpleCacheInterface; + +if (interface_exists(SimpleCacheInterface::class)) { + class LogicException extends \LogicException implements Psr6CacheInterface, SimpleCacheInterface + { + } +} else { + class LogicException extends \LogicException implements Psr6CacheInterface + { + } +} diff --git a/vendor/symfony/cache/LICENSE b/vendor/symfony/cache/LICENSE new file mode 100644 index 0000000..3796612 --- /dev/null +++ b/vendor/symfony/cache/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2016-2021 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/cache/LockRegistry.php b/vendor/symfony/cache/LockRegistry.php new file mode 100644 index 0000000..910c11f --- /dev/null +++ b/vendor/symfony/cache/LockRegistry.php @@ -0,0 +1,188 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache; + +use Psr\Log\LoggerInterface; +use Symfony\Contracts\Cache\CacheInterface; +use Symfony\Contracts\Cache\ItemInterface; + +/** + * LockRegistry is used internally by existing adapters to protect against cache stampede. + * + * It does so by wrapping the computation of items in a pool of locks. + * Foreach each apps, there can be at most 20 concurrent processes that + * compute items at the same time and only one per cache-key. + * + * @author Nicolas Grekas + */ +final class LockRegistry +{ + private static $openedFiles = []; + private static $lockedKeys; + + /** + * The number of items in this list controls the max number of concurrent processes. + */ + private static $files = [ + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'AbstractAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'AbstractTagAwareAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'AdapterInterface.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ApcuAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ArrayAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ChainAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'CouchbaseBucketAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'CouchbaseCollectionAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'DoctrineAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'DoctrineDbalAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'FilesystemAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'FilesystemTagAwareAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'MemcachedAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'NullAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ParameterNormalizer.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'PdoAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'PhpArrayAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'PhpFilesAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ProxyAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'Psr16Adapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'RedisAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'RedisTagAwareAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TagAwareAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TagAwareAdapterInterface.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TraceableAdapter.php', + __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TraceableTagAwareAdapter.php', + ]; + + /** + * Defines a set of existing files that will be used as keys to acquire locks. + * + * @return array The previously defined set of files + */ + public static function setFiles(array $files): array + { + $previousFiles = self::$files; + self::$files = $files; + + foreach (self::$openedFiles as $file) { + if ($file) { + flock($file, \LOCK_UN); + fclose($file); + } + } + self::$openedFiles = self::$lockedKeys = []; + + return $previousFiles; + } + + public static function compute(callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, \Closure $setMetadata = null, LoggerInterface $logger = null) + { + if ('\\' === \DIRECTORY_SEPARATOR && null === self::$lockedKeys) { + // disable locking on Windows by default + self::$files = self::$lockedKeys = []; + } + + $key = unpack('i', md5($item->getKey(), true))[1]; + + if (!\function_exists('sem_get')) { + $key = self::$files ? abs($key) % \count(self::$files) : null; + } + + if (null === $key || (self::$lockedKeys[$key] ?? false) || !$lock = self::open($key)) { + return $callback($item, $save); + } + + while (true) { + try { + $locked = false; + // race to get the lock in non-blocking mode + if ($wouldBlock = \function_exists('sem_get')) { + $locked = @sem_acquire($lock, true); + } else { + $locked = flock($lock, \LOCK_EX | \LOCK_NB, $wouldBlock); + } + + if ($locked || !$wouldBlock) { + $logger && $logger->info(sprintf('Lock %s, now computing item "{key}"', $locked ? 'acquired' : 'not supported'), ['key' => $item->getKey()]); + self::$lockedKeys[$key] = true; + + $value = $callback($item, $save); + + if ($save) { + if ($setMetadata) { + $setMetadata($item); + } + + $pool->save($item->set($value)); + $save = false; + } + + return $value; + } + + // if we failed the race, retry locking in blocking mode to wait for the winner + $logger && $logger->info('Item "{key}" is locked, waiting for it to be released', ['key' => $item->getKey()]); + + if (\function_exists('sem_get')) { + $lock = sem_get($key); + @sem_acquire($lock); + } else { + flock($lock, \LOCK_SH); + } + } finally { + if ($locked) { + if (\function_exists('sem_get')) { + sem_remove($lock); + } else { + flock($lock, \LOCK_UN); + } + } + unset(self::$lockedKeys[$key]); + } + static $signalingException, $signalingCallback; + $signalingException = $signalingException ?? unserialize("O:9:\"Exception\":1:{s:16:\"\0Exception\0trace\";a:0:{}}"); + $signalingCallback = $signalingCallback ?? function () use ($signalingException) { throw $signalingException; }; + + try { + $value = $pool->get($item->getKey(), $signalingCallback, 0); + $logger && $logger->info('Item "{key}" retrieved after lock was released', ['key' => $item->getKey()]); + $save = false; + + return $value; + } catch (\Exception $e) { + if ($signalingException !== $e) { + throw $e; + } + $logger && $logger->info('Item "{key}" not found while lock was released, now retrying', ['key' => $item->getKey()]); + } + } + + return null; + } + + private static function open(int $key) + { + if (\function_exists('sem_get')) { + return sem_get($key); + } + + if (null !== $h = self::$openedFiles[$key] ?? null) { + return $h; + } + set_error_handler(function () {}); + try { + $h = fopen(self::$files[$key], 'r+'); + } finally { + restore_error_handler(); + } + + return self::$openedFiles[$key] = $h ?: @fopen(self::$files[$key], 'r'); + } +} diff --git a/vendor/symfony/cache/Marshaller/DefaultMarshaller.php b/vendor/symfony/cache/Marshaller/DefaultMarshaller.php new file mode 100644 index 0000000..3202dd6 --- /dev/null +++ b/vendor/symfony/cache/Marshaller/DefaultMarshaller.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Marshaller; + +use Symfony\Component\Cache\Exception\CacheException; + +/** + * Serializes/unserializes values using igbinary_serialize() if available, serialize() otherwise. + * + * @author Nicolas Grekas + */ +class DefaultMarshaller implements MarshallerInterface +{ + private $useIgbinarySerialize = true; + private $throwOnSerializationFailure; + + public function __construct(bool $useIgbinarySerialize = null, bool $throwOnSerializationFailure = false) + { + if (null === $useIgbinarySerialize) { + $useIgbinarySerialize = \extension_loaded('igbinary') && (\PHP_VERSION_ID < 70400 || version_compare('3.1.6', phpversion('igbinary'), '<=')); + } elseif ($useIgbinarySerialize && (!\extension_loaded('igbinary') || (\PHP_VERSION_ID >= 70400 && version_compare('3.1.6', phpversion('igbinary'), '>')))) { + throw new CacheException(\extension_loaded('igbinary') && \PHP_VERSION_ID >= 70400 ? 'Please upgrade the "igbinary" PHP extension to v3.1.6 or higher.' : 'The "igbinary" PHP extension is not loaded.'); + } + $this->useIgbinarySerialize = $useIgbinarySerialize; + $this->throwOnSerializationFailure = $throwOnSerializationFailure; + } + + /** + * {@inheritdoc} + */ + public function marshall(array $values, ?array &$failed): array + { + $serialized = $failed = []; + + foreach ($values as $id => $value) { + try { + if ($this->useIgbinarySerialize) { + $serialized[$id] = igbinary_serialize($value); + } else { + $serialized[$id] = serialize($value); + } + } catch (\Exception $e) { + if ($this->throwOnSerializationFailure) { + throw new \ValueError($e->getMessage(), 0, $e); + } + $failed[] = $id; + } + } + + return $serialized; + } + + /** + * {@inheritdoc} + */ + public function unmarshall(string $value) + { + if ('b:0;' === $value) { + return false; + } + if ('N;' === $value) { + return null; + } + static $igbinaryNull; + if ($value === ($igbinaryNull ?? $igbinaryNull = \extension_loaded('igbinary') ? igbinary_serialize(null) : false)) { + return null; + } + $unserializeCallbackHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback'); + try { + if (':' === ($value[1] ?? ':')) { + if (false !== $value = unserialize($value)) { + return $value; + } + } elseif (false === $igbinaryNull) { + throw new \RuntimeException('Failed to unserialize values, did you forget to install the "igbinary" extension?'); + } elseif (null !== $value = igbinary_unserialize($value)) { + return $value; + } + + throw new \DomainException(error_get_last() ? error_get_last()['message'] : 'Failed to unserialize values.'); + } catch (\Error $e) { + throw new \ErrorException($e->getMessage(), $e->getCode(), \E_ERROR, $e->getFile(), $e->getLine()); + } finally { + ini_set('unserialize_callback_func', $unserializeCallbackHandler); + } + } + + /** + * @internal + */ + public static function handleUnserializeCallback(string $class) + { + throw new \DomainException('Class not found: '.$class); + } +} diff --git a/vendor/symfony/cache/Marshaller/DeflateMarshaller.php b/vendor/symfony/cache/Marshaller/DeflateMarshaller.php new file mode 100644 index 0000000..5544806 --- /dev/null +++ b/vendor/symfony/cache/Marshaller/DeflateMarshaller.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Marshaller; + +use Symfony\Component\Cache\Exception\CacheException; + +/** + * Compresses values using gzdeflate(). + * + * @author Nicolas Grekas + */ +class DeflateMarshaller implements MarshallerInterface +{ + private $marshaller; + + public function __construct(MarshallerInterface $marshaller) + { + if (!\function_exists('gzdeflate')) { + throw new CacheException('The "zlib" PHP extension is not loaded.'); + } + + $this->marshaller = $marshaller; + } + + /** + * {@inheritdoc} + */ + public function marshall(array $values, ?array &$failed): array + { + return array_map('gzdeflate', $this->marshaller->marshall($values, $failed)); + } + + /** + * {@inheritdoc} + */ + public function unmarshall(string $value) + { + if (false !== $inflatedValue = @gzinflate($value)) { + $value = $inflatedValue; + } + + return $this->marshaller->unmarshall($value); + } +} diff --git a/vendor/symfony/cache/Marshaller/MarshallerInterface.php b/vendor/symfony/cache/Marshaller/MarshallerInterface.php new file mode 100644 index 0000000..cdd6c40 --- /dev/null +++ b/vendor/symfony/cache/Marshaller/MarshallerInterface.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Marshaller; + +/** + * Serializes/unserializes PHP values. + * + * Implementations of this interface MUST deal with errors carefully. They MUST + * also deal with forward and backward compatibility at the storage format level. + * + * @author Nicolas Grekas + */ +interface MarshallerInterface +{ + /** + * Serializes a list of values. + * + * When serialization fails for a specific value, no exception should be + * thrown. Instead, its key should be listed in $failed. + */ + public function marshall(array $values, ?array &$failed): array; + + /** + * Unserializes a single value and throws an exception if anything goes wrong. + * + * @return mixed + * + * @throws \Exception Whenever unserialization fails + */ + public function unmarshall(string $value); +} diff --git a/vendor/symfony/cache/Marshaller/SodiumMarshaller.php b/vendor/symfony/cache/Marshaller/SodiumMarshaller.php new file mode 100644 index 0000000..dbf486a --- /dev/null +++ b/vendor/symfony/cache/Marshaller/SodiumMarshaller.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Marshaller; + +use Symfony\Component\Cache\Exception\CacheException; +use Symfony\Component\Cache\Exception\InvalidArgumentException; + +/** + * Encrypt/decrypt values using Libsodium. + * + * @author Ahmed TAILOULOUTE + */ +class SodiumMarshaller implements MarshallerInterface +{ + private $marshaller; + private $decryptionKeys; + + /** + * @param string[] $decryptionKeys The key at index "0" is required and is used to decrypt and encrypt values; + * more rotating keys can be provided to decrypt values; + * each key must be generated using sodium_crypto_box_keypair() + */ + public function __construct(array $decryptionKeys, MarshallerInterface $marshaller = null) + { + if (!self::isSupported()) { + throw new CacheException('The "sodium" PHP extension is not loaded.'); + } + + if (!isset($decryptionKeys[0])) { + throw new InvalidArgumentException('At least one decryption key must be provided at index "0".'); + } + + $this->marshaller = $marshaller ?? new DefaultMarshaller(); + $this->decryptionKeys = $decryptionKeys; + } + + public static function isSupported(): bool + { + return \function_exists('sodium_crypto_box_seal'); + } + + /** + * {@inheritdoc} + */ + public function marshall(array $values, ?array &$failed): array + { + $encryptionKey = sodium_crypto_box_publickey($this->decryptionKeys[0]); + + $encryptedValues = []; + foreach ($this->marshaller->marshall($values, $failed) as $k => $v) { + $encryptedValues[$k] = sodium_crypto_box_seal($v, $encryptionKey); + } + + return $encryptedValues; + } + + /** + * {@inheritdoc} + */ + public function unmarshall(string $value) + { + foreach ($this->decryptionKeys as $k) { + if (false !== $decryptedValue = @sodium_crypto_box_seal_open($value, $k)) { + $value = $decryptedValue; + break; + } + } + + return $this->marshaller->unmarshall($value); + } +} diff --git a/vendor/symfony/cache/Marshaller/TagAwareMarshaller.php b/vendor/symfony/cache/Marshaller/TagAwareMarshaller.php new file mode 100644 index 0000000..5d1e303 --- /dev/null +++ b/vendor/symfony/cache/Marshaller/TagAwareMarshaller.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Marshaller; + +/** + * A marshaller optimized for data structures generated by AbstractTagAwareAdapter. + * + * @author Nicolas Grekas + */ +class TagAwareMarshaller implements MarshallerInterface +{ + private $marshaller; + + public function __construct(MarshallerInterface $marshaller = null) + { + $this->marshaller = $marshaller ?? new DefaultMarshaller(); + } + + /** + * {@inheritdoc} + */ + public function marshall(array $values, ?array &$failed): array + { + $failed = $notSerialized = $serialized = []; + + foreach ($values as $id => $value) { + if (\is_array($value) && \is_array($value['tags'] ?? null) && \array_key_exists('value', $value) && \count($value) === 2 + (\is_string($value['meta'] ?? null) && 8 === \strlen($value['meta']))) { + // if the value is an array with keys "tags", "value" and "meta", use a compact serialization format + // magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F allow detecting this format quickly in unmarshall() + + $v = $this->marshaller->marshall($value, $f); + + if ($f) { + $f = []; + $failed[] = $id; + } else { + if ([] === $value['tags']) { + $v['tags'] = ''; + } + + $serialized[$id] = "\x9D".($value['meta'] ?? "\0\0\0\0\0\0\0\0").pack('N', \strlen($v['tags'])).$v['tags'].$v['value']; + $serialized[$id][9] = "\x5F"; + } + } else { + // other arbitratry values are serialized using the decorated marshaller below + $notSerialized[$id] = $value; + } + } + + if ($notSerialized) { + $serialized += $this->marshaller->marshall($notSerialized, $f); + $failed = array_merge($failed, $f); + } + + return $serialized; + } + + /** + * {@inheritdoc} + */ + public function unmarshall(string $value) + { + // detect the compact format used in marshall() using magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F + if (13 >= \strlen($value) || "\x9D" !== $value[0] || "\0" !== $value[5] || "\x5F" !== $value[9]) { + return $this->marshaller->unmarshall($value); + } + + // data consists of value, tags and metadata which we need to unpack + $meta = substr($value, 1, 12); + $meta[8] = "\0"; + $tagLen = unpack('Nlen', $meta, 8)['len']; + $meta = substr($meta, 0, 8); + + return [ + 'value' => $this->marshaller->unmarshall(substr($value, 13 + $tagLen)), + 'tags' => $tagLen ? $this->marshaller->unmarshall(substr($value, 13, $tagLen)) : [], + 'meta' => "\0\0\0\0\0\0\0\0" === $meta ? null : $meta, + ]; + } +} diff --git a/vendor/symfony/cache/Messenger/EarlyExpirationDispatcher.php b/vendor/symfony/cache/Messenger/EarlyExpirationDispatcher.php new file mode 100644 index 0000000..6f11b8b --- /dev/null +++ b/vendor/symfony/cache/Messenger/EarlyExpirationDispatcher.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Messenger; + +use Psr\Log\LoggerInterface; +use Symfony\Component\Cache\Adapter\AdapterInterface; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\DependencyInjection\ReverseContainer; +use Symfony\Component\Messenger\MessageBusInterface; +use Symfony\Component\Messenger\Stamp\HandledStamp; + +/** + * Sends the computation of cached values to a message bus. + */ +class EarlyExpirationDispatcher +{ + private $bus; + private $reverseContainer; + private $callbackWrapper; + + public function __construct(MessageBusInterface $bus, ReverseContainer $reverseContainer, callable $callbackWrapper = null) + { + $this->bus = $bus; + $this->reverseContainer = $reverseContainer; + $this->callbackWrapper = $callbackWrapper; + } + + public function __invoke(callable $callback, CacheItem $item, bool &$save, AdapterInterface $pool, \Closure $setMetadata, LoggerInterface $logger = null) + { + if (!$item->isHit() || null === $message = EarlyExpirationMessage::create($this->reverseContainer, $callback, $item, $pool)) { + // The item is stale or the callback cannot be reversed: we must compute the value now + $logger && $logger->info('Computing item "{key}" online: '.($item->isHit() ? 'callback cannot be reversed' : 'item is stale'), ['key' => $item->getKey()]); + + return null !== $this->callbackWrapper ? ($this->callbackWrapper)($callback, $item, $save, $pool, $setMetadata, $logger) : $callback($item, $save); + } + + $envelope = $this->bus->dispatch($message); + + if ($logger) { + if ($envelope->last(HandledStamp::class)) { + $logger->info('Item "{key}" was computed online', ['key' => $item->getKey()]); + } else { + $logger->info('Item "{key}" sent for recomputation', ['key' => $item->getKey()]); + } + } + + // The item's value is not stale, no need to write it to the backend + $save = false; + + return $message->getItem()->get() ?? $item->get(); + } +} diff --git a/vendor/symfony/cache/Messenger/EarlyExpirationHandler.php b/vendor/symfony/cache/Messenger/EarlyExpirationHandler.php new file mode 100644 index 0000000..1f0bd56 --- /dev/null +++ b/vendor/symfony/cache/Messenger/EarlyExpirationHandler.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Messenger; + +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\DependencyInjection\ReverseContainer; +use Symfony\Component\Messenger\Handler\MessageHandlerInterface; + +/** + * Computes cached values sent to a message bus. + */ +class EarlyExpirationHandler implements MessageHandlerInterface +{ + private $reverseContainer; + private $processedNonces = []; + + public function __construct(ReverseContainer $reverseContainer) + { + $this->reverseContainer = $reverseContainer; + } + + public function __invoke(EarlyExpirationMessage $message) + { + $item = $message->getItem(); + $metadata = $item->getMetadata(); + $expiry = $metadata[CacheItem::METADATA_EXPIRY] ?? 0; + $ctime = $metadata[CacheItem::METADATA_CTIME] ?? 0; + + if ($expiry && $ctime) { + // skip duplicate or expired messages + + $processingNonce = [$expiry, $ctime]; + $pool = $message->getPool(); + $key = $item->getKey(); + + if (($this->processedNonces[$pool][$key] ?? null) === $processingNonce) { + return; + } + + if (microtime(true) >= $expiry) { + return; + } + + $this->processedNonces[$pool] = [$key => $processingNonce] + ($this->processedNonces[$pool] ?? []); + + if (\count($this->processedNonces[$pool]) > 100) { + array_pop($this->processedNonces[$pool]); + } + } + + static $setMetadata; + + $setMetadata ?? $setMetadata = \Closure::bind( + function (CacheItem $item, float $startTime) { + if ($item->expiry > $endTime = microtime(true)) { + $item->newMetadata[CacheItem::METADATA_EXPIRY] = $item->expiry; + $item->newMetadata[CacheItem::METADATA_CTIME] = (int) ceil(1000 * ($endTime - $startTime)); + } + }, + null, + CacheItem::class + ); + + $startTime = microtime(true); + $pool = $message->findPool($this->reverseContainer); + $callback = $message->findCallback($this->reverseContainer); + $value = $callback($item); + $setMetadata($item, $startTime); + $pool->save($item->set($value)); + } +} diff --git a/vendor/symfony/cache/Messenger/EarlyExpirationMessage.php b/vendor/symfony/cache/Messenger/EarlyExpirationMessage.php new file mode 100644 index 0000000..e25c07e --- /dev/null +++ b/vendor/symfony/cache/Messenger/EarlyExpirationMessage.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Messenger; + +use Symfony\Component\Cache\Adapter\AdapterInterface; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\DependencyInjection\ReverseContainer; + +/** + * Conveys a cached value that needs to be computed. + */ +final class EarlyExpirationMessage +{ + private $item; + private $pool; + private $callback; + + public static function create(ReverseContainer $reverseContainer, callable $callback, CacheItem $item, AdapterInterface $pool): ?self + { + try { + $item = clone $item; + $item->set(null); + } catch (\Exception $e) { + return null; + } + + $pool = $reverseContainer->getId($pool); + + if (\is_object($callback)) { + if (null === $id = $reverseContainer->getId($callback)) { + return null; + } + + $callback = '@'.$id; + } elseif (!\is_array($callback)) { + $callback = (string) $callback; + } elseif (!\is_object($callback[0])) { + $callback = [(string) $callback[0], (string) $callback[1]]; + } else { + if (null === $id = $reverseContainer->getId($callback[0])) { + return null; + } + + $callback = ['@'.$id, (string) $callback[1]]; + } + + return new self($item, $pool, $callback); + } + + public function getItem(): CacheItem + { + return $this->item; + } + + public function getPool(): string + { + return $this->pool; + } + + public function getCallback() + { + return $this->callback; + } + + public function findPool(ReverseContainer $reverseContainer): AdapterInterface + { + return $reverseContainer->getService($this->pool); + } + + public function findCallback(ReverseContainer $reverseContainer): callable + { + if (\is_string($callback = $this->callback)) { + return '@' === $callback[0] ? $reverseContainer->getService(substr($callback, 1)) : $callback; + } + if ('@' === $callback[0][0]) { + $callback[0] = $reverseContainer->getService(substr($callback[0], 1)); + } + + return $callback; + } + + private function __construct(CacheItem $item, string $pool, $callback) + { + $this->item = $item; + $this->pool = $pool; + $this->callback = $callback; + } +} diff --git a/vendor/symfony/cache/PruneableInterface.php b/vendor/symfony/cache/PruneableInterface.php new file mode 100644 index 0000000..4261525 --- /dev/null +++ b/vendor/symfony/cache/PruneableInterface.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache; + +/** + * Interface extends psr-6 and psr-16 caches to allow for pruning (deletion) of all expired cache items. + */ +interface PruneableInterface +{ + /** + * @return bool + */ + public function prune(); +} diff --git a/vendor/symfony/cache/Psr16Cache.php b/vendor/symfony/cache/Psr16Cache.php new file mode 100644 index 0000000..c785144 --- /dev/null +++ b/vendor/symfony/cache/Psr16Cache.php @@ -0,0 +1,285 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache; + +use Psr\Cache\CacheException as Psr6CacheException; +use Psr\Cache\CacheItemPoolInterface; +use Psr\SimpleCache\CacheException as SimpleCacheException; +use Psr\SimpleCache\CacheInterface; +use Symfony\Component\Cache\Adapter\AdapterInterface; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Traits\ProxyTrait; + +/** + * Turns a PSR-6 cache into a PSR-16 one. + * + * @author Nicolas Grekas + */ +class Psr16Cache implements CacheInterface, PruneableInterface, ResettableInterface +{ + use ProxyTrait; + + private const METADATA_EXPIRY_OFFSET = 1527506807; + + private $createCacheItem; + private $cacheItemPrototype; + + public function __construct(CacheItemPoolInterface $pool) + { + $this->pool = $pool; + + if (!$pool instanceof AdapterInterface) { + return; + } + $cacheItemPrototype = &$this->cacheItemPrototype; + $createCacheItem = \Closure::bind( + static function ($key, $value, $allowInt = false) use (&$cacheItemPrototype) { + $item = clone $cacheItemPrototype; + $item->poolHash = $item->innerItem = null; + if ($allowInt && \is_int($key)) { + $item->key = (string) $key; + } else { + \assert('' !== CacheItem::validateKey($key)); + $item->key = $key; + } + $item->value = $value; + $item->isHit = false; + + return $item; + }, + null, + CacheItem::class + ); + $this->createCacheItem = function ($key, $value, $allowInt = false) use ($createCacheItem) { + if (null === $this->cacheItemPrototype) { + $this->get($allowInt && \is_int($key) ? (string) $key : $key); + } + $this->createCacheItem = $createCacheItem; + + return $createCacheItem($key, null, $allowInt)->set($value); + }; + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function get($key, $default = null) + { + try { + $item = $this->pool->getItem($key); + } catch (SimpleCacheException $e) { + throw $e; + } catch (Psr6CacheException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + if (null === $this->cacheItemPrototype) { + $this->cacheItemPrototype = clone $item; + $this->cacheItemPrototype->set(null); + } + + return $item->isHit() ? $item->get() : $default; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function set($key, $value, $ttl = null) + { + try { + if (null !== $f = $this->createCacheItem) { + $item = $f($key, $value); + } else { + $item = $this->pool->getItem($key)->set($value); + } + } catch (SimpleCacheException $e) { + throw $e; + } catch (Psr6CacheException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + if (null !== $ttl) { + $item->expiresAfter($ttl); + } + + return $this->pool->save($item); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function delete($key) + { + try { + return $this->pool->deleteItem($key); + } catch (SimpleCacheException $e) { + throw $e; + } catch (Psr6CacheException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function clear() + { + return $this->pool->clear(); + } + + /** + * {@inheritdoc} + * + * @return iterable + */ + public function getMultiple($keys, $default = null) + { + if ($keys instanceof \Traversable) { + $keys = iterator_to_array($keys, false); + } elseif (!\is_array($keys)) { + throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given.', get_debug_type($keys))); + } + + try { + $items = $this->pool->getItems($keys); + } catch (SimpleCacheException $e) { + throw $e; + } catch (Psr6CacheException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + $values = []; + + if (!$this->pool instanceof AdapterInterface) { + foreach ($items as $key => $item) { + $values[$key] = $item->isHit() ? $item->get() : $default; + } + + return $values; + } + + foreach ($items as $key => $item) { + if (!$item->isHit()) { + $values[$key] = $default; + continue; + } + $values[$key] = $item->get(); + + if (!$metadata = $item->getMetadata()) { + continue; + } + unset($metadata[CacheItem::METADATA_TAGS]); + + if ($metadata) { + $values[$key] = ["\x9D".pack('VN', (int) (0.1 + $metadata[CacheItem::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[CacheItem::METADATA_CTIME])."\x5F" => $values[$key]]; + } + } + + return $values; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function setMultiple($values, $ttl = null) + { + $valuesIsArray = \is_array($values); + if (!$valuesIsArray && !$values instanceof \Traversable) { + throw new InvalidArgumentException(sprintf('Cache values must be array or Traversable, "%s" given.', get_debug_type($values))); + } + $items = []; + + try { + if (null !== $f = $this->createCacheItem) { + $valuesIsArray = false; + foreach ($values as $key => $value) { + $items[$key] = $f($key, $value, true); + } + } elseif ($valuesIsArray) { + $items = []; + foreach ($values as $key => $value) { + $items[] = (string) $key; + } + $items = $this->pool->getItems($items); + } else { + foreach ($values as $key => $value) { + if (\is_int($key)) { + $key = (string) $key; + } + $items[$key] = $this->pool->getItem($key)->set($value); + } + } + } catch (SimpleCacheException $e) { + throw $e; + } catch (Psr6CacheException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + $ok = true; + + foreach ($items as $key => $item) { + if ($valuesIsArray) { + $item->set($values[$key]); + } + if (null !== $ttl) { + $item->expiresAfter($ttl); + } + $ok = $this->pool->saveDeferred($item) && $ok; + } + + return $this->pool->commit() && $ok; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function deleteMultiple($keys) + { + if ($keys instanceof \Traversable) { + $keys = iterator_to_array($keys, false); + } elseif (!\is_array($keys)) { + throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given.', get_debug_type($keys))); + } + + try { + return $this->pool->deleteItems($keys); + } catch (SimpleCacheException $e) { + throw $e; + } catch (Psr6CacheException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function has($key) + { + try { + return $this->pool->hasItem($key); + } catch (SimpleCacheException $e) { + throw $e; + } catch (Psr6CacheException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + } +} diff --git a/vendor/symfony/cache/README.md b/vendor/symfony/cache/README.md new file mode 100644 index 0000000..7405205 --- /dev/null +++ b/vendor/symfony/cache/README.md @@ -0,0 +1,19 @@ +Symfony PSR-6 implementation for caching +======================================== + +The Cache component provides an extended +[PSR-6](http://www.php-fig.org/psr/psr-6/) implementation for adding cache to +your applications. It is designed to have a low overhead so that caching is +fastest. It ships with a few caching adapters for the most widespread and +suited to caching backends. It also provides a `doctrine/cache` proxy adapter +to cover more advanced caching needs and a proxy adapter for greater +interoperability between PSR-6 implementations. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/cache.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/cache/ResettableInterface.php b/vendor/symfony/cache/ResettableInterface.php new file mode 100644 index 0000000..7b0a853 --- /dev/null +++ b/vendor/symfony/cache/ResettableInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache; + +use Symfony\Contracts\Service\ResetInterface; + +/** + * Resets a pool's local state. + */ +interface ResettableInterface extends ResetInterface +{ +} diff --git a/vendor/symfony/cache/Traits/AbstractAdapterTrait.php b/vendor/symfony/cache/Traits/AbstractAdapterTrait.php new file mode 100644 index 0000000..3912ede --- /dev/null +++ b/vendor/symfony/cache/Traits/AbstractAdapterTrait.php @@ -0,0 +1,411 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Psr\Cache\CacheItemInterface; +use Psr\Log\LoggerAwareTrait; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\InvalidArgumentException; + +/** + * @author Nicolas Grekas + * + * @internal + */ +trait AbstractAdapterTrait +{ + use LoggerAwareTrait; + + /** + * @var \Closure needs to be set by class, signature is function(string , mixed , bool ) + */ + private static $createCacheItem; + + /** + * @var \Closure needs to be set by class, signature is function(array , string , array <&expiredIds>) + */ + private static $mergeByLifetime; + + private $namespace = ''; + private $defaultLifetime; + private $namespaceVersion = ''; + private $versioningIsEnabled = false; + private $deferred = []; + private $ids = []; + + /** + * @var int|null The maximum length to enforce for identifiers or null when no limit applies + */ + protected $maxIdLength; + + /** + * Fetches several cache items. + * + * @param array $ids The cache identifiers to fetch + * + * @return array|\Traversable + */ + abstract protected function doFetch(array $ids); + + /** + * Confirms if the cache contains specified cache item. + * + * @param string $id The identifier for which to check existence + * + * @return bool + */ + abstract protected function doHave(string $id); + + /** + * Deletes all items in the pool. + * + * @param string $namespace The prefix used for all identifiers managed by this pool + * + * @return bool + */ + abstract protected function doClear(string $namespace); + + /** + * Removes multiple items from the pool. + * + * @param array $ids An array of identifiers that should be removed from the pool + * + * @return bool + */ + abstract protected function doDelete(array $ids); + + /** + * Persists several cache items immediately. + * + * @param array $values The values to cache, indexed by their cache identifier + * @param int $lifetime The lifetime of the cached values, 0 for persisting until manual cleaning + * + * @return array|bool The identifiers that failed to be cached or a boolean stating if caching succeeded or not + */ + abstract protected function doSave(array $values, int $lifetime); + + /** + * {@inheritdoc} + * + * @return bool + */ + public function hasItem($key) + { + $id = $this->getId($key); + + if (isset($this->deferred[$key])) { + $this->commit(); + } + + try { + return $this->doHave($id); + } catch (\Exception $e) { + CacheItem::log($this->logger, 'Failed to check if key "{key}" is cached: '.$e->getMessage(), ['key' => $key, 'exception' => $e, 'cache-adapter' => get_debug_type($this)]); + + return false; + } + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function clear(string $prefix = '') + { + $this->deferred = []; + if ($cleared = $this->versioningIsEnabled) { + if ('' === $namespaceVersionToClear = $this->namespaceVersion) { + foreach ($this->doFetch([static::NS_SEPARATOR.$this->namespace]) as $v) { + $namespaceVersionToClear = $v; + } + } + $namespaceToClear = $this->namespace.$namespaceVersionToClear; + $namespaceVersion = strtr(substr_replace(base64_encode(pack('V', mt_rand())), static::NS_SEPARATOR, 5), '/', '_'); + try { + $cleared = $this->doSave([static::NS_SEPARATOR.$this->namespace => $namespaceVersion], 0); + } catch (\Exception $e) { + $cleared = false; + } + if ($cleared = true === $cleared || [] === $cleared) { + $this->namespaceVersion = $namespaceVersion; + $this->ids = []; + } + } else { + $namespaceToClear = $this->namespace.$prefix; + } + + try { + return $this->doClear($namespaceToClear) || $cleared; + } catch (\Exception $e) { + CacheItem::log($this->logger, 'Failed to clear the cache: '.$e->getMessage(), ['exception' => $e, 'cache-adapter' => get_debug_type($this)]); + + return false; + } + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function deleteItem($key) + { + return $this->deleteItems([$key]); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function deleteItems(array $keys) + { + $ids = []; + + foreach ($keys as $key) { + $ids[$key] = $this->getId($key); + unset($this->deferred[$key]); + } + + try { + if ($this->doDelete($ids)) { + return true; + } + } catch (\Exception $e) { + } + + $ok = true; + + // When bulk-delete failed, retry each item individually + foreach ($ids as $key => $id) { + try { + $e = null; + if ($this->doDelete([$id])) { + continue; + } + } catch (\Exception $e) { + } + $message = 'Failed to delete key "{key}"'.($e instanceof \Exception ? ': '.$e->getMessage() : '.'); + CacheItem::log($this->logger, $message, ['key' => $key, 'exception' => $e, 'cache-adapter' => get_debug_type($this)]); + $ok = false; + } + + return $ok; + } + + /** + * {@inheritdoc} + */ + public function getItem($key) + { + $id = $this->getId($key); + + if (isset($this->deferred[$key])) { + $this->commit(); + } + + $isHit = false; + $value = null; + + try { + foreach ($this->doFetch([$id]) as $value) { + $isHit = true; + } + + return (self::$createCacheItem)($key, $value, $isHit); + } catch (\Exception $e) { + CacheItem::log($this->logger, 'Failed to fetch key "{key}": '.$e->getMessage(), ['key' => $key, 'exception' => $e, 'cache-adapter' => get_debug_type($this)]); + } + + return (self::$createCacheItem)($key, null, false); + } + + /** + * {@inheritdoc} + */ + public function getItems(array $keys = []) + { + $ids = []; + $commit = false; + + foreach ($keys as $key) { + $ids[] = $this->getId($key); + $commit = $commit || isset($this->deferred[$key]); + } + + if ($commit) { + $this->commit(); + } + + try { + $items = $this->doFetch($ids); + } catch (\Exception $e) { + CacheItem::log($this->logger, 'Failed to fetch items: '.$e->getMessage(), ['keys' => $keys, 'exception' => $e, 'cache-adapter' => get_debug_type($this)]); + $items = []; + } + $ids = array_combine($ids, $keys); + + return $this->generateItems($items, $ids); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function save(CacheItemInterface $item) + { + if (!$item instanceof CacheItem) { + return false; + } + $this->deferred[$item->getKey()] = $item; + + return $this->commit(); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function saveDeferred(CacheItemInterface $item) + { + if (!$item instanceof CacheItem) { + return false; + } + $this->deferred[$item->getKey()] = $item; + + return true; + } + + /** + * Enables/disables versioning of items. + * + * When versioning is enabled, clearing the cache is atomic and doesn't require listing existing keys to proceed, + * but old keys may need garbage collection and extra round-trips to the back-end are required. + * + * Calling this method also clears the memoized namespace version and thus forces a resynchonization of it. + * + * @return bool the previous state of versioning + */ + public function enableVersioning(bool $enable = true) + { + $wasEnabled = $this->versioningIsEnabled; + $this->versioningIsEnabled = $enable; + $this->namespaceVersion = ''; + $this->ids = []; + + return $wasEnabled; + } + + /** + * {@inheritdoc} + */ + public function reset() + { + if ($this->deferred) { + $this->commit(); + } + $this->namespaceVersion = ''; + $this->ids = []; + } + + /** + * @return array + */ + public function __sleep() + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + if ($this->deferred) { + $this->commit(); + } + } + + private function generateItems(iterable $items, array &$keys): \Generator + { + $f = self::$createCacheItem; + + try { + foreach ($items as $id => $value) { + if (!isset($keys[$id])) { + throw new InvalidArgumentException(sprintf('Could not match value id "%s" to keys "%s".', $id, implode('", "', $keys))); + } + $key = $keys[$id]; + unset($keys[$id]); + yield $key => $f($key, $value, true); + } + } catch (\Exception $e) { + CacheItem::log($this->logger, 'Failed to fetch items: '.$e->getMessage(), ['keys' => array_values($keys), 'exception' => $e, 'cache-adapter' => get_debug_type($this)]); + } + + foreach ($keys as $key) { + yield $key => $f($key, null, false); + } + } + + private function getId($key) + { + if ($this->versioningIsEnabled && '' === $this->namespaceVersion) { + $this->ids = []; + $this->namespaceVersion = '1'.static::NS_SEPARATOR; + try { + foreach ($this->doFetch([static::NS_SEPARATOR.$this->namespace]) as $v) { + $this->namespaceVersion = $v; + } + if ('1'.static::NS_SEPARATOR === $this->namespaceVersion) { + $this->namespaceVersion = strtr(substr_replace(base64_encode(pack('V', time())), static::NS_SEPARATOR, 5), '/', '_'); + $this->doSave([static::NS_SEPARATOR.$this->namespace => $this->namespaceVersion], 0); + } + } catch (\Exception $e) { + } + } + + if (\is_string($key) && isset($this->ids[$key])) { + return $this->namespace.$this->namespaceVersion.$this->ids[$key]; + } + \assert('' !== CacheItem::validateKey($key)); + $this->ids[$key] = $key; + + if (\count($this->ids) > 1000) { + array_splice($this->ids, 0, 500); // stop memory leak if there are many keys + } + + if (null === $this->maxIdLength) { + return $this->namespace.$this->namespaceVersion.$key; + } + if (\strlen($id = $this->namespace.$this->namespaceVersion.$key) > $this->maxIdLength) { + // Use MD5 to favor speed over security, which is not an issue here + $this->ids[$key] = $id = substr_replace(base64_encode(hash('md5', $key, true)), static::NS_SEPARATOR, -(\strlen($this->namespaceVersion) + 2)); + $id = $this->namespace.$this->namespaceVersion.$id; + } + + return $id; + } + + /** + * @internal + */ + public static function handleUnserializeCallback(string $class) + { + throw new \DomainException('Class not found: '.$class); + } +} diff --git a/vendor/symfony/cache/Traits/ContractsTrait.php b/vendor/symfony/cache/Traits/ContractsTrait.php new file mode 100644 index 0000000..2f5af04 --- /dev/null +++ b/vendor/symfony/cache/Traits/ContractsTrait.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Psr\Log\LoggerInterface; +use Symfony\Component\Cache\Adapter\AdapterInterface; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\LockRegistry; +use Symfony\Contracts\Cache\CacheInterface; +use Symfony\Contracts\Cache\CacheTrait; +use Symfony\Contracts\Cache\ItemInterface; + +/** + * @author Nicolas Grekas + * + * @internal + */ +trait ContractsTrait +{ + use CacheTrait { + doGet as private contractsGet; + } + + private $callbackWrapper = [LockRegistry::class, 'compute']; + private $computing = []; + + /** + * Wraps the callback passed to ->get() in a callable. + * + * @return callable the previous callback wrapper + */ + public function setCallbackWrapper(?callable $callbackWrapper): callable + { + $previousWrapper = $this->callbackWrapper; + $this->callbackWrapper = $callbackWrapper ?? function (callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, \Closure $setMetadata, ?LoggerInterface $logger) { + return $callback($item, $save); + }; + + return $previousWrapper; + } + + private function doGet(AdapterInterface $pool, string $key, callable $callback, ?float $beta, array &$metadata = null) + { + if (0 > $beta = $beta ?? 1.0) { + throw new InvalidArgumentException(sprintf('Argument "$beta" provided to "%s::get()" must be a positive number, %f given.', static::class, $beta)); + } + + static $setMetadata; + + $setMetadata ?? $setMetadata = \Closure::bind( + static function (CacheItem $item, float $startTime, ?array &$metadata) { + if ($item->expiry > $endTime = microtime(true)) { + $item->newMetadata[CacheItem::METADATA_EXPIRY] = $metadata[CacheItem::METADATA_EXPIRY] = $item->expiry; + $item->newMetadata[CacheItem::METADATA_CTIME] = $metadata[CacheItem::METADATA_CTIME] = (int) ceil(1000 * ($endTime - $startTime)); + } else { + unset($metadata[CacheItem::METADATA_EXPIRY], $metadata[CacheItem::METADATA_CTIME]); + } + }, + null, + CacheItem::class + ); + + return $this->contractsGet($pool, $key, function (CacheItem $item, bool &$save) use ($pool, $callback, $setMetadata, &$metadata, $key) { + // don't wrap nor save recursive calls + if (isset($this->computing[$key])) { + $value = $callback($item, $save); + $save = false; + + return $value; + } + + $this->computing[$key] = $key; + $startTime = microtime(true); + + try { + $value = ($this->callbackWrapper)($callback, $item, $save, $pool, function (CacheItem $item) use ($setMetadata, $startTime, &$metadata) { + $setMetadata($item, $startTime, $metadata); + }, $this->logger ?? null); + $setMetadata($item, $startTime, $metadata); + + return $value; + } finally { + unset($this->computing[$key]); + } + }, $beta, $metadata, $this->logger ?? null); + } +} diff --git a/vendor/symfony/cache/Traits/FilesystemCommonTrait.php b/vendor/symfony/cache/Traits/FilesystemCommonTrait.php new file mode 100644 index 0000000..8e45081 --- /dev/null +++ b/vendor/symfony/cache/Traits/FilesystemCommonTrait.php @@ -0,0 +1,196 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Symfony\Component\Cache\Exception\InvalidArgumentException; + +/** + * @author Nicolas Grekas + * + * @internal + */ +trait FilesystemCommonTrait +{ + private $directory; + private $tmp; + + private function init(string $namespace, ?string $directory) + { + if (!isset($directory[0])) { + $directory = sys_get_temp_dir().\DIRECTORY_SEPARATOR.'symfony-cache'; + } else { + $directory = realpath($directory) ?: $directory; + } + if (isset($namespace[0])) { + if (preg_match('#[^-+_.A-Za-z0-9]#', $namespace, $match)) { + throw new InvalidArgumentException(sprintf('Namespace contains "%s" but only characters in [-+_.A-Za-z0-9] are allowed.', $match[0])); + } + $directory .= \DIRECTORY_SEPARATOR.$namespace; + } else { + $directory .= \DIRECTORY_SEPARATOR.'@'; + } + if (!is_dir($directory)) { + @mkdir($directory, 0777, true); + } + $directory .= \DIRECTORY_SEPARATOR; + // On Windows the whole path is limited to 258 chars + if ('\\' === \DIRECTORY_SEPARATOR && \strlen($directory) > 234) { + throw new InvalidArgumentException(sprintf('Cache directory too long (%s).', $directory)); + } + + $this->directory = $directory; + } + + /** + * {@inheritdoc} + */ + protected function doClear(string $namespace) + { + $ok = true; + + foreach ($this->scanHashDir($this->directory) as $file) { + if ('' !== $namespace && !str_starts_with($this->getFileKey($file), $namespace)) { + continue; + } + + $ok = ($this->doUnlink($file) || !file_exists($file)) && $ok; + } + + return $ok; + } + + /** + * {@inheritdoc} + */ + protected function doDelete(array $ids) + { + $ok = true; + + foreach ($ids as $id) { + $file = $this->getFile($id); + $ok = (!is_file($file) || $this->doUnlink($file) || !file_exists($file)) && $ok; + } + + return $ok; + } + + protected function doUnlink(string $file) + { + return @unlink($file); + } + + private function write(string $file, string $data, int $expiresAt = null) + { + set_error_handler(__CLASS__.'::throwError'); + try { + if (null === $this->tmp) { + $this->tmp = $this->directory.bin2hex(random_bytes(6)); + } + try { + $h = fopen($this->tmp, 'x'); + } catch (\ErrorException $e) { + if (!str_contains($e->getMessage(), 'File exists')) { + throw $e; + } + + $this->tmp = $this->directory.bin2hex(random_bytes(6)); + $h = fopen($this->tmp, 'x'); + } + fwrite($h, $data); + fclose($h); + + if (null !== $expiresAt) { + touch($this->tmp, $expiresAt); + } + + return rename($this->tmp, $file); + } finally { + restore_error_handler(); + } + } + + private function getFile(string $id, bool $mkdir = false, string $directory = null) + { + // Use MD5 to favor speed over security, which is not an issue here + $hash = str_replace('/', '-', base64_encode(hash('md5', static::class.$id, true))); + $dir = ($directory ?? $this->directory).strtoupper($hash[0].\DIRECTORY_SEPARATOR.$hash[1].\DIRECTORY_SEPARATOR); + + if ($mkdir && !is_dir($dir)) { + @mkdir($dir, 0777, true); + } + + return $dir.substr($hash, 2, 20); + } + + private function getFileKey(string $file): string + { + return ''; + } + + private function scanHashDir(string $directory): \Generator + { + if (!is_dir($directory)) { + return; + } + + $chars = '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + + for ($i = 0; $i < 38; ++$i) { + if (!is_dir($directory.$chars[$i])) { + continue; + } + + for ($j = 0; $j < 38; ++$j) { + if (!is_dir($dir = $directory.$chars[$i].\DIRECTORY_SEPARATOR.$chars[$j])) { + continue; + } + + foreach (@scandir($dir, \SCANDIR_SORT_NONE) ?: [] as $file) { + if ('.' !== $file && '..' !== $file) { + yield $dir.\DIRECTORY_SEPARATOR.$file; + } + } + } + } + } + + /** + * @internal + */ + public static function throwError(int $type, string $message, string $file, int $line) + { + throw new \ErrorException($message, 0, $type, $file, $line); + } + + /** + * @return array + */ + public function __sleep() + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + if (method_exists(parent::class, '__destruct')) { + parent::__destruct(); + } + if (null !== $this->tmp && is_file($this->tmp)) { + unlink($this->tmp); + } + } +} diff --git a/vendor/symfony/cache/Traits/FilesystemTrait.php b/vendor/symfony/cache/Traits/FilesystemTrait.php new file mode 100644 index 0000000..38b741f --- /dev/null +++ b/vendor/symfony/cache/Traits/FilesystemTrait.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Symfony\Component\Cache\Exception\CacheException; + +/** + * @author Nicolas Grekas + * @author Rob Frawley 2nd + * + * @internal + */ +trait FilesystemTrait +{ + use FilesystemCommonTrait; + + private $marshaller; + + /** + * @return bool + */ + public function prune() + { + $time = time(); + $pruned = true; + + foreach ($this->scanHashDir($this->directory) as $file) { + if (!$h = @fopen($file, 'r')) { + continue; + } + + if (($expiresAt = (int) fgets($h)) && $time >= $expiresAt) { + fclose($h); + $pruned = @unlink($file) && !file_exists($file) && $pruned; + } else { + fclose($h); + } + } + + return $pruned; + } + + /** + * {@inheritdoc} + */ + protected function doFetch(array $ids) + { + $values = []; + $now = time(); + + foreach ($ids as $id) { + $file = $this->getFile($id); + if (!is_file($file) || !$h = @fopen($file, 'r')) { + continue; + } + if (($expiresAt = (int) fgets($h)) && $now >= $expiresAt) { + fclose($h); + @unlink($file); + } else { + $i = rawurldecode(rtrim(fgets($h))); + $value = stream_get_contents($h); + fclose($h); + if ($i === $id) { + $values[$id] = $this->marshaller->unmarshall($value); + } + } + } + + return $values; + } + + /** + * {@inheritdoc} + */ + protected function doHave(string $id) + { + $file = $this->getFile($id); + + return is_file($file) && (@filemtime($file) > time() || $this->doFetch([$id])); + } + + /** + * {@inheritdoc} + */ + protected function doSave(array $values, int $lifetime) + { + $expiresAt = $lifetime ? (time() + $lifetime) : 0; + $values = $this->marshaller->marshall($values, $failed); + + foreach ($values as $id => $value) { + if (!$this->write($this->getFile($id, true), $expiresAt."\n".rawurlencode($id)."\n".$value, $expiresAt)) { + $failed[] = $id; + } + } + + if ($failed && !is_writable($this->directory)) { + throw new CacheException(sprintf('Cache directory is not writable (%s).', $this->directory)); + } + + return $failed; + } + + private function getFileKey(string $file): string + { + if (!$h = @fopen($file, 'r')) { + return ''; + } + + fgets($h); // expiry + $encodedKey = fgets($h); + fclose($h); + + return rawurldecode(rtrim($encodedKey)); + } +} diff --git a/vendor/symfony/cache/Traits/ProxyTrait.php b/vendor/symfony/cache/Traits/ProxyTrait.php new file mode 100644 index 0000000..c86f360 --- /dev/null +++ b/vendor/symfony/cache/Traits/ProxyTrait.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Symfony\Component\Cache\PruneableInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * @author Nicolas Grekas + * + * @internal + */ +trait ProxyTrait +{ + private $pool; + + /** + * {@inheritdoc} + */ + public function prune() + { + return $this->pool instanceof PruneableInterface && $this->pool->prune(); + } + + /** + * {@inheritdoc} + */ + public function reset() + { + if ($this->pool instanceof ResetInterface) { + $this->pool->reset(); + } + } +} diff --git a/vendor/symfony/cache/Traits/RedisClusterNodeProxy.php b/vendor/symfony/cache/Traits/RedisClusterNodeProxy.php new file mode 100644 index 0000000..deba74f --- /dev/null +++ b/vendor/symfony/cache/Traits/RedisClusterNodeProxy.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +/** + * This file acts as a wrapper to the \RedisCluster implementation so it can accept the same type of calls as + * individual \Redis objects. + * + * Calls are made to individual nodes via: RedisCluster->{method}($host, ...args)' + * according to https://github.com/phpredis/phpredis/blob/develop/cluster.markdown#directed-node-commands + * + * @author Jack Thomas + * + * @internal + */ +class RedisClusterNodeProxy +{ + private $host; + private $redis; + + /** + * @param \RedisCluster|RedisClusterProxy $redis + */ + public function __construct(array $host, $redis) + { + $this->host = $host; + $this->redis = $redis; + } + + public function __call(string $method, array $args) + { + return $this->redis->{$method}($this->host, ...$args); + } + + public function scan(&$iIterator, $strPattern = null, $iCount = null) + { + return $this->redis->scan($iIterator, $this->host, $strPattern, $iCount); + } + + public function getOption($name) + { + return $this->redis->getOption($name); + } +} diff --git a/vendor/symfony/cache/Traits/RedisClusterProxy.php b/vendor/symfony/cache/Traits/RedisClusterProxy.php new file mode 100644 index 0000000..73c6a4f --- /dev/null +++ b/vendor/symfony/cache/Traits/RedisClusterProxy.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +/** + * @author Alessandro Chitolina + * + * @internal + */ +class RedisClusterProxy +{ + private $redis; + private $initializer; + + public function __construct(\Closure $initializer) + { + $this->initializer = $initializer; + } + + public function __call(string $method, array $args) + { + $this->redis ?: $this->redis = $this->initializer->__invoke(); + + return $this->redis->{$method}(...$args); + } + + public function hscan($strKey, &$iIterator, $strPattern = null, $iCount = null) + { + $this->redis ?: $this->redis = $this->initializer->__invoke(); + + return $this->redis->hscan($strKey, $iIterator, $strPattern, $iCount); + } + + public function scan(&$iIterator, $strPattern = null, $iCount = null) + { + $this->redis ?: $this->redis = $this->initializer->__invoke(); + + return $this->redis->scan($iIterator, $strPattern, $iCount); + } + + public function sscan($strKey, &$iIterator, $strPattern = null, $iCount = null) + { + $this->redis ?: $this->redis = $this->initializer->__invoke(); + + return $this->redis->sscan($strKey, $iIterator, $strPattern, $iCount); + } + + public function zscan($strKey, &$iIterator, $strPattern = null, $iCount = null) + { + $this->redis ?: $this->redis = $this->initializer->__invoke(); + + return $this->redis->zscan($strKey, $iIterator, $strPattern, $iCount); + } +} diff --git a/vendor/symfony/cache/Traits/RedisProxy.php b/vendor/symfony/cache/Traits/RedisProxy.php new file mode 100644 index 0000000..ec5cfab --- /dev/null +++ b/vendor/symfony/cache/Traits/RedisProxy.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class RedisProxy +{ + private $redis; + private $initializer; + private $ready = false; + + public function __construct(\Redis $redis, \Closure $initializer) + { + $this->redis = $redis; + $this->initializer = $initializer; + } + + public function __call(string $method, array $args) + { + $this->ready ?: $this->ready = $this->initializer->__invoke($this->redis); + + return $this->redis->{$method}(...$args); + } + + public function hscan($strKey, &$iIterator, $strPattern = null, $iCount = null) + { + $this->ready ?: $this->ready = $this->initializer->__invoke($this->redis); + + return $this->redis->hscan($strKey, $iIterator, $strPattern, $iCount); + } + + public function scan(&$iIterator, $strPattern = null, $iCount = null) + { + $this->ready ?: $this->ready = $this->initializer->__invoke($this->redis); + + return $this->redis->scan($iIterator, $strPattern, $iCount); + } + + public function sscan($strKey, &$iIterator, $strPattern = null, $iCount = null) + { + $this->ready ?: $this->ready = $this->initializer->__invoke($this->redis); + + return $this->redis->sscan($strKey, $iIterator, $strPattern, $iCount); + } + + public function zscan($strKey, &$iIterator, $strPattern = null, $iCount = null) + { + $this->ready ?: $this->ready = $this->initializer->__invoke($this->redis); + + return $this->redis->zscan($strKey, $iIterator, $strPattern, $iCount); + } +} diff --git a/vendor/symfony/cache/Traits/RedisTrait.php b/vendor/symfony/cache/Traits/RedisTrait.php new file mode 100644 index 0000000..aacd883 --- /dev/null +++ b/vendor/symfony/cache/Traits/RedisTrait.php @@ -0,0 +1,590 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Predis\Command\Redis\UNLINK; +use Predis\Connection\Aggregate\ClusterInterface; +use Predis\Connection\Aggregate\RedisCluster; +use Predis\Connection\Aggregate\ReplicationInterface; +use Predis\Response\Status; +use Symfony\Component\Cache\Exception\CacheException; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Marshaller\DefaultMarshaller; +use Symfony\Component\Cache\Marshaller\MarshallerInterface; + +/** + * @author Aurimas Niekis + * @author Nicolas Grekas + * + * @internal + */ +trait RedisTrait +{ + private static $defaultConnectionOptions = [ + 'class' => null, + 'persistent' => 0, + 'persistent_id' => null, + 'timeout' => 30, + 'read_timeout' => 0, + 'retry_interval' => 0, + 'tcp_keepalive' => 0, + 'lazy' => null, + 'redis_cluster' => false, + 'redis_sentinel' => null, + 'dbindex' => 0, + 'failover' => 'none', + 'ssl' => null, // see https://php.net/context.ssl + ]; + private $redis; + private $marshaller; + + /** + * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|RedisProxy|RedisClusterProxy $redis + */ + private function init($redis, string $namespace, int $defaultLifetime, ?MarshallerInterface $marshaller) + { + parent::__construct($namespace, $defaultLifetime); + + if (preg_match('#[^-+_.A-Za-z0-9]#', $namespace, $match)) { + throw new InvalidArgumentException(sprintf('RedisAdapter namespace contains "%s" but only characters in [-+_.A-Za-z0-9] are allowed.', $match[0])); + } + + if (!$redis instanceof \Redis && !$redis instanceof \RedisArray && !$redis instanceof \RedisCluster && !$redis instanceof \Predis\ClientInterface && !$redis instanceof RedisProxy && !$redis instanceof RedisClusterProxy) { + throw new InvalidArgumentException(sprintf('"%s()" expects parameter 1 to be Redis, RedisArray, RedisCluster or Predis\ClientInterface, "%s" given.', __METHOD__, get_debug_type($redis))); + } + + if ($redis instanceof \Predis\ClientInterface && $redis->getOptions()->exceptions) { + $options = clone $redis->getOptions(); + \Closure::bind(function () { $this->options['exceptions'] = false; }, $options, $options)(); + $redis = new $redis($redis->getConnection(), $options); + } + + $this->redis = $redis; + $this->marshaller = $marshaller ?? new DefaultMarshaller(); + } + + /** + * Creates a Redis connection using a DSN configuration. + * + * Example DSN: + * - redis://localhost + * - redis://example.com:1234 + * - redis://secret@example.com/13 + * - redis:///var/run/redis.sock + * - redis://secret@/var/run/redis.sock/13 + * + * @param array $options See self::$defaultConnectionOptions + * + * @return \Redis|\RedisArray|\RedisCluster|RedisClusterProxy|RedisProxy|\Predis\ClientInterface According to the "class" option + * + * @throws InvalidArgumentException when the DSN is invalid + */ + public static function createConnection(string $dsn, array $options = []) + { + if (str_starts_with($dsn, 'redis:')) { + $scheme = 'redis'; + } elseif (str_starts_with($dsn, 'rediss:')) { + $scheme = 'rediss'; + } else { + throw new InvalidArgumentException(sprintf('Invalid Redis DSN: "%s" does not start with "redis:" or "rediss".', $dsn)); + } + + if (!\extension_loaded('redis') && !class_exists(\Predis\Client::class)) { + throw new CacheException(sprintf('Cannot find the "redis" extension nor the "predis/predis" package: "%s".', $dsn)); + } + + $params = preg_replace_callback('#^'.$scheme.':(//)?(?:(?:[^:@]*+:)?([^@]*+)@)?#', function ($m) use (&$auth) { + if (isset($m[2])) { + $auth = $m[2]; + + if ('' === $auth) { + $auth = null; + } + } + + return 'file:'.($m[1] ?? ''); + }, $dsn); + + if (false === $params = parse_url($params)) { + throw new InvalidArgumentException(sprintf('Invalid Redis DSN: "%s".', $dsn)); + } + + $query = $hosts = []; + + $tls = 'rediss' === $scheme; + $tcpScheme = $tls ? 'tls' : 'tcp'; + + if (isset($params['query'])) { + parse_str($params['query'], $query); + + if (isset($query['host'])) { + if (!\is_array($hosts = $query['host'])) { + throw new InvalidArgumentException(sprintf('Invalid Redis DSN: "%s".', $dsn)); + } + foreach ($hosts as $host => $parameters) { + if (\is_string($parameters)) { + parse_str($parameters, $parameters); + } + if (false === $i = strrpos($host, ':')) { + $hosts[$host] = ['scheme' => $tcpScheme, 'host' => $host, 'port' => 6379] + $parameters; + } elseif ($port = (int) substr($host, 1 + $i)) { + $hosts[$host] = ['scheme' => $tcpScheme, 'host' => substr($host, 0, $i), 'port' => $port] + $parameters; + } else { + $hosts[$host] = ['scheme' => 'unix', 'path' => substr($host, 0, $i)] + $parameters; + } + } + $hosts = array_values($hosts); + } + } + + if (isset($params['host']) || isset($params['path'])) { + if (!isset($params['dbindex']) && isset($params['path'])) { + if (preg_match('#/(\d+)$#', $params['path'], $m)) { + $params['dbindex'] = $m[1]; + $params['path'] = substr($params['path'], 0, -\strlen($m[0])); + } elseif (isset($params['host'])) { + throw new InvalidArgumentException(sprintf('Invalid Redis DSN: "%s", the "dbindex" parameter must be a number.', $dsn)); + } + } + + if (isset($params['host'])) { + array_unshift($hosts, ['scheme' => $tcpScheme, 'host' => $params['host'], 'port' => $params['port'] ?? 6379]); + } else { + array_unshift($hosts, ['scheme' => 'unix', 'path' => $params['path']]); + } + } + + if (!$hosts) { + throw new InvalidArgumentException(sprintf('Invalid Redis DSN: "%s".', $dsn)); + } + + $params += $query + $options + self::$defaultConnectionOptions; + + if (isset($params['redis_sentinel']) && !class_exists(\Predis\Client::class) && !class_exists(\RedisSentinel::class)) { + throw new CacheException(sprintf('Redis Sentinel support requires the "predis/predis" package or the "redis" extension v5.2 or higher: "%s".', $dsn)); + } + + if ($params['redis_cluster'] && isset($params['redis_sentinel'])) { + throw new InvalidArgumentException(sprintf('Cannot use both "redis_cluster" and "redis_sentinel" at the same time: "%s".', $dsn)); + } + + if (null === $params['class'] && \extension_loaded('redis')) { + $class = $params['redis_cluster'] ? \RedisCluster::class : (1 < \count($hosts) ? \RedisArray::class : \Redis::class); + } else { + $class = $params['class'] ?? \Predis\Client::class; + } + + if (is_a($class, \Redis::class, true)) { + $connect = $params['persistent'] || $params['persistent_id'] ? 'pconnect' : 'connect'; + $redis = new $class(); + + $initializer = static function ($redis) use ($connect, $params, $dsn, $auth, $hosts, $tls) { + $host = $hosts[0]['host'] ?? $hosts[0]['path']; + $port = $hosts[0]['port'] ?? null; + + if (isset($hosts[0]['host']) && $tls) { + $host = 'tls://'.$host; + } + + if (isset($params['redis_sentinel'])) { + $sentinel = new \RedisSentinel($host, $port, $params['timeout'], (string) $params['persistent_id'], $params['retry_interval'], $params['read_timeout']); + + if (!$address = $sentinel->getMasterAddrByName($params['redis_sentinel'])) { + throw new InvalidArgumentException(sprintf('Failed to retrieve master information from master name "%s" and address "%s:%d".', $params['redis_sentinel'], $host, $port)); + } + + [$host, $port] = $address; + } + + try { + @$redis->{$connect}($host, $port, $params['timeout'], (string) $params['persistent_id'], $params['retry_interval'], $params['read_timeout'], ...\defined('Redis::SCAN_PREFIX') ? [['stream' => $params['ssl'] ?? null]] : []); + + set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; }); + try { + $isConnected = $redis->isConnected(); + } finally { + restore_error_handler(); + } + if (!$isConnected) { + $error = preg_match('/^Redis::p?connect\(\): (.*)/', $error, $error) ? sprintf(' (%s)', $error[1]) : ''; + throw new InvalidArgumentException(sprintf('Redis connection "%s" failed: ', $dsn).$error.'.'); + } + + if ((null !== $auth && !$redis->auth($auth)) + || ($params['dbindex'] && !$redis->select($params['dbindex'])) + ) { + $e = preg_replace('/^ERR /', '', $redis->getLastError()); + throw new InvalidArgumentException(sprintf('Redis connection "%s" failed: ', $dsn).$e.'.'); + } + + if (0 < $params['tcp_keepalive'] && \defined('Redis::OPT_TCP_KEEPALIVE')) { + $redis->setOption(\Redis::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']); + } + } catch (\RedisException $e) { + throw new InvalidArgumentException(sprintf('Redis connection "%s" failed: ', $dsn).$e->getMessage()); + } + + return true; + }; + + if ($params['lazy']) { + $redis = new RedisProxy($redis, $initializer); + } else { + $initializer($redis); + } + } elseif (is_a($class, \RedisArray::class, true)) { + foreach ($hosts as $i => $host) { + switch ($host['scheme']) { + case 'tcp': $hosts[$i] = $host['host'].':'.$host['port']; break; + case 'tls': $hosts[$i] = 'tls://'.$host['host'].':'.$host['port']; break; + default: $hosts[$i] = $host['path']; + } + } + $params['lazy_connect'] = $params['lazy'] ?? true; + $params['connect_timeout'] = $params['timeout']; + + try { + $redis = new $class($hosts, $params); + } catch (\RedisClusterException $e) { + throw new InvalidArgumentException(sprintf('Redis connection "%s" failed: ', $dsn).$e->getMessage()); + } + + if (0 < $params['tcp_keepalive'] && \defined('Redis::OPT_TCP_KEEPALIVE')) { + $redis->setOption(\Redis::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']); + } + } elseif (is_a($class, \RedisCluster::class, true)) { + $initializer = static function () use ($class, $params, $dsn, $hosts) { + foreach ($hosts as $i => $host) { + switch ($host['scheme']) { + case 'tcp': $hosts[$i] = $host['host'].':'.$host['port']; break; + case 'tls': $hosts[$i] = 'tls://'.$host['host'].':'.$host['port']; break; + default: $hosts[$i] = $host['path']; + } + } + + try { + $redis = new $class(null, $hosts, $params['timeout'], $params['read_timeout'], (bool) $params['persistent'], $params['auth'] ?? '', ...\defined('Redis::SCAN_PREFIX') ? [$params['ssl'] ?? null] : []); + } catch (\RedisClusterException $e) { + throw new InvalidArgumentException(sprintf('Redis connection "%s" failed: ', $dsn).$e->getMessage()); + } + + if (0 < $params['tcp_keepalive'] && \defined('Redis::OPT_TCP_KEEPALIVE')) { + $redis->setOption(\Redis::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']); + } + switch ($params['failover']) { + case 'error': $redis->setOption(\RedisCluster::OPT_SLAVE_FAILOVER, \RedisCluster::FAILOVER_ERROR); break; + case 'distribute': $redis->setOption(\RedisCluster::OPT_SLAVE_FAILOVER, \RedisCluster::FAILOVER_DISTRIBUTE); break; + case 'slaves': $redis->setOption(\RedisCluster::OPT_SLAVE_FAILOVER, \RedisCluster::FAILOVER_DISTRIBUTE_SLAVES); break; + } + + return $redis; + }; + + $redis = $params['lazy'] ? new RedisClusterProxy($initializer) : $initializer(); + } elseif (is_a($class, \Predis\ClientInterface::class, true)) { + if ($params['redis_cluster']) { + $params['cluster'] = 'redis'; + } elseif (isset($params['redis_sentinel'])) { + $params['replication'] = 'sentinel'; + $params['service'] = $params['redis_sentinel']; + } + $params += ['parameters' => []]; + $params['parameters'] += [ + 'persistent' => $params['persistent'], + 'timeout' => $params['timeout'], + 'read_write_timeout' => $params['read_timeout'], + 'tcp_nodelay' => true, + ]; + if ($params['dbindex']) { + $params['parameters']['database'] = $params['dbindex']; + } + if (null !== $auth) { + $params['parameters']['password'] = $auth; + } + if (1 === \count($hosts) && !($params['redis_cluster'] || $params['redis_sentinel'])) { + $hosts = $hosts[0]; + } elseif (\in_array($params['failover'], ['slaves', 'distribute'], true) && !isset($params['replication'])) { + $params['replication'] = true; + $hosts[0] += ['alias' => 'master']; + } + $params['exceptions'] = false; + + $redis = new $class($hosts, array_diff_key($params, array_diff_key(self::$defaultConnectionOptions, ['ssl' => null]))); + if (isset($params['redis_sentinel'])) { + $redis->getConnection()->setSentinelTimeout($params['timeout']); + } + } elseif (class_exists($class, false)) { + throw new InvalidArgumentException(sprintf('"%s" is not a subclass of "Redis", "RedisArray", "RedisCluster" nor "Predis\ClientInterface".', $class)); + } else { + throw new InvalidArgumentException(sprintf('Class "%s" does not exist.', $class)); + } + + return $redis; + } + + /** + * {@inheritdoc} + */ + protected function doFetch(array $ids) + { + if (!$ids) { + return []; + } + + $result = []; + + if ($this->redis instanceof \Predis\ClientInterface && $this->redis->getConnection() instanceof ClusterInterface) { + $values = $this->pipeline(function () use ($ids) { + foreach ($ids as $id) { + yield 'get' => [$id]; + } + }); + } else { + $values = $this->redis->mget($ids); + + if (!\is_array($values) || \count($values) !== \count($ids)) { + return []; + } + + $values = array_combine($ids, $values); + } + + foreach ($values as $id => $v) { + if ($v) { + $result[$id] = $this->marshaller->unmarshall($v); + } + } + + return $result; + } + + /** + * {@inheritdoc} + */ + protected function doHave(string $id) + { + return (bool) $this->redis->exists($id); + } + + /** + * {@inheritdoc} + */ + protected function doClear(string $namespace) + { + if ($this->redis instanceof \Predis\ClientInterface) { + $prefix = $this->redis->getOptions()->prefix ? $this->redis->getOptions()->prefix->getPrefix() : ''; + $prefixLen = \strlen($prefix); + } + + $cleared = true; + $hosts = $this->getHosts(); + $host = reset($hosts); + if ($host instanceof \Predis\Client && $host->getConnection() instanceof ReplicationInterface) { + // Predis supports info command only on the master in replication environments + $hosts = [$host->getClientFor('master')]; + } + + foreach ($hosts as $host) { + if (!isset($namespace[0])) { + $cleared = $host->flushDb() && $cleared; + continue; + } + + $info = $host->info('Server'); + $info = $info['Server'] ?? $info; + + if (!$host instanceof \Predis\ClientInterface) { + $prefix = \defined('Redis::SCAN_PREFIX') && (\Redis::SCAN_PREFIX & $host->getOption(\Redis::OPT_SCAN)) ? '' : $host->getOption(\Redis::OPT_PREFIX); + $prefixLen = \strlen($host->getOption(\Redis::OPT_PREFIX) ?? ''); + } + $pattern = $prefix.$namespace.'*'; + + if (!version_compare($info['redis_version'], '2.8', '>=')) { + // As documented in Redis documentation (http://redis.io/commands/keys) using KEYS + // can hang your server when it is executed against large databases (millions of items). + // Whenever you hit this scale, you should really consider upgrading to Redis 2.8 or above. + $unlink = version_compare($info['redis_version'], '4.0', '>=') ? 'UNLINK' : 'DEL'; + $args = $this->redis instanceof \Predis\ClientInterface ? [0, $pattern] : [[$pattern], 0]; + $cleared = $host->eval("local keys=redis.call('KEYS',ARGV[1]) for i=1,#keys,5000 do redis.call('$unlink',unpack(keys,i,math.min(i+4999,#keys))) end return 1", $args[0], $args[1]) && $cleared; + continue; + } + + $cursor = null; + do { + $keys = $host instanceof \Predis\ClientInterface ? $host->scan($cursor, 'MATCH', $pattern, 'COUNT', 1000) : $host->scan($cursor, $pattern, 1000); + if (isset($keys[1]) && \is_array($keys[1])) { + $cursor = $keys[0]; + $keys = $keys[1]; + } + if ($keys) { + if ($prefixLen) { + foreach ($keys as $i => $key) { + $keys[$i] = substr($key, $prefixLen); + } + } + $this->doDelete($keys); + } + } while ($cursor = (int) $cursor); + } + + return $cleared; + } + + /** + * {@inheritdoc} + */ + protected function doDelete(array $ids) + { + if (!$ids) { + return true; + } + + if ($this->redis instanceof \Predis\ClientInterface && $this->redis->getConnection() instanceof ClusterInterface) { + static $del; + $del = $del ?? (class_exists(UNLINK::class) ? 'unlink' : 'del'); + + $this->pipeline(function () use ($ids, $del) { + foreach ($ids as $id) { + yield $del => [$id]; + } + })->rewind(); + } else { + static $unlink = true; + + if ($unlink) { + try { + $unlink = false !== $this->redis->unlink($ids); + } catch (\Throwable $e) { + $unlink = false; + } + } + + if (!$unlink) { + $this->redis->del($ids); + } + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doSave(array $values, int $lifetime) + { + if (!$values = $this->marshaller->marshall($values, $failed)) { + return $failed; + } + + $results = $this->pipeline(function () use ($values, $lifetime) { + foreach ($values as $id => $value) { + if (0 >= $lifetime) { + yield 'set' => [$id, $value]; + } else { + yield 'setEx' => [$id, $lifetime, $value]; + } + } + }); + + foreach ($results as $id => $result) { + if (true !== $result && (!$result instanceof Status || Status::get('OK') !== $result)) { + $failed[] = $id; + } + } + + return $failed; + } + + private function pipeline(\Closure $generator, object $redis = null): \Generator + { + $ids = []; + $redis = $redis ?? $this->redis; + + if ($redis instanceof RedisClusterProxy || $redis instanceof \RedisCluster || ($redis instanceof \Predis\ClientInterface && $redis->getConnection() instanceof RedisCluster)) { + // phpredis & predis don't support pipelining with RedisCluster + // see https://github.com/phpredis/phpredis/blob/develop/cluster.markdown#pipelining + // see https://github.com/nrk/predis/issues/267#issuecomment-123781423 + $results = []; + foreach ($generator() as $command => $args) { + $results[] = $redis->{$command}(...$args); + $ids[] = 'eval' === $command ? ($redis instanceof \Predis\ClientInterface ? $args[2] : $args[1][0]) : $args[0]; + } + } elseif ($redis instanceof \Predis\ClientInterface) { + $results = $redis->pipeline(static function ($redis) use ($generator, &$ids) { + foreach ($generator() as $command => $args) { + $redis->{$command}(...$args); + $ids[] = 'eval' === $command ? $args[2] : $args[0]; + } + }); + } elseif ($redis instanceof \RedisArray) { + $connections = $results = $ids = []; + foreach ($generator() as $command => $args) { + $id = 'eval' === $command ? $args[1][0] : $args[0]; + if (!isset($connections[$h = $redis->_target($id)])) { + $connections[$h] = [$redis->_instance($h), -1]; + $connections[$h][0]->multi(\Redis::PIPELINE); + } + $connections[$h][0]->{$command}(...$args); + $results[] = [$h, ++$connections[$h][1]]; + $ids[] = $id; + } + foreach ($connections as $h => $c) { + $connections[$h] = $c[0]->exec(); + } + foreach ($results as $k => [$h, $c]) { + $results[$k] = $connections[$h][$c]; + } + } else { + $redis->multi(\Redis::PIPELINE); + foreach ($generator() as $command => $args) { + $redis->{$command}(...$args); + $ids[] = 'eval' === $command ? $args[1][0] : $args[0]; + } + $results = $redis->exec(); + } + + if (!$redis instanceof \Predis\ClientInterface && 'eval' === $command && $redis->getLastError()) { + $e = new \RedisException($redis->getLastError()); + $results = array_map(function ($v) use ($e) { return false === $v ? $e : $v; }, $results); + } + + foreach ($ids as $k => $id) { + yield $id => $results[$k]; + } + } + + private function getHosts(): array + { + $hosts = [$this->redis]; + if ($this->redis instanceof \Predis\ClientInterface) { + $connection = $this->redis->getConnection(); + if ($connection instanceof ClusterInterface && $connection instanceof \Traversable) { + $hosts = []; + foreach ($connection as $c) { + $hosts[] = new \Predis\Client($c); + } + } + } elseif ($this->redis instanceof \RedisArray) { + $hosts = []; + foreach ($this->redis->_hosts() as $host) { + $hosts[] = $this->redis->_instance($host); + } + } elseif ($this->redis instanceof RedisClusterProxy || $this->redis instanceof \RedisCluster) { + $hosts = []; + foreach ($this->redis->_masters() as $host) { + $hosts[] = new RedisClusterNodeProxy($host, $this->redis); + } + } + + return $hosts; + } +} diff --git a/vendor/symfony/cache/composer.json b/vendor/symfony/cache/composer.json new file mode 100644 index 0000000..1ebcfc9 --- /dev/null +++ b/vendor/symfony/cache/composer.json @@ -0,0 +1,60 @@ +{ + "name": "symfony/cache", + "type": "library", + "description": "Provides an extended PSR-6, PSR-16 (and tags) implementation", + "keywords": ["caching", "psr6"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "provide": { + "psr/cache-implementation": "1.0|2.0", + "psr/simple-cache-implementation": "1.0|2.0", + "symfony/cache-implementation": "1.0|2.0" + }, + "require": { + "php": ">=7.2.5", + "psr/cache": "^1.0|^2.0", + "psr/log": "^1.1|^2|^3", + "symfony/cache-contracts": "^1.1.7|^2", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php73": "^1.9", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/var-exporter": "^4.4|^5.0|^6.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/cache": "^1.6|^2.0", + "doctrine/dbal": "^2.13.1|^3.0", + "predis/predis": "^1.1", + "psr/simple-cache": "^1.0|^2.0", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/filesystem": "^4.4|^5.0|^6.0", + "symfony/http-kernel": "^4.4|^5.0|^6.0", + "symfony/messenger": "^4.4|^5.0|^6.0", + "symfony/var-dumper": "^4.4|^5.0|^6.0" + }, + "conflict": { + "doctrine/dbal": "<2.13.1", + "symfony/dependency-injection": "<4.4", + "symfony/http-kernel": "<4.4", + "symfony/var-dumper": "<4.4" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Cache\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/deprecation-contracts/.gitignore b/vendor/symfony/deprecation-contracts/.gitignore new file mode 100644 index 0000000..c49a5d8 --- /dev/null +++ b/vendor/symfony/deprecation-contracts/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/deprecation-contracts/CHANGELOG.md b/vendor/symfony/deprecation-contracts/CHANGELOG.md new file mode 100644 index 0000000..7932e26 --- /dev/null +++ b/vendor/symfony/deprecation-contracts/CHANGELOG.md @@ -0,0 +1,5 @@ +CHANGELOG +========= + +The changelog is maintained for all Symfony contracts at the following URL: +https://github.com/symfony/contracts/blob/main/CHANGELOG.md diff --git a/vendor/symfony/deprecation-contracts/LICENSE b/vendor/symfony/deprecation-contracts/LICENSE new file mode 100644 index 0000000..ad85e17 --- /dev/null +++ b/vendor/symfony/deprecation-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020-2021 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/deprecation-contracts/README.md b/vendor/symfony/deprecation-contracts/README.md new file mode 100644 index 0000000..4957933 --- /dev/null +++ b/vendor/symfony/deprecation-contracts/README.md @@ -0,0 +1,26 @@ +Symfony Deprecation Contracts +============================= + +A generic function and convention to trigger deprecation notices. + +This package provides a single global function named `trigger_deprecation()` that triggers silenced deprecation notices. + +By using a custom PHP error handler such as the one provided by the Symfony ErrorHandler component, +the triggered deprecations can be caught and logged for later discovery, both on dev and prod environments. + +The function requires at least 3 arguments: + - the name of the Composer package that is triggering the deprecation + - the version of the package that introduced the deprecation + - the message of the deprecation + - more arguments can be provided: they will be inserted in the message using `printf()` formatting + +Example: +```php +trigger_deprecation('symfony/blockchain', '8.9', 'Using "%s" is deprecated, use "%s" instead.', 'bitcoin', 'fabcoin'); +``` + +This will generate the following message: +`Since symfony/blockchain 8.9: Using "bitcoin" is deprecated, use "fabcoin" instead.` + +While not necessarily recommended, the deprecation notices can be completely ignored by declaring an empty +`function trigger_deprecation() {}` in your application. diff --git a/vendor/symfony/deprecation-contracts/composer.json b/vendor/symfony/deprecation-contracts/composer.json new file mode 100644 index 0000000..cc7cc12 --- /dev/null +++ b/vendor/symfony/deprecation-contracts/composer.json @@ -0,0 +1,35 @@ +{ + "name": "symfony/deprecation-contracts", + "type": "library", + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + } +} diff --git a/vendor/symfony/deprecation-contracts/function.php b/vendor/symfony/deprecation-contracts/function.php new file mode 100644 index 0000000..d437150 --- /dev/null +++ b/vendor/symfony/deprecation-contracts/function.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (!function_exists('trigger_deprecation')) { + /** + * Triggers a silenced deprecation notice. + * + * @param string $package The name of the Composer package that is triggering the deprecation + * @param string $version The version of the package that introduced the deprecation + * @param string $message The message of the deprecation + * @param mixed ...$args Values to insert in the message using printf() formatting + * + * @author Nicolas Grekas + */ + function trigger_deprecation(string $package, string $version, string $message, ...$args): void + { + @trigger_error(($package || $version ? "Since $package $version: " : '').($args ? vsprintf($message, $args) : $message), \E_USER_DEPRECATED); + } +} diff --git a/vendor/symfony/event-dispatcher-contracts/.gitignore b/vendor/symfony/event-dispatcher-contracts/.gitignore new file mode 100644 index 0000000..c49a5d8 --- /dev/null +++ b/vendor/symfony/event-dispatcher-contracts/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/event-dispatcher-contracts/CHANGELOG.md b/vendor/symfony/event-dispatcher-contracts/CHANGELOG.md new file mode 100644 index 0000000..7932e26 --- /dev/null +++ b/vendor/symfony/event-dispatcher-contracts/CHANGELOG.md @@ -0,0 +1,5 @@ +CHANGELOG +========= + +The changelog is maintained for all Symfony contracts at the following URL: +https://github.com/symfony/contracts/blob/main/CHANGELOG.md diff --git a/vendor/symfony/event-dispatcher-contracts/Event.php b/vendor/symfony/event-dispatcher-contracts/Event.php new file mode 100644 index 0000000..46dcb2b --- /dev/null +++ b/vendor/symfony/event-dispatcher-contracts/Event.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\EventDispatcher; + +use Psr\EventDispatcher\StoppableEventInterface; + +/** + * Event is the base class for classes containing event data. + * + * This class contains no event data. It is used by events that do not pass + * state information to an event handler when an event is raised. + * + * You can call the method stopPropagation() to abort the execution of + * further listeners in your event listener. + * + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Bernhard Schussek + * @author Nicolas Grekas + */ +class Event implements StoppableEventInterface +{ + private $propagationStopped = false; + + /** + * {@inheritdoc} + */ + public function isPropagationStopped(): bool + { + return $this->propagationStopped; + } + + /** + * Stops the propagation of the event to further event listeners. + * + * If multiple event listeners are connected to the same event, no + * further event listener will be triggered once any trigger calls + * stopPropagation(). + */ + public function stopPropagation(): void + { + $this->propagationStopped = true; + } +} diff --git a/vendor/symfony/event-dispatcher-contracts/EventDispatcherInterface.php b/vendor/symfony/event-dispatcher-contracts/EventDispatcherInterface.php new file mode 100644 index 0000000..351dc51 --- /dev/null +++ b/vendor/symfony/event-dispatcher-contracts/EventDispatcherInterface.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\EventDispatcher; + +use Psr\EventDispatcher\EventDispatcherInterface as PsrEventDispatcherInterface; + +/** + * Allows providing hooks on domain-specific lifecycles by dispatching events. + */ +interface EventDispatcherInterface extends PsrEventDispatcherInterface +{ + /** + * Dispatches an event to all registered listeners. + * + * @param object $event The event to pass to the event handlers/listeners + * @param string|null $eventName The name of the event to dispatch. If not supplied, + * the class of $event should be used instead. + * + * @return object The passed $event MUST be returned + */ + public function dispatch(object $event, string $eventName = null): object; +} diff --git a/vendor/symfony/event-dispatcher-contracts/LICENSE b/vendor/symfony/event-dispatcher-contracts/LICENSE new file mode 100644 index 0000000..2358414 --- /dev/null +++ b/vendor/symfony/event-dispatcher-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2021 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/event-dispatcher-contracts/README.md b/vendor/symfony/event-dispatcher-contracts/README.md new file mode 100644 index 0000000..b1ab4c0 --- /dev/null +++ b/vendor/symfony/event-dispatcher-contracts/README.md @@ -0,0 +1,9 @@ +Symfony EventDispatcher Contracts +================================= + +A set of abstractions extracted out of the Symfony components. + +Can be used to build on semantics that the Symfony components proved useful - and +that already have battle tested implementations. + +See https://github.com/symfony/contracts/blob/main/README.md for more information. diff --git a/vendor/symfony/event-dispatcher-contracts/composer.json b/vendor/symfony/event-dispatcher-contracts/composer.json new file mode 100644 index 0000000..660df81 --- /dev/null +++ b/vendor/symfony/event-dispatcher-contracts/composer.json @@ -0,0 +1,38 @@ +{ + "name": "symfony/event-dispatcher-contracts", + "type": "library", + "description": "Generic abstractions related to dispatching event", + "keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.2.5", + "psr/event-dispatcher": "^1" + }, + "suggest": { + "symfony/event-dispatcher-implementation": "" + }, + "autoload": { + "psr-4": { "Symfony\\Contracts\\EventDispatcher\\": "" } + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + } +} diff --git a/vendor/symfony/event-dispatcher/Attribute/AsEventListener.php b/vendor/symfony/event-dispatcher/Attribute/AsEventListener.php new file mode 100644 index 0000000..bb931b8 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Attribute/AsEventListener.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Attribute; + +/** + * Service tag to autoconfigure event listeners. + * + * @author Alexander M. Turek + */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class AsEventListener +{ + public function __construct( + public ?string $event = null, + public ?string $method = null, + public int $priority = 0, + public ?string $dispatcher = null, + ) { + } +} diff --git a/vendor/symfony/event-dispatcher/CHANGELOG.md b/vendor/symfony/event-dispatcher/CHANGELOG.md new file mode 100644 index 0000000..0f98598 --- /dev/null +++ b/vendor/symfony/event-dispatcher/CHANGELOG.md @@ -0,0 +1,91 @@ +CHANGELOG +========= + +5.4 +--- + + * Allow `#[AsEventListener]` attribute on methods + +5.3 +--- + + * Add `#[AsEventListener]` attribute for declaring listeners on PHP 8 + +5.1.0 +----- + + * The `LegacyEventDispatcherProxy` class has been deprecated. + * Added an optional `dispatcher` attribute to the listener and subscriber tags in `RegisterListenerPass`. + +5.0.0 +----- + + * The signature of the `EventDispatcherInterface::dispatch()` method has been changed to `dispatch($event, string $eventName = null): object`. + * The `Event` class has been removed in favor of `Symfony\Contracts\EventDispatcher\Event`. + * The `TraceableEventDispatcherInterface` has been removed. + * The `WrappedListener` class is now final. + +4.4.0 +----- + + * `AddEventAliasesPass` has been added, allowing applications and bundles to extend the event alias mapping used by `RegisterListenersPass`. + * Made the `event` attribute of the `kernel.event_listener` tag optional for FQCN events. + +4.3.0 +----- + + * The signature of the `EventDispatcherInterface::dispatch()` method should be updated to `dispatch($event, string $eventName = null)`, not doing so is deprecated + * deprecated the `Event` class, use `Symfony\Contracts\EventDispatcher\Event` instead + +4.1.0 +----- + + * added support for invokable event listeners tagged with `kernel.event_listener` by default + * The `TraceableEventDispatcher::getOrphanedEvents()` method has been added. + * The `TraceableEventDispatcherInterface` has been deprecated. + +4.0.0 +----- + + * removed the `ContainerAwareEventDispatcher` class + * added the `reset()` method to the `TraceableEventDispatcherInterface` + +3.4.0 +----- + + * Implementing `TraceableEventDispatcherInterface` without the `reset()` method has been deprecated. + +3.3.0 +----- + + * The ContainerAwareEventDispatcher class has been deprecated. Use EventDispatcher with closure factories instead. + +3.0.0 +----- + + * The method `getListenerPriority($eventName, $listener)` has been added to the + `EventDispatcherInterface`. + * The methods `Event::setDispatcher()`, `Event::getDispatcher()`, `Event::setName()` + and `Event::getName()` have been removed. + The event dispatcher and the event name are passed to the listener call. + +2.5.0 +----- + + * added Debug\TraceableEventDispatcher (originally in HttpKernel) + * changed Debug\TraceableEventDispatcherInterface to extend EventDispatcherInterface + * added RegisterListenersPass (originally in HttpKernel) + +2.1.0 +----- + + * added TraceableEventDispatcherInterface + * added ContainerAwareEventDispatcher + * added a reference to the EventDispatcher on the Event + * added a reference to the Event name on the event + * added fluid interface to the dispatch() method which now returns the Event + object + * added GenericEvent event class + * added the possibility for subscribers to subscribe several times for the + same event + * added ImmutableEventDispatcher diff --git a/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php b/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php new file mode 100644 index 0000000..e1d2007 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php @@ -0,0 +1,366 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Debug; + +use Psr\EventDispatcher\StoppableEventInterface; +use Psr\Log\LoggerInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Contracts\Service\ResetInterface; + +/** + * Collects some data about event listeners. + * + * This event dispatcher delegates the dispatching to another one. + * + * @author Fabien Potencier + */ +class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterface +{ + protected $logger; + protected $stopwatch; + + /** + * @var \SplObjectStorage + */ + private $callStack; + private $dispatcher; + private $wrappedListeners; + private $orphanedEvents; + private $requestStack; + private $currentRequestHash = ''; + + public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, LoggerInterface $logger = null, RequestStack $requestStack = null) + { + $this->dispatcher = $dispatcher; + $this->stopwatch = $stopwatch; + $this->logger = $logger; + $this->wrappedListeners = []; + $this->orphanedEvents = []; + $this->requestStack = $requestStack; + } + + /** + * {@inheritdoc} + */ + public function addListener(string $eventName, $listener, int $priority = 0) + { + $this->dispatcher->addListener($eventName, $listener, $priority); + } + + /** + * {@inheritdoc} + */ + public function addSubscriber(EventSubscriberInterface $subscriber) + { + $this->dispatcher->addSubscriber($subscriber); + } + + /** + * {@inheritdoc} + */ + public function removeListener(string $eventName, $listener) + { + if (isset($this->wrappedListeners[$eventName])) { + foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) { + if ($wrappedListener->getWrappedListener() === $listener) { + $listener = $wrappedListener; + unset($this->wrappedListeners[$eventName][$index]); + break; + } + } + } + + return $this->dispatcher->removeListener($eventName, $listener); + } + + /** + * {@inheritdoc} + */ + public function removeSubscriber(EventSubscriberInterface $subscriber) + { + return $this->dispatcher->removeSubscriber($subscriber); + } + + /** + * {@inheritdoc} + */ + public function getListeners(string $eventName = null) + { + return $this->dispatcher->getListeners($eventName); + } + + /** + * {@inheritdoc} + */ + public function getListenerPriority(string $eventName, $listener) + { + // we might have wrapped listeners for the event (if called while dispatching) + // in that case get the priority by wrapper + if (isset($this->wrappedListeners[$eventName])) { + foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) { + if ($wrappedListener->getWrappedListener() === $listener) { + return $this->dispatcher->getListenerPriority($eventName, $wrappedListener); + } + } + } + + return $this->dispatcher->getListenerPriority($eventName, $listener); + } + + /** + * {@inheritdoc} + */ + public function hasListeners(string $eventName = null) + { + return $this->dispatcher->hasListeners($eventName); + } + + /** + * {@inheritdoc} + */ + public function dispatch(object $event, string $eventName = null): object + { + $eventName = $eventName ?? \get_class($event); + + if (null === $this->callStack) { + $this->callStack = new \SplObjectStorage(); + } + + $currentRequestHash = $this->currentRequestHash = $this->requestStack && ($request = $this->requestStack->getCurrentRequest()) ? spl_object_hash($request) : ''; + + if (null !== $this->logger && $event instanceof StoppableEventInterface && $event->isPropagationStopped()) { + $this->logger->debug(sprintf('The "%s" event is already stopped. No listeners have been called.', $eventName)); + } + + $this->preProcess($eventName); + try { + $this->beforeDispatch($eventName, $event); + try { + $e = $this->stopwatch->start($eventName, 'section'); + try { + $this->dispatcher->dispatch($event, $eventName); + } finally { + if ($e->isStarted()) { + $e->stop(); + } + } + } finally { + $this->afterDispatch($eventName, $event); + } + } finally { + $this->currentRequestHash = $currentRequestHash; + $this->postProcess($eventName); + } + + return $event; + } + + /** + * @return array + */ + public function getCalledListeners(Request $request = null) + { + if (null === $this->callStack) { + return []; + } + + $hash = $request ? spl_object_hash($request) : null; + $called = []; + foreach ($this->callStack as $listener) { + [$eventName, $requestHash] = $this->callStack->getInfo(); + if (null === $hash || $hash === $requestHash) { + $called[] = $listener->getInfo($eventName); + } + } + + return $called; + } + + /** + * @return array + */ + public function getNotCalledListeners(Request $request = null) + { + try { + $allListeners = $this->getListeners(); + } catch (\Exception $e) { + if (null !== $this->logger) { + $this->logger->info('An exception was thrown while getting the uncalled listeners.', ['exception' => $e]); + } + + // unable to retrieve the uncalled listeners + return []; + } + + $hash = $request ? spl_object_hash($request) : null; + $calledListeners = []; + + if (null !== $this->callStack) { + foreach ($this->callStack as $calledListener) { + [, $requestHash] = $this->callStack->getInfo(); + + if (null === $hash || $hash === $requestHash) { + $calledListeners[] = $calledListener->getWrappedListener(); + } + } + } + + $notCalled = []; + foreach ($allListeners as $eventName => $listeners) { + foreach ($listeners as $listener) { + if (!\in_array($listener, $calledListeners, true)) { + if (!$listener instanceof WrappedListener) { + $listener = new WrappedListener($listener, null, $this->stopwatch, $this); + } + $notCalled[] = $listener->getInfo($eventName); + } + } + } + + uasort($notCalled, [$this, 'sortNotCalledListeners']); + + return $notCalled; + } + + public function getOrphanedEvents(Request $request = null): array + { + if ($request) { + return $this->orphanedEvents[spl_object_hash($request)] ?? []; + } + + if (!$this->orphanedEvents) { + return []; + } + + return array_merge(...array_values($this->orphanedEvents)); + } + + public function reset() + { + $this->callStack = null; + $this->orphanedEvents = []; + $this->currentRequestHash = ''; + } + + /** + * Proxies all method calls to the original event dispatcher. + * + * @param string $method The method name + * @param array $arguments The method arguments + * + * @return mixed + */ + public function __call(string $method, array $arguments) + { + return $this->dispatcher->{$method}(...$arguments); + } + + /** + * Called before dispatching the event. + */ + protected function beforeDispatch(string $eventName, object $event) + { + } + + /** + * Called after dispatching the event. + */ + protected function afterDispatch(string $eventName, object $event) + { + } + + private function preProcess(string $eventName): void + { + if (!$this->dispatcher->hasListeners($eventName)) { + $this->orphanedEvents[$this->currentRequestHash][] = $eventName; + + return; + } + + foreach ($this->dispatcher->getListeners($eventName) as $listener) { + $priority = $this->getListenerPriority($eventName, $listener); + $wrappedListener = new WrappedListener($listener instanceof WrappedListener ? $listener->getWrappedListener() : $listener, null, $this->stopwatch, $this); + $this->wrappedListeners[$eventName][] = $wrappedListener; + $this->dispatcher->removeListener($eventName, $listener); + $this->dispatcher->addListener($eventName, $wrappedListener, $priority); + $this->callStack->attach($wrappedListener, [$eventName, $this->currentRequestHash]); + } + } + + private function postProcess(string $eventName): void + { + unset($this->wrappedListeners[$eventName]); + $skipped = false; + foreach ($this->dispatcher->getListeners($eventName) as $listener) { + if (!$listener instanceof WrappedListener) { // #12845: a new listener was added during dispatch. + continue; + } + // Unwrap listener + $priority = $this->getListenerPriority($eventName, $listener); + $this->dispatcher->removeListener($eventName, $listener); + $this->dispatcher->addListener($eventName, $listener->getWrappedListener(), $priority); + + if (null !== $this->logger) { + $context = ['event' => $eventName, 'listener' => $listener->getPretty()]; + } + + if ($listener->wasCalled()) { + if (null !== $this->logger) { + $this->logger->debug('Notified event "{event}" to listener "{listener}".', $context); + } + } else { + $this->callStack->detach($listener); + } + + if (null !== $this->logger && $skipped) { + $this->logger->debug('Listener "{listener}" was not called for event "{event}".', $context); + } + + if ($listener->stoppedPropagation()) { + if (null !== $this->logger) { + $this->logger->debug('Listener "{listener}" stopped propagation of the event "{event}".', $context); + } + + $skipped = true; + } + } + } + + private function sortNotCalledListeners(array $a, array $b) + { + if (0 !== $cmp = strcmp($a['event'], $b['event'])) { + return $cmp; + } + + if (\is_int($a['priority']) && !\is_int($b['priority'])) { + return 1; + } + + if (!\is_int($a['priority']) && \is_int($b['priority'])) { + return -1; + } + + if ($a['priority'] === $b['priority']) { + return 0; + } + + if ($a['priority'] > $b['priority']) { + return -1; + } + + return 1; + } +} diff --git a/vendor/symfony/event-dispatcher/Debug/WrappedListener.php b/vendor/symfony/event-dispatcher/Debug/WrappedListener.php new file mode 100644 index 0000000..3916716 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Debug/WrappedListener.php @@ -0,0 +1,127 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Debug; + +use Psr\EventDispatcher\StoppableEventInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\VarDumper\Caster\ClassStub; + +/** + * @author Fabien Potencier + */ +final class WrappedListener +{ + private $listener; + private $optimizedListener; + private $name; + private $called; + private $stoppedPropagation; + private $stopwatch; + private $dispatcher; + private $pretty; + private $stub; + private $priority; + private static $hasClassStub; + + public function __construct($listener, ?string $name, Stopwatch $stopwatch, EventDispatcherInterface $dispatcher = null) + { + $this->listener = $listener; + $this->optimizedListener = $listener instanceof \Closure ? $listener : (\is_callable($listener) ? \Closure::fromCallable($listener) : null); + $this->stopwatch = $stopwatch; + $this->dispatcher = $dispatcher; + $this->called = false; + $this->stoppedPropagation = false; + + if (\is_array($listener)) { + $this->name = \is_object($listener[0]) ? get_debug_type($listener[0]) : $listener[0]; + $this->pretty = $this->name.'::'.$listener[1]; + } elseif ($listener instanceof \Closure) { + $r = new \ReflectionFunction($listener); + if (str_contains($r->name, '{closure}')) { + $this->pretty = $this->name = 'closure'; + } elseif ($class = $r->getClosureScopeClass()) { + $this->name = $class->name; + $this->pretty = $this->name.'::'.$r->name; + } else { + $this->pretty = $this->name = $r->name; + } + } elseif (\is_string($listener)) { + $this->pretty = $this->name = $listener; + } else { + $this->name = get_debug_type($listener); + $this->pretty = $this->name.'::__invoke'; + } + + if (null !== $name) { + $this->name = $name; + } + + if (null === self::$hasClassStub) { + self::$hasClassStub = class_exists(ClassStub::class); + } + } + + public function getWrappedListener() + { + return $this->listener; + } + + public function wasCalled(): bool + { + return $this->called; + } + + public function stoppedPropagation(): bool + { + return $this->stoppedPropagation; + } + + public function getPretty(): string + { + return $this->pretty; + } + + public function getInfo(string $eventName): array + { + if (null === $this->stub) { + $this->stub = self::$hasClassStub ? new ClassStub($this->pretty.'()', $this->listener) : $this->pretty.'()'; + } + + return [ + 'event' => $eventName, + 'priority' => null !== $this->priority ? $this->priority : (null !== $this->dispatcher ? $this->dispatcher->getListenerPriority($eventName, $this->listener) : null), + 'pretty' => $this->pretty, + 'stub' => $this->stub, + ]; + } + + public function __invoke(object $event, string $eventName, EventDispatcherInterface $dispatcher): void + { + $dispatcher = $this->dispatcher ?: $dispatcher; + + $this->called = true; + $this->priority = $dispatcher->getListenerPriority($eventName, $this->listener); + + $e = $this->stopwatch->start($this->name, 'event_listener'); + + ($this->optimizedListener ?? $this->listener)($event, $eventName, $dispatcher); + + if ($e->isStarted()) { + $e->stop(); + } + + if ($event instanceof StoppableEventInterface && $event->isPropagationStopped()) { + $this->stoppedPropagation = true; + } + } +} diff --git a/vendor/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.php b/vendor/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.php new file mode 100644 index 0000000..6e7292b --- /dev/null +++ b/vendor/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * This pass allows bundles to extend the list of event aliases. + * + * @author Alexander M. Turek + */ +class AddEventAliasesPass implements CompilerPassInterface +{ + private $eventAliases; + private $eventAliasesParameter; + + public function __construct(array $eventAliases, string $eventAliasesParameter = 'event_dispatcher.event_aliases') + { + if (1 < \func_num_args()) { + trigger_deprecation('symfony/event-dispatcher', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); + } + + $this->eventAliases = $eventAliases; + $this->eventAliasesParameter = $eventAliasesParameter; + } + + public function process(ContainerBuilder $container): void + { + $eventAliases = $container->hasParameter($this->eventAliasesParameter) ? $container->getParameter($this->eventAliasesParameter) : []; + + $container->setParameter( + $this->eventAliasesParameter, + array_merge($eventAliases, $this->eventAliases) + ); + } +} diff --git a/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php b/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php new file mode 100644 index 0000000..8eabe7d --- /dev/null +++ b/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php @@ -0,0 +1,238 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\DependencyInjection; + +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Contracts\EventDispatcher\Event; + +/** + * Compiler pass to register tagged services for an event dispatcher. + */ +class RegisterListenersPass implements CompilerPassInterface +{ + protected $dispatcherService; + protected $listenerTag; + protected $subscriberTag; + protected $eventAliasesParameter; + + private $hotPathEvents = []; + private $hotPathTagName = 'container.hot_path'; + private $noPreloadEvents = []; + private $noPreloadTagName = 'container.no_preload'; + + public function __construct(string $dispatcherService = 'event_dispatcher', string $listenerTag = 'kernel.event_listener', string $subscriberTag = 'kernel.event_subscriber', string $eventAliasesParameter = 'event_dispatcher.event_aliases') + { + if (0 < \func_num_args()) { + trigger_deprecation('symfony/event-dispatcher', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); + } + + $this->dispatcherService = $dispatcherService; + $this->listenerTag = $listenerTag; + $this->subscriberTag = $subscriberTag; + $this->eventAliasesParameter = $eventAliasesParameter; + } + + /** + * @return $this + */ + public function setHotPathEvents(array $hotPathEvents) + { + $this->hotPathEvents = array_flip($hotPathEvents); + + if (1 < \func_num_args()) { + trigger_deprecation('symfony/event-dispatcher', '5.4', 'Configuring "$tagName" in "%s" is deprecated.', __METHOD__); + $this->hotPathTagName = func_get_arg(1); + } + + return $this; + } + + /** + * @return $this + */ + public function setNoPreloadEvents(array $noPreloadEvents): self + { + $this->noPreloadEvents = array_flip($noPreloadEvents); + + if (1 < \func_num_args()) { + trigger_deprecation('symfony/event-dispatcher', '5.4', 'Configuring "$tagName" in "%s" is deprecated.', __METHOD__); + $this->noPreloadTagName = func_get_arg(1); + } + + return $this; + } + + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition($this->dispatcherService) && !$container->hasAlias($this->dispatcherService)) { + return; + } + + $aliases = []; + + if ($container->hasParameter($this->eventAliasesParameter)) { + $aliases = $container->getParameter($this->eventAliasesParameter); + } + + $globalDispatcherDefinition = $container->findDefinition($this->dispatcherService); + + foreach ($container->findTaggedServiceIds($this->listenerTag, true) as $id => $events) { + $noPreload = 0; + + foreach ($events as $event) { + $priority = $event['priority'] ?? 0; + + if (!isset($event['event'])) { + if ($container->getDefinition($id)->hasTag($this->subscriberTag)) { + continue; + } + + $event['method'] = $event['method'] ?? '__invoke'; + $event['event'] = $this->getEventFromTypeDeclaration($container, $id, $event['method']); + } + + $event['event'] = $aliases[$event['event']] ?? $event['event']; + + if (!isset($event['method'])) { + $event['method'] = 'on'.preg_replace_callback([ + '/(?<=\b|_)[a-z]/i', + '/[^a-z0-9]/i', + ], function ($matches) { return strtoupper($matches[0]); }, $event['event']); + $event['method'] = preg_replace('/[^a-z0-9]/i', '', $event['method']); + + if (null !== ($class = $container->getDefinition($id)->getClass()) && ($r = $container->getReflectionClass($class, false)) && !$r->hasMethod($event['method']) && $r->hasMethod('__invoke')) { + $event['method'] = '__invoke'; + } + } + + $dispatcherDefinition = $globalDispatcherDefinition; + if (isset($event['dispatcher'])) { + $dispatcherDefinition = $container->getDefinition($event['dispatcher']); + } + + $dispatcherDefinition->addMethodCall('addListener', [$event['event'], [new ServiceClosureArgument(new Reference($id)), $event['method']], $priority]); + + if (isset($this->hotPathEvents[$event['event']])) { + $container->getDefinition($id)->addTag($this->hotPathTagName); + } elseif (isset($this->noPreloadEvents[$event['event']])) { + ++$noPreload; + } + } + + if ($noPreload && \count($events) === $noPreload) { + $container->getDefinition($id)->addTag($this->noPreloadTagName); + } + } + + $extractingDispatcher = new ExtractingEventDispatcher(); + + foreach ($container->findTaggedServiceIds($this->subscriberTag, true) as $id => $tags) { + $def = $container->getDefinition($id); + + // We must assume that the class value has been correctly filled, even if the service is created by a factory + $class = $def->getClass(); + + if (!$r = $container->getReflectionClass($class)) { + throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); + } + if (!$r->isSubclassOf(EventSubscriberInterface::class)) { + throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, EventSubscriberInterface::class)); + } + $class = $r->name; + + $dispatcherDefinitions = []; + foreach ($tags as $attributes) { + if (!isset($attributes['dispatcher']) || isset($dispatcherDefinitions[$attributes['dispatcher']])) { + continue; + } + + $dispatcherDefinitions[$attributes['dispatcher']] = $container->getDefinition($attributes['dispatcher']); + } + + if (!$dispatcherDefinitions) { + $dispatcherDefinitions = [$globalDispatcherDefinition]; + } + + $noPreload = 0; + ExtractingEventDispatcher::$aliases = $aliases; + ExtractingEventDispatcher::$subscriber = $class; + $extractingDispatcher->addSubscriber($extractingDispatcher); + foreach ($extractingDispatcher->listeners as $args) { + $args[1] = [new ServiceClosureArgument(new Reference($id)), $args[1]]; + foreach ($dispatcherDefinitions as $dispatcherDefinition) { + $dispatcherDefinition->addMethodCall('addListener', $args); + } + + if (isset($this->hotPathEvents[$args[0]])) { + $container->getDefinition($id)->addTag($this->hotPathTagName); + } elseif (isset($this->noPreloadEvents[$args[0]])) { + ++$noPreload; + } + } + if ($noPreload && \count($extractingDispatcher->listeners) === $noPreload) { + $container->getDefinition($id)->addTag($this->noPreloadTagName); + } + $extractingDispatcher->listeners = []; + ExtractingEventDispatcher::$aliases = []; + } + } + + private function getEventFromTypeDeclaration(ContainerBuilder $container, string $id, string $method): string + { + if ( + null === ($class = $container->getDefinition($id)->getClass()) + || !($r = $container->getReflectionClass($class, false)) + || !$r->hasMethod($method) + || 1 > ($m = $r->getMethod($method))->getNumberOfParameters() + || !($type = $m->getParameters()[0]->getType()) instanceof \ReflectionNamedType + || $type->isBuiltin() + || Event::class === ($name = $type->getName()) + ) { + throw new InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "%s" tags.', $id, $this->listenerTag)); + } + + return $name; + } +} + +/** + * @internal + */ +class ExtractingEventDispatcher extends EventDispatcher implements EventSubscriberInterface +{ + public $listeners = []; + + public static $aliases = []; + public static $subscriber; + + public function addListener(string $eventName, $listener, int $priority = 0) + { + $this->listeners[] = [$eventName, $listener[1], $priority]; + } + + public static function getSubscribedEvents(): array + { + $events = []; + + foreach ([self::$subscriber, 'getSubscribedEvents']() as $eventName => $params) { + $events[self::$aliases[$eventName] ?? $eventName] = $params; + } + + return $events; + } +} diff --git a/vendor/symfony/event-dispatcher/EventDispatcher.php b/vendor/symfony/event-dispatcher/EventDispatcher.php new file mode 100644 index 0000000..c479a75 --- /dev/null +++ b/vendor/symfony/event-dispatcher/EventDispatcher.php @@ -0,0 +1,280 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +use Psr\EventDispatcher\StoppableEventInterface; +use Symfony\Component\EventDispatcher\Debug\WrappedListener; + +/** + * The EventDispatcherInterface is the central point of Symfony's event listener system. + * + * Listeners are registered on the manager and events are dispatched through the + * manager. + * + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Bernhard Schussek + * @author Fabien Potencier + * @author Jordi Boggiano + * @author Jordan Alliot + * @author Nicolas Grekas + */ +class EventDispatcher implements EventDispatcherInterface +{ + private $listeners = []; + private $sorted = []; + private $optimized; + + public function __construct() + { + if (__CLASS__ === static::class) { + $this->optimized = []; + } + } + + /** + * {@inheritdoc} + */ + public function dispatch(object $event, string $eventName = null): object + { + $eventName = $eventName ?? \get_class($event); + + if (null !== $this->optimized) { + $listeners = $this->optimized[$eventName] ?? (empty($this->listeners[$eventName]) ? [] : $this->optimizeListeners($eventName)); + } else { + $listeners = $this->getListeners($eventName); + } + + if ($listeners) { + $this->callListeners($listeners, $eventName, $event); + } + + return $event; + } + + /** + * {@inheritdoc} + */ + public function getListeners(string $eventName = null) + { + if (null !== $eventName) { + if (empty($this->listeners[$eventName])) { + return []; + } + + if (!isset($this->sorted[$eventName])) { + $this->sortListeners($eventName); + } + + return $this->sorted[$eventName]; + } + + foreach ($this->listeners as $eventName => $eventListeners) { + if (!isset($this->sorted[$eventName])) { + $this->sortListeners($eventName); + } + } + + return array_filter($this->sorted); + } + + /** + * {@inheritdoc} + */ + public function getListenerPriority(string $eventName, $listener) + { + if (empty($this->listeners[$eventName])) { + return null; + } + + if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) { + $listener[0] = $listener[0](); + $listener[1] = $listener[1] ?? '__invoke'; + } + + foreach ($this->listeners[$eventName] as $priority => &$listeners) { + foreach ($listeners as &$v) { + if ($v !== $listener && \is_array($v) && isset($v[0]) && $v[0] instanceof \Closure && 2 >= \count($v)) { + $v[0] = $v[0](); + $v[1] = $v[1] ?? '__invoke'; + } + if ($v === $listener) { + return $priority; + } + } + } + + return null; + } + + /** + * {@inheritdoc} + */ + public function hasListeners(string $eventName = null) + { + if (null !== $eventName) { + return !empty($this->listeners[$eventName]); + } + + foreach ($this->listeners as $eventListeners) { + if ($eventListeners) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function addListener(string $eventName, $listener, int $priority = 0) + { + $this->listeners[$eventName][$priority][] = $listener; + unset($this->sorted[$eventName], $this->optimized[$eventName]); + } + + /** + * {@inheritdoc} + */ + public function removeListener(string $eventName, $listener) + { + if (empty($this->listeners[$eventName])) { + return; + } + + if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) { + $listener[0] = $listener[0](); + $listener[1] = $listener[1] ?? '__invoke'; + } + + foreach ($this->listeners[$eventName] as $priority => &$listeners) { + foreach ($listeners as $k => &$v) { + if ($v !== $listener && \is_array($v) && isset($v[0]) && $v[0] instanceof \Closure && 2 >= \count($v)) { + $v[0] = $v[0](); + $v[1] = $v[1] ?? '__invoke'; + } + if ($v === $listener) { + unset($listeners[$k], $this->sorted[$eventName], $this->optimized[$eventName]); + } + } + + if (!$listeners) { + unset($this->listeners[$eventName][$priority]); + } + } + } + + /** + * {@inheritdoc} + */ + public function addSubscriber(EventSubscriberInterface $subscriber) + { + foreach ($subscriber->getSubscribedEvents() as $eventName => $params) { + if (\is_string($params)) { + $this->addListener($eventName, [$subscriber, $params]); + } elseif (\is_string($params[0])) { + $this->addListener($eventName, [$subscriber, $params[0]], $params[1] ?? 0); + } else { + foreach ($params as $listener) { + $this->addListener($eventName, [$subscriber, $listener[0]], $listener[1] ?? 0); + } + } + } + } + + /** + * {@inheritdoc} + */ + public function removeSubscriber(EventSubscriberInterface $subscriber) + { + foreach ($subscriber->getSubscribedEvents() as $eventName => $params) { + if (\is_array($params) && \is_array($params[0])) { + foreach ($params as $listener) { + $this->removeListener($eventName, [$subscriber, $listener[0]]); + } + } else { + $this->removeListener($eventName, [$subscriber, \is_string($params) ? $params : $params[0]]); + } + } + } + + /** + * Triggers the listeners of an event. + * + * This method can be overridden to add functionality that is executed + * for each listener. + * + * @param callable[] $listeners The event listeners + * @param string $eventName The name of the event to dispatch + * @param object $event The event object to pass to the event handlers/listeners + */ + protected function callListeners(iterable $listeners, string $eventName, object $event) + { + $stoppable = $event instanceof StoppableEventInterface; + + foreach ($listeners as $listener) { + if ($stoppable && $event->isPropagationStopped()) { + break; + } + $listener($event, $eventName, $this); + } + } + + /** + * Sorts the internal list of listeners for the given event by priority. + */ + private function sortListeners(string $eventName) + { + krsort($this->listeners[$eventName]); + $this->sorted[$eventName] = []; + + foreach ($this->listeners[$eventName] as &$listeners) { + foreach ($listeners as $k => &$listener) { + if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) { + $listener[0] = $listener[0](); + $listener[1] = $listener[1] ?? '__invoke'; + } + $this->sorted[$eventName][] = $listener; + } + } + } + + /** + * Optimizes the internal list of listeners for the given event by priority. + */ + private function optimizeListeners(string $eventName): array + { + krsort($this->listeners[$eventName]); + $this->optimized[$eventName] = []; + + foreach ($this->listeners[$eventName] as &$listeners) { + foreach ($listeners as &$listener) { + $closure = &$this->optimized[$eventName][]; + if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) { + $closure = static function (...$args) use (&$listener, &$closure) { + if ($listener[0] instanceof \Closure) { + $listener[0] = $listener[0](); + $listener[1] = $listener[1] ?? '__invoke'; + } + ($closure = \Closure::fromCallable($listener))(...$args); + }; + } else { + $closure = $listener instanceof \Closure || $listener instanceof WrappedListener ? $listener : \Closure::fromCallable($listener); + } + } + } + + return $this->optimized[$eventName]; + } +} diff --git a/vendor/symfony/event-dispatcher/EventDispatcherInterface.php b/vendor/symfony/event-dispatcher/EventDispatcherInterface.php new file mode 100644 index 0000000..cc324e1 --- /dev/null +++ b/vendor/symfony/event-dispatcher/EventDispatcherInterface.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface as ContractsEventDispatcherInterface; + +/** + * The EventDispatcherInterface is the central point of Symfony's event listener system. + * Listeners are registered on the manager and events are dispatched through the + * manager. + * + * @author Bernhard Schussek + */ +interface EventDispatcherInterface extends ContractsEventDispatcherInterface +{ + /** + * Adds an event listener that listens on the specified events. + * + * @param int $priority The higher this value, the earlier an event + * listener will be triggered in the chain (defaults to 0) + */ + public function addListener(string $eventName, callable $listener, int $priority = 0); + + /** + * Adds an event subscriber. + * + * The subscriber is asked for all the events it is + * interested in and added as a listener for these events. + */ + public function addSubscriber(EventSubscriberInterface $subscriber); + + /** + * Removes an event listener from the specified events. + */ + public function removeListener(string $eventName, callable $listener); + + public function removeSubscriber(EventSubscriberInterface $subscriber); + + /** + * Gets the listeners of a specific event or all listeners sorted by descending priority. + * + * @return array + */ + public function getListeners(string $eventName = null); + + /** + * Gets the listener priority for a specific event. + * + * Returns null if the event or the listener does not exist. + * + * @return int|null + */ + public function getListenerPriority(string $eventName, callable $listener); + + /** + * Checks whether an event has any registered listeners. + * + * @return bool + */ + public function hasListeners(string $eventName = null); +} diff --git a/vendor/symfony/event-dispatcher/EventSubscriberInterface.php b/vendor/symfony/event-dispatcher/EventSubscriberInterface.php new file mode 100644 index 0000000..2085e42 --- /dev/null +++ b/vendor/symfony/event-dispatcher/EventSubscriberInterface.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +/** + * An EventSubscriber knows itself what events it is interested in. + * If an EventSubscriber is added to an EventDispatcherInterface, the manager invokes + * {@link getSubscribedEvents} and registers the subscriber as a listener for all + * returned events. + * + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Bernhard Schussek + */ +interface EventSubscriberInterface +{ + /** + * Returns an array of event names this subscriber wants to listen to. + * + * The array keys are event names and the value can be: + * + * * The method name to call (priority defaults to 0) + * * An array composed of the method name to call and the priority + * * An array of arrays composed of the method names to call and respective + * priorities, or 0 if unset + * + * For instance: + * + * * ['eventName' => 'methodName'] + * * ['eventName' => ['methodName', $priority]] + * * ['eventName' => [['methodName1', $priority], ['methodName2']]] + * + * The code must not depend on runtime state as it will only be called at compile time. + * All logic depending on runtime state must be put into the individual methods handling the events. + * + * @return array> + */ + public static function getSubscribedEvents(); +} diff --git a/vendor/symfony/event-dispatcher/GenericEvent.php b/vendor/symfony/event-dispatcher/GenericEvent.php new file mode 100644 index 0000000..b32a301 --- /dev/null +++ b/vendor/symfony/event-dispatcher/GenericEvent.php @@ -0,0 +1,182 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +use Symfony\Contracts\EventDispatcher\Event; + +/** + * Event encapsulation class. + * + * Encapsulates events thus decoupling the observer from the subject they encapsulate. + * + * @author Drak + * + * @implements \ArrayAccess + * @implements \IteratorAggregate + */ +class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate +{ + protected $subject; + protected $arguments; + + /** + * Encapsulate an event with $subject and $args. + * + * @param mixed $subject The subject of the event, usually an object or a callable + * @param array $arguments Arguments to store in the event + */ + public function __construct($subject = null, array $arguments = []) + { + $this->subject = $subject; + $this->arguments = $arguments; + } + + /** + * Getter for subject property. + * + * @return mixed + */ + public function getSubject() + { + return $this->subject; + } + + /** + * Get argument by key. + * + * @return mixed + * + * @throws \InvalidArgumentException if key is not found + */ + public function getArgument(string $key) + { + if ($this->hasArgument($key)) { + return $this->arguments[$key]; + } + + throw new \InvalidArgumentException(sprintf('Argument "%s" not found.', $key)); + } + + /** + * Add argument to event. + * + * @param mixed $value Value + * + * @return $this + */ + public function setArgument(string $key, $value) + { + $this->arguments[$key] = $value; + + return $this; + } + + /** + * Getter for all arguments. + * + * @return array + */ + public function getArguments() + { + return $this->arguments; + } + + /** + * Set args property. + * + * @return $this + */ + public function setArguments(array $args = []) + { + $this->arguments = $args; + + return $this; + } + + /** + * Has argument. + * + * @return bool + */ + public function hasArgument(string $key) + { + return \array_key_exists($key, $this->arguments); + } + + /** + * ArrayAccess for argument getter. + * + * @param string $key Array key + * + * @return mixed + * + * @throws \InvalidArgumentException if key does not exist in $this->args + */ + #[\ReturnTypeWillChange] + public function offsetGet($key) + { + return $this->getArgument($key); + } + + /** + * ArrayAccess for argument setter. + * + * @param string $key Array key to set + * @param mixed $value Value + * + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetSet($key, $value) + { + $this->setArgument($key, $value); + } + + /** + * ArrayAccess for unset argument. + * + * @param string $key Array key + * + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetUnset($key) + { + if ($this->hasArgument($key)) { + unset($this->arguments[$key]); + } + } + + /** + * ArrayAccess has argument. + * + * @param string $key Array key + * + * @return bool + */ + #[\ReturnTypeWillChange] + public function offsetExists($key) + { + return $this->hasArgument($key); + } + + /** + * IteratorAggregate for iterating over the object like an array. + * + * @return \ArrayIterator + */ + #[\ReturnTypeWillChange] + public function getIterator() + { + return new \ArrayIterator($this->arguments); + } +} diff --git a/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php b/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php new file mode 100644 index 0000000..568d79c --- /dev/null +++ b/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +/** + * A read-only proxy for an event dispatcher. + * + * @author Bernhard Schussek + */ +class ImmutableEventDispatcher implements EventDispatcherInterface +{ + private $dispatcher; + + public function __construct(EventDispatcherInterface $dispatcher) + { + $this->dispatcher = $dispatcher; + } + + /** + * {@inheritdoc} + */ + public function dispatch(object $event, string $eventName = null): object + { + return $this->dispatcher->dispatch($event, $eventName); + } + + /** + * {@inheritdoc} + */ + public function addListener(string $eventName, $listener, int $priority = 0) + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); + } + + /** + * {@inheritdoc} + */ + public function addSubscriber(EventSubscriberInterface $subscriber) + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); + } + + /** + * {@inheritdoc} + */ + public function removeListener(string $eventName, $listener) + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); + } + + /** + * {@inheritdoc} + */ + public function removeSubscriber(EventSubscriberInterface $subscriber) + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); + } + + /** + * {@inheritdoc} + */ + public function getListeners(string $eventName = null) + { + return $this->dispatcher->getListeners($eventName); + } + + /** + * {@inheritdoc} + */ + public function getListenerPriority(string $eventName, $listener) + { + return $this->dispatcher->getListenerPriority($eventName, $listener); + } + + /** + * {@inheritdoc} + */ + public function hasListeners(string $eventName = null) + { + return $this->dispatcher->hasListeners($eventName); + } +} diff --git a/vendor/symfony/event-dispatcher/LICENSE b/vendor/symfony/event-dispatcher/LICENSE new file mode 100644 index 0000000..9ff2d0d --- /dev/null +++ b/vendor/symfony/event-dispatcher/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2021 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/event-dispatcher/LegacyEventDispatcherProxy.php b/vendor/symfony/event-dispatcher/LegacyEventDispatcherProxy.php new file mode 100644 index 0000000..6e17c8f --- /dev/null +++ b/vendor/symfony/event-dispatcher/LegacyEventDispatcherProxy.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; + +trigger_deprecation('symfony/event-dispatcher', '5.1', '%s is deprecated, use the event dispatcher without the proxy.', LegacyEventDispatcherProxy::class); + +/** + * A helper class to provide BC/FC with the legacy signature of EventDispatcherInterface::dispatch(). + * + * @author Nicolas Grekas + * + * @deprecated since Symfony 5.1 + */ +final class LegacyEventDispatcherProxy +{ + public static function decorate(?EventDispatcherInterface $dispatcher): ?EventDispatcherInterface + { + return $dispatcher; + } +} diff --git a/vendor/symfony/event-dispatcher/README.md b/vendor/symfony/event-dispatcher/README.md new file mode 100644 index 0000000..dcdb68d --- /dev/null +++ b/vendor/symfony/event-dispatcher/README.md @@ -0,0 +1,15 @@ +EventDispatcher Component +========================= + +The EventDispatcher component provides tools that allow your application +components to communicate with each other by dispatching events and listening to +them. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/event_dispatcher.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/event-dispatcher/composer.json b/vendor/symfony/event-dispatcher/composer.json new file mode 100644 index 0000000..32b42e4 --- /dev/null +++ b/vendor/symfony/event-dispatcher/composer.json @@ -0,0 +1,52 @@ +{ + "name": "symfony/event-dispatcher", + "type": "library", + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/event-dispatcher-contracts": "^2|^3", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/error-handler": "^4.4|^5.0|^6.0", + "symfony/http-foundation": "^4.4|^5.0|^6.0", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/stopwatch": "^4.4|^5.0|^6.0", + "psr/log": "^1|^2|^3" + }, + "conflict": { + "symfony/dependency-injection": "<4.4" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\EventDispatcher\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/expression-language/CHANGELOG.md b/vendor/symfony/expression-language/CHANGELOG.md new file mode 100644 index 0000000..f5c1f6d --- /dev/null +++ b/vendor/symfony/expression-language/CHANGELOG.md @@ -0,0 +1,26 @@ +CHANGELOG +========= + +5.1.0 +----- + + * added `lint` method to `ExpressionLanguage` class + * added `lint` method to `Parser` class + +4.0.0 +----- + + * the first argument of the `ExpressionLanguage` constructor must be an instance + of `CacheItemPoolInterface` + * removed the `ArrayParserCache` and `ParserCacheAdapter` classes + * removed the `ParserCacheInterface` + +2.6.0 +----- + + * Added ExpressionFunction and ExpressionFunctionProviderInterface + +2.4.0 +----- + + * added the component diff --git a/vendor/symfony/expression-language/Compiler.php b/vendor/symfony/expression-language/Compiler.php new file mode 100644 index 0000000..e8a064d --- /dev/null +++ b/vendor/symfony/expression-language/Compiler.php @@ -0,0 +1,147 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage; + +use Symfony\Contracts\Service\ResetInterface; + +/** + * Compiles a node to PHP code. + * + * @author Fabien Potencier + */ +class Compiler implements ResetInterface +{ + private $source; + private $functions; + + public function __construct(array $functions) + { + $this->functions = $functions; + } + + public function getFunction(string $name) + { + return $this->functions[$name]; + } + + /** + * Gets the current PHP code after compilation. + * + * @return string + */ + public function getSource() + { + return $this->source; + } + + /** + * @return $this + */ + public function reset() + { + $this->source = ''; + + return $this; + } + + /** + * Compiles a node. + * + * @return $this + */ + public function compile(Node\Node $node) + { + $node->compile($this); + + return $this; + } + + public function subcompile(Node\Node $node) + { + $current = $this->source; + $this->source = ''; + + $node->compile($this); + + $source = $this->source; + $this->source = $current; + + return $source; + } + + /** + * Adds a raw string to the compiled code. + * + * @return $this + */ + public function raw(string $string) + { + $this->source .= $string; + + return $this; + } + + /** + * Adds a quoted string to the compiled code. + * + * @return $this + */ + public function string(string $value) + { + $this->source .= sprintf('"%s"', addcslashes($value, "\0\t\"\$\\")); + + return $this; + } + + /** + * Returns a PHP representation of a given value. + * + * @param mixed $value The value to convert + * + * @return $this + */ + public function repr($value) + { + if (\is_int($value) || \is_float($value)) { + if (false !== $locale = setlocale(\LC_NUMERIC, 0)) { + setlocale(\LC_NUMERIC, 'C'); + } + + $this->raw($value); + + if (false !== $locale) { + setlocale(\LC_NUMERIC, $locale); + } + } elseif (null === $value) { + $this->raw('null'); + } elseif (\is_bool($value)) { + $this->raw($value ? 'true' : 'false'); + } elseif (\is_array($value)) { + $this->raw('['); + $first = true; + foreach ($value as $key => $value) { + if (!$first) { + $this->raw(', '); + } + $first = false; + $this->repr($key); + $this->raw(' => '); + $this->repr($value); + } + $this->raw(']'); + } else { + $this->string($value); + } + + return $this; + } +} diff --git a/vendor/symfony/expression-language/Expression.php b/vendor/symfony/expression-language/Expression.php new file mode 100644 index 0000000..6b81478 --- /dev/null +++ b/vendor/symfony/expression-language/Expression.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage; + +/** + * Represents an expression. + * + * @author Fabien Potencier + */ +class Expression +{ + protected $expression; + + public function __construct(string $expression) + { + $this->expression = $expression; + } + + /** + * Gets the expression. + * + * @return string + */ + public function __toString() + { + return $this->expression; + } +} diff --git a/vendor/symfony/expression-language/ExpressionFunction.php b/vendor/symfony/expression-language/ExpressionFunction.php new file mode 100644 index 0000000..2bc17b4 --- /dev/null +++ b/vendor/symfony/expression-language/ExpressionFunction.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage; + +/** + * Represents a function that can be used in an expression. + * + * A function is defined by two PHP callables. The callables are used + * by the language to compile and/or evaluate the function. + * + * The "compiler" function is used at compilation time and must return a + * PHP representation of the function call (it receives the function + * arguments as arguments). + * + * The "evaluator" function is used for expression evaluation and must return + * the value of the function call based on the values defined for the + * expression (it receives the values as a first argument and the function + * arguments as remaining arguments). + * + * @author Fabien Potencier + */ +class ExpressionFunction +{ + private $name; + private $compiler; + private $evaluator; + + /** + * @param string $name The function name + * @param callable $compiler A callable able to compile the function + * @param callable $evaluator A callable able to evaluate the function + */ + public function __construct(string $name, callable $compiler, callable $evaluator) + { + $this->name = $name; + $this->compiler = $compiler instanceof \Closure ? $compiler : \Closure::fromCallable($compiler); + $this->evaluator = $evaluator instanceof \Closure ? $evaluator : \Closure::fromCallable($evaluator); + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @return \Closure + */ + public function getCompiler() + { + return $this->compiler; + } + + /** + * @return \Closure + */ + public function getEvaluator() + { + return $this->evaluator; + } + + /** + * Creates an ExpressionFunction from a PHP function name. + * + * @param string|null $expressionFunctionName The expression function name (default: same than the PHP function name) + * + * @return self + * + * @throws \InvalidArgumentException if given PHP function name does not exist + * @throws \InvalidArgumentException if given PHP function name is in namespace + * and expression function name is not defined + */ + public static function fromPhp(string $phpFunctionName, string $expressionFunctionName = null) + { + $phpFunctionName = ltrim($phpFunctionName, '\\'); + if (!\function_exists($phpFunctionName)) { + throw new \InvalidArgumentException(sprintf('PHP function "%s" does not exist.', $phpFunctionName)); + } + + $parts = explode('\\', $phpFunctionName); + if (!$expressionFunctionName && \count($parts) > 1) { + throw new \InvalidArgumentException(sprintf('An expression function name must be defined when PHP function "%s" is namespaced.', $phpFunctionName)); + } + + $compiler = function (...$args) use ($phpFunctionName) { + return sprintf('\%s(%s)', $phpFunctionName, implode(', ', $args)); + }; + + $evaluator = function ($p, ...$args) use ($phpFunctionName) { + return $phpFunctionName(...$args); + }; + + return new self($expressionFunctionName ?: end($parts), $compiler, $evaluator); + } +} diff --git a/vendor/symfony/expression-language/ExpressionFunctionProviderInterface.php b/vendor/symfony/expression-language/ExpressionFunctionProviderInterface.php new file mode 100644 index 0000000..479aeef --- /dev/null +++ b/vendor/symfony/expression-language/ExpressionFunctionProviderInterface.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage; + +/** + * @author Fabien Potencier + */ +interface ExpressionFunctionProviderInterface +{ + /** + * @return ExpressionFunction[] + */ + public function getFunctions(); +} diff --git a/vendor/symfony/expression-language/ExpressionLanguage.php b/vendor/symfony/expression-language/ExpressionLanguage.php new file mode 100644 index 0000000..001f49d --- /dev/null +++ b/vendor/symfony/expression-language/ExpressionLanguage.php @@ -0,0 +1,182 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage; + +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\Adapter\ArrayAdapter; + +// Help opcache.preload discover always-needed symbols +class_exists(ParsedExpression::class); + +/** + * Allows to compile and evaluate expressions written in your own DSL. + * + * @author Fabien Potencier + */ +class ExpressionLanguage +{ + private $cache; + private $lexer; + private $parser; + private $compiler; + + protected $functions = []; + + /** + * @param ExpressionFunctionProviderInterface[] $providers + */ + public function __construct(CacheItemPoolInterface $cache = null, array $providers = []) + { + $this->cache = $cache ?? new ArrayAdapter(); + $this->registerFunctions(); + foreach ($providers as $provider) { + $this->registerProvider($provider); + } + } + + /** + * Compiles an expression source code. + * + * @param Expression|string $expression The expression to compile + * + * @return string + */ + public function compile($expression, array $names = []) + { + return $this->getCompiler()->compile($this->parse($expression, $names)->getNodes())->getSource(); + } + + /** + * Evaluate an expression. + * + * @param Expression|string $expression The expression to compile + * + * @return mixed + */ + public function evaluate($expression, array $values = []) + { + return $this->parse($expression, array_keys($values))->getNodes()->evaluate($this->functions, $values); + } + + /** + * Parses an expression. + * + * @param Expression|string $expression The expression to parse + * + * @return ParsedExpression + */ + public function parse($expression, array $names) + { + if ($expression instanceof ParsedExpression) { + return $expression; + } + + asort($names); + $cacheKeyItems = []; + + foreach ($names as $nameKey => $name) { + $cacheKeyItems[] = \is_int($nameKey) ? $name : $nameKey.':'.$name; + } + + $cacheItem = $this->cache->getItem(rawurlencode($expression.'//'.implode('|', $cacheKeyItems))); + + if (null === $parsedExpression = $cacheItem->get()) { + $nodes = $this->getParser()->parse($this->getLexer()->tokenize((string) $expression), $names); + $parsedExpression = new ParsedExpression((string) $expression, $nodes); + + $cacheItem->set($parsedExpression); + $this->cache->save($cacheItem); + } + + return $parsedExpression; + } + + /** + * Validates the syntax of an expression. + * + * @param Expression|string $expression The expression to validate + * @param array|null $names The list of acceptable variable names in the expression, or null to accept any names + * + * @throws SyntaxError When the passed expression is invalid + */ + public function lint($expression, ?array $names): void + { + if ($expression instanceof ParsedExpression) { + return; + } + + $this->getParser()->lint($this->getLexer()->tokenize((string) $expression), $names); + } + + /** + * Registers a function. + * + * @param callable $compiler A callable able to compile the function + * @param callable $evaluator A callable able to evaluate the function + * + * @throws \LogicException when registering a function after calling evaluate(), compile() or parse() + * + * @see ExpressionFunction + */ + public function register(string $name, callable $compiler, callable $evaluator) + { + if (null !== $this->parser) { + throw new \LogicException('Registering functions after calling evaluate(), compile() or parse() is not supported.'); + } + + $this->functions[$name] = ['compiler' => $compiler, 'evaluator' => $evaluator]; + } + + public function addFunction(ExpressionFunction $function) + { + $this->register($function->getName(), $function->getCompiler(), $function->getEvaluator()); + } + + public function registerProvider(ExpressionFunctionProviderInterface $provider) + { + foreach ($provider->getFunctions() as $function) { + $this->addFunction($function); + } + } + + protected function registerFunctions() + { + $this->addFunction(ExpressionFunction::fromPhp('constant')); + } + + private function getLexer(): Lexer + { + if (null === $this->lexer) { + $this->lexer = new Lexer(); + } + + return $this->lexer; + } + + private function getParser(): Parser + { + if (null === $this->parser) { + $this->parser = new Parser($this->functions); + } + + return $this->parser; + } + + private function getCompiler(): Compiler + { + if (null === $this->compiler) { + $this->compiler = new Compiler($this->functions); + } + + return $this->compiler->reset(); + } +} diff --git a/vendor/symfony/expression-language/LICENSE b/vendor/symfony/expression-language/LICENSE new file mode 100644 index 0000000..9ff2d0d --- /dev/null +++ b/vendor/symfony/expression-language/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2021 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/expression-language/Lexer.php b/vendor/symfony/expression-language/Lexer.php new file mode 100644 index 0000000..3b63448 --- /dev/null +++ b/vendor/symfony/expression-language/Lexer.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage; + +/** + * Lexes an expression. + * + * @author Fabien Potencier + */ +class Lexer +{ + /** + * Tokenizes an expression. + * + * @return TokenStream + * + * @throws SyntaxError + */ + public function tokenize(string $expression) + { + $expression = str_replace(["\r", "\n", "\t", "\v", "\f"], ' ', $expression); + $cursor = 0; + $tokens = []; + $brackets = []; + $end = \strlen($expression); + + while ($cursor < $end) { + if (' ' == $expression[$cursor]) { + ++$cursor; + + continue; + } + + if (preg_match('/[0-9]+(?:\.[0-9]+)?([Ee][\+\-][0-9]+)?/A', $expression, $match, 0, $cursor)) { + // numbers + $number = (float) $match[0]; // floats + if (preg_match('/^[0-9]+$/', $match[0]) && $number <= \PHP_INT_MAX) { + $number = (int) $match[0]; // integers lower than the maximum + } + $tokens[] = new Token(Token::NUMBER_TYPE, $number, $cursor + 1); + $cursor += \strlen($match[0]); + } elseif (false !== strpos('([{', $expression[$cursor])) { + // opening bracket + $brackets[] = [$expression[$cursor], $cursor]; + + $tokens[] = new Token(Token::PUNCTUATION_TYPE, $expression[$cursor], $cursor + 1); + ++$cursor; + } elseif (false !== strpos(')]}', $expression[$cursor])) { + // closing bracket + if (empty($brackets)) { + throw new SyntaxError(sprintf('Unexpected "%s".', $expression[$cursor]), $cursor, $expression); + } + + [$expect, $cur] = array_pop($brackets); + if ($expression[$cursor] != strtr($expect, '([{', ')]}')) { + throw new SyntaxError(sprintf('Unclosed "%s".', $expect), $cur, $expression); + } + + $tokens[] = new Token(Token::PUNCTUATION_TYPE, $expression[$cursor], $cursor + 1); + ++$cursor; + } elseif (preg_match('/"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As', $expression, $match, 0, $cursor)) { + // strings + $tokens[] = new Token(Token::STRING_TYPE, stripcslashes(substr($match[0], 1, -1)), $cursor + 1); + $cursor += \strlen($match[0]); + } elseif (preg_match('/(?<=^|[\s(])not in(?=[\s(])|\!\=\=|(?<=^|[\s(])not(?=[\s(])|(?<=^|[\s(])and(?=[\s(])|\=\=\=|\>\=|(?<=^|[\s(])or(?=[\s(])|\<\=|\*\*|\.\.|(?<=^|[\s(])in(?=[\s(])|&&|\|\||(?<=^|[\s(])matches|\=\=|\!\=|\*|~|%|\/|\>|\||\!|\^|&|\+|\<|\-/A', $expression, $match, 0, $cursor)) { + // operators + $tokens[] = new Token(Token::OPERATOR_TYPE, $match[0], $cursor + 1); + $cursor += \strlen($match[0]); + } elseif (false !== strpos('.,?:', $expression[$cursor])) { + // punctuation + $tokens[] = new Token(Token::PUNCTUATION_TYPE, $expression[$cursor], $cursor + 1); + ++$cursor; + } elseif (preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A', $expression, $match, 0, $cursor)) { + // names + $tokens[] = new Token(Token::NAME_TYPE, $match[0], $cursor + 1); + $cursor += \strlen($match[0]); + } else { + // unlexable + throw new SyntaxError(sprintf('Unexpected character "%s".', $expression[$cursor]), $cursor, $expression); + } + } + + $tokens[] = new Token(Token::EOF_TYPE, null, $cursor + 1); + + if (!empty($brackets)) { + [$expect, $cur] = array_pop($brackets); + throw new SyntaxError(sprintf('Unclosed "%s".', $expect), $cur, $expression); + } + + return new TokenStream($tokens, $expression); + } +} diff --git a/vendor/symfony/expression-language/Node/ArgumentsNode.php b/vendor/symfony/expression-language/Node/ArgumentsNode.php new file mode 100644 index 0000000..e9849a4 --- /dev/null +++ b/vendor/symfony/expression-language/Node/ArgumentsNode.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Node; + +use Symfony\Component\ExpressionLanguage\Compiler; + +/** + * @author Fabien Potencier + * + * @internal + */ +class ArgumentsNode extends ArrayNode +{ + public function compile(Compiler $compiler) + { + $this->compileArguments($compiler, false); + } + + public function toArray() + { + $array = []; + + foreach ($this->getKeyValuePairs() as $pair) { + $array[] = $pair['value']; + $array[] = ', '; + } + array_pop($array); + + return $array; + } +} diff --git a/vendor/symfony/expression-language/Node/ArrayNode.php b/vendor/symfony/expression-language/Node/ArrayNode.php new file mode 100644 index 0000000..a8d68f9 --- /dev/null +++ b/vendor/symfony/expression-language/Node/ArrayNode.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Node; + +use Symfony\Component\ExpressionLanguage\Compiler; + +/** + * @author Fabien Potencier + * + * @internal + */ +class ArrayNode extends Node +{ + protected $index; + + public function __construct() + { + $this->index = -1; + } + + public function addElement(Node $value, Node $key = null) + { + if (null === $key) { + $key = new ConstantNode(++$this->index); + } + + array_push($this->nodes, $key, $value); + } + + /** + * Compiles the node to PHP. + */ + public function compile(Compiler $compiler) + { + $compiler->raw('['); + $this->compileArguments($compiler); + $compiler->raw(']'); + } + + public function evaluate(array $functions, array $values) + { + $result = []; + foreach ($this->getKeyValuePairs() as $pair) { + $result[$pair['key']->evaluate($functions, $values)] = $pair['value']->evaluate($functions, $values); + } + + return $result; + } + + public function toArray() + { + $value = []; + foreach ($this->getKeyValuePairs() as $pair) { + $value[$pair['key']->attributes['value']] = $pair['value']; + } + + $array = []; + + if ($this->isHash($value)) { + foreach ($value as $k => $v) { + $array[] = ', '; + $array[] = new ConstantNode($k); + $array[] = ': '; + $array[] = $v; + } + $array[0] = '{'; + $array[] = '}'; + } else { + foreach ($value as $v) { + $array[] = ', '; + $array[] = $v; + } + $array[0] = '['; + $array[] = ']'; + } + + return $array; + } + + protected function getKeyValuePairs() + { + $pairs = []; + foreach (array_chunk($this->nodes, 2) as $pair) { + $pairs[] = ['key' => $pair[0], 'value' => $pair[1]]; + } + + return $pairs; + } + + protected function compileArguments(Compiler $compiler, bool $withKeys = true) + { + $first = true; + foreach ($this->getKeyValuePairs() as $pair) { + if (!$first) { + $compiler->raw(', '); + } + $first = false; + + if ($withKeys) { + $compiler + ->compile($pair['key']) + ->raw(' => ') + ; + } + + $compiler->compile($pair['value']); + } + } +} diff --git a/vendor/symfony/expression-language/Node/BinaryNode.php b/vendor/symfony/expression-language/Node/BinaryNode.php new file mode 100644 index 0000000..469f80c --- /dev/null +++ b/vendor/symfony/expression-language/Node/BinaryNode.php @@ -0,0 +1,170 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Node; + +use Symfony\Component\ExpressionLanguage\Compiler; + +/** + * @author Fabien Potencier + * + * @internal + */ +class BinaryNode extends Node +{ + private const OPERATORS = [ + '~' => '.', + 'and' => '&&', + 'or' => '||', + ]; + + private const FUNCTIONS = [ + '**' => 'pow', + '..' => 'range', + 'in' => 'in_array', + 'not in' => '!in_array', + ]; + + public function __construct(string $operator, Node $left, Node $right) + { + parent::__construct( + ['left' => $left, 'right' => $right], + ['operator' => $operator] + ); + } + + public function compile(Compiler $compiler) + { + $operator = $this->attributes['operator']; + + if ('matches' == $operator) { + $compiler + ->raw('preg_match(') + ->compile($this->nodes['right']) + ->raw(', ') + ->compile($this->nodes['left']) + ->raw(')') + ; + + return; + } + + if (isset(self::FUNCTIONS[$operator])) { + $compiler + ->raw(sprintf('%s(', self::FUNCTIONS[$operator])) + ->compile($this->nodes['left']) + ->raw(', ') + ->compile($this->nodes['right']) + ->raw(')') + ; + + return; + } + + if (isset(self::OPERATORS[$operator])) { + $operator = self::OPERATORS[$operator]; + } + + $compiler + ->raw('(') + ->compile($this->nodes['left']) + ->raw(' ') + ->raw($operator) + ->raw(' ') + ->compile($this->nodes['right']) + ->raw(')') + ; + } + + public function evaluate(array $functions, array $values) + { + $operator = $this->attributes['operator']; + $left = $this->nodes['left']->evaluate($functions, $values); + + if (isset(self::FUNCTIONS[$operator])) { + $right = $this->nodes['right']->evaluate($functions, $values); + + if ('not in' === $operator) { + return !\in_array($left, $right); + } + $f = self::FUNCTIONS[$operator]; + + return $f($left, $right); + } + + switch ($operator) { + case 'or': + case '||': + return $left || $this->nodes['right']->evaluate($functions, $values); + case 'and': + case '&&': + return $left && $this->nodes['right']->evaluate($functions, $values); + } + + $right = $this->nodes['right']->evaluate($functions, $values); + + switch ($operator) { + case '|': + return $left | $right; + case '^': + return $left ^ $right; + case '&': + return $left & $right; + case '==': + return $left == $right; + case '===': + return $left === $right; + case '!=': + return $left != $right; + case '!==': + return $left !== $right; + case '<': + return $left < $right; + case '>': + return $left > $right; + case '>=': + return $left >= $right; + case '<=': + return $left <= $right; + case 'not in': + return !\in_array($left, $right); + case 'in': + return \in_array($left, $right); + case '+': + return $left + $right; + case '-': + return $left - $right; + case '~': + return $left.$right; + case '*': + return $left * $right; + case '/': + if (0 == $right) { + throw new \DivisionByZeroError('Division by zero.'); + } + + return $left / $right; + case '%': + if (0 == $right) { + throw new \DivisionByZeroError('Modulo by zero.'); + } + + return $left % $right; + case 'matches': + return preg_match($right, $left); + } + } + + public function toArray() + { + return ['(', $this->nodes['left'], ' '.$this->attributes['operator'].' ', $this->nodes['right'], ')']; + } +} diff --git a/vendor/symfony/expression-language/Node/ConditionalNode.php b/vendor/symfony/expression-language/Node/ConditionalNode.php new file mode 100644 index 0000000..ba78a28 --- /dev/null +++ b/vendor/symfony/expression-language/Node/ConditionalNode.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Node; + +use Symfony\Component\ExpressionLanguage\Compiler; + +/** + * @author Fabien Potencier + * + * @internal + */ +class ConditionalNode extends Node +{ + public function __construct(Node $expr1, Node $expr2, Node $expr3) + { + parent::__construct( + ['expr1' => $expr1, 'expr2' => $expr2, 'expr3' => $expr3] + ); + } + + public function compile(Compiler $compiler) + { + $compiler + ->raw('((') + ->compile($this->nodes['expr1']) + ->raw(') ? (') + ->compile($this->nodes['expr2']) + ->raw(') : (') + ->compile($this->nodes['expr3']) + ->raw('))') + ; + } + + public function evaluate(array $functions, array $values) + { + if ($this->nodes['expr1']->evaluate($functions, $values)) { + return $this->nodes['expr2']->evaluate($functions, $values); + } + + return $this->nodes['expr3']->evaluate($functions, $values); + } + + public function toArray() + { + return ['(', $this->nodes['expr1'], ' ? ', $this->nodes['expr2'], ' : ', $this->nodes['expr3'], ')']; + } +} diff --git a/vendor/symfony/expression-language/Node/ConstantNode.php b/vendor/symfony/expression-language/Node/ConstantNode.php new file mode 100644 index 0000000..b86abd4 --- /dev/null +++ b/vendor/symfony/expression-language/Node/ConstantNode.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Node; + +use Symfony\Component\ExpressionLanguage\Compiler; + +/** + * @author Fabien Potencier + * + * @internal + */ +class ConstantNode extends Node +{ + private $isIdentifier; + + public function __construct($value, bool $isIdentifier = false) + { + $this->isIdentifier = $isIdentifier; + parent::__construct( + [], + ['value' => $value] + ); + } + + public function compile(Compiler $compiler) + { + $compiler->repr($this->attributes['value']); + } + + public function evaluate(array $functions, array $values) + { + return $this->attributes['value']; + } + + public function toArray() + { + $array = []; + $value = $this->attributes['value']; + + if ($this->isIdentifier) { + $array[] = $value; + } elseif (true === $value) { + $array[] = 'true'; + } elseif (false === $value) { + $array[] = 'false'; + } elseif (null === $value) { + $array[] = 'null'; + } elseif (is_numeric($value)) { + $array[] = $value; + } elseif (!\is_array($value)) { + $array[] = $this->dumpString($value); + } elseif ($this->isHash($value)) { + foreach ($value as $k => $v) { + $array[] = ', '; + $array[] = new self($k); + $array[] = ': '; + $array[] = new self($v); + } + $array[0] = '{'; + $array[] = '}'; + } else { + foreach ($value as $v) { + $array[] = ', '; + $array[] = new self($v); + } + $array[0] = '['; + $array[] = ']'; + } + + return $array; + } +} diff --git a/vendor/symfony/expression-language/Node/FunctionNode.php b/vendor/symfony/expression-language/Node/FunctionNode.php new file mode 100644 index 0000000..37b5982 --- /dev/null +++ b/vendor/symfony/expression-language/Node/FunctionNode.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Node; + +use Symfony\Component\ExpressionLanguage\Compiler; + +/** + * @author Fabien Potencier + * + * @internal + */ +class FunctionNode extends Node +{ + public function __construct(string $name, Node $arguments) + { + parent::__construct( + ['arguments' => $arguments], + ['name' => $name] + ); + } + + public function compile(Compiler $compiler) + { + $arguments = []; + foreach ($this->nodes['arguments']->nodes as $node) { + $arguments[] = $compiler->subcompile($node); + } + + $function = $compiler->getFunction($this->attributes['name']); + + $compiler->raw($function['compiler'](...$arguments)); + } + + public function evaluate(array $functions, array $values) + { + $arguments = [$values]; + foreach ($this->nodes['arguments']->nodes as $node) { + $arguments[] = $node->evaluate($functions, $values); + } + + return $functions[$this->attributes['name']]['evaluator'](...$arguments); + } + + public function toArray() + { + $array = []; + $array[] = $this->attributes['name']; + + foreach ($this->nodes['arguments']->nodes as $node) { + $array[] = ', '; + $array[] = $node; + } + $array[1] = '('; + $array[] = ')'; + + return $array; + } +} diff --git a/vendor/symfony/expression-language/Node/GetAttrNode.php b/vendor/symfony/expression-language/Node/GetAttrNode.php new file mode 100644 index 0000000..04ac669 --- /dev/null +++ b/vendor/symfony/expression-language/Node/GetAttrNode.php @@ -0,0 +1,114 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Node; + +use Symfony\Component\ExpressionLanguage\Compiler; + +/** + * @author Fabien Potencier + * + * @internal + */ +class GetAttrNode extends Node +{ + public const PROPERTY_CALL = 1; + public const METHOD_CALL = 2; + public const ARRAY_CALL = 3; + + public function __construct(Node $node, Node $attribute, ArrayNode $arguments, int $type) + { + parent::__construct( + ['node' => $node, 'attribute' => $attribute, 'arguments' => $arguments], + ['type' => $type] + ); + } + + public function compile(Compiler $compiler) + { + switch ($this->attributes['type']) { + case self::PROPERTY_CALL: + $compiler + ->compile($this->nodes['node']) + ->raw('->') + ->raw($this->nodes['attribute']->attributes['value']) + ; + break; + + case self::METHOD_CALL: + $compiler + ->compile($this->nodes['node']) + ->raw('->') + ->raw($this->nodes['attribute']->attributes['value']) + ->raw('(') + ->compile($this->nodes['arguments']) + ->raw(')') + ; + break; + + case self::ARRAY_CALL: + $compiler + ->compile($this->nodes['node']) + ->raw('[') + ->compile($this->nodes['attribute'])->raw(']') + ; + break; + } + } + + public function evaluate(array $functions, array $values) + { + switch ($this->attributes['type']) { + case self::PROPERTY_CALL: + $obj = $this->nodes['node']->evaluate($functions, $values); + if (!\is_object($obj)) { + throw new \RuntimeException(sprintf('Unable to get property "%s" of non-object "%s".', $this->nodes['attribute']->dump(), $this->nodes['node']->dump())); + } + + $property = $this->nodes['attribute']->attributes['value']; + + return $obj->$property; + + case self::METHOD_CALL: + $obj = $this->nodes['node']->evaluate($functions, $values); + if (!\is_object($obj)) { + throw new \RuntimeException(sprintf('Unable to call method "%s" of non-object "%s".', $this->nodes['attribute']->dump(), $this->nodes['node']->dump())); + } + if (!\is_callable($toCall = [$obj, $this->nodes['attribute']->attributes['value']])) { + throw new \RuntimeException(sprintf('Unable to call method "%s" of object "%s".', $this->nodes['attribute']->attributes['value'], get_debug_type($obj))); + } + + return $toCall(...array_values($this->nodes['arguments']->evaluate($functions, $values))); + + case self::ARRAY_CALL: + $array = $this->nodes['node']->evaluate($functions, $values); + if (!\is_array($array) && !$array instanceof \ArrayAccess) { + throw new \RuntimeException(sprintf('Unable to get an item of non-array "%s".', $this->nodes['node']->dump())); + } + + return $array[$this->nodes['attribute']->evaluate($functions, $values)]; + } + } + + public function toArray() + { + switch ($this->attributes['type']) { + case self::PROPERTY_CALL: + return [$this->nodes['node'], '.', $this->nodes['attribute']]; + + case self::METHOD_CALL: + return [$this->nodes['node'], '.', $this->nodes['attribute'], '(', $this->nodes['arguments'], ')']; + + case self::ARRAY_CALL: + return [$this->nodes['node'], '[', $this->nodes['attribute'], ']']; + } + } +} diff --git a/vendor/symfony/expression-language/Node/NameNode.php b/vendor/symfony/expression-language/Node/NameNode.php new file mode 100644 index 0000000..e017e96 --- /dev/null +++ b/vendor/symfony/expression-language/Node/NameNode.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Node; + +use Symfony\Component\ExpressionLanguage\Compiler; + +/** + * @author Fabien Potencier + * + * @internal + */ +class NameNode extends Node +{ + public function __construct(string $name) + { + parent::__construct( + [], + ['name' => $name] + ); + } + + public function compile(Compiler $compiler) + { + $compiler->raw('$'.$this->attributes['name']); + } + + public function evaluate(array $functions, array $values) + { + return $values[$this->attributes['name']]; + } + + public function toArray() + { + return [$this->attributes['name']]; + } +} diff --git a/vendor/symfony/expression-language/Node/Node.php b/vendor/symfony/expression-language/Node/Node.php new file mode 100644 index 0000000..dc76de0 --- /dev/null +++ b/vendor/symfony/expression-language/Node/Node.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Node; + +use Symfony\Component\ExpressionLanguage\Compiler; + +/** + * Represents a node in the AST. + * + * @author Fabien Potencier + */ +class Node +{ + public $nodes = []; + public $attributes = []; + + /** + * @param array $nodes An array of nodes + * @param array $attributes An array of attributes + */ + public function __construct(array $nodes = [], array $attributes = []) + { + $this->nodes = $nodes; + $this->attributes = $attributes; + } + + /** + * @return string + */ + public function __toString() + { + $attributes = []; + foreach ($this->attributes as $name => $value) { + $attributes[] = sprintf('%s: %s', $name, str_replace("\n", '', var_export($value, true))); + } + + $repr = [str_replace('Symfony\Component\ExpressionLanguage\Node\\', '', static::class).'('.implode(', ', $attributes)]; + + if (\count($this->nodes)) { + foreach ($this->nodes as $node) { + foreach (explode("\n", (string) $node) as $line) { + $repr[] = ' '.$line; + } + } + + $repr[] = ')'; + } else { + $repr[0] .= ')'; + } + + return implode("\n", $repr); + } + + public function compile(Compiler $compiler) + { + foreach ($this->nodes as $node) { + $node->compile($compiler); + } + } + + public function evaluate(array $functions, array $values) + { + $results = []; + foreach ($this->nodes as $node) { + $results[] = $node->evaluate($functions, $values); + } + + return $results; + } + + public function toArray() + { + throw new \BadMethodCallException(sprintf('Dumping a "%s" instance is not supported yet.', static::class)); + } + + public function dump() + { + $dump = ''; + + foreach ($this->toArray() as $v) { + $dump .= is_scalar($v) ? $v : $v->dump(); + } + + return $dump; + } + + protected function dumpString(string $value) + { + return sprintf('"%s"', addcslashes($value, "\0\t\"\\")); + } + + protected function isHash(array $value) + { + $expectedKey = 0; + + foreach ($value as $key => $val) { + if ($key !== $expectedKey++) { + return true; + } + } + + return false; + } +} diff --git a/vendor/symfony/expression-language/Node/UnaryNode.php b/vendor/symfony/expression-language/Node/UnaryNode.php new file mode 100644 index 0000000..9bd9d9b --- /dev/null +++ b/vendor/symfony/expression-language/Node/UnaryNode.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage\Node; + +use Symfony\Component\ExpressionLanguage\Compiler; + +/** + * @author Fabien Potencier + * + * @internal + */ +class UnaryNode extends Node +{ + private const OPERATORS = [ + '!' => '!', + 'not' => '!', + '+' => '+', + '-' => '-', + ]; + + public function __construct(string $operator, Node $node) + { + parent::__construct( + ['node' => $node], + ['operator' => $operator] + ); + } + + public function compile(Compiler $compiler) + { + $compiler + ->raw('(') + ->raw(self::OPERATORS[$this->attributes['operator']]) + ->compile($this->nodes['node']) + ->raw(')') + ; + } + + public function evaluate(array $functions, array $values) + { + $value = $this->nodes['node']->evaluate($functions, $values); + switch ($this->attributes['operator']) { + case 'not': + case '!': + return !$value; + case '-': + return -$value; + } + + return $value; + } + + public function toArray(): array + { + return ['(', $this->attributes['operator'].' ', $this->nodes['node'], ')']; + } +} diff --git a/vendor/symfony/expression-language/ParsedExpression.php b/vendor/symfony/expression-language/ParsedExpression.php new file mode 100644 index 0000000..1416db1 --- /dev/null +++ b/vendor/symfony/expression-language/ParsedExpression.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage; + +use Symfony\Component\ExpressionLanguage\Node\Node; + +/** + * Represents an already parsed expression. + * + * @author Fabien Potencier + */ +class ParsedExpression extends Expression +{ + private $nodes; + + public function __construct(string $expression, Node $nodes) + { + parent::__construct($expression); + + $this->nodes = $nodes; + } + + public function getNodes() + { + return $this->nodes; + } +} diff --git a/vendor/symfony/expression-language/Parser.php b/vendor/symfony/expression-language/Parser.php new file mode 100644 index 0000000..1fabea0 --- /dev/null +++ b/vendor/symfony/expression-language/Parser.php @@ -0,0 +1,409 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage; + +/** + * Parsers a token stream. + * + * This parser implements a "Precedence climbing" algorithm. + * + * @see http://www.engr.mun.ca/~theo/Misc/exp_parsing.htm + * @see http://en.wikipedia.org/wiki/Operator-precedence_parser + * + * @author Fabien Potencier + */ +class Parser +{ + public const OPERATOR_LEFT = 1; + public const OPERATOR_RIGHT = 2; + + private $stream; + private $unaryOperators; + private $binaryOperators; + private $functions; + private $names; + private $lint; + + public function __construct(array $functions) + { + $this->functions = $functions; + + $this->unaryOperators = [ + 'not' => ['precedence' => 50], + '!' => ['precedence' => 50], + '-' => ['precedence' => 500], + '+' => ['precedence' => 500], + ]; + $this->binaryOperators = [ + 'or' => ['precedence' => 10, 'associativity' => self::OPERATOR_LEFT], + '||' => ['precedence' => 10, 'associativity' => self::OPERATOR_LEFT], + 'and' => ['precedence' => 15, 'associativity' => self::OPERATOR_LEFT], + '&&' => ['precedence' => 15, 'associativity' => self::OPERATOR_LEFT], + '|' => ['precedence' => 16, 'associativity' => self::OPERATOR_LEFT], + '^' => ['precedence' => 17, 'associativity' => self::OPERATOR_LEFT], + '&' => ['precedence' => 18, 'associativity' => self::OPERATOR_LEFT], + '==' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT], + '===' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT], + '!=' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT], + '!==' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT], + '<' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT], + '>' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT], + '>=' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT], + '<=' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT], + 'not in' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT], + 'in' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT], + 'matches' => ['precedence' => 20, 'associativity' => self::OPERATOR_LEFT], + '..' => ['precedence' => 25, 'associativity' => self::OPERATOR_LEFT], + '+' => ['precedence' => 30, 'associativity' => self::OPERATOR_LEFT], + '-' => ['precedence' => 30, 'associativity' => self::OPERATOR_LEFT], + '~' => ['precedence' => 40, 'associativity' => self::OPERATOR_LEFT], + '*' => ['precedence' => 60, 'associativity' => self::OPERATOR_LEFT], + '/' => ['precedence' => 60, 'associativity' => self::OPERATOR_LEFT], + '%' => ['precedence' => 60, 'associativity' => self::OPERATOR_LEFT], + '**' => ['precedence' => 200, 'associativity' => self::OPERATOR_RIGHT], + ]; + } + + /** + * Converts a token stream to a node tree. + * + * The valid names is an array where the values + * are the names that the user can use in an expression. + * + * If the variable name in the compiled PHP code must be + * different, define it as the key. + * + * For instance, ['this' => 'container'] means that the + * variable 'container' can be used in the expression + * but the compiled code will use 'this'. + * + * @return Node\Node + * + * @throws SyntaxError + */ + public function parse(TokenStream $stream, array $names = []) + { + $this->lint = false; + + return $this->doParse($stream, $names); + } + + /** + * Validates the syntax of an expression. + * + * The syntax of the passed expression will be checked, but not parsed. + * If you want to skip checking dynamic variable names, pass `null` instead of the array. + * + * @throws SyntaxError When the passed expression is invalid + */ + public function lint(TokenStream $stream, ?array $names = []): void + { + $this->lint = true; + $this->doParse($stream, $names); + } + + /** + * @throws SyntaxError + */ + private function doParse(TokenStream $stream, ?array $names = []): Node\Node + { + $this->stream = $stream; + $this->names = $names; + + $node = $this->parseExpression(); + if (!$stream->isEOF()) { + throw new SyntaxError(sprintf('Unexpected token "%s" of value "%s".', $stream->current->type, $stream->current->value), $stream->current->cursor, $stream->getExpression()); + } + + $this->stream = null; + $this->names = null; + + return $node; + } + + public function parseExpression(int $precedence = 0) + { + $expr = $this->getPrimary(); + $token = $this->stream->current; + while ($token->test(Token::OPERATOR_TYPE) && isset($this->binaryOperators[$token->value]) && $this->binaryOperators[$token->value]['precedence'] >= $precedence) { + $op = $this->binaryOperators[$token->value]; + $this->stream->next(); + + $expr1 = $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + 1 : $op['precedence']); + $expr = new Node\BinaryNode($token->value, $expr, $expr1); + + $token = $this->stream->current; + } + + if (0 === $precedence) { + return $this->parseConditionalExpression($expr); + } + + return $expr; + } + + protected function getPrimary() + { + $token = $this->stream->current; + + if ($token->test(Token::OPERATOR_TYPE) && isset($this->unaryOperators[$token->value])) { + $operator = $this->unaryOperators[$token->value]; + $this->stream->next(); + $expr = $this->parseExpression($operator['precedence']); + + return $this->parsePostfixExpression(new Node\UnaryNode($token->value, $expr)); + } + + if ($token->test(Token::PUNCTUATION_TYPE, '(')) { + $this->stream->next(); + $expr = $this->parseExpression(); + $this->stream->expect(Token::PUNCTUATION_TYPE, ')', 'An opened parenthesis is not properly closed'); + + return $this->parsePostfixExpression($expr); + } + + return $this->parsePrimaryExpression(); + } + + protected function parseConditionalExpression(Node\Node $expr) + { + while ($this->stream->current->test(Token::PUNCTUATION_TYPE, '?')) { + $this->stream->next(); + if (!$this->stream->current->test(Token::PUNCTUATION_TYPE, ':')) { + $expr2 = $this->parseExpression(); + if ($this->stream->current->test(Token::PUNCTUATION_TYPE, ':')) { + $this->stream->next(); + $expr3 = $this->parseExpression(); + } else { + $expr3 = new Node\ConstantNode(null); + } + } else { + $this->stream->next(); + $expr2 = $expr; + $expr3 = $this->parseExpression(); + } + + $expr = new Node\ConditionalNode($expr, $expr2, $expr3); + } + + return $expr; + } + + public function parsePrimaryExpression() + { + $token = $this->stream->current; + switch ($token->type) { + case Token::NAME_TYPE: + $this->stream->next(); + switch ($token->value) { + case 'true': + case 'TRUE': + return new Node\ConstantNode(true); + + case 'false': + case 'FALSE': + return new Node\ConstantNode(false); + + case 'null': + case 'NULL': + return new Node\ConstantNode(null); + + default: + if ('(' === $this->stream->current->value) { + if (false === isset($this->functions[$token->value])) { + throw new SyntaxError(sprintf('The function "%s" does not exist.', $token->value), $token->cursor, $this->stream->getExpression(), $token->value, array_keys($this->functions)); + } + + $node = new Node\FunctionNode($token->value, $this->parseArguments()); + } else { + if (!$this->lint || \is_array($this->names)) { + if (!\in_array($token->value, $this->names, true)) { + throw new SyntaxError(sprintf('Variable "%s" is not valid.', $token->value), $token->cursor, $this->stream->getExpression(), $token->value, $this->names); + } + + // is the name used in the compiled code different + // from the name used in the expression? + if (\is_int($name = array_search($token->value, $this->names))) { + $name = $token->value; + } + } else { + $name = $token->value; + } + + $node = new Node\NameNode($name); + } + } + break; + + case Token::NUMBER_TYPE: + case Token::STRING_TYPE: + $this->stream->next(); + + return new Node\ConstantNode($token->value); + + default: + if ($token->test(Token::PUNCTUATION_TYPE, '[')) { + $node = $this->parseArrayExpression(); + } elseif ($token->test(Token::PUNCTUATION_TYPE, '{')) { + $node = $this->parseHashExpression(); + } else { + throw new SyntaxError(sprintf('Unexpected token "%s" of value "%s".', $token->type, $token->value), $token->cursor, $this->stream->getExpression()); + } + } + + return $this->parsePostfixExpression($node); + } + + public function parseArrayExpression() + { + $this->stream->expect(Token::PUNCTUATION_TYPE, '[', 'An array element was expected'); + + $node = new Node\ArrayNode(); + $first = true; + while (!$this->stream->current->test(Token::PUNCTUATION_TYPE, ']')) { + if (!$first) { + $this->stream->expect(Token::PUNCTUATION_TYPE, ',', 'An array element must be followed by a comma'); + + // trailing ,? + if ($this->stream->current->test(Token::PUNCTUATION_TYPE, ']')) { + break; + } + } + $first = false; + + $node->addElement($this->parseExpression()); + } + $this->stream->expect(Token::PUNCTUATION_TYPE, ']', 'An opened array is not properly closed'); + + return $node; + } + + public function parseHashExpression() + { + $this->stream->expect(Token::PUNCTUATION_TYPE, '{', 'A hash element was expected'); + + $node = new Node\ArrayNode(); + $first = true; + while (!$this->stream->current->test(Token::PUNCTUATION_TYPE, '}')) { + if (!$first) { + $this->stream->expect(Token::PUNCTUATION_TYPE, ',', 'A hash value must be followed by a comma'); + + // trailing ,? + if ($this->stream->current->test(Token::PUNCTUATION_TYPE, '}')) { + break; + } + } + $first = false; + + // a hash key can be: + // + // * a number -- 12 + // * a string -- 'a' + // * a name, which is equivalent to a string -- a + // * an expression, which must be enclosed in parentheses -- (1 + 2) + if ($this->stream->current->test(Token::STRING_TYPE) || $this->stream->current->test(Token::NAME_TYPE) || $this->stream->current->test(Token::NUMBER_TYPE)) { + $key = new Node\ConstantNode($this->stream->current->value); + $this->stream->next(); + } elseif ($this->stream->current->test(Token::PUNCTUATION_TYPE, '(')) { + $key = $this->parseExpression(); + } else { + $current = $this->stream->current; + + throw new SyntaxError(sprintf('A hash key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "%s" of value "%s".', $current->type, $current->value), $current->cursor, $this->stream->getExpression()); + } + + $this->stream->expect(Token::PUNCTUATION_TYPE, ':', 'A hash key must be followed by a colon (:)'); + $value = $this->parseExpression(); + + $node->addElement($value, $key); + } + $this->stream->expect(Token::PUNCTUATION_TYPE, '}', 'An opened hash is not properly closed'); + + return $node; + } + + public function parsePostfixExpression(Node\Node $node) + { + $token = $this->stream->current; + while (Token::PUNCTUATION_TYPE == $token->type) { + if ('.' === $token->value) { + $this->stream->next(); + $token = $this->stream->current; + $this->stream->next(); + + if ( + Token::NAME_TYPE !== $token->type + && + // Operators like "not" and "matches" are valid method or property names, + // + // In other words, besides NAME_TYPE, OPERATOR_TYPE could also be parsed as a property or method. + // This is because operators are processed by the lexer prior to names. So "not" in "foo.not()" or "matches" in "foo.matches" will be recognized as an operator first. + // But in fact, "not" and "matches" in such expressions shall be parsed as method or property names. + // + // And this ONLY works if the operator consists of valid characters for a property or method name. + // + // Other types, such as STRING_TYPE and NUMBER_TYPE, can't be parsed as property nor method names. + // + // As a result, if $token is NOT an operator OR $token->value is NOT a valid property or method name, an exception shall be thrown. + (Token::OPERATOR_TYPE !== $token->type || !preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A', $token->value)) + ) { + throw new SyntaxError('Expected name.', $token->cursor, $this->stream->getExpression()); + } + + $arg = new Node\ConstantNode($token->value, true); + + $arguments = new Node\ArgumentsNode(); + if ($this->stream->current->test(Token::PUNCTUATION_TYPE, '(')) { + $type = Node\GetAttrNode::METHOD_CALL; + foreach ($this->parseArguments()->nodes as $n) { + $arguments->addElement($n); + } + } else { + $type = Node\GetAttrNode::PROPERTY_CALL; + } + + $node = new Node\GetAttrNode($node, $arg, $arguments, $type); + } elseif ('[' === $token->value) { + $this->stream->next(); + $arg = $this->parseExpression(); + $this->stream->expect(Token::PUNCTUATION_TYPE, ']'); + + $node = new Node\GetAttrNode($node, $arg, new Node\ArgumentsNode(), Node\GetAttrNode::ARRAY_CALL); + } else { + break; + } + + $token = $this->stream->current; + } + + return $node; + } + + /** + * Parses arguments. + */ + public function parseArguments() + { + $args = []; + $this->stream->expect(Token::PUNCTUATION_TYPE, '(', 'A list of arguments must begin with an opening parenthesis'); + while (!$this->stream->current->test(Token::PUNCTUATION_TYPE, ')')) { + if (!empty($args)) { + $this->stream->expect(Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma'); + } + + $args[] = $this->parseExpression(); + } + $this->stream->expect(Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis'); + + return new Node\Node($args); + } +} diff --git a/vendor/symfony/expression-language/README.md b/vendor/symfony/expression-language/README.md new file mode 100644 index 0000000..2b2e3de --- /dev/null +++ b/vendor/symfony/expression-language/README.md @@ -0,0 +1,15 @@ +ExpressionLanguage Component +============================ + +The ExpressionLanguage component provides an engine that can compile and +evaluate expressions. An expression is a one-liner that returns a value +(mostly, but not limited to, Booleans). + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/expression_language/introduction.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/expression-language/Resources/bin/generate_operator_regex.php b/vendor/symfony/expression-language/Resources/bin/generate_operator_regex.php new file mode 100644 index 0000000..c86e962 --- /dev/null +++ b/vendor/symfony/expression-language/Resources/bin/generate_operator_regex.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +$operators = ['not', '!', 'or', '||', '&&', 'and', '|', '^', '&', '==', '===', '!=', '!==', '<', '>', '>=', '<=', 'not in', 'in', '..', '+', '-', '~', '*', '/', '%', 'matches', '**']; +$operators = array_combine($operators, array_map('strlen', $operators)); +arsort($operators); + +$regex = []; +foreach ($operators as $operator => $length) { + // Collisions of character operators: + // - an operator that begins with a character must have a space or a parenthesis before or starting at the beginning of a string + // - an operator that ends with a character must be followed by a whitespace or a parenthesis + $regex[] = + (ctype_alpha($operator[0]) ? '(?<=^|[\s(])' : '') + .preg_quote($operator, '/') + .(ctype_alpha($operator[$length - 1]) ? '(?=[\s(])' : ''); +} + +echo '/'.implode('|', $regex).'/A'; diff --git a/vendor/symfony/expression-language/SerializedParsedExpression.php b/vendor/symfony/expression-language/SerializedParsedExpression.php new file mode 100644 index 0000000..a1f838c --- /dev/null +++ b/vendor/symfony/expression-language/SerializedParsedExpression.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage; + +/** + * Represents an already parsed expression. + * + * @author Fabien Potencier + */ +class SerializedParsedExpression extends ParsedExpression +{ + private $nodes; + + /** + * @param string $expression An expression + * @param string $nodes The serialized nodes for the expression + */ + public function __construct(string $expression, string $nodes) + { + $this->expression = $expression; + $this->nodes = $nodes; + } + + public function getNodes() + { + return unserialize($this->nodes); + } +} diff --git a/vendor/symfony/expression-language/SyntaxError.php b/vendor/symfony/expression-language/SyntaxError.php new file mode 100644 index 0000000..0bfd7e9 --- /dev/null +++ b/vendor/symfony/expression-language/SyntaxError.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage; + +class SyntaxError extends \LogicException +{ + public function __construct(string $message, int $cursor = 0, string $expression = '', string $subject = null, array $proposals = null) + { + $message = sprintf('%s around position %d', rtrim($message, '.'), $cursor); + if ($expression) { + $message = sprintf('%s for expression `%s`', $message, $expression); + } + $message .= '.'; + + if (null !== $subject && null !== $proposals) { + $minScore = \INF; + foreach ($proposals as $proposal) { + $distance = levenshtein($subject, $proposal); + if ($distance < $minScore) { + $guess = $proposal; + $minScore = $distance; + } + } + + if (isset($guess) && $minScore < 3) { + $message .= sprintf(' Did you mean "%s"?', $guess); + } + } + + parent::__construct($message); + } +} diff --git a/vendor/symfony/expression-language/Token.php b/vendor/symfony/expression-language/Token.php new file mode 100644 index 0000000..8399f70 --- /dev/null +++ b/vendor/symfony/expression-language/Token.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage; + +/** + * Represents a Token. + * + * @author Fabien Potencier + */ +class Token +{ + public $value; + public $type; + public $cursor; + + public const EOF_TYPE = 'end of expression'; + public const NAME_TYPE = 'name'; + public const NUMBER_TYPE = 'number'; + public const STRING_TYPE = 'string'; + public const OPERATOR_TYPE = 'operator'; + public const PUNCTUATION_TYPE = 'punctuation'; + + /** + * @param string $type The type of the token (self::*_TYPE) + * @param string|int|float|null $value The token value + * @param int $cursor The cursor position in the source + */ + public function __construct(string $type, $value, ?int $cursor) + { + $this->type = $type; + $this->value = $value; + $this->cursor = $cursor; + } + + /** + * Returns a string representation of the token. + * + * @return string + */ + public function __toString() + { + return sprintf('%3d %-11s %s', $this->cursor, strtoupper($this->type), $this->value); + } + + /** + * Tests the current token for a type and/or a value. + * + * @return bool + */ + public function test(string $type, string $value = null) + { + return $this->type === $type && (null === $value || $this->value == $value); + } +} diff --git a/vendor/symfony/expression-language/TokenStream.php b/vendor/symfony/expression-language/TokenStream.php new file mode 100644 index 0000000..130513b --- /dev/null +++ b/vendor/symfony/expression-language/TokenStream.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ExpressionLanguage; + +/** + * Represents a token stream. + * + * @author Fabien Potencier + */ +class TokenStream +{ + public $current; + + private $tokens; + private $position = 0; + private $expression; + + public function __construct(array $tokens, string $expression = '') + { + $this->tokens = $tokens; + $this->current = $tokens[0]; + $this->expression = $expression; + } + + /** + * Returns a string representation of the token stream. + * + * @return string + */ + public function __toString() + { + return implode("\n", $this->tokens); + } + + /** + * Sets the pointer to the next token and returns the old one. + */ + public function next() + { + ++$this->position; + + if (!isset($this->tokens[$this->position])) { + throw new SyntaxError('Unexpected end of expression.', $this->current->cursor, $this->expression); + } + + $this->current = $this->tokens[$this->position]; + } + + /** + * @param string|null $message The syntax error message + */ + public function expect(string $type, string $value = null, string $message = null) + { + $token = $this->current; + if (!$token->test($type, $value)) { + throw new SyntaxError(sprintf('%sUnexpected token "%s" of value "%s" ("%s" expected%s).', $message ? $message.'. ' : '', $token->type, $token->value, $type, $value ? sprintf(' with value "%s"', $value) : ''), $token->cursor, $this->expression); + } + $this->next(); + } + + /** + * Checks if end of stream was reached. + * + * @return bool + */ + public function isEOF() + { + return Token::EOF_TYPE === $this->current->type; + } + + /** + * @internal + */ + public function getExpression(): string + { + return $this->expression; + } +} diff --git a/vendor/symfony/expression-language/composer.json b/vendor/symfony/expression-language/composer.json new file mode 100644 index 0000000..9319974 --- /dev/null +++ b/vendor/symfony/expression-language/composer.json @@ -0,0 +1,30 @@ +{ + "name": "symfony/expression-language", + "type": "library", + "description": "Provides an engine that can compile and evaluate expressions", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.2.5", + "symfony/cache": "^4.4|^5.0|^6.0", + "symfony/service-contracts": "^1.1|^2|^3" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\ExpressionLanguage\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/http-foundation/AcceptHeader.php b/vendor/symfony/http-foundation/AcceptHeader.php new file mode 100644 index 0000000..057c6b5 --- /dev/null +++ b/vendor/symfony/http-foundation/AcceptHeader.php @@ -0,0 +1,168 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +// Help opcache.preload discover always-needed symbols +class_exists(AcceptHeaderItem::class); + +/** + * Represents an Accept-* header. + * + * An accept header is compound with a list of items, + * sorted by descending quality. + * + * @author Jean-François Simon + */ +class AcceptHeader +{ + /** + * @var AcceptHeaderItem[] + */ + private $items = []; + + /** + * @var bool + */ + private $sorted = true; + + /** + * @param AcceptHeaderItem[] $items + */ + public function __construct(array $items) + { + foreach ($items as $item) { + $this->add($item); + } + } + + /** + * Builds an AcceptHeader instance from a string. + * + * @return self + */ + public static function fromString(?string $headerValue) + { + $index = 0; + + $parts = HeaderUtils::split($headerValue ?? '', ',;='); + + return new self(array_map(function ($subParts) use (&$index) { + $part = array_shift($subParts); + $attributes = HeaderUtils::combine($subParts); + + $item = new AcceptHeaderItem($part[0], $attributes); + $item->setIndex($index++); + + return $item; + }, $parts)); + } + + /** + * Returns header value's string representation. + * + * @return string + */ + public function __toString() + { + return implode(',', $this->items); + } + + /** + * Tests if header has given value. + * + * @return bool + */ + public function has(string $value) + { + return isset($this->items[$value]); + } + + /** + * Returns given value's item, if exists. + * + * @return AcceptHeaderItem|null + */ + public function get(string $value) + { + return $this->items[$value] ?? $this->items[explode('/', $value)[0].'/*'] ?? $this->items['*/*'] ?? $this->items['*'] ?? null; + } + + /** + * Adds an item. + * + * @return $this + */ + public function add(AcceptHeaderItem $item) + { + $this->items[$item->getValue()] = $item; + $this->sorted = false; + + return $this; + } + + /** + * Returns all items. + * + * @return AcceptHeaderItem[] + */ + public function all() + { + $this->sort(); + + return $this->items; + } + + /** + * Filters items on their value using given regex. + * + * @return self + */ + public function filter(string $pattern) + { + return new self(array_filter($this->items, function (AcceptHeaderItem $item) use ($pattern) { + return preg_match($pattern, $item->getValue()); + })); + } + + /** + * Returns first item. + * + * @return AcceptHeaderItem|null + */ + public function first() + { + $this->sort(); + + return !empty($this->items) ? reset($this->items) : null; + } + + /** + * Sorts items by descending quality. + */ + private function sort(): void + { + if (!$this->sorted) { + uasort($this->items, function (AcceptHeaderItem $a, AcceptHeaderItem $b) { + $qA = $a->getQuality(); + $qB = $b->getQuality(); + + if ($qA === $qB) { + return $a->getIndex() > $b->getIndex() ? 1 : -1; + } + + return $qA > $qB ? -1 : 1; + }); + + $this->sorted = true; + } + } +} diff --git a/vendor/symfony/http-foundation/AcceptHeaderItem.php b/vendor/symfony/http-foundation/AcceptHeaderItem.php new file mode 100644 index 0000000..8b86eee --- /dev/null +++ b/vendor/symfony/http-foundation/AcceptHeaderItem.php @@ -0,0 +1,177 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Represents an Accept-* header item. + * + * @author Jean-François Simon + */ +class AcceptHeaderItem +{ + private $value; + private $quality = 1.0; + private $index = 0; + private $attributes = []; + + public function __construct(string $value, array $attributes = []) + { + $this->value = $value; + foreach ($attributes as $name => $value) { + $this->setAttribute($name, $value); + } + } + + /** + * Builds an AcceptHeaderInstance instance from a string. + * + * @return self + */ + public static function fromString(?string $itemValue) + { + $parts = HeaderUtils::split($itemValue ?? '', ';='); + + $part = array_shift($parts); + $attributes = HeaderUtils::combine($parts); + + return new self($part[0], $attributes); + } + + /** + * Returns header value's string representation. + * + * @return string + */ + public function __toString() + { + $string = $this->value.($this->quality < 1 ? ';q='.$this->quality : ''); + if (\count($this->attributes) > 0) { + $string .= '; '.HeaderUtils::toString($this->attributes, ';'); + } + + return $string; + } + + /** + * Set the item value. + * + * @return $this + */ + public function setValue(string $value) + { + $this->value = $value; + + return $this; + } + + /** + * Returns the item value. + * + * @return string + */ + public function getValue() + { + return $this->value; + } + + /** + * Set the item quality. + * + * @return $this + */ + public function setQuality(float $quality) + { + $this->quality = $quality; + + return $this; + } + + /** + * Returns the item quality. + * + * @return float + */ + public function getQuality() + { + return $this->quality; + } + + /** + * Set the item index. + * + * @return $this + */ + public function setIndex(int $index) + { + $this->index = $index; + + return $this; + } + + /** + * Returns the item index. + * + * @return int + */ + public function getIndex() + { + return $this->index; + } + + /** + * Tests if an attribute exists. + * + * @return bool + */ + public function hasAttribute(string $name) + { + return isset($this->attributes[$name]); + } + + /** + * Returns an attribute by its name. + * + * @param mixed $default + * + * @return mixed + */ + public function getAttribute(string $name, $default = null) + { + return $this->attributes[$name] ?? $default; + } + + /** + * Returns all attributes. + * + * @return array + */ + public function getAttributes() + { + return $this->attributes; + } + + /** + * Set an attribute. + * + * @return $this + */ + public function setAttribute(string $name, string $value) + { + if ('q' === $name) { + $this->quality = (float) $value; + } else { + $this->attributes[$name] = $value; + } + + return $this; + } +} diff --git a/vendor/symfony/http-foundation/BinaryFileResponse.php b/vendor/symfony/http-foundation/BinaryFileResponse.php new file mode 100644 index 0000000..4769cab --- /dev/null +++ b/vendor/symfony/http-foundation/BinaryFileResponse.php @@ -0,0 +1,363 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\HttpFoundation\File\Exception\FileException; +use Symfony\Component\HttpFoundation\File\File; + +/** + * BinaryFileResponse represents an HTTP response delivering a file. + * + * @author Niklas Fiekas + * @author stealth35 + * @author Igor Wiedler + * @author Jordan Alliot + * @author Sergey Linnik + */ +class BinaryFileResponse extends Response +{ + protected static $trustXSendfileTypeHeader = false; + + /** + * @var File + */ + protected $file; + protected $offset = 0; + protected $maxlen = -1; + protected $deleteFileAfterSend = false; + + /** + * @param \SplFileInfo|string $file The file to stream + * @param int $status The response status code + * @param array $headers An array of response headers + * @param bool $public Files are public by default + * @param string|null $contentDisposition The type of Content-Disposition to set automatically with the filename + * @param bool $autoEtag Whether the ETag header should be automatically set + * @param bool $autoLastModified Whether the Last-Modified header should be automatically set + */ + public function __construct($file, int $status = 200, array $headers = [], bool $public = true, string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true) + { + parent::__construct(null, $status, $headers); + + $this->setFile($file, $contentDisposition, $autoEtag, $autoLastModified); + + if ($public) { + $this->setPublic(); + } + } + + /** + * @param \SplFileInfo|string $file The file to stream + * @param int $status The response status code + * @param array $headers An array of response headers + * @param bool $public Files are public by default + * @param string|null $contentDisposition The type of Content-Disposition to set automatically with the filename + * @param bool $autoEtag Whether the ETag header should be automatically set + * @param bool $autoLastModified Whether the Last-Modified header should be automatically set + * + * @return static + * + * @deprecated since Symfony 5.2, use __construct() instead. + */ + public static function create($file = null, int $status = 200, array $headers = [], bool $public = true, string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true) + { + trigger_deprecation('symfony/http-foundation', '5.2', 'The "%s()" method is deprecated, use "new %s()" instead.', __METHOD__, static::class); + + return new static($file, $status, $headers, $public, $contentDisposition, $autoEtag, $autoLastModified); + } + + /** + * Sets the file to stream. + * + * @param \SplFileInfo|string $file The file to stream + * + * @return $this + * + * @throws FileException + */ + public function setFile($file, string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true) + { + if (!$file instanceof File) { + if ($file instanceof \SplFileInfo) { + $file = new File($file->getPathname()); + } else { + $file = new File((string) $file); + } + } + + if (!$file->isReadable()) { + throw new FileException('File must be readable.'); + } + + $this->file = $file; + + if ($autoEtag) { + $this->setAutoEtag(); + } + + if ($autoLastModified) { + $this->setAutoLastModified(); + } + + if ($contentDisposition) { + $this->setContentDisposition($contentDisposition); + } + + return $this; + } + + /** + * Gets the file. + * + * @return File + */ + public function getFile() + { + return $this->file; + } + + /** + * Automatically sets the Last-Modified header according the file modification date. + * + * @return $this + */ + public function setAutoLastModified() + { + $this->setLastModified(\DateTime::createFromFormat('U', $this->file->getMTime())); + + return $this; + } + + /** + * Automatically sets the ETag header according to the checksum of the file. + * + * @return $this + */ + public function setAutoEtag() + { + $this->setEtag(base64_encode(hash_file('sha256', $this->file->getPathname(), true))); + + return $this; + } + + /** + * Sets the Content-Disposition header with the given filename. + * + * @param string $disposition ResponseHeaderBag::DISPOSITION_INLINE or ResponseHeaderBag::DISPOSITION_ATTACHMENT + * @param string $filename Optionally use this UTF-8 encoded filename instead of the real name of the file + * @param string $filenameFallback A fallback filename, containing only ASCII characters. Defaults to an automatically encoded filename + * + * @return $this + */ + public function setContentDisposition(string $disposition, string $filename = '', string $filenameFallback = '') + { + if ('' === $filename) { + $filename = $this->file->getFilename(); + } + + if ('' === $filenameFallback && (!preg_match('/^[\x20-\x7e]*$/', $filename) || str_contains($filename, '%'))) { + $encoding = mb_detect_encoding($filename, null, true) ?: '8bit'; + + for ($i = 0, $filenameLength = mb_strlen($filename, $encoding); $i < $filenameLength; ++$i) { + $char = mb_substr($filename, $i, 1, $encoding); + + if ('%' === $char || \ord($char) < 32 || \ord($char) > 126) { + $filenameFallback .= '_'; + } else { + $filenameFallback .= $char; + } + } + } + + $dispositionHeader = $this->headers->makeDisposition($disposition, $filename, $filenameFallback); + $this->headers->set('Content-Disposition', $dispositionHeader); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function prepare(Request $request) + { + if (!$this->headers->has('Content-Type')) { + $this->headers->set('Content-Type', $this->file->getMimeType() ?: 'application/octet-stream'); + } + + if ('HTTP/1.0' !== $request->server->get('SERVER_PROTOCOL')) { + $this->setProtocolVersion('1.1'); + } + + $this->ensureIEOverSSLCompatibility($request); + + $this->offset = 0; + $this->maxlen = -1; + + if (false === $fileSize = $this->file->getSize()) { + return $this; + } + $this->headers->set('Content-Length', $fileSize); + + if (!$this->headers->has('Accept-Ranges')) { + // Only accept ranges on safe HTTP methods + $this->headers->set('Accept-Ranges', $request->isMethodSafe() ? 'bytes' : 'none'); + } + + if (self::$trustXSendfileTypeHeader && $request->headers->has('X-Sendfile-Type')) { + // Use X-Sendfile, do not send any content. + $type = $request->headers->get('X-Sendfile-Type'); + $path = $this->file->getRealPath(); + // Fall back to scheme://path for stream wrapped locations. + if (false === $path) { + $path = $this->file->getPathname(); + } + if ('x-accel-redirect' === strtolower($type)) { + // Do X-Accel-Mapping substitutions. + // @link https://www.nginx.com/resources/wiki/start/topics/examples/x-accel/#x-accel-redirect + $parts = HeaderUtils::split($request->headers->get('X-Accel-Mapping', ''), ',='); + foreach ($parts as $part) { + [$pathPrefix, $location] = $part; + if (substr($path, 0, \strlen($pathPrefix)) === $pathPrefix) { + $path = $location.substr($path, \strlen($pathPrefix)); + // Only set X-Accel-Redirect header if a valid URI can be produced + // as nginx does not serve arbitrary file paths. + $this->headers->set($type, $path); + $this->maxlen = 0; + break; + } + } + } else { + $this->headers->set($type, $path); + $this->maxlen = 0; + } + } elseif ($request->headers->has('Range') && $request->isMethod('GET')) { + // Process the range headers. + if (!$request->headers->has('If-Range') || $this->hasValidIfRangeHeader($request->headers->get('If-Range'))) { + $range = $request->headers->get('Range'); + + if (str_starts_with($range, 'bytes=')) { + [$start, $end] = explode('-', substr($range, 6), 2) + [0]; + + $end = ('' === $end) ? $fileSize - 1 : (int) $end; + + if ('' === $start) { + $start = $fileSize - $end; + $end = $fileSize - 1; + } else { + $start = (int) $start; + } + + if ($start <= $end) { + $end = min($end, $fileSize - 1); + if ($start < 0 || $start > $end) { + $this->setStatusCode(416); + $this->headers->set('Content-Range', sprintf('bytes */%s', $fileSize)); + } elseif ($end - $start < $fileSize - 1) { + $this->maxlen = $end < $fileSize ? $end - $start + 1 : -1; + $this->offset = $start; + + $this->setStatusCode(206); + $this->headers->set('Content-Range', sprintf('bytes %s-%s/%s', $start, $end, $fileSize)); + $this->headers->set('Content-Length', $end - $start + 1); + } + } + } + } + } + + return $this; + } + + private function hasValidIfRangeHeader(?string $header): bool + { + if ($this->getEtag() === $header) { + return true; + } + + if (null === $lastModified = $this->getLastModified()) { + return false; + } + + return $lastModified->format('D, d M Y H:i:s').' GMT' === $header; + } + + /** + * {@inheritdoc} + */ + public function sendContent() + { + if (!$this->isSuccessful()) { + return parent::sendContent(); + } + + if (0 === $this->maxlen) { + return $this; + } + + $out = fopen('php://output', 'w'); + $file = fopen($this->file->getPathname(), 'r'); + + stream_copy_to_stream($file, $out, $this->maxlen, $this->offset); + + fclose($out); + fclose($file); + + if ($this->deleteFileAfterSend && is_file($this->file->getPathname())) { + unlink($this->file->getPathname()); + } + + return $this; + } + + /** + * {@inheritdoc} + * + * @throws \LogicException when the content is not null + */ + public function setContent(?string $content) + { + if (null !== $content) { + throw new \LogicException('The content cannot be set on a BinaryFileResponse instance.'); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getContent() + { + return false; + } + + /** + * Trust X-Sendfile-Type header. + */ + public static function trustXSendfileTypeHeader() + { + self::$trustXSendfileTypeHeader = true; + } + + /** + * If this is set to true, the file will be unlinked after the request is sent + * Note: If the X-Sendfile header is used, the deleteFileAfterSend setting will not be used. + * + * @return $this + */ + public function deleteFileAfterSend(bool $shouldDelete = true) + { + $this->deleteFileAfterSend = $shouldDelete; + + return $this; + } +} diff --git a/vendor/symfony/http-foundation/CHANGELOG.md b/vendor/symfony/http-foundation/CHANGELOG.md new file mode 100644 index 0000000..ad7607a --- /dev/null +++ b/vendor/symfony/http-foundation/CHANGELOG.md @@ -0,0 +1,296 @@ +CHANGELOG +========= + +5.4 +--- + + * Deprecate passing `null` as `$requestIp` to `IpUtils::__checkIp()`, `IpUtils::__checkIp4()` or `IpUtils::__checkIp6()`, pass an empty string instead. + * Add the `litespeed_finish_request` method to work with Litespeed + * Deprecate `upload_progress.*` and `url_rewriter.tags` session options + * Allow setting session options via DSN + +5.3 +--- + + * Add the `SessionFactory`, `NativeSessionStorageFactory`, `PhpBridgeSessionStorageFactory` and `MockFileSessionStorageFactory` classes + * Calling `Request::getSession()` when there is no available session throws a `SessionNotFoundException` + * Add the `RequestStack::getSession` method + * Deprecate the `NamespacedAttributeBag` class + * Add `ResponseFormatSame` PHPUnit constraint + * Deprecate the `RequestStack::getMasterRequest()` method and add `getMainRequest()` as replacement + +5.2.0 +----- + + * added support for `X-Forwarded-Prefix` header + * added `HeaderUtils::parseQuery()`: it does the same as `parse_str()` but preserves dots in variable names + * added `File::getContent()` + * added ability to use comma separated ip addresses for `RequestMatcher::matchIps()` + * added `Request::toArray()` to parse a JSON request body to an array + * added `RateLimiter\RequestRateLimiterInterface` and `RateLimiter\AbstractRequestRateLimiter` + * deprecated not passing a `Closure` together with `FILTER_CALLBACK` to `ParameterBag::filter()`; wrap your filter in a closure instead. + * Deprecated the `Request::HEADER_X_FORWARDED_ALL` constant, use either `HEADER_X_FORWARDED_FOR | HEADER_X_FORWARDED_HOST | HEADER_X_FORWARDED_PORT | HEADER_X_FORWARDED_PROTO` or `HEADER_X_FORWARDED_AWS_ELB` or `HEADER_X_FORWARDED_TRAEFIK` constants instead. + * Deprecated `BinaryFileResponse::create()`, use `__construct()` instead + +5.1.0 +----- + + * added `Cookie::withValue`, `Cookie::withDomain`, `Cookie::withExpires`, + `Cookie::withPath`, `Cookie::withSecure`, `Cookie::withHttpOnly`, + `Cookie::withRaw`, `Cookie::withSameSite` + * Deprecate `Response::create()`, `JsonResponse::create()`, + `RedirectResponse::create()`, and `StreamedResponse::create()` methods (use + `__construct()` instead) + * added `Request::preferSafeContent()` and `Response::setContentSafe()` to handle "safe" HTTP preference + according to [RFC 8674](https://tools.ietf.org/html/rfc8674) + * made the Mime component an optional dependency + * added `MarshallingSessionHandler`, `IdentityMarshaller` + * made `Session` accept a callback to report when the session is being used + * Add support for all core cache control directives + * Added `Symfony\Component\HttpFoundation\InputBag` + * Deprecated retrieving non-string values using `InputBag::get()`, use `InputBag::all()` if you need access to the collection of values + +5.0.0 +----- + + * made `Cookie` auto-secure and lax by default + * removed classes in the `MimeType` namespace, use the Symfony Mime component instead + * removed method `UploadedFile::getClientSize()` and the related constructor argument + * made `Request::getSession()` throw if the session has not been set before + * removed `Response::HTTP_RESERVED_FOR_WEBDAV_ADVANCED_COLLECTIONS_EXPIRED_PROPOSAL` + * passing a null url when instantiating a `RedirectResponse` is not allowed + +4.4.0 +----- + + * passing arguments to `Request::isMethodSafe()` is deprecated. + * `ApacheRequest` is deprecated, use the `Request` class instead. + * passing a third argument to `HeaderBag::get()` is deprecated, use method `all()` instead + * [BC BREAK] `PdoSessionHandler` with MySQL changed the type of the lifetime column, + make sure to run `ALTER TABLE sessions MODIFY sess_lifetime INTEGER UNSIGNED NOT NULL` to + update your database. + * `PdoSessionHandler` now precalculates the expiry timestamp in the lifetime column, + make sure to run `CREATE INDEX EXPIRY ON sessions (sess_lifetime)` to update your database + to speed up garbage collection of expired sessions. + * added `SessionHandlerFactory` to create session handlers with a DSN + * added `IpUtils::anonymize()` to help with GDPR compliance. + +4.3.0 +----- + + * added PHPUnit constraints: `RequestAttributeValueSame`, `ResponseCookieValueSame`, `ResponseHasCookie`, + `ResponseHasHeader`, `ResponseHeaderSame`, `ResponseIsRedirected`, `ResponseIsSuccessful`, and `ResponseStatusCodeSame` + * deprecated `MimeTypeGuesserInterface` and `ExtensionGuesserInterface` in favor of `Symfony\Component\Mime\MimeTypesInterface`. + * deprecated `MimeType` and `MimeTypeExtensionGuesser` in favor of `Symfony\Component\Mime\MimeTypes`. + * deprecated `FileBinaryMimeTypeGuesser` in favor of `Symfony\Component\Mime\FileBinaryMimeTypeGuesser`. + * deprecated `FileinfoMimeTypeGuesser` in favor of `Symfony\Component\Mime\FileinfoMimeTypeGuesser`. + * added `UrlHelper` that allows to get an absolute URL and a relative path for a given path + +4.2.0 +----- + + * the default value of the "$secure" and "$samesite" arguments of Cookie's constructor + will respectively change from "false" to "null" and from "null" to "lax" in Symfony + 5.0, you should define their values explicitly or use "Cookie::create()" instead. + * added `matchPort()` in RequestMatcher + +4.1.3 +----- + + * [BC BREAK] Support for the IIS-only `X_ORIGINAL_URL` and `X_REWRITE_URL` + HTTP headers has been dropped for security reasons. + +4.1.0 +----- + + * Query string normalization uses `parse_str()` instead of custom parsing logic. + * Passing the file size to the constructor of the `UploadedFile` class is deprecated. + * The `getClientSize()` method of the `UploadedFile` class is deprecated. Use `getSize()` instead. + * added `RedisSessionHandler` to use Redis as a session storage + * The `get()` method of the `AcceptHeader` class now takes into account the + `*` and `*/*` default values (if they are present in the Accept HTTP header) + when looking for items. + * deprecated `Request::getSession()` when no session has been set. Use `Request::hasSession()` instead. + * added `CannotWriteFileException`, `ExtensionFileException`, `FormSizeFileException`, + `IniSizeFileException`, `NoFileException`, `NoTmpDirFileException`, `PartialFileException` to + handle failed `UploadedFile`. + * added `MigratingSessionHandler` for migrating between two session handlers without losing sessions + * added `HeaderUtils`. + +4.0.0 +----- + + * the `Request::setTrustedHeaderName()` and `Request::getTrustedHeaderName()` + methods have been removed + * the `Request::HEADER_CLIENT_IP` constant has been removed, use + `Request::HEADER_X_FORWARDED_FOR` instead + * the `Request::HEADER_CLIENT_HOST` constant has been removed, use + `Request::HEADER_X_FORWARDED_HOST` instead + * the `Request::HEADER_CLIENT_PROTO` constant has been removed, use + `Request::HEADER_X_FORWARDED_PROTO` instead + * the `Request::HEADER_CLIENT_PORT` constant has been removed, use + `Request::HEADER_X_FORWARDED_PORT` instead + * checking for cacheable HTTP methods using the `Request::isMethodSafe()` + method (by not passing `false` as its argument) is not supported anymore and + throws a `\BadMethodCallException` + * the `WriteCheckSessionHandler`, `NativeSessionHandler` and `NativeProxy` classes have been removed + * setting session save handlers that do not implement `\SessionHandlerInterface` in + `NativeSessionStorage::setSaveHandler()` is not supported anymore and throws a + `\TypeError` + +3.4.0 +----- + + * implemented PHP 7.0's `SessionUpdateTimestampHandlerInterface` with a new + `AbstractSessionHandler` base class and a new `StrictSessionHandler` wrapper + * deprecated the `WriteCheckSessionHandler`, `NativeSessionHandler` and `NativeProxy` classes + * deprecated setting session save handlers that do not implement `\SessionHandlerInterface` in `NativeSessionStorage::setSaveHandler()` + * deprecated using `MongoDbSessionHandler` with the legacy mongo extension; use it with the mongodb/mongodb package and ext-mongodb instead + * deprecated `MemcacheSessionHandler`; use `MemcachedSessionHandler` instead + +3.3.0 +----- + + * the `Request::setTrustedProxies()` method takes a new `$trustedHeaderSet` argument, + see https://symfony.com/doc/current/deployment/proxies.html for more info, + * deprecated the `Request::setTrustedHeaderName()` and `Request::getTrustedHeaderName()` methods, + * added `File\Stream`, to be passed to `BinaryFileResponse` when the size of the served file is unknown, + disabling `Range` and `Content-Length` handling, switching to chunked encoding instead + * added the `Cookie::fromString()` method that allows to create a cookie from a + raw header string + +3.1.0 +----- + + * Added support for creating `JsonResponse` with a string of JSON data + +3.0.0 +----- + + * The precedence of parameters returned from `Request::get()` changed from "GET, PATH, BODY" to "PATH, GET, BODY" + +2.8.0 +----- + + * Finding deep items in `ParameterBag::get()` is deprecated since version 2.8 and + will be removed in 3.0. + +2.6.0 +----- + + * PdoSessionHandler changes + - implemented different session locking strategies to prevent loss of data by concurrent access to the same session + - [BC BREAK] save session data in a binary column without base64_encode + - [BC BREAK] added lifetime column to the session table which allows to have different lifetimes for each session + - implemented lazy connections that are only opened when a session is used by either passing a dsn string + explicitly or falling back to session.save_path ini setting + - added a createTable method that initializes a correctly defined table depending on the database vendor + +2.5.0 +----- + + * added `JsonResponse::setEncodingOptions()` & `JsonResponse::getEncodingOptions()` for easier manipulation + of the options used while encoding data to JSON format. + +2.4.0 +----- + + * added RequestStack + * added Request::getEncodings() + * added accessors methods to session handlers + +2.3.0 +----- + + * added support for ranges of IPs in trusted proxies + * `UploadedFile::isValid` now returns false if the file was not uploaded via HTTP (in a non-test mode) + * Improved error-handling of `\Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler` + to ensure the supplied PDO handler throws Exceptions on error (as the class expects). Added related test cases + to verify that Exceptions are properly thrown when the PDO queries fail. + +2.2.0 +----- + + * fixed the Request::create() precedence (URI information always take precedence now) + * added Request::getTrustedProxies() + * deprecated Request::isProxyTrusted() + * [BC BREAK] JsonResponse does not turn a top level empty array to an object anymore, use an ArrayObject to enforce objects + * added a IpUtils class to check if an IP belongs to a CIDR + * added Request::getRealMethod() to get the "real" HTTP method (getMethod() returns the "intended" HTTP method) + * disabled _method request parameter support by default (call Request::enableHttpMethodParameterOverride() to + enable it, and Request::getHttpMethodParameterOverride() to check if it is supported) + * Request::splitHttpAcceptHeader() method is deprecated and will be removed in 2.3 + * Deprecated Flashbag::count() and \Countable interface, will be removed in 2.3 + +2.1.0 +----- + + * added Request::getSchemeAndHttpHost() and Request::getUserInfo() + * added a fluent interface to the Response class + * added Request::isProxyTrusted() + * added JsonResponse + * added a getTargetUrl method to RedirectResponse + * added support for streamed responses + * made Response::prepare() method the place to enforce HTTP specification + * [BC BREAK] moved management of the locale from the Session class to the Request class + * added a generic access to the PHP built-in filter mechanism: ParameterBag::filter() + * made FileBinaryMimeTypeGuesser command configurable + * added Request::getUser() and Request::getPassword() + * added support for the PATCH method in Request + * removed the ContentTypeMimeTypeGuesser class as it is deprecated and never used on PHP 5.3 + * added ResponseHeaderBag::makeDisposition() (implements RFC 6266) + * made mimetype to extension conversion configurable + * [BC BREAK] Moved all session related classes and interfaces into own namespace, as + `Symfony\Component\HttpFoundation\Session` and renamed classes accordingly. + Session handlers are located in the subnamespace `Symfony\Component\HttpFoundation\Session\Handler`. + * SessionHandlers must implement `\SessionHandlerInterface` or extend from the + `Symfony\Component\HttpFoundation\Storage\Handler\NativeSessionHandler` base class. + * Added internal storage driver proxy mechanism for forward compatibility with + PHP 5.4 `\SessionHandler` class. + * Added session handlers for custom Memcache, Memcached and Null session save handlers. + * [BC BREAK] Removed `NativeSessionStorage` and replaced with `NativeFileSessionHandler`. + * [BC BREAK] `SessionStorageInterface` methods removed: `write()`, `read()` and + `remove()`. Added `getBag()`, `registerBag()`. The `NativeSessionStorage` class + is a mediator for the session storage internals including the session handlers + which do the real work of participating in the internal PHP session workflow. + * [BC BREAK] Introduced mock implementations of `SessionStorage` to enable unit + and functional testing without starting real PHP sessions. Removed + `ArraySessionStorage`, and replaced with `MockArraySessionStorage` for unit + tests; removed `FilesystemSessionStorage`, and replaced with`MockFileSessionStorage` + for functional tests. These do not interact with global session ini + configuration values, session functions or `$_SESSION` superglobal. This means + they can be configured directly allowing multiple instances to work without + conflicting in the same PHP process. + * [BC BREAK] Removed the `close()` method from the `Session` class, as this is + now redundant. + * Deprecated the following methods from the Session class: `setFlash()`, `setFlashes()` + `getFlash()`, `hasFlash()`, and `removeFlash()`. Use `getFlashBag()` instead + which returns a `FlashBagInterface`. + * `Session->clear()` now only clears session attributes as before it cleared + flash messages and attributes. `Session->getFlashBag()->all()` clears flashes now. + * Session data is now managed by `SessionBagInterface` to better encapsulate + session data. + * Refactored session attribute and flash messages system to their own + `SessionBagInterface` implementations. + * Added `FlashBag`. Flashes expire when retrieved by `get()` or `all()`. This + implementation is ESI compatible. + * Added `AutoExpireFlashBag` (default) to replicate Symfony 2.0.x auto expire + behavior of messages auto expiring after one page page load. Messages must + be retrieved by `get()` or `all()`. + * Added `Symfony\Component\HttpFoundation\Attribute\AttributeBag` to replicate + attributes storage behavior from 2.0.x (default). + * Added `Symfony\Component\HttpFoundation\Attribute\NamespacedAttributeBag` for + namespace session attributes. + * Flash API can stores messages in an array so there may be multiple messages + per flash type. The old `Session` class API remains without BC break as it + will allow single messages as before. + * Added basic session meta-data to the session to record session create time, + last updated time, and the lifetime of the session cookie that was provided + to the client. + * Request::getClientIp() method doesn't take a parameter anymore but bases + itself on the trustProxy parameter. + * Added isMethod() to Request object. + * [BC BREAK] The methods `getPathInfo()`, `getBaseUrl()` and `getBasePath()` of + a `Request` now all return a raw value (vs a urldecoded value before). Any call + to one of these methods must be checked and wrapped in a `rawurldecode()` if + needed. diff --git a/vendor/symfony/http-foundation/Cookie.php b/vendor/symfony/http-foundation/Cookie.php new file mode 100644 index 0000000..b4b26c0 --- /dev/null +++ b/vendor/symfony/http-foundation/Cookie.php @@ -0,0 +1,422 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Represents a cookie. + * + * @author Johannes M. Schmitt + */ +class Cookie +{ + public const SAMESITE_NONE = 'none'; + public const SAMESITE_LAX = 'lax'; + public const SAMESITE_STRICT = 'strict'; + + protected $name; + protected $value; + protected $domain; + protected $expire; + protected $path; + protected $secure; + protected $httpOnly; + + private $raw; + private $sameSite; + private $secureDefault = false; + + private const RESERVED_CHARS_LIST = "=,; \t\r\n\v\f"; + private const RESERVED_CHARS_FROM = ['=', ',', ';', ' ', "\t", "\r", "\n", "\v", "\f"]; + private const RESERVED_CHARS_TO = ['%3D', '%2C', '%3B', '%20', '%09', '%0D', '%0A', '%0B', '%0C']; + + /** + * Creates cookie from raw header string. + * + * @return static + */ + public static function fromString(string $cookie, bool $decode = false) + { + $data = [ + 'expires' => 0, + 'path' => '/', + 'domain' => null, + 'secure' => false, + 'httponly' => false, + 'raw' => !$decode, + 'samesite' => null, + ]; + + $parts = HeaderUtils::split($cookie, ';='); + $part = array_shift($parts); + + $name = $decode ? urldecode($part[0]) : $part[0]; + $value = isset($part[1]) ? ($decode ? urldecode($part[1]) : $part[1]) : null; + + $data = HeaderUtils::combine($parts) + $data; + $data['expires'] = self::expiresTimestamp($data['expires']); + + if (isset($data['max-age']) && ($data['max-age'] > 0 || $data['expires'] > time())) { + $data['expires'] = time() + (int) $data['max-age']; + } + + return new static($name, $value, $data['expires'], $data['path'], $data['domain'], $data['secure'], $data['httponly'], $data['raw'], $data['samesite']); + } + + public static function create(string $name, string $value = null, $expire = 0, ?string $path = '/', string $domain = null, bool $secure = null, bool $httpOnly = true, bool $raw = false, ?string $sameSite = self::SAMESITE_LAX): self + { + return new self($name, $value, $expire, $path, $domain, $secure, $httpOnly, $raw, $sameSite); + } + + /** + * @param string $name The name of the cookie + * @param string|null $value The value of the cookie + * @param int|string|\DateTimeInterface $expire The time the cookie expires + * @param string $path The path on the server in which the cookie will be available on + * @param string|null $domain The domain that the cookie is available to + * @param bool|null $secure Whether the client should send back the cookie only over HTTPS or null to auto-enable this when the request is already using HTTPS + * @param bool $httpOnly Whether the cookie will be made accessible only through the HTTP protocol + * @param bool $raw Whether the cookie value should be sent with no url encoding + * @param string|null $sameSite Whether the cookie will be available for cross-site requests + * + * @throws \InvalidArgumentException + */ + public function __construct(string $name, string $value = null, $expire = 0, ?string $path = '/', string $domain = null, bool $secure = null, bool $httpOnly = true, bool $raw = false, ?string $sameSite = 'lax') + { + // from PHP source code + if ($raw && false !== strpbrk($name, self::RESERVED_CHARS_LIST)) { + throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name)); + } + + if (empty($name)) { + throw new \InvalidArgumentException('The cookie name cannot be empty.'); + } + + $this->name = $name; + $this->value = $value; + $this->domain = $domain; + $this->expire = self::expiresTimestamp($expire); + $this->path = empty($path) ? '/' : $path; + $this->secure = $secure; + $this->httpOnly = $httpOnly; + $this->raw = $raw; + $this->sameSite = $this->withSameSite($sameSite)->sameSite; + } + + /** + * Creates a cookie copy with a new value. + * + * @return static + */ + public function withValue(?string $value): self + { + $cookie = clone $this; + $cookie->value = $value; + + return $cookie; + } + + /** + * Creates a cookie copy with a new domain that the cookie is available to. + * + * @return static + */ + public function withDomain(?string $domain): self + { + $cookie = clone $this; + $cookie->domain = $domain; + + return $cookie; + } + + /** + * Creates a cookie copy with a new time the cookie expires. + * + * @param int|string|\DateTimeInterface $expire + * + * @return static + */ + public function withExpires($expire = 0): self + { + $cookie = clone $this; + $cookie->expire = self::expiresTimestamp($expire); + + return $cookie; + } + + /** + * Converts expires formats to a unix timestamp. + * + * @param int|string|\DateTimeInterface $expire + */ + private static function expiresTimestamp($expire = 0): int + { + // convert expiration time to a Unix timestamp + if ($expire instanceof \DateTimeInterface) { + $expire = $expire->format('U'); + } elseif (!is_numeric($expire)) { + $expire = strtotime($expire); + + if (false === $expire) { + throw new \InvalidArgumentException('The cookie expiration time is not valid.'); + } + } + + return 0 < $expire ? (int) $expire : 0; + } + + /** + * Creates a cookie copy with a new path on the server in which the cookie will be available on. + * + * @return static + */ + public function withPath(string $path): self + { + $cookie = clone $this; + $cookie->path = '' === $path ? '/' : $path; + + return $cookie; + } + + /** + * Creates a cookie copy that only be transmitted over a secure HTTPS connection from the client. + * + * @return static + */ + public function withSecure(bool $secure = true): self + { + $cookie = clone $this; + $cookie->secure = $secure; + + return $cookie; + } + + /** + * Creates a cookie copy that be accessible only through the HTTP protocol. + * + * @return static + */ + public function withHttpOnly(bool $httpOnly = true): self + { + $cookie = clone $this; + $cookie->httpOnly = $httpOnly; + + return $cookie; + } + + /** + * Creates a cookie copy that uses no url encoding. + * + * @return static + */ + public function withRaw(bool $raw = true): self + { + if ($raw && false !== strpbrk($this->name, self::RESERVED_CHARS_LIST)) { + throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $this->name)); + } + + $cookie = clone $this; + $cookie->raw = $raw; + + return $cookie; + } + + /** + * Creates a cookie copy with SameSite attribute. + * + * @return static + */ + public function withSameSite(?string $sameSite): self + { + if ('' === $sameSite) { + $sameSite = null; + } elseif (null !== $sameSite) { + $sameSite = strtolower($sameSite); + } + + if (!\in_array($sameSite, [self::SAMESITE_LAX, self::SAMESITE_STRICT, self::SAMESITE_NONE, null], true)) { + throw new \InvalidArgumentException('The "sameSite" parameter value is not valid.'); + } + + $cookie = clone $this; + $cookie->sameSite = $sameSite; + + return $cookie; + } + + /** + * Returns the cookie as a string. + * + * @return string + */ + public function __toString() + { + if ($this->isRaw()) { + $str = $this->getName(); + } else { + $str = str_replace(self::RESERVED_CHARS_FROM, self::RESERVED_CHARS_TO, $this->getName()); + } + + $str .= '='; + + if ('' === (string) $this->getValue()) { + $str .= 'deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; Max-Age=0'; + } else { + $str .= $this->isRaw() ? $this->getValue() : rawurlencode($this->getValue()); + + if (0 !== $this->getExpiresTime()) { + $str .= '; expires='.gmdate('D, d-M-Y H:i:s T', $this->getExpiresTime()).'; Max-Age='.$this->getMaxAge(); + } + } + + if ($this->getPath()) { + $str .= '; path='.$this->getPath(); + } + + if ($this->getDomain()) { + $str .= '; domain='.$this->getDomain(); + } + + if (true === $this->isSecure()) { + $str .= '; secure'; + } + + if (true === $this->isHttpOnly()) { + $str .= '; httponly'; + } + + if (null !== $this->getSameSite()) { + $str .= '; samesite='.$this->getSameSite(); + } + + return $str; + } + + /** + * Gets the name of the cookie. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Gets the value of the cookie. + * + * @return string|null + */ + public function getValue() + { + return $this->value; + } + + /** + * Gets the domain that the cookie is available to. + * + * @return string|null + */ + public function getDomain() + { + return $this->domain; + } + + /** + * Gets the time the cookie expires. + * + * @return int + */ + public function getExpiresTime() + { + return $this->expire; + } + + /** + * Gets the max-age attribute. + * + * @return int + */ + public function getMaxAge() + { + $maxAge = $this->expire - time(); + + return 0 >= $maxAge ? 0 : $maxAge; + } + + /** + * Gets the path on the server in which the cookie will be available on. + * + * @return string + */ + public function getPath() + { + return $this->path; + } + + /** + * Checks whether the cookie should only be transmitted over a secure HTTPS connection from the client. + * + * @return bool + */ + public function isSecure() + { + return $this->secure ?? $this->secureDefault; + } + + /** + * Checks whether the cookie will be made accessible only through the HTTP protocol. + * + * @return bool + */ + public function isHttpOnly() + { + return $this->httpOnly; + } + + /** + * Whether this cookie is about to be cleared. + * + * @return bool + */ + public function isCleared() + { + return 0 !== $this->expire && $this->expire < time(); + } + + /** + * Checks if the cookie value should be sent with no url encoding. + * + * @return bool + */ + public function isRaw() + { + return $this->raw; + } + + /** + * Gets the SameSite attribute. + * + * @return string|null + */ + public function getSameSite() + { + return $this->sameSite; + } + + /** + * @param bool $default The default value of the "secure" flag when it is set to null + */ + public function setSecureDefault(bool $default): void + { + $this->secureDefault = $default; + } +} diff --git a/vendor/symfony/http-foundation/Exception/BadRequestException.php b/vendor/symfony/http-foundation/Exception/BadRequestException.php new file mode 100644 index 0000000..e4bb309 --- /dev/null +++ b/vendor/symfony/http-foundation/Exception/BadRequestException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Exception; + +/** + * Raised when a user sends a malformed request. + */ +class BadRequestException extends \UnexpectedValueException implements RequestExceptionInterface +{ +} diff --git a/vendor/symfony/http-foundation/Exception/ConflictingHeadersException.php b/vendor/symfony/http-foundation/Exception/ConflictingHeadersException.php new file mode 100644 index 0000000..5fcf5b4 --- /dev/null +++ b/vendor/symfony/http-foundation/Exception/ConflictingHeadersException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Exception; + +/** + * The HTTP request contains headers with conflicting information. + * + * @author Magnus Nordlander + */ +class ConflictingHeadersException extends \UnexpectedValueException implements RequestExceptionInterface +{ +} diff --git a/vendor/symfony/http-foundation/Exception/JsonException.php b/vendor/symfony/http-foundation/Exception/JsonException.php new file mode 100644 index 0000000..5990e76 --- /dev/null +++ b/vendor/symfony/http-foundation/Exception/JsonException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Exception; + +/** + * Thrown by Request::toArray() when the content cannot be JSON-decoded. + * + * @author Tobias Nyholm + */ +final class JsonException extends \UnexpectedValueException implements RequestExceptionInterface +{ +} diff --git a/vendor/symfony/http-foundation/Exception/RequestExceptionInterface.php b/vendor/symfony/http-foundation/Exception/RequestExceptionInterface.php new file mode 100644 index 0000000..478d0dc --- /dev/null +++ b/vendor/symfony/http-foundation/Exception/RequestExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Exception; + +/** + * Interface for Request exceptions. + * + * Exceptions implementing this interface should trigger an HTTP 400 response in the application code. + */ +interface RequestExceptionInterface +{ +} diff --git a/vendor/symfony/http-foundation/Exception/SessionNotFoundException.php b/vendor/symfony/http-foundation/Exception/SessionNotFoundException.php new file mode 100644 index 0000000..9c719aa --- /dev/null +++ b/vendor/symfony/http-foundation/Exception/SessionNotFoundException.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Exception; + +/** + * Raised when a session does not exists. This happens in the following cases: + * - the session is not enabled + * - attempt to read a session outside a request context (ie. cli script). + * + * @author Jérémy Derussé + */ +class SessionNotFoundException extends \LogicException implements RequestExceptionInterface +{ + public function __construct(string $message = 'There is currently no session available.', int $code = 0, \Throwable $previous = null) + { + parent::__construct($message, $code, $previous); + } +} diff --git a/vendor/symfony/http-foundation/Exception/SuspiciousOperationException.php b/vendor/symfony/http-foundation/Exception/SuspiciousOperationException.php new file mode 100644 index 0000000..ae7a5f1 --- /dev/null +++ b/vendor/symfony/http-foundation/Exception/SuspiciousOperationException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Exception; + +/** + * Raised when a user has performed an operation that should be considered + * suspicious from a security perspective. + */ +class SuspiciousOperationException extends \UnexpectedValueException implements RequestExceptionInterface +{ +} diff --git a/vendor/symfony/http-foundation/ExpressionRequestMatcher.php b/vendor/symfony/http-foundation/ExpressionRequestMatcher.php new file mode 100644 index 0000000..26bed7d --- /dev/null +++ b/vendor/symfony/http-foundation/ExpressionRequestMatcher.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; + +/** + * ExpressionRequestMatcher uses an expression to match a Request. + * + * @author Fabien Potencier + */ +class ExpressionRequestMatcher extends RequestMatcher +{ + private $language; + private $expression; + + public function setExpression(ExpressionLanguage $language, $expression) + { + $this->language = $language; + $this->expression = $expression; + } + + public function matches(Request $request) + { + if (!$this->language) { + throw new \LogicException('Unable to match the request as the expression language is not available.'); + } + + return $this->language->evaluate($this->expression, [ + 'request' => $request, + 'method' => $request->getMethod(), + 'path' => rawurldecode($request->getPathInfo()), + 'host' => $request->getHost(), + 'ip' => $request->getClientIp(), + 'attributes' => $request->attributes->all(), + ]) && parent::matches($request); + } +} diff --git a/vendor/symfony/http-foundation/File/Exception/AccessDeniedException.php b/vendor/symfony/http-foundation/File/Exception/AccessDeniedException.php new file mode 100644 index 0000000..136d2a9 --- /dev/null +++ b/vendor/symfony/http-foundation/File/Exception/AccessDeniedException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when the access on a file was denied. + * + * @author Bernhard Schussek + */ +class AccessDeniedException extends FileException +{ + public function __construct(string $path) + { + parent::__construct(sprintf('The file %s could not be accessed', $path)); + } +} diff --git a/vendor/symfony/http-foundation/File/Exception/CannotWriteFileException.php b/vendor/symfony/http-foundation/File/Exception/CannotWriteFileException.php new file mode 100644 index 0000000..c49f53a --- /dev/null +++ b/vendor/symfony/http-foundation/File/Exception/CannotWriteFileException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an UPLOAD_ERR_CANT_WRITE error occurred with UploadedFile. + * + * @author Florent Mata + */ +class CannotWriteFileException extends FileException +{ +} diff --git a/vendor/symfony/http-foundation/File/Exception/ExtensionFileException.php b/vendor/symfony/http-foundation/File/Exception/ExtensionFileException.php new file mode 100644 index 0000000..ed83499 --- /dev/null +++ b/vendor/symfony/http-foundation/File/Exception/ExtensionFileException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an UPLOAD_ERR_EXTENSION error occurred with UploadedFile. + * + * @author Florent Mata + */ +class ExtensionFileException extends FileException +{ +} diff --git a/vendor/symfony/http-foundation/File/Exception/FileException.php b/vendor/symfony/http-foundation/File/Exception/FileException.php new file mode 100644 index 0000000..fad5133 --- /dev/null +++ b/vendor/symfony/http-foundation/File/Exception/FileException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an error occurred in the component File. + * + * @author Bernhard Schussek + */ +class FileException extends \RuntimeException +{ +} diff --git a/vendor/symfony/http-foundation/File/Exception/FileNotFoundException.php b/vendor/symfony/http-foundation/File/Exception/FileNotFoundException.php new file mode 100644 index 0000000..31bdf68 --- /dev/null +++ b/vendor/symfony/http-foundation/File/Exception/FileNotFoundException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when a file was not found. + * + * @author Bernhard Schussek + */ +class FileNotFoundException extends FileException +{ + public function __construct(string $path) + { + parent::__construct(sprintf('The file "%s" does not exist', $path)); + } +} diff --git a/vendor/symfony/http-foundation/File/Exception/FormSizeFileException.php b/vendor/symfony/http-foundation/File/Exception/FormSizeFileException.php new file mode 100644 index 0000000..8741be0 --- /dev/null +++ b/vendor/symfony/http-foundation/File/Exception/FormSizeFileException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an UPLOAD_ERR_FORM_SIZE error occurred with UploadedFile. + * + * @author Florent Mata + */ +class FormSizeFileException extends FileException +{ +} diff --git a/vendor/symfony/http-foundation/File/Exception/IniSizeFileException.php b/vendor/symfony/http-foundation/File/Exception/IniSizeFileException.php new file mode 100644 index 0000000..c8fde61 --- /dev/null +++ b/vendor/symfony/http-foundation/File/Exception/IniSizeFileException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an UPLOAD_ERR_INI_SIZE error occurred with UploadedFile. + * + * @author Florent Mata + */ +class IniSizeFileException extends FileException +{ +} diff --git a/vendor/symfony/http-foundation/File/Exception/NoFileException.php b/vendor/symfony/http-foundation/File/Exception/NoFileException.php new file mode 100644 index 0000000..4b48cc7 --- /dev/null +++ b/vendor/symfony/http-foundation/File/Exception/NoFileException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an UPLOAD_ERR_NO_FILE error occurred with UploadedFile. + * + * @author Florent Mata + */ +class NoFileException extends FileException +{ +} diff --git a/vendor/symfony/http-foundation/File/Exception/NoTmpDirFileException.php b/vendor/symfony/http-foundation/File/Exception/NoTmpDirFileException.php new file mode 100644 index 0000000..bdead2d --- /dev/null +++ b/vendor/symfony/http-foundation/File/Exception/NoTmpDirFileException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an UPLOAD_ERR_NO_TMP_DIR error occurred with UploadedFile. + * + * @author Florent Mata + */ +class NoTmpDirFileException extends FileException +{ +} diff --git a/vendor/symfony/http-foundation/File/Exception/PartialFileException.php b/vendor/symfony/http-foundation/File/Exception/PartialFileException.php new file mode 100644 index 0000000..4641efb --- /dev/null +++ b/vendor/symfony/http-foundation/File/Exception/PartialFileException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an UPLOAD_ERR_PARTIAL error occurred with UploadedFile. + * + * @author Florent Mata + */ +class PartialFileException extends FileException +{ +} diff --git a/vendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php b/vendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php new file mode 100644 index 0000000..8533f99 --- /dev/null +++ b/vendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +class UnexpectedTypeException extends FileException +{ + public function __construct($value, string $expectedType) + { + parent::__construct(sprintf('Expected argument of type %s, %s given', $expectedType, get_debug_type($value))); + } +} diff --git a/vendor/symfony/http-foundation/File/Exception/UploadException.php b/vendor/symfony/http-foundation/File/Exception/UploadException.php new file mode 100644 index 0000000..7074e76 --- /dev/null +++ b/vendor/symfony/http-foundation/File/Exception/UploadException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an error occurred during file upload. + * + * @author Bernhard Schussek + */ +class UploadException extends FileException +{ +} diff --git a/vendor/symfony/http-foundation/File/File.php b/vendor/symfony/http-foundation/File/File.php new file mode 100644 index 0000000..d941577 --- /dev/null +++ b/vendor/symfony/http-foundation/File/File.php @@ -0,0 +1,152 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File; + +use Symfony\Component\HttpFoundation\File\Exception\FileException; +use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; +use Symfony\Component\Mime\MimeTypes; + +/** + * A file in the file system. + * + * @author Bernhard Schussek + */ +class File extends \SplFileInfo +{ + /** + * Constructs a new file from the given path. + * + * @param string $path The path to the file + * @param bool $checkPath Whether to check the path or not + * + * @throws FileNotFoundException If the given path is not a file + */ + public function __construct(string $path, bool $checkPath = true) + { + if ($checkPath && !is_file($path)) { + throw new FileNotFoundException($path); + } + + parent::__construct($path); + } + + /** + * Returns the extension based on the mime type. + * + * If the mime type is unknown, returns null. + * + * This method uses the mime type as guessed by getMimeType() + * to guess the file extension. + * + * @return string|null + * + * @see MimeTypes + * @see getMimeType() + */ + public function guessExtension() + { + if (!class_exists(MimeTypes::class)) { + throw new \LogicException('You cannot guess the extension as the Mime component is not installed. Try running "composer require symfony/mime".'); + } + + return MimeTypes::getDefault()->getExtensions($this->getMimeType())[0] ?? null; + } + + /** + * Returns the mime type of the file. + * + * The mime type is guessed using a MimeTypeGuesserInterface instance, + * which uses finfo_file() then the "file" system binary, + * depending on which of those are available. + * + * @return string|null + * + * @see MimeTypes + */ + public function getMimeType() + { + if (!class_exists(MimeTypes::class)) { + throw new \LogicException('You cannot guess the mime type as the Mime component is not installed. Try running "composer require symfony/mime".'); + } + + return MimeTypes::getDefault()->guessMimeType($this->getPathname()); + } + + /** + * Moves the file to a new location. + * + * @return self + * + * @throws FileException if the target file could not be created + */ + public function move(string $directory, string $name = null) + { + $target = $this->getTargetFile($directory, $name); + + set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; }); + try { + $renamed = rename($this->getPathname(), $target); + } finally { + restore_error_handler(); + } + if (!$renamed) { + throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s).', $this->getPathname(), $target, strip_tags($error))); + } + + @chmod($target, 0666 & ~umask()); + + return $target; + } + + public function getContent(): string + { + $content = file_get_contents($this->getPathname()); + + if (false === $content) { + throw new FileException(sprintf('Could not get the content of the file "%s".', $this->getPathname())); + } + + return $content; + } + + /** + * @return self + */ + protected function getTargetFile(string $directory, string $name = null) + { + if (!is_dir($directory)) { + if (false === @mkdir($directory, 0777, true) && !is_dir($directory)) { + throw new FileException(sprintf('Unable to create the "%s" directory.', $directory)); + } + } elseif (!is_writable($directory)) { + throw new FileException(sprintf('Unable to write in the "%s" directory.', $directory)); + } + + $target = rtrim($directory, '/\\').\DIRECTORY_SEPARATOR.(null === $name ? $this->getBasename() : $this->getName($name)); + + return new self($target, false); + } + + /** + * Returns locale independent base name of the given path. + * + * @return string + */ + protected function getName(string $name) + { + $originalName = str_replace('\\', '/', $name); + $pos = strrpos($originalName, '/'); + $originalName = false === $pos ? $originalName : substr($originalName, $pos + 1); + + return $originalName; + } +} diff --git a/vendor/symfony/http-foundation/File/Stream.php b/vendor/symfony/http-foundation/File/Stream.php new file mode 100644 index 0000000..cef3e03 --- /dev/null +++ b/vendor/symfony/http-foundation/File/Stream.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File; + +/** + * A PHP stream of unknown size. + * + * @author Nicolas Grekas + */ +class Stream extends File +{ + /** + * {@inheritdoc} + * + * @return int|false + */ + #[\ReturnTypeWillChange] + public function getSize() + { + return false; + } +} diff --git a/vendor/symfony/http-foundation/File/UploadedFile.php b/vendor/symfony/http-foundation/File/UploadedFile.php new file mode 100644 index 0000000..5cfe8c4 --- /dev/null +++ b/vendor/symfony/http-foundation/File/UploadedFile.php @@ -0,0 +1,290 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File; + +use Symfony\Component\HttpFoundation\File\Exception\CannotWriteFileException; +use Symfony\Component\HttpFoundation\File\Exception\ExtensionFileException; +use Symfony\Component\HttpFoundation\File\Exception\FileException; +use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; +use Symfony\Component\HttpFoundation\File\Exception\FormSizeFileException; +use Symfony\Component\HttpFoundation\File\Exception\IniSizeFileException; +use Symfony\Component\HttpFoundation\File\Exception\NoFileException; +use Symfony\Component\HttpFoundation\File\Exception\NoTmpDirFileException; +use Symfony\Component\HttpFoundation\File\Exception\PartialFileException; +use Symfony\Component\Mime\MimeTypes; + +/** + * A file uploaded through a form. + * + * @author Bernhard Schussek + * @author Florian Eckerstorfer + * @author Fabien Potencier + */ +class UploadedFile extends File +{ + private $test; + private $originalName; + private $mimeType; + private $error; + + /** + * Accepts the information of the uploaded file as provided by the PHP global $_FILES. + * + * The file object is only created when the uploaded file is valid (i.e. when the + * isValid() method returns true). Otherwise the only methods that could be called + * on an UploadedFile instance are: + * + * * getClientOriginalName, + * * getClientMimeType, + * * isValid, + * * getError. + * + * Calling any other method on an non-valid instance will cause an unpredictable result. + * + * @param string $path The full temporary path to the file + * @param string $originalName The original file name of the uploaded file + * @param string|null $mimeType The type of the file as provided by PHP; null defaults to application/octet-stream + * @param int|null $error The error constant of the upload (one of PHP's UPLOAD_ERR_XXX constants); null defaults to UPLOAD_ERR_OK + * @param bool $test Whether the test mode is active + * Local files are used in test mode hence the code should not enforce HTTP uploads + * + * @throws FileException If file_uploads is disabled + * @throws FileNotFoundException If the file does not exist + */ + public function __construct(string $path, string $originalName, string $mimeType = null, int $error = null, bool $test = false) + { + $this->originalName = $this->getName($originalName); + $this->mimeType = $mimeType ?: 'application/octet-stream'; + $this->error = $error ?: \UPLOAD_ERR_OK; + $this->test = $test; + + parent::__construct($path, \UPLOAD_ERR_OK === $this->error); + } + + /** + * Returns the original file name. + * + * It is extracted from the request from which the file has been uploaded. + * Then it should not be considered as a safe value. + * + * @return string + */ + public function getClientOriginalName() + { + return $this->originalName; + } + + /** + * Returns the original file extension. + * + * It is extracted from the original file name that was uploaded. + * Then it should not be considered as a safe value. + * + * @return string + */ + public function getClientOriginalExtension() + { + return pathinfo($this->originalName, \PATHINFO_EXTENSION); + } + + /** + * Returns the file mime type. + * + * The client mime type is extracted from the request from which the file + * was uploaded, so it should not be considered as a safe value. + * + * For a trusted mime type, use getMimeType() instead (which guesses the mime + * type based on the file content). + * + * @return string + * + * @see getMimeType() + */ + public function getClientMimeType() + { + return $this->mimeType; + } + + /** + * Returns the extension based on the client mime type. + * + * If the mime type is unknown, returns null. + * + * This method uses the mime type as guessed by getClientMimeType() + * to guess the file extension. As such, the extension returned + * by this method cannot be trusted. + * + * For a trusted extension, use guessExtension() instead (which guesses + * the extension based on the guessed mime type for the file). + * + * @return string|null + * + * @see guessExtension() + * @see getClientMimeType() + */ + public function guessClientExtension() + { + if (!class_exists(MimeTypes::class)) { + throw new \LogicException('You cannot guess the extension as the Mime component is not installed. Try running "composer require symfony/mime".'); + } + + return MimeTypes::getDefault()->getExtensions($this->getClientMimeType())[0] ?? null; + } + + /** + * Returns the upload error. + * + * If the upload was successful, the constant UPLOAD_ERR_OK is returned. + * Otherwise one of the other UPLOAD_ERR_XXX constants is returned. + * + * @return int + */ + public function getError() + { + return $this->error; + } + + /** + * Returns whether the file has been uploaded with HTTP and no error occurred. + * + * @return bool + */ + public function isValid() + { + $isOk = \UPLOAD_ERR_OK === $this->error; + + return $this->test ? $isOk : $isOk && is_uploaded_file($this->getPathname()); + } + + /** + * Moves the file to a new location. + * + * @return File + * + * @throws FileException if, for any reason, the file could not have been moved + */ + public function move(string $directory, string $name = null) + { + if ($this->isValid()) { + if ($this->test) { + return parent::move($directory, $name); + } + + $target = $this->getTargetFile($directory, $name); + + set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; }); + try { + $moved = move_uploaded_file($this->getPathname(), $target); + } finally { + restore_error_handler(); + } + if (!$moved) { + throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s).', $this->getPathname(), $target, strip_tags($error))); + } + + @chmod($target, 0666 & ~umask()); + + return $target; + } + + switch ($this->error) { + case \UPLOAD_ERR_INI_SIZE: + throw new IniSizeFileException($this->getErrorMessage()); + case \UPLOAD_ERR_FORM_SIZE: + throw new FormSizeFileException($this->getErrorMessage()); + case \UPLOAD_ERR_PARTIAL: + throw new PartialFileException($this->getErrorMessage()); + case \UPLOAD_ERR_NO_FILE: + throw new NoFileException($this->getErrorMessage()); + case \UPLOAD_ERR_CANT_WRITE: + throw new CannotWriteFileException($this->getErrorMessage()); + case \UPLOAD_ERR_NO_TMP_DIR: + throw new NoTmpDirFileException($this->getErrorMessage()); + case \UPLOAD_ERR_EXTENSION: + throw new ExtensionFileException($this->getErrorMessage()); + } + + throw new FileException($this->getErrorMessage()); + } + + /** + * Returns the maximum size of an uploaded file as configured in php.ini. + * + * @return int|float The maximum size of an uploaded file in bytes (returns float if size > PHP_INT_MAX) + */ + public static function getMaxFilesize() + { + $sizePostMax = self::parseFilesize(ini_get('post_max_size')); + $sizeUploadMax = self::parseFilesize(ini_get('upload_max_filesize')); + + return min($sizePostMax ?: \PHP_INT_MAX, $sizeUploadMax ?: \PHP_INT_MAX); + } + + /** + * Returns the given size from an ini value in bytes. + * + * @return int|float Returns float if size > PHP_INT_MAX + */ + private static function parseFilesize(string $size) + { + if ('' === $size) { + return 0; + } + + $size = strtolower($size); + + $max = ltrim($size, '+'); + if (str_starts_with($max, '0x')) { + $max = \intval($max, 16); + } elseif (str_starts_with($max, '0')) { + $max = \intval($max, 8); + } else { + $max = (int) $max; + } + + switch (substr($size, -1)) { + case 't': $max *= 1024; + // no break + case 'g': $max *= 1024; + // no break + case 'm': $max *= 1024; + // no break + case 'k': $max *= 1024; + } + + return $max; + } + + /** + * Returns an informative upload error message. + * + * @return string + */ + public function getErrorMessage() + { + static $errors = [ + \UPLOAD_ERR_INI_SIZE => 'The file "%s" exceeds your upload_max_filesize ini directive (limit is %d KiB).', + \UPLOAD_ERR_FORM_SIZE => 'The file "%s" exceeds the upload limit defined in your form.', + \UPLOAD_ERR_PARTIAL => 'The file "%s" was only partially uploaded.', + \UPLOAD_ERR_NO_FILE => 'No file was uploaded.', + \UPLOAD_ERR_CANT_WRITE => 'The file "%s" could not be written on disk.', + \UPLOAD_ERR_NO_TMP_DIR => 'File could not be uploaded: missing temporary directory.', + \UPLOAD_ERR_EXTENSION => 'File upload was stopped by a PHP extension.', + ]; + + $errorCode = $this->error; + $maxFilesize = \UPLOAD_ERR_INI_SIZE === $errorCode ? self::getMaxFilesize() / 1024 : 0; + $message = $errors[$errorCode] ?? 'The file "%s" was not uploaded due to an unknown error.'; + + return sprintf($message, $this->getClientOriginalName(), $maxFilesize); + } +} diff --git a/vendor/symfony/http-foundation/FileBag.php b/vendor/symfony/http-foundation/FileBag.php new file mode 100644 index 0000000..ff5ab77 --- /dev/null +++ b/vendor/symfony/http-foundation/FileBag.php @@ -0,0 +1,140 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\HttpFoundation\File\UploadedFile; + +/** + * FileBag is a container for uploaded files. + * + * @author Fabien Potencier + * @author Bulat Shakirzyanov + */ +class FileBag extends ParameterBag +{ + private const FILE_KEYS = ['error', 'name', 'size', 'tmp_name', 'type']; + + /** + * @param array|UploadedFile[] $parameters An array of HTTP files + */ + public function __construct(array $parameters = []) + { + $this->replace($parameters); + } + + /** + * {@inheritdoc} + */ + public function replace(array $files = []) + { + $this->parameters = []; + $this->add($files); + } + + /** + * {@inheritdoc} + */ + public function set(string $key, $value) + { + if (!\is_array($value) && !$value instanceof UploadedFile) { + throw new \InvalidArgumentException('An uploaded file must be an array or an instance of UploadedFile.'); + } + + parent::set($key, $this->convertFileInformation($value)); + } + + /** + * {@inheritdoc} + */ + public function add(array $files = []) + { + foreach ($files as $key => $file) { + $this->set($key, $file); + } + } + + /** + * Converts uploaded files to UploadedFile instances. + * + * @param array|UploadedFile $file A (multi-dimensional) array of uploaded file information + * + * @return UploadedFile[]|UploadedFile|null + */ + protected function convertFileInformation($file) + { + if ($file instanceof UploadedFile) { + return $file; + } + + $file = $this->fixPhpFilesArray($file); + $keys = array_keys($file); + sort($keys); + + if (self::FILE_KEYS == $keys) { + if (\UPLOAD_ERR_NO_FILE == $file['error']) { + $file = null; + } else { + $file = new UploadedFile($file['tmp_name'], $file['name'], $file['type'], $file['error'], false); + } + } else { + $file = array_map(function ($v) { return $v instanceof UploadedFile || \is_array($v) ? $this->convertFileInformation($v) : $v; }, $file); + if (array_keys($keys) === $keys) { + $file = array_filter($file); + } + } + + return $file; + } + + /** + * Fixes a malformed PHP $_FILES array. + * + * PHP has a bug that the format of the $_FILES array differs, depending on + * whether the uploaded file fields had normal field names or array-like + * field names ("normal" vs. "parent[child]"). + * + * This method fixes the array to look like the "normal" $_FILES array. + * + * It's safe to pass an already converted array, in which case this method + * just returns the original array unmodified. + * + * @return array + */ + protected function fixPhpFilesArray(array $data) + { + // Remove extra key added by PHP 8.1. + unset($data['full_path']); + $keys = array_keys($data); + sort($keys); + + if (self::FILE_KEYS != $keys || !isset($data['name']) || !\is_array($data['name'])) { + return $data; + } + + $files = $data; + foreach (self::FILE_KEYS as $k) { + unset($files[$k]); + } + + foreach ($data['name'] as $key => $name) { + $files[$key] = $this->fixPhpFilesArray([ + 'error' => $data['error'][$key], + 'name' => $name, + 'type' => $data['type'][$key], + 'tmp_name' => $data['tmp_name'][$key], + 'size' => $data['size'][$key], + ]); + } + + return $files; + } +} diff --git a/vendor/symfony/http-foundation/HeaderBag.php b/vendor/symfony/http-foundation/HeaderBag.php new file mode 100644 index 0000000..4683a68 --- /dev/null +++ b/vendor/symfony/http-foundation/HeaderBag.php @@ -0,0 +1,295 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * HeaderBag is a container for HTTP headers. + * + * @author Fabien Potencier + * + * @implements \IteratorAggregate> + */ +class HeaderBag implements \IteratorAggregate, \Countable +{ + protected const UPPER = '_ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + protected const LOWER = '-abcdefghijklmnopqrstuvwxyz'; + + /** + * @var array> + */ + protected $headers = []; + protected $cacheControl = []; + + public function __construct(array $headers = []) + { + foreach ($headers as $key => $values) { + $this->set($key, $values); + } + } + + /** + * Returns the headers as a string. + * + * @return string + */ + public function __toString() + { + if (!$headers = $this->all()) { + return ''; + } + + ksort($headers); + $max = max(array_map('strlen', array_keys($headers))) + 1; + $content = ''; + foreach ($headers as $name => $values) { + $name = ucwords($name, '-'); + foreach ($values as $value) { + $content .= sprintf("%-{$max}s %s\r\n", $name.':', $value); + } + } + + return $content; + } + + /** + * Returns the headers. + * + * @param string|null $key The name of the headers to return or null to get them all + * + * @return array>|array + */ + public function all(string $key = null) + { + if (null !== $key) { + return $this->headers[strtr($key, self::UPPER, self::LOWER)] ?? []; + } + + return $this->headers; + } + + /** + * Returns the parameter keys. + * + * @return string[] + */ + public function keys() + { + return array_keys($this->all()); + } + + /** + * Replaces the current HTTP headers by a new set. + */ + public function replace(array $headers = []) + { + $this->headers = []; + $this->add($headers); + } + + /** + * Adds new headers the current HTTP headers set. + */ + public function add(array $headers) + { + foreach ($headers as $key => $values) { + $this->set($key, $values); + } + } + + /** + * Returns the first header by name or the default one. + * + * @return string|null + */ + public function get(string $key, string $default = null) + { + $headers = $this->all($key); + + if (!$headers) { + return $default; + } + + if (null === $headers[0]) { + return null; + } + + return (string) $headers[0]; + } + + /** + * Sets a header by name. + * + * @param string|string[]|null $values The value or an array of values + * @param bool $replace Whether to replace the actual value or not (true by default) + */ + public function set(string $key, $values, bool $replace = true) + { + $key = strtr($key, self::UPPER, self::LOWER); + + if (\is_array($values)) { + $values = array_values($values); + + if (true === $replace || !isset($this->headers[$key])) { + $this->headers[$key] = $values; + } else { + $this->headers[$key] = array_merge($this->headers[$key], $values); + } + } else { + if (true === $replace || !isset($this->headers[$key])) { + $this->headers[$key] = [$values]; + } else { + $this->headers[$key][] = $values; + } + } + + if ('cache-control' === $key) { + $this->cacheControl = $this->parseCacheControl(implode(', ', $this->headers[$key])); + } + } + + /** + * Returns true if the HTTP header is defined. + * + * @return bool + */ + public function has(string $key) + { + return \array_key_exists(strtr($key, self::UPPER, self::LOWER), $this->all()); + } + + /** + * Returns true if the given HTTP header contains the given value. + * + * @return bool + */ + public function contains(string $key, string $value) + { + return \in_array($value, $this->all($key)); + } + + /** + * Removes a header. + */ + public function remove(string $key) + { + $key = strtr($key, self::UPPER, self::LOWER); + + unset($this->headers[$key]); + + if ('cache-control' === $key) { + $this->cacheControl = []; + } + } + + /** + * Returns the HTTP header value converted to a date. + * + * @return \DateTimeInterface|null + * + * @throws \RuntimeException When the HTTP header is not parseable + */ + public function getDate(string $key, \DateTime $default = null) + { + if (null === $value = $this->get($key)) { + return $default; + } + + if (false === $date = \DateTime::createFromFormat(\DATE_RFC2822, $value)) { + throw new \RuntimeException(sprintf('The "%s" HTTP header is not parseable (%s).', $key, $value)); + } + + return $date; + } + + /** + * Adds a custom Cache-Control directive. + * + * @param bool|string $value The Cache-Control directive value + */ + public function addCacheControlDirective(string $key, $value = true) + { + $this->cacheControl[$key] = $value; + + $this->set('Cache-Control', $this->getCacheControlHeader()); + } + + /** + * Returns true if the Cache-Control directive is defined. + * + * @return bool + */ + public function hasCacheControlDirective(string $key) + { + return \array_key_exists($key, $this->cacheControl); + } + + /** + * Returns a Cache-Control directive value by name. + * + * @return bool|string|null + */ + public function getCacheControlDirective(string $key) + { + return $this->cacheControl[$key] ?? null; + } + + /** + * Removes a Cache-Control directive. + */ + public function removeCacheControlDirective(string $key) + { + unset($this->cacheControl[$key]); + + $this->set('Cache-Control', $this->getCacheControlHeader()); + } + + /** + * Returns an iterator for headers. + * + * @return \ArrayIterator> + */ + #[\ReturnTypeWillChange] + public function getIterator() + { + return new \ArrayIterator($this->headers); + } + + /** + * Returns the number of headers. + * + * @return int + */ + #[\ReturnTypeWillChange] + public function count() + { + return \count($this->headers); + } + + protected function getCacheControlHeader() + { + ksort($this->cacheControl); + + return HeaderUtils::toString($this->cacheControl, ','); + } + + /** + * Parses a Cache-Control HTTP header. + * + * @return array + */ + protected function parseCacheControl(string $header) + { + $parts = HeaderUtils::split($header, ',='); + + return HeaderUtils::combine($parts); + } +} diff --git a/vendor/symfony/http-foundation/HeaderUtils.php b/vendor/symfony/http-foundation/HeaderUtils.php new file mode 100644 index 0000000..8f1b8bf --- /dev/null +++ b/vendor/symfony/http-foundation/HeaderUtils.php @@ -0,0 +1,295 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * HTTP header utility functions. + * + * @author Christian Schmidt + */ +class HeaderUtils +{ + public const DISPOSITION_ATTACHMENT = 'attachment'; + public const DISPOSITION_INLINE = 'inline'; + + /** + * This class should not be instantiated. + */ + private function __construct() + { + } + + /** + * Splits an HTTP header by one or more separators. + * + * Example: + * + * HeaderUtils::split("da, en-gb;q=0.8", ",;") + * // => ['da'], ['en-gb', 'q=0.8']] + * + * @param string $separators List of characters to split on, ordered by + * precedence, e.g. ",", ";=", or ",;=" + * + * @return array Nested array with as many levels as there are characters in + * $separators + */ + public static function split(string $header, string $separators): array + { + $quotedSeparators = preg_quote($separators, '/'); + + preg_match_all(' + / + (?!\s) + (?: + # quoted-string + "(?:[^"\\\\]|\\\\.)*(?:"|\\\\|$) + | + # token + [^"'.$quotedSeparators.']+ + )+ + (?['.$quotedSeparators.']) + \s* + /x', trim($header), $matches, \PREG_SET_ORDER); + + return self::groupParts($matches, $separators); + } + + /** + * Combines an array of arrays into one associative array. + * + * Each of the nested arrays should have one or two elements. The first + * value will be used as the keys in the associative array, and the second + * will be used as the values, or true if the nested array only contains one + * element. Array keys are lowercased. + * + * Example: + * + * HeaderUtils::combine([["foo", "abc"], ["bar"]]) + * // => ["foo" => "abc", "bar" => true] + */ + public static function combine(array $parts): array + { + $assoc = []; + foreach ($parts as $part) { + $name = strtolower($part[0]); + $value = $part[1] ?? true; + $assoc[$name] = $value; + } + + return $assoc; + } + + /** + * Joins an associative array into a string for use in an HTTP header. + * + * The key and value of each entry are joined with "=", and all entries + * are joined with the specified separator and an additional space (for + * readability). Values are quoted if necessary. + * + * Example: + * + * HeaderUtils::toString(["foo" => "abc", "bar" => true, "baz" => "a b c"], ",") + * // => 'foo=abc, bar, baz="a b c"' + */ + public static function toString(array $assoc, string $separator): string + { + $parts = []; + foreach ($assoc as $name => $value) { + if (true === $value) { + $parts[] = $name; + } else { + $parts[] = $name.'='.self::quote($value); + } + } + + return implode($separator.' ', $parts); + } + + /** + * Encodes a string as a quoted string, if necessary. + * + * If a string contains characters not allowed by the "token" construct in + * the HTTP specification, it is backslash-escaped and enclosed in quotes + * to match the "quoted-string" construct. + */ + public static function quote(string $s): string + { + if (preg_match('/^[a-z0-9!#$%&\'*.^_`|~-]+$/i', $s)) { + return $s; + } + + return '"'.addcslashes($s, '"\\"').'"'; + } + + /** + * Decodes a quoted string. + * + * If passed an unquoted string that matches the "token" construct (as + * defined in the HTTP specification), it is passed through verbatimly. + */ + public static function unquote(string $s): string + { + return preg_replace('/\\\\(.)|"/', '$1', $s); + } + + /** + * Generates an HTTP Content-Disposition field-value. + * + * @param string $disposition One of "inline" or "attachment" + * @param string $filename A unicode string + * @param string $filenameFallback A string containing only ASCII characters that + * is semantically equivalent to $filename. If the filename is already ASCII, + * it can be omitted, or just copied from $filename + * + * @return string + * + * @throws \InvalidArgumentException + * + * @see RFC 6266 + */ + public static function makeDisposition(string $disposition, string $filename, string $filenameFallback = ''): string + { + if (!\in_array($disposition, [self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE])) { + throw new \InvalidArgumentException(sprintf('The disposition must be either "%s" or "%s".', self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE)); + } + + if ('' === $filenameFallback) { + $filenameFallback = $filename; + } + + // filenameFallback is not ASCII. + if (!preg_match('/^[\x20-\x7e]*$/', $filenameFallback)) { + throw new \InvalidArgumentException('The filename fallback must only contain ASCII characters.'); + } + + // percent characters aren't safe in fallback. + if (str_contains($filenameFallback, '%')) { + throw new \InvalidArgumentException('The filename fallback cannot contain the "%" character.'); + } + + // path separators aren't allowed in either. + if (str_contains($filename, '/') || str_contains($filename, '\\') || str_contains($filenameFallback, '/') || str_contains($filenameFallback, '\\')) { + throw new \InvalidArgumentException('The filename and the fallback cannot contain the "/" and "\\" characters.'); + } + + $params = ['filename' => $filenameFallback]; + if ($filename !== $filenameFallback) { + $params['filename*'] = "utf-8''".rawurlencode($filename); + } + + return $disposition.'; '.self::toString($params, ';'); + } + + /** + * Like parse_str(), but preserves dots in variable names. + */ + public static function parseQuery(string $query, bool $ignoreBrackets = false, string $separator = '&'): array + { + $q = []; + + foreach (explode($separator, $query) as $v) { + if (false !== $i = strpos($v, "\0")) { + $v = substr($v, 0, $i); + } + + if (false === $i = strpos($v, '=')) { + $k = urldecode($v); + $v = ''; + } else { + $k = urldecode(substr($v, 0, $i)); + $v = substr($v, $i); + } + + if (false !== $i = strpos($k, "\0")) { + $k = substr($k, 0, $i); + } + + $k = ltrim($k, ' '); + + if ($ignoreBrackets) { + $q[$k][] = urldecode(substr($v, 1)); + + continue; + } + + if (false === $i = strpos($k, '[')) { + $q[] = bin2hex($k).$v; + } else { + $q[] = bin2hex(substr($k, 0, $i)).rawurlencode(substr($k, $i)).$v; + } + } + + if ($ignoreBrackets) { + return $q; + } + + parse_str(implode('&', $q), $q); + + $query = []; + + foreach ($q as $k => $v) { + if (false !== $i = strpos($k, '_')) { + $query[substr_replace($k, hex2bin(substr($k, 0, $i)).'[', 0, 1 + $i)] = $v; + } else { + $query[hex2bin($k)] = $v; + } + } + + return $query; + } + + private static function groupParts(array $matches, string $separators, bool $first = true): array + { + $separator = $separators[0]; + $partSeparators = substr($separators, 1); + + $i = 0; + $partMatches = []; + $previousMatchWasSeparator = false; + foreach ($matches as $match) { + if (!$first && $previousMatchWasSeparator && isset($match['separator']) && $match['separator'] === $separator) { + $previousMatchWasSeparator = true; + $partMatches[$i][] = $match; + } elseif (isset($match['separator']) && $match['separator'] === $separator) { + $previousMatchWasSeparator = true; + ++$i; + } else { + $previousMatchWasSeparator = false; + $partMatches[$i][] = $match; + } + } + + $parts = []; + if ($partSeparators) { + foreach ($partMatches as $matches) { + $parts[] = self::groupParts($matches, $partSeparators, false); + } + } else { + foreach ($partMatches as $matches) { + $parts[] = self::unquote($matches[0][0]); + } + + if (!$first && 2 < \count($parts)) { + $parts = [ + $parts[0], + implode($separator, \array_slice($parts, 1)), + ]; + } + } + + return $parts; + } +} diff --git a/vendor/symfony/http-foundation/InputBag.php b/vendor/symfony/http-foundation/InputBag.php new file mode 100644 index 0000000..b36001d --- /dev/null +++ b/vendor/symfony/http-foundation/InputBag.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\HttpFoundation\Exception\BadRequestException; + +/** + * InputBag is a container for user input values such as $_GET, $_POST, $_REQUEST, and $_COOKIE. + * + * @author Saif Eddin Gmati + */ +final class InputBag extends ParameterBag +{ + /** + * Returns a scalar input value by name. + * + * @param string|int|float|bool|null $default The default value if the input key does not exist + * + * @return string|int|float|bool|null + */ + public function get(string $key, $default = null) + { + if (null !== $default && !is_scalar($default) && !(\is_object($default) && method_exists($default, '__toString'))) { + trigger_deprecation('symfony/http-foundation', '5.1', 'Passing a non-scalar value as 2nd argument to "%s()" is deprecated, pass a scalar or null instead.', __METHOD__); + } + + $value = parent::get($key, $this); + + if (null !== $value && $this !== $value && !is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { + trigger_deprecation('symfony/http-foundation', '5.1', 'Retrieving a non-string value from "%s()" is deprecated, and will throw a "%s" exception in Symfony 6.0, use "%s::all($key)" instead.', __METHOD__, BadRequestException::class, __CLASS__); + } + + return $this === $value ? $default : $value; + } + + /** + * {@inheritdoc} + */ + public function all(string $key = null): array + { + return parent::all($key); + } + + /** + * Replaces the current input values by a new set. + */ + public function replace(array $inputs = []) + { + $this->parameters = []; + $this->add($inputs); + } + + /** + * Adds input values. + */ + public function add(array $inputs = []) + { + foreach ($inputs as $input => $value) { + $this->set($input, $value); + } + } + + /** + * Sets an input by name. + * + * @param string|int|float|bool|array|null $value + */ + public function set(string $key, $value) + { + if (null !== $value && !is_scalar($value) && !\is_array($value) && !method_exists($value, '__toString')) { + trigger_deprecation('symfony/http-foundation', '5.1', 'Passing "%s" as a 2nd Argument to "%s()" is deprecated, pass a scalar, array, or null instead.', get_debug_type($value), __METHOD__); + } + + $this->parameters[$key] = $value; + } + + /** + * {@inheritdoc} + */ + public function filter(string $key, $default = null, int $filter = \FILTER_DEFAULT, $options = []) + { + $value = $this->has($key) ? $this->all()[$key] : $default; + + // Always turn $options into an array - this allows filter_var option shortcuts. + if (!\is_array($options) && $options) { + $options = ['flags' => $options]; + } + + if (\is_array($value) && !(($options['flags'] ?? 0) & (\FILTER_REQUIRE_ARRAY | \FILTER_FORCE_ARRAY))) { + trigger_deprecation('symfony/http-foundation', '5.1', 'Filtering an array value with "%s()" without passing the FILTER_REQUIRE_ARRAY or FILTER_FORCE_ARRAY flag is deprecated', __METHOD__); + + if (!isset($options['flags'])) { + $options['flags'] = \FILTER_REQUIRE_ARRAY; + } + } + + if ((\FILTER_CALLBACK & $filter) && !(($options['options'] ?? null) instanceof \Closure)) { + trigger_deprecation('symfony/http-foundation', '5.2', 'Not passing a Closure together with FILTER_CALLBACK to "%s()" is deprecated. Wrap your filter in a closure instead.', __METHOD__); + // throw new \InvalidArgumentException(sprintf('A Closure must be passed to "%s()" when FILTER_CALLBACK is used, "%s" given.', __METHOD__, get_debug_type($options['options'] ?? null))); + } + + return filter_var($value, $filter, $options); + } +} diff --git a/vendor/symfony/http-foundation/IpUtils.php b/vendor/symfony/http-foundation/IpUtils.php new file mode 100644 index 0000000..24111f1 --- /dev/null +++ b/vendor/symfony/http-foundation/IpUtils.php @@ -0,0 +1,203 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Http utility functions. + * + * @author Fabien Potencier + */ +class IpUtils +{ + private static $checkedIps = []; + + /** + * This class should not be instantiated. + */ + private function __construct() + { + } + + /** + * Checks if an IPv4 or IPv6 address is contained in the list of given IPs or subnets. + * + * @param string|array $ips List of IPs or subnets (can be a string if only a single one) + * + * @return bool + */ + public static function checkIp(?string $requestIp, $ips) + { + if (null === $requestIp) { + trigger_deprecation('symfony/http-foundation', '5.4', 'Passing null as $requestIp to "%s()" is deprecated, pass an empty string instead.', __METHOD__); + + return false; + } + + if (!\is_array($ips)) { + $ips = [$ips]; + } + + $method = substr_count($requestIp, ':') > 1 ? 'checkIp6' : 'checkIp4'; + + foreach ($ips as $ip) { + if (self::$method($requestIp, $ip)) { + return true; + } + } + + return false; + } + + /** + * Compares two IPv4 addresses. + * In case a subnet is given, it checks if it contains the request IP. + * + * @param string $ip IPv4 address or subnet in CIDR notation + * + * @return bool Whether the request IP matches the IP, or whether the request IP is within the CIDR subnet + */ + public static function checkIp4(?string $requestIp, string $ip) + { + if (null === $requestIp) { + trigger_deprecation('symfony/http-foundation', '5.4', 'Passing null as $requestIp to "%s()" is deprecated, pass an empty string instead.', __METHOD__); + + return false; + } + + $cacheKey = $requestIp.'-'.$ip; + if (isset(self::$checkedIps[$cacheKey])) { + return self::$checkedIps[$cacheKey]; + } + + if (!filter_var($requestIp, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4)) { + return self::$checkedIps[$cacheKey] = false; + } + + if (str_contains($ip, '/')) { + [$address, $netmask] = explode('/', $ip, 2); + + if ('0' === $netmask) { + return self::$checkedIps[$cacheKey] = filter_var($address, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4); + } + + if ($netmask < 0 || $netmask > 32) { + return self::$checkedIps[$cacheKey] = false; + } + } else { + $address = $ip; + $netmask = 32; + } + + if (false === ip2long($address)) { + return self::$checkedIps[$cacheKey] = false; + } + + return self::$checkedIps[$cacheKey] = 0 === substr_compare(sprintf('%032b', ip2long($requestIp)), sprintf('%032b', ip2long($address)), 0, $netmask); + } + + /** + * Compares two IPv6 addresses. + * In case a subnet is given, it checks if it contains the request IP. + * + * @author David Soria Parra + * + * @see https://github.com/dsp/v6tools + * + * @param string $ip IPv6 address or subnet in CIDR notation + * + * @return bool + * + * @throws \RuntimeException When IPV6 support is not enabled + */ + public static function checkIp6(?string $requestIp, string $ip) + { + if (null === $requestIp) { + trigger_deprecation('symfony/http-foundation', '5.4', 'Passing null as $requestIp to "%s()" is deprecated, pass an empty string instead.', __METHOD__); + + return false; + } + + $cacheKey = $requestIp.'-'.$ip; + if (isset(self::$checkedIps[$cacheKey])) { + return self::$checkedIps[$cacheKey]; + } + + if (!((\extension_loaded('sockets') && \defined('AF_INET6')) || @inet_pton('::1'))) { + throw new \RuntimeException('Unable to check Ipv6. Check that PHP was not compiled with option "disable-ipv6".'); + } + + if (str_contains($ip, '/')) { + [$address, $netmask] = explode('/', $ip, 2); + + if ('0' === $netmask) { + return (bool) unpack('n*', @inet_pton($address)); + } + + if ($netmask < 1 || $netmask > 128) { + return self::$checkedIps[$cacheKey] = false; + } + } else { + $address = $ip; + $netmask = 128; + } + + $bytesAddr = unpack('n*', @inet_pton($address)); + $bytesTest = unpack('n*', @inet_pton($requestIp)); + + if (!$bytesAddr || !$bytesTest) { + return self::$checkedIps[$cacheKey] = false; + } + + for ($i = 1, $ceil = ceil($netmask / 16); $i <= $ceil; ++$i) { + $left = $netmask - 16 * ($i - 1); + $left = ($left <= 16) ? $left : 16; + $mask = ~(0xFFFF >> $left) & 0xFFFF; + if (($bytesAddr[$i] & $mask) != ($bytesTest[$i] & $mask)) { + return self::$checkedIps[$cacheKey] = false; + } + } + + return self::$checkedIps[$cacheKey] = true; + } + + /** + * Anonymizes an IP/IPv6. + * + * Removes the last byte for v4 and the last 8 bytes for v6 IPs + */ + public static function anonymize(string $ip): string + { + $wrappedIPv6 = false; + if ('[' === substr($ip, 0, 1) && ']' === substr($ip, -1, 1)) { + $wrappedIPv6 = true; + $ip = substr($ip, 1, -1); + } + + $packedAddress = inet_pton($ip); + if (4 === \strlen($packedAddress)) { + $mask = '255.255.255.0'; + } elseif ($ip === inet_ntop($packedAddress & inet_pton('::ffff:ffff:ffff'))) { + $mask = '::ffff:ffff:ff00'; + } elseif ($ip === inet_ntop($packedAddress & inet_pton('::ffff:ffff'))) { + $mask = '::ffff:ff00'; + } else { + $mask = 'ffff:ffff:ffff:ffff:0000:0000:0000:0000'; + } + $ip = inet_ntop($packedAddress & inet_pton($mask)); + + if ($wrappedIPv6) { + $ip = '['.$ip.']'; + } + + return $ip; + } +} diff --git a/vendor/symfony/http-foundation/JsonResponse.php b/vendor/symfony/http-foundation/JsonResponse.php new file mode 100644 index 0000000..501a638 --- /dev/null +++ b/vendor/symfony/http-foundation/JsonResponse.php @@ -0,0 +1,221 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Response represents an HTTP response in JSON format. + * + * Note that this class does not force the returned JSON content to be an + * object. It is however recommended that you do return an object as it + * protects yourself against XSSI and JSON-JavaScript Hijacking. + * + * @see https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/AJAX_Security_Cheat_Sheet.md#always-return-json-with-an-object-on-the-outside + * + * @author Igor Wiedler + */ +class JsonResponse extends Response +{ + protected $data; + protected $callback; + + // Encode <, >, ', &, and " characters in the JSON, making it also safe to be embedded into HTML. + // 15 === JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT + public const DEFAULT_ENCODING_OPTIONS = 15; + + protected $encodingOptions = self::DEFAULT_ENCODING_OPTIONS; + + /** + * @param mixed $data The response data + * @param int $status The response status code + * @param array $headers An array of response headers + * @param bool $json If the data is already a JSON string + */ + public function __construct($data = null, int $status = 200, array $headers = [], bool $json = false) + { + parent::__construct('', $status, $headers); + + if ($json && !\is_string($data) && !is_numeric($data) && !\is_callable([$data, '__toString'])) { + throw new \TypeError(sprintf('"%s": If $json is set to true, argument $data must be a string or object implementing __toString(), "%s" given.', __METHOD__, get_debug_type($data))); + } + + if (null === $data) { + $data = new \ArrayObject(); + } + + $json ? $this->setJson($data) : $this->setData($data); + } + + /** + * Factory method for chainability. + * + * Example: + * + * return JsonResponse::create(['key' => 'value']) + * ->setSharedMaxAge(300); + * + * @param mixed $data The JSON response data + * @param int $status The response status code + * @param array $headers An array of response headers + * + * @return static + * + * @deprecated since Symfony 5.1, use __construct() instead. + */ + public static function create($data = null, int $status = 200, array $headers = []) + { + trigger_deprecation('symfony/http-foundation', '5.1', 'The "%s()" method is deprecated, use "new %s()" instead.', __METHOD__, static::class); + + return new static($data, $status, $headers); + } + + /** + * Factory method for chainability. + * + * Example: + * + * return JsonResponse::fromJsonString('{"key": "value"}') + * ->setSharedMaxAge(300); + * + * @param string $data The JSON response string + * @param int $status The response status code + * @param array $headers An array of response headers + * + * @return static + */ + public static function fromJsonString(string $data, int $status = 200, array $headers = []) + { + return new static($data, $status, $headers, true); + } + + /** + * Sets the JSONP callback. + * + * @param string|null $callback The JSONP callback or null to use none + * + * @return $this + * + * @throws \InvalidArgumentException When the callback name is not valid + */ + public function setCallback(string $callback = null) + { + if (null !== $callback) { + // partially taken from https://geekality.net/2011/08/03/valid-javascript-identifier/ + // partially taken from https://github.com/willdurand/JsonpCallbackValidator + // JsonpCallbackValidator is released under the MIT License. See https://github.com/willdurand/JsonpCallbackValidator/blob/v1.1.0/LICENSE for details. + // (c) William Durand + $pattern = '/^[$_\p{L}][$_\p{L}\p{Mn}\p{Mc}\p{Nd}\p{Pc}\x{200C}\x{200D}]*(?:\[(?:"(?:\\\.|[^"\\\])*"|\'(?:\\\.|[^\'\\\])*\'|\d+)\])*?$/u'; + $reserved = [ + 'break', 'do', 'instanceof', 'typeof', 'case', 'else', 'new', 'var', 'catch', 'finally', 'return', 'void', 'continue', 'for', 'switch', 'while', + 'debugger', 'function', 'this', 'with', 'default', 'if', 'throw', 'delete', 'in', 'try', 'class', 'enum', 'extends', 'super', 'const', 'export', + 'import', 'implements', 'let', 'private', 'public', 'yield', 'interface', 'package', 'protected', 'static', 'null', 'true', 'false', + ]; + $parts = explode('.', $callback); + foreach ($parts as $part) { + if (!preg_match($pattern, $part) || \in_array($part, $reserved, true)) { + throw new \InvalidArgumentException('The callback name is not valid.'); + } + } + } + + $this->callback = $callback; + + return $this->update(); + } + + /** + * Sets a raw string containing a JSON document to be sent. + * + * @return $this + */ + public function setJson(string $json) + { + $this->data = $json; + + return $this->update(); + } + + /** + * Sets the data to be sent as JSON. + * + * @param mixed $data + * + * @return $this + * + * @throws \InvalidArgumentException + */ + public function setData($data = []) + { + try { + $data = json_encode($data, $this->encodingOptions); + } catch (\Exception $e) { + if ('Exception' === \get_class($e) && str_starts_with($e->getMessage(), 'Failed calling ')) { + throw $e->getPrevious() ?: $e; + } + throw $e; + } + + if (\PHP_VERSION_ID >= 70300 && (\JSON_THROW_ON_ERROR & $this->encodingOptions)) { + return $this->setJson($data); + } + + if (\JSON_ERROR_NONE !== json_last_error()) { + throw new \InvalidArgumentException(json_last_error_msg()); + } + + return $this->setJson($data); + } + + /** + * Returns options used while encoding data to JSON. + * + * @return int + */ + public function getEncodingOptions() + { + return $this->encodingOptions; + } + + /** + * Sets options used while encoding data to JSON. + * + * @return $this + */ + public function setEncodingOptions(int $encodingOptions) + { + $this->encodingOptions = $encodingOptions; + + return $this->setData(json_decode($this->data)); + } + + /** + * Updates the content and headers according to the JSON data and callback. + * + * @return $this + */ + protected function update() + { + if (null !== $this->callback) { + // Not using application/javascript for compatibility reasons with older browsers. + $this->headers->set('Content-Type', 'text/javascript'); + + return $this->setContent(sprintf('/**/%s(%s);', $this->callback, $this->data)); + } + + // Only set the header when there is none or when it equals 'text/javascript' (from a previous update with callback) + // in order to not overwrite a custom definition. + if (!$this->headers->has('Content-Type') || 'text/javascript' === $this->headers->get('Content-Type')) { + $this->headers->set('Content-Type', 'application/json'); + } + + return $this->setContent($this->data); + } +} diff --git a/vendor/symfony/http-foundation/LICENSE b/vendor/symfony/http-foundation/LICENSE new file mode 100644 index 0000000..9ff2d0d --- /dev/null +++ b/vendor/symfony/http-foundation/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2021 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/http-foundation/ParameterBag.php b/vendor/symfony/http-foundation/ParameterBag.php new file mode 100644 index 0000000..7d051ab --- /dev/null +++ b/vendor/symfony/http-foundation/ParameterBag.php @@ -0,0 +1,228 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\HttpFoundation\Exception\BadRequestException; + +/** + * ParameterBag is a container for key/value pairs. + * + * @author Fabien Potencier + * + * @implements \IteratorAggregate + */ +class ParameterBag implements \IteratorAggregate, \Countable +{ + /** + * Parameter storage. + */ + protected $parameters; + + public function __construct(array $parameters = []) + { + $this->parameters = $parameters; + } + + /** + * Returns the parameters. + * + * @param string|null $key The name of the parameter to return or null to get them all + * + * @return array + */ + public function all(/*string $key = null*/) + { + $key = \func_num_args() > 0 ? func_get_arg(0) : null; + + if (null === $key) { + return $this->parameters; + } + + if (!\is_array($value = $this->parameters[$key] ?? [])) { + throw new BadRequestException(sprintf('Unexpected value for parameter "%s": expecting "array", got "%s".', $key, get_debug_type($value))); + } + + return $value; + } + + /** + * Returns the parameter keys. + * + * @return array + */ + public function keys() + { + return array_keys($this->parameters); + } + + /** + * Replaces the current parameters by a new set. + */ + public function replace(array $parameters = []) + { + $this->parameters = $parameters; + } + + /** + * Adds parameters. + */ + public function add(array $parameters = []) + { + $this->parameters = array_replace($this->parameters, $parameters); + } + + /** + * Returns a parameter by name. + * + * @param mixed $default The default value if the parameter key does not exist + * + * @return mixed + */ + public function get(string $key, $default = null) + { + return \array_key_exists($key, $this->parameters) ? $this->parameters[$key] : $default; + } + + /** + * Sets a parameter by name. + * + * @param mixed $value The value + */ + public function set(string $key, $value) + { + $this->parameters[$key] = $value; + } + + /** + * Returns true if the parameter is defined. + * + * @return bool + */ + public function has(string $key) + { + return \array_key_exists($key, $this->parameters); + } + + /** + * Removes a parameter. + */ + public function remove(string $key) + { + unset($this->parameters[$key]); + } + + /** + * Returns the alphabetic characters of the parameter value. + * + * @return string + */ + public function getAlpha(string $key, string $default = '') + { + return preg_replace('/[^[:alpha:]]/', '', $this->get($key, $default)); + } + + /** + * Returns the alphabetic characters and digits of the parameter value. + * + * @return string + */ + public function getAlnum(string $key, string $default = '') + { + return preg_replace('/[^[:alnum:]]/', '', $this->get($key, $default)); + } + + /** + * Returns the digits of the parameter value. + * + * @return string + */ + public function getDigits(string $key, string $default = '') + { + // we need to remove - and + because they're allowed in the filter + return str_replace(['-', '+'], '', $this->filter($key, $default, \FILTER_SANITIZE_NUMBER_INT)); + } + + /** + * Returns the parameter value converted to integer. + * + * @return int + */ + public function getInt(string $key, int $default = 0) + { + return (int) $this->get($key, $default); + } + + /** + * Returns the parameter value converted to boolean. + * + * @return bool + */ + public function getBoolean(string $key, bool $default = false) + { + return $this->filter($key, $default, \FILTER_VALIDATE_BOOLEAN); + } + + /** + * Filter key. + * + * @param mixed $default Default = null + * @param int $filter FILTER_* constant + * @param mixed $options Filter options + * + * @see https://php.net/filter-var + * + * @return mixed + */ + public function filter(string $key, $default = null, int $filter = \FILTER_DEFAULT, $options = []) + { + $value = $this->get($key, $default); + + // Always turn $options into an array - this allows filter_var option shortcuts. + if (!\is_array($options) && $options) { + $options = ['flags' => $options]; + } + + // Add a convenience check for arrays. + if (\is_array($value) && !isset($options['flags'])) { + $options['flags'] = \FILTER_REQUIRE_ARRAY; + } + + if ((\FILTER_CALLBACK & $filter) && !(($options['options'] ?? null) instanceof \Closure)) { + trigger_deprecation('symfony/http-foundation', '5.2', 'Not passing a Closure together with FILTER_CALLBACK to "%s()" is deprecated. Wrap your filter in a closure instead.', __METHOD__); + // throw new \InvalidArgumentException(sprintf('A Closure must be passed to "%s()" when FILTER_CALLBACK is used, "%s" given.', __METHOD__, get_debug_type($options['options'] ?? null))); + } + + return filter_var($value, $filter, $options); + } + + /** + * Returns an iterator for parameters. + * + * @return \ArrayIterator + */ + #[\ReturnTypeWillChange] + public function getIterator() + { + return new \ArrayIterator($this->parameters); + } + + /** + * Returns the number of parameters. + * + * @return int + */ + #[\ReturnTypeWillChange] + public function count() + { + return \count($this->parameters); + } +} diff --git a/vendor/symfony/http-foundation/README.md b/vendor/symfony/http-foundation/README.md new file mode 100644 index 0000000..424f2c4 --- /dev/null +++ b/vendor/symfony/http-foundation/README.md @@ -0,0 +1,28 @@ +HttpFoundation Component +======================== + +The HttpFoundation component defines an object-oriented layer for the HTTP +specification. + +Sponsor +------- + +The HttpFoundation component for Symfony 5.4/6.0 is [backed][1] by [Laravel][2]. + +Laravel is a PHP web development framework that is passionate about maximum developer +happiness. Laravel is built using a variety of bespoke and Symfony based components. + +Help Symfony by [sponsoring][3] its development! + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/http_foundation.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) + +[1]: https://symfony.com/backers +[2]: https://laravel.com/ +[3]: https://symfony.com/sponsor diff --git a/vendor/symfony/http-foundation/RateLimiter/AbstractRequestRateLimiter.php b/vendor/symfony/http-foundation/RateLimiter/AbstractRequestRateLimiter.php new file mode 100644 index 0000000..c91d614 --- /dev/null +++ b/vendor/symfony/http-foundation/RateLimiter/AbstractRequestRateLimiter.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\RateLimiter; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\RateLimiter\LimiterInterface; +use Symfony\Component\RateLimiter\Policy\NoLimiter; +use Symfony\Component\RateLimiter\RateLimit; + +/** + * An implementation of RequestRateLimiterInterface that + * fits most use-cases. + * + * @author Wouter de Jong + */ +abstract class AbstractRequestRateLimiter implements RequestRateLimiterInterface +{ + public function consume(Request $request): RateLimit + { + $limiters = $this->getLimiters($request); + if (0 === \count($limiters)) { + $limiters = [new NoLimiter()]; + } + + $minimalRateLimit = null; + foreach ($limiters as $limiter) { + $rateLimit = $limiter->consume(1); + + if (null === $minimalRateLimit || $rateLimit->getRemainingTokens() < $minimalRateLimit->getRemainingTokens()) { + $minimalRateLimit = $rateLimit; + } + } + + return $minimalRateLimit; + } + + public function reset(Request $request): void + { + foreach ($this->getLimiters($request) as $limiter) { + $limiter->reset(); + } + } + + /** + * @return LimiterInterface[] a set of limiters using keys extracted from the request + */ + abstract protected function getLimiters(Request $request): array; +} diff --git a/vendor/symfony/http-foundation/RateLimiter/RequestRateLimiterInterface.php b/vendor/symfony/http-foundation/RateLimiter/RequestRateLimiterInterface.php new file mode 100644 index 0000000..4c87a40 --- /dev/null +++ b/vendor/symfony/http-foundation/RateLimiter/RequestRateLimiterInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\RateLimiter; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\RateLimiter\RateLimit; + +/** + * A special type of limiter that deals with requests. + * + * This allows to limit on different types of information + * from the requests. + * + * @author Wouter de Jong + */ +interface RequestRateLimiterInterface +{ + public function consume(Request $request): RateLimit; + + public function reset(Request $request): void; +} diff --git a/vendor/symfony/http-foundation/RedirectResponse.php b/vendor/symfony/http-foundation/RedirectResponse.php new file mode 100644 index 0000000..2103280 --- /dev/null +++ b/vendor/symfony/http-foundation/RedirectResponse.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * RedirectResponse represents an HTTP response doing a redirect. + * + * @author Fabien Potencier + */ +class RedirectResponse extends Response +{ + protected $targetUrl; + + /** + * Creates a redirect response so that it conforms to the rules defined for a redirect status code. + * + * @param string $url The URL to redirect to. The URL should be a full URL, with schema etc., + * but practically every browser redirects on paths only as well + * @param int $status The status code (302 by default) + * @param array $headers The headers (Location is always set to the given URL) + * + * @throws \InvalidArgumentException + * + * @see https://tools.ietf.org/html/rfc2616#section-10.3 + */ + public function __construct(string $url, int $status = 302, array $headers = []) + { + parent::__construct('', $status, $headers); + + $this->setTargetUrl($url); + + if (!$this->isRedirect()) { + throw new \InvalidArgumentException(sprintf('The HTTP status code is not a redirect ("%s" given).', $status)); + } + + if (301 == $status && !\array_key_exists('cache-control', array_change_key_case($headers, \CASE_LOWER))) { + $this->headers->remove('cache-control'); + } + } + + /** + * Factory method for chainability. + * + * @param string $url The URL to redirect to + * + * @return static + * + * @deprecated since Symfony 5.1, use __construct() instead. + */ + public static function create($url = '', int $status = 302, array $headers = []) + { + trigger_deprecation('symfony/http-foundation', '5.1', 'The "%s()" method is deprecated, use "new %s()" instead.', __METHOD__, static::class); + + return new static($url, $status, $headers); + } + + /** + * Returns the target URL. + * + * @return string + */ + public function getTargetUrl() + { + return $this->targetUrl; + } + + /** + * Sets the redirect target of this response. + * + * @return $this + * + * @throws \InvalidArgumentException + */ + public function setTargetUrl(string $url) + { + if ('' === $url) { + throw new \InvalidArgumentException('Cannot redirect to an empty URL.'); + } + + $this->targetUrl = $url; + + $this->setContent( + sprintf(' + + + + + + Redirecting to %1$s + + + Redirecting to %1$s. + +', htmlspecialchars($url, \ENT_QUOTES, 'UTF-8'))); + + $this->headers->set('Location', $url); + + return $this; + } +} diff --git a/vendor/symfony/http-foundation/Request.php b/vendor/symfony/http-foundation/Request.php new file mode 100644 index 0000000..d112b1f --- /dev/null +++ b/vendor/symfony/http-foundation/Request.php @@ -0,0 +1,2147 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException; +use Symfony\Component\HttpFoundation\Exception\JsonException; +use Symfony\Component\HttpFoundation\Exception\SessionNotFoundException; +use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException; +use Symfony\Component\HttpFoundation\Session\SessionInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(AcceptHeader::class); +class_exists(FileBag::class); +class_exists(HeaderBag::class); +class_exists(HeaderUtils::class); +class_exists(InputBag::class); +class_exists(ParameterBag::class); +class_exists(ServerBag::class); + +/** + * Request represents an HTTP request. + * + * The methods dealing with URL accept / return a raw path (% encoded): + * * getBasePath + * * getBaseUrl + * * getPathInfo + * * getRequestUri + * * getUri + * * getUriForPath + * + * @author Fabien Potencier + */ +class Request +{ + public const HEADER_FORWARDED = 0b000001; // When using RFC 7239 + public const HEADER_X_FORWARDED_FOR = 0b000010; + public const HEADER_X_FORWARDED_HOST = 0b000100; + public const HEADER_X_FORWARDED_PROTO = 0b001000; + public const HEADER_X_FORWARDED_PORT = 0b010000; + public const HEADER_X_FORWARDED_PREFIX = 0b100000; + + /** @deprecated since Symfony 5.2, use either "HEADER_X_FORWARDED_FOR | HEADER_X_FORWARDED_HOST | HEADER_X_FORWARDED_PORT | HEADER_X_FORWARDED_PROTO" or "HEADER_X_FORWARDED_AWS_ELB" or "HEADER_X_FORWARDED_TRAEFIK" constants instead. */ + public const HEADER_X_FORWARDED_ALL = 0b1011110; // All "X-Forwarded-*" headers sent by "usual" reverse proxy + public const HEADER_X_FORWARDED_AWS_ELB = 0b0011010; // AWS ELB doesn't send X-Forwarded-Host + public const HEADER_X_FORWARDED_TRAEFIK = 0b0111110; // All "X-Forwarded-*" headers sent by Traefik reverse proxy + + public const METHOD_HEAD = 'HEAD'; + public const METHOD_GET = 'GET'; + public const METHOD_POST = 'POST'; + public const METHOD_PUT = 'PUT'; + public const METHOD_PATCH = 'PATCH'; + public const METHOD_DELETE = 'DELETE'; + public const METHOD_PURGE = 'PURGE'; + public const METHOD_OPTIONS = 'OPTIONS'; + public const METHOD_TRACE = 'TRACE'; + public const METHOD_CONNECT = 'CONNECT'; + + /** + * @var string[] + */ + protected static $trustedProxies = []; + + /** + * @var string[] + */ + protected static $trustedHostPatterns = []; + + /** + * @var string[] + */ + protected static $trustedHosts = []; + + protected static $httpMethodParameterOverride = false; + + /** + * Custom parameters. + * + * @var ParameterBag + */ + public $attributes; + + /** + * Request body parameters ($_POST). + * + * @var InputBag + */ + public $request; + + /** + * Query string parameters ($_GET). + * + * @var InputBag + */ + public $query; + + /** + * Server and execution environment parameters ($_SERVER). + * + * @var ServerBag + */ + public $server; + + /** + * Uploaded files ($_FILES). + * + * @var FileBag + */ + public $files; + + /** + * Cookies ($_COOKIE). + * + * @var InputBag + */ + public $cookies; + + /** + * Headers (taken from the $_SERVER). + * + * @var HeaderBag + */ + public $headers; + + /** + * @var string|resource|false|null + */ + protected $content; + + /** + * @var array + */ + protected $languages; + + /** + * @var array + */ + protected $charsets; + + /** + * @var array + */ + protected $encodings; + + /** + * @var array + */ + protected $acceptableContentTypes; + + /** + * @var string + */ + protected $pathInfo; + + /** + * @var string + */ + protected $requestUri; + + /** + * @var string + */ + protected $baseUrl; + + /** + * @var string + */ + protected $basePath; + + /** + * @var string + */ + protected $method; + + /** + * @var string + */ + protected $format; + + /** + * @var SessionInterface|callable(): SessionInterface + */ + protected $session; + + /** + * @var string + */ + protected $locale; + + /** + * @var string + */ + protected $defaultLocale = 'en'; + + /** + * @var array + */ + protected static $formats; + + protected static $requestFactory; + + /** + * @var string|null + */ + private $preferredFormat; + private $isHostValid = true; + private $isForwardedValid = true; + + /** + * @var bool|null + */ + private $isSafeContentPreferred; + + private static $trustedHeaderSet = -1; + + private const FORWARDED_PARAMS = [ + self::HEADER_X_FORWARDED_FOR => 'for', + self::HEADER_X_FORWARDED_HOST => 'host', + self::HEADER_X_FORWARDED_PROTO => 'proto', + self::HEADER_X_FORWARDED_PORT => 'host', + ]; + + /** + * Names for headers that can be trusted when + * using trusted proxies. + * + * The FORWARDED header is the standard as of rfc7239. + * + * The other headers are non-standard, but widely used + * by popular reverse proxies (like Apache mod_proxy or Amazon EC2). + */ + private const TRUSTED_HEADERS = [ + self::HEADER_FORWARDED => 'FORWARDED', + self::HEADER_X_FORWARDED_FOR => 'X_FORWARDED_FOR', + self::HEADER_X_FORWARDED_HOST => 'X_FORWARDED_HOST', + self::HEADER_X_FORWARDED_PROTO => 'X_FORWARDED_PROTO', + self::HEADER_X_FORWARDED_PORT => 'X_FORWARDED_PORT', + self::HEADER_X_FORWARDED_PREFIX => 'X_FORWARDED_PREFIX', + ]; + + /** + * @param array $query The GET parameters + * @param array $request The POST parameters + * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...) + * @param array $cookies The COOKIE parameters + * @param array $files The FILES parameters + * @param array $server The SERVER parameters + * @param string|resource|null $content The raw body data + */ + public function __construct(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null) + { + $this->initialize($query, $request, $attributes, $cookies, $files, $server, $content); + } + + /** + * Sets the parameters for this request. + * + * This method also re-initializes all properties. + * + * @param array $query The GET parameters + * @param array $request The POST parameters + * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...) + * @param array $cookies The COOKIE parameters + * @param array $files The FILES parameters + * @param array $server The SERVER parameters + * @param string|resource|null $content The raw body data + */ + public function initialize(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null) + { + $this->request = new InputBag($request); + $this->query = new InputBag($query); + $this->attributes = new ParameterBag($attributes); + $this->cookies = new InputBag($cookies); + $this->files = new FileBag($files); + $this->server = new ServerBag($server); + $this->headers = new HeaderBag($this->server->getHeaders()); + + $this->content = $content; + $this->languages = null; + $this->charsets = null; + $this->encodings = null; + $this->acceptableContentTypes = null; + $this->pathInfo = null; + $this->requestUri = null; + $this->baseUrl = null; + $this->basePath = null; + $this->method = null; + $this->format = null; + } + + /** + * Creates a new request with values from PHP's super globals. + * + * @return static + */ + public static function createFromGlobals() + { + $request = self::createRequestFromFactory($_GET, $_POST, [], $_COOKIE, $_FILES, $_SERVER); + + if (str_starts_with($request->headers->get('CONTENT_TYPE', ''), 'application/x-www-form-urlencoded') + && \in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), ['PUT', 'DELETE', 'PATCH']) + ) { + parse_str($request->getContent(), $data); + $request->request = new InputBag($data); + } + + return $request; + } + + /** + * Creates a Request based on a given URI and configuration. + * + * The information contained in the URI always take precedence + * over the other information (server and parameters). + * + * @param string $uri The URI + * @param string $method The HTTP method + * @param array $parameters The query (GET) or request (POST) parameters + * @param array $cookies The request cookies ($_COOKIE) + * @param array $files The request files ($_FILES) + * @param array $server The server parameters ($_SERVER) + * @param string|resource|null $content The raw body data + * + * @return static + */ + public static function create(string $uri, string $method = 'GET', array $parameters = [], array $cookies = [], array $files = [], array $server = [], $content = null) + { + $server = array_replace([ + 'SERVER_NAME' => 'localhost', + 'SERVER_PORT' => 80, + 'HTTP_HOST' => 'localhost', + 'HTTP_USER_AGENT' => 'Symfony', + 'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'HTTP_ACCEPT_LANGUAGE' => 'en-us,en;q=0.5', + 'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', + 'REMOTE_ADDR' => '127.0.0.1', + 'SCRIPT_NAME' => '', + 'SCRIPT_FILENAME' => '', + 'SERVER_PROTOCOL' => 'HTTP/1.1', + 'REQUEST_TIME' => time(), + 'REQUEST_TIME_FLOAT' => microtime(true), + ], $server); + + $server['PATH_INFO'] = ''; + $server['REQUEST_METHOD'] = strtoupper($method); + + $components = parse_url($uri); + if (isset($components['host'])) { + $server['SERVER_NAME'] = $components['host']; + $server['HTTP_HOST'] = $components['host']; + } + + if (isset($components['scheme'])) { + if ('https' === $components['scheme']) { + $server['HTTPS'] = 'on'; + $server['SERVER_PORT'] = 443; + } else { + unset($server['HTTPS']); + $server['SERVER_PORT'] = 80; + } + } + + if (isset($components['port'])) { + $server['SERVER_PORT'] = $components['port']; + $server['HTTP_HOST'] .= ':'.$components['port']; + } + + if (isset($components['user'])) { + $server['PHP_AUTH_USER'] = $components['user']; + } + + if (isset($components['pass'])) { + $server['PHP_AUTH_PW'] = $components['pass']; + } + + if (!isset($components['path'])) { + $components['path'] = '/'; + } + + switch (strtoupper($method)) { + case 'POST': + case 'PUT': + case 'DELETE': + if (!isset($server['CONTENT_TYPE'])) { + $server['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'; + } + // no break + case 'PATCH': + $request = $parameters; + $query = []; + break; + default: + $request = []; + $query = $parameters; + break; + } + + $queryString = ''; + if (isset($components['query'])) { + parse_str(html_entity_decode($components['query']), $qs); + + if ($query) { + $query = array_replace($qs, $query); + $queryString = http_build_query($query, '', '&'); + } else { + $query = $qs; + $queryString = $components['query']; + } + } elseif ($query) { + $queryString = http_build_query($query, '', '&'); + } + + $server['REQUEST_URI'] = $components['path'].('' !== $queryString ? '?'.$queryString : ''); + $server['QUERY_STRING'] = $queryString; + + return self::createRequestFromFactory($query, $request, [], $cookies, $files, $server, $content); + } + + /** + * Sets a callable able to create a Request instance. + * + * This is mainly useful when you need to override the Request class + * to keep BC with an existing system. It should not be used for any + * other purpose. + */ + public static function setFactory(?callable $callable) + { + self::$requestFactory = $callable; + } + + /** + * Clones a request and overrides some of its parameters. + * + * @param array $query The GET parameters + * @param array $request The POST parameters + * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...) + * @param array $cookies The COOKIE parameters + * @param array $files The FILES parameters + * @param array $server The SERVER parameters + * + * @return static + */ + public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null) + { + $dup = clone $this; + if (null !== $query) { + $dup->query = new InputBag($query); + } + if (null !== $request) { + $dup->request = new InputBag($request); + } + if (null !== $attributes) { + $dup->attributes = new ParameterBag($attributes); + } + if (null !== $cookies) { + $dup->cookies = new InputBag($cookies); + } + if (null !== $files) { + $dup->files = new FileBag($files); + } + if (null !== $server) { + $dup->server = new ServerBag($server); + $dup->headers = new HeaderBag($dup->server->getHeaders()); + } + $dup->languages = null; + $dup->charsets = null; + $dup->encodings = null; + $dup->acceptableContentTypes = null; + $dup->pathInfo = null; + $dup->requestUri = null; + $dup->baseUrl = null; + $dup->basePath = null; + $dup->method = null; + $dup->format = null; + + if (!$dup->get('_format') && $this->get('_format')) { + $dup->attributes->set('_format', $this->get('_format')); + } + + if (!$dup->getRequestFormat(null)) { + $dup->setRequestFormat($this->getRequestFormat(null)); + } + + return $dup; + } + + /** + * Clones the current request. + * + * Note that the session is not cloned as duplicated requests + * are most of the time sub-requests of the main one. + */ + public function __clone() + { + $this->query = clone $this->query; + $this->request = clone $this->request; + $this->attributes = clone $this->attributes; + $this->cookies = clone $this->cookies; + $this->files = clone $this->files; + $this->server = clone $this->server; + $this->headers = clone $this->headers; + } + + /** + * Returns the request as a string. + * + * @return string + */ + public function __toString() + { + $content = $this->getContent(); + + $cookieHeader = ''; + $cookies = []; + + foreach ($this->cookies as $k => $v) { + $cookies[] = $k.'='.$v; + } + + if (!empty($cookies)) { + $cookieHeader = 'Cookie: '.implode('; ', $cookies)."\r\n"; + } + + return + sprintf('%s %s %s', $this->getMethod(), $this->getRequestUri(), $this->server->get('SERVER_PROTOCOL'))."\r\n". + $this->headers. + $cookieHeader."\r\n". + $content; + } + + /** + * Overrides the PHP global variables according to this request instance. + * + * It overrides $_GET, $_POST, $_REQUEST, $_SERVER, $_COOKIE. + * $_FILES is never overridden, see rfc1867 + */ + public function overrideGlobals() + { + $this->server->set('QUERY_STRING', static::normalizeQueryString(http_build_query($this->query->all(), '', '&'))); + + $_GET = $this->query->all(); + $_POST = $this->request->all(); + $_SERVER = $this->server->all(); + $_COOKIE = $this->cookies->all(); + + foreach ($this->headers->all() as $key => $value) { + $key = strtoupper(str_replace('-', '_', $key)); + if (\in_array($key, ['CONTENT_TYPE', 'CONTENT_LENGTH', 'CONTENT_MD5'], true)) { + $_SERVER[$key] = implode(', ', $value); + } else { + $_SERVER['HTTP_'.$key] = implode(', ', $value); + } + } + + $request = ['g' => $_GET, 'p' => $_POST, 'c' => $_COOKIE]; + + $requestOrder = ini_get('request_order') ?: ini_get('variables_order'); + $requestOrder = preg_replace('#[^cgp]#', '', strtolower($requestOrder)) ?: 'gp'; + + $_REQUEST = [[]]; + + foreach (str_split($requestOrder) as $order) { + $_REQUEST[] = $request[$order]; + } + + $_REQUEST = array_merge(...$_REQUEST); + } + + /** + * Sets a list of trusted proxies. + * + * You should only list the reverse proxies that you manage directly. + * + * @param array $proxies A list of trusted proxies, the string 'REMOTE_ADDR' will be replaced with $_SERVER['REMOTE_ADDR'] + * @param int $trustedHeaderSet A bit field of Request::HEADER_*, to set which headers to trust from your proxies + */ + public static function setTrustedProxies(array $proxies, int $trustedHeaderSet) + { + if (self::HEADER_X_FORWARDED_ALL === $trustedHeaderSet) { + trigger_deprecation('symfony/http-foundation', '5.2', 'The "HEADER_X_FORWARDED_ALL" constant is deprecated, use either "HEADER_X_FORWARDED_FOR | HEADER_X_FORWARDED_HOST | HEADER_X_FORWARDED_PORT | HEADER_X_FORWARDED_PROTO" or "HEADER_X_FORWARDED_AWS_ELB" or "HEADER_X_FORWARDED_TRAEFIK" constants instead.'); + } + self::$trustedProxies = array_reduce($proxies, function ($proxies, $proxy) { + if ('REMOTE_ADDR' !== $proxy) { + $proxies[] = $proxy; + } elseif (isset($_SERVER['REMOTE_ADDR'])) { + $proxies[] = $_SERVER['REMOTE_ADDR']; + } + + return $proxies; + }, []); + self::$trustedHeaderSet = $trustedHeaderSet; + } + + /** + * Gets the list of trusted proxies. + * + * @return array + */ + public static function getTrustedProxies() + { + return self::$trustedProxies; + } + + /** + * Gets the set of trusted headers from trusted proxies. + * + * @return int A bit field of Request::HEADER_* that defines which headers are trusted from your proxies + */ + public static function getTrustedHeaderSet() + { + return self::$trustedHeaderSet; + } + + /** + * Sets a list of trusted host patterns. + * + * You should only list the hosts you manage using regexs. + * + * @param array $hostPatterns A list of trusted host patterns + */ + public static function setTrustedHosts(array $hostPatterns) + { + self::$trustedHostPatterns = array_map(function ($hostPattern) { + return sprintf('{%s}i', $hostPattern); + }, $hostPatterns); + // we need to reset trusted hosts on trusted host patterns change + self::$trustedHosts = []; + } + + /** + * Gets the list of trusted host patterns. + * + * @return array + */ + public static function getTrustedHosts() + { + return self::$trustedHostPatterns; + } + + /** + * Normalizes a query string. + * + * It builds a normalized query string, where keys/value pairs are alphabetized, + * have consistent escaping and unneeded delimiters are removed. + * + * @return string + */ + public static function normalizeQueryString(?string $qs) + { + if ('' === ($qs ?? '')) { + return ''; + } + + $qs = HeaderUtils::parseQuery($qs); + ksort($qs); + + return http_build_query($qs, '', '&', \PHP_QUERY_RFC3986); + } + + /** + * Enables support for the _method request parameter to determine the intended HTTP method. + * + * Be warned that enabling this feature might lead to CSRF issues in your code. + * Check that you are using CSRF tokens when required. + * If the HTTP method parameter override is enabled, an html-form with method "POST" can be altered + * and used to send a "PUT" or "DELETE" request via the _method request parameter. + * If these methods are not protected against CSRF, this presents a possible vulnerability. + * + * The HTTP method can only be overridden when the real HTTP method is POST. + */ + public static function enableHttpMethodParameterOverride() + { + self::$httpMethodParameterOverride = true; + } + + /** + * Checks whether support for the _method request parameter is enabled. + * + * @return bool + */ + public static function getHttpMethodParameterOverride() + { + return self::$httpMethodParameterOverride; + } + + /** + * Gets a "parameter" value from any bag. + * + * This method is mainly useful for libraries that want to provide some flexibility. If you don't need the + * flexibility in controllers, it is better to explicitly get request parameters from the appropriate + * public property instead (attributes, query, request). + * + * Order of precedence: PATH (routing placeholders or custom attributes), GET, POST + * + * @param mixed $default The default value if the parameter key does not exist + * + * @return mixed + * + * @internal since Symfony 5.4, use explicit input sources instead + */ + public function get(string $key, $default = null) + { + if ($this !== $result = $this->attributes->get($key, $this)) { + return $result; + } + + if ($this->query->has($key)) { + return $this->query->all()[$key]; + } + + if ($this->request->has($key)) { + return $this->request->all()[$key]; + } + + return $default; + } + + /** + * Gets the Session. + * + * @return SessionInterface + */ + public function getSession() + { + $session = $this->session; + if (!$session instanceof SessionInterface && null !== $session) { + $this->setSession($session = $session()); + } + + if (null === $session) { + throw new SessionNotFoundException('Session has not been set.'); + } + + return $session; + } + + /** + * Whether the request contains a Session which was started in one of the + * previous requests. + * + * @return bool + */ + public function hasPreviousSession() + { + // the check for $this->session avoids malicious users trying to fake a session cookie with proper name + return $this->hasSession() && $this->cookies->has($this->getSession()->getName()); + } + + /** + * Whether the request contains a Session object. + * + * This method does not give any information about the state of the session object, + * like whether the session is started or not. It is just a way to check if this Request + * is associated with a Session instance. + * + * @param bool $skipIfUninitialized When true, ignores factories injected by `setSessionFactory` + * + * @return bool + */ + public function hasSession(/* bool $skipIfUninitialized = false */) + { + $skipIfUninitialized = \func_num_args() > 0 ? func_get_arg(0) : false; + + return null !== $this->session && (!$skipIfUninitialized || $this->session instanceof SessionInterface); + } + + public function setSession(SessionInterface $session) + { + $this->session = $session; + } + + /** + * @internal + * + * @param callable(): SessionInterface $factory + */ + public function setSessionFactory(callable $factory) + { + $this->session = $factory; + } + + /** + * Returns the client IP addresses. + * + * In the returned array the most trusted IP address is first, and the + * least trusted one last. The "real" client IP address is the last one, + * but this is also the least trusted one. Trusted proxies are stripped. + * + * Use this method carefully; you should use getClientIp() instead. + * + * @return array + * + * @see getClientIp() + */ + public function getClientIps() + { + $ip = $this->server->get('REMOTE_ADDR'); + + if (!$this->isFromTrustedProxy()) { + return [$ip]; + } + + return $this->getTrustedValues(self::HEADER_X_FORWARDED_FOR, $ip) ?: [$ip]; + } + + /** + * Returns the client IP address. + * + * This method can read the client IP address from the "X-Forwarded-For" header + * when trusted proxies were set via "setTrustedProxies()". The "X-Forwarded-For" + * header value is a comma+space separated list of IP addresses, the left-most + * being the original client, and each successive proxy that passed the request + * adding the IP address where it received the request from. + * + * If your reverse proxy uses a different header name than "X-Forwarded-For", + * ("Client-Ip" for instance), configure it via the $trustedHeaderSet + * argument of the Request::setTrustedProxies() method instead. + * + * @return string|null + * + * @see getClientIps() + * @see https://wikipedia.org/wiki/X-Forwarded-For + */ + public function getClientIp() + { + $ipAddresses = $this->getClientIps(); + + return $ipAddresses[0]; + } + + /** + * Returns current script name. + * + * @return string + */ + public function getScriptName() + { + return $this->server->get('SCRIPT_NAME', $this->server->get('ORIG_SCRIPT_NAME', '')); + } + + /** + * Returns the path being requested relative to the executed script. + * + * The path info always starts with a /. + * + * Suppose this request is instantiated from /mysite on localhost: + * + * * http://localhost/mysite returns an empty string + * * http://localhost/mysite/about returns '/about' + * * http://localhost/mysite/enco%20ded returns '/enco%20ded' + * * http://localhost/mysite/about?var=1 returns '/about' + * + * @return string The raw path (i.e. not urldecoded) + */ + public function getPathInfo() + { + if (null === $this->pathInfo) { + $this->pathInfo = $this->preparePathInfo(); + } + + return $this->pathInfo; + } + + /** + * Returns the root path from which this request is executed. + * + * Suppose that an index.php file instantiates this request object: + * + * * http://localhost/index.php returns an empty string + * * http://localhost/index.php/page returns an empty string + * * http://localhost/web/index.php returns '/web' + * * http://localhost/we%20b/index.php returns '/we%20b' + * + * @return string The raw path (i.e. not urldecoded) + */ + public function getBasePath() + { + if (null === $this->basePath) { + $this->basePath = $this->prepareBasePath(); + } + + return $this->basePath; + } + + /** + * Returns the root URL from which this request is executed. + * + * The base URL never ends with a /. + * + * This is similar to getBasePath(), except that it also includes the + * script filename (e.g. index.php) if one exists. + * + * @return string The raw URL (i.e. not urldecoded) + */ + public function getBaseUrl() + { + $trustedPrefix = ''; + + // the proxy prefix must be prepended to any prefix being needed at the webserver level + if ($this->isFromTrustedProxy() && $trustedPrefixValues = $this->getTrustedValues(self::HEADER_X_FORWARDED_PREFIX)) { + $trustedPrefix = rtrim($trustedPrefixValues[0], '/'); + } + + return $trustedPrefix.$this->getBaseUrlReal(); + } + + /** + * Returns the real base URL received by the webserver from which this request is executed. + * The URL does not include trusted reverse proxy prefix. + * + * @return string The raw URL (i.e. not urldecoded) + */ + private function getBaseUrlReal(): string + { + if (null === $this->baseUrl) { + $this->baseUrl = $this->prepareBaseUrl(); + } + + return $this->baseUrl; + } + + /** + * Gets the request's scheme. + * + * @return string + */ + public function getScheme() + { + return $this->isSecure() ? 'https' : 'http'; + } + + /** + * Returns the port on which the request is made. + * + * This method can read the client port from the "X-Forwarded-Port" header + * when trusted proxies were set via "setTrustedProxies()". + * + * The "X-Forwarded-Port" header must contain the client port. + * + * @return int|string|null Can be a string if fetched from the server bag + */ + public function getPort() + { + if ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_X_FORWARDED_PORT)) { + $host = $host[0]; + } elseif ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_X_FORWARDED_HOST)) { + $host = $host[0]; + } elseif (!$host = $this->headers->get('HOST')) { + return $this->server->get('SERVER_PORT'); + } + + if ('[' === $host[0]) { + $pos = strpos($host, ':', strrpos($host, ']')); + } else { + $pos = strrpos($host, ':'); + } + + if (false !== $pos && $port = substr($host, $pos + 1)) { + return (int) $port; + } + + return 'https' === $this->getScheme() ? 443 : 80; + } + + /** + * Returns the user. + * + * @return string|null + */ + public function getUser() + { + return $this->headers->get('PHP_AUTH_USER'); + } + + /** + * Returns the password. + * + * @return string|null + */ + public function getPassword() + { + return $this->headers->get('PHP_AUTH_PW'); + } + + /** + * Gets the user info. + * + * @return string|null A user name if any and, optionally, scheme-specific information about how to gain authorization to access the server + */ + public function getUserInfo() + { + $userinfo = $this->getUser(); + + $pass = $this->getPassword(); + if ('' != $pass) { + $userinfo .= ":$pass"; + } + + return $userinfo; + } + + /** + * Returns the HTTP host being requested. + * + * The port name will be appended to the host if it's non-standard. + * + * @return string + */ + public function getHttpHost() + { + $scheme = $this->getScheme(); + $port = $this->getPort(); + + if (('http' == $scheme && 80 == $port) || ('https' == $scheme && 443 == $port)) { + return $this->getHost(); + } + + return $this->getHost().':'.$port; + } + + /** + * Returns the requested URI (path and query string). + * + * @return string The raw URI (i.e. not URI decoded) + */ + public function getRequestUri() + { + if (null === $this->requestUri) { + $this->requestUri = $this->prepareRequestUri(); + } + + return $this->requestUri; + } + + /** + * Gets the scheme and HTTP host. + * + * If the URL was called with basic authentication, the user + * and the password are not added to the generated string. + * + * @return string + */ + public function getSchemeAndHttpHost() + { + return $this->getScheme().'://'.$this->getHttpHost(); + } + + /** + * Generates a normalized URI (URL) for the Request. + * + * @return string + * + * @see getQueryString() + */ + public function getUri() + { + if (null !== $qs = $this->getQueryString()) { + $qs = '?'.$qs; + } + + return $this->getSchemeAndHttpHost().$this->getBaseUrl().$this->getPathInfo().$qs; + } + + /** + * Generates a normalized URI for the given path. + * + * @param string $path A path to use instead of the current one + * + * @return string + */ + public function getUriForPath(string $path) + { + return $this->getSchemeAndHttpHost().$this->getBaseUrl().$path; + } + + /** + * Returns the path as relative reference from the current Request path. + * + * Only the URIs path component (no schema, host etc.) is relevant and must be given. + * Both paths must be absolute and not contain relative parts. + * Relative URLs from one resource to another are useful when generating self-contained downloadable document archives. + * Furthermore, they can be used to reduce the link size in documents. + * + * Example target paths, given a base path of "/a/b/c/d": + * - "/a/b/c/d" -> "" + * - "/a/b/c/" -> "./" + * - "/a/b/" -> "../" + * - "/a/b/c/other" -> "other" + * - "/a/x/y" -> "../../x/y" + * + * @return string + */ + public function getRelativeUriForPath(string $path) + { + // be sure that we are dealing with an absolute path + if (!isset($path[0]) || '/' !== $path[0]) { + return $path; + } + + if ($path === $basePath = $this->getPathInfo()) { + return ''; + } + + $sourceDirs = explode('/', isset($basePath[0]) && '/' === $basePath[0] ? substr($basePath, 1) : $basePath); + $targetDirs = explode('/', substr($path, 1)); + array_pop($sourceDirs); + $targetFile = array_pop($targetDirs); + + foreach ($sourceDirs as $i => $dir) { + if (isset($targetDirs[$i]) && $dir === $targetDirs[$i]) { + unset($sourceDirs[$i], $targetDirs[$i]); + } else { + break; + } + } + + $targetDirs[] = $targetFile; + $path = str_repeat('../', \count($sourceDirs)).implode('/', $targetDirs); + + // A reference to the same base directory or an empty subdirectory must be prefixed with "./". + // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used + // as the first segment of a relative-path reference, as it would be mistaken for a scheme name + // (see https://tools.ietf.org/html/rfc3986#section-4.2). + return !isset($path[0]) || '/' === $path[0] + || false !== ($colonPos = strpos($path, ':')) && ($colonPos < ($slashPos = strpos($path, '/')) || false === $slashPos) + ? "./$path" : $path; + } + + /** + * Generates the normalized query string for the Request. + * + * It builds a normalized query string, where keys/value pairs are alphabetized + * and have consistent escaping. + * + * @return string|null + */ + public function getQueryString() + { + $qs = static::normalizeQueryString($this->server->get('QUERY_STRING')); + + return '' === $qs ? null : $qs; + } + + /** + * Checks whether the request is secure or not. + * + * This method can read the client protocol from the "X-Forwarded-Proto" header + * when trusted proxies were set via "setTrustedProxies()". + * + * The "X-Forwarded-Proto" header must contain the protocol: "https" or "http". + * + * @return bool + */ + public function isSecure() + { + if ($this->isFromTrustedProxy() && $proto = $this->getTrustedValues(self::HEADER_X_FORWARDED_PROTO)) { + return \in_array(strtolower($proto[0]), ['https', 'on', 'ssl', '1'], true); + } + + $https = $this->server->get('HTTPS'); + + return !empty($https) && 'off' !== strtolower($https); + } + + /** + * Returns the host name. + * + * This method can read the client host name from the "X-Forwarded-Host" header + * when trusted proxies were set via "setTrustedProxies()". + * + * The "X-Forwarded-Host" header must contain the client host name. + * + * @return string + * + * @throws SuspiciousOperationException when the host name is invalid or not trusted + */ + public function getHost() + { + if ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_X_FORWARDED_HOST)) { + $host = $host[0]; + } elseif (!$host = $this->headers->get('HOST')) { + if (!$host = $this->server->get('SERVER_NAME')) { + $host = $this->server->get('SERVER_ADDR', ''); + } + } + + // trim and remove port number from host + // host is lowercase as per RFC 952/2181 + $host = strtolower(preg_replace('/:\d+$/', '', trim($host))); + + // as the host can come from the user (HTTP_HOST and depending on the configuration, SERVER_NAME too can come from the user) + // check that it does not contain forbidden characters (see RFC 952 and RFC 2181) + // use preg_replace() instead of preg_match() to prevent DoS attacks with long host names + if ($host && '' !== preg_replace('/(?:^\[)?[a-zA-Z0-9-:\]_]+\.?/', '', $host)) { + if (!$this->isHostValid) { + return ''; + } + $this->isHostValid = false; + + throw new SuspiciousOperationException(sprintf('Invalid Host "%s".', $host)); + } + + if (\count(self::$trustedHostPatterns) > 0) { + // to avoid host header injection attacks, you should provide a list of trusted host patterns + + if (\in_array($host, self::$trustedHosts)) { + return $host; + } + + foreach (self::$trustedHostPatterns as $pattern) { + if (preg_match($pattern, $host)) { + self::$trustedHosts[] = $host; + + return $host; + } + } + + if (!$this->isHostValid) { + return ''; + } + $this->isHostValid = false; + + throw new SuspiciousOperationException(sprintf('Untrusted Host "%s".', $host)); + } + + return $host; + } + + /** + * Sets the request method. + */ + public function setMethod(string $method) + { + $this->method = null; + $this->server->set('REQUEST_METHOD', $method); + } + + /** + * Gets the request "intended" method. + * + * If the X-HTTP-Method-Override header is set, and if the method is a POST, + * then it is used to determine the "real" intended HTTP method. + * + * The _method request parameter can also be used to determine the HTTP method, + * but only if enableHttpMethodParameterOverride() has been called. + * + * The method is always an uppercased string. + * + * @return string + * + * @see getRealMethod() + */ + public function getMethod() + { + if (null !== $this->method) { + return $this->method; + } + + $this->method = strtoupper($this->server->get('REQUEST_METHOD', 'GET')); + + if ('POST' !== $this->method) { + return $this->method; + } + + $method = $this->headers->get('X-HTTP-METHOD-OVERRIDE'); + + if (!$method && self::$httpMethodParameterOverride) { + $method = $this->request->get('_method', $this->query->get('_method', 'POST')); + } + + if (!\is_string($method)) { + return $this->method; + } + + $method = strtoupper($method); + + if (\in_array($method, ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'PATCH', 'PURGE', 'TRACE'], true)) { + return $this->method = $method; + } + + if (!preg_match('/^[A-Z]++$/D', $method)) { + throw new SuspiciousOperationException(sprintf('Invalid method override "%s".', $method)); + } + + return $this->method = $method; + } + + /** + * Gets the "real" request method. + * + * @return string + * + * @see getMethod() + */ + public function getRealMethod() + { + return strtoupper($this->server->get('REQUEST_METHOD', 'GET')); + } + + /** + * Gets the mime type associated with the format. + * + * @return string|null + */ + public function getMimeType(string $format) + { + if (null === static::$formats) { + static::initializeFormats(); + } + + return isset(static::$formats[$format]) ? static::$formats[$format][0] : null; + } + + /** + * Gets the mime types associated with the format. + * + * @return array + */ + public static function getMimeTypes(string $format) + { + if (null === static::$formats) { + static::initializeFormats(); + } + + return static::$formats[$format] ?? []; + } + + /** + * Gets the format associated with the mime type. + * + * @return string|null + */ + public function getFormat(?string $mimeType) + { + $canonicalMimeType = null; + if ($mimeType && false !== $pos = strpos($mimeType, ';')) { + $canonicalMimeType = trim(substr($mimeType, 0, $pos)); + } + + if (null === static::$formats) { + static::initializeFormats(); + } + + foreach (static::$formats as $format => $mimeTypes) { + if (\in_array($mimeType, (array) $mimeTypes)) { + return $format; + } + if (null !== $canonicalMimeType && \in_array($canonicalMimeType, (array) $mimeTypes)) { + return $format; + } + } + + return null; + } + + /** + * Associates a format with mime types. + * + * @param string|array $mimeTypes The associated mime types (the preferred one must be the first as it will be used as the content type) + */ + public function setFormat(?string $format, $mimeTypes) + { + if (null === static::$formats) { + static::initializeFormats(); + } + + static::$formats[$format] = \is_array($mimeTypes) ? $mimeTypes : [$mimeTypes]; + } + + /** + * Gets the request format. + * + * Here is the process to determine the format: + * + * * format defined by the user (with setRequestFormat()) + * * _format request attribute + * * $default + * + * @see getPreferredFormat + * + * @return string|null + */ + public function getRequestFormat(?string $default = 'html') + { + if (null === $this->format) { + $this->format = $this->attributes->get('_format'); + } + + return $this->format ?? $default; + } + + /** + * Sets the request format. + */ + public function setRequestFormat(?string $format) + { + $this->format = $format; + } + + /** + * Gets the format associated with the request. + * + * @return string|null + */ + public function getContentType() + { + return $this->getFormat($this->headers->get('CONTENT_TYPE', '')); + } + + /** + * Sets the default locale. + */ + public function setDefaultLocale(string $locale) + { + $this->defaultLocale = $locale; + + if (null === $this->locale) { + $this->setPhpDefaultLocale($locale); + } + } + + /** + * Get the default locale. + * + * @return string + */ + public function getDefaultLocale() + { + return $this->defaultLocale; + } + + /** + * Sets the locale. + */ + public function setLocale(string $locale) + { + $this->setPhpDefaultLocale($this->locale = $locale); + } + + /** + * Get the locale. + * + * @return string + */ + public function getLocale() + { + return null === $this->locale ? $this->defaultLocale : $this->locale; + } + + /** + * Checks if the request method is of specified type. + * + * @param string $method Uppercase request method (GET, POST etc) + * + * @return bool + */ + public function isMethod(string $method) + { + return $this->getMethod() === strtoupper($method); + } + + /** + * Checks whether or not the method is safe. + * + * @see https://tools.ietf.org/html/rfc7231#section-4.2.1 + * + * @return bool + */ + public function isMethodSafe() + { + return \in_array($this->getMethod(), ['GET', 'HEAD', 'OPTIONS', 'TRACE']); + } + + /** + * Checks whether or not the method is idempotent. + * + * @return bool + */ + public function isMethodIdempotent() + { + return \in_array($this->getMethod(), ['HEAD', 'GET', 'PUT', 'DELETE', 'TRACE', 'OPTIONS', 'PURGE']); + } + + /** + * Checks whether the method is cacheable or not. + * + * @see https://tools.ietf.org/html/rfc7231#section-4.2.3 + * + * @return bool + */ + public function isMethodCacheable() + { + return \in_array($this->getMethod(), ['GET', 'HEAD']); + } + + /** + * Returns the protocol version. + * + * If the application is behind a proxy, the protocol version used in the + * requests between the client and the proxy and between the proxy and the + * server might be different. This returns the former (from the "Via" header) + * if the proxy is trusted (see "setTrustedProxies()"), otherwise it returns + * the latter (from the "SERVER_PROTOCOL" server parameter). + * + * @return string|null + */ + public function getProtocolVersion() + { + if ($this->isFromTrustedProxy()) { + preg_match('~^(HTTP/)?([1-9]\.[0-9]) ~', $this->headers->get('Via') ?? '', $matches); + + if ($matches) { + return 'HTTP/'.$matches[2]; + } + } + + return $this->server->get('SERVER_PROTOCOL'); + } + + /** + * Returns the request body content. + * + * @param bool $asResource If true, a resource will be returned + * + * @return string|resource + */ + public function getContent(bool $asResource = false) + { + $currentContentIsResource = \is_resource($this->content); + + if (true === $asResource) { + if ($currentContentIsResource) { + rewind($this->content); + + return $this->content; + } + + // Content passed in parameter (test) + if (\is_string($this->content)) { + $resource = fopen('php://temp', 'r+'); + fwrite($resource, $this->content); + rewind($resource); + + return $resource; + } + + $this->content = false; + + return fopen('php://input', 'r'); + } + + if ($currentContentIsResource) { + rewind($this->content); + + return stream_get_contents($this->content); + } + + if (null === $this->content || false === $this->content) { + $this->content = file_get_contents('php://input'); + } + + return $this->content; + } + + /** + * Gets the request body decoded as array, typically from a JSON payload. + * + * @throws JsonException When the body cannot be decoded to an array + * + * @return array + */ + public function toArray() + { + if ('' === $content = $this->getContent()) { + throw new JsonException('Request body is empty.'); + } + + try { + $content = json_decode($content, true, 512, \JSON_BIGINT_AS_STRING | (\PHP_VERSION_ID >= 70300 ? \JSON_THROW_ON_ERROR : 0)); + } catch (\JsonException $e) { + throw new JsonException('Could not decode request body.', $e->getCode(), $e); + } + + if (\PHP_VERSION_ID < 70300 && \JSON_ERROR_NONE !== json_last_error()) { + throw new JsonException('Could not decode request body: '.json_last_error_msg(), json_last_error()); + } + + if (!\is_array($content)) { + throw new JsonException(sprintf('JSON content was expected to decode to an array, "%s" returned.', get_debug_type($content))); + } + + return $content; + } + + /** + * Gets the Etags. + * + * @return array + */ + public function getETags() + { + return preg_split('/\s*,\s*/', $this->headers->get('If-None-Match', ''), -1, \PREG_SPLIT_NO_EMPTY); + } + + /** + * @return bool + */ + public function isNoCache() + { + return $this->headers->hasCacheControlDirective('no-cache') || 'no-cache' == $this->headers->get('Pragma'); + } + + /** + * Gets the preferred format for the response by inspecting, in the following order: + * * the request format set using setRequestFormat; + * * the values of the Accept HTTP header. + * + * Note that if you use this method, you should send the "Vary: Accept" header + * in the response to prevent any issues with intermediary HTTP caches. + */ + public function getPreferredFormat(?string $default = 'html'): ?string + { + if (null !== $this->preferredFormat || null !== $this->preferredFormat = $this->getRequestFormat(null)) { + return $this->preferredFormat; + } + + foreach ($this->getAcceptableContentTypes() as $mimeType) { + if ($this->preferredFormat = $this->getFormat($mimeType)) { + return $this->preferredFormat; + } + } + + return $default; + } + + /** + * Returns the preferred language. + * + * @param string[] $locales An array of ordered available locales + * + * @return string|null + */ + public function getPreferredLanguage(array $locales = null) + { + $preferredLanguages = $this->getLanguages(); + + if (empty($locales)) { + return $preferredLanguages[0] ?? null; + } + + if (!$preferredLanguages) { + return $locales[0]; + } + + $extendedPreferredLanguages = []; + foreach ($preferredLanguages as $language) { + $extendedPreferredLanguages[] = $language; + if (false !== $position = strpos($language, '_')) { + $superLanguage = substr($language, 0, $position); + if (!\in_array($superLanguage, $preferredLanguages)) { + $extendedPreferredLanguages[] = $superLanguage; + } + } + } + + $preferredLanguages = array_values(array_intersect($extendedPreferredLanguages, $locales)); + + return $preferredLanguages[0] ?? $locales[0]; + } + + /** + * Gets a list of languages acceptable by the client browser ordered in the user browser preferences. + * + * @return array + */ + public function getLanguages() + { + if (null !== $this->languages) { + return $this->languages; + } + + $languages = AcceptHeader::fromString($this->headers->get('Accept-Language'))->all(); + $this->languages = []; + foreach ($languages as $lang => $acceptHeaderItem) { + if (str_contains($lang, '-')) { + $codes = explode('-', $lang); + if ('i' === $codes[0]) { + // Language not listed in ISO 639 that are not variants + // of any listed language, which can be registered with the + // i-prefix, such as i-cherokee + if (\count($codes) > 1) { + $lang = $codes[1]; + } + } else { + for ($i = 0, $max = \count($codes); $i < $max; ++$i) { + if (0 === $i) { + $lang = strtolower($codes[0]); + } else { + $lang .= '_'.strtoupper($codes[$i]); + } + } + } + } + + $this->languages[] = $lang; + } + + return $this->languages; + } + + /** + * Gets a list of charsets acceptable by the client browser in preferable order. + * + * @return array + */ + public function getCharsets() + { + if (null !== $this->charsets) { + return $this->charsets; + } + + return $this->charsets = array_keys(AcceptHeader::fromString($this->headers->get('Accept-Charset'))->all()); + } + + /** + * Gets a list of encodings acceptable by the client browser in preferable order. + * + * @return array + */ + public function getEncodings() + { + if (null !== $this->encodings) { + return $this->encodings; + } + + return $this->encodings = array_keys(AcceptHeader::fromString($this->headers->get('Accept-Encoding'))->all()); + } + + /** + * Gets a list of content types acceptable by the client browser in preferable order. + * + * @return array + */ + public function getAcceptableContentTypes() + { + if (null !== $this->acceptableContentTypes) { + return $this->acceptableContentTypes; + } + + return $this->acceptableContentTypes = array_keys(AcceptHeader::fromString($this->headers->get('Accept'))->all()); + } + + /** + * Returns true if the request is an XMLHttpRequest. + * + * It works if your JavaScript library sets an X-Requested-With HTTP header. + * It is known to work with common JavaScript frameworks: + * + * @see https://wikipedia.org/wiki/List_of_Ajax_frameworks#JavaScript + * + * @return bool + */ + public function isXmlHttpRequest() + { + return 'XMLHttpRequest' == $this->headers->get('X-Requested-With'); + } + + /** + * Checks whether the client browser prefers safe content or not according to RFC8674. + * + * @see https://tools.ietf.org/html/rfc8674 + */ + public function preferSafeContent(): bool + { + if (null !== $this->isSafeContentPreferred) { + return $this->isSafeContentPreferred; + } + + if (!$this->isSecure()) { + // see https://tools.ietf.org/html/rfc8674#section-3 + return $this->isSafeContentPreferred = false; + } + + return $this->isSafeContentPreferred = AcceptHeader::fromString($this->headers->get('Prefer'))->has('safe'); + } + + /* + * The following methods are derived from code of the Zend Framework (1.10dev - 2010-01-24) + * + * Code subject to the new BSD license (https://framework.zend.com/license). + * + * Copyright (c) 2005-2010 Zend Technologies USA Inc. (https://www.zend.com/) + */ + + protected function prepareRequestUri() + { + $requestUri = ''; + + if ('1' == $this->server->get('IIS_WasUrlRewritten') && '' != $this->server->get('UNENCODED_URL')) { + // IIS7 with URL Rewrite: make sure we get the unencoded URL (double slash problem) + $requestUri = $this->server->get('UNENCODED_URL'); + $this->server->remove('UNENCODED_URL'); + $this->server->remove('IIS_WasUrlRewritten'); + } elseif ($this->server->has('REQUEST_URI')) { + $requestUri = $this->server->get('REQUEST_URI'); + + if ('' !== $requestUri && '/' === $requestUri[0]) { + // To only use path and query remove the fragment. + if (false !== $pos = strpos($requestUri, '#')) { + $requestUri = substr($requestUri, 0, $pos); + } + } else { + // HTTP proxy reqs setup request URI with scheme and host [and port] + the URL path, + // only use URL path. + $uriComponents = parse_url($requestUri); + + if (isset($uriComponents['path'])) { + $requestUri = $uriComponents['path']; + } + + if (isset($uriComponents['query'])) { + $requestUri .= '?'.$uriComponents['query']; + } + } + } elseif ($this->server->has('ORIG_PATH_INFO')) { + // IIS 5.0, PHP as CGI + $requestUri = $this->server->get('ORIG_PATH_INFO'); + if ('' != $this->server->get('QUERY_STRING')) { + $requestUri .= '?'.$this->server->get('QUERY_STRING'); + } + $this->server->remove('ORIG_PATH_INFO'); + } + + // normalize the request URI to ease creating sub-requests from this request + $this->server->set('REQUEST_URI', $requestUri); + + return $requestUri; + } + + /** + * Prepares the base URL. + * + * @return string + */ + protected function prepareBaseUrl() + { + $filename = basename($this->server->get('SCRIPT_FILENAME', '')); + + if (basename($this->server->get('SCRIPT_NAME', '')) === $filename) { + $baseUrl = $this->server->get('SCRIPT_NAME'); + } elseif (basename($this->server->get('PHP_SELF', '')) === $filename) { + $baseUrl = $this->server->get('PHP_SELF'); + } elseif (basename($this->server->get('ORIG_SCRIPT_NAME', '')) === $filename) { + $baseUrl = $this->server->get('ORIG_SCRIPT_NAME'); // 1and1 shared hosting compatibility + } else { + // Backtrack up the script_filename to find the portion matching + // php_self + $path = $this->server->get('PHP_SELF', ''); + $file = $this->server->get('SCRIPT_FILENAME', ''); + $segs = explode('/', trim($file, '/')); + $segs = array_reverse($segs); + $index = 0; + $last = \count($segs); + $baseUrl = ''; + do { + $seg = $segs[$index]; + $baseUrl = '/'.$seg.$baseUrl; + ++$index; + } while ($last > $index && (false !== $pos = strpos($path, $baseUrl)) && 0 != $pos); + } + + // Does the baseUrl have anything in common with the request_uri? + $requestUri = $this->getRequestUri(); + if ('' !== $requestUri && '/' !== $requestUri[0]) { + $requestUri = '/'.$requestUri; + } + + if ($baseUrl && null !== $prefix = $this->getUrlencodedPrefix($requestUri, $baseUrl)) { + // full $baseUrl matches + return $prefix; + } + + if ($baseUrl && null !== $prefix = $this->getUrlencodedPrefix($requestUri, rtrim(\dirname($baseUrl), '/'.\DIRECTORY_SEPARATOR).'/')) { + // directory portion of $baseUrl matches + return rtrim($prefix, '/'.\DIRECTORY_SEPARATOR); + } + + $truncatedRequestUri = $requestUri; + if (false !== $pos = strpos($requestUri, '?')) { + $truncatedRequestUri = substr($requestUri, 0, $pos); + } + + $basename = basename($baseUrl ?? ''); + if (empty($basename) || !strpos(rawurldecode($truncatedRequestUri), $basename)) { + // no match whatsoever; set it blank + return ''; + } + + // If using mod_rewrite or ISAPI_Rewrite strip the script filename + // out of baseUrl. $pos !== 0 makes sure it is not matching a value + // from PATH_INFO or QUERY_STRING + if (\strlen($requestUri) >= \strlen($baseUrl) && (false !== $pos = strpos($requestUri, $baseUrl)) && 0 !== $pos) { + $baseUrl = substr($requestUri, 0, $pos + \strlen($baseUrl)); + } + + return rtrim($baseUrl, '/'.\DIRECTORY_SEPARATOR); + } + + /** + * Prepares the base path. + * + * @return string + */ + protected function prepareBasePath() + { + $baseUrl = $this->getBaseUrl(); + if (empty($baseUrl)) { + return ''; + } + + $filename = basename($this->server->get('SCRIPT_FILENAME')); + if (basename($baseUrl) === $filename) { + $basePath = \dirname($baseUrl); + } else { + $basePath = $baseUrl; + } + + if ('\\' === \DIRECTORY_SEPARATOR) { + $basePath = str_replace('\\', '/', $basePath); + } + + return rtrim($basePath, '/'); + } + + /** + * Prepares the path info. + * + * @return string + */ + protected function preparePathInfo() + { + if (null === ($requestUri = $this->getRequestUri())) { + return '/'; + } + + // Remove the query string from REQUEST_URI + if (false !== $pos = strpos($requestUri, '?')) { + $requestUri = substr($requestUri, 0, $pos); + } + if ('' !== $requestUri && '/' !== $requestUri[0]) { + $requestUri = '/'.$requestUri; + } + + if (null === ($baseUrl = $this->getBaseUrlReal())) { + return $requestUri; + } + + $pathInfo = substr($requestUri, \strlen($baseUrl)); + if (false === $pathInfo || '' === $pathInfo) { + // If substr() returns false then PATH_INFO is set to an empty string + return '/'; + } + + return $pathInfo; + } + + /** + * Initializes HTTP request formats. + */ + protected static function initializeFormats() + { + static::$formats = [ + 'html' => ['text/html', 'application/xhtml+xml'], + 'txt' => ['text/plain'], + 'js' => ['application/javascript', 'application/x-javascript', 'text/javascript'], + 'css' => ['text/css'], + 'json' => ['application/json', 'application/x-json'], + 'jsonld' => ['application/ld+json'], + 'xml' => ['text/xml', 'application/xml', 'application/x-xml'], + 'rdf' => ['application/rdf+xml'], + 'atom' => ['application/atom+xml'], + 'rss' => ['application/rss+xml'], + 'form' => ['application/x-www-form-urlencoded', 'multipart/form-data'], + ]; + } + + private function setPhpDefaultLocale(string $locale): void + { + // if either the class Locale doesn't exist, or an exception is thrown when + // setting the default locale, the intl module is not installed, and + // the call can be ignored: + try { + if (class_exists(\Locale::class, false)) { + \Locale::setDefault($locale); + } + } catch (\Exception $e) { + } + } + + /** + * Returns the prefix as encoded in the string when the string starts with + * the given prefix, null otherwise. + */ + private function getUrlencodedPrefix(string $string, string $prefix): ?string + { + if (!str_starts_with(rawurldecode($string), $prefix)) { + return null; + } + + $len = \strlen($prefix); + + if (preg_match(sprintf('#^(%%[[:xdigit:]]{2}|.){%d}#', $len), $string, $match)) { + return $match[0]; + } + + return null; + } + + private static function createRequestFromFactory(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null): self + { + if (self::$requestFactory) { + $request = (self::$requestFactory)($query, $request, $attributes, $cookies, $files, $server, $content); + + if (!$request instanceof self) { + throw new \LogicException('The Request factory must return an instance of Symfony\Component\HttpFoundation\Request.'); + } + + return $request; + } + + return new static($query, $request, $attributes, $cookies, $files, $server, $content); + } + + /** + * Indicates whether this request originated from a trusted proxy. + * + * This can be useful to determine whether or not to trust the + * contents of a proxy-specific header. + * + * @return bool + */ + public function isFromTrustedProxy() + { + return self::$trustedProxies && IpUtils::checkIp($this->server->get('REMOTE_ADDR', ''), self::$trustedProxies); + } + + private function getTrustedValues(int $type, string $ip = null): array + { + $clientValues = []; + $forwardedValues = []; + + if ((self::$trustedHeaderSet & $type) && $this->headers->has(self::TRUSTED_HEADERS[$type])) { + foreach (explode(',', $this->headers->get(self::TRUSTED_HEADERS[$type])) as $v) { + $clientValues[] = (self::HEADER_X_FORWARDED_PORT === $type ? '0.0.0.0:' : '').trim($v); + } + } + + if ((self::$trustedHeaderSet & self::HEADER_FORWARDED) && (isset(self::FORWARDED_PARAMS[$type])) && $this->headers->has(self::TRUSTED_HEADERS[self::HEADER_FORWARDED])) { + $forwarded = $this->headers->get(self::TRUSTED_HEADERS[self::HEADER_FORWARDED]); + $parts = HeaderUtils::split($forwarded, ',;='); + $forwardedValues = []; + $param = self::FORWARDED_PARAMS[$type]; + foreach ($parts as $subParts) { + if (null === $v = HeaderUtils::combine($subParts)[$param] ?? null) { + continue; + } + if (self::HEADER_X_FORWARDED_PORT === $type) { + if (str_ends_with($v, ']') || false === $v = strrchr($v, ':')) { + $v = $this->isSecure() ? ':443' : ':80'; + } + $v = '0.0.0.0'.$v; + } + $forwardedValues[] = $v; + } + } + + if (null !== $ip) { + $clientValues = $this->normalizeAndFilterClientIps($clientValues, $ip); + $forwardedValues = $this->normalizeAndFilterClientIps($forwardedValues, $ip); + } + + if ($forwardedValues === $clientValues || !$clientValues) { + return $forwardedValues; + } + + if (!$forwardedValues) { + return $clientValues; + } + + if (!$this->isForwardedValid) { + return null !== $ip ? ['0.0.0.0', $ip] : []; + } + $this->isForwardedValid = false; + + throw new ConflictingHeadersException(sprintf('The request has both a trusted "%s" header and a trusted "%s" header, conflicting with each other. You should either configure your proxy to remove one of them, or configure your project to distrust the offending one.', self::TRUSTED_HEADERS[self::HEADER_FORWARDED], self::TRUSTED_HEADERS[$type])); + } + + private function normalizeAndFilterClientIps(array $clientIps, string $ip): array + { + if (!$clientIps) { + return []; + } + $clientIps[] = $ip; // Complete the IP chain with the IP the request actually came from + $firstTrustedIp = null; + + foreach ($clientIps as $key => $clientIp) { + if (strpos($clientIp, '.')) { + // Strip :port from IPv4 addresses. This is allowed in Forwarded + // and may occur in X-Forwarded-For. + $i = strpos($clientIp, ':'); + if ($i) { + $clientIps[$key] = $clientIp = substr($clientIp, 0, $i); + } + } elseif (str_starts_with($clientIp, '[')) { + // Strip brackets and :port from IPv6 addresses. + $i = strpos($clientIp, ']', 1); + $clientIps[$key] = $clientIp = substr($clientIp, 1, $i - 1); + } + + if (!filter_var($clientIp, \FILTER_VALIDATE_IP)) { + unset($clientIps[$key]); + + continue; + } + + if (IpUtils::checkIp($clientIp, self::$trustedProxies)) { + unset($clientIps[$key]); + + // Fallback to this when the client IP falls into the range of trusted proxies + if (null === $firstTrustedIp) { + $firstTrustedIp = $clientIp; + } + } + } + + // Now the IP chain contains only untrusted proxies and the client IP + return $clientIps ? array_reverse($clientIps) : [$firstTrustedIp]; + } +} diff --git a/vendor/symfony/http-foundation/RequestMatcher.php b/vendor/symfony/http-foundation/RequestMatcher.php new file mode 100644 index 0000000..f2645f9 --- /dev/null +++ b/vendor/symfony/http-foundation/RequestMatcher.php @@ -0,0 +1,196 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * RequestMatcher compares a pre-defined set of checks against a Request instance. + * + * @author Fabien Potencier + */ +class RequestMatcher implements RequestMatcherInterface +{ + /** + * @var string|null + */ + private $path; + + /** + * @var string|null + */ + private $host; + + /** + * @var int|null + */ + private $port; + + /** + * @var string[] + */ + private $methods = []; + + /** + * @var string[] + */ + private $ips = []; + + /** + * @var array + */ + private $attributes = []; + + /** + * @var string[] + */ + private $schemes = []; + + /** + * @param string|string[]|null $methods + * @param string|string[]|null $ips + * @param string|string[]|null $schemes + */ + public function __construct(string $path = null, string $host = null, $methods = null, $ips = null, array $attributes = [], $schemes = null, int $port = null) + { + $this->matchPath($path); + $this->matchHost($host); + $this->matchMethod($methods); + $this->matchIps($ips); + $this->matchScheme($schemes); + $this->matchPort($port); + + foreach ($attributes as $k => $v) { + $this->matchAttribute($k, $v); + } + } + + /** + * Adds a check for the HTTP scheme. + * + * @param string|string[]|null $scheme An HTTP scheme or an array of HTTP schemes + */ + public function matchScheme($scheme) + { + $this->schemes = null !== $scheme ? array_map('strtolower', (array) $scheme) : []; + } + + /** + * Adds a check for the URL host name. + */ + public function matchHost(?string $regexp) + { + $this->host = $regexp; + } + + /** + * Adds a check for the the URL port. + * + * @param int|null $port The port number to connect to + */ + public function matchPort(?int $port) + { + $this->port = $port; + } + + /** + * Adds a check for the URL path info. + */ + public function matchPath(?string $regexp) + { + $this->path = $regexp; + } + + /** + * Adds a check for the client IP. + * + * @param string $ip A specific IP address or a range specified using IP/netmask like 192.168.1.0/24 + */ + public function matchIp(string $ip) + { + $this->matchIps($ip); + } + + /** + * Adds a check for the client IP. + * + * @param string|string[]|null $ips A specific IP address or a range specified using IP/netmask like 192.168.1.0/24 + */ + public function matchIps($ips) + { + $ips = null !== $ips ? (array) $ips : []; + + $this->ips = array_reduce($ips, static function (array $ips, string $ip) { + return array_merge($ips, preg_split('/\s*,\s*/', $ip)); + }, []); + } + + /** + * Adds a check for the HTTP method. + * + * @param string|string[]|null $method An HTTP method or an array of HTTP methods + */ + public function matchMethod($method) + { + $this->methods = null !== $method ? array_map('strtoupper', (array) $method) : []; + } + + /** + * Adds a check for request attribute. + */ + public function matchAttribute(string $key, string $regexp) + { + $this->attributes[$key] = $regexp; + } + + /** + * {@inheritdoc} + */ + public function matches(Request $request) + { + if ($this->schemes && !\in_array($request->getScheme(), $this->schemes, true)) { + return false; + } + + if ($this->methods && !\in_array($request->getMethod(), $this->methods, true)) { + return false; + } + + foreach ($this->attributes as $key => $pattern) { + $requestAttribute = $request->attributes->get($key); + if (!\is_string($requestAttribute)) { + return false; + } + if (!preg_match('{'.$pattern.'}', $requestAttribute)) { + return false; + } + } + + if (null !== $this->path && !preg_match('{'.$this->path.'}', rawurldecode($request->getPathInfo()))) { + return false; + } + + if (null !== $this->host && !preg_match('{'.$this->host.'}i', $request->getHost())) { + return false; + } + + if (null !== $this->port && 0 < $this->port && $request->getPort() !== $this->port) { + return false; + } + + if (IpUtils::checkIp($request->getClientIp() ?? '', $this->ips)) { + return true; + } + + // Note to future implementors: add additional checks above the + // foreach above or else your check might not be run! + return 0 === \count($this->ips); + } +} diff --git a/vendor/symfony/http-foundation/RequestMatcherInterface.php b/vendor/symfony/http-foundation/RequestMatcherInterface.php new file mode 100644 index 0000000..c2e1478 --- /dev/null +++ b/vendor/symfony/http-foundation/RequestMatcherInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * RequestMatcherInterface is an interface for strategies to match a Request. + * + * @author Fabien Potencier + */ +interface RequestMatcherInterface +{ + /** + * Decides whether the rule(s) implemented by the strategy matches the supplied request. + * + * @return bool + */ + public function matches(Request $request); +} diff --git a/vendor/symfony/http-foundation/RequestStack.php b/vendor/symfony/http-foundation/RequestStack.php new file mode 100644 index 0000000..855b518 --- /dev/null +++ b/vendor/symfony/http-foundation/RequestStack.php @@ -0,0 +1,128 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\HttpFoundation\Exception\SessionNotFoundException; +use Symfony\Component\HttpFoundation\Session\SessionInterface; + +/** + * Request stack that controls the lifecycle of requests. + * + * @author Benjamin Eberlei + */ +class RequestStack +{ + /** + * @var Request[] + */ + private $requests = []; + + /** + * Pushes a Request on the stack. + * + * This method should generally not be called directly as the stack + * management should be taken care of by the application itself. + */ + public function push(Request $request) + { + $this->requests[] = $request; + } + + /** + * Pops the current request from the stack. + * + * This operation lets the current request go out of scope. + * + * This method should generally not be called directly as the stack + * management should be taken care of by the application itself. + * + * @return Request|null + */ + public function pop() + { + if (!$this->requests) { + return null; + } + + return array_pop($this->requests); + } + + /** + * @return Request|null + */ + public function getCurrentRequest() + { + return end($this->requests) ?: null; + } + + /** + * Gets the main request. + * + * Be warned that making your code aware of the main request + * might make it un-compatible with other features of your framework + * like ESI support. + */ + public function getMainRequest(): ?Request + { + if (!$this->requests) { + return null; + } + + return $this->requests[0]; + } + + /** + * Gets the master request. + * + * @return Request|null + * + * @deprecated since symfony/http-foundation 5.3, use getMainRequest() instead + */ + public function getMasterRequest() + { + trigger_deprecation('symfony/http-foundation', '5.3', '"%s()" is deprecated, use "getMainRequest()" instead.', __METHOD__); + + return $this->getMainRequest(); + } + + /** + * Returns the parent request of the current. + * + * Be warned that making your code aware of the parent request + * might make it un-compatible with other features of your framework + * like ESI support. + * + * If current Request is the main request, it returns null. + * + * @return Request|null + */ + public function getParentRequest() + { + $pos = \count($this->requests) - 2; + + return $this->requests[$pos] ?? null; + } + + /** + * Gets the current session. + * + * @throws SessionNotFoundException + */ + public function getSession(): SessionInterface + { + if ((null !== $request = end($this->requests) ?: null) && $request->hasSession()) { + return $request->getSession(); + } + + throw new SessionNotFoundException(); + } +} diff --git a/vendor/symfony/http-foundation/Response.php b/vendor/symfony/http-foundation/Response.php new file mode 100644 index 0000000..265e6ea --- /dev/null +++ b/vendor/symfony/http-foundation/Response.php @@ -0,0 +1,1286 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +// Help opcache.preload discover always-needed symbols +class_exists(ResponseHeaderBag::class); + +/** + * Response represents an HTTP response. + * + * @author Fabien Potencier + */ +class Response +{ + public const HTTP_CONTINUE = 100; + public const HTTP_SWITCHING_PROTOCOLS = 101; + public const HTTP_PROCESSING = 102; // RFC2518 + public const HTTP_EARLY_HINTS = 103; // RFC8297 + public const HTTP_OK = 200; + public const HTTP_CREATED = 201; + public const HTTP_ACCEPTED = 202; + public const HTTP_NON_AUTHORITATIVE_INFORMATION = 203; + public const HTTP_NO_CONTENT = 204; + public const HTTP_RESET_CONTENT = 205; + public const HTTP_PARTIAL_CONTENT = 206; + public const HTTP_MULTI_STATUS = 207; // RFC4918 + public const HTTP_ALREADY_REPORTED = 208; // RFC5842 + public const HTTP_IM_USED = 226; // RFC3229 + public const HTTP_MULTIPLE_CHOICES = 300; + public const HTTP_MOVED_PERMANENTLY = 301; + public const HTTP_FOUND = 302; + public const HTTP_SEE_OTHER = 303; + public const HTTP_NOT_MODIFIED = 304; + public const HTTP_USE_PROXY = 305; + public const HTTP_RESERVED = 306; + public const HTTP_TEMPORARY_REDIRECT = 307; + public const HTTP_PERMANENTLY_REDIRECT = 308; // RFC7238 + public const HTTP_BAD_REQUEST = 400; + public const HTTP_UNAUTHORIZED = 401; + public const HTTP_PAYMENT_REQUIRED = 402; + public const HTTP_FORBIDDEN = 403; + public const HTTP_NOT_FOUND = 404; + public const HTTP_METHOD_NOT_ALLOWED = 405; + public const HTTP_NOT_ACCEPTABLE = 406; + public const HTTP_PROXY_AUTHENTICATION_REQUIRED = 407; + public const HTTP_REQUEST_TIMEOUT = 408; + public const HTTP_CONFLICT = 409; + public const HTTP_GONE = 410; + public const HTTP_LENGTH_REQUIRED = 411; + public const HTTP_PRECONDITION_FAILED = 412; + public const HTTP_REQUEST_ENTITY_TOO_LARGE = 413; + public const HTTP_REQUEST_URI_TOO_LONG = 414; + public const HTTP_UNSUPPORTED_MEDIA_TYPE = 415; + public const HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416; + public const HTTP_EXPECTATION_FAILED = 417; + public const HTTP_I_AM_A_TEAPOT = 418; // RFC2324 + public const HTTP_MISDIRECTED_REQUEST = 421; // RFC7540 + public const HTTP_UNPROCESSABLE_ENTITY = 422; // RFC4918 + public const HTTP_LOCKED = 423; // RFC4918 + public const HTTP_FAILED_DEPENDENCY = 424; // RFC4918 + public const HTTP_TOO_EARLY = 425; // RFC-ietf-httpbis-replay-04 + public const HTTP_UPGRADE_REQUIRED = 426; // RFC2817 + public const HTTP_PRECONDITION_REQUIRED = 428; // RFC6585 + public const HTTP_TOO_MANY_REQUESTS = 429; // RFC6585 + public const HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431; // RFC6585 + public const HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451; + public const HTTP_INTERNAL_SERVER_ERROR = 500; + public const HTTP_NOT_IMPLEMENTED = 501; + public const HTTP_BAD_GATEWAY = 502; + public const HTTP_SERVICE_UNAVAILABLE = 503; + public const HTTP_GATEWAY_TIMEOUT = 504; + public const HTTP_VERSION_NOT_SUPPORTED = 505; + public const HTTP_VARIANT_ALSO_NEGOTIATES_EXPERIMENTAL = 506; // RFC2295 + public const HTTP_INSUFFICIENT_STORAGE = 507; // RFC4918 + public const HTTP_LOOP_DETECTED = 508; // RFC5842 + public const HTTP_NOT_EXTENDED = 510; // RFC2774 + public const HTTP_NETWORK_AUTHENTICATION_REQUIRED = 511; // RFC6585 + + /** + * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control + */ + private const HTTP_RESPONSE_CACHE_CONTROL_DIRECTIVES = [ + 'must_revalidate' => false, + 'no_cache' => false, + 'no_store' => false, + 'no_transform' => false, + 'public' => false, + 'private' => false, + 'proxy_revalidate' => false, + 'max_age' => true, + 's_maxage' => true, + 'immutable' => false, + 'last_modified' => true, + 'etag' => true, + ]; + + /** + * @var ResponseHeaderBag + */ + public $headers; + + /** + * @var string + */ + protected $content; + + /** + * @var string + */ + protected $version; + + /** + * @var int + */ + protected $statusCode; + + /** + * @var string + */ + protected $statusText; + + /** + * @var string + */ + protected $charset; + + /** + * Status codes translation table. + * + * The list of codes is complete according to the + * {@link https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml Hypertext Transfer Protocol (HTTP) Status Code Registry} + * (last updated 2021-10-01). + * + * Unless otherwise noted, the status code is defined in RFC2616. + * + * @var array + */ + public static $statusTexts = [ + 100 => 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', // RFC2518 + 103 => 'Early Hints', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-Status', // RFC4918 + 208 => 'Already Reported', // RFC5842 + 226 => 'IM Used', // RFC3229 + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 307 => 'Temporary Redirect', + 308 => 'Permanent Redirect', // RFC7238 + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Content Too Large', // RFC-ietf-httpbis-semantics + 414 => 'URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Range Not Satisfiable', + 417 => 'Expectation Failed', + 418 => 'I\'m a teapot', // RFC2324 + 421 => 'Misdirected Request', // RFC7540 + 422 => 'Unprocessable Content', // RFC-ietf-httpbis-semantics + 423 => 'Locked', // RFC4918 + 424 => 'Failed Dependency', // RFC4918 + 425 => 'Too Early', // RFC-ietf-httpbis-replay-04 + 426 => 'Upgrade Required', // RFC2817 + 428 => 'Precondition Required', // RFC6585 + 429 => 'Too Many Requests', // RFC6585 + 431 => 'Request Header Fields Too Large', // RFC6585 + 451 => 'Unavailable For Legal Reasons', // RFC7725 + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + 506 => 'Variant Also Negotiates', // RFC2295 + 507 => 'Insufficient Storage', // RFC4918 + 508 => 'Loop Detected', // RFC5842 + 510 => 'Not Extended', // RFC2774 + 511 => 'Network Authentication Required', // RFC6585 + ]; + + /** + * @throws \InvalidArgumentException When the HTTP status code is not valid + */ + public function __construct(?string $content = '', int $status = 200, array $headers = []) + { + $this->headers = new ResponseHeaderBag($headers); + $this->setContent($content); + $this->setStatusCode($status); + $this->setProtocolVersion('1.0'); + } + + /** + * Factory method for chainability. + * + * Example: + * + * return Response::create($body, 200) + * ->setSharedMaxAge(300); + * + * @return static + * + * @deprecated since Symfony 5.1, use __construct() instead. + */ + public static function create(?string $content = '', int $status = 200, array $headers = []) + { + trigger_deprecation('symfony/http-foundation', '5.1', 'The "%s()" method is deprecated, use "new %s()" instead.', __METHOD__, static::class); + + return new static($content, $status, $headers); + } + + /** + * Returns the Response as an HTTP string. + * + * The string representation of the Response is the same as the + * one that will be sent to the client only if the prepare() method + * has been called before. + * + * @return string + * + * @see prepare() + */ + public function __toString() + { + return + sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText)."\r\n". + $this->headers."\r\n". + $this->getContent(); + } + + /** + * Clones the current Response instance. + */ + public function __clone() + { + $this->headers = clone $this->headers; + } + + /** + * Prepares the Response before it is sent to the client. + * + * This method tweaks the Response to ensure that it is + * compliant with RFC 2616. Most of the changes are based on + * the Request that is "associated" with this Response. + * + * @return $this + */ + public function prepare(Request $request) + { + $headers = $this->headers; + + if ($this->isInformational() || $this->isEmpty()) { + $this->setContent(null); + $headers->remove('Content-Type'); + $headers->remove('Content-Length'); + // prevent PHP from sending the Content-Type header based on default_mimetype + ini_set('default_mimetype', ''); + } else { + // Content-type based on the Request + if (!$headers->has('Content-Type')) { + $format = $request->getRequestFormat(null); + if (null !== $format && $mimeType = $request->getMimeType($format)) { + $headers->set('Content-Type', $mimeType); + } + } + + // Fix Content-Type + $charset = $this->charset ?: 'UTF-8'; + if (!$headers->has('Content-Type')) { + $headers->set('Content-Type', 'text/html; charset='.$charset); + } elseif (0 === stripos($headers->get('Content-Type'), 'text/') && false === stripos($headers->get('Content-Type'), 'charset')) { + // add the charset + $headers->set('Content-Type', $headers->get('Content-Type').'; charset='.$charset); + } + + // Fix Content-Length + if ($headers->has('Transfer-Encoding')) { + $headers->remove('Content-Length'); + } + + if ($request->isMethod('HEAD')) { + // cf. RFC2616 14.13 + $length = $headers->get('Content-Length'); + $this->setContent(null); + if ($length) { + $headers->set('Content-Length', $length); + } + } + } + + // Fix protocol + if ('HTTP/1.0' != $request->server->get('SERVER_PROTOCOL')) { + $this->setProtocolVersion('1.1'); + } + + // Check if we need to send extra expire info headers + if ('1.0' == $this->getProtocolVersion() && str_contains($headers->get('Cache-Control', ''), 'no-cache')) { + $headers->set('pragma', 'no-cache'); + $headers->set('expires', -1); + } + + $this->ensureIEOverSSLCompatibility($request); + + if ($request->isSecure()) { + foreach ($headers->getCookies() as $cookie) { + $cookie->setSecureDefault(true); + } + } + + return $this; + } + + /** + * Sends HTTP headers. + * + * @return $this + */ + public function sendHeaders() + { + // headers have already been sent by the developer + if (headers_sent()) { + return $this; + } + + // headers + foreach ($this->headers->allPreserveCaseWithoutCookies() as $name => $values) { + $replace = 0 === strcasecmp($name, 'Content-Type'); + foreach ($values as $value) { + header($name.': '.$value, $replace, $this->statusCode); + } + } + + // cookies + foreach ($this->headers->getCookies() as $cookie) { + header('Set-Cookie: '.$cookie, false, $this->statusCode); + } + + // status + header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode); + + return $this; + } + + /** + * Sends content for the current web response. + * + * @return $this + */ + public function sendContent() + { + echo $this->content; + + return $this; + } + + /** + * Sends HTTP headers and content. + * + * @return $this + */ + public function send() + { + $this->sendHeaders(); + $this->sendContent(); + + if (\function_exists('fastcgi_finish_request')) { + fastcgi_finish_request(); + } elseif (\function_exists('litespeed_finish_request')) { + litespeed_finish_request(); + } elseif (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) { + static::closeOutputBuffers(0, true); + } + + return $this; + } + + /** + * Sets the response content. + * + * @return $this + */ + public function setContent(?string $content) + { + $this->content = $content ?? ''; + + return $this; + } + + /** + * Gets the current response content. + * + * @return string|false + */ + public function getContent() + { + return $this->content; + } + + /** + * Sets the HTTP protocol version (1.0 or 1.1). + * + * @return $this + * + * @final + */ + public function setProtocolVersion(string $version): object + { + $this->version = $version; + + return $this; + } + + /** + * Gets the HTTP protocol version. + * + * @final + */ + public function getProtocolVersion(): string + { + return $this->version; + } + + /** + * Sets the response status code. + * + * If the status text is null it will be automatically populated for the known + * status codes and left empty otherwise. + * + * @return $this + * + * @throws \InvalidArgumentException When the HTTP status code is not valid + * + * @final + */ + public function setStatusCode(int $code, string $text = null): object + { + $this->statusCode = $code; + if ($this->isInvalid()) { + throw new \InvalidArgumentException(sprintf('The HTTP status code "%s" is not valid.', $code)); + } + + if (null === $text) { + $this->statusText = self::$statusTexts[$code] ?? 'unknown status'; + + return $this; + } + + if (false === $text) { + $this->statusText = ''; + + return $this; + } + + $this->statusText = $text; + + return $this; + } + + /** + * Retrieves the status code for the current web response. + * + * @final + */ + public function getStatusCode(): int + { + return $this->statusCode; + } + + /** + * Sets the response charset. + * + * @return $this + * + * @final + */ + public function setCharset(string $charset): object + { + $this->charset = $charset; + + return $this; + } + + /** + * Retrieves the response charset. + * + * @final + */ + public function getCharset(): ?string + { + return $this->charset; + } + + /** + * Returns true if the response may safely be kept in a shared (surrogate) cache. + * + * Responses marked "private" with an explicit Cache-Control directive are + * considered uncacheable. + * + * Responses with neither a freshness lifetime (Expires, max-age) nor cache + * validator (Last-Modified, ETag) are considered uncacheable because there is + * no way to tell when or how to remove them from the cache. + * + * Note that RFC 7231 and RFC 7234 possibly allow for a more permissive implementation, + * for example "status codes that are defined as cacheable by default [...] + * can be reused by a cache with heuristic expiration unless otherwise indicated" + * (https://tools.ietf.org/html/rfc7231#section-6.1) + * + * @final + */ + public function isCacheable(): bool + { + if (!\in_array($this->statusCode, [200, 203, 300, 301, 302, 404, 410])) { + return false; + } + + if ($this->headers->hasCacheControlDirective('no-store') || $this->headers->getCacheControlDirective('private')) { + return false; + } + + return $this->isValidateable() || $this->isFresh(); + } + + /** + * Returns true if the response is "fresh". + * + * Fresh responses may be served from cache without any interaction with the + * origin. A response is considered fresh when it includes a Cache-Control/max-age + * indicator or Expires header and the calculated age is less than the freshness lifetime. + * + * @final + */ + public function isFresh(): bool + { + return $this->getTtl() > 0; + } + + /** + * Returns true if the response includes headers that can be used to validate + * the response with the origin server using a conditional GET request. + * + * @final + */ + public function isValidateable(): bool + { + return $this->headers->has('Last-Modified') || $this->headers->has('ETag'); + } + + /** + * Marks the response as "private". + * + * It makes the response ineligible for serving other clients. + * + * @return $this + * + * @final + */ + public function setPrivate(): object + { + $this->headers->removeCacheControlDirective('public'); + $this->headers->addCacheControlDirective('private'); + + return $this; + } + + /** + * Marks the response as "public". + * + * It makes the response eligible for serving other clients. + * + * @return $this + * + * @final + */ + public function setPublic(): object + { + $this->headers->addCacheControlDirective('public'); + $this->headers->removeCacheControlDirective('private'); + + return $this; + } + + /** + * Marks the response as "immutable". + * + * @return $this + * + * @final + */ + public function setImmutable(bool $immutable = true): object + { + if ($immutable) { + $this->headers->addCacheControlDirective('immutable'); + } else { + $this->headers->removeCacheControlDirective('immutable'); + } + + return $this; + } + + /** + * Returns true if the response is marked as "immutable". + * + * @final + */ + public function isImmutable(): bool + { + return $this->headers->hasCacheControlDirective('immutable'); + } + + /** + * Returns true if the response must be revalidated by shared caches once it has become stale. + * + * This method indicates that the response must not be served stale by a + * cache in any circumstance without first revalidating with the origin. + * When present, the TTL of the response should not be overridden to be + * greater than the value provided by the origin. + * + * @final + */ + public function mustRevalidate(): bool + { + return $this->headers->hasCacheControlDirective('must-revalidate') || $this->headers->hasCacheControlDirective('proxy-revalidate'); + } + + /** + * Returns the Date header as a DateTime instance. + * + * @throws \RuntimeException When the header is not parseable + * + * @final + */ + public function getDate(): ?\DateTimeInterface + { + return $this->headers->getDate('Date'); + } + + /** + * Sets the Date header. + * + * @return $this + * + * @final + */ + public function setDate(\DateTimeInterface $date): object + { + if ($date instanceof \DateTime) { + $date = \DateTimeImmutable::createFromMutable($date); + } + + $date = $date->setTimezone(new \DateTimeZone('UTC')); + $this->headers->set('Date', $date->format('D, d M Y H:i:s').' GMT'); + + return $this; + } + + /** + * Returns the age of the response in seconds. + * + * @final + */ + public function getAge(): int + { + if (null !== $age = $this->headers->get('Age')) { + return (int) $age; + } + + return max(time() - (int) $this->getDate()->format('U'), 0); + } + + /** + * Marks the response stale by setting the Age header to be equal to the maximum age of the response. + * + * @return $this + */ + public function expire() + { + if ($this->isFresh()) { + $this->headers->set('Age', $this->getMaxAge()); + $this->headers->remove('Expires'); + } + + return $this; + } + + /** + * Returns the value of the Expires header as a DateTime instance. + * + * @final + */ + public function getExpires(): ?\DateTimeInterface + { + try { + return $this->headers->getDate('Expires'); + } catch (\RuntimeException $e) { + // according to RFC 2616 invalid date formats (e.g. "0" and "-1") must be treated as in the past + return \DateTime::createFromFormat('U', time() - 172800); + } + } + + /** + * Sets the Expires HTTP header with a DateTime instance. + * + * Passing null as value will remove the header. + * + * @return $this + * + * @final + */ + public function setExpires(\DateTimeInterface $date = null): object + { + if (null === $date) { + $this->headers->remove('Expires'); + + return $this; + } + + if ($date instanceof \DateTime) { + $date = \DateTimeImmutable::createFromMutable($date); + } + + $date = $date->setTimezone(new \DateTimeZone('UTC')); + $this->headers->set('Expires', $date->format('D, d M Y H:i:s').' GMT'); + + return $this; + } + + /** + * Returns the number of seconds after the time specified in the response's Date + * header when the response should no longer be considered fresh. + * + * First, it checks for a s-maxage directive, then a max-age directive, and then it falls + * back on an expires header. It returns null when no maximum age can be established. + * + * @final + */ + public function getMaxAge(): ?int + { + if ($this->headers->hasCacheControlDirective('s-maxage')) { + return (int) $this->headers->getCacheControlDirective('s-maxage'); + } + + if ($this->headers->hasCacheControlDirective('max-age')) { + return (int) $this->headers->getCacheControlDirective('max-age'); + } + + if (null !== $this->getExpires()) { + return (int) $this->getExpires()->format('U') - (int) $this->getDate()->format('U'); + } + + return null; + } + + /** + * Sets the number of seconds after which the response should no longer be considered fresh. + * + * This methods sets the Cache-Control max-age directive. + * + * @return $this + * + * @final + */ + public function setMaxAge(int $value): object + { + $this->headers->addCacheControlDirective('max-age', $value); + + return $this; + } + + /** + * Sets the number of seconds after which the response should no longer be considered fresh by shared caches. + * + * This methods sets the Cache-Control s-maxage directive. + * + * @return $this + * + * @final + */ + public function setSharedMaxAge(int $value): object + { + $this->setPublic(); + $this->headers->addCacheControlDirective('s-maxage', $value); + + return $this; + } + + /** + * Returns the response's time-to-live in seconds. + * + * It returns null when no freshness information is present in the response. + * + * When the responses TTL is <= 0, the response may not be served from cache without first + * revalidating with the origin. + * + * @final + */ + public function getTtl(): ?int + { + $maxAge = $this->getMaxAge(); + + return null !== $maxAge ? $maxAge - $this->getAge() : null; + } + + /** + * Sets the response's time-to-live for shared caches in seconds. + * + * This method adjusts the Cache-Control/s-maxage directive. + * + * @return $this + * + * @final + */ + public function setTtl(int $seconds): object + { + $this->setSharedMaxAge($this->getAge() + $seconds); + + return $this; + } + + /** + * Sets the response's time-to-live for private/client caches in seconds. + * + * This method adjusts the Cache-Control/max-age directive. + * + * @return $this + * + * @final + */ + public function setClientTtl(int $seconds): object + { + $this->setMaxAge($this->getAge() + $seconds); + + return $this; + } + + /** + * Returns the Last-Modified HTTP header as a DateTime instance. + * + * @throws \RuntimeException When the HTTP header is not parseable + * + * @final + */ + public function getLastModified(): ?\DateTimeInterface + { + return $this->headers->getDate('Last-Modified'); + } + + /** + * Sets the Last-Modified HTTP header with a DateTime instance. + * + * Passing null as value will remove the header. + * + * @return $this + * + * @final + */ + public function setLastModified(\DateTimeInterface $date = null): object + { + if (null === $date) { + $this->headers->remove('Last-Modified'); + + return $this; + } + + if ($date instanceof \DateTime) { + $date = \DateTimeImmutable::createFromMutable($date); + } + + $date = $date->setTimezone(new \DateTimeZone('UTC')); + $this->headers->set('Last-Modified', $date->format('D, d M Y H:i:s').' GMT'); + + return $this; + } + + /** + * Returns the literal value of the ETag HTTP header. + * + * @final + */ + public function getEtag(): ?string + { + return $this->headers->get('ETag'); + } + + /** + * Sets the ETag value. + * + * @param string|null $etag The ETag unique identifier or null to remove the header + * @param bool $weak Whether you want a weak ETag or not + * + * @return $this + * + * @final + */ + public function setEtag(string $etag = null, bool $weak = false): object + { + if (null === $etag) { + $this->headers->remove('Etag'); + } else { + if (!str_starts_with($etag, '"')) { + $etag = '"'.$etag.'"'; + } + + $this->headers->set('ETag', (true === $weak ? 'W/' : '').$etag); + } + + return $this; + } + + /** + * Sets the response's cache headers (validation and/or expiration). + * + * Available options are: must_revalidate, no_cache, no_store, no_transform, public, private, proxy_revalidate, max_age, s_maxage, immutable, last_modified and etag. + * + * @return $this + * + * @throws \InvalidArgumentException + * + * @final + */ + public function setCache(array $options): object + { + if ($diff = array_diff(array_keys($options), array_keys(self::HTTP_RESPONSE_CACHE_CONTROL_DIRECTIVES))) { + throw new \InvalidArgumentException(sprintf('Response does not support the following options: "%s".', implode('", "', $diff))); + } + + if (isset($options['etag'])) { + $this->setEtag($options['etag']); + } + + if (isset($options['last_modified'])) { + $this->setLastModified($options['last_modified']); + } + + if (isset($options['max_age'])) { + $this->setMaxAge($options['max_age']); + } + + if (isset($options['s_maxage'])) { + $this->setSharedMaxAge($options['s_maxage']); + } + + foreach (self::HTTP_RESPONSE_CACHE_CONTROL_DIRECTIVES as $directive => $hasValue) { + if (!$hasValue && isset($options[$directive])) { + if ($options[$directive]) { + $this->headers->addCacheControlDirective(str_replace('_', '-', $directive)); + } else { + $this->headers->removeCacheControlDirective(str_replace('_', '-', $directive)); + } + } + } + + if (isset($options['public'])) { + if ($options['public']) { + $this->setPublic(); + } else { + $this->setPrivate(); + } + } + + if (isset($options['private'])) { + if ($options['private']) { + $this->setPrivate(); + } else { + $this->setPublic(); + } + } + + return $this; + } + + /** + * Modifies the response so that it conforms to the rules defined for a 304 status code. + * + * This sets the status, removes the body, and discards any headers + * that MUST NOT be included in 304 responses. + * + * @return $this + * + * @see https://tools.ietf.org/html/rfc2616#section-10.3.5 + * + * @final + */ + public function setNotModified(): object + { + $this->setStatusCode(304); + $this->setContent(null); + + // remove headers that MUST NOT be included with 304 Not Modified responses + foreach (['Allow', 'Content-Encoding', 'Content-Language', 'Content-Length', 'Content-MD5', 'Content-Type', 'Last-Modified'] as $header) { + $this->headers->remove($header); + } + + return $this; + } + + /** + * Returns true if the response includes a Vary header. + * + * @final + */ + public function hasVary(): bool + { + return null !== $this->headers->get('Vary'); + } + + /** + * Returns an array of header names given in the Vary header. + * + * @final + */ + public function getVary(): array + { + if (!$vary = $this->headers->all('Vary')) { + return []; + } + + $ret = []; + foreach ($vary as $item) { + $ret[] = preg_split('/[\s,]+/', $item); + } + + return array_merge([], ...$ret); + } + + /** + * Sets the Vary header. + * + * @param string|array $headers + * @param bool $replace Whether to replace the actual value or not (true by default) + * + * @return $this + * + * @final + */ + public function setVary($headers, bool $replace = true): object + { + $this->headers->set('Vary', $headers, $replace); + + return $this; + } + + /** + * Determines if the Response validators (ETag, Last-Modified) match + * a conditional value specified in the Request. + * + * If the Response is not modified, it sets the status code to 304 and + * removes the actual content by calling the setNotModified() method. + * + * @final + */ + public function isNotModified(Request $request): bool + { + if (!$request->isMethodCacheable()) { + return false; + } + + $notModified = false; + $lastModified = $this->headers->get('Last-Modified'); + $modifiedSince = $request->headers->get('If-Modified-Since'); + + if ($ifNoneMatchEtags = $request->getETags()) { + $etag = $this->getEtag(); + if (0 == strncmp($etag, 'W/', 2)) { + $etag = substr($etag, 2); + } + + // Use weak comparison as per https://tools.ietf.org/html/rfc7232#section-3.2. + foreach ($ifNoneMatchEtags as $ifNoneMatchEtag) { + if (0 == strncmp($ifNoneMatchEtag, 'W/', 2)) { + $ifNoneMatchEtag = substr($ifNoneMatchEtag, 2); + } + + if ($ifNoneMatchEtag === $etag || '*' === $ifNoneMatchEtag) { + $notModified = true; + break; + } + } + } + // Only do If-Modified-Since date comparison when If-None-Match is not present as per https://tools.ietf.org/html/rfc7232#section-3.3. + elseif ($modifiedSince && $lastModified) { + $notModified = strtotime($modifiedSince) >= strtotime($lastModified); + } + + if ($notModified) { + $this->setNotModified(); + } + + return $notModified; + } + + /** + * Is response invalid? + * + * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html + * + * @final + */ + public function isInvalid(): bool + { + return $this->statusCode < 100 || $this->statusCode >= 600; + } + + /** + * Is response informative? + * + * @final + */ + public function isInformational(): bool + { + return $this->statusCode >= 100 && $this->statusCode < 200; + } + + /** + * Is response successful? + * + * @final + */ + public function isSuccessful(): bool + { + return $this->statusCode >= 200 && $this->statusCode < 300; + } + + /** + * Is the response a redirect? + * + * @final + */ + public function isRedirection(): bool + { + return $this->statusCode >= 300 && $this->statusCode < 400; + } + + /** + * Is there a client error? + * + * @final + */ + public function isClientError(): bool + { + return $this->statusCode >= 400 && $this->statusCode < 500; + } + + /** + * Was there a server side error? + * + * @final + */ + public function isServerError(): bool + { + return $this->statusCode >= 500 && $this->statusCode < 600; + } + + /** + * Is the response OK? + * + * @final + */ + public function isOk(): bool + { + return 200 === $this->statusCode; + } + + /** + * Is the response forbidden? + * + * @final + */ + public function isForbidden(): bool + { + return 403 === $this->statusCode; + } + + /** + * Is the response a not found error? + * + * @final + */ + public function isNotFound(): bool + { + return 404 === $this->statusCode; + } + + /** + * Is the response a redirect of some form? + * + * @final + */ + public function isRedirect(string $location = null): bool + { + return \in_array($this->statusCode, [201, 301, 302, 303, 307, 308]) && (null === $location ?: $location == $this->headers->get('Location')); + } + + /** + * Is the response empty? + * + * @final + */ + public function isEmpty(): bool + { + return \in_array($this->statusCode, [204, 304]); + } + + /** + * Cleans or flushes output buffers up to target level. + * + * Resulting level can be greater than target level if a non-removable buffer has been encountered. + * + * @final + */ + public static function closeOutputBuffers(int $targetLevel, bool $flush): void + { + $status = ob_get_status(true); + $level = \count($status); + $flags = \PHP_OUTPUT_HANDLER_REMOVABLE | ($flush ? \PHP_OUTPUT_HANDLER_FLUSHABLE : \PHP_OUTPUT_HANDLER_CLEANABLE); + + while ($level-- > $targetLevel && ($s = $status[$level]) && (!isset($s['del']) ? !isset($s['flags']) || ($s['flags'] & $flags) === $flags : $s['del'])) { + if ($flush) { + ob_end_flush(); + } else { + ob_end_clean(); + } + } + } + + /** + * Marks a response as safe according to RFC8674. + * + * @see https://tools.ietf.org/html/rfc8674 + */ + public function setContentSafe(bool $safe = true): void + { + if ($safe) { + $this->headers->set('Preference-Applied', 'safe'); + } elseif ('safe' === $this->headers->get('Preference-Applied')) { + $this->headers->remove('Preference-Applied'); + } + + $this->setVary('Prefer', false); + } + + /** + * Checks if we need to remove Cache-Control for SSL encrypted downloads when using IE < 9. + * + * @see http://support.microsoft.com/kb/323308 + * + * @final + */ + protected function ensureIEOverSSLCompatibility(Request $request): void + { + if (false !== stripos($this->headers->get('Content-Disposition') ?? '', 'attachment') && 1 == preg_match('/MSIE (.*?);/i', $request->server->get('HTTP_USER_AGENT') ?? '', $match) && true === $request->isSecure()) { + if ((int) preg_replace('/(MSIE )(.*?);/', '$2', $match[0]) < 9) { + $this->headers->remove('Cache-Control'); + } + } + } +} diff --git a/vendor/symfony/http-foundation/ResponseHeaderBag.php b/vendor/symfony/http-foundation/ResponseHeaderBag.php new file mode 100644 index 0000000..1df13fa --- /dev/null +++ b/vendor/symfony/http-foundation/ResponseHeaderBag.php @@ -0,0 +1,291 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * ResponseHeaderBag is a container for Response HTTP headers. + * + * @author Fabien Potencier + */ +class ResponseHeaderBag extends HeaderBag +{ + public const COOKIES_FLAT = 'flat'; + public const COOKIES_ARRAY = 'array'; + + public const DISPOSITION_ATTACHMENT = 'attachment'; + public const DISPOSITION_INLINE = 'inline'; + + protected $computedCacheControl = []; + protected $cookies = []; + protected $headerNames = []; + + public function __construct(array $headers = []) + { + parent::__construct($headers); + + if (!isset($this->headers['cache-control'])) { + $this->set('Cache-Control', ''); + } + + /* RFC2616 - 14.18 says all Responses need to have a Date */ + if (!isset($this->headers['date'])) { + $this->initDate(); + } + } + + /** + * Returns the headers, with original capitalizations. + * + * @return array + */ + public function allPreserveCase() + { + $headers = []; + foreach ($this->all() as $name => $value) { + $headers[$this->headerNames[$name] ?? $name] = $value; + } + + return $headers; + } + + public function allPreserveCaseWithoutCookies() + { + $headers = $this->allPreserveCase(); + if (isset($this->headerNames['set-cookie'])) { + unset($headers[$this->headerNames['set-cookie']]); + } + + return $headers; + } + + /** + * {@inheritdoc} + */ + public function replace(array $headers = []) + { + $this->headerNames = []; + + parent::replace($headers); + + if (!isset($this->headers['cache-control'])) { + $this->set('Cache-Control', ''); + } + + if (!isset($this->headers['date'])) { + $this->initDate(); + } + } + + /** + * {@inheritdoc} + */ + public function all(string $key = null) + { + $headers = parent::all(); + + if (null !== $key) { + $key = strtr($key, self::UPPER, self::LOWER); + + return 'set-cookie' !== $key ? $headers[$key] ?? [] : array_map('strval', $this->getCookies()); + } + + foreach ($this->getCookies() as $cookie) { + $headers['set-cookie'][] = (string) $cookie; + } + + return $headers; + } + + /** + * {@inheritdoc} + */ + public function set(string $key, $values, bool $replace = true) + { + $uniqueKey = strtr($key, self::UPPER, self::LOWER); + + if ('set-cookie' === $uniqueKey) { + if ($replace) { + $this->cookies = []; + } + foreach ((array) $values as $cookie) { + $this->setCookie(Cookie::fromString($cookie)); + } + $this->headerNames[$uniqueKey] = $key; + + return; + } + + $this->headerNames[$uniqueKey] = $key; + + parent::set($key, $values, $replace); + + // ensure the cache-control header has sensible defaults + if (\in_array($uniqueKey, ['cache-control', 'etag', 'last-modified', 'expires'], true) && '' !== $computed = $this->computeCacheControlValue()) { + $this->headers['cache-control'] = [$computed]; + $this->headerNames['cache-control'] = 'Cache-Control'; + $this->computedCacheControl = $this->parseCacheControl($computed); + } + } + + /** + * {@inheritdoc} + */ + public function remove(string $key) + { + $uniqueKey = strtr($key, self::UPPER, self::LOWER); + unset($this->headerNames[$uniqueKey]); + + if ('set-cookie' === $uniqueKey) { + $this->cookies = []; + + return; + } + + parent::remove($key); + + if ('cache-control' === $uniqueKey) { + $this->computedCacheControl = []; + } + + if ('date' === $uniqueKey) { + $this->initDate(); + } + } + + /** + * {@inheritdoc} + */ + public function hasCacheControlDirective(string $key) + { + return \array_key_exists($key, $this->computedCacheControl); + } + + /** + * {@inheritdoc} + */ + public function getCacheControlDirective(string $key) + { + return $this->computedCacheControl[$key] ?? null; + } + + public function setCookie(Cookie $cookie) + { + $this->cookies[$cookie->getDomain()][$cookie->getPath()][$cookie->getName()] = $cookie; + $this->headerNames['set-cookie'] = 'Set-Cookie'; + } + + /** + * Removes a cookie from the array, but does not unset it in the browser. + */ + public function removeCookie(string $name, ?string $path = '/', string $domain = null) + { + if (null === $path) { + $path = '/'; + } + + unset($this->cookies[$domain][$path][$name]); + + if (empty($this->cookies[$domain][$path])) { + unset($this->cookies[$domain][$path]); + + if (empty($this->cookies[$domain])) { + unset($this->cookies[$domain]); + } + } + + if (empty($this->cookies)) { + unset($this->headerNames['set-cookie']); + } + } + + /** + * Returns an array with all cookies. + * + * @return Cookie[] + * + * @throws \InvalidArgumentException When the $format is invalid + */ + public function getCookies(string $format = self::COOKIES_FLAT) + { + if (!\in_array($format, [self::COOKIES_FLAT, self::COOKIES_ARRAY])) { + throw new \InvalidArgumentException(sprintf('Format "%s" invalid (%s).', $format, implode(', ', [self::COOKIES_FLAT, self::COOKIES_ARRAY]))); + } + + if (self::COOKIES_ARRAY === $format) { + return $this->cookies; + } + + $flattenedCookies = []; + foreach ($this->cookies as $path) { + foreach ($path as $cookies) { + foreach ($cookies as $cookie) { + $flattenedCookies[] = $cookie; + } + } + } + + return $flattenedCookies; + } + + /** + * Clears a cookie in the browser. + */ + public function clearCookie(string $name, ?string $path = '/', string $domain = null, bool $secure = false, bool $httpOnly = true, string $sameSite = null) + { + $this->setCookie(new Cookie($name, null, 1, $path, $domain, $secure, $httpOnly, false, $sameSite)); + } + + /** + * @see HeaderUtils::makeDisposition() + */ + public function makeDisposition(string $disposition, string $filename, string $filenameFallback = '') + { + return HeaderUtils::makeDisposition($disposition, $filename, $filenameFallback); + } + + /** + * Returns the calculated value of the cache-control header. + * + * This considers several other headers and calculates or modifies the + * cache-control header to a sensible, conservative value. + * + * @return string + */ + protected function computeCacheControlValue() + { + if (!$this->cacheControl) { + if ($this->has('Last-Modified') || $this->has('Expires')) { + return 'private, must-revalidate'; // allows for heuristic expiration (RFC 7234 Section 4.2.2) in the case of "Last-Modified" + } + + // conservative by default + return 'no-cache, private'; + } + + $header = $this->getCacheControlHeader(); + if (isset($this->cacheControl['public']) || isset($this->cacheControl['private'])) { + return $header; + } + + // public if s-maxage is defined, private otherwise + if (!isset($this->cacheControl['s-maxage'])) { + return $header.', private'; + } + + return $header; + } + + private function initDate(): void + { + $this->set('Date', gmdate('D, d M Y H:i:s').' GMT'); + } +} diff --git a/vendor/symfony/http-foundation/ServerBag.php b/vendor/symfony/http-foundation/ServerBag.php new file mode 100644 index 0000000..7af111c --- /dev/null +++ b/vendor/symfony/http-foundation/ServerBag.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * ServerBag is a container for HTTP headers from the $_SERVER variable. + * + * @author Fabien Potencier + * @author Bulat Shakirzyanov + * @author Robert Kiss + */ +class ServerBag extends ParameterBag +{ + /** + * Gets the HTTP headers. + * + * @return array + */ + public function getHeaders() + { + $headers = []; + foreach ($this->parameters as $key => $value) { + if (str_starts_with($key, 'HTTP_')) { + $headers[substr($key, 5)] = $value; + } elseif (\in_array($key, ['CONTENT_TYPE', 'CONTENT_LENGTH', 'CONTENT_MD5'], true)) { + $headers[$key] = $value; + } + } + + if (isset($this->parameters['PHP_AUTH_USER'])) { + $headers['PHP_AUTH_USER'] = $this->parameters['PHP_AUTH_USER']; + $headers['PHP_AUTH_PW'] = $this->parameters['PHP_AUTH_PW'] ?? ''; + } else { + /* + * php-cgi under Apache does not pass HTTP Basic user/pass to PHP by default + * For this workaround to work, add these lines to your .htaccess file: + * RewriteCond %{HTTP:Authorization} .+ + * RewriteRule ^ - [E=HTTP_AUTHORIZATION:%0] + * + * A sample .htaccess file: + * RewriteEngine On + * RewriteCond %{HTTP:Authorization} .+ + * RewriteRule ^ - [E=HTTP_AUTHORIZATION:%0] + * RewriteCond %{REQUEST_FILENAME} !-f + * RewriteRule ^(.*)$ app.php [QSA,L] + */ + + $authorizationHeader = null; + if (isset($this->parameters['HTTP_AUTHORIZATION'])) { + $authorizationHeader = $this->parameters['HTTP_AUTHORIZATION']; + } elseif (isset($this->parameters['REDIRECT_HTTP_AUTHORIZATION'])) { + $authorizationHeader = $this->parameters['REDIRECT_HTTP_AUTHORIZATION']; + } + + if (null !== $authorizationHeader) { + if (0 === stripos($authorizationHeader, 'basic ')) { + // Decode AUTHORIZATION header into PHP_AUTH_USER and PHP_AUTH_PW when authorization header is basic + $exploded = explode(':', base64_decode(substr($authorizationHeader, 6)), 2); + if (2 == \count($exploded)) { + [$headers['PHP_AUTH_USER'], $headers['PHP_AUTH_PW']] = $exploded; + } + } elseif (empty($this->parameters['PHP_AUTH_DIGEST']) && (0 === stripos($authorizationHeader, 'digest '))) { + // In some circumstances PHP_AUTH_DIGEST needs to be set + $headers['PHP_AUTH_DIGEST'] = $authorizationHeader; + $this->parameters['PHP_AUTH_DIGEST'] = $authorizationHeader; + } elseif (0 === stripos($authorizationHeader, 'bearer ')) { + /* + * XXX: Since there is no PHP_AUTH_BEARER in PHP predefined variables, + * I'll just set $headers['AUTHORIZATION'] here. + * https://php.net/reserved.variables.server + */ + $headers['AUTHORIZATION'] = $authorizationHeader; + } + } + } + + if (isset($headers['AUTHORIZATION'])) { + return $headers; + } + + // PHP_AUTH_USER/PHP_AUTH_PW + if (isset($headers['PHP_AUTH_USER'])) { + $headers['AUTHORIZATION'] = 'Basic '.base64_encode($headers['PHP_AUTH_USER'].':'.$headers['PHP_AUTH_PW']); + } elseif (isset($headers['PHP_AUTH_DIGEST'])) { + $headers['AUTHORIZATION'] = $headers['PHP_AUTH_DIGEST']; + } + + return $headers; + } +} diff --git a/vendor/symfony/http-foundation/Session/Attribute/AttributeBag.php b/vendor/symfony/http-foundation/Session/Attribute/AttributeBag.php new file mode 100644 index 0000000..f4f051c --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Attribute/AttributeBag.php @@ -0,0 +1,152 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Attribute; + +/** + * This class relates to session attribute storage. + * + * @implements \IteratorAggregate + */ +class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Countable +{ + private $name = 'attributes'; + private $storageKey; + + protected $attributes = []; + + /** + * @param string $storageKey The key used to store attributes in the session + */ + public function __construct(string $storageKey = '_sf2_attributes') + { + $this->storageKey = $storageKey; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + public function setName(string $name) + { + $this->name = $name; + } + + /** + * {@inheritdoc} + */ + public function initialize(array &$attributes) + { + $this->attributes = &$attributes; + } + + /** + * {@inheritdoc} + */ + public function getStorageKey() + { + return $this->storageKey; + } + + /** + * {@inheritdoc} + */ + public function has(string $name) + { + return \array_key_exists($name, $this->attributes); + } + + /** + * {@inheritdoc} + */ + public function get(string $name, $default = null) + { + return \array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $default; + } + + /** + * {@inheritdoc} + */ + public function set(string $name, $value) + { + $this->attributes[$name] = $value; + } + + /** + * {@inheritdoc} + */ + public function all() + { + return $this->attributes; + } + + /** + * {@inheritdoc} + */ + public function replace(array $attributes) + { + $this->attributes = []; + foreach ($attributes as $key => $value) { + $this->set($key, $value); + } + } + + /** + * {@inheritdoc} + */ + public function remove(string $name) + { + $retval = null; + if (\array_key_exists($name, $this->attributes)) { + $retval = $this->attributes[$name]; + unset($this->attributes[$name]); + } + + return $retval; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + $return = $this->attributes; + $this->attributes = []; + + return $return; + } + + /** + * Returns an iterator for attributes. + * + * @return \ArrayIterator + */ + #[\ReturnTypeWillChange] + public function getIterator() + { + return new \ArrayIterator($this->attributes); + } + + /** + * Returns the number of attributes. + * + * @return int + */ + #[\ReturnTypeWillChange] + public function count() + { + return \count($this->attributes); + } +} diff --git a/vendor/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php b/vendor/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php new file mode 100644 index 0000000..cb50696 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Attribute; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; + +/** + * Attributes store. + * + * @author Drak + */ +interface AttributeBagInterface extends SessionBagInterface +{ + /** + * Checks if an attribute is defined. + * + * @return bool + */ + public function has(string $name); + + /** + * Returns an attribute. + * + * @param mixed $default The default value if not found + * + * @return mixed + */ + public function get(string $name, $default = null); + + /** + * Sets an attribute. + * + * @param mixed $value + */ + public function set(string $name, $value); + + /** + * Returns attributes. + * + * @return array + */ + public function all(); + + public function replace(array $attributes); + + /** + * Removes an attribute. + * + * @return mixed The removed value or null when it does not exist + */ + public function remove(string $name); +} diff --git a/vendor/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php b/vendor/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php new file mode 100644 index 0000000..864b35f --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php @@ -0,0 +1,161 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Attribute; + +trigger_deprecation('symfony/http-foundation', '5.3', 'The "%s" class is deprecated.', NamespacedAttributeBag::class); + +/** + * This class provides structured storage of session attributes using + * a name spacing character in the key. + * + * @author Drak + * + * @deprecated since Symfony 5.3 + */ +class NamespacedAttributeBag extends AttributeBag +{ + private $namespaceCharacter; + + /** + * @param string $storageKey Session storage key + * @param string $namespaceCharacter Namespace character to use in keys + */ + public function __construct(string $storageKey = '_sf2_attributes', string $namespaceCharacter = '/') + { + $this->namespaceCharacter = $namespaceCharacter; + parent::__construct($storageKey); + } + + /** + * {@inheritdoc} + */ + public function has(string $name) + { + // reference mismatch: if fixed, re-introduced in array_key_exists; keep as it is + $attributes = $this->resolveAttributePath($name); + $name = $this->resolveKey($name); + + if (null === $attributes) { + return false; + } + + return \array_key_exists($name, $attributes); + } + + /** + * {@inheritdoc} + */ + public function get(string $name, $default = null) + { + // reference mismatch: if fixed, re-introduced in array_key_exists; keep as it is + $attributes = $this->resolveAttributePath($name); + $name = $this->resolveKey($name); + + if (null === $attributes) { + return $default; + } + + return \array_key_exists($name, $attributes) ? $attributes[$name] : $default; + } + + /** + * {@inheritdoc} + */ + public function set(string $name, $value) + { + $attributes = &$this->resolveAttributePath($name, true); + $name = $this->resolveKey($name); + $attributes[$name] = $value; + } + + /** + * {@inheritdoc} + */ + public function remove(string $name) + { + $retval = null; + $attributes = &$this->resolveAttributePath($name); + $name = $this->resolveKey($name); + if (null !== $attributes && \array_key_exists($name, $attributes)) { + $retval = $attributes[$name]; + unset($attributes[$name]); + } + + return $retval; + } + + /** + * Resolves a path in attributes property and returns it as a reference. + * + * This method allows structured namespacing of session attributes. + * + * @param string $name Key name + * @param bool $writeContext Write context, default false + * + * @return array|null + */ + protected function &resolveAttributePath(string $name, bool $writeContext = false) + { + $array = &$this->attributes; + $name = (str_starts_with($name, $this->namespaceCharacter)) ? substr($name, 1) : $name; + + // Check if there is anything to do, else return + if (!$name) { + return $array; + } + + $parts = explode($this->namespaceCharacter, $name); + if (\count($parts) < 2) { + if (!$writeContext) { + return $array; + } + + $array[$parts[0]] = []; + + return $array; + } + + unset($parts[\count($parts) - 1]); + + foreach ($parts as $part) { + if (null !== $array && !\array_key_exists($part, $array)) { + if (!$writeContext) { + $null = null; + + return $null; + } + + $array[$part] = []; + } + + $array = &$array[$part]; + } + + return $array; + } + + /** + * Resolves the key from the name. + * + * This is the last part in a dot separated string. + * + * @return string + */ + protected function resolveKey(string $name) + { + if (false !== $pos = strrpos($name, $this->namespaceCharacter)) { + $name = substr($name, $pos + 1); + } + + return $name; + } +} diff --git a/vendor/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php b/vendor/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php new file mode 100644 index 0000000..8aab3a1 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php @@ -0,0 +1,161 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Flash; + +/** + * AutoExpireFlashBag flash message container. + * + * @author Drak + */ +class AutoExpireFlashBag implements FlashBagInterface +{ + private $name = 'flashes'; + private $flashes = ['display' => [], 'new' => []]; + private $storageKey; + + /** + * @param string $storageKey The key used to store flashes in the session + */ + public function __construct(string $storageKey = '_symfony_flashes') + { + $this->storageKey = $storageKey; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + public function setName(string $name) + { + $this->name = $name; + } + + /** + * {@inheritdoc} + */ + public function initialize(array &$flashes) + { + $this->flashes = &$flashes; + + // The logic: messages from the last request will be stored in new, so we move them to previous + // This request we will show what is in 'display'. What is placed into 'new' this time round will + // be moved to display next time round. + $this->flashes['display'] = \array_key_exists('new', $this->flashes) ? $this->flashes['new'] : []; + $this->flashes['new'] = []; + } + + /** + * {@inheritdoc} + */ + public function add(string $type, $message) + { + $this->flashes['new'][$type][] = $message; + } + + /** + * {@inheritdoc} + */ + public function peek(string $type, array $default = []) + { + return $this->has($type) ? $this->flashes['display'][$type] : $default; + } + + /** + * {@inheritdoc} + */ + public function peekAll() + { + return \array_key_exists('display', $this->flashes) ? $this->flashes['display'] : []; + } + + /** + * {@inheritdoc} + */ + public function get(string $type, array $default = []) + { + $return = $default; + + if (!$this->has($type)) { + return $return; + } + + if (isset($this->flashes['display'][$type])) { + $return = $this->flashes['display'][$type]; + unset($this->flashes['display'][$type]); + } + + return $return; + } + + /** + * {@inheritdoc} + */ + public function all() + { + $return = $this->flashes['display']; + $this->flashes['display'] = []; + + return $return; + } + + /** + * {@inheritdoc} + */ + public function setAll(array $messages) + { + $this->flashes['new'] = $messages; + } + + /** + * {@inheritdoc} + */ + public function set(string $type, $messages) + { + $this->flashes['new'][$type] = (array) $messages; + } + + /** + * {@inheritdoc} + */ + public function has(string $type) + { + return \array_key_exists($type, $this->flashes['display']) && $this->flashes['display'][$type]; + } + + /** + * {@inheritdoc} + */ + public function keys() + { + return array_keys($this->flashes['display']); + } + + /** + * {@inheritdoc} + */ + public function getStorageKey() + { + return $this->storageKey; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + return $this->all(); + } +} diff --git a/vendor/symfony/http-foundation/Session/Flash/FlashBag.php b/vendor/symfony/http-foundation/Session/Flash/FlashBag.php new file mode 100644 index 0000000..88df750 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Flash/FlashBag.php @@ -0,0 +1,152 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Flash; + +/** + * FlashBag flash message container. + * + * @author Drak + */ +class FlashBag implements FlashBagInterface +{ + private $name = 'flashes'; + private $flashes = []; + private $storageKey; + + /** + * @param string $storageKey The key used to store flashes in the session + */ + public function __construct(string $storageKey = '_symfony_flashes') + { + $this->storageKey = $storageKey; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + public function setName(string $name) + { + $this->name = $name; + } + + /** + * {@inheritdoc} + */ + public function initialize(array &$flashes) + { + $this->flashes = &$flashes; + } + + /** + * {@inheritdoc} + */ + public function add(string $type, $message) + { + $this->flashes[$type][] = $message; + } + + /** + * {@inheritdoc} + */ + public function peek(string $type, array $default = []) + { + return $this->has($type) ? $this->flashes[$type] : $default; + } + + /** + * {@inheritdoc} + */ + public function peekAll() + { + return $this->flashes; + } + + /** + * {@inheritdoc} + */ + public function get(string $type, array $default = []) + { + if (!$this->has($type)) { + return $default; + } + + $return = $this->flashes[$type]; + + unset($this->flashes[$type]); + + return $return; + } + + /** + * {@inheritdoc} + */ + public function all() + { + $return = $this->peekAll(); + $this->flashes = []; + + return $return; + } + + /** + * {@inheritdoc} + */ + public function set(string $type, $messages) + { + $this->flashes[$type] = (array) $messages; + } + + /** + * {@inheritdoc} + */ + public function setAll(array $messages) + { + $this->flashes = $messages; + } + + /** + * {@inheritdoc} + */ + public function has(string $type) + { + return \array_key_exists($type, $this->flashes) && $this->flashes[$type]; + } + + /** + * {@inheritdoc} + */ + public function keys() + { + return array_keys($this->flashes); + } + + /** + * {@inheritdoc} + */ + public function getStorageKey() + { + return $this->storageKey; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + return $this->all(); + } +} diff --git a/vendor/symfony/http-foundation/Session/Flash/FlashBagInterface.php b/vendor/symfony/http-foundation/Session/Flash/FlashBagInterface.php new file mode 100644 index 0000000..8713e71 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Flash/FlashBagInterface.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Flash; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; + +/** + * FlashBagInterface. + * + * @author Drak + */ +interface FlashBagInterface extends SessionBagInterface +{ + /** + * Adds a flash message for the given type. + * + * @param mixed $message + */ + public function add(string $type, $message); + + /** + * Registers one or more messages for a given type. + * + * @param string|array $messages + */ + public function set(string $type, $messages); + + /** + * Gets flash messages for a given type. + * + * @param string $type Message category type + * @param array $default Default value if $type does not exist + * + * @return array + */ + public function peek(string $type, array $default = []); + + /** + * Gets all flash messages. + * + * @return array + */ + public function peekAll(); + + /** + * Gets and clears flash from the stack. + * + * @param array $default Default value if $type does not exist + * + * @return array + */ + public function get(string $type, array $default = []); + + /** + * Gets and clears flashes from the stack. + * + * @return array + */ + public function all(); + + /** + * Sets all flash messages. + */ + public function setAll(array $messages); + + /** + * Has flash messages for a given type? + * + * @return bool + */ + public function has(string $type); + + /** + * Returns a list of all defined types. + * + * @return array + */ + public function keys(); +} diff --git a/vendor/symfony/http-foundation/Session/Session.php b/vendor/symfony/http-foundation/Session/Session.php new file mode 100644 index 0000000..022e398 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Session.php @@ -0,0 +1,285 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; +use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBagInterface; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBag; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface; +use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; +use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(AttributeBag::class); +class_exists(FlashBag::class); +class_exists(SessionBagProxy::class); + +/** + * @author Fabien Potencier + * @author Drak + * + * @implements \IteratorAggregate + */ +class Session implements SessionInterface, \IteratorAggregate, \Countable +{ + protected $storage; + + private $flashName; + private $attributeName; + private $data = []; + private $usageIndex = 0; + private $usageReporter; + + public function __construct(SessionStorageInterface $storage = null, AttributeBagInterface $attributes = null, FlashBagInterface $flashes = null, callable $usageReporter = null) + { + $this->storage = $storage ?? new NativeSessionStorage(); + $this->usageReporter = $usageReporter; + + $attributes = $attributes ?? new AttributeBag(); + $this->attributeName = $attributes->getName(); + $this->registerBag($attributes); + + $flashes = $flashes ?? new FlashBag(); + $this->flashName = $flashes->getName(); + $this->registerBag($flashes); + } + + /** + * {@inheritdoc} + */ + public function start() + { + return $this->storage->start(); + } + + /** + * {@inheritdoc} + */ + public function has(string $name) + { + return $this->getAttributeBag()->has($name); + } + + /** + * {@inheritdoc} + */ + public function get(string $name, $default = null) + { + return $this->getAttributeBag()->get($name, $default); + } + + /** + * {@inheritdoc} + */ + public function set(string $name, $value) + { + $this->getAttributeBag()->set($name, $value); + } + + /** + * {@inheritdoc} + */ + public function all() + { + return $this->getAttributeBag()->all(); + } + + /** + * {@inheritdoc} + */ + public function replace(array $attributes) + { + $this->getAttributeBag()->replace($attributes); + } + + /** + * {@inheritdoc} + */ + public function remove(string $name) + { + return $this->getAttributeBag()->remove($name); + } + + /** + * {@inheritdoc} + */ + public function clear() + { + $this->getAttributeBag()->clear(); + } + + /** + * {@inheritdoc} + */ + public function isStarted() + { + return $this->storage->isStarted(); + } + + /** + * Returns an iterator for attributes. + * + * @return \ArrayIterator + */ + #[\ReturnTypeWillChange] + public function getIterator() + { + return new \ArrayIterator($this->getAttributeBag()->all()); + } + + /** + * Returns the number of attributes. + * + * @return int + */ + #[\ReturnTypeWillChange] + public function count() + { + return \count($this->getAttributeBag()->all()); + } + + public function &getUsageIndex(): int + { + return $this->usageIndex; + } + + /** + * @internal + */ + public function isEmpty(): bool + { + if ($this->isStarted()) { + ++$this->usageIndex; + if ($this->usageReporter && 0 <= $this->usageIndex) { + ($this->usageReporter)(); + } + } + foreach ($this->data as &$data) { + if (!empty($data)) { + return false; + } + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function invalidate(int $lifetime = null) + { + $this->storage->clear(); + + return $this->migrate(true, $lifetime); + } + + /** + * {@inheritdoc} + */ + public function migrate(bool $destroy = false, int $lifetime = null) + { + return $this->storage->regenerate($destroy, $lifetime); + } + + /** + * {@inheritdoc} + */ + public function save() + { + $this->storage->save(); + } + + /** + * {@inheritdoc} + */ + public function getId() + { + return $this->storage->getId(); + } + + /** + * {@inheritdoc} + */ + public function setId(string $id) + { + if ($this->storage->getId() !== $id) { + $this->storage->setId($id); + } + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->storage->getName(); + } + + /** + * {@inheritdoc} + */ + public function setName(string $name) + { + $this->storage->setName($name); + } + + /** + * {@inheritdoc} + */ + public function getMetadataBag() + { + ++$this->usageIndex; + if ($this->usageReporter && 0 <= $this->usageIndex) { + ($this->usageReporter)(); + } + + return $this->storage->getMetadataBag(); + } + + /** + * {@inheritdoc} + */ + public function registerBag(SessionBagInterface $bag) + { + $this->storage->registerBag(new SessionBagProxy($bag, $this->data, $this->usageIndex, $this->usageReporter)); + } + + /** + * {@inheritdoc} + */ + public function getBag(string $name) + { + $bag = $this->storage->getBag($name); + + return method_exists($bag, 'getBag') ? $bag->getBag() : $bag; + } + + /** + * Gets the flashbag interface. + * + * @return FlashBagInterface + */ + public function getFlashBag() + { + return $this->getBag($this->flashName); + } + + /** + * Gets the attributebag interface. + * + * Note that this method was added to help with IDE autocompletion. + */ + private function getAttributeBag(): AttributeBagInterface + { + return $this->getBag($this->attributeName); + } +} diff --git a/vendor/symfony/http-foundation/Session/SessionBagInterface.php b/vendor/symfony/http-foundation/Session/SessionBagInterface.php new file mode 100644 index 0000000..8e37d06 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/SessionBagInterface.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +/** + * Session Bag store. + * + * @author Drak + */ +interface SessionBagInterface +{ + /** + * Gets this bag's name. + * + * @return string + */ + public function getName(); + + /** + * Initializes the Bag. + */ + public function initialize(array &$array); + + /** + * Gets the storage key for this bag. + * + * @return string + */ + public function getStorageKey(); + + /** + * Clears out data from bag. + * + * @return mixed Whatever data was contained + */ + public function clear(); +} diff --git a/vendor/symfony/http-foundation/Session/SessionBagProxy.php b/vendor/symfony/http-foundation/Session/SessionBagProxy.php new file mode 100644 index 0000000..90aa010 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/SessionBagProxy.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +/** + * @author Nicolas Grekas + * + * @internal + */ +final class SessionBagProxy implements SessionBagInterface +{ + private $bag; + private $data; + private $usageIndex; + private $usageReporter; + + public function __construct(SessionBagInterface $bag, array &$data, ?int &$usageIndex, ?callable $usageReporter) + { + $this->bag = $bag; + $this->data = &$data; + $this->usageIndex = &$usageIndex; + $this->usageReporter = $usageReporter; + } + + public function getBag(): SessionBagInterface + { + ++$this->usageIndex; + if ($this->usageReporter && 0 <= $this->usageIndex) { + ($this->usageReporter)(); + } + + return $this->bag; + } + + public function isEmpty(): bool + { + if (!isset($this->data[$this->bag->getStorageKey()])) { + return true; + } + ++$this->usageIndex; + if ($this->usageReporter && 0 <= $this->usageIndex) { + ($this->usageReporter)(); + } + + return empty($this->data[$this->bag->getStorageKey()]); + } + + /** + * {@inheritdoc} + */ + public function getName(): string + { + return $this->bag->getName(); + } + + /** + * {@inheritdoc} + */ + public function initialize(array &$array): void + { + ++$this->usageIndex; + if ($this->usageReporter && 0 <= $this->usageIndex) { + ($this->usageReporter)(); + } + + $this->data[$this->bag->getStorageKey()] = &$array; + + $this->bag->initialize($array); + } + + /** + * {@inheritdoc} + */ + public function getStorageKey(): string + { + return $this->bag->getStorageKey(); + } + + /** + * {@inheritdoc} + */ + public function clear() + { + return $this->bag->clear(); + } +} diff --git a/vendor/symfony/http-foundation/Session/SessionFactory.php b/vendor/symfony/http-foundation/Session/SessionFactory.php new file mode 100644 index 0000000..04c4b06 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/SessionFactory.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageFactoryInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(Session::class); + +/** + * @author Jérémy Derussé + */ +class SessionFactory implements SessionFactoryInterface +{ + private $requestStack; + private $storageFactory; + private $usageReporter; + + public function __construct(RequestStack $requestStack, SessionStorageFactoryInterface $storageFactory, callable $usageReporter = null) + { + $this->requestStack = $requestStack; + $this->storageFactory = $storageFactory; + $this->usageReporter = $usageReporter; + } + + public function createSession(): SessionInterface + { + return new Session($this->storageFactory->createStorage($this->requestStack->getMainRequest()), null, null, $this->usageReporter); + } +} diff --git a/vendor/symfony/http-foundation/Session/SessionFactoryInterface.php b/vendor/symfony/http-foundation/Session/SessionFactoryInterface.php new file mode 100644 index 0000000..b24fdc4 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/SessionFactoryInterface.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +/** + * @author Kevin Bond + */ +interface SessionFactoryInterface +{ + public function createSession(): SessionInterface; +} diff --git a/vendor/symfony/http-foundation/Session/SessionInterface.php b/vendor/symfony/http-foundation/Session/SessionInterface.php new file mode 100644 index 0000000..b2f09fd --- /dev/null +++ b/vendor/symfony/http-foundation/Session/SessionInterface.php @@ -0,0 +1,166 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +use Symfony\Component\HttpFoundation\Session\Storage\MetadataBag; + +/** + * Interface for the session. + * + * @author Drak + */ +interface SessionInterface +{ + /** + * Starts the session storage. + * + * @return bool + * + * @throws \RuntimeException if session fails to start + */ + public function start(); + + /** + * Returns the session ID. + * + * @return string + */ + public function getId(); + + /** + * Sets the session ID. + */ + public function setId(string $id); + + /** + * Returns the session name. + * + * @return string + */ + public function getName(); + + /** + * Sets the session name. + */ + public function setName(string $name); + + /** + * Invalidates the current session. + * + * Clears all session attributes and flashes and regenerates the + * session and deletes the old session from persistence. + * + * @param int $lifetime Sets the cookie lifetime for the session cookie. A null value + * will leave the system settings unchanged, 0 sets the cookie + * to expire with browser session. Time is in seconds, and is + * not a Unix timestamp. + * + * @return bool + */ + public function invalidate(int $lifetime = null); + + /** + * Migrates the current session to a new session id while maintaining all + * session attributes. + * + * @param bool $destroy Whether to delete the old session or leave it to garbage collection + * @param int $lifetime Sets the cookie lifetime for the session cookie. A null value + * will leave the system settings unchanged, 0 sets the cookie + * to expire with browser session. Time is in seconds, and is + * not a Unix timestamp. + * + * @return bool + */ + public function migrate(bool $destroy = false, int $lifetime = null); + + /** + * Force the session to be saved and closed. + * + * This method is generally not required for real sessions as + * the session will be automatically saved at the end of + * code execution. + */ + public function save(); + + /** + * Checks if an attribute is defined. + * + * @return bool + */ + public function has(string $name); + + /** + * Returns an attribute. + * + * @param mixed $default The default value if not found + * + * @return mixed + */ + public function get(string $name, $default = null); + + /** + * Sets an attribute. + * + * @param mixed $value + */ + public function set(string $name, $value); + + /** + * Returns attributes. + * + * @return array + */ + public function all(); + + /** + * Sets attributes. + */ + public function replace(array $attributes); + + /** + * Removes an attribute. + * + * @return mixed The removed value or null when it does not exist + */ + public function remove(string $name); + + /** + * Clears all attributes. + */ + public function clear(); + + /** + * Checks if the session was started. + * + * @return bool + */ + public function isStarted(); + + /** + * Registers a SessionBagInterface with the session. + */ + public function registerBag(SessionBagInterface $bag); + + /** + * Gets a bag instance by name. + * + * @return SessionBagInterface + */ + public function getBag(string $name); + + /** + * Gets session meta. + * + * @return MetadataBag + */ + public function getMetadataBag(); +} diff --git a/vendor/symfony/http-foundation/Session/SessionUtils.php b/vendor/symfony/http-foundation/Session/SessionUtils.php new file mode 100644 index 0000000..b5bce4a --- /dev/null +++ b/vendor/symfony/http-foundation/Session/SessionUtils.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +/** + * Session utility functions. + * + * @author Nicolas Grekas + * @author Rémon van de Kamp + * + * @internal + */ +final class SessionUtils +{ + /** + * Finds the session header amongst the headers that are to be sent, removes it, and returns + * it so the caller can process it further. + */ + public static function popSessionCookie(string $sessionName, string $sessionId): ?string + { + $sessionCookie = null; + $sessionCookiePrefix = sprintf(' %s=', urlencode($sessionName)); + $sessionCookieWithId = sprintf('%s%s;', $sessionCookiePrefix, urlencode($sessionId)); + $otherCookies = []; + foreach (headers_list() as $h) { + if (0 !== stripos($h, 'Set-Cookie:')) { + continue; + } + if (11 === strpos($h, $sessionCookiePrefix, 11)) { + $sessionCookie = $h; + + if (11 !== strpos($h, $sessionCookieWithId, 11)) { + $otherCookies[] = $h; + } + } else { + $otherCookies[] = $h; + } + } + if (null === $sessionCookie) { + return null; + } + + header_remove('Set-Cookie'); + foreach ($otherCookies as $h) { + header($h, false); + } + + return $sessionCookie; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php new file mode 100644 index 0000000..bc7b944 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php @@ -0,0 +1,155 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +use Symfony\Component\HttpFoundation\Session\SessionUtils; + +/** + * This abstract session handler provides a generic implementation + * of the PHP 7.0 SessionUpdateTimestampHandlerInterface, + * enabling strict and lazy session handling. + * + * @author Nicolas Grekas + */ +abstract class AbstractSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface +{ + private $sessionName; + private $prefetchId; + private $prefetchData; + private $newSessionId; + private $igbinaryEmptyData; + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function open($savePath, $sessionName) + { + $this->sessionName = $sessionName; + if (!headers_sent() && !ini_get('session.cache_limiter') && '0' !== ini_get('session.cache_limiter')) { + header(sprintf('Cache-Control: max-age=%d, private, must-revalidate', 60 * (int) ini_get('session.cache_expire'))); + } + + return true; + } + + /** + * @return string + */ + abstract protected function doRead(string $sessionId); + + /** + * @return bool + */ + abstract protected function doWrite(string $sessionId, string $data); + + /** + * @return bool + */ + abstract protected function doDestroy(string $sessionId); + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function validateId($sessionId) + { + $this->prefetchData = $this->read($sessionId); + $this->prefetchId = $sessionId; + + if (\PHP_VERSION_ID < 70317 || (70400 <= \PHP_VERSION_ID && \PHP_VERSION_ID < 70405)) { + // work around https://bugs.php.net/79413 + foreach (debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS) as $frame) { + if (!isset($frame['class']) && isset($frame['function']) && \in_array($frame['function'], ['session_regenerate_id', 'session_create_id'], true)) { + return '' === $this->prefetchData; + } + } + } + + return '' !== $this->prefetchData; + } + + /** + * @return string + */ + #[\ReturnTypeWillChange] + public function read($sessionId) + { + if (null !== $this->prefetchId) { + $prefetchId = $this->prefetchId; + $prefetchData = $this->prefetchData; + $this->prefetchId = $this->prefetchData = null; + + if ($prefetchId === $sessionId || '' === $prefetchData) { + $this->newSessionId = '' === $prefetchData ? $sessionId : null; + + return $prefetchData; + } + } + + $data = $this->doRead($sessionId); + $this->newSessionId = '' === $data ? $sessionId : null; + + return $data; + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function write($sessionId, $data) + { + if (null === $this->igbinaryEmptyData) { + // see https://github.com/igbinary/igbinary/issues/146 + $this->igbinaryEmptyData = \function_exists('igbinary_serialize') ? igbinary_serialize([]) : ''; + } + if ('' === $data || $this->igbinaryEmptyData === $data) { + return $this->destroy($sessionId); + } + $this->newSessionId = null; + + return $this->doWrite($sessionId, $data); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function destroy($sessionId) + { + if (!headers_sent() && filter_var(ini_get('session.use_cookies'), \FILTER_VALIDATE_BOOLEAN)) { + if (!$this->sessionName) { + throw new \LogicException(sprintf('Session name cannot be empty, did you forget to call "parent::open()" in "%s"?.', static::class)); + } + $cookie = SessionUtils::popSessionCookie($this->sessionName, $sessionId); + + /* + * We send an invalidation Set-Cookie header (zero lifetime) + * when either the session was started or a cookie with + * the session name was sent by the client (in which case + * we know it's invalid as a valid session cookie would've + * started the session). + */ + if (null === $cookie || isset($_COOKIE[$this->sessionName])) { + if (\PHP_VERSION_ID < 70300) { + setcookie($this->sessionName, '', 0, ini_get('session.cookie_path'), ini_get('session.cookie_domain'), filter_var(ini_get('session.cookie_secure'), \FILTER_VALIDATE_BOOLEAN), filter_var(ini_get('session.cookie_httponly'), \FILTER_VALIDATE_BOOLEAN)); + } else { + $params = session_get_cookie_params(); + unset($params['lifetime']); + setcookie($this->sessionName, '', $params); + } + } + } + + return $this->newSessionId === $sessionId || $this->doDestroy($sessionId); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/IdentityMarshaller.php b/vendor/symfony/http-foundation/Session/Storage/Handler/IdentityMarshaller.php new file mode 100644 index 0000000..bea3a32 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/IdentityMarshaller.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +use Symfony\Component\Cache\Marshaller\MarshallerInterface; + +/** + * @author Ahmed TAILOULOUTE + */ +class IdentityMarshaller implements MarshallerInterface +{ + /** + * {@inheritdoc} + */ + public function marshall(array $values, ?array &$failed): array + { + foreach ($values as $key => $value) { + if (!\is_string($value)) { + throw new \LogicException(sprintf('%s accepts only string as data.', __METHOD__)); + } + } + + return $values; + } + + /** + * {@inheritdoc} + */ + public function unmarshall(string $value): string + { + return $value; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/MarshallingSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/MarshallingSessionHandler.php new file mode 100644 index 0000000..c321c8c --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/MarshallingSessionHandler.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +use Symfony\Component\Cache\Marshaller\MarshallerInterface; + +/** + * @author Ahmed TAILOULOUTE + */ +class MarshallingSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface +{ + private $handler; + private $marshaller; + + public function __construct(AbstractSessionHandler $handler, MarshallerInterface $marshaller) + { + $this->handler = $handler; + $this->marshaller = $marshaller; + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function open($savePath, $name) + { + return $this->handler->open($savePath, $name); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function close() + { + return $this->handler->close(); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function destroy($sessionId) + { + return $this->handler->destroy($sessionId); + } + + /** + * @return int|false + */ + #[\ReturnTypeWillChange] + public function gc($maxlifetime) + { + return $this->handler->gc($maxlifetime); + } + + /** + * @return string + */ + #[\ReturnTypeWillChange] + public function read($sessionId) + { + return $this->marshaller->unmarshall($this->handler->read($sessionId)); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function write($sessionId, $data) + { + $failed = []; + $marshalledData = $this->marshaller->marshall(['data' => $data], $failed); + + if (isset($failed['data'])) { + return false; + } + + return $this->handler->write($sessionId, $marshalledData['data']); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function validateId($sessionId) + { + return $this->handler->validateId($sessionId); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function updateTimestamp($sessionId, $data) + { + return $this->handler->updateTimestamp($sessionId, $data); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php new file mode 100644 index 0000000..a5a78eb --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * Memcached based session storage handler based on the Memcached class + * provided by the PHP memcached extension. + * + * @see https://php.net/memcached + * + * @author Drak + */ +class MemcachedSessionHandler extends AbstractSessionHandler +{ + private $memcached; + + /** + * @var int Time to live in seconds + */ + private $ttl; + + /** + * @var string Key prefix for shared environments + */ + private $prefix; + + /** + * Constructor. + * + * List of available options: + * * prefix: The prefix to use for the memcached keys in order to avoid collision + * * ttl: The time to live in seconds. + * + * @throws \InvalidArgumentException When unsupported options are passed + */ + public function __construct(\Memcached $memcached, array $options = []) + { + $this->memcached = $memcached; + + if ($diff = array_diff(array_keys($options), ['prefix', 'expiretime', 'ttl'])) { + throw new \InvalidArgumentException(sprintf('The following options are not supported "%s".', implode(', ', $diff))); + } + + $this->ttl = $options['expiretime'] ?? $options['ttl'] ?? null; + $this->prefix = $options['prefix'] ?? 'sf2s'; + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function close() + { + return $this->memcached->quit(); + } + + /** + * {@inheritdoc} + */ + protected function doRead(string $sessionId) + { + return $this->memcached->get($this->prefix.$sessionId) ?: ''; + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function updateTimestamp($sessionId, $data) + { + $this->memcached->touch($this->prefix.$sessionId, time() + (int) ($this->ttl ?? ini_get('session.gc_maxlifetime'))); + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doWrite(string $sessionId, string $data) + { + return $this->memcached->set($this->prefix.$sessionId, $data, time() + (int) ($this->ttl ?? ini_get('session.gc_maxlifetime'))); + } + + /** + * {@inheritdoc} + */ + protected function doDestroy(string $sessionId) + { + $result = $this->memcached->delete($this->prefix.$sessionId); + + return $result || \Memcached::RES_NOTFOUND == $this->memcached->getResultCode(); + } + + /** + * @return int|false + */ + #[\ReturnTypeWillChange] + public function gc($maxlifetime) + { + // not required here because memcached will auto expire the records anyhow. + return 0; + } + + /** + * Return a Memcached instance. + * + * @return \Memcached + */ + protected function getMemcached() + { + return $this->memcached; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/MigratingSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/MigratingSessionHandler.php new file mode 100644 index 0000000..bf27ca6 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/MigratingSessionHandler.php @@ -0,0 +1,139 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * Migrating session handler for migrating from one handler to another. It reads + * from the current handler and writes both the current and new ones. + * + * It ignores errors from the new handler. + * + * @author Ross Motley + * @author Oliver Radwell + */ +class MigratingSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface +{ + /** + * @var \SessionHandlerInterface&\SessionUpdateTimestampHandlerInterface + */ + private $currentHandler; + + /** + * @var \SessionHandlerInterface&\SessionUpdateTimestampHandlerInterface + */ + private $writeOnlyHandler; + + public function __construct(\SessionHandlerInterface $currentHandler, \SessionHandlerInterface $writeOnlyHandler) + { + if (!$currentHandler instanceof \SessionUpdateTimestampHandlerInterface) { + $currentHandler = new StrictSessionHandler($currentHandler); + } + if (!$writeOnlyHandler instanceof \SessionUpdateTimestampHandlerInterface) { + $writeOnlyHandler = new StrictSessionHandler($writeOnlyHandler); + } + + $this->currentHandler = $currentHandler; + $this->writeOnlyHandler = $writeOnlyHandler; + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function close() + { + $result = $this->currentHandler->close(); + $this->writeOnlyHandler->close(); + + return $result; + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function destroy($sessionId) + { + $result = $this->currentHandler->destroy($sessionId); + $this->writeOnlyHandler->destroy($sessionId); + + return $result; + } + + /** + * @return int|false + */ + #[\ReturnTypeWillChange] + public function gc($maxlifetime) + { + $result = $this->currentHandler->gc($maxlifetime); + $this->writeOnlyHandler->gc($maxlifetime); + + return $result; + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function open($savePath, $sessionName) + { + $result = $this->currentHandler->open($savePath, $sessionName); + $this->writeOnlyHandler->open($savePath, $sessionName); + + return $result; + } + + /** + * @return string + */ + #[\ReturnTypeWillChange] + public function read($sessionId) + { + // No reading from new handler until switch-over + return $this->currentHandler->read($sessionId); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function write($sessionId, $sessionData) + { + $result = $this->currentHandler->write($sessionId, $sessionData); + $this->writeOnlyHandler->write($sessionId, $sessionData); + + return $result; + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function validateId($sessionId) + { + // No reading from new handler until switch-over + return $this->currentHandler->validateId($sessionId); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function updateTimestamp($sessionId, $sessionData) + { + $result = $this->currentHandler->updateTimestamp($sessionId, $sessionData); + $this->writeOnlyHandler->updateTimestamp($sessionId, $sessionData); + + return $result; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php new file mode 100644 index 0000000..8384e79 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php @@ -0,0 +1,193 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +use MongoDB\BSON\Binary; +use MongoDB\BSON\UTCDateTime; +use MongoDB\Client; +use MongoDB\Collection; + +/** + * Session handler using the mongodb/mongodb package and MongoDB driver extension. + * + * @author Markus Bachmann + * + * @see https://packagist.org/packages/mongodb/mongodb + * @see https://php.net/mongodb + */ +class MongoDbSessionHandler extends AbstractSessionHandler +{ + private $mongo; + + /** + * @var Collection + */ + private $collection; + + /** + * @var array + */ + private $options; + + /** + * Constructor. + * + * List of available options: + * * database: The name of the database [required] + * * collection: The name of the collection [required] + * * id_field: The field name for storing the session id [default: _id] + * * data_field: The field name for storing the session data [default: data] + * * time_field: The field name for storing the timestamp [default: time] + * * expiry_field: The field name for storing the expiry-timestamp [default: expires_at]. + * + * It is strongly recommended to put an index on the `expiry_field` for + * garbage-collection. Alternatively it's possible to automatically expire + * the sessions in the database as described below: + * + * A TTL collections can be used on MongoDB 2.2+ to cleanup expired sessions + * automatically. Such an index can for example look like this: + * + * db..createIndex( + * { "": 1 }, + * { "expireAfterSeconds": 0 } + * ) + * + * More details on: https://docs.mongodb.org/manual/tutorial/expire-data/ + * + * If you use such an index, you can drop `gc_probability` to 0 since + * no garbage-collection is required. + * + * @throws \InvalidArgumentException When "database" or "collection" not provided + */ + public function __construct(Client $mongo, array $options) + { + if (!isset($options['database']) || !isset($options['collection'])) { + throw new \InvalidArgumentException('You must provide the "database" and "collection" option for MongoDBSessionHandler.'); + } + + $this->mongo = $mongo; + + $this->options = array_merge([ + 'id_field' => '_id', + 'data_field' => 'data', + 'time_field' => 'time', + 'expiry_field' => 'expires_at', + ], $options); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function close() + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function doDestroy(string $sessionId) + { + $this->getCollection()->deleteOne([ + $this->options['id_field'] => $sessionId, + ]); + + return true; + } + + /** + * @return int|false + */ + #[\ReturnTypeWillChange] + public function gc($maxlifetime) + { + return $this->getCollection()->deleteMany([ + $this->options['expiry_field'] => ['$lt' => new UTCDateTime()], + ])->getDeletedCount(); + } + + /** + * {@inheritdoc} + */ + protected function doWrite(string $sessionId, string $data) + { + $expiry = new UTCDateTime((time() + (int) ini_get('session.gc_maxlifetime')) * 1000); + + $fields = [ + $this->options['time_field'] => new UTCDateTime(), + $this->options['expiry_field'] => $expiry, + $this->options['data_field'] => new Binary($data, Binary::TYPE_OLD_BINARY), + ]; + + $this->getCollection()->updateOne( + [$this->options['id_field'] => $sessionId], + ['$set' => $fields], + ['upsert' => true] + ); + + return true; + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function updateTimestamp($sessionId, $data) + { + $expiry = new UTCDateTime((time() + (int) ini_get('session.gc_maxlifetime')) * 1000); + + $this->getCollection()->updateOne( + [$this->options['id_field'] => $sessionId], + ['$set' => [ + $this->options['time_field'] => new UTCDateTime(), + $this->options['expiry_field'] => $expiry, + ]] + ); + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doRead(string $sessionId) + { + $dbData = $this->getCollection()->findOne([ + $this->options['id_field'] => $sessionId, + $this->options['expiry_field'] => ['$gte' => new UTCDateTime()], + ]); + + if (null === $dbData) { + return ''; + } + + return $dbData[$this->options['data_field']]->getData(); + } + + private function getCollection(): Collection + { + if (null === $this->collection) { + $this->collection = $this->mongo->selectCollection($this->options['database'], $this->options['collection']); + } + + return $this->collection; + } + + /** + * @return Client + */ + protected function getMongo() + { + return $this->mongo; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php new file mode 100644 index 0000000..effc9db --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * Native session handler using PHP's built in file storage. + * + * @author Drak + */ +class NativeFileSessionHandler extends \SessionHandler +{ + /** + * @param string $savePath Path of directory to save session files + * Default null will leave setting as defined by PHP. + * '/path', 'N;/path', or 'N;octal-mode;/path + * + * @see https://php.net/session.configuration#ini.session.save-path for further details. + * + * @throws \InvalidArgumentException On invalid $savePath + * @throws \RuntimeException When failing to create the save directory + */ + public function __construct(string $savePath = null) + { + if (null === $savePath) { + $savePath = ini_get('session.save_path'); + } + + $baseDir = $savePath; + + if ($count = substr_count($savePath, ';')) { + if ($count > 2) { + throw new \InvalidArgumentException(sprintf('Invalid argument $savePath \'%s\'.', $savePath)); + } + + // characters after last ';' are the path + $baseDir = ltrim(strrchr($savePath, ';'), ';'); + } + + if ($baseDir && !is_dir($baseDir) && !@mkdir($baseDir, 0777, true) && !is_dir($baseDir)) { + throw new \RuntimeException(sprintf('Session Storage was not able to create directory "%s".', $baseDir)); + } + + ini_set('session.save_path', $savePath); + ini_set('session.save_handler', 'files'); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php new file mode 100644 index 0000000..4331dbe --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * Can be used in unit testing or in a situations where persisted sessions are not desired. + * + * @author Drak + */ +class NullSessionHandler extends AbstractSessionHandler +{ + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function close() + { + return true; + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function validateId($sessionId) + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function doRead(string $sessionId) + { + return ''; + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function updateTimestamp($sessionId, $data) + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function doWrite(string $sessionId, string $data) + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function doDestroy(string $sessionId) + { + return true; + } + + /** + * @return int|false + */ + #[\ReturnTypeWillChange] + public function gc($maxlifetime) + { + return 0; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php new file mode 100644 index 0000000..0b9e1c6 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php @@ -0,0 +1,943 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * Session handler using a PDO connection to read and write data. + * + * It works with MySQL, PostgreSQL, Oracle, SQL Server and SQLite and implements + * different locking strategies to handle concurrent access to the same session. + * Locking is necessary to prevent loss of data due to race conditions and to keep + * the session data consistent between read() and write(). With locking, requests + * for the same session will wait until the other one finished writing. For this + * reason it's best practice to close a session as early as possible to improve + * concurrency. PHPs internal files session handler also implements locking. + * + * Attention: Since SQLite does not support row level locks but locks the whole database, + * it means only one session can be accessed at a time. Even different sessions would wait + * for another to finish. So saving session in SQLite should only be considered for + * development or prototypes. + * + * Session data is a binary string that can contain non-printable characters like the null byte. + * For this reason it must be saved in a binary column in the database like BLOB in MySQL. + * Saving it in a character column could corrupt the data. You can use createTable() + * to initialize a correctly defined table. + * + * @see https://php.net/sessionhandlerinterface + * + * @author Fabien Potencier + * @author Michael Williams + * @author Tobias Schultze + */ +class PdoSessionHandler extends AbstractSessionHandler +{ + /** + * No locking is done. This means sessions are prone to loss of data due to + * race conditions of concurrent requests to the same session. The last session + * write will win in this case. It might be useful when you implement your own + * logic to deal with this like an optimistic approach. + */ + public const LOCK_NONE = 0; + + /** + * Creates an application-level lock on a session. The disadvantage is that the + * lock is not enforced by the database and thus other, unaware parts of the + * application could still concurrently modify the session. The advantage is it + * does not require a transaction. + * This mode is not available for SQLite and not yet implemented for oci and sqlsrv. + */ + public const LOCK_ADVISORY = 1; + + /** + * Issues a real row lock. Since it uses a transaction between opening and + * closing a session, you have to be careful when you use same database connection + * that you also use for your application logic. This mode is the default because + * it's the only reliable solution across DBMSs. + */ + public const LOCK_TRANSACTIONAL = 2; + + private const MAX_LIFETIME = 315576000; + + /** + * @var \PDO|null PDO instance or null when not connected yet + */ + private $pdo; + + /** + * DSN string or null for session.save_path or false when lazy connection disabled. + * + * @var string|false|null + */ + private $dsn = false; + + /** + * @var string|null + */ + private $driver; + + /** + * @var string + */ + private $table = 'sessions'; + + /** + * @var string + */ + private $idCol = 'sess_id'; + + /** + * @var string + */ + private $dataCol = 'sess_data'; + + /** + * @var string + */ + private $lifetimeCol = 'sess_lifetime'; + + /** + * @var string + */ + private $timeCol = 'sess_time'; + + /** + * Username when lazy-connect. + * + * @var string + */ + private $username = ''; + + /** + * Password when lazy-connect. + * + * @var string + */ + private $password = ''; + + /** + * Connection options when lazy-connect. + * + * @var array + */ + private $connectionOptions = []; + + /** + * The strategy for locking, see constants. + * + * @var int + */ + private $lockMode = self::LOCK_TRANSACTIONAL; + + /** + * It's an array to support multiple reads before closing which is manual, non-standard usage. + * + * @var \PDOStatement[] An array of statements to release advisory locks + */ + private $unlockStatements = []; + + /** + * True when the current session exists but expired according to session.gc_maxlifetime. + * + * @var bool + */ + private $sessionExpired = false; + + /** + * Whether a transaction is active. + * + * @var bool + */ + private $inTransaction = false; + + /** + * Whether gc() has been called. + * + * @var bool + */ + private $gcCalled = false; + + /** + * You can either pass an existing database connection as PDO instance or + * pass a DSN string that will be used to lazy-connect to the database + * when the session is actually used. Furthermore it's possible to pass null + * which will then use the session.save_path ini setting as PDO DSN parameter. + * + * List of available options: + * * db_table: The name of the table [default: sessions] + * * db_id_col: The column where to store the session id [default: sess_id] + * * db_data_col: The column where to store the session data [default: sess_data] + * * db_lifetime_col: The column where to store the lifetime [default: sess_lifetime] + * * db_time_col: The column where to store the timestamp [default: sess_time] + * * db_username: The username when lazy-connect [default: ''] + * * db_password: The password when lazy-connect [default: ''] + * * db_connection_options: An array of driver-specific connection options [default: []] + * * lock_mode: The strategy for locking, see constants [default: LOCK_TRANSACTIONAL] + * + * @param \PDO|string|null $pdoOrDsn A \PDO instance or DSN string or URL string or null + * + * @throws \InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION + */ + public function __construct($pdoOrDsn = null, array $options = []) + { + if ($pdoOrDsn instanceof \PDO) { + if (\PDO::ERRMODE_EXCEPTION !== $pdoOrDsn->getAttribute(\PDO::ATTR_ERRMODE)) { + throw new \InvalidArgumentException(sprintf('"%s" requires PDO error mode attribute be set to throw Exceptions (i.e. $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION)).', __CLASS__)); + } + + $this->pdo = $pdoOrDsn; + $this->driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME); + } elseif (\is_string($pdoOrDsn) && str_contains($pdoOrDsn, '://')) { + $this->dsn = $this->buildDsnFromUrl($pdoOrDsn); + } else { + $this->dsn = $pdoOrDsn; + } + + $this->table = $options['db_table'] ?? $this->table; + $this->idCol = $options['db_id_col'] ?? $this->idCol; + $this->dataCol = $options['db_data_col'] ?? $this->dataCol; + $this->lifetimeCol = $options['db_lifetime_col'] ?? $this->lifetimeCol; + $this->timeCol = $options['db_time_col'] ?? $this->timeCol; + $this->username = $options['db_username'] ?? $this->username; + $this->password = $options['db_password'] ?? $this->password; + $this->connectionOptions = $options['db_connection_options'] ?? $this->connectionOptions; + $this->lockMode = $options['lock_mode'] ?? $this->lockMode; + } + + /** + * Creates the table to store sessions which can be called once for setup. + * + * Session ID is saved in a column of maximum length 128 because that is enough even + * for a 512 bit configured session.hash_function like Whirlpool. Session data is + * saved in a BLOB. One could also use a shorter inlined varbinary column + * if one was sure the data fits into it. + * + * @throws \PDOException When the table already exists + * @throws \DomainException When an unsupported PDO driver is used + */ + public function createTable() + { + // connect if we are not yet + $this->getConnection(); + + switch ($this->driver) { + case 'mysql': + // We use varbinary for the ID column because it prevents unwanted conversions: + // - character set conversions between server and client + // - trailing space removal + // - case-insensitivity + // - language processing like é == e + $sql = "CREATE TABLE $this->table ($this->idCol VARBINARY(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER UNSIGNED NOT NULL, $this->timeCol INTEGER UNSIGNED NOT NULL) COLLATE utf8mb4_bin, ENGINE = InnoDB"; + break; + case 'sqlite': + $sql = "CREATE TABLE $this->table ($this->idCol TEXT NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)"; + break; + case 'pgsql': + $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(128) NOT NULL PRIMARY KEY, $this->dataCol BYTEA NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)"; + break; + case 'oci': + $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR2(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)"; + break; + case 'sqlsrv': + $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(128) NOT NULL PRIMARY KEY, $this->dataCol VARBINARY(MAX) NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)"; + break; + default: + throw new \DomainException(sprintf('Creating the session table is currently not implemented for PDO driver "%s".', $this->driver)); + } + + try { + $this->pdo->exec($sql); + $this->pdo->exec("CREATE INDEX EXPIRY ON $this->table ($this->lifetimeCol)"); + } catch (\PDOException $e) { + $this->rollback(); + + throw $e; + } + } + + /** + * Returns true when the current session exists but expired according to session.gc_maxlifetime. + * + * Can be used to distinguish between a new session and one that expired due to inactivity. + * + * @return bool + */ + public function isSessionExpired() + { + return $this->sessionExpired; + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function open($savePath, $sessionName) + { + $this->sessionExpired = false; + + if (null === $this->pdo) { + $this->connect($this->dsn ?: $savePath); + } + + return parent::open($savePath, $sessionName); + } + + /** + * @return string + */ + #[\ReturnTypeWillChange] + public function read($sessionId) + { + try { + return parent::read($sessionId); + } catch (\PDOException $e) { + $this->rollback(); + + throw $e; + } + } + + /** + * @return int|false + */ + #[\ReturnTypeWillChange] + public function gc($maxlifetime) + { + // We delay gc() to close() so that it is executed outside the transactional and blocking read-write process. + // This way, pruning expired sessions does not block them from being started while the current session is used. + $this->gcCalled = true; + + return 0; + } + + /** + * {@inheritdoc} + */ + protected function doDestroy(string $sessionId) + { + // delete the record associated with this id + $sql = "DELETE FROM $this->table WHERE $this->idCol = :id"; + + try { + $stmt = $this->pdo->prepare($sql); + $stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $stmt->execute(); + } catch (\PDOException $e) { + $this->rollback(); + + throw $e; + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doWrite(string $sessionId, string $data) + { + $maxlifetime = (int) ini_get('session.gc_maxlifetime'); + + try { + // We use a single MERGE SQL query when supported by the database. + $mergeStmt = $this->getMergeStatement($sessionId, $data, $maxlifetime); + if (null !== $mergeStmt) { + $mergeStmt->execute(); + + return true; + } + + $updateStmt = $this->getUpdateStatement($sessionId, $data, $maxlifetime); + $updateStmt->execute(); + + // When MERGE is not supported, like in Postgres < 9.5, we have to use this approach that can result in + // duplicate key errors when the same session is written simultaneously (given the LOCK_NONE behavior). + // We can just catch such an error and re-execute the update. This is similar to a serializable + // transaction with retry logic on serialization failures but without the overhead and without possible + // false positives due to longer gap locking. + if (!$updateStmt->rowCount()) { + try { + $insertStmt = $this->getInsertStatement($sessionId, $data, $maxlifetime); + $insertStmt->execute(); + } catch (\PDOException $e) { + // Handle integrity violation SQLSTATE 23000 (or a subclass like 23505 in Postgres) for duplicate keys + if (str_starts_with($e->getCode(), '23')) { + $updateStmt->execute(); + } else { + throw $e; + } + } + } + } catch (\PDOException $e) { + $this->rollback(); + + throw $e; + } + + return true; + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function updateTimestamp($sessionId, $data) + { + $expiry = time() + (int) ini_get('session.gc_maxlifetime'); + + try { + $updateStmt = $this->pdo->prepare( + "UPDATE $this->table SET $this->lifetimeCol = :expiry, $this->timeCol = :time WHERE $this->idCol = :id" + ); + $updateStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $updateStmt->bindParam(':expiry', $expiry, \PDO::PARAM_INT); + $updateStmt->bindValue(':time', time(), \PDO::PARAM_INT); + $updateStmt->execute(); + } catch (\PDOException $e) { + $this->rollback(); + + throw $e; + } + + return true; + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function close() + { + $this->commit(); + + while ($unlockStmt = array_shift($this->unlockStatements)) { + $unlockStmt->execute(); + } + + if ($this->gcCalled) { + $this->gcCalled = false; + + // delete the session records that have expired + $sql = "DELETE FROM $this->table WHERE $this->lifetimeCol < :time AND $this->lifetimeCol > :min"; + $stmt = $this->pdo->prepare($sql); + $stmt->bindValue(':time', time(), \PDO::PARAM_INT); + $stmt->bindValue(':min', self::MAX_LIFETIME, \PDO::PARAM_INT); + $stmt->execute(); + // to be removed in 6.0 + if ('mysql' === $this->driver) { + $legacySql = "DELETE FROM $this->table WHERE $this->lifetimeCol <= :min AND $this->lifetimeCol + $this->timeCol < :time"; + } else { + $legacySql = "DELETE FROM $this->table WHERE $this->lifetimeCol <= :min AND $this->lifetimeCol < :time - $this->timeCol"; + } + + $stmt = $this->pdo->prepare($legacySql); + $stmt->bindValue(':time', time(), \PDO::PARAM_INT); + $stmt->bindValue(':min', self::MAX_LIFETIME, \PDO::PARAM_INT); + $stmt->execute(); + } + + if (false !== $this->dsn) { + $this->pdo = null; // only close lazy-connection + $this->driver = null; + } + + return true; + } + + /** + * Lazy-connects to the database. + */ + private function connect(string $dsn): void + { + $this->pdo = new \PDO($dsn, $this->username, $this->password, $this->connectionOptions); + $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + $this->driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME); + } + + /** + * Builds a PDO DSN from a URL-like connection string. + * + * @todo implement missing support for oci DSN (which look totally different from other PDO ones) + */ + private function buildDsnFromUrl(string $dsnOrUrl): string + { + // (pdo_)?sqlite3?:///... => (pdo_)?sqlite3?://localhost/... or else the URL will be invalid + $url = preg_replace('#^((?:pdo_)?sqlite3?):///#', '$1://localhost/', $dsnOrUrl); + + $params = parse_url($url); + + if (false === $params) { + return $dsnOrUrl; // If the URL is not valid, let's assume it might be a DSN already. + } + + $params = array_map('rawurldecode', $params); + + // Override the default username and password. Values passed through options will still win over these in the constructor. + if (isset($params['user'])) { + $this->username = $params['user']; + } + + if (isset($params['pass'])) { + $this->password = $params['pass']; + } + + if (!isset($params['scheme'])) { + throw new \InvalidArgumentException('URLs without scheme are not supported to configure the PdoSessionHandler.'); + } + + $driverAliasMap = [ + 'mssql' => 'sqlsrv', + 'mysql2' => 'mysql', // Amazon RDS, for some weird reason + 'postgres' => 'pgsql', + 'postgresql' => 'pgsql', + 'sqlite3' => 'sqlite', + ]; + + $driver = $driverAliasMap[$params['scheme']] ?? $params['scheme']; + + // Doctrine DBAL supports passing its internal pdo_* driver names directly too (allowing both dashes and underscores). This allows supporting the same here. + if (str_starts_with($driver, 'pdo_') || str_starts_with($driver, 'pdo-')) { + $driver = substr($driver, 4); + } + + $dsn = null; + switch ($driver) { + case 'mysql': + $dsn = 'mysql:'; + if ('' !== ($params['query'] ?? '')) { + $queryParams = []; + parse_str($params['query'], $queryParams); + if ('' !== ($queryParams['charset'] ?? '')) { + $dsn .= 'charset='.$queryParams['charset'].';'; + } + + if ('' !== ($queryParams['unix_socket'] ?? '')) { + $dsn .= 'unix_socket='.$queryParams['unix_socket'].';'; + + if (isset($params['path'])) { + $dbName = substr($params['path'], 1); // Remove the leading slash + $dsn .= 'dbname='.$dbName.';'; + } + + return $dsn; + } + } + // If "unix_socket" is not in the query, we continue with the same process as pgsql + // no break + case 'pgsql': + $dsn ?? $dsn = 'pgsql:'; + + if (isset($params['host']) && '' !== $params['host']) { + $dsn .= 'host='.$params['host'].';'; + } + + if (isset($params['port']) && '' !== $params['port']) { + $dsn .= 'port='.$params['port'].';'; + } + + if (isset($params['path'])) { + $dbName = substr($params['path'], 1); // Remove the leading slash + $dsn .= 'dbname='.$dbName.';'; + } + + return $dsn; + + case 'sqlite': + return 'sqlite:'.substr($params['path'], 1); + + case 'sqlsrv': + $dsn = 'sqlsrv:server='; + + if (isset($params['host'])) { + $dsn .= $params['host']; + } + + if (isset($params['port']) && '' !== $params['port']) { + $dsn .= ','.$params['port']; + } + + if (isset($params['path'])) { + $dbName = substr($params['path'], 1); // Remove the leading slash + $dsn .= ';Database='.$dbName; + } + + return $dsn; + + default: + throw new \InvalidArgumentException(sprintf('The scheme "%s" is not supported by the PdoSessionHandler URL configuration. Pass a PDO DSN directly.', $params['scheme'])); + } + } + + /** + * Helper method to begin a transaction. + * + * Since SQLite does not support row level locks, we have to acquire a reserved lock + * on the database immediately. Because of https://bugs.php.net/42766 we have to create + * such a transaction manually which also means we cannot use PDO::commit or + * PDO::rollback or PDO::inTransaction for SQLite. + * + * Also MySQLs default isolation, REPEATABLE READ, causes deadlock for different sessions + * due to https://percona.com/blog/2013/12/12/one-more-innodb-gap-lock-to-avoid/ . + * So we change it to READ COMMITTED. + */ + private function beginTransaction(): void + { + if (!$this->inTransaction) { + if ('sqlite' === $this->driver) { + $this->pdo->exec('BEGIN IMMEDIATE TRANSACTION'); + } else { + if ('mysql' === $this->driver) { + $this->pdo->exec('SET TRANSACTION ISOLATION LEVEL READ COMMITTED'); + } + $this->pdo->beginTransaction(); + } + $this->inTransaction = true; + } + } + + /** + * Helper method to commit a transaction. + */ + private function commit(): void + { + if ($this->inTransaction) { + try { + // commit read-write transaction which also releases the lock + if ('sqlite' === $this->driver) { + $this->pdo->exec('COMMIT'); + } else { + $this->pdo->commit(); + } + $this->inTransaction = false; + } catch (\PDOException $e) { + $this->rollback(); + + throw $e; + } + } + } + + /** + * Helper method to rollback a transaction. + */ + private function rollback(): void + { + // We only need to rollback if we are in a transaction. Otherwise the resulting + // error would hide the real problem why rollback was called. We might not be + // in a transaction when not using the transactional locking behavior or when + // two callbacks (e.g. destroy and write) are invoked that both fail. + if ($this->inTransaction) { + if ('sqlite' === $this->driver) { + $this->pdo->exec('ROLLBACK'); + } else { + $this->pdo->rollBack(); + } + $this->inTransaction = false; + } + } + + /** + * Reads the session data in respect to the different locking strategies. + * + * We need to make sure we do not return session data that is already considered garbage according + * to the session.gc_maxlifetime setting because gc() is called after read() and only sometimes. + * + * @return string + */ + protected function doRead(string $sessionId) + { + if (self::LOCK_ADVISORY === $this->lockMode) { + $this->unlockStatements[] = $this->doAdvisoryLock($sessionId); + } + + $selectSql = $this->getSelectSql(); + $selectStmt = $this->pdo->prepare($selectSql); + $selectStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $insertStmt = null; + + do { + $selectStmt->execute(); + $sessionRows = $selectStmt->fetchAll(\PDO::FETCH_NUM); + + if ($sessionRows) { + $expiry = (int) $sessionRows[0][1]; + if ($expiry <= self::MAX_LIFETIME) { + $expiry += $sessionRows[0][2]; + } + + if ($expiry < time()) { + $this->sessionExpired = true; + + return ''; + } + + return \is_resource($sessionRows[0][0]) ? stream_get_contents($sessionRows[0][0]) : $sessionRows[0][0]; + } + + if (null !== $insertStmt) { + $this->rollback(); + throw new \RuntimeException('Failed to read session: INSERT reported a duplicate id but next SELECT did not return any data.'); + } + + if (!filter_var(ini_get('session.use_strict_mode'), \FILTER_VALIDATE_BOOLEAN) && self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) { + // In strict mode, session fixation is not possible: new sessions always start with a unique + // random id, so that concurrency is not possible and this code path can be skipped. + // Exclusive-reading of non-existent rows does not block, so we need to do an insert to block + // until other connections to the session are committed. + try { + $insertStmt = $this->getInsertStatement($sessionId, '', 0); + $insertStmt->execute(); + } catch (\PDOException $e) { + // Catch duplicate key error because other connection created the session already. + // It would only not be the case when the other connection destroyed the session. + if (str_starts_with($e->getCode(), '23')) { + // Retrieve finished session data written by concurrent connection by restarting the loop. + // We have to start a new transaction as a failed query will mark the current transaction as + // aborted in PostgreSQL and disallow further queries within it. + $this->rollback(); + $this->beginTransaction(); + continue; + } + + throw $e; + } + } + + return ''; + } while (true); + } + + /** + * Executes an application-level lock on the database. + * + * @return \PDOStatement The statement that needs to be executed later to release the lock + * + * @throws \DomainException When an unsupported PDO driver is used + * + * @todo implement missing advisory locks + * - for oci using DBMS_LOCK.REQUEST + * - for sqlsrv using sp_getapplock with LockOwner = Session + */ + private function doAdvisoryLock(string $sessionId): \PDOStatement + { + switch ($this->driver) { + case 'mysql': + // MySQL 5.7.5 and later enforces a maximum length on lock names of 64 characters. Previously, no limit was enforced. + $lockId = substr($sessionId, 0, 64); + // should we handle the return value? 0 on timeout, null on error + // we use a timeout of 50 seconds which is also the default for innodb_lock_wait_timeout + $stmt = $this->pdo->prepare('SELECT GET_LOCK(:key, 50)'); + $stmt->bindValue(':key', $lockId, \PDO::PARAM_STR); + $stmt->execute(); + + $releaseStmt = $this->pdo->prepare('DO RELEASE_LOCK(:key)'); + $releaseStmt->bindValue(':key', $lockId, \PDO::PARAM_STR); + + return $releaseStmt; + case 'pgsql': + // Obtaining an exclusive session level advisory lock requires an integer key. + // When session.sid_bits_per_character > 4, the session id can contain non-hex-characters. + // So we cannot just use hexdec(). + if (4 === \PHP_INT_SIZE) { + $sessionInt1 = $this->convertStringToInt($sessionId); + $sessionInt2 = $this->convertStringToInt(substr($sessionId, 4, 4)); + + $stmt = $this->pdo->prepare('SELECT pg_advisory_lock(:key1, :key2)'); + $stmt->bindValue(':key1', $sessionInt1, \PDO::PARAM_INT); + $stmt->bindValue(':key2', $sessionInt2, \PDO::PARAM_INT); + $stmt->execute(); + + $releaseStmt = $this->pdo->prepare('SELECT pg_advisory_unlock(:key1, :key2)'); + $releaseStmt->bindValue(':key1', $sessionInt1, \PDO::PARAM_INT); + $releaseStmt->bindValue(':key2', $sessionInt2, \PDO::PARAM_INT); + } else { + $sessionBigInt = $this->convertStringToInt($sessionId); + + $stmt = $this->pdo->prepare('SELECT pg_advisory_lock(:key)'); + $stmt->bindValue(':key', $sessionBigInt, \PDO::PARAM_INT); + $stmt->execute(); + + $releaseStmt = $this->pdo->prepare('SELECT pg_advisory_unlock(:key)'); + $releaseStmt->bindValue(':key', $sessionBigInt, \PDO::PARAM_INT); + } + + return $releaseStmt; + case 'sqlite': + throw new \DomainException('SQLite does not support advisory locks.'); + default: + throw new \DomainException(sprintf('Advisory locks are currently not implemented for PDO driver "%s".', $this->driver)); + } + } + + /** + * Encodes the first 4 (when PHP_INT_SIZE == 4) or 8 characters of the string as an integer. + * + * Keep in mind, PHP integers are signed. + */ + private function convertStringToInt(string $string): int + { + if (4 === \PHP_INT_SIZE) { + return (\ord($string[3]) << 24) + (\ord($string[2]) << 16) + (\ord($string[1]) << 8) + \ord($string[0]); + } + + $int1 = (\ord($string[7]) << 24) + (\ord($string[6]) << 16) + (\ord($string[5]) << 8) + \ord($string[4]); + $int2 = (\ord($string[3]) << 24) + (\ord($string[2]) << 16) + (\ord($string[1]) << 8) + \ord($string[0]); + + return $int2 + ($int1 << 32); + } + + /** + * Return a locking or nonlocking SQL query to read session information. + * + * @throws \DomainException When an unsupported PDO driver is used + */ + private function getSelectSql(): string + { + if (self::LOCK_TRANSACTIONAL === $this->lockMode) { + $this->beginTransaction(); + + // selecting the time column should be removed in 6.0 + switch ($this->driver) { + case 'mysql': + case 'oci': + case 'pgsql': + return "SELECT $this->dataCol, $this->lifetimeCol, $this->timeCol FROM $this->table WHERE $this->idCol = :id FOR UPDATE"; + case 'sqlsrv': + return "SELECT $this->dataCol, $this->lifetimeCol, $this->timeCol FROM $this->table WITH (UPDLOCK, ROWLOCK) WHERE $this->idCol = :id"; + case 'sqlite': + // we already locked when starting transaction + break; + default: + throw new \DomainException(sprintf('Transactional locks are currently not implemented for PDO driver "%s".', $this->driver)); + } + } + + return "SELECT $this->dataCol, $this->lifetimeCol, $this->timeCol FROM $this->table WHERE $this->idCol = :id"; + } + + /** + * Returns an insert statement supported by the database for writing session data. + */ + private function getInsertStatement(string $sessionId, string $sessionData, int $maxlifetime): \PDOStatement + { + switch ($this->driver) { + case 'oci': + $data = fopen('php://memory', 'r+'); + fwrite($data, $sessionData); + rewind($data); + $sql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, EMPTY_BLOB(), :expiry, :time) RETURNING $this->dataCol into :data"; + break; + default: + $data = $sessionData; + $sql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :expiry, :time)"; + break; + } + + $stmt = $this->pdo->prepare($sql); + $stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $stmt->bindParam(':data', $data, \PDO::PARAM_LOB); + $stmt->bindValue(':expiry', time() + $maxlifetime, \PDO::PARAM_INT); + $stmt->bindValue(':time', time(), \PDO::PARAM_INT); + + return $stmt; + } + + /** + * Returns an update statement supported by the database for writing session data. + */ + private function getUpdateStatement(string $sessionId, string $sessionData, int $maxlifetime): \PDOStatement + { + switch ($this->driver) { + case 'oci': + $data = fopen('php://memory', 'r+'); + fwrite($data, $sessionData); + rewind($data); + $sql = "UPDATE $this->table SET $this->dataCol = EMPTY_BLOB(), $this->lifetimeCol = :expiry, $this->timeCol = :time WHERE $this->idCol = :id RETURNING $this->dataCol into :data"; + break; + default: + $data = $sessionData; + $sql = "UPDATE $this->table SET $this->dataCol = :data, $this->lifetimeCol = :expiry, $this->timeCol = :time WHERE $this->idCol = :id"; + break; + } + + $stmt = $this->pdo->prepare($sql); + $stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $stmt->bindParam(':data', $data, \PDO::PARAM_LOB); + $stmt->bindValue(':expiry', time() + $maxlifetime, \PDO::PARAM_INT); + $stmt->bindValue(':time', time(), \PDO::PARAM_INT); + + return $stmt; + } + + /** + * Returns a merge/upsert (i.e. insert or update) statement when supported by the database for writing session data. + */ + private function getMergeStatement(string $sessionId, string $data, int $maxlifetime): ?\PDOStatement + { + switch (true) { + case 'mysql' === $this->driver: + $mergeSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :expiry, :time) ". + "ON DUPLICATE KEY UPDATE $this->dataCol = VALUES($this->dataCol), $this->lifetimeCol = VALUES($this->lifetimeCol), $this->timeCol = VALUES($this->timeCol)"; + break; + case 'sqlsrv' === $this->driver && version_compare($this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '10', '>='): + // MERGE is only available since SQL Server 2008 and must be terminated by semicolon + // It also requires HOLDLOCK according to https://weblogs.sqlteam.com/dang/2009/01/31/upsert-race-condition-with-merge/ + $mergeSql = "MERGE INTO $this->table WITH (HOLDLOCK) USING (SELECT 1 AS dummy) AS src ON ($this->idCol = ?) ". + "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ". + "WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?;"; + break; + case 'sqlite' === $this->driver: + $mergeSql = "INSERT OR REPLACE INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :expiry, :time)"; + break; + case 'pgsql' === $this->driver && version_compare($this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '9.5', '>='): + $mergeSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :expiry, :time) ". + "ON CONFLICT ($this->idCol) DO UPDATE SET ($this->dataCol, $this->lifetimeCol, $this->timeCol) = (EXCLUDED.$this->dataCol, EXCLUDED.$this->lifetimeCol, EXCLUDED.$this->timeCol)"; + break; + default: + // MERGE is not supported with LOBs: https://oracle.com/technetwork/articles/fuecks-lobs-095315.html + return null; + } + + $mergeStmt = $this->pdo->prepare($mergeSql); + + if ('sqlsrv' === $this->driver) { + $mergeStmt->bindParam(1, $sessionId, \PDO::PARAM_STR); + $mergeStmt->bindParam(2, $sessionId, \PDO::PARAM_STR); + $mergeStmt->bindParam(3, $data, \PDO::PARAM_LOB); + $mergeStmt->bindValue(4, time() + $maxlifetime, \PDO::PARAM_INT); + $mergeStmt->bindValue(5, time(), \PDO::PARAM_INT); + $mergeStmt->bindParam(6, $data, \PDO::PARAM_LOB); + $mergeStmt->bindValue(7, time() + $maxlifetime, \PDO::PARAM_INT); + $mergeStmt->bindValue(8, time(), \PDO::PARAM_INT); + } else { + $mergeStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $mergeStmt->bindParam(':data', $data, \PDO::PARAM_LOB); + $mergeStmt->bindValue(':expiry', time() + $maxlifetime, \PDO::PARAM_INT); + $mergeStmt->bindValue(':time', time(), \PDO::PARAM_INT); + } + + return $mergeStmt; + } + + /** + * Return a PDO instance. + * + * @return \PDO + */ + protected function getConnection() + { + if (null === $this->pdo) { + $this->connect($this->dsn ?: ini_get('session.save_path')); + } + + return $this->pdo; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php new file mode 100644 index 0000000..5cc2e34 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php @@ -0,0 +1,136 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +use Predis\Response\ErrorInterface; +use Symfony\Component\Cache\Traits\RedisClusterProxy; +use Symfony\Component\Cache\Traits\RedisProxy; + +/** + * Redis based session storage handler based on the Redis class + * provided by the PHP redis extension. + * + * @author Dalibor Karlović + */ +class RedisSessionHandler extends AbstractSessionHandler +{ + private $redis; + + /** + * @var string Key prefix for shared environments + */ + private $prefix; + + /** + * @var int Time to live in seconds + */ + private $ttl; + + /** + * List of available options: + * * prefix: The prefix to use for the keys in order to avoid collision on the Redis server + * * ttl: The time to live in seconds. + * + * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|RedisProxy|RedisClusterProxy $redis + * + * @throws \InvalidArgumentException When unsupported client or options are passed + */ + public function __construct($redis, array $options = []) + { + if ( + !$redis instanceof \Redis && + !$redis instanceof \RedisArray && + !$redis instanceof \RedisCluster && + !$redis instanceof \Predis\ClientInterface && + !$redis instanceof RedisProxy && + !$redis instanceof RedisClusterProxy + ) { + throw new \InvalidArgumentException(sprintf('"%s()" expects parameter 1 to be Redis, RedisArray, RedisCluster or Predis\ClientInterface, "%s" given.', __METHOD__, get_debug_type($redis))); + } + + if ($diff = array_diff(array_keys($options), ['prefix', 'ttl'])) { + throw new \InvalidArgumentException(sprintf('The following options are not supported "%s".', implode(', ', $diff))); + } + + $this->redis = $redis; + $this->prefix = $options['prefix'] ?? 'sf_s'; + $this->ttl = $options['ttl'] ?? null; + } + + /** + * {@inheritdoc} + */ + protected function doRead(string $sessionId): string + { + return $this->redis->get($this->prefix.$sessionId) ?: ''; + } + + /** + * {@inheritdoc} + */ + protected function doWrite(string $sessionId, string $data): bool + { + $result = $this->redis->setEx($this->prefix.$sessionId, (int) ($this->ttl ?? ini_get('session.gc_maxlifetime')), $data); + + return $result && !$result instanceof ErrorInterface; + } + + /** + * {@inheritdoc} + */ + protected function doDestroy(string $sessionId): bool + { + static $unlink = true; + + if ($unlink) { + try { + $unlink = false !== $this->redis->unlink($this->prefix.$sessionId); + } catch (\Throwable $e) { + $unlink = false; + } + } + + if (!$unlink) { + $this->redis->del($this->prefix.$sessionId); + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function close(): bool + { + return true; + } + + /** + * {@inheritdoc} + * + * @return int|false + */ + #[\ReturnTypeWillChange] + public function gc($maxlifetime) + { + return 0; + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function updateTimestamp($sessionId, $data) + { + return (bool) $this->redis->expire($this->prefix.$sessionId, (int) ($this->ttl ?? ini_get('session.gc_maxlifetime'))); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/SessionHandlerFactory.php b/vendor/symfony/http-foundation/Session/Storage/Handler/SessionHandlerFactory.php new file mode 100644 index 0000000..f3f7b20 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/SessionHandlerFactory.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +use Doctrine\DBAL\DriverManager; +use Symfony\Component\Cache\Adapter\AbstractAdapter; +use Symfony\Component\Cache\Traits\RedisClusterProxy; +use Symfony\Component\Cache\Traits\RedisProxy; + +/** + * @author Nicolas Grekas + */ +class SessionHandlerFactory +{ + /** + * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|RedisProxy|RedisClusterProxy|\Memcached|\PDO|string $connection Connection or DSN + */ + public static function createHandler($connection): AbstractSessionHandler + { + if (!\is_string($connection) && !\is_object($connection)) { + throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be a string or a connection object, "%s" given.', __METHOD__, get_debug_type($connection))); + } + + if ($options = \is_string($connection) ? parse_url($connection) : false) { + parse_str($options['query'] ?? '', $options); + } + + switch (true) { + case $connection instanceof \Redis: + case $connection instanceof \RedisArray: + case $connection instanceof \RedisCluster: + case $connection instanceof \Predis\ClientInterface: + case $connection instanceof RedisProxy: + case $connection instanceof RedisClusterProxy: + return new RedisSessionHandler($connection); + + case $connection instanceof \Memcached: + return new MemcachedSessionHandler($connection); + + case $connection instanceof \PDO: + return new PdoSessionHandler($connection); + + case !\is_string($connection): + throw new \InvalidArgumentException(sprintf('Unsupported Connection: "%s".', get_debug_type($connection))); + case str_starts_with($connection, 'file://'): + $savePath = substr($connection, 7); + + return new StrictSessionHandler(new NativeFileSessionHandler('' === $savePath ? null : $savePath)); + + case str_starts_with($connection, 'redis:'): + case str_starts_with($connection, 'rediss:'): + case str_starts_with($connection, 'memcached:'): + if (!class_exists(AbstractAdapter::class)) { + throw new \InvalidArgumentException(sprintf('Unsupported DSN "%s". Try running "composer require symfony/cache".', $connection)); + } + $handlerClass = str_starts_with($connection, 'memcached:') ? MemcachedSessionHandler::class : RedisSessionHandler::class; + $connection = AbstractAdapter::createConnection($connection, ['lazy' => true]); + + return new $handlerClass($connection, array_intersect_key($options ?: [], ['prefix' => 1, 'ttl' => 1])); + + case str_starts_with($connection, 'pdo_oci://'): + if (!class_exists(DriverManager::class)) { + throw new \InvalidArgumentException(sprintf('Unsupported DSN "%s". Try running "composer require doctrine/dbal".', $connection)); + } + $connection = DriverManager::getConnection(['url' => $connection])->getWrappedConnection(); + // no break; + + case str_starts_with($connection, 'mssql://'): + case str_starts_with($connection, 'mysql://'): + case str_starts_with($connection, 'mysql2://'): + case str_starts_with($connection, 'pgsql://'): + case str_starts_with($connection, 'postgres://'): + case str_starts_with($connection, 'postgresql://'): + case str_starts_with($connection, 'sqlsrv://'): + case str_starts_with($connection, 'sqlite://'): + case str_starts_with($connection, 'sqlite3://'): + return new PdoSessionHandler($connection, $options ?: []); + } + + throw new \InvalidArgumentException(sprintf('Unsupported Connection: "%s".', $connection)); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php new file mode 100644 index 0000000..0461e99 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * Adds basic `SessionUpdateTimestampHandlerInterface` behaviors to another `SessionHandlerInterface`. + * + * @author Nicolas Grekas + */ +class StrictSessionHandler extends AbstractSessionHandler +{ + private $handler; + private $doDestroy; + + public function __construct(\SessionHandlerInterface $handler) + { + if ($handler instanceof \SessionUpdateTimestampHandlerInterface) { + throw new \LogicException(sprintf('"%s" is already an instance of "SessionUpdateTimestampHandlerInterface", you cannot wrap it with "%s".', get_debug_type($handler), self::class)); + } + + $this->handler = $handler; + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function open($savePath, $sessionName) + { + parent::open($savePath, $sessionName); + + return $this->handler->open($savePath, $sessionName); + } + + /** + * {@inheritdoc} + */ + protected function doRead(string $sessionId) + { + return $this->handler->read($sessionId); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function updateTimestamp($sessionId, $data) + { + return $this->write($sessionId, $data); + } + + /** + * {@inheritdoc} + */ + protected function doWrite(string $sessionId, string $data) + { + return $this->handler->write($sessionId, $data); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function destroy($sessionId) + { + $this->doDestroy = true; + $destroyed = parent::destroy($sessionId); + + return $this->doDestroy ? $this->doDestroy($sessionId) : $destroyed; + } + + /** + * {@inheritdoc} + */ + protected function doDestroy(string $sessionId) + { + $this->doDestroy = false; + + return $this->handler->destroy($sessionId); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function close() + { + return $this->handler->close(); + } + + /** + * @return int|false + */ + #[\ReturnTypeWillChange] + public function gc($maxlifetime) + { + return $this->handler->gc($maxlifetime); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/MetadataBag.php b/vendor/symfony/http-foundation/Session/Storage/MetadataBag.php new file mode 100644 index 0000000..1bfce55 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/MetadataBag.php @@ -0,0 +1,167 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; + +/** + * Metadata container. + * + * Adds metadata to the session. + * + * @author Drak + */ +class MetadataBag implements SessionBagInterface +{ + public const CREATED = 'c'; + public const UPDATED = 'u'; + public const LIFETIME = 'l'; + + /** + * @var string + */ + private $name = '__metadata'; + + /** + * @var string + */ + private $storageKey; + + /** + * @var array + */ + protected $meta = [self::CREATED => 0, self::UPDATED => 0, self::LIFETIME => 0]; + + /** + * Unix timestamp. + * + * @var int + */ + private $lastUsed; + + /** + * @var int + */ + private $updateThreshold; + + /** + * @param string $storageKey The key used to store bag in the session + * @param int $updateThreshold The time to wait between two UPDATED updates + */ + public function __construct(string $storageKey = '_sf2_meta', int $updateThreshold = 0) + { + $this->storageKey = $storageKey; + $this->updateThreshold = $updateThreshold; + } + + /** + * {@inheritdoc} + */ + public function initialize(array &$array) + { + $this->meta = &$array; + + if (isset($array[self::CREATED])) { + $this->lastUsed = $this->meta[self::UPDATED]; + + $timeStamp = time(); + if ($timeStamp - $array[self::UPDATED] >= $this->updateThreshold) { + $this->meta[self::UPDATED] = $timeStamp; + } + } else { + $this->stampCreated(); + } + } + + /** + * Gets the lifetime that the session cookie was set with. + * + * @return int + */ + public function getLifetime() + { + return $this->meta[self::LIFETIME]; + } + + /** + * Stamps a new session's metadata. + * + * @param int $lifetime Sets the cookie lifetime for the session cookie. A null value + * will leave the system settings unchanged, 0 sets the cookie + * to expire with browser session. Time is in seconds, and is + * not a Unix timestamp. + */ + public function stampNew(int $lifetime = null) + { + $this->stampCreated($lifetime); + } + + /** + * {@inheritdoc} + */ + public function getStorageKey() + { + return $this->storageKey; + } + + /** + * Gets the created timestamp metadata. + * + * @return int Unix timestamp + */ + public function getCreated() + { + return $this->meta[self::CREATED]; + } + + /** + * Gets the last used metadata. + * + * @return int Unix timestamp + */ + public function getLastUsed() + { + return $this->lastUsed; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + // nothing to do + return null; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + /** + * Sets name. + */ + public function setName(string $name) + { + $this->name = $name; + } + + private function stampCreated(int $lifetime = null): void + { + $timeStamp = time(); + $this->meta[self::CREATED] = $this->meta[self::UPDATED] = $this->lastUsed = $timeStamp; + $this->meta[self::LIFETIME] = $lifetime ?? (int) ini_get('session.cookie_lifetime'); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php b/vendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php new file mode 100644 index 0000000..c5c2bb0 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php @@ -0,0 +1,252 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; + +/** + * MockArraySessionStorage mocks the session for unit tests. + * + * No PHP session is actually started since a session can be initialized + * and shutdown only once per PHP execution cycle. + * + * When doing functional testing, you should use MockFileSessionStorage instead. + * + * @author Fabien Potencier + * @author Bulat Shakirzyanov + * @author Drak + */ +class MockArraySessionStorage implements SessionStorageInterface +{ + /** + * @var string + */ + protected $id = ''; + + /** + * @var string + */ + protected $name; + + /** + * @var bool + */ + protected $started = false; + + /** + * @var bool + */ + protected $closed = false; + + /** + * @var array + */ + protected $data = []; + + /** + * @var MetadataBag + */ + protected $metadataBag; + + /** + * @var array|SessionBagInterface[] + */ + protected $bags = []; + + public function __construct(string $name = 'MOCKSESSID', MetadataBag $metaBag = null) + { + $this->name = $name; + $this->setMetadataBag($metaBag); + } + + public function setSessionData(array $array) + { + $this->data = $array; + } + + /** + * {@inheritdoc} + */ + public function start() + { + if ($this->started) { + return true; + } + + if (empty($this->id)) { + $this->id = $this->generateId(); + } + + $this->loadSession(); + + return true; + } + + /** + * {@inheritdoc} + */ + public function regenerate(bool $destroy = false, int $lifetime = null) + { + if (!$this->started) { + $this->start(); + } + + $this->metadataBag->stampNew($lifetime); + $this->id = $this->generateId(); + + return true; + } + + /** + * {@inheritdoc} + */ + public function getId() + { + return $this->id; + } + + /** + * {@inheritdoc} + */ + public function setId(string $id) + { + if ($this->started) { + throw new \LogicException('Cannot set session ID after the session has started.'); + } + + $this->id = $id; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function setName(string $name) + { + $this->name = $name; + } + + /** + * {@inheritdoc} + */ + public function save() + { + if (!$this->started || $this->closed) { + throw new \RuntimeException('Trying to save a session that was not started yet or was already closed.'); + } + // nothing to do since we don't persist the session data + $this->closed = false; + $this->started = false; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + // clear out the bags + foreach ($this->bags as $bag) { + $bag->clear(); + } + + // clear out the session + $this->data = []; + + // reconnect the bags to the session + $this->loadSession(); + } + + /** + * {@inheritdoc} + */ + public function registerBag(SessionBagInterface $bag) + { + $this->bags[$bag->getName()] = $bag; + } + + /** + * {@inheritdoc} + */ + public function getBag(string $name) + { + if (!isset($this->bags[$name])) { + throw new \InvalidArgumentException(sprintf('The SessionBagInterface "%s" is not registered.', $name)); + } + + if (!$this->started) { + $this->start(); + } + + return $this->bags[$name]; + } + + /** + * {@inheritdoc} + */ + public function isStarted() + { + return $this->started; + } + + public function setMetadataBag(MetadataBag $bag = null) + { + if (null === $bag) { + $bag = new MetadataBag(); + } + + $this->metadataBag = $bag; + } + + /** + * Gets the MetadataBag. + * + * @return MetadataBag + */ + public function getMetadataBag() + { + return $this->metadataBag; + } + + /** + * Generates a session ID. + * + * This doesn't need to be particularly cryptographically secure since this is just + * a mock. + * + * @return string + */ + protected function generateId() + { + return hash('sha256', uniqid('ss_mock_', true)); + } + + protected function loadSession() + { + $bags = array_merge($this->bags, [$this->metadataBag]); + + foreach ($bags as $bag) { + $key = $bag->getStorageKey(); + $this->data[$key] = $this->data[$key] ?? []; + $bag->initialize($this->data[$key]); + } + + $this->started = true; + $this->closed = false; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php b/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php new file mode 100644 index 0000000..a1b7a31 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php @@ -0,0 +1,159 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +/** + * MockFileSessionStorage is used to mock sessions for + * functional testing when done in a single PHP process. + * + * No PHP session is actually started since a session can be initialized + * and shutdown only once per PHP execution cycle and this class does + * not pollute any session related globals, including session_*() functions + * or session.* PHP ini directives. + * + * @author Drak + */ +class MockFileSessionStorage extends MockArraySessionStorage +{ + private $savePath; + + /** + * @param string|null $savePath Path of directory to save session files + */ + public function __construct(string $savePath = null, string $name = 'MOCKSESSID', MetadataBag $metaBag = null) + { + if (null === $savePath) { + $savePath = sys_get_temp_dir(); + } + + if (!is_dir($savePath) && !@mkdir($savePath, 0777, true) && !is_dir($savePath)) { + throw new \RuntimeException(sprintf('Session Storage was not able to create directory "%s".', $savePath)); + } + + $this->savePath = $savePath; + + parent::__construct($name, $metaBag); + } + + /** + * {@inheritdoc} + */ + public function start() + { + if ($this->started) { + return true; + } + + if (!$this->id) { + $this->id = $this->generateId(); + } + + $this->read(); + + $this->started = true; + + return true; + } + + /** + * {@inheritdoc} + */ + public function regenerate(bool $destroy = false, int $lifetime = null) + { + if (!$this->started) { + $this->start(); + } + + if ($destroy) { + $this->destroy(); + } + + return parent::regenerate($destroy, $lifetime); + } + + /** + * {@inheritdoc} + */ + public function save() + { + if (!$this->started) { + throw new \RuntimeException('Trying to save a session that was not started yet or was already closed.'); + } + + $data = $this->data; + + foreach ($this->bags as $bag) { + if (empty($data[$key = $bag->getStorageKey()])) { + unset($data[$key]); + } + } + if ([$key = $this->metadataBag->getStorageKey()] === array_keys($data)) { + unset($data[$key]); + } + + try { + if ($data) { + $path = $this->getFilePath(); + $tmp = $path.bin2hex(random_bytes(6)); + file_put_contents($tmp, serialize($data)); + rename($tmp, $path); + } else { + $this->destroy(); + } + } finally { + $this->data = $data; + } + + // this is needed when the session object is re-used across multiple requests + // in functional tests. + $this->started = false; + } + + /** + * Deletes a session from persistent storage. + * Deliberately leaves session data in memory intact. + */ + private function destroy(): void + { + set_error_handler(static function () {}); + try { + unlink($this->getFilePath()); + } finally { + restore_error_handler(); + } + } + + /** + * Calculate path to file. + */ + private function getFilePath(): string + { + return $this->savePath.'/'.$this->id.'.mocksess'; + } + + /** + * Reads session from storage and loads session. + */ + private function read(): void + { + set_error_handler(static function () {}); + try { + $data = file_get_contents($this->getFilePath()); + } finally { + restore_error_handler(); + } + + $this->data = $data ? unserialize($data) : []; + + $this->loadSession(); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorageFactory.php b/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorageFactory.php new file mode 100644 index 0000000..d0da1e1 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorageFactory.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Request; + +// Help opcache.preload discover always-needed symbols +class_exists(MockFileSessionStorage::class); + +/** + * @author Jérémy Derussé + */ +class MockFileSessionStorageFactory implements SessionStorageFactoryInterface +{ + private $savePath; + private $name; + private $metaBag; + + /** + * @see MockFileSessionStorage constructor. + */ + public function __construct(string $savePath = null, string $name = 'MOCKSESSID', MetadataBag $metaBag = null) + { + $this->savePath = $savePath; + $this->name = $name; + $this->metaBag = $metaBag; + } + + public function createStorage(?Request $request): SessionStorageInterface + { + return new MockFileSessionStorage($this->savePath, $this->name, $this->metaBag); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php b/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php new file mode 100644 index 0000000..02467da --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php @@ -0,0 +1,470 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; +use Symfony\Component\HttpFoundation\Session\SessionUtils; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler; +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy; +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy; + +// Help opcache.preload discover always-needed symbols +class_exists(MetadataBag::class); +class_exists(StrictSessionHandler::class); +class_exists(SessionHandlerProxy::class); + +/** + * This provides a base class for session attribute storage. + * + * @author Drak + */ +class NativeSessionStorage implements SessionStorageInterface +{ + /** + * @var SessionBagInterface[] + */ + protected $bags = []; + + /** + * @var bool + */ + protected $started = false; + + /** + * @var bool + */ + protected $closed = false; + + /** + * @var AbstractProxy|\SessionHandlerInterface + */ + protected $saveHandler; + + /** + * @var MetadataBag + */ + protected $metadataBag; + + /** + * @var string|null + */ + private $emulateSameSite; + + /** + * Depending on how you want the storage driver to behave you probably + * want to override this constructor entirely. + * + * List of options for $options array with their defaults. + * + * @see https://php.net/session.configuration for options + * but we omit 'session.' from the beginning of the keys for convenience. + * + * ("auto_start", is not supported as it tells PHP to start a session before + * PHP starts to execute user-land code. Setting during runtime has no effect). + * + * cache_limiter, "" (use "0" to prevent headers from being sent entirely). + * cache_expire, "0" + * cookie_domain, "" + * cookie_httponly, "" + * cookie_lifetime, "0" + * cookie_path, "/" + * cookie_secure, "" + * cookie_samesite, null + * gc_divisor, "100" + * gc_maxlifetime, "1440" + * gc_probability, "1" + * lazy_write, "1" + * name, "PHPSESSID" + * referer_check, "" + * serialize_handler, "php" + * use_strict_mode, "1" + * use_cookies, "1" + * use_only_cookies, "1" + * use_trans_sid, "0" + * sid_length, "32" + * sid_bits_per_character, "5" + * trans_sid_hosts, $_SERVER['HTTP_HOST'] + * trans_sid_tags, "a=href,area=href,frame=src,form=" + * + * @param AbstractProxy|\SessionHandlerInterface|null $handler + */ + public function __construct(array $options = [], $handler = null, MetadataBag $metaBag = null) + { + if (!\extension_loaded('session')) { + throw new \LogicException('PHP extension "session" is required.'); + } + + $options += [ + 'cache_limiter' => '', + 'cache_expire' => 0, + 'use_cookies' => 1, + 'lazy_write' => 1, + 'use_strict_mode' => 1, + ]; + + session_register_shutdown(); + + $this->setMetadataBag($metaBag); + $this->setOptions($options); + $this->setSaveHandler($handler); + } + + /** + * Gets the save handler instance. + * + * @return AbstractProxy|\SessionHandlerInterface + */ + public function getSaveHandler() + { + return $this->saveHandler; + } + + /** + * {@inheritdoc} + */ + public function start() + { + if ($this->started) { + return true; + } + + if (\PHP_SESSION_ACTIVE === session_status()) { + throw new \RuntimeException('Failed to start the session: already started by PHP.'); + } + + if (filter_var(ini_get('session.use_cookies'), \FILTER_VALIDATE_BOOLEAN) && headers_sent($file, $line)) { + throw new \RuntimeException(sprintf('Failed to start the session because headers have already been sent by "%s" at line %d.', $file, $line)); + } + + // ok to try and start the session + if (!session_start()) { + throw new \RuntimeException('Failed to start the session.'); + } + + if (null !== $this->emulateSameSite) { + $originalCookie = SessionUtils::popSessionCookie(session_name(), session_id()); + if (null !== $originalCookie) { + header(sprintf('%s; SameSite=%s', $originalCookie, $this->emulateSameSite), false); + } + } + + $this->loadSession(); + + return true; + } + + /** + * {@inheritdoc} + */ + public function getId() + { + return $this->saveHandler->getId(); + } + + /** + * {@inheritdoc} + */ + public function setId(string $id) + { + $this->saveHandler->setId($id); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->saveHandler->getName(); + } + + /** + * {@inheritdoc} + */ + public function setName(string $name) + { + $this->saveHandler->setName($name); + } + + /** + * {@inheritdoc} + */ + public function regenerate(bool $destroy = false, int $lifetime = null) + { + // Cannot regenerate the session ID for non-active sessions. + if (\PHP_SESSION_ACTIVE !== session_status()) { + return false; + } + + if (headers_sent()) { + return false; + } + + if (null !== $lifetime && $lifetime != ini_get('session.cookie_lifetime')) { + $this->save(); + ini_set('session.cookie_lifetime', $lifetime); + $this->start(); + } + + if ($destroy) { + $this->metadataBag->stampNew(); + } + + $isRegenerated = session_regenerate_id($destroy); + + if (null !== $this->emulateSameSite) { + $originalCookie = SessionUtils::popSessionCookie(session_name(), session_id()); + if (null !== $originalCookie) { + header(sprintf('%s; SameSite=%s', $originalCookie, $this->emulateSameSite), false); + } + } + + return $isRegenerated; + } + + /** + * {@inheritdoc} + */ + public function save() + { + // Store a copy so we can restore the bags in case the session was not left empty + $session = $_SESSION; + + foreach ($this->bags as $bag) { + if (empty($_SESSION[$key = $bag->getStorageKey()])) { + unset($_SESSION[$key]); + } + } + if ([$key = $this->metadataBag->getStorageKey()] === array_keys($_SESSION)) { + unset($_SESSION[$key]); + } + + // Register error handler to add information about the current save handler + $previousHandler = set_error_handler(function ($type, $msg, $file, $line) use (&$previousHandler) { + if (\E_WARNING === $type && str_starts_with($msg, 'session_write_close():')) { + $handler = $this->saveHandler instanceof SessionHandlerProxy ? $this->saveHandler->getHandler() : $this->saveHandler; + $msg = sprintf('session_write_close(): Failed to write session data with "%s" handler', \get_class($handler)); + } + + return $previousHandler ? $previousHandler($type, $msg, $file, $line) : false; + }); + + try { + session_write_close(); + } finally { + restore_error_handler(); + + // Restore only if not empty + if ($_SESSION) { + $_SESSION = $session; + } + } + + $this->closed = true; + $this->started = false; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + // clear out the bags + foreach ($this->bags as $bag) { + $bag->clear(); + } + + // clear out the session + $_SESSION = []; + + // reconnect the bags to the session + $this->loadSession(); + } + + /** + * {@inheritdoc} + */ + public function registerBag(SessionBagInterface $bag) + { + if ($this->started) { + throw new \LogicException('Cannot register a bag when the session is already started.'); + } + + $this->bags[$bag->getName()] = $bag; + } + + /** + * {@inheritdoc} + */ + public function getBag(string $name) + { + if (!isset($this->bags[$name])) { + throw new \InvalidArgumentException(sprintf('The SessionBagInterface "%s" is not registered.', $name)); + } + + if (!$this->started && $this->saveHandler->isActive()) { + $this->loadSession(); + } elseif (!$this->started) { + $this->start(); + } + + return $this->bags[$name]; + } + + public function setMetadataBag(MetadataBag $metaBag = null) + { + if (null === $metaBag) { + $metaBag = new MetadataBag(); + } + + $this->metadataBag = $metaBag; + } + + /** + * Gets the MetadataBag. + * + * @return MetadataBag + */ + public function getMetadataBag() + { + return $this->metadataBag; + } + + /** + * {@inheritdoc} + */ + public function isStarted() + { + return $this->started; + } + + /** + * Sets session.* ini variables. + * + * For convenience we omit 'session.' from the beginning of the keys. + * Explicitly ignores other ini keys. + * + * @param array $options Session ini directives [key => value] + * + * @see https://php.net/session.configuration + */ + public function setOptions(array $options) + { + if (headers_sent() || \PHP_SESSION_ACTIVE === session_status()) { + return; + } + + $validOptions = array_flip([ + 'cache_expire', 'cache_limiter', 'cookie_domain', 'cookie_httponly', + 'cookie_lifetime', 'cookie_path', 'cookie_secure', 'cookie_samesite', + 'gc_divisor', 'gc_maxlifetime', 'gc_probability', + 'lazy_write', 'name', 'referer_check', + 'serialize_handler', 'use_strict_mode', 'use_cookies', + 'use_only_cookies', 'use_trans_sid', 'upload_progress.enabled', + 'upload_progress.cleanup', 'upload_progress.prefix', 'upload_progress.name', + 'upload_progress.freq', 'upload_progress.min_freq', 'url_rewriter.tags', + 'sid_length', 'sid_bits_per_character', 'trans_sid_hosts', 'trans_sid_tags', + ]); + + foreach ($options as $key => $value) { + if (isset($validOptions[$key])) { + if (str_starts_with($key, 'upload_progress.')) { + trigger_deprecation('symfony/http-foundation', '5.4', 'Support for the "%s" session option is deprecated. The settings prefixed with "session.upload_progress." can not be changed at runtime.', $key); + continue; + } + if ('url_rewriter.tags' === $key) { + trigger_deprecation('symfony/http-foundation', '5.4', 'Support for the "%s" session option is deprecated. Use "trans_sid_tags" instead.', $key); + } + if ('cookie_samesite' === $key && \PHP_VERSION_ID < 70300) { + // PHP < 7.3 does not support same_site cookies. We will emulate it in + // the start() method instead. + $this->emulateSameSite = $value; + continue; + } + if ('cookie_secure' === $key && 'auto' === $value) { + continue; + } + ini_set('url_rewriter.tags' !== $key ? 'session.'.$key : $key, $value); + } + } + } + + /** + * Registers session save handler as a PHP session handler. + * + * To use internal PHP session save handlers, override this method using ini_set with + * session.save_handler and session.save_path e.g. + * + * ini_set('session.save_handler', 'files'); + * ini_set('session.save_path', '/tmp'); + * + * or pass in a \SessionHandler instance which configures session.save_handler in the + * constructor, for a template see NativeFileSessionHandler. + * + * @see https://php.net/session-set-save-handler + * @see https://php.net/sessionhandlerinterface + * @see https://php.net/sessionhandler + * + * @param AbstractProxy|\SessionHandlerInterface|null $saveHandler + * + * @throws \InvalidArgumentException + */ + public function setSaveHandler($saveHandler = null) + { + if (!$saveHandler instanceof AbstractProxy && + !$saveHandler instanceof \SessionHandlerInterface && + null !== $saveHandler) { + throw new \InvalidArgumentException('Must be instance of AbstractProxy; implement \SessionHandlerInterface; or be null.'); + } + + // Wrap $saveHandler in proxy and prevent double wrapping of proxy + if (!$saveHandler instanceof AbstractProxy && $saveHandler instanceof \SessionHandlerInterface) { + $saveHandler = new SessionHandlerProxy($saveHandler); + } elseif (!$saveHandler instanceof AbstractProxy) { + $saveHandler = new SessionHandlerProxy(new StrictSessionHandler(new \SessionHandler())); + } + $this->saveHandler = $saveHandler; + + if (headers_sent() || \PHP_SESSION_ACTIVE === session_status()) { + return; + } + + if ($this->saveHandler instanceof SessionHandlerProxy) { + session_set_save_handler($this->saveHandler, false); + } + } + + /** + * Load the session with attributes. + * + * After starting the session, PHP retrieves the session from whatever handlers + * are set to (either PHP's internal, or a custom save handler set with session_set_save_handler()). + * PHP takes the return value from the read() handler, unserializes it + * and populates $_SESSION with the result automatically. + */ + protected function loadSession(array &$session = null) + { + if (null === $session) { + $session = &$_SESSION; + } + + $bags = array_merge($this->bags, [$this->metadataBag]); + + foreach ($bags as $bag) { + $key = $bag->getStorageKey(); + $session[$key] = isset($session[$key]) && \is_array($session[$key]) ? $session[$key] : []; + $bag->initialize($session[$key]); + } + + $this->started = true; + $this->closed = false; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorageFactory.php b/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorageFactory.php new file mode 100644 index 0000000..a7d7411 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorageFactory.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Request; + +// Help opcache.preload discover always-needed symbols +class_exists(NativeSessionStorage::class); + +/** + * @author Jérémy Derussé + */ +class NativeSessionStorageFactory implements SessionStorageFactoryInterface +{ + private $options; + private $handler; + private $metaBag; + private $secure; + + /** + * @see NativeSessionStorage constructor. + */ + public function __construct(array $options = [], $handler = null, MetadataBag $metaBag = null, bool $secure = false) + { + $this->options = $options; + $this->handler = $handler; + $this->metaBag = $metaBag; + $this->secure = $secure; + } + + public function createStorage(?Request $request): SessionStorageInterface + { + $storage = new NativeSessionStorage($this->options, $this->handler, $this->metaBag); + if ($this->secure && $request && $request->isSecure()) { + $storage->setOptions(['cookie_secure' => true]); + } + + return $storage; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php b/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php new file mode 100644 index 0000000..72dbef1 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy; + +/** + * Allows session to be started by PHP and managed by Symfony. + * + * @author Drak + */ +class PhpBridgeSessionStorage extends NativeSessionStorage +{ + /** + * @param AbstractProxy|\SessionHandlerInterface|null $handler + */ + public function __construct($handler = null, MetadataBag $metaBag = null) + { + if (!\extension_loaded('session')) { + throw new \LogicException('PHP extension "session" is required.'); + } + + $this->setMetadataBag($metaBag); + $this->setSaveHandler($handler); + } + + /** + * {@inheritdoc} + */ + public function start() + { + if ($this->started) { + return true; + } + + $this->loadSession(); + + return true; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + // clear out the bags and nothing else that may be set + // since the purpose of this driver is to share a handler + foreach ($this->bags as $bag) { + $bag->clear(); + } + + // reconnect the bags to the session + $this->loadSession(); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorageFactory.php b/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorageFactory.php new file mode 100644 index 0000000..173ef71 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorageFactory.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Request; + +// Help opcache.preload discover always-needed symbols +class_exists(PhpBridgeSessionStorage::class); + +/** + * @author Jérémy Derussé + */ +class PhpBridgeSessionStorageFactory implements SessionStorageFactoryInterface +{ + private $handler; + private $metaBag; + private $secure; + + /** + * @see PhpBridgeSessionStorage constructor. + */ + public function __construct($handler = null, MetadataBag $metaBag = null, bool $secure = false) + { + $this->handler = $handler; + $this->metaBag = $metaBag; + $this->secure = $secure; + } + + public function createStorage(?Request $request): SessionStorageInterface + { + $storage = new PhpBridgeSessionStorage($this->handler, $this->metaBag); + if ($this->secure && $request && $request->isSecure()) { + $storage->setOptions(['cookie_secure' => true]); + } + + return $storage; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php b/vendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php new file mode 100644 index 0000000..edd04df --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy; + +/** + * @author Drak + */ +abstract class AbstractProxy +{ + /** + * Flag if handler wraps an internal PHP session handler (using \SessionHandler). + * + * @var bool + */ + protected $wrapper = false; + + /** + * @var string + */ + protected $saveHandlerName; + + /** + * Gets the session.save_handler name. + * + * @return string|null + */ + public function getSaveHandlerName() + { + return $this->saveHandlerName; + } + + /** + * Is this proxy handler and instance of \SessionHandlerInterface. + * + * @return bool + */ + public function isSessionHandlerInterface() + { + return $this instanceof \SessionHandlerInterface; + } + + /** + * Returns true if this handler wraps an internal PHP session save handler using \SessionHandler. + * + * @return bool + */ + public function isWrapper() + { + return $this->wrapper; + } + + /** + * Has a session started? + * + * @return bool + */ + public function isActive() + { + return \PHP_SESSION_ACTIVE === session_status(); + } + + /** + * Gets the session ID. + * + * @return string + */ + public function getId() + { + return session_id(); + } + + /** + * Sets the session ID. + * + * @throws \LogicException + */ + public function setId(string $id) + { + if ($this->isActive()) { + throw new \LogicException('Cannot change the ID of an active session.'); + } + + session_id($id); + } + + /** + * Gets the session name. + * + * @return string + */ + public function getName() + { + return session_name(); + } + + /** + * Sets the session name. + * + * @throws \LogicException + */ + public function setName(string $name) + { + if ($this->isActive()) { + throw new \LogicException('Cannot change the name of an active session.'); + } + + session_name($name); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php b/vendor/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php new file mode 100644 index 0000000..9b0cdeb --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy; + +/** + * @author Drak + */ +class SessionHandlerProxy extends AbstractProxy implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface +{ + protected $handler; + + public function __construct(\SessionHandlerInterface $handler) + { + $this->handler = $handler; + $this->wrapper = $handler instanceof \SessionHandler; + $this->saveHandlerName = $this->wrapper ? ini_get('session.save_handler') : 'user'; + } + + /** + * @return \SessionHandlerInterface + */ + public function getHandler() + { + return $this->handler; + } + + // \SessionHandlerInterface + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function open($savePath, $sessionName) + { + return $this->handler->open($savePath, $sessionName); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function close() + { + return $this->handler->close(); + } + + /** + * @return string|false + */ + #[\ReturnTypeWillChange] + public function read($sessionId) + { + return $this->handler->read($sessionId); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function write($sessionId, $data) + { + return $this->handler->write($sessionId, $data); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function destroy($sessionId) + { + return $this->handler->destroy($sessionId); + } + + /** + * @return int|false + */ + #[\ReturnTypeWillChange] + public function gc($maxlifetime) + { + return $this->handler->gc($maxlifetime); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function validateId($sessionId) + { + return !$this->handler instanceof \SessionUpdateTimestampHandlerInterface || $this->handler->validateId($sessionId); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function updateTimestamp($sessionId, $data) + { + return $this->handler instanceof \SessionUpdateTimestampHandlerInterface ? $this->handler->updateTimestamp($sessionId, $data) : $this->write($sessionId, $data); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/ServiceSessionFactory.php b/vendor/symfony/http-foundation/Session/Storage/ServiceSessionFactory.php new file mode 100644 index 0000000..d17c60a --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/ServiceSessionFactory.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Request; + +/** + * @author Jérémy Derussé + * + * @internal to be removed in Symfony 6 + */ +final class ServiceSessionFactory implements SessionStorageFactoryInterface +{ + private $storage; + + public function __construct(SessionStorageInterface $storage) + { + $this->storage = $storage; + } + + public function createStorage(?Request $request): SessionStorageInterface + { + if ($this->storage instanceof NativeSessionStorage && $request && $request->isSecure()) { + $this->storage->setOptions(['cookie_secure' => true]); + } + + return $this->storage; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/SessionStorageFactoryInterface.php b/vendor/symfony/http-foundation/Session/Storage/SessionStorageFactoryInterface.php new file mode 100644 index 0000000..d03f0da --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/SessionStorageFactoryInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Request; + +/** + * @author Jérémy Derussé + */ +interface SessionStorageFactoryInterface +{ + /** + * Creates a new instance of SessionStorageInterface. + */ + public function createStorage(?Request $request): SessionStorageInterface; +} diff --git a/vendor/symfony/http-foundation/Session/Storage/SessionStorageInterface.php b/vendor/symfony/http-foundation/Session/Storage/SessionStorageInterface.php new file mode 100644 index 0000000..b7f66e7 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/SessionStorageInterface.php @@ -0,0 +1,131 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; + +/** + * StorageInterface. + * + * @author Fabien Potencier + * @author Drak + */ +interface SessionStorageInterface +{ + /** + * Starts the session. + * + * @return bool + * + * @throws \RuntimeException if something goes wrong starting the session + */ + public function start(); + + /** + * Checks if the session is started. + * + * @return bool + */ + public function isStarted(); + + /** + * Returns the session ID. + * + * @return string + */ + public function getId(); + + /** + * Sets the session ID. + */ + public function setId(string $id); + + /** + * Returns the session name. + * + * @return string + */ + public function getName(); + + /** + * Sets the session name. + */ + public function setName(string $name); + + /** + * Regenerates id that represents this storage. + * + * This method must invoke session_regenerate_id($destroy) unless + * this interface is used for a storage object designed for unit + * or functional testing where a real PHP session would interfere + * with testing. + * + * Note regenerate+destroy should not clear the session data in memory + * only delete the session data from persistent storage. + * + * Care: When regenerating the session ID no locking is involved in PHP's + * session design. See https://bugs.php.net/61470 for a discussion. + * So you must make sure the regenerated session is saved BEFORE sending the + * headers with the new ID. Symfony's HttpKernel offers a listener for this. + * See Symfony\Component\HttpKernel\EventListener\SaveSessionListener. + * Otherwise session data could get lost again for concurrent requests with the + * new ID. One result could be that you get logged out after just logging in. + * + * @param bool $destroy Destroy session when regenerating? + * @param int $lifetime Sets the cookie lifetime for the session cookie. A null value + * will leave the system settings unchanged, 0 sets the cookie + * to expire with browser session. Time is in seconds, and is + * not a Unix timestamp. + * + * @return bool + * + * @throws \RuntimeException If an error occurs while regenerating this storage + */ + public function regenerate(bool $destroy = false, int $lifetime = null); + + /** + * Force the session to be saved and closed. + * + * This method must invoke session_write_close() unless this interface is + * used for a storage object design for unit or functional testing where + * a real PHP session would interfere with testing, in which case + * it should actually persist the session data if required. + * + * @throws \RuntimeException if the session is saved without being started, or if the session + * is already closed + */ + public function save(); + + /** + * Clear all session data in memory. + */ + public function clear(); + + /** + * Gets a SessionBagInterface by name. + * + * @return SessionBagInterface + * + * @throws \InvalidArgumentException If the bag does not exist + */ + public function getBag(string $name); + + /** + * Registers a SessionBagInterface for use. + */ + public function registerBag(SessionBagInterface $bag); + + /** + * @return MetadataBag + */ + public function getMetadataBag(); +} diff --git a/vendor/symfony/http-foundation/StreamedResponse.php b/vendor/symfony/http-foundation/StreamedResponse.php new file mode 100644 index 0000000..676cd66 --- /dev/null +++ b/vendor/symfony/http-foundation/StreamedResponse.php @@ -0,0 +1,139 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * StreamedResponse represents a streamed HTTP response. + * + * A StreamedResponse uses a callback for its content. + * + * The callback should use the standard PHP functions like echo + * to stream the response back to the client. The flush() function + * can also be used if needed. + * + * @see flush() + * + * @author Fabien Potencier + */ +class StreamedResponse extends Response +{ + protected $callback; + protected $streamed; + private $headersSent; + + public function __construct(callable $callback = null, int $status = 200, array $headers = []) + { + parent::__construct(null, $status, $headers); + + if (null !== $callback) { + $this->setCallback($callback); + } + $this->streamed = false; + $this->headersSent = false; + } + + /** + * Factory method for chainability. + * + * @param callable|null $callback A valid PHP callback or null to set it later + * + * @return static + * + * @deprecated since Symfony 5.1, use __construct() instead. + */ + public static function create($callback = null, int $status = 200, array $headers = []) + { + trigger_deprecation('symfony/http-foundation', '5.1', 'The "%s()" method is deprecated, use "new %s()" instead.', __METHOD__, static::class); + + return new static($callback, $status, $headers); + } + + /** + * Sets the PHP callback associated with this Response. + * + * @return $this + */ + public function setCallback(callable $callback) + { + $this->callback = $callback; + + return $this; + } + + /** + * {@inheritdoc} + * + * This method only sends the headers once. + * + * @return $this + */ + public function sendHeaders() + { + if ($this->headersSent) { + return $this; + } + + $this->headersSent = true; + + return parent::sendHeaders(); + } + + /** + * {@inheritdoc} + * + * This method only sends the content once. + * + * @return $this + */ + public function sendContent() + { + if ($this->streamed) { + return $this; + } + + $this->streamed = true; + + if (null === $this->callback) { + throw new \LogicException('The Response callback must not be null.'); + } + + ($this->callback)(); + + return $this; + } + + /** + * {@inheritdoc} + * + * @throws \LogicException when the content is not null + * + * @return $this + */ + public function setContent(?string $content) + { + if (null !== $content) { + throw new \LogicException('The content cannot be set on a StreamedResponse instance.'); + } + + $this->streamed = true; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getContent() + { + return false; + } +} diff --git a/vendor/symfony/http-foundation/Test/Constraint/RequestAttributeValueSame.php b/vendor/symfony/http-foundation/Test/Constraint/RequestAttributeValueSame.php new file mode 100644 index 0000000..cb216ea --- /dev/null +++ b/vendor/symfony/http-foundation/Test/Constraint/RequestAttributeValueSame.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\HttpFoundation\Request; + +final class RequestAttributeValueSame extends Constraint +{ + private $name; + private $value; + + public function __construct(string $name, string $value) + { + $this->name = $name; + $this->value = $value; + } + + /** + * {@inheritdoc} + */ + public function toString(): string + { + return sprintf('has attribute "%s" with value "%s"', $this->name, $this->value); + } + + /** + * @param Request $request + * + * {@inheritdoc} + */ + protected function matches($request): bool + { + return $this->value === $request->attributes->get($this->name); + } + + /** + * @param Request $request + * + * {@inheritdoc} + */ + protected function failureDescription($request): string + { + return 'the Request '.$this->toString(); + } +} diff --git a/vendor/symfony/http-foundation/Test/Constraint/ResponseCookieValueSame.php b/vendor/symfony/http-foundation/Test/Constraint/ResponseCookieValueSame.php new file mode 100644 index 0000000..554e1a1 --- /dev/null +++ b/vendor/symfony/http-foundation/Test/Constraint/ResponseCookieValueSame.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpFoundation\Response; + +final class ResponseCookieValueSame extends Constraint +{ + private $name; + private $value; + private $path; + private $domain; + + public function __construct(string $name, string $value, string $path = '/', string $domain = null) + { + $this->name = $name; + $this->value = $value; + $this->path = $path; + $this->domain = $domain; + } + + /** + * {@inheritdoc} + */ + public function toString(): string + { + $str = sprintf('has cookie "%s"', $this->name); + if ('/' !== $this->path) { + $str .= sprintf(' with path "%s"', $this->path); + } + if ($this->domain) { + $str .= sprintf(' for domain "%s"', $this->domain); + } + $str .= sprintf(' with value "%s"', $this->value); + + return $str; + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function matches($response): bool + { + $cookie = $this->getCookie($response); + if (!$cookie) { + return false; + } + + return $this->value === $cookie->getValue(); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function failureDescription($response): string + { + return 'the Response '.$this->toString(); + } + + protected function getCookie(Response $response): ?Cookie + { + $cookies = $response->headers->getCookies(); + + $filteredCookies = array_filter($cookies, function (Cookie $cookie) { + return $cookie->getName() === $this->name && $cookie->getPath() === $this->path && $cookie->getDomain() === $this->domain; + }); + + return reset($filteredCookies) ?: null; + } +} diff --git a/vendor/symfony/http-foundation/Test/Constraint/ResponseFormatSame.php b/vendor/symfony/http-foundation/Test/Constraint/ResponseFormatSame.php new file mode 100644 index 0000000..f73aedf --- /dev/null +++ b/vendor/symfony/http-foundation/Test/Constraint/ResponseFormatSame.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Asserts that the response is in the given format. + * + * @author Kévin Dunglas + */ +final class ResponseFormatSame extends Constraint +{ + private $request; + private $format; + + public function __construct(Request $request, ?string $format) + { + $this->request = $request; + $this->format = $format; + } + + /** + * {@inheritdoc} + */ + public function toString(): string + { + return 'format is '.($this->format ?? 'null'); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function matches($response): bool + { + return $this->format === $this->request->getFormat($response->headers->get('Content-Type')); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function failureDescription($response): string + { + return 'the Response '.$this->toString(); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function additionalFailureDescription($response): string + { + return (string) $response; + } +} diff --git a/vendor/symfony/http-foundation/Test/Constraint/ResponseHasCookie.php b/vendor/symfony/http-foundation/Test/Constraint/ResponseHasCookie.php new file mode 100644 index 0000000..eae9e27 --- /dev/null +++ b/vendor/symfony/http-foundation/Test/Constraint/ResponseHasCookie.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpFoundation\Response; + +final class ResponseHasCookie extends Constraint +{ + private $name; + private $path; + private $domain; + + public function __construct(string $name, string $path = '/', string $domain = null) + { + $this->name = $name; + $this->path = $path; + $this->domain = $domain; + } + + /** + * {@inheritdoc} + */ + public function toString(): string + { + $str = sprintf('has cookie "%s"', $this->name); + if ('/' !== $this->path) { + $str .= sprintf(' with path "%s"', $this->path); + } + if ($this->domain) { + $str .= sprintf(' for domain "%s"', $this->domain); + } + + return $str; + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function matches($response): bool + { + return null !== $this->getCookie($response); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function failureDescription($response): string + { + return 'the Response '.$this->toString(); + } + + private function getCookie(Response $response): ?Cookie + { + $cookies = $response->headers->getCookies(); + + $filteredCookies = array_filter($cookies, function (Cookie $cookie) { + return $cookie->getName() === $this->name && $cookie->getPath() === $this->path && $cookie->getDomain() === $this->domain; + }); + + return reset($filteredCookies) ?: null; + } +} diff --git a/vendor/symfony/http-foundation/Test/Constraint/ResponseHasHeader.php b/vendor/symfony/http-foundation/Test/Constraint/ResponseHasHeader.php new file mode 100644 index 0000000..68ad827 --- /dev/null +++ b/vendor/symfony/http-foundation/Test/Constraint/ResponseHasHeader.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\HttpFoundation\Response; + +final class ResponseHasHeader extends Constraint +{ + private $headerName; + + public function __construct(string $headerName) + { + $this->headerName = $headerName; + } + + /** + * {@inheritdoc} + */ + public function toString(): string + { + return sprintf('has header "%s"', $this->headerName); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function matches($response): bool + { + return $response->headers->has($this->headerName); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function failureDescription($response): string + { + return 'the Response '.$this->toString(); + } +} diff --git a/vendor/symfony/http-foundation/Test/Constraint/ResponseHeaderSame.php b/vendor/symfony/http-foundation/Test/Constraint/ResponseHeaderSame.php new file mode 100644 index 0000000..a27d0c7 --- /dev/null +++ b/vendor/symfony/http-foundation/Test/Constraint/ResponseHeaderSame.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\HttpFoundation\Response; + +final class ResponseHeaderSame extends Constraint +{ + private $headerName; + private $expectedValue; + + public function __construct(string $headerName, string $expectedValue) + { + $this->headerName = $headerName; + $this->expectedValue = $expectedValue; + } + + /** + * {@inheritdoc} + */ + public function toString(): string + { + return sprintf('has header "%s" with value "%s"', $this->headerName, $this->expectedValue); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function matches($response): bool + { + return $this->expectedValue === $response->headers->get($this->headerName, null); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function failureDescription($response): string + { + return 'the Response '.$this->toString(); + } +} diff --git a/vendor/symfony/http-foundation/Test/Constraint/ResponseIsRedirected.php b/vendor/symfony/http-foundation/Test/Constraint/ResponseIsRedirected.php new file mode 100644 index 0000000..8c4b883 --- /dev/null +++ b/vendor/symfony/http-foundation/Test/Constraint/ResponseIsRedirected.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\HttpFoundation\Response; + +final class ResponseIsRedirected extends Constraint +{ + /** + * {@inheritdoc} + */ + public function toString(): string + { + return 'is redirected'; + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function matches($response): bool + { + return $response->isRedirect(); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function failureDescription($response): string + { + return 'the Response '.$this->toString(); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function additionalFailureDescription($response): string + { + return (string) $response; + } +} diff --git a/vendor/symfony/http-foundation/Test/Constraint/ResponseIsSuccessful.php b/vendor/symfony/http-foundation/Test/Constraint/ResponseIsSuccessful.php new file mode 100644 index 0000000..9c66558 --- /dev/null +++ b/vendor/symfony/http-foundation/Test/Constraint/ResponseIsSuccessful.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\HttpFoundation\Response; + +final class ResponseIsSuccessful extends Constraint +{ + /** + * {@inheritdoc} + */ + public function toString(): string + { + return 'is successful'; + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function matches($response): bool + { + return $response->isSuccessful(); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function failureDescription($response): string + { + return 'the Response '.$this->toString(); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function additionalFailureDescription($response): string + { + return (string) $response; + } +} diff --git a/vendor/symfony/http-foundation/Test/Constraint/ResponseIsUnprocessable.php b/vendor/symfony/http-foundation/Test/Constraint/ResponseIsUnprocessable.php new file mode 100644 index 0000000..880c781 --- /dev/null +++ b/vendor/symfony/http-foundation/Test/Constraint/ResponseIsUnprocessable.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\HttpFoundation\Response; + +final class ResponseIsUnprocessable extends Constraint +{ + /** + * {@inheritdoc} + */ + public function toString(): string + { + return 'is unprocessable'; + } + + /** + * @param Response $other + * + * {@inheritdoc} + */ + protected function matches($other): bool + { + return Response::HTTP_UNPROCESSABLE_ENTITY === $other->getStatusCode(); + } + + /** + * @param Response $other + * + * {@inheritdoc} + */ + protected function failureDescription($other): string + { + return 'the Response '.$this->toString(); + } + + /** + * @param Response $other + * + * {@inheritdoc} + */ + protected function additionalFailureDescription($other): string + { + return (string) $other; + } +} diff --git a/vendor/symfony/http-foundation/Test/Constraint/ResponseStatusCodeSame.php b/vendor/symfony/http-foundation/Test/Constraint/ResponseStatusCodeSame.php new file mode 100644 index 0000000..72bb000 --- /dev/null +++ b/vendor/symfony/http-foundation/Test/Constraint/ResponseStatusCodeSame.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\HttpFoundation\Response; + +final class ResponseStatusCodeSame extends Constraint +{ + private $statusCode; + + public function __construct(int $statusCode) + { + $this->statusCode = $statusCode; + } + + /** + * {@inheritdoc} + */ + public function toString(): string + { + return 'status code is '.$this->statusCode; + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function matches($response): bool + { + return $this->statusCode === $response->getStatusCode(); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function failureDescription($response): string + { + return 'the Response '.$this->toString(); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function additionalFailureDescription($response): string + { + return (string) $response; + } +} diff --git a/vendor/symfony/http-foundation/UrlHelper.php b/vendor/symfony/http-foundation/UrlHelper.php new file mode 100644 index 0000000..c15f101 --- /dev/null +++ b/vendor/symfony/http-foundation/UrlHelper.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\Routing\RequestContext; + +/** + * A helper service for manipulating URLs within and outside the request scope. + * + * @author Valentin Udaltsov + */ +final class UrlHelper +{ + private $requestStack; + private $requestContext; + + public function __construct(RequestStack $requestStack, RequestContext $requestContext = null) + { + $this->requestStack = $requestStack; + $this->requestContext = $requestContext; + } + + public function getAbsoluteUrl(string $path): string + { + if (str_contains($path, '://') || '//' === substr($path, 0, 2)) { + return $path; + } + + if (null === $request = $this->requestStack->getMainRequest()) { + return $this->getAbsoluteUrlFromContext($path); + } + + if ('#' === $path[0]) { + $path = $request->getRequestUri().$path; + } elseif ('?' === $path[0]) { + $path = $request->getPathInfo().$path; + } + + if (!$path || '/' !== $path[0]) { + $prefix = $request->getPathInfo(); + $last = \strlen($prefix) - 1; + if ($last !== $pos = strrpos($prefix, '/')) { + $prefix = substr($prefix, 0, $pos).'/'; + } + + return $request->getUriForPath($prefix.$path); + } + + return $request->getSchemeAndHttpHost().$path; + } + + public function getRelativePath(string $path): string + { + if (str_contains($path, '://') || '//' === substr($path, 0, 2)) { + return $path; + } + + if (null === $request = $this->requestStack->getMainRequest()) { + return $path; + } + + return $request->getRelativeUriForPath($path); + } + + private function getAbsoluteUrlFromContext(string $path): string + { + if (null === $this->requestContext || '' === $host = $this->requestContext->getHost()) { + return $path; + } + + $scheme = $this->requestContext->getScheme(); + $port = ''; + + if ('http' === $scheme && 80 !== $this->requestContext->getHttpPort()) { + $port = ':'.$this->requestContext->getHttpPort(); + } elseif ('https' === $scheme && 443 !== $this->requestContext->getHttpsPort()) { + $port = ':'.$this->requestContext->getHttpsPort(); + } + + if ('#' === $path[0]) { + $queryString = $this->requestContext->getQueryString(); + $path = $this->requestContext->getPathInfo().($queryString ? '?'.$queryString : '').$path; + } elseif ('?' === $path[0]) { + $path = $this->requestContext->getPathInfo().$path; + } + + if ('/' !== $path[0]) { + $path = rtrim($this->requestContext->getBaseUrl(), '/').'/'.$path; + } + + return $scheme.'://'.$host.$port.$path; + } +} diff --git a/vendor/symfony/http-foundation/composer.json b/vendor/symfony/http-foundation/composer.json new file mode 100644 index 0000000..d54bbfd --- /dev/null +++ b/vendor/symfony/http-foundation/composer.json @@ -0,0 +1,40 @@ +{ + "name": "symfony/http-foundation", + "type": "library", + "description": "Defines an object-oriented layer for the HTTP specification", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "predis/predis": "~1.0", + "symfony/cache": "^4.4|^5.0|^6.0", + "symfony/mime": "^4.4|^5.0|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0" + }, + "suggest" : { + "symfony/mime": "To use the file extension guesser" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\HttpFoundation\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/polyfill-mbstring/LICENSE b/vendor/symfony/polyfill-mbstring/LICENSE new file mode 100644 index 0000000..4cd8bdd --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-2019 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/polyfill-mbstring/Mbstring.php b/vendor/symfony/polyfill-mbstring/Mbstring.php new file mode 100644 index 0000000..b599095 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Mbstring.php @@ -0,0 +1,870 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Mbstring; + +/** + * Partial mbstring implementation in PHP, iconv based, UTF-8 centric. + * + * Implemented: + * - mb_chr - Returns a specific character from its Unicode code point + * - mb_convert_encoding - Convert character encoding + * - mb_convert_variables - Convert character code in variable(s) + * - mb_decode_mimeheader - Decode string in MIME header field + * - mb_encode_mimeheader - Encode string for MIME header XXX NATIVE IMPLEMENTATION IS REALLY BUGGED + * - mb_decode_numericentity - Decode HTML numeric string reference to character + * - mb_encode_numericentity - Encode character to HTML numeric string reference + * - mb_convert_case - Perform case folding on a string + * - mb_detect_encoding - Detect character encoding + * - mb_get_info - Get internal settings of mbstring + * - mb_http_input - Detect HTTP input character encoding + * - mb_http_output - Set/Get HTTP output character encoding + * - mb_internal_encoding - Set/Get internal character encoding + * - mb_list_encodings - Returns an array of all supported encodings + * - mb_ord - Returns the Unicode code point of a character + * - mb_output_handler - Callback function converts character encoding in output buffer + * - mb_scrub - Replaces ill-formed byte sequences with substitute characters + * - mb_strlen - Get string length + * - mb_strpos - Find position of first occurrence of string in a string + * - mb_strrpos - Find position of last occurrence of a string in a string + * - mb_str_split - Convert a string to an array + * - mb_strtolower - Make a string lowercase + * - mb_strtoupper - Make a string uppercase + * - mb_substitute_character - Set/Get substitution character + * - mb_substr - Get part of string + * - mb_stripos - Finds position of first occurrence of a string within another, case insensitive + * - mb_stristr - Finds first occurrence of a string within another, case insensitive + * - mb_strrchr - Finds the last occurrence of a character in a string within another + * - mb_strrichr - Finds the last occurrence of a character in a string within another, case insensitive + * - mb_strripos - Finds position of last occurrence of a string within another, case insensitive + * - mb_strstr - Finds first occurrence of a string within another + * - mb_strwidth - Return width of string + * - mb_substr_count - Count the number of substring occurrences + * + * Not implemented: + * - mb_convert_kana - Convert "kana" one from another ("zen-kaku", "han-kaku" and more) + * - mb_ereg_* - Regular expression with multibyte support + * - mb_parse_str - Parse GET/POST/COOKIE data and set global variable + * - mb_preferred_mime_name - Get MIME charset string + * - mb_regex_encoding - Returns current encoding for multibyte regex as string + * - mb_regex_set_options - Set/Get the default options for mbregex functions + * - mb_send_mail - Send encoded mail + * - mb_split - Split multibyte string using regular expression + * - mb_strcut - Get part of string + * - mb_strimwidth - Get truncated string with specified width + * + * @author Nicolas Grekas + * + * @internal + */ +final class Mbstring +{ + public const MB_CASE_FOLD = \PHP_INT_MAX; + + private const CASE_FOLD = [ + ['µ', 'ſ', "\xCD\x85", 'ς', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"], + ['μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "\xE1\xB9\xA1", 'ι'], + ]; + + private static $encodingList = ['ASCII', 'UTF-8']; + private static $language = 'neutral'; + private static $internalEncoding = 'UTF-8'; + + public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null) + { + if (\is_array($fromEncoding) || false !== strpos($fromEncoding, ',')) { + $fromEncoding = self::mb_detect_encoding($s, $fromEncoding); + } else { + $fromEncoding = self::getEncoding($fromEncoding); + } + + $toEncoding = self::getEncoding($toEncoding); + + if ('BASE64' === $fromEncoding) { + $s = base64_decode($s); + $fromEncoding = $toEncoding; + } + + if ('BASE64' === $toEncoding) { + return base64_encode($s); + } + + if ('HTML-ENTITIES' === $toEncoding || 'HTML' === $toEncoding) { + if ('HTML-ENTITIES' === $fromEncoding || 'HTML' === $fromEncoding) { + $fromEncoding = 'Windows-1252'; + } + if ('UTF-8' !== $fromEncoding) { + $s = \iconv($fromEncoding, 'UTF-8//IGNORE', $s); + } + + return preg_replace_callback('/[\x80-\xFF]+/', [__CLASS__, 'html_encoding_callback'], $s); + } + + if ('HTML-ENTITIES' === $fromEncoding) { + $s = html_entity_decode($s, \ENT_COMPAT, 'UTF-8'); + $fromEncoding = 'UTF-8'; + } + + return \iconv($fromEncoding, $toEncoding.'//IGNORE', $s); + } + + public static function mb_convert_variables($toEncoding, $fromEncoding, &...$vars) + { + $ok = true; + array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) { + if (false === $v = self::mb_convert_encoding($v, $toEncoding, $fromEncoding)) { + $ok = false; + } + }); + + return $ok ? $fromEncoding : false; + } + + public static function mb_decode_mimeheader($s) + { + return \iconv_mime_decode($s, 2, self::$internalEncoding); + } + + public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null) + { + trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', \E_USER_WARNING); + } + + public static function mb_decode_numericentity($s, $convmap, $encoding = null) + { + if (null !== $s && !is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) { + trigger_error('mb_decode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return null; + } + + if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) { + return false; + } + + if (null !== $encoding && !is_scalar($encoding)) { + trigger_error('mb_decode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return ''; // Instead of null (cf. mb_encode_numericentity). + } + + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @\iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = \iconv($encoding, 'UTF-8//IGNORE', $s); + } + + $cnt = floor(\count($convmap) / 4) * 4; + + for ($i = 0; $i < $cnt; $i += 4) { + // collector_decode_htmlnumericentity ignores $convmap[$i + 3] + $convmap[$i] += $convmap[$i + 2]; + $convmap[$i + 1] += $convmap[$i + 2]; + } + + $s = preg_replace_callback('/&#(?:0*([0-9]+)|x0*([0-9a-fA-F]+))(?!&);?/', function (array $m) use ($cnt, $convmap) { + $c = isset($m[2]) ? (int) hexdec($m[2]) : $m[1]; + for ($i = 0; $i < $cnt; $i += 4) { + if ($c >= $convmap[$i] && $c <= $convmap[$i + 1]) { + return self::mb_chr($c - $convmap[$i + 2]); + } + } + + return $m[0]; + }, $s); + + if (null === $encoding) { + return $s; + } + + return \iconv('UTF-8', $encoding.'//IGNORE', $s); + } + + public static function mb_encode_numericentity($s, $convmap, $encoding = null, $is_hex = false) + { + if (null !== $s && !is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) { + trigger_error('mb_encode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return null; + } + + if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) { + return false; + } + + if (null !== $encoding && !is_scalar($encoding)) { + trigger_error('mb_encode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return null; // Instead of '' (cf. mb_decode_numericentity). + } + + if (null !== $is_hex && !is_scalar($is_hex)) { + trigger_error('mb_encode_numericentity() expects parameter 4 to be boolean, '.\gettype($s).' given', \E_USER_WARNING); + + return null; + } + + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @\iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = \iconv($encoding, 'UTF-8//IGNORE', $s); + } + + static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; + + $cnt = floor(\count($convmap) / 4) * 4; + $i = 0; + $len = \strlen($s); + $result = ''; + + while ($i < $len) { + $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + $c = self::mb_ord($uchr); + + for ($j = 0; $j < $cnt; $j += 4) { + if ($c >= $convmap[$j] && $c <= $convmap[$j + 1]) { + $cOffset = ($c + $convmap[$j + 2]) & $convmap[$j + 3]; + $result .= $is_hex ? sprintf('&#x%X;', $cOffset) : '&#'.$cOffset.';'; + continue 2; + } + } + $result .= $uchr; + } + + if (null === $encoding) { + return $result; + } + + return \iconv('UTF-8', $encoding.'//IGNORE', $result); + } + + public static function mb_convert_case($s, $mode, $encoding = null) + { + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @\iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = \iconv($encoding, 'UTF-8//IGNORE', $s); + } + + if (\MB_CASE_TITLE == $mode) { + static $titleRegexp = null; + if (null === $titleRegexp) { + $titleRegexp = self::getData('titleCaseRegexp'); + } + $s = preg_replace_callback($titleRegexp, [__CLASS__, 'title_case'], $s); + } else { + if (\MB_CASE_UPPER == $mode) { + static $upper = null; + if (null === $upper) { + $upper = self::getData('upperCase'); + } + $map = $upper; + } else { + if (self::MB_CASE_FOLD === $mode) { + $s = str_replace(self::CASE_FOLD[0], self::CASE_FOLD[1], $s); + } + + static $lower = null; + if (null === $lower) { + $lower = self::getData('lowerCase'); + } + $map = $lower; + } + + static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; + + $i = 0; + $len = \strlen($s); + + while ($i < $len) { + $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + + if (isset($map[$uchr])) { + $uchr = $map[$uchr]; + $nlen = \strlen($uchr); + + if ($nlen == $ulen) { + $nlen = $i; + do { + $s[--$nlen] = $uchr[--$ulen]; + } while ($ulen); + } else { + $s = substr_replace($s, $uchr, $i - $ulen, $ulen); + $len += $nlen - $ulen; + $i += $nlen - $ulen; + } + } + } + } + + if (null === $encoding) { + return $s; + } + + return \iconv('UTF-8', $encoding.'//IGNORE', $s); + } + + public static function mb_internal_encoding($encoding = null) + { + if (null === $encoding) { + return self::$internalEncoding; + } + + $normalizedEncoding = self::getEncoding($encoding); + + if ('UTF-8' === $normalizedEncoding || false !== @\iconv($normalizedEncoding, $normalizedEncoding, ' ')) { + self::$internalEncoding = $normalizedEncoding; + + return true; + } + + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError(sprintf('Argument #1 ($encoding) must be a valid encoding, "%s" given', $encoding)); + } + + public static function mb_language($lang = null) + { + if (null === $lang) { + return self::$language; + } + + switch ($normalizedLang = strtolower($lang)) { + case 'uni': + case 'neutral': + self::$language = $normalizedLang; + + return true; + } + + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError(sprintf('Argument #1 ($language) must be a valid language, "%s" given', $lang)); + } + + public static function mb_list_encodings() + { + return ['UTF-8']; + } + + public static function mb_encoding_aliases($encoding) + { + switch (strtoupper($encoding)) { + case 'UTF8': + case 'UTF-8': + return ['utf8']; + } + + return false; + } + + public static function mb_check_encoding($var = null, $encoding = null) + { + if (null === $encoding) { + if (null === $var) { + return false; + } + $encoding = self::$internalEncoding; + } + + return self::mb_detect_encoding($var, [$encoding]) || false !== @\iconv($encoding, $encoding, $var); + } + + public static function mb_detect_encoding($str, $encodingList = null, $strict = false) + { + if (null === $encodingList) { + $encodingList = self::$encodingList; + } else { + if (!\is_array($encodingList)) { + $encodingList = array_map('trim', explode(',', $encodingList)); + } + $encodingList = array_map('strtoupper', $encodingList); + } + + foreach ($encodingList as $enc) { + switch ($enc) { + case 'ASCII': + if (!preg_match('/[\x80-\xFF]/', $str)) { + return $enc; + } + break; + + case 'UTF8': + case 'UTF-8': + if (preg_match('//u', $str)) { + return 'UTF-8'; + } + break; + + default: + if (0 === strncmp($enc, 'ISO-8859-', 9)) { + return $enc; + } + } + } + + return false; + } + + public static function mb_detect_order($encodingList = null) + { + if (null === $encodingList) { + return self::$encodingList; + } + + if (!\is_array($encodingList)) { + $encodingList = array_map('trim', explode(',', $encodingList)); + } + $encodingList = array_map('strtoupper', $encodingList); + + foreach ($encodingList as $enc) { + switch ($enc) { + default: + if (strncmp($enc, 'ISO-8859-', 9)) { + return false; + } + // no break + case 'ASCII': + case 'UTF8': + case 'UTF-8': + } + } + + self::$encodingList = $encodingList; + + return true; + } + + public static function mb_strlen($s, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return \strlen($s); + } + + return @\iconv_strlen($s, $encoding); + } + + public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return strpos($haystack, $needle, $offset); + } + + $needle = (string) $needle; + if ('' === $needle) { + if (80000 > \PHP_VERSION_ID) { + trigger_error(__METHOD__.': Empty delimiter', \E_USER_WARNING); + + return false; + } + + return 0; + } + + return \iconv_strpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return strrpos($haystack, $needle, $offset); + } + + if ($offset != (int) $offset) { + $offset = 0; + } elseif ($offset = (int) $offset) { + if ($offset < 0) { + if (0 > $offset += self::mb_strlen($needle)) { + $haystack = self::mb_substr($haystack, 0, $offset, $encoding); + } + $offset = 0; + } else { + $haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding); + } + } + + $pos = '' !== $needle || 80000 > \PHP_VERSION_ID + ? \iconv_strrpos($haystack, $needle, $encoding) + : self::mb_strlen($haystack, $encoding); + + return false !== $pos ? $offset + $pos : false; + } + + public static function mb_str_split($string, $split_length = 1, $encoding = null) + { + if (null !== $string && !is_scalar($string) && !(\is_object($string) && method_exists($string, '__toString'))) { + trigger_error('mb_str_split() expects parameter 1 to be string, '.\gettype($string).' given', \E_USER_WARNING); + + return null; + } + + if (1 > $split_length = (int) $split_length) { + if (80000 > \PHP_VERSION_ID) { + trigger_error('The length of each segment must be greater than zero', \E_USER_WARNING); + return false; + } + + throw new \ValueError('Argument #2 ($length) must be greater than 0'); + } + + if (null === $encoding) { + $encoding = mb_internal_encoding(); + } + + if ('UTF-8' === $encoding = self::getEncoding($encoding)) { + $rx = '/('; + while (65535 < $split_length) { + $rx .= '.{65535}'; + $split_length -= 65535; + } + $rx .= '.{'.$split_length.'})/us'; + + return preg_split($rx, $string, null, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY); + } + + $result = []; + $length = mb_strlen($string, $encoding); + + for ($i = 0; $i < $length; $i += $split_length) { + $result[] = mb_substr($string, $i, $split_length, $encoding); + } + + return $result; + } + + public static function mb_strtolower($s, $encoding = null) + { + return self::mb_convert_case($s, \MB_CASE_LOWER, $encoding); + } + + public static function mb_strtoupper($s, $encoding = null) + { + return self::mb_convert_case($s, \MB_CASE_UPPER, $encoding); + } + + public static function mb_substitute_character($c = null) + { + if (null === $c) { + return 'none'; + } + if (0 === strcasecmp($c, 'none')) { + return true; + } + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError('Argument #1 ($substitute_character) must be "none", "long", "entity" or a valid codepoint'); + } + + public static function mb_substr($s, $start, $length = null, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return (string) substr($s, $start, null === $length ? 2147483647 : $length); + } + + if ($start < 0) { + $start = \iconv_strlen($s, $encoding) + $start; + if ($start < 0) { + $start = 0; + } + } + + if (null === $length) { + $length = 2147483647; + } elseif ($length < 0) { + $length = \iconv_strlen($s, $encoding) + $length - $start; + if ($length < 0) { + return ''; + } + } + + return (string) \iconv_substr($s, $start, $length, $encoding); + } + + public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) + { + $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding); + $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding); + + return self::mb_strpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_stristr($haystack, $needle, $part = false, $encoding = null) + { + $pos = self::mb_stripos($haystack, $needle, 0, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strrchr($haystack, $needle, $part = false, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + $pos = strrpos($haystack, $needle); + } else { + $needle = self::mb_substr($needle, 0, 1, $encoding); + $pos = \iconv_strrpos($haystack, $needle, $encoding); + } + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strrichr($haystack, $needle, $part = false, $encoding = null) + { + $needle = self::mb_substr($needle, 0, 1, $encoding); + $pos = self::mb_strripos($haystack, $needle, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) + { + $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding); + $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding); + + return self::mb_strrpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_strstr($haystack, $needle, $part = false, $encoding = null) + { + $pos = strpos($haystack, $needle); + if (false === $pos) { + return false; + } + if ($part) { + return substr($haystack, 0, $pos); + } + + return substr($haystack, $pos); + } + + public static function mb_get_info($type = 'all') + { + $info = [ + 'internal_encoding' => self::$internalEncoding, + 'http_output' => 'pass', + 'http_output_conv_mimetypes' => '^(text/|application/xhtml\+xml)', + 'func_overload' => 0, + 'func_overload_list' => 'no overload', + 'mail_charset' => 'UTF-8', + 'mail_header_encoding' => 'BASE64', + 'mail_body_encoding' => 'BASE64', + 'illegal_chars' => 0, + 'encoding_translation' => 'Off', + 'language' => self::$language, + 'detect_order' => self::$encodingList, + 'substitute_character' => 'none', + 'strict_detection' => 'Off', + ]; + + if ('all' === $type) { + return $info; + } + if (isset($info[$type])) { + return $info[$type]; + } + + return false; + } + + public static function mb_http_input($type = '') + { + return false; + } + + public static function mb_http_output($encoding = null) + { + return null !== $encoding ? 'pass' === $encoding : 'pass'; + } + + public static function mb_strwidth($s, $encoding = null) + { + $encoding = self::getEncoding($encoding); + + if ('UTF-8' !== $encoding) { + $s = \iconv($encoding, 'UTF-8//IGNORE', $s); + } + + $s = preg_replace('/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u', '', $s, -1, $wide); + + return ($wide << 1) + \iconv_strlen($s, 'UTF-8'); + } + + public static function mb_substr_count($haystack, $needle, $encoding = null) + { + return substr_count($haystack, $needle); + } + + public static function mb_output_handler($contents, $status) + { + return $contents; + } + + public static function mb_chr($code, $encoding = null) + { + if (0x80 > $code %= 0x200000) { + $s = \chr($code); + } elseif (0x800 > $code) { + $s = \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F); + } elseif (0x10000 > $code) { + $s = \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } else { + $s = \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } + + if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { + $s = mb_convert_encoding($s, $encoding, 'UTF-8'); + } + + return $s; + } + + public static function mb_ord($s, $encoding = null) + { + if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { + $s = mb_convert_encoding($s, 'UTF-8', $encoding); + } + + if (1 === \strlen($s)) { + return \ord($s); + } + + $code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0; + if (0xF0 <= $code) { + return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80; + } + if (0xE0 <= $code) { + return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80; + } + if (0xC0 <= $code) { + return (($code - 0xC0) << 6) + $s[2] - 0x80; + } + + return $code; + } + + private static function getSubpart($pos, $part, $haystack, $encoding) + { + if (false === $pos) { + return false; + } + if ($part) { + return self::mb_substr($haystack, 0, $pos, $encoding); + } + + return self::mb_substr($haystack, $pos, null, $encoding); + } + + private static function html_encoding_callback(array $m) + { + $i = 1; + $entities = ''; + $m = unpack('C*', htmlentities($m[0], \ENT_COMPAT, 'UTF-8')); + + while (isset($m[$i])) { + if (0x80 > $m[$i]) { + $entities .= \chr($m[$i++]); + continue; + } + if (0xF0 <= $m[$i]) { + $c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; + } elseif (0xE0 <= $m[$i]) { + $c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; + } else { + $c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80; + } + + $entities .= '&#'.$c.';'; + } + + return $entities; + } + + private static function title_case(array $s) + { + return self::mb_convert_case($s[1], \MB_CASE_UPPER, 'UTF-8').self::mb_convert_case($s[2], \MB_CASE_LOWER, 'UTF-8'); + } + + private static function getData($file) + { + if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) { + return require $file; + } + + return false; + } + + private static function getEncoding($encoding) + { + if (null === $encoding) { + return self::$internalEncoding; + } + + if ('UTF-8' === $encoding) { + return 'UTF-8'; + } + + $encoding = strtoupper($encoding); + + if ('8BIT' === $encoding || 'BINARY' === $encoding) { + return 'CP850'; + } + + if ('UTF8' === $encoding) { + return 'UTF-8'; + } + + return $encoding; + } +} diff --git a/vendor/symfony/polyfill-mbstring/README.md b/vendor/symfony/polyfill-mbstring/README.md new file mode 100644 index 0000000..4efb599 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/README.md @@ -0,0 +1,13 @@ +Symfony Polyfill / Mbstring +=========================== + +This component provides a partial, native PHP implementation for the +[Mbstring](https://php.net/mbstring) extension. + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php b/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php new file mode 100644 index 0000000..fac60b0 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php @@ -0,0 +1,1397 @@ + 'a', + 'B' => 'b', + 'C' => 'c', + 'D' => 'd', + 'E' => 'e', + 'F' => 'f', + 'G' => 'g', + 'H' => 'h', + 'I' => 'i', + 'J' => 'j', + 'K' => 'k', + 'L' => 'l', + 'M' => 'm', + 'N' => 'n', + 'O' => 'o', + 'P' => 'p', + 'Q' => 'q', + 'R' => 'r', + 'S' => 's', + 'T' => 't', + 'U' => 'u', + 'V' => 'v', + 'W' => 'w', + 'X' => 'x', + 'Y' => 'y', + 'Z' => 'z', + 'À' => 'à', + 'Á' => 'á', + 'Â' => 'â', + 'Ã' => 'ã', + 'Ä' => 'ä', + 'Å' => 'å', + 'Æ' => 'æ', + 'Ç' => 'ç', + 'È' => 'è', + 'É' => 'é', + 'Ê' => 'ê', + 'Ë' => 'ë', + 'Ì' => 'ì', + 'Í' => 'í', + 'Î' => 'î', + 'Ï' => 'ï', + 'Ð' => 'ð', + 'Ñ' => 'ñ', + 'Ò' => 'ò', + 'Ó' => 'ó', + 'Ô' => 'ô', + 'Õ' => 'õ', + 'Ö' => 'ö', + 'Ø' => 'ø', + 'Ù' => 'ù', + 'Ú' => 'ú', + 'Û' => 'û', + 'Ü' => 'ü', + 'Ý' => 'ý', + 'Þ' => 'þ', + 'Ā' => 'ā', + 'Ă' => 'ă', + 'Ą' => 'ą', + 'Ć' => 'ć', + 'Ĉ' => 'ĉ', + 'Ċ' => 'ċ', + 'Č' => 'č', + 'Ď' => 'ď', + 'Đ' => 'đ', + 'Ē' => 'ē', + 'Ĕ' => 'ĕ', + 'Ė' => 'ė', + 'Ę' => 'ę', + 'Ě' => 'ě', + 'Ĝ' => 'ĝ', + 'Ğ' => 'ğ', + 'Ġ' => 'ġ', + 'Ģ' => 'ģ', + 'Ĥ' => 'ĥ', + 'Ħ' => 'ħ', + 'Ĩ' => 'ĩ', + 'Ī' => 'ī', + 'Ĭ' => 'ĭ', + 'Į' => 'į', + 'İ' => 'i̇', + 'IJ' => 'ij', + 'Ĵ' => 'ĵ', + 'Ķ' => 'ķ', + 'Ĺ' => 'ĺ', + 'Ļ' => 'ļ', + 'Ľ' => 'ľ', + 'Ŀ' => 'ŀ', + 'Ł' => 'ł', + 'Ń' => 'ń', + 'Ņ' => 'ņ', + 'Ň' => 'ň', + 'Ŋ' => 'ŋ', + 'Ō' => 'ō', + 'Ŏ' => 'ŏ', + 'Ő' => 'ő', + 'Œ' => 'œ', + 'Ŕ' => 'ŕ', + 'Ŗ' => 'ŗ', + 'Ř' => 'ř', + 'Ś' => 'ś', + 'Ŝ' => 'ŝ', + 'Ş' => 'ş', + 'Š' => 'š', + 'Ţ' => 'ţ', + 'Ť' => 'ť', + 'Ŧ' => 'ŧ', + 'Ũ' => 'ũ', + 'Ū' => 'ū', + 'Ŭ' => 'ŭ', + 'Ů' => 'ů', + 'Ű' => 'ű', + 'Ų' => 'ų', + 'Ŵ' => 'ŵ', + 'Ŷ' => 'ŷ', + 'Ÿ' => 'ÿ', + 'Ź' => 'ź', + 'Ż' => 'ż', + 'Ž' => 'ž', + 'Ɓ' => 'ɓ', + 'Ƃ' => 'ƃ', + 'Ƅ' => 'ƅ', + 'Ɔ' => 'ɔ', + 'Ƈ' => 'ƈ', + 'Ɖ' => 'ɖ', + 'Ɗ' => 'ɗ', + 'Ƌ' => 'ƌ', + 'Ǝ' => 'ǝ', + 'Ə' => 'ə', + 'Ɛ' => 'ɛ', + 'Ƒ' => 'ƒ', + 'Ɠ' => 'ɠ', + 'Ɣ' => 'ɣ', + 'Ɩ' => 'ɩ', + 'Ɨ' => 'ɨ', + 'Ƙ' => 'ƙ', + 'Ɯ' => 'ɯ', + 'Ɲ' => 'ɲ', + 'Ɵ' => 'ɵ', + 'Ơ' => 'ơ', + 'Ƣ' => 'ƣ', + 'Ƥ' => 'ƥ', + 'Ʀ' => 'ʀ', + 'Ƨ' => 'ƨ', + 'Ʃ' => 'ʃ', + 'Ƭ' => 'ƭ', + 'Ʈ' => 'ʈ', + 'Ư' => 'ư', + 'Ʊ' => 'ʊ', + 'Ʋ' => 'ʋ', + 'Ƴ' => 'ƴ', + 'Ƶ' => 'ƶ', + 'Ʒ' => 'ʒ', + 'Ƹ' => 'ƹ', + 'Ƽ' => 'ƽ', + 'DŽ' => 'dž', + 'Dž' => 'dž', + 'LJ' => 'lj', + 'Lj' => 'lj', + 'NJ' => 'nj', + 'Nj' => 'nj', + 'Ǎ' => 'ǎ', + 'Ǐ' => 'ǐ', + 'Ǒ' => 'ǒ', + 'Ǔ' => 'ǔ', + 'Ǖ' => 'ǖ', + 'Ǘ' => 'ǘ', + 'Ǚ' => 'ǚ', + 'Ǜ' => 'ǜ', + 'Ǟ' => 'ǟ', + 'Ǡ' => 'ǡ', + 'Ǣ' => 'ǣ', + 'Ǥ' => 'ǥ', + 'Ǧ' => 'ǧ', + 'Ǩ' => 'ǩ', + 'Ǫ' => 'ǫ', + 'Ǭ' => 'ǭ', + 'Ǯ' => 'ǯ', + 'DZ' => 'dz', + 'Dz' => 'dz', + 'Ǵ' => 'ǵ', + 'Ƕ' => 'ƕ', + 'Ƿ' => 'ƿ', + 'Ǹ' => 'ǹ', + 'Ǻ' => 'ǻ', + 'Ǽ' => 'ǽ', + 'Ǿ' => 'ǿ', + 'Ȁ' => 'ȁ', + 'Ȃ' => 'ȃ', + 'Ȅ' => 'ȅ', + 'Ȇ' => 'ȇ', + 'Ȉ' => 'ȉ', + 'Ȋ' => 'ȋ', + 'Ȍ' => 'ȍ', + 'Ȏ' => 'ȏ', + 'Ȑ' => 'ȑ', + 'Ȓ' => 'ȓ', + 'Ȕ' => 'ȕ', + 'Ȗ' => 'ȗ', + 'Ș' => 'ș', + 'Ț' => 'ț', + 'Ȝ' => 'ȝ', + 'Ȟ' => 'ȟ', + 'Ƞ' => 'ƞ', + 'Ȣ' => 'ȣ', + 'Ȥ' => 'ȥ', + 'Ȧ' => 'ȧ', + 'Ȩ' => 'ȩ', + 'Ȫ' => 'ȫ', + 'Ȭ' => 'ȭ', + 'Ȯ' => 'ȯ', + 'Ȱ' => 'ȱ', + 'Ȳ' => 'ȳ', + 'Ⱥ' => 'ⱥ', + 'Ȼ' => 'ȼ', + 'Ƚ' => 'ƚ', + 'Ⱦ' => 'ⱦ', + 'Ɂ' => 'ɂ', + 'Ƀ' => 'ƀ', + 'Ʉ' => 'ʉ', + 'Ʌ' => 'ʌ', + 'Ɇ' => 'ɇ', + 'Ɉ' => 'ɉ', + 'Ɋ' => 'ɋ', + 'Ɍ' => 'ɍ', + 'Ɏ' => 'ɏ', + 'Ͱ' => 'ͱ', + 'Ͳ' => 'ͳ', + 'Ͷ' => 'ͷ', + 'Ϳ' => 'ϳ', + 'Ά' => 'ά', + 'Έ' => 'έ', + 'Ή' => 'ή', + 'Ί' => 'ί', + 'Ό' => 'ό', + 'Ύ' => 'ύ', + 'Ώ' => 'ώ', + 'Α' => 'α', + 'Β' => 'β', + 'Γ' => 'γ', + 'Δ' => 'δ', + 'Ε' => 'ε', + 'Ζ' => 'ζ', + 'Η' => 'η', + 'Θ' => 'θ', + 'Ι' => 'ι', + 'Κ' => 'κ', + 'Λ' => 'λ', + 'Μ' => 'μ', + 'Ν' => 'ν', + 'Ξ' => 'ξ', + 'Ο' => 'ο', + 'Π' => 'π', + 'Ρ' => 'ρ', + 'Σ' => 'σ', + 'Τ' => 'τ', + 'Υ' => 'υ', + 'Φ' => 'φ', + 'Χ' => 'χ', + 'Ψ' => 'ψ', + 'Ω' => 'ω', + 'Ϊ' => 'ϊ', + 'Ϋ' => 'ϋ', + 'Ϗ' => 'ϗ', + 'Ϙ' => 'ϙ', + 'Ϛ' => 'ϛ', + 'Ϝ' => 'ϝ', + 'Ϟ' => 'ϟ', + 'Ϡ' => 'ϡ', + 'Ϣ' => 'ϣ', + 'Ϥ' => 'ϥ', + 'Ϧ' => 'ϧ', + 'Ϩ' => 'ϩ', + 'Ϫ' => 'ϫ', + 'Ϭ' => 'ϭ', + 'Ϯ' => 'ϯ', + 'ϴ' => 'θ', + 'Ϸ' => 'ϸ', + 'Ϲ' => 'ϲ', + 'Ϻ' => 'ϻ', + 'Ͻ' => 'ͻ', + 'Ͼ' => 'ͼ', + 'Ͽ' => 'ͽ', + 'Ѐ' => 'ѐ', + 'Ё' => 'ё', + 'Ђ' => 'ђ', + 'Ѓ' => 'ѓ', + 'Є' => 'є', + 'Ѕ' => 'ѕ', + 'І' => 'і', + 'Ї' => 'ї', + 'Ј' => 'ј', + 'Љ' => 'љ', + 'Њ' => 'њ', + 'Ћ' => 'ћ', + 'Ќ' => 'ќ', + 'Ѝ' => 'ѝ', + 'Ў' => 'ў', + 'Џ' => 'џ', + 'А' => 'а', + 'Б' => 'б', + 'В' => 'в', + 'Г' => 'г', + 'Д' => 'д', + 'Е' => 'е', + 'Ж' => 'ж', + 'З' => 'з', + 'И' => 'и', + 'Й' => 'й', + 'К' => 'к', + 'Л' => 'л', + 'М' => 'м', + 'Н' => 'н', + 'О' => 'о', + 'П' => 'п', + 'Р' => 'р', + 'С' => 'с', + 'Т' => 'т', + 'У' => 'у', + 'Ф' => 'ф', + 'Х' => 'х', + 'Ц' => 'ц', + 'Ч' => 'ч', + 'Ш' => 'ш', + 'Щ' => 'щ', + 'Ъ' => 'ъ', + 'Ы' => 'ы', + 'Ь' => 'ь', + 'Э' => 'э', + 'Ю' => 'ю', + 'Я' => 'я', + 'Ѡ' => 'ѡ', + 'Ѣ' => 'ѣ', + 'Ѥ' => 'ѥ', + 'Ѧ' => 'ѧ', + 'Ѩ' => 'ѩ', + 'Ѫ' => 'ѫ', + 'Ѭ' => 'ѭ', + 'Ѯ' => 'ѯ', + 'Ѱ' => 'ѱ', + 'Ѳ' => 'ѳ', + 'Ѵ' => 'ѵ', + 'Ѷ' => 'ѷ', + 'Ѹ' => 'ѹ', + 'Ѻ' => 'ѻ', + 'Ѽ' => 'ѽ', + 'Ѿ' => 'ѿ', + 'Ҁ' => 'ҁ', + 'Ҋ' => 'ҋ', + 'Ҍ' => 'ҍ', + 'Ҏ' => 'ҏ', + 'Ґ' => 'ґ', + 'Ғ' => 'ғ', + 'Ҕ' => 'ҕ', + 'Җ' => 'җ', + 'Ҙ' => 'ҙ', + 'Қ' => 'қ', + 'Ҝ' => 'ҝ', + 'Ҟ' => 'ҟ', + 'Ҡ' => 'ҡ', + 'Ң' => 'ң', + 'Ҥ' => 'ҥ', + 'Ҧ' => 'ҧ', + 'Ҩ' => 'ҩ', + 'Ҫ' => 'ҫ', + 'Ҭ' => 'ҭ', + 'Ү' => 'ү', + 'Ұ' => 'ұ', + 'Ҳ' => 'ҳ', + 'Ҵ' => 'ҵ', + 'Ҷ' => 'ҷ', + 'Ҹ' => 'ҹ', + 'Һ' => 'һ', + 'Ҽ' => 'ҽ', + 'Ҿ' => 'ҿ', + 'Ӏ' => 'ӏ', + 'Ӂ' => 'ӂ', + 'Ӄ' => 'ӄ', + 'Ӆ' => 'ӆ', + 'Ӈ' => 'ӈ', + 'Ӊ' => 'ӊ', + 'Ӌ' => 'ӌ', + 'Ӎ' => 'ӎ', + 'Ӑ' => 'ӑ', + 'Ӓ' => 'ӓ', + 'Ӕ' => 'ӕ', + 'Ӗ' => 'ӗ', + 'Ә' => 'ә', + 'Ӛ' => 'ӛ', + 'Ӝ' => 'ӝ', + 'Ӟ' => 'ӟ', + 'Ӡ' => 'ӡ', + 'Ӣ' => 'ӣ', + 'Ӥ' => 'ӥ', + 'Ӧ' => 'ӧ', + 'Ө' => 'ө', + 'Ӫ' => 'ӫ', + 'Ӭ' => 'ӭ', + 'Ӯ' => 'ӯ', + 'Ӱ' => 'ӱ', + 'Ӳ' => 'ӳ', + 'Ӵ' => 'ӵ', + 'Ӷ' => 'ӷ', + 'Ӹ' => 'ӹ', + 'Ӻ' => 'ӻ', + 'Ӽ' => 'ӽ', + 'Ӿ' => 'ӿ', + 'Ԁ' => 'ԁ', + 'Ԃ' => 'ԃ', + 'Ԅ' => 'ԅ', + 'Ԇ' => 'ԇ', + 'Ԉ' => 'ԉ', + 'Ԋ' => 'ԋ', + 'Ԍ' => 'ԍ', + 'Ԏ' => 'ԏ', + 'Ԑ' => 'ԑ', + 'Ԓ' => 'ԓ', + 'Ԕ' => 'ԕ', + 'Ԗ' => 'ԗ', + 'Ԙ' => 'ԙ', + 'Ԛ' => 'ԛ', + 'Ԝ' => 'ԝ', + 'Ԟ' => 'ԟ', + 'Ԡ' => 'ԡ', + 'Ԣ' => 'ԣ', + 'Ԥ' => 'ԥ', + 'Ԧ' => 'ԧ', + 'Ԩ' => 'ԩ', + 'Ԫ' => 'ԫ', + 'Ԭ' => 'ԭ', + 'Ԯ' => 'ԯ', + 'Ա' => 'ա', + 'Բ' => 'բ', + 'Գ' => 'գ', + 'Դ' => 'դ', + 'Ե' => 'ե', + 'Զ' => 'զ', + 'Է' => 'է', + 'Ը' => 'ը', + 'Թ' => 'թ', + 'Ժ' => 'ժ', + 'Ի' => 'ի', + 'Լ' => 'լ', + 'Խ' => 'խ', + 'Ծ' => 'ծ', + 'Կ' => 'կ', + 'Հ' => 'հ', + 'Ձ' => 'ձ', + 'Ղ' => 'ղ', + 'Ճ' => 'ճ', + 'Մ' => 'մ', + 'Յ' => 'յ', + 'Ն' => 'ն', + 'Շ' => 'շ', + 'Ո' => 'ո', + 'Չ' => 'չ', + 'Պ' => 'պ', + 'Ջ' => 'ջ', + 'Ռ' => 'ռ', + 'Ս' => 'ս', + 'Վ' => 'վ', + 'Տ' => 'տ', + 'Ր' => 'ր', + 'Ց' => 'ց', + 'Ւ' => 'ւ', + 'Փ' => 'փ', + 'Ք' => 'ք', + 'Օ' => 'օ', + 'Ֆ' => 'ֆ', + 'Ⴀ' => 'ⴀ', + 'Ⴁ' => 'ⴁ', + 'Ⴂ' => 'ⴂ', + 'Ⴃ' => 'ⴃ', + 'Ⴄ' => 'ⴄ', + 'Ⴅ' => 'ⴅ', + 'Ⴆ' => 'ⴆ', + 'Ⴇ' => 'ⴇ', + 'Ⴈ' => 'ⴈ', + 'Ⴉ' => 'ⴉ', + 'Ⴊ' => 'ⴊ', + 'Ⴋ' => 'ⴋ', + 'Ⴌ' => 'ⴌ', + 'Ⴍ' => 'ⴍ', + 'Ⴎ' => 'ⴎ', + 'Ⴏ' => 'ⴏ', + 'Ⴐ' => 'ⴐ', + 'Ⴑ' => 'ⴑ', + 'Ⴒ' => 'ⴒ', + 'Ⴓ' => 'ⴓ', + 'Ⴔ' => 'ⴔ', + 'Ⴕ' => 'ⴕ', + 'Ⴖ' => 'ⴖ', + 'Ⴗ' => 'ⴗ', + 'Ⴘ' => 'ⴘ', + 'Ⴙ' => 'ⴙ', + 'Ⴚ' => 'ⴚ', + 'Ⴛ' => 'ⴛ', + 'Ⴜ' => 'ⴜ', + 'Ⴝ' => 'ⴝ', + 'Ⴞ' => 'ⴞ', + 'Ⴟ' => 'ⴟ', + 'Ⴠ' => 'ⴠ', + 'Ⴡ' => 'ⴡ', + 'Ⴢ' => 'ⴢ', + 'Ⴣ' => 'ⴣ', + 'Ⴤ' => 'ⴤ', + 'Ⴥ' => 'ⴥ', + 'Ⴧ' => 'ⴧ', + 'Ⴭ' => 'ⴭ', + 'Ꭰ' => 'ꭰ', + 'Ꭱ' => 'ꭱ', + 'Ꭲ' => 'ꭲ', + 'Ꭳ' => 'ꭳ', + 'Ꭴ' => 'ꭴ', + 'Ꭵ' => 'ꭵ', + 'Ꭶ' => 'ꭶ', + 'Ꭷ' => 'ꭷ', + 'Ꭸ' => 'ꭸ', + 'Ꭹ' => 'ꭹ', + 'Ꭺ' => 'ꭺ', + 'Ꭻ' => 'ꭻ', + 'Ꭼ' => 'ꭼ', + 'Ꭽ' => 'ꭽ', + 'Ꭾ' => 'ꭾ', + 'Ꭿ' => 'ꭿ', + 'Ꮀ' => 'ꮀ', + 'Ꮁ' => 'ꮁ', + 'Ꮂ' => 'ꮂ', + 'Ꮃ' => 'ꮃ', + 'Ꮄ' => 'ꮄ', + 'Ꮅ' => 'ꮅ', + 'Ꮆ' => 'ꮆ', + 'Ꮇ' => 'ꮇ', + 'Ꮈ' => 'ꮈ', + 'Ꮉ' => 'ꮉ', + 'Ꮊ' => 'ꮊ', + 'Ꮋ' => 'ꮋ', + 'Ꮌ' => 'ꮌ', + 'Ꮍ' => 'ꮍ', + 'Ꮎ' => 'ꮎ', + 'Ꮏ' => 'ꮏ', + 'Ꮐ' => 'ꮐ', + 'Ꮑ' => 'ꮑ', + 'Ꮒ' => 'ꮒ', + 'Ꮓ' => 'ꮓ', + 'Ꮔ' => 'ꮔ', + 'Ꮕ' => 'ꮕ', + 'Ꮖ' => 'ꮖ', + 'Ꮗ' => 'ꮗ', + 'Ꮘ' => 'ꮘ', + 'Ꮙ' => 'ꮙ', + 'Ꮚ' => 'ꮚ', + 'Ꮛ' => 'ꮛ', + 'Ꮜ' => 'ꮜ', + 'Ꮝ' => 'ꮝ', + 'Ꮞ' => 'ꮞ', + 'Ꮟ' => 'ꮟ', + 'Ꮠ' => 'ꮠ', + 'Ꮡ' => 'ꮡ', + 'Ꮢ' => 'ꮢ', + 'Ꮣ' => 'ꮣ', + 'Ꮤ' => 'ꮤ', + 'Ꮥ' => 'ꮥ', + 'Ꮦ' => 'ꮦ', + 'Ꮧ' => 'ꮧ', + 'Ꮨ' => 'ꮨ', + 'Ꮩ' => 'ꮩ', + 'Ꮪ' => 'ꮪ', + 'Ꮫ' => 'ꮫ', + 'Ꮬ' => 'ꮬ', + 'Ꮭ' => 'ꮭ', + 'Ꮮ' => 'ꮮ', + 'Ꮯ' => 'ꮯ', + 'Ꮰ' => 'ꮰ', + 'Ꮱ' => 'ꮱ', + 'Ꮲ' => 'ꮲ', + 'Ꮳ' => 'ꮳ', + 'Ꮴ' => 'ꮴ', + 'Ꮵ' => 'ꮵ', + 'Ꮶ' => 'ꮶ', + 'Ꮷ' => 'ꮷ', + 'Ꮸ' => 'ꮸ', + 'Ꮹ' => 'ꮹ', + 'Ꮺ' => 'ꮺ', + 'Ꮻ' => 'ꮻ', + 'Ꮼ' => 'ꮼ', + 'Ꮽ' => 'ꮽ', + 'Ꮾ' => 'ꮾ', + 'Ꮿ' => 'ꮿ', + 'Ᏸ' => 'ᏸ', + 'Ᏹ' => 'ᏹ', + 'Ᏺ' => 'ᏺ', + 'Ᏻ' => 'ᏻ', + 'Ᏼ' => 'ᏼ', + 'Ᏽ' => 'ᏽ', + 'Ა' => 'ა', + 'Ბ' => 'ბ', + 'Გ' => 'გ', + 'Დ' => 'დ', + 'Ე' => 'ე', + 'Ვ' => 'ვ', + 'Ზ' => 'ზ', + 'Თ' => 'თ', + 'Ი' => 'ი', + 'Კ' => 'კ', + 'Ლ' => 'ლ', + 'Მ' => 'მ', + 'Ნ' => 'ნ', + 'Ო' => 'ო', + 'Პ' => 'პ', + 'Ჟ' => 'ჟ', + 'Რ' => 'რ', + 'Ს' => 'ს', + 'Ტ' => 'ტ', + 'Უ' => 'უ', + 'Ფ' => 'ფ', + 'Ქ' => 'ქ', + 'Ღ' => 'ღ', + 'Ყ' => 'ყ', + 'Შ' => 'შ', + 'Ჩ' => 'ჩ', + 'Ც' => 'ც', + 'Ძ' => 'ძ', + 'Წ' => 'წ', + 'Ჭ' => 'ჭ', + 'Ხ' => 'ხ', + 'Ჯ' => 'ჯ', + 'Ჰ' => 'ჰ', + 'Ჱ' => 'ჱ', + 'Ჲ' => 'ჲ', + 'Ჳ' => 'ჳ', + 'Ჴ' => 'ჴ', + 'Ჵ' => 'ჵ', + 'Ჶ' => 'ჶ', + 'Ჷ' => 'ჷ', + 'Ჸ' => 'ჸ', + 'Ჹ' => 'ჹ', + 'Ჺ' => 'ჺ', + 'Ჽ' => 'ჽ', + 'Ჾ' => 'ჾ', + 'Ჿ' => 'ჿ', + 'Ḁ' => 'ḁ', + 'Ḃ' => 'ḃ', + 'Ḅ' => 'ḅ', + 'Ḇ' => 'ḇ', + 'Ḉ' => 'ḉ', + 'Ḋ' => 'ḋ', + 'Ḍ' => 'ḍ', + 'Ḏ' => 'ḏ', + 'Ḑ' => 'ḑ', + 'Ḓ' => 'ḓ', + 'Ḕ' => 'ḕ', + 'Ḗ' => 'ḗ', + 'Ḙ' => 'ḙ', + 'Ḛ' => 'ḛ', + 'Ḝ' => 'ḝ', + 'Ḟ' => 'ḟ', + 'Ḡ' => 'ḡ', + 'Ḣ' => 'ḣ', + 'Ḥ' => 'ḥ', + 'Ḧ' => 'ḧ', + 'Ḩ' => 'ḩ', + 'Ḫ' => 'ḫ', + 'Ḭ' => 'ḭ', + 'Ḯ' => 'ḯ', + 'Ḱ' => 'ḱ', + 'Ḳ' => 'ḳ', + 'Ḵ' => 'ḵ', + 'Ḷ' => 'ḷ', + 'Ḹ' => 'ḹ', + 'Ḻ' => 'ḻ', + 'Ḽ' => 'ḽ', + 'Ḿ' => 'ḿ', + 'Ṁ' => 'ṁ', + 'Ṃ' => 'ṃ', + 'Ṅ' => 'ṅ', + 'Ṇ' => 'ṇ', + 'Ṉ' => 'ṉ', + 'Ṋ' => 'ṋ', + 'Ṍ' => 'ṍ', + 'Ṏ' => 'ṏ', + 'Ṑ' => 'ṑ', + 'Ṓ' => 'ṓ', + 'Ṕ' => 'ṕ', + 'Ṗ' => 'ṗ', + 'Ṙ' => 'ṙ', + 'Ṛ' => 'ṛ', + 'Ṝ' => 'ṝ', + 'Ṟ' => 'ṟ', + 'Ṡ' => 'ṡ', + 'Ṣ' => 'ṣ', + 'Ṥ' => 'ṥ', + 'Ṧ' => 'ṧ', + 'Ṩ' => 'ṩ', + 'Ṫ' => 'ṫ', + 'Ṭ' => 'ṭ', + 'Ṯ' => 'ṯ', + 'Ṱ' => 'ṱ', + 'Ṳ' => 'ṳ', + 'Ṵ' => 'ṵ', + 'Ṷ' => 'ṷ', + 'Ṹ' => 'ṹ', + 'Ṻ' => 'ṻ', + 'Ṽ' => 'ṽ', + 'Ṿ' => 'ṿ', + 'Ẁ' => 'ẁ', + 'Ẃ' => 'ẃ', + 'Ẅ' => 'ẅ', + 'Ẇ' => 'ẇ', + 'Ẉ' => 'ẉ', + 'Ẋ' => 'ẋ', + 'Ẍ' => 'ẍ', + 'Ẏ' => 'ẏ', + 'Ẑ' => 'ẑ', + 'Ẓ' => 'ẓ', + 'Ẕ' => 'ẕ', + 'ẞ' => 'ß', + 'Ạ' => 'ạ', + 'Ả' => 'ả', + 'Ấ' => 'ấ', + 'Ầ' => 'ầ', + 'Ẩ' => 'ẩ', + 'Ẫ' => 'ẫ', + 'Ậ' => 'ậ', + 'Ắ' => 'ắ', + 'Ằ' => 'ằ', + 'Ẳ' => 'ẳ', + 'Ẵ' => 'ẵ', + 'Ặ' => 'ặ', + 'Ẹ' => 'ẹ', + 'Ẻ' => 'ẻ', + 'Ẽ' => 'ẽ', + 'Ế' => 'ế', + 'Ề' => 'ề', + 'Ể' => 'ể', + 'Ễ' => 'ễ', + 'Ệ' => 'ệ', + 'Ỉ' => 'ỉ', + 'Ị' => 'ị', + 'Ọ' => 'ọ', + 'Ỏ' => 'ỏ', + 'Ố' => 'ố', + 'Ồ' => 'ồ', + 'Ổ' => 'ổ', + 'Ỗ' => 'ỗ', + 'Ộ' => 'ộ', + 'Ớ' => 'ớ', + 'Ờ' => 'ờ', + 'Ở' => 'ở', + 'Ỡ' => 'ỡ', + 'Ợ' => 'ợ', + 'Ụ' => 'ụ', + 'Ủ' => 'ủ', + 'Ứ' => 'ứ', + 'Ừ' => 'ừ', + 'Ử' => 'ử', + 'Ữ' => 'ữ', + 'Ự' => 'ự', + 'Ỳ' => 'ỳ', + 'Ỵ' => 'ỵ', + 'Ỷ' => 'ỷ', + 'Ỹ' => 'ỹ', + 'Ỻ' => 'ỻ', + 'Ỽ' => 'ỽ', + 'Ỿ' => 'ỿ', + 'Ἀ' => 'ἀ', + 'Ἁ' => 'ἁ', + 'Ἂ' => 'ἂ', + 'Ἃ' => 'ἃ', + 'Ἄ' => 'ἄ', + 'Ἅ' => 'ἅ', + 'Ἆ' => 'ἆ', + 'Ἇ' => 'ἇ', + 'Ἐ' => 'ἐ', + 'Ἑ' => 'ἑ', + 'Ἒ' => 'ἒ', + 'Ἓ' => 'ἓ', + 'Ἔ' => 'ἔ', + 'Ἕ' => 'ἕ', + 'Ἠ' => 'ἠ', + 'Ἡ' => 'ἡ', + 'Ἢ' => 'ἢ', + 'Ἣ' => 'ἣ', + 'Ἤ' => 'ἤ', + 'Ἥ' => 'ἥ', + 'Ἦ' => 'ἦ', + 'Ἧ' => 'ἧ', + 'Ἰ' => 'ἰ', + 'Ἱ' => 'ἱ', + 'Ἲ' => 'ἲ', + 'Ἳ' => 'ἳ', + 'Ἴ' => 'ἴ', + 'Ἵ' => 'ἵ', + 'Ἶ' => 'ἶ', + 'Ἷ' => 'ἷ', + 'Ὀ' => 'ὀ', + 'Ὁ' => 'ὁ', + 'Ὂ' => 'ὂ', + 'Ὃ' => 'ὃ', + 'Ὄ' => 'ὄ', + 'Ὅ' => 'ὅ', + 'Ὑ' => 'ὑ', + 'Ὓ' => 'ὓ', + 'Ὕ' => 'ὕ', + 'Ὗ' => 'ὗ', + 'Ὠ' => 'ὠ', + 'Ὡ' => 'ὡ', + 'Ὢ' => 'ὢ', + 'Ὣ' => 'ὣ', + 'Ὤ' => 'ὤ', + 'Ὥ' => 'ὥ', + 'Ὦ' => 'ὦ', + 'Ὧ' => 'ὧ', + 'ᾈ' => 'ᾀ', + 'ᾉ' => 'ᾁ', + 'ᾊ' => 'ᾂ', + 'ᾋ' => 'ᾃ', + 'ᾌ' => 'ᾄ', + 'ᾍ' => 'ᾅ', + 'ᾎ' => 'ᾆ', + 'ᾏ' => 'ᾇ', + 'ᾘ' => 'ᾐ', + 'ᾙ' => 'ᾑ', + 'ᾚ' => 'ᾒ', + 'ᾛ' => 'ᾓ', + 'ᾜ' => 'ᾔ', + 'ᾝ' => 'ᾕ', + 'ᾞ' => 'ᾖ', + 'ᾟ' => 'ᾗ', + 'ᾨ' => 'ᾠ', + 'ᾩ' => 'ᾡ', + 'ᾪ' => 'ᾢ', + 'ᾫ' => 'ᾣ', + 'ᾬ' => 'ᾤ', + 'ᾭ' => 'ᾥ', + 'ᾮ' => 'ᾦ', + 'ᾯ' => 'ᾧ', + 'Ᾰ' => 'ᾰ', + 'Ᾱ' => 'ᾱ', + 'Ὰ' => 'ὰ', + 'Ά' => 'ά', + 'ᾼ' => 'ᾳ', + 'Ὲ' => 'ὲ', + 'Έ' => 'έ', + 'Ὴ' => 'ὴ', + 'Ή' => 'ή', + 'ῌ' => 'ῃ', + 'Ῐ' => 'ῐ', + 'Ῑ' => 'ῑ', + 'Ὶ' => 'ὶ', + 'Ί' => 'ί', + 'Ῠ' => 'ῠ', + 'Ῡ' => 'ῡ', + 'Ὺ' => 'ὺ', + 'Ύ' => 'ύ', + 'Ῥ' => 'ῥ', + 'Ὸ' => 'ὸ', + 'Ό' => 'ό', + 'Ὼ' => 'ὼ', + 'Ώ' => 'ώ', + 'ῼ' => 'ῳ', + 'Ω' => 'ω', + 'K' => 'k', + 'Å' => 'å', + 'Ⅎ' => 'ⅎ', + 'Ⅰ' => 'ⅰ', + 'Ⅱ' => 'ⅱ', + 'Ⅲ' => 'ⅲ', + 'Ⅳ' => 'ⅳ', + 'Ⅴ' => 'ⅴ', + 'Ⅵ' => 'ⅵ', + 'Ⅶ' => 'ⅶ', + 'Ⅷ' => 'ⅷ', + 'Ⅸ' => 'ⅸ', + 'Ⅹ' => 'ⅹ', + 'Ⅺ' => 'ⅺ', + 'Ⅻ' => 'ⅻ', + 'Ⅼ' => 'ⅼ', + 'Ⅽ' => 'ⅽ', + 'Ⅾ' => 'ⅾ', + 'Ⅿ' => 'ⅿ', + 'Ↄ' => 'ↄ', + 'Ⓐ' => 'ⓐ', + 'Ⓑ' => 'ⓑ', + 'Ⓒ' => 'ⓒ', + 'Ⓓ' => 'ⓓ', + 'Ⓔ' => 'ⓔ', + 'Ⓕ' => 'ⓕ', + 'Ⓖ' => 'ⓖ', + 'Ⓗ' => 'ⓗ', + 'Ⓘ' => 'ⓘ', + 'Ⓙ' => 'ⓙ', + 'Ⓚ' => 'ⓚ', + 'Ⓛ' => 'ⓛ', + 'Ⓜ' => 'ⓜ', + 'Ⓝ' => 'ⓝ', + 'Ⓞ' => 'ⓞ', + 'Ⓟ' => 'ⓟ', + 'Ⓠ' => 'ⓠ', + 'Ⓡ' => 'ⓡ', + 'Ⓢ' => 'ⓢ', + 'Ⓣ' => 'ⓣ', + 'Ⓤ' => 'ⓤ', + 'Ⓥ' => 'ⓥ', + 'Ⓦ' => 'ⓦ', + 'Ⓧ' => 'ⓧ', + 'Ⓨ' => 'ⓨ', + 'Ⓩ' => 'ⓩ', + 'Ⰰ' => 'ⰰ', + 'Ⰱ' => 'ⰱ', + 'Ⰲ' => 'ⰲ', + 'Ⰳ' => 'ⰳ', + 'Ⰴ' => 'ⰴ', + 'Ⰵ' => 'ⰵ', + 'Ⰶ' => 'ⰶ', + 'Ⰷ' => 'ⰷ', + 'Ⰸ' => 'ⰸ', + 'Ⰹ' => 'ⰹ', + 'Ⰺ' => 'ⰺ', + 'Ⰻ' => 'ⰻ', + 'Ⰼ' => 'ⰼ', + 'Ⰽ' => 'ⰽ', + 'Ⰾ' => 'ⰾ', + 'Ⰿ' => 'ⰿ', + 'Ⱀ' => 'ⱀ', + 'Ⱁ' => 'ⱁ', + 'Ⱂ' => 'ⱂ', + 'Ⱃ' => 'ⱃ', + 'Ⱄ' => 'ⱄ', + 'Ⱅ' => 'ⱅ', + 'Ⱆ' => 'ⱆ', + 'Ⱇ' => 'ⱇ', + 'Ⱈ' => 'ⱈ', + 'Ⱉ' => 'ⱉ', + 'Ⱊ' => 'ⱊ', + 'Ⱋ' => 'ⱋ', + 'Ⱌ' => 'ⱌ', + 'Ⱍ' => 'ⱍ', + 'Ⱎ' => 'ⱎ', + 'Ⱏ' => 'ⱏ', + 'Ⱐ' => 'ⱐ', + 'Ⱑ' => 'ⱑ', + 'Ⱒ' => 'ⱒ', + 'Ⱓ' => 'ⱓ', + 'Ⱔ' => 'ⱔ', + 'Ⱕ' => 'ⱕ', + 'Ⱖ' => 'ⱖ', + 'Ⱗ' => 'ⱗ', + 'Ⱘ' => 'ⱘ', + 'Ⱙ' => 'ⱙ', + 'Ⱚ' => 'ⱚ', + 'Ⱛ' => 'ⱛ', + 'Ⱜ' => 'ⱜ', + 'Ⱝ' => 'ⱝ', + 'Ⱞ' => 'ⱞ', + 'Ⱡ' => 'ⱡ', + 'Ɫ' => 'ɫ', + 'Ᵽ' => 'ᵽ', + 'Ɽ' => 'ɽ', + 'Ⱨ' => 'ⱨ', + 'Ⱪ' => 'ⱪ', + 'Ⱬ' => 'ⱬ', + 'Ɑ' => 'ɑ', + 'Ɱ' => 'ɱ', + 'Ɐ' => 'ɐ', + 'Ɒ' => 'ɒ', + 'Ⱳ' => 'ⱳ', + 'Ⱶ' => 'ⱶ', + 'Ȿ' => 'ȿ', + 'Ɀ' => 'ɀ', + 'Ⲁ' => 'ⲁ', + 'Ⲃ' => 'ⲃ', + 'Ⲅ' => 'ⲅ', + 'Ⲇ' => 'ⲇ', + 'Ⲉ' => 'ⲉ', + 'Ⲋ' => 'ⲋ', + 'Ⲍ' => 'ⲍ', + 'Ⲏ' => 'ⲏ', + 'Ⲑ' => 'ⲑ', + 'Ⲓ' => 'ⲓ', + 'Ⲕ' => 'ⲕ', + 'Ⲗ' => 'ⲗ', + 'Ⲙ' => 'ⲙ', + 'Ⲛ' => 'ⲛ', + 'Ⲝ' => 'ⲝ', + 'Ⲟ' => 'ⲟ', + 'Ⲡ' => 'ⲡ', + 'Ⲣ' => 'ⲣ', + 'Ⲥ' => 'ⲥ', + 'Ⲧ' => 'ⲧ', + 'Ⲩ' => 'ⲩ', + 'Ⲫ' => 'ⲫ', + 'Ⲭ' => 'ⲭ', + 'Ⲯ' => 'ⲯ', + 'Ⲱ' => 'ⲱ', + 'Ⲳ' => 'ⲳ', + 'Ⲵ' => 'ⲵ', + 'Ⲷ' => 'ⲷ', + 'Ⲹ' => 'ⲹ', + 'Ⲻ' => 'ⲻ', + 'Ⲽ' => 'ⲽ', + 'Ⲿ' => 'ⲿ', + 'Ⳁ' => 'ⳁ', + 'Ⳃ' => 'ⳃ', + 'Ⳅ' => 'ⳅ', + 'Ⳇ' => 'ⳇ', + 'Ⳉ' => 'ⳉ', + 'Ⳋ' => 'ⳋ', + 'Ⳍ' => 'ⳍ', + 'Ⳏ' => 'ⳏ', + 'Ⳑ' => 'ⳑ', + 'Ⳓ' => 'ⳓ', + 'Ⳕ' => 'ⳕ', + 'Ⳗ' => 'ⳗ', + 'Ⳙ' => 'ⳙ', + 'Ⳛ' => 'ⳛ', + 'Ⳝ' => 'ⳝ', + 'Ⳟ' => 'ⳟ', + 'Ⳡ' => 'ⳡ', + 'Ⳣ' => 'ⳣ', + 'Ⳬ' => 'ⳬ', + 'Ⳮ' => 'ⳮ', + 'Ⳳ' => 'ⳳ', + 'Ꙁ' => 'ꙁ', + 'Ꙃ' => 'ꙃ', + 'Ꙅ' => 'ꙅ', + 'Ꙇ' => 'ꙇ', + 'Ꙉ' => 'ꙉ', + 'Ꙋ' => 'ꙋ', + 'Ꙍ' => 'ꙍ', + 'Ꙏ' => 'ꙏ', + 'Ꙑ' => 'ꙑ', + 'Ꙓ' => 'ꙓ', + 'Ꙕ' => 'ꙕ', + 'Ꙗ' => 'ꙗ', + 'Ꙙ' => 'ꙙ', + 'Ꙛ' => 'ꙛ', + 'Ꙝ' => 'ꙝ', + 'Ꙟ' => 'ꙟ', + 'Ꙡ' => 'ꙡ', + 'Ꙣ' => 'ꙣ', + 'Ꙥ' => 'ꙥ', + 'Ꙧ' => 'ꙧ', + 'Ꙩ' => 'ꙩ', + 'Ꙫ' => 'ꙫ', + 'Ꙭ' => 'ꙭ', + 'Ꚁ' => 'ꚁ', + 'Ꚃ' => 'ꚃ', + 'Ꚅ' => 'ꚅ', + 'Ꚇ' => 'ꚇ', + 'Ꚉ' => 'ꚉ', + 'Ꚋ' => 'ꚋ', + 'Ꚍ' => 'ꚍ', + 'Ꚏ' => 'ꚏ', + 'Ꚑ' => 'ꚑ', + 'Ꚓ' => 'ꚓ', + 'Ꚕ' => 'ꚕ', + 'Ꚗ' => 'ꚗ', + 'Ꚙ' => 'ꚙ', + 'Ꚛ' => 'ꚛ', + 'Ꜣ' => 'ꜣ', + 'Ꜥ' => 'ꜥ', + 'Ꜧ' => 'ꜧ', + 'Ꜩ' => 'ꜩ', + 'Ꜫ' => 'ꜫ', + 'Ꜭ' => 'ꜭ', + 'Ꜯ' => 'ꜯ', + 'Ꜳ' => 'ꜳ', + 'Ꜵ' => 'ꜵ', + 'Ꜷ' => 'ꜷ', + 'Ꜹ' => 'ꜹ', + 'Ꜻ' => 'ꜻ', + 'Ꜽ' => 'ꜽ', + 'Ꜿ' => 'ꜿ', + 'Ꝁ' => 'ꝁ', + 'Ꝃ' => 'ꝃ', + 'Ꝅ' => 'ꝅ', + 'Ꝇ' => 'ꝇ', + 'Ꝉ' => 'ꝉ', + 'Ꝋ' => 'ꝋ', + 'Ꝍ' => 'ꝍ', + 'Ꝏ' => 'ꝏ', + 'Ꝑ' => 'ꝑ', + 'Ꝓ' => 'ꝓ', + 'Ꝕ' => 'ꝕ', + 'Ꝗ' => 'ꝗ', + 'Ꝙ' => 'ꝙ', + 'Ꝛ' => 'ꝛ', + 'Ꝝ' => 'ꝝ', + 'Ꝟ' => 'ꝟ', + 'Ꝡ' => 'ꝡ', + 'Ꝣ' => 'ꝣ', + 'Ꝥ' => 'ꝥ', + 'Ꝧ' => 'ꝧ', + 'Ꝩ' => 'ꝩ', + 'Ꝫ' => 'ꝫ', + 'Ꝭ' => 'ꝭ', + 'Ꝯ' => 'ꝯ', + 'Ꝺ' => 'ꝺ', + 'Ꝼ' => 'ꝼ', + 'Ᵹ' => 'ᵹ', + 'Ꝿ' => 'ꝿ', + 'Ꞁ' => 'ꞁ', + 'Ꞃ' => 'ꞃ', + 'Ꞅ' => 'ꞅ', + 'Ꞇ' => 'ꞇ', + 'Ꞌ' => 'ꞌ', + 'Ɥ' => 'ɥ', + 'Ꞑ' => 'ꞑ', + 'Ꞓ' => 'ꞓ', + 'Ꞗ' => 'ꞗ', + 'Ꞙ' => 'ꞙ', + 'Ꞛ' => 'ꞛ', + 'Ꞝ' => 'ꞝ', + 'Ꞟ' => 'ꞟ', + 'Ꞡ' => 'ꞡ', + 'Ꞣ' => 'ꞣ', + 'Ꞥ' => 'ꞥ', + 'Ꞧ' => 'ꞧ', + 'Ꞩ' => 'ꞩ', + 'Ɦ' => 'ɦ', + 'Ɜ' => 'ɜ', + 'Ɡ' => 'ɡ', + 'Ɬ' => 'ɬ', + 'Ɪ' => 'ɪ', + 'Ʞ' => 'ʞ', + 'Ʇ' => 'ʇ', + 'Ʝ' => 'ʝ', + 'Ꭓ' => 'ꭓ', + 'Ꞵ' => 'ꞵ', + 'Ꞷ' => 'ꞷ', + 'Ꞹ' => 'ꞹ', + 'Ꞻ' => 'ꞻ', + 'Ꞽ' => 'ꞽ', + 'Ꞿ' => 'ꞿ', + 'Ꟃ' => 'ꟃ', + 'Ꞔ' => 'ꞔ', + 'Ʂ' => 'ʂ', + 'Ᶎ' => 'ᶎ', + 'Ꟈ' => 'ꟈ', + 'Ꟊ' => 'ꟊ', + 'Ꟶ' => 'ꟶ', + 'A' => 'a', + 'B' => 'b', + 'C' => 'c', + 'D' => 'd', + 'E' => 'e', + 'F' => 'f', + 'G' => 'g', + 'H' => 'h', + 'I' => 'i', + 'J' => 'j', + 'K' => 'k', + 'L' => 'l', + 'M' => 'm', + 'N' => 'n', + 'O' => 'o', + 'P' => 'p', + 'Q' => 'q', + 'R' => 'r', + 'S' => 's', + 'T' => 't', + 'U' => 'u', + 'V' => 'v', + 'W' => 'w', + 'X' => 'x', + 'Y' => 'y', + 'Z' => 'z', + '𐐀' => '𐐨', + '𐐁' => '𐐩', + '𐐂' => '𐐪', + '𐐃' => '𐐫', + '𐐄' => '𐐬', + '𐐅' => '𐐭', + '𐐆' => '𐐮', + '𐐇' => '𐐯', + '𐐈' => '𐐰', + '𐐉' => '𐐱', + '𐐊' => '𐐲', + '𐐋' => '𐐳', + '𐐌' => '𐐴', + '𐐍' => '𐐵', + '𐐎' => '𐐶', + '𐐏' => '𐐷', + '𐐐' => '𐐸', + '𐐑' => '𐐹', + '𐐒' => '𐐺', + '𐐓' => '𐐻', + '𐐔' => '𐐼', + '𐐕' => '𐐽', + '𐐖' => '𐐾', + '𐐗' => '𐐿', + '𐐘' => '𐑀', + '𐐙' => '𐑁', + '𐐚' => '𐑂', + '𐐛' => '𐑃', + '𐐜' => '𐑄', + '𐐝' => '𐑅', + '𐐞' => '𐑆', + '𐐟' => '𐑇', + '𐐠' => '𐑈', + '𐐡' => '𐑉', + '𐐢' => '𐑊', + '𐐣' => '𐑋', + '𐐤' => '𐑌', + '𐐥' => '𐑍', + '𐐦' => '𐑎', + '𐐧' => '𐑏', + '𐒰' => '𐓘', + '𐒱' => '𐓙', + '𐒲' => '𐓚', + '𐒳' => '𐓛', + '𐒴' => '𐓜', + '𐒵' => '𐓝', + '𐒶' => '𐓞', + '𐒷' => '𐓟', + '𐒸' => '𐓠', + '𐒹' => '𐓡', + '𐒺' => '𐓢', + '𐒻' => '𐓣', + '𐒼' => '𐓤', + '𐒽' => '𐓥', + '𐒾' => '𐓦', + '𐒿' => '𐓧', + '𐓀' => '𐓨', + '𐓁' => '𐓩', + '𐓂' => '𐓪', + '𐓃' => '𐓫', + '𐓄' => '𐓬', + '𐓅' => '𐓭', + '𐓆' => '𐓮', + '𐓇' => '𐓯', + '𐓈' => '𐓰', + '𐓉' => '𐓱', + '𐓊' => '𐓲', + '𐓋' => '𐓳', + '𐓌' => '𐓴', + '𐓍' => '𐓵', + '𐓎' => '𐓶', + '𐓏' => '𐓷', + '𐓐' => '𐓸', + '𐓑' => '𐓹', + '𐓒' => '𐓺', + '𐓓' => '𐓻', + '𐲀' => '𐳀', + '𐲁' => '𐳁', + '𐲂' => '𐳂', + '𐲃' => '𐳃', + '𐲄' => '𐳄', + '𐲅' => '𐳅', + '𐲆' => '𐳆', + '𐲇' => '𐳇', + '𐲈' => '𐳈', + '𐲉' => '𐳉', + '𐲊' => '𐳊', + '𐲋' => '𐳋', + '𐲌' => '𐳌', + '𐲍' => '𐳍', + '𐲎' => '𐳎', + '𐲏' => '𐳏', + '𐲐' => '𐳐', + '𐲑' => '𐳑', + '𐲒' => '𐳒', + '𐲓' => '𐳓', + '𐲔' => '𐳔', + '𐲕' => '𐳕', + '𐲖' => '𐳖', + '𐲗' => '𐳗', + '𐲘' => '𐳘', + '𐲙' => '𐳙', + '𐲚' => '𐳚', + '𐲛' => '𐳛', + '𐲜' => '𐳜', + '𐲝' => '𐳝', + '𐲞' => '𐳞', + '𐲟' => '𐳟', + '𐲠' => '𐳠', + '𐲡' => '𐳡', + '𐲢' => '𐳢', + '𐲣' => '𐳣', + '𐲤' => '𐳤', + '𐲥' => '𐳥', + '𐲦' => '𐳦', + '𐲧' => '𐳧', + '𐲨' => '𐳨', + '𐲩' => '𐳩', + '𐲪' => '𐳪', + '𐲫' => '𐳫', + '𐲬' => '𐳬', + '𐲭' => '𐳭', + '𐲮' => '𐳮', + '𐲯' => '𐳯', + '𐲰' => '𐳰', + '𐲱' => '𐳱', + '𐲲' => '𐳲', + '𑢠' => '𑣀', + '𑢡' => '𑣁', + '𑢢' => '𑣂', + '𑢣' => '𑣃', + '𑢤' => '𑣄', + '𑢥' => '𑣅', + '𑢦' => '𑣆', + '𑢧' => '𑣇', + '𑢨' => '𑣈', + '𑢩' => '𑣉', + '𑢪' => '𑣊', + '𑢫' => '𑣋', + '𑢬' => '𑣌', + '𑢭' => '𑣍', + '𑢮' => '𑣎', + '𑢯' => '𑣏', + '𑢰' => '𑣐', + '𑢱' => '𑣑', + '𑢲' => '𑣒', + '𑢳' => '𑣓', + '𑢴' => '𑣔', + '𑢵' => '𑣕', + '𑢶' => '𑣖', + '𑢷' => '𑣗', + '𑢸' => '𑣘', + '𑢹' => '𑣙', + '𑢺' => '𑣚', + '𑢻' => '𑣛', + '𑢼' => '𑣜', + '𑢽' => '𑣝', + '𑢾' => '𑣞', + '𑢿' => '𑣟', + '𖹀' => '𖹠', + '𖹁' => '𖹡', + '𖹂' => '𖹢', + '𖹃' => '𖹣', + '𖹄' => '𖹤', + '𖹅' => '𖹥', + '𖹆' => '𖹦', + '𖹇' => '𖹧', + '𖹈' => '𖹨', + '𖹉' => '𖹩', + '𖹊' => '𖹪', + '𖹋' => '𖹫', + '𖹌' => '𖹬', + '𖹍' => '𖹭', + '𖹎' => '𖹮', + '𖹏' => '𖹯', + '𖹐' => '𖹰', + '𖹑' => '𖹱', + '𖹒' => '𖹲', + '𖹓' => '𖹳', + '𖹔' => '𖹴', + '𖹕' => '𖹵', + '𖹖' => '𖹶', + '𖹗' => '𖹷', + '𖹘' => '𖹸', + '𖹙' => '𖹹', + '𖹚' => '𖹺', + '𖹛' => '𖹻', + '𖹜' => '𖹼', + '𖹝' => '𖹽', + '𖹞' => '𖹾', + '𖹟' => '𖹿', + '𞤀' => '𞤢', + '𞤁' => '𞤣', + '𞤂' => '𞤤', + '𞤃' => '𞤥', + '𞤄' => '𞤦', + '𞤅' => '𞤧', + '𞤆' => '𞤨', + '𞤇' => '𞤩', + '𞤈' => '𞤪', + '𞤉' => '𞤫', + '𞤊' => '𞤬', + '𞤋' => '𞤭', + '𞤌' => '𞤮', + '𞤍' => '𞤯', + '𞤎' => '𞤰', + '𞤏' => '𞤱', + '𞤐' => '𞤲', + '𞤑' => '𞤳', + '𞤒' => '𞤴', + '𞤓' => '𞤵', + '𞤔' => '𞤶', + '𞤕' => '𞤷', + '𞤖' => '𞤸', + '𞤗' => '𞤹', + '𞤘' => '𞤺', + '𞤙' => '𞤻', + '𞤚' => '𞤼', + '𞤛' => '𞤽', + '𞤜' => '𞤾', + '𞤝' => '𞤿', + '𞤞' => '𞥀', + '𞤟' => '𞥁', + '𞤠' => '𞥂', + '𞤡' => '𞥃', +); diff --git a/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php b/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php new file mode 100644 index 0000000..2a8f6e7 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php @@ -0,0 +1,5 @@ + 'A', + 'b' => 'B', + 'c' => 'C', + 'd' => 'D', + 'e' => 'E', + 'f' => 'F', + 'g' => 'G', + 'h' => 'H', + 'i' => 'I', + 'j' => 'J', + 'k' => 'K', + 'l' => 'L', + 'm' => 'M', + 'n' => 'N', + 'o' => 'O', + 'p' => 'P', + 'q' => 'Q', + 'r' => 'R', + 's' => 'S', + 't' => 'T', + 'u' => 'U', + 'v' => 'V', + 'w' => 'W', + 'x' => 'X', + 'y' => 'Y', + 'z' => 'Z', + 'µ' => 'Μ', + 'à' => 'À', + 'á' => 'Á', + 'â' => 'Â', + 'ã' => 'Ã', + 'ä' => 'Ä', + 'å' => 'Å', + 'æ' => 'Æ', + 'ç' => 'Ç', + 'è' => 'È', + 'é' => 'É', + 'ê' => 'Ê', + 'ë' => 'Ë', + 'ì' => 'Ì', + 'í' => 'Í', + 'î' => 'Î', + 'ï' => 'Ï', + 'ð' => 'Ð', + 'ñ' => 'Ñ', + 'ò' => 'Ò', + 'ó' => 'Ó', + 'ô' => 'Ô', + 'õ' => 'Õ', + 'ö' => 'Ö', + 'ø' => 'Ø', + 'ù' => 'Ù', + 'ú' => 'Ú', + 'û' => 'Û', + 'ü' => 'Ü', + 'ý' => 'Ý', + 'þ' => 'Þ', + 'ÿ' => 'Ÿ', + 'ā' => 'Ā', + 'ă' => 'Ă', + 'ą' => 'Ą', + 'ć' => 'Ć', + 'ĉ' => 'Ĉ', + 'ċ' => 'Ċ', + 'č' => 'Č', + 'ď' => 'Ď', + 'đ' => 'Đ', + 'ē' => 'Ē', + 'ĕ' => 'Ĕ', + 'ė' => 'Ė', + 'ę' => 'Ę', + 'ě' => 'Ě', + 'ĝ' => 'Ĝ', + 'ğ' => 'Ğ', + 'ġ' => 'Ġ', + 'ģ' => 'Ģ', + 'ĥ' => 'Ĥ', + 'ħ' => 'Ħ', + 'ĩ' => 'Ĩ', + 'ī' => 'Ī', + 'ĭ' => 'Ĭ', + 'į' => 'Į', + 'ı' => 'I', + 'ij' => 'IJ', + 'ĵ' => 'Ĵ', + 'ķ' => 'Ķ', + 'ĺ' => 'Ĺ', + 'ļ' => 'Ļ', + 'ľ' => 'Ľ', + 'ŀ' => 'Ŀ', + 'ł' => 'Ł', + 'ń' => 'Ń', + 'ņ' => 'Ņ', + 'ň' => 'Ň', + 'ŋ' => 'Ŋ', + 'ō' => 'Ō', + 'ŏ' => 'Ŏ', + 'ő' => 'Ő', + 'œ' => 'Œ', + 'ŕ' => 'Ŕ', + 'ŗ' => 'Ŗ', + 'ř' => 'Ř', + 'ś' => 'Ś', + 'ŝ' => 'Ŝ', + 'ş' => 'Ş', + 'š' => 'Š', + 'ţ' => 'Ţ', + 'ť' => 'Ť', + 'ŧ' => 'Ŧ', + 'ũ' => 'Ũ', + 'ū' => 'Ū', + 'ŭ' => 'Ŭ', + 'ů' => 'Ů', + 'ű' => 'Ű', + 'ų' => 'Ų', + 'ŵ' => 'Ŵ', + 'ŷ' => 'Ŷ', + 'ź' => 'Ź', + 'ż' => 'Ż', + 'ž' => 'Ž', + 'ſ' => 'S', + 'ƀ' => 'Ƀ', + 'ƃ' => 'Ƃ', + 'ƅ' => 'Ƅ', + 'ƈ' => 'Ƈ', + 'ƌ' => 'Ƌ', + 'ƒ' => 'Ƒ', + 'ƕ' => 'Ƕ', + 'ƙ' => 'Ƙ', + 'ƚ' => 'Ƚ', + 'ƞ' => 'Ƞ', + 'ơ' => 'Ơ', + 'ƣ' => 'Ƣ', + 'ƥ' => 'Ƥ', + 'ƨ' => 'Ƨ', + 'ƭ' => 'Ƭ', + 'ư' => 'Ư', + 'ƴ' => 'Ƴ', + 'ƶ' => 'Ƶ', + 'ƹ' => 'Ƹ', + 'ƽ' => 'Ƽ', + 'ƿ' => 'Ƿ', + 'Dž' => 'DŽ', + 'dž' => 'DŽ', + 'Lj' => 'LJ', + 'lj' => 'LJ', + 'Nj' => 'NJ', + 'nj' => 'NJ', + 'ǎ' => 'Ǎ', + 'ǐ' => 'Ǐ', + 'ǒ' => 'Ǒ', + 'ǔ' => 'Ǔ', + 'ǖ' => 'Ǖ', + 'ǘ' => 'Ǘ', + 'ǚ' => 'Ǚ', + 'ǜ' => 'Ǜ', + 'ǝ' => 'Ǝ', + 'ǟ' => 'Ǟ', + 'ǡ' => 'Ǡ', + 'ǣ' => 'Ǣ', + 'ǥ' => 'Ǥ', + 'ǧ' => 'Ǧ', + 'ǩ' => 'Ǩ', + 'ǫ' => 'Ǫ', + 'ǭ' => 'Ǭ', + 'ǯ' => 'Ǯ', + 'Dz' => 'DZ', + 'dz' => 'DZ', + 'ǵ' => 'Ǵ', + 'ǹ' => 'Ǹ', + 'ǻ' => 'Ǻ', + 'ǽ' => 'Ǽ', + 'ǿ' => 'Ǿ', + 'ȁ' => 'Ȁ', + 'ȃ' => 'Ȃ', + 'ȅ' => 'Ȅ', + 'ȇ' => 'Ȇ', + 'ȉ' => 'Ȉ', + 'ȋ' => 'Ȋ', + 'ȍ' => 'Ȍ', + 'ȏ' => 'Ȏ', + 'ȑ' => 'Ȑ', + 'ȓ' => 'Ȓ', + 'ȕ' => 'Ȕ', + 'ȗ' => 'Ȗ', + 'ș' => 'Ș', + 'ț' => 'Ț', + 'ȝ' => 'Ȝ', + 'ȟ' => 'Ȟ', + 'ȣ' => 'Ȣ', + 'ȥ' => 'Ȥ', + 'ȧ' => 'Ȧ', + 'ȩ' => 'Ȩ', + 'ȫ' => 'Ȫ', + 'ȭ' => 'Ȭ', + 'ȯ' => 'Ȯ', + 'ȱ' => 'Ȱ', + 'ȳ' => 'Ȳ', + 'ȼ' => 'Ȼ', + 'ȿ' => 'Ȿ', + 'ɀ' => 'Ɀ', + 'ɂ' => 'Ɂ', + 'ɇ' => 'Ɇ', + 'ɉ' => 'Ɉ', + 'ɋ' => 'Ɋ', + 'ɍ' => 'Ɍ', + 'ɏ' => 'Ɏ', + 'ɐ' => 'Ɐ', + 'ɑ' => 'Ɑ', + 'ɒ' => 'Ɒ', + 'ɓ' => 'Ɓ', + 'ɔ' => 'Ɔ', + 'ɖ' => 'Ɖ', + 'ɗ' => 'Ɗ', + 'ə' => 'Ə', + 'ɛ' => 'Ɛ', + 'ɜ' => 'Ɜ', + 'ɠ' => 'Ɠ', + 'ɡ' => 'Ɡ', + 'ɣ' => 'Ɣ', + 'ɥ' => 'Ɥ', + 'ɦ' => 'Ɦ', + 'ɨ' => 'Ɨ', + 'ɩ' => 'Ɩ', + 'ɪ' => 'Ɪ', + 'ɫ' => 'Ɫ', + 'ɬ' => 'Ɬ', + 'ɯ' => 'Ɯ', + 'ɱ' => 'Ɱ', + 'ɲ' => 'Ɲ', + 'ɵ' => 'Ɵ', + 'ɽ' => 'Ɽ', + 'ʀ' => 'Ʀ', + 'ʂ' => 'Ʂ', + 'ʃ' => 'Ʃ', + 'ʇ' => 'Ʇ', + 'ʈ' => 'Ʈ', + 'ʉ' => 'Ʉ', + 'ʊ' => 'Ʊ', + 'ʋ' => 'Ʋ', + 'ʌ' => 'Ʌ', + 'ʒ' => 'Ʒ', + 'ʝ' => 'Ʝ', + 'ʞ' => 'Ʞ', + 'ͅ' => 'Ι', + 'ͱ' => 'Ͱ', + 'ͳ' => 'Ͳ', + 'ͷ' => 'Ͷ', + 'ͻ' => 'Ͻ', + 'ͼ' => 'Ͼ', + 'ͽ' => 'Ͽ', + 'ά' => 'Ά', + 'έ' => 'Έ', + 'ή' => 'Ή', + 'ί' => 'Ί', + 'α' => 'Α', + 'β' => 'Β', + 'γ' => 'Γ', + 'δ' => 'Δ', + 'ε' => 'Ε', + 'ζ' => 'Ζ', + 'η' => 'Η', + 'θ' => 'Θ', + 'ι' => 'Ι', + 'κ' => 'Κ', + 'λ' => 'Λ', + 'μ' => 'Μ', + 'ν' => 'Ν', + 'ξ' => 'Ξ', + 'ο' => 'Ο', + 'π' => 'Π', + 'ρ' => 'Ρ', + 'ς' => 'Σ', + 'σ' => 'Σ', + 'τ' => 'Τ', + 'υ' => 'Υ', + 'φ' => 'Φ', + 'χ' => 'Χ', + 'ψ' => 'Ψ', + 'ω' => 'Ω', + 'ϊ' => 'Ϊ', + 'ϋ' => 'Ϋ', + 'ό' => 'Ό', + 'ύ' => 'Ύ', + 'ώ' => 'Ώ', + 'ϐ' => 'Β', + 'ϑ' => 'Θ', + 'ϕ' => 'Φ', + 'ϖ' => 'Π', + 'ϗ' => 'Ϗ', + 'ϙ' => 'Ϙ', + 'ϛ' => 'Ϛ', + 'ϝ' => 'Ϝ', + 'ϟ' => 'Ϟ', + 'ϡ' => 'Ϡ', + 'ϣ' => 'Ϣ', + 'ϥ' => 'Ϥ', + 'ϧ' => 'Ϧ', + 'ϩ' => 'Ϩ', + 'ϫ' => 'Ϫ', + 'ϭ' => 'Ϭ', + 'ϯ' => 'Ϯ', + 'ϰ' => 'Κ', + 'ϱ' => 'Ρ', + 'ϲ' => 'Ϲ', + 'ϳ' => 'Ϳ', + 'ϵ' => 'Ε', + 'ϸ' => 'Ϸ', + 'ϻ' => 'Ϻ', + 'а' => 'А', + 'б' => 'Б', + 'в' => 'В', + 'г' => 'Г', + 'д' => 'Д', + 'е' => 'Е', + 'ж' => 'Ж', + 'з' => 'З', + 'и' => 'И', + 'й' => 'Й', + 'к' => 'К', + 'л' => 'Л', + 'м' => 'М', + 'н' => 'Н', + 'о' => 'О', + 'п' => 'П', + 'р' => 'Р', + 'с' => 'С', + 'т' => 'Т', + 'у' => 'У', + 'ф' => 'Ф', + 'х' => 'Х', + 'ц' => 'Ц', + 'ч' => 'Ч', + 'ш' => 'Ш', + 'щ' => 'Щ', + 'ъ' => 'Ъ', + 'ы' => 'Ы', + 'ь' => 'Ь', + 'э' => 'Э', + 'ю' => 'Ю', + 'я' => 'Я', + 'ѐ' => 'Ѐ', + 'ё' => 'Ё', + 'ђ' => 'Ђ', + 'ѓ' => 'Ѓ', + 'є' => 'Є', + 'ѕ' => 'Ѕ', + 'і' => 'І', + 'ї' => 'Ї', + 'ј' => 'Ј', + 'љ' => 'Љ', + 'њ' => 'Њ', + 'ћ' => 'Ћ', + 'ќ' => 'Ќ', + 'ѝ' => 'Ѝ', + 'ў' => 'Ў', + 'џ' => 'Џ', + 'ѡ' => 'Ѡ', + 'ѣ' => 'Ѣ', + 'ѥ' => 'Ѥ', + 'ѧ' => 'Ѧ', + 'ѩ' => 'Ѩ', + 'ѫ' => 'Ѫ', + 'ѭ' => 'Ѭ', + 'ѯ' => 'Ѯ', + 'ѱ' => 'Ѱ', + 'ѳ' => 'Ѳ', + 'ѵ' => 'Ѵ', + 'ѷ' => 'Ѷ', + 'ѹ' => 'Ѹ', + 'ѻ' => 'Ѻ', + 'ѽ' => 'Ѽ', + 'ѿ' => 'Ѿ', + 'ҁ' => 'Ҁ', + 'ҋ' => 'Ҋ', + 'ҍ' => 'Ҍ', + 'ҏ' => 'Ҏ', + 'ґ' => 'Ґ', + 'ғ' => 'Ғ', + 'ҕ' => 'Ҕ', + 'җ' => 'Җ', + 'ҙ' => 'Ҙ', + 'қ' => 'Қ', + 'ҝ' => 'Ҝ', + 'ҟ' => 'Ҟ', + 'ҡ' => 'Ҡ', + 'ң' => 'Ң', + 'ҥ' => 'Ҥ', + 'ҧ' => 'Ҧ', + 'ҩ' => 'Ҩ', + 'ҫ' => 'Ҫ', + 'ҭ' => 'Ҭ', + 'ү' => 'Ү', + 'ұ' => 'Ұ', + 'ҳ' => 'Ҳ', + 'ҵ' => 'Ҵ', + 'ҷ' => 'Ҷ', + 'ҹ' => 'Ҹ', + 'һ' => 'Һ', + 'ҽ' => 'Ҽ', + 'ҿ' => 'Ҿ', + 'ӂ' => 'Ӂ', + 'ӄ' => 'Ӄ', + 'ӆ' => 'Ӆ', + 'ӈ' => 'Ӈ', + 'ӊ' => 'Ӊ', + 'ӌ' => 'Ӌ', + 'ӎ' => 'Ӎ', + 'ӏ' => 'Ӏ', + 'ӑ' => 'Ӑ', + 'ӓ' => 'Ӓ', + 'ӕ' => 'Ӕ', + 'ӗ' => 'Ӗ', + 'ә' => 'Ә', + 'ӛ' => 'Ӛ', + 'ӝ' => 'Ӝ', + 'ӟ' => 'Ӟ', + 'ӡ' => 'Ӡ', + 'ӣ' => 'Ӣ', + 'ӥ' => 'Ӥ', + 'ӧ' => 'Ӧ', + 'ө' => 'Ө', + 'ӫ' => 'Ӫ', + 'ӭ' => 'Ӭ', + 'ӯ' => 'Ӯ', + 'ӱ' => 'Ӱ', + 'ӳ' => 'Ӳ', + 'ӵ' => 'Ӵ', + 'ӷ' => 'Ӷ', + 'ӹ' => 'Ӹ', + 'ӻ' => 'Ӻ', + 'ӽ' => 'Ӽ', + 'ӿ' => 'Ӿ', + 'ԁ' => 'Ԁ', + 'ԃ' => 'Ԃ', + 'ԅ' => 'Ԅ', + 'ԇ' => 'Ԇ', + 'ԉ' => 'Ԉ', + 'ԋ' => 'Ԋ', + 'ԍ' => 'Ԍ', + 'ԏ' => 'Ԏ', + 'ԑ' => 'Ԑ', + 'ԓ' => 'Ԓ', + 'ԕ' => 'Ԕ', + 'ԗ' => 'Ԗ', + 'ԙ' => 'Ԙ', + 'ԛ' => 'Ԛ', + 'ԝ' => 'Ԝ', + 'ԟ' => 'Ԟ', + 'ԡ' => 'Ԡ', + 'ԣ' => 'Ԣ', + 'ԥ' => 'Ԥ', + 'ԧ' => 'Ԧ', + 'ԩ' => 'Ԩ', + 'ԫ' => 'Ԫ', + 'ԭ' => 'Ԭ', + 'ԯ' => 'Ԯ', + 'ա' => 'Ա', + 'բ' => 'Բ', + 'գ' => 'Գ', + 'դ' => 'Դ', + 'ե' => 'Ե', + 'զ' => 'Զ', + 'է' => 'Է', + 'ը' => 'Ը', + 'թ' => 'Թ', + 'ժ' => 'Ժ', + 'ի' => 'Ի', + 'լ' => 'Լ', + 'խ' => 'Խ', + 'ծ' => 'Ծ', + 'կ' => 'Կ', + 'հ' => 'Հ', + 'ձ' => 'Ձ', + 'ղ' => 'Ղ', + 'ճ' => 'Ճ', + 'մ' => 'Մ', + 'յ' => 'Յ', + 'ն' => 'Ն', + 'շ' => 'Շ', + 'ո' => 'Ո', + 'չ' => 'Չ', + 'պ' => 'Պ', + 'ջ' => 'Ջ', + 'ռ' => 'Ռ', + 'ս' => 'Ս', + 'վ' => 'Վ', + 'տ' => 'Տ', + 'ր' => 'Ր', + 'ց' => 'Ց', + 'ւ' => 'Ւ', + 'փ' => 'Փ', + 'ք' => 'Ք', + 'օ' => 'Օ', + 'ֆ' => 'Ֆ', + 'ა' => 'Ა', + 'ბ' => 'Ბ', + 'გ' => 'Გ', + 'დ' => 'Დ', + 'ე' => 'Ე', + 'ვ' => 'Ვ', + 'ზ' => 'Ზ', + 'თ' => 'Თ', + 'ი' => 'Ი', + 'კ' => 'Კ', + 'ლ' => 'Ლ', + 'მ' => 'Მ', + 'ნ' => 'Ნ', + 'ო' => 'Ო', + 'პ' => 'Პ', + 'ჟ' => 'Ჟ', + 'რ' => 'Რ', + 'ს' => 'Ს', + 'ტ' => 'Ტ', + 'უ' => 'Უ', + 'ფ' => 'Ფ', + 'ქ' => 'Ქ', + 'ღ' => 'Ღ', + 'ყ' => 'Ყ', + 'შ' => 'Შ', + 'ჩ' => 'Ჩ', + 'ც' => 'Ც', + 'ძ' => 'Ძ', + 'წ' => 'Წ', + 'ჭ' => 'Ჭ', + 'ხ' => 'Ხ', + 'ჯ' => 'Ჯ', + 'ჰ' => 'Ჰ', + 'ჱ' => 'Ჱ', + 'ჲ' => 'Ჲ', + 'ჳ' => 'Ჳ', + 'ჴ' => 'Ჴ', + 'ჵ' => 'Ჵ', + 'ჶ' => 'Ჶ', + 'ჷ' => 'Ჷ', + 'ჸ' => 'Ჸ', + 'ჹ' => 'Ჹ', + 'ჺ' => 'Ჺ', + 'ჽ' => 'Ჽ', + 'ჾ' => 'Ჾ', + 'ჿ' => 'Ჿ', + 'ᏸ' => 'Ᏸ', + 'ᏹ' => 'Ᏹ', + 'ᏺ' => 'Ᏺ', + 'ᏻ' => 'Ᏻ', + 'ᏼ' => 'Ᏼ', + 'ᏽ' => 'Ᏽ', + 'ᲀ' => 'В', + 'ᲁ' => 'Д', + 'ᲂ' => 'О', + 'ᲃ' => 'С', + 'ᲄ' => 'Т', + 'ᲅ' => 'Т', + 'ᲆ' => 'Ъ', + 'ᲇ' => 'Ѣ', + 'ᲈ' => 'Ꙋ', + 'ᵹ' => 'Ᵹ', + 'ᵽ' => 'Ᵽ', + 'ᶎ' => 'Ᶎ', + 'ḁ' => 'Ḁ', + 'ḃ' => 'Ḃ', + 'ḅ' => 'Ḅ', + 'ḇ' => 'Ḇ', + 'ḉ' => 'Ḉ', + 'ḋ' => 'Ḋ', + 'ḍ' => 'Ḍ', + 'ḏ' => 'Ḏ', + 'ḑ' => 'Ḑ', + 'ḓ' => 'Ḓ', + 'ḕ' => 'Ḕ', + 'ḗ' => 'Ḗ', + 'ḙ' => 'Ḙ', + 'ḛ' => 'Ḛ', + 'ḝ' => 'Ḝ', + 'ḟ' => 'Ḟ', + 'ḡ' => 'Ḡ', + 'ḣ' => 'Ḣ', + 'ḥ' => 'Ḥ', + 'ḧ' => 'Ḧ', + 'ḩ' => 'Ḩ', + 'ḫ' => 'Ḫ', + 'ḭ' => 'Ḭ', + 'ḯ' => 'Ḯ', + 'ḱ' => 'Ḱ', + 'ḳ' => 'Ḳ', + 'ḵ' => 'Ḵ', + 'ḷ' => 'Ḷ', + 'ḹ' => 'Ḹ', + 'ḻ' => 'Ḻ', + 'ḽ' => 'Ḽ', + 'ḿ' => 'Ḿ', + 'ṁ' => 'Ṁ', + 'ṃ' => 'Ṃ', + 'ṅ' => 'Ṅ', + 'ṇ' => 'Ṇ', + 'ṉ' => 'Ṉ', + 'ṋ' => 'Ṋ', + 'ṍ' => 'Ṍ', + 'ṏ' => 'Ṏ', + 'ṑ' => 'Ṑ', + 'ṓ' => 'Ṓ', + 'ṕ' => 'Ṕ', + 'ṗ' => 'Ṗ', + 'ṙ' => 'Ṙ', + 'ṛ' => 'Ṛ', + 'ṝ' => 'Ṝ', + 'ṟ' => 'Ṟ', + 'ṡ' => 'Ṡ', + 'ṣ' => 'Ṣ', + 'ṥ' => 'Ṥ', + 'ṧ' => 'Ṧ', + 'ṩ' => 'Ṩ', + 'ṫ' => 'Ṫ', + 'ṭ' => 'Ṭ', + 'ṯ' => 'Ṯ', + 'ṱ' => 'Ṱ', + 'ṳ' => 'Ṳ', + 'ṵ' => 'Ṵ', + 'ṷ' => 'Ṷ', + 'ṹ' => 'Ṹ', + 'ṻ' => 'Ṻ', + 'ṽ' => 'Ṽ', + 'ṿ' => 'Ṿ', + 'ẁ' => 'Ẁ', + 'ẃ' => 'Ẃ', + 'ẅ' => 'Ẅ', + 'ẇ' => 'Ẇ', + 'ẉ' => 'Ẉ', + 'ẋ' => 'Ẋ', + 'ẍ' => 'Ẍ', + 'ẏ' => 'Ẏ', + 'ẑ' => 'Ẑ', + 'ẓ' => 'Ẓ', + 'ẕ' => 'Ẕ', + 'ẛ' => 'Ṡ', + 'ạ' => 'Ạ', + 'ả' => 'Ả', + 'ấ' => 'Ấ', + 'ầ' => 'Ầ', + 'ẩ' => 'Ẩ', + 'ẫ' => 'Ẫ', + 'ậ' => 'Ậ', + 'ắ' => 'Ắ', + 'ằ' => 'Ằ', + 'ẳ' => 'Ẳ', + 'ẵ' => 'Ẵ', + 'ặ' => 'Ặ', + 'ẹ' => 'Ẹ', + 'ẻ' => 'Ẻ', + 'ẽ' => 'Ẽ', + 'ế' => 'Ế', + 'ề' => 'Ề', + 'ể' => 'Ể', + 'ễ' => 'Ễ', + 'ệ' => 'Ệ', + 'ỉ' => 'Ỉ', + 'ị' => 'Ị', + 'ọ' => 'Ọ', + 'ỏ' => 'Ỏ', + 'ố' => 'Ố', + 'ồ' => 'Ồ', + 'ổ' => 'Ổ', + 'ỗ' => 'Ỗ', + 'ộ' => 'Ộ', + 'ớ' => 'Ớ', + 'ờ' => 'Ờ', + 'ở' => 'Ở', + 'ỡ' => 'Ỡ', + 'ợ' => 'Ợ', + 'ụ' => 'Ụ', + 'ủ' => 'Ủ', + 'ứ' => 'Ứ', + 'ừ' => 'Ừ', + 'ử' => 'Ử', + 'ữ' => 'Ữ', + 'ự' => 'Ự', + 'ỳ' => 'Ỳ', + 'ỵ' => 'Ỵ', + 'ỷ' => 'Ỷ', + 'ỹ' => 'Ỹ', + 'ỻ' => 'Ỻ', + 'ỽ' => 'Ỽ', + 'ỿ' => 'Ỿ', + 'ἀ' => 'Ἀ', + 'ἁ' => 'Ἁ', + 'ἂ' => 'Ἂ', + 'ἃ' => 'Ἃ', + 'ἄ' => 'Ἄ', + 'ἅ' => 'Ἅ', + 'ἆ' => 'Ἆ', + 'ἇ' => 'Ἇ', + 'ἐ' => 'Ἐ', + 'ἑ' => 'Ἑ', + 'ἒ' => 'Ἒ', + 'ἓ' => 'Ἓ', + 'ἔ' => 'Ἔ', + 'ἕ' => 'Ἕ', + 'ἠ' => 'Ἠ', + 'ἡ' => 'Ἡ', + 'ἢ' => 'Ἢ', + 'ἣ' => 'Ἣ', + 'ἤ' => 'Ἤ', + 'ἥ' => 'Ἥ', + 'ἦ' => 'Ἦ', + 'ἧ' => 'Ἧ', + 'ἰ' => 'Ἰ', + 'ἱ' => 'Ἱ', + 'ἲ' => 'Ἲ', + 'ἳ' => 'Ἳ', + 'ἴ' => 'Ἴ', + 'ἵ' => 'Ἵ', + 'ἶ' => 'Ἶ', + 'ἷ' => 'Ἷ', + 'ὀ' => 'Ὀ', + 'ὁ' => 'Ὁ', + 'ὂ' => 'Ὂ', + 'ὃ' => 'Ὃ', + 'ὄ' => 'Ὄ', + 'ὅ' => 'Ὅ', + 'ὑ' => 'Ὑ', + 'ὓ' => 'Ὓ', + 'ὕ' => 'Ὕ', + 'ὗ' => 'Ὗ', + 'ὠ' => 'Ὠ', + 'ὡ' => 'Ὡ', + 'ὢ' => 'Ὢ', + 'ὣ' => 'Ὣ', + 'ὤ' => 'Ὤ', + 'ὥ' => 'Ὥ', + 'ὦ' => 'Ὦ', + 'ὧ' => 'Ὧ', + 'ὰ' => 'Ὰ', + 'ά' => 'Ά', + 'ὲ' => 'Ὲ', + 'έ' => 'Έ', + 'ὴ' => 'Ὴ', + 'ή' => 'Ή', + 'ὶ' => 'Ὶ', + 'ί' => 'Ί', + 'ὸ' => 'Ὸ', + 'ό' => 'Ό', + 'ὺ' => 'Ὺ', + 'ύ' => 'Ύ', + 'ὼ' => 'Ὼ', + 'ώ' => 'Ώ', + 'ᾀ' => 'ἈΙ', + 'ᾁ' => 'ἉΙ', + 'ᾂ' => 'ἊΙ', + 'ᾃ' => 'ἋΙ', + 'ᾄ' => 'ἌΙ', + 'ᾅ' => 'ἍΙ', + 'ᾆ' => 'ἎΙ', + 'ᾇ' => 'ἏΙ', + 'ᾐ' => 'ἨΙ', + 'ᾑ' => 'ἩΙ', + 'ᾒ' => 'ἪΙ', + 'ᾓ' => 'ἫΙ', + 'ᾔ' => 'ἬΙ', + 'ᾕ' => 'ἭΙ', + 'ᾖ' => 'ἮΙ', + 'ᾗ' => 'ἯΙ', + 'ᾠ' => 'ὨΙ', + 'ᾡ' => 'ὩΙ', + 'ᾢ' => 'ὪΙ', + 'ᾣ' => 'ὫΙ', + 'ᾤ' => 'ὬΙ', + 'ᾥ' => 'ὭΙ', + 'ᾦ' => 'ὮΙ', + 'ᾧ' => 'ὯΙ', + 'ᾰ' => 'Ᾰ', + 'ᾱ' => 'Ᾱ', + 'ᾳ' => 'ΑΙ', + 'ι' => 'Ι', + 'ῃ' => 'ΗΙ', + 'ῐ' => 'Ῐ', + 'ῑ' => 'Ῑ', + 'ῠ' => 'Ῠ', + 'ῡ' => 'Ῡ', + 'ῥ' => 'Ῥ', + 'ῳ' => 'ΩΙ', + 'ⅎ' => 'Ⅎ', + 'ⅰ' => 'Ⅰ', + 'ⅱ' => 'Ⅱ', + 'ⅲ' => 'Ⅲ', + 'ⅳ' => 'Ⅳ', + 'ⅴ' => 'Ⅴ', + 'ⅵ' => 'Ⅵ', + 'ⅶ' => 'Ⅶ', + 'ⅷ' => 'Ⅷ', + 'ⅸ' => 'Ⅸ', + 'ⅹ' => 'Ⅹ', + 'ⅺ' => 'Ⅺ', + 'ⅻ' => 'Ⅻ', + 'ⅼ' => 'Ⅼ', + 'ⅽ' => 'Ⅽ', + 'ⅾ' => 'Ⅾ', + 'ⅿ' => 'Ⅿ', + 'ↄ' => 'Ↄ', + 'ⓐ' => 'Ⓐ', + 'ⓑ' => 'Ⓑ', + 'ⓒ' => 'Ⓒ', + 'ⓓ' => 'Ⓓ', + 'ⓔ' => 'Ⓔ', + 'ⓕ' => 'Ⓕ', + 'ⓖ' => 'Ⓖ', + 'ⓗ' => 'Ⓗ', + 'ⓘ' => 'Ⓘ', + 'ⓙ' => 'Ⓙ', + 'ⓚ' => 'Ⓚ', + 'ⓛ' => 'Ⓛ', + 'ⓜ' => 'Ⓜ', + 'ⓝ' => 'Ⓝ', + 'ⓞ' => 'Ⓞ', + 'ⓟ' => 'Ⓟ', + 'ⓠ' => 'Ⓠ', + 'ⓡ' => 'Ⓡ', + 'ⓢ' => 'Ⓢ', + 'ⓣ' => 'Ⓣ', + 'ⓤ' => 'Ⓤ', + 'ⓥ' => 'Ⓥ', + 'ⓦ' => 'Ⓦ', + 'ⓧ' => 'Ⓧ', + 'ⓨ' => 'Ⓨ', + 'ⓩ' => 'Ⓩ', + 'ⰰ' => 'Ⰰ', + 'ⰱ' => 'Ⰱ', + 'ⰲ' => 'Ⰲ', + 'ⰳ' => 'Ⰳ', + 'ⰴ' => 'Ⰴ', + 'ⰵ' => 'Ⰵ', + 'ⰶ' => 'Ⰶ', + 'ⰷ' => 'Ⰷ', + 'ⰸ' => 'Ⰸ', + 'ⰹ' => 'Ⰹ', + 'ⰺ' => 'Ⰺ', + 'ⰻ' => 'Ⰻ', + 'ⰼ' => 'Ⰼ', + 'ⰽ' => 'Ⰽ', + 'ⰾ' => 'Ⰾ', + 'ⰿ' => 'Ⰿ', + 'ⱀ' => 'Ⱀ', + 'ⱁ' => 'Ⱁ', + 'ⱂ' => 'Ⱂ', + 'ⱃ' => 'Ⱃ', + 'ⱄ' => 'Ⱄ', + 'ⱅ' => 'Ⱅ', + 'ⱆ' => 'Ⱆ', + 'ⱇ' => 'Ⱇ', + 'ⱈ' => 'Ⱈ', + 'ⱉ' => 'Ⱉ', + 'ⱊ' => 'Ⱊ', + 'ⱋ' => 'Ⱋ', + 'ⱌ' => 'Ⱌ', + 'ⱍ' => 'Ⱍ', + 'ⱎ' => 'Ⱎ', + 'ⱏ' => 'Ⱏ', + 'ⱐ' => 'Ⱐ', + 'ⱑ' => 'Ⱑ', + 'ⱒ' => 'Ⱒ', + 'ⱓ' => 'Ⱓ', + 'ⱔ' => 'Ⱔ', + 'ⱕ' => 'Ⱕ', + 'ⱖ' => 'Ⱖ', + 'ⱗ' => 'Ⱗ', + 'ⱘ' => 'Ⱘ', + 'ⱙ' => 'Ⱙ', + 'ⱚ' => 'Ⱚ', + 'ⱛ' => 'Ⱛ', + 'ⱜ' => 'Ⱜ', + 'ⱝ' => 'Ⱝ', + 'ⱞ' => 'Ⱞ', + 'ⱡ' => 'Ⱡ', + 'ⱥ' => 'Ⱥ', + 'ⱦ' => 'Ⱦ', + 'ⱨ' => 'Ⱨ', + 'ⱪ' => 'Ⱪ', + 'ⱬ' => 'Ⱬ', + 'ⱳ' => 'Ⱳ', + 'ⱶ' => 'Ⱶ', + 'ⲁ' => 'Ⲁ', + 'ⲃ' => 'Ⲃ', + 'ⲅ' => 'Ⲅ', + 'ⲇ' => 'Ⲇ', + 'ⲉ' => 'Ⲉ', + 'ⲋ' => 'Ⲋ', + 'ⲍ' => 'Ⲍ', + 'ⲏ' => 'Ⲏ', + 'ⲑ' => 'Ⲑ', + 'ⲓ' => 'Ⲓ', + 'ⲕ' => 'Ⲕ', + 'ⲗ' => 'Ⲗ', + 'ⲙ' => 'Ⲙ', + 'ⲛ' => 'Ⲛ', + 'ⲝ' => 'Ⲝ', + 'ⲟ' => 'Ⲟ', + 'ⲡ' => 'Ⲡ', + 'ⲣ' => 'Ⲣ', + 'ⲥ' => 'Ⲥ', + 'ⲧ' => 'Ⲧ', + 'ⲩ' => 'Ⲩ', + 'ⲫ' => 'Ⲫ', + 'ⲭ' => 'Ⲭ', + 'ⲯ' => 'Ⲯ', + 'ⲱ' => 'Ⲱ', + 'ⲳ' => 'Ⲳ', + 'ⲵ' => 'Ⲵ', + 'ⲷ' => 'Ⲷ', + 'ⲹ' => 'Ⲹ', + 'ⲻ' => 'Ⲻ', + 'ⲽ' => 'Ⲽ', + 'ⲿ' => 'Ⲿ', + 'ⳁ' => 'Ⳁ', + 'ⳃ' => 'Ⳃ', + 'ⳅ' => 'Ⳅ', + 'ⳇ' => 'Ⳇ', + 'ⳉ' => 'Ⳉ', + 'ⳋ' => 'Ⳋ', + 'ⳍ' => 'Ⳍ', + 'ⳏ' => 'Ⳏ', + 'ⳑ' => 'Ⳑ', + 'ⳓ' => 'Ⳓ', + 'ⳕ' => 'Ⳕ', + 'ⳗ' => 'Ⳗ', + 'ⳙ' => 'Ⳙ', + 'ⳛ' => 'Ⳛ', + 'ⳝ' => 'Ⳝ', + 'ⳟ' => 'Ⳟ', + 'ⳡ' => 'Ⳡ', + 'ⳣ' => 'Ⳣ', + 'ⳬ' => 'Ⳬ', + 'ⳮ' => 'Ⳮ', + 'ⳳ' => 'Ⳳ', + 'ⴀ' => 'Ⴀ', + 'ⴁ' => 'Ⴁ', + 'ⴂ' => 'Ⴂ', + 'ⴃ' => 'Ⴃ', + 'ⴄ' => 'Ⴄ', + 'ⴅ' => 'Ⴅ', + 'ⴆ' => 'Ⴆ', + 'ⴇ' => 'Ⴇ', + 'ⴈ' => 'Ⴈ', + 'ⴉ' => 'Ⴉ', + 'ⴊ' => 'Ⴊ', + 'ⴋ' => 'Ⴋ', + 'ⴌ' => 'Ⴌ', + 'ⴍ' => 'Ⴍ', + 'ⴎ' => 'Ⴎ', + 'ⴏ' => 'Ⴏ', + 'ⴐ' => 'Ⴐ', + 'ⴑ' => 'Ⴑ', + 'ⴒ' => 'Ⴒ', + 'ⴓ' => 'Ⴓ', + 'ⴔ' => 'Ⴔ', + 'ⴕ' => 'Ⴕ', + 'ⴖ' => 'Ⴖ', + 'ⴗ' => 'Ⴗ', + 'ⴘ' => 'Ⴘ', + 'ⴙ' => 'Ⴙ', + 'ⴚ' => 'Ⴚ', + 'ⴛ' => 'Ⴛ', + 'ⴜ' => 'Ⴜ', + 'ⴝ' => 'Ⴝ', + 'ⴞ' => 'Ⴞ', + 'ⴟ' => 'Ⴟ', + 'ⴠ' => 'Ⴠ', + 'ⴡ' => 'Ⴡ', + 'ⴢ' => 'Ⴢ', + 'ⴣ' => 'Ⴣ', + 'ⴤ' => 'Ⴤ', + 'ⴥ' => 'Ⴥ', + 'ⴧ' => 'Ⴧ', + 'ⴭ' => 'Ⴭ', + 'ꙁ' => 'Ꙁ', + 'ꙃ' => 'Ꙃ', + 'ꙅ' => 'Ꙅ', + 'ꙇ' => 'Ꙇ', + 'ꙉ' => 'Ꙉ', + 'ꙋ' => 'Ꙋ', + 'ꙍ' => 'Ꙍ', + 'ꙏ' => 'Ꙏ', + 'ꙑ' => 'Ꙑ', + 'ꙓ' => 'Ꙓ', + 'ꙕ' => 'Ꙕ', + 'ꙗ' => 'Ꙗ', + 'ꙙ' => 'Ꙙ', + 'ꙛ' => 'Ꙛ', + 'ꙝ' => 'Ꙝ', + 'ꙟ' => 'Ꙟ', + 'ꙡ' => 'Ꙡ', + 'ꙣ' => 'Ꙣ', + 'ꙥ' => 'Ꙥ', + 'ꙧ' => 'Ꙧ', + 'ꙩ' => 'Ꙩ', + 'ꙫ' => 'Ꙫ', + 'ꙭ' => 'Ꙭ', + 'ꚁ' => 'Ꚁ', + 'ꚃ' => 'Ꚃ', + 'ꚅ' => 'Ꚅ', + 'ꚇ' => 'Ꚇ', + 'ꚉ' => 'Ꚉ', + 'ꚋ' => 'Ꚋ', + 'ꚍ' => 'Ꚍ', + 'ꚏ' => 'Ꚏ', + 'ꚑ' => 'Ꚑ', + 'ꚓ' => 'Ꚓ', + 'ꚕ' => 'Ꚕ', + 'ꚗ' => 'Ꚗ', + 'ꚙ' => 'Ꚙ', + 'ꚛ' => 'Ꚛ', + 'ꜣ' => 'Ꜣ', + 'ꜥ' => 'Ꜥ', + 'ꜧ' => 'Ꜧ', + 'ꜩ' => 'Ꜩ', + 'ꜫ' => 'Ꜫ', + 'ꜭ' => 'Ꜭ', + 'ꜯ' => 'Ꜯ', + 'ꜳ' => 'Ꜳ', + 'ꜵ' => 'Ꜵ', + 'ꜷ' => 'Ꜷ', + 'ꜹ' => 'Ꜹ', + 'ꜻ' => 'Ꜻ', + 'ꜽ' => 'Ꜽ', + 'ꜿ' => 'Ꜿ', + 'ꝁ' => 'Ꝁ', + 'ꝃ' => 'Ꝃ', + 'ꝅ' => 'Ꝅ', + 'ꝇ' => 'Ꝇ', + 'ꝉ' => 'Ꝉ', + 'ꝋ' => 'Ꝋ', + 'ꝍ' => 'Ꝍ', + 'ꝏ' => 'Ꝏ', + 'ꝑ' => 'Ꝑ', + 'ꝓ' => 'Ꝓ', + 'ꝕ' => 'Ꝕ', + 'ꝗ' => 'Ꝗ', + 'ꝙ' => 'Ꝙ', + 'ꝛ' => 'Ꝛ', + 'ꝝ' => 'Ꝝ', + 'ꝟ' => 'Ꝟ', + 'ꝡ' => 'Ꝡ', + 'ꝣ' => 'Ꝣ', + 'ꝥ' => 'Ꝥ', + 'ꝧ' => 'Ꝧ', + 'ꝩ' => 'Ꝩ', + 'ꝫ' => 'Ꝫ', + 'ꝭ' => 'Ꝭ', + 'ꝯ' => 'Ꝯ', + 'ꝺ' => 'Ꝺ', + 'ꝼ' => 'Ꝼ', + 'ꝿ' => 'Ꝿ', + 'ꞁ' => 'Ꞁ', + 'ꞃ' => 'Ꞃ', + 'ꞅ' => 'Ꞅ', + 'ꞇ' => 'Ꞇ', + 'ꞌ' => 'Ꞌ', + 'ꞑ' => 'Ꞑ', + 'ꞓ' => 'Ꞓ', + 'ꞔ' => 'Ꞔ', + 'ꞗ' => 'Ꞗ', + 'ꞙ' => 'Ꞙ', + 'ꞛ' => 'Ꞛ', + 'ꞝ' => 'Ꞝ', + 'ꞟ' => 'Ꞟ', + 'ꞡ' => 'Ꞡ', + 'ꞣ' => 'Ꞣ', + 'ꞥ' => 'Ꞥ', + 'ꞧ' => 'Ꞧ', + 'ꞩ' => 'Ꞩ', + 'ꞵ' => 'Ꞵ', + 'ꞷ' => 'Ꞷ', + 'ꞹ' => 'Ꞹ', + 'ꞻ' => 'Ꞻ', + 'ꞽ' => 'Ꞽ', + 'ꞿ' => 'Ꞿ', + 'ꟃ' => 'Ꟃ', + 'ꟈ' => 'Ꟈ', + 'ꟊ' => 'Ꟊ', + 'ꟶ' => 'Ꟶ', + 'ꭓ' => 'Ꭓ', + 'ꭰ' => 'Ꭰ', + 'ꭱ' => 'Ꭱ', + 'ꭲ' => 'Ꭲ', + 'ꭳ' => 'Ꭳ', + 'ꭴ' => 'Ꭴ', + 'ꭵ' => 'Ꭵ', + 'ꭶ' => 'Ꭶ', + 'ꭷ' => 'Ꭷ', + 'ꭸ' => 'Ꭸ', + 'ꭹ' => 'Ꭹ', + 'ꭺ' => 'Ꭺ', + 'ꭻ' => 'Ꭻ', + 'ꭼ' => 'Ꭼ', + 'ꭽ' => 'Ꭽ', + 'ꭾ' => 'Ꭾ', + 'ꭿ' => 'Ꭿ', + 'ꮀ' => 'Ꮀ', + 'ꮁ' => 'Ꮁ', + 'ꮂ' => 'Ꮂ', + 'ꮃ' => 'Ꮃ', + 'ꮄ' => 'Ꮄ', + 'ꮅ' => 'Ꮅ', + 'ꮆ' => 'Ꮆ', + 'ꮇ' => 'Ꮇ', + 'ꮈ' => 'Ꮈ', + 'ꮉ' => 'Ꮉ', + 'ꮊ' => 'Ꮊ', + 'ꮋ' => 'Ꮋ', + 'ꮌ' => 'Ꮌ', + 'ꮍ' => 'Ꮍ', + 'ꮎ' => 'Ꮎ', + 'ꮏ' => 'Ꮏ', + 'ꮐ' => 'Ꮐ', + 'ꮑ' => 'Ꮑ', + 'ꮒ' => 'Ꮒ', + 'ꮓ' => 'Ꮓ', + 'ꮔ' => 'Ꮔ', + 'ꮕ' => 'Ꮕ', + 'ꮖ' => 'Ꮖ', + 'ꮗ' => 'Ꮗ', + 'ꮘ' => 'Ꮘ', + 'ꮙ' => 'Ꮙ', + 'ꮚ' => 'Ꮚ', + 'ꮛ' => 'Ꮛ', + 'ꮜ' => 'Ꮜ', + 'ꮝ' => 'Ꮝ', + 'ꮞ' => 'Ꮞ', + 'ꮟ' => 'Ꮟ', + 'ꮠ' => 'Ꮠ', + 'ꮡ' => 'Ꮡ', + 'ꮢ' => 'Ꮢ', + 'ꮣ' => 'Ꮣ', + 'ꮤ' => 'Ꮤ', + 'ꮥ' => 'Ꮥ', + 'ꮦ' => 'Ꮦ', + 'ꮧ' => 'Ꮧ', + 'ꮨ' => 'Ꮨ', + 'ꮩ' => 'Ꮩ', + 'ꮪ' => 'Ꮪ', + 'ꮫ' => 'Ꮫ', + 'ꮬ' => 'Ꮬ', + 'ꮭ' => 'Ꮭ', + 'ꮮ' => 'Ꮮ', + 'ꮯ' => 'Ꮯ', + 'ꮰ' => 'Ꮰ', + 'ꮱ' => 'Ꮱ', + 'ꮲ' => 'Ꮲ', + 'ꮳ' => 'Ꮳ', + 'ꮴ' => 'Ꮴ', + 'ꮵ' => 'Ꮵ', + 'ꮶ' => 'Ꮶ', + 'ꮷ' => 'Ꮷ', + 'ꮸ' => 'Ꮸ', + 'ꮹ' => 'Ꮹ', + 'ꮺ' => 'Ꮺ', + 'ꮻ' => 'Ꮻ', + 'ꮼ' => 'Ꮼ', + 'ꮽ' => 'Ꮽ', + 'ꮾ' => 'Ꮾ', + 'ꮿ' => 'Ꮿ', + 'a' => 'A', + 'b' => 'B', + 'c' => 'C', + 'd' => 'D', + 'e' => 'E', + 'f' => 'F', + 'g' => 'G', + 'h' => 'H', + 'i' => 'I', + 'j' => 'J', + 'k' => 'K', + 'l' => 'L', + 'm' => 'M', + 'n' => 'N', + 'o' => 'O', + 'p' => 'P', + 'q' => 'Q', + 'r' => 'R', + 's' => 'S', + 't' => 'T', + 'u' => 'U', + 'v' => 'V', + 'w' => 'W', + 'x' => 'X', + 'y' => 'Y', + 'z' => 'Z', + '𐐨' => '𐐀', + '𐐩' => '𐐁', + '𐐪' => '𐐂', + '𐐫' => '𐐃', + '𐐬' => '𐐄', + '𐐭' => '𐐅', + '𐐮' => '𐐆', + '𐐯' => '𐐇', + '𐐰' => '𐐈', + '𐐱' => '𐐉', + '𐐲' => '𐐊', + '𐐳' => '𐐋', + '𐐴' => '𐐌', + '𐐵' => '𐐍', + '𐐶' => '𐐎', + '𐐷' => '𐐏', + '𐐸' => '𐐐', + '𐐹' => '𐐑', + '𐐺' => '𐐒', + '𐐻' => '𐐓', + '𐐼' => '𐐔', + '𐐽' => '𐐕', + '𐐾' => '𐐖', + '𐐿' => '𐐗', + '𐑀' => '𐐘', + '𐑁' => '𐐙', + '𐑂' => '𐐚', + '𐑃' => '𐐛', + '𐑄' => '𐐜', + '𐑅' => '𐐝', + '𐑆' => '𐐞', + '𐑇' => '𐐟', + '𐑈' => '𐐠', + '𐑉' => '𐐡', + '𐑊' => '𐐢', + '𐑋' => '𐐣', + '𐑌' => '𐐤', + '𐑍' => '𐐥', + '𐑎' => '𐐦', + '𐑏' => '𐐧', + '𐓘' => '𐒰', + '𐓙' => '𐒱', + '𐓚' => '𐒲', + '𐓛' => '𐒳', + '𐓜' => '𐒴', + '𐓝' => '𐒵', + '𐓞' => '𐒶', + '𐓟' => '𐒷', + '𐓠' => '𐒸', + '𐓡' => '𐒹', + '𐓢' => '𐒺', + '𐓣' => '𐒻', + '𐓤' => '𐒼', + '𐓥' => '𐒽', + '𐓦' => '𐒾', + '𐓧' => '𐒿', + '𐓨' => '𐓀', + '𐓩' => '𐓁', + '𐓪' => '𐓂', + '𐓫' => '𐓃', + '𐓬' => '𐓄', + '𐓭' => '𐓅', + '𐓮' => '𐓆', + '𐓯' => '𐓇', + '𐓰' => '𐓈', + '𐓱' => '𐓉', + '𐓲' => '𐓊', + '𐓳' => '𐓋', + '𐓴' => '𐓌', + '𐓵' => '𐓍', + '𐓶' => '𐓎', + '𐓷' => '𐓏', + '𐓸' => '𐓐', + '𐓹' => '𐓑', + '𐓺' => '𐓒', + '𐓻' => '𐓓', + '𐳀' => '𐲀', + '𐳁' => '𐲁', + '𐳂' => '𐲂', + '𐳃' => '𐲃', + '𐳄' => '𐲄', + '𐳅' => '𐲅', + '𐳆' => '𐲆', + '𐳇' => '𐲇', + '𐳈' => '𐲈', + '𐳉' => '𐲉', + '𐳊' => '𐲊', + '𐳋' => '𐲋', + '𐳌' => '𐲌', + '𐳍' => '𐲍', + '𐳎' => '𐲎', + '𐳏' => '𐲏', + '𐳐' => '𐲐', + '𐳑' => '𐲑', + '𐳒' => '𐲒', + '𐳓' => '𐲓', + '𐳔' => '𐲔', + '𐳕' => '𐲕', + '𐳖' => '𐲖', + '𐳗' => '𐲗', + '𐳘' => '𐲘', + '𐳙' => '𐲙', + '𐳚' => '𐲚', + '𐳛' => '𐲛', + '𐳜' => '𐲜', + '𐳝' => '𐲝', + '𐳞' => '𐲞', + '𐳟' => '𐲟', + '𐳠' => '𐲠', + '𐳡' => '𐲡', + '𐳢' => '𐲢', + '𐳣' => '𐲣', + '𐳤' => '𐲤', + '𐳥' => '𐲥', + '𐳦' => '𐲦', + '𐳧' => '𐲧', + '𐳨' => '𐲨', + '𐳩' => '𐲩', + '𐳪' => '𐲪', + '𐳫' => '𐲫', + '𐳬' => '𐲬', + '𐳭' => '𐲭', + '𐳮' => '𐲮', + '𐳯' => '𐲯', + '𐳰' => '𐲰', + '𐳱' => '𐲱', + '𐳲' => '𐲲', + '𑣀' => '𑢠', + '𑣁' => '𑢡', + '𑣂' => '𑢢', + '𑣃' => '𑢣', + '𑣄' => '𑢤', + '𑣅' => '𑢥', + '𑣆' => '𑢦', + '𑣇' => '𑢧', + '𑣈' => '𑢨', + '𑣉' => '𑢩', + '𑣊' => '𑢪', + '𑣋' => '𑢫', + '𑣌' => '𑢬', + '𑣍' => '𑢭', + '𑣎' => '𑢮', + '𑣏' => '𑢯', + '𑣐' => '𑢰', + '𑣑' => '𑢱', + '𑣒' => '𑢲', + '𑣓' => '𑢳', + '𑣔' => '𑢴', + '𑣕' => '𑢵', + '𑣖' => '𑢶', + '𑣗' => '𑢷', + '𑣘' => '𑢸', + '𑣙' => '𑢹', + '𑣚' => '𑢺', + '𑣛' => '𑢻', + '𑣜' => '𑢼', + '𑣝' => '𑢽', + '𑣞' => '𑢾', + '𑣟' => '𑢿', + '𖹠' => '𖹀', + '𖹡' => '𖹁', + '𖹢' => '𖹂', + '𖹣' => '𖹃', + '𖹤' => '𖹄', + '𖹥' => '𖹅', + '𖹦' => '𖹆', + '𖹧' => '𖹇', + '𖹨' => '𖹈', + '𖹩' => '𖹉', + '𖹪' => '𖹊', + '𖹫' => '𖹋', + '𖹬' => '𖹌', + '𖹭' => '𖹍', + '𖹮' => '𖹎', + '𖹯' => '𖹏', + '𖹰' => '𖹐', + '𖹱' => '𖹑', + '𖹲' => '𖹒', + '𖹳' => '𖹓', + '𖹴' => '𖹔', + '𖹵' => '𖹕', + '𖹶' => '𖹖', + '𖹷' => '𖹗', + '𖹸' => '𖹘', + '𖹹' => '𖹙', + '𖹺' => '𖹚', + '𖹻' => '𖹛', + '𖹼' => '𖹜', + '𖹽' => '𖹝', + '𖹾' => '𖹞', + '𖹿' => '𖹟', + '𞤢' => '𞤀', + '𞤣' => '𞤁', + '𞤤' => '𞤂', + '𞤥' => '𞤃', + '𞤦' => '𞤄', + '𞤧' => '𞤅', + '𞤨' => '𞤆', + '𞤩' => '𞤇', + '𞤪' => '𞤈', + '𞤫' => '𞤉', + '𞤬' => '𞤊', + '𞤭' => '𞤋', + '𞤮' => '𞤌', + '𞤯' => '𞤍', + '𞤰' => '𞤎', + '𞤱' => '𞤏', + '𞤲' => '𞤐', + '𞤳' => '𞤑', + '𞤴' => '𞤒', + '𞤵' => '𞤓', + '𞤶' => '𞤔', + '𞤷' => '𞤕', + '𞤸' => '𞤖', + '𞤹' => '𞤗', + '𞤺' => '𞤘', + '𞤻' => '𞤙', + '𞤼' => '𞤚', + '𞤽' => '𞤛', + '𞤾' => '𞤜', + '𞤿' => '𞤝', + '𞥀' => '𞤞', + '𞥁' => '𞤟', + '𞥂' => '𞤠', + '𞥃' => '𞤡', + 'ß' => 'SS', + 'ff' => 'FF', + 'fi' => 'FI', + 'fl' => 'FL', + 'ffi' => 'FFI', + 'ffl' => 'FFL', + 'ſt' => 'ST', + 'st' => 'ST', + 'և' => 'ԵՒ', + 'ﬓ' => 'ՄՆ', + 'ﬔ' => 'ՄԵ', + 'ﬕ' => 'ՄԻ', + 'ﬖ' => 'ՎՆ', + 'ﬗ' => 'ՄԽ', + 'ʼn' => 'ʼN', + 'ΐ' => 'Ϊ́', + 'ΰ' => 'Ϋ́', + 'ǰ' => 'J̌', + 'ẖ' => 'H̱', + 'ẗ' => 'T̈', + 'ẘ' => 'W̊', + 'ẙ' => 'Y̊', + 'ẚ' => 'Aʾ', + 'ὐ' => 'Υ̓', + 'ὒ' => 'Υ̓̀', + 'ὔ' => 'Υ̓́', + 'ὖ' => 'Υ̓͂', + 'ᾶ' => 'Α͂', + 'ῆ' => 'Η͂', + 'ῒ' => 'Ϊ̀', + 'ΐ' => 'Ϊ́', + 'ῖ' => 'Ι͂', + 'ῗ' => 'Ϊ͂', + 'ῢ' => 'Ϋ̀', + 'ΰ' => 'Ϋ́', + 'ῤ' => 'Ρ̓', + 'ῦ' => 'Υ͂', + 'ῧ' => 'Ϋ͂', + 'ῶ' => 'Ω͂', + 'ᾈ' => 'ἈΙ', + 'ᾉ' => 'ἉΙ', + 'ᾊ' => 'ἊΙ', + 'ᾋ' => 'ἋΙ', + 'ᾌ' => 'ἌΙ', + 'ᾍ' => 'ἍΙ', + 'ᾎ' => 'ἎΙ', + 'ᾏ' => 'ἏΙ', + 'ᾘ' => 'ἨΙ', + 'ᾙ' => 'ἩΙ', + 'ᾚ' => 'ἪΙ', + 'ᾛ' => 'ἫΙ', + 'ᾜ' => 'ἬΙ', + 'ᾝ' => 'ἭΙ', + 'ᾞ' => 'ἮΙ', + 'ᾟ' => 'ἯΙ', + 'ᾨ' => 'ὨΙ', + 'ᾩ' => 'ὩΙ', + 'ᾪ' => 'ὪΙ', + 'ᾫ' => 'ὫΙ', + 'ᾬ' => 'ὬΙ', + 'ᾭ' => 'ὭΙ', + 'ᾮ' => 'ὮΙ', + 'ᾯ' => 'ὯΙ', + 'ᾼ' => 'ΑΙ', + 'ῌ' => 'ΗΙ', + 'ῼ' => 'ΩΙ', + 'ᾲ' => 'ᾺΙ', + 'ᾴ' => 'ΆΙ', + 'ῂ' => 'ῊΙ', + 'ῄ' => 'ΉΙ', + 'ῲ' => 'ῺΙ', + 'ῴ' => 'ΏΙ', + 'ᾷ' => 'Α͂Ι', + 'ῇ' => 'Η͂Ι', + 'ῷ' => 'Ω͂Ι', +); diff --git a/vendor/symfony/polyfill-mbstring/bootstrap.php b/vendor/symfony/polyfill-mbstring/bootstrap.php new file mode 100644 index 0000000..1fedd1f --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/bootstrap.php @@ -0,0 +1,147 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Mbstring as p; + +if (\PHP_VERSION_ID >= 80000) { + return require __DIR__.'/bootstrap80.php'; +} + +if (!function_exists('mb_convert_encoding')) { + function mb_convert_encoding($string, $to_encoding, $from_encoding = null) { return p\Mbstring::mb_convert_encoding($string, $to_encoding, $from_encoding); } +} +if (!function_exists('mb_decode_mimeheader')) { + function mb_decode_mimeheader($string) { return p\Mbstring::mb_decode_mimeheader($string); } +} +if (!function_exists('mb_encode_mimeheader')) { + function mb_encode_mimeheader($string, $charset = null, $transfer_encoding = null, $newline = "\r\n", $indent = 0) { return p\Mbstring::mb_encode_mimeheader($string, $charset, $transfer_encoding, $newline, $indent); } +} +if (!function_exists('mb_decode_numericentity')) { + function mb_decode_numericentity($string, $map, $encoding = null) { return p\Mbstring::mb_decode_numericentity($string, $map, $encoding); } +} +if (!function_exists('mb_encode_numericentity')) { + function mb_encode_numericentity($string, $map, $encoding = null, $hex = false) { return p\Mbstring::mb_encode_numericentity($string, $map, $encoding, $hex); } +} +if (!function_exists('mb_convert_case')) { + function mb_convert_case($string, $mode, $encoding = null) { return p\Mbstring::mb_convert_case($string, $mode, $encoding); } +} +if (!function_exists('mb_internal_encoding')) { + function mb_internal_encoding($encoding = null) { return p\Mbstring::mb_internal_encoding($encoding); } +} +if (!function_exists('mb_language')) { + function mb_language($language = null) { return p\Mbstring::mb_language($language); } +} +if (!function_exists('mb_list_encodings')) { + function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); } +} +if (!function_exists('mb_encoding_aliases')) { + function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); } +} +if (!function_exists('mb_check_encoding')) { + function mb_check_encoding($value = null, $encoding = null) { return p\Mbstring::mb_check_encoding($value, $encoding); } +} +if (!function_exists('mb_detect_encoding')) { + function mb_detect_encoding($string, $encodings = null, $strict = false) { return p\Mbstring::mb_detect_encoding($string, $encodings, $strict); } +} +if (!function_exists('mb_detect_order')) { + function mb_detect_order($encoding = null) { return p\Mbstring::mb_detect_order($encoding); } +} +if (!function_exists('mb_parse_str')) { + function mb_parse_str($string, &$result = []) { parse_str($string, $result); return (bool) $result; } +} +if (!function_exists('mb_strlen')) { + function mb_strlen($string, $encoding = null) { return p\Mbstring::mb_strlen($string, $encoding); } +} +if (!function_exists('mb_strpos')) { + function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strpos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strtolower')) { + function mb_strtolower($string, $encoding = null) { return p\Mbstring::mb_strtolower($string, $encoding); } +} +if (!function_exists('mb_strtoupper')) { + function mb_strtoupper($string, $encoding = null) { return p\Mbstring::mb_strtoupper($string, $encoding); } +} +if (!function_exists('mb_substitute_character')) { + function mb_substitute_character($substitute_character = null) { return p\Mbstring::mb_substitute_character($substitute_character); } +} +if (!function_exists('mb_substr')) { + function mb_substr($string, $start, $length = 2147483647, $encoding = null) { return p\Mbstring::mb_substr($string, $start, $length, $encoding); } +} +if (!function_exists('mb_stripos')) { + function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_stripos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_stristr')) { + function mb_stristr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_stristr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strrchr')) { + function mb_strrchr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrchr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strrichr')) { + function mb_strrichr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrichr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strripos')) { + function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strripos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strrpos')) { + function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strrpos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strstr')) { + function mb_strstr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strstr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_get_info')) { + function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); } +} +if (!function_exists('mb_http_output')) { + function mb_http_output($encoding = null) { return p\Mbstring::mb_http_output($encoding); } +} +if (!function_exists('mb_strwidth')) { + function mb_strwidth($string, $encoding = null) { return p\Mbstring::mb_strwidth($string, $encoding); } +} +if (!function_exists('mb_substr_count')) { + function mb_substr_count($haystack, $needle, $encoding = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $encoding); } +} +if (!function_exists('mb_output_handler')) { + function mb_output_handler($string, $status) { return p\Mbstring::mb_output_handler($string, $status); } +} +if (!function_exists('mb_http_input')) { + function mb_http_input($type = null) { return p\Mbstring::mb_http_input($type); } +} + +if (!function_exists('mb_convert_variables')) { + function mb_convert_variables($to_encoding, $from_encoding, &...$vars) { return p\Mbstring::mb_convert_variables($to_encoding, $from_encoding, ...$vars); } +} + +if (!function_exists('mb_ord')) { + function mb_ord($string, $encoding = null) { return p\Mbstring::mb_ord($string, $encoding); } +} +if (!function_exists('mb_chr')) { + function mb_chr($codepoint, $encoding = null) { return p\Mbstring::mb_chr($codepoint, $encoding); } +} +if (!function_exists('mb_scrub')) { + function mb_scrub($string, $encoding = null) { $encoding = null === $encoding ? mb_internal_encoding() : $encoding; return mb_convert_encoding($string, $encoding, $encoding); } +} +if (!function_exists('mb_str_split')) { + function mb_str_split($string, $length = 1, $encoding = null) { return p\Mbstring::mb_str_split($string, $length, $encoding); } +} + +if (extension_loaded('mbstring')) { + return; +} + +if (!defined('MB_CASE_UPPER')) { + define('MB_CASE_UPPER', 0); +} +if (!defined('MB_CASE_LOWER')) { + define('MB_CASE_LOWER', 1); +} +if (!defined('MB_CASE_TITLE')) { + define('MB_CASE_TITLE', 2); +} diff --git a/vendor/symfony/polyfill-mbstring/bootstrap80.php b/vendor/symfony/polyfill-mbstring/bootstrap80.php new file mode 100644 index 0000000..82f5ac4 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/bootstrap80.php @@ -0,0 +1,143 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Mbstring as p; + +if (!function_exists('mb_convert_encoding')) { + function mb_convert_encoding(array|string|null $string, ?string $to_encoding, array|string|null $from_encoding = null): array|string|false { return p\Mbstring::mb_convert_encoding($string ?? '', (string) $to_encoding, $from_encoding); } +} +if (!function_exists('mb_decode_mimeheader')) { + function mb_decode_mimeheader(?string $string): string { return p\Mbstring::mb_decode_mimeheader((string) $string); } +} +if (!function_exists('mb_encode_mimeheader')) { + function mb_encode_mimeheader(?string $string, ?string $charset = null, ?string $transfer_encoding = null, ?string $newline = "\r\n", ?int $indent = 0): string { return p\Mbstring::mb_encode_mimeheader((string) $string, $charset, $transfer_encoding, (string) $newline, (int) $indent); } +} +if (!function_exists('mb_decode_numericentity')) { + function mb_decode_numericentity(?string $string, array $map, ?string $encoding = null): string { return p\Mbstring::mb_decode_numericentity((string) $string, $map, $encoding); } +} +if (!function_exists('mb_encode_numericentity')) { + function mb_encode_numericentity(?string $string, array $map, ?string $encoding = null, ?bool $hex = false): string { return p\Mbstring::mb_encode_numericentity((string) $string, $map, $encoding, (bool) $hex); } +} +if (!function_exists('mb_convert_case')) { + function mb_convert_case(?string $string, ?int $mode, ?string $encoding = null): string { return p\Mbstring::mb_convert_case((string) $string, (int) $mode, $encoding); } +} +if (!function_exists('mb_internal_encoding')) { + function mb_internal_encoding(?string $encoding = null): string|bool { return p\Mbstring::mb_internal_encoding($encoding); } +} +if (!function_exists('mb_language')) { + function mb_language(?string $language = null): string|bool { return p\Mbstring::mb_language($language); } +} +if (!function_exists('mb_list_encodings')) { + function mb_list_encodings(): array { return p\Mbstring::mb_list_encodings(); } +} +if (!function_exists('mb_encoding_aliases')) { + function mb_encoding_aliases(?string $encoding): array { return p\Mbstring::mb_encoding_aliases((string) $encoding); } +} +if (!function_exists('mb_check_encoding')) { + function mb_check_encoding(array|string|null $value = null, ?string $encoding = null): bool { return p\Mbstring::mb_check_encoding($value, $encoding); } +} +if (!function_exists('mb_detect_encoding')) { + function mb_detect_encoding(?string $string, array|string|null $encodings = null, ?bool $strict = false): string|false { return p\Mbstring::mb_detect_encoding((string) $string, $encodings, (bool) $strict); } +} +if (!function_exists('mb_detect_order')) { + function mb_detect_order(array|string|null $encoding = null): array|bool { return p\Mbstring::mb_detect_order($encoding); } +} +if (!function_exists('mb_parse_str')) { + function mb_parse_str(?string $string, &$result = []): bool { parse_str((string) $string, $result); return (bool) $result; } +} +if (!function_exists('mb_strlen')) { + function mb_strlen(?string $string, ?string $encoding = null): int { return p\Mbstring::mb_strlen((string) $string, $encoding); } +} +if (!function_exists('mb_strpos')) { + function mb_strpos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strpos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strtolower')) { + function mb_strtolower(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_strtolower((string) $string, $encoding); } +} +if (!function_exists('mb_strtoupper')) { + function mb_strtoupper(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_strtoupper((string) $string, $encoding); } +} +if (!function_exists('mb_substitute_character')) { + function mb_substitute_character(string|int|null $substitute_character = null): string|int|bool { return p\Mbstring::mb_substitute_character($substitute_character); } +} +if (!function_exists('mb_substr')) { + function mb_substr(?string $string, ?int $start, ?int $length = null, ?string $encoding = null): string { return p\Mbstring::mb_substr((string) $string, (int) $start, $length, $encoding); } +} +if (!function_exists('mb_stripos')) { + function mb_stripos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_stripos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_stristr')) { + function mb_stristr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_stristr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strrchr')) { + function mb_strrchr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrchr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strrichr')) { + function mb_strrichr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrichr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strripos')) { + function mb_strripos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strripos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strrpos')) { + function mb_strrpos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strrpos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strstr')) { + function mb_strstr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strstr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_get_info')) { + function mb_get_info(?string $type = 'all'): array|string|int|false { return p\Mbstring::mb_get_info((string) $type); } +} +if (!function_exists('mb_http_output')) { + function mb_http_output(?string $encoding = null): string|bool { return p\Mbstring::mb_http_output($encoding); } +} +if (!function_exists('mb_strwidth')) { + function mb_strwidth(?string $string, ?string $encoding = null): int { return p\Mbstring::mb_strwidth((string) $string, $encoding); } +} +if (!function_exists('mb_substr_count')) { + function mb_substr_count(?string $haystack, ?string $needle, ?string $encoding = null): int { return p\Mbstring::mb_substr_count((string) $haystack, (string) $needle, $encoding); } +} +if (!function_exists('mb_output_handler')) { + function mb_output_handler(?string $string, ?int $status): string { return p\Mbstring::mb_output_handler((string) $string, (int) $status); } +} +if (!function_exists('mb_http_input')) { + function mb_http_input(?string $type = null): array|string|false { return p\Mbstring::mb_http_input($type); } +} + +if (!function_exists('mb_convert_variables')) { + function mb_convert_variables(?string $to_encoding, array|string|null $from_encoding, mixed &$var, mixed &...$vars): string|false { return p\Mbstring::mb_convert_variables((string) $to_encoding, $from_encoding ?? '', $var, ...$vars); } +} + +if (!function_exists('mb_ord')) { + function mb_ord(?string $string, ?string $encoding = null): int|false { return p\Mbstring::mb_ord((string) $string, $encoding); } +} +if (!function_exists('mb_chr')) { + function mb_chr(?int $codepoint, ?string $encoding = null): string|false { return p\Mbstring::mb_chr((int) $codepoint, $encoding); } +} +if (!function_exists('mb_scrub')) { + function mb_scrub(?string $string, ?string $encoding = null): string { $encoding ??= mb_internal_encoding(); return mb_convert_encoding((string) $string, $encoding, $encoding); } +} +if (!function_exists('mb_str_split')) { + function mb_str_split(?string $string, ?int $length = 1, ?string $encoding = null): array { return p\Mbstring::mb_str_split((string) $string, (int) $length, $encoding); } +} + +if (extension_loaded('mbstring')) { + return; +} + +if (!defined('MB_CASE_UPPER')) { + define('MB_CASE_UPPER', 0); +} +if (!defined('MB_CASE_LOWER')) { + define('MB_CASE_LOWER', 1); +} +if (!defined('MB_CASE_TITLE')) { + define('MB_CASE_TITLE', 2); +} diff --git a/vendor/symfony/polyfill-mbstring/composer.json b/vendor/symfony/polyfill-mbstring/composer.json new file mode 100644 index 0000000..2ed7a74 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/composer.json @@ -0,0 +1,38 @@ +{ + "name": "symfony/polyfill-mbstring", + "type": "library", + "description": "Symfony polyfill for the Mbstring extension", + "keywords": ["polyfill", "shim", "compatibility", "portable", "mbstring"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Mbstring\\": "" }, + "files": [ "bootstrap.php" ] + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/vendor/symfony/polyfill-php72/LICENSE b/vendor/symfony/polyfill-php72/LICENSE new file mode 100644 index 0000000..4cd8bdd --- /dev/null +++ b/vendor/symfony/polyfill-php72/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-2019 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/polyfill-php72/Php72.php b/vendor/symfony/polyfill-php72/Php72.php new file mode 100644 index 0000000..5e20d5b --- /dev/null +++ b/vendor/symfony/polyfill-php72/Php72.php @@ -0,0 +1,217 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php72; + +/** + * @author Nicolas Grekas + * @author Dariusz Rumiński + * + * @internal + */ +final class Php72 +{ + private static $hashMask; + + public static function utf8_encode($s) + { + $s .= $s; + $len = \strlen($s); + + for ($i = $len >> 1, $j = 0; $i < $len; ++$i, ++$j) { + switch (true) { + case $s[$i] < "\x80": $s[$j] = $s[$i]; break; + case $s[$i] < "\xC0": $s[$j] = "\xC2"; $s[++$j] = $s[$i]; break; + default: $s[$j] = "\xC3"; $s[++$j] = \chr(\ord($s[$i]) - 64); break; + } + } + + return substr($s, 0, $j); + } + + public static function utf8_decode($s) + { + $s = (string) $s; + $len = \strlen($s); + + for ($i = 0, $j = 0; $i < $len; ++$i, ++$j) { + switch ($s[$i] & "\xF0") { + case "\xC0": + case "\xD0": + $c = (\ord($s[$i] & "\x1F") << 6) | \ord($s[++$i] & "\x3F"); + $s[$j] = $c < 256 ? \chr($c) : '?'; + break; + + case "\xF0": + ++$i; + // no break + + case "\xE0": + $s[$j] = '?'; + $i += 2; + break; + + default: + $s[$j] = $s[$i]; + } + } + + return substr($s, 0, $j); + } + + public static function php_os_family() + { + if ('\\' === \DIRECTORY_SEPARATOR) { + return 'Windows'; + } + + $map = [ + 'Darwin' => 'Darwin', + 'DragonFly' => 'BSD', + 'FreeBSD' => 'BSD', + 'NetBSD' => 'BSD', + 'OpenBSD' => 'BSD', + 'Linux' => 'Linux', + 'SunOS' => 'Solaris', + ]; + + return isset($map[\PHP_OS]) ? $map[\PHP_OS] : 'Unknown'; + } + + public static function spl_object_id($object) + { + if (null === self::$hashMask) { + self::initHashMask(); + } + if (null === $hash = spl_object_hash($object)) { + return; + } + + // On 32-bit systems, PHP_INT_SIZE is 4, + return self::$hashMask ^ hexdec(substr($hash, 16 - (\PHP_INT_SIZE * 2 - 1), (\PHP_INT_SIZE * 2 - 1))); + } + + public static function sapi_windows_vt100_support($stream, $enable = null) + { + if (!\is_resource($stream)) { + trigger_error('sapi_windows_vt100_support() expects parameter 1 to be resource, '.\gettype($stream).' given', \E_USER_WARNING); + + return false; + } + + $meta = stream_get_meta_data($stream); + + if ('STDIO' !== $meta['stream_type']) { + trigger_error('sapi_windows_vt100_support() was not able to analyze the specified stream', \E_USER_WARNING); + + return false; + } + + // We cannot actually disable vt100 support if it is set + if (false === $enable || !self::stream_isatty($stream)) { + return false; + } + + // The native function does not apply to stdin + $meta = array_map('strtolower', $meta); + $stdin = 'php://stdin' === $meta['uri'] || 'php://fd/0' === $meta['uri']; + + return !$stdin + && (false !== getenv('ANSICON') + || 'ON' === getenv('ConEmuANSI') + || 'xterm' === getenv('TERM') + || 'Hyper' === getenv('TERM_PROGRAM')); + } + + public static function stream_isatty($stream) + { + if (!\is_resource($stream)) { + trigger_error('stream_isatty() expects parameter 1 to be resource, '.\gettype($stream).' given', \E_USER_WARNING); + + return false; + } + + if ('\\' === \DIRECTORY_SEPARATOR) { + $stat = @fstat($stream); + // Check if formatted mode is S_IFCHR + return $stat ? 0020000 === ($stat['mode'] & 0170000) : false; + } + + return \function_exists('posix_isatty') && @posix_isatty($stream); + } + + private static function initHashMask() + { + $obj = (object) []; + self::$hashMask = -1; + + // check if we are nested in an output buffering handler to prevent a fatal error with ob_start() below + $obFuncs = ['ob_clean', 'ob_end_clean', 'ob_flush', 'ob_end_flush', 'ob_get_contents', 'ob_get_flush']; + foreach (debug_backtrace(\PHP_VERSION_ID >= 50400 ? \DEBUG_BACKTRACE_IGNORE_ARGS : false) as $frame) { + if (isset($frame['function'][0]) && !isset($frame['class']) && 'o' === $frame['function'][0] && \in_array($frame['function'], $obFuncs)) { + $frame['line'] = 0; + break; + } + } + if (!empty($frame['line'])) { + ob_start(); + debug_zval_dump($obj); + self::$hashMask = (int) substr(ob_get_clean(), 17); + } + + self::$hashMask ^= hexdec(substr(spl_object_hash($obj), 16 - (\PHP_INT_SIZE * 2 - 1), (\PHP_INT_SIZE * 2 - 1))); + } + + public static function mb_chr($code, $encoding = null) + { + if (0x80 > $code %= 0x200000) { + $s = \chr($code); + } elseif (0x800 > $code) { + $s = \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F); + } elseif (0x10000 > $code) { + $s = \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } else { + $s = \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } + + if ('UTF-8' !== $encoding = $encoding ?? mb_internal_encoding()) { + $s = mb_convert_encoding($s, $encoding, 'UTF-8'); + } + + return $s; + } + + public static function mb_ord($s, $encoding = null) + { + if (null === $encoding) { + $s = mb_convert_encoding($s, 'UTF-8'); + } elseif ('UTF-8' !== $encoding) { + $s = mb_convert_encoding($s, 'UTF-8', $encoding); + } + + if (1 === \strlen($s)) { + return \ord($s); + } + + $code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0; + if (0xF0 <= $code) { + return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80; + } + if (0xE0 <= $code) { + return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80; + } + if (0xC0 <= $code) { + return (($code - 0xC0) << 6) + $s[2] - 0x80; + } + + return $code; + } +} diff --git a/vendor/symfony/polyfill-php72/README.md b/vendor/symfony/polyfill-php72/README.md new file mode 100644 index 0000000..59dec8a --- /dev/null +++ b/vendor/symfony/polyfill-php72/README.md @@ -0,0 +1,28 @@ +Symfony Polyfill / Php72 +======================== + +This component provides functions added to PHP 7.2 core: + +- [`spl_object_id`](https://php.net/spl_object_id) +- [`stream_isatty`](https://php.net/stream_isatty) + +On Windows only: + +- [`sapi_windows_vt100_support`](https://php.net/sapi_windows_vt100_support) + +Moved to core since 7.2 (was in the optional XML extension earlier): + +- [`utf8_encode`](https://php.net/utf8_encode) +- [`utf8_decode`](https://php.net/utf8_decode) + +Also, it provides constants added to PHP 7.2: +- [`PHP_FLOAT_*`](https://php.net/reserved.constants#constant.php-float-dig) +- [`PHP_OS_FAMILY`](https://php.net/reserved.constants#constant.php-os-family) + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-php72/bootstrap.php b/vendor/symfony/polyfill-php72/bootstrap.php new file mode 100644 index 0000000..b5c92d4 --- /dev/null +++ b/vendor/symfony/polyfill-php72/bootstrap.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php72 as p; + +if (\PHP_VERSION_ID >= 70200) { + return; +} + +if (!defined('PHP_FLOAT_DIG')) { + define('PHP_FLOAT_DIG', 15); +} +if (!defined('PHP_FLOAT_EPSILON')) { + define('PHP_FLOAT_EPSILON', 2.2204460492503E-16); +} +if (!defined('PHP_FLOAT_MIN')) { + define('PHP_FLOAT_MIN', 2.2250738585072E-308); +} +if (!defined('PHP_FLOAT_MAX')) { + define('PHP_FLOAT_MAX', 1.7976931348623157E+308); +} +if (!defined('PHP_OS_FAMILY')) { + define('PHP_OS_FAMILY', p\Php72::php_os_family()); +} + +if ('\\' === \DIRECTORY_SEPARATOR && !function_exists('sapi_windows_vt100_support')) { + function sapi_windows_vt100_support($stream, $enable = null) { return p\Php72::sapi_windows_vt100_support($stream, $enable); } +} +if (!function_exists('stream_isatty')) { + function stream_isatty($stream) { return p\Php72::stream_isatty($stream); } +} +if (!function_exists('utf8_encode')) { + function utf8_encode($string) { return p\Php72::utf8_encode($string); } +} +if (!function_exists('utf8_decode')) { + function utf8_decode($string) { return p\Php72::utf8_decode($string); } +} +if (!function_exists('spl_object_id')) { + function spl_object_id($object) { return p\Php72::spl_object_id($object); } +} +if (!function_exists('mb_ord')) { + function mb_ord($string, $encoding = null) { return p\Php72::mb_ord($string, $encoding); } +} +if (!function_exists('mb_chr')) { + function mb_chr($codepoint, $encoding = null) { return p\Php72::mb_chr($codepoint, $encoding); } +} +if (!function_exists('mb_scrub')) { + function mb_scrub($string, $encoding = null) { $encoding = null === $encoding ? mb_internal_encoding() : $encoding; return mb_convert_encoding($string, $encoding, $encoding); } +} diff --git a/vendor/symfony/polyfill-php72/composer.json b/vendor/symfony/polyfill-php72/composer.json new file mode 100644 index 0000000..c96c844 --- /dev/null +++ b/vendor/symfony/polyfill-php72/composer.json @@ -0,0 +1,35 @@ +{ + "name": "symfony/polyfill-php72", + "type": "library", + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "keywords": ["polyfill", "shim", "compatibility", "portable"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php72\\": "" }, + "files": [ "bootstrap.php" ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/vendor/symfony/polyfill-php73/LICENSE b/vendor/symfony/polyfill-php73/LICENSE new file mode 100644 index 0000000..3f853aa --- /dev/null +++ b/vendor/symfony/polyfill-php73/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2019 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/polyfill-php73/Php73.php b/vendor/symfony/polyfill-php73/Php73.php new file mode 100644 index 0000000..65c35a6 --- /dev/null +++ b/vendor/symfony/polyfill-php73/Php73.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php73; + +/** + * @author Gabriel Caruso + * @author Ion Bazan + * + * @internal + */ +final class Php73 +{ + public static $startAt = 1533462603; + + /** + * @param bool $asNum + * + * @return array|float|int + */ + public static function hrtime($asNum = false) + { + $ns = microtime(false); + $s = substr($ns, 11) - self::$startAt; + $ns = 1E9 * (float) $ns; + + if ($asNum) { + $ns += $s * 1E9; + + return \PHP_INT_SIZE === 4 ? $ns : (int) $ns; + } + + return [$s, (int) $ns]; + } +} diff --git a/vendor/symfony/polyfill-php73/README.md b/vendor/symfony/polyfill-php73/README.md new file mode 100644 index 0000000..b3ebbce --- /dev/null +++ b/vendor/symfony/polyfill-php73/README.md @@ -0,0 +1,18 @@ +Symfony Polyfill / Php73 +======================== + +This component provides functions added to PHP 7.3 core: + +- [`array_key_first`](https://php.net/array_key_first) +- [`array_key_last`](https://php.net/array_key_last) +- [`hrtime`](https://php.net/function.hrtime) +- [`is_countable`](https://php.net/is_countable) +- [`JsonException`](https://php.net/JsonException) + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-php73/Resources/stubs/JsonException.php b/vendor/symfony/polyfill-php73/Resources/stubs/JsonException.php new file mode 100644 index 0000000..673d100 --- /dev/null +++ b/vendor/symfony/polyfill-php73/Resources/stubs/JsonException.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +class JsonException extends Exception +{ +} diff --git a/vendor/symfony/polyfill-php73/bootstrap.php b/vendor/symfony/polyfill-php73/bootstrap.php new file mode 100644 index 0000000..d6b2153 --- /dev/null +++ b/vendor/symfony/polyfill-php73/bootstrap.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php73 as p; + +if (\PHP_VERSION_ID >= 70300) { + return; +} + +if (!function_exists('is_countable')) { + function is_countable($value) { return is_array($value) || $value instanceof Countable || $value instanceof ResourceBundle || $value instanceof SimpleXmlElement; } +} +if (!function_exists('hrtime')) { + require_once __DIR__.'/Php73.php'; + p\Php73::$startAt = (int) microtime(true); + function hrtime($as_number = false) { return p\Php73::hrtime($as_number); } +} +if (!function_exists('array_key_first')) { + function array_key_first(array $array) { foreach ($array as $key => $value) { return $key; } } +} +if (!function_exists('array_key_last')) { + function array_key_last(array $array) { return key(array_slice($array, -1, 1, true)); } +} diff --git a/vendor/symfony/polyfill-php73/composer.json b/vendor/symfony/polyfill-php73/composer.json new file mode 100644 index 0000000..a7fe478 --- /dev/null +++ b/vendor/symfony/polyfill-php73/composer.json @@ -0,0 +1,36 @@ +{ + "name": "symfony/polyfill-php73", + "type": "library", + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "keywords": ["polyfill", "shim", "compatibility", "portable"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php73\\": "" }, + "files": [ "bootstrap.php" ], + "classmap": [ "Resources/stubs" ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/vendor/symfony/polyfill-php80/LICENSE b/vendor/symfony/polyfill-php80/LICENSE new file mode 100644 index 0000000..5593b1d --- /dev/null +++ b/vendor/symfony/polyfill-php80/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/polyfill-php80/Php80.php b/vendor/symfony/polyfill-php80/Php80.php new file mode 100644 index 0000000..5fef511 --- /dev/null +++ b/vendor/symfony/polyfill-php80/Php80.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php80; + +/** + * @author Ion Bazan + * @author Nico Oelgart + * @author Nicolas Grekas + * + * @internal + */ +final class Php80 +{ + public static function fdiv(float $dividend, float $divisor): float + { + return @($dividend / $divisor); + } + + public static function get_debug_type($value): string + { + switch (true) { + case null === $value: return 'null'; + case \is_bool($value): return 'bool'; + case \is_string($value): return 'string'; + case \is_array($value): return 'array'; + case \is_int($value): return 'int'; + case \is_float($value): return 'float'; + case \is_object($value): break; + case $value instanceof \__PHP_Incomplete_Class: return '__PHP_Incomplete_Class'; + default: + if (null === $type = @get_resource_type($value)) { + return 'unknown'; + } + + if ('Unknown' === $type) { + $type = 'closed'; + } + + return "resource ($type)"; + } + + $class = \get_class($value); + + if (false === strpos($class, '@')) { + return $class; + } + + return (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous'; + } + + public static function get_resource_id($res): int + { + if (!\is_resource($res) && null === @get_resource_type($res)) { + throw new \TypeError(sprintf('Argument 1 passed to get_resource_id() must be of the type resource, %s given', get_debug_type($res))); + } + + return (int) $res; + } + + public static function preg_last_error_msg(): string + { + switch (preg_last_error()) { + case \PREG_INTERNAL_ERROR: + return 'Internal error'; + case \PREG_BAD_UTF8_ERROR: + return 'Malformed UTF-8 characters, possibly incorrectly encoded'; + case \PREG_BAD_UTF8_OFFSET_ERROR: + return 'The offset did not correspond to the beginning of a valid UTF-8 code point'; + case \PREG_BACKTRACK_LIMIT_ERROR: + return 'Backtrack limit exhausted'; + case \PREG_RECURSION_LIMIT_ERROR: + return 'Recursion limit exhausted'; + case \PREG_JIT_STACKLIMIT_ERROR: + return 'JIT stack limit exhausted'; + case \PREG_NO_ERROR: + return 'No error'; + default: + return 'Unknown error'; + } + } + + public static function str_contains(string $haystack, string $needle): bool + { + return '' === $needle || false !== strpos($haystack, $needle); + } + + public static function str_starts_with(string $haystack, string $needle): bool + { + return 0 === strncmp($haystack, $needle, \strlen($needle)); + } + + public static function str_ends_with(string $haystack, string $needle): bool + { + return '' === $needle || ('' !== $haystack && 0 === substr_compare($haystack, $needle, -\strlen($needle))); + } +} diff --git a/vendor/symfony/polyfill-php80/README.md b/vendor/symfony/polyfill-php80/README.md new file mode 100644 index 0000000..10b8ee4 --- /dev/null +++ b/vendor/symfony/polyfill-php80/README.md @@ -0,0 +1,24 @@ +Symfony Polyfill / Php80 +======================== + +This component provides features added to PHP 8.0 core: + +- `Stringable` interface +- [`fdiv`](https://php.net/fdiv) +- `ValueError` class +- `UnhandledMatchError` class +- `FILTER_VALIDATE_BOOL` constant +- [`get_debug_type`](https://php.net/get_debug_type) +- [`preg_last_error_msg`](https://php.net/preg_last_error_msg) +- [`str_contains`](https://php.net/str_contains) +- [`str_starts_with`](https://php.net/str_starts_with) +- [`str_ends_with`](https://php.net/str_ends_with) +- [`get_resource_id`](https://php.net/get_resource_id) + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php b/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php new file mode 100644 index 0000000..7ea6d27 --- /dev/null +++ b/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php @@ -0,0 +1,22 @@ +flags = $flags; + } +} diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php b/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php new file mode 100644 index 0000000..77e037c --- /dev/null +++ b/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php @@ -0,0 +1,11 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php80 as p; + +if (\PHP_VERSION_ID >= 80000) { + return; +} + +if (!defined('FILTER_VALIDATE_BOOL') && defined('FILTER_VALIDATE_BOOLEAN')) { + define('FILTER_VALIDATE_BOOL', \FILTER_VALIDATE_BOOLEAN); +} + +if (!function_exists('fdiv')) { + function fdiv(float $num1, float $num2): float { return p\Php80::fdiv($num1, $num2); } +} +if (!function_exists('preg_last_error_msg')) { + function preg_last_error_msg(): string { return p\Php80::preg_last_error_msg(); } +} +if (!function_exists('str_contains')) { + function str_contains(?string $haystack, ?string $needle): bool { return p\Php80::str_contains($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('str_starts_with')) { + function str_starts_with(?string $haystack, ?string $needle): bool { return p\Php80::str_starts_with($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('str_ends_with')) { + function str_ends_with(?string $haystack, ?string $needle): bool { return p\Php80::str_ends_with($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('get_debug_type')) { + function get_debug_type($value): string { return p\Php80::get_debug_type($value); } +} +if (!function_exists('get_resource_id')) { + function get_resource_id($resource): int { return p\Php80::get_resource_id($resource); } +} diff --git a/vendor/symfony/polyfill-php80/composer.json b/vendor/symfony/polyfill-php80/composer.json new file mode 100644 index 0000000..5fe679d --- /dev/null +++ b/vendor/symfony/polyfill-php80/composer.json @@ -0,0 +1,40 @@ +{ + "name": "symfony/polyfill-php80", + "type": "library", + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "keywords": ["polyfill", "shim", "compatibility", "portable"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php80\\": "" }, + "files": [ "bootstrap.php" ], + "classmap": [ "Resources/stubs" ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/vendor/symfony/process/CHANGELOG.md b/vendor/symfony/process/CHANGELOG.md new file mode 100644 index 0000000..69d4cbd --- /dev/null +++ b/vendor/symfony/process/CHANGELOG.md @@ -0,0 +1,96 @@ +CHANGELOG +========= + +4.4.0 +----- + + * deprecated `Process::inheritEnvironmentVariables()`: env variables are always inherited. + * added `Process::getLastOutputTime()` method + +4.2.0 +----- + + * added the `Process::fromShellCommandline()` to run commands in a shell wrapper + * deprecated passing a command as string when creating a `Process` instance + * deprecated the `Process::setCommandline()` and the `PhpProcess::setPhpBinary()` methods + * added the `Process::waitUntil()` method to wait for the process only for a + specific output, then continue the normal execution of your application + +4.1.0 +----- + + * added the `Process::isTtySupported()` method that allows to check for TTY support + * made `PhpExecutableFinder` look for the `PHP_BINARY` env var when searching the php binary + * added the `ProcessSignaledException` class to properly catch signaled process errors + +4.0.0 +----- + + * environment variables will always be inherited + * added a second `array $env = []` argument to the `start()`, `run()`, + `mustRun()`, and `restart()` methods of the `Process` class + * added a second `array $env = []` argument to the `start()` method of the + `PhpProcess` class + * the `ProcessUtils::escapeArgument()` method has been removed + * the `areEnvironmentVariablesInherited()`, `getOptions()`, and `setOptions()` + methods of the `Process` class have been removed + * support for passing `proc_open()` options has been removed + * removed the `ProcessBuilder` class, use the `Process` class instead + * removed the `getEnhanceWindowsCompatibility()` and `setEnhanceWindowsCompatibility()` methods of the `Process` class + * passing a not existing working directory to the constructor of the `Symfony\Component\Process\Process` class is not + supported anymore + +3.4.0 +----- + + * deprecated the ProcessBuilder class + * deprecated calling `Process::start()` without setting a valid working directory beforehand (via `setWorkingDirectory()` or constructor) + +3.3.0 +----- + + * added command line arrays in the `Process` class + * added `$env` argument to `Process::start()`, `run()`, `mustRun()` and `restart()` methods + * deprecated the `ProcessUtils::escapeArgument()` method + * deprecated not inheriting environment variables + * deprecated configuring `proc_open()` options + * deprecated configuring enhanced Windows compatibility + * deprecated configuring enhanced sigchild compatibility + +2.5.0 +----- + + * added support for PTY mode + * added the convenience method "mustRun" + * deprecation: Process::setStdin() is deprecated in favor of Process::setInput() + * deprecation: Process::getStdin() is deprecated in favor of Process::getInput() + * deprecation: Process::setInput() and ProcessBuilder::setInput() do not accept non-scalar types + +2.4.0 +----- + + * added the ability to define an idle timeout + +2.3.0 +----- + + * added ProcessUtils::escapeArgument() to fix the bug in escapeshellarg() function on Windows + * added Process::signal() + * added Process::getPid() + * added support for a TTY mode + +2.2.0 +----- + + * added ProcessBuilder::setArguments() to reset the arguments on a builder + * added a way to retrieve the standard and error output incrementally + * added Process:restart() + +2.1.0 +----- + + * added support for non-blocking processes (start(), wait(), isRunning(), stop()) + * enhanced Windows compatibility + * added Process::getExitCodeText() that returns a string representation for + the exit code returned by the process + * added ProcessBuilder diff --git a/vendor/symfony/process/Exception/ExceptionInterface.php b/vendor/symfony/process/Exception/ExceptionInterface.php new file mode 100644 index 0000000..bd4a604 --- /dev/null +++ b/vendor/symfony/process/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +/** + * Marker Interface for the Process Component. + * + * @author Johannes M. Schmitt + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/vendor/symfony/process/Exception/InvalidArgumentException.php b/vendor/symfony/process/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..926ee21 --- /dev/null +++ b/vendor/symfony/process/Exception/InvalidArgumentException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +/** + * InvalidArgumentException for the Process Component. + * + * @author Romain Neutron + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/process/Exception/LogicException.php b/vendor/symfony/process/Exception/LogicException.php new file mode 100644 index 0000000..be3d490 --- /dev/null +++ b/vendor/symfony/process/Exception/LogicException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +/** + * LogicException for the Process Component. + * + * @author Romain Neutron + */ +class LogicException extends \LogicException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/process/Exception/ProcessFailedException.php b/vendor/symfony/process/Exception/ProcessFailedException.php new file mode 100644 index 0000000..328acfd --- /dev/null +++ b/vendor/symfony/process/Exception/ProcessFailedException.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +use Symfony\Component\Process\Process; + +/** + * Exception for failed processes. + * + * @author Johannes M. Schmitt + */ +class ProcessFailedException extends RuntimeException +{ + private $process; + + public function __construct(Process $process) + { + if ($process->isSuccessful()) { + throw new InvalidArgumentException('Expected a failed process, but the given process was successful.'); + } + + $error = sprintf('The command "%s" failed.'."\n\nExit Code: %s(%s)\n\nWorking directory: %s", + $process->getCommandLine(), + $process->getExitCode(), + $process->getExitCodeText(), + $process->getWorkingDirectory() + ); + + if (!$process->isOutputDisabled()) { + $error .= sprintf("\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s", + $process->getOutput(), + $process->getErrorOutput() + ); + } + + parent::__construct($error); + + $this->process = $process; + } + + public function getProcess() + { + return $this->process; + } +} diff --git a/vendor/symfony/process/Exception/ProcessSignaledException.php b/vendor/symfony/process/Exception/ProcessSignaledException.php new file mode 100644 index 0000000..d4d3227 --- /dev/null +++ b/vendor/symfony/process/Exception/ProcessSignaledException.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +use Symfony\Component\Process\Process; + +/** + * Exception that is thrown when a process has been signaled. + * + * @author Sullivan Senechal + */ +final class ProcessSignaledException extends RuntimeException +{ + private $process; + + public function __construct(Process $process) + { + $this->process = $process; + + parent::__construct(sprintf('The process has been signaled with signal "%s".', $process->getTermSignal())); + } + + public function getProcess(): Process + { + return $this->process; + } + + public function getSignal(): int + { + return $this->getProcess()->getTermSignal(); + } +} diff --git a/vendor/symfony/process/Exception/ProcessTimedOutException.php b/vendor/symfony/process/Exception/ProcessTimedOutException.php new file mode 100644 index 0000000..94391a4 --- /dev/null +++ b/vendor/symfony/process/Exception/ProcessTimedOutException.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +use Symfony\Component\Process\Process; + +/** + * Exception that is thrown when a process times out. + * + * @author Johannes M. Schmitt + */ +class ProcessTimedOutException extends RuntimeException +{ + public const TYPE_GENERAL = 1; + public const TYPE_IDLE = 2; + + private $process; + private $timeoutType; + + public function __construct(Process $process, int $timeoutType) + { + $this->process = $process; + $this->timeoutType = $timeoutType; + + parent::__construct(sprintf( + 'The process "%s" exceeded the timeout of %s seconds.', + $process->getCommandLine(), + $this->getExceededTimeout() + )); + } + + public function getProcess() + { + return $this->process; + } + + public function isGeneralTimeout() + { + return self::TYPE_GENERAL === $this->timeoutType; + } + + public function isIdleTimeout() + { + return self::TYPE_IDLE === $this->timeoutType; + } + + public function getExceededTimeout() + { + switch ($this->timeoutType) { + case self::TYPE_GENERAL: + return $this->process->getTimeout(); + + case self::TYPE_IDLE: + return $this->process->getIdleTimeout(); + + default: + throw new \LogicException(sprintf('Unknown timeout type "%d".', $this->timeoutType)); + } + } +} diff --git a/vendor/symfony/process/Exception/RuntimeException.php b/vendor/symfony/process/Exception/RuntimeException.php new file mode 100644 index 0000000..adead25 --- /dev/null +++ b/vendor/symfony/process/Exception/RuntimeException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +/** + * RuntimeException for the Process Component. + * + * @author Johannes M. Schmitt + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/process/ExecutableFinder.php b/vendor/symfony/process/ExecutableFinder.php new file mode 100644 index 0000000..ff68ed3 --- /dev/null +++ b/vendor/symfony/process/ExecutableFinder.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +/** + * Generic executable finder. + * + * @author Fabien Potencier + * @author Johannes M. Schmitt + */ +class ExecutableFinder +{ + private $suffixes = ['.exe', '.bat', '.cmd', '.com']; + + /** + * Replaces default suffixes of executable. + */ + public function setSuffixes(array $suffixes) + { + $this->suffixes = $suffixes; + } + + /** + * Adds new possible suffix to check for executable. + * + * @param string $suffix + */ + public function addSuffix($suffix) + { + $this->suffixes[] = $suffix; + } + + /** + * Finds an executable by name. + * + * @param string $name The executable name (without the extension) + * @param string|null $default The default to return if no executable is found + * @param array $extraDirs Additional dirs to check into + * + * @return string|null The executable path or default value + */ + public function find($name, $default = null, array $extraDirs = []) + { + if (ini_get('open_basedir')) { + $searchPath = array_merge(explode(\PATH_SEPARATOR, ini_get('open_basedir')), $extraDirs); + $dirs = []; + foreach ($searchPath as $path) { + // Silencing against https://bugs.php.net/69240 + if (@is_dir($path)) { + $dirs[] = $path; + } else { + if (basename($path) == $name && @is_executable($path)) { + return $path; + } + } + } + } else { + $dirs = array_merge( + explode(\PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')), + $extraDirs + ); + } + + $suffixes = ['']; + if ('\\' === \DIRECTORY_SEPARATOR) { + $pathExt = getenv('PATHEXT'); + $suffixes = array_merge($pathExt ? explode(\PATH_SEPARATOR, $pathExt) : $this->suffixes, $suffixes); + } + foreach ($suffixes as $suffix) { + foreach ($dirs as $dir) { + if (@is_file($file = $dir.\DIRECTORY_SEPARATOR.$name.$suffix) && ('\\' === \DIRECTORY_SEPARATOR || @is_executable($file))) { + return $file; + } + } + } + + return $default; + } +} diff --git a/vendor/symfony/process/InputStream.php b/vendor/symfony/process/InputStream.php new file mode 100644 index 0000000..4f8f713 --- /dev/null +++ b/vendor/symfony/process/InputStream.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +use Symfony\Component\Process\Exception\RuntimeException; + +/** + * Provides a way to continuously write to the input of a Process until the InputStream is closed. + * + * @author Nicolas Grekas + */ +class InputStream implements \IteratorAggregate +{ + /** @var callable|null */ + private $onEmpty = null; + private $input = []; + private $open = true; + + /** + * Sets a callback that is called when the write buffer becomes empty. + */ + public function onEmpty(callable $onEmpty = null) + { + $this->onEmpty = $onEmpty; + } + + /** + * Appends an input to the write buffer. + * + * @param resource|string|int|float|bool|\Traversable|null $input The input to append as scalar, + * stream resource or \Traversable + */ + public function write($input) + { + if (null === $input) { + return; + } + if ($this->isClosed()) { + throw new RuntimeException(sprintf('"%s" is closed.', static::class)); + } + $this->input[] = ProcessUtils::validateInput(__METHOD__, $input); + } + + /** + * Closes the write buffer. + */ + public function close() + { + $this->open = false; + } + + /** + * Tells whether the write buffer is closed or not. + */ + public function isClosed() + { + return !$this->open; + } + + /** + * @return \Traversable + */ + #[\ReturnTypeWillChange] + public function getIterator() + { + $this->open = true; + + while ($this->open || $this->input) { + if (!$this->input) { + yield ''; + continue; + } + $current = array_shift($this->input); + + if ($current instanceof \Iterator) { + yield from $current; + } else { + yield $current; + } + if (!$this->input && $this->open && null !== $onEmpty = $this->onEmpty) { + $this->write($onEmpty($this)); + } + } + } +} diff --git a/vendor/symfony/process/LICENSE b/vendor/symfony/process/LICENSE new file mode 100644 index 0000000..9ff2d0d --- /dev/null +++ b/vendor/symfony/process/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2021 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/process/PhpExecutableFinder.php b/vendor/symfony/process/PhpExecutableFinder.php new file mode 100644 index 0000000..3d5eabd --- /dev/null +++ b/vendor/symfony/process/PhpExecutableFinder.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +/** + * An executable finder specifically designed for the PHP executable. + * + * @author Fabien Potencier + * @author Johannes M. Schmitt + */ +class PhpExecutableFinder +{ + private $executableFinder; + + public function __construct() + { + $this->executableFinder = new ExecutableFinder(); + } + + /** + * Finds The PHP executable. + * + * @param bool $includeArgs Whether or not include command arguments + * + * @return string|false The PHP executable path or false if it cannot be found + */ + public function find($includeArgs = true) + { + if ($php = getenv('PHP_BINARY')) { + if (!is_executable($php)) { + $command = '\\' === \DIRECTORY_SEPARATOR ? 'where' : 'command -v'; + if ($php = strtok(exec($command.' '.escapeshellarg($php)), \PHP_EOL)) { + if (!is_executable($php)) { + return false; + } + } else { + return false; + } + } + + return $php; + } + + $args = $this->findArguments(); + $args = $includeArgs && $args ? ' '.implode(' ', $args) : ''; + + // PHP_BINARY return the current sapi executable + if (\PHP_BINARY && \in_array(\PHP_SAPI, ['cgi-fcgi', 'cli', 'cli-server', 'phpdbg'], true)) { + return \PHP_BINARY.$args; + } + + if ($php = getenv('PHP_PATH')) { + if (!@is_executable($php)) { + return false; + } + + return $php; + } + + if ($php = getenv('PHP_PEAR_PHP_BIN')) { + if (@is_executable($php)) { + return $php; + } + } + + if (@is_executable($php = \PHP_BINDIR.('\\' === \DIRECTORY_SEPARATOR ? '\\php.exe' : '/php'))) { + return $php; + } + + $dirs = [\PHP_BINDIR]; + if ('\\' === \DIRECTORY_SEPARATOR) { + $dirs[] = 'C:\xampp\php\\'; + } + + return $this->executableFinder->find('php', false, $dirs); + } + + /** + * Finds the PHP executable arguments. + * + * @return array The PHP executable arguments + */ + public function findArguments() + { + $arguments = []; + if ('phpdbg' === \PHP_SAPI) { + $arguments[] = '-qrr'; + } + + return $arguments; + } +} diff --git a/vendor/symfony/process/PhpProcess.php b/vendor/symfony/process/PhpProcess.php new file mode 100644 index 0000000..dc064e0 --- /dev/null +++ b/vendor/symfony/process/PhpProcess.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +use Symfony\Component\Process\Exception\LogicException; +use Symfony\Component\Process\Exception\RuntimeException; + +/** + * PhpProcess runs a PHP script in an independent process. + * + * $p = new PhpProcess(''); + * $p->run(); + * print $p->getOutput()."\n"; + * + * @author Fabien Potencier + */ +class PhpProcess extends Process +{ + /** + * @param string $script The PHP script to run (as a string) + * @param string|null $cwd The working directory or null to use the working dir of the current PHP process + * @param array|null $env The environment variables or null to use the same environment as the current PHP process + * @param int $timeout The timeout in seconds + * @param array|null $php Path to the PHP binary to use with any additional arguments + */ + public function __construct(string $script, string $cwd = null, array $env = null, int $timeout = 60, array $php = null) + { + if (null === $php) { + $executableFinder = new PhpExecutableFinder(); + $php = $executableFinder->find(false); + $php = false === $php ? null : array_merge([$php], $executableFinder->findArguments()); + } + if ('phpdbg' === \PHP_SAPI) { + $file = tempnam(sys_get_temp_dir(), 'dbg'); + file_put_contents($file, $script); + register_shutdown_function('unlink', $file); + $php[] = $file; + $script = null; + } + + parent::__construct($php, $cwd, $env, $script, $timeout); + } + + /** + * {@inheritdoc} + */ + public static function fromShellCommandline(string $command, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60) + { + throw new LogicException(sprintf('The "%s()" method cannot be called when using "%s".', __METHOD__, self::class)); + } + + /** + * Sets the path to the PHP binary to use. + * + * @deprecated since Symfony 4.2, use the $php argument of the constructor instead. + */ + public function setPhpBinary($php) + { + @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the $php argument of the constructor instead.', __METHOD__), \E_USER_DEPRECATED); + + $this->setCommandLine($php); + } + + /** + * {@inheritdoc} + */ + public function start(callable $callback = null, array $env = []) + { + if (null === $this->getCommandLine()) { + throw new RuntimeException('Unable to find the PHP executable.'); + } + + parent::start($callback, $env); + } +} diff --git a/vendor/symfony/process/Pipes/AbstractPipes.php b/vendor/symfony/process/Pipes/AbstractPipes.php new file mode 100644 index 0000000..21ab3e3 --- /dev/null +++ b/vendor/symfony/process/Pipes/AbstractPipes.php @@ -0,0 +1,178 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Pipes; + +use Symfony\Component\Process\Exception\InvalidArgumentException; + +/** + * @author Romain Neutron + * + * @internal + */ +abstract class AbstractPipes implements PipesInterface +{ + public $pipes = []; + + private $inputBuffer = ''; + private $input; + private $blocked = true; + private $lastError; + + /** + * @param resource|string|int|float|bool|\Iterator|null $input + */ + public function __construct($input) + { + if (\is_resource($input) || $input instanceof \Iterator) { + $this->input = $input; + } elseif (\is_string($input)) { + $this->inputBuffer = $input; + } else { + $this->inputBuffer = (string) $input; + } + } + + /** + * {@inheritdoc} + */ + public function close() + { + foreach ($this->pipes as $pipe) { + fclose($pipe); + } + $this->pipes = []; + } + + /** + * Returns true if a system call has been interrupted. + */ + protected function hasSystemCallBeenInterrupted(): bool + { + $lastError = $this->lastError; + $this->lastError = null; + + // stream_select returns false when the `select` system call is interrupted by an incoming signal + return null !== $lastError && false !== stripos($lastError, 'interrupted system call'); + } + + /** + * Unblocks streams. + */ + protected function unblock() + { + if (!$this->blocked) { + return; + } + + foreach ($this->pipes as $pipe) { + stream_set_blocking($pipe, 0); + } + if (\is_resource($this->input)) { + stream_set_blocking($this->input, 0); + } + + $this->blocked = false; + } + + /** + * Writes input to stdin. + * + * @throws InvalidArgumentException When an input iterator yields a non supported value + */ + protected function write(): ?array + { + if (!isset($this->pipes[0])) { + return null; + } + $input = $this->input; + + if ($input instanceof \Iterator) { + if (!$input->valid()) { + $input = null; + } elseif (\is_resource($input = $input->current())) { + stream_set_blocking($input, 0); + } elseif (!isset($this->inputBuffer[0])) { + if (!\is_string($input)) { + if (!is_scalar($input)) { + throw new InvalidArgumentException(sprintf('"%s" yielded a value of type "%s", but only scalars and stream resources are supported.', \get_class($this->input), \gettype($input))); + } + $input = (string) $input; + } + $this->inputBuffer = $input; + $this->input->next(); + $input = null; + } else { + $input = null; + } + } + + $r = $e = []; + $w = [$this->pipes[0]]; + + // let's have a look if something changed in streams + if (false === @stream_select($r, $w, $e, 0, 0)) { + return null; + } + + foreach ($w as $stdin) { + if (isset($this->inputBuffer[0])) { + $written = fwrite($stdin, $this->inputBuffer); + $this->inputBuffer = substr($this->inputBuffer, $written); + if (isset($this->inputBuffer[0])) { + return [$this->pipes[0]]; + } + } + + if ($input) { + for (;;) { + $data = fread($input, self::CHUNK_SIZE); + if (!isset($data[0])) { + break; + } + $written = fwrite($stdin, $data); + $data = substr($data, $written); + if (isset($data[0])) { + $this->inputBuffer = $data; + + return [$this->pipes[0]]; + } + } + if (feof($input)) { + if ($this->input instanceof \Iterator) { + $this->input->next(); + } else { + $this->input = null; + } + } + } + } + + // no input to read on resource, buffer is empty + if (!isset($this->inputBuffer[0]) && !($this->input instanceof \Iterator ? $this->input->valid() : $this->input)) { + $this->input = null; + fclose($this->pipes[0]); + unset($this->pipes[0]); + } elseif (!$w) { + return [$this->pipes[0]]; + } + + return null; + } + + /** + * @internal + */ + public function handleError(int $type, string $msg) + { + $this->lastError = $msg; + } +} diff --git a/vendor/symfony/process/Pipes/PipesInterface.php b/vendor/symfony/process/Pipes/PipesInterface.php new file mode 100644 index 0000000..50eb5c4 --- /dev/null +++ b/vendor/symfony/process/Pipes/PipesInterface.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Pipes; + +/** + * PipesInterface manages descriptors and pipes for the use of proc_open. + * + * @author Romain Neutron + * + * @internal + */ +interface PipesInterface +{ + public const CHUNK_SIZE = 16384; + + /** + * Returns an array of descriptors for the use of proc_open. + */ + public function getDescriptors(): array; + + /** + * Returns an array of filenames indexed by their related stream in case these pipes use temporary files. + * + * @return string[] + */ + public function getFiles(): array; + + /** + * Reads data in file handles and pipes. + * + * @param bool $blocking Whether to use blocking calls or not + * @param bool $close Whether to close pipes if they've reached EOF + * + * @return string[] An array of read data indexed by their fd + */ + public function readAndWrite(bool $blocking, bool $close = false): array; + + /** + * Returns if the current state has open file handles or pipes. + */ + public function areOpen(): bool; + + /** + * Returns if pipes are able to read output. + */ + public function haveReadSupport(): bool; + + /** + * Closes file handles and pipes. + */ + public function close(); +} diff --git a/vendor/symfony/process/Pipes/UnixPipes.php b/vendor/symfony/process/Pipes/UnixPipes.php new file mode 100644 index 0000000..58a8da0 --- /dev/null +++ b/vendor/symfony/process/Pipes/UnixPipes.php @@ -0,0 +1,166 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Pipes; + +use Symfony\Component\Process\Process; + +/** + * UnixPipes implementation uses unix pipes as handles. + * + * @author Romain Neutron + * + * @internal + */ +class UnixPipes extends AbstractPipes +{ + private $ttyMode; + private $ptyMode; + private $haveReadSupport; + + public function __construct(?bool $ttyMode, bool $ptyMode, $input, bool $haveReadSupport) + { + $this->ttyMode = $ttyMode; + $this->ptyMode = $ptyMode; + $this->haveReadSupport = $haveReadSupport; + + parent::__construct($input); + } + + /** + * @return array + */ + public function __sleep() + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + $this->close(); + } + + /** + * {@inheritdoc} + */ + public function getDescriptors(): array + { + if (!$this->haveReadSupport) { + $nullstream = fopen('/dev/null', 'c'); + + return [ + ['pipe', 'r'], + $nullstream, + $nullstream, + ]; + } + + if ($this->ttyMode) { + return [ + ['file', '/dev/tty', 'r'], + ['file', '/dev/tty', 'w'], + ['file', '/dev/tty', 'w'], + ]; + } + + if ($this->ptyMode && Process::isPtySupported()) { + return [ + ['pty'], + ['pty'], + ['pty'], + ]; + } + + return [ + ['pipe', 'r'], + ['pipe', 'w'], // stdout + ['pipe', 'w'], // stderr + ]; + } + + /** + * {@inheritdoc} + */ + public function getFiles(): array + { + return []; + } + + /** + * {@inheritdoc} + */ + public function readAndWrite(bool $blocking, bool $close = false): array + { + $this->unblock(); + $w = $this->write(); + + $read = $e = []; + $r = $this->pipes; + unset($r[0]); + + // let's have a look if something changed in streams + set_error_handler([$this, 'handleError']); + if (($r || $w) && false === stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) { + restore_error_handler(); + // if a system call has been interrupted, forget about it, let's try again + // otherwise, an error occurred, let's reset pipes + if (!$this->hasSystemCallBeenInterrupted()) { + $this->pipes = []; + } + + return $read; + } + restore_error_handler(); + + foreach ($r as $pipe) { + // prior PHP 5.4 the array passed to stream_select is modified and + // lose key association, we have to find back the key + $read[$type = array_search($pipe, $this->pipes, true)] = ''; + + do { + $data = @fread($pipe, self::CHUNK_SIZE); + $read[$type] .= $data; + } while (isset($data[0]) && ($close || isset($data[self::CHUNK_SIZE - 1]))); + + if (!isset($read[$type][0])) { + unset($read[$type]); + } + + if ($close && feof($pipe)) { + fclose($pipe); + unset($this->pipes[$type]); + } + } + + return $read; + } + + /** + * {@inheritdoc} + */ + public function haveReadSupport(): bool + { + return $this->haveReadSupport; + } + + /** + * {@inheritdoc} + */ + public function areOpen(): bool + { + return (bool) $this->pipes; + } +} diff --git a/vendor/symfony/process/Pipes/WindowsPipes.php b/vendor/symfony/process/Pipes/WindowsPipes.php new file mode 100644 index 0000000..69768f3 --- /dev/null +++ b/vendor/symfony/process/Pipes/WindowsPipes.php @@ -0,0 +1,207 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Pipes; + +use Symfony\Component\Process\Exception\RuntimeException; +use Symfony\Component\Process\Process; + +/** + * WindowsPipes implementation uses temporary files as handles. + * + * @see https://bugs.php.net/51800 + * @see https://bugs.php.net/65650 + * + * @author Romain Neutron + * + * @internal + */ +class WindowsPipes extends AbstractPipes +{ + private $files = []; + private $fileHandles = []; + private $lockHandles = []; + private $readBytes = [ + Process::STDOUT => 0, + Process::STDERR => 0, + ]; + private $haveReadSupport; + + public function __construct($input, bool $haveReadSupport) + { + $this->haveReadSupport = $haveReadSupport; + + if ($this->haveReadSupport) { + // Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big. + // Workaround for this problem is to use temporary files instead of pipes on Windows platform. + // + // @see https://bugs.php.net/51800 + $pipes = [ + Process::STDOUT => Process::OUT, + Process::STDERR => Process::ERR, + ]; + $tmpDir = sys_get_temp_dir(); + $lastError = 'unknown reason'; + set_error_handler(function ($type, $msg) use (&$lastError) { $lastError = $msg; }); + for ($i = 0;; ++$i) { + foreach ($pipes as $pipe => $name) { + $file = sprintf('%s\\sf_proc_%02X.%s', $tmpDir, $i, $name); + + if (!$h = fopen($file.'.lock', 'w')) { + if (file_exists($file.'.lock')) { + continue 2; + } + restore_error_handler(); + throw new RuntimeException('A temporary file could not be opened to write the process output: '.$lastError); + } + if (!flock($h, \LOCK_EX | \LOCK_NB)) { + continue 2; + } + if (isset($this->lockHandles[$pipe])) { + flock($this->lockHandles[$pipe], \LOCK_UN); + fclose($this->lockHandles[$pipe]); + } + $this->lockHandles[$pipe] = $h; + + if (!($h = fopen($file, 'w')) || !fclose($h) || !$h = fopen($file, 'r')) { + flock($this->lockHandles[$pipe], \LOCK_UN); + fclose($this->lockHandles[$pipe]); + unset($this->lockHandles[$pipe]); + continue 2; + } + $this->fileHandles[$pipe] = $h; + $this->files[$pipe] = $file; + } + break; + } + restore_error_handler(); + } + + parent::__construct($input); + } + + /** + * @return array + */ + public function __sleep() + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + $this->close(); + } + + /** + * {@inheritdoc} + */ + public function getDescriptors(): array + { + if (!$this->haveReadSupport) { + $nullstream = fopen('NUL', 'c'); + + return [ + ['pipe', 'r'], + $nullstream, + $nullstream, + ]; + } + + // We're not using pipe on Windows platform as it hangs (https://bugs.php.net/51800) + // We're not using file handles as it can produce corrupted output https://bugs.php.net/65650 + // So we redirect output within the commandline and pass the nul device to the process + return [ + ['pipe', 'r'], + ['file', 'NUL', 'w'], + ['file', 'NUL', 'w'], + ]; + } + + /** + * {@inheritdoc} + */ + public function getFiles(): array + { + return $this->files; + } + + /** + * {@inheritdoc} + */ + public function readAndWrite(bool $blocking, bool $close = false): array + { + $this->unblock(); + $w = $this->write(); + $read = $r = $e = []; + + if ($blocking) { + if ($w) { + @stream_select($r, $w, $e, 0, Process::TIMEOUT_PRECISION * 1E6); + } elseif ($this->fileHandles) { + usleep(Process::TIMEOUT_PRECISION * 1E6); + } + } + foreach ($this->fileHandles as $type => $fileHandle) { + $data = stream_get_contents($fileHandle, -1, $this->readBytes[$type]); + + if (isset($data[0])) { + $this->readBytes[$type] += \strlen($data); + $read[$type] = $data; + } + if ($close) { + ftruncate($fileHandle, 0); + fclose($fileHandle); + flock($this->lockHandles[$type], \LOCK_UN); + fclose($this->lockHandles[$type]); + unset($this->fileHandles[$type], $this->lockHandles[$type]); + } + } + + return $read; + } + + /** + * {@inheritdoc} + */ + public function haveReadSupport(): bool + { + return $this->haveReadSupport; + } + + /** + * {@inheritdoc} + */ + public function areOpen(): bool + { + return $this->pipes && $this->fileHandles; + } + + /** + * {@inheritdoc} + */ + public function close() + { + parent::close(); + foreach ($this->fileHandles as $type => $handle) { + ftruncate($handle, 0); + fclose($handle); + flock($this->lockHandles[$type], \LOCK_UN); + fclose($this->lockHandles[$type]); + } + $this->fileHandles = $this->lockHandles = []; + } +} diff --git a/vendor/symfony/process/Process.php b/vendor/symfony/process/Process.php new file mode 100644 index 0000000..d5c697c --- /dev/null +++ b/vendor/symfony/process/Process.php @@ -0,0 +1,1666 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +use Symfony\Component\Process\Exception\InvalidArgumentException; +use Symfony\Component\Process\Exception\LogicException; +use Symfony\Component\Process\Exception\ProcessFailedException; +use Symfony\Component\Process\Exception\ProcessSignaledException; +use Symfony\Component\Process\Exception\ProcessTimedOutException; +use Symfony\Component\Process\Exception\RuntimeException; +use Symfony\Component\Process\Pipes\PipesInterface; +use Symfony\Component\Process\Pipes\UnixPipes; +use Symfony\Component\Process\Pipes\WindowsPipes; + +/** + * Process is a thin wrapper around proc_* functions to easily + * start independent PHP processes. + * + * @author Fabien Potencier + * @author Romain Neutron + */ +class Process implements \IteratorAggregate +{ + public const ERR = 'err'; + public const OUT = 'out'; + + public const STATUS_READY = 'ready'; + public const STATUS_STARTED = 'started'; + public const STATUS_TERMINATED = 'terminated'; + + public const STDIN = 0; + public const STDOUT = 1; + public const STDERR = 2; + + // Timeout Precision in seconds. + public const TIMEOUT_PRECISION = 0.2; + + public const ITER_NON_BLOCKING = 1; // By default, iterating over outputs is a blocking call, use this flag to make it non-blocking + public const ITER_KEEP_OUTPUT = 2; // By default, outputs are cleared while iterating, use this flag to keep them in memory + public const ITER_SKIP_OUT = 4; // Use this flag to skip STDOUT while iterating + public const ITER_SKIP_ERR = 8; // Use this flag to skip STDERR while iterating + + private $callback; + private $hasCallback = false; + private $commandline; + private $cwd; + private $env; + private $input; + private $starttime; + private $lastOutputTime; + private $timeout; + private $idleTimeout; + private $exitcode; + private $fallbackStatus = []; + private $processInformation; + private $outputDisabled = false; + private $stdout; + private $stderr; + private $process; + private $status = self::STATUS_READY; + private $incrementalOutputOffset = 0; + private $incrementalErrorOutputOffset = 0; + private $tty = false; + private $pty; + + private $useFileHandles = false; + /** @var PipesInterface */ + private $processPipes; + + private $latestSignal; + + private static $sigchild; + + /** + * Exit codes translation table. + * + * User-defined errors must use exit codes in the 64-113 range. + */ + public static $exitCodes = [ + 0 => 'OK', + 1 => 'General error', + 2 => 'Misuse of shell builtins', + + 126 => 'Invoked command cannot execute', + 127 => 'Command not found', + 128 => 'Invalid exit argument', + + // signals + 129 => 'Hangup', + 130 => 'Interrupt', + 131 => 'Quit and dump core', + 132 => 'Illegal instruction', + 133 => 'Trace/breakpoint trap', + 134 => 'Process aborted', + 135 => 'Bus error: "access to undefined portion of memory object"', + 136 => 'Floating point exception: "erroneous arithmetic operation"', + 137 => 'Kill (terminate immediately)', + 138 => 'User-defined 1', + 139 => 'Segmentation violation', + 140 => 'User-defined 2', + 141 => 'Write to pipe with no one reading', + 142 => 'Signal raised by alarm', + 143 => 'Termination (request to terminate)', + // 144 - not defined + 145 => 'Child process terminated, stopped (or continued*)', + 146 => 'Continue if stopped', + 147 => 'Stop executing temporarily', + 148 => 'Terminal stop signal', + 149 => 'Background process attempting to read from tty ("in")', + 150 => 'Background process attempting to write to tty ("out")', + 151 => 'Urgent data available on socket', + 152 => 'CPU time limit exceeded', + 153 => 'File size limit exceeded', + 154 => 'Signal raised by timer counting virtual time: "virtual timer expired"', + 155 => 'Profiling timer expired', + // 156 - not defined + 157 => 'Pollable event', + // 158 - not defined + 159 => 'Bad syscall', + ]; + + /** + * @param array $command The command to run and its arguments listed as separate entries + * @param string|null $cwd The working directory or null to use the working dir of the current PHP process + * @param array|null $env The environment variables or null to use the same environment as the current PHP process + * @param mixed $input The input as stream resource, scalar or \Traversable, or null for no input + * @param int|float|null $timeout The timeout in seconds or null to disable + * + * @throws LogicException When proc_open is not installed + */ + public function __construct($command, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60) + { + if (!\function_exists('proc_open')) { + throw new LogicException('The Process class relies on proc_open, which is not available on your PHP installation.'); + } + + if (!\is_array($command)) { + @trigger_error(sprintf('Passing a command as string when creating a "%s" instance is deprecated since Symfony 4.2, pass it as an array of its arguments instead, or use the "Process::fromShellCommandline()" constructor if you need features provided by the shell.', __CLASS__), \E_USER_DEPRECATED); + } + + $this->commandline = $command; + $this->cwd = $cwd; + + // on Windows, if the cwd changed via chdir(), proc_open defaults to the dir where PHP was started + // on Gnu/Linux, PHP builds with --enable-maintainer-zts are also affected + // @see : https://bugs.php.net/51800 + // @see : https://bugs.php.net/50524 + if (null === $this->cwd && (\defined('ZEND_THREAD_SAFE') || '\\' === \DIRECTORY_SEPARATOR)) { + $this->cwd = getcwd(); + } + if (null !== $env) { + $this->setEnv($env); + } + + $this->setInput($input); + $this->setTimeout($timeout); + $this->useFileHandles = '\\' === \DIRECTORY_SEPARATOR; + $this->pty = false; + } + + /** + * Creates a Process instance as a command-line to be run in a shell wrapper. + * + * Command-lines are parsed by the shell of your OS (/bin/sh on Unix-like, cmd.exe on Windows.) + * This allows using e.g. pipes or conditional execution. In this mode, signals are sent to the + * shell wrapper and not to your commands. + * + * In order to inject dynamic values into command-lines, we strongly recommend using placeholders. + * This will save escaping values, which is not portable nor secure anyway: + * + * $process = Process::fromShellCommandline('my_command "$MY_VAR"'); + * $process->run(null, ['MY_VAR' => $theValue]); + * + * @param string $command The command line to pass to the shell of the OS + * @param string|null $cwd The working directory or null to use the working dir of the current PHP process + * @param array|null $env The environment variables or null to use the same environment as the current PHP process + * @param mixed $input The input as stream resource, scalar or \Traversable, or null for no input + * @param int|float|null $timeout The timeout in seconds or null to disable + * + * @return static + * + * @throws LogicException When proc_open is not installed + */ + public static function fromShellCommandline(string $command, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60) + { + $process = new static([], $cwd, $env, $input, $timeout); + $process->commandline = $command; + + return $process; + } + + /** + * @return array + */ + public function __sleep() + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + $this->stop(0); + } + + public function __clone() + { + $this->resetProcessData(); + } + + /** + * Runs the process. + * + * The callback receives the type of output (out or err) and + * some bytes from the output in real-time. It allows to have feedback + * from the independent process during execution. + * + * The STDOUT and STDERR are also available after the process is finished + * via the getOutput() and getErrorOutput() methods. + * + * @param callable|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * + * @return int The exit status code + * + * @throws RuntimeException When process can't be launched + * @throws RuntimeException When process is already running + * @throws ProcessTimedOutException When process timed out + * @throws ProcessSignaledException When process stopped after receiving signal + * @throws LogicException In case a callback is provided and output has been disabled + * + * @final + */ + public function run(callable $callback = null, array $env = []): int + { + $this->start($callback, $env); + + return $this->wait(); + } + + /** + * Runs the process. + * + * This is identical to run() except that an exception is thrown if the process + * exits with a non-zero exit code. + * + * @return $this + * + * @throws ProcessFailedException if the process didn't terminate successfully + * + * @final + */ + public function mustRun(callable $callback = null, array $env = []): self + { + if (0 !== $this->run($callback, $env)) { + throw new ProcessFailedException($this); + } + + return $this; + } + + /** + * Starts the process and returns after writing the input to STDIN. + * + * This method blocks until all STDIN data is sent to the process then it + * returns while the process runs in the background. + * + * The termination of the process can be awaited with wait(). + * + * The callback receives the type of output (out or err) and some bytes from + * the output in real-time while writing the standard input to the process. + * It allows to have feedback from the independent process during execution. + * + * @param callable|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * + * @throws RuntimeException When process can't be launched + * @throws RuntimeException When process is already running + * @throws LogicException In case a callback is provided and output has been disabled + */ + public function start(callable $callback = null, array $env = []) + { + if ($this->isRunning()) { + throw new RuntimeException('Process is already running.'); + } + + $this->resetProcessData(); + $this->starttime = $this->lastOutputTime = microtime(true); + $this->callback = $this->buildCallback($callback); + $this->hasCallback = null !== $callback; + $descriptors = $this->getDescriptors(); + + if ($this->env) { + $env += $this->env; + } + + $env += $this->getDefaultEnv(); + + if (\is_array($commandline = $this->commandline)) { + $commandline = implode(' ', array_map([$this, 'escapeArgument'], $commandline)); + + if ('\\' !== \DIRECTORY_SEPARATOR) { + // exec is mandatory to deal with sending a signal to the process + $commandline = 'exec '.$commandline; + } + } else { + $commandline = $this->replacePlaceholders($commandline, $env); + } + + $options = ['suppress_errors' => true]; + + if ('\\' === \DIRECTORY_SEPARATOR) { + $options['bypass_shell'] = true; + $commandline = $this->prepareWindowsCommandLine($commandline, $env); + } elseif (!$this->useFileHandles && $this->isSigchildEnabled()) { + // last exit code is output on the fourth pipe and caught to work around --enable-sigchild + $descriptors[3] = ['pipe', 'w']; + + // See https://unix.stackexchange.com/questions/71205/background-process-pipe-input + $commandline = '{ ('.$commandline.') <&3 3<&- 3>/dev/null & } 3<&0;'; + $commandline .= 'pid=$!; echo $pid >&3; wait $pid; code=$?; echo $code >&3; exit $code'; + + // Workaround for the bug, when PTS functionality is enabled. + // @see : https://bugs.php.net/69442 + $ptsWorkaround = fopen(__FILE__, 'r'); + } + + $envPairs = []; + foreach ($env as $k => $v) { + if (false !== $v && 'argc' !== $k && 'argv' !== $k) { + $envPairs[] = $k.'='.$v; + } + } + + if (!is_dir($this->cwd)) { + throw new RuntimeException(sprintf('The provided cwd "%s" does not exist.', $this->cwd)); + } + + $this->process = @proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $envPairs, $options); + + if (!\is_resource($this->process)) { + throw new RuntimeException('Unable to launch a new process.'); + } + $this->status = self::STATUS_STARTED; + + if (isset($descriptors[3])) { + $this->fallbackStatus['pid'] = (int) fgets($this->processPipes->pipes[3]); + } + + if ($this->tty) { + return; + } + + $this->updateStatus(false); + $this->checkTimeout(); + } + + /** + * Restarts the process. + * + * Be warned that the process is cloned before being started. + * + * @param callable|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * + * @return static + * + * @throws RuntimeException When process can't be launched + * @throws RuntimeException When process is already running + * + * @see start() + * + * @final + */ + public function restart(callable $callback = null, array $env = []): self + { + if ($this->isRunning()) { + throw new RuntimeException('Process is already running.'); + } + + $process = clone $this; + $process->start($callback, $env); + + return $process; + } + + /** + * Waits for the process to terminate. + * + * The callback receives the type of output (out or err) and some bytes + * from the output in real-time while writing the standard input to the process. + * It allows to have feedback from the independent process during execution. + * + * @param callable|null $callback A valid PHP callback + * + * @return int The exitcode of the process + * + * @throws ProcessTimedOutException When process timed out + * @throws ProcessSignaledException When process stopped after receiving signal + * @throws LogicException When process is not yet started + */ + public function wait(callable $callback = null) + { + $this->requireProcessIsStarted(__FUNCTION__); + + $this->updateStatus(false); + + if (null !== $callback) { + if (!$this->processPipes->haveReadSupport()) { + $this->stop(0); + throw new LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::wait".'); + } + $this->callback = $this->buildCallback($callback); + } + + do { + $this->checkTimeout(); + $running = '\\' === \DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen(); + $this->readPipes($running, '\\' !== \DIRECTORY_SEPARATOR || !$running); + } while ($running); + + while ($this->isRunning()) { + $this->checkTimeout(); + usleep(1000); + } + + if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) { + throw new ProcessSignaledException($this); + } + + return $this->exitcode; + } + + /** + * Waits until the callback returns true. + * + * The callback receives the type of output (out or err) and some bytes + * from the output in real-time while writing the standard input to the process. + * It allows to have feedback from the independent process during execution. + * + * @throws RuntimeException When process timed out + * @throws LogicException When process is not yet started + * @throws ProcessTimedOutException In case the timeout was reached + */ + public function waitUntil(callable $callback): bool + { + $this->requireProcessIsStarted(__FUNCTION__); + $this->updateStatus(false); + + if (!$this->processPipes->haveReadSupport()) { + $this->stop(0); + throw new LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::waitUntil".'); + } + $callback = $this->buildCallback($callback); + + $ready = false; + while (true) { + $this->checkTimeout(); + $running = '\\' === \DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen(); + $output = $this->processPipes->readAndWrite($running, '\\' !== \DIRECTORY_SEPARATOR || !$running); + + foreach ($output as $type => $data) { + if (3 !== $type) { + $ready = $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data) || $ready; + } elseif (!isset($this->fallbackStatus['signaled'])) { + $this->fallbackStatus['exitcode'] = (int) $data; + } + } + if ($ready) { + return true; + } + if (!$running) { + return false; + } + + usleep(1000); + } + } + + /** + * Returns the Pid (process identifier), if applicable. + * + * @return int|null The process id if running, null otherwise + */ + public function getPid() + { + return $this->isRunning() ? $this->processInformation['pid'] : null; + } + + /** + * Sends a POSIX signal to the process. + * + * @param int $signal A valid POSIX signal (see https://php.net/pcntl.constants) + * + * @return $this + * + * @throws LogicException In case the process is not running + * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed + * @throws RuntimeException In case of failure + */ + public function signal($signal) + { + $this->doSignal($signal, true); + + return $this; + } + + /** + * Disables fetching output and error output from the underlying process. + * + * @return $this + * + * @throws RuntimeException In case the process is already running + * @throws LogicException if an idle timeout is set + */ + public function disableOutput() + { + if ($this->isRunning()) { + throw new RuntimeException('Disabling output while the process is running is not possible.'); + } + if (null !== $this->idleTimeout) { + throw new LogicException('Output can not be disabled while an idle timeout is set.'); + } + + $this->outputDisabled = true; + + return $this; + } + + /** + * Enables fetching output and error output from the underlying process. + * + * @return $this + * + * @throws RuntimeException In case the process is already running + */ + public function enableOutput() + { + if ($this->isRunning()) { + throw new RuntimeException('Enabling output while the process is running is not possible.'); + } + + $this->outputDisabled = false; + + return $this; + } + + /** + * Returns true in case the output is disabled, false otherwise. + * + * @return bool + */ + public function isOutputDisabled() + { + return $this->outputDisabled; + } + + /** + * Returns the current output of the process (STDOUT). + * + * @return string The process output + * + * @throws LogicException in case the output has been disabled + * @throws LogicException In case the process is not started + */ + public function getOutput() + { + $this->readPipesForOutput(__FUNCTION__); + + if (false === $ret = stream_get_contents($this->stdout, -1, 0)) { + return ''; + } + + return $ret; + } + + /** + * Returns the output incrementally. + * + * In comparison with the getOutput method which always return the whole + * output, this one returns the new output since the last call. + * + * @return string The process output since the last call + * + * @throws LogicException in case the output has been disabled + * @throws LogicException In case the process is not started + */ + public function getIncrementalOutput() + { + $this->readPipesForOutput(__FUNCTION__); + + $latest = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset); + $this->incrementalOutputOffset = ftell($this->stdout); + + if (false === $latest) { + return ''; + } + + return $latest; + } + + /** + * Returns an iterator to the output of the process, with the output type as keys (Process::OUT/ERR). + * + * @param int $flags A bit field of Process::ITER_* flags + * + * @throws LogicException in case the output has been disabled + * @throws LogicException In case the process is not started + * + * @return \Generator + */ + #[\ReturnTypeWillChange] + public function getIterator($flags = 0) + { + $this->readPipesForOutput(__FUNCTION__, false); + + $clearOutput = !(self::ITER_KEEP_OUTPUT & $flags); + $blocking = !(self::ITER_NON_BLOCKING & $flags); + $yieldOut = !(self::ITER_SKIP_OUT & $flags); + $yieldErr = !(self::ITER_SKIP_ERR & $flags); + + while (null !== $this->callback || ($yieldOut && !feof($this->stdout)) || ($yieldErr && !feof($this->stderr))) { + if ($yieldOut) { + $out = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset); + + if (isset($out[0])) { + if ($clearOutput) { + $this->clearOutput(); + } else { + $this->incrementalOutputOffset = ftell($this->stdout); + } + + yield self::OUT => $out; + } + } + + if ($yieldErr) { + $err = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset); + + if (isset($err[0])) { + if ($clearOutput) { + $this->clearErrorOutput(); + } else { + $this->incrementalErrorOutputOffset = ftell($this->stderr); + } + + yield self::ERR => $err; + } + } + + if (!$blocking && !isset($out[0]) && !isset($err[0])) { + yield self::OUT => ''; + } + + $this->checkTimeout(); + $this->readPipesForOutput(__FUNCTION__, $blocking); + } + } + + /** + * Clears the process output. + * + * @return $this + */ + public function clearOutput() + { + ftruncate($this->stdout, 0); + fseek($this->stdout, 0); + $this->incrementalOutputOffset = 0; + + return $this; + } + + /** + * Returns the current error output of the process (STDERR). + * + * @return string The process error output + * + * @throws LogicException in case the output has been disabled + * @throws LogicException In case the process is not started + */ + public function getErrorOutput() + { + $this->readPipesForOutput(__FUNCTION__); + + if (false === $ret = stream_get_contents($this->stderr, -1, 0)) { + return ''; + } + + return $ret; + } + + /** + * Returns the errorOutput incrementally. + * + * In comparison with the getErrorOutput method which always return the + * whole error output, this one returns the new error output since the last + * call. + * + * @return string The process error output since the last call + * + * @throws LogicException in case the output has been disabled + * @throws LogicException In case the process is not started + */ + public function getIncrementalErrorOutput() + { + $this->readPipesForOutput(__FUNCTION__); + + $latest = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset); + $this->incrementalErrorOutputOffset = ftell($this->stderr); + + if (false === $latest) { + return ''; + } + + return $latest; + } + + /** + * Clears the process output. + * + * @return $this + */ + public function clearErrorOutput() + { + ftruncate($this->stderr, 0); + fseek($this->stderr, 0); + $this->incrementalErrorOutputOffset = 0; + + return $this; + } + + /** + * Returns the exit code returned by the process. + * + * @return int|null The exit status code, null if the Process is not terminated + */ + public function getExitCode() + { + $this->updateStatus(false); + + return $this->exitcode; + } + + /** + * Returns a string representation for the exit code returned by the process. + * + * This method relies on the Unix exit code status standardization + * and might not be relevant for other operating systems. + * + * @return string|null A string representation for the exit status code, null if the Process is not terminated + * + * @see http://tldp.org/LDP/abs/html/exitcodes.html + * @see http://en.wikipedia.org/wiki/Unix_signal + */ + public function getExitCodeText() + { + if (null === $exitcode = $this->getExitCode()) { + return null; + } + + return self::$exitCodes[$exitcode] ?? 'Unknown error'; + } + + /** + * Checks if the process ended successfully. + * + * @return bool true if the process ended successfully, false otherwise + */ + public function isSuccessful() + { + return 0 === $this->getExitCode(); + } + + /** + * Returns true if the child process has been terminated by an uncaught signal. + * + * It always returns false on Windows. + * + * @return bool + * + * @throws LogicException In case the process is not terminated + */ + public function hasBeenSignaled() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + return $this->processInformation['signaled']; + } + + /** + * Returns the number of the signal that caused the child process to terminate its execution. + * + * It is only meaningful if hasBeenSignaled() returns true. + * + * @return int + * + * @throws RuntimeException In case --enable-sigchild is activated + * @throws LogicException In case the process is not terminated + */ + public function getTermSignal() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + if ($this->isSigchildEnabled() && -1 === $this->processInformation['termsig']) { + throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.'); + } + + return $this->processInformation['termsig']; + } + + /** + * Returns true if the child process has been stopped by a signal. + * + * It always returns false on Windows. + * + * @return bool + * + * @throws LogicException In case the process is not terminated + */ + public function hasBeenStopped() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + return $this->processInformation['stopped']; + } + + /** + * Returns the number of the signal that caused the child process to stop its execution. + * + * It is only meaningful if hasBeenStopped() returns true. + * + * @return int + * + * @throws LogicException In case the process is not terminated + */ + public function getStopSignal() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + return $this->processInformation['stopsig']; + } + + /** + * Checks if the process is currently running. + * + * @return bool true if the process is currently running, false otherwise + */ + public function isRunning() + { + if (self::STATUS_STARTED !== $this->status) { + return false; + } + + $this->updateStatus(false); + + return $this->processInformation['running']; + } + + /** + * Checks if the process has been started with no regard to the current state. + * + * @return bool true if status is ready, false otherwise + */ + public function isStarted() + { + return self::STATUS_READY != $this->status; + } + + /** + * Checks if the process is terminated. + * + * @return bool true if process is terminated, false otherwise + */ + public function isTerminated() + { + $this->updateStatus(false); + + return self::STATUS_TERMINATED == $this->status; + } + + /** + * Gets the process status. + * + * The status is one of: ready, started, terminated. + * + * @return string The current process status + */ + public function getStatus() + { + $this->updateStatus(false); + + return $this->status; + } + + /** + * Stops the process. + * + * @param int|float $timeout The timeout in seconds + * @param int $signal A POSIX signal to send in case the process has not stop at timeout, default is SIGKILL (9) + * + * @return int|null The exit-code of the process or null if it's not running + */ + public function stop($timeout = 10, $signal = null) + { + $timeoutMicro = microtime(true) + $timeout; + if ($this->isRunning()) { + // given SIGTERM may not be defined and that "proc_terminate" uses the constant value and not the constant itself, we use the same here + $this->doSignal(15, false); + do { + usleep(1000); + } while ($this->isRunning() && microtime(true) < $timeoutMicro); + + if ($this->isRunning()) { + // Avoid exception here: process is supposed to be running, but it might have stopped just + // after this line. In any case, let's silently discard the error, we cannot do anything. + $this->doSignal($signal ?: 9, false); + } + } + + if ($this->isRunning()) { + if (isset($this->fallbackStatus['pid'])) { + unset($this->fallbackStatus['pid']); + + return $this->stop(0, $signal); + } + $this->close(); + } + + return $this->exitcode; + } + + /** + * Adds a line to the STDOUT stream. + * + * @internal + */ + public function addOutput(string $line) + { + $this->lastOutputTime = microtime(true); + + fseek($this->stdout, 0, \SEEK_END); + fwrite($this->stdout, $line); + fseek($this->stdout, $this->incrementalOutputOffset); + } + + /** + * Adds a line to the STDERR stream. + * + * @internal + */ + public function addErrorOutput(string $line) + { + $this->lastOutputTime = microtime(true); + + fseek($this->stderr, 0, \SEEK_END); + fwrite($this->stderr, $line); + fseek($this->stderr, $this->incrementalErrorOutputOffset); + } + + /** + * Gets the last output time in seconds. + * + * @return float|null The last output time in seconds or null if it isn't started + */ + public function getLastOutputTime(): ?float + { + return $this->lastOutputTime; + } + + /** + * Gets the command line to be executed. + * + * @return string The command to execute + */ + public function getCommandLine() + { + return \is_array($this->commandline) ? implode(' ', array_map([$this, 'escapeArgument'], $this->commandline)) : $this->commandline; + } + + /** + * Sets the command line to be executed. + * + * @param string|array $commandline The command to execute + * + * @return $this + * + * @deprecated since Symfony 4.2. + */ + public function setCommandLine($commandline) + { + @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2.', __METHOD__), \E_USER_DEPRECATED); + + $this->commandline = $commandline; + + return $this; + } + + /** + * Gets the process timeout (max. runtime). + * + * @return float|null The timeout in seconds or null if it's disabled + */ + public function getTimeout() + { + return $this->timeout; + } + + /** + * Gets the process idle timeout (max. time since last output). + * + * @return float|null The timeout in seconds or null if it's disabled + */ + public function getIdleTimeout() + { + return $this->idleTimeout; + } + + /** + * Sets the process timeout (max. runtime) in seconds. + * + * To disable the timeout, set this value to null. + * + * @param int|float|null $timeout The timeout in seconds + * + * @return $this + * + * @throws InvalidArgumentException if the timeout is negative + */ + public function setTimeout($timeout) + { + $this->timeout = $this->validateTimeout($timeout); + + return $this; + } + + /** + * Sets the process idle timeout (max. time since last output). + * + * To disable the timeout, set this value to null. + * + * @param int|float|null $timeout The timeout in seconds + * + * @return $this + * + * @throws LogicException if the output is disabled + * @throws InvalidArgumentException if the timeout is negative + */ + public function setIdleTimeout($timeout) + { + if (null !== $timeout && $this->outputDisabled) { + throw new LogicException('Idle timeout can not be set while the output is disabled.'); + } + + $this->idleTimeout = $this->validateTimeout($timeout); + + return $this; + } + + /** + * Enables or disables the TTY mode. + * + * @param bool $tty True to enabled and false to disable + * + * @return $this + * + * @throws RuntimeException In case the TTY mode is not supported + */ + public function setTty($tty) + { + if ('\\' === \DIRECTORY_SEPARATOR && $tty) { + throw new RuntimeException('TTY mode is not supported on Windows platform.'); + } + + if ($tty && !self::isTtySupported()) { + throw new RuntimeException('TTY mode requires /dev/tty to be read/writable.'); + } + + $this->tty = (bool) $tty; + + return $this; + } + + /** + * Checks if the TTY mode is enabled. + * + * @return bool true if the TTY mode is enabled, false otherwise + */ + public function isTty() + { + return $this->tty; + } + + /** + * Sets PTY mode. + * + * @param bool $bool + * + * @return $this + */ + public function setPty($bool) + { + $this->pty = (bool) $bool; + + return $this; + } + + /** + * Returns PTY state. + * + * @return bool + */ + public function isPty() + { + return $this->pty; + } + + /** + * Gets the working directory. + * + * @return string|null The current working directory or null on failure + */ + public function getWorkingDirectory() + { + if (null === $this->cwd) { + // getcwd() will return false if any one of the parent directories does not have + // the readable or search mode set, even if the current directory does + return getcwd() ?: null; + } + + return $this->cwd; + } + + /** + * Sets the current working directory. + * + * @param string $cwd The new working directory + * + * @return $this + */ + public function setWorkingDirectory($cwd) + { + $this->cwd = $cwd; + + return $this; + } + + /** + * Gets the environment variables. + * + * @return array The current environment variables + */ + public function getEnv() + { + return $this->env; + } + + /** + * Sets the environment variables. + * + * @param array $env The new environment variables + * + * @return $this + */ + public function setEnv(array $env) + { + $this->env = $env; + + return $this; + } + + /** + * Gets the Process input. + * + * @return resource|string|\Iterator|null The Process input + */ + public function getInput() + { + return $this->input; + } + + /** + * Sets the input. + * + * This content will be passed to the underlying process standard input. + * + * @param string|int|float|bool|resource|\Traversable|null $input The content + * + * @return $this + * + * @throws LogicException In case the process is running + */ + public function setInput($input) + { + if ($this->isRunning()) { + throw new LogicException('Input can not be set while the process is running.'); + } + + $this->input = ProcessUtils::validateInput(__METHOD__, $input); + + return $this; + } + + /** + * Sets whether environment variables will be inherited or not. + * + * @param bool $inheritEnv + * + * @return $this + * + * @deprecated since Symfony 4.4, env variables are always inherited + */ + public function inheritEnvironmentVariables($inheritEnv = true) + { + @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.4, env variables are always inherited.', __METHOD__), \E_USER_DEPRECATED); + + if (!$inheritEnv) { + throw new InvalidArgumentException('Not inheriting environment variables is not supported.'); + } + + return $this; + } + + /** + * Performs a check between the timeout definition and the time the process started. + * + * In case you run a background process (with the start method), you should + * trigger this method regularly to ensure the process timeout + * + * @throws ProcessTimedOutException In case the timeout was reached + */ + public function checkTimeout() + { + if (self::STATUS_STARTED !== $this->status) { + return; + } + + if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) { + $this->stop(0); + + throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_GENERAL); + } + + if (null !== $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) { + $this->stop(0); + + throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_IDLE); + } + } + + /** + * Returns whether TTY is supported on the current operating system. + */ + public static function isTtySupported(): bool + { + static $isTtySupported; + + if (null === $isTtySupported) { + $isTtySupported = (bool) @proc_open('echo 1 >/dev/null', [['file', '/dev/tty', 'r'], ['file', '/dev/tty', 'w'], ['file', '/dev/tty', 'w']], $pipes); + } + + return $isTtySupported; + } + + /** + * Returns whether PTY is supported on the current operating system. + * + * @return bool + */ + public static function isPtySupported() + { + static $result; + + if (null !== $result) { + return $result; + } + + if ('\\' === \DIRECTORY_SEPARATOR) { + return $result = false; + } + + return $result = (bool) @proc_open('echo 1 >/dev/null', [['pty'], ['pty'], ['pty']], $pipes); + } + + /** + * Creates the descriptors needed by the proc_open. + */ + private function getDescriptors(): array + { + if ($this->input instanceof \Iterator) { + $this->input->rewind(); + } + if ('\\' === \DIRECTORY_SEPARATOR) { + $this->processPipes = new WindowsPipes($this->input, !$this->outputDisabled || $this->hasCallback); + } else { + $this->processPipes = new UnixPipes($this->isTty(), $this->isPty(), $this->input, !$this->outputDisabled || $this->hasCallback); + } + + return $this->processPipes->getDescriptors(); + } + + /** + * Builds up the callback used by wait(). + * + * The callbacks adds all occurred output to the specific buffer and calls + * the user callback (if present) with the received output. + * + * @param callable|null $callback The user defined PHP callback + * + * @return \Closure A PHP closure + */ + protected function buildCallback(callable $callback = null) + { + if ($this->outputDisabled) { + return function ($type, $data) use ($callback): bool { + return null !== $callback && $callback($type, $data); + }; + } + + $out = self::OUT; + + return function ($type, $data) use ($callback, $out): bool { + if ($out == $type) { + $this->addOutput($data); + } else { + $this->addErrorOutput($data); + } + + return null !== $callback && $callback($type, $data); + }; + } + + /** + * Updates the status of the process, reads pipes. + * + * @param bool $blocking Whether to use a blocking read call + */ + protected function updateStatus($blocking) + { + if (self::STATUS_STARTED !== $this->status) { + return; + } + + $this->processInformation = proc_get_status($this->process); + $running = $this->processInformation['running']; + + $this->readPipes($running && $blocking, '\\' !== \DIRECTORY_SEPARATOR || !$running); + + if ($this->fallbackStatus && $this->isSigchildEnabled()) { + $this->processInformation = $this->fallbackStatus + $this->processInformation; + } + + if (!$running) { + $this->close(); + } + } + + /** + * Returns whether PHP has been compiled with the '--enable-sigchild' option or not. + * + * @return bool + */ + protected function isSigchildEnabled() + { + if (null !== self::$sigchild) { + return self::$sigchild; + } + + if (!\function_exists('phpinfo')) { + return self::$sigchild = false; + } + + ob_start(); + phpinfo(\INFO_GENERAL); + + return self::$sigchild = str_contains(ob_get_clean(), '--enable-sigchild'); + } + + /** + * Reads pipes for the freshest output. + * + * @param string $caller The name of the method that needs fresh outputs + * @param bool $blocking Whether to use blocking calls or not + * + * @throws LogicException in case output has been disabled or process is not started + */ + private function readPipesForOutput(string $caller, bool $blocking = false) + { + if ($this->outputDisabled) { + throw new LogicException('Output has been disabled.'); + } + + $this->requireProcessIsStarted($caller); + + $this->updateStatus($blocking); + } + + /** + * Validates and returns the filtered timeout. + * + * @throws InvalidArgumentException if the given timeout is a negative number + */ + private function validateTimeout(?float $timeout): ?float + { + $timeout = (float) $timeout; + + if (0.0 === $timeout) { + $timeout = null; + } elseif ($timeout < 0) { + throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.'); + } + + return $timeout; + } + + /** + * Reads pipes, executes callback. + * + * @param bool $blocking Whether to use blocking calls or not + * @param bool $close Whether to close file handles or not + */ + private function readPipes(bool $blocking, bool $close) + { + $result = $this->processPipes->readAndWrite($blocking, $close); + + $callback = $this->callback; + foreach ($result as $type => $data) { + if (3 !== $type) { + $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data); + } elseif (!isset($this->fallbackStatus['signaled'])) { + $this->fallbackStatus['exitcode'] = (int) $data; + } + } + } + + /** + * Closes process resource, closes file handles, sets the exitcode. + * + * @return int The exitcode + */ + private function close(): int + { + $this->processPipes->close(); + if (\is_resource($this->process)) { + proc_close($this->process); + } + $this->exitcode = $this->processInformation['exitcode']; + $this->status = self::STATUS_TERMINATED; + + if (-1 === $this->exitcode) { + if ($this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) { + // if process has been signaled, no exitcode but a valid termsig, apply Unix convention + $this->exitcode = 128 + $this->processInformation['termsig']; + } elseif ($this->isSigchildEnabled()) { + $this->processInformation['signaled'] = true; + $this->processInformation['termsig'] = -1; + } + } + + // Free memory from self-reference callback created by buildCallback + // Doing so in other contexts like __destruct or by garbage collector is ineffective + // Now pipes are closed, so the callback is no longer necessary + $this->callback = null; + + return $this->exitcode; + } + + /** + * Resets data related to the latest run of the process. + */ + private function resetProcessData() + { + $this->starttime = null; + $this->callback = null; + $this->exitcode = null; + $this->fallbackStatus = []; + $this->processInformation = null; + $this->stdout = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+'); + $this->stderr = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+'); + $this->process = null; + $this->latestSignal = null; + $this->status = self::STATUS_READY; + $this->incrementalOutputOffset = 0; + $this->incrementalErrorOutputOffset = 0; + } + + /** + * Sends a POSIX signal to the process. + * + * @param int $signal A valid POSIX signal (see https://php.net/pcntl.constants) + * @param bool $throwException Whether to throw exception in case signal failed + * + * @return bool True if the signal was sent successfully, false otherwise + * + * @throws LogicException In case the process is not running + * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed + * @throws RuntimeException In case of failure + */ + private function doSignal(int $signal, bool $throwException): bool + { + if (null === $pid = $this->getPid()) { + if ($throwException) { + throw new LogicException('Can not send signal on a non running process.'); + } + + return false; + } + + if ('\\' === \DIRECTORY_SEPARATOR) { + exec(sprintf('taskkill /F /T /PID %d 2>&1', $pid), $output, $exitCode); + if ($exitCode && $this->isRunning()) { + if ($throwException) { + throw new RuntimeException(sprintf('Unable to kill the process (%s).', implode(' ', $output))); + } + + return false; + } + } else { + if (!$this->isSigchildEnabled()) { + $ok = @proc_terminate($this->process, $signal); + } elseif (\function_exists('posix_kill')) { + $ok = @posix_kill($pid, $signal); + } elseif ($ok = proc_open(sprintf('kill -%d %d', $signal, $pid), [2 => ['pipe', 'w']], $pipes)) { + $ok = false === fgets($pipes[2]); + } + if (!$ok) { + if ($throwException) { + throw new RuntimeException(sprintf('Error while sending signal "%s".', $signal)); + } + + return false; + } + } + + $this->latestSignal = $signal; + $this->fallbackStatus['signaled'] = true; + $this->fallbackStatus['exitcode'] = -1; + $this->fallbackStatus['termsig'] = $this->latestSignal; + + return true; + } + + private function prepareWindowsCommandLine(string $cmd, array &$env): string + { + $uid = uniqid('', true); + $varCount = 0; + $varCache = []; + $cmd = preg_replace_callback( + '/"(?:( + [^"%!^]*+ + (?: + (?: !LF! | "(?:\^[%!^])?+" ) + [^"%!^]*+ + )++ + ) | [^"]*+ )"/x', + function ($m) use (&$env, &$varCache, &$varCount, $uid) { + if (!isset($m[1])) { + return $m[0]; + } + if (isset($varCache[$m[0]])) { + return $varCache[$m[0]]; + } + if (str_contains($value = $m[1], "\0")) { + $value = str_replace("\0", '?', $value); + } + if (false === strpbrk($value, "\"%!\n")) { + return '"'.$value.'"'; + } + + $value = str_replace(['!LF!', '"^!"', '"^%"', '"^^"', '""'], ["\n", '!', '%', '^', '"'], $value); + $value = '"'.preg_replace('/(\\\\*)"/', '$1$1\\"', $value).'"'; + $var = $uid.++$varCount; + + $env[$var] = $value; + + return $varCache[$m[0]] = '!'.$var.'!'; + }, + $cmd + ); + + $cmd = 'cmd /V:ON /E:ON /D /C ('.str_replace("\n", ' ', $cmd).')'; + foreach ($this->processPipes->getFiles() as $offset => $filename) { + $cmd .= ' '.$offset.'>"'.$filename.'"'; + } + + return $cmd; + } + + /** + * Ensures the process is running or terminated, throws a LogicException if the process has a not started. + * + * @throws LogicException if the process has not run + */ + private function requireProcessIsStarted(string $functionName) + { + if (!$this->isStarted()) { + throw new LogicException(sprintf('Process must be started before calling "%s()".', $functionName)); + } + } + + /** + * Ensures the process is terminated, throws a LogicException if the process has a status different than "terminated". + * + * @throws LogicException if the process is not yet terminated + */ + private function requireProcessIsTerminated(string $functionName) + { + if (!$this->isTerminated()) { + throw new LogicException(sprintf('Process must be terminated before calling "%s()".', $functionName)); + } + } + + /** + * Escapes a string to be used as a shell argument. + */ + private function escapeArgument(?string $argument): string + { + if ('' === $argument || null === $argument) { + return '""'; + } + if ('\\' !== \DIRECTORY_SEPARATOR) { + return "'".str_replace("'", "'\\''", $argument)."'"; + } + if (str_contains($argument, "\0")) { + $argument = str_replace("\0", '?', $argument); + } + if (!preg_match('/[\/()%!^"<>&|\s]/', $argument)) { + return $argument; + } + $argument = preg_replace('/(\\\\+)$/', '$1$1', $argument); + + return '"'.str_replace(['"', '^', '%', '!', "\n"], ['""', '"^^"', '"^%"', '"^!"', '!LF!'], $argument).'"'; + } + + private function replacePlaceholders(string $commandline, array $env) + { + return preg_replace_callback('/"\$\{:([_a-zA-Z]++[_a-zA-Z0-9]*+)\}"/', function ($matches) use ($commandline, $env) { + if (!isset($env[$matches[1]]) || false === $env[$matches[1]]) { + throw new InvalidArgumentException(sprintf('Command line is missing a value for parameter "%s": ', $matches[1]).$commandline); + } + + return $this->escapeArgument($env[$matches[1]]); + }, $commandline); + } + + private function getDefaultEnv(): array + { + $env = getenv(); + $env = array_intersect_key($env, $_SERVER) ?: $env; + + return $_ENV + $env; + } +} diff --git a/vendor/symfony/process/ProcessUtils.php b/vendor/symfony/process/ProcessUtils.php new file mode 100644 index 0000000..eb39a4a --- /dev/null +++ b/vendor/symfony/process/ProcessUtils.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +use Symfony\Component\Process\Exception\InvalidArgumentException; + +/** + * ProcessUtils is a bunch of utility methods. + * + * This class contains static methods only and is not meant to be instantiated. + * + * @author Martin Hasoň + */ +class ProcessUtils +{ + /** + * This class should not be instantiated. + */ + private function __construct() + { + } + + /** + * Validates and normalizes a Process input. + * + * @param string $caller The name of method call that validates the input + * @param mixed $input The input to validate + * + * @return mixed The validated input + * + * @throws InvalidArgumentException In case the input is not valid + */ + public static function validateInput($caller, $input) + { + if (null !== $input) { + if (\is_resource($input)) { + return $input; + } + if (\is_string($input)) { + return $input; + } + if (is_scalar($input)) { + return (string) $input; + } + if ($input instanceof Process) { + return $input->getIterator($input::ITER_SKIP_ERR); + } + if ($input instanceof \Iterator) { + return $input; + } + if ($input instanceof \Traversable) { + return new \IteratorIterator($input); + } + + throw new InvalidArgumentException(sprintf('"%s" only accepts strings, Traversable objects or stream resources.', $caller)); + } + + return $input; + } +} diff --git a/vendor/symfony/process/README.md b/vendor/symfony/process/README.md new file mode 100644 index 0000000..afce5e4 --- /dev/null +++ b/vendor/symfony/process/README.md @@ -0,0 +1,13 @@ +Process Component +================= + +The Process component executes commands in sub-processes. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/process.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/process/composer.json b/vendor/symfony/process/composer.json new file mode 100644 index 0000000..c0f7599 --- /dev/null +++ b/vendor/symfony/process/composer.json @@ -0,0 +1,29 @@ +{ + "name": "symfony/process", + "type": "library", + "description": "Executes commands in sub-processes", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1.3", + "symfony/polyfill-php80": "^1.16" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Process\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/psr-http-message-bridge/.github/workflows/ci.yml b/vendor/symfony/psr-http-message-bridge/.github/workflows/ci.yml new file mode 100644 index 0000000..4c0e8ae --- /dev/null +++ b/vendor/symfony/psr-http-message-bridge/.github/workflows/ci.yml @@ -0,0 +1,49 @@ +name: CI + +on: + pull_request: + push: + +jobs: + test: + name: 'Test ${{ matrix.deps }} on PHP ${{ matrix.php }}' + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + php: ['7.1.3', '7.2', '7.3', '7.4', '8.0', '8.1'] + include: + - php: '7.4' + deps: lowest + deprecations: max[self]=0 + - php: '8.0' + deps: highest + deprecations: max[indirect]=5 + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '${{ matrix.php }}' + coverage: none + + - name: Configure composer + if: "${{ matrix.deps == 'highest' }}" + run: composer config minimum-stability dev + + - name: Composer install + uses: ramsey/composer-install@v1 + with: + dependency-versions: '${{ matrix.deps }}' + + - name: Install PHPUnit + run: vendor/bin/simple-phpunit install + + - name: Run tests + run: vendor/bin/simple-phpunit + env: + SYMFONY_DEPRECATIONS_HELPER: '${{ matrix.deprecations }}' diff --git a/vendor/symfony/psr-http-message-bridge/.gitignore b/vendor/symfony/psr-http-message-bridge/.gitignore new file mode 100644 index 0000000..55ce5dd --- /dev/null +++ b/vendor/symfony/psr-http-message-bridge/.gitignore @@ -0,0 +1,6 @@ +vendor/ +composer.lock +phpunit.xml +.php_cs.cache +.phpunit.result.cache +/Tests/Fixtures/App/var diff --git a/vendor/symfony/psr-http-message-bridge/.php_cs.dist b/vendor/symfony/psr-http-message-bridge/.php_cs.dist new file mode 100644 index 0000000..d741d39 --- /dev/null +++ b/vendor/symfony/psr-http-message-bridge/.php_cs.dist @@ -0,0 +1,24 @@ +setRules([ + '@Symfony' => true, + '@Symfony:risky' => true, + '@PHPUnit48Migration:risky' => true, + 'php_unit_no_expectation_annotation' => false, // part of `PHPUnitXYMigration:risky` ruleset, to be enabled when PHPUnit 4.x support will be dropped, as we don't want to rewrite exceptions handling twice + 'array_syntax' => ['syntax' => 'short'], + 'fopen_flags' => false, + 'ordered_imports' => true, + 'protected_to_private' => false, + // Part of @Symfony:risky in PHP-CS-Fixer 2.13.0. To be removed from the config file once upgrading + 'native_function_invocation' => ['include' => ['@compiler_optimized'], 'scope' => 'namespaced'], + // Part of future @Symfony ruleset in PHP-CS-Fixer To be removed from the config file once upgrading + 'phpdoc_types_order' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'], + ]) + ->setRiskyAllowed(true) + ->setFinder( + PhpCsFixer\Finder::create() + ->in(__DIR__) + ->name('*.php') + ) +; diff --git a/vendor/symfony/psr-http-message-bridge/ArgumentValueResolver/PsrServerRequestResolver.php b/vendor/symfony/psr-http-message-bridge/ArgumentValueResolver/PsrServerRequestResolver.php new file mode 100644 index 0000000..29dc7dc --- /dev/null +++ b/vendor/symfony/psr-http-message-bridge/ArgumentValueResolver/PsrServerRequestResolver.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\ArgumentValueResolver; + +use Psr\Http\Message\MessageInterface; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ServerRequestInterface; +use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; + +/** + * Injects the RequestInterface, MessageInterface or ServerRequestInterface when requested. + * + * @author Iltar van der Berg + * @author Alexander M. Turek + */ +final class PsrServerRequestResolver implements ArgumentValueResolverInterface +{ + private const SUPPORTED_TYPES = [ + ServerRequestInterface::class => true, + RequestInterface::class => true, + MessageInterface::class => true, + ]; + + private $httpMessageFactory; + + public function __construct(HttpMessageFactoryInterface $httpMessageFactory) + { + $this->httpMessageFactory = $httpMessageFactory; + } + + /** + * {@inheritdoc} + */ + public function supports(Request $request, ArgumentMetadata $argument): bool + { + return self::SUPPORTED_TYPES[$argument->getType()] ?? false; + } + + /** + * {@inheritdoc} + */ + public function resolve(Request $request, ArgumentMetadata $argument): \Traversable + { + yield $this->httpMessageFactory->createRequest($request); + } +} diff --git a/vendor/symfony/psr-http-message-bridge/CHANGELOG.md b/vendor/symfony/psr-http-message-bridge/CHANGELOG.md new file mode 100644 index 0000000..c17d8f3 --- /dev/null +++ b/vendor/symfony/psr-http-message-bridge/CHANGELOG.md @@ -0,0 +1,65 @@ +CHANGELOG +========= + +# 2.1.2 (2021-11-05) + +* Allow Symfony 6 + +# 2.1.0 (2021-02-17) + + * Added a `PsrResponseListener` to automatically convert PSR-7 responses returned by controllers + * Added a `PsrServerRequestResolver` that allows injecting PSR-7 request objects into controllers + +# 2.0.2 (2020-09-29) + + * Fix populating server params from URI in HttpFoundationFactory + * Create cookies as raw in HttpFoundationFactory + * Fix BinaryFileResponse with Content-Range PsrHttpFactory + +# 2.0.1 (2020-06-25) + + * Don't normalize query string in PsrHttpFactory + * Fix conversion for HTTPS requests + * Fix populating default port and headers in HttpFoundationFactory + +# 2.0.0 (2020-01-02) + + * Remove DiactorosFactory + +# 1.3.0 (2019-11-25) + + * Added support for streamed requests + * Added support for Symfony 5.0+ + * Fixed bridging UploadedFile objects + * Bumped minimum version of Symfony to 4.4 + +# 1.2.0 (2019-03-11) + + * Added new documentation links + * Bumped minimum version of PHP to 7.1 + * Added support for streamed responses + +# 1.1.2 (2019-04-03) + + * Fixed createResponse + +# 1.1.1 (2019-03-11) + + * Deprecated DiactorosFactory, use PsrHttpFactory instead + * Removed triggering of deprecation + +# 1.1.0 (2018-08-30) + + * Added support for creating PSR-7 messages using PSR-17 factories + +# 1.0.2 (2017-12-19) + + * Fixed request target in PSR7 Request (mtibben) + +# 1.0.1 (2017-12-04) + + * Added support for Symfony 4 (dunglas) + +# 1.0.0 (2016-09-14) + + * Initial release diff --git a/vendor/symfony/psr-http-message-bridge/EventListener/PsrResponseListener.php b/vendor/symfony/psr-http-message-bridge/EventListener/PsrResponseListener.php new file mode 100644 index 0000000..ee0e047 --- /dev/null +++ b/vendor/symfony/psr-http-message-bridge/EventListener/PsrResponseListener.php @@ -0,0 +1,50 @@ + + * @author Alexander M. Turek + */ +final class PsrResponseListener implements EventSubscriberInterface +{ + private $httpFoundationFactory; + + public function __construct(HttpFoundationFactoryInterface $httpFoundationFactory = null) + { + $this->httpFoundationFactory = $httpFoundationFactory ?? new HttpFoundationFactory(); + } + + /** + * Do the conversion if applicable and update the response of the event. + */ + public function onKernelView(ViewEvent $event): void + { + $controllerResult = $event->getControllerResult(); + + if (!$controllerResult instanceof ResponseInterface) { + return; + } + + $event->setResponse($this->httpFoundationFactory->createResponse($controllerResult)); + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents(): array + { + return [ + KernelEvents::VIEW => 'onKernelView', + ]; + } +} diff --git a/vendor/symfony/psr-http-message-bridge/Factory/HttpFoundationFactory.php b/vendor/symfony/psr-http-message-bridge/Factory/HttpFoundationFactory.php new file mode 100644 index 0000000..457e346 --- /dev/null +++ b/vendor/symfony/psr-http-message-bridge/Factory/HttpFoundationFactory.php @@ -0,0 +1,248 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Factory; + +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\StreamInterface; +use Psr\Http\Message\UploadedFileInterface; +use Psr\Http\Message\UriInterface; +use Symfony\Bridge\PsrHttpMessage\HttpFoundationFactoryInterface; +use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; + +/** + * {@inheritdoc} + * + * @author Kévin Dunglas + */ +class HttpFoundationFactory implements HttpFoundationFactoryInterface +{ + /** + * @var int The maximum output buffering size for each iteration when sending the response + */ + private $responseBufferMaxLength; + + public function __construct(int $responseBufferMaxLength = 16372) + { + $this->responseBufferMaxLength = $responseBufferMaxLength; + } + + /** + * {@inheritdoc} + */ + public function createRequest(ServerRequestInterface $psrRequest, bool $streamed = false) + { + $server = []; + $uri = $psrRequest->getUri(); + + if ($uri instanceof UriInterface) { + $server['SERVER_NAME'] = $uri->getHost(); + $server['SERVER_PORT'] = $uri->getPort() ?: ('https' === $uri->getScheme() ? 443 : 80); + $server['REQUEST_URI'] = $uri->getPath(); + $server['QUERY_STRING'] = $uri->getQuery(); + + if ('' !== $server['QUERY_STRING']) { + $server['REQUEST_URI'] .= '?'.$server['QUERY_STRING']; + } + + if ('https' === $uri->getScheme()) { + $server['HTTPS'] = 'on'; + } + } + + $server['REQUEST_METHOD'] = $psrRequest->getMethod(); + + $server = array_replace($psrRequest->getServerParams(), $server); + + $parsedBody = $psrRequest->getParsedBody(); + $parsedBody = \is_array($parsedBody) ? $parsedBody : []; + + $request = new Request( + $psrRequest->getQueryParams(), + $parsedBody, + $psrRequest->getAttributes(), + $psrRequest->getCookieParams(), + $this->getFiles($psrRequest->getUploadedFiles()), + $server, + $streamed ? $psrRequest->getBody()->detach() : $psrRequest->getBody()->__toString() + ); + $request->headers->add($psrRequest->getHeaders()); + + return $request; + } + + /** + * Converts to the input array to $_FILES structure. + */ + private function getFiles(array $uploadedFiles): array + { + $files = []; + + foreach ($uploadedFiles as $key => $value) { + if ($value instanceof UploadedFileInterface) { + $files[$key] = $this->createUploadedFile($value); + } else { + $files[$key] = $this->getFiles($value); + } + } + + return $files; + } + + /** + * Creates Symfony UploadedFile instance from PSR-7 ones. + */ + private function createUploadedFile(UploadedFileInterface $psrUploadedFile): UploadedFile + { + return new UploadedFile($psrUploadedFile, function () { return $this->getTemporaryPath(); }); + } + + /** + * Gets a temporary file path. + * + * @return string + */ + protected function getTemporaryPath() + { + return tempnam(sys_get_temp_dir(), uniqid('symfony', true)); + } + + /** + * {@inheritdoc} + */ + public function createResponse(ResponseInterface $psrResponse, bool $streamed = false) + { + $cookies = $psrResponse->getHeader('Set-Cookie'); + $psrResponse = $psrResponse->withoutHeader('Set-Cookie'); + + if ($streamed) { + $response = new StreamedResponse( + $this->createStreamedResponseCallback($psrResponse->getBody()), + $psrResponse->getStatusCode(), + $psrResponse->getHeaders() + ); + } else { + $response = new Response( + $psrResponse->getBody()->__toString(), + $psrResponse->getStatusCode(), + $psrResponse->getHeaders() + ); + } + + $response->setProtocolVersion($psrResponse->getProtocolVersion()); + + foreach ($cookies as $cookie) { + $response->headers->setCookie($this->createCookie($cookie)); + } + + return $response; + } + + /** + * Creates a Cookie instance from a cookie string. + * + * Some snippets have been taken from the Guzzle project: https://github.com/guzzle/guzzle/blob/5.3/src/Cookie/SetCookie.php#L34 + * + * @throws \InvalidArgumentException + */ + private function createCookie(string $cookie): Cookie + { + foreach (explode(';', $cookie) as $part) { + $part = trim($part); + + $data = explode('=', $part, 2); + $name = $data[0]; + $value = isset($data[1]) ? trim($data[1], " \n\r\t\0\x0B\"") : null; + + if (!isset($cookieName)) { + $cookieName = $name; + $cookieValue = $value; + + continue; + } + + if ('expires' === strtolower($name) && null !== $value) { + $cookieExpire = new \DateTime($value); + + continue; + } + + if ('path' === strtolower($name) && null !== $value) { + $cookiePath = $value; + + continue; + } + + if ('domain' === strtolower($name) && null !== $value) { + $cookieDomain = $value; + + continue; + } + + if ('secure' === strtolower($name)) { + $cookieSecure = true; + + continue; + } + + if ('httponly' === strtolower($name)) { + $cookieHttpOnly = true; + + continue; + } + + if ('samesite' === strtolower($name) && null !== $value) { + $samesite = $value; + + continue; + } + } + + if (!isset($cookieName)) { + throw new \InvalidArgumentException('The value of the Set-Cookie header is malformed.'); + } + + return new Cookie( + $cookieName, + $cookieValue, + isset($cookieExpire) ? $cookieExpire : 0, + isset($cookiePath) ? $cookiePath : '/', + isset($cookieDomain) ? $cookieDomain : null, + isset($cookieSecure), + isset($cookieHttpOnly), + true, + isset($samesite) ? $samesite : null + ); + } + + private function createStreamedResponseCallback(StreamInterface $body): callable + { + return function () use ($body) { + if ($body->isSeekable()) { + $body->rewind(); + } + + if (!$body->isReadable()) { + echo $body; + + return; + } + + while (!$body->eof()) { + echo $body->read($this->responseBufferMaxLength); + } + }; + } +} diff --git a/vendor/symfony/psr-http-message-bridge/Factory/PsrHttpFactory.php b/vendor/symfony/psr-http-message-bridge/Factory/PsrHttpFactory.php new file mode 100644 index 0000000..d19baa1 --- /dev/null +++ b/vendor/symfony/psr-http-message-bridge/Factory/PsrHttpFactory.php @@ -0,0 +1,171 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Factory; + +use Psr\Http\Message\ResponseFactoryInterface; +use Psr\Http\Message\ServerRequestFactoryInterface; +use Psr\Http\Message\StreamFactoryInterface; +use Psr\Http\Message\UploadedFileFactoryInterface; +use Psr\Http\Message\UploadedFileInterface; +use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; +use Symfony\Component\HttpFoundation\BinaryFileResponse; +use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; + +/** + * Builds Psr\HttpMessage instances using a PSR-17 implementation. + * + * @author Antonio J. García Lagar + */ +class PsrHttpFactory implements HttpMessageFactoryInterface +{ + private $serverRequestFactory; + private $streamFactory; + private $uploadedFileFactory; + private $responseFactory; + + public function __construct(ServerRequestFactoryInterface $serverRequestFactory, StreamFactoryInterface $streamFactory, UploadedFileFactoryInterface $uploadedFileFactory, ResponseFactoryInterface $responseFactory) + { + $this->serverRequestFactory = $serverRequestFactory; + $this->streamFactory = $streamFactory; + $this->uploadedFileFactory = $uploadedFileFactory; + $this->responseFactory = $responseFactory; + } + + /** + * {@inheritdoc} + */ + public function createRequest(Request $symfonyRequest) + { + $uri = $symfonyRequest->server->get('QUERY_STRING', ''); + $uri = $symfonyRequest->getSchemeAndHttpHost().$symfonyRequest->getBaseUrl().$symfonyRequest->getPathInfo().('' !== $uri ? '?'.$uri : ''); + + $request = $this->serverRequestFactory->createServerRequest( + $symfonyRequest->getMethod(), + $uri, + $symfonyRequest->server->all() + ); + + foreach ($symfonyRequest->headers->all() as $name => $value) { + $request = $request->withHeader($name, $value); + } + + $body = $this->streamFactory->createStreamFromResource($symfonyRequest->getContent(true)); + + $request = $request + ->withBody($body) + ->withUploadedFiles($this->getFiles($symfonyRequest->files->all())) + ->withCookieParams($symfonyRequest->cookies->all()) + ->withQueryParams($symfonyRequest->query->all()) + ->withParsedBody($symfonyRequest->request->all()) + ; + + foreach ($symfonyRequest->attributes->all() as $key => $value) { + $request = $request->withAttribute($key, $value); + } + + return $request; + } + + /** + * Converts Symfony uploaded files array to the PSR one. + * + * @return array + */ + private function getFiles(array $uploadedFiles) + { + $files = []; + + foreach ($uploadedFiles as $key => $value) { + if (null === $value) { + $files[$key] = $this->uploadedFileFactory->createUploadedFile($this->streamFactory->createStream(), 0, \UPLOAD_ERR_NO_FILE); + continue; + } + if ($value instanceof UploadedFile) { + $files[$key] = $this->createUploadedFile($value); + } else { + $files[$key] = $this->getFiles($value); + } + } + + return $files; + } + + /** + * Creates a PSR-7 UploadedFile instance from a Symfony one. + * + * @return UploadedFileInterface + */ + private function createUploadedFile(UploadedFile $symfonyUploadedFile) + { + return $this->uploadedFileFactory->createUploadedFile( + $this->streamFactory->createStreamFromFile( + $symfonyUploadedFile->getRealPath() + ), + (int) $symfonyUploadedFile->getSize(), + $symfonyUploadedFile->getError(), + $symfonyUploadedFile->getClientOriginalName(), + $symfonyUploadedFile->getClientMimeType() + ); + } + + /** + * {@inheritdoc} + */ + public function createResponse(Response $symfonyResponse) + { + $response = $this->responseFactory->createResponse($symfonyResponse->getStatusCode(), Response::$statusTexts[$symfonyResponse->getStatusCode()] ?? ''); + + if ($symfonyResponse instanceof BinaryFileResponse && !$symfonyResponse->headers->has('Content-Range')) { + $stream = $this->streamFactory->createStreamFromFile( + $symfonyResponse->getFile()->getPathname() + ); + } else { + $stream = $this->streamFactory->createStreamFromFile('php://temp', 'wb+'); + if ($symfonyResponse instanceof StreamedResponse || $symfonyResponse instanceof BinaryFileResponse) { + ob_start(function ($buffer) use ($stream) { + $stream->write($buffer); + + return ''; + }); + + $symfonyResponse->sendContent(); + ob_end_clean(); + } else { + $stream->write($symfonyResponse->getContent()); + } + } + + $response = $response->withBody($stream); + + $headers = $symfonyResponse->headers->all(); + $cookies = $symfonyResponse->headers->getCookies(); + if (!empty($cookies)) { + $headers['Set-Cookie'] = []; + + foreach ($cookies as $cookie) { + $headers['Set-Cookie'][] = $cookie->__toString(); + } + } + + foreach ($headers as $name => $value) { + $response = $response->withHeader($name, $value); + } + + $protocolVersion = $symfonyResponse->getProtocolVersion(); + $response = $response->withProtocolVersion($protocolVersion); + + return $response; + } +} diff --git a/vendor/symfony/psr-http-message-bridge/Factory/UploadedFile.php b/vendor/symfony/psr-http-message-bridge/Factory/UploadedFile.php new file mode 100644 index 0000000..53aa37a --- /dev/null +++ b/vendor/symfony/psr-http-message-bridge/Factory/UploadedFile.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Factory; + +use Psr\Http\Message\UploadedFileInterface; +use Symfony\Component\HttpFoundation\File\Exception\FileException; +use Symfony\Component\HttpFoundation\File\File; +use Symfony\Component\HttpFoundation\File\UploadedFile as BaseUploadedFile; + +/** + * @author Nicolas Grekas + */ +class UploadedFile extends BaseUploadedFile +{ + private $psrUploadedFile; + private $test = false; + + public function __construct(UploadedFileInterface $psrUploadedFile, callable $getTemporaryPath) + { + $error = $psrUploadedFile->getError(); + $path = ''; + + if (\UPLOAD_ERR_NO_FILE !== $error) { + $path = $psrUploadedFile->getStream()->getMetadata('uri') ?? ''; + + if ($this->test = !\is_string($path) || !is_uploaded_file($path)) { + $path = $getTemporaryPath(); + $psrUploadedFile->moveTo($path); + } + } + + parent::__construct( + $path, + (string) $psrUploadedFile->getClientFilename(), + $psrUploadedFile->getClientMediaType(), + $psrUploadedFile->getError(), + $this->test + ); + + $this->psrUploadedFile = $psrUploadedFile; + } + + /** + * {@inheritdoc} + */ + public function move($directory, $name = null): File + { + if (!$this->isValid() || $this->test) { + return parent::move($directory, $name); + } + + $target = $this->getTargetFile($directory, $name); + + try { + $this->psrUploadedFile->moveTo($target); + } catch (\RuntimeException $e) { + throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, $e->getMessage()), 0, $e); + } + + @chmod($target, 0666 & ~umask()); + + return $target; + } +} diff --git a/vendor/symfony/psr-http-message-bridge/HttpFoundationFactoryInterface.php b/vendor/symfony/psr-http-message-bridge/HttpFoundationFactoryInterface.php new file mode 100644 index 0000000..a3f9043 --- /dev/null +++ b/vendor/symfony/psr-http-message-bridge/HttpFoundationFactoryInterface.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage; + +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Creates Symfony Request and Response instances from PSR-7 ones. + * + * @author Kévin Dunglas + */ +interface HttpFoundationFactoryInterface +{ + /** + * Creates a Symfony Request instance from a PSR-7 one. + * + * @return Request + */ + public function createRequest(ServerRequestInterface $psrRequest, bool $streamed = false); + + /** + * Creates a Symfony Response instance from a PSR-7 one. + * + * @return Response + */ + public function createResponse(ResponseInterface $psrResponse, bool $streamed = false); +} diff --git a/vendor/symfony/psr-http-message-bridge/HttpMessageFactoryInterface.php b/vendor/symfony/psr-http-message-bridge/HttpMessageFactoryInterface.php new file mode 100644 index 0000000..f7b964e --- /dev/null +++ b/vendor/symfony/psr-http-message-bridge/HttpMessageFactoryInterface.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage; + +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Creates PSR HTTP Request and Response instances from Symfony ones. + * + * @author Kévin Dunglas + */ +interface HttpMessageFactoryInterface +{ + /** + * Creates a PSR-7 Request instance from a Symfony one. + * + * @return ServerRequestInterface + */ + public function createRequest(Request $symfonyRequest); + + /** + * Creates a PSR-7 Response instance from a Symfony one. + * + * @return ResponseInterface + */ + public function createResponse(Response $symfonyResponse); +} diff --git a/vendor/symfony/psr-http-message-bridge/LICENSE b/vendor/symfony/psr-http-message-bridge/LICENSE new file mode 100644 index 0000000..9ff2d0d --- /dev/null +++ b/vendor/symfony/psr-http-message-bridge/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2021 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/psr-http-message-bridge/README.md b/vendor/symfony/psr-http-message-bridge/README.md new file mode 100644 index 0000000..dcbc09a --- /dev/null +++ b/vendor/symfony/psr-http-message-bridge/README.md @@ -0,0 +1,19 @@ +PSR-7 Bridge +============ + +Provides integration for PSR7. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/psr7.html) + +Running the tests +----------------- + +If you want to run the unit tests, install dev dependencies before +running PHPUnit: + + $ cd path/to/Symfony/Bridge/PsrHttpMessage/ + $ composer.phar install + $ phpunit diff --git a/vendor/symfony/psr-http-message-bridge/Tests/ArgumentValueResolver/PsrServerRequestResolverTest.php b/vendor/symfony/psr-http-message-bridge/Tests/ArgumentValueResolver/PsrServerRequestResolverTest.php new file mode 100644 index 0000000..662b186 --- /dev/null +++ b/vendor/symfony/psr-http-message-bridge/Tests/ArgumentValueResolver/PsrServerRequestResolverTest.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Tests\ArgumentValueResolver; + +use PHPUnit\Framework\TestCase; +use Psr\Http\Message\MessageInterface; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ServerRequestInterface; +use Symfony\Bridge\PsrHttpMessage\ArgumentValueResolver\PsrServerRequestResolver; +use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver; + +/** + * @author Alexander M. Turek + */ +final class PsrServerRequestResolverTest extends TestCase +{ + public function testServerRequest() + { + $symfonyRequest = $this->createMock(Request::class); + $psrRequest = $this->createMock(ServerRequestInterface::class); + + $resolver = $this->bootstrapResolver($symfonyRequest, $psrRequest); + + self::assertSame([$psrRequest], $resolver->getArguments($symfonyRequest, static function (ServerRequestInterface $serverRequest): void {})); + } + + public function testRequest() + { + $symfonyRequest = $this->createMock(Request::class); + $psrRequest = $this->createMock(ServerRequestInterface::class); + + $resolver = $this->bootstrapResolver($symfonyRequest, $psrRequest); + + self::assertSame([$psrRequest], $resolver->getArguments($symfonyRequest, static function (RequestInterface $request): void {})); + } + + public function testMessage() + { + $symfonyRequest = $this->createMock(Request::class); + $psrRequest = $this->createMock(ServerRequestInterface::class); + + $resolver = $this->bootstrapResolver($symfonyRequest, $psrRequest); + + self::assertSame([$psrRequest], $resolver->getArguments($symfonyRequest, static function (MessageInterface $request): void {})); + } + + private function bootstrapResolver(Request $symfonyRequest, ServerRequestInterface $psrRequest): ArgumentResolver + { + $messageFactory = $this->createMock(HttpMessageFactoryInterface::class); + $messageFactory->expects(self::once()) + ->method('createRequest') + ->with(self::identicalTo($symfonyRequest)) + ->willReturn($psrRequest); + + return new ArgumentResolver(null, [new PsrServerRequestResolver($messageFactory)]); + } +} diff --git a/vendor/symfony/psr-http-message-bridge/Tests/EventListener/PsrResponseListenerTest.php b/vendor/symfony/psr-http-message-bridge/Tests/EventListener/PsrResponseListenerTest.php new file mode 100644 index 0000000..9a94b20 --- /dev/null +++ b/vendor/symfony/psr-http-message-bridge/Tests/EventListener/PsrResponseListenerTest.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PsrHttpMessage\EventListener\PsrResponseListener; +use Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\Response; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Event\ViewEvent; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * @author Kévin Dunglas + */ +class PsrResponseListenerTest extends TestCase +{ + public function testConvertsControllerResult() + { + $listener = new PsrResponseListener(); + $event = $this->createEventMock(new Response()); + $listener->onKernelView($event); + + self::assertTrue($event->hasResponse()); + } + + public function testDoesNotConvertControllerResult() + { + $listener = new PsrResponseListener(); + $event = $this->createEventMock([]); + + $listener->onKernelView($event); + self::assertFalse($event->hasResponse()); + + $event = $this->createEventMock(null); + + $listener->onKernelView($event); + self::assertFalse($event->hasResponse()); + } + + private function createEventMock($controllerResult): ViewEvent + { + return new ViewEvent($this->createMock(HttpKernelInterface::class), new Request(), HttpKernelInterface::MASTER_REQUEST, $controllerResult); + } +} diff --git a/vendor/symfony/psr-http-message-bridge/Tests/Factory/AbstractHttpMessageFactoryTest.php b/vendor/symfony/psr-http-message-bridge/Tests/Factory/AbstractHttpMessageFactoryTest.php new file mode 100644 index 0000000..82d3fc7 --- /dev/null +++ b/vendor/symfony/psr-http-message-bridge/Tests/Factory/AbstractHttpMessageFactoryTest.php @@ -0,0 +1,234 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Tests\Factory; + +use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; +use Symfony\Component\HttpFoundation\BinaryFileResponse; +use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; + +/** + * @author Kévin Dunglas + * @author Antonio J. García Lagar + */ +abstract class AbstractHttpMessageFactoryTest extends TestCase +{ + private $factory; + private $tmpDir; + + abstract protected function buildHttpMessageFactory(): HttpMessageFactoryInterface; + + protected function setUp(): void + { + $this->factory = $this->buildHttpMessageFactory(); + $this->tmpDir = sys_get_temp_dir(); + } + + public function testCreateRequest() + { + $stdClass = new \stdClass(); + $request = new Request( + [ + 'bar' => ['baz' => '42'], + 'foo' => '1', + ], + [ + 'twitter' => [ + '@dunglas' => 'Kévin Dunglas', + '@coopTilleuls' => 'Les-Tilleuls.coop', + ], + 'baz' => '2', + ], + [ + 'a1' => $stdClass, + 'a2' => ['foo' => 'bar'], + ], + [ + 'c1' => 'foo', + 'c2' => ['c3' => 'bar'], + ], + [ + 'f1' => $this->createUploadedFile('F1', 'f1.txt', 'text/plain', \UPLOAD_ERR_OK), + 'foo' => ['f2' => $this->createUploadedFile('F2', 'f2.txt', 'text/plain', \UPLOAD_ERR_OK)], + ], + [ + 'REQUEST_METHOD' => 'POST', + 'HTTP_HOST' => 'dunglas.fr', + 'HTTP_X_SYMFONY' => '2.8', + 'REQUEST_URI' => '/testCreateRequest?bar[baz]=42&foo=1', + 'QUERY_STRING' => 'bar[baz]=42&foo=1', + ], + 'Content' + ); + + $psrRequest = $this->factory->createRequest($request); + + $this->assertEquals('Content', $psrRequest->getBody()->__toString()); + + $queryParams = $psrRequest->getQueryParams(); + $this->assertEquals('1', $queryParams['foo']); + $this->assertEquals('42', $queryParams['bar']['baz']); + + $requestTarget = $psrRequest->getRequestTarget(); + $this->assertEquals('/testCreateRequest?bar[baz]=42&foo=1', urldecode($requestTarget)); + + $parsedBody = $psrRequest->getParsedBody(); + $this->assertEquals('Kévin Dunglas', $parsedBody['twitter']['@dunglas']); + $this->assertEquals('Les-Tilleuls.coop', $parsedBody['twitter']['@coopTilleuls']); + $this->assertEquals('2', $parsedBody['baz']); + + $attributes = $psrRequest->getAttributes(); + $this->assertEquals($stdClass, $attributes['a1']); + $this->assertEquals('bar', $attributes['a2']['foo']); + + $cookies = $psrRequest->getCookieParams(); + $this->assertEquals('foo', $cookies['c1']); + $this->assertEquals('bar', $cookies['c2']['c3']); + + $uploadedFiles = $psrRequest->getUploadedFiles(); + $this->assertEquals('F1', $uploadedFiles['f1']->getStream()->__toString()); + $this->assertEquals('f1.txt', $uploadedFiles['f1']->getClientFilename()); + $this->assertEquals('text/plain', $uploadedFiles['f1']->getClientMediaType()); + $this->assertEquals(\UPLOAD_ERR_OK, $uploadedFiles['f1']->getError()); + + $this->assertEquals('F2', $uploadedFiles['foo']['f2']->getStream()->__toString()); + $this->assertEquals('f2.txt', $uploadedFiles['foo']['f2']->getClientFilename()); + $this->assertEquals('text/plain', $uploadedFiles['foo']['f2']->getClientMediaType()); + $this->assertEquals(\UPLOAD_ERR_OK, $uploadedFiles['foo']['f2']->getError()); + + $serverParams = $psrRequest->getServerParams(); + $this->assertEquals('POST', $serverParams['REQUEST_METHOD']); + $this->assertEquals('2.8', $serverParams['HTTP_X_SYMFONY']); + $this->assertEquals('POST', $psrRequest->getMethod()); + $this->assertEquals(['2.8'], $psrRequest->getHeader('X-Symfony')); + } + + public function testGetContentCanBeCalledAfterRequestCreation() + { + $header = ['HTTP_HOST' => 'dunglas.fr']; + $request = new Request([], [], [], [], [], $header, 'Content'); + + $psrRequest = $this->factory->createRequest($request); + + $this->assertEquals('Content', $psrRequest->getBody()->__toString()); + $this->assertEquals('Content', $request->getContent()); + } + + private function createUploadedFile($content, $originalName, $mimeType, $error) + { + $path = tempnam($this->tmpDir, uniqid()); + file_put_contents($path, $content); + + return new UploadedFile($path, $originalName, $mimeType, $error, true); + } + + public function testCreateResponse() + { + $response = new Response( + 'Response content.', + 202, + ['X-Symfony' => ['3.4']] + ); + $response->headers->setCookie(new Cookie('city', 'Lille', new \DateTime('Wed, 13 Jan 2021 22:23:01 GMT'), '/', null, false, true, false, 'lax')); + + $psrResponse = $this->factory->createResponse($response); + $this->assertEquals('Response content.', $psrResponse->getBody()->__toString()); + $this->assertEquals(202, $psrResponse->getStatusCode()); + $this->assertEquals(['3.4'], $psrResponse->getHeader('X-Symfony')); + + $cookieHeader = $psrResponse->getHeader('Set-Cookie'); + $this->assertIsArray($cookieHeader); + $this->assertCount(1, $cookieHeader); + $this->assertMatchesRegularExpression('{city=Lille; expires=Wed, 13-Jan-2021 22:23:01 GMT;( max-age=\d+;)? path=/; httponly}i', $cookieHeader[0]); + } + + public function testCreateResponseFromStreamed() + { + $response = new StreamedResponse(function () { + echo "Line 1\n"; + flush(); + + echo "Line 2\n"; + flush(); + }); + + $psrResponse = $this->factory->createResponse($response); + + $this->assertEquals("Line 1\nLine 2\n", $psrResponse->getBody()->__toString()); + } + + public function testCreateResponseFromBinaryFile() + { + $path = tempnam($this->tmpDir, uniqid()); + file_put_contents($path, 'Binary'); + + $response = new BinaryFileResponse($path); + + $psrResponse = $this->factory->createResponse($response); + + $this->assertEquals('Binary', $psrResponse->getBody()->__toString()); + } + + public function testCreateResponseFromBinaryFileWithRange() + { + $path = tempnam($this->tmpDir, uniqid()); + file_put_contents($path, 'Binary'); + + $request = new Request(); + $request->headers->set('Range', 'bytes=1-4'); + + $response = new BinaryFileResponse($path, 200, ['Content-Type' => 'plain/text']); + $response->prepare($request); + + $psrResponse = $this->factory->createResponse($response); + + $this->assertEquals('inar', $psrResponse->getBody()->__toString()); + $this->assertSame('bytes 1-4/6', $psrResponse->getHeaderLine('Content-Range')); + } + + public function testUploadErrNoFile() + { + $file = new UploadedFile('', '', null, \UPLOAD_ERR_NO_FILE, true); + + $this->assertEquals(0, $file->getSize()); + $this->assertEquals(\UPLOAD_ERR_NO_FILE, $file->getError()); + $this->assertFalse($file->getSize(), 'SplFile::getSize() returns false on error'); + + $request = new Request( + [], + [], + [], + [], + [ + 'f1' => $file, + 'f2' => ['name' => null, 'type' => null, 'tmp_name' => null, 'error' => \UPLOAD_ERR_NO_FILE, 'size' => 0], + ], + [ + 'REQUEST_METHOD' => 'POST', + 'HTTP_HOST' => 'dunglas.fr', + 'HTTP_X_SYMFONY' => '2.8', + ], + 'Content' + ); + + $psrRequest = $this->factory->createRequest($request); + + $uploadedFiles = $psrRequest->getUploadedFiles(); + + $this->assertEquals(\UPLOAD_ERR_NO_FILE, $uploadedFiles['f1']->getError()); + $this->assertEquals(\UPLOAD_ERR_NO_FILE, $uploadedFiles['f2']->getError()); + } +} diff --git a/vendor/symfony/psr-http-message-bridge/Tests/Factory/HttpFoundationFactoryTest.php b/vendor/symfony/psr-http-message-bridge/Tests/Factory/HttpFoundationFactoryTest.php new file mode 100644 index 0000000..3a00e2f --- /dev/null +++ b/vendor/symfony/psr-http-message-bridge/Tests/Factory/HttpFoundationFactoryTest.php @@ -0,0 +1,272 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Tests\Factory; + +use PHPUnit\Framework\TestCase; +use Psr\Http\Message\UploadedFileInterface; +use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory; +use Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\Response; +use Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\ServerRequest; +use Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\Stream; +use Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\UploadedFile; +use Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\Uri; +use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpFoundation\File\Exception\FileException; +use Symfony\Component\HttpFoundation\File\UploadedFile as HttpFoundationUploadedFile; + +/** + * @author Kévin Dunglas + */ +class HttpFoundationFactoryTest extends TestCase +{ + /** @var HttpFoundationFactory */ + private $factory; + + /** @var string */ + private $tmpDir; + + protected function setUp(): void + { + $this->factory = new HttpFoundationFactory(); + $this->tmpDir = sys_get_temp_dir(); + } + + public function testCreateRequest() + { + $stdClass = new \stdClass(); + $serverRequest = new ServerRequest( + '1.1', + [ + 'X-Dunglas-API-Platform' => '1.0', + 'X-data' => ['a', 'b'], + ], + new Stream('The body'), + '/about/kevin', + 'GET', + 'http://les-tilleuls.coop/about/kevin', + ['country' => 'France'], + ['city' => 'Lille'], + ['url' => 'http://les-tilleuls.coop'], + [ + 'doc1' => $this->createUploadedFile('Doc 1', \UPLOAD_ERR_OK, 'doc1.txt', 'text/plain'), + 'nested' => [ + 'docs' => [ + $this->createUploadedFile('Doc 2', \UPLOAD_ERR_OK, 'doc2.txt', 'text/plain'), + $this->createUploadedFile('Doc 3', \UPLOAD_ERR_OK, 'doc3.txt', 'text/plain'), + ], + ], + ], + ['url' => 'http://dunglas.fr'], + ['custom' => $stdClass] + ); + + $symfonyRequest = $this->factory->createRequest($serverRequest); + $files = $symfonyRequest->files->all(); + + $this->assertEquals('http://les-tilleuls.coop', $symfonyRequest->query->get('url')); + $this->assertEquals('doc1.txt', $files['doc1']->getClientOriginalName()); + $this->assertEquals('doc2.txt', $files['nested']['docs'][0]->getClientOriginalName()); + $this->assertEquals('doc3.txt', $files['nested']['docs'][1]->getClientOriginalName()); + $this->assertEquals('http://dunglas.fr', $symfonyRequest->request->get('url')); + $this->assertEquals($stdClass, $symfonyRequest->attributes->get('custom')); + $this->assertEquals('Lille', $symfonyRequest->cookies->get('city')); + $this->assertEquals('France', $symfonyRequest->server->get('country')); + $this->assertEquals('The body', $symfonyRequest->getContent()); + $this->assertEquals('1.0', $symfonyRequest->headers->get('X-Dunglas-API-Platform')); + $this->assertEquals(['a', 'b'], $symfonyRequest->headers->all('X-data')); + } + + public function testCreateRequestWithStreamedBody() + { + $serverRequest = new ServerRequest( + '1.1', + [], + new Stream('The body'), + '/', + 'GET', + null, + [], + [], + [], + [], + null, + [] + ); + + $symfonyRequest = $this->factory->createRequest($serverRequest, true); + $this->assertEquals('The body', $symfonyRequest->getContent()); + } + + public function testCreateRequestWithNullParsedBody() + { + $serverRequest = new ServerRequest( + '1.1', + [], + new Stream(), + '/', + 'GET', + null, + [], + [], + [], + [], + null, + [] + ); + + $this->assertCount(0, $this->factory->createRequest($serverRequest)->request); + } + + public function testCreateRequestWithObjectParsedBody() + { + $serverRequest = new ServerRequest( + '1.1', + [], + new Stream(), + '/', + 'GET', + null, + [], + [], + [], + [], + new \stdClass(), + [] + ); + + $this->assertCount(0, $this->factory->createRequest($serverRequest)->request); + } + + public function testCreateRequestWithUri() + { + $serverRequest = new ServerRequest( + '1.1', + [], + new Stream(), + '/', + 'GET', + new Uri('http://les-tilleuls.coop/about/kevin'), + [], + [], + [], + [], + null, + [] + ); + + $this->assertEquals('/about/kevin', $this->factory->createRequest($serverRequest)->getPathInfo()); + } + + public function testCreateUploadedFile() + { + $uploadedFile = $this->createUploadedFile('An uploaded file.', \UPLOAD_ERR_OK, 'myfile.txt', 'text/plain'); + $symfonyUploadedFile = $this->callCreateUploadedFile($uploadedFile); + $size = $symfonyUploadedFile->getSize(); + + $uniqid = uniqid(); + $symfonyUploadedFile->move($this->tmpDir, $uniqid); + + $this->assertEquals($uploadedFile->getSize(), $size); + $this->assertEquals(\UPLOAD_ERR_OK, $symfonyUploadedFile->getError()); + $this->assertEquals('myfile.txt', $symfonyUploadedFile->getClientOriginalName()); + $this->assertEquals('txt', $symfonyUploadedFile->getClientOriginalExtension()); + $this->assertEquals('text/plain', $symfonyUploadedFile->getClientMimeType()); + $this->assertEquals('An uploaded file.', file_get_contents($this->tmpDir.'/'.$uniqid)); + } + + public function testCreateUploadedFileWithError() + { + $this->expectException(FileException::class); + $this->expectExceptionMessage('The file "e" could not be written on disk.'); + + $uploadedFile = $this->createUploadedFile('Error.', \UPLOAD_ERR_CANT_WRITE, 'e', 'text/plain'); + $symfonyUploadedFile = $this->callCreateUploadedFile($uploadedFile); + + $this->assertEquals(\UPLOAD_ERR_CANT_WRITE, $symfonyUploadedFile->getError()); + + $symfonyUploadedFile->move($this->tmpDir, 'shouldFail.txt'); + } + + private function createUploadedFile($content, $error, $clientFileName, $clientMediaType): UploadedFile + { + $filePath = tempnam($this->tmpDir, uniqid()); + file_put_contents($filePath, $content); + + return new UploadedFile($filePath, filesize($filePath), $error, $clientFileName, $clientMediaType); + } + + private function callCreateUploadedFile(UploadedFileInterface $uploadedFile): HttpFoundationUploadedFile + { + $reflection = new \ReflectionClass($this->factory); + $createUploadedFile = $reflection->getMethod('createUploadedFile'); + $createUploadedFile->setAccessible(true); + + return $createUploadedFile->invokeArgs($this->factory, [$uploadedFile]); + } + + public function testCreateResponse() + { + $response = new Response( + '1.0', + [ + 'X-Symfony' => ['2.8'], + 'Set-Cookie' => [ + 'theme=light', + 'test', + 'ABC=AeD; Domain=dunglas.fr; Path=/kevin; Expires=Wed, 13 Jan 2021 22:23:01 GMT; Secure; HttpOnly; SameSite=Strict', + ], + ], + new Stream('The response body'), + 200 + ); + + $symfonyResponse = $this->factory->createResponse($response); + + $this->assertEquals('1.0', $symfonyResponse->getProtocolVersion()); + $this->assertEquals('2.8', $symfonyResponse->headers->get('X-Symfony')); + + $cookies = $symfonyResponse->headers->getCookies(); + $this->assertEquals('theme', $cookies[0]->getName()); + $this->assertEquals('light', $cookies[0]->getValue()); + $this->assertEquals(0, $cookies[0]->getExpiresTime()); + $this->assertNull($cookies[0]->getDomain()); + $this->assertEquals('/', $cookies[0]->getPath()); + $this->assertFalse($cookies[0]->isSecure()); + $this->assertFalse($cookies[0]->isHttpOnly()); + + $this->assertEquals('test', $cookies[1]->getName()); + $this->assertNull($cookies[1]->getValue()); + + $this->assertEquals('ABC', $cookies[2]->getName()); + $this->assertEquals('AeD', $cookies[2]->getValue()); + $this->assertEquals(strtotime('Wed, 13 Jan 2021 22:23:01 GMT'), $cookies[2]->getExpiresTime()); + $this->assertEquals('dunglas.fr', $cookies[2]->getDomain()); + $this->assertEquals('/kevin', $cookies[2]->getPath()); + $this->assertTrue($cookies[2]->isSecure()); + $this->assertTrue($cookies[2]->isHttpOnly()); + if (\defined('Symfony\Component\HttpFoundation\Cookie::SAMESITE_STRICT')) { + $this->assertEquals(Cookie::SAMESITE_STRICT, $cookies[2]->getSameSite()); + } + + $this->assertEquals('The response body', $symfonyResponse->getContent()); + $this->assertEquals(200, $symfonyResponse->getStatusCode()); + + $symfonyResponse = $this->factory->createResponse($response, true); + + ob_start(); + $symfonyResponse->sendContent(); + $sentContent = ob_get_clean(); + + $this->assertEquals('The response body', $sentContent); + $this->assertEquals(200, $symfonyResponse->getStatusCode()); + } +} diff --git a/vendor/symfony/psr-http-message-bridge/Tests/Factory/PsrHttpFactoryTest.php b/vendor/symfony/psr-http-message-bridge/Tests/Factory/PsrHttpFactoryTest.php new file mode 100644 index 0000000..b47cefc --- /dev/null +++ b/vendor/symfony/psr-http-message-bridge/Tests/Factory/PsrHttpFactoryTest.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Tests\Factory; + +use Nyholm\Psr7\Factory\Psr17Factory; +use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory; +use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; + +/** + * @author Kévin Dunglas + * @author Antonio J. García Lagar + */ +class PsrHttpFactoryTest extends AbstractHttpMessageFactoryTest +{ + protected function buildHttpMessageFactory(): HttpMessageFactoryInterface + { + $factory = new Psr17Factory(); + + return new PsrHttpFactory($factory, $factory, $factory, $factory); + } +} diff --git a/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/App/Controller/PsrRequestController.php b/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/App/Controller/PsrRequestController.php new file mode 100644 index 0000000..18b7741 --- /dev/null +++ b/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/App/Controller/PsrRequestController.php @@ -0,0 +1,45 @@ +responseFactory = $responseFactory; + $this->streamFactory = $streamFactory; + } + + public function serverRequestAction(ServerRequestInterface $request): ResponseInterface + { + return $this->responseFactory + ->createResponse() + ->withBody($this->streamFactory->createStream(sprintf('%s', $request->getMethod()))); + } + + public function requestAction(RequestInterface $request): ResponseInterface + { + return $this->responseFactory + ->createResponse() + ->withStatus(403) + ->withBody($this->streamFactory->createStream(sprintf('%s %s', $request->getMethod(), $request->getBody()->getContents()))); + } + + public function messageAction(MessageInterface $request): ResponseInterface + { + return $this->responseFactory + ->createResponse() + ->withStatus(422) + ->withBody($this->streamFactory->createStream(sprintf('%s', $request->getHeader('X-My-Header')[0]))); + } +} diff --git a/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/App/Kernel.php b/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/App/Kernel.php new file mode 100644 index 0000000..aef8193 --- /dev/null +++ b/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/App/Kernel.php @@ -0,0 +1,76 @@ +add('server_request', '/server-request')->controller([PsrRequestController::class, 'serverRequestAction'])->methods(['GET']) + ->add('request', '/request')->controller([PsrRequestController::class, 'requestAction'])->methods(['POST']) + ->add('message', '/message')->controller([PsrRequestController::class, 'messageAction'])->methods(['PUT']) + ; + } + + protected function configureContainer(ContainerConfigurator $container): void + { + $container->extension('framework', [ + 'router' => ['utf8' => true], + 'secret' => 'for your eyes only', + 'test' => true, + ]); + + $container->services() + ->set('nyholm.psr_factory', Psr17Factory::class) + ->alias(ResponseFactoryInterface::class, 'nyholm.psr_factory') + ->alias(ServerRequestFactoryInterface::class, 'nyholm.psr_factory') + ->alias(StreamFactoryInterface::class, 'nyholm.psr_factory') + ->alias(UploadedFileFactoryInterface::class, 'nyholm.psr_factory') + ; + + $container->services() + ->defaults()->autowire()->autoconfigure() + ->set(HttpFoundationFactoryInterface::class, HttpFoundationFactory::class) + ->set(HttpMessageFactoryInterface::class, PsrHttpFactory::class) + ->set(PsrResponseListener::class) + ->set(PsrServerRequestResolver::class) + ; + + $container->services() + ->set('logger', NullLogger::class) + ->set(PsrRequestController::class)->public()->autowire() + ; + } +} diff --git a/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/App/Kernel44.php b/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/App/Kernel44.php new file mode 100644 index 0000000..e976ae2 --- /dev/null +++ b/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/App/Kernel44.php @@ -0,0 +1,67 @@ +add('/server-request', PsrRequestController::class.'::serverRequestAction')->setMethods(['GET']); + $routes->add('/request', PsrRequestController::class.'::requestAction')->setMethods(['POST']); + $routes->add('/message', PsrRequestController::class.'::messageAction')->setMethods(['PUT']); + } + + protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void + { + $container->loadFromExtension('framework', [ + 'secret' => 'for your eyes only', + 'test' => true, + ]); + + $container->register('nyholm.psr_factory', Psr17Factory::class); + $container->setAlias(ResponseFactoryInterface::class, 'nyholm.psr_factory'); + $container->setAlias(ServerRequestFactoryInterface::class, 'nyholm.psr_factory'); + $container->setAlias(StreamFactoryInterface::class, 'nyholm.psr_factory'); + $container->setAlias(UploadedFileFactoryInterface::class, 'nyholm.psr_factory'); + + $container->register(HttpFoundationFactoryInterface::class, HttpFoundationFactory::class)->setAutowired(true)->setAutoconfigured(true); + $container->register(HttpMessageFactoryInterface::class, PsrHttpFactory::class)->setAutowired(true)->setAutoconfigured(true); + $container->register(PsrResponseListener::class)->setAutowired(true)->setAutoconfigured(true); + $container->register(PsrServerRequestResolver::class)->setAutowired(true)->setAutoconfigured(true); + + $container->register('logger', NullLogger::class); + $container->register(PsrRequestController::class)->setPublic(true)->setAutowired(true); + } +} diff --git a/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/Message.php b/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/Message.php new file mode 100644 index 0000000..d561086 --- /dev/null +++ b/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/Message.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Tests\Fixtures; + +use Psr\Http\Message\MessageInterface; +use Psr\Http\Message\StreamInterface; + +/** + * Message. + * + * @author Kévin Dunglas + */ +class Message implements MessageInterface +{ + private $version = '1.1'; + private $headers = []; + private $body; + + public function __construct($version = '1.1', array $headers = [], StreamInterface $body = null) + { + $this->version = $version; + $this->headers = $headers; + $this->body = $body ?? new Stream(); + } + + public function getProtocolVersion(): string + { + return $this->version; + } + + /** + * {@inheritdoc} + * + * @return static + */ + public function withProtocolVersion($version) + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function getHeaders(): array + { + return $this->headers; + } + + public function hasHeader($name): bool + { + return isset($this->headers[$name]); + } + + public function getHeader($name): array + { + return $this->hasHeader($name) ? $this->headers[$name] : []; + } + + public function getHeaderLine($name): string + { + return $this->hasHeader($name) ? implode(',', $this->headers[$name]) : ''; + } + + /** + * {@inheritdoc} + * + * @return static + */ + public function withHeader($name, $value) + { + $this->headers[$name] = (array) $value; + + return $this; + } + + /** + * {@inheritdoc} + * + * @return static + */ + public function withAddedHeader($name, $value) + { + throw new \BadMethodCallException('Not implemented.'); + } + + /** + * {@inheritdoc} + * + * @return static + */ + public function withoutHeader($name) + { + unset($this->headers[$name]); + + return $this; + } + + public function getBody(): StreamInterface + { + return $this->body; + } + + /** + * {@inheritdoc} + * + * @return static + */ + public function withBody(StreamInterface $body) + { + throw new \BadMethodCallException('Not implemented.'); + } +} diff --git a/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/Response.php b/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/Response.php new file mode 100644 index 0000000..0bcf7f4 --- /dev/null +++ b/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/Response.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Tests\Fixtures; + +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamInterface; + +/** + * @author Kévin Dunglas + */ +class Response extends Message implements ResponseInterface +{ + private $statusCode; + + public function __construct($version = '1.1', array $headers = [], StreamInterface $body = null, $statusCode = 200) + { + parent::__construct($version, $headers, $body); + + $this->statusCode = $statusCode; + } + + public function getStatusCode(): int + { + return $this->statusCode; + } + + /** + * @return static + */ + public function withStatus($code, $reasonPhrase = '') + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function getReasonPhrase(): string + { + throw new \BadMethodCallException('Not implemented.'); + } +} diff --git a/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/ServerRequest.php b/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/ServerRequest.php new file mode 100644 index 0000000..b8df06a --- /dev/null +++ b/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/ServerRequest.php @@ -0,0 +1,202 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Tests\Fixtures; + +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\StreamInterface; +use Psr\Http\Message\UriInterface; + +/** + * @author Kévin Dunglas + */ +class ServerRequest extends Message implements ServerRequestInterface +{ + private $requestTarget; + private $method; + private $uri; + private $server; + private $cookies; + private $query; + private $uploadedFiles; + private $data; + private $attributes; + + public function __construct($version = '1.1', array $headers = [], StreamInterface $body = null, $requestTarget = '/', $method = 'GET', $uri = null, array $server = [], array $cookies = [], array $query = [], array $uploadedFiles = [], $data = null, array $attributes = []) + { + parent::__construct($version, $headers, $body); + + $this->requestTarget = $requestTarget; + $this->method = $method; + $this->uri = $uri; + $this->server = $server; + $this->cookies = $cookies; + $this->query = $query; + $this->uploadedFiles = $uploadedFiles; + $this->data = $data; + $this->attributes = $attributes; + } + + public function getRequestTarget(): string + { + return $this->requestTarget; + } + + /** + * {@inheritdoc} + * + * @return static + */ + public function withRequestTarget($requestTarget) + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function getMethod(): string + { + return $this->method; + } + + /** + * {@inheritdoc} + * + * @return static + */ + public function withMethod($method) + { + throw new \BadMethodCallException('Not implemented.'); + } + + /** + * {@inheritdoc} + * + * @return UriInterface + */ + public function getUri() + { + return $this->uri; + } + + /** + * {@inheritdoc} + * + * @return static + */ + public function withUri(UriInterface $uri, $preserveHost = false) + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function getServerParams(): array + { + return $this->server; + } + + public function getCookieParams(): array + { + return $this->cookies; + } + + /** + * {@inheritdoc} + * + * @return static + */ + public function withCookieParams(array $cookies) + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function getQueryParams(): array + { + return $this->query; + } + + /** + * {@inheritdoc} + * + * @return static + */ + public function withQueryParams(array $query) + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function getUploadedFiles(): array + { + return $this->uploadedFiles; + } + + /** + * {@inheritdoc} + * + * @return static + */ + public function withUploadedFiles(array $uploadedFiles) + { + throw new \BadMethodCallException('Not implemented.'); + } + + /** + * {@inheritdoc} + * + * @return array|object|null + */ + public function getParsedBody() + { + return $this->data; + } + + /** + * {@inheritdoc} + * + * @return static + */ + public function withParsedBody($data) + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function getAttributes(): array + { + return $this->attributes; + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function getAttribute($name, $default = null) + { + return $this->attributes[$name] ?? $default; + } + + /** + * {@inheritdoc} + * + * @return static + */ + public function withAttribute($name, $value) + { + throw new \BadMethodCallException('Not implemented.'); + } + + /** + * {@inheritdoc} + * + * @return static + */ + public function withoutAttribute($name) + { + throw new \BadMethodCallException('Not implemented.'); + } +} diff --git a/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/Stream.php b/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/Stream.php new file mode 100644 index 0000000..f664bae --- /dev/null +++ b/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/Stream.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Tests\Fixtures; + +use Psr\Http\Message\StreamInterface; + +/** + * @author Kévin Dunglas + */ +class Stream implements StreamInterface +{ + private $stringContent; + private $eof = true; + + public function __construct($stringContent = '') + { + $this->stringContent = $stringContent; + } + + public function __toString(): string + { + return $this->stringContent; + } + + public function close(): void + { + } + + public function detach() + { + return fopen('data://text/plain,'.$this->stringContent, 'r'); + } + + public function getSize(): ?int + { + return null; + } + + public function tell(): int + { + return 0; + } + + public function eof(): bool + { + return $this->eof; + } + + public function isSeekable(): bool + { + return true; + } + + public function seek($offset, $whence = \SEEK_SET): void + { + } + + public function rewind(): void + { + $this->eof = false; + } + + public function isWritable(): bool + { + return false; + } + + public function write($string): int + { + return \strlen($string); + } + + public function isReadable(): bool + { + return true; + } + + public function read($length): string + { + $this->eof = true; + + return $this->stringContent; + } + + public function getContents(): string + { + return $this->stringContent; + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function getMetadata($key = null) + { + return null; + } +} diff --git a/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/UploadedFile.php b/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/UploadedFile.php new file mode 100644 index 0000000..9004008 --- /dev/null +++ b/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/UploadedFile.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Tests\Fixtures; + +use Psr\Http\Message\UploadedFileInterface; + +/** + * @author Kévin Dunglas + */ +class UploadedFile implements UploadedFileInterface +{ + private $filePath; + private $size; + private $error; + private $clientFileName; + private $clientMediaType; + + public function __construct($filePath, $size = null, $error = \UPLOAD_ERR_OK, $clientFileName = null, $clientMediaType = null) + { + $this->filePath = $filePath; + $this->size = $size; + $this->error = $error; + $this->clientFileName = $clientFileName; + $this->clientMediaType = $clientMediaType; + } + + public function getStream(): Stream + { + return new Stream(file_get_contents($this->filePath)); + } + + public function moveTo($targetPath): void + { + rename($this->filePath, $targetPath); + } + + public function getSize(): ?int + { + return $this->size; + } + + public function getError(): int + { + return $this->error; + } + + public function getClientFilename(): ?string + { + return $this->clientFileName; + } + + public function getClientMediaType(): ?string + { + return $this->clientMediaType; + } +} diff --git a/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/Uri.php b/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/Uri.php new file mode 100644 index 0000000..48f513d --- /dev/null +++ b/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/Uri.php @@ -0,0 +1,170 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Tests\Fixtures; + +use Psr\Http\Message\UriInterface; + +/** + * @author Rougin Royce Gutib + */ +class Uri implements UriInterface +{ + private $scheme = ''; + private $userInfo = ''; + private $host = ''; + private $port; + private $path = ''; + private $query = ''; + private $fragment = ''; + private $uriString; + + public function __construct(string $uri = '') + { + $parts = parse_url($uri); + + $this->scheme = $parts['scheme'] ?? ''; + $this->userInfo = $parts['user'] ?? ''; + $this->host = $parts['host'] ?? ''; + $this->port = $parts['port'] ?? null; + $this->path = $parts['path'] ?? ''; + $this->query = $parts['query'] ?? ''; + $this->fragment = $parts['fragment'] ?? ''; + $this->uriString = $uri; + } + + public function getScheme(): string + { + return $this->scheme; + } + + public function getAuthority(): string + { + if (empty($this->host)) { + return ''; + } + + $authority = $this->host; + + if (!empty($this->userInfo)) { + $authority = $this->userInfo.'@'.$authority; + } + + $authority .= ':'.$this->port; + + return $authority; + } + + public function getUserInfo(): string + { + return $this->userInfo; + } + + public function getHost(): string + { + return $this->host; + } + + public function getPort(): ?int + { + return $this->port; + } + + public function getPath(): string + { + return $this->path; + } + + public function getQuery(): string + { + return $this->query; + } + + public function getFragment(): string + { + return $this->fragment; + } + + /** + * {@inheritdoc} + * + * @return static + */ + public function withScheme($scheme) + { + throw new \BadMethodCallException('Not implemented.'); + } + + /** + * {@inheritdoc} + * + * @return static + */ + public function withUserInfo($user, $password = null) + { + throw new \BadMethodCallException('Not implemented.'); + } + + /** + * {@inheritdoc} + * + * @return static + */ + public function withHost($host) + { + throw new \BadMethodCallException('Not implemented.'); + } + + /** + * {@inheritdoc} + * + * @return static + */ + public function withPort($port) + { + throw new \BadMethodCallException('Not implemented.'); + } + + /** + * {@inheritdoc} + * + * @return static + */ + public function withPath($path) + { + throw new \BadMethodCallException('Not implemented.'); + } + + /** + * {@inheritdoc} + * + * @return static + */ + public function withQuery($query) + { + throw new \BadMethodCallException('Not implemented.'); + } + + /** + * {@inheritdoc} + * + * @return static + */ + public function withFragment($fragment) + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function __toString(): string + { + return $this->uriString; + } +} diff --git a/vendor/symfony/psr-http-message-bridge/Tests/Functional/ControllerTest.php b/vendor/symfony/psr-http-message-bridge/Tests/Functional/ControllerTest.php new file mode 100644 index 0000000..0b88405 --- /dev/null +++ b/vendor/symfony/psr-http-message-bridge/Tests/Functional/ControllerTest.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Tests\Functional; + +use Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\App\Kernel; +use Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\App\Kernel44; +use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; +use Symfony\Component\HttpKernel\Kernel as SymfonyKernel; + +/** + * @author Alexander M. Turek + */ +final class ControllerTest extends WebTestCase +{ + public function testServerRequestAction() + { + $client = self::createClient(); + $crawler = $client->request('GET', '/server-request'); + + self::assertResponseStatusCodeSame(200); + self::assertSame('GET', $crawler->text()); + } + + public function testRequestAction() + { + $client = self::createClient(); + $crawler = $client->request('POST', '/request', [], [], [], 'some content'); + + self::assertResponseStatusCodeSame(403); + self::assertSame('POST some content', $crawler->text()); + } + + public function testMessageAction() + { + $client = self::createClient(); + $crawler = $client->request('PUT', '/message', [], [], ['HTTP_X_MY_HEADER' => 'some content']); + + self::assertResponseStatusCodeSame(422); + self::assertSame('some content', $crawler->text()); + } + + protected static function getKernelClass(): string + { + return SymfonyKernel::VERSION_ID >= 50200 ? Kernel::class : Kernel44::class; + } +} diff --git a/vendor/symfony/psr-http-message-bridge/Tests/Functional/CovertTest.php b/vendor/symfony/psr-http-message-bridge/Tests/Functional/CovertTest.php new file mode 100644 index 0000000..3d72b71 --- /dev/null +++ b/vendor/symfony/psr-http-message-bridge/Tests/Functional/CovertTest.php @@ -0,0 +1,237 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Tests\Functional; + +use Nyholm\Psr7\Factory\Psr17Factory; +use Nyholm\Psr7\Response as Psr7Response; +use Nyholm\Psr7\ServerRequest as Psr7Request; +use Nyholm\Psr7\Stream as Psr7Stream; +use PHPUnit\Framework\TestCase; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory; +use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory; +use Symfony\Bridge\PsrHttpMessage\HttpFoundationFactoryInterface; +use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; +use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Test to convert a request/response back and forth to make sure we do not loose data. + * + * @author Tobias Nyholm + */ +class CovertTest extends TestCase +{ + protected function setUp(): void + { + if (!class_exists(Psr7Request::class)) { + $this->markTestSkipped('nyholm/psr7 is not installed.'); + } + } + + /** + * @dataProvider requestProvider + * + * @param Request|ServerRequestInterface $request + * @param HttpFoundationFactoryInterface|HttpMessageFactoryInterface $firstFactory + * @param HttpFoundationFactoryInterface|HttpMessageFactoryInterface $secondFactory + */ + public function testConvertRequestMultipleTimes($request, $firstFactory, $secondFactory) + { + $temporaryRequest = $firstFactory->createRequest($request); + $finalRequest = $secondFactory->createRequest($temporaryRequest); + + if ($finalRequest instanceof Request) { + $this->assertEquals($request->getBasePath(), $finalRequest->getBasePath()); + $this->assertEquals($request->getBaseUrl(), $finalRequest->getBaseUrl()); + $this->assertEquals($request->getContent(), $finalRequest->getContent()); + $this->assertEquals($request->getEncodings(), $finalRequest->getEncodings()); + $this->assertEquals($request->getETags(), $finalRequest->getETags()); + $this->assertEquals($request->getHost(), $finalRequest->getHost()); + $this->assertEquals($request->getHttpHost(), $finalRequest->getHttpHost()); + $this->assertEquals($request->getMethod(), $finalRequest->getMethod()); + $this->assertEquals($request->getPassword(), $finalRequest->getPassword()); + $this->assertEquals($request->getPathInfo(), $finalRequest->getPathInfo()); + $this->assertEquals($request->getPort(), $finalRequest->getPort()); + $this->assertEquals($request->getProtocolVersion(), $finalRequest->getProtocolVersion()); + $this->assertEquals($request->getQueryString(), $finalRequest->getQueryString()); + $this->assertEquals($request->getRequestUri(), $finalRequest->getRequestUri()); + $this->assertEquals($request->getScheme(), $finalRequest->getScheme()); + $this->assertEquals($request->getSchemeAndHttpHost(), $finalRequest->getSchemeAndHttpHost()); + $this->assertEquals($request->getScriptName(), $finalRequest->getScriptName()); + $this->assertEquals($request->getUri(), $finalRequest->getUri()); + $this->assertEquals($request->getUser(), $finalRequest->getUser()); + $this->assertEquals($request->getUserInfo(), $finalRequest->getUserInfo()); + } elseif ($finalRequest instanceof ServerRequestInterface) { + $strToLower = function ($arr) { + foreach ($arr as $key => $value) { + yield strtolower($key) => $value; + } + }; + $this->assertEquals($request->getAttributes(), $finalRequest->getAttributes()); + $this->assertEquals($request->getCookieParams(), $finalRequest->getCookieParams()); + $this->assertEquals((array) $request->getParsedBody(), (array) $finalRequest->getParsedBody()); + $this->assertEquals($request->getQueryParams(), $finalRequest->getQueryParams()); + // PSR7 does not define a "withServerParams" so this is impossible to implement without knowing the PSR7 implementation. + //$this->assertEquals($request->getServerParams(), $finalRequest->getServerParams()); + $this->assertEquals($request->getUploadedFiles(), $finalRequest->getUploadedFiles()); + $this->assertEquals($request->getMethod(), $finalRequest->getMethod()); + $this->assertEquals($request->getRequestTarget(), $finalRequest->getRequestTarget()); + $this->assertEquals((string) $request->getUri(), (string) $finalRequest->getUri()); + $this->assertEquals((string) $request->getBody(), (string) $finalRequest->getBody()); + $this->assertEquals($strToLower($request->getHeaders()), $strToLower($finalRequest->getHeaders())); + $this->assertEquals($request->getProtocolVersion(), $finalRequest->getProtocolVersion()); + } else { + $this->fail('$finalRequest must be an instance of PSR7 or a HTTPFoundation request'); + } + } + + public function requestProvider() + { + $sfRequest = new Request( + [ + 'foo' => '1', + 'bar' => ['baz' => '42'], + ], + [ + 'twitter' => [ + '@dunglas' => 'Kévin Dunglas', + '@coopTilleuls' => 'Les-Tilleuls.coop', + ], + 'baz' => '2', + ], + [ + 'a2' => ['foo' => 'bar'], + ], + [ + 'c1' => 'foo', + 'c2' => ['c3' => 'bar'], + ], + [ + 'f1' => $this->createUploadedFile('F1', 'f1.txt', 'text/plain', \UPLOAD_ERR_OK), + 'foo' => ['f2' => $this->createUploadedFile('F2', 'f2.txt', 'text/plain', \UPLOAD_ERR_OK)], + ], + [ + 'REQUEST_METHOD' => 'POST', + 'HTTP_HOST' => 'dunglas.fr', + 'SERVER_NAME' => 'dunglas.fr', + 'SERVER_PORT' => null, + 'HTTP_X_SYMFONY' => '2.8', + 'REQUEST_URI' => '/testCreateRequest?foo=1&bar%5Bbaz%5D=42', + 'QUERY_STRING' => 'foo=1&bar%5Bbaz%5D=42', + ], + 'Content' + ); + + $psr7Requests = [ + (new Psr7Request('POST', 'http://tnyholm.se/foo/?bar=biz')) + ->withQueryParams(['bar' => 'biz']), + new Psr7Request('GET', 'https://hey-octave.com/'), + new Psr7Request('GET', 'https://hey-octave.com:443/'), + new Psr7Request('GET', 'https://hey-octave.com:4242/'), + new Psr7Request('GET', 'http://hey-octave.com:80/'), + ]; + + $nyholmFactory = new Psr17Factory(); + $psr17Factory = new PsrHttpFactory($nyholmFactory, $nyholmFactory, $nyholmFactory, $nyholmFactory); + $symfonyFactory = new HttpFoundationFactory(); + + return array_merge([ + [$sfRequest, $psr17Factory, $symfonyFactory], + ], array_map(function ($psr7Request) use ($symfonyFactory, $psr17Factory) { + return [$psr7Request, $symfonyFactory, $psr17Factory]; + }, $psr7Requests)); + } + + /** + * @dataProvider responseProvider + * + * @param Response|ResponseInterface $response + * @param HttpFoundationFactoryInterface|HttpMessageFactoryInterface $firstFactory + * @param HttpFoundationFactoryInterface|HttpMessageFactoryInterface $secondFactory + */ + public function testConvertResponseMultipleTimes($response, $firstFactory, $secondFactory) + { + $temporaryResponse = $firstFactory->createResponse($response); + $finalResponse = $secondFactory->createResponse($temporaryResponse); + + if ($finalResponse instanceof Response) { + $this->assertEquals($response->getAge(), $finalResponse->getAge()); + $this->assertEquals($response->getCharset(), $finalResponse->getCharset()); + $this->assertEquals($response->getContent(), $finalResponse->getContent()); + $this->assertEquals($response->getDate(), $finalResponse->getDate()); + $this->assertEquals($response->getEtag(), $finalResponse->getEtag()); + $this->assertEquals($response->getExpires(), $finalResponse->getExpires()); + $this->assertEquals($response->getLastModified(), $finalResponse->getLastModified()); + $this->assertEquals($response->getMaxAge(), $finalResponse->getMaxAge()); + $this->assertEquals($response->getProtocolVersion(), $finalResponse->getProtocolVersion()); + $this->assertEquals($response->getStatusCode(), $finalResponse->getStatusCode()); + $this->assertEquals($response->getTtl(), $finalResponse->getTtl()); + } elseif ($finalResponse instanceof ResponseInterface) { + $strToLower = function ($arr) { + foreach ($arr as $key => $value) { + yield strtolower($key) => $value; + } + }; + $this->assertEquals($response->getStatusCode(), $finalResponse->getStatusCode()); + $this->assertEquals($response->getReasonPhrase(), $finalResponse->getReasonPhrase()); + $this->assertEquals((string) $response->getBody(), (string) $finalResponse->getBody()); + $this->assertEquals($strToLower($response->getHeaders()), $strToLower($finalResponse->getHeaders())); + $this->assertEquals($response->getProtocolVersion(), $finalResponse->getProtocolVersion()); + } else { + $this->fail('$finalResponse must be an instance of PSR7 or a HTTPFoundation response'); + } + } + + public function responseProvider() + { + $sfResponse = new Response( + 'Response content.', + 202, + ['x-symfony' => ['3.4']] + ); + + if (method_exists(Cookie::class, 'create')) { + $cookie = Cookie::create('city', 'Lille', new \DateTime('Wed, 13 Jan 2021 22:23:01 GMT')); + } else { + $cookie = new Cookie('city', 'Lille', new \DateTime('Wed, 13 Jan 2021 22:23:01 GMT')); + } + + $sfResponse->headers->setCookie($cookie); + $body = Psr7Stream::create(); + $status = 302; + $headers = [ + 'location' => ['http://example.com/'], + ]; + $zendResponse = new Psr7Response($status, $headers, $body); + + $nyholmFactory = new Psr17Factory(); + $psr17Factory = new PsrHttpFactory($nyholmFactory, $nyholmFactory, $nyholmFactory, $nyholmFactory); + $symfonyFactory = new HttpFoundationFactory(); + + return [ + [$sfResponse, $psr17Factory, $symfonyFactory], + [$zendResponse, $symfonyFactory, $psr17Factory], + ]; + } + + private function createUploadedFile($content, $originalName, $mimeType, $error) + { + $path = tempnam(sys_get_temp_dir(), uniqid()); + file_put_contents($path, $content); + + return new UploadedFile($path, $originalName, $mimeType, $error, true); + } +} diff --git a/vendor/symfony/psr-http-message-bridge/composer.json b/vendor/symfony/psr-http-message-bridge/composer.json new file mode 100644 index 0000000..0282307 --- /dev/null +++ b/vendor/symfony/psr-http-message-bridge/composer.json @@ -0,0 +1,47 @@ +{ + "name": "symfony/psr-http-message-bridge", + "type": "symfony-bridge", + "description": "PSR HTTP message bridge", + "keywords": ["http", "psr-7", "psr-17", "http-message"], + "homepage": "http://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0", + "symfony/http-foundation": "^4.4 || ^5.0 || ^6.0" + }, + "require-dev": { + "symfony/browser-kit": "^4.4 || ^5.0 || ^6.0", + "symfony/config": "^4.4 || ^5.0 || ^6.0", + "symfony/event-dispatcher": "^4.4 || ^5.0 || ^6.0", + "symfony/framework-bundle": "^4.4 || ^5.0 || ^6.0", + "symfony/http-kernel": "^4.4 || ^5.0 || ^6.0", + "symfony/phpunit-bridge": "^5.4@dev || ^6.0", + "nyholm/psr7": "^1.1", + "psr/log": "^1.1 || ^2 || ^3" + }, + "suggest": { + "nyholm/psr7": "For a super lightweight PSR-7/17 implementation" + }, + "autoload": { + "psr-4": { "Symfony\\Bridge\\PsrHttpMessage\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "extra": { + "branch-alias": { + "dev-main": "2.1-dev" + } + } +} diff --git a/vendor/symfony/psr-http-message-bridge/phpunit.xml.dist b/vendor/symfony/psr-http-message-bridge/phpunit.xml.dist new file mode 100644 index 0000000..43aeaa3 --- /dev/null +++ b/vendor/symfony/psr-http-message-bridge/phpunit.xml.dist @@ -0,0 +1,29 @@ + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + + diff --git a/vendor/symfony/service-contracts/.gitignore b/vendor/symfony/service-contracts/.gitignore new file mode 100644 index 0000000..c49a5d8 --- /dev/null +++ b/vendor/symfony/service-contracts/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/service-contracts/Attribute/Required.php b/vendor/symfony/service-contracts/Attribute/Required.php new file mode 100644 index 0000000..9df8511 --- /dev/null +++ b/vendor/symfony/service-contracts/Attribute/Required.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service\Attribute; + +/** + * A required dependency. + * + * This attribute indicates that a property holds a required dependency. The annotated property or method should be + * considered during the instantiation process of the containing class. + * + * @author Alexander M. Turek + */ +#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] +final class Required +{ +} diff --git a/vendor/symfony/service-contracts/Attribute/SubscribedService.php b/vendor/symfony/service-contracts/Attribute/SubscribedService.php new file mode 100644 index 0000000..10d1bc3 --- /dev/null +++ b/vendor/symfony/service-contracts/Attribute/SubscribedService.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service\Attribute; + +use Symfony\Contracts\Service\ServiceSubscriberTrait; + +/** + * Use with {@see ServiceSubscriberTrait} to mark a method's return type + * as a subscribed service. + * + * @author Kevin Bond + */ +#[\Attribute(\Attribute::TARGET_METHOD)] +final class SubscribedService +{ + /** + * @param string|null $key The key to use for the service + * If null, use "ClassName::methodName" + */ + public function __construct( + public ?string $key = null + ) { + } +} diff --git a/vendor/symfony/service-contracts/CHANGELOG.md b/vendor/symfony/service-contracts/CHANGELOG.md new file mode 100644 index 0000000..7932e26 --- /dev/null +++ b/vendor/symfony/service-contracts/CHANGELOG.md @@ -0,0 +1,5 @@ +CHANGELOG +========= + +The changelog is maintained for all Symfony contracts at the following URL: +https://github.com/symfony/contracts/blob/main/CHANGELOG.md diff --git a/vendor/symfony/service-contracts/LICENSE b/vendor/symfony/service-contracts/LICENSE new file mode 100644 index 0000000..2358414 --- /dev/null +++ b/vendor/symfony/service-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2021 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/service-contracts/README.md b/vendor/symfony/service-contracts/README.md new file mode 100644 index 0000000..41e054a --- /dev/null +++ b/vendor/symfony/service-contracts/README.md @@ -0,0 +1,9 @@ +Symfony Service Contracts +========================= + +A set of abstractions extracted out of the Symfony components. + +Can be used to build on semantics that the Symfony components proved useful - and +that already have battle tested implementations. + +See https://github.com/symfony/contracts/blob/main/README.md for more information. diff --git a/vendor/symfony/service-contracts/ResetInterface.php b/vendor/symfony/service-contracts/ResetInterface.php new file mode 100644 index 0000000..1af1075 --- /dev/null +++ b/vendor/symfony/service-contracts/ResetInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +/** + * Provides a way to reset an object to its initial state. + * + * When calling the "reset()" method on an object, it should be put back to its + * initial state. This usually means clearing any internal buffers and forwarding + * the call to internal dependencies. All properties of the object should be put + * back to the same state it had when it was first ready to use. + * + * This method could be called, for example, to recycle objects that are used as + * services, so that they can be used to handle several requests in the same + * process loop (note that we advise making your services stateless instead of + * implementing this interface when possible.) + */ +interface ResetInterface +{ + public function reset(); +} diff --git a/vendor/symfony/service-contracts/ServiceLocatorTrait.php b/vendor/symfony/service-contracts/ServiceLocatorTrait.php new file mode 100644 index 0000000..74dfa43 --- /dev/null +++ b/vendor/symfony/service-contracts/ServiceLocatorTrait.php @@ -0,0 +1,128 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\NotFoundExceptionInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(ContainerExceptionInterface::class); +class_exists(NotFoundExceptionInterface::class); + +/** + * A trait to help implement ServiceProviderInterface. + * + * @author Robin Chalas + * @author Nicolas Grekas + */ +trait ServiceLocatorTrait +{ + private $factories; + private $loading = []; + private $providedTypes; + + /** + * @param callable[] $factories + */ + public function __construct(array $factories) + { + $this->factories = $factories; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function has(string $id) + { + return isset($this->factories[$id]); + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function get(string $id) + { + if (!isset($this->factories[$id])) { + throw $this->createNotFoundException($id); + } + + if (isset($this->loading[$id])) { + $ids = array_values($this->loading); + $ids = \array_slice($this->loading, array_search($id, $ids)); + $ids[] = $id; + + throw $this->createCircularReferenceException($id, $ids); + } + + $this->loading[$id] = $id; + try { + return $this->factories[$id]($this); + } finally { + unset($this->loading[$id]); + } + } + + /** + * {@inheritdoc} + */ + public function getProvidedServices(): array + { + if (null === $this->providedTypes) { + $this->providedTypes = []; + + foreach ($this->factories as $name => $factory) { + if (!\is_callable($factory)) { + $this->providedTypes[$name] = '?'; + } else { + $type = (new \ReflectionFunction($factory))->getReturnType(); + + $this->providedTypes[$name] = $type ? ($type->allowsNull() ? '?' : '').($type instanceof \ReflectionNamedType ? $type->getName() : $type) : '?'; + } + } + } + + return $this->providedTypes; + } + + private function createNotFoundException(string $id): NotFoundExceptionInterface + { + if (!$alternatives = array_keys($this->factories)) { + $message = 'is empty...'; + } else { + $last = array_pop($alternatives); + if ($alternatives) { + $message = sprintf('only knows about the "%s" and "%s" services.', implode('", "', $alternatives), $last); + } else { + $message = sprintf('only knows about the "%s" service.', $last); + } + } + + if ($this->loading) { + $message = sprintf('The service "%s" has a dependency on a non-existent service "%s". This locator %s', end($this->loading), $id, $message); + } else { + $message = sprintf('Service "%s" not found: the current service locator %s', $id, $message); + } + + return new class($message) extends \InvalidArgumentException implements NotFoundExceptionInterface { + }; + } + + private function createCircularReferenceException(string $id, array $path): ContainerExceptionInterface + { + return new class(sprintf('Circular reference detected for service "%s", path: "%s".', $id, implode(' -> ', $path))) extends \RuntimeException implements ContainerExceptionInterface { + }; + } +} diff --git a/vendor/symfony/service-contracts/ServiceProviderInterface.php b/vendor/symfony/service-contracts/ServiceProviderInterface.php new file mode 100644 index 0000000..c60ad0b --- /dev/null +++ b/vendor/symfony/service-contracts/ServiceProviderInterface.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +use Psr\Container\ContainerInterface; + +/** + * A ServiceProviderInterface exposes the identifiers and the types of services provided by a container. + * + * @author Nicolas Grekas + * @author Mateusz Sip + */ +interface ServiceProviderInterface extends ContainerInterface +{ + /** + * Returns an associative array of service types keyed by the identifiers provided by the current container. + * + * Examples: + * + * * ['logger' => 'Psr\Log\LoggerInterface'] means the object provides a service named "logger" that implements Psr\Log\LoggerInterface + * * ['foo' => '?'] means the container provides service name "foo" of unspecified type + * * ['bar' => '?Bar\Baz'] means the container provides a service "bar" of type Bar\Baz|null + * + * @return string[] The provided service types, keyed by service names + */ + public function getProvidedServices(): array; +} diff --git a/vendor/symfony/service-contracts/ServiceSubscriberInterface.php b/vendor/symfony/service-contracts/ServiceSubscriberInterface.php new file mode 100644 index 0000000..098ab90 --- /dev/null +++ b/vendor/symfony/service-contracts/ServiceSubscriberInterface.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +/** + * A ServiceSubscriber exposes its dependencies via the static {@link getSubscribedServices} method. + * + * The getSubscribedServices method returns an array of service types required by such instances, + * optionally keyed by the service names used internally. Service types that start with an interrogation + * mark "?" are optional, while the other ones are mandatory service dependencies. + * + * The injected service locators SHOULD NOT allow access to any other services not specified by the method. + * + * It is expected that ServiceSubscriber instances consume PSR-11-based service locators internally. + * This interface does not dictate any injection method for these service locators, although constructor + * injection is recommended. + * + * @author Nicolas Grekas + */ +interface ServiceSubscriberInterface +{ + /** + * Returns an array of service types required by such instances, optionally keyed by the service names used internally. + * + * For mandatory dependencies: + * + * * ['logger' => 'Psr\Log\LoggerInterface'] means the objects use the "logger" name + * internally to fetch a service which must implement Psr\Log\LoggerInterface. + * * ['loggers' => 'Psr\Log\LoggerInterface[]'] means the objects use the "loggers" name + * internally to fetch an iterable of Psr\Log\LoggerInterface instances. + * * ['Psr\Log\LoggerInterface'] is a shortcut for + * * ['Psr\Log\LoggerInterface' => 'Psr\Log\LoggerInterface'] + * + * otherwise: + * + * * ['logger' => '?Psr\Log\LoggerInterface'] denotes an optional dependency + * * ['loggers' => '?Psr\Log\LoggerInterface[]'] denotes an optional iterable dependency + * * ['?Psr\Log\LoggerInterface'] is a shortcut for + * * ['Psr\Log\LoggerInterface' => '?Psr\Log\LoggerInterface'] + * + * @return string[] The required service types, optionally keyed by service names + */ + public static function getSubscribedServices(); +} diff --git a/vendor/symfony/service-contracts/ServiceSubscriberTrait.php b/vendor/symfony/service-contracts/ServiceSubscriberTrait.php new file mode 100644 index 0000000..46cd007 --- /dev/null +++ b/vendor/symfony/service-contracts/ServiceSubscriberTrait.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +use Psr\Container\ContainerInterface; +use Symfony\Contracts\Service\Attribute\SubscribedService; + +/** + * Implementation of ServiceSubscriberInterface that determines subscribed services from + * method return types. Service ids are available as "ClassName::methodName". + * + * @author Kevin Bond + */ +trait ServiceSubscriberTrait +{ + /** @var ContainerInterface */ + protected $container; + + /** + * {@inheritdoc} + */ + public static function getSubscribedServices(): array + { + static $services; + + if (null !== $services) { + return $services; + } + + $services = \is_callable(['parent', __FUNCTION__]) ? parent::getSubscribedServices() : []; + $attributeOptIn = false; + + if (\PHP_VERSION_ID >= 80000) { + foreach ((new \ReflectionClass(self::class))->getMethods() as $method) { + if (self::class !== $method->getDeclaringClass()->name) { + continue; + } + + if (!$attribute = $method->getAttributes(SubscribedService::class)[0] ?? null) { + continue; + } + + if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) { + throw new \LogicException(sprintf('Cannot use "%s" on method "%s::%s()" (can only be used on non-static, non-abstract methods with no parameters).', SubscribedService::class, self::class, $method->name)); + } + + if (!$returnType = $method->getReturnType()) { + throw new \LogicException(sprintf('Cannot use "%s" on methods without a return type in "%s::%s()".', SubscribedService::class, $method->name, self::class)); + } + + $serviceId = $returnType instanceof \ReflectionNamedType ? $returnType->getName() : (string) $returnType; + + if ($returnType->allowsNull()) { + $serviceId = '?'.$serviceId; + } + + $services[$attribute->newInstance()->key ?? self::class.'::'.$method->name] = $serviceId; + $attributeOptIn = true; + } + } + + if (!$attributeOptIn) { + foreach ((new \ReflectionClass(self::class))->getMethods() as $method) { + if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) { + continue; + } + + if (self::class !== $method->getDeclaringClass()->name) { + continue; + } + + if (!($returnType = $method->getReturnType()) instanceof \ReflectionNamedType) { + continue; + } + + if ($returnType->isBuiltin()) { + continue; + } + + if (\PHP_VERSION_ID >= 80000) { + trigger_deprecation('symfony/service-contracts', '2.5', 'Using "%s" in "%s" without using the "%s" attribute on any method is deprecated.', ServiceSubscriberTrait::class, self::class, SubscribedService::class); + } + + $services[self::class.'::'.$method->name] = '?'.($returnType instanceof \ReflectionNamedType ? $returnType->getName() : $returnType); + } + } + + return $services; + } + + /** + * @required + * + * @return ContainerInterface|null + */ + public function setContainer(ContainerInterface $container) + { + $this->container = $container; + + if (\is_callable(['parent', __FUNCTION__])) { + return parent::setContainer($container); + } + + return null; + } +} diff --git a/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php b/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php new file mode 100644 index 0000000..2a1b565 --- /dev/null +++ b/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service\Test; + +use PHPUnit\Framework\TestCase; +use Psr\Container\ContainerInterface; +use Symfony\Contracts\Service\ServiceLocatorTrait; + +abstract class ServiceLocatorTest extends TestCase +{ + /** + * @return ContainerInterface + */ + protected function getServiceLocator(array $factories) + { + return new class($factories) implements ContainerInterface { + use ServiceLocatorTrait; + }; + } + + public function testHas() + { + $locator = $this->getServiceLocator([ + 'foo' => function () { return 'bar'; }, + 'bar' => function () { return 'baz'; }, + function () { return 'dummy'; }, + ]); + + $this->assertTrue($locator->has('foo')); + $this->assertTrue($locator->has('bar')); + $this->assertFalse($locator->has('dummy')); + } + + public function testGet() + { + $locator = $this->getServiceLocator([ + 'foo' => function () { return 'bar'; }, + 'bar' => function () { return 'baz'; }, + ]); + + $this->assertSame('bar', $locator->get('foo')); + $this->assertSame('baz', $locator->get('bar')); + } + + public function testGetDoesNotMemoize() + { + $i = 0; + $locator = $this->getServiceLocator([ + 'foo' => function () use (&$i) { + ++$i; + + return 'bar'; + }, + ]); + + $this->assertSame('bar', $locator->get('foo')); + $this->assertSame('bar', $locator->get('foo')); + $this->assertSame(2, $i); + } + + public function testThrowsOnUndefinedInternalService() + { + if (!$this->getExpectedException()) { + $this->expectException(\Psr\Container\NotFoundExceptionInterface::class); + $this->expectExceptionMessage('The service "foo" has a dependency on a non-existent service "bar". This locator only knows about the "foo" service.'); + } + $locator = $this->getServiceLocator([ + 'foo' => function () use (&$locator) { return $locator->get('bar'); }, + ]); + + $locator->get('foo'); + } + + public function testThrowsOnCircularReference() + { + $this->expectException(\Psr\Container\ContainerExceptionInterface::class); + $this->expectExceptionMessage('Circular reference detected for service "bar", path: "bar -> baz -> bar".'); + $locator = $this->getServiceLocator([ + 'foo' => function () use (&$locator) { return $locator->get('bar'); }, + 'bar' => function () use (&$locator) { return $locator->get('baz'); }, + 'baz' => function () use (&$locator) { return $locator->get('bar'); }, + ]); + + $locator->get('foo'); + } +} diff --git a/vendor/symfony/service-contracts/composer.json b/vendor/symfony/service-contracts/composer.json new file mode 100644 index 0000000..e680798 --- /dev/null +++ b/vendor/symfony/service-contracts/composer.json @@ -0,0 +1,42 @@ +{ + "name": "symfony/service-contracts", + "type": "library", + "description": "Generic abstractions related to writing services", + "keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "autoload": { + "psr-4": { "Symfony\\Contracts\\Service\\": "" } + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + } +} diff --git a/vendor/symfony/translation-contracts/.gitignore b/vendor/symfony/translation-contracts/.gitignore new file mode 100644 index 0000000..c49a5d8 --- /dev/null +++ b/vendor/symfony/translation-contracts/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/translation-contracts/CHANGELOG.md b/vendor/symfony/translation-contracts/CHANGELOG.md new file mode 100644 index 0000000..7932e26 --- /dev/null +++ b/vendor/symfony/translation-contracts/CHANGELOG.md @@ -0,0 +1,5 @@ +CHANGELOG +========= + +The changelog is maintained for all Symfony contracts at the following URL: +https://github.com/symfony/contracts/blob/main/CHANGELOG.md diff --git a/vendor/symfony/translation-contracts/LICENSE b/vendor/symfony/translation-contracts/LICENSE new file mode 100644 index 0000000..2358414 --- /dev/null +++ b/vendor/symfony/translation-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2021 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/translation-contracts/LocaleAwareInterface.php b/vendor/symfony/translation-contracts/LocaleAwareInterface.php new file mode 100644 index 0000000..693f92b --- /dev/null +++ b/vendor/symfony/translation-contracts/LocaleAwareInterface.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Translation; + +interface LocaleAwareInterface +{ + /** + * Sets the current locale. + * + * @param string $locale The locale + * + * @throws \InvalidArgumentException If the locale contains invalid characters + */ + public function setLocale(string $locale); + + /** + * Returns the current locale. + * + * @return string + */ + public function getLocale(); +} diff --git a/vendor/symfony/translation-contracts/README.md b/vendor/symfony/translation-contracts/README.md new file mode 100644 index 0000000..42e5c51 --- /dev/null +++ b/vendor/symfony/translation-contracts/README.md @@ -0,0 +1,9 @@ +Symfony Translation Contracts +============================= + +A set of abstractions extracted out of the Symfony components. + +Can be used to build on semantics that the Symfony components proved useful - and +that already have battle tested implementations. + +See https://github.com/symfony/contracts/blob/main/README.md for more information. diff --git a/vendor/symfony/translation-contracts/Test/TranslatorTest.php b/vendor/symfony/translation-contracts/Test/TranslatorTest.php new file mode 100644 index 0000000..8903676 --- /dev/null +++ b/vendor/symfony/translation-contracts/Test/TranslatorTest.php @@ -0,0 +1,390 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Translation\Test; + +use PHPUnit\Framework\TestCase; +use Symfony\Contracts\Translation\TranslatorInterface; +use Symfony\Contracts\Translation\TranslatorTrait; + +/** + * Test should cover all languages mentioned on http://translate.sourceforge.net/wiki/l10n/pluralforms + * and Plural forms mentioned on http://www.gnu.org/software/gettext/manual/gettext.html#Plural-forms. + * + * See also https://developer.mozilla.org/en/Localization_and_Plurals which mentions 15 rules having a maximum of 6 forms. + * The mozilla code is also interesting to check for. + * + * As mentioned by chx http://drupal.org/node/1273968 we can cover all by testing number from 0 to 199 + * + * The goal to cover all languages is to far fetched so this test case is smaller. + * + * @author Clemens Tolboom clemens@build2be.nl + */ +class TranslatorTest extends TestCase +{ + private $defaultLocale; + + protected function setUp(): void + { + $this->defaultLocale = \Locale::getDefault(); + \Locale::setDefault('en'); + } + + protected function tearDown(): void + { + \Locale::setDefault($this->defaultLocale); + } + + /** + * @return TranslatorInterface + */ + public function getTranslator() + { + return new class() implements TranslatorInterface { + use TranslatorTrait; + }; + } + + /** + * @dataProvider getTransTests + */ + public function testTrans($expected, $id, $parameters) + { + $translator = $this->getTranslator(); + + $this->assertEquals($expected, $translator->trans($id, $parameters)); + } + + /** + * @dataProvider getTransChoiceTests + */ + public function testTransChoiceWithExplicitLocale($expected, $id, $number) + { + $translator = $this->getTranslator(); + + $this->assertEquals($expected, $translator->trans($id, ['%count%' => $number])); + } + + /** + * @requires extension intl + * + * @dataProvider getTransChoiceTests + */ + public function testTransChoiceWithDefaultLocale($expected, $id, $number) + { + $translator = $this->getTranslator(); + + $this->assertEquals($expected, $translator->trans($id, ['%count%' => $number])); + } + + /** + * @dataProvider getTransChoiceTests + */ + public function testTransChoiceWithEnUsPosix($expected, $id, $number) + { + $translator = $this->getTranslator(); + $translator->setLocale('en_US_POSIX'); + + $this->assertEquals($expected, $translator->trans($id, ['%count%' => $number])); + } + + public function testGetSetLocale() + { + $translator = $this->getTranslator(); + + $this->assertEquals('en', $translator->getLocale()); + } + + /** + * @requires extension intl + */ + public function testGetLocaleReturnsDefaultLocaleIfNotSet() + { + $translator = $this->getTranslator(); + + \Locale::setDefault('pt_BR'); + $this->assertEquals('pt_BR', $translator->getLocale()); + + \Locale::setDefault('en'); + $this->assertEquals('en', $translator->getLocale()); + } + + public function getTransTests() + { + return [ + ['Symfony is great!', 'Symfony is great!', []], + ['Symfony is awesome!', 'Symfony is %what%!', ['%what%' => 'awesome']], + ]; + } + + public function getTransChoiceTests() + { + return [ + ['There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0], + ['There is one apple', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 1], + ['There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10], + ['There are 0 apples', 'There is 1 apple|There are %count% apples', 0], + ['There is 1 apple', 'There is 1 apple|There are %count% apples', 1], + ['There are 10 apples', 'There is 1 apple|There are %count% apples', 10], + // custom validation messages may be coded with a fixed value + ['There are 2 apples', 'There are 2 apples', 2], + ]; + } + + /** + * @dataProvider getInternal + */ + public function testInterval($expected, $number, $interval) + { + $translator = $this->getTranslator(); + + $this->assertEquals($expected, $translator->trans($interval.' foo|[1,Inf[ bar', ['%count%' => $number])); + } + + public function getInternal() + { + return [ + ['foo', 3, '{1,2, 3 ,4}'], + ['bar', 10, '{1,2, 3 ,4}'], + ['bar', 3, '[1,2]'], + ['foo', 1, '[1,2]'], + ['foo', 2, '[1,2]'], + ['bar', 1, ']1,2['], + ['bar', 2, ']1,2['], + ['foo', log(0), '[-Inf,2['], + ['foo', -log(0), '[-2,+Inf]'], + ]; + } + + /** + * @dataProvider getChooseTests + */ + public function testChoose($expected, $id, $number, $locale = null) + { + $translator = $this->getTranslator(); + + $this->assertEquals($expected, $translator->trans($id, ['%count%' => $number], null, $locale)); + } + + public function testReturnMessageIfExactlyOneStandardRuleIsGiven() + { + $translator = $this->getTranslator(); + + $this->assertEquals('There are two apples', $translator->trans('There are two apples', ['%count%' => 2])); + } + + /** + * @dataProvider getNonMatchingMessages + */ + public function testThrowExceptionIfMatchingMessageCannotBeFound($id, $number) + { + $this->expectException(\InvalidArgumentException::class); + $translator = $this->getTranslator(); + + $translator->trans($id, ['%count%' => $number]); + } + + public function getNonMatchingMessages() + { + return [ + ['{0} There are no apples|{1} There is one apple', 2], + ['{1} There is one apple|]1,Inf] There are %count% apples', 0], + ['{1} There is one apple|]2,Inf] There are %count% apples', 2], + ['{0} There are no apples|There is one apple', 2], + ]; + } + + public function getChooseTests() + { + return [ + ['There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0], + ['There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0], + ['There are no apples', '{0}There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0], + + ['There is one apple', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 1], + + ['There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10], + ['There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf]There are %count% apples', 10], + ['There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10], + + ['There are 0 apples', 'There is one apple|There are %count% apples', 0], + ['There is one apple', 'There is one apple|There are %count% apples', 1], + ['There are 10 apples', 'There is one apple|There are %count% apples', 10], + + ['There are 0 apples', 'one: There is one apple|more: There are %count% apples', 0], + ['There is one apple', 'one: There is one apple|more: There are %count% apples', 1], + ['There are 10 apples', 'one: There is one apple|more: There are %count% apples', 10], + + ['There are no apples', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 0], + ['There is one apple', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 1], + ['There are 10 apples', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 10], + + ['', '{0}|{1} There is one apple|]1,Inf] There are %count% apples', 0], + ['', '{0} There are no apples|{1}|]1,Inf] There are %count% apples', 1], + + // Indexed only tests which are Gettext PoFile* compatible strings. + ['There are 0 apples', 'There is one apple|There are %count% apples', 0], + ['There is one apple', 'There is one apple|There are %count% apples', 1], + ['There are 2 apples', 'There is one apple|There are %count% apples', 2], + + // Tests for float numbers + ['There is almost one apple', '{0} There are no apples|]0,1[ There is almost one apple|{1} There is one apple|[1,Inf] There is more than one apple', 0.7], + ['There is one apple', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 1], + ['There is more than one apple', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 1.7], + ['There are no apples', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 0], + ['There are no apples', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 0.0], + ['There are no apples', '{0.0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 0], + + // Test texts with new-lines + // with double-quotes and \n in id & double-quotes and actual newlines in text + ["This is a text with a\n new-line in it. Selector = 0.", '{0}This is a text with a + new-line in it. Selector = 0.|{1}This is a text with a + new-line in it. Selector = 1.|[1,Inf]This is a text with a + new-line in it. Selector > 1.', 0], + // with double-quotes and \n in id and single-quotes and actual newlines in text + ["This is a text with a\n new-line in it. Selector = 1.", '{0}This is a text with a + new-line in it. Selector = 0.|{1}This is a text with a + new-line in it. Selector = 1.|[1,Inf]This is a text with a + new-line in it. Selector > 1.', 1], + ["This is a text with a\n new-line in it. Selector > 1.", '{0}This is a text with a + new-line in it. Selector = 0.|{1}This is a text with a + new-line in it. Selector = 1.|[1,Inf]This is a text with a + new-line in it. Selector > 1.', 5], + // with double-quotes and id split accros lines + ['This is a text with a + new-line in it. Selector = 1.', '{0}This is a text with a + new-line in it. Selector = 0.|{1}This is a text with a + new-line in it. Selector = 1.|[1,Inf]This is a text with a + new-line in it. Selector > 1.', 1], + // with single-quotes and id split accros lines + ['This is a text with a + new-line in it. Selector > 1.', '{0}This is a text with a + new-line in it. Selector = 0.|{1}This is a text with a + new-line in it. Selector = 1.|[1,Inf]This is a text with a + new-line in it. Selector > 1.', 5], + // with single-quotes and \n in text + ['This is a text with a\nnew-line in it. Selector = 0.', '{0}This is a text with a\nnew-line in it. Selector = 0.|{1}This is a text with a\nnew-line in it. Selector = 1.|[1,Inf]This is a text with a\nnew-line in it. Selector > 1.', 0], + // with double-quotes and id split accros lines + ["This is a text with a\nnew-line in it. Selector = 1.", "{0}This is a text with a\nnew-line in it. Selector = 0.|{1}This is a text with a\nnew-line in it. Selector = 1.|[1,Inf]This is a text with a\nnew-line in it. Selector > 1.", 1], + // esacape pipe + ['This is a text with | in it. Selector = 0.', '{0}This is a text with || in it. Selector = 0.|{1}This is a text with || in it. Selector = 1.', 0], + // Empty plural set (2 plural forms) from a .PO file + ['', '|', 1], + // Empty plural set (3 plural forms) from a .PO file + ['', '||', 1], + + // Floating values + ['1.5 liters', '%count% liter|%count% liters', 1.5], + ['1.5 litre', '%count% litre|%count% litres', 1.5, 'fr'], + + // Negative values + ['-1 degree', '%count% degree|%count% degrees', -1], + ['-1 degré', '%count% degré|%count% degrés', -1], + ['-1.5 degrees', '%count% degree|%count% degrees', -1.5], + ['-1.5 degré', '%count% degré|%count% degrés', -1.5, 'fr'], + ['-2 degrees', '%count% degree|%count% degrees', -2], + ['-2 degrés', '%count% degré|%count% degrés', -2], + ]; + } + + /** + * @dataProvider failingLangcodes + */ + public function testFailedLangcodes($nplural, $langCodes) + { + $matrix = $this->generateTestData($langCodes); + $this->validateMatrix($nplural, $matrix, false); + } + + /** + * @dataProvider successLangcodes + */ + public function testLangcodes($nplural, $langCodes) + { + $matrix = $this->generateTestData($langCodes); + $this->validateMatrix($nplural, $matrix); + } + + /** + * This array should contain all currently known langcodes. + * + * As it is impossible to have this ever complete we should try as hard as possible to have it almost complete. + * + * @return array + */ + public function successLangcodes() + { + return [ + ['1', ['ay', 'bo', 'cgg', 'dz', 'id', 'ja', 'jbo', 'ka', 'kk', 'km', 'ko', 'ky']], + ['2', ['nl', 'fr', 'en', 'de', 'de_GE', 'hy', 'hy_AM', 'en_US_POSIX']], + ['3', ['be', 'bs', 'cs', 'hr']], + ['4', ['cy', 'mt', 'sl']], + ['6', ['ar']], + ]; + } + + /** + * This array should be at least empty within the near future. + * + * This both depends on a complete list trying to add above as understanding + * the plural rules of the current failing languages. + * + * @return array with nplural together with langcodes + */ + public function failingLangcodes() + { + return [ + ['1', ['fa']], + ['2', ['jbo']], + ['3', ['cbs']], + ['4', ['gd', 'kw']], + ['5', ['ga']], + ]; + } + + /** + * We validate only on the plural coverage. Thus the real rules is not tested. + * + * @param string $nplural Plural expected + * @param array $matrix Containing langcodes and their plural index values + * @param bool $expectSuccess + */ + protected function validateMatrix($nplural, $matrix, $expectSuccess = true) + { + foreach ($matrix as $langCode => $data) { + $indexes = array_flip($data); + if ($expectSuccess) { + $this->assertEquals($nplural, \count($indexes), "Langcode '$langCode' has '$nplural' plural forms."); + } else { + $this->assertNotEquals((int) $nplural, \count($indexes), "Langcode '$langCode' has '$nplural' plural forms."); + } + } + } + + protected function generateTestData($langCodes) + { + $translator = new class() { + use TranslatorTrait { + getPluralizationRule as public; + } + }; + + $matrix = []; + foreach ($langCodes as $langCode) { + for ($count = 0; $count < 200; ++$count) { + $plural = $translator->getPluralizationRule($count, $langCode); + $matrix[$langCode][$count] = $plural; + } + } + + return $matrix; + } +} diff --git a/vendor/symfony/translation-contracts/TranslatableInterface.php b/vendor/symfony/translation-contracts/TranslatableInterface.php new file mode 100644 index 0000000..47fd6fa --- /dev/null +++ b/vendor/symfony/translation-contracts/TranslatableInterface.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Translation; + +/** + * @author Nicolas Grekas + */ +interface TranslatableInterface +{ + public function trans(TranslatorInterface $translator, string $locale = null): string; +} diff --git a/vendor/symfony/translation-contracts/TranslatorInterface.php b/vendor/symfony/translation-contracts/TranslatorInterface.php new file mode 100644 index 0000000..77b7a9c --- /dev/null +++ b/vendor/symfony/translation-contracts/TranslatorInterface.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Translation; + +/** + * @author Fabien Potencier + * + * @method string getLocale() Returns the default locale + */ +interface TranslatorInterface +{ + /** + * Translates the given message. + * + * When a number is provided as a parameter named "%count%", the message is parsed for plural + * forms and a translation is chosen according to this number using the following rules: + * + * Given a message with different plural translations separated by a + * pipe (|), this method returns the correct portion of the message based + * on the given number, locale and the pluralization rules in the message + * itself. + * + * The message supports two different types of pluralization rules: + * + * interval: {0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples + * indexed: There is one apple|There are %count% apples + * + * The indexed solution can also contain labels (e.g. one: There is one apple). + * This is purely for making the translations more clear - it does not + * affect the functionality. + * + * The two methods can also be mixed: + * {0} There are no apples|one: There is one apple|more: There are %count% apples + * + * An interval can represent a finite set of numbers: + * {1,2,3,4} + * + * An interval can represent numbers between two numbers: + * [1, +Inf] + * ]-1,2[ + * + * The left delimiter can be [ (inclusive) or ] (exclusive). + * The right delimiter can be [ (exclusive) or ] (inclusive). + * Beside numbers, you can use -Inf and +Inf for the infinite. + * + * @see https://en.wikipedia.org/wiki/ISO_31-11 + * + * @param string $id The message id (may also be an object that can be cast to string) + * @param array $parameters An array of parameters for the message + * @param string|null $domain The domain for the message or null to use the default + * @param string|null $locale The locale or null to use the default + * + * @return string + * + * @throws \InvalidArgumentException If the locale contains invalid characters + */ + public function trans(string $id, array $parameters = [], string $domain = null, string $locale = null); +} diff --git a/vendor/symfony/translation-contracts/TranslatorTrait.php b/vendor/symfony/translation-contracts/TranslatorTrait.php new file mode 100644 index 0000000..405ce8d --- /dev/null +++ b/vendor/symfony/translation-contracts/TranslatorTrait.php @@ -0,0 +1,262 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Translation; + +use Symfony\Component\Translation\Exception\InvalidArgumentException; + +/** + * A trait to help implement TranslatorInterface and LocaleAwareInterface. + * + * @author Fabien Potencier + */ +trait TranslatorTrait +{ + private $locale; + + /** + * {@inheritdoc} + */ + public function setLocale(string $locale) + { + $this->locale = $locale; + } + + /** + * {@inheritdoc} + * + * @return string + */ + public function getLocale() + { + return $this->locale ?: (class_exists(\Locale::class) ? \Locale::getDefault() : 'en'); + } + + /** + * {@inheritdoc} + */ + public function trans(?string $id, array $parameters = [], string $domain = null, string $locale = null): string + { + if (null === $id || '' === $id) { + return ''; + } + + if (!isset($parameters['%count%']) || !is_numeric($parameters['%count%'])) { + return strtr($id, $parameters); + } + + $number = (float) $parameters['%count%']; + $locale = $locale ?: $this->getLocale(); + + $parts = []; + if (preg_match('/^\|++$/', $id)) { + $parts = explode('|', $id); + } elseif (preg_match_all('/(?:\|\||[^\|])++/', $id, $matches)) { + $parts = $matches[0]; + } + + $intervalRegexp = <<<'EOF' +/^(?P + ({\s* + (\-?\d+(\.\d+)?[\s*,\s*\-?\d+(\.\d+)?]*) + \s*}) + + | + + (?P[\[\]]) + \s* + (?P-Inf|\-?\d+(\.\d+)?) + \s*,\s* + (?P\+?Inf|\-?\d+(\.\d+)?) + \s* + (?P[\[\]]) +)\s*(?P.*?)$/xs +EOF; + + $standardRules = []; + foreach ($parts as $part) { + $part = trim(str_replace('||', '|', $part)); + + // try to match an explicit rule, then fallback to the standard ones + if (preg_match($intervalRegexp, $part, $matches)) { + if ($matches[2]) { + foreach (explode(',', $matches[3]) as $n) { + if ($number == $n) { + return strtr($matches['message'], $parameters); + } + } + } else { + $leftNumber = '-Inf' === $matches['left'] ? -\INF : (float) $matches['left']; + $rightNumber = is_numeric($matches['right']) ? (float) $matches['right'] : \INF; + + if (('[' === $matches['left_delimiter'] ? $number >= $leftNumber : $number > $leftNumber) + && (']' === $matches['right_delimiter'] ? $number <= $rightNumber : $number < $rightNumber) + ) { + return strtr($matches['message'], $parameters); + } + } + } elseif (preg_match('/^\w+\:\s*(.*?)$/', $part, $matches)) { + $standardRules[] = $matches[1]; + } else { + $standardRules[] = $part; + } + } + + $position = $this->getPluralizationRule($number, $locale); + + if (!isset($standardRules[$position])) { + // when there's exactly one rule given, and that rule is a standard + // rule, use this rule + if (1 === \count($parts) && isset($standardRules[0])) { + return strtr($standardRules[0], $parameters); + } + + $message = sprintf('Unable to choose a translation for "%s" with locale "%s" for value "%d". Double check that this translation has the correct plural options (e.g. "There is one apple|There are %%count%% apples").', $id, $locale, $number); + + if (class_exists(InvalidArgumentException::class)) { + throw new InvalidArgumentException($message); + } + + throw new \InvalidArgumentException($message); + } + + return strtr($standardRules[$position], $parameters); + } + + /** + * Returns the plural position to use for the given locale and number. + * + * The plural rules are derived from code of the Zend Framework (2010-09-25), + * which is subject to the new BSD license (http://framework.zend.com/license/new-bsd). + * Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) + */ + private function getPluralizationRule(float $number, string $locale): int + { + $number = abs($number); + + switch ('pt_BR' !== $locale && 'en_US_POSIX' !== $locale && \strlen($locale) > 3 ? substr($locale, 0, strrpos($locale, '_')) : $locale) { + case 'af': + case 'bn': + case 'bg': + case 'ca': + case 'da': + case 'de': + case 'el': + case 'en': + case 'en_US_POSIX': + case 'eo': + case 'es': + case 'et': + case 'eu': + case 'fa': + case 'fi': + case 'fo': + case 'fur': + case 'fy': + case 'gl': + case 'gu': + case 'ha': + case 'he': + case 'hu': + case 'is': + case 'it': + case 'ku': + case 'lb': + case 'ml': + case 'mn': + case 'mr': + case 'nah': + case 'nb': + case 'ne': + case 'nl': + case 'nn': + case 'no': + case 'oc': + case 'om': + case 'or': + case 'pa': + case 'pap': + case 'ps': + case 'pt': + case 'so': + case 'sq': + case 'sv': + case 'sw': + case 'ta': + case 'te': + case 'tk': + case 'ur': + case 'zu': + return (1 == $number) ? 0 : 1; + + case 'am': + case 'bh': + case 'fil': + case 'fr': + case 'gun': + case 'hi': + case 'hy': + case 'ln': + case 'mg': + case 'nso': + case 'pt_BR': + case 'ti': + case 'wa': + return ($number < 2) ? 0 : 1; + + case 'be': + case 'bs': + case 'hr': + case 'ru': + case 'sh': + case 'sr': + case 'uk': + return ((1 == $number % 10) && (11 != $number % 100)) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2); + + case 'cs': + case 'sk': + return (1 == $number) ? 0 : ((($number >= 2) && ($number <= 4)) ? 1 : 2); + + case 'ga': + return (1 == $number) ? 0 : ((2 == $number) ? 1 : 2); + + case 'lt': + return ((1 == $number % 10) && (11 != $number % 100)) ? 0 : ((($number % 10 >= 2) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2); + + case 'sl': + return (1 == $number % 100) ? 0 : ((2 == $number % 100) ? 1 : (((3 == $number % 100) || (4 == $number % 100)) ? 2 : 3)); + + case 'mk': + return (1 == $number % 10) ? 0 : 1; + + case 'mt': + return (1 == $number) ? 0 : (((0 == $number) || (($number % 100 > 1) && ($number % 100 < 11))) ? 1 : ((($number % 100 > 10) && ($number % 100 < 20)) ? 2 : 3)); + + case 'lv': + return (0 == $number) ? 0 : (((1 == $number % 10) && (11 != $number % 100)) ? 1 : 2); + + case 'pl': + return (1 == $number) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 12) || ($number % 100 > 14))) ? 1 : 2); + + case 'cy': + return (1 == $number) ? 0 : ((2 == $number) ? 1 : (((8 == $number) || (11 == $number)) ? 2 : 3)); + + case 'ro': + return (1 == $number) ? 0 : (((0 == $number) || (($number % 100 > 0) && ($number % 100 < 20))) ? 1 : 2); + + case 'ar': + return (0 == $number) ? 0 : ((1 == $number) ? 1 : ((2 == $number) ? 2 : ((($number % 100 >= 3) && ($number % 100 <= 10)) ? 3 : ((($number % 100 >= 11) && ($number % 100 <= 99)) ? 4 : 5)))); + + default: + return 0; + } + } +} diff --git a/vendor/symfony/translation-contracts/composer.json b/vendor/symfony/translation-contracts/composer.json new file mode 100644 index 0000000..65fe243 --- /dev/null +++ b/vendor/symfony/translation-contracts/composer.json @@ -0,0 +1,37 @@ +{ + "name": "symfony/translation-contracts", + "type": "library", + "description": "Generic abstractions related to translation", + "keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.2.5" + }, + "suggest": { + "symfony/translation-implementation": "" + }, + "autoload": { + "psr-4": { "Symfony\\Contracts\\Translation\\": "" } + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + } +} diff --git a/vendor/symfony/translation/CHANGELOG.md b/vendor/symfony/translation/CHANGELOG.md new file mode 100644 index 0000000..160b5e6 --- /dev/null +++ b/vendor/symfony/translation/CHANGELOG.md @@ -0,0 +1,176 @@ +CHANGELOG +========= + +5.4 +--- + + * Add `github` format & autodetection to render errors as annotations when + running the XLIFF linter command in a Github Actions environment. + * Translation providers are not experimental anymore + +5.3 +--- + + * Add `translation:pull` and `translation:push` commands to manage translations with third-party providers + * Add `TranslatorBagInterface::getCatalogues` method + * Add support to load XLIFF string in `XliffFileLoader` + +5.2.0 +----- + + * added support for calling `trans` with ICU formatted messages + * added `PseudoLocalizationTranslator` + * added `TranslatableMessage` objects that represent a message that can be translated + * added the `t()` function to easily create `TranslatableMessage` objects + * Added support for extracting messages from `TranslatableMessage` objects + +5.1.0 +----- + + * added support for `name` attribute on `unit` element from xliff2 to be used as a translation key instead of always the `source` element + +5.0.0 +----- + + * removed support for using `null` as the locale in `Translator` + * removed `TranslatorInterface` + * removed `MessageSelector` + * removed `ChoiceMessageFormatterInterface` + * removed `PluralizationRule` + * removed `Interval` + * removed `transChoice()` methods, use the trans() method instead with a %count% parameter + * removed `FileDumper::setBackup()` and `TranslationWriter::disableBackup()` + * removed `MessageFormatter::choiceFormat()` + * added argument `$filename` to `PhpExtractor::parseTokens()` + * removed support for implicit STDIN usage in the `lint:xliff` command, use `lint:xliff -` (append a dash) instead to make it explicit. + +4.4.0 +----- + + * deprecated support for using `null` as the locale in `Translator` + * deprecated accepting STDIN implicitly when using the `lint:xliff` command, use `lint:xliff -` (append a dash) instead to make it explicit. + * Marked the `TranslationDataCollector` class as `@final`. + +4.3.0 +----- + + * Improved Xliff 1.2 loader to load the original file's metadata + * Added `TranslatorPathsPass` + +4.2.0 +----- + + * Started using ICU parent locales as fallback locales. + * allow using the ICU message format using domains with the "+intl-icu" suffix + * deprecated `Translator::transChoice()` in favor of using `Translator::trans()` with a `%count%` parameter + * deprecated `TranslatorInterface` in favor of `Symfony\Contracts\Translation\TranslatorInterface` + * deprecated `MessageSelector`, `Interval` and `PluralizationRules`; use `IdentityTranslator` instead + * Added `IntlFormatter` and `IntlFormatterInterface` + * added support for multiple files and directories in `XliffLintCommand` + * Marked `Translator::getFallbackLocales()` and `TranslationDataCollector::getFallbackLocales()` as internal + +4.1.0 +----- + + * The `FileDumper::setBackup()` method is deprecated. + * The `TranslationWriter::disableBackup()` method is deprecated. + * The `XliffFileDumper` will write "name" on the "unit" node when dumping XLIFF 2.0. + +4.0.0 +----- + + * removed the backup feature of the `FileDumper` class + * removed `TranslationWriter::writeTranslations()` method + * removed support for passing `MessageSelector` instances to the constructor of the `Translator` class + +3.4.0 +----- + + * Added `TranslationDumperPass` + * Added `TranslationExtractorPass` + * Added `TranslatorPass` + * Added `TranslationReader` and `TranslationReaderInterface` + * Added `` section to the Xliff 2.0 dumper. + * Improved Xliff 2.0 loader to load `` section. + * Added `TranslationWriterInterface` + * Deprecated `TranslationWriter::writeTranslations` in favor of `TranslationWriter::write` + * added support for adding custom message formatter and decoupling the default one. + * Added `PhpExtractor` + * Added `PhpStringTokenParser` + +3.2.0 +----- + + * Added support for escaping `|` in plural translations with double pipe. + +3.1.0 +----- + + * Deprecated the backup feature of the file dumper classes. + +3.0.0 +----- + + * removed `FileDumper::format()` method. + * Changed the visibility of the locale property in `Translator` from protected to private. + +2.8.0 +----- + + * deprecated FileDumper::format(), overwrite FileDumper::formatCatalogue() instead. + * deprecated Translator::getMessages(), rely on TranslatorBagInterface::getCatalogue() instead. + * added `FileDumper::formatCatalogue` which allows format the catalogue without dumping it into file. + * added option `json_encoding` to JsonFileDumper + * added options `as_tree`, `inline` to YamlFileDumper + * added support for XLIFF 2.0. + * added support for XLIFF target and tool attributes. + * added message parameters to DataCollectorTranslator. + * [DEPRECATION] The `DiffOperation` class has been deprecated and + will be removed in Symfony 3.0, since its operation has nothing to do with 'diff', + so the class name is misleading. The `TargetOperation` class should be used for + this use-case instead. + +2.7.0 +----- + + * added DataCollectorTranslator for collecting the translated messages. + +2.6.0 +----- + + * added possibility to cache catalogues + * added TranslatorBagInterface + * added LoggingTranslator + * added Translator::getMessages() for retrieving the message catalogue as an array + +2.5.0 +----- + + * added relative file path template to the file dumpers + * added optional backup to the file dumpers + * changed IcuResFileDumper to extend FileDumper + +2.3.0 +----- + + * added classes to make operations on catalogues (like making a diff or a merge on 2 catalogues) + * added Translator::getFallbackLocales() + * deprecated Translator::setFallbackLocale() in favor of the new Translator::setFallbackLocales() method + +2.2.0 +----- + + * QtTranslationsLoader class renamed to QtFileLoader. QtTranslationsLoader is deprecated and will be removed in 2.3. + * [BC BREAK] uniformized the exception thrown by the load() method when an error occurs. The load() method now + throws Symfony\Component\Translation\Exception\NotFoundResourceException when a resource cannot be found + and Symfony\Component\Translation\Exception\InvalidResourceException when a resource is invalid. + * changed the exception class thrown by some load() methods from \RuntimeException to \InvalidArgumentException + (IcuDatFileLoader, IcuResFileLoader and QtFileLoader) + +2.1.0 +----- + + * added support for more than one fallback locale + * added support for extracting translation messages from templates (Twig and PHP) + * added dumpers for translation catalogs + * added support for QT, gettext, and ResourceBundles diff --git a/vendor/symfony/translation/Catalogue/AbstractOperation.php b/vendor/symfony/translation/Catalogue/AbstractOperation.php new file mode 100644 index 0000000..9869fbb --- /dev/null +++ b/vendor/symfony/translation/Catalogue/AbstractOperation.php @@ -0,0 +1,192 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Catalogue; + +use Symfony\Component\Translation\Exception\InvalidArgumentException; +use Symfony\Component\Translation\Exception\LogicException; +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\MessageCatalogueInterface; + +/** + * Base catalogues binary operation class. + * + * A catalogue binary operation performs operation on + * source (the left argument) and target (the right argument) catalogues. + * + * @author Jean-François Simon + */ +abstract class AbstractOperation implements OperationInterface +{ + public const OBSOLETE_BATCH = 'obsolete'; + public const NEW_BATCH = 'new'; + public const ALL_BATCH = 'all'; + + protected $source; + protected $target; + protected $result; + + /** + * @var array|null The domains affected by this operation + */ + private $domains; + + /** + * This array stores 'all', 'new' and 'obsolete' messages for all valid domains. + * + * The data structure of this array is as follows: + * + * [ + * 'domain 1' => [ + * 'all' => [...], + * 'new' => [...], + * 'obsolete' => [...] + * ], + * 'domain 2' => [ + * 'all' => [...], + * 'new' => [...], + * 'obsolete' => [...] + * ], + * ... + * ] + * + * @var array The array that stores 'all', 'new' and 'obsolete' messages + */ + protected $messages; + + /** + * @throws LogicException + */ + public function __construct(MessageCatalogueInterface $source, MessageCatalogueInterface $target) + { + if ($source->getLocale() !== $target->getLocale()) { + throw new LogicException('Operated catalogues must belong to the same locale.'); + } + + $this->source = $source; + $this->target = $target; + $this->result = new MessageCatalogue($source->getLocale()); + $this->messages = []; + } + + /** + * {@inheritdoc} + */ + public function getDomains() + { + if (null === $this->domains) { + $this->domains = array_values(array_unique(array_merge($this->source->getDomains(), $this->target->getDomains()))); + } + + return $this->domains; + } + + /** + * {@inheritdoc} + */ + public function getMessages(string $domain) + { + if (!\in_array($domain, $this->getDomains())) { + throw new InvalidArgumentException(sprintf('Invalid domain: "%s".', $domain)); + } + + if (!isset($this->messages[$domain][self::ALL_BATCH])) { + $this->processDomain($domain); + } + + return $this->messages[$domain][self::ALL_BATCH]; + } + + /** + * {@inheritdoc} + */ + public function getNewMessages(string $domain) + { + if (!\in_array($domain, $this->getDomains())) { + throw new InvalidArgumentException(sprintf('Invalid domain: "%s".', $domain)); + } + + if (!isset($this->messages[$domain][self::NEW_BATCH])) { + $this->processDomain($domain); + } + + return $this->messages[$domain][self::NEW_BATCH]; + } + + /** + * {@inheritdoc} + */ + public function getObsoleteMessages(string $domain) + { + if (!\in_array($domain, $this->getDomains())) { + throw new InvalidArgumentException(sprintf('Invalid domain: "%s".', $domain)); + } + + if (!isset($this->messages[$domain][self::OBSOLETE_BATCH])) { + $this->processDomain($domain); + } + + return $this->messages[$domain][self::OBSOLETE_BATCH]; + } + + /** + * {@inheritdoc} + */ + public function getResult() + { + foreach ($this->getDomains() as $domain) { + if (!isset($this->messages[$domain])) { + $this->processDomain($domain); + } + } + + return $this->result; + } + + /** + * @param self::*_BATCH $batch + */ + public function moveMessagesToIntlDomainsIfPossible(string $batch = self::ALL_BATCH): void + { + // If MessageFormatter class does not exists, intl domains are not supported. + if (!class_exists(\MessageFormatter::class)) { + return; + } + + foreach ($this->getDomains() as $domain) { + $intlDomain = $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX; + switch ($batch) { + case self::OBSOLETE_BATCH: $messages = $this->getObsoleteMessages($domain); break; + case self::NEW_BATCH: $messages = $this->getNewMessages($domain); break; + case self::ALL_BATCH: $messages = $this->getMessages($domain); break; + default: throw new \InvalidArgumentException(sprintf('$batch argument must be one of ["%s", "%s", "%s"].', self::ALL_BATCH, self::NEW_BATCH, self::OBSOLETE_BATCH)); + } + + if (!$messages || (!$this->source->all($intlDomain) && $this->source->all($domain))) { + continue; + } + + $result = $this->getResult(); + $allIntlMessages = $result->all($intlDomain); + $currentMessages = array_diff_key($messages, $result->all($domain)); + $result->replace($currentMessages, $domain); + $result->replace($allIntlMessages + $messages, $intlDomain); + } + } + + /** + * Performs operation on source and target catalogues for the given domain and + * stores the results. + * + * @param string $domain The domain which the operation will be performed for + */ + abstract protected function processDomain(string $domain); +} diff --git a/vendor/symfony/translation/Catalogue/MergeOperation.php b/vendor/symfony/translation/Catalogue/MergeOperation.php new file mode 100644 index 0000000..87db2fb --- /dev/null +++ b/vendor/symfony/translation/Catalogue/MergeOperation.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Catalogue; + +use Symfony\Component\Translation\MessageCatalogueInterface; + +/** + * Merge operation between two catalogues as follows: + * all = source ∪ target = {x: x ∈ source ∨ x ∈ target} + * new = all ∖ source = {x: x ∈ target ∧ x ∉ source} + * obsolete = source ∖ all = {x: x ∈ source ∧ x ∉ source ∧ x ∉ target} = ∅ + * Basically, the result contains messages from both catalogues. + * + * @author Jean-François Simon + */ +class MergeOperation extends AbstractOperation +{ + /** + * {@inheritdoc} + */ + protected function processDomain(string $domain) + { + $this->messages[$domain] = [ + 'all' => [], + 'new' => [], + 'obsolete' => [], + ]; + $intlDomain = $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX; + + foreach ($this->source->all($domain) as $id => $message) { + $this->messages[$domain]['all'][$id] = $message; + $d = $this->source->defines($id, $intlDomain) ? $intlDomain : $domain; + $this->result->add([$id => $message], $d); + if (null !== $keyMetadata = $this->source->getMetadata($id, $d)) { + $this->result->setMetadata($id, $keyMetadata, $d); + } + } + + foreach ($this->target->all($domain) as $id => $message) { + if (!$this->source->has($id, $domain)) { + $this->messages[$domain]['all'][$id] = $message; + $this->messages[$domain]['new'][$id] = $message; + $d = $this->target->defines($id, $intlDomain) ? $intlDomain : $domain; + $this->result->add([$id => $message], $d); + if (null !== $keyMetadata = $this->target->getMetadata($id, $d)) { + $this->result->setMetadata($id, $keyMetadata, $d); + } + } + } + } +} diff --git a/vendor/symfony/translation/Catalogue/OperationInterface.php b/vendor/symfony/translation/Catalogue/OperationInterface.php new file mode 100644 index 0000000..9ffac88 --- /dev/null +++ b/vendor/symfony/translation/Catalogue/OperationInterface.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Catalogue; + +use Symfony\Component\Translation\MessageCatalogueInterface; + +/** + * Represents an operation on catalogue(s). + * + * An instance of this interface performs an operation on one or more catalogues and + * stores intermediate and final results of the operation. + * + * The first catalogue in its argument(s) is called the 'source catalogue' or 'source' and + * the following results are stored: + * + * Messages: also called 'all', are valid messages for the given domain after the operation is performed. + * + * New Messages: also called 'new' (new = all ∖ source = {x: x ∈ all ∧ x ∉ source}). + * + * Obsolete Messages: also called 'obsolete' (obsolete = source ∖ all = {x: x ∈ source ∧ x ∉ all}). + * + * Result: also called 'result', is the resulting catalogue for the given domain that holds the same messages as 'all'. + * + * @author Jean-François Simon + */ +interface OperationInterface +{ + /** + * Returns domains affected by operation. + * + * @return array + */ + public function getDomains(); + + /** + * Returns all valid messages ('all') after operation. + * + * @return array + */ + public function getMessages(string $domain); + + /** + * Returns new messages ('new') after operation. + * + * @return array + */ + public function getNewMessages(string $domain); + + /** + * Returns obsolete messages ('obsolete') after operation. + * + * @return array + */ + public function getObsoleteMessages(string $domain); + + /** + * Returns resulting catalogue ('result'). + * + * @return MessageCatalogueInterface + */ + public function getResult(); +} diff --git a/vendor/symfony/translation/Catalogue/TargetOperation.php b/vendor/symfony/translation/Catalogue/TargetOperation.php new file mode 100644 index 0000000..682b575 --- /dev/null +++ b/vendor/symfony/translation/Catalogue/TargetOperation.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Catalogue; + +use Symfony\Component\Translation\MessageCatalogueInterface; + +/** + * Target operation between two catalogues: + * intersection = source ∩ target = {x: x ∈ source ∧ x ∈ target} + * all = intersection ∪ (target ∖ intersection) = target + * new = all ∖ source = {x: x ∈ target ∧ x ∉ source} + * obsolete = source ∖ all = source ∖ target = {x: x ∈ source ∧ x ∉ target} + * Basically, the result contains messages from the target catalogue. + * + * @author Michael Lee + */ +class TargetOperation extends AbstractOperation +{ + /** + * {@inheritdoc} + */ + protected function processDomain(string $domain) + { + $this->messages[$domain] = [ + 'all' => [], + 'new' => [], + 'obsolete' => [], + ]; + $intlDomain = $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX; + + // For 'all' messages, the code can't be simplified as ``$this->messages[$domain]['all'] = $target->all($domain);``, + // because doing so will drop messages like {x: x ∈ source ∧ x ∉ target.all ∧ x ∈ target.fallback} + // + // For 'new' messages, the code can't be simplified as ``array_diff_assoc($this->target->all($domain), $this->source->all($domain));`` + // because doing so will not exclude messages like {x: x ∈ target ∧ x ∉ source.all ∧ x ∈ source.fallback} + // + // For 'obsolete' messages, the code can't be simplified as ``array_diff_assoc($this->source->all($domain), $this->target->all($domain))`` + // because doing so will not exclude messages like {x: x ∈ source ∧ x ∉ target.all ∧ x ∈ target.fallback} + + foreach ($this->source->all($domain) as $id => $message) { + if ($this->target->has($id, $domain)) { + $this->messages[$domain]['all'][$id] = $message; + $d = $this->source->defines($id, $intlDomain) ? $intlDomain : $domain; + $this->result->add([$id => $message], $d); + if (null !== $keyMetadata = $this->source->getMetadata($id, $d)) { + $this->result->setMetadata($id, $keyMetadata, $d); + } + } else { + $this->messages[$domain]['obsolete'][$id] = $message; + } + } + + foreach ($this->target->all($domain) as $id => $message) { + if (!$this->source->has($id, $domain)) { + $this->messages[$domain]['all'][$id] = $message; + $this->messages[$domain]['new'][$id] = $message; + $d = $this->target->defines($id, $intlDomain) ? $intlDomain : $domain; + $this->result->add([$id => $message], $d); + if (null !== $keyMetadata = $this->target->getMetadata($id, $d)) { + $this->result->setMetadata($id, $keyMetadata, $d); + } + } + } + } +} diff --git a/vendor/symfony/translation/Command/TranslationPullCommand.php b/vendor/symfony/translation/Command/TranslationPullCommand.php new file mode 100644 index 0000000..7045a9e --- /dev/null +++ b/vendor/symfony/translation/Command/TranslationPullCommand.php @@ -0,0 +1,188 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Translation\Catalogue\TargetOperation; +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\Provider\TranslationProviderCollection; +use Symfony\Component\Translation\Reader\TranslationReaderInterface; +use Symfony\Component\Translation\Writer\TranslationWriterInterface; + +/** + * @author Mathieu Santostefano + */ +final class TranslationPullCommand extends Command +{ + use TranslationTrait; + + protected static $defaultName = 'translation:pull'; + protected static $defaultDescription = 'Pull translations from a given provider.'; + + private $providerCollection; + private $writer; + private $reader; + private $defaultLocale; + private $transPaths; + private $enabledLocales; + + public function __construct(TranslationProviderCollection $providerCollection, TranslationWriterInterface $writer, TranslationReaderInterface $reader, string $defaultLocale, array $transPaths = [], array $enabledLocales = []) + { + $this->providerCollection = $providerCollection; + $this->writer = $writer; + $this->reader = $reader; + $this->defaultLocale = $defaultLocale; + $this->transPaths = $transPaths; + $this->enabledLocales = $enabledLocales; + + parent::__construct(); + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestArgumentValuesFor('provider')) { + $suggestions->suggestValues($this->providerCollection->keys()); + + return; + } + + if ($input->mustSuggestOptionValuesFor('domains')) { + $provider = $this->providerCollection->get($input->getArgument('provider')); + + if ($provider && method_exists($provider, 'getDomains')) { + $domains = $provider->getDomains(); + $suggestions->suggestValues($domains); + } + + return; + } + + if ($input->mustSuggestOptionValuesFor('locales')) { + $suggestions->suggestValues($this->enabledLocales); + + return; + } + + if ($input->mustSuggestOptionValuesFor('format')) { + $suggestions->suggestValues(['php', 'xlf', 'xlf12', 'xlf20', 'po', 'mo', 'yml', 'yaml', 'ts', 'csv', 'json', 'ini', 'res']); + } + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $keys = $this->providerCollection->keys(); + $defaultProvider = 1 === \count($keys) ? $keys[0] : null; + + $this + ->setDefinition([ + new InputArgument('provider', null !== $defaultProvider ? InputArgument::OPTIONAL : InputArgument::REQUIRED, 'The provider to pull translations from.', $defaultProvider), + new InputOption('force', null, InputOption::VALUE_NONE, 'Override existing translations with provider ones (it will delete not synchronized messages).'), + new InputOption('intl-icu', null, InputOption::VALUE_NONE, 'Associated to --force option, it will write messages in "%domain%+intl-icu.%locale%.xlf" files.'), + new InputOption('domains', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Specify the domains to pull.'), + new InputOption('locales', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Specify the locales to pull.'), + new InputOption('format', null, InputOption::VALUE_OPTIONAL, 'Override the default output format.', 'xlf12'), + ]) + ->setHelp(<<<'EOF' +The %command.name% command pulls translations from the given provider. Only +new translations are pulled, existing ones are not overwritten. + +You can overwrite existing translations (and remove the missing ones on local side) by using the --force flag: + + php %command.full_name% --force provider + +Full example: + + php %command.full_name% provider --force --domains=messages,validators --locales=en + +This command pulls all translations associated with the messages and validators domains for the en locale. +Local translations for the specified domains and locale are deleted if they're not present on the provider and overwritten if it's the case. +Local translations for others domains and locales are ignored. +EOF + ) + ; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + + $provider = $this->providerCollection->get($input->getArgument('provider')); + $force = $input->getOption('force'); + $intlIcu = $input->getOption('intl-icu'); + $locales = $input->getOption('locales') ?: $this->enabledLocales; + $domains = $input->getOption('domains'); + $format = $input->getOption('format'); + $xliffVersion = '1.2'; + + if ($intlIcu && !$force) { + $io->note('--intl-icu option only has an effect when used with --force. Here, it will be ignored.'); + } + + switch ($format) { + case 'xlf20': $xliffVersion = '2.0'; + // no break + case 'xlf12': $format = 'xlf'; + } + + $writeOptions = [ + 'path' => end($this->transPaths), + 'xliff_version' => $xliffVersion, + 'default_locale' => $this->defaultLocale, + ]; + + if (!$domains) { + $domains = $provider->getDomains(); + } + + $providerTranslations = $provider->read($domains, $locales); + + if ($force) { + foreach ($providerTranslations->getCatalogues() as $catalogue) { + $operation = new TargetOperation((new MessageCatalogue($catalogue->getLocale())), $catalogue); + if ($intlIcu) { + $operation->moveMessagesToIntlDomainsIfPossible(); + } + $this->writer->write($operation->getResult(), $format, $writeOptions); + } + + $io->success(sprintf('Local translations has been updated from "%s" (for "%s" locale(s), and "%s" domain(s)).', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains))); + + return 0; + } + + $localTranslations = $this->readLocalTranslations($locales, $domains, $this->transPaths); + + // Append pulled translations to local ones. + $localTranslations->addBag($providerTranslations->diff($localTranslations)); + + foreach ($localTranslations->getCatalogues() as $catalogue) { + $this->writer->write($catalogue, $format, $writeOptions); + } + + $io->success(sprintf('New translations from "%s" has been written locally (for "%s" locale(s), and "%s" domain(s)).', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains))); + + return 0; + } +} diff --git a/vendor/symfony/translation/Command/TranslationPushCommand.php b/vendor/symfony/translation/Command/TranslationPushCommand.php new file mode 100644 index 0000000..628a06b --- /dev/null +++ b/vendor/symfony/translation/Command/TranslationPushCommand.php @@ -0,0 +1,182 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Translation\Provider\TranslationProviderCollection; +use Symfony\Component\Translation\Reader\TranslationReaderInterface; +use Symfony\Component\Translation\TranslatorBag; + +/** + * @author Mathieu Santostefano + */ +final class TranslationPushCommand extends Command +{ + use TranslationTrait; + + protected static $defaultName = 'translation:push'; + protected static $defaultDescription = 'Push translations to a given provider.'; + + private $providers; + private $reader; + private $transPaths; + private $enabledLocales; + + public function __construct(TranslationProviderCollection $providers, TranslationReaderInterface $reader, array $transPaths = [], array $enabledLocales = []) + { + $this->providers = $providers; + $this->reader = $reader; + $this->transPaths = $transPaths; + $this->enabledLocales = $enabledLocales; + + parent::__construct(); + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestArgumentValuesFor('provider')) { + $suggestions->suggestValues($this->providers->keys()); + + return; + } + + if ($input->mustSuggestOptionValuesFor('domains')) { + $provider = $this->providers->get($input->getArgument('provider')); + + if ($provider && method_exists($provider, 'getDomains')) { + $domains = $provider->getDomains(); + $suggestions->suggestValues($domains); + } + + return; + } + + if ($input->mustSuggestOptionValuesFor('locales')) { + $suggestions->suggestValues($this->enabledLocales); + } + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $keys = $this->providers->keys(); + $defaultProvider = 1 === \count($keys) ? $keys[0] : null; + + $this + ->setDefinition([ + new InputArgument('provider', null !== $defaultProvider ? InputArgument::OPTIONAL : InputArgument::REQUIRED, 'The provider to push translations to.', $defaultProvider), + new InputOption('force', null, InputOption::VALUE_NONE, 'Override existing translations with local ones (it will delete not synchronized messages).'), + new InputOption('delete-missing', null, InputOption::VALUE_NONE, 'Delete translations available on provider but not locally.'), + new InputOption('domains', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Specify the domains to push.'), + new InputOption('locales', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Specify the locales to push.', $this->enabledLocales), + ]) + ->setHelp(<<<'EOF' +The %command.name% command pushes translations to the given provider. Only new +translations are pushed, existing ones are not overwritten. + +You can overwrite existing translations by using the --force flag: + + php %command.full_name% --force provider + +You can delete provider translations which are not present locally by using the --delete-missing flag: + + php %command.full_name% --delete-missing provider + +Full example: + + php %command.full_name% provider --force --delete-missing --domains=messages,validators --locales=en + +This command pushes all translations associated with the messages and validators domains for the en locale. +Provider translations for the specified domains and locale are deleted if they're not present locally and overwritten if it's the case. +Provider translations for others domains and locales are ignored. +EOF + ) + ; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $provider = $this->providers->get($input->getArgument('provider')); + + if (!$this->enabledLocales) { + throw new InvalidArgumentException(sprintf('You must define "framework.translator.enabled_locales" or "framework.translator.providers.%s.locales" config key in order to work with translation providers.', parse_url($provider, \PHP_URL_SCHEME))); + } + + $io = new SymfonyStyle($input, $output); + $domains = $input->getOption('domains'); + $locales = $input->getOption('locales'); + $force = $input->getOption('force'); + $deleteMissing = $input->getOption('delete-missing'); + + $localTranslations = $this->readLocalTranslations($locales, $domains, $this->transPaths); + + if (!$domains) { + $domains = $this->getDomainsFromTranslatorBag($localTranslations); + } + + if (!$deleteMissing && $force) { + $provider->write($localTranslations); + + $io->success(sprintf('All local translations has been sent to "%s" (for "%s" locale(s), and "%s" domain(s)).', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains))); + + return 0; + } + + $providerTranslations = $provider->read($domains, $locales); + + if ($deleteMissing) { + $provider->delete($providerTranslations->diff($localTranslations)); + + $io->success(sprintf('Missing translations on "%s" has been deleted (for "%s" locale(s), and "%s" domain(s)).', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains))); + + // Read provider translations again, after missing translations deletion, + // to avoid push freshly deleted translations. + $providerTranslations = $provider->read($domains, $locales); + } + + $translationsToWrite = $localTranslations->diff($providerTranslations); + + if ($force) { + $translationsToWrite->addBag($localTranslations->intersect($providerTranslations)); + } + + $provider->write($translationsToWrite); + + $io->success(sprintf('%s local translations has been sent to "%s" (for "%s" locale(s), and "%s" domain(s)).', $force ? 'All' : 'New', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains))); + + return 0; + } + + private function getDomainsFromTranslatorBag(TranslatorBag $translatorBag): array + { + $domains = []; + + foreach ($translatorBag->getCatalogues() as $catalogue) { + $domains += $catalogue->getDomains(); + } + + return array_unique($domains); + } +} diff --git a/vendor/symfony/translation/Command/TranslationTrait.php b/vendor/symfony/translation/Command/TranslationTrait.php new file mode 100644 index 0000000..eafaffd --- /dev/null +++ b/vendor/symfony/translation/Command/TranslationTrait.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Command; + +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\MessageCatalogueInterface; +use Symfony\Component\Translation\TranslatorBag; + +/** + * @internal + */ +trait TranslationTrait +{ + private function readLocalTranslations(array $locales, array $domains, array $transPaths): TranslatorBag + { + $bag = new TranslatorBag(); + + foreach ($locales as $locale) { + $catalogue = new MessageCatalogue($locale); + foreach ($transPaths as $path) { + $this->reader->read($path, $catalogue); + } + + if ($domains) { + foreach ($domains as $domain) { + $bag->addCatalogue($this->filterCatalogue($catalogue, $domain)); + } + } else { + $bag->addCatalogue($catalogue); + } + } + + return $bag; + } + + private function filterCatalogue(MessageCatalogue $catalogue, string $domain): MessageCatalogue + { + $filteredCatalogue = new MessageCatalogue($catalogue->getLocale()); + + // extract intl-icu messages only + $intlDomain = $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX; + if ($intlMessages = $catalogue->all($intlDomain)) { + $filteredCatalogue->add($intlMessages, $intlDomain); + } + + // extract all messages and subtract intl-icu messages + if ($messages = array_diff($catalogue->all($domain), $intlMessages)) { + $filteredCatalogue->add($messages, $domain); + } + foreach ($catalogue->getResources() as $resource) { + $filteredCatalogue->addResource($resource); + } + + if ($metadata = $catalogue->getMetadata('', $intlDomain)) { + foreach ($metadata as $k => $v) { + $filteredCatalogue->setMetadata($k, $v, $intlDomain); + } + } + + if ($metadata = $catalogue->getMetadata('', $domain)) { + foreach ($metadata as $k => $v) { + $filteredCatalogue->setMetadata($k, $v, $domain); + } + } + + return $filteredCatalogue; + } +} diff --git a/vendor/symfony/translation/Command/XliffLintCommand.php b/vendor/symfony/translation/Command/XliffLintCommand.php new file mode 100644 index 0000000..fb2b5f3 --- /dev/null +++ b/vendor/symfony/translation/Command/XliffLintCommand.php @@ -0,0 +1,286 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Command; + +use Symfony\Component\Console\CI\GithubActionReporter; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Translation\Exception\InvalidArgumentException; +use Symfony\Component\Translation\Util\XliffUtils; + +/** + * Validates XLIFF files syntax and outputs encountered errors. + * + * @author Grégoire Pineau + * @author Robin Chalas + * @author Javier Eguiluz + */ +class XliffLintCommand extends Command +{ + protected static $defaultName = 'lint:xliff'; + protected static $defaultDescription = 'Lint an XLIFF file and outputs encountered errors'; + + private $format; + private $displayCorrectFiles; + private $directoryIteratorProvider; + private $isReadableProvider; + private $requireStrictFileNames; + + public function __construct(string $name = null, callable $directoryIteratorProvider = null, callable $isReadableProvider = null, bool $requireStrictFileNames = true) + { + parent::__construct($name); + + $this->directoryIteratorProvider = $directoryIteratorProvider; + $this->isReadableProvider = $isReadableProvider; + $this->requireStrictFileNames = $requireStrictFileNames; + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setDescription(self::$defaultDescription) + ->addArgument('filename', InputArgument::IS_ARRAY, 'A file, a directory or "-" for reading from STDIN') + ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format') + ->setHelp(<<%command.name% command lints an XLIFF file and outputs to STDOUT +the first encountered syntax error. + +You can validates XLIFF contents passed from STDIN: + + cat filename | php %command.full_name% - + +You can also validate the syntax of a file: + + php %command.full_name% filename + +Or of a whole directory: + + php %command.full_name% dirname + php %command.full_name% dirname --format=json + +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output); + $filenames = (array) $input->getArgument('filename'); + $this->format = $input->getOption('format') ?? (GithubActionReporter::isGithubActionEnvironment() ? 'github' : 'txt'); + $this->displayCorrectFiles = $output->isVerbose(); + + if (['-'] === $filenames) { + return $this->display($io, [$this->validate(file_get_contents('php://stdin'))]); + } + + if (!$filenames) { + throw new RuntimeException('Please provide a filename or pipe file content to STDIN.'); + } + + $filesInfo = []; + foreach ($filenames as $filename) { + if (!$this->isReadable($filename)) { + throw new RuntimeException(sprintf('File or directory "%s" is not readable.', $filename)); + } + + foreach ($this->getFiles($filename) as $file) { + $filesInfo[] = $this->validate(file_get_contents($file), $file); + } + } + + return $this->display($io, $filesInfo); + } + + private function validate(string $content, string $file = null): array + { + $errors = []; + + // Avoid: Warning DOMDocument::loadXML(): Empty string supplied as input + if ('' === trim($content)) { + return ['file' => $file, 'valid' => true]; + } + + $internal = libxml_use_internal_errors(true); + + $document = new \DOMDocument(); + $document->loadXML($content); + + if (null !== $targetLanguage = $this->getTargetLanguageFromFile($document)) { + $normalizedLocalePattern = sprintf('(%s|%s)', preg_quote($targetLanguage, '/'), preg_quote(str_replace('-', '_', $targetLanguage), '/')); + // strict file names require translation files to be named '____.locale.xlf' + // otherwise, both '____.locale.xlf' and 'locale.____.xlf' are allowed + // also, the regexp matching must be case-insensitive, as defined for 'target-language' values + // http://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html#target-language + $expectedFilenamePattern = $this->requireStrictFileNames ? sprintf('/^.*\.(?i:%s)\.(?:xlf|xliff)/', $normalizedLocalePattern) : sprintf('/^(?:.*\.(?i:%s)|(?i:%s)\..*)\.(?:xlf|xliff)/', $normalizedLocalePattern, $normalizedLocalePattern); + + if (0 === preg_match($expectedFilenamePattern, basename($file))) { + $errors[] = [ + 'line' => -1, + 'column' => -1, + 'message' => sprintf('There is a mismatch between the language included in the file name ("%s") and the "%s" value used in the "target-language" attribute of the file.', basename($file), $targetLanguage), + ]; + } + } + + foreach (XliffUtils::validateSchema($document) as $xmlError) { + $errors[] = [ + 'line' => $xmlError['line'], + 'column' => $xmlError['column'], + 'message' => $xmlError['message'], + ]; + } + + libxml_clear_errors(); + libxml_use_internal_errors($internal); + + return ['file' => $file, 'valid' => 0 === \count($errors), 'messages' => $errors]; + } + + private function display(SymfonyStyle $io, array $files) + { + switch ($this->format) { + case 'txt': + return $this->displayTxt($io, $files); + case 'json': + return $this->displayJson($io, $files); + case 'github': + return $this->displayTxt($io, $files, true); + default: + throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $this->format)); + } + } + + private function displayTxt(SymfonyStyle $io, array $filesInfo, bool $errorAsGithubAnnotations = false) + { + $countFiles = \count($filesInfo); + $erroredFiles = 0; + $githubReporter = $errorAsGithubAnnotations ? new GithubActionReporter($io) : null; + + foreach ($filesInfo as $info) { + if ($info['valid'] && $this->displayCorrectFiles) { + $io->comment('OK'.($info['file'] ? sprintf(' in %s', $info['file']) : '')); + } elseif (!$info['valid']) { + ++$erroredFiles; + $io->text(' ERROR '.($info['file'] ? sprintf(' in %s', $info['file']) : '')); + $io->listing(array_map(function ($error) use ($info, $githubReporter) { + // general document errors have a '-1' line number + $line = -1 === $error['line'] ? null : $error['line']; + + if ($githubReporter) { + $githubReporter->error($error['message'], $info['file'], $line, null !== $line ? $error['column'] : null); + } + + return null === $line ? $error['message'] : sprintf('Line %d, Column %d: %s', $line, $error['column'], $error['message']); + }, $info['messages'])); + } + } + + if (0 === $erroredFiles) { + $io->success(sprintf('All %d XLIFF files contain valid syntax.', $countFiles)); + } else { + $io->warning(sprintf('%d XLIFF files have valid syntax and %d contain errors.', $countFiles - $erroredFiles, $erroredFiles)); + } + + return min($erroredFiles, 1); + } + + private function displayJson(SymfonyStyle $io, array $filesInfo) + { + $errors = 0; + + array_walk($filesInfo, function (&$v) use (&$errors) { + $v['file'] = (string) $v['file']; + if (!$v['valid']) { + ++$errors; + } + }); + + $io->writeln(json_encode($filesInfo, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)); + + return min($errors, 1); + } + + private function getFiles(string $fileOrDirectory) + { + if (is_file($fileOrDirectory)) { + yield new \SplFileInfo($fileOrDirectory); + + return; + } + + foreach ($this->getDirectoryIterator($fileOrDirectory) as $file) { + if (!\in_array($file->getExtension(), ['xlf', 'xliff'])) { + continue; + } + + yield $file; + } + } + + private function getDirectoryIterator(string $directory) + { + $default = function ($directory) { + return new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS), + \RecursiveIteratorIterator::LEAVES_ONLY + ); + }; + + if (null !== $this->directoryIteratorProvider) { + return ($this->directoryIteratorProvider)($directory, $default); + } + + return $default($directory); + } + + private function isReadable(string $fileOrDirectory) + { + $default = function ($fileOrDirectory) { + return is_readable($fileOrDirectory); + }; + + if (null !== $this->isReadableProvider) { + return ($this->isReadableProvider)($fileOrDirectory, $default); + } + + return $default($fileOrDirectory); + } + + private function getTargetLanguageFromFile(\DOMDocument $xliffContents): ?string + { + foreach ($xliffContents->getElementsByTagName('file')[0]->attributes ?? [] as $attribute) { + if ('target-language' === $attribute->nodeName) { + return $attribute->nodeValue; + } + } + + return null; + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestOptionValuesFor('format')) { + $suggestions->suggestValues(['txt', 'json', 'github']); + } + } +} diff --git a/vendor/symfony/translation/DataCollector/TranslationDataCollector.php b/vendor/symfony/translation/DataCollector/TranslationDataCollector.php new file mode 100644 index 0000000..379130a --- /dev/null +++ b/vendor/symfony/translation/DataCollector/TranslationDataCollector.php @@ -0,0 +1,163 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\DataCollector\DataCollector; +use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface; +use Symfony\Component\Translation\DataCollectorTranslator; +use Symfony\Component\VarDumper\Cloner\Data; + +/** + * @author Abdellatif Ait boudad + * + * @final + */ +class TranslationDataCollector extends DataCollector implements LateDataCollectorInterface +{ + private $translator; + + public function __construct(DataCollectorTranslator $translator) + { + $this->translator = $translator; + } + + /** + * {@inheritdoc} + */ + public function lateCollect() + { + $messages = $this->sanitizeCollectedMessages($this->translator->getCollectedMessages()); + + $this->data += $this->computeCount($messages); + $this->data['messages'] = $messages; + + $this->data = $this->cloneVar($this->data); + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Throwable $exception = null) + { + $this->data['locale'] = $this->translator->getLocale(); + $this->data['fallback_locales'] = $this->translator->getFallbackLocales(); + } + + /** + * {@inheritdoc} + */ + public function reset() + { + $this->data = []; + } + + /** + * @return array|Data + */ + public function getMessages() + { + return $this->data['messages'] ?? []; + } + + public function getCountMissings(): int + { + return $this->data[DataCollectorTranslator::MESSAGE_MISSING] ?? 0; + } + + public function getCountFallbacks(): int + { + return $this->data[DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK] ?? 0; + } + + public function getCountDefines(): int + { + return $this->data[DataCollectorTranslator::MESSAGE_DEFINED] ?? 0; + } + + public function getLocale() + { + return !empty($this->data['locale']) ? $this->data['locale'] : null; + } + + /** + * @internal + */ + public function getFallbackLocales() + { + return (isset($this->data['fallback_locales']) && \count($this->data['fallback_locales']) > 0) ? $this->data['fallback_locales'] : []; + } + + /** + * {@inheritdoc} + */ + public function getName(): string + { + return 'translation'; + } + + private function sanitizeCollectedMessages(array $messages) + { + $result = []; + foreach ($messages as $key => $message) { + $messageId = $message['locale'].$message['domain'].$message['id']; + + if (!isset($result[$messageId])) { + $message['count'] = 1; + $message['parameters'] = !empty($message['parameters']) ? [$message['parameters']] : []; + $messages[$key]['translation'] = $this->sanitizeString($message['translation']); + $result[$messageId] = $message; + } else { + if (!empty($message['parameters'])) { + $result[$messageId]['parameters'][] = $message['parameters']; + } + + ++$result[$messageId]['count']; + } + + unset($messages[$key]); + } + + return $result; + } + + private function computeCount(array $messages) + { + $count = [ + DataCollectorTranslator::MESSAGE_DEFINED => 0, + DataCollectorTranslator::MESSAGE_MISSING => 0, + DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK => 0, + ]; + + foreach ($messages as $message) { + ++$count[$message['state']]; + } + + return $count; + } + + private function sanitizeString(string $string, int $length = 80) + { + $string = trim(preg_replace('/\s+/', ' ', $string)); + + if (false !== $encoding = mb_detect_encoding($string, null, true)) { + if (mb_strlen($string, $encoding) > $length) { + return mb_substr($string, 0, $length - 3, $encoding).'...'; + } + } elseif (\strlen($string) > $length) { + return substr($string, 0, $length - 3).'...'; + } + + return $string; + } +} diff --git a/vendor/symfony/translation/DataCollectorTranslator.php b/vendor/symfony/translation/DataCollectorTranslator.php new file mode 100644 index 0000000..ea5a2dd --- /dev/null +++ b/vendor/symfony/translation/DataCollectorTranslator.php @@ -0,0 +1,167 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation; + +use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface; +use Symfony\Component\Translation\Exception\InvalidArgumentException; +use Symfony\Contracts\Translation\LocaleAwareInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * @author Abdellatif Ait boudad + */ +class DataCollectorTranslator implements TranslatorInterface, TranslatorBagInterface, LocaleAwareInterface, WarmableInterface +{ + public const MESSAGE_DEFINED = 0; + public const MESSAGE_MISSING = 1; + public const MESSAGE_EQUALS_FALLBACK = 2; + + private $translator; + private $messages = []; + + /** + * @param TranslatorInterface&TranslatorBagInterface&LocaleAwareInterface $translator + */ + public function __construct(TranslatorInterface $translator) + { + if (!$translator instanceof TranslatorBagInterface || !$translator instanceof LocaleAwareInterface) { + throw new InvalidArgumentException(sprintf('The Translator "%s" must implement TranslatorInterface, TranslatorBagInterface and LocaleAwareInterface.', get_debug_type($translator))); + } + + $this->translator = $translator; + } + + /** + * {@inheritdoc} + */ + public function trans(?string $id, array $parameters = [], string $domain = null, string $locale = null) + { + $trans = $this->translator->trans($id = (string) $id, $parameters, $domain, $locale); + $this->collectMessage($locale, $domain, $id, $trans, $parameters); + + return $trans; + } + + /** + * {@inheritdoc} + */ + public function setLocale(string $locale) + { + $this->translator->setLocale($locale); + } + + /** + * {@inheritdoc} + */ + public function getLocale() + { + return $this->translator->getLocale(); + } + + /** + * {@inheritdoc} + */ + public function getCatalogue(string $locale = null) + { + return $this->translator->getCatalogue($locale); + } + + /** + * {@inheritdoc} + */ + public function getCatalogues(): array + { + return $this->translator->getCatalogues(); + } + + /** + * {@inheritdoc} + * + * @return string[] + */ + public function warmUp(string $cacheDir) + { + if ($this->translator instanceof WarmableInterface) { + return (array) $this->translator->warmUp($cacheDir); + } + + return []; + } + + /** + * Gets the fallback locales. + * + * @return array + */ + public function getFallbackLocales() + { + if ($this->translator instanceof Translator || method_exists($this->translator, 'getFallbackLocales')) { + return $this->translator->getFallbackLocales(); + } + + return []; + } + + /** + * Passes through all unknown calls onto the translator object. + */ + public function __call(string $method, array $args) + { + return $this->translator->{$method}(...$args); + } + + /** + * @return array + */ + public function getCollectedMessages() + { + return $this->messages; + } + + private function collectMessage(?string $locale, ?string $domain, string $id, string $translation, ?array $parameters = []) + { + if (null === $domain) { + $domain = 'messages'; + } + + $catalogue = $this->translator->getCatalogue($locale); + $locale = $catalogue->getLocale(); + $fallbackLocale = null; + if ($catalogue->defines($id, $domain)) { + $state = self::MESSAGE_DEFINED; + } elseif ($catalogue->has($id, $domain)) { + $state = self::MESSAGE_EQUALS_FALLBACK; + + $fallbackCatalogue = $catalogue->getFallbackCatalogue(); + while ($fallbackCatalogue) { + if ($fallbackCatalogue->defines($id, $domain)) { + $fallbackLocale = $fallbackCatalogue->getLocale(); + break; + } + $fallbackCatalogue = $fallbackCatalogue->getFallbackCatalogue(); + } + } else { + $state = self::MESSAGE_MISSING; + } + + $this->messages[] = [ + 'locale' => $locale, + 'fallbackLocale' => $fallbackLocale, + 'domain' => $domain, + 'id' => $id, + 'translation' => $translation, + 'parameters' => $parameters, + 'state' => $state, + 'transChoiceNumber' => isset($parameters['%count%']) && is_numeric($parameters['%count%']) ? $parameters['%count%'] : null, + ]; + } +} diff --git a/vendor/symfony/translation/DependencyInjection/TranslationDumperPass.php b/vendor/symfony/translation/DependencyInjection/TranslationDumperPass.php new file mode 100644 index 0000000..6d78342 --- /dev/null +++ b/vendor/symfony/translation/DependencyInjection/TranslationDumperPass.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Adds tagged translation.formatter services to translation writer. + */ +class TranslationDumperPass implements CompilerPassInterface +{ + private $writerServiceId; + private $dumperTag; + + public function __construct(string $writerServiceId = 'translation.writer', string $dumperTag = 'translation.dumper') + { + if (1 < \func_num_args()) { + trigger_deprecation('symfony/translation', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); + } + + $this->writerServiceId = $writerServiceId; + $this->dumperTag = $dumperTag; + } + + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition($this->writerServiceId)) { + return; + } + + $definition = $container->getDefinition($this->writerServiceId); + + foreach ($container->findTaggedServiceIds($this->dumperTag, true) as $id => $attributes) { + $definition->addMethodCall('addDumper', [$attributes[0]['alias'], new Reference($id)]); + } + } +} diff --git a/vendor/symfony/translation/DependencyInjection/TranslationExtractorPass.php b/vendor/symfony/translation/DependencyInjection/TranslationExtractorPass.php new file mode 100644 index 0000000..fab6b20 --- /dev/null +++ b/vendor/symfony/translation/DependencyInjection/TranslationExtractorPass.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Adds tagged translation.extractor services to translation extractor. + */ +class TranslationExtractorPass implements CompilerPassInterface +{ + private $extractorServiceId; + private $extractorTag; + + public function __construct(string $extractorServiceId = 'translation.extractor', string $extractorTag = 'translation.extractor') + { + if (0 < \func_num_args()) { + trigger_deprecation('symfony/translation', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); + } + + $this->extractorServiceId = $extractorServiceId; + $this->extractorTag = $extractorTag; + } + + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition($this->extractorServiceId)) { + return; + } + + $definition = $container->getDefinition($this->extractorServiceId); + + foreach ($container->findTaggedServiceIds($this->extractorTag, true) as $id => $attributes) { + if (!isset($attributes[0]['alias'])) { + throw new RuntimeException(sprintf('The alias for the tag "translation.extractor" of service "%s" must be set.', $id)); + } + + $definition->addMethodCall('addExtractor', [$attributes[0]['alias'], new Reference($id)]); + } + } +} diff --git a/vendor/symfony/translation/DependencyInjection/TranslatorPass.php b/vendor/symfony/translation/DependencyInjection/TranslatorPass.php new file mode 100644 index 0000000..e6a4b36 --- /dev/null +++ b/vendor/symfony/translation/DependencyInjection/TranslatorPass.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +class TranslatorPass implements CompilerPassInterface +{ + private $translatorServiceId; + private $readerServiceId; + private $loaderTag; + private $debugCommandServiceId; + private $updateCommandServiceId; + + public function __construct(string $translatorServiceId = 'translator.default', string $readerServiceId = 'translation.reader', string $loaderTag = 'translation.loader', string $debugCommandServiceId = 'console.command.translation_debug', string $updateCommandServiceId = 'console.command.translation_extract') + { + if (0 < \func_num_args()) { + trigger_deprecation('symfony/translation', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); + } + + $this->translatorServiceId = $translatorServiceId; + $this->readerServiceId = $readerServiceId; + $this->loaderTag = $loaderTag; + $this->debugCommandServiceId = $debugCommandServiceId; + $this->updateCommandServiceId = $updateCommandServiceId; + } + + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition($this->translatorServiceId)) { + return; + } + + $loaders = []; + $loaderRefs = []; + foreach ($container->findTaggedServiceIds($this->loaderTag, true) as $id => $attributes) { + $loaderRefs[$id] = new Reference($id); + $loaders[$id][] = $attributes[0]['alias']; + if (isset($attributes[0]['legacy-alias'])) { + $loaders[$id][] = $attributes[0]['legacy-alias']; + } + } + + if ($container->hasDefinition($this->readerServiceId)) { + $definition = $container->getDefinition($this->readerServiceId); + foreach ($loaders as $id => $formats) { + foreach ($formats as $format) { + $definition->addMethodCall('addLoader', [$format, $loaderRefs[$id]]); + } + } + } + + $container + ->findDefinition($this->translatorServiceId) + ->replaceArgument(0, ServiceLocatorTagPass::register($container, $loaderRefs)) + ->replaceArgument(3, $loaders) + ; + + if (!$container->hasParameter('twig.default_path')) { + return; + } + + $paths = array_keys($container->getDefinition('twig.template_iterator')->getArgument(1)); + if ($container->hasDefinition($this->debugCommandServiceId)) { + $definition = $container->getDefinition($this->debugCommandServiceId); + $definition->replaceArgument(4, $container->getParameter('twig.default_path')); + + if (\count($definition->getArguments()) > 6) { + $definition->replaceArgument(6, $paths); + } + } + if ($container->hasDefinition($this->updateCommandServiceId)) { + $definition = $container->getDefinition($this->updateCommandServiceId); + $definition->replaceArgument(5, $container->getParameter('twig.default_path')); + + if (\count($definition->getArguments()) > 7) { + $definition->replaceArgument(7, $paths); + } + } + } +} diff --git a/vendor/symfony/translation/DependencyInjection/TranslatorPathsPass.php b/vendor/symfony/translation/DependencyInjection/TranslatorPathsPass.php new file mode 100644 index 0000000..18a71c4 --- /dev/null +++ b/vendor/symfony/translation/DependencyInjection/TranslatorPathsPass.php @@ -0,0 +1,163 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\AbstractRecursivePass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\ServiceLocator; + +/** + * @author Yonel Ceruto + */ +class TranslatorPathsPass extends AbstractRecursivePass +{ + private $translatorServiceId; + private $debugCommandServiceId; + private $updateCommandServiceId; + private $resolverServiceId; + private $level = 0; + + /** + * @var array + */ + private $paths = []; + + /** + * @var array + */ + private $definitions = []; + + /** + * @var array> + */ + private $controllers = []; + + public function __construct(string $translatorServiceId = 'translator', string $debugCommandServiceId = 'console.command.translation_debug', string $updateCommandServiceId = 'console.command.translation_extract', string $resolverServiceId = 'argument_resolver.service') + { + if (0 < \func_num_args()) { + trigger_deprecation('symfony/translation', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); + } + + $this->translatorServiceId = $translatorServiceId; + $this->debugCommandServiceId = $debugCommandServiceId; + $this->updateCommandServiceId = $updateCommandServiceId; + $this->resolverServiceId = $resolverServiceId; + } + + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition($this->translatorServiceId)) { + return; + } + + foreach ($this->findControllerArguments($container) as $controller => $argument) { + $id = substr($controller, 0, strpos($controller, ':') ?: \strlen($controller)); + if ($container->hasDefinition($id)) { + [$locatorRef] = $argument->getValues(); + $this->controllers[(string) $locatorRef][$container->getDefinition($id)->getClass()] = true; + } + } + + try { + parent::process($container); + + $paths = []; + foreach ($this->paths as $class => $_) { + if (($r = $container->getReflectionClass($class)) && !$r->isInterface()) { + $paths[] = $r->getFileName(); + foreach ($r->getTraits() as $trait) { + $paths[] = $trait->getFileName(); + } + } + } + if ($paths) { + if ($container->hasDefinition($this->debugCommandServiceId)) { + $definition = $container->getDefinition($this->debugCommandServiceId); + $definition->replaceArgument(6, array_merge($definition->getArgument(6), $paths)); + } + if ($container->hasDefinition($this->updateCommandServiceId)) { + $definition = $container->getDefinition($this->updateCommandServiceId); + $definition->replaceArgument(7, array_merge($definition->getArgument(7), $paths)); + } + } + } finally { + $this->level = 0; + $this->paths = []; + $this->definitions = []; + } + } + + protected function processValue($value, bool $isRoot = false) + { + if ($value instanceof Reference) { + if ((string) $value === $this->translatorServiceId) { + for ($i = $this->level - 1; $i >= 0; --$i) { + $class = $this->definitions[$i]->getClass(); + + if (ServiceLocator::class === $class) { + if (!isset($this->controllers[$this->currentId])) { + continue; + } + foreach ($this->controllers[$this->currentId] as $class => $_) { + $this->paths[$class] = true; + } + } else { + $this->paths[$class] = true; + } + + break; + } + } + + return $value; + } + + if ($value instanceof Definition) { + $this->definitions[$this->level++] = $value; + $value = parent::processValue($value, $isRoot); + unset($this->definitions[--$this->level]); + + return $value; + } + + return parent::processValue($value, $isRoot); + } + + private function findControllerArguments(ContainerBuilder $container): array + { + if ($container->hasDefinition($this->resolverServiceId)) { + $argument = $container->getDefinition($this->resolverServiceId)->getArgument(0); + if ($argument instanceof Reference) { + $argument = $container->getDefinition($argument); + } + + return $argument->getArgument(0); + } + + if ($container->hasDefinition('debug.'.$this->resolverServiceId)) { + $argument = $container->getDefinition('debug.'.$this->resolverServiceId)->getArgument(0); + if ($argument instanceof Reference) { + $argument = $container->getDefinition($argument); + } + $argument = $argument->getArgument(0); + if ($argument instanceof Reference) { + $argument = $container->getDefinition($argument); + } + + return $argument->getArgument(0); + } + + return []; + } +} diff --git a/vendor/symfony/translation/Dumper/CsvFileDumper.php b/vendor/symfony/translation/Dumper/CsvFileDumper.php new file mode 100644 index 0000000..0c8589a --- /dev/null +++ b/vendor/symfony/translation/Dumper/CsvFileDumper.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Dumper; + +use Symfony\Component\Translation\MessageCatalogue; + +/** + * CsvFileDumper generates a csv formatted string representation of a message catalogue. + * + * @author Stealth35 + */ +class CsvFileDumper extends FileDumper +{ + private $delimiter = ';'; + private $enclosure = '"'; + + /** + * {@inheritdoc} + */ + public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []) + { + $handle = fopen('php://memory', 'r+'); + + foreach ($messages->all($domain) as $source => $target) { + fputcsv($handle, [$source, $target], $this->delimiter, $this->enclosure); + } + + rewind($handle); + $output = stream_get_contents($handle); + fclose($handle); + + return $output; + } + + /** + * Sets the delimiter and escape character for CSV. + */ + public function setCsvControl(string $delimiter = ';', string $enclosure = '"') + { + $this->delimiter = $delimiter; + $this->enclosure = $enclosure; + } + + /** + * {@inheritdoc} + */ + protected function getExtension() + { + return 'csv'; + } +} diff --git a/vendor/symfony/translation/Dumper/DumperInterface.php b/vendor/symfony/translation/Dumper/DumperInterface.php new file mode 100644 index 0000000..7cdaef5 --- /dev/null +++ b/vendor/symfony/translation/Dumper/DumperInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Dumper; + +use Symfony\Component\Translation\MessageCatalogue; + +/** + * DumperInterface is the interface implemented by all translation dumpers. + * There is no common option. + * + * @author Michel Salib + */ +interface DumperInterface +{ + /** + * Dumps the message catalogue. + * + * @param array $options Options that are used by the dumper + */ + public function dump(MessageCatalogue $messages, array $options = []); +} diff --git a/vendor/symfony/translation/Dumper/FileDumper.php b/vendor/symfony/translation/Dumper/FileDumper.php new file mode 100644 index 0000000..0e10845 --- /dev/null +++ b/vendor/symfony/translation/Dumper/FileDumper.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Dumper; + +use Symfony\Component\Translation\Exception\InvalidArgumentException; +use Symfony\Component\Translation\Exception\RuntimeException; +use Symfony\Component\Translation\MessageCatalogue; + +/** + * FileDumper is an implementation of DumperInterface that dump a message catalogue to file(s). + * + * Options: + * - path (mandatory): the directory where the files should be saved + * + * @author Michel Salib + */ +abstract class FileDumper implements DumperInterface +{ + /** + * A template for the relative paths to files. + * + * @var string + */ + protected $relativePathTemplate = '%domain%.%locale%.%extension%'; + + /** + * Sets the template for the relative paths to files. + * + * @param string $relativePathTemplate A template for the relative paths to files + */ + public function setRelativePathTemplate(string $relativePathTemplate) + { + $this->relativePathTemplate = $relativePathTemplate; + } + + /** + * {@inheritdoc} + */ + public function dump(MessageCatalogue $messages, array $options = []) + { + if (!\array_key_exists('path', $options)) { + throw new InvalidArgumentException('The file dumper needs a path option.'); + } + + // save a file for each domain + foreach ($messages->getDomains() as $domain) { + $fullpath = $options['path'].'/'.$this->getRelativePath($domain, $messages->getLocale()); + if (!file_exists($fullpath)) { + $directory = \dirname($fullpath); + if (!file_exists($directory) && !@mkdir($directory, 0777, true)) { + throw new RuntimeException(sprintf('Unable to create directory "%s".', $directory)); + } + } + + $intlDomain = $domain.MessageCatalogue::INTL_DOMAIN_SUFFIX; + $intlMessages = $messages->all($intlDomain); + + if ($intlMessages) { + $intlPath = $options['path'].'/'.$this->getRelativePath($intlDomain, $messages->getLocale()); + file_put_contents($intlPath, $this->formatCatalogue($messages, $intlDomain, $options)); + + $messages->replace([], $intlDomain); + + try { + if ($messages->all($domain)) { + file_put_contents($fullpath, $this->formatCatalogue($messages, $domain, $options)); + } + continue; + } finally { + $messages->replace($intlMessages, $intlDomain); + } + } + + file_put_contents($fullpath, $this->formatCatalogue($messages, $domain, $options)); + } + } + + /** + * Transforms a domain of a message catalogue to its string representation. + * + * @return string + */ + abstract public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []); + + /** + * Gets the file extension of the dumper. + * + * @return string + */ + abstract protected function getExtension(); + + /** + * Gets the relative file path using the template. + */ + private function getRelativePath(string $domain, string $locale): string + { + return strtr($this->relativePathTemplate, [ + '%domain%' => $domain, + '%locale%' => $locale, + '%extension%' => $this->getExtension(), + ]); + } +} diff --git a/vendor/symfony/translation/Dumper/IcuResFileDumper.php b/vendor/symfony/translation/Dumper/IcuResFileDumper.php new file mode 100644 index 0000000..cdc5991 --- /dev/null +++ b/vendor/symfony/translation/Dumper/IcuResFileDumper.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Dumper; + +use Symfony\Component\Translation\MessageCatalogue; + +/** + * IcuResDumper generates an ICU ResourceBundle formatted string representation of a message catalogue. + * + * @author Stealth35 + */ +class IcuResFileDumper extends FileDumper +{ + /** + * {@inheritdoc} + */ + protected $relativePathTemplate = '%domain%/%locale%.%extension%'; + + /** + * {@inheritdoc} + */ + public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []) + { + $data = $indexes = $resources = ''; + + foreach ($messages->all($domain) as $source => $target) { + $indexes .= pack('v', \strlen($data) + 28); + $data .= $source."\0"; + } + + $data .= $this->writePadding($data); + + $keyTop = $this->getPosition($data); + + foreach ($messages->all($domain) as $source => $target) { + $resources .= pack('V', $this->getPosition($data)); + + $data .= pack('V', \strlen($target)) + .mb_convert_encoding($target."\0", 'UTF-16LE', 'UTF-8') + .$this->writePadding($data) + ; + } + + $resOffset = $this->getPosition($data); + + $data .= pack('v', \count($messages->all($domain))) + .$indexes + .$this->writePadding($data) + .$resources + ; + + $bundleTop = $this->getPosition($data); + + $root = pack('V7', + $resOffset + (2 << 28), // Resource Offset + Resource Type + 6, // Index length + $keyTop, // Index keys top + $bundleTop, // Index resources top + $bundleTop, // Index bundle top + \count($messages->all($domain)), // Index max table length + 0 // Index attributes + ); + + $header = pack('vC2v4C12@32', + 32, // Header size + 0xDA, 0x27, // Magic number 1 and 2 + 20, 0, 0, 2, // Rest of the header, ..., Size of a char + 0x52, 0x65, 0x73, 0x42, // Data format identifier + 1, 2, 0, 0, // Data version + 1, 4, 0, 0 // Unicode version + ); + + return $header.$root.$data; + } + + private function writePadding(string $data): ?string + { + $padding = \strlen($data) % 4; + + return $padding ? str_repeat("\xAA", 4 - $padding) : null; + } + + private function getPosition(string $data) + { + return (\strlen($data) + 28) / 4; + } + + /** + * {@inheritdoc} + */ + protected function getExtension() + { + return 'res'; + } +} diff --git a/vendor/symfony/translation/Dumper/IniFileDumper.php b/vendor/symfony/translation/Dumper/IniFileDumper.php new file mode 100644 index 0000000..93c900a --- /dev/null +++ b/vendor/symfony/translation/Dumper/IniFileDumper.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Dumper; + +use Symfony\Component\Translation\MessageCatalogue; + +/** + * IniFileDumper generates an ini formatted string representation of a message catalogue. + * + * @author Stealth35 + */ +class IniFileDumper extends FileDumper +{ + /** + * {@inheritdoc} + */ + public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []) + { + $output = ''; + + foreach ($messages->all($domain) as $source => $target) { + $escapeTarget = str_replace('"', '\"', $target); + $output .= $source.'="'.$escapeTarget."\"\n"; + } + + return $output; + } + + /** + * {@inheritdoc} + */ + protected function getExtension() + { + return 'ini'; + } +} diff --git a/vendor/symfony/translation/Dumper/JsonFileDumper.php b/vendor/symfony/translation/Dumper/JsonFileDumper.php new file mode 100644 index 0000000..34c0b56 --- /dev/null +++ b/vendor/symfony/translation/Dumper/JsonFileDumper.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Dumper; + +use Symfony\Component\Translation\MessageCatalogue; + +/** + * JsonFileDumper generates an json formatted string representation of a message catalogue. + * + * @author singles + */ +class JsonFileDumper extends FileDumper +{ + /** + * {@inheritdoc} + */ + public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []) + { + $flags = $options['json_encoding'] ?? \JSON_PRETTY_PRINT; + + return json_encode($messages->all($domain), $flags); + } + + /** + * {@inheritdoc} + */ + protected function getExtension() + { + return 'json'; + } +} diff --git a/vendor/symfony/translation/Dumper/MoFileDumper.php b/vendor/symfony/translation/Dumper/MoFileDumper.php new file mode 100644 index 0000000..54d0da8 --- /dev/null +++ b/vendor/symfony/translation/Dumper/MoFileDumper.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Dumper; + +use Symfony\Component\Translation\Loader\MoFileLoader; +use Symfony\Component\Translation\MessageCatalogue; + +/** + * MoFileDumper generates a gettext formatted string representation of a message catalogue. + * + * @author Stealth35 + */ +class MoFileDumper extends FileDumper +{ + /** + * {@inheritdoc} + */ + public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []) + { + $sources = $targets = $sourceOffsets = $targetOffsets = ''; + $offsets = []; + $size = 0; + + foreach ($messages->all($domain) as $source => $target) { + $offsets[] = array_map('strlen', [$sources, $source, $targets, $target]); + $sources .= "\0".$source; + $targets .= "\0".$target; + ++$size; + } + + $header = [ + 'magicNumber' => MoFileLoader::MO_LITTLE_ENDIAN_MAGIC, + 'formatRevision' => 0, + 'count' => $size, + 'offsetId' => MoFileLoader::MO_HEADER_SIZE, + 'offsetTranslated' => MoFileLoader::MO_HEADER_SIZE + (8 * $size), + 'sizeHashes' => 0, + 'offsetHashes' => MoFileLoader::MO_HEADER_SIZE + (16 * $size), + ]; + + $sourcesSize = \strlen($sources); + $sourcesStart = $header['offsetHashes'] + 1; + + foreach ($offsets as $offset) { + $sourceOffsets .= $this->writeLong($offset[1]) + .$this->writeLong($offset[0] + $sourcesStart); + $targetOffsets .= $this->writeLong($offset[3]) + .$this->writeLong($offset[2] + $sourcesStart + $sourcesSize); + } + + $output = implode('', array_map([$this, 'writeLong'], $header)) + .$sourceOffsets + .$targetOffsets + .$sources + .$targets + ; + + return $output; + } + + /** + * {@inheritdoc} + */ + protected function getExtension() + { + return 'mo'; + } + + private function writeLong($str): string + { + return pack('V*', $str); + } +} diff --git a/vendor/symfony/translation/Dumper/PhpFileDumper.php b/vendor/symfony/translation/Dumper/PhpFileDumper.php new file mode 100644 index 0000000..6163b52 --- /dev/null +++ b/vendor/symfony/translation/Dumper/PhpFileDumper.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Dumper; + +use Symfony\Component\Translation\MessageCatalogue; + +/** + * PhpFileDumper generates PHP files from a message catalogue. + * + * @author Michel Salib + */ +class PhpFileDumper extends FileDumper +{ + /** + * {@inheritdoc} + */ + public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []) + { + return "all($domain), true).";\n"; + } + + /** + * {@inheritdoc} + */ + protected function getExtension() + { + return 'php'; + } +} diff --git a/vendor/symfony/translation/Dumper/PoFileDumper.php b/vendor/symfony/translation/Dumper/PoFileDumper.php new file mode 100644 index 0000000..0d82281 --- /dev/null +++ b/vendor/symfony/translation/Dumper/PoFileDumper.php @@ -0,0 +1,137 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Dumper; + +use Symfony\Component\Translation\MessageCatalogue; + +/** + * PoFileDumper generates a gettext formatted string representation of a message catalogue. + * + * @author Stealth35 + */ +class PoFileDumper extends FileDumper +{ + /** + * {@inheritdoc} + */ + public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []) + { + $output = 'msgid ""'."\n"; + $output .= 'msgstr ""'."\n"; + $output .= '"Content-Type: text/plain; charset=UTF-8\n"'."\n"; + $output .= '"Content-Transfer-Encoding: 8bit\n"'."\n"; + $output .= '"Language: '.$messages->getLocale().'\n"'."\n"; + $output .= "\n"; + + $newLine = false; + foreach ($messages->all($domain) as $source => $target) { + if ($newLine) { + $output .= "\n"; + } else { + $newLine = true; + } + $metadata = $messages->getMetadata($source, $domain); + + if (isset($metadata['comments'])) { + $output .= $this->formatComments($metadata['comments']); + } + if (isset($metadata['flags'])) { + $output .= $this->formatComments(implode(',', (array) $metadata['flags']), ','); + } + if (isset($metadata['sources'])) { + $output .= $this->formatComments(implode(' ', (array) $metadata['sources']), ':'); + } + + $sourceRules = $this->getStandardRules($source); + $targetRules = $this->getStandardRules($target); + if (2 == \count($sourceRules) && [] !== $targetRules) { + $output .= sprintf('msgid "%s"'."\n", $this->escape($sourceRules[0])); + $output .= sprintf('msgid_plural "%s"'."\n", $this->escape($sourceRules[1])); + foreach ($targetRules as $i => $targetRule) { + $output .= sprintf('msgstr[%d] "%s"'."\n", $i, $this->escape($targetRule)); + } + } else { + $output .= sprintf('msgid "%s"'."\n", $this->escape($source)); + $output .= sprintf('msgstr "%s"'."\n", $this->escape($target)); + } + } + + return $output; + } + + private function getStandardRules(string $id) + { + // Partly copied from TranslatorTrait::trans. + $parts = []; + if (preg_match('/^\|++$/', $id)) { + $parts = explode('|', $id); + } elseif (preg_match_all('/(?:\|\||[^\|])++/', $id, $matches)) { + $parts = $matches[0]; + } + + $intervalRegexp = <<<'EOF' +/^(?P + ({\s* + (\-?\d+(\.\d+)?[\s*,\s*\-?\d+(\.\d+)?]*) + \s*}) + + | + + (?P[\[\]]) + \s* + (?P-Inf|\-?\d+(\.\d+)?) + \s*,\s* + (?P\+?Inf|\-?\d+(\.\d+)?) + \s* + (?P[\[\]]) +)\s*(?P.*?)$/xs +EOF; + + $standardRules = []; + foreach ($parts as $part) { + $part = trim(str_replace('||', '|', $part)); + + if (preg_match($intervalRegexp, $part)) { + // Explicit rule is not a standard rule. + return []; + } else { + $standardRules[] = $part; + } + } + + return $standardRules; + } + + /** + * {@inheritdoc} + */ + protected function getExtension() + { + return 'po'; + } + + private function escape(string $str): string + { + return addcslashes($str, "\0..\37\42\134"); + } + + private function formatComments($comments, string $prefix = ''): ?string + { + $output = null; + + foreach ((array) $comments as $comment) { + $output .= sprintf('#%s %s'."\n", $prefix, $comment); + } + + return $output; + } +} diff --git a/vendor/symfony/translation/Dumper/QtFileDumper.php b/vendor/symfony/translation/Dumper/QtFileDumper.php new file mode 100644 index 0000000..406e9f0 --- /dev/null +++ b/vendor/symfony/translation/Dumper/QtFileDumper.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Dumper; + +use Symfony\Component\Translation\MessageCatalogue; + +/** + * QtFileDumper generates ts files from a message catalogue. + * + * @author Benjamin Eberlei + */ +class QtFileDumper extends FileDumper +{ + /** + * {@inheritdoc} + */ + public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []) + { + $dom = new \DOMDocument('1.0', 'utf-8'); + $dom->formatOutput = true; + $ts = $dom->appendChild($dom->createElement('TS')); + $context = $ts->appendChild($dom->createElement('context')); + $context->appendChild($dom->createElement('name', $domain)); + + foreach ($messages->all($domain) as $source => $target) { + $message = $context->appendChild($dom->createElement('message')); + $metadata = $messages->getMetadata($source, $domain); + if (isset($metadata['sources'])) { + foreach ((array) $metadata['sources'] as $location) { + $loc = explode(':', $location, 2); + $location = $message->appendChild($dom->createElement('location')); + $location->setAttribute('filename', $loc[0]); + if (isset($loc[1])) { + $location->setAttribute('line', $loc[1]); + } + } + } + $message->appendChild($dom->createElement('source', $source)); + $message->appendChild($dom->createElement('translation', $target)); + } + + return $dom->saveXML(); + } + + /** + * {@inheritdoc} + */ + protected function getExtension() + { + return 'ts'; + } +} diff --git a/vendor/symfony/translation/Dumper/XliffFileDumper.php b/vendor/symfony/translation/Dumper/XliffFileDumper.php new file mode 100644 index 0000000..f7dbdcd --- /dev/null +++ b/vendor/symfony/translation/Dumper/XliffFileDumper.php @@ -0,0 +1,203 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Dumper; + +use Symfony\Component\Translation\Exception\InvalidArgumentException; +use Symfony\Component\Translation\MessageCatalogue; + +/** + * XliffFileDumper generates xliff files from a message catalogue. + * + * @author Michel Salib + */ +class XliffFileDumper extends FileDumper +{ + /** + * {@inheritdoc} + */ + public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []) + { + $xliffVersion = '1.2'; + if (\array_key_exists('xliff_version', $options)) { + $xliffVersion = $options['xliff_version']; + } + + if (\array_key_exists('default_locale', $options)) { + $defaultLocale = $options['default_locale']; + } else { + $defaultLocale = \Locale::getDefault(); + } + + if ('1.2' === $xliffVersion) { + return $this->dumpXliff1($defaultLocale, $messages, $domain, $options); + } + if ('2.0' === $xliffVersion) { + return $this->dumpXliff2($defaultLocale, $messages, $domain); + } + + throw new InvalidArgumentException(sprintf('No support implemented for dumping XLIFF version "%s".', $xliffVersion)); + } + + /** + * {@inheritdoc} + */ + protected function getExtension() + { + return 'xlf'; + } + + private function dumpXliff1(string $defaultLocale, MessageCatalogue $messages, ?string $domain, array $options = []) + { + $toolInfo = ['tool-id' => 'symfony', 'tool-name' => 'Symfony']; + if (\array_key_exists('tool_info', $options)) { + $toolInfo = array_merge($toolInfo, $options['tool_info']); + } + + $dom = new \DOMDocument('1.0', 'utf-8'); + $dom->formatOutput = true; + + $xliff = $dom->appendChild($dom->createElement('xliff')); + $xliff->setAttribute('version', '1.2'); + $xliff->setAttribute('xmlns', 'urn:oasis:names:tc:xliff:document:1.2'); + + $xliffFile = $xliff->appendChild($dom->createElement('file')); + $xliffFile->setAttribute('source-language', str_replace('_', '-', $defaultLocale)); + $xliffFile->setAttribute('target-language', str_replace('_', '-', $messages->getLocale())); + $xliffFile->setAttribute('datatype', 'plaintext'); + $xliffFile->setAttribute('original', 'file.ext'); + + $xliffHead = $xliffFile->appendChild($dom->createElement('header')); + $xliffTool = $xliffHead->appendChild($dom->createElement('tool')); + foreach ($toolInfo as $id => $value) { + $xliffTool->setAttribute($id, $value); + } + + $xliffBody = $xliffFile->appendChild($dom->createElement('body')); + foreach ($messages->all($domain) as $source => $target) { + $translation = $dom->createElement('trans-unit'); + + $translation->setAttribute('id', strtr(substr(base64_encode(hash('sha256', $source, true)), 0, 7), '/+', '._')); + $translation->setAttribute('resname', $source); + + $s = $translation->appendChild($dom->createElement('source')); + $s->appendChild($dom->createTextNode($source)); + + // Does the target contain characters requiring a CDATA section? + $text = 1 === preg_match('/[&<>]/', $target) ? $dom->createCDATASection($target) : $dom->createTextNode($target); + + $targetElement = $dom->createElement('target'); + $metadata = $messages->getMetadata($source, $domain); + if ($this->hasMetadataArrayInfo('target-attributes', $metadata)) { + foreach ($metadata['target-attributes'] as $name => $value) { + $targetElement->setAttribute($name, $value); + } + } + $t = $translation->appendChild($targetElement); + $t->appendChild($text); + + if ($this->hasMetadataArrayInfo('notes', $metadata)) { + foreach ($metadata['notes'] as $note) { + if (!isset($note['content'])) { + continue; + } + + $n = $translation->appendChild($dom->createElement('note')); + $n->appendChild($dom->createTextNode($note['content'])); + + if (isset($note['priority'])) { + $n->setAttribute('priority', $note['priority']); + } + + if (isset($note['from'])) { + $n->setAttribute('from', $note['from']); + } + } + } + + $xliffBody->appendChild($translation); + } + + return $dom->saveXML(); + } + + private function dumpXliff2(string $defaultLocale, MessageCatalogue $messages, ?string $domain) + { + $dom = new \DOMDocument('1.0', 'utf-8'); + $dom->formatOutput = true; + + $xliff = $dom->appendChild($dom->createElement('xliff')); + $xliff->setAttribute('xmlns', 'urn:oasis:names:tc:xliff:document:2.0'); + $xliff->setAttribute('version', '2.0'); + $xliff->setAttribute('srcLang', str_replace('_', '-', $defaultLocale)); + $xliff->setAttribute('trgLang', str_replace('_', '-', $messages->getLocale())); + + $xliffFile = $xliff->appendChild($dom->createElement('file')); + if (str_ends_with($domain, MessageCatalogue::INTL_DOMAIN_SUFFIX)) { + $xliffFile->setAttribute('id', substr($domain, 0, -\strlen(MessageCatalogue::INTL_DOMAIN_SUFFIX)).'.'.$messages->getLocale()); + } else { + $xliffFile->setAttribute('id', $domain.'.'.$messages->getLocale()); + } + + foreach ($messages->all($domain) as $source => $target) { + $translation = $dom->createElement('unit'); + $translation->setAttribute('id', strtr(substr(base64_encode(hash('sha256', $source, true)), 0, 7), '/+', '._')); + + if (\strlen($source) <= 80) { + $translation->setAttribute('name', $source); + } + + $metadata = $messages->getMetadata($source, $domain); + + // Add notes section + if ($this->hasMetadataArrayInfo('notes', $metadata)) { + $notesElement = $dom->createElement('notes'); + foreach ($metadata['notes'] as $note) { + $n = $dom->createElement('note'); + $n->appendChild($dom->createTextNode($note['content'] ?? '')); + unset($note['content']); + + foreach ($note as $name => $value) { + $n->setAttribute($name, $value); + } + $notesElement->appendChild($n); + } + $translation->appendChild($notesElement); + } + + $segment = $translation->appendChild($dom->createElement('segment')); + + $s = $segment->appendChild($dom->createElement('source')); + $s->appendChild($dom->createTextNode($source)); + + // Does the target contain characters requiring a CDATA section? + $text = 1 === preg_match('/[&<>]/', $target) ? $dom->createCDATASection($target) : $dom->createTextNode($target); + + $targetElement = $dom->createElement('target'); + if ($this->hasMetadataArrayInfo('target-attributes', $metadata)) { + foreach ($metadata['target-attributes'] as $name => $value) { + $targetElement->setAttribute($name, $value); + } + } + $t = $segment->appendChild($targetElement); + $t->appendChild($text); + + $xliffFile->appendChild($translation); + } + + return $dom->saveXML(); + } + + private function hasMetadataArrayInfo(string $key, array $metadata = null): bool + { + return is_iterable($metadata[$key] ?? null); + } +} diff --git a/vendor/symfony/translation/Dumper/YamlFileDumper.php b/vendor/symfony/translation/Dumper/YamlFileDumper.php new file mode 100644 index 0000000..0b21e8c --- /dev/null +++ b/vendor/symfony/translation/Dumper/YamlFileDumper.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Dumper; + +use Symfony\Component\Translation\Exception\LogicException; +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\Util\ArrayConverter; +use Symfony\Component\Yaml\Yaml; + +/** + * YamlFileDumper generates yaml files from a message catalogue. + * + * @author Michel Salib + */ +class YamlFileDumper extends FileDumper +{ + private $extension; + + public function __construct(string $extension = 'yml') + { + $this->extension = $extension; + } + + /** + * {@inheritdoc} + */ + public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []) + { + if (!class_exists(Yaml::class)) { + throw new LogicException('Dumping translations in the YAML format requires the Symfony Yaml component.'); + } + + $data = $messages->all($domain); + + if (isset($options['as_tree']) && $options['as_tree']) { + $data = ArrayConverter::expandToTree($data); + } + + if (isset($options['inline']) && ($inline = (int) $options['inline']) > 0) { + return Yaml::dump($data, $inline); + } + + return Yaml::dump($data); + } + + /** + * {@inheritdoc} + */ + protected function getExtension() + { + return $this->extension; + } +} diff --git a/vendor/symfony/translation/Exception/ExceptionInterface.php b/vendor/symfony/translation/Exception/ExceptionInterface.php new file mode 100644 index 0000000..8f9c54e --- /dev/null +++ b/vendor/symfony/translation/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Exception; + +/** + * Exception interface for all exceptions thrown by the component. + * + * @author Fabien Potencier + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/vendor/symfony/translation/Exception/IncompleteDsnException.php b/vendor/symfony/translation/Exception/IncompleteDsnException.php new file mode 100644 index 0000000..cb0ce02 --- /dev/null +++ b/vendor/symfony/translation/Exception/IncompleteDsnException.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Exception; + +class IncompleteDsnException extends InvalidArgumentException +{ + public function __construct(string $message, string $dsn = null, \Throwable $previous = null) + { + if ($dsn) { + $message = sprintf('Invalid "%s" provider DSN: ', $dsn).$message; + } + + parent::__construct($message, 0, $previous); + } +} diff --git a/vendor/symfony/translation/Exception/InvalidArgumentException.php b/vendor/symfony/translation/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..90d0669 --- /dev/null +++ b/vendor/symfony/translation/Exception/InvalidArgumentException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Exception; + +/** + * Base InvalidArgumentException for the Translation component. + * + * @author Abdellatif Ait boudad + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/translation/Exception/InvalidResourceException.php b/vendor/symfony/translation/Exception/InvalidResourceException.php new file mode 100644 index 0000000..cf07943 --- /dev/null +++ b/vendor/symfony/translation/Exception/InvalidResourceException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Exception; + +/** + * Thrown when a resource cannot be loaded. + * + * @author Fabien Potencier + */ +class InvalidResourceException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/translation/Exception/LogicException.php b/vendor/symfony/translation/Exception/LogicException.php new file mode 100644 index 0000000..9019c7e --- /dev/null +++ b/vendor/symfony/translation/Exception/LogicException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Exception; + +/** + * Base LogicException for Translation component. + * + * @author Abdellatif Ait boudad + */ +class LogicException extends \LogicException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/translation/Exception/MissingRequiredOptionException.php b/vendor/symfony/translation/Exception/MissingRequiredOptionException.php new file mode 100644 index 0000000..2b5f808 --- /dev/null +++ b/vendor/symfony/translation/Exception/MissingRequiredOptionException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Exception; + +/** + * @author Oskar Stark + */ +class MissingRequiredOptionException extends IncompleteDsnException +{ + public function __construct(string $option, string $dsn = null, \Throwable $previous = null) + { + $message = sprintf('The option "%s" is required but missing.', $option); + + parent::__construct($message, $dsn, $previous); + } +} diff --git a/vendor/symfony/translation/Exception/NotFoundResourceException.php b/vendor/symfony/translation/Exception/NotFoundResourceException.php new file mode 100644 index 0000000..cff73ae --- /dev/null +++ b/vendor/symfony/translation/Exception/NotFoundResourceException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Exception; + +/** + * Thrown when a resource does not exist. + * + * @author Fabien Potencier + */ +class NotFoundResourceException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/translation/Exception/ProviderException.php b/vendor/symfony/translation/Exception/ProviderException.php new file mode 100644 index 0000000..571920d --- /dev/null +++ b/vendor/symfony/translation/Exception/ProviderException.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Exception; + +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * @author Fabien Potencier + */ +class ProviderException extends RuntimeException implements ProviderExceptionInterface +{ + private $response; + private $debug; + + public function __construct(string $message, ResponseInterface $response, int $code = 0, \Exception $previous = null) + { + $this->response = $response; + $this->debug = $response->getInfo('debug') ?? ''; + + parent::__construct($message, $code, $previous); + } + + public function getResponse(): ResponseInterface + { + return $this->response; + } + + public function getDebug(): string + { + return $this->debug; + } +} diff --git a/vendor/symfony/translation/Exception/ProviderExceptionInterface.php b/vendor/symfony/translation/Exception/ProviderExceptionInterface.php new file mode 100644 index 0000000..922e827 --- /dev/null +++ b/vendor/symfony/translation/Exception/ProviderExceptionInterface.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Exception; + +/** + * @author Fabien Potencier + */ +interface ProviderExceptionInterface extends ExceptionInterface +{ + /* + * Returns debug info coming from the Symfony\Contracts\HttpClient\ResponseInterface + */ + public function getDebug(): string; +} diff --git a/vendor/symfony/translation/Exception/RuntimeException.php b/vendor/symfony/translation/Exception/RuntimeException.php new file mode 100644 index 0000000..dcd7940 --- /dev/null +++ b/vendor/symfony/translation/Exception/RuntimeException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Exception; + +/** + * Base RuntimeException for the Translation component. + * + * @author Abdellatif Ait boudad + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/translation/Exception/UnsupportedSchemeException.php b/vendor/symfony/translation/Exception/UnsupportedSchemeException.php new file mode 100644 index 0000000..7fbaa8f --- /dev/null +++ b/vendor/symfony/translation/Exception/UnsupportedSchemeException.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Exception; + +use Symfony\Component\Translation\Bridge; +use Symfony\Component\Translation\Provider\Dsn; + +class UnsupportedSchemeException extends LogicException +{ + private const SCHEME_TO_PACKAGE_MAP = [ + 'crowdin' => [ + 'class' => Bridge\Crowdin\CrowdinProviderFactory::class, + 'package' => 'symfony/crowdin-translation-provider', + ], + 'loco' => [ + 'class' => Bridge\Loco\LocoProviderFactory::class, + 'package' => 'symfony/loco-translation-provider', + ], + 'lokalise' => [ + 'class' => Bridge\Lokalise\LokaliseProviderFactory::class, + 'package' => 'symfony/lokalise-translation-provider', + ], + ]; + + public function __construct(Dsn $dsn, string $name = null, array $supported = []) + { + $provider = $dsn->getScheme(); + if (false !== $pos = strpos($provider, '+')) { + $provider = substr($provider, 0, $pos); + } + $package = self::SCHEME_TO_PACKAGE_MAP[$provider] ?? null; + if ($package && !class_exists($package['class'])) { + parent::__construct(sprintf('Unable to synchronize translations via "%s" as the provider is not installed; try running "composer require %s".', $provider, $package['package'])); + + return; + } + + $message = sprintf('The "%s" scheme is not supported', $dsn->getScheme()); + if ($name && $supported) { + $message .= sprintf('; supported schemes for translation provider "%s" are: "%s"', $name, implode('", "', $supported)); + } + + parent::__construct($message.'.'); + } +} diff --git a/vendor/symfony/translation/Extractor/AbstractFileExtractor.php b/vendor/symfony/translation/Extractor/AbstractFileExtractor.php new file mode 100644 index 0000000..3bc8783 --- /dev/null +++ b/vendor/symfony/translation/Extractor/AbstractFileExtractor.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Extractor; + +use Symfony\Component\Translation\Exception\InvalidArgumentException; + +/** + * Base class used by classes that extract translation messages from files. + * + * @author Marcos D. Sánchez + */ +abstract class AbstractFileExtractor +{ + /** + * @param string|iterable $resource Files, a file or a directory + * + * @return iterable + */ + protected function extractFiles($resource) + { + if (is_iterable($resource)) { + $files = []; + foreach ($resource as $file) { + if ($this->canBeExtracted($file)) { + $files[] = $this->toSplFileInfo($file); + } + } + } elseif (is_file($resource)) { + $files = $this->canBeExtracted($resource) ? [$this->toSplFileInfo($resource)] : []; + } else { + $files = $this->extractFromDirectory($resource); + } + + return $files; + } + + private function toSplFileInfo(string $file): \SplFileInfo + { + return new \SplFileInfo($file); + } + + /** + * @return bool + * + * @throws InvalidArgumentException + */ + protected function isFile(string $file) + { + if (!is_file($file)) { + throw new InvalidArgumentException(sprintf('The "%s" file does not exist.', $file)); + } + + return true; + } + + /** + * @return bool + */ + abstract protected function canBeExtracted(string $file); + + /** + * @param string|array $resource Files, a file or a directory + * + * @return iterable + */ + abstract protected function extractFromDirectory($resource); +} diff --git a/vendor/symfony/translation/Extractor/ChainExtractor.php b/vendor/symfony/translation/Extractor/ChainExtractor.php new file mode 100644 index 0000000..95dcf15 --- /dev/null +++ b/vendor/symfony/translation/Extractor/ChainExtractor.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Extractor; + +use Symfony\Component\Translation\MessageCatalogue; + +/** + * ChainExtractor extracts translation messages from template files. + * + * @author Michel Salib + */ +class ChainExtractor implements ExtractorInterface +{ + /** + * The extractors. + * + * @var ExtractorInterface[] + */ + private $extractors = []; + + /** + * Adds a loader to the translation extractor. + */ + public function addExtractor(string $format, ExtractorInterface $extractor) + { + $this->extractors[$format] = $extractor; + } + + /** + * {@inheritdoc} + */ + public function setPrefix(string $prefix) + { + foreach ($this->extractors as $extractor) { + $extractor->setPrefix($prefix); + } + } + + /** + * {@inheritdoc} + */ + public function extract($directory, MessageCatalogue $catalogue) + { + foreach ($this->extractors as $extractor) { + $extractor->extract($directory, $catalogue); + } + } +} diff --git a/vendor/symfony/translation/Extractor/ExtractorInterface.php b/vendor/symfony/translation/Extractor/ExtractorInterface.php new file mode 100644 index 0000000..e1db8a9 --- /dev/null +++ b/vendor/symfony/translation/Extractor/ExtractorInterface.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Extractor; + +use Symfony\Component\Translation\MessageCatalogue; + +/** + * Extracts translation messages from a directory or files to the catalogue. + * New found messages are injected to the catalogue using the prefix. + * + * @author Michel Salib + */ +interface ExtractorInterface +{ + /** + * Extracts translation messages from files, a file or a directory to the catalogue. + * + * @param string|iterable $resource Files, a file or a directory + */ + public function extract($resource, MessageCatalogue $catalogue); + + /** + * Sets the prefix that should be used for new found messages. + */ + public function setPrefix(string $prefix); +} diff --git a/vendor/symfony/translation/Extractor/PhpExtractor.php b/vendor/symfony/translation/Extractor/PhpExtractor.php new file mode 100644 index 0000000..38c08d5 --- /dev/null +++ b/vendor/symfony/translation/Extractor/PhpExtractor.php @@ -0,0 +1,336 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Extractor; + +use Symfony\Component\Finder\Finder; +use Symfony\Component\Translation\MessageCatalogue; + +/** + * PhpExtractor extracts translation messages from a PHP template. + * + * @author Michel Salib + */ +class PhpExtractor extends AbstractFileExtractor implements ExtractorInterface +{ + public const MESSAGE_TOKEN = 300; + public const METHOD_ARGUMENTS_TOKEN = 1000; + public const DOMAIN_TOKEN = 1001; + + /** + * Prefix for new found message. + */ + private $prefix = ''; + + /** + * The sequence that captures translation messages. + */ + protected $sequences = [ + [ + '->', + 'trans', + '(', + self::MESSAGE_TOKEN, + ',', + self::METHOD_ARGUMENTS_TOKEN, + ',', + self::DOMAIN_TOKEN, + ], + [ + '->', + 'trans', + '(', + self::MESSAGE_TOKEN, + ], + [ + 'new', + 'TranslatableMessage', + '(', + self::MESSAGE_TOKEN, + ',', + self::METHOD_ARGUMENTS_TOKEN, + ',', + self::DOMAIN_TOKEN, + ], + [ + 'new', + 'TranslatableMessage', + '(', + self::MESSAGE_TOKEN, + ], + [ + 'new', + '\\', + 'Symfony', + '\\', + 'Component', + '\\', + 'Translation', + '\\', + 'TranslatableMessage', + '(', + self::MESSAGE_TOKEN, + ',', + self::METHOD_ARGUMENTS_TOKEN, + ',', + self::DOMAIN_TOKEN, + ], + [ + 'new', + '\Symfony\Component\Translation\TranslatableMessage', + '(', + self::MESSAGE_TOKEN, + ',', + self::METHOD_ARGUMENTS_TOKEN, + ',', + self::DOMAIN_TOKEN, + ], + [ + 'new', + '\\', + 'Symfony', + '\\', + 'Component', + '\\', + 'Translation', + '\\', + 'TranslatableMessage', + '(', + self::MESSAGE_TOKEN, + ], + [ + 'new', + '\Symfony\Component\Translation\TranslatableMessage', + '(', + self::MESSAGE_TOKEN, + ], + [ + 't', + '(', + self::MESSAGE_TOKEN, + ',', + self::METHOD_ARGUMENTS_TOKEN, + ',', + self::DOMAIN_TOKEN, + ], + [ + 't', + '(', + self::MESSAGE_TOKEN, + ], + ]; + + /** + * {@inheritdoc} + */ + public function extract($resource, MessageCatalogue $catalog) + { + $files = $this->extractFiles($resource); + foreach ($files as $file) { + $this->parseTokens(token_get_all(file_get_contents($file)), $catalog, $file); + + gc_mem_caches(); + } + } + + /** + * {@inheritdoc} + */ + public function setPrefix(string $prefix) + { + $this->prefix = $prefix; + } + + /** + * Normalizes a token. + * + * @param mixed $token + * + * @return string|null + */ + protected function normalizeToken($token) + { + if (isset($token[1]) && 'b"' !== $token) { + return $token[1]; + } + + return $token; + } + + /** + * Seeks to a non-whitespace token. + */ + private function seekToNextRelevantToken(\Iterator $tokenIterator) + { + for (; $tokenIterator->valid(); $tokenIterator->next()) { + $t = $tokenIterator->current(); + if (\T_WHITESPACE !== $t[0]) { + break; + } + } + } + + private function skipMethodArgument(\Iterator $tokenIterator) + { + $openBraces = 0; + + for (; $tokenIterator->valid(); $tokenIterator->next()) { + $t = $tokenIterator->current(); + + if ('[' === $t[0] || '(' === $t[0]) { + ++$openBraces; + } + + if (']' === $t[0] || ')' === $t[0]) { + --$openBraces; + } + + if ((0 === $openBraces && ',' === $t[0]) || (-1 === $openBraces && ')' === $t[0])) { + break; + } + } + } + + /** + * Extracts the message from the iterator while the tokens + * match allowed message tokens. + */ + private function getValue(\Iterator $tokenIterator) + { + $message = ''; + $docToken = ''; + $docPart = ''; + + for (; $tokenIterator->valid(); $tokenIterator->next()) { + $t = $tokenIterator->current(); + if ('.' === $t) { + // Concatenate with next token + continue; + } + if (!isset($t[1])) { + break; + } + + switch ($t[0]) { + case \T_START_HEREDOC: + $docToken = $t[1]; + break; + case \T_ENCAPSED_AND_WHITESPACE: + case \T_CONSTANT_ENCAPSED_STRING: + if ('' === $docToken) { + $message .= PhpStringTokenParser::parse($t[1]); + } else { + $docPart = $t[1]; + } + break; + case \T_END_HEREDOC: + if ($indentation = strspn($t[1], ' ')) { + $docPartWithLineBreaks = $docPart; + $docPart = ''; + + foreach (preg_split('~(\r\n|\n|\r)~', $docPartWithLineBreaks, -1, \PREG_SPLIT_DELIM_CAPTURE) as $str) { + if (\in_array($str, ["\r\n", "\n", "\r"], true)) { + $docPart .= $str; + } else { + $docPart .= substr($str, $indentation); + } + } + } + + $message .= PhpStringTokenParser::parseDocString($docToken, $docPart); + $docToken = ''; + $docPart = ''; + break; + case \T_WHITESPACE: + break; + default: + break 2; + } + } + + return $message; + } + + /** + * Extracts trans message from PHP tokens. + */ + protected function parseTokens(array $tokens, MessageCatalogue $catalog, string $filename) + { + $tokenIterator = new \ArrayIterator($tokens); + + for ($key = 0; $key < $tokenIterator->count(); ++$key) { + foreach ($this->sequences as $sequence) { + $message = ''; + $domain = 'messages'; + $tokenIterator->seek($key); + + foreach ($sequence as $sequenceKey => $item) { + $this->seekToNextRelevantToken($tokenIterator); + + if ($this->normalizeToken($tokenIterator->current()) === $item) { + $tokenIterator->next(); + continue; + } elseif (self::MESSAGE_TOKEN === $item) { + $message = $this->getValue($tokenIterator); + + if (\count($sequence) === ($sequenceKey + 1)) { + break; + } + } elseif (self::METHOD_ARGUMENTS_TOKEN === $item) { + $this->skipMethodArgument($tokenIterator); + } elseif (self::DOMAIN_TOKEN === $item) { + $domainToken = $this->getValue($tokenIterator); + if ('' !== $domainToken) { + $domain = $domainToken; + } + + break; + } else { + break; + } + } + + if ($message) { + $catalog->set($message, $this->prefix.$message, $domain); + $metadata = $catalog->getMetadata($message, $domain) ?? []; + $normalizedFilename = preg_replace('{[\\\\/]+}', '/', $filename); + $metadata['sources'][] = $normalizedFilename.':'.$tokens[$key][2]; + $catalog->setMetadata($message, $metadata, $domain); + break; + } + } + } + } + + /** + * @return bool + * + * @throws \InvalidArgumentException + */ + protected function canBeExtracted(string $file) + { + return $this->isFile($file) && 'php' === pathinfo($file, \PATHINFO_EXTENSION); + } + + /** + * {@inheritdoc} + */ + protected function extractFromDirectory($directory) + { + if (!class_exists(Finder::class)) { + throw new \LogicException(sprintf('You cannot use "%s" as the "symfony/finder" package is not installed. Try running "composer require symfony/finder".', static::class)); + } + + $finder = new Finder(); + + return $finder->files()->name('*.php')->in($directory); + } +} diff --git a/vendor/symfony/translation/Extractor/PhpStringTokenParser.php b/vendor/symfony/translation/Extractor/PhpStringTokenParser.php new file mode 100644 index 0000000..d114cc7 --- /dev/null +++ b/vendor/symfony/translation/Extractor/PhpStringTokenParser.php @@ -0,0 +1,142 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Extractor; + +/* + * The following is derived from code at http://github.com/nikic/PHP-Parser + * + * Copyright (c) 2011 by Nikita Popov + * + * Some rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * * The names of the contributors may not be used to endorse or + * promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +class PhpStringTokenParser +{ + protected static $replacements = [ + '\\' => '\\', + '$' => '$', + 'n' => "\n", + 'r' => "\r", + 't' => "\t", + 'f' => "\f", + 'v' => "\v", + 'e' => "\x1B", + ]; + + /** + * Parses a string token. + * + * @param string $str String token content + * + * @return string + */ + public static function parse(string $str) + { + $bLength = 0; + if ('b' === $str[0]) { + $bLength = 1; + } + + if ('\'' === $str[$bLength]) { + return str_replace( + ['\\\\', '\\\''], + ['\\', '\''], + substr($str, $bLength + 1, -1) + ); + } else { + return self::parseEscapeSequences(substr($str, $bLength + 1, -1), '"'); + } + } + + /** + * Parses escape sequences in strings (all string types apart from single quoted). + * + * @param string $str String without quotes + * @param string|null $quote Quote type + * + * @return string + */ + public static function parseEscapeSequences(string $str, string $quote = null) + { + if (null !== $quote) { + $str = str_replace('\\'.$quote, $quote, $str); + } + + return preg_replace_callback( + '~\\\\([\\\\$nrtfve]|[xX][0-9a-fA-F]{1,2}|[0-7]{1,3})~', + [__CLASS__, 'parseCallback'], + $str + ); + } + + private static function parseCallback(array $matches): string + { + $str = $matches[1]; + + if (isset(self::$replacements[$str])) { + return self::$replacements[$str]; + } elseif ('x' === $str[0] || 'X' === $str[0]) { + return \chr(hexdec($str)); + } else { + return \chr(octdec($str)); + } + } + + /** + * Parses a constant doc string. + * + * @param string $startToken Doc string start token content (<< + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Formatter; + +use Symfony\Component\Translation\Exception\InvalidArgumentException; +use Symfony\Component\Translation\Exception\LogicException; + +/** + * @author Guilherme Blanco + * @author Abdellatif Ait boudad + */ +class IntlFormatter implements IntlFormatterInterface +{ + private $hasMessageFormatter; + private $cache = []; + + /** + * {@inheritdoc} + */ + public function formatIntl(string $message, string $locale, array $parameters = []): string + { + // MessageFormatter constructor throws an exception if the message is empty + if ('' === $message) { + return ''; + } + + if (!$formatter = $this->cache[$locale][$message] ?? null) { + if (!($this->hasMessageFormatter ?? $this->hasMessageFormatter = class_exists(\MessageFormatter::class))) { + throw new LogicException('Cannot parse message translation: please install the "intl" PHP extension or the "symfony/polyfill-intl-messageformatter" package.'); + } + try { + $this->cache[$locale][$message] = $formatter = new \MessageFormatter($locale, $message); + } catch (\IntlException $e) { + throw new InvalidArgumentException(sprintf('Invalid message format (error #%d): ', intl_get_error_code()).intl_get_error_message(), 0, $e); + } + } + + foreach ($parameters as $key => $value) { + if (\in_array($key[0] ?? null, ['%', '{'], true)) { + unset($parameters[$key]); + $parameters[trim($key, '%{ }')] = $value; + } + } + + if (false === $message = $formatter->format($parameters)) { + throw new InvalidArgumentException(sprintf('Unable to format message (error #%s): ', $formatter->getErrorCode()).$formatter->getErrorMessage()); + } + + return $message; + } +} diff --git a/vendor/symfony/translation/Formatter/IntlFormatterInterface.php b/vendor/symfony/translation/Formatter/IntlFormatterInterface.php new file mode 100644 index 0000000..02fc6ac --- /dev/null +++ b/vendor/symfony/translation/Formatter/IntlFormatterInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Formatter; + +/** + * Formats ICU message patterns. + * + * @author Nicolas Grekas + */ +interface IntlFormatterInterface +{ + /** + * Formats a localized message using rules defined by ICU MessageFormat. + * + * @see http://icu-project.org/apiref/icu4c/classMessageFormat.html#details + */ + public function formatIntl(string $message, string $locale, array $parameters = []): string; +} diff --git a/vendor/symfony/translation/Formatter/MessageFormatter.php b/vendor/symfony/translation/Formatter/MessageFormatter.php new file mode 100644 index 0000000..0407964 --- /dev/null +++ b/vendor/symfony/translation/Formatter/MessageFormatter.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Formatter; + +use Symfony\Component\Translation\IdentityTranslator; +use Symfony\Contracts\Translation\TranslatorInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(IntlFormatter::class); + +/** + * @author Abdellatif Ait boudad + */ +class MessageFormatter implements MessageFormatterInterface, IntlFormatterInterface +{ + private $translator; + private $intlFormatter; + + /** + * @param TranslatorInterface|null $translator An identity translator to use as selector for pluralization + */ + public function __construct(TranslatorInterface $translator = null, IntlFormatterInterface $intlFormatter = null) + { + $this->translator = $translator ?? new IdentityTranslator(); + $this->intlFormatter = $intlFormatter ?? new IntlFormatter(); + } + + /** + * {@inheritdoc} + */ + public function format(string $message, string $locale, array $parameters = []) + { + if ($this->translator instanceof TranslatorInterface) { + return $this->translator->trans($message, $parameters, null, $locale); + } + + return strtr($message, $parameters); + } + + /** + * {@inheritdoc} + */ + public function formatIntl(string $message, string $locale, array $parameters = []): string + { + return $this->intlFormatter->formatIntl($message, $locale, $parameters); + } +} diff --git a/vendor/symfony/translation/Formatter/MessageFormatterInterface.php b/vendor/symfony/translation/Formatter/MessageFormatterInterface.php new file mode 100644 index 0000000..b85dbfd --- /dev/null +++ b/vendor/symfony/translation/Formatter/MessageFormatterInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Formatter; + +/** + * @author Guilherme Blanco + * @author Abdellatif Ait boudad + */ +interface MessageFormatterInterface +{ + /** + * Formats a localized message pattern with given arguments. + * + * @param string $message The message (may also be an object that can be cast to string) + * @param string $locale The message locale + * @param array $parameters An array of parameters for the message + * + * @return string + */ + public function format(string $message, string $locale, array $parameters = []); +} diff --git a/vendor/symfony/translation/IdentityTranslator.php b/vendor/symfony/translation/IdentityTranslator.php new file mode 100644 index 0000000..46875ed --- /dev/null +++ b/vendor/symfony/translation/IdentityTranslator.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation; + +use Symfony\Contracts\Translation\LocaleAwareInterface; +use Symfony\Contracts\Translation\TranslatorInterface; +use Symfony\Contracts\Translation\TranslatorTrait; + +/** + * IdentityTranslator does not translate anything. + * + * @author Fabien Potencier + */ +class IdentityTranslator implements TranslatorInterface, LocaleAwareInterface +{ + use TranslatorTrait; +} diff --git a/vendor/symfony/translation/LICENSE b/vendor/symfony/translation/LICENSE new file mode 100644 index 0000000..9ff2d0d --- /dev/null +++ b/vendor/symfony/translation/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2021 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/translation/Loader/ArrayLoader.php b/vendor/symfony/translation/Loader/ArrayLoader.php new file mode 100644 index 0000000..0758da8 --- /dev/null +++ b/vendor/symfony/translation/Loader/ArrayLoader.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Loader; + +use Symfony\Component\Translation\MessageCatalogue; + +/** + * ArrayLoader loads translations from a PHP array. + * + * @author Fabien Potencier + */ +class ArrayLoader implements LoaderInterface +{ + /** + * {@inheritdoc} + */ + public function load($resource, string $locale, string $domain = 'messages') + { + $resource = $this->flatten($resource); + $catalogue = new MessageCatalogue($locale); + $catalogue->add($resource, $domain); + + return $catalogue; + } + + /** + * Flattens an nested array of translations. + * + * The scheme used is: + * 'key' => ['key2' => ['key3' => 'value']] + * Becomes: + * 'key.key2.key3' => 'value' + */ + private function flatten(array $messages): array + { + $result = []; + foreach ($messages as $key => $value) { + if (\is_array($value)) { + foreach ($this->flatten($value) as $k => $v) { + $result[$key.'.'.$k] = $v; + } + } else { + $result[$key] = $value; + } + } + + return $result; + } +} diff --git a/vendor/symfony/translation/Loader/CsvFileLoader.php b/vendor/symfony/translation/Loader/CsvFileLoader.php new file mode 100644 index 0000000..8d5d4db --- /dev/null +++ b/vendor/symfony/translation/Loader/CsvFileLoader.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Loader; + +use Symfony\Component\Translation\Exception\NotFoundResourceException; + +/** + * CsvFileLoader loads translations from CSV files. + * + * @author Saša Stamenković + */ +class CsvFileLoader extends FileLoader +{ + private $delimiter = ';'; + private $enclosure = '"'; + private $escape = '\\'; + + /** + * {@inheritdoc} + */ + protected function loadResource(string $resource) + { + $messages = []; + + try { + $file = new \SplFileObject($resource, 'rb'); + } catch (\RuntimeException $e) { + throw new NotFoundResourceException(sprintf('Error opening file "%s".', $resource), 0, $e); + } + + $file->setFlags(\SplFileObject::READ_CSV | \SplFileObject::SKIP_EMPTY); + $file->setCsvControl($this->delimiter, $this->enclosure, $this->escape); + + foreach ($file as $data) { + if (false === $data) { + continue; + } + + if ('#' !== substr($data[0], 0, 1) && isset($data[1]) && 2 === \count($data)) { + $messages[$data[0]] = $data[1]; + } + } + + return $messages; + } + + /** + * Sets the delimiter, enclosure, and escape character for CSV. + */ + public function setCsvControl(string $delimiter = ';', string $enclosure = '"', string $escape = '\\') + { + $this->delimiter = $delimiter; + $this->enclosure = $enclosure; + $this->escape = $escape; + } +} diff --git a/vendor/symfony/translation/Loader/FileLoader.php b/vendor/symfony/translation/Loader/FileLoader.php new file mode 100644 index 0000000..4725ea6 --- /dev/null +++ b/vendor/symfony/translation/Loader/FileLoader.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Loader; + +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Translation\Exception\InvalidResourceException; +use Symfony\Component\Translation\Exception\NotFoundResourceException; + +/** + * @author Abdellatif Ait boudad + */ +abstract class FileLoader extends ArrayLoader +{ + /** + * {@inheritdoc} + */ + public function load($resource, string $locale, string $domain = 'messages') + { + if (!stream_is_local($resource)) { + throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource)); + } + + if (!file_exists($resource)) { + throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource)); + } + + $messages = $this->loadResource($resource); + + // empty resource + if (null === $messages) { + $messages = []; + } + + // not an array + if (!\is_array($messages)) { + throw new InvalidResourceException(sprintf('Unable to load file "%s".', $resource)); + } + + $catalogue = parent::load($messages, $locale, $domain); + + if (class_exists(FileResource::class)) { + $catalogue->addResource(new FileResource($resource)); + } + + return $catalogue; + } + + /** + * @return array + * + * @throws InvalidResourceException if stream content has an invalid format + */ + abstract protected function loadResource(string $resource); +} diff --git a/vendor/symfony/translation/Loader/IcuDatFileLoader.php b/vendor/symfony/translation/Loader/IcuDatFileLoader.php new file mode 100644 index 0000000..2a1aecc --- /dev/null +++ b/vendor/symfony/translation/Loader/IcuDatFileLoader.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Loader; + +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Translation\Exception\InvalidResourceException; +use Symfony\Component\Translation\Exception\NotFoundResourceException; +use Symfony\Component\Translation\MessageCatalogue; + +/** + * IcuResFileLoader loads translations from a resource bundle. + * + * @author stealth35 + */ +class IcuDatFileLoader extends IcuResFileLoader +{ + /** + * {@inheritdoc} + */ + public function load($resource, string $locale, string $domain = 'messages') + { + if (!stream_is_local($resource.'.dat')) { + throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource)); + } + + if (!file_exists($resource.'.dat')) { + throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource)); + } + + try { + $rb = new \ResourceBundle($locale, $resource); + } catch (\Exception $e) { + $rb = null; + } + + if (!$rb) { + throw new InvalidResourceException(sprintf('Cannot load resource "%s".', $resource)); + } elseif (intl_is_failure($rb->getErrorCode())) { + throw new InvalidResourceException($rb->getErrorMessage(), $rb->getErrorCode()); + } + + $messages = $this->flatten($rb); + $catalogue = new MessageCatalogue($locale); + $catalogue->add($messages, $domain); + + if (class_exists(FileResource::class)) { + $catalogue->addResource(new FileResource($resource.'.dat')); + } + + return $catalogue; + } +} diff --git a/vendor/symfony/translation/Loader/IcuResFileLoader.php b/vendor/symfony/translation/Loader/IcuResFileLoader.php new file mode 100644 index 0000000..7e3391e --- /dev/null +++ b/vendor/symfony/translation/Loader/IcuResFileLoader.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Loader; + +use Symfony\Component\Config\Resource\DirectoryResource; +use Symfony\Component\Translation\Exception\InvalidResourceException; +use Symfony\Component\Translation\Exception\NotFoundResourceException; +use Symfony\Component\Translation\MessageCatalogue; + +/** + * IcuResFileLoader loads translations from a resource bundle. + * + * @author stealth35 + */ +class IcuResFileLoader implements LoaderInterface +{ + /** + * {@inheritdoc} + */ + public function load($resource, string $locale, string $domain = 'messages') + { + if (!stream_is_local($resource)) { + throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource)); + } + + if (!is_dir($resource)) { + throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource)); + } + + try { + $rb = new \ResourceBundle($locale, $resource); + } catch (\Exception $e) { + $rb = null; + } + + if (!$rb) { + throw new InvalidResourceException(sprintf('Cannot load resource "%s".', $resource)); + } elseif (intl_is_failure($rb->getErrorCode())) { + throw new InvalidResourceException($rb->getErrorMessage(), $rb->getErrorCode()); + } + + $messages = $this->flatten($rb); + $catalogue = new MessageCatalogue($locale); + $catalogue->add($messages, $domain); + + if (class_exists(DirectoryResource::class)) { + $catalogue->addResource(new DirectoryResource($resource)); + } + + return $catalogue; + } + + /** + * Flattens an ResourceBundle. + * + * The scheme used is: + * key { key2 { key3 { "value" } } } + * Becomes: + * 'key.key2.key3' => 'value' + * + * This function takes an array by reference and will modify it + * + * @param \ResourceBundle $rb The ResourceBundle that will be flattened + * @param array $messages Used internally for recursive calls + * @param string $path Current path being parsed, used internally for recursive calls + * + * @return array + */ + protected function flatten(\ResourceBundle $rb, array &$messages = [], string $path = null) + { + foreach ($rb as $key => $value) { + $nodePath = $path ? $path.'.'.$key : $key; + if ($value instanceof \ResourceBundle) { + $this->flatten($value, $messages, $nodePath); + } else { + $messages[$nodePath] = $value; + } + } + + return $messages; + } +} diff --git a/vendor/symfony/translation/Loader/IniFileLoader.php b/vendor/symfony/translation/Loader/IniFileLoader.php new file mode 100644 index 0000000..7398f77 --- /dev/null +++ b/vendor/symfony/translation/Loader/IniFileLoader.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Loader; + +/** + * IniFileLoader loads translations from an ini file. + * + * @author stealth35 + */ +class IniFileLoader extends FileLoader +{ + /** + * {@inheritdoc} + */ + protected function loadResource(string $resource) + { + return parse_ini_file($resource, true); + } +} diff --git a/vendor/symfony/translation/Loader/JsonFileLoader.php b/vendor/symfony/translation/Loader/JsonFileLoader.php new file mode 100644 index 0000000..5aefba0 --- /dev/null +++ b/vendor/symfony/translation/Loader/JsonFileLoader.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Loader; + +use Symfony\Component\Translation\Exception\InvalidResourceException; + +/** + * JsonFileLoader loads translations from an json file. + * + * @author singles + */ +class JsonFileLoader extends FileLoader +{ + /** + * {@inheritdoc} + */ + protected function loadResource(string $resource) + { + $messages = []; + if ($data = file_get_contents($resource)) { + $messages = json_decode($data, true); + + if (0 < $errorCode = json_last_error()) { + throw new InvalidResourceException('Error parsing JSON: '.$this->getJSONErrorMessage($errorCode)); + } + } + + return $messages; + } + + /** + * Translates JSON_ERROR_* constant into meaningful message. + */ + private function getJSONErrorMessage(int $errorCode): string + { + switch ($errorCode) { + case \JSON_ERROR_DEPTH: + return 'Maximum stack depth exceeded'; + case \JSON_ERROR_STATE_MISMATCH: + return 'Underflow or the modes mismatch'; + case \JSON_ERROR_CTRL_CHAR: + return 'Unexpected control character found'; + case \JSON_ERROR_SYNTAX: + return 'Syntax error, malformed JSON'; + case \JSON_ERROR_UTF8: + return 'Malformed UTF-8 characters, possibly incorrectly encoded'; + default: + return 'Unknown error'; + } + } +} diff --git a/vendor/symfony/translation/Loader/LoaderInterface.php b/vendor/symfony/translation/Loader/LoaderInterface.php new file mode 100644 index 0000000..96b0c0d --- /dev/null +++ b/vendor/symfony/translation/Loader/LoaderInterface.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Loader; + +use Symfony\Component\Translation\Exception\InvalidResourceException; +use Symfony\Component\Translation\Exception\NotFoundResourceException; +use Symfony\Component\Translation\MessageCatalogue; + +/** + * LoaderInterface is the interface implemented by all translation loaders. + * + * @author Fabien Potencier + */ +interface LoaderInterface +{ + /** + * Loads a locale. + * + * @param mixed $resource A resource + * @param string $locale A locale + * @param string $domain The domain + * + * @return MessageCatalogue + * + * @throws NotFoundResourceException when the resource cannot be found + * @throws InvalidResourceException when the resource cannot be loaded + */ + public function load($resource, string $locale, string $domain = 'messages'); +} diff --git a/vendor/symfony/translation/Loader/MoFileLoader.php b/vendor/symfony/translation/Loader/MoFileLoader.php new file mode 100644 index 0000000..7fa2f2d --- /dev/null +++ b/vendor/symfony/translation/Loader/MoFileLoader.php @@ -0,0 +1,140 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Loader; + +use Symfony\Component\Translation\Exception\InvalidResourceException; + +/** + * @copyright Copyright (c) 2010, Union of RAD http://union-of-rad.org (http://lithify.me/) + */ +class MoFileLoader extends FileLoader +{ + /** + * Magic used for validating the format of an MO file as well as + * detecting if the machine used to create that file was little endian. + */ + public const MO_LITTLE_ENDIAN_MAGIC = 0x950412DE; + + /** + * Magic used for validating the format of an MO file as well as + * detecting if the machine used to create that file was big endian. + */ + public const MO_BIG_ENDIAN_MAGIC = 0xDE120495; + + /** + * The size of the header of an MO file in bytes. + */ + public const MO_HEADER_SIZE = 28; + + /** + * Parses machine object (MO) format, independent of the machine's endian it + * was created on. Both 32bit and 64bit systems are supported. + * + * {@inheritdoc} + */ + protected function loadResource(string $resource) + { + $stream = fopen($resource, 'r'); + + $stat = fstat($stream); + + if ($stat['size'] < self::MO_HEADER_SIZE) { + throw new InvalidResourceException('MO stream content has an invalid format.'); + } + $magic = unpack('V1', fread($stream, 4)); + $magic = hexdec(substr(dechex(current($magic)), -8)); + + if (self::MO_LITTLE_ENDIAN_MAGIC == $magic) { + $isBigEndian = false; + } elseif (self::MO_BIG_ENDIAN_MAGIC == $magic) { + $isBigEndian = true; + } else { + throw new InvalidResourceException('MO stream content has an invalid format.'); + } + + // formatRevision + $this->readLong($stream, $isBigEndian); + $count = $this->readLong($stream, $isBigEndian); + $offsetId = $this->readLong($stream, $isBigEndian); + $offsetTranslated = $this->readLong($stream, $isBigEndian); + // sizeHashes + $this->readLong($stream, $isBigEndian); + // offsetHashes + $this->readLong($stream, $isBigEndian); + + $messages = []; + + for ($i = 0; $i < $count; ++$i) { + $pluralId = null; + $translated = null; + + fseek($stream, $offsetId + $i * 8); + + $length = $this->readLong($stream, $isBigEndian); + $offset = $this->readLong($stream, $isBigEndian); + + if ($length < 1) { + continue; + } + + fseek($stream, $offset); + $singularId = fread($stream, $length); + + if (str_contains($singularId, "\000")) { + [$singularId, $pluralId] = explode("\000", $singularId); + } + + fseek($stream, $offsetTranslated + $i * 8); + $length = $this->readLong($stream, $isBigEndian); + $offset = $this->readLong($stream, $isBigEndian); + + if ($length < 1) { + continue; + } + + fseek($stream, $offset); + $translated = fread($stream, $length); + + if (str_contains($translated, "\000")) { + $translated = explode("\000", $translated); + } + + $ids = ['singular' => $singularId, 'plural' => $pluralId]; + $item = compact('ids', 'translated'); + + if (!empty($item['ids']['singular'])) { + $id = $item['ids']['singular']; + if (isset($item['ids']['plural'])) { + $id .= '|'.$item['ids']['plural']; + } + $messages[$id] = stripcslashes(implode('|', (array) $item['translated'])); + } + } + + fclose($stream); + + return array_filter($messages); + } + + /** + * Reads an unsigned long from stream respecting endianness. + * + * @param resource $stream + */ + private function readLong($stream, bool $isBigEndian): int + { + $result = unpack($isBigEndian ? 'N1' : 'V1', fread($stream, 4)); + $result = current($result); + + return (int) substr($result, -8); + } +} diff --git a/vendor/symfony/translation/Loader/PhpFileLoader.php b/vendor/symfony/translation/Loader/PhpFileLoader.php new file mode 100644 index 0000000..85f1090 --- /dev/null +++ b/vendor/symfony/translation/Loader/PhpFileLoader.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Loader; + +/** + * PhpFileLoader loads translations from PHP files returning an array of translations. + * + * @author Fabien Potencier + */ +class PhpFileLoader extends FileLoader +{ + private static $cache = []; + + /** + * {@inheritdoc} + */ + protected function loadResource(string $resource) + { + if ([] === self::$cache && \function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN) && (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) || filter_var(ini_get('opcache.enable_cli'), \FILTER_VALIDATE_BOOLEAN))) { + self::$cache = null; + } + + if (null === self::$cache) { + return require $resource; + } + + if (isset(self::$cache[$resource])) { + return self::$cache[$resource]; + } + + return self::$cache[$resource] = require $resource; + } +} diff --git a/vendor/symfony/translation/Loader/PoFileLoader.php b/vendor/symfony/translation/Loader/PoFileLoader.php new file mode 100644 index 0000000..ee143e2 --- /dev/null +++ b/vendor/symfony/translation/Loader/PoFileLoader.php @@ -0,0 +1,149 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Loader; + +/** + * @copyright Copyright (c) 2010, Union of RAD https://github.com/UnionOfRAD/lithium + * @copyright Copyright (c) 2012, Clemens Tolboom + */ +class PoFileLoader extends FileLoader +{ + /** + * Parses portable object (PO) format. + * + * From https://www.gnu.org/software/gettext/manual/gettext.html#PO-Files + * we should be able to parse files having: + * + * white-space + * # translator-comments + * #. extracted-comments + * #: reference... + * #, flag... + * #| msgid previous-untranslated-string + * msgid untranslated-string + * msgstr translated-string + * + * extra or different lines are: + * + * #| msgctxt previous-context + * #| msgid previous-untranslated-string + * msgctxt context + * + * #| msgid previous-untranslated-string-singular + * #| msgid_plural previous-untranslated-string-plural + * msgid untranslated-string-singular + * msgid_plural untranslated-string-plural + * msgstr[0] translated-string-case-0 + * ... + * msgstr[N] translated-string-case-n + * + * The definition states: + * - white-space and comments are optional. + * - msgid "" that an empty singleline defines a header. + * + * This parser sacrifices some features of the reference implementation the + * differences to that implementation are as follows. + * - No support for comments spanning multiple lines. + * - Translator and extracted comments are treated as being the same type. + * - Message IDs are allowed to have other encodings as just US-ASCII. + * + * Items with an empty id are ignored. + * + * {@inheritdoc} + */ + protected function loadResource(string $resource) + { + $stream = fopen($resource, 'r'); + + $defaults = [ + 'ids' => [], + 'translated' => null, + ]; + + $messages = []; + $item = $defaults; + $flags = []; + + while ($line = fgets($stream)) { + $line = trim($line); + + if ('' === $line) { + // Whitespace indicated current item is done + if (!\in_array('fuzzy', $flags)) { + $this->addMessage($messages, $item); + } + $item = $defaults; + $flags = []; + } elseif ('#,' === substr($line, 0, 2)) { + $flags = array_map('trim', explode(',', substr($line, 2))); + } elseif ('msgid "' === substr($line, 0, 7)) { + // We start a new msg so save previous + // TODO: this fails when comments or contexts are added + $this->addMessage($messages, $item); + $item = $defaults; + $item['ids']['singular'] = substr($line, 7, -1); + } elseif ('msgstr "' === substr($line, 0, 8)) { + $item['translated'] = substr($line, 8, -1); + } elseif ('"' === $line[0]) { + $continues = isset($item['translated']) ? 'translated' : 'ids'; + + if (\is_array($item[$continues])) { + end($item[$continues]); + $item[$continues][key($item[$continues])] .= substr($line, 1, -1); + } else { + $item[$continues] .= substr($line, 1, -1); + } + } elseif ('msgid_plural "' === substr($line, 0, 14)) { + $item['ids']['plural'] = substr($line, 14, -1); + } elseif ('msgstr[' === substr($line, 0, 7)) { + $size = strpos($line, ']'); + $item['translated'][(int) substr($line, 7, 1)] = substr($line, $size + 3, -1); + } + } + // save last item + if (!\in_array('fuzzy', $flags)) { + $this->addMessage($messages, $item); + } + fclose($stream); + + return $messages; + } + + /** + * Save a translation item to the messages. + * + * A .po file could contain by error missing plural indexes. We need to + * fix these before saving them. + */ + private function addMessage(array &$messages, array $item) + { + if (!empty($item['ids']['singular'])) { + $id = stripcslashes($item['ids']['singular']); + if (isset($item['ids']['plural'])) { + $id .= '|'.stripcslashes($item['ids']['plural']); + } + + $translated = (array) $item['translated']; + // PO are by definition indexed so sort by index. + ksort($translated); + // Make sure every index is filled. + end($translated); + $count = key($translated); + // Fill missing spots with '-'. + $empties = array_fill(0, $count + 1, '-'); + $translated += $empties; + ksort($translated); + + $messages[$id] = stripcslashes(implode('|', $translated)); + } + } +} diff --git a/vendor/symfony/translation/Loader/QtFileLoader.php b/vendor/symfony/translation/Loader/QtFileLoader.php new file mode 100644 index 0000000..9cf2fe9 --- /dev/null +++ b/vendor/symfony/translation/Loader/QtFileLoader.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Loader; + +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Config\Util\XmlUtils; +use Symfony\Component\Translation\Exception\InvalidResourceException; +use Symfony\Component\Translation\Exception\NotFoundResourceException; +use Symfony\Component\Translation\Exception\RuntimeException; +use Symfony\Component\Translation\MessageCatalogue; + +/** + * QtFileLoader loads translations from QT Translations XML files. + * + * @author Benjamin Eberlei + */ +class QtFileLoader implements LoaderInterface +{ + /** + * {@inheritdoc} + */ + public function load($resource, string $locale, string $domain = 'messages') + { + if (!class_exists(XmlUtils::class)) { + throw new RuntimeException('Loading translations from the QT format requires the Symfony Config component.'); + } + + if (!stream_is_local($resource)) { + throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource)); + } + + if (!file_exists($resource)) { + throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource)); + } + + try { + $dom = XmlUtils::loadFile($resource); + } catch (\InvalidArgumentException $e) { + throw new InvalidResourceException(sprintf('Unable to load "%s".', $resource), $e->getCode(), $e); + } + + $internalErrors = libxml_use_internal_errors(true); + libxml_clear_errors(); + + $xpath = new \DOMXPath($dom); + $nodes = $xpath->evaluate('//TS/context/name[text()="'.$domain.'"]'); + + $catalogue = new MessageCatalogue($locale); + if (1 == $nodes->length) { + $translations = $nodes->item(0)->nextSibling->parentNode->parentNode->getElementsByTagName('message'); + foreach ($translations as $translation) { + $translationValue = (string) $translation->getElementsByTagName('translation')->item(0)->nodeValue; + + if (!empty($translationValue)) { + $catalogue->set( + (string) $translation->getElementsByTagName('source')->item(0)->nodeValue, + $translationValue, + $domain + ); + } + $translation = $translation->nextSibling; + } + + if (class_exists(FileResource::class)) { + $catalogue->addResource(new FileResource($resource)); + } + } + + libxml_use_internal_errors($internalErrors); + + return $catalogue; + } +} diff --git a/vendor/symfony/translation/Loader/XliffFileLoader.php b/vendor/symfony/translation/Loader/XliffFileLoader.php new file mode 100644 index 0000000..35ad33e --- /dev/null +++ b/vendor/symfony/translation/Loader/XliffFileLoader.php @@ -0,0 +1,232 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Loader; + +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Config\Util\Exception\InvalidXmlException; +use Symfony\Component\Config\Util\Exception\XmlParsingException; +use Symfony\Component\Config\Util\XmlUtils; +use Symfony\Component\Translation\Exception\InvalidResourceException; +use Symfony\Component\Translation\Exception\NotFoundResourceException; +use Symfony\Component\Translation\Exception\RuntimeException; +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\Util\XliffUtils; + +/** + * XliffFileLoader loads translations from XLIFF files. + * + * @author Fabien Potencier + */ +class XliffFileLoader implements LoaderInterface +{ + /** + * {@inheritdoc} + */ + public function load($resource, string $locale, string $domain = 'messages') + { + if (!class_exists(XmlUtils::class)) { + throw new RuntimeException('Loading translations from the Xliff format requires the Symfony Config component.'); + } + + if (!$this->isXmlString($resource)) { + if (!stream_is_local($resource)) { + throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource)); + } + + if (!file_exists($resource)) { + throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource)); + } + + if (!is_file($resource)) { + throw new InvalidResourceException(sprintf('This is neither a file nor an XLIFF string "%s".', $resource)); + } + } + + try { + if ($this->isXmlString($resource)) { + $dom = XmlUtils::parse($resource); + } else { + $dom = XmlUtils::loadFile($resource); + } + } catch (\InvalidArgumentException | XmlParsingException | InvalidXmlException $e) { + throw new InvalidResourceException(sprintf('Unable to load "%s": ', $resource).$e->getMessage(), $e->getCode(), $e); + } + + if ($errors = XliffUtils::validateSchema($dom)) { + throw new InvalidResourceException(sprintf('Invalid resource provided: "%s"; Errors: ', $resource).XliffUtils::getErrorsAsString($errors)); + } + + $catalogue = new MessageCatalogue($locale); + $this->extract($dom, $catalogue, $domain); + + if (is_file($resource) && class_exists(FileResource::class)) { + $catalogue->addResource(new FileResource($resource)); + } + + return $catalogue; + } + + private function extract(\DOMDocument $dom, MessageCatalogue $catalogue, string $domain) + { + $xliffVersion = XliffUtils::getVersionNumber($dom); + + if ('1.2' === $xliffVersion) { + $this->extractXliff1($dom, $catalogue, $domain); + } + + if ('2.0' === $xliffVersion) { + $this->extractXliff2($dom, $catalogue, $domain); + } + } + + /** + * Extract messages and metadata from DOMDocument into a MessageCatalogue. + */ + private function extractXliff1(\DOMDocument $dom, MessageCatalogue $catalogue, string $domain) + { + $xml = simplexml_import_dom($dom); + $encoding = $dom->encoding ? strtoupper($dom->encoding) : null; + + $namespace = 'urn:oasis:names:tc:xliff:document:1.2'; + $xml->registerXPathNamespace('xliff', $namespace); + + foreach ($xml->xpath('//xliff:file') as $file) { + $fileAttributes = $file->attributes(); + + $file->registerXPathNamespace('xliff', $namespace); + + foreach ($file->xpath('.//xliff:trans-unit') as $translation) { + $attributes = $translation->attributes(); + + if (!(isset($attributes['resname']) || isset($translation->source))) { + continue; + } + + $source = isset($attributes['resname']) && $attributes['resname'] ? $attributes['resname'] : $translation->source; + // If the xlf file has another encoding specified, try to convert it because + // simple_xml will always return utf-8 encoded values + $target = $this->utf8ToCharset((string) ($translation->target ?? $translation->source), $encoding); + + $catalogue->set((string) $source, $target, $domain); + + $metadata = [ + 'source' => (string) $translation->source, + 'file' => [ + 'original' => (string) $fileAttributes['original'], + ], + ]; + if ($notes = $this->parseNotesMetadata($translation->note, $encoding)) { + $metadata['notes'] = $notes; + } + + if (isset($translation->target) && $translation->target->attributes()) { + $metadata['target-attributes'] = []; + foreach ($translation->target->attributes() as $key => $value) { + $metadata['target-attributes'][$key] = (string) $value; + } + } + + if (isset($attributes['id'])) { + $metadata['id'] = (string) $attributes['id']; + } + + $catalogue->setMetadata((string) $source, $metadata, $domain); + } + } + } + + private function extractXliff2(\DOMDocument $dom, MessageCatalogue $catalogue, string $domain) + { + $xml = simplexml_import_dom($dom); + $encoding = $dom->encoding ? strtoupper($dom->encoding) : null; + + $xml->registerXPathNamespace('xliff', 'urn:oasis:names:tc:xliff:document:2.0'); + + foreach ($xml->xpath('//xliff:unit') as $unit) { + foreach ($unit->segment as $segment) { + $attributes = $unit->attributes(); + $source = $attributes['name'] ?? $segment->source; + + // If the xlf file has another encoding specified, try to convert it because + // simple_xml will always return utf-8 encoded values + $target = $this->utf8ToCharset((string) ($segment->target ?? $segment->source), $encoding); + + $catalogue->set((string) $source, $target, $domain); + + $metadata = []; + if (isset($segment->target) && $segment->target->attributes()) { + $metadata['target-attributes'] = []; + foreach ($segment->target->attributes() as $key => $value) { + $metadata['target-attributes'][$key] = (string) $value; + } + } + + if (isset($unit->notes)) { + $metadata['notes'] = []; + foreach ($unit->notes->note as $noteNode) { + $note = []; + foreach ($noteNode->attributes() as $key => $value) { + $note[$key] = (string) $value; + } + $note['content'] = (string) $noteNode; + $metadata['notes'][] = $note; + } + } + + $catalogue->setMetadata((string) $source, $metadata, $domain); + } + } + } + + /** + * Convert a UTF8 string to the specified encoding. + */ + private function utf8ToCharset(string $content, string $encoding = null): string + { + if ('UTF-8' !== $encoding && !empty($encoding)) { + return mb_convert_encoding($content, $encoding, 'UTF-8'); + } + + return $content; + } + + private function parseNotesMetadata(\SimpleXMLElement $noteElement = null, string $encoding = null): array + { + $notes = []; + + if (null === $noteElement) { + return $notes; + } + + /** @var \SimpleXMLElement $xmlNote */ + foreach ($noteElement as $xmlNote) { + $noteAttributes = $xmlNote->attributes(); + $note = ['content' => $this->utf8ToCharset((string) $xmlNote, $encoding)]; + if (isset($noteAttributes['priority'])) { + $note['priority'] = (int) $noteAttributes['priority']; + } + + if (isset($noteAttributes['from'])) { + $note['from'] = (string) $noteAttributes['from']; + } + + $notes[] = $note; + } + + return $notes; + } + + private function isXmlString(string $resource): bool + { + return 0 === strpos($resource, ' + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Loader; + +use Symfony\Component\Translation\Exception\InvalidResourceException; +use Symfony\Component\Translation\Exception\LogicException; +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Parser as YamlParser; +use Symfony\Component\Yaml\Yaml; + +/** + * YamlFileLoader loads translations from Yaml files. + * + * @author Fabien Potencier + */ +class YamlFileLoader extends FileLoader +{ + private $yamlParser; + + /** + * {@inheritdoc} + */ + protected function loadResource(string $resource) + { + if (null === $this->yamlParser) { + if (!class_exists(\Symfony\Component\Yaml\Parser::class)) { + throw new LogicException('Loading translations from the YAML format requires the Symfony Yaml component.'); + } + + $this->yamlParser = new YamlParser(); + } + + try { + $messages = $this->yamlParser->parseFile($resource, Yaml::PARSE_CONSTANT); + } catch (ParseException $e) { + throw new InvalidResourceException(sprintf('The file "%s" does not contain valid YAML: ', $resource).$e->getMessage(), 0, $e); + } + + if (null !== $messages && !\is_array($messages)) { + throw new InvalidResourceException(sprintf('Unable to load file "%s".', $resource)); + } + + return $messages ?: []; + } +} diff --git a/vendor/symfony/translation/LoggingTranslator.php b/vendor/symfony/translation/LoggingTranslator.php new file mode 100644 index 0000000..6ccd482 --- /dev/null +++ b/vendor/symfony/translation/LoggingTranslator.php @@ -0,0 +1,131 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation; + +use Psr\Log\LoggerInterface; +use Symfony\Component\Translation\Exception\InvalidArgumentException; +use Symfony\Contracts\Translation\LocaleAwareInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * @author Abdellatif Ait boudad + */ +class LoggingTranslator implements TranslatorInterface, TranslatorBagInterface, LocaleAwareInterface +{ + private $translator; + private $logger; + + /** + * @param TranslatorInterface&TranslatorBagInterface&LocaleAwareInterface $translator The translator must implement TranslatorBagInterface + */ + public function __construct(TranslatorInterface $translator, LoggerInterface $logger) + { + if (!$translator instanceof TranslatorBagInterface || !$translator instanceof LocaleAwareInterface) { + throw new InvalidArgumentException(sprintf('The Translator "%s" must implement TranslatorInterface, TranslatorBagInterface and LocaleAwareInterface.', get_debug_type($translator))); + } + + $this->translator = $translator; + $this->logger = $logger; + } + + /** + * {@inheritdoc} + */ + public function trans(?string $id, array $parameters = [], string $domain = null, string $locale = null) + { + $trans = $this->translator->trans($id = (string) $id, $parameters, $domain, $locale); + $this->log($id, $domain, $locale); + + return $trans; + } + + /** + * {@inheritdoc} + */ + public function setLocale(string $locale) + { + $prev = $this->translator->getLocale(); + $this->translator->setLocale($locale); + if ($prev === $locale) { + return; + } + + $this->logger->debug(sprintf('The locale of the translator has changed from "%s" to "%s".', $prev, $locale)); + } + + /** + * {@inheritdoc} + */ + public function getLocale() + { + return $this->translator->getLocale(); + } + + /** + * {@inheritdoc} + */ + public function getCatalogue(string $locale = null) + { + return $this->translator->getCatalogue($locale); + } + + /** + * {@inheritdoc} + */ + public function getCatalogues(): array + { + return $this->translator->getCatalogues(); + } + + /** + * Gets the fallback locales. + * + * @return array + */ + public function getFallbackLocales() + { + if ($this->translator instanceof Translator || method_exists($this->translator, 'getFallbackLocales')) { + return $this->translator->getFallbackLocales(); + } + + return []; + } + + /** + * Passes through all unknown calls onto the translator object. + */ + public function __call(string $method, array $args) + { + return $this->translator->{$method}(...$args); + } + + /** + * Logs for missing translations. + */ + private function log(string $id, ?string $domain, ?string $locale) + { + if (null === $domain) { + $domain = 'messages'; + } + + $catalogue = $this->translator->getCatalogue($locale); + if ($catalogue->defines($id, $domain)) { + return; + } + + if ($catalogue->has($id, $domain)) { + $this->logger->debug('Translation use fallback catalogue.', ['id' => $id, 'domain' => $domain, 'locale' => $catalogue->getLocale()]); + } else { + $this->logger->warning('Translation not found.', ['id' => $id, 'domain' => $domain, 'locale' => $catalogue->getLocale()]); + } + } +} diff --git a/vendor/symfony/translation/MessageCatalogue.php b/vendor/symfony/translation/MessageCatalogue.php new file mode 100644 index 0000000..ff49b5a --- /dev/null +++ b/vendor/symfony/translation/MessageCatalogue.php @@ -0,0 +1,314 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation; + +use Symfony\Component\Config\Resource\ResourceInterface; +use Symfony\Component\Translation\Exception\LogicException; + +/** + * @author Fabien Potencier + */ +class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterface +{ + private $messages = []; + private $metadata = []; + private $resources = []; + private $locale; + private $fallbackCatalogue; + private $parent; + + /** + * @param array $messages An array of messages classified by domain + */ + public function __construct(string $locale, array $messages = []) + { + $this->locale = $locale; + $this->messages = $messages; + } + + /** + * {@inheritdoc} + */ + public function getLocale() + { + return $this->locale; + } + + /** + * {@inheritdoc} + */ + public function getDomains() + { + $domains = []; + + foreach ($this->messages as $domain => $messages) { + if (str_ends_with($domain, self::INTL_DOMAIN_SUFFIX)) { + $domain = substr($domain, 0, -\strlen(self::INTL_DOMAIN_SUFFIX)); + } + $domains[$domain] = $domain; + } + + return array_values($domains); + } + + /** + * {@inheritdoc} + */ + public function all(string $domain = null) + { + if (null !== $domain) { + // skip messages merge if intl-icu requested explicitly + if (str_ends_with($domain, self::INTL_DOMAIN_SUFFIX)) { + return $this->messages[$domain] ?? []; + } + + return ($this->messages[$domain.self::INTL_DOMAIN_SUFFIX] ?? []) + ($this->messages[$domain] ?? []); + } + + $allMessages = []; + + foreach ($this->messages as $domain => $messages) { + if (str_ends_with($domain, self::INTL_DOMAIN_SUFFIX)) { + $domain = substr($domain, 0, -\strlen(self::INTL_DOMAIN_SUFFIX)); + $allMessages[$domain] = $messages + ($allMessages[$domain] ?? []); + } else { + $allMessages[$domain] = ($allMessages[$domain] ?? []) + $messages; + } + } + + return $allMessages; + } + + /** + * {@inheritdoc} + */ + public function set(string $id, string $translation, string $domain = 'messages') + { + $this->add([$id => $translation], $domain); + } + + /** + * {@inheritdoc} + */ + public function has(string $id, string $domain = 'messages') + { + if (isset($this->messages[$domain][$id]) || isset($this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id])) { + return true; + } + + if (null !== $this->fallbackCatalogue) { + return $this->fallbackCatalogue->has($id, $domain); + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function defines(string $id, string $domain = 'messages') + { + return isset($this->messages[$domain][$id]) || isset($this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id]); + } + + /** + * {@inheritdoc} + */ + public function get(string $id, string $domain = 'messages') + { + if (isset($this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id])) { + return $this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id]; + } + + if (isset($this->messages[$domain][$id])) { + return $this->messages[$domain][$id]; + } + + if (null !== $this->fallbackCatalogue) { + return $this->fallbackCatalogue->get($id, $domain); + } + + return $id; + } + + /** + * {@inheritdoc} + */ + public function replace(array $messages, string $domain = 'messages') + { + unset($this->messages[$domain], $this->messages[$domain.self::INTL_DOMAIN_SUFFIX]); + + $this->add($messages, $domain); + } + + /** + * {@inheritdoc} + */ + public function add(array $messages, string $domain = 'messages') + { + if (!isset($this->messages[$domain])) { + $this->messages[$domain] = []; + } + $intlDomain = $domain; + if (!str_ends_with($domain, self::INTL_DOMAIN_SUFFIX)) { + $intlDomain .= self::INTL_DOMAIN_SUFFIX; + } + foreach ($messages as $id => $message) { + if (isset($this->messages[$intlDomain]) && \array_key_exists($id, $this->messages[$intlDomain])) { + $this->messages[$intlDomain][$id] = $message; + } else { + $this->messages[$domain][$id] = $message; + } + } + } + + /** + * {@inheritdoc} + */ + public function addCatalogue(MessageCatalogueInterface $catalogue) + { + if ($catalogue->getLocale() !== $this->locale) { + throw new LogicException(sprintf('Cannot add a catalogue for locale "%s" as the current locale for this catalogue is "%s".', $catalogue->getLocale(), $this->locale)); + } + + foreach ($catalogue->all() as $domain => $messages) { + if ($intlMessages = $catalogue->all($domain.self::INTL_DOMAIN_SUFFIX)) { + $this->add($intlMessages, $domain.self::INTL_DOMAIN_SUFFIX); + $messages = array_diff_key($messages, $intlMessages); + } + $this->add($messages, $domain); + } + + foreach ($catalogue->getResources() as $resource) { + $this->addResource($resource); + } + + if ($catalogue instanceof MetadataAwareInterface) { + $metadata = $catalogue->getMetadata('', ''); + $this->addMetadata($metadata); + } + } + + /** + * {@inheritdoc} + */ + public function addFallbackCatalogue(MessageCatalogueInterface $catalogue) + { + // detect circular references + $c = $catalogue; + while ($c = $c->getFallbackCatalogue()) { + if ($c->getLocale() === $this->getLocale()) { + throw new LogicException(sprintf('Circular reference detected when adding a fallback catalogue for locale "%s".', $catalogue->getLocale())); + } + } + + $c = $this; + do { + if ($c->getLocale() === $catalogue->getLocale()) { + throw new LogicException(sprintf('Circular reference detected when adding a fallback catalogue for locale "%s".', $catalogue->getLocale())); + } + + foreach ($catalogue->getResources() as $resource) { + $c->addResource($resource); + } + } while ($c = $c->parent); + + $catalogue->parent = $this; + $this->fallbackCatalogue = $catalogue; + + foreach ($catalogue->getResources() as $resource) { + $this->addResource($resource); + } + } + + /** + * {@inheritdoc} + */ + public function getFallbackCatalogue() + { + return $this->fallbackCatalogue; + } + + /** + * {@inheritdoc} + */ + public function getResources() + { + return array_values($this->resources); + } + + /** + * {@inheritdoc} + */ + public function addResource(ResourceInterface $resource) + { + $this->resources[$resource->__toString()] = $resource; + } + + /** + * {@inheritdoc} + */ + public function getMetadata(string $key = '', string $domain = 'messages') + { + if ('' == $domain) { + return $this->metadata; + } + + if (isset($this->metadata[$domain])) { + if ('' == $key) { + return $this->metadata[$domain]; + } + + if (isset($this->metadata[$domain][$key])) { + return $this->metadata[$domain][$key]; + } + } + + return null; + } + + /** + * {@inheritdoc} + */ + public function setMetadata(string $key, $value, string $domain = 'messages') + { + $this->metadata[$domain][$key] = $value; + } + + /** + * {@inheritdoc} + */ + public function deleteMetadata(string $key = '', string $domain = 'messages') + { + if ('' == $domain) { + $this->metadata = []; + } elseif ('' == $key) { + unset($this->metadata[$domain]); + } else { + unset($this->metadata[$domain][$key]); + } + } + + /** + * Adds current values with the new values. + * + * @param array $values Values to add + */ + private function addMetadata(array $values) + { + foreach ($values as $domain => $keys) { + foreach ($keys as $key => $value) { + $this->setMetadata($key, $value, $domain); + } + } + } +} diff --git a/vendor/symfony/translation/MessageCatalogueInterface.php b/vendor/symfony/translation/MessageCatalogueInterface.php new file mode 100644 index 0000000..cf7746c --- /dev/null +++ b/vendor/symfony/translation/MessageCatalogueInterface.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation; + +use Symfony\Component\Config\Resource\ResourceInterface; + +/** + * MessageCatalogueInterface. + * + * @author Fabien Potencier + */ +interface MessageCatalogueInterface +{ + public const INTL_DOMAIN_SUFFIX = '+intl-icu'; + + /** + * Gets the catalogue locale. + * + * @return string + */ + public function getLocale(); + + /** + * Gets the domains. + * + * @return array + */ + public function getDomains(); + + /** + * Gets the messages within a given domain. + * + * If $domain is null, it returns all messages. + * + * @param string $domain The domain name + * + * @return array + */ + public function all(string $domain = null); + + /** + * Sets a message translation. + * + * @param string $id The message id + * @param string $translation The messages translation + * @param string $domain The domain name + */ + public function set(string $id, string $translation, string $domain = 'messages'); + + /** + * Checks if a message has a translation. + * + * @param string $id The message id + * @param string $domain The domain name + * + * @return bool + */ + public function has(string $id, string $domain = 'messages'); + + /** + * Checks if a message has a translation (it does not take into account the fallback mechanism). + * + * @param string $id The message id + * @param string $domain The domain name + * + * @return bool + */ + public function defines(string $id, string $domain = 'messages'); + + /** + * Gets a message translation. + * + * @param string $id The message id + * @param string $domain The domain name + * + * @return string + */ + public function get(string $id, string $domain = 'messages'); + + /** + * Sets translations for a given domain. + * + * @param array $messages An array of translations + * @param string $domain The domain name + */ + public function replace(array $messages, string $domain = 'messages'); + + /** + * Adds translations for a given domain. + * + * @param array $messages An array of translations + * @param string $domain The domain name + */ + public function add(array $messages, string $domain = 'messages'); + + /** + * Merges translations from the given Catalogue into the current one. + * + * The two catalogues must have the same locale. + */ + public function addCatalogue(self $catalogue); + + /** + * Merges translations from the given Catalogue into the current one + * only when the translation does not exist. + * + * This is used to provide default translations when they do not exist for the current locale. + */ + public function addFallbackCatalogue(self $catalogue); + + /** + * Gets the fallback catalogue. + * + * @return self|null + */ + public function getFallbackCatalogue(); + + /** + * Returns an array of resources loaded to build this collection. + * + * @return ResourceInterface[] + */ + public function getResources(); + + /** + * Adds a resource for this collection. + */ + public function addResource(ResourceInterface $resource); +} diff --git a/vendor/symfony/translation/MetadataAwareInterface.php b/vendor/symfony/translation/MetadataAwareInterface.php new file mode 100644 index 0000000..2216eed --- /dev/null +++ b/vendor/symfony/translation/MetadataAwareInterface.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation; + +/** + * MetadataAwareInterface. + * + * @author Fabien Potencier + */ +interface MetadataAwareInterface +{ + /** + * Gets metadata for the given domain and key. + * + * Passing an empty domain will return an array with all metadata indexed by + * domain and then by key. Passing an empty key will return an array with all + * metadata for the given domain. + * + * @return mixed The value that was set or an array with the domains/keys or null + */ + public function getMetadata(string $key = '', string $domain = 'messages'); + + /** + * Adds metadata to a message domain. + * + * @param mixed $value + */ + public function setMetadata(string $key, $value, string $domain = 'messages'); + + /** + * Deletes metadata for the given key and domain. + * + * Passing an empty domain will delete all metadata. Passing an empty key will + * delete all metadata for the given domain. + */ + public function deleteMetadata(string $key = '', string $domain = 'messages'); +} diff --git a/vendor/symfony/translation/Provider/AbstractProviderFactory.php b/vendor/symfony/translation/Provider/AbstractProviderFactory.php new file mode 100644 index 0000000..17442fd --- /dev/null +++ b/vendor/symfony/translation/Provider/AbstractProviderFactory.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Provider; + +use Symfony\Component\Translation\Exception\IncompleteDsnException; + +abstract class AbstractProviderFactory implements ProviderFactoryInterface +{ + public function supports(Dsn $dsn): bool + { + return \in_array($dsn->getScheme(), $this->getSupportedSchemes(), true); + } + + /** + * @return string[] + */ + abstract protected function getSupportedSchemes(): array; + + protected function getUser(Dsn $dsn): string + { + if (null === $user = $dsn->getUser()) { + throw new IncompleteDsnException('User is not set.', $dsn->getOriginalDsn()); + } + + return $user; + } + + protected function getPassword(Dsn $dsn): string + { + if (null === $password = $dsn->getPassword()) { + throw new IncompleteDsnException('Password is not set.', $dsn->getOriginalDsn()); + } + + return $password; + } +} diff --git a/vendor/symfony/translation/Provider/Dsn.php b/vendor/symfony/translation/Provider/Dsn.php new file mode 100644 index 0000000..820cabf --- /dev/null +++ b/vendor/symfony/translation/Provider/Dsn.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Provider; + +use Symfony\Component\Translation\Exception\InvalidArgumentException; +use Symfony\Component\Translation\Exception\MissingRequiredOptionException; + +/** + * @author Fabien Potencier + * @author Oskar Stark + */ +final class Dsn +{ + private $scheme; + private $host; + private $user; + private $password; + private $port; + private $path; + private $options; + private $originalDsn; + + public function __construct(string $dsn) + { + $this->originalDsn = $dsn; + + if (false === $parsedDsn = parse_url($dsn)) { + throw new InvalidArgumentException(sprintf('The "%s" translation provider DSN is invalid.', $dsn)); + } + + if (!isset($parsedDsn['scheme'])) { + throw new InvalidArgumentException(sprintf('The "%s" translation provider DSN must contain a scheme.', $dsn)); + } + $this->scheme = $parsedDsn['scheme']; + + if (!isset($parsedDsn['host'])) { + throw new InvalidArgumentException(sprintf('The "%s" translation provider DSN must contain a host (use "default" by default).', $dsn)); + } + $this->host = $parsedDsn['host']; + + $this->user = '' !== ($parsedDsn['user'] ?? '') ? urldecode($parsedDsn['user']) : null; + $this->password = '' !== ($parsedDsn['pass'] ?? '') ? urldecode($parsedDsn['pass']) : null; + $this->port = $parsedDsn['port'] ?? null; + $this->path = $parsedDsn['path'] ?? null; + parse_str($parsedDsn['query'] ?? '', $this->options); + } + + public function getScheme(): string + { + return $this->scheme; + } + + public function getHost(): string + { + return $this->host; + } + + public function getUser(): ?string + { + return $this->user; + } + + public function getPassword(): ?string + { + return $this->password; + } + + public function getPort(int $default = null): ?int + { + return $this->port ?? $default; + } + + public function getOption(string $key, $default = null) + { + return $this->options[$key] ?? $default; + } + + public function getRequiredOption(string $key) + { + if (!\array_key_exists($key, $this->options) || '' === trim($this->options[$key])) { + throw new MissingRequiredOptionException($key); + } + + return $this->options[$key]; + } + + public function getOptions(): array + { + return $this->options; + } + + public function getPath(): ?string + { + return $this->path; + } + + public function getOriginalDsn(): string + { + return $this->originalDsn; + } +} diff --git a/vendor/symfony/translation/Provider/FilteringProvider.php b/vendor/symfony/translation/Provider/FilteringProvider.php new file mode 100644 index 0000000..5f970a2 --- /dev/null +++ b/vendor/symfony/translation/Provider/FilteringProvider.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Provider; + +use Symfony\Component\Translation\TranslatorBag; +use Symfony\Component\Translation\TranslatorBagInterface; + +/** + * Filters domains and locales between the Translator config values and those specific to each provider. + * + * @author Mathieu Santostefano + */ +class FilteringProvider implements ProviderInterface +{ + private $provider; + private $locales; + private $domains; + + public function __construct(ProviderInterface $provider, array $locales, array $domains = []) + { + $this->provider = $provider; + $this->locales = $locales; + $this->domains = $domains; + } + + public function __toString(): string + { + return (string) $this->provider; + } + + /** + * {@inheritdoc} + */ + public function write(TranslatorBagInterface $translatorBag): void + { + $this->provider->write($translatorBag); + } + + public function read(array $domains, array $locales): TranslatorBag + { + $domains = !$this->domains ? $domains : array_intersect($this->domains, $domains); + $locales = array_intersect($this->locales, $locales); + + return $this->provider->read($domains, $locales); + } + + public function delete(TranslatorBagInterface $translatorBag): void + { + $this->provider->delete($translatorBag); + } + + public function getDomains(): array + { + return $this->domains; + } +} diff --git a/vendor/symfony/translation/Provider/NullProvider.php b/vendor/symfony/translation/Provider/NullProvider.php new file mode 100644 index 0000000..f00392e --- /dev/null +++ b/vendor/symfony/translation/Provider/NullProvider.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Provider; + +use Symfony\Component\Translation\TranslatorBag; +use Symfony\Component\Translation\TranslatorBagInterface; + +/** + * @author Mathieu Santostefano + */ +class NullProvider implements ProviderInterface +{ + public function __toString(): string + { + return 'null'; + } + + public function write(TranslatorBagInterface $translatorBag, bool $override = false): void + { + } + + public function read(array $domains, array $locales): TranslatorBag + { + return new TranslatorBag(); + } + + public function delete(TranslatorBagInterface $translatorBag): void + { + } +} diff --git a/vendor/symfony/translation/Provider/NullProviderFactory.php b/vendor/symfony/translation/Provider/NullProviderFactory.php new file mode 100644 index 0000000..f350f16 --- /dev/null +++ b/vendor/symfony/translation/Provider/NullProviderFactory.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Provider; + +use Symfony\Component\Translation\Exception\UnsupportedSchemeException; + +/** + * @author Mathieu Santostefano + */ +final class NullProviderFactory extends AbstractProviderFactory +{ + public function create(Dsn $dsn): ProviderInterface + { + if ('null' === $dsn->getScheme()) { + return new NullProvider(); + } + + throw new UnsupportedSchemeException($dsn, 'null', $this->getSupportedSchemes()); + } + + protected function getSupportedSchemes(): array + { + return ['null']; + } +} diff --git a/vendor/symfony/translation/Provider/ProviderFactoryInterface.php b/vendor/symfony/translation/Provider/ProviderFactoryInterface.php new file mode 100644 index 0000000..3fd4494 --- /dev/null +++ b/vendor/symfony/translation/Provider/ProviderFactoryInterface.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Provider; + +use Symfony\Component\Translation\Exception\IncompleteDsnException; +use Symfony\Component\Translation\Exception\UnsupportedSchemeException; + +interface ProviderFactoryInterface +{ + /** + * @throws UnsupportedSchemeException + * @throws IncompleteDsnException + */ + public function create(Dsn $dsn): ProviderInterface; + + public function supports(Dsn $dsn): bool; +} diff --git a/vendor/symfony/translation/Provider/ProviderInterface.php b/vendor/symfony/translation/Provider/ProviderInterface.php new file mode 100644 index 0000000..a32193f --- /dev/null +++ b/vendor/symfony/translation/Provider/ProviderInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Provider; + +use Symfony\Component\Translation\TranslatorBag; +use Symfony\Component\Translation\TranslatorBagInterface; + +interface ProviderInterface +{ + public function __toString(): string; + + /** + * Translations available in the TranslatorBag only must be created. + * Translations available in both the TranslatorBag and on the provider + * must be overwritten. + * Translations available on the provider only must be kept. + */ + public function write(TranslatorBagInterface $translatorBag): void; + + public function read(array $domains, array $locales): TranslatorBag; + + public function delete(TranslatorBagInterface $translatorBag): void; +} diff --git a/vendor/symfony/translation/Provider/TranslationProviderCollection.php b/vendor/symfony/translation/Provider/TranslationProviderCollection.php new file mode 100644 index 0000000..61ac641 --- /dev/null +++ b/vendor/symfony/translation/Provider/TranslationProviderCollection.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Provider; + +use Symfony\Component\Translation\Exception\InvalidArgumentException; + +/** + * @author Mathieu Santostefano + */ +final class TranslationProviderCollection +{ + /** + * @var array + */ + private $providers; + + /** + * @param array $providers + */ + public function __construct(iterable $providers) + { + $this->providers = \is_array($providers) ? $providers : iterator_to_array($providers); + } + + public function __toString(): string + { + return '['.implode(',', array_keys($this->providers)).']'; + } + + public function has(string $name): bool + { + return isset($this->providers[$name]); + } + + public function get(string $name): ProviderInterface + { + if (!$this->has($name)) { + throw new InvalidArgumentException(sprintf('Provider "%s" not found. Available: "%s".', $name, (string) $this)); + } + + return $this->providers[$name]; + } + + public function keys(): array + { + return array_keys($this->providers); + } +} diff --git a/vendor/symfony/translation/Provider/TranslationProviderCollectionFactory.php b/vendor/symfony/translation/Provider/TranslationProviderCollectionFactory.php new file mode 100644 index 0000000..81db3a5 --- /dev/null +++ b/vendor/symfony/translation/Provider/TranslationProviderCollectionFactory.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Provider; + +use Symfony\Component\Translation\Exception\UnsupportedSchemeException; + +/** + * @author Mathieu Santostefano + */ +class TranslationProviderCollectionFactory +{ + private $factories; + private $enabledLocales; + + /** + * @param iterable $factories + */ + public function __construct(iterable $factories, array $enabledLocales) + { + $this->factories = $factories; + $this->enabledLocales = $enabledLocales; + } + + public function fromConfig(array $config): TranslationProviderCollection + { + $providers = []; + foreach ($config as $name => $currentConfig) { + $providers[$name] = $this->fromDsnObject( + new Dsn($currentConfig['dsn']), + !$currentConfig['locales'] ? $this->enabledLocales : $currentConfig['locales'], + !$currentConfig['domains'] ? [] : $currentConfig['domains'] + ); + } + + return new TranslationProviderCollection($providers); + } + + public function fromDsnObject(Dsn $dsn, array $locales, array $domains = []): ProviderInterface + { + foreach ($this->factories as $factory) { + if ($factory->supports($dsn)) { + return new FilteringProvider($factory->create($dsn), $locales, $domains); + } + } + + throw new UnsupportedSchemeException($dsn); + } +} diff --git a/vendor/symfony/translation/PseudoLocalizationTranslator.php b/vendor/symfony/translation/PseudoLocalizationTranslator.php new file mode 100644 index 0000000..3fdc1aa --- /dev/null +++ b/vendor/symfony/translation/PseudoLocalizationTranslator.php @@ -0,0 +1,368 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation; + +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * This translator should only be used in a development environment. + */ +final class PseudoLocalizationTranslator implements TranslatorInterface +{ + private const EXPANSION_CHARACTER = '~'; + + private $translator; + private $accents; + private $expansionFactor; + private $brackets; + private $parseHTML; + + /** + * @var string[] + */ + private $localizableHTMLAttributes; + + /** + * Available options: + * * accents: + * type: boolean + * default: true + * description: replace ASCII characters of the translated string with accented versions or similar characters + * example: if true, "foo" => "ƒöö". + * + * * expansion_factor: + * type: float + * default: 1 + * validation: it must be greater than or equal to 1 + * description: expand the translated string by the given factor with spaces and tildes + * example: if 2, "foo" => "~foo ~" + * + * * brackets: + * type: boolean + * default: true + * description: wrap the translated string with brackets + * example: if true, "foo" => "[foo]" + * + * * parse_html: + * type: boolean + * default: false + * description: parse the translated string as HTML - looking for HTML tags has a performance impact but allows to preserve them from alterations - it also allows to compute the visible translated string length which is useful to correctly expand ot when it contains HTML + * warning: unclosed tags are unsupported, they will be fixed (closed) by the parser - eg, "foo
        bar" => "foo
        bar
        " + * + * * localizable_html_attributes: + * type: string[] + * default: [] + * description: the list of HTML attributes whose values can be altered - it is only useful when the "parse_html" option is set to true + * example: if ["title"], and with the "accents" option set to true, "Profile" => "Þŕöƒîļé" - if "title" was not in the "localizable_html_attributes" list, the title attribute data would be left unchanged. + */ + public function __construct(TranslatorInterface $translator, array $options = []) + { + $this->translator = $translator; + $this->accents = $options['accents'] ?? true; + + if (1.0 > ($this->expansionFactor = $options['expansion_factor'] ?? 1.0)) { + throw new \InvalidArgumentException('The expansion factor must be greater than or equal to 1.'); + } + + $this->brackets = $options['brackets'] ?? true; + + $this->parseHTML = $options['parse_html'] ?? false; + if ($this->parseHTML && !$this->accents && 1.0 === $this->expansionFactor) { + $this->parseHTML = false; + } + + $this->localizableHTMLAttributes = $options['localizable_html_attributes'] ?? []; + } + + /** + * {@inheritdoc} + */ + public function trans(string $id, array $parameters = [], string $domain = null, string $locale = null): string + { + $trans = ''; + $visibleText = ''; + + foreach ($this->getParts($this->translator->trans($id, $parameters, $domain, $locale)) as [$visible, $localizable, $text]) { + if ($visible) { + $visibleText .= $text; + } + + if (!$localizable) { + $trans .= $text; + + continue; + } + + $this->addAccents($trans, $text); + } + + $this->expand($trans, $visibleText); + + $this->addBrackets($trans); + + return $trans; + } + + public function getLocale(): string + { + return $this->translator->getLocale(); + } + + private function getParts(string $originalTrans): array + { + if (!$this->parseHTML) { + return [[true, true, $originalTrans]]; + } + + $html = mb_convert_encoding($originalTrans, 'HTML-ENTITIES', mb_detect_encoding($originalTrans, null, true) ?: 'UTF-8'); + + $useInternalErrors = libxml_use_internal_errors(true); + + $dom = new \DOMDocument(); + $dom->loadHTML(''.$html.''); + + libxml_clear_errors(); + libxml_use_internal_errors($useInternalErrors); + + return $this->parseNode($dom->childNodes->item(1)->childNodes->item(0)->childNodes->item(0)); + } + + private function parseNode(\DOMNode $node): array + { + $parts = []; + + foreach ($node->childNodes as $childNode) { + if (!$childNode instanceof \DOMElement) { + $parts[] = [true, true, $childNode->nodeValue]; + + continue; + } + + $parts[] = [false, false, '<'.$childNode->tagName]; + + /** @var \DOMAttr $attribute */ + foreach ($childNode->attributes as $attribute) { + $parts[] = [false, false, ' '.$attribute->nodeName.'="']; + + $localizableAttribute = \in_array($attribute->nodeName, $this->localizableHTMLAttributes, true); + foreach (preg_split('/(&(?:amp|quot|#039|lt|gt);+)/', htmlspecialchars($attribute->nodeValue, \ENT_QUOTES, 'UTF-8'), -1, \PREG_SPLIT_DELIM_CAPTURE) as $i => $match) { + if ('' === $match) { + continue; + } + + $parts[] = [false, $localizableAttribute && 0 === $i % 2, $match]; + } + + $parts[] = [false, false, '"']; + } + + $parts[] = [false, false, '>']; + + $parts = array_merge($parts, $this->parseNode($childNode, $parts)); + + $parts[] = [false, false, 'tagName.'>']; + } + + return $parts; + } + + private function addAccents(string &$trans, string $text): void + { + $trans .= $this->accents ? strtr($text, [ + ' ' => ' ', + '!' => '¡', + '"' => '″', + '#' => '♯', + '$' => '€', + '%' => '‰', + '&' => '⅋', + '\'' => '´', + '(' => '{', + ')' => '}', + '*' => '⁎', + '+' => '⁺', + ',' => '،', + '-' => '‐', + '.' => '·', + '/' => '⁄', + '0' => '⓪', + '1' => '①', + '2' => '②', + '3' => '③', + '4' => '④', + '5' => '⑤', + '6' => '⑥', + '7' => '⑦', + '8' => '⑧', + '9' => '⑨', + ':' => '∶', + ';' => '⁏', + '<' => '≤', + '=' => '≂', + '>' => '≥', + '?' => '¿', + '@' => '՞', + 'A' => 'Å', + 'B' => 'Ɓ', + 'C' => 'Ç', + 'D' => 'Ð', + 'E' => 'É', + 'F' => 'Ƒ', + 'G' => 'Ĝ', + 'H' => 'Ĥ', + 'I' => 'Î', + 'J' => 'Ĵ', + 'K' => 'Ķ', + 'L' => 'Ļ', + 'M' => 'Ṁ', + 'N' => 'Ñ', + 'O' => 'Ö', + 'P' => 'Þ', + 'Q' => 'Ǫ', + 'R' => 'Ŕ', + 'S' => 'Š', + 'T' => 'Ţ', + 'U' => 'Û', + 'V' => 'Ṽ', + 'W' => 'Ŵ', + 'X' => 'Ẋ', + 'Y' => 'Ý', + 'Z' => 'Ž', + '[' => '⁅', + '\\' => '∖', + ']' => '⁆', + '^' => '˄', + '_' => '‿', + '`' => '‵', + 'a' => 'å', + 'b' => 'ƀ', + 'c' => 'ç', + 'd' => 'ð', + 'e' => 'é', + 'f' => 'ƒ', + 'g' => 'ĝ', + 'h' => 'ĥ', + 'i' => 'î', + 'j' => 'ĵ', + 'k' => 'ķ', + 'l' => 'ļ', + 'm' => 'ɱ', + 'n' => 'ñ', + 'o' => 'ö', + 'p' => 'þ', + 'q' => 'ǫ', + 'r' => 'ŕ', + 's' => 'š', + 't' => 'ţ', + 'u' => 'û', + 'v' => 'ṽ', + 'w' => 'ŵ', + 'x' => 'ẋ', + 'y' => 'ý', + 'z' => 'ž', + '{' => '(', + '|' => '¦', + '}' => ')', + '~' => '˞', + ]) : $text; + } + + private function expand(string &$trans, string $visibleText): void + { + if (1.0 >= $this->expansionFactor) { + return; + } + + $visibleLength = $this->strlen($visibleText); + $missingLength = (int) (ceil($visibleLength * $this->expansionFactor)) - $visibleLength; + if ($this->brackets) { + $missingLength -= 2; + } + + if (0 >= $missingLength) { + return; + } + + $words = []; + $wordsCount = 0; + foreach (preg_split('/ +/', $visibleText, -1, \PREG_SPLIT_NO_EMPTY) as $word) { + $wordLength = $this->strlen($word); + + if ($wordLength >= $missingLength) { + continue; + } + + if (!isset($words[$wordLength])) { + $words[$wordLength] = 0; + } + + ++$words[$wordLength]; + ++$wordsCount; + } + + if (!$words) { + $trans .= 1 === $missingLength ? self::EXPANSION_CHARACTER : ' '.str_repeat(self::EXPANSION_CHARACTER, $missingLength - 1); + + return; + } + + arsort($words, \SORT_NUMERIC); + + $longestWordLength = max(array_keys($words)); + + while (true) { + $r = mt_rand(1, $wordsCount); + + foreach ($words as $length => $count) { + $r -= $count; + if ($r <= 0) { + break; + } + } + + $trans .= ' '.str_repeat(self::EXPANSION_CHARACTER, $length); + + $missingLength -= $length + 1; + + if (0 === $missingLength) { + return; + } + + while ($longestWordLength >= $missingLength) { + $wordsCount -= $words[$longestWordLength]; + unset($words[$longestWordLength]); + + if (!$words) { + $trans .= 1 === $missingLength ? self::EXPANSION_CHARACTER : ' '.str_repeat(self::EXPANSION_CHARACTER, $missingLength - 1); + + return; + } + + $longestWordLength = max(array_keys($words)); + } + } + } + + private function addBrackets(string &$trans): void + { + if (!$this->brackets) { + return; + } + + $trans = '['.$trans.']'; + } + + private function strlen(string $s): int + { + return false === ($encoding = mb_detect_encoding($s, null, true)) ? \strlen($s) : mb_strlen($s, $encoding); + } +} diff --git a/vendor/symfony/translation/README.md b/vendor/symfony/translation/README.md new file mode 100644 index 0000000..adda9a5 --- /dev/null +++ b/vendor/symfony/translation/README.md @@ -0,0 +1,48 @@ +Translation Component +===================== + +The Translation component provides tools to internationalize your application. + +Getting Started +--------------- + +``` +$ composer require symfony/translation +``` + +```php +use Symfony\Component\Translation\Translator; +use Symfony\Component\Translation\Loader\ArrayLoader; + +$translator = new Translator('fr_FR'); +$translator->addLoader('array', new ArrayLoader()); +$translator->addResource('array', [ + 'Hello World!' => 'Bonjour !', +], 'fr_FR'); + +echo $translator->trans('Hello World!'); // outputs « Bonjour ! » +``` + +Sponsor +------- + +The Translation component for Symfony 5.4/6.0 is [backed][1] by: + + * [Crowdin][2], a cloud-based localization management software helping teams to go global and stay agile. + * [Lokalise][3], a continuous localization and translation management platform that integrates into your development workflow so you can ship localized products, faster. + +Help Symfony by [sponsoring][4] its development! + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/translation.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) + +[1]: https://symfony.com/backers +[2]: https://crowdin.com +[3]: https://lokalise.com +[4]: https://symfony.com/sponsor diff --git a/vendor/symfony/translation/Reader/TranslationReader.php b/vendor/symfony/translation/Reader/TranslationReader.php new file mode 100644 index 0000000..e8e8638 --- /dev/null +++ b/vendor/symfony/translation/Reader/TranslationReader.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Reader; + +use Symfony\Component\Finder\Finder; +use Symfony\Component\Translation\Loader\LoaderInterface; +use Symfony\Component\Translation\MessageCatalogue; + +/** + * TranslationReader reads translation messages from translation files. + * + * @author Michel Salib + */ +class TranslationReader implements TranslationReaderInterface +{ + /** + * Loaders used for import. + * + * @var array + */ + private $loaders = []; + + /** + * Adds a loader to the translation extractor. + * + * @param string $format The format of the loader + */ + public function addLoader(string $format, LoaderInterface $loader) + { + $this->loaders[$format] = $loader; + } + + /** + * {@inheritdoc} + */ + public function read(string $directory, MessageCatalogue $catalogue) + { + if (!is_dir($directory)) { + return; + } + + foreach ($this->loaders as $format => $loader) { + // load any existing translation files + $finder = new Finder(); + $extension = $catalogue->getLocale().'.'.$format; + $files = $finder->files()->name('*.'.$extension)->in($directory); + foreach ($files as $file) { + $domain = substr($file->getFilename(), 0, -1 * \strlen($extension) - 1); + $catalogue->addCatalogue($loader->load($file->getPathname(), $catalogue->getLocale(), $domain)); + } + } + } +} diff --git a/vendor/symfony/translation/Reader/TranslationReaderInterface.php b/vendor/symfony/translation/Reader/TranslationReaderInterface.php new file mode 100644 index 0000000..bc37204 --- /dev/null +++ b/vendor/symfony/translation/Reader/TranslationReaderInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Reader; + +use Symfony\Component\Translation\MessageCatalogue; + +/** + * TranslationReader reads translation messages from translation files. + * + * @author Tobias Nyholm + */ +interface TranslationReaderInterface +{ + /** + * Reads translation messages from a directory to the catalogue. + */ + public function read(string $directory, MessageCatalogue $catalogue); +} diff --git a/vendor/symfony/translation/Resources/bin/translation-status.php b/vendor/symfony/translation/Resources/bin/translation-status.php new file mode 100644 index 0000000..fac8acb --- /dev/null +++ b/vendor/symfony/translation/Resources/bin/translation-status.php @@ -0,0 +1,274 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +$usageInstructions = << false, + // NULL = analyze all locales + 'locale_to_analyze' => null, + // append --incomplete to only show incomplete languages + 'include_completed_languages' => true, + // the reference files all the other translations are compared to + 'original_files' => [ + 'src/Symfony/Component/Form/Resources/translations/validators.en.xlf', + 'src/Symfony/Component/Security/Core/Resources/translations/security.en.xlf', + 'src/Symfony/Component/Validator/Resources/translations/validators.en.xlf', + ], +]; + +$argc = $_SERVER['argc']; +$argv = $_SERVER['argv']; + +if ($argc > 4) { + echo str_replace('translation-status.php', $argv[0], $usageInstructions); + exit(1); +} + +foreach (array_slice($argv, 1) as $argumentOrOption) { + if ('--incomplete' === $argumentOrOption) { + $config['include_completed_languages'] = false; + continue; + } + + if (0 === strpos($argumentOrOption, '-')) { + $config['verbose_output'] = true; + } else { + $config['locale_to_analyze'] = $argumentOrOption; + } +} + +foreach ($config['original_files'] as $originalFilePath) { + if (!file_exists($originalFilePath)) { + echo sprintf('The following file does not exist. Make sure that you execute this command at the root dir of the Symfony code repository.%s %s', \PHP_EOL, $originalFilePath); + exit(1); + } +} + +$totalMissingTranslations = 0; +$totalTranslationMismatches = 0; + +foreach ($config['original_files'] as $originalFilePath) { + $translationFilePaths = findTranslationFiles($originalFilePath, $config['locale_to_analyze']); + $translationStatus = calculateTranslationStatus($originalFilePath, $translationFilePaths); + + $totalMissingTranslations += array_sum(array_map(function ($translation) { + return count($translation['missingKeys']); + }, array_values($translationStatus))); + $totalTranslationMismatches += array_sum(array_map(function ($translation) { + return count($translation['mismatches']); + }, array_values($translationStatus))); + + printTranslationStatus($originalFilePath, $translationStatus, $config['verbose_output'], $config['include_completed_languages']); +} + +exit($totalTranslationMismatches > 0 ? 1 : 0); + +function findTranslationFiles($originalFilePath, $localeToAnalyze) +{ + $translations = []; + + $translationsDir = dirname($originalFilePath); + $originalFileName = basename($originalFilePath); + $translationFileNamePattern = str_replace('.en.', '.*.', $originalFileName); + + $translationFiles = glob($translationsDir.'/'.$translationFileNamePattern, \GLOB_NOSORT); + sort($translationFiles); + foreach ($translationFiles as $filePath) { + $locale = extractLocaleFromFilePath($filePath); + + if (null !== $localeToAnalyze && $locale !== $localeToAnalyze) { + continue; + } + + $translations[$locale] = $filePath; + } + + return $translations; +} + +function calculateTranslationStatus($originalFilePath, $translationFilePaths) +{ + $translationStatus = []; + $allTranslationKeys = extractTranslationKeys($originalFilePath); + + foreach ($translationFilePaths as $locale => $translationPath) { + $translatedKeys = extractTranslationKeys($translationPath); + $missingKeys = array_diff_key($allTranslationKeys, $translatedKeys); + $mismatches = findTransUnitMismatches($allTranslationKeys, $translatedKeys); + + $translationStatus[$locale] = [ + 'total' => count($allTranslationKeys), + 'translated' => count($translatedKeys), + 'missingKeys' => $missingKeys, + 'mismatches' => $mismatches, + ]; + $translationStatus[$locale]['is_completed'] = isTranslationCompleted($translationStatus[$locale]); + } + + return $translationStatus; +} + +function isTranslationCompleted(array $translationStatus): bool +{ + return $translationStatus['total'] === $translationStatus['translated'] && 0 === count($translationStatus['mismatches']); +} + +function printTranslationStatus($originalFilePath, $translationStatus, $verboseOutput, $includeCompletedLanguages) +{ + printTitle($originalFilePath); + printTable($translationStatus, $verboseOutput, $includeCompletedLanguages); + echo \PHP_EOL.\PHP_EOL; +} + +function extractLocaleFromFilePath($filePath) +{ + $parts = explode('.', $filePath); + + return $parts[count($parts) - 2]; +} + +function extractTranslationKeys($filePath) +{ + $translationKeys = []; + $contents = new \SimpleXMLElement(file_get_contents($filePath)); + + foreach ($contents->file->body->{'trans-unit'} as $translationKey) { + $translationId = (string) $translationKey['id']; + $translationKey = (string) $translationKey->source; + + $translationKeys[$translationId] = $translationKey; + } + + return $translationKeys; +} + +/** + * Check whether the trans-unit id and source match with the base translation. + */ +function findTransUnitMismatches(array $baseTranslationKeys, array $translatedKeys): array +{ + $mismatches = []; + + foreach ($baseTranslationKeys as $translationId => $translationKey) { + if (!isset($translatedKeys[$translationId])) { + continue; + } + if ($translatedKeys[$translationId] !== $translationKey) { + $mismatches[$translationId] = [ + 'found' => $translatedKeys[$translationId], + 'expected' => $translationKey, + ]; + } + } + + return $mismatches; +} + +function printTitle($title) +{ + echo $title.\PHP_EOL; + echo str_repeat('=', strlen($title)).\PHP_EOL.\PHP_EOL; +} + +function printTable($translations, $verboseOutput, bool $includeCompletedLanguages) +{ + if (0 === count($translations)) { + echo 'No translations found'; + + return; + } + $longestLocaleNameLength = max(array_map('strlen', array_keys($translations))); + + foreach ($translations as $locale => $translation) { + if (!$includeCompletedLanguages && $translation['is_completed']) { + continue; + } + + if ($translation['translated'] > $translation['total']) { + textColorRed(); + } elseif (count($translation['mismatches']) > 0) { + textColorRed(); + } elseif ($translation['is_completed']) { + textColorGreen(); + } + + echo sprintf( + '| Locale: %-'.$longestLocaleNameLength.'s | Translated: %2d/%2d | Mismatches: %d |', + $locale, + $translation['translated'], + $translation['total'], + count($translation['mismatches']) + ).\PHP_EOL; + + textColorNormal(); + + $shouldBeClosed = false; + if (true === $verboseOutput && count($translation['missingKeys']) > 0) { + echo '| Missing Translations:'.\PHP_EOL; + + foreach ($translation['missingKeys'] as $id => $content) { + echo sprintf('| (id=%s) %s', $id, $content).\PHP_EOL; + } + $shouldBeClosed = true; + } + if (true === $verboseOutput && count($translation['mismatches']) > 0) { + echo '| Mismatches between trans-unit id and source:'.\PHP_EOL; + + foreach ($translation['mismatches'] as $id => $content) { + echo sprintf('| (id=%s) Expected: %s', $id, $content['expected']).\PHP_EOL; + echo sprintf('| Found: %s', $content['found']).\PHP_EOL; + } + $shouldBeClosed = true; + } + if ($shouldBeClosed) { + echo str_repeat('-', 80).\PHP_EOL; + } + } +} + +function textColorGreen() +{ + echo "\033[32m"; +} + +function textColorRed() +{ + echo "\033[31m"; +} + +function textColorNormal() +{ + echo "\033[0m"; +} diff --git a/vendor/symfony/translation/Resources/data/parents.json b/vendor/symfony/translation/Resources/data/parents.json new file mode 100644 index 0000000..288f163 --- /dev/null +++ b/vendor/symfony/translation/Resources/data/parents.json @@ -0,0 +1,138 @@ +{ + "az_Cyrl": "root", + "bs_Cyrl": "root", + "en_150": "en_001", + "en_AG": "en_001", + "en_AI": "en_001", + "en_AT": "en_150", + "en_AU": "en_001", + "en_BB": "en_001", + "en_BE": "en_150", + "en_BM": "en_001", + "en_BS": "en_001", + "en_BW": "en_001", + "en_BZ": "en_001", + "en_CC": "en_001", + "en_CH": "en_150", + "en_CK": "en_001", + "en_CM": "en_001", + "en_CX": "en_001", + "en_CY": "en_001", + "en_DE": "en_150", + "en_DG": "en_001", + "en_DK": "en_150", + "en_DM": "en_001", + "en_ER": "en_001", + "en_FI": "en_150", + "en_FJ": "en_001", + "en_FK": "en_001", + "en_FM": "en_001", + "en_GB": "en_001", + "en_GD": "en_001", + "en_GG": "en_001", + "en_GH": "en_001", + "en_GI": "en_001", + "en_GM": "en_001", + "en_GY": "en_001", + "en_HK": "en_001", + "en_IE": "en_001", + "en_IL": "en_001", + "en_IM": "en_001", + "en_IN": "en_001", + "en_IO": "en_001", + "en_JE": "en_001", + "en_JM": "en_001", + "en_KE": "en_001", + "en_KI": "en_001", + "en_KN": "en_001", + "en_KY": "en_001", + "en_LC": "en_001", + "en_LR": "en_001", + "en_LS": "en_001", + "en_MG": "en_001", + "en_MO": "en_001", + "en_MS": "en_001", + "en_MT": "en_001", + "en_MU": "en_001", + "en_MW": "en_001", + "en_MY": "en_001", + "en_NA": "en_001", + "en_NF": "en_001", + "en_NG": "en_001", + "en_NL": "en_150", + "en_NR": "en_001", + "en_NU": "en_001", + "en_NZ": "en_001", + "en_PG": "en_001", + "en_PK": "en_001", + "en_PN": "en_001", + "en_PW": "en_001", + "en_RW": "en_001", + "en_SB": "en_001", + "en_SC": "en_001", + "en_SD": "en_001", + "en_SE": "en_150", + "en_SG": "en_001", + "en_SH": "en_001", + "en_SI": "en_150", + "en_SL": "en_001", + "en_SS": "en_001", + "en_SX": "en_001", + "en_SZ": "en_001", + "en_TC": "en_001", + "en_TK": "en_001", + "en_TO": "en_001", + "en_TT": "en_001", + "en_TV": "en_001", + "en_TZ": "en_001", + "en_UG": "en_001", + "en_VC": "en_001", + "en_VG": "en_001", + "en_VU": "en_001", + "en_WS": "en_001", + "en_ZA": "en_001", + "en_ZM": "en_001", + "en_ZW": "en_001", + "es_AR": "es_419", + "es_BO": "es_419", + "es_BR": "es_419", + "es_BZ": "es_419", + "es_CL": "es_419", + "es_CO": "es_419", + "es_CR": "es_419", + "es_CU": "es_419", + "es_DO": "es_419", + "es_EC": "es_419", + "es_GT": "es_419", + "es_HN": "es_419", + "es_MX": "es_419", + "es_NI": "es_419", + "es_PA": "es_419", + "es_PE": "es_419", + "es_PR": "es_419", + "es_PY": "es_419", + "es_SV": "es_419", + "es_US": "es_419", + "es_UY": "es_419", + "es_VE": "es_419", + "ff_Adlm": "root", + "nb": "no", + "nn": "no", + "pa_Arab": "root", + "pt_AO": "pt_PT", + "pt_CH": "pt_PT", + "pt_CV": "pt_PT", + "pt_GQ": "pt_PT", + "pt_GW": "pt_PT", + "pt_LU": "pt_PT", + "pt_MO": "pt_PT", + "pt_MZ": "pt_PT", + "pt_ST": "pt_PT", + "pt_TL": "pt_PT", + "sd_Deva": "root", + "sr_Latn": "root", + "uz_Arab": "root", + "uz_Cyrl": "root", + "zh_Hant": "root", + "zh_Hant_MO": "zh_Hant_HK" +} diff --git a/vendor/symfony/translation/Resources/functions.php b/vendor/symfony/translation/Resources/functions.php new file mode 100644 index 0000000..901d2f8 --- /dev/null +++ b/vendor/symfony/translation/Resources/functions.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation; + +if (!\function_exists(t::class)) { + /** + * @author Nate Wiebe + */ + function t(string $message, array $parameters = [], string $domain = null): TranslatableMessage + { + return new TranslatableMessage($message, $parameters, $domain); + } +} diff --git a/vendor/symfony/translation/Resources/schemas/xliff-core-1.2-strict.xsd b/vendor/symfony/translation/Resources/schemas/xliff-core-1.2-strict.xsd new file mode 100644 index 0000000..dface62 --- /dev/null +++ b/vendor/symfony/translation/Resources/schemas/xliff-core-1.2-strict.xsd @@ -0,0 +1,2223 @@ + + + + + + + + + + + + + + + Values for the attribute 'context-type'. + + + + + Indicates a database content. + + + + + Indicates the content of an element within an XML document. + + + + + Indicates the name of an element within an XML document. + + + + + Indicates the line number from the sourcefile (see context-type="sourcefile") where the <source> is found. + + + + + Indicates a the number of parameters contained within the <source>. + + + + + Indicates notes pertaining to the parameters in the <source>. + + + + + Indicates the content of a record within a database. + + + + + Indicates the name of a record within a database. + + + + + Indicates the original source file in the case that multiple files are merged to form the original file from which the XLIFF file is created. This differs from the original <file> attribute in that this sourcefile is one of many that make up that file. + + + + + + + Values for the attribute 'count-type'. + + + + + Indicates the count units are items that are used X times in a certain context; example: this is a reusable text unit which is used 42 times in other texts. + + + + + Indicates the count units are translation units existing already in the same document. + + + + + Indicates a total count. + + + + + + + Values for the attribute 'ctype' when used other elements than <ph> or <x>. + + + + + Indicates a run of bolded text. + + + + + Indicates a run of text in italics. + + + + + Indicates a run of underlined text. + + + + + Indicates a run of hyper-text. + + + + + + + Values for the attribute 'ctype' when used with <ph> or <x>. + + + + + Indicates a inline image. + + + + + Indicates a page break. + + + + + Indicates a line break. + + + + + + + + + + + + Values for the attribute 'datatype'. + + + + + Indicates Active Server Page data. + + + + + Indicates C source file data. + + + + + Indicates Channel Definition Format (CDF) data. + + + + + Indicates ColdFusion data. + + + + + Indicates C++ source file data. + + + + + Indicates C-Sharp data. + + + + + Indicates strings from C, ASM, and driver files data. + + + + + Indicates comma-separated values data. + + + + + Indicates database data. + + + + + Indicates portions of document that follows data and contains metadata. + + + + + Indicates portions of document that precedes data and contains metadata. + + + + + Indicates data from standard UI file operations dialogs (e.g., Open, Save, Save As, Export, Import). + + + + + Indicates standard user input screen data. + + + + + Indicates HyperText Markup Language (HTML) data - document instance. + + + + + Indicates content within an HTML document’s <body> element. + + + + + Indicates Windows INI file data. + + + + + Indicates Interleaf data. + + + + + Indicates Java source file data (extension '.java'). + + + + + Indicates Java property resource bundle data. + + + + + Indicates Java list resource bundle data. + + + + + Indicates JavaScript source file data. + + + + + Indicates JScript source file data. + + + + + Indicates information relating to formatting. + + + + + Indicates LISP source file data. + + + + + Indicates information relating to margin formats. + + + + + Indicates a file containing menu. + + + + + Indicates numerically identified string table. + + + + + Indicates Maker Interchange Format (MIF) data. + + + + + Indicates that the datatype attribute value is a MIME Type value and is defined in the mime-type attribute. + + + + + Indicates GNU Machine Object data. + + + + + Indicates Message Librarian strings created by Novell's Message Librarian Tool. + + + + + Indicates information to be displayed at the bottom of each page of a document. + + + + + Indicates information to be displayed at the top of each page of a document. + + + + + Indicates a list of property values (e.g., settings within INI files or preferences dialog). + + + + + Indicates Pascal source file data. + + + + + Indicates Hypertext Preprocessor data. + + + + + Indicates plain text file (no formatting other than, possibly, wrapping). + + + + + Indicates GNU Portable Object file. + + + + + Indicates dynamically generated user defined document. e.g. Oracle Report, Crystal Report, etc. + + + + + Indicates Windows .NET binary resources. + + + + + Indicates Windows .NET Resources. + + + + + Indicates Rich Text Format (RTF) data. + + + + + Indicates Standard Generalized Markup Language (SGML) data - document instance. + + + + + Indicates Standard Generalized Markup Language (SGML) data - Document Type Definition (DTD). + + + + + Indicates Scalable Vector Graphic (SVG) data. + + + + + Indicates VisualBasic Script source file. + + + + + Indicates warning message. + + + + + Indicates Windows (Win32) resources (i.e. resources extracted from an RC script, a message file, or a compiled file). + + + + + Indicates Extensible HyperText Markup Language (XHTML) data - document instance. + + + + + Indicates Extensible Markup Language (XML) data - document instance. + + + + + Indicates Extensible Markup Language (XML) data - Document Type Definition (DTD). + + + + + Indicates Extensible Stylesheet Language (XSL) data. + + + + + Indicates XUL elements. + + + + + + + Values for the attribute 'mtype'. + + + + + Indicates the marked text is an abbreviation. + + + + + ISO-12620 2.1.8: A term resulting from the omission of any part of the full term while designating the same concept. + + + + + ISO-12620 2.1.8.1: An abbreviated form of a simple term resulting from the omission of some of its letters (e.g. 'adj.' for 'adjective'). + + + + + ISO-12620 2.1.8.4: An abbreviated form of a term made up of letters from the full form of a multiword term strung together into a sequence pronounced only syllabically (e.g. 'radar' for 'radio detecting and ranging'). + + + + + ISO-12620: A proper-name term, such as the name of an agency or other proper entity. + + + + + ISO-12620 2.1.18.1: A recurrent word combination characterized by cohesion in that the components of the collocation must co-occur within an utterance or series of utterances, even though they do not necessarily have to maintain immediate proximity to one another. + + + + + ISO-12620 2.1.5: A synonym for an international scientific term that is used in general discourse in a given language. + + + + + Indicates the marked text is a date and/or time. + + + + + ISO-12620 2.1.15: An expression used to represent a concept based on a statement that two mathematical expressions are, for instance, equal as identified by the equal sign (=), or assigned to one another by a similar sign. + + + + + ISO-12620 2.1.7: The complete representation of a term for which there is an abbreviated form. + + + + + ISO-12620 2.1.14: Figures, symbols or the like used to express a concept briefly, such as a mathematical or chemical formula. + + + + + ISO-12620 2.1.1: The concept designation that has been chosen to head a terminological record. + + + + + ISO-12620 2.1.8.3: An abbreviated form of a term consisting of some of the initial letters of the words making up a multiword term or the term elements making up a compound term when these letters are pronounced individually (e.g. 'BSE' for 'bovine spongiform encephalopathy'). + + + + + ISO-12620 2.1.4: A term that is part of an international scientific nomenclature as adopted by an appropriate scientific body. + + + + + ISO-12620 2.1.6: A term that has the same or nearly identical orthographic or phonemic form in many languages. + + + + + ISO-12620 2.1.16: An expression used to represent a concept based on mathematical or logical relations, such as statements of inequality, set relationships, Boolean operations, and the like. + + + + + ISO-12620 2.1.17: A unit to track object. + + + + + Indicates the marked text is a name. + + + + + ISO-12620 2.1.3: A term that represents the same or a very similar concept as another term in the same language, but for which interchangeability is limited to some contexts and inapplicable in others. + + + + + ISO-12620 2.1.17.2: A unique alphanumeric designation assigned to an object in a manufacturing system. + + + + + Indicates the marked text is a phrase. + + + + + ISO-12620 2.1.18: Any group of two or more words that form a unit, the meaning of which frequently cannot be deduced based on the combined sense of the words making up the phrase. + + + + + Indicates the marked text should not be translated. + + + + + ISO-12620 2.1.12: A form of a term resulting from an operation whereby non-Latin writing systems are converted to the Latin alphabet. + + + + + Indicates that the marked text represents a segment. + + + + + ISO-12620 2.1.18.2: A fixed, lexicalized phrase. + + + + + ISO-12620 2.1.8.2: A variant of a multiword term that includes fewer words than the full form of the term (e.g. 'Group of Twenty-four' for 'Intergovernmental Group of Twenty-four on International Monetary Affairs'). + + + + + ISO-12620 2.1.17.1: Stock keeping unit, an inventory item identified by a unique alphanumeric designation assigned to an object in an inventory control system. + + + + + ISO-12620 2.1.19: A fixed chunk of recurring text. + + + + + ISO-12620 2.1.13: A designation of a concept by letters, numerals, pictograms or any combination thereof. + + + + + ISO-12620 2.1.2: Any term that represents the same or a very similar concept as the main entry term in a term entry. + + + + + ISO-12620 2.1.18.3: Phraseological unit in a language that expresses the same semantic content as another phrase in that same language. + + + + + Indicates the marked text is a term. + + + + + ISO-12620 2.1.11: A form of a term resulting from an operation whereby the characters of one writing system are represented by characters from another writing system, taking into account the pronunciation of the characters converted. + + + + + ISO-12620 2.1.10: A form of a term resulting from an operation whereby the characters of an alphabetic writing system are represented by characters from another alphabetic writing system. + + + + + ISO-12620 2.1.8.5: An abbreviated form of a term resulting from the omission of one or more term elements or syllables (e.g. 'flu' for 'influenza'). + + + + + ISO-12620 2.1.9: One of the alternate forms of a term. + + + + + + + Values for the attribute 'restype'. + + + + + Indicates a Windows RC AUTO3STATE control. + + + + + Indicates a Windows RC AUTOCHECKBOX control. + + + + + Indicates a Windows RC AUTORADIOBUTTON control. + + + + + Indicates a Windows RC BEDIT control. + + + + + Indicates a bitmap, for example a BITMAP resource in Windows. + + + + + Indicates a button object, for example a BUTTON control Windows. + + + + + Indicates a caption, such as the caption of a dialog box. + + + + + Indicates the cell in a table, for example the content of the <td> element in HTML. + + + + + Indicates check box object, for example a CHECKBOX control in Windows. + + + + + Indicates a menu item with an associated checkbox. + + + + + Indicates a list box, but with a check-box for each item. + + + + + Indicates a color selection dialog. + + + + + Indicates a combination of edit box and listbox object, for example a COMBOBOX control in Windows. + + + + + Indicates an initialization entry of an extended combobox DLGINIT resource block. (code 0x1234). + + + + + Indicates an initialization entry of a combobox DLGINIT resource block (code 0x0403). + + + + + Indicates a UI base class element that cannot be represented by any other element. + + + + + Indicates a context menu. + + + + + Indicates a Windows RC CTEXT control. + + + + + Indicates a cursor, for example a CURSOR resource in Windows. + + + + + Indicates a date/time picker. + + + + + Indicates a Windows RC DEFPUSHBUTTON control. + + + + + Indicates a dialog box. + + + + + Indicates a Windows RC DLGINIT resource block. + + + + + Indicates an edit box object, for example an EDIT control in Windows. + + + + + Indicates a filename. + + + + + Indicates a file dialog. + + + + + Indicates a footnote. + + + + + Indicates a font name. + + + + + Indicates a footer. + + + + + Indicates a frame object. + + + + + Indicates a XUL grid element. + + + + + Indicates a groupbox object, for example a GROUPBOX control in Windows. + + + + + Indicates a header item. + + + + + Indicates a heading, such has the content of <h1>, <h2>, etc. in HTML. + + + + + Indicates a Windows RC HEDIT control. + + + + + Indicates a horizontal scrollbar. + + + + + Indicates an icon, for example an ICON resource in Windows. + + + + + Indicates a Windows RC IEDIT control. + + + + + Indicates keyword list, such as the content of the Keywords meta-data in HTML, or a K footnote in WinHelp RTF. + + + + + Indicates a label object. + + + + + Indicates a label that is also a HTML link (not necessarily a URL). + + + + + Indicates a list (a group of list-items, for example an <ol> or <ul> element in HTML). + + + + + Indicates a listbox object, for example an LISTBOX control in Windows. + + + + + Indicates an list item (an entry in a list). + + + + + Indicates a Windows RC LTEXT control. + + + + + Indicates a menu (a group of menu-items). + + + + + Indicates a toolbar containing one or more tope level menus. + + + + + Indicates a menu item (an entry in a menu). + + + + + Indicates a XUL menuseparator element. + + + + + Indicates a message, for example an entry in a MESSAGETABLE resource in Windows. + + + + + Indicates a calendar control. + + + + + Indicates an edit box beside a spin control. + + + + + Indicates a catch all for rectangular areas. + + + + + Indicates a standalone menu not necessarily associated with a menubar. + + + + + Indicates a pushbox object, for example a PUSHBOX control in Windows. + + + + + Indicates a Windows RC PUSHBUTTON control. + + + + + Indicates a radio button object. + + + + + Indicates a menuitem with associated radio button. + + + + + Indicates raw data resources for an application. + + + + + Indicates a row in a table. + + + + + Indicates a Windows RC RTEXT control. + + + + + Indicates a user navigable container used to show a portion of a document. + + + + + Indicates a generic divider object (e.g. menu group separator). + + + + + Windows accelerators, shortcuts in resource or property files. + + + + + Indicates a UI control to indicate process activity but not progress. + + + + + Indicates a splitter bar. + + + + + Indicates a Windows RC STATE3 control. + + + + + Indicates a window for providing feedback to the users, like 'read-only', etc. + + + + + Indicates a string, for example an entry in a STRINGTABLE resource in Windows. + + + + + Indicates a layers of controls with a tab to select layers. + + + + + Indicates a display and edits regular two-dimensional tables of cells. + + + + + Indicates a XUL textbox element. + + + + + Indicates a UI button that can be toggled to on or off state. + + + + + Indicates an array of controls, usually buttons. + + + + + Indicates a pop up tool tip text. + + + + + Indicates a bar with a pointer indicating a position within a certain range. + + + + + Indicates a control that displays a set of hierarchical data. + + + + + Indicates a URI (URN or URL). + + + + + Indicates a Windows RC USERBUTTON control. + + + + + Indicates a user-defined control like CONTROL control in Windows. + + + + + Indicates the text of a variable. + + + + + Indicates version information about a resource like VERSIONINFO in Windows. + + + + + Indicates a vertical scrollbar. + + + + + Indicates a graphical window. + + + + + + + Values for the attribute 'size-unit'. + + + + + Indicates a size in 8-bit bytes. + + + + + Indicates a size in Unicode characters. + + + + + Indicates a size in columns. Used for HTML text area. + + + + + Indicates a size in centimeters. + + + + + Indicates a size in dialog units, as defined in Windows resources. + + + + + Indicates a size in 'font-size' units (as defined in CSS). + + + + + Indicates a size in 'x-height' units (as defined in CSS). + + + + + Indicates a size in glyphs. A glyph is considered to be one or more combined Unicode characters that represent a single displayable text character. Sometimes referred to as a 'grapheme cluster' + + + + + Indicates a size in inches. + + + + + Indicates a size in millimeters. + + + + + Indicates a size in percentage. + + + + + Indicates a size in pixels. + + + + + Indicates a size in point. + + + + + Indicates a size in rows. Used for HTML text area. + + + + + + + Values for the attribute 'state'. + + + + + Indicates the terminating state. + + + + + Indicates only non-textual information needs adaptation. + + + + + Indicates both text and non-textual information needs adaptation. + + + + + Indicates only non-textual information needs review. + + + + + Indicates both text and non-textual information needs review. + + + + + Indicates that only the text of the item needs to be reviewed. + + + + + Indicates that the item needs to be translated. + + + + + Indicates that the item is new. For example, translation units that were not in a previous version of the document. + + + + + Indicates that changes are reviewed and approved. + + + + + Indicates that the item has been translated. + + + + + + + Values for the attribute 'state-qualifier'. + + + + + Indicates an exact match. An exact match occurs when a source text of a segment is exactly the same as the source text of a segment that was translated previously. + + + + + Indicates a fuzzy match. A fuzzy match occurs when a source text of a segment is very similar to the source text of a segment that was translated previously (e.g. when the difference is casing, a few changed words, white-space discripancy, etc.). + + + + + Indicates a match based on matching IDs (in addition to matching text). + + + + + Indicates a translation derived from a glossary. + + + + + Indicates a translation derived from existing translation. + + + + + Indicates a translation derived from machine translation. + + + + + Indicates a translation derived from a translation repository. + + + + + Indicates a translation derived from a translation memory. + + + + + Indicates the translation is suggested by machine translation. + + + + + Indicates that the item has been rejected because of incorrect grammar. + + + + + Indicates that the item has been rejected because it is incorrect. + + + + + Indicates that the item has been rejected because it is too long or too short. + + + + + Indicates that the item has been rejected because of incorrect spelling. + + + + + Indicates the translation is suggested by translation memory. + + + + + + + Values for the attribute 'unit'. + + + + + Refers to words. + + + + + Refers to pages. + + + + + Refers to <trans-unit> elements. + + + + + Refers to <bin-unit> elements. + + + + + Refers to glyphs. + + + + + Refers to <trans-unit> and/or <bin-unit> elements. + + + + + Refers to the occurrences of instances defined by the count-type value. + + + + + Refers to characters. + + + + + Refers to lines. + + + + + Refers to sentences. + + + + + Refers to paragraphs. + + + + + Refers to segments. + + + + + Refers to placeables (inline elements). + + + + + + + Values for the attribute 'priority'. + + + + + Highest priority. + + + + + High priority. + + + + + High priority, but not as important as 2. + + + + + High priority, but not as important as 3. + + + + + Medium priority, but more important than 6. + + + + + Medium priority, but less important than 5. + + + + + Low priority, but more important than 8. + + + + + Low priority, but more important than 9. + + + + + Low priority. + + + + + Lowest priority. + + + + + + + + + This value indicates that all properties can be reformatted. This value must be used alone. + + + + + This value indicates that no properties should be reformatted. This value must be used alone. + + + + + + + + + + + + + This value indicates that all information in the coord attribute can be modified. + + + + + This value indicates that the x information in the coord attribute can be modified. + + + + + This value indicates that the y information in the coord attribute can be modified. + + + + + This value indicates that the cx information in the coord attribute can be modified. + + + + + This value indicates that the cy information in the coord attribute can be modified. + + + + + This value indicates that all the information in the font attribute can be modified. + + + + + This value indicates that the name information in the font attribute can be modified. + + + + + This value indicates that the size information in the font attribute can be modified. + + + + + This value indicates that the weight information in the font attribute can be modified. + + + + + This value indicates that the information in the css-style attribute can be modified. + + + + + This value indicates that the information in the style attribute can be modified. + + + + + This value indicates that the information in the exstyle attribute can be modified. + + + + + + + + + + + + + Indicates that the context is informational in nature, specifying for example, how a term should be translated. Thus, should be displayed to anyone editing the XLIFF document. + + + + + Indicates that the context-group is used to specify where the term was found in the translatable source. Thus, it is not displayed. + + + + + Indicates that the context information should be used during translation memory lookups. Thus, it is not displayed. + + + + + + + + + Represents a translation proposal from a translation memory or other resource. + + + + + Represents a previous version of the target element. + + + + + Represents a rejected version of the target element. + + + + + Represents a translation to be used for reference purposes only, for example from a related product or a different language. + + + + + Represents a proposed translation that was used for the translation of the trans-unit, possibly modified. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Values for the attribute 'coord'. + + + + + + + + Version values: 1.0 and 1.1 are allowed for backward compatibility. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/symfony/translation/Resources/schemas/xliff-core-2.0.xsd b/vendor/symfony/translation/Resources/schemas/xliff-core-2.0.xsd new file mode 100644 index 0000000..963232f --- /dev/null +++ b/vendor/symfony/translation/Resources/schemas/xliff-core-2.0.xsd @@ -0,0 +1,411 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/symfony/translation/Resources/schemas/xml.xsd b/vendor/symfony/translation/Resources/schemas/xml.xsd new file mode 100644 index 0000000..a46162a --- /dev/null +++ b/vendor/symfony/translation/Resources/schemas/xml.xsd @@ -0,0 +1,309 @@ + + + + + + +
        +

        About the XML namespace

        + +
        +

        + + This schema document describes the XML namespace, in a form + suitable for import by other schema documents. +

        +

        + See + http://www.w3.org/XML/1998/namespace.html and + + http://www.w3.org/TR/REC-xml for information + about this namespace. +

        + +

        + Note that local names in this namespace are intended to be + defined only by the World Wide Web Consortium or its subgroups. + The names currently defined in this namespace are listed below. + They should not be used with conflicting semantics by any Working + Group, specification, or document instance. +

        +

        + See further below in this document for more information about how to refer to this schema document from your own + XSD schema documents and about the + namespace-versioning policy governing this schema document. +

        +
        +
        + +
        +
        + + + + +
        + +

        lang (as an attribute name)

        +

        + + denotes an attribute whose value + is a language code for the natural language of the content of + any element; its value is inherited. This name is reserved + by virtue of its definition in the XML specification.

        + +
        +
        +

        Notes

        +

        + Attempting to install the relevant ISO 2- and 3-letter + codes as the enumerated possible values is probably never + going to be a realistic possibility. +

        +

        + + See BCP 47 at + http://www.rfc-editor.org/rfc/bcp/bcp47.txt + and the IANA language subtag registry at + + http://www.iana.org/assignments/language-subtag-registry + for further information. +

        +

        + + The union allows for the 'un-declaration' of xml:lang with + the empty string. +

        +
        +
        +
        + + + + + + + + + + +
        + + + + + +
        + +

        space (as an attribute name)

        +

        + denotes an attribute whose + value is a keyword indicating what whitespace processing + discipline is intended for the content of the element; its + value is inherited. This name is reserved by virtue of its + definition in the XML specification.

        + +
        +
        +
        + + + + + + + +
        + + + + +
        + +

        base (as an attribute name)

        +

        + denotes an attribute whose value + provides a URI to be used as the base for interpreting any + relative URIs in the scope of the element on which it + appears; its value is inherited. This name is reserved + by virtue of its definition in the XML Base specification.

        + +

        + See http://www.w3.org/TR/xmlbase/ + for information about this attribute. +

        + +
        +
        +
        +
        + + + + +
        + +

        id (as an attribute name)

        +

        + + denotes an attribute whose value + should be interpreted as if declared to be of type ID. + This name is reserved by virtue of its definition in the + xml:id specification.

        + +

        + See http://www.w3.org/TR/xml-id/ + for information about this attribute. +

        +
        +
        +
        + +
        + + + + + + + + + + + +
        + +

        Father (in any context at all)

        + +
        +

        + denotes Jon Bosak, the chair of + the original XML Working Group. This name is reserved by + the following decision of the W3C XML Plenary and + XML Coordination groups: +

        +
        +

        + + In appreciation for his vision, leadership and + dedication the W3C XML Plenary on this 10th day of + February, 2000, reserves for Jon Bosak in perpetuity + the XML name "xml:Father". +

        +
        +
        +
        +
        +
        + + + + +
        +

        About this schema document

        + +
        +

        + This schema defines attributes and an attribute group suitable + for use by schemas wishing to allow xml:base, + xml:lang, xml:space or + xml:id attributes on elements they define. +

        + +

        + To enable this, such a schema must import this schema for + the XML namespace, e.g. as follows: +

        +
        +          <schema.. .>
        +          .. .
        +           <import namespace="http://www.w3.org/XML/1998/namespace"
        +                      schemaLocation="http://www.w3.org/2001/xml.xsd"/>
        +     
        +

        + or +

        +
        +
        +           <import namespace="http://www.w3.org/XML/1998/namespace"
        +                      schemaLocation="http://www.w3.org/2009/01/xml.xsd"/>
        +     
        +

        + Subsequently, qualified reference to any of the attributes or the + group defined below will have the desired effect, e.g. +

        +
        +          <type.. .>
        +          .. .
        +           <attributeGroup ref="xml:specialAttrs"/>
        +     
        +

        + will define a type which will schema-validate an instance element + with any of those attributes. +

        + +
        +
        +
        +
        + + + +
        +

        Versioning policy for this schema document

        + +
        +

        + In keeping with the XML Schema WG's standard versioning + policy, this schema document will persist at + + http://www.w3.org/2009/01/xml.xsd. +

        +

        + At the date of issue it can also be found at + + http://www.w3.org/2001/xml.xsd. +

        + +

        + The schema document at that URI may however change in the future, + in order to remain compatible with the latest version of XML + Schema itself, or with the XML namespace itself. In other words, + if the XML Schema or XML namespaces change, the version of this + document at + http://www.w3.org/2001/xml.xsd + + will change accordingly; the version at + + http://www.w3.org/2009/01/xml.xsd + + will not change. +

        +

        + + Previous dated (and unchanging) versions of this schema + document are at: +

        + +
        +
        +
        +
        + +
        diff --git a/vendor/symfony/translation/Test/ProviderFactoryTestCase.php b/vendor/symfony/translation/Test/ProviderFactoryTestCase.php new file mode 100644 index 0000000..6d5f4b7 --- /dev/null +++ b/vendor/symfony/translation/Test/ProviderFactoryTestCase.php @@ -0,0 +1,147 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Test; + +use PHPUnit\Framework\TestCase; +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\Translation\Dumper\XliffFileDumper; +use Symfony\Component\Translation\Exception\IncompleteDsnException; +use Symfony\Component\Translation\Exception\UnsupportedSchemeException; +use Symfony\Component\Translation\Loader\LoaderInterface; +use Symfony\Component\Translation\Provider\Dsn; +use Symfony\Component\Translation\Provider\ProviderFactoryInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * A test case to ease testing a translation provider factory. + * + * @author Mathieu Santostefano + * + * @internal + */ +abstract class ProviderFactoryTestCase extends TestCase +{ + protected $client; + protected $logger; + protected $defaultLocale; + protected $loader; + protected $xliffFileDumper; + + abstract public function createFactory(): ProviderFactoryInterface; + + /** + * @return iterable + */ + abstract public function supportsProvider(): iterable; + + /** + * @return iterable + */ + abstract public function createProvider(): iterable; + + /** + * @return iterable + */ + public function unsupportedSchemeProvider(): iterable + { + return []; + } + + /** + * @return iterable + */ + public function incompleteDsnProvider(): iterable + { + return []; + } + + /** + * @dataProvider supportsProvider + */ + public function testSupports(bool $expected, string $dsn) + { + $factory = $this->createFactory(); + + $this->assertSame($expected, $factory->supports(new Dsn($dsn))); + } + + /** + * @dataProvider createProvider + */ + public function testCreate(string $expected, string $dsn) + { + $factory = $this->createFactory(); + $provider = $factory->create(new Dsn($dsn)); + + $this->assertSame($expected, (string) $provider); + } + + /** + * @dataProvider unsupportedSchemeProvider + */ + public function testUnsupportedSchemeException(string $dsn, string $message = null) + { + $factory = $this->createFactory(); + + $dsn = new Dsn($dsn); + + $this->expectException(UnsupportedSchemeException::class); + if (null !== $message) { + $this->expectExceptionMessage($message); + } + + $factory->create($dsn); + } + + /** + * @dataProvider incompleteDsnProvider + */ + public function testIncompleteDsnException(string $dsn, string $message = null) + { + $factory = $this->createFactory(); + + $dsn = new Dsn($dsn); + + $this->expectException(IncompleteDsnException::class); + if (null !== $message) { + $this->expectExceptionMessage($message); + } + + $factory->create($dsn); + } + + protected function getClient(): HttpClientInterface + { + return $this->client ?? $this->client = new MockHttpClient(); + } + + protected function getLogger(): LoggerInterface + { + return $this->logger ?? $this->logger = $this->createMock(LoggerInterface::class); + } + + protected function getDefaultLocale(): string + { + return $this->defaultLocale ?? $this->defaultLocale = 'en'; + } + + protected function getLoader(): LoaderInterface + { + return $this->loader ?? $this->loader = $this->createMock(LoaderInterface::class); + } + + protected function getXliffFileDumper(): XliffFileDumper + { + return $this->xliffFileDumper ?? $this->xliffFileDumper = $this->createMock(XliffFileDumper::class); + } +} diff --git a/vendor/symfony/translation/Test/ProviderTestCase.php b/vendor/symfony/translation/Test/ProviderTestCase.php new file mode 100644 index 0000000..238fd96 --- /dev/null +++ b/vendor/symfony/translation/Test/ProviderTestCase.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Test; + +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\Translation\Dumper\XliffFileDumper; +use Symfony\Component\Translation\Loader\LoaderInterface; +use Symfony\Component\Translation\Provider\ProviderInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * A test case to ease testing a translation provider. + * + * @author Mathieu Santostefano + * + * @internal + */ +abstract class ProviderTestCase extends TestCase +{ + protected $client; + protected $logger; + protected $defaultLocale; + protected $loader; + protected $xliffFileDumper; + + abstract public function createProvider(HttpClientInterface $client, LoaderInterface $loader, LoggerInterface $logger, string $defaultLocale, string $endpoint): ProviderInterface; + + /** + * @return iterable + */ + abstract public function toStringProvider(): iterable; + + /** + * @dataProvider toStringProvider + */ + public function testToString(ProviderInterface $provider, string $expected) + { + $this->assertSame($expected, (string) $provider); + } + + protected function getClient(): MockHttpClient + { + return $this->client ?? $this->client = new MockHttpClient(); + } + + /** + * @return LoaderInterface&MockObject + */ + protected function getLoader(): LoaderInterface + { + return $this->loader ?? $this->loader = $this->createMock(LoaderInterface::class); + } + + /** + * @return LoaderInterface&MockObject + */ + protected function getLogger(): LoggerInterface + { + return $this->logger ?? $this->logger = $this->createMock(LoggerInterface::class); + } + + protected function getDefaultLocale(): string + { + return $this->defaultLocale ?? $this->defaultLocale = 'en'; + } + + /** + * @return LoaderInterface&MockObject + */ + protected function getXliffFileDumper(): XliffFileDumper + { + return $this->xliffFileDumper ?? $this->xliffFileDumper = $this->createMock(XliffFileDumper::class); + } +} diff --git a/vendor/symfony/translation/TranslatableMessage.php b/vendor/symfony/translation/TranslatableMessage.php new file mode 100644 index 0000000..282d289 --- /dev/null +++ b/vendor/symfony/translation/TranslatableMessage.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation; + +use Symfony\Contracts\Translation\TranslatableInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * @author Nate Wiebe + */ +class TranslatableMessage implements TranslatableInterface +{ + private $message; + private $parameters; + private $domain; + + public function __construct(string $message, array $parameters = [], string $domain = null) + { + $this->message = $message; + $this->parameters = $parameters; + $this->domain = $domain; + } + + public function __toString(): string + { + return $this->getMessage(); + } + + public function getMessage(): string + { + return $this->message; + } + + public function getParameters(): array + { + return $this->parameters; + } + + public function getDomain(): ?string + { + return $this->domain; + } + + public function trans(TranslatorInterface $translator, string $locale = null): string + { + return $translator->trans($this->getMessage(), array_map( + static function ($parameter) use ($translator, $locale) { + return $parameter instanceof TranslatableInterface ? $parameter->trans($translator, $locale) : $parameter; + }, + $this->getParameters() + ), $this->getDomain(), $locale); + } +} diff --git a/vendor/symfony/translation/Translator.php b/vendor/symfony/translation/Translator.php new file mode 100644 index 0000000..dc06260 --- /dev/null +++ b/vendor/symfony/translation/Translator.php @@ -0,0 +1,494 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation; + +use Symfony\Component\Config\ConfigCacheFactory; +use Symfony\Component\Config\ConfigCacheFactoryInterface; +use Symfony\Component\Config\ConfigCacheInterface; +use Symfony\Component\Translation\Exception\InvalidArgumentException; +use Symfony\Component\Translation\Exception\NotFoundResourceException; +use Symfony\Component\Translation\Exception\RuntimeException; +use Symfony\Component\Translation\Formatter\IntlFormatterInterface; +use Symfony\Component\Translation\Formatter\MessageFormatter; +use Symfony\Component\Translation\Formatter\MessageFormatterInterface; +use Symfony\Component\Translation\Loader\LoaderInterface; +use Symfony\Contracts\Translation\LocaleAwareInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(MessageCatalogue::class); + +/** + * @author Fabien Potencier + */ +class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleAwareInterface +{ + /** + * @var MessageCatalogueInterface[] + */ + protected $catalogues = []; + + /** + * @var string + */ + private $locale; + + /** + * @var string[] + */ + private $fallbackLocales = []; + + /** + * @var LoaderInterface[] + */ + private $loaders = []; + + /** + * @var array + */ + private $resources = []; + + /** + * @var MessageFormatterInterface + */ + private $formatter; + + /** + * @var string + */ + private $cacheDir; + + /** + * @var bool + */ + private $debug; + + private $cacheVary; + + /** + * @var ConfigCacheFactoryInterface|null + */ + private $configCacheFactory; + + /** + * @var array|null + */ + private $parentLocales; + + private $hasIntlFormatter; + + /** + * @throws InvalidArgumentException If a locale contains invalid characters + */ + public function __construct(string $locale, MessageFormatterInterface $formatter = null, string $cacheDir = null, bool $debug = false, array $cacheVary = []) + { + $this->setLocale($locale); + + if (null === $formatter) { + $formatter = new MessageFormatter(); + } + + $this->formatter = $formatter; + $this->cacheDir = $cacheDir; + $this->debug = $debug; + $this->cacheVary = $cacheVary; + $this->hasIntlFormatter = $formatter instanceof IntlFormatterInterface; + } + + public function setConfigCacheFactory(ConfigCacheFactoryInterface $configCacheFactory) + { + $this->configCacheFactory = $configCacheFactory; + } + + /** + * Adds a Loader. + * + * @param string $format The name of the loader (@see addResource()) + */ + public function addLoader(string $format, LoaderInterface $loader) + { + $this->loaders[$format] = $loader; + } + + /** + * Adds a Resource. + * + * @param string $format The name of the loader (@see addLoader()) + * @param mixed $resource The resource name + * + * @throws InvalidArgumentException If the locale contains invalid characters + */ + public function addResource(string $format, $resource, string $locale, string $domain = null) + { + if (null === $domain) { + $domain = 'messages'; + } + + $this->assertValidLocale($locale); + $locale ?: $locale = class_exists(\Locale::class) ? \Locale::getDefault() : 'en'; + + $this->resources[$locale][] = [$format, $resource, $domain]; + + if (\in_array($locale, $this->fallbackLocales)) { + $this->catalogues = []; + } else { + unset($this->catalogues[$locale]); + } + } + + /** + * {@inheritdoc} + */ + public function setLocale(string $locale) + { + $this->assertValidLocale($locale); + $this->locale = $locale; + } + + /** + * {@inheritdoc} + */ + public function getLocale() + { + return $this->locale ?: (class_exists(\Locale::class) ? \Locale::getDefault() : 'en'); + } + + /** + * Sets the fallback locales. + * + * @param string[] $locales + * + * @throws InvalidArgumentException If a locale contains invalid characters + */ + public function setFallbackLocales(array $locales) + { + // needed as the fallback locales are linked to the already loaded catalogues + $this->catalogues = []; + + foreach ($locales as $locale) { + $this->assertValidLocale($locale); + } + + $this->fallbackLocales = $this->cacheVary['fallback_locales'] = $locales; + } + + /** + * Gets the fallback locales. + * + * @internal + */ + public function getFallbackLocales(): array + { + return $this->fallbackLocales; + } + + /** + * {@inheritdoc} + */ + public function trans(?string $id, array $parameters = [], string $domain = null, string $locale = null) + { + if (null === $id || '' === $id) { + return ''; + } + + if (null === $domain) { + $domain = 'messages'; + } + + $catalogue = $this->getCatalogue($locale); + $locale = $catalogue->getLocale(); + while (!$catalogue->defines($id, $domain)) { + if ($cat = $catalogue->getFallbackCatalogue()) { + $catalogue = $cat; + $locale = $catalogue->getLocale(); + } else { + break; + } + } + + $len = \strlen(MessageCatalogue::INTL_DOMAIN_SUFFIX); + if ($this->hasIntlFormatter + && ($catalogue->defines($id, $domain.MessageCatalogue::INTL_DOMAIN_SUFFIX) + || (\strlen($domain) > $len && 0 === substr_compare($domain, MessageCatalogue::INTL_DOMAIN_SUFFIX, -$len, $len))) + ) { + return $this->formatter->formatIntl($catalogue->get($id, $domain), $locale, $parameters); + } + + return $this->formatter->format($catalogue->get($id, $domain), $locale, $parameters); + } + + /** + * {@inheritdoc} + */ + public function getCatalogue(string $locale = null) + { + if (!$locale) { + $locale = $this->getLocale(); + } else { + $this->assertValidLocale($locale); + } + + if (!isset($this->catalogues[$locale])) { + $this->loadCatalogue($locale); + } + + return $this->catalogues[$locale]; + } + + /** + * {@inheritdoc} + */ + public function getCatalogues(): array + { + return array_values($this->catalogues); + } + + /** + * Gets the loaders. + * + * @return LoaderInterface[] + */ + protected function getLoaders() + { + return $this->loaders; + } + + protected function loadCatalogue(string $locale) + { + if (null === $this->cacheDir) { + $this->initializeCatalogue($locale); + } else { + $this->initializeCacheCatalogue($locale); + } + } + + protected function initializeCatalogue(string $locale) + { + $this->assertValidLocale($locale); + + try { + $this->doLoadCatalogue($locale); + } catch (NotFoundResourceException $e) { + if (!$this->computeFallbackLocales($locale)) { + throw $e; + } + } + $this->loadFallbackCatalogues($locale); + } + + private function initializeCacheCatalogue(string $locale): void + { + if (isset($this->catalogues[$locale])) { + /* Catalogue already initialized. */ + return; + } + + $this->assertValidLocale($locale); + $cache = $this->getConfigCacheFactory()->cache($this->getCatalogueCachePath($locale), + function (ConfigCacheInterface $cache) use ($locale) { + $this->dumpCatalogue($locale, $cache); + } + ); + + if (isset($this->catalogues[$locale])) { + /* Catalogue has been initialized as it was written out to cache. */ + return; + } + + /* Read catalogue from cache. */ + $this->catalogues[$locale] = include $cache->getPath(); + } + + private function dumpCatalogue(string $locale, ConfigCacheInterface $cache): void + { + $this->initializeCatalogue($locale); + $fallbackContent = $this->getFallbackContent($this->catalogues[$locale]); + + $content = sprintf(<<getAllMessages($this->catalogues[$locale]), true), + $fallbackContent + ); + + $cache->write($content, $this->catalogues[$locale]->getResources()); + } + + private function getFallbackContent(MessageCatalogue $catalogue): string + { + $fallbackContent = ''; + $current = ''; + $replacementPattern = '/[^a-z0-9_]/i'; + $fallbackCatalogue = $catalogue->getFallbackCatalogue(); + while ($fallbackCatalogue) { + $fallback = $fallbackCatalogue->getLocale(); + $fallbackSuffix = ucfirst(preg_replace($replacementPattern, '_', $fallback)); + $currentSuffix = ucfirst(preg_replace($replacementPattern, '_', $current)); + + $fallbackContent .= sprintf(<<<'EOF' +$catalogue%s = new MessageCatalogue('%s', %s); +$catalogue%s->addFallbackCatalogue($catalogue%s); + +EOF + , + $fallbackSuffix, + $fallback, + var_export($this->getAllMessages($fallbackCatalogue), true), + $currentSuffix, + $fallbackSuffix + ); + $current = $fallbackCatalogue->getLocale(); + $fallbackCatalogue = $fallbackCatalogue->getFallbackCatalogue(); + } + + return $fallbackContent; + } + + private function getCatalogueCachePath(string $locale): string + { + return $this->cacheDir.'/catalogue.'.$locale.'.'.strtr(substr(base64_encode(hash('sha256', serialize($this->cacheVary), true)), 0, 7), '/', '_').'.php'; + } + + /** + * @internal + */ + protected function doLoadCatalogue(string $locale): void + { + $this->catalogues[$locale] = new MessageCatalogue($locale); + + if (isset($this->resources[$locale])) { + foreach ($this->resources[$locale] as $resource) { + if (!isset($this->loaders[$resource[0]])) { + if (\is_string($resource[1])) { + throw new RuntimeException(sprintf('No loader is registered for the "%s" format when loading the "%s" resource.', $resource[0], $resource[1])); + } + + throw new RuntimeException(sprintf('No loader is registered for the "%s" format.', $resource[0])); + } + $this->catalogues[$locale]->addCatalogue($this->loaders[$resource[0]]->load($resource[1], $locale, $resource[2])); + } + } + } + + private function loadFallbackCatalogues(string $locale): void + { + $current = $this->catalogues[$locale]; + + foreach ($this->computeFallbackLocales($locale) as $fallback) { + if (!isset($this->catalogues[$fallback])) { + $this->initializeCatalogue($fallback); + } + + $fallbackCatalogue = new MessageCatalogue($fallback, $this->getAllMessages($this->catalogues[$fallback])); + foreach ($this->catalogues[$fallback]->getResources() as $resource) { + $fallbackCatalogue->addResource($resource); + } + $current->addFallbackCatalogue($fallbackCatalogue); + $current = $fallbackCatalogue; + } + } + + protected function computeFallbackLocales(string $locale) + { + if (null === $this->parentLocales) { + $this->parentLocales = json_decode(file_get_contents(__DIR__.'/Resources/data/parents.json'), true); + } + + $originLocale = $locale; + $locales = []; + + while ($locale) { + $parent = $this->parentLocales[$locale] ?? null; + + if ($parent) { + $locale = 'root' !== $parent ? $parent : null; + } elseif (\function_exists('locale_parse')) { + $localeSubTags = locale_parse($locale); + $locale = null; + if (1 < \count($localeSubTags)) { + array_pop($localeSubTags); + $locale = locale_compose($localeSubTags) ?: null; + } + } elseif ($i = strrpos($locale, '_') ?: strrpos($locale, '-')) { + $locale = substr($locale, 0, $i); + } else { + $locale = null; + } + + if (null !== $locale) { + $locales[] = $locale; + } + } + + foreach ($this->fallbackLocales as $fallback) { + if ($fallback === $originLocale) { + continue; + } + + $locales[] = $fallback; + } + + return array_unique($locales); + } + + /** + * Asserts that the locale is valid, throws an Exception if not. + * + * @throws InvalidArgumentException If the locale contains invalid characters + */ + protected function assertValidLocale(string $locale) + { + if (!preg_match('/^[a-z0-9@_\\.\\-]*$/i', $locale)) { + throw new InvalidArgumentException(sprintf('Invalid "%s" locale.', $locale)); + } + } + + /** + * Provides the ConfigCache factory implementation, falling back to a + * default implementation if necessary. + */ + private function getConfigCacheFactory(): ConfigCacheFactoryInterface + { + if (!$this->configCacheFactory) { + $this->configCacheFactory = new ConfigCacheFactory($this->debug); + } + + return $this->configCacheFactory; + } + + private function getAllMessages(MessageCatalogueInterface $catalogue): array + { + $allMessages = []; + + foreach ($catalogue->all() as $domain => $messages) { + if ($intlMessages = $catalogue->all($domain.MessageCatalogue::INTL_DOMAIN_SUFFIX)) { + $allMessages[$domain.MessageCatalogue::INTL_DOMAIN_SUFFIX] = $intlMessages; + $messages = array_diff_key($messages, $intlMessages); + } + if ($messages) { + $allMessages[$domain] = $messages; + } + } + + return $allMessages; + } +} diff --git a/vendor/symfony/translation/TranslatorBag.php b/vendor/symfony/translation/TranslatorBag.php new file mode 100644 index 0000000..6d98455 --- /dev/null +++ b/vendor/symfony/translation/TranslatorBag.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation; + +use Symfony\Component\Translation\Catalogue\AbstractOperation; +use Symfony\Component\Translation\Catalogue\TargetOperation; + +final class TranslatorBag implements TranslatorBagInterface +{ + /** @var MessageCatalogue[] */ + private $catalogues = []; + + public function addCatalogue(MessageCatalogue $catalogue): void + { + if (null !== $existingCatalogue = $this->getCatalogue($catalogue->getLocale())) { + $catalogue->addCatalogue($existingCatalogue); + } + + $this->catalogues[$catalogue->getLocale()] = $catalogue; + } + + public function addBag(TranslatorBagInterface $bag): void + { + foreach ($bag->getCatalogues() as $catalogue) { + $this->addCatalogue($catalogue); + } + } + + /** + * {@inheritdoc} + */ + public function getCatalogue(string $locale = null): MessageCatalogueInterface + { + if (null === $locale || !isset($this->catalogues[$locale])) { + $this->catalogues[$locale] = new MessageCatalogue($locale); + } + + return $this->catalogues[$locale]; + } + + /** + * {@inheritdoc} + */ + public function getCatalogues(): array + { + return array_values($this->catalogues); + } + + public function diff(TranslatorBagInterface $diffBag): self + { + $diff = new self(); + + foreach ($this->catalogues as $locale => $catalogue) { + if (null === $diffCatalogue = $diffBag->getCatalogue($locale)) { + $diff->addCatalogue($catalogue); + + continue; + } + + $operation = new TargetOperation($diffCatalogue, $catalogue); + $operation->moveMessagesToIntlDomainsIfPossible(AbstractOperation::NEW_BATCH); + $newCatalogue = new MessageCatalogue($locale); + + foreach ($operation->getDomains() as $domain) { + $newCatalogue->add($operation->getNewMessages($domain), $domain); + } + + $diff->addCatalogue($newCatalogue); + } + + return $diff; + } + + public function intersect(TranslatorBagInterface $intersectBag): self + { + $diff = new self(); + + foreach ($this->catalogues as $locale => $catalogue) { + if (null === $intersectCatalogue = $intersectBag->getCatalogue($locale)) { + continue; + } + + $operation = new TargetOperation($catalogue, $intersectCatalogue); + $operation->moveMessagesToIntlDomainsIfPossible(AbstractOperation::OBSOLETE_BATCH); + $obsoleteCatalogue = new MessageCatalogue($locale); + + foreach ($operation->getDomains() as $domain) { + $obsoleteCatalogue->add($operation->getObsoleteMessages($domain), $domain); + } + + $diff->addCatalogue($obsoleteCatalogue); + } + + return $diff; + } +} diff --git a/vendor/symfony/translation/TranslatorBagInterface.php b/vendor/symfony/translation/TranslatorBagInterface.php new file mode 100644 index 0000000..4228977 --- /dev/null +++ b/vendor/symfony/translation/TranslatorBagInterface.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation; + +use Symfony\Component\Translation\Exception\InvalidArgumentException; + +/** + * TranslatorBagInterface. + * + * @method MessageCatalogueInterface[] getCatalogues() Returns all catalogues of the instance + * + * @author Abdellatif Ait boudad + */ +interface TranslatorBagInterface +{ + /** + * Gets the catalogue by locale. + * + * @param string|null $locale The locale or null to use the default + * + * @return MessageCatalogueInterface + * + * @throws InvalidArgumentException If the locale contains invalid characters + */ + public function getCatalogue(string $locale = null); +} diff --git a/vendor/symfony/translation/Util/ArrayConverter.php b/vendor/symfony/translation/Util/ArrayConverter.php new file mode 100644 index 0000000..f69c2e3 --- /dev/null +++ b/vendor/symfony/translation/Util/ArrayConverter.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Util; + +/** + * ArrayConverter generates tree like structure from a message catalogue. + * e.g. this + * 'foo.bar1' => 'test1', + * 'foo.bar2' => 'test2' + * converts to follows: + * foo: + * bar1: test1 + * bar2: test2. + * + * @author Gennady Telegin + */ +class ArrayConverter +{ + /** + * Converts linear messages array to tree-like array. + * For example this array('foo.bar' => 'value') will be converted to ['foo' => ['bar' => 'value']]. + * + * @param array $messages Linear messages array + * + * @return array + */ + public static function expandToTree(array $messages) + { + $tree = []; + + foreach ($messages as $id => $value) { + $referenceToElement = &self::getElementByPath($tree, explode('.', $id)); + + $referenceToElement = $value; + + unset($referenceToElement); + } + + return $tree; + } + + private static function &getElementByPath(array &$tree, array $parts) + { + $elem = &$tree; + $parentOfElem = null; + + foreach ($parts as $i => $part) { + if (isset($elem[$part]) && \is_string($elem[$part])) { + /* Process next case: + * 'foo': 'test1', + * 'foo.bar': 'test2' + * + * $tree['foo'] was string before we found array {bar: test2}. + * Treat new element as string too, e.g. add $tree['foo.bar'] = 'test2'; + */ + $elem = &$elem[implode('.', \array_slice($parts, $i))]; + break; + } + $parentOfElem = &$elem; + $elem = &$elem[$part]; + } + + if ($elem && \is_array($elem) && $parentOfElem) { + /* Process next case: + * 'foo.bar': 'test1' + * 'foo': 'test2' + * + * $tree['foo'] was array = {bar: 'test1'} before we found string constant `foo`. + * Cancel treating $tree['foo'] as array and cancel back it expansion, + * e.g. make it $tree['foo.bar'] = 'test1' again. + */ + self::cancelExpand($parentOfElem, $part, $elem); + } + + return $elem; + } + + private static function cancelExpand(array &$tree, string $prefix, array $node) + { + $prefix .= '.'; + + foreach ($node as $id => $value) { + if (\is_string($value)) { + $tree[$prefix.$id] = $value; + } else { + self::cancelExpand($tree, $prefix.$id, $value); + } + } + } +} diff --git a/vendor/symfony/translation/Util/XliffUtils.php b/vendor/symfony/translation/Util/XliffUtils.php new file mode 100644 index 0000000..e4373a7 --- /dev/null +++ b/vendor/symfony/translation/Util/XliffUtils.php @@ -0,0 +1,196 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Util; + +use Symfony\Component\Translation\Exception\InvalidArgumentException; +use Symfony\Component\Translation\Exception\InvalidResourceException; + +/** + * Provides some utility methods for XLIFF translation files, such as validating + * their contents according to the XSD schema. + * + * @author Fabien Potencier + */ +class XliffUtils +{ + /** + * Gets xliff file version based on the root "version" attribute. + * + * Defaults to 1.2 for backwards compatibility. + * + * @throws InvalidArgumentException + */ + public static function getVersionNumber(\DOMDocument $dom): string + { + /** @var \DOMNode $xliff */ + foreach ($dom->getElementsByTagName('xliff') as $xliff) { + $version = $xliff->attributes->getNamedItem('version'); + if ($version) { + return $version->nodeValue; + } + + $namespace = $xliff->attributes->getNamedItem('xmlns'); + if ($namespace) { + if (0 !== substr_compare('urn:oasis:names:tc:xliff:document:', $namespace->nodeValue, 0, 34)) { + throw new InvalidArgumentException(sprintf('Not a valid XLIFF namespace "%s".', $namespace)); + } + + return substr($namespace, 34); + } + } + + // Falls back to v1.2 + return '1.2'; + } + + /** + * Validates and parses the given file into a DOMDocument. + * + * @throws InvalidResourceException + */ + public static function validateSchema(\DOMDocument $dom): array + { + $xliffVersion = static::getVersionNumber($dom); + $internalErrors = libxml_use_internal_errors(true); + if ($shouldEnable = self::shouldEnableEntityLoader()) { + $disableEntities = libxml_disable_entity_loader(false); + } + try { + $isValid = @$dom->schemaValidateSource(self::getSchema($xliffVersion)); + if (!$isValid) { + return self::getXmlErrors($internalErrors); + } + } finally { + if ($shouldEnable) { + libxml_disable_entity_loader($disableEntities); + } + } + + $dom->normalizeDocument(); + + libxml_clear_errors(); + libxml_use_internal_errors($internalErrors); + + return []; + } + + private static function shouldEnableEntityLoader(): bool + { + // Version prior to 8.0 can be enabled without deprecation + if (\PHP_VERSION_ID < 80000) { + return true; + } + + static $dom, $schema; + if (null === $dom) { + $dom = new \DOMDocument(); + $dom->loadXML(''); + + $tmpfile = tempnam(sys_get_temp_dir(), 'symfony'); + register_shutdown_function(static function () use ($tmpfile) { + @unlink($tmpfile); + }); + $schema = ' + + +'; + file_put_contents($tmpfile, ' + + + +'); + } + + return !@$dom->schemaValidateSource($schema); + } + + public static function getErrorsAsString(array $xmlErrors): string + { + $errorsAsString = ''; + + foreach ($xmlErrors as $error) { + $errorsAsString .= sprintf("[%s %s] %s (in %s - line %d, column %d)\n", + \LIBXML_ERR_WARNING === $error['level'] ? 'WARNING' : 'ERROR', + $error['code'], + $error['message'], + $error['file'], + $error['line'], + $error['column'] + ); + } + + return $errorsAsString; + } + + private static function getSchema(string $xliffVersion): string + { + if ('1.2' === $xliffVersion) { + $schemaSource = file_get_contents(__DIR__.'/../Resources/schemas/xliff-core-1.2-strict.xsd'); + $xmlUri = 'http://www.w3.org/2001/xml.xsd'; + } elseif ('2.0' === $xliffVersion) { + $schemaSource = file_get_contents(__DIR__.'/../Resources/schemas/xliff-core-2.0.xsd'); + $xmlUri = 'informativeCopiesOf3rdPartySchemas/w3c/xml.xsd'; + } else { + throw new InvalidArgumentException(sprintf('No support implemented for loading XLIFF version "%s".', $xliffVersion)); + } + + return self::fixXmlLocation($schemaSource, $xmlUri); + } + + /** + * Internally changes the URI of a dependent xsd to be loaded locally. + */ + private static function fixXmlLocation(string $schemaSource, string $xmlUri): string + { + $newPath = str_replace('\\', '/', __DIR__).'/../Resources/schemas/xml.xsd'; + $parts = explode('/', $newPath); + $locationstart = 'file:///'; + if (0 === stripos($newPath, 'phar://')) { + $tmpfile = tempnam(sys_get_temp_dir(), 'symfony'); + if ($tmpfile) { + copy($newPath, $tmpfile); + $parts = explode('/', str_replace('\\', '/', $tmpfile)); + } else { + array_shift($parts); + $locationstart = 'phar:///'; + } + } + + $drive = '\\' === \DIRECTORY_SEPARATOR ? array_shift($parts).'/' : ''; + $newPath = $locationstart.$drive.implode('/', array_map('rawurlencode', $parts)); + + return str_replace($xmlUri, $newPath, $schemaSource); + } + + /** + * Returns the XML errors of the internal XML parser. + */ + private static function getXmlErrors(bool $internalErrors): array + { + $errors = []; + foreach (libxml_get_errors() as $error) { + $errors[] = [ + 'level' => \LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR', + 'code' => $error->code, + 'message' => trim($error->message), + 'file' => $error->file ?: 'n/a', + 'line' => $error->line, + 'column' => $error->column, + ]; + } + + libxml_clear_errors(); + libxml_use_internal_errors($internalErrors); + + return $errors; + } +} diff --git a/vendor/symfony/translation/Writer/TranslationWriter.php b/vendor/symfony/translation/Writer/TranslationWriter.php new file mode 100644 index 0000000..96d608f --- /dev/null +++ b/vendor/symfony/translation/Writer/TranslationWriter.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Writer; + +use Symfony\Component\Translation\Dumper\DumperInterface; +use Symfony\Component\Translation\Exception\InvalidArgumentException; +use Symfony\Component\Translation\Exception\RuntimeException; +use Symfony\Component\Translation\MessageCatalogue; + +/** + * TranslationWriter writes translation messages. + * + * @author Michel Salib + */ +class TranslationWriter implements TranslationWriterInterface +{ + /** + * @var array + */ + private $dumpers = []; + + /** + * Adds a dumper to the writer. + */ + public function addDumper(string $format, DumperInterface $dumper) + { + $this->dumpers[$format] = $dumper; + } + + /** + * Obtains the list of supported formats. + * + * @return array + */ + public function getFormats() + { + return array_keys($this->dumpers); + } + + /** + * Writes translation from the catalogue according to the selected format. + * + * @param string $format The format to use to dump the messages + * @param array $options Options that are passed to the dumper + * + * @throws InvalidArgumentException + */ + public function write(MessageCatalogue $catalogue, string $format, array $options = []) + { + if (!isset($this->dumpers[$format])) { + throw new InvalidArgumentException(sprintf('There is no dumper associated with format "%s".', $format)); + } + + // get the right dumper + $dumper = $this->dumpers[$format]; + + if (isset($options['path']) && !is_dir($options['path']) && !@mkdir($options['path'], 0777, true) && !is_dir($options['path'])) { + throw new RuntimeException(sprintf('Translation Writer was not able to create directory "%s".', $options['path'])); + } + + // save + $dumper->dump($catalogue, $options); + } +} diff --git a/vendor/symfony/translation/Writer/TranslationWriterInterface.php b/vendor/symfony/translation/Writer/TranslationWriterInterface.php new file mode 100644 index 0000000..4321309 --- /dev/null +++ b/vendor/symfony/translation/Writer/TranslationWriterInterface.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Writer; + +use Symfony\Component\Translation\Exception\InvalidArgumentException; +use Symfony\Component\Translation\MessageCatalogue; + +/** + * TranslationWriter writes translation messages. + * + * @author Michel Salib + */ +interface TranslationWriterInterface +{ + /** + * Writes translation from the catalogue according to the selected format. + * + * @param string $format The format to use to dump the messages + * @param array $options Options that are passed to the dumper + * + * @throws InvalidArgumentException + */ + public function write(MessageCatalogue $catalogue, string $format, array $options = []); +} diff --git a/vendor/symfony/translation/composer.json b/vendor/symfony/translation/composer.json new file mode 100644 index 0000000..5c9266b --- /dev/null +++ b/vendor/symfony/translation/composer.json @@ -0,0 +1,62 @@ +{ + "name": "symfony/translation", + "type": "library", + "description": "Provides tools to internationalize your application", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "^1.16", + "symfony/translation-contracts": "^2.3" + }, + "require-dev": { + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/console": "^5.4|^6.0", + "symfony/dependency-injection": "^5.0|^6.0", + "symfony/http-client-contracts": "^1.1|^2.0|^3.0", + "symfony/http-kernel": "^5.0|^6.0", + "symfony/intl": "^4.4|^5.0|^6.0", + "symfony/polyfill-intl-icu": "^1.21", + "symfony/service-contracts": "^1.1.2|^2|^3", + "symfony/yaml": "^4.4|^5.0|^6.0", + "symfony/finder": "^4.4|^5.0|^6.0", + "psr/log": "^1|^2|^3" + }, + "conflict": { + "symfony/config": "<4.4", + "symfony/dependency-injection": "<5.0", + "symfony/http-kernel": "<5.0", + "symfony/twig-bundle": "<5.0", + "symfony/yaml": "<4.4", + "symfony/console": "<5.3" + }, + "provide": { + "symfony/translation-implementation": "2.3" + }, + "suggest": { + "symfony/config": "", + "symfony/yaml": "", + "psr/log-implementation": "To use logging capability in translator" + }, + "autoload": { + "files": [ "Resources/functions.php" ], + "psr-4": { "Symfony\\Component\\Translation\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/var-dumper/CHANGELOG.md b/vendor/symfony/var-dumper/CHANGELOG.md new file mode 100644 index 0000000..94b1c17 --- /dev/null +++ b/vendor/symfony/var-dumper/CHANGELOG.md @@ -0,0 +1,53 @@ +CHANGELOG +========= + +4.4.0 +----- + + * added `VarDumperTestTrait::setUpVarDumper()` and `VarDumperTestTrait::tearDownVarDumper()` + to configure casters & flags to use in tests + * added `ImagineCaster` and infrastructure to dump images + * added the stamps of a message after it is dispatched in `TraceableMessageBus` and `MessengerDataCollector` collected data + * added `UuidCaster` + * made all casters final + * added support for the `NO_COLOR` env var (https://no-color.org/) + +4.3.0 +----- + + * added `DsCaster` to support dumping the contents of data structures from the Ds extension + +4.2.0 +----- + + * support selecting the format to use by setting the environment variable `VAR_DUMPER_FORMAT` to `html` or `cli` + +4.1.0 +----- + + * added a `ServerDumper` to send serialized Data clones to a server + * added a `ServerDumpCommand` and `DumpServer` to run a server collecting + and displaying dumps on a single place with multiple formats support + * added `CliDescriptor` and `HtmlDescriptor` descriptors for `server:dump` CLI and HTML formats support + +4.0.0 +----- + + * support for passing `\ReflectionClass` instances to the `Caster::castObject()` + method has been dropped, pass class names as strings instead + * the `Data::getRawData()` method has been removed + * the `VarDumperTestTrait::assertDumpEquals()` method expects a 3rd `$filter = 0` + argument and moves `$message = ''` argument at 4th position. + * the `VarDumperTestTrait::assertDumpMatchesFormat()` method expects a 3rd `$filter = 0` + argument and moves `$message = ''` argument at 4th position. + +3.4.0 +----- + + * added `AbstractCloner::setMinDepth()` function to ensure minimum tree depth + * deprecated `MongoCaster` + +2.7.0 +----- + + * deprecated `Cloner\Data::getLimitedClone()`. Use `withMaxDepth`, `withMaxItemsPerDepth` or `withRefHandles` instead. diff --git a/vendor/symfony/var-dumper/Caster/AmqpCaster.php b/vendor/symfony/var-dumper/Caster/AmqpCaster.php new file mode 100644 index 0000000..60045ff --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/AmqpCaster.php @@ -0,0 +1,212 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts Amqp related classes to array representation. + * + * @author Grégoire Pineau + * + * @final since Symfony 4.4 + */ +class AmqpCaster +{ + private const FLAGS = [ + \AMQP_DURABLE => 'AMQP_DURABLE', + \AMQP_PASSIVE => 'AMQP_PASSIVE', + \AMQP_EXCLUSIVE => 'AMQP_EXCLUSIVE', + \AMQP_AUTODELETE => 'AMQP_AUTODELETE', + \AMQP_INTERNAL => 'AMQP_INTERNAL', + \AMQP_NOLOCAL => 'AMQP_NOLOCAL', + \AMQP_AUTOACK => 'AMQP_AUTOACK', + \AMQP_IFEMPTY => 'AMQP_IFEMPTY', + \AMQP_IFUNUSED => 'AMQP_IFUNUSED', + \AMQP_MANDATORY => 'AMQP_MANDATORY', + \AMQP_IMMEDIATE => 'AMQP_IMMEDIATE', + \AMQP_MULTIPLE => 'AMQP_MULTIPLE', + \AMQP_NOWAIT => 'AMQP_NOWAIT', + \AMQP_REQUEUE => 'AMQP_REQUEUE', + ]; + + private const EXCHANGE_TYPES = [ + \AMQP_EX_TYPE_DIRECT => 'AMQP_EX_TYPE_DIRECT', + \AMQP_EX_TYPE_FANOUT => 'AMQP_EX_TYPE_FANOUT', + \AMQP_EX_TYPE_TOPIC => 'AMQP_EX_TYPE_TOPIC', + \AMQP_EX_TYPE_HEADERS => 'AMQP_EX_TYPE_HEADERS', + ]; + + public static function castConnection(\AMQPConnection $c, array $a, Stub $stub, $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'is_connected' => $c->isConnected(), + ]; + + // Recent version of the extension already expose private properties + if (isset($a["\x00AMQPConnection\x00login"])) { + return $a; + } + + // BC layer in the amqp lib + if (method_exists($c, 'getReadTimeout')) { + $timeout = $c->getReadTimeout(); + } else { + $timeout = $c->getTimeout(); + } + + $a += [ + $prefix.'is_connected' => $c->isConnected(), + $prefix.'login' => $c->getLogin(), + $prefix.'password' => $c->getPassword(), + $prefix.'host' => $c->getHost(), + $prefix.'vhost' => $c->getVhost(), + $prefix.'port' => $c->getPort(), + $prefix.'read_timeout' => $timeout, + ]; + + return $a; + } + + public static function castChannel(\AMQPChannel $c, array $a, Stub $stub, $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'is_connected' => $c->isConnected(), + $prefix.'channel_id' => $c->getChannelId(), + ]; + + // Recent version of the extension already expose private properties + if (isset($a["\x00AMQPChannel\x00connection"])) { + return $a; + } + + $a += [ + $prefix.'connection' => $c->getConnection(), + $prefix.'prefetch_size' => $c->getPrefetchSize(), + $prefix.'prefetch_count' => $c->getPrefetchCount(), + ]; + + return $a; + } + + public static function castQueue(\AMQPQueue $c, array $a, Stub $stub, $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'flags' => self::extractFlags($c->getFlags()), + ]; + + // Recent version of the extension already expose private properties + if (isset($a["\x00AMQPQueue\x00name"])) { + return $a; + } + + $a += [ + $prefix.'connection' => $c->getConnection(), + $prefix.'channel' => $c->getChannel(), + $prefix.'name' => $c->getName(), + $prefix.'arguments' => $c->getArguments(), + ]; + + return $a; + } + + public static function castExchange(\AMQPExchange $c, array $a, Stub $stub, $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'flags' => self::extractFlags($c->getFlags()), + ]; + + $type = isset(self::EXCHANGE_TYPES[$c->getType()]) ? new ConstStub(self::EXCHANGE_TYPES[$c->getType()], $c->getType()) : $c->getType(); + + // Recent version of the extension already expose private properties + if (isset($a["\x00AMQPExchange\x00name"])) { + $a["\x00AMQPExchange\x00type"] = $type; + + return $a; + } + + $a += [ + $prefix.'connection' => $c->getConnection(), + $prefix.'channel' => $c->getChannel(), + $prefix.'name' => $c->getName(), + $prefix.'type' => $type, + $prefix.'arguments' => $c->getArguments(), + ]; + + return $a; + } + + public static function castEnvelope(\AMQPEnvelope $c, array $a, Stub $stub, $isNested, $filter = 0) + { + $prefix = Caster::PREFIX_VIRTUAL; + + $deliveryMode = new ConstStub($c->getDeliveryMode().(2 === $c->getDeliveryMode() ? ' (persistent)' : ' (non-persistent)'), $c->getDeliveryMode()); + + // Recent version of the extension already expose private properties + if (isset($a["\x00AMQPEnvelope\x00body"])) { + $a["\0AMQPEnvelope\0delivery_mode"] = $deliveryMode; + + return $a; + } + + if (!($filter & Caster::EXCLUDE_VERBOSE)) { + $a += [$prefix.'body' => $c->getBody()]; + } + + $a += [ + $prefix.'delivery_tag' => $c->getDeliveryTag(), + $prefix.'is_redelivery' => $c->isRedelivery(), + $prefix.'exchange_name' => $c->getExchangeName(), + $prefix.'routing_key' => $c->getRoutingKey(), + $prefix.'content_type' => $c->getContentType(), + $prefix.'content_encoding' => $c->getContentEncoding(), + $prefix.'headers' => $c->getHeaders(), + $prefix.'delivery_mode' => $deliveryMode, + $prefix.'priority' => $c->getPriority(), + $prefix.'correlation_id' => $c->getCorrelationId(), + $prefix.'reply_to' => $c->getReplyTo(), + $prefix.'expiration' => $c->getExpiration(), + $prefix.'message_id' => $c->getMessageId(), + $prefix.'timestamp' => $c->getTimeStamp(), + $prefix.'type' => $c->getType(), + $prefix.'user_id' => $c->getUserId(), + $prefix.'app_id' => $c->getAppId(), + ]; + + return $a; + } + + private static function extractFlags(int $flags): ConstStub + { + $flagsArray = []; + + foreach (self::FLAGS as $value => $name) { + if ($flags & $value) { + $flagsArray[] = $name; + } + } + + if (!$flagsArray) { + $flagsArray = ['AMQP_NOPARAM']; + } + + return new ConstStub(implode('|', $flagsArray), $flags); + } +} diff --git a/vendor/symfony/var-dumper/Caster/ArgsStub.php b/vendor/symfony/var-dumper/Caster/ArgsStub.php new file mode 100644 index 0000000..f8b485b --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ArgsStub.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Represents a list of function arguments. + * + * @author Nicolas Grekas + */ +class ArgsStub extends EnumStub +{ + private static $parameters = []; + + public function __construct(array $args, string $function, ?string $class) + { + [$variadic, $params] = self::getParameters($function, $class); + + $values = []; + foreach ($args as $k => $v) { + $values[$k] = !is_scalar($v) && !$v instanceof Stub ? new CutStub($v) : $v; + } + if (null === $params) { + parent::__construct($values, false); + + return; + } + if (\count($values) < \count($params)) { + $params = \array_slice($params, 0, \count($values)); + } elseif (\count($values) > \count($params)) { + $values[] = new EnumStub(array_splice($values, \count($params)), false); + $params[] = $variadic; + } + if (['...'] === $params) { + $this->dumpKeys = false; + $this->value = $values[0]->value; + } else { + $this->value = array_combine($params, $values); + } + } + + private static function getParameters(string $function, ?string $class): array + { + if (isset(self::$parameters[$k = $class.'::'.$function])) { + return self::$parameters[$k]; + } + + try { + $r = null !== $class ? new \ReflectionMethod($class, $function) : new \ReflectionFunction($function); + } catch (\ReflectionException $e) { + return [null, null]; + } + + $variadic = '...'; + $params = []; + foreach ($r->getParameters() as $v) { + $k = '$'.$v->name; + if ($v->isPassedByReference()) { + $k = '&'.$k; + } + if ($v->isVariadic()) { + $variadic .= $k; + } else { + $params[] = $k; + } + } + + return self::$parameters[$k] = [$variadic, $params]; + } +} diff --git a/vendor/symfony/var-dumper/Caster/Caster.php b/vendor/symfony/var-dumper/Caster/Caster.php new file mode 100644 index 0000000..d35f323 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/Caster.php @@ -0,0 +1,175 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Helper for filtering out properties in casters. + * + * @author Nicolas Grekas + * + * @final + */ +class Caster +{ + public const EXCLUDE_VERBOSE = 1; + public const EXCLUDE_VIRTUAL = 2; + public const EXCLUDE_DYNAMIC = 4; + public const EXCLUDE_PUBLIC = 8; + public const EXCLUDE_PROTECTED = 16; + public const EXCLUDE_PRIVATE = 32; + public const EXCLUDE_NULL = 64; + public const EXCLUDE_EMPTY = 128; + public const EXCLUDE_NOT_IMPORTANT = 256; + public const EXCLUDE_STRICT = 512; + + public const PREFIX_VIRTUAL = "\0~\0"; + public const PREFIX_DYNAMIC = "\0+\0"; + public const PREFIX_PROTECTED = "\0*\0"; + + /** + * Casts objects to arrays and adds the dynamic property prefix. + * + * @param object $obj The object to cast + * @param bool $hasDebugInfo Whether the __debugInfo method exists on $obj or not + * + * @return array The array-cast of the object, with prefixed dynamic properties + */ + public static function castObject($obj, string $class, bool $hasDebugInfo = false, string $debugClass = null): array + { + if ($hasDebugInfo) { + try { + $debugInfo = $obj->__debugInfo(); + } catch (\Exception $e) { + // ignore failing __debugInfo() + $hasDebugInfo = false; + } + } + + $a = $obj instanceof \Closure ? [] : (array) $obj; + + if ($obj instanceof \__PHP_Incomplete_Class) { + return $a; + } + + if ($a) { + static $publicProperties = []; + $debugClass = $debugClass ?? get_debug_type($obj); + + $i = 0; + $prefixedKeys = []; + foreach ($a as $k => $v) { + if (isset($k[0]) ? "\0" !== $k[0] : \PHP_VERSION_ID >= 70200) { + if (!isset($publicProperties[$class])) { + foreach ((new \ReflectionClass($class))->getProperties(\ReflectionProperty::IS_PUBLIC) as $prop) { + $publicProperties[$class][$prop->name] = true; + } + } + if (!isset($publicProperties[$class][$k])) { + $prefixedKeys[$i] = self::PREFIX_DYNAMIC.$k; + } + } elseif ($debugClass !== $class && 1 === strpos($k, $class)) { + $prefixedKeys[$i] = "\0".$debugClass.strrchr($k, "\0"); + } + ++$i; + } + if ($prefixedKeys) { + $keys = array_keys($a); + foreach ($prefixedKeys as $i => $k) { + $keys[$i] = $k; + } + $a = array_combine($keys, $a); + } + } + + if ($hasDebugInfo && \is_array($debugInfo)) { + foreach ($debugInfo as $k => $v) { + if (!isset($k[0]) || "\0" !== $k[0]) { + if (\array_key_exists(self::PREFIX_DYNAMIC.$k, $a)) { + continue; + } + $k = self::PREFIX_VIRTUAL.$k; + } + + unset($a[$k]); + $a[$k] = $v; + } + } + + return $a; + } + + /** + * Filters out the specified properties. + * + * By default, a single match in the $filter bit field filters properties out, following an "or" logic. + * When EXCLUDE_STRICT is set, an "and" logic is applied: all bits must match for a property to be removed. + * + * @param array $a The array containing the properties to filter + * @param int $filter A bit field of Caster::EXCLUDE_* constants specifying which properties to filter out + * @param string[] $listedProperties List of properties to exclude when Caster::EXCLUDE_VERBOSE is set, and to preserve when Caster::EXCLUDE_NOT_IMPORTANT is set + * @param int &$count Set to the number of removed properties + * + * @return array The filtered array + */ + public static function filter(array $a, int $filter, array $listedProperties = [], ?int &$count = 0): array + { + $count = 0; + + foreach ($a as $k => $v) { + $type = self::EXCLUDE_STRICT & $filter; + + if (null === $v) { + $type |= self::EXCLUDE_NULL & $filter; + $type |= self::EXCLUDE_EMPTY & $filter; + } elseif (false === $v || '' === $v || '0' === $v || 0 === $v || 0.0 === $v || [] === $v) { + $type |= self::EXCLUDE_EMPTY & $filter; + } + if ((self::EXCLUDE_NOT_IMPORTANT & $filter) && !\in_array($k, $listedProperties, true)) { + $type |= self::EXCLUDE_NOT_IMPORTANT; + } + if ((self::EXCLUDE_VERBOSE & $filter) && \in_array($k, $listedProperties, true)) { + $type |= self::EXCLUDE_VERBOSE; + } + + if (!isset($k[1]) || "\0" !== $k[0]) { + $type |= self::EXCLUDE_PUBLIC & $filter; + } elseif ('~' === $k[1]) { + $type |= self::EXCLUDE_VIRTUAL & $filter; + } elseif ('+' === $k[1]) { + $type |= self::EXCLUDE_DYNAMIC & $filter; + } elseif ('*' === $k[1]) { + $type |= self::EXCLUDE_PROTECTED & $filter; + } else { + $type |= self::EXCLUDE_PRIVATE & $filter; + } + + if ((self::EXCLUDE_STRICT & $filter) ? $type === $filter : $type) { + unset($a[$k]); + ++$count; + } + } + + return $a; + } + + public static function castPhpIncompleteClass(\__PHP_Incomplete_Class $c, array $a, Stub $stub, bool $isNested): array + { + if (isset($a['__PHP_Incomplete_Class_Name'])) { + $stub->class .= '('.$a['__PHP_Incomplete_Class_Name'].')'; + unset($a['__PHP_Incomplete_Class_Name']); + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/ClassStub.php b/vendor/symfony/var-dumper/Caster/ClassStub.php new file mode 100644 index 0000000..48f8483 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ClassStub.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Represents a PHP class identifier. + * + * @author Nicolas Grekas + */ +class ClassStub extends ConstStub +{ + /** + * @param string $identifier A PHP identifier, e.g. a class, method, interface, etc. name + * @param callable $callable The callable targeted by the identifier when it is ambiguous or not a real PHP identifier + */ + public function __construct(string $identifier, $callable = null) + { + $this->value = $identifier; + + try { + if (null !== $callable) { + if ($callable instanceof \Closure) { + $r = new \ReflectionFunction($callable); + } elseif (\is_object($callable)) { + $r = [$callable, '__invoke']; + } elseif (\is_array($callable)) { + $r = $callable; + } elseif (false !== $i = strpos($callable, '::')) { + $r = [substr($callable, 0, $i), substr($callable, 2 + $i)]; + } else { + $r = new \ReflectionFunction($callable); + } + } elseif (0 < $i = strpos($identifier, '::') ?: strpos($identifier, '->')) { + $r = [substr($identifier, 0, $i), substr($identifier, 2 + $i)]; + } else { + $r = new \ReflectionClass($identifier); + } + + if (\is_array($r)) { + try { + $r = new \ReflectionMethod($r[0], $r[1]); + } catch (\ReflectionException $e) { + $r = new \ReflectionClass($r[0]); + } + } + + if (str_contains($identifier, "@anonymous\0")) { + $this->value = $identifier = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', function ($m) { + return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0]; + }, $identifier); + } + + if (null !== $callable && $r instanceof \ReflectionFunctionAbstract) { + $s = ReflectionCaster::castFunctionAbstract($r, [], new Stub(), true, Caster::EXCLUDE_VERBOSE); + $s = ReflectionCaster::getSignature($s); + + if (str_ends_with($identifier, '()')) { + $this->value = substr_replace($identifier, $s, -2); + } else { + $this->value .= $s; + } + } + } catch (\ReflectionException $e) { + return; + } finally { + if (0 < $i = strrpos($this->value, '\\')) { + $this->attr['ellipsis'] = \strlen($this->value) - $i; + $this->attr['ellipsis-type'] = 'class'; + $this->attr['ellipsis-tail'] = 1; + } + } + + if ($f = $r->getFileName()) { + $this->attr['file'] = $f; + $this->attr['line'] = $r->getStartLine(); + } + } + + public static function wrapCallable($callable) + { + if (\is_object($callable) || !\is_callable($callable)) { + return $callable; + } + + if (!\is_array($callable)) { + $callable = new static($callable, $callable); + } elseif (\is_string($callable[0])) { + $callable[0] = new static($callable[0], $callable); + } else { + $callable[1] = new static($callable[1], $callable); + } + + return $callable; + } +} diff --git a/vendor/symfony/var-dumper/Caster/ConstStub.php b/vendor/symfony/var-dumper/Caster/ConstStub.php new file mode 100644 index 0000000..8b01797 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ConstStub.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Represents a PHP constant and its value. + * + * @author Nicolas Grekas + */ +class ConstStub extends Stub +{ + public function __construct(string $name, $value = null) + { + $this->class = $name; + $this->value = 1 < \func_num_args() ? $value : $name; + } + + /** + * @return string + */ + public function __toString() + { + return (string) $this->value; + } +} diff --git a/vendor/symfony/var-dumper/Caster/CutArrayStub.php b/vendor/symfony/var-dumper/Caster/CutArrayStub.php new file mode 100644 index 0000000..0e4fb36 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/CutArrayStub.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +/** + * Represents a cut array. + * + * @author Nicolas Grekas + */ +class CutArrayStub extends CutStub +{ + public $preservedSubset; + + public function __construct(array $value, array $preservedKeys) + { + parent::__construct($value); + + $this->preservedSubset = array_intersect_key($value, array_flip($preservedKeys)); + $this->cut -= \count($this->preservedSubset); + } +} diff --git a/vendor/symfony/var-dumper/Caster/CutStub.php b/vendor/symfony/var-dumper/Caster/CutStub.php new file mode 100644 index 0000000..464c6db --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/CutStub.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Represents the main properties of a PHP variable, pre-casted by a caster. + * + * @author Nicolas Grekas + */ +class CutStub extends Stub +{ + public function __construct($value) + { + $this->value = $value; + + switch (\gettype($value)) { + case 'object': + $this->type = self::TYPE_OBJECT; + $this->class = \get_class($value); + + if ($value instanceof \Closure) { + ReflectionCaster::castClosure($value, [], $this, true, Caster::EXCLUDE_VERBOSE); + } + + $this->cut = -1; + break; + + case 'array': + $this->type = self::TYPE_ARRAY; + $this->class = self::ARRAY_ASSOC; + $this->cut = $this->value = \count($value); + break; + + case 'resource': + case 'unknown type': + case 'resource (closed)': + $this->type = self::TYPE_RESOURCE; + $this->handle = (int) $value; + if ('Unknown' === $this->class = @get_resource_type($value)) { + $this->class = 'Closed'; + } + $this->cut = -1; + break; + + case 'string': + $this->type = self::TYPE_STRING; + $this->class = preg_match('//u', $value) ? self::STRING_UTF8 : self::STRING_BINARY; + $this->cut = self::STRING_BINARY === $this->class ? \strlen($value) : mb_strlen($value, 'UTF-8'); + $this->value = ''; + break; + } + } +} diff --git a/vendor/symfony/var-dumper/Caster/DOMCaster.php b/vendor/symfony/var-dumper/Caster/DOMCaster.php new file mode 100644 index 0000000..5f2b9cd --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/DOMCaster.php @@ -0,0 +1,304 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts DOM related classes to array representation. + * + * @author Nicolas Grekas + * + * @final since Symfony 4.4 + */ +class DOMCaster +{ + private const ERROR_CODES = [ + \DOM_PHP_ERR => 'DOM_PHP_ERR', + \DOM_INDEX_SIZE_ERR => 'DOM_INDEX_SIZE_ERR', + \DOMSTRING_SIZE_ERR => 'DOMSTRING_SIZE_ERR', + \DOM_HIERARCHY_REQUEST_ERR => 'DOM_HIERARCHY_REQUEST_ERR', + \DOM_WRONG_DOCUMENT_ERR => 'DOM_WRONG_DOCUMENT_ERR', + \DOM_INVALID_CHARACTER_ERR => 'DOM_INVALID_CHARACTER_ERR', + \DOM_NO_DATA_ALLOWED_ERR => 'DOM_NO_DATA_ALLOWED_ERR', + \DOM_NO_MODIFICATION_ALLOWED_ERR => 'DOM_NO_MODIFICATION_ALLOWED_ERR', + \DOM_NOT_FOUND_ERR => 'DOM_NOT_FOUND_ERR', + \DOM_NOT_SUPPORTED_ERR => 'DOM_NOT_SUPPORTED_ERR', + \DOM_INUSE_ATTRIBUTE_ERR => 'DOM_INUSE_ATTRIBUTE_ERR', + \DOM_INVALID_STATE_ERR => 'DOM_INVALID_STATE_ERR', + \DOM_SYNTAX_ERR => 'DOM_SYNTAX_ERR', + \DOM_INVALID_MODIFICATION_ERR => 'DOM_INVALID_MODIFICATION_ERR', + \DOM_NAMESPACE_ERR => 'DOM_NAMESPACE_ERR', + \DOM_INVALID_ACCESS_ERR => 'DOM_INVALID_ACCESS_ERR', + \DOM_VALIDATION_ERR => 'DOM_VALIDATION_ERR', + ]; + + private const NODE_TYPES = [ + \XML_ELEMENT_NODE => 'XML_ELEMENT_NODE', + \XML_ATTRIBUTE_NODE => 'XML_ATTRIBUTE_NODE', + \XML_TEXT_NODE => 'XML_TEXT_NODE', + \XML_CDATA_SECTION_NODE => 'XML_CDATA_SECTION_NODE', + \XML_ENTITY_REF_NODE => 'XML_ENTITY_REF_NODE', + \XML_ENTITY_NODE => 'XML_ENTITY_NODE', + \XML_PI_NODE => 'XML_PI_NODE', + \XML_COMMENT_NODE => 'XML_COMMENT_NODE', + \XML_DOCUMENT_NODE => 'XML_DOCUMENT_NODE', + \XML_DOCUMENT_TYPE_NODE => 'XML_DOCUMENT_TYPE_NODE', + \XML_DOCUMENT_FRAG_NODE => 'XML_DOCUMENT_FRAG_NODE', + \XML_NOTATION_NODE => 'XML_NOTATION_NODE', + \XML_HTML_DOCUMENT_NODE => 'XML_HTML_DOCUMENT_NODE', + \XML_DTD_NODE => 'XML_DTD_NODE', + \XML_ELEMENT_DECL_NODE => 'XML_ELEMENT_DECL_NODE', + \XML_ATTRIBUTE_DECL_NODE => 'XML_ATTRIBUTE_DECL_NODE', + \XML_ENTITY_DECL_NODE => 'XML_ENTITY_DECL_NODE', + \XML_NAMESPACE_DECL_NODE => 'XML_NAMESPACE_DECL_NODE', + ]; + + public static function castException(\DOMException $e, array $a, Stub $stub, $isNested) + { + $k = Caster::PREFIX_PROTECTED.'code'; + if (isset($a[$k], self::ERROR_CODES[$a[$k]])) { + $a[$k] = new ConstStub(self::ERROR_CODES[$a[$k]], $a[$k]); + } + + return $a; + } + + public static function castLength($dom, array $a, Stub $stub, $isNested) + { + $a += [ + 'length' => $dom->length, + ]; + + return $a; + } + + public static function castImplementation($dom, array $a, Stub $stub, $isNested) + { + $a += [ + Caster::PREFIX_VIRTUAL.'Core' => '1.0', + Caster::PREFIX_VIRTUAL.'XML' => '2.0', + ]; + + return $a; + } + + public static function castNode(\DOMNode $dom, array $a, Stub $stub, $isNested) + { + $a += [ + 'nodeName' => $dom->nodeName, + 'nodeValue' => new CutStub($dom->nodeValue), + 'nodeType' => new ConstStub(self::NODE_TYPES[$dom->nodeType], $dom->nodeType), + 'parentNode' => new CutStub($dom->parentNode), + 'childNodes' => $dom->childNodes, + 'firstChild' => new CutStub($dom->firstChild), + 'lastChild' => new CutStub($dom->lastChild), + 'previousSibling' => new CutStub($dom->previousSibling), + 'nextSibling' => new CutStub($dom->nextSibling), + 'attributes' => $dom->attributes, + 'ownerDocument' => new CutStub($dom->ownerDocument), + 'namespaceURI' => $dom->namespaceURI, + 'prefix' => $dom->prefix, + 'localName' => $dom->localName, + 'baseURI' => $dom->baseURI ? new LinkStub($dom->baseURI) : $dom->baseURI, + 'textContent' => new CutStub($dom->textContent), + ]; + + return $a; + } + + public static function castNameSpaceNode(\DOMNameSpaceNode $dom, array $a, Stub $stub, $isNested) + { + $a += [ + 'nodeName' => $dom->nodeName, + 'nodeValue' => new CutStub($dom->nodeValue), + 'nodeType' => new ConstStub(self::NODE_TYPES[$dom->nodeType], $dom->nodeType), + 'prefix' => $dom->prefix, + 'localName' => $dom->localName, + 'namespaceURI' => $dom->namespaceURI, + 'ownerDocument' => new CutStub($dom->ownerDocument), + 'parentNode' => new CutStub($dom->parentNode), + ]; + + return $a; + } + + public static function castDocument(\DOMDocument $dom, array $a, Stub $stub, $isNested, $filter = 0) + { + $a += [ + 'doctype' => $dom->doctype, + 'implementation' => $dom->implementation, + 'documentElement' => new CutStub($dom->documentElement), + 'actualEncoding' => $dom->actualEncoding, + 'encoding' => $dom->encoding, + 'xmlEncoding' => $dom->xmlEncoding, + 'standalone' => $dom->standalone, + 'xmlStandalone' => $dom->xmlStandalone, + 'version' => $dom->version, + 'xmlVersion' => $dom->xmlVersion, + 'strictErrorChecking' => $dom->strictErrorChecking, + 'documentURI' => $dom->documentURI ? new LinkStub($dom->documentURI) : $dom->documentURI, + 'config' => $dom->config, + 'formatOutput' => $dom->formatOutput, + 'validateOnParse' => $dom->validateOnParse, + 'resolveExternals' => $dom->resolveExternals, + 'preserveWhiteSpace' => $dom->preserveWhiteSpace, + 'recover' => $dom->recover, + 'substituteEntities' => $dom->substituteEntities, + ]; + + if (!($filter & Caster::EXCLUDE_VERBOSE)) { + $formatOutput = $dom->formatOutput; + $dom->formatOutput = true; + $a += [Caster::PREFIX_VIRTUAL.'xml' => $dom->saveXML()]; + $dom->formatOutput = $formatOutput; + } + + return $a; + } + + public static function castCharacterData(\DOMCharacterData $dom, array $a, Stub $stub, $isNested) + { + $a += [ + 'data' => $dom->data, + 'length' => $dom->length, + ]; + + return $a; + } + + public static function castAttr(\DOMAttr $dom, array $a, Stub $stub, $isNested) + { + $a += [ + 'name' => $dom->name, + 'specified' => $dom->specified, + 'value' => $dom->value, + 'ownerElement' => $dom->ownerElement, + 'schemaTypeInfo' => $dom->schemaTypeInfo, + ]; + + return $a; + } + + public static function castElement(\DOMElement $dom, array $a, Stub $stub, $isNested) + { + $a += [ + 'tagName' => $dom->tagName, + 'schemaTypeInfo' => $dom->schemaTypeInfo, + ]; + + return $a; + } + + public static function castText(\DOMText $dom, array $a, Stub $stub, $isNested) + { + $a += [ + 'wholeText' => $dom->wholeText, + ]; + + return $a; + } + + public static function castTypeinfo(\DOMTypeinfo $dom, array $a, Stub $stub, $isNested) + { + $a += [ + 'typeName' => $dom->typeName, + 'typeNamespace' => $dom->typeNamespace, + ]; + + return $a; + } + + public static function castDomError(\DOMDomError $dom, array $a, Stub $stub, $isNested) + { + $a += [ + 'severity' => $dom->severity, + 'message' => $dom->message, + 'type' => $dom->type, + 'relatedException' => $dom->relatedException, + 'related_data' => $dom->related_data, + 'location' => $dom->location, + ]; + + return $a; + } + + public static function castLocator(\DOMLocator $dom, array $a, Stub $stub, $isNested) + { + $a += [ + 'lineNumber' => $dom->lineNumber, + 'columnNumber' => $dom->columnNumber, + 'offset' => $dom->offset, + 'relatedNode' => $dom->relatedNode, + 'uri' => $dom->uri ? new LinkStub($dom->uri, $dom->lineNumber) : $dom->uri, + ]; + + return $a; + } + + public static function castDocumentType(\DOMDocumentType $dom, array $a, Stub $stub, $isNested) + { + $a += [ + 'name' => $dom->name, + 'entities' => $dom->entities, + 'notations' => $dom->notations, + 'publicId' => $dom->publicId, + 'systemId' => $dom->systemId, + 'internalSubset' => $dom->internalSubset, + ]; + + return $a; + } + + public static function castNotation(\DOMNotation $dom, array $a, Stub $stub, $isNested) + { + $a += [ + 'publicId' => $dom->publicId, + 'systemId' => $dom->systemId, + ]; + + return $a; + } + + public static function castEntity(\DOMEntity $dom, array $a, Stub $stub, $isNested) + { + $a += [ + 'publicId' => $dom->publicId, + 'systemId' => $dom->systemId, + 'notationName' => $dom->notationName, + 'actualEncoding' => $dom->actualEncoding, + 'encoding' => $dom->encoding, + 'version' => $dom->version, + ]; + + return $a; + } + + public static function castProcessingInstruction(\DOMProcessingInstruction $dom, array $a, Stub $stub, $isNested) + { + $a += [ + 'target' => $dom->target, + 'data' => $dom->data, + ]; + + return $a; + } + + public static function castXPath(\DOMXPath $dom, array $a, Stub $stub, $isNested) + { + $a += [ + 'document' => $dom->document, + ]; + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/DateCaster.php b/vendor/symfony/var-dumper/Caster/DateCaster.php new file mode 100644 index 0000000..1b195f4 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/DateCaster.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts DateTimeInterface related classes to array representation. + * + * @author Dany Maillard + * + * @final since Symfony 4.4 + */ +class DateCaster +{ + private const PERIOD_LIMIT = 3; + + public static function castDateTime(\DateTimeInterface $d, array $a, Stub $stub, $isNested, $filter) + { + $prefix = Caster::PREFIX_VIRTUAL; + $location = $d->getTimezone()->getLocation(); + $fromNow = (new \DateTime())->diff($d); + + $title = $d->format('l, F j, Y') + ."\n".self::formatInterval($fromNow).' from now' + .($location ? ($d->format('I') ? "\nDST On" : "\nDST Off") : '') + ; + + unset( + $a[Caster::PREFIX_DYNAMIC.'date'], + $a[Caster::PREFIX_DYNAMIC.'timezone'], + $a[Caster::PREFIX_DYNAMIC.'timezone_type'] + ); + $a[$prefix.'date'] = new ConstStub(self::formatDateTime($d, $location ? ' e (P)' : ' P'), $title); + + $stub->class .= $d->format(' @U'); + + return $a; + } + + public static function castInterval(\DateInterval $interval, array $a, Stub $stub, $isNested, $filter) + { + $now = new \DateTimeImmutable('@0', new \DateTimeZone('UTC')); + $numberOfSeconds = $now->add($interval)->getTimestamp() - $now->getTimestamp(); + $title = number_format($numberOfSeconds, 0, '.', ' ').'s'; + + $i = [Caster::PREFIX_VIRTUAL.'interval' => new ConstStub(self::formatInterval($interval), $title)]; + + return $filter & Caster::EXCLUDE_VERBOSE ? $i : $i + $a; + } + + private static function formatInterval(\DateInterval $i): string + { + $format = '%R '; + + if (0 === $i->y && 0 === $i->m && ($i->h >= 24 || $i->i >= 60 || $i->s >= 60)) { + $d = new \DateTimeImmutable('@0', new \DateTimeZone('UTC')); + $i = $d->diff($d->add($i)); // recalculate carry over points + $format .= 0 < $i->days ? '%ad ' : ''; + } else { + $format .= ($i->y ? '%yy ' : '').($i->m ? '%mm ' : '').($i->d ? '%dd ' : ''); + } + + $format .= $i->h || $i->i || $i->s || $i->f ? '%H:%I:'.self::formatSeconds($i->s, substr($i->f, 2)) : ''; + $format = '%R ' === $format ? '0s' : $format; + + return $i->format(rtrim($format)); + } + + public static function castTimeZone(\DateTimeZone $timeZone, array $a, Stub $stub, $isNested, $filter) + { + $location = $timeZone->getLocation(); + $formatted = (new \DateTime('now', $timeZone))->format($location ? 'e (P)' : 'P'); + $title = $location && \extension_loaded('intl') ? \Locale::getDisplayRegion('-'.$location['country_code']) : ''; + + $z = [Caster::PREFIX_VIRTUAL.'timezone' => new ConstStub($formatted, $title)]; + + return $filter & Caster::EXCLUDE_VERBOSE ? $z : $z + $a; + } + + public static function castPeriod(\DatePeriod $p, array $a, Stub $stub, $isNested, $filter) + { + $dates = []; + if (\PHP_VERSION_ID >= 70107) { // see https://bugs.php.net/74639 + foreach (clone $p as $i => $d) { + if (self::PERIOD_LIMIT === $i) { + $now = new \DateTimeImmutable('now', new \DateTimeZone('UTC')); + $dates[] = sprintf('%s more', ($end = $p->getEndDate()) + ? ceil(($end->format('U.u') - $d->format('U.u')) / ((int) $now->add($p->getDateInterval())->format('U.u') - (int) $now->format('U.u'))) + : $p->recurrences - $i + ); + break; + } + $dates[] = sprintf('%s) %s', $i + 1, self::formatDateTime($d)); + } + } + + $period = sprintf( + 'every %s, from %s (%s) %s', + self::formatInterval($p->getDateInterval()), + self::formatDateTime($p->getStartDate()), + $p->include_start_date ? 'included' : 'excluded', + ($end = $p->getEndDate()) ? 'to '.self::formatDateTime($end) : 'recurring '.$p->recurrences.' time/s' + ); + + $p = [Caster::PREFIX_VIRTUAL.'period' => new ConstStub($period, implode("\n", $dates))]; + + return $filter & Caster::EXCLUDE_VERBOSE ? $p : $p + $a; + } + + private static function formatDateTime(\DateTimeInterface $d, string $extra = ''): string + { + return $d->format('Y-m-d H:i:'.self::formatSeconds($d->format('s'), $d->format('u')).$extra); + } + + private static function formatSeconds(string $s, string $us): string + { + return sprintf('%02d.%s', $s, 0 === ($len = \strlen($t = rtrim($us, '0'))) ? '0' : ($len <= 3 ? str_pad($t, 3, '0') : $us)); + } +} diff --git a/vendor/symfony/var-dumper/Caster/DoctrineCaster.php b/vendor/symfony/var-dumper/Caster/DoctrineCaster.php new file mode 100644 index 0000000..7409508 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/DoctrineCaster.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Doctrine\Common\Proxy\Proxy as CommonProxy; +use Doctrine\ORM\PersistentCollection; +use Doctrine\ORM\Proxy\Proxy as OrmProxy; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts Doctrine related classes to array representation. + * + * @author Nicolas Grekas + * + * @final since Symfony 4.4 + */ +class DoctrineCaster +{ + public static function castCommonProxy(CommonProxy $proxy, array $a, Stub $stub, $isNested) + { + foreach (['__cloner__', '__initializer__'] as $k) { + if (\array_key_exists($k, $a)) { + unset($a[$k]); + ++$stub->cut; + } + } + + return $a; + } + + public static function castOrmProxy(OrmProxy $proxy, array $a, Stub $stub, $isNested) + { + foreach (['_entityPersister', '_identifier'] as $k) { + if (\array_key_exists($k = "\0Doctrine\\ORM\\Proxy\\Proxy\0".$k, $a)) { + unset($a[$k]); + ++$stub->cut; + } + } + + return $a; + } + + public static function castPersistentCollection(PersistentCollection $coll, array $a, Stub $stub, $isNested) + { + foreach (['snapshot', 'association', 'typeClass'] as $k) { + if (\array_key_exists($k = "\0Doctrine\\ORM\\PersistentCollection\0".$k, $a)) { + $a[$k] = new CutStub($a[$k]); + } + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/DsCaster.php b/vendor/symfony/var-dumper/Caster/DsCaster.php new file mode 100644 index 0000000..11423c9 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/DsCaster.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Ds\Collection; +use Ds\Map; +use Ds\Pair; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts Ds extension classes to array representation. + * + * @author Jáchym Toušek + * + * @final since Symfony 4.4 + */ +class DsCaster +{ + public static function castCollection(Collection $c, array $a, Stub $stub, bool $isNested): array + { + $a[Caster::PREFIX_VIRTUAL.'count'] = $c->count(); + $a[Caster::PREFIX_VIRTUAL.'capacity'] = $c->capacity(); + + if (!$c instanceof Map) { + $a += $c->toArray(); + } + + return $a; + } + + public static function castMap(Map $c, array $a, Stub $stub, bool $isNested): array + { + foreach ($c as $k => $v) { + $a[] = new DsPairStub($k, $v); + } + + return $a; + } + + public static function castPair(Pair $c, array $a, Stub $stub, bool $isNested): array + { + foreach ($c->toArray() as $k => $v) { + $a[Caster::PREFIX_VIRTUAL.$k] = $v; + } + + return $a; + } + + public static function castPairStub(DsPairStub $c, array $a, Stub $stub, bool $isNested): array + { + if ($isNested) { + $stub->class = Pair::class; + $stub->value = null; + $stub->handle = 0; + + $a = $c->value; + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/DsPairStub.php b/vendor/symfony/var-dumper/Caster/DsPairStub.php new file mode 100644 index 0000000..a1dcc15 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/DsPairStub.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Nicolas Grekas + */ +class DsPairStub extends Stub +{ + public function __construct($key, $value) + { + $this->value = [ + Caster::PREFIX_VIRTUAL.'key' => $key, + Caster::PREFIX_VIRTUAL.'value' => $value, + ]; + } +} diff --git a/vendor/symfony/var-dumper/Caster/EnumStub.php b/vendor/symfony/var-dumper/Caster/EnumStub.php new file mode 100644 index 0000000..7a4e98a --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/EnumStub.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Represents an enumeration of values. + * + * @author Nicolas Grekas + */ +class EnumStub extends Stub +{ + public $dumpKeys = true; + + public function __construct(array $values, bool $dumpKeys = true) + { + $this->value = $values; + $this->dumpKeys = $dumpKeys; + } +} diff --git a/vendor/symfony/var-dumper/Caster/ExceptionCaster.php b/vendor/symfony/var-dumper/Caster/ExceptionCaster.php new file mode 100644 index 0000000..f2c0f96 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ExceptionCaster.php @@ -0,0 +1,388 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\ErrorHandler\Exception\SilencedErrorContext; +use Symfony\Component\VarDumper\Cloner\Stub; +use Symfony\Component\VarDumper\Exception\ThrowingCasterException; + +/** + * Casts common Exception classes to array representation. + * + * @author Nicolas Grekas + * + * @final since Symfony 4.4 + */ +class ExceptionCaster +{ + public static $srcContext = 1; + public static $traceArgs = true; + public static $errorTypes = [ + \E_DEPRECATED => 'E_DEPRECATED', + \E_USER_DEPRECATED => 'E_USER_DEPRECATED', + \E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR', + \E_ERROR => 'E_ERROR', + \E_WARNING => 'E_WARNING', + \E_PARSE => 'E_PARSE', + \E_NOTICE => 'E_NOTICE', + \E_CORE_ERROR => 'E_CORE_ERROR', + \E_CORE_WARNING => 'E_CORE_WARNING', + \E_COMPILE_ERROR => 'E_COMPILE_ERROR', + \E_COMPILE_WARNING => 'E_COMPILE_WARNING', + \E_USER_ERROR => 'E_USER_ERROR', + \E_USER_WARNING => 'E_USER_WARNING', + \E_USER_NOTICE => 'E_USER_NOTICE', + \E_STRICT => 'E_STRICT', + ]; + + private static $framesCache = []; + + public static function castError(\Error $e, array $a, Stub $stub, $isNested, $filter = 0) + { + return self::filterExceptionArray($stub->class, $a, "\0Error\0", $filter); + } + + public static function castException(\Exception $e, array $a, Stub $stub, $isNested, $filter = 0) + { + return self::filterExceptionArray($stub->class, $a, "\0Exception\0", $filter); + } + + public static function castErrorException(\ErrorException $e, array $a, Stub $stub, $isNested) + { + if (isset($a[$s = Caster::PREFIX_PROTECTED.'severity'], self::$errorTypes[$a[$s]])) { + $a[$s] = new ConstStub(self::$errorTypes[$a[$s]], $a[$s]); + } + + return $a; + } + + public static function castThrowingCasterException(ThrowingCasterException $e, array $a, Stub $stub, $isNested) + { + $trace = Caster::PREFIX_VIRTUAL.'trace'; + $prefix = Caster::PREFIX_PROTECTED; + $xPrefix = "\0Exception\0"; + + if (isset($a[$xPrefix.'previous'], $a[$trace]) && $a[$xPrefix.'previous'] instanceof \Exception) { + $b = (array) $a[$xPrefix.'previous']; + $class = get_debug_type($a[$xPrefix.'previous']); + self::traceUnshift($b[$xPrefix.'trace'], $class, $b[$prefix.'file'], $b[$prefix.'line']); + $a[$trace] = new TraceStub($b[$xPrefix.'trace'], false, 0, -\count($a[$trace]->value)); + } + + unset($a[$xPrefix.'previous'], $a[$prefix.'code'], $a[$prefix.'file'], $a[$prefix.'line']); + + return $a; + } + + public static function castSilencedErrorContext(SilencedErrorContext $e, array $a, Stub $stub, $isNested) + { + $sPrefix = "\0".SilencedErrorContext::class."\0"; + + if (!isset($a[$s = $sPrefix.'severity'])) { + return $a; + } + + if (isset(self::$errorTypes[$a[$s]])) { + $a[$s] = new ConstStub(self::$errorTypes[$a[$s]], $a[$s]); + } + + $trace = [[ + 'file' => $a[$sPrefix.'file'], + 'line' => $a[$sPrefix.'line'], + ]]; + + if (isset($a[$sPrefix.'trace'])) { + $trace = array_merge($trace, $a[$sPrefix.'trace']); + } + + unset($a[$sPrefix.'file'], $a[$sPrefix.'line'], $a[$sPrefix.'trace']); + $a[Caster::PREFIX_VIRTUAL.'trace'] = new TraceStub($trace, self::$traceArgs); + + return $a; + } + + public static function castTraceStub(TraceStub $trace, array $a, Stub $stub, $isNested) + { + if (!$isNested) { + return $a; + } + $stub->class = ''; + $stub->handle = 0; + $frames = $trace->value; + $prefix = Caster::PREFIX_VIRTUAL; + + $a = []; + $j = \count($frames); + if (0 > $i = $trace->sliceOffset) { + $i = max(0, $j + $i); + } + if (!isset($trace->value[$i])) { + return []; + } + $lastCall = isset($frames[$i]['function']) ? (isset($frames[$i]['class']) ? $frames[0]['class'].$frames[$i]['type'] : '').$frames[$i]['function'].'()' : ''; + $frames[] = ['function' => '']; + $collapse = false; + + for ($j += $trace->numberingOffset - $i++; isset($frames[$i]); ++$i, --$j) { + $f = $frames[$i]; + $call = isset($f['function']) ? (isset($f['class']) ? $f['class'].$f['type'] : '').$f['function'] : '???'; + + $frame = new FrameStub( + [ + 'object' => $f['object'] ?? null, + 'class' => $f['class'] ?? null, + 'type' => $f['type'] ?? null, + 'function' => $f['function'] ?? null, + ] + $frames[$i - 1], + false, + true + ); + $f = self::castFrameStub($frame, [], $frame, true); + if (isset($f[$prefix.'src'])) { + foreach ($f[$prefix.'src']->value as $label => $frame) { + if (str_starts_with($label, "\0~collapse=0")) { + if ($collapse) { + $label = substr_replace($label, '1', 11, 1); + } else { + $collapse = true; + } + } + $label = substr_replace($label, "title=Stack level $j.&", 2, 0); + } + $f = $frames[$i - 1]; + if ($trace->keepArgs && !empty($f['args']) && $frame instanceof EnumStub) { + $frame->value['arguments'] = new ArgsStub($f['args'], $f['function'] ?? null, $f['class'] ?? null); + } + } elseif ('???' !== $lastCall) { + $label = new ClassStub($lastCall); + if (isset($label->attr['ellipsis'])) { + $label->attr['ellipsis'] += 2; + $label = substr_replace($prefix, "ellipsis-type=class&ellipsis={$label->attr['ellipsis']}&ellipsis-tail=1&title=Stack level $j.", 2, 0).$label->value.'()'; + } else { + $label = substr_replace($prefix, "title=Stack level $j.", 2, 0).$label->value.'()'; + } + } else { + $label = substr_replace($prefix, "title=Stack level $j.", 2, 0).$lastCall; + } + $a[substr_replace($label, sprintf('separator=%s&', $frame instanceof EnumStub ? ' ' : ':'), 2, 0)] = $frame; + + $lastCall = $call; + } + if (null !== $trace->sliceLength) { + $a = \array_slice($a, 0, $trace->sliceLength, true); + } + + return $a; + } + + public static function castFrameStub(FrameStub $frame, array $a, Stub $stub, $isNested) + { + if (!$isNested) { + return $a; + } + $f = $frame->value; + $prefix = Caster::PREFIX_VIRTUAL; + + if (isset($f['file'], $f['line'])) { + $cacheKey = $f; + unset($cacheKey['object'], $cacheKey['args']); + $cacheKey[] = self::$srcContext; + $cacheKey = implode('-', $cacheKey); + + if (isset(self::$framesCache[$cacheKey])) { + $a[$prefix.'src'] = self::$framesCache[$cacheKey]; + } else { + if (preg_match('/\((\d+)\)(?:\([\da-f]{32}\))? : (?:eval\(\)\'d code|runtime-created function)$/', $f['file'], $match)) { + $f['file'] = substr($f['file'], 0, -\strlen($match[0])); + $f['line'] = (int) $match[1]; + } + $src = $f['line']; + $srcKey = $f['file']; + $ellipsis = new LinkStub($srcKey, 0); + $srcAttr = 'collapse='.(int) $ellipsis->inVendor; + $ellipsisTail = $ellipsis->attr['ellipsis-tail'] ?? 0; + $ellipsis = $ellipsis->attr['ellipsis'] ?? 0; + + if (file_exists($f['file']) && 0 <= self::$srcContext) { + if (!empty($f['class']) && (is_subclass_of($f['class'], 'Twig\Template') || is_subclass_of($f['class'], 'Twig_Template')) && method_exists($f['class'], 'getDebugInfo')) { + $template = null; + if (isset($f['object'])) { + $template = $f['object']; + } elseif ((new \ReflectionClass($f['class']))->isInstantiable()) { + $template = unserialize(sprintf('O:%d:"%s":0:{}', \strlen($f['class']), $f['class'])); + } + if (null !== $template) { + $ellipsis = 0; + $templateSrc = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getCode() : (method_exists($template, 'getSource') ? $template->getSource() : ''); + $templateInfo = $template->getDebugInfo(); + if (isset($templateInfo[$f['line']])) { + if (!method_exists($template, 'getSourceContext') || !file_exists($templatePath = $template->getSourceContext()->getPath())) { + $templatePath = null; + } + if ($templateSrc) { + $src = self::extractSource($templateSrc, $templateInfo[$f['line']], self::$srcContext, 'twig', $templatePath, $f); + $srcKey = ($templatePath ?: $template->getTemplateName()).':'.$templateInfo[$f['line']]; + } + } + } + } + if ($srcKey == $f['file']) { + $src = self::extractSource(file_get_contents($f['file']), $f['line'], self::$srcContext, 'php', $f['file'], $f); + $srcKey .= ':'.$f['line']; + if ($ellipsis) { + $ellipsis += 1 + \strlen($f['line']); + } + } + $srcAttr .= sprintf('&separator= &file=%s&line=%d', rawurlencode($f['file']), $f['line']); + } else { + $srcAttr .= '&separator=:'; + } + $srcAttr .= $ellipsis ? '&ellipsis-type=path&ellipsis='.$ellipsis.'&ellipsis-tail='.$ellipsisTail : ''; + self::$framesCache[$cacheKey] = $a[$prefix.'src'] = new EnumStub(["\0~$srcAttr\0$srcKey" => $src]); + } + } + + unset($a[$prefix.'args'], $a[$prefix.'line'], $a[$prefix.'file']); + if ($frame->inTraceStub) { + unset($a[$prefix.'class'], $a[$prefix.'type'], $a[$prefix.'function']); + } + foreach ($a as $k => $v) { + if (!$v) { + unset($a[$k]); + } + } + if ($frame->keepArgs && !empty($f['args'])) { + $a[$prefix.'arguments'] = new ArgsStub($f['args'], $f['function'], $f['class']); + } + + return $a; + } + + private static function filterExceptionArray(string $xClass, array $a, string $xPrefix, int $filter): array + { + if (isset($a[$xPrefix.'trace'])) { + $trace = $a[$xPrefix.'trace']; + unset($a[$xPrefix.'trace']); // Ensures the trace is always last + } else { + $trace = []; + } + + if (!($filter & Caster::EXCLUDE_VERBOSE) && $trace) { + if (isset($a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line'])) { + self::traceUnshift($trace, $xClass, $a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line']); + } + $a[Caster::PREFIX_VIRTUAL.'trace'] = new TraceStub($trace, self::$traceArgs); + } + if (empty($a[$xPrefix.'previous'])) { + unset($a[$xPrefix.'previous']); + } + unset($a[$xPrefix.'string'], $a[Caster::PREFIX_DYNAMIC.'xdebug_message'], $a[Caster::PREFIX_DYNAMIC.'__destructorException']); + + if (isset($a[Caster::PREFIX_PROTECTED.'message']) && str_contains($a[Caster::PREFIX_PROTECTED.'message'], "@anonymous\0")) { + $a[Caster::PREFIX_PROTECTED.'message'] = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', function ($m) { + return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0]; + }, $a[Caster::PREFIX_PROTECTED.'message']); + } + + if (isset($a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line'])) { + $a[Caster::PREFIX_PROTECTED.'file'] = new LinkStub($a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line']); + } + + return $a; + } + + private static function traceUnshift(array &$trace, ?string $class, string $file, int $line): void + { + if (isset($trace[0]['file'], $trace[0]['line']) && $trace[0]['file'] === $file && $trace[0]['line'] === $line) { + return; + } + array_unshift($trace, [ + 'function' => $class ? 'new '.$class : null, + 'file' => $file, + 'line' => $line, + ]); + } + + private static function extractSource(string $srcLines, int $line, int $srcContext, string $lang, ?string $file, array $frame): EnumStub + { + $srcLines = explode("\n", $srcLines); + $src = []; + + for ($i = $line - 1 - $srcContext; $i <= $line - 1 + $srcContext; ++$i) { + $src[] = ($srcLines[$i] ?? '')."\n"; + } + + if ($frame['function'] ?? false) { + $stub = new CutStub(new \stdClass()); + $stub->class = (isset($frame['class']) ? $frame['class'].$frame['type'] : '').$frame['function']; + $stub->type = Stub::TYPE_OBJECT; + $stub->attr['cut_hash'] = true; + $stub->attr['file'] = $frame['file']; + $stub->attr['line'] = $frame['line']; + + try { + $caller = isset($frame['class']) ? new \ReflectionMethod($frame['class'], $frame['function']) : new \ReflectionFunction($frame['function']); + $stub->class .= ReflectionCaster::getSignature(ReflectionCaster::castFunctionAbstract($caller, [], $stub, true, Caster::EXCLUDE_VERBOSE)); + + if ($f = $caller->getFileName()) { + $stub->attr['file'] = $f; + $stub->attr['line'] = $caller->getStartLine(); + } + } catch (\ReflectionException $e) { + // ignore fake class/function + } + + $srcLines = ["\0~separator=\0" => $stub]; + } else { + $stub = null; + $srcLines = []; + } + + $ltrim = 0; + do { + $pad = null; + for ($i = $srcContext << 1; $i >= 0; --$i) { + if (isset($src[$i][$ltrim]) && "\r" !== ($c = $src[$i][$ltrim]) && "\n" !== $c) { + if (null === $pad) { + $pad = $c; + } + if ((' ' !== $c && "\t" !== $c) || $pad !== $c) { + break; + } + } + } + ++$ltrim; + } while (0 > $i && null !== $pad); + + --$ltrim; + + foreach ($src as $i => $c) { + if ($ltrim) { + $c = isset($c[$ltrim]) && "\r" !== $c[$ltrim] ? substr($c, $ltrim) : ltrim($c, " \t"); + } + $c = substr($c, 0, -1); + if ($i !== $srcContext) { + $c = new ConstStub('default', $c); + } else { + $c = new ConstStub($c, $stub ? 'in '.$stub->class : ''); + if (null !== $file) { + $c->attr['file'] = $file; + $c->attr['line'] = $line; + } + } + $c->attr['lang'] = $lang; + $srcLines[sprintf("\0~separator=› &%d\0", $i + $line - $srcContext)] = $c; + } + + return new EnumStub($srcLines); + } +} diff --git a/vendor/symfony/var-dumper/Caster/FrameStub.php b/vendor/symfony/var-dumper/Caster/FrameStub.php new file mode 100644 index 0000000..8786755 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/FrameStub.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +/** + * Represents a single backtrace frame as returned by debug_backtrace() or Exception->getTrace(). + * + * @author Nicolas Grekas + */ +class FrameStub extends EnumStub +{ + public $keepArgs; + public $inTraceStub; + + public function __construct(array $frame, bool $keepArgs = true, bool $inTraceStub = false) + { + $this->value = $frame; + $this->keepArgs = $keepArgs; + $this->inTraceStub = $inTraceStub; + } +} diff --git a/vendor/symfony/var-dumper/Caster/GmpCaster.php b/vendor/symfony/var-dumper/Caster/GmpCaster.php new file mode 100644 index 0000000..2b20e15 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/GmpCaster.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts GMP objects to array representation. + * + * @author Hamza Amrouche + * @author Nicolas Grekas + * + * @final since Symfony 4.4 + */ +class GmpCaster +{ + public static function castGmp(\GMP $gmp, array $a, Stub $stub, $isNested, $filter): array + { + $a[Caster::PREFIX_VIRTUAL.'value'] = new ConstStub(gmp_strval($gmp), gmp_strval($gmp)); + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/ImagineCaster.php b/vendor/symfony/var-dumper/Caster/ImagineCaster.php new file mode 100644 index 0000000..d1289da --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ImagineCaster.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Imagine\Image\ImageInterface; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Grégoire Pineau + */ +final class ImagineCaster +{ + public static function castImage(ImageInterface $c, array $a, Stub $stub, bool $isNested): array + { + $imgData = $c->get('png'); + if (\strlen($imgData) > 1 * 1000 * 1000) { + $a += [ + Caster::PREFIX_VIRTUAL.'image' => new ConstStub($c->getSize()), + ]; + } else { + $a += [ + Caster::PREFIX_VIRTUAL.'image' => new ImgStub($imgData, 'image/png', $c->getSize()), + ]; + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/ImgStub.php b/vendor/symfony/var-dumper/Caster/ImgStub.php new file mode 100644 index 0000000..05789fe --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ImgStub.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +/** + * @author Grégoire Pineau + */ +class ImgStub extends ConstStub +{ + public function __construct(string $data, string $contentType, string $size) + { + $this->value = ''; + $this->attr['img-data'] = $data; + $this->attr['img-size'] = $size; + $this->attr['content-type'] = $contentType; + } +} diff --git a/vendor/symfony/var-dumper/Caster/IntlCaster.php b/vendor/symfony/var-dumper/Caster/IntlCaster.php new file mode 100644 index 0000000..d7099cb --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/IntlCaster.php @@ -0,0 +1,172 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Nicolas Grekas + * @author Jan Schädlich + * + * @final since Symfony 4.4 + */ +class IntlCaster +{ + public static function castMessageFormatter(\MessageFormatter $c, array $a, Stub $stub, $isNested) + { + $a += [ + Caster::PREFIX_VIRTUAL.'locale' => $c->getLocale(), + Caster::PREFIX_VIRTUAL.'pattern' => $c->getPattern(), + ]; + + return self::castError($c, $a); + } + + public static function castNumberFormatter(\NumberFormatter $c, array $a, Stub $stub, $isNested, $filter = 0) + { + $a += [ + Caster::PREFIX_VIRTUAL.'locale' => $c->getLocale(), + Caster::PREFIX_VIRTUAL.'pattern' => $c->getPattern(), + ]; + + if ($filter & Caster::EXCLUDE_VERBOSE) { + $stub->cut += 3; + + return self::castError($c, $a); + } + + $a += [ + Caster::PREFIX_VIRTUAL.'attributes' => new EnumStub( + [ + 'PARSE_INT_ONLY' => $c->getAttribute(\NumberFormatter::PARSE_INT_ONLY), + 'GROUPING_USED' => $c->getAttribute(\NumberFormatter::GROUPING_USED), + 'DECIMAL_ALWAYS_SHOWN' => $c->getAttribute(\NumberFormatter::DECIMAL_ALWAYS_SHOWN), + 'MAX_INTEGER_DIGITS' => $c->getAttribute(\NumberFormatter::MAX_INTEGER_DIGITS), + 'MIN_INTEGER_DIGITS' => $c->getAttribute(\NumberFormatter::MIN_INTEGER_DIGITS), + 'INTEGER_DIGITS' => $c->getAttribute(\NumberFormatter::INTEGER_DIGITS), + 'MAX_FRACTION_DIGITS' => $c->getAttribute(\NumberFormatter::MAX_FRACTION_DIGITS), + 'MIN_FRACTION_DIGITS' => $c->getAttribute(\NumberFormatter::MIN_FRACTION_DIGITS), + 'FRACTION_DIGITS' => $c->getAttribute(\NumberFormatter::FRACTION_DIGITS), + 'MULTIPLIER' => $c->getAttribute(\NumberFormatter::MULTIPLIER), + 'GROUPING_SIZE' => $c->getAttribute(\NumberFormatter::GROUPING_SIZE), + 'ROUNDING_MODE' => $c->getAttribute(\NumberFormatter::ROUNDING_MODE), + 'ROUNDING_INCREMENT' => $c->getAttribute(\NumberFormatter::ROUNDING_INCREMENT), + 'FORMAT_WIDTH' => $c->getAttribute(\NumberFormatter::FORMAT_WIDTH), + 'PADDING_POSITION' => $c->getAttribute(\NumberFormatter::PADDING_POSITION), + 'SECONDARY_GROUPING_SIZE' => $c->getAttribute(\NumberFormatter::SECONDARY_GROUPING_SIZE), + 'SIGNIFICANT_DIGITS_USED' => $c->getAttribute(\NumberFormatter::SIGNIFICANT_DIGITS_USED), + 'MIN_SIGNIFICANT_DIGITS' => $c->getAttribute(\NumberFormatter::MIN_SIGNIFICANT_DIGITS), + 'MAX_SIGNIFICANT_DIGITS' => $c->getAttribute(\NumberFormatter::MAX_SIGNIFICANT_DIGITS), + 'LENIENT_PARSE' => $c->getAttribute(\NumberFormatter::LENIENT_PARSE), + ] + ), + Caster::PREFIX_VIRTUAL.'text_attributes' => new EnumStub( + [ + 'POSITIVE_PREFIX' => $c->getTextAttribute(\NumberFormatter::POSITIVE_PREFIX), + 'POSITIVE_SUFFIX' => $c->getTextAttribute(\NumberFormatter::POSITIVE_SUFFIX), + 'NEGATIVE_PREFIX' => $c->getTextAttribute(\NumberFormatter::NEGATIVE_PREFIX), + 'NEGATIVE_SUFFIX' => $c->getTextAttribute(\NumberFormatter::NEGATIVE_SUFFIX), + 'PADDING_CHARACTER' => $c->getTextAttribute(\NumberFormatter::PADDING_CHARACTER), + 'CURRENCY_CODE' => $c->getTextAttribute(\NumberFormatter::CURRENCY_CODE), + 'DEFAULT_RULESET' => $c->getTextAttribute(\NumberFormatter::DEFAULT_RULESET), + 'PUBLIC_RULESETS' => $c->getTextAttribute(\NumberFormatter::PUBLIC_RULESETS), + ] + ), + Caster::PREFIX_VIRTUAL.'symbols' => new EnumStub( + [ + 'DECIMAL_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::DECIMAL_SEPARATOR_SYMBOL), + 'GROUPING_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::GROUPING_SEPARATOR_SYMBOL), + 'PATTERN_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::PATTERN_SEPARATOR_SYMBOL), + 'PERCENT_SYMBOL' => $c->getSymbol(\NumberFormatter::PERCENT_SYMBOL), + 'ZERO_DIGIT_SYMBOL' => $c->getSymbol(\NumberFormatter::ZERO_DIGIT_SYMBOL), + 'DIGIT_SYMBOL' => $c->getSymbol(\NumberFormatter::DIGIT_SYMBOL), + 'MINUS_SIGN_SYMBOL' => $c->getSymbol(\NumberFormatter::MINUS_SIGN_SYMBOL), + 'PLUS_SIGN_SYMBOL' => $c->getSymbol(\NumberFormatter::PLUS_SIGN_SYMBOL), + 'CURRENCY_SYMBOL' => $c->getSymbol(\NumberFormatter::CURRENCY_SYMBOL), + 'INTL_CURRENCY_SYMBOL' => $c->getSymbol(\NumberFormatter::INTL_CURRENCY_SYMBOL), + 'MONETARY_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::MONETARY_SEPARATOR_SYMBOL), + 'EXPONENTIAL_SYMBOL' => $c->getSymbol(\NumberFormatter::EXPONENTIAL_SYMBOL), + 'PERMILL_SYMBOL' => $c->getSymbol(\NumberFormatter::PERMILL_SYMBOL), + 'PAD_ESCAPE_SYMBOL' => $c->getSymbol(\NumberFormatter::PAD_ESCAPE_SYMBOL), + 'INFINITY_SYMBOL' => $c->getSymbol(\NumberFormatter::INFINITY_SYMBOL), + 'NAN_SYMBOL' => $c->getSymbol(\NumberFormatter::NAN_SYMBOL), + 'SIGNIFICANT_DIGIT_SYMBOL' => $c->getSymbol(\NumberFormatter::SIGNIFICANT_DIGIT_SYMBOL), + 'MONETARY_GROUPING_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL), + ] + ), + ]; + + return self::castError($c, $a); + } + + public static function castIntlTimeZone(\IntlTimeZone $c, array $a, Stub $stub, $isNested) + { + $a += [ + Caster::PREFIX_VIRTUAL.'display_name' => $c->getDisplayName(), + Caster::PREFIX_VIRTUAL.'id' => $c->getID(), + Caster::PREFIX_VIRTUAL.'raw_offset' => $c->getRawOffset(), + ]; + + if ($c->useDaylightTime()) { + $a += [ + Caster::PREFIX_VIRTUAL.'dst_savings' => $c->getDSTSavings(), + ]; + } + + return self::castError($c, $a); + } + + public static function castIntlCalendar(\IntlCalendar $c, array $a, Stub $stub, $isNested, $filter = 0) + { + $a += [ + Caster::PREFIX_VIRTUAL.'type' => $c->getType(), + Caster::PREFIX_VIRTUAL.'first_day_of_week' => $c->getFirstDayOfWeek(), + Caster::PREFIX_VIRTUAL.'minimal_days_in_first_week' => $c->getMinimalDaysInFirstWeek(), + Caster::PREFIX_VIRTUAL.'repeated_wall_time_option' => $c->getRepeatedWallTimeOption(), + Caster::PREFIX_VIRTUAL.'skipped_wall_time_option' => $c->getSkippedWallTimeOption(), + Caster::PREFIX_VIRTUAL.'time' => $c->getTime(), + Caster::PREFIX_VIRTUAL.'in_daylight_time' => $c->inDaylightTime(), + Caster::PREFIX_VIRTUAL.'is_lenient' => $c->isLenient(), + Caster::PREFIX_VIRTUAL.'time_zone' => ($filter & Caster::EXCLUDE_VERBOSE) ? new CutStub($c->getTimeZone()) : $c->getTimeZone(), + ]; + + return self::castError($c, $a); + } + + public static function castIntlDateFormatter(\IntlDateFormatter $c, array $a, Stub $stub, $isNested, $filter = 0) + { + $a += [ + Caster::PREFIX_VIRTUAL.'locale' => $c->getLocale(), + Caster::PREFIX_VIRTUAL.'pattern' => $c->getPattern(), + Caster::PREFIX_VIRTUAL.'calendar' => $c->getCalendar(), + Caster::PREFIX_VIRTUAL.'time_zone_id' => $c->getTimeZoneId(), + Caster::PREFIX_VIRTUAL.'time_type' => $c->getTimeType(), + Caster::PREFIX_VIRTUAL.'date_type' => $c->getDateType(), + Caster::PREFIX_VIRTUAL.'calendar_object' => ($filter & Caster::EXCLUDE_VERBOSE) ? new CutStub($c->getCalendarObject()) : $c->getCalendarObject(), + Caster::PREFIX_VIRTUAL.'time_zone' => ($filter & Caster::EXCLUDE_VERBOSE) ? new CutStub($c->getTimeZone()) : $c->getTimeZone(), + ]; + + return self::castError($c, $a); + } + + private static function castError($c, array $a): array + { + if ($errorCode = $c->getErrorCode()) { + $a += [ + Caster::PREFIX_VIRTUAL.'error_code' => $errorCode, + Caster::PREFIX_VIRTUAL.'error_message' => $c->getErrorMessage(), + ]; + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/LinkStub.php b/vendor/symfony/var-dumper/Caster/LinkStub.php new file mode 100644 index 0000000..c619d9d --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/LinkStub.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +/** + * Represents a file or a URL. + * + * @author Nicolas Grekas + */ +class LinkStub extends ConstStub +{ + public $inVendor = false; + + private static $vendorRoots; + private static $composerRoots; + + public function __construct(string $label, int $line = 0, string $href = null) + { + $this->value = $label; + + if (null === $href) { + $href = $label; + } + if (!\is_string($href)) { + return; + } + if (str_starts_with($href, 'file://')) { + if ($href === $label) { + $label = substr($label, 7); + } + $href = substr($href, 7); + } elseif (str_contains($href, '://')) { + $this->attr['href'] = $href; + + return; + } + if (!file_exists($href)) { + return; + } + if ($line) { + $this->attr['line'] = $line; + } + if ($label !== $this->attr['file'] = realpath($href) ?: $href) { + return; + } + if ($composerRoot = $this->getComposerRoot($href, $this->inVendor)) { + $this->attr['ellipsis'] = \strlen($href) - \strlen($composerRoot) + 1; + $this->attr['ellipsis-type'] = 'path'; + $this->attr['ellipsis-tail'] = 1 + ($this->inVendor ? 2 + \strlen(implode('', \array_slice(explode(\DIRECTORY_SEPARATOR, substr($href, 1 - $this->attr['ellipsis'])), 0, 2))) : 0); + } elseif (3 < \count($ellipsis = explode(\DIRECTORY_SEPARATOR, $href))) { + $this->attr['ellipsis'] = 2 + \strlen(implode('', \array_slice($ellipsis, -2))); + $this->attr['ellipsis-type'] = 'path'; + $this->attr['ellipsis-tail'] = 1; + } + } + + private function getComposerRoot(string $file, bool &$inVendor) + { + if (null === self::$vendorRoots) { + self::$vendorRoots = []; + + foreach (get_declared_classes() as $class) { + if ('C' === $class[0] && str_starts_with($class, 'ComposerAutoloaderInit')) { + $r = new \ReflectionClass($class); + $v = \dirname($r->getFileName(), 2); + if (file_exists($v.'/composer/installed.json')) { + self::$vendorRoots[] = $v.\DIRECTORY_SEPARATOR; + } + } + } + } + $inVendor = false; + + if (isset(self::$composerRoots[$dir = \dirname($file)])) { + return self::$composerRoots[$dir]; + } + + foreach (self::$vendorRoots as $root) { + if ($inVendor = str_starts_with($file, $root)) { + return $root; + } + } + + $parent = $dir; + while (!@file_exists($parent.'/composer.json')) { + if (!@file_exists($parent)) { + // open_basedir restriction in effect + break; + } + if ($parent === \dirname($parent)) { + return self::$composerRoots[$dir] = false; + } + + $parent = \dirname($parent); + } + + return self::$composerRoots[$dir] = $parent.\DIRECTORY_SEPARATOR; + } +} diff --git a/vendor/symfony/var-dumper/Caster/MemcachedCaster.php b/vendor/symfony/var-dumper/Caster/MemcachedCaster.php new file mode 100644 index 0000000..696cef1 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/MemcachedCaster.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Jan Schädlich + * + * @final since Symfony 4.4 + */ +class MemcachedCaster +{ + private static $optionConstants; + private static $defaultOptions; + + public static function castMemcached(\Memcached $c, array $a, Stub $stub, $isNested) + { + $a += [ + Caster::PREFIX_VIRTUAL.'servers' => $c->getServerList(), + Caster::PREFIX_VIRTUAL.'options' => new EnumStub( + self::getNonDefaultOptions($c) + ), + ]; + + return $a; + } + + private static function getNonDefaultOptions(\Memcached $c): array + { + self::$defaultOptions = self::$defaultOptions ?? self::discoverDefaultOptions(); + self::$optionConstants = self::$optionConstants ?? self::getOptionConstants(); + + $nonDefaultOptions = []; + foreach (self::$optionConstants as $constantKey => $value) { + if (self::$defaultOptions[$constantKey] !== $option = $c->getOption($value)) { + $nonDefaultOptions[$constantKey] = $option; + } + } + + return $nonDefaultOptions; + } + + private static function discoverDefaultOptions(): array + { + $defaultMemcached = new \Memcached(); + $defaultMemcached->addServer('127.0.0.1', 11211); + + $defaultOptions = []; + self::$optionConstants = self::$optionConstants ?? self::getOptionConstants(); + + foreach (self::$optionConstants as $constantKey => $value) { + $defaultOptions[$constantKey] = $defaultMemcached->getOption($value); + } + + return $defaultOptions; + } + + private static function getOptionConstants(): array + { + $reflectedMemcached = new \ReflectionClass(\Memcached::class); + + $optionConstants = []; + foreach ($reflectedMemcached->getConstants() as $constantKey => $value) { + if (str_starts_with($constantKey, 'OPT_')) { + $optionConstants[$constantKey] = $value; + } + } + + return $optionConstants; + } +} diff --git a/vendor/symfony/var-dumper/Caster/PdoCaster.php b/vendor/symfony/var-dumper/Caster/PdoCaster.php new file mode 100644 index 0000000..47b0a62 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/PdoCaster.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts PDO related classes to array representation. + * + * @author Nicolas Grekas + * + * @final since Symfony 4.4 + */ +class PdoCaster +{ + private const PDO_ATTRIBUTES = [ + 'CASE' => [ + \PDO::CASE_LOWER => 'LOWER', + \PDO::CASE_NATURAL => 'NATURAL', + \PDO::CASE_UPPER => 'UPPER', + ], + 'ERRMODE' => [ + \PDO::ERRMODE_SILENT => 'SILENT', + \PDO::ERRMODE_WARNING => 'WARNING', + \PDO::ERRMODE_EXCEPTION => 'EXCEPTION', + ], + 'TIMEOUT', + 'PREFETCH', + 'AUTOCOMMIT', + 'PERSISTENT', + 'DRIVER_NAME', + 'SERVER_INFO', + 'ORACLE_NULLS' => [ + \PDO::NULL_NATURAL => 'NATURAL', + \PDO::NULL_EMPTY_STRING => 'EMPTY_STRING', + \PDO::NULL_TO_STRING => 'TO_STRING', + ], + 'CLIENT_VERSION', + 'SERVER_VERSION', + 'STATEMENT_CLASS', + 'EMULATE_PREPARES', + 'CONNECTION_STATUS', + 'STRINGIFY_FETCHES', + 'DEFAULT_FETCH_MODE' => [ + \PDO::FETCH_ASSOC => 'ASSOC', + \PDO::FETCH_BOTH => 'BOTH', + \PDO::FETCH_LAZY => 'LAZY', + \PDO::FETCH_NUM => 'NUM', + \PDO::FETCH_OBJ => 'OBJ', + ], + ]; + + public static function castPdo(\PDO $c, array $a, Stub $stub, $isNested) + { + $attr = []; + $errmode = $c->getAttribute(\PDO::ATTR_ERRMODE); + $c->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + + foreach (self::PDO_ATTRIBUTES as $k => $v) { + if (!isset($k[0])) { + $k = $v; + $v = []; + } + + try { + $attr[$k] = 'ERRMODE' === $k ? $errmode : $c->getAttribute(\constant('PDO::ATTR_'.$k)); + if ($v && isset($v[$attr[$k]])) { + $attr[$k] = new ConstStub($v[$attr[$k]], $attr[$k]); + } + } catch (\Exception $e) { + } + } + if (isset($attr[$k = 'STATEMENT_CLASS'][1])) { + if ($attr[$k][1]) { + $attr[$k][1] = new ArgsStub($attr[$k][1], '__construct', $attr[$k][0]); + } + $attr[$k][0] = new ClassStub($attr[$k][0]); + } + + $prefix = Caster::PREFIX_VIRTUAL; + $a += [ + $prefix.'inTransaction' => method_exists($c, 'inTransaction'), + $prefix.'errorInfo' => $c->errorInfo(), + $prefix.'attributes' => new EnumStub($attr), + ]; + + if ($a[$prefix.'inTransaction']) { + $a[$prefix.'inTransaction'] = $c->inTransaction(); + } else { + unset($a[$prefix.'inTransaction']); + } + + if (!isset($a[$prefix.'errorInfo'][1], $a[$prefix.'errorInfo'][2])) { + unset($a[$prefix.'errorInfo']); + } + + $c->setAttribute(\PDO::ATTR_ERRMODE, $errmode); + + return $a; + } + + public static function castPdoStatement(\PDOStatement $c, array $a, Stub $stub, $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + $a[$prefix.'errorInfo'] = $c->errorInfo(); + + if (!isset($a[$prefix.'errorInfo'][1], $a[$prefix.'errorInfo'][2])) { + unset($a[$prefix.'errorInfo']); + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/PgSqlCaster.php b/vendor/symfony/var-dumper/Caster/PgSqlCaster.php new file mode 100644 index 0000000..3097c51 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/PgSqlCaster.php @@ -0,0 +1,156 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts pqsql resources to array representation. + * + * @author Nicolas Grekas + * + * @final since Symfony 4.4 + */ +class PgSqlCaster +{ + private const PARAM_CODES = [ + 'server_encoding', + 'client_encoding', + 'is_superuser', + 'session_authorization', + 'DateStyle', + 'TimeZone', + 'IntervalStyle', + 'integer_datetimes', + 'application_name', + 'standard_conforming_strings', + ]; + + private const TRANSACTION_STATUS = [ + \PGSQL_TRANSACTION_IDLE => 'PGSQL_TRANSACTION_IDLE', + \PGSQL_TRANSACTION_ACTIVE => 'PGSQL_TRANSACTION_ACTIVE', + \PGSQL_TRANSACTION_INTRANS => 'PGSQL_TRANSACTION_INTRANS', + \PGSQL_TRANSACTION_INERROR => 'PGSQL_TRANSACTION_INERROR', + \PGSQL_TRANSACTION_UNKNOWN => 'PGSQL_TRANSACTION_UNKNOWN', + ]; + + private const RESULT_STATUS = [ + \PGSQL_EMPTY_QUERY => 'PGSQL_EMPTY_QUERY', + \PGSQL_COMMAND_OK => 'PGSQL_COMMAND_OK', + \PGSQL_TUPLES_OK => 'PGSQL_TUPLES_OK', + \PGSQL_COPY_OUT => 'PGSQL_COPY_OUT', + \PGSQL_COPY_IN => 'PGSQL_COPY_IN', + \PGSQL_BAD_RESPONSE => 'PGSQL_BAD_RESPONSE', + \PGSQL_NONFATAL_ERROR => 'PGSQL_NONFATAL_ERROR', + \PGSQL_FATAL_ERROR => 'PGSQL_FATAL_ERROR', + ]; + + private const DIAG_CODES = [ + 'severity' => \PGSQL_DIAG_SEVERITY, + 'sqlstate' => \PGSQL_DIAG_SQLSTATE, + 'message' => \PGSQL_DIAG_MESSAGE_PRIMARY, + 'detail' => \PGSQL_DIAG_MESSAGE_DETAIL, + 'hint' => \PGSQL_DIAG_MESSAGE_HINT, + 'statement position' => \PGSQL_DIAG_STATEMENT_POSITION, + 'internal position' => \PGSQL_DIAG_INTERNAL_POSITION, + 'internal query' => \PGSQL_DIAG_INTERNAL_QUERY, + 'context' => \PGSQL_DIAG_CONTEXT, + 'file' => \PGSQL_DIAG_SOURCE_FILE, + 'line' => \PGSQL_DIAG_SOURCE_LINE, + 'function' => \PGSQL_DIAG_SOURCE_FUNCTION, + ]; + + public static function castLargeObject($lo, array $a, Stub $stub, $isNested) + { + $a['seek position'] = pg_lo_tell($lo); + + return $a; + } + + public static function castLink($link, array $a, Stub $stub, $isNested) + { + $a['status'] = pg_connection_status($link); + $a['status'] = new ConstStub(\PGSQL_CONNECTION_OK === $a['status'] ? 'PGSQL_CONNECTION_OK' : 'PGSQL_CONNECTION_BAD', $a['status']); + $a['busy'] = pg_connection_busy($link); + + $a['transaction'] = pg_transaction_status($link); + if (isset(self::TRANSACTION_STATUS[$a['transaction']])) { + $a['transaction'] = new ConstStub(self::TRANSACTION_STATUS[$a['transaction']], $a['transaction']); + } + + $a['pid'] = pg_get_pid($link); + $a['last error'] = pg_last_error($link); + $a['last notice'] = pg_last_notice($link); + $a['host'] = pg_host($link); + $a['port'] = pg_port($link); + $a['dbname'] = pg_dbname($link); + $a['options'] = pg_options($link); + $a['version'] = pg_version($link); + + foreach (self::PARAM_CODES as $v) { + if (false !== $s = pg_parameter_status($link, $v)) { + $a['param'][$v] = $s; + } + } + + $a['param']['client_encoding'] = pg_client_encoding($link); + $a['param'] = new EnumStub($a['param']); + + return $a; + } + + public static function castResult($result, array $a, Stub $stub, $isNested) + { + $a['num rows'] = pg_num_rows($result); + $a['status'] = pg_result_status($result); + if (isset(self::RESULT_STATUS[$a['status']])) { + $a['status'] = new ConstStub(self::RESULT_STATUS[$a['status']], $a['status']); + } + $a['command-completion tag'] = pg_result_status($result, \PGSQL_STATUS_STRING); + + if (-1 === $a['num rows']) { + foreach (self::DIAG_CODES as $k => $v) { + $a['error'][$k] = pg_result_error_field($result, $v); + } + } + + $a['affected rows'] = pg_affected_rows($result); + $a['last OID'] = pg_last_oid($result); + + $fields = pg_num_fields($result); + + for ($i = 0; $i < $fields; ++$i) { + $field = [ + 'name' => pg_field_name($result, $i), + 'table' => sprintf('%s (OID: %s)', pg_field_table($result, $i), pg_field_table($result, $i, true)), + 'type' => sprintf('%s (OID: %s)', pg_field_type($result, $i), pg_field_type_oid($result, $i)), + 'nullable' => (bool) pg_field_is_null($result, $i), + 'storage' => pg_field_size($result, $i).' bytes', + 'display' => pg_field_prtlen($result, $i).' chars', + ]; + if (' (OID: )' === $field['table']) { + $field['table'] = null; + } + if ('-1 bytes' === $field['storage']) { + $field['storage'] = 'variable size'; + } elseif ('1 bytes' === $field['storage']) { + $field['storage'] = '1 byte'; + } + if ('1 chars' === $field['display']) { + $field['display'] = '1 char'; + } + $a['fields'][] = new EnumStub($field); + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/ProxyManagerCaster.php b/vendor/symfony/var-dumper/Caster/ProxyManagerCaster.php new file mode 100644 index 0000000..ec02f81 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ProxyManagerCaster.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use ProxyManager\Proxy\ProxyInterface; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Nicolas Grekas + * + * @final since Symfony 4.4 + */ +class ProxyManagerCaster +{ + public static function castProxy(ProxyInterface $c, array $a, Stub $stub, $isNested) + { + if ($parent = get_parent_class($c)) { + $stub->class .= ' - '.$parent; + } + $stub->class .= '@proxy'; + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/RedisCaster.php b/vendor/symfony/var-dumper/Caster/RedisCaster.php new file mode 100644 index 0000000..bd877cb --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/RedisCaster.php @@ -0,0 +1,152 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts Redis class from ext-redis to array representation. + * + * @author Nicolas Grekas + * + * @final since Symfony 4.4 + */ +class RedisCaster +{ + private const SERIALIZERS = [ + \Redis::SERIALIZER_NONE => 'NONE', + \Redis::SERIALIZER_PHP => 'PHP', + 2 => 'IGBINARY', // Optional Redis::SERIALIZER_IGBINARY + ]; + + private const MODES = [ + \Redis::ATOMIC => 'ATOMIC', + \Redis::MULTI => 'MULTI', + \Redis::PIPELINE => 'PIPELINE', + ]; + + private const COMPRESSION_MODES = [ + 0 => 'NONE', // Redis::COMPRESSION_NONE + 1 => 'LZF', // Redis::COMPRESSION_LZF + ]; + + private const FAILOVER_OPTIONS = [ + \RedisCluster::FAILOVER_NONE => 'NONE', + \RedisCluster::FAILOVER_ERROR => 'ERROR', + \RedisCluster::FAILOVER_DISTRIBUTE => 'DISTRIBUTE', + \RedisCluster::FAILOVER_DISTRIBUTE_SLAVES => 'DISTRIBUTE_SLAVES', + ]; + + public static function castRedis(\Redis $c, array $a, Stub $stub, $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + if (!$connected = $c->isConnected()) { + return $a + [ + $prefix.'isConnected' => $connected, + ]; + } + + $mode = $c->getMode(); + + return $a + [ + $prefix.'isConnected' => $connected, + $prefix.'host' => $c->getHost(), + $prefix.'port' => $c->getPort(), + $prefix.'auth' => $c->getAuth(), + $prefix.'mode' => isset(self::MODES[$mode]) ? new ConstStub(self::MODES[$mode], $mode) : $mode, + $prefix.'dbNum' => $c->getDbNum(), + $prefix.'timeout' => $c->getTimeout(), + $prefix.'lastError' => $c->getLastError(), + $prefix.'persistentId' => $c->getPersistentID(), + $prefix.'options' => self::getRedisOptions($c), + ]; + } + + public static function castRedisArray(\RedisArray $c, array $a, Stub $stub, $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + return $a + [ + $prefix.'hosts' => $c->_hosts(), + $prefix.'function' => ClassStub::wrapCallable($c->_function()), + $prefix.'lastError' => $c->getLastError(), + $prefix.'options' => self::getRedisOptions($c), + ]; + } + + public static function castRedisCluster(\RedisCluster $c, array $a, Stub $stub, $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + $failover = $c->getOption(\RedisCluster::OPT_SLAVE_FAILOVER); + + $a += [ + $prefix.'_masters' => $c->_masters(), + $prefix.'_redir' => $c->_redir(), + $prefix.'mode' => new ConstStub($c->getMode() ? 'MULTI' : 'ATOMIC', $c->getMode()), + $prefix.'lastError' => $c->getLastError(), + $prefix.'options' => self::getRedisOptions($c, [ + 'SLAVE_FAILOVER' => isset(self::FAILOVER_OPTIONS[$failover]) ? new ConstStub(self::FAILOVER_OPTIONS[$failover], $failover) : $failover, + ]), + ]; + + return $a; + } + + /** + * @param \Redis|\RedisArray|\RedisCluster $redis + */ + private static function getRedisOptions($redis, array $options = []): EnumStub + { + $serializer = $redis->getOption(\Redis::OPT_SERIALIZER); + if (\is_array($serializer)) { + foreach ($serializer as &$v) { + if (isset(self::SERIALIZERS[$v])) { + $v = new ConstStub(self::SERIALIZERS[$v], $v); + } + } + } elseif (isset(self::SERIALIZERS[$serializer])) { + $serializer = new ConstStub(self::SERIALIZERS[$serializer], $serializer); + } + + $compression = \defined('Redis::OPT_COMPRESSION') ? $redis->getOption(\Redis::OPT_COMPRESSION) : 0; + if (\is_array($compression)) { + foreach ($compression as &$v) { + if (isset(self::COMPRESSION_MODES[$v])) { + $v = new ConstStub(self::COMPRESSION_MODES[$v], $v); + } + } + } elseif (isset(self::COMPRESSION_MODES[$compression])) { + $compression = new ConstStub(self::COMPRESSION_MODES[$compression], $compression); + } + + $retry = \defined('Redis::OPT_SCAN') ? $redis->getOption(\Redis::OPT_SCAN) : 0; + if (\is_array($retry)) { + foreach ($retry as &$v) { + $v = new ConstStub($v ? 'RETRY' : 'NORETRY', $v); + } + } else { + $retry = new ConstStub($retry ? 'RETRY' : 'NORETRY', $retry); + } + + $options += [ + 'TCP_KEEPALIVE' => \defined('Redis::OPT_TCP_KEEPALIVE') ? $redis->getOption(\Redis::OPT_TCP_KEEPALIVE) : 0, + 'READ_TIMEOUT' => $redis->getOption(\Redis::OPT_READ_TIMEOUT), + 'COMPRESSION' => $compression, + 'SERIALIZER' => $serializer, + 'PREFIX' => $redis->getOption(\Redis::OPT_PREFIX), + 'SCAN' => $retry, + ]; + + return new EnumStub($options); + } +} diff --git a/vendor/symfony/var-dumper/Caster/ReflectionCaster.php b/vendor/symfony/var-dumper/Caster/ReflectionCaster.php new file mode 100644 index 0000000..819a618 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ReflectionCaster.php @@ -0,0 +1,403 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts Reflector related classes to array representation. + * + * @author Nicolas Grekas + * + * @final since Symfony 4.4 + */ +class ReflectionCaster +{ + public const UNSET_CLOSURE_FILE_INFO = ['Closure' => __CLASS__.'::unsetClosureFileInfo']; + + private const EXTRA_MAP = [ + 'docComment' => 'getDocComment', + 'extension' => 'getExtensionName', + 'isDisabled' => 'isDisabled', + 'isDeprecated' => 'isDeprecated', + 'isInternal' => 'isInternal', + 'isUserDefined' => 'isUserDefined', + 'isGenerator' => 'isGenerator', + 'isVariadic' => 'isVariadic', + ]; + + public static function castClosure(\Closure $c, array $a, Stub $stub, $isNested, $filter = 0) + { + $prefix = Caster::PREFIX_VIRTUAL; + $c = new \ReflectionFunction($c); + + $a = static::castFunctionAbstract($c, $a, $stub, $isNested, $filter); + + if (!str_contains($c->name, '{closure}')) { + $stub->class = isset($a[$prefix.'class']) ? $a[$prefix.'class']->value.'::'.$c->name : $c->name; + unset($a[$prefix.'class']); + } + unset($a[$prefix.'extra']); + + $stub->class .= self::getSignature($a); + + if ($f = $c->getFileName()) { + $stub->attr['file'] = $f; + $stub->attr['line'] = $c->getStartLine(); + } + + unset($a[$prefix.'parameters']); + + if ($filter & Caster::EXCLUDE_VERBOSE) { + $stub->cut += ($c->getFileName() ? 2 : 0) + \count($a); + + return []; + } + + if ($f) { + $a[$prefix.'file'] = new LinkStub($f, $c->getStartLine()); + $a[$prefix.'line'] = $c->getStartLine().' to '.$c->getEndLine(); + } + + return $a; + } + + public static function unsetClosureFileInfo(\Closure $c, array $a) + { + unset($a[Caster::PREFIX_VIRTUAL.'file'], $a[Caster::PREFIX_VIRTUAL.'line']); + + return $a; + } + + public static function castGenerator(\Generator $c, array $a, Stub $stub, $isNested) + { + // Cannot create ReflectionGenerator based on a terminated Generator + try { + $reflectionGenerator = new \ReflectionGenerator($c); + } catch (\Exception $e) { + $a[Caster::PREFIX_VIRTUAL.'closed'] = true; + + return $a; + } + + return self::castReflectionGenerator($reflectionGenerator, $a, $stub, $isNested); + } + + public static function castType(\ReflectionType $c, array $a, Stub $stub, $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + if ($c instanceof \ReflectionNamedType || \PHP_VERSION_ID < 80000) { + $a += [ + $prefix.'name' => $c instanceof \ReflectionNamedType ? $c->getName() : (string) $c, + $prefix.'allowsNull' => $c->allowsNull(), + $prefix.'isBuiltin' => $c->isBuiltin(), + ]; + } elseif ($c instanceof \ReflectionUnionType || $c instanceof \ReflectionIntersectionType) { + $a[$prefix.'allowsNull'] = $c->allowsNull(); + self::addMap($a, $c, [ + 'types' => 'getTypes', + ]); + } else { + $a[$prefix.'allowsNull'] = $c->allowsNull(); + } + + return $a; + } + + public static function castReflectionGenerator(\ReflectionGenerator $c, array $a, Stub $stub, $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + if ($c->getThis()) { + $a[$prefix.'this'] = new CutStub($c->getThis()); + } + $function = $c->getFunction(); + $frame = [ + 'class' => $function->class ?? null, + 'type' => isset($function->class) ? ($function->isStatic() ? '::' : '->') : null, + 'function' => $function->name, + 'file' => $c->getExecutingFile(), + 'line' => $c->getExecutingLine(), + ]; + if ($trace = $c->getTrace(\DEBUG_BACKTRACE_IGNORE_ARGS)) { + $function = new \ReflectionGenerator($c->getExecutingGenerator()); + array_unshift($trace, [ + 'function' => 'yield', + 'file' => $function->getExecutingFile(), + 'line' => $function->getExecutingLine() - 1, + ]); + $trace[] = $frame; + $a[$prefix.'trace'] = new TraceStub($trace, false, 0, -1, -1); + } else { + $function = new FrameStub($frame, false, true); + $function = ExceptionCaster::castFrameStub($function, [], $function, true); + $a[$prefix.'executing'] = $function[$prefix.'src']; + } + + $a[Caster::PREFIX_VIRTUAL.'closed'] = false; + + return $a; + } + + public static function castClass(\ReflectionClass $c, array $a, Stub $stub, $isNested, $filter = 0) + { + $prefix = Caster::PREFIX_VIRTUAL; + + if ($n = \Reflection::getModifierNames($c->getModifiers())) { + $a[$prefix.'modifiers'] = implode(' ', $n); + } + + self::addMap($a, $c, [ + 'extends' => 'getParentClass', + 'implements' => 'getInterfaceNames', + 'constants' => 'getConstants', + ]); + + foreach ($c->getProperties() as $n) { + $a[$prefix.'properties'][$n->name] = $n; + } + + foreach ($c->getMethods() as $n) { + $a[$prefix.'methods'][$n->name] = $n; + } + + if (!($filter & Caster::EXCLUDE_VERBOSE) && !$isNested) { + self::addExtra($a, $c); + } + + return $a; + } + + public static function castFunctionAbstract(\ReflectionFunctionAbstract $c, array $a, Stub $stub, $isNested, $filter = 0) + { + $prefix = Caster::PREFIX_VIRTUAL; + + self::addMap($a, $c, [ + 'returnsReference' => 'returnsReference', + 'returnType' => 'getReturnType', + 'class' => 'getClosureScopeClass', + 'this' => 'getClosureThis', + ]); + + if (isset($a[$prefix.'returnType'])) { + $v = $a[$prefix.'returnType']; + $v = $v instanceof \ReflectionNamedType ? $v->getName() : (string) $v; + $a[$prefix.'returnType'] = new ClassStub($a[$prefix.'returnType'] instanceof \ReflectionNamedType && $a[$prefix.'returnType']->allowsNull() && 'mixed' !== $v ? '?'.$v : $v, [class_exists($v, false) || interface_exists($v, false) || trait_exists($v, false) ? $v : '', '']); + } + if (isset($a[$prefix.'class'])) { + $a[$prefix.'class'] = new ClassStub($a[$prefix.'class']); + } + if (isset($a[$prefix.'this'])) { + $a[$prefix.'this'] = new CutStub($a[$prefix.'this']); + } + + foreach ($c->getParameters() as $v) { + $k = '$'.$v->name; + if ($v->isVariadic()) { + $k = '...'.$k; + } + if ($v->isPassedByReference()) { + $k = '&'.$k; + } + $a[$prefix.'parameters'][$k] = $v; + } + if (isset($a[$prefix.'parameters'])) { + $a[$prefix.'parameters'] = new EnumStub($a[$prefix.'parameters']); + } + + if (!($filter & Caster::EXCLUDE_VERBOSE) && $v = $c->getStaticVariables()) { + foreach ($v as $k => &$v) { + if (\is_object($v)) { + $a[$prefix.'use']['$'.$k] = new CutStub($v); + } else { + $a[$prefix.'use']['$'.$k] = &$v; + } + } + unset($v); + $a[$prefix.'use'] = new EnumStub($a[$prefix.'use']); + } + + if (!($filter & Caster::EXCLUDE_VERBOSE) && !$isNested) { + self::addExtra($a, $c); + } + + return $a; + } + + public static function castMethod(\ReflectionMethod $c, array $a, Stub $stub, $isNested) + { + $a[Caster::PREFIX_VIRTUAL.'modifiers'] = implode(' ', \Reflection::getModifierNames($c->getModifiers())); + + return $a; + } + + public static function castParameter(\ReflectionParameter $c, array $a, Stub $stub, $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + self::addMap($a, $c, [ + 'position' => 'getPosition', + 'isVariadic' => 'isVariadic', + 'byReference' => 'isPassedByReference', + 'allowsNull' => 'allowsNull', + ]); + + if ($v = $c->getType()) { + $a[$prefix.'typeHint'] = $v instanceof \ReflectionNamedType ? $v->getName() : (string) $v; + } + + if (isset($a[$prefix.'typeHint'])) { + $v = $a[$prefix.'typeHint']; + $a[$prefix.'typeHint'] = new ClassStub($v, [class_exists($v, false) || interface_exists($v, false) || trait_exists($v, false) ? $v : '', '']); + } else { + unset($a[$prefix.'allowsNull']); + } + + try { + $a[$prefix.'default'] = $v = $c->getDefaultValue(); + if ($c->isDefaultValueConstant()) { + $a[$prefix.'default'] = new ConstStub($c->getDefaultValueConstantName(), $v); + } + if (null === $v) { + unset($a[$prefix.'allowsNull']); + } + } catch (\ReflectionException $e) { + } + + return $a; + } + + public static function castProperty(\ReflectionProperty $c, array $a, Stub $stub, $isNested) + { + $a[Caster::PREFIX_VIRTUAL.'modifiers'] = implode(' ', \Reflection::getModifierNames($c->getModifiers())); + self::addExtra($a, $c); + + return $a; + } + + public static function castReference(\ReflectionReference $c, array $a, Stub $stub, $isNested) + { + $a[Caster::PREFIX_VIRTUAL.'id'] = $c->getId(); + + return $a; + } + + public static function castExtension(\ReflectionExtension $c, array $a, Stub $stub, $isNested) + { + self::addMap($a, $c, [ + 'version' => 'getVersion', + 'dependencies' => 'getDependencies', + 'iniEntries' => 'getIniEntries', + 'isPersistent' => 'isPersistent', + 'isTemporary' => 'isTemporary', + 'constants' => 'getConstants', + 'functions' => 'getFunctions', + 'classes' => 'getClasses', + ]); + + return $a; + } + + public static function castZendExtension(\ReflectionZendExtension $c, array $a, Stub $stub, $isNested) + { + self::addMap($a, $c, [ + 'version' => 'getVersion', + 'author' => 'getAuthor', + 'copyright' => 'getCopyright', + 'url' => 'getURL', + ]); + + return $a; + } + + public static function getSignature(array $a) + { + $prefix = Caster::PREFIX_VIRTUAL; + $signature = ''; + + if (isset($a[$prefix.'parameters'])) { + foreach ($a[$prefix.'parameters']->value as $k => $param) { + $signature .= ', '; + if ($type = $param->getType()) { + if (!$type instanceof \ReflectionNamedType) { + $signature .= $type.' '; + } else { + if (!$param->isOptional() && $param->allowsNull() && 'mixed' !== $type->getName()) { + $signature .= '?'; + } + $signature .= substr(strrchr('\\'.$type->getName(), '\\'), 1).' '; + } + } + $signature .= $k; + + if (!$param->isDefaultValueAvailable()) { + continue; + } + $v = $param->getDefaultValue(); + $signature .= ' = '; + + if ($param->isDefaultValueConstant()) { + $signature .= substr(strrchr('\\'.$param->getDefaultValueConstantName(), '\\'), 1); + } elseif (null === $v) { + $signature .= 'null'; + } elseif (\is_array($v)) { + $signature .= $v ? '[…'.\count($v).']' : '[]'; + } elseif (\is_string($v)) { + $signature .= 10 > \strlen($v) && !str_contains($v, '\\') ? "'{$v}'" : "'…".\strlen($v)."'"; + } elseif (\is_bool($v)) { + $signature .= $v ? 'true' : 'false'; + } elseif (\is_object($v)) { + $signature .= 'new '.substr(strrchr('\\'.get_debug_type($v), '\\'), 1); + } else { + $signature .= $v; + } + } + } + $signature = (empty($a[$prefix.'returnsReference']) ? '' : '&').'('.substr($signature, 2).')'; + + if (isset($a[$prefix.'returnType'])) { + $signature .= ': '.substr(strrchr('\\'.$a[$prefix.'returnType'], '\\'), 1); + } + + return $signature; + } + + private static function addExtra(array &$a, \Reflector $c) + { + $x = isset($a[Caster::PREFIX_VIRTUAL.'extra']) ? $a[Caster::PREFIX_VIRTUAL.'extra']->value : []; + + if (method_exists($c, 'getFileName') && $m = $c->getFileName()) { + $x['file'] = new LinkStub($m, $c->getStartLine()); + $x['line'] = $c->getStartLine().' to '.$c->getEndLine(); + } + + self::addMap($x, $c, self::EXTRA_MAP, ''); + + if ($x) { + $a[Caster::PREFIX_VIRTUAL.'extra'] = new EnumStub($x); + } + } + + private static function addMap(array &$a, $c, array $map, string $prefix = Caster::PREFIX_VIRTUAL) + { + foreach ($map as $k => $m) { + if (\PHP_VERSION_ID >= 80000 && 'isDisabled' === $k) { + continue; + } + + if (method_exists($c, $m) && false !== ($m = $c->$m()) && null !== $m) { + $a[$prefix.$k] = $m instanceof \Reflector ? $m->name : $m; + } + } + } +} diff --git a/vendor/symfony/var-dumper/Caster/ResourceCaster.php b/vendor/symfony/var-dumper/Caster/ResourceCaster.php new file mode 100644 index 0000000..a3278a8 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ResourceCaster.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts common resource types to array representation. + * + * @author Nicolas Grekas + * + * @final since Symfony 4.4 + */ +class ResourceCaster +{ + /** + * @param \CurlHandle|resource $h + * + * @return array + */ + public static function castCurl($h, array $a, Stub $stub, $isNested) + { + return curl_getinfo($h); + } + + public static function castDba($dba, array $a, Stub $stub, $isNested) + { + $list = dba_list(); + $a['file'] = $list[(int) $dba]; + + return $a; + } + + public static function castProcess($process, array $a, Stub $stub, $isNested) + { + return proc_get_status($process); + } + + public static function castStream($stream, array $a, Stub $stub, $isNested) + { + $a = stream_get_meta_data($stream) + static::castStreamContext($stream, $a, $stub, $isNested); + if ($a['uri'] ?? false) { + $a['uri'] = new LinkStub($a['uri']); + } + + return $a; + } + + public static function castStreamContext($stream, array $a, Stub $stub, $isNested) + { + return @stream_context_get_params($stream) ?: $a; + } + + public static function castGd($gd, array $a, Stub $stub, $isNested) + { + $a['size'] = imagesx($gd).'x'.imagesy($gd); + $a['trueColor'] = imageistruecolor($gd); + + return $a; + } + + public static function castMysqlLink($h, array $a, Stub $stub, $isNested) + { + $a['host'] = mysql_get_host_info($h); + $a['protocol'] = mysql_get_proto_info($h); + $a['server'] = mysql_get_server_info($h); + + return $a; + } + + public static function castOpensslX509($h, array $a, Stub $stub, $isNested) + { + $stub->cut = -1; + $info = openssl_x509_parse($h, false); + + $pin = openssl_pkey_get_public($h); + $pin = openssl_pkey_get_details($pin)['key']; + $pin = \array_slice(explode("\n", $pin), 1, -2); + $pin = base64_decode(implode('', $pin)); + $pin = base64_encode(hash('sha256', $pin, true)); + + $a += [ + 'subject' => new EnumStub(array_intersect_key($info['subject'], ['organizationName' => true, 'commonName' => true])), + 'issuer' => new EnumStub(array_intersect_key($info['issuer'], ['organizationName' => true, 'commonName' => true])), + 'expiry' => new ConstStub(date(\DateTime::ISO8601, $info['validTo_time_t']), $info['validTo_time_t']), + 'fingerprint' => new EnumStub([ + 'md5' => new ConstStub(wordwrap(strtoupper(openssl_x509_fingerprint($h, 'md5')), 2, ':', true)), + 'sha1' => new ConstStub(wordwrap(strtoupper(openssl_x509_fingerprint($h, 'sha1')), 2, ':', true)), + 'sha256' => new ConstStub(wordwrap(strtoupper(openssl_x509_fingerprint($h, 'sha256')), 2, ':', true)), + 'pin-sha256' => new ConstStub($pin), + ]), + ]; + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/SplCaster.php b/vendor/symfony/var-dumper/Caster/SplCaster.php new file mode 100644 index 0000000..be9d66b --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/SplCaster.php @@ -0,0 +1,245 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts SPL related classes to array representation. + * + * @author Nicolas Grekas + * + * @final since Symfony 4.4 + */ +class SplCaster +{ + private const SPL_FILE_OBJECT_FLAGS = [ + \SplFileObject::DROP_NEW_LINE => 'DROP_NEW_LINE', + \SplFileObject::READ_AHEAD => 'READ_AHEAD', + \SplFileObject::SKIP_EMPTY => 'SKIP_EMPTY', + \SplFileObject::READ_CSV => 'READ_CSV', + ]; + + public static function castArrayObject(\ArrayObject $c, array $a, Stub $stub, $isNested) + { + return self::castSplArray($c, $a, $stub, $isNested); + } + + public static function castArrayIterator(\ArrayIterator $c, array $a, Stub $stub, $isNested) + { + return self::castSplArray($c, $a, $stub, $isNested); + } + + public static function castHeap(\Iterator $c, array $a, Stub $stub, $isNested) + { + $a += [ + Caster::PREFIX_VIRTUAL.'heap' => iterator_to_array(clone $c), + ]; + + return $a; + } + + public static function castDoublyLinkedList(\SplDoublyLinkedList $c, array $a, Stub $stub, $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + $mode = $c->getIteratorMode(); + $c->setIteratorMode(\SplDoublyLinkedList::IT_MODE_KEEP | $mode & ~\SplDoublyLinkedList::IT_MODE_DELETE); + + $a += [ + $prefix.'mode' => new ConstStub((($mode & \SplDoublyLinkedList::IT_MODE_LIFO) ? 'IT_MODE_LIFO' : 'IT_MODE_FIFO').' | '.(($mode & \SplDoublyLinkedList::IT_MODE_DELETE) ? 'IT_MODE_DELETE' : 'IT_MODE_KEEP'), $mode), + $prefix.'dllist' => iterator_to_array($c), + ]; + $c->setIteratorMode($mode); + + return $a; + } + + public static function castFileInfo(\SplFileInfo $c, array $a, Stub $stub, $isNested) + { + static $map = [ + 'path' => 'getPath', + 'filename' => 'getFilename', + 'basename' => 'getBasename', + 'pathname' => 'getPathname', + 'extension' => 'getExtension', + 'realPath' => 'getRealPath', + 'aTime' => 'getATime', + 'mTime' => 'getMTime', + 'cTime' => 'getCTime', + 'inode' => 'getInode', + 'size' => 'getSize', + 'perms' => 'getPerms', + 'owner' => 'getOwner', + 'group' => 'getGroup', + 'type' => 'getType', + 'writable' => 'isWritable', + 'readable' => 'isReadable', + 'executable' => 'isExecutable', + 'file' => 'isFile', + 'dir' => 'isDir', + 'link' => 'isLink', + 'linkTarget' => 'getLinkTarget', + ]; + + $prefix = Caster::PREFIX_VIRTUAL; + unset($a["\0SplFileInfo\0fileName"]); + unset($a["\0SplFileInfo\0pathName"]); + + if (\PHP_VERSION_ID < 80000) { + if (false === $c->getPathname()) { + $a[$prefix.'⚠'] = 'The parent constructor was not called: the object is in an invalid state'; + + return $a; + } + } else { + try { + $c->isReadable(); + } catch (\RuntimeException $e) { + if ('Object not initialized' !== $e->getMessage()) { + throw $e; + } + + $a[$prefix.'⚠'] = 'The parent constructor was not called: the object is in an invalid state'; + + return $a; + } catch (\Error $e) { + if ('Object not initialized' !== $e->getMessage()) { + throw $e; + } + + $a[$prefix.'⚠'] = 'The parent constructor was not called: the object is in an invalid state'; + + return $a; + } + } + + foreach ($map as $key => $accessor) { + try { + $a[$prefix.$key] = $c->$accessor(); + } catch (\Exception $e) { + } + } + + if ($a[$prefix.'realPath'] ?? false) { + $a[$prefix.'realPath'] = new LinkStub($a[$prefix.'realPath']); + } + + if (isset($a[$prefix.'perms'])) { + $a[$prefix.'perms'] = new ConstStub(sprintf('0%o', $a[$prefix.'perms']), $a[$prefix.'perms']); + } + + static $mapDate = ['aTime', 'mTime', 'cTime']; + foreach ($mapDate as $key) { + if (isset($a[$prefix.$key])) { + $a[$prefix.$key] = new ConstStub(date('Y-m-d H:i:s', $a[$prefix.$key]), $a[$prefix.$key]); + } + } + + return $a; + } + + public static function castFileObject(\SplFileObject $c, array $a, Stub $stub, $isNested) + { + static $map = [ + 'csvControl' => 'getCsvControl', + 'flags' => 'getFlags', + 'maxLineLen' => 'getMaxLineLen', + 'fstat' => 'fstat', + 'eof' => 'eof', + 'key' => 'key', + ]; + + $prefix = Caster::PREFIX_VIRTUAL; + + foreach ($map as $key => $accessor) { + try { + $a[$prefix.$key] = $c->$accessor(); + } catch (\Exception $e) { + } + } + + if (isset($a[$prefix.'flags'])) { + $flagsArray = []; + foreach (self::SPL_FILE_OBJECT_FLAGS as $value => $name) { + if ($a[$prefix.'flags'] & $value) { + $flagsArray[] = $name; + } + } + $a[$prefix.'flags'] = new ConstStub(implode('|', $flagsArray), $a[$prefix.'flags']); + } + + if (isset($a[$prefix.'fstat'])) { + $a[$prefix.'fstat'] = new CutArrayStub($a[$prefix.'fstat'], ['dev', 'ino', 'nlink', 'rdev', 'blksize', 'blocks']); + } + + return $a; + } + + public static function castObjectStorage(\SplObjectStorage $c, array $a, Stub $stub, $isNested) + { + $storage = []; + unset($a[Caster::PREFIX_DYNAMIC."\0gcdata"]); // Don't hit https://bugs.php.net/65967 + unset($a["\0SplObjectStorage\0storage"]); + + $clone = clone $c; + foreach ($clone as $obj) { + $storage[] = [ + 'object' => $obj, + 'info' => $clone->getInfo(), + ]; + } + + $a += [ + Caster::PREFIX_VIRTUAL.'storage' => $storage, + ]; + + return $a; + } + + public static function castOuterIterator(\OuterIterator $c, array $a, Stub $stub, $isNested) + { + $a[Caster::PREFIX_VIRTUAL.'innerIterator'] = $c->getInnerIterator(); + + return $a; + } + + public static function castWeakReference(\WeakReference $c, array $a, Stub $stub, $isNested) + { + $a[Caster::PREFIX_VIRTUAL.'object'] = $c->get(); + + return $a; + } + + private static function castSplArray($c, array $a, Stub $stub, bool $isNested): array + { + $prefix = Caster::PREFIX_VIRTUAL; + $flags = $c->getFlags(); + + if (!($flags & \ArrayObject::STD_PROP_LIST)) { + $c->setFlags(\ArrayObject::STD_PROP_LIST); + $a = Caster::castObject($c, \get_class($c), method_exists($c, '__debugInfo'), $stub->class); + $c->setFlags($flags); + } + if (\PHP_VERSION_ID < 70400) { + $a[$prefix.'storage'] = $c->getArrayCopy(); + } + $a += [ + $prefix.'flag::STD_PROP_LIST' => (bool) ($flags & \ArrayObject::STD_PROP_LIST), + $prefix.'flag::ARRAY_AS_PROPS' => (bool) ($flags & \ArrayObject::ARRAY_AS_PROPS), + ]; + if ($c instanceof \ArrayObject) { + $a[$prefix.'iteratorClass'] = new ClassStub($c->getIteratorClass()); + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/StubCaster.php b/vendor/symfony/var-dumper/Caster/StubCaster.php new file mode 100644 index 0000000..b6332fb --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/StubCaster.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts a caster's Stub. + * + * @author Nicolas Grekas + * + * @final since Symfony 4.4 + */ +class StubCaster +{ + public static function castStub(Stub $c, array $a, Stub $stub, $isNested) + { + if ($isNested) { + $stub->type = $c->type; + $stub->class = $c->class; + $stub->value = $c->value; + $stub->handle = $c->handle; + $stub->cut = $c->cut; + $stub->attr = $c->attr; + + if (Stub::TYPE_REF === $c->type && !$c->class && \is_string($c->value) && !preg_match('//u', $c->value)) { + $stub->type = Stub::TYPE_STRING; + $stub->class = Stub::STRING_BINARY; + } + + $a = []; + } + + return $a; + } + + public static function castCutArray(CutArrayStub $c, array $a, Stub $stub, $isNested) + { + return $isNested ? $c->preservedSubset : $a; + } + + public static function cutInternals($obj, array $a, Stub $stub, $isNested) + { + if ($isNested) { + $stub->cut += \count($a); + + return []; + } + + return $a; + } + + public static function castEnum(EnumStub $c, array $a, Stub $stub, $isNested) + { + if ($isNested) { + $stub->class = $c->dumpKeys ? '' : null; + $stub->handle = 0; + $stub->value = null; + $stub->cut = $c->cut; + $stub->attr = $c->attr; + + $a = []; + + if ($c->value) { + foreach (array_keys($c->value) as $k) { + $keys[] = !isset($k[0]) || "\0" !== $k[0] ? Caster::PREFIX_VIRTUAL.$k : $k; + } + // Preserve references with array_combine() + $a = array_combine($keys, $c->value); + } + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/SymfonyCaster.php b/vendor/symfony/var-dumper/Caster/SymfonyCaster.php new file mode 100644 index 0000000..06f213e --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/SymfonyCaster.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @final since Symfony 4.4 + */ +class SymfonyCaster +{ + private const REQUEST_GETTERS = [ + 'pathInfo' => 'getPathInfo', + 'requestUri' => 'getRequestUri', + 'baseUrl' => 'getBaseUrl', + 'basePath' => 'getBasePath', + 'method' => 'getMethod', + 'format' => 'getRequestFormat', + ]; + + public static function castRequest(Request $request, array $a, Stub $stub, $isNested) + { + $clone = null; + + foreach (self::REQUEST_GETTERS as $prop => $getter) { + $key = Caster::PREFIX_PROTECTED.$prop; + if (\array_key_exists($key, $a) && null === $a[$key]) { + if (null === $clone) { + $clone = clone $request; + } + $a[Caster::PREFIX_VIRTUAL.$prop] = $clone->{$getter}(); + } + } + + return $a; + } + + public static function castHttpClient($client, array $a, Stub $stub, $isNested) + { + $multiKey = sprintf("\0%s\0multi", \get_class($client)); + if (isset($a[$multiKey])) { + $a[$multiKey] = new CutStub($a[$multiKey]); + } + + return $a; + } + + public static function castHttpClientResponse($response, array $a, Stub $stub, $isNested) + { + $stub->cut += \count($a); + $a = []; + + foreach ($response->getInfo() as $k => $v) { + $a[Caster::PREFIX_VIRTUAL.$k] = $v; + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/TraceStub.php b/vendor/symfony/var-dumper/Caster/TraceStub.php new file mode 100644 index 0000000..5eea1c8 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/TraceStub.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Represents a backtrace as returned by debug_backtrace() or Exception->getTrace(). + * + * @author Nicolas Grekas + */ +class TraceStub extends Stub +{ + public $keepArgs; + public $sliceOffset; + public $sliceLength; + public $numberingOffset; + + public function __construct(array $trace, bool $keepArgs = true, int $sliceOffset = 0, int $sliceLength = null, int $numberingOffset = 0) + { + $this->value = $trace; + $this->keepArgs = $keepArgs; + $this->sliceOffset = $sliceOffset; + $this->sliceLength = $sliceLength; + $this->numberingOffset = $numberingOffset; + } +} diff --git a/vendor/symfony/var-dumper/Caster/UuidCaster.php b/vendor/symfony/var-dumper/Caster/UuidCaster.php new file mode 100644 index 0000000..b102774 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/UuidCaster.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Ramsey\Uuid\UuidInterface; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Grégoire Pineau + */ +final class UuidCaster +{ + public static function castRamseyUuid(UuidInterface $c, array $a, Stub $stub, bool $isNested): array + { + $a += [ + Caster::PREFIX_VIRTUAL.'uuid' => (string) $c, + ]; + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/XmlReaderCaster.php b/vendor/symfony/var-dumper/Caster/XmlReaderCaster.php new file mode 100644 index 0000000..e7a0f64 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/XmlReaderCaster.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts XmlReader class to array representation. + * + * @author Baptiste Clavié + * + * @final since Symfony 4.4 + */ +class XmlReaderCaster +{ + private const NODE_TYPES = [ + \XMLReader::NONE => 'NONE', + \XMLReader::ELEMENT => 'ELEMENT', + \XMLReader::ATTRIBUTE => 'ATTRIBUTE', + \XMLReader::TEXT => 'TEXT', + \XMLReader::CDATA => 'CDATA', + \XMLReader::ENTITY_REF => 'ENTITY_REF', + \XMLReader::ENTITY => 'ENTITY', + \XMLReader::PI => 'PI (Processing Instruction)', + \XMLReader::COMMENT => 'COMMENT', + \XMLReader::DOC => 'DOC', + \XMLReader::DOC_TYPE => 'DOC_TYPE', + \XMLReader::DOC_FRAGMENT => 'DOC_FRAGMENT', + \XMLReader::NOTATION => 'NOTATION', + \XMLReader::WHITESPACE => 'WHITESPACE', + \XMLReader::SIGNIFICANT_WHITESPACE => 'SIGNIFICANT_WHITESPACE', + \XMLReader::END_ELEMENT => 'END_ELEMENT', + \XMLReader::END_ENTITY => 'END_ENTITY', + \XMLReader::XML_DECLARATION => 'XML_DECLARATION', + ]; + + public static function castXmlReader(\XMLReader $reader, array $a, Stub $stub, $isNested) + { + try { + $properties = [ + 'LOADDTD' => @$reader->getParserProperty(\XMLReader::LOADDTD), + 'DEFAULTATTRS' => @$reader->getParserProperty(\XMLReader::DEFAULTATTRS), + 'VALIDATE' => @$reader->getParserProperty(\XMLReader::VALIDATE), + 'SUBST_ENTITIES' => @$reader->getParserProperty(\XMLReader::SUBST_ENTITIES), + ]; + } catch (\Error $e) { + $properties = [ + 'LOADDTD' => false, + 'DEFAULTATTRS' => false, + 'VALIDATE' => false, + 'SUBST_ENTITIES' => false, + ]; + } + + $props = Caster::PREFIX_VIRTUAL.'parserProperties'; + $info = [ + 'localName' => $reader->localName, + 'prefix' => $reader->prefix, + 'nodeType' => new ConstStub(self::NODE_TYPES[$reader->nodeType], $reader->nodeType), + 'depth' => $reader->depth, + 'isDefault' => $reader->isDefault, + 'isEmptyElement' => \XMLReader::NONE === $reader->nodeType ? null : $reader->isEmptyElement, + 'xmlLang' => $reader->xmlLang, + 'attributeCount' => $reader->attributeCount, + 'value' => $reader->value, + 'namespaceURI' => $reader->namespaceURI, + 'baseURI' => $reader->baseURI ? new LinkStub($reader->baseURI) : $reader->baseURI, + $props => $properties, + ]; + + if ($info[$props] = Caster::filter($info[$props], Caster::EXCLUDE_EMPTY, [], $count)) { + $info[$props] = new EnumStub($info[$props]); + $info[$props]->cut = $count; + } + + $info = Caster::filter($info, Caster::EXCLUDE_EMPTY, [], $count); + // +2 because hasValue and hasAttributes are always filtered + $stub->cut += $count + 2; + + return $a + $info; + } +} diff --git a/vendor/symfony/var-dumper/Caster/XmlResourceCaster.php b/vendor/symfony/var-dumper/Caster/XmlResourceCaster.php new file mode 100644 index 0000000..455fc06 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/XmlResourceCaster.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts XML resources to array representation. + * + * @author Nicolas Grekas + * + * @final since Symfony 4.4 + */ +class XmlResourceCaster +{ + private const XML_ERRORS = [ + \XML_ERROR_NONE => 'XML_ERROR_NONE', + \XML_ERROR_NO_MEMORY => 'XML_ERROR_NO_MEMORY', + \XML_ERROR_SYNTAX => 'XML_ERROR_SYNTAX', + \XML_ERROR_NO_ELEMENTS => 'XML_ERROR_NO_ELEMENTS', + \XML_ERROR_INVALID_TOKEN => 'XML_ERROR_INVALID_TOKEN', + \XML_ERROR_UNCLOSED_TOKEN => 'XML_ERROR_UNCLOSED_TOKEN', + \XML_ERROR_PARTIAL_CHAR => 'XML_ERROR_PARTIAL_CHAR', + \XML_ERROR_TAG_MISMATCH => 'XML_ERROR_TAG_MISMATCH', + \XML_ERROR_DUPLICATE_ATTRIBUTE => 'XML_ERROR_DUPLICATE_ATTRIBUTE', + \XML_ERROR_JUNK_AFTER_DOC_ELEMENT => 'XML_ERROR_JUNK_AFTER_DOC_ELEMENT', + \XML_ERROR_PARAM_ENTITY_REF => 'XML_ERROR_PARAM_ENTITY_REF', + \XML_ERROR_UNDEFINED_ENTITY => 'XML_ERROR_UNDEFINED_ENTITY', + \XML_ERROR_RECURSIVE_ENTITY_REF => 'XML_ERROR_RECURSIVE_ENTITY_REF', + \XML_ERROR_ASYNC_ENTITY => 'XML_ERROR_ASYNC_ENTITY', + \XML_ERROR_BAD_CHAR_REF => 'XML_ERROR_BAD_CHAR_REF', + \XML_ERROR_BINARY_ENTITY_REF => 'XML_ERROR_BINARY_ENTITY_REF', + \XML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF => 'XML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF', + \XML_ERROR_MISPLACED_XML_PI => 'XML_ERROR_MISPLACED_XML_PI', + \XML_ERROR_UNKNOWN_ENCODING => 'XML_ERROR_UNKNOWN_ENCODING', + \XML_ERROR_INCORRECT_ENCODING => 'XML_ERROR_INCORRECT_ENCODING', + \XML_ERROR_UNCLOSED_CDATA_SECTION => 'XML_ERROR_UNCLOSED_CDATA_SECTION', + \XML_ERROR_EXTERNAL_ENTITY_HANDLING => 'XML_ERROR_EXTERNAL_ENTITY_HANDLING', + ]; + + public static function castXml($h, array $a, Stub $stub, $isNested) + { + $a['current_byte_index'] = xml_get_current_byte_index($h); + $a['current_column_number'] = xml_get_current_column_number($h); + $a['current_line_number'] = xml_get_current_line_number($h); + $a['error_code'] = xml_get_error_code($h); + + if (isset(self::XML_ERRORS[$a['error_code']])) { + $a['error_code'] = new ConstStub(self::XML_ERRORS[$a['error_code']], $a['error_code']); + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Cloner/AbstractCloner.php b/vendor/symfony/var-dumper/Cloner/AbstractCloner.php new file mode 100644 index 0000000..eeac882 --- /dev/null +++ b/vendor/symfony/var-dumper/Cloner/AbstractCloner.php @@ -0,0 +1,375 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Cloner; + +use Symfony\Component\VarDumper\Caster\Caster; +use Symfony\Component\VarDumper\Exception\ThrowingCasterException; + +/** + * AbstractCloner implements a generic caster mechanism for objects and resources. + * + * @author Nicolas Grekas + */ +abstract class AbstractCloner implements ClonerInterface +{ + public static $defaultCasters = [ + '__PHP_Incomplete_Class' => ['Symfony\Component\VarDumper\Caster\Caster', 'castPhpIncompleteClass'], + + 'Symfony\Component\VarDumper\Caster\CutStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castStub'], + 'Symfony\Component\VarDumper\Caster\CutArrayStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castCutArray'], + 'Symfony\Component\VarDumper\Caster\ConstStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castStub'], + 'Symfony\Component\VarDumper\Caster\EnumStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castEnum'], + + 'Closure' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castClosure'], + 'Generator' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castGenerator'], + 'ReflectionType' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castType'], + 'ReflectionGenerator' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castReflectionGenerator'], + 'ReflectionClass' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castClass'], + 'ReflectionFunctionAbstract' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castFunctionAbstract'], + 'ReflectionMethod' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castMethod'], + 'ReflectionParameter' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castParameter'], + 'ReflectionProperty' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castProperty'], + 'ReflectionReference' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castReference'], + 'ReflectionExtension' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castExtension'], + 'ReflectionZendExtension' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castZendExtension'], + + 'Doctrine\Common\Persistence\ObjectManager' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'Doctrine\Common\Proxy\Proxy' => ['Symfony\Component\VarDumper\Caster\DoctrineCaster', 'castCommonProxy'], + 'Doctrine\ORM\Proxy\Proxy' => ['Symfony\Component\VarDumper\Caster\DoctrineCaster', 'castOrmProxy'], + 'Doctrine\ORM\PersistentCollection' => ['Symfony\Component\VarDumper\Caster\DoctrineCaster', 'castPersistentCollection'], + 'Doctrine\Persistence\ObjectManager' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + + 'DOMException' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castException'], + 'DOMStringList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], + 'DOMNameList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], + 'DOMImplementation' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castImplementation'], + 'DOMImplementationList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], + 'DOMNode' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castNode'], + 'DOMNameSpaceNode' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castNameSpaceNode'], + 'DOMDocument' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDocument'], + 'DOMNodeList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], + 'DOMNamedNodeMap' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], + 'DOMCharacterData' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castCharacterData'], + 'DOMAttr' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castAttr'], + 'DOMElement' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castElement'], + 'DOMText' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castText'], + 'DOMTypeinfo' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castTypeinfo'], + 'DOMDomError' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDomError'], + 'DOMLocator' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLocator'], + 'DOMDocumentType' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDocumentType'], + 'DOMNotation' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castNotation'], + 'DOMEntity' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castEntity'], + 'DOMProcessingInstruction' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castProcessingInstruction'], + 'DOMXPath' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castXPath'], + + 'XMLReader' => ['Symfony\Component\VarDumper\Caster\XmlReaderCaster', 'castXmlReader'], + + 'ErrorException' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castErrorException'], + 'Exception' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castException'], + 'Error' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castError'], + 'Symfony\Component\DependencyInjection\ContainerInterface' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'Symfony\Component\EventDispatcher\EventDispatcherInterface' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'Symfony\Component\HttpClient\CurlHttpClient' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClient'], + 'Symfony\Component\HttpClient\NativeHttpClient' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClient'], + 'Symfony\Component\HttpClient\Response\CurlResponse' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClientResponse'], + 'Symfony\Component\HttpClient\Response\NativeResponse' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClientResponse'], + 'Symfony\Component\HttpFoundation\Request' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castRequest'], + 'Symfony\Component\VarDumper\Exception\ThrowingCasterException' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castThrowingCasterException'], + 'Symfony\Component\VarDumper\Caster\TraceStub' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castTraceStub'], + 'Symfony\Component\VarDumper\Caster\FrameStub' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castFrameStub'], + 'Symfony\Component\VarDumper\Cloner\AbstractCloner' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'Symfony\Component\ErrorHandler\Exception\SilencedErrorContext' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castSilencedErrorContext'], + + 'Imagine\Image\ImageInterface' => ['Symfony\Component\VarDumper\Caster\ImagineCaster', 'castImage'], + + 'Ramsey\Uuid\UuidInterface' => ['Symfony\Component\VarDumper\Caster\UuidCaster', 'castRamseyUuid'], + + 'ProxyManager\Proxy\ProxyInterface' => ['Symfony\Component\VarDumper\Caster\ProxyManagerCaster', 'castProxy'], + 'PHPUnit_Framework_MockObject_MockObject' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'PHPUnit\Framework\MockObject\MockObject' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'PHPUnit\Framework\MockObject\Stub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'Prophecy\Prophecy\ProphecySubjectInterface' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'Mockery\MockInterface' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + + 'PDO' => ['Symfony\Component\VarDumper\Caster\PdoCaster', 'castPdo'], + 'PDOStatement' => ['Symfony\Component\VarDumper\Caster\PdoCaster', 'castPdoStatement'], + + 'AMQPConnection' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castConnection'], + 'AMQPChannel' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castChannel'], + 'AMQPQueue' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castQueue'], + 'AMQPExchange' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castExchange'], + 'AMQPEnvelope' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castEnvelope'], + + 'ArrayObject' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castArrayObject'], + 'ArrayIterator' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castArrayIterator'], + 'SplDoublyLinkedList' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castDoublyLinkedList'], + 'SplFileInfo' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castFileInfo'], + 'SplFileObject' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castFileObject'], + 'SplHeap' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castHeap'], + 'SplObjectStorage' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castObjectStorage'], + 'SplPriorityQueue' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castHeap'], + 'OuterIterator' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castOuterIterator'], + 'WeakReference' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castWeakReference'], + + 'Redis' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedis'], + 'RedisArray' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedisArray'], + 'RedisCluster' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedisCluster'], + + 'DateTimeInterface' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castDateTime'], + 'DateInterval' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castInterval'], + 'DateTimeZone' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castTimeZone'], + 'DatePeriod' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castPeriod'], + + 'GMP' => ['Symfony\Component\VarDumper\Caster\GmpCaster', 'castGmp'], + + 'MessageFormatter' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castMessageFormatter'], + 'NumberFormatter' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castNumberFormatter'], + 'IntlTimeZone' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castIntlTimeZone'], + 'IntlCalendar' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castIntlCalendar'], + 'IntlDateFormatter' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castIntlDateFormatter'], + + 'Memcached' => ['Symfony\Component\VarDumper\Caster\MemcachedCaster', 'castMemcached'], + + 'Ds\Collection' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castCollection'], + 'Ds\Map' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castMap'], + 'Ds\Pair' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castPair'], + 'Symfony\Component\VarDumper\Caster\DsPairStub' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castPairStub'], + + 'CurlHandle' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castCurl'], + ':curl' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castCurl'], + + ':dba' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castDba'], + ':dba persistent' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castDba'], + + 'GdImage' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castGd'], + ':gd' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castGd'], + + ':mysql link' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castMysqlLink'], + ':pgsql large object' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLargeObject'], + ':pgsql link' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLink'], + ':pgsql link persistent' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLink'], + ':pgsql result' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castResult'], + ':process' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castProcess'], + ':stream' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStream'], + + 'OpenSSLCertificate' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castOpensslX509'], + ':OpenSSL X.509' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castOpensslX509'], + + ':persistent stream' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStream'], + ':stream-context' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStreamContext'], + + 'XmlParser' => ['Symfony\Component\VarDumper\Caster\XmlResourceCaster', 'castXml'], + ':xml' => ['Symfony\Component\VarDumper\Caster\XmlResourceCaster', 'castXml'], + ]; + + protected $maxItems = 2500; + protected $maxString = -1; + protected $minDepth = 1; + + private $casters = []; + private $prevErrorHandler; + private $classInfo = []; + private $filter = 0; + + /** + * @param callable[]|null $casters A map of casters + * + * @see addCasters + */ + public function __construct(array $casters = null) + { + if (null === $casters) { + $casters = static::$defaultCasters; + } + $this->addCasters($casters); + } + + /** + * Adds casters for resources and objects. + * + * Maps resources or objects types to a callback. + * Types are in the key, with a callable caster for value. + * Resource types are to be prefixed with a `:`, + * see e.g. static::$defaultCasters. + * + * @param callable[] $casters A map of casters + */ + public function addCasters(array $casters) + { + foreach ($casters as $type => $callback) { + $this->casters[$type][] = $callback; + } + } + + /** + * Sets the maximum number of items to clone past the minimum depth in nested structures. + * + * @param int $maxItems + */ + public function setMaxItems($maxItems) + { + $this->maxItems = (int) $maxItems; + } + + /** + * Sets the maximum cloned length for strings. + * + * @param int $maxString + */ + public function setMaxString($maxString) + { + $this->maxString = (int) $maxString; + } + + /** + * Sets the minimum tree depth where we are guaranteed to clone all the items. After this + * depth is reached, only setMaxItems items will be cloned. + * + * @param int $minDepth + */ + public function setMinDepth($minDepth) + { + $this->minDepth = (int) $minDepth; + } + + /** + * Clones a PHP variable. + * + * @param mixed $var Any PHP variable + * @param int $filter A bit field of Caster::EXCLUDE_* constants + * + * @return Data The cloned variable represented by a Data object + */ + public function cloneVar($var, $filter = 0) + { + $this->prevErrorHandler = set_error_handler(function ($type, $msg, $file, $line, $context = []) { + if (\E_RECOVERABLE_ERROR === $type || \E_USER_ERROR === $type) { + // Cloner never dies + throw new \ErrorException($msg, 0, $type, $file, $line); + } + + if ($this->prevErrorHandler) { + return ($this->prevErrorHandler)($type, $msg, $file, $line, $context); + } + + return false; + }); + $this->filter = $filter; + + if ($gc = gc_enabled()) { + gc_disable(); + } + try { + return new Data($this->doClone($var)); + } finally { + if ($gc) { + gc_enable(); + } + restore_error_handler(); + $this->prevErrorHandler = null; + } + } + + /** + * Effectively clones the PHP variable. + * + * @param mixed $var Any PHP variable + * + * @return array The cloned variable represented in an array + */ + abstract protected function doClone($var); + + /** + * Casts an object to an array representation. + * + * @param bool $isNested True if the object is nested in the dumped structure + * + * @return array The object casted as array + */ + protected function castObject(Stub $stub, $isNested) + { + $obj = $stub->value; + $class = $stub->class; + + if (\PHP_VERSION_ID < 80000 ? "\0" === ($class[15] ?? null) : str_contains($class, "@anonymous\0")) { + $stub->class = get_debug_type($obj); + } + if (isset($this->classInfo[$class])) { + [$i, $parents, $hasDebugInfo, $fileInfo] = $this->classInfo[$class]; + } else { + $i = 2; + $parents = [$class]; + $hasDebugInfo = method_exists($class, '__debugInfo'); + + foreach (class_parents($class) as $p) { + $parents[] = $p; + ++$i; + } + foreach (class_implements($class) as $p) { + $parents[] = $p; + ++$i; + } + $parents[] = '*'; + + $r = new \ReflectionClass($class); + $fileInfo = $r->isInternal() || $r->isSubclassOf(Stub::class) ? [] : [ + 'file' => $r->getFileName(), + 'line' => $r->getStartLine(), + ]; + + $this->classInfo[$class] = [$i, $parents, $hasDebugInfo, $fileInfo]; + } + + $stub->attr += $fileInfo; + $a = Caster::castObject($obj, $class, $hasDebugInfo, $stub->class); + + try { + while ($i--) { + if (!empty($this->casters[$p = $parents[$i]])) { + foreach ($this->casters[$p] as $callback) { + $a = $callback($obj, $a, $stub, $isNested, $this->filter); + } + } + } + } catch (\Exception $e) { + $a = [(Stub::TYPE_OBJECT === $stub->type ? Caster::PREFIX_VIRTUAL : '').'⚠' => new ThrowingCasterException($e)] + $a; + } + + return $a; + } + + /** + * Casts a resource to an array representation. + * + * @param bool $isNested True if the object is nested in the dumped structure + * + * @return array The resource casted as array + */ + protected function castResource(Stub $stub, $isNested) + { + $a = []; + $res = $stub->value; + $type = $stub->class; + + try { + if (!empty($this->casters[':'.$type])) { + foreach ($this->casters[':'.$type] as $callback) { + $a = $callback($res, $a, $stub, $isNested, $this->filter); + } + } + } catch (\Exception $e) { + $a = [(Stub::TYPE_OBJECT === $stub->type ? Caster::PREFIX_VIRTUAL : '').'⚠' => new ThrowingCasterException($e)] + $a; + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Cloner/ClonerInterface.php b/vendor/symfony/var-dumper/Cloner/ClonerInterface.php new file mode 100644 index 0000000..7ed287a --- /dev/null +++ b/vendor/symfony/var-dumper/Cloner/ClonerInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Cloner; + +/** + * @author Nicolas Grekas + */ +interface ClonerInterface +{ + /** + * Clones a PHP variable. + * + * @param mixed $var Any PHP variable + * + * @return Data The cloned variable represented by a Data object + */ + public function cloneVar($var); +} diff --git a/vendor/symfony/var-dumper/Cloner/Cursor.php b/vendor/symfony/var-dumper/Cloner/Cursor.php new file mode 100644 index 0000000..1fd796d --- /dev/null +++ b/vendor/symfony/var-dumper/Cloner/Cursor.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Cloner; + +/** + * Represents the current state of a dumper while dumping. + * + * @author Nicolas Grekas + */ +class Cursor +{ + public const HASH_INDEXED = Stub::ARRAY_INDEXED; + public const HASH_ASSOC = Stub::ARRAY_ASSOC; + public const HASH_OBJECT = Stub::TYPE_OBJECT; + public const HASH_RESOURCE = Stub::TYPE_RESOURCE; + + public $depth = 0; + public $refIndex = 0; + public $softRefTo = 0; + public $softRefCount = 0; + public $softRefHandle = 0; + public $hardRefTo = 0; + public $hardRefCount = 0; + public $hardRefHandle = 0; + public $hashType; + public $hashKey; + public $hashKeyIsBinary; + public $hashIndex = 0; + public $hashLength = 0; + public $hashCut = 0; + public $stop = false; + public $attr = []; + public $skipChildren = false; +} diff --git a/vendor/symfony/var-dumper/Cloner/Data.php b/vendor/symfony/var-dumper/Cloner/Data.php new file mode 100644 index 0000000..8f621b1 --- /dev/null +++ b/vendor/symfony/var-dumper/Cloner/Data.php @@ -0,0 +1,470 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Cloner; + +use Symfony\Component\VarDumper\Caster\Caster; +use Symfony\Component\VarDumper\Dumper\ContextProvider\SourceContextProvider; + +/** + * @author Nicolas Grekas + */ +class Data implements \ArrayAccess, \Countable, \IteratorAggregate +{ + private $data; + private $position = 0; + private $key = 0; + private $maxDepth = 20; + private $maxItemsPerDepth = -1; + private $useRefHandles = -1; + private $context = []; + + /** + * @param array $data An array as returned by ClonerInterface::cloneVar() + */ + public function __construct(array $data) + { + $this->data = $data; + } + + /** + * @return string|null The type of the value + */ + public function getType() + { + $item = $this->data[$this->position][$this->key]; + + if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) { + $item = $item->value; + } + if (!$item instanceof Stub) { + return \gettype($item); + } + if (Stub::TYPE_STRING === $item->type) { + return 'string'; + } + if (Stub::TYPE_ARRAY === $item->type) { + return 'array'; + } + if (Stub::TYPE_OBJECT === $item->type) { + return $item->class; + } + if (Stub::TYPE_RESOURCE === $item->type) { + return $item->class.' resource'; + } + + return null; + } + + /** + * @param array|bool $recursive Whether values should be resolved recursively or not + * + * @return string|int|float|bool|array|Data[]|null A native representation of the original value + */ + public function getValue($recursive = false) + { + $item = $this->data[$this->position][$this->key]; + + if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) { + $item = $item->value; + } + if (!($item = $this->getStub($item)) instanceof Stub) { + return $item; + } + if (Stub::TYPE_STRING === $item->type) { + return $item->value; + } + + $children = $item->position ? $this->data[$item->position] : []; + + foreach ($children as $k => $v) { + if ($recursive && !($v = $this->getStub($v)) instanceof Stub) { + continue; + } + $children[$k] = clone $this; + $children[$k]->key = $k; + $children[$k]->position = $item->position; + + if ($recursive) { + if (Stub::TYPE_REF === $v->type && ($v = $this->getStub($v->value)) instanceof Stub) { + $recursive = (array) $recursive; + if (isset($recursive[$v->position])) { + continue; + } + $recursive[$v->position] = true; + } + $children[$k] = $children[$k]->getValue($recursive); + } + } + + return $children; + } + + /** + * @return int + */ + #[\ReturnTypeWillChange] + public function count() + { + return \count($this->getValue()); + } + + /** + * @return \Traversable + */ + #[\ReturnTypeWillChange] + public function getIterator() + { + if (!\is_array($value = $this->getValue())) { + throw new \LogicException(sprintf('"%s" object holds non-iterable type "%s".', self::class, \gettype($value))); + } + + yield from $value; + } + + public function __get($key) + { + if (null !== $data = $this->seek($key)) { + $item = $this->getStub($data->data[$data->position][$data->key]); + + return $item instanceof Stub || [] === $item ? $data : $item; + } + + return null; + } + + /** + * @return bool + */ + public function __isset($key) + { + return null !== $this->seek($key); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function offsetExists($key) + { + return $this->__isset($key); + } + + /** + * @return mixed + */ + #[\ReturnTypeWillChange] + public function offsetGet($key) + { + return $this->__get($key); + } + + /** + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetSet($key, $value) + { + throw new \BadMethodCallException(self::class.' objects are immutable.'); + } + + /** + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetUnset($key) + { + throw new \BadMethodCallException(self::class.' objects are immutable.'); + } + + /** + * @return string + */ + public function __toString() + { + $value = $this->getValue(); + + if (!\is_array($value)) { + return (string) $value; + } + + return sprintf('%s (count=%d)', $this->getType(), \count($value)); + } + + /** + * Returns a depth limited clone of $this. + * + * @param int $maxDepth The max dumped depth level + * + * @return static + */ + public function withMaxDepth($maxDepth) + { + $data = clone $this; + $data->maxDepth = (int) $maxDepth; + + return $data; + } + + /** + * Limits the number of elements per depth level. + * + * @param int $maxItemsPerDepth The max number of items dumped per depth level + * + * @return static + */ + public function withMaxItemsPerDepth($maxItemsPerDepth) + { + $data = clone $this; + $data->maxItemsPerDepth = (int) $maxItemsPerDepth; + + return $data; + } + + /** + * Enables/disables objects' identifiers tracking. + * + * @param bool $useRefHandles False to hide global ref. handles + * + * @return static + */ + public function withRefHandles($useRefHandles) + { + $data = clone $this; + $data->useRefHandles = $useRefHandles ? -1 : 0; + + return $data; + } + + /** + * @return static + */ + public function withContext(array $context) + { + $data = clone $this; + $data->context = $context; + + return $data; + } + + /** + * Seeks to a specific key in nested data structures. + * + * @param string|int $key The key to seek to + * + * @return static|null Null if the key is not set + */ + public function seek($key) + { + $item = $this->data[$this->position][$this->key]; + + if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) { + $item = $item->value; + } + if (!($item = $this->getStub($item)) instanceof Stub || !$item->position) { + return null; + } + $keys = [$key]; + + switch ($item->type) { + case Stub::TYPE_OBJECT: + $keys[] = Caster::PREFIX_DYNAMIC.$key; + $keys[] = Caster::PREFIX_PROTECTED.$key; + $keys[] = Caster::PREFIX_VIRTUAL.$key; + $keys[] = "\0$item->class\0$key"; + // no break + case Stub::TYPE_ARRAY: + case Stub::TYPE_RESOURCE: + break; + default: + return null; + } + + $data = null; + $children = $this->data[$item->position]; + + foreach ($keys as $key) { + if (isset($children[$key]) || \array_key_exists($key, $children)) { + $data = clone $this; + $data->key = $key; + $data->position = $item->position; + break; + } + } + + return $data; + } + + /** + * Dumps data with a DumperInterface dumper. + */ + public function dump(DumperInterface $dumper) + { + $refs = [0]; + $cursor = new Cursor(); + + if ($cursor->attr = $this->context[SourceContextProvider::class] ?? []) { + $cursor->attr['if_links'] = true; + $cursor->hashType = -1; + $dumper->dumpScalar($cursor, 'default', '^'); + $cursor->attr = ['if_links' => true]; + $dumper->dumpScalar($cursor, 'default', ' '); + $cursor->hashType = 0; + } + + $this->dumpItem($dumper, $cursor, $refs, $this->data[$this->position][$this->key]); + } + + /** + * Depth-first dumping of items. + * + * @param mixed $item A Stub object or the original value being dumped + */ + private function dumpItem(DumperInterface $dumper, Cursor $cursor, array &$refs, $item) + { + $cursor->refIndex = 0; + $cursor->softRefTo = $cursor->softRefHandle = $cursor->softRefCount = 0; + $cursor->hardRefTo = $cursor->hardRefHandle = $cursor->hardRefCount = 0; + $firstSeen = true; + + if (!$item instanceof Stub) { + $cursor->attr = []; + $type = \gettype($item); + if ($item && 'array' === $type) { + $item = $this->getStub($item); + } + } elseif (Stub::TYPE_REF === $item->type) { + if ($item->handle) { + if (!isset($refs[$r = $item->handle - (\PHP_INT_MAX >> 1)])) { + $cursor->refIndex = $refs[$r] = $cursor->refIndex ?: ++$refs[0]; + } else { + $firstSeen = false; + } + $cursor->hardRefTo = $refs[$r]; + $cursor->hardRefHandle = $this->useRefHandles & $item->handle; + $cursor->hardRefCount = 0 < $item->handle ? $item->refCount : 0; + } + $cursor->attr = $item->attr; + $type = $item->class ?: \gettype($item->value); + $item = $this->getStub($item->value); + } + if ($item instanceof Stub) { + if ($item->refCount) { + if (!isset($refs[$r = $item->handle])) { + $cursor->refIndex = $refs[$r] = $cursor->refIndex ?: ++$refs[0]; + } else { + $firstSeen = false; + } + $cursor->softRefTo = $refs[$r]; + } + $cursor->softRefHandle = $this->useRefHandles & $item->handle; + $cursor->softRefCount = $item->refCount; + $cursor->attr = $item->attr; + $cut = $item->cut; + + if ($item->position && $firstSeen) { + $children = $this->data[$item->position]; + + if ($cursor->stop) { + if ($cut >= 0) { + $cut += \count($children); + } + $children = []; + } + } else { + $children = []; + } + switch ($item->type) { + case Stub::TYPE_STRING: + $dumper->dumpString($cursor, $item->value, Stub::STRING_BINARY === $item->class, $cut); + break; + + case Stub::TYPE_ARRAY: + $item = clone $item; + $item->type = $item->class; + $item->class = $item->value; + // no break + case Stub::TYPE_OBJECT: + case Stub::TYPE_RESOURCE: + $withChildren = $children && $cursor->depth !== $this->maxDepth && $this->maxItemsPerDepth; + $dumper->enterHash($cursor, $item->type, $item->class, $withChildren); + if ($withChildren) { + if ($cursor->skipChildren) { + $withChildren = false; + $cut = -1; + } else { + $cut = $this->dumpChildren($dumper, $cursor, $refs, $children, $cut, $item->type, null !== $item->class); + } + } elseif ($children && 0 <= $cut) { + $cut += \count($children); + } + $cursor->skipChildren = false; + $dumper->leaveHash($cursor, $item->type, $item->class, $withChildren, $cut); + break; + + default: + throw new \RuntimeException(sprintf('Unexpected Stub type: "%s".', $item->type)); + } + } elseif ('array' === $type) { + $dumper->enterHash($cursor, Cursor::HASH_INDEXED, 0, false); + $dumper->leaveHash($cursor, Cursor::HASH_INDEXED, 0, false, 0); + } elseif ('string' === $type) { + $dumper->dumpString($cursor, $item, false, 0); + } else { + $dumper->dumpScalar($cursor, $type, $item); + } + } + + /** + * Dumps children of hash structures. + * + * @return int The final number of removed items + */ + private function dumpChildren(DumperInterface $dumper, Cursor $parentCursor, array &$refs, array $children, int $hashCut, int $hashType, bool $dumpKeys): int + { + $cursor = clone $parentCursor; + ++$cursor->depth; + $cursor->hashType = $hashType; + $cursor->hashIndex = 0; + $cursor->hashLength = \count($children); + $cursor->hashCut = $hashCut; + foreach ($children as $key => $child) { + $cursor->hashKeyIsBinary = isset($key[0]) && !preg_match('//u', $key); + $cursor->hashKey = $dumpKeys ? $key : null; + $this->dumpItem($dumper, $cursor, $refs, $child); + if (++$cursor->hashIndex === $this->maxItemsPerDepth || $cursor->stop) { + $parentCursor->stop = true; + + return $hashCut >= 0 ? $hashCut + $cursor->hashLength - $cursor->hashIndex : $hashCut; + } + } + + return $hashCut; + } + + private function getStub($item) + { + if (!$item || !\is_array($item)) { + return $item; + } + + $stub = new Stub(); + $stub->type = Stub::TYPE_ARRAY; + foreach ($item as $stub->class => $stub->position) { + } + if (isset($item[0])) { + $stub->cut = $item[0]; + } + $stub->value = $stub->cut + ($stub->position ? \count($this->data[$stub->position]) : 0); + + return $stub; + } +} diff --git a/vendor/symfony/var-dumper/Cloner/DumperInterface.php b/vendor/symfony/var-dumper/Cloner/DumperInterface.php new file mode 100644 index 0000000..ec8ef27 --- /dev/null +++ b/vendor/symfony/var-dumper/Cloner/DumperInterface.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Cloner; + +/** + * DumperInterface used by Data objects. + * + * @author Nicolas Grekas + */ +interface DumperInterface +{ + /** + * Dumps a scalar value. + * + * @param string $type The PHP type of the value being dumped + * @param string|int|float|bool $value The scalar value being dumped + */ + public function dumpScalar(Cursor $cursor, $type, $value); + + /** + * Dumps a string. + * + * @param string $str The string being dumped + * @param bool $bin Whether $str is UTF-8 or binary encoded + * @param int $cut The number of characters $str has been cut by + */ + public function dumpString(Cursor $cursor, $str, $bin, $cut); + + /** + * Dumps while entering an hash. + * + * @param int $type A Cursor::HASH_* const for the type of hash + * @param string|int $class The object class, resource type or array count + * @param bool $hasChild When the dump of the hash has child item + */ + public function enterHash(Cursor $cursor, $type, $class, $hasChild); + + /** + * Dumps while leaving an hash. + * + * @param int $type A Cursor::HASH_* const for the type of hash + * @param string|int $class The object class, resource type or array count + * @param bool $hasChild When the dump of the hash has child item + * @param int $cut The number of items the hash has been cut by + */ + public function leaveHash(Cursor $cursor, $type, $class, $hasChild, $cut); +} diff --git a/vendor/symfony/var-dumper/Cloner/Stub.php b/vendor/symfony/var-dumper/Cloner/Stub.php new file mode 100644 index 0000000..073c56e --- /dev/null +++ b/vendor/symfony/var-dumper/Cloner/Stub.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Cloner; + +/** + * Represents the main properties of a PHP variable. + * + * @author Nicolas Grekas + */ +class Stub +{ + public const TYPE_REF = 1; + public const TYPE_STRING = 2; + public const TYPE_ARRAY = 3; + public const TYPE_OBJECT = 4; + public const TYPE_RESOURCE = 5; + + public const STRING_BINARY = 1; + public const STRING_UTF8 = 2; + + public const ARRAY_ASSOC = 1; + public const ARRAY_INDEXED = 2; + + public $type = self::TYPE_REF; + public $class = ''; + public $value; + public $cut = 0; + public $handle = 0; + public $refCount = 0; + public $position = 0; + public $attr = []; + + private static $defaultProperties = []; + + /** + * @internal + */ + public function __sleep(): array + { + $properties = []; + + if (!isset(self::$defaultProperties[$c = static::class])) { + self::$defaultProperties[$c] = get_class_vars($c); + + foreach ((new \ReflectionClass($c))->getStaticProperties() as $k => $v) { + unset(self::$defaultProperties[$c][$k]); + } + } + + foreach (self::$defaultProperties[$c] as $k => $v) { + if ($this->$k !== $v) { + $properties[] = $k; + } + } + + return $properties; + } +} diff --git a/vendor/symfony/var-dumper/Cloner/VarCloner.php b/vendor/symfony/var-dumper/Cloner/VarCloner.php new file mode 100644 index 0000000..cd6e7dc --- /dev/null +++ b/vendor/symfony/var-dumper/Cloner/VarCloner.php @@ -0,0 +1,324 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Cloner; + +/** + * @author Nicolas Grekas + */ +class VarCloner extends AbstractCloner +{ + private static $gid; + private static $arrayCache = []; + + /** + * {@inheritdoc} + */ + protected function doClone($var) + { + $len = 1; // Length of $queue + $pos = 0; // Number of cloned items past the minimum depth + $refsCounter = 0; // Hard references counter + $queue = [[$var]]; // This breadth-first queue is the return value + $indexedArrays = []; // Map of queue indexes that hold numerically indexed arrays + $hardRefs = []; // Map of original zval ids to stub objects + $objRefs = []; // Map of original object handles to their stub object counterpart + $objects = []; // Keep a ref to objects to ensure their handle cannot be reused while cloning + $resRefs = []; // Map of original resource handles to their stub object counterpart + $values = []; // Map of stub objects' ids to original values + $maxItems = $this->maxItems; + $maxString = $this->maxString; + $minDepth = $this->minDepth; + $currentDepth = 0; // Current tree depth + $currentDepthFinalIndex = 0; // Final $queue index for current tree depth + $minimumDepthReached = 0 === $minDepth; // Becomes true when minimum tree depth has been reached + $cookie = (object) []; // Unique object used to detect hard references + $a = null; // Array cast for nested structures + $stub = null; // Stub capturing the main properties of an original item value + // or null if the original value is used directly + + if (!$gid = self::$gid) { + $gid = self::$gid = md5(random_bytes(6)); // Unique string used to detect the special $GLOBALS variable + } + $arrayStub = new Stub(); + $arrayStub->type = Stub::TYPE_ARRAY; + $fromObjCast = false; + + for ($i = 0; $i < $len; ++$i) { + // Detect when we move on to the next tree depth + if ($i > $currentDepthFinalIndex) { + ++$currentDepth; + $currentDepthFinalIndex = $len - 1; + if ($currentDepth >= $minDepth) { + $minimumDepthReached = true; + } + } + + $refs = $vals = $queue[$i]; + if (\PHP_VERSION_ID < 70200 && empty($indexedArrays[$i])) { + // see https://wiki.php.net/rfc/convert_numeric_keys_in_object_array_casts + foreach ($vals as $k => $v) { + if (\is_int($k)) { + continue; + } + foreach ([$k => true] as $gk => $gv) { + } + if ($gk !== $k) { + $fromObjCast = true; + $refs = $vals = array_values($queue[$i]); + break; + } + } + } + foreach ($vals as $k => $v) { + // $v is the original value or a stub object in case of hard references + + if (\PHP_VERSION_ID >= 70400) { + $zvalRef = ($r = \ReflectionReference::fromArrayElement($vals, $k)) ? $r->getId() : null; + } else { + $refs[$k] = $cookie; + $zvalRef = $vals[$k] === $cookie; + } + + if ($zvalRef) { + $vals[$k] = &$stub; // Break hard references to make $queue completely + unset($stub); // independent from the original structure + if (\PHP_VERSION_ID >= 70400 ? null !== $vals[$k] = $hardRefs[$zvalRef] ?? null : $v instanceof Stub && isset($hardRefs[spl_object_id($v)])) { + if (\PHP_VERSION_ID >= 70400) { + $v = $vals[$k]; + } else { + $refs[$k] = $vals[$k] = $v; + } + if ($v->value instanceof Stub && (Stub::TYPE_OBJECT === $v->value->type || Stub::TYPE_RESOURCE === $v->value->type)) { + ++$v->value->refCount; + } + ++$v->refCount; + continue; + } + $vals[$k] = new Stub(); + $vals[$k]->value = $v; + $vals[$k]->handle = ++$refsCounter; + + if (\PHP_VERSION_ID >= 70400) { + $hardRefs[$zvalRef] = $vals[$k]; + } else { + $refs[$k] = $vals[$k]; + $h = spl_object_id($refs[$k]); + $hardRefs[$h] = &$refs[$k]; + $values[$h] = $v; + } + } + // Create $stub when the original value $v can not be used directly + // If $v is a nested structure, put that structure in array $a + switch (true) { + case null === $v: + case \is_bool($v): + case \is_int($v): + case \is_float($v): + continue 2; + case \is_string($v): + if ('' === $v) { + continue 2; + } + if (!preg_match('//u', $v)) { + $stub = new Stub(); + $stub->type = Stub::TYPE_STRING; + $stub->class = Stub::STRING_BINARY; + if (0 <= $maxString && 0 < $cut = \strlen($v) - $maxString) { + $stub->cut = $cut; + $stub->value = substr($v, 0, -$cut); + } else { + $stub->value = $v; + } + } elseif (0 <= $maxString && isset($v[1 + ($maxString >> 2)]) && 0 < $cut = mb_strlen($v, 'UTF-8') - $maxString) { + $stub = new Stub(); + $stub->type = Stub::TYPE_STRING; + $stub->class = Stub::STRING_UTF8; + $stub->cut = $cut; + $stub->value = mb_substr($v, 0, $maxString, 'UTF-8'); + } else { + continue 2; + } + $a = null; + break; + + case \is_array($v): + if (!$v) { + continue 2; + } + $stub = $arrayStub; + $stub->class = Stub::ARRAY_INDEXED; + + $j = -1; + foreach ($v as $gk => $gv) { + if ($gk !== ++$j) { + $stub->class = Stub::ARRAY_ASSOC; + break; + } + } + $a = $v; + + if (Stub::ARRAY_ASSOC === $stub->class) { + // Copies of $GLOBALS have very strange behavior, + // let's detect them with some black magic + if (\PHP_VERSION_ID < 80100 && ($a[$gid] = true) && isset($v[$gid])) { + unset($v[$gid]); + $a = []; + foreach ($v as $gk => &$gv) { + if ($v === $gv && (\PHP_VERSION_ID < 70400 || !isset($hardRefs[\ReflectionReference::fromArrayElement($v, $gk)->getId()]))) { + unset($v); + $v = new Stub(); + $v->value = [$v->cut = \count($gv), Stub::TYPE_ARRAY => 0]; + $v->handle = -1; + if (\PHP_VERSION_ID >= 70400) { + $gv = &$a[$gk]; + $hardRefs[\ReflectionReference::fromArrayElement($a, $gk)->getId()] = &$gv; + } else { + $gv = &$hardRefs[spl_object_id($v)]; + } + $gv = $v; + } + + $a[$gk] = &$gv; + } + unset($gv); + } else { + $a = $v; + } + } elseif (\PHP_VERSION_ID < 70200) { + $indexedArrays[$len] = true; + } + break; + + case \is_object($v): + case $v instanceof \__PHP_Incomplete_Class: + if (empty($objRefs[$h = spl_object_id($v)])) { + $stub = new Stub(); + $stub->type = Stub::TYPE_OBJECT; + $stub->class = \get_class($v); + $stub->value = $v; + $stub->handle = $h; + $a = $this->castObject($stub, 0 < $i); + if ($v !== $stub->value) { + if (Stub::TYPE_OBJECT !== $stub->type || null === $stub->value) { + break; + } + $stub->handle = $h = spl_object_id($stub->value); + } + $stub->value = null; + if (0 <= $maxItems && $maxItems <= $pos && $minimumDepthReached) { + $stub->cut = \count($a); + $a = null; + } + } + if (empty($objRefs[$h])) { + $objRefs[$h] = $stub; + $objects[] = $v; + } else { + $stub = $objRefs[$h]; + ++$stub->refCount; + $a = null; + } + break; + + default: // resource + if (empty($resRefs[$h = (int) $v])) { + $stub = new Stub(); + $stub->type = Stub::TYPE_RESOURCE; + if ('Unknown' === $stub->class = @get_resource_type($v)) { + $stub->class = 'Closed'; + } + $stub->value = $v; + $stub->handle = $h; + $a = $this->castResource($stub, 0 < $i); + $stub->value = null; + if (0 <= $maxItems && $maxItems <= $pos && $minimumDepthReached) { + $stub->cut = \count($a); + $a = null; + } + } + if (empty($resRefs[$h])) { + $resRefs[$h] = $stub; + } else { + $stub = $resRefs[$h]; + ++$stub->refCount; + $a = null; + } + break; + } + + if ($a) { + if (!$minimumDepthReached || 0 > $maxItems) { + $queue[$len] = $a; + $stub->position = $len++; + } elseif ($pos < $maxItems) { + if ($maxItems < $pos += \count($a)) { + $a = \array_slice($a, 0, $maxItems - $pos, true); + if ($stub->cut >= 0) { + $stub->cut += $pos - $maxItems; + } + } + $queue[$len] = $a; + $stub->position = $len++; + } elseif ($stub->cut >= 0) { + $stub->cut += \count($a); + $stub->position = 0; + } + } + + if ($arrayStub === $stub) { + if ($arrayStub->cut) { + $stub = [$arrayStub->cut, $arrayStub->class => $arrayStub->position]; + $arrayStub->cut = 0; + } elseif (isset(self::$arrayCache[$arrayStub->class][$arrayStub->position])) { + $stub = self::$arrayCache[$arrayStub->class][$arrayStub->position]; + } else { + self::$arrayCache[$arrayStub->class][$arrayStub->position] = $stub = [$arrayStub->class => $arrayStub->position]; + } + } + + if (!$zvalRef) { + $vals[$k] = $stub; + } elseif (\PHP_VERSION_ID >= 70400) { + $hardRefs[$zvalRef]->value = $stub; + } else { + $refs[$k]->value = $stub; + } + } + + if ($fromObjCast) { + $fromObjCast = false; + $refs = $vals; + $vals = []; + $j = -1; + foreach ($queue[$i] as $k => $v) { + foreach ([$k => true] as $gk => $gv) { + } + if ($gk !== $k) { + $vals = (object) $vals; + $vals->{$k} = $refs[++$j]; + $vals = (array) $vals; + } else { + $vals[$k] = $refs[++$j]; + } + } + } + + $queue[$i] = $vals; + } + + foreach ($values as $h => $v) { + $hardRefs[$h] = $v; + } + + return $queue; + } +} diff --git a/vendor/symfony/var-dumper/Command/Descriptor/CliDescriptor.php b/vendor/symfony/var-dumper/Command/Descriptor/CliDescriptor.php new file mode 100644 index 0000000..7d9ec0e --- /dev/null +++ b/vendor/symfony/var-dumper/Command/Descriptor/CliDescriptor.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Command\Descriptor; + +use Symfony\Component\Console\Formatter\OutputFormatterStyle; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Dumper\CliDumper; + +/** + * Describe collected data clones for cli output. + * + * @author Maxime Steinhausser + * + * @final + */ +class CliDescriptor implements DumpDescriptorInterface +{ + private $dumper; + private $lastIdentifier; + private $supportsHref; + + public function __construct(CliDumper $dumper) + { + $this->dumper = $dumper; + $this->supportsHref = method_exists(OutputFormatterStyle::class, 'setHref'); + } + + public function describe(OutputInterface $output, Data $data, array $context, int $clientId): void + { + $io = $output instanceof SymfonyStyle ? $output : new SymfonyStyle(new ArrayInput([]), $output); + $this->dumper->setColors($output->isDecorated()); + + $rows = [['date', date('r', (int) $context['timestamp'])]]; + $lastIdentifier = $this->lastIdentifier; + $this->lastIdentifier = $clientId; + + $section = "Received from client #$clientId"; + if (isset($context['request'])) { + $request = $context['request']; + $this->lastIdentifier = $request['identifier']; + $section = sprintf('%s %s', $request['method'], $request['uri']); + if ($controller = $request['controller']) { + $rows[] = ['controller', rtrim($this->dumper->dump($controller, true), "\n")]; + } + } elseif (isset($context['cli'])) { + $this->lastIdentifier = $context['cli']['identifier']; + $section = '$ '.$context['cli']['command_line']; + } + + if ($this->lastIdentifier !== $lastIdentifier) { + $io->section($section); + } + + if (isset($context['source'])) { + $source = $context['source']; + $sourceInfo = sprintf('%s on line %d', $source['name'], $source['line']); + $fileLink = $source['file_link'] ?? null; + if ($this->supportsHref && $fileLink) { + $sourceInfo = sprintf('%s', $fileLink, $sourceInfo); + } + $rows[] = ['source', $sourceInfo]; + $file = $source['file_relative'] ?? $source['file']; + $rows[] = ['file', $file]; + } + + $io->table([], $rows); + + if (!$this->supportsHref && isset($fileLink)) { + $io->writeln(['Open source in your IDE/browser:', $fileLink]); + $io->newLine(); + } + + $this->dumper->dump($data); + $io->newLine(); + } +} diff --git a/vendor/symfony/var-dumper/Command/Descriptor/DumpDescriptorInterface.php b/vendor/symfony/var-dumper/Command/Descriptor/DumpDescriptorInterface.php new file mode 100644 index 0000000..267d27b --- /dev/null +++ b/vendor/symfony/var-dumper/Command/Descriptor/DumpDescriptorInterface.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Command\Descriptor; + +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\VarDumper\Cloner\Data; + +/** + * @author Maxime Steinhausser + */ +interface DumpDescriptorInterface +{ + public function describe(OutputInterface $output, Data $data, array $context, int $clientId): void; +} diff --git a/vendor/symfony/var-dumper/Command/Descriptor/HtmlDescriptor.php b/vendor/symfony/var-dumper/Command/Descriptor/HtmlDescriptor.php new file mode 100644 index 0000000..636b618 --- /dev/null +++ b/vendor/symfony/var-dumper/Command/Descriptor/HtmlDescriptor.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Command\Descriptor; + +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; + +/** + * Describe collected data clones for html output. + * + * @author Maxime Steinhausser + * + * @final + */ +class HtmlDescriptor implements DumpDescriptorInterface +{ + private $dumper; + private $initialized = false; + + public function __construct(HtmlDumper $dumper) + { + $this->dumper = $dumper; + } + + public function describe(OutputInterface $output, Data $data, array $context, int $clientId): void + { + if (!$this->initialized) { + $styles = file_get_contents(__DIR__.'/../../Resources/css/htmlDescriptor.css'); + $scripts = file_get_contents(__DIR__.'/../../Resources/js/htmlDescriptor.js'); + $output->writeln(""); + $this->initialized = true; + } + + $title = '-'; + if (isset($context['request'])) { + $request = $context['request']; + $controller = "{$this->dumper->dump($request['controller'], true, ['maxDepth' => 0])}"; + $title = sprintf('%s %s', $request['method'], $uri = $request['uri'], $uri); + $dedupIdentifier = $request['identifier']; + } elseif (isset($context['cli'])) { + $title = '$ '.$context['cli']['command_line']; + $dedupIdentifier = $context['cli']['identifier']; + } else { + $dedupIdentifier = uniqid('', true); + } + + $sourceDescription = ''; + if (isset($context['source'])) { + $source = $context['source']; + $projectDir = $source['project_dir'] ?? null; + $sourceDescription = sprintf('%s on line %d', $source['name'], $source['line']); + if (isset($source['file_link'])) { + $sourceDescription = sprintf('%s', $source['file_link'], $sourceDescription); + } + } + + $isoDate = $this->extractDate($context, 'c'); + $tags = array_filter([ + 'controller' => $controller ?? null, + 'project dir' => $projectDir ?? null, + ]); + + $output->writeln(<< +
        +
        +

        $title

        + +
        + {$this->renderTags($tags)} +
        +
        +

        + $sourceDescription +

        + {$this->dumper->dump($data, true)} +
        + +HTML + ); + } + + private function extractDate(array $context, string $format = 'r'): string + { + return date($format, (int) $context['timestamp']); + } + + private function renderTags(array $tags): string + { + if (!$tags) { + return ''; + } + + $renderedTags = ''; + foreach ($tags as $key => $value) { + $renderedTags .= sprintf('
      • %s%s
      • ', $key, $value); + } + + return << +
          + $renderedTags +
        +
        +HTML; + } +} diff --git a/vendor/symfony/var-dumper/Command/ServerDumpCommand.php b/vendor/symfony/var-dumper/Command/ServerDumpCommand.php new file mode 100644 index 0000000..b66301b --- /dev/null +++ b/vendor/symfony/var-dumper/Command/ServerDumpCommand.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Command\Descriptor\CliDescriptor; +use Symfony\Component\VarDumper\Command\Descriptor\DumpDescriptorInterface; +use Symfony\Component\VarDumper\Command\Descriptor\HtmlDescriptor; +use Symfony\Component\VarDumper\Dumper\CliDumper; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; +use Symfony\Component\VarDumper\Server\DumpServer; + +/** + * Starts a dump server to collect and output dumps on a single place with multiple formats support. + * + * @author Maxime Steinhausser + * + * @final + */ +class ServerDumpCommand extends Command +{ + protected static $defaultName = 'server:dump'; + + private $server; + + /** @var DumpDescriptorInterface[] */ + private $descriptors; + + public function __construct(DumpServer $server, array $descriptors = []) + { + $this->server = $server; + $this->descriptors = $descriptors + [ + 'cli' => new CliDescriptor(new CliDumper()), + 'html' => new HtmlDescriptor(new HtmlDumper()), + ]; + + parent::__construct(); + } + + protected function configure() + { + $availableFormats = implode(', ', array_keys($this->descriptors)); + + $this + ->addOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format (%s)', $availableFormats), 'cli') + ->setDescription('Start a dump server that collects and displays dumps in a single place') + ->setHelp(<<<'EOF' +%command.name% starts a dump server that collects and displays +dumps in a single place for debugging you application: + + php %command.full_name% + +You can consult dumped data in HTML format in your browser by providing the --format=html option +and redirecting the output to a file: + + php %command.full_name% --format="html" > dump.html + +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $format = $input->getOption('format'); + + if (!$descriptor = $this->descriptors[$format] ?? null) { + throw new InvalidArgumentException(sprintf('Unsupported format "%s".', $format)); + } + + $errorIo = $io->getErrorStyle(); + $errorIo->title('Symfony Var Dumper Server'); + + $this->server->start(); + + $errorIo->success(sprintf('Server listening on %s', $this->server->getHost())); + $errorIo->comment('Quit the server with CONTROL-C.'); + + $this->server->listen(function (Data $data, array $context, int $clientId) use ($descriptor, $io) { + $descriptor->describe($io, $data, $context, $clientId); + }); + + return 0; + } +} diff --git a/vendor/symfony/var-dumper/Dumper/AbstractDumper.php b/vendor/symfony/var-dumper/Dumper/AbstractDumper.php new file mode 100644 index 0000000..4ddaf5e --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/AbstractDumper.php @@ -0,0 +1,212 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper; + +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Cloner\DumperInterface; + +/** + * Abstract mechanism for dumping a Data object. + * + * @author Nicolas Grekas + */ +abstract class AbstractDumper implements DataDumperInterface, DumperInterface +{ + public const DUMP_LIGHT_ARRAY = 1; + public const DUMP_STRING_LENGTH = 2; + public const DUMP_COMMA_SEPARATOR = 4; + public const DUMP_TRAILING_COMMA = 8; + + public static $defaultOutput = 'php://output'; + + protected $line = ''; + protected $lineDumper; + protected $outputStream; + protected $decimalPoint; // This is locale dependent + protected $indentPad = ' '; + protected $flags; + + private $charset = ''; + + /** + * @param callable|resource|string|null $output A line dumper callable, an opened stream or an output path, defaults to static::$defaultOutput + * @param string|null $charset The default character encoding to use for non-UTF8 strings + * @param int $flags A bit field of static::DUMP_* constants to fine tune dumps representation + */ + public function __construct($output = null, string $charset = null, int $flags = 0) + { + $this->flags = $flags; + $this->setCharset($charset ?: ini_get('php.output_encoding') ?: ini_get('default_charset') ?: 'UTF-8'); + $this->decimalPoint = localeconv(); + $this->decimalPoint = $this->decimalPoint['decimal_point']; + $this->setOutput($output ?: static::$defaultOutput); + if (!$output && \is_string(static::$defaultOutput)) { + static::$defaultOutput = $this->outputStream; + } + } + + /** + * Sets the output destination of the dumps. + * + * @param callable|resource|string $output A line dumper callable, an opened stream or an output path + * + * @return callable|resource|string The previous output destination + */ + public function setOutput($output) + { + $prev = $this->outputStream ?? $this->lineDumper; + + if (\is_callable($output)) { + $this->outputStream = null; + $this->lineDumper = $output; + } else { + if (\is_string($output)) { + $output = fopen($output, 'w'); + } + $this->outputStream = $output; + $this->lineDumper = [$this, 'echoLine']; + } + + return $prev; + } + + /** + * Sets the default character encoding to use for non-UTF8 strings. + * + * @param string $charset The default character encoding to use for non-UTF8 strings + * + * @return string The previous charset + */ + public function setCharset($charset) + { + $prev = $this->charset; + + $charset = strtoupper($charset); + $charset = null === $charset || 'UTF-8' === $charset || 'UTF8' === $charset ? 'CP1252' : $charset; + + $this->charset = $charset; + + return $prev; + } + + /** + * Sets the indentation pad string. + * + * @param string $pad A string that will be prepended to dumped lines, repeated by nesting level + * + * @return string The previous indent pad + */ + public function setIndentPad($pad) + { + $prev = $this->indentPad; + $this->indentPad = $pad; + + return $prev; + } + + /** + * Dumps a Data object. + * + * @param callable|resource|string|true|null $output A line dumper callable, an opened stream, an output path or true to return the dump + * + * @return string|null The dump as string when $output is true + */ + public function dump(Data $data, $output = null) + { + $this->decimalPoint = localeconv(); + $this->decimalPoint = $this->decimalPoint['decimal_point']; + + if ($locale = $this->flags & (self::DUMP_COMMA_SEPARATOR | self::DUMP_TRAILING_COMMA) ? setlocale(\LC_NUMERIC, 0) : null) { + setlocale(\LC_NUMERIC, 'C'); + } + + if ($returnDump = true === $output) { + $output = fopen('php://memory', 'r+'); + } + if ($output) { + $prevOutput = $this->setOutput($output); + } + try { + $data->dump($this); + $this->dumpLine(-1); + + if ($returnDump) { + $result = stream_get_contents($output, -1, 0); + fclose($output); + + return $result; + } + } finally { + if ($output) { + $this->setOutput($prevOutput); + } + if ($locale) { + setlocale(\LC_NUMERIC, $locale); + } + } + + return null; + } + + /** + * Dumps the current line. + * + * @param int $depth The recursive depth in the dumped structure for the line being dumped, + * or -1 to signal the end-of-dump to the line dumper callable + */ + protected function dumpLine($depth) + { + ($this->lineDumper)($this->line, $depth, $this->indentPad); + $this->line = ''; + } + + /** + * Generic line dumper callback. + * + * @param string $line The line to write + * @param int $depth The recursive depth in the dumped structure + * @param string $indentPad The line indent pad + */ + protected function echoLine($line, $depth, $indentPad) + { + if (-1 !== $depth) { + fwrite($this->outputStream, str_repeat($indentPad, $depth).$line."\n"); + } + } + + /** + * Converts a non-UTF-8 string to UTF-8. + * + * @param string|null $s The non-UTF-8 string to convert + * + * @return string|null The string converted to UTF-8 + */ + protected function utf8Encode($s) + { + if (null === $s || preg_match('//u', $s)) { + return $s; + } + + if (!\function_exists('iconv')) { + throw new \RuntimeException('Unable to convert a non-UTF-8 string to UTF-8: required function iconv() does not exist. You should install ext-iconv or symfony/polyfill-iconv.'); + } + + if (false !== $c = @iconv($this->charset, 'UTF-8', $s)) { + return $c; + } + if ('CP1252' !== $this->charset && false !== $c = @iconv('CP1252', 'UTF-8', $s)) { + return $c; + } + + return iconv('CP850', 'UTF-8', $s); + } +} diff --git a/vendor/symfony/var-dumper/Dumper/CliDumper.php b/vendor/symfony/var-dumper/Dumper/CliDumper.php new file mode 100644 index 0000000..b3d9e25 --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/CliDumper.php @@ -0,0 +1,655 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper; + +use Symfony\Component\VarDumper\Cloner\Cursor; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * CliDumper dumps variables for command line output. + * + * @author Nicolas Grekas + */ +class CliDumper extends AbstractDumper +{ + public static $defaultColors; + public static $defaultOutput = 'php://stdout'; + + protected $colors; + protected $maxStringWidth = 0; + protected $styles = [ + // See http://en.wikipedia.org/wiki/ANSI_escape_code#graphics + 'default' => '0;38;5;208', + 'num' => '1;38;5;38', + 'const' => '1;38;5;208', + 'str' => '1;38;5;113', + 'note' => '38;5;38', + 'ref' => '38;5;247', + 'public' => '', + 'protected' => '', + 'private' => '', + 'meta' => '38;5;170', + 'key' => '38;5;113', + 'index' => '38;5;38', + ]; + + protected static $controlCharsRx = '/[\x00-\x1F\x7F]+/'; + protected static $controlCharsMap = [ + "\t" => '\t', + "\n" => '\n', + "\v" => '\v', + "\f" => '\f', + "\r" => '\r', + "\033" => '\e', + ]; + + protected $collapseNextHash = false; + protected $expandNextHash = false; + + private $displayOptions = [ + 'fileLinkFormat' => null, + ]; + + private $handlesHrefGracefully; + + /** + * {@inheritdoc} + */ + public function __construct($output = null, string $charset = null, int $flags = 0) + { + parent::__construct($output, $charset, $flags); + + if ('\\' === \DIRECTORY_SEPARATOR && !$this->isWindowsTrueColor()) { + // Use only the base 16 xterm colors when using ANSICON or standard Windows 10 CLI + $this->setStyles([ + 'default' => '31', + 'num' => '1;34', + 'const' => '1;31', + 'str' => '1;32', + 'note' => '34', + 'ref' => '1;30', + 'meta' => '35', + 'key' => '32', + 'index' => '34', + ]); + } + + $this->displayOptions['fileLinkFormat'] = ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format') ?: 'file://%f#L%l'; + } + + /** + * Enables/disables colored output. + * + * @param bool $colors + */ + public function setColors($colors) + { + $this->colors = (bool) $colors; + } + + /** + * Sets the maximum number of characters per line for dumped strings. + * + * @param int $maxStringWidth + */ + public function setMaxStringWidth($maxStringWidth) + { + $this->maxStringWidth = (int) $maxStringWidth; + } + + /** + * Configures styles. + * + * @param array $styles A map of style names to style definitions + */ + public function setStyles(array $styles) + { + $this->styles = $styles + $this->styles; + } + + /** + * Configures display options. + * + * @param array $displayOptions A map of display options to customize the behavior + */ + public function setDisplayOptions(array $displayOptions) + { + $this->displayOptions = $displayOptions + $this->displayOptions; + } + + /** + * {@inheritdoc} + */ + public function dumpScalar(Cursor $cursor, $type, $value) + { + $this->dumpKey($cursor); + + $style = 'const'; + $attr = $cursor->attr; + + switch ($type) { + case 'default': + $style = 'default'; + break; + + case 'integer': + $style = 'num'; + break; + + case 'double': + $style = 'num'; + + switch (true) { + case \INF === $value: $value = 'INF'; break; + case -\INF === $value: $value = '-INF'; break; + case is_nan($value): $value = 'NAN'; break; + default: + $value = (string) $value; + if (!str_contains($value, $this->decimalPoint)) { + $value .= $this->decimalPoint.'0'; + } + break; + } + break; + + case 'NULL': + $value = 'null'; + break; + + case 'boolean': + $value = $value ? 'true' : 'false'; + break; + + default: + $attr += ['value' => $this->utf8Encode($value)]; + $value = $this->utf8Encode($type); + break; + } + + $this->line .= $this->style($style, $value, $attr); + + $this->endValue($cursor); + } + + /** + * {@inheritdoc} + */ + public function dumpString(Cursor $cursor, $str, $bin, $cut) + { + $this->dumpKey($cursor); + $attr = $cursor->attr; + + if ($bin) { + $str = $this->utf8Encode($str); + } + if ('' === $str) { + $this->line .= '""'; + $this->endValue($cursor); + } else { + $attr += [ + 'length' => 0 <= $cut ? mb_strlen($str, 'UTF-8') + $cut : 0, + 'binary' => $bin, + ]; + $str = explode("\n", $str); + if (isset($str[1]) && !isset($str[2]) && !isset($str[1][0])) { + unset($str[1]); + $str[0] .= "\n"; + } + $m = \count($str) - 1; + $i = $lineCut = 0; + + if (self::DUMP_STRING_LENGTH & $this->flags) { + $this->line .= '('.$attr['length'].') '; + } + if ($bin) { + $this->line .= 'b'; + } + + if ($m) { + $this->line .= '"""'; + $this->dumpLine($cursor->depth); + } else { + $this->line .= '"'; + } + + foreach ($str as $str) { + if ($i < $m) { + $str .= "\n"; + } + if (0 < $this->maxStringWidth && $this->maxStringWidth < $len = mb_strlen($str, 'UTF-8')) { + $str = mb_substr($str, 0, $this->maxStringWidth, 'UTF-8'); + $lineCut = $len - $this->maxStringWidth; + } + if ($m && 0 < $cursor->depth) { + $this->line .= $this->indentPad; + } + if ('' !== $str) { + $this->line .= $this->style('str', $str, $attr); + } + if ($i++ == $m) { + if ($m) { + if ('' !== $str) { + $this->dumpLine($cursor->depth); + if (0 < $cursor->depth) { + $this->line .= $this->indentPad; + } + } + $this->line .= '"""'; + } else { + $this->line .= '"'; + } + if ($cut < 0) { + $this->line .= '…'; + $lineCut = 0; + } elseif ($cut) { + $lineCut += $cut; + } + } + if ($lineCut) { + $this->line .= '…'.$lineCut; + $lineCut = 0; + } + + if ($i > $m) { + $this->endValue($cursor); + } else { + $this->dumpLine($cursor->depth); + } + } + } + } + + /** + * {@inheritdoc} + */ + public function enterHash(Cursor $cursor, $type, $class, $hasChild) + { + if (null === $this->colors) { + $this->colors = $this->supportsColors(); + } + + $this->dumpKey($cursor); + $attr = $cursor->attr; + + if ($this->collapseNextHash) { + $cursor->skipChildren = true; + $this->collapseNextHash = $hasChild = false; + } + + $class = $this->utf8Encode($class); + if (Cursor::HASH_OBJECT === $type) { + $prefix = $class && 'stdClass' !== $class ? $this->style('note', $class, $attr).(empty($attr['cut_hash']) ? ' {' : '') : '{'; + } elseif (Cursor::HASH_RESOURCE === $type) { + $prefix = $this->style('note', $class.' resource', $attr).($hasChild ? ' {' : ' '); + } else { + $prefix = $class && !(self::DUMP_LIGHT_ARRAY & $this->flags) ? $this->style('note', 'array:'.$class).' [' : '['; + } + + if (($cursor->softRefCount || 0 < $cursor->softRefHandle) && empty($attr['cut_hash'])) { + $prefix .= $this->style('ref', (Cursor::HASH_RESOURCE === $type ? '@' : '#').(0 < $cursor->softRefHandle ? $cursor->softRefHandle : $cursor->softRefTo), ['count' => $cursor->softRefCount]); + } elseif ($cursor->hardRefTo && !$cursor->refIndex && $class) { + $prefix .= $this->style('ref', '&'.$cursor->hardRefTo, ['count' => $cursor->hardRefCount]); + } elseif (!$hasChild && Cursor::HASH_RESOURCE === $type) { + $prefix = substr($prefix, 0, -1); + } + + $this->line .= $prefix; + + if ($hasChild) { + $this->dumpLine($cursor->depth); + } + } + + /** + * {@inheritdoc} + */ + public function leaveHash(Cursor $cursor, $type, $class, $hasChild, $cut) + { + if (empty($cursor->attr['cut_hash'])) { + $this->dumpEllipsis($cursor, $hasChild, $cut); + $this->line .= Cursor::HASH_OBJECT === $type ? '}' : (Cursor::HASH_RESOURCE !== $type ? ']' : ($hasChild ? '}' : '')); + } + + $this->endValue($cursor); + } + + /** + * Dumps an ellipsis for cut children. + * + * @param bool $hasChild When the dump of the hash has child item + * @param int $cut The number of items the hash has been cut by + */ + protected function dumpEllipsis(Cursor $cursor, $hasChild, $cut) + { + if ($cut) { + $this->line .= ' …'; + if (0 < $cut) { + $this->line .= $cut; + } + if ($hasChild) { + $this->dumpLine($cursor->depth + 1); + } + } + } + + /** + * Dumps a key in a hash structure. + */ + protected function dumpKey(Cursor $cursor) + { + if (null !== $key = $cursor->hashKey) { + if ($cursor->hashKeyIsBinary) { + $key = $this->utf8Encode($key); + } + $attr = ['binary' => $cursor->hashKeyIsBinary]; + $bin = $cursor->hashKeyIsBinary ? 'b' : ''; + $style = 'key'; + switch ($cursor->hashType) { + default: + case Cursor::HASH_INDEXED: + if (self::DUMP_LIGHT_ARRAY & $this->flags) { + break; + } + $style = 'index'; + // no break + case Cursor::HASH_ASSOC: + if (\is_int($key)) { + $this->line .= $this->style($style, $key).' => '; + } else { + $this->line .= $bin.'"'.$this->style($style, $key).'" => '; + } + break; + + case Cursor::HASH_RESOURCE: + $key = "\0~\0".$key; + // no break + case Cursor::HASH_OBJECT: + if (!isset($key[0]) || "\0" !== $key[0]) { + $this->line .= '+'.$bin.$this->style('public', $key).': '; + } elseif (0 < strpos($key, "\0", 1)) { + $key = explode("\0", substr($key, 1), 2); + + switch ($key[0][0]) { + case '+': // User inserted keys + $attr['dynamic'] = true; + $this->line .= '+'.$bin.'"'.$this->style('public', $key[1], $attr).'": '; + break 2; + case '~': + $style = 'meta'; + if (isset($key[0][1])) { + parse_str(substr($key[0], 1), $attr); + $attr += ['binary' => $cursor->hashKeyIsBinary]; + } + break; + case '*': + $style = 'protected'; + $bin = '#'.$bin; + break; + default: + $attr['class'] = $key[0]; + $style = 'private'; + $bin = '-'.$bin; + break; + } + + if (isset($attr['collapse'])) { + if ($attr['collapse']) { + $this->collapseNextHash = true; + } else { + $this->expandNextHash = true; + } + } + + $this->line .= $bin.$this->style($style, $key[1], $attr).($attr['separator'] ?? ': '); + } else { + // This case should not happen + $this->line .= '-'.$bin.'"'.$this->style('private', $key, ['class' => '']).'": '; + } + break; + } + + if ($cursor->hardRefTo) { + $this->line .= $this->style('ref', '&'.($cursor->hardRefCount ? $cursor->hardRefTo : ''), ['count' => $cursor->hardRefCount]).' '; + } + } + } + + /** + * Decorates a value with some style. + * + * @param string $style The type of style being applied + * @param string $value The value being styled + * @param array $attr Optional context information + * + * @return string The value with style decoration + */ + protected function style($style, $value, $attr = []) + { + if (null === $this->colors) { + $this->colors = $this->supportsColors(); + } + + if (null === $this->handlesHrefGracefully) { + $this->handlesHrefGracefully = 'JetBrains-JediTerm' !== getenv('TERMINAL_EMULATOR') + && (!getenv('KONSOLE_VERSION') || (int) getenv('KONSOLE_VERSION') > 201100); + } + + if (isset($attr['ellipsis'], $attr['ellipsis-type'])) { + $prefix = substr($value, 0, -$attr['ellipsis']); + if ('cli' === \PHP_SAPI && 'path' === $attr['ellipsis-type'] && isset($_SERVER[$pwd = '\\' === \DIRECTORY_SEPARATOR ? 'CD' : 'PWD']) && str_starts_with($prefix, $_SERVER[$pwd])) { + $prefix = '.'.substr($prefix, \strlen($_SERVER[$pwd])); + } + if (!empty($attr['ellipsis-tail'])) { + $prefix .= substr($value, -$attr['ellipsis'], $attr['ellipsis-tail']); + $value = substr($value, -$attr['ellipsis'] + $attr['ellipsis-tail']); + } else { + $value = substr($value, -$attr['ellipsis']); + } + + $value = $this->style('default', $prefix).$this->style($style, $value); + + goto href; + } + + $map = static::$controlCharsMap; + $startCchr = $this->colors ? "\033[m\033[{$this->styles['default']}m" : ''; + $endCchr = $this->colors ? "\033[m\033[{$this->styles[$style]}m" : ''; + $value = preg_replace_callback(static::$controlCharsRx, function ($c) use ($map, $startCchr, $endCchr) { + $s = $startCchr; + $c = $c[$i = 0]; + do { + $s .= $map[$c[$i]] ?? sprintf('\x%02X', \ord($c[$i])); + } while (isset($c[++$i])); + + return $s.$endCchr; + }, $value, -1, $cchrCount); + + if ($this->colors) { + if ($cchrCount && "\033" === $value[0]) { + $value = substr($value, \strlen($startCchr)); + } else { + $value = "\033[{$this->styles[$style]}m".$value; + } + if ($cchrCount && str_ends_with($value, $endCchr)) { + $value = substr($value, 0, -\strlen($endCchr)); + } else { + $value .= "\033[{$this->styles['default']}m"; + } + } + + href: + if ($this->colors && $this->handlesHrefGracefully) { + if (isset($attr['file']) && $href = $this->getSourceLink($attr['file'], $attr['line'] ?? 0)) { + if ('note' === $style) { + $value .= "\033]8;;{$href}\033\\^\033]8;;\033\\"; + } else { + $attr['href'] = $href; + } + } + if (isset($attr['href'])) { + $value = "\033]8;;{$attr['href']}\033\\{$value}\033]8;;\033\\"; + } + } elseif ($attr['if_links'] ?? false) { + return ''; + } + + return $value; + } + + /** + * @return bool Tells if the current output stream supports ANSI colors or not + */ + protected function supportsColors() + { + if ($this->outputStream !== static::$defaultOutput) { + return $this->hasColorSupport($this->outputStream); + } + if (null !== static::$defaultColors) { + return static::$defaultColors; + } + if (isset($_SERVER['argv'][1])) { + $colors = $_SERVER['argv']; + $i = \count($colors); + while (--$i > 0) { + if (isset($colors[$i][5])) { + switch ($colors[$i]) { + case '--ansi': + case '--color': + case '--color=yes': + case '--color=force': + case '--color=always': + return static::$defaultColors = true; + + case '--no-ansi': + case '--color=no': + case '--color=none': + case '--color=never': + return static::$defaultColors = false; + } + } + } + } + + $h = stream_get_meta_data($this->outputStream) + ['wrapper_type' => null]; + $h = 'Output' === $h['stream_type'] && 'PHP' === $h['wrapper_type'] ? fopen('php://stdout', 'w') : $this->outputStream; + + return static::$defaultColors = $this->hasColorSupport($h); + } + + /** + * {@inheritdoc} + */ + protected function dumpLine($depth, $endOfValue = false) + { + if ($this->colors) { + $this->line = sprintf("\033[%sm%s\033[m", $this->styles['default'], $this->line); + } + parent::dumpLine($depth); + } + + protected function endValue(Cursor $cursor) + { + if (-1 === $cursor->hashType) { + return; + } + + if (Stub::ARRAY_INDEXED === $cursor->hashType || Stub::ARRAY_ASSOC === $cursor->hashType) { + if (self::DUMP_TRAILING_COMMA & $this->flags && 0 < $cursor->depth) { + $this->line .= ','; + } elseif (self::DUMP_COMMA_SEPARATOR & $this->flags && 1 < $cursor->hashLength - $cursor->hashIndex) { + $this->line .= ','; + } + } + + $this->dumpLine($cursor->depth, true); + } + + /** + * Returns true if the stream supports colorization. + * + * Reference: Composer\XdebugHandler\Process::supportsColor + * https://github.com/composer/xdebug-handler + * + * @param mixed $stream A CLI output stream + */ + private function hasColorSupport($stream): bool + { + if (!\is_resource($stream) || 'stream' !== get_resource_type($stream)) { + return false; + } + + // Follow https://no-color.org/ + if (isset($_SERVER['NO_COLOR']) || false !== getenv('NO_COLOR')) { + return false; + } + + if ('Hyper' === getenv('TERM_PROGRAM')) { + return true; + } + + if (\DIRECTORY_SEPARATOR === '\\') { + return (\function_exists('sapi_windows_vt100_support') + && @sapi_windows_vt100_support($stream)) + || false !== getenv('ANSICON') + || 'ON' === getenv('ConEmuANSI') + || 'xterm' === getenv('TERM'); + } + + if (\function_exists('stream_isatty')) { + return @stream_isatty($stream); + } + + if (\function_exists('posix_isatty')) { + return @posix_isatty($stream); + } + + $stat = @fstat($stream); + // Check if formatted mode is S_IFCHR + return $stat ? 0020000 === ($stat['mode'] & 0170000) : false; + } + + /** + * Returns true if the Windows terminal supports true color. + * + * Note that this does not check an output stream, but relies on environment + * variables from known implementations, or a PHP and Windows version that + * supports true color. + */ + private function isWindowsTrueColor(): bool + { + $result = 183 <= getenv('ANSICON_VER') + || 'ON' === getenv('ConEmuANSI') + || 'xterm' === getenv('TERM') + || 'Hyper' === getenv('TERM_PROGRAM'); + + if (!$result && \PHP_VERSION_ID >= 70200) { + $version = sprintf( + '%s.%s.%s', + PHP_WINDOWS_VERSION_MAJOR, + PHP_WINDOWS_VERSION_MINOR, + PHP_WINDOWS_VERSION_BUILD + ); + $result = $version >= '10.0.15063'; + } + + return $result; + } + + private function getSourceLink(string $file, int $line) + { + if ($fmt = $this->displayOptions['fileLinkFormat']) { + return \is_string($fmt) ? strtr($fmt, ['%f' => $file, '%l' => $line]) : ($fmt->format($file, $line) ?: 'file://'.$file.'#L'.$line); + } + + return false; + } +} diff --git a/vendor/symfony/var-dumper/Dumper/ContextProvider/CliContextProvider.php b/vendor/symfony/var-dumper/Dumper/ContextProvider/CliContextProvider.php new file mode 100644 index 0000000..38f8789 --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/ContextProvider/CliContextProvider.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper\ContextProvider; + +/** + * Tries to provide context on CLI. + * + * @author Maxime Steinhausser + */ +final class CliContextProvider implements ContextProviderInterface +{ + public function getContext(): ?array + { + if ('cli' !== \PHP_SAPI) { + return null; + } + + return [ + 'command_line' => $commandLine = implode(' ', $_SERVER['argv'] ?? []), + 'identifier' => hash('crc32b', $commandLine.$_SERVER['REQUEST_TIME_FLOAT']), + ]; + } +} diff --git a/vendor/symfony/var-dumper/Dumper/ContextProvider/ContextProviderInterface.php b/vendor/symfony/var-dumper/Dumper/ContextProvider/ContextProviderInterface.php new file mode 100644 index 0000000..38ef3b0 --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/ContextProvider/ContextProviderInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper\ContextProvider; + +/** + * Interface to provide contextual data about dump data clones sent to a server. + * + * @author Maxime Steinhausser + */ +interface ContextProviderInterface +{ + /** + * @return array|null Context data or null if unable to provide any context + */ + public function getContext(): ?array; +} diff --git a/vendor/symfony/var-dumper/Dumper/ContextProvider/RequestContextProvider.php b/vendor/symfony/var-dumper/Dumper/ContextProvider/RequestContextProvider.php new file mode 100644 index 0000000..3684a47 --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/ContextProvider/RequestContextProvider.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper\ContextProvider; + +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\VarDumper\Caster\ReflectionCaster; +use Symfony\Component\VarDumper\Cloner\VarCloner; + +/** + * Tries to provide context from a request. + * + * @author Maxime Steinhausser + */ +final class RequestContextProvider implements ContextProviderInterface +{ + private $requestStack; + private $cloner; + + public function __construct(RequestStack $requestStack) + { + $this->requestStack = $requestStack; + $this->cloner = new VarCloner(); + $this->cloner->setMaxItems(0); + $this->cloner->addCasters(ReflectionCaster::UNSET_CLOSURE_FILE_INFO); + } + + public function getContext(): ?array + { + if (null === $request = $this->requestStack->getCurrentRequest()) { + return null; + } + + $controller = $request->attributes->get('_controller'); + + return [ + 'uri' => $request->getUri(), + 'method' => $request->getMethod(), + 'controller' => $controller ? $this->cloner->cloneVar($controller) : $controller, + 'identifier' => spl_object_hash($request), + ]; + } +} diff --git a/vendor/symfony/var-dumper/Dumper/ContextProvider/SourceContextProvider.php b/vendor/symfony/var-dumper/Dumper/ContextProvider/SourceContextProvider.php new file mode 100644 index 0000000..2e2c818 --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/ContextProvider/SourceContextProvider.php @@ -0,0 +1,126 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper\ContextProvider; + +use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; +use Symfony\Component\VarDumper\VarDumper; +use Twig\Template; + +/** + * Tries to provide context from sources (class name, file, line, code excerpt, ...). + * + * @author Nicolas Grekas + * @author Maxime Steinhausser + */ +final class SourceContextProvider implements ContextProviderInterface +{ + private $limit; + private $charset; + private $projectDir; + private $fileLinkFormatter; + + public function __construct(string $charset = null, string $projectDir = null, FileLinkFormatter $fileLinkFormatter = null, int $limit = 9) + { + $this->charset = $charset; + $this->projectDir = $projectDir; + $this->fileLinkFormatter = $fileLinkFormatter; + $this->limit = $limit; + } + + public function getContext(): ?array + { + $trace = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS, $this->limit); + + $file = $trace[1]['file']; + $line = $trace[1]['line']; + $name = false; + $fileExcerpt = false; + + for ($i = 2; $i < $this->limit; ++$i) { + if (isset($trace[$i]['class'], $trace[$i]['function']) + && 'dump' === $trace[$i]['function'] + && VarDumper::class === $trace[$i]['class'] + ) { + $file = $trace[$i]['file'] ?? $file; + $line = $trace[$i]['line'] ?? $line; + + while (++$i < $this->limit) { + if (isset($trace[$i]['function'], $trace[$i]['file']) && empty($trace[$i]['class']) && !str_starts_with($trace[$i]['function'], 'call_user_func')) { + $file = $trace[$i]['file']; + $line = $trace[$i]['line']; + + break; + } elseif (isset($trace[$i]['object']) && $trace[$i]['object'] instanceof Template) { + $template = $trace[$i]['object']; + $name = $template->getTemplateName(); + $src = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getCode() : (method_exists($template, 'getSource') ? $template->getSource() : false); + $info = $template->getDebugInfo(); + if (isset($info[$trace[$i - 1]['line']])) { + $line = $info[$trace[$i - 1]['line']]; + $file = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getPath() : null; + + if ($src) { + $src = explode("\n", $src); + $fileExcerpt = []; + + for ($i = max($line - 3, 1), $max = min($line + 3, \count($src)); $i <= $max; ++$i) { + $fileExcerpt[] = ''.$this->htmlEncode($src[$i - 1]).''; + } + + $fileExcerpt = '
          '.implode("\n", $fileExcerpt).'
        '; + } + } + break; + } + } + break; + } + } + + if (false === $name) { + $name = str_replace('\\', '/', $file); + $name = substr($name, strrpos($name, '/') + 1); + } + + $context = ['name' => $name, 'file' => $file, 'line' => $line]; + $context['file_excerpt'] = $fileExcerpt; + + if (null !== $this->projectDir) { + $context['project_dir'] = $this->projectDir; + if (str_starts_with($file, $this->projectDir)) { + $context['file_relative'] = ltrim(substr($file, \strlen($this->projectDir)), \DIRECTORY_SEPARATOR); + } + } + + if ($this->fileLinkFormatter && $fileLink = $this->fileLinkFormatter->format($context['file'], $context['line'])) { + $context['file_link'] = $fileLink; + } + + return $context; + } + + private function htmlEncode(string $s): string + { + $html = ''; + + $dumper = new HtmlDumper(function ($line) use (&$html) { $html .= $line; }, $this->charset); + $dumper->setDumpHeader(''); + $dumper->setDumpBoundaries('', ''); + + $cloner = new VarCloner(); + $dumper->dump($cloner->cloneVar($s)); + + return substr(strip_tags($html), 1, -1); + } +} diff --git a/vendor/symfony/var-dumper/Dumper/ContextualizedDumper.php b/vendor/symfony/var-dumper/Dumper/ContextualizedDumper.php new file mode 100644 index 0000000..7638417 --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/ContextualizedDumper.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper; + +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface; + +/** + * @author Kévin Thérage + */ +class ContextualizedDumper implements DataDumperInterface +{ + private $wrappedDumper; + private $contextProviders; + + /** + * @param ContextProviderInterface[] $contextProviders + */ + public function __construct(DataDumperInterface $wrappedDumper, array $contextProviders) + { + $this->wrappedDumper = $wrappedDumper; + $this->contextProviders = $contextProviders; + } + + public function dump(Data $data) + { + $context = []; + foreach ($this->contextProviders as $contextProvider) { + $context[\get_class($contextProvider)] = $contextProvider->getContext(); + } + + $this->wrappedDumper->dump($data->withContext($context)); + } +} diff --git a/vendor/symfony/var-dumper/Dumper/DataDumperInterface.php b/vendor/symfony/var-dumper/Dumper/DataDumperInterface.php new file mode 100644 index 0000000..b173bcc --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/DataDumperInterface.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper; + +use Symfony\Component\VarDumper\Cloner\Data; + +/** + * DataDumperInterface for dumping Data objects. + * + * @author Nicolas Grekas + */ +interface DataDumperInterface +{ + public function dump(Data $data); +} diff --git a/vendor/symfony/var-dumper/Dumper/HtmlDumper.php b/vendor/symfony/var-dumper/Dumper/HtmlDumper.php new file mode 100644 index 0000000..8409a0c --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/HtmlDumper.php @@ -0,0 +1,1004 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper; + +use Symfony\Component\VarDumper\Cloner\Cursor; +use Symfony\Component\VarDumper\Cloner\Data; + +/** + * HtmlDumper dumps variables as HTML. + * + * @author Nicolas Grekas + */ +class HtmlDumper extends CliDumper +{ + public static $defaultOutput = 'php://output'; + + protected static $themes = [ + 'dark' => [ + 'default' => 'background-color:#18171B; color:#FF8400; line-height:1.2em; font:12px Menlo, Monaco, Consolas, monospace; word-wrap: break-word; white-space: pre-wrap; position:relative; z-index:99999; word-break: break-all', + 'num' => 'font-weight:bold; color:#1299DA', + 'const' => 'font-weight:bold', + 'str' => 'font-weight:bold; color:#56DB3A', + 'note' => 'color:#1299DA', + 'ref' => 'color:#A0A0A0', + 'public' => 'color:#FFFFFF', + 'protected' => 'color:#FFFFFF', + 'private' => 'color:#FFFFFF', + 'meta' => 'color:#B729D9', + 'key' => 'color:#56DB3A', + 'index' => 'color:#1299DA', + 'ellipsis' => 'color:#FF8400', + 'ns' => 'user-select:none;', + ], + 'light' => [ + 'default' => 'background:none; color:#CC7832; line-height:1.2em; font:12px Menlo, Monaco, Consolas, monospace; word-wrap: break-word; white-space: pre-wrap; position:relative; z-index:99999; word-break: break-all', + 'num' => 'font-weight:bold; color:#1299DA', + 'const' => 'font-weight:bold', + 'str' => 'font-weight:bold; color:#629755;', + 'note' => 'color:#6897BB', + 'ref' => 'color:#6E6E6E', + 'public' => 'color:#262626', + 'protected' => 'color:#262626', + 'private' => 'color:#262626', + 'meta' => 'color:#B729D9', + 'key' => 'color:#789339', + 'index' => 'color:#1299DA', + 'ellipsis' => 'color:#CC7832', + 'ns' => 'user-select:none;', + ], + ]; + + protected $dumpHeader; + protected $dumpPrefix = '
        ';
        +    protected $dumpSuffix = '
        '; + protected $dumpId = 'sf-dump'; + protected $colors = true; + protected $headerIsDumped = false; + protected $lastDepth = -1; + protected $styles; + + private $displayOptions = [ + 'maxDepth' => 1, + 'maxStringLength' => 160, + 'fileLinkFormat' => null, + ]; + private $extraDisplayOptions = []; + + /** + * {@inheritdoc} + */ + public function __construct($output = null, string $charset = null, int $flags = 0) + { + AbstractDumper::__construct($output, $charset, $flags); + $this->dumpId = 'sf-dump-'.mt_rand(); + $this->displayOptions['fileLinkFormat'] = ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); + $this->styles = static::$themes['dark'] ?? self::$themes['dark']; + } + + /** + * {@inheritdoc} + */ + public function setStyles(array $styles) + { + $this->headerIsDumped = false; + $this->styles = $styles + $this->styles; + } + + public function setTheme(string $themeName) + { + if (!isset(static::$themes[$themeName])) { + throw new \InvalidArgumentException(sprintf('Theme "%s" does not exist in class "%s".', $themeName, static::class)); + } + + $this->setStyles(static::$themes[$themeName]); + } + + /** + * Configures display options. + * + * @param array $displayOptions A map of display options to customize the behavior + */ + public function setDisplayOptions(array $displayOptions) + { + $this->headerIsDumped = false; + $this->displayOptions = $displayOptions + $this->displayOptions; + } + + /** + * Sets an HTML header that will be dumped once in the output stream. + * + * @param string $header An HTML string + */ + public function setDumpHeader($header) + { + $this->dumpHeader = $header; + } + + /** + * Sets an HTML prefix and suffix that will encapse every single dump. + * + * @param string $prefix The prepended HTML string + * @param string $suffix The appended HTML string + */ + public function setDumpBoundaries($prefix, $suffix) + { + $this->dumpPrefix = $prefix; + $this->dumpSuffix = $suffix; + } + + /** + * {@inheritdoc} + */ + public function dump(Data $data, $output = null, array $extraDisplayOptions = []) + { + $this->extraDisplayOptions = $extraDisplayOptions; + $result = parent::dump($data, $output); + $this->dumpId = 'sf-dump-'.mt_rand(); + + return $result; + } + + /** + * Dumps the HTML header. + */ + protected function getDumpHeader() + { + $this->headerIsDumped = $this->outputStream ?? $this->lineDumper; + + if (null !== $this->dumpHeader) { + return $this->dumpHeader; + } + + $line = str_replace('{$options}', json_encode($this->displayOptions, \JSON_FORCE_OBJECT), <<<'EOHTML' +'.$this->dumpHeader; + } + + /** + * {@inheritdoc} + */ + public function dumpString(Cursor $cursor, $str, $bin, $cut) + { + if ('' === $str && isset($cursor->attr['img-data'], $cursor->attr['content-type'])) { + $this->dumpKey($cursor); + $this->line .= $this->style('default', $cursor->attr['img-size'] ?? '', []).' '; + $this->endValue($cursor); + $this->line .= $this->indentPad; + $this->line .= sprintf('', $cursor->attr['content-type'], base64_encode($cursor->attr['img-data'])); + $this->endValue($cursor); + } else { + parent::dumpString($cursor, $str, $bin, $cut); + } + } + + /** + * {@inheritdoc} + */ + public function enterHash(Cursor $cursor, $type, $class, $hasChild) + { + if (Cursor::HASH_OBJECT === $type) { + $cursor->attr['depth'] = $cursor->depth; + } + parent::enterHash($cursor, $type, $class, false); + + if ($cursor->skipChildren) { + $cursor->skipChildren = false; + $eol = ' class=sf-dump-compact>'; + } elseif ($this->expandNextHash) { + $this->expandNextHash = false; + $eol = ' class=sf-dump-expanded>'; + } else { + $eol = '>'; + } + + if ($hasChild) { + $this->line .= 'refIndex) { + $r = Cursor::HASH_OBJECT !== $type ? 1 - (Cursor::HASH_RESOURCE !== $type) : 2; + $r .= $r && 0 < $cursor->softRefHandle ? $cursor->softRefHandle : $cursor->refIndex; + + $this->line .= sprintf(' id=%s-ref%s', $this->dumpId, $r); + } + $this->line .= $eol; + $this->dumpLine($cursor->depth); + } + } + + /** + * {@inheritdoc} + */ + public function leaveHash(Cursor $cursor, $type, $class, $hasChild, $cut) + { + $this->dumpEllipsis($cursor, $hasChild, $cut); + if ($hasChild) { + $this->line .= ''; + } + parent::leaveHash($cursor, $type, $class, $hasChild, 0); + } + + /** + * {@inheritdoc} + */ + protected function style($style, $value, $attr = []) + { + if ('' === $value) { + return ''; + } + + $v = esc($value); + + if ('ref' === $style) { + if (empty($attr['count'])) { + return sprintf('%s', $v); + } + $r = ('#' !== $v[0] ? 1 - ('@' !== $v[0]) : 2).substr($value, 1); + + return sprintf('%s', $this->dumpId, $r, 1 + $attr['count'], $v); + } + + if ('const' === $style && isset($attr['value'])) { + $style .= sprintf(' title="%s"', esc(is_scalar($attr['value']) ? $attr['value'] : json_encode($attr['value']))); + } elseif ('public' === $style) { + $style .= sprintf(' title="%s"', empty($attr['dynamic']) ? 'Public property' : 'Runtime added dynamic property'); + } elseif ('str' === $style && 1 < $attr['length']) { + $style .= sprintf(' title="%d%s characters"', $attr['length'], $attr['binary'] ? ' binary or non-UTF-8' : ''); + } elseif ('note' === $style && 0 < ($attr['depth'] ?? 0) && false !== $c = strrpos($value, '\\')) { + $style .= ' title=""'; + $attr += [ + 'ellipsis' => \strlen($value) - $c, + 'ellipsis-type' => 'note', + 'ellipsis-tail' => 1, + ]; + } elseif ('protected' === $style) { + $style .= ' title="Protected property"'; + } elseif ('meta' === $style && isset($attr['title'])) { + $style .= sprintf(' title="%s"', esc($this->utf8Encode($attr['title']))); + } elseif ('private' === $style) { + $style .= sprintf(' title="Private property defined in class: `%s`"', esc($this->utf8Encode($attr['class']))); + } + $map = static::$controlCharsMap; + + if (isset($attr['ellipsis'])) { + $class = 'sf-dump-ellipsis'; + if (isset($attr['ellipsis-type'])) { + $class = sprintf('"%s sf-dump-ellipsis-%s"', $class, $attr['ellipsis-type']); + } + $label = esc(substr($value, -$attr['ellipsis'])); + $style = str_replace(' title="', " title=\"$v\n", $style); + $v = sprintf('%s', $class, substr($v, 0, -\strlen($label))); + + if (!empty($attr['ellipsis-tail'])) { + $tail = \strlen(esc(substr($value, -$attr['ellipsis'], $attr['ellipsis-tail']))); + $v .= sprintf('%s%s', $class, substr($label, 0, $tail), substr($label, $tail)); + } else { + $v .= $label; + } + } + + $v = "".preg_replace_callback(static::$controlCharsRx, function ($c) use ($map) { + $s = $b = ''; + }, $v).''; + + if (isset($attr['file']) && $href = $this->getSourceLink($attr['file'], $attr['line'] ?? 0)) { + $attr['href'] = $href; + } + if (isset($attr['href'])) { + $target = isset($attr['file']) ? '' : ' target="_blank"'; + $v = sprintf('%s', esc($this->utf8Encode($attr['href'])), $target, $v); + } + if (isset($attr['lang'])) { + $v = sprintf('%s', esc($attr['lang']), $v); + } + + return $v; + } + + /** + * {@inheritdoc} + */ + protected function dumpLine($depth, $endOfValue = false) + { + if (-1 === $this->lastDepth) { + $this->line = sprintf($this->dumpPrefix, $this->dumpId, $this->indentPad).$this->line; + } + if ($this->headerIsDumped !== ($this->outputStream ?? $this->lineDumper)) { + $this->line = $this->getDumpHeader().$this->line; + } + + if (-1 === $depth) { + $args = ['"'.$this->dumpId.'"']; + if ($this->extraDisplayOptions) { + $args[] = json_encode($this->extraDisplayOptions, \JSON_FORCE_OBJECT); + } + // Replace is for BC + $this->line .= sprintf(str_replace('"%s"', '%s', $this->dumpSuffix), implode(', ', $args)); + } + $this->lastDepth = $depth; + + $this->line = mb_convert_encoding($this->line, 'HTML-ENTITIES', 'UTF-8'); + + if (-1 === $depth) { + AbstractDumper::dumpLine(0); + } + AbstractDumper::dumpLine($depth); + } + + private function getSourceLink(string $file, int $line) + { + $options = $this->extraDisplayOptions + $this->displayOptions; + + if ($fmt = $options['fileLinkFormat']) { + return \is_string($fmt) ? strtr($fmt, ['%f' => $file, '%l' => $line]) : $fmt->format($file, $line); + } + + return false; + } +} + +function esc(string $str) +{ + return htmlspecialchars($str, \ENT_QUOTES, 'UTF-8'); +} diff --git a/vendor/symfony/var-dumper/Dumper/ServerDumper.php b/vendor/symfony/var-dumper/Dumper/ServerDumper.php new file mode 100644 index 0000000..94795bf --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/ServerDumper.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper; + +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface; +use Symfony\Component\VarDumper\Server\Connection; + +/** + * ServerDumper forwards serialized Data clones to a server. + * + * @author Maxime Steinhausser + */ +class ServerDumper implements DataDumperInterface +{ + private $connection; + private $wrappedDumper; + + /** + * @param string $host The server host + * @param DataDumperInterface|null $wrappedDumper A wrapped instance used whenever we failed contacting the server + * @param ContextProviderInterface[] $contextProviders Context providers indexed by context name + */ + public function __construct(string $host, DataDumperInterface $wrappedDumper = null, array $contextProviders = []) + { + $this->connection = new Connection($host, $contextProviders); + $this->wrappedDumper = $wrappedDumper; + } + + public function getContextProviders(): array + { + return $this->connection->getContextProviders(); + } + + /** + * {@inheritdoc} + */ + public function dump(Data $data) + { + if (!$this->connection->write($data) && $this->wrappedDumper) { + $this->wrappedDumper->dump($data); + } + } +} diff --git a/vendor/symfony/var-dumper/Exception/ThrowingCasterException.php b/vendor/symfony/var-dumper/Exception/ThrowingCasterException.php new file mode 100644 index 0000000..122f0d3 --- /dev/null +++ b/vendor/symfony/var-dumper/Exception/ThrowingCasterException.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Exception; + +/** + * @author Nicolas Grekas + */ +class ThrowingCasterException extends \Exception +{ + /** + * @param \Throwable $prev The exception thrown from the caster + */ + public function __construct(\Throwable $prev) + { + parent::__construct('Unexpected '.\get_class($prev).' thrown from a caster: '.$prev->getMessage(), 0, $prev); + } +} diff --git a/vendor/symfony/var-dumper/LICENSE b/vendor/symfony/var-dumper/LICENSE new file mode 100644 index 0000000..c1f0aac --- /dev/null +++ b/vendor/symfony/var-dumper/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2014-2021 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/var-dumper/README.md b/vendor/symfony/var-dumper/README.md new file mode 100644 index 0000000..a0da8c9 --- /dev/null +++ b/vendor/symfony/var-dumper/README.md @@ -0,0 +1,15 @@ +VarDumper Component +=================== + +The VarDumper component provides mechanisms for walking through any arbitrary +PHP variable. It provides a better `dump()` function that you can use instead +of `var_dump()`. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/var_dumper/introduction.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/var-dumper/Resources/bin/var-dump-server b/vendor/symfony/var-dumper/Resources/bin/var-dump-server new file mode 100644 index 0000000..98c813a --- /dev/null +++ b/vendor/symfony/var-dumper/Resources/bin/var-dump-server @@ -0,0 +1,63 @@ +#!/usr/bin/env php + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Starts a dump server to collect and output dumps on a single place with multiple formats support. + * + * @author Maxime Steinhausser + */ + +use Psr\Log\LoggerInterface; +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Input\ArgvInput; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Logger\ConsoleLogger; +use Symfony\Component\Console\Output\ConsoleOutput; +use Symfony\Component\VarDumper\Command\ServerDumpCommand; +use Symfony\Component\VarDumper\Server\DumpServer; + +function includeIfExists(string $file): bool +{ + return file_exists($file) && include $file; +} + +if ( + !includeIfExists(__DIR__ . '/../../../../autoload.php') && + !includeIfExists(__DIR__ . '/../../vendor/autoload.php') && + !includeIfExists(__DIR__ . '/../../../../../../vendor/autoload.php') +) { + fwrite(STDERR, 'Install dependencies using Composer.'.PHP_EOL); + exit(1); +} + +if (!class_exists(Application::class)) { + fwrite(STDERR, 'You need the "symfony/console" component in order to run the VarDumper server.'.PHP_EOL); + exit(1); +} + +$input = new ArgvInput(); +$output = new ConsoleOutput(); +$defaultHost = '127.0.0.1:9912'; +$host = $input->getParameterOption(['--host'], $_SERVER['VAR_DUMPER_SERVER'] ?? $defaultHost, true); +$logger = interface_exists(LoggerInterface::class) ? new ConsoleLogger($output->getErrorOutput()) : null; + +$app = new Application(); + +$app->getDefinition()->addOption( + new InputOption('--host', null, InputOption::VALUE_REQUIRED, 'The address the server should listen to', $defaultHost) +); + +$app->add($command = new ServerDumpCommand(new DumpServer($host, $logger))) + ->getApplication() + ->setDefaultCommand($command->getName(), true) + ->run($input, $output) +; diff --git a/vendor/symfony/var-dumper/Resources/css/htmlDescriptor.css b/vendor/symfony/var-dumper/Resources/css/htmlDescriptor.css new file mode 100644 index 0000000..8f706d6 --- /dev/null +++ b/vendor/symfony/var-dumper/Resources/css/htmlDescriptor.css @@ -0,0 +1,130 @@ +body { + display: flex; + flex-direction: column-reverse; + justify-content: flex-end; + max-width: 1140px; + margin: auto; + padding: 15px; + word-wrap: break-word; + background-color: #F9F9F9; + color: #222; + font-family: Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 1.4; +} +p { + margin: 0; +} +a { + color: #218BC3; + text-decoration: none; +} +a:hover { + text-decoration: underline; +} +.text-small { + font-size: 12px !important; +} +article { + margin: 5px; + margin-bottom: 10px; +} +article > header > .row { + display: flex; + flex-direction: row; + align-items: baseline; + margin-bottom: 10px; +} +article > header > .row > .col { + flex: 1; + display: flex; + align-items: baseline; +} +article > header > .row > h2 { + font-size: 14px; + color: #222; + font-weight: normal; + font-family: "Lucida Console", monospace, sans-serif; + word-break: break-all; + margin: 20px 5px 0 0; + user-select: all; +} +article > header > .row > h2 > code { + white-space: nowrap; + user-select: none; + color: #cc2255; + background-color: #f7f7f9; + border: 1px solid #e1e1e8; + border-radius: 3px; + margin-right: 5px; + padding: 0 3px; +} +article > header > .row > time.col { + flex: 0; + text-align: right; + white-space: nowrap; + color: #999; + font-style: italic; +} +article > header ul.tags { + list-style: none; + padding: 0; + margin: 0; + font-size: 12px; +} +article > header ul.tags > li { + user-select: all; + margin-bottom: 2px; +} +article > header ul.tags > li > span.badge { + display: inline-block; + padding: .25em .4em; + margin-right: 5px; + border-radius: 4px; + background-color: #6c757d3b; + color: #524d4d; + font-size: 12px; + text-align: center; + font-weight: 700; + line-height: 1; + white-space: nowrap; + vertical-align: baseline; + user-select: none; +} +article > section.body { + border: 1px solid #d8d8d8; + background: #FFF; + padding: 10px; + border-radius: 3px; +} +pre.sf-dump { + border-radius: 3px; + margin-bottom: 0; +} +.hidden { + display: none !important; +} +.dumped-tag > .sf-dump { + display: inline-block; + margin: 0; + padding: 1px 5px; + line-height: 1.4; + vertical-align: top; + background-color: transparent; + user-select: auto; +} +.dumped-tag > pre.sf-dump, +.dumped-tag > .sf-dump-default { + color: #CC7832; + background: none; +} +.dumped-tag > .sf-dump .sf-dump-str { color: #629755; } +.dumped-tag > .sf-dump .sf-dump-private, +.dumped-tag > .sf-dump .sf-dump-protected, +.dumped-tag > .sf-dump .sf-dump-public { color: #262626; } +.dumped-tag > .sf-dump .sf-dump-note { color: #6897BB; } +.dumped-tag > .sf-dump .sf-dump-key { color: #789339; } +.dumped-tag > .sf-dump .sf-dump-ref { color: #6E6E6E; } +.dumped-tag > .sf-dump .sf-dump-ellipsis { color: #CC7832; max-width: 100em; } +.dumped-tag > .sf-dump .sf-dump-ellipsis-path { max-width: 5em; } +.dumped-tag > .sf-dump .sf-dump-ns { user-select: none; } diff --git a/vendor/symfony/var-dumper/Resources/functions/dump.php b/vendor/symfony/var-dumper/Resources/functions/dump.php new file mode 100644 index 0000000..a485d57 --- /dev/null +++ b/vendor/symfony/var-dumper/Resources/functions/dump.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Component\VarDumper\VarDumper; + +if (!function_exists('dump')) { + /** + * @author Nicolas Grekas + */ + function dump($var, ...$moreVars) + { + VarDumper::dump($var); + + foreach ($moreVars as $v) { + VarDumper::dump($v); + } + + if (1 < func_num_args()) { + return func_get_args(); + } + + return $var; + } +} + +if (!function_exists('dd')) { + function dd(...$vars) + { + foreach ($vars as $v) { + VarDumper::dump($v); + } + + exit(1); + } +} diff --git a/vendor/symfony/var-dumper/Resources/js/htmlDescriptor.js b/vendor/symfony/var-dumper/Resources/js/htmlDescriptor.js new file mode 100644 index 0000000..63101e5 --- /dev/null +++ b/vendor/symfony/var-dumper/Resources/js/htmlDescriptor.js @@ -0,0 +1,10 @@ +document.addEventListener('DOMContentLoaded', function() { + let prev = null; + Array.from(document.getElementsByTagName('article')).reverse().forEach(function (article) { + const dedupId = article.dataset.dedupId; + if (dedupId === prev) { + article.getElementsByTagName('header')[0].classList.add('hidden'); + } + prev = dedupId; + }); +}); diff --git a/vendor/symfony/var-dumper/Server/Connection.php b/vendor/symfony/var-dumper/Server/Connection.php new file mode 100644 index 0000000..55d9214 --- /dev/null +++ b/vendor/symfony/var-dumper/Server/Connection.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Server; + +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface; + +/** + * Forwards serialized Data clones to a server. + * + * @author Maxime Steinhausser + */ +class Connection +{ + private $host; + private $contextProviders; + private $socket; + + /** + * @param string $host The server host + * @param ContextProviderInterface[] $contextProviders Context providers indexed by context name + */ + public function __construct(string $host, array $contextProviders = []) + { + if (!str_contains($host, '://')) { + $host = 'tcp://'.$host; + } + + $this->host = $host; + $this->contextProviders = $contextProviders; + } + + public function getContextProviders(): array + { + return $this->contextProviders; + } + + public function write(Data $data): bool + { + $socketIsFresh = !$this->socket; + if (!$this->socket = $this->socket ?: $this->createSocket()) { + return false; + } + + $context = ['timestamp' => microtime(true)]; + foreach ($this->contextProviders as $name => $provider) { + $context[$name] = $provider->getContext(); + } + $context = array_filter($context); + $encodedPayload = base64_encode(serialize([$data, $context]))."\n"; + + set_error_handler([self::class, 'nullErrorHandler']); + try { + if (-1 !== stream_socket_sendto($this->socket, $encodedPayload)) { + return true; + } + if (!$socketIsFresh) { + stream_socket_shutdown($this->socket, \STREAM_SHUT_RDWR); + fclose($this->socket); + $this->socket = $this->createSocket(); + } + if (-1 !== stream_socket_sendto($this->socket, $encodedPayload)) { + return true; + } + } finally { + restore_error_handler(); + } + + return false; + } + + private static function nullErrorHandler(int $t, string $m) + { + // no-op + } + + private function createSocket() + { + set_error_handler([self::class, 'nullErrorHandler']); + try { + return stream_socket_client($this->host, $errno, $errstr, 3, \STREAM_CLIENT_CONNECT | \STREAM_CLIENT_ASYNC_CONNECT); + } finally { + restore_error_handler(); + } + } +} diff --git a/vendor/symfony/var-dumper/Server/DumpServer.php b/vendor/symfony/var-dumper/Server/DumpServer.php new file mode 100644 index 0000000..1c2c348 --- /dev/null +++ b/vendor/symfony/var-dumper/Server/DumpServer.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Server; + +use Psr\Log\LoggerInterface; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * A server collecting Data clones sent by a ServerDumper. + * + * @author Maxime Steinhausser + * + * @final + */ +class DumpServer +{ + private $host; + private $socket; + private $logger; + + public function __construct(string $host, LoggerInterface $logger = null) + { + if (!str_contains($host, '://')) { + $host = 'tcp://'.$host; + } + + $this->host = $host; + $this->logger = $logger; + } + + public function start(): void + { + if (!$this->socket = stream_socket_server($this->host, $errno, $errstr)) { + throw new \RuntimeException(sprintf('Server start failed on "%s": ', $this->host).$errstr.' '.$errno); + } + } + + public function listen(callable $callback): void + { + if (null === $this->socket) { + $this->start(); + } + + foreach ($this->getMessages() as $clientId => $message) { + $payload = @unserialize(base64_decode($message), ['allowed_classes' => [Data::class, Stub::class]]); + + // Impossible to decode the message, give up. + if (false === $payload) { + if ($this->logger) { + $this->logger->warning('Unable to decode a message from {clientId} client.', ['clientId' => $clientId]); + } + + continue; + } + + if (!\is_array($payload) || \count($payload) < 2 || !$payload[0] instanceof Data || !\is_array($payload[1])) { + if ($this->logger) { + $this->logger->warning('Invalid payload from {clientId} client. Expected an array of two elements (Data $data, array $context)', ['clientId' => $clientId]); + } + + continue; + } + + [$data, $context] = $payload; + + $callback($data, $context, $clientId); + } + } + + public function getHost(): string + { + return $this->host; + } + + private function getMessages(): iterable + { + $sockets = [(int) $this->socket => $this->socket]; + $write = []; + + while (true) { + $read = $sockets; + stream_select($read, $write, $write, null); + + foreach ($read as $stream) { + if ($this->socket === $stream) { + $stream = stream_socket_accept($this->socket); + $sockets[(int) $stream] = $stream; + } elseif (feof($stream)) { + unset($sockets[(int) $stream]); + fclose($stream); + } else { + yield (int) $stream => fgets($stream); + } + } + } + } +} diff --git a/vendor/symfony/var-dumper/Test/VarDumperTestTrait.php b/vendor/symfony/var-dumper/Test/VarDumperTestTrait.php new file mode 100644 index 0000000..3d3d18e --- /dev/null +++ b/vendor/symfony/var-dumper/Test/VarDumperTestTrait.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Test; + +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\CliDumper; + +/** + * @author Nicolas Grekas + */ +trait VarDumperTestTrait +{ + /** + * @internal + */ + private $varDumperConfig = [ + 'casters' => [], + 'flags' => null, + ]; + + protected function setUpVarDumper(array $casters, int $flags = null): void + { + $this->varDumperConfig['casters'] = $casters; + $this->varDumperConfig['flags'] = $flags; + } + + /** + * @after + */ + protected function tearDownVarDumper(): void + { + $this->varDumperConfig['casters'] = []; + $this->varDumperConfig['flags'] = null; + } + + public function assertDumpEquals($expected, $data, $filter = 0, $message = '') + { + $this->assertSame($this->prepareExpectation($expected, $filter), $this->getDump($data, null, $filter), $message); + } + + public function assertDumpMatchesFormat($expected, $data, $filter = 0, $message = '') + { + $this->assertStringMatchesFormat($this->prepareExpectation($expected, $filter), $this->getDump($data, null, $filter), $message); + } + + /** + * @return string|null + */ + protected function getDump($data, $key = null, $filter = 0) + { + if (null === $flags = $this->varDumperConfig['flags']) { + $flags = getenv('DUMP_LIGHT_ARRAY') ? CliDumper::DUMP_LIGHT_ARRAY : 0; + $flags |= getenv('DUMP_STRING_LENGTH') ? CliDumper::DUMP_STRING_LENGTH : 0; + $flags |= getenv('DUMP_COMMA_SEPARATOR') ? CliDumper::DUMP_COMMA_SEPARATOR : 0; + } + + $cloner = new VarCloner(); + $cloner->addCasters($this->varDumperConfig['casters']); + $cloner->setMaxItems(-1); + $dumper = new CliDumper(null, null, $flags); + $dumper->setColors(false); + $data = $cloner->cloneVar($data, $filter)->withRefHandles(false); + if (null !== $key && null === $data = $data->seek($key)) { + return null; + } + + return rtrim($dumper->dump($data, true)); + } + + private function prepareExpectation($expected, int $filter): string + { + if (!\is_string($expected)) { + $expected = $this->getDump($expected, null, $filter); + } + + return rtrim($expected); + } +} diff --git a/vendor/symfony/var-dumper/VarDumper.php b/vendor/symfony/var-dumper/VarDumper.php new file mode 100644 index 0000000..febc1e0 --- /dev/null +++ b/vendor/symfony/var-dumper/VarDumper.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper; + +use Symfony\Component\VarDumper\Caster\ReflectionCaster; +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\CliDumper; +use Symfony\Component\VarDumper\Dumper\ContextProvider\SourceContextProvider; +use Symfony\Component\VarDumper\Dumper\ContextualizedDumper; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; + +// Load the global dump() function +require_once __DIR__.'/Resources/functions/dump.php'; + +/** + * @author Nicolas Grekas + */ +class VarDumper +{ + private static $handler; + + public static function dump($var) + { + if (null === self::$handler) { + $cloner = new VarCloner(); + $cloner->addCasters(ReflectionCaster::UNSET_CLOSURE_FILE_INFO); + + if (isset($_SERVER['VAR_DUMPER_FORMAT'])) { + $dumper = 'html' === $_SERVER['VAR_DUMPER_FORMAT'] ? new HtmlDumper() : new CliDumper(); + } else { + $dumper = \in_array(\PHP_SAPI, ['cli', 'phpdbg']) ? new CliDumper() : new HtmlDumper(); + } + + $dumper = new ContextualizedDumper($dumper, [new SourceContextProvider()]); + + self::$handler = function ($var) use ($cloner, $dumper) { + $dumper->dump($cloner->cloneVar($var)); + }; + } + + return (self::$handler)($var); + } + + public static function setHandler(callable $callable = null) + { + $prevHandler = self::$handler; + + // Prevent replacing the handler with expected format as soon as the env var was set: + if (isset($_SERVER['VAR_DUMPER_FORMAT'])) { + return $prevHandler; + } + + self::$handler = $callable; + + return $prevHandler; + } +} diff --git a/vendor/symfony/var-dumper/composer.json b/vendor/symfony/var-dumper/composer.json new file mode 100644 index 0000000..d4e64cc --- /dev/null +++ b/vendor/symfony/var-dumper/composer.json @@ -0,0 +1,50 @@ +{ + "name": "symfony/var-dumper", + "type": "library", + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "keywords": ["dump", "debug"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1.3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php72": "~1.5", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^3.4|^4.0|^5.0", + "symfony/process": "^4.4|^5.0", + "twig/twig": "^1.43|^2.13|^3.0.4" + }, + "conflict": { + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", + "symfony/console": "<3.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "autoload": { + "files": [ "Resources/functions/dump.php" ], + "psr-4": { "Symfony\\Component\\VarDumper\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "minimum-stability": "dev" +} diff --git a/vendor/symfony/var-exporter/CHANGELOG.md b/vendor/symfony/var-exporter/CHANGELOG.md new file mode 100644 index 0000000..3406c30 --- /dev/null +++ b/vendor/symfony/var-exporter/CHANGELOG.md @@ -0,0 +1,12 @@ +CHANGELOG +========= + +5.1.0 +----- + + * added argument `array &$foundClasses` to `VarExporter::export()` to ease with preloading exported values + +4.2.0 +----- + + * added the component diff --git a/vendor/symfony/var-exporter/Exception/ClassNotFoundException.php b/vendor/symfony/var-exporter/Exception/ClassNotFoundException.php new file mode 100644 index 0000000..4cebe44 --- /dev/null +++ b/vendor/symfony/var-exporter/Exception/ClassNotFoundException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Exception; + +class ClassNotFoundException extends \Exception implements ExceptionInterface +{ + public function __construct(string $class, \Throwable $previous = null) + { + parent::__construct(sprintf('Class "%s" not found.', $class), 0, $previous); + } +} diff --git a/vendor/symfony/var-exporter/Exception/ExceptionInterface.php b/vendor/symfony/var-exporter/Exception/ExceptionInterface.php new file mode 100644 index 0000000..adfaed4 --- /dev/null +++ b/vendor/symfony/var-exporter/Exception/ExceptionInterface.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Exception; + +interface ExceptionInterface extends \Throwable +{ +} diff --git a/vendor/symfony/var-exporter/Exception/NotInstantiableTypeException.php b/vendor/symfony/var-exporter/Exception/NotInstantiableTypeException.php new file mode 100644 index 0000000..771ee61 --- /dev/null +++ b/vendor/symfony/var-exporter/Exception/NotInstantiableTypeException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Exception; + +class NotInstantiableTypeException extends \Exception implements ExceptionInterface +{ + public function __construct(string $type, \Throwable $previous = null) + { + parent::__construct(sprintf('Type "%s" is not instantiable.', $type), 0, $previous); + } +} diff --git a/vendor/symfony/var-exporter/Instantiator.php b/vendor/symfony/var-exporter/Instantiator.php new file mode 100644 index 0000000..9e33ea7 --- /dev/null +++ b/vendor/symfony/var-exporter/Instantiator.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter; + +use Symfony\Component\VarExporter\Exception\ExceptionInterface; +use Symfony\Component\VarExporter\Exception\NotInstantiableTypeException; +use Symfony\Component\VarExporter\Internal\Hydrator; +use Symfony\Component\VarExporter\Internal\Registry; + +/** + * A utility class to create objects without calling their constructor. + * + * @author Nicolas Grekas + */ +final class Instantiator +{ + /** + * Creates an object and sets its properties without calling its constructor nor any other methods. + * + * For example: + * + * // creates an empty instance of Foo + * Instantiator::instantiate(Foo::class); + * + * // creates a Foo instance and sets one of its properties + * Instantiator::instantiate(Foo::class, ['propertyName' => $propertyValue]); + * + * // creates a Foo instance and sets a private property defined on its parent Bar class + * Instantiator::instantiate(Foo::class, [], [ + * Bar::class => ['privateBarProperty' => $propertyValue], + * ]); + * + * Instances of ArrayObject, ArrayIterator and SplObjectHash can be created + * by using the special "\0" property name to define their internal value: + * + * // creates an SplObjectHash where $info1 is attached to $obj1, etc. + * Instantiator::instantiate(SplObjectStorage::class, ["\0" => [$obj1, $info1, $obj2, $info2...]]); + * + * // creates an ArrayObject populated with $inputArray + * Instantiator::instantiate(ArrayObject::class, ["\0" => [$inputArray]]); + * + * @param string $class The class of the instance to create + * @param array $properties The properties to set on the instance + * @param array $privateProperties The private properties to set on the instance, + * keyed by their declaring class + * + * @throws ExceptionInterface When the instance cannot be created + */ + public static function instantiate(string $class, array $properties = [], array $privateProperties = []): object + { + $reflector = Registry::$reflectors[$class] ?? Registry::getClassReflector($class); + + if (Registry::$cloneable[$class]) { + $wrappedInstance = [clone Registry::$prototypes[$class]]; + } elseif (Registry::$instantiableWithoutConstructor[$class]) { + $wrappedInstance = [$reflector->newInstanceWithoutConstructor()]; + } elseif (null === Registry::$prototypes[$class]) { + throw new NotInstantiableTypeException($class); + } elseif ($reflector->implementsInterface('Serializable') && (\PHP_VERSION_ID < 70400 || !method_exists($class, '__unserialize'))) { + $wrappedInstance = [unserialize('C:'.\strlen($class).':"'.$class.'":0:{}')]; + } else { + $wrappedInstance = [unserialize('O:'.\strlen($class).':"'.$class.'":0:{}')]; + } + + if ($properties) { + $privateProperties[$class] = isset($privateProperties[$class]) ? $properties + $privateProperties[$class] : $properties; + } + + foreach ($privateProperties as $class => $properties) { + if (!$properties) { + continue; + } + foreach ($properties as $name => $value) { + // because they're also used for "unserialization", hydrators + // deal with array of instances, so we need to wrap values + $properties[$name] = [$value]; + } + (Hydrator::$hydrators[$class] ?? Hydrator::getHydrator($class))($properties, $wrappedInstance); + } + + return $wrappedInstance[0]; + } +} diff --git a/vendor/symfony/var-exporter/Internal/Exporter.php b/vendor/symfony/var-exporter/Internal/Exporter.php new file mode 100644 index 0000000..73a5610 --- /dev/null +++ b/vendor/symfony/var-exporter/Internal/Exporter.php @@ -0,0 +1,402 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +use Symfony\Component\VarExporter\Exception\NotInstantiableTypeException; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class Exporter +{ + /** + * Prepares an array of values for VarExporter. + * + * For performance this method is public and has no type-hints. + * + * @param array &$values + * @param \SplObjectStorage $objectsPool + * @param array &$refsPool + * @param int &$objectsCount + * @param bool &$valuesAreStatic + * + * @throws NotInstantiableTypeException When a value cannot be serialized + */ + public static function prepare($values, $objectsPool, &$refsPool, &$objectsCount, &$valuesAreStatic): array + { + $refs = $values; + foreach ($values as $k => $value) { + if (\is_resource($value)) { + throw new NotInstantiableTypeException(get_resource_type($value).' resource'); + } + $refs[$k] = $objectsPool; + + if ($isRef = !$valueIsStatic = $values[$k] !== $objectsPool) { + $values[$k] = &$value; // Break hard references to make $values completely + unset($value); // independent from the original structure + $refs[$k] = $value = $values[$k]; + if ($value instanceof Reference && 0 > $value->id) { + $valuesAreStatic = false; + ++$value->count; + continue; + } + $refsPool[] = [&$refs[$k], $value, &$value]; + $refs[$k] = $values[$k] = new Reference(-\count($refsPool), $value); + } + + if (\is_array($value)) { + if ($value) { + $value = self::prepare($value, $objectsPool, $refsPool, $objectsCount, $valueIsStatic); + } + goto handle_value; + } elseif (!\is_object($value) || $value instanceof \UnitEnum) { + goto handle_value; + } + + $valueIsStatic = false; + if (isset($objectsPool[$value])) { + ++$objectsCount; + $value = new Reference($objectsPool[$value][0]); + goto handle_value; + } + + $class = \get_class($value); + $reflector = Registry::$reflectors[$class] ?? Registry::getClassReflector($class); + + if ($reflector->hasMethod('__serialize')) { + if (!$reflector->getMethod('__serialize')->isPublic()) { + throw new \Error(sprintf('Call to %s method "%s::__serialize()".', $reflector->getMethod('__serialize')->isProtected() ? 'protected' : 'private', $class)); + } + + if (!\is_array($properties = $value->__serialize())) { + throw new \TypeError($class.'::__serialize() must return an array'); + } + + goto prepare_value; + } + + $properties = []; + $sleep = null; + $proto = Registry::$prototypes[$class]; + + if (($value instanceof \ArrayIterator || $value instanceof \ArrayObject) && null !== $proto) { + // ArrayIterator and ArrayObject need special care because their "flags" + // option changes the behavior of the (array) casting operator. + [$arrayValue, $properties] = self::getArrayObjectProperties($value, $proto); + + // populates Registry::$prototypes[$class] with a new instance + Registry::getClassReflector($class, Registry::$instantiableWithoutConstructor[$class], Registry::$cloneable[$class]); + } elseif ($value instanceof \SplObjectStorage && Registry::$cloneable[$class] && null !== $proto) { + // By implementing Serializable, SplObjectStorage breaks + // internal references; let's deal with it on our own. + foreach (clone $value as $v) { + $properties[] = $v; + $properties[] = $value[$v]; + } + $properties = ['SplObjectStorage' => ["\0" => $properties]]; + $arrayValue = (array) $value; + } elseif ($value instanceof \Serializable || $value instanceof \__PHP_Incomplete_Class) { + ++$objectsCount; + $objectsPool[$value] = [$id = \count($objectsPool), serialize($value), [], 0]; + $value = new Reference($id); + goto handle_value; + } else { + if (method_exists($class, '__sleep')) { + if (!\is_array($sleep = $value->__sleep())) { + trigger_error('serialize(): __sleep should return an array only containing the names of instance-variables to serialize', \E_USER_NOTICE); + $value = null; + goto handle_value; + } + $sleep = array_flip($sleep); + } + + $arrayValue = (array) $value; + } + + $proto = (array) $proto; + + foreach ($arrayValue as $name => $v) { + $i = 0; + $n = (string) $name; + if ('' === $n || "\0" !== $n[0]) { + $c = 'stdClass'; + } elseif ('*' === $n[1]) { + $n = substr($n, 3); + $c = $reflector->getProperty($n)->class; + if ('Error' === $c) { + $c = 'TypeError'; + } elseif ('Exception' === $c) { + $c = 'ErrorException'; + } + } else { + $i = strpos($n, "\0", 2); + $c = substr($n, 1, $i - 1); + $n = substr($n, 1 + $i); + } + if (null !== $sleep) { + if (!isset($sleep[$n]) || ($i && $c !== $class)) { + continue; + } + $sleep[$n] = false; + } + if (!\array_key_exists($name, $proto) || $proto[$name] !== $v || "\x00Error\x00trace" === $name || "\x00Exception\x00trace" === $name) { + $properties[$c][$n] = $v; + } + } + if ($sleep) { + foreach ($sleep as $n => $v) { + if (false !== $v) { + trigger_error(sprintf('serialize(): "%s" returned as member variable from __sleep() but does not exist', $n), \E_USER_NOTICE); + } + } + } + + prepare_value: + $objectsPool[$value] = [$id = \count($objectsPool)]; + $properties = self::prepare($properties, $objectsPool, $refsPool, $objectsCount, $valueIsStatic); + ++$objectsCount; + $objectsPool[$value] = [$id, $class, $properties, method_exists($class, '__unserialize') ? -$objectsCount : (method_exists($class, '__wakeup') ? $objectsCount : 0)]; + + $value = new Reference($id); + + handle_value: + if ($isRef) { + unset($value); // Break the hard reference created above + } elseif (!$valueIsStatic) { + $values[$k] = $value; + } + $valuesAreStatic = $valueIsStatic && $valuesAreStatic; + } + + return $values; + } + + public static function export($value, string $indent = '') + { + switch (true) { + case \is_int($value) || \is_float($value) || $value instanceof \UnitEnum: return var_export($value, true); + case [] === $value: return '[]'; + case false === $value: return 'false'; + case true === $value: return 'true'; + case null === $value: return 'null'; + case '' === $value: return "''"; + } + + if ($value instanceof Reference) { + if (0 <= $value->id) { + return '$o['.$value->id.']'; + } + if (!$value->count) { + return self::export($value->value, $indent); + } + $value = -$value->id; + + return '&$r['.$value.']'; + } + $subIndent = $indent.' '; + + if (\is_string($value)) { + $code = sprintf("'%s'", addcslashes($value, "'\\")); + + $code = preg_replace_callback("/((?:[\\0\\r\\n]|\u{202A}|\u{202B}|\u{202D}|\u{202E}|\u{2066}|\u{2067}|\u{2068}|\u{202C}|\u{2069})++)(.)/", function ($m) use ($subIndent) { + $m[1] = sprintf('\'."%s".\'', str_replace( + ["\0", "\r", "\n", "\u{202A}", "\u{202B}", "\u{202D}", "\u{202E}", "\u{2066}", "\u{2067}", "\u{2068}", "\u{202C}", "\u{2069}", '\n\\'], + ['\0', '\r', '\n', '\u{202A}', '\u{202B}', '\u{202D}', '\u{202E}', '\u{2066}', '\u{2067}', '\u{2068}', '\u{202C}', '\u{2069}', '\n"'."\n".$subIndent.'."\\'], + $m[1] + )); + + if ("'" === $m[2]) { + return substr($m[1], 0, -2); + } + + if ('n".\'' === substr($m[1], -4)) { + return substr_replace($m[1], "\n".$subIndent.".'".$m[2], -2); + } + + return $m[1].$m[2]; + }, $code, -1, $count); + + if ($count && str_starts_with($code, "''.")) { + $code = substr($code, 3); + } + + return $code; + } + + if (\is_array($value)) { + $j = -1; + $code = ''; + foreach ($value as $k => $v) { + $code .= $subIndent; + if (!\is_int($k) || 1 !== $k - $j) { + $code .= self::export($k, $subIndent).' => '; + } + if (\is_int($k) && $k > $j) { + $j = $k; + } + $code .= self::export($v, $subIndent).",\n"; + } + + return "[\n".$code.$indent.']'; + } + + if ($value instanceof Values) { + $code = $subIndent."\$r = [],\n"; + foreach ($value->values as $k => $v) { + $code .= $subIndent.'$r['.$k.'] = '.self::export($v, $subIndent).",\n"; + } + + return "[\n".$code.$indent.']'; + } + + if ($value instanceof Registry) { + return self::exportRegistry($value, $indent, $subIndent); + } + + if ($value instanceof Hydrator) { + return self::exportHydrator($value, $indent, $subIndent); + } + + throw new \UnexpectedValueException(sprintf('Cannot export value of type "%s".', get_debug_type($value))); + } + + private static function exportRegistry(Registry $value, string $indent, string $subIndent): string + { + $code = ''; + $serializables = []; + $seen = []; + $prototypesAccess = 0; + $factoriesAccess = 0; + $r = '\\'.Registry::class; + $j = -1; + + foreach ($value->classes as $k => $class) { + if (':' === ($class[1] ?? null)) { + $serializables[$k] = $class; + continue; + } + if (!Registry::$instantiableWithoutConstructor[$class]) { + if (is_subclass_of($class, 'Serializable') && !method_exists($class, '__unserialize')) { + $serializables[$k] = 'C:'.\strlen($class).':"'.$class.'":0:{}'; + } else { + $serializables[$k] = 'O:'.\strlen($class).':"'.$class.'":0:{}'; + } + if (is_subclass_of($class, 'Throwable')) { + $eol = is_subclass_of($class, 'Error') ? "\0Error\0" : "\0Exception\0"; + $serializables[$k] = substr_replace($serializables[$k], '1:{s:'.(5 + \strlen($eol)).':"'.$eol.'trace";a:0:{}}', -4); + } + continue; + } + $code .= $subIndent.(1 !== $k - $j ? $k.' => ' : ''); + $j = $k; + $eol = ",\n"; + $c = '['.self::export($class).']'; + + if ($seen[$class] ?? false) { + if (Registry::$cloneable[$class]) { + ++$prototypesAccess; + $code .= 'clone $p'.$c; + } else { + ++$factoriesAccess; + $code .= '$f'.$c.'()'; + } + } else { + $seen[$class] = true; + if (Registry::$cloneable[$class]) { + $code .= 'clone ('.($prototypesAccess++ ? '$p' : '($p = &'.$r.'::$prototypes)').$c.' ?? '.$r.'::p'; + } else { + $code .= '('.($factoriesAccess++ ? '$f' : '($f = &'.$r.'::$factories)').$c.' ?? '.$r.'::f'; + $eol = '()'.$eol; + } + $code .= '('.substr($c, 1, -1).'))'; + } + $code .= $eol; + } + + if (1 === $prototypesAccess) { + $code = str_replace('($p = &'.$r.'::$prototypes)', $r.'::$prototypes', $code); + } + if (1 === $factoriesAccess) { + $code = str_replace('($f = &'.$r.'::$factories)', $r.'::$factories', $code); + } + if ('' !== $code) { + $code = "\n".$code.$indent; + } + + if ($serializables) { + $code = $r.'::unserialize(['.$code.'], '.self::export($serializables, $indent).')'; + } else { + $code = '['.$code.']'; + } + + return '$o = '.$code; + } + + private static function exportHydrator(Hydrator $value, string $indent, string $subIndent): string + { + $code = ''; + foreach ($value->properties as $class => $properties) { + $code .= $subIndent.' '.self::export($class).' => '.self::export($properties, $subIndent.' ').",\n"; + } + + $code = [ + self::export($value->registry, $subIndent), + self::export($value->values, $subIndent), + '' !== $code ? "[\n".$code.$subIndent.']' : '[]', + self::export($value->value, $subIndent), + self::export($value->wakeups, $subIndent), + ]; + + return '\\'.\get_class($value)."::hydrate(\n".$subIndent.implode(",\n".$subIndent, $code)."\n".$indent.')'; + } + + /** + * @param \ArrayIterator|\ArrayObject $value + * @param \ArrayIterator|\ArrayObject $proto + */ + private static function getArrayObjectProperties($value, $proto): array + { + $reflector = $value instanceof \ArrayIterator ? 'ArrayIterator' : 'ArrayObject'; + $reflector = Registry::$reflectors[$reflector] ?? Registry::getClassReflector($reflector); + + $properties = [ + $arrayValue = (array) $value, + $reflector->getMethod('getFlags')->invoke($value), + $value instanceof \ArrayObject ? $reflector->getMethod('getIteratorClass')->invoke($value) : 'ArrayIterator', + ]; + + $reflector = $reflector->getMethod('setFlags'); + $reflector->invoke($proto, \ArrayObject::STD_PROP_LIST); + + if ($properties[1] & \ArrayObject::STD_PROP_LIST) { + $reflector->invoke($value, 0); + $properties[0] = (array) $value; + } else { + $reflector->invoke($value, \ArrayObject::STD_PROP_LIST); + $arrayValue = (array) $value; + } + $reflector->invoke($value, $properties[1]); + + if ([[], 0, 'ArrayIterator'] === $properties) { + $properties = []; + } else { + if ('ArrayIterator' === $properties[2]) { + unset($properties[2]); + } + $properties = [$reflector->class => ["\0" => $properties]]; + } + + return [$arrayValue, $properties]; + } +} diff --git a/vendor/symfony/var-exporter/Internal/Hydrator.php b/vendor/symfony/var-exporter/Internal/Hydrator.php new file mode 100644 index 0000000..364d292 --- /dev/null +++ b/vendor/symfony/var-exporter/Internal/Hydrator.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +use Symfony\Component\VarExporter\Exception\ClassNotFoundException; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class Hydrator +{ + public static $hydrators = []; + + public $registry; + public $values; + public $properties; + public $value; + public $wakeups; + + public function __construct(?Registry $registry, ?Values $values, array $properties, $value, array $wakeups) + { + $this->registry = $registry; + $this->values = $values; + $this->properties = $properties; + $this->value = $value; + $this->wakeups = $wakeups; + } + + public static function hydrate($objects, $values, $properties, $value, $wakeups) + { + foreach ($properties as $class => $vars) { + (self::$hydrators[$class] ?? self::getHydrator($class))($vars, $objects); + } + foreach ($wakeups as $k => $v) { + if (\is_array($v)) { + $objects[-$k]->__unserialize($v); + } else { + $objects[$v]->__wakeup(); + } + } + + return $value; + } + + public static function getHydrator($class) + { + if ('stdClass' === $class) { + return self::$hydrators[$class] = static function ($properties, $objects) { + foreach ($properties as $name => $values) { + foreach ($values as $i => $v) { + $objects[$i]->$name = $v; + } + } + }; + } + + if (!class_exists($class) && !interface_exists($class, false) && !trait_exists($class, false)) { + throw new ClassNotFoundException($class); + } + $classReflector = new \ReflectionClass($class); + + if (!$classReflector->isInternal()) { + return self::$hydrators[$class] = (self::$hydrators['stdClass'] ?? self::getHydrator('stdClass'))->bindTo(null, $class); + } + + if ($classReflector->name !== $class) { + return self::$hydrators[$classReflector->name] ?? self::getHydrator($classReflector->name); + } + + switch ($class) { + case 'ArrayIterator': + case 'ArrayObject': + $constructor = \Closure::fromCallable([$classReflector->getConstructor(), 'invokeArgs']); + + return self::$hydrators[$class] = static function ($properties, $objects) use ($constructor) { + foreach ($properties as $name => $values) { + if ("\0" !== $name) { + foreach ($values as $i => $v) { + $objects[$i]->$name = $v; + } + } + } + foreach ($properties["\0"] ?? [] as $i => $v) { + $constructor($objects[$i], $v); + } + }; + + case 'ErrorException': + return self::$hydrators[$class] = (self::$hydrators['stdClass'] ?? self::getHydrator('stdClass'))->bindTo(null, new class() extends \ErrorException { + }); + + case 'TypeError': + return self::$hydrators[$class] = (self::$hydrators['stdClass'] ?? self::getHydrator('stdClass'))->bindTo(null, new class() extends \Error { + }); + + case 'SplObjectStorage': + return self::$hydrators[$class] = static function ($properties, $objects) { + foreach ($properties as $name => $values) { + if ("\0" === $name) { + foreach ($values as $i => $v) { + for ($j = 0; $j < \count($v); ++$j) { + $objects[$i]->attach($v[$j], $v[++$j]); + } + } + continue; + } + foreach ($values as $i => $v) { + $objects[$i]->$name = $v; + } + } + }; + } + + $propertySetters = []; + foreach ($classReflector->getProperties() as $propertyReflector) { + if (!$propertyReflector->isStatic()) { + $propertyReflector->setAccessible(true); + $propertySetters[$propertyReflector->name] = \Closure::fromCallable([$propertyReflector, 'setValue']); + } + } + + if (!$propertySetters) { + return self::$hydrators[$class] = self::$hydrators['stdClass'] ?? self::getHydrator('stdClass'); + } + + return self::$hydrators[$class] = static function ($properties, $objects) use ($propertySetters) { + foreach ($properties as $name => $values) { + if ($setValue = $propertySetters[$name] ?? null) { + foreach ($values as $i => $v) { + $setValue($objects[$i], $v); + } + continue; + } + foreach ($values as $i => $v) { + $objects[$i]->$name = $v; + } + } + }; + } +} diff --git a/vendor/symfony/var-exporter/Internal/Reference.php b/vendor/symfony/var-exporter/Internal/Reference.php new file mode 100644 index 0000000..e371c07 --- /dev/null +++ b/vendor/symfony/var-exporter/Internal/Reference.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class Reference +{ + public $id; + public $value; + public $count = 0; + + public function __construct(int $id, $value = null) + { + $this->id = $id; + $this->value = $value; + } +} diff --git a/vendor/symfony/var-exporter/Internal/Registry.php b/vendor/symfony/var-exporter/Internal/Registry.php new file mode 100644 index 0000000..24b77b9 --- /dev/null +++ b/vendor/symfony/var-exporter/Internal/Registry.php @@ -0,0 +1,146 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +use Symfony\Component\VarExporter\Exception\ClassNotFoundException; +use Symfony\Component\VarExporter\Exception\NotInstantiableTypeException; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class Registry +{ + public static $reflectors = []; + public static $prototypes = []; + public static $factories = []; + public static $cloneable = []; + public static $instantiableWithoutConstructor = []; + + public $classes = []; + + public function __construct(array $classes) + { + $this->classes = $classes; + } + + public static function unserialize($objects, $serializables) + { + $unserializeCallback = ini_set('unserialize_callback_func', __CLASS__.'::getClassReflector'); + + try { + foreach ($serializables as $k => $v) { + $objects[$k] = unserialize($v); + } + } finally { + ini_set('unserialize_callback_func', $unserializeCallback); + } + + return $objects; + } + + public static function p($class) + { + self::getClassReflector($class, true, true); + + return self::$prototypes[$class]; + } + + public static function f($class) + { + $reflector = self::$reflectors[$class] ?? self::getClassReflector($class, true, false); + + return self::$factories[$class] = \Closure::fromCallable([$reflector, 'newInstanceWithoutConstructor']); + } + + public static function getClassReflector($class, $instantiableWithoutConstructor = false, $cloneable = null) + { + if (!($isClass = class_exists($class)) && !interface_exists($class, false) && !trait_exists($class, false)) { + throw new ClassNotFoundException($class); + } + $reflector = new \ReflectionClass($class); + + if ($instantiableWithoutConstructor) { + $proto = $reflector->newInstanceWithoutConstructor(); + } elseif (!$isClass || $reflector->isAbstract()) { + throw new NotInstantiableTypeException($class); + } elseif ($reflector->name !== $class) { + $reflector = self::$reflectors[$name = $reflector->name] ?? self::getClassReflector($name, false, $cloneable); + self::$cloneable[$class] = self::$cloneable[$name]; + self::$instantiableWithoutConstructor[$class] = self::$instantiableWithoutConstructor[$name]; + self::$prototypes[$class] = self::$prototypes[$name]; + + return self::$reflectors[$class] = $reflector; + } else { + try { + $proto = $reflector->newInstanceWithoutConstructor(); + $instantiableWithoutConstructor = true; + } catch (\ReflectionException $e) { + $proto = $reflector->implementsInterface('Serializable') && !method_exists($class, '__unserialize') ? 'C:' : 'O:'; + if ('C:' === $proto && !$reflector->getMethod('unserialize')->isInternal()) { + $proto = null; + } else { + try { + $proto = @unserialize($proto.\strlen($class).':"'.$class.'":0:{}'); + } catch (\Exception $e) { + if (__FILE__ !== $e->getFile()) { + throw $e; + } + throw new NotInstantiableTypeException($class, $e); + } + if (false === $proto) { + throw new NotInstantiableTypeException($class); + } + } + } + if (null !== $proto && !$proto instanceof \Throwable && !$proto instanceof \Serializable && !method_exists($class, '__sleep') && (\PHP_VERSION_ID < 70400 || !method_exists($class, '__serialize'))) { + try { + serialize($proto); + } catch (\Exception $e) { + throw new NotInstantiableTypeException($class, $e); + } + } + } + + if (null === $cloneable) { + if (($proto instanceof \Reflector || $proto instanceof \ReflectionGenerator || $proto instanceof \ReflectionType || $proto instanceof \IteratorIterator || $proto instanceof \RecursiveIteratorIterator) && (!$proto instanceof \Serializable && !method_exists($proto, '__wakeup') && (\PHP_VERSION_ID < 70400 || !method_exists($class, '__unserialize')))) { + throw new NotInstantiableTypeException($class); + } + + $cloneable = $reflector->isCloneable() && !$reflector->hasMethod('__clone'); + } + + self::$cloneable[$class] = $cloneable; + self::$instantiableWithoutConstructor[$class] = $instantiableWithoutConstructor; + self::$prototypes[$class] = $proto; + + if ($proto instanceof \Throwable) { + static $setTrace; + + if (null === $setTrace) { + $setTrace = [ + new \ReflectionProperty(\Error::class, 'trace'), + new \ReflectionProperty(\Exception::class, 'trace'), + ]; + $setTrace[0]->setAccessible(true); + $setTrace[1]->setAccessible(true); + $setTrace[0] = \Closure::fromCallable([$setTrace[0], 'setValue']); + $setTrace[1] = \Closure::fromCallable([$setTrace[1], 'setValue']); + } + + $setTrace[$proto instanceof \Exception]($proto, []); + } + + return self::$reflectors[$class] = $reflector; + } +} diff --git a/vendor/symfony/var-exporter/Internal/Values.php b/vendor/symfony/var-exporter/Internal/Values.php new file mode 100644 index 0000000..21ae04e --- /dev/null +++ b/vendor/symfony/var-exporter/Internal/Values.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class Values +{ + public $values; + + public function __construct(array $values) + { + $this->values = $values; + } +} diff --git a/vendor/symfony/var-exporter/LICENSE b/vendor/symfony/var-exporter/LICENSE new file mode 100644 index 0000000..2358414 --- /dev/null +++ b/vendor/symfony/var-exporter/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2021 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/var-exporter/README.md b/vendor/symfony/var-exporter/README.md new file mode 100644 index 0000000..a34e4c2 --- /dev/null +++ b/vendor/symfony/var-exporter/README.md @@ -0,0 +1,38 @@ +VarExporter Component +===================== + +The VarExporter component allows exporting any serializable PHP data structure to +plain PHP code. While doing so, it preserves all the semantics associated with +the serialization mechanism of PHP (`__wakeup`, `__sleep`, `Serializable`, +`__serialize`, `__unserialize`). + +It also provides an instantiator that allows creating and populating objects +without calling their constructor nor any other methods. + +The reason to use this component *vs* `serialize()` or +[igbinary](https://github.com/igbinary/igbinary) is performance: thanks to +OPcache, the resulting code is significantly faster and more memory efficient +than using `unserialize()` or `igbinary_unserialize()`. + +Unlike `var_export()`, this works on any serializable PHP value. + +It also provides a few improvements over `var_export()`/`serialize()`: + + * the output is PSR-2 compatible; + * the output can be re-indented without messing up with `\r` or `\n` in the data + * missing classes throw a `ClassNotFoundException` instead of being unserialized to + `PHP_Incomplete_Class` objects; + * references involving `SplObjectStorage`, `ArrayObject` or `ArrayIterator` + instances are preserved; + * `Reflection*`, `IteratorIterator` and `RecursiveIteratorIterator` classes + throw an exception when being serialized (their unserialized version is broken + anyway, see https://bugs.php.net/76737). + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/var_exporter.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/var-exporter/VarExporter.php b/vendor/symfony/var-exporter/VarExporter.php new file mode 100644 index 0000000..c562719 --- /dev/null +++ b/vendor/symfony/var-exporter/VarExporter.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter; + +use Symfony\Component\VarExporter\Exception\ExceptionInterface; +use Symfony\Component\VarExporter\Internal\Exporter; +use Symfony\Component\VarExporter\Internal\Hydrator; +use Symfony\Component\VarExporter\Internal\Registry; +use Symfony\Component\VarExporter\Internal\Values; + +/** + * Exports serializable PHP values to PHP code. + * + * VarExporter allows serializing PHP data structures to plain PHP code (like var_export()) + * while preserving all the semantics associated with serialize() (unlike var_export()). + * + * By leveraging OPcache, the generated PHP code is faster than doing the same with unserialize(). + * + * @author Nicolas Grekas + */ +final class VarExporter +{ + /** + * Exports a serializable PHP value to PHP code. + * + * @param mixed $value The value to export + * @param bool &$isStaticValue Set to true after execution if the provided value is static, false otherwise + * @param bool &$classes Classes found in the value are added to this list as both keys and values + * + * @return string + * + * @throws ExceptionInterface When the provided value cannot be serialized + */ + public static function export($value, bool &$isStaticValue = null, array &$foundClasses = []): string + { + $isStaticValue = true; + + if (!\is_object($value) && !(\is_array($value) && $value) && !\is_resource($value) || $value instanceof \UnitEnum) { + return Exporter::export($value); + } + + $objectsPool = new \SplObjectStorage(); + $refsPool = []; + $objectsCount = 0; + + try { + $value = Exporter::prepare([$value], $objectsPool, $refsPool, $objectsCount, $isStaticValue)[0]; + } finally { + $references = []; + foreach ($refsPool as $i => $v) { + if ($v[0]->count) { + $references[1 + $i] = $v[2]; + } + $v[0] = $v[1]; + } + } + + if ($isStaticValue) { + return Exporter::export($value); + } + + $classes = []; + $values = []; + $states = []; + foreach ($objectsPool as $i => $v) { + [, $class, $values[], $wakeup] = $objectsPool[$v]; + $foundClasses[$class] = $classes[] = $class; + + if (0 < $wakeup) { + $states[$wakeup] = $i; + } elseif (0 > $wakeup) { + $states[-$wakeup] = [$i, array_pop($values)]; + $values[] = []; + } + } + ksort($states); + + $wakeups = [null]; + foreach ($states as $k => $v) { + if (\is_array($v)) { + $wakeups[-$v[0]] = $v[1]; + } else { + $wakeups[] = $v; + } + } + + if (null === $wakeups[0]) { + unset($wakeups[0]); + } + + $properties = []; + foreach ($values as $i => $vars) { + foreach ($vars as $class => $values) { + foreach ($values as $name => $v) { + $properties[$class][$name][$i] = $v; + } + } + } + + if ($classes || $references) { + $value = new Hydrator(new Registry($classes), $references ? new Values($references) : null, $properties, $value, $wakeups); + } else { + $isStaticValue = true; + } + + return Exporter::export($value); + } +} diff --git a/vendor/symfony/var-exporter/composer.json b/vendor/symfony/var-exporter/composer.json new file mode 100644 index 0000000..29d4901 --- /dev/null +++ b/vendor/symfony/var-exporter/composer.json @@ -0,0 +1,32 @@ +{ + "name": "symfony/var-exporter", + "type": "library", + "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "keywords": ["export", "serialize", "instantiate", "hydrate", "construct", "clone"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "symfony/var-dumper": "^4.4.9|^5.0.9|^6.0" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\VarExporter\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/topthink/framework/.gitignore b/vendor/topthink/framework/.gitignore new file mode 100644 index 0000000..b267fba --- /dev/null +++ b/vendor/topthink/framework/.gitignore @@ -0,0 +1,7 @@ +/vendor +composer.phar +composer.lock +.DS_Store +Thumbs.db +/.idea +/.vscode \ No newline at end of file diff --git a/vendor/topthink/framework/.travis.yml b/vendor/topthink/framework/.travis.yml new file mode 100644 index 0000000..abaa271 --- /dev/null +++ b/vendor/topthink/framework/.travis.yml @@ -0,0 +1,35 @@ +dist: xenial +language: php + +matrix: + fast_finish: true + include: + - php: 7.2 + - php: 7.3 + - php: 8.0 + +cache: + directories: + - $HOME/.composer/cache + +services: + - memcached + - redis-server + - mysql + +before_install: + - echo "extension = memcached.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini + - echo 'xdebug.mode = coverage' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini + - printf "\n" | pecl install -f redis + - travis_retry composer self-update + - mysql -e 'CREATE DATABASE test;' + +install: + - travis_retry composer update --prefer-dist --no-interaction --prefer-stable --no-suggest + +script: + - vendor/bin/phpunit --coverage-clover build/logs/coverage.xml + +after_script: + - travis_retry wget https://scrutinizer-ci.com/ocular.phar + - php ocular.phar code-coverage:upload --format=php-clover build/logs/coverage.xml diff --git a/vendor/topthink/framework/CONTRIBUTING.md b/vendor/topthink/framework/CONTRIBUTING.md new file mode 100644 index 0000000..efa3ad9 --- /dev/null +++ b/vendor/topthink/framework/CONTRIBUTING.md @@ -0,0 +1,119 @@ +如何贡献我的源代码 +=== + +此文档介绍了 ThinkPHP 团队的组成以及运转机制,您提交的代码将给 ThinkPHP 项目带来什么好处,以及如何才能加入我们的行列。 + +## 通过 Github 贡献代码 + +ThinkPHP 目前使用 Git 来控制程序版本,如果你想为 ThinkPHP 贡献源代码,请先大致了解 Git 的使用方法。我们目前把项目托管在 GitHub 上,任何 GitHub 用户都可以向我们贡献代码。 + +参与的方式很简单,`fork`一份 ThinkPHP 的代码到你的仓库中,修改后提交,并向我们发起`pull request`申请,我们会及时对代码进行审查并处理你的申请并。审查通过后,你的代码将被`merge`进我们的仓库中,这样你就会自动出现在贡献者名单里了,非常方便。 + +我们希望你贡献的代码符合: + +* ThinkPHP 的编码规范 +* 适当的注释,能让其他人读懂 +* 遵循 Apache2 开源协议 + +**如果想要了解更多细节或有任何疑问,请继续阅读下面的内容** + +### 注意事项 + +* 本项目代码格式化标准选用 [**PSR-2**](http://www.kancloud.cn/thinkphp/php-fig-psr/3141); +* 类名和类文件名遵循 [**PSR-4**](http://www.kancloud.cn/thinkphp/php-fig-psr/3144); +* 对于 Issues 的处理,请使用诸如 `fix #xxx(Issue ID)` 的 commit title 直接关闭 issue。 +* 系统会自动在 PHP 7.1 ~ 7.3 上测试修改,请确保你的修改符合 PHP 7.1 ~ 7.3 的语法规范; +* 管理员不会合并造成 CI faild 的修改,若出现 CI faild 请检查自己的源代码或修改相应的[单元测试文件](tests); + +## GitHub Issue + +GitHub 提供了 Issue 功能,该功能可以用于: + +* 提出 bug +* 提出功能改进 +* 反馈使用体验 + +该功能不应该用于: + + * 提出修改意见(涉及代码署名和修订追溯问题) + * 不友善的言论 + +## 快速修改 + +**GitHub 提供了快速编辑文件的功能** + +1. 登录 GitHub 帐号; +2. 浏览项目文件,找到要进行修改的文件; +3. 点击右上角铅笔图标进行修改; +4. 填写 `Commit changes` 相关内容(Title 必填); +5. 提交修改,等待 CI 验证和管理员合并。 + +**若您需要一次提交大量修改,请继续阅读下面的内容** + +## 完整流程 + +1. `fork`本项目; +2. 克隆(`clone`)你 `fork` 的项目到本地; +3. 新建分支(`branch`)并检出(`checkout`)新分支; +4. 添加本项目到你的本地 git 仓库作为上游(`upstream`); +5. 进行修改,若你的修改包含方法或函数的增减,请记得修改[单元测试文件](tests); +6. 变基(衍合 `rebase`)你的分支到上游 master 分支; +7. `push` 你的本地仓库到 GitHub; +8. 提交 `pull request`; +9. 等待 CI 验证(若不通过则重复 5~7,GitHub 会自动更新你的 `pull request`); +10. 等待管理员处理,并及时 `rebase` 你的分支到上游 master 分支(若上游 master 分支有修改)。 + +*若有必要,可以 `git push -f` 强行推送 rebase 后的分支到自己的 `fork`* + +*绝对不可以使用 `git push -f` 强行推送修改到上游* + +### 注意事项 + +* 若对上述流程有任何不清楚的地方,请查阅 GIT 教程,如 [这个](http://backlogtool.com/git-guide/cn/); +* 对于代码**不同方面**的修改,请在自己 `fork` 的项目中**创建不同的分支**(原因参见`完整流程`第9条备注部分); +* 变基及交互式变基操作参见 [Git 交互式变基](http://pakchoi.me/2015/03/17/git-interactive-rebase/) + +## 推荐资源 + +### 开发环境 + +* XAMPP for Windows 5.5.x +* WampServer (for Windows) +* upupw Apache PHP5.4 ( for Windows) + +或自行安装 + +- Apache / Nginx +- PHP 7.1 ~ 7.3 +- MySQL / MariaDB + +*Windows 用户推荐添加 PHP bin 目录到 PATH,方便使用 composer* + +*Linux 用户自行配置环境, Mac 用户推荐使用内置 Apache 配合 Homebrew 安装 PHP 和 MariaDB* + +### 编辑器 + +Sublime Text 3 + phpfmt 插件 + +phpfmt 插件参数 + +```json +{ + "autocomplete": true, + "enable_auto_align": true, + "format_on_save": true, + "indent_with_space": true, + "psr1_naming": false, + "psr2": true, + "version": 4 +} +``` + +或其他 编辑器 / IDE 配合 PSR2 自动格式化工具 + +### Git GUI + +* SourceTree +* GitHub Desktop + +或其他 Git 图形界面客户端 diff --git a/vendor/topthink/framework/LICENSE.txt b/vendor/topthink/framework/LICENSE.txt new file mode 100644 index 0000000..4e910bb --- /dev/null +++ b/vendor/topthink/framework/LICENSE.txt @@ -0,0 +1,32 @@ + +ThinkPHP遵循Apache2开源协议发布,并提供免费使用。 +版权所有Copyright © 2006-2019 by ThinkPHP (http://thinkphp.cn) +All rights reserved。 +ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。 + +Apache Licence是著名的非盈利开源组织Apache采用的协议。 +该协议和BSD类似,鼓励代码共享和尊重原作者的著作权, +允许代码修改,再作为开源或商业软件发布。需要满足 +的条件: +1. 需要给代码的用户一份Apache Licence ; +2. 如果你修改了代码,需要在被修改的文件中说明; +3. 在延伸的代码中(修改和有源代码衍生的代码中)需要 +带有原来代码中的协议,商标,专利声明和其他原来作者规 +定需要包含的说明; +4. 如果再发布的产品中包含一个Notice文件,则在Notice文 +件中需要带有本协议内容。你可以在Notice中增加自己的 +许可,但不可以表现为对Apache Licence构成更改。 +具体的协议参考:http://www.apache.org/licenses/LICENSE-2.0 + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/topthink/framework/README.md b/vendor/topthink/framework/README.md new file mode 100644 index 0000000..0ea03c9 --- /dev/null +++ b/vendor/topthink/framework/README.md @@ -0,0 +1,86 @@ +![](https://box.kancloud.cn/5a0aaa69a5ff42657b5c4715f3d49221) + +ThinkPHP 6.0 +=============== + +[![Build Status](https://travis-ci.org/top-think/framework.svg?branch=6.0)](https://travis-ci.org/top-think/framework) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/top-think/framework/badges/quality-score.png?b=6.0)](https://scrutinizer-ci.com/g/top-think/framework/?branch=6.0) +[![Code Coverage](https://scrutinizer-ci.com/g/top-think/framework/badges/coverage.png?b=6.0)](https://scrutinizer-ci.com/g/top-think/framework/?branch=6.0) +[![Total Downloads](https://poser.pugx.org/topthink/framework/downloads)](https://packagist.org/packages/topthink/framework) +[![Latest Stable Version](https://poser.pugx.org/topthink/framework/v/stable)](https://packagist.org/packages/topthink/framework) +[![PHP Version](https://img.shields.io/badge/php-%3E%3D7.1-8892BF.svg)](http://www.php.net/) +[![License](https://poser.pugx.org/topthink/framework/license)](https://packagist.org/packages/topthink/framework) + +ThinkPHP6.0底层架构采用PHP7.1改写和进一步优化。 + +[官方应用服务市场](https://market.topthink.com) | [`ThinkAPI`——官方统一API服务](https://docs.topthink.com/think-api/) + +## 主要新特性 + +* 采用`PHP7`强类型(严格模式) +* 支持更多的`PSR`规范 +* 原生多应用支持 +* 系统服务注入支持 +* ORM作为独立组件使用 +* 增加Filesystem +* 全新的事件系统 +* 模板引擎分离出核心 +* 内部功能中间件化 +* SESSION机制改进 +* 日志多通道支持 +* 规范扩展接口 +* 更强大的控制台 +* 对Swoole以及协程支持改进 +* 对IDE更加友好 +* 统一和精简大量用法 + + +> ThinkPHP6.0的运行环境要求PHP7.1+,兼容PHP8.0。 + +## 安装 + +~~~ +composer create-project topthink/think tp +~~~ + +启动服务 + +~~~ +cd tp +php think run +~~~ + +然后就可以在浏览器中访问 + +~~~ +http://localhost:8000 +~~~ + +如果需要更新框架使用 +~~~ +composer update topthink/framework +~~~ + +## 文档 + +[完全开发手册](https://www.kancloud.cn/manual/thinkphp6_0/content) + +## 命名规范 + +`ThinkPHP6`遵循PSR-2命名规范和PSR-4自动加载规范。 + +## 参与开发 + +直接提交PR或者Issue即可 + +## 版权信息 + +ThinkPHP遵循Apache2开源协议发布,并提供免费使用。 + +本项目包含的第三方源码和二进制文件之版权信息另行标注。 + +版权所有Copyright © 2006-2021 by ThinkPHP (http://thinkphp.cn) All rights reserved。 + +ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。 + +更多细节参阅 [LICENSE.txt](LICENSE.txt) diff --git a/vendor/topthink/framework/composer.json b/vendor/topthink/framework/composer.json new file mode 100644 index 0000000..4be2ae0 --- /dev/null +++ b/vendor/topthink/framework/composer.json @@ -0,0 +1,54 @@ +{ + "name": "topthink/framework", + "description": "The ThinkPHP Framework.", + "keywords": [ + "framework", + "thinkphp", + "ORM" + ], + "homepage": "http://thinkphp.cn/", + "license": "Apache-2.0", + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + }, + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "require": { + "php": ">=7.2.5", + "ext-json": "*", + "ext-mbstring": "*", + "league/flysystem": "^1.1.4", + "league/flysystem-cached-adapter": "^1.0", + "psr/log": "~1.0", + "psr/container": "~1.0", + "psr/simple-cache": "^1.0", + "topthink/think-orm": "^2.0", + "topthink/think-helper": "^3.1.1" + }, + "require-dev": { + "mikey179/vfsstream": "^1.6", + "mockery/mockery": "^1.2", + "phpunit/phpunit": "^7.0" + }, + "autoload": { + "files": [], + "psr-4": { + "think\\": "src/think/" + } + }, + "autoload-dev": { + "psr-4": { + "think\\tests\\": "tests/" + } + }, + "minimum-stability": "dev", + "prefer-stable": true, + "config": { + "sort-packages": true + } +} diff --git a/vendor/topthink/framework/logo.png b/vendor/topthink/framework/logo.png new file mode 100644 index 0000000..25fd059 Binary files /dev/null and b/vendor/topthink/framework/logo.png differ diff --git a/vendor/topthink/framework/phpunit.xml.dist b/vendor/topthink/framework/phpunit.xml.dist new file mode 100644 index 0000000..e20a133 --- /dev/null +++ b/vendor/topthink/framework/phpunit.xml.dist @@ -0,0 +1,25 @@ + + + + + ./tests + + + + + ./src/think + + + diff --git a/vendor/topthink/framework/src/helper.php b/vendor/topthink/framework/src/helper.php new file mode 100644 index 0000000..650edcb --- /dev/null +++ b/vendor/topthink/framework/src/helper.php @@ -0,0 +1,663 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +//------------------------ +// ThinkPHP 助手函数 +//------------------------- + +use think\App; +use think\Container; +use think\exception\HttpException; +use think\exception\HttpResponseException; +use think\facade\Cache; +use think\facade\Config; +use think\facade\Cookie; +use think\facade\Env; +use think\facade\Event; +use think\facade\Lang; +use think\facade\Log; +use think\facade\Request; +use think\facade\Route; +use think\facade\Session; +use think\Response; +use think\response\File; +use think\response\Json; +use think\response\Jsonp; +use think\response\Redirect; +use think\response\View; +use think\response\Xml; +use think\route\Url as UrlBuild; +use think\Validate; + +if (!function_exists('abort')) { + /** + * 抛出HTTP异常 + * @param integer|Response $code 状态码 或者 Response对象实例 + * @param string $message 错误信息 + * @param array $header 参数 + */ + function abort($code, string $message = '', array $header = []) + { + if ($code instanceof Response) { + throw new HttpResponseException($code); + } else { + throw new HttpException($code, $message, null, $header); + } + } +} + +if (!function_exists('app')) { + /** + * 快速获取容器中的实例 支持依赖注入 + * @param string $name 类名或标识 默认获取当前应用实例 + * @param array $args 参数 + * @param bool $newInstance 是否每次创建新的实例 + * @return object|App + */ + function app(string $name = '', array $args = [], bool $newInstance = false) + { + return Container::getInstance()->make($name ?: App::class, $args, $newInstance); + } +} + +if (!function_exists('bind')) { + /** + * 绑定一个类到容器 + * @param string|array $abstract 类标识、接口(支持批量绑定) + * @param mixed $concrete 要绑定的类、闭包或者实例 + * @return Container + */ + function bind($abstract, $concrete = null) + { + return Container::getInstance()->bind($abstract, $concrete); + } +} + +if (!function_exists('cache')) { + /** + * 缓存管理 + * @param string $name 缓存名称 + * @param mixed $value 缓存值 + * @param mixed $options 缓存参数 + * @param string $tag 缓存标签 + * @return mixed + */ + function cache(string $name = null, $value = '', $options = null, $tag = null) + { + if (is_null($name)) { + return app('cache'); + } + + if ('' === $value) { + // 获取缓存 + return 0 === strpos($name, '?') ? Cache::has(substr($name, 1)) : Cache::get($name); + } elseif (is_null($value)) { + // 删除缓存 + return Cache::delete($name); + } + + // 缓存数据 + if (is_array($options)) { + $expire = $options['expire'] ?? null; //修复查询缓存无法设置过期时间 + } else { + $expire = $options; + } + + if (is_null($tag)) { + return Cache::set($name, $value, $expire); + } else { + return Cache::tag($tag)->set($name, $value, $expire); + } + } +} + +if (!function_exists('config')) { + /** + * 获取和设置配置参数 + * @param string|array $name 参数名 + * @param mixed $value 参数值 + * @return mixed + */ + function config($name = '', $value = null) + { + if (is_array($name)) { + return Config::set($name, $value); + } + + return 0 === strpos($name, '?') ? Config::has(substr($name, 1)) : Config::get($name, $value); + } +} + +if (!function_exists('cookie')) { + /** + * Cookie管理 + * @param string $name cookie名称 + * @param mixed $value cookie值 + * @param mixed $option 参数 + * @return mixed + */ + function cookie(string $name, $value = '', $option = null) + { + if (is_null($value)) { + // 删除 + Cookie::delete($name); + } elseif ('' === $value) { + // 获取 + return 0 === strpos($name, '?') ? Cookie::has(substr($name, 1)) : Cookie::get($name); + } else { + // 设置 + return Cookie::set($name, $value, $option); + } + } +} + +if (!function_exists('download')) { + /** + * 获取\think\response\Download对象实例 + * @param string $filename 要下载的文件 + * @param string $name 显示文件名 + * @param bool $content 是否为内容 + * @param int $expire 有效期(秒) + * @return \think\response\File + */ + function download(string $filename, string $name = '', bool $content = false, int $expire = 180): File + { + return Response::create($filename, 'file')->name($name)->isContent($content)->expire($expire); + } +} + +if (!function_exists('dump')) { + /** + * 浏览器友好的变量输出 + * @param mixed $vars 要输出的变量 + * @return void + */ + function dump(...$vars) + { + ob_start(); + var_dump(...$vars); + + $output = ob_get_clean(); + $output = preg_replace('/\]\=\>\n(\s+)/m', '] => ', $output); + + if (PHP_SAPI == 'cli') { + $output = PHP_EOL . $output . PHP_EOL; + } else { + if (!extension_loaded('xdebug')) { + $output = htmlspecialchars($output, ENT_SUBSTITUTE); + } + $output = '
        ' . $output . '
        '; + } + + echo $output; + } +} + +if (!function_exists('env')) { + /** + * 获取环境变量值 + * @access public + * @param string $name 环境变量名(支持二级 .号分割) + * @param string $default 默认值 + * @return mixed + */ + function env(string $name = null, $default = null) + { + return Env::get($name, $default); + } +} + +if (!function_exists('event')) { + /** + * 触发事件 + * @param mixed $event 事件名(或者类名) + * @param mixed $args 参数 + * @return mixed + */ + function event($event, $args = null) + { + return Event::trigger($event, $args); + } +} + +if (!function_exists('halt')) { + /** + * 调试变量并且中断输出 + * @param mixed $vars 调试变量或者信息 + */ + function halt(...$vars) + { + dump(...$vars); + + throw new HttpResponseException(Response::create()); + } +} + +if (!function_exists('input')) { + /** + * 获取输入数据 支持默认值和过滤 + * @param string $key 获取的变量名 + * @param mixed $default 默认值 + * @param string $filter 过滤方法 + * @return mixed + */ + function input(string $key = '', $default = null, $filter = '') + { + if (0 === strpos($key, '?')) { + $key = substr($key, 1); + $has = true; + } + + if ($pos = strpos($key, '.')) { + // 指定参数来源 + $method = substr($key, 0, $pos); + if (in_array($method, ['get', 'post', 'put', 'patch', 'delete', 'route', 'param', 'request', 'session', 'cookie', 'server', 'env', 'path', 'file'])) { + $key = substr($key, $pos + 1); + if ('server' == $method && is_null($default)) { + $default = ''; + } + } else { + $method = 'param'; + } + } else { + // 默认为自动判断 + $method = 'param'; + } + + return isset($has) ? + request()->has($key, $method) : + request()->$method($key, $default, $filter); + } +} + +if (!function_exists('invoke')) { + /** + * 调用反射实例化对象或者执行方法 支持依赖注入 + * @param mixed $call 类名或者callable + * @param array $args 参数 + * @return mixed + */ + function invoke($call, array $args = []) + { + if (is_callable($call)) { + return Container::getInstance()->invoke($call, $args); + } + + return Container::getInstance()->invokeClass($call, $args); + } +} + +if (!function_exists('json')) { + /** + * 获取\think\response\Json对象实例 + * @param mixed $data 返回的数据 + * @param int $code 状态码 + * @param array $header 头部 + * @param array $options 参数 + * @return \think\response\Json + */ + function json($data = [], $code = 200, $header = [], $options = []): Json + { + return Response::create($data, 'json', $code)->header($header)->options($options); + } +} + +if (!function_exists('jsonp')) { + /** + * 获取\think\response\Jsonp对象实例 + * @param mixed $data 返回的数据 + * @param int $code 状态码 + * @param array $header 头部 + * @param array $options 参数 + * @return \think\response\Jsonp + */ + function jsonp($data = [], $code = 200, $header = [], $options = []): Jsonp + { + return Response::create($data, 'jsonp', $code)->header($header)->options($options); + } +} + +if (!function_exists('lang')) { + /** + * 获取语言变量值 + * @param string $name 语言变量名 + * @param array $vars 动态变量值 + * @param string $lang 语言 + * @return mixed + */ + function lang(string $name, array $vars = [], string $lang = '') + { + return Lang::get($name, $vars, $lang); + } +} + +if (!function_exists('parse_name')) { + /** + * 字符串命名风格转换 + * type 0 将Java风格转换为C的风格 1 将C风格转换为Java的风格 + * @param string $name 字符串 + * @param int $type 转换类型 + * @param bool $ucfirst 首字母是否大写(驼峰规则) + * @return string + */ + function parse_name(string $name, int $type = 0, bool $ucfirst = true): string + { + if ($type) { + $name = preg_replace_callback('/_([a-zA-Z])/', function ($match) { + return strtoupper($match[1]); + }, $name); + + return $ucfirst ? ucfirst($name) : lcfirst($name); + } + + return strtolower(trim(preg_replace('/[A-Z]/', '_\\0', $name), '_')); + } +} + +if (!function_exists('redirect')) { + /** + * 获取\think\response\Redirect对象实例 + * @param string $url 重定向地址 + * @param int $code 状态码 + * @return \think\response\Redirect + */ + function redirect(string $url = '', int $code = 302): Redirect + { + return Response::create($url, 'redirect', $code); + } +} + +if (!function_exists('request')) { + /** + * 获取当前Request对象实例 + * @return Request + */ + function request(): \think\Request + { + return app('request'); + } +} + +if (!function_exists('response')) { + /** + * 创建普通 Response 对象实例 + * @param mixed $data 输出数据 + * @param int|string $code 状态码 + * @param array $header 头信息 + * @param string $type + * @return Response + */ + function response($data = '', $code = 200, $header = [], $type = 'html'): Response + { + return Response::create($data, $type, $code)->header($header); + } +} + +if (!function_exists('session')) { + /** + * Session管理 + * @param string $name session名称 + * @param mixed $value session值 + * @return mixed + */ + function session($name = '', $value = '') + { + if (is_null($name)) { + // 清除 + Session::clear(); + } elseif ('' === $name) { + return Session::all(); + } elseif (is_null($value)) { + // 删除 + Session::delete($name); + } elseif ('' === $value) { + // 判断或获取 + return 0 === strpos($name, '?') ? Session::has(substr($name, 1)) : Session::get($name); + } else { + // 设置 + Session::set($name, $value); + } + } +} + +if (!function_exists('token')) { + /** + * 获取Token令牌 + * @param string $name 令牌名称 + * @param mixed $type 令牌生成方法 + * @return string + */ + function token(string $name = '__token__', string $type = 'md5'): string + { + return Request::buildToken($name, $type); + } +} + +if (!function_exists('token_field')) { + /** + * 生成令牌隐藏表单 + * @param string $name 令牌名称 + * @param mixed $type 令牌生成方法 + * @return string + */ + function token_field(string $name = '__token__', string $type = 'md5'): string + { + $token = Request::buildToken($name, $type); + + return ''; + } +} + +if (!function_exists('token_meta')) { + /** + * 生成令牌meta + * @param string $name 令牌名称 + * @param mixed $type 令牌生成方法 + * @return string + */ + function token_meta(string $name = '__token__', string $type = 'md5'): string + { + $token = Request::buildToken($name, $type); + + return ''; + } +} + +if (!function_exists('trace')) { + /** + * 记录日志信息 + * @param mixed $log log信息 支持字符串和数组 + * @param string $level 日志级别 + * @return array|void + */ + function trace($log = '[think]', string $level = 'log') + { + if ('[think]' === $log) { + return Log::getLog(); + } + + Log::record($log, $level); + } +} + +if (!function_exists('url')) { + /** + * Url生成 + * @param string $url 路由地址 + * @param array $vars 变量 + * @param bool|string $suffix 生成的URL后缀 + * @param bool|string $domain 域名 + * @return UrlBuild + */ + function url(string $url = '', array $vars = [], $suffix = true, $domain = false): UrlBuild + { + return Route::buildUrl($url, $vars)->suffix($suffix)->domain($domain); + } +} + +if (!function_exists('validate')) { + /** + * 生成验证对象 + * @param string|array $validate 验证器类名或者验证规则数组 + * @param array $message 错误提示信息 + * @param bool $batch 是否批量验证 + * @param bool $failException 是否抛出异常 + * @return Validate + */ + function validate($validate = '', array $message = [], bool $batch = false, bool $failException = true): Validate + { + if (is_array($validate) || '' === $validate) { + $v = new Validate(); + if (is_array($validate)) { + $v->rule($validate); + } + } else { + if (strpos($validate, '.')) { + // 支持场景 + [$validate, $scene] = explode('.', $validate); + } + + $class = false !== strpos($validate, '\\') ? $validate : app()->parseClass('validate', $validate); + + $v = new $class(); + + if (!empty($scene)) { + $v->scene($scene); + } + } + + return $v->message($message)->batch($batch)->failException($failException); + } +} + +if (!function_exists('view')) { + /** + * 渲染模板输出 + * @param string $template 模板文件 + * @param array $vars 模板变量 + * @param int $code 状态码 + * @param callable $filter 内容过滤 + * @return \think\response\View + */ + function view(string $template = '', $vars = [], $code = 200, $filter = null): View + { + return Response::create($template, 'view', $code)->assign($vars)->filter($filter); + } +} + +if (!function_exists('display')) { + /** + * 渲染模板输出 + * @param string $content 渲染内容 + * @param array $vars 模板变量 + * @param int $code 状态码 + * @param callable $filter 内容过滤 + * @return \think\response\View + */ + function display(string $content, $vars = [], $code = 200, $filter = null): View + { + return Response::create($content, 'view', $code)->isContent(true)->assign($vars)->filter($filter); + } +} + +if (!function_exists('xml')) { + /** + * 获取\think\response\Xml对象实例 + * @param mixed $data 返回的数据 + * @param int $code 状态码 + * @param array $header 头部 + * @param array $options 参数 + * @return \think\response\Xml + */ + function xml($data = [], $code = 200, $header = [], $options = []): Xml + { + return Response::create($data, 'xml', $code)->header($header)->options($options); + } +} + +if (!function_exists('app_path')) { + /** + * 获取当前应用目录 + * + * @param string $path + * @return string + */ + function app_path($path = '') + { + return app()->getAppPath() . ($path ? $path . DIRECTORY_SEPARATOR : $path); + } +} + +if (!function_exists('base_path')) { + /** + * 获取应用基础目录 + * + * @param string $path + * @return string + */ + function base_path($path = '') + { + return app()->getBasePath() . ($path ? $path . DIRECTORY_SEPARATOR : $path); + } +} + +if (!function_exists('config_path')) { + /** + * 获取应用配置目录 + * + * @param string $path + * @return string + */ + function config_path($path = '') + { + return app()->getConfigPath() . ($path ? $path . DIRECTORY_SEPARATOR : $path); + } +} + +if (!function_exists('public_path')) { + /** + * 获取web根目录 + * + * @param string $path + * @return string + */ + function public_path($path = '') + { + return app()->getRootPath() . 'public' . DIRECTORY_SEPARATOR . ($path ? ltrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : $path); + } +} + +if (!function_exists('runtime_path')) { + /** + * 获取应用运行时目录 + * + * @param string $path + * @return string + */ + function runtime_path($path = '') + { + return app()->getRuntimePath() . ($path ? $path . DIRECTORY_SEPARATOR : $path); + } +} + +if (!function_exists('root_path')) { + /** + * 获取项目根目录 + * + * @param string $path + * @return string + */ + function root_path($path = '') + { + return app()->getRootPath() . ($path ? $path . DIRECTORY_SEPARATOR : $path); + } +} diff --git a/vendor/topthink/framework/src/lang/zh-cn.php b/vendor/topthink/framework/src/lang/zh-cn.php new file mode 100644 index 0000000..a546330 --- /dev/null +++ b/vendor/topthink/framework/src/lang/zh-cn.php @@ -0,0 +1,148 @@ + +// +---------------------------------------------------------------------- + +// 核心中文语言包 +return [ + // 系统错误提示 + 'Undefined variable' => '未定义变量', + 'Undefined index' => '未定义数组索引', + 'Undefined offset' => '未定义数组下标', + 'Parse error' => '语法解析错误', + 'Type error' => '类型错误', + 'Fatal error' => '致命错误', + 'syntax error' => '语法错误', + + // 框架核心错误提示 + 'dispatch type not support' => '不支持的调度类型', + 'method param miss' => '方法参数错误', + 'method not exists' => '方法不存在', + 'function not exists' => '函数不存在', + 'app not exists' => '应用不存在', + 'controller not exists' => '控制器不存在', + 'class not exists' => '类不存在', + 'property not exists' => '类的属性不存在', + 'template not exists' => '模板文件不存在', + 'illegal controller name' => '非法的控制器名称', + 'illegal action name' => '非法的操作名称', + 'url suffix deny' => '禁止的URL后缀访问', + 'Undefined cache config' => '缓存配置未定义', + 'Route Not Found' => '当前访问路由未定义或不匹配', + 'Undefined db config' => '数据库配置未定义', + 'Undefined log config' => '日志配置未定义', + 'Undefined db type' => '未定义数据库类型', + 'variable type error' => '变量类型错误', + 'PSR-4 error' => 'PSR-4 规范错误', + 'not support type' => '不支持的分页索引字段类型', + 'not support total' => '简洁模式下不能获取数据总数', + 'not support last' => '简洁模式下不能获取最后一页', + 'error session handler' => '错误的SESSION处理器类', + 'not allow php tag' => '模板不允许使用PHP语法', + 'not support' => '不支持', + 'database config error' => '数据库配置信息错误', + 'redisd master' => 'Redisd 主服务器错误', + 'redisd slave' => 'Redisd 从服务器错误', + 'must run at sae' => '必须在SAE运行', + 'memcache init error' => '未开通Memcache服务,请在SAE管理平台初始化Memcache服务', + 'KVDB init error' => '没有初始化KVDB,请在SAE管理平台初始化KVDB服务', + 'fields not exists' => '数据表字段不存在', + 'where express error' => '查询表达式错误', + 'no data to update' => '没有任何数据需要更新', + 'miss data to insert' => '缺少需要写入的数据', + 'miss complex primary data' => '缺少复合主键数据', + 'miss update condition' => '缺少更新条件', + 'model data Not Found' => '模型数据不存在', + 'table data not Found' => '表数据不存在', + 'delete without condition' => '没有条件不会执行删除操作', + 'miss relation data' => '缺少关联表数据', + 'tag attr must' => '模板标签属性必须', + 'tag error' => '模板标签错误', + 'cache write error' => '缓存写入失败', + 'sae mc write error' => 'SAE mc 写入错误', + 'route name not exists' => '路由标识不存在(或参数不够)', + 'invalid request' => '非法请求', + 'bind attr has exists' => '模型的属性已经存在', + 'relation data not exists' => '关联数据不存在', + 'relation not support' => '关联不支持', + 'chunk not support order' => 'Chunk不支持调用order方法', + 'route pattern error' => '路由变量规则定义错误', + 'route behavior will not support' => '路由行为废弃(使用中间件替代)', + 'closure not support cache(true)' => '使用闭包查询不支持cache(true),请指定缓存Key', + + // 上传错误信息 + 'unknown upload error' => '未知上传错误!', + 'file write error' => '文件写入失败!', + 'upload temp dir not found' => '找不到临时文件夹!', + 'no file to uploaded' => '没有文件被上传!', + 'only the portion of file is uploaded' => '文件只有部分被上传!', + 'upload File size exceeds the maximum value' => '上传文件大小超过了最大值!', + 'upload write error' => '文件上传保存错误!', + 'has the same filename: {:filename}' => '存在同名文件:{:filename}', + 'upload illegal files' => '非法上传文件', + 'illegal image files' => '非法图片文件', + 'extensions to upload is not allowed' => '上传文件后缀不允许', + 'mimetype to upload is not allowed' => '上传文件MIME类型不允许!', + 'filesize not match' => '上传文件大小不符!', + 'directory {:path} creation failed' => '目录 {:path} 创建失败!', + + 'The middleware must return Response instance' => '中间件方法必须返回Response对象实例', + 'The queue was exhausted, with no response returned' => '中间件队列为空', + // Validate Error Message + ':attribute require' => ':attribute不能为空', + ':attribute must' => ':attribute必须', + ':attribute must be numeric' => ':attribute必须是数字', + ':attribute must be integer' => ':attribute必须是整数', + ':attribute must be float' => ':attribute必须是浮点数', + ':attribute must be bool' => ':attribute必须是布尔值', + ':attribute not a valid email address' => ':attribute格式不符', + ':attribute not a valid mobile' => ':attribute格式不符', + ':attribute must be a array' => ':attribute必须是数组', + ':attribute must be yes,on or 1' => ':attribute必须是yes、on或者1', + ':attribute not a valid datetime' => ':attribute不是一个有效的日期或时间格式', + ':attribute not a valid file' => ':attribute不是有效的上传文件', + ':attribute not a valid image' => ':attribute不是有效的图像文件', + ':attribute must be alpha' => ':attribute只能是字母', + ':attribute must be alpha-numeric' => ':attribute只能是字母和数字', + ':attribute must be alpha-numeric, dash, underscore' => ':attribute只能是字母、数字和下划线_及破折号-', + ':attribute not a valid domain or ip' => ':attribute不是有效的域名或者IP', + ':attribute must be chinese' => ':attribute只能是汉字', + ':attribute must be chinese or alpha' => ':attribute只能是汉字、字母', + ':attribute must be chinese,alpha-numeric' => ':attribute只能是汉字、字母和数字', + ':attribute must be chinese,alpha-numeric,underscore, dash' => ':attribute只能是汉字、字母、数字和下划线_及破折号-', + ':attribute not a valid url' => ':attribute不是有效的URL地址', + ':attribute not a valid ip' => ':attribute不是有效的IP地址', + ':attribute must be dateFormat of :rule' => ':attribute必须使用日期格式 :rule', + ':attribute must be in :rule' => ':attribute必须在 :rule 范围内', + ':attribute be notin :rule' => ':attribute不能在 :rule 范围内', + ':attribute must between :1 - :2' => ':attribute只能在 :1 - :2 之间', + ':attribute not between :1 - :2' => ':attribute不能在 :1 - :2 之间', + 'size of :attribute must be :rule' => ':attribute长度不符合要求 :rule', + 'max size of :attribute must be :rule' => ':attribute长度不能超过 :rule', + 'min size of :attribute must be :rule' => ':attribute长度不能小于 :rule', + ':attribute cannot be less than :rule' => ':attribute日期不能小于 :rule', + ':attribute cannot exceed :rule' => ':attribute日期不能超过 :rule', + ':attribute not within :rule' => '不在有效期内 :rule', + 'access IP is not allowed' => '不允许的IP访问', + 'access IP denied' => '禁止的IP访问', + ':attribute out of accord with :2' => ':attribute和确认字段:2不一致', + ':attribute cannot be same with :2' => ':attribute和比较字段:2不能相同', + ':attribute must greater than or equal :rule' => ':attribute必须大于等于 :rule', + ':attribute must greater than :rule' => ':attribute必须大于 :rule', + ':attribute must less than or equal :rule' => ':attribute必须小于等于 :rule', + ':attribute must less than :rule' => ':attribute必须小于 :rule', + ':attribute must equal :rule' => ':attribute必须等于 :rule', + ':attribute has exists' => ':attribute已存在', + ':attribute not conform to the rules' => ':attribute不符合指定规则', + 'invalid Request method' => '无效的请求类型', + 'invalid token' => '令牌数据无效', + 'not conform to the rules' => '规则错误', + + 'record has update' => '记录已经被更新了', +]; diff --git a/vendor/topthink/framework/src/think/App.php b/vendor/topthink/framework/src/think/App.php new file mode 100644 index 0000000..056a341 --- /dev/null +++ b/vendor/topthink/framework/src/think/App.php @@ -0,0 +1,639 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use think\event\AppInit; +use think\helper\Str; +use think\initializer\BootService; +use think\initializer\Error; +use think\initializer\RegisterService; + +/** + * App 基础类 + * @property Route $route + * @property Config $config + * @property Cache $cache + * @property Request $request + * @property Http $http + * @property Console $console + * @property Env $env + * @property Event $event + * @property Middleware $middleware + * @property Log $log + * @property Lang $lang + * @property Db $db + * @property Cookie $cookie + * @property Session $session + * @property Validate $validate + * @property Filesystem $filesystem + */ +class App extends Container +{ + const VERSION = '6.0.9'; + + /** + * 应用调试模式 + * @var bool + */ + protected $appDebug = false; + + /** + * 环境变量标识 + * @var string + */ + protected $envName = ''; + + /** + * 应用开始时间 + * @var float + */ + protected $beginTime; + + /** + * 应用内存初始占用 + * @var integer + */ + protected $beginMem; + + /** + * 当前应用类库命名空间 + * @var string + */ + protected $namespace = 'app'; + + /** + * 应用根目录 + * @var string + */ + protected $rootPath = ''; + + /** + * 框架目录 + * @var string + */ + protected $thinkPath = ''; + + /** + * 应用目录 + * @var string + */ + protected $appPath = ''; + + /** + * Runtime目录 + * @var string + */ + protected $runtimePath = ''; + + /** + * 路由定义目录 + * @var string + */ + protected $routePath = ''; + + /** + * 配置后缀 + * @var string + */ + protected $configExt = '.php'; + + /** + * 应用初始化器 + * @var array + */ + protected $initializers = [ + Error::class, + RegisterService::class, + BootService::class, + ]; + + /** + * 注册的系统服务 + * @var array + */ + protected $services = []; + + /** + * 初始化 + * @var bool + */ + protected $initialized = false; + + /** + * 容器绑定标识 + * @var array + */ + protected $bind = [ + 'app' => App::class, + 'cache' => Cache::class, + 'config' => Config::class, + 'console' => Console::class, + 'cookie' => Cookie::class, + 'db' => Db::class, + 'env' => Env::class, + 'event' => Event::class, + 'http' => Http::class, + 'lang' => Lang::class, + 'log' => Log::class, + 'middleware' => Middleware::class, + 'request' => Request::class, + 'response' => Response::class, + 'route' => Route::class, + 'session' => Session::class, + 'validate' => Validate::class, + 'view' => View::class, + 'filesystem' => Filesystem::class, + 'think\DbManager' => Db::class, + 'think\LogManager' => Log::class, + 'think\CacheManager' => Cache::class, + + // 接口依赖注入 + 'Psr\Log\LoggerInterface' => Log::class, + ]; + + /** + * 架构方法 + * @access public + * @param string $rootPath 应用根目录 + */ + public function __construct(string $rootPath = '') + { + $this->thinkPath = dirname(__DIR__) . DIRECTORY_SEPARATOR; + $this->rootPath = $rootPath ? rtrim($rootPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : $this->getDefaultRootPath(); + $this->appPath = $this->rootPath . 'app' . DIRECTORY_SEPARATOR; + $this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR; + + if (is_file($this->appPath . 'provider.php')) { + $this->bind(include $this->appPath . 'provider.php'); + } + + static::setInstance($this); + + $this->instance('app', $this); + $this->instance('think\Container', $this); + } + + /** + * 注册服务 + * @access public + * @param Service|string $service 服务 + * @param bool $force 强制重新注册 + * @return Service|null + */ + public function register($service, bool $force = false) + { + $registered = $this->getService($service); + + if ($registered && !$force) { + return $registered; + } + + if (is_string($service)) { + $service = new $service($this); + } + + if (method_exists($service, 'register')) { + $service->register(); + } + + if (property_exists($service, 'bind')) { + $this->bind($service->bind); + } + + $this->services[] = $service; + } + + /** + * 执行服务 + * @access public + * @param Service $service 服务 + * @return mixed + */ + public function bootService($service) + { + if (method_exists($service, 'boot')) { + return $this->invoke([$service, 'boot']); + } + } + + /** + * 获取服务 + * @param string|Service $service + * @return Service|null + */ + public function getService($service) + { + $name = is_string($service) ? $service : get_class($service); + return array_values(array_filter($this->services, function ($value) use ($name) { + return $value instanceof $name; + }, ARRAY_FILTER_USE_BOTH))[0] ?? null; + } + + /** + * 开启应用调试模式 + * @access public + * @param bool $debug 开启应用调试模式 + * @return $this + */ + public function debug(bool $debug = true) + { + $this->appDebug = $debug; + return $this; + } + + /** + * 是否为调试模式 + * @access public + * @return bool + */ + public function isDebug(): bool + { + return $this->appDebug; + } + + /** + * 设置应用命名空间 + * @access public + * @param string $namespace 应用命名空间 + * @return $this + */ + public function setNamespace(string $namespace) + { + $this->namespace = $namespace; + return $this; + } + + /** + * 获取应用类库命名空间 + * @access public + * @return string + */ + public function getNamespace(): string + { + return $this->namespace; + } + + /** + * 设置环境变量标识 + * @access public + * @param string $name 环境标识 + * @return $this + */ + public function setEnvName(string $name) + { + $this->envName = $name; + return $this; + } + + /** + * 获取框架版本 + * @access public + * @return string + */ + public function version(): string + { + return static::VERSION; + } + + /** + * 获取应用根目录 + * @access public + * @return string + */ + public function getRootPath(): string + { + return $this->rootPath; + } + + /** + * 获取应用基础目录 + * @access public + * @return string + */ + public function getBasePath(): string + { + return $this->rootPath . 'app' . DIRECTORY_SEPARATOR; + } + + /** + * 获取当前应用目录 + * @access public + * @return string + */ + public function getAppPath(): string + { + return $this->appPath; + } + + /** + * 设置应用目录 + * @param string $path 应用目录 + */ + public function setAppPath(string $path) + { + $this->appPath = $path; + } + + /** + * 获取应用运行时目录 + * @access public + * @return string + */ + public function getRuntimePath(): string + { + return $this->runtimePath; + } + + /** + * 设置runtime目录 + * @param string $path 定义目录 + */ + public function setRuntimePath(string $path): void + { + $this->runtimePath = $path; + } + + /** + * 获取核心框架目录 + * @access public + * @return string + */ + public function getThinkPath(): string + { + return $this->thinkPath; + } + + /** + * 获取应用配置目录 + * @access public + * @return string + */ + public function getConfigPath(): string + { + return $this->rootPath . 'config' . DIRECTORY_SEPARATOR; + } + + /** + * 获取配置后缀 + * @access public + * @return string + */ + public function getConfigExt(): string + { + return $this->configExt; + } + + /** + * 获取应用开启时间 + * @access public + * @return float + */ + public function getBeginTime(): float + { + return $this->beginTime; + } + + /** + * 获取应用初始内存占用 + * @access public + * @return integer + */ + public function getBeginMem(): int + { + return $this->beginMem; + } + + /** + * 加载环境变量定义 + * @access public + * @param string $envName 环境标识 + * @return void + */ + public function loadEnv(string $envName = ''): void + { + // 加载环境变量 + $envFile = $envName ? $this->rootPath . '.env.' . $envName : $this->rootPath . '.env'; + + if (is_file($envFile)) { + $this->env->load($envFile); + } + } + + /** + * 初始化应用 + * @access public + * @return $this + */ + public function initialize() + { + $this->initialized = true; + + $this->beginTime = microtime(true); + $this->beginMem = memory_get_usage(); + + $this->loadEnv($this->envName); + + $this->configExt = $this->env->get('config_ext', '.php'); + + $this->debugModeInit(); + + // 加载全局初始化文件 + $this->load(); + + // 加载框架默认语言包 + $langSet = $this->lang->defaultLangSet(); + + $this->lang->load($this->thinkPath . 'lang' . DIRECTORY_SEPARATOR . $langSet . '.php'); + + // 加载应用默认语言包 + $this->loadLangPack($langSet); + + // 监听AppInit + $this->event->trigger(AppInit::class); + + date_default_timezone_set($this->config->get('app.default_timezone', 'Asia/Shanghai')); + + // 初始化 + foreach ($this->initializers as $initializer) { + $this->make($initializer)->init($this); + } + + return $this; + } + + /** + * 是否初始化过 + * @return bool + */ + public function initialized() + { + return $this->initialized; + } + + /** + * 加载语言包 + * @param string $langset 语言 + * @return void + */ + public function loadLangPack($langset) + { + if (empty($langset)) { + return; + } + + // 加载系统语言包 + $files = glob($this->appPath . 'lang' . DIRECTORY_SEPARATOR . $langset . '.*'); + $this->lang->load($files); + + // 加载扩展(自定义)语言包 + $list = $this->config->get('lang.extend_list', []); + + if (isset($list[$langset])) { + $this->lang->load($list[$langset]); + } + } + + /** + * 引导应用 + * @access public + * @return void + */ + public function boot(): void + { + array_walk($this->services, function ($service) { + $this->bootService($service); + }); + } + + /** + * 加载应用文件和配置 + * @access protected + * @return void + */ + protected function load(): void + { + $appPath = $this->getAppPath(); + + if (is_file($appPath . 'common.php')) { + include_once $appPath . 'common.php'; + } + + include_once $this->thinkPath . 'helper.php'; + + $configPath = $this->getConfigPath(); + + $files = []; + + if (is_dir($configPath)) { + $files = glob($configPath . '*' . $this->configExt); + } + + foreach ($files as $file) { + $this->config->load($file, pathinfo($file, PATHINFO_FILENAME)); + } + + if (is_file($appPath . 'event.php')) { + $this->loadEvent(include $appPath . 'event.php'); + } + + if (is_file($appPath . 'service.php')) { + $services = include $appPath . 'service.php'; + foreach ($services as $service) { + $this->register($service); + } + } + } + + /** + * 调试模式设置 + * @access protected + * @return void + */ + protected function debugModeInit(): void + { + // 应用调试模式 + if (!$this->appDebug) { + $this->appDebug = $this->env->get('app_debug') ? true : false; + ini_set('display_errors', 'Off'); + } + + if (!$this->runningInConsole()) { + //重新申请一块比较大的buffer + if (ob_get_level() > 0) { + $output = ob_get_clean(); + } + ob_start(); + if (!empty($output)) { + echo $output; + } + } + } + + /** + * 注册应用事件 + * @access protected + * @param array $event 事件数据 + * @return void + */ + public function loadEvent(array $event): void + { + if (isset($event['bind'])) { + $this->event->bind($event['bind']); + } + + if (isset($event['listen'])) { + $this->event->listenEvents($event['listen']); + } + + if (isset($event['subscribe'])) { + $this->event->subscribe($event['subscribe']); + } + } + + /** + * 解析应用类的类名 + * @access public + * @param string $layer 层名 controller model ... + * @param string $name 类名 + * @return string + */ + public function parseClass(string $layer, string $name): string + { + $name = str_replace(['/', '.'], '\\', $name); + $array = explode('\\', $name); + $class = Str::studly(array_pop($array)); + $path = $array ? implode('\\', $array) . '\\' : ''; + + return $this->namespace . '\\' . $layer . '\\' . $path . $class; + } + + /** + * 是否运行在命令行下 + * @return bool + */ + public function runningInConsole(): bool + { + return php_sapi_name() === 'cli' || php_sapi_name() === 'phpdbg'; + } + + /** + * 获取应用根目录 + * @access protected + * @return string + */ + protected function getDefaultRootPath(): string + { + return dirname($this->thinkPath, 4) . DIRECTORY_SEPARATOR; + } + +} diff --git a/vendor/topthink/framework/src/think/Cache.php b/vendor/topthink/framework/src/think/Cache.php new file mode 100644 index 0000000..f802b55 --- /dev/null +++ b/vendor/topthink/framework/src/think/Cache.php @@ -0,0 +1,197 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use Psr\SimpleCache\CacheInterface; +use think\cache\Driver; +use think\cache\TagSet; +use think\exception\InvalidArgumentException; +use think\helper\Arr; + +/** + * 缓存管理类 + * @mixin Driver + * @mixin \think\cache\driver\File + */ +class Cache extends Manager implements CacheInterface +{ + + protected $namespace = '\\think\\cache\\driver\\'; + + /** + * 默认驱动 + * @return string|null + */ + public function getDefaultDriver() + { + return $this->getConfig('default'); + } + + /** + * 获取缓存配置 + * @access public + * @param null|string $name 名称 + * @param mixed $default 默认值 + * @return mixed + */ + public function getConfig(string $name = null, $default = null) + { + if (!is_null($name)) { + return $this->app->config->get('cache.' . $name, $default); + } + + return $this->app->config->get('cache'); + } + + /** + * 获取驱动配置 + * @param string $store + * @param string $name + * @param null $default + * @return array + */ + public function getStoreConfig(string $store, string $name = null, $default = null) + { + if ($config = $this->getConfig("stores.{$store}")) { + return Arr::get($config, $name, $default); + } + + throw new \InvalidArgumentException("Store [$store] not found."); + } + + protected function resolveType(string $name) + { + return $this->getStoreConfig($name, 'type', 'file'); + } + + protected function resolveConfig(string $name) + { + return $this->getStoreConfig($name); + } + + /** + * 连接或者切换缓存 + * @access public + * @param string $name 连接配置名 + * @return Driver + */ + public function store(string $name = null) + { + return $this->driver($name); + } + + /** + * 清空缓冲池 + * @access public + * @return bool + */ + public function clear(): bool + { + return $this->store()->clear(); + } + + /** + * 读取缓存 + * @access public + * @param string $key 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($key, $default = null) + { + return $this->store()->get($key, $default); + } + + /** + * 写入缓存 + * @access public + * @param string $key 缓存变量名 + * @param mixed $value 存储数据 + * @param int|\DateTime $ttl 有效时间 0为永久 + * @return bool + */ + public function set($key, $value, $ttl = null): bool + { + return $this->store()->set($key, $value, $ttl); + } + + /** + * 删除缓存 + * @access public + * @param string $key 缓存变量名 + * @return bool + */ + public function delete($key): bool + { + return $this->store()->delete($key); + } + + /** + * 读取缓存 + * @access public + * @param iterable $keys 缓存变量名 + * @param mixed $default 默认值 + * @return iterable + * @throws InvalidArgumentException + */ + public function getMultiple($keys, $default = null): iterable + { + return $this->store()->getMultiple($keys, $default); + } + + /** + * 写入缓存 + * @access public + * @param iterable $values 缓存数据 + * @param null|int|\DateInterval $ttl 有效时间 0为永久 + * @return bool + */ + public function setMultiple($values, $ttl = null): bool + { + return $this->store()->setMultiple($values, $ttl); + } + + /** + * 删除缓存 + * @access public + * @param iterable $keys 缓存变量名 + * @return bool + * @throws InvalidArgumentException + */ + public function deleteMultiple($keys): bool + { + return $this->store()->deleteMultiple($keys); + } + + /** + * 判断缓存是否存在 + * @access public + * @param string $key 缓存变量名 + * @return bool + */ + public function has($key): bool + { + return $this->store()->has($key); + } + + /** + * 缓存标签 + * @access public + * @param string|array $name 标签名 + * @return TagSet + */ + public function tag($name): TagSet + { + return $this->store()->tag($name); + } +} diff --git a/vendor/topthink/framework/src/think/Config.php b/vendor/topthink/framework/src/think/Config.php new file mode 100644 index 0000000..9162e82 --- /dev/null +++ b/vendor/topthink/framework/src/think/Config.php @@ -0,0 +1,197 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +/** + * 配置管理类 + * @package think + */ +class Config +{ + /** + * 配置参数 + * @var array + */ + protected $config = []; + + /** + * 配置文件目录 + * @var string + */ + protected $path; + + /** + * 配置文件后缀 + * @var string + */ + protected $ext; + + /** + * 构造方法 + * @access public + */ + public function __construct(string $path = null, string $ext = '.php') + { + $this->path = $path ?: ''; + $this->ext = $ext; + } + + public static function __make(App $app) + { + $path = $app->getConfigPath(); + $ext = $app->getConfigExt(); + + return new static($path, $ext); + } + + /** + * 加载配置文件(多种格式) + * @access public + * @param string $file 配置文件名 + * @param string $name 一级配置名 + * @return array + */ + public function load(string $file, string $name = ''): array + { + if (is_file($file)) { + $filename = $file; + } elseif (is_file($this->path . $file . $this->ext)) { + $filename = $this->path . $file . $this->ext; + } + + if (isset($filename)) { + return $this->parse($filename, $name); + } + + return $this->config; + } + + /** + * 解析配置文件 + * @access public + * @param string $file 配置文件名 + * @param string $name 一级配置名 + * @return array + */ + protected function parse(string $file, string $name): array + { + $type = pathinfo($file, PATHINFO_EXTENSION); + $config = []; + switch ($type) { + case 'php': + $config = include $file; + break; + case 'yml': + case 'yaml': + if (function_exists('yaml_parse_file')) { + $config = yaml_parse_file($file); + } + break; + case 'ini': + $config = parse_ini_file($file, true, INI_SCANNER_TYPED) ?: []; + break; + case 'json': + $config = json_decode(file_get_contents($file), true); + break; + } + + return is_array($config) ? $this->set($config, strtolower($name)) : []; + } + + /** + * 检测配置是否存在 + * @access public + * @param string $name 配置参数名(支持多级配置 .号分割) + * @return bool + */ + public function has(string $name): bool + { + if (false === strpos($name, '.') && !isset($this->config[strtolower($name)])) { + return false; + } + + return !is_null($this->get($name)); + } + + /** + * 获取一级配置 + * @access protected + * @param string $name 一级配置名 + * @return array + */ + protected function pull(string $name): array + { + $name = strtolower($name); + + return $this->config[$name] ?? []; + } + + /** + * 获取配置参数 为空则获取所有配置 + * @access public + * @param string $name 配置参数名(支持多级配置 .号分割) + * @param mixed $default 默认值 + * @return mixed + */ + public function get(string $name = null, $default = null) + { + // 无参数时获取所有 + if (empty($name)) { + return $this->config; + } + + if (false === strpos($name, '.')) { + return $this->pull($name); + } + + $name = explode('.', $name); + $name[0] = strtolower($name[0]); + $config = $this->config; + + // 按.拆分成多维数组进行判断 + foreach ($name as $val) { + if (isset($config[$val])) { + $config = $config[$val]; + } else { + return $default; + } + } + + return $config; + } + + /** + * 设置配置参数 name为数组则为批量设置 + * @access public + * @param array $config 配置参数 + * @param string $name 配置名 + * @return array + */ + public function set(array $config, string $name = null): array + { + if (!empty($name)) { + if (isset($this->config[$name])) { + $result = array_merge($this->config[$name], $config); + } else { + $result = $config; + } + + $this->config[$name] = $result; + } else { + $result = $this->config = array_merge($this->config, array_change_key_case($config)); + } + + return $result; + } + +} diff --git a/vendor/topthink/framework/src/think/Console.php b/vendor/topthink/framework/src/think/Console.php new file mode 100644 index 0000000..389d104 --- /dev/null +++ b/vendor/topthink/framework/src/think/Console.php @@ -0,0 +1,787 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use Closure; +use InvalidArgumentException; +use LogicException; +use think\console\Command; +use think\console\command\Clear; +use think\console\command\Help; +use think\console\command\Help as HelpCommand; +use think\console\command\Lists; +use think\console\command\make\Command as MakeCommand; +use think\console\command\make\Controller; +use think\console\command\make\Event; +use think\console\command\make\Listener; +use think\console\command\make\Middleware; +use think\console\command\make\Model; +use think\console\command\make\Service; +use think\console\command\make\Subscribe; +use think\console\command\make\Validate; +use think\console\command\optimize\Route; +use think\console\command\optimize\Schema; +use think\console\command\RouteList; +use think\console\command\RunServer; +use think\console\command\ServiceDiscover; +use think\console\command\VendorPublish; +use think\console\command\Version; +use think\console\Input; +use think\console\input\Argument as InputArgument; +use think\console\input\Definition as InputDefinition; +use think\console\input\Option as InputOption; +use think\console\Output; +use think\console\output\driver\Buffer; + +/** + * 控制台应用管理类 + */ +class Console +{ + + protected $app; + + /** @var Command[] */ + protected $commands = []; + + protected $wantHelps = false; + + protected $catchExceptions = true; + protected $autoExit = true; + protected $definition; + protected $defaultCommand = 'list'; + + protected $defaultCommands = [ + 'help' => Help::class, + 'list' => Lists::class, + 'clear' => Clear::class, + 'make:command' => MakeCommand::class, + 'make:controller' => Controller::class, + 'make:model' => Model::class, + 'make:middleware' => Middleware::class, + 'make:validate' => Validate::class, + 'make:event' => Event::class, + 'make:listener' => Listener::class, + 'make:service' => Service::class, + 'make:subscribe' => Subscribe::class, + 'optimize:route' => Route::class, + 'optimize:schema' => Schema::class, + 'run' => RunServer::class, + 'version' => Version::class, + 'route:list' => RouteList::class, + 'service:discover' => ServiceDiscover::class, + 'vendor:publish' => VendorPublish::class, + ]; + + /** + * 启动器 + * @var array + */ + protected static $startCallbacks = []; + + public function __construct(App $app) + { + $this->app = $app; + + $this->initialize(); + + $this->definition = $this->getDefaultInputDefinition(); + + //加载指令 + $this->loadCommands(); + + $this->start(); + } + + /** + * 初始化 + */ + protected function initialize() + { + if (!$this->app->initialized()) { + $this->app->initialize(); + } + $this->makeRequest(); + } + + /** + * 构造request + */ + protected function makeRequest() + { + $uri = $this->app->config->get('app.url', 'http://localhost'); + + $components = parse_url($uri); + + $server = $_SERVER; + + if (isset($components['path'])) { + $server = array_merge($server, [ + 'SCRIPT_FILENAME' => $components['path'], + 'SCRIPT_NAME' => $components['path'], + ]); + } + + if (isset($components['host'])) { + $server['SERVER_NAME'] = $components['host']; + $server['HTTP_HOST'] = $components['host']; + } + + if (isset($components['scheme'])) { + if ('https' === $components['scheme']) { + $server['HTTPS'] = 'on'; + $server['SERVER_PORT'] = 443; + } else { + unset($server['HTTPS']); + $server['SERVER_PORT'] = 80; + } + } + + if (isset($components['port'])) { + $server['SERVER_PORT'] = $components['port']; + $server['HTTP_HOST'] .= ':' . $components['port']; + } + + $server['REQUEST_URI'] = $uri; + + /** @var Request $request */ + $request = $this->app->make('request'); + + $request->withServer($server); + } + + /** + * 添加初始化器 + * @param Closure $callback + */ + public static function starting(Closure $callback): void + { + static::$startCallbacks[] = $callback; + } + + /** + * 清空启动器 + */ + public static function flushStartCallbacks(): void + { + static::$startCallbacks = []; + } + + /** + * 设置执行用户 + * @param $user + */ + public static function setUser(string $user): void + { + if (extension_loaded('posix')) { + $user = posix_getpwnam($user); + + if (!empty($user)) { + posix_setgid($user['gid']); + posix_setuid($user['uid']); + } + } + } + + /** + * 启动 + */ + protected function start(): void + { + foreach (static::$startCallbacks as $callback) { + $callback($this); + } + } + + /** + * 加载指令 + * @access protected + */ + protected function loadCommands(): void + { + $commands = $this->app->config->get('console.commands', []); + $commands = array_merge($this->defaultCommands, $commands); + + $this->addCommands($commands); + } + + /** + * @access public + * @param string $command + * @param array $parameters + * @param string $driver + * @return Output|Buffer + */ + public function call(string $command, array $parameters = [], string $driver = 'buffer') + { + array_unshift($parameters, $command); + + $input = new Input($parameters); + $output = new Output($driver); + + $this->setCatchExceptions(false); + $this->find($command)->run($input, $output); + + return $output; + } + + /** + * 执行当前的指令 + * @access public + * @return int + * @throws \Exception + * @api + */ + public function run() + { + $input = new Input(); + $output = new Output(); + + $this->configureIO($input, $output); + + try { + $exitCode = $this->doRun($input, $output); + } catch (\Exception $e) { + if (!$this->catchExceptions) { + throw $e; + } + + $output->renderException($e); + + $exitCode = $e->getCode(); + if (is_numeric($exitCode)) { + $exitCode = (int) $exitCode; + if (0 === $exitCode) { + $exitCode = 1; + } + } else { + $exitCode = 1; + } + } + + if ($this->autoExit) { + if ($exitCode > 255) { + $exitCode = 255; + } + + exit($exitCode); + } + + return $exitCode; + } + + /** + * 执行指令 + * @access public + * @param Input $input + * @param Output $output + * @return int + */ + public function doRun(Input $input, Output $output) + { + if (true === $input->hasParameterOption(['--version', '-V'])) { + $output->writeln($this->getLongVersion()); + + return 0; + } + + $name = $this->getCommandName($input); + + if (true === $input->hasParameterOption(['--help', '-h'])) { + if (!$name) { + $name = 'help'; + $input = new Input(['help']); + } else { + $this->wantHelps = true; + } + } + + if (!$name) { + $name = $this->defaultCommand; + $input = new Input([$this->defaultCommand]); + } + + $command = $this->find($name); + + return $this->doRunCommand($command, $input, $output); + } + + /** + * 设置输入参数定义 + * @access public + * @param InputDefinition $definition + */ + public function setDefinition(InputDefinition $definition): void + { + $this->definition = $definition; + } + + /** + * 获取输入参数定义 + * @access public + * @return InputDefinition The InputDefinition instance + */ + public function getDefinition(): InputDefinition + { + return $this->definition; + } + + /** + * Gets the help message. + * @access public + * @return string A help message. + */ + public function getHelp(): string + { + return $this->getLongVersion(); + } + + /** + * 是否捕获异常 + * @access public + * @param bool $boolean + * @api + */ + public function setCatchExceptions(bool $boolean): void + { + $this->catchExceptions = $boolean; + } + + /** + * 是否自动退出 + * @access public + * @param bool $boolean + * @api + */ + public function setAutoExit(bool $boolean): void + { + $this->autoExit = $boolean; + } + + /** + * 获取完整的版本号 + * @access public + * @return string + */ + public function getLongVersion(): string + { + if ($this->app->version()) { + return sprintf('version %s', $this->app->version()); + } + + return 'Console Tool'; + } + + /** + * 添加指令集 + * @access public + * @param array $commands + */ + public function addCommands(array $commands): void + { + foreach ($commands as $key => $command) { + if (is_subclass_of($command, Command::class)) { + // 注册指令 + $this->addCommand($command, is_numeric($key) ? '' : $key); + } + } + } + + /** + * 添加一个指令 + * @access public + * @param string|Command $command 指令对象或者指令类名 + * @param string $name 指令名 留空则自动获取 + * @return Command|void + */ + public function addCommand($command, string $name = '') + { + if ($name) { + $this->commands[$name] = $command; + return; + } + + if (is_string($command)) { + $command = $this->app->invokeClass($command); + } + + $command->setConsole($this); + + if (!$command->isEnabled()) { + $command->setConsole(null); + return; + } + + $command->setApp($this->app); + + if (null === $command->getDefinition()) { + throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', get_class($command))); + } + + $this->commands[$command->getName()] = $command; + + foreach ($command->getAliases() as $alias) { + $this->commands[$alias] = $command; + } + + return $command; + } + + /** + * 获取指令 + * @access public + * @param string $name 指令名称 + * @return Command + * @throws InvalidArgumentException + */ + public function getCommand(string $name): Command + { + if (!isset($this->commands[$name])) { + throw new InvalidArgumentException(sprintf('The command "%s" does not exist.', $name)); + } + + $command = $this->commands[$name]; + + if (is_string($command)) { + $command = $this->app->invokeClass($command); + /** @var Command $command */ + $command->setConsole($this); + $command->setApp($this->app); + } + + if ($this->wantHelps) { + $this->wantHelps = false; + + /** @var HelpCommand $helpCommand */ + $helpCommand = $this->getCommand('help'); + $helpCommand->setCommand($command); + + return $helpCommand; + } + + return $command; + } + + /** + * 某个指令是否存在 + * @access public + * @param string $name 指令名称 + * @return bool + */ + public function hasCommand(string $name): bool + { + return isset($this->commands[$name]); + } + + /** + * 获取所有的命名空间 + * @access public + * @return array + */ + public function getNamespaces(): array + { + $namespaces = []; + foreach ($this->commands as $key => $command) { + if (is_string($command)) { + $namespaces = array_merge($namespaces, $this->extractAllNamespaces($key)); + } else { + $namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName())); + + foreach ($command->getAliases() as $alias) { + $namespaces = array_merge($namespaces, $this->extractAllNamespaces($alias)); + } + } + } + + return array_values(array_unique(array_filter($namespaces))); + } + + /** + * 查找注册命名空间中的名称或缩写。 + * @access public + * @param string $namespace + * @return string + * @throws InvalidArgumentException + */ + public function findNamespace(string $namespace): string + { + $allNamespaces = $this->getNamespaces(); + $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { + return preg_quote($matches[1]) . '[^:]*'; + }, $namespace); + $namespaces = preg_grep('{^' . $expr . '}', $allNamespaces); + + if (empty($namespaces)) { + $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace); + + if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) { + if (1 == count($alternatives)) { + $message .= "\n\nDid you mean this?\n "; + } else { + $message .= "\n\nDid you mean one of these?\n "; + } + + $message .= implode("\n ", $alternatives); + } + + throw new InvalidArgumentException($message); + } + + $exact = in_array($namespace, $namespaces, true); + if (count($namespaces) > 1 && !$exact) { + throw new InvalidArgumentException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions(array_values($namespaces)))); + } + + return $exact ? $namespace : reset($namespaces); + } + + /** + * 查找指令 + * @access public + * @param string $name 名称或者别名 + * @return Command + * @throws InvalidArgumentException + */ + public function find(string $name): Command + { + $allCommands = array_keys($this->commands); + + $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { + return preg_quote($matches[1]) . '[^:]*'; + }, $name); + + $commands = preg_grep('{^' . $expr . '}', $allCommands); + + if (empty($commands) || count(preg_grep('{^' . $expr . '$}', $commands)) < 1) { + if (false !== $pos = strrpos($name, ':')) { + $this->findNamespace(substr($name, 0, $pos)); + } + + $message = sprintf('Command "%s" is not defined.', $name); + + if ($alternatives = $this->findAlternatives($name, $allCommands)) { + if (1 == count($alternatives)) { + $message .= "\n\nDid you mean this?\n "; + } else { + $message .= "\n\nDid you mean one of these?\n "; + } + $message .= implode("\n ", $alternatives); + } + + throw new InvalidArgumentException($message); + } + + $exact = in_array($name, $commands, true); + if (count($commands) > 1 && !$exact) { + $suggestions = $this->getAbbreviationSuggestions(array_values($commands)); + + throw new InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions)); + } + + return $this->getCommand($exact ? $name : reset($commands)); + } + + /** + * 获取所有的指令 + * @access public + * @param string $namespace 命名空间 + * @return Command[] + * @api + */ + public function all(string $namespace = null): array + { + if (null === $namespace) { + return $this->commands; + } + + $commands = []; + foreach ($this->commands as $name => $command) { + if ($this->extractNamespace($name, substr_count($namespace, ':') + 1) === $namespace) { + $commands[$name] = $command; + } + } + + return $commands; + } + + /** + * 配置基于用户的参数和选项的输入和输出实例。 + * @access protected + * @param Input $input 输入实例 + * @param Output $output 输出实例 + */ + protected function configureIO(Input $input, Output $output): void + { + if (true === $input->hasParameterOption(['--ansi'])) { + $output->setDecorated(true); + } elseif (true === $input->hasParameterOption(['--no-ansi'])) { + $output->setDecorated(false); + } + + if (true === $input->hasParameterOption(['--no-interaction', '-n'])) { + $input->setInteractive(false); + } + + if (true === $input->hasParameterOption(['--quiet', '-q'])) { + $output->setVerbosity(Output::VERBOSITY_QUIET); + } elseif ($input->hasParameterOption('-vvv') || $input->hasParameterOption('--verbose=3') || $input->getParameterOption('--verbose') === 3) { + $output->setVerbosity(Output::VERBOSITY_DEBUG); + } elseif ($input->hasParameterOption('-vv') || $input->hasParameterOption('--verbose=2') || $input->getParameterOption('--verbose') === 2) { + $output->setVerbosity(Output::VERBOSITY_VERY_VERBOSE); + } elseif ($input->hasParameterOption('-v') || $input->hasParameterOption('--verbose=1') || $input->hasParameterOption('--verbose') || $input->getParameterOption('--verbose')) { + $output->setVerbosity(Output::VERBOSITY_VERBOSE); + } + } + + /** + * 执行指令 + * @access protected + * @param Command $command 指令实例 + * @param Input $input 输入实例 + * @param Output $output 输出实例 + * @return int + * @throws \Exception + */ + protected function doRunCommand(Command $command, Input $input, Output $output) + { + return $command->run($input, $output); + } + + /** + * 获取指令的基础名称 + * @access protected + * @param Input $input + * @return string + */ + protected function getCommandName(Input $input): string + { + return $input->getFirstArgument() ?: ''; + } + + /** + * 获取默认输入定义 + * @access protected + * @return InputDefinition + */ + protected function getDefaultInputDefinition(): InputDefinition + { + return new InputDefinition([ + new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'), + new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message'), + new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this console version'), + new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'), + new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'), + new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'), + new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'), + new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'), + ]); + } + + /** + * 获取可能的建议 + * @access private + * @param array $abbrevs + * @return string + */ + private function getAbbreviationSuggestions(array $abbrevs): string + { + return sprintf('%s, %s%s', $abbrevs[0], $abbrevs[1], count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : ''); + } + + /** + * 返回命名空间部分 + * @access public + * @param string $name 指令 + * @param int $limit 部分的命名空间的最大数量 + * @return string + */ + public function extractNamespace(string $name, int $limit = 0): string + { + $parts = explode(':', $name); + array_pop($parts); + + return implode(':', 0 === $limit ? $parts : array_slice($parts, 0, $limit)); + } + + /** + * 查找可替代的建议 + * @access private + * @param string $name + * @param array|\Traversable $collection + * @return array + */ + private function findAlternatives(string $name, $collection): array + { + $threshold = 1e3; + $alternatives = []; + + $collectionParts = []; + foreach ($collection as $item) { + $collectionParts[$item] = explode(':', $item); + } + + foreach (explode(':', $name) as $i => $subname) { + foreach ($collectionParts as $collectionName => $parts) { + $exists = isset($alternatives[$collectionName]); + if (!isset($parts[$i]) && $exists) { + $alternatives[$collectionName] += $threshold; + continue; + } elseif (!isset($parts[$i])) { + continue; + } + + $lev = levenshtein($subname, $parts[$i]); + if ($lev <= strlen($subname) / 3 || '' !== $subname && false !== strpos($parts[$i], $subname)) { + $alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev; + } elseif ($exists) { + $alternatives[$collectionName] += $threshold; + } + } + } + + foreach ($collection as $item) { + $lev = levenshtein($name, $item); + if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) { + $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev; + } + } + + $alternatives = array_filter($alternatives, function ($lev) use ($threshold) { + return $lev < 2 * $threshold; + }); + asort($alternatives); + + return array_keys($alternatives); + } + + /** + * 返回所有的命名空间 + * @access private + * @param string $name + * @return array + */ + private function extractAllNamespaces(string $name): array + { + $parts = explode(':', $name, -1); + $namespaces = []; + + foreach ($parts as $part) { + if (count($namespaces)) { + $namespaces[] = end($namespaces) . ':' . $part; + } else { + $namespaces[] = $part; + } + } + + return $namespaces; + } + +} diff --git a/vendor/topthink/framework/src/think/Container.php b/vendor/topthink/framework/src/think/Container.php new file mode 100644 index 0000000..74026bb --- /dev/null +++ b/vendor/topthink/framework/src/think/Container.php @@ -0,0 +1,554 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use ArrayAccess; +use ArrayIterator; +use Closure; +use Countable; +use InvalidArgumentException; +use IteratorAggregate; +use Psr\Container\ContainerInterface; +use ReflectionClass; +use ReflectionException; +use ReflectionFunction; +use ReflectionFunctionAbstract; +use ReflectionMethod; +use think\exception\ClassNotFoundException; +use think\exception\FuncNotFoundException; +use think\helper\Str; + +/** + * 容器管理类 支持PSR-11 + */ +class Container implements ContainerInterface, ArrayAccess, IteratorAggregate, Countable +{ + /** + * 容器对象实例 + * @var Container|Closure + */ + protected static $instance; + + /** + * 容器中的对象实例 + * @var array + */ + protected $instances = []; + + /** + * 容器绑定标识 + * @var array + */ + protected $bind = []; + + /** + * 容器回调 + * @var array + */ + protected $invokeCallback = []; + + /** + * 获取当前容器的实例(单例) + * @access public + * @return static + */ + public static function getInstance() + { + if (is_null(static::$instance)) { + static::$instance = new static; + } + + if (static::$instance instanceof Closure) { + return (static::$instance)(); + } + + return static::$instance; + } + + /** + * 设置当前容器的实例 + * @access public + * @param object|Closure $instance + * @return void + */ + public static function setInstance($instance): void + { + static::$instance = $instance; + } + + /** + * 注册一个容器对象回调 + * + * @param string|Closure $abstract + * @param Closure|null $callback + * @return void + */ + public function resolving($abstract, Closure $callback = null): void + { + if ($abstract instanceof Closure) { + $this->invokeCallback['*'][] = $abstract; + return; + } + + $abstract = $this->getAlias($abstract); + + $this->invokeCallback[$abstract][] = $callback; + } + + /** + * 获取容器中的对象实例 不存在则创建 + * @access public + * @param string $abstract 类名或者标识 + * @param array|true $vars 变量 + * @param bool $newInstance 是否每次创建新的实例 + * @return object + */ + public static function pull(string $abstract, array $vars = [], bool $newInstance = false) + { + return static::getInstance()->make($abstract, $vars, $newInstance); + } + + /** + * 获取容器中的对象实例 + * @access public + * @param string $abstract 类名或者标识 + * @return object + */ + public function get($abstract) + { + if ($this->has($abstract)) { + return $this->make($abstract); + } + + throw new ClassNotFoundException('class not exists: ' . $abstract, $abstract); + } + + /** + * 绑定一个类、闭包、实例、接口实现到容器 + * @access public + * @param string|array $abstract 类标识、接口 + * @param mixed $concrete 要绑定的类、闭包或者实例 + * @return $this + */ + public function bind($abstract, $concrete = null) + { + if (is_array($abstract)) { + foreach ($abstract as $key => $val) { + $this->bind($key, $val); + } + } elseif ($concrete instanceof Closure) { + $this->bind[$abstract] = $concrete; + } elseif (is_object($concrete)) { + $this->instance($abstract, $concrete); + } else { + $abstract = $this->getAlias($abstract); + if ($abstract != $concrete) { + $this->bind[$abstract] = $concrete; + } + } + + return $this; + } + + /** + * 根据别名获取真实类名 + * @param string $abstract + * @return string + */ + public function getAlias(string $abstract): string + { + if (isset($this->bind[$abstract])) { + $bind = $this->bind[$abstract]; + + if (is_string($bind)) { + return $this->getAlias($bind); + } + } + + return $abstract; + } + + /** + * 绑定一个类实例到容器 + * @access public + * @param string $abstract 类名或者标识 + * @param object $instance 类的实例 + * @return $this + */ + public function instance(string $abstract, $instance) + { + $abstract = $this->getAlias($abstract); + + $this->instances[$abstract] = $instance; + + return $this; + } + + /** + * 判断容器中是否存在类及标识 + * @access public + * @param string $abstract 类名或者标识 + * @return bool + */ + public function bound(string $abstract): bool + { + return isset($this->bind[$abstract]) || isset($this->instances[$abstract]); + } + + /** + * 判断容器中是否存在类及标识 + * @access public + * @param string $name 类名或者标识 + * @return bool + */ + public function has($name): bool + { + return $this->bound($name); + } + + /** + * 判断容器中是否存在对象实例 + * @access public + * @param string $abstract 类名或者标识 + * @return bool + */ + public function exists(string $abstract): bool + { + $abstract = $this->getAlias($abstract); + + return isset($this->instances[$abstract]); + } + + /** + * 创建类的实例 已经存在则直接获取 + * @access public + * @param string $abstract 类名或者标识 + * @param array $vars 变量 + * @param bool $newInstance 是否每次创建新的实例 + * @return mixed + */ + public function make(string $abstract, array $vars = [], bool $newInstance = false) + { + $abstract = $this->getAlias($abstract); + + if (isset($this->instances[$abstract]) && !$newInstance) { + return $this->instances[$abstract]; + } + + if (isset($this->bind[$abstract]) && $this->bind[$abstract] instanceof Closure) { + $object = $this->invokeFunction($this->bind[$abstract], $vars); + } else { + $object = $this->invokeClass($abstract, $vars); + } + + if (!$newInstance) { + $this->instances[$abstract] = $object; + } + + return $object; + } + + /** + * 删除容器中的对象实例 + * @access public + * @param string $name 类名或者标识 + * @return void + */ + public function delete($name) + { + $name = $this->getAlias($name); + + if (isset($this->instances[$name])) { + unset($this->instances[$name]); + } + } + + /** + * 执行函数或者闭包方法 支持参数调用 + * @access public + * @param string|Closure $function 函数或者闭包 + * @param array $vars 参数 + * @return mixed + */ + public function invokeFunction($function, array $vars = []) + { + try { + $reflect = new ReflectionFunction($function); + } catch (ReflectionException $e) { + throw new FuncNotFoundException("function not exists: {$function}()", $function, $e); + } + + $args = $this->bindParams($reflect, $vars); + + return $function(...$args); + } + + /** + * 调用反射执行类的方法 支持参数绑定 + * @access public + * @param mixed $method 方法 + * @param array $vars 参数 + * @param bool $accessible 设置是否可访问 + * @return mixed + */ + public function invokeMethod($method, array $vars = [], bool $accessible = false) + { + if (is_array($method)) { + [$class, $method] = $method; + + $class = is_object($class) ? $class : $this->invokeClass($class); + } else { + // 静态方法 + [$class, $method] = explode('::', $method); + } + + try { + $reflect = new ReflectionMethod($class, $method); + } catch (ReflectionException $e) { + $class = is_object($class) ? get_class($class) : $class; + throw new FuncNotFoundException('method not exists: ' . $class . '::' . $method . '()', "{$class}::{$method}", $e); + } + + $args = $this->bindParams($reflect, $vars); + + if ($accessible) { + $reflect->setAccessible($accessible); + } + + return $reflect->invokeArgs(is_object($class) ? $class : null, $args); + } + + /** + * 调用反射执行类的方法 支持参数绑定 + * @access public + * @param object $instance 对象实例 + * @param mixed $reflect 反射类 + * @param array $vars 参数 + * @return mixed + */ + public function invokeReflectMethod($instance, $reflect, array $vars = []) + { + $args = $this->bindParams($reflect, $vars); + + return $reflect->invokeArgs($instance, $args); + } + + /** + * 调用反射执行callable 支持参数绑定 + * @access public + * @param mixed $callable + * @param array $vars 参数 + * @param bool $accessible 设置是否可访问 + * @return mixed + */ + public function invoke($callable, array $vars = [], bool $accessible = false) + { + if ($callable instanceof Closure) { + return $this->invokeFunction($callable, $vars); + } elseif (is_string($callable) && false === strpos($callable, '::')) { + return $this->invokeFunction($callable, $vars); + } else { + return $this->invokeMethod($callable, $vars, $accessible); + } + } + + /** + * 调用反射执行类的实例化 支持依赖注入 + * @access public + * @param string $class 类名 + * @param array $vars 参数 + * @return mixed + */ + public function invokeClass(string $class, array $vars = []) + { + try { + $reflect = new ReflectionClass($class); + } catch (ReflectionException $e) { + throw new ClassNotFoundException('class not exists: ' . $class, $class, $e); + } + + if ($reflect->hasMethod('__make')) { + $method = $reflect->getMethod('__make'); + if ($method->isPublic() && $method->isStatic()) { + $args = $this->bindParams($method, $vars); + $object = $method->invokeArgs(null, $args); + $this->invokeAfter($class, $object); + return $object; + } + } + + $constructor = $reflect->getConstructor(); + + $args = $constructor ? $this->bindParams($constructor, $vars) : []; + + $object = $reflect->newInstanceArgs($args); + + $this->invokeAfter($class, $object); + + return $object; + } + + /** + * 执行invokeClass回调 + * @access protected + * @param string $class 对象类名 + * @param object $object 容器对象实例 + * @return void + */ + protected function invokeAfter(string $class, $object): void + { + if (isset($this->invokeCallback['*'])) { + foreach ($this->invokeCallback['*'] as $callback) { + $callback($object, $this); + } + } + + if (isset($this->invokeCallback[$class])) { + foreach ($this->invokeCallback[$class] as $callback) { + $callback($object, $this); + } + } + } + + /** + * 绑定参数 + * @access protected + * @param ReflectionFunctionAbstract $reflect 反射类 + * @param array $vars 参数 + * @return array + */ + protected function bindParams(ReflectionFunctionAbstract $reflect, array $vars = []): array + { + if ($reflect->getNumberOfParameters() == 0) { + return []; + } + + // 判断数组类型 数字数组时按顺序绑定参数 + reset($vars); + $type = key($vars) === 0 ? 1 : 0; + $params = $reflect->getParameters(); + $args = []; + + foreach ($params as $param) { + $name = $param->getName(); + $lowerName = Str::snake($name); + $reflectionType = $param->getType(); + + if ($reflectionType && $reflectionType->isBuiltin() === false) { + $args[] = $this->getObjectParam($reflectionType->getName(), $vars); + } elseif (1 == $type && !empty($vars)) { + $args[] = array_shift($vars); + } elseif (0 == $type && array_key_exists($name, $vars)) { + $args[] = $vars[$name]; + } elseif (0 == $type && array_key_exists($lowerName, $vars)) { + $args[] = $vars[$lowerName]; + } elseif ($param->isDefaultValueAvailable()) { + $args[] = $param->getDefaultValue(); + } else { + throw new InvalidArgumentException('method param miss:' . $name); + } + } + + return $args; + } + + /** + * 创建工厂对象实例 + * @param string $name 工厂类名 + * @param string $namespace 默认命名空间 + * @param array $args + * @return mixed + * @deprecated + * @access public + */ + public static function factory(string $name, string $namespace = '', ...$args) + { + $class = false !== strpos($name, '\\') ? $name : $namespace . ucwords($name); + + return Container::getInstance()->invokeClass($class, $args); + } + + /** + * 获取对象类型的参数值 + * @access protected + * @param string $className 类名 + * @param array $vars 参数 + * @return mixed + */ + protected function getObjectParam(string $className, array &$vars) + { + $array = $vars; + $value = array_shift($array); + + if ($value instanceof $className) { + $result = $value; + array_shift($vars); + } else { + $result = $this->make($className); + } + + return $result; + } + + public function __set($name, $value) + { + $this->bind($name, $value); + } + + public function __get($name) + { + return $this->get($name); + } + + public function __isset($name): bool + { + return $this->exists($name); + } + + public function __unset($name) + { + $this->delete($name); + } + + public function offsetExists($key) + { + return $this->exists($key); + } + + public function offsetGet($key) + { + return $this->make($key); + } + + public function offsetSet($key, $value) + { + $this->bind($key, $value); + } + + public function offsetUnset($key) + { + $this->delete($key); + } + + //Countable + public function count() + { + return count($this->instances); + } + + //IteratorAggregate + public function getIterator() + { + return new ArrayIterator($this->instances); + } +} diff --git a/vendor/topthink/framework/src/think/Cookie.php b/vendor/topthink/framework/src/think/Cookie.php new file mode 100644 index 0000000..ebbfd64 --- /dev/null +++ b/vendor/topthink/framework/src/think/Cookie.php @@ -0,0 +1,230 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use DateTimeInterface; + +/** + * Cookie管理类 + * @package think + */ +class Cookie +{ + /** + * 配置参数 + * @var array + */ + protected $config = [ + // cookie 保存时间 + 'expire' => 0, + // cookie 保存路径 + 'path' => '/', + // cookie 有效域名 + 'domain' => '', + // cookie 启用安全传输 + 'secure' => false, + // httponly设置 + 'httponly' => false, + // samesite 设置,支持 'strict' 'lax' + 'samesite' => '', + ]; + + /** + * Cookie写入数据 + * @var array + */ + protected $cookie = []; + + /** + * 当前Request对象 + * @var Request + */ + protected $request; + + /** + * 构造方法 + * @access public + */ + public function __construct(Request $request, array $config = []) + { + $this->request = $request; + $this->config = array_merge($this->config, array_change_key_case($config)); + } + + public static function __make(Request $request, Config $config) + { + return new static($request, $config->get('cookie')); + } + + /** + * 获取cookie + * @access public + * @param mixed $name 数据名称 + * @param string $default 默认值 + * @return mixed + */ + public function get(string $name = '', $default = null) + { + return $this->request->cookie($name, $default); + } + + /** + * 是否存在Cookie参数 + * @access public + * @param string $name 变量名 + * @return bool + */ + public function has(string $name): bool + { + return $this->request->has($name, 'cookie'); + } + + /** + * Cookie 设置 + * + * @access public + * @param string $name cookie名称 + * @param string $value cookie值 + * @param mixed $option 可选参数 + * @return void + */ + public function set(string $name, string $value, $option = null): void + { + // 参数设置(会覆盖黙认设置) + if (!is_null($option)) { + if (is_numeric($option) || $option instanceof DateTimeInterface) { + $option = ['expire' => $option]; + } + + $config = array_merge($this->config, array_change_key_case($option)); + } else { + $config = $this->config; + } + + if ($config['expire'] instanceof DateTimeInterface) { + $expire = $config['expire']->getTimestamp(); + } else { + $expire = !empty($config['expire']) ? time() + intval($config['expire']) : 0; + } + + $this->setCookie($name, $value, $expire, $config); + } + + /** + * Cookie 保存 + * + * @access public + * @param string $name cookie名称 + * @param string $value cookie值 + * @param int $expire 有效期 + * @param array $option 可选参数 + * @return void + */ + protected function setCookie(string $name, string $value, int $expire, array $option = []): void + { + $this->cookie[$name] = [$value, $expire, $option]; + } + + /** + * 永久保存Cookie数据 + * @access public + * @param string $name cookie名称 + * @param string $value cookie值 + * @param mixed $option 可选参数 可能会是 null|integer|string + * @return void + */ + public function forever(string $name, string $value = '', $option = null): void + { + if (is_null($option) || is_numeric($option)) { + $option = []; + } + + $option['expire'] = 315360000; + + $this->set($name, $value, $option); + } + + /** + * Cookie删除 + * @access public + * @param string $name cookie名称 + * @return void + */ + public function delete(string $name): void + { + $this->setCookie($name, '', time() - 3600, $this->config); + } + + /** + * 获取cookie保存数据 + * @access public + * @return array + */ + public function getCookie(): array + { + return $this->cookie; + } + + /** + * 保存Cookie + * @access public + * @return void + */ + public function save(): void + { + foreach ($this->cookie as $name => $val) { + [$value, $expire, $option] = $val; + + $this->saveCookie( + $name, + $value, + $expire, + $option['path'], + $option['domain'], + $option['secure'] ? true : false, + $option['httponly'] ? true : false, + $option['samesite'] + ); + } + } + + /** + * 保存Cookie + * @access public + * @param string $name cookie名称 + * @param string $value cookie值 + * @param int $expire cookie过期时间 + * @param string $path 有效的服务器路径 + * @param string $domain 有效域名/子域名 + * @param bool $secure 是否仅仅通过HTTPS + * @param bool $httponly 仅可通过HTTP访问 + * @param string $samesite 防止CSRF攻击和用户追踪 + * @return void + */ + protected function saveCookie(string $name, string $value, int $expire, string $path, string $domain, bool $secure, bool $httponly, string $samesite): void + { + if (version_compare(PHP_VERSION, '7.3.0', '>=')) { + setcookie($name, $value, [ + 'expires' => $expire, + 'path' => $path, + 'domain' => $domain, + 'secure' => $secure, + 'httponly' => $httponly, + 'samesite' => $samesite, + ]); + } else { + setcookie($name, $value, $expire, $path, $domain, $secure, $httponly); + } + } + +} diff --git a/vendor/topthink/framework/src/think/Db.php b/vendor/topthink/framework/src/think/Db.php new file mode 100644 index 0000000..0048874 --- /dev/null +++ b/vendor/topthink/framework/src/think/Db.php @@ -0,0 +1,117 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +/** + * 数据库管理类 + * @package think + * @property Config $config + */ +class Db extends DbManager +{ + /** + * @param Event $event + * @param Config $config + * @param Log $log + * @param Cache $cache + * @return Db + * @codeCoverageIgnore + */ + public static function __make(Event $event, Config $config, Log $log, Cache $cache) + { + $db = new static(); + $db->setConfig($config); + $db->setEvent($event); + $db->setLog($log); + + $store = $db->getConfig('cache_store'); + $db->setCache($cache->store($store)); + $db->triggerSql(); + + return $db; + } + + /** + * 注入模型对象 + * @access public + * @return void + */ + protected function modelMaker() + { + } + + /** + * 设置配置对象 + * @access public + * @param Config $config 配置对象 + * @return void + */ + public function setConfig($config): void + { + $this->config = $config; + } + + /** + * 获取配置参数 + * @access public + * @param string $name 配置参数 + * @param mixed $default 默认值 + * @return mixed + */ + public function getConfig(string $name = '', $default = null) + { + if ('' !== $name) { + return $this->config->get('database.' . $name, $default); + } + + return $this->config->get('database', []); + } + + /** + * 设置Event对象 + * @param Event $event + */ + public function setEvent(Event $event): void + { + $this->event = $event; + } + + /** + * 注册回调方法 + * @access public + * @param string $event 事件名 + * @param callable $callback 回调方法 + * @return void + */ + public function event(string $event, callable $callback): void + { + if ($this->event) { + $this->event->listen('db.' . $event, $callback); + } + } + + /** + * 触发事件 + * @access public + * @param string $event 事件名 + * @param mixed $params 传入参数 + * @param bool $once + * @return mixed + */ + public function trigger(string $event, $params = null, bool $once = false) + { + if ($this->event) { + return $this->event->trigger('db.' . $event, $params, $once); + } + } +} diff --git a/vendor/topthink/framework/src/think/Env.php b/vendor/topthink/framework/src/think/Env.php new file mode 100644 index 0000000..4c26b33 --- /dev/null +++ b/vendor/topthink/framework/src/think/Env.php @@ -0,0 +1,181 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use ArrayAccess; + +/** + * Env管理类 + * @package think + */ +class Env implements ArrayAccess +{ + /** + * 环境变量数据 + * @var array + */ + protected $data = []; + + public function __construct() + { + $this->data = $_ENV; + } + + /** + * 读取环境变量定义文件 + * @access public + * @param string $file 环境变量定义文件 + * @return void + */ + public function load(string $file): void + { + $env = parse_ini_file($file, true) ?: []; + $this->set($env); + } + + /** + * 获取环境变量值 + * @access public + * @param string $name 环境变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get(string $name = null, $default = null) + { + if (is_null($name)) { + return $this->data; + } + + $name = strtoupper(str_replace('.', '_', $name)); + + if (isset($this->data[$name])) { + return $this->data[$name]; + } + + return $this->getEnv($name, $default); + } + + protected function getEnv(string $name, $default = null) + { + $result = getenv('PHP_' . $name); + + if (false === $result) { + return $default; + } + + if ('false' === $result) { + $result = false; + } elseif ('true' === $result) { + $result = true; + } + + if (!isset($this->data[$name])) { + $this->data[$name] = $result; + } + + return $result; + } + + /** + * 设置环境变量值 + * @access public + * @param string|array $env 环境变量 + * @param mixed $value 值 + * @return void + */ + public function set($env, $value = null): void + { + if (is_array($env)) { + $env = array_change_key_case($env, CASE_UPPER); + + foreach ($env as $key => $val) { + if (is_array($val)) { + foreach ($val as $k => $v) { + $this->data[$key . '_' . strtoupper($k)] = $v; + } + } else { + $this->data[$key] = $val; + } + } + } else { + $name = strtoupper(str_replace('.', '_', $env)); + + $this->data[$name] = $value; + } + } + + /** + * 检测是否存在环境变量 + * @access public + * @param string $name 参数名 + * @return bool + */ + public function has(string $name): bool + { + return !is_null($this->get($name)); + } + + /** + * 设置环境变量 + * @access public + * @param string $name 参数名 + * @param mixed $value 值 + */ + public function __set(string $name, $value): void + { + $this->set($name, $value); + } + + /** + * 获取环境变量 + * @access public + * @param string $name 参数名 + * @return mixed + */ + public function __get(string $name) + { + return $this->get($name); + } + + /** + * 检测是否存在环境变量 + * @access public + * @param string $name 参数名 + * @return bool + */ + public function __isset(string $name): bool + { + return $this->has($name); + } + + // ArrayAccess + public function offsetSet($name, $value): void + { + $this->set($name, $value); + } + + public function offsetExists($name): bool + { + return $this->__isset($name); + } + + public function offsetUnset($name) + { + throw new Exception('not support: unset'); + } + + public function offsetGet($name) + { + return $this->get($name); + } +} diff --git a/vendor/topthink/framework/src/think/Event.php b/vendor/topthink/framework/src/think/Event.php new file mode 100644 index 0000000..3c70aad --- /dev/null +++ b/vendor/topthink/framework/src/think/Event.php @@ -0,0 +1,272 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use ReflectionClass; +use ReflectionMethod; + +/** + * 事件管理类 + * @package think + */ +class Event +{ + /** + * 监听者 + * @var array + */ + protected $listener = []; + + /** + * 事件别名 + * @var array + */ + protected $bind = [ + 'AppInit' => event\AppInit::class, + 'HttpRun' => event\HttpRun::class, + 'HttpEnd' => event\HttpEnd::class, + 'RouteLoaded' => event\RouteLoaded::class, + 'LogWrite' => event\LogWrite::class, + 'LogRecord' => event\LogRecord::class, + ]; + + /** + * 应用对象 + * @var App + */ + protected $app; + + public function __construct(App $app) + { + $this->app = $app; + } + + /** + * 批量注册事件监听 + * @access public + * @param array $events 事件定义 + * @return $this + */ + public function listenEvents(array $events) + { + foreach ($events as $event => $listeners) { + if (isset($this->bind[$event])) { + $event = $this->bind[$event]; + } + + $this->listener[$event] = array_merge($this->listener[$event] ?? [], $listeners); + } + + return $this; + } + + /** + * 注册事件监听 + * @access public + * @param string $event 事件名称 + * @param mixed $listener 监听操作(或者类名) + * @param bool $first 是否优先执行 + * @return $this + */ + public function listen(string $event, $listener, bool $first = false) + { + if (isset($this->bind[$event])) { + $event = $this->bind[$event]; + } + + if ($first && isset($this->listener[$event])) { + array_unshift($this->listener[$event], $listener); + } else { + $this->listener[$event][] = $listener; + } + + return $this; + } + + /** + * 是否存在事件监听 + * @access public + * @param string $event 事件名称 + * @return bool + */ + public function hasListener(string $event): bool + { + if (isset($this->bind[$event])) { + $event = $this->bind[$event]; + } + + return isset($this->listener[$event]); + } + + /** + * 移除事件监听 + * @access public + * @param string $event 事件名称 + * @return void + */ + public function remove(string $event): void + { + if (isset($this->bind[$event])) { + $event = $this->bind[$event]; + } + + unset($this->listener[$event]); + } + + /** + * 指定事件别名标识 便于调用 + * @access public + * @param array $events 事件别名 + * @return $this + */ + public function bind(array $events) + { + $this->bind = array_merge($this->bind, $events); + + return $this; + } + + /** + * 注册事件订阅者 + * @access public + * @param mixed $subscriber 订阅者 + * @return $this + */ + public function subscribe($subscriber) + { + $subscribers = (array) $subscriber; + + foreach ($subscribers as $subscriber) { + if (is_string($subscriber)) { + $subscriber = $this->app->make($subscriber); + } + + if (method_exists($subscriber, 'subscribe')) { + // 手动订阅 + $subscriber->subscribe($this); + } else { + // 智能订阅 + $this->observe($subscriber); + } + } + + return $this; + } + + /** + * 自动注册事件观察者 + * @access public + * @param string|object $observer 观察者 + * @param null|string $prefix 事件名前缀 + * @return $this + */ + public function observe($observer, string $prefix = '') + { + if (is_string($observer)) { + $observer = $this->app->make($observer); + } + + $reflect = new ReflectionClass($observer); + $methods = $reflect->getMethods(ReflectionMethod::IS_PUBLIC); + + if (empty($prefix) && $reflect->hasProperty('eventPrefix')) { + $reflectProperty = $reflect->getProperty('eventPrefix'); + $reflectProperty->setAccessible(true); + $prefix = $reflectProperty->getValue($observer); + } + + foreach ($methods as $method) { + $name = $method->getName(); + if (0 === strpos($name, 'on')) { + $this->listen($prefix . substr($name, 2), [$observer, $name]); + } + } + + return $this; + } + + /** + * 触发事件 + * @access public + * @param string|object $event 事件名称 + * @param mixed $params 传入参数 + * @param bool $once 只获取一个有效返回值 + * @return mixed + */ + public function trigger($event, $params = null, bool $once = false) + { + if (is_object($event)) { + $params = $event; + $event = get_class($event); + } + + if (isset($this->bind[$event])) { + $event = $this->bind[$event]; + } + + $result = []; + $listeners = $this->listener[$event] ?? []; + + if (strpos($event, '.')) { + [$prefix, $event] = explode('.', $event, 2); + if (isset($this->listener[$prefix . '.*'])) { + $listeners = array_merge($listeners, $this->listener[$prefix . '.*']); + } + } + + $listeners = array_unique($listeners, SORT_REGULAR); + + foreach ($listeners as $key => $listener) { + $result[$key] = $this->dispatch($listener, $params); + + if (false === $result[$key] || (!is_null($result[$key]) && $once)) { + break; + } + } + + return $once ? end($result) : $result; + } + + /** + * 触发事件(只获取一个有效返回值) + * @param $event + * @param null $params + * @return mixed + */ + public function until($event, $params = null) + { + return $this->trigger($event, $params, true); + } + + /** + * 执行事件调度 + * @access protected + * @param mixed $event 事件方法 + * @param mixed $params 参数 + * @return mixed + */ + protected function dispatch($event, $params = null) + { + if (!is_string($event)) { + $call = $event; + } elseif (strpos($event, '::')) { + $call = $event; + } else { + $obj = $this->app->make($event); + $call = [$obj, 'handle']; + } + + return $this->app->invoke($call, [$params]); + } + +} diff --git a/vendor/topthink/framework/src/think/Exception.php b/vendor/topthink/framework/src/think/Exception.php new file mode 100644 index 0000000..5cf7954 --- /dev/null +++ b/vendor/topthink/framework/src/think/Exception.php @@ -0,0 +1,60 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +/** + * 异常基础类 + * @package think + */ +class Exception extends \Exception +{ + /** + * 保存异常页面显示的额外Debug数据 + * @var array + */ + protected $data = []; + + /** + * 设置异常额外的Debug数据 + * 数据将会显示为下面的格式 + * + * Exception Data + * -------------------------------------------------- + * Label 1 + * key1 value1 + * key2 value2 + * Label 2 + * key1 value1 + * key2 value2 + * + * @access protected + * @param string $label 数据分类,用于异常页面显示 + * @param array $data 需要显示的数据,必须为关联数组 + */ + final protected function setData(string $label, array $data) + { + $this->data[$label] = $data; + } + + /** + * 获取异常额外Debug数据 + * 主要用于输出到异常页面便于调试 + * @access public + * @return array 由setData设置的Debug数据 + */ + final public function getData() + { + return $this->data; + } + +} diff --git a/vendor/topthink/framework/src/think/Facade.php b/vendor/topthink/framework/src/think/Facade.php new file mode 100644 index 0000000..9a0e333 --- /dev/null +++ b/vendor/topthink/framework/src/think/Facade.php @@ -0,0 +1,98 @@ + +// +---------------------------------------------------------------------- +namespace think; + +/** + * Facade管理类 + */ +class Facade +{ + /** + * 始终创建新的对象实例 + * @var bool + */ + protected static $alwaysNewInstance; + + /** + * 创建Facade实例 + * @static + * @access protected + * @param string $class 类名或标识 + * @param array $args 变量 + * @param bool $newInstance 是否每次创建新的实例 + * @return object + */ + protected static function createFacade(string $class = '', array $args = [], bool $newInstance = false) + { + $class = $class ?: static::class; + + $facadeClass = static::getFacadeClass(); + + if ($facadeClass) { + $class = $facadeClass; + } + + if (static::$alwaysNewInstance) { + $newInstance = true; + } + + return Container::getInstance()->make($class, $args, $newInstance); + } + + /** + * 获取当前Facade对应类名 + * @access protected + * @return string + */ + protected static function getFacadeClass() + {} + + /** + * 带参数实例化当前Facade类 + * @access public + * @return object + */ + public static function instance(...$args) + { + if (__CLASS__ != static::class) { + return self::createFacade('', $args); + } + } + + /** + * 调用类的实例 + * @access public + * @param string $class 类名或者标识 + * @param array|true $args 变量 + * @param bool $newInstance 是否每次创建新的实例 + * @return object + */ + public static function make(string $class, $args = [], $newInstance = false) + { + if (__CLASS__ != static::class) { + return self::__callStatic('make', func_get_args()); + } + + if (true === $args) { + // 总是创建新的实例化对象 + $newInstance = true; + $args = []; + } + + return self::createFacade($class, $args, $newInstance); + } + + // 调用实际类的方法 + public static function __callStatic($method, $params) + { + return call_user_func_array([static::createFacade(), $method], $params); + } +} diff --git a/vendor/topthink/framework/src/think/File.php b/vendor/topthink/framework/src/think/File.php new file mode 100644 index 0000000..f7c37bd --- /dev/null +++ b/vendor/topthink/framework/src/think/File.php @@ -0,0 +1,187 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use SplFileInfo; +use think\exception\FileException; + +/** + * 文件上传类 + * @package think + */ +class File extends SplFileInfo +{ + + /** + * 文件hash规则 + * @var array + */ + protected $hash = []; + + protected $hashName; + + public function __construct(string $path, bool $checkPath = true) + { + if ($checkPath && !is_file($path)) { + throw new FileException(sprintf('The file "%s" does not exist', $path)); + } + + parent::__construct($path); + } + + /** + * 获取文件的哈希散列值 + * @access public + * @param string $type + * @return string + */ + public function hash(string $type = 'sha1'): string + { + if (!isset($this->hash[$type])) { + $this->hash[$type] = hash_file($type, $this->getPathname()); + } + + return $this->hash[$type]; + } + + /** + * 获取文件的MD5值 + * @access public + * @return string + */ + public function md5(): string + { + return $this->hash('md5'); + } + + /** + * 获取文件的SHA1值 + * @access public + * @return string + */ + public function sha1(): string + { + return $this->hash('sha1'); + } + + /** + * 获取文件类型信息 + * @access public + * @return string + */ + public function getMime(): string + { + $finfo = finfo_open(FILEINFO_MIME_TYPE); + + return finfo_file($finfo, $this->getPathname()); + } + + /** + * 移动文件 + * @access public + * @param string $directory 保存路径 + * @param string|null $name 保存的文件名 + * @return File + */ + public function move(string $directory, string $name = null): File + { + $target = $this->getTargetFile($directory, $name); + + set_error_handler(function ($type, $msg) use (&$error) { + $error = $msg; + }); + $renamed = rename($this->getPathname(), (string) $target); + restore_error_handler(); + if (!$renamed) { + throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error))); + } + + @chmod((string) $target, 0666 & ~umask()); + + return $target; + } + + /** + * 实例化一个新文件 + * @param string $directory + * @param null|string $name + * @return File + */ + protected function getTargetFile(string $directory, string $name = null): File + { + if (!is_dir($directory)) { + if (false === @mkdir($directory, 0777, true) && !is_dir($directory)) { + throw new FileException(sprintf('Unable to create the "%s" directory', $directory)); + } + } elseif (!is_writable($directory)) { + throw new FileException(sprintf('Unable to write in the "%s" directory', $directory)); + } + + $target = rtrim($directory, '/\\') . \DIRECTORY_SEPARATOR . (null === $name ? $this->getBasename() : $this->getName($name)); + + return new self($target, false); + } + + /** + * 获取文件名 + * @param string $name + * @return string + */ + protected function getName(string $name): string + { + $originalName = str_replace('\\', '/', $name); + $pos = strrpos($originalName, '/'); + $originalName = false === $pos ? $originalName : substr($originalName, $pos + 1); + + return $originalName; + } + + /** + * 文件扩展名 + * @return string + */ + public function extension(): string + { + return $this->getExtension(); + } + + /** + * 自动生成文件名 + * @access public + * @param string|\Closure $rule + * @return string + */ + public function hashName($rule = ''): string + { + if (!$this->hashName) { + if ($rule instanceof \Closure) { + $this->hashName = call_user_func_array($rule, [$this]); + } else { + switch (true) { + case in_array($rule, hash_algos()): + $hash = $this->hash($rule); + $this->hashName = substr($hash, 0, 2) . DIRECTORY_SEPARATOR . substr($hash, 2); + break; + case is_callable($rule): + $this->hashName = call_user_func($rule); + break; + default: + $this->hashName = date('Ymd') . DIRECTORY_SEPARATOR . md5((string) microtime(true)); + break; + } + } + } + + return $this->hashName . '.' . $this->extension(); + } +} diff --git a/vendor/topthink/framework/src/think/Filesystem.php b/vendor/topthink/framework/src/think/Filesystem.php new file mode 100644 index 0000000..0aee929 --- /dev/null +++ b/vendor/topthink/framework/src/think/Filesystem.php @@ -0,0 +1,89 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use InvalidArgumentException; +use think\filesystem\Driver; +use think\filesystem\driver\Local; +use think\helper\Arr; + +/** + * Class Filesystem + * @package think + * @mixin Driver + * @mixin Local + */ +class Filesystem extends Manager +{ + protected $namespace = '\\think\\filesystem\\driver\\'; + + /** + * @param null|string $name + * @return Driver + */ + public function disk(string $name = null): Driver + { + return $this->driver($name); + } + + protected function resolveType(string $name) + { + return $this->getDiskConfig($name, 'type', 'local'); + } + + protected function resolveConfig(string $name) + { + return $this->getDiskConfig($name); + } + + /** + * 获取缓存配置 + * @access public + * @param null|string $name 名称 + * @param mixed $default 默认值 + * @return mixed + */ + public function getConfig(string $name = null, $default = null) + { + if (!is_null($name)) { + return $this->app->config->get('filesystem.' . $name, $default); + } + + return $this->app->config->get('filesystem'); + } + + /** + * 获取磁盘配置 + * @param string $disk + * @param null $name + * @param null $default + * @return array + */ + public function getDiskConfig($disk, $name = null, $default = null) + { + if ($config = $this->getConfig("disks.{$disk}")) { + return Arr::get($config, $name, $default); + } + + throw new InvalidArgumentException("Disk [$disk] not found."); + } + + /** + * 默认驱动 + * @return string|null + */ + public function getDefaultDriver() + { + return $this->getConfig('default'); + } +} diff --git a/vendor/topthink/framework/src/think/Http.php b/vendor/topthink/framework/src/think/Http.php new file mode 100644 index 0000000..4e49c88 --- /dev/null +++ b/vendor/topthink/framework/src/think/Http.php @@ -0,0 +1,288 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use think\event\HttpEnd; +use think\event\HttpRun; +use think\event\RouteLoaded; +use think\exception\Handle; +use Throwable; + +/** + * Web应用管理类 + * @package think + */ +class Http +{ + + /** + * @var App + */ + protected $app; + + /** + * 应用名称 + * @var string + */ + protected $name; + + /** + * 应用路径 + * @var string + */ + protected $path; + + /** + * 路由路径 + * @var string + */ + protected $routePath; + + /** + * 是否绑定应用 + * @var bool + */ + protected $isBind = false; + + public function __construct(App $app) + { + $this->app = $app; + + $this->routePath = $this->app->getRootPath() . 'route' . DIRECTORY_SEPARATOR; + } + + /** + * 设置应用名称 + * @access public + * @param string $name 应用名称 + * @return $this + */ + public function name(string $name) + { + $this->name = $name; + return $this; + } + + /** + * 获取应用名称 + * @access public + * @return string + */ + public function getName(): string + { + return $this->name ?: ''; + } + + /** + * 设置应用目录 + * @access public + * @param string $path 应用目录 + * @return $this + */ + public function path(string $path) + { + if (substr($path, -1) != DIRECTORY_SEPARATOR) { + $path .= DIRECTORY_SEPARATOR; + } + + $this->path = $path; + return $this; + } + + /** + * 获取应用路径 + * @access public + * @return string + */ + public function getPath(): string + { + return $this->path ?: ''; + } + + /** + * 获取路由目录 + * @access public + * @return string + */ + public function getRoutePath(): string + { + return $this->routePath; + } + + /** + * 设置路由目录 + * @access public + * @param string $path 路由定义目录 + */ + public function setRoutePath(string $path): void + { + $this->routePath = $path; + } + + /** + * 设置应用绑定 + * @access public + * @param bool $bind 是否绑定 + * @return $this + */ + public function setBind(bool $bind = true) + { + $this->isBind = $bind; + return $this; + } + + /** + * 是否绑定应用 + * @access public + * @return bool + */ + public function isBind(): bool + { + return $this->isBind; + } + + /** + * 执行应用程序 + * @access public + * @param Request|null $request + * @return Response + */ + public function run(Request $request = null): Response + { + //初始化 + $this->initialize(); + + //自动创建request对象 + $request = $request ?? $this->app->make('request', [], true); + $this->app->instance('request', $request); + + try { + $response = $this->runWithRequest($request); + } catch (Throwable $e) { + $this->reportException($e); + + $response = $this->renderException($request, $e); + } + + return $response; + } + + /** + * 初始化 + */ + protected function initialize() + { + if (!$this->app->initialized()) { + $this->app->initialize(); + } + } + + /** + * 执行应用程序 + * @param Request $request + * @return mixed + */ + protected function runWithRequest(Request $request) + { + // 加载全局中间件 + $this->loadMiddleware(); + + // 监听HttpRun + $this->app->event->trigger(HttpRun::class); + + return $this->app->middleware->pipeline() + ->send($request) + ->then(function ($request) { + return $this->dispatchToRoute($request); + }); + } + + protected function dispatchToRoute($request) + { + $withRoute = $this->app->config->get('app.with_route', true) ? function () { + $this->loadRoutes(); + } : null; + + return $this->app->route->dispatch($request, $withRoute); + } + + /** + * 加载全局中间件 + */ + protected function loadMiddleware(): void + { + if (is_file($this->app->getBasePath() . 'middleware.php')) { + $this->app->middleware->import(include $this->app->getBasePath() . 'middleware.php'); + } + } + + /** + * 加载路由 + * @access protected + * @return void + */ + protected function loadRoutes(): void + { + // 加载路由定义 + $routePath = $this->getRoutePath(); + + if (is_dir($routePath)) { + $files = glob($routePath . '*.php'); + foreach ($files as $file) { + include $file; + } + } + + $this->app->event->trigger(RouteLoaded::class); + } + + /** + * Report the exception to the exception handler. + * + * @param Throwable $e + * @return void + */ + protected function reportException(Throwable $e) + { + $this->app->make(Handle::class)->report($e); + } + + /** + * Render the exception to a response. + * + * @param Request $request + * @param Throwable $e + * @return Response + */ + protected function renderException($request, Throwable $e) + { + return $this->app->make(Handle::class)->render($request, $e); + } + + /** + * HttpEnd + * @param Response $response + * @return void + */ + public function end(Response $response): void + { + $this->app->event->trigger(HttpEnd::class, $response); + + //执行中间件 + $this->app->middleware->end($response); + + // 写入日志 + $this->app->log->save(); + } + +} diff --git a/vendor/topthink/framework/src/think/Lang.php b/vendor/topthink/framework/src/think/Lang.php new file mode 100644 index 0000000..0b79b76 --- /dev/null +++ b/vendor/topthink/framework/src/think/Lang.php @@ -0,0 +1,294 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +/** + * 多语言管理类 + * @package think + */ +class Lang +{ + /** + * 配置参数 + * @var array + */ + protected $config = [ + // 默认语言 + 'default_lang' => 'zh-cn', + // 允许的语言列表 + 'allow_lang_list' => [], + // 是否使用Cookie记录 + 'use_cookie' => true, + // 扩展语言包 + 'extend_list' => [], + // 多语言cookie变量 + 'cookie_var' => 'think_lang', + // 多语言header变量 + 'header_var' => 'think-lang', + // 多语言自动侦测变量名 + 'detect_var' => 'lang', + // Accept-Language转义为对应语言包名称 + 'accept_language' => [ + 'zh-hans-cn' => 'zh-cn', + ], + // 是否支持语言分组 + 'allow_group' => false, + ]; + + /** + * 多语言信息 + * @var array + */ + private $lang = []; + + /** + * 当前语言 + * @var string + */ + private $range = 'zh-cn'; + + /** + * 构造方法 + * @access public + * @param array $config + */ + public function __construct(array $config = []) + { + $this->config = array_merge($this->config, array_change_key_case($config)); + $this->range = $this->config['default_lang']; + } + + public static function __make(Config $config) + { + return new static($config->get('lang')); + } + + /** + * 设置当前语言 + * @access public + * @param string $lang 语言 + * @return void + */ + public function setLangSet(string $lang): void + { + $this->range = $lang; + } + + /** + * 获取当前语言 + * @access public + * @return string + */ + public function getLangSet(): string + { + return $this->range; + } + + /** + * 获取默认语言 + * @access public + * @return string + */ + public function defaultLangSet() + { + return $this->config['default_lang']; + } + + /** + * 加载语言定义(不区分大小写) + * @access public + * @param string|array $file 语言文件 + * @param string $range 语言作用域 + * @return array + */ + public function load($file, $range = ''): array + { + $range = $range ?: $this->range; + if (!isset($this->lang[$range])) { + $this->lang[$range] = []; + } + + $lang = []; + + foreach ((array) $file as $name) { + if (is_file($name)) { + $result = $this->parse($name); + $lang = array_change_key_case($result) + $lang; + } + } + + if (!empty($lang)) { + $this->lang[$range] = $lang + $this->lang[$range]; + } + + return $this->lang[$range]; + } + + /** + * 解析语言文件 + * @access protected + * @param string $file 语言文件名 + * @return array + */ + protected function parse(string $file): array + { + $type = pathinfo($file, PATHINFO_EXTENSION); + + switch ($type) { + case 'php': + $result = include $file; + break; + case 'yml': + case 'yaml': + if (function_exists('yaml_parse_file')) { + $result = yaml_parse_file($file); + } + break; + case 'json': + $data = file_get_contents($file); + + if (false !== $data) { + $data = json_decode($data, true); + + if (json_last_error() === JSON_ERROR_NONE) { + $result = $data; + } + } + + break; + } + + return isset($result) && is_array($result) ? $result : []; + } + + /** + * 判断是否存在语言定义(不区分大小写) + * @access public + * @param string|null $name 语言变量 + * @param string $range 语言作用域 + * @return bool + */ + public function has(string $name, string $range = ''): bool + { + $range = $range ?: $this->range; + + if ($this->config['allow_group'] && strpos($name, '.')) { + [$name1, $name2] = explode('.', $name, 2); + return isset($this->lang[$range][strtolower($name1)][$name2]); + } + + return isset($this->lang[$range][strtolower($name)]); + } + + /** + * 获取语言定义(不区分大小写) + * @access public + * @param string|null $name 语言变量 + * @param array $vars 变量替换 + * @param string $range 语言作用域 + * @return mixed + */ + public function get(string $name = null, array $vars = [], string $range = '') + { + $range = $range ?: $this->range; + + // 空参数返回所有定义 + if (is_null($name)) { + return $this->lang[$range] ?? []; + } + + if ($this->config['allow_group'] && strpos($name, '.')) { + [$name1, $name2] = explode('.', $name, 2); + + $value = $this->lang[$range][strtolower($name1)][$name2] ?? $name; + } else { + $value = $this->lang[$range][strtolower($name)] ?? $name; + } + + // 变量解析 + if (!empty($vars) && is_array($vars)) { + /** + * Notes: + * 为了检测的方便,数字索引的判断仅仅是参数数组的第一个元素的key为数字0 + * 数字索引采用的是系统的 sprintf 函数替换,用法请参考 sprintf 函数 + */ + if (key($vars) === 0) { + // 数字索引解析 + array_unshift($vars, $value); + $value = call_user_func_array('sprintf', $vars); + } else { + // 关联索引解析 + $replace = array_keys($vars); + foreach ($replace as &$v) { + $v = "{:{$v}}"; + } + $value = str_replace($replace, $vars, $value); + } + } + + return $value; + } + + /** + * 自动侦测设置获取语言选择 + * @access public + * @param Request $request + * @return string + */ + public function detect(Request $request): string + { + // 自动侦测设置获取语言选择 + $langSet = ''; + + if ($request->get($this->config['detect_var'])) { + // url中设置了语言变量 + $langSet = strtolower($request->get($this->config['detect_var'])); + } elseif ($request->header($this->config['header_var'])) { + // Header中设置了语言变量 + $langSet = strtolower($request->header($this->config['header_var'])); + } elseif ($request->cookie($this->config['cookie_var'])) { + // Cookie中设置了语言变量 + $langSet = strtolower($request->cookie($this->config['cookie_var'])); + } elseif ($request->server('HTTP_ACCEPT_LANGUAGE')) { + // 自动侦测浏览器语言 + $match = preg_match('/^([a-z\d\-]+)/i', $request->server('HTTP_ACCEPT_LANGUAGE'), $matches); + if ($match) { + $langSet = strtolower($matches[1]); + if (isset($this->config['accept_language'][$langSet])) { + $langSet = $this->config['accept_language'][$langSet]; + } + } + } + + if (empty($this->config['allow_lang_list']) || in_array($langSet, $this->config['allow_lang_list'])) { + // 合法的语言 + $this->range = $langSet; + } + + return $this->range; + } + + /** + * 保存当前语言到Cookie + * @access public + * @param Cookie $cookie Cookie对象 + * @return void + */ + public function saveToCookie(Cookie $cookie) + { + if ($this->config['use_cookie']) { + $cookie->set($this->config['cookie_var'], $this->range); + } + } + +} diff --git a/vendor/topthink/framework/src/think/Log.php b/vendor/topthink/framework/src/think/Log.php new file mode 100644 index 0000000..c31210c --- /dev/null +++ b/vendor/topthink/framework/src/think/Log.php @@ -0,0 +1,342 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use InvalidArgumentException; +use Psr\Log\LoggerInterface; +use think\event\LogWrite; +use think\helper\Arr; +use think\log\Channel; +use think\log\ChannelSet; + +/** + * 日志管理类 + * @package think + * @mixin Channel + */ +class Log extends Manager implements LoggerInterface +{ + const EMERGENCY = 'emergency'; + const ALERT = 'alert'; + const CRITICAL = 'critical'; + const ERROR = 'error'; + const WARNING = 'warning'; + const NOTICE = 'notice'; + const INFO = 'info'; + const DEBUG = 'debug'; + const SQL = 'sql'; + + protected $namespace = '\\think\\log\\driver\\'; + + /** + * 默认驱动 + * @return string|null + */ + public function getDefaultDriver() + { + return $this->getConfig('default'); + } + + /** + * 获取日志配置 + * @access public + * @param null|string $name 名称 + * @param mixed $default 默认值 + * @return mixed + */ + public function getConfig(string $name = null, $default = null) + { + if (!is_null($name)) { + return $this->app->config->get('log.' . $name, $default); + } + + return $this->app->config->get('log'); + } + + /** + * 获取渠道配置 + * @param string $channel + * @param null $name + * @param null $default + * @return array + */ + public function getChannelConfig($channel, $name = null, $default = null) + { + if ($config = $this->getConfig("channels.{$channel}")) { + return Arr::get($config, $name, $default); + } + + throw new InvalidArgumentException("Channel [$channel] not found."); + } + + /** + * driver()的别名 + * @param string|array $name 渠道名 + * @return Channel|ChannelSet + */ + public function channel($name = null) + { + if (is_array($name)) { + return new ChannelSet($this, $name); + } + + return $this->driver($name); + } + + protected function resolveType(string $name) + { + return $this->getChannelConfig($name, 'type', 'file'); + } + + public function createDriver(string $name) + { + $driver = parent::createDriver($name); + + $lazy = !$this->getChannelConfig($name, "realtime_write", false) && !$this->app->runningInConsole(); + $allow = array_merge($this->getConfig("level", []), $this->getChannelConfig($name, "level", [])); + + return new Channel($name, $driver, $allow, $lazy, $this->app->event); + } + + protected function resolveConfig(string $name) + { + return $this->getChannelConfig($name); + } + + /** + * 清空日志信息 + * @access public + * @param string|array $channel 日志通道名 + * @return $this + */ + public function clear($channel = '*') + { + if ('*' == $channel) { + $channel = array_keys($this->drivers); + } + + $this->channel($channel)->clear(); + + return $this; + } + + /** + * 关闭本次请求日志写入 + * @access public + * @param string|array $channel 日志通道名 + * @return $this + */ + public function close($channel = '*') + { + if ('*' == $channel) { + $channel = array_keys($this->drivers); + } + + $this->channel($channel)->close(); + + return $this; + } + + /** + * 获取日志信息 + * @access public + * @param string $channel 日志通道名 + * @return array + */ + public function getLog(string $channel = null): array + { + return $this->channel($channel)->getLog(); + } + + /** + * 保存日志信息 + * @access public + * @return bool + */ + public function save(): bool + { + /** @var Channel $channel */ + foreach ($this->drivers as $channel) { + $channel->save(); + } + + return true; + } + + /** + * 记录日志信息 + * @access public + * @param mixed $msg 日志信息 + * @param string $type 日志级别 + * @param array $context 替换内容 + * @param bool $lazy + * @return $this + */ + public function record($msg, string $type = 'info', array $context = [], bool $lazy = true) + { + $channel = $this->getConfig('type_channel.' . $type); + + $this->channel($channel)->record($msg, $type, $context, $lazy); + + return $this; + } + + /** + * 实时写入日志信息 + * @access public + * @param mixed $msg 调试信息 + * @param string $type 日志级别 + * @param array $context 替换内容 + * @return $this + */ + public function write($msg, string $type = 'info', array $context = []) + { + return $this->record($msg, $type, $context, false); + } + + /** + * 注册日志写入事件监听 + * @param $listener + * @return Event + */ + public function listen($listener) + { + return $this->app->event->listen(LogWrite::class, $listener); + } + + /** + * 记录日志信息 + * @access public + * @param string $level 日志级别 + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function log($level, $message, array $context = []): void + { + $this->record($message, $level, $context); + } + + /** + * 记录emergency信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function emergency($message, array $context = []): void + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录警报信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function alert($message, array $context = []): void + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录紧急情况 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function critical($message, array $context = []): void + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录错误信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function error($message, array $context = []): void + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录warning信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function warning($message, array $context = []): void + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录notice信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function notice($message, array $context = []): void + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录一般信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function info($message, array $context = []): void + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录调试信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function debug($message, array $context = []): void + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录sql信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function sql($message, array $context = []): void + { + $this->log(__FUNCTION__, $message, $context); + } + + public function __call($method, $parameters) + { + $this->log($method, ...$parameters); + } +} diff --git a/vendor/topthink/framework/src/think/Manager.php b/vendor/topthink/framework/src/think/Manager.php new file mode 100644 index 0000000..ca3f6a5 --- /dev/null +++ b/vendor/topthink/framework/src/think/Manager.php @@ -0,0 +1,177 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use InvalidArgumentException; +use think\helper\Str; + +abstract class Manager +{ + /** @var App */ + protected $app; + + /** + * 驱动 + * @var array + */ + protected $drivers = []; + + /** + * 驱动的命名空间 + * @var string + */ + protected $namespace = null; + + public function __construct(App $app) + { + $this->app = $app; + } + + /** + * 获取驱动实例 + * @param null|string $name + * @return mixed + */ + protected function driver(string $name = null) + { + $name = $name ?: $this->getDefaultDriver(); + + if (is_null($name)) { + throw new InvalidArgumentException(sprintf( + 'Unable to resolve NULL driver for [%s].', + static::class + )); + } + + return $this->drivers[$name] = $this->getDriver($name); + } + + /** + * 获取驱动实例 + * @param string $name + * @return mixed + */ + protected function getDriver(string $name) + { + return $this->drivers[$name] ?? $this->createDriver($name); + } + + /** + * 获取驱动类型 + * @param string $name + * @return mixed + */ + protected function resolveType(string $name) + { + return $name; + } + + /** + * 获取驱动配置 + * @param string $name + * @return mixed + */ + protected function resolveConfig(string $name) + { + return $name; + } + + /** + * 获取驱动类 + * @param string $type + * @return string + */ + protected function resolveClass(string $type): string + { + if ($this->namespace || false !== strpos($type, '\\')) { + $class = false !== strpos($type, '\\') ? $type : $this->namespace . Str::studly($type); + + if (class_exists($class)) { + return $class; + } + } + + throw new InvalidArgumentException("Driver [$type] not supported."); + } + + /** + * 获取驱动参数 + * @param $name + * @return array + */ + protected function resolveParams($name): array + { + $config = $this->resolveConfig($name); + return [$config]; + } + + /** + * 创建驱动 + * + * @param string $name + * @return mixed + * + */ + protected function createDriver(string $name) + { + $type = $this->resolveType($name); + + $method = 'create' . Str::studly($type) . 'Driver'; + + $params = $this->resolveParams($name); + + if (method_exists($this, $method)) { + return $this->$method(...$params); + } + + $class = $this->resolveClass($type); + + return $this->app->invokeClass($class, $params); + } + + /** + * 移除一个驱动实例 + * + * @param array|string|null $name + * @return $this + */ + public function forgetDriver($name = null) + { + $name = $name ?? $this->getDefaultDriver(); + + foreach ((array) $name as $cacheName) { + if (isset($this->drivers[$cacheName])) { + unset($this->drivers[$cacheName]); + } + } + + return $this; + } + + /** + * 默认驱动 + * @return string|null + */ + abstract public function getDefaultDriver(); + + /** + * 动态调用 + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + return $this->driver()->$method(...$parameters); + } +} diff --git a/vendor/topthink/framework/src/think/Middleware.php b/vendor/topthink/framework/src/think/Middleware.php new file mode 100644 index 0000000..a3db0f2 --- /dev/null +++ b/vendor/topthink/framework/src/think/Middleware.php @@ -0,0 +1,257 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use Closure; +use InvalidArgumentException; +use LogicException; +use think\exception\Handle; +use Throwable; + +/** + * 中间件管理类 + * @package think + */ +class Middleware +{ + /** + * 中间件执行队列 + * @var array + */ + protected $queue = []; + + /** + * 应用对象 + * @var App + */ + protected $app; + + public function __construct(App $app) + { + $this->app = $app; + } + + /** + * 导入中间件 + * @access public + * @param array $middlewares + * @param string $type 中间件类型 + * @return void + */ + public function import(array $middlewares = [], string $type = 'global'): void + { + foreach ($middlewares as $middleware) { + $this->add($middleware, $type); + } + } + + /** + * 注册中间件 + * @access public + * @param mixed $middleware + * @param string $type 中间件类型 + * @return void + */ + public function add($middleware, string $type = 'global'): void + { + $middleware = $this->buildMiddleware($middleware, $type); + + if (!empty($middleware)) { + $this->queue[$type][] = $middleware; + $this->queue[$type] = array_unique($this->queue[$type], SORT_REGULAR); + } + } + + /** + * 注册路由中间件 + * @access public + * @param mixed $middleware + * @return void + */ + public function route($middleware): void + { + $this->add($middleware, 'route'); + } + + /** + * 注册控制器中间件 + * @access public + * @param mixed $middleware + * @return void + */ + public function controller($middleware): void + { + $this->add($middleware, 'controller'); + } + + /** + * 注册中间件到开始位置 + * @access public + * @param mixed $middleware + * @param string $type 中间件类型 + */ + public function unshift($middleware, string $type = 'global') + { + $middleware = $this->buildMiddleware($middleware, $type); + + if (!empty($middleware)) { + if (!isset($this->queue[$type])) { + $this->queue[$type] = []; + } + + array_unshift($this->queue[$type], $middleware); + } + } + + /** + * 获取注册的中间件 + * @access public + * @param string $type 中间件类型 + * @return array + */ + public function all(string $type = 'global'): array + { + return $this->queue[$type] ?? []; + } + + /** + * 调度管道 + * @access public + * @param string $type 中间件类型 + * @return Pipeline + */ + public function pipeline(string $type = 'global') + { + return (new Pipeline()) + ->through(array_map(function ($middleware) { + return function ($request, $next) use ($middleware) { + [$call, $params] = $middleware; + if (is_array($call) && is_string($call[0])) { + $call = [$this->app->make($call[0]), $call[1]]; + } + $response = call_user_func($call, $request, $next, ...$params); + + if (!$response instanceof Response) { + throw new LogicException('The middleware must return Response instance'); + } + return $response; + }; + }, $this->sortMiddleware($this->queue[$type] ?? []))) + ->whenException([$this, 'handleException']); + } + + /** + * 结束调度 + * @param Response $response + */ + public function end(Response $response) + { + foreach ($this->queue as $queue) { + foreach ($queue as $middleware) { + [$call] = $middleware; + if (is_array($call) && is_string($call[0])) { + $instance = $this->app->make($call[0]); + if (method_exists($instance, 'end')) { + $instance->end($response); + } + } + } + } + } + + /** + * 异常处理 + * @param Request $passable + * @param Throwable $e + * @return Response + */ + public function handleException($passable, Throwable $e) + { + /** @var Handle $handler */ + $handler = $this->app->make(Handle::class); + + $handler->report($e); + + return $handler->render($passable, $e); + } + + /** + * 解析中间件 + * @access protected + * @param mixed $middleware + * @param string $type 中间件类型 + * @return array + */ + protected function buildMiddleware($middleware, string $type): array + { + if (is_array($middleware)) { + [$middleware, $params] = $middleware; + } + + if ($middleware instanceof Closure) { + return [$middleware, $params ?? []]; + } + + if (!is_string($middleware)) { + throw new InvalidArgumentException('The middleware is invalid'); + } + + //中间件别名检查 + $alias = $this->app->config->get('middleware.alias', []); + + if (isset($alias[$middleware])) { + $middleware = $alias[$middleware]; + } + + if (is_array($middleware)) { + $this->import($middleware, $type); + return []; + } + + return [[$middleware, 'handle'], $params ?? []]; + } + + /** + * 中间件排序 + * @param array $middlewares + * @return array + */ + protected function sortMiddleware(array $middlewares) + { + $priority = $this->app->config->get('middleware.priority', []); + uasort($middlewares, function ($a, $b) use ($priority) { + $aPriority = $this->getMiddlewarePriority($priority, $a); + $bPriority = $this->getMiddlewarePriority($priority, $b); + return $bPriority - $aPriority; + }); + + return $middlewares; + } + + /** + * 获取中间件优先级 + * @param $priority + * @param $middleware + * @return int + */ + protected function getMiddlewarePriority($priority, $middleware) + { + [$call] = $middleware; + if (is_array($call) && is_string($call[0])) { + $index = array_search($call[0], array_reverse($priority)); + return false === $index ? -1 : $index; + } + return -1; + } + +} diff --git a/vendor/topthink/framework/src/think/Pipeline.php b/vendor/topthink/framework/src/think/Pipeline.php new file mode 100644 index 0000000..77151f3 --- /dev/null +++ b/vendor/topthink/framework/src/think/Pipeline.php @@ -0,0 +1,107 @@ + +// +---------------------------------------------------------------------- +namespace think; + +use Closure; +use Exception; +use Throwable; + +class Pipeline +{ + protected $passable; + + protected $pipes = []; + + protected $exceptionHandler; + + /** + * 初始数据 + * @param $passable + * @return $this + */ + public function send($passable) + { + $this->passable = $passable; + return $this; + } + + /** + * 调用栈 + * @param $pipes + * @return $this + */ + public function through($pipes) + { + $this->pipes = is_array($pipes) ? $pipes : func_get_args(); + return $this; + } + + /** + * 执行 + * @param Closure $destination + * @return mixed + */ + public function then(Closure $destination) + { + $pipeline = array_reduce( + array_reverse($this->pipes), + $this->carry(), + function ($passable) use ($destination) { + try { + return $destination($passable); + } catch (Throwable | Exception $e) { + return $this->handleException($passable, $e); + } + } + ); + + return $pipeline($this->passable); + } + + /** + * 设置异常处理器 + * @param callable $handler + * @return $this + */ + public function whenException($handler) + { + $this->exceptionHandler = $handler; + return $this; + } + + protected function carry() + { + return function ($stack, $pipe) { + return function ($passable) use ($stack, $pipe) { + try { + return $pipe($passable, $stack); + } catch (Throwable | Exception $e) { + return $this->handleException($passable, $e); + } + }; + }; + } + + /** + * 异常处理 + * @param $passable + * @param $e + * @return mixed + */ + protected function handleException($passable, Throwable $e) + { + if ($this->exceptionHandler) { + return call_user_func($this->exceptionHandler, $passable, $e); + } + throw $e; + } + +} diff --git a/vendor/topthink/framework/src/think/Request.php b/vendor/topthink/framework/src/think/Request.php new file mode 100644 index 0000000..1c15f63 --- /dev/null +++ b/vendor/topthink/framework/src/think/Request.php @@ -0,0 +1,2169 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use ArrayAccess; +use think\file\UploadedFile; +use think\route\Rule; + +/** + * 请求管理类 + * @package think + */ +class Request implements ArrayAccess +{ + /** + * 兼容PATH_INFO获取 + * @var array + */ + protected $pathinfoFetch = ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL']; + + /** + * PATHINFO变量名 用于兼容模式 + * @var string + */ + protected $varPathinfo = 's'; + + /** + * 请求类型 + * @var string + */ + protected $varMethod = '_method'; + + /** + * 表单ajax伪装变量 + * @var string + */ + protected $varAjax = '_ajax'; + + /** + * 表单pjax伪装变量 + * @var string + */ + protected $varPjax = '_pjax'; + + /** + * 域名根 + * @var string + */ + protected $rootDomain = ''; + + /** + * HTTPS代理标识 + * @var string + */ + protected $httpsAgentName = ''; + + /** + * 前端代理服务器IP + * @var array + */ + protected $proxyServerIp = []; + + /** + * 前端代理服务器真实IP头 + * @var array + */ + protected $proxyServerIpHeader = ['HTTP_X_REAL_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'HTTP_X_CLIENT_IP', 'HTTP_X_CLUSTER_CLIENT_IP']; + + /** + * 请求类型 + * @var string + */ + protected $method; + + /** + * 域名(含协议及端口) + * @var string + */ + protected $domain; + + /** + * HOST(含端口) + * @var string + */ + protected $host; + + /** + * 子域名 + * @var string + */ + protected $subDomain; + + /** + * 泛域名 + * @var string + */ + protected $panDomain; + + /** + * 当前URL地址 + * @var string + */ + protected $url; + + /** + * 基础URL + * @var string + */ + protected $baseUrl; + + /** + * 当前执行的文件 + * @var string + */ + protected $baseFile; + + /** + * 访问的ROOT地址 + * @var string + */ + protected $root; + + /** + * pathinfo + * @var string + */ + protected $pathinfo; + + /** + * pathinfo(不含后缀) + * @var string + */ + protected $path; + + /** + * 当前请求的IP地址 + * @var string + */ + protected $realIP; + + /** + * 当前控制器名 + * @var string + */ + protected $controller; + + /** + * 当前操作名 + * @var string + */ + protected $action; + + /** + * 当前请求参数 + * @var array + */ + protected $param = []; + + /** + * 当前GET参数 + * @var array + */ + protected $get = []; + + /** + * 当前POST参数 + * @var array + */ + protected $post = []; + + /** + * 当前REQUEST参数 + * @var array + */ + protected $request = []; + + /** + * 当前路由对象 + * @var Rule + */ + protected $rule; + + /** + * 当前ROUTE参数 + * @var array + */ + protected $route = []; + + /** + * 中间件传递的参数 + * @var array + */ + protected $middleware = []; + + /** + * 当前PUT参数 + * @var array + */ + protected $put; + + /** + * SESSION对象 + * @var Session + */ + protected $session; + + /** + * COOKIE数据 + * @var array + */ + protected $cookie = []; + + /** + * ENV对象 + * @var Env + */ + protected $env; + + /** + * 当前SERVER参数 + * @var array + */ + protected $server = []; + + /** + * 当前FILE参数 + * @var array + */ + protected $file = []; + + /** + * 当前HEADER参数 + * @var array + */ + protected $header = []; + + /** + * 资源类型定义 + * @var array + */ + protected $mimeType = [ + 'xml' => 'application/xml,text/xml,application/x-xml', + 'json' => 'application/json,text/x-json,application/jsonrequest,text/json', + 'js' => 'text/javascript,application/javascript,application/x-javascript', + 'css' => 'text/css', + 'rss' => 'application/rss+xml', + 'yaml' => 'application/x-yaml,text/yaml', + 'atom' => 'application/atom+xml', + 'pdf' => 'application/pdf', + 'text' => 'text/plain', + 'image' => 'image/png,image/jpg,image/jpeg,image/pjpeg,image/gif,image/webp,image/*', + 'csv' => 'text/csv', + 'html' => 'text/html,application/xhtml+xml,*/*', + ]; + + /** + * 当前请求内容 + * @var string + */ + protected $content; + + /** + * 全局过滤规则 + * @var array + */ + protected $filter; + + /** + * php://input内容 + * @var string + */ + // php://input + protected $input; + + /** + * 请求安全Key + * @var string + */ + protected $secureKey; + + /** + * 是否合并Param + * @var bool + */ + protected $mergeParam = false; + + /** + * 架构函数 + * @access public + */ + public function __construct() + { + // 保存 php://input + $this->input = file_get_contents('php://input'); + } + + public static function __make(App $app) + { + $request = new static(); + + if (function_exists('apache_request_headers') && $result = apache_request_headers()) { + $header = $result; + } else { + $header = []; + $server = $_SERVER; + foreach ($server as $key => $val) { + if (0 === strpos($key, 'HTTP_')) { + $key = str_replace('_', '-', strtolower(substr($key, 5))); + $header[$key] = $val; + } + } + if (isset($server['CONTENT_TYPE'])) { + $header['content-type'] = $server['CONTENT_TYPE']; + } + if (isset($server['CONTENT_LENGTH'])) { + $header['content-length'] = $server['CONTENT_LENGTH']; + } + } + + $request->header = array_change_key_case($header); + $request->server = $_SERVER; + $request->env = $app->env; + + $inputData = $request->getInputData($request->input); + + $request->get = $_GET; + $request->post = $_POST ?: $inputData; + $request->put = $inputData; + $request->request = $_REQUEST; + $request->cookie = $_COOKIE; + $request->file = $_FILES ?? []; + + return $request; + } + + /** + * 设置当前包含协议的域名 + * @access public + * @param string $domain 域名 + * @return $this + */ + public function setDomain(string $domain) + { + $this->domain = $domain; + return $this; + } + + /** + * 获取当前包含协议的域名 + * @access public + * @param bool $port 是否需要去除端口号 + * @return string + */ + public function domain(bool $port = false): string + { + return $this->scheme() . '://' . $this->host($port); + } + + /** + * 获取当前根域名 + * @access public + * @return string + */ + public function rootDomain(): string + { + $root = $this->rootDomain; + + if (!$root) { + $item = explode('.', $this->host()); + $count = count($item); + $root = $count > 1 ? $item[$count - 2] . '.' . $item[$count - 1] : $item[0]; + } + + return $root; + } + + /** + * 设置当前泛域名的值 + * @access public + * @param string $domain 域名 + * @return $this + */ + public function setSubDomain(string $domain) + { + $this->subDomain = $domain; + return $this; + } + + /** + * 获取当前子域名 + * @access public + * @return string + */ + public function subDomain(): string + { + if (is_null($this->subDomain)) { + // 获取当前主域名 + $rootDomain = $this->rootDomain(); + + if ($rootDomain) { + $sub = stristr($this->host(), $rootDomain, true); + $this->subDomain = $sub ? rtrim($sub, '.') : ''; + } else { + $this->subDomain = ''; + } + } + + return $this->subDomain; + } + + /** + * 设置当前泛域名的值 + * @access public + * @param string $domain 域名 + * @return $this + */ + public function setPanDomain(string $domain) + { + $this->panDomain = $domain; + return $this; + } + + /** + * 获取当前泛域名的值 + * @access public + * @return string + */ + public function panDomain(): string + { + return $this->panDomain ?: ''; + } + + /** + * 设置当前完整URL 包括QUERY_STRING + * @access public + * @param string $url URL地址 + * @return $this + */ + public function setUrl(string $url) + { + $this->url = $url; + return $this; + } + + /** + * 获取当前完整URL 包括QUERY_STRING + * @access public + * @param bool $complete 是否包含完整域名 + * @return string + */ + public function url(bool $complete = false): string + { + if ($this->url) { + $url = $this->url; + } elseif ($this->server('HTTP_X_REWRITE_URL')) { + $url = $this->server('HTTP_X_REWRITE_URL'); + } elseif ($this->server('REQUEST_URI')) { + $url = $this->server('REQUEST_URI'); + } elseif ($this->server('ORIG_PATH_INFO')) { + $url = $this->server('ORIG_PATH_INFO') . (!empty($this->server('QUERY_STRING')) ? '?' . $this->server('QUERY_STRING') : ''); + } elseif (isset($_SERVER['argv'][1])) { + $url = $_SERVER['argv'][1]; + } else { + $url = ''; + } + + return $complete ? $this->domain() . $url : $url; + } + + /** + * 设置当前URL 不含QUERY_STRING + * @access public + * @param string $url URL地址 + * @return $this + */ + public function setBaseUrl(string $url) + { + $this->baseUrl = $url; + return $this; + } + + /** + * 获取当前URL 不含QUERY_STRING + * @access public + * @param bool $complete 是否包含完整域名 + * @return string + */ + public function baseUrl(bool $complete = false): string + { + if (!$this->baseUrl) { + $str = $this->url(); + $this->baseUrl = strpos($str, '?') ? strstr($str, '?', true) : $str; + } + + return $complete ? $this->domain() . $this->baseUrl : $this->baseUrl; + } + + /** + * 获取当前执行的文件 SCRIPT_NAME + * @access public + * @param bool $complete 是否包含完整域名 + * @return string + */ + public function baseFile(bool $complete = false): string + { + if (!$this->baseFile) { + $url = ''; + if (!$this->isCli()) { + $script_name = basename($this->server('SCRIPT_FILENAME')); + if (basename($this->server('SCRIPT_NAME')) === $script_name) { + $url = $this->server('SCRIPT_NAME'); + } elseif (basename($this->server('PHP_SELF')) === $script_name) { + $url = $this->server('PHP_SELF'); + } elseif (basename($this->server('ORIG_SCRIPT_NAME')) === $script_name) { + $url = $this->server('ORIG_SCRIPT_NAME'); + } elseif (($pos = strpos($this->server('PHP_SELF'), '/' . $script_name)) !== false) { + $url = substr($this->server('SCRIPT_NAME'), 0, $pos) . '/' . $script_name; + } elseif ($this->server('DOCUMENT_ROOT') && strpos($this->server('SCRIPT_FILENAME'), $this->server('DOCUMENT_ROOT')) === 0) { + $url = str_replace('\\', '/', str_replace($this->server('DOCUMENT_ROOT'), '', $this->server('SCRIPT_FILENAME'))); + } + } + $this->baseFile = $url; + } + + return $complete ? $this->domain() . $this->baseFile : $this->baseFile; + } + + /** + * 设置URL访问根地址 + * @access public + * @param string $url URL地址 + * @return $this + */ + public function setRoot(string $url) + { + $this->root = $url; + return $this; + } + + /** + * 获取URL访问根地址 + * @access public + * @param bool $complete 是否包含完整域名 + * @return string + */ + public function root(bool $complete = false): string + { + if (!$this->root) { + $file = $this->baseFile(); + if ($file && 0 !== strpos($this->url(), $file)) { + $file = str_replace('\\', '/', dirname($file)); + } + $this->root = rtrim($file, '/'); + } + + return $complete ? $this->domain() . $this->root : $this->root; + } + + /** + * 获取URL访问根目录 + * @access public + * @return string + */ + public function rootUrl(): string + { + $base = $this->root(); + $root = strpos($base, '.') ? ltrim(dirname($base), DIRECTORY_SEPARATOR) : $base; + + if ('' != $root) { + $root = '/' . ltrim($root, '/'); + } + + return $root; + } + + /** + * 设置当前请求的pathinfo + * @access public + * @param string $pathinfo + * @return $this + */ + public function setPathinfo(string $pathinfo) + { + $this->pathinfo = $pathinfo; + return $this; + } + + /** + * 获取当前请求URL的pathinfo信息(含URL后缀) + * @access public + * @return string + */ + public function pathinfo(): string + { + if (is_null($this->pathinfo)) { + if (isset($_GET[$this->varPathinfo])) { + // 判断URL里面是否有兼容模式参数 + $pathinfo = $_GET[$this->varPathinfo]; + unset($_GET[$this->varPathinfo]); + unset($this->get[$this->varPathinfo]); + } elseif ($this->server('PATH_INFO')) { + $pathinfo = $this->server('PATH_INFO'); + } elseif (false !== strpos(PHP_SAPI, 'cli')) { + $pathinfo = strpos($this->server('REQUEST_URI'), '?') ? strstr($this->server('REQUEST_URI'), '?', true) : $this->server('REQUEST_URI'); + } + + // 分析PATHINFO信息 + if (!isset($pathinfo)) { + foreach ($this->pathinfoFetch as $type) { + if ($this->server($type)) { + $pathinfo = (0 === strpos($this->server($type), $this->server('SCRIPT_NAME'))) ? + substr($this->server($type), strlen($this->server('SCRIPT_NAME'))) : $this->server($type); + break; + } + } + } + + if (!empty($pathinfo)) { + unset($this->get[$pathinfo], $this->request[$pathinfo]); + } + + $this->pathinfo = empty($pathinfo) || '/' == $pathinfo ? '' : ltrim($pathinfo, '/'); + } + + return $this->pathinfo; + } + + /** + * 当前URL的访问后缀 + * @access public + * @return string + */ + public function ext(): string + { + return pathinfo($this->pathinfo(), PATHINFO_EXTENSION); + } + + /** + * 获取当前请求的时间 + * @access public + * @param bool $float 是否使用浮点类型 + * @return integer|float + */ + public function time(bool $float = false) + { + return $float ? $this->server('REQUEST_TIME_FLOAT') : $this->server('REQUEST_TIME'); + } + + /** + * 当前请求的资源类型 + * @access public + * @return string + */ + public function type(): string + { + $accept = $this->server('HTTP_ACCEPT'); + + if (empty($accept)) { + return ''; + } + + foreach ($this->mimeType as $key => $val) { + $array = explode(',', $val); + foreach ($array as $k => $v) { + if (stristr($accept, $v)) { + return $key; + } + } + } + + return ''; + } + + /** + * 设置资源类型 + * @access public + * @param string|array $type 资源类型名 + * @param string $val 资源类型 + * @return void + */ + public function mimeType($type, $val = ''): void + { + if (is_array($type)) { + $this->mimeType = array_merge($this->mimeType, $type); + } else { + $this->mimeType[$type] = $val; + } + } + + /** + * 设置请求类型 + * @access public + * @param string $method 请求类型 + * @return $this + */ + public function setMethod(string $method) + { + $this->method = strtoupper($method); + return $this; + } + + /** + * 当前的请求类型 + * @access public + * @param bool $origin 是否获取原始请求类型 + * @return string + */ + public function method(bool $origin = false): string + { + if ($origin) { + // 获取原始请求类型 + return $this->server('REQUEST_METHOD') ?: 'GET'; + } elseif (!$this->method) { + if (isset($this->post[$this->varMethod])) { + $method = strtolower($this->post[$this->varMethod]); + if (in_array($method, ['get', 'post', 'put', 'patch', 'delete'])) { + $this->method = strtoupper($method); + $this->{$method} = $this->post; + } else { + $this->method = 'POST'; + } + unset($this->post[$this->varMethod]); + } elseif ($this->server('HTTP_X_HTTP_METHOD_OVERRIDE')) { + $this->method = strtoupper($this->server('HTTP_X_HTTP_METHOD_OVERRIDE')); + } else { + $this->method = $this->server('REQUEST_METHOD') ?: 'GET'; + } + } + + return $this->method; + } + + /** + * 是否为GET请求 + * @access public + * @return bool + */ + public function isGet(): bool + { + return $this->method() == 'GET'; + } + + /** + * 是否为POST请求 + * @access public + * @return bool + */ + public function isPost(): bool + { + return $this->method() == 'POST'; + } + + /** + * 是否为PUT请求 + * @access public + * @return bool + */ + public function isPut(): bool + { + return $this->method() == 'PUT'; + } + + /** + * 是否为DELTE请求 + * @access public + * @return bool + */ + public function isDelete(): bool + { + return $this->method() == 'DELETE'; + } + + /** + * 是否为HEAD请求 + * @access public + * @return bool + */ + public function isHead(): bool + { + return $this->method() == 'HEAD'; + } + + /** + * 是否为PATCH请求 + * @access public + * @return bool + */ + public function isPatch(): bool + { + return $this->method() == 'PATCH'; + } + + /** + * 是否为OPTIONS请求 + * @access public + * @return bool + */ + public function isOptions(): bool + { + return $this->method() == 'OPTIONS'; + } + + /** + * 是否为cli + * @access public + * @return bool + */ + public function isCli(): bool + { + return PHP_SAPI == 'cli'; + } + + /** + * 是否为cgi + * @access public + * @return bool + */ + public function isCgi(): bool + { + return strpos(PHP_SAPI, 'cgi') === 0; + } + + /** + * 获取当前请求的参数 + * @access public + * @param string|array $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function param($name = '', $default = null, $filter = '') + { + if (empty($this->mergeParam)) { + $method = $this->method(true); + + // 自动获取请求变量 + switch ($method) { + case 'POST': + $vars = $this->post(false); + break; + case 'PUT': + case 'DELETE': + case 'PATCH': + $vars = $this->put(false); + break; + default: + $vars = []; + } + + // 当前请求参数和URL地址中的参数合并 + $this->param = array_merge($this->param, $this->get(false), $vars, $this->route(false)); + + $this->mergeParam = true; + } + + if (is_array($name)) { + return $this->only($name, $this->param, $filter); + } + + return $this->input($this->param, $name, $default, $filter); + } + + /** + * 获取包含文件在内的请求参数 + * @access public + * @param string|array $name 变量名 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function all($name = '', $filter = '') + { + $data = array_merge($this->param(), $this->file() ?: []); + + if (is_array($name)) { + $data = $this->only($name, $data, $filter); + } elseif ($name) { + $data = $data[$name] ?? null; + } + + return $data; + } + + /** + * 设置路由变量 + * @access public + * @param Rule $rule 路由对象 + * @return $this + */ + public function setRule(Rule $rule) + { + $this->rule = $rule; + return $this; + } + + /** + * 获取当前路由对象 + * @access public + * @return Rule|null + */ + public function rule() + { + return $this->rule; + } + + /** + * 设置路由变量 + * @access public + * @param array $route 路由变量 + * @return $this + */ + public function setRoute(array $route) + { + $this->route = array_merge($this->route, $route); + $this->mergeParam = false; + return $this; + } + + /** + * 获取路由参数 + * @access public + * @param string|array $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function route($name = '', $default = null, $filter = '') + { + if (is_array($name)) { + return $this->only($name, $this->route, $filter); + } + + return $this->input($this->route, $name, $default, $filter); + } + + /** + * 获取GET参数 + * @access public + * @param string|array $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function get($name = '', $default = null, $filter = '') + { + if (is_array($name)) { + return $this->only($name, $this->get, $filter); + } + + return $this->input($this->get, $name, $default, $filter); + } + + /** + * 获取中间件传递的参数 + * @access public + * @param mixed $name 变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function middleware($name, $default = null) + { + return $this->middleware[$name] ?? $default; + } + + /** + * 获取POST参数 + * @access public + * @param string|array $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function post($name = '', $default = null, $filter = '') + { + if (is_array($name)) { + return $this->only($name, $this->post, $filter); + } + + return $this->input($this->post, $name, $default, $filter); + } + + /** + * 获取PUT参数 + * @access public + * @param string|array $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function put($name = '', $default = null, $filter = '') + { + if (is_array($name)) { + return $this->only($name, $this->put, $filter); + } + + return $this->input($this->put, $name, $default, $filter); + } + + protected function getInputData($content): array + { + $contentType = $this->contentType(); + if ('application/x-www-form-urlencoded' == $contentType) { + parse_str($content, $data); + return $data; + } elseif (false !== strpos($contentType, 'json')) { + return (array) json_decode($content, true); + } + + return []; + } + + /** + * 设置获取DELETE参数 + * @access public + * @param mixed $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function delete($name = '', $default = null, $filter = '') + { + return $this->put($name, $default, $filter); + } + + /** + * 设置获取PATCH参数 + * @access public + * @param mixed $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function patch($name = '', $default = null, $filter = '') + { + return $this->put($name, $default, $filter); + } + + /** + * 获取request变量 + * @access public + * @param string|array $name 数据名称 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function request($name = '', $default = null, $filter = '') + { + if (is_array($name)) { + return $this->only($name, $this->request, $filter); + } + + return $this->input($this->request, $name, $default, $filter); + } + + /** + * 获取环境变量 + * @access public + * @param string $name 数据名称 + * @param string $default 默认值 + * @return mixed + */ + public function env(string $name = '', string $default = null) + { + if (empty($name)) { + return $this->env->get(); + } else { + $name = strtoupper($name); + } + + return $this->env->get($name, $default); + } + + /** + * 获取session数据 + * @access public + * @param string $name 数据名称 + * @param string $default 默认值 + * @return mixed + */ + public function session(string $name = '', $default = null) + { + if ('' === $name) { + return $this->session->all(); + } + return $this->session->get($name, $default); + } + + /** + * 获取cookie参数 + * @access public + * @param mixed $name 数据名称 + * @param string $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function cookie(string $name = '', $default = null, $filter = '') + { + if (!empty($name)) { + $data = $this->getData($this->cookie, $name, $default); + } else { + $data = $this->cookie; + } + + // 解析过滤器 + $filter = $this->getFilter($filter, $default); + + if (is_array($data)) { + array_walk_recursive($data, [$this, 'filterValue'], $filter); + } else { + $this->filterValue($data, $name, $filter); + } + + return $data; + } + + /** + * 获取server参数 + * @access public + * @param string $name 数据名称 + * @param string $default 默认值 + * @return mixed + */ + public function server(string $name = '', string $default = '') + { + if (empty($name)) { + return $this->server; + } else { + $name = strtoupper($name); + } + + return $this->server[$name] ?? $default; + } + + /** + * 获取上传的文件信息 + * @access public + * @param string $name 名称 + * @return null|array|UploadedFile + */ + public function file(string $name = '') + { + $files = $this->file; + if (!empty($files)) { + if (strpos($name, '.')) { + [$name, $sub] = explode('.', $name); + } + + // 处理上传文件 + $array = $this->dealUploadFile($files, $name); + + if ('' === $name) { + // 获取全部文件 + return $array; + } elseif (isset($sub) && isset($array[$name][$sub])) { + return $array[$name][$sub]; + } elseif (isset($array[$name])) { + return $array[$name]; + } + } + } + + protected function dealUploadFile(array $files, string $name): array + { + $array = []; + foreach ($files as $key => $file) { + if (is_array($file['name'])) { + $item = []; + $keys = array_keys($file); + $count = count($file['name']); + + for ($i = 0; $i < $count; $i++) { + if ($file['error'][$i] > 0) { + if ($name == $key) { + $this->throwUploadFileError($file['error'][$i]); + } else { + continue; + } + } + + $temp['key'] = $key; + + foreach ($keys as $_key) { + $temp[$_key] = $file[$_key][$i]; + } + + $item[] = new UploadedFile($temp['tmp_name'], $temp['name'], $temp['type'], $temp['error']); + } + + $array[$key] = $item; + } else { + if ($file instanceof File) { + $array[$key] = $file; + } else { + if ($file['error'] > 0) { + if ($key == $name) { + $this->throwUploadFileError($file['error']); + } else { + continue; + } + } + + $array[$key] = new UploadedFile($file['tmp_name'], $file['name'], $file['type'], $file['error']); + } + } + } + + return $array; + } + + protected function throwUploadFileError($error) + { + static $fileUploadErrors = [ + 1 => 'upload File size exceeds the maximum value', + 2 => 'upload File size exceeds the maximum value', + 3 => 'only the portion of file is uploaded', + 4 => 'no file to uploaded', + 6 => 'upload temp dir not found', + 7 => 'file write error', + ]; + + $msg = $fileUploadErrors[$error]; + throw new Exception($msg, $error); + } + + /** + * 设置或者获取当前的Header + * @access public + * @param string $name header名称 + * @param string $default 默认值 + * @return string|array + */ + public function header(string $name = '', string $default = null) + { + if ('' === $name) { + return $this->header; + } + + $name = str_replace('_', '-', strtolower($name)); + + return $this->header[$name] ?? $default; + } + + /** + * 获取变量 支持过滤和默认值 + * @access public + * @param array $data 数据源 + * @param string|false $name 字段名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤函数 + * @return mixed + */ + public function input(array $data = [], $name = '', $default = null, $filter = '') + { + if (false === $name) { + // 获取原始数据 + return $data; + } + + $name = (string) $name; + if ('' != $name) { + // 解析name + if (strpos($name, '/')) { + [$name, $type] = explode('/', $name); + } + + $data = $this->getData($data, $name); + + if (is_null($data)) { + return $default; + } + + if (is_object($data)) { + return $data; + } + } + + $data = $this->filterData($data, $filter, $name, $default); + + if (isset($type) && $data !== $default) { + // 强制类型转换 + $this->typeCast($data, $type); + } + + return $data; + } + + protected function filterData($data, $filter, $name, $default) + { + // 解析过滤器 + $filter = $this->getFilter($filter, $default); + + if (is_array($data)) { + array_walk_recursive($data, [$this, 'filterValue'], $filter); + } else { + $this->filterValue($data, $name, $filter); + } + + return $data; + } + + /** + * 强制类型转换 + * @access protected + * @param mixed $data + * @param string $type + * @return mixed + */ + protected function typeCast(&$data, string $type) + { + switch (strtolower($type)) { + // 数组 + case 'a': + $data = (array) $data; + break; + // 数字 + case 'd': + $data = (int) $data; + break; + // 浮点 + case 'f': + $data = (float) $data; + break; + // 布尔 + case 'b': + $data = (boolean) $data; + break; + // 字符串 + case 's': + if (is_scalar($data)) { + $data = (string) $data; + } else { + throw new \InvalidArgumentException('variable type error:' . gettype($data)); + } + break; + } + } + + /** + * 获取数据 + * @access protected + * @param array $data 数据源 + * @param string $name 字段名 + * @param mixed $default 默认值 + * @return mixed + */ + protected function getData(array $data, string $name, $default = null) + { + foreach (explode('.', $name) as $val) { + if (isset($data[$val])) { + $data = $data[$val]; + } else { + return $default; + } + } + + return $data; + } + + /** + * 设置或获取当前的过滤规则 + * @access public + * @param mixed $filter 过滤规则 + * @return mixed + */ + public function filter($filter = null) + { + if (is_null($filter)) { + return $this->filter; + } + + $this->filter = $filter; + + return $this; + } + + protected function getFilter($filter, $default): array + { + if (is_null($filter)) { + $filter = []; + } else { + $filter = $filter ?: $this->filter; + if (is_string($filter) && false === strpos($filter, '/')) { + $filter = explode(',', $filter); + } else { + $filter = (array) $filter; + } + } + + $filter[] = $default; + + return $filter; + } + + /** + * 递归过滤给定的值 + * @access public + * @param mixed $value 键值 + * @param mixed $key 键名 + * @param array $filters 过滤方法+默认值 + * @return mixed + */ + public function filterValue(&$value, $key, $filters) + { + $default = array_pop($filters); + + foreach ($filters as $filter) { + if (is_callable($filter)) { + // 调用函数或者方法过滤 + $value = call_user_func($filter, $value); + } elseif (is_scalar($value)) { + if (is_string($filter) && false !== strpos($filter, '/')) { + // 正则过滤 + if (!preg_match($filter, $value)) { + // 匹配不成功返回默认值 + $value = $default; + break; + } + } elseif (!empty($filter)) { + // filter函数不存在时, 则使用filter_var进行过滤 + // filter为非整形值时, 调用filter_id取得过滤id + $value = filter_var($value, is_int($filter) ? $filter : filter_id($filter)); + if (false === $value) { + $value = $default; + break; + } + } + } + } + + return $value; + } + + /** + * 是否存在某个请求参数 + * @access public + * @param string $name 变量名 + * @param string $type 变量类型 + * @param bool $checkEmpty 是否检测空值 + * @return bool + */ + public function has(string $name, string $type = 'param', bool $checkEmpty = false): bool + { + if (!in_array($type, ['param', 'get', 'post', 'put', 'patch', 'route', 'delete', 'cookie', 'session', 'env', 'request', 'server', 'header', 'file'])) { + return false; + } + + $param = empty($this->$type) ? $this->$type() : $this->$type; + + if (is_object($param)) { + return $param->has($name); + } + + // 按.拆分成多维数组进行判断 + foreach (explode('.', $name) as $val) { + if (isset($param[$val])) { + $param = $param[$val]; + } else { + return false; + } + } + + return ($checkEmpty && '' === $param) ? false : true; + } + + /** + * 获取指定的参数 + * @access public + * @param array $name 变量名 + * @param mixed $data 数据或者变量类型 + * @param string|array $filter 过滤方法 + * @return array + */ + public function only(array $name, $data = 'param', $filter = ''): array + { + $data = is_array($data) ? $data : $this->$data(); + + $item = []; + foreach ($name as $key => $val) { + + if (is_int($key)) { + $default = null; + $key = $val; + if (!isset($data[$key])) { + continue; + } + } else { + $default = $val; + } + + $item[$key] = $this->filterData($data[$key] ?? $default, $filter, $key, $default); + } + + return $item; + } + + /** + * 排除指定参数获取 + * @access public + * @param array $name 变量名 + * @param string $type 变量类型 + * @return mixed + */ + public function except(array $name, string $type = 'param'): array + { + $param = $this->$type(); + + foreach ($name as $key) { + if (isset($param[$key])) { + unset($param[$key]); + } + } + + return $param; + } + + /** + * 当前是否ssl + * @access public + * @return bool + */ + public function isSsl(): bool + { + if ($this->server('HTTPS') && ('1' == $this->server('HTTPS') || 'on' == strtolower($this->server('HTTPS')))) { + return true; + } elseif ('https' == $this->server('REQUEST_SCHEME')) { + return true; + } elseif ('443' == $this->server('SERVER_PORT')) { + return true; + } elseif ('https' == $this->server('HTTP_X_FORWARDED_PROTO')) { + return true; + } elseif ($this->httpsAgentName && $this->server($this->httpsAgentName)) { + return true; + } + + return false; + } + + /** + * 当前是否JSON请求 + * @access public + * @return bool + */ + public function isJson(): bool + { + $acceptType = $this->type(); + + return false !== strpos($acceptType, 'json'); + } + + /** + * 当前是否Ajax请求 + * @access public + * @param bool $ajax true 获取原始ajax请求 + * @return bool + */ + public function isAjax(bool $ajax = false): bool + { + $value = $this->server('HTTP_X_REQUESTED_WITH'); + $result = $value && 'xmlhttprequest' == strtolower($value) ? true : false; + + if (true === $ajax) { + return $result; + } + + return $this->param($this->varAjax) ? true : $result; + } + + /** + * 当前是否Pjax请求 + * @access public + * @param bool $pjax true 获取原始pjax请求 + * @return bool + */ + public function isPjax(bool $pjax = false): bool + { + $result = !empty($this->server('HTTP_X_PJAX')) ? true : false; + + if (true === $pjax) { + return $result; + } + + return $this->param($this->varPjax) ? true : $result; + } + + /** + * 获取客户端IP地址 + * @access public + * @return string + */ + public function ip(): string + { + if (!empty($this->realIP)) { + return $this->realIP; + } + + $this->realIP = $this->server('REMOTE_ADDR', ''); + + // 如果指定了前端代理服务器IP以及其会发送的IP头 + // 则尝试获取前端代理服务器发送过来的真实IP + $proxyIp = $this->proxyServerIp; + $proxyIpHeader = $this->proxyServerIpHeader; + + if (count($proxyIp) > 0 && count($proxyIpHeader) > 0) { + // 从指定的HTTP头中依次尝试获取IP地址 + // 直到获取到一个合法的IP地址 + foreach ($proxyIpHeader as $header) { + $tempIP = $this->server($header); + + if (empty($tempIP)) { + continue; + } + + $tempIP = trim(explode(',', $tempIP)[0]); + + if (!$this->isValidIP($tempIP)) { + $tempIP = null; + } else { + break; + } + } + + // tempIP不为空,说明获取到了一个IP地址 + // 这时我们检查 REMOTE_ADDR 是不是指定的前端代理服务器之一 + // 如果是的话说明该 IP头 是由前端代理服务器设置的 + // 否则则是伪装的 + if (!empty($tempIP)) { + $realIPBin = $this->ip2bin($this->realIP); + + foreach ($proxyIp as $ip) { + $serverIPElements = explode('/', $ip); + $serverIP = $serverIPElements[0]; + $serverIPPrefix = $serverIPElements[1] ?? 128; + $serverIPBin = $this->ip2bin($serverIP); + + // IP类型不符 + if (strlen($realIPBin) !== strlen($serverIPBin)) { + continue; + } + + if (strncmp($realIPBin, $serverIPBin, (int) $serverIPPrefix) === 0) { + $this->realIP = $tempIP; + break; + } + } + } + } + + if (!$this->isValidIP($this->realIP)) { + $this->realIP = '0.0.0.0'; + } + + return $this->realIP; + } + + /** + * 检测是否是合法的IP地址 + * + * @param string $ip IP地址 + * @param string $type IP地址类型 (ipv4, ipv6) + * + * @return boolean + */ + public function isValidIP(string $ip, string $type = ''): bool + { + switch (strtolower($type)) { + case 'ipv4': + $flag = FILTER_FLAG_IPV4; + break; + case 'ipv6': + $flag = FILTER_FLAG_IPV6; + break; + default: + $flag = 0; + break; + } + + return boolval(filter_var($ip, FILTER_VALIDATE_IP, $flag)); + } + + /** + * 将IP地址转换为二进制字符串 + * + * @param string $ip + * + * @return string + */ + public function ip2bin(string $ip): string + { + if ($this->isValidIP($ip, 'ipv6')) { + $IPHex = str_split(bin2hex(inet_pton($ip)), 4); + foreach ($IPHex as $key => $value) { + $IPHex[$key] = intval($value, 16); + } + $IPBin = vsprintf('%016b%016b%016b%016b%016b%016b%016b%016b', $IPHex); + } else { + $IPHex = str_split(bin2hex(inet_pton($ip)), 2); + foreach ($IPHex as $key => $value) { + $IPHex[$key] = intval($value, 16); + } + $IPBin = vsprintf('%08b%08b%08b%08b', $IPHex); + } + + return $IPBin; + } + + /** + * 检测是否使用手机访问 + * @access public + * @return bool + */ + public function isMobile(): bool + { + if ($this->server('HTTP_VIA') && stristr($this->server('HTTP_VIA'), "wap")) { + return true; + } elseif ($this->server('HTTP_ACCEPT') && strpos(strtoupper($this->server('HTTP_ACCEPT')), "VND.WAP.WML")) { + return true; + } elseif ($this->server('HTTP_X_WAP_PROFILE') || $this->server('HTTP_PROFILE')) { + return true; + } elseif ($this->server('HTTP_USER_AGENT') && preg_match('/(blackberry|configuration\/cldc|hp |hp-|htc |htc_|htc-|iemobile|kindle|midp|mmp|motorola|mobile|nokia|opera mini|opera |Googlebot-Mobile|YahooSeeker\/M1A1-R2D2|android|iphone|ipod|mobi|palm|palmos|pocket|portalmmm|ppc;|smartphone|sonyericsson|sqh|spv|symbian|treo|up.browser|up.link|vodafone|windows ce|xda |xda_)/i', $this->server('HTTP_USER_AGENT'))) { + return true; + } + + return false; + } + + /** + * 当前URL地址中的scheme参数 + * @access public + * @return string + */ + public function scheme(): string + { + return $this->isSsl() ? 'https' : 'http'; + } + + /** + * 当前请求URL地址中的query参数 + * @access public + * @return string + */ + public function query(): string + { + return $this->server('QUERY_STRING', ''); + } + + /** + * 设置当前请求的host(包含端口) + * @access public + * @param string $host 主机名(含端口) + * @return $this + */ + public function setHost(string $host) + { + $this->host = $host; + + return $this; + } + + /** + * 当前请求的host + * @access public + * @param bool $strict true 仅仅获取HOST + * @return string + */ + public function host(bool $strict = false): string + { + if ($this->host) { + $host = $this->host; + } else { + $host = strval($this->server('HTTP_X_FORWARDED_HOST') ?: $this->server('HTTP_HOST')); + } + + return true === $strict && strpos($host, ':') ? strstr($host, ':', true) : $host; + } + + /** + * 当前请求URL地址中的port参数 + * @access public + * @return int + */ + public function port(): int + { + return (int) ($this->server('HTTP_X_FORWARDED_PORT') ?: $this->server('SERVER_PORT', '')); + } + + /** + * 当前请求 SERVER_PROTOCOL + * @access public + * @return string + */ + public function protocol(): string + { + return $this->server('SERVER_PROTOCOL', ''); + } + + /** + * 当前请求 REMOTE_PORT + * @access public + * @return int + */ + public function remotePort(): int + { + return (int) $this->server('REMOTE_PORT', ''); + } + + /** + * 当前请求 HTTP_CONTENT_TYPE + * @access public + * @return string + */ + public function contentType(): string + { + $contentType = $this->header('Content-Type'); + + if ($contentType) { + if (strpos($contentType, ';')) { + [$type] = explode(';', $contentType); + } else { + $type = $contentType; + } + return trim($type); + } + + return ''; + } + + /** + * 获取当前请求的安全Key + * @access public + * @return string + */ + public function secureKey(): string + { + if (is_null($this->secureKey)) { + $this->secureKey = uniqid('', true); + } + + return $this->secureKey; + } + + /** + * 设置当前的控制器名 + * @access public + * @param string $controller 控制器名 + * @return $this + */ + public function setController(string $controller) + { + $this->controller = $controller; + return $this; + } + + /** + * 设置当前的操作名 + * @access public + * @param string $action 操作名 + * @return $this + */ + public function setAction(string $action) + { + $this->action = $action; + return $this; + } + + /** + * 获取当前的控制器名 + * @access public + * @param bool $convert 转换为小写 + * @return string + */ + public function controller(bool $convert = false): string + { + $name = $this->controller ?: ''; + return $convert ? strtolower($name) : $name; + } + + /** + * 获取当前的操作名 + * @access public + * @param bool $convert 转换为小写 + * @return string + */ + public function action(bool $convert = false): string + { + $name = $this->action ?: ''; + return $convert ? strtolower($name) : $name; + } + + /** + * 设置或者获取当前请求的content + * @access public + * @return string + */ + public function getContent(): string + { + if (is_null($this->content)) { + $this->content = $this->input; + } + + return $this->content; + } + + /** + * 获取当前请求的php://input + * @access public + * @return string + */ + public function getInput(): string + { + return $this->input; + } + + /** + * 生成请求令牌 + * @access public + * @param string $name 令牌名称 + * @param mixed $type 令牌生成方法 + * @return string + */ + public function buildToken(string $name = '__token__', $type = 'md5'): string + { + $type = is_callable($type) ? $type : 'md5'; + $token = call_user_func($type, $this->server('REQUEST_TIME_FLOAT')); + + $this->session->set($name, $token); + + return $token; + } + + /** + * 检查请求令牌 + * @access public + * @param string $token 令牌名称 + * @param array $data 表单数据 + * @return bool + */ + public function checkToken(string $token = '__token__', array $data = []): bool + { + if (in_array($this->method(), ['GET', 'HEAD', 'OPTIONS'], true)) { + return true; + } + + if (!$this->session->has($token)) { + // 令牌数据无效 + return false; + } + + // Header验证 + if ($this->header('X-CSRF-TOKEN') && $this->session->get($token) === $this->header('X-CSRF-TOKEN')) { + // 防止重复提交 + $this->session->delete($token); // 验证完成销毁session + return true; + } + + if (empty($data)) { + $data = $this->post(); + } + + // 令牌验证 + if (isset($data[$token]) && $this->session->get($token) === $data[$token]) { + // 防止重复提交 + $this->session->delete($token); // 验证完成销毁session + return true; + } + + // 开启TOKEN重置 + $this->session->delete($token); + return false; + } + + /** + * 设置在中间件传递的数据 + * @access public + * @param array $middleware 数据 + * @return $this + */ + public function withMiddleware(array $middleware) + { + $this->middleware = array_merge($this->middleware, $middleware); + return $this; + } + + /** + * 设置GET数据 + * @access public + * @param array $get 数据 + * @return $this + */ + public function withGet(array $get) + { + $this->get = $get; + return $this; + } + + /** + * 设置POST数据 + * @access public + * @param array $post 数据 + * @return $this + */ + public function withPost(array $post) + { + $this->post = $post; + return $this; + } + + /** + * 设置COOKIE数据 + * @access public + * @param array $cookie 数据 + * @return $this + */ + public function withCookie(array $cookie) + { + $this->cookie = $cookie; + return $this; + } + + /** + * 设置SESSION数据 + * @access public + * @param Session $session 数据 + * @return $this + */ + public function withSession(Session $session) + { + $this->session = $session; + return $this; + } + + /** + * 设置SERVER数据 + * @access public + * @param array $server 数据 + * @return $this + */ + public function withServer(array $server) + { + $this->server = array_change_key_case($server, CASE_UPPER); + return $this; + } + + /** + * 设置HEADER数据 + * @access public + * @param array $header 数据 + * @return $this + */ + public function withHeader(array $header) + { + $this->header = array_change_key_case($header); + return $this; + } + + /** + * 设置ENV数据 + * @access public + * @param Env $env 数据 + * @return $this + */ + public function withEnv(Env $env) + { + $this->env = $env; + return $this; + } + + /** + * 设置php://input数据 + * @access public + * @param string $input RAW数据 + * @return $this + */ + public function withInput(string $input) + { + $this->input = $input; + if (!empty($input)) { + $inputData = $this->getInputData($input); + if (!empty($inputData)) { + $this->post = $inputData; + $this->put = $inputData; + } + } + return $this; + } + + /** + * 设置文件上传数据 + * @access public + * @param array $files 上传信息 + * @return $this + */ + public function withFiles(array $files) + { + $this->file = $files; + return $this; + } + + /** + * 设置ROUTE变量 + * @access public + * @param array $route 数据 + * @return $this + */ + public function withRoute(array $route) + { + $this->route = $route; + return $this; + } + + /** + * 设置中间传递数据 + * @access public + * @param string $name 参数名 + * @param mixed $value 值 + */ + public function __set(string $name, $value) + { + $this->middleware[$name] = $value; + } + + /** + * 获取中间传递数据的值 + * @access public + * @param string $name 名称 + * @return mixed + */ + public function __get(string $name) + { + return $this->middleware($name); + } + + /** + * 检测中间传递数据的值 + * @access public + * @param string $name 名称 + * @return boolean + */ + public function __isset(string $name): bool + { + return isset($this->middleware[$name]); + } + + // ArrayAccess + public function offsetExists($name): bool + { + return $this->has($name); + } + + public function offsetGet($name) + { + return $this->param($name); + } + + public function offsetSet($name, $value) + {} + + public function offsetUnset($name) + {} + +} diff --git a/vendor/topthink/framework/src/think/Response.php b/vendor/topthink/framework/src/think/Response.php new file mode 100644 index 0000000..a8a61ff --- /dev/null +++ b/vendor/topthink/framework/src/think/Response.php @@ -0,0 +1,410 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +/** + * 响应输出基础类 + * @package think + */ +abstract class Response +{ + /** + * 原始数据 + * @var mixed + */ + protected $data; + + /** + * 当前contentType + * @var string + */ + protected $contentType = 'text/html'; + + /** + * 字符集 + * @var string + */ + protected $charset = 'utf-8'; + + /** + * 状态码 + * @var integer + */ + protected $code = 200; + + /** + * 是否允许请求缓存 + * @var bool + */ + protected $allowCache = true; + + /** + * 输出参数 + * @var array + */ + protected $options = []; + + /** + * header参数 + * @var array + */ + protected $header = []; + + /** + * 输出内容 + * @var string + */ + protected $content = null; + + /** + * Cookie对象 + * @var Cookie + */ + protected $cookie; + + /** + * Session对象 + * @var Session + */ + protected $session; + + /** + * 初始化 + * @access protected + * @param mixed $data 输出数据 + * @param int $code 状态码 + */ + protected function init($data = '', int $code = 200) + { + $this->data($data); + $this->code = $code; + + $this->contentType($this->contentType, $this->charset); + } + + /** + * 创建Response对象 + * @access public + * @param mixed $data 输出数据 + * @param string $type 输出类型 + * @param int $code 状态码 + * @return Response + */ + public static function create($data = '', string $type = 'html', int $code = 200): Response + { + $class = false !== strpos($type, '\\') ? $type : '\\think\\response\\' . ucfirst(strtolower($type)); + + return Container::getInstance()->invokeClass($class, [$data, $code]); + } + + /** + * 设置Session对象 + * @access public + * @param Session $session Session对象 + * @return $this + */ + public function setSession(Session $session) + { + $this->session = $session; + return $this; + } + + /** + * 发送数据到客户端 + * @access public + * @return void + * @throws \InvalidArgumentException + */ + public function send(): void + { + // 处理输出数据 + $data = $this->getContent(); + + if (!headers_sent() && !empty($this->header)) { + // 发送状态码 + http_response_code($this->code); + // 发送头部信息 + foreach ($this->header as $name => $val) { + header($name . (!is_null($val) ? ':' . $val : '')); + } + } + if ($this->cookie) { + $this->cookie->save(); + } + + $this->sendData($data); + + if (function_exists('fastcgi_finish_request')) { + // 提高页面响应 + fastcgi_finish_request(); + } + } + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + */ + protected function output($data) + { + return $data; + } + + /** + * 输出数据 + * @access protected + * @param string $data 要处理的数据 + * @return void + */ + protected function sendData(string $data): void + { + echo $data; + } + + /** + * 输出的参数 + * @access public + * @param mixed $options 输出参数 + * @return $this + */ + public function options(array $options = []) + { + $this->options = array_merge($this->options, $options); + + return $this; + } + + /** + * 输出数据设置 + * @access public + * @param mixed $data 输出数据 + * @return $this + */ + public function data($data) + { + $this->data = $data; + + return $this; + } + + /** + * 是否允许请求缓存 + * @access public + * @param bool $cache 允许请求缓存 + * @return $this + */ + public function allowCache(bool $cache) + { + $this->allowCache = $cache; + + return $this; + } + + /** + * 是否允许请求缓存 + * @access public + * @return bool + */ + public function isAllowCache() + { + return $this->allowCache; + } + + /** + * 设置Cookie + * @access public + * @param string $name cookie名称 + * @param string $value cookie值 + * @param mixed $option 可选参数 + * @return $this + */ + public function cookie(string $name, string $value, $option = null) + { + $this->cookie->set($name, $value, $option); + + return $this; + } + + /** + * 设置响应头 + * @access public + * @param array $header 参数 + * @return $this + */ + public function header(array $header = []) + { + $this->header = array_merge($this->header, $header); + + return $this; + } + + /** + * 设置页面输出内容 + * @access public + * @param mixed $content + * @return $this + */ + public function content($content) + { + if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable([ + $content, + '__toString', + ]) + ) { + throw new \InvalidArgumentException(sprintf('variable type error: %s', gettype($content))); + } + + $this->content = (string) $content; + + return $this; + } + + /** + * 发送HTTP状态 + * @access public + * @param integer $code 状态码 + * @return $this + */ + public function code(int $code) + { + $this->code = $code; + + return $this; + } + + /** + * LastModified + * @access public + * @param string $time + * @return $this + */ + public function lastModified(string $time) + { + $this->header['Last-Modified'] = $time; + + return $this; + } + + /** + * Expires + * @access public + * @param string $time + * @return $this + */ + public function expires(string $time) + { + $this->header['Expires'] = $time; + + return $this; + } + + /** + * ETag + * @access public + * @param string $eTag + * @return $this + */ + public function eTag(string $eTag) + { + $this->header['ETag'] = $eTag; + + return $this; + } + + /** + * 页面缓存控制 + * @access public + * @param string $cache 状态码 + * @return $this + */ + public function cacheControl(string $cache) + { + $this->header['Cache-control'] = $cache; + + return $this; + } + + /** + * 页面输出类型 + * @access public + * @param string $contentType 输出类型 + * @param string $charset 输出编码 + * @return $this + */ + public function contentType(string $contentType, string $charset = 'utf-8') + { + $this->header['Content-Type'] = $contentType . '; charset=' . $charset; + + return $this; + } + + /** + * 获取头部信息 + * @access public + * @param string $name 头部名称 + * @return mixed + */ + public function getHeader(string $name = '') + { + if (!empty($name)) { + return $this->header[$name] ?? null; + } + + return $this->header; + } + + /** + * 获取原始数据 + * @access public + * @return mixed + */ + public function getData() + { + return $this->data; + } + + /** + * 获取输出数据 + * @access public + * @return string + */ + public function getContent(): string + { + if (null == $this->content) { + $content = $this->output($this->data); + + if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable([ + $content, + '__toString', + ]) + ) { + throw new \InvalidArgumentException(sprintf('variable type error: %s', gettype($content))); + } + + $this->content = (string) $content; + } + + return $this->content; + } + + /** + * 获取状态码 + * @access public + * @return integer + */ + public function getCode(): int + { + return $this->code; + } +} diff --git a/vendor/topthink/framework/src/think/Route.php b/vendor/topthink/framework/src/think/Route.php new file mode 100644 index 0000000..a3acf85 --- /dev/null +++ b/vendor/topthink/framework/src/think/Route.php @@ -0,0 +1,926 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use Closure; +use think\exception\RouteNotFoundException; +use think\route\Dispatch; +use think\route\dispatch\Callback; +use think\route\dispatch\Url as UrlDispatch; +use think\route\Domain; +use think\route\Resource; +use think\route\Rule; +use think\route\RuleGroup; +use think\route\RuleItem; +use think\route\RuleName; +use think\route\Url as UrlBuild; + +/** + * 路由管理类 + * @package think + */ +class Route +{ + /** + * REST定义 + * @var array + */ + protected $rest = [ + 'index' => ['get', '', 'index'], + 'create' => ['get', '/create', 'create'], + 'edit' => ['get', '//edit', 'edit'], + 'read' => ['get', '/', 'read'], + 'save' => ['post', '', 'save'], + 'update' => ['put', '/', 'update'], + 'delete' => ['delete', '/', 'delete'], + ]; + + /** + * 配置参数 + * @var array + */ + protected $config = [ + // pathinfo分隔符 + 'pathinfo_depr' => '/', + // 是否开启路由延迟解析 + 'url_lazy_route' => false, + // 是否强制使用路由 + 'url_route_must' => false, + // 合并路由规则 + 'route_rule_merge' => false, + // 路由是否完全匹配 + 'route_complete_match' => false, + // 去除斜杠 + 'remove_slash' => false, + // 使用注解路由 + 'route_annotation' => false, + // 默认的路由变量规则 + 'default_route_pattern' => '[\w\.]+', + // URL伪静态后缀 + 'url_html_suffix' => 'html', + // 访问控制器层名称 + 'controller_layer' => 'controller', + // 空控制器名 + 'empty_controller' => 'Error', + // 是否使用控制器后缀 + 'controller_suffix' => false, + // 默认控制器名 + 'default_controller' => 'Index', + // 默认操作名 + 'default_action' => 'index', + // 操作方法后缀 + 'action_suffix' => '', + // 非路由变量是否使用普通参数方式(用于URL生成) + 'url_common_param' => true, + ]; + + /** + * 当前应用 + * @var App + */ + protected $app; + + /** + * 请求对象 + * @var Request + */ + protected $request; + + /** + * @var RuleName + */ + protected $ruleName; + + /** + * 当前HOST + * @var string + */ + protected $host; + + /** + * 当前分组对象 + * @var RuleGroup + */ + protected $group; + + /** + * 路由绑定 + * @var array + */ + protected $bind = []; + + /** + * 域名对象 + * @var Domain[] + */ + protected $domains = []; + + /** + * 跨域路由规则 + * @var RuleGroup + */ + protected $cross; + + /** + * 路由是否延迟解析 + * @var bool + */ + protected $lazy = false; + + /** + * 路由是否测试模式 + * @var bool + */ + protected $isTest = false; + + /** + * (分组)路由规则是否合并解析 + * @var bool + */ + protected $mergeRuleRegex = false; + + /** + * 是否去除URL最后的斜线 + * @var bool + */ + protected $removeSlash = false; + + public function __construct(App $app) + { + $this->app = $app; + $this->ruleName = new RuleName(); + $this->setDefaultDomain(); + + if (is_file($this->app->getRuntimePath() . 'route.php')) { + // 读取路由映射文件 + $this->import(include $this->app->getRuntimePath() . 'route.php'); + } + + $this->config = array_merge($this->config, $this->app->config->get('route')); + } + + protected function init() + { + if (!empty($this->config['middleware'])) { + $this->app->middleware->import($this->config['middleware'], 'route'); + } + + $this->lazy($this->config['url_lazy_route']); + $this->mergeRuleRegex = $this->config['route_rule_merge']; + $this->removeSlash = $this->config['remove_slash']; + + $this->group->removeSlash($this->removeSlash); + } + + public function config(string $name = null) + { + if (is_null($name)) { + return $this->config; + } + + return $this->config[$name] ?? null; + } + + /** + * 设置路由域名及分组(包括资源路由)是否延迟解析 + * @access public + * @param bool $lazy 路由是否延迟解析 + * @return $this + */ + public function lazy(bool $lazy = true) + { + $this->lazy = $lazy; + return $this; + } + + /** + * 设置路由为测试模式 + * @access public + * @param bool $test 路由是否测试模式 + * @return void + */ + public function setTestMode(bool $test): void + { + $this->isTest = $test; + } + + /** + * 检查路由是否为测试模式 + * @access public + * @return bool + */ + public function isTest(): bool + { + return $this->isTest; + } + + /** + * 设置路由域名及分组(包括资源路由)是否合并解析 + * @access public + * @param bool $merge 路由是否合并解析 + * @return $this + */ + public function mergeRuleRegex(bool $merge = true) + { + $this->mergeRuleRegex = $merge; + $this->group->mergeRuleRegex($merge); + + return $this; + } + + /** + * 初始化默认域名 + * @access protected + * @return void + */ + protected function setDefaultDomain(): void + { + // 注册默认域名 + $domain = new Domain($this); + + $this->domains['-'] = $domain; + + // 默认分组 + $this->group = $domain; + } + + /** + * 设置当前分组 + * @access public + * @param RuleGroup $group 域名 + * @return void + */ + public function setGroup(RuleGroup $group): void + { + $this->group = $group; + } + + /** + * 获取指定标识的路由分组 不指定则获取当前分组 + * @access public + * @param string $name 分组标识 + * @return RuleGroup + */ + public function getGroup(string $name = null) + { + return $name ? $this->ruleName->getGroup($name) : $this->group; + } + + /** + * 注册变量规则 + * @access public + * @param array $pattern 变量规则 + * @return $this + */ + public function pattern(array $pattern) + { + $this->group->pattern($pattern); + + return $this; + } + + /** + * 注册路由参数 + * @access public + * @param array $option 参数 + * @return $this + */ + public function option(array $option) + { + $this->group->option($option); + + return $this; + } + + /** + * 注册域名路由 + * @access public + * @param string|array $name 子域名 + * @param mixed $rule 路由规则 + * @return Domain + */ + public function domain($name, $rule = null): Domain + { + // 支持多个域名使用相同路由规则 + $domainName = is_array($name) ? array_shift($name) : $name; + + if (!isset($this->domains[$domainName])) { + $domain = (new Domain($this, $domainName, $rule)) + ->lazy($this->lazy) + ->removeSlash($this->removeSlash) + ->mergeRuleRegex($this->mergeRuleRegex); + + $this->domains[$domainName] = $domain; + } else { + $domain = $this->domains[$domainName]; + $domain->parseGroupRule($rule); + } + + if (is_array($name) && !empty($name)) { + foreach ($name as $item) { + $this->domains[$item] = $domainName; + } + } + + // 返回域名对象 + return $domain; + } + + /** + * 获取域名 + * @access public + * @return array + */ + public function getDomains(): array + { + return $this->domains; + } + + /** + * 获取RuleName对象 + * @access public + * @return RuleName + */ + public function getRuleName(): RuleName + { + return $this->ruleName; + } + + /** + * 设置路由绑定 + * @access public + * @param string $bind 绑定信息 + * @param string $domain 域名 + * @return $this + */ + public function bind(string $bind, string $domain = null) + { + $domain = is_null($domain) ? '-' : $domain; + + $this->bind[$domain] = $bind; + + return $this; + } + + /** + * 读取路由绑定信息 + * @access public + * @return array + */ + public function getBind(): array + { + return $this->bind; + } + + /** + * 读取路由绑定 + * @access public + * @param string $domain 域名 + * @return string|null + */ + public function getDomainBind(string $domain = null) + { + if (is_null($domain)) { + $domain = $this->host; + } elseif (false === strpos($domain, '.') && $this->request) { + $domain .= '.' . $this->request->rootDomain(); + } + + if ($this->request) { + $subDomain = $this->request->subDomain(); + + if (strpos($subDomain, '.')) { + $name = '*' . strstr($subDomain, '.'); + } + } + + if (isset($this->bind[$domain])) { + $result = $this->bind[$domain]; + } elseif (isset($name) && isset($this->bind[$name])) { + $result = $this->bind[$name]; + } elseif (!empty($subDomain) && isset($this->bind['*'])) { + $result = $this->bind['*']; + } else { + $result = null; + } + + return $result; + } + + /** + * 读取路由标识 + * @access public + * @param string $name 路由标识 + * @param string $domain 域名 + * @param string $method 请求类型 + * @return array + */ + public function getName(string $name = null, string $domain = null, string $method = '*'): array + { + return $this->ruleName->getName($name, $domain, $method); + } + + /** + * 批量导入路由标识 + * @access public + * @param array $name 路由标识 + * @return void + */ + public function import(array $name): void + { + $this->ruleName->import($name); + } + + /** + * 注册路由标识 + * @access public + * @param string $name 路由标识 + * @param RuleItem $ruleItem 路由规则 + * @param bool $first 是否优先 + * @return void + */ + public function setName(string $name, RuleItem $ruleItem, bool $first = false): void + { + $this->ruleName->setName($name, $ruleItem, $first); + } + + /** + * 保存路由规则 + * @access public + * @param string $rule 路由规则 + * @param RuleItem $ruleItem RuleItem对象 + * @return void + */ + public function setRule(string $rule, RuleItem $ruleItem = null): void + { + $this->ruleName->setRule($rule, $ruleItem); + } + + /** + * 读取路由 + * @access public + * @param string $rule 路由规则 + * @return RuleItem[] + */ + public function getRule(string $rule): array + { + return $this->ruleName->getRule($rule); + } + + /** + * 读取路由列表 + * @access public + * @return array + */ + public function getRuleList(): array + { + return $this->ruleName->getRuleList(); + } + + /** + * 清空路由规则 + * @access public + * @return void + */ + public function clear(): void + { + $this->ruleName->clear(); + + if ($this->group) { + $this->group->clear(); + } + } + + /** + * 注册路由规则 + * @access public + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @param string $method 请求类型 + * @return RuleItem + */ + public function rule(string $rule, $route = null, string $method = '*'): RuleItem + { + if ($route instanceof Response) { + // 兼容之前的路由到响应对象,感觉不需要,使用场景很少,闭包就能实现 + $route = function () use ($route) { + return $route; + }; + } + return $this->group->addRule($rule, $route, $method); + } + + /** + * 设置跨域有效路由规则 + * @access public + * @param Rule $rule 路由规则 + * @param string $method 请求类型 + * @return $this + */ + public function setCrossDomainRule(Rule $rule, string $method = '*') + { + if (!isset($this->cross)) { + $this->cross = (new RuleGroup($this))->mergeRuleRegex($this->mergeRuleRegex); + } + + $this->cross->addRuleItem($rule, $method); + + return $this; + } + + /** + * 注册路由分组 + * @access public + * @param string|\Closure $name 分组名称或者参数 + * @param mixed $route 分组路由 + * @return RuleGroup + */ + public function group($name, $route = null): RuleGroup + { + if ($name instanceof Closure) { + $route = $name; + $name = ''; + } + + return (new RuleGroup($this, $this->group, $name, $route)) + ->lazy($this->lazy) + ->removeSlash($this->removeSlash) + ->mergeRuleRegex($this->mergeRuleRegex); + } + + /** + * 注册路由 + * @access public + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @return RuleItem + */ + public function any(string $rule, $route): RuleItem + { + return $this->rule($rule, $route, '*'); + } + + /** + * 注册GET路由 + * @access public + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @return RuleItem + */ + public function get(string $rule, $route): RuleItem + { + return $this->rule($rule, $route, 'GET'); + } + + /** + * 注册POST路由 + * @access public + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @return RuleItem + */ + public function post(string $rule, $route): RuleItem + { + return $this->rule($rule, $route, 'POST'); + } + + /** + * 注册PUT路由 + * @access public + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @return RuleItem + */ + public function put(string $rule, $route): RuleItem + { + return $this->rule($rule, $route, 'PUT'); + } + + /** + * 注册DELETE路由 + * @access public + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @return RuleItem + */ + public function delete(string $rule, $route): RuleItem + { + return $this->rule($rule, $route, 'DELETE'); + } + + /** + * 注册PATCH路由 + * @access public + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @return RuleItem + */ + public function patch(string $rule, $route): RuleItem + { + return $this->rule($rule, $route, 'PATCH'); + } + + /** + * 注册OPTIONS路由 + * @access public + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @return RuleItem + */ + public function options(string $rule, $route): RuleItem + { + return $this->rule($rule, $route, 'OPTIONS'); + } + + /** + * 注册资源路由 + * @access public + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @return Resource + */ + public function resource(string $rule, string $route): Resource + { + return (new Resource($this, $this->group, $rule, $route, $this->rest)) + ->lazy($this->lazy); + } + + /** + * 注册视图路由 + * @access public + * @param string $rule 路由规则 + * @param string $template 路由模板地址 + * @param array $vars 模板变量 + * @return RuleItem + */ + public function view(string $rule, string $template = '', array $vars = []): RuleItem + { + return $this->rule($rule, function () use ($vars, $template) { + return Response::create($template, 'view')->assign($vars); + }, 'GET'); + } + + /** + * 注册重定向路由 + * @access public + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param int $status 状态码 + * @return RuleItem + */ + public function redirect(string $rule, string $route = '', int $status = 301): RuleItem + { + return $this->rule($rule, function (Request $request) use ($status, $route) { + $search = $replace = []; + $matches = $request->rule()->getVars(); + + foreach ($matches as $key => $value) { + $search[] = '<' . $key . '>'; + $replace[] = $value; + + $search[] = ':' . $key; + $replace[] = $value; + } + + $route = str_replace($search, $replace, $route); + return Response::create($route, 'redirect')->code($status); + }, '*'); + } + + /** + * rest方法定义和修改 + * @access public + * @param string|array $name 方法名称 + * @param array|bool $resource 资源 + * @return $this + */ + public function rest($name, $resource = []) + { + if (is_array($name)) { + $this->rest = $resource ? $name : array_merge($this->rest, $name); + } else { + $this->rest[$name] = $resource; + } + + return $this; + } + + /** + * 获取rest方法定义的参数 + * @access public + * @param string $name 方法名称 + * @return array|null + */ + public function getRest(string $name = null) + { + if (is_null($name)) { + return $this->rest; + } + + return $this->rest[$name] ?? null; + } + + /** + * 注册未匹配路由规则后的处理 + * @access public + * @param string|Closure $route 路由地址 + * @param string $method 请求类型 + * @return RuleItem + */ + public function miss($route, string $method = '*'): RuleItem + { + return $this->group->miss($route, $method); + } + + /** + * 路由调度 + * @param Request $request + * @param Closure|bool $withRoute + * @return Response + */ + public function dispatch(Request $request, $withRoute = true) + { + $this->request = $request; + $this->host = $this->request->host(true); + $this->init(); + + if ($withRoute) { + //加载路由 + if ($withRoute instanceof Closure) { + $withRoute(); + } + $dispatch = $this->check(); + } else { + $dispatch = $this->url($this->path()); + } + + $dispatch->init($this->app); + + return $this->app->middleware->pipeline('route') + ->send($request) + ->then(function () use ($dispatch) { + return $dispatch->run(); + }); + } + + /** + * 检测URL路由 + * @access public + * @return Dispatch|false + * @throws RouteNotFoundException + */ + public function check() + { + // 自动检测域名路由 + $url = str_replace($this->config['pathinfo_depr'], '|', $this->path()); + + $completeMatch = $this->config['route_complete_match']; + + $result = $this->checkDomain()->check($this->request, $url, $completeMatch); + + if (false === $result && !empty($this->cross)) { + // 检测跨域路由 + $result = $this->cross->check($this->request, $url, $completeMatch); + } + + if (false !== $result) { + return $result; + } elseif ($this->config['url_route_must']) { + throw new RouteNotFoundException(); + } + + return $this->url($url); + } + + /** + * 获取当前请求URL的pathinfo信息(不含URL后缀) + * @access protected + * @return string + */ + protected function path(): string + { + $suffix = $this->config['url_html_suffix']; + $pathinfo = $this->request->pathinfo(); + + if (false === $suffix) { + // 禁止伪静态访问 + $path = $pathinfo; + } elseif ($suffix) { + // 去除正常的URL后缀 + $path = preg_replace('/\.(' . ltrim($suffix, '.') . ')$/i', '', $pathinfo); + } else { + // 允许任何后缀访问 + $path = preg_replace('/\.' . $this->request->ext() . '$/i', '', $pathinfo); + } + + return $path; + } + + /** + * 默认URL解析 + * @access public + * @param string $url URL地址 + * @return Dispatch + */ + public function url(string $url): Dispatch + { + if ($this->request->method() == 'OPTIONS') { + // 自动响应options请求 + return new Callback($this->request, $this->group, function () { + return Response::create('', 'html', 204)->header(['Allow' => 'GET, POST, PUT, DELETE']); + }); + } + + return new UrlDispatch($this->request, $this->group, $url); + } + + /** + * 检测域名的路由规则 + * @access protected + * @return Domain + */ + protected function checkDomain(): Domain + { + $item = false; + + if (count($this->domains) > 1) { + // 获取当前子域名 + $subDomain = $this->request->subDomain(); + + $domain = $subDomain ? explode('.', $subDomain) : []; + $domain2 = $domain ? array_pop($domain) : ''; + + if ($domain) { + // 存在三级域名 + $domain3 = array_pop($domain); + } + + if (isset($this->domains[$this->host])) { + // 子域名配置 + $item = $this->domains[$this->host]; + } elseif (isset($this->domains[$subDomain])) { + $item = $this->domains[$subDomain]; + } elseif (isset($this->domains['*.' . $domain2]) && !empty($domain3)) { + // 泛三级域名 + $item = $this->domains['*.' . $domain2]; + $panDomain = $domain3; + } elseif (isset($this->domains['*']) && !empty($domain2)) { + // 泛二级域名 + if ('www' != $domain2) { + $item = $this->domains['*']; + $panDomain = $domain2; + } + } + + if (isset($panDomain)) { + // 保存当前泛域名 + $this->request->setPanDomain($panDomain); + } + } + + if (false === $item) { + // 检测全局域名规则 + $item = $this->domains['-']; + } + + if (is_string($item)) { + $item = $this->domains[$item]; + } + + return $item; + } + + /** + * URL生成 支持路由反射 + * @access public + * @param string $url 路由地址 + * @param array $vars 参数 ['a'=>'val1', 'b'=>'val2'] + * @return UrlBuild + */ + public function buildUrl(string $url = '', array $vars = []): UrlBuild + { + return $this->app->make(UrlBuild::class, [$this, $this->app, $url, $vars], true); + } + + /** + * 设置全局的路由分组参数 + * @access public + * @param string $method 方法名 + * @param array $args 调用参数 + * @return RuleGroup + */ + public function __call($method, $args) + { + return call_user_func_array([$this->group, $method], $args); + } +} diff --git a/vendor/topthink/framework/src/think/Service.php b/vendor/topthink/framework/src/think/Service.php new file mode 100644 index 0000000..d9e8960 --- /dev/null +++ b/vendor/topthink/framework/src/think/Service.php @@ -0,0 +1,66 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use Closure; +use think\event\RouteLoaded; + +/** + * 系统服务基础类 + * @method void register() + * @method void boot() + */ +abstract class Service +{ + protected $app; + + public function __construct(App $app) + { + $this->app = $app; + } + + /** + * 加载路由 + * @access protected + * @param string $path 路由路径 + */ + protected function loadRoutesFrom($path) + { + $this->registerRoutes(function () use ($path) { + include $path; + }); + } + + /** + * 注册路由 + * @param Closure $closure + */ + protected function registerRoutes(Closure $closure) + { + $this->app->event->listen(RouteLoaded::class, $closure); + } + + /** + * 添加指令 + * @access protected + * @param array|string $commands 指令 + */ + protected function commands($commands) + { + $commands = is_array($commands) ? $commands : func_get_args(); + + Console::starting(function (Console $console) use ($commands) { + $console->addCommands($commands); + }); + } +} diff --git a/vendor/topthink/framework/src/think/Session.php b/vendor/topthink/framework/src/think/Session.php new file mode 100644 index 0000000..6c84faf --- /dev/null +++ b/vendor/topthink/framework/src/think/Session.php @@ -0,0 +1,65 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use think\helper\Arr; +use think\session\Store; + +/** + * Session管理类 + * @package think + * @mixin Store + */ +class Session extends Manager +{ + protected $namespace = '\\think\\session\\driver\\'; + + protected function createDriver(string $name) + { + $handler = parent::createDriver($name); + + return new Store($this->getConfig('name') ?: 'PHPSESSID', $handler, $this->getConfig('serialize')); + } + + /** + * 获取Session配置 + * @access public + * @param null|string $name 名称 + * @param mixed $default 默认值 + * @return mixed + */ + public function getConfig(string $name = null, $default = null) + { + if (!is_null($name)) { + return $this->app->config->get('session.' . $name, $default); + } + + return $this->app->config->get('session'); + } + + protected function resolveConfig(string $name) + { + $config = $this->app->config->get('session', []); + Arr::forget($config, 'type'); + return $config; + } + + /** + * 默认驱动 + * @return string|null + */ + public function getDefaultDriver() + { + return $this->app->config->get('session.type', 'file'); + } +} diff --git a/vendor/topthink/framework/src/think/Validate.php b/vendor/topthink/framework/src/think/Validate.php new file mode 100644 index 0000000..0788406 --- /dev/null +++ b/vendor/topthink/framework/src/think/Validate.php @@ -0,0 +1,1688 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use Closure; +use think\exception\ValidateException; +use think\helper\Str; +use think\validate\ValidateRule; + +/** + * 数据验证类 + * @package think + */ +class Validate +{ + /** + * 自定义验证类型 + * @var array + */ + protected $type = []; + + /** + * 验证类型别名 + * @var array + */ + protected $alias = [ + '>' => 'gt', '>=' => 'egt', '<' => 'lt', '<=' => 'elt', '=' => 'eq', 'same' => 'eq', + ]; + + /** + * 当前验证规则 + * @var array + */ + protected $rule = []; + + /** + * 验证提示信息 + * @var array + */ + protected $message = []; + + /** + * 验证字段描述 + * @var array + */ + protected $field = []; + + /** + * 默认规则提示 + * @var array + */ + protected $typeMsg = [ + 'require' => ':attribute require', + 'must' => ':attribute must', + 'number' => ':attribute must be numeric', + 'integer' => ':attribute must be integer', + 'float' => ':attribute must be float', + 'boolean' => ':attribute must be bool', + 'email' => ':attribute not a valid email address', + 'mobile' => ':attribute not a valid mobile', + 'array' => ':attribute must be a array', + 'accepted' => ':attribute must be yes,on or 1', + 'date' => ':attribute not a valid datetime', + 'file' => ':attribute not a valid file', + 'image' => ':attribute not a valid image', + 'alpha' => ':attribute must be alpha', + 'alphaNum' => ':attribute must be alpha-numeric', + 'alphaDash' => ':attribute must be alpha-numeric, dash, underscore', + 'activeUrl' => ':attribute not a valid domain or ip', + 'chs' => ':attribute must be chinese', + 'chsAlpha' => ':attribute must be chinese or alpha', + 'chsAlphaNum' => ':attribute must be chinese,alpha-numeric', + 'chsDash' => ':attribute must be chinese,alpha-numeric,underscore, dash', + 'url' => ':attribute not a valid url', + 'ip' => ':attribute not a valid ip', + 'dateFormat' => ':attribute must be dateFormat of :rule', + 'in' => ':attribute must be in :rule', + 'notIn' => ':attribute be notin :rule', + 'between' => ':attribute must between :1 - :2', + 'notBetween' => ':attribute not between :1 - :2', + 'length' => 'size of :attribute must be :rule', + 'max' => 'max size of :attribute must be :rule', + 'min' => 'min size of :attribute must be :rule', + 'after' => ':attribute cannot be less than :rule', + 'before' => ':attribute cannot exceed :rule', + 'expire' => ':attribute not within :rule', + 'allowIp' => 'access IP is not allowed', + 'denyIp' => 'access IP denied', + 'confirm' => ':attribute out of accord with :2', + 'different' => ':attribute cannot be same with :2', + 'egt' => ':attribute must greater than or equal :rule', + 'gt' => ':attribute must greater than :rule', + 'elt' => ':attribute must less than or equal :rule', + 'lt' => ':attribute must less than :rule', + 'eq' => ':attribute must equal :rule', + 'unique' => ':attribute has exists', + 'regex' => ':attribute not conform to the rules', + 'method' => 'invalid Request method', + 'token' => 'invalid token', + 'fileSize' => 'filesize not match', + 'fileExt' => 'extensions to upload is not allowed', + 'fileMime' => 'mimetype to upload is not allowed', + ]; + + /** + * 当前验证场景 + * @var string + */ + protected $currentScene; + + /** + * 内置正则验证规则 + * @var array + */ + protected $defaultRegex = [ + 'alpha' => '/^[A-Za-z]+$/', + 'alphaNum' => '/^[A-Za-z0-9]+$/', + 'alphaDash' => '/^[A-Za-z0-9\-\_]+$/', + 'chs' => '/^[\x{4e00}-\x{9fa5}\x{9fa6}-\x{9fef}\x{3400}-\x{4db5}\x{20000}-\x{2ebe0}]+$/u', + 'chsAlpha' => '/^[\x{4e00}-\x{9fa5}\x{9fa6}-\x{9fef}\x{3400}-\x{4db5}\x{20000}-\x{2ebe0}a-zA-Z]+$/u', + 'chsAlphaNum' => '/^[\x{4e00}-\x{9fa5}\x{9fa6}-\x{9fef}\x{3400}-\x{4db5}\x{20000}-\x{2ebe0}a-zA-Z0-9]+$/u', + 'chsDash' => '/^[\x{4e00}-\x{9fa5}\x{9fa6}-\x{9fef}\x{3400}-\x{4db5}\x{20000}-\x{2ebe0}a-zA-Z0-9\_\-]+$/u', + 'mobile' => '/^1[3-9]\d{9}$/', + 'idCard' => '/(^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$)|(^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}$)/', + 'zip' => '/\d{6}/', + ]; + + /** + * Filter_var 规则 + * @var array + */ + protected $filter = [ + 'email' => FILTER_VALIDATE_EMAIL, + 'ip' => [FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6], + 'integer' => FILTER_VALIDATE_INT, + 'url' => FILTER_VALIDATE_URL, + 'macAddr' => FILTER_VALIDATE_MAC, + 'float' => FILTER_VALIDATE_FLOAT, + ]; + + /** + * 验证场景定义 + * @var array + */ + protected $scene = []; + + /** + * 验证失败错误信息 + * @var string|array + */ + protected $error = []; + + /** + * 是否批量验证 + * @var bool + */ + protected $batch = false; + + /** + * 验证失败是否抛出异常 + * @var bool + */ + protected $failException = false; + + /** + * 场景需要验证的规则 + * @var array + */ + protected $only = []; + + /** + * 场景需要移除的验证规则 + * @var array + */ + protected $remove = []; + + /** + * 场景需要追加的验证规则 + * @var array + */ + protected $append = []; + + /** + * 验证正则定义 + * @var array + */ + protected $regex = []; + + /** + * Db对象 + * @var Db + */ + protected $db; + + /** + * 语言对象 + * @var Lang + */ + protected $lang; + + /** + * 请求对象 + * @var Request + */ + protected $request; + + /** + * @var Closure[] + */ + protected static $maker = []; + + /** + * 构造方法 + * @access public + */ + public function __construct() + { + if (!empty(static::$maker)) { + foreach (static::$maker as $maker) { + call_user_func($maker, $this); + } + } + } + + /** + * 设置服务注入 + * @access public + * @param Closure $maker + * @return void + */ + public static function maker(Closure $maker) + { + static::$maker[] = $maker; + } + + /** + * 设置Lang对象 + * @access public + * @param Lang $lang Lang对象 + * @return void + */ + public function setLang(Lang $lang) + { + $this->lang = $lang; + } + + /** + * 设置Db对象 + * @access public + * @param Db $db Db对象 + * @return void + */ + public function setDb(Db $db) + { + $this->db = $db; + } + + /** + * 设置Request对象 + * @access public + * @param Request $request Request对象 + * @return void + */ + public function setRequest(Request $request) + { + $this->request = $request; + } + + /** + * 添加字段验证规则 + * @access protected + * @param string|array $name 字段名称或者规则数组 + * @param mixed $rule 验证规则或者字段描述信息 + * @return $this + */ + public function rule($name, $rule = '') + { + if (is_array($name)) { + $this->rule = $name + $this->rule; + if (is_array($rule)) { + $this->field = array_merge($this->field, $rule); + } + } else { + $this->rule[$name] = $rule; + } + + return $this; + } + + /** + * 注册验证(类型)规则 + * @access public + * @param string $type 验证规则类型 + * @param callable $callback callback方法(或闭包) + * @param string $message 验证失败提示信息 + * @return $this + */ + public function extend(string $type, callable $callback = null, string $message = null) + { + $this->type[$type] = $callback; + + if ($message) { + $this->typeMsg[$type] = $message; + } + + return $this; + } + + /** + * 设置验证规则的默认提示信息 + * @access public + * @param string|array $type 验证规则类型名称或者数组 + * @param string $msg 验证提示信息 + * @return void + */ + public function setTypeMsg($type, string $msg = null): void + { + if (is_array($type)) { + $this->typeMsg = array_merge($this->typeMsg, $type); + } else { + $this->typeMsg[$type] = $msg; + } + } + + /** + * 设置提示信息 + * @access public + * @param array $message 错误信息 + * @return Validate + */ + public function message(array $message) + { + $this->message = array_merge($this->message, $message); + + return $this; + } + + /** + * 设置验证场景 + * @access public + * @param string $name 场景名 + * @return $this + */ + public function scene(string $name) + { + // 设置当前场景 + $this->currentScene = $name; + + return $this; + } + + /** + * 判断是否存在某个验证场景 + * @access public + * @param string $name 场景名 + * @return bool + */ + public function hasScene(string $name): bool + { + return isset($this->scene[$name]) || method_exists($this, 'scene' . $name); + } + + /** + * 设置批量验证 + * @access public + * @param bool $batch 是否批量验证 + * @return $this + */ + public function batch(bool $batch = true) + { + $this->batch = $batch; + + return $this; + } + + /** + * 设置验证失败后是否抛出异常 + * @access protected + * @param bool $fail 是否抛出异常 + * @return $this + */ + public function failException(bool $fail = true) + { + $this->failException = $fail; + + return $this; + } + + /** + * 指定需要验证的字段列表 + * @access public + * @param array $fields 字段名 + * @return $this + */ + public function only(array $fields) + { + $this->only = $fields; + + return $this; + } + + /** + * 移除某个字段的验证规则 + * @access public + * @param string|array $field 字段名 + * @param mixed $rule 验证规则 true 移除所有规则 + * @return $this + */ + public function remove($field, $rule = null) + { + if (is_array($field)) { + foreach ($field as $key => $rule) { + if (is_int($key)) { + $this->remove($rule); + } else { + $this->remove($key, $rule); + } + } + } else { + if (is_string($rule)) { + $rule = explode('|', $rule); + } + + $this->remove[$field] = $rule; + } + + return $this; + } + + /** + * 追加某个字段的验证规则 + * @access public + * @param string|array $field 字段名 + * @param mixed $rule 验证规则 + * @return $this + */ + public function append($field, $rule = null) + { + if (is_array($field)) { + foreach ($field as $key => $rule) { + $this->append($key, $rule); + } + } else { + if (is_string($rule)) { + $rule = explode('|', $rule); + } + + $this->append[$field] = $rule; + } + + return $this; + } + + /** + * 数据自动验证 + * @access public + * @param array $data 数据 + * @param array $rules 验证规则 + * @return bool + */ + public function check(array $data, array $rules = []): bool + { + $this->error = []; + + if ($this->currentScene) { + $this->getScene($this->currentScene); + } + + if (empty($rules)) { + // 读取验证规则 + $rules = $this->rule; + } + + foreach ($this->append as $key => $rule) { + if (!isset($rules[$key])) { + $rules[$key] = $rule; + unset($this->append[$key]); + } + } + + foreach ($rules as $key => $rule) { + // field => 'rule1|rule2...' field => ['rule1','rule2',...] + if (strpos($key, '|')) { + // 字段|描述 用于指定属性名称 + [$key, $title] = explode('|', $key); + } else { + $title = $this->field[$key] ?? $key; + } + + // 场景检测 + if (!empty($this->only) && !in_array($key, $this->only)) { + continue; + } + + // 获取数据 支持二维数组 + $value = $this->getDataValue($data, $key); + + // 字段验证 + if ($rule instanceof Closure) { + $result = call_user_func_array($rule, [$value, $data]); + } elseif ($rule instanceof ValidateRule) { + // 验证因子 + $result = $this->checkItem($key, $value, $rule->getRule(), $data, $rule->getTitle() ?: $title, $rule->getMsg()); + } else { + $result = $this->checkItem($key, $value, $rule, $data, $title); + } + + if (true !== $result) { + // 没有返回true 则表示验证失败 + if (!empty($this->batch)) { + // 批量验证 + $this->error[$key] = $result; + } elseif ($this->failException) { + throw new ValidateException($result); + } else { + $this->error = $result; + return false; + } + } + } + + if (!empty($this->error)) { + if ($this->failException) { + throw new ValidateException($this->error); + } + return false; + } + + return true; + } + + /** + * 根据验证规则验证数据 + * @access public + * @param mixed $value 字段值 + * @param mixed $rules 验证规则 + * @return bool + */ + public function checkRule($value, $rules): bool + { + if ($rules instanceof Closure) { + return call_user_func_array($rules, [$value]); + } elseif ($rules instanceof ValidateRule) { + $rules = $rules->getRule(); + } elseif (is_string($rules)) { + $rules = explode('|', $rules); + } + + foreach ($rules as $key => $rule) { + if ($rule instanceof Closure) { + $result = call_user_func_array($rule, [$value]); + } else { + // 判断验证类型 + [$type, $rule] = $this->getValidateType($key, $rule); + + $callback = $this->type[$type] ?? [$this, $type]; + + $result = call_user_func_array($callback, [$value, $rule]); + } + + if (true !== $result) { + if ($this->failException) { + throw new ValidateException($result); + } + + return $result; + } + } + + return true; + } + + /** + * 验证单个字段规则 + * @access protected + * @param string $field 字段名 + * @param mixed $value 字段值 + * @param mixed $rules 验证规则 + * @param array $data 数据 + * @param string $title 字段描述 + * @param array $msg 提示信息 + * @return mixed + */ + protected function checkItem(string $field, $value, $rules, $data, string $title = '', array $msg = []) + { + if (isset($this->remove[$field]) && true === $this->remove[$field] && empty($this->append[$field])) { + // 字段已经移除 无需验证 + return true; + } + + // 支持多规则验证 require|in:a,b,c|... 或者 ['require','in'=>'a,b,c',...] + if (is_string($rules)) { + $rules = explode('|', $rules); + } + + if (isset($this->append[$field])) { + // 追加额外的验证规则 + $rules = array_unique(array_merge($rules, $this->append[$field]), SORT_REGULAR); + unset($this->append[$field]); + } + + if (empty($rules)) { + return true; + } + + $i = 0; + foreach ($rules as $key => $rule) { + if ($rule instanceof Closure) { + $result = call_user_func_array($rule, [$value, $data]); + $info = is_numeric($key) ? '' : $key; + } else { + // 判断验证类型 + [$type, $rule, $info] = $this->getValidateType($key, $rule); + + if (isset($this->append[$field]) && in_array($info, $this->append[$field])) { + } elseif (isset($this->remove[$field]) && in_array($info, $this->remove[$field])) { + // 规则已经移除 + $i++; + continue; + } + + if (isset($this->type[$type])) { + $result = call_user_func_array($this->type[$type], [$value, $rule, $data, $field, $title]); + } elseif ('must' == $info || 0 === strpos($info, 'require') || (!is_null($value) && '' !== $value)) { + $result = call_user_func_array([$this, $type], [$value, $rule, $data, $field, $title]); + } else { + $result = true; + } + } + + if (false === $result) { + // 验证失败 返回错误信息 + if (!empty($msg[$i])) { + $message = $msg[$i]; + if (is_string($message) && strpos($message, '{%') === 0) { + $message = $this->lang->get(substr($message, 2, -1)); + } + } else { + $message = $this->getRuleMsg($field, $title, $info, $rule); + } + + return $message; + } elseif (true !== $result) { + // 返回自定义错误信息 + if (is_string($result) && false !== strpos($result, ':')) { + $result = str_replace(':attribute', $title, $result); + + if (strpos($result, ':rule') && is_scalar($rule)) { + $result = str_replace(':rule', (string) $rule, $result); + } + } + + return $result; + } + $i++; + } + + return $result ?? true; + } + + /** + * 获取当前验证类型及规则 + * @access public + * @param mixed $key + * @param mixed $rule + * @return array + */ + protected function getValidateType($key, $rule): array + { + // 判断验证类型 + if (!is_numeric($key)) { + if (isset($this->alias[$key])) { + // 判断别名 + $key = $this->alias[$key]; + } + return [$key, $rule, $key]; + } + + if (strpos($rule, ':')) { + [$type, $rule] = explode(':', $rule, 2); + if (isset($this->alias[$type])) { + // 判断别名 + $type = $this->alias[$type]; + } + $info = $type; + } elseif (method_exists($this, $rule)) { + $type = $rule; + $info = $rule; + $rule = ''; + } else { + $type = 'is'; + $info = $rule; + } + + return [$type, $rule, $info]; + } + + /** + * 验证是否和某个字段的值一致 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @param string $field 字段名 + * @return bool + */ + public function confirm($value, $rule, array $data = [], string $field = ''): bool + { + if ('' == $rule) { + if (strpos($field, '_confirm')) { + $rule = strstr($field, '_confirm', true); + } else { + $rule = $field . '_confirm'; + } + } + + return $this->getDataValue($data, $rule) === $value; + } + + /** + * 验证是否和某个字段的值是否不同 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function different($value, $rule, array $data = []): bool + { + return $this->getDataValue($data, $rule) != $value; + } + + /** + * 验证是否大于等于某个值 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function egt($value, $rule, array $data = []): bool + { + return $value >= $this->getDataValue($data, $rule); + } + + /** + * 验证是否大于某个值 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function gt($value, $rule, array $data = []): bool + { + return $value > $this->getDataValue($data, $rule); + } + + /** + * 验证是否小于等于某个值 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function elt($value, $rule, array $data = []): bool + { + return $value <= $this->getDataValue($data, $rule); + } + + /** + * 验证是否小于某个值 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function lt($value, $rule, array $data = []): bool + { + return $value < $this->getDataValue($data, $rule); + } + + /** + * 验证是否等于某个值 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function eq($value, $rule): bool + { + return $value == $rule; + } + + /** + * 必须验证 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function must($value, $rule = null): bool + { + return !empty($value) || '0' == $value; + } + + /** + * 验证字段值是否为有效格式 + * @access public + * @param mixed $value 字段值 + * @param string $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function is($value, string $rule, array $data = []): bool + { + switch (Str::camel($rule)) { + case 'require': + // 必须 + $result = !empty($value) || '0' == $value; + break; + case 'accepted': + // 接受 + $result = in_array($value, ['1', 'on', 'yes']); + break; + case 'date': + // 是否是一个有效日期 + $result = false !== strtotime($value); + break; + case 'activeUrl': + // 是否为有效的网址 + $result = checkdnsrr($value); + break; + case 'boolean': + case 'bool': + // 是否为布尔值 + $result = in_array($value, [true, false, 0, 1, '0', '1'], true); + break; + case 'number': + $result = ctype_digit((string) $value); + break; + case 'alphaNum': + $result = ctype_alnum($value); + break; + case 'array': + // 是否为数组 + $result = is_array($value); + break; + case 'file': + $result = $value instanceof File; + break; + case 'image': + $result = $value instanceof File && in_array($this->getImageType($value->getRealPath()), [1, 2, 3, 6]); + break; + case 'token': + $result = $this->token($value, '__token__', $data); + break; + default: + if (isset($this->type[$rule])) { + // 注册的验证规则 + $result = call_user_func_array($this->type[$rule], [$value]); + } elseif (function_exists('ctype_' . $rule)) { + // ctype验证规则 + $ctypeFun = 'ctype_' . $rule; + $result = $ctypeFun($value); + } elseif (isset($this->filter[$rule])) { + // Filter_var验证规则 + $result = $this->filter($value, $this->filter[$rule]); + } else { + // 正则验证 + $result = $this->regex($value, $rule); + } + } + + return $result; + } + + // 判断图像类型 + protected function getImageType($image) + { + if (function_exists('exif_imagetype')) { + return exif_imagetype($image); + } + + try { + $info = getimagesize($image); + return $info ? $info[2] : false; + } catch (\Exception $e) { + return false; + } + } + + /** + * 验证表单令牌 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function token($value, string $rule, array $data): bool + { + $rule = !empty($rule) ? $rule : '__token__'; + return $this->request->checkToken($rule, $data); + } + + /** + * 验证是否为合格的域名或者IP 支持A,MX,NS,SOA,PTR,CNAME,AAAA,A6, SRV,NAPTR,TXT 或者 ANY类型 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function activeUrl(string $value, string $rule = 'MX'): bool + { + if (!in_array($rule, ['A', 'MX', 'NS', 'SOA', 'PTR', 'CNAME', 'AAAA', 'A6', 'SRV', 'NAPTR', 'TXT', 'ANY'])) { + $rule = 'MX'; + } + + return checkdnsrr($value, $rule); + } + + /** + * 验证是否有效IP + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 ipv4 ipv6 + * @return bool + */ + public function ip($value, string $rule = 'ipv4'): bool + { + if (!in_array($rule, ['ipv4', 'ipv6'])) { + $rule = 'ipv4'; + } + + return $this->filter($value, [FILTER_VALIDATE_IP, 'ipv6' == $rule ? FILTER_FLAG_IPV6 : FILTER_FLAG_IPV4]); + } + + /** + * 检测上传文件后缀 + * @access public + * @param File $file + * @param array|string $ext 允许后缀 + * @return bool + */ + protected function checkExt(File $file, $ext): bool + { + if (is_string($ext)) { + $ext = explode(',', $ext); + } + + return in_array(strtolower($file->extension()), $ext); + } + + /** + * 检测上传文件大小 + * @access public + * @param File $file + * @param integer $size 最大大小 + * @return bool + */ + protected function checkSize(File $file, $size): bool + { + return $file->getSize() <= (int) $size; + } + + /** + * 检测上传文件类型 + * @access public + * @param File $file + * @param array|string $mime 允许类型 + * @return bool + */ + protected function checkMime(File $file, $mime): bool + { + if (is_string($mime)) { + $mime = explode(',', $mime); + } + + return in_array(strtolower($file->getMime()), $mime); + } + + /** + * 验证上传文件后缀 + * @access public + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 + * @return bool + */ + public function fileExt($file, $rule): bool + { + if (is_array($file)) { + foreach ($file as $item) { + if (!($item instanceof File) || !$this->checkExt($item, $rule)) { + return false; + } + } + return true; + } elseif ($file instanceof File) { + return $this->checkExt($file, $rule); + } + + return false; + } + + /** + * 验证上传文件类型 + * @access public + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 + * @return bool + */ + public function fileMime($file, $rule): bool + { + if (is_array($file)) { + foreach ($file as $item) { + if (!($item instanceof File) || !$this->checkMime($item, $rule)) { + return false; + } + } + return true; + } elseif ($file instanceof File) { + return $this->checkMime($file, $rule); + } + + return false; + } + + /** + * 验证上传文件大小 + * @access public + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 + * @return bool + */ + public function fileSize($file, $rule): bool + { + if (is_array($file)) { + foreach ($file as $item) { + if (!($item instanceof File) || !$this->checkSize($item, $rule)) { + return false; + } + } + return true; + } elseif ($file instanceof File) { + return $this->checkSize($file, $rule); + } + + return false; + } + + /** + * 验证图片的宽高及类型 + * @access public + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 + * @return bool + */ + public function image($file, $rule): bool + { + if (!($file instanceof File)) { + return false; + } + + if ($rule) { + $rule = explode(',', $rule); + + [$width, $height, $type] = getimagesize($file->getRealPath()); + + if (isset($rule[2])) { + $imageType = strtolower($rule[2]); + + if ('jpg' == $imageType) { + $imageType = 'jpeg'; + } + + if (image_type_to_extension($type, false) != $imageType) { + return false; + } + } + + [$w, $h] = $rule; + + return $w == $width && $h == $height; + } + + return in_array($this->getImageType($file->getRealPath()), [1, 2, 3, 6]); + } + + /** + * 验证时间和日期是否符合指定格式 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function dateFormat($value, $rule): bool + { + $info = date_parse_from_format($rule, $value); + return 0 == $info['warning_count'] && 0 == $info['error_count']; + } + + /** + * 验证是否唯一 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 格式:数据表,字段名,排除ID,主键名 + * @param array $data 数据 + * @param string $field 验证字段名 + * @return bool + */ + public function unique($value, $rule, array $data = [], string $field = ''): bool + { + if (is_string($rule)) { + $rule = explode(',', $rule); + } + + if (false !== strpos($rule[0], '\\')) { + // 指定模型类 + $db = new $rule[0]; + } else { + $db = $this->db->name($rule[0]); + } + + $key = $rule[1] ?? $field; + $map = []; + + if (strpos($key, '^')) { + // 支持多个字段验证 + $fields = explode('^', $key); + foreach ($fields as $key) { + if (isset($data[$key])) { + $map[] = [$key, '=', $data[$key]]; + } + } + } elseif (isset($data[$field])) { + $map[] = [$key, '=', $data[$field]]; + } else { + $map = []; + } + + $pk = !empty($rule[3]) ? $rule[3] : $db->getPk(); + + if (is_string($pk)) { + if (isset($rule[2])) { + $map[] = [$pk, '<>', $rule[2]]; + } elseif (isset($data[$pk])) { + $map[] = [$pk, '<>', $data[$pk]]; + } + } + + if ($db->where($map)->field($pk)->find()) { + return false; + } + + return true; + } + + /** + * 使用filter_var方式验证 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function filter($value, $rule): bool + { + if (is_string($rule) && strpos($rule, ',')) { + [$rule, $param] = explode(',', $rule); + } elseif (is_array($rule)) { + $param = $rule[1] ?? 0; + $rule = $rule[0]; + } else { + $param = 0; + } + + return false !== filter_var($value, is_int($rule) ? $rule : filter_id($rule), $param); + } + + /** + * 验证某个字段等于某个值的时候必须 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function requireIf($value, $rule, array $data = []): bool + { + [$field, $val] = explode(',', $rule); + + if ($this->getDataValue($data, $field) == $val) { + return !empty($value) || '0' == $value; + } + + return true; + } + + /** + * 通过回调方法验证某个字段是否必须 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function requireCallback($value, $rule, array $data = []): bool + { + $result = call_user_func_array([$this, $rule], [$value, $data]); + + if ($result) { + return !empty($value) || '0' == $value; + } + + return true; + } + + /** + * 验证某个字段有值的情况下必须 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function requireWith($value, $rule, array $data = []): bool + { + $val = $this->getDataValue($data, $rule); + + if (!empty($val)) { + return !empty($value) || '0' == $value; + } + + return true; + } + + /** + * 验证某个字段没有值的情况下必须 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function requireWithout($value, $rule, array $data = []): bool + { + $val = $this->getDataValue($data, $rule); + + if (empty($val)) { + return !empty($value) || '0' == $value; + } + + return true; + } + + /** + * 验证是否在范围内 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function in($value, $rule): bool + { + return in_array($value, is_array($rule) ? $rule : explode(',', $rule)); + } + + /** + * 验证是否不在某个范围 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function notIn($value, $rule): bool + { + return !in_array($value, is_array($rule) ? $rule : explode(',', $rule)); + } + + /** + * between验证数据 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function between($value, $rule): bool + { + if (is_string($rule)) { + $rule = explode(',', $rule); + } + [$min, $max] = $rule; + + return $value >= $min && $value <= $max; + } + + /** + * 使用notbetween验证数据 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function notBetween($value, $rule): bool + { + if (is_string($rule)) { + $rule = explode(',', $rule); + } + [$min, $max] = $rule; + + return $value < $min || $value > $max; + } + + /** + * 验证数据长度 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function length($value, $rule): bool + { + if (is_array($value)) { + $length = count($value); + } elseif ($value instanceof File) { + $length = $value->getSize(); + } else { + $length = mb_strlen((string) $value); + } + + if (is_string($rule) && strpos($rule, ',')) { + // 长度区间 + [$min, $max] = explode(',', $rule); + return $length >= $min && $length <= $max; + } + + // 指定长度 + return $length == $rule; + } + + /** + * 验证数据最大长度 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function max($value, $rule): bool + { + if (is_array($value)) { + $length = count($value); + } elseif ($value instanceof File) { + $length = $value->getSize(); + } else { + $length = mb_strlen((string) $value); + } + + return $length <= $rule; + } + + /** + * 验证数据最小长度 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function min($value, $rule): bool + { + if (is_array($value)) { + $length = count($value); + } elseif ($value instanceof File) { + $length = $value->getSize(); + } else { + $length = mb_strlen((string) $value); + } + + return $length >= $rule; + } + + /** + * 验证日期 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function after($value, $rule, array $data = []): bool + { + return strtotime($value) >= strtotime($rule); + } + + /** + * 验证日期 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function before($value, $rule, array $data = []): bool + { + return strtotime($value) <= strtotime($rule); + } + + /** + * 验证日期 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function afterWith($value, $rule, array $data = []): bool + { + $rule = $this->getDataValue($data, $rule); + return !is_null($rule) && strtotime($value) >= strtotime($rule); + } + + /** + * 验证日期 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function beforeWith($value, $rule, array $data = []): bool + { + $rule = $this->getDataValue($data, $rule); + return !is_null($rule) && strtotime($value) <= strtotime($rule); + } + + /** + * 验证有效期 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function expire($value, $rule): bool + { + if (is_string($rule)) { + $rule = explode(',', $rule); + } + + [$start, $end] = $rule; + + if (!is_numeric($start)) { + $start = strtotime($start); + } + + if (!is_numeric($end)) { + $end = strtotime($end); + } + + return time() >= $start && time() <= $end; + } + + /** + * 验证IP许可 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function allowIp($value, $rule): bool + { + return in_array($value, is_array($rule) ? $rule : explode(',', $rule)); + } + + /** + * 验证IP禁用 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function denyIp($value, $rule): bool + { + return !in_array($value, is_array($rule) ? $rule : explode(',', $rule)); + } + + /** + * 使用正则验证数据 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 正则规则或者预定义正则名 + * @return bool + */ + public function regex($value, $rule): bool + { + if (isset($this->regex[$rule])) { + $rule = $this->regex[$rule]; + } elseif (isset($this->defaultRegex[$rule])) { + $rule = $this->defaultRegex[$rule]; + } + + if (is_string($rule) && 0 !== strpos($rule, '/') && !preg_match('/\/[imsU]{0,4}$/', $rule)) { + // 不是正则表达式则两端补上/ + $rule = '/^' . $rule . '$/'; + } + + return is_scalar($value) && 1 === preg_match($rule, (string) $value); + } + + /** + * 获取错误信息 + * @return array|string + */ + public function getError() + { + return $this->error; + } + + /** + * 获取数据值 + * @access protected + * @param array $data 数据 + * @param string $key 数据标识 支持二维 + * @return mixed + */ + protected function getDataValue(array $data, $key) + { + if (is_numeric($key)) { + $value = $key; + } elseif (is_string($key) && strpos($key, '.')) { + // 支持多维数组验证 + foreach (explode('.', $key) as $key) { + if (!isset($data[$key])) { + $value = null; + break; + } + $value = $data = $data[$key]; + } + } else { + $value = $data[$key] ?? null; + } + + return $value; + } + + /** + * 获取验证规则的错误提示信息 + * @access protected + * @param string $attribute 字段英文名 + * @param string $title 字段描述名 + * @param string $type 验证规则名称 + * @param mixed $rule 验证规则数据 + * @return string|array + */ + protected function getRuleMsg(string $attribute, string $title, string $type, $rule) + { + if (isset($this->message[$attribute . '.' . $type])) { + $msg = $this->message[$attribute . '.' . $type]; + } elseif (isset($this->message[$attribute][$type])) { + $msg = $this->message[$attribute][$type]; + } elseif (isset($this->message[$attribute])) { + $msg = $this->message[$attribute]; + } elseif (isset($this->typeMsg[$type])) { + $msg = $this->typeMsg[$type]; + } elseif (0 === strpos($type, 'require')) { + $msg = $this->typeMsg['require']; + } else { + $msg = $title . $this->lang->get('not conform to the rules'); + } + + if (is_array($msg)) { + return $this->errorMsgIsArray($msg, $rule, $title); + } + + return $this->parseErrorMsg($msg, $rule, $title); + } + + /** + * 获取验证规则的错误提示信息 + * @access protected + * @param string $msg 错误信息 + * @param mixed $rule 验证规则数据 + * @param string $title 字段描述名 + * @return string|array + */ + protected function parseErrorMsg(string $msg, $rule, string $title) + { + if (0 === strpos($msg, '{%')) { + $msg = $this->lang->get(substr($msg, 2, -1)); + } elseif ($this->lang->has($msg)) { + $msg = $this->lang->get($msg); + } + + if (is_array($msg)) { + return $this->errorMsgIsArray($msg, $rule, $title); + } + + // rule若是数组则转为字符串 + if (is_array($rule)) { + $rule = implode(',', $rule); + } + + if (is_scalar($rule) && false !== strpos($msg, ':')) { + // 变量替换 + if (is_string($rule) && strpos($rule, ',')) { + $array = array_pad(explode(',', $rule), 3, ''); + } else { + $array = array_pad([], 3, ''); + } + + $msg = str_replace( + [':attribute', ':1', ':2', ':3'], + [$title, $array[0], $array[1], $array[2]], + $msg + ); + + if (strpos($msg, ':rule')) { + $msg = str_replace(':rule', (string) $rule, $msg); + } + } + + return $msg; + } + + /** + * 错误信息数组处理 + * @access protected + * @param array $msg 错误信息 + * @param mixed $rule 验证规则数据 + * @param string $title 字段描述名 + * @return array + */ + protected function errorMsgIsArray(array $msg, $rule, string $title) + { + foreach ($msg as $key => $val) { + if (is_string($val)) { + $msg[$key] = $this->parseErrorMsg($val, $rule, $title); + } + } + return $msg; + } + + /** + * 获取数据验证的场景 + * @access protected + * @param string $scene 验证场景 + * @return void + */ + protected function getScene(string $scene): void + { + $this->only = $this->append = $this->remove = []; + + if (method_exists($this, 'scene' . $scene)) { + call_user_func([$this, 'scene' . $scene]); + } elseif (isset($this->scene[$scene])) { + // 如果设置了验证适用场景 + $this->only = $this->scene[$scene]; + } + } + + /** + * 动态方法 直接调用is方法进行验证 + * @access public + * @param string $method 方法名 + * @param array $args 调用参数 + * @return bool + */ + public function __call($method, $args) + { + if ('is' == strtolower(substr($method, 0, 2))) { + $method = substr($method, 2); + } + + array_push($args, lcfirst($method)); + + return call_user_func_array([$this, 'is'], $args); + } +} diff --git a/vendor/topthink/framework/src/think/View.php b/vendor/topthink/framework/src/think/View.php new file mode 100644 index 0000000..2e71088 --- /dev/null +++ b/vendor/topthink/framework/src/think/View.php @@ -0,0 +1,191 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use think\helper\Arr; + +/** + * 视图类 + * @package think + */ +class View extends Manager +{ + + protected $namespace = '\\think\\view\\driver\\'; + + /** + * 模板变量 + * @var array + */ + protected $data = []; + + /** + * 内容过滤 + * @var mixed + */ + protected $filter; + + /** + * 获取模板引擎 + * @access public + * @param string $type 模板引擎类型 + * @return $this + */ + public function engine(string $type = null) + { + return $this->driver($type); + } + + /** + * 模板变量赋值 + * @access public + * @param string|array $name 模板变量 + * @param mixed $value 变量值 + * @return $this + */ + public function assign($name, $value = null) + { + if (is_array($name)) { + $this->data = array_merge($this->data, $name); + } else { + $this->data[$name] = $value; + } + + return $this; + } + + /** + * 视图过滤 + * @access public + * @param Callable $filter 过滤方法或闭包 + * @return $this + */ + public function filter(callable $filter = null) + { + $this->filter = $filter; + return $this; + } + + /** + * 解析和获取模板内容 用于输出 + * @access public + * @param string $template 模板文件名或者内容 + * @param array $vars 模板变量 + * @return string + * @throws \Exception + */ + public function fetch(string $template = '', array $vars = []): string + { + return $this->getContent(function () use ($vars, $template) { + $this->engine()->fetch($template, array_merge($this->data, $vars)); + }); + } + + /** + * 渲染内容输出 + * @access public + * @param string $content 内容 + * @param array $vars 模板变量 + * @return string + */ + public function display(string $content, array $vars = []): string + { + return $this->getContent(function () use ($vars, $content) { + $this->engine()->display($content, array_merge($this->data, $vars)); + }); + } + + /** + * 获取模板引擎渲染内容 + * @param $callback + * @return string + * @throws \Exception + */ + protected function getContent($callback): string + { + // 页面缓存 + ob_start(); + if (PHP_VERSION > 8.0) { + ob_implicit_flush(false); + } else { + ob_implicit_flush(0); + } + + // 渲染输出 + try { + $callback(); + } catch (\Exception $e) { + ob_end_clean(); + throw $e; + } + + // 获取并清空缓存 + $content = ob_get_clean(); + + if ($this->filter) { + $content = call_user_func_array($this->filter, [$content]); + } + + return $content; + } + + /** + * 模板变量赋值 + * @access public + * @param string $name 变量名 + * @param mixed $value 变量值 + */ + public function __set($name, $value) + { + $this->data[$name] = $value; + } + + /** + * 取得模板显示变量的值 + * @access protected + * @param string $name 模板变量 + * @return mixed + */ + public function __get($name) + { + return $this->data[$name]; + } + + /** + * 检测模板变量是否设置 + * @access public + * @param string $name 模板变量名 + * @return bool + */ + public function __isset($name) + { + return isset($this->data[$name]); + } + + protected function resolveConfig(string $name) + { + $config = $this->app->config->get('view', []); + Arr::forget($config, 'type'); + return $config; + } + + /** + * 默认驱动 + * @return string|null + */ + public function getDefaultDriver() + { + return $this->app->config->get('view.type', 'php'); + } + +} diff --git a/vendor/topthink/framework/src/think/cache/Driver.php b/vendor/topthink/framework/src/think/cache/Driver.php new file mode 100644 index 0000000..5813c7b --- /dev/null +++ b/vendor/topthink/framework/src/think/cache/Driver.php @@ -0,0 +1,357 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\cache; + +use Closure; +use DateInterval; +use DateTime; +use DateTimeInterface; +use Exception; +use Psr\SimpleCache\CacheInterface; +use think\Container; +use think\contract\CacheHandlerInterface; +use think\exception\InvalidArgumentException; +use throwable; + +/** + * 缓存基础类 + */ +abstract class Driver implements CacheInterface, CacheHandlerInterface +{ + /** + * 驱动句柄 + * @var object + */ + protected $handler = null; + + /** + * 缓存读取次数 + * @var integer + */ + protected $readTimes = 0; + + /** + * 缓存写入次数 + * @var integer + */ + protected $writeTimes = 0; + + /** + * 缓存参数 + * @var array + */ + protected $options = []; + + /** + * 缓存标签 + * @var array + */ + protected $tag = []; + + /** + * 获取有效期 + * @access protected + * @param integer|DateTimeInterface|DateInterval $expire 有效期 + * @return int + */ + protected function getExpireTime($expire): int + { + if ($expire instanceof DateTimeInterface) { + $expire = $expire->getTimestamp() - time(); + } elseif ($expire instanceof DateInterval) { + $expire = DateTime::createFromFormat('U', (string) time()) + ->add($expire) + ->format('U') - time(); + } + + return (int) $expire; + } + + /** + * 获取实际的缓存标识 + * @access public + * @param string $name 缓存名 + * @return string + */ + public function getCacheKey(string $name): string + { + return $this->options['prefix'] . $name; + } + + /** + * 读取缓存并删除 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public function pull(string $name) + { + $result = $this->get($name, false); + + if ($result) { + $this->delete($name); + return $result; + } + } + + /** + * 追加(数组)缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @return void + */ + public function push(string $name, $value): void + { + $item = $this->get($name, []); + + if (!is_array($item)) { + throw new InvalidArgumentException('only array cache can be push'); + } + + $item[] = $value; + + if (count($item) > 1000) { + array_shift($item); + } + + $item = array_unique($item); + + $this->set($name, $item); + } + + /** + * 追加TagSet数据 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @return void + */ + public function append(string $name, $value): void + { + $this->push($name, $value); + } + + /** + * 如果不存在则写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int $expire 有效时间 0为永久 + * @return mixed + */ + public function remember(string $name, $value, $expire = null) + { + if ($this->has($name)) { + return $this->get($name); + } + + $time = time(); + + while ($time + 5 > time() && $this->has($name . '_lock')) { + // 存在锁定则等待 + usleep(200000); + } + + try { + // 锁定 + $this->set($name . '_lock', true); + + if ($value instanceof Closure) { + // 获取缓存数据 + $value = Container::getInstance()->invokeFunction($value); + } + + // 缓存数据 + $this->set($name, $value, $expire); + + // 解锁 + $this->delete($name . '_lock'); + } catch (Exception | throwable $e) { + $this->delete($name . '_lock'); + throw $e; + } + + return $value; + } + + /** + * 缓存标签 + * @access public + * @param string|array $name 标签名 + * @return TagSet + */ + public function tag($name): TagSet + { + $name = (array) $name; + $key = implode('-', $name); + + if (!isset($this->tag[$key])) { + $this->tag[$key] = new TagSet($name, $this); + } + + return $this->tag[$key]; + } + + /** + * 获取标签包含的缓存标识 + * @access public + * @param string $tag 标签标识 + * @return array + */ + public function getTagItems(string $tag): array + { + $name = $this->getTagKey($tag); + return $this->get($name, []); + } + + /** + * 获取实际标签名 + * @access public + * @param string $tag 标签名 + * @return string + */ + public function getTagKey(string $tag): string + { + return $this->options['tag_prefix'] . md5($tag); + } + + /** + * 序列化数据 + * @access protected + * @param mixed $data 缓存数据 + * @return string + */ + protected function serialize($data): string + { + if (is_numeric($data)) { + return (string) $data; + } + + $serialize = $this->options['serialize'][0] ?? "serialize"; + + return $serialize($data); + } + + /** + * 反序列化数据 + * @access protected + * @param string $data 缓存数据 + * @return mixed + */ + protected function unserialize(string $data) + { + if (is_numeric($data)) { + return $data; + } + + $unserialize = $this->options['serialize'][1] ?? "unserialize"; + + return $unserialize($data); + } + + /** + * 返回句柄对象,可执行其它高级方法 + * + * @access public + * @return object + */ + public function handler() + { + return $this->handler; + } + + /** + * 返回缓存读取次数 + * @access public + * @return int + */ + public function getReadTimes(): int + { + return $this->readTimes; + } + + /** + * 返回缓存写入次数 + * @access public + * @return int + */ + public function getWriteTimes(): int + { + return $this->writeTimes; + } + + /** + * 读取缓存 + * @access public + * @param iterable $keys 缓存变量名 + * @param mixed $default 默认值 + * @return iterable + * @throws InvalidArgumentException + */ + public function getMultiple($keys, $default = null): iterable + { + $result = []; + + foreach ($keys as $key) { + $result[$key] = $this->get($key, $default); + } + + return $result; + } + + /** + * 写入缓存 + * @access public + * @param iterable $values 缓存数据 + * @param null|int|\DateInterval $ttl 有效时间 0为永久 + * @return bool + */ + public function setMultiple($values, $ttl = null): bool + { + foreach ($values as $key => $val) { + $result = $this->set($key, $val, $ttl); + + if (false === $result) { + return false; + } + } + + return true; + } + + /** + * 删除缓存 + * @access public + * @param iterable $keys 缓存变量名 + * @return bool + * @throws InvalidArgumentException + */ + public function deleteMultiple($keys): bool + { + foreach ($keys as $key) { + $result = $this->delete($key); + + if (false === $result) { + return false; + } + } + + return true; + } + + public function __call($method, $args) + { + return call_user_func_array([$this->handler, $method], $args); + } +} diff --git a/vendor/topthink/framework/src/think/cache/TagSet.php b/vendor/topthink/framework/src/think/cache/TagSet.php new file mode 100644 index 0000000..5ba2076 --- /dev/null +++ b/vendor/topthink/framework/src/think/cache/TagSet.php @@ -0,0 +1,132 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\cache; + +/** + * 标签集合 + */ +class TagSet +{ + /** + * 标签的缓存Key + * @var array + */ + protected $tag; + + /** + * 缓存句柄 + * @var Driver + */ + protected $handler; + + /** + * 架构函数 + * @access public + * @param array $tag 缓存标签 + * @param Driver $cache 缓存对象 + */ + public function __construct(array $tag, Driver $cache) + { + $this->tag = $tag; + $this->handler = $cache; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return bool + */ + public function set(string $name, $value, $expire = null): bool + { + $this->handler->set($name, $value, $expire); + + $this->append($name); + + return true; + } + + /** + * 追加缓存标识到标签 + * @access public + * @param string $name 缓存变量名 + * @return void + */ + public function append(string $name): void + { + $name = $this->handler->getCacheKey($name); + + foreach ($this->tag as $tag) { + $key = $this->handler->getTagKey($tag); + $this->handler->append($key, $name); + } + } + + /** + * 写入缓存 + * @access public + * @param iterable $values 缓存数据 + * @param null|int|\DateInterval $ttl 有效时间 0为永久 + * @return bool + */ + public function setMultiple($values, $ttl = null): bool + { + foreach ($values as $key => $val) { + $result = $this->set($key, $val, $ttl); + + if (false === $result) { + return false; + } + } + + return true; + } + + /** + * 如果不存在则写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int $expire 有效时间 0为永久 + * @return mixed + */ + public function remember(string $name, $value, $expire = null) + { + $result = $this->handler->remember($name, $value, $expire); + + $this->append($name); + + return $result; + } + + /** + * 清除缓存 + * @access public + * @return bool + */ + public function clear(): bool + { + // 指定标签清除 + foreach ($this->tag as $tag) { + $names = $this->handler->getTagItems($tag); + $this->handler->clearTag($names); + + $key = $this->handler->getTagKey($tag); + $this->handler->delete($key); + } + + return true; + } +} diff --git a/vendor/topthink/framework/src/think/cache/driver/File.php b/vendor/topthink/framework/src/think/cache/driver/File.php new file mode 100644 index 0000000..b36b069 --- /dev/null +++ b/vendor/topthink/framework/src/think/cache/driver/File.php @@ -0,0 +1,304 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\cache\driver; + +use FilesystemIterator; +use think\App; +use think\cache\Driver; + +/** + * 文件缓存类 + */ +class File extends Driver +{ + /** + * 配置参数 + * @var array + */ + protected $options = [ + 'expire' => 0, + 'cache_subdir' => true, + 'prefix' => '', + 'path' => '', + 'hash_type' => 'md5', + 'data_compress' => false, + 'tag_prefix' => 'tag:', + 'serialize' => [], + ]; + + /** + * 架构函数 + * @param App $app + * @param array $options 参数 + */ + public function __construct(App $app, array $options = []) + { + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + + if (empty($this->options['path'])) { + $this->options['path'] = $app->getRuntimePath() . 'cache'; + } + + if (substr($this->options['path'], -1) != DIRECTORY_SEPARATOR) { + $this->options['path'] .= DIRECTORY_SEPARATOR; + } + } + + /** + * 取得变量的存储文件名 + * @access public + * @param string $name 缓存变量名 + * @return string + */ + public function getCacheKey(string $name): string + { + $name = hash($this->options['hash_type'], $name); + + if ($this->options['cache_subdir']) { + // 使用子目录 + $name = substr($name, 0, 2) . DIRECTORY_SEPARATOR . substr($name, 2); + } + + if ($this->options['prefix']) { + $name = $this->options['prefix'] . DIRECTORY_SEPARATOR . $name; + } + + return $this->options['path'] . $name . '.php'; + } + + /** + * 获取缓存数据 + * @param string $name 缓存标识名 + * @return array|null + */ + protected function getRaw(string $name) + { + $filename = $this->getCacheKey($name); + + if (!is_file($filename)) { + return; + } + + $content = @file_get_contents($filename); + + if (false !== $content) { + $expire = (int) substr($content, 8, 12); + if (0 != $expire && time() - $expire > filemtime($filename)) { + //缓存过期删除缓存文件 + $this->unlink($filename); + return; + } + + $content = substr($content, 32); + + if ($this->options['data_compress'] && function_exists('gzcompress')) { + //启用数据压缩 + $content = gzuncompress($content); + } + + return is_string($content) ? ['content' => $content, 'expire' => $expire] : null; + } + } + + /** + * 判断缓存是否存在 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name): bool + { + return $this->getRaw($name) !== null; + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = null) + { + $this->readTimes++; + + $raw = $this->getRaw($name); + + return is_null($raw) ? $default : $this->unserialize($raw['content']); + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int|\DateTime $expire 有效时间 0为永久 + * @return bool + */ + public function set($name, $value, $expire = null): bool + { + $this->writeTimes++; + + if (is_null($expire)) { + $expire = $this->options['expire']; + } + + $expire = $this->getExpireTime($expire); + $filename = $this->getCacheKey($name); + + $dir = dirname($filename); + + if (!is_dir($dir)) { + try { + mkdir($dir, 0755, true); + } catch (\Exception $e) { + // 创建失败 + } + } + + $data = $this->serialize($value); + + if ($this->options['data_compress'] && function_exists('gzcompress')) { + //数据压缩 + $data = gzcompress($data, 3); + } + + $data = "\n" . $data; + $result = file_put_contents($filename, $data); + + if ($result) { + clearstatcache(); + return true; + } + + return false; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc(string $name, int $step = 1) + { + if ($raw = $this->getRaw($name)) { + $value = $this->unserialize($raw['content']) + $step; + $expire = $raw['expire']; + } else { + $value = $step; + $expire = 0; + } + + return $this->set($name, $value, $expire) ? $value : false; + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec(string $name, int $step = 1) + { + return $this->inc($name, -$step); + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function delete($name): bool + { + $this->writeTimes++; + + return $this->unlink($this->getCacheKey($name)); + } + + /** + * 清除缓存 + * @access public + * @return bool + */ + public function clear(): bool + { + $this->writeTimes++; + + $dirname = $this->options['path'] . $this->options['prefix']; + + $this->rmdir($dirname); + + return true; + } + + /** + * 删除缓存标签 + * @access public + * @param array $keys 缓存标识列表 + * @return void + */ + public function clearTag(array $keys): void + { + foreach ($keys as $key) { + $this->unlink($key); + } + } + + /** + * 判断文件是否存在后,删除 + * @access private + * @param string $path + * @return bool + */ + private function unlink(string $path): bool + { + try { + return is_file($path) && unlink($path); + } catch (\Exception $e) { + return false; + } + } + + /** + * 删除文件夹 + * @param $dirname + * @return bool + */ + private function rmdir($dirname) + { + if (!is_dir($dirname)) { + return false; + } + + $items = new FilesystemIterator($dirname); + + foreach ($items as $item) { + if ($item->isDir() && !$item->isLink()) { + $this->rmdir($item->getPathname()); + } else { + $this->unlink($item->getPathname()); + } + } + + @rmdir($dirname); + + return true; + } + +} diff --git a/vendor/topthink/framework/src/think/cache/driver/Memcache.php b/vendor/topthink/framework/src/think/cache/driver/Memcache.php new file mode 100644 index 0000000..2fbbb9c --- /dev/null +++ b/vendor/topthink/framework/src/think/cache/driver/Memcache.php @@ -0,0 +1,209 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\cache\driver; + +use think\cache\Driver; + +/** + * Memcache缓存类 + */ +class Memcache extends Driver +{ + /** + * 配置参数 + * @var array + */ + protected $options = [ + 'host' => '127.0.0.1', + 'port' => 11211, + 'expire' => 0, + 'timeout' => 0, // 超时时间(单位:毫秒) + 'persistent' => true, + 'prefix' => '', + 'tag_prefix' => 'tag:', + 'serialize' => [], + ]; + + /** + * 架构函数 + * @access public + * @param array $options 缓存参数 + * @throws \BadFunctionCallException + */ + public function __construct(array $options = []) + { + if (!extension_loaded('memcache')) { + throw new \BadFunctionCallException('not support: memcache'); + } + + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + + $this->handler = new \Memcache; + + // 支持集群 + $hosts = (array) $this->options['host']; + $ports = (array) $this->options['port']; + + if (empty($ports[0])) { + $ports[0] = 11211; + } + + // 建立连接 + foreach ($hosts as $i => $host) { + $port = $ports[$i] ?? $ports[0]; + $this->options['timeout'] > 0 ? + $this->handler->addServer($host, (int) $port, $this->options['persistent'], 1, (int) $this->options['timeout']) : + $this->handler->addServer($host, (int) $port, $this->options['persistent'], 1); + } + } + + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name): bool + { + $key = $this->getCacheKey($name); + + return false !== $this->handler->get($key); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = null) + { + $this->readTimes++; + + $result = $this->handler->get($this->getCacheKey($name)); + + return false !== $result ? $this->unserialize($result) : $default; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int|\DateTime $expire 有效时间(秒) + * @return bool + */ + public function set($name, $value, $expire = null): bool + { + $this->writeTimes++; + + if (is_null($expire)) { + $expire = $this->options['expire']; + } + + $key = $this->getCacheKey($name); + $expire = $this->getExpireTime($expire); + $value = $this->serialize($value); + + if ($this->handler->set($key, $value, 0, $expire)) { + return true; + } + + return false; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc(string $name, int $step = 1) + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + + if ($this->handler->get($key)) { + return $this->handler->increment($key, $step); + } + + return $this->handler->set($key, $step); + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec(string $name, int $step = 1) + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + $value = $this->handler->get($key) - $step; + $res = $this->handler->set($key, $value); + + return !$res ? false : $value; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @param bool|false $ttl + * @return bool + */ + public function delete($name, $ttl = false): bool + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + + return false === $ttl ? + $this->handler->delete($key) : + $this->handler->delete($key, $ttl); + } + + /** + * 清除缓存 + * @access public + * @return bool + */ + public function clear(): bool + { + $this->writeTimes++; + + return $this->handler->flush(); + } + + /** + * 删除缓存标签 + * @access public + * @param array $keys 缓存标识列表 + * @return void + */ + public function clearTag(array $keys): void + { + foreach ($keys as $key) { + $this->handler->delete($key); + } + } + +} diff --git a/vendor/topthink/framework/src/think/cache/driver/Memcached.php b/vendor/topthink/framework/src/think/cache/driver/Memcached.php new file mode 100644 index 0000000..71edb05 --- /dev/null +++ b/vendor/topthink/framework/src/think/cache/driver/Memcached.php @@ -0,0 +1,221 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\cache\driver; + +use think\cache\Driver; + +/** + * Memcached缓存类 + */ +class Memcached extends Driver +{ + /** + * 配置参数 + * @var array + */ + protected $options = [ + 'host' => '127.0.0.1', + 'port' => 11211, + 'expire' => 0, + 'timeout' => 0, // 超时时间(单位:毫秒) + 'prefix' => '', + 'username' => '', //账号 + 'password' => '', //密码 + 'option' => [], + 'tag_prefix' => 'tag:', + 'serialize' => [], + ]; + + /** + * 架构函数 + * @access public + * @param array $options 缓存参数 + */ + public function __construct(array $options = []) + { + if (!extension_loaded('memcached')) { + throw new \BadFunctionCallException('not support: memcached'); + } + + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + + $this->handler = new \Memcached; + + if (!empty($this->options['option'])) { + $this->handler->setOptions($this->options['option']); + } + + // 设置连接超时时间(单位:毫秒) + if ($this->options['timeout'] > 0) { + $this->handler->setOption(\Memcached::OPT_CONNECT_TIMEOUT, $this->options['timeout']); + } + + // 支持集群 + $hosts = (array) $this->options['host']; + $ports = (array) $this->options['port']; + if (empty($ports[0])) { + $ports[0] = 11211; + } + + // 建立连接 + $servers = []; + foreach ($hosts as $i => $host) { + $servers[] = [$host, $ports[$i] ?? $ports[0], 1]; + } + + $this->handler->addServers($servers); + + if ('' != $this->options['username']) { + $this->handler->setOption(\Memcached::OPT_BINARY_PROTOCOL, true); + $this->handler->setSaslAuthData($this->options['username'], $this->options['password']); + } + } + + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name): bool + { + $key = $this->getCacheKey($name); + + return $this->handler->get($key) ? true : false; + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = null) + { + $this->readTimes++; + + $result = $this->handler->get($this->getCacheKey($name)); + + return false !== $result ? $this->unserialize($result) : $default; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return bool + */ + public function set($name, $value, $expire = null): bool + { + $this->writeTimes++; + + if (is_null($expire)) { + $expire = $this->options['expire']; + } + + $key = $this->getCacheKey($name); + $expire = $this->getExpireTime($expire); + $value = $this->serialize($value); + + if ($this->handler->set($key, $value, $expire)) { + return true; + } + + return false; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc(string $name, int $step = 1) + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + + if ($this->handler->get($key)) { + return $this->handler->increment($key, $step); + } + + return $this->handler->set($key, $step); + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec(string $name, int $step = 1) + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + $value = $this->handler->get($key) - $step; + $res = $this->handler->set($key, $value); + + return !$res ? false : $value; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @param bool|false $ttl + * @return bool + */ + public function delete($name, $ttl = false): bool + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + + return false === $ttl ? + $this->handler->delete($key) : + $this->handler->delete($key, $ttl); + } + + /** + * 清除缓存 + * @access public + * @return bool + */ + public function clear(): bool + { + $this->writeTimes++; + + return $this->handler->flush(); + } + + /** + * 删除缓存标签 + * @access public + * @param array $keys 缓存标识列表 + * @return void + */ + public function clearTag(array $keys): void + { + $this->handler->deleteMulti($keys); + } + +} diff --git a/vendor/topthink/framework/src/think/cache/driver/Redis.php b/vendor/topthink/framework/src/think/cache/driver/Redis.php new file mode 100644 index 0000000..791b27b --- /dev/null +++ b/vendor/topthink/framework/src/think/cache/driver/Redis.php @@ -0,0 +1,249 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\cache\driver; + +use think\cache\Driver; + +/** + * Redis缓存驱动,适合单机部署、有前端代理实现高可用的场景,性能最好 + * 有需要在业务层实现读写分离、或者使用RedisCluster的需求,请使用Redisd驱动 + * + * 要求安装phpredis扩展:https://github.com/nicolasff/phpredis + * @author 尘缘 <130775@qq.com> + */ +class Redis extends Driver +{ + /** @var \Predis\Client|\Redis */ + protected $handler; + + /** + * 配置参数 + * @var array + */ + protected $options = [ + 'host' => '127.0.0.1', + 'port' => 6379, + 'password' => '', + 'select' => 0, + 'timeout' => 0, + 'expire' => 0, + 'persistent' => false, + 'prefix' => '', + 'tag_prefix' => 'tag:', + 'serialize' => [], + ]; + + /** + * 架构函数 + * @access public + * @param array $options 缓存参数 + */ + public function __construct(array $options = []) + { + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + + if (extension_loaded('redis')) { + $this->handler = new \Redis; + + if ($this->options['persistent']) { + $this->handler->pconnect($this->options['host'], (int) $this->options['port'], (int) $this->options['timeout'], 'persistent_id_' . $this->options['select']); + } else { + $this->handler->connect($this->options['host'], (int) $this->options['port'], (int) $this->options['timeout']); + } + + if ('' != $this->options['password']) { + $this->handler->auth($this->options['password']); + } + } elseif (class_exists('\Predis\Client')) { + $params = []; + foreach ($this->options as $key => $val) { + if (in_array($key, ['aggregate', 'cluster', 'connections', 'exceptions', 'prefix', 'profile', 'replication', 'parameters'])) { + $params[$key] = $val; + unset($this->options[$key]); + } + } + + if ('' == $this->options['password']) { + unset($this->options['password']); + } + + $this->handler = new \Predis\Client($this->options, $params); + + $this->options['prefix'] = ''; + } else { + throw new \BadFunctionCallException('not support: redis'); + } + + if (0 != $this->options['select']) { + $this->handler->select((int) $this->options['select']); + } + } + + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name): bool + { + return $this->handler->exists($this->getCacheKey($name)) ? true : false; + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = null) + { + $this->readTimes++; + $key = $this->getCacheKey($name); + $value = $this->handler->get($key); + + if (false === $value || is_null($value)) { + return $default; + } + + return $this->unserialize($value); + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return bool + */ + public function set($name, $value, $expire = null): bool + { + $this->writeTimes++; + + if (is_null($expire)) { + $expire = $this->options['expire']; + } + + $key = $this->getCacheKey($name); + $expire = $this->getExpireTime($expire); + $value = $this->serialize($value); + + if ($expire) { + $this->handler->setex($key, $expire, $value); + } else { + $this->handler->set($key, $value); + } + + return true; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc(string $name, int $step = 1) + { + $this->writeTimes++; + $key = $this->getCacheKey($name); + + return $this->handler->incrby($key, $step); + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec(string $name, int $step = 1) + { + $this->writeTimes++; + $key = $this->getCacheKey($name); + + return $this->handler->decrby($key, $step); + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function delete($name): bool + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + $result = $this->handler->del($key); + return $result > 0; + } + + /** + * 清除缓存 + * @access public + * @return bool + */ + public function clear(): bool + { + $this->writeTimes++; + $this->handler->flushDB(); + return true; + } + + /** + * 删除缓存标签 + * @access public + * @param array $keys 缓存标识列表 + * @return void + */ + public function clearTag(array $keys): void + { + // 指定标签清除 + $this->handler->del($keys); + } + + /** + * 追加TagSet数据 + * @access public + * @param string $name 缓存标识 + * @param mixed $value 数据 + * @return void + */ + public function append(string $name, $value): void + { + $key = $this->getCacheKey($name); + $this->handler->sAdd($key, $value); + } + + /** + * 获取标签包含的缓存标识 + * @access public + * @param string $tag 缓存标签 + * @return array + */ + public function getTagItems(string $tag): array + { + $name = $this->getTagKey($tag); + $key = $this->getCacheKey($name); + return $this->handler->sMembers($key); + } + +} diff --git a/vendor/topthink/framework/src/think/cache/driver/Wincache.php b/vendor/topthink/framework/src/think/cache/driver/Wincache.php new file mode 100644 index 0000000..8b3e8b8 --- /dev/null +++ b/vendor/topthink/framework/src/think/cache/driver/Wincache.php @@ -0,0 +1,175 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\cache\driver; + +use think\cache\Driver; + +/** + * Wincache缓存驱动 + */ +class Wincache extends Driver +{ + /** + * 配置参数 + * @var array + */ + protected $options = [ + 'prefix' => '', + 'expire' => 0, + 'tag_prefix' => 'tag:', + 'serialize' => [], + ]; + + /** + * 架构函数 + * @access public + * @param array $options 缓存参数 + * @throws \BadFunctionCallException + */ + public function __construct(array $options = []) + { + if (!function_exists('wincache_ucache_info')) { + throw new \BadFunctionCallException('not support: WinCache'); + } + + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + } + + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name): bool + { + $this->readTimes++; + + $key = $this->getCacheKey($name); + + return wincache_ucache_exists($key); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = null) + { + $this->readTimes++; + + $key = $this->getCacheKey($name); + + return wincache_ucache_exists($key) ? $this->unserialize(wincache_ucache_get($key)) : $default; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return bool + */ + public function set($name, $value, $expire = null): bool + { + $this->writeTimes++; + + if (is_null($expire)) { + $expire = $this->options['expire']; + } + + $key = $this->getCacheKey($name); + $expire = $this->getExpireTime($expire); + $value = $this->serialize($value); + + if (wincache_ucache_set($key, $value, $expire)) { + return true; + } + + return false; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc(string $name, int $step = 1) + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + + return wincache_ucache_inc($key, $step); + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec(string $name, int $step = 1) + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + + return wincache_ucache_dec($key, $step); + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function delete($name): bool + { + $this->writeTimes++; + + return wincache_ucache_delete($this->getCacheKey($name)); + } + + /** + * 清除缓存 + * @access public + * @return bool + */ + public function clear(): bool + { + $this->writeTimes++; + return wincache_ucache_clear(); + } + + /** + * 删除缓存标签 + * @access public + * @param array $keys 缓存标识列表 + * @return void + */ + public function clearTag(array $keys): void + { + wincache_ucache_delete($keys); + } + +} diff --git a/vendor/topthink/framework/src/think/console/Command.php b/vendor/topthink/framework/src/think/console/Command.php new file mode 100644 index 0000000..bd3fb20 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/Command.php @@ -0,0 +1,504 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\console; + +use Exception; +use InvalidArgumentException; +use LogicException; +use think\App; +use think\Console; +use think\console\input\Argument; +use think\console\input\Definition; +use think\console\input\Option; + +abstract class Command +{ + + /** @var Console */ + private $console; + private $name; + private $processTitle; + private $aliases = []; + private $definition; + private $help; + private $description; + private $ignoreValidationErrors = false; + private $consoleDefinitionMerged = false; + private $consoleDefinitionMergedWithArgs = false; + private $synopsis = []; + private $usages = []; + + /** @var Input */ + protected $input; + + /** @var Output */ + protected $output; + + /** @var App */ + protected $app; + + /** + * 构造方法 + * @throws LogicException + * @api + */ + public function __construct() + { + $this->definition = new Definition(); + + $this->configure(); + + if (!$this->name) { + throw new LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_class($this))); + } + } + + /** + * 忽略验证错误 + */ + public function ignoreValidationErrors(): void + { + $this->ignoreValidationErrors = true; + } + + /** + * 设置控制台 + * @param Console $console + */ + public function setConsole(Console $console = null): void + { + $this->console = $console; + } + + /** + * 获取控制台 + * @return Console + * @api + */ + public function getConsole(): Console + { + return $this->console; + } + + /** + * 设置app + * @param App $app + */ + public function setApp(App $app) + { + $this->app = $app; + } + + /** + * 获取app + * @return App + */ + public function getApp() + { + return $this->app; + } + + /** + * 是否有效 + * @return bool + */ + public function isEnabled(): bool + { + return true; + } + + /** + * 配置指令 + */ + protected function configure() + { + } + + /** + * 执行指令 + * @param Input $input + * @param Output $output + * @return null|int + * @throws LogicException + * @see setCode() + */ + protected function execute(Input $input, Output $output) + { + return $this->app->invoke([$this, 'handle']); + } + + /** + * 用户验证 + * @param Input $input + * @param Output $output + */ + protected function interact(Input $input, Output $output) + { + } + + /** + * 初始化 + * @param Input $input An InputInterface instance + * @param Output $output An OutputInterface instance + */ + protected function initialize(Input $input, Output $output) + { + } + + /** + * 执行 + * @param Input $input + * @param Output $output + * @return int + * @throws Exception + * @see setCode() + * @see execute() + */ + public function run(Input $input, Output $output): int + { + $this->input = $input; + $this->output = $output; + + $this->getSynopsis(true); + $this->getSynopsis(false); + + $this->mergeConsoleDefinition(); + + try { + $input->bind($this->definition); + } catch (Exception $e) { + if (!$this->ignoreValidationErrors) { + throw $e; + } + } + + $this->initialize($input, $output); + + if (null !== $this->processTitle) { + if (function_exists('cli_set_process_title')) { + if (false === @cli_set_process_title($this->processTitle)) { + if ('Darwin' === PHP_OS) { + $output->writeln('Running "cli_get_process_title" as an unprivileged user is not supported on MacOS.'); + } else { + $error = error_get_last(); + trigger_error($error['message'], E_USER_WARNING); + } + } + } elseif (function_exists('setproctitle')) { + setproctitle($this->processTitle); + } elseif (Output::VERBOSITY_VERY_VERBOSE === $output->getVerbosity()) { + $output->writeln('Install the proctitle PECL to be able to change the process title.'); + } + } + + if ($input->isInteractive()) { + $this->interact($input, $output); + } + + $input->validate(); + + $statusCode = $this->execute($input, $output); + + return is_numeric($statusCode) ? (int) $statusCode : 0; + } + + /** + * 合并参数定义 + * @param bool $mergeArgs + */ + public function mergeConsoleDefinition(bool $mergeArgs = true) + { + if (null === $this->console + || (true === $this->consoleDefinitionMerged + && ($this->consoleDefinitionMergedWithArgs || !$mergeArgs)) + ) { + return; + } + + if ($mergeArgs) { + $currentArguments = $this->definition->getArguments(); + $this->definition->setArguments($this->console->getDefinition()->getArguments()); + $this->definition->addArguments($currentArguments); + } + + $this->definition->addOptions($this->console->getDefinition()->getOptions()); + + $this->consoleDefinitionMerged = true; + if ($mergeArgs) { + $this->consoleDefinitionMergedWithArgs = true; + } + } + + /** + * 设置参数定义 + * @param array|Definition $definition + * @return Command + * @api + */ + public function setDefinition($definition) + { + if ($definition instanceof Definition) { + $this->definition = $definition; + } else { + $this->definition->setDefinition($definition); + } + + $this->consoleDefinitionMerged = false; + + return $this; + } + + /** + * 获取参数定义 + * @return Definition + * @api + */ + public function getDefinition(): Definition + { + return $this->definition; + } + + /** + * 获取当前指令的参数定义 + * @return Definition + */ + public function getNativeDefinition(): Definition + { + return $this->getDefinition(); + } + + /** + * 添加参数 + * @param string $name 名称 + * @param int $mode 类型 + * @param string $description 描述 + * @param mixed $default 默认值 + * @return Command + */ + public function addArgument(string $name, int $mode = null, string $description = '', $default = null) + { + $this->definition->addArgument(new Argument($name, $mode, $description, $default)); + + return $this; + } + + /** + * 添加选项 + * @param string $name 选项名称 + * @param string $shortcut 别名 + * @param int $mode 类型 + * @param string $description 描述 + * @param mixed $default 默认值 + * @return Command + */ + public function addOption(string $name, string $shortcut = null, int $mode = null, string $description = '', $default = null) + { + $this->definition->addOption(new Option($name, $shortcut, $mode, $description, $default)); + + return $this; + } + + /** + * 设置指令名称 + * @param string $name + * @return Command + * @throws InvalidArgumentException + */ + public function setName(string $name) + { + $this->validateName($name); + + $this->name = $name; + + return $this; + } + + /** + * 设置进程名称 + * + * PHP 5.5+ or the proctitle PECL library is required + * + * @param string $title The process title + * + * @return $this + */ + public function setProcessTitle($title) + { + $this->processTitle = $title; + + return $this; + } + + /** + * 获取指令名称 + * @return string + */ + public function getName(): string + { + return $this->name ?: ''; + } + + /** + * 设置描述 + * @param string $description + * @return Command + */ + public function setDescription(string $description) + { + $this->description = $description; + + return $this; + } + + /** + * 获取描述 + * @return string + */ + public function getDescription(): string + { + return $this->description ?: ''; + } + + /** + * 设置帮助信息 + * @param string $help + * @return Command + */ + public function setHelp(string $help) + { + $this->help = $help; + + return $this; + } + + /** + * 获取帮助信息 + * @return string + */ + public function getHelp(): string + { + return $this->help ?: ''; + } + + /** + * 描述信息 + * @return string + */ + public function getProcessedHelp(): string + { + $name = $this->name; + + $placeholders = [ + '%command.name%', + '%command.full_name%', + ]; + $replacements = [ + $name, + $_SERVER['PHP_SELF'] . ' ' . $name, + ]; + + return str_replace($placeholders, $replacements, $this->getHelp()); + } + + /** + * 设置别名 + * @param string[] $aliases + * @return Command + * @throws InvalidArgumentException + */ + public function setAliases(iterable $aliases) + { + foreach ($aliases as $alias) { + $this->validateName($alias); + } + + $this->aliases = $aliases; + + return $this; + } + + /** + * 获取别名 + * @return array + */ + public function getAliases(): array + { + return $this->aliases; + } + + /** + * 获取简介 + * @param bool $short 是否简单的 + * @return string + */ + public function getSynopsis(bool $short = false): string + { + $key = $short ? 'short' : 'long'; + + if (!isset($this->synopsis[$key])) { + $this->synopsis[$key] = trim(sprintf('%s %s', $this->name, $this->definition->getSynopsis($short))); + } + + return $this->synopsis[$key]; + } + + /** + * 添加用法介绍 + * @param string $usage + * @return $this + */ + public function addUsage(string $usage) + { + if (0 !== strpos($usage, $this->name)) { + $usage = sprintf('%s %s', $this->name, $usage); + } + + $this->usages[] = $usage; + + return $this; + } + + /** + * 获取用法介绍 + * @return array + */ + public function getUsages(): array + { + return $this->usages; + } + + /** + * 验证指令名称 + * @param string $name + * @throws InvalidArgumentException + */ + private function validateName(string $name) + { + if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) { + throw new InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name)); + } + } + + /** + * 输出表格 + * @param Table $table + * @return string + */ + protected function table(Table $table): string + { + $content = $table->render(); + $this->output->writeln($content); + return $content; + } + +} diff --git a/vendor/topthink/framework/src/think/console/Input.php b/vendor/topthink/framework/src/think/console/Input.php new file mode 100644 index 0000000..9ae9077 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/Input.php @@ -0,0 +1,465 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\console; + +use think\console\input\Argument; +use think\console\input\Definition; +use think\console\input\Option; + +class Input +{ + + /** + * @var Definition + */ + protected $definition; + + /** + * @var Option[] + */ + protected $options = []; + + /** + * @var Argument[] + */ + protected $arguments = []; + + protected $interactive = true; + + private $tokens; + private $parsed; + + public function __construct($argv = null) + { + if (null === $argv) { + $argv = $_SERVER['argv']; + // 去除命令名 + array_shift($argv); + } + + $this->tokens = $argv; + + $this->definition = new Definition(); + } + + protected function setTokens(array $tokens) + { + $this->tokens = $tokens; + } + + /** + * 绑定实例 + * @param Definition $definition A InputDefinition instance + */ + public function bind(Definition $definition): void + { + $this->arguments = []; + $this->options = []; + $this->definition = $definition; + + $this->parse(); + } + + /** + * 解析参数 + */ + protected function parse(): void + { + $parseOptions = true; + $this->parsed = $this->tokens; + while (null !== $token = array_shift($this->parsed)) { + if ($parseOptions && '' == $token) { + $this->parseArgument($token); + } elseif ($parseOptions && '--' == $token) { + $parseOptions = false; + } elseif ($parseOptions && 0 === strpos($token, '--')) { + $this->parseLongOption($token); + } elseif ($parseOptions && '-' === $token[0] && '-' !== $token) { + $this->parseShortOption($token); + } else { + $this->parseArgument($token); + } + } + } + + /** + * 解析短选项 + * @param string $token 当前的指令. + */ + private function parseShortOption(string $token): void + { + $name = substr($token, 1); + + if (strlen($name) > 1) { + if ($this->definition->hasShortcut($name[0]) + && $this->definition->getOptionForShortcut($name[0])->acceptValue() + ) { + $this->addShortOption($name[0], substr($name, 1)); + } else { + $this->parseShortOptionSet($name); + } + } else { + $this->addShortOption($name, null); + } + } + + /** + * 解析短选项 + * @param string $name 当前指令 + * @throws \RuntimeException + */ + private function parseShortOptionSet(string $name): void + { + $len = strlen($name); + for ($i = 0; $i < $len; ++$i) { + if (!$this->definition->hasShortcut($name[$i])) { + throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $name[$i])); + } + + $option = $this->definition->getOptionForShortcut($name[$i]); + if ($option->acceptValue()) { + $this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1)); + + break; + } else { + $this->addLongOption($option->getName(), null); + } + } + } + + /** + * 解析完整选项 + * @param string $token 当前指令 + */ + private function parseLongOption(string $token): void + { + $name = substr($token, 2); + + if (false !== $pos = strpos($name, '=')) { + $this->addLongOption(substr($name, 0, $pos), substr($name, $pos + 1)); + } else { + $this->addLongOption($name, null); + } + } + + /** + * 解析参数 + * @param string $token 当前指令 + * @throws \RuntimeException + */ + private function parseArgument(string $token): void + { + $c = count($this->arguments); + + if ($this->definition->hasArgument($c)) { + $arg = $this->definition->getArgument($c); + + $this->arguments[$arg->getName()] = $arg->isArray() ? [$token] : $token; + + } elseif ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) { + $arg = $this->definition->getArgument($c - 1); + + $this->arguments[$arg->getName()][] = $token; + } else { + throw new \RuntimeException('Too many arguments.'); + } + } + + /** + * 添加一个短选项的值 + * @param string $shortcut 短名称 + * @param mixed $value 值 + * @throws \RuntimeException + */ + private function addShortOption(string $shortcut, $value): void + { + if (!$this->definition->hasShortcut($shortcut)) { + throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut)); + } + + $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value); + } + + /** + * 添加一个完整选项的值 + * @param string $name 选项名 + * @param mixed $value 值 + * @throws \RuntimeException + */ + private function addLongOption(string $name, $value): void + { + if (!$this->definition->hasOption($name)) { + throw new \RuntimeException(sprintf('The "--%s" option does not exist.', $name)); + } + + $option = $this->definition->getOption($name); + + if (false === $value) { + $value = null; + } + + if (null !== $value && !$option->acceptValue()) { + throw new \RuntimeException(sprintf('The "--%s" option does not accept a value.', $name, $value)); + } + + if (null === $value && $option->acceptValue() && count($this->parsed)) { + $next = array_shift($this->parsed); + if (isset($next[0]) && '-' !== $next[0]) { + $value = $next; + } elseif (empty($next)) { + $value = ''; + } else { + array_unshift($this->parsed, $next); + } + } + + if (null === $value) { + if ($option->isValueRequired()) { + throw new \RuntimeException(sprintf('The "--%s" option requires a value.', $name)); + } + + if (!$option->isArray()) { + $value = $option->isValueOptional() ? $option->getDefault() : true; + } + } + + if ($option->isArray()) { + $this->options[$name][] = $value; + } else { + $this->options[$name] = $value; + } + } + + /** + * 获取第一个参数 + * @return string|null + */ + public function getFirstArgument() + { + foreach ($this->tokens as $token) { + if ($token && '-' === $token[0]) { + continue; + } + + return $token; + } + return; + } + + /** + * 检查原始参数是否包含某个值 + * @param string|array $values 需要检查的值 + * @return bool + */ + public function hasParameterOption($values): bool + { + $values = (array) $values; + + foreach ($this->tokens as $token) { + foreach ($values as $value) { + if ($token === $value || 0 === strpos($token, $value . '=')) { + return true; + } + } + } + + return false; + } + + /** + * 获取原始选项的值 + * @param string|array $values 需要检查的值 + * @param mixed $default 默认值 + * @return mixed The option value + */ + public function getParameterOption($values, $default = false) + { + $values = (array) $values; + $tokens = $this->tokens; + + while (0 < count($tokens)) { + $token = array_shift($tokens); + + foreach ($values as $value) { + if ($token === $value || 0 === strpos($token, $value . '=')) { + if (false !== $pos = strpos($token, '=')) { + return substr($token, $pos + 1); + } + + return array_shift($tokens); + } + } + } + + return $default; + } + + /** + * 验证输入 + * @throws \RuntimeException + */ + public function validate() + { + if (count($this->arguments) < $this->definition->getArgumentRequiredCount()) { + throw new \RuntimeException('Not enough arguments.'); + } + } + + /** + * 检查输入是否是交互的 + * @return bool + */ + public function isInteractive(): bool + { + return $this->interactive; + } + + /** + * 设置输入的交互 + * @param bool + */ + public function setInteractive(bool $interactive): void + { + $this->interactive = $interactive; + } + + /** + * 获取所有的参数 + * @return Argument[] + */ + public function getArguments(): array + { + return array_merge($this->definition->getArgumentDefaults(), $this->arguments); + } + + /** + * 根据名称获取参数 + * @param string $name 参数名 + * @return mixed + * @throws \InvalidArgumentException + */ + public function getArgument(string $name) + { + if (!$this->definition->hasArgument($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + return $this->arguments[$name] ?? $this->definition->getArgument($name) + ->getDefault(); + } + + /** + * 设置参数的值 + * @param string $name 参数名 + * @param string $value 值 + * @throws \InvalidArgumentException + */ + public function setArgument(string $name, $value) + { + if (!$this->definition->hasArgument($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + $this->arguments[$name] = $value; + } + + /** + * 检查是否存在某个参数 + * @param string|int $name 参数名或位置 + * @return bool + */ + public function hasArgument($name): bool + { + return $this->definition->hasArgument($name); + } + + /** + * 获取所有的选项 + * @return Option[] + */ + public function getOptions(): array + { + return array_merge($this->definition->getOptionDefaults(), $this->options); + } + + /** + * 获取选项值 + * @param string $name 选项名称 + * @return mixed + * @throws \InvalidArgumentException + */ + public function getOption(string $name) + { + if (!$this->definition->hasOption($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); + } + + return $this->options[$name] ?? $this->definition->getOption($name)->getDefault(); + } + + /** + * 设置选项值 + * @param string $name 选项名 + * @param string|bool $value 值 + * @throws \InvalidArgumentException + */ + public function setOption(string $name, $value): void + { + if (!$this->definition->hasOption($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); + } + + $this->options[$name] = $value; + } + + /** + * 是否有某个选项 + * @param string $name 选项名 + * @return bool + */ + public function hasOption(string $name): bool + { + return $this->definition->hasOption($name) && isset($this->options[$name]); + } + + /** + * 转义指令 + * @param string $token + * @return string + */ + public function escapeToken(string $token): string + { + return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token); + } + + /** + * 返回传递给命令的参数的字符串 + * @return string + */ + public function __toString() + { + $tokens = array_map(function ($token) { + if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) { + return $match[1] . $this->escapeToken($match[2]); + } + + if ($token && '-' !== $token[0]) { + return $this->escapeToken($token); + } + + return $token; + }, $this->tokens); + + return implode(' ', $tokens); + } +} diff --git a/vendor/topthink/framework/src/think/console/LICENSE b/vendor/topthink/framework/src/think/console/LICENSE new file mode 100644 index 0000000..0abe056 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2016 Fabien Potencier + +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. \ No newline at end of file diff --git a/vendor/topthink/framework/src/think/console/Output.php b/vendor/topthink/framework/src/think/console/Output.php new file mode 100644 index 0000000..294c4b8 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/Output.php @@ -0,0 +1,231 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\console; + +use Exception; +use think\console\output\Ask; +use think\console\output\Descriptor; +use think\console\output\driver\Buffer; +use think\console\output\driver\Console; +use think\console\output\driver\Nothing; +use think\console\output\Question; +use think\console\output\question\Choice; +use think\console\output\question\Confirmation; +use Throwable; + +/** + * Class Output + * @package think\console + * + * @see \think\console\output\driver\Console::setDecorated + * @method void setDecorated($decorated) + * + * @see \think\console\output\driver\Buffer::fetch + * @method string fetch() + * + * @method void info($message) + * @method void error($message) + * @method void comment($message) + * @method void warning($message) + * @method void highlight($message) + * @method void question($message) + */ +class Output +{ + // 不显示信息(静默) + const VERBOSITY_QUIET = 0; + // 正常信息 + const VERBOSITY_NORMAL = 1; + // 详细信息 + const VERBOSITY_VERBOSE = 2; + // 非常详细的信息 + const VERBOSITY_VERY_VERBOSE = 3; + // 调试信息 + const VERBOSITY_DEBUG = 4; + + const OUTPUT_NORMAL = 0; + const OUTPUT_RAW = 1; + const OUTPUT_PLAIN = 2; + + // 输出信息级别 + private $verbosity = self::VERBOSITY_NORMAL; + + /** @var Buffer|Console|Nothing */ + private $handle = null; + + protected $styles = [ + 'info', + 'error', + 'comment', + 'question', + 'highlight', + 'warning', + ]; + + public function __construct($driver = 'console') + { + $class = '\\think\\console\\output\\driver\\' . ucwords($driver); + + $this->handle = new $class($this); + } + + public function ask(Input $input, $question, $default = null, $validator = null) + { + $question = new Question($question, $default); + $question->setValidator($validator); + + return $this->askQuestion($input, $question); + } + + public function askHidden(Input $input, $question, $validator = null) + { + $question = new Question($question); + + $question->setHidden(true); + $question->setValidator($validator); + + return $this->askQuestion($input, $question); + } + + public function confirm(Input $input, $question, $default = true) + { + return $this->askQuestion($input, new Confirmation($question, $default)); + } + + /** + * {@inheritdoc} + */ + public function choice(Input $input, $question, array $choices, $default = null) + { + if (null !== $default) { + $values = array_flip($choices); + $default = $values[$default]; + } + + return $this->askQuestion($input, new Choice($question, $choices, $default)); + } + + protected function askQuestion(Input $input, Question $question) + { + $ask = new Ask($input, $this, $question); + $answer = $ask->run(); + + if ($input->isInteractive()) { + $this->newLine(); + } + + return $answer; + } + + protected function block(string $style, string $message): void + { + $this->writeln("<{$style}>{$message}"); + } + + /** + * 输出空行 + * @param int $count + */ + public function newLine(int $count = 1): void + { + $this->write(str_repeat(PHP_EOL, $count)); + } + + /** + * 输出信息并换行 + * @param string $messages + * @param int $type + */ + public function writeln(string $messages, int $type = 0): void + { + $this->write($messages, true, $type); + } + + /** + * 输出信息 + * @param string $messages + * @param bool $newline + * @param int $type + */ + public function write(string $messages, bool $newline = false, int $type = 0): void + { + $this->handle->write($messages, $newline, $type); + } + + public function renderException(Throwable $e): void + { + $this->handle->renderException($e); + } + + /** + * 设置输出信息级别 + * @param int $level 输出信息级别 + */ + public function setVerbosity(int $level) + { + $this->verbosity = $level; + } + + /** + * 获取输出信息级别 + * @return int + */ + public function getVerbosity(): int + { + return $this->verbosity; + } + + public function isQuiet(): bool + { + return self::VERBOSITY_QUIET === $this->verbosity; + } + + public function isVerbose(): bool + { + return self::VERBOSITY_VERBOSE <= $this->verbosity; + } + + public function isVeryVerbose(): bool + { + return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity; + } + + public function isDebug(): bool + { + return self::VERBOSITY_DEBUG <= $this->verbosity; + } + + public function describe($object, array $options = []): void + { + $descriptor = new Descriptor(); + $options = array_merge([ + 'raw_text' => false, + ], $options); + + $descriptor->describe($this, $object, $options); + } + + public function __call($method, $args) + { + if (in_array($method, $this->styles)) { + array_unshift($args, $method); + return call_user_func_array([$this, 'block'], $args); + } + + if ($this->handle && method_exists($this->handle, $method)) { + return call_user_func_array([$this->handle, $method], $args); + } else { + throw new Exception('method not exists:' . __CLASS__ . '->' . $method); + } + } +} diff --git a/vendor/topthink/framework/src/think/console/Table.php b/vendor/topthink/framework/src/think/console/Table.php new file mode 100644 index 0000000..5a861d7 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/Table.php @@ -0,0 +1,300 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\console; + +class Table +{ + const ALIGN_LEFT = 1; + const ALIGN_RIGHT = 0; + const ALIGN_CENTER = 2; + + /** + * 头信息数据 + * @var array + */ + protected $header = []; + + /** + * 头部对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER + * @var int + */ + protected $headerAlign = 1; + + /** + * 表格数据(二维数组) + * @var array + */ + protected $rows = []; + + /** + * 单元格对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER + * @var int + */ + protected $cellAlign = 1; + + /** + * 单元格宽度信息 + * @var array + */ + protected $colWidth = []; + + /** + * 表格输出样式 + * @var string + */ + protected $style = 'default'; + + /** + * 表格样式定义 + * @var array + */ + protected $format = [ + 'compact' => [], + 'default' => [ + 'top' => ['+', '-', '+', '+'], + 'cell' => ['|', ' ', '|', '|'], + 'middle' => ['+', '-', '+', '+'], + 'bottom' => ['+', '-', '+', '+'], + 'cross-top' => ['+', '-', '-', '+'], + 'cross-bottom' => ['+', '-', '-', '+'], + ], + 'markdown' => [ + 'top' => [' ', ' ', ' ', ' '], + 'cell' => ['|', ' ', '|', '|'], + 'middle' => ['|', '-', '|', '|'], + 'bottom' => [' ', ' ', ' ', ' '], + 'cross-top' => ['|', ' ', ' ', '|'], + 'cross-bottom' => ['|', ' ', ' ', '|'], + ], + 'borderless' => [ + 'top' => ['=', '=', ' ', '='], + 'cell' => [' ', ' ', ' ', ' '], + 'middle' => ['=', '=', ' ', '='], + 'bottom' => ['=', '=', ' ', '='], + 'cross-top' => ['=', '=', ' ', '='], + 'cross-bottom' => ['=', '=', ' ', '='], + ], + 'box' => [ + 'top' => ['┌', '─', '┬', '┐'], + 'cell' => ['│', ' ', '│', '│'], + 'middle' => ['├', '─', '┼', '┤'], + 'bottom' => ['└', '─', '┴', '┘'], + 'cross-top' => ['├', '─', '┴', '┤'], + 'cross-bottom' => ['├', '─', '┬', '┤'], + ], + 'box-double' => [ + 'top' => ['╔', '═', '╤', '╗'], + 'cell' => ['║', ' ', '│', '║'], + 'middle' => ['╠', '─', '╪', '╣'], + 'bottom' => ['╚', '═', '╧', '╝'], + 'cross-top' => ['╠', '═', '╧', '╣'], + 'cross-bottom' => ['╠', '═', '╤', '╣'], + ], + ]; + + /** + * 设置表格头信息 以及对齐方式 + * @access public + * @param array $header 要输出的Header信息 + * @param int $align 对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER + * @return void + */ + public function setHeader(array $header, int $align = 1): void + { + $this->header = $header; + $this->headerAlign = $align; + + $this->checkColWidth($header); + } + + /** + * 设置输出表格数据 及对齐方式 + * @access public + * @param array $rows 要输出的表格数据(二维数组) + * @param int $align 对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER + * @return void + */ + public function setRows(array $rows, int $align = 1): void + { + $this->rows = $rows; + $this->cellAlign = $align; + + foreach ($rows as $row) { + $this->checkColWidth($row); + } + } + + /** + * 设置全局单元格对齐方式 + * @param int $align 对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER + * @return $this + */ + public function setCellAlign(int $align = 1) + { + $this->cellAlign = $align; + return $this; + } + + /** + * 检查列数据的显示宽度 + * @access public + * @param mixed $row 行数据 + * @return void + */ + protected function checkColWidth($row): void + { + if (is_array($row)) { + foreach ($row as $key => $cell) { + $width = mb_strwidth((string) $cell); + if (!isset($this->colWidth[$key]) || $width > $this->colWidth[$key]) { + $this->colWidth[$key] = $width; + } + } + } + } + + /** + * 增加一行表格数据 + * @access public + * @param mixed $row 行数据 + * @param bool $first 是否在开头插入 + * @return void + */ + public function addRow($row, bool $first = false): void + { + if ($first) { + array_unshift($this->rows, $row); + } else { + $this->rows[] = $row; + } + + $this->checkColWidth($row); + } + + /** + * 设置输出表格的样式 + * @access public + * @param string $style 样式名 + * @return void + */ + public function setStyle(string $style): void + { + $this->style = isset($this->format[$style]) ? $style : 'default'; + } + + /** + * 输出分隔行 + * @access public + * @param string $pos 位置 + * @return string + */ + protected function renderSeparator(string $pos): string + { + $style = $this->getStyle($pos); + $array = []; + + foreach ($this->colWidth as $width) { + $array[] = str_repeat($style[1], $width + 2); + } + + return $style[0] . implode($style[2], $array) . $style[3] . PHP_EOL; + } + + /** + * 输出表格头部 + * @access public + * @return string + */ + protected function renderHeader(): string + { + $style = $this->getStyle('cell'); + $content = $this->renderSeparator('top'); + + foreach ($this->header as $key => $header) { + $array[] = ' ' . str_pad($header, $this->colWidth[$key], $style[1], $this->headerAlign); + } + + if (!empty($array)) { + $content .= $style[0] . implode(' ' . $style[2], $array) . ' ' . $style[3] . PHP_EOL; + + if (!empty($this->rows)) { + $content .= $this->renderSeparator('middle'); + } + } + + return $content; + } + + protected function getStyle(string $style): array + { + if ($this->format[$this->style]) { + $style = $this->format[$this->style][$style]; + } else { + $style = [' ', ' ', ' ', ' ']; + } + + return $style; + } + + /** + * 输出表格 + * @access public + * @param array $dataList 表格数据 + * @return string + */ + public function render(array $dataList = []): string + { + if (!empty($dataList)) { + $this->setRows($dataList); + } + + // 输出头部 + $content = $this->renderHeader(); + $style = $this->getStyle('cell'); + + if (!empty($this->rows)) { + foreach ($this->rows as $row) { + if (is_string($row) && '-' === $row) { + $content .= $this->renderSeparator('middle'); + } elseif (is_scalar($row)) { + $content .= $this->renderSeparator('cross-top'); + $width = 3 * (count($this->colWidth) - 1) + array_reduce($this->colWidth, function ($a, $b) { + return $a + $b; + }); + $array = str_pad($row, $width); + + $content .= $style[0] . ' ' . $array . ' ' . $style[3] . PHP_EOL; + $content .= $this->renderSeparator('cross-bottom'); + } else { + $array = []; + + foreach ($row as $key => $val) { + $width = $this->colWidth[$key]; + // form https://github.com/symfony/console/blob/20c9821c8d1c2189f287dcee709b2f86353ea08f/Helper/Table.php#L467 + // str_pad won't work properly with multi-byte strings, we need to fix the padding + if (false !== $encoding = mb_detect_encoding((string) $val, null, true)) { + $width += strlen((string) $val) - mb_strwidth((string) $val, $encoding); + } + $array[] = ' ' . str_pad((string) $val, $width, ' ', $this->cellAlign); + } + + $content .= $style[0] . implode(' ' . $style[2], $array) . ' ' . $style[3] . PHP_EOL; + } + } + } + + $content .= $this->renderSeparator('bottom'); + + return $content; + } +} diff --git a/vendor/topthink/framework/src/think/console/bin/README.md b/vendor/topthink/framework/src/think/console/bin/README.md new file mode 100644 index 0000000..9acc52f --- /dev/null +++ b/vendor/topthink/framework/src/think/console/bin/README.md @@ -0,0 +1 @@ +console 工具使用 hiddeninput.exe 在 windows 上隐藏密码输入,该二进制文件由第三方提供,相关源码和其他细节可以在 [Hidden Input](https://github.com/Seldaek/hidden-input) 找到。 diff --git a/vendor/topthink/framework/src/think/console/bin/hiddeninput.exe b/vendor/topthink/framework/src/think/console/bin/hiddeninput.exe new file mode 100644 index 0000000..c8cf65e Binary files /dev/null and b/vendor/topthink/framework/src/think/console/bin/hiddeninput.exe differ diff --git a/vendor/topthink/framework/src/think/console/command/Clear.php b/vendor/topthink/framework/src/think/console/command/Clear.php new file mode 100644 index 0000000..da70b35 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/Clear.php @@ -0,0 +1,85 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\input\Option; +use think\console\Output; + +class Clear extends Command +{ + protected function configure() + { + // 指令配置 + $this->setName('clear') + ->addOption('path', 'd', Option::VALUE_OPTIONAL, 'path to clear', null) + ->addOption('cache', 'c', Option::VALUE_NONE, 'clear cache file') + ->addOption('log', 'l', Option::VALUE_NONE, 'clear log file') + ->addOption('dir', 'r', Option::VALUE_NONE, 'clear empty dir') + ->addOption('expire', 'e', Option::VALUE_NONE, 'clear cache file if cache has expired') + ->setDescription('Clear runtime file'); + } + + protected function execute(Input $input, Output $output) + { + $runtimePath = $this->app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR; + + if ($input->getOption('cache')) { + $path = $runtimePath . 'cache'; + } elseif ($input->getOption('log')) { + $path = $runtimePath . 'log'; + } else { + $path = $input->getOption('path') ?: $runtimePath; + } + + $rmdir = $input->getOption('dir') ? true : false; + // --expire 仅当 --cache 时生效 + $cache_expire = $input->getOption('expire') && $input->getOption('cache') ? true : false; + $this->clear(rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR, $rmdir, $cache_expire); + + $output->writeln("Clear Successed"); + } + + protected function clear(string $path, bool $rmdir, bool $cache_expire): void + { + $files = is_dir($path) ? scandir($path) : []; + + foreach ($files as $file) { + if ('.' != $file && '..' != $file && is_dir($path . $file)) { + $this->clear($path . $file . DIRECTORY_SEPARATOR, $rmdir, $cache_expire); + if ($rmdir) { + @rmdir($path . $file); + } + } elseif ('.gitignore' != $file && is_file($path . $file)) { + if ($cache_expire) { + if ($this->cacheHasExpired($path . $file)) { + unlink($path . $file); + } + } else { + unlink($path . $file); + } + } + } + } + + /** + * 缓存文件是否已过期 + * @param $filename string 文件路径 + * @return bool + */ + protected function cacheHasExpired($filename) { + $content = file_get_contents($filename); + $expire = (int) substr($content, 8, 12); + return 0 != $expire && time() - $expire > filemtime($filename); + } + +} diff --git a/vendor/topthink/framework/src/think/console/command/Help.php b/vendor/topthink/framework/src/think/console/command/Help.php new file mode 100644 index 0000000..2e4f2ca --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/Help.php @@ -0,0 +1,70 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\input\Argument as InputArgument; +use think\console\input\Option as InputOption; +use think\console\Output; + +class Help extends Command +{ + + private $command; + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->ignoreValidationErrors(); + + $this->setName('help')->setDefinition([ + new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'), + new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'), + ])->setDescription('Displays help for a command')->setHelp( + <<%command.name%
        command displays help for a given command: + + php %command.full_name% list + +To display the list of available commands, please use the list command. +EOF + ); + } + + /** + * Sets the command. + * @param Command $command The command to set + */ + public function setCommand(Command $command): void + { + $this->command = $command; + } + + /** + * {@inheritdoc} + */ + protected function execute(Input $input, Output $output) + { + if (null === $this->command) { + $this->command = $this->getConsole()->find($input->getArgument('command_name')); + } + + $output->describe($this->command, [ + 'raw_text' => $input->getOption('raw'), + ]); + + $this->command = null; + } +} diff --git a/vendor/topthink/framework/src/think/console/command/Lists.php b/vendor/topthink/framework/src/think/console/command/Lists.php new file mode 100644 index 0000000..d20fc75 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/Lists.php @@ -0,0 +1,74 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\input\Argument as InputArgument; +use think\console\input\Definition as InputDefinition; +use think\console\input\Option as InputOption; +use think\console\Output; + +class Lists extends Command +{ + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setName('list')->setDefinition($this->createDefinition())->setDescription('Lists commands')->setHelp( + <<%command.name%
        command lists all commands: + + php %command.full_name% + +You can also display the commands for a specific namespace: + + php %command.full_name% test + +It's also possible to get raw list of commands (useful for embedding command runner): + + php %command.full_name% --raw +EOF + ); + } + + /** + * {@inheritdoc} + */ + public function getNativeDefinition(): InputDefinition + { + return $this->createDefinition(); + } + + /** + * {@inheritdoc} + */ + protected function execute(Input $input, Output $output) + { + $output->describe($this->getConsole(), [ + 'raw_text' => $input->getOption('raw'), + 'namespace' => $input->getArgument('namespace'), + ]); + } + + /** + * {@inheritdoc} + */ + private function createDefinition(): InputDefinition + { + return new InputDefinition([ + new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'), + new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'), + ]); + } +} diff --git a/vendor/topthink/framework/src/think/console/command/Make.php b/vendor/topthink/framework/src/think/console/command/Make.php new file mode 100644 index 0000000..662b337 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/Make.php @@ -0,0 +1,99 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\input\Argument; +use think\console\Output; + +abstract class Make extends Command +{ + protected $type; + + abstract protected function getStub(); + + protected function configure() + { + $this->addArgument('name', Argument::REQUIRED, "The name of the class"); + } + + protected function execute(Input $input, Output $output) + { + $name = trim($input->getArgument('name')); + + $classname = $this->getClassName($name); + + $pathname = $this->getPathName($classname); + + if (is_file($pathname)) { + $output->writeln('' . $this->type . ':' . $classname . ' already exists!'); + return false; + } + + if (!is_dir(dirname($pathname))) { + mkdir(dirname($pathname), 0755, true); + } + + file_put_contents($pathname, $this->buildClass($classname)); + + $output->writeln('' . $this->type . ':' . $classname . ' created successfully.'); + } + + protected function buildClass(string $name) + { + $stub = file_get_contents($this->getStub()); + + $namespace = trim(implode('\\', array_slice(explode('\\', $name), 0, -1)), '\\'); + + $class = str_replace($namespace . '\\', '', $name); + + return str_replace(['{%className%}', '{%actionSuffix%}', '{%namespace%}', '{%app_namespace%}'], [ + $class, + $this->app->config->get('route.action_suffix'), + $namespace, + $this->app->getNamespace(), + ], $stub); + } + + protected function getPathName(string $name): string + { + $name = str_replace('app\\', '', $name); + + return $this->app->getBasePath() . ltrim(str_replace('\\', '/', $name), '/') . '.php'; + } + + protected function getClassName(string $name): string + { + if (strpos($name, '\\') !== false) { + return $name; + } + + if (strpos($name, '@')) { + [$app, $name] = explode('@', $name); + } else { + $app = ''; + } + + if (strpos($name, '/') !== false) { + $name = str_replace('/', '\\', $name); + } + + return $this->getNamespace($app) . '\\' . $name; + } + + protected function getNamespace(string $app): string + { + return 'app' . ($app ? '\\' . $app : ''); + } + +} diff --git a/vendor/topthink/framework/src/think/console/command/RouteList.php b/vendor/topthink/framework/src/think/console/command/RouteList.php new file mode 100644 index 0000000..ed579b8 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/RouteList.php @@ -0,0 +1,129 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\input\Argument; +use think\console\input\Option; +use think\console\Output; +use think\console\Table; +use think\event\RouteLoaded; + +class RouteList extends Command +{ + protected $sortBy = [ + 'rule' => 0, + 'route' => 1, + 'method' => 2, + 'name' => 3, + 'domain' => 4, + ]; + + protected function configure() + { + $this->setName('route:list') + ->addArgument('dir', Argument::OPTIONAL, 'dir name .') + ->addArgument('style', Argument::OPTIONAL, "the style of the table.", 'default') + ->addOption('sort', 's', Option::VALUE_OPTIONAL, 'order by rule name.', 0) + ->addOption('more', 'm', Option::VALUE_NONE, 'show route options.') + ->setDescription('show route list.'); + } + + protected function execute(Input $input, Output $output) + { + $dir = $input->getArgument('dir') ?: ''; + + $filename = $this->app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR . ($dir ? $dir . DIRECTORY_SEPARATOR : '') . 'route_list.php'; + + if (is_file($filename)) { + unlink($filename); + } elseif (!is_dir(dirname($filename))) { + mkdir(dirname($filename), 0755); + } + + $content = $this->getRouteList($dir); + file_put_contents($filename, 'Route List' . PHP_EOL . $content); + } + + protected function getRouteList(string $dir = null): string + { + $this->app->route->setTestMode(true); + $this->app->route->clear(); + + if ($dir) { + $path = $this->app->getRootPath() . 'route' . DIRECTORY_SEPARATOR . $dir . DIRECTORY_SEPARATOR; + } else { + $path = $this->app->getRootPath() . 'route' . DIRECTORY_SEPARATOR; + } + + $files = is_dir($path) ? scandir($path) : []; + + foreach ($files as $file) { + if (strpos($file, '.php')) { + include $path . $file; + } + } + + //触发路由载入完成事件 + $this->app->event->trigger(RouteLoaded::class); + + $table = new Table(); + + if ($this->input->hasOption('more')) { + $header = ['Rule', 'Route', 'Method', 'Name', 'Domain', 'Option', 'Pattern']; + } else { + $header = ['Rule', 'Route', 'Method', 'Name']; + } + + $table->setHeader($header); + + $routeList = $this->app->route->getRuleList(); + $rows = []; + + foreach ($routeList as $item) { + $item['route'] = $item['route'] instanceof \Closure ? '' : $item['route']; + + if ($this->input->hasOption('more')) { + $item = [$item['rule'], $item['route'], $item['method'], $item['name'], $item['domain'], json_encode($item['option']), json_encode($item['pattern'])]; + } else { + $item = [$item['rule'], $item['route'], $item['method'], $item['name']]; + } + + $rows[] = $item; + } + + if ($this->input->getOption('sort')) { + $sort = strtolower($this->input->getOption('sort')); + + if (isset($this->sortBy[$sort])) { + $sort = $this->sortBy[$sort]; + } + + uasort($rows, function ($a, $b) use ($sort) { + $itemA = $a[$sort] ?? null; + $itemB = $b[$sort] ?? null; + + return strcasecmp($itemA, $itemB); + }); + } + + $table->setRows($rows); + + if ($this->input->getArgument('style')) { + $style = $this->input->getArgument('style'); + $table->setStyle($style); + } + + return $this->table($table); + } + +} diff --git a/vendor/topthink/framework/src/think/console/command/RunServer.php b/vendor/topthink/framework/src/think/console/command/RunServer.php new file mode 100644 index 0000000..d507c1d --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/RunServer.php @@ -0,0 +1,72 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\input\Option; +use think\console\Output; + +class RunServer extends Command +{ + public function configure() + { + $this->setName('run') + ->addOption( + 'host', + 'H', + Option::VALUE_OPTIONAL, + 'The host to server the application on', + '0.0.0.0' + ) + ->addOption( + 'port', + 'p', + Option::VALUE_OPTIONAL, + 'The port to server the application on', + 8000 + ) + ->addOption( + 'root', + 'r', + Option::VALUE_OPTIONAL, + 'The document root of the application', + '' + ) + ->setDescription('PHP Built-in Server for ThinkPHP'); + } + + public function execute(Input $input, Output $output) + { + $host = $input->getOption('host'); + $port = $input->getOption('port'); + $root = $input->getOption('root'); + if (empty($root)) { + $root = $this->app->getRootPath() . 'public'; + } + + $command = sprintf( + 'php -S %s:%d -t %s %s', + $host, + $port, + escapeshellarg($root), + escapeshellarg($root . DIRECTORY_SEPARATOR . 'router.php') + ); + + $output->writeln(sprintf('ThinkPHP Development server is started On ', $host, $port)); + $output->writeln(sprintf('You can exit with `CTRL-C`')); + $output->writeln(sprintf('Document root is: %s', $root)); + passthru($command); + } + +} diff --git a/vendor/topthink/framework/src/think/console/command/ServiceDiscover.php b/vendor/topthink/framework/src/think/console/command/ServiceDiscover.php new file mode 100644 index 0000000..e90f433 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/ServiceDiscover.php @@ -0,0 +1,52 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\Output; + +class ServiceDiscover extends Command +{ + public function configure() + { + $this->setName('service:discover') + ->setDescription('Discover Services for ThinkPHP'); + } + + public function execute(Input $input, Output $output) + { + if (is_file($path = $this->app->getRootPath() . 'vendor/composer/installed.json')) { + $packages = json_decode(@file_get_contents($path), true); + // Compatibility with Composer 2.0 + if (isset($packages['packages'])) { + $packages = $packages['packages']; + } + + $services = []; + foreach ($packages as $package) { + if (!empty($package['extra']['think']['services'])) { + $services = array_merge($services, (array) $package['extra']['think']['services']); + } + } + + $header = '// This file is automatically generated at:' . date('Y-m-d H:i:s') . PHP_EOL . 'declare (strict_types = 1);' . PHP_EOL; + + $content = 'app->getRootPath() . 'vendor/services.php', $content); + + $output->writeln('Succeed!'); + } + } +} diff --git a/vendor/topthink/framework/src/think/console/command/VendorPublish.php b/vendor/topthink/framework/src/think/console/command/VendorPublish.php new file mode 100644 index 0000000..3998765 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/VendorPublish.php @@ -0,0 +1,69 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\console\command; + +use think\console\Command; +use think\console\input\Option; + +class VendorPublish extends Command +{ + public function configure() + { + $this->setName('vendor:publish') + ->addOption('force', 'f', Option::VALUE_NONE, 'Overwrite any existing files') + ->setDescription('Publish any publishable assets from vendor packages'); + } + + public function handle() + { + + $force = $this->input->getOption('force'); + + if (is_file($path = $this->app->getRootPath() . 'vendor/composer/installed.json')) { + $packages = json_decode(@file_get_contents($path), true); + // Compatibility with Composer 2.0 + if (isset($packages['packages'])) { + $packages = $packages['packages']; + } + foreach ($packages as $package) { + //配置 + $configDir = $this->app->getConfigPath(); + + if (!empty($package['extra']['think']['config'])) { + + $installPath = $this->app->getRootPath() . 'vendor/' . $package['name'] . DIRECTORY_SEPARATOR; + + foreach ((array) $package['extra']['think']['config'] as $name => $file) { + + $target = $configDir . $name . '.php'; + $source = $installPath . $file; + + if (is_file($target) && !$force) { + $this->output->info("File {$target} exist!"); + continue; + } + + if (!is_file($source)) { + $this->output->info("File {$source} not exist!"); + continue; + } + + copy($source, $target); + } + } + } + + $this->output->writeln('Succeed!'); + } + } +} diff --git a/vendor/topthink/framework/src/think/console/command/Version.php b/vendor/topthink/framework/src/think/console/command/Version.php new file mode 100644 index 0000000..beb49d2 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/Version.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\Output; + +class Version extends Command +{ + protected function configure() + { + // 指令配置 + $this->setName('version') + ->setDescription('show thinkphp framework version'); + } + + protected function execute(Input $input, Output $output) + { + $output->writeln('v' . $this->app->version()); + } + +} diff --git a/vendor/topthink/framework/src/think/console/command/make/Command.php b/vendor/topthink/framework/src/think/console/command/make/Command.php new file mode 100644 index 0000000..9549a02 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/make/Command.php @@ -0,0 +1,55 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command\make; + +use think\console\command\Make; +use think\console\input\Argument; + +class Command extends Make +{ + protected $type = "Command"; + + protected function configure() + { + parent::configure(); + $this->setName('make:command') + ->addArgument('commandName', Argument::OPTIONAL, "The name of the command") + ->setDescription('Create a new command class'); + } + + protected function buildClass(string $name): string + { + $commandName = $this->input->getArgument('commandName') ?: strtolower(basename($name)); + $namespace = trim(implode('\\', array_slice(explode('\\', $name), 0, -1)), '\\'); + + $class = str_replace($namespace . '\\', '', $name); + $stub = file_get_contents($this->getStub()); + + return str_replace(['{%commandName%}', '{%className%}', '{%namespace%}', '{%app_namespace%}'], [ + $commandName, + $class, + $namespace, + $this->app->getNamespace(), + ], $stub); + } + + protected function getStub(): string + { + return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'command.stub'; + } + + protected function getNamespace(string $app): string + { + return parent::getNamespace($app) . '\\command'; + } + +} diff --git a/vendor/topthink/framework/src/think/console/command/make/Controller.php b/vendor/topthink/framework/src/think/console/command/make/Controller.php new file mode 100644 index 0000000..4a8d226 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/make/Controller.php @@ -0,0 +1,56 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command\make; + +use think\console\command\Make; +use think\console\input\Option; + +class Controller extends Make +{ + + protected $type = "Controller"; + + protected function configure() + { + parent::configure(); + $this->setName('make:controller') + ->addOption('api', null, Option::VALUE_NONE, 'Generate an api controller class.') + ->addOption('plain', null, Option::VALUE_NONE, 'Generate an empty controller class.') + ->setDescription('Create a new resource controller class'); + } + + protected function getStub(): string + { + $stubPath = __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR; + + if ($this->input->getOption('api')) { + return $stubPath . 'controller.api.stub'; + } + + if ($this->input->getOption('plain')) { + return $stubPath . 'controller.plain.stub'; + } + + return $stubPath . 'controller.stub'; + } + + protected function getClassName(string $name): string + { + return parent::getClassName($name) . ($this->app->config->get('route.controller_suffix') ? 'Controller' : ''); + } + + protected function getNamespace(string $app): string + { + return parent::getNamespace($app) . '\\controller'; + } + +} diff --git a/vendor/topthink/framework/src/think/console/command/make/Event.php b/vendor/topthink/framework/src/think/console/command/make/Event.php new file mode 100644 index 0000000..6b16689 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/make/Event.php @@ -0,0 +1,35 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command\make; + +use think\console\command\Make; + +class Event extends Make +{ + protected $type = "Event"; + + protected function configure() + { + parent::configure(); + $this->setName('make:event') + ->setDescription('Create a new event class'); + } + + protected function getStub(): string + { + return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'event.stub'; + } + + protected function getNamespace(string $app): string + { + return parent::getNamespace($app) . '\\event'; + } +} diff --git a/vendor/topthink/framework/src/think/console/command/make/Listener.php b/vendor/topthink/framework/src/think/console/command/make/Listener.php new file mode 100644 index 0000000..5c92673 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/make/Listener.php @@ -0,0 +1,35 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command\make; + +use think\console\command\Make; + +class Listener extends Make +{ + protected $type = "Listener"; + + protected function configure() + { + parent::configure(); + $this->setName('make:listener') + ->setDescription('Create a new listener class'); + } + + protected function getStub(): string + { + return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'listener.stub'; + } + + protected function getNamespace(string $app): string + { + return parent::getNamespace($app) . '\\listener'; + } +} diff --git a/vendor/topthink/framework/src/think/console/command/make/Middleware.php b/vendor/topthink/framework/src/think/console/command/make/Middleware.php new file mode 100644 index 0000000..3b68b4a --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/make/Middleware.php @@ -0,0 +1,36 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command\make; + +use think\console\command\Make; + +class Middleware extends Make +{ + protected $type = "Middleware"; + + protected function configure() + { + parent::configure(); + $this->setName('make:middleware') + ->setDescription('Create a new middleware class'); + } + + protected function getStub(): string + { + return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'middleware.stub'; + } + + protected function getNamespace(string $app): string + { + return parent::getNamespace($app) . '\\middleware'; + } +} diff --git a/vendor/topthink/framework/src/think/console/command/make/Model.php b/vendor/topthink/framework/src/think/console/command/make/Model.php new file mode 100644 index 0000000..cb7a23c --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/make/Model.php @@ -0,0 +1,36 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command\make; + +use think\console\command\Make; + +class Model extends Make +{ + protected $type = "Model"; + + protected function configure() + { + parent::configure(); + $this->setName('make:model') + ->setDescription('Create a new model class'); + } + + protected function getStub(): string + { + return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'model.stub'; + } + + protected function getNamespace(string $app): string + { + return parent::getNamespace($app) . '\\model'; + } +} diff --git a/vendor/topthink/framework/src/think/console/command/make/Service.php b/vendor/topthink/framework/src/think/console/command/make/Service.php new file mode 100644 index 0000000..c4bbaa0 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/make/Service.php @@ -0,0 +1,36 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command\make; + +use think\console\command\Make; + +class Service extends Make +{ + protected $type = "Service"; + + protected function configure() + { + parent::configure(); + $this->setName('make:service') + ->setDescription('Create a new Service class'); + } + + protected function getStub(): string + { + return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'service.stub'; + } + + protected function getNamespace(string $app): string + { + return parent::getNamespace($app) . '\\service'; + } +} diff --git a/vendor/topthink/framework/src/think/console/command/make/Subscribe.php b/vendor/topthink/framework/src/think/console/command/make/Subscribe.php new file mode 100644 index 0000000..a1dc2a8 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/make/Subscribe.php @@ -0,0 +1,35 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command\make; + +use think\console\command\Make; + +class Subscribe extends Make +{ + protected $type = "Subscribe"; + + protected function configure() + { + parent::configure(); + $this->setName('make:subscribe') + ->setDescription('Create a new subscribe class'); + } + + protected function getStub(): string + { + return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'subscribe.stub'; + } + + protected function getNamespace(string $app): string + { + return parent::getNamespace($app) . '\\subscribe'; + } +} diff --git a/vendor/topthink/framework/src/think/console/command/make/Validate.php b/vendor/topthink/framework/src/think/console/command/make/Validate.php new file mode 100644 index 0000000..8d36431 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/make/Validate.php @@ -0,0 +1,39 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command\make; + +use think\console\command\Make; + +class Validate extends Make +{ + protected $type = "Validate"; + + protected function configure() + { + parent::configure(); + $this->setName('make:validate') + ->setDescription('Create a validate class'); + } + + protected function getStub(): string + { + $stubPath = __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR; + + return $stubPath . 'validate.stub'; + } + + protected function getNamespace(string $app): string + { + return parent::getNamespace($app) . '\\validate'; + } + +} diff --git a/vendor/topthink/framework/src/think/console/command/make/stubs/command.stub b/vendor/topthink/framework/src/think/console/command/make/stubs/command.stub new file mode 100644 index 0000000..3ee2b1c --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/make/stubs/command.stub @@ -0,0 +1,26 @@ +setName('{%commandName%}') + ->setDescription('the {%commandName%} command'); + } + + protected function execute(Input $input, Output $output) + { + // 指令输出 + $output->writeln('{%commandName%}'); + } +} diff --git a/vendor/topthink/framework/src/think/console/command/make/stubs/controller.api.stub b/vendor/topthink/framework/src/think/console/command/make/stubs/controller.api.stub new file mode 100644 index 0000000..5d3383d --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/make/stubs/controller.api.stub @@ -0,0 +1,64 @@ + ['规则1','规则2'...] + * + * @var array + */ + protected $rule = []; + + /** + * 定义错误信息 + * 格式:'字段名.规则名' => '错误信息' + * + * @var array + */ + protected $message = []; +} diff --git a/vendor/topthink/framework/src/think/console/command/optimize/Route.php b/vendor/topthink/framework/src/think/console/command/optimize/Route.php new file mode 100644 index 0000000..56f7f5a --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/optimize/Route.php @@ -0,0 +1,66 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command\optimize; + +use think\console\Command; +use think\console\Input; +use think\console\input\Argument; +use think\console\Output; +use think\event\RouteLoaded; + +class Route extends Command +{ + protected function configure() + { + $this->setName('optimize:route') + ->addArgument('dir', Argument::OPTIONAL, 'dir name .') + ->setDescription('Build app route cache.'); + } + + protected function execute(Input $input, Output $output) + { + $dir = $input->getArgument('dir') ?: ''; + + $path = $this->app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR . ($dir ? $dir . DIRECTORY_SEPARATOR : ''); + + $filename = $path . 'route.php'; + if (is_file($filename)) { + unlink($filename); + } + + file_put_contents($filename, $this->buildRouteCache($dir)); + $output->writeln('Succeed!'); + } + + protected function buildRouteCache(string $dir = null): string + { + $this->app->route->clear(); + $this->app->route->lazy(false); + + // 路由检测 + $path = $this->app->getRootPath() . ($dir ? 'app' . DIRECTORY_SEPARATOR . $dir . DIRECTORY_SEPARATOR : '') . 'route' . DIRECTORY_SEPARATOR; + + $files = is_dir($path) ? scandir($path) : []; + + foreach ($files as $file) { + if (strpos($file, '.php')) { + include $path . $file; + } + } + + //触发路由载入完成事件 + $this->app->event->trigger(RouteLoaded::class); + $rules = $this->app->route->getName(); + + return ' +// +---------------------------------------------------------------------- +namespace think\console\command\optimize; + +use Exception; +use think\console\Command; +use think\console\Input; +use think\console\input\Argument; +use think\console\input\Option; +use think\console\Output; +use think\db\PDOConnection; + +class Schema extends Command +{ + protected function configure() + { + $this->setName('optimize:schema') + ->addArgument('dir', Argument::OPTIONAL, 'dir name .') + ->addOption('connection', null, Option::VALUE_REQUIRED, 'connection name .') + ->addOption('table', null, Option::VALUE_REQUIRED, 'table name .') + ->setDescription('Build database schema cache.'); + } + + protected function execute(Input $input, Output $output) + { + $dir = $input->getArgument('dir') ?: ''; + + if ($input->hasOption('table')) { + $connection = $this->app->db->connect($input->getOption('connection')); + if (!$connection instanceof PDOConnection) { + $output->error("only PDO connection support schema cache!"); + return; + } + $table = $input->getOption('table'); + if (false === strpos($table, '.')) { + $dbName = $connection->getConfig('database'); + } else { + [$dbName, $table] = explode('.', $table); + } + + if ($table == '*') { + $table = $connection->getTables($dbName); + } + + $this->buildDataBaseSchema($connection, (array) $table, $dbName); + } else { + if ($dir) { + $appPath = $this->app->getBasePath() . $dir . DIRECTORY_SEPARATOR; + $namespace = 'app\\' . $dir; + } else { + $appPath = $this->app->getBasePath(); + $namespace = 'app'; + } + + $path = $appPath . 'model'; + $list = is_dir($path) ? scandir($path) : []; + + foreach ($list as $file) { + if (0 === strpos($file, '.')) { + continue; + } + $class = '\\' . $namespace . '\\model\\' . pathinfo($file, PATHINFO_FILENAME); + $this->buildModelSchema($class); + } + } + + $output->writeln('Succeed!'); + } + + protected function buildModelSchema(string $class): void + { + $reflect = new \ReflectionClass($class); + if (!$reflect->isAbstract() && $reflect->isSubclassOf('\think\Model')) { + try { + /** @var \think\Model $model */ + $model = new $class; + $connection = $model->db()->getConnection(); + if ($connection instanceof PDOConnection) { + $table = $model->getTable(); + //预读字段信息 + $connection->getSchemaInfo($table, true); + } + } catch (Exception $e) { + + } + } + } + + protected function buildDataBaseSchema(PDOConnection $connection, array $tables, string $dbName): void + { + foreach ($tables as $table) { + //预读字段信息 + $connection->getSchemaInfo("{$dbName}.{$table}", true); + } + } +} diff --git a/vendor/topthink/framework/src/think/console/input/Argument.php b/vendor/topthink/framework/src/think/console/input/Argument.php new file mode 100644 index 0000000..86cca36 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/input/Argument.php @@ -0,0 +1,138 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\input; + +class Argument +{ + // 必传参数 + const REQUIRED = 1; + + // 可选参数 + const OPTIONAL = 2; + + // 数组参数 + const IS_ARRAY = 4; + + /** + * 参数名 + * @var string + */ + private $name; + + /** + * 参数类型 + * @var int + */ + private $mode; + + /** + * 参数默认值 + * @var mixed + */ + private $default; + + /** + * 参数描述 + * @var string + */ + private $description; + + /** + * 构造方法 + * @param string $name 参数名 + * @param int $mode 参数类型: self::REQUIRED 或者 self::OPTIONAL + * @param string $description 描述 + * @param mixed $default 默认值 (仅 self::OPTIONAL 类型有效) + * @throws \InvalidArgumentException + */ + public function __construct(string $name, int $mode = null, string $description = '', $default = null) + { + if (null === $mode) { + $mode = self::OPTIONAL; + } elseif (!is_int($mode) || $mode > 7 || $mode < 1) { + throw new \InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode)); + } + + $this->name = $name; + $this->mode = $mode; + $this->description = $description; + + $this->setDefault($default); + } + + /** + * 获取参数名 + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * 是否必须 + * @return bool + */ + public function isRequired(): bool + { + return self::REQUIRED === (self::REQUIRED & $this->mode); + } + + /** + * 该参数是否接受数组 + * @return bool + */ + public function isArray(): bool + { + return self::IS_ARRAY === (self::IS_ARRAY & $this->mode); + } + + /** + * 设置默认值 + * @param mixed $default 默认值 + * @throws \LogicException + */ + public function setDefault($default = null): void + { + if (self::REQUIRED === $this->mode && null !== $default) { + throw new \LogicException('Cannot set a default value except for InputArgument::OPTIONAL mode.'); + } + + if ($this->isArray()) { + if (null === $default) { + $default = []; + } elseif (!is_array($default)) { + throw new \LogicException('A default value for an array argument must be an array.'); + } + } + + $this->default = $default; + } + + /** + * 获取默认值 + * @return mixed + */ + public function getDefault() + { + return $this->default; + } + + /** + * 获取描述 + * @return string + */ + public function getDescription(): string + { + return $this->description; + } +} diff --git a/vendor/topthink/framework/src/think/console/input/Definition.php b/vendor/topthink/framework/src/think/console/input/Definition.php new file mode 100644 index 0000000..ccf02a0 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/input/Definition.php @@ -0,0 +1,375 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\input; + +class Definition +{ + + /** + * @var Argument[] + */ + private $arguments; + + private $requiredCount; + private $hasAnArrayArgument = false; + private $hasOptional; + + /** + * @var Option[] + */ + private $options; + private $shortcuts; + + /** + * 构造方法 + * @param array $definition + * @api + */ + public function __construct(array $definition = []) + { + $this->setDefinition($definition); + } + + /** + * 设置指令的定义 + * @param array $definition 定义的数组 + */ + public function setDefinition(array $definition): void + { + $arguments = []; + $options = []; + foreach ($definition as $item) { + if ($item instanceof Option) { + $options[] = $item; + } else { + $arguments[] = $item; + } + } + + $this->setArguments($arguments); + $this->setOptions($options); + } + + /** + * 设置参数 + * @param Argument[] $arguments 参数数组 + */ + public function setArguments(array $arguments = []): void + { + $this->arguments = []; + $this->requiredCount = 0; + $this->hasOptional = false; + $this->hasAnArrayArgument = false; + $this->addArguments($arguments); + } + + /** + * 添加参数 + * @param Argument[] $arguments 参数数组 + * @api + */ + public function addArguments(array $arguments = []): void + { + if (null !== $arguments) { + foreach ($arguments as $argument) { + $this->addArgument($argument); + } + } + } + + /** + * 添加一个参数 + * @param Argument $argument 参数 + * @throws \LogicException + */ + public function addArgument(Argument $argument): void + { + if (isset($this->arguments[$argument->getName()])) { + throw new \LogicException(sprintf('An argument with name "%s" already exists.', $argument->getName())); + } + + if ($this->hasAnArrayArgument) { + throw new \LogicException('Cannot add an argument after an array argument.'); + } + + if ($argument->isRequired() && $this->hasOptional) { + throw new \LogicException('Cannot add a required argument after an optional one.'); + } + + if ($argument->isArray()) { + $this->hasAnArrayArgument = true; + } + + if ($argument->isRequired()) { + ++$this->requiredCount; + } else { + $this->hasOptional = true; + } + + $this->arguments[$argument->getName()] = $argument; + } + + /** + * 根据名称或者位置获取参数 + * @param string|int $name 参数名或者位置 + * @return Argument 参数 + * @throws \InvalidArgumentException + */ + public function getArgument($name): Argument + { + if (!$this->hasArgument($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments; + + return $arguments[$name]; + } + + /** + * 根据名称或位置检查是否具有某个参数 + * @param string|int $name 参数名或者位置 + * @return bool + * @api + */ + public function hasArgument($name): bool + { + $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments; + + return isset($arguments[$name]); + } + + /** + * 获取所有的参数 + * @return Argument[] 参数数组 + */ + public function getArguments(): array + { + return $this->arguments; + } + + /** + * 获取参数数量 + * @return int + */ + public function getArgumentCount(): int + { + return $this->hasAnArrayArgument ? PHP_INT_MAX : count($this->arguments); + } + + /** + * 获取必填的参数的数量 + * @return int + */ + public function getArgumentRequiredCount(): int + { + return $this->requiredCount; + } + + /** + * 获取参数默认值 + * @return array + */ + public function getArgumentDefaults(): array + { + $values = []; + foreach ($this->arguments as $argument) { + $values[$argument->getName()] = $argument->getDefault(); + } + + return $values; + } + + /** + * 设置选项 + * @param Option[] $options 选项数组 + */ + public function setOptions(array $options = []): void + { + $this->options = []; + $this->shortcuts = []; + $this->addOptions($options); + } + + /** + * 添加选项 + * @param Option[] $options 选项数组 + * @api + */ + public function addOptions(array $options = []): void + { + foreach ($options as $option) { + $this->addOption($option); + } + } + + /** + * 添加一个选项 + * @param Option $option 选项 + * @throws \LogicException + * @api + */ + public function addOption(Option $option): void + { + if (isset($this->options[$option->getName()]) && !$option->equals($this->options[$option->getName()])) { + throw new \LogicException(sprintf('An option named "%s" already exists.', $option->getName())); + } + + if ($option->getShortcut()) { + foreach (explode('|', $option->getShortcut()) as $shortcut) { + if (isset($this->shortcuts[$shortcut]) + && !$option->equals($this->options[$this->shortcuts[$shortcut]]) + ) { + throw new \LogicException(sprintf('An option with shortcut "%s" already exists.', $shortcut)); + } + } + } + + $this->options[$option->getName()] = $option; + if ($option->getShortcut()) { + foreach (explode('|', $option->getShortcut()) as $shortcut) { + $this->shortcuts[$shortcut] = $option->getName(); + } + } + } + + /** + * 根据名称获取选项 + * @param string $name 选项名 + * @return Option + * @throws \InvalidArgumentException + * @api + */ + public function getOption(string $name): Option + { + if (!$this->hasOption($name)) { + throw new \InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name)); + } + + return $this->options[$name]; + } + + /** + * 根据名称检查是否有这个选项 + * @param string $name 选项名 + * @return bool + * @api + */ + public function hasOption(string $name): bool + { + return isset($this->options[$name]); + } + + /** + * 获取所有选项 + * @return Option[] + * @api + */ + public function getOptions(): array + { + return $this->options; + } + + /** + * 根据名称检查某个选项是否有短名称 + * @param string $name 短名称 + * @return bool + */ + public function hasShortcut(string $name): bool + { + return isset($this->shortcuts[$name]); + } + + /** + * 根据短名称获取选项 + * @param string $shortcut 短名称 + * @return Option + */ + public function getOptionForShortcut(string $shortcut): Option + { + return $this->getOption($this->shortcutToName($shortcut)); + } + + /** + * 获取所有选项的默认值 + * @return array + */ + public function getOptionDefaults(): array + { + $values = []; + foreach ($this->options as $option) { + $values[$option->getName()] = $option->getDefault(); + } + + return $values; + } + + /** + * 根据短名称获取选项名 + * @param string $shortcut 短名称 + * @return string + * @throws \InvalidArgumentException + */ + private function shortcutToName(string $shortcut): string + { + if (!isset($this->shortcuts[$shortcut])) { + throw new \InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut)); + } + + return $this->shortcuts[$shortcut]; + } + + /** + * 获取该指令的介绍 + * @param bool $short 是否简洁介绍 + * @return string + */ + public function getSynopsis(bool $short = false): string + { + $elements = []; + + if ($short && $this->getOptions()) { + $elements[] = '[options]'; + } elseif (!$short) { + foreach ($this->getOptions() as $option) { + $value = ''; + if ($option->acceptValue()) { + $value = sprintf(' %s%s%s', $option->isValueOptional() ? '[' : '', strtoupper($option->getName()), $option->isValueOptional() ? ']' : ''); + } + + $shortcut = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : ''; + $elements[] = sprintf('[%s--%s%s]', $shortcut, $option->getName(), $value); + } + } + + if (count($elements) && $this->getArguments()) { + $elements[] = '[--]'; + } + + foreach ($this->getArguments() as $argument) { + $element = '<' . $argument->getName() . '>'; + if (!$argument->isRequired()) { + $element = '[' . $element . ']'; + } elseif ($argument->isArray()) { + $element .= ' (' . $element . ')'; + } + + if ($argument->isArray()) { + $element .= '...'; + } + + $elements[] = $element; + } + + return implode(' ', $elements); + } +} diff --git a/vendor/topthink/framework/src/think/console/input/Option.php b/vendor/topthink/framework/src/think/console/input/Option.php new file mode 100644 index 0000000..19c7e1e --- /dev/null +++ b/vendor/topthink/framework/src/think/console/input/Option.php @@ -0,0 +1,221 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\input; + +/** + * 命令行选项 + * @package think\console\input + */ +class Option +{ + // 无需传值 + const VALUE_NONE = 1; + // 必须传值 + const VALUE_REQUIRED = 2; + // 可选传值 + const VALUE_OPTIONAL = 4; + // 传数组值 + const VALUE_IS_ARRAY = 8; + + /** + * 选项名 + * @var string + */ + private $name; + + /** + * 选项短名称 + * @var string + */ + private $shortcut; + + /** + * 选项类型 + * @var int + */ + private $mode; + + /** + * 选项默认值 + * @var mixed + */ + private $default; + + /** + * 选项描述 + * @var string + */ + private $description; + + /** + * 构造方法 + * @param string $name 选项名 + * @param string|array $shortcut 短名称,多个用|隔开或者使用数组 + * @param int $mode 选项类型(可选类型为 self::VALUE_*) + * @param string $description 描述 + * @param mixed $default 默认值 (类型为 self::VALUE_REQUIRED 或者 self::VALUE_NONE 的时候必须为null) + * @throws \InvalidArgumentException + */ + public function __construct($name, $shortcut = null, $mode = null, $description = '', $default = null) + { + if (0 === strpos($name, '--')) { + $name = substr($name, 2); + } + + if (empty($name)) { + throw new \InvalidArgumentException('An option name cannot be empty.'); + } + + if (empty($shortcut)) { + $shortcut = null; + } + + if (null !== $shortcut) { + if (is_array($shortcut)) { + $shortcut = implode('|', $shortcut); + } + $shortcuts = preg_split('{(\|)-?}', ltrim($shortcut, '-')); + $shortcuts = array_filter($shortcuts); + $shortcut = implode('|', $shortcuts); + + if (empty($shortcut)) { + throw new \InvalidArgumentException('An option shortcut cannot be empty.'); + } + } + + if (null === $mode) { + $mode = self::VALUE_NONE; + } elseif (!is_int($mode) || $mode > 15 || $mode < 1) { + throw new \InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode)); + } + + $this->name = $name; + $this->shortcut = $shortcut; + $this->mode = $mode; + $this->description = $description; + + if ($this->isArray() && !$this->acceptValue()) { + throw new \InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.'); + } + + $this->setDefault($default); + } + + /** + * 获取短名称 + * @return string + */ + public function getShortcut() + { + return $this->shortcut; + } + + /** + * 获取选项名 + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * 是否可以设置值 + * @return bool 类型不是 self::VALUE_NONE 的时候返回true,其他均返回false + */ + public function acceptValue() + { + return $this->isValueRequired() || $this->isValueOptional(); + } + + /** + * 是否必须 + * @return bool 类型是 self::VALUE_REQUIRED 的时候返回true,其他均返回false + */ + public function isValueRequired() + { + return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode); + } + + /** + * 是否可选 + * @return bool 类型是 self::VALUE_OPTIONAL 的时候返回true,其他均返回false + */ + public function isValueOptional() + { + return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode); + } + + /** + * 选项值是否接受数组 + * @return bool 类型是 self::VALUE_IS_ARRAY 的时候返回true,其他均返回false + */ + public function isArray() + { + return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode); + } + + /** + * 设置默认值 + * @param mixed $default 默认值 + * @throws \LogicException + */ + public function setDefault($default = null) + { + if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) { + throw new \LogicException('Cannot set a default value when using InputOption::VALUE_NONE mode.'); + } + + if ($this->isArray()) { + if (null === $default) { + $default = []; + } elseif (!is_array($default)) { + throw new \LogicException('A default value for an array option must be an array.'); + } + } + + $this->default = $this->acceptValue() ? $default : false; + } + + /** + * 获取默认值 + * @return mixed + */ + public function getDefault() + { + return $this->default; + } + + /** + * 获取描述文字 + * @return string + */ + public function getDescription() + { + return $this->description; + } + + /** + * 检查所给选项是否是当前这个 + * @param Option $option + * @return bool + */ + public function equals(Option $option) + { + return $option->getName() === $this->getName() + && $option->getShortcut() === $this->getShortcut() + && $option->getDefault() === $this->getDefault() + && $option->isArray() === $this->isArray() + && $option->isValueRequired() === $this->isValueRequired() + && $option->isValueOptional() === $this->isValueOptional(); + } +} diff --git a/vendor/topthink/framework/src/think/console/output/Ask.php b/vendor/topthink/framework/src/think/console/output/Ask.php new file mode 100644 index 0000000..56821c7 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/output/Ask.php @@ -0,0 +1,336 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output; + +use think\console\Input; +use think\console\Output; +use think\console\output\question\Choice; +use think\console\output\question\Confirmation; + +class Ask +{ + private static $stty; + + private static $shell; + + /** @var Input */ + protected $input; + + /** @var Output */ + protected $output; + + /** @var Question */ + protected $question; + + public function __construct(Input $input, Output $output, Question $question) + { + $this->input = $input; + $this->output = $output; + $this->question = $question; + } + + public function run() + { + if (!$this->input->isInteractive()) { + return $this->question->getDefault(); + } + + if (!$this->question->getValidator()) { + return $this->doAsk(); + } + + $that = $this; + + $interviewer = function () use ($that) { + return $that->doAsk(); + }; + + return $this->validateAttempts($interviewer); + } + + protected function doAsk() + { + $this->writePrompt(); + + $inputStream = STDIN; + $autocomplete = $this->question->getAutocompleterValues(); + + if (null === $autocomplete || !$this->hasSttyAvailable()) { + $ret = false; + if ($this->question->isHidden()) { + try { + $ret = trim($this->getHiddenResponse($inputStream)); + } catch (\RuntimeException $e) { + if (!$this->question->isHiddenFallback()) { + throw $e; + } + } + } + + if (false === $ret) { + $ret = fgets($inputStream, 4096); + if (false === $ret) { + throw new \RuntimeException('Aborted'); + } + $ret = trim($ret); + } + } else { + $ret = trim($this->autocomplete($inputStream)); + } + + $ret = strlen($ret) > 0 ? $ret : $this->question->getDefault(); + + if ($normalizer = $this->question->getNormalizer()) { + return $normalizer($ret); + } + + return $ret; + } + + private function autocomplete($inputStream) + { + $autocomplete = $this->question->getAutocompleterValues(); + $ret = ''; + + $i = 0; + $ofs = -1; + $matches = $autocomplete; + $numMatches = count($matches); + + $sttyMode = shell_exec('stty -g'); + + shell_exec('stty -icanon -echo'); + + while (!feof($inputStream)) { + $c = fread($inputStream, 1); + + if ("\177" === $c) { + if (0 === $numMatches && 0 !== $i) { + --$i; + $this->output->write("\033[1D"); + } + + if ($i === 0) { + $ofs = -1; + $matches = $autocomplete; + $numMatches = count($matches); + } else { + $numMatches = 0; + } + + $ret = substr($ret, 0, $i); + } elseif ("\033" === $c) { + $c .= fread($inputStream, 2); + + if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) { + if ('A' === $c[2] && -1 === $ofs) { + $ofs = 0; + } + + if (0 === $numMatches) { + continue; + } + + $ofs += ('A' === $c[2]) ? -1 : 1; + $ofs = ($numMatches + $ofs) % $numMatches; + } + } elseif (ord($c) < 32) { + if ("\t" === $c || "\n" === $c) { + if ($numMatches > 0 && -1 !== $ofs) { + $ret = $matches[$ofs]; + $this->output->write(substr($ret, $i)); + $i = strlen($ret); + } + + if ("\n" === $c) { + $this->output->write($c); + break; + } + + $numMatches = 0; + } + + continue; + } else { + $this->output->write($c); + $ret .= $c; + ++$i; + + $numMatches = 0; + $ofs = 0; + + foreach ($autocomplete as $value) { + if (0 === strpos($value, $ret) && $i !== strlen($value)) { + $matches[$numMatches++] = $value; + } + } + } + + $this->output->write("\033[K"); + + if ($numMatches > 0 && -1 !== $ofs) { + $this->output->write("\0337"); + $this->output->highlight(substr($matches[$ofs], $i)); + $this->output->write("\0338"); + } + } + + shell_exec(sprintf('stty %s', $sttyMode)); + + return $ret; + } + + protected function getHiddenResponse($inputStream) + { + if ('\\' === DIRECTORY_SEPARATOR) { + $exe = __DIR__ . '/../bin/hiddeninput.exe'; + + $value = rtrim(shell_exec($exe)); + $this->output->writeln(''); + + return $value; + } + + if ($this->hasSttyAvailable()) { + $sttyMode = shell_exec('stty -g'); + + shell_exec('stty -echo'); + $value = fgets($inputStream, 4096); + shell_exec(sprintf('stty %s', $sttyMode)); + + if (false === $value) { + throw new \RuntimeException('Aborted'); + } + + $value = trim($value); + $this->output->writeln(''); + + return $value; + } + + if (false !== $shell = $this->getShell()) { + $readCmd = $shell === 'csh' ? 'set mypassword = $<' : 'read -r mypassword'; + $command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword'", $shell, $readCmd); + $value = rtrim(shell_exec($command)); + $this->output->writeln(''); + + return $value; + } + + throw new \RuntimeException('Unable to hide the response.'); + } + + protected function validateAttempts($interviewer) + { + /** @var \Exception $error */ + $error = null; + $attempts = $this->question->getMaxAttempts(); + while (null === $attempts || $attempts--) { + if (null !== $error) { + $this->output->error($error->getMessage()); + } + + try { + return call_user_func($this->question->getValidator(), $interviewer()); + } catch (\Exception $error) { + } + } + + throw $error; + } + + /** + * 显示问题的提示信息 + */ + protected function writePrompt() + { + $text = $this->question->getQuestion(); + $default = $this->question->getDefault(); + + switch (true) { + case null === $default: + $text = sprintf(' %s:', $text); + + break; + + case $this->question instanceof Confirmation: + $text = sprintf(' %s (yes/no) [%s]:', $text, $default ? 'yes' : 'no'); + + break; + + case $this->question instanceof Choice && $this->question->isMultiselect(): + $choices = $this->question->getChoices(); + $default = explode(',', $default); + + foreach ($default as $key => $value) { + $default[$key] = $choices[trim($value)]; + } + + $text = sprintf(' %s [%s]:', $text, implode(', ', $default)); + + break; + + case $this->question instanceof Choice: + $choices = $this->question->getChoices(); + $text = sprintf(' %s [%s]:', $text, $choices[$default]); + + break; + + default: + $text = sprintf(' %s [%s]:', $text, $default); + } + + $this->output->writeln($text); + + if ($this->question instanceof Choice) { + $width = max(array_map('strlen', array_keys($this->question->getChoices()))); + + foreach ($this->question->getChoices() as $key => $value) { + $this->output->writeln(sprintf(" [%-${width}s] %s", $key, $value)); + } + } + + $this->output->write(' > '); + } + + private function getShell() + { + if (null !== self::$shell) { + return self::$shell; + } + + self::$shell = false; + + if (file_exists('/usr/bin/env')) { + $test = "/usr/bin/env %s -c 'echo OK' 2> /dev/null"; + foreach (['bash', 'zsh', 'ksh', 'csh'] as $sh) { + if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) { + self::$shell = $sh; + break; + } + } + } + + return self::$shell; + } + + private function hasSttyAvailable() + { + if (null !== self::$stty) { + return self::$stty; + } + + exec('stty 2>&1', $output, $exitcode); + + return self::$stty = $exitcode === 0; + } +} diff --git a/vendor/topthink/framework/src/think/console/output/Descriptor.php b/vendor/topthink/framework/src/think/console/output/Descriptor.php new file mode 100644 index 0000000..e4a9e61 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/output/Descriptor.php @@ -0,0 +1,323 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output; + +use think\Console; +use think\console\Command; +use think\console\input\Argument as InputArgument; +use think\console\input\Definition as InputDefinition; +use think\console\input\Option as InputOption; +use think\console\Output; +use think\console\output\descriptor\Console as ConsoleDescription; + +class Descriptor +{ + + /** + * @var Output + */ + protected $output; + + /** + * {@inheritdoc} + */ + public function describe(Output $output, $object, array $options = []) + { + $this->output = $output; + + switch (true) { + case $object instanceof InputArgument: + $this->describeInputArgument($object, $options); + break; + case $object instanceof InputOption: + $this->describeInputOption($object, $options); + break; + case $object instanceof InputDefinition: + $this->describeInputDefinition($object, $options); + break; + case $object instanceof Command: + $this->describeCommand($object, $options); + break; + case $object instanceof Console: + $this->describeConsole($object, $options); + break; + default: + throw new \InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_class($object))); + } + } + + /** + * 输出内容 + * @param string $content + * @param bool $decorated + */ + protected function write($content, $decorated = false) + { + $this->output->write($content, false, $decorated ? Output::OUTPUT_NORMAL : Output::OUTPUT_RAW); + } + + /** + * 描述参数 + * @param InputArgument $argument + * @param array $options + * @return string|mixed + */ + protected function describeInputArgument(InputArgument $argument, array $options = []) + { + if (null !== $argument->getDefault() + && (!is_array($argument->getDefault()) + || count($argument->getDefault())) + ) { + $default = sprintf(' [default: %s]', $this->formatDefaultValue($argument->getDefault())); + } else { + $default = ''; + } + + $totalWidth = $options['total_width'] ?? strlen($argument->getName()); + $spacingWidth = $totalWidth - strlen($argument->getName()) + 2; + + $this->writeText(sprintf(" %s%s%s%s", $argument->getName(), str_repeat(' ', $spacingWidth), // + 17 = 2 spaces + + + 2 spaces + preg_replace('/\s*\R\s*/', PHP_EOL . str_repeat(' ', $totalWidth + 17), $argument->getDescription()), $default), $options); + } + + /** + * 描述选项 + * @param InputOption $option + * @param array $options + * @return string|mixed + */ + protected function describeInputOption(InputOption $option, array $options = []) + { + if ($option->acceptValue() && null !== $option->getDefault() + && (!is_array($option->getDefault()) + || count($option->getDefault())) + ) { + $default = sprintf(' [default: %s]', $this->formatDefaultValue($option->getDefault())); + } else { + $default = ''; + } + + $value = ''; + if ($option->acceptValue()) { + $value = '=' . strtoupper($option->getName()); + + if ($option->isValueOptional()) { + $value = '[' . $value . ']'; + } + } + + $totalWidth = $options['total_width'] ?? $this->calculateTotalWidthForOptions([$option]); + $synopsis = sprintf('%s%s', $option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : ' ', sprintf('--%s%s', $option->getName(), $value)); + + $spacingWidth = $totalWidth - strlen($synopsis) + 2; + + $this->writeText(sprintf(" %s%s%s%s%s", $synopsis, str_repeat(' ', $spacingWidth), // + 17 = 2 spaces + + + 2 spaces + preg_replace('/\s*\R\s*/', "\n" . str_repeat(' ', $totalWidth + 17), $option->getDescription()), $default, $option->isArray() ? ' (multiple values allowed)' : ''), $options); + } + + /** + * 描述输入 + * @param InputDefinition $definition + * @param array $options + * @return string|mixed + */ + protected function describeInputDefinition(InputDefinition $definition, array $options = []) + { + $totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions()); + foreach ($definition->getArguments() as $argument) { + $totalWidth = max($totalWidth, strlen($argument->getName())); + } + + if ($definition->getArguments()) { + $this->writeText('Arguments:', $options); + $this->writeText("\n"); + foreach ($definition->getArguments() as $argument) { + $this->describeInputArgument($argument, array_merge($options, ['total_width' => $totalWidth])); + $this->writeText("\n"); + } + } + + if ($definition->getArguments() && $definition->getOptions()) { + $this->writeText("\n"); + } + + if ($definition->getOptions()) { + $laterOptions = []; + + $this->writeText('Options:', $options); + foreach ($definition->getOptions() as $option) { + if (strlen($option->getShortcut()) > 1) { + $laterOptions[] = $option; + continue; + } + $this->writeText("\n"); + $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth])); + } + foreach ($laterOptions as $option) { + $this->writeText("\n"); + $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth])); + } + } + } + + /** + * 描述指令 + * @param Command $command + * @param array $options + * @return string|mixed + */ + protected function describeCommand(Command $command, array $options = []) + { + $command->getSynopsis(true); + $command->getSynopsis(false); + $command->mergeConsoleDefinition(false); + + $this->writeText('Usage:', $options); + foreach (array_merge([$command->getSynopsis(true)], $command->getAliases(), $command->getUsages()) as $usage) { + $this->writeText("\n"); + $this->writeText(' ' . $usage, $options); + } + $this->writeText("\n"); + + $definition = $command->getNativeDefinition(); + if ($definition->getOptions() || $definition->getArguments()) { + $this->writeText("\n"); + $this->describeInputDefinition($definition, $options); + $this->writeText("\n"); + } + + if ($help = $command->getProcessedHelp()) { + $this->writeText("\n"); + $this->writeText('Help:', $options); + $this->writeText("\n"); + $this->writeText(' ' . str_replace("\n", "\n ", $help), $options); + $this->writeText("\n"); + } + } + + /** + * 描述控制台 + * @param Console $console + * @param array $options + * @return string|mixed + */ + protected function describeConsole(Console $console, array $options = []) + { + $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null; + $description = new ConsoleDescription($console, $describedNamespace); + + if (isset($options['raw_text']) && $options['raw_text']) { + $width = $this->getColumnWidth($description->getNamespaces()); + + foreach ($description->getCommands() as $command) { + $this->writeText(sprintf("%-${width}s %s", $command->getName(), $command->getDescription()), $options); + $this->writeText("\n"); + } + } else { + if ('' != $help = $console->getHelp()) { + $this->writeText("$help\n\n", $options); + } + + $this->writeText("Usage:\n", $options); + $this->writeText(" command [options] [arguments]\n\n", $options); + + $this->describeInputDefinition(new InputDefinition($console->getDefinition()->getOptions()), $options); + + $this->writeText("\n"); + $this->writeText("\n"); + + $width = $this->getColumnWidth($description->getNamespaces()); + + if ($describedNamespace) { + $this->writeText(sprintf('Available commands for the "%s" namespace:', $describedNamespace), $options); + } else { + $this->writeText('Available commands:', $options); + } + + // add commands by namespace + foreach ($description->getNamespaces() as $namespace) { + if (!$describedNamespace && ConsoleDescription::GLOBAL_NAMESPACE !== $namespace['id']) { + $this->writeText("\n"); + $this->writeText(' ' . $namespace['id'] . '', $options); + } + + foreach ($namespace['commands'] as $name) { + $this->writeText("\n"); + $spacingWidth = $width - strlen($name); + $this->writeText(sprintf(" %s%s%s", $name, str_repeat(' ', $spacingWidth), $description->getCommand($name) + ->getDescription()), $options); + } + } + + $this->writeText("\n"); + } + } + + /** + * {@inheritdoc} + */ + private function writeText($content, array $options = []) + { + $this->write(isset($options['raw_text']) + && $options['raw_text'] ? strip_tags($content) : $content, isset($options['raw_output']) ? !$options['raw_output'] : true); + } + + /** + * 格式化 + * @param mixed $default + * @return string + */ + private function formatDefaultValue($default) + { + return json_encode($default, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + } + + /** + * @param Namespaces[] $namespaces + * @return int + */ + private function getColumnWidth(array $namespaces) + { + $width = 0; + foreach ($namespaces as $namespace) { + foreach ($namespace['commands'] as $name) { + if (strlen($name) > $width) { + $width = strlen($name); + } + } + } + + return $width + 2; + } + + /** + * @param InputOption[] $options + * @return int + */ + private function calculateTotalWidthForOptions($options) + { + $totalWidth = 0; + foreach ($options as $option) { + $nameLength = 4 + strlen($option->getName()) + 2; // - + shortcut + , + whitespace + name + -- + + if ($option->acceptValue()) { + $valueLength = 1 + strlen($option->getName()); // = + value + $valueLength += $option->isValueOptional() ? 2 : 0; // [ + ] + + $nameLength += $valueLength; + } + $totalWidth = max($totalWidth, $nameLength); + } + + return $totalWidth; + } +} diff --git a/vendor/topthink/framework/src/think/console/output/Formatter.php b/vendor/topthink/framework/src/think/console/output/Formatter.php new file mode 100644 index 0000000..1b97ca3 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/output/Formatter.php @@ -0,0 +1,198 @@ + +// +---------------------------------------------------------------------- +namespace think\console\output; + +use think\console\output\formatter\Stack as StyleStack; +use think\console\output\formatter\Style; + +class Formatter +{ + + private $decorated = false; + private $styles = []; + private $styleStack; + + /** + * 转义 + * @param string $text + * @return string + */ + public static function escape($text) + { + return preg_replace('/([^\\\\]?)setStyle('error', new Style('white', 'red')); + $this->setStyle('info', new Style('green')); + $this->setStyle('comment', new Style('yellow')); + $this->setStyle('question', new Style('black', 'cyan')); + $this->setStyle('highlight', new Style('red')); + $this->setStyle('warning', new Style('black', 'yellow')); + + $this->styleStack = new StyleStack(); + } + + /** + * 设置外观标识 + * @param bool $decorated 是否美化文字 + */ + public function setDecorated($decorated) + { + $this->decorated = (bool) $decorated; + } + + /** + * 获取外观标识 + * @return bool + */ + public function isDecorated() + { + return $this->decorated; + } + + /** + * 添加一个新样式 + * @param string $name 样式名 + * @param Style $style 样式实例 + */ + public function setStyle($name, Style $style) + { + $this->styles[strtolower($name)] = $style; + } + + /** + * 是否有这个样式 + * @param string $name + * @return bool + */ + public function hasStyle($name) + { + return isset($this->styles[strtolower($name)]); + } + + /** + * 获取样式 + * @param string $name + * @return Style + * @throws \InvalidArgumentException + */ + public function getStyle($name) + { + if (!$this->hasStyle($name)) { + throw new \InvalidArgumentException(sprintf('Undefined style: %s', $name)); + } + + return $this->styles[strtolower($name)]; + } + + /** + * 使用所给的样式格式化文字 + * @param string $message 文字 + * @return string + */ + public function format($message) + { + $offset = 0; + $output = ''; + $tagRegex = '[a-z][a-z0-9_=;-]*'; + preg_match_all("#<(($tagRegex) | /($tagRegex)?)>#isx", $message, $matches, PREG_OFFSET_CAPTURE); + foreach ($matches[0] as $i => $match) { + $pos = $match[1]; + $text = $match[0]; + + if (0 != $pos && '\\' == $message[$pos - 1]) { + continue; + } + + $output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset)); + $offset = $pos + strlen($text); + + if ($open = '/' != $text[1]) { + $tag = $matches[1][$i][0]; + } else { + $tag = $matches[3][$i][0] ?? ''; + } + + if (!$open && !$tag) { + // + $this->styleStack->pop(); + } elseif (false === $style = $this->createStyleFromString(strtolower($tag))) { + $output .= $this->applyCurrentStyle($text); + } elseif ($open) { + $this->styleStack->push($style); + } else { + $this->styleStack->pop($style); + } + } + + $output .= $this->applyCurrentStyle(substr($message, $offset)); + + return str_replace('\\<', '<', $output); + } + + /** + * @return StyleStack + */ + public function getStyleStack() + { + return $this->styleStack; + } + + /** + * 根据字符串创建新的样式实例 + * @param string $string + * @return Style|bool + */ + private function createStyleFromString($string) + { + if (isset($this->styles[$string])) { + return $this->styles[$string]; + } + + if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', strtolower($string), $matches, PREG_SET_ORDER)) { + return false; + } + + $style = new Style(); + foreach ($matches as $match) { + array_shift($match); + + if ('fg' == $match[0]) { + $style->setForeground($match[1]); + } elseif ('bg' == $match[0]) { + $style->setBackground($match[1]); + } else { + try { + $style->setOption($match[1]); + } catch (\InvalidArgumentException $e) { + return false; + } + } + } + + return $style; + } + + /** + * 从堆栈应用样式到文字 + * @param string $text 文字 + * @return string + */ + private function applyCurrentStyle($text) + { + return $this->isDecorated() && strlen($text) > 0 ? $this->styleStack->getCurrent()->apply($text) : $text; + } +} diff --git a/vendor/topthink/framework/src/think/console/output/Question.php b/vendor/topthink/framework/src/think/console/output/Question.php new file mode 100644 index 0000000..03975f2 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/output/Question.php @@ -0,0 +1,211 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output; + +class Question +{ + + private $question; + private $attempts; + private $hidden = false; + private $hiddenFallback = true; + private $autocompleterValues; + private $validator; + private $default; + private $normalizer; + + /** + * 构造方法 + * @param string $question 问题 + * @param mixed $default 默认答案 + */ + public function __construct($question, $default = null) + { + $this->question = $question; + $this->default = $default; + } + + /** + * 获取问题 + * @return string + */ + public function getQuestion() + { + return $this->question; + } + + /** + * 获取默认答案 + * @return mixed + */ + public function getDefault() + { + return $this->default; + } + + /** + * 是否隐藏答案 + * @return bool + */ + public function isHidden() + { + return $this->hidden; + } + + /** + * 隐藏答案 + * @param bool $hidden + * @return Question + */ + public function setHidden($hidden) + { + if ($this->autocompleterValues) { + throw new \LogicException('A hidden question cannot use the autocompleter.'); + } + + $this->hidden = (bool) $hidden; + + return $this; + } + + /** + * 不能被隐藏是否撤销 + * @return bool + */ + public function isHiddenFallback() + { + return $this->hiddenFallback; + } + + /** + * 设置不能被隐藏的时候的操作 + * @param bool $fallback + * @return Question + */ + public function setHiddenFallback($fallback) + { + $this->hiddenFallback = (bool) $fallback; + + return $this; + } + + /** + * 获取自动完成 + * @return null|array|\Traversable + */ + public function getAutocompleterValues() + { + return $this->autocompleterValues; + } + + /** + * 设置自动完成的值 + * @param null|array|\Traversable $values + * @return Question + * @throws \InvalidArgumentException + * @throws \LogicException + */ + public function setAutocompleterValues($values) + { + if (is_array($values) && $this->isAssoc($values)) { + $values = array_merge(array_keys($values), array_values($values)); + } + + if (null !== $values && !is_array($values)) { + if (!$values instanceof \Traversable || $values instanceof \Countable) { + throw new \InvalidArgumentException('Autocompleter values can be either an array, `null` or an object implementing both `Countable` and `Traversable` interfaces.'); + } + } + + if ($this->hidden) { + throw new \LogicException('A hidden question cannot use the autocompleter.'); + } + + $this->autocompleterValues = $values; + + return $this; + } + + /** + * 设置答案的验证器 + * @param null|callable $validator + * @return Question The current instance + */ + public function setValidator($validator) + { + $this->validator = $validator; + + return $this; + } + + /** + * 获取验证器 + * @return null|callable + */ + public function getValidator() + { + return $this->validator; + } + + /** + * 设置最大重试次数 + * @param null|int $attempts + * @return Question + * @throws \InvalidArgumentException + */ + public function setMaxAttempts($attempts) + { + if (null !== $attempts && $attempts < 1) { + throw new \InvalidArgumentException('Maximum number of attempts must be a positive value.'); + } + + $this->attempts = $attempts; + + return $this; + } + + /** + * 获取最大重试次数 + * @return null|int + */ + public function getMaxAttempts() + { + return $this->attempts; + } + + /** + * 设置响应的回调 + * @param string|\Closure $normalizer + * @return Question + */ + public function setNormalizer($normalizer) + { + $this->normalizer = $normalizer; + + return $this; + } + + /** + * 获取响应回调 + * The normalizer can ba a callable (a string), a closure or a class implementing __invoke. + * @return string|\Closure + */ + public function getNormalizer() + { + return $this->normalizer; + } + + protected function isAssoc($array) + { + return (bool) count(array_filter(array_keys($array), 'is_string')); + } +} diff --git a/vendor/topthink/framework/src/think/console/output/descriptor/Console.php b/vendor/topthink/framework/src/think/console/output/descriptor/Console.php new file mode 100644 index 0000000..ff9f464 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/output/descriptor/Console.php @@ -0,0 +1,153 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output\descriptor; + +use think\Console as ThinkConsole; +use think\console\Command; + +class Console +{ + + const GLOBAL_NAMESPACE = '_global'; + + /** + * @var ThinkConsole + */ + private $console; + + /** + * @var null|string + */ + private $namespace; + + /** + * @var array + */ + private $namespaces; + + /** + * @var Command[] + */ + private $commands; + + /** + * @var Command[] + */ + private $aliases; + + /** + * 构造方法 + * @param ThinkConsole $console + * @param string|null $namespace + */ + public function __construct(ThinkConsole $console, $namespace = null) + { + $this->console = $console; + $this->namespace = $namespace; + } + + /** + * @return array + */ + public function getNamespaces(): array + { + if (null === $this->namespaces) { + $this->inspectConsole(); + } + + return $this->namespaces; + } + + /** + * @return Command[] + */ + public function getCommands(): array + { + if (null === $this->commands) { + $this->inspectConsole(); + } + + return $this->commands; + } + + /** + * @param string $name + * @return Command + * @throws \InvalidArgumentException + */ + public function getCommand(string $name): Command + { + if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) { + throw new \InvalidArgumentException(sprintf('Command %s does not exist.', $name)); + } + + return $this->commands[$name] ?? $this->aliases[$name]; + } + + private function inspectConsole(): void + { + $this->commands = []; + $this->namespaces = []; + + $all = $this->console->all($this->namespace ? $this->console->findNamespace($this->namespace) : null); + foreach ($this->sortCommands($all) as $namespace => $commands) { + $names = []; + + /** @var Command $command */ + foreach ($commands as $name => $command) { + if (is_string($command)) { + $command = new $command(); + } + + if (!$command->getName()) { + continue; + } + + if ($command->getName() === $name) { + $this->commands[$name] = $command; + } else { + $this->aliases[$name] = $command; + } + + $names[] = $name; + } + + $this->namespaces[$namespace] = ['id' => $namespace, 'commands' => $names]; + } + } + + /** + * @param array $commands + * @return array + */ + private function sortCommands(array $commands): array + { + $namespacedCommands = []; + foreach ($commands as $name => $command) { + $key = $this->console->extractNamespace($name, 1); + if (!$key) { + $key = self::GLOBAL_NAMESPACE; + } + + $namespacedCommands[$key][$name] = $command; + } + ksort($namespacedCommands); + + foreach ($namespacedCommands as &$commandsSet) { + ksort($commandsSet); + } + // unset reference to keep scope clear + unset($commandsSet); + + return $namespacedCommands; + } +} diff --git a/vendor/topthink/framework/src/think/console/output/driver/Buffer.php b/vendor/topthink/framework/src/think/console/output/driver/Buffer.php new file mode 100644 index 0000000..576f31a --- /dev/null +++ b/vendor/topthink/framework/src/think/console/output/driver/Buffer.php @@ -0,0 +1,52 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output\driver; + +use think\console\Output; + +class Buffer +{ + /** + * @var string + */ + private $buffer = ''; + + public function __construct(Output $output) + { + // do nothing + } + + public function fetch() + { + $content = $this->buffer; + $this->buffer = ''; + return $content; + } + + public function write($messages, bool $newline = false, int $options = 0) + { + $messages = (array) $messages; + + foreach ($messages as $message) { + $this->buffer .= $message; + } + if ($newline) { + $this->buffer .= "\n"; + } + } + + public function renderException(\Throwable $e) + { + // do nothing + } + +} diff --git a/vendor/topthink/framework/src/think/console/output/driver/Console.php b/vendor/topthink/framework/src/think/console/output/driver/Console.php new file mode 100644 index 0000000..31bdf1f --- /dev/null +++ b/vendor/topthink/framework/src/think/console/output/driver/Console.php @@ -0,0 +1,368 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output\driver; + +use think\console\Output; +use think\console\output\Formatter; + +class Console +{ + + /** @var Resource */ + private $stdout; + + /** @var Formatter */ + private $formatter; + + private $terminalDimensions; + + /** @var Output */ + private $output; + + public function __construct(Output $output) + { + $this->output = $output; + $this->formatter = new Formatter(); + $this->stdout = $this->openOutputStream(); + $decorated = $this->hasColorSupport($this->stdout); + $this->formatter->setDecorated($decorated); + } + + public function setDecorated($decorated) + { + $this->formatter->setDecorated($decorated); + } + + public function write($messages, bool $newline = false, int $type = 0, $stream = null) + { + if (Output::VERBOSITY_QUIET === $this->output->getVerbosity()) { + return; + } + + $messages = (array) $messages; + + foreach ($messages as $message) { + switch ($type) { + case Output::OUTPUT_NORMAL: + $message = $this->formatter->format($message); + break; + case Output::OUTPUT_RAW: + break; + case Output::OUTPUT_PLAIN: + $message = strip_tags($this->formatter->format($message)); + break; + default: + throw new \InvalidArgumentException(sprintf('Unknown output type given (%s)', $type)); + } + + $this->doWrite($message, $newline, $stream); + } + } + + public function renderException(\Throwable $e) + { + $stderr = $this->openErrorStream(); + $decorated = $this->hasColorSupport($stderr); + $this->formatter->setDecorated($decorated); + + do { + $title = sprintf(' [%s] ', get_class($e)); + + $len = $this->stringWidth($title); + + $width = $this->getTerminalWidth() ? $this->getTerminalWidth() - 1 : PHP_INT_MAX; + + if (defined('HHVM_VERSION') && $width > 1 << 31) { + $width = 1 << 31; + } + $lines = []; + foreach (preg_split('/\r?\n/', $e->getMessage()) as $line) { + foreach ($this->splitStringByWidth($line, $width - 4) as $line) { + + $lineLength = $this->stringWidth(preg_replace('/\[[^m]*m/', '', $line)) + 4; + $lines[] = [$line, $lineLength]; + + $len = max($lineLength, $len); + } + } + + $messages = ['', '']; + $messages[] = $emptyLine = sprintf('%s', str_repeat(' ', $len)); + $messages[] = sprintf('%s%s', $title, str_repeat(' ', max(0, $len - $this->stringWidth($title)))); + foreach ($lines as $line) { + $messages[] = sprintf(' %s %s', $line[0], str_repeat(' ', $len - $line[1])); + } + $messages[] = $emptyLine; + $messages[] = ''; + $messages[] = ''; + + $this->write($messages, true, Output::OUTPUT_NORMAL, $stderr); + + if (Output::VERBOSITY_VERBOSE <= $this->output->getVerbosity()) { + $this->write('Exception trace:', true, Output::OUTPUT_NORMAL, $stderr); + + // exception related properties + $trace = $e->getTrace(); + array_unshift($trace, [ + 'function' => '', + 'file' => $e->getFile() !== null ? $e->getFile() : 'n/a', + 'line' => $e->getLine() !== null ? $e->getLine() : 'n/a', + 'args' => [], + ]); + + for ($i = 0, $count = count($trace); $i < $count; ++$i) { + $class = isset($trace[$i]['class']) ? $trace[$i]['class'] : ''; + $type = isset($trace[$i]['type']) ? $trace[$i]['type'] : ''; + $function = $trace[$i]['function']; + $file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a'; + $line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a'; + + $this->write(sprintf(' %s%s%s() at %s:%s', $class, $type, $function, $file, $line), true, Output::OUTPUT_NORMAL, $stderr); + } + + $this->write('', true, Output::OUTPUT_NORMAL, $stderr); + $this->write('', true, Output::OUTPUT_NORMAL, $stderr); + } + } while ($e = $e->getPrevious()); + + } + + /** + * 获取终端宽度 + * @return int|null + */ + protected function getTerminalWidth() + { + $dimensions = $this->getTerminalDimensions(); + + return $dimensions[0]; + } + + /** + * 获取终端高度 + * @return int|null + */ + protected function getTerminalHeight() + { + $dimensions = $this->getTerminalDimensions(); + + return $dimensions[1]; + } + + /** + * 获取当前终端的尺寸 + * @return array + */ + public function getTerminalDimensions(): array + { + if ($this->terminalDimensions) { + return $this->terminalDimensions; + } + + if ('\\' === DIRECTORY_SEPARATOR) { + if (preg_match('/^(\d+)x\d+ \(\d+x(\d+)\)$/', trim(getenv('ANSICON')), $matches)) { + return [(int) $matches[1], (int) $matches[2]]; + } + if (preg_match('/^(\d+)x(\d+)$/', $this->getMode(), $matches)) { + return [(int) $matches[1], (int) $matches[2]]; + } + } + + if ($sttyString = $this->getSttyColumns()) { + if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) { + return [(int) $matches[2], (int) $matches[1]]; + } + if (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) { + return [(int) $matches[2], (int) $matches[1]]; + } + } + + return [null, null]; + } + + /** + * 获取stty列数 + * @return string + */ + private function getSttyColumns() + { + if (!function_exists('proc_open')) { + return; + } + + $descriptorspec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; + $process = proc_open('stty -a | grep columns', $descriptorspec, $pipes, null, null, ['suppress_errors' => true]); + if (is_resource($process)) { + $info = stream_get_contents($pipes[1]); + fclose($pipes[1]); + fclose($pipes[2]); + proc_close($process); + + return $info; + } + return; + } + + /** + * 获取终端模式 + * @return string x 或 null + */ + private function getMode() + { + if (!function_exists('proc_open')) { + return; + } + + $descriptorspec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; + $process = proc_open('mode CON', $descriptorspec, $pipes, null, null, ['suppress_errors' => true]); + if (is_resource($process)) { + $info = stream_get_contents($pipes[1]); + fclose($pipes[1]); + fclose($pipes[2]); + proc_close($process); + + if (preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) { + return $matches[2] . 'x' . $matches[1]; + } + } + return; + } + + private function stringWidth(string $string): int + { + if (!function_exists('mb_strwidth')) { + return strlen($string); + } + + if (false === $encoding = mb_detect_encoding($string)) { + return strlen($string); + } + + return mb_strwidth($string, $encoding); + } + + private function splitStringByWidth(string $string, int $width): array + { + if (!function_exists('mb_strwidth')) { + return str_split($string, $width); + } + + if (false === $encoding = mb_detect_encoding($string)) { + return str_split($string, $width); + } + + $utf8String = mb_convert_encoding($string, 'utf8', $encoding); + $lines = []; + $line = ''; + foreach (preg_split('//u', $utf8String) as $char) { + if (mb_strwidth($line . $char, 'utf8') <= $width) { + $line .= $char; + continue; + } + $lines[] = str_pad($line, $width); + $line = $char; + } + if (strlen($line)) { + $lines[] = count($lines) ? str_pad($line, $width) : $line; + } + + mb_convert_variables($encoding, 'utf8', $lines); + + return $lines; + } + + private function isRunningOS400(): bool + { + $checks = [ + function_exists('php_uname') ? php_uname('s') : '', + getenv('OSTYPE'), + PHP_OS, + ]; + return false !== stripos(implode(';', $checks), 'OS400'); + } + + /** + * 当前环境是否支持写入控制台输出到stdout. + * + * @return bool + */ + protected function hasStdoutSupport(): bool + { + return false === $this->isRunningOS400(); + } + + /** + * 当前环境是否支持写入控制台输出到stderr. + * + * @return bool + */ + protected function hasStderrSupport(): bool + { + return false === $this->isRunningOS400(); + } + + /** + * @return resource + */ + private function openOutputStream() + { + if (!$this->hasStdoutSupport()) { + return fopen('php://output', 'w'); + } + return @fopen('php://stdout', 'w') ?: fopen('php://output', 'w'); + } + + /** + * @return resource + */ + private function openErrorStream() + { + return fopen($this->hasStderrSupport() ? 'php://stderr' : 'php://output', 'w'); + } + + /** + * 将消息写入到输出。 + * @param string $message 消息 + * @param bool $newline 是否另起一行 + * @param null $stream + */ + protected function doWrite($message, $newline, $stream = null) + { + if (null === $stream) { + $stream = $this->stdout; + } + if (false === @fwrite($stream, $message . ($newline ? PHP_EOL : ''))) { + throw new \RuntimeException('Unable to write output.'); + } + + fflush($stream); + } + + /** + * 是否支持着色 + * @param $stream + * @return bool + */ + protected function hasColorSupport($stream): bool + { + if (DIRECTORY_SEPARATOR === '\\') { + return + '10.0.10586' === PHP_WINDOWS_VERSION_MAJOR . '.' . PHP_WINDOWS_VERSION_MINOR . '.' . PHP_WINDOWS_VERSION_BUILD + || false !== getenv('ANSICON') + || 'ON' === getenv('ConEmuANSI') + || 'xterm' === getenv('TERM'); + } + + return function_exists('posix_isatty') && @posix_isatty($stream); + } + +} diff --git a/vendor/topthink/framework/src/think/console/output/driver/Nothing.php b/vendor/topthink/framework/src/think/console/output/driver/Nothing.php new file mode 100644 index 0000000..a7cc49e --- /dev/null +++ b/vendor/topthink/framework/src/think/console/output/driver/Nothing.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output\driver; + +use think\console\Output; + +class Nothing +{ + + public function __construct(Output $output) + { + // do nothing + } + + public function write($messages, bool $newline = false, int $options = 0) + { + // do nothing + } + + public function renderException(\Throwable $e) + { + // do nothing + } +} diff --git a/vendor/topthink/framework/src/think/console/output/formatter/Stack.php b/vendor/topthink/framework/src/think/console/output/formatter/Stack.php new file mode 100644 index 0000000..5366259 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/output/formatter/Stack.php @@ -0,0 +1,116 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output\formatter; + +class Stack +{ + + /** + * @var Style[] + */ + private $styles; + + /** + * @var Style + */ + private $emptyStyle; + + /** + * 构造方法 + * @param Style|null $emptyStyle + */ + public function __construct(Style $emptyStyle = null) + { + $this->emptyStyle = $emptyStyle ?: new Style(); + $this->reset(); + } + + /** + * 重置堆栈 + */ + public function reset(): void + { + $this->styles = []; + } + + /** + * 推一个样式进入堆栈 + * @param Style $style + */ + public function push(Style $style): void + { + $this->styles[] = $style; + } + + /** + * 从堆栈中弹出一个样式 + * @param Style|null $style + * @return Style + * @throws \InvalidArgumentException + */ + public function pop(Style $style = null): Style + { + if (empty($this->styles)) { + return $this->emptyStyle; + } + + if (null === $style) { + return array_pop($this->styles); + } + + /** + * @var int $index + * @var Style $stackedStyle + */ + foreach (array_reverse($this->styles, true) as $index => $stackedStyle) { + if ($style->apply('') === $stackedStyle->apply('')) { + $this->styles = array_slice($this->styles, 0, $index); + + return $stackedStyle; + } + } + + throw new \InvalidArgumentException('Incorrectly nested style tag found.'); + } + + /** + * 计算堆栈的当前样式。 + * @return Style + */ + public function getCurrent(): Style + { + if (empty($this->styles)) { + return $this->emptyStyle; + } + + return $this->styles[count($this->styles) - 1]; + } + + /** + * @param Style $emptyStyle + * @return Stack + */ + public function setEmptyStyle(Style $emptyStyle) + { + $this->emptyStyle = $emptyStyle; + + return $this; + } + + /** + * @return Style + */ + public function getEmptyStyle(): Style + { + return $this->emptyStyle; + } +} diff --git a/vendor/topthink/framework/src/think/console/output/formatter/Style.php b/vendor/topthink/framework/src/think/console/output/formatter/Style.php new file mode 100644 index 0000000..2aae768 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/output/formatter/Style.php @@ -0,0 +1,190 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output\formatter; + +class Style +{ + protected static $availableForegroundColors = [ + 'black' => ['set' => 30, 'unset' => 39], + 'red' => ['set' => 31, 'unset' => 39], + 'green' => ['set' => 32, 'unset' => 39], + 'yellow' => ['set' => 33, 'unset' => 39], + 'blue' => ['set' => 34, 'unset' => 39], + 'magenta' => ['set' => 35, 'unset' => 39], + 'cyan' => ['set' => 36, 'unset' => 39], + 'white' => ['set' => 37, 'unset' => 39], + ]; + + protected static $availableBackgroundColors = [ + 'black' => ['set' => 40, 'unset' => 49], + 'red' => ['set' => 41, 'unset' => 49], + 'green' => ['set' => 42, 'unset' => 49], + 'yellow' => ['set' => 43, 'unset' => 49], + 'blue' => ['set' => 44, 'unset' => 49], + 'magenta' => ['set' => 45, 'unset' => 49], + 'cyan' => ['set' => 46, 'unset' => 49], + 'white' => ['set' => 47, 'unset' => 49], + ]; + + protected static $availableOptions = [ + 'bold' => ['set' => 1, 'unset' => 22], + 'underscore' => ['set' => 4, 'unset' => 24], + 'blink' => ['set' => 5, 'unset' => 25], + 'reverse' => ['set' => 7, 'unset' => 27], + 'conceal' => ['set' => 8, 'unset' => 28], + ]; + + private $foreground; + private $background; + private $options = []; + + /** + * 初始化输出的样式 + * @param string|null $foreground 字体颜色 + * @param string|null $background 背景色 + * @param array $options 格式 + * @api + */ + public function __construct($foreground = null, $background = null, array $options = []) + { + if (null !== $foreground) { + $this->setForeground($foreground); + } + if (null !== $background) { + $this->setBackground($background); + } + if (count($options)) { + $this->setOptions($options); + } + } + + /** + * 设置字体颜色 + * @param string|null $color 颜色名 + * @throws \InvalidArgumentException + * @api + */ + public function setForeground($color = null) + { + if (null === $color) { + $this->foreground = null; + + return; + } + + if (!isset(static::$availableForegroundColors[$color])) { + throw new \InvalidArgumentException(sprintf('Invalid foreground color specified: "%s". Expected one of (%s)', $color, implode(', ', array_keys(static::$availableForegroundColors)))); + } + + $this->foreground = static::$availableForegroundColors[$color]; + } + + /** + * 设置背景色 + * @param string|null $color 颜色名 + * @throws \InvalidArgumentException + * @api + */ + public function setBackground($color = null) + { + if (null === $color) { + $this->background = null; + + return; + } + + if (!isset(static::$availableBackgroundColors[$color])) { + throw new \InvalidArgumentException(sprintf('Invalid background color specified: "%s". Expected one of (%s)', $color, implode(', ', array_keys(static::$availableBackgroundColors)))); + } + + $this->background = static::$availableBackgroundColors[$color]; + } + + /** + * 设置字体格式 + * @param string $option 格式名 + * @throws \InvalidArgumentException When the option name isn't defined + * @api + */ + public function setOption(string $option): void + { + if (!isset(static::$availableOptions[$option])) { + throw new \InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s)', $option, implode(', ', array_keys(static::$availableOptions)))); + } + + if (!in_array(static::$availableOptions[$option], $this->options)) { + $this->options[] = static::$availableOptions[$option]; + } + } + + /** + * 重置字体格式 + * @param string $option 格式名 + * @throws \InvalidArgumentException + */ + public function unsetOption(string $option): void + { + if (!isset(static::$availableOptions[$option])) { + throw new \InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s)', $option, implode(', ', array_keys(static::$availableOptions)))); + } + + $pos = array_search(static::$availableOptions[$option], $this->options); + if (false !== $pos) { + unset($this->options[$pos]); + } + } + + /** + * 批量设置字体格式 + * @param array $options + */ + public function setOptions(array $options) + { + $this->options = []; + + foreach ($options as $option) { + $this->setOption($option); + } + } + + /** + * 应用样式到文字 + * @param string $text 文字 + * @return string + */ + public function apply(string $text): string + { + $setCodes = []; + $unsetCodes = []; + + if (null !== $this->foreground) { + $setCodes[] = $this->foreground['set']; + $unsetCodes[] = $this->foreground['unset']; + } + if (null !== $this->background) { + $setCodes[] = $this->background['set']; + $unsetCodes[] = $this->background['unset']; + } + if (count($this->options)) { + foreach ($this->options as $option) { + $setCodes[] = $option['set']; + $unsetCodes[] = $option['unset']; + } + } + + if (0 === count($setCodes)) { + return $text; + } + + return sprintf("\033[%sm%s\033[%sm", implode(';', $setCodes), $text, implode(';', $unsetCodes)); + } +} diff --git a/vendor/topthink/framework/src/think/console/output/question/Choice.php b/vendor/topthink/framework/src/think/console/output/question/Choice.php new file mode 100644 index 0000000..1da1750 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/output/question/Choice.php @@ -0,0 +1,163 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output\question; + +use think\console\output\Question; + +class Choice extends Question +{ + + private $choices; + private $multiselect = false; + private $prompt = ' > '; + private $errorMessage = 'Value "%s" is invalid'; + + /** + * 构造方法 + * @param string $question 问题 + * @param array $choices 选项 + * @param mixed $default 默认答案 + */ + public function __construct($question, array $choices, $default = null) + { + parent::__construct($question, $default); + + $this->choices = $choices; + $this->setValidator($this->getDefaultValidator()); + $this->setAutocompleterValues($choices); + } + + /** + * 可选项 + * @return array + */ + public function getChoices(): array + { + return $this->choices; + } + + /** + * 设置可否多选 + * @param bool $multiselect + * @return self + */ + public function setMultiselect(bool $multiselect) + { + $this->multiselect = $multiselect; + $this->setValidator($this->getDefaultValidator()); + + return $this; + } + + public function isMultiselect(): bool + { + return $this->multiselect; + } + + /** + * 获取提示 + * @return string + */ + public function getPrompt(): string + { + return $this->prompt; + } + + /** + * 设置提示 + * @param string $prompt + * @return self + */ + public function setPrompt(string $prompt) + { + $this->prompt = $prompt; + + return $this; + } + + /** + * 设置错误提示信息 + * @param string $errorMessage + * @return self + */ + public function setErrorMessage(string $errorMessage) + { + $this->errorMessage = $errorMessage; + $this->setValidator($this->getDefaultValidator()); + + return $this; + } + + /** + * 获取默认的验证方法 + * @return callable + */ + private function getDefaultValidator() + { + $choices = $this->choices; + $errorMessage = $this->errorMessage; + $multiselect = $this->multiselect; + $isAssoc = $this->isAssoc($choices); + + return function ($selected) use ($choices, $errorMessage, $multiselect, $isAssoc) { + // Collapse all spaces. + $selectedChoices = str_replace(' ', '', $selected); + + if ($multiselect) { + // Check for a separated comma values + if (!preg_match('/^[a-zA-Z0-9_-]+(?:,[a-zA-Z0-9_-]+)*$/', $selectedChoices, $matches)) { + throw new \InvalidArgumentException(sprintf($errorMessage, $selected)); + } + $selectedChoices = explode(',', $selectedChoices); + } else { + $selectedChoices = [$selected]; + } + + $multiselectChoices = []; + foreach ($selectedChoices as $value) { + $results = []; + foreach ($choices as $key => $choice) { + if ($choice === $value) { + $results[] = $key; + } + } + + if (count($results) > 1) { + throw new \InvalidArgumentException(sprintf('The provided answer is ambiguous. Value should be one of %s.', implode(' or ', $results))); + } + + $result = array_search($value, $choices); + + if (!$isAssoc) { + if (!empty($result)) { + $result = $choices[$result]; + } elseif (isset($choices[$value])) { + $result = $choices[$value]; + } + } elseif (empty($result) && array_key_exists($value, $choices)) { + $result = $value; + } + + if (false === $result) { + throw new \InvalidArgumentException(sprintf($errorMessage, $value)); + } + array_push($multiselectChoices, $result); + } + + if ($multiselect) { + return $multiselectChoices; + } + + return current($multiselectChoices); + }; + } +} diff --git a/vendor/topthink/framework/src/think/console/output/question/Confirmation.php b/vendor/topthink/framework/src/think/console/output/question/Confirmation.php new file mode 100644 index 0000000..bf71b5d --- /dev/null +++ b/vendor/topthink/framework/src/think/console/output/question/Confirmation.php @@ -0,0 +1,57 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output\question; + +use think\console\output\Question; + +class Confirmation extends Question +{ + + private $trueAnswerRegex; + + /** + * 构造方法 + * @param string $question 问题 + * @param bool $default 默认答案 + * @param string $trueAnswerRegex 验证正则 + */ + public function __construct(string $question, bool $default = true, string $trueAnswerRegex = '/^y/i') + { + parent::__construct($question, (bool) $default); + + $this->trueAnswerRegex = $trueAnswerRegex; + $this->setNormalizer($this->getDefaultNormalizer()); + } + + /** + * 获取默认的答案回调 + * @return callable + */ + private function getDefaultNormalizer() + { + $default = $this->getDefault(); + $regex = $this->trueAnswerRegex; + + return function ($answer) use ($default, $regex) { + if (is_bool($answer)) { + return $answer; + } + + $answerIsTrue = (bool) preg_match($regex, $answer); + if (false === $default) { + return $answer && $answerIsTrue; + } + + return !$answer || $answerIsTrue; + }; + } +} diff --git a/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php b/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php new file mode 100644 index 0000000..da5e696 --- /dev/null +++ b/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php @@ -0,0 +1,88 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\contract; + +/** + * 缓存驱动接口 + */ +interface CacheHandlerInterface +{ + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name); + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = null); + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return bool + */ + public function set($name, $value, $expire = null); + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc(string $name, int $step = 1); + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec(string $name, int $step = 1); + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function delete($name); + + /** + * 清除缓存 + * @access public + * @return bool + */ + public function clear(); + + /** + * 删除缓存标签 + * @access public + * @param array $keys 缓存标识列表 + * @return void + */ + public function clearTag(array $keys); + +} diff --git a/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php b/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php new file mode 100644 index 0000000..896ac29 --- /dev/null +++ b/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php @@ -0,0 +1,28 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\contract; + +/** + * 日志驱动接口 + */ +interface LogHandlerInterface +{ + /** + * 日志写入接口 + * @access public + * @param array $log 日志信息 + * @return bool + */ + public function save(array $log): bool; + +} diff --git a/vendor/topthink/framework/src/think/contract/ModelRelationInterface.php b/vendor/topthink/framework/src/think/contract/ModelRelationInterface.php new file mode 100644 index 0000000..1f6f994 --- /dev/null +++ b/vendor/topthink/framework/src/think/contract/ModelRelationInterface.php @@ -0,0 +1,99 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\contract; + +use Closure; +use think\Collection; +use think\db\Query; +use think\Model; + +/** + * 模型关联接口 + */ +interface ModelRelationInterface +{ + /** + * 延迟获取关联数据 + * @access public + * @param array $subRelation 子关联 + * @param Closure $closure 闭包查询条件 + * @return Collection + */ + public function getRelation(array $subRelation = [], Closure $closure = null): Collection; + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包条件 + * @return void + */ + public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, Closure $closure = null): void; + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包条件 + * @return void + */ + public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null): void; + + /** + * 关联统计 + * @access public + * @param Model $result 模型对象 + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return integer + */ + public function relationCount(Model $result, Closure $closure, string $aggregate = 'count', string $field = '*', string &$name = null); + + /** + * 创建关联统计子查询 + * @access public + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return string + */ + public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string; + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = 'INNER'): Query; + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function hasWhere($where = [], $fields = null, string $joinType = ''): Query; +} diff --git a/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php b/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php new file mode 100644 index 0000000..0b2e414 --- /dev/null +++ b/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php @@ -0,0 +1,23 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\contract; + +/** + * Session驱动接口 + */ +interface SessionHandlerInterface +{ + public function read(string $sessionId): string; + public function delete(string $sessionId): bool; + public function write(string $sessionId, string $data): bool; +} diff --git a/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php b/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php new file mode 100644 index 0000000..9be93d2 --- /dev/null +++ b/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php @@ -0,0 +1,61 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\contract; + +/** + * 视图驱动接口 + */ +interface TemplateHandlerInterface +{ + /** + * 检测是否存在模板文件 + * @access public + * @param string $template 模板文件或者模板规则 + * @return bool + */ + public function exists(string $template): bool; + + /** + * 渲染模板文件 + * @access public + * @param string $template 模板文件 + * @param array $data 模板变量 + * @return void + */ + public function fetch(string $template, array $data = []): void; + + /** + * 渲染模板内容 + * @access public + * @param string $content 模板内容 + * @param array $data 模板变量 + * @return void + */ + public function display(string $content, array $data = []): void; + + /** + * 配置模板引擎 + * @access private + * @param array $config 参数 + * @return void + */ + public function config(array $config): void; + + /** + * 获取模板引擎配置 + * @access public + * @param string $name 参数名 + * @return void + */ + public function getConfig(string $name); +} diff --git a/vendor/topthink/framework/src/think/event/AppInit.php b/vendor/topthink/framework/src/think/event/AppInit.php new file mode 100644 index 0000000..dda820b --- /dev/null +++ b/vendor/topthink/framework/src/think/event/AppInit.php @@ -0,0 +1,19 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\event; + +/** + * AppInit事件类 + */ +class AppInit +{} diff --git a/vendor/topthink/framework/src/think/event/HttpEnd.php b/vendor/topthink/framework/src/think/event/HttpEnd.php new file mode 100644 index 0000000..c40da57 --- /dev/null +++ b/vendor/topthink/framework/src/think/event/HttpEnd.php @@ -0,0 +1,19 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\event; + +/** + * HttpEnd事件类 + */ +class HttpEnd +{} diff --git a/vendor/topthink/framework/src/think/event/HttpRun.php b/vendor/topthink/framework/src/think/event/HttpRun.php new file mode 100644 index 0000000..ce67e93 --- /dev/null +++ b/vendor/topthink/framework/src/think/event/HttpRun.php @@ -0,0 +1,19 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\event; + +/** + * HttpRun事件类 + */ +class HttpRun +{} diff --git a/vendor/topthink/framework/src/think/event/LogRecord.php b/vendor/topthink/framework/src/think/event/LogRecord.php new file mode 100644 index 0000000..237468d --- /dev/null +++ b/vendor/topthink/framework/src/think/event/LogRecord.php @@ -0,0 +1,29 @@ + +// +---------------------------------------------------------------------- +namespace think\event; + +/** + * LogRecord事件类 + */ +class LogRecord +{ + /** @var string */ + public $type; + + /** @var string */ + public $message; + + public function __construct($type, $message) + { + $this->type = $type; + $this->message = $message; + } +} diff --git a/vendor/topthink/framework/src/think/event/LogWrite.php b/vendor/topthink/framework/src/think/event/LogWrite.php new file mode 100644 index 0000000..a787301 --- /dev/null +++ b/vendor/topthink/framework/src/think/event/LogWrite.php @@ -0,0 +1,31 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\event; + +/** + * LogWrite事件类 + */ +class LogWrite +{ + /** @var string */ + public $channel; + + /** @var array */ + public $log; + + public function __construct($channel, $log) + { + $this->channel = $channel; + $this->log = $log; + } +} diff --git a/vendor/topthink/framework/src/think/event/RouteLoaded.php b/vendor/topthink/framework/src/think/event/RouteLoaded.php new file mode 100644 index 0000000..ace7992 --- /dev/null +++ b/vendor/topthink/framework/src/think/event/RouteLoaded.php @@ -0,0 +1,21 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\event; + +/** + * 路由加载完成事件 + */ +class RouteLoaded +{ + +} diff --git a/vendor/topthink/framework/src/think/exception/ClassNotFoundException.php b/vendor/topthink/framework/src/think/exception/ClassNotFoundException.php new file mode 100644 index 0000000..c4cda77 --- /dev/null +++ b/vendor/topthink/framework/src/think/exception/ClassNotFoundException.php @@ -0,0 +1,39 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +use Psr\Container\NotFoundExceptionInterface; +use RuntimeException; +use Throwable; + +class ClassNotFoundException extends RuntimeException implements NotFoundExceptionInterface +{ + protected $class; + + public function __construct(string $message, string $class = '', Throwable $previous = null) + { + $this->message = $message; + $this->class = $class; + + parent::__construct($message, 0, $previous); + } + + /** + * 获取类名 + * @access public + * @return string + */ + public function getClass() + { + return $this->class; + } +} diff --git a/vendor/topthink/framework/src/think/exception/ErrorException.php b/vendor/topthink/framework/src/think/exception/ErrorException.php new file mode 100644 index 0000000..d1a2378 --- /dev/null +++ b/vendor/topthink/framework/src/think/exception/ErrorException.php @@ -0,0 +1,57 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\exception; + +use think\Exception; + +/** + * ThinkPHP错误异常 + * 主要用于封装 set_error_handler 和 register_shutdown_function 得到的错误 + * 除开从 think\Exception 继承的功能 + * 其他和PHP系统\ErrorException功能基本一样 + */ +class ErrorException extends Exception +{ + /** + * 用于保存错误级别 + * @var integer + */ + protected $severity; + + /** + * 错误异常构造函数 + * @access public + * @param integer $severity 错误级别 + * @param string $message 错误详细信息 + * @param string $file 出错文件路径 + * @param integer $line 出错行号 + */ + public function __construct(int $severity, string $message, string $file, int $line) + { + $this->severity = $severity; + $this->message = $message; + $this->file = $file; + $this->line = $line; + $this->code = 0; + } + + /** + * 获取错误级别 + * @access public + * @return integer 错误级别 + */ + final public function getSeverity() + { + return $this->severity; + } +} diff --git a/vendor/topthink/framework/src/think/exception/FileException.php b/vendor/topthink/framework/src/think/exception/FileException.php new file mode 100644 index 0000000..228a189 --- /dev/null +++ b/vendor/topthink/framework/src/think/exception/FileException.php @@ -0,0 +1,17 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\exception; + +class FileException extends \RuntimeException +{ +} diff --git a/vendor/topthink/framework/src/think/exception/FuncNotFoundException.php b/vendor/topthink/framework/src/think/exception/FuncNotFoundException.php new file mode 100644 index 0000000..ee2bcad --- /dev/null +++ b/vendor/topthink/framework/src/think/exception/FuncNotFoundException.php @@ -0,0 +1,30 @@ +message = $message; + $this->func = $func; + + parent::__construct($message, 0, $previous); + } + + /** + * 获取方法名 + * @access public + * @return string + */ + public function getFunc() + { + return $this->func; + } +} diff --git a/vendor/topthink/framework/src/think/exception/Handle.php b/vendor/topthink/framework/src/think/exception/Handle.php new file mode 100644 index 0000000..1f783bc --- /dev/null +++ b/vendor/topthink/framework/src/think/exception/Handle.php @@ -0,0 +1,332 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\exception; + +use Exception; +use think\App; +use think\console\Output; +use think\db\exception\DataNotFoundException; +use think\db\exception\ModelNotFoundException; +use think\Request; +use think\Response; +use Throwable; + +/** + * 系统异常处理类 + */ +class Handle +{ + /** @var App */ + protected $app; + + protected $ignoreReport = [ + HttpException::class, + HttpResponseException::class, + ModelNotFoundException::class, + DataNotFoundException::class, + ValidateException::class, + ]; + + protected $isJson = false; + + public function __construct(App $app) + { + $this->app = $app; + } + + /** + * Report or log an exception. + * + * @access public + * @param Throwable $exception + * @return void + */ + public function report(Throwable $exception): void + { + if (!$this->isIgnoreReport($exception)) { + // 收集异常数据 + if ($this->app->isDebug()) { + $data = [ + 'file' => $exception->getFile(), + 'line' => $exception->getLine(), + 'message' => $this->getMessage($exception), + 'code' => $this->getCode($exception), + ]; + $log = "[{$data['code']}]{$data['message']}[{$data['file']}:{$data['line']}]"; + } else { + $data = [ + 'code' => $this->getCode($exception), + 'message' => $this->getMessage($exception), + ]; + $log = "[{$data['code']}]{$data['message']}"; + } + + if ($this->app->config->get('log.record_trace')) { + $log .= PHP_EOL . $exception->getTraceAsString(); + } + + try { + $this->app->log->record($log, 'error'); + } catch (Exception $e) {} + } + } + + protected function isIgnoreReport(Throwable $exception): bool + { + foreach ($this->ignoreReport as $class) { + if ($exception instanceof $class) { + return true; + } + } + + return false; + } + + /** + * Render an exception into an HTTP response. + * + * @access public + * @param Request $request + * @param Throwable $e + * @return Response + */ + public function render($request, Throwable $e): Response + { + $this->isJson = $request->isJson(); + if ($e instanceof HttpResponseException) { + return $e->getResponse(); + } elseif ($e instanceof HttpException) { + return $this->renderHttpException($e); + } else { + return $this->convertExceptionToResponse($e); + } + } + + /** + * @access public + * @param Output $output + * @param Throwable $e + */ + public function renderForConsole(Output $output, Throwable $e): void + { + if ($this->app->isDebug()) { + $output->setVerbosity(Output::VERBOSITY_DEBUG); + } + + $output->renderException($e); + } + + /** + * @access protected + * @param HttpException $e + * @return Response + */ + protected function renderHttpException(HttpException $e): Response + { + $status = $e->getStatusCode(); + $template = $this->app->config->get('app.http_exception_template'); + + if (!$this->app->isDebug() && !empty($template[$status])) { + return Response::create($template[$status], 'view', $status)->assign(['e' => $e]); + } else { + return $this->convertExceptionToResponse($e); + } + } + + /** + * 收集异常数据 + * @param Throwable $exception + * @return array + */ + protected function convertExceptionToArray(Throwable $exception): array + { + if ($this->app->isDebug()) { + // 调试模式,获取详细的错误信息 + $traces = []; + $nextException = $exception; + do { + $traces[] = [ + 'name' => get_class($nextException), + 'file' => $nextException->getFile(), + 'line' => $nextException->getLine(), + 'code' => $this->getCode($nextException), + 'message' => $this->getMessage($nextException), + 'trace' => $nextException->getTrace(), + 'source' => $this->getSourceCode($nextException), + ]; + } while ($nextException = $nextException->getPrevious()); + $data = [ + 'code' => $this->getCode($exception), + 'message' => $this->getMessage($exception), + 'traces' => $traces, + 'datas' => $this->getExtendData($exception), + 'tables' => [ + 'GET Data' => $this->app->request->get(), + 'POST Data' => $this->app->request->post(), + 'Files' => $this->app->request->file(), + 'Cookies' => $this->app->request->cookie(), + 'Session' => $this->app->exists('session') ? $this->app->session->all() : [], + 'Server/Request Data' => $this->app->request->server(), + ], + ]; + } else { + // 部署模式仅显示 Code 和 Message + $data = [ + 'code' => $this->getCode($exception), + 'message' => $this->getMessage($exception), + ]; + + if (!$this->app->config->get('app.show_error_msg')) { + // 不显示详细错误信息 + $data['message'] = $this->app->config->get('app.error_message'); + } + } + + return $data; + } + + /** + * @access protected + * @param Throwable $exception + * @return Response + */ + protected function convertExceptionToResponse(Throwable $exception): Response + { + if (!$this->isJson) { + $response = Response::create($this->renderExceptionContent($exception)); + } else { + $response = Response::create($this->convertExceptionToArray($exception), 'json'); + } + + if ($exception instanceof HttpException) { + $statusCode = $exception->getStatusCode(); + $response->header($exception->getHeaders()); + } + + return $response->code($statusCode ?? 500); + } + + protected function renderExceptionContent(Throwable $exception): string + { + ob_start(); + $data = $this->convertExceptionToArray($exception); + extract($data); + include $this->app->config->get('app.exception_tmpl') ?: __DIR__ . '/../../tpl/think_exception.tpl'; + + return ob_get_clean(); + } + + /** + * 获取错误编码 + * ErrorException则使用错误级别作为错误编码 + * @access protected + * @param Throwable $exception + * @return integer 错误编码 + */ + protected function getCode(Throwable $exception) + { + $code = $exception->getCode(); + + if (!$code && $exception instanceof ErrorException) { + $code = $exception->getSeverity(); + } + + return $code; + } + + /** + * 获取错误信息 + * ErrorException则使用错误级别作为错误编码 + * @access protected + * @param Throwable $exception + * @return string 错误信息 + */ + protected function getMessage(Throwable $exception): string + { + $message = $exception->getMessage(); + + if ($this->app->runningInConsole()) { + return $message; + } + + $lang = $this->app->lang; + + if (strpos($message, ':')) { + $name = strstr($message, ':', true); + $message = $lang->has($name) ? $lang->get($name) . strstr($message, ':') : $message; + } elseif (strpos($message, ',')) { + $name = strstr($message, ',', true); + $message = $lang->has($name) ? $lang->get($name) . ':' . substr(strstr($message, ','), 1) : $message; + } elseif ($lang->has($message)) { + $message = $lang->get($message); + } + + return $message; + } + + /** + * 获取出错文件内容 + * 获取错误的前9行和后9行 + * @access protected + * @param Throwable $exception + * @return array 错误文件内容 + */ + protected function getSourceCode(Throwable $exception): array + { + // 读取前9行和后9行 + $line = $exception->getLine(); + $first = ($line - 9 > 0) ? $line - 9 : 1; + + try { + $contents = file($exception->getFile()) ?: []; + $source = [ + 'first' => $first, + 'source' => array_slice($contents, $first - 1, 19), + ]; + } catch (Exception $e) { + $source = []; + } + + return $source; + } + + /** + * 获取异常扩展信息 + * 用于非调试模式html返回类型显示 + * @access protected + * @param Throwable $exception + * @return array 异常类定义的扩展数据 + */ + protected function getExtendData(Throwable $exception): array + { + $data = []; + + if ($exception instanceof \think\Exception) { + $data = $exception->getData(); + } + + return $data; + } + + /** + * 获取常量列表 + * @access protected + * @return array 常量列表 + */ + protected function getConst(): array + { + $const = get_defined_constants(true); + + return $const['user'] ?? []; + } +} diff --git a/vendor/topthink/framework/src/think/exception/HttpException.php b/vendor/topthink/framework/src/think/exception/HttpException.php new file mode 100644 index 0000000..45302e5 --- /dev/null +++ b/vendor/topthink/framework/src/think/exception/HttpException.php @@ -0,0 +1,42 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\exception; + +use Exception; + +/** + * HTTP异常 + */ +class HttpException extends \RuntimeException +{ + private $statusCode; + private $headers; + + public function __construct(int $statusCode, string $message = '', Exception $previous = null, array $headers = [], $code = 0) + { + $this->statusCode = $statusCode; + $this->headers = $headers; + + parent::__construct($message, $code, $previous); + } + + public function getStatusCode() + { + return $this->statusCode; + } + + public function getHeaders() + { + return $this->headers; + } +} diff --git a/vendor/topthink/framework/src/think/exception/HttpResponseException.php b/vendor/topthink/framework/src/think/exception/HttpResponseException.php new file mode 100644 index 0000000..607813d --- /dev/null +++ b/vendor/topthink/framework/src/think/exception/HttpResponseException.php @@ -0,0 +1,37 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\exception; + +use think\Response; + +/** + * HTTP响应异常 + */ +class HttpResponseException extends \RuntimeException +{ + /** + * @var Response + */ + protected $response; + + public function __construct(Response $response) + { + $this->response = $response; + } + + public function getResponse() + { + return $this->response; + } + +} diff --git a/vendor/topthink/framework/src/think/exception/InvalidArgumentException.php b/vendor/topthink/framework/src/think/exception/InvalidArgumentException.php new file mode 100644 index 0000000..8ccd6f6 --- /dev/null +++ b/vendor/topthink/framework/src/think/exception/InvalidArgumentException.php @@ -0,0 +1,22 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); +namespace think\exception; + +use Psr\Cache\InvalidArgumentException as Psr6CacheInvalidArgumentInterface; +use Psr\SimpleCache\InvalidArgumentException as SimpleCacheInvalidArgumentInterface; + +/** + * 非法数据异常 + */ +class InvalidArgumentException extends \InvalidArgumentException implements Psr6CacheInvalidArgumentInterface, SimpleCacheInvalidArgumentInterface +{ +} diff --git a/vendor/topthink/framework/src/think/exception/RouteNotFoundException.php b/vendor/topthink/framework/src/think/exception/RouteNotFoundException.php new file mode 100644 index 0000000..7a2ee87 --- /dev/null +++ b/vendor/topthink/framework/src/think/exception/RouteNotFoundException.php @@ -0,0 +1,26 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\exception; + +/** + * 路由未定义异常 + */ +class RouteNotFoundException extends HttpException +{ + + public function __construct() + { + parent::__construct(404, 'Route Not Found'); + } + +} diff --git a/vendor/topthink/framework/src/think/exception/ValidateException.php b/vendor/topthink/framework/src/think/exception/ValidateException.php new file mode 100644 index 0000000..89b4e4d --- /dev/null +++ b/vendor/topthink/framework/src/think/exception/ValidateException.php @@ -0,0 +1,37 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\exception; + +/** + * 数据验证异常 + */ +class ValidateException extends \RuntimeException +{ + protected $error; + + public function __construct($error) + { + $this->error = $error; + $this->message = is_array($error) ? implode(PHP_EOL, $error) : $error; + } + + /** + * 获取验证错误信息 + * @access public + * @return array|string + */ + public function getError() + { + return $this->error; + } +} diff --git a/vendor/topthink/framework/src/think/facade/App.php b/vendor/topthink/framework/src/think/facade/App.php new file mode 100644 index 0000000..e9f8105 --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/App.php @@ -0,0 +1,59 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\App + * @package think\facade + * @mixin \think\App + * @method static \think\Service|null register(\think\Service|string $service, bool $force = false) 注册服务 + * @method static mixed bootService(\think\Service $service) 执行服务 + * @method static \think\Service|null getService(string|\think\Service $service) 获取服务 + * @method static \think\App debug(bool $debug = true) 开启应用调试模式 + * @method static bool isDebug() 是否为调试模式 + * @method static \think\App setNamespace(string $namespace) 设置应用命名空间 + * @method static string getNamespace() 获取应用类库命名空间 + * @method static string version() 获取框架版本 + * @method static string getRootPath() 获取应用根目录 + * @method static string getBasePath() 获取应用基础目录 + * @method static string getAppPath() 获取当前应用目录 + * @method static mixed setAppPath(string $path) 设置应用目录 + * @method static string getRuntimePath() 获取应用运行时目录 + * @method static void setRuntimePath(string $path) 设置runtime目录 + * @method static string getThinkPath() 获取核心框架目录 + * @method static string getConfigPath() 获取应用配置目录 + * @method static string getConfigExt() 获取配置后缀 + * @method static float getBeginTime() 获取应用开启时间 + * @method static integer getBeginMem() 获取应用初始内存占用 + * @method static \think\App initialize() 初始化应用 + * @method static bool initialized() 是否初始化过 + * @method static void loadLangPack(string $langset) 加载语言包 + * @method static void boot() 引导应用 + * @method static void loadEvent(array $event) 注册应用事件 + * @method static string parseClass(string $layer, string $name) 解析应用类的类名 + * @method static bool runningInConsole() 是否运行在命令行下 + */ +class App extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'app'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/Cache.php b/vendor/topthink/framework/src/think/facade/Cache.php new file mode 100644 index 0000000..aac105d --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/Cache.php @@ -0,0 +1,48 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\cache\Driver; +use think\cache\TagSet; +use think\Facade; + +/** + * @see \think\Cache + * @package think\facade + * @mixin \think\Cache + * @method static string|null getDefaultDriver() 默认驱动 + * @method static mixed getConfig(null|string $name = null, mixed $default = null) 获取缓存配置 + * @method static array getStoreConfig(string $store, string $name = null, null $default = null) 获取驱动配置 + * @method static Driver store(string $name = null) 连接或者切换缓存 + * @method static bool clear() 清空缓冲池 + * @method static mixed get(string $key, mixed $default = null) 读取缓存 + * @method static bool set(string $key, mixed $value, int|\DateTime $ttl = null) 写入缓存 + * @method static bool delete(string $key) 删除缓存 + * @method static iterable getMultiple(iterable $keys, mixed $default = null) 读取缓存 + * @method static bool setMultiple(iterable $values, null|int|\DateInterval $ttl = null) 写入缓存 + * @method static bool deleteMultiple(iterable $keys) 删除缓存 + * @method static bool has(string $key) 判断缓存是否存在 + * @method static TagSet tag(string|array $name) 缓存标签 + */ +class Cache extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'cache'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/Config.php b/vendor/topthink/framework/src/think/facade/Config.php new file mode 100644 index 0000000..4ce73dd --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/Config.php @@ -0,0 +1,37 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Config + * @package think\facade + * @mixin \think\Config + * @method static array load(string $file, string $name = '') 加载配置文件(多种格式) + * @method static bool has(string $name) 检测配置是否存在 + * @method static mixed get(string $name = null, mixed $default = null) 获取配置参数 为空则获取所有配置 + * @method static array set(array $config, string $name = null) 设置配置参数 name为数组则为批量设置 + */ +class Config extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'config'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/Console.php b/vendor/topthink/framework/src/think/facade/Console.php new file mode 100644 index 0000000..30dd935 --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/Console.php @@ -0,0 +1,56 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\console\Command; +use think\console\Input; +use think\console\input\Definition as InputDefinition; +use think\console\Output; +use think\console\output\driver\Buffer; +use think\Facade; + +/** + * Class Console + * @package think\facade + * @mixin \think\Console + * @method static Output|Buffer call(string $command, array $parameters = [], string $driver = 'buffer') + * @method static int run() 执行当前的指令 + * @method static int doRun(Input $input, Output $output) 执行指令 + * @method static void setDefinition(InputDefinition $definition) 设置输入参数定义 + * @method static InputDefinition The InputDefinition instance getDefinition() 获取输入参数定义 + * @method static string A help message. getHelp() Gets the help message. + * @method static void setCatchExceptions(bool $boolean) 是否捕获异常 + * @method static void setAutoExit(bool $boolean) 是否自动退出 + * @method static string getLongVersion() 获取完整的版本号 + * @method static void addCommands(array $commands) 添加指令集 + * @method static Command|void addCommand(string|Command $command, string $name = '') 添加一个指令 + * @method static Command getCommand(string $name) 获取指令 + * @method static bool hasCommand(string $name) 某个指令是否存在 + * @method static array getNamespaces() 获取所有的命名空间 + * @method static string findNamespace(string $namespace) 查找注册命名空间中的名称或缩写。 + * @method static Command find(string $name) 查找指令 + * @method static Command[] all(string $namespace = null) 获取所有的指令 + * @method static string extractNamespace(string $name, int $limit = 0) 返回命名空间部分 + */ +class Console extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'console'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/Cookie.php b/vendor/topthink/framework/src/think/facade/Cookie.php new file mode 100644 index 0000000..960f4a3 --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/Cookie.php @@ -0,0 +1,40 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Cookie + * @package think\facade + * @mixin \think\Cookie + * @method static mixed get(mixed $name = '', string $default = null) 获取cookie + * @method static bool has(string $name) 是否存在Cookie参数 + * @method static void set(string $name, string $value, mixed $option = null) Cookie 设置 + * @method static void forever(string $name, string $value = '', mixed $option = null) 永久保存Cookie数据 + * @method static void delete(string $name) Cookie删除 + * @method static array getCookie() 获取cookie保存数据 + * @method static void save() 保存Cookie + */ +class Cookie extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'cookie'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/Env.php b/vendor/topthink/framework/src/think/facade/Env.php new file mode 100644 index 0000000..bed2538 --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/Env.php @@ -0,0 +1,44 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Env + * @package think\facade + * @mixin \think\Env + * @method static void load(string $file) 读取环境变量定义文件 + * @method static mixed get(string $name = null, mixed $default = null) 获取环境变量值 + * @method static void set(string|array $env, mixed $value = null) 设置环境变量值 + * @method static bool has(string $name) 检测是否存在环境变量 + * @method static void __set(string $name, mixed $value) 设置环境变量 + * @method static mixed __get(string $name) 获取环境变量 + * @method static bool __isset(string $name) 检测是否存在环境变量 + * @method static void offsetSet($name, $value) + * @method static bool offsetExists($name) + * @method static mixed offsetUnset($name) + * @method static mixed offsetGet($name) + */ +class Env extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'env'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/Event.php b/vendor/topthink/framework/src/think/facade/Event.php new file mode 100644 index 0000000..c09d816 --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/Event.php @@ -0,0 +1,42 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Event + * @package think\facade + * @mixin \think\Event + * @method static \think\Event listenEvents(array $events) 批量注册事件监听 + * @method static \think\Event listen(string $event, mixed $listener, bool $first = false) 注册事件监听 + * @method static bool hasListener(string $event) 是否存在事件监听 + * @method static void remove(string $event) 移除事件监听 + * @method static \think\Event bind(array $events) 指定事件别名标识 便于调用 + * @method static \think\Event subscribe(mixed $subscriber) 注册事件订阅者 + * @method static \think\Event observe(string|object $observer, null|string $prefix = '') 自动注册事件观察者 + * @method static mixed trigger(string|object $event, mixed $params = null, bool $once = false) 触发事件 + * @method static mixed until($event, $params = null) 触发事件(只获取一个有效返回值) + */ +class Event extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'event'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/Filesystem.php b/vendor/topthink/framework/src/think/facade/Filesystem.php new file mode 100644 index 0000000..53706a8 --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/Filesystem.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; +use think\filesystem\Driver; + +/** + * Class Filesystem + * @package think\facade + * @mixin \think\Filesystem + * @method static Driver disk(string $name = null) ,null|string + * @method static mixed getConfig(null|string $name = null, mixed $default = null) 获取缓存配置 + * @method static array getDiskConfig(string $disk, null $name = null, null $default = null) 获取磁盘配置 + * @method static string|null getDefaultDriver() 默认驱动 + */ +class Filesystem extends Facade +{ + protected static function getFacadeClass() + { + return 'filesystem'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/Lang.php b/vendor/topthink/framework/src/think/facade/Lang.php new file mode 100644 index 0000000..b460fe2 --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/Lang.php @@ -0,0 +1,41 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Lang + * @package think\facade + * @mixin \think\Lang + * @method static void setLangSet(string $lang) 设置当前语言 + * @method static string getLangSet() 获取当前语言 + * @method static string defaultLangSet() 获取默认语言 + * @method static array load(string|array $file, string $range = '') 加载语言定义(不区分大小写) + * @method static bool has(string|null $name, string $range = '') 判断是否存在语言定义(不区分大小写) + * @method static mixed get(string|null $name = null, array $vars = [], string $range = '') 获取语言定义(不区分大小写) + * @method static string detect(\think\Request $request) 自动侦测设置获取语言选择 + * @method static void saveToCookie(\think\Cookie $cookie) 保存当前语言到Cookie + */ +class Lang extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'lang'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/Log.php b/vendor/topthink/framework/src/think/facade/Log.php new file mode 100644 index 0000000..7c43d37 --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/Log.php @@ -0,0 +1,58 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; +use think\log\Channel; +use think\log\ChannelSet; + +/** + * @see \think\Log + * @package think\facade + * @mixin \think\Log + * @method static string|null getDefaultDriver() 默认驱动 + * @method static mixed getConfig(null|string $name = null, mixed $default = null) 获取日志配置 + * @method static array getChannelConfig(string $channel, null $name = null, null $default = null) 获取渠道配置 + * @method static Channel|ChannelSet channel(string|array $name = null) driver() 的别名 + * @method static mixed createDriver(string $name) + * @method static \think\Log clear(string|array $channel = '*') 清空日志信息 + * @method static \think\Log close(string|array $channel = '*') 关闭本次请求日志写入 + * @method static array getLog(string $channel = null) 获取日志信息 + * @method static bool save() 保存日志信息 + * @method static \think\Log record(mixed $msg, string $type = 'info', array $context = [], bool $lazy = true) 记录日志信息 + * @method static \think\Log write(mixed $msg, string $type = 'info', array $context = []) 实时写入日志信息 + * @method static Event listen($listener) 注册日志写入事件监听 + * @method static void log(string $level, mixed $message, array $context = []) 记录日志信息 + * @method static void emergency(mixed $message, array $context = []) 记录emergency信息 + * @method static void alert(mixed $message, array $context = []) 记录警报信息 + * @method static void critical(mixed $message, array $context = []) 记录紧急情况 + * @method static void error(mixed $message, array $context = []) 记录错误信息 + * @method static void warning(mixed $message, array $context = []) 记录warning信息 + * @method static void notice(mixed $message, array $context = []) 记录notice信息 + * @method static void info(mixed $message, array $context = []) 记录一般信息 + * @method static void debug(mixed $message, array $context = []) 记录调试信息 + * @method static void sql(mixed $message, array $context = []) 记录sql信息 + * @method static mixed __call($method, $parameters) + */ +class Log extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'log'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/Middleware.php b/vendor/topthink/framework/src/think/facade/Middleware.php new file mode 100644 index 0000000..4203f82 --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/Middleware.php @@ -0,0 +1,42 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Middleware + * @package think\facade + * @mixin \think\Middleware + * @method static void import(array $middlewares = [], string $type = 'global') 导入中间件 + * @method static void add(mixed $middleware, string $type = 'global') 注册中间件 + * @method static void route(mixed $middleware) 注册路由中间件 + * @method static void controller(mixed $middleware) 注册控制器中间件 + * @method static mixed unshift(mixed $middleware, string $type = 'global') 注册中间件到开始位置 + * @method static array all(string $type = 'global') 获取注册的中间件 + * @method static Pipeline pipeline(string $type = 'global') 调度管道 + * @method static mixed end(\think\Response $response) 结束调度 + * @method static \think\Response handleException(\think\Request $passable, \Throwable $e) 异常处理 + */ +class Middleware extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'middleware'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/Request.php b/vendor/topthink/framework/src/think/facade/Request.php new file mode 100644 index 0000000..6531f46 --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/Request.php @@ -0,0 +1,134 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; +use think\file\UploadedFile; +use think\route\Rule; + +/** + * @see \think\Request + * @package think\facade + * @mixin \think\Request + * @method static \think\Request setDomain(string $domain) 设置当前包含协议的域名 + * @method static string domain(bool $port = false) 获取当前包含协议的域名 + * @method static string rootDomain() 获取当前根域名 + * @method static \think\Request setSubDomain(string $domain) 设置当前泛域名的值 + * @method static string subDomain() 获取当前子域名 + * @method static \think\Request setPanDomain(string $domain) 设置当前泛域名的值 + * @method static string panDomain() 获取当前泛域名的值 + * @method static \think\Request setUrl(string $url) 设置当前完整URL 包括QUERY_STRING + * @method static string url(bool $complete = false) 获取当前完整URL 包括QUERY_STRING + * @method static \think\Request setBaseUrl(string $url) 设置当前URL 不含QUERY_STRING + * @method static string baseUrl(bool $complete = false) 获取当前URL 不含QUERY_STRING + * @method static string baseFile(bool $complete = false) 获取当前执行的文件 SCRIPT_NAME + * @method static \think\Request setRoot(string $url) 设置URL访问根地址 + * @method static string root(bool $complete = false) 获取URL访问根地址 + * @method static string rootUrl() 获取URL访问根目录 + * @method static \think\Request setPathinfo(string $pathinfo) 设置当前请求的pathinfo + * @method static string pathinfo() 获取当前请求URL的pathinfo信息(含URL后缀) + * @method static string ext() 当前URL的访问后缀 + * @method static integer|float time(bool $float = false) 获取当前请求的时间 + * @method static string type() 当前请求的资源类型 + * @method static void mimeType(string|array $type, string $val = '') 设置资源类型 + * @method static \think\Request setMethod(string $method) 设置请求类型 + * @method static string method(bool $origin = false) 当前的请求类型 + * @method static bool isGet() 是否为GET请求 + * @method static bool isPost() 是否为POST请求 + * @method static bool isPut() 是否为PUT请求 + * @method static bool isDelete() 是否为DELTE请求 + * @method static bool isHead() 是否为HEAD请求 + * @method static bool isPatch() 是否为PATCH请求 + * @method static bool isOptions() 是否为OPTIONS请求 + * @method static bool isCli() 是否为cli + * @method static bool isCgi() 是否为cgi + * @method static mixed param(string|array $name = '', mixed $default = null, string|array $filter = '') 获取当前请求的参数 + * @method static \think\Request setRule(Rule $rule) 设置路由变量 + * @method static Rule|null rule() 获取当前路由对象 + * @method static \think\Request setRoute(array $route) 设置路由变量 + * @method static mixed route(string|array $name = '', mixed $default = null, string|array $filter = '') 获取路由参数 + * @method static mixed get(string|array $name = '', mixed $default = null, string|array $filter = '') 获取GET参数 + * @method static mixed middleware(mixed $name, mixed $default = null) 获取中间件传递的参数 + * @method static mixed post(string|array $name = '', mixed $default = null, string|array $filter = '') 获取POST参数 + * @method static mixed put(string|array $name = '', mixed $default = null, string|array $filter = '') 获取PUT参数 + * @method static mixed delete(mixed $name = '', mixed $default = null, string|array $filter = '') 设置获取DELETE参数 + * @method static mixed patch(mixed $name = '', mixed $default = null, string|array $filter = '') 设置获取PATCH参数 + * @method static mixed request(string|array $name = '', mixed $default = null, string|array $filter = '') 获取request变量 + * @method static mixed env(string $name = '', string $default = null) 获取环境变量 + * @method static mixed session(string $name = '', string $default = null) 获取session数据 + * @method static mixed cookie(mixed $name = '', string $default = null, string|array $filter = '') 获取cookie参数 + * @method static mixed server(string $name = '', string $default = '') 获取server参数 + * @method static null|array|UploadedFile file(string $name = '') 获取上传的文件信息 + * @method static string|array header(string $name = '', string $default = null) 设置或者获取当前的Header + * @method static mixed input(array $data = [], string|false $name = '', mixed $default = null, string|array $filter = '') 获取变量 支持过滤和默认值 + * @method static mixed filter(mixed $filter = null) 设置或获取当前的过滤规则 + * @method static mixed filterValue(mixed &$value, mixed $key, array $filters) 递归过滤给定的值 + * @method static bool has(string $name, string $type = 'param', bool $checkEmpty = false) 是否存在某个请求参数 + * @method static array only(array $name, mixed $data = 'param', string|array $filter = '') 获取指定的参数 + * @method static mixed except(array $name, string $type = 'param') 排除指定参数获取 + * @method static bool isSsl() 当前是否ssl + * @method static bool isJson() 当前是否JSON请求 + * @method static bool isAjax(bool $ajax = false) 当前是否Ajax请求 + * @method static bool isPjax(bool $pjax = false) 当前是否Pjax请求 + * @method static string ip() 获取客户端IP地址 + * @method static boolean isValidIP(string $ip, string $type = '') 检测是否是合法的IP地址 + * @method static string ip2bin(string $ip) 将IP地址转换为二进制字符串 + * @method static bool isMobile() 检测是否使用手机访问 + * @method static string scheme() 当前URL地址中的scheme参数 + * @method static string query() 当前请求URL地址中的query参数 + * @method static \think\Request setHost(string $host) 设置当前请求的host(包含端口) + * @method static string host(bool $strict = false) 当前请求的host + * @method static int port() 当前请求URL地址中的port参数 + * @method static string protocol() 当前请求 SERVER_PROTOCOL + * @method static int remotePort() 当前请求 REMOTE_PORT + * @method static string contentType() 当前请求 HTTP_CONTENT_TYPE + * @method static string secureKey() 获取当前请求的安全Key + * @method static \think\Request setController(string $controller) 设置当前的控制器名 + * @method static \think\Request setAction(string $action) 设置当前的操作名 + * @method static string controller(bool $convert = false) 获取当前的控制器名 + * @method static string action(bool $convert = false) 获取当前的操作名 + * @method static string getContent() 设置或者获取当前请求的content + * @method static string getInput() 获取当前请求的php://input + * @method static string buildToken(string $name = '__token__', mixed $type = 'md5') 生成请求令牌 + * @method static bool checkToken(string $token = '__token__', array $data = []) 检查请求令牌 + * @method static \think\Request withMiddleware(array $middleware) 设置在中间件传递的数据 + * @method static \think\Request withGet(array $get) 设置GET数据 + * @method static \think\Request withPost(array $post) 设置POST数据 + * @method static \think\Request withCookie(array $cookie) 设置COOKIE数据 + * @method static \think\Request withSession(Session $session) 设置SESSION数据 + * @method static \think\Request withServer(array $server) 设置SERVER数据 + * @method static \think\Request withHeader(array $header) 设置HEADER数据 + * @method static \think\Request withEnv(Env $env) 设置ENV数据 + * @method static \think\Request withInput(string $input) 设置php://input数据 + * @method static \think\Request withFiles(array $files) 设置文件上传数据 + * @method static \think\Request withRoute(array $route) 设置ROUTE变量 + * @method static mixed __set(string $name, mixed $value) 设置中间传递数据 + * @method static mixed __get(string $name) 获取中间传递数据的值 + * @method static boolean __isset(string $name) 检测中间传递数据的值 + * @method static bool offsetExists($name) + * @method static mixed offsetGet($name) + * @method static mixed offsetSet($name, $value) + * @method static mixed offsetUnset($name) + */ +class Request extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'request'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/Route.php b/vendor/topthink/framework/src/think/facade/Route.php new file mode 100644 index 0000000..5a5b955 --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/Route.php @@ -0,0 +1,83 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; +use think\route\Dispatch; +use think\route\Domain; +use think\route\Rule; +use think\route\RuleGroup; +use think\route\RuleItem; +use think\route\RuleName; +use think\route\Url as UrlBuild; + +/** + * @see \think\Route + * @package think\facade + * @mixin \think\Route + * @method static mixed config(string $name = null) + * @method static \think\Route lazy(bool $lazy = true) 设置路由域名及分组(包括资源路由)是否延迟解析 + * @method static void setTestMode(bool $test) 设置路由为测试模式 + * @method static bool isTest() 检查路由是否为测试模式 + * @method static \think\Route mergeRuleRegex(bool $merge = true) 设置路由域名及分组(包括资源路由)是否合并解析 + * @method static void setGroup(RuleGroup $group) 设置当前分组 + * @method static RuleGroup getGroup(string $name = null) 获取指定标识的路由分组 不指定则获取当前分组 + * @method static \think\Route pattern(array $pattern) 注册变量规则 + * @method static \think\Route option(array $option) 注册路由参数 + * @method static Domain domain(string|array $name, mixed $rule = null) 注册域名路由 + * @method static array getDomains() 获取域名 + * @method static RuleName getRuleName() 获取RuleName对象 + * @method static \think\Route bind(string $bind, string $domain = null) 设置路由绑定 + * @method static array getBind() 读取路由绑定信息 + * @method static string|null getDomainBind(string $domain = null) 读取路由绑定 + * @method static RuleItem[] getName(string $name = null, string $domain = null, string $method = '*') 读取路由标识 + * @method static void import(array $name) 批量导入路由标识 + * @method static void setName(string $name, RuleItem $ruleItem, bool $first = false) 注册路由标识 + * @method static void setRule(string $rule, RuleItem $ruleItem = null) 保存路由规则 + * @method static RuleItem[] getRule(string $rule) 读取路由 + * @method static array getRuleList() 读取路由列表 + * @method static void clear() 清空路由规则 + * @method static RuleItem rule(string $rule, mixed $route = null, string $method = '*') 注册路由规则 + * @method static \think\Route setCrossDomainRule(Rule $rule, string $method = '*') 设置跨域有效路由规则 + * @method static RuleGroup group(string|\Closure $name, mixed $route = null) 注册路由分组 + * @method static RuleItem any(string $rule, mixed $route) 注册路由 + * @method static RuleItem get(string $rule, mixed $route) 注册GET路由 + * @method static RuleItem post(string $rule, mixed $route) 注册POST路由 + * @method static RuleItem put(string $rule, mixed $route) 注册PUT路由 + * @method static RuleItem delete(string $rule, mixed $route) 注册DELETE路由 + * @method static RuleItem patch(string $rule, mixed $route) 注册PATCH路由 + * @method static RuleItem options(string $rule, mixed $route) 注册OPTIONS路由 + * @method static Resource resource(string $rule, string $route) 注册资源路由 + * @method static RuleItem view(string $rule, string $template = '', array $vars = []) 注册视图路由 + * @method static RuleItem redirect(string $rule, string $route = '', int $status = 301) 注册重定向路由 + * @method static \think\Route rest(string|array $name, array|bool $resource = []) rest方法定义和修改 + * @method static array|null getRest(string $name = null) 获取rest方法定义的参数 + * @method static RuleItem miss(string|\Closure $route, string $method = '*') 注册未匹配路由规则后的处理 + * @method static Response dispatch(\think\Request $request, Closure|bool $withRoute = true) 路由调度 + * @method static Dispatch|false check() 检测URL路由 + * @method static Dispatch url(string $url) 默认URL解析 + * @method static UrlBuild buildUrl(string $url = '', array $vars = []) URL生成 支持路由反射 + * @method static RuleGroup __call(string $method, array $args) 设置全局的路由分组参数 + */ +class Route extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'route'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/Session.php b/vendor/topthink/framework/src/think/facade/Session.php new file mode 100644 index 0000000..68bf993 --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/Session.php @@ -0,0 +1,35 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Session + * @package think\facade + * @mixin \think\Session + * @method static mixed getConfig(null|string $name = null, mixed $default = null) 获取Session配置 + * @method static string|null getDefaultDriver() 默认驱动 + */ +class Session extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'session'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/Validate.php b/vendor/topthink/framework/src/think/facade/Validate.php new file mode 100644 index 0000000..6db6d34 --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/Validate.php @@ -0,0 +1,95 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Validate + * @package think\facade + * @mixin \think\Validate + * @method static void setLang(\think\Lang $lang) 设置Lang对象 + * @method static void setDb(\think\Db $db) 设置Db对象 + * @method static void setRequest(\think\Request $request) 设置Request对象 + * @method static \think\Validate rule(string|array $name, mixed $rule = '') 添加字段验证规则 + * @method static \think\Validate extend(string $type, callable $callback = null, string $message = null) 注册验证(类型)规则 + * @method static void setTypeMsg(string|array $type, string $msg = null) 设置验证规则的默认提示信息 + * @method static Validate message(array $message) 设置提示信息 + * @method static \think\Validate scene(string $name) 设置验证场景 + * @method static bool hasScene(string $name) 判断是否存在某个验证场景 + * @method static \think\Validate batch(bool $batch = true) 设置批量验证 + * @method static \think\Validate failException(bool $fail = true) 设置验证失败后是否抛出异常 + * @method static \think\Validate only(array $fields) 指定需要验证的字段列表 + * @method static \think\Validate remove(string|array $field, mixed $rule = null) 移除某个字段的验证规则 + * @method static \think\Validate append(string|array $field, mixed $rule = null) 追加某个字段的验证规则 + * @method static bool check(array $data, array $rules = []) 数据自动验证 + * @method static bool checkRule(mixed $value, mixed $rules) 根据验证规则验证数据 + * @method static bool confirm(mixed $value, mixed $rule, array $data = [], string $field = '') 验证是否和某个字段的值一致 + * @method static bool different(mixed $value, mixed $rule, array $data = []) 验证是否和某个字段的值是否不同 + * @method static bool egt(mixed $value, mixed $rule, array $data = []) 验证是否大于等于某个值 + * @method static bool gt(mixed $value, mixed $rule, array $data = []) 验证是否大于某个值 + * @method static bool elt(mixed $value, mixed $rule, array $data = []) 验证是否小于等于某个值 + * @method static bool lt(mixed $value, mixed $rule, array $data = []) 验证是否小于某个值 + * @method static bool eq(mixed $value, mixed $rule) 验证是否等于某个值 + * @method static bool must(mixed $value, mixed $rule = null) 必须验证 + * @method static bool is(mixed $value, string $rule, array $data = []) 验证字段值是否为有效格式 + * @method static bool token(mixed $value, mixed $rule, array $data) 验证表单令牌 + * @method static bool activeUrl(mixed $value, mixed $rule = 'MX') 验证是否为合格的域名或者IP 支持A,MX,NS,SOA,PTR,CNAME,AAAA,A6, SRV,NAPTR,TXT 或者 ANY类型 + * @method static bool ip(mixed $value, mixed $rule = 'ipv4') 验证是否有效IP + * @method static bool fileExt(mixed $file, mixed $rule) 验证上传文件后缀 + * @method static bool fileMime(mixed $file, mixed $rule) 验证上传文件类型 + * @method static bool fileSize(mixed $file, mixed $rule) 验证上传文件大小 + * @method static bool image(mixed $file, mixed $rule) 验证图片的宽高及类型 + * @method static bool dateFormat(mixed $value, mixed $rule) 验证时间和日期是否符合指定格式 + * @method static bool unique(mixed $value, mixed $rule, array $data = [], string $field = '') 验证是否唯一 + * @method static bool filter(mixed $value, mixed $rule) 使用filter_var方式验证 + * @method static bool requireIf(mixed $value, mixed $rule, array $data = []) 验证某个字段等于某个值的时候必须 + * @method static bool requireCallback(mixed $value, mixed $rule, array $data = []) 通过回调方法验证某个字段是否必须 + * @method static bool requireWith(mixed $value, mixed $rule, array $data = []) 验证某个字段有值的情况下必须 + * @method static bool requireWithout(mixed $value, mixed $rule, array $data = []) 验证某个字段没有值的情况下必须 + * @method static bool in(mixed $value, mixed $rule) 验证是否在范围内 + * @method static bool notIn(mixed $value, mixed $rule) 验证是否不在某个范围 + * @method static bool between(mixed $value, mixed $rule) between验证数据 + * @method static bool notBetween(mixed $value, mixed $rule) 使用notbetween验证数据 + * @method static bool length(mixed $value, mixed $rule) 验证数据长度 + * @method static bool max(mixed $value, mixed $rule) 验证数据最大长度 + * @method static bool min(mixed $value, mixed $rule) 验证数据最小长度 + * @method static bool after(mixed $value, mixed $rule, array $data = []) 验证日期 + * @method static bool before(mixed $value, mixed $rule, array $data = []) 验证日期 + * @method static bool afterWith(mixed $value, mixed $rule, array $data = []) 验证日期 + * @method static bool beforeWith(mixed $value, mixed $rule, array $data = []) 验证日期 + * @method static bool expire(mixed $value, mixed $rule) 验证有效期 + * @method static bool allowIp(mixed $value, mixed $rule) 验证IP许可 + * @method static bool denyIp(mixed $value, mixed $rule) 验证IP禁用 + * @method static bool regex(mixed $value, mixed $rule) 使用正则验证数据 + * @method static array|string getError() 获取错误信息 + * @method static bool __call(string $method, array $args) 动态方法 直接调用is方法进行验证 + */ +class Validate extends Facade +{ + /** + * 始终创建新的对象实例 + * @var bool + */ + protected static $alwaysNewInstance = true; + + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'validate'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/View.php b/vendor/topthink/framework/src/think/facade/View.php new file mode 100644 index 0000000..acde3b5 --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/View.php @@ -0,0 +1,42 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\View + * @package think\facade + * @mixin \think\View + * @method static \think\View engine(string $type = null) 获取模板引擎 + * @method static \think\View assign(string|array $name, mixed $value = null) 模板变量赋值 + * @method static \think\View filter(\think\Callable $filter = null) 视图过滤 + * @method static string fetch(string $template = '', array $vars = []) 解析和获取模板内容 用于输出 + * @method static string display(string $content, array $vars = []) 渲染内容输出 + * @method static mixed __set(string $name, mixed $value) 模板变量赋值 + * @method static mixed __get(string $name) 取得模板显示变量的值 + * @method static bool __isset(string $name) 检测模板变量是否设置 + * @method static string|null getDefaultDriver() 默认驱动 + */ +class View extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'view'; + } +} diff --git a/vendor/topthink/framework/src/think/file/UploadedFile.php b/vendor/topthink/framework/src/think/file/UploadedFile.php new file mode 100644 index 0000000..7dff766 --- /dev/null +++ b/vendor/topthink/framework/src/think/file/UploadedFile.php @@ -0,0 +1,143 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\file; + +use think\exception\FileException; +use think\File; + +class UploadedFile extends File +{ + + private $test = false; + private $originalName; + private $mimeType; + private $error; + + public function __construct(string $path, string $originalName, string $mimeType = null, int $error = null, bool $test = false) + { + $this->originalName = $originalName; + $this->mimeType = $mimeType ?: 'application/octet-stream'; + $this->test = $test; + $this->error = $error ?: UPLOAD_ERR_OK; + + parent::__construct($path, UPLOAD_ERR_OK === $this->error); + } + + public function isValid(): bool + { + $isOk = UPLOAD_ERR_OK === $this->error; + + return $this->test ? $isOk : $isOk && is_uploaded_file($this->getPathname()); + } + + /** + * 上传文件 + * @access public + * @param string $directory 保存路径 + * @param string|null $name 保存的文件名 + * @return File + */ + public function move(string $directory, string $name = null): File + { + if ($this->isValid()) { + if ($this->test) { + return parent::move($directory, $name); + } + + $target = $this->getTargetFile($directory, $name); + + set_error_handler(function ($type, $msg) use (&$error) { + $error = $msg; + }); + + $moved = move_uploaded_file($this->getPathname(), (string) $target); + restore_error_handler(); + if (!$moved) { + throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error))); + } + + @chmod((string) $target, 0666 & ~umask()); + + return $target; + } + + throw new FileException($this->getErrorMessage()); + } + + /** + * 获取错误信息 + * @access public + * @return string + */ + protected function getErrorMessage(): string + { + switch ($this->error) { + case 1: + case 2: + $message = 'upload File size exceeds the maximum value'; + break; + case 3: + $message = 'only the portion of file is uploaded'; + break; + case 4: + $message = 'no file to uploaded'; + break; + case 6: + $message = 'upload temp dir not found'; + break; + case 7: + $message = 'file write error'; + break; + default: + $message = 'unknown upload error'; + } + + return $message; + } + + /** + * 获取上传文件类型信息 + * @return string + */ + public function getOriginalMime(): string + { + return $this->mimeType; + } + + /** + * 上传文件名 + * @return string + */ + public function getOriginalName(): string + { + return $this->originalName; + } + + /** + * 获取上传文件扩展名 + * @return string + */ + public function getOriginalExtension(): string + { + return pathinfo($this->originalName, PATHINFO_EXTENSION); + } + + /** + * 获取文件扩展名 + * @return string + */ + public function extension(): string + { + return $this->getOriginalExtension(); + } +} diff --git a/vendor/topthink/framework/src/think/filesystem/CacheStore.php b/vendor/topthink/framework/src/think/filesystem/CacheStore.php new file mode 100644 index 0000000..0a62399 --- /dev/null +++ b/vendor/topthink/framework/src/think/filesystem/CacheStore.php @@ -0,0 +1,54 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\filesystem; + +use League\Flysystem\Cached\Storage\AbstractCache; +use Psr\SimpleCache\CacheInterface; + +class CacheStore extends AbstractCache +{ + protected $store; + + protected $key; + + protected $expire; + + public function __construct(CacheInterface $store, $key = 'flysystem', $expire = null) + { + $this->key = $key; + $this->store = $store; + $this->expire = $expire; + } + + /** + * Store the cache. + */ + public function save() + { + $contents = $this->getForStorage(); + + $this->store->set($this->key, $contents, $this->expire); + } + + /** + * Load the cache. + */ + public function load() + { + $contents = $this->store->get($this->key); + + if (!is_null($contents)) { + $this->setFromStorage($contents); + } + } +} diff --git a/vendor/topthink/framework/src/think/filesystem/Driver.php b/vendor/topthink/framework/src/think/filesystem/Driver.php new file mode 100644 index 0000000..6712959 --- /dev/null +++ b/vendor/topthink/framework/src/think/filesystem/Driver.php @@ -0,0 +1,133 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\filesystem; + +use League\Flysystem\AdapterInterface; +use League\Flysystem\Adapter\AbstractAdapter; +use League\Flysystem\Cached\CachedAdapter; +use League\Flysystem\Cached\Storage\Memory as MemoryStore; +use League\Flysystem\Filesystem; +use think\Cache; +use think\File; + +/** + * Class Driver + * @package think\filesystem + * @mixin Filesystem + */ +abstract class Driver +{ + + /** @var Cache */ + protected $cache; + + /** @var Filesystem */ + protected $filesystem; + + /** + * 配置参数 + * @var array + */ + protected $config = []; + + public function __construct(Cache $cache, array $config) + { + $this->cache = $cache; + $this->config = array_merge($this->config, $config); + + $adapter = $this->createAdapter(); + $this->filesystem = $this->createFilesystem($adapter); + } + + protected function createCacheStore($config) + { + if (true === $config) { + return new MemoryStore; + } + + return new CacheStore( + $this->cache->store($config['store']), + $config['prefix'] ?? 'flysystem', + $config['expire'] ?? null + ); + } + + abstract protected function createAdapter(): AdapterInterface; + + protected function createFilesystem(AdapterInterface $adapter): Filesystem + { + if (!empty($this->config['cache'])) { + $adapter = new CachedAdapter($adapter, $this->createCacheStore($this->config['cache'])); + } + + $config = array_intersect_key($this->config, array_flip(['visibility', 'disable_asserts', 'url'])); + + return new Filesystem($adapter, count($config) > 0 ? $config : null); + } + + /** + * 获取文件完整路径 + * @param string $path + * @return string + */ + public function path(string $path): string + { + $adapter = $this->filesystem->getAdapter(); + + if ($adapter instanceof AbstractAdapter) { + return $adapter->applyPathPrefix($path); + } + + return $path; + } + + /** + * 保存文件 + * @param string $path 路径 + * @param File $file 文件 + * @param null|string|\Closure $rule 文件名规则 + * @param array $options 参数 + * @return bool|string + */ + public function putFile(string $path, File $file, $rule = null, array $options = []) + { + return $this->putFileAs($path, $file, $file->hashName($rule), $options); + } + + /** + * 指定文件名保存文件 + * @param string $path 路径 + * @param File $file 文件 + * @param string $name 文件名 + * @param array $options 参数 + * @return bool|string + */ + public function putFileAs(string $path, File $file, string $name, array $options = []) + { + $stream = fopen($file->getRealPath(), 'r'); + $path = trim($path . '/' . $name, '/'); + + $result = $this->putStream($path, $stream, $options); + + if (is_resource($stream)) { + fclose($stream); + } + + return $result ? $path : false; + } + + public function __call($method, $parameters) + { + return $this->filesystem->$method(...$parameters); + } +} diff --git a/vendor/topthink/framework/src/think/filesystem/driver/Local.php b/vendor/topthink/framework/src/think/filesystem/driver/Local.php new file mode 100644 index 0000000..c10ccc3 --- /dev/null +++ b/vendor/topthink/framework/src/think/filesystem/driver/Local.php @@ -0,0 +1,44 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\filesystem\driver; + +use League\Flysystem\AdapterInterface; +use League\Flysystem\Adapter\Local as LocalAdapter; +use think\filesystem\Driver; + +class Local extends Driver +{ + /** + * 配置参数 + * @var array + */ + protected $config = [ + 'root' => '', + ]; + + protected function createAdapter(): AdapterInterface + { + $permissions = $this->config['permissions'] ?? []; + + $links = ($this->config['links'] ?? null) === 'skip' + ? LocalAdapter::SKIP_LINKS + : LocalAdapter::DISALLOW_LINKS; + + return new LocalAdapter( + $this->config['root'], + LOCK_EX, + $links, + $permissions + ); + } +} diff --git a/vendor/topthink/framework/src/think/initializer/BootService.php b/vendor/topthink/framework/src/think/initializer/BootService.php new file mode 100644 index 0000000..bab6d39 --- /dev/null +++ b/vendor/topthink/framework/src/think/initializer/BootService.php @@ -0,0 +1,26 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\initializer; + +use think\App; + +/** + * 启动系统服务 + */ +class BootService +{ + public function init(App $app) + { + $app->boot(); + } +} diff --git a/vendor/topthink/framework/src/think/initializer/Error.php b/vendor/topthink/framework/src/think/initializer/Error.php new file mode 100644 index 0000000..201d947 --- /dev/null +++ b/vendor/topthink/framework/src/think/initializer/Error.php @@ -0,0 +1,117 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\initializer; + +use think\App; +use think\console\Output as ConsoleOutput; +use think\exception\ErrorException; +use think\exception\Handle; +use Throwable; + +/** + * 错误和异常处理 + */ +class Error +{ + /** @var App */ + protected $app; + + /** + * 注册异常处理 + * @access public + * @param App $app + * @return void + */ + public function init(App $app) + { + $this->app = $app; + error_reporting(E_ALL); + set_error_handler([$this, 'appError']); + set_exception_handler([$this, 'appException']); + register_shutdown_function([$this, 'appShutdown']); + } + + /** + * Exception Handler + * @access public + * @param \Throwable $e + */ + public function appException(Throwable $e): void + { + $handler = $this->getExceptionHandler(); + + $handler->report($e); + + if ($this->app->runningInConsole()) { + $handler->renderForConsole(new ConsoleOutput, $e); + } else { + $handler->render($this->app->request, $e)->send(); + } + } + + /** + * Error Handler + * @access public + * @param integer $errno 错误编号 + * @param string $errstr 详细错误信息 + * @param string $errfile 出错的文件 + * @param integer $errline 出错行号 + * @throws ErrorException + */ + public function appError(int $errno, string $errstr, string $errfile = '', int $errline = 0): void + { + $exception = new ErrorException($errno, $errstr, $errfile, $errline); + + if (error_reporting() & $errno) { + // 将错误信息托管至 think\exception\ErrorException + throw $exception; + } + } + + /** + * Shutdown Handler + * @access public + */ + public function appShutdown(): void + { + if (!is_null($error = error_get_last()) && $this->isFatal($error['type'])) { + // 将错误信息托管至think\ErrorException + $exception = new ErrorException($error['type'], $error['message'], $error['file'], $error['line']); + + $this->appException($exception); + } + } + + /** + * 确定错误类型是否致命 + * + * @access protected + * @param int $type + * @return bool + */ + protected function isFatal(int $type): bool + { + return in_array($type, [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE]); + } + + /** + * Get an instance of the exception handler. + * + * @access protected + * @return Handle + */ + protected function getExceptionHandler() + { + return $this->app->make(Handle::class); + } +} diff --git a/vendor/topthink/framework/src/think/initializer/RegisterService.php b/vendor/topthink/framework/src/think/initializer/RegisterService.php new file mode 100644 index 0000000..b682a0b --- /dev/null +++ b/vendor/topthink/framework/src/think/initializer/RegisterService.php @@ -0,0 +1,48 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\initializer; + +use think\App; +use think\service\ModelService; +use think\service\PaginatorService; +use think\service\ValidateService; + +/** + * 注册系统服务 + */ +class RegisterService +{ + + protected $services = [ + PaginatorService::class, + ValidateService::class, + ModelService::class, + ]; + + public function init(App $app) + { + $file = $app->getRootPath() . 'vendor/services.php'; + + $services = $this->services; + + if (is_file($file)) { + $services = array_merge($services, include $file); + } + + foreach ($services as $service) { + if (class_exists($service)) { + $app->register($service); + } + } + } +} diff --git a/vendor/topthink/framework/src/think/log/Channel.php b/vendor/topthink/framework/src/think/log/Channel.php new file mode 100644 index 0000000..1de96f1 --- /dev/null +++ b/vendor/topthink/framework/src/think/log/Channel.php @@ -0,0 +1,286 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\log; + +use Psr\Log\LoggerInterface; +use think\contract\LogHandlerInterface; +use think\Event; +use think\event\LogRecord; +use think\event\LogWrite; + +class Channel implements LoggerInterface +{ + protected $name; + protected $logger; + protected $event; + + protected $lazy = true; + /** + * 日志信息 + * @var array + */ + protected $log = []; + + /** + * 关闭日志 + * @var array + */ + protected $close = false; + + /** + * 允许写入类型 + * @var array + */ + protected $allow = []; + + public function __construct(string $name, LogHandlerInterface $logger, array $allow, bool $lazy = true, Event $event = null) + { + $this->name = $name; + $this->logger = $logger; + $this->allow = $allow; + $this->lazy = $lazy; + $this->event = $event; + } + + /** + * 关闭通道 + */ + public function close() + { + $this->clear(); + $this->close = true; + } + + /** + * 清空日志 + */ + public function clear() + { + $this->log = []; + } + + /** + * 记录日志信息 + * @access public + * @param mixed $msg 日志信息 + * @param string $type 日志级别 + * @param array $context 替换内容 + * @param bool $lazy + * @return $this + */ + public function record($msg, string $type = 'info', array $context = [], bool $lazy = true) + { + if ($this->close || (!empty($this->allow) && !in_array($type, $this->allow))) { + return $this; + } + + if (is_string($msg) && !empty($context)) { + $replace = []; + foreach ($context as $key => $val) { + $replace['{' . $key . '}'] = $val; + } + + $msg = strtr($msg, $replace); + } + + if (!empty($msg) || 0 === $msg) { + $this->log[$type][] = $msg; + if ($this->event) { + $this->event->trigger(new LogRecord($type, $msg)); + } + } + + if (!$this->lazy || !$lazy) { + $this->save(); + } + + return $this; + } + + /** + * 实时写入日志信息 + * @access public + * @param mixed $msg 调试信息 + * @param string $type 日志级别 + * @param array $context 替换内容 + * @return $this + */ + public function write($msg, string $type = 'info', array $context = []) + { + return $this->record($msg, $type, $context, false); + } + + /** + * 获取日志信息 + * @return array + */ + public function getLog(): array + { + return $this->log; + } + + /** + * 保存日志 + * @return bool + */ + public function save(): bool + { + $log = $this->log; + if ($this->event) { + $event = new LogWrite($this->name, $log); + $this->event->trigger($event); + $log = $event->log; + } + + if ($this->logger->save($log)) { + $this->clear(); + return true; + } + + return false; + } + + /** + * System is unusable. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function emergency($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function alert($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function critical($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function error($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function warning($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function notice($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function info($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function debug($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * + * @return void + */ + public function log($level, $message, array $context = []) + { + $this->record($message, $level, $context); + } + + public function __call($method, $parameters) + { + $this->log($method, ...$parameters); + } +} diff --git a/vendor/topthink/framework/src/think/log/ChannelSet.php b/vendor/topthink/framework/src/think/log/ChannelSet.php new file mode 100644 index 0000000..6dcb0bd --- /dev/null +++ b/vendor/topthink/framework/src/think/log/ChannelSet.php @@ -0,0 +1,39 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\log; + +use think\Log; + +/** + * Class ChannelSet + * @package think\log + * @mixin Channel + */ +class ChannelSet +{ + protected $log; + protected $channels; + + public function __construct(Log $log, array $channels) + { + $this->log = $log; + $this->channels = $channels; + } + + public function __call($method, $arguments) + { + foreach ($this->channels as $channel) { + $this->log->channel($channel)->{$method}(...$arguments); + } + } +} diff --git a/vendor/topthink/framework/src/think/log/driver/File.php b/vendor/topthink/framework/src/think/log/driver/File.php new file mode 100644 index 0000000..e5682fc --- /dev/null +++ b/vendor/topthink/framework/src/think/log/driver/File.php @@ -0,0 +1,205 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\log\driver; + +use think\App; +use think\contract\LogHandlerInterface; + +/** + * 本地化调试输出到文件 + */ +class File implements LogHandlerInterface +{ + /** + * 配置参数 + * @var array + */ + protected $config = [ + 'time_format' => 'c', + 'single' => false, + 'file_size' => 2097152, + 'path' => '', + 'apart_level' => [], + 'max_files' => 0, + 'json' => false, + 'json_options' => JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES, + 'format' => '[%s][%s] %s', + ]; + + // 实例化并传入参数 + public function __construct(App $app, $config = []) + { + if (is_array($config)) { + $this->config = array_merge($this->config, $config); + } + + if (empty($this->config['format'])) { + $this->config['format'] = '[%s][%s] %s'; + } + + if (empty($this->config['path'])) { + $this->config['path'] = $app->getRuntimePath() . 'log'; + } + + if (substr($this->config['path'], -1) != DIRECTORY_SEPARATOR) { + $this->config['path'] .= DIRECTORY_SEPARATOR; + } + } + + /** + * 日志写入接口 + * @access public + * @param array $log 日志信息 + * @return bool + */ + public function save(array $log): bool + { + $destination = $this->getMasterLogFile(); + + $path = dirname($destination); + !is_dir($path) && mkdir($path, 0755, true); + + $info = []; + + // 日志信息封装 + $time = \DateTime::createFromFormat('0.u00 U', microtime())->setTimezone(new \DateTimeZone(date_default_timezone_get()))->format($this->config['time_format']); + + foreach ($log as $type => $val) { + $message = []; + foreach ($val as $msg) { + if (!is_string($msg)) { + $msg = var_export($msg, true); + } + + $message[] = $this->config['json'] ? + json_encode(['time' => $time, 'type' => $type, 'msg' => $msg], $this->config['json_options']) : + sprintf($this->config['format'], $time, $type, $msg); + } + + if (true === $this->config['apart_level'] || in_array($type, $this->config['apart_level'])) { + // 独立记录的日志级别 + $filename = $this->getApartLevelFile($path, $type); + $this->write($message, $filename); + continue; + } + + $info[$type] = $message; + } + + if ($info) { + return $this->write($info, $destination); + } + + return true; + } + + /** + * 日志写入 + * @access protected + * @param array $message 日志信息 + * @param string $destination 日志文件 + * @return bool + */ + protected function write(array $message, string $destination): bool + { + // 检测日志文件大小,超过配置大小则备份日志文件重新生成 + $this->checkLogSize($destination); + + $info = []; + + foreach ($message as $type => $msg) { + $info[$type] = is_array($msg) ? implode(PHP_EOL, $msg) : $msg; + } + + $message = implode(PHP_EOL, $info) . PHP_EOL; + + return error_log($message, 3, $destination); + } + + /** + * 获取主日志文件名 + * @access public + * @return string + */ + protected function getMasterLogFile(): string + { + + if ($this->config['max_files']) { + $files = glob($this->config['path'] . '*.log'); + + try { + if (count($files) > $this->config['max_files']) { + unlink($files[0]); + } + } catch (\Exception $e) { + // + } + } + + if ($this->config['single']) { + $name = is_string($this->config['single']) ? $this->config['single'] : 'single'; + $destination = $this->config['path'] . $name . '.log'; + } else { + + if ($this->config['max_files']) { + $filename = date('Ymd') . '.log'; + } else { + $filename = date('Ym') . DIRECTORY_SEPARATOR . date('d') . '.log'; + } + + $destination = $this->config['path'] . $filename; + } + + return $destination; + } + + /** + * 获取独立日志文件名 + * @access public + * @param string $path 日志目录 + * @param string $type 日志类型 + * @return string + */ + protected function getApartLevelFile(string $path, string $type): string + { + + if ($this->config['single']) { + $name = is_string($this->config['single']) ? $this->config['single'] : 'single'; + + $name .= '_' . $type; + } elseif ($this->config['max_files']) { + $name = date('Ymd') . '_' . $type; + } else { + $name = date('d') . '_' . $type; + } + + return $path . DIRECTORY_SEPARATOR . $name . '.log'; + } + + /** + * 检查日志文件大小并自动生成备份文件 + * @access protected + * @param string $destination 日志文件 + * @return void + */ + protected function checkLogSize(string $destination): void + { + if (is_file($destination) && floor($this->config['file_size']) <= filesize($destination)) { + try { + rename($destination, dirname($destination) . DIRECTORY_SEPARATOR . time() . '-' . basename($destination)); + } catch (\Exception $e) { + // + } + } + } +} diff --git a/vendor/topthink/framework/src/think/log/driver/Socket.php b/vendor/topthink/framework/src/think/log/driver/Socket.php new file mode 100644 index 0000000..2cfb943 --- /dev/null +++ b/vendor/topthink/framework/src/think/log/driver/Socket.php @@ -0,0 +1,311 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\log\driver; + +use Psr\Container\NotFoundExceptionInterface; +use think\App; +use think\contract\LogHandlerInterface; + +/** + * github: https://github.com/luofei614/SocketLog + * @author luofei614 + */ +class Socket implements LogHandlerInterface +{ + protected $app; + + protected $config = [ + // socket服务器地址 + 'host' => 'localhost', + // socket服务器端口 + 'port' => 1116, + // 是否显示加载的文件列表 + 'show_included_files' => false, + // 日志强制记录到配置的client_id + 'force_client_ids' => [], + // 限制允许读取日志的client_id + 'allow_client_ids' => [], + // 调试开关 + 'debug' => false, + // 输出到浏览器时默认展开的日志级别 + 'expand_level' => ['debug'], + // 日志头渲染回调 + 'format_head' => null, + // curl opt + 'curl_opt' => [ + CURLOPT_CONNECTTIMEOUT => 1, + CURLOPT_TIMEOUT => 10, + ], + ]; + + protected $css = [ + 'sql' => 'color:#009bb4;', + 'sql_warn' => 'color:#009bb4;font-size:14px;', + 'error' => 'color:#f4006b;font-size:14px;', + 'page' => 'color:#40e2ff;background:#171717;', + 'big' => 'font-size:20px;color:red;', + ]; + + protected $allowForceClientIds = []; //配置强制推送且被授权的client_id + + protected $clientArg = []; + + /** + * 架构函数 + * @access public + * @param App $app + * @param array $config 缓存参数 + */ + public function __construct(App $app, array $config = []) + { + $this->app = $app; + + if (!empty($config)) { + $this->config = array_merge($this->config, $config); + } + + if (!isset($config['debug'])) { + $this->config['debug'] = $app->isDebug(); + } + } + + /** + * 调试输出接口 + * @access public + * @param array $log 日志信息 + * @return bool + */ + public function save(array $log = []): bool + { + if (!$this->check()) { + return false; + } + + $trace = []; + + if ($this->config['debug']) { + if ($this->app->exists('request')) { + $currentUri = $this->app->request->url(true); + } else { + $currentUri = 'cmd:' . implode(' ', $_SERVER['argv'] ?? []); + } + + if (!empty($this->config['format_head'])) { + try { + $currentUri = $this->app->invoke($this->config['format_head'], [$currentUri]); + } catch (NotFoundExceptionInterface $notFoundException) { + // Ignore exception + } + } + + // 基本信息 + $trace[] = [ + 'type' => 'group', + 'msg' => $currentUri, + 'css' => $this->css['page'], + ]; + } + + $expandLevel = array_flip($this->config['expand_level']); + + foreach ($log as $type => $val) { + $trace[] = [ + 'type' => isset($expandLevel[$type]) ? 'group' : 'groupCollapsed', + 'msg' => '[ ' . $type . ' ]', + 'css' => $this->css[$type] ?? '', + ]; + + foreach ($val as $msg) { + if (!is_string($msg)) { + $msg = var_export($msg, true); + } + $trace[] = [ + 'type' => 'log', + 'msg' => $msg, + 'css' => '', + ]; + } + + $trace[] = [ + 'type' => 'groupEnd', + 'msg' => '', + 'css' => '', + ]; + } + + if ($this->config['show_included_files']) { + $trace[] = [ + 'type' => 'groupCollapsed', + 'msg' => '[ file ]', + 'css' => '', + ]; + + $trace[] = [ + 'type' => 'log', + 'msg' => implode("\n", get_included_files()), + 'css' => '', + ]; + + $trace[] = [ + 'type' => 'groupEnd', + 'msg' => '', + 'css' => '', + ]; + } + + $trace[] = [ + 'type' => 'groupEnd', + 'msg' => '', + 'css' => '', + ]; + + $tabid = $this->getClientArg('tabid'); + + if (!$clientId = $this->getClientArg('client_id')) { + $clientId = ''; + } + + if (!empty($this->allowForceClientIds)) { + //强制推送到多个client_id + foreach ($this->allowForceClientIds as $forceClientId) { + $clientId = $forceClientId; + $this->sendToClient($tabid, $clientId, $trace, $forceClientId); + } + } else { + $this->sendToClient($tabid, $clientId, $trace, ''); + } + + return true; + } + + /** + * 发送给指定客户端 + * @access protected + * @author Zjmainstay + * @param $tabid + * @param $clientId + * @param $logs + * @param $forceClientId + */ + protected function sendToClient($tabid, $clientId, $logs, $forceClientId) + { + $logs = [ + 'tabid' => $tabid, + 'client_id' => $clientId, + 'logs' => $logs, + 'force_client_id' => $forceClientId, + ]; + + $msg = json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PARTIAL_OUTPUT_ON_ERROR); + $address = '/' . $clientId; //将client_id作为地址, server端通过地址判断将日志发布给谁 + + $this->send($this->config['host'], $this->config['port'], $msg, $address); + } + + /** + * 检测客户授权 + * @access protected + * @return bool + */ + protected function check() + { + $tabid = $this->getClientArg('tabid'); + + //是否记录日志的检查 + if (!$tabid && !$this->config['force_client_ids']) { + return false; + } + + //用户认证 + $allowClientIds = $this->config['allow_client_ids']; + + if (!empty($allowClientIds)) { + //通过数组交集得出授权强制推送的client_id + $this->allowForceClientIds = array_intersect($allowClientIds, $this->config['force_client_ids']); + if (!$tabid && count($this->allowForceClientIds)) { + return true; + } + + $clientId = $this->getClientArg('client_id'); + if (!in_array($clientId, $allowClientIds)) { + return false; + } + } else { + $this->allowForceClientIds = $this->config['force_client_ids']; + } + + return true; + } + + /** + * 获取客户参数 + * @access protected + * @param string $name + * @return string + */ + protected function getClientArg(string $name) + { + if (!$this->app->exists('request')) { + return ''; + } + + if (empty($this->clientArg)) { + if (empty($socketLog = $this->app->request->header('socketlog'))) { + if (empty($socketLog = $this->app->request->header('User-Agent'))) { + return ''; + } + } + + if (!preg_match('/SocketLog\((.*?)\)/', $socketLog, $match)) { + $this->clientArg = ['tabid' => null, 'client_id' => null]; + return ''; + } + parse_str($match[1] ?? '', $this->clientArg); + } + + if (isset($this->clientArg[$name])) { + return $this->clientArg[$name]; + } + + return ''; + } + + /** + * @access protected + * @param string $host - $host of socket server + * @param int $port - $port of socket server + * @param string $message - 发送的消息 + * @param string $address - 地址 + * @return bool + */ + protected function send($host, $port, $message = '', $address = '/') + { + $url = 'http://' . $host . ':' . $port . $address; + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $message); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->config['curl_opt'][CURLOPT_CONNECTTIMEOUT] ?? 1); + curl_setopt($ch, CURLOPT_TIMEOUT, $this->config['curl_opt'][CURLOPT_TIMEOUT] ?? 10); + + $headers = [ + "Content-Type: application/json;charset=UTF-8", + ]; + + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); //设置header + + return curl_exec($ch); + } +} diff --git a/vendor/topthink/framework/src/think/middleware/AllowCrossDomain.php b/vendor/topthink/framework/src/think/middleware/AllowCrossDomain.php new file mode 100644 index 0000000..b7ab842 --- /dev/null +++ b/vendor/topthink/framework/src/think/middleware/AllowCrossDomain.php @@ -0,0 +1,63 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\middleware; + +use Closure; +use think\Config; +use think\Request; +use think\Response; + +/** + * 跨域请求支持 + */ +class AllowCrossDomain +{ + protected $cookieDomain; + + protected $header = [ + 'Access-Control-Allow-Credentials' => 'true', + 'Access-Control-Max-Age' => 1800, + 'Access-Control-Allow-Methods' => 'GET, POST, PATCH, PUT, DELETE, OPTIONS', + 'Access-Control-Allow-Headers' => 'Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-CSRF-TOKEN, X-Requested-With', + ]; + + public function __construct(Config $config) + { + $this->cookieDomain = $config->get('cookie.domain', ''); + } + + /** + * 允许跨域请求 + * @access public + * @param Request $request + * @param Closure $next + * @param array $header + * @return Response + */ + public function handle($request, Closure $next, ? array $header = []) + { + $header = !empty($header) ? array_merge($this->header, $header) : $this->header; + + if (!isset($header['Access-Control-Allow-Origin'])) { + $origin = $request->header('origin'); + + if ($origin && ('' == $this->cookieDomain || strpos($origin, $this->cookieDomain))) { + $header['Access-Control-Allow-Origin'] = $origin; + } else { + $header['Access-Control-Allow-Origin'] = '*'; + } + } + + return $next($request)->header($header); + } +} diff --git a/vendor/topthink/framework/src/think/middleware/CheckRequestCache.php b/vendor/topthink/framework/src/think/middleware/CheckRequestCache.php new file mode 100644 index 0000000..b114351 --- /dev/null +++ b/vendor/topthink/framework/src/think/middleware/CheckRequestCache.php @@ -0,0 +1,183 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\middleware; + +use Closure; +use think\Cache; +use think\Config; +use think\Request; +use think\Response; + +/** + * 请求缓存处理 + */ +class CheckRequestCache +{ + /** + * 缓存对象 + * @var Cache + */ + protected $cache; + + /** + * 配置参数 + * @var array + */ + protected $config = [ + // 请求缓存规则 true为自动规则 + 'request_cache_key' => true, + // 请求缓存有效期 + 'request_cache_expire' => null, + // 全局请求缓存排除规则 + 'request_cache_except' => [], + // 请求缓存的Tag + 'request_cache_tag' => '', + ]; + + public function __construct(Cache $cache, Config $config) + { + $this->cache = $cache; + $this->config = array_merge($this->config, $config->get('route')); + } + + /** + * 设置当前地址的请求缓存 + * @access public + * @param Request $request + * @param Closure $next + * @param mixed $cache + * @return Response + */ + public function handle($request, Closure $next, $cache = null) + { + if ($request->isGet() && false !== $cache) { + if (false === $this->config['request_cache_key']) { + // 关闭当前缓存 + $cache = false; + } + + $cache = $cache ?? $this->getRequestCache($request); + + if ($cache) { + if (is_array($cache)) { + [$key, $expire, $tag] = array_pad($cache, 3, null); + } else { + $key = md5($request->url(true)); + $expire = $cache; + $tag = null; + } + + $key = $this->parseCacheKey($request, $key); + + if (strtotime($request->server('HTTP_IF_MODIFIED_SINCE', '')) + $expire > $request->server('REQUEST_TIME')) { + // 读取缓存 + return Response::create()->code(304); + } elseif (($hit = $this->cache->get($key)) !== null) { + [$content, $header, $when] = $hit; + if (null === $expire || $when + $expire > $request->server('REQUEST_TIME')) { + return Response::create($content)->header($header); + } + } + } + } + + $response = $next($request); + + if (isset($key) && 200 == $response->getCode() && $response->isAllowCache()) { + $header = $response->getHeader(); + $header['Cache-Control'] = 'max-age=' . $expire . ',must-revalidate'; + $header['Last-Modified'] = gmdate('D, d M Y H:i:s') . ' GMT'; + $header['Expires'] = gmdate('D, d M Y H:i:s', time() + $expire) . ' GMT'; + + $this->cache->tag($tag)->set($key, [$response->getContent(), $header, time()], $expire); + } + + return $response; + } + + /** + * 读取当前地址的请求缓存信息 + * @access protected + * @param Request $request + * @return mixed + */ + protected function getRequestCache($request) + { + $key = $this->config['request_cache_key']; + $expire = $this->config['request_cache_expire']; + $except = $this->config['request_cache_except']; + $tag = $this->config['request_cache_tag']; + + foreach ($except as $rule) { + if (0 === stripos($request->url(), $rule)) { + return; + } + } + + return [$key, $expire, $tag]; + } + + /** + * 读取当前地址的请求缓存信息 + * @access protected + * @param Request $request + * @param mixed $key + * @return null|string + */ + protected function parseCacheKey($request, $key) + { + if ($key instanceof \Closure) { + $key = call_user_func($key, $request); + } + + if (false === $key) { + // 关闭当前缓存 + return; + } + + if (true === $key) { + // 自动缓存功能 + $key = '__URL__'; + } elseif (strpos($key, '|')) { + [$key, $fun] = explode('|', $key); + } + + // 特殊规则替换 + if (false !== strpos($key, '__')) { + $key = str_replace(['__CONTROLLER__', '__ACTION__', '__URL__'], [$request->controller(), $request->action(), md5($request->url(true))], $key); + } + + if (false !== strpos($key, ':')) { + $param = $request->param(); + + foreach ($param as $item => $val) { + if (is_string($val) && false !== strpos($key, ':' . $item)) { + $key = str_replace(':' . $item, (string) $val, $key); + } + } + } elseif (strpos($key, ']')) { + if ('[' . $request->ext() . ']' == $key) { + // 缓存某个后缀的请求 + $key = md5($request->url()); + } else { + return; + } + } + + if (isset($fun)) { + $key = $fun($key); + } + + return $key; + } +} diff --git a/vendor/topthink/framework/src/think/middleware/FormTokenCheck.php b/vendor/topthink/framework/src/think/middleware/FormTokenCheck.php new file mode 100644 index 0000000..efbb77b --- /dev/null +++ b/vendor/topthink/framework/src/think/middleware/FormTokenCheck.php @@ -0,0 +1,45 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\middleware; + +use Closure; +use think\exception\ValidateException; +use think\Request; +use think\Response; + +/** + * 表单令牌支持 + */ +class FormTokenCheck +{ + + /** + * 表单令牌检测 + * @access public + * @param Request $request + * @param Closure $next + * @param string $token 表单令牌Token名称 + * @return Response + */ + public function handle(Request $request, Closure $next, string $token = null) + { + $check = $request->checkToken($token ?: '__token__'); + + if (false === $check) { + throw new ValidateException('invalid token'); + } + + return $next($request); + } + +} diff --git a/vendor/topthink/framework/src/think/middleware/LoadLangPack.php b/vendor/topthink/framework/src/think/middleware/LoadLangPack.php new file mode 100644 index 0000000..478e29c --- /dev/null +++ b/vendor/topthink/framework/src/think/middleware/LoadLangPack.php @@ -0,0 +1,61 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\middleware; + +use Closure; +use think\App; +use think\Lang; +use think\Request; +use think\Response; + +/** + * 多语言加载 + */ +class LoadLangPack +{ + protected $app; + + protected $lang; + + public function __construct(App $app, Lang $lang) + { + $this->app = $app; + $this->lang = $lang; + } + + /** + * 路由初始化(路由规则注册) + * @access public + * @param Request $request + * @param Closure $next + * @return Response + */ + public function handle($request, Closure $next) + { + // 自动侦测当前语言 + $langset = $this->lang->detect($request); + + if ($this->lang->defaultLangSet() != $langset) { + // 加载系统语言包 + $this->lang->load([ + $this->app->getThinkPath() . 'lang' . DIRECTORY_SEPARATOR . $langset . '.php', + ]); + + $this->app->LoadLangPack($langset); + } + + $this->lang->saveToCookie($this->app->cookie); + + return $next($request); + } +} diff --git a/vendor/topthink/framework/src/think/middleware/SessionInit.php b/vendor/topthink/framework/src/think/middleware/SessionInit.php new file mode 100644 index 0000000..3cb2fad --- /dev/null +++ b/vendor/topthink/framework/src/think/middleware/SessionInit.php @@ -0,0 +1,80 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\middleware; + +use Closure; +use think\App; +use think\Request; +use think\Response; +use think\Session; + +/** + * Session初始化 + */ +class SessionInit +{ + + /** @var App */ + protected $app; + + /** @var Session */ + protected $session; + + public function __construct(App $app, Session $session) + { + $this->app = $app; + $this->session = $session; + } + + /** + * Session初始化 + * @access public + * @param Request $request + * @param Closure $next + * @return Response + */ + public function handle($request, Closure $next) + { + // Session初始化 + $varSessionId = $this->app->config->get('session.var_session_id'); + $cookieName = $this->session->getName(); + + if ($varSessionId && $request->request($varSessionId)) { + $sessionId = $request->request($varSessionId); + } else { + $sessionId = $request->cookie($cookieName); + } + + if ($sessionId) { + $this->session->setId($sessionId); + } + + $this->session->init(); + + $request->withSession($this->session); + + /** @var Response $response */ + $response = $next($request); + + $response->setSession($this->session); + + $this->app->cookie->set($cookieName, $this->session->getId()); + + return $response; + } + + public function end(Response $response) + { + $this->session->save(); + } +} diff --git a/vendor/topthink/framework/src/think/response/File.php b/vendor/topthink/framework/src/think/response/File.php new file mode 100644 index 0000000..1e45f2f --- /dev/null +++ b/vendor/topthink/framework/src/think/response/File.php @@ -0,0 +1,160 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\response; + +use think\Exception; +use think\Response; + +/** + * File Response + */ +class File extends Response +{ + protected $expire = 360; + protected $name; + protected $mimeType; + protected $isContent = false; + protected $force = true; + + public function __construct($data = '', int $code = 200) + { + $this->init($data, $code); + } + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + * @throws \Exception + */ + protected function output($data) + { + if (!$this->isContent && !is_file($data)) { + throw new Exception('file not exists:' . $data); + } + + while (ob_get_level() > 0) { + ob_end_clean(); + } + + if (!empty($this->name)) { + $name = $this->name; + } else { + $name = !$this->isContent ? pathinfo($data, PATHINFO_BASENAME) : ''; + } + + if ($this->isContent) { + $mimeType = $this->mimeType; + $size = strlen($data); + } else { + $mimeType = $this->getMimeType($data); + $size = filesize($data); + } + + $this->header['Pragma'] = 'public'; + $this->header['Content-Type'] = $mimeType ?: 'application/octet-stream'; + $this->header['Cache-control'] = 'max-age=' . $this->expire; + $this->header['Content-Disposition'] = ($this->force ? 'attachment; ' : '') . 'filename="' . $name . '"'; + $this->header['Content-Length'] = $size; + $this->header['Content-Transfer-Encoding'] = 'binary'; + $this->header['Expires'] = gmdate("D, d M Y H:i:s", time() + $this->expire) . ' GMT'; + + $this->lastModified(gmdate('D, d M Y H:i:s', time()) . ' GMT'); + + return $this->isContent ? $data : file_get_contents($data); + } + + /** + * 设置是否为内容 必须配合mimeType方法使用 + * @access public + * @param bool $content + * @return $this + */ + public function isContent(bool $content = true) + { + $this->isContent = $content; + return $this; + } + + /** + * 设置有效期 + * @access public + * @param integer $expire 有效期 + * @return $this + */ + public function expire(int $expire) + { + $this->expire = $expire; + return $this; + } + + /** + * 设置文件类型 + * @access public + * @param string $filename 文件名 + * @return $this + */ + public function mimeType(string $mimeType) + { + $this->mimeType = $mimeType; + return $this; + } + + /** + * 设置文件强制下载 + * @access public + * @param bool $force 强制浏览器下载 + * @return $this + */ + public function force(bool $force) + { + $this->force = $force; + return $this; + } + + /** + * 获取文件类型信息 + * @access public + * @param string $filename 文件名 + * @return string + */ + protected function getMimeType(string $filename): string + { + if (!empty($this->mimeType)) { + return $this->mimeType; + } + + $finfo = finfo_open(FILEINFO_MIME_TYPE); + + return finfo_file($finfo, $filename); + } + + /** + * 设置下载文件的显示名称 + * @access public + * @param string $filename 文件名 + * @param bool $extension 后缀自动识别 + * @return $this + */ + public function name(string $filename, bool $extension = true) + { + $this->name = $filename; + + if ($extension && false === strpos($filename, '.')) { + $this->name .= '.' . pathinfo($this->data, PATHINFO_EXTENSION); + } + + return $this; + } +} diff --git a/vendor/topthink/framework/src/think/response/Html.php b/vendor/topthink/framework/src/think/response/Html.php new file mode 100644 index 0000000..c158f78 --- /dev/null +++ b/vendor/topthink/framework/src/think/response/Html.php @@ -0,0 +1,34 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\response; + +use think\Cookie; +use think\Response; + +/** + * Html Response + */ +class Html extends Response +{ + /** + * 输出type + * @var string + */ + protected $contentType = 'text/html'; + + public function __construct(Cookie $cookie, $data = '', int $code = 200) + { + $this->init($data, $code); + $this->cookie = $cookie; + } +} diff --git a/vendor/topthink/framework/src/think/response/Json.php b/vendor/topthink/framework/src/think/response/Json.php new file mode 100644 index 0000000..a84501f --- /dev/null +++ b/vendor/topthink/framework/src/think/response/Json.php @@ -0,0 +1,62 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\response; + +use think\Cookie; +use think\Response; + +/** + * Json Response + */ +class Json extends Response +{ + // 输出参数 + protected $options = [ + 'json_encode_param' => JSON_UNESCAPED_UNICODE, + ]; + + protected $contentType = 'application/json'; + + public function __construct(Cookie $cookie, $data = '', int $code = 200) + { + $this->init($data, $code); + $this->cookie = $cookie; + } + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return string + * @throws \Exception + */ + protected function output($data): string + { + try { + // 返回JSON数据格式到客户端 包含状态信息 + $data = json_encode($data, $this->options['json_encode_param']); + + if (false === $data) { + throw new \InvalidArgumentException(json_last_error_msg()); + } + + return $data; + } catch (\Exception $e) { + if ($e->getPrevious()) { + throw $e->getPrevious(); + } + throw $e; + } + } + +} diff --git a/vendor/topthink/framework/src/think/response/Jsonp.php b/vendor/topthink/framework/src/think/response/Jsonp.php new file mode 100644 index 0000000..81d3a06 --- /dev/null +++ b/vendor/topthink/framework/src/think/response/Jsonp.php @@ -0,0 +1,74 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\response; + +use think\Cookie; +use think\Request; +use think\Response; + +/** + * Jsonp Response + */ +class Jsonp extends Response +{ + // 输出参数 + protected $options = [ + 'var_jsonp_handler' => 'callback', + 'default_jsonp_handler' => 'jsonpReturn', + 'json_encode_param' => JSON_UNESCAPED_UNICODE, + ]; + + protected $contentType = 'application/javascript'; + + protected $request; + + public function __construct(Cookie $cookie, Request $request, $data = '', int $code = 200) + { + $this->init($data, $code); + + $this->cookie = $cookie; + $this->request = $request; + } + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return string + * @throws \Exception + */ + protected function output($data): string + { + try { + // 返回JSON数据格式到客户端 包含状态信息 [当url_common_param为false时是无法获取到$_GET的数据的,故使用Request来获取] + $varJsonpHandler = $this->request->param($this->options['var_jsonp_handler'], ""); + $handler = !empty($varJsonpHandler) ? $varJsonpHandler : $this->options['default_jsonp_handler']; + + $data = json_encode($data, $this->options['json_encode_param']); + + if (false === $data) { + throw new \InvalidArgumentException(json_last_error_msg()); + } + + $data = $handler . '(' . $data . ');'; + + return $data; + } catch (\Exception $e) { + if ($e->getPrevious()) { + throw $e->getPrevious(); + } + throw $e; + } + } + +} diff --git a/vendor/topthink/framework/src/think/response/Redirect.php b/vendor/topthink/framework/src/think/response/Redirect.php new file mode 100644 index 0000000..1f38764 --- /dev/null +++ b/vendor/topthink/framework/src/think/response/Redirect.php @@ -0,0 +1,98 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\response; + +use think\Cookie; +use think\Request; +use think\Response; +use think\Session; + +/** + * Redirect Response + */ +class Redirect extends Response +{ + + protected $request; + + public function __construct(Cookie $cookie, Request $request, Session $session, $data = '', int $code = 302) + { + $this->init((string) $data, $code); + + $this->cookie = $cookie; + $this->request = $request; + $this->session = $session; + + $this->cacheControl('no-cache,must-revalidate'); + } + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return string + */ + protected function output($data): string + { + $this->header['Location'] = $data; + + return ''; + } + + /** + * 重定向传值(通过Session) + * @access protected + * @param string|array $name 变量名或者数组 + * @param mixed $value 值 + * @return $this + */ + public function with($name, $value = null) + { + if (is_array($name)) { + foreach ($name as $key => $val) { + $this->session->flash($key, $val); + } + } else { + $this->session->flash($name, $value); + } + + return $this; + } + + /** + * 记住当前url后跳转 + * @access public + * @return $this + */ + public function remember() + { + $this->session->set('redirect_url', $this->request->url()); + + return $this; + } + + /** + * 跳转到上次记住的url + * @access public + * @return $this + */ + public function restore() + { + if ($this->session->has('redirect_url')) { + $this->data = $this->session->get('redirect_url'); + $this->session->delete('redirect_url'); + } + + return $this; + } +} diff --git a/vendor/topthink/framework/src/think/response/View.php b/vendor/topthink/framework/src/think/response/View.php new file mode 100644 index 0000000..2c116c7 --- /dev/null +++ b/vendor/topthink/framework/src/think/response/View.php @@ -0,0 +1,151 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\response; + +use think\Cookie; +use think\Response; +use think\View as BaseView; + +/** + * View Response + */ +class View extends Response +{ + /** + * 输出参数 + * @var array + */ + protected $options = []; + + /** + * 输出变量 + * @var array + */ + protected $vars = []; + + /** + * 输出过滤 + * @var mixed + */ + protected $filter; + + /** + * 输出type + * @var string + */ + protected $contentType = 'text/html'; + + /** + * View对象 + * @var BaseView + */ + protected $view; + + /** + * 是否内容渲染 + * @var bool + */ + protected $isContent = false; + + public function __construct(Cookie $cookie, BaseView $view, $data = '', int $code = 200) + { + $this->init($data, $code); + + $this->cookie = $cookie; + $this->view = $view; + } + + /** + * 设置是否为内容渲染 + * @access public + * @param bool $content + * @return $this + */ + public function isContent(bool $content = true) + { + $this->isContent = $content; + return $this; + } + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return string + */ + protected function output($data): string + { + // 渲染模板输出 + $this->view->filter($this->filter); + return $this->isContent ? + $this->view->display($data, $this->vars) : + $this->view->fetch($data, $this->vars); + } + + /** + * 获取视图变量 + * @access public + * @param string $name 模板变量 + * @return mixed + */ + public function getVars(string $name = null) + { + if (is_null($name)) { + return $this->vars; + } else { + return $this->vars[$name] ?? null; + } + } + + /** + * 模板变量赋值 + * @access public + * @param string|array $name 模板变量 + * @param mixed $value 变量值 + * @return $this + */ + public function assign($name, $value = null) + { + if (is_array($name)) { + $this->vars = array_merge($this->vars, $name); + } else { + $this->vars[$name] = $value; + } + + return $this; + } + + /** + * 视图内容过滤 + * @access public + * @param callable $filter + * @return $this + */ + public function filter(callable $filter = null) + { + $this->filter = $filter; + return $this; + } + + /** + * 检查模板是否存在 + * @access public + * @param string $name 模板名 + * @return bool + */ + public function exists(string $name): bool + { + return $this->view->exists($name); + } + +} diff --git a/vendor/topthink/framework/src/think/response/Xml.php b/vendor/topthink/framework/src/think/response/Xml.php new file mode 100644 index 0000000..bddbb48 --- /dev/null +++ b/vendor/topthink/framework/src/think/response/Xml.php @@ -0,0 +1,127 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\response; + +use think\Collection; +use think\Cookie; +use think\Model; +use think\Response; + +/** + * XML Response + */ +class Xml extends Response +{ + // 输出参数 + protected $options = [ + // 根节点名 + 'root_node' => 'think', + // 根节点属性 + 'root_attr' => '', + //数字索引的子节点名 + 'item_node' => 'item', + // 数字索引子节点key转换的属性名 + 'item_key' => 'id', + // 数据编码 + 'encoding' => 'utf-8', + ]; + + protected $contentType = 'text/xml'; + + public function __construct(Cookie $cookie, $data = '', int $code = 200) + { + $this->init($data, $code); + $this->cookie = $cookie; + } + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + */ + protected function output($data): string + { + if (is_string($data)) { + if (0 !== strpos($data, 'options['encoding']; + $xml = ""; + $data = $xml . $data; + } + return $data; + } + + // XML数据转换 + return $this->xmlEncode($data, $this->options['root_node'], $this->options['item_node'], $this->options['root_attr'], $this->options['item_key'], $this->options['encoding']); + } + + /** + * XML编码 + * @access protected + * @param mixed $data 数据 + * @param string $root 根节点名 + * @param string $item 数字索引的子节点名 + * @param mixed $attr 根节点属性 + * @param string $id 数字索引子节点key转换的属性名 + * @param string $encoding 数据编码 + * @return string + */ + protected function xmlEncode($data, string $root, string $item, $attr, string $id, string $encoding): string + { + if (is_array($attr)) { + $array = []; + foreach ($attr as $key => $value) { + $array[] = "{$key}=\"{$value}\""; + } + $attr = implode(' ', $array); + } + + $attr = trim($attr); + $attr = empty($attr) ? '' : " {$attr}"; + $xml = ""; + $xml .= "<{$root}{$attr}>"; + $xml .= $this->dataToXml($data, $item, $id); + $xml .= ""; + + return $xml; + } + + /** + * 数据XML编码 + * @access protected + * @param mixed $data 数据 + * @param string $item 数字索引时的节点名称 + * @param string $id 数字索引key转换为的属性名 + * @return string + */ + protected function dataToXml($data, string $item, string $id): string + { + $xml = $attr = ''; + + if ($data instanceof Collection || $data instanceof Model) { + $data = $data->toArray(); + } + + foreach ($data as $key => $val) { + if (is_numeric($key)) { + $id && $attr = " {$id}=\"{$key}\""; + $key = $item; + } + $xml .= "<{$key}{$attr}>"; + $xml .= (is_array($val) || is_object($val)) ? $this->dataToXml($val, $item, $id) : $val; + $xml .= ""; + } + + return $xml; + } +} diff --git a/vendor/topthink/framework/src/think/route/Dispatch.php b/vendor/topthink/framework/src/think/route/Dispatch.php new file mode 100644 index 0000000..e77e299 --- /dev/null +++ b/vendor/topthink/framework/src/think/route/Dispatch.php @@ -0,0 +1,257 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\route; + +use think\App; +use think\Container; +use think\Request; +use think\Response; +use think\Validate; + +/** + * 路由调度基础类 + */ +abstract class Dispatch +{ + /** + * 应用对象 + * @var \think\App + */ + protected $app; + + /** + * 请求对象 + * @var Request + */ + protected $request; + + /** + * 路由规则 + * @var Rule + */ + protected $rule; + + /** + * 调度信息 + * @var mixed + */ + protected $dispatch; + + /** + * 路由变量 + * @var array + */ + protected $param; + + public function __construct(Request $request, Rule $rule, $dispatch, array $param = []) + { + $this->request = $request; + $this->rule = $rule; + $this->dispatch = $dispatch; + $this->param = $param; + } + + public function init(App $app) + { + $this->app = $app; + + // 执行路由后置操作 + $this->doRouteAfter(); + } + + /** + * 执行路由调度 + * @access public + * @return mixed + */ + public function run(): Response + { + if ($this->rule instanceof RuleItem && $this->request->method() == 'OPTIONS' && $this->rule->isAutoOptions()) { + $rules = $this->rule->getRouter()->getRule($this->rule->getRule()); + $allow = []; + foreach ($rules as $item) { + $allow[] = strtoupper($item->getMethod()); + } + + return Response::create('', 'html', 204)->header(['Allow' => implode(', ', $allow)]); + } + + $data = $this->exec(); + return $this->autoResponse($data); + } + + protected function autoResponse($data): Response + { + if ($data instanceof Response) { + $response = $data; + } elseif (!is_null($data)) { + // 默认自动识别响应输出类型 + $type = $this->request->isJson() ? 'json' : 'html'; + $response = Response::create($data, $type); + } else { + $data = ob_get_clean(); + + $content = false === $data ? '' : $data; + $status = '' === $content && $this->request->isJson() ? 204 : 200; + $response = Response::create($content, 'html', $status); + } + + return $response; + } + + /** + * 检查路由后置操作 + * @access protected + * @return void + */ + protected function doRouteAfter(): void + { + $option = $this->rule->getOption(); + + // 添加中间件 + if (!empty($option['middleware'])) { + $this->app->middleware->import($option['middleware'], 'route'); + } + + if (!empty($option['append'])) { + $this->param = array_merge($this->param, $option['append']); + } + + // 绑定模型数据 + if (!empty($option['model'])) { + $this->createBindModel($option['model'], $this->param); + } + + // 记录当前请求的路由规则 + $this->request->setRule($this->rule); + + // 记录路由变量 + $this->request->setRoute($this->param); + + // 数据自动验证 + if (isset($option['validate'])) { + $this->autoValidate($option['validate']); + } + } + + /** + * 路由绑定模型实例 + * @access protected + * @param array $bindModel 绑定模型 + * @param array $matches 路由变量 + * @return void + */ + protected function createBindModel(array $bindModel, array $matches): void + { + foreach ($bindModel as $key => $val) { + if ($val instanceof \Closure) { + $result = $this->app->invokeFunction($val, $matches); + } else { + $fields = explode('&', $key); + + if (is_array($val)) { + [$model, $exception] = $val; + } else { + $model = $val; + $exception = true; + } + + $where = []; + $match = true; + + foreach ($fields as $field) { + if (!isset($matches[$field])) { + $match = false; + break; + } else { + $where[] = [$field, '=', $matches[$field]]; + } + } + + if ($match) { + $result = $model::where($where)->failException($exception)->find(); + } + } + + if (!empty($result)) { + // 注入容器 + $this->app->instance(get_class($result), $result); + } + } + } + + /** + * 验证数据 + * @access protected + * @param array $option + * @return void + * @throws \think\exception\ValidateException + */ + protected function autoValidate(array $option): void + { + [$validate, $scene, $message, $batch] = $option; + + if (is_array($validate)) { + // 指定验证规则 + $v = new Validate(); + $v->rule($validate); + } else { + // 调用验证器 + $class = false !== strpos($validate, '\\') ? $validate : $this->app->parseClass('validate', $validate); + + $v = new $class(); + + if (!empty($scene)) { + $v->scene($scene); + } + } + + /** @var Validate $v */ + $v->message($message) + ->batch($batch) + ->failException(true) + ->check($this->request->param()); + } + + public function getDispatch() + { + return $this->dispatch; + } + + public function getParam(): array + { + return $this->param; + } + + abstract public function exec(); + + public function __sleep() + { + return ['rule', 'dispatch', 'param', 'controller', 'actionName']; + } + + public function __wakeup() + { + $this->app = Container::pull('app'); + $this->request = $this->app->request; + } + + public function __debugInfo() + { + return [ + 'dispatch' => $this->dispatch, + 'param' => $this->param, + 'rule' => $this->rule, + ]; + } +} diff --git a/vendor/topthink/framework/src/think/route/Domain.php b/vendor/topthink/framework/src/think/route/Domain.php new file mode 100644 index 0000000..84f1d46 --- /dev/null +++ b/vendor/topthink/framework/src/think/route/Domain.php @@ -0,0 +1,183 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\route; + +use think\helper\Str; +use think\Request; +use think\Route; +use think\route\dispatch\Callback as CallbackDispatch; +use think\route\dispatch\Controller as ControllerDispatch; + +/** + * 域名路由 + */ +class Domain extends RuleGroup +{ + /** + * 架构函数 + * @access public + * @param Route $router 路由对象 + * @param string $name 路由域名 + * @param mixed $rule 域名路由 + */ + public function __construct(Route $router, string $name = null, $rule = null) + { + $this->router = $router; + $this->domain = $name; + $this->rule = $rule; + } + + /** + * 检测域名路由 + * @access public + * @param Request $request 请求对象 + * @param string $url 访问地址 + * @param bool $completeMatch 路由是否完全匹配 + * @return Dispatch|false + */ + public function check(Request $request, string $url, bool $completeMatch = false) + { + // 检测URL绑定 + $result = $this->checkUrlBind($request, $url); + + if (!empty($this->option['append'])) { + $request->setRoute($this->option['append']); + unset($this->option['append']); + } + + if (false !== $result) { + return $result; + } + + return parent::check($request, $url, $completeMatch); + } + + /** + * 设置路由绑定 + * @access public + * @param string $bind 绑定信息 + * @return $this + */ + public function bind(string $bind) + { + $this->router->bind($bind, $this->domain); + + return $this; + } + + /** + * 检测URL绑定 + * @access private + * @param Request $request + * @param string $url URL地址 + * @return Dispatch|false + */ + private function checkUrlBind(Request $request, string $url) + { + $bind = $this->router->getDomainBind($this->domain); + + if ($bind) { + $this->parseBindAppendParam($bind); + + // 如果有URL绑定 则进行绑定检测 + $type = substr($bind, 0, 1); + $bind = substr($bind, 1); + + $bindTo = [ + '\\' => 'bindToClass', + '@' => 'bindToController', + ':' => 'bindToNamespace', + ]; + + if (isset($bindTo[$type])) { + return $this->{$bindTo[$type]}($request, $url, $bind); + } + } + + return false; + } + + protected function parseBindAppendParam(string &$bind): void + { + if (false !== strpos($bind, '?')) { + [$bind, $query] = explode('?', $bind); + parse_str($query, $vars); + $this->append($vars); + } + } + + /** + * 绑定到类 + * @access protected + * @param Request $request + * @param string $url URL地址 + * @param string $class 类名(带命名空间) + * @return CallbackDispatch + */ + protected function bindToClass(Request $request, string $url, string $class): CallbackDispatch + { + $array = explode('|', $url, 2); + $action = !empty($array[0]) ? $array[0] : $this->router->config('default_action'); + $param = []; + + if (!empty($array[1])) { + $this->parseUrlParams($array[1], $param); + } + + return new CallbackDispatch($request, $this, [$class, $action], $param); + } + + /** + * 绑定到命名空间 + * @access protected + * @param Request $request + * @param string $url URL地址 + * @param string $namespace 命名空间 + * @return CallbackDispatch + */ + protected function bindToNamespace(Request $request, string $url, string $namespace): CallbackDispatch + { + $array = explode('|', $url, 3); + $class = !empty($array[0]) ? $array[0] : $this->router->config('default_controller'); + $method = !empty($array[1]) ? $array[1] : $this->router->config('default_action'); + $param = []; + + if (!empty($array[2])) { + $this->parseUrlParams($array[2], $param); + } + + return new CallbackDispatch($request, $this, [$namespace . '\\' . Str::studly($class), $method], $param); + } + + /** + * 绑定到控制器 + * @access protected + * @param Request $request + * @param string $url URL地址 + * @param string $controller 控制器名 + * @return ControllerDispatch + */ + protected function bindToController(Request $request, string $url, string $controller): ControllerDispatch + { + $array = explode('|', $url, 2); + $action = !empty($array[0]) ? $array[0] : $this->router->config('default_action'); + $param = []; + + if (!empty($array[1])) { + $this->parseUrlParams($array[1], $param); + } + + return new ControllerDispatch($request, $this, $controller . '/' . $action, $param); + } + +} diff --git a/vendor/topthink/framework/src/think/route/Resource.php b/vendor/topthink/framework/src/think/route/Resource.php new file mode 100644 index 0000000..bb37cb6 --- /dev/null +++ b/vendor/topthink/framework/src/think/route/Resource.php @@ -0,0 +1,251 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\route; + +use think\Route; + +/** + * 资源路由类 + */ +class Resource extends RuleGroup +{ + /** + * 资源路由名称 + * @var string + */ + protected $resource; + + /** + * 资源路由地址 + * @var string + */ + protected $route; + + /** + * REST方法定义 + * @var array + */ + protected $rest = []; + + /** + * 模型绑定 + * @var array + */ + protected $model = []; + + /** + * 数据验证 + * @var array + */ + protected $validate = []; + + /** + * 中间件 + * @var array + */ + protected $middleware = []; + + /** + * 架构函数 + * @access public + * @param Route $router 路由对象 + * @param RuleGroup $parent 上级对象 + * @param string $name 资源名称 + * @param string $route 路由地址 + * @param array $rest 资源定义 + */ + public function __construct(Route $router, RuleGroup $parent = null, string $name = '', string $route = '', array $rest = []) + { + $name = ltrim($name, '/'); + $this->router = $router; + $this->parent = $parent; + $this->resource = $name; + $this->route = $route; + $this->name = strpos($name, '.') ? strstr($name, '.', true) : $name; + + $this->setFullName(); + + // 资源路由默认为完整匹配 + $this->option['complete_match'] = true; + + $this->rest = $rest; + + if ($this->parent) { + $this->domain = $this->parent->getDomain(); + $this->parent->addRuleItem($this); + } + + if ($router->isTest()) { + $this->buildResourceRule(); + } + } + + /** + * 生成资源路由规则 + * @access protected + * @return void + */ + protected function buildResourceRule(): void + { + $rule = $this->resource; + $option = $this->option; + $origin = $this->router->getGroup(); + $this->router->setGroup($this); + + if (strpos($rule, '.')) { + // 注册嵌套资源路由 + $array = explode('.', $rule); + $last = array_pop($array); + $item = []; + + foreach ($array as $val) { + $item[] = $val . '/<' . ($option['var'][$val] ?? $val . '_id') . '>'; + } + + $rule = implode('/', $item) . '/' . $last; + } + + $prefix = substr($rule, strlen($this->name) + 1); + + // 注册资源路由 + foreach ($this->rest as $key => $val) { + if ((isset($option['only']) && !in_array($key, $option['only'])) + || (isset($option['except']) && in_array($key, $option['except']))) { + continue; + } + + if (isset($last) && strpos($val[1], '') && isset($option['var'][$last])) { + $val[1] = str_replace('', '<' . $option['var'][$last] . '>', $val[1]); + } elseif (strpos($val[1], '') && isset($option['var'][$rule])) { + $val[1] = str_replace('', '<' . $option['var'][$rule] . '>', $val[1]); + } + + $ruleItem = $this->addRule(trim($prefix . $val[1], '/'), $this->route . '/' . $val[2], $val[0]); + + foreach (['model', 'validate', 'middleware', 'pattern'] as $name) { + if (isset($this->$name[$key])) { + call_user_func_array([$ruleItem, $name], (array) $this->$name[$key]); + } + + } + } + + $this->router->setGroup($origin); + } + + /** + * 设置资源允许 + * @access public + * @param array $only 资源允许 + * @return $this + */ + public function only(array $only) + { + return $this->setOption('only', $only); + } + + /** + * 设置资源排除 + * @access public + * @param array $except 排除资源 + * @return $this + */ + public function except(array $except) + { + return $this->setOption('except', $except); + } + + /** + * 设置资源路由的变量 + * @access public + * @param array $vars 资源变量 + * @return $this + */ + public function vars(array $vars) + { + return $this->setOption('var', $vars); + } + + /** + * 绑定资源验证 + * @access public + * @param array|string $name 资源类型或者验证信息 + * @param array|string $validate 验证信息 + * @return $this + */ + public function withValidate($name, $validate = []) + { + if (is_array($name)) { + $this->validate = array_merge($this->validate, $name); + } else { + $this->validate[$name] = $validate; + } + + return $this; + } + + /** + * 绑定资源模型 + * @access public + * @param array|string $name 资源类型或者模型绑定 + * @param array|string $model 模型绑定 + * @return $this + */ + public function withModel($name, $model = []) + { + if (is_array($name)) { + $this->model = array_merge($this->model, $name); + } else { + $this->model[$name] = $model; + } + + return $this; + } + + /** + * 绑定资源模型 + * @access public + * @param array|string $name 资源类型或者中间件定义 + * @param array|string $middleware 中间件定义 + * @return $this + */ + public function withMiddleware($name, $middleware = []) + { + if (is_array($name)) { + $this->middleware = array_merge($this->middleware, $name); + } else { + $this->middleware[$name] = $middleware; + } + + return $this; + } + + /** + * rest方法定义和修改 + * @access public + * @param array|string $name 方法名称 + * @param array|bool $resource 资源 + * @return $this + */ + public function rest($name, $resource = []) + { + if (is_array($name)) { + $this->rest = $resource ? $name : array_merge($this->rest, $name); + } else { + $this->rest[$name] = $resource; + } + + return $this; + } + +} diff --git a/vendor/topthink/framework/src/think/route/Rule.php b/vendor/topthink/framework/src/think/route/Rule.php new file mode 100644 index 0000000..31b2e0e --- /dev/null +++ b/vendor/topthink/framework/src/think/route/Rule.php @@ -0,0 +1,905 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\route; + +use Closure; +use think\Container; +use think\middleware\AllowCrossDomain; +use think\middleware\CheckRequestCache; +use think\middleware\FormTokenCheck; +use think\Request; +use think\Route; +use think\route\dispatch\Callback as CallbackDispatch; +use think\route\dispatch\Controller as ControllerDispatch; + +/** + * 路由规则基础类 + */ +abstract class Rule +{ + /** + * 路由标识 + * @var string + */ + protected $name; + + /** + * 所在域名 + * @var string + */ + protected $domain; + + /** + * 路由对象 + * @var Route + */ + protected $router; + + /** + * 路由所属分组 + * @var RuleGroup + */ + protected $parent; + + /** + * 路由规则 + * @var mixed + */ + protected $rule; + + /** + * 路由地址 + * @var string|Closure + */ + protected $route; + + /** + * 请求类型 + * @var string + */ + protected $method; + + /** + * 路由变量 + * @var array + */ + protected $vars = []; + + /** + * 路由参数 + * @var array + */ + protected $option = []; + + /** + * 路由变量规则 + * @var array + */ + protected $pattern = []; + + /** + * 需要和分组合并的路由参数 + * @var array + */ + protected $mergeOptions = ['model', 'append', 'middleware']; + + abstract public function check(Request $request, string $url, bool $completeMatch = false); + + /** + * 设置路由参数 + * @access public + * @param array $option 参数 + * @return $this + */ + public function option(array $option) + { + $this->option = array_merge($this->option, $option); + + return $this; + } + + /** + * 设置单个路由参数 + * @access public + * @param string $name 参数名 + * @param mixed $value 值 + * @return $this + */ + public function setOption(string $name, $value) + { + $this->option[$name] = $value; + + return $this; + } + + /** + * 注册变量规则 + * @access public + * @param array $pattern 变量规则 + * @return $this + */ + public function pattern(array $pattern) + { + $this->pattern = array_merge($this->pattern, $pattern); + + return $this; + } + + /** + * 设置标识 + * @access public + * @param string $name 标识名 + * @return $this + */ + public function name(string $name) + { + $this->name = $name; + + return $this; + } + + /** + * 获取路由对象 + * @access public + * @return Route + */ + public function getRouter(): Route + { + return $this->router; + } + + /** + * 获取Name + * @access public + * @return string + */ + public function getName(): string + { + return $this->name ?: ''; + } + + /** + * 获取当前路由规则 + * @access public + * @return mixed + */ + public function getRule() + { + return $this->rule; + } + + /** + * 获取当前路由地址 + * @access public + * @return mixed + */ + public function getRoute() + { + return $this->route; + } + + /** + * 获取当前路由的变量 + * @access public + * @return array + */ + public function getVars(): array + { + return $this->vars; + } + + /** + * 获取Parent对象 + * @access public + * @return $this|null + */ + public function getParent() + { + return $this->parent; + } + + /** + * 获取路由所在域名 + * @access public + * @return string + */ + public function getDomain(): string + { + return $this->domain ?: $this->parent->getDomain(); + } + + /** + * 获取路由参数 + * @access public + * @param string $name 变量名 + * @return mixed + */ + public function config(string $name = '') + { + return $this->router->config($name); + } + + /** + * 获取变量规则定义 + * @access public + * @param string $name 变量名 + * @return mixed + */ + public function getPattern(string $name = '') + { + $pattern = $this->pattern; + + if ($this->parent) { + $pattern = array_merge($this->parent->getPattern(), $pattern); + } + + if ('' === $name) { + return $pattern; + } + + return $pattern[$name] ?? null; + } + + /** + * 获取路由参数定义 + * @access public + * @param string $name 参数名 + * @param mixed $default 默认值 + * @return mixed + */ + public function getOption(string $name = '', $default = null) + { + $option = $this->option; + + if ($this->parent) { + $parentOption = $this->parent->getOption(); + + // 合并分组参数 + foreach ($this->mergeOptions as $item) { + if (isset($parentOption[$item]) && isset($option[$item])) { + $option[$item] = array_merge($parentOption[$item], $option[$item]); + } + } + + $option = array_merge($parentOption, $option); + } + + if ('' === $name) { + return $option; + } + + return $option[$name] ?? $default; + } + + /** + * 获取当前路由的请求类型 + * @access public + * @return string + */ + public function getMethod(): string + { + return strtolower($this->method); + } + + /** + * 设置路由请求类型 + * @access public + * @param string $method 请求类型 + * @return $this + */ + public function method(string $method) + { + return $this->setOption('method', strtolower($method)); + } + + /** + * 检查后缀 + * @access public + * @param string $ext URL后缀 + * @return $this + */ + public function ext(string $ext = '') + { + return $this->setOption('ext', $ext); + } + + /** + * 检查禁止后缀 + * @access public + * @param string $ext URL后缀 + * @return $this + */ + public function denyExt(string $ext = '') + { + return $this->setOption('deny_ext', $ext); + } + + /** + * 检查域名 + * @access public + * @param string $domain 域名 + * @return $this + */ + public function domain(string $domain) + { + $this->domain = $domain; + return $this->setOption('domain', $domain); + } + + /** + * 设置参数过滤检查 + * @access public + * @param array $filter 参数过滤 + * @return $this + */ + public function filter(array $filter) + { + $this->option['filter'] = $filter; + + return $this; + } + + /** + * 绑定模型 + * @access public + * @param array|string|Closure $var 路由变量名 多个使用 & 分割 + * @param string|Closure $model 绑定模型类 + * @param bool $exception 是否抛出异常 + * @return $this + */ + public function model($var, $model = null, bool $exception = true) + { + if ($var instanceof Closure) { + $this->option['model'][] = $var; + } elseif (is_array($var)) { + $this->option['model'] = $var; + } elseif (is_null($model)) { + $this->option['model']['id'] = [$var, true]; + } else { + $this->option['model'][$var] = [$model, $exception]; + } + + return $this; + } + + /** + * 附加路由隐式参数 + * @access public + * @param array $append 追加参数 + * @return $this + */ + public function append(array $append = []) + { + $this->option['append'] = $append; + + return $this; + } + + /** + * 绑定验证 + * @access public + * @param mixed $validate 验证器类 + * @param string $scene 验证场景 + * @param array $message 验证提示 + * @param bool $batch 批量验证 + * @return $this + */ + public function validate($validate, string $scene = null, array $message = [], bool $batch = false) + { + $this->option['validate'] = [$validate, $scene, $message, $batch]; + + return $this; + } + + /** + * 指定路由中间件 + * @access public + * @param string|array|Closure $middleware 中间件 + * @param mixed $params 参数 + * @return $this + */ + public function middleware($middleware, ...$params) + { + if (empty($params) && is_array($middleware)) { + $this->option['middleware'] = $middleware; + } else { + foreach ((array) $middleware as $item) { + $this->option['middleware'][] = [$item, $params]; + } + } + + return $this; + } + + /** + * 允许跨域 + * @access public + * @param array $header 自定义Header + * @return $this + */ + public function allowCrossDomain(array $header = []) + { + return $this->middleware(AllowCrossDomain::class, $header); + } + + /** + * 表单令牌验证 + * @access public + * @param string $token 表单令牌token名称 + * @return $this + */ + public function token(string $token = '__token__') + { + return $this->middleware(FormTokenCheck::class, $token); + } + + /** + * 设置路由缓存 + * @access public + * @param array|string $cache 缓存 + * @return $this + */ + public function cache($cache) + { + return $this->middleware(CheckRequestCache::class, $cache); + } + + /** + * 检查URL分隔符 + * @access public + * @param string $depr URL分隔符 + * @return $this + */ + public function depr(string $depr) + { + return $this->setOption('param_depr', $depr); + } + + /** + * 设置需要合并的路由参数 + * @access public + * @param array $option 路由参数 + * @return $this + */ + public function mergeOptions(array $option = []) + { + $this->mergeOptions = array_merge($this->mergeOptions, $option); + return $this; + } + + /** + * 检查是否为HTTPS请求 + * @access public + * @param bool $https 是否为HTTPS + * @return $this + */ + public function https(bool $https = true) + { + return $this->setOption('https', $https); + } + + /** + * 检查是否为JSON请求 + * @access public + * @param bool $json 是否为JSON + * @return $this + */ + public function json(bool $json = true) + { + return $this->setOption('json', $json); + } + + /** + * 检查是否为AJAX请求 + * @access public + * @param bool $ajax 是否为AJAX + * @return $this + */ + public function ajax(bool $ajax = true) + { + return $this->setOption('ajax', $ajax); + } + + /** + * 检查是否为PJAX请求 + * @access public + * @param bool $pjax 是否为PJAX + * @return $this + */ + public function pjax(bool $pjax = true) + { + return $this->setOption('pjax', $pjax); + } + + /** + * 路由到一个模板地址 需要额外传入的模板变量 + * @access public + * @param array $view 视图 + * @return $this + */ + public function view(array $view = []) + { + return $this->setOption('view', $view); + } + + /** + * 设置路由完整匹配 + * @access public + * @param bool $match 是否完整匹配 + * @return $this + */ + public function completeMatch(bool $match = true) + { + return $this->setOption('complete_match', $match); + } + + /** + * 是否去除URL最后的斜线 + * @access public + * @param bool $remove 是否去除最后斜线 + * @return $this + */ + public function removeSlash(bool $remove = true) + { + return $this->setOption('remove_slash', $remove); + } + + /** + * 设置路由规则全局有效 + * @access public + * @return $this + */ + public function crossDomainRule() + { + if ($this instanceof RuleGroup) { + $method = '*'; + } else { + $method = $this->method; + } + + $this->router->setCrossDomainRule($this, $method); + + return $this; + } + + /** + * 解析匹配到的规则路由 + * @access public + * @param Request $request 请求对象 + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @param string $url URL地址 + * @param array $option 路由参数 + * @param array $matches 匹配的变量 + * @return Dispatch + */ + public function parseRule(Request $request, string $rule, $route, string $url, array $option = [], array $matches = []): Dispatch + { + if (is_string($route) && isset($option['prefix'])) { + // 路由地址前缀 + $route = $option['prefix'] . $route; + } + + // 替换路由地址中的变量 + $extraParams = true; + $search = $replace = []; + $depr = $this->router->config('pathinfo_depr'); + foreach ($matches as $key => $value) { + $search[] = '<' . $key . '>'; + $replace[] = $value; + + $search[] = ':' . $key; + $replace[] = $value; + + if (strpos($value, $depr)) { + $extraParams = false; + } + } + + if (is_string($route)) { + $route = str_replace($search, $replace, $route); + } + + // 解析额外参数 + if ($extraParams) { + $count = substr_count($rule, '/'); + $url = array_slice(explode('|', $url), $count + 1); + $this->parseUrlParams(implode('|', $url), $matches); + } + + $this->vars = $matches; + + // 发起路由调度 + return $this->dispatch($request, $route, $option); + } + + /** + * 发起路由调度 + * @access protected + * @param Request $request Request对象 + * @param mixed $route 路由地址 + * @param array $option 路由参数 + * @return Dispatch + */ + protected function dispatch(Request $request, $route, array $option): Dispatch + { + if (is_subclass_of($route, Dispatch::class)) { + $result = new $route($request, $this, $route, $this->vars); + } elseif ($route instanceof Closure) { + // 执行闭包 + $result = new CallbackDispatch($request, $this, $route, $this->vars); + } elseif (false !== strpos($route, '@') || false !== strpos($route, '::') || false !== strpos($route, '\\')) { + // 路由到类的方法 + $route = str_replace('::', '@', $route); + $result = $this->dispatchMethod($request, $route); + } else { + // 路由到控制器/操作 + $result = $this->dispatchController($request, $route); + } + + return $result; + } + + /** + * 解析URL地址为 模块/控制器/操作 + * @access protected + * @param Request $request Request对象 + * @param string $route 路由地址 + * @return CallbackDispatch + */ + protected function dispatchMethod(Request $request, string $route): CallbackDispatch + { + $path = $this->parseUrlPath($route); + + $route = str_replace('/', '@', implode('/', $path)); + $method = strpos($route, '@') ? explode('@', $route) : $route; + + return new CallbackDispatch($request, $this, $method, $this->vars); + } + + /** + * 解析URL地址为 模块/控制器/操作 + * @access protected + * @param Request $request Request对象 + * @param string $route 路由地址 + * @return ControllerDispatch + */ + protected function dispatchController(Request $request, string $route): ControllerDispatch + { + $path = $this->parseUrlPath($route); + + $action = array_pop($path); + $controller = !empty($path) ? array_pop($path) : null; + + // 路由到模块/控制器/操作 + return new ControllerDispatch($request, $this, [$controller, $action], $this->vars); + } + + /** + * 路由检查 + * @access protected + * @param array $option 路由参数 + * @param Request $request Request对象 + * @return bool + */ + protected function checkOption(array $option, Request $request): bool + { + // 请求类型检测 + if (!empty($option['method'])) { + if (is_string($option['method']) && false === stripos($option['method'], $request->method())) { + return false; + } + } + + // AJAX PJAX 请求检查 + foreach (['ajax', 'pjax', 'json'] as $item) { + if (isset($option[$item])) { + $call = 'is' . $item; + if ($option[$item] && !$request->$call() || !$option[$item] && $request->$call()) { + return false; + } + } + } + + // 伪静态后缀检测 + if ($request->url() != '/' && ((isset($option['ext']) && false === stripos('|' . $option['ext'] . '|', '|' . $request->ext() . '|')) + || (isset($option['deny_ext']) && false !== stripos('|' . $option['deny_ext'] . '|', '|' . $request->ext() . '|')))) { + return false; + } + + // 域名检查 + if ((isset($option['domain']) && !in_array($option['domain'], [$request->host(true), $request->subDomain()]))) { + return false; + } + + // HTTPS检查 + if ((isset($option['https']) && $option['https'] && !$request->isSsl()) + || (isset($option['https']) && !$option['https'] && $request->isSsl())) { + return false; + } + + // 请求参数检查 + if (isset($option['filter'])) { + foreach ($option['filter'] as $name => $value) { + if ($request->param($name, '', null) != $value) { + return false; + } + } + } + + return true; + } + + /** + * 解析URL地址中的参数Request对象 + * @access protected + * @param string $rule 路由规则 + * @param array $var 变量 + * @return void + */ + protected function parseUrlParams(string $url, array &$var = []): void + { + if ($url) { + preg_replace_callback('/(\w+)\|([^\|]+)/', function ($match) use (&$var) { + $var[$match[1]] = strip_tags($match[2]); + }, $url); + } + } + + /** + * 解析URL的pathinfo参数 + * @access public + * @param string $url URL地址 + * @return array + */ + public function parseUrlPath(string $url): array + { + // 分隔符替换 确保路由定义使用统一的分隔符 + $url = str_replace('|', '/', $url); + $url = trim($url, '/'); + + if (strpos($url, '/')) { + // [控制器/操作] + $path = explode('/', $url); + } else { + $path = [$url]; + } + + return $path; + } + + /** + * 生成路由的正则规则 + * @access protected + * @param string $rule 路由规则 + * @param array $match 匹配的变量 + * @param array $pattern 路由变量规则 + * @param array $option 路由参数 + * @param bool $completeMatch 路由是否完全匹配 + * @param string $suffix 路由正则变量后缀 + * @return string + */ + protected function buildRuleRegex(string $rule, array $match, array $pattern = [], array $option = [], bool $completeMatch = false, string $suffix = ''): string + { + foreach ($match as $name) { + $value = $this->buildNameRegex($name, $pattern, $suffix); + if ($value) { + $origin[] = $name; + $replace[] = $value; + } + } + + // 是否区分 / 地址访问 + if ('/' != $rule) { + if (!empty($option['remove_slash'])) { + $rule = rtrim($rule, '/'); + } elseif (substr($rule, -1) == '/') { + $rule = rtrim($rule, '/'); + $hasSlash = true; + } + } + + $regex = isset($replace) ? str_replace($origin, $replace, $rule) : $rule; + $regex = str_replace([')?/', ')?-'], [')/', ')-'], $regex); + + if (isset($hasSlash)) { + $regex .= '/'; + } + + return $regex . ($completeMatch ? '$' : ''); + } + + /** + * 生成路由变量的正则规则 + * @access protected + * @param string $name 路由变量 + * @param array $pattern 变量规则 + * @param string $suffix 路由正则变量后缀 + * @return string + */ + protected function buildNameRegex(string $name, array $pattern, string $suffix): string + { + $optional = ''; + $slash = substr($name, 0, 1); + + if (in_array($slash, ['/', '-'])) { + $prefix = $slash; + $name = substr($name, 1); + $slash = substr($name, 0, 1); + } else { + $prefix = ''; + } + + if ('<' != $slash) { + return ''; + } + + if (strpos($name, '?')) { + $name = substr($name, 1, -2); + $optional = '?'; + } elseif (strpos($name, '>')) { + $name = substr($name, 1, -1); + } + + if (isset($pattern[$name])) { + $nameRule = $pattern[$name]; + if (0 === strpos($nameRule, '/') && '/' == substr($nameRule, -1)) { + $nameRule = substr($nameRule, 1, -1); + } + } else { + $nameRule = $this->router->config('default_route_pattern'); + } + + return '(' . $prefix . '(?<' . $name . $suffix . '>' . $nameRule . '))' . $optional; + } + + /** + * 设置路由参数 + * @access public + * @param string $method 方法名 + * @param array $args 调用参数 + * @return $this + */ + public function __call($method, $args) + { + if (count($args) > 1) { + $args[0] = $args; + } + array_unshift($args, $method); + + return call_user_func_array([$this, 'setOption'], $args); + } + + public function __sleep() + { + return ['name', 'rule', 'route', 'method', 'vars', 'option', 'pattern']; + } + + public function __wakeup() + { + $this->router = Container::pull('route'); + } + + public function __debugInfo() + { + return [ + 'name' => $this->name, + 'rule' => $this->rule, + 'route' => $this->route, + 'method' => $this->method, + 'vars' => $this->vars, + 'option' => $this->option, + 'pattern' => $this->pattern, + ]; + } +} diff --git a/vendor/topthink/framework/src/think/route/RuleGroup.php b/vendor/topthink/framework/src/think/route/RuleGroup.php new file mode 100644 index 0000000..cd9ddbd --- /dev/null +++ b/vendor/topthink/framework/src/think/route/RuleGroup.php @@ -0,0 +1,523 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\route; + +use Closure; +use think\Container; +use think\Exception; +use think\Request; +use think\Route; + +/** + * 路由分组类 + */ +class RuleGroup extends Rule +{ + /** + * 分组路由(包括子分组) + * @var array + */ + protected $rules = []; + + /** + * 分组路由规则 + * @var mixed + */ + protected $rule; + + /** + * MISS路由 + * @var RuleItem + */ + protected $miss; + + /** + * 完整名称 + * @var string + */ + protected $fullName; + + /** + * 分组别名 + * @var string + */ + protected $alias; + + /** + * 架构函数 + * @access public + * @param Route $router 路由对象 + * @param RuleGroup $parent 上级对象 + * @param string $name 分组名称 + * @param mixed $rule 分组路由 + */ + public function __construct(Route $router, RuleGroup $parent = null, string $name = '', $rule = null) + { + $this->router = $router; + $this->parent = $parent; + $this->rule = $rule; + $this->name = trim($name, '/'); + + $this->setFullName(); + + if ($this->parent) { + $this->domain = $this->parent->getDomain(); + $this->parent->addRuleItem($this); + } + + if ($router->isTest()) { + $this->lazy(false); + } + } + + /** + * 设置分组的路由规则 + * @access public + * @return void + */ + protected function setFullName(): void + { + if (false !== strpos($this->name, ':')) { + $this->name = preg_replace(['/\[\:(\w+)\]/', '/\:(\w+)/'], ['<\1?>', '<\1>'], $this->name); + } + + if ($this->parent && $this->parent->getFullName()) { + $this->fullName = $this->parent->getFullName() . ($this->name ? '/' . $this->name : ''); + } else { + $this->fullName = $this->name; + } + + if ($this->name) { + $this->router->getRuleName()->setGroup($this->name, $this); + } + } + + /** + * 获取所属域名 + * @access public + * @return string + */ + public function getDomain(): string + { + return $this->domain ?: '-'; + } + + /** + * 获取分组别名 + * @access public + * @return string + */ + public function getAlias(): string + { + return $this->alias ?: ''; + } + + /** + * 检测分组路由 + * @access public + * @param Request $request 请求对象 + * @param string $url 访问地址 + * @param bool $completeMatch 路由是否完全匹配 + * @return Dispatch|false + */ + public function check(Request $request, string $url, bool $completeMatch = false) + { + // 检查分组有效性 + if (!$this->checkOption($this->option, $request) || !$this->checkUrl($url)) { + return false; + } + + // 解析分组路由 + if ($this instanceof Resource) { + $this->buildResourceRule(); + } else { + $this->parseGroupRule($this->rule); + } + + // 获取当前路由规则 + $method = strtolower($request->method()); + $rules = $this->getRules($method); + $option = $this->getOption(); + + if (isset($option['complete_match'])) { + $completeMatch = $option['complete_match']; + } + + if (!empty($option['merge_rule_regex'])) { + // 合并路由正则规则进行路由匹配检查 + $result = $this->checkMergeRuleRegex($request, $rules, $url, $completeMatch); + + if (false !== $result) { + return $result; + } + } + + // 检查分组路由 + foreach ($rules as $key => $item) { + $result = $item[1]->check($request, $url, $completeMatch); + + if (false !== $result) { + return $result; + } + } + + if (!empty($option['dispatcher'])) { + $result = $this->parseRule($request, '', $option['dispatcher'], $url, $option); + } elseif ($this->miss && in_array($this->miss->getMethod(), ['*', $method])) { + // 未匹配所有路由的路由规则处理 + $result = $this->parseRule($request, '', $this->miss->getRoute(), $url, $this->miss->getOption()); + } else { + $result = false; + } + + return $result; + } + + /** + * 分组URL匹配检查 + * @access protected + * @param string $url URL + * @return bool + */ + protected function checkUrl(string $url): bool + { + if ($this->fullName) { + $pos = strpos($this->fullName, '<'); + + if (false !== $pos) { + $str = substr($this->fullName, 0, $pos); + } else { + $str = $this->fullName; + } + + if ($str && 0 !== stripos(str_replace('|', '/', $url), $str)) { + return false; + } + } + + return true; + } + + /** + * 设置路由分组别名 + * @access public + * @param string $alias 路由分组别名 + * @return $this + */ + public function alias(string $alias) + { + $this->alias = $alias; + $this->router->getRuleName()->setGroup($alias, $this); + + return $this; + } + + /** + * 延迟解析分组的路由规则 + * @access public + * @param bool $lazy 路由是否延迟解析 + * @return $this + */ + public function lazy(bool $lazy = true) + { + if (!$lazy) { + $this->parseGroupRule($this->rule); + $this->rule = null; + } + + return $this; + } + + /** + * 解析分组和域名的路由规则及绑定 + * @access public + * @param mixed $rule 路由规则 + * @return void + */ + public function parseGroupRule($rule): void + { + if (is_string($rule) && is_subclass_of($rule, Dispatch::class)) { + $this->dispatcher($rule); + return; + } + + $origin = $this->router->getGroup(); + $this->router->setGroup($this); + + if ($rule instanceof \Closure) { + Container::getInstance()->invokeFunction($rule); + } elseif (is_string($rule) && $rule) { + $this->router->bind($rule, $this->domain); + } + + $this->router->setGroup($origin); + } + + /** + * 检测分组路由 + * @access public + * @param Request $request 请求对象 + * @param array $rules 路由规则 + * @param string $url 访问地址 + * @param bool $completeMatch 路由是否完全匹配 + * @return Dispatch|false + */ + protected function checkMergeRuleRegex(Request $request, array &$rules, string $url, bool $completeMatch) + { + $depr = $this->router->config('pathinfo_depr'); + $url = $depr . str_replace('|', $depr, $url); + $regex = []; + $items = []; + + foreach ($rules as $key => $val) { + $item = $val[1]; + if ($item instanceof RuleItem) { + $rule = $depr . str_replace('/', $depr, $item->getRule()); + if ($depr == $rule && $depr != $url) { + unset($rules[$key]); + continue; + } + + $complete = $item->getOption('complete_match', $completeMatch); + + if (false === strpos($rule, '<')) { + if (0 === strcasecmp($rule, $url) || (!$complete && 0 === strncasecmp($rule, $url, strlen($rule)))) { + return $item->checkRule($request, $url, []); + } + + unset($rules[$key]); + continue; + } + + $slash = preg_quote('/-' . $depr, '/'); + + if ($matchRule = preg_split('/[' . $slash . ']<\w+\??>/', $rule, 2)) { + if ($matchRule[0] && 0 !== strncasecmp($rule, $url, strlen($matchRule[0]))) { + unset($rules[$key]); + continue; + } + } + + if (preg_match_all('/[' . $slash . ']??/', $rule, $matches)) { + unset($rules[$key]); + $pattern = array_merge($this->getPattern(), $item->getPattern()); + $option = array_merge($this->getOption(), $item->getOption()); + + $regex[$key] = $this->buildRuleRegex($rule, $matches[0], $pattern, $option, $complete, '_THINK_' . $key); + $items[$key] = $item; + } + } + } + + if (empty($regex)) { + return false; + } + + try { + $result = preg_match('~^(?:' . implode('|', $regex) . ')~u', $url, $match); + } catch (\Exception $e) { + throw new Exception('route pattern error'); + } + + if ($result) { + $var = []; + foreach ($match as $key => $val) { + if (is_string($key) && '' !== $val) { + [$name, $pos] = explode('_THINK_', $key); + + $var[$name] = $val; + } + } + + if (!isset($pos)) { + foreach ($regex as $key => $item) { + if (0 === strpos(str_replace(['\/', '\-', '\\' . $depr], ['/', '-', $depr], $item), $match[0])) { + $pos = $key; + break; + } + } + } + + $rule = $items[$pos]->getRule(); + $array = $this->router->getRule($rule); + + foreach ($array as $item) { + if (in_array($item->getMethod(), ['*', strtolower($request->method())])) { + $result = $item->checkRule($request, $url, $var); + + if (false !== $result) { + return $result; + } + } + } + } + + return false; + } + + /** + * 获取分组的MISS路由 + * @access public + * @return RuleItem|null + */ + public function getMissRule(): ? RuleItem + { + return $this->miss; + } + + /** + * 注册MISS路由 + * @access public + * @param string|Closure $route 路由地址 + * @param string $method 请求类型 + * @return RuleItem + */ + public function miss($route, string $method = '*') : RuleItem + { + // 创建路由规则实例 + $ruleItem = new RuleItem($this->router, $this, null, '', $route, strtolower($method)); + + $ruleItem->setMiss(); + $this->miss = $ruleItem; + + return $ruleItem; + } + + /** + * 添加分组下的路由规则 + * @access public + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @param string $method 请求类型 + * @return RuleItem + */ + public function addRule(string $rule, $route = null, string $method = '*'): RuleItem + { + // 读取路由标识 + if (is_string($route)) { + $name = $route; + } else { + $name = null; + } + + $method = strtolower($method); + + if ('' === $rule || '/' === $rule) { + $rule .= '$'; + } + + // 创建路由规则实例 + $ruleItem = new RuleItem($this->router, $this, $name, $rule, $route, $method); + + $this->addRuleItem($ruleItem, $method); + + return $ruleItem; + } + + /** + * 注册分组下的路由规则 + * @access public + * @param Rule $rule 路由规则 + * @param string $method 请求类型 + * @return $this + */ + public function addRuleItem(Rule $rule, string $method = '*') + { + if (strpos($method, '|')) { + $rule->method($method); + $method = '*'; + } + + $this->rules[] = [$method, $rule]; + + if ($rule instanceof RuleItem && 'options' != $method) { + $this->rules[] = ['options', $rule->setAutoOptions()]; + } + + return $this; + } + + /** + * 设置分组的路由前缀 + * @access public + * @param string $prefix 路由前缀 + * @return $this + */ + public function prefix(string $prefix) + { + if ($this->parent && $this->parent->getOption('prefix')) { + $prefix = $this->parent->getOption('prefix') . $prefix; + } + + return $this->setOption('prefix', $prefix); + } + + /** + * 合并分组的路由规则正则 + * @access public + * @param bool $merge 是否合并 + * @return $this + */ + public function mergeRuleRegex(bool $merge = true) + { + return $this->setOption('merge_rule_regex', $merge); + } + + /** + * 设置分组的Dispatch调度 + * @access public + * @param string $dispatch 调度类 + * @return $this + */ + public function dispatcher(string $dispatch) + { + return $this->setOption('dispatcher', $dispatch); + } + + /** + * 获取完整分组Name + * @access public + * @return string + */ + public function getFullName(): string + { + return $this->fullName ?: ''; + } + + /** + * 获取分组的路由规则 + * @access public + * @param string $method 请求类型 + * @return array + */ + public function getRules(string $method = ''): array + { + if ('' === $method) { + return $this->rules; + } + + return array_filter($this->rules, function ($item) use ($method) { + return $method == $item[0] || '*' == $item[0]; + }); + } + + /** + * 清空分组下的路由规则 + * @access public + * @return void + */ + public function clear(): void + { + $this->rules = []; + } +} diff --git a/vendor/topthink/framework/src/think/route/RuleItem.php b/vendor/topthink/framework/src/think/route/RuleItem.php new file mode 100644 index 0000000..1f9aa52 --- /dev/null +++ b/vendor/topthink/framework/src/think/route/RuleItem.php @@ -0,0 +1,330 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\route; + +use think\Exception; +use think\Request; +use think\Route; + +/** + * 路由规则类 + */ +class RuleItem extends Rule +{ + /** + * 是否为MISS规则 + * @var bool + */ + protected $miss = false; + + /** + * 是否为额外自动注册的OPTIONS规则 + * @var bool + */ + protected $autoOption = false; + + /** + * 架构函数 + * @access public + * @param Route $router 路由实例 + * @param RuleGroup $parent 上级对象 + * @param string $name 路由标识 + * @param string $rule 路由规则 + * @param string|\Closure $route 路由地址 + * @param string $method 请求类型 + */ + public function __construct(Route $router, RuleGroup $parent, string $name = null, string $rule = '', $route = null, string $method = '*') + { + $this->router = $router; + $this->parent = $parent; + $this->name = $name; + $this->route = $route; + $this->method = $method; + + $this->setRule($rule); + + $this->router->setRule($this->rule, $this); + } + + /** + * 设置当前路由规则为MISS路由 + * @access public + * @return $this + */ + public function setMiss() + { + $this->miss = true; + return $this; + } + + /** + * 判断当前路由规则是否为MISS路由 + * @access public + * @return bool + */ + public function isMiss(): bool + { + return $this->miss; + } + + /** + * 设置当前路由为自动注册OPTIONS + * @access public + * @return $this + */ + public function setAutoOptions() + { + $this->autoOption = true; + return $this; + } + + /** + * 判断当前路由规则是否为自动注册的OPTIONS路由 + * @access public + * @return bool + */ + public function isAutoOptions(): bool + { + return $this->autoOption; + } + + /** + * 获取当前路由的URL后缀 + * @access public + * @return string|null + */ + public function getSuffix() + { + if (isset($this->option['ext'])) { + $suffix = $this->option['ext']; + } elseif ($this->parent->getOption('ext')) { + $suffix = $this->parent->getOption('ext'); + } else { + $suffix = null; + } + + return $suffix; + } + + /** + * 路由规则预处理 + * @access public + * @param string $rule 路由规则 + * @return void + */ + public function setRule(string $rule): void + { + if ('$' == substr($rule, -1, 1)) { + // 是否完整匹配 + $rule = substr($rule, 0, -1); + + $this->option['complete_match'] = true; + } + + $rule = '/' != $rule ? ltrim($rule, '/') : ''; + + if ($this->parent && $prefix = $this->parent->getFullName()) { + $rule = $prefix . ($rule ? '/' . ltrim($rule, '/') : ''); + } + + if (false !== strpos($rule, ':')) { + $this->rule = preg_replace(['/\[\:(\w+)\]/', '/\:(\w+)/'], ['<\1?>', '<\1>'], $rule); + } else { + $this->rule = $rule; + } + + // 生成路由标识的快捷访问 + $this->setRuleName(); + } + + /** + * 设置别名 + * @access public + * @param string $name + * @return $this + */ + public function name(string $name) + { + $this->name = $name; + $this->setRuleName(true); + + return $this; + } + + /** + * 设置路由标识 用于URL反解生成 + * @access protected + * @param bool $first 是否插入开头 + * @return void + */ + protected function setRuleName(bool $first = false): void + { + if ($this->name) { + $this->router->setName($this->name, $this, $first); + } + } + + /** + * 检测路由 + * @access public + * @param Request $request 请求对象 + * @param string $url 访问地址 + * @param array $match 匹配路由变量 + * @param bool $completeMatch 路由是否完全匹配 + * @return Dispatch|false + */ + public function checkRule(Request $request, string $url, $match = null, bool $completeMatch = false) + { + // 检查参数有效性 + if (!$this->checkOption($this->option, $request)) { + return false; + } + + // 合并分组参数 + $option = $this->getOption(); + $pattern = $this->getPattern(); + $url = $this->urlSuffixCheck($request, $url, $option); + + if (is_null($match)) { + $match = $this->match($url, $option, $pattern, $completeMatch); + } + + if (false !== $match) { + return $this->parseRule($request, $this->rule, $this->route, $url, $option, $match); + } + + return false; + } + + /** + * 检测路由(含路由匹配) + * @access public + * @param Request $request 请求对象 + * @param string $url 访问地址 + * @param bool $completeMatch 路由是否完全匹配 + * @return Dispatch|false + */ + public function check(Request $request, string $url, bool $completeMatch = false) + { + return $this->checkRule($request, $url, null, $completeMatch); + } + + /** + * URL后缀及Slash检查 + * @access protected + * @param Request $request 请求对象 + * @param string $url 访问地址 + * @param array $option 路由参数 + * @return string + */ + protected function urlSuffixCheck(Request $request, string $url, array $option = []): string + { + // 是否区分 / 地址访问 + if (!empty($option['remove_slash']) && '/' != $this->rule) { + $this->rule = rtrim($this->rule, '/'); + $url = rtrim($url, '|'); + } + + if (isset($option['ext'])) { + // 路由ext参数 优先于系统配置的URL伪静态后缀参数 + $url = preg_replace('/\.(' . $request->ext() . ')$/i', '', $url); + } + + return $url; + } + + /** + * 检测URL和规则路由是否匹配 + * @access private + * @param string $url URL地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @param bool $completeMatch 是否完全匹配 + * @return array|false + */ + private function match(string $url, array $option, array $pattern, bool $completeMatch) + { + if (isset($option['complete_match'])) { + $completeMatch = $option['complete_match']; + } + + $depr = $this->router->config('pathinfo_depr'); + + // 检查完整规则定义 + if (isset($pattern['__url__']) && !preg_match(0 === strpos($pattern['__url__'], '/') ? $pattern['__url__'] : '/^' . $pattern['__url__'] . ($completeMatch ? '$' : '') . '/', str_replace('|', $depr, $url))) { + return false; + } + + $var = []; + $url = $depr . str_replace('|', $depr, $url); + $rule = $depr . str_replace('/', $depr, $this->rule); + + if ($depr == $rule && $depr != $url) { + return false; + } + + if (false === strpos($rule, '<')) { + if (0 === strcasecmp($rule, $url) || (!$completeMatch && 0 === strncasecmp($rule . $depr, $url . $depr, strlen($rule . $depr)))) { + return $var; + } + return false; + } + + $slash = preg_quote('/-' . $depr, '/'); + + if ($matchRule = preg_split('/[' . $slash . ']?<\w+\??>/', $rule, 2)) { + if ($matchRule[0] && 0 !== strncasecmp($rule, $url, strlen($matchRule[0]))) { + return false; + } + } + + if (preg_match_all('/[' . $slash . ']??/', $rule, $matches)) { + $regex = $this->buildRuleRegex($rule, $matches[0], $pattern, $option, $completeMatch); + + try { + if (!preg_match('~^' . $regex . '~u', $url, $match)) { + return false; + } + } catch (\Exception $e) { + throw new Exception('route pattern error'); + } + + foreach ($match as $key => $val) { + if (is_string($key)) { + $var[$key] = $val; + } + } + } + + // 成功匹配后返回URL中的动态变量数组 + return $var; + } + + /** + * 设置路由所属分组(用于注解路由) + * @access public + * @param string $name 分组名称或者标识 + * @return $this + */ + public function group(string $name) + { + $group = $this->router->getRuleName()->getGroup($name); + + if ($group) { + $this->parent = $group; + $this->setRule($this->rule); + } + + return $this; + } +} diff --git a/vendor/topthink/framework/src/think/route/RuleName.php b/vendor/topthink/framework/src/think/route/RuleName.php new file mode 100644 index 0000000..0684367 --- /dev/null +++ b/vendor/topthink/framework/src/think/route/RuleName.php @@ -0,0 +1,211 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\route; + +/** + * 路由标识管理类 + */ +class RuleName +{ + /** + * 路由标识 + * @var array + */ + protected $item = []; + + /** + * 路由规则 + * @var array + */ + protected $rule = []; + + /** + * 路由分组 + * @var array + */ + protected $group = []; + + /** + * 注册路由标识 + * @access public + * @param string $name 路由标识 + * @param RuleItem $ruleItem 路由规则 + * @param bool $first 是否优先 + * @return void + */ + public function setName(string $name, RuleItem $ruleItem, bool $first = false): void + { + $name = strtolower($name); + $item = $this->getRuleItemInfo($ruleItem); + if ($first && isset($this->item[$name])) { + array_unshift($this->item[$name], $item); + } else { + $this->item[$name][] = $item; + } + } + + /** + * 注册路由分组标识 + * @access public + * @param string $name 路由分组标识 + * @param RuleGroup $group 路由分组 + * @return void + */ + public function setGroup(string $name, RuleGroup $group): void + { + $this->group[strtolower($name)] = $group; + } + + /** + * 注册路由规则 + * @access public + * @param string $rule 路由规则 + * @param RuleItem $ruleItem 路由 + * @return void + */ + public function setRule(string $rule, RuleItem $ruleItem): void + { + $route = $ruleItem->getRoute(); + + if (is_string($route)) { + $this->rule[$rule][$route] = $ruleItem; + } else { + $this->rule[$rule][] = $ruleItem; + } + } + + /** + * 根据路由规则获取路由对象(列表) + * @access public + * @param string $rule 路由标识 + * @return RuleItem[] + */ + public function getRule(string $rule): array + { + return $this->rule[$rule] ?? []; + } + + /** + * 根据路由分组标识获取分组 + * @access public + * @param string $name 路由分组标识 + * @return RuleGroup|null + */ + public function getGroup(string $name) + { + return $this->group[strtolower($name)] ?? null; + } + + /** + * 清空路由规则 + * @access public + * @return void + */ + public function clear(): void + { + $this->item = []; + $this->rule = []; + } + + /** + * 获取全部路由列表 + * @access public + * @return array + */ + public function getRuleList(): array + { + $list = []; + + foreach ($this->rule as $rule => $rules) { + foreach ($rules as $item) { + $val = []; + + foreach (['method', 'rule', 'name', 'route', 'domain', 'pattern', 'option'] as $param) { + $call = 'get' . $param; + $val[$param] = $item->$call(); + } + + if ($item->isMiss()) { + $val['rule'] .= ''; + } + + $list[] = $val; + } + } + + return $list; + } + + /** + * 导入路由标识 + * @access public + * @param array $item 路由标识 + * @return void + */ + public function import(array $item): void + { + $this->item = $item; + } + + /** + * 根据路由标识获取路由信息(用于URL生成) + * @access public + * @param string $name 路由标识 + * @param string $domain 域名 + * @param string $method 请求类型 + * @return array + */ + public function getName(string $name = null, string $domain = null, string $method = '*'): array + { + if (is_null($name)) { + return $this->item; + } + + $name = strtolower($name); + $method = strtolower($method); + $result = []; + + if (isset($this->item[$name])) { + if (is_null($domain)) { + $result = $this->item[$name]; + } else { + foreach ($this->item[$name] as $item) { + $itemDomain = $item['domain']; + $itemMethod = $item['method']; + + if (($itemDomain == $domain || '-' == $itemDomain) && ('*' == $itemMethod || '*' == $method || $method == $itemMethod)) { + $result[] = $item; + } + } + } + } + + return $result; + } + + /** + * 获取路由信息 + * @access protected + * @param RuleItem $item 路由规则 + * @return array + */ + protected function getRuleItemInfo(RuleItem $item): array + { + return [ + 'rule' => $item->getRule(), + 'domain' => $item->getDomain(), + 'method' => $item->getMethod(), + 'suffix' => $item->getSuffix(), + ]; + } +} diff --git a/vendor/topthink/framework/src/think/route/Url.php b/vendor/topthink/framework/src/think/route/Url.php new file mode 100644 index 0000000..8dd410c --- /dev/null +++ b/vendor/topthink/framework/src/think/route/Url.php @@ -0,0 +1,517 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\route; + +use think\App; +use think\Route; + +/** + * 路由地址生成 + */ +class Url +{ + /** + * 应用对象 + * @var App + */ + protected $app; + + /** + * 路由对象 + * @var Route + */ + protected $route; + + /** + * URL变量 + * @var array + */ + protected $vars = []; + + /** + * 路由URL + * @var string + */ + protected $url; + + /** + * URL 根地址 + * @var string + */ + protected $root = ''; + + /** + * HTTPS + * @var bool + */ + protected $https; + + /** + * URL后缀 + * @var string|bool + */ + protected $suffix = true; + + /** + * URL域名 + * @var string|bool + */ + protected $domain = false; + + /** + * 架构函数 + * @access public + * @param string $url URL地址 + * @param array $vars 参数 + */ + public function __construct(Route $route, App $app, string $url = '', array $vars = []) + { + $this->route = $route; + $this->app = $app; + $this->url = $url; + $this->vars = $vars; + } + + /** + * 设置URL参数 + * @access public + * @param array $vars URL参数 + * @return $this + */ + public function vars(array $vars = []) + { + $this->vars = $vars; + return $this; + } + + /** + * 设置URL后缀 + * @access public + * @param string|bool $suffix URL后缀 + * @return $this + */ + public function suffix($suffix) + { + $this->suffix = $suffix; + return $this; + } + + /** + * 设置URL域名(或者子域名) + * @access public + * @param string|bool $domain URL域名 + * @return $this + */ + public function domain($domain) + { + $this->domain = $domain; + return $this; + } + + /** + * 设置URL 根地址 + * @access public + * @param string $root URL root + * @return $this + */ + public function root(string $root) + { + $this->root = $root; + return $this; + } + + /** + * 设置是否使用HTTPS + * @access public + * @param bool $https + * @return $this + */ + public function https(bool $https = true) + { + $this->https = $https; + return $this; + } + + /** + * 检测域名 + * @access protected + * @param string $url URL + * @param string|true $domain 域名 + * @return string + */ + protected function parseDomain(string &$url, $domain): string + { + if (!$domain) { + return ''; + } + + $request = $this->app->request; + $rootDomain = $request->rootDomain(); + + if (true === $domain) { + // 自动判断域名 + $domain = $request->host(); + $domains = $this->route->getDomains(); + + if (!empty($domains)) { + $routeDomain = array_keys($domains); + foreach ($routeDomain as $domainPrefix) { + if (0 === strpos($domainPrefix, '*.') && strpos($domain, ltrim($domainPrefix, '*.')) !== false) { + foreach ($domains as $key => $rule) { + $rule = is_array($rule) ? $rule[0] : $rule; + if (is_string($rule) && false === strpos($key, '*') && 0 === strpos($url, $rule)) { + $url = ltrim($url, $rule); + $domain = $key; + + // 生成对应子域名 + if (!empty($rootDomain)) { + $domain .= $rootDomain; + } + break; + } elseif (false !== strpos($key, '*')) { + if (!empty($rootDomain)) { + $domain .= $rootDomain; + } + + break; + } + } + } + } + } + } elseif (false === strpos($domain, '.') && 0 !== strpos($domain, $rootDomain)) { + $domain .= '.' . $rootDomain; + } + + if (false !== strpos($domain, '://')) { + $scheme = ''; + } else { + $scheme = $this->https || $request->isSsl() ? 'https://' : 'http://'; + } + + return $scheme . $domain; + } + + /** + * 解析URL后缀 + * @access protected + * @param string|bool $suffix 后缀 + * @return string + */ + protected function parseSuffix($suffix): string + { + if ($suffix) { + $suffix = true === $suffix ? $this->route->config('url_html_suffix') : $suffix; + + if (is_string($suffix) && $pos = strpos($suffix, '|')) { + $suffix = substr($suffix, 0, $pos); + } + } + + return (empty($suffix) || 0 === strpos($suffix, '.')) ? (string) $suffix : '.' . $suffix; + } + + /** + * 直接解析URL地址 + * @access protected + * @param string $url URL + * @param string|bool $domain Domain + * @return string + */ + protected function parseUrl(string $url, &$domain): string + { + $request = $this->app->request; + + if (0 === strpos($url, '/')) { + // 直接作为路由地址解析 + $url = substr($url, 1); + } elseif (false !== strpos($url, '\\')) { + // 解析到类 + $url = ltrim(str_replace('\\', '/', $url), '/'); + } elseif (0 === strpos($url, '@')) { + // 解析到控制器 + $url = substr($url, 1); + } elseif ('' === $url) { + $url = $request->controller() . '/' . $request->action(); + } else { + $controller = $request->controller(); + + $path = explode('/', $url); + $action = array_pop($path); + $controller = empty($path) ? $controller : array_pop($path); + + $url = $controller . '/' . $action; + } + + return $url; + } + + /** + * 分析路由规则中的变量 + * @access protected + * @param string $rule 路由规则 + * @return array + */ + protected function parseVar(string $rule): array + { + // 提取路由规则中的变量 + $var = []; + + if (preg_match_all('/<\w+\??>/', $rule, $matches)) { + foreach ($matches[0] as $name) { + $optional = false; + + if (strpos($name, '?')) { + $name = substr($name, 1, -2); + $optional = true; + } else { + $name = substr($name, 1, -1); + } + + $var[$name] = $optional ? 2 : 1; + } + } + + return $var; + } + + /** + * 匹配路由地址 + * @access protected + * @param array $rule 路由规则 + * @param array $vars 路由变量 + * @param mixed $allowDomain 允许域名 + * @return array + */ + protected function getRuleUrl(array $rule, array &$vars = [], $allowDomain = ''): array + { + $request = $this->app->request; + if (is_string($allowDomain) && false === strpos($allowDomain, '.')) { + $allowDomain .= '.' . $request->rootDomain(); + } + $port = $request->port(); + + foreach ($rule as $item) { + $url = $item['rule']; + $pattern = $this->parseVar($url); + $domain = $item['domain']; + $suffix = $item['suffix']; + + if ('-' == $domain) { + $domain = is_string($allowDomain) ? $allowDomain : $request->host(true); + } + + if (is_string($allowDomain) && $domain != $allowDomain) { + continue; + } + + if ($port && !in_array($port, [80, 443])) { + $domain .= ':' . $port; + } + + if (empty($pattern)) { + return [rtrim($url, '?/-'), $domain, $suffix]; + } + + $type = $this->route->config('url_common_param'); + $keys = []; + + foreach ($pattern as $key => $val) { + if (isset($vars[$key])) { + $url = str_replace(['[:' . $key . ']', '<' . $key . '?>', ':' . $key, '<' . $key . '>'], $type ? (string) $vars[$key] : urlencode((string) $vars[$key]), $url); + $keys[] = $key; + $url = str_replace(['/?', '-?'], ['/', '-'], $url); + $result = [rtrim($url, '?/-'), $domain, $suffix]; + } elseif (2 == $val) { + $url = str_replace(['/[:' . $key . ']', '[:' . $key . ']', '<' . $key . '?>'], '', $url); + $url = str_replace(['/?', '-?'], ['/', '-'], $url); + $result = [rtrim($url, '?/-'), $domain, $suffix]; + } else { + $result = null; + $keys = []; + break; + } + } + + $vars = array_diff_key($vars, array_flip($keys)); + + if (isset($result)) { + return $result; + } + } + + return []; + } + + /** + * 生成URL地址 + * @access public + * @return string + */ + public function build() + { + // 解析URL + $url = $this->url; + $suffix = $this->suffix; + $domain = $this->domain; + $request = $this->app->request; + $vars = $this->vars; + + if (0 === strpos($url, '[') && $pos = strpos($url, ']')) { + // [name] 表示使用路由命名标识生成URL + $name = substr($url, 1, $pos - 1); + $url = 'name' . substr($url, $pos + 1); + } + + if (false === strpos($url, '://') && 0 !== strpos($url, '/')) { + $info = parse_url($url); + $url = !empty($info['path']) ? $info['path'] : ''; + + if (isset($info['fragment'])) { + // 解析锚点 + $anchor = $info['fragment']; + + if (false !== strpos($anchor, '?')) { + // 解析参数 + [$anchor, $info['query']] = explode('?', $anchor, 2); + } + + if (false !== strpos($anchor, '@')) { + // 解析域名 + [$anchor, $domain] = explode('@', $anchor, 2); + } + } elseif (strpos($url, '@') && false === strpos($url, '\\')) { + // 解析域名 + [$url, $domain] = explode('@', $url, 2); + } + } + + if ($url) { + $checkName = isset($name) ? $name : $url . (isset($info['query']) ? '?' . $info['query'] : ''); + $checkDomain = $domain && is_string($domain) ? $domain : null; + + $rule = $this->route->getName($checkName, $checkDomain); + + if (empty($rule) && isset($info['query'])) { + $rule = $this->route->getName($url, $checkDomain); + // 解析地址里面参数 合并到vars + parse_str($info['query'], $params); + $vars = array_merge($params, $vars); + unset($info['query']); + } + } + + if (!empty($rule) && $match = $this->getRuleUrl($rule, $vars, $domain)) { + // 匹配路由命名标识 + $url = $match[0]; + + if ($domain && !empty($match[1])) { + $domain = $match[1]; + } + + if (!is_null($match[2])) { + $suffix = $match[2]; + } + } elseif (!empty($rule) && isset($name)) { + throw new \InvalidArgumentException('route name not exists:' . $name); + } else { + // 检测URL绑定 + $bind = $this->route->getDomainBind($domain && is_string($domain) ? $domain : null); + + if ($bind && 0 === strpos($url, $bind)) { + $url = substr($url, strlen($bind) + 1); + } else { + $binds = $this->route->getBind(); + + foreach ($binds as $key => $val) { + if (is_string($val) && 0 === strpos($url, $val) && substr_count($val, '/') > 1) { + $url = substr($url, strlen($val) + 1); + $domain = $key; + break; + } + } + } + + // 路由标识不存在 直接解析 + $url = $this->parseUrl($url, $domain); + + if (isset($info['query'])) { + // 解析地址里面参数 合并到vars + parse_str($info['query'], $params); + $vars = array_merge($params, $vars); + } + } + + // 还原URL分隔符 + $depr = $this->route->config('pathinfo_depr'); + $url = str_replace('/', $depr, $url); + + $file = $request->baseFile(); + if ($file && 0 !== strpos($request->url(), $file)) { + $file = str_replace('\\', '/', dirname($file)); + } + + $url = rtrim($file, '/') . '/' . $url; + + // URL后缀 + if ('/' == substr($url, -1) || '' == $url) { + $suffix = ''; + } else { + $suffix = $this->parseSuffix($suffix); + } + + // 锚点 + $anchor = !empty($anchor) ? '#' . $anchor : ''; + + // 参数组装 + if (!empty($vars)) { + // 添加参数 + if ($this->route->config('url_common_param')) { + $vars = http_build_query($vars); + $url .= $suffix . ($vars ? '?' . $vars : '') . $anchor; + } else { + foreach ($vars as $var => $val) { + $val = (string) $val; + if ('' !== $val) { + $url .= $depr . $var . $depr . urlencode($val); + } + } + + $url .= $suffix . $anchor; + } + } else { + $url .= $suffix . $anchor; + } + + // 检测域名 + $domain = $this->parseDomain($url, $domain); + + // URL组装 + return $domain . rtrim($this->root, '/') . '/' . ltrim($url, '/'); + } + + public function __toString() + { + return $this->build(); + } + + public function __debugInfo() + { + return [ + 'url' => $this->url, + 'vars' => $this->vars, + 'suffix' => $this->suffix, + 'domain' => $this->domain, + ]; + } +} diff --git a/vendor/topthink/framework/src/think/route/dispatch/Callback.php b/vendor/topthink/framework/src/think/route/dispatch/Callback.php new file mode 100644 index 0000000..2044ef8 --- /dev/null +++ b/vendor/topthink/framework/src/think/route/dispatch/Callback.php @@ -0,0 +1,30 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\route\dispatch; + +use think\route\Dispatch; + +/** + * Callback Dispatcher + */ +class Callback extends Dispatch +{ + public function exec() + { + // 执行回调方法 + $vars = array_merge($this->request->param(), $this->param); + + return $this->app->invoke($this->dispatch, $vars); + } + +} diff --git a/vendor/topthink/framework/src/think/route/dispatch/Controller.php b/vendor/topthink/framework/src/think/route/dispatch/Controller.php new file mode 100644 index 0000000..611101b --- /dev/null +++ b/vendor/topthink/framework/src/think/route/dispatch/Controller.php @@ -0,0 +1,183 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\route\dispatch; + +use ReflectionClass; +use ReflectionException; +use ReflectionMethod; +use think\App; +use think\exception\ClassNotFoundException; +use think\exception\HttpException; +use think\helper\Str; +use think\route\Dispatch; + +/** + * Controller Dispatcher + */ +class Controller extends Dispatch +{ + /** + * 控制器名 + * @var string + */ + protected $controller; + + /** + * 操作名 + * @var string + */ + protected $actionName; + + public function init(App $app) + { + parent::init($app); + + $result = $this->dispatch; + + if (is_string($result)) { + $result = explode('/', $result); + } + + // 获取控制器名 + $controller = strip_tags($result[0] ?: $this->rule->config('default_controller')); + + if (strpos($controller, '.')) { + $pos = strrpos($controller, '.'); + $this->controller = substr($controller, 0, $pos) . '.' . Str::studly(substr($controller, $pos + 1)); + } else { + $this->controller = Str::studly($controller); + } + + // 获取操作名 + $this->actionName = strip_tags($result[1] ?: $this->rule->config('default_action')); + + // 设置当前请求的控制器、操作 + $this->request + ->setController($this->controller) + ->setAction($this->actionName); + } + + public function exec() + { + try { + // 实例化控制器 + $instance = $this->controller($this->controller); + } catch (ClassNotFoundException $e) { + throw new HttpException(404, 'controller not exists:' . $e->getClass()); + } + + // 注册控制器中间件 + $this->registerControllerMiddleware($instance); + + return $this->app->middleware->pipeline('controller') + ->send($this->request) + ->then(function () use ($instance) { + // 获取当前操作名 + $suffix = $this->rule->config('action_suffix'); + $action = $this->actionName . $suffix; + + if (is_callable([$instance, $action])) { + $vars = $this->request->param(); + try { + $reflect = new ReflectionMethod($instance, $action); + // 严格获取当前操作方法名 + $actionName = $reflect->getName(); + if ($suffix) { + $actionName = substr($actionName, 0, -strlen($suffix)); + } + + $this->request->setAction($actionName); + } catch (ReflectionException $e) { + $reflect = new ReflectionMethod($instance, '__call'); + $vars = [$action, $vars]; + $this->request->setAction($action); + } + } else { + // 操作不存在 + throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()'); + } + + $data = $this->app->invokeReflectMethod($instance, $reflect, $vars); + + return $this->autoResponse($data); + }); + } + + /** + * 使用反射机制注册控制器中间件 + * @access public + * @param object $controller 控制器实例 + * @return void + */ + protected function registerControllerMiddleware($controller): void + { + $class = new ReflectionClass($controller); + + if ($class->hasProperty('middleware')) { + $reflectionProperty = $class->getProperty('middleware'); + $reflectionProperty->setAccessible(true); + + $middlewares = $reflectionProperty->getValue($controller); + + foreach ($middlewares as $key => $val) { + if (!is_int($key)) { + if (isset($val['only']) && !in_array($this->request->action(true), array_map(function ($item) { + return strtolower($item); + }, is_string($val['only']) ? explode(",", $val['only']) : $val['only']))) { + continue; + } elseif (isset($val['except']) && in_array($this->request->action(true), array_map(function ($item) { + return strtolower($item); + }, is_string($val['except']) ? explode(',', $val['except']) : $val['except']))) { + continue; + } else { + $val = $key; + } + } + + if (is_string($val) && strpos($val, ':')) { + $val = explode(':', $val); + if (count($val) > 1) { + $val = [$val[0], array_slice($val, 1)]; + } + } + + $this->app->middleware->controller($val); + } + } + } + + /** + * 实例化访问控制器 + * @access public + * @param string $name 资源地址 + * @return object + * @throws ClassNotFoundException + */ + public function controller(string $name) + { + $suffix = $this->rule->config('controller_suffix') ? 'Controller' : ''; + + $controllerLayer = $this->rule->config('controller_layer') ?: 'controller'; + $emptyController = $this->rule->config('empty_controller') ?: 'Error'; + + $class = $this->app->parseClass($controllerLayer, $name . $suffix); + + if (class_exists($class)) { + return $this->app->make($class, [], true); + } elseif ($emptyController && class_exists($emptyClass = $this->app->parseClass($controllerLayer, $emptyController . $suffix))) { + return $this->app->make($emptyClass, [], true); + } + + throw new ClassNotFoundException('class not exists:' . $class, $class); + } +} diff --git a/vendor/topthink/framework/src/think/route/dispatch/Url.php b/vendor/topthink/framework/src/think/route/dispatch/Url.php new file mode 100644 index 0000000..147f5cb --- /dev/null +++ b/vendor/topthink/framework/src/think/route/dispatch/Url.php @@ -0,0 +1,118 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\route\dispatch; + +use think\exception\HttpException; +use think\helper\Str; +use think\Request; +use think\route\Rule; + +/** + * Url Dispatcher + */ +class Url extends Controller +{ + + public function __construct(Request $request, Rule $rule, $dispatch) + { + $this->request = $request; + $this->rule = $rule; + // 解析默认的URL规则 + $dispatch = $this->parseUrl($dispatch); + + parent::__construct($request, $rule, $dispatch, $this->param); + } + + /** + * 解析URL地址 + * @access protected + * @param string $url URL + * @return array + */ + protected function parseUrl(string $url): array + { + $depr = $this->rule->config('pathinfo_depr'); + $bind = $this->rule->getRouter()->getDomainBind(); + + if ($bind && preg_match('/^[a-z]/is', $bind)) { + $bind = str_replace('/', $depr, $bind); + // 如果有域名绑定 + $url = $bind . ('.' != substr($bind, -1) ? $depr : '') . ltrim($url, $depr); + } + + $path = $this->rule->parseUrlPath($url); + if (empty($path)) { + return [null, null]; + } + + // 解析控制器 + $controller = !empty($path) ? array_shift($path) : null; + + if ($controller && !preg_match('/^[A-Za-z0-9][\w|\.]*$/', $controller)) { + throw new HttpException(404, 'controller not exists:' . $controller); + } + + // 解析操作 + $action = !empty($path) ? array_shift($path) : null; + $var = []; + + // 解析额外参数 + if ($path) { + preg_replace_callback('/(\w+)\|([^\|]+)/', function ($match) use (&$var) { + $var[$match[1]] = strip_tags($match[2]); + }, implode('|', $path)); + } + + $panDomain = $this->request->panDomain(); + if ($panDomain && $key = array_search('*', $var)) { + // 泛域名赋值 + $var[$key] = $panDomain; + } + + // 设置当前请求的参数 + $this->param = $var; + + // 封装路由 + $route = [$controller, $action]; + + if ($this->hasDefinedRoute($route)) { + throw new HttpException(404, 'invalid request:' . str_replace('|', $depr, $url)); + } + + return $route; + } + + /** + * 检查URL是否已经定义过路由 + * @access protected + * @param array $route 路由信息 + * @return bool + */ + protected function hasDefinedRoute(array $route): bool + { + [$controller, $action] = $route; + + // 检查地址是否被定义过路由 + $name = strtolower(Str::studly($controller) . '/' . $action); + + $host = $this->request->host(true); + $method = $this->request->method(); + + if ($this->rule->getRouter()->getName($name, $host, $method)) { + return true; + } + + return false; + } + +} diff --git a/vendor/topthink/framework/src/think/service/ModelService.php b/vendor/topthink/framework/src/think/service/ModelService.php new file mode 100644 index 0000000..b517c4e --- /dev/null +++ b/vendor/topthink/framework/src/think/service/ModelService.php @@ -0,0 +1,53 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\service; + +use think\Model; +use think\Service; + +/** + * 模型服务类 + */ +class ModelService extends Service +{ + public function boot() + { + Model::setDb($this->app->db); + Model::setEvent($this->app->event); + Model::setInvoker([$this->app, 'invoke']); + Model::maker(function (Model $model) { + $config = $this->app->config; + + $isAutoWriteTimestamp = $model->getAutoWriteTimestamp(); + + if (is_null($isAutoWriteTimestamp)) { + // 自动写入时间戳 + $model->isAutoWriteTimestamp($config->get('database.auto_timestamp', 'timestamp')); + } + + $dateFormat = $model->getDateFormat(); + + if (is_null($dateFormat)) { + // 设置时间戳格式 + $model->setDateFormat($config->get('database.datetime_format', 'Y-m-d H:i:s')); + } + + $timeField = $config->get('database.datetime_field'); + if (!empty($timeField)) { + [$createTime, $updateTime] = explode(',', $timeField); + $model->setTimeField($createTime, $updateTime); + } + + }); + } +} diff --git a/vendor/topthink/framework/src/think/service/PaginatorService.php b/vendor/topthink/framework/src/think/service/PaginatorService.php new file mode 100644 index 0000000..a01977d --- /dev/null +++ b/vendor/topthink/framework/src/think/service/PaginatorService.php @@ -0,0 +1,52 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\service; + +use think\Paginator; +use think\paginator\driver\Bootstrap; +use think\Service; + +/** + * 分页服务类 + */ +class PaginatorService extends Service +{ + public function register() + { + if (!$this->app->bound(Paginator::class)) { + $this->app->bind(Paginator::class, Bootstrap::class); + } + } + + public function boot() + { + Paginator::maker(function (...$args) { + return $this->app->make(Paginator::class, $args, true); + }); + + Paginator::currentPathResolver(function () { + return $this->app->request->baseUrl(); + }); + + Paginator::currentPageResolver(function ($varPage = 'page') { + + $page = $this->app->request->param($varPage); + + if (filter_var($page, FILTER_VALIDATE_INT) !== false && (int) $page >= 1) { + return (int) $page; + } + + return 1; + }); + } +} diff --git a/vendor/topthink/framework/src/think/service/ValidateService.php b/vendor/topthink/framework/src/think/service/ValidateService.php new file mode 100644 index 0000000..94d7638 --- /dev/null +++ b/vendor/topthink/framework/src/think/service/ValidateService.php @@ -0,0 +1,31 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\service; + +use think\Service; +use think\Validate; + +/** + * 验证服务类 + */ +class ValidateService extends Service +{ + public function boot() + { + Validate::maker(function (Validate $validate) { + $validate->setLang($this->app->lang); + $validate->setDb($this->app->db); + $validate->setRequest($this->app->request); + }); + } +} diff --git a/vendor/topthink/framework/src/think/session/Store.php b/vendor/topthink/framework/src/think/session/Store.php new file mode 100644 index 0000000..49e1ba9 --- /dev/null +++ b/vendor/topthink/framework/src/think/session/Store.php @@ -0,0 +1,340 @@ + +// +---------------------------------------------------------------------- + +namespace think\session; + +use think\contract\SessionHandlerInterface; +use think\helper\Arr; + +class Store +{ + + /** + * Session数据 + * @var array + */ + protected $data = []; + + /** + * 是否初始化 + * @var bool + */ + protected $init = null; + + /** + * 记录Session name + * @var string + */ + protected $name = 'PHPSESSID'; + + /** + * 记录Session Id + * @var string + */ + protected $id; + + /** + * @var SessionHandlerInterface + */ + protected $handler; + + /** @var array */ + protected $serialize = []; + + public function __construct($name, SessionHandlerInterface $handler, array $serialize = null) + { + $this->name = $name; + $this->handler = $handler; + + if (!empty($serialize)) { + $this->serialize = $serialize; + } + + $this->setId(); + } + + /** + * 设置数据 + * @access public + * @param array $data + * @return void + */ + public function setData(array $data): void + { + $this->data = $data; + } + + /** + * session初始化 + * @access public + * @return void + */ + public function init(): void + { + // 读取缓存数据 + $data = $this->handler->read($this->getId()); + + if (!empty($data)) { + $this->data = array_merge($this->data, $this->unserialize($data)); + } + + $this->init = true; + } + + /** + * 设置SessionName + * @access public + * @param string $name session_name + * @return void + */ + public function setName(string $name): void + { + $this->name = $name; + } + + /** + * 获取sessionName + * @access public + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * session_id设置 + * @access public + * @param string $id session_id + * @return void + */ + public function setId($id = null): void + { + $this->id = is_string($id) && strlen($id) === 32 && ctype_alnum($id) ? $id : md5(microtime(true) . session_create_id()); + } + + /** + * 获取session_id + * @access public + * @return string + */ + public function getId(): string + { + return $this->id; + } + + /** + * 获取所有数据 + * @return array + */ + public function all(): array + { + return $this->data; + } + + /** + * session设置 + * @access public + * @param string $name session名称 + * @param mixed $value session值 + * @return void + */ + public function set(string $name, $value): void + { + Arr::set($this->data, $name, $value); + } + + /** + * session获取 + * @access public + * @param string $name session名称 + * @param mixed $default 默认值 + * @return mixed + */ + public function get(string $name, $default = null) + { + return Arr::get($this->data, $name, $default); + } + + /** + * session获取并删除 + * @access public + * @param string $name session名称 + * @return mixed + */ + public function pull(string $name) + { + return Arr::pull($this->data, $name); + } + + /** + * 添加数据到一个session数组 + * @access public + * @param string $key + * @param mixed $value + * @return void + */ + public function push(string $key, $value): void + { + $array = $this->get($key, []); + + $array[] = $value; + + $this->set($key, $array); + } + + /** + * 判断session数据 + * @access public + * @param string $name session名称 + * @return bool + */ + public function has(string $name): bool + { + return Arr::has($this->data, $name); + } + + /** + * 删除session数据 + * @access public + * @param string $name session名称 + * @return void + */ + public function delete(string $name): void + { + Arr::forget($this->data, $name); + } + + /** + * 清空session数据 + * @access public + * @return void + */ + public function clear(): void + { + $this->data = []; + } + + /** + * 销毁session + */ + public function destroy(): void + { + $this->clear(); + + $this->regenerate(true); + } + + /** + * 重新生成session id + * @param bool $destroy + */ + public function regenerate(bool $destroy = false): void + { + if ($destroy) { + $this->handler->delete($this->getId()); + } + + $this->setId(); + } + + /** + * 保存session数据 + * @access public + * @return void + */ + public function save(): void + { + $this->clearFlashData(); + + $sessionId = $this->getId(); + + if (!empty($this->data)) { + $data = $this->serialize($this->data); + + $this->handler->write($sessionId, $data); + } else { + $this->handler->delete($sessionId); + } + + $this->init = false; + } + + /** + * session设置 下一次请求有效 + * @access public + * @param string $name session名称 + * @param mixed $value session值 + * @return void + */ + public function flash(string $name, $value): void + { + $this->set($name, $value); + $this->push('__flash__.__next__', $name); + $this->set('__flash__.__current__', Arr::except($this->get('__flash__.__current__', []), $name)); + } + + /** + * 将本次闪存数据推迟到下次请求 + * + * @return void + */ + public function reflash(): void + { + $keys = $this->get('__flash__.__current__', []); + $values = array_unique(array_merge($this->get('__flash__.__next__', []), $keys)); + $this->set('__flash__.__next__', $values); + $this->set('__flash__.__current__', []); + } + + /** + * 清空当前请求的session数据 + * @access public + * @return void + */ + public function clearFlashData(): void + { + Arr::forget($this->data, $this->get('__flash__.__current__', [])); + if (!empty($next = $this->get('__flash__.__next__', []))) { + $this->set('__flash__.__current__', $next); + } else { + $this->delete('__flash__.__current__'); + } + $this->delete('__flash__.__next__'); + } + + /** + * 序列化数据 + * @access protected + * @param mixed $data + * @return string + */ + protected function serialize($data): string + { + $serialize = $this->serialize[0] ?? 'serialize'; + + return $serialize($data); + } + + /** + * 反序列化数据 + * @access protected + * @param string $data + * @return array + */ + protected function unserialize(string $data): array + { + $unserialize = $this->serialize[1] ?? 'unserialize'; + + return (array) $unserialize($data); + } + +} diff --git a/vendor/topthink/framework/src/think/session/driver/Cache.php b/vendor/topthink/framework/src/think/session/driver/Cache.php new file mode 100644 index 0000000..4fabc79 --- /dev/null +++ b/vendor/topthink/framework/src/think/session/driver/Cache.php @@ -0,0 +1,50 @@ + +// +---------------------------------------------------------------------- +namespace think\session\driver; + +use Psr\SimpleCache\CacheInterface; +use think\contract\SessionHandlerInterface; +use think\helper\Arr; + +class Cache implements SessionHandlerInterface +{ + + /** @var CacheInterface */ + protected $handler; + + /** @var integer */ + protected $expire; + + /** @var string */ + protected $prefix; + + public function __construct(\think\Cache $cache, array $config = []) + { + $this->handler = $cache->store(Arr::get($config, 'store')); + $this->expire = Arr::get($config, 'expire', 1440); + $this->prefix = Arr::get($config, 'prefix', ''); + } + + public function read(string $sessionId): string + { + return (string) $this->handler->get($this->prefix . $sessionId); + } + + public function delete(string $sessionId): bool + { + return $this->handler->delete($this->prefix . $sessionId); + } + + public function write(string $sessionId, string $data): bool + { + return $this->handler->set($this->prefix . $sessionId, $data, $this->expire); + } +} diff --git a/vendor/topthink/framework/src/think/session/driver/File.php b/vendor/topthink/framework/src/think/session/driver/File.php new file mode 100644 index 0000000..788f323 --- /dev/null +++ b/vendor/topthink/framework/src/think/session/driver/File.php @@ -0,0 +1,249 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\session\driver; + +use Closure; +use Exception; +use FilesystemIterator; +use Generator; +use SplFileInfo; +use think\App; +use think\contract\SessionHandlerInterface; + +/** + * Session 文件驱动 + */ +class File implements SessionHandlerInterface +{ + protected $config = [ + 'path' => '', + 'expire' => 1440, + 'prefix' => '', + 'data_compress' => false, + 'gc_probability' => 1, + 'gc_divisor' => 100, + ]; + + public function __construct(App $app, array $config = []) + { + $this->config = array_merge($this->config, $config); + + if (empty($this->config['path'])) { + $this->config['path'] = $app->getRuntimePath() . 'session' . DIRECTORY_SEPARATOR; + } elseif (substr($this->config['path'], -1) != DIRECTORY_SEPARATOR) { + $this->config['path'] .= DIRECTORY_SEPARATOR; + } + + $this->init(); + } + + /** + * 打开Session + * @access protected + * @throws Exception + */ + protected function init(): void + { + try { + !is_dir($this->config['path']) && mkdir($this->config['path'], 0755, true); + } catch (\Exception $e) { + // 写入失败 + } + + // 垃圾回收 + if (random_int(1, $this->config['gc_divisor']) <= $this->config['gc_probability']) { + $this->gc(); + } + } + + /** + * Session 垃圾回收 + * @access public + * @return void + */ + public function gc(): void + { + $lifetime = $this->config['expire']; + $now = time(); + + $files = $this->findFiles($this->config['path'], function (SplFileInfo $item) use ($lifetime, $now) { + return $now - $lifetime > $item->getMTime(); + }); + + foreach ($files as $file) { + $this->unlink($file->getPathname()); + } + } + + /** + * 查找文件 + * @param string $root + * @param Closure $filter + * @return Generator + */ + protected function findFiles(string $root, Closure $filter) + { + $items = new FilesystemIterator($root); + + /** @var SplFileInfo $item */ + foreach ($items as $item) { + if ($item->isDir() && !$item->isLink()) { + yield from $this->findFiles($item->getPathname(), $filter); + } else { + if ($filter($item)) { + yield $item; + } + } + } + } + + /** + * 取得变量的存储文件名 + * @access protected + * @param string $name 缓存变量名 + * @param bool $auto 是否自动创建目录 + * @return string + */ + protected function getFileName(string $name, bool $auto = false): string + { + if ($this->config['prefix']) { + // 使用子目录 + $name = $this->config['prefix'] . DIRECTORY_SEPARATOR . 'sess_' . $name; + } else { + $name = 'sess_' . $name; + } + + $filename = $this->config['path'] . $name; + $dir = dirname($filename); + + if ($auto && !is_dir($dir)) { + try { + mkdir($dir, 0755, true); + } catch (\Exception $e) { + // 创建失败 + } + } + + return $filename; + } + + /** + * 读取Session + * @access public + * @param string $sessID + * @return string + */ + public function read(string $sessID): string + { + $filename = $this->getFileName($sessID); + + if (is_file($filename) && filemtime($filename) >= time() - $this->config['expire']) { + $content = $this->readFile($filename); + + if ($this->config['data_compress'] && function_exists('gzcompress')) { + //启用数据压缩 + $content = (string) gzuncompress($content); + } + + return $content; + } + + return ''; + } + + /** + * 写文件(加锁) + * @param $path + * @param $content + * @return bool + */ + protected function writeFile($path, $content): bool + { + return (bool) file_put_contents($path, $content, LOCK_EX); + } + + /** + * 读取文件内容(加锁) + * @param $path + * @return string + */ + protected function readFile($path): string + { + $contents = ''; + + $handle = fopen($path, 'rb'); + + if ($handle) { + try { + if (flock($handle, LOCK_SH)) { + clearstatcache(true, $path); + + $contents = fread($handle, filesize($path) ?: 1); + + flock($handle, LOCK_UN); + } + } finally { + fclose($handle); + } + } + + return $contents; + } + + /** + * 写入Session + * @access public + * @param string $sessID + * @param string $sessData + * @return bool + */ + public function write(string $sessID, string $sessData): bool + { + $filename = $this->getFileName($sessID, true); + $data = $sessData; + + if ($this->config['data_compress'] && function_exists('gzcompress')) { + //数据压缩 + $data = gzcompress($data, 3); + } + + return $this->writeFile($filename, $data); + } + + /** + * 删除Session + * @access public + * @param string $sessID + * @return bool + */ + public function delete(string $sessID): bool + { + try { + return $this->unlink($this->getFileName($sessID)); + } catch (\Exception $e) { + return false; + } + } + + /** + * 判断文件是否存在后,删除 + * @access private + * @param string $file + * @return bool + */ + private function unlink(string $file): bool + { + return is_file($file) && unlink($file); + } + +} diff --git a/vendor/topthink/framework/src/think/validate/ValidateRule.php b/vendor/topthink/framework/src/think/validate/ValidateRule.php new file mode 100644 index 0000000..b741f53 --- /dev/null +++ b/vendor/topthink/framework/src/think/validate/ValidateRule.php @@ -0,0 +1,172 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\validate; + +/** + * Class ValidateRule + * @package think\validate + * @method ValidateRule confirm(mixed $rule, string $msg = '') static 验证是否和某个字段的值一致 + * @method ValidateRule different(mixed $rule, string $msg = '') static 验证是否和某个字段的值是否不同 + * @method ValidateRule egt(mixed $rule, string $msg = '') static 验证是否大于等于某个值 + * @method ValidateRule gt(mixed $rule, string $msg = '') static 验证是否大于某个值 + * @method ValidateRule elt(mixed $rule, string $msg = '') static 验证是否小于等于某个值 + * @method ValidateRule lt(mixed $rule, string $msg = '') static 验证是否小于某个值 + * @method ValidateRule eg(mixed $rule, string $msg = '') static 验证是否等于某个值 + * @method ValidateRule in(mixed $rule, string $msg = '') static 验证是否在范围内 + * @method ValidateRule notIn(mixed $rule, string $msg = '') static 验证是否不在某个范围 + * @method ValidateRule between(mixed $rule, string $msg = '') static 验证是否在某个区间 + * @method ValidateRule notBetween(mixed $rule, string $msg = '') static 验证是否不在某个区间 + * @method ValidateRule length(mixed $rule, string $msg = '') static 验证数据长度 + * @method ValidateRule max(mixed $rule, string $msg = '') static 验证数据最大长度 + * @method ValidateRule min(mixed $rule, string $msg = '') static 验证数据最小长度 + * @method ValidateRule after(mixed $rule, string $msg = '') static 验证日期 + * @method ValidateRule before(mixed $rule, string $msg = '') static 验证日期 + * @method ValidateRule expire(mixed $rule, string $msg = '') static 验证有效期 + * @method ValidateRule allowIp(mixed $rule, string $msg = '') static 验证IP许可 + * @method ValidateRule denyIp(mixed $rule, string $msg = '') static 验证IP禁用 + * @method ValidateRule regex(mixed $rule, string $msg = '') static 使用正则验证数据 + * @method ValidateRule token(mixed $rule='__token__', string $msg = '') static 验证表单令牌 + * @method ValidateRule is(mixed $rule, string $msg = '') static 验证字段值是否为有效格式 + * @method ValidateRule isRequire(mixed $rule = null, string $msg = '') static 验证字段必须 + * @method ValidateRule isNumber(mixed $rule = null, string $msg = '') static 验证字段值是否为数字 + * @method ValidateRule isArray(mixed $rule = null, string $msg = '') static 验证字段值是否为数组 + * @method ValidateRule isInteger(mixed $rule = null, string $msg = '') static 验证字段值是否为整形 + * @method ValidateRule isFloat(mixed $rule = null, string $msg = '') static 验证字段值是否为浮点数 + * @method ValidateRule isMobile(mixed $rule = null, string $msg = '') static 验证字段值是否为手机 + * @method ValidateRule isIdCard(mixed $rule = null, string $msg = '') static 验证字段值是否为身份证号码 + * @method ValidateRule isChs(mixed $rule = null, string $msg = '') static 验证字段值是否为中文 + * @method ValidateRule isChsDash(mixed $rule = null, string $msg = '') static 验证字段值是否为中文字母及下划线 + * @method ValidateRule isChsAlpha(mixed $rule = null, string $msg = '') static 验证字段值是否为中文和字母 + * @method ValidateRule isChsAlphaNum(mixed $rule = null, string $msg = '') static 验证字段值是否为中文字母和数字 + * @method ValidateRule isDate(mixed $rule = null, string $msg = '') static 验证字段值是否为有效格式 + * @method ValidateRule isBool(mixed $rule = null, string $msg = '') static 验证字段值是否为布尔值 + * @method ValidateRule isAlpha(mixed $rule = null, string $msg = '') static 验证字段值是否为字母 + * @method ValidateRule isAlphaDash(mixed $rule = null, string $msg = '') static 验证字段值是否为字母和下划线 + * @method ValidateRule isAlphaNum(mixed $rule = null, string $msg = '') static 验证字段值是否为字母和数字 + * @method ValidateRule isAccepted(mixed $rule = null, string $msg = '') static 验证字段值是否为yes, on, 或是 1 + * @method ValidateRule isEmail(mixed $rule = null, string $msg = '') static 验证字段值是否为有效邮箱格式 + * @method ValidateRule isUrl(mixed $rule = null, string $msg = '') static 验证字段值是否为有效URL地址 + * @method ValidateRule activeUrl(mixed $rule, string $msg = '') static 验证是否为合格的域名或者IP + * @method ValidateRule ip(mixed $rule, string $msg = '') static 验证是否有效IP + * @method ValidateRule fileExt(mixed $rule, string $msg = '') static 验证文件后缀 + * @method ValidateRule fileMime(mixed $rule, string $msg = '') static 验证文件类型 + * @method ValidateRule fileSize(mixed $rule, string $msg = '') static 验证文件大小 + * @method ValidateRule image(mixed $rule, string $msg = '') static 验证图像文件 + * @method ValidateRule method(mixed $rule, string $msg = '') static 验证请求类型 + * @method ValidateRule dateFormat(mixed $rule, string $msg = '') static 验证时间和日期是否符合指定格式 + * @method ValidateRule unique(mixed $rule, string $msg = '') static 验证是否唯一 + * @method ValidateRule behavior(mixed $rule, string $msg = '') static 使用行为类验证 + * @method ValidateRule filter(mixed $rule, string $msg = '') static 使用filter_var方式验证 + * @method ValidateRule requireIf(mixed $rule, string $msg = '') static 验证某个字段等于某个值的时候必须 + * @method ValidateRule requireCallback(mixed $rule, string $msg = '') static 通过回调方法验证某个字段是否必须 + * @method ValidateRule requireWith(mixed $rule, string $msg = '') static 验证某个字段有值的情况下必须 + * @method ValidateRule must(mixed $rule = null, string $msg = '') static 必须验证 + */ +class ValidateRule +{ + // 验证字段的名称 + protected $title; + + // 当前验证规则 + protected $rule = []; + + // 验证提示信息 + protected $message = []; + + /** + * 添加验证因子 + * @access protected + * @param string $name 验证名称 + * @param mixed $rule 验证规则 + * @param string $msg 提示信息 + * @return $this + */ + protected function addItem(string $name, $rule = null, string $msg = '') + { + if ($rule || 0 === $rule) { + $this->rule[$name] = $rule; + } else { + $this->rule[] = $name; + } + + $this->message[] = $msg; + + return $this; + } + + /** + * 获取验证规则 + * @access public + * @return array + */ + public function getRule(): array + { + return $this->rule; + } + + /** + * 获取验证字段名称 + * @access public + * @return string + */ + public function getTitle(): string + { + return $this->title ?: ''; + } + + /** + * 获取验证提示 + * @access public + * @return array + */ + public function getMsg(): array + { + return $this->message; + } + + /** + * 设置验证字段名称 + * @access public + * @return $this + */ + public function title(string $title) + { + $this->title = $title; + + return $this; + } + + public function __call($method, $args) + { + if ('is' == strtolower(substr($method, 0, 2))) { + $method = substr($method, 2); + } + + array_unshift($args, lcfirst($method)); + + return call_user_func_array([$this, 'addItem'], $args); + } + + public static function __callStatic($method, $args) + { + $rule = new static(); + + if ('is' == strtolower(substr($method, 0, 2))) { + $method = substr($method, 2); + } + + array_unshift($args, lcfirst($method)); + + return call_user_func_array([$rule, 'addItem'], $args); + } +} diff --git a/vendor/topthink/framework/src/think/view/driver/Php.php b/vendor/topthink/framework/src/think/view/driver/Php.php new file mode 100644 index 0000000..9e6e54a --- /dev/null +++ b/vendor/topthink/framework/src/think/view/driver/Php.php @@ -0,0 +1,191 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\view\driver; + +use RuntimeException; +use think\App; +use think\contract\TemplateHandlerInterface; +use think\helper\Str; + +/** + * PHP原生模板驱动 + */ +class Php implements TemplateHandlerInterface +{ + protected $template; + protected $content; + protected $app; + + // 模板引擎参数 + protected $config = [ + // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 3 保持操作方法 + 'auto_rule' => 1, + // 视图目录名 + 'view_dir_name' => 'view', + // 应用模板路径 + 'view_path' => '', + // 模板文件后缀 + 'view_suffix' => 'php', + // 模板文件名分隔符 + 'view_depr' => DIRECTORY_SEPARATOR, + ]; + + public function __construct(App $app, array $config = []) + { + $this->app = $app; + $this->config = array_merge($this->config, (array) $config); + } + + /** + * 检测是否存在模板文件 + * @access public + * @param string $template 模板文件或者模板规则 + * @return bool + */ + public function exists(string $template): bool + { + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + // 获取模板文件名 + $template = $this->parseTemplate($template); + } + + return is_file($template); + } + + /** + * 渲染模板文件 + * @access public + * @param string $template 模板文件 + * @param array $data 模板变量 + * @return void + */ + public function fetch(string $template, array $data = []): void + { + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + // 获取模板文件名 + $template = $this->parseTemplate($template); + } + + // 模板不存在 抛出异常 + if (!is_file($template)) { + throw new RuntimeException('template not exists:' . $template); + } + + $this->template = $template; + + extract($data, EXTR_OVERWRITE); + + include $this->template; + } + + /** + * 渲染模板内容 + * @access public + * @param string $content 模板内容 + * @param array $data 模板变量 + * @return void + */ + public function display(string $content, array $data = []): void + { + $this->content = $content; + + extract($data, EXTR_OVERWRITE); + eval('?>' . $this->content); + } + + /** + * 自动定位模板文件 + * @access private + * @param string $template 模板文件规则 + * @return string + */ + private function parseTemplate(string $template): string + { + $request = $this->app->request; + + // 获取视图根目录 + if (strpos($template, '@')) { + // 跨应用调用 + [$app, $template] = explode('@', $template); + } + + if ($this->config['view_path'] && !isset($app)) { + $path = $this->config['view_path']; + } else { + $appName = isset($app) ? $app : $this->app->http->getName(); + $view = $this->config['view_dir_name']; + + if (is_dir($this->app->getAppPath() . $view)) { + $path = isset($app) ? $this->app->getBasePath() . ($appName ? $appName . DIRECTORY_SEPARATOR : '') . $view . DIRECTORY_SEPARATOR : $this->app->getAppPath() . $view . DIRECTORY_SEPARATOR; + } else { + $path = $this->app->getRootPath() . $view . DIRECTORY_SEPARATOR . ($appName ? $appName . DIRECTORY_SEPARATOR : ''); + } + } + + $depr = $this->config['view_depr']; + + if (0 !== strpos($template, '/')) { + $template = str_replace(['/', ':'], $depr, $template); + $controller = $request->controller(); + if (strpos($controller, '.')) { + $pos = strrpos($controller, '.'); + $controller = substr($controller, 0, $pos) . '.' . Str::snake(substr($controller, $pos + 1)); + } else { + $controller = Str::snake($controller); + } + + if ($controller) { + if ('' == $template) { + // 如果模板文件名为空 按照默认规则定位 + if (2 == $this->config['auto_rule']) { + $template = $request->action(true); + } elseif (3 == $this->config['auto_rule']) { + $template = $request->action(); + } else { + $template = Str::snake($request->action()); + } + + $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $template; + } elseif (false === strpos($template, $depr)) { + $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $template; + } + } + } else { + $template = str_replace(['/', ':'], $depr, substr($template, 1)); + } + + return $path . ltrim($template, '/') . '.' . ltrim($this->config['view_suffix'], '.'); + } + + /** + * 配置模板引擎 + * @access private + * @param array $config 参数 + * @return void + */ + public function config(array $config): void + { + $this->config = array_merge($this->config, $config); + } + + /** + * 获取模板引擎配置 + * @access public + * @param string $name 参数名 + * @return mixed + */ + public function getConfig(string $name) + { + return $this->config[$name] ?? null; + } +} diff --git a/vendor/topthink/framework/src/tpl/think_exception.tpl b/vendor/topthink/framework/src/tpl/think_exception.tpl new file mode 100644 index 0000000..7766caf --- /dev/null +++ b/vendor/topthink/framework/src/tpl/think_exception.tpl @@ -0,0 +1,502 @@ +'.end($names).''; + } +} + +if (!function_exists('parse_file')) { + function parse_file($file, $line) + { + return ''.basename($file)." line {$line}".''; + } +} + +if (!function_exists('parse_args')) { + function parse_args($args) + { + $result = []; + foreach ($args as $key => $item) { + switch (true) { + case is_object($item): + $value = sprintf('object(%s)', parse_class(get_class($item))); + break; + case is_array($item): + if (count($item) > 3) { + $value = sprintf('[%s, ...]', parse_args(array_slice($item, 0, 3))); + } else { + $value = sprintf('[%s]', parse_args($item)); + } + break; + case is_string($item): + if (strlen($item) > 20) { + $value = sprintf( + '\'%s...\'', + htmlentities($item), + htmlentities(substr($item, 0, 20)) + ); + } else { + $value = sprintf("'%s'", htmlentities($item)); + } + break; + case is_int($item): + case is_float($item): + $value = $item; + break; + case is_null($item): + $value = 'null'; + break; + case is_bool($item): + $value = '' . ($item ? 'true' : 'false') . ''; + break; + case is_resource($item): + $value = 'resource'; + break; + default: + $value = htmlentities(str_replace("\n", '', var_export(strval($item), true))); + break; + } + + $result[] = is_int($key) ? $value : "'{$key}' => {$value}"; + } + + return implode(', ', $result); + } +} +if (!function_exists('echo_value')) { + function echo_value($val) + { + if (is_array($val) || is_object($val)) { + echo htmlentities(json_encode($val, JSON_PRETTY_PRINT)); + } elseif (is_bool($val)) { + echo $val ? 'true' : 'false'; + } elseif (is_scalar($val)) { + echo htmlentities($val); + } else { + echo 'Resource'; + } + } +} +?> + + + + + 系统发生错误 + + + + + + $trace) { ?> +
        +
        +
        +
        +

        +
        +

        +
        +
        + +
        +
          $value) { ?>
        1. ">
        +
        + +
        +

        Call Stack

        +
          +
        1. + +
        2. + +
        3. + +
        +
        +
        + + +
        +

        +
        + + + +
        +

        Exception Datas

        + $value) { ?> +
        element because TCPDF + // does not recognize e.g.
        element because TCPDF + // does not recognize e.g.
        + + + + + + $val) { ?> + + + + + + + +
        empty
        + +
        + + + +
        +

        Environment Variables

        + $value) { ?> + + + + + + + $val) { ?> + + + + + + + +
        empty
        + +
        + + + + + + + + diff --git a/vendor/topthink/framework/tests/AppTest.php b/vendor/topthink/framework/tests/AppTest.php new file mode 100644 index 0000000..6b86015 --- /dev/null +++ b/vendor/topthink/framework/tests/AppTest.php @@ -0,0 +1,215 @@ + 'class', + ]; + + public function register() + { + + } + + public function boot() + { + + } +} + +/** + * @property array initializers + */ +class AppTest extends TestCase +{ + /** @var App */ + protected $app; + + protected function setUp() + { + $this->app = new App(); + } + + protected function tearDown(): void + { + m::close(); + } + + public function testService() + { + $this->app->register(stdClass::class); + + $this->assertInstanceOf(stdClass::class, $this->app->getService(stdClass::class)); + + $service = m::mock(SomeService::class); + + $service->shouldReceive('register')->once(); + + $this->app->register($service); + + $this->assertEquals($service, $this->app->getService(SomeService::class)); + + $service2 = m::mock(SomeService::class); + + $service2->shouldReceive('register')->once(); + + $this->app->register($service2); + + $this->assertEquals($service, $this->app->getService(SomeService::class)); + + $this->app->register($service2, true); + + $this->assertEquals($service2, $this->app->getService(SomeService::class)); + + $service->shouldReceive('boot')->once(); + $service2->shouldReceive('boot')->once(); + + $this->app->boot(); + } + + public function testDebug() + { + $this->app->debug(false); + + $this->assertFalse($this->app->isDebug()); + + $this->app->debug(true); + + $this->assertTrue($this->app->isDebug()); + } + + public function testNamespace() + { + $namespace = 'test'; + + $this->app->setNamespace($namespace); + + $this->assertEquals($namespace, $this->app->getNamespace()); + } + + public function testVersion() + { + $this->assertEquals(App::VERSION, $this->app->version()); + } + + public function testPath() + { + $rootPath = __DIR__ . DIRECTORY_SEPARATOR; + + $app = new App($rootPath); + + $this->assertEquals($rootPath, $app->getRootPath()); + + $this->assertEquals(dirname(__DIR__) . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR, $app->getThinkPath()); + + $this->assertEquals($rootPath . 'app' . DIRECTORY_SEPARATOR, $app->getAppPath()); + + $appPath = $rootPath . 'app' . DIRECTORY_SEPARATOR . 'admin' . DIRECTORY_SEPARATOR; + $app->setAppPath($appPath); + $this->assertEquals($appPath, $app->getAppPath()); + + $this->assertEquals($rootPath . 'app' . DIRECTORY_SEPARATOR, $app->getBasePath()); + + $this->assertEquals($rootPath . 'config' . DIRECTORY_SEPARATOR, $app->getConfigPath()); + + $this->assertEquals($rootPath . 'runtime' . DIRECTORY_SEPARATOR, $app->getRuntimePath()); + + $runtimePath = $rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'admin' . DIRECTORY_SEPARATOR; + $app->setRuntimePath($runtimePath); + $this->assertEquals($runtimePath, $app->getRuntimePath()); + } + + /** + * @param vfsStreamDirectory $root + * @param bool $debug + * @return App + */ + protected function prepareAppForInitialize(vfsStreamDirectory $root, $debug = true) + { + $rootPath = $root->url() . DIRECTORY_SEPARATOR; + + $app = new App($rootPath); + + $initializer = m::mock(); + $initializer->shouldReceive('init')->once()->with($app); + + $app->instance($initializer->mockery_getName(), $initializer); + + (function () use ($initializer) { + $this->initializers = [$initializer->mockery_getName()]; + })->call($app); + + $env = m::mock(Env::class); + $env->shouldReceive('load')->once()->with($rootPath . '.env'); + $env->shouldReceive('get')->once()->with('config_ext', '.php')->andReturn('.php'); + $env->shouldReceive('get')->once()->with('app_debug')->andReturn($debug); + + $event = m::mock(Event::class); + $event->shouldReceive('trigger')->once()->with(AppInit::class); + $event->shouldReceive('bind')->once()->with([]); + $event->shouldReceive('listenEvents')->once()->with([]); + $event->shouldReceive('subscribe')->once()->with([]); + + $app->instance('env', $env); + $app->instance('event', $event); + + return $app; + } + + public function testInitialize() + { + $root = vfsStream::setup('rootDir', null, [ + '.env' => '', + 'app' => [ + 'common.php' => '', + 'event.php' => '[],"listen"=>[],"subscribe"=>[]];', + 'provider.php' => ' [ + 'app.php' => 'prepareAppForInitialize($root, true); + + $app->debug(false); + + $app->initialize(); + + $this->assertIsInt($app->getBeginMem()); + $this->assertIsFloat($app->getBeginTime()); + + $this->assertTrue($app->initialized()); + } + + public function testFactory() + { + $this->assertInstanceOf(stdClass::class, App::factory(stdClass::class)); + + $this->expectException(ClassNotFoundException::class); + + App::factory('SomeClass'); + } + + public function testParseClass() + { + $this->assertEquals('app\\controller\\SomeClass', $this->app->parseClass('controller', 'some_class')); + $this->app->setNamespace('app2'); + $this->assertEquals('app2\\controller\\SomeClass', $this->app->parseClass('controller', 'some_class')); + } + +} diff --git a/vendor/topthink/framework/tests/CacheTest.php b/vendor/topthink/framework/tests/CacheTest.php new file mode 100644 index 0000000..5b5a13c --- /dev/null +++ b/vendor/topthink/framework/tests/CacheTest.php @@ -0,0 +1,149 @@ +app = m::mock(App::class)->makePartial(); + + Container::setInstance($this->app); + $this->app->shouldReceive('make')->with(App::class)->andReturn($this->app); + $this->config = m::mock(Config::class)->makePartial(); + $this->app->shouldReceive('get')->with('config')->andReturn($this->config); + + $this->cache = new Cache($this->app); + } + + public function testGetConfig() + { + $config = [ + 'default' => 'file', + ]; + + $this->config->shouldReceive('get')->with('cache')->andReturn($config); + + $this->assertEquals($config, $this->cache->getConfig()); + + $this->expectException(InvalidArgumentException::class); + $this->cache->getStoreConfig('foo'); + } + + public function testCacheManagerInstances() + { + $this->config->shouldReceive('get')->with("cache.stores.single", null)->andReturn(['type' => 'file']); + + $channel1 = $this->cache->store('single'); + $channel2 = $this->cache->store('single'); + + $this->assertSame($channel1, $channel2); + } + + public function testFileCache() + { + $root = vfsStream::setup(); + + $this->config->shouldReceive('get')->with("cache.default", null)->andReturn('file'); + + $this->config->shouldReceive('get')->with("cache.stores.file", null)->andReturn(['type' => 'file', 'path' => $root->url()]); + + $this->cache->set('foo', 5); + $this->cache->inc('foo'); + $this->assertEquals(6, $this->cache->get('foo')); + $this->cache->dec('foo', 2); + $this->assertEquals(4, $this->cache->get('foo')); + + $this->cache->set('bar', true); + $this->assertTrue($this->cache->get('bar')); + + $this->cache->set('baz', null); + $this->assertNull($this->cache->get('baz')); + + $this->assertTrue($this->cache->has('baz')); + $this->cache->delete('baz'); + $this->assertFalse($this->cache->has('baz')); + $this->assertNull($this->cache->get('baz')); + $this->assertFalse($this->cache->get('baz', false)); + + $this->assertTrue($root->hasChildren()); + $this->cache->clear(); + $this->assertFalse($root->hasChildren()); + + //tags + $this->cache->tag('foo')->set('bar', 'foobar'); + $this->assertEquals('foobar', $this->cache->get('bar')); + $this->cache->tag('foo')->clear(); + $this->assertFalse($this->cache->has('bar')); + + //multiple + $this->cache->setMultiple(['foo' => ['foobar', 'bar'], 'foobar' => ['foo', 'bar']]); + $this->assertEquals(['foo' => ['foobar', 'bar'], 'foobar' => ['foo', 'bar']], $this->cache->getMultiple(['foo', 'foobar'])); + $this->assertTrue($this->cache->deleteMultiple(['foo', 'foobar'])); + } + + public function testRedisCache() + { + if (extension_loaded('redis')) { + return; + } + $this->config->shouldReceive('get')->with("cache.default", null)->andReturn('redis'); + $this->config->shouldReceive('get')->with("cache.stores.redis", null)->andReturn(['type' => 'redis']); + + $redis = m::mock('overload:\Predis\Client'); + + $redis->shouldReceive("set")->once()->with('foo', 5)->andReturnTrue(); + $redis->shouldReceive("incrby")->once()->with('foo', 1)->andReturnTrue(); + $redis->shouldReceive("decrby")->once()->with('foo', 2)->andReturnTrue(); + $redis->shouldReceive("get")->once()->with('foo')->andReturn('6'); + $redis->shouldReceive("get")->once()->with('foo')->andReturn('4'); + $redis->shouldReceive("set")->once()->with('bar', serialize(true))->andReturnTrue(); + $redis->shouldReceive("set")->once()->with('baz', serialize(null))->andReturnTrue(); + $redis->shouldReceive("del")->once()->with('baz')->andReturnTrue(); + $redis->shouldReceive("flushDB")->once()->andReturnTrue(); + $redis->shouldReceive("set")->once()->with('bar', serialize('foobar'))->andReturnTrue(); + $redis->shouldReceive("sAdd")->once()->with('tag:' . md5('foo'), 'bar')->andReturnTrue(); + $redis->shouldReceive("sMembers")->once()->with('tag:' . md5('foo'))->andReturn(['bar']); + $redis->shouldReceive("del")->once()->with(['bar'])->andReturnTrue(); + $redis->shouldReceive("del")->once()->with('tag:' . md5('foo'))->andReturnTrue(); + + $this->cache->set('foo', 5); + $this->cache->inc('foo'); + $this->assertEquals(6, $this->cache->get('foo')); + $this->cache->dec('foo', 2); + $this->assertEquals(4, $this->cache->get('foo')); + + $this->cache->set('bar', true); + $this->cache->set('baz', null); + $this->cache->delete('baz'); + $this->cache->clear(); + + //tags + $this->cache->tag('foo')->set('bar', 'foobar'); + $this->cache->tag('foo')->clear(); + } +} diff --git a/vendor/topthink/framework/tests/ConfigTest.php b/vendor/topthink/framework/tests/ConfigTest.php new file mode 100644 index 0000000..271a34f --- /dev/null +++ b/vendor/topthink/framework/tests/ConfigTest.php @@ -0,0 +1,46 @@ +setContent(" 'value1','key2'=>'value2'];"); + $root->addChild($file); + + $config = new Config(); + + $config->load($file->url(), 'test'); + + $this->assertEquals('value1', $config->get('test.key1')); + $this->assertEquals('value2', $config->get('test.key2')); + + $this->assertSame(['key1' => 'value1', 'key2' => 'value2'], $config->get('test')); + } + + public function testSetAndGet() + { + $config = new Config(); + + $config->set([ + 'key1' => 'value1', + 'key2' => [ + 'key3' => 'value3', + ], + ], 'test'); + + $this->assertTrue($config->has('test.key1')); + $this->assertEquals('value1', $config->get('test.key1')); + $this->assertEquals('value3', $config->get('test.key2.key3')); + + $this->assertEquals(['key3' => 'value3'], $config->get('test.key2')); + $this->assertFalse($config->has('test.key3')); + $this->assertEquals('none', $config->get('test.key3', 'none')); + } +} diff --git a/vendor/topthink/framework/tests/ContainerTest.php b/vendor/topthink/framework/tests/ContainerTest.php new file mode 100644 index 0000000..e27deb0 --- /dev/null +++ b/vendor/topthink/framework/tests/ContainerTest.php @@ -0,0 +1,314 @@ +name = $name; + } + + public function some(Container $container) + { + } + + protected function protectionFun() + { + return true; + } + + public static function test(Container $container) + { + return $container; + } + + public static function __make() + { + return new self('Taylor'); + } +} + +class SomeClass +{ + public $container; + + public $count = 0; + + public function __construct(Container $container) + { + $this->container = $container; + } +} + +class ContainerTest extends TestCase +{ + protected function tearDown(): void + { + Container::setInstance(null); + } + + public function testClosureResolution() + { + $container = new Container; + + Container::setInstance($container); + + $container->bind('name', function () { + return 'Taylor'; + }); + + $this->assertEquals('Taylor', $container->make('name')); + + $this->assertEquals('Taylor', Container::pull('name')); + } + + public function testGet() + { + $container = new Container; + + $this->expectException(ClassNotFoundException::class); + $this->expectExceptionMessage('class not exists: name'); + $container->get('name'); + + $container->bind('name', function () { + return 'Taylor'; + }); + + $this->assertSame('Taylor', $container->get('name')); + } + + public function testExist() + { + $container = new Container; + + $container->bind('name', function () { + return 'Taylor'; + }); + + $this->assertFalse($container->exists("name")); + + $container->make('name'); + + $this->assertTrue($container->exists('name')); + } + + public function testInstance() + { + $container = new Container; + + $container->bind('name', function () { + return 'Taylor'; + }); + + $this->assertEquals('Taylor', $container->get('name')); + + $container->bind('name2', Taylor::class); + + $object = new stdClass(); + + $this->assertFalse($container->exists('name2')); + + $container->instance('name2', $object); + + $this->assertTrue($container->exists('name2')); + + $this->assertTrue($container->exists(Taylor::class)); + + $this->assertEquals($object, $container->make(Taylor::class)); + + unset($container->name1); + + $this->assertFalse($container->exists('name1')); + + $container->delete('name2'); + + $this->assertFalse($container->exists('name2')); + + foreach ($container as $class => $instance) { + + } + } + + public function testBind() + { + $container = new Container; + + $object = new stdClass(); + + $container->bind(['name' => Taylor::class]); + + $container->bind('name2', $object); + + $container->bind('name3', Taylor::class); + $container->bind('name3', Taylor::class); + + $container->name4 = $object; + + $container['name5'] = $object; + + $this->assertTrue(isset($container->name4)); + + $this->assertTrue(isset($container['name5'])); + + $this->assertInstanceOf(Taylor::class, $container->get('name')); + + $this->assertSame($object, $container->get('name2')); + + $this->assertSame($object, $container->name4); + + $this->assertSame($object, $container['name5']); + + $this->assertInstanceOf(Taylor::class, $container->get('name3')); + + unset($container['name']); + + $this->assertFalse(isset($container['name'])); + + unset($container->name3); + + $this->assertFalse(isset($container->name3)); + } + + public function testAutoConcreteResolution() + { + $container = new Container; + + $taylor = $container->make(Taylor::class); + + $this->assertInstanceOf(Taylor::class, $taylor); + $this->assertAttributeSame('Taylor', 'name', $taylor); + } + + public function testGetAndSetInstance() + { + $this->assertInstanceOf(Container::class, Container::getInstance()); + + $object = new stdClass(); + + Container::setInstance($object); + + $this->assertSame($object, Container::getInstance()); + + Container::setInstance(function () { + return $this; + }); + + $this->assertSame($this, Container::getInstance()); + } + + public function testResolving() + { + $container = new Container(); + $container->bind(Container::class, $container); + + $container->resolving(function (SomeClass $taylor, Container $container) { + $taylor->count++; + }); + $container->resolving(SomeClass::class, function (SomeClass $taylor, Container $container) { + $taylor->count++; + }); + + /** @var SomeClass $someClass */ + $someClass = $container->invokeClass(SomeClass::class); + $this->assertEquals(2, $someClass->count); + } + + public function testInvokeFunctionWithoutMethodThrowsException() + { + $this->expectException(FuncNotFoundException::class); + $this->expectExceptionMessage('function not exists: ContainerTestCallStub()'); + $container = new Container(); + $container->invokeFunction('ContainerTestCallStub', []); + } + + public function testInvokeProtectionMethod() + { + $container = new Container(); + $this->assertTrue($container->invokeMethod([Taylor::class, 'protectionFun'], [], true)); + } + + public function testInvoke() + { + $container = new Container(); + + Container::setInstance($container); + + $container->bind(Container::class, $container); + + $stub = $this->createMock(Taylor::class); + + $stub->expects($this->once())->method('some')->with($container)->will($this->returnSelf()); + + $container->invokeMethod([$stub, 'some']); + + $this->assertEquals('48', $container->invoke('ord', ['0'])); + + $this->assertSame($container, $container->invoke(Taylor::class . '::test', [])); + + $this->assertSame($container, $container->invokeMethod(Taylor::class . '::test')); + + $reflect = new ReflectionMethod($container, 'exists'); + + $this->assertTrue($container->invokeReflectMethod($container, $reflect, [Container::class])); + + $this->assertSame($container, $container->invoke(function (Container $container) { + return $container; + })); + + $this->assertSame($container, $container->invoke(Taylor::class . '::test')); + + $object = $container->invokeClass(SomeClass::class); + $this->assertInstanceOf(SomeClass::class, $object); + $this->assertSame($container, $object->container); + + $stdClass = new stdClass(); + + $container->invoke(function (Container $container, stdClass $stdObject, $key1, $lowKey, $key2 = 'default') use ($stdClass) { + $this->assertEquals('value1', $key1); + $this->assertEquals('default', $key2); + $this->assertEquals('value2', $lowKey); + $this->assertSame($stdClass, $stdObject); + return $container; + }, ['some' => $stdClass, 'key1' => 'value1', 'low_key' => 'value2']); + } + + public function testInvokeMethodNotExists() + { + $container = $this->resolveContainer(); + $this->expectException(FuncNotFoundException::class); + + $container->invokeMethod([SomeClass::class, 'any']); + } + + public function testInvokeClassNotExists() + { + $container = new Container(); + + Container::setInstance($container); + + $container->bind(Container::class, $container); + + $this->expectExceptionObject(new ClassNotFoundException('class not exists: SomeClass')); + + $container->invokeClass('SomeClass'); + } + + protected function resolveContainer() + { + $container = new Container(); + + Container::setInstance($container); + return $container; + } + +} diff --git a/vendor/topthink/framework/tests/DbTest.php b/vendor/topthink/framework/tests/DbTest.php new file mode 100644 index 0000000..3bd0c1e --- /dev/null +++ b/vendor/topthink/framework/tests/DbTest.php @@ -0,0 +1,49 @@ +shouldReceive('get')->with('database.cache_store', null)->andReturn(null); + $cache->shouldReceive('store')->with(null)->andReturn($store); + + $db = Db::__make($event, $config, $log, $cache); + + $config->shouldReceive('get')->with('database.foo', null)->andReturn('foo'); + $this->assertEquals('foo', $db->getConfig('foo')); + + $config->shouldReceive('get')->with('database', [])->andReturn([]); + $this->assertEquals([], $db->getConfig()); + + $callback = function () { + }; + $event->shouldReceive('listen')->with('db.some', $callback); + $db->event('some', $callback); + + $event->shouldReceive('trigger')->with('db.some', null, false); + $db->trigger('some'); + } + +} diff --git a/vendor/topthink/framework/tests/EnvTest.php b/vendor/topthink/framework/tests/EnvTest.php new file mode 100644 index 0000000..cf2e65f --- /dev/null +++ b/vendor/topthink/framework/tests/EnvTest.php @@ -0,0 +1,82 @@ +setContent("key1=value1\nkey2=value2"); + $root->addChild($envFile); + + $env = new Env(); + + $env->load($envFile->url()); + + $this->assertEquals('value1', $env->get('key1')); + $this->assertEquals('value2', $env->get('key2')); + + $this->assertSame(['KEY1' => 'value1', 'KEY2' => 'value2'], $env->get()); + } + + public function testServerEnv() + { + $env = new Env(); + + $this->assertEquals('value2', $env->get('key2', 'value2')); + + putenv('PHP_KEY7=value7'); + putenv('PHP_KEY8=false'); + putenv('PHP_KEY9=true'); + + $this->assertEquals('value7', $env->get('key7')); + $this->assertFalse($env->get('KEY8')); + $this->assertTrue($env->get('key9')); + } + + public function testSetEnv() + { + $env = new Env(); + + $env->set([ + 'key1' => 'value1', + 'key2' => [ + 'key1' => 'value1-2', + ], + ]); + + $env->set('key3', 'value3'); + + $env->key4 = 'value4'; + + $env['key5'] = 'value5'; + + $this->assertEquals('value1', $env->get('key1')); + $this->assertEquals('value1-2', $env->get('key2.key1')); + + $this->assertEquals('value3', $env->get('key3')); + + $this->assertEquals('value4', $env->key4); + + $this->assertEquals('value5', $env['key5']); + + $this->expectException(Exception::class); + + unset($env['key5']); + } + + public function testHasEnv() + { + $env = new Env(); + $env->set(['foo' => 'bar']); + $this->assertTrue($env->has('foo')); + $this->assertTrue(isset($env->foo)); + $this->assertTrue($env->offsetExists('foo')); + } +} diff --git a/vendor/topthink/framework/tests/EventTest.php b/vendor/topthink/framework/tests/EventTest.php new file mode 100644 index 0000000..ded5a36 --- /dev/null +++ b/vendor/topthink/framework/tests/EventTest.php @@ -0,0 +1,134 @@ +app = m::mock(App::class)->makePartial(); + + Container::setInstance($this->app); + $this->app->shouldReceive('make')->with(App::class)->andReturn($this->app); + $this->config = m::mock(Config::class)->makePartial(); + $this->app->shouldReceive('get')->with('config')->andReturn($this->config); + + $this->event = new Event($this->app); + } + + public function testBasic() + { + $this->event->bind(['foo' => 'baz']); + + $this->event->listen('foo', function ($bar) { + $this->assertEquals('bar', $bar); + }); + + $this->assertTrue($this->event->hasListener('foo')); + + $this->event->trigger('baz', 'bar'); + + $this->event->remove('foo'); + + $this->assertFalse($this->event->hasListener('foo')); + } + + public function testOnceEvent() + { + $this->event->listen('AppInit', function ($bar) { + $this->assertEquals('bar', $bar); + return 'foo'; + }); + + $this->assertEquals('foo', $this->event->trigger('AppInit', 'bar', true)); + $this->assertEquals(['foo'], $this->event->trigger('AppInit', 'bar')); + } + + public function testClassListener() + { + $listener = m::mock("overload:SomeListener", TestListener::class); + + $listener->shouldReceive('handle')->andReturnTrue(); + + $this->event->listen('some', "SomeListener"); + + $this->assertTrue($this->event->until('some')); + } + + public function testSubscribe() + { + $listener = m::mock("overload:SomeListener", TestListener::class); + + $listener->shouldReceive('subscribe')->andReturnUsing(function (Event $event) use ($listener) { + + $listener->shouldReceive('onBar')->once()->andReturnFalse(); + + $event->listenEvents(['SomeListener::onBar' => [[$listener, 'onBar']]]); + }); + + $this->event->subscribe('SomeListener'); + + $this->assertTrue($this->event->hasListener('SomeListener::onBar')); + + $this->event->trigger('SomeListener::onBar'); + } + + public function testAutoObserve() + { + $listener = m::mock("overload:SomeListener", TestListener::class); + + $listener->shouldReceive('onBar')->once(); + + $this->app->shouldReceive('make')->with('SomeListener')->andReturn($listener); + + $this->event->observe('SomeListener'); + + $this->event->trigger('bar'); + } + +} + +class TestListener +{ + public function handle() + { + + } + + public function onBar() + { + + } + + public function onFoo() + { + + } + + public function subscribe() + { + + } +} diff --git a/vendor/topthink/framework/tests/FilesystemTest.php b/vendor/topthink/framework/tests/FilesystemTest.php new file mode 100644 index 0000000..df5ffe2 --- /dev/null +++ b/vendor/topthink/framework/tests/FilesystemTest.php @@ -0,0 +1,131 @@ +app = m::mock(App::class)->makePartial(); + Container::setInstance($this->app); + $this->app->shouldReceive('make')->with(App::class)->andReturn($this->app); + $this->config = m::mock(Config::class); + $this->config->shouldReceive('get')->with('filesystem.default', null)->andReturn('local'); + $this->app->shouldReceive('get')->with('config')->andReturn($this->config); + $this->filesystem = new Filesystem($this->app); + + $this->root = vfsStream::setup('rootDir'); + } + + protected function tearDown(): void + { + m::close(); + } + + public function testDisk() + { + $this->config->shouldReceive('get')->with('filesystem.disks.local', null)->andReturn([ + 'type' => 'local', + 'root' => $this->root->url(), + ]); + + $this->config->shouldReceive('get')->with('filesystem.disks.foo', null)->andReturn([ + 'type' => 'local', + 'root' => $this->root->url(), + ]); + + $this->assertInstanceOf(Local::class, $this->filesystem->disk()); + + $this->assertInstanceOf(Local::class, $this->filesystem->disk('foo')); + } + + public function testCache() + { + $this->config->shouldReceive('get')->with('filesystem.disks.local', null)->andReturn([ + 'type' => 'local', + 'root' => $this->root->url(), + 'cache' => true, + ]); + + $this->assertInstanceOf(Local::class, $this->filesystem->disk()); + + $this->config->shouldReceive('get')->with('filesystem.disks.cache', null)->andReturn([ + 'type' => NullDriver::class, + 'root' => $this->root->url(), + 'cache' => [ + 'store' => 'flysystem', + ], + ]); + + $cache = m::mock(Cache::class); + + $cacheDriver = m::mock(File::class); + + $cache->shouldReceive('store')->once()->with('flysystem')->andReturn($cacheDriver); + + $this->app->shouldReceive('make')->with(Cache::class)->andReturn($cache); + + $cacheDriver->shouldReceive('get')->with('flysystem')->once()->andReturn(null); + + $cacheDriver->shouldReceive('set')->withAnyArgs(); + + $this->filesystem->disk('cache')->put('test.txt', 'aa'); + } + + public function testPutFile() + { + $root = vfsStream::setup('rootDir', null, [ + 'foo.jpg' => 'hello', + ]); + + $this->config->shouldReceive('get')->with('filesystem.disks.local', null)->andReturn([ + 'type' => NullDriver::class, + 'root' => $root->url(), + 'cache' => true, + ]); + + $file = m::mock(\think\File::class); + + $file->shouldReceive('hashName')->with(null)->once()->andReturn('foo.jpg'); + + $file->shouldReceive('getRealPath')->once()->andReturn($root->getChild('foo.jpg')->url()); + + $this->filesystem->putFile('test', $file); + } +} + +class NullDriver extends Driver +{ + protected function createAdapter(): AdapterInterface + { + return new NullAdapter(); + } +} diff --git a/vendor/topthink/framework/tests/HttpTest.php b/vendor/topthink/framework/tests/HttpTest.php new file mode 100644 index 0000000..c3e0abd --- /dev/null +++ b/vendor/topthink/framework/tests/HttpTest.php @@ -0,0 +1,155 @@ +app = m::mock(App::class)->makePartial(); + + $this->http = m::mock(Http::class, [$this->app])->shouldAllowMockingProtectedMethods()->makePartial(); + } + + protected function prepareApp($request, $response) + { + $this->app->shouldReceive('instance')->once()->with('request', $request); + $this->app->shouldReceive('initialized')->once()->andReturnFalse(); + $this->app->shouldReceive('initialize')->once(); + $this->app->shouldReceive('get')->with('request')->andReturn($request); + + $route = m::mock(Route::class); + + $route->shouldReceive('dispatch')->withArgs(function ($req, $withRoute) use ($request) { + if ($withRoute) { + $withRoute(); + } + return $req === $request; + })->andReturn($response); + + $route->shouldReceive('config')->with('route_annotation')->andReturn(true); + + $this->app->shouldReceive('get')->with('route')->andReturn($route); + + $console = m::mock(Console::class); + + $console->shouldReceive('call'); + + $this->app->shouldReceive('get')->with('console')->andReturn($console); + } + + public function testRun() + { + $root = vfsStream::setup('rootDir', null, [ + 'app' => [ + 'controller' => [], + 'middleware.php' => ' [ + 'route.php' => 'app->shouldReceive('getBasePath')->andReturn($root->getChild('app')->url() . DIRECTORY_SEPARATOR); + $this->app->shouldReceive('getRootPath')->andReturn($root->url() . DIRECTORY_SEPARATOR); + + $request = m::mock(Request::class)->makePartial(); + $response = m::mock(Response::class)->makePartial(); + + $this->prepareApp($request, $response); + + $this->assertEquals($response, $this->http->run($request)); + } + + public function multiAppRunProvider() + { + $request1 = m::mock(Request::class)->makePartial(); + $request1->shouldReceive('subDomain')->andReturn('www'); + $request1->shouldReceive('host')->andReturn('www.domain.com'); + + $request2 = m::mock(Request::class)->makePartial(); + $request2->shouldReceive('subDomain')->andReturn('app2'); + $request2->shouldReceive('host')->andReturn('app2.domain.com'); + + $request3 = m::mock(Request::class)->makePartial(); + $request3->shouldReceive('pathinfo')->andReturn('some1/a/b/c'); + + $request4 = m::mock(Request::class)->makePartial(); + $request4->shouldReceive('pathinfo')->andReturn('app3/a/b/c'); + + $request5 = m::mock(Request::class)->makePartial(); + $request5->shouldReceive('pathinfo')->andReturn('some2/a/b/c'); + + return [ + [$request1, true, 'app1'], + [$request2, true, 'app2'], + [$request3, true, 'app3'], + [$request4, true, null], + [$request5, true, 'some2', 'path'], + [$request1, false, 'some3'], + ]; + } + + public function testRunWithException() + { + $request = m::mock(Request::class); + $response = m::mock(Response::class); + + $this->app->shouldReceive('instance')->once()->with('request', $request); + $this->app->shouldReceive('initialize')->once(); + + $exception = new Exception(); + + $this->http->shouldReceive('runWithRequest')->once()->with($request)->andThrow($exception); + + $handle = m::mock(Handle::class); + + $handle->shouldReceive('report')->once()->with($exception); + $handle->shouldReceive('render')->once()->with($request, $exception)->andReturn($response); + + $this->app->shouldReceive('make')->with(Handle::class)->andReturn($handle); + + $this->assertEquals($response, $this->http->run($request)); + } + + public function testEnd() + { + $response = m::mock(Response::class); + $event = m::mock(Event::class); + $event->shouldReceive('trigger')->once()->with(HttpEnd::class, $response); + $this->app->shouldReceive('get')->once()->with('event')->andReturn($event); + $log = m::mock(Log::class); + $log->shouldReceive('save')->once(); + $this->app->shouldReceive('get')->once()->with('log')->andReturn($log); + + $this->http->end($response); + } + +} diff --git a/vendor/topthink/framework/tests/InteractsWithApp.php b/vendor/topthink/framework/tests/InteractsWithApp.php new file mode 100644 index 0000000..f4fcf73 --- /dev/null +++ b/vendor/topthink/framework/tests/InteractsWithApp.php @@ -0,0 +1,30 @@ +app = m::mock(App::class)->makePartial(); + Container::setInstance($this->app); + $this->app->shouldReceive('make')->with(App::class)->andReturn($this->app); + $this->app->shouldReceive('isDebug')->andReturnTrue(); + $this->config = m::mock(Config::class)->makePartial(); + $this->config->shouldReceive('get')->with('app.show_error_msg')->andReturnTrue(); + $this->app->shouldReceive('get')->with('config')->andReturn($this->config); + $this->app->shouldReceive('runningInConsole')->andReturn(false); + } +} diff --git a/vendor/topthink/framework/tests/LogTest.php b/vendor/topthink/framework/tests/LogTest.php new file mode 100644 index 0000000..981110f --- /dev/null +++ b/vendor/topthink/framework/tests/LogTest.php @@ -0,0 +1,130 @@ +prepareApp(); + + $this->log = new Log($this->app); + } + + public function testGetConfig() + { + $config = [ + 'default' => 'file', + ]; + + $this->config->shouldReceive('get')->with('log')->andReturn($config); + + $this->assertEquals($config, $this->log->getConfig()); + + $this->expectException(InvalidArgumentException::class); + $this->log->getChannelConfig('foo'); + } + + public function testChannel() + { + $this->assertInstanceOf(ChannelSet::class, $this->log->channel(['file', 'mail'])); + } + + public function testLogManagerInstances() + { + $this->config->shouldReceive('get')->with("log.channels.single", null)->andReturn(['type' => 'file']); + + $channel1 = $this->log->channel('single'); + $channel2 = $this->log->channel('single'); + + $this->assertSame($channel1, $channel2); + } + + public function testFileLog() + { + $root = vfsStream::setup(); + + $this->config->shouldReceive('get')->with("log.default", null)->andReturn('file'); + + $this->config->shouldReceive('get')->with("log.channels.file", null)->andReturn(['type' => 'file', 'path' => $root->url()]); + + $this->log->info('foo'); + + $this->assertEquals($this->log->getLog(), ['info' => ['foo']]); + + $this->log->clear(); + + $this->assertEmpty($this->log->getLog()); + + $this->log->error('foo'); + $this->assertArrayHasKey('error', $this->log->getLog()); + + $this->log->emergency('foo'); + $this->assertArrayHasKey('emergency', $this->log->getLog()); + + $this->log->alert('foo'); + $this->assertArrayHasKey('alert', $this->log->getLog()); + + $this->log->critical('foo'); + $this->assertArrayHasKey('critical', $this->log->getLog()); + + $this->log->warning('foo'); + $this->assertArrayHasKey('warning', $this->log->getLog()); + + $this->log->notice('foo'); + $this->assertArrayHasKey('notice', $this->log->getLog()); + + $this->log->debug('foo'); + $this->assertArrayHasKey('debug', $this->log->getLog()); + + $this->log->sql('foo'); + $this->assertArrayHasKey('sql', $this->log->getLog()); + + $this->log->custom('foo'); + $this->assertArrayHasKey('custom', $this->log->getLog()); + + $this->log->write('foo'); + $this->assertTrue($root->hasChildren()); + $this->assertEmpty($this->log->getLog()); + + $this->log->close(); + + $this->log->info('foo'); + + $this->assertEmpty($this->log->getLog()); + } + + public function testSave() + { + $root = vfsStream::setup(); + + $this->config->shouldReceive('get')->with("log.default", null)->andReturn('file'); + + $this->config->shouldReceive('get')->with("log.channels.file", null)->andReturn(['type' => 'file', 'path' => $root->url()]); + + $this->log->info('foo'); + + $this->log->save(); + + $this->assertTrue($root->hasChildren()); + } + +} diff --git a/vendor/topthink/framework/tests/MiddlewareTest.php b/vendor/topthink/framework/tests/MiddlewareTest.php new file mode 100644 index 0000000..aa53059 --- /dev/null +++ b/vendor/topthink/framework/tests/MiddlewareTest.php @@ -0,0 +1,108 @@ +prepareApp(); + + $this->middleware = new Middleware($this->app); + } + + public function testSetMiddleware() + { + $this->middleware->add('BarMiddleware', 'bar'); + + $this->assertEquals(1, count($this->middleware->all('bar'))); + + $this->middleware->controller('BarMiddleware'); + $this->assertEquals(1, count($this->middleware->all('controller'))); + + $this->middleware->import(['FooMiddleware']); + $this->assertEquals(1, count($this->middleware->all())); + + $this->middleware->unshift(['BazMiddleware', 'baz']); + $this->assertEquals(2, count($this->middleware->all())); + $this->assertEquals([['BazMiddleware', 'handle'], 'baz'], $this->middleware->all()[0]); + + $this->config->shouldReceive('get')->with('middleware.alias', [])->andReturn(['foo' => ['FooMiddleware', 'FarMiddleware']]); + + $this->middleware->add('foo'); + $this->assertEquals(3, count($this->middleware->all())); + $this->middleware->add(function () { + }); + $this->middleware->add(function () { + }); + $this->assertEquals(5, count($this->middleware->all())); + } + + public function testPipelineAndEnd() + { + $bar = m::mock("overload:BarMiddleware"); + $foo = m::mock("overload:FooMiddleware", Foo::class); + + $request = m::mock(Request::class); + $response = m::mock(Response::class); + + $e = new Exception(); + + $handle = m::mock(Handle::class); + $handle->shouldReceive('report')->with($e)->andReturnNull(); + $handle->shouldReceive('render')->with($request, $e)->andReturn($response); + + $foo->shouldReceive('handle')->once()->andReturnUsing(function ($request, $next) { + return $next($request); + }); + $bar->shouldReceive('handle')->once()->andReturnUsing(function ($request, $next) use ($e) { + $next($request); + throw $e; + }); + + $foo->shouldReceive('end')->once()->with($response)->andReturnNull(); + + $this->app->shouldReceive('make')->with(Handle::class)->andReturn($handle); + + $this->config->shouldReceive('get')->once()->with('middleware.priority', [])->andReturn(['FooMiddleware', 'BarMiddleware']); + + $this->middleware->import([function ($request, $next) { + return $next($request); + }, 'BarMiddleware', 'FooMiddleware']); + + $this->assertInstanceOf(Pipeline::class, $pipeline = $this->middleware->pipeline()); + + $pipeline->send($request)->then(function ($request) use ($e, $response) { + throw $e; + }); + + $this->middleware->end($response); + } +} + +class Foo +{ + public function end(Response $response) + { + } +} diff --git a/vendor/topthink/framework/tests/RouteTest.php b/vendor/topthink/framework/tests/RouteTest.php new file mode 100644 index 0000000..e992d0f --- /dev/null +++ b/vendor/topthink/framework/tests/RouteTest.php @@ -0,0 +1,286 @@ +prepareApp(); + $this->route = new Route($this->app); + } + + /** + * @param $path + * @param string $method + * @param string $host + * @return m\Mock|Request + */ + protected function makeRequest($path, $method = 'GET', $host = 'localhost') + { + $request = m::mock(Request::class)->makePartial(); + $request->shouldReceive('host')->andReturn($host); + $request->shouldReceive('pathinfo')->andReturn($path); + $request->shouldReceive('url')->andReturn('/' . $path); + $request->shouldReceive('method')->andReturn(strtoupper($method)); + return $request; + } + + public function testSimpleRequest() + { + $this->route->get('foo', function () { + return 'get-foo'; + }); + + $this->route->put('foo', function () { + return 'put-foo'; + }); + + $this->route->group(function () { + $this->route->post('foo', function () { + return 'post-foo'; + }); + }); + + $request = $this->makeRequest('foo', 'post'); + $response = $this->route->dispatch($request); + $this->assertEquals(200, $response->getCode()); + $this->assertEquals('post-foo', $response->getContent()); + + $request = $this->makeRequest('foo', 'get'); + $response = $this->route->dispatch($request); + $this->assertEquals(200, $response->getCode()); + $this->assertEquals('get-foo', $response->getContent()); + } + + public function testOptionsRequest() + { + $this->route->get('foo', function () { + return 'get-foo'; + }); + + $this->route->put('foo', function () { + return 'put-foo'; + }); + + $this->route->group(function () { + $this->route->post('foo', function () { + return 'post-foo'; + }); + }); + $this->route->group('abc', function () { + $this->route->post('foo/:id', function () { + return 'post-abc-foo'; + }); + }); + + $this->route->post('foo/:id', function () { + return 'post-abc-foo'; + }); + + $this->route->resource('bar', 'SomeClass'); + + $request = $this->makeRequest('foo', 'options'); + $response = $this->route->dispatch($request); + $this->assertEquals(204, $response->getCode()); + $this->assertEquals('GET, PUT, POST', $response->getHeader('Allow')); + + $request = $this->makeRequest('bar', 'options'); + $response = $this->route->dispatch($request); + $this->assertEquals(204, $response->getCode()); + $this->assertEquals('GET, POST', $response->getHeader('Allow')); + + $request = $this->makeRequest('bar/1', 'options'); + $response = $this->route->dispatch($request); + $this->assertEquals(204, $response->getCode()); + $this->assertEquals('GET, PUT, DELETE', $response->getHeader('Allow')); + + $request = $this->makeRequest('xxxx', 'options'); + $response = $this->route->dispatch($request); + $this->assertEquals(204, $response->getCode()); + $this->assertEquals('GET, POST, PUT, DELETE', $response->getHeader('Allow')); + } + + public function testAllowCrossDomain() + { + $this->route->get('foo', function () { + return 'get-foo'; + })->allowCrossDomain(['some' => 'bar']); + + $request = $this->makeRequest('foo', 'get'); + $response = $this->route->dispatch($request); + + $this->assertEquals('bar', $response->getHeader('some')); + $this->assertArrayHasKey('Access-Control-Allow-Credentials', $response->getHeader()); + + $request = $this->makeRequest('foo2', 'options'); + $response = $this->route->dispatch($request); + + $this->assertEquals(204, $response->getCode()); + $this->assertArrayHasKey('Access-Control-Allow-Credentials', $response->getHeader()); + $this->assertEquals('GET, POST, PUT, DELETE', $response->getHeader('Allow')); + } + + public function testControllerDispatch() + { + $this->route->get('foo', 'foo/bar'); + + $controller = m::Mock(\stdClass::class); + + $this->app->shouldReceive('parseClass')->with('controller', 'Foo')->andReturn($controller->mockery_getName()); + $this->app->shouldReceive('make')->with($controller->mockery_getName(), [], true)->andReturn($controller); + + $controller->shouldReceive('bar')->andReturn('bar'); + + $request = $this->makeRequest('foo'); + $response = $this->route->dispatch($request); + $this->assertEquals('bar', $response->getContent()); + } + + public function testEmptyControllerDispatch() + { + $this->route->get('foo', 'foo/bar'); + + $controller = m::Mock(\stdClass::class); + + $this->app->shouldReceive('parseClass')->with('controller', 'Error')->andReturn($controller->mockery_getName()); + $this->app->shouldReceive('make')->with($controller->mockery_getName(), [], true)->andReturn($controller); + + $controller->shouldReceive('bar')->andReturn('bar'); + + $request = $this->makeRequest('foo'); + $response = $this->route->dispatch($request); + $this->assertEquals('bar', $response->getContent()); + } + + protected function createMiddleware($times = 1) + { + $middleware = m::mock(Str::random(5)); + $middleware->shouldReceive('handle')->times($times)->andReturnUsing(function ($request, Closure $next) { + return $next($request); + }); + $this->app->shouldReceive('make')->with($middleware->mockery_getName())->andReturn($middleware); + + return $middleware; + } + + public function testControllerWithMiddleware() + { + $this->route->get('foo', 'foo/bar'); + + $controller = m::mock(FooClass::class); + + $controller->middleware = [ + $this->createMiddleware()->mockery_getName() . ":params1:params2", + $this->createMiddleware(0)->mockery_getName() => ['except' => 'bar'], + $this->createMiddleware()->mockery_getName() => ['only' => 'bar'], + ]; + + $this->app->shouldReceive('parseClass')->with('controller', 'Foo')->andReturn($controller->mockery_getName()); + $this->app->shouldReceive('make')->with($controller->mockery_getName(), [], true)->andReturn($controller); + + $controller->shouldReceive('bar')->once()->andReturn('bar'); + + $request = $this->makeRequest('foo'); + $response = $this->route->dispatch($request); + $this->assertEquals('bar', $response->getContent()); + } + + public function testUrlDispatch() + { + $controller = m::mock(FooClass::class); + $controller->shouldReceive('index')->andReturn('bar'); + + $this->app->shouldReceive('parseClass')->once()->with('controller', 'Foo')->andReturn($controller->mockery_getName()); + $this->app->shouldReceive('make')->with($controller->mockery_getName(), [], true)->andReturn($controller); + + $request = $this->makeRequest('foo'); + $response = $this->route->dispatch($request); + $this->assertEquals('bar', $response->getContent()); + } + + public function testRedirectDispatch() + { + $this->route->redirect('foo', 'http://localhost', 302); + + $request = $this->makeRequest('foo'); + $this->app->shouldReceive('make')->with(Request::class)->andReturn($request); + $response = $this->route->dispatch($request); + + $this->assertInstanceOf(Redirect::class, $response); + $this->assertEquals(302, $response->getCode()); + $this->assertEquals('http://localhost', $response->getData()); + } + + public function testViewDispatch() + { + $this->route->view('foo', 'index/hello', ['city' => 'shanghai']); + + $request = $this->makeRequest('foo'); + $response = $this->route->dispatch($request); + + $this->assertInstanceOf(View::class, $response); + $this->assertEquals(['city' => 'shanghai'], $response->getVars()); + $this->assertEquals('index/hello', $response->getData()); + } + + public function testResponseDispatch() + { + $this->route->get('hello/:name', response() + ->data('Hello,ThinkPHP') + ->code(200) + ->contentType('text/plain')); + + $request = $this->makeRequest('hello/some'); + $response = $this->route->dispatch($request); + + $this->assertEquals('Hello,ThinkPHP', $response->getContent()); + $this->assertEquals(200, $response->getCode()); + } + + public function testDomainBindResponse() + { + $this->route->domain('test', function () { + $this->route->get('/', function () { + return 'Hello,ThinkPHP'; + }); + }); + + $request = $this->makeRequest('', 'get', 'test.domain.com'); + $response = $this->route->dispatch($request); + + $this->assertEquals('Hello,ThinkPHP', $response->getContent()); + $this->assertEquals(200, $response->getCode()); + } + +} + +class FooClass +{ + public $middleware = []; + + public function bar() + { + + } +} diff --git a/vendor/topthink/framework/tests/SessionTest.php b/vendor/topthink/framework/tests/SessionTest.php new file mode 100644 index 0000000..b3b48a7 --- /dev/null +++ b/vendor/topthink/framework/tests/SessionTest.php @@ -0,0 +1,225 @@ +app = m::mock(App::class)->makePartial(); + Container::setInstance($this->app); + + $this->app->shouldReceive('make')->with(App::class)->andReturn($this->app); + $this->config = m::mock(Config::class)->makePartial(); + + $this->app->shouldReceive('get')->with('config')->andReturn($this->config); + $handlerClass = "\\think\\session\\driver\\Test" . Str::random(10); + $this->config->shouldReceive("get")->with("session.type", "file")->andReturn($handlerClass); + $this->session = new Session($this->app); + + $this->handler = m::mock('overload:' . $handlerClass, SessionHandlerInterface::class); + } + + public function testLoadData() + { + $data = [ + "bar" => 'foo', + ]; + + $id = md5(uniqid()); + + $this->handler->shouldReceive("read")->once()->with($id)->andReturn(serialize($data)); + + $this->session->setId($id); + $this->session->init(); + + $this->assertEquals('foo', $this->session->get('bar')); + $this->assertTrue($this->session->has('bar')); + $this->assertFalse($this->session->has('foo')); + + $this->session->set('foo', 'bar'); + $this->assertTrue($this->session->has('foo')); + + $this->assertEquals('bar', $this->session->pull('foo')); + $this->assertFalse($this->session->has('foo')); + } + + public function testSave() + { + + $id = md5(uniqid()); + + $this->handler->shouldReceive('read')->once()->with($id)->andReturn(""); + + $this->handler->shouldReceive('write')->once()->with($id, serialize([ + "bar" => 'foo', + ]))->andReturnTrue(); + + $this->session->setId($id); + $this->session->init(); + + $this->session->set('bar', 'foo'); + + $this->session->save(); + } + + public function testFlash() + { + $this->session->flash('foo', 'bar'); + $this->session->flash('bar', 0); + $this->session->flash('baz', true); + + $this->assertTrue($this->session->has('foo')); + $this->assertEquals('bar', $this->session->get('foo')); + $this->assertEquals(0, $this->session->get('bar')); + $this->assertTrue($this->session->get('baz')); + + $this->session->clearFlashData(); + + $this->assertTrue($this->session->has('foo')); + $this->assertEquals('bar', $this->session->get('foo')); + $this->assertEquals(0, $this->session->get('bar')); + + $this->session->clearFlashData(); + + $this->assertFalse($this->session->has('foo')); + $this->assertNull($this->session->get('foo')); + + $this->session->flash('foo', 'bar'); + $this->assertTrue($this->session->has('foo')); + $this->session->clearFlashData(); + $this->session->reflash(); + $this->session->clearFlashData(); + + $this->assertTrue($this->session->has('foo')); + } + + public function testClear() + { + $this->session->set('bar', 'foo'); + $this->assertEquals('foo', $this->session->get('bar')); + $this->session->clear(); + $this->assertFalse($this->session->has('foo')); + } + + public function testSetName() + { + $this->session->setName('foo'); + $this->assertEquals('foo', $this->session->getName()); + } + + public function testDestroy() + { + $id = md5(uniqid()); + + $this->handler->shouldReceive('read')->once()->with($id)->andReturn(""); + $this->handler->shouldReceive('delete')->once()->with($id)->andReturnTrue(); + + $this->session->setId($id); + $this->session->init(); + + $this->session->set('bar', 'foo'); + + $this->session->destroy(); + + $this->assertFalse($this->session->has('bar')); + + $this->assertNotEquals($id, $this->session->getId()); + } + + public function testFileHandler() + { + $root = vfsStream::setup(); + + vfsStream::newFile('bar') + ->at($root) + ->lastModified(time()); + + vfsStream::newFile('bar') + ->at(vfsStream::newDirectory("foo")->at($root)) + ->lastModified(100); + + $this->assertTrue($root->hasChild("bar")); + $this->assertTrue($root->hasChild("foo/bar")); + + $handler = new TestFileHandle($this->app, [ + 'path' => $root->url(), + 'gc_probability' => 1, + 'gc_divisor' => 1, + ]); + + $this->assertTrue($root->hasChild("bar")); + $this->assertFalse($root->hasChild("foo/bar")); + + $id = md5(uniqid()); + $handler->write($id, "bar"); + + $this->assertTrue($root->hasChild("sess_{$id}")); + + $this->assertEquals("bar", $handler->read($id)); + + $handler->delete($id); + + $this->assertFalse($root->hasChild("sess_{$id}")); + } + + public function testCacheHandler() + { + $id = md5(uniqid()); + + $cache = m::mock(\think\Cache::class); + + $store = m::mock(Driver::class); + + $cache->shouldReceive('store')->once()->with('redis')->andReturn($store); + + $handler = new Cache($cache, ['store' => 'redis']); + + $store->shouldReceive("set")->with($id, "bar", 1440)->once()->andReturnTrue(); + $handler->write($id, "bar"); + + $store->shouldReceive("get")->with($id)->once()->andReturn("bar"); + $this->assertEquals("bar", $handler->read($id)); + + $store->shouldReceive("delete")->with($id)->once()->andReturnTrue(); + $handler->delete($id); + } +} + +class TestFileHandle extends File +{ + protected function writeFile($path, $content): bool + { + return (bool) file_put_contents($path, $content); + } +} diff --git a/vendor/topthink/framework/tests/ViewTest.php b/vendor/topthink/framework/tests/ViewTest.php new file mode 100644 index 0000000..e413510 --- /dev/null +++ b/vendor/topthink/framework/tests/ViewTest.php @@ -0,0 +1,127 @@ +app = m::mock(App::class)->makePartial(); + Container::setInstance($this->app); + + $this->app->shouldReceive('make')->with(App::class)->andReturn($this->app); + $this->config = m::mock(Config::class)->makePartial(); + $this->app->shouldReceive('get')->with('config')->andReturn($this->config); + + $this->view = new View($this->app); + } + + public function testAssignData() + { + $this->view->assign('foo', 'bar'); + $this->view->assign(['baz' => 'boom']); + $this->view->qux = "corge"; + + $this->assertEquals('bar', $this->view->foo); + $this->assertEquals('boom', $this->view->baz); + $this->assertEquals('corge', $this->view->qux); + $this->assertTrue(isset($this->view->qux)); + } + + public function testRender() + { + $this->config->shouldReceive("get")->with("view.type", 'php')->andReturn(TestTemplate::class); + + $this->view->filter(function ($content) { + return $content; + }); + + $this->assertEquals("fetch", $this->view->fetch('foo')); + $this->assertEquals("display", $this->view->display('foo')); + } + +} + +class TestTemplate implements TemplateHandlerInterface +{ + + /** + * 检测是否存在模板文件 + * @access public + * @param string $template 模板文件或者模板规则 + * @return bool + */ + public function exists(string $template): bool + { + return true; + } + + /** + * 渲染模板文件 + * @access public + * @param string $template 模板文件 + * @param array $data 模板变量 + * @return void + */ + public function fetch(string $template, array $data = []): void + { + echo "fetch"; + } + + /** + * 渲染模板内容 + * @access public + * @param string $content 模板内容 + * @param array $data 模板变量 + * @return void + */ + public function display(string $content, array $data = []): void + { + echo "display"; + } + + /** + * 配置模板引擎 + * @access private + * @param array $config 参数 + * @return void + */ + public function config(array $config): void + { + // TODO: Implement config() method. + } + + /** + * 获取模板引擎配置 + * @access public + * @param string $name 参数名 + * @return void + */ + public function getConfig(string $name) + { + // TODO: Implement getConfig() method. + } +} diff --git a/vendor/topthink/framework/tests/bootstrap.php b/vendor/topthink/framework/tests/bootstrap.php new file mode 100644 index 0000000..3459061 --- /dev/null +++ b/vendor/topthink/framework/tests/bootstrap.php @@ -0,0 +1,3 @@ + composer require topthink/think-captcha + + + +## 使用 + +### 在控制器中输出验证码 + +在控制器的操作方法中使用 + +~~~ +public function captcha($id = '') +{ + return captcha($id); +} +~~~ +然后注册对应的路由来输出验证码 + + +### 模板里输出验证码 + +首先要在你应用的路由定义文件中,注册一个验证码路由规则。 + +~~~ +\think\facade\Route::get('captcha/[:id]', "\\think\\captcha\\CaptchaController@index"); +~~~ + +然后就可以在模板文件中使用 +~~~ +
        {:captcha_img()}
        +~~~ +或者 +~~~ +
        captcha
        +~~~ +> 上面两种的最终效果是一样的 + + +### 控制器里验证 + +使用TP的内置验证功能即可 +~~~ +$this->validate($data,[ + 'captcha|验证码'=>'require|captcha' +]); +~~~ +或者手动验证 +~~~ +if(!captcha_check($captcha)){ + //验证失败 +}; +~~~ \ No newline at end of file diff --git a/vendor/topthink/think-captcha/assets/bgs/1.jpg b/vendor/topthink/think-captcha/assets/bgs/1.jpg new file mode 100644 index 0000000..d417136 Binary files /dev/null and b/vendor/topthink/think-captcha/assets/bgs/1.jpg differ diff --git a/vendor/topthink/think-captcha/assets/bgs/2.jpg b/vendor/topthink/think-captcha/assets/bgs/2.jpg new file mode 100644 index 0000000..56640bd Binary files /dev/null and b/vendor/topthink/think-captcha/assets/bgs/2.jpg differ diff --git a/vendor/topthink/think-captcha/assets/bgs/3.jpg b/vendor/topthink/think-captcha/assets/bgs/3.jpg new file mode 100644 index 0000000..83e5bd9 Binary files /dev/null and b/vendor/topthink/think-captcha/assets/bgs/3.jpg differ diff --git a/vendor/topthink/think-captcha/assets/bgs/4.jpg b/vendor/topthink/think-captcha/assets/bgs/4.jpg new file mode 100644 index 0000000..97a3721 Binary files /dev/null and b/vendor/topthink/think-captcha/assets/bgs/4.jpg differ diff --git a/vendor/topthink/think-captcha/assets/bgs/5.jpg b/vendor/topthink/think-captcha/assets/bgs/5.jpg new file mode 100644 index 0000000..220a17a Binary files /dev/null and b/vendor/topthink/think-captcha/assets/bgs/5.jpg differ diff --git a/vendor/topthink/think-captcha/assets/bgs/6.jpg b/vendor/topthink/think-captcha/assets/bgs/6.jpg new file mode 100644 index 0000000..be53ea0 Binary files /dev/null and b/vendor/topthink/think-captcha/assets/bgs/6.jpg differ diff --git a/vendor/topthink/think-captcha/assets/bgs/7.jpg b/vendor/topthink/think-captcha/assets/bgs/7.jpg new file mode 100644 index 0000000..fbf537f Binary files /dev/null and b/vendor/topthink/think-captcha/assets/bgs/7.jpg differ diff --git a/vendor/topthink/think-captcha/assets/bgs/8.jpg b/vendor/topthink/think-captcha/assets/bgs/8.jpg new file mode 100644 index 0000000..e10cf28 Binary files /dev/null and b/vendor/topthink/think-captcha/assets/bgs/8.jpg differ diff --git a/vendor/topthink/think-captcha/assets/ttfs/1.ttf b/vendor/topthink/think-captcha/assets/ttfs/1.ttf new file mode 100644 index 0000000..9eae6f2 Binary files /dev/null and b/vendor/topthink/think-captcha/assets/ttfs/1.ttf differ diff --git a/vendor/topthink/think-captcha/assets/ttfs/2.ttf b/vendor/topthink/think-captcha/assets/ttfs/2.ttf new file mode 100644 index 0000000..6386c6b Binary files /dev/null and b/vendor/topthink/think-captcha/assets/ttfs/2.ttf differ diff --git a/vendor/topthink/think-captcha/assets/ttfs/3.ttf b/vendor/topthink/think-captcha/assets/ttfs/3.ttf new file mode 100644 index 0000000..678a491 Binary files /dev/null and b/vendor/topthink/think-captcha/assets/ttfs/3.ttf differ diff --git a/vendor/topthink/think-captcha/assets/ttfs/4.ttf b/vendor/topthink/think-captcha/assets/ttfs/4.ttf new file mode 100644 index 0000000..db43334 Binary files /dev/null and b/vendor/topthink/think-captcha/assets/ttfs/4.ttf differ diff --git a/vendor/topthink/think-captcha/assets/ttfs/5.ttf b/vendor/topthink/think-captcha/assets/ttfs/5.ttf new file mode 100644 index 0000000..8c082c8 Binary files /dev/null and b/vendor/topthink/think-captcha/assets/ttfs/5.ttf differ diff --git a/vendor/topthink/think-captcha/assets/ttfs/6.ttf b/vendor/topthink/think-captcha/assets/ttfs/6.ttf new file mode 100644 index 0000000..45a038b Binary files /dev/null and b/vendor/topthink/think-captcha/assets/ttfs/6.ttf differ diff --git a/vendor/topthink/think-captcha/assets/zhttfs/1.ttf b/vendor/topthink/think-captcha/assets/zhttfs/1.ttf new file mode 100644 index 0000000..1c14f7f Binary files /dev/null and b/vendor/topthink/think-captcha/assets/zhttfs/1.ttf differ diff --git a/vendor/topthink/think-captcha/composer.json b/vendor/topthink/think-captcha/composer.json new file mode 100644 index 0000000..e598819 --- /dev/null +++ b/vendor/topthink/think-captcha/composer.json @@ -0,0 +1,32 @@ +{ + "name": "topthink/think-captcha", + "description": "captcha package for thinkphp", + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "license": "Apache-2.0", + "require": { + "topthink/framework": "^6.0.0" + }, + "autoload": { + "psr-4": { + "think\\captcha\\": "src/" + }, + "files": [ + "src/helper.php" + ] + }, + "extra": { + "think": { + "services": [ + "think\\captcha\\CaptchaService" + ], + "config":{ + "captcha": "src/config.php" + } + } + } +} diff --git a/vendor/topthink/think-captcha/src/Captcha.php b/vendor/topthink/think-captcha/src/Captcha.php new file mode 100644 index 0000000..0789087 --- /dev/null +++ b/vendor/topthink/think-captcha/src/Captcha.php @@ -0,0 +1,340 @@ + +// +---------------------------------------------------------------------- + +namespace think\captcha; + +use Exception; +use think\Config; +use think\Response; +use think\Session; + +class Captcha +{ + private $im = null; // 验证码图片实例 + private $color = null; // 验证码字体颜色 + + /** + * @var Config|null + */ + private $config = null; + + /** + * @var Session|null + */ + private $session = null; + + // 验证码字符集合 + protected $codeSet = '2345678abcdefhijkmnpqrstuvwxyzABCDEFGHJKLMNPQRTUVWXY'; + // 验证码过期时间(s) + protected $expire = 1800; + // 使用中文验证码 + protected $useZh = false; + // 中文验证码字符串 + protected $zhSet = '们以我到他会作时要动国产的一是工就年阶义发成部民可出能方进在了不和有大这主中人上为来分生对于学下级地个用同行面说种过命度革而多子后自社加小机也经力线本电高量长党得实家定深法表着水理化争现所二起政三好十战无农使性前等反体合斗路图把结第里正新开论之物从当两些还天资事队批点育重其思与间内去因件日利相由压员气业代全组数果期导平各基或月毛然如应形想制心样干都向变关问比展那它最及外没看治提五解系林者米群头意只明四道马认次文通但条较克又公孔领军流入接席位情运器并飞原油放立题质指建区验活众很教决特此常石强极土少已根共直团统式转别造切九你取西持总料连任志观调七么山程百报更见必真保热委手改管处己将修支识病象几先老光专什六型具示复安带每东增则完风回南广劳轮科北打积车计给节做务被整联步类集号列温装即毫知轴研单色坚据速防史拉世设达尔场织历花受求传口断况采精金界品判参层止边清至万确究书术状厂须离再目海交权且儿青才证低越际八试规斯近注办布门铁需走议县兵固除般引齿千胜细影济白格效置推空配刀叶率述今选养德话查差半敌始片施响收华觉备名红续均药标记难存测士身紧液派准斤角降维板许破述技消底床田势端感往神便贺村构照容非搞亚磨族火段算适讲按值美态黄易彪服早班麦削信排台声该击素张密害侯草何树肥继右属市严径螺检左页抗苏显苦英快称坏移约巴材省黑武培著河帝仅针怎植京助升王眼她抓含苗副杂普谈围食射源例致酸旧却充足短划剂宣环落首尺波承粉践府鱼随考刻靠够满夫失包住促枝局菌杆周护岩师举曲春元超负砂封换太模贫减阳扬江析亩木言球朝医校古呢稻宋听唯输滑站另卫字鼓刚写刘微略范供阿块某功套友限项余倒卷创律雨让骨远帮初皮播优占死毒圈伟季训控激找叫云互跟裂粮粒母练塞钢顶策双留误础吸阻故寸盾晚丝女散焊功株亲院冷彻弹错散商视艺灭版烈零室轻血倍缺厘泵察绝富城冲喷壤简否柱李望盘磁雄似困巩益洲脱投送奴侧润盖挥距触星松送获兴独官混纪依未突架宽冬章湿偏纹吃执阀矿寨责熟稳夺硬价努翻奇甲预职评读背协损棉侵灰虽矛厚罗泥辟告卵箱掌氧恩爱停曾溶营终纲孟钱待尽俄缩沙退陈讨奋械载胞幼哪剥迫旋征槽倒握担仍呀鲜吧卡粗介钻逐弱脚怕盐末阴丰雾冠丙街莱贝辐肠付吉渗瑞惊顿挤秒悬姆烂森糖圣凹陶词迟蚕亿矩康遵牧遭幅园腔订香肉弟屋敏恢忘编印蜂急拿扩伤飞露核缘游振操央伍域甚迅辉异序免纸夜乡久隶缸夹念兰映沟乙吗儒杀汽磷艰晶插埃燃欢铁补咱芽永瓦倾阵碳演威附牙芽永瓦斜灌欧献顺猪洋腐请透司危括脉宜笑若尾束壮暴企菜穗楚汉愈绿拖牛份染既秋遍锻玉夏疗尖殖井费州访吹荣铜沿替滚客召旱悟刺脑措贯藏敢令隙炉壳硫煤迎铸粘探临薄旬善福纵择礼愿伏残雷延烟句纯渐耕跑泽慢栽鲁赤繁境潮横掉锥希池败船假亮谓托伙哲怀割摆贡呈劲财仪沉炼麻罪祖息车穿货销齐鼠抽画饲龙库守筑房歌寒喜哥洗蚀废纳腹乎录镜妇恶脂庄擦险赞钟摇典柄辩竹谷卖乱虚桥奥伯赶垂途额壁网截野遗静谋弄挂课镇妄盛耐援扎虑键归符庆聚绕摩忙舞遇索顾胶羊湖钉仁音迹碎伸灯避泛亡答勇频皇柳哈揭甘诺概宪浓岛袭谁洪谢炮浇斑讯懂灵蛋闭孩释乳巨徒私银伊景坦累匀霉杜乐勒隔弯绩招绍胡呼痛峰零柴簧午跳居尚丁秦稍追梁折耗碱殊岗挖氏刃剧堆赫荷胸衡勤膜篇登驻案刊秧缓凸役剪川雪链渔啦脸户洛孢勃盟买杨宗焦赛旗滤硅炭股坐蒸凝竟陷枪黎救冒暗洞犯筒您宋弧爆谬涂味津臂障褐陆啊健尊豆拔莫抵桑坡缝警挑污冰柬嘴啥饭塑寄赵喊垫丹渡耳刨虎笔稀昆浪萨茶滴浅拥穴覆伦娘吨浸袖珠雌妈紫戏塔锤震岁貌洁剖牢锋疑霸闪埔猛诉刷狠忽灾闹乔唐漏闻沈熔氯荒茎男凡抢像浆旁玻亦忠唱蒙予纷捕锁尤乘乌智淡允叛畜俘摸锈扫毕璃宝芯爷鉴秘净蒋钙肩腾枯抛轨堂拌爸循诱祝励肯酒绳穷塘燥泡袋朗喂铝软渠颗惯贸粪综墙趋彼届墨碍启逆卸航衣孙龄岭骗休借'; + // 使用背景图片 + protected $useImgBg = false; + // 验证码字体大小(px) + protected $fontSize = 25; + // 是否画混淆曲线 + protected $useCurve = true; + // 是否添加杂点 + protected $useNoise = true; + // 验证码图片高度 + protected $imageH = 0; + // 验证码图片宽度 + protected $imageW = 0; + // 验证码位数 + protected $length = 5; + // 验证码字体,不设置随机获取 + protected $fontttf = ''; + // 背景颜色 + protected $bg = [243, 251, 254]; + //算术验证码 + protected $math = false; + + /** + * 架构方法 设置参数 + * @access public + * @param Config $config + * @param Session $session + */ + public function __construct(Config $config, Session $session) + { + $this->config = $config; + $this->session = $session; + } + + /** + * 配置验证码 + * @param string|null $config + */ + protected function configure(string $config = null): void + { + if (is_null($config)) { + $config = $this->config->get('captcha', []); + } else { + $config = $this->config->get('captcha.' . $config, []); + } + + foreach ($config as $key => $val) { + if (property_exists($this, $key)) { + $this->{$key} = $val; + } + } + } + + /** + * 创建验证码 + * @return array + * @throws Exception + */ + protected function generate(): array + { + $bag = ''; + + if ($this->math) { + $this->useZh = false; + $this->length = 5; + + $x = random_int(10, 30); + $y = random_int(1, 9); + $bag = "{$x} + {$y} = "; + $key = $x + $y; + $key .= ''; + } else { + if ($this->useZh) { + $characters = preg_split('/(?zhSet); + } else { + $characters = str_split($this->codeSet); + } + + for ($i = 0; $i < $this->length; $i++) { + $bag .= $characters[rand(0, count($characters) - 1)]; + } + + $key = mb_strtolower($bag, 'UTF-8'); + } + + $hash = password_hash($key, PASSWORD_BCRYPT, ['cost' => 10]); + + $this->session->set('captcha', [ + 'key' => $hash, + ]); + + return [ + 'value' => $bag, + 'key' => $hash, + ]; + } + + /** + * 验证验证码是否正确 + * @access public + * @param string $code 用户验证码 + * @return bool 用户验证码是否正确 + */ + public function check(string $code): bool + { + if (!$this->session->has('captcha')) { + return false; + } + + $key = $this->session->get('captcha.key'); + + $code = mb_strtolower($code, 'UTF-8'); + + $res = password_verify($code, $key); + + if ($res) { + $this->session->delete('captcha'); + } + + return $res; + } + + /** + * 输出验证码并把验证码的值保存的session中 + * @access public + * @param null|string $config + * @param bool $api + * @return Response + */ + public function create(string $config = null, bool $api = false): Response + { + $this->configure($config); + + $generator = $this->generate(); + + // 图片宽(px) + $this->imageW || $this->imageW = $this->length * $this->fontSize * 1.5 + $this->length * $this->fontSize / 2; + // 图片高(px) + $this->imageH || $this->imageH = $this->fontSize * 2.5; + // 建立一幅 $this->imageW x $this->imageH 的图像 + $this->im = imagecreate($this->imageW, $this->imageH); + // 设置背景 + imagecolorallocate($this->im, $this->bg[0], $this->bg[1], $this->bg[2]); + + // 验证码字体随机颜色 + $this->color = imagecolorallocate($this->im, mt_rand(1, 150), mt_rand(1, 150), mt_rand(1, 150)); + + // 验证码使用随机字体 + $ttfPath = __DIR__ . '/../assets/' . ($this->useZh ? 'zhttfs' : 'ttfs') . '/'; + + if (empty($this->fontttf)) { + $dir = dir($ttfPath); + $ttfs = []; + while (false !== ($file = $dir->read())) { + if ('.' != $file[0] && substr($file, -4) == '.ttf') { + $ttfs[] = $file; + } + } + $dir->close(); + $this->fontttf = $ttfs[array_rand($ttfs)]; + } + + $fontttf = $ttfPath . $this->fontttf; + + if ($this->useImgBg) { + $this->background(); + } + + if ($this->useNoise) { + // 绘杂点 + $this->writeNoise(); + } + if ($this->useCurve) { + // 绘干扰线 + $this->writeCurve(); + } + + // 绘验证码 + $text = $this->useZh ? preg_split('/(? $char) { + + $x = $this->fontSize * ($index + 1) * mt_rand(1.2, 1.6) * ($this->math ? 1 : 1.5); + $y = $this->fontSize + mt_rand(10, 20); + $angle = $this->math ? 0 : mt_rand(-40, 40); + + imagettftext($this->im, $this->fontSize, $angle, $x, $y, $this->color, $fontttf, $char); + } + + ob_start(); + // 输出图像 + imagepng($this->im); + $content = ob_get_clean(); + imagedestroy($this->im); + + return response($content, 200, ['Content-Length' => strlen($content)])->contentType('image/png'); + } + + /** + * 画一条由两条连在一起构成的随机正弦函数曲线作干扰线(你可以改成更帅的曲线函数) + * + * 高中的数学公式咋都忘了涅,写出来 + * 正弦型函数解析式:y=Asin(ωx+φ)+b + * 各常数值对函数图像的影响: + * A:决定峰值(即纵向拉伸压缩的倍数) + * b:表示波形在Y轴的位置关系或纵向移动距离(上加下减) + * φ:决定波形与X轴位置关系或横向移动距离(左加右减) + * ω:决定周期(最小正周期T=2π/∣ω∣) + * + */ + protected function writeCurve(): void + { + $px = $py = 0; + + // 曲线前部分 + $A = mt_rand(1, $this->imageH / 2); // 振幅 + $b = mt_rand(-$this->imageH / 4, $this->imageH / 4); // Y轴方向偏移量 + $f = mt_rand(-$this->imageH / 4, $this->imageH / 4); // X轴方向偏移量 + $T = mt_rand($this->imageH, $this->imageW * 2); // 周期 + $w = (2 * M_PI) / $T; + + $px1 = 0; // 曲线横坐标起始位置 + $px2 = mt_rand($this->imageW / 2, $this->imageW * 0.8); // 曲线横坐标结束位置 + + for ($px = $px1; $px <= $px2; $px = $px + 1) { + if (0 != $w) { + $py = $A * sin($w * $px + $f) + $b + $this->imageH / 2; // y = Asin(ωx+φ) + b + $i = (int) ($this->fontSize / 5); + while ($i > 0) { + imagesetpixel($this->im, $px + $i, $py + $i, $this->color); // 这里(while)循环画像素点比imagettftext和imagestring用字体大小一次画出(不用这while循环)性能要好很多 + $i--; + } + } + } + + // 曲线后部分 + $A = mt_rand(1, $this->imageH / 2); // 振幅 + $f = mt_rand(-$this->imageH / 4, $this->imageH / 4); // X轴方向偏移量 + $T = mt_rand($this->imageH, $this->imageW * 2); // 周期 + $w = (2 * M_PI) / $T; + $b = $py - $A * sin($w * $px + $f) - $this->imageH / 2; + $px1 = $px2; + $px2 = $this->imageW; + + for ($px = $px1; $px <= $px2; $px = $px + 1) { + if (0 != $w) { + $py = $A * sin($w * $px + $f) + $b + $this->imageH / 2; // y = Asin(ωx+φ) + b + $i = (int) ($this->fontSize / 5); + while ($i > 0) { + imagesetpixel($this->im, $px + $i, $py + $i, $this->color); + $i--; + } + } + } + } + + /** + * 画杂点 + * 往图片上写不同颜色的字母或数字 + */ + protected function writeNoise(): void + { + $codeSet = '2345678abcdefhijkmnpqrstuvwxyz'; + for ($i = 0; $i < 10; $i++) { + //杂点颜色 + $noiseColor = imagecolorallocate($this->im, mt_rand(150, 225), mt_rand(150, 225), mt_rand(150, 225)); + for ($j = 0; $j < 5; $j++) { + // 绘杂点 + imagestring($this->im, 5, mt_rand(-10, $this->imageW), mt_rand(-10, $this->imageH), $codeSet[mt_rand(0, 29)], $noiseColor); + } + } + } + + /** + * 绘制背景图片 + * 注:如果验证码输出图片比较大,将占用比较多的系统资源 + */ + protected function background(): void + { + $path = __DIR__ . '/../assets/bgs/'; + $dir = dir($path); + + $bgs = []; + while (false !== ($file = $dir->read())) { + if ('.' != $file[0] && substr($file, -4) == '.jpg') { + $bgs[] = $path . $file; + } + } + $dir->close(); + + $gb = $bgs[array_rand($bgs)]; + + list($width, $height) = @getimagesize($gb); + // Resample + $bgImage = @imagecreatefromjpeg($gb); + @imagecopyresampled($this->im, $bgImage, 0, 0, 0, 0, $this->imageW, $this->imageH, $width, $height); + @imagedestroy($bgImage); + } + +} diff --git a/vendor/topthink/think-captcha/src/CaptchaController.php b/vendor/topthink/think-captcha/src/CaptchaController.php new file mode 100644 index 0000000..2c3cf59 --- /dev/null +++ b/vendor/topthink/think-captcha/src/CaptchaController.php @@ -0,0 +1,20 @@ + +// +---------------------------------------------------------------------- + +namespace think\captcha; + +class CaptchaController +{ + public function index(Captcha $captcha, $config = null) + { + return $captcha->create($config); + } +} diff --git a/vendor/topthink/think-captcha/src/CaptchaService.php b/vendor/topthink/think-captcha/src/CaptchaService.php new file mode 100644 index 0000000..1848858 --- /dev/null +++ b/vendor/topthink/think-captcha/src/CaptchaService.php @@ -0,0 +1,23 @@ +extend('captcha', function ($value) { + return captcha_check($value); + }, ':attribute错误!'); + }); + + $this->registerRoutes(function (Route $route) { + $route->get('captcha/[:config]', "\\think\\captcha\\CaptchaController@index"); + }); + } +} diff --git a/vendor/topthink/think-captcha/src/config.php b/vendor/topthink/think-captcha/src/config.php new file mode 100644 index 0000000..9bbf529 --- /dev/null +++ b/vendor/topthink/think-captcha/src/config.php @@ -0,0 +1,39 @@ + 5, + // 验证码字符集合 + 'codeSet' => '2345678abcdefhijkmnpqrstuvwxyzABCDEFGHJKLMNPQRTUVWXY', + // 验证码过期时间 + 'expire' => 1800, + // 是否使用中文验证码 + 'useZh' => false, + // 是否使用算术验证码 + 'math' => false, + // 是否使用背景图 + 'useImgBg' => false, + //验证码字符大小 + 'fontSize' => 25, + // 是否使用混淆曲线 + 'useCurve' => true, + //是否添加杂点 + 'useNoise' => true, + // 验证码字体 不设置则随机 + 'fontttf' => '', + //背景颜色 + 'bg' => [243, 251, 254], + // 验证码图片高度 + 'imageH' => 0, + // 验证码图片宽度 + 'imageW' => 0, + + // 添加额外的验证码设置 + // verify => [ + // 'length'=>4, + // ... + //], +]; diff --git a/vendor/topthink/think-captcha/src/facade/Captcha.php b/vendor/topthink/think-captcha/src/facade/Captcha.php new file mode 100644 index 0000000..cd9f793 --- /dev/null +++ b/vendor/topthink/think-captcha/src/facade/Captcha.php @@ -0,0 +1,18 @@ + +// +---------------------------------------------------------------------- + +use think\captcha\facade\Captcha; +use think\facade\Route; +use think\Response; + +/** + * @param string $config + * @return \think\Response + */ +function captcha($config = null): Response +{ + return Captcha::create($config); +} + +/** + * @param $config + * @return string + */ +function captcha_src($config = null): string +{ + return Route::buildUrl('/captcha' . ($config ? "/{$config}" : '')); +} + +/** + * @param $id + * @return string + */ +function captcha_img($id = '', $domid = ''): string +{ + $src = captcha_src($id); + + $domid = empty($domid) ? $domid : "id='" . $domid . "'"; + + return "captcha"; +} + +/** + * @param string $value + * @return bool + */ +function captcha_check($value) +{ + return Captcha::check($value); +} diff --git a/vendor/topthink/think-helper/.gitignore b/vendor/topthink/think-helper/.gitignore new file mode 100644 index 0000000..d851bdb --- /dev/null +++ b/vendor/topthink/think-helper/.gitignore @@ -0,0 +1,3 @@ +/vendor/ +/.idea/ +composer.lock \ No newline at end of file diff --git a/vendor/topthink/think-helper/LICENSE b/vendor/topthink/think-helper/LICENSE new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/vendor/topthink/think-helper/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/topthink/think-helper/README.md b/vendor/topthink/think-helper/README.md new file mode 100644 index 0000000..7baf8f7 --- /dev/null +++ b/vendor/topthink/think-helper/README.md @@ -0,0 +1,33 @@ +# thinkphp6 常用的一些扩展类库 + +基于PHP7.1+ + +> 以下类库都在`\\think\\helper`命名空间下 + +## Str + +> 字符串操作 + +``` +// 检查字符串中是否包含某些字符串 +Str::contains($haystack, $needles) + +// 检查字符串是否以某些字符串结尾 +Str::endsWith($haystack, $needles) + +// 获取指定长度的随机字母数字组合的字符串 +Str::random($length = 16) + +// 字符串转小写 +Str::lower($value) + +// 字符串转大写 +Str::upper($value) + +// 获取字符串的长度 +Str::length($value) + +// 截取字符串 +Str::substr($string, $start, $length = null) + +``` \ No newline at end of file diff --git a/vendor/topthink/think-helper/composer.json b/vendor/topthink/think-helper/composer.json new file mode 100644 index 0000000..b68c43b --- /dev/null +++ b/vendor/topthink/think-helper/composer.json @@ -0,0 +1,22 @@ +{ + "name": "topthink/think-helper", + "description": "The ThinkPHP6 Helper Package", + "license": "Apache-2.0", + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "require": { + "php": ">=7.1.0" + }, + "autoload": { + "psr-4": { + "think\\": "src" + }, + "files": [ + "src/helper.php" + ] + } +} diff --git a/vendor/topthink/think-helper/src/Collection.php b/vendor/topthink/think-helper/src/Collection.php new file mode 100644 index 0000000..fa408c2 --- /dev/null +++ b/vendor/topthink/think-helper/src/Collection.php @@ -0,0 +1,655 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use ArrayAccess; +use ArrayIterator; +use Countable; +use IteratorAggregate; +use JsonSerializable; +use think\contract\Arrayable; +use think\contract\Jsonable; +use think\helper\Arr; + +/** + * 数据集管理类 + */ +class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable, Arrayable, Jsonable +{ + /** + * 数据集数据 + * @var array + */ + protected $items = []; + + public function __construct($items = []) + { + $this->items = $this->convertToArray($items); + } + + public static function make($items = []) + { + return new static($items); + } + + /** + * 是否为空 + * @access public + * @return bool + */ + public function isEmpty(): bool + { + return empty($this->items); + } + + public function toArray(): array + { + return array_map(function ($value) { + return $value instanceof Arrayable ? $value->toArray() : $value; + }, $this->items); + } + + public function all(): array + { + return $this->items; + } + + /** + * 合并数组 + * + * @access public + * @param mixed $items 数据 + * @return static + */ + public function merge($items) + { + return new static(array_merge($this->items, $this->convertToArray($items))); + } + + /** + * 按指定键整理数据 + * + * @access public + * @param mixed $items 数据 + * @param string $indexKey 键名 + * @return array + */ + public function dictionary($items = null, string &$indexKey = null) + { + if ($items instanceof self) { + $items = $items->all(); + } + + $items = is_null($items) ? $this->items : $items; + + if ($items && empty($indexKey)) { + $indexKey = is_array($items[0]) ? 'id' : $items[0]->getPk(); + } + + if (isset($indexKey) && is_string($indexKey)) { + return array_column($items, null, $indexKey); + } + + return $items; + } + + /** + * 比较数组,返回差集 + * + * @access public + * @param mixed $items 数据 + * @param string $indexKey 指定比较的键名 + * @return static + */ + public function diff($items, string $indexKey = null) + { + if ($this->isEmpty() || is_scalar($this->items[0])) { + return new static(array_diff($this->items, $this->convertToArray($items))); + } + + $diff = []; + $dictionary = $this->dictionary($items, $indexKey); + + if (is_string($indexKey)) { + foreach ($this->items as $item) { + if (!isset($dictionary[$item[$indexKey]])) { + $diff[] = $item; + } + } + } + + return new static($diff); + } + + /** + * 比较数组,返回交集 + * + * @access public + * @param mixed $items 数据 + * @param string $indexKey 指定比较的键名 + * @return static + */ + public function intersect($items, string $indexKey = null) + { + if ($this->isEmpty() || is_scalar($this->items[0])) { + return new static(array_diff($this->items, $this->convertToArray($items))); + } + + $intersect = []; + $dictionary = $this->dictionary($items, $indexKey); + + if (is_string($indexKey)) { + foreach ($this->items as $item) { + if (isset($dictionary[$item[$indexKey]])) { + $intersect[] = $item; + } + } + } + + return new static($intersect); + } + + /** + * 交换数组中的键和值 + * + * @access public + * @return static + */ + public function flip() + { + return new static(array_flip($this->items)); + } + + /** + * 返回数组中所有的键名 + * + * @access public + * @return static + */ + public function keys() + { + return new static(array_keys($this->items)); + } + + /** + * 返回数组中所有的值组成的新 Collection 实例 + * @access public + * @return static + */ + public function values() + { + return new static(array_values($this->items)); + } + + /** + * 删除数组的最后一个元素(出栈) + * + * @access public + * @return mixed + */ + public function pop() + { + return array_pop($this->items); + } + + /** + * 通过使用用户自定义函数,以字符串返回数组 + * + * @access public + * @param callable $callback 调用方法 + * @param mixed $initial + * @return mixed + */ + public function reduce(callable $callback, $initial = null) + { + return array_reduce($this->items, $callback, $initial); + } + + /** + * 以相反的顺序返回数组。 + * + * @access public + * @return static + */ + public function reverse() + { + return new static(array_reverse($this->items)); + } + + /** + * 删除数组中首个元素,并返回被删除元素的值 + * + * @access public + * @return mixed + */ + public function shift() + { + return array_shift($this->items); + } + + /** + * 在数组结尾插入一个元素 + * @access public + * @param mixed $value 元素 + * @param string $key KEY + * @return $this + */ + public function push($value, string $key = null) + { + if (is_null($key)) { + $this->items[] = $value; + } else { + $this->items[$key] = $value; + } + + return $this; + } + + /** + * 把一个数组分割为新的数组块. + * + * @access public + * @param int $size 块大小 + * @param bool $preserveKeys + * @return static + */ + public function chunk(int $size, bool $preserveKeys = false) + { + $chunks = []; + + foreach (array_chunk($this->items, $size, $preserveKeys) as $chunk) { + $chunks[] = new static($chunk); + } + + return new static($chunks); + } + + /** + * 在数组开头插入一个元素 + * @access public + * @param mixed $value 元素 + * @param string $key KEY + * @return $this + */ + public function unshift($value, string $key = null) + { + if (is_null($key)) { + array_unshift($this->items, $value); + } else { + $this->items = [$key => $value] + $this->items; + } + + return $this; + } + + /** + * 给每个元素执行个回调 + * + * @access public + * @param callable $callback 回调 + * @return $this + */ + public function each(callable $callback) + { + foreach ($this->items as $key => $item) { + $result = $callback($item, $key); + + if (false === $result) { + break; + } elseif (!is_object($item)) { + $this->items[$key] = $result; + } + } + + return $this; + } + + /** + * 用回调函数处理数组中的元素 + * @access public + * @param callable|null $callback 回调 + * @return static + */ + public function map(callable $callback) + { + return new static(array_map($callback, $this->items)); + } + + /** + * 用回调函数过滤数组中的元素 + * @access public + * @param callable|null $callback 回调 + * @return static + */ + public function filter(callable $callback = null) + { + if ($callback) { + return new static(array_filter($this->items, $callback)); + } + + return new static(array_filter($this->items)); + } + + /** + * 根据字段条件过滤数组中的元素 + * @access public + * @param string $field 字段名 + * @param mixed $operator 操作符 + * @param mixed $value 数据 + * @return static + */ + public function where(string $field, $operator, $value = null) + { + if (is_null($value)) { + $value = $operator; + $operator = '='; + } + + return $this->filter(function ($data) use ($field, $operator, $value) { + if (strpos($field, '.')) { + [$field, $relation] = explode('.', $field); + + $result = $data[$field][$relation] ?? null; + } else { + $result = $data[$field] ?? null; + } + + switch (strtolower($operator)) { + case '===': + return $result === $value; + case '!==': + return $result !== $value; + case '!=': + case '<>': + return $result != $value; + case '>': + return $result > $value; + case '>=': + return $result >= $value; + case '<': + return $result < $value; + case '<=': + return $result <= $value; + case 'like': + return is_string($result) && false !== strpos($result, $value); + case 'not like': + return is_string($result) && false === strpos($result, $value); + case 'in': + return is_scalar($result) && in_array($result, $value, true); + case 'not in': + return is_scalar($result) && !in_array($result, $value, true); + case 'between': + [$min, $max] = is_string($value) ? explode(',', $value) : $value; + return is_scalar($result) && $result >= $min && $result <= $max; + case 'not between': + [$min, $max] = is_string($value) ? explode(',', $value) : $value; + return is_scalar($result) && $result > $max || $result < $min; + case '==': + case '=': + default: + return $result == $value; + } + }); + } + + /** + * LIKE过滤 + * @access public + * @param string $field 字段名 + * @param string $value 数据 + * @return static + */ + public function whereLike(string $field, string $value) + { + return $this->where($field, 'like', $value); + } + + /** + * NOT LIKE过滤 + * @access public + * @param string $field 字段名 + * @param string $value 数据 + * @return static + */ + public function whereNotLike(string $field, string $value) + { + return $this->where($field, 'not like', $value); + } + + /** + * IN过滤 + * @access public + * @param string $field 字段名 + * @param array $value 数据 + * @return static + */ + public function whereIn(string $field, array $value) + { + return $this->where($field, 'in', $value); + } + + /** + * NOT IN过滤 + * @access public + * @param string $field 字段名 + * @param array $value 数据 + * @return static + */ + public function whereNotIn(string $field, array $value) + { + return $this->where($field, 'not in', $value); + } + + /** + * BETWEEN 过滤 + * @access public + * @param string $field 字段名 + * @param mixed $value 数据 + * @return static + */ + public function whereBetween(string $field, $value) + { + return $this->where($field, 'between', $value); + } + + /** + * NOT BETWEEN 过滤 + * @access public + * @param string $field 字段名 + * @param mixed $value 数据 + * @return static + */ + public function whereNotBetween(string $field, $value) + { + return $this->where($field, 'not between', $value); + } + + /** + * 返回数据中指定的一列 + * @access public + * @param string|null $columnKey 键名 + * @param string|null $indexKey 作为索引值的列 + * @return array + */ + public function column( ? string $columnKey, string $indexKey = null) + { + return array_column($this->items, $columnKey, $indexKey); + } + + /** + * 对数组排序 + * + * @access public + * @param callable|null $callback 回调 + * @return static + */ + public function sort(callable $callback = null) + { + $items = $this->items; + + $callback = $callback ?: function ($a, $b) { + return $a == $b ? 0 : (($a < $b) ? -1 : 1); + }; + + uasort($items, $callback); + + return new static($items); + } + + /** + * 指定字段排序 + * @access public + * @param string $field 排序字段 + * @param string $order 排序 + * @return $this + */ + public function order(string $field, string $order = 'asc') + { + return $this->sort(function ($a, $b) use ($field, $order) { + $fieldA = $a[$field] ?? null; + $fieldB = $b[$field] ?? null; + + return 'desc' == strtolower($order) ? intval($fieldB > $fieldA) : intval($fieldA > $fieldB); + }); + } + + /** + * 将数组打乱 + * + * @access public + * @return static + */ + public function shuffle() + { + $items = $this->items; + + shuffle($items); + + return new static($items); + } + + /** + * 获取第一个单元数据 + * + * @access public + * @param callable|null $callback + * @param null $default + * @return mixed + */ + public function first(callable $callback = null, $default = null) + { + return Arr::first($this->items, $callback, $default); + } + + /** + * 获取最后一个单元数据 + * + * @access public + * @param callable|null $callback + * @param null $default + * @return mixed + */ + public function last(callable $callback = null, $default = null) + { + return Arr::last($this->items, $callback, $default); + } + + /** + * 截取数组 + * + * @access public + * @param int $offset 起始位置 + * @param int $length 截取长度 + * @param bool $preserveKeys preserveKeys + * @return static + */ + public function slice(int $offset, int $length = null, bool $preserveKeys = false) + { + return new static(array_slice($this->items, $offset, $length, $preserveKeys)); + } + + // ArrayAccess + public function offsetExists($offset) + { + return array_key_exists($offset, $this->items); + } + + public function offsetGet($offset) + { + return $this->items[$offset]; + } + + public function offsetSet($offset, $value) + { + if (is_null($offset)) { + $this->items[] = $value; + } else { + $this->items[$offset] = $value; + } + } + + public function offsetUnset($offset) + { + unset($this->items[$offset]); + } + + //Countable + public function count() + { + return count($this->items); + } + + //IteratorAggregate + public function getIterator() + { + return new ArrayIterator($this->items); + } + + //JsonSerializable + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * 转换当前数据集为JSON字符串 + * @access public + * @param integer $options json参数 + * @return string + */ + public function toJson(int $options = JSON_UNESCAPED_UNICODE) : string + { + return json_encode($this->toArray(), $options); + } + + public function __toString() + { + return $this->toJson(); + } + + /** + * 转换成数组 + * + * @access public + * @param mixed $items 数据 + * @return array + */ + protected function convertToArray($items): array + { + if ($items instanceof self) { + return $items->all(); + } + + return (array) $items; + } +} diff --git a/vendor/topthink/think-helper/src/contract/Arrayable.php b/vendor/topthink/think-helper/src/contract/Arrayable.php new file mode 100644 index 0000000..7c6b992 --- /dev/null +++ b/vendor/topthink/think-helper/src/contract/Arrayable.php @@ -0,0 +1,8 @@ + +// +---------------------------------------------------------------------- + +use think\Collection; +use think\helper\Arr; + +if (!function_exists('throw_if')) { + /** + * 按条件抛异常 + * + * @param mixed $condition + * @param Throwable|string $exception + * @param array ...$parameters + * @return mixed + * + * @throws Throwable + */ + function throw_if($condition, $exception, ...$parameters) + { + if ($condition) { + throw (is_string($exception) ? new $exception(...$parameters) : $exception); + } + + return $condition; + } +} + +if (!function_exists('throw_unless')) { + /** + * 按条件抛异常 + * + * @param mixed $condition + * @param Throwable|string $exception + * @param array ...$parameters + * @return mixed + * @throws Throwable + */ + function throw_unless($condition, $exception, ...$parameters) + { + if (!$condition) { + throw (is_string($exception) ? new $exception(...$parameters) : $exception); + } + + return $condition; + } +} + +if (!function_exists('tap')) { + /** + * 对一个值调用给定的闭包,然后返回该值 + * + * @param mixed $value + * @param callable|null $callback + * @return mixed + */ + function tap($value, $callback = null) + { + if (is_null($callback)) { + return $value; + } + + $callback($value); + + return $value; + } +} + +if (!function_exists('value')) { + /** + * Return the default value of the given value. + * + * @param mixed $value + * @return mixed + */ + function value($value) + { + return $value instanceof Closure ? $value() : $value; + } +} + +if (!function_exists('collect')) { + /** + * Create a collection from the given value. + * + * @param mixed $value + * @return Collection + */ + function collect($value = null) + { + return new Collection($value); + } +} + +if (!function_exists('data_fill')) { + /** + * Fill in data where it's missing. + * + * @param mixed $target + * @param string|array $key + * @param mixed $value + * @return mixed + */ + function data_fill(&$target, $key, $value) + { + return data_set($target, $key, $value, false); + } +} + +if (!function_exists('data_get')) { + /** + * Get an item from an array or object using "dot" notation. + * + * @param mixed $target + * @param string|array|int $key + * @param mixed $default + * @return mixed + */ + function data_get($target, $key, $default = null) + { + if (is_null($key)) { + return $target; + } + + $key = is_array($key) ? $key : explode('.', $key); + + while (!is_null($segment = array_shift($key))) { + if ('*' === $segment) { + if ($target instanceof Collection) { + $target = $target->all(); + } elseif (!is_array($target)) { + return value($default); + } + + $result = []; + + foreach ($target as $item) { + $result[] = data_get($item, $key); + } + + return in_array('*', $key) ? Arr::collapse($result) : $result; + } + + if (Arr::accessible($target) && Arr::exists($target, $segment)) { + $target = $target[$segment]; + } elseif (is_object($target) && isset($target->{$segment})) { + $target = $target->{$segment}; + } else { + return value($default); + } + } + + return $target; + } +} + +if (!function_exists('data_set')) { + /** + * Set an item on an array or object using dot notation. + * + * @param mixed $target + * @param string|array $key + * @param mixed $value + * @param bool $overwrite + * @return mixed + */ + function data_set(&$target, $key, $value, $overwrite = true) + { + $segments = is_array($key) ? $key : explode('.', $key); + + if (($segment = array_shift($segments)) === '*') { + if (!Arr::accessible($target)) { + $target = []; + } + + if ($segments) { + foreach ($target as &$inner) { + data_set($inner, $segments, $value, $overwrite); + } + } elseif ($overwrite) { + foreach ($target as &$inner) { + $inner = $value; + } + } + } elseif (Arr::accessible($target)) { + if ($segments) { + if (!Arr::exists($target, $segment)) { + $target[$segment] = []; + } + + data_set($target[$segment], $segments, $value, $overwrite); + } elseif ($overwrite || !Arr::exists($target, $segment)) { + $target[$segment] = $value; + } + } elseif (is_object($target)) { + if ($segments) { + if (!isset($target->{$segment})) { + $target->{$segment} = []; + } + + data_set($target->{$segment}, $segments, $value, $overwrite); + } elseif ($overwrite || !isset($target->{$segment})) { + $target->{$segment} = $value; + } + } else { + $target = []; + + if ($segments) { + data_set($target[$segment], $segments, $value, $overwrite); + } elseif ($overwrite) { + $target[$segment] = $value; + } + } + + return $target; + } +} + +if (!function_exists('trait_uses_recursive')) { + /** + * 获取一个trait里所有引用到的trait + * + * @param string $trait Trait + * @return array + */ + function trait_uses_recursive(string $trait): array + { + $traits = class_uses($trait); + foreach ($traits as $trait) { + $traits += trait_uses_recursive($trait); + } + + return $traits; + } +} + +if (!function_exists('class_basename')) { + /** + * 获取类名(不包含命名空间) + * + * @param mixed $class 类名 + * @return string + */ + function class_basename($class): string + { + $class = is_object($class) ? get_class($class) : $class; + return basename(str_replace('\\', '/', $class)); + } +} + +if (!function_exists('class_uses_recursive')) { + /** + *获取一个类里所有用到的trait,包括父类的 + * + * @param mixed $class 类名 + * @return array + */ + function class_uses_recursive($class): array + { + if (is_object($class)) { + $class = get_class($class); + } + + $results = []; + $classes = array_merge([$class => $class], class_parents($class)); + foreach ($classes as $class) { + $results += trait_uses_recursive($class); + } + + return array_unique($results); + } +} diff --git a/vendor/topthink/think-helper/src/helper/Arr.php b/vendor/topthink/think-helper/src/helper/Arr.php new file mode 100644 index 0000000..ed4d6a9 --- /dev/null +++ b/vendor/topthink/think-helper/src/helper/Arr.php @@ -0,0 +1,634 @@ + +// +---------------------------------------------------------------------- + +namespace think\helper; + +use ArrayAccess; +use InvalidArgumentException; +use think\Collection; + +class Arr +{ + + /** + * Determine whether the given value is array accessible. + * + * @param mixed $value + * @return bool + */ + public static function accessible($value) + { + return is_array($value) || $value instanceof ArrayAccess; + } + + /** + * Add an element to an array using "dot" notation if it doesn't exist. + * + * @param array $array + * @param string $key + * @param mixed $value + * @return array + */ + public static function add($array, $key, $value) + { + if (is_null(static::get($array, $key))) { + static::set($array, $key, $value); + } + + return $array; + } + + /** + * Collapse an array of arrays into a single array. + * + * @param array $array + * @return array + */ + public static function collapse($array) + { + $results = []; + + foreach ($array as $values) { + if ($values instanceof Collection) { + $values = $values->all(); + } elseif (!is_array($values)) { + continue; + } + + $results = array_merge($results, $values); + } + + return $results; + } + + /** + * Cross join the given arrays, returning all possible permutations. + * + * @param array ...$arrays + * @return array + */ + public static function crossJoin(...$arrays) + { + $results = [[]]; + + foreach ($arrays as $index => $array) { + $append = []; + + foreach ($results as $product) { + foreach ($array as $item) { + $product[$index] = $item; + + $append[] = $product; + } + } + + $results = $append; + } + + return $results; + } + + /** + * Divide an array into two arrays. One with keys and the other with values. + * + * @param array $array + * @return array + */ + public static function divide($array) + { + return [array_keys($array), array_values($array)]; + } + + /** + * Flatten a multi-dimensional associative array with dots. + * + * @param array $array + * @param string $prepend + * @return array + */ + public static function dot($array, $prepend = '') + { + $results = []; + + foreach ($array as $key => $value) { + if (is_array($value) && !empty($value)) { + $results = array_merge($results, static::dot($value, $prepend . $key . '.')); + } else { + $results[$prepend . $key] = $value; + } + } + + return $results; + } + + /** + * Get all of the given array except for a specified array of keys. + * + * @param array $array + * @param array|string $keys + * @return array + */ + public static function except($array, $keys) + { + static::forget($array, $keys); + + return $array; + } + + /** + * Determine if the given key exists in the provided array. + * + * @param \ArrayAccess|array $array + * @param string|int $key + * @return bool + */ + public static function exists($array, $key) + { + if ($array instanceof ArrayAccess) { + return $array->offsetExists($key); + } + + return array_key_exists($key, $array); + } + + /** + * Return the first element in an array passing a given truth test. + * + * @param array $array + * @param callable|null $callback + * @param mixed $default + * @return mixed + */ + public static function first($array, callable $callback = null, $default = null) + { + if (is_null($callback)) { + if (empty($array)) { + return value($default); + } + + foreach ($array as $item) { + return $item; + } + } + + foreach ($array as $key => $value) { + if (call_user_func($callback, $value, $key)) { + return $value; + } + } + + return value($default); + } + + /** + * Return the last element in an array passing a given truth test. + * + * @param array $array + * @param callable|null $callback + * @param mixed $default + * @return mixed + */ + public static function last($array, callable $callback = null, $default = null) + { + if (is_null($callback)) { + return empty($array) ? value($default) : end($array); + } + + return static::first(array_reverse($array, true), $callback, $default); + } + + /** + * Flatten a multi-dimensional array into a single level. + * + * @param array $array + * @param int $depth + * @return array + */ + public static function flatten($array, $depth = INF) + { + $result = []; + + foreach ($array as $item) { + $item = $item instanceof Collection ? $item->all() : $item; + + if (!is_array($item)) { + $result[] = $item; + } elseif ($depth === 1) { + $result = array_merge($result, array_values($item)); + } else { + $result = array_merge($result, static::flatten($item, $depth - 1)); + } + } + + return $result; + } + + /** + * Remove one or many array items from a given array using "dot" notation. + * + * @param array $array + * @param array|string $keys + * @return void + */ + public static function forget(&$array, $keys) + { + $original = &$array; + + $keys = (array) $keys; + + if (count($keys) === 0) { + return; + } + + foreach ($keys as $key) { + // if the exact key exists in the top-level, remove it + if (static::exists($array, $key)) { + unset($array[$key]); + + continue; + } + + $parts = explode('.', $key); + + // clean up before each pass + $array = &$original; + + while (count($parts) > 1) { + $part = array_shift($parts); + + if (isset($array[$part]) && is_array($array[$part])) { + $array = &$array[$part]; + } else { + continue 2; + } + } + + unset($array[array_shift($parts)]); + } + } + + /** + * Get an item from an array using "dot" notation. + * + * @param \ArrayAccess|array $array + * @param string $key + * @param mixed $default + * @return mixed + */ + public static function get($array, $key, $default = null) + { + if (!static::accessible($array)) { + return value($default); + } + + if (is_null($key)) { + return $array; + } + + if (static::exists($array, $key)) { + return $array[$key]; + } + + if (strpos($key, '.') === false) { + return $array[$key] ?? value($default); + } + + foreach (explode('.', $key) as $segment) { + if (static::accessible($array) && static::exists($array, $segment)) { + $array = $array[$segment]; + } else { + return value($default); + } + } + + return $array; + } + + /** + * Check if an item or items exist in an array using "dot" notation. + * + * @param \ArrayAccess|array $array + * @param string|array $keys + * @return bool + */ + public static function has($array, $keys) + { + $keys = (array) $keys; + + if (!$array || $keys === []) { + return false; + } + + foreach ($keys as $key) { + $subKeyArray = $array; + + if (static::exists($array, $key)) { + continue; + } + + foreach (explode('.', $key) as $segment) { + if (static::accessible($subKeyArray) && static::exists($subKeyArray, $segment)) { + $subKeyArray = $subKeyArray[$segment]; + } else { + return false; + } + } + } + + return true; + } + + /** + * Determines if an array is associative. + * + * An array is "associative" if it doesn't have sequential numerical keys beginning with zero. + * + * @param array $array + * @return bool + */ + public static function isAssoc(array $array) + { + $keys = array_keys($array); + + return array_keys($keys) !== $keys; + } + + /** + * Get a subset of the items from the given array. + * + * @param array $array + * @param array|string $keys + * @return array + */ + public static function only($array, $keys) + { + return array_intersect_key($array, array_flip((array) $keys)); + } + + /** + * Pluck an array of values from an array. + * + * @param array $array + * @param string|array $value + * @param string|array|null $key + * @return array + */ + public static function pluck($array, $value, $key = null) + { + $results = []; + + [$value, $key] = static::explodePluckParameters($value, $key); + + foreach ($array as $item) { + $itemValue = data_get($item, $value); + + // If the key is "null", we will just append the value to the array and keep + // looping. Otherwise we will key the array using the value of the key we + // received from the developer. Then we'll return the final array form. + if (is_null($key)) { + $results[] = $itemValue; + } else { + $itemKey = data_get($item, $key); + + if (is_object($itemKey) && method_exists($itemKey, '__toString')) { + $itemKey = (string) $itemKey; + } + + $results[$itemKey] = $itemValue; + } + } + + return $results; + } + + /** + * Explode the "value" and "key" arguments passed to "pluck". + * + * @param string|array $value + * @param string|array|null $key + * @return array + */ + protected static function explodePluckParameters($value, $key) + { + $value = is_string($value) ? explode('.', $value) : $value; + + $key = is_null($key) || is_array($key) ? $key : explode('.', $key); + + return [$value, $key]; + } + + /** + * Push an item onto the beginning of an array. + * + * @param array $array + * @param mixed $value + * @param mixed $key + * @return array + */ + public static function prepend($array, $value, $key = null) + { + if (is_null($key)) { + array_unshift($array, $value); + } else { + $array = [$key => $value] + $array; + } + + return $array; + } + + /** + * Get a value from the array, and remove it. + * + * @param array $array + * @param string $key + * @param mixed $default + * @return mixed + */ + public static function pull(&$array, $key, $default = null) + { + $value = static::get($array, $key, $default); + + static::forget($array, $key); + + return $value; + } + + /** + * Get one or a specified number of random values from an array. + * + * @param array $array + * @param int|null $number + * @return mixed + * + * @throws \InvalidArgumentException + */ + public static function random($array, $number = null) + { + $requested = is_null($number) ? 1 : $number; + + $count = count($array); + + if ($requested > $count) { + throw new InvalidArgumentException( + "You requested {$requested} items, but there are only {$count} items available." + ); + } + + if (is_null($number)) { + return $array[array_rand($array)]; + } + + if ((int) $number === 0) { + return []; + } + + $keys = array_rand($array, $number); + + $results = []; + + foreach ((array) $keys as $key) { + $results[] = $array[$key]; + } + + return $results; + } + + /** + * Set an array item to a given value using "dot" notation. + * + * If no key is given to the method, the entire array will be replaced. + * + * @param array $array + * @param string $key + * @param mixed $value + * @return array + */ + public static function set(&$array, $key, $value) + { + if (is_null($key)) { + return $array = $value; + } + + $keys = explode('.', $key); + + while (count($keys) > 1) { + $key = array_shift($keys); + + // If the key doesn't exist at this depth, we will just create an empty array + // to hold the next value, allowing us to create the arrays to hold final + // values at the correct depth. Then we'll keep digging into the array. + if (!isset($array[$key]) || !is_array($array[$key])) { + $array[$key] = []; + } + + $array = &$array[$key]; + } + + $array[array_shift($keys)] = $value; + + return $array; + } + + /** + * Shuffle the given array and return the result. + * + * @param array $array + * @param int|null $seed + * @return array + */ + public static function shuffle($array, $seed = null) + { + if (is_null($seed)) { + shuffle($array); + } else { + srand($seed); + + usort($array, function () { + return rand(-1, 1); + }); + } + + return $array; + } + + /** + * Sort the array using the given callback or "dot" notation. + * + * @param array $array + * @param callable|string|null $callback + * @return array + */ + public static function sort($array, $callback = null) + { + return Collection::make($array)->sort($callback)->all(); + } + + /** + * Recursively sort an array by keys and values. + * + * @param array $array + * @return array + */ + public static function sortRecursive($array) + { + foreach ($array as &$value) { + if (is_array($value)) { + $value = static::sortRecursive($value); + } + } + + if (static::isAssoc($array)) { + ksort($array); + } else { + sort($array); + } + + return $array; + } + + /** + * Convert the array into a query string. + * + * @param array $array + * @return string + */ + public static function query($array) + { + return http_build_query($array, null, '&', PHP_QUERY_RFC3986); + } + + /** + * Filter the array using the given callback. + * + * @param array $array + * @param callable $callback + * @return array + */ + public static function where($array, callable $callback) + { + return array_filter($array, $callback, ARRAY_FILTER_USE_BOTH); + } + + /** + * If the given value is not an array and not null, wrap it in one. + * + * @param mixed $value + * @return array + */ + public static function wrap($value) + { + if (is_null($value)) { + return []; + } + + return is_array($value) ? $value : [$value]; + } +} \ No newline at end of file diff --git a/vendor/topthink/think-helper/src/helper/Str.php b/vendor/topthink/think-helper/src/helper/Str.php new file mode 100644 index 0000000..7391fbd --- /dev/null +++ b/vendor/topthink/think-helper/src/helper/Str.php @@ -0,0 +1,234 @@ + +// +---------------------------------------------------------------------- +namespace think\helper; + +class Str +{ + + protected static $snakeCache = []; + + protected static $camelCache = []; + + protected static $studlyCache = []; + + /** + * 检查字符串中是否包含某些字符串 + * @param string $haystack + * @param string|array $needles + * @return bool + */ + public static function contains(string $haystack, $needles): bool + { + foreach ((array) $needles as $needle) { + if ('' != $needle && mb_strpos($haystack, $needle) !== false) { + return true; + } + } + + return false; + } + + /** + * 检查字符串是否以某些字符串结尾 + * + * @param string $haystack + * @param string|array $needles + * @return bool + */ + public static function endsWith(string $haystack, $needles): bool + { + foreach ((array) $needles as $needle) { + if ((string) $needle === static::substr($haystack, -static::length($needle))) { + return true; + } + } + + return false; + } + + /** + * 检查字符串是否以某些字符串开头 + * + * @param string $haystack + * @param string|array $needles + * @return bool + */ + public static function startsWith(string $haystack, $needles): bool + { + foreach ((array) $needles as $needle) { + if ('' != $needle && mb_strpos($haystack, $needle) === 0) { + return true; + } + } + + return false; + } + + /** + * 获取指定长度的随机字母数字组合的字符串 + * + * @param int $length + * @param int $type + * @param string $addChars + * @return string + */ + public static function random(int $length = 6, int $type = null, string $addChars = ''): string + { + $str = ''; + switch ($type) { + case 0: + $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' . $addChars; + break; + case 1: + $chars = str_repeat('0123456789', 3); + break; + case 2: + $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' . $addChars; + break; + case 3: + $chars = 'abcdefghijklmnopqrstuvwxyz' . $addChars; + break; + case 4: + $chars = "们以我到他会作时要动国产的一是工就年阶义发成部民可出能方进在了不和有大这主中人上为来分生对于学下级地个用同行面说种过命度革而多子后自社加小机也经力线本电高量长党得实家定深法表着水理化争现所二起政三好十战无农使性前等反体合斗路图把结第里正新开论之物从当两些还天资事队批点育重其思与间内去因件日利相由压员气业代全组数果期导平各基或月毛然如应形想制心样干都向变关问比展那它最及外没看治提五解系林者米群头意只明四道马认次文通但条较克又公孔领军流入接席位情运器并飞原油放立题质指建区验活众很教决特此常石强极土少已根共直团统式转别造切九你取西持总料连任志观调七么山程百报更见必真保热委手改管处己将修支识病象几先老光专什六型具示复安带每东增则完风回南广劳轮科北打积车计给节做务被整联步类集号列温装即毫知轴研单色坚据速防史拉世设达尔场织历花受求传口断况采精金界品判参层止边清至万确究书" . $addChars; + break; + default: + $chars = 'ABCDEFGHIJKMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789' . $addChars; + break; + } + if ($length > 10) { + $chars = $type == 1 ? str_repeat($chars, $length) : str_repeat($chars, 5); + } + if ($type != 4) { + $chars = str_shuffle($chars); + $str = substr($chars, 0, $length); + } else { + for ($i = 0; $i < $length; $i++) { + $str .= mb_substr($chars, floor(mt_rand(0, mb_strlen($chars, 'utf-8') - 1)), 1); + } + } + return $str; + } + + /** + * 字符串转小写 + * + * @param string $value + * @return string + */ + public static function lower(string $value): string + { + return mb_strtolower($value, 'UTF-8'); + } + + /** + * 字符串转大写 + * + * @param string $value + * @return string + */ + public static function upper(string $value): string + { + return mb_strtoupper($value, 'UTF-8'); + } + + /** + * 获取字符串的长度 + * + * @param string $value + * @return int + */ + public static function length(string $value): int + { + return mb_strlen($value); + } + + /** + * 截取字符串 + * + * @param string $string + * @param int $start + * @param int|null $length + * @return string + */ + public static function substr(string $string, int $start, int $length = null): string + { + return mb_substr($string, $start, $length, 'UTF-8'); + } + + /** + * 驼峰转下划线 + * + * @param string $value + * @param string $delimiter + * @return string + */ + public static function snake(string $value, string $delimiter = '_'): string + { + $key = $value; + + if (isset(static::$snakeCache[$key][$delimiter])) { + return static::$snakeCache[$key][$delimiter]; + } + + if (!ctype_lower($value)) { + $value = preg_replace('/\s+/u', '', $value); + + $value = static::lower(preg_replace('/(.)(?=[A-Z])/u', '$1' . $delimiter, $value)); + } + + return static::$snakeCache[$key][$delimiter] = $value; + } + + /** + * 下划线转驼峰(首字母小写) + * + * @param string $value + * @return string + */ + public static function camel(string $value): string + { + if (isset(static::$camelCache[$value])) { + return static::$camelCache[$value]; + } + + return static::$camelCache[$value] = lcfirst(static::studly($value)); + } + + /** + * 下划线转驼峰(首字母大写) + * + * @param string $value + * @return string + */ + public static function studly(string $value): string + { + $key = $value; + + if (isset(static::$studlyCache[$key])) { + return static::$studlyCache[$key]; + } + + $value = ucwords(str_replace(['-', '_'], ' ', $value)); + + return static::$studlyCache[$key] = str_replace(' ', '', $value); + } + + /** + * 转为首字母大写的标题格式 + * + * @param string $value + * @return string + */ + public static function title(string $value): string + { + return mb_convert_case($value, MB_CASE_TITLE, 'UTF-8'); + } +} diff --git a/vendor/topthink/think-image/.gitignore b/vendor/topthink/think-image/.gitignore new file mode 100644 index 0000000..828149d --- /dev/null +++ b/vendor/topthink/think-image/.gitignore @@ -0,0 +1,4 @@ +/vendor/ +/thinkphp/ +/composer.lock +/.idea/ \ No newline at end of file diff --git a/vendor/topthink/think-image/.travis.yml b/vendor/topthink/think-image/.travis.yml new file mode 100644 index 0000000..28ef8fb --- /dev/null +++ b/vendor/topthink/think-image/.travis.yml @@ -0,0 +1,22 @@ +language: php + +php: + - 5.4 + - 5.5 + - 5.6 + - 7.0 + - hhvm + +matrix: + allow_failures: + - php: 7.0 + - php: hhvm + +before_script: + - composer self-update + - composer install --prefer-source --no-interaction --dev + +script: phpunit --coverage-clover=coverage.xml --configuration=phpunit.xml + +after_success: + - bash <(curl -s https://codecov.io/bash) \ No newline at end of file diff --git a/vendor/topthink/think-image/LICENSE b/vendor/topthink/think-image/LICENSE new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/vendor/topthink/think-image/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/topthink/think-image/README.md b/vendor/topthink/think-image/README.md new file mode 100644 index 0000000..866a0b4 --- /dev/null +++ b/vendor/topthink/think-image/README.md @@ -0,0 +1,29 @@ +# The ThinkPHP5 Image Package + +[![Build Status](https://img.shields.io/travis/top-think/think-image.svg)](https://travis-ci.org/top-think/think-image) +[![Coverage Status](https://img.shields.io/codecov/c/github/top-think/think-image.svg)](https://codecov.io/github/top-think/think-image) +[![Downloads](https://img.shields.io/github/downloads/top-think/think-image/total.svg)](https://github.com/top-think/think-image/releases) +[![Releases](https://img.shields.io/github/release/top-think/think-image.svg)](https://github.com/top-think/think-image/releases/latest) +[![Releases Downloads](https://img.shields.io/github/downloads/top-think/think-image/latest/total.svg)](https://github.com/top-think/think-image/releases/latest) +[![Packagist Status](https://img.shields.io/packagist/v/top-think/think-image.svg)](https://packagist.org/packages/topthink/think-image) +[![Packagist Downloads](https://img.shields.io/packagist/dt/top-think/think-image.svg)](https://packagist.org/packages/topthink/think-image) + +## 安装 + +> composer require topthink/think-image + +## 使用 + +~~~ +$image = \think\Image::open('./image.jpg'); +或者 +$image = \think\Image::open(request()->file('image')); + + +$image->crop(...) + ->thumb(...) + ->water(...) + ->text(....) + ->save(..); + +~~~ \ No newline at end of file diff --git a/vendor/topthink/think-image/composer.json b/vendor/topthink/think-image/composer.json new file mode 100644 index 0000000..276d029 --- /dev/null +++ b/vendor/topthink/think-image/composer.json @@ -0,0 +1,26 @@ +{ + "name": "topthink/think-image", + "description": "The ThinkPHP5 Image Package", + "license": "Apache-2.0", + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "require": { + "ext-gd": "*" + }, + "require-dev": { + "topthink/framework": "^5.0", + "phpunit/phpunit": "4.8.*" + }, + "config": { + "preferred-install": "dist" + }, + "autoload": { + "psr-4": { + "think\\": "src" + } + } +} diff --git a/vendor/topthink/think-image/phpunit.xml b/vendor/topthink/think-image/phpunit.xml new file mode 100644 index 0000000..ee93855 --- /dev/null +++ b/vendor/topthink/think-image/phpunit.xml @@ -0,0 +1,20 @@ + + + + + ./tests/ + + + + + + diff --git a/vendor/topthink/think-image/src/Image.php b/vendor/topthink/think-image/src/Image.php new file mode 100644 index 0000000..e28a13d --- /dev/null +++ b/vendor/topthink/think-image/src/Image.php @@ -0,0 +1,610 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\image\Exception as ImageException; +use think\image\gif\Gif; + +class Image +{ + + /* 缩略图相关常量定义 */ + const THUMB_SCALING = 1; //常量,标识缩略图等比例缩放类型 + const THUMB_FILLED = 2; //常量,标识缩略图缩放后填充类型 + const THUMB_CENTER = 3; //常量,标识缩略图居中裁剪类型 + const THUMB_NORTHWEST = 4; //常量,标识缩略图左上角裁剪类型 + const THUMB_SOUTHEAST = 5; //常量,标识缩略图右下角裁剪类型 + const THUMB_FIXED = 6; //常量,标识缩略图固定尺寸缩放类型 + /* 水印相关常量定义 */ + const WATER_NORTHWEST = 1; //常量,标识左上角水印 + const WATER_NORTH = 2; //常量,标识上居中水印 + const WATER_NORTHEAST = 3; //常量,标识右上角水印 + const WATER_WEST = 4; //常量,标识左居中水印 + const WATER_CENTER = 5; //常量,标识居中水印 + const WATER_EAST = 6; //常量,标识右居中水印 + const WATER_SOUTHWEST = 7; //常量,标识左下角水印 + const WATER_SOUTH = 8; //常量,标识下居中水印 + const WATER_SOUTHEAST = 9; //常量,标识右下角水印 + /* 翻转相关常量定义 */ + const FLIP_X = 1; //X轴翻转 + const FLIP_Y = 2; //Y轴翻转 + + /** + * 图像资源对象 + * + * @var resource + */ + protected $im; + + /** @var Gif */ + protected $gif; + + /** + * 图像信息,包括 width, height, type, mime, size + * + * @var array + */ + protected $info; + + protected function __construct(\SplFileInfo $file) + { + //获取图像信息 + $info = @getimagesize($file->getPathname()); + + //检测图像合法性 + if (false === $info || (IMAGETYPE_GIF === $info[2] && empty($info['bits']))) { + throw new ImageException('Illegal image file'); + } + + //设置图像信息 + $this->info = [ + 'width' => $info[0], + 'height' => $info[1], + 'type' => image_type_to_extension($info[2], false), + 'mime' => $info['mime'], + ]; + + //打开图像 + if ('gif' == $this->info['type']) { + $this->gif = new Gif($file->getPathname()); + $this->im = @imagecreatefromstring($this->gif->image()); + } else { + $fun = "imagecreatefrom{$this->info['type']}"; + $this->im = @$fun($file->getPathname()); + } + + if (empty($this->im)) { + throw new ImageException('Failed to create image resources!'); + } + + } + + /** + * 打开一个图片文件 + * @param \SplFileInfo|string $file + * @return Image + */ + public static function open($file) + { + if (is_string($file)) { + $file = new \SplFileInfo($file); + } + if (!$file->isFile()) { + throw new ImageException('image file not exist'); + } + return new self($file); + } + + /** + * 保存图像 + * @param string $pathname 图像保存路径名称 + * @param null|string $type 图像类型 + * @param int $quality 图像质量 + * @param bool $interlace 是否对JPEG类型图像设置隔行扫描 + * @return $this + */ + public function save($pathname, $type = null, $quality = 80, $interlace = true) + { + //自动获取图像类型 + if (is_null($type)) { + $type = $this->info['type']; + } else { + $type = strtolower($type); + } + //保存图像 + if ('jpeg' == $type || 'jpg' == $type) { + //JPEG图像设置隔行扫描 + imageinterlace($this->im, $interlace); + imagejpeg($this->im, $pathname, $quality); + } elseif ('gif' == $type && !empty($this->gif)) { + $this->gif->save($pathname); + } elseif ('png' == $type) { + //设定保存完整的 alpha 通道信息 + imagesavealpha($this->im, true); + //ImagePNG生成图像的质量范围从0到9的 + imagepng($this->im, $pathname, min((int) ($quality / 10), 9)); + } else { + $fun = 'image' . $type; + $fun($this->im, $pathname); + } + + return $this; + } + + /** + * 返回图像宽度 + * @return int 图像宽度 + */ + public function width() + { + return $this->info['width']; + } + + /** + * 返回图像高度 + * @return int 图像高度 + */ + public function height() + { + return $this->info['height']; + } + + /** + * 返回图像类型 + * @return string 图像类型 + */ + public function type() + { + return $this->info['type']; + } + + /** + * 返回图像MIME类型 + * @return string 图像MIME类型 + */ + public function mime() + { + return $this->info['mime']; + } + + /** + * 返回图像尺寸数组 0 - 图像宽度,1 - 图像高度 + * @return array 图像尺寸 + */ + public function size() + { + return [$this->info['width'], $this->info['height']]; + } + + /** + * 旋转图像 + * @param int $degrees 顺时针旋转的度数 + * @return $this + */ + public function rotate($degrees = 90) + { + do { + $img = imagerotate($this->im, -$degrees, imagecolorallocatealpha($this->im, 0, 0, 0, 127)); + imagedestroy($this->im); + $this->im = $img; + } while (!empty($this->gif) && $this->gifNext()); + + $this->info['width'] = imagesx($this->im); + $this->info['height'] = imagesy($this->im); + + return $this; + } + + /** + * 翻转图像 + * @param integer $direction 翻转轴,X或者Y + * @return $this + */ + public function flip($direction = self::FLIP_X) + { + //原图宽度和高度 + $w = $this->info['width']; + $h = $this->info['height']; + + do { + + $img = imagecreatetruecolor($w, $h); + + switch ($direction) { + case self::FLIP_X: + for ($y = 0; $y < $h; $y++) { + imagecopy($img, $this->im, 0, $h - $y - 1, 0, $y, $w, 1); + } + break; + case self::FLIP_Y: + for ($x = 0; $x < $w; $x++) { + imagecopy($img, $this->im, $w - $x - 1, 0, $x, 0, 1, $h); + } + break; + default: + throw new ImageException('不支持的翻转类型'); + } + + imagedestroy($this->im); + $this->im = $img; + + } while (!empty($this->gif) && $this->gifNext()); + + return $this; + } + + /** + * 裁剪图像 + * + * @param integer $w 裁剪区域宽度 + * @param integer $h 裁剪区域高度 + * @param integer $x 裁剪区域x坐标 + * @param integer $y 裁剪区域y坐标 + * @param integer $width 图像保存宽度 + * @param integer $height 图像保存高度 + * + * @return $this + */ + public function crop($w, $h, $x = 0, $y = 0, $width = null, $height = null) + { + //设置保存尺寸 + empty($width) && $width = $w; + empty($height) && $height = $h; + do { + //创建新图像 + $img = imagecreatetruecolor($width, $height); + // 调整默认颜色 + $color = imagecolorallocate($img, 255, 255, 255); + imagefill($img, 0, 0, $color); + //裁剪 + imagecopyresampled($img, $this->im, 0, 0, $x, $y, $width, $height, $w, $h); + imagedestroy($this->im); //销毁原图 + //设置新图像 + $this->im = $img; + } while (!empty($this->gif) && $this->gifNext()); + $this->info['width'] = (int) $width; + $this->info['height'] = (int) $height; + return $this; + } + + /** + * 生成缩略图 + * + * @param integer $width 缩略图最大宽度 + * @param integer $height 缩略图最大高度 + * @param int $type 缩略图裁剪类型 + * + * @return $this + */ + public function thumb($width, $height, $type = self::THUMB_SCALING) + { + //原图宽度和高度 + $w = $this->info['width']; + $h = $this->info['height']; + /* 计算缩略图生成的必要参数 */ + switch ($type) { + /* 等比例缩放 */ + case self::THUMB_SCALING: + //原图尺寸小于缩略图尺寸则不进行缩略 + if ($w < $width && $h < $height) { + return $this; + } + //计算缩放比例 + $scale = min($width / $w, $height / $h); + //设置缩略图的坐标及宽度和高度 + $x = $y = 0; + $width = $w * $scale; + $height = $h * $scale; + break; + /* 居中裁剪 */ + case self::THUMB_CENTER: + //计算缩放比例 + $scale = max($width / $w, $height / $h); + //设置缩略图的坐标及宽度和高度 + $w = $width / $scale; + $h = $height / $scale; + $x = ($this->info['width'] - $w) / 2; + $y = ($this->info['height'] - $h) / 2; + break; + /* 左上角裁剪 */ + case self::THUMB_NORTHWEST: + //计算缩放比例 + $scale = max($width / $w, $height / $h); + //设置缩略图的坐标及宽度和高度 + $x = $y = 0; + $w = $width / $scale; + $h = $height / $scale; + break; + /* 右下角裁剪 */ + case self::THUMB_SOUTHEAST: + //计算缩放比例 + $scale = max($width / $w, $height / $h); + //设置缩略图的坐标及宽度和高度 + $w = $width / $scale; + $h = $height / $scale; + $x = $this->info['width'] - $w; + $y = $this->info['height'] - $h; + break; + /* 填充 */ + case self::THUMB_FILLED: + //计算缩放比例 + if ($w < $width && $h < $height) { + $scale = 1; + } else { + $scale = min($width / $w, $height / $h); + } + //设置缩略图的坐标及宽度和高度 + $neww = $w * $scale; + $newh = $h * $scale; + $x = $this->info['width'] - $w; + $y = $this->info['height'] - $h; + $posx = ($width - $w * $scale) / 2; + $posy = ($height - $h * $scale) / 2; + do { + //创建新图像 + $img = imagecreatetruecolor($width, $height); + // 调整默认颜色 + $color = imagecolorallocate($img, 255, 255, 255); + imagefill($img, 0, 0, $color); + //裁剪 + imagecopyresampled($img, $this->im, $posx, $posy, $x, $y, $neww, $newh, $w, $h); + imagedestroy($this->im); //销毁原图 + $this->im = $img; + } while (!empty($this->gif) && $this->gifNext()); + $this->info['width'] = (int) $width; + $this->info['height'] = (int) $height; + return $this; + /* 固定 */ + case self::THUMB_FIXED: + $x = $y = 0; + break; + default: + throw new ImageException('不支持的缩略图裁剪类型'); + } + /* 裁剪图像 */ + return $this->crop($w, $h, $x, $y, $width, $height); + } + + /** + * 添加水印 + * + * @param string $source 水印图片路径 + * @param int $locate 水印位置 + * @param int $alpha 透明度 + * @return $this + */ + public function water($source, $locate = self::WATER_SOUTHEAST, $alpha = 100) + { + if (!is_file($source)) { + throw new ImageException('水印图像不存在'); + } + //获取水印图像信息 + $info = getimagesize($source); + if (false === $info || (IMAGETYPE_GIF === $info[2] && empty($info['bits']))) { + throw new ImageException('非法水印文件'); + } + //创建水印图像资源 + $fun = 'imagecreatefrom' . image_type_to_extension($info[2], false); + $water = $fun($source); + //设定水印图像的混色模式 + imagealphablending($water, true); + /* 设定水印位置 */ + switch ($locate) { + /* 右下角水印 */ + case self::WATER_SOUTHEAST: + $x = $this->info['width'] - $info[0]; + $y = $this->info['height'] - $info[1]; + break; + /* 左下角水印 */ + case self::WATER_SOUTHWEST: + $x = 0; + $y = $this->info['height'] - $info[1]; + break; + /* 左上角水印 */ + case self::WATER_NORTHWEST: + $x = $y = 0; + break; + /* 右上角水印 */ + case self::WATER_NORTHEAST: + $x = $this->info['width'] - $info[0]; + $y = 0; + break; + /* 居中水印 */ + case self::WATER_CENTER: + $x = ($this->info['width'] - $info[0]) / 2; + $y = ($this->info['height'] - $info[1]) / 2; + break; + /* 下居中水印 */ + case self::WATER_SOUTH: + $x = ($this->info['width'] - $info[0]) / 2; + $y = $this->info['height'] - $info[1]; + break; + /* 右居中水印 */ + case self::WATER_EAST: + $x = $this->info['width'] - $info[0]; + $y = ($this->info['height'] - $info[1]) / 2; + break; + /* 上居中水印 */ + case self::WATER_NORTH: + $x = ($this->info['width'] - $info[0]) / 2; + $y = 0; + break; + /* 左居中水印 */ + case self::WATER_WEST: + $x = 0; + $y = ($this->info['height'] - $info[1]) / 2; + break; + default: + /* 自定义水印坐标 */ + if (is_array($locate)) { + list($x, $y) = $locate; + } else { + throw new ImageException('不支持的水印位置类型'); + } + } + do { + //添加水印 + $src = imagecreatetruecolor($info[0], $info[1]); + // 调整默认颜色 + $color = imagecolorallocate($src, 255, 255, 255); + imagefill($src, 0, 0, $color); + imagecopy($src, $this->im, 0, 0, $x, $y, $info[0], $info[1]); + imagecopy($src, $water, 0, 0, 0, 0, $info[0], $info[1]); + imagecopymerge($this->im, $src, $x, $y, 0, 0, $info[0], $info[1], $alpha); + //销毁零时图片资源 + imagedestroy($src); + } while (!empty($this->gif) && $this->gifNext()); + //销毁水印资源 + imagedestroy($water); + return $this; + } + + /** + * 图像添加文字 + * + * @param string $text 添加的文字 + * @param string $font 字体路径 + * @param integer $size 字号 + * @param string $color 文字颜色 + * @param int $locate 文字写入位置 + * @param integer $offset 文字相对当前位置的偏移量 + * @param integer $angle 文字倾斜角度 + * + * @return $this + * @throws ImageException + */ + public function text($text, $font, $size, $color = '#00000000', + $locate = self::WATER_SOUTHEAST, $offset = 0, $angle = 0) { + + if (!is_file($font)) { + throw new ImageException("不存在的字体文件:{$font}"); + } + //获取文字信息 + $info = imagettfbbox($size, $angle, $font, $text); + $minx = min($info[0], $info[2], $info[4], $info[6]); + $maxx = max($info[0], $info[2], $info[4], $info[6]); + $miny = min($info[1], $info[3], $info[5], $info[7]); + $maxy = max($info[1], $info[3], $info[5], $info[7]); + /* 计算文字初始坐标和尺寸 */ + $x = $minx; + $y = abs($miny); + $w = $maxx - $minx; + $h = $maxy - $miny; + /* 设定文字位置 */ + switch ($locate) { + /* 右下角文字 */ + case self::WATER_SOUTHEAST: + $x += $this->info['width'] - $w; + $y += $this->info['height'] - $h; + break; + /* 左下角文字 */ + case self::WATER_SOUTHWEST: + $y += $this->info['height'] - $h; + break; + /* 左上角文字 */ + case self::WATER_NORTHWEST: + // 起始坐标即为左上角坐标,无需调整 + break; + /* 右上角文字 */ + case self::WATER_NORTHEAST: + $x += $this->info['width'] - $w; + break; + /* 居中文字 */ + case self::WATER_CENTER: + $x += ($this->info['width'] - $w) / 2; + $y += ($this->info['height'] - $h) / 2; + break; + /* 下居中文字 */ + case self::WATER_SOUTH: + $x += ($this->info['width'] - $w) / 2; + $y += $this->info['height'] - $h; + break; + /* 右居中文字 */ + case self::WATER_EAST: + $x += $this->info['width'] - $w; + $y += ($this->info['height'] - $h) / 2; + break; + /* 上居中文字 */ + case self::WATER_NORTH: + $x += ($this->info['width'] - $w) / 2; + break; + /* 左居中文字 */ + case self::WATER_WEST: + $y += ($this->info['height'] - $h) / 2; + break; + default: + /* 自定义文字坐标 */ + if (is_array($locate)) { + list($posx, $posy) = $locate; + $x += $posx; + $y += $posy; + } else { + throw new ImageException('不支持的文字位置类型'); + } + } + /* 设置偏移量 */ + if (is_array($offset)) { + $offset = array_map('intval', $offset); + list($ox, $oy) = $offset; + } else { + $offset = intval($offset); + $ox = $oy = $offset; + } + /* 设置颜色 */ + if (is_string($color) && 0 === strpos($color, '#')) { + $color = str_split(substr($color, 1), 2); + $color = array_map('hexdec', $color); + if (empty($color[3]) || $color[3] > 127) { + $color[3] = 0; + } + } elseif (!is_array($color)) { + throw new ImageException('错误的颜色值'); + } + do { + /* 写入文字 */ + $col = imagecolorallocatealpha($this->im, $color[0], $color[1], $color[2], $color[3]); + imagettftext($this->im, $size, $angle, $x + $ox, $y + $oy, $col, $font, $text); + } while (!empty($this->gif) && $this->gifNext()); + return $this; + } + + /** + * 切换到GIF的下一帧并保存当前帧 + */ + protected function gifNext() + { + ob_start(); + ob_implicit_flush(0); + imagegif($this->im); + $img = ob_get_clean(); + $this->gif->image($img); + $next = $this->gif->nextImage(); + if ($next) { + imagedestroy($this->im); + $this->im = imagecreatefromstring($next); + return $next; + } else { + imagedestroy($this->im); + $this->im = imagecreatefromstring($this->gif->image()); + return false; + } + } + + /** + * 析构方法,用于销毁图像资源 + */ + public function __destruct() + { + empty($this->im) || imagedestroy($this->im); + } + +} diff --git a/vendor/topthink/think-image/src/image/Exception.php b/vendor/topthink/think-image/src/image/Exception.php new file mode 100644 index 0000000..2ebafd8 --- /dev/null +++ b/vendor/topthink/think-image/src/image/Exception.php @@ -0,0 +1,18 @@ + +// +---------------------------------------------------------------------- + +namespace think\image; + + +class Exception extends \RuntimeException +{ + +} \ No newline at end of file diff --git a/vendor/topthink/think-image/src/image/gif/Decoder.php b/vendor/topthink/think-image/src/image/gif/Decoder.php new file mode 100644 index 0000000..a563092 --- /dev/null +++ b/vendor/topthink/think-image/src/image/gif/Decoder.php @@ -0,0 +1,207 @@ + +// +---------------------------------------------------------------------- + +namespace think\image\gif; + + +class Decoder +{ + public $GIF_buffer = []; + public $GIF_arrays = []; + public $GIF_delays = []; + public $GIF_stream = ""; + public $GIF_string = ""; + public $GIF_bfseek = 0; + public $GIF_screen = []; + public $GIF_global = []; + public $GIF_sorted; + public $GIF_colorS; + public $GIF_colorC; + public $GIF_colorF; + + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFDecoder ( $GIF_pointer ) + :: + */ + public function __construct($GIF_pointer) + { + $this->GIF_stream = $GIF_pointer; + $this->getByte(6); // GIF89a + $this->getByte(7); // Logical Screen Descriptor + $this->GIF_screen = $this->GIF_buffer; + $this->GIF_colorF = $this->GIF_buffer[4] & 0x80 ? 1 : 0; + $this->GIF_sorted = $this->GIF_buffer[4] & 0x08 ? 1 : 0; + $this->GIF_colorC = $this->GIF_buffer[4] & 0x07; + $this->GIF_colorS = 2 << $this->GIF_colorC; + if (1 == $this->GIF_colorF) { + $this->getByte(3 * $this->GIF_colorS); + $this->GIF_global = $this->GIF_buffer; + } + + for ($cycle = 1; $cycle;) { + if ($this->getByte(1)) { + switch ($this->GIF_buffer[0]) { + case 0x21: + $this->readExtensions(); + break; + case 0x2C: + $this->readDescriptor(); + break; + case 0x3B: + $cycle = 0; + break; + } + } else { + $cycle = 0; + } + } + } + + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFReadExtension ( ) + :: + */ + public function readExtensions() + { + $this->getByte(1); + for (; ;) { + $this->getByte(1); + if (($u = $this->GIF_buffer[0]) == 0x00) { + break; + } + $this->getByte($u); + /* + * 07.05.2007. + * Implemented a new line for a new function + * to determine the originaly delays between + * frames. + * + */ + if (4 == $u) { + $this->GIF_delays[] = ($this->GIF_buffer[1] | $this->GIF_buffer[2] << 8); + } + } + } + + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFReadExtension ( ) + :: + */ + public function readDescriptor() + { + $this->getByte(9); + $GIF_screen = $this->GIF_buffer; + $GIF_colorF = $this->GIF_buffer[8] & 0x80 ? 1 : 0; + if ($GIF_colorF) { + $GIF_code = $this->GIF_buffer[8] & 0x07; + $GIF_sort = $this->GIF_buffer[8] & 0x20 ? 1 : 0; + } else { + $GIF_code = $this->GIF_colorC; + $GIF_sort = $this->GIF_sorted; + } + $GIF_size = 2 << $GIF_code; + $this->GIF_screen[4] &= 0x70; + $this->GIF_screen[4] |= 0x80; + $this->GIF_screen[4] |= $GIF_code; + if ($GIF_sort) { + $this->GIF_screen[4] |= 0x08; + } + $this->GIF_string = "GIF87a"; + $this->putByte($this->GIF_screen); + if (1 == $GIF_colorF) { + $this->getByte(3 * $GIF_size); + $this->putByte($this->GIF_buffer); + } else { + $this->putByte($this->GIF_global); + } + $this->GIF_string .= chr(0x2C); + $GIF_screen[8] &= 0x40; + $this->putByte($GIF_screen); + $this->getByte(1); + $this->putByte($this->GIF_buffer); + for (; ;) { + $this->getByte(1); + $this->putByte($this->GIF_buffer); + if (($u = $this->GIF_buffer[0]) == 0x00) { + break; + } + $this->getByte($u); + $this->putByte($this->GIF_buffer); + } + $this->GIF_string .= chr(0x3B); + /* + Add frames into $GIF_stream array... + */ + $this->GIF_arrays[] = $this->GIF_string; + } + + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFGetByte ( $len ) + :: + */ + public function getByte($len) + { + $this->GIF_buffer = []; + for ($i = 0; $i < $len; $i++) { + if ($this->GIF_bfseek > strlen($this->GIF_stream)) { + return 0; + } + $this->GIF_buffer[] = ord($this->GIF_stream{$this->GIF_bfseek++}); + } + return 1; + } + + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFPutByte ( $bytes ) + :: + */ + public function putByte($bytes) + { + for ($i = 0; $i < count($bytes); $i++) { + $this->GIF_string .= chr($bytes[$i]); + } + } + + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: PUBLIC FUNCTIONS + :: + :: + :: GIFGetFrames ( ) + :: + */ + public function getFrames() + { + return ($this->GIF_arrays); + } + + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFGetDelays ( ) + :: + */ + public function getDelays() + { + return ($this->GIF_delays); + } +} \ No newline at end of file diff --git a/vendor/topthink/think-image/src/image/gif/Encoder.php b/vendor/topthink/think-image/src/image/gif/Encoder.php new file mode 100644 index 0000000..688f7a0 --- /dev/null +++ b/vendor/topthink/think-image/src/image/gif/Encoder.php @@ -0,0 +1,222 @@ + +// +---------------------------------------------------------------------- +namespace think\image\gif; + +class Encoder +{ + public $GIF = "GIF89a"; /* GIF header 6 bytes */ + public $VER = "GIFEncoder V2.05"; /* Encoder version */ + public $BUF = []; + public $LOP = 0; + public $DIS = 2; + public $COL = -1; + public $IMG = -1; + public $ERR = [ + 'ERR00' => "Does not supported function for only one image!", + 'ERR01' => "Source is not a GIF image!", + 'ERR02' => "Unintelligible flag ", + 'ERR03' => "Does not make animation from animated GIF source", + ]; + + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFEncoder... + :: + */ + public function __construct( + $GIF_src, $GIF_dly, $GIF_lop, $GIF_dis, + $GIF_red, $GIF_grn, $GIF_blu, $GIF_mod + ) + { + if (!is_array($GIF_src)) { + printf("%s: %s", $this->VER, $this->ERR['ERR00']); + exit(0); + } + $this->LOP = ($GIF_lop > -1) ? $GIF_lop : 0; + $this->DIS = ($GIF_dis > -1) ? (($GIF_dis < 3) ? $GIF_dis : 3) : 2; + $this->COL = ($GIF_red > -1 && $GIF_grn > -1 && $GIF_blu > -1) ? + ($GIF_red | ($GIF_grn << 8) | ($GIF_blu << 16)) : -1; + for ($i = 0; $i < count($GIF_src); $i++) { + if (strtolower($GIF_mod) == "url") { + $this->BUF[] = fread(fopen($GIF_src[$i], "rb"), filesize($GIF_src[$i])); + } else if (strtolower($GIF_mod) == "bin") { + $this->BUF[] = $GIF_src[$i]; + } else { + printf("%s: %s ( %s )!", $this->VER, $this->ERR['ERR02'], $GIF_mod); + exit(0); + } + if (substr($this->BUF[$i], 0, 6) != "GIF87a" && substr($this->BUF[$i], 0, 6) != "GIF89a") { + printf("%s: %d %s", $this->VER, $i, $this->ERR['ERR01']); + exit(0); + } + for ($j = (13 + 3 * (2 << (ord($this->BUF[$i]{10}) & 0x07))), $k = true; $k; $j++) { + switch ($this->BUF[$i]{$j}) { + case "!": + if ((substr($this->BUF[$i], ($j + 3), 8)) == "NETSCAPE") { + printf("%s: %s ( %s source )!", $this->VER, $this->ERR['ERR03'], ($i + 1)); + exit(0); + } + break; + case ";": + $k = false; + break; + } + } + } + $this->addHeader(); + for ($i = 0; $i < count($this->BUF); $i++) { + $this->addFrames($i, $GIF_dly[$i]); + } + $this->addFooter(); + } + + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFAddHeader... + :: + */ + public function addHeader() + { + if (ord($this->BUF[0]{10}) & 0x80) { + $cmap = 3 * (2 << (ord($this->BUF[0]{10}) & 0x07)); + $this->GIF .= substr($this->BUF[0], 6, 7); + $this->GIF .= substr($this->BUF[0], 13, $cmap); + $this->GIF .= "!\377\13NETSCAPE2.0\3\1" . $this->word($this->LOP) . "\0"; + } + } + + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFAddFrames... + :: + */ + public function addFrames($i, $d) + { + $Locals_img = ''; + $Locals_str = 13 + 3 * (2 << (ord($this->BUF[$i]{10}) & 0x07)); + $Locals_end = strlen($this->BUF[$i]) - $Locals_str - 1; + $Locals_tmp = substr($this->BUF[$i], $Locals_str, $Locals_end); + $Global_len = 2 << (ord($this->BUF[0]{10}) & 0x07); + $Locals_len = 2 << (ord($this->BUF[$i]{10}) & 0x07); + $Global_rgb = substr($this->BUF[0], 13, + 3 * (2 << (ord($this->BUF[0]{10}) & 0x07))); + $Locals_rgb = substr($this->BUF[$i], 13, + 3 * (2 << (ord($this->BUF[$i]{10}) & 0x07))); + $Locals_ext = "!\xF9\x04" . chr(($this->DIS << 2) + 0) . + chr(($d >> 0) & 0xFF) . chr(($d >> 8) & 0xFF) . "\x0\x0"; + if ($this->COL > -1 && ord($this->BUF[$i]{10}) & 0x80) { + for ($j = 0; $j < (2 << (ord($this->BUF[$i]{10}) & 0x07)); $j++) { + if ( + ord($Locals_rgb{3 * $j + 0}) == (($this->COL >> 16) & 0xFF) && + ord($Locals_rgb{3 * $j + 1}) == (($this->COL >> 8) & 0xFF) && + ord($Locals_rgb{3 * $j + 2}) == (($this->COL >> 0) & 0xFF) + ) { + $Locals_ext = "!\xF9\x04" . chr(($this->DIS << 2) + 1) . + chr(($d >> 0) & 0xFF) . chr(($d >> 8) & 0xFF) . chr($j) . "\x0"; + break; + } + } + } + switch ($Locals_tmp{0}) { + case "!": + /** + * @var string $Locals_img ; + */ + $Locals_img = substr($Locals_tmp, 8, 10); + $Locals_tmp = substr($Locals_tmp, 18, strlen($Locals_tmp) - 18); + break; + case ",": + $Locals_img = substr($Locals_tmp, 0, 10); + $Locals_tmp = substr($Locals_tmp, 10, strlen($Locals_tmp) - 10); + break; + } + if (ord($this->BUF[$i]{10}) & 0x80 && $this->IMG > -1) { + if ($Global_len == $Locals_len) { + if ($this->blockCompare($Global_rgb, $Locals_rgb, $Global_len)) { + $this->GIF .= ($Locals_ext . $Locals_img . $Locals_tmp); + } else { + $byte = ord($Locals_img{9}); + $byte |= 0x80; + $byte &= 0xF8; + $byte |= (ord($this->BUF[0]{10}) & 0x07); + $Locals_img{9} = chr($byte); + $this->GIF .= ($Locals_ext . $Locals_img . $Locals_rgb . $Locals_tmp); + } + } else { + $byte = ord($Locals_img{9}); + $byte |= 0x80; + $byte &= 0xF8; + $byte |= (ord($this->BUF[$i]{10}) & 0x07); + $Locals_img{9} = chr($byte); + $this->GIF .= ($Locals_ext . $Locals_img . $Locals_rgb . $Locals_tmp); + } + } else { + $this->GIF .= ($Locals_ext . $Locals_img . $Locals_tmp); + } + $this->IMG = 1; + } + + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFAddFooter... + :: + */ + public function addFooter() + { + $this->GIF .= ";"; + } + + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFBlockCompare... + :: + */ + public function blockCompare($GlobalBlock, $LocalBlock, $Len) + { + for ($i = 0; $i < $Len; $i++) { + if ( + $GlobalBlock{3 * $i + 0} != $LocalBlock{3 * $i + 0} || + $GlobalBlock{3 * $i + 1} != $LocalBlock{3 * $i + 1} || + $GlobalBlock{3 * $i + 2} != $LocalBlock{3 * $i + 2} + ) { + return (0); + } + } + return (1); + } + + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFWord... + :: + */ + public function word($int) + { + return (chr($int & 0xFF) . chr(($int >> 8) & 0xFF)); + } + + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GetAnimation... + :: + */ + public function getAnimation() + { + return ($this->GIF); + } +} \ No newline at end of file diff --git a/vendor/topthink/think-image/src/image/gif/Gif.php b/vendor/topthink/think-image/src/image/gif/Gif.php new file mode 100644 index 0000000..b890915 --- /dev/null +++ b/vendor/topthink/think-image/src/image/gif/Gif.php @@ -0,0 +1,88 @@ + +// +---------------------------------------------------------------------- + +namespace think\image\gif; + +class Gif +{ + /** + * GIF帧列表 + * + * @var array + */ + private $frames = []; + /** + * 每帧等待时间列表 + * + * @var array + */ + private $delays = []; + + /** + * 构造方法,用于解码GIF图片 + * + * @param string $src GIF图片数据 + * @param string $mod 图片数据类型 + * @throws \Exception + */ + public function __construct($src = null, $mod = 'url') + { + if (!is_null($src)) { + if ('url' == $mod && is_file($src)) { + $src = file_get_contents($src); + } + /* 解码GIF图片 */ + try { + $de = new Decoder($src); + $this->frames = $de->getFrames(); + $this->delays = $de->getDelays(); + } catch (\Exception $e) { + throw new \Exception("解码GIF图片出错"); + } + } + } + + /** + * 设置或获取当前帧的数据 + * + * @param string $stream 二进制数据流 + * @return mixed 获取到的数据 + */ + public function image($stream = null) + { + if (is_null($stream)) { + $current = current($this->frames); + return false === $current ? reset($this->frames) : $current; + } + $this->frames[key($this->frames)] = $stream; + } + + /** + * 将当前帧移动到下一帧 + * + * @return string 当前帧数据 + */ + public function nextImage() + { + return next($this->frames); + } + + /** + * 编码并保存当前GIF图片 + * + * @param string $pathname 图片名称 + */ + public function save($pathname) + { + $gif = new Encoder($this->frames, $this->delays, 0, 2, 0, 0, 0, 'bin'); + file_put_contents($pathname, $gif->getAnimation()); + } +} \ No newline at end of file diff --git a/vendor/topthink/think-image/tests/CropTest.php b/vendor/topthink/think-image/tests/CropTest.php new file mode 100644 index 0000000..26a0531 --- /dev/null +++ b/vendor/topthink/think-image/tests/CropTest.php @@ -0,0 +1,67 @@ + +// +---------------------------------------------------------------------- +namespace tests; + +use think\Image; + +class CropTest extends TestCase +{ + public function testJpeg() + { + $pathname = TEST_PATH . 'tmp/crop.jpg'; + $image = Image::open($this->getJpeg()); + + $image->crop(200, 200, 100, 100, 300, 300)->save($pathname); + + $this->assertEquals(300, $image->width()); + $this->assertEquals(300, $image->height()); + + $file = new \SplFileInfo($pathname); + + $this->assertTrue($file->isFile()); + + @unlink($pathname); + } + + public function testPng() + { + $pathname = TEST_PATH . 'tmp/crop.png'; + $image = Image::open($this->getPng()); + + $image->crop(200, 200, 100, 100, 300, 300)->save($pathname); + + $this->assertEquals(300, $image->width()); + $this->assertEquals(300, $image->height()); + + $file = new \SplFileInfo($pathname); + + $this->assertTrue($file->isFile()); + + @unlink($pathname); + } + + public function testGif() + { + $pathname = TEST_PATH . 'tmp/crop.gif'; + $image = Image::open($this->getGif()); + + $image->crop(200, 200, 100, 100, 300, 300)->save($pathname); + + $this->assertEquals(300, $image->width()); + $this->assertEquals(300, $image->height()); + + $file = new \SplFileInfo($pathname); + + $this->assertTrue($file->isFile()); + + @unlink($pathname); + } +} \ No newline at end of file diff --git a/vendor/topthink/think-image/tests/FlipTest.php b/vendor/topthink/think-image/tests/FlipTest.php new file mode 100644 index 0000000..bf7a5e2 --- /dev/null +++ b/vendor/topthink/think-image/tests/FlipTest.php @@ -0,0 +1,43 @@ + +// +---------------------------------------------------------------------- +namespace tests; + +use think\Image; + +class FlipTest extends TestCase +{ + public function testJpeg() + { + $pathname = TEST_PATH . 'tmp/flip.jpg'; + $image = Image::open($this->getJpeg()); + $image->flip()->save($pathname); + + $file = new \SplFileInfo($pathname); + + $this->assertTrue($file->isFile()); + + @unlink($pathname); + } + + + public function testGif() + { + $pathname = TEST_PATH . 'tmp/flip.gif'; + $image = Image::open($this->getGif()); + $image->flip(Image::FLIP_Y)->save($pathname); + + $file = new \SplFileInfo($pathname); + + $this->assertTrue($file->isFile()); + + @unlink($pathname); + } +} \ No newline at end of file diff --git a/vendor/topthink/think-image/tests/InfoTest.php b/vendor/topthink/think-image/tests/InfoTest.php new file mode 100644 index 0000000..22132ca --- /dev/null +++ b/vendor/topthink/think-image/tests/InfoTest.php @@ -0,0 +1,60 @@ + +// +---------------------------------------------------------------------- +namespace tests; + +use think\Image; + +class InfoTest extends TestCase +{ + + public function testOpen() + { + $this->setExpectedException("\\think\\image\\Exception"); + Image::open(''); + } + + public function testIllegal() + { + $this->setExpectedException("\\think\\image\\Exception", 'Illegal image file'); + Image::open(TEST_PATH . 'images/test.bmp'); + } + + public function testJpeg() + { + $image = Image::open($this->getJpeg()); + $this->assertEquals(800, $image->width()); + $this->assertEquals(600, $image->height()); + $this->assertEquals('jpeg', $image->type()); + $this->assertEquals('image/jpeg', $image->mime()); + $this->assertEquals([800, 600], $image->size()); + } + + + public function testPng() + { + $image = Image::open($this->getPng()); + $this->assertEquals(800, $image->width()); + $this->assertEquals(600, $image->height()); + $this->assertEquals('png', $image->type()); + $this->assertEquals('image/png', $image->mime()); + $this->assertEquals([800, 600], $image->size()); + } + + public function testGif() + { + $image = Image::open($this->getGif()); + $this->assertEquals(380, $image->width()); + $this->assertEquals(216, $image->height()); + $this->assertEquals('gif', $image->type()); + $this->assertEquals('image/gif', $image->mime()); + $this->assertEquals([380, 216], $image->size()); + } +} \ No newline at end of file diff --git a/vendor/topthink/think-image/tests/RotateTest.php b/vendor/topthink/think-image/tests/RotateTest.php new file mode 100644 index 0000000..56572d2 --- /dev/null +++ b/vendor/topthink/think-image/tests/RotateTest.php @@ -0,0 +1,42 @@ + +// +---------------------------------------------------------------------- +namespace tests; + +use think\Image; + +class RotateTest extends TestCase +{ + public function testJpeg() + { + $pathname = TEST_PATH . 'tmp/rotate.jpg'; + $image = Image::open($this->getJpeg()); + $image->rotate(90)->save($pathname); + + $file = new \SplFileInfo($pathname); + + $this->assertTrue($file->isFile()); + + @unlink($pathname); + } + + public function testGif() + { + $pathname = TEST_PATH . 'tmp/rotate.gif'; + $image = Image::open($this->getGif()); + $image->rotate(90)->save($pathname); + + $file = new \SplFileInfo($pathname); + + $this->assertTrue($file->isFile()); + + @unlink($pathname); + } +} \ No newline at end of file diff --git a/vendor/topthink/think-image/tests/TestCase.php b/vendor/topthink/think-image/tests/TestCase.php new file mode 100644 index 0000000..89ba1b4 --- /dev/null +++ b/vendor/topthink/think-image/tests/TestCase.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- + +namespace tests; + +use think\File; + +abstract class TestCase extends \PHPUnit_Framework_TestCase +{ + + protected function getJpeg() + { + return new File(TEST_PATH . 'images/test.jpg'); + } + + protected function getPng() + { + return new File(TEST_PATH . 'images/test.png'); + } + + protected function getGif() + { + return new File(TEST_PATH . 'images/test.gif'); + } +} \ No newline at end of file diff --git a/vendor/topthink/think-image/tests/TextTest.php b/vendor/topthink/think-image/tests/TextTest.php new file mode 100644 index 0000000..04506a2 --- /dev/null +++ b/vendor/topthink/think-image/tests/TextTest.php @@ -0,0 +1,58 @@ + +// +---------------------------------------------------------------------- +namespace tests; + +use think\Image; + +class TextTest extends TestCase +{ + public function testJpeg() + { + $pathname = TEST_PATH . 'tmp/text.jpg'; + $image = Image::open($this->getJpeg()); + + $image->text('test', TEST_PATH . 'images/test.ttf', 12)->save($pathname); + + $file = new \SplFileInfo($pathname); + + $this->assertTrue($file->isFile()); + + @unlink($pathname); + } + + public function testPng() + { + $pathname = TEST_PATH . 'tmp/text.png'; + $image = Image::open($this->getPng()); + + $image->text('test', TEST_PATH . 'images/test.ttf', 12)->save($pathname); + + $file = new \SplFileInfo($pathname); + + $this->assertTrue($file->isFile()); + + @unlink($pathname); + } + + public function testGif() + { + $pathname = TEST_PATH . 'tmp/text.gif'; + $image = Image::open($this->getGif()); + + $image->text('test', TEST_PATH . 'images/test.ttf', 12)->save($pathname); + + $file = new \SplFileInfo($pathname); + + $this->assertTrue($file->isFile()); + + @unlink($pathname); + } +} \ No newline at end of file diff --git a/vendor/topthink/think-image/tests/ThumbTest.php b/vendor/topthink/think-image/tests/ThumbTest.php new file mode 100644 index 0000000..07113c8 --- /dev/null +++ b/vendor/topthink/think-image/tests/ThumbTest.php @@ -0,0 +1,284 @@ + +// +---------------------------------------------------------------------- +namespace tests; + +use think\Image; + +class ThumbTest extends TestCase +{ + public function testJpeg() + { + $pathname = TEST_PATH . 'tmp/thumb.jpg'; + + //1 + $image = Image::open($this->getJpeg()); + + $image->thumb(200, 200, Image::THUMB_CENTER)->save($pathname); + + $this->assertEquals(200, $image->width()); + $this->assertEquals(200, $image->height()); + + $file = new \SplFileInfo($pathname); + + $this->assertTrue($file->isFile()); + + @unlink($pathname); + + //2 + $image = Image::open($this->getJpeg()); + + $image->thumb(200, 200, Image::THUMB_SCALING)->save($pathname); + + $this->assertEquals(200, $image->width()); + $this->assertEquals(150, $image->height()); + + $file = new \SplFileInfo($pathname); + + $this->assertTrue($file->isFile()); + + @unlink($pathname); + + //3 + $image = Image::open($this->getJpeg()); + + $image->thumb(200, 200, Image::THUMB_FILLED)->save($pathname); + + $this->assertEquals(200, $image->width()); + $this->assertEquals(200, $image->height()); + + $file = new \SplFileInfo($pathname); + + $this->assertTrue($file->isFile()); + + @unlink($pathname); + + //4 + $image = Image::open($this->getJpeg()); + + $image->thumb(200, 200, Image::THUMB_NORTHWEST)->save($pathname); + + $this->assertEquals(200, $image->width()); + $this->assertEquals(200, $image->height()); + + $file = new \SplFileInfo($pathname); + + $this->assertTrue($file->isFile()); + + @unlink($pathname); + + //5 + $image = Image::open($this->getJpeg()); + + $image->thumb(200, 200, Image::THUMB_SOUTHEAST)->save($pathname); + + $this->assertEquals(200, $image->width()); + $this->assertEquals(200, $image->height()); + + $file = new \SplFileInfo($pathname); + + $this->assertTrue($file->isFile()); + + @unlink($pathname); + + //6 + $image = Image::open($this->getJpeg()); + + $image->thumb(200, 200, Image::THUMB_FIXED)->save($pathname); + + $this->assertEquals(200, $image->width()); + $this->assertEquals(200, $image->height()); + + $file = new \SplFileInfo($pathname); + + $this->assertTrue($file->isFile()); + + @unlink($pathname); + } + + + public function testPng() + { + $pathname = TEST_PATH . 'tmp/thumb.png'; + + //1 + $image = Image::open($this->getPng()); + + $image->thumb(200, 200, Image::THUMB_CENTER)->save($pathname); + + $this->assertEquals(200, $image->width()); + $this->assertEquals(200, $image->height()); + + $file = new \SplFileInfo($pathname); + + $this->assertTrue($file->isFile()); + + @unlink($pathname); + + //2 + $image = Image::open($this->getPng()); + + $image->thumb(200, 200, Image::THUMB_SCALING)->save($pathname); + + $this->assertEquals(200, $image->width()); + $this->assertEquals(150, $image->height()); + + $file = new \SplFileInfo($pathname); + + $this->assertTrue($file->isFile()); + + @unlink($pathname); + + //3 + $image = Image::open($this->getPng()); + + $image->thumb(200, 200, Image::THUMB_FILLED)->save($pathname); + + $this->assertEquals(200, $image->width()); + $this->assertEquals(200, $image->height()); + + $file = new \SplFileInfo($pathname); + + $this->assertTrue($file->isFile()); + + @unlink($pathname); + + //4 + $image = Image::open($this->getPng()); + + $image->thumb(200, 200, Image::THUMB_NORTHWEST)->save($pathname); + + $this->assertEquals(200, $image->width()); + $this->assertEquals(200, $image->height()); + + $file = new \SplFileInfo($pathname); + + $this->assertTrue($file->isFile()); + + @unlink($pathname); + + //5 + $image = Image::open($this->getPng()); + + $image->thumb(200, 200, Image::THUMB_SOUTHEAST)->save($pathname); + + $this->assertEquals(200, $image->width()); + $this->assertEquals(200, $image->height()); + + $file = new \SplFileInfo($pathname); + + $this->assertTrue($file->isFile()); + + @unlink($pathname); + + //6 + $image = Image::open($this->getPng()); + + $image->thumb(200, 200, Image::THUMB_FIXED)->save($pathname); + + $this->assertEquals(200, $image->width()); + $this->assertEquals(200, $image->height()); + + $file = new \SplFileInfo($pathname); + + $this->assertTrue($file->isFile()); + + @unlink($pathname); + } + + public function testGif() + { + $pathname = TEST_PATH . 'tmp/thumb.gif'; + + //1 + $image = Image::open($this->getGif()); + + $image->thumb(200, 200, Image::THUMB_CENTER)->save($pathname); + + $this->assertEquals(200, $image->width()); + $this->assertEquals(200, $image->height()); + + $file = new \SplFileInfo($pathname); + + $this->assertTrue($file->isFile()); + + @unlink($pathname); + + //2 + $image = Image::open($this->getGif()); + + $image->thumb(200, 200, Image::THUMB_SCALING)->save($pathname); + + $this->assertEquals(200, $image->width()); + $this->assertEquals(113, $image->height()); + + $file = new \SplFileInfo($pathname); + + $this->assertTrue($file->isFile()); + + @unlink($pathname); + + //3 + $image = Image::open($this->getGif()); + + $image->thumb(200, 200, Image::THUMB_FILLED)->save($pathname); + + $this->assertEquals(200, $image->width()); + $this->assertEquals(200, $image->height()); + + $file = new \SplFileInfo($pathname); + + $this->assertTrue($file->isFile()); + + @unlink($pathname); + + //4 + $image = Image::open($this->getGif()); + + $image->thumb(200, 200, Image::THUMB_NORTHWEST)->save($pathname); + + $this->assertEquals(200, $image->width()); + $this->assertEquals(200, $image->height()); + + $file = new \SplFileInfo($pathname); + + $this->assertTrue($file->isFile()); + + @unlink($pathname); + + //5 + $image = Image::open($this->getGif()); + + $image->thumb(200, 200, Image::THUMB_SOUTHEAST)->save($pathname); + + $this->assertEquals(200, $image->width()); + $this->assertEquals(200, $image->height()); + + $file = new \SplFileInfo($pathname); + + $this->assertTrue($file->isFile()); + + @unlink($pathname); + + //6 + $image = Image::open($this->getGif()); + + $image->thumb(200, 200, Image::THUMB_FIXED)->save($pathname); + + $this->assertEquals(200, $image->width()); + $this->assertEquals(200, $image->height()); + + $file = new \SplFileInfo($pathname); + + $this->assertTrue($file->isFile()); + + @unlink($pathname); + } +} \ No newline at end of file diff --git a/vendor/topthink/think-image/tests/WaterTest.php b/vendor/topthink/think-image/tests/WaterTest.php new file mode 100644 index 0000000..b6a2bcc --- /dev/null +++ b/vendor/topthink/think-image/tests/WaterTest.php @@ -0,0 +1,58 @@ + +// +---------------------------------------------------------------------- +namespace tests; + +use think\Image; + +class WaterTest extends TestCase +{ + public function testJpeg() + { + $pathname = TEST_PATH . 'tmp/water.jpg'; + $image = Image::open($this->getJpeg()); + + $image->water(TEST_PATH . 'images/test.gif')->save($pathname); + + $file = new \SplFileInfo($pathname); + + $this->assertTrue($file->isFile()); + + @unlink($pathname); + } + + public function testPng() + { + $pathname = TEST_PATH . 'tmp/water.png'; + $image = Image::open($this->getPng()); + + $image->water(TEST_PATH . 'images/test.gif')->save($pathname); + + $file = new \SplFileInfo($pathname); + + $this->assertTrue($file->isFile()); + + @unlink($pathname); + } + + public function testGif() + { + $pathname = TEST_PATH . 'tmp/water.gif'; + $image = Image::open($this->getGif()); + + $image->water(TEST_PATH . 'images/test.jpg')->save($pathname); + + $file = new \SplFileInfo($pathname); + + $this->assertTrue($file->isFile()); + + @unlink($pathname); + } +} \ No newline at end of file diff --git a/vendor/topthink/think-image/tests/autoload.php b/vendor/topthink/think-image/tests/autoload.php new file mode 100644 index 0000000..f2e8aae --- /dev/null +++ b/vendor/topthink/think-image/tests/autoload.php @@ -0,0 +1,15 @@ + +// +---------------------------------------------------------------------- +define('TEST_PATH', __DIR__ . '/'); +// 加载框架基础文件 +require __DIR__ . '/../thinkphp/base.php'; +\think\Loader::addNamespace('tests', TEST_PATH); +\think\Loader::addNamespace('think', __DIR__ . '/../src/'); \ No newline at end of file diff --git a/vendor/topthink/think-image/tests/images/test.bmp b/vendor/topthink/think-image/tests/images/test.bmp new file mode 100644 index 0000000..e69de29 diff --git a/vendor/topthink/think-image/tests/images/test.gif b/vendor/topthink/think-image/tests/images/test.gif new file mode 100644 index 0000000..c6d5472 Binary files /dev/null and b/vendor/topthink/think-image/tests/images/test.gif differ diff --git a/vendor/topthink/think-image/tests/images/test.jpg b/vendor/topthink/think-image/tests/images/test.jpg new file mode 100644 index 0000000..4bb6549 Binary files /dev/null and b/vendor/topthink/think-image/tests/images/test.jpg differ diff --git a/vendor/topthink/think-image/tests/images/test.png b/vendor/topthink/think-image/tests/images/test.png new file mode 100644 index 0000000..f4830e3 Binary files /dev/null and b/vendor/topthink/think-image/tests/images/test.png differ diff --git a/vendor/topthink/think-image/tests/images/test.ttf b/vendor/topthink/think-image/tests/images/test.ttf new file mode 100644 index 0000000..4f985c8 Binary files /dev/null and b/vendor/topthink/think-image/tests/images/test.ttf differ diff --git a/vendor/topthink/think-image/tests/tmp/.gitignore b/vendor/topthink/think-image/tests/tmp/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/vendor/topthink/think-image/tests/tmp/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/vendor/topthink/think-migration/.gitignore b/vendor/topthink/think-migration/.gitignore new file mode 100644 index 0000000..30babff --- /dev/null +++ b/vendor/topthink/think-migration/.gitignore @@ -0,0 +1,3 @@ +/vendor/ +.idea +composer.lock diff --git a/vendor/topthink/think-migration/LICENSE b/vendor/topthink/think-migration/LICENSE new file mode 100644 index 0000000..2c76a5a --- /dev/null +++ b/vendor/topthink/think-migration/LICENSE @@ -0,0 +1,32 @@ + +ThinkPHP遵循Apache2开源协议发布,并提供免费使用。 +版权所有Copyright © 2006-2016 by ThinkPHP (http://thinkphp.cn) +All rights reserved。 +ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。 + +Apache Licence是著名的非盈利开源组织Apache采用的协议。 +该协议和BSD类似,鼓励代码共享和尊重原作者的著作权, +允许代码修改,再作为开源或商业软件发布。需要满足 +的条件: +1. 需要给代码的用户一份Apache Licence ; +2. 如果你修改了代码,需要在被修改的文件中说明; +3. 在延伸的代码中(修改和有源代码衍生的代码中)需要 +带有原来代码中的协议,商标,专利声明和其他原来作者规 +定需要包含的说明; +4. 如果再发布的产品中包含一个Notice文件,则在Notice文 +件中需要带有本协议内容。你可以在Notice中增加自己的 +许可,但不可以表现为对Apache Licence构成更改。 +具体的协议参考:http://www.apache.org/licenses/LICENSE-2.0 + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/vendor/topthink/think-migration/README.md b/vendor/topthink/think-migration/README.md new file mode 100644 index 0000000..39e6584 --- /dev/null +++ b/vendor/topthink/think-migration/README.md @@ -0,0 +1,6 @@ +# thinkphp6 数据库迁移工具 + +## 安装 +~~~ +composer require topthink/think-migration +~~~ diff --git a/vendor/topthink/think-migration/composer.json b/vendor/topthink/think-migration/composer.json new file mode 100644 index 0000000..34b1339 --- /dev/null +++ b/vendor/topthink/think-migration/composer.json @@ -0,0 +1,34 @@ +{ + "name": "topthink/think-migration", + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "license": "Apache-2.0", + "autoload": { + "psr-4": { + "Phinx\\": "phinx/src/Phinx", + "think\\migration\\": "src" + } + }, + "extra": { + "think": { + "services": [ + "think\\migration\\Service" + ] + } + }, + "require": { + "topthink/framework": "^6.0.0", + "topthink/think-helper": "^3.0.3" + }, + "minimum-stability": "dev", + "suggest": { + "fzaninotto/faker": "Required to use the factory builder (^1.8)." + }, + "require-dev": { + "fzaninotto/faker": "^1.8" + } +} diff --git a/vendor/topthink/think-migration/phinx/CHANGELOG.md b/vendor/topthink/think-migration/phinx/CHANGELOG.md new file mode 100644 index 0000000..df1c2e4 --- /dev/null +++ b/vendor/topthink/think-migration/phinx/CHANGELOG.md @@ -0,0 +1,370 @@ +# Version History + +**0.6.5** (Thursday, 27 October 2016) + +* Documentation updates +* Pull requests + * [#831](https://github.com/robmorgan/phinx/pull/831) Typos + * [#929](https://github.com/robmorgan/phinx/pull/929) Support glob brace for seed paths + * [#949](https://github.com/robmorgan/phinx/pull/949) Fix for Config::getMigrationBaseClassName + * [#958](https://github.com/robmorgan/phinx/pull/958) Allow console input to be used within adapters + +**0.6.4** (Wednesday, 27th July 2016) + +* Documentation updates +* Pull requests + * [#909](https://github.com/robmorgan/phinx/pull/909) Declare test class properties + * [#910](https://github.com/robmorgan/phinx/pull/910), [#916](https://github.com/robmorgan/phinx/pull/916) Remove unused variables + * [#912](https://github.com/robmorgan/phinx/pull/912) ConfigInterface usage consistency + * [#914](https://github.com/robmorgan/phinx/pull/914) Set return values and @return documentation + * [#918](https://github.com/robmorgan/phinx/pull/918) Docblock correction for Phinx\Migration\Manager::executeSeed() + * [#921](https://github.com/robmorgan/phinx/pull/921) Add Phinx\Wrapper\TextWrapper::getSeed() +* Bug fixes + * [#908](https://github.com/robmorgan/phinx/pull/908) Fix setting options for Column, ForeignKey and Index + * [#922](https://github.com/robmorgan/phinx/pull/922) SQLite adapter drops table on changeColumn if there's a foreign key + +**0.6.3** (Monday, 18th July 2016) + +* New features + * [#707](https://github.com/robmorgan/phinx/pull/707/files) Add arguments for timestamps columns names +* Documentation cleanup +* Bug fixes + * [#884](https://github.com/robmorgan/phinx/pull/884) Only rollback 1 migration when only 2 migrations exist + * Input and Output are now correctly supplied to migration template creation classes + +**0.6.2** (Thursday, 23rd June 2016) + +* Fix breakpoint support for Postgres +* HHVM now passes all tests + +**0.6.1** (Tuesday, 21st June 2016) + +* Fix rollback when only 1 migration + +**0.6.0** (Tuesday, 21st June 2016) + +* Backward incompatibility - see [UPGRADE_0.6](UPGRADE_0.6.md) document +* Introduce Input and Output access to migrations and template creation +* New breakpoint command +* Moved version history to this CHANGELOG.md document +* More tests + +**0.5.5** (Friday, 17th May 2016) + +* Fix support for running multiple seeders +* Bug fix for migration template source - defaults and command line +* Bug fixes + +**0.5.4** (Monday, 25th April 2016) + +* Added support for running multiple seeders +* Use `GLOB_BRACE` when finding migrations only if its available +* Added support for MySQL `VARBINARY` column type +* Minor bug fixes + +**0.5.3** (Monday, 7th March 2016) + +* Critical fix: allow `migration_name` to be `null`. Introduced in 0.5.2 +* Status command now shows migration start and end times +* Bug fix for rolling back by date +* Documentation improvements + +**0.5.2** (Tuesday, 1st March 2016) + +* Status command now includes missing migration names +* Added support for Postgres table comments +* Added `insert()` for the TablePrefixAdapter +* Fixed the migration verbosity flag +* Added MySQL 5.7 JSON support +* Added support for MySQL `FULLTEXT` indexes +* Postgres now supports `BIGSERIAL` for primary keys +* Added support for MySQL index limits +* Initial support for multiple migration paths (using glob) +* Documentation improvements +* Unit test enhancements + +**0.5.1** (Wednesday, 30th December 2015) + +* **PHP 5.3 is no longer supported!** +* Add support for Symfony 3.0 components +* Ensure that the `status` command returns the correct exit code +* Allow `$version` to be passed into templates +* Support for MySQL `YEAR` column type +* Multiple documentation updates and corrections + +**0.5.0** (Monday, 30th November 2015) + +* Support for seeding data after database creation +* The migration and seed directories are now nested under `db` by default +* Moved `Phinx\Migration\Util` to `Phinx\Util\Util` +* All `insert()` methods now have a slightly different method signature +* Fixed key/insert operations for MySQL +* Introduced `AdapterInterface::hasIndexByName()` +* Improved `dropForeignKey()` handling for SQLite +* Added support for the MySQL `binary` datatype. BLOBs now use the proper type. +* The status command shows a count of pending migrations in JSON output +* We are now testing against PHP 7 + +**0.4.6** (Friday, 11th September 2015) + +* You can now set custom migration templates in the config files +* Support for MySQL unsigned booleans +* Support for Postgres `smallint` column types +* Support for `AFTER` when using `changeColumn()` with MySQL +* Support for `precision` and `scale` when using the Postgres `decimal` type +* Fixed a bug where duplicate migration names could be used +* The schema table is now created with a primary key +* Fixed issues when using the MySQL `STRICT_TRANS_TABLE` mode +* Improved the docs in the default migration template +* Made Box PHAR ignore the bundled `phinx.yml` configuration file +* Updated Box installer URL +* Internal code improvements +* Documentation improvements + +**0.4.5** (Tuesday, 1st September 2015) + +* The rollback command now supports a date argument +* Fixed DBLIB DSN strings for Microsoft SQL Server +* Postgres support for `jsonb` columns added +* The `addTimestamps()` helper method no longer updates the `created_at` column +* Fix for Postgres named foreign keys +* Unit test improvements (including strict warnings) +* Documentation improvements + +**0.4.4** (Sunday, 14th June 2015) + +* The `change` method is now the default +* Added a generic adapter insert method. Warning: The implementation will change! +* Updated Symfony depdencies to ~2.7 +* Support for MySQL `BLOB` column types +* SQLite migration fixes +* Documentation improvements + +**0.4.3** (Monday, 23rd Feburary 2015) + +* Postgres bugfix for modifying column DEFAULTs +* MySQL bugfix for setting column INTEGER lengths +* SQLite bugfix for creating multiple indexes with similar names + +**0.4.2.1** (Saturday, 7th Feburary 2015) + +* Proper release, updated docs + +**0.4.2** (Friday, 6th Feburary 2015) + +* Postgres support for `json` columns added +* MySQL support for `enum` and `set` columns added +* Allow setting `identity` option on columns +* Template configuration and generation made more extensible +* Created a base class for `ProxyAdapter` and `TablePrefixAdapter` +* Switched to PSR-4 + +**0.4.1** (Tuesday, 23rd December 2014) + +* MySQL support for reserved words in hasColumn and getColumns methods +* Better MySQL Adapter test coverage and performance fixes +* Updated dependent Symfony components to 2.6.x + +**0.4.0** (Sunday, 14th December 2014) + +* Adding initial support for running Phinx via a web interface +* Support for table prefixes and suffixes +* Bugfix for foreign key options +* MySQL keeps column default when renaming columns +* MySQL support for tiny/medium and longtext columns added +* Changed SQL Server binary columns to varbinary +* MySQL supports table comments +* Postgres supports column comments +* Empty strings are now supported for default column values +* Booleans are now supported for default column values +* Fixed SQL Server default constraint error when changing column types +* Migration timestamps are now created in UTC +* Locked Symfony Components to 2.5.0 +* Support for custom migration base classes +* Cleaned up source code formatting +* Migrations have access to the output stream +* Support for custom PDO connections when a PHP config +* Added support for Postgres UUID type +* Fixed issue with Postgres dropping foreign keys + +**0.3.8** (Sunday, 5th October 2014) + +* Added new CHAR & Geospatial column types +* Added MySQL unix socket support +* Added precision & scale support for SQL Server +* Several bug fixes for SQLite +* Improved error messages +* Overall code optimizations +* Optimizations to MySQL hasTable method + +**0.3.7** (Tuesday, 12th August 2014) + +* Smarter configuration file support +* Support for Postgres Schemas +* Fixed charset support for Microsoft SQL Server +* Fix for Unique indexes in all adapters +* Improvements for MySQL foreign key migration syntax +* Allow MySQL column types with extra info +* Fixed SQLite autoincrement behaviour +* PHPDoc improvements +* Documentation improvements +* Unit test improvements +* Removing primary_key as a type + +**0.3.6** (Sunday, 29th June 2014) + +* Add custom adapter support +* Fix PHP 5.3 compatibility for SQL Server + +**0.3.5** (Saturday, 21st June 2014) + +* Added Microsoft SQL Server support +* Removed Primary Key column type +* Cleaned up and optimized many methods +* Updated Symfony dependencies to v2.5.0 +* PHPDoc improvements + +**0.3.4** (Sunday, 27th April 2014) + +* Added support MySQL unsigned integer, biginteger, float and decimal types +* Added JSON output support for the status command +* Fix a bug where Postgres couldnt rollback foreign keys +* Moved Phinx type references to interface constants +* Fixed a bug with SQLite in-memory databases + +**0.3.3** (Saturday, 22nd March 2014) + +* Added support for JSON configuration +* Named index support for all adapters (thanks @archer308) +* Updated Composer dependencies +* Fix for SQLite Integer Type +* Fix for MySQL port option + +**0.3.2** (Monday, 24th February 2014) + +* Adding better Postgres type support + +**0.3.1** (Sunday, 23rd February 2014) + +* Adding MySQL charset support to the YAML config +* Removing trailing spaces + +**0.3.0** (Sunday, 2nd February 2014) + +* PSR-2 support +* Method to add timestamps easily to tables +* Support for column comments in the Postgres adapter +* Fixes for MySQL driver options +* Fixes for MySQL biginteger type + +**0.2.9** (Saturday, 16th November 2013) + +* Added SQLite Support +* Improving the unit tests, especially on Windows + +**0.2.8** (Sunday, 25th August 2013) + +* Added PostgresSQL Support + +**0.2.7** (Saturday, 24th August 2013) + +* Critical fix for a token parsing bug +* Removed legacy build system +* Improving docs + +**0.2.6** (Saturday, 24th August 2013) + +* Added support for environment vars in config files +* Added support for environment vars to set the Phinx Env +* Improving docs +* Fixed a bug with column names in indexes +* Changes for developers in regards to the unit tests + +**0.2.5** (Sunday, 26th May 2013) + +* Added support for Box Phar Archive Packaging +* Added support for MYSQL_ATTR driver options +* Fixed a bug where foreign keys cannot be removed +* Added support for MySQL table collation +* Updated Composer dependencies +* Removed verbosity options, now relies on Symfony instead +* Improved unit tests + +**0.2.4** (Saturday, 20th April 2013) + +* The Rollback command supports the verbosity parameter +* The Rollback command has more detailed output +* Table::dropForeignKey now returns the table instance + +**0.2.3** (Saturday, 6th April 2013) + +* Fixed a reporting bug when Phinx couldn't connect to a database +* Added support for the MySQL 'ON UPDATE' function +* Phinx timestamp is now mapped to MySQL timestamp instead of datetime +* Fixed a docs typo for the minimum PHP version +* Added UTF8 support for migrations +* Changed regex to handle migration names differently +* Added support for custom MySQL table engines such as MyISAM +* Added the change method to the migration template + +**0.2.2** (Sunday, 3rd March 2013) + +* Added a new verbosity parameter to see more output when migrating +* Support for PHP config files + +**0.2.1** (Sunday, 3rd March 2013) + +* Broken Release. Do not use! +* Unit tests no longer rely on the default phinx.yml file +* Running migrate for the first time does not give php warnings +* `default_migration_table` is now actually supported +* Updated docblocks to 2013. + +**0.2.0** (Sunday, 13th January 2013) + +* First Birthday Release +* Added Reversible Migrations +* Removed options parameter from AdapterInterface::hasColumn() + +**0.1.7** (Tuesday, 8th January 2013) + +* Improved documentation on the YAML configuration file +* Removed options parameter from AdapterInterface::dropIndex() + +**0.1.6** (Sunday, 9th December 2012) + +* Added foreign key support +* Removed PEAR support +* Support for auto_increment on custom id columns +* Bugfix for column default value 0 +* Documentation improvements + +**0.1.5** (Sunday, 4th November 2012) + +* Added a test command +* Added transactions for adapters that support it +* Changing the Table API to use pending column methods +* Fixed a bug when defining multiple indexes on a table + +**0.1.4** (Sunday, 21st October 2012) + +* Documentation Improvements + +**0.1.3** (Saturday, 20th October 2012) + +* Fixed broken composer support + +**0.1.2** (Saturday, 20th October 2012) + +* Added composer support +* Now forces migrations to be in CamelCase format +* Now specifies the database name when migrating +* Creates the internal log table using its API instead of raw SQL + +**0.1.1** (Wednesday, 13th June 2012) + +* First point release. Ready for limited production use. + +**0.1.0** (Friday, 13th January 2012) + +* Initial public release. diff --git a/vendor/topthink/think-migration/phinx/CONTRIBUTING.md b/vendor/topthink/think-migration/phinx/CONTRIBUTING.md new file mode 100644 index 0000000..73b54ac --- /dev/null +++ b/vendor/topthink/think-migration/phinx/CONTRIBUTING.md @@ -0,0 +1,66 @@ +# How to contribute to Phinx + +Phinx relies heavily on external contributions in order to make it the best database migration +tool possible. Without the support of our 115+ contributors we wouldn't be where we are today! +We encourage anyone to submit documentation enhancements and code. + +Issues, feature requests and bugs should be submitted using the Github issue tool: +https://github.com/robmorgan/phinx/issues. + +This document briefly outlines the requirements to contribute code to Phinx. + +## Considerations + +Before you submit your pull request take a moment to answer the following questions. + +Answering '**YES**' to all questions will increase the likelihood of your PR being accepted! + +* Have I implemented my feature for as many database adapters as possible? +* Does my new feature improve Phinx's performance or keep it consistent? +* Does my feature fit within the database migration space? +* Is the code entirely my own and free from any commercial licensing? +* Am I happy to release my code under the MIT license? +* Is my code formatted using the [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) coding standard? + +**Note:** We accept bug fixes much faster into our development branch than features. + +## Getting Started + +Great, so you want to contribute. Let's get started: + +1. Start by forking Phinx on GitHub: https://github.com/robmorgan/phinx + +1. Clone your repository to a local directory on your development box. + +1. If you do not have Composer set up already, install it: + + ``` + curl -sS https://getcomposer.org/installer | php + ``` + +1. Change to your Phinx clone directory and pull the necessary dependencies: + + ``` + php composer.phar install + ``` + +1. Copy the `phpunit.xml.dist` template to `phpunit.xml` and change the configuration to suit your environment. If you are not using any particular adapter you can disable it in the `phpunit.xml` file. + +1. Run the unit tests locally to ensure they pass: + + ``` + php vendor/bin/phpunit --config phpunit.xml + ``` + +1. Write the code and unit tests for your bug fix or feature. + +1. Add any relevant documentation. + +1. Run the unit tests again and ensure they pass. + +1. Open a pull request on the Github project page. Ensure the code is being merged into the latest development branch (e.g: `0.5.x-dev`) and not `master`. + +## Documentation + +The Phinx documentation is stored in the **docs** directory using the [RestructedText](http://docutils.sourceforge.net/rst.html) format. All documentation merged to `master` is automatically published to the Phinx documentation site available +at: http://docs.phinx.org. Keep this in mind when submitting your PR, or ask someone to merge the development branch back down to master. diff --git a/vendor/topthink/think-migration/phinx/LICENSE b/vendor/topthink/think-migration/phinx/LICENSE new file mode 100644 index 0000000..26ade90 --- /dev/null +++ b/vendor/topthink/think-migration/phinx/LICENSE @@ -0,0 +1,9 @@ +(The MIT license) + +Copyright (c) 2014 Rob Morgan + +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. \ No newline at end of file diff --git a/vendor/topthink/think-migration/phinx/README.md b/vendor/topthink/think-migration/phinx/README.md new file mode 100644 index 0000000..ed1badc --- /dev/null +++ b/vendor/topthink/think-migration/phinx/README.md @@ -0,0 +1,128 @@ +# [Phinx](https://phinx.org): Simple PHP Database Migrations + +[![Build Status](https://travis-ci.org/robmorgan/phinx.png?branch=master)](https://travis-ci.org/robmorgan/phinx) +[![Build status](https://ci.appveyor.com/api/projects/status/9vag4892hfq6effr)](https://ci.appveyor.com/project/robmorgan/phinx) +[![Code Coverage](https://scrutinizer-ci.com/g/robmorgan/phinx/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/robmorgan/phinx/) +[![Latest Stable Version](https://poser.pugx.org/robmorgan/phinx/version.png)](https://packagist.org/packages/robmorgan/phinx) +[![Total Downloads](https://poser.pugx.org/robmorgan/phinx/d/total.png)](https://packagist.org/packages/robmorgan/phinx) + +Phinx makes it ridiculously easy to manage the database migrations for your PHP app. In less than 5 minutes you can install Phinx and create your first database migration. Phinx is just about migrations without all the bloat of a database ORM system or framework. + +**Check out http://docs.phinx.org for the comprehensive documentation.** + +![phinxterm](https://cloud.githubusercontent.com/assets/178939/3887559/e6b5e524-21f2-11e4-8256-0ba6040725fc.gif) + +### Features + +* Write database migrations using database agnostic PHP code. +* Migrate up and down. +* Migrate on deployment. +* Seed data after database creation. +* Get going in less than 5 minutes. +* Stop worrying about the state of your database. +* Take advantage of SCM features such as branching. +* Integrate with any app. + +### Supported Adapters + +Phinx natively supports the following database adapters: + +* MySQL +* PostgreSQL +* SQLite +* Microsoft SQL Server + +## Install & Run + +### Composer + +The fastest way to install Phinx is to add it to your project using Composer (http://getcomposer.org/). + +1. Install Composer: + + ``` + curl -sS https://getcomposer.org/installer | php + ``` + +1. Require Phinx as a dependency using Composer: + + ``` + php composer.phar require robmorgan/phinx + ``` + +1. Install Phinx: + + ``` + php composer.phar install + ``` + +1. Execute Phinx: + + ``` + php vendor/bin/phinx + ``` + +### As a Phar + +You can also use the Box application to build Phinx as a Phar archive (https://box-project.github.io/box2/). + +1. Clone Phinx from GitHub + + ``` + git clone git://github.com/robmorgan/phinx.git + cd phinx + ``` + +1. Install Composer + + ``` + curl -s https://getcomposer.org/installer | php + ``` + +1. Install the Phinx dependencies + + ``` + php composer.phar install + ``` + +1. Install Box: + + ``` + curl -LSs https://box-project.github.io/box2/installer.php | php + ``` + +1. Create a Phar archive + + ``` + php box.phar build + ``` + +## Documentation + +Check out http://docs.phinx.org for the comprehensive documentation. + +## Contributing + +Please read the [CONTRIBUTING](CONTRIBUTING.md) document. + +## News & Updates + +Follow Rob (@\_rjm\_) on Twitter to stay up to date (http://twitter.com/_rjm_) + +## Misc + +### Version History + +Please read the [CHANGELOG](CHANGELOG.md) document. + +### License + +(The MIT license) + +Copyright (c) 2016 Rob Morgan + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/AdapterFactory.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/AdapterFactory.php new file mode 100644 index 0000000..90674f9 --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/AdapterFactory.php @@ -0,0 +1,181 @@ + + */ +class AdapterFactory +{ + /** + * @var AdapterFactory + */ + protected static $instance; + + /** + * Get the factory singleton instance. + * + * @return AdapterFactory + */ + public static function instance() + { + if (!static::$instance) { + static::$instance = new static(); + } + return static::$instance; + } + + /** + * Class map of database adapters, indexed by PDO::ATTR_DRIVER_NAME. + * + * @var array + */ + protected $adapters = array( + 'mysql' => 'Phinx\Db\Adapter\MysqlAdapter', + 'pgsql' => 'Phinx\Db\Adapter\PostgresAdapter', + 'sqlite' => 'Phinx\Db\Adapter\SQLiteAdapter', + 'sqlsrv' => 'Phinx\Db\Adapter\SqlServerAdapter', + ); + + /** + * Class map of adapters wrappers, indexed by name. + * + * @var array + */ + protected $wrappers = array( + 'prefix' => 'Phinx\Db\Adapter\TablePrefixAdapter', + 'proxy' => 'Phinx\Db\Adapter\ProxyAdapter', + ); + + /** + * Add or replace an adapter with a fully qualified class name. + * + * @throws \RuntimeException + * @param string $name + * @param string $class + * @return $this + */ + public function registerAdapter($name, $class) + { + if (!is_subclass_of($class, 'Phinx\Db\Adapter\AdapterInterface')) { + throw new \RuntimeException(sprintf( + 'Adapter class "%s" must implement Phinx\\Db\\Adapter\\AdapterInterface', + $class + )); + } + $this->adapters[$name] = $class; + return $this; + } + + /** + * Get an adapter class by name. + * + * @throws \RuntimeException + * @param string $name + * @return string + */ + protected function getClass($name) + { + if (empty($this->adapters[$name])) { + throw new \RuntimeException(sprintf( + 'Adapter "%s" has not been registered', + $name + )); + } + return $this->adapters[$name]; + } + + /** + * Get an adapter instance by name. + * + * @param string $name + * @param array $options + * @return AdapterInterface + */ + public function getAdapter($name, array $options) + { + $class = $this->getClass($name); + return new $class($options); + } + + /** + * Add or replace a wrapper with a fully qualified class name. + * + * @throws \RuntimeException + * @param string $name + * @param string $class + * @return $this + */ + public function registerWrapper($name, $class) + { + if (!is_subclass_of($class, 'Phinx\Db\Adapter\WrapperInterface')) { + throw new \RuntimeException(sprintf( + 'Wrapper class "%s" must be implement Phinx\\Db\\Adapter\\WrapperInterface', + $class + )); + } + $this->wrappers[$name] = $class; + return $this; + } + + /** + * Get a wrapper class by name. + * + * @throws \RuntimeException + * @param string $name + * @return string + */ + protected function getWrapperClass($name) + { + if (empty($this->wrappers[$name])) { + throw new \RuntimeException(sprintf( + 'Wrapper "%s" has not been registered', + $name + )); + } + return $this->wrappers[$name]; + } + + /** + * Get a wrapper instance by name. + * + * @param string $name + * @param AdapterInterface $adapter + * @return AdapterInterface + */ + public function getWrapper($name, AdapterInterface $adapter) + { + $class = $this->getWrapperClass($name); + return new $class($adapter); + } +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/AdapterInterface.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/AdapterInterface.php new file mode 100644 index 0000000..f2e9fe1 --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/AdapterInterface.php @@ -0,0 +1,510 @@ + + */ +interface AdapterInterface +{ + const PHINX_TYPE_STRING = 'string'; + const PHINX_TYPE_CHAR = 'char'; + const PHINX_TYPE_TEXT = 'text'; + const PHINX_TYPE_INTEGER = 'integer'; + const PHINX_TYPE_BIG_INTEGER = 'biginteger'; + const PHINX_TYPE_FLOAT = 'float'; + const PHINX_TYPE_DECIMAL = 'decimal'; + const PHINX_TYPE_DATETIME = 'datetime'; + const PHINX_TYPE_TIMESTAMP = 'timestamp'; + const PHINX_TYPE_TIME = 'time'; + const PHINX_TYPE_DATE = 'date'; + const PHINX_TYPE_BINARY = 'binary'; + const PHINX_TYPE_VARBINARY = 'varbinary'; + const PHINX_TYPE_BLOB = 'blob'; + const PHINX_TYPE_BOOLEAN = 'boolean'; + const PHINX_TYPE_JSON = 'json'; + const PHINX_TYPE_JSONB = 'jsonb'; + const PHINX_TYPE_UUID = 'uuid'; + const PHINX_TYPE_FILESTREAM = 'filestream'; + + // Geospatial database types + const PHINX_TYPE_GEOMETRY = 'geometry'; + const PHINX_TYPE_POINT = 'point'; + const PHINX_TYPE_LINESTRING = 'linestring'; + const PHINX_TYPE_POLYGON = 'polygon'; + + // only for mysql so far + const PHINX_TYPE_ENUM = 'enum'; + const PHINX_TYPE_SET = 'set'; + + /** + * Get all migrated version numbers. + * + * @return array + */ + public function getVersions(); + + /** + * Get all migration log entries, indexed by version number. + * + * @return array + */ + public function getVersionLog(); + + /** + * Set adapter configuration options. + * + * @param array $options + * @return AdapterInterface + */ + public function setOptions(array $options); + + /** + * Get all adapter options. + * + * @return array + */ + public function getOptions(); + + /** + * Check if an option has been set. + * + * @param string $name + * @return boolean + */ + public function hasOption($name); + + /** + * Get a single adapter option, or null if the option does not exist. + * + * @param string $name + * @return mixed + */ + public function getOption($name); + + /** + * Sets the console input. + * + * @param InputInterface $input Input + * @return AdapterInterface + */ + public function setInput(InputInterface $input); + + /** + * Gets the console input. + * + * @return InputInterface + */ + public function getInput(); + + /** + * Sets the console output. + * + * @param OutputInterface $output Output + * @return AdapterInterface + */ + public function setOutput(OutputInterface $output); + + /** + * Gets the console output. + * + * @return OutputInterface + */ + public function getOutput(); + + /** + * Records a migration being run. + * + * @param MigrationInterface $migration Migration + * @param string $direction Direction + * @param int $startTime Start Time + * @param int $endTime End Time + * @return AdapterInterface + */ + public function migrated(MigrationInterface $migration, $direction, $startTime, $endTime); + + /** + * Toggle a migration breakpoint. + * + * @param MigrationInterface $migration + * + * @return AdapterInterface + */ + public function toggleBreakpoint(MigrationInterface $migration); + + /** + * Reset all migration breakpoints. + * + * @return int The number of breakpoints reset + */ + public function resetAllBreakpoints(); + + /** + * Does the schema table exist? + * + * @deprecated use hasTable instead. + * @return boolean + */ + public function hasSchemaTable(); + + /** + * Creates the schema table. + * + * @return void + */ + public function createSchemaTable(); + + /** + * Returns the adapter type. + * + * @return string + */ + public function getAdapterType(); + + /** + * Initializes the database connection. + * + * @throws \RuntimeException When the requested database driver is not installed. + * @return void + */ + public function connect(); + + /** + * Closes the database connection. + * + * @return void + */ + public function disconnect(); + + /** + * Does the adapter support transactions? + * + * @return boolean + */ + public function hasTransactions(); + + /** + * Begin a transaction. + * + * @return void + */ + public function beginTransaction(); + + /** + * Commit a transaction. + * + * @return void + */ + public function commitTransaction(); + + /** + * Rollback a transaction. + * + * @return void + */ + public function rollbackTransaction(); + + /** + * Executes a SQL statement and returns the number of affected rows. + * + * @param string $sql SQL + * @return int + */ + public function execute($sql); + + /** + * Executes a SQL statement and returns the result as an array. + * + * @param string $sql SQL + * @return array + */ + public function query($sql); + + /** + * Executes a query and returns only one row as an array. + * + * @param string $sql SQL + * @return array + */ + public function fetchRow($sql); + + /** + * Executes a query and returns an array of rows. + * + * @param string $sql SQL + * @return array + */ + public function fetchAll($sql); + + /** + * Inserts data into a table. + * + * @param Table $table where to insert data + * @param array $row + * @return void + */ + public function insert(Table $table, $row); + + /** + * Quotes a table name for use in a query. + * + * @param string $tableName Table Name + * @return string + */ + public function quoteTableName($tableName); + + /** + * Quotes a column name for use in a query. + * + * @param string $columnName Table Name + * @return string + */ + public function quoteColumnName($columnName); + + /** + * Checks to see if a table exists. + * + * @param string $tableName Table Name + * @return boolean + */ + public function hasTable($tableName); + + /** + * Creates the specified database table. + * + * @param Table $table Table + * @return void + */ + public function createTable(Table $table); + + /** + * Renames the specified database table. + * + * @param string $tableName Table Name + * @param string $newName New Name + * @return void + */ + public function renameTable($tableName, $newName); + + /** + * Drops the specified database table. + * + * @param string $tableName Table Name + * @return void + */ + public function dropTable($tableName); + + /** + * Returns table columns + * + * @param string $tableName Table Name + * @return Column[] + */ + public function getColumns($tableName); + + /** + * Checks to see if a column exists. + * + * @param string $tableName Table Name + * @param string $columnName Column Name + * @return boolean + */ + public function hasColumn($tableName, $columnName); + + /** + * Adds the specified column to a database table. + * + * @param Table $table Table + * @param Column $column Column + * @return void + */ + public function addColumn(Table $table, Column $column); + + /** + * Renames the specified column. + * + * @param string $tableName Table Name + * @param string $columnName Column Name + * @param string $newColumnName New Column Name + * @return void + */ + public function renameColumn($tableName, $columnName, $newColumnName); + + /** + * Change a table column type. + * + * @param string $tableName Table Name + * @param string $columnName Column Name + * @param Column $newColumn New Column + * @return Table + */ + public function changeColumn($tableName, $columnName, Column $newColumn); + + /** + * Drops the specified column. + * + * @param string $tableName Table Name + * @param string $columnName Column Name + * @return void + */ + public function dropColumn($tableName, $columnName); + + /** + * Checks to see if an index exists. + * + * @param string $tableName Table Name + * @param mixed $columns Column(s) + * @return boolean + */ + public function hasIndex($tableName, $columns); + + /** + * Checks to see if an index specified by name exists. + * + * @param string $tableName Table Name + * @param string $indexName + * @return boolean + */ + public function hasIndexByName($tableName, $indexName); + + /** + * Adds the specified index to a database table. + * + * @param Table $table Table + * @param Index $index Index + * @return void + */ + public function addIndex(Table $table, Index $index); + + /** + * Drops the specified index from a database table. + * + * @param string $tableName + * @param mixed $columns Column(s) + * @return void + */ + public function dropIndex($tableName, $columns); + + /** + * Drops the index specified by name from a database table. + * + * @param string $tableName + * @param string $indexName + * @return void + */ + public function dropIndexByName($tableName, $indexName); + + /** + * Checks to see if a foreign key exists. + * + * @param string $tableName + * @param string[] $columns Column(s) + * @param string $constraint Constraint name + * @return boolean + */ + public function hasForeignKey($tableName, $columns, $constraint = null); + + /** + * Adds the specified foreign key to a database table. + * + * @param Table $table + * @param ForeignKey $foreignKey + * @return void + */ + public function addForeignKey(Table $table, ForeignKey $foreignKey); + + /** + * Drops the specified foreign key from a database table. + * + * @param string $tableName + * @param string[] $columns Column(s) + * @param string $constraint Constraint name + * @return void + */ + public function dropForeignKey($tableName, $columns, $constraint = null); + + /** + * Returns an array of the supported Phinx column types. + * + * @return array + */ + public function getColumnTypes(); + + /** + * Checks that the given column is of a supported type. + * + * @param Column $column + * @return boolean + */ + public function isValidColumnType(Column $column); + + /** + * Converts the Phinx logical type to the adapter's SQL type. + * + * @param string $type + * @param integer $limit + * @return string + */ + public function getSqlType($type, $limit = null); + + /** + * Creates a new database. + * + * @param string $name Database Name + * @param array $options Options + * @return void + */ + public function createDatabase($name, $options = array()); + + /** + * Checks to see if a database exists. + * + * @param string $name Database Name + * @return boolean + */ + public function hasDatabase($name); + + /** + * Drops the specified database. + * + * @param string $name Database Name + * @return void + */ + public function dropDatabase($name); +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/AdapterWrapper.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/AdapterWrapper.php new file mode 100644 index 0000000..ae5ec39 --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/AdapterWrapper.php @@ -0,0 +1,507 @@ + + */ +abstract class AdapterWrapper implements AdapterInterface, WrapperInterface +{ + /** + * @var AdapterInterface + */ + protected $adapter; + + /** + * {@inheritdoc} + */ + public function __construct(AdapterInterface $adapter) + { + $this->setAdapter($adapter); + } + + /** + * {@inheritdoc} + */ + public function setAdapter(AdapterInterface $adapter) + { + $this->adapter = $adapter; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getAdapter() + { + return $this->adapter; + } + + /** + * {@inheritdoc} + */ + public function setOptions(array $options) + { + $this->adapter->setOptions($options); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getOptions() + { + return $this->adapter->getOptions(); + } + + /** + * {@inheritdoc} + */ + public function hasOption($name) + { + return $this->adapter->hasOption($name); + } + + /** + * {@inheritdoc} + */ + public function getOption($name) + { + return $this->adapter->getOption($name); + } + + /** + * {@inheritdoc} + */ + public function setInput(InputInterface $input) + { + $this->adapter->setInput($input); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getInput() + { + return $this->adapter->getInput(); + } + + /** + * {@inheritdoc} + */ + public function setOutput(OutputInterface $output) + { + $this->adapter->setOutput($output); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getOutput() + { + return $this->adapter->getOutput(); + } + + /** + * {@inheritdoc} + */ + public function connect() + { + return $this->getAdapter()->connect(); + } + + /** + * {@inheritdoc} + */ + public function disconnect() + { + return $this->getAdapter()->disconnect(); + } + + /** + * {@inheritdoc} + */ + public function execute($sql) + { + return $this->getAdapter()->execute($sql); + } + + /** + * {@inheritdoc} + */ + public function query($sql) + { + return $this->getAdapter()->query($sql); + } + + /** + * {@inheritdoc} + */ + public function insert(Table $table, $row) + { + return $this->getAdapter()->insert($table, $row); + } + + /** + * {@inheritdoc} + */ + public function fetchRow($sql) + { + return $this->getAdapter()->fetchRow($sql); + } + + /** + * {@inheritdoc} + */ + public function fetchAll($sql) + { + return $this->getAdapter()->fetchAll($sql); + } + + /** + * {@inheritdoc} + */ + public function getVersions() + { + return $this->getAdapter()->getVersions(); + } + + /** + * {@inheritdoc} + */ + public function getVersionLog() + { + return $this->getAdapter()->getVersionLog(); + } + + /** + * {@inheritdoc} + */ + public function migrated(MigrationInterface $migration, $direction, $startTime, $endTime) + { + $this->getAdapter()->migrated($migration, $direction, $startTime, $endTime); + return $this; + } + + /** + * @inheritDoc + */ + public function toggleBreakpoint(MigrationInterface $migration) + { + $this->getAdapter()->toggleBreakpoint($migration); + return $this; + } + + /** + * @inheritDoc + */ + public function resetAllBreakpoints() + { + return $this->getAdapter()->resetAllBreakpoints(); + } + + /** + * {@inheritdoc} + */ + public function hasSchemaTable() + { + return $this->getAdapter()->hasSchemaTable(); + } + + /** + * {@inheritdoc} + */ + public function createSchemaTable() + { + return $this->getAdapter()->createSchemaTable(); + } + + /** + * {@inheritdoc} + */ + public function getColumnTypes() + { + return $this->getAdapter()->getColumnTypes(); + } + + /** + * {@inheritdoc} + */ + public function isValidColumnType(Column $column) + { + return $this->getAdapter()->isValidColumnType($column); + } + + /** + * {@inheritdoc} + */ + public function hasTransactions() + { + return $this->getAdapter()->hasTransactions(); + } + + /** + * {@inheritdoc} + */ + public function beginTransaction() + { + return $this->getAdapter()->beginTransaction(); + } + + /** + * {@inheritdoc} + */ + public function commitTransaction() + { + return $this->getAdapter()->commitTransaction(); + } + + /** + * {@inheritdoc} + */ + public function rollbackTransaction() + { + return $this->getAdapter()->rollbackTransaction(); + } + + /** + * {@inheritdoc} + */ + public function quoteTableName($tableName) + { + return $this->getAdapter()->quoteTableName($tableName); + } + + /** + * {@inheritdoc} + */ + public function quoteColumnName($columnName) + { + return $this->getAdapter()->quoteColumnName($columnName); + } + + /** + * {@inheritdoc} + */ + public function hasTable($tableName) + { + return $this->getAdapter()->hasTable($tableName); + } + + /** + * {@inheritdoc} + */ + public function createTable(Table $table) + { + return $this->getAdapter()->createTable($table); + } + + /** + * {@inheritdoc} + */ + public function renameTable($tableName, $newTableName) + { + return $this->getAdapter()->renameTable($tableName, $newTableName); + } + + /** + * {@inheritdoc} + */ + public function dropTable($tableName) + { + return $this->getAdapter()->dropTable($tableName); + } + + /** + * {@inheritdoc} + */ + public function getColumns($tableName) + { + return $this->getAdapter()->getColumns($tableName); + } + + /** + * {@inheritdoc} + */ + public function hasColumn($tableName, $columnName) + { + return $this->getAdapter()->hasColumn($tableName, $columnName); + } + + /** + * {@inheritdoc} + */ + public function addColumn(Table $table, Column $column) + { + return $this->getAdapter()->addColumn($table, $column); + } + + /** + * {@inheritdoc} + */ + public function renameColumn($tableName, $columnName, $newColumnName) + { + return $this->getAdapter()->renameColumn($tableName, $columnName, $newColumnName); + } + + /** + * {@inheritdoc} + */ + public function changeColumn($tableName, $columnName, Column $newColumn) + { + return $this->getAdapter()->changeColumn($tableName, $columnName, $newColumn); + } + + /** + * {@inheritdoc} + */ + public function dropColumn($tableName, $columnName) + { + return $this->getAdapter()->dropColumn($tableName, $columnName); + } + + /** + * {@inheritdoc} + */ + public function hasIndex($tableName, $columns) + { + return $this->getAdapter()->hasIndex($tableName, $columns); + } + + /** + * {@inheritdoc} + */ + public function hasIndexByName($tableName, $indexName) + { + return $this->getAdapter()->hasIndexByName($tableName, $indexName); + } + + /** + * {@inheritdoc} + */ + public function addIndex(Table $table, Index $index) + { + return $this->getAdapter()->addIndex($table, $index); + } + + /** + * {@inheritdoc} + */ + public function dropIndex($tableName, $columns, $options = array()) + { + return $this->getAdapter()->dropIndex($tableName, $columns, $options); + } + + /** + * {@inheritdoc} + */ + public function dropIndexByName($tableName, $indexName) + { + return $this->getAdapter()->dropIndexByName($tableName, $indexName); + } + + /** + * {@inheritdoc} + */ + public function hasForeignKey($tableName, $columns, $constraint = null) + { + return $this->getAdapter()->hasForeignKey($tableName, $columns, $constraint); + } + + /** + * {@inheritdoc} + */ + public function addForeignKey(Table $table, ForeignKey $foreignKey) + { + return $this->getAdapter()->addForeignKey($table, $foreignKey); + } + + /** + * {@inheritdoc} + */ + public function dropForeignKey($tableName, $columns, $constraint = null) + { + return $this->getAdapter()->dropForeignKey($tableName, $columns, $constraint); + } + + /** + * {@inheritdoc} + */ + public function getSqlType($type, $limit = null) + { + return $this->getAdapter()->getSqlType($type, $limit); + } + + /** + * {@inheritdoc} + */ + public function createDatabase($name, $options = array()) + { + return $this->getAdapter()->createDatabase($name, $options); + } + + /** + * {@inheritdoc} + */ + public function hasDatabase($name) + { + return $this->getAdapter()->hasDatabase($name); + } + + /** + * {@inheritdoc} + */ + public function dropDatabase($name) + { + return $this->getAdapter()->dropDatabase($name); + } + + /** + * @inheritDoc + */ + public function castToBool($value) + { + return $this->getAdapter()->castToBool($value); + } +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/MysqlAdapter.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/MysqlAdapter.php new file mode 100644 index 0000000..f882fb5 --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/MysqlAdapter.php @@ -0,0 +1,1145 @@ + + */ +class MysqlAdapter extends PdoAdapter implements AdapterInterface +{ + + protected $signedColumnTypes = array('integer' => true, 'biginteger' => true, 'float' => true, 'decimal' => true, 'boolean' => true); + + const TEXT_TINY = 255; + const TEXT_SMALL = 255; /* deprecated, alias of TEXT_TINY */ + const TEXT_REGULAR = 65535; + const TEXT_MEDIUM = 16777215; + const TEXT_LONG = 4294967295; + + // According to https://dev.mysql.com/doc/refman/5.0/en/blob.html BLOB sizes are the same as TEXT + const BLOB_TINY = 255; + const BLOB_SMALL = 255; /* deprecated, alias of BLOB_TINY */ + const BLOB_REGULAR = 65535; + const BLOB_MEDIUM = 16777215; + const BLOB_LONG = 4294967295; + + const INT_TINY = 255; + const INT_SMALL = 65535; + const INT_MEDIUM = 16777215; + const INT_REGULAR = 4294967295; + const INT_BIG = 18446744073709551615; + + const TYPE_YEAR = 'year'; + + /** + * {@inheritdoc} + */ + public function connect() + { + if (null === $this->connection) { + if (!class_exists('PDO') || !in_array('mysql', \PDO::getAvailableDrivers(), true)) { + // @codeCoverageIgnoreStart + throw new \RuntimeException('You need to enable the PDO_Mysql extension for Phinx to run properly.'); + // @codeCoverageIgnoreEnd + } + + $db = null; + $options = $this->getOptions(); + + $dsn = 'mysql:'; + + if (!empty($options['unix_socket'])) { + // use socket connection + $dsn .= 'unix_socket=' . $options['unix_socket']; + } else { + // use network connection + $dsn .= 'host=' . $options['host']; + if (!empty($options['port'])) { + $dsn .= ';port=' . $options['port']; + } + } + + $dsn .= ';dbname=' . $options['name']; + + // charset support + if (!empty($options['charset'])) { + $dsn .= ';charset=' . $options['charset']; + } + + $driverOptions = array(\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION); + + // support arbitrary \PDO::MYSQL_ATTR_* driver options and pass them to PDO + // http://php.net/manual/en/ref.pdo-mysql.php#pdo-mysql.constants + foreach ($options as $key => $option) { + if (strpos($key, 'mysql_attr_') === 0) { + $driverOptions[constant('\PDO::' . strtoupper($key))] = $option; + } + } + + try { + $db = new \PDO($dsn, $options['user'], $options['pass'], $driverOptions); + } catch (\PDOException $exception) { + throw new \InvalidArgumentException(sprintf( + 'There was a problem connecting to the database: %s', + $exception->getMessage() + )); + } + + $this->setConnection($db); + } + } + + /** + * {@inheritdoc} + */ + public function disconnect() + { + $this->connection = null; + } + + /** + * {@inheritdoc} + */ + public function hasTransactions() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function beginTransaction() + { + $this->execute('START TRANSACTION'); + } + + /** + * {@inheritdoc} + */ + public function commitTransaction() + { + $this->execute('COMMIT'); + } + + /** + * {@inheritdoc} + */ + public function rollbackTransaction() + { + $this->execute('ROLLBACK'); + } + + /** + * {@inheritdoc} + */ + public function quoteTableName($tableName) + { + return str_replace('.', '`.`', $this->quoteColumnName($tableName)); + } + + /** + * {@inheritdoc} + */ + public function quoteColumnName($columnName) + { + return '`' . str_replace('`', '``', $columnName) . '`'; + } + + /** + * {@inheritdoc} + */ + public function hasTable($tableName) + { + $options = $this->getOptions(); + + $exists = $this->fetchRow(sprintf( + "SELECT TABLE_NAME + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s'", + $options['name'], $tableName + )); + + return !empty($exists); + } + + /** + * {@inheritdoc} + */ + public function createTable(Table $table) + { + $this->startCommandTimer(); + + // This method is based on the MySQL docs here: http://dev.mysql.com/doc/refman/5.1/en/create-index.html + $defaultOptions = array( + 'engine' => 'InnoDB', + 'collation' => 'utf8_general_ci' + ); + $options = array_merge($defaultOptions, $table->getOptions()); + + // Add the default primary key + $columns = $table->getPendingColumns(); + + if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) { + $options['id'] = 'id'; + } + + if (isset($options['id']) && is_string($options['id'])) { + // Handle id => "field_name" to support AUTO_INCREMENT + $column = new Column(); + $column->setName($options['id']) + ->setType('integer') + ->setSigned(isset($options['signed']) ? $options['signed'] : true) + ->setIdentity(true); + + array_unshift($columns, $column); + $options['primary_key'] = $options['id']; + } + + // TODO - process table options like collation etc + + // process table engine (default to InnoDB) + $optionsStr = 'ENGINE = InnoDB'; + if (isset($options['engine'])) { + $optionsStr = sprintf('ENGINE = %s', $options['engine']); + } + + // process table collation + if (isset($options['collation'])) { + $charset = explode('_', $options['collation']); + $optionsStr .= sprintf(' CHARACTER SET %s', $charset[0]); + $optionsStr .= sprintf(' COLLATE %s', $options['collation']); + } + + // set the table comment + if (isset($options['comment'])) { + $optionsStr .= sprintf(" COMMENT=%s ", $this->getConnection()->quote($options['comment'])); + } + + $sql = 'CREATE TABLE '; + $sql .= $this->quoteTableName($table->getName()) . ' ('; + foreach ($columns as $column) { + $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', '; + } + + // set the primary key(s) + if (isset($options['primary_key'])) { + $sql = rtrim($sql); + $sql .= ' PRIMARY KEY ('; + if (is_string($options['primary_key'])) { // handle primary_key => 'id' + $sql .= $this->quoteColumnName($options['primary_key']); + } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id') + // PHP 5.4 will allow access of $this, so we can call quoteColumnName() directly in the + // anonymous function, but for now just hard-code the adapter quotes + $sql .= implode( + ',', + array_map( + function ($v) { + return '`' . $v . '`'; + }, + $options['primary_key'] + ) + ); + } + $sql .= ')'; + } else { + $sql = substr(rtrim($sql), 0, -1); // no primary keys + } + + // set the indexes + $indexes = $table->getIndexes(); + if (!empty($indexes)) { + foreach ($indexes as $index) { + $sql .= ', ' . $this->getIndexSqlDefinition($index); + } + } + + // set the foreign keys + $foreignKeys = $table->getForeignKeys(); + if (!empty($foreignKeys)) { + foreach ($foreignKeys as $foreignKey) { + $sql .= ', ' . $this->getForeignKeySqlDefinition($foreignKey); + } + } + + $sql .= ') ' . $optionsStr; + $sql = rtrim($sql) . ';'; + + // execute the sql + $this->writeCommand('createTable', array($table->getName())); + $this->execute($sql); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function renameTable($tableName, $newTableName) + { + $this->startCommandTimer(); + $this->writeCommand('renameTable', array($tableName, $newTableName)); + $this->execute(sprintf('RENAME TABLE %s TO %s', $this->quoteTableName($tableName), $this->quoteTableName($newTableName))); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropTable($tableName) + { + $this->startCommandTimer(); + $this->writeCommand('dropTable', array($tableName)); + $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tableName))); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function getColumns($tableName) + { + $columns = array(); + $rows = $this->fetchAll(sprintf('SHOW COLUMNS FROM %s', $this->quoteTableName($tableName))); + foreach ($rows as $columnInfo) { + + $phinxType = $this->getPhinxType($columnInfo['Type']); + + $column = new Column(); + $column->setName($columnInfo['Field']) + ->setNull($columnInfo['Null'] !== 'NO') + ->setDefault($columnInfo['Default']) + ->setType($phinxType['name']) + ->setLimit($phinxType['limit']); + + if ($columnInfo['Extra'] === 'auto_increment') { + $column->setIdentity(true); + } + + $columns[] = $column; + } + + return $columns; + } + + /** + * {@inheritdoc} + */ + public function hasColumn($tableName, $columnName) + { + $rows = $this->fetchAll(sprintf('SHOW COLUMNS FROM %s', $this->quoteTableName($tableName))); + foreach ($rows as $column) { + if (strcasecmp($column['Field'], $columnName) === 0) { + return true; + } + } + + return false; + } + + /** + * Get the defintion for a `DEFAULT` statement. + * + * @param mixed $default + * @return string + */ + protected function getDefaultValueDefinition($default) + { + if (is_string($default) && 'CURRENT_TIMESTAMP' !== $default) { + $default = $this->getConnection()->quote($default); + } elseif (is_bool($default)) { + $default = $this->castToBool($default); + } + return isset($default) ? ' DEFAULT ' . $default : ''; + } + + /** + * {@inheritdoc} + */ + public function addColumn(Table $table, Column $column) + { + $this->startCommandTimer(); + $sql = sprintf( + 'ALTER TABLE %s ADD %s %s', + $this->quoteTableName($table->getName()), + $this->quoteColumnName($column->getName()), + $this->getColumnSqlDefinition($column) + ); + + if ($column->getAfter()) { + $sql .= ' AFTER ' . $this->quoteColumnName($column->getAfter()); + } + + $this->writeCommand('addColumn', array($table->getName(), $column->getName(), $column->getType())); + $this->execute($sql); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function renameColumn($tableName, $columnName, $newColumnName) + { + $this->startCommandTimer(); + $rows = $this->fetchAll(sprintf('DESCRIBE %s', $this->quoteTableName($tableName))); + foreach ($rows as $row) { + if (strcasecmp($row['Field'], $columnName) === 0) { + $null = ($row['Null'] == 'NO') ? 'NOT NULL' : 'NULL'; + $extra = ' ' . strtoupper($row['Extra']); + if (!is_null($row['Default'])) { + $extra .= $this->getDefaultValueDefinition($row['Default']); + } + $definition = $row['Type'] . ' ' . $null . $extra; + + $this->writeCommand('renameColumn', array($tableName, $columnName, $newColumnName)); + $this->execute( + sprintf( + 'ALTER TABLE %s CHANGE COLUMN %s %s %s', + $this->quoteTableName($tableName), + $this->quoteColumnName($columnName), + $this->quoteColumnName($newColumnName), + $definition + ) + ); + $this->endCommandTimer(); + return; + } + } + + throw new \InvalidArgumentException(sprintf( + 'The specified column doesn\'t exist: ' + . $columnName + )); + } + + /** + * {@inheritdoc} + */ + public function changeColumn($tableName, $columnName, Column $newColumn) + { + $this->startCommandTimer(); + $this->writeCommand('changeColumn', array($tableName, $columnName, $newColumn->getType())); + $after = $newColumn->getAfter() ? ' AFTER ' . $this->quoteColumnName($newColumn->getAfter()) : ''; + $this->execute( + sprintf( + 'ALTER TABLE %s CHANGE %s %s %s%s', + $this->quoteTableName($tableName), + $this->quoteColumnName($columnName), + $this->quoteColumnName($newColumn->getName()), + $this->getColumnSqlDefinition($newColumn), + $after + ) + ); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropColumn($tableName, $columnName) + { + $this->startCommandTimer(); + $this->writeCommand('dropColumn', array($tableName, $columnName)); + $this->execute( + sprintf( + 'ALTER TABLE %s DROP COLUMN %s', + $this->quoteTableName($tableName), + $this->quoteColumnName($columnName) + ) + ); + $this->endCommandTimer(); + } + + /** + * Get an array of indexes from a particular table. + * + * @param string $tableName Table Name + * @return array + */ + protected function getIndexes($tableName) + { + $indexes = array(); + $rows = $this->fetchAll(sprintf('SHOW INDEXES FROM %s', $this->quoteTableName($tableName))); + foreach ($rows as $row) { + if (!isset($indexes[$row['Key_name']])) { + $indexes[$row['Key_name']] = array('columns' => array()); + } + $indexes[$row['Key_name']]['columns'][] = strtolower($row['Column_name']); + } + return $indexes; + } + + /** + * {@inheritdoc} + */ + public function hasIndex($tableName, $columns) + { + if (is_string($columns)) { + $columns = array($columns); // str to array + } + + $columns = array_map('strtolower', $columns); + $indexes = $this->getIndexes($tableName); + + foreach ($indexes as $index) { + if ($columns == $index['columns']) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function hasIndexByName($tableName, $indexName) + { + $indexes = $this->getIndexes($tableName); + + foreach ($indexes as $name => $index) { + if ($name === $indexName) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function addIndex(Table $table, Index $index) + { + $this->startCommandTimer(); + $this->writeCommand('addIndex', array($table->getName(), $index->getColumns())); + $this->execute( + sprintf( + 'ALTER TABLE %s ADD %s', + $this->quoteTableName($table->getName()), + $this->getIndexSqlDefinition($index) + ) + ); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropIndex($tableName, $columns) + { + $this->startCommandTimer(); + if (is_string($columns)) { + $columns = array($columns); // str to array + } + + $this->writeCommand('dropIndex', array($tableName, $columns)); + $indexes = $this->getIndexes($tableName); + $columns = array_map('strtolower', $columns); + + foreach ($indexes as $indexName => $index) { + if ($columns == $index['columns']) { + $this->execute( + sprintf( + 'ALTER TABLE %s DROP INDEX %s', + $this->quoteTableName($tableName), + $this->quoteColumnName($indexName) + ) + ); + $this->endCommandTimer(); + return; + } + } + } + + /** + * {@inheritdoc} + */ + public function dropIndexByName($tableName, $indexName) + { + $this->startCommandTimer(); + $this->writeCommand('dropIndexByName', array($tableName, $indexName)); + $indexes = $this->getIndexes($tableName); + + foreach ($indexes as $name => $index) { + //$a = array_diff($columns, $index['columns']); + if ($name === $indexName) { + $this->execute( + sprintf( + 'ALTER TABLE %s DROP INDEX %s', + $this->quoteTableName($tableName), + $this->quoteColumnName($indexName) + ) + ); + $this->endCommandTimer(); + return; + } + } + } + + /** + * {@inheritdoc} + */ + public function hasForeignKey($tableName, $columns, $constraint = null) + { + if (is_string($columns)) { + $columns = array($columns); // str to array + } + $foreignKeys = $this->getForeignKeys($tableName); + if ($constraint) { + if (isset($foreignKeys[$constraint])) { + return !empty($foreignKeys[$constraint]); + } + return false; + } else { + foreach ($foreignKeys as $key) { + $a = array_diff($columns, $key['columns']); + if ($columns == $key['columns']) { + return true; + } + } + return false; + } + } + + /** + * Get an array of foreign keys from a particular table. + * + * @param string $tableName Table Name + * @return array + */ + protected function getForeignKeys($tableName) + { + $foreignKeys = array(); + $rows = $this->fetchAll(sprintf( + "SELECT + CONSTRAINT_NAME, + TABLE_NAME, + COLUMN_NAME, + REFERENCED_TABLE_NAME, + REFERENCED_COLUMN_NAME + FROM information_schema.KEY_COLUMN_USAGE + WHERE REFERENCED_TABLE_SCHEMA = DATABASE() + AND REFERENCED_TABLE_NAME IS NOT NULL + AND TABLE_NAME = '%s' + ORDER BY POSITION_IN_UNIQUE_CONSTRAINT", + $tableName + )); + foreach ($rows as $row) { + $foreignKeys[$row['CONSTRAINT_NAME']]['table'] = $row['TABLE_NAME']; + $foreignKeys[$row['CONSTRAINT_NAME']]['columns'][] = $row['COLUMN_NAME']; + $foreignKeys[$row['CONSTRAINT_NAME']]['referenced_table'] = $row['REFERENCED_TABLE_NAME']; + $foreignKeys[$row['CONSTRAINT_NAME']]['referenced_columns'][] = $row['REFERENCED_COLUMN_NAME']; + } + return $foreignKeys; + } + + /** + * {@inheritdoc} + */ + public function addForeignKey(Table $table, ForeignKey $foreignKey) + { + $this->startCommandTimer(); + $this->writeCommand('addForeignKey', array($table->getName(), $foreignKey->getColumns())); + $this->execute( + sprintf( + 'ALTER TABLE %s ADD %s', + $this->quoteTableName($table->getName()), + $this->getForeignKeySqlDefinition($foreignKey) + ) + ); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropForeignKey($tableName, $columns, $constraint = null) + { + $this->startCommandTimer(); + if (is_string($columns)) { + $columns = array($columns); // str to array + } + + $this->writeCommand('dropForeignKey', array($tableName, $columns)); + + if ($constraint) { + $this->execute( + sprintf( + 'ALTER TABLE %s DROP FOREIGN KEY %s', + $this->quoteTableName($tableName), + $constraint + ) + ); + $this->endCommandTimer(); + return; + } else { + foreach ($columns as $column) { + $rows = $this->fetchAll(sprintf( + "SELECT + CONSTRAINT_NAME + FROM information_schema.KEY_COLUMN_USAGE + WHERE REFERENCED_TABLE_SCHEMA = DATABASE() + AND REFERENCED_TABLE_NAME IS NOT NULL + AND TABLE_NAME = '%s' + AND COLUMN_NAME = '%s' + ORDER BY POSITION_IN_UNIQUE_CONSTRAINT", + $tableName, + $column + )); + foreach ($rows as $row) { + $this->dropForeignKey($tableName, $columns, $row['CONSTRAINT_NAME']); + } + } + } + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function getSqlType($type, $limit = null) + { + switch ($type) { + case static::PHINX_TYPE_STRING: + return array('name' => 'varchar', 'limit' => $limit ? $limit : 255); + break; + case static::PHINX_TYPE_CHAR: + return array('name' => 'char', 'limit' => $limit ? $limit : 255); + break; + case static::PHINX_TYPE_TEXT: + if ($limit) { + $sizes = array( + // Order matters! Size must always be tested from longest to shortest! + 'longtext' => static::TEXT_LONG, + 'mediumtext' => static::TEXT_MEDIUM, + 'text' => static::TEXT_REGULAR, + 'tinytext' => static::TEXT_SMALL, + ); + foreach ($sizes as $name => $length) { + if ($limit >= $length) { + return array('name' => $name); + } + } + } + return array('name' => 'text'); + break; + case static::PHINX_TYPE_BINARY: + return array('name' => 'binary', 'limit' => $limit ? $limit : 255); + break; + case static::PHINX_TYPE_VARBINARY: + return array('name' => 'varbinary', 'limit' => $limit ? $limit : 255); + break; + case static::PHINX_TYPE_BLOB: + if ($limit) { + $sizes = array( + // Order matters! Size must always be tested from longest to shortest! + 'longblob' => static::BLOB_LONG, + 'mediumblob' => static::BLOB_MEDIUM, + 'blob' => static::BLOB_REGULAR, + 'tinyblob' => static::BLOB_SMALL, + ); + foreach ($sizes as $name => $length) { + if ($limit >= $length) { + return array('name' => $name); + } + } + } + return array('name' => 'blob'); + break; + case static::PHINX_TYPE_INTEGER: + if ($limit && $limit >= static::INT_TINY) { + $sizes = array( + // Order matters! Size must always be tested from longest to shortest! + 'bigint' => static::INT_BIG, + 'int' => static::INT_REGULAR, + 'mediumint' => static::INT_MEDIUM, + 'smallint' => static::INT_SMALL, + 'tinyint' => static::INT_TINY, + ); + $limits = array( + 'int' => 11, + 'bigint' => 20, + ); + foreach ($sizes as $name => $length) { + if ($limit >= $length) { + $def = array('name' => $name); + if (isset($limits[$name])) { + $def['limit'] = $limits[$name]; + } + return $def; + } + } + } elseif (!$limit) { + $limit = 11; + } + return array('name' => 'int', 'limit' => $limit); + break; + case static::PHINX_TYPE_BIG_INTEGER: + return array('name' => 'bigint', 'limit' => 20); + break; + case static::PHINX_TYPE_FLOAT: + return array('name' => 'float'); + break; + case static::PHINX_TYPE_DECIMAL: + return array('name' => 'decimal'); + break; + case static::PHINX_TYPE_DATETIME: + return array('name' => 'datetime'); + break; + case static::PHINX_TYPE_TIMESTAMP: + return array('name' => 'timestamp'); + break; + case static::PHINX_TYPE_TIME: + return array('name' => 'time'); + break; + case static::PHINX_TYPE_DATE: + return array('name' => 'date'); + break; + case static::PHINX_TYPE_BOOLEAN: + return array('name' => 'tinyint', 'limit' => 1); + break; + case static::PHINX_TYPE_UUID: + return array('name' => 'char', 'limit' => 36); + // Geospatial database types + case static::PHINX_TYPE_GEOMETRY: + case static::PHINX_TYPE_POINT: + case static::PHINX_TYPE_LINESTRING: + case static::PHINX_TYPE_POLYGON: + return array('name' => $type); + case static::PHINX_TYPE_ENUM: + return array('name' => 'enum'); + break; + case static::PHINX_TYPE_SET: + return array('name' => 'set'); + break; + case static::TYPE_YEAR: + if (!$limit || in_array($limit, array(2, 4))) + $limit = 4; + return array('name' => 'year', 'limit' => $limit); + break; + case static::PHINX_TYPE_JSON: + return array('name' => 'json'); + break; + default: + throw new \RuntimeException('The type: "' . $type . '" is not supported.'); + } + } + + /** + * Returns Phinx type by SQL type + * + * @param string $sqlTypeDef + * @throws \RuntimeException + * @internal param string $sqlType SQL type + * @returns string Phinx type + */ + public function getPhinxType($sqlTypeDef) + { + if (!preg_match('/^([\w]+)(\(([\d]+)*(,([\d]+))*\))*(.+)*$/', $sqlTypeDef, $matches)) { + throw new \RuntimeException('Column type ' . $sqlTypeDef . ' is not supported'); + } else { + $limit = null; + $precision = null; + $type = $matches[1]; + if (count($matches) > 2) { + $limit = $matches[3] ? (int) $matches[3] : null; + } + if (count($matches) > 4) { + $precision = (int) $matches[5]; + } + if ($type === 'tinyint' && $limit === 1) { + $type = static::PHINX_TYPE_BOOLEAN; + $limit = null; + } + switch ($type) { + case 'varchar': + $type = static::PHINX_TYPE_STRING; + if ($limit === 255) { + $limit = null; + } + break; + case 'char': + $type = static::PHINX_TYPE_CHAR; + if ($limit === 255) { + $limit = null; + } + if ($limit === 36) { + $type = static::PHINX_TYPE_UUID; + } + break; + case 'tinyint': + $type = static::PHINX_TYPE_INTEGER; + $limit = static::INT_TINY; + break; + case 'smallint': + $type = static::PHINX_TYPE_INTEGER; + $limit = static::INT_SMALL; + break; + case 'mediumint': + $type = static::PHINX_TYPE_INTEGER; + $limit = static::INT_MEDIUM; + break; + case 'int': + $type = static::PHINX_TYPE_INTEGER; + if ($limit === 11) { + $limit = null; + } + break; + case 'bigint': + if ($limit === 20) { + $limit = null; + } + $type = static::PHINX_TYPE_BIG_INTEGER; + break; + case 'blob': + $type = static::PHINX_TYPE_BINARY; + break; + case 'tinyblob': + $type = static::PHINX_TYPE_BINARY; + $limit = static::BLOB_TINY; + break; + case 'mediumblob': + $type = static::PHINX_TYPE_BINARY; + $limit = static::BLOB_MEDIUM; + break; + case 'longblob': + $type = static::PHINX_TYPE_BINARY; + $limit = static::BLOB_LONG; + break; + case 'tinytext': + $type = static::PHINX_TYPE_TEXT; + $limit = static::TEXT_TINY; + break; + case 'mediumtext': + $type = static::PHINX_TYPE_TEXT; + $limit = static::TEXT_MEDIUM; + break; + case 'longtext': + $type = static::PHINX_TYPE_TEXT; + $limit = static::TEXT_LONG; + break; + } + + $this->getSqlType($type, $limit); + + return array( + 'name' => $type, + 'limit' => $limit, + 'precision' => $precision + ); + } + } + + /** + * {@inheritdoc} + */ + public function createDatabase($name, $options = array()) + { + $this->startCommandTimer(); + $this->writeCommand('createDatabase', array($name)); + $charset = isset($options['charset']) ? $options['charset'] : 'utf8'; + + if (isset($options['collation'])) { + $this->execute(sprintf('CREATE DATABASE `%s` DEFAULT CHARACTER SET `%s` COLLATE `%s`', $name, $charset, $options['collation'])); + } else { + $this->execute(sprintf('CREATE DATABASE `%s` DEFAULT CHARACTER SET `%s`', $name, $charset)); + } + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function hasDatabase($name) + { + $rows = $this->fetchAll( + sprintf( + 'SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = \'%s\'', + $name + ) + ); + + foreach ($rows as $row) { + if (!empty($row)) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function dropDatabase($name) + { + $this->startCommandTimer(); + $this->writeCommand('dropDatabase', array($name)); + $this->execute(sprintf('DROP DATABASE IF EXISTS `%s`', $name)); + $this->endCommandTimer(); + } + + /** + * Gets the MySQL Column Definition for a Column object. + * + * @param Column $column Column + * @return string + */ + protected function getColumnSqlDefinition(Column $column) + { + $sqlType = $this->getSqlType($column->getType(), $column->getLimit()); + + $def = ''; + $def .= strtoupper($sqlType['name']); + if ($column->getPrecision() && $column->getScale()) { + $def .= '(' . $column->getPrecision() . ',' . $column->getScale() . ')'; + } elseif (isset($sqlType['limit'])) { + $def .= '(' . $sqlType['limit'] . ')'; + } + if (($values = $column->getValues()) && is_array($values)) { + $def .= "('" . implode("', '", $values) . "')"; + } + $def .= (!$column->isSigned() && isset($this->signedColumnTypes[$column->getType()])) ? ' unsigned' : '' ; + $def .= ($column->isNull() == false) ? ' NOT NULL' : ' NULL'; + $def .= ($column->isIdentity()) ? ' AUTO_INCREMENT' : ''; + $def .= $this->getDefaultValueDefinition($column->getDefault()); + + if ($column->getComment()) { + $def .= ' COMMENT ' . $this->getConnection()->quote($column->getComment()); + } + + if ($column->getUpdate()) { + $def .= ' ON UPDATE ' . $column->getUpdate(); + } + + return $def; + } + + /** + * Gets the MySQL Index Definition for an Index object. + * + * @param Index $index Index + * @return string + */ + protected function getIndexSqlDefinition(Index $index) + { + $def = ''; + $limit = ''; + if ($index->getLimit()) { + $limit = '(' . $index->getLimit() . ')'; + } + + if ($index->getType() == Index::UNIQUE) { + $def .= ' UNIQUE'; + } + + if ($index->getType() == Index::FULLTEXT) { + $def .= ' FULLTEXT'; + } + + $def .= ' KEY'; + + if (is_string($index->getName())) { + $def .= ' `' . $index->getName() . '`'; + } + + $def .= ' (`' . implode('`,`', $index->getColumns()) . '`' . $limit . ')'; + + return $def; + } + + /** + * Gets the MySQL Foreign Key Definition for an ForeignKey object. + * + * @param ForeignKey $foreignKey + * @return string + */ + protected function getForeignKeySqlDefinition(ForeignKey $foreignKey) + { + $def = ''; + if ($foreignKey->getConstraint()) { + $def .= ' CONSTRAINT ' . $this->quoteColumnName($foreignKey->getConstraint()); + } + $columnNames = array(); + foreach ($foreignKey->getColumns() as $column) { + $columnNames[] = $this->quoteColumnName($column); + } + $def .= ' FOREIGN KEY (' . implode(',', $columnNames) . ')'; + $refColumnNames = array(); + foreach ($foreignKey->getReferencedColumns() as $column) { + $refColumnNames[] = $this->quoteColumnName($column); + } + $def .= ' REFERENCES ' . $this->quoteTableName($foreignKey->getReferencedTable()->getName()) . ' (' . implode(',', $refColumnNames) . ')'; + if ($foreignKey->getOnDelete()) { + $def .= ' ON DELETE ' . $foreignKey->getOnDelete(); + } + if ($foreignKey->getOnUpdate()) { + $def .= ' ON UPDATE ' . $foreignKey->getOnUpdate(); + } + return $def; + } + + /** + * Describes a database table. This is a MySQL adapter specific method. + * + * @param string $tableName Table name + * @return array + */ + public function describeTable($tableName) + { + $options = $this->getOptions(); + + // mysql specific + $sql = sprintf( + "SELECT * + FROM information_schema.tables + WHERE table_schema = '%s' + AND table_name = '%s'", + $options['name'], + $tableName + ); + + return $this->fetchRow($sql); + } + + /** + * Returns MySQL column types (inherited and MySQL specified). + * @return array + */ + public function getColumnTypes() + { + return array_merge(parent::getColumnTypes(), array('enum', 'set', 'year', 'json')); + } +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/PdoAdapter.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/PdoAdapter.php new file mode 100644 index 0000000..9924c3a --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/PdoAdapter.php @@ -0,0 +1,587 @@ + + */ +abstract class PdoAdapter implements AdapterInterface +{ + /** + * @var array + */ + protected $options = array(); + + /** + * @var InputInterface + */ + protected $input; + + /** + * @var OutputInterface + */ + protected $output; + + /** + * @var string + */ + protected $schemaTableName = 'migrations'; + + /** + * @var \PDO + */ + protected $connection; + + /** + * @var float + */ + protected $commandStartTime; + + /** + * Class Constructor. + * + * @param array $options Options + * @param InputInterface $input Input Interface + * @param OutputInterface $output Output Interface + */ + public function __construct(array $options, InputInterface $input = null, OutputInterface $output = null) + { + $this->setOptions($options); + if (null !== $input) { + $this->setInput($input); + } + if (null !== $output) { + $this->setOutput($output); + } + } + + /** + * {@inheritdoc} + */ + public function setOptions(array $options) + { + $this->options = $options; + + if (isset($options['default_migration_table'])) { + $this->setSchemaTableName($options['default_migration_table']); + } + + if (isset($options['connection'])) { + $this->setConnection($options['connection']); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getOptions() + { + return $this->options; + } + + /** + * {@inheritdoc} + */ + public function hasOption($name) + { + return isset($this->options[$name]); + } + + /** + * {@inheritdoc} + */ + public function getOption($name) + { + if (!$this->hasOption($name)) { + return; + } + return $this->options[$name]; + } + + /** + * {@inheritdoc} + */ + public function setInput(InputInterface $input) + { + $this->input = $input; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getInput() + { + return $this->input; + } + + /** + * {@inheritdoc} + */ + public function setOutput(OutputInterface $output) + { + $this->output = $output; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getOutput() + { + if (null === $this->output) { + $output = new OutputInterface('nothing'); + $this->setOutput($output); + } + return $this->output; + } + + /** + * Sets the schema table name. + * + * @param string $schemaTableName Schema Table Name + * @return PdoAdapter + */ + public function setSchemaTableName($schemaTableName) + { + $this->schemaTableName = $schemaTableName; + return $this; + } + + /** + * Gets the schema table name. + * + * @return string + */ + public function getSchemaTableName() + { + return $this->schemaTableName; + } + + /** + * Sets the database connection. + * + * @param \PDO $connection Connection + * @return AdapterInterface + */ + public function setConnection(\PDO $connection) + { + $this->connection = $connection; + + // Create the schema table if it doesn't already exist + if (!$this->hasSchemaTable()) { + $this->createSchemaTable(); + } else { + $table = new Table($this->getSchemaTableName(), array(), $this); + if (!$table->hasColumn('migration_name')) { + $table + ->addColumn('migration_name', 'string', + array('limit' => 100, 'after' => 'version', 'default' => null, 'null' => true) + ) + ->save(); + } + if (!$table->hasColumn('breakpoint')) { + $table + ->addColumn('breakpoint', 'boolean', array('default' => false)) + ->save(); + } + } + + return $this; + } + + /** + * Gets the database connection + * + * @return \PDO + */ + public function getConnection() + { + if (null === $this->connection) { + $this->connect(); + } + return $this->connection; + } + + /** + * Sets the command start time + * + * @param int $time + * @return AdapterInterface + */ + public function setCommandStartTime($time) + { + $this->commandStartTime = $time; + return $this; + } + + /** + * Gets the command start time + * + * @return int + */ + public function getCommandStartTime() + { + return $this->commandStartTime; + } + + /** + * Start timing a command. + * + * @return void + */ + public function startCommandTimer() + { + $this->setCommandStartTime(microtime(true)); + } + + /** + * Stop timing the current command and write the elapsed time to the + * output. + * + * @return void + */ + public function endCommandTimer() + { + $end = microtime(true); + if (OutputInterface::VERBOSITY_VERBOSE <= $this->getOutput()->getVerbosity()) { + $this->getOutput()->writeln(' -> ' . sprintf('%.4fs', $end - $this->getCommandStartTime())); + } + } + + /** + * Write a Phinx command to the output. + * + * @param string $command Command Name + * @param array $args Command Args + * @return void + */ + public function writeCommand($command, $args = array()) + { + if (OutputInterface::VERBOSITY_VERBOSE <= $this->getOutput()->getVerbosity()) { + if (count($args)) { + $outArr = array(); + foreach ($args as $arg) { + if (is_array($arg)) { + $arg = array_map(function ($value) { + return '\'' . $value . '\''; + }, $arg); + $outArr[] = '[' . implode(', ', $arg) . ']'; + continue; + } + + $outArr[] = '\'' . $arg . '\''; + } + $this->getOutput()->writeln(' -- ' . $command . '(' . implode(', ', $outArr) . ')'); + return; + } + $this->getOutput()->writeln(' -- ' . $command); + } + } + + /** + * {@inheritdoc} + */ + public function connect() + { + } + + /** + * {@inheritdoc} + */ + public function disconnect() + { + } + + /** + * {@inheritdoc} + */ + public function execute($sql) + { + return $this->getConnection()->exec($sql); + } + + /** + * {@inheritdoc} + */ + public function query($sql) + { + return $this->getConnection()->query($sql); + } + + /** + * {@inheritdoc} + */ + public function fetchRow($sql) + { + $result = $this->query($sql); + return $result->fetch(); + } + + /** + * {@inheritdoc} + */ + public function fetchAll($sql) + { + $rows = array(); + $result = $this->query($sql); + while ($row = $result->fetch()) { + $rows[] = $row; + } + return $rows; + } + + /** + * {@inheritdoc} + */ + public function insert(Table $table, $row) + { + $this->startCommandTimer(); + $this->writeCommand('insert', array($table->getName())); + + $sql = sprintf( + "INSERT INTO %s ", + $this->quoteTableName($table->getName()) + ); + + $columns = array_keys($row); + $sql .= "(". implode(', ', array_map(array($this, 'quoteColumnName'), $columns)) . ")"; + $sql .= " VALUES (" . implode(', ', array_fill(0, count($columns), '?')) . ")"; + + $stmt = $this->getConnection()->prepare($sql); + $stmt->execute(array_values($row)); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function getVersions() + { + $rows = $this->getVersionLog(); + + return array_keys($rows); + } + + /** + * {@inheritdoc} + */ + public function getVersionLog() + { + $result = array(); + $rows = $this->fetchAll(sprintf('SELECT * FROM %s ORDER BY version ASC', $this->getSchemaTableName())); + foreach ($rows as $version) { + $result[$version['version']] = $version; + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function migrated(MigrationInterface $migration, $direction, $startTime, $endTime) + { + if (strcasecmp($direction, MigrationInterface::UP) === 0) { + // up + $sql = sprintf( + "INSERT INTO %s (%s, %s, %s, %s, %s) VALUES ('%s', '%s', '%s', '%s', %s);", + $this->getSchemaTableName(), + $this->quoteColumnName('version'), + $this->quoteColumnName('migration_name'), + $this->quoteColumnName('start_time'), + $this->quoteColumnName('end_time'), + $this->quoteColumnName('breakpoint'), + $migration->getVersion(), + substr($migration->getName(), 0, 100), + $startTime, + $endTime, + $this->castToBool(false) + ); + + $this->query($sql); + } else { + // down + $sql = sprintf( + "DELETE FROM %s WHERE %s = '%s'", + $this->getSchemaTableName(), + $this->quoteColumnName('version'), + $migration->getVersion() + ); + + $this->query($sql); + } + + return $this; + } + + /** + * @inheritDoc + */ + public function toggleBreakpoint(MigrationInterface $migration) + { + $this->query( + sprintf( + 'UPDATE %1$s SET %2$s = CASE %2$s WHEN %3$s THEN %4$s ELSE %3$s END WHERE %5$s = \'%6$s\';', + $this->getSchemaTableName(), + $this->quoteColumnName('breakpoint'), + $this->castToBool(true), + $this->castToBool(false), + $this->quoteColumnName('version'), + $migration->getVersion() + ) + ); + + return $this; + } + + /** + * @inheritDoc + */ + public function resetAllBreakpoints() + { + return $this->execute( + sprintf( + 'UPDATE %1$s SET %2$s = %3$s WHERE %2$s <> %3$s;', + $this->getSchemaTableName(), + $this->quoteColumnName('breakpoint'), + $this->castToBool(false) + ) + ); + } + + /** + * {@inheritdoc} + */ + public function hasSchemaTable() + { + return $this->hasTable($this->getSchemaTableName()); + } + + /** + * {@inheritdoc} + */ + public function createSchemaTable() + { + try { + $options = array( + 'id' => false, + 'primary_key' => 'version' + ); + + $table = new Table($this->getSchemaTableName(), $options, $this); + + if ($this->getConnection()->getAttribute(\PDO::ATTR_DRIVER_NAME) === 'mysql' + && version_compare($this->getConnection()->getAttribute(\PDO::ATTR_SERVER_VERSION), '5.6.0', '>=')) { + $table->addColumn('version', 'biginteger', array('limit' => 14)) + ->addColumn('migration_name', 'string', array('limit' => 100, 'default' => null, 'null' => true)) + ->addColumn('start_time', 'timestamp', array('default' => 'CURRENT_TIMESTAMP')) + ->addColumn('end_time', 'timestamp', array('default' => 'CURRENT_TIMESTAMP')) + ->addColumn('breakpoint', 'boolean', array('default' => false)) + ->save(); + } else { + $table->addColumn('version', 'biginteger') + ->addColumn('migration_name', 'string', array('limit' => 100, 'default' => null, 'null' => true)) + ->addColumn('start_time', 'timestamp') + ->addColumn('end_time', 'timestamp') + ->addColumn('breakpoint', 'boolean', array('default' => false)) + ->save(); + } + } catch (\Exception $exception) { + throw new \InvalidArgumentException('There was a problem creating the schema table: ' . $exception->getMessage()); + } + } + + /** + * {@inheritdoc} + */ + public function getAdapterType() + { + return $this->getOption('adapter'); + } + + /** + * {@inheritdoc} + */ + public function getColumnTypes() + { + return array( + 'string', + 'char', + 'text', + 'integer', + 'biginteger', + 'float', + 'decimal', + 'datetime', + 'timestamp', + 'time', + 'date', + 'blob', + 'binary', + 'varbinary', + 'boolean', + 'uuid', + // Geospatial data types + 'geometry', + 'point', + 'linestring', + 'polygon', + ); + } + + /** + * {@inheritdoc} + */ + public function isValidColumnType(Column $column) { + return in_array($column->getType(), $this->getColumnTypes()); + } + + /** + * Cast a value to a boolean appropriate for the adapter. + * + * @param mixed $value The value to be cast + * + * @return mixed + */ + public function castToBool($value) + { + return (bool) $value ? 1 : 0; + } +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/PostgresAdapter.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/PostgresAdapter.php new file mode 100644 index 0000000..3c506bd --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/PostgresAdapter.php @@ -0,0 +1,1182 @@ +connection) { + if (!class_exists('PDO') || !in_array('pgsql', \PDO::getAvailableDrivers(), true)) { + // @codeCoverageIgnoreStart + throw new \RuntimeException('You need to enable the PDO_Pgsql extension for Phinx to run properly.'); + // @codeCoverageIgnoreEnd + } + + $db = null; + $options = $this->getOptions(); + + // if port is specified use it, otherwise use the PostgreSQL default + if (isset($options['port'])) { + $dsn = 'pgsql:host=' . $options['host'] . ';port=' . $options['port'] . ';dbname=' . $options['name']; + } else { + $dsn = 'pgsql:host=' . $options['host'] . ';dbname=' . $options['name']; + } + + try { + $db = new \PDO($dsn, $options['user'], $options['pass'], array(\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION)); + } catch (\PDOException $exception) { + throw new \InvalidArgumentException(sprintf( + 'There was a problem connecting to the database: %s', + $exception->getMessage() + )); + } + + $this->setConnection($db); + } + } + + /** + * {@inheritdoc} + */ + public function disconnect() + { + $this->connection = null; + } + + /** + * {@inheritdoc} + */ + public function hasTransactions() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function beginTransaction() + { + $this->execute('BEGIN'); + } + + /** + * {@inheritdoc} + */ + public function commitTransaction() + { + $this->execute('COMMIT'); + } + + /** + * {@inheritdoc} + */ + public function rollbackTransaction() + { + $this->execute('ROLLBACK'); + } + + /** + * Quotes a schema name for use in a query. + * + * @param string $schemaName Schema Name + * @return string + */ + public function quoteSchemaName($schemaName) + { + return $this->quoteColumnName($schemaName); + } + + /** + * {@inheritdoc} + */ + public function quoteTableName($tableName) + { + return $this->quoteSchemaName($this->getSchemaName()) . '.' . $this->quoteColumnName($tableName); + } + + /** + * {@inheritdoc} + */ + public function quoteColumnName($columnName) + { + return '"'. $columnName . '"'; + } + + /** + * {@inheritdoc} + */ + public function hasTable($tableName) + { + $result = $this->getConnection()->query( + sprintf( + 'SELECT * + FROM information_schema.tables + WHERE table_schema = %s + AND lower(table_name) = lower(%s)', + $this->getConnection()->quote($this->getSchemaName()), + $this->getConnection()->quote($tableName) + ) + ); + + return $result->rowCount() === 1; + } + + /** + * {@inheritdoc} + */ + public function createTable(Table $table) + { + $this->startCommandTimer(); + $options = $table->getOptions(); + + // Add the default primary key + $columns = $table->getPendingColumns(); + if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) { + $column = new Column(); + $column->setName('id') + ->setType('integer') + ->setIdentity(true); + + array_unshift($columns, $column); + $options['primary_key'] = 'id'; + + } elseif (isset($options['id']) && is_string($options['id'])) { + // Handle id => "field_name" to support AUTO_INCREMENT + $column = new Column(); + $column->setName($options['id']) + ->setType('integer') + ->setIdentity(true); + + array_unshift($columns, $column); + $options['primary_key'] = $options['id']; + } + + // TODO - process table options like collation etc + $sql = 'CREATE TABLE '; + $sql .= $this->quoteTableName($table->getName()) . ' ('; + + $this->columnsWithComments = array(); + foreach ($columns as $column) { + $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', '; + + // set column comments, if needed + if ($column->getComment()) { + $this->columnsWithComments[] = $column; + } + } + + // set the primary key(s) + if (isset($options['primary_key'])) { + $sql = rtrim($sql); + $sql .= sprintf(' CONSTRAINT %s_pkey PRIMARY KEY (', $table->getName()); + if (is_string($options['primary_key'])) { // handle primary_key => 'id' + $sql .= $this->quoteColumnName($options['primary_key']); + } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id') + // PHP 5.4 will allow access of $this, so we can call quoteColumnName() directly in the anonymous function, + // but for now just hard-code the adapter quotes + $sql .= implode( + ',', + array_map( + function ($v) { + return '"' . $v . '"'; + }, + $options['primary_key'] + ) + ); + } + $sql .= ')'; + } else { + $sql = substr(rtrim($sql), 0, -1); // no primary keys + } + + // set the foreign keys + $foreignKeys = $table->getForeignKeys(); + if (!empty($foreignKeys)) { + foreach ($foreignKeys as $foreignKey) { + $sql .= ', ' . $this->getForeignKeySqlDefinition($foreignKey, $table->getName()); + } + } + + $sql .= ');'; + + // process column comments + if (!empty($this->columnsWithComments)) { + foreach ($this->columnsWithComments as $column) { + $sql .= $this->getColumnCommentSqlDefinition($column, $table->getName()); + } + } + + // set the indexes + $indexes = $table->getIndexes(); + if (!empty($indexes)) { + foreach ($indexes as $index) { + $sql .= $this->getIndexSqlDefinition($index, $table->getName()); + } + } + + // execute the sql + $this->writeCommand('createTable', array($table->getName())); + $this->execute($sql); + + // process table comments + if (isset($options['comment'])) { + $sql = sprintf( + 'COMMENT ON TABLE %s IS %s', + $this->quoteTableName($table->getName()), + $this->getConnection()->quote($options['comment']) + ); + $this->execute($sql); + } + + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function renameTable($tableName, $newTableName) + { + $this->startCommandTimer(); + $this->writeCommand('renameTable', array($tableName, $newTableName)); + $sql = sprintf( + 'ALTER TABLE %s RENAME TO %s', + $this->quoteTableName($tableName), + $this->quoteColumnName($newTableName) + ); + $this->execute($sql); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropTable($tableName) + { + $this->startCommandTimer(); + $this->writeCommand('dropTable', array($tableName)); + $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tableName))); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function getColumns($tableName) + { + $columns = array(); + $sql = sprintf( + "SELECT column_name, data_type, is_identity, is_nullable, + column_default, character_maximum_length, numeric_precision, numeric_scale + FROM information_schema.columns + WHERE table_name ='%s'", + $tableName + ); + $columnsInfo = $this->fetchAll($sql); + + foreach ($columnsInfo as $columnInfo) { + $column = new Column(); + $column->setName($columnInfo['column_name']) + ->setType($this->getPhinxType($columnInfo['data_type'])) + ->setNull($columnInfo['is_nullable'] === 'YES') + ->setDefault($columnInfo['column_default']) + ->setIdentity($columnInfo['is_identity'] === 'YES') + ->setPrecision($columnInfo['numeric_precision']) + ->setScale($columnInfo['numeric_scale']); + + if (preg_match('/\bwith time zone$/', $columnInfo['data_type'])) { + $column->setTimezone(true); + } + + if (isset($columnInfo['character_maximum_length'])) { + $column->setLimit($columnInfo['character_maximum_length']); + } + $columns[] = $column; + } + return $columns; + } + + /** + * {@inheritdoc} + */ + public function hasColumn($tableName, $columnName, $options = array()) + { + $sql = sprintf("SELECT count(*) + FROM information_schema.columns + WHERE table_schema = '%s' AND table_name = '%s' AND column_name = '%s'", + $this->getSchemaName(), + $tableName, + $columnName + ); + + $result = $this->fetchRow($sql); + return $result['count'] > 0; + } + + /** + * {@inheritdoc} + */ + public function addColumn(Table $table, Column $column) + { + $this->startCommandTimer(); + $this->writeCommand('addColumn', array($table->getName(), $column->getName(), $column->getType())); + $sql = sprintf( + 'ALTER TABLE %s ADD %s %s', + $this->quoteTableName($table->getName()), + $this->quoteColumnName($column->getName()), + $this->getColumnSqlDefinition($column) + ); + + $this->execute($sql); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function renameColumn($tableName, $columnName, $newColumnName) + { + $this->startCommandTimer(); + $sql = sprintf( + "SELECT CASE WHEN COUNT(*) > 0 THEN 1 ELSE 0 END AS column_exists + FROM information_schema.columns + WHERE table_name ='%s' AND column_name = '%s'", + $tableName, + $columnName + ); + $result = $this->fetchRow($sql); + if (!(bool) $result['column_exists']) { + throw new \InvalidArgumentException("The specified column does not exist: $columnName"); + } + $this->writeCommand('renameColumn', array($tableName, $columnName, $newColumnName)); + $this->execute( + sprintf( + 'ALTER TABLE %s RENAME COLUMN %s TO %s', + $this->quoteTableName($tableName), + $this->quoteColumnName($columnName), + $newColumnName + ) + ); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function changeColumn($tableName, $columnName, Column $newColumn) + { + // TODO - is it possible to merge these 3 queries into less? + $this->startCommandTimer(); + $this->writeCommand('changeColumn', array($tableName, $columnName, $newColumn->getType())); + // change data type + $sql = sprintf( + 'ALTER TABLE %s ALTER COLUMN %s TYPE %s', + $this->quoteTableName($tableName), + $this->quoteColumnName($columnName), + $this->getColumnSqlDefinition($newColumn) + ); + //NULL and DEFAULT cannot be set while changing column type + $sql = preg_replace('/ NOT NULL/', '', $sql); + $sql = preg_replace('/ NULL/', '', $sql); + //If it is set, DEFAULT is the last definition + $sql = preg_replace('/DEFAULT .*/', '', $sql); + $this->execute($sql); + // process null + $sql = sprintf( + 'ALTER TABLE %s ALTER COLUMN %s', + $this->quoteTableName($tableName), + $this->quoteColumnName($columnName) + ); + if ($newColumn->isNull()) { + $sql .= ' DROP NOT NULL'; + } else { + $sql .= ' SET NOT NULL'; + } + $this->execute($sql); + if (!is_null($newColumn->getDefault())) { + //change default + $this->execute( + sprintf( + 'ALTER TABLE %s ALTER COLUMN %s SET %s', + $this->quoteTableName($tableName), + $this->quoteColumnName($columnName), + $this->getDefaultValueDefinition($newColumn->getDefault()) + ) + ); + } + else { + //drop default + $this->execute( + sprintf( + 'ALTER TABLE %s ALTER COLUMN %s DROP DEFAULT', + $this->quoteTableName($tableName), + $this->quoteColumnName($columnName) + ) + ); + } + // rename column + if ($columnName !== $newColumn->getName()) { + $this->execute( + sprintf( + 'ALTER TABLE %s RENAME COLUMN %s TO %s', + $this->quoteTableName($tableName), + $this->quoteColumnName($columnName), + $this->quoteColumnName($newColumn->getName()) + ) + ); + } + + // change column comment if needed + if ($newColumn->getComment()) { + $sql = $this->getColumnCommentSqlDefinition($newColumn, $tableName); + $this->execute($sql); + } + + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropColumn($tableName, $columnName) + { + $this->startCommandTimer(); + $this->writeCommand('dropColumn', array($tableName, $columnName)); + $this->execute( + sprintf( + 'ALTER TABLE %s DROP COLUMN %s', + $this->quoteTableName($tableName), + $this->quoteColumnName($columnName) + ) + ); + $this->endCommandTimer(); + } + + /** + * Get an array of indexes from a particular table. + * + * @param string $tableName Table Name + * @return array + */ + protected function getIndexes($tableName) + { + $indexes = array(); + $sql = "SELECT + i.relname AS index_name, + a.attname AS column_name + FROM + pg_class t, + pg_class i, + pg_index ix, + pg_attribute a + WHERE + t.oid = ix.indrelid + AND i.oid = ix.indexrelid + AND a.attrelid = t.oid + AND a.attnum = ANY(ix.indkey) + AND t.relkind = 'r' + AND t.relname = '$tableName' + ORDER BY + t.relname, + i.relname;"; + $rows = $this->fetchAll($sql); + foreach ($rows as $row) { + if (!isset($indexes[$row['index_name']])) { + $indexes[$row['index_name']] = array('columns' => array()); + } + $indexes[$row['index_name']]['columns'][] = strtolower($row['column_name']); + } + return $indexes; + } + + /** + * {@inheritdoc} + */ + public function hasIndex($tableName, $columns) + { + if (is_string($columns)) { + $columns = array($columns); + } + $columns = array_map('strtolower', $columns); + $indexes = $this->getIndexes($tableName); + foreach ($indexes as $index) { + if (array_diff($index['columns'], $columns) === array_diff($columns, $index['columns'])) { + return true; + } + } + return false; + } + + /** + * {@inheritdoc} + */ + public function hasIndexByName($tableName, $indexName) + { + $indexes = $this->getIndexes($tableName); + foreach ($indexes as $name => $index) { + if ($name === $indexName) { + return true; + } + } + return false; + } + + /** + * {@inheritdoc} + */ + public function addIndex(Table $table, Index $index) + { + $this->startCommandTimer(); + $this->writeCommand('addIndex', array($table->getName(), $index->getColumns())); + $sql = $this->getIndexSqlDefinition($index, $table->getName()); + $this->execute($sql); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropIndex($tableName, $columns) + { + $this->startCommandTimer(); + if (is_string($columns)) { + $columns = array($columns); // str to array + } + + $this->writeCommand('dropIndex', array($tableName, $columns)); + $indexes = $this->getIndexes($tableName); + $columns = array_map('strtolower', $columns); + + foreach ($indexes as $indexName => $index) { + $a = array_diff($columns, $index['columns']); + if (empty($a)) { + $this->execute( + sprintf( + 'DROP INDEX IF EXISTS %s', + $this->quoteColumnName($indexName) + ) + ); + $this->endCommandTimer(); + return; + } + } + } + + /** + * {@inheritdoc} + */ + public function dropIndexByName($tableName, $indexName) + { + $this->startCommandTimer(); + $this->writeCommand('dropIndexByName', array($tableName, $indexName)); + $sql = sprintf( + 'DROP INDEX IF EXISTS %s', + $indexName + ); + $this->execute($sql); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function hasForeignKey($tableName, $columns, $constraint = null) + { + if (is_string($columns)) { + $columns = array($columns); // str to array + } + $foreignKeys = $this->getForeignKeys($tableName); + if ($constraint) { + if (isset($foreignKeys[$constraint])) { + return !empty($foreignKeys[$constraint]); + } + return false; + } else { + foreach ($foreignKeys as $key) { + $a = array_diff($columns, $key['columns']); + if (empty($a)) { + return true; + } + } + return false; + } + } + + /** + * Get an array of foreign keys from a particular table. + * + * @param string $tableName Table Name + * @return array + */ + protected function getForeignKeys($tableName) + { + $foreignKeys = array(); + $rows = $this->fetchAll(sprintf( + "SELECT + tc.constraint_name, + tc.table_name, kcu.column_name, + ccu.table_name AS referenced_table_name, + ccu.column_name AS referenced_column_name + FROM + information_schema.table_constraints AS tc + JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name + JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name + WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = '%s' + ORDER BY kcu.position_in_unique_constraint", + $tableName + )); + foreach ($rows as $row) { + $foreignKeys[$row['constraint_name']]['table'] = $row['table_name']; + $foreignKeys[$row['constraint_name']]['columns'][] = $row['column_name']; + $foreignKeys[$row['constraint_name']]['referenced_table'] = $row['referenced_table_name']; + $foreignKeys[$row['constraint_name']]['referenced_columns'][] = $row['referenced_column_name']; + } + return $foreignKeys; + } + + /** + * {@inheritdoc} + */ + public function addForeignKey(Table $table, ForeignKey $foreignKey) + { + $this->startCommandTimer(); + $this->writeCommand('addForeignKey', array($table->getName(), $foreignKey->getColumns())); + $sql = sprintf( + 'ALTER TABLE %s ADD %s', + $this->quoteTableName($table->getName()), + $this->getForeignKeySqlDefinition($foreignKey, $table->getName()) + ); + $this->execute($sql); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropForeignKey($tableName, $columns, $constraint = null) + { + $this->startCommandTimer(); + if (is_string($columns)) { + $columns = array($columns); // str to array + } + $this->writeCommand('dropForeignKey', array($tableName, $columns)); + + if ($constraint) { + $this->execute( + sprintf( + 'ALTER TABLE %s DROP CONSTRAINT %s', + $this->quoteTableName($tableName), + $constraint + ) + ); + } else { + foreach ($columns as $column) { + $rows = $this->fetchAll(sprintf( + "SELECT CONSTRAINT_NAME + FROM information_schema.KEY_COLUMN_USAGE + WHERE TABLE_SCHEMA = CURRENT_SCHEMA() + AND TABLE_NAME IS NOT NULL + AND TABLE_NAME = '%s' + AND COLUMN_NAME = '%s' + ORDER BY POSITION_IN_UNIQUE_CONSTRAINT", + $tableName, + $column + )); + + foreach ($rows as $row) { + $this->dropForeignKey($tableName, $columns, $row['constraint_name']); + } + } + } + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function getSqlType($type, $limit = null) + { + switch ($type) { + case static::PHINX_TYPE_INTEGER: + if ($limit && $limit == static::INT_SMALL) { + return array( + 'name' => 'smallint', + 'limit' => static::INT_SMALL + ); + } + return array('name' => $type); + case static::PHINX_TYPE_TEXT: + case static::PHINX_TYPE_TIME: + case static::PHINX_TYPE_DATE: + case static::PHINX_TYPE_BOOLEAN: + case static::PHINX_TYPE_JSON: + case static::PHINX_TYPE_JSONB: + case static::PHINX_TYPE_UUID: + return array('name' => $type); + case static::PHINX_TYPE_DECIMAL: + return array('name' => $type, 'precision' => 18, 'scale' => 0); + case static::PHINX_TYPE_STRING: + return array('name' => 'character varying', 'limit' => 255); + case static::PHINX_TYPE_CHAR: + return array('name' => 'character', 'limit' => 255); + case static::PHINX_TYPE_BIG_INTEGER: + return array('name' => 'bigint'); + case static::PHINX_TYPE_FLOAT: + return array('name' => 'real'); + case static::PHINX_TYPE_DATETIME: + case static::PHINX_TYPE_TIMESTAMP: + return array('name' => 'timestamp'); + case static::PHINX_TYPE_BLOB: + case static::PHINX_TYPE_BINARY: + return array('name' => 'bytea'); + // Geospatial database types + // Spatial storage in Postgres is done via the PostGIS extension, + // which enables the use of the "geography" type in combination + // with SRID 4326. + case static::PHINX_TYPE_GEOMETRY: + return array('name' => 'geography', 'geometry', 4326); + break; + case static::PHINX_TYPE_POINT: + return array('name' => 'geography', 'point', 4326); + break; + case static::PHINX_TYPE_LINESTRING: + return array('name' => 'geography', 'linestring', 4326); + break; + case static::PHINX_TYPE_POLYGON: + return array('name' => 'geography', 'polygon', 4326); + break; + default: + if ($this->isArrayType($type)) { + return array('name' => $type); + } + // Return array type + throw new \RuntimeException('The type: "' . $type . '" is not supported'); + } + } + + /** + * Returns Phinx type by SQL type + * + * @param string $sqlType SQL type + * @returns string Phinx type + */ + public function getPhinxType($sqlType) + { + switch ($sqlType) { + case 'character varying': + case 'varchar': + return static::PHINX_TYPE_STRING; + case 'character': + case 'char': + return static::PHINX_TYPE_CHAR; + case 'text': + return static::PHINX_TYPE_TEXT; + case 'json': + return static::PHINX_TYPE_JSON; + case 'jsonb': + return static::PHINX_TYPE_JSONB; + case 'smallint': + return array( + 'name' => 'smallint', + 'limit' => static::INT_SMALL + ); + case 'int': + case 'int4': + case 'integer': + return static::PHINX_TYPE_INTEGER; + case 'decimal': + case 'numeric': + return static::PHINX_TYPE_DECIMAL; + case 'bigint': + case 'int8': + return static::PHINX_TYPE_BIG_INTEGER; + case 'real': + case 'float4': + return static::PHINX_TYPE_FLOAT; + case 'bytea': + return static::PHINX_TYPE_BINARY; + break; + case 'time': + case 'timetz': + case 'time with time zone': + case 'time without time zone': + return static::PHINX_TYPE_TIME; + case 'date': + return static::PHINX_TYPE_DATE; + case 'timestamp': + case 'timestamptz': + case 'timestamp with time zone': + case 'timestamp without time zone': + return static::PHINX_TYPE_DATETIME; + case 'bool': + case 'boolean': + return static::PHINX_TYPE_BOOLEAN; + case 'uuid': + return static::PHINX_TYPE_UUID; + default: + throw new \RuntimeException('The PostgreSQL type: "' . $sqlType . '" is not supported'); + } + } + + /** + * {@inheritdoc} + */ + public function createDatabase($name, $options = array()) + { + $this->startCommandTimer(); + $this->writeCommand('createDatabase', array($name)); + $charset = isset($options['charset']) ? $options['charset'] : 'utf8'; + $this->execute(sprintf("CREATE DATABASE %s WITH ENCODING = '%s'", $name, $charset)); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function hasDatabase($databaseName) + { + $sql = sprintf("SELECT count(*) FROM pg_database WHERE datname = '%s'", $databaseName); + $result = $this->fetchRow($sql); + return $result['count'] > 0; + } + + /** + * {@inheritdoc} + */ + public function dropDatabase($name) + { + $this->startCommandTimer(); + $this->writeCommand('dropDatabase', array($name)); + $this->disconnect(); + $this->execute(sprintf('DROP DATABASE IF EXISTS %s', $name)); + $this->connect(); + $this->endCommandTimer(); + } + + /** + * Get the defintion for a `DEFAULT` statement. + * + * @param mixed $default + * @return string + */ + protected function getDefaultValueDefinition($default) + { + if (is_string($default) && 'CURRENT_TIMESTAMP' !== $default) { + $default = $this->getConnection()->quote($default); + } elseif (is_bool($default)) { + $default = $this->castToBool($default); + } + return isset($default) ? 'DEFAULT ' . $default : ''; + } + + /** + * Gets the PostgreSQL Column Definition for a Column object. + * + * @param Column $column Column + * @return string + */ + protected function getColumnSqlDefinition(Column $column) + { + $buffer = array(); + if ($column->isIdentity()) { + $buffer[] = $column->getType() == 'biginteger' ? 'BIGSERIAL' : 'SERIAL'; + } else { + $sqlType = $this->getSqlType($column->getType(), $column->getLimit()); + $buffer[] = strtoupper($sqlType['name']); + // integers cant have limits in postgres + if (static::PHINX_TYPE_DECIMAL === $sqlType['name'] && ($column->getPrecision() || $column->getScale())) { + $buffer[] = sprintf( + '(%s, %s)', + $column->getPrecision() ? $column->getPrecision() : $sqlType['precision'], + $column->getScale() ? $column->getScale() : $sqlType['scale'] + ); + } elseif (!in_array($sqlType['name'], array('integer', 'smallint'))) { + if ($column->getLimit() || isset($sqlType['limit'])) { + $buffer[] = sprintf('(%s)', $column->getLimit() ? $column->getLimit() : $sqlType['limit']); + } + } + + $timeTypes = array( + 'time', + 'timestamp', + ); + if (in_array($sqlType['name'], $timeTypes) && $column->isTimezone()) { + $buffer[] = strtoupper('with time zone'); + } + } + + $buffer[] = $column->isNull() ? 'NULL' : 'NOT NULL'; + + if (!is_null($column->getDefault())) { + $buffer[] = $this->getDefaultValueDefinition($column->getDefault()); + } + + return implode(' ', $buffer); + } + + /** + * Gets the PostgreSQL Column Comment Defininition for a column object. + * + * @param Column $column Column + * @param string $tableName Table name + * @return string + */ + protected function getColumnCommentSqlDefinition(Column $column, $tableName) + { + // passing 'null' is to remove column comment + $comment = (strcasecmp($column->getComment(), 'NULL') !== 0) + ? $this->getConnection()->quote($column->getComment()) + : 'NULL'; + + return sprintf( + 'COMMENT ON COLUMN %s.%s IS %s;', + $tableName, + $column->getName(), + $comment + ); + } + + /** + * Gets the PostgreSQL Index Definition for an Index object. + * + * @param Index $index Index + * @param string $tableName Table name + * @return string + */ + protected function getIndexSqlDefinition(Index $index, $tableName) + { + if (is_string($index->getName())) { + $indexName = $index->getName(); + } else { + $columnNames = $index->getColumns(); + if (is_string($columnNames)) { + $columnNames = array($columnNames); + } + $indexName = sprintf('%s_%s', $tableName, implode('_', $columnNames)); + } + $def = sprintf( + "CREATE %s INDEX %s ON %s (%s);", + ($index->getType() === Index::UNIQUE ? 'UNIQUE' : ''), + $indexName, + $this->quoteTableName($tableName), + implode(',', $index->getColumns()) + ); + return $def; + } + + /** + * Gets the MySQL Foreign Key Definition for an ForeignKey object. + * + * @param ForeignKey $foreignKey + * @param string $tableName Table name + * @return string + */ + protected function getForeignKeySqlDefinition(ForeignKey $foreignKey, $tableName) + { + $constraintName = $foreignKey->getConstraint() ?: $tableName . '_' . implode('_', $foreignKey->getColumns()); + $def = ' CONSTRAINT "' . $constraintName . '" FOREIGN KEY ("' . implode('", "', $foreignKey->getColumns()) . '")'; + $def .= " REFERENCES {$this->quoteTableName($foreignKey->getReferencedTable()->getName())} (\"" . implode('", "', $foreignKey->getReferencedColumns()) . '")'; + if ($foreignKey->getOnDelete()) { + $def .= " ON DELETE {$foreignKey->getOnDelete()}"; + } + if ($foreignKey->getOnUpdate()) { + $def .= " ON UPDATE {$foreignKey->getOnUpdate()}"; + } + return $def; + } + + /** + * {@inheritdoc} + */ + public function createSchemaTable() + { + // Create the public/custom schema if it doesn't already exist + if (false === $this->hasSchema($this->getSchemaName())) { + $this->createSchema($this->getSchemaName()); + } + + $this->fetchAll(sprintf('SET search_path TO %s', $this->getSchemaName())); + + return parent::createSchemaTable(); + } + + /** + * Creates the specified schema. + * + * @param string $schemaName Schema Name + * @return void + */ + public function createSchema($schemaName = 'public') + { + $this->startCommandTimer(); + $this->writeCommand('addSchema', array($schemaName)); + $sql = sprintf('CREATE SCHEMA %s;', $this->quoteSchemaName($schemaName)); // from postgres 9.3 we can use "CREATE SCHEMA IF NOT EXISTS schema_name" + $this->execute($sql); + $this->endCommandTimer(); + } + + /** + * Checks to see if a schema exists. + * + * @param string $schemaName Schema Name + * @return boolean + */ + public function hasSchema($schemaName) + { + $sql = sprintf( + "SELECT count(*) + FROM pg_namespace + WHERE nspname = '%s'", + $schemaName + ); + $result = $this->fetchRow($sql); + return $result['count'] > 0; + } + + /** + * Drops the specified schema table. + * + * @param string $schemaName Schema name + * @return void + */ + public function dropSchema($schemaName) + { + $this->startCommandTimer(); + $this->writeCommand('dropSchema', array($schemaName)); + $sql = sprintf("DROP SCHEMA IF EXISTS %s CASCADE;", $this->quoteSchemaName($schemaName)); + $this->execute($sql); + $this->endCommandTimer(); + } + + /** + * Drops all schemas. + * + * @return void + */ + public function dropAllSchemas() + { + $this->startCommandTimer(); + $this->writeCommand('dropAllSchemas'); + foreach ($this->getAllSchemas() as $schema) { + $this->dropSchema($schema); + } + $this->endCommandTimer(); + } + + /** + * Returns schemas. + * + * @return array + */ + public function getAllSchemas() + { + $sql = "SELECT schema_name + FROM information_schema.schemata + WHERE schema_name <> 'information_schema' AND schema_name !~ '^pg_'"; + $items = $this->fetchAll($sql); + $schemaNames = array(); + foreach ($items as $item) { + $schemaNames[] = $item['schema_name']; + } + return $schemaNames; + } + + /** + * {@inheritdoc} + */ + public function getColumnTypes() + { + return array_merge(parent::getColumnTypes(), array('json', 'jsonb')); + } + + /** + * {@inheritdoc} + */ + public function isValidColumnType(Column $column) + { + // If not a standard column type, maybe it is array type? + return (parent::isValidColumnType($column) || $this->isArrayType($column->getType())); + } + + /** + * Check if the given column is an array of a valid type. + * + * @param string $columnType + * @return bool + */ + protected function isArrayType($columnType) + { + if (!preg_match('/^([a-z]+)(?:\[\]){1,}$/', $columnType, $matches)) { + return false; + } + + $baseType = $matches[1]; + return in_array($baseType, $this->getColumnTypes()); + } + + /** + * Gets the schema name. + * + * @return string + */ + private function getSchemaName() + { + $options = $this->getOptions(); + return empty($options['schema']) ? 'public' : $options['schema']; + } + + /** + * Cast a value to a boolean appropriate for the adapter. + * + * @param mixed $value The value to be cast + * + * @return mixed + */ + public function castToBool($value) + { + return (bool) $value ? 'TRUE' : 'FALSE'; + } +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/ProxyAdapter.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/ProxyAdapter.php new file mode 100644 index 0000000..1236031 --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/ProxyAdapter.php @@ -0,0 +1,325 @@ + + */ +class ProxyAdapter extends AdapterWrapper +{ + /** + * @var array + */ + protected $commands; + + /** + * {@inheritdoc} + */ + public function getAdapterType() + { + return 'ProxyAdapter'; + } + /** + * {@inheritdoc} + */ + public function createTable(Table $table) + { + $this->recordCommand('createTable', array($table->getName())); + } + + /** + * {@inheritdoc} + */ + public function renameTable($tableName, $newTableName) + { + $this->recordCommand('renameTable', array($tableName, $newTableName)); + } + + /** + * {@inheritdoc} + */ + public function dropTable($tableName) + { + $this->recordCommand('dropTable', array($tableName)); + } + + /** + * {@inheritdoc} + */ + public function addColumn(Table $table, Column $column) + { + $this->recordCommand('addColumn', array($table, $column)); + } + + /** + * {@inheritdoc} + */ + public function renameColumn($tableName, $columnName, $newColumnName) + { + $this->recordCommand('renameColumn', array($tableName, $columnName, $newColumnName)); + } + + /** + * {@inheritdoc} + */ + public function changeColumn($tableName, $columnName, Column $newColumn) + { + $this->recordCommand('changeColumn', array($tableName, $columnName, $newColumn)); + } + + /** + * {@inheritdoc} + */ + public function dropColumn($tableName, $columnName) + { + $this->recordCommand('dropColumn', array($tableName, $columnName)); + } + + /** + * {@inheritdoc} + */ + public function addIndex(Table $table, Index $index) + { + $this->recordCommand('addIndex', array($table, $index)); + } + + /** + * {@inheritdoc} + */ + public function dropIndex($tableName, $columns, $options = array()) + { + $this->recordCommand('dropIndex', array($tableName, $columns, $options)); + } + + /** + * {@inheritdoc} + */ + public function dropIndexByName($tableName, $indexName) + { + $this->recordCommand('dropIndexByName', array($tableName, $indexName)); + } + + /** + * {@inheritdoc} + */ + public function addForeignKey(Table $table, ForeignKey $foreignKey) + { + $this->recordCommand('addForeignKey', array($table, $foreignKey)); + } + + /** + * {@inheritdoc} + */ + public function dropForeignKey($tableName, $columns, $constraint = null) + { + $this->recordCommand('dropForeignKey', array($columns, $constraint)); + } + + /** + * {@inheritdoc} + */ + public function createDatabase($name, $options = array()) + { + $this->recordCommand('createDatabase', array($name, $options)); + } + + /** + * Record a command for execution later. + * + * @param string $name Command Name + * @param array $arguments Command Arguments + * @return void + */ + public function recordCommand($name, $arguments) + { + $this->commands[] = array( + 'name' => $name, + 'arguments' => $arguments + ); + } + + /** + * Sets an array of recorded commands. + * + * @param array $commands Commands + * @return ProxyAdapter + */ + public function setCommands($commands) + { + $this->commands = $commands; + return $this; + } + + /** + * Gets an array of the recorded commands. + * + * @return array + */ + public function getCommands() + { + return $this->commands; + } + + /** + * Gets an array of the recorded commands in reverse. + * + * @throws IrreversibleMigrationException if a command cannot be reversed. + * @return array + */ + public function getInvertedCommands() + { + if (null === $this->getCommands()) { + return array(); + } + + $invCommands = array(); + $supportedCommands = array( + 'createTable', 'renameTable', 'addColumn', + 'renameColumn', 'addIndex', 'addForeignKey' + ); + foreach (array_reverse($this->getCommands()) as $command) { + if (!in_array($command['name'], $supportedCommands)) { + throw new IrreversibleMigrationException(sprintf( + 'Cannot reverse a "%s" command', + $command['name'] + )); + } + $invertMethod = 'invert' . ucfirst($command['name']); + $invertedCommand = $this->$invertMethod($command['arguments']); + $invCommands[] = array( + 'name' => $invertedCommand['name'], + 'arguments' => $invertedCommand['arguments'] + ); + } + + return $invCommands; + } + + /** + * Execute the recorded commands. + * + * @return void + */ + public function executeCommands() + { + $commands = $this->getCommands(); + foreach ($commands as $command) { + call_user_func_array(array($this->getAdapter(), $command['name']), $command['arguments']); + } + } + + /** + * Execute the recorded commands in reverse. + * + * @return void + */ + public function executeInvertedCommands() + { + $commands = $this->getInvertedCommands(); + foreach ($commands as $command) { + call_user_func_array(array($this->getAdapter(), $command['name']), $command['arguments']); + } + } + + /** + * Returns the reverse of a createTable command. + * + * @param array $args Method Arguments + * @return array + */ + public function invertCreateTable($args) + { + return array('name' => 'dropTable', 'arguments' => array($args[0])); + } + + /** + * Returns the reverse of a renameTable command. + * + * @param array $args Method Arguments + * @return array + */ + public function invertRenameTable($args) + { + return array('name' => 'renameTable', 'arguments' => array($args[1], $args[0])); + } + + /** + * Returns the reverse of a addColumn command. + * + * @param array $args Method Arguments + * @return array + */ + public function invertAddColumn($args) + { + return array('name' => 'dropColumn', 'arguments' => array($args[0]->getName(), $args[1]->getName())); + } + + /** + * Returns the reverse of a renameColumn command. + * + * @param array $args Method Arguments + * @return array + */ + public function invertRenameColumn($args) + { + return array('name' => 'renameColumn', 'arguments' => array($args[0], $args[2], $args[1])); + } + + /** + * Returns the reverse of a addIndex command. + * + * @param array $args Method Arguments + * @return array + */ + public function invertAddIndex($args) + { + return array('name' => 'dropIndex', 'arguments' => array($args[0]->getName(), $args[1]->getColumns())); + } + + /** + * Returns the reverse of a addForeignKey command. + * + * @param array $args Method Arguments + * @return array + */ + public function invertAddForeignKey($args) + { + return array('name' => 'dropForeignKey', 'arguments' => array($args[0]->getName(), $args[1]->getColumns())); + } +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/SQLiteAdapter.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/SQLiteAdapter.php new file mode 100644 index 0000000..4ff7b74 --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/SQLiteAdapter.php @@ -0,0 +1,1136 @@ + + * @author Richard McIntyre + */ +class SQLiteAdapter extends PdoAdapter implements AdapterInterface +{ + protected $definitionsWithLimits = array( + 'CHARACTER', + 'VARCHAR', + 'VARYING CHARACTER', + 'NCHAR', + 'NATIVE CHARACTER', + 'NVARCHAR' + ); + + /** + * {@inheritdoc} + */ + public function connect() + { + if (null === $this->connection) { + if (!class_exists('PDO') || !in_array('sqlite', \PDO::getAvailableDrivers(), true)) { + // @codeCoverageIgnoreStart + throw new \RuntimeException('You need to enable the PDO_SQLITE extension for Phinx to run properly.'); + // @codeCoverageIgnoreEnd + } + + $db = null; + $options = $this->getOptions(); + + // if port is specified use it, otherwise use the MySQL default + if (isset($options['memory'])) { + $dsn = 'sqlite::memory:'; + } else { + $dsn = 'sqlite:' . $options['name']; + if (file_exists($options['name'] . '.sqlite3')) { + $dsn = 'sqlite:' . $options['name'] . '.sqlite3'; + } + } + + try { + $db = new \PDO($dsn); + } catch (\PDOException $exception) { + throw new \InvalidArgumentException(sprintf( + 'There was a problem connecting to the database: %s', + $exception->getMessage() + )); + } + + $this->setConnection($db); + } + } + + /** + * {@inheritdoc} + */ + public function disconnect() + { + $this->connection = null; + } + + /** + * {@inheritdoc} + */ + public function hasTransactions() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function beginTransaction() + { + $this->execute('BEGIN TRANSACTION'); + } + + /** + * {@inheritdoc} + */ + public function commitTransaction() + { + $this->execute('COMMIT'); + } + + /** + * {@inheritdoc} + */ + public function rollbackTransaction() + { + $this->execute('ROLLBACK'); + } + + /** + * {@inheritdoc} + */ + public function quoteTableName($tableName) + { + return str_replace('.', '`.`', $this->quoteColumnName($tableName)); + } + + /** + * {@inheritdoc} + */ + public function quoteColumnName($columnName) + { + return '`' . str_replace('`', '``', $columnName) . '`'; + } + + /** + * {@inheritdoc} + */ + public function hasTable($tableName) + { + $tables = array(); + $rows = $this->fetchAll(sprintf('SELECT name FROM sqlite_master WHERE type=\'table\' AND name=\'%s\'', $tableName)); + foreach ($rows as $row) { + $tables[] = strtolower($row[0]); + } + + return in_array(strtolower($tableName), $tables); + } + + /** + * {@inheritdoc} + */ + public function createTable(Table $table) + { + $this->startCommandTimer(); + + // Add the default primary key + $columns = $table->getPendingColumns(); + $options = $table->getOptions(); + if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) { + $column = new Column(); + $column->setName('id') + ->setType('integer') + ->setIdentity(true); + + array_unshift($columns, $column); + + } elseif (isset($options['id']) && is_string($options['id'])) { + // Handle id => "field_name" to support AUTO_INCREMENT + $column = new Column(); + $column->setName($options['id']) + ->setType('integer') + ->setIdentity(true); + + array_unshift($columns, $column); + } + + $sql = 'CREATE TABLE '; + $sql .= $this->quoteTableName($table->getName()) . ' ('; + foreach ($columns as $column) { + $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', '; + } + + // set the primary key(s) + if (isset($options['primary_key'])) { + $sql = rtrim($sql); + $sql .= ' PRIMARY KEY ('; + if (is_string($options['primary_key'])) { // handle primary_key => 'id' + $sql .= $this->quoteColumnName($options['primary_key']); + } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id') + // PHP 5.4 will allow access of $this, so we can call quoteColumnName() directly in the anonymous function, + // but for now just hard-code the adapter quotes + $sql .= implode( + ',', + array_map( + function ($v) { + return '`' . $v . '`'; + }, + $options['primary_key'] + ) + ); + } + $sql .= ')'; + } else { + $sql = substr(rtrim($sql), 0, -1); // no primary keys + } + + // set the foreign keys + $foreignKeys = $table->getForeignKeys(); + if (!empty($foreignKeys)) { + foreach ($foreignKeys as $foreignKey) { + $sql .= ', ' . $this->getForeignKeySqlDefinition($foreignKey); + } + } + + $sql = rtrim($sql) . ');'; + // execute the sql + $this->writeCommand('createTable', array($table->getName())); + $this->execute($sql); + $this->endCommandTimer(); + + foreach ($table->getIndexes() as $index) { + $this->addIndex($table, $index); + } + } + + /** + * {@inheritdoc} + */ + public function renameTable($tableName, $newTableName) + { + $this->startCommandTimer(); + $this->writeCommand('renameTable', array($tableName, $newTableName)); + $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $this->quoteTableName($tableName), $this->quoteTableName($newTableName))); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropTable($tableName) + { + $this->startCommandTimer(); + $this->writeCommand('dropTable', array($tableName)); + $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tableName))); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function getColumns($tableName) + { + $columns = array(); + $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName))); + + foreach ($rows as $columnInfo) { + $column = new Column(); + $type = strtolower($columnInfo['type']); + $column->setName($columnInfo['name']) + ->setNull($columnInfo['notnull'] !== '1') + ->setDefault($columnInfo['dflt_value']); + + $phinxType = $this->getPhinxType($type); + $column->setType($phinxType['name']) + ->setLimit($phinxType['limit']); + + if ($columnInfo['pk'] == 1) { + $column->setIdentity(true); + } + + $columns[] = $column; + } + + return $columns; + } + + /** + * {@inheritdoc} + */ + public function hasColumn($tableName, $columnName) + { + $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName))); + foreach ($rows as $column) { + if (strcasecmp($column['name'], $columnName) === 0) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function addColumn(Table $table, Column $column) + { + $this->startCommandTimer(); + + $sql = sprintf( + 'ALTER TABLE %s ADD COLUMN %s %s', + $this->quoteTableName($table->getName()), + $this->quoteColumnName($column->getName()), + $this->getColumnSqlDefinition($column) + ); + + $this->writeCommand('addColumn', array($table->getName(), $column->getName(), $column->getType())); + $this->execute($sql); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function renameColumn($tableName, $columnName, $newColumnName) + { + $tmpTableName = 'tmp_' . $tableName; + + $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\''); + + $sql = ''; + foreach ($rows as $table) { + if ($table['tbl_name'] === $tableName) { + $sql = $table['sql']; + } + } + + $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName))); + $selectColumns = array(); + $writeColumns = array(); + foreach ($columns as $column) { + $selectName = $column['name']; + $writeName = ($selectName == $columnName) ? $newColumnName : $selectName; + $selectColumns[] = $this->quoteColumnName($selectName); + $writeColumns[] = $this->quoteColumnName($writeName); + } + + if (!in_array($this->quoteColumnName($columnName), $selectColumns)) { + throw new \InvalidArgumentException(sprintf( + 'The specified column doesn\'t exist: ' . $columnName + )); + } + + $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $tableName, $tmpTableName)); + + $sql = str_replace( + $this->quoteColumnName($columnName), + $this->quoteColumnName($newColumnName), + $sql + ); + $this->execute($sql); + + $sql = sprintf( + 'INSERT INTO %s(%s) SELECT %s FROM %s', + $tableName, + implode(', ', $writeColumns), + implode(', ', $selectColumns), + $tmpTableName + ); + + $this->execute($sql); + + $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName))); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function changeColumn($tableName, $columnName, Column $newColumn) + { + + // TODO: DRY this up.... + + $this->startCommandTimer(); + $this->writeCommand('changeColumn', array($tableName, $columnName, $newColumn->getType())); + + $tmpTableName = 'tmp_' . $tableName; + + $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\''); + + $sql = ''; + foreach ($rows as $table) { + if ($table['tbl_name'] === $tableName) { + $sql = $table['sql']; + } + } + + $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName))); + $selectColumns = array(); + $writeColumns = array(); + foreach ($columns as $column) { + $selectName = $column['name']; + $writeName = ($selectName === $columnName) ? $newColumn->getName() : $selectName; + $selectColumns[] = $this->quoteColumnName($selectName); + $writeColumns[] = $this->quoteColumnName($writeName); + } + + if (!in_array($this->quoteColumnName($columnName), $selectColumns)) { + throw new \InvalidArgumentException(sprintf( + 'The specified column doesn\'t exist: ' . $columnName + )); + } + + $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $tableName, $tmpTableName)); + + $sql = preg_replace( + sprintf("/%s[^,]+([,)])/", $this->quoteColumnName($columnName)), + sprintf('%s %s$1', $this->quoteColumnName($newColumn->getName()), $this->getColumnSqlDefinition($newColumn)), + $sql, + 1 + ); + + $this->execute($sql); + + $sql = sprintf( + 'INSERT INTO %s(%s) SELECT %s FROM %s', + $tableName, + implode(', ', $writeColumns), + implode(', ', $selectColumns), + $tmpTableName + ); + + $this->execute($sql); + $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName))); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropColumn($tableName, $columnName) + { + // TODO: DRY this up.... + + $this->startCommandTimer(); + $this->writeCommand('dropColumn', array($tableName, $columnName)); + + $tmpTableName = 'tmp_' . $tableName; + + $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\''); + + $sql = ''; + foreach ($rows as $table) { + if ($table['tbl_name'] === $tableName) { + $sql = $table['sql']; + } + } + + $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName))); + $columns = array(); + $columnType = null; + foreach ($rows as $row) { + if ($row['name'] !== $columnName) { + $columns[] = $row['name']; + } else { + $found = true; + $columnType = $row['type']; + } + } + + if (!isset($found)) { + throw new \InvalidArgumentException(sprintf( + 'The specified column doesn\'t exist: ' . $columnName + )); + } + + $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $tableName, $tmpTableName)); + + $sql = preg_replace( + sprintf("/%s\s%s[^,)]*(,\s|\))/", preg_quote($this->quoteColumnName($columnName)), preg_quote($columnType)), + "", + $sql + ); + + if (substr($sql, -2) === ', ') { + $sql = substr($sql, 0, -2) . ')'; + } + + $this->execute($sql); + + $sql = sprintf( + 'INSERT INTO %s(%s) SELECT %s FROM %s', + $tableName, + implode(', ', $columns), + implode(', ', $columns), + $tmpTableName + ); + + $this->execute($sql); + $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName))); + $this->endCommandTimer(); + } + + /** + * Get an array of indexes from a particular table. + * + * @param string $tableName Table Name + * @return array + */ + protected function getIndexes($tableName) + { + $indexes = array(); + $rows = $this->fetchAll(sprintf('pragma index_list(%s)', $tableName)); + + foreach ($rows as $row) { + $indexData = $this->fetchAll(sprintf('pragma index_info(%s)', $row['name'])); + if (!isset($indexes[$tableName])) { + $indexes[$tableName] = array('index' => $row['name'], 'columns' => array()); + } + foreach ($indexData as $indexItem) { + $indexes[$tableName]['columns'][] = strtolower($indexItem['name']); + } + } + return $indexes; + } + + /** + * {@inheritdoc} + */ + public function hasIndex($tableName, $columns) + { + if (is_string($columns)) { + $columns = array($columns); // str to array + } + + $columns = array_map('strtolower', $columns); + $indexes = $this->getIndexes($tableName); + + foreach ($indexes as $index) { + $a = array_diff($columns, $index['columns']); + if (empty($a)) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function hasIndexByName($tableName, $indexName) + { + $indexes = $this->getIndexes($tableName); + + foreach ($indexes as $index) { + if ($indexName === $index['index']) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function addIndex(Table $table, Index $index) + { + $this->startCommandTimer(); + $this->writeCommand('addIndex', array($table->getName(), $index->getColumns())); + $indexColumnArray = array(); + foreach ($index->getColumns() as $column) { + $indexColumnArray []= sprintf('`%s` ASC', $column); + } + $indexColumns = implode(',', $indexColumnArray); + $this->execute( + sprintf( + 'CREATE %s ON %s (%s)', + $this->getIndexSqlDefinition($table, $index), + $this->quoteTableName($table->getName()), + $indexColumns + ) + ); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropIndex($tableName, $columns) + { + $this->startCommandTimer(); + if (is_string($columns)) { + $columns = array($columns); // str to array + } + + $this->writeCommand('dropIndex', array($tableName, $columns)); + $indexes = $this->getIndexes($tableName); + $columns = array_map('strtolower', $columns); + + foreach ($indexes as $index) { + $a = array_diff($columns, $index['columns']); + if (empty($a)) { + $this->execute( + sprintf( + 'DROP INDEX %s', + $this->quoteColumnName($index['index']) + ) + ); + $this->endCommandTimer(); + return; + } + } + } + + /** + * {@inheritdoc} + */ + public function dropIndexByName($tableName, $indexName) + { + $this->startCommandTimer(); + + $this->writeCommand('dropIndexByName', array($tableName, $indexName)); + $indexes = $this->getIndexes($tableName); + + foreach ($indexes as $index) { + if ($indexName === $index['index']) { + $this->execute( + sprintf( + 'DROP INDEX %s', + $this->quoteColumnName($indexName) + ) + ); + $this->endCommandTimer(); + return; + } + } + } + + /** + * {@inheritdoc} + */ + public function hasForeignKey($tableName, $columns, $constraint = null) + { + if (is_string($columns)) { + $columns = array($columns); // str to array + } + $foreignKeys = $this->getForeignKeys($tableName); + + $a = array_diff($columns, $foreignKeys); + if (empty($a)) { + return true; + } + return false; + } + + /** + * Get an array of foreign keys from a particular table. + * + * @param string $tableName Table Name + * @return array + */ + protected function getForeignKeys($tableName) + { + $foreignKeys = array(); + $rows = $this->fetchAll( + "SELECT sql, tbl_name + FROM ( + SELECT sql sql, type type, tbl_name tbl_name, name name + FROM sqlite_master + UNION ALL + SELECT sql, type, tbl_name, name + FROM sqlite_temp_master + ) + WHERE type != 'meta' + AND sql NOTNULL + AND name NOT LIKE 'sqlite_%' + ORDER BY substr(type, 2, 1), name" + ); + + foreach ($rows as $row) { + if ($row['tbl_name'] === $tableName) { + + if (strpos($row['sql'], 'REFERENCES') !== false) { + preg_match_all("/\(`([^`]*)`\) REFERENCES/", $row['sql'], $matches); + foreach ($matches[1] as $match) { + $foreignKeys[] = $match; + } + } + } + } + return $foreignKeys; + } + + /** + * {@inheritdoc} + */ + public function addForeignKey(Table $table, ForeignKey $foreignKey) + { + // TODO: DRY this up.... + $this->startCommandTimer(); + $this->writeCommand('addForeignKey', array($table->getName(), $foreignKey->getColumns())); + $this->execute('pragma foreign_keys = ON'); + + $tmpTableName = 'tmp_' . $table->getName(); + $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\''); + + $sql = ''; + foreach ($rows as $row) { + if ($row['tbl_name'] === $table->getName()) { + $sql = $row['sql']; + } + } + + $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($table->getName()))); + $columns = array(); + foreach ($rows as $column) { + $columns[] = $this->quoteColumnName($column['name']); + } + + $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $this->quoteTableName($table->getName()), $tmpTableName)); + + $sql = substr($sql, 0, -1) . ',' . $this->getForeignKeySqlDefinition($foreignKey) . ')'; + $this->execute($sql); + + $sql = sprintf( + 'INSERT INTO %s(%s) SELECT %s FROM %s', + $this->quoteTableName($table->getName()), + implode(', ', $columns), + implode(', ', $columns), + $this->quoteTableName($tmpTableName) + ); + + $this->execute($sql); + $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName))); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropForeignKey($tableName, $columns, $constraint = null) + { + // TODO: DRY this up.... + + $this->startCommandTimer(); + if (is_string($columns)) { + $columns = array($columns); // str to array + } + + $this->writeCommand('dropForeignKey', array($tableName, $columns)); + + $tmpTableName = 'tmp_' . $tableName; + + $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\''); + + $sql = ''; + foreach ($rows as $table) { + if ($table['tbl_name'] === $tableName) { + $sql = $table['sql']; + } + } + + $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName))); + $replaceColumns = array(); + foreach ($rows as $row) { + if (!in_array($row['name'], $columns)) { + $replaceColumns[] = $row['name']; + } else { + $found = true; + } + } + + if (!isset($found)) { + throw new \InvalidArgumentException(sprintf( + 'The specified column doesn\'t exist: ' + )); + } + + $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $this->quoteTableName($tableName), $tmpTableName)); + + foreach ($columns as $columnName) { + $search = sprintf( + "/,[^,]*\(%s(?:,`?(.*)`?)?\) REFERENCES[^,]*\([^\)]*\)[^,)]*/", + $this->quoteColumnName($columnName) + ); + $sql = preg_replace($search, '', $sql, 1); + } + + $this->execute($sql); + + $sql = sprintf( + 'INSERT INTO %s(%s) SELECT %s FROM %s', + $tableName, + implode(', ', $columns), + implode(', ', $columns), + $tmpTableName + ); + + $this->execute($sql); + $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName))); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function insert(Table $table, $row) + { + $this->startCommandTimer(); + $this->writeCommand('insert', array($table->getName())); + + $sql = sprintf( + "INSERT INTO %s ", + $this->quoteTableName($table->getName()) + ); + + $columns = array_keys($row); + $sql .= "(". implode(', ', array_map(array($this, 'quoteColumnName'), $columns)) . ")"; + $sql .= " VALUES "; + + $sql .= "(" . implode(', ', array_map(function ($value) { + if (is_numeric($value)) { + return $value; + } + return "'{$value}'"; + }, $row)) . ")"; + + $this->execute($sql); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function getSqlType($type, $limit = null) + { + switch ($type) { + case static::PHINX_TYPE_STRING: + return array('name' => 'varchar', 'limit' => 255); + break; + case static::PHINX_TYPE_CHAR: + return array('name' => 'char', 'limit' => 255); + break; + case static::PHINX_TYPE_TEXT: + return array('name' => 'text'); + break; + case static::PHINX_TYPE_INTEGER: + return array('name' => 'integer'); + break; + case static::PHINX_TYPE_BIG_INTEGER: + return array('name' => 'bigint'); + break; + case static::PHINX_TYPE_FLOAT: + return array('name' => 'float'); + break; + case static::PHINX_TYPE_DECIMAL: + return array('name' => 'decimal'); + break; + case static::PHINX_TYPE_DATETIME: + return array('name' => 'datetime'); + break; + case static::PHINX_TYPE_TIMESTAMP: + return array('name' => 'datetime'); + break; + case static::PHINX_TYPE_TIME: + return array('name' => 'time'); + break; + case static::PHINX_TYPE_DATE: + return array('name' => 'date'); + break; + case static::PHINX_TYPE_BLOB: + case static::PHINX_TYPE_BINARY: + return array('name' => 'blob'); + break; + case static::PHINX_TYPE_BOOLEAN: + return array('name' => 'boolean'); + break; + case static::PHINX_TYPE_UUID: + return array('name' => 'char', 'limit' => 36); + case static::PHINX_TYPE_ENUM: + return array('name' => 'enum'); + // Geospatial database types + // No specific data types exist in SQLite, instead all geospatial + // functionality is handled in the client. See also: SpatiaLite. + case static::PHINX_TYPE_GEOMETRY: + case static::PHINX_TYPE_POLYGON: + return array('name' => 'text'); + return; + case static::PHINX_TYPE_LINESTRING: + return array('name' => 'varchar', 'limit' => 255); + break; + case static::PHINX_TYPE_POINT: + return array('name' => 'float'); + default: + throw new \RuntimeException('The type: "' . $type . '" is not supported.'); + } + } + + /** + * Returns Phinx type by SQL type + * + * @param string $sqlTypeDef SQL type + * @returns string Phinx type + */ + public function getPhinxType($sqlTypeDef) + { + if (!preg_match('/^([\w]+)(\(([\d]+)*(,([\d]+))*\))*$/', $sqlTypeDef, $matches)) { + throw new \RuntimeException('Column type ' . $sqlTypeDef . ' is not supported'); + } else { + $limit = null; + $precision = null; + $type = $matches[1]; + if (count($matches) > 2) { + $limit = $matches[3] ? $matches[3] : null; + } + if (count($matches) > 4) { + $precision = $matches[5]; + } + switch ($matches[1]) { + case 'varchar': + $type = static::PHINX_TYPE_STRING; + if ($limit === 255) { + $limit = null; + } + break; + case 'char': + $type = static::PHINX_TYPE_CHAR; + if ($limit === 255) { + $limit = null; + } + if ($limit === 36) { + $type = static::PHINX_TYPE_UUID; + } + break; + case 'int': + $type = static::PHINX_TYPE_INTEGER; + if ($limit === 11) { + $limit = null; + } + break; + case 'bigint': + if ($limit === 11) { + $limit = null; + } + $type = static::PHINX_TYPE_BIG_INTEGER; + break; + case 'blob': + $type = static::PHINX_TYPE_BINARY; + break; + } + if ($type === 'tinyint') { + if ($matches[3] === 1) { + $type = static::PHINX_TYPE_BOOLEAN; + $limit = null; + } + } + + $this->getSqlType($type); + + return array( + 'name' => $type, + 'limit' => $limit, + 'precision' => $precision + ); + } + } + + /** + * {@inheritdoc} + */ + public function createDatabase($name, $options = array()) + { + $this->startCommandTimer(); + $this->writeCommand('createDatabase', array($name)); + touch($name . '.sqlite3'); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function hasDatabase($name) + { + return is_file($name . '.sqlite3'); + } + + /** + * {@inheritdoc} + */ + public function dropDatabase($name) + { + $this->startCommandTimer(); + $this->writeCommand('dropDatabase', array($name)); + if (file_exists($name . '.sqlite3')) { + unlink($name . '.sqlite3'); + } + $this->endCommandTimer(); + } + + /** + * Get the definition for a `DEFAULT` statement. + * + * @param mixed $default + * @return string + */ + protected function getDefaultValueDefinition($default) + { + if (is_string($default) && 'CURRENT_TIMESTAMP' !== $default) { + $default = $this->getConnection()->quote($default); + } elseif (is_bool($default)) { + $default = $this->castToBool($default); + } + return isset($default) ? ' DEFAULT ' . $default : ''; + } + + /** + * Gets the SQLite Column Definition for a Column object. + * + * @param Column $column Column + * @return string + */ + protected function getColumnSqlDefinition(Column $column) + { + $sqlType = $this->getSqlType($column->getType()); + $def = ''; + $def .= strtoupper($sqlType['name']); + if ($column->getPrecision() && $column->getScale()) { + $def .= '(' . $column->getPrecision() . ',' . $column->getScale() . ')'; + } + $limitable = in_array(strtoupper($sqlType['name']), $this->definitionsWithLimits); + if (($column->getLimit() || isset($sqlType['limit'])) && $limitable) { + $def .= '(' . ($column->getLimit() ? $column->getLimit() : $sqlType['limit']) . ')'; + } + if (($values = $column->getValues()) && is_array($values)) { + $def .= " CHECK({$column->getName()} IN ('" . implode("', '", $values) . "'))"; + } + + $default = $column->getDefault(); + + $def .= ($column->isNull() || is_null($default)) ? ' NULL' : ' NOT NULL'; + $def .= $this->getDefaultValueDefinition($default); + $def .= ($column->isIdentity()) ? ' PRIMARY KEY AUTOINCREMENT' : ''; + + if ($column->getUpdate()) { + $def .= ' ON UPDATE ' . $column->getUpdate(); + } + + $def .= $this->getCommentDefinition($column); + + return $def; + } + + /** + * Gets the comment Definition for a Column object. + * + * @param Column $column Column + * @return string + */ + protected function getCommentDefinition(Column $column) + { + if ($column->getComment()) { + return ' /* ' . $column->getComment() . ' */ '; + } + return ''; + } + + /** + * Gets the SQLite Index Definition for an Index object. + * + * @param Index $index Index + * @return string + */ + protected function getIndexSqlDefinition(Table $table, Index $index) + { + if ($index->getType() === Index::UNIQUE) { + $def = 'UNIQUE INDEX'; + } else { + $def = 'INDEX'; + } + if (is_string($index->getName())) { + $indexName = $index->getName(); + } else { + $indexName = $table->getName() . '_'; + foreach ($index->getColumns() as $column) { + $indexName .= $column . '_'; + } + $indexName .= 'index'; + } + $def .= ' `' . $indexName . '`'; + return $def; + } + + /** + * {@inheritdoc} + */ + public function getColumnTypes() + { + return array_merge(parent::getColumnTypes(), array('enum')); + } + + /** + * Gets the SQLite Foreign Key Definition for an ForeignKey object. + * + * @param ForeignKey $foreignKey + * @return string + */ + protected function getForeignKeySqlDefinition(ForeignKey $foreignKey) + { + $def = ''; + if ($foreignKey->getConstraint()) { + $def .= ' CONSTRAINT ' . $this->quoteColumnName($foreignKey->getConstraint()); + } else { + $columnNames = array(); + foreach ($foreignKey->getColumns() as $column) { + $columnNames[] = $this->quoteColumnName($column); + } + $def .= ' FOREIGN KEY (' . implode(',', $columnNames) . ')'; + $refColumnNames = array(); + foreach ($foreignKey->getReferencedColumns() as $column) { + $refColumnNames[] = $this->quoteColumnName($column); + } + $def .= ' REFERENCES ' . $this->quoteTableName($foreignKey->getReferencedTable()->getName()) . ' (' . implode(',', $refColumnNames) . ')'; + if ($foreignKey->getOnDelete()) { + $def .= ' ON DELETE ' . $foreignKey->getOnDelete(); + } + if ($foreignKey->getOnUpdate()) { + $def .= ' ON UPDATE ' . $foreignKey->getOnUpdate(); + } + } + return $def; + } +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/SqlServerAdapter.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/SqlServerAdapter.php new file mode 100644 index 0000000..99f8a82 --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/SqlServerAdapter.php @@ -0,0 +1,1165 @@ + + */ +class SqlServerAdapter extends PdoAdapter implements AdapterInterface +{ + protected $schema = 'dbo'; + + protected $signedColumnTypes = array('integer' => true, 'biginteger' => true, 'float' => true, 'decimal' => true); + + /** + * {@inheritdoc} + */ + public function connect() + { + if (null === $this->connection) { + if (!class_exists('PDO') || !in_array('sqlsrv', \PDO::getAvailableDrivers(), true)) { + // try our connection via freetds (Mac/Linux) + return $this->connectDblib(); + } + + $db = null; + $options = $this->getOptions(); + + // if port is specified use it, otherwise use the SqlServer default + if (empty($options['port'])) { + $dsn = 'sqlsrv:server=' . $options['host'] . ';database=' . $options['name']; + } else { + $dsn = 'sqlsrv:server=' . $options['host'] . ',' . $options['port'] . ';database=' . $options['name']; + } + $dsn .= ';MultipleActiveResultSets=false'; + + $driverOptions = array(\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION); + + // charset support + if (isset($options['charset'])) { + $driverOptions[\PDO::SQLSRV_ATTR_ENCODING] = $options['charset']; + } + + // support arbitrary \PDO::SQLSRV_ATTR_* driver options and pass them to PDO + // http://php.net/manual/en/ref.pdo-sqlsrv.php#pdo-sqlsrv.constants + foreach ($options as $key => $option) { + if (strpos($key, 'sqlsrv_attr_') === 0) { + $driverOptions[constant('\PDO::' . strtoupper($key))] = $option; + } + } + + try { + $db = new \PDO($dsn, $options['user'], $options['pass'], $driverOptions); + } catch (\PDOException $exception) { + throw new \InvalidArgumentException(sprintf( + 'There was a problem connecting to the database: %s', + $exception->getMessage() + )); + } + + $this->setConnection($db); + } + } + + /** + * Connect to MSSQL using dblib/freetds. + * + * The "sqlsrv" driver is not available on Unix machines. + * + * @throws \InvalidArgumentException + */ + protected function connectDblib() + { + if (!class_exists('PDO') || !in_array('dblib', \PDO::getAvailableDrivers(), true)) { + // @codeCoverageIgnoreStart + throw new \RuntimeException('You need to enable the PDO_Dblib extension for Phinx to run properly.'); + // @codeCoverageIgnoreEnd + } + + $options = $this->getOptions(); + + // if port is specified use it, otherwise use the SqlServer default + if (empty($options['port'])) { + $dsn = 'dblib:host=' . $options['host'] . ';dbname=' . $options['name']; + } else { + $dsn = 'dblib:host=' . $options['host'] . ':' . $options['port'] . ';dbname=' . $options['name']; + } + + $driverOptions = array(\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION); + + try { + $db = new \PDO($dsn, $options['user'], $options['pass'], $driverOptions); + } catch (\PDOException $exception) { + throw new \InvalidArgumentException(sprintf( + 'There was a problem connecting to the database: %s', + $exception->getMessage() + )); + } + + $this->setConnection($db); + } + + /** + * {@inheritdoc} + */ + public function disconnect() + { + $this->connection = null; + } + + /** + * {@inheritdoc} + */ + public function hasTransactions() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function beginTransaction() + { + $this->execute('BEGIN TRANSACTION'); + } + + /** + * {@inheritdoc} + */ + public function commitTransaction() + { + $this->execute('COMMIT TRANSACTION'); + } + + /** + * {@inheritdoc} + */ + public function rollbackTransaction() + { + $this->execute('ROLLBACK TRANSACTION'); + } + + /** + * {@inheritdoc} + */ + public function quoteTableName($tableName) + { + return str_replace('.', '].[', $this->quoteColumnName($tableName)); + } + + /** + * {@inheritdoc} + */ + public function quoteColumnName($columnName) + { + return '[' . str_replace(']', '\]', $columnName) . ']'; + } + + /** + * {@inheritdoc} + */ + public function hasTable($tableName) + { + $result = $this->fetchRow(sprintf('SELECT count(*) as [count] FROM information_schema.tables WHERE table_name = \'%s\';', $tableName)); + return $result['count'] > 0; + } + + /** + * {@inheritdoc} + */ + public function createTable(Table $table) + { + $this->startCommandTimer(); + + $options = $table->getOptions(); + + // Add the default primary key + $columns = $table->getPendingColumns(); + if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) { + $column = new Column(); + $column->setName('id') + ->setType('integer') + ->setIdentity(true); + + array_unshift($columns, $column); + $options['primary_key'] = 'id'; + + } elseif (isset($options['id']) && is_string($options['id'])) { + // Handle id => "field_name" to support AUTO_INCREMENT + $column = new Column(); + $column->setName($options['id']) + ->setType('integer') + ->setIdentity(true); + + array_unshift($columns, $column); + $options['primary_key'] = $options['id']; + } + + $sql = 'CREATE TABLE '; + $sql .= $this->quoteTableName($table->getName()) . ' ('; + $sqlBuffer = array(); + $columnsWithComments = array(); + foreach ($columns as $column) { + $sqlBuffer[] = $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column); + + // set column comments, if needed + if ($column->getComment()) { + $columnsWithComments[] = $column; + } + } + + // set the primary key(s) + if (isset($options['primary_key'])) { + $pkSql = sprintf('CONSTRAINT PK_%s PRIMARY KEY (', $table->getName()); + if (is_string($options['primary_key'])) { // handle primary_key => 'id' + $pkSql .= $this->quoteColumnName($options['primary_key']); + } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id') + // PHP 5.4 will allow access of $this, so we can call quoteColumnName() directly in the anonymous function, + // but for now just hard-code the adapter quotes + $pkSql .= implode( + ',', + array_map( + function ($v) { + return '[' . $v . ']'; + }, + $options['primary_key'] + ) + ); + } + $pkSql .= ')'; + $sqlBuffer[] = $pkSql; + } + + // set the foreign keys + $foreignKeys = $table->getForeignKeys(); + if (!empty($foreignKeys)) { + foreach ($foreignKeys as $foreignKey) { + $sqlBuffer[] = $this->getForeignKeySqlDefinition($foreignKey, $table->getName()); + } + } + + $sql .= implode(', ', $sqlBuffer); + $sql .= ');'; + + // process column comments + if (!empty($columnsWithComments)) { + foreach ($columnsWithComments as $column) { + $sql .= $this->getColumnCommentSqlDefinition($column, $table->getName()); + } + } + + // set the indexes + $indexes = $table->getIndexes(); + if (!empty($indexes)) { + foreach ($indexes as $index) { + $sql .= $this->getIndexSqlDefinition($index, $table->getName()); + } + } + + // execute the sql + $this->writeCommand('createTable', array($table->getName())); + $this->execute($sql); + $this->endCommandTimer(); + } + + /** + * Gets the SqlServer Column Comment Defininition for a column object. + * + * @param Column $column Column + * @param string $tableName Table name + * + * @return string + */ + protected function getColumnCommentSqlDefinition(Column $column, $tableName) + { + // passing 'null' is to remove column comment + $currentComment = $this->getColumnComment($tableName, $column->getName()); + + $comment = (strcasecmp($column->getComment(), 'NULL') !== 0) ? $this->getConnection()->quote($column->getComment()) : '\'\''; + $command = $currentComment === false ? 'sp_addextendedproperty' : 'sp_updateextendedproperty'; + return sprintf( + "EXECUTE %s N'MS_Description', N%s, N'SCHEMA', N'%s', N'TABLE', N'%s', N'COLUMN', N'%s';", + $command, + $comment, + $this->schema, + $tableName, + $column->getName() + ); + } + + /** + * {@inheritdoc} + */ + public function renameTable($tableName, $newTableName) + { + $this->startCommandTimer(); + $this->writeCommand('renameTable', array($tableName, $newTableName)); + $this->execute(sprintf('EXEC sp_rename \'%s\', \'%s\'', $tableName, $newTableName)); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropTable($tableName) + { + $this->startCommandTimer(); + $this->writeCommand('dropTable', array($tableName)); + $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tableName))); + $this->endCommandTimer(); + } + + public function getColumnComment($tableName, $columnName) + { + $sql = sprintf("SELECT cast(extended_properties.[value] as nvarchar(4000)) comment + FROM sys.schemas + INNER JOIN sys.tables + ON schemas.schema_id = tables.schema_id + INNER JOIN sys.columns + ON tables.object_id = columns.object_id + INNER JOIN sys.extended_properties + ON tables.object_id = extended_properties.major_id + AND columns.column_id = extended_properties.minor_id + AND extended_properties.name = 'MS_Description' + WHERE schemas.[name] = '%s' AND tables.[name] = '%s' AND columns.[name] = '%s'", $this->schema, $tableName, $columnName); + $row = $this->fetchRow($sql); + + if ($row) { + return $row['comment']; + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function getColumns($tableName) + { + $columns = array(); + $sql = sprintf( + "SELECT DISTINCT TABLE_SCHEMA AS [schema], TABLE_NAME as [table_name], COLUMN_NAME AS [name], DATA_TYPE AS [type], + IS_NULLABLE AS [null], COLUMN_DEFAULT AS [default], + CHARACTER_MAXIMUM_LENGTH AS [char_length], + NUMERIC_PRECISION AS [precision], + NUMERIC_SCALE AS [scale], ORDINAL_POSITION AS [ordinal_position], + COLUMNPROPERTY(object_id(TABLE_NAME), COLUMN_NAME, 'IsIdentity') as [identity] + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_NAME = '%s' + ORDER BY ordinal_position", + $tableName + ); + $rows = $this->fetchAll($sql); + foreach ($rows as $columnInfo) { + $column = new Column(); + $column->setName($columnInfo['name']) + ->setType($this->getPhinxType($columnInfo['type'])) + ->setNull($columnInfo['null'] !== 'NO') + ->setDefault($this->parseDefault($columnInfo['default'])) + ->setIdentity($columnInfo['identity'] === '1') + ->setComment($this->getColumnComment($columnInfo['table_name'], $columnInfo['name'])); + + if (!empty($columnInfo['char_length'])) { + $column->setLimit($columnInfo['char_length']); + } + + $columns[$columnInfo['name']] = $column; + } + + return $columns; + } + + protected function parseDefault($default) + { + $default = preg_replace(array("/\('(.*)'\)/", "/\(\((.*)\)\)/", "/\((.*)\)/"), '$1', $default); + + if (strtoupper($default) === 'NULL') { + $default = null; + } elseif (is_numeric($default)) { + $default = (int) $default; + } + + return $default; + } + + /** + * {@inheritdoc} + */ + public function hasColumn($tableName, $columnName, $options = array()) + { + $sql = sprintf( + "SELECT count(*) as [count] + FROM information_schema.columns + WHERE table_name = '%s' AND column_name = '%s'", + $tableName, + $columnName + ); + $result = $this->fetchRow($sql); + + return $result['count'] > 0; + } + + /** + * {@inheritdoc} + */ + public function addColumn(Table $table, Column $column) + { + $this->startCommandTimer(); + $sql = sprintf( + 'ALTER TABLE %s ADD %s %s', + $this->quoteTableName($table->getName()), + $this->quoteColumnName($column->getName()), + $this->getColumnSqlDefinition($column) + ); + + $this->writeCommand('addColumn', array($table->getName(), $column->getName(), $column->getType())); + $this->execute($sql); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function renameColumn($tableName, $columnName, $newColumnName) + { + $this->startCommandTimer(); + + if (!$this->hasColumn($tableName, $columnName)) { + throw new \InvalidArgumentException("The specified column does not exist: $columnName"); + } + $this->writeCommand('renameColumn', array($tableName, $columnName, $newColumnName)); + $this->renameDefault($tableName, $columnName, $newColumnName); + $this->execute( + sprintf( + "EXECUTE sp_rename N'%s.%s', N'%s', 'COLUMN' ", + $tableName, + $columnName, + $newColumnName + ) + ); + $this->endCommandTimer(); + } + + protected function renameDefault($tableName, $columnName, $newColumnName) + { + $oldConstraintName = "DF_{$tableName}_{$columnName}"; + $newConstraintName = "DF_{$tableName}_{$newColumnName}"; + $sql = <<execute(sprintf( + $sql, + $oldConstraintName, + $newConstraintName + )); + } + + public function changeDefault($tableName, Column $newColumn) + { + $constraintName = "DF_{$tableName}_{$newColumn->getName()}"; + $default = $newColumn->getDefault(); + + if ($default === null) { + $default = 'DEFAULT NULL'; + } else { + $default = $this->getDefaultValueDefinition($default); + } + + if (empty($default)) { + return; + } + + $this->execute(sprintf( + 'ALTER TABLE %s ADD CONSTRAINT %s %s FOR %s', + $this->quoteTableName($tableName), + $constraintName, + $default, + $this->quoteColumnName($newColumn->getName()) + )); + } + + /** + * {@inheritdoc} + */ + public function changeColumn($tableName, $columnName, Column $newColumn) + { + $this->startCommandTimer(); + $this->writeCommand('changeColumn', array($tableName, $columnName, $newColumn->getType())); + $columns = $this->getColumns($tableName); + $changeDefault = $newColumn->getDefault() !== $columns[$columnName]->getDefault() || $newColumn->getType() !== $columns[$columnName]->getType(); + if ($columnName !== $newColumn->getName()) { + $this->renameColumn($tableName, $columnName, $newColumn->getName()); + } + + if ($changeDefault) { + $this->dropDefaultConstraint($tableName, $newColumn->getName()); + } + + $this->execute( + sprintf( + 'ALTER TABLE %s ALTER COLUMN %s %s', + $this->quoteTableName($tableName), + $this->quoteColumnName($newColumn->getName()), + $this->getColumnSqlDefinition($newColumn, false) + ) + ); + // change column comment if needed + if ($newColumn->getComment()) { + $sql = $this->getColumnCommentSqlDefinition($newColumn, $tableName); + $this->execute($sql); + } + + if ($changeDefault) { + $this->changeDefault($tableName, $newColumn); + } + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropColumn($tableName, $columnName) + { + $this->startCommandTimer(); + $this->writeCommand('dropColumn', array($tableName, $columnName)); + $this->dropDefaultConstraint($tableName, $columnName); + + $this->execute( + sprintf( + 'ALTER TABLE %s DROP COLUMN %s', + $this->quoteTableName($tableName), + $this->quoteColumnName($columnName) + ) + ); + $this->endCommandTimer(); + } + + protected function dropDefaultConstraint($tableName, $columnName) + { + $defaultConstraint = $this->getDefaultConstraint($tableName, $columnName); + + if (!$defaultConstraint) { + return; + } + + $this->dropForeignKey($tableName, $columnName, $defaultConstraint); + } + + protected function getDefaultConstraint($tableName, $columnName) + { + $sql = "SELECT + default_constraints.name +FROM + sys.all_columns + + INNER JOIN + sys.tables + ON all_columns.object_id = tables.object_id + + INNER JOIN + sys.schemas + ON tables.schema_id = schemas.schema_id + + INNER JOIN + sys.default_constraints + ON all_columns.default_object_id = default_constraints.object_id + +WHERE + schemas.name = 'dbo' + AND tables.name = '{$tableName}' + AND all_columns.name = '{$columnName}'"; + + $rows = $this->fetchAll($sql); + return empty($rows) ? false : $rows[0]['name']; + } + + protected function getIndexColums($tableId, $indexId) + { + $sql = "SELECT AC.[name] AS [column_name] +FROM sys.[index_columns] IC + INNER JOIN sys.[all_columns] AC ON IC.[column_id] = AC.[column_id] +WHERE AC.[object_id] = {$tableId} AND IC.[index_id] = {$indexId} AND IC.[object_id] = {$tableId} +ORDER BY IC.[key_ordinal];"; + + $rows = $this->fetchAll($sql); + $columns = array(); + foreach($rows as $row) { + $columns[] = strtolower($row['column_name']); + } + return $columns; + } + + /** + * Get an array of indexes from a particular table. + * + * @param string $tableName Table Name + * @return array + */ + public function getIndexes($tableName) + { + $indexes = array(); + $sql = "SELECT I.[name] AS [index_name], I.[index_id] as [index_id], T.[object_id] as [table_id] +FROM sys.[tables] AS T + INNER JOIN sys.[indexes] I ON T.[object_id] = I.[object_id] +WHERE T.[is_ms_shipped] = 0 AND I.[type_desc] <> 'HEAP' AND T.[name] = '{$tableName}' +ORDER BY T.[name], I.[index_id];"; + + $rows = $this->fetchAll($sql); + foreach ($rows as $row) { + $columns = $this->getIndexColums($row['table_id'], $row['index_id']); + $indexes[$row['index_name']] = array('columns' => $columns); + } + + return $indexes; + } + + /** + * {@inheritdoc} + */ + public function hasIndex($tableName, $columns) + { + if (is_string($columns)) { + $columns = array($columns); // str to array + } + + $columns = array_map('strtolower', $columns); + $indexes = $this->getIndexes($tableName); + + foreach ($indexes as $index) { + $a = array_diff($columns, $index['columns']); + + if (empty($a)) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function hasIndexByName($tableName, $indexName) + { + $indexes = $this->getIndexes($tableName); + + foreach ($indexes as $name => $index) { + if ($name === $indexName) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function addIndex(Table $table, Index $index) + { + $this->startCommandTimer(); + $this->writeCommand('addIndex', array($table->getName(), $index->getColumns())); + $sql = $this->getIndexSqlDefinition($index, $table->getName()); + $this->execute($sql); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropIndex($tableName, $columns) + { + $this->startCommandTimer(); + if (is_string($columns)) { + $columns = array($columns); // str to array + } + + $this->writeCommand('dropIndex', array($tableName, $columns)); + $indexes = $this->getIndexes($tableName); + $columns = array_map('strtolower', $columns); + + foreach ($indexes as $indexName => $index) { + $a = array_diff($columns, $index['columns']); + if (empty($a)) { + $this->execute( + sprintf( + 'DROP INDEX %s ON %s', + $this->quoteColumnName($indexName), + $this->quoteTableName($tableName) + ) + ); + $this->endCommandTimer(); + return; + } + } + } + + /** + * {@inheritdoc} + */ + public function dropIndexByName($tableName, $indexName) + { + $this->startCommandTimer(); + + $this->writeCommand('dropIndexByName', array($tableName, $indexName)); + $indexes = $this->getIndexes($tableName); + + foreach ($indexes as $name => $index) { + if ($name === $indexName) { + $this->execute( + sprintf( + 'DROP INDEX %s ON %s', + $this->quoteColumnName($indexName), + $this->quoteTableName($tableName) + ) + ); + $this->endCommandTimer(); + return; + } + } + } + + /** + * {@inheritdoc} + */ + public function hasForeignKey($tableName, $columns, $constraint = null) + { + if (is_string($columns)) { + $columns = array($columns); // str to array + } + $foreignKeys = $this->getForeignKeys($tableName); + if ($constraint) { + if (isset($foreignKeys[$constraint])) { + return !empty($foreignKeys[$constraint]); + } + return false; + } else { + foreach ($foreignKeys as $key) { + $a = array_diff($columns, $key['columns']); + if (empty($a)) { + return true; + } + } + return false; + } + } + + /** + * Get an array of foreign keys from a particular table. + * + * @param string $tableName Table Name + * @return array + */ + protected function getForeignKeys($tableName) + { + $foreignKeys = array(); + $rows = $this->fetchAll(sprintf( + "SELECT + tc.constraint_name, + tc.table_name, kcu.column_name, + ccu.table_name AS referenced_table_name, + ccu.column_name AS referenced_column_name + FROM + information_schema.table_constraints AS tc + JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name + JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name + WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = '%s' + ORDER BY kcu.ordinal_position", + $tableName + )); + foreach ($rows as $row) { + $foreignKeys[$row['constraint_name']]['table'] = $row['table_name']; + $foreignKeys[$row['constraint_name']]['columns'][] = $row['column_name']; + $foreignKeys[$row['constraint_name']]['referenced_table'] = $row['referenced_table_name']; + $foreignKeys[$row['constraint_name']]['referenced_columns'][] = $row['referenced_column_name']; + } + + return $foreignKeys; + } + + /** + * {@inheritdoc} + */ + public function addForeignKey(Table $table, ForeignKey $foreignKey) + { + $this->startCommandTimer(); + $this->writeCommand('addForeignKey', array($table->getName(), $foreignKey->getColumns())); + $this->execute( + sprintf( + 'ALTER TABLE %s ADD %s', + $this->quoteTableName($table->getName()), + $this->getForeignKeySqlDefinition($foreignKey, $table->getName()) + ) + ); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropForeignKey($tableName, $columns, $constraint = null) + { + $this->startCommandTimer(); + if (is_string($columns)) { + $columns = array($columns); // str to array + } + + $this->writeCommand('dropForeignKey', array($tableName, $columns)); + + if ($constraint) { + $this->execute( + sprintf( + 'ALTER TABLE %s DROP CONSTRAINT %s', + $this->quoteTableName($tableName), + $constraint + ) + ); + $this->endCommandTimer(); + return; + } else { + foreach ($columns as $column) { + $rows = $this->fetchAll(sprintf( + "SELECT + tc.constraint_name, + tc.table_name, kcu.column_name, + ccu.table_name AS referenced_table_name, + ccu.column_name AS referenced_column_name + FROM + information_schema.table_constraints AS tc + JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name + JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name + WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = '%s' and ccu.column_name='%s' + ORDER BY kcu.ordinal_position", + $tableName, + $column + )); + foreach ($rows as $row) { + $this->dropForeignKey($tableName, $columns, $row['constraint_name']); + } + } + } + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function getSqlType($type, $limit = null) + { + switch ($type) { + case static::PHINX_TYPE_STRING: + return array('name' => 'nvarchar', 'limit' => 255); + break; + case static::PHINX_TYPE_CHAR: + return array('name' => 'nchar', 'limit' => 255); + break; + case static::PHINX_TYPE_TEXT: + return array('name' => 'ntext'); + break; + case static::PHINX_TYPE_INTEGER: + return array('name' => 'int'); + break; + case static::PHINX_TYPE_BIG_INTEGER: + return array('name' => 'bigint'); + break; + case static::PHINX_TYPE_FLOAT: + return array('name' => 'float'); + break; + case static::PHINX_TYPE_DECIMAL: + return array('name' => 'decimal'); + break; + case static::PHINX_TYPE_DATETIME: + case static::PHINX_TYPE_TIMESTAMP: + return array('name' => 'datetime'); + break; + case static::PHINX_TYPE_TIME: + return array('name' => 'time'); + break; + case static::PHINX_TYPE_DATE: + return array('name' => 'date'); + break; + case static::PHINX_TYPE_BLOB: + case static::PHINX_TYPE_BINARY: + return array('name' => 'varbinary'); + break; + case static::PHINX_TYPE_BOOLEAN: + return array('name' => 'bit'); + break; + case static::PHINX_TYPE_UUID: + return array('name' => 'uniqueidentifier'); + case static::PHINX_TYPE_FILESTREAM: + return array('name' => 'varbinary', 'limit' => 'max'); + // Geospatial database types + case static::PHINX_TYPE_GEOMETRY: + case static::PHINX_TYPE_POINT: + case static::PHINX_TYPE_LINESTRING: + case static::PHINX_TYPE_POLYGON: + // SQL Server stores all spatial data using a single data type. + // Specific types (point, polygon, etc) are set at insert time. + return array('name' => 'geography'); + break; + default: + throw new \RuntimeException('The type: "' . $type . '" is not supported.'); + } + } + + /** + * Returns Phinx type by SQL type + * + * @param $sqlTypeDef + * @throws \RuntimeException + * @internal param string $sqlType SQL type + * @returns string Phinx type + */ + public function getPhinxType($sqlType) + { + switch ($sqlType) { + case 'nvarchar': + case 'varchar': + return static::PHINX_TYPE_STRING; + case 'char': + case 'nchar': + return static::PHINX_TYPE_CHAR; + case 'text': + case 'ntext': + return static::PHINX_TYPE_TEXT; + case 'int': + case 'integer': + return static::PHINX_TYPE_INTEGER; + case 'decimal': + case 'numeric': + case 'money': + return static::PHINX_TYPE_DECIMAL; + case 'bigint': + return static::PHINX_TYPE_BIG_INTEGER; + case 'real': + case 'float': + return static::PHINX_TYPE_FLOAT; + case 'binary': + case 'image': + case 'varbinary': + return static::PHINX_TYPE_BINARY; + break; + case 'time': + return static::PHINX_TYPE_TIME; + case 'date': + return static::PHINX_TYPE_DATE; + case 'datetime': + case 'timestamp': + return static::PHINX_TYPE_DATETIME; + case 'bit': + return static::PHINX_TYPE_BOOLEAN; + case 'uniqueidentifier': + return static::PHINX_TYPE_UUID; + case 'filestream': + return static::PHINX_TYPE_FILESTREAM; + default: + throw new \RuntimeException('The SqlServer type: "' . $sqlType . '" is not supported'); + } + } + + /** + * {@inheritdoc} + */ + public function createDatabase($name, $options = array()) + { + $this->startCommandTimer(); + $this->writeCommand('createDatabase', array($name)); + + if (isset($options['collation'])) { + $this->execute(sprintf('CREATE DATABASE [%s] COLLATE [%s]', $name, $options['collation'])); + } else { + $this->execute(sprintf('CREATE DATABASE [%s]', $name)); + } + $this->execute(sprintf('USE [%s]', $name)); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function hasDatabase($name) + { + $result = $this->fetchRow( + sprintf( + 'SELECT count(*) as [count] FROM master.dbo.sysdatabases WHERE [name] = \'%s\'', + $name + ) + ); + + return $result['count'] > 0; + } + + /** + * {@inheritdoc} + */ + public function dropDatabase($name) + { + $this->startCommandTimer(); + $this->writeCommand('dropDatabase', array($name)); + $sql = <<execute($sql); + $this->endCommandTimer(); + } + + /** + * Get the defintion for a `DEFAULT` statement. + * + * @param mixed $default + * @return string + */ + protected function getDefaultValueDefinition($default) + { + if (is_string($default) && 'CURRENT_TIMESTAMP' !== $default) { + $default = $this->getConnection()->quote($default); + } elseif (is_bool($default)) { + $default = $this->castToBool($default); + } + return isset($default) ? ' DEFAULT ' . $default : ''; + } + + /** + * Gets the SqlServer Column Definition for a Column object. + * + * @param Column $column Column + * @return string + */ + protected function getColumnSqlDefinition(Column $column, $create = true) + { + $buffer = array(); + + $sqlType = $this->getSqlType($column->getType()); + $buffer[] = strtoupper($sqlType['name']); + // integers cant have limits in SQlServer + $noLimits = array( + 'bigint', + 'int', + 'tinyint' + ); + if (!in_array($sqlType['name'], $noLimits) && ($column->getLimit() || isset($sqlType['limit']))) { + $buffer[] = sprintf('(%s)', $column->getLimit() ? $column->getLimit() : $sqlType['limit']); + } + if ($column->getPrecision() && $column->getScale()) { + $buffer[] = '(' . $column->getPrecision() . ',' . $column->getScale() . ')'; + } + + $properties = $column->getProperties(); + $buffer[] = $column->getType() === 'filestream' ? 'FILESTREAM' : ''; + $buffer[] = isset($properties['rowguidcol']) ? 'ROWGUIDCOL' : ''; + + $buffer[] = $column->isNull() ? 'NULL' : 'NOT NULL'; + + if ($create === true) { + if ($column->getDefault() === null && $column->isNull()) { + $buffer[] = ' DEFAULT NULL'; + } else { + $buffer[] = $this->getDefaultValueDefinition($column->getDefault()); + } + } + + if ($column->isIdentity()) { + $buffer[] = 'IDENTITY(1, 1)'; + } + + return implode(' ', $buffer); + } + + /** + * Gets the SqlServer Index Definition for an Index object. + * + * @param Index $index Index + * @return string + */ + protected function getIndexSqlDefinition(Index $index, $tableName) + { + if (is_string($index->getName())) { + $indexName = $index->getName(); + } else { + $columnNames = $index->getColumns(); + if (is_string($columnNames)) { + $columnNames = array($columnNames); + } + $indexName = sprintf('%s_%s', $tableName, implode('_', $columnNames)); + } + $def = sprintf( + "CREATE %s INDEX %s ON %s (%s);", + ($index->getType() === Index::UNIQUE ? 'UNIQUE' : ''), + $indexName, + $this->quoteTableName($tableName), + '[' . implode('],[', $index->getColumns()) . ']' + ); + + return $def; + } + + /** + * Gets the SqlServer Foreign Key Definition for an ForeignKey object. + * + * @param ForeignKey $foreignKey + * @return string + */ + protected function getForeignKeySqlDefinition(ForeignKey $foreignKey, $tableName) + { + $def = ' CONSTRAINT "'; + $def .= $tableName . '_' . implode('_', $foreignKey->getColumns()); + $def .= '" FOREIGN KEY ("' . implode('", "', $foreignKey->getColumns()) . '")'; + $def .= " REFERENCES {$this->quoteTableName($foreignKey->getReferencedTable()->getName())} (\"" . implode('", "', $foreignKey->getReferencedColumns()) . '")'; + if ($foreignKey->getOnDelete()) { + $def .= " ON DELETE {$foreignKey->getOnDelete()}"; + } + if ($foreignKey->getOnUpdate()) { + $def .= " ON UPDATE {$foreignKey->getOnUpdate()}"; + } + + return $def; + } + + /** + * {@inheritdoc} + */ + public function getColumnTypes() + { + return array_merge(parent::getColumnTypes(), array('filestream')); + } +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/TablePrefixAdapter.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/TablePrefixAdapter.php new file mode 100644 index 0000000..66a850c --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/TablePrefixAdapter.php @@ -0,0 +1,272 @@ + + */ +class TablePrefixAdapter extends AdapterWrapper +{ + /** + * {@inheritdoc} + */ + public function getAdapterType() + { + return 'TablePrefixAdapter'; + } + + /** + * {@inheritdoc} + */ + public function hasTable($tableName) + { + $adapterTableName = $this->getAdapterTableName($tableName); + return parent::hasTable($adapterTableName); + } + + /** + * {@inheritdoc} + */ + public function createTable(Table $table) + { + $adapterTable = clone $table; + $adapterTableName = $this->getAdapterTableName($table->getName()); + $adapterTable->setName($adapterTableName); + + foreach ($adapterTable->getForeignKeys() as $fk) { + $adapterReferenceTable = $fk->getReferencedTable(); + $adapterReferenceTableName = $this->getAdapterTableName($adapterReferenceTable->getName()); + $adapterReferenceTable->setName($adapterReferenceTableName); + } + + return parent::createTable($adapterTable); + } + + /** + * {@inheritdoc} + */ + public function renameTable($tableName, $newTableName) + { + $adapterTableName = $this->getAdapterTableName($tableName); + $adapterNewTableName = $this->getAdapterTableName($newTableName); + return parent::renameTable($adapterTableName, $adapterNewTableName); + } + + /** + * {@inheritdoc} + */ + public function dropTable($tableName) + { + $adapterTableName = $this->getAdapterTableName($tableName); + return parent::dropTable($adapterTableName); + } + + /** + * {@inheritdoc} + */ + public function getColumns($tableName) + { + $adapterTableName = $this->getAdapterTableName($tableName); + return parent::getColumns($adapterTableName); + } + + /** + * {@inheritdoc} + */ + public function hasColumn($tableName, $columnName) + { + $adapterTableName = $this->getAdapterTableName($tableName); + return parent::hasColumn($adapterTableName, $columnName); + } + + /** + * {@inheritdoc} + */ + public function addColumn(Table $table, Column $column) + { + $adapterTable = clone $table; + $adapterTableName = $this->getAdapterTableName($table->getName()); + $adapterTable->setName($adapterTableName); + return parent::addColumn($adapterTable, $column); + } + + /** + * {@inheritdoc} + */ + public function renameColumn($tableName, $columnName, $newColumnName) + { + $adapterTableName = $this->getAdapterTableName($tableName); + return parent::renameColumn($adapterTableName, $columnName, $newColumnName); + } + + /** + * {@inheritdoc} + */ + public function changeColumn($tableName, $columnName, Column $newColumn) + { + $adapterTableName = $this->getAdapterTableName($tableName); + return parent::changeColumn($adapterTableName, $columnName, $newColumn); + } + + /** + * {@inheritdoc} + */ + public function dropColumn($tableName, $columnName) + { + $adapterTableName = $this->getAdapterTableName($tableName); + return parent::dropColumn($adapterTableName, $columnName); + } + + /** + * {@inheritdoc} + */ + public function hasIndex($tableName, $columns) + { + $adapterTableName = $this->getAdapterTableName($tableName); + return parent::hasIndex($adapterTableName, $columns); + } + + /** + * {@inheritdoc} + */ + public function hasIndexByName($tableName, $indexName) + { + $adapterTableName = $this->getAdapterTableName($tableName); + return parent::hasIndexByName($adapterTableName, $indexName); + } + + /** + * {@inheritdoc} + */ + public function addIndex(Table $table, Index $index) + { + $adapterTable = clone $table; + $adapterTableName = $this->getAdapterTableName($table->getName()); + $adapterTable->setName($adapterTableName); + return parent::addIndex($adapterTable, $index); + } + + /** + * {@inheritdoc} + */ + public function dropIndex($tableName, $columns, $options = array()) + { + $adapterTableName = $this->getAdapterTableName($tableName); + return parent::dropIndex($adapterTableName, $columns, $options); + } + + /** + * {@inheritdoc} + */ + public function dropIndexByName($tableName, $indexName) + { + $adapterTableName = $this->getAdapterTableName($tableName); + return parent::dropIndexByName($adapterTableName, $indexName); + } + + /** + * {@inheritdoc} + */ + public function hasForeignKey($tableName, $columns, $constraint = null) + { + $adapterTableName = $this->getAdapterTableName($tableName); + return parent::hasForeignKey($adapterTableName, $columns, $constraint); + } + + /** + * {@inheritdoc} + */ + public function addForeignKey(Table $table, ForeignKey $foreignKey) + { + $adapterTable = clone $table; + $adapterTableName = $this->getAdapterTableName($table->getName()); + $adapterTable->setName($adapterTableName); + return parent::addForeignKey($adapterTable, $foreignKey); + } + + /** + * {@inheritdoc} + */ + public function dropForeignKey($tableName, $columns, $constraint = null) + { + $adapterTableName = $this->getAdapterTableName($tableName); + return parent::dropForeignKey($adapterTableName, $columns, $constraint); + } + + /** + * {@inheritdoc} + */ + public function insert(Table $table, $row) + { + $adapterTable = clone $table; + $adapterTableName = $this->getAdapterTableName($table->getName()); + $adapterTable->setName($adapterTableName); + return parent::insert($adapterTable, $row); + } + + /** + * Gets the table prefix. + * + * @return string + */ + public function getPrefix() + { + return (string) $this->getOption('table_prefix'); + } + + /** + * Gets the table suffix. + * + * @return string + */ + public function getSuffix() + { + return (string) $this->getOption('table_suffix'); + } + + /** + * Applies the prefix and suffix to the table name. + * + * @param string $tableName + * @return string + */ + public function getAdapterTableName($tableName) + { + return $this->getPrefix() . $tableName . $this->getSuffix(); + } +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/WrapperInterface.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/WrapperInterface.php new file mode 100644 index 0000000..b8181d4 --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/WrapperInterface.php @@ -0,0 +1,60 @@ + + */ +interface WrapperInterface +{ + /** + * Class constructor, must always wrap another adapter. + * + * @param AdapterInterface $adapter + */ + public function __construct(AdapterInterface $adapter); + + /** + * Sets the database adapter to proxy commands to. + * + * @param AdapterInterface $adapter + * @return AdapterInterface + */ + public function setAdapter(AdapterInterface $adapter); + + /** + * Gets the database adapter. + * + * @throws \RuntimeException if the adapter has not been set + * @return AdapterInterface + */ + public function getAdapter(); +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Table.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Table.php new file mode 100644 index 0000000..b0e7202 --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Table.php @@ -0,0 +1,674 @@ +setName($name); + $this->setOptions($options); + + if (null !== $adapter) { + $this->setAdapter($adapter); + } + } + + /** + * Sets the table name. + * + * @param string $name Table Name + * @return Table + */ + public function setName($name) + { + $this->name = $name; + return $this; + } + + /** + * Gets the table name. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Sets the table options. + * + * @param array $options + * @return Table + */ + public function setOptions($options) + { + $this->options = $options; + return $this; + } + + /** + * Gets the table options. + * + * @return array + */ + public function getOptions() + { + return $this->options; + } + + /** + * Sets the database adapter. + * + * @param AdapterInterface $adapter Database Adapter + * @return Table + */ + public function setAdapter(AdapterInterface $adapter) + { + $this->adapter = $adapter; + return $this; + } + + /** + * Gets the database adapter. + * + * @return AdapterInterface + */ + public function getAdapter() + { + return $this->adapter; + } + + /** + * Does the table exist? + * + * @return boolean + */ + public function exists() + { + return $this->getAdapter()->hasTable($this->getName()); + } + + /** + * Drops the database table. + * + * @return void + */ + public function drop() + { + $this->getAdapter()->dropTable($this->getName()); + } + + /** + * Renames the database table. + * + * @param string $newTableName New Table Name + * @return Table + */ + public function rename($newTableName) + { + $this->getAdapter()->renameTable($this->getName(), $newTableName); + $this->setName($newTableName); + return $this; + } + + /** + * Sets an array of columns waiting to be committed. + * Use setPendingColumns + * + * @deprecated + * @param array $columns Columns + * @return Table + */ + public function setColumns($columns) + { + $this->setPendingColumns($columns); + } + + /** + * Gets an array of the table columns. + * + * @return Column[] + */ + public function getColumns() + { + return $this->getAdapter()->getColumns($this->getName()); + } + + /** + * Sets an array of columns waiting to be committed. + * + * @param array $columns Columns + * @return Table + */ + public function setPendingColumns($columns) + { + $this->columns = $columns; + return $this; + } + + /** + * Gets an array of columns waiting to be committed. + * + * @return Column[] + */ + public function getPendingColumns() + { + return $this->columns; + } + + /** + * Sets an array of columns waiting to be indexed. + * + * @param array $indexes Indexes + * @return Table + */ + public function setIndexes($indexes) + { + $this->indexes = $indexes; + return $this; + } + + /** + * Gets an array of indexes waiting to be committed. + * + * @return array + */ + public function getIndexes() + { + return $this->indexes; + } + + /** + * Sets an array of foreign keys waiting to be commited. + * + * @param ForeignKey[] $foreignKeys foreign keys + * @return Table + */ + public function setForeignKeys($foreignKeys) + { + $this->foreignKeys = $foreignKeys; + return $this; + } + + /** + * Gets an array of foreign keys waiting to be commited. + * + * @return array|ForeignKey[] + */ + public function getForeignKeys() + { + return $this->foreignKeys; + } + + /** + * Sets an array of data to be inserted. + * + * @param array $data Data + * @return Table + */ + public function setData($data) + { + $this->data = $data; + return $this; + } + + /** + * Gets the data waiting to be inserted. + * + * @return array + */ + public function getData() + { + return $this->data; + } + + /** + * Resets all of the pending table changes. + * + * @return void + */ + public function reset() + { + $this->setPendingColumns(array()); + $this->setIndexes(array()); + $this->setForeignKeys(array()); + $this->setData(array()); + } + + /** + * Add a table column. + * + * Type can be: string, text, integer, float, decimal, datetime, timestamp, + * time, date, binary, boolean. + * + * Valid options can be: limit, default, null, precision or scale. + * + * @param string|Column $columnName Column Name + * @param string $type Column Type + * @param array $options Column Options + * @throws \RuntimeException + * @throws \InvalidArgumentException + * @return Table + */ + public function addColumn($columnName, $type = null, $options = array()) + { + // we need an adapter set to add a column + if (null === $this->getAdapter()) { + throw new \RuntimeException('An adapter must be specified to add a column.'); + } + + // create a new column object if only strings were supplied + if (!$columnName instanceof Column) { + $column = new Column(); + $column->setName($columnName); + $column->setType($type); + $column->setOptions($options); // map options to column methods + } else { + $column = $columnName; + } + + // Delegate to Adapters to check column type + if (!$this->getAdapter()->isValidColumnType($column)) { + throw new \InvalidArgumentException(sprintf( + 'An invalid column type "%s" was specified for column "%s".', + $column->getType(), + $column->getName() + )); + } + + $this->columns[] = $column; + return $this; + } + + /** + * Remove a table column. + * + * @param string $columnName Column Name + * @return Table + */ + public function removeColumn($columnName) + { + $this->getAdapter()->dropColumn($this->getName(), $columnName); + return $this; + } + + /** + * Rename a table column. + * + * @param string $oldName Old Column Name + * @param string $newName New Column Name + * @return Table + */ + public function renameColumn($oldName, $newName) + { + $this->getAdapter()->renameColumn($this->getName(), $oldName, $newName); + return $this; + } + + /** + * Change a table column type. + * + * @param string $columnName Column Name + * @param string|Column $newColumnType New Column Type + * @param array $options Options + * @return Table + */ + public function changeColumn($columnName, $newColumnType, $options = array()) + { + // create a column object if one wasn't supplied + if (!$newColumnType instanceof Column) { + $newColumn = new Column(); + $newColumn->setType($newColumnType); + $newColumn->setOptions($options); + } else { + $newColumn = $newColumnType; + } + + // if the name was omitted use the existing column name + if (null === $newColumn->getName() || strlen($newColumn->getName()) === 0) { + $newColumn->setName($columnName); + } + + $this->getAdapter()->changeColumn($this->getName(), $columnName, $newColumn); + return $this; + } + + /** + * Checks to see if a column exists. + * + * @param string $columnName Column Name + * @param array $options Options + * @return boolean + */ + public function hasColumn($columnName, $options = array()) + { + return $this->getAdapter()->hasColumn($this->getName(), $columnName, $options); + } + + /** + * Add an index to a database table. + * + * In $options you can specific unique = true/false or name (index name). + * + * @param string|array|Index $columns Table Column(s) + * @param array $options Index Options + * @return Table + */ + public function addIndex($columns, $options = array()) + { + // create a new index object if strings or an array of strings were supplied + if (!$columns instanceof Index) { + $index = new Index(); + if (is_string($columns)) { + $columns = array($columns); // str to array + } + $index->setColumns($columns); + $index->setOptions($options); + } else { + $index = $columns; + } + + $this->indexes[] = $index; + return $this; + } + + /** + * Removes the given index from a table. + * + * @param array $columns Columns + * @param array $options Options + * @return Table + */ + public function removeIndex($columns, $options = array()) + { + $this->getAdapter()->dropIndex($this->getName(), $columns, $options); + return $this; + } + + /** + * Removes the given index identified by its name from a table. + * + * @param string $name Index name + * @return Table + */ + public function removeIndexByName($name) + { + $this->getAdapter()->dropIndexByName($this->getName(), $name); + return $this; + } + + /** + * Checks to see if an index exists. + * + * @param string|array $columns Columns + * @param array $options Options + * @return boolean + */ + public function hasIndex($columns, $options = array()) + { + return $this->getAdapter()->hasIndex($this->getName(), $columns, $options); + } + + /** + * Add a foreign key to a database table. + * + * In $options you can specify on_delete|on_delete = cascade|no_action .., + * on_update, constraint = constraint name. + * + * @param string|array $columns Columns + * @param string|Table $referencedTable Referenced Table + * @param string|array $referencedColumns Referenced Columns + * @param array $options Options + * @return Table + */ + public function addForeignKey($columns, $referencedTable, $referencedColumns = array('id'), $options = array()) + { + if (is_string($referencedColumns)) { + $referencedColumns = array($referencedColumns); // str to array + } + $fk = new ForeignKey(); + if ($referencedTable instanceof Table) { + $fk->setReferencedTable($referencedTable); + } else { + $fk->setReferencedTable(new Table($referencedTable, array(), $this->adapter)); + } + $fk->setColumns($columns) + ->setReferencedColumns($referencedColumns) + ->setOptions($options); + $this->foreignKeys[] = $fk; + + return $this; + } + + /** + * Removes the given foreign key from the table. + * + * @param string|array $columns Column(s) + * @param null|string $constraint Constraint names + * @return Table + */ + public function dropForeignKey($columns, $constraint = null) + { + if (is_string($columns)) { + $columns = array($columns); + } + if ($constraint) { + $this->getAdapter()->dropForeignKey($this->getName(), array(), $constraint); + } else { + $this->getAdapter()->dropForeignKey($this->getName(), $columns); + } + + return $this; + } + + /** + * Checks to see if a foreign key exists. + * + * @param string|array $columns Column(s) + * @param null|string $constraint Constraint names + * @return boolean + */ + public function hasForeignKey($columns, $constraint = null) + { + return $this->getAdapter()->hasForeignKey($this->getName(), $columns, $constraint); + } + + /** + * Add timestamp columns created_at and updated_at to the table. + * + * @param string $createdAtColumnName + * @param string $updatedAtColumnName + * + * @return Table + */ + public function addTimestamps($createdAtColumnName = 'created_at', $updatedAtColumnName = 'updated_at') + { + $createdAtColumnName = is_null($createdAtColumnName) ? 'created_at' : $createdAtColumnName; + $updatedAtColumnName = is_null($updatedAtColumnName) ? 'updated_at' : $updatedAtColumnName; + $this->addColumn($createdAtColumnName, 'timestamp', array( + 'default' => 'CURRENT_TIMESTAMP', + 'update' => '' + )) + ->addColumn($updatedAtColumnName, 'timestamp', array( + 'null' => true, + 'default' => null + )); + + return $this; + } + + /** + * Insert data into the table. + * + * @param $data array of data in the form: + * array( + * array("col1" => "value1", "col2" => "anotherValue1"), + * array("col2" => "value2", "col2" => "anotherValue2"), + * ) + * or array("col1" => "value1", "col2" => "anotherValue1") + * + * @return Table + */ + public function insert($data) + { + // handle array of array situations + if (isset($data[0]) && is_array($data[0])) { + foreach ($data as $row) { + $this->data[] = $row; + } + return $this; + } + $this->data[] = $data; + return $this; + } + + /** + * Creates a table from the object instance. + * + * @return void + */ + public function create() + { + $this->getAdapter()->createTable($this); + $this->saveData(); + $this->reset(); // reset pending changes + } + + /** + * Updates a table from the object instance. + * + * @throws \RuntimeException + * @return void + */ + public function update() + { + if (!$this->exists()) { + throw new \RuntimeException('Cannot update a table that doesn\'t exist!'); + } + + // update table + foreach ($this->getPendingColumns() as $column) { + $this->getAdapter()->addColumn($this, $column); + } + + foreach ($this->getIndexes() as $index) { + $this->getAdapter()->addIndex($this, $index); + } + + foreach ($this->getForeignKeys() as $foreignKey) { + $this->getAdapter()->addForeignKey($this, $foreignKey); + } + + $this->saveData(); + $this->reset(); // reset pending changes + } + + /** + * Commit the pending data waiting for insertion. + * + * @return void + */ + public function saveData() + { + foreach ($this->getData() as $row) { + $this->getAdapter()->insert($this, $row); + } + } + + /** + * Commits the table changes. + * + * If the table doesn't exist it is created otherwise it is updated. + * + * @return void + */ + public function save() + { + if ($this->exists()) { + $this->update(); // update the table + } else { + $this->create(); // create the table + } + + $this->reset(); // reset pending changes + } +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Table/Column.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Table/Column.php new file mode 100644 index 0000000..874341e --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Table/Column.php @@ -0,0 +1,550 @@ +name = $name; + return $this; + } + + /** + * Gets the column name. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Sets the column type. + * + * @param string $type + * @return $this + */ + public function setType($type) + { + $this->type = $type; + return $this; + } + + /** + * Gets the column type. + * + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * Sets the column limit. + * + * @param integer $limit + * @return $this + */ + public function setLimit($limit) + { + $this->limit = $limit; + return $this; + } + + /** + * Gets the column limit. + * + * @return integer + */ + public function getLimit() + { + return $this->limit; + } + + /** + * Sets whether the column allows nulls. + * + * @param boolean $null + * @return $this + */ + public function setNull($null) + { + $this->null = (bool) $null; + return $this; + } + + /** + * Gets whether the column allows nulls. + * + * @return boolean + */ + public function getNull() + { + return $this->null; + } + + /** + * Does the column allow nulls? + * + * @return boolean + */ + public function isNull() + { + return $this->getNull(); + } + + /** + * Sets the default column value. + * + * @param mixed $default + * @return $this + */ + public function setDefault($default) + { + $this->default = $default; + return $this; + } + + /** + * Gets the default column value. + * + * @return mixed + */ + public function getDefault() + { + return $this->default; + } + + /** + * Sets whether or not the column is an identity column. + * + * @param boolean $identity + * @return $this + */ + public function setIdentity($identity) + { + $this->identity = $identity; + return $this; + } + + /** + * Gets whether or not the column is an identity column. + * + * @return boolean + */ + public function getIdentity() + { + return $this->identity; + } + + /** + * Is the column an identity column? + * + * @return boolean + */ + public function isIdentity() + { + return $this->getIdentity(); + } + + /** + * Sets the name of the column to add this column after. + * + * @param string $after After + * @return $this + */ + public function setAfter($after) + { + $this->after = $after; + return $this; + } + + /** + * Returns the name of the column to add this column after. + * + * @return string + */ + public function getAfter() + { + return $this->after; + } + + /** + * Sets the 'ON UPDATE' mysql column function. + * + * @param string $update On Update function + * @return $this + */ + public function setUpdate($update) + { + $this->update = $update; + return $this; + } + + /** + * Returns the value of the ON UPDATE column function. + * + * @return string + */ + public function getUpdate() + { + return $this->update; + } + + /** + * Sets the column precision for decimal. + * + * @param integer $precision + * @return $this + */ + public function setPrecision($precision) + { + $this->precision = $precision; + return $this; + } + + /** + * Gets the column precision for decimal. + * + * @return integer + */ + public function getPrecision() + { + return $this->precision; + } + + /** + * Sets the column scale for decimal. + * + * @param integer $scale + * @return $this + */ + public function setScale($scale) + { + $this->scale = $scale; + return $this; + } + + /** + * Gets the column scale for decimal. + * + * @return integer + */ + public function getScale() + { + return $this->scale; + } + + /** + * Sets the column comment. + * + * @param string $comment + * @return $this + */ + public function setComment($comment) + { + $this->comment = $comment; + return $this; + } + + /** + * Gets the column comment. + * + * @return string + */ + public function getComment() + { + return $this->comment; + } + + /** + * Sets whether field should be signed. + * + * @param bool $signed + * @return $this + */ + public function setSigned($signed) + { + $this->signed = (bool) $signed; + return $this; + } + + /** + * Gets whether field should be signed. + * + * @return string + */ + public function getSigned() + { + return $this->signed; + } + + /** + * Should the column be signed? + * + * @return boolean + */ + public function isSigned() + { + return $this->getSigned(); + } + + /** + * Sets whether the field should have a timezone identifier. + * Used for date/time columns only! + * + * @param bool $timezone + * @return $this + */ + public function setTimezone($timezone) + { + $this->timezone = (bool) $timezone; + return $this; + } + + /** + * Gets whether field has a timezone identifier. + * + * @return boolean + */ + public function getTimezone() + { + return $this->timezone; + } + + /** + * Should the column have a timezone? + * + * @return boolean + */ + public function isTimezone() + { + return $this->getTimezone(); + } + + /** + * Sets field properties. + * + * @param array $properties + * + * @return $this + */ + public function setProperties($properties) + { + $this->properties = $properties; + return $this; + } + + /** + * Gets field properties + * + * @return array + */ + public function getProperties() + { + return $this->properties; + } + + /** + * Sets field values. + * + * @param mixed (array|string) $values + * + * @return $this + */ + public function setValues($values) + { + if (!is_array($values)) { + $values = preg_split('/,\s*/', $values); + } + $this->values = $values; + return $this; + } + + /** + * Gets field values + * + * @return string + */ + public function getValues() + { + return $this->values; + } + + /** + * Gets all allowed options. Each option must have a corresponding `setFoo` method. + * + * @return array + */ + protected function getValidOptions() + { + return array( + 'limit', + 'default', + 'null', + 'identity', + 'precision', + 'scale', + 'after', + 'update', + 'comment', + 'signed', + 'timezone', + 'properties', + 'values', + ); + } + + /** + * Gets all aliased options. Each alias must reference a valid option. + * + * @return array + */ + protected function getAliasedOptions() + { + return array( + 'length' => 'limit', + ); + } + + /** + * Utility method that maps an array of column options to this objects methods. + * + * @param array $options Options + * @return $this + */ + public function setOptions($options) + { + $validOptions = $this->getValidOptions(); + $aliasOptions = $this->getAliasedOptions(); + + foreach ($options as $option => $value) { + if (isset($aliasOptions[$option])) { + // proxy alias -> option + $option = $aliasOptions[$option]; + } + + if (!in_array($option, $validOptions, true)) { + throw new \RuntimeException(sprintf('"%s" is not a valid column option.', $option)); + } + + $method = 'set' . ucfirst($option); + $this->$method($value); + } + return $this; + } +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Table/ForeignKey.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Table/ForeignKey.php new file mode 100644 index 0000000..3033503 --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Table/ForeignKey.php @@ -0,0 +1,252 @@ + + */ +namespace Phinx\Db\Table; + +use Phinx\Db\Table; + +class ForeignKey +{ + const CASCADE = 'CASCADE'; + const RESTRICT = 'RESTRICT'; + const SET_NULL = 'SET NULL'; + const NO_ACTION = 'NO ACTION'; + + /** + * @var array + */ + protected $columns = array(); + + /** + * @var Table + */ + protected $referencedTable; + + /** + * @var array + */ + protected $referencedColumns = array(); + + /** + * @var string + */ + protected $onDelete; + + /** + * @var string + */ + protected $onUpdate; + + /** + * @var string|boolean + */ + protected $constraint; + + /** + * Sets the foreign key columns. + * + * @param array|string $columns + * @return ForeignKey + */ + public function setColumns($columns) + { + if (is_string($columns)) { + $columns = array($columns); + } + $this->columns = $columns; + return $this; + } + + /** + * Gets the foreign key columns. + * + * @return array + */ + public function getColumns() + { + return $this->columns; + } + + /** + * Sets the foreign key referenced table. + * + * @param Table $table + * @return ForeignKey + */ + public function setReferencedTable(Table $table) + { + $this->referencedTable = $table; + return $this; + } + + /** + * Gets the foreign key referenced table. + * + * @return Table + */ + public function getReferencedTable() + { + return $this->referencedTable; + } + + /** + * Sets the foreign key referenced columns. + * + * @param array $referencedColumns + * @return ForeignKey + */ + public function setReferencedColumns(array $referencedColumns) + { + $this->referencedColumns = $referencedColumns; + return $this; + } + + /** + * Gets the foreign key referenced columns. + * + * @return array + */ + public function getReferencedColumns() + { + return $this->referencedColumns; + } + + /** + * Sets ON DELETE action for the foreign key. + * + * @param string $onDelete + * @return ForeignKey + */ + public function setOnDelete($onDelete) + { + $this->onDelete = $this->normalizeAction($onDelete); + return $this; + } + + /** + * Gets ON DELETE action for the foreign key. + * + * @return string + */ + public function getOnDelete() + { + return $this->onDelete; + } + + /** + * Gets ON UPDATE action for the foreign key. + * + * @return string + */ + public function getOnUpdate() + { + return $this->onUpdate; + } + + /** + * Sets ON UPDATE action for the foreign key. + * + * @param string $onUpdate + * @return ForeignKey + */ + public function setOnUpdate($onUpdate) + { + $this->onUpdate = $this->normalizeAction($onUpdate); + return $this; + } + + /** + * Sets constraint for the foreign key. + * + * @param string $constraint + * @return ForeignKey + */ + public function setConstraint($constraint) + { + $this->constraint = $constraint; + return $this; + } + + /** + * Gets constraint name for the foreign key. + * + * @return string + */ + public function getConstraint() + { + return $this->constraint; + } + + /** + * Utility method that maps an array of index options to this objects methods. + * + * @param array $options Options + * @throws \RuntimeException + * @throws \InvalidArgumentException + * @return ForeignKey + */ + public function setOptions($options) + { + // Valid Options + $validOptions = array('delete', 'update', 'constraint'); + foreach ($options as $option => $value) { + if (!in_array($option, $validOptions, true)) { + throw new \RuntimeException(sprintf('"%s" is not a valid foreign key option.', $option)); + } + + // handle $options['delete'] as $options['update'] + if ('delete' === $option) { + $this->setOnDelete($value); + } elseif ('update' === $option) { + $this->setOnUpdate($value); + } else { + $method = 'set' . ucfirst($option); + $this->$method($value); + } + } + + return $this; + } + + /** + * From passed value checks if it's correct and fixes if needed + * + * @param string $action + * @throws \InvalidArgumentException + * @return string + */ + protected function normalizeAction($action) + { + $constantName = 'static::' . str_replace(' ', '_', strtoupper(trim($action))); + if (!defined($constantName)) { + throw new \InvalidArgumentException('Unknown action passed: ' . $action); + } + return constant($constantName); + } +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Table/Index.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Table/Index.php new file mode 100644 index 0000000..82404cb --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Table/Index.php @@ -0,0 +1,185 @@ +columns = $columns; + return $this; + } + + /** + * Gets the index columns. + * + * @return array + */ + public function getColumns() + { + return $this->columns; + } + + /** + * Sets the index type. + * + * @param string $type + * @return Index + */ + public function setType($type) + { + $this->type = $type; + return $this; + } + + /** + * Gets the index type. + * + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * Sets the index name. + * + * @param string $name + * @return Index + */ + public function setName($name) + { + $this->name = $name; + return $this; + } + + /** + * Gets the index name. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Sets the index limit. + * + * @param integer $limit + * @return Index + */ + public function setLimit($limit) + { + $this->limit = $limit; + return $this; + } + + /** + * Gets the index limit. + * + * @return integer + */ + public function getLimit() + { + return $this->limit; + } + + /** + * Utility method that maps an array of index options to this objects methods. + * + * @param array $options Options + * @throws \RuntimeException + * @return Index + */ + public function setOptions($options) + { + // Valid Options + $validOptions = array('type', 'unique', 'name', 'limit'); + foreach ($options as $option => $value) { + if (!in_array($option, $validOptions, true)) { + throw new \RuntimeException(sprintf('"%s" is not a valid index option.', $option)); + } + + // handle $options['unique'] + if (strcasecmp($option, self::UNIQUE) === 0) { + if ((bool) $value) { + $this->setType(self::UNIQUE); + } + continue; + } + + $method = 'set' . ucfirst($option); + $this->$method($value); + } + return $this; + } +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Migration/AbstractMigration.php b/vendor/topthink/think-migration/phinx/src/Phinx/Migration/AbstractMigration.php new file mode 100644 index 0000000..1598cfb --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Migration/AbstractMigration.php @@ -0,0 +1,273 @@ + + */ +abstract class AbstractMigration implements MigrationInterface +{ + /** + * @var float + */ + protected $version; + + /** + * @var AdapterInterface + */ + protected $adapter; + + /** + * @var OutputInterface + */ + protected $output; + + /** + * @var InputInterface + */ + protected $input; + + /** + * Class Constructor. + * + * @param int $version Migration Version + * @param InputInterface|null $input + * @param OutputInterface|null $output + */ + final public function __construct($version, InputInterface $input = null, OutputInterface $output = null) + { + $this->version = $version; + if (!is_null($input)){ + $this->setInput($input); + } + if (!is_null($output)){ + $this->setOutput($output); + } + + $this->init(); + } + + /** + * Initialize method. + * + * @return void + */ + protected function init() + { + } + + /** + * {@inheritdoc} + */ + public function up() + { + } + + /** + * {@inheritdoc} + */ + public function down() + { + } + + /** + * {@inheritdoc} + */ + public function setAdapter(AdapterInterface $adapter) + { + $this->adapter = $adapter; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getAdapter() + { + return $this->adapter; + } + + /** + * {@inheritdoc} + */ + public function setInput(InputInterface $input) + { + $this->input = $input; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getInput() + { + return $this->input; + } + + /** + * {@inheritdoc} + */ + public function setOutput(OutputInterface $output) + { + $this->output = $output; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getOutput() + { + return $this->output; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return get_class($this); + } + + /** + * {@inheritdoc} + */ + public function setVersion($version) + { + $this->version = $version; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getVersion() + { + return $this->version; + } + + /** + * {@inheritdoc} + */ + public function execute($sql) + { + return $this->getAdapter()->execute($sql); + } + + /** + * {@inheritdoc} + */ + public function query($sql) + { + return $this->getAdapter()->query($sql); + } + + /** + * {@inheritdoc} + */ + public function fetchRow($sql) + { + return $this->getAdapter()->fetchRow($sql); + } + + /** + * {@inheritdoc} + */ + public function fetchAll($sql) + { + return $this->getAdapter()->fetchAll($sql); + } + + /** + * {@inheritdoc} + */ + public function insert($table, $data) + { + // convert to table object + if (is_string($table)) { + $table = new Table($table, array(), $this->getAdapter()); + } + return $table->insert($data)->save(); + } + + /** + * {@inheritdoc} + */ + public function createDatabase($name, $options) + { + $this->getAdapter()->createDatabase($name, $options); + } + + /** + * {@inheritdoc} + */ + public function dropDatabase($name) + { + $this->getAdapter()->dropDatabase($name); + } + + /** + * {@inheritdoc} + */ + public function hasTable($tableName) + { + return $this->getAdapter()->hasTable($tableName); + } + + /** + * {@inheritdoc} + */ + public function table($tableName, $options = array()) + { + return new Table($tableName, $options, $this->getAdapter()); + } + + /** + * A short-hand method to drop the given database table. + * + * @param string $tableName Table Name + * @return void + */ + public function dropTable($tableName) + { + $this->table($tableName)->drop(); + } +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Migration/AbstractTemplateCreation.php b/vendor/topthink/think-migration/phinx/src/Phinx/Migration/AbstractTemplateCreation.php new file mode 100644 index 0000000..589a5f7 --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Migration/AbstractTemplateCreation.php @@ -0,0 +1,97 @@ +setInput($input); + } + if (!is_null($output)) { + $this->setOutput($output); + } + } + + /** + * {@inheritdoc} + */ + public function getInput() + { + return $this->input; + } + + /** + * {@inheritdoc} + */ + public function setInput(InputInterface $input) + { + $this->input = $input; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getOutput() + { + return $this->output; + } + + /** + * {@inheritdoc} + */ + public function setOutput(OutputInterface $output) + { + $this->output = $output; + + return $this; + } +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Migration/CreationInterface.php b/vendor/topthink/think-migration/phinx/src/Phinx/Migration/CreationInterface.php new file mode 100644 index 0000000..b60c889 --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Migration/CreationInterface.php @@ -0,0 +1,94 @@ + + */ +interface CreationInterface +{ + /** + * CreationInterface constructor. + * + * @param InputInterface|null $input + * @param OutputInterface|null $output + */ + public function __construct(InputInterface $input = null, OutputInterface $output = null); + + /** + * @param InputInterface $input + * + * @return CreationInterface + */ + public function setInput(InputInterface $input); + + /** + * @param OutputInterface $output + * + * @return CreationInterface + */ + public function setOutput(OutputInterface $output); + + /** + * @return InputInterface + */ + public function getInput(); + + /** + * @return OutputInterface + */ + public function getOutput(); + + /** + * Get the migration template. + * + * This will be the content that Phinx will amend to generate the migration file. + * + * @return string The content of the template for Phinx to amend. + */ + public function getMigrationTemplate(); + + /** + * Post Migration Creation. + * + * Once the migration file has been created, this method will be called, allowing any additional + * processing, specific to the template to be performed. + * + * @param string $migrationFilename The name of the newly created migration. + * @param string $className The class name. + * @param string $baseClassName The name of the base class. + * @return void + */ + public function postMigrationCreation($migrationFilename, $className, $baseClassName); +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Migration/IrreversibleMigrationException.php b/vendor/topthink/think-migration/phinx/src/Phinx/Migration/IrreversibleMigrationException.php new file mode 100644 index 0000000..9c2a8d2 --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Migration/IrreversibleMigrationException.php @@ -0,0 +1,39 @@ + + */ +class IrreversibleMigrationException extends \Exception +{ +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Migration/Migration.template.php.dist b/vendor/topthink/think-migration/phinx/src/Phinx/Migration/Migration.template.php.dist new file mode 100644 index 0000000..80ac0c1 --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Migration/Migration.template.php.dist @@ -0,0 +1,32 @@ + + */ +interface MigrationInterface +{ + /** + * @var string + */ + const CHANGE = 'change'; + + /** + * @var string + */ + const UP = 'up'; + + /** + * @var string + */ + const DOWN = 'down'; + + /** + * Migrate Up + * + * @return void + */ + public function up(); + + /** + * Migrate Down + * + * @return void + */ + public function down(); + + /** + * Sets the database adapter. + * + * @param AdapterInterface $adapter Database Adapter + * @return MigrationInterface + */ + public function setAdapter(AdapterInterface $adapter); + + /** + * Gets the database adapter. + * + * @return AdapterInterface + */ + public function getAdapter(); + + /** + * Sets the input object to be used in migration object + * + * @param InputInterface $input + * @return MigrationInterface + */ + public function setInput(InputInterface $input); + + /** + * Gets the input object to be used in migration object + * + * @return InputInterface + */ + public function getInput(); + + /** + * Sets the output object to be used in migration object + * + * @param OutputInterface $output + * @return MigrationInterface + */ + public function setOutput(OutputInterface $output); + + /** + * Gets the output object to be used in migration object + * + * @return OutputInterface + */ + public function getOutput(); + + /** + * Gets the name. + * + * @return string + */ + public function getName(); + + /** + * Sets the migration version number. + * + * @param float $version Version + * @return MigrationInterface + */ + public function setVersion($version); + + /** + * Gets the migration version number. + * + * @return float + */ + public function getVersion(); + + /** + * Executes a SQL statement and returns the number of affected rows. + * + * @param string $sql SQL + * @return int + */ + public function execute($sql); + + /** + * Executes a SQL statement and returns the result as an array. + * + * @param string $sql SQL + * @return array + */ + public function query($sql); + + /** + * Executes a query and returns only one row as an array. + * + * @param string $sql SQL + * @return array + */ + public function fetchRow($sql); + + /** + * Executes a query and returns an array of rows. + * + * @param string $sql SQL + * @return array + */ + public function fetchAll($sql); + + /** + * Insert data into a table. + * + * @param string $tableName + * @param array $data + * @return void + */ + public function insert($tableName, $data); + + /** + * Create a new database. + * + * @param string $name Database Name + * @param array $options Options + * @return void + */ + public function createDatabase($name, $options); + + /** + * Drop a database. + * + * @param string $name Database Name + * @return void + */ + public function dropDatabase($name); + + /** + * Checks to see if a table exists. + * + * @param string $tableName Table Name + * @return boolean + */ + public function hasTable($tableName); + + /** + * Returns an instance of the \Table class. + * + * You can use this class to create and manipulate tables. + * + * @param string $tableName Table Name + * @param array $options Options + * @return Table + */ + public function table($tableName, $options); +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Seed/AbstractSeed.php b/vendor/topthink/think-migration/phinx/src/Phinx/Seed/AbstractSeed.php new file mode 100644 index 0000000..4d631f5 --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Seed/AbstractSeed.php @@ -0,0 +1,215 @@ + + */ +abstract class AbstractSeed implements SeedInterface +{ + /** + * @var AdapterInterface + */ + protected $adapter; + + /** + * @var InputInterface + */ + protected $input; + + /** + * @var OutputInterface + */ + protected $output; + + /** + * Class Constructor. + * + * @param InputInterface $input + * @param OutputInterface $output + */ + final public function __construct(InputInterface $input = null, OutputInterface $output = null) + { + if (!is_null($input)){ + $this->setInput($input); + } + if (!is_null($output)){ + $this->setOutput($output); + } + + $this->init(); + } + + /** + * Initialize method. + * + * @return void + */ + protected function init() + { + } + + /** + * {@inheritdoc} + */ + public function run() + { + } + + /** + * {@inheritdoc} + */ + public function setAdapter(AdapterInterface $adapter) + { + $this->adapter = $adapter; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getAdapter() + { + return $this->adapter; + } + + /** + * {@inheritdoc} + */ + public function setInput(InputInterface $input) + { + $this->input = $input; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getInput() + { + return $this->input; + } + + /** + * {@inheritdoc} + */ + public function setOutput(OutputInterface $output) + { + $this->output = $output; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getOutput() + { + return $this->output; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return get_class($this); + } + + /** + * {@inheritdoc} + */ + public function execute($sql) + { + return $this->getAdapter()->execute($sql); + } + + /** + * {@inheritdoc} + */ + public function query($sql) + { + return $this->getAdapter()->query($sql); + } + + /** + * {@inheritdoc} + */ + public function fetchRow($sql) + { + return $this->getAdapter()->fetchRow($sql); + } + + /** + * {@inheritdoc} + */ + public function fetchAll($sql) + { + return $this->getAdapter()->fetchAll($sql); + } + + /** + * {@inheritdoc} + */ + public function insert($table, $data) + { + // convert to table object + if (is_string($table)) { + $table = new Table($table, array(), $this->getAdapter()); + } + return $table->insert($data)->save(); + } + + /** + * {@inheritdoc} + */ + public function hasTable($tableName) + { + return $this->getAdapter()->hasTable($tableName); + } + + /** + * {@inheritdoc} + */ + public function table($tableName, $options = array()) + { + return new Table($tableName, $options, $this->getAdapter()); + } +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Seed/Seed.template.php.dist b/vendor/topthink/think-migration/phinx/src/Phinx/Seed/Seed.template.php.dist new file mode 100644 index 0000000..698ae26 --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Seed/Seed.template.php.dist @@ -0,0 +1,19 @@ + + */ +interface SeedInterface +{ + /** + * @var string + */ + const RUN = 'run'; + + /** + * Run the seeder. + * + * @return void + */ + public function run(); + + /** + * Sets the database adapter. + * + * @param AdapterInterface $adapter Database Adapter + * @return MigrationInterface + */ + public function setAdapter(AdapterInterface $adapter); + + /** + * Gets the database adapter. + * + * @return AdapterInterface + */ + public function getAdapter(); + + /** + * Sets the input object to be used in migration object + * + * @param InputInterface $input + * @return MigrationInterface + */ + public function setInput(InputInterface $input); + + /** + * Gets the input object to be used in migration object + * + * @return InputInterface + */ + public function getInput(); + + /** + * Sets the output object to be used in migration object + * + * @param OutputInterface $output + * @return MigrationInterface + */ + public function setOutput(OutputInterface $output); + + /** + * Gets the output object to be used in migration object + * + * @return OutputInterface + */ + public function getOutput(); + + /** + * Gets the name. + * + * @return string + */ + public function getName(); + + /** + * Executes a SQL statement and returns the number of affected rows. + * + * @param string $sql SQL + * @return int + */ + public function execute($sql); + + /** + * Executes a SQL statement and returns the result as an array. + * + * @param string $sql SQL + * @return array + */ + public function query($sql); + + /** + * Executes a query and returns only one row as an array. + * + * @param string $sql SQL + * @return array + */ + public function fetchRow($sql); + + /** + * Executes a query and returns an array of rows. + * + * @param string $sql SQL + * @return array + */ + public function fetchAll($sql); + + /** + * Insert data into a table. + * + * @param string $tableName + * @param array $data + * @return void + */ + public function insert($tableName, $data); + + /** + * Checks to see if a table exists. + * + * @param string $tableName Table Name + * @return boolean + */ + public function hasTable($tableName); + + /** + * Returns an instance of the \Table class. + * + * You can use this class to create and manipulate tables. + * + * @param string $tableName Table Name + * @param array $options Options + * @return Table + */ + public function table($tableName, $options); +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Util/Util.php b/vendor/topthink/think-migration/phinx/src/Phinx/Util/Util.php new file mode 100644 index 0000000..9b8edf2 --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Util/Util.php @@ -0,0 +1,190 @@ +format(static::DATE_FORMAT); + } + + /** + * Gets an array of all the existing migration class names. + * + * @return string + */ + public static function getExistingMigrationClassNames($path) + { + $classNames = array(); + + if (!is_dir($path)) { + return $classNames; + } + + // filter the files to only get the ones that match our naming scheme + $phpFiles = glob($path . DIRECTORY_SEPARATOR . '*.php'); + + foreach ($phpFiles as $filePath) { + if (preg_match('/([0-9]+)_([_a-z0-9]*).php/', basename($filePath))) { + $classNames[] = static::mapFileNameToClassName(basename($filePath)); + } + } + + return $classNames; + } + + /** + * Get the version from the beginning of a file name. + * + * @param string $fileName File Name + * @return string + */ + public static function getVersionFromFileName($fileName) + { + $matches = array(); + preg_match('/^[0-9]+/', basename($fileName), $matches); + return $matches[0]; + } + + /** + * Turn migration names like 'CreateUserTable' into file names like + * '12345678901234_create_user_table.php' or 'LimitResourceNamesTo30Chars' into + * '12345678901234_limit_resource_names_to_30_chars.php'. + * + * @param string $className Class Name + * @return string + */ + public static function mapClassNameToFileName($className) + { + $arr = preg_split('/(?=[A-Z])/', $className); + unset($arr[0]); // remove the first element ('') + $fileName = static::getCurrentTimestamp() . '_' . strtolower(implode('_', $arr)) . '.php'; + return $fileName; + } + + /** + * Turn file names like '12345678901234_create_user_table.php' into class + * names like 'CreateUserTable'. + * + * @param string $fileName File Name + * @return string + */ + public static function mapFileNameToClassName($fileName) + { + $matches = array(); + if (preg_match(static::MIGRATION_FILE_NAME_PATTERN, $fileName, $matches)) { + $fileName = $matches[1]; + } + + return str_replace(' ', '', ucwords(str_replace('_', ' ', $fileName))); + } + + /** + * Check if a migration class name is unique regardless of the + * timestamp. + * + * This method takes a class name and a path to a migrations directory. + * + * Migration class names must be in CamelCase format. + * e.g: CreateUserTable or AddIndexToPostsTable. + * + * Single words are not allowed on their own. + * + * @param string $className Class Name + * @param string $path Path + * @return boolean + */ + public static function isUniqueMigrationClassName($className, $path) + { + $existingClassNames = static::getExistingMigrationClassNames($path); + return !(in_array($className, $existingClassNames)); + } + + /** + * Check if a migration/seed class name is valid. + * + * Migration & Seed class names must be in CamelCase format. + * e.g: CreateUserTable, AddIndexToPostsTable or UserSeeder. + * + * Single words are not allowed on their own. + * + * @param string $className Class Name + * @return boolean + */ + public static function isValidPhinxClassName($className) + { + return (bool) preg_match('/^([A-Z][a-z0-9]+)+$/', $className); + } + + /** + * Check if a migration file name is valid. + * + * @param string $fileName File Name + * @return boolean + */ + public static function isValidMigrationFileName($fileName) + { + $matches = array(); + return preg_match(static::MIGRATION_FILE_NAME_PATTERN, $fileName, $matches); + } + + /** + * Check if a seed file name is valid. + * + * @param string $fileName File Name + * @return boolean + */ + public static function isValidSeedFileName($fileName) + { + $matches = array(); + return preg_match(static::SEED_FILE_NAME_PATTERN, $fileName, $matches); + } +} diff --git a/vendor/topthink/think-migration/src/Command.php b/vendor/topthink/think-migration/src/Command.php new file mode 100644 index 0000000..b4db21f --- /dev/null +++ b/vendor/topthink/think-migration/src/Command.php @@ -0,0 +1,89 @@ + +// +---------------------------------------------------------------------- +namespace think\migration; + +use InvalidArgumentException; +use Phinx\Db\Adapter\AdapterFactory; + +abstract class Command extends \think\console\Command +{ + + public function getAdapter() + { + if (isset($this->adapter)) { + return $this->adapter; + } + + $options = $this->getDbConfig(); + + $adapter = AdapterFactory::instance()->getAdapter($options['adapter'], $options); + + if ($adapter->hasOption('table_prefix') || $adapter->hasOption('table_suffix')) { + $adapter = AdapterFactory::instance()->getWrapper('prefix', $adapter); + } + + $this->adapter = $adapter; + + return $adapter; + } + + /** + * 获取数据库配置 + * @return array + */ + protected function getDbConfig(): array + { + $default = $this->app->config->get('database.default'); + + $config = $this->app->config->get("database.connections.{$default}"); + + if (0 == $config['deploy']) { + $dbConfig = [ + 'adapter' => $config['type'], + 'host' => $config['hostname'], + 'name' => $config['database'], + 'user' => $config['username'], + 'pass' => $config['password'], + 'port' => $config['hostport'], + 'charset' => $config['charset'], + 'table_prefix' => $config['prefix'], + ]; + } else { + $dbConfig = [ + 'adapter' => explode(',', $config['type'])[0], + 'host' => explode(',', $config['hostname'])[0], + 'name' => explode(',', $config['database'])[0], + 'user' => explode(',', $config['username'])[0], + 'pass' => explode(',', $config['password'])[0], + 'port' => explode(',', $config['hostport'])[0], + 'charset' => explode(',', $config['charset'])[0], + 'table_prefix' => explode(',', $config['prefix'])[0], + ]; + } + + $table = $this->app->config->get('database.migration_table', 'migrations'); + + $dbConfig['default_migration_table'] = $dbConfig['table_prefix'] . $table; + + return $dbConfig; + } + + protected function verifyMigrationDirectory(string $path) + { + if (!is_dir($path)) { + throw new InvalidArgumentException(sprintf('Migration directory "%s" does not exist', $path)); + } + + if (!is_writable($path)) { + throw new InvalidArgumentException(sprintf('Migration directory "%s" is not writable', $path)); + } + } +} diff --git a/vendor/topthink/think-migration/src/Creator.php b/vendor/topthink/think-migration/src/Creator.php new file mode 100644 index 0000000..f4bc804 --- /dev/null +++ b/vendor/topthink/think-migration/src/Creator.php @@ -0,0 +1,77 @@ +app = $app; + } + + public function create(string $className) + { + $path = $this->ensureDirectory(); + + if (!Util::isValidPhinxClassName($className)) { + throw new InvalidArgumentException(sprintf('The migration class name "%s" is invalid. Please use CamelCase format.', $className)); + } + + if (!Util::isUniqueMigrationClassName($className, $path)) { + throw new InvalidArgumentException(sprintf('The migration class name "%s" already exists', $className)); + } + + // Compute the file path + $fileName = Util::mapClassNameToFileName($className); + $filePath = $path . DIRECTORY_SEPARATOR . $fileName; + + if (is_file($filePath)) { + throw new InvalidArgumentException(sprintf('The file "%s" already exists', $filePath)); + } + + // Verify that the template creation class (or the aliased class) exists and that it implements the required interface. + $aliasedClassName = null; + + // Load the alternative template if it is defined. + $contents = file_get_contents($this->getTemplate()); + + // inject the class names appropriate to this migration + $contents = strtr($contents, [ + 'MigratorClass' => $className, + ]); + + if (false === file_put_contents($filePath, $contents)) { + throw new RuntimeException(sprintf('The file "%s" could not be written to', $path)); + } + + return $filePath; + } + + protected function ensureDirectory() + { + $path = $this->app->getRootPath() . 'database' . DIRECTORY_SEPARATOR . 'migrations'; + + if (!is_dir($path) && !mkdir($path, 0755, true)) { + throw new InvalidArgumentException(sprintf('directory "%s" does not exist', $path)); + } + + if (!is_writable($path)) { + throw new InvalidArgumentException(sprintf('directory "%s" is not writable', $path)); + } + + return $path; + } + + protected function getTemplate() + { + return __DIR__ . '/command/stubs/migrate.stub'; + } +} diff --git a/vendor/topthink/think-migration/src/Factory.php b/vendor/topthink/think-migration/src/Factory.php new file mode 100644 index 0000000..976a999 --- /dev/null +++ b/vendor/topthink/think-migration/src/Factory.php @@ -0,0 +1,313 @@ +faker = $faker; + } + + /** + * Define a class with a given short-name. + * + * @param string $class + * @param string $name + * @param callable $attributes + * @return $this + */ + public function defineAs(string $class, string $name, callable $attributes) + { + return $this->define($class, $attributes, $name); + } + + /** + * Define a class with a given set of attributes. + * + * @param string $class + * @param callable $attributes + * @param string $name + * @return $this + */ + public function define(string $class, callable $attributes, string $name = 'default') + { + $this->definitions[$class][$name] = $attributes; + + return $this; + } + + /** + * Define a state with a given set of attributes. + * + * @param string $class + * @param string $state + * @param callable|array $attributes + * @return $this + */ + public function state(string $class, string $state, $attributes) + { + $this->states[$class][$state] = $attributes; + + return $this; + } + + /** + * Define a callback to run after making a model. + * + * @param string $class + * @param callable $callback + * @param string $name + * @return $this + */ + public function afterMaking(string $class, callable $callback, string $name = 'default') + { + $this->afterMaking[$class][$name][] = $callback; + + return $this; + } + + /** + * Define a callback to run after making a model with given state. + * + * @param string $class + * @param string $state + * @param callable $callback + * @return $this + */ + public function afterMakingState(string $class, string $state, callable $callback) + { + return $this->afterMaking($class, $callback, $state); + } + + /** + * Define a callback to run after creating a model. + * + * @param string $class + * @param callable $callback + * @param string $name + * @return $this + */ + public function afterCreating(string $class, callable $callback, string $name = 'default') + { + $this->afterCreating[$class][$name][] = $callback; + + return $this; + } + + /** + * Define a callback to run after creating a model with given state. + * + * @param string $class + * @param string $state + * @param callable $callback + * @return $this + */ + public function afterCreatingState(string $class, string $state, callable $callback) + { + return $this->afterCreating($class, $callback, $state); + } + + /** + * Create an instance of the given model and persist it to the database. + * + * @param string $class + * @param array $attributes + * @return mixed + */ + public function create(string $class, array $attributes = []) + { + return $this->of($class)->create($attributes); + } + + /** + * Create an instance of the given model and type and persist it to the database. + * + * @param string $class + * @param string $name + * @param array $attributes + * @return mixed + */ + public function createAs(string $class, string $name, array $attributes = []) + { + return $this->of($class, $name)->create($attributes); + } + + /** + * Create an instance of the given model. + * + * @param string $class + * @param array $attributes + * @return mixed + */ + public function make(string $class, array $attributes = []) + { + return $this->of($class)->make($attributes); + } + + /** + * Create an instance of the given model and type. + * + * @param string $class + * @param string $name + * @param array $attributes + * @return mixed + */ + public function makeAs(string $class, string $name, array $attributes = []) + { + return $this->of($class, $name)->make($attributes); + } + + /** + * Get the raw attribute array for a given named model. + * + * @param string $class + * @param string $name + * @param array $attributes + * @return array + */ + public function rawOf(string $class, string $name, array $attributes = []) + { + return $this->raw($class, $attributes, $name); + } + + /** + * Get the raw attribute array for a given model. + * + * @param string $class + * @param array $attributes + * @param string $name + * @return array + */ + public function raw(string $class, array $attributes = [], string $name = 'default') + { + return array_merge( + call_user_func($this->definitions[$class][$name], $this->faker), $attributes + ); + } + + /** + * Create a builder for the given model. + * + * @param string $class + * @param string $name + * @return FactoryBuilder + */ + public function of(string $class, string $name = 'default') + { + return new FactoryBuilder( + $class, $name, $this->definitions, $this->states, + $this->afterMaking, $this->afterCreating, $this->faker + ); + } + + /** + * Load factories from path. + * + * @param string $path + * @return $this + */ + public function load(string $path) + { + $factory = $this; + + if (is_dir($path)) { + foreach (glob($path . '*.php') as $file) { + require $file; + } + } + + return $factory; + } + + /** + * Determine if the given offset exists. + * + * @param string $offset + * @return bool + */ + public function offsetExists($offset) + { + return isset($this->definitions[$offset]); + } + + /** + * Get the value of the given offset. + * + * @param string $offset + * @return mixed + */ + public function offsetGet($offset) + { + return $this->make($offset); + } + + /** + * Set the given offset to the given value. + * + * @param string $offset + * @param callable $value + * @return void + */ + public function offsetSet($offset, $value) + { + $this->define($offset, $value); + } + + /** + * Unset the value at the given offset. + * + * @param string $offset + * @return void + */ + public function offsetUnset($offset) + { + unset($this->definitions[$offset]); + } + +} diff --git a/vendor/topthink/think-migration/src/FactoryBuilder.php b/vendor/topthink/think-migration/src/FactoryBuilder.php new file mode 100644 index 0000000..d192ba1 --- /dev/null +++ b/vendor/topthink/think-migration/src/FactoryBuilder.php @@ -0,0 +1,434 @@ +name = $name; + $this->class = $class; + $this->faker = $faker; + $this->states = $states; + $this->definitions = $definitions; + $this->afterMaking = $afterMaking; + $this->afterCreating = $afterCreating; + } + + /** + * Set the amount of models you wish to create / make. + * + * @param int $amount + * @return $this + */ + public function times($amount) + { + $this->amount = $amount; + + return $this; + } + + /** + * Set the state to be applied to the model. + * + * @param string $state + * @return $this + */ + public function state($state) + { + return $this->states([$state]); + } + + /** + * Set the states to be applied to the model. + * + * @param array|mixed $states + * @return $this + */ + public function states($states) + { + $this->activeStates = is_array($states) ? $states : func_get_args(); + + return $this; + } + + /** + * Set the database connection on which the model instance should be persisted. + * + * @param string $name + * @return $this + */ + public function connection($name) + { + $this->connection = $name; + + return $this; + } + + /** + * Create a model and persist it in the database if requested. + * + * @param array $attributes + * @return \Closure + */ + public function lazy(array $attributes = []) + { + return function () use ($attributes) { + return $this->create($attributes); + }; + } + + /** + * Create a collection of models and persist them to the database. + * + * @param array $attributes + * @return mixed + */ + public function create(array $attributes = []) + { + $results = $this->make($attributes); + + if ($results instanceof Model) { + $this->store(new Collection([$results])); + + $this->callAfterCreating(new Collection([$results])); + } else { + $this->store($results); + + $this->callAfterCreating($results); + } + + return $results; + } + + /** + * Set the connection name on the results and store them. + * + * @param Collection $results + * @return void + */ + protected function store($results) + { + $results->each(function (Model $model) { + $model->save(); + }); + } + + /** + * Create a collection of models. + * + * @param array $attributes + * @return mixed + */ + public function make(array $attributes = []) + { + if ($this->amount === null) { + return tap($this->makeInstance($attributes), function ($instance) { + $this->callAfterMaking(new Collection([$instance])); + }); + } + + if ($this->amount < 1) { + return (new $this->class)->toCollection(); + } + + $instances = (new $this->class)->toCollection(array_map(function () use ($attributes) { + return $this->makeInstance($attributes); + }, range(1, $this->amount))); + + $this->callAfterMaking($instances); + + return $instances; + } + + /** + * Create an array of raw attribute arrays. + * + * @param array $attributes + * @return mixed + */ + public function raw(array $attributes = []) + { + if ($this->amount === null) { + return $this->getRawAttributes($attributes); + } + + if ($this->amount < 1) { + return []; + } + + return array_map(function () use ($attributes) { + return $this->getRawAttributes($attributes); + }, range(1, $this->amount)); + } + + /** + * Get a raw attributes array for the model. + * + * @param array $attributes + * @return mixed + * + * @throws \InvalidArgumentException + */ + protected function getRawAttributes(array $attributes = []) + { + if (!isset($this->definitions[$this->class][$this->name])) { + throw new InvalidArgumentException("Unable to locate factory with name [{$this->name}] [{$this->class}]."); + } + + $definition = call_user_func( + $this->definitions[$this->class][$this->name], + $this->faker, $attributes + ); + + return $this->expandAttributes( + array_merge($this->applyStates($definition, $attributes), $attributes) + ); + } + + /** + * Make an instance of the model with the given attributes. + * + * @param array $attributes + * @return Model + */ + protected function makeInstance(array $attributes = []) + { + return new $this->class( + $this->getRawAttributes($attributes) + ); + } + + /** + * Apply the active states to the model definition array. + * + * @param array $definition + * @param array $attributes + * @return array + */ + protected function applyStates(array $definition, array $attributes = []) + { + foreach ($this->activeStates as $state) { + if (!isset($this->states[$this->class][$state])) { + if ($this->stateHasAfterCallback($state)) { + continue; + } + + throw new InvalidArgumentException("Unable to locate [{$state}] state for [{$this->class}]."); + } + + $definition = array_merge( + $definition, + $this->stateAttributes($state, $attributes) + ); + } + + return $definition; + } + + /** + * Get the state attributes. + * + * @param string $state + * @param array $attributes + * @return array + */ + protected function stateAttributes($state, array $attributes) + { + $stateAttributes = $this->states[$this->class][$state]; + + if (!is_callable($stateAttributes)) { + return $stateAttributes; + } + + return call_user_func( + $stateAttributes, + $this->faker, $attributes + ); + } + + /** + * Expand all attributes to their underlying values. + * + * @param array $attributes + * @return array + */ + protected function expandAttributes(array $attributes) + { + foreach ($attributes as &$attribute) { + if (is_callable($attribute) && !is_string($attribute) && !is_array($attribute)) { + $attribute = $attribute($attributes); + } + + if ($attribute instanceof static) { + $attribute = $attribute->create()->getKey(); + } + + if ($attribute instanceof Model) { + $attribute = $attribute->getKey(); + } + } + + return $attributes; + } + + /** + * Run after making callbacks on a collection of models. + * + * @param Collection $models + * @return void + */ + public function callAfterMaking($models) + { + $this->callAfter($this->afterMaking, $models); + } + + /** + * Run after creating callbacks on a collection of models. + * + * @param Collection $models + * @return void + */ + public function callAfterCreating($models) + { + $this->callAfter($this->afterCreating, $models); + } + + /** + * Call after callbacks for each model and state. + * + * @param array $afterCallbacks + * @param Collection $models + * @return void + */ + protected function callAfter(array $afterCallbacks, $models) + { + $states = array_merge([$this->name], $this->activeStates); + + $models->each(function ($model) use ($states, $afterCallbacks) { + foreach ($states as $state) { + $this->callAfterCallbacks($afterCallbacks, $model, $state); + } + }); + } + + /** + * Call after callbacks for each model and state. + * + * @param array $afterCallbacks + * @param Model $model + * @param string $state + * @return void + */ + protected function callAfterCallbacks(array $afterCallbacks, $model, $state) + { + if (!isset($afterCallbacks[$this->class][$state])) { + return; + } + + foreach ($afterCallbacks[$this->class][$state] as $callback) { + $callback($model, $this->faker); + } + } + + /** + * Determine if the given state has an "after" callback. + * + * @param string $state + * @return bool + */ + protected function stateHasAfterCallback($state) + { + return isset($this->afterMaking[$this->class][$state]) || + isset($this->afterCreating[$this->class][$state]); + } +} diff --git a/vendor/topthink/think-migration/src/Migrator.php b/vendor/topthink/think-migration/src/Migrator.php new file mode 100644 index 0000000..1965953 --- /dev/null +++ b/vendor/topthink/think-migration/src/Migrator.php @@ -0,0 +1,27 @@ + +// +---------------------------------------------------------------------- +namespace think\migration; + +use Phinx\Migration\AbstractMigration; +use think\migration\db\Table; + +class Migrator extends AbstractMigration +{ + /** + * @param string $tableName + * @param array $options + * @return Table + */ + public function table($tableName, $options = []) + { + return new Table($tableName, $options, $this->getAdapter()); + } +} diff --git a/vendor/topthink/think-migration/src/Seeder.php b/vendor/topthink/think-migration/src/Seeder.php new file mode 100644 index 0000000..a1c46db --- /dev/null +++ b/vendor/topthink/think-migration/src/Seeder.php @@ -0,0 +1,18 @@ + +// +---------------------------------------------------------------------- +namespace think\migration; + +use Phinx\Seed\AbstractSeed; + +class Seeder extends AbstractSeed +{ + +} diff --git a/vendor/topthink/think-migration/src/Service.php b/vendor/topthink/think-migration/src/Service.php new file mode 100644 index 0000000..614e9e0 --- /dev/null +++ b/vendor/topthink/think-migration/src/Service.php @@ -0,0 +1,51 @@ + +// +---------------------------------------------------------------------- + +namespace think\migration; + +use Faker\Factory as FakerFactory; +use Faker\Generator as FakerGenerator; +use think\migration\command\factory\Create as FactoryCreate; +use think\migration\command\migrate\Breakpoint as MigrateBreakpoint; +use think\migration\command\migrate\Create as MigrateCreate; +use think\migration\command\migrate\Rollback as MigrateRollback; +use think\migration\command\migrate\Run as MigrateRun; +use think\migration\command\migrate\Status as MigrateStatus; +use think\migration\command\seed\Create as SeedCreate; +use think\migration\command\seed\Run as SeedRun; + +class Service extends \think\Service +{ + + public function boot() + { + $this->app->bind(FakerGenerator::class, function () { + return FakerFactory::create($this->app->config->get('app.faker_locale', 'zh_CN')); + }); + + $this->app->bind(Factory::class, function () { + return (new Factory($this->app->make(FakerGenerator::class)))->load($this->app->getRootPath() . 'database/factories/'); + }); + + $this->app->bind('migration.creator', Creator::class); + + $this->commands([ + MigrateCreate::class, + MigrateRun::class, + MigrateRollback::class, + MigrateBreakpoint::class, + MigrateStatus::class, + SeedCreate::class, + SeedRun::class, + FactoryCreate::class, + ]); + } +} diff --git a/vendor/topthink/think-migration/src/command/Migrate.php b/vendor/topthink/think-migration/src/command/Migrate.php new file mode 100644 index 0000000..1d97973 --- /dev/null +++ b/vendor/topthink/think-migration/src/command/Migrate.php @@ -0,0 +1,146 @@ + +// +---------------------------------------------------------------------- + +namespace think\migration\command; + +use Phinx\Db\Adapter\AdapterFactory; +use Phinx\Db\Adapter\ProxyAdapter; +use Phinx\Migration\AbstractMigration; +use Phinx\Migration\MigrationInterface; +use Phinx\Util\Util; +use think\migration\Command; +use think\migration\Migrator; + +abstract class Migrate extends Command +{ + /** + * @var array + */ + protected $migrations; + + protected function getPath() + { + return $this->app->getRootPath() . 'database' . DIRECTORY_SEPARATOR . 'migrations'; + } + + protected function executeMigration(MigrationInterface $migration, $direction = MigrationInterface::UP) + { + $this->output->writeln(''); + $this->output->writeln(' ==' . ' ' . $migration->getVersion() . ' ' . $migration->getName() . ':' . ' ' . (MigrationInterface::UP === $direction ? 'migrating' : 'reverting') . ''); + + // Execute the migration and log the time elapsed. + $start = microtime(true); + + $startTime = time(); + $direction = (MigrationInterface::UP === $direction) ? MigrationInterface::UP : MigrationInterface::DOWN; + $migration->setAdapter($this->getAdapter()); + + // begin the transaction if the adapter supports it + if ($this->getAdapter()->hasTransactions()) { + $this->getAdapter()->beginTransaction(); + } + + // Run the migration + if (method_exists($migration, MigrationInterface::CHANGE)) { + if (MigrationInterface::DOWN === $direction) { + // Create an instance of the ProxyAdapter so we can record all + // of the migration commands for reverse playback + /** @var ProxyAdapter $proxyAdapter */ + $proxyAdapter = AdapterFactory::instance()->getWrapper('proxy', $this->getAdapter()); + $migration->setAdapter($proxyAdapter); + /** @noinspection PhpUndefinedMethodInspection */ + $migration->change(); + $proxyAdapter->executeInvertedCommands(); + $migration->setAdapter($this->getAdapter()); + } else { + /** @noinspection PhpUndefinedMethodInspection */ + $migration->change(); + } + } else { + $migration->{$direction}(); + } + + // commit the transaction if the adapter supports it + if ($this->getAdapter()->hasTransactions()) { + $this->getAdapter()->commitTransaction(); + } + + // Record it in the database + $this->getAdapter() + ->migrated($migration, $direction, date('Y-m-d H:i:s', $startTime), date('Y-m-d H:i:s', time())); + + $end = microtime(true); + + $this->output->writeln(' ==' . ' ' . $migration->getVersion() . ' ' . $migration->getName() . ':' . ' ' . (MigrationInterface::UP === $direction ? 'migrated' : 'reverted') . ' ' . sprintf('%.4fs', $end - $start) . ''); + } + + protected function getVersionLog() + { + return $this->getAdapter()->getVersionLog(); + } + + protected function getVersions() + { + return $this->getAdapter()->getVersions(); + } + + protected function getMigrations() + { + if (null === $this->migrations) { + $phpFiles = glob($this->getPath() . DIRECTORY_SEPARATOR . '*.php', defined('GLOB_BRACE') ? GLOB_BRACE : 0); + + // filter the files to only get the ones that match our naming scheme + $fileNames = []; + /** @var Migrator[] $versions */ + $versions = []; + + foreach ($phpFiles as $filePath) { + if (Util::isValidMigrationFileName(basename($filePath))) { + $version = Util::getVersionFromFileName(basename($filePath)); + + if (isset($versions[$version])) { + throw new \InvalidArgumentException(sprintf('Duplicate migration - "%s" has the same version as "%s"', $filePath, $versions[$version]->getVersion())); + } + + // convert the filename to a class name + $class = Util::mapFileNameToClassName(basename($filePath)); + + if (isset($fileNames[$class])) { + throw new \InvalidArgumentException(sprintf('Migration "%s" has the same name as "%s"', basename($filePath), $fileNames[$class])); + } + + $fileNames[$class] = basename($filePath); + + // load the migration file + /** @noinspection PhpIncludeInspection */ + require_once $filePath; + if (!class_exists($class)) { + throw new \InvalidArgumentException(sprintf('Could not find class "%s" in file "%s"', $class, $filePath)); + } + + // instantiate it + $migration = new $class($version, $this->input, $this->output); + + if (!($migration instanceof AbstractMigration)) { + throw new \InvalidArgumentException(sprintf('The class "%s" in file "%s" must extend \Phinx\Migration\AbstractMigration', $class, $filePath)); + } + + $versions[$version] = $migration; + } + } + + ksort($versions); + $this->migrations = $versions; + } + + return $this->migrations; + } +} diff --git a/vendor/topthink/think-migration/src/command/Seed.php b/vendor/topthink/think-migration/src/command/Seed.php new file mode 100644 index 0000000..e7a2c06 --- /dev/null +++ b/vendor/topthink/think-migration/src/command/Seed.php @@ -0,0 +1,73 @@ + +// +---------------------------------------------------------------------- + +namespace think\migration\command; + +use InvalidArgumentException; +use Phinx\Seed\AbstractSeed; +use Phinx\Util\Util; +use think\migration\Command; +use think\migration\Seeder; + +abstract class Seed extends Command +{ + + /** + * @var array + */ + protected $seeds; + + protected function getPath() + { + return $this->app->getRootPath() . 'database' . DIRECTORY_SEPARATOR . 'seeds'; + } + + public function getSeeds() + { + if (null === $this->seeds) { + $phpFiles = glob($this->getPath() . DIRECTORY_SEPARATOR . '*.php', defined('GLOB_BRACE') ? GLOB_BRACE : 0); + + // filter the files to only get the ones that match our naming scheme + $fileNames = []; + /** @var Seeder[] $seeds */ + $seeds = []; + + foreach ($phpFiles as $filePath) { + if (Util::isValidSeedFileName(basename($filePath))) { + // convert the filename to a class name + $class = pathinfo($filePath, PATHINFO_FILENAME); + $fileNames[$class] = basename($filePath); + + // load the seed file + /** @noinspection PhpIncludeInspection */ + require_once $filePath; + if (!class_exists($class)) { + throw new InvalidArgumentException(sprintf('Could not find class "%s" in file "%s"', $class, $filePath)); + } + + // instantiate it + $seed = new $class($this->input, $this->output); + + if (!($seed instanceof AbstractSeed)) { + throw new InvalidArgumentException(sprintf('The class "%s" in file "%s" must extend \Phinx\Seed\AbstractSeed', $class, $filePath)); + } + + $seeds[$class] = $seed; + } + } + + ksort($seeds); + $this->seeds = $seeds; + } + + return $this->seeds; + } +} diff --git a/vendor/topthink/think-migration/src/command/factory/Create.php b/vendor/topthink/think-migration/src/command/factory/Create.php new file mode 100644 index 0000000..3db6497 --- /dev/null +++ b/vendor/topthink/think-migration/src/command/factory/Create.php @@ -0,0 +1,82 @@ + +// +---------------------------------------------------------------------- + +namespace think\migration\command\factory; + +use InvalidArgumentException; +use Phinx\Util\Util; +use RuntimeException; +use think\console\Command; +use think\console\input\Argument as InputArgument; + +class Create extends Command +{ + protected function configure() + { + $this->setName('factory:create') + ->setDescription('Create a new model factory') + ->addArgument('name', InputArgument::REQUIRED, 'What is the name of the model?'); + } + + protected function handle() + { + $path = $this->getPath(); + + if (!file_exists($path)) { + mkdir($path, 0755, true); + } + + if (!is_dir($path)) { + throw new InvalidArgumentException(sprintf('Factory directory "%s" does not exist', $path)); + } + + if (!is_writable($path)) { + throw new InvalidArgumentException(sprintf('Factory directory "%s" is not writable', $path)); + } + + $path = realpath($path); + $className = $this->input->getArgument('name'); + + if (!Util::isValidPhinxClassName($className)) { + throw new InvalidArgumentException(sprintf('The migration class name "%s" is invalid. Please use CamelCase format.', $className)); + } + + $filePath = $path . DIRECTORY_SEPARATOR . $className . '.php'; + + if (is_file($filePath)) { + throw new InvalidArgumentException(sprintf('The file "%s" already exists', $filePath)); + } + + // Load the alternative template if it is defined. + $contents = file_get_contents($this->getTemplate()); + + // inject the class names appropriate to this migration + $contents = strtr($contents, [ + '"ModelClass"' => "\\app\\model\\" . $className . '::class', + ]); + + if (false === file_put_contents($filePath, $contents)) { + throw new RuntimeException(sprintf('The file "%s" could not be written to', $path)); + } + + $this->output->writeln('created .' . str_replace(getcwd(), '', $filePath)); + } + + protected function getTemplate() + { + return __DIR__ . '/../stubs/factory.stub'; + } + + protected function getPath() + { + return $this->app->getRootPath() . 'database' . DIRECTORY_SEPARATOR . 'factories'; + } +} diff --git a/vendor/topthink/think-migration/src/command/migrate/Breakpoint.php b/vendor/topthink/think-migration/src/command/migrate/Breakpoint.php new file mode 100644 index 0000000..cfb2d32 --- /dev/null +++ b/vendor/topthink/think-migration/src/command/migrate/Breakpoint.php @@ -0,0 +1,92 @@ + +// +---------------------------------------------------------------------- + +namespace think\migration\command\migrate; + +use think\console\Input; +use think\console\input\Option as InputOption; +use think\console\Output; +use think\migration\command\Migrate; + +class Breakpoint extends Migrate +{ + protected function configure() + { + $this->setName('migrate:breakpoint') + ->setDescription('Manage breakpoints') + ->addOption('--target', '-t', InputOption::VALUE_REQUIRED, 'The version number to set or clear a breakpoint against') + ->addOption('--remove-all', '-r', InputOption::VALUE_NONE, 'Remove all breakpoints') + ->setHelp(<<breakpoint command allows you to set or clear a breakpoint against a specific target to inhibit rollbacks beyond a certain target. +If no target is supplied then the most recent migration will be used. +You cannot specify un-migrated targets + +php think migrate:breakpoint +php think migrate:breakpoint -t 20110103081132 +php think migrate:breakpoint -r +EOT + ); + } + + protected function execute(Input $input, Output $output) + { + $version = $input->getOption('target'); + $removeAll = $input->getOption('remove-all'); + + if ($version && $removeAll) { + throw new \InvalidArgumentException('Cannot toggle a breakpoint and remove all breakpoints at the same time.'); + } + + // Remove all breakpoints + if ($removeAll) { + $this->removeBreakpoints(); + } else { + // Toggle the breakpoint. + $this->toggleBreakpoint($version); + } + } + + protected function toggleBreakpoint($version) + { + $migrations = $this->getMigrations(); + $versions = $this->getVersionLog(); + + if (empty($versions) || empty($migrations)) { + return; + } + + if (null === $version) { + $lastVersion = end($versions); + $version = $lastVersion['version']; + } + + if (0 != $version && !isset($migrations[$version])) { + $this->output->writeln(sprintf('warning %s is not a valid version', $version)); + return; + } + + $this->getAdapter()->toggleBreakpoint($migrations[$version]); + + $versions = $this->getVersionLog(); + + $this->output->writeln(' Breakpoint ' . ($versions[$version]['breakpoint'] ? 'set' : 'cleared') . ' for ' . $version . '' . ' ' . $migrations[$version]->getName() . ''); + } + + /** + * Remove all breakpoints + * + * @return void + */ + protected function removeBreakpoints() + { + $this->output->writeln(sprintf(' %d breakpoints cleared.', $this->getAdapter()->resetAllBreakpoints())); + } +} diff --git a/vendor/topthink/think-migration/src/command/migrate/Create.php b/vendor/topthink/think-migration/src/command/migrate/Create.php new file mode 100644 index 0000000..8f96de7 --- /dev/null +++ b/vendor/topthink/think-migration/src/command/migrate/Create.php @@ -0,0 +1,55 @@ + +// +---------------------------------------------------------------------- + +namespace think\migration\command\migrate; + +use InvalidArgumentException; +use RuntimeException; +use think\console\Command; +use think\console\Input; +use think\console\input\Argument as InputArgument; +use think\console\Output; +use think\migration\Creator; + +class Create extends Command +{ + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setName('migrate:create') + ->setDescription('Create a new migration') + ->addArgument('name', InputArgument::REQUIRED, 'What is the name of the migration?') + ->setHelp(sprintf('%sCreates a new database migration%s', PHP_EOL, PHP_EOL)); + } + + /** + * Create the new migration. + * + * @param Input $input + * @param Output $output + * @return void + * @throws InvalidArgumentException + * @throws RuntimeException + */ + protected function execute(Input $input, Output $output) + { + /** @var Creator $creator */ + $creator = $this->app->get('migration.creator'); + + $className = $input->getArgument('name'); + + $path = $creator->create($className); + + $output->writeln('created .' . str_replace(getcwd(), '', realpath($path))); + } + +} diff --git a/vendor/topthink/think-migration/src/command/migrate/Rollback.php b/vendor/topthink/think-migration/src/command/migrate/Rollback.php new file mode 100644 index 0000000..e668ac7 --- /dev/null +++ b/vendor/topthink/think-migration/src/command/migrate/Rollback.php @@ -0,0 +1,146 @@ + +// +---------------------------------------------------------------------- + +namespace think\migration\command\migrate; + +use Phinx\Migration\MigrationInterface; +use think\console\input\Option as InputOption; +use think\console\Input; +use think\console\Output; +use think\migration\command\Migrate; + +class Rollback extends Migrate +{ + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setName('migrate:rollback') + ->setDescription('Rollback the last or to a specific migration') + ->addOption('--target', '-t', InputOption::VALUE_REQUIRED, 'The version number to rollback to') + ->addOption('--date', '-d', InputOption::VALUE_REQUIRED, 'The date to rollback to') + ->addOption('--force', '-f', InputOption::VALUE_NONE, 'Force rollback to ignore breakpoints') + ->setHelp(<<migrate:rollback command reverts the last migration, or optionally up to a specific version + +php think migrate:rollback +php think migrate:rollback -t 20111018185412 +php think migrate:rollback -d 20111018 +php think migrate:rollback -v + +EOT + ); + } + + /** + * Rollback the migration. + * + * @param Input $input + * @param Output $output + * @return void + */ + protected function execute(Input $input, Output $output) + { + $version = $input->getOption('target'); + $date = $input->getOption('date'); + $force = !!$input->getOption('force'); + + // rollback the specified environment + $start = microtime(true); + if (null !== $date) { + $this->rollbackToDateTime(new \DateTime($date), $force); + } else { + $this->rollback($version, $force); + } + $end = microtime(true); + + $output->writeln(''); + $output->writeln('All Done. Took ' . sprintf('%.4fs', $end - $start) . ''); + } + + protected function rollback($version = null, $force = false) + { + $migrations = $this->getMigrations(); + $versionLog = $this->getVersionLog(); + $versions = array_keys($versionLog); + + ksort($migrations); + sort($versions); + + // Check we have at least 1 migration to revert + if (empty($versions) || $version == end($versions)) { + $this->output->writeln('No migrations to rollback'); + return; + } + + // If no target version was supplied, revert the last migration + if (null === $version) { + // Get the migration before the last run migration + $prev = count($versions) - 2; + $version = $prev < 0 ? 0 : $versions[$prev]; + } else { + // Get the first migration number + $first = $versions[0]; + + // If the target version is before the first migration, revert all migrations + if ($version < $first) { + $version = 0; + } + } + + // Check the target version exists + if (0 !== $version && !isset($migrations[$version])) { + $this->output->writeln("Target version ($version) not found"); + return; + } + + // Revert the migration(s) + krsort($migrations); + foreach ($migrations as $migration) { + if ($migration->getVersion() <= $version) { + break; + } + + if (in_array($migration->getVersion(), $versions)) { + if (isset($versionLog[$migration->getVersion()]) && 0 != $versionLog[$migration->getVersion()]['breakpoint'] && !$force) { + $this->output->writeln('Breakpoint reached. Further rollbacks inhibited.'); + break; + } + $this->executeMigration($migration, MigrationInterface::DOWN); + } + } + } + + protected function rollbackToDateTime(\DateTime $dateTime, $force = false) + { + $versions = $this->getVersions(); + $dateString = $dateTime->format('YmdHis'); + sort($versions); + + $earlierVersion = null; + $availableMigrations = array_filter($versions, function ($version) use ($dateString, &$earlierVersion) { + if ($version <= $dateString) { + $earlierVersion = $version; + } + return $version >= $dateString; + }); + + if (count($availableMigrations) > 0) { + if (is_null($earlierVersion)) { + $this->output->writeln('Rolling back all migrations'); + $migration = 0; + } else { + $this->output->writeln('Rolling back to version ' . $earlierVersion); + $migration = $earlierVersion; + } + $this->rollback($migration, $force); + } + } +} diff --git a/vendor/topthink/think-migration/src/command/migrate/Run.php b/vendor/topthink/think-migration/src/command/migrate/Run.php new file mode 100644 index 0000000..4714f02 --- /dev/null +++ b/vendor/topthink/think-migration/src/command/migrate/Run.php @@ -0,0 +1,140 @@ + +// +---------------------------------------------------------------------- + +namespace think\migration\command\migrate; + +use Phinx\Migration\MigrationInterface; +use think\console\Input; +use think\console\input\Option as InputOption; +use think\console\Output; +use think\migration\command\Migrate; + +class Run extends Migrate +{ + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setName('migrate:run') + ->setDescription('Migrate the database') + ->addOption('--target', '-t', InputOption::VALUE_REQUIRED, 'The version number to migrate to') + ->addOption('--date', '-d', InputOption::VALUE_REQUIRED, 'The date to migrate to') + ->setHelp(<<migrate:run command runs all available migrations, optionally up to a specific version + +php think migrate:run +php think migrate:run -t 20110103081132 +php think migrate:run -d 20110103 +php think migrate:run -v + +EOT + ); + } + + /** + * Migrate the database. + * + * @param Input $input + * @param Output $output + */ + protected function execute(Input $input, Output $output) + { + $version = $input->getOption('target'); + $date = $input->getOption('date'); + + // run the migrations + $start = microtime(true); + if (null !== $date) { + $this->migrateToDateTime(new \DateTime($date)); + } else { + $this->migrate($version); + } + $end = microtime(true); + + $output->writeln(''); + $output->writeln('All Done. Took ' . sprintf('%.4fs', $end - $start) . ''); + } + + public function migrateToDateTime(\DateTime $dateTime) + { + $versions = array_keys($this->getMigrations()); + $dateString = $dateTime->format('YmdHis'); + + $outstandingMigrations = array_filter($versions, function ($version) use ($dateString) { + return $version <= $dateString; + }); + + if (count($outstandingMigrations) > 0) { + $migration = max($outstandingMigrations); + $this->output->writeln('Migrating to version ' . $migration); + $this->migrate($migration); + } + } + + protected function migrate($version = null) + { + $migrations = $this->getMigrations(); + $versions = $this->getVersions(); + $current = $this->getCurrentVersion(); + + if (empty($versions) && empty($migrations)) { + return; + } + + if (null === $version) { + $version = max(array_merge($versions, array_keys($migrations))); + } else { + if (0 != $version && !isset($migrations[$version])) { + $this->output->writeln(sprintf('warning %s is not a valid version', $version)); + return; + } + } + + // are we migrating up or down? + $direction = $version > $current ? MigrationInterface::UP : MigrationInterface::DOWN; + + if ($direction === MigrationInterface::DOWN) { + // run downs first + krsort($migrations); + foreach ($migrations as $migration) { + if ($migration->getVersion() <= $version) { + break; + } + + if (in_array($migration->getVersion(), $versions)) { + $this->executeMigration($migration, MigrationInterface::DOWN); + } + } + } + + ksort($migrations); + foreach ($migrations as $migration) { + if ($migration->getVersion() > $version) { + break; + } + + if (!in_array($migration->getVersion(), $versions)) { + $this->executeMigration($migration, MigrationInterface::UP); + } + } + } + + protected function getCurrentVersion() + { + $versions = $this->getVersions(); + $version = 0; + + if (!empty($versions)) { + $version = end($versions); + } + + return $version; + } +} diff --git a/vendor/topthink/think-migration/src/command/migrate/Status.php b/vendor/topthink/think-migration/src/command/migrate/Status.php new file mode 100644 index 0000000..1b5f8d4 --- /dev/null +++ b/vendor/topthink/think-migration/src/command/migrate/Status.php @@ -0,0 +1,124 @@ + +// +---------------------------------------------------------------------- + +namespace think\migration\command\migrate; + +use think\console\input\Option as InputOption; +use think\console\Input; +use think\console\Output; +use think\migration\command\Migrate; + +class Status extends Migrate +{ + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setName('migrate:status') + ->setDescription('Show migration status') + ->addOption('--format', '-f', InputOption::VALUE_REQUIRED, 'The output format: text or json. Defaults to text.') + ->setHelp(<<migrate:status command prints a list of all migrations, along with their current status + +php think migrate:status +php think migrate:status -f json +EOT + ); + } + + /** + * Show the migration status. + * + * @param Input $input + * @param Output $output + * @return integer 0 if all migrations are up, or an error code + */ + protected function execute(Input $input, Output $output) + { + $format = $input->getOption('format'); + + if (null !== $format) { + $output->writeln('using format ' . $format); + } + + // print the status + return $this->printStatus($format); + } + + protected function printStatus($format = null) + { + $output = $this->output; + $migrations = []; + if (count($this->getMigrations())) { + // TODO - rewrite using Symfony Table Helper as we already have this library + // included and it will fix formatting issues (e.g drawing the lines) + $output->writeln(''); + $output->writeln(' Status Migration ID Started Finished Migration Name '); + $output->writeln('----------------------------------------------------------------------------------'); + + $versions = $this->getVersionLog(); + $maxNameLength = $versions ? max(array_map(function ($version) { + return strlen($version['migration_name']); + }, $versions)) : 0; + + foreach ($this->getMigrations() as $migration) { + $version = array_key_exists($migration->getVersion(), $versions) ? $versions[$migration->getVersion()] : false; + if ($version) { + $status = ' up '; + } else { + $status = ' down '; + } + $maxNameLength = max($maxNameLength, strlen($migration->getName())); + + $output->writeln(sprintf('%s %14.0f %19s %19s %s', $status, $migration->getVersion(), $version['start_time'], $version['end_time'], $migration->getName())); + + if ($version && $version['breakpoint']) { + $output->writeln(' BREAKPOINT SET'); + } + + $migrations[] = [ + 'migration_status' => trim(strip_tags($status)), + 'migration_id' => sprintf('%14.0f', $migration->getVersion()), + 'migration_name' => $migration->getName() + ]; + unset($versions[$migration->getVersion()]); + } + + if (count($versions)) { + foreach ($versions as $missing => $version) { + $output->writeln(sprintf(' up %14.0f %19s %19s %s ** MISSING **', $missing, $version['start_time'], $version['end_time'], str_pad($version['migration_name'], $maxNameLength, ' '))); + + if ($version && $version['breakpoint']) { + $output->writeln(' BREAKPOINT SET'); + } + } + } + } else { + // there are no migrations + $output->writeln(''); + $output->writeln('There are no available migrations. Try creating one using the create command.'); + } + + // write an empty line + $output->writeln(''); + if ($format !== null) { + switch ($format) { + case 'json': + $output->writeln(json_encode([ + 'pending_count' => count($this->getMigrations()), + 'migrations' => $migrations + ])); + break; + default: + $output->writeln('Unsupported format: ' . $format . ''); + } + } + } +} diff --git a/vendor/topthink/think-migration/src/command/seed/Create.php b/vendor/topthink/think-migration/src/command/seed/Create.php new file mode 100644 index 0000000..6873980 --- /dev/null +++ b/vendor/topthink/think-migration/src/command/seed/Create.php @@ -0,0 +1,83 @@ + +// +---------------------------------------------------------------------- + +namespace think\migration\command\seed; + +use Phinx\Util\Util; +use think\console\Input; +use think\console\input\Argument as InputArgument; +use think\console\Output; +use think\migration\command\Seed; + +class Create extends Seed +{ + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setName('seed:create') + ->setDescription('Create a new database seeder') + ->addArgument('name', InputArgument::REQUIRED, 'What is the name of the seeder?') + ->setHelp(sprintf('%sCreates a new database seeder%s', PHP_EOL, PHP_EOL)); + } + + /** + * Create the new seeder. + * + * @param Input $input + * @param Output $output + * @return void + * @throws \InvalidArgumentException + * @throws \RuntimeException + */ + protected function execute(Input $input, Output $output) + { + $path = $this->getPath(); + + if (!file_exists($path)) { + mkdir($path, 0755, true); + } + + $this->verifyMigrationDirectory($path); + + $path = realpath($path); + + $className = $input->getArgument('name'); + + if (!Util::isValidPhinxClassName($className)) { + throw new \InvalidArgumentException(sprintf('The seed class name "%s" is invalid. Please use CamelCase format', $className)); + } + + // Compute the file path + $filePath = $path . DIRECTORY_SEPARATOR . $className . '.php'; + + if (is_file($filePath)) { + throw new \InvalidArgumentException(sprintf('The file "%s" already exists', basename($filePath))); + } + + // inject the class names appropriate to this seeder + $contents = file_get_contents($this->getTemplate()); + $classes = [ + 'SeederClass' => $className, + ]; + $contents = strtr($contents, $classes); + + if (false === file_put_contents($filePath, $contents)) { + throw new \RuntimeException(sprintf('The file "%s" could not be written to', $path)); + } + + $output->writeln('created .' . str_replace(getcwd(), '', $filePath)); + } + + protected function getTemplate() + { + return __DIR__ . '/../stubs/seed.stub'; + } +} diff --git a/vendor/topthink/think-migration/src/command/seed/Run.php b/vendor/topthink/think-migration/src/command/seed/Run.php new file mode 100644 index 0000000..a0ac8f9 --- /dev/null +++ b/vendor/topthink/think-migration/src/command/seed/Run.php @@ -0,0 +1,107 @@ + +// +---------------------------------------------------------------------- + +namespace think\migration\command\seed; + +use Phinx\Seed\SeedInterface; +use think\console\Input; +use think\console\input\Option as InputOption; +use think\console\Output; +use think\migration\command\Seed; + +class Run extends Seed +{ + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setName('seed:run') + ->setDescription('Run database seeders') + ->addOption('--seed', '-s', InputOption::VALUE_REQUIRED, 'What is the name of the seeder?') + ->setHelp(<<seed:run command runs all available or individual seeders + +php think seed:run +php think seed:run -s UserSeeder +php think seed:run -v + +EOT + ); + } + + /** + * Run database seeders. + * + * @param Input $input + * @param Output $output + * @return void + */ + protected function execute(Input $input, Output $output) + { + $seed = $input->getOption('seed'); + + // run the seed(ers) + $start = microtime(true); + $this->seed($seed); + $end = microtime(true); + + $output->writeln(''); + $output->writeln('All Done. Took ' . sprintf('%.4fs', $end - $start) . ''); + } + + public function seed($seed = null) + { + $seeds = $this->getSeeds(); + + if (null === $seed) { + // run all seeders + foreach ($seeds as $seeder) { + if (array_key_exists($seeder->getName(), $seeds)) { + $this->executeSeed($seeder); + } + } + } else { + // run only one seeder + if (array_key_exists($seed, $seeds)) { + $this->executeSeed($seeds[$seed]); + } else { + throw new \InvalidArgumentException(sprintf('The seed class "%s" does not exist', $seed)); + } + } + } + + protected function executeSeed(SeedInterface $seed) + { + $this->output->writeln(''); + $this->output->writeln(' ==' . ' ' . $seed->getName() . ':' . ' seeding'); + + // Execute the seeder and log the time elapsed. + $start = microtime(true); + $seed->setAdapter($this->getAdapter()); + + // begin the transaction if the adapter supports it + if ($this->getAdapter()->hasTransactions()) { + $this->getAdapter()->beginTransaction(); + } + + // Run the seeder + if (method_exists($seed, SeedInterface::RUN)) { + $seed->run(); + } + + // commit the transaction if the adapter supports it + if ($this->getAdapter()->hasTransactions()) { + $this->getAdapter()->commitTransaction(); + } + $end = microtime(true); + + $this->output->writeln(' ==' . ' ' . $seed->getName() . ':' . ' seeded' . ' ' . sprintf('%.4fs', $end - $start) . ''); + } +} diff --git a/vendor/topthink/think-migration/src/command/stubs/factory.stub b/vendor/topthink/think-migration/src/command/stubs/factory.stub new file mode 100644 index 0000000..eef91dc --- /dev/null +++ b/vendor/topthink/think-migration/src/command/stubs/factory.stub @@ -0,0 +1,11 @@ +define("ModelClass", function (Faker $faker) { + return [ + // + ]; +}); diff --git a/vendor/topthink/think-migration/src/command/stubs/migrate.stub b/vendor/topthink/think-migration/src/command/stubs/migrate.stub new file mode 100644 index 0000000..f11d29a --- /dev/null +++ b/vendor/topthink/think-migration/src/command/stubs/migrate.stub @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- + +namespace think\migration\db; + +use Phinx\Db\Adapter\AdapterInterface; +use Phinx\Db\Adapter\MysqlAdapter; + +class Column extends \Phinx\Db\Table\Column +{ + protected $unique = false; + + public function setNullable() + { + return $this->setNull(true); + } + + public function setUnsigned() + { + return $this->setSigned(false); + } + + public function setUnique() + { + $this->unique = true; + return $this; + } + + public function getUnique() + { + return $this->unique; + } + + public function isUnique() + { + return $this->getUnique(); + } + + public static function make($name, $type, $options = []) + { + $column = new self(); + $column->setName($name); + $column->setType($type); + $column->setOptions($options); + return $column; + } + + public static function bigInteger($name) + { + return self::make($name, AdapterInterface::PHINX_TYPE_BIG_INTEGER); + } + + public static function binary($name) + { + return self::make($name, AdapterInterface::PHINX_TYPE_BLOB); + } + + public static function boolean($name) + { + return self::make($name, AdapterInterface::PHINX_TYPE_BOOLEAN); + } + + public static function char($name, $length = 255) + { + return self::make($name, AdapterInterface::PHINX_TYPE_CHAR, compact('length')); + } + + public static function date($name) + { + return self::make($name, AdapterInterface::PHINX_TYPE_DATE); + } + + public static function dateTime($name) + { + return self::make($name, AdapterInterface::PHINX_TYPE_DATETIME); + } + + public static function decimal($name, $precision = 8, $scale = 2) + { + return self::make($name, AdapterInterface::PHINX_TYPE_DECIMAL, compact('precision', 'scale')); + } + + public static function enum($name, array $values) + { + return self::make($name, AdapterInterface::PHINX_TYPE_ENUM, compact('values')); + } + + public static function float($name) + { + return self::make($name, AdapterInterface::PHINX_TYPE_FLOAT); + } + + public static function integer($name) + { + return self::make($name, AdapterInterface::PHINX_TYPE_INTEGER); + } + + public static function json($name) + { + return self::make($name, AdapterInterface::PHINX_TYPE_JSON); + } + + public static function jsonb($name) + { + return self::make($name, AdapterInterface::PHINX_TYPE_JSONB); + } + + public static function longText($name) + { + return self::make($name, AdapterInterface::PHINX_TYPE_TEXT, ['length' => MysqlAdapter::TEXT_LONG]); + } + + public static function mediumInteger($name) + { + return self::make($name, AdapterInterface::PHINX_TYPE_INTEGER, ['length' => MysqlAdapter::INT_MEDIUM]); + } + + public static function mediumText($name) + { + return self::make($name, AdapterInterface::PHINX_TYPE_TEXT, ['length' => MysqlAdapter::TEXT_MEDIUM]); + } + + public static function smallInteger($name) + { + return self::make($name, AdapterInterface::PHINX_TYPE_INTEGER, ['length' => MysqlAdapter::INT_SMALL]); + } + + public static function string($name, $length = 255) + { + return self::make($name, AdapterInterface::PHINX_TYPE_STRING, compact('length')); + } + + public static function text($name) + { + return self::make($name, AdapterInterface::PHINX_TYPE_TEXT); + } + + public static function time($name) + { + return self::make($name, AdapterInterface::PHINX_TYPE_TIME); + } + + public static function tinyInteger($name) + { + return self::make($name, AdapterInterface::PHINX_TYPE_INTEGER, ['length' => MysqlAdapter::INT_TINY]); + } + + public static function unsignedInteger($name) + { + return self::integer($name)->setUnSigned(); + } + + public static function timestamp($name) + { + return self::make($name, AdapterInterface::PHINX_TYPE_TIMESTAMP); + } + + public static function uuid($name) + { + return self::make($name, AdapterInterface::PHINX_TYPE_UUID); + } + +} diff --git a/vendor/topthink/think-migration/src/db/Table.php b/vendor/topthink/think-migration/src/db/Table.php new file mode 100644 index 0000000..cf4daa9 --- /dev/null +++ b/vendor/topthink/think-migration/src/db/Table.php @@ -0,0 +1,135 @@ + +// +---------------------------------------------------------------------- + +namespace think\migration\db; + +use Phinx\Db\Table\Index; + +class Table extends \Phinx\Db\Table +{ + /** + * 设置id + * @param $id + * @return $this + */ + public function setId($id) + { + $this->options['id'] = $id; + return $this; + } + + /** + * 设置主键 + * @param $key + * @return $this + */ + public function setPrimaryKey($key) + { + $this->options['primary_key'] = $key; + return $this; + } + + /** + * 设置引擎 + * @param $engine + * @return $this + */ + public function setEngine($engine) + { + $this->options['engine'] = $engine; + return $this; + } + + /** + * 设置表注释 + * @param $comment + * @return $this + */ + public function setComment($comment) + { + $this->options['comment'] = $comment; + return $this; + } + + /** + * 设置排序比对方法 + * @param $collation + * @return $this + */ + public function setCollation($collation) + { + $this->options['collation'] = $collation; + return $this; + } + + public function addSoftDelete() + { + $this->addColumn(Column::timestamp('delete_time')->setNullable()); + return $this; + } + + public function addMorphs($name, $indexName = null) + { + $this->addColumn(Column::unsignedInteger("{$name}_id")); + $this->addColumn(Column::string("{$name}_type")); + $this->addIndex(["{$name}_id", "{$name}_type"], ['name' => $indexName]); + return $this; + } + + public function addNullableMorphs($name, $indexName = null) + { + $this->addColumn(Column::unsignedInteger("{$name}_id")->setNullable()); + $this->addColumn(Column::string("{$name}_type")->setNullable()); + $this->addIndex(["{$name}_id", "{$name}_type"], ['name' => $indexName]); + return $this; + } + + /** + * @param string $createdAtColumnName + * @param string $updatedAtColumnName + * @return \Phinx\Db\Table|Table + */ + public function addTimestamps($createdAtColumnName = 'create_time', $updatedAtColumnName = 'update_time') + { + return parent::addTimestamps($createdAtColumnName, $updatedAtColumnName); + } + + /** + * @param \Phinx\Db\Table\Column|string $columnName + * @param null $type + * @param array $options + * @return \Phinx\Db\Table|Table + */ + public function addColumn($columnName, $type = null, $options = []) + { + if ($columnName instanceof Column && $columnName->getUnique()) { + $index = new Index(); + $index->setColumns([$columnName->getName()]); + $index->setType(Index::UNIQUE); + $this->addIndex($index); + } + return parent::addColumn($columnName, $type, $options); + } + + /** + * @param string $columnName + * @param null $newColumnType + * @param array $options + * @return \Phinx\Db\Table|Table + */ + public function changeColumn($columnName, $newColumnType = null, $options = []) + { + if ($columnName instanceof \Phinx\Db\Table\Column) { + return parent::changeColumn($columnName->getName(), $columnName, $options); + } + return parent::changeColumn($columnName, $newColumnType, $options); + } +} diff --git a/vendor/topthink/think-migration/src/helper.php b/vendor/topthink/think-migration/src/helper.php new file mode 100644 index 0000000..6dc47ab --- /dev/null +++ b/vendor/topthink/think-migration/src/helper.php @@ -0,0 +1,40 @@ +of($arguments[0], $arguments[1])->times($arguments[2] ?? null); + } elseif (isset($arguments[1])) { + return $factory->of($arguments[0])->times($arguments[1]); + } + + return $factory->of($arguments[0]); + } +} + +if (!function_exists('database_path')) { + /** + * 获取数据迁移脚本地址 + * @param string $path + * @return string + */ + function database_path($path = '') + { + return app()->getRootPath() . 'database' . DIRECTORY_SEPARATOR . $path; + } +} diff --git a/vendor/topthink/think-orm/.gitattributes b/vendor/topthink/think-orm/.gitattributes new file mode 100644 index 0000000..36c9cd1 --- /dev/null +++ b/vendor/topthink/think-orm/.gitattributes @@ -0,0 +1,3 @@ +/.github export-ignore +/tests export-ignore +/phpunit.xml export-ignore \ No newline at end of file diff --git a/vendor/topthink/think-orm/.gitignore b/vendor/topthink/think-orm/.gitignore new file mode 100644 index 0000000..82cfc4e --- /dev/null +++ b/vendor/topthink/think-orm/.gitignore @@ -0,0 +1,3 @@ +.idea +composer.lock +vendor diff --git a/vendor/topthink/think-orm/LICENSE b/vendor/topthink/think-orm/LICENSE new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/vendor/topthink/think-orm/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/topthink/think-orm/README.md b/vendor/topthink/think-orm/README.md new file mode 100644 index 0000000..c65afe9 --- /dev/null +++ b/vendor/topthink/think-orm/README.md @@ -0,0 +1,27 @@ +# ThinkORM + +基于PHP7.1+ 和PDO实现的ORM,支持多数据库,2.0版本主要特性包括: + +* 基于PDO和PHP强类型实现 +* 支持原生查询和查询构造器 +* 自动参数绑定和预查询 +* 简洁易用的查询功能 +* 强大灵活的模型用法 +* 支持预载入关联查询和延迟关联查询 +* 支持多数据库及动态切换 +* 支持`MongoDb` +* 支持分布式及事务 +* 支持断点重连 +* 支持`JSON`查询 +* 支持数据库日志 +* 支持`PSR-16`缓存及`PSR-3`日志规范 + + +## 安装 +~~~ +composer require topthink/think-orm +~~~ + +## 文档 + +详细参考 [ThinkORM开发指南](https://www.kancloud.cn/manual/think-orm/content) diff --git a/vendor/topthink/think-orm/composer.json b/vendor/topthink/think-orm/composer.json new file mode 100644 index 0000000..5e86ac8 --- /dev/null +++ b/vendor/topthink/think-orm/composer.json @@ -0,0 +1,42 @@ +{ + "name": "topthink/think-orm", + "description": "think orm", + "keywords": [ + "orm", + "database" + ], + "license": "Apache-2.0", + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "require": { + "php": ">=7.1.0", + "ext-json": "*", + "ext-pdo": "*", + "psr/simple-cache": "^1.0", + "psr/log": "~1.0", + "topthink/think-helper":"^3.1" + }, + "require-dev": { + "phpunit/phpunit": "^7|^8|^9.5" + }, + "autoload": { + "psr-4": { + "think\\": "src" + }, + "files": [ + "stubs/load_stubs.php" + ] + }, + "autoload-dev": { + "psr-4": { + "tests\\": "tests" + } + }, + "config": { + "sort-packages": true + } +} diff --git a/vendor/topthink/think-orm/src/DbManager.php b/vendor/topthink/think-orm/src/DbManager.php new file mode 100644 index 0000000..f223cd2 --- /dev/null +++ b/vendor/topthink/think-orm/src/DbManager.php @@ -0,0 +1,376 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use InvalidArgumentException; +use Psr\Log\LoggerInterface; +use Psr\SimpleCache\CacheInterface; +use think\db\BaseQuery; +use think\db\ConnectionInterface; +use think\db\Query; +use think\db\Raw; + +/** + * Class DbManager + * @package think + * @mixin BaseQuery + * @mixin Query + */ +class DbManager +{ + /** + * 数据库连接实例 + * @var array + */ + protected $instance = []; + + /** + * 数据库配置 + * @var array + */ + protected $config = []; + + /** + * Event对象或者数组 + * @var array|object + */ + protected $event; + + /** + * SQL监听 + * @var array + */ + protected $listen = []; + + /** + * SQL日志 + * @var array + */ + protected $dbLog = []; + + /** + * 查询次数 + * @var int + */ + protected $queryTimes = 0; + + /** + * 查询缓存对象 + * @var CacheInterface + */ + protected $cache; + + /** + * 查询日志对象 + * @var LoggerInterface + */ + protected $log; + + /** + * 架构函数 + * @access public + */ + public function __construct() + { + $this->modelMaker(); + } + + /** + * 注入模型对象 + * @access public + * @return void + */ + protected function modelMaker() + { + Model::setDb($this); + + if (is_object($this->event)) { + Model::setEvent($this->event); + } + + Model::maker(function (Model $model) { + $isAutoWriteTimestamp = $model->getAutoWriteTimestamp(); + + if (is_null($isAutoWriteTimestamp)) { + // 自动写入时间戳 + $model->isAutoWriteTimestamp($this->getConfig('auto_timestamp', true)); + } + + $dateFormat = $model->getDateFormat(); + + if (is_null($dateFormat)) { + // 设置时间戳格式 + $model->setDateFormat($this->getConfig('datetime_format', 'Y-m-d H:i:s')); + } + }); + } + + /** + * 监听SQL + * @access protected + * @return void + */ + public function triggerSql(): void + {} + + /** + * 初始化配置参数 + * @access public + * @param array $config 连接配置 + * @return void + */ + public function setConfig($config): void + { + $this->config = $config; + } + + /** + * 设置缓存对象 + * @access public + * @param CacheInterface $cache 缓存对象 + * @return void + */ + public function setCache(CacheInterface $cache): void + { + $this->cache = $cache; + } + + /** + * 设置日志对象 + * @access public + * @param LoggerInterface $log 日志对象 + * @return void + */ + public function setLog(LoggerInterface $log): void + { + $this->log = $log; + } + + /** + * 记录SQL日志 + * @access protected + * @param string $log SQL日志信息 + * @param string $type 日志类型 + * @return void + */ + public function log(string $log, string $type = 'sql') + { + if ($this->log) { + $this->log->log($type, $log); + } else { + $this->dbLog[$type][] = $log; + } + } + + /** + * 获得查询日志(没有设置日志对象使用) + * @access public + * @param bool $clear 是否清空 + * @return array + */ + public function getDbLog(bool $clear = false): array + { + $logs = $this->dbLog; + if ($clear) { + $this->dbLog = []; + } + + return $logs; + } + + /** + * 获取配置参数 + * @access public + * @param string $name 配置参数 + * @param mixed $default 默认值 + * @return mixed + */ + public function getConfig(string $name = '', $default = null) + { + if ('' === $name) { + return $this->config; + } + + return $this->config[$name] ?? $default; + } + + /** + * 创建/切换数据库连接查询 + * @access public + * @param string|null $name 连接配置标识 + * @param bool $force 强制重新连接 + * @return ConnectionInterface + */ + public function connect(string $name = null, bool $force = false) + { + return $this->instance($name, $force); + } + + /** + * 创建数据库连接实例 + * @access protected + * @param string|null $name 连接标识 + * @param bool $force 强制重新连接 + * @return ConnectionInterface + */ + protected function instance(string $name = null, bool $force = false): ConnectionInterface + { + if (empty($name)) { + $name = $this->getConfig('default', 'mysql'); + } + + if ($force || !isset($this->instance[$name])) { + $this->instance[$name] = $this->createConnection($name); + } + + return $this->instance[$name]; + } + + /** + * 获取连接配置 + * @param string $name + * @return array + */ + protected function getConnectionConfig(string $name): array + { + $connections = $this->getConfig('connections'); + if (!isset($connections[$name])) { + throw new InvalidArgumentException('Undefined db config:' . $name); + } + + return $connections[$name]; + } + + /** + * 创建连接 + * @param $name + * @return ConnectionInterface + */ + protected function createConnection(string $name): ConnectionInterface + { + $config = $this->getConnectionConfig($name); + + $type = !empty($config['type']) ? $config['type'] : 'mysql'; + + if (false !== strpos($type, '\\')) { + $class = $type; + } else { + $class = '\\think\\db\\connector\\' . ucfirst($type); + } + + /** @var ConnectionInterface $connection */ + $connection = new $class($config); + $connection->setDb($this); + + if ($this->cache) { + $connection->setCache($this->cache); + } + + return $connection; + } + + /** + * 使用表达式设置数据 + * @access public + * @param string $value 表达式 + * @return Raw + */ + public function raw(string $value): Raw + { + return new Raw($value); + } + + /** + * 更新查询次数 + * @access public + * @return void + */ + public function updateQueryTimes(): void + { + $this->queryTimes++; + } + + /** + * 重置查询次数 + * @access public + * @return void + */ + public function clearQueryTimes(): void + { + $this->queryTimes = 0; + } + + /** + * 获得查询次数 + * @access public + * @return integer + */ + public function getQueryTimes(): int + { + return $this->queryTimes; + } + + /** + * 监听SQL执行 + * @access public + * @param callable $callback 回调方法 + * @return void + */ + public function listen(callable $callback): void + { + $this->listen[] = $callback; + } + + /** + * 获取监听SQL执行 + * @access public + * @return array + */ + public function getListen(): array + { + return $this->listen; + } + + /** + * 注册回调方法 + * @access public + * @param string $event 事件名 + * @param callable $callback 回调方法 + * @return void + */ + public function event(string $event, callable $callback): void + { + $this->event[$event][] = $callback; + } + + /** + * 触发事件 + * @access public + * @param string $event 事件名 + * @param mixed $params 传入参数 + * @return mixed + */ + public function trigger(string $event, $params = null) + { + if (isset($this->event[$event])) { + foreach ($this->event[$event] as $callback) { + call_user_func_array($callback, [$params]); + } + } + } + + public function __call($method, $args) + { + return call_user_func_array([$this->connect(), $method], $args); + } +} diff --git a/vendor/topthink/think-orm/src/Model.php b/vendor/topthink/think-orm/src/Model.php new file mode 100644 index 0000000..0345358 --- /dev/null +++ b/vendor/topthink/think-orm/src/Model.php @@ -0,0 +1,1068 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use ArrayAccess; +use Closure; +use JsonSerializable; +use think\contract\Arrayable; +use think\contract\Jsonable; +use think\db\BaseQuery as Query; + +/** + * Class Model + * @package think + * @mixin Query + * @method void onAfterRead(Model $model) static after_read事件定义 + * @method mixed onBeforeInsert(Model $model) static before_insert事件定义 + * @method void onAfterInsert(Model $model) static after_insert事件定义 + * @method mixed onBeforeUpdate(Model $model) static before_update事件定义 + * @method void onAfterUpdate(Model $model) static after_update事件定义 + * @method mixed onBeforeWrite(Model $model) static before_write事件定义 + * @method void onAfterWrite(Model $model) static after_write事件定义 + * @method mixed onBeforeDelete(Model $model) static before_write事件定义 + * @method void onAfterDelete(Model $model) static after_delete事件定义 + * @method void onBeforeRestore(Model $model) static before_restore事件定义 + * @method void onAfterRestore(Model $model) static after_restore事件定义 + */ +abstract class Model implements JsonSerializable, ArrayAccess, Arrayable, Jsonable +{ + use model\concern\Attribute; + use model\concern\RelationShip; + use model\concern\ModelEvent; + use model\concern\TimeStamp; + use model\concern\Conversion; + + /** + * 数据是否存在 + * @var bool + */ + private $exists = false; + + /** + * 是否强制更新所有数据 + * @var bool + */ + private $force = false; + + /** + * 是否Replace + * @var bool + */ + private $replace = false; + + /** + * 数据表后缀 + * @var string + */ + protected $suffix; + + /** + * 更新条件 + * @var array + */ + private $updateWhere; + + /** + * 数据库配置 + * @var string + */ + protected $connection; + + /** + * 模型名称 + * @var string + */ + protected $name; + + /** + * 主键值 + * @var string + */ + protected $key; + + /** + * 数据表名称 + * @var string + */ + protected $table; + + /** + * 初始化过的模型. + * @var array + */ + protected static $initialized = []; + + /** + * 软删除字段默认值 + * @var mixed + */ + protected $defaultSoftDelete; + + /** + * 全局查询范围 + * @var array + */ + protected $globalScope = []; + + /** + * 延迟保存信息 + * @var bool + */ + private $lazySave = false; + + /** + * Db对象 + * @var DbManager + */ + protected static $db; + + /** + * 容器对象的依赖注入方法 + * @var callable + */ + protected static $invoker; + + /** + * 服务注入 + * @var Closure[] + */ + protected static $maker = []; + + /** + * 方法注入 + * @var Closure[][] + */ + protected static $macro = []; + + /** + * 设置服务注入 + * @access public + * @param Closure $maker + * @return void + */ + public static function maker(Closure $maker) + { + static::$maker[] = $maker; + } + + /** + * 设置方法注入 + * @access public + * @param string $method + * @param Closure $closure + * @return void + */ + public static function macro(string $method, Closure $closure) + { + if (!isset(static::$macro[static::class])) { + static::$macro[static::class] = []; + } + static::$macro[static::class][$method] = $closure; + } + + /** + * 设置Db对象 + * @access public + * @param DbManager $db Db对象 + * @return void + */ + public static function setDb(DbManager $db) + { + self::$db = $db; + } + + /** + * 设置容器对象的依赖注入方法 + * @access public + * @param callable $callable 依赖注入方法 + * @return void + */ + public static function setInvoker(callable $callable): void + { + self::$invoker = $callable; + } + + /** + * 调用反射执行模型方法 支持参数绑定 + * @access public + * @param mixed $method + * @param array $vars 参数 + * @return mixed + */ + public function invoke($method, array $vars = []) + { + if (self::$invoker) { + $call = self::$invoker; + return $call($method instanceof Closure ? $method : Closure::fromCallable([$this, $method]), $vars); + } + + return call_user_func_array($method instanceof Closure ? $method : [$this, $method], $vars); + } + + /** + * 架构函数 + * @access public + * @param array $data 数据 + */ + public function __construct(array $data = []) + { + $this->data = $data; + + if (!empty($this->data)) { + // 废弃字段 + foreach ((array) $this->disuse as $key) { + if (array_key_exists($key, $this->data)) { + unset($this->data[$key]); + } + } + } + + // 记录原始数据 + $this->origin = $this->data; + + if (empty($this->name)) { + // 当前模型名 + $name = str_replace('\\', '/', static::class); + $this->name = basename($name); + } + + if (!empty(static::$maker)) { + foreach (static::$maker as $maker) { + call_user_func($maker, $this); + } + } + + // 执行初始化操作 + $this->initialize(); + } + + /** + * 获取当前模型名称 + * @access public + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * 创建新的模型实例 + * @access public + * @param array $data 数据 + * @param mixed $where 更新条件 + * @return Model + */ + public function newInstance(array $data = [], $where = null): Model + { + $model = new static($data); + + if ($this->connection) { + $model->setConnection($this->connection); + } + + if ($this->suffix) { + $model->setSuffix($this->suffix); + } + + if (empty($data)) { + return $model; + } + + $model->exists(true); + + $model->setUpdateWhere($where); + + $model->trigger('AfterRead'); + + return $model; + } + + /** + * 设置模型的更新条件 + * @access protected + * @param mixed $where 更新条件 + * @return void + */ + protected function setUpdateWhere($where): void + { + $this->updateWhere = $where; + } + + /** + * 设置当前模型的数据库连接 + * @access public + * @param string $connection 数据表连接标识 + * @return $this + */ + public function setConnection(string $connection) + { + $this->connection = $connection; + return $this; + } + + /** + * 获取当前模型的数据库连接标识 + * @access public + * @return string + */ + public function getConnection(): string + { + return $this->connection ?: ''; + } + + /** + * 设置当前模型数据表的后缀 + * @access public + * @param string $suffix 数据表后缀 + * @return $this + */ + public function setSuffix(string $suffix) + { + $this->suffix = $suffix; + return $this; + } + + /** + * 获取当前模型的数据表后缀 + * @access public + * @return string + */ + public function getSuffix(): string + { + return $this->suffix ?: ''; + } + + /** + * 获取当前模型的数据库查询对象 + * @access public + * @param array $scope 设置不使用的全局查询范围 + * @return Query + */ + public function db($scope = []): Query + { + /** @var Query $query */ + $query = self::$db->connect($this->connection) + ->name($this->name . $this->suffix) + ->pk($this->pk); + + if (!empty($this->table)) { + $query->table($this->table . $this->suffix); + } + + $query->model($this) + ->json($this->json, $this->jsonAssoc) + ->setFieldType(array_merge($this->schema, $this->jsonType)); + + // 软删除 + if (property_exists($this, 'withTrashed') && !$this->withTrashed) { + $this->withNoTrashed($query); + } + + // 全局作用域 + if (is_array($scope)) { + $globalScope = array_diff($this->globalScope, $scope); + $query->scope($globalScope); + } + + // 返回当前模型的数据库查询对象 + return $query; + } + + /** + * 初始化模型 + * @access private + * @return void + */ + private function initialize(): void + { + if (!isset(static::$initialized[static::class])) { + static::$initialized[static::class] = true; + static::init(); + } + } + + /** + * 初始化处理 + * @access protected + * @return void + */ + protected static function init() + { + } + + protected function checkData(): void + { + } + + protected function checkResult($result): void + { + } + + /** + * 更新是否强制写入数据 而不做比较(亦可用于软删除的强制删除) + * @access public + * @param bool $force + * @return $this + */ + public function force(bool $force = true) + { + $this->force = $force; + return $this; + } + + /** + * 判断force + * @access public + * @return bool + */ + public function isForce(): bool + { + return $this->force; + } + + /** + * 新增数据是否使用Replace + * @access public + * @param bool $replace + * @return $this + */ + public function replace(bool $replace = true) + { + $this->replace = $replace; + return $this; + } + + /** + * 刷新模型数据 + * @access public + * @param bool $relation 是否刷新关联数据 + * @return $this + */ + public function refresh(bool $relation = false) + { + if ($this->exists) { + $this->data = $this->db()->find($this->getKey())->getData(); + $this->origin = $this->data; + $this->get = []; + + if ($relation) { + $this->relation = []; + } + } + + return $this; + } + + /** + * 设置数据是否存在 + * @access public + * @param bool $exists + * @return $this + */ + public function exists(bool $exists = true) + { + $this->exists = $exists; + return $this; + } + + /** + * 判断数据是否存在数据库 + * @access public + * @return bool + */ + public function isExists(): bool + { + return $this->exists; + } + + /** + * 判断模型是否为空 + * @access public + * @return bool + */ + public function isEmpty(): bool + { + return empty($this->data); + } + + /** + * 延迟保存当前数据对象 + * @access public + * @param array|bool $data 数据 + * @return void + */ + public function lazySave($data = []): void + { + if (false === $data) { + $this->lazySave = false; + } else { + if (is_array($data)) { + $this->setAttrs($data); + } + + $this->lazySave = true; + } + } + + /** + * 保存当前数据对象 + * @access public + * @param array $data 数据 + * @param string $sequence 自增序列名 + * @return bool + */ + public function save(array $data = [], string $sequence = null): bool + { + // 数据对象赋值 + $this->setAttrs($data); + + if ($this->isEmpty() || false === $this->trigger('BeforeWrite')) { + return false; + } + + $result = $this->exists ? $this->updateData() : $this->insertData($sequence); + + if (false === $result) { + return false; + } + + // 写入回调 + $this->trigger('AfterWrite'); + + // 重新记录原始数据 + $this->origin = $this->data; + $this->get = []; + $this->lazySave = false; + + return true; + } + + /** + * 检查数据是否允许写入 + * @access protected + * @return array + */ + protected function checkAllowFields(): array + { + // 检测字段 + if (empty($this->field)) { + if (!empty($this->schema)) { + $this->field = array_keys(array_merge($this->schema, $this->jsonType)); + } else { + $query = $this->db(); + $table = $this->table ? $this->table . $this->suffix : $query->getTable(); + + $this->field = $query->getConnection()->getTableFields($table); + } + + return $this->field; + } + + $field = $this->field; + + if ($this->autoWriteTimestamp) { + array_push($field, $this->createTime, $this->updateTime); + } + + if (!empty($this->disuse)) { + // 废弃字段 + $field = array_diff($field, $this->disuse); + } + + return $field; + } + + /** + * 保存写入数据 + * @access protected + * @return bool + */ + protected function updateData(): bool + { + // 事件回调 + if (false === $this->trigger('BeforeUpdate')) { + return false; + } + + $this->checkData(); + + // 获取有更新的数据 + $data = $this->getChangedData(); + + if (empty($data)) { + // 关联更新 + if (!empty($this->relationWrite)) { + $this->autoRelationUpdate(); + } + + return true; + } + + if ($this->autoWriteTimestamp && $this->updateTime) { + // 自动写入更新时间 + $data[$this->updateTime] = $this->autoWriteTimestamp(); + $this->data[$this->updateTime] = $data[$this->updateTime]; + } + + // 检查允许字段 + $allowFields = $this->checkAllowFields(); + + foreach ($this->relationWrite as $name => $val) { + if (!is_array($val)) { + continue; + } + + foreach ($val as $key) { + if (isset($data[$key])) { + unset($data[$key]); + } + } + } + + // 模型更新 + $db = $this->db(); + + $db->transaction(function () use ($data, $allowFields, $db) { + $this->key = null; + $where = $this->getWhere(); + + $result = $db->where($where) + ->strict(false) + ->cache(true) + ->setOption('key', $this->key) + ->field($allowFields) + ->update($data); + + $this->checkResult($result); + + // 关联更新 + if (!empty($this->relationWrite)) { + $this->autoRelationUpdate(); + } + }); + + // 更新回调 + $this->trigger('AfterUpdate'); + + return true; + } + + /** + * 新增写入数据 + * @access protected + * @param string $sequence 自增名 + * @return bool + */ + protected function insertData(string $sequence = null): bool + { + if (false === $this->trigger('BeforeInsert')) { + return false; + } + + $this->checkData(); + $data = $this->data; + + // 时间戳自动写入 + if ($this->autoWriteTimestamp) { + if ($this->createTime && !isset($data[$this->createTime])) { + $data[$this->createTime] = $this->autoWriteTimestamp(); + $this->data[$this->createTime] = $data[$this->createTime]; + } + + if ($this->updateTime && !isset($data[$this->updateTime])) { + $data[$this->updateTime] = $this->autoWriteTimestamp(); + $this->data[$this->updateTime] = $data[$this->updateTime]; + } + } + + // 检查允许字段 + $allowFields = $this->checkAllowFields(); + + $db = $this->db(); + + $db->transaction(function () use ($data, $sequence, $allowFields, $db) { + $result = $db->strict(false) + ->field($allowFields) + ->replace($this->replace) + ->sequence($sequence) + ->insert($data, true); + + // 获取自动增长主键 + if ($result) { + $pk = $this->getPk(); + + if (is_string($pk) && (!isset($this->data[$pk]) || '' == $this->data[$pk])) { + unset($this->get[$pk]); + $this->data[$pk] = $result; + } + } + + // 关联写入 + if (!empty($this->relationWrite)) { + $this->autoRelationInsert(); + } + }); + + // 标记数据已经存在 + $this->exists = true; + $this->origin = $this->data; + + // 新增回调 + $this->trigger('AfterInsert'); + + return true; + } + + /** + * 获取当前的更新条件 + * @access public + * @return mixed + */ + public function getWhere() + { + $pk = $this->getPk(); + + if (is_string($pk) && isset($this->origin[$pk])) { + $where = [[$pk, '=', $this->origin[$pk]]]; + $this->key = $this->origin[$pk]; + } elseif (is_array($pk)) { + foreach ($pk as $field) { + if (isset($this->origin[$field])) { + $where[] = [$field, '=', $this->origin[$field]]; + } + } + } + + if (empty($where)) { + $where = empty($this->updateWhere) ? null : $this->updateWhere; + } + + return $where; + } + + /** + * 保存多个数据到当前数据对象 + * @access public + * @param iterable $dataSet 数据 + * @param boolean $replace 是否自动识别更新和写入 + * @return Collection + * @throws \Exception + */ + public function saveAll(iterable $dataSet, bool $replace = true): Collection + { + $db = $this->db(); + + $result = $db->transaction(function () use ($replace, $dataSet) { + + $pk = $this->getPk(); + + if (is_string($pk) && $replace) { + $auto = true; + } + + $result = []; + + $suffix = $this->getSuffix(); + + foreach ($dataSet as $key => $data) { + if ($this->exists || (!empty($auto) && isset($data[$pk]))) { + $result[$key] = static::update($data, [], [], $suffix); + } else { + $result[$key] = static::create($data, $this->field, $this->replace, $suffix); + } + } + + return $result; + }); + + return $this->toCollection($result); + } + + /** + * 删除当前的记录 + * @access public + * @return bool + */ + public function delete(): bool + { + if (!$this->exists || $this->isEmpty() || false === $this->trigger('BeforeDelete')) { + return false; + } + + // 读取更新条件 + $where = $this->getWhere(); + + $db = $this->db(); + + $db->transaction(function () use ($where, $db) { + // 删除当前模型数据 + $db->where($where)->delete(); + + // 关联删除 + if (!empty($this->relationWrite)) { + $this->autoRelationDelete(); + } + }); + + $this->trigger('AfterDelete'); + + $this->exists = false; + $this->lazySave = false; + + return true; + } + + /** + * 写入数据 + * @access public + * @param array $data 数据数组 + * @param array $allowField 允许字段 + * @param bool $replace 使用Replace + * @param string $suffix 数据表后缀 + * @return static + */ + public static function create(array $data, array $allowField = [], bool $replace = false, string $suffix = ''): Model + { + $model = new static(); + + if (!empty($allowField)) { + $model->allowField($allowField); + } + + if (!empty($suffix)) { + $model->setSuffix($suffix); + } + + $model->replace($replace)->save($data); + + return $model; + } + + /** + * 更新数据 + * @access public + * @param array $data 数据数组 + * @param mixed $where 更新条件 + * @param array $allowField 允许字段 + * @param string $suffix 数据表后缀 + * @return static + */ + public static function update(array $data, $where = [], array $allowField = [], string $suffix = '') + { + $model = new static(); + + if (!empty($allowField)) { + $model->allowField($allowField); + } + + if (!empty($where)) { + $model->setUpdateWhere($where); + } + + if (!empty($suffix)) { + $model->setSuffix($suffix); + } + + $model->exists(true)->save($data); + + return $model; + } + + /** + * 删除记录 + * @access public + * @param mixed $data 主键列表 支持闭包查询条件 + * @param bool $force 是否强制删除 + * @return bool + */ + public static function destroy($data, bool $force = false): bool + { + if (empty($data) && 0 !== $data) { + return false; + } + + $model = new static(); + + $query = $model->db(); + + if (is_array($data) && key($data) !== 0) { + $query->where($data); + $data = null; + } elseif ($data instanceof \Closure) { + $data($query); + $data = null; + } + + $resultSet = $query->select($data); + + foreach ($resultSet as $result) { + $result->force($force)->delete(); + } + + return true; + } + + /** + * 解序列化后处理 + */ + public function __wakeup() + { + $this->initialize(); + } + + /** + * 修改器 设置数据对象的值 + * @access public + * @param string $name 名称 + * @param mixed $value 值 + * @return void + */ + public function __set(string $name, $value): void + { + $this->setAttr($name, $value); + } + + /** + * 获取器 获取数据对象的值 + * @access public + * @param string $name 名称 + * @return mixed + */ + public function __get(string $name) + { + return $this->getAttr($name); + } + + /** + * 检测数据对象的值 + * @access public + * @param string $name 名称 + * @return bool + */ + public function __isset(string $name): bool + { + return !is_null($this->getAttr($name)); + } + + /** + * 销毁数据对象的值 + * @access public + * @param string $name 名称 + * @return void + */ + public function __unset(string $name): void + { + unset($this->data[$name], + $this->get[$name], + $this->relation[$name]); + } + + // ArrayAccess + public function offsetSet($name, $value) + { + $this->setAttr($name, $value); + } + + public function offsetExists($name): bool + { + return $this->__isset($name); + } + + public function offsetUnset($name) + { + $this->__unset($name); + } + + public function offsetGet($name) + { + return $this->getAttr($name); + } + + /** + * 设置不使用的全局查询范围 + * @access public + * @param array $scope 不启用的全局查询范围 + * @return Query + */ + public static function withoutGlobalScope(array $scope = null) + { + $model = new static(); + + return $model->db($scope); + } + + /** + * 切换后缀进行查询 + * @access public + * @param string $suffix 切换的表后缀 + * @return Model + */ + public static function suffix(string $suffix) + { + $model = new static(); + $model->setSuffix($suffix); + + return $model; + } + + /** + * 切换数据库连接进行查询 + * @access public + * @param string $connection 数据库连接标识 + * @return Model + */ + public static function connect(string $connection) + { + $model = new static(); + $model->setConnection($connection); + + return $model; + } + + public function __call($method, $args) + { + if (isset(static::$macro[static::class][$method])) { + return call_user_func_array(static::$macro[static::class][$method]->bindTo($this, static::class), $args); + } + + if ('withattr' == strtolower($method)) { + return call_user_func_array([$this, 'withAttribute'], $args); + } + + return call_user_func_array([$this->db(), $method], $args); + } + + public static function __callStatic($method, $args) + { + if (isset(static::$macro[static::class][$method])) { + return call_user_func_array(static::$macro[static::class][$method]->bindTo(null, static::class), $args); + } + + $model = new static(); + + return call_user_func_array([$model->db(), $method], $args); + } + + /** + * 析构方法 + * @access public + */ + public function __destruct() + { + if ($this->lazySave) { + $this->save(); + } + } +} diff --git a/vendor/topthink/think-orm/src/Paginator.php b/vendor/topthink/think-orm/src/Paginator.php new file mode 100644 index 0000000..d8d43d7 --- /dev/null +++ b/vendor/topthink/think-orm/src/Paginator.php @@ -0,0 +1,518 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use ArrayAccess; +use ArrayIterator; +use Closure; +use Countable; +use DomainException; +use IteratorAggregate; +use JsonSerializable; +use think\paginator\driver\Bootstrap; +use Traversable; + +/** + * 分页基础类 + * @mixin Collection + */ +abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable +{ + /** + * 是否简洁模式 + * @var bool + */ + protected $simple = false; + + /** + * 数据集 + * @var Collection + */ + protected $items; + + /** + * 当前页 + * @var int + */ + protected $currentPage; + + /** + * 最后一页 + * @var int + */ + protected $lastPage; + + /** + * 数据总数 + * @var integer|null + */ + protected $total; + + /** + * 每页数量 + * @var int + */ + protected $listRows; + + /** + * 是否有下一页 + * @var bool + */ + protected $hasMore; + + /** + * 分页配置 + * @var array + */ + protected $options = [ + 'var_page' => 'page', + 'path' => '/', + 'query' => [], + 'fragment' => '', + ]; + + /** + * 获取当前页码 + * @var Closure + */ + protected static $currentPageResolver; + + /** + * 获取当前路径 + * @var Closure + */ + protected static $currentPathResolver; + + /** + * @var Closure + */ + protected static $maker; + + public function __construct($items, int $listRows, int $currentPage = 1, int $total = null, bool $simple = false, array $options = []) + { + $this->options = array_merge($this->options, $options); + + $this->options['path'] = '/' != $this->options['path'] ? rtrim($this->options['path'], '/') : $this->options['path']; + + $this->simple = $simple; + $this->listRows = $listRows; + + if (!$items instanceof Collection) { + $items = Collection::make($items); + } + + if ($simple) { + $this->currentPage = $this->setCurrentPage($currentPage); + $this->hasMore = count($items) > ($this->listRows); + $items = $items->slice(0, $this->listRows); + } else { + $this->total = $total; + $this->lastPage = (int) ceil($total / $listRows); + $this->currentPage = $this->setCurrentPage($currentPage); + $this->hasMore = $this->currentPage < $this->lastPage; + } + $this->items = $items; + } + + /** + * @access public + * @param mixed $items + * @param int $listRows + * @param int $currentPage + * @param int $total + * @param bool $simple + * @param array $options + * @return Paginator + */ + public static function make($items, int $listRows, int $currentPage = 1, int $total = null, bool $simple = false, array $options = []) + { + if (isset(static::$maker)) { + return call_user_func(static::$maker, $items, $listRows, $currentPage, $total, $simple, $options); + } + + return new Bootstrap($items, $listRows, $currentPage, $total, $simple, $options); + } + + public static function maker(Closure $resolver) + { + static::$maker = $resolver; + } + + protected function setCurrentPage(int $currentPage): int + { + if (!$this->simple && $currentPage > $this->lastPage) { + return $this->lastPage > 0 ? $this->lastPage : 1; + } + + return $currentPage; + } + + /** + * 获取页码对应的链接 + * + * @access protected + * @param int $page + * @return string + */ + protected function url(int $page): string + { + if ($page <= 0) { + $page = 1; + } + + if (strpos($this->options['path'], '[PAGE]') === false) { + $parameters = [$this->options['var_page'] => $page]; + $path = $this->options['path']; + } else { + $parameters = []; + $path = str_replace('[PAGE]', (string) $page, $this->options['path']); + } + + if (count($this->options['query']) > 0) { + $parameters = array_merge($this->options['query'], $parameters); + } + + $url = $path; + if (!empty($parameters)) { + $url .= '?' . http_build_query($parameters, '', '&'); + } + + return $url . $this->buildFragment(); + } + + /** + * 自动获取当前页码 + * @access public + * @param string $varPage + * @param int $default + * @return int + */ + public static function getCurrentPage(string $varPage = 'page', int $default = 1): int + { + if (isset(static::$currentPageResolver)) { + return call_user_func(static::$currentPageResolver, $varPage); + } + + return $default; + } + + /** + * 设置获取当前页码闭包 + * @param Closure $resolver + */ + public static function currentPageResolver(Closure $resolver) + { + static::$currentPageResolver = $resolver; + } + + /** + * 自动获取当前的path + * @access public + * @param string $default + * @return string + */ + public static function getCurrentPath($default = '/'): string + { + if (isset(static::$currentPathResolver)) { + return call_user_func(static::$currentPathResolver); + } + + return $default; + } + + /** + * 设置获取当前路径闭包 + * @param Closure $resolver + */ + public static function currentPathResolver(Closure $resolver) + { + static::$currentPathResolver = $resolver; + } + + /** + * 获取数据总条数 + * @return int + */ + public function total(): int + { + if ($this->simple) { + throw new DomainException('not support total'); + } + + return $this->total; + } + + /** + * 获取每页数量 + * @return int + */ + public function listRows(): int + { + return $this->listRows; + } + + /** + * 获取当前页页码 + * @return int + */ + public function currentPage(): int + { + return $this->currentPage; + } + + /** + * 获取最后一页页码 + * @return int + */ + public function lastPage(): int + { + if ($this->simple) { + throw new DomainException('not support last'); + } + + return $this->lastPage; + } + + /** + * 数据是否足够分页 + * @access public + * @return bool + */ + public function hasPages(): bool + { + return !(1 == $this->currentPage && !$this->hasMore); + } + + /** + * 创建一组分页链接 + * + * @access public + * @param int $start + * @param int $end + * @return array + */ + public function getUrlRange(int $start, int $end): array + { + $urls = []; + + for ($page = $start; $page <= $end; $page++) { + $urls[$page] = $this->url($page); + } + + return $urls; + } + + /** + * 设置URL锚点 + * + * @access public + * @param string|null $fragment + * @return $this + */ + public function fragment(string $fragment = null) + { + $this->options['fragment'] = $fragment; + + return $this; + } + + /** + * 添加URL参数 + * + * @access public + * @param array $append + * @return $this + */ + public function appends(array $append) + { + foreach ($append as $k => $v) { + if ($k !== $this->options['var_page']) { + $this->options['query'][$k] = $v; + } + } + + return $this; + } + + /** + * 构造锚点字符串 + * + * @access public + * @return string + */ + protected function buildFragment(): string + { + return $this->options['fragment'] ? '#' . $this->options['fragment'] : ''; + } + + /** + * 渲染分页html + * @access public + * @return mixed + */ + abstract public function render(); + + public function items() + { + return $this->items->all(); + } + + /** + * 获取数据集 + * + * @return Collection|\think\model\Collection + */ + public function getCollection() + { + return $this->items; + } + + public function isEmpty(): bool + { + return $this->items->isEmpty(); + } + + /** + * 给每个元素执行个回调 + * + * @access public + * @param callable $callback + * @return $this + */ + public function each(callable $callback) + { + foreach ($this->items as $key => $item) { + $result = $callback($item, $key); + + if (false === $result) { + break; + } elseif (!is_object($item)) { + $this->items[$key] = $result; + } + } + + return $this; + } + + /** + * Retrieve an external iterator + * @access public + * @return Traversable An instance of an object implementing Iterator or + * Traversable + */ + public function getIterator() + { + return new ArrayIterator($this->items->all()); + } + + /** + * Whether a offset exists + * @access public + * @param mixed $offset + * @return bool + */ + public function offsetExists($offset) + { + return $this->items->offsetExists($offset); + } + + /** + * Offset to retrieve + * @access public + * @param mixed $offset + * @return mixed + */ + public function offsetGet($offset) + { + return $this->items->offsetGet($offset); + } + + /** + * Offset to set + * @access public + * @param mixed $offset + * @param mixed $value + */ + public function offsetSet($offset, $value) + { + $this->items->offsetSet($offset, $value); + } + + /** + * Offset to unset + * @access public + * @param mixed $offset + * @return void + * @since 5.0.0 + */ + public function offsetUnset($offset) + { + $this->items->offsetUnset($offset); + } + + /** + * 统计数据集条数 + * @return int + */ + public function count(): int + { + return $this->items->count(); + } + + public function __toString() + { + return (string) $this->render(); + } + + /** + * 转换为数组 + * @return array + */ + public function toArray(): array + { + try { + $total = $this->total(); + } catch (DomainException $e) { + $total = null; + } + + return [ + 'total' => $total, + 'per_page' => $this->listRows(), + 'current_page' => $this->currentPage(), + 'last_page' => $this->lastPage, + 'data' => $this->items->toArray(), + ]; + } + + /** + * Specify data which should be serialized to JSON + */ + public function jsonSerialize() + { + return $this->toArray(); + } + + public function __call($name, $arguments) + { + $result = call_user_func_array([$this->items, $name], $arguments); + + if ($result instanceof Collection) { + $this->items = $result; + return $this; + } + + return $result; + } + +} diff --git a/vendor/topthink/think-orm/src/db/BaseQuery.php b/vendor/topthink/think-orm/src/db/BaseQuery.php new file mode 100644 index 0000000..4bd4bfb --- /dev/null +++ b/vendor/topthink/think-orm/src/db/BaseQuery.php @@ -0,0 +1,1293 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db; + +use think\Collection; +use think\db\exception\DataNotFoundException; +use think\db\exception\DbException as Exception; +use think\db\exception\ModelNotFoundException; +use think\helper\Str; +use think\Model; +use think\Paginator; + +/** + * 数据查询基础类 + */ +abstract class BaseQuery +{ + use concern\TimeFieldQuery; + use concern\AggregateQuery; + use concern\ModelRelationQuery; + use concern\ResultOperation; + use concern\Transaction; + use concern\WhereQuery; + + /** + * 当前数据库连接对象 + * @var Connection + */ + protected $connection; + + /** + * 当前数据表名称(不含前缀) + * @var string + */ + protected $name = ''; + + /** + * 当前数据表主键 + * @var string|array + */ + protected $pk; + + /** + * 当前数据表自增主键 + * @var string + */ + protected $autoinc; + + /** + * 当前数据表前缀 + * @var string + */ + protected $prefix = ''; + + /** + * 当前查询参数 + * @var array + */ + protected $options = []; + + /** + * 架构函数 + * @access public + * @param ConnectionInterface $connection 数据库连接对象 + */ + public function __construct(ConnectionInterface $connection) + { + $this->connection = $connection; + + $this->prefix = $this->connection->getConfig('prefix'); + } + + /** + * 利用__call方法实现一些特殊的Model方法 + * @access public + * @param string $method 方法名称 + * @param array $args 调用参数 + * @return mixed + * @throws Exception + */ + public function __call(string $method, array $args) + { + if (strtolower(substr($method, 0, 5)) == 'getby') { + // 根据某个字段获取记录 + $field = Str::snake(substr($method, 5)); + return $this->where($field, '=', $args[0])->find(); + } elseif (strtolower(substr($method, 0, 10)) == 'getfieldby') { + // 根据某个字段获取记录的某个值 + $name = Str::snake(substr($method, 10)); + return $this->where($name, '=', $args[0])->value($args[1]); + } elseif (strtolower(substr($method, 0, 7)) == 'whereor') { + $name = Str::snake(substr($method, 7)); + array_unshift($args, $name); + return call_user_func_array([$this, 'whereOr'], $args); + } elseif (strtolower(substr($method, 0, 5)) == 'where') { + $name = Str::snake(substr($method, 5)); + array_unshift($args, $name); + return call_user_func_array([$this, 'where'], $args); + } elseif ($this->model && method_exists($this->model, 'scope' . $method)) { + // 动态调用命名范围 + $method = 'scope' . $method; + array_unshift($args, $this); + + call_user_func_array([$this->model, $method], $args); + return $this; + } else { + throw new Exception('method not exist:' . static::class . '->' . $method); + } + } + + /** + * 创建一个新的查询对象 + * @access public + * @return BaseQuery + */ + public function newQuery(): BaseQuery + { + $query = new static($this->connection); + + if ($this->model) { + $query->model($this->model); + } + + if (isset($this->options['table'])) { + $query->table($this->options['table']); + } else { + $query->name($this->name); + } + + if (isset($this->options['json'])) { + $query->json($this->options['json'], $this->options['json_assoc']); + } + + if (isset($this->options['field_type'])) { + $query->setFieldType($this->options['field_type']); + } + + return $query; + } + + /** + * 获取当前的数据库Connection对象 + * @access public + * @return ConnectionInterface + */ + public function getConnection() + { + return $this->connection; + } + + /** + * 指定当前数据表名(不含前缀) + * @access public + * @param string $name 不含前缀的数据表名字 + * @return $this + */ + public function name(string $name) + { + $this->name = $name; + return $this; + } + + /** + * 获取当前的数据表名称 + * @access public + * @return string + */ + public function getName(): string + { + return $this->name ?: $this->model->getName(); + } + + /** + * 获取数据库的配置参数 + * @access public + * @param string $name 参数名称 + * @return mixed + */ + public function getConfig(string $name = '') + { + return $this->connection->getConfig($name); + } + + /** + * 得到当前或者指定名称的数据表 + * @access public + * @param string $name 不含前缀的数据表名字 + * @return mixed + */ + public function getTable(string $name = '') + { + if (empty($name) && isset($this->options['table'])) { + return $this->options['table']; + } + + $name = $name ?: $this->name; + + return $this->prefix . Str::snake($name); + } + + /** + * 设置字段类型信息 + * @access public + * @param array $type 字段类型信息 + * @return $this + */ + public function setFieldType(array $type) + { + $this->options['field_type'] = $type; + return $this; + } + + /** + * 获取最近一次查询的sql语句 + * @access public + * @return string + */ + public function getLastSql(): string + { + return $this->connection->getLastSql(); + } + + /** + * 获取返回或者影响的记录数 + * @access public + * @return integer + */ + public function getNumRows(): int + { + return $this->connection->getNumRows(); + } + + /** + * 获取最近插入的ID + * @access public + * @param string $sequence 自增序列名 + * @return mixed + */ + public function getLastInsID(string $sequence = null) + { + return $this->connection->getLastInsID($this, $sequence); + } + + /** + * 得到某个字段的值 + * @access public + * @param string $field 字段名 + * @param mixed $default 默认值 + * @return mixed + */ + public function value(string $field, $default = null) + { + $result = $this->connection->value($this, $field, $default); + + $array[$field] = $result; + $this->result($array); + + return $array[$field]; + } + + /** + * 得到某个列的数组 + * @access public + * @param string|array $field 字段名 多个字段用逗号分隔 + * @param string $key 索引 + * @return array + */ + public function column($field, string $key = ''): array + { + $result = $this->connection->column($this, $field, $key); + $this->resultSet($result, false); + return $result; + } + + /** + * 查询SQL组装 union + * @access public + * @param mixed $union UNION + * @param boolean $all 是否适用UNION ALL + * @return $this + */ + public function union($union, bool $all = false) + { + $this->options['union']['type'] = $all ? 'UNION ALL' : 'UNION'; + + if (is_array($union)) { + $this->options['union'] = array_merge($this->options['union'], $union); + } else { + $this->options['union'][] = $union; + } + + return $this; + } + + /** + * 查询SQL组装 union all + * @access public + * @param mixed $union UNION数据 + * @return $this + */ + public function unionAll($union) + { + return $this->union($union, true); + } + + /** + * 指定查询字段 + * @access public + * @param mixed $field 字段信息 + * @return $this + */ + public function field($field) + { + if (empty($field)) { + return $this; + } elseif ($field instanceof Raw) { + $this->options['field'][] = $field; + return $this; + } + + if (is_string($field)) { + if (preg_match('/[\<\'\"\(]/', $field)) { + return $this->fieldRaw($field); + } + + $field = array_map('trim', explode(',', $field)); + } + + if (true === $field) { + // 获取全部字段 + $fields = $this->getTableFields(); + $field = $fields ?: ['*']; + } + + if (isset($this->options['field'])) { + $field = array_merge((array) $this->options['field'], $field); + } + + $this->options['field'] = array_unique($field); + + return $this; + } + + /** + * 指定要排除的查询字段 + * @access public + * @param array|string $field 要排除的字段 + * @return $this + */ + public function withoutField($field) + { + if (empty($field)) { + return $this; + } + + if (is_string($field)) { + $field = array_map('trim', explode(',', $field)); + } + + // 字段排除 + $fields = $this->getTableFields(); + $field = $fields ? array_diff($fields, $field) : $field; + + if (isset($this->options['field'])) { + $field = array_merge((array) $this->options['field'], $field); + } + + $this->options['field'] = array_unique($field); + + return $this; + } + + /** + * 指定其它数据表的查询字段 + * @access public + * @param mixed $field 字段信息 + * @param string $tableName 数据表名 + * @param string $prefix 字段前缀 + * @param string $alias 别名前缀 + * @return $this + */ + public function tableField($field, string $tableName, string $prefix = '', string $alias = '') + { + if (empty($field)) { + return $this; + } + + if (is_string($field)) { + $field = array_map('trim', explode(',', $field)); + } + + if (true === $field) { + // 获取全部字段 + $fields = $this->getTableFields($tableName); + $field = $fields ?: ['*']; + } + + // 添加统一的前缀 + $prefix = $prefix ?: $tableName; + foreach ($field as $key => &$val) { + if (is_numeric($key) && $alias) { + $field[$prefix . '.' . $val] = $alias . $val; + unset($field[$key]); + } elseif (is_numeric($key)) { + $val = $prefix . '.' . $val; + } + } + + if (isset($this->options['field'])) { + $field = array_merge((array) $this->options['field'], $field); + } + + $this->options['field'] = array_unique($field); + + return $this; + } + + /** + * 设置数据 + * @access public + * @param array $data 数据 + * @return $this + */ + public function data(array $data) + { + $this->options['data'] = $data; + + return $this; + } + + /** + * 去除查询参数 + * @access public + * @param string $option 参数名 留空去除所有参数 + * @return $this + */ + public function removeOption(string $option = '') + { + if ('' === $option) { + $this->options = []; + $this->bind = []; + } elseif (isset($this->options[$option])) { + unset($this->options[$option]); + } + + return $this; + } + + /** + * 指定查询数量 + * @access public + * @param int $offset 起始位置 + * @param int $length 查询数量 + * @return $this + */ + public function limit(int $offset, int $length = null) + { + $this->options['limit'] = $offset . ($length ? ',' . $length : ''); + + return $this; + } + + /** + * 指定分页 + * @access public + * @param int $page 页数 + * @param int $listRows 每页数量 + * @return $this + */ + public function page(int $page, int $listRows = null) + { + $this->options['page'] = [$page, $listRows]; + + return $this; + } + + /** + * 指定当前操作的数据表 + * @access public + * @param mixed $table 表名 + * @return $this + */ + public function table($table) + { + if (is_string($table)) { + if (strpos($table, ')')) { + // 子查询 + } elseif (false === strpos($table, ',')) { + if (strpos($table, ' ')) { + [$item, $alias] = explode(' ', $table); + $table = []; + $this->alias([$item => $alias]); + $table[$item] = $alias; + } + } else { + $tables = explode(',', $table); + $table = []; + + foreach ($tables as $item) { + $item = trim($item); + if (strpos($item, ' ')) { + [$item, $alias] = explode(' ', $item); + $this->alias([$item => $alias]); + $table[$item] = $alias; + } else { + $table[] = $item; + } + } + } + } elseif (is_array($table)) { + $tables = $table; + $table = []; + + foreach ($tables as $key => $val) { + if (is_numeric($key)) { + $table[] = $val; + } else { + $this->alias([$key => $val]); + $table[$key] = $val; + } + } + } + + $this->options['table'] = $table; + + return $this; + } + + /** + * 指定排序 order('id','desc') 或者 order(['id'=>'desc','create_time'=>'desc']) + * @access public + * @param string|array|Raw $field 排序字段 + * @param string $order 排序 + * @return $this + */ + public function order($field, string $order = '') + { + if (empty($field)) { + return $this; + } elseif ($field instanceof Raw) { + $this->options['order'][] = $field; + return $this; + } + + if (is_string($field)) { + if (!empty($this->options['via'])) { + $field = $this->options['via'] . '.' . $field; + } + if (strpos($field, ',')) { + $field = array_map('trim', explode(',', $field)); + } else { + $field = empty($order) ? $field : [$field => $order]; + } + } elseif (!empty($this->options['via'])) { + foreach ($field as $key => $val) { + if (is_numeric($key)) { + $field[$key] = $this->options['via'] . '.' . $val; + } else { + $field[$this->options['via'] . '.' . $key] = $val; + unset($field[$key]); + } + } + } + + if (!isset($this->options['order'])) { + $this->options['order'] = []; + } + + if (is_array($field)) { + $this->options['order'] = array_merge($this->options['order'], $field); + } else { + $this->options['order'][] = $field; + } + + return $this; + } + + /** + * 分页查询 + * @access public + * @param int|array $listRows 每页数量 数组表示配置参数 + * @param int|bool $simple 是否简洁模式或者总记录数 + * @return Paginator + * @throws Exception + */ + public function paginate($listRows = null, $simple = false): Paginator + { + if (is_int($simple)) { + $total = $simple; + $simple = false; + } + + $defaultConfig = [ + 'query' => [], //url额外参数 + 'fragment' => '', //url锚点 + 'var_page' => 'page', //分页变量 + 'list_rows' => 15, //每页数量 + ]; + + if (is_array($listRows)) { + $config = array_merge($defaultConfig, $listRows); + $listRows = intval($config['list_rows']); + } else { + $config = $defaultConfig; + $listRows = intval($listRows ?: $config['list_rows']); + } + + $page = isset($config['page']) ? (int) $config['page'] : Paginator::getCurrentPage($config['var_page']); + + $page = $page < 1 ? 1 : $page; + + $config['path'] = $config['path'] ?? Paginator::getCurrentPath(); + + if (!isset($total) && !$simple) { + $options = $this->getOptions(); + + unset($this->options['order'], $this->options['limit'], $this->options['page'], $this->options['field']); + + $bind = $this->bind; + $total = $this->count(); + if ($total > 0) { + $results = $this->options($options)->bind($bind)->page($page, $listRows)->select(); + } else { + if (!empty($this->model)) { + $results = new \think\model\Collection([]); + } else { + $results = new \think\Collection([]); + } + } + } elseif ($simple) { + $results = $this->limit(($page - 1) * $listRows, $listRows + 1)->select(); + $total = null; + } else { + $results = $this->page($page, $listRows)->select(); + } + + $this->removeOption('limit'); + $this->removeOption('page'); + + return Paginator::make($results, $listRows, $page, $total, $simple, $config); + } + + /** + * 根据数字类型字段进行分页查询(大数据) + * @access public + * @param int|array $listRows 每页数量或者分页配置 + * @param string $key 分页索引键 + * @param string $sort 索引键排序 asc|desc + * @return Paginator + * @throws Exception + */ + public function paginateX($listRows = null, string $key = null, string $sort = null): Paginator + { + $defaultConfig = [ + 'query' => [], //url额外参数 + 'fragment' => '', //url锚点 + 'var_page' => 'page', //分页变量 + 'list_rows' => 15, //每页数量 + ]; + + $config = is_array($listRows) ? array_merge($defaultConfig, $listRows) : $defaultConfig; + $listRows = is_int($listRows) ? $listRows : (int) $config['list_rows']; + $page = isset($config['page']) ? (int) $config['page'] : Paginator::getCurrentPage($config['var_page']); + $page = $page < 1 ? 1 : $page; + + $config['path'] = $config['path'] ?? Paginator::getCurrentPath(); + + $key = $key ?: $this->getPk(); + $options = $this->getOptions(); + + if (is_null($sort)) { + $order = $options['order'] ?? ''; + if (!empty($order)) { + $sort = $order[$key] ?? 'desc'; + } else { + $this->order($key, 'desc'); + $sort = 'desc'; + } + } else { + $this->order($key, $sort); + } + + $newOption = $options; + unset($newOption['field'], $newOption['page']); + + $data = $this->newQuery() + ->options($newOption) + ->field($key) + ->where(true) + ->order($key, $sort) + ->limit(1) + ->find(); + + $result = $data[$key]; + + if (is_numeric($result)) { + $lastId = 'asc' == $sort ? ($result - 1) + ($page - 1) * $listRows : ($result + 1) - ($page - 1) * $listRows; + } else { + throw new Exception('not support type'); + } + + $results = $this->when($lastId, function ($query) use ($key, $sort, $lastId) { + $query->where($key, 'asc' == $sort ? '>' : '<', $lastId); + }) + ->limit($listRows) + ->select(); + + $this->options($options); + + return Paginator::make($results, $listRows, $page, null, true, $config); + } + + /** + * 根据最后ID查询更多N个数据 + * @access public + * @param int $limit LIMIT + * @param int|string $lastId LastId + * @param string $key 分页索引键 默认为主键 + * @param string $sort 索引键排序 asc|desc + * @return array + * @throws Exception + */ + public function more(int $limit, $lastId = null, string $key = null, string $sort = null): array + { + $key = $key ?: $this->getPk(); + + if (is_null($sort)) { + $order = $this->getOptions('order'); + if (!empty($order)) { + $sort = $order[$key] ?? 'desc'; + } else { + $this->order($key, 'desc'); + $sort = 'desc'; + } + } else { + $this->order($key, $sort); + } + + $result = $this->when($lastId, function ($query) use ($key, $sort, $lastId) { + $query->where($key, 'asc' == $sort ? '>' : '<', $lastId); + })->limit($limit)->select(); + + $last = $result->last(); + + $result->first(); + + return [ + 'data' => $result, + 'lastId' => $last ? $last[$key] : null, + ]; + } + + /** + * 查询缓存 + * @access public + * @param mixed $key 缓存key + * @param integer|\DateTime $expire 缓存有效期 + * @param string|array $tag 缓存标签 + * @return $this + */ + public function cache($key = true, $expire = null, $tag = null) + { + if (false === $key || !$this->getConnection()->getCache()) { + return $this; + } + + if ($key instanceof \DateTimeInterface || $key instanceof \DateInterval || (is_int($key) && is_null($expire))) { + $expire = $key; + $key = true; + } + + $this->options['cache'] = [$key, $expire, $tag]; + + return $this; + } + + /** + * 指定查询lock + * @access public + * @param bool|string $lock 是否lock + * @return $this + */ + public function lock($lock = false) + { + $this->options['lock'] = $lock; + + if ($lock) { + $this->options['master'] = true; + } + + return $this; + } + + /** + * 指定数据表别名 + * @access public + * @param array|string $alias 数据表别名 + * @return $this + */ + public function alias($alias) + { + if (is_array($alias)) { + $this->options['alias'] = $alias; + } else { + $table = $this->getTable(); + + $this->options['alias'][$table] = $alias; + } + + return $this; + } + + /** + * 设置从主服务器读取数据 + * @access public + * @param bool $readMaster 是否从主服务器读取 + * @return $this + */ + public function master(bool $readMaster = true) + { + $this->options['master'] = $readMaster; + return $this; + } + + /** + * 设置是否严格检查字段名 + * @access public + * @param bool $strict 是否严格检查字段 + * @return $this + */ + public function strict(bool $strict = true) + { + $this->options['strict'] = $strict; + return $this; + } + + /** + * 设置自增序列名 + * @access public + * @param string $sequence 自增序列名 + * @return $this + */ + public function sequence(string $sequence = null) + { + $this->options['sequence'] = $sequence; + return $this; + } + + /** + * 设置JSON字段信息 + * @access public + * @param array $json JSON字段 + * @param bool $assoc 是否取出数组 + * @return $this + */ + public function json(array $json = [], bool $assoc = false) + { + $this->options['json'] = $json; + $this->options['json_assoc'] = $assoc; + return $this; + } + + /** + * 指定数据表主键 + * @access public + * @param string|array $pk 主键 + * @return $this + */ + public function pk($pk) + { + $this->pk = $pk; + return $this; + } + + /** + * 查询参数批量赋值 + * @access protected + * @param array $options 表达式参数 + * @return $this + */ + protected function options(array $options) + { + $this->options = $options; + return $this; + } + + /** + * 获取当前的查询参数 + * @access public + * @param string $name 参数名 + * @return mixed + */ + public function getOptions(string $name = '') + { + if ('' === $name) { + return $this->options; + } + + return $this->options[$name] ?? null; + } + + /** + * 设置当前的查询参数 + * @access public + * @param string $option 参数名 + * @param mixed $value 参数值 + * @return $this + */ + public function setOption(string $option, $value) + { + $this->options[$option] = $value; + return $this; + } + + /** + * 设置当前字段添加的表别名 + * @access public + * @param string $via 临时表别名 + * @return $this + */ + public function via(string $via = '') + { + $this->options['via'] = $via; + + return $this; + } + + /** + * 保存记录 自动判断insert或者update + * @access public + * @param array $data 数据 + * @param bool $forceInsert 是否强制insert + * @return integer + */ + public function save(array $data = [], bool $forceInsert = false) + { + if ($forceInsert) { + return $this->insert($data); + } + + $this->options['data'] = array_merge($this->options['data'] ?? [], $data); + + if (!empty($this->options['where'])) { + $isUpdate = true; + } else { + $isUpdate = $this->parseUpdateData($this->options['data']); + } + + return $isUpdate ? $this->update() : $this->insert(); + } + + /** + * 插入记录 + * @access public + * @param array $data 数据 + * @param boolean $getLastInsID 返回自增主键 + * @return integer|string + */ + public function insert(array $data = [], bool $getLastInsID = false) + { + if (!empty($data)) { + $this->options['data'] = $data; + } + + return $this->connection->insert($this, $getLastInsID); + } + + /** + * 插入记录并获取自增ID + * @access public + * @param array $data 数据 + * @return integer|string + */ + public function insertGetId(array $data) + { + return $this->insert($data, true); + } + + /** + * 批量插入记录 + * @access public + * @param array $dataSet 数据集 + * @param integer $limit 每次写入数据限制 + * @return integer + */ + public function insertAll(array $dataSet = [], int $limit = 0): int + { + if (empty($dataSet)) { + $dataSet = $this->options['data'] ?? []; + } + + if (empty($limit) && !empty($this->options['limit']) && is_numeric($this->options['limit'])) { + $limit = (int) $this->options['limit']; + } + + return $this->connection->insertAll($this, $dataSet, $limit); + } + + /** + * 通过Select方式插入记录 + * @access public + * @param array $fields 要插入的数据表字段名 + * @param string $table 要插入的数据表名 + * @return integer + */ + public function selectInsert(array $fields, string $table): int + { + return $this->connection->selectInsert($this, $fields, $table); + } + + /** + * 更新记录 + * @access public + * @param mixed $data 数据 + * @return integer + * @throws Exception + */ + public function update(array $data = []): int + { + if (!empty($data)) { + $this->options['data'] = array_merge($this->options['data'] ?? [], $data); + } + + if (empty($this->options['where'])) { + $this->parseUpdateData($this->options['data']); + } + + if (empty($this->options['where']) && $this->model) { + $this->where($this->model->getWhere()); + } + + if (empty($this->options['where'])) { + // 如果没有任何更新条件则不执行 + throw new Exception('miss update condition'); + } + + return $this->connection->update($this); + } + + /** + * 删除记录 + * @access public + * @param mixed $data 表达式 true 表示强制删除 + * @return int + * @throws Exception + */ + public function delete($data = null): int + { + if (!is_null($data) && true !== $data) { + // AR模式分析主键条件 + $this->parsePkWhere($data); + } + + if (empty($this->options['where']) && $this->model) { + $this->where($this->model->getWhere()); + } + + if (true !== $data && empty($this->options['where'])) { + // 如果条件为空 不进行删除操作 除非设置 1=1 + throw new Exception('delete without condition'); + } + + if (!empty($this->options['soft_delete'])) { + // 软删除 + list($field, $condition) = $this->options['soft_delete']; + if ($condition) { + unset($this->options['soft_delete']); + $this->options['data'] = [$field => $condition]; + + return $this->connection->update($this); + } + } + + $this->options['data'] = $data; + + return $this->connection->delete($this); + } + + /** + * 查找记录 + * @access public + * @param mixed $data 数据 + * @return Collection|array|static[] + * @throws Exception + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function select($data = null): Collection + { + if (!is_null($data)) { + // 主键条件分析 + $this->parsePkWhere($data); + } + + $resultSet = $this->connection->select($this); + + // 返回结果处理 + if (!empty($this->options['fail']) && count($resultSet) == 0) { + $this->throwNotFound(); + } + + // 数据列表读取后的处理 + if (!empty($this->model)) { + // 生成模型对象 + $resultSet = $this->resultSetToModelCollection($resultSet); + } else { + $this->resultSet($resultSet); + } + + return $resultSet; + } + + /** + * 查找单条记录 + * @access public + * @param mixed $data 查询数据 + * @return array|Model|null|static + * @throws Exception + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function find($data = null) + { + if (!is_null($data)) { + // AR模式分析主键条件 + $this->parsePkWhere($data); + } + + if (empty($this->options['where']) && empty($this->options['order'])) { + $result = []; + } else { + $result = $this->connection->find($this); + } + + // 数据处理 + if (empty($result)) { + return $this->resultToEmpty(); + } + + if (!empty($this->model)) { + // 返回模型对象 + $this->resultToModel($result, $this->options); + } else { + $this->result($result); + } + + return $result; + } + + /** + * 分析表达式(可用于查询或者写入操作) + * @access public + * @return array + */ + public function parseOptions(): array + { + $options = $this->getOptions(); + + // 获取数据表 + if (empty($options['table'])) { + $options['table'] = $this->getTable(); + } + + if (!isset($options['where'])) { + $options['where'] = []; + } elseif (isset($options['view'])) { + // 视图查询条件处理 + $this->parseView($options); + } + + foreach (['data', 'order', 'join', 'union'] as $name) { + if (!isset($options[$name])) { + $options[$name] = []; + } + } + + if (!isset($options['strict'])) { + $options['strict'] = $this->connection->getConfig('fields_strict'); + } + + foreach (['master', 'lock', 'fetch_sql', 'array', 'distinct', 'procedure'] as $name) { + if (!isset($options[$name])) { + $options[$name] = false; + } + } + + foreach (['group', 'having', 'limit', 'force', 'comment', 'partition', 'duplicate', 'extra'] as $name) { + if (!isset($options[$name])) { + $options[$name] = ''; + } + } + + if (isset($options['page'])) { + // 根据页数计算limit + [$page, $listRows] = $options['page']; + $page = $page > 0 ? $page : 1; + $listRows = $listRows ?: (is_numeric($options['limit']) ? $options['limit'] : 20); + $offset = $listRows * ($page - 1); + $options['limit'] = $offset . ',' . $listRows; + } + + $this->options = $options; + + return $options; + } + + /** + * 分析数据是否存在更新条件 + * @access public + * @param array $data 数据 + * @return bool + * @throws Exception + */ + public function parseUpdateData(&$data): bool + { + $pk = $this->getPk(); + $isUpdate = false; + // 如果存在主键数据 则自动作为更新条件 + if (is_string($pk) && isset($data[$pk])) { + $this->where($pk, '=', $data[$pk]); + $this->options['key'] = $data[$pk]; + unset($data[$pk]); + $isUpdate = true; + } elseif (is_array($pk)) { + foreach ($pk as $field) { + if (isset($data[$field])) { + $this->where($field, '=', $data[$field]); + $isUpdate = true; + } else { + // 如果缺少复合主键数据则不执行 + throw new Exception('miss complex primary data'); + } + unset($data[$field]); + } + } + + return $isUpdate; + } + + /** + * 把主键值转换为查询条件 支持复合主键 + * @access public + * @param array|string $data 主键数据 + * @return void + * @throws Exception + */ + public function parsePkWhere($data): void + { + $pk = $this->getPk(); + + if (is_string($pk)) { + // 获取数据表 + if (empty($this->options['table'])) { + $this->options['table'] = $this->getTable(); + } + + $table = is_array($this->options['table']) ? key($this->options['table']) : $this->options['table']; + + if (!empty($this->options['alias'][$table])) { + $alias = $this->options['alias'][$table]; + } + + $key = isset($alias) ? $alias . '.' . $pk : $pk; + // 根据主键查询 + if (is_array($data)) { + $this->where($key, 'in', $data); + } else { + $this->where($key, '=', $data); + $this->options['key'] = $data; + } + } + } + + /** + * 获取模型的更新条件 + * @access protected + * @param array $options 查询参数 + */ + protected function getModelUpdateCondition(array $options) + { + return $options['where']['AND'] ?? null; + } +} diff --git a/vendor/topthink/think-orm/src/db/Builder.php b/vendor/topthink/think-orm/src/db/Builder.php new file mode 100644 index 0000000..06e689f --- /dev/null +++ b/vendor/topthink/think-orm/src/db/Builder.php @@ -0,0 +1,1305 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db; + +use Closure; +use PDO; +use think\db\exception\DbException as Exception; + +/** + * Db Builder + */ +abstract class Builder +{ + /** + * Connection对象 + * @var ConnectionInterface + */ + protected $connection; + + /** + * 查询表达式映射 + * @var array + */ + protected $exp = ['NOTLIKE' => 'NOT LIKE', 'NOTIN' => 'NOT IN', 'NOTBETWEEN' => 'NOT BETWEEN', 'NOTEXISTS' => 'NOT EXISTS', 'NOTNULL' => 'NOT NULL', 'NOTBETWEEN TIME' => 'NOT BETWEEN TIME']; + + /** + * 查询表达式解析 + * @var array + */ + protected $parser = [ + 'parseCompare' => ['=', '<>', '>', '>=', '<', '<='], + 'parseLike' => ['LIKE', 'NOT LIKE'], + 'parseBetween' => ['NOT BETWEEN', 'BETWEEN'], + 'parseIn' => ['NOT IN', 'IN'], + 'parseExp' => ['EXP'], + 'parseNull' => ['NOT NULL', 'NULL'], + 'parseBetweenTime' => ['BETWEEN TIME', 'NOT BETWEEN TIME'], + 'parseTime' => ['< TIME', '> TIME', '<= TIME', '>= TIME'], + 'parseExists' => ['NOT EXISTS', 'EXISTS'], + 'parseColumn' => ['COLUMN'], + ]; + + /** + * SELECT SQL表达式 + * @var string + */ + protected $selectSql = 'SELECT%DISTINCT%%EXTRA% %FIELD% FROM %TABLE%%FORCE%%JOIN%%WHERE%%GROUP%%HAVING%%UNION%%ORDER%%LIMIT% %LOCK%%COMMENT%'; + + /** + * INSERT SQL表达式 + * @var string + */ + protected $insertSql = '%INSERT%%EXTRA% INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; + + /** + * INSERT ALL SQL表达式 + * @var string + */ + protected $insertAllSql = '%INSERT%%EXTRA% INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; + + /** + * UPDATE SQL表达式 + * @var string + */ + protected $updateSql = 'UPDATE%EXTRA% %TABLE% SET %SET%%JOIN%%WHERE%%ORDER%%LIMIT% %LOCK%%COMMENT%'; + + /** + * DELETE SQL表达式 + * @var string + */ + protected $deleteSql = 'DELETE%EXTRA% FROM %TABLE%%USING%%JOIN%%WHERE%%ORDER%%LIMIT% %LOCK%%COMMENT%'; + + /** + * 架构函数 + * @access public + * @param ConnectionInterface $connection 数据库连接对象实例 + */ + public function __construct(ConnectionInterface $connection) + { + $this->connection = $connection; + } + + /** + * 获取当前的连接对象实例 + * @access public + * @return ConnectionInterface + */ + public function getConnection(): ConnectionInterface + { + return $this->connection; + } + + /** + * 注册查询表达式解析 + * @access public + * @param string $name 解析方法 + * @param array $parser 匹配表达式数据 + * @return $this + */ + public function bindParser(string $name, array $parser) + { + $this->parser[$name] = $parser; + return $this; + } + + /** + * 数据分析 + * @access protected + * @param Query $query 查询对象 + * @param array $data 数据 + * @param array $fields 字段信息 + * @param array $bind 参数绑定 + * @return array + */ + protected function parseData(Query $query, array $data = [], array $fields = [], array $bind = []): array + { + if (empty($data)) { + return []; + } + + $options = $query->getOptions(); + + // 获取绑定信息 + if (empty($bind)) { + $bind = $query->getFieldsBindType(); + } + + if (empty($fields)) { + if (empty($options['field']) || '*' == $options['field']) { + $fields = array_keys($bind); + } else { + $fields = $options['field']; + } + } + + $result = []; + + foreach ($data as $key => $val) { + $item = $this->parseKey($query, $key, true); + + if ($val instanceof Raw) { + $result[$item] = $this->parseRaw($query, $val); + continue; + } elseif (!is_scalar($val) && (in_array($key, (array) $query->getOptions('json')) || 'json' == $query->getFieldType($key))) { + $val = json_encode($val); + } + + if (false !== strpos($key, '->')) { + [$key, $name] = explode('->', $key, 2); + $item = $this->parseKey($query, $key); + $result[$item] = 'json_set(' . $item . ', \'$.' . $name . '\', ' . $this->parseDataBind($query, $key . '->' . $name, $val, $bind) . ')'; + } elseif (false === strpos($key, '.') && !in_array($key, $fields, true)) { + if ($options['strict']) { + throw new Exception('fields not exists:[' . $key . ']'); + } + } elseif (is_null($val)) { + $result[$item] = 'NULL'; + } elseif (is_array($val) && !empty($val) && is_string($val[0])) { + switch (strtoupper($val[0])) { + case 'INC': + $result[$item] = $item . ' + ' . floatval($val[1]); + break; + case 'DEC': + $result[$item] = $item . ' - ' . floatval($val[1]); + break; + } + } elseif (is_scalar($val)) { + // 过滤非标量数据 + $result[$item] = $this->parseDataBind($query, $key, $val, $bind); + } + } + + return $result; + } + + /** + * 数据绑定处理 + * @access protected + * @param Query $query 查询对象 + * @param string $key 字段名 + * @param mixed $data 数据 + * @param array $bind 绑定数据 + * @return string + */ + protected function parseDataBind(Query $query, string $key, $data, array $bind = []): string + { + if ($data instanceof Raw) { + return $this->parseRaw($query, $data); + } + + $name = $query->bindValue($data, $bind[$key] ?? PDO::PARAM_STR); + + return ':' . $name; + } + + /** + * 字段名分析 + * @access public + * @param Query $query 查询对象 + * @param mixed $key 字段名 + * @param bool $strict 严格检测 + * @return string + */ + public function parseKey(Query $query, $key, bool $strict = false): string + { + return $key; + } + + /** + * 查询额外参数分析 + * @access protected + * @param Query $query 查询对象 + * @param string $extra 额外参数 + * @return string + */ + protected function parseExtra(Query $query, string $extra): string + { + return preg_match('/^[\w]+$/i', $extra) ? ' ' . strtoupper($extra) : ''; + } + + /** + * field分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $fields 字段名 + * @return string + */ + protected function parseField(Query $query, $fields): string + { + if (is_array($fields)) { + // 支持 'field1'=>'field2' 这样的字段别名定义 + $array = []; + + foreach ($fields as $key => $field) { + if ($field instanceof Raw) { + $array[] = $this->parseRaw($query, $field); + } elseif (!is_numeric($key)) { + $array[] = $this->parseKey($query, $key) . ' AS ' . $this->parseKey($query, $field, true); + } else { + $array[] = $this->parseKey($query, $field); + } + } + + $fieldsStr = implode(',', $array); + } else { + $fieldsStr = '*'; + } + + return $fieldsStr; + } + + /** + * table分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $tables 表名 + * @return string + */ + protected function parseTable(Query $query, $tables): string + { + $item = []; + $options = $query->getOptions(); + + foreach ((array) $tables as $key => $table) { + if ($table instanceof Raw) { + $item[] = $this->parseRaw($query, $table); + } elseif (!is_numeric($key)) { + $item[] = $this->parseKey($query, $key) . ' ' . $this->parseKey($query, $table); + } elseif (isset($options['alias'][$table])) { + $item[] = $this->parseKey($query, $table) . ' ' . $this->parseKey($query, $options['alias'][$table]); + } else { + $item[] = $this->parseKey($query, $table); + } + } + + return implode(',', $item); + } + + /** + * where分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $where 查询条件 + * @return string + */ + protected function parseWhere(Query $query, array $where): string + { + $options = $query->getOptions(); + $whereStr = $this->buildWhere($query, $where); + + if (!empty($options['soft_delete'])) { + // 附加软删除条件 + [$field, $condition] = $options['soft_delete']; + + $binds = $query->getFieldsBindType(); + $whereStr = $whereStr ? '( ' . $whereStr . ' ) AND ' : ''; + $whereStr = $whereStr . $this->parseWhereItem($query, $field, $condition, $binds); + } + + return empty($whereStr) ? '' : ' WHERE ' . $whereStr; + } + + /** + * 生成查询条件SQL + * @access public + * @param Query $query 查询对象 + * @param mixed $where 查询条件 + * @return string + */ + public function buildWhere(Query $query, array $where): string + { + if (empty($where)) { + $where = []; + } + + $whereStr = ''; + + $binds = $query->getFieldsBindType(); + + foreach ($where as $logic => $val) { + $str = $this->parseWhereLogic($query, $logic, $val, $binds); + + $whereStr .= empty($whereStr) ? substr(implode(' ', $str), strlen($logic) + 1) : implode(' ', $str); + } + + return $whereStr; + } + + /** + * 不同字段使用相同查询条件(AND) + * @access protected + * @param Query $query 查询对象 + * @param string $logic Logic + * @param array $val 查询条件 + * @param array $binds 参数绑定 + * @return array + */ + protected function parseWhereLogic(Query $query, string $logic, array $val, array $binds = []): array + { + $where = []; + foreach ($val as $value) { + if ($value instanceof Raw) { + $where[] = ' ' . $logic . ' ( ' . $this->parseRaw($query, $value) . ' )'; + continue; + } + + if (is_array($value)) { + if (key($value) !== 0) { + throw new Exception('where express error:' . var_export($value, true)); + } + $field = array_shift($value); + } elseif (true === $value) { + $where[] = ' ' . $logic . ' 1 '; + continue; + } elseif (!($value instanceof Closure)) { + throw new Exception('where express error:' . var_export($value, true)); + } + + if ($value instanceof Closure) { + // 使用闭包查询 + $whereClosureStr = $this->parseClosureWhere($query, $value, $logic); + if ($whereClosureStr) { + $where[] = $whereClosureStr; + } + } elseif (is_array($field)) { + $where[] = $this->parseMultiWhereField($query, $value, $field, $logic, $binds); + } elseif ($field instanceof Raw) { + $where[] = ' ' . $logic . ' ' . $this->parseWhereItem($query, $field, $value, $binds); + } elseif (strpos($field, '|')) { + $where[] = $this->parseFieldsOr($query, $value, $field, $logic, $binds); + } elseif (strpos($field, '&')) { + $where[] = $this->parseFieldsAnd($query, $value, $field, $logic, $binds); + } else { + // 对字段使用表达式查询 + $field = is_string($field) ? $field : ''; + $where[] = ' ' . $logic . ' ' . $this->parseWhereItem($query, $field, $value, $binds); + } + } + + return $where; + } + + /** + * 不同字段使用相同查询条件(AND) + * @access protected + * @param Query $query 查询对象 + * @param mixed $value 查询条件 + * @param string $field 查询字段 + * @param string $logic Logic + * @param array $binds 参数绑定 + * @return string + */ + protected function parseFieldsAnd(Query $query, $value, string $field, string $logic, array $binds): string + { + $item = []; + + foreach (explode('&', $field) as $k) { + $item[] = $this->parseWhereItem($query, $k, $value, $binds); + } + + return ' ' . $logic . ' ( ' . implode(' AND ', $item) . ' )'; + } + + /** + * 不同字段使用相同查询条件(OR) + * @access protected + * @param Query $query 查询对象 + * @param mixed $value 查询条件 + * @param string $field 查询字段 + * @param string $logic Logic + * @param array $binds 参数绑定 + * @return string + */ + protected function parseFieldsOr(Query $query, $value, string $field, string $logic, array $binds): string + { + $item = []; + + foreach (explode('|', $field) as $k) { + $item[] = $this->parseWhereItem($query, $k, $value, $binds); + } + + return ' ' . $logic . ' ( ' . implode(' OR ', $item) . ' )'; + } + + /** + * 闭包查询 + * @access protected + * @param Query $query 查询对象 + * @param Closure $value 查询条件 + * @param string $logic Logic + * @return string + */ + protected function parseClosureWhere(Query $query, Closure $value, string $logic): string + { + $newQuery = $query->newQuery(); + $value($newQuery); + $whereClosure = $this->buildWhere($newQuery, $newQuery->getOptions('where') ?: []); + + if (!empty($whereClosure)) { + $query->bind($newQuery->getBind(false)); + $where = ' ' . $logic . ' ( ' . $whereClosure . ' )'; + } + + return $where ?? ''; + } + + /** + * 复合条件查询 + * @access protected + * @param Query $query 查询对象 + * @param mixed $value 查询条件 + * @param mixed $field 查询字段 + * @param string $logic Logic + * @param array $binds 参数绑定 + * @return string + */ + protected function parseMultiWhereField(Query $query, $value, $field, string $logic, array $binds): string + { + array_unshift($value, $field); + + $where = []; + foreach ($value as $item) { + $where[] = $this->parseWhereItem($query, array_shift($item), $item, $binds); + } + + return ' ' . $logic . ' ( ' . implode(' AND ', $where) . ' )'; + } + + /** + * where子单元分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $field 查询字段 + * @param array $val 查询条件 + * @param array $binds 参数绑定 + * @return string + */ + protected function parseWhereItem(Query $query, $field, array $val, array $binds = []): string + { + // 字段分析 + $key = $field ? $this->parseKey($query, $field, true) : ''; + + [$exp, $value] = $val; + + // 检测操作符 + if (!is_string($exp)) { + throw new Exception('where express error:' . var_export($exp, true)); + } + + $exp = strtoupper($exp); + if (isset($this->exp[$exp])) { + $exp = $this->exp[$exp]; + } + + if (is_string($field) && 'LIKE' != $exp) { + $bindType = $binds[$field] ?? PDO::PARAM_STR; + } else { + $bindType = PDO::PARAM_STR; + } + + if ($value instanceof Raw) { + + } elseif (is_object($value) && method_exists($value, '__toString')) { + // 对象数据写入 + $value = $value->__toString(); + } + + if (is_scalar($value) && !in_array($exp, ['EXP', 'NOT NULL', 'NULL', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN']) && strpos($exp, 'TIME') === false) { + if (is_string($value) && 0 === strpos($value, ':') && $query->isBind(substr($value, 1))) { + } else { + $name = $query->bindValue($value, $bindType); + $value = ':' . $name; + } + } + + // 解析查询表达式 + foreach ($this->parser as $fun => $parse) { + if (in_array($exp, $parse)) { + return $this->$fun($query, $key, $exp, $value, $field, $bindType, $val[2] ?? 'AND'); + } + } + + throw new Exception('where express error:' . $exp); + } + + /** + * 模糊查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param array $value + * @param string $field + * @param integer $bindType + * @param string $logic + * @return string + */ + protected function parseLike(Query $query, string $key, string $exp, $value, $field, int $bindType, string $logic): string + { + // 模糊匹配 + if (is_array($value)) { + $array = []; + foreach ($value as $item) { + $name = $query->bindValue($item, PDO::PARAM_STR); + $array[] = $key . ' ' . $exp . ' :' . $name; + } + + $whereStr = '(' . implode(' ' . strtoupper($logic) . ' ', $array) . ')'; + } else { + $whereStr = $key . ' ' . $exp . ' ' . $value; + } + + return $whereStr; + } + + /** + * 表达式查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param array $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseExp(Query $query, string $key, string $exp, Raw $value, string $field, int $bindType): string + { + // 表达式查询 + return '( ' . $key . ' ' . $this->parseRaw($query, $value) . ' )'; + } + + /** + * 表达式查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param array $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseColumn(Query $query, string $key, $exp, array $value, string $field, int $bindType): string + { + // 字段比较查询 + [$op, $field] = $value; + + if (!in_array(trim($op), ['=', '<>', '>', '>=', '<', '<='])) { + throw new Exception('where express error:' . var_export($value, true)); + } + + return '( ' . $key . ' ' . $op . ' ' . $this->parseKey($query, $field, true) . ' )'; + } + + /** + * Null查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseNull(Query $query, string $key, string $exp, $value, $field, int $bindType): string + { + // NULL 查询 + return $key . ' IS ' . $exp; + } + + /** + * 范围查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseBetween(Query $query, string $key, string $exp, $value, $field, int $bindType): string + { + // BETWEEN 查询 + $data = is_array($value) ? $value : explode(',', $value); + + $min = $query->bindValue($data[0], $bindType); + $max = $query->bindValue($data[1], $bindType); + + return $key . ' ' . $exp . ' :' . $min . ' AND :' . $max . ' '; + } + + /** + * Exists查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseExists(Query $query, string $key, string $exp, $value, string $field, int $bindType): string + { + // EXISTS 查询 + if ($value instanceof Closure) { + $value = $this->parseClosure($query, $value, false); + } elseif ($value instanceof Raw) { + $value = $this->parseRaw($query, $value); + } else { + throw new Exception('where express error:' . $value); + } + + return $exp . ' ( ' . $value . ' )'; + } + + /** + * 时间比较查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseTime(Query $query, string $key, string $exp, $value, $field, int $bindType): string + { + return $key . ' ' . substr($exp, 0, 2) . ' ' . $this->parseDateTime($query, $value, $field, $bindType); + } + + /** + * 大小比较查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseCompare(Query $query, string $key, string $exp, $value, $field, int $bindType): string + { + if (is_array($value)) { + throw new Exception('where express error:' . $exp . var_export($value, true)); + } + + // 比较运算 + if ($value instanceof Closure) { + $value = $this->parseClosure($query, $value); + } + + if ('=' == $exp && is_null($value)) { + return $key . ' IS NULL'; + } + + return $key . ' ' . $exp . ' ' . $value; + } + + /** + * 时间范围查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseBetweenTime(Query $query, string $key, string $exp, $value, $field, int $bindType): string + { + if (is_string($value)) { + $value = explode(',', $value); + } + + return $key . ' ' . substr($exp, 0, -4) + . $this->parseDateTime($query, $value[0], $field, $bindType) + . ' AND ' + . $this->parseDateTime($query, $value[1], $field, $bindType); + + } + + /** + * IN查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseIn(Query $query, string $key, string $exp, $value, $field, int $bindType): string + { + // IN 查询 + if ($value instanceof Closure) { + $value = $this->parseClosure($query, $value, false); + } elseif ($value instanceof Raw) { + $value = $this->parseRaw($query, $value); + } else { + $value = array_unique(is_array($value) ? $value : explode(',', (string) $value)); + if (count($value) === 0) { + return 'IN' == $exp ? '0 = 1' : '1 = 1'; + } + $array = []; + + foreach ($value as $v) { + $name = $query->bindValue($v, $bindType); + $array[] = ':' . $name; + } + + if (count($array) == 1) { + return $key . ('IN' == $exp ? ' = ' : ' <> ') . $array[0]; + } else { + $value = implode(',', $array); + } + } + + return $key . ' ' . $exp . ' (' . $value . ')'; + } + + /** + * 闭包子查询 + * @access protected + * @param Query $query 查询对象 + * @param \Closure $call + * @param bool $show + * @return string + */ + protected function parseClosure(Query $query, Closure $call, bool $show = true): string + { + $newQuery = $query->newQuery()->removeOption(); + $call($newQuery); + + return $newQuery->buildSql($show); + } + + /** + * 日期时间条件解析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $value + * @param string $key + * @param integer $bindType + * @return string + */ + protected function parseDateTime(Query $query, $value, string $key, int $bindType): string + { + $options = $query->getOptions(); + + // 获取时间字段类型 + if (strpos($key, '.')) { + [$table, $key] = explode('.', $key); + + if (isset($options['alias']) && $pos = array_search($table, $options['alias'])) { + $table = $pos; + } + } else { + $table = $options['table']; + } + + $type = $query->getFieldType($key); + + if ($type) { + if (is_string($value)) { + $value = strtotime($value) ?: $value; + } + + if (is_int($value)) { + if (preg_match('/(datetime|timestamp)/is', $type)) { + // 日期及时间戳类型 + $value = date('Y-m-d H:i:s', $value); + } elseif (preg_match('/(date)/is', $type)) { + // 日期及时间戳类型 + $value = date('Y-m-d', $value); + } + } + } + + $name = $query->bindValue($value, $bindType); + + return ':' . $name; + } + + /** + * limit分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $limit + * @return string + */ + protected function parseLimit(Query $query, string $limit): string + { + return (!empty($limit) && false === strpos($limit, '(')) ? ' LIMIT ' . $limit . ' ' : ''; + } + + /** + * join分析 + * @access protected + * @param Query $query 查询对象 + * @param array $join + * @return string + */ + protected function parseJoin(Query $query, array $join): string + { + $joinStr = ''; + + foreach ($join as $item) { + [$table, $type, $on] = $item; + + if (strpos($on, '=')) { + [$val1, $val2] = explode('=', $on, 2); + + $condition = $this->parseKey($query, $val1) . '=' . $this->parseKey($query, $val2); + } else { + $condition = $on; + } + + $table = $this->parseTable($query, $table); + + $joinStr .= ' ' . $type . ' JOIN ' . $table . ' ON ' . $condition; + } + + return $joinStr; + } + + /** + * order分析 + * @access protected + * @param Query $query 查询对象 + * @param array $order + * @return string + */ + protected function parseOrder(Query $query, array $order): string + { + $array = []; + foreach ($order as $key => $val) { + if ($val instanceof Raw) { + $array[] = $this->parseRaw($query, $val); + } elseif (is_array($val) && preg_match('/^[\w\.]+$/', $key)) { + $array[] = $this->parseOrderField($query, $key, $val); + } elseif ('[rand]' == $val) { + $array[] = $this->parseRand($query); + } elseif (is_string($val)) { + if (is_numeric($key)) { + [$key, $sort] = explode(' ', strpos($val, ' ') ? $val : $val . ' '); + } else { + $sort = $val; + } + + if (preg_match('/^[\w\.]+$/', $key)) { + $sort = strtoupper($sort); + $sort = in_array($sort, ['ASC', 'DESC'], true) ? ' ' . $sort : ''; + $array[] = $this->parseKey($query, $key, true) . $sort; + } else { + throw new Exception('order express error:' . $key); + } + } + } + + return empty($array) ? '' : ' ORDER BY ' . implode(',', $array); + } + + /** + * 分析Raw对象 + * @access protected + * @param Query $query 查询对象 + * @param Raw $raw Raw对象 + * @return string + */ + protected function parseRaw(Query $query, Raw $raw): string + { + $sql = $raw->getValue(); + $bind = $raw->getBind(); + + if ($bind) { + $query->bindParams($sql, $bind); + } + + return $sql; + } + + /** + * 随机排序 + * @access protected + * @param Query $query 查询对象 + * @return string + */ + protected function parseRand(Query $query): string + { + return ''; + } + + /** + * orderField分析 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param array $val + * @return string + */ + protected function parseOrderField(Query $query, string $key, array $val): string + { + if (isset($val['sort'])) { + $sort = $val['sort']; + unset($val['sort']); + } else { + $sort = ''; + } + + $sort = strtoupper($sort); + $sort = in_array($sort, ['ASC', 'DESC'], true) ? ' ' . $sort : ''; + $bind = $query->getFieldsBindType(); + + foreach ($val as $k => $item) { + $val[$k] = $this->parseDataBind($query, $key, $item, $bind); + } + + return 'field(' . $this->parseKey($query, $key, true) . ',' . implode(',', $val) . ')' . $sort; + } + + /** + * group分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $group + * @return string + */ + protected function parseGroup(Query $query, $group): string + { + if (empty($group)) { + return ''; + } + + if (is_string($group)) { + $group = explode(',', $group); + } + + $val = []; + foreach ($group as $key) { + $val[] = $this->parseKey($query, $key); + } + + return ' GROUP BY ' . implode(',', $val); + } + + /** + * having分析 + * @access protected + * @param Query $query 查询对象 + * @param string $having + * @return string + */ + protected function parseHaving(Query $query, string $having): string + { + return !empty($having) ? ' HAVING ' . $having : ''; + } + + /** + * comment分析 + * @access protected + * @param Query $query 查询对象 + * @param string $comment + * @return string + */ + protected function parseComment(Query $query, string $comment): string + { + if (false !== strpos($comment, '*/')) { + $comment = strstr($comment, '*/', true); + } + + return !empty($comment) ? ' /* ' . $comment . ' */' : ''; + } + + /** + * distinct分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $distinct + * @return string + */ + protected function parseDistinct(Query $query, bool $distinct): string + { + return !empty($distinct) ? ' DISTINCT ' : ''; + } + + /** + * union分析 + * @access protected + * @param Query $query 查询对象 + * @param array $union + * @return string + */ + protected function parseUnion(Query $query, array $union): string + { + if (empty($union)) { + return ''; + } + + $type = $union['type']; + unset($union['type']); + + foreach ($union as $u) { + if ($u instanceof Closure) { + $sql[] = $type . ' ' . $this->parseClosure($query, $u); + } elseif (is_string($u)) { + $sql[] = $type . ' ( ' . $u . ' )'; + } + } + + return ' ' . implode(' ', $sql); + } + + /** + * index分析,可在操作链中指定需要强制使用的索引 + * @access protected + * @param Query $query 查询对象 + * @param mixed $index + * @return string + */ + protected function parseForce(Query $query, $index): string + { + if (empty($index)) { + return ''; + } + + if (is_array($index)) { + $index = join(',', $index); + } + + return sprintf(" FORCE INDEX ( %s ) ", $index); + } + + /** + * 设置锁机制 + * @access protected + * @param Query $query 查询对象 + * @param bool|string $lock + * @return string + */ + protected function parseLock(Query $query, $lock = false): string + { + if (is_bool($lock)) { + return $lock ? ' FOR UPDATE ' : ''; + } + + if (is_string($lock) && !empty($lock)) { + return ' ' . trim($lock) . ' '; + } else { + return ''; + } + } + + /** + * 生成查询SQL + * @access public + * @param Query $query 查询对象 + * @param bool $one 是否仅获取一个记录 + * @return string + */ + public function select(Query $query, bool $one = false): string + { + $options = $query->getOptions(); + + return str_replace( + ['%TABLE%', '%DISTINCT%', '%EXTRA%', '%FIELD%', '%JOIN%', '%WHERE%', '%GROUP%', '%HAVING%', '%ORDER%', '%LIMIT%', '%UNION%', '%LOCK%', '%COMMENT%', '%FORCE%'], + [ + $this->parseTable($query, $options['table']), + $this->parseDistinct($query, $options['distinct']), + $this->parseExtra($query, $options['extra']), + $this->parseField($query, $options['field'] ?? '*'), + $this->parseJoin($query, $options['join']), + $this->parseWhere($query, $options['where']), + $this->parseGroup($query, $options['group']), + $this->parseHaving($query, $options['having']), + $this->parseOrder($query, $options['order']), + $this->parseLimit($query, $one ? '1' : $options['limit']), + $this->parseUnion($query, $options['union']), + $this->parseLock($query, $options['lock']), + $this->parseComment($query, $options['comment']), + $this->parseForce($query, $options['force']), + ], + $this->selectSql); + } + + /** + * 生成Insert SQL + * @access public + * @param Query $query 查询对象 + * @return string + */ + public function insert(Query $query): string + { + $options = $query->getOptions(); + + // 分析并处理数据 + $data = $this->parseData($query, $options['data']); + if (empty($data)) { + return ''; + } + + $fields = array_keys($data); + $values = array_values($data); + + return str_replace( + ['%INSERT%', '%TABLE%', '%EXTRA%', '%FIELD%', '%DATA%', '%COMMENT%'], + [ + !empty($options['replace']) ? 'REPLACE' : 'INSERT', + $this->parseTable($query, $options['table']), + $this->parseExtra($query, $options['extra']), + implode(' , ', $fields), + implode(' , ', $values), + $this->parseComment($query, $options['comment']), + ], + $this->insertSql); + } + + /** + * 生成insertall SQL + * @access public + * @param Query $query 查询对象 + * @param array $dataSet 数据集 + * @return string + */ + public function insertAll(Query $query, array $dataSet): string + { + $options = $query->getOptions(); + + // 获取绑定信息 + $bind = $query->getFieldsBindType(); + + // 获取合法的字段 + if (empty($options['field']) || '*' == $options['field']) { + $allowFields = array_keys($bind); + } else { + $allowFields = $options['field']; + } + + $fields = []; + $values = []; + + foreach ($dataSet as $k => $data) { + $data = $this->parseData($query, $data, $allowFields, $bind); + + $values[] = 'SELECT ' . implode(',', array_values($data)); + + if (!isset($insertFields)) { + $insertFields = array_keys($data); + } + } + + foreach ($insertFields as $field) { + $fields[] = $this->parseKey($query, $field); + } + + return str_replace( + ['%INSERT%', '%TABLE%', '%EXTRA%', '%FIELD%', '%DATA%', '%COMMENT%'], + [ + !empty($options['replace']) ? 'REPLACE' : 'INSERT', + $this->parseTable($query, $options['table']), + $this->parseExtra($query, $options['extra']), + implode(' , ', $fields), + implode(' UNION ALL ', $values), + $this->parseComment($query, $options['comment']), + ], + $this->insertAllSql); + } + + /** + * 生成slect insert SQL + * @access public + * @param Query $query 查询对象 + * @param array $fields 数据 + * @param string $table 数据表 + * @return string + */ + public function selectInsert(Query $query, array $fields, string $table): string + { + foreach ($fields as &$field) { + $field = $this->parseKey($query, $field, true); + } + + return 'INSERT INTO ' . $this->parseTable($query, $table) . ' (' . implode(',', $fields) . ') ' . $this->select($query); + } + + /** + * 生成update SQL + * @access public + * @param Query $query 查询对象 + * @return string + */ + public function update(Query $query): string + { + $options = $query->getOptions(); + + $data = $this->parseData($query, $options['data']); + + if (empty($data)) { + return ''; + } + + $set = []; + foreach ($data as $key => $val) { + $set[] = $key . ' = ' . $val; + } + + return str_replace( + ['%TABLE%', '%EXTRA%', '%SET%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'], + [ + $this->parseTable($query, $options['table']), + $this->parseExtra($query, $options['extra']), + implode(' , ', $set), + $this->parseJoin($query, $options['join']), + $this->parseWhere($query, $options['where']), + $this->parseOrder($query, $options['order']), + $this->parseLimit($query, $options['limit']), + $this->parseLock($query, $options['lock']), + $this->parseComment($query, $options['comment']), + ], + $this->updateSql); + } + + /** + * 生成delete SQL + * @access public + * @param Query $query 查询对象 + * @return string + */ + public function delete(Query $query): string + { + $options = $query->getOptions(); + + return str_replace( + ['%TABLE%', '%EXTRA%', '%USING%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'], + [ + $this->parseTable($query, $options['table']), + $this->parseExtra($query, $options['extra']), + !empty($options['using']) ? ' USING ' . $this->parseTable($query, $options['using']) . ' ' : '', + $this->parseJoin($query, $options['join']), + $this->parseWhere($query, $options['where']), + $this->parseOrder($query, $options['order']), + $this->parseLimit($query, $options['limit']), + $this->parseLock($query, $options['lock']), + $this->parseComment($query, $options['comment']), + ], + $this->deleteSql); + } +} diff --git a/vendor/topthink/think-orm/src/db/CacheItem.php b/vendor/topthink/think-orm/src/db/CacheItem.php new file mode 100644 index 0000000..839f384 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/CacheItem.php @@ -0,0 +1,209 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db; + +use DateInterval; +use DateTime; +use DateTimeInterface; +use think\db\exception\InvalidArgumentException; + +/** + * CacheItem实现类 + */ +class CacheItem +{ + /** + * 缓存Key + * @var string + */ + protected $key; + + /** + * 缓存内容 + * @var mixed + */ + protected $value; + + /** + * 过期时间 + * @var int|DateTimeInterface + */ + protected $expire; + + /** + * 缓存tag + * @var string + */ + protected $tag; + + /** + * 缓存是否命中 + * @var bool + */ + protected $isHit = false; + + public function __construct(string $key = null) + { + $this->key = $key; + } + + /** + * 为此缓存项设置「键」 + * @access public + * @param string $key + * @return $this + */ + public function setKey(string $key) + { + $this->key = $key; + return $this; + } + + /** + * 返回当前缓存项的「键」 + * @access public + * @return string + */ + public function getKey() + { + return $this->key; + } + + /** + * 返回当前缓存项的有效期 + * @access public + * @return DateTimeInterface|int|null + */ + public function getExpire() + { + if ($this->expire instanceof DateTimeInterface) { + return $this->expire; + } + + return $this->expire ? $this->expire - time() : null; + } + + /** + * 获取缓存Tag + * @access public + * @return string|array + */ + public function getTag() + { + return $this->tag; + } + + /** + * 凭借此缓存项的「键」从缓存系统里面取出缓存项 + * @access public + * @return mixed + */ + public function get() + { + return $this->value; + } + + /** + * 确认缓存项的检查是否命中 + * @access public + * @return bool + */ + public function isHit(): bool + { + return $this->isHit; + } + + /** + * 为此缓存项设置「值」 + * @access public + * @param mixed $value + * @return $this + */ + public function set($value) + { + $this->value = $value; + $this->isHit = true; + return $this; + } + + /** + * 为此缓存项设置所属标签 + * @access public + * @param string|array $tag + * @return $this + */ + public function tag($tag = null) + { + $this->tag = $tag; + return $this; + } + + /** + * 设置缓存项的有效期 + * @access public + * @param mixed $expire + * @return $this + */ + public function expire($expire) + { + if (is_null($expire)) { + $this->expire = null; + } elseif (is_numeric($expire) || $expire instanceof DateInterval) { + $this->expiresAfter($expire); + } elseif ($expire instanceof DateTimeInterface) { + $this->expire = $expire; + } else { + throw new InvalidArgumentException('not support datetime'); + } + + return $this; + } + + /** + * 设置缓存项的准确过期时间点 + * @access public + * @param DateTimeInterface $expiration + * @return $this + */ + public function expiresAt($expiration) + { + if ($expiration instanceof DateTimeInterface) { + $this->expire = $expiration; + } else { + throw new InvalidArgumentException('not support datetime'); + } + + return $this; + } + + /** + * 设置缓存项的过期时间 + * @access public + * @param int|DateInterval $timeInterval + * @return $this + * @throws InvalidArgumentException + */ + public function expiresAfter($timeInterval) + { + if ($timeInterval instanceof DateInterval) { + $this->expire = (int) DateTime::createFromFormat('U', (string) time())->add($timeInterval)->format('U'); + } elseif (is_numeric($timeInterval)) { + $this->expire = $timeInterval + time(); + } else { + throw new InvalidArgumentException('not support datetime'); + } + + return $this; + } + +} diff --git a/vendor/topthink/think-orm/src/db/Connection.php b/vendor/topthink/think-orm/src/db/Connection.php new file mode 100644 index 0000000..aa86ba8 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/Connection.php @@ -0,0 +1,347 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db; + +use Psr\SimpleCache\CacheInterface; +use think\DbManager; + +/** + * 数据库连接基础类 + */ +abstract class Connection implements ConnectionInterface +{ + + /** + * 当前SQL指令 + * @var string + */ + protected $queryStr = ''; + + /** + * 返回或者影响记录数 + * @var int + */ + protected $numRows = 0; + + /** + * 事务指令数 + * @var int + */ + protected $transTimes = 0; + + /** + * 错误信息 + * @var string + */ + protected $error = ''; + + /** + * 数据库连接ID 支持多个连接 + * @var array + */ + protected $links = []; + + /** + * 当前连接ID + * @var object + */ + protected $linkID; + + /** + * 当前读连接ID + * @var object + */ + protected $linkRead; + + /** + * 当前写连接ID + * @var object + */ + protected $linkWrite; + + /** + * 数据表信息 + * @var array + */ + protected $info = []; + + /** + * 查询开始时间 + * @var float + */ + protected $queryStartTime; + + /** + * Builder对象 + * @var Builder + */ + protected $builder; + + /** + * Db对象 + * @var DbManager + */ + protected $db; + + /** + * 是否读取主库 + * @var bool + */ + protected $readMaster = false; + + /** + * 数据库连接参数配置 + * @var array + */ + protected $config = []; + + /** + * 缓存对象 + * @var CacheInterface + */ + protected $cache; + + /** + * 架构函数 读取数据库配置信息 + * @access public + * @param array $config 数据库配置数组 + */ + public function __construct(array $config = []) + { + if (!empty($config)) { + $this->config = array_merge($this->config, $config); + } + + // 创建Builder对象 + $class = $this->getBuilderClass(); + + $this->builder = new $class($this); + } + + /** + * 获取当前的builder实例对象 + * @access public + * @return Builder + */ + public function getBuilder() + { + return $this->builder; + } + + /** + * 创建查询对象 + */ + public function newQuery() + { + $class = $this->getQueryClass(); + + /** @var BaseQuery $query */ + $query = new $class($this); + + $timeRule = $this->db->getConfig('time_query_rule'); + if (!empty($timeRule)) { + $query->timeRule($timeRule); + } + + return $query; + } + + /** + * 指定表名开始查询 + * @param $table + * @return BaseQuery + */ + public function table($table) + { + return $this->newQuery()->table($table); + } + + /** + * 指定表名开始查询(不带前缀) + * @param $name + * @return BaseQuery + */ + public function name($name) + { + return $this->newQuery()->name($name); + } + + /** + * 设置当前的数据库Db对象 + * @access public + * @param DbManager $db + * @return void + */ + public function setDb(DbManager $db) + { + $this->db = $db; + } + + /** + * 设置当前的缓存对象 + * @access public + * @param CacheInterface $cache + * @return void + */ + public function setCache(CacheInterface $cache) + { + $this->cache = $cache; + } + + /** + * 获取当前的缓存对象 + * @access public + * @return CacheInterface|null + */ + public function getCache() + { + return $this->cache; + } + + /** + * 获取数据库的配置参数 + * @access public + * @param string $config 配置名称 + * @return mixed + */ + public function getConfig(string $config = '') + { + if ('' === $config) { + return $this->config; + } + + return $this->config[$config] ?? null; + } + + /** + * 数据库SQL监控 + * @access protected + * @param string $sql 执行的SQL语句 留空自动获取 + * @param bool $master 主从标记 + * @return void + */ + protected function trigger(string $sql = '', bool $master = false): void + { + $listen = $this->db->getListen(); + if (empty($listen)) { + $listen[] = function ($sql, $time, $master) { + if (0 === strpos($sql, 'CONNECT:')) { + $this->db->log($sql); + return; + } + + // 记录SQL + if (is_bool($master)) { + // 分布式记录当前操作的主从 + $master = $master ? 'master|' : 'slave|'; + } else { + $master = ''; + } + + $this->db->log($sql . ' [ ' . $master . 'RunTime:' . $time . 's ]'); + }; + } + + $runtime = number_format((microtime(true) - $this->queryStartTime), 6); + $sql = $sql ?: $this->getLastsql(); + + if (empty($this->config['deploy'])) { + $master = null; + } + + foreach ($listen as $callback) { + if (is_callable($callback)) { + $callback($sql, $runtime, $master); + } + } + } + + /** + * 缓存数据 + * @access protected + * @param CacheItem $cacheItem 缓存Item + */ + protected function cacheData(CacheItem $cacheItem) + { + if ($cacheItem->getTag() && method_exists($this->cache, 'tag')) { + $this->cache->tag($cacheItem->getTag())->set($cacheItem->getKey(), $cacheItem->get(), $cacheItem->getExpire()); + } else { + $this->cache->set($cacheItem->getKey(), $cacheItem->get(), $cacheItem->getExpire()); + } + } + + /** + * 分析缓存Key + * @access protected + * @param BaseQuery $query 查询对象 + * @param string $method 查询方法 + * @return string + */ + protected function getCacheKey(BaseQuery $query, string $method = ''): string + { + if (!empty($query->getOptions('key')) && empty($method)) { + $key = 'think_' . $this->getConfig('database') . '.' . $query->getTable() . '|' . $query->getOptions('key'); + } else { + $key = $query->getQueryGuid(); + } + + return $key; + } + + /** + * 分析缓存 + * @access protected + * @param BaseQuery $query 查询对象 + * @param array $cache 缓存信息 + * @param string $method 查询方法 + * @return CacheItem + */ + protected function parseCache(BaseQuery $query, array $cache, string $method = ''): CacheItem + { + [$key, $expire, $tag] = $cache; + + if ($key instanceof CacheItem) { + $cacheItem = $key; + } else { + if (true === $key) { + $key = $this->getCacheKey($query, $method); + } + + $cacheItem = new CacheItem($key); + $cacheItem->expire($expire); + $cacheItem->tag($tag); + } + + return $cacheItem; + } + + /** + * 获取返回或者影响的记录数 + * @access public + * @return integer + */ + public function getNumRows(): int + { + return $this->numRows; + } + + /** + * 析构方法 + * @access public + */ + public function __destruct() + { + // 关闭连接 + $this->close(); + } +} diff --git a/vendor/topthink/think-orm/src/db/ConnectionInterface.php b/vendor/topthink/think-orm/src/db/ConnectionInterface.php new file mode 100644 index 0000000..18fe131 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/ConnectionInterface.php @@ -0,0 +1,190 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db; + +use Psr\SimpleCache\CacheInterface; +use think\DbManager; + +/** + * Connection interface + */ +interface ConnectionInterface +{ + /** + * 获取当前连接器类对应的Query类 + * @access public + * @return string + */ + public function getQueryClass(): string; + + /** + * 指定表名开始查询 + * @param $table + * @return BaseQuery + */ + public function table($table); + + /** + * 指定表名开始查询(不带前缀) + * @param $name + * @return BaseQuery + */ + public function name($name); + + /** + * 连接数据库方法 + * @access public + * @param array $config 接参数 + * @param integer $linkNum 连接序号 + * @return mixed + */ + public function connect(array $config = [], $linkNum = 0); + + /** + * 设置当前的数据库Db对象 + * @access public + * @param DbManager $db + * @return void + */ + public function setDb(DbManager $db); + + /** + * 设置当前的缓存对象 + * @access public + * @param CacheInterface $cache + * @return void + */ + public function setCache(CacheInterface $cache); + + /** + * 获取数据库的配置参数 + * @access public + * @param string $config 配置名称 + * @return mixed + */ + public function getConfig(string $config = ''); + + /** + * 关闭数据库(或者重新连接) + * @access public + * @return $this + */ + public function close(); + + /** + * 查找单条记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return array + */ + public function find(BaseQuery $query): array; + + /** + * 查找记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return array + */ + public function select(BaseQuery $query): array; + + /** + * 插入记录 + * @access public + * @param BaseQuery $query 查询对象 + * @param boolean $getLastInsID 返回自增主键 + * @return mixed + */ + public function insert(BaseQuery $query, bool $getLastInsID = false); + + /** + * 批量插入记录 + * @access public + * @param BaseQuery $query 查询对象 + * @param mixed $dataSet 数据集 + * @return integer + */ + public function insertAll(BaseQuery $query, array $dataSet = []): int; + + /** + * 更新记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return integer + */ + public function update(BaseQuery $query): int; + + /** + * 删除记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return int + */ + public function delete(BaseQuery $query): int; + + /** + * 得到某个字段的值 + * @access public + * @param BaseQuery $query 查询对象 + * @param string $field 字段名 + * @param mixed $default 默认值 + * @return mixed + */ + public function value(BaseQuery $query, string $field, $default = null); + + /** + * 得到某个列的数组 + * @access public + * @param BaseQuery $query 查询对象 + * @param string|array $column 字段名 多个字段用逗号分隔 + * @param string $key 索引 + * @return array + */ + public function column(BaseQuery $query, $column, string $key = ''): array; + + /** + * 执行数据库事务 + * @access public + * @param callable $callback 数据操作方法回调 + * @return mixed + */ + public function transaction(callable $callback); + + /** + * 启动事务 + * @access public + * @return void + */ + public function startTrans(); + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return void + */ + public function commit(); + + /** + * 事务回滚 + * @access public + * @return void + */ + public function rollback(); + + /** + * 获取最近一次查询的sql语句 + * @access public + * @return string + */ + public function getLastSql(): string; + +} diff --git a/vendor/topthink/think-orm/src/db/Fetch.php b/vendor/topthink/think-orm/src/db/Fetch.php new file mode 100644 index 0000000..16caed2 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/Fetch.php @@ -0,0 +1,494 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db; + +use think\db\exception\DbException as Exception; +use think\helper\Str; + +/** + * SQL获取类 + */ +class Fetch +{ + /** + * 查询对象 + * @var Query + */ + protected $query; + + /** + * Connection对象 + * @var Connection + */ + protected $connection; + + /** + * Builder对象 + * @var Builder + */ + protected $builder; + + /** + * 创建一个查询SQL获取对象 + * + * @param Query $query 查询对象 + */ + public function __construct(Query $query) + { + $this->query = $query; + $this->connection = $query->getConnection(); + $this->builder = $this->connection->getBuilder(); + } + + /** + * 聚合查询 + * @access protected + * @param string $aggregate 聚合方法 + * @param string $field 字段名 + * @return string + */ + protected function aggregate(string $aggregate, string $field): string + { + $this->query->parseOptions(); + + $field = $aggregate . '(' . $this->builder->parseKey($this->query, $field) . ') AS think_' . strtolower($aggregate); + + return $this->value($field, 0, false); + } + + /** + * 得到某个字段的值 + * @access public + * @param string $field 字段名 + * @param mixed $default 默认值 + * @param bool $one + * @return string + */ + public function value(string $field, $default = null, bool $one = true): string + { + $options = $this->query->parseOptions(); + + if (isset($options['field'])) { + $this->query->removeOption('field'); + } + + $this->query->setOption('field', (array) $field); + + // 生成查询SQL + $sql = $this->builder->select($this->query, $one); + + if (isset($options['field'])) { + $this->query->setOption('field', $options['field']); + } else { + $this->query->removeOption('field'); + } + + return $this->fetch($sql); + } + + /** + * 得到某个列的数组 + * @access public + * @param string $field 字段名 多个字段用逗号分隔 + * @param string $key 索引 + * @return string + */ + public function column(string $field, string $key = ''): string + { + $options = $this->query->parseOptions(); + + if (isset($options['field'])) { + $this->query->removeOption('field'); + } + + if ($key && '*' != $field) { + $field = $key . ',' . $field; + } + + $field = array_map('trim', explode(',', $field)); + + $this->query->setOption('field', $field); + + // 生成查询SQL + $sql = $this->builder->select($this->query); + + if (isset($options['field'])) { + $this->query->setOption('field', $options['field']); + } else { + $this->query->removeOption('field'); + } + + return $this->fetch($sql); + } + + /** + * 插入记录 + * @access public + * @param array $data 数据 + * @return string + */ + public function insert(array $data = []): string + { + $options = $this->query->parseOptions(); + + if (!empty($data)) { + $this->query->setOption('data', $data); + } + + $sql = $this->builder->insert($this->query); + + return $this->fetch($sql); + } + + /** + * 插入记录并获取自增ID + * @access public + * @param array $data 数据 + * @return string + */ + public function insertGetId(array $data = []): string + { + return $this->insert($data); + } + + /** + * 保存数据 自动判断insert或者update + * @access public + * @param array $data 数据 + * @param bool $forceInsert 是否强制insert + * @return string + */ + public function save(array $data = [], bool $forceInsert = false): string + { + if ($forceInsert) { + return $this->insert($data); + } + + $data = array_merge($this->query->getOptions('data') ?: [], $data); + + $this->query->setOption('data', $data); + + if ($this->query->getOptions('where')) { + $isUpdate = true; + } else { + $isUpdate = $this->query->parseUpdateData($data); + } + + return $isUpdate ? $this->update() : $this->insert(); + } + + /** + * 批量插入记录 + * @access public + * @param array $dataSet 数据集 + * @param integer $limit 每次写入数据限制 + * @return string + */ + public function insertAll(array $dataSet = [], int $limit = null): string + { + $options = $this->query->parseOptions(); + + if (empty($dataSet)) { + $dataSet = $options['data']; + } + + if (empty($limit) && !empty($options['limit'])) { + $limit = $options['limit']; + } + + if ($limit) { + $array = array_chunk($dataSet, $limit, true); + $fetchSql = []; + foreach ($array as $item) { + $sql = $this->builder->insertAll($this->query, $item); + $bind = $this->query->getBind(); + + $fetchSql[] = $this->connection->getRealSql($sql, $bind); + } + + return implode(';', $fetchSql); + } + + $sql = $this->builder->insertAll($this->query, $dataSet); + + return $this->fetch($sql); + } + + /** + * 通过Select方式插入记录 + * @access public + * @param array $fields 要插入的数据表字段名 + * @param string $table 要插入的数据表名 + * @return string + */ + public function selectInsert(array $fields, string $table): string + { + $this->query->parseOptions(); + + $sql = $this->builder->selectInsert($this->query, $fields, $table); + + return $this->fetch($sql); + } + + /** + * 更新记录 + * @access public + * @param mixed $data 数据 + * @return string + */ + public function update(array $data = []): string + { + $options = $this->query->parseOptions(); + + $data = !empty($data) ? $data : $options['data']; + + $pk = $this->query->getPk(); + + if (empty($options['where'])) { + // 如果存在主键数据 则自动作为更新条件 + if (is_string($pk) && isset($data[$pk])) { + $this->query->where($pk, '=', $data[$pk]); + unset($data[$pk]); + } elseif (is_array($pk)) { + // 增加复合主键支持 + foreach ($pk as $field) { + if (isset($data[$field])) { + $this->query->where($field, '=', $data[$field]); + } else { + // 如果缺少复合主键数据则不执行 + throw new Exception('miss complex primary data'); + } + unset($data[$field]); + } + } + + if (empty($this->query->getOptions('where'))) { + // 如果没有任何更新条件则不执行 + throw new Exception('miss update condition'); + } + } + + // 更新数据 + $this->query->setOption('data', $data); + + // 生成UPDATE SQL语句 + $sql = $this->builder->update($this->query); + + return $this->fetch($sql); + } + + /** + * 删除记录 + * @access public + * @param mixed $data 表达式 true 表示强制删除 + * @return string + */ + public function delete($data = null): string + { + $options = $this->query->parseOptions(); + + if (!is_null($data) && true !== $data) { + // AR模式分析主键条件 + $this->query->parsePkWhere($data); + } + + if (!empty($options['soft_delete'])) { + // 软删除 + [$field, $condition] = $options['soft_delete']; + if ($condition) { + $this->query->setOption('soft_delete', null); + $this->query->setOption('data', [$field => $condition]); + // 生成删除SQL语句 + $sql = $this->builder->delete($this->query); + return $this->fetch($sql); + } + } + + // 生成删除SQL语句 + $sql = $this->builder->delete($this->query); + + return $this->fetch($sql); + } + + /** + * 查找记录 返回SQL + * @access public + * @param mixed $data + * @return string + */ + public function select($data = null): string + { + $this->query->parseOptions(); + + if (!is_null($data)) { + // 主键条件分析 + $this->query->parsePkWhere($data); + } + + // 生成查询SQL + $sql = $this->builder->select($this->query); + + return $this->fetch($sql); + } + + /** + * 查找单条记录 返回SQL语句 + * @access public + * @param mixed $data + * @return string + */ + public function find($data = null): string + { + $this->query->parseOptions(); + + if (!is_null($data)) { + // AR模式分析主键条件 + $this->query->parsePkWhere($data); + } + + // 生成查询SQL + $sql = $this->builder->select($this->query, true); + + // 获取实际执行的SQL语句 + return $this->fetch($sql); + } + + /** + * 查找多条记录 如果不存在则抛出异常 + * @access public + * @param mixed $data + * @return string + */ + public function selectOrFail($data = null): string + { + return $this->select($data); + } + + /** + * 查找单条记录 如果不存在则抛出异常 + * @access public + * @param mixed $data + * @return string + */ + public function findOrFail($data = null): string + { + return $this->find($data); + } + + /** + * 查找单条记录 不存在返回空数据(或者空模型) + * @access public + * @param mixed $data 数据 + * @return string + */ + public function findOrEmpty($data = null) + { + return $this->find($data); + } + + /** + * 获取实际的SQL语句 + * @access public + * @param string $sql + * @return string + */ + public function fetch(string $sql): string + { + $bind = $this->query->getBind(); + + return $this->connection->getRealSql($sql, $bind); + } + + /** + * COUNT查询 + * @access public + * @param string $field 字段名 + * @return string + */ + public function count(string $field = '*'): string + { + $options = $this->query->parseOptions(); + + if (!empty($options['group'])) { + // 支持GROUP + $bind = $this->query->getBind(); + $subSql = $this->query->options($options)->field('count(' . $field . ') AS think_count')->bind($bind)->buildSql(); + + $query = $this->query->newQuery()->table([$subSql => '_group_count_']); + + return $query->fetchsql()->aggregate('COUNT', '*'); + } else { + return $this->aggregate('COUNT', $field); + } + } + + /** + * SUM查询 + * @access public + * @param string $field 字段名 + * @return string + */ + public function sum(string $field): string + { + return $this->aggregate('SUM', $field); + } + + /** + * MIN查询 + * @access public + * @param string $field 字段名 + * @return string + */ + public function min(string $field): string + { + return $this->aggregate('MIN', $field); + } + + /** + * MAX查询 + * @access public + * @param string $field 字段名 + * @return string + */ + public function max(string $field): string + { + return $this->aggregate('MAX', $field); + } + + /** + * AVG查询 + * @access public + * @param string $field 字段名 + * @return string + */ + public function avg(string $field): string + { + return $this->aggregate('AVG', $field); + } + + public function __call($method, $args) + { + if (strtolower(substr($method, 0, 5)) == 'getby') { + // 根据某个字段获取记录 + $field = Str::snake(substr($method, 5)); + return $this->where($field, '=', $args[0])->find(); + } elseif (strtolower(substr($method, 0, 10)) == 'getfieldby') { + // 根据某个字段获取记录的某个值 + $name = Str::snake(substr($method, 10)); + return $this->where($name, '=', $args[0])->value($args[1]); + } + + $result = call_user_func_array([$this->query, $method], $args); + return $result === $this->query ? $this : $result; + } +} diff --git a/vendor/topthink/think-orm/src/db/Mongo.php b/vendor/topthink/think-orm/src/db/Mongo.php new file mode 100644 index 0000000..5e8a09a --- /dev/null +++ b/vendor/topthink/think-orm/src/db/Mongo.php @@ -0,0 +1,712 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); +namespace think\db; + +use MongoDB\Driver\Command; +use MongoDB\Driver\Cursor; +use MongoDB\Driver\Exception\AuthenticationException; +use MongoDB\Driver\Exception\ConnectionException; +use MongoDB\Driver\Exception\InvalidArgumentException; +use MongoDB\Driver\Exception\RuntimeException; +use MongoDB\Driver\ReadPreference; +use MongoDB\Driver\WriteConcern; +use think\db\exception\DbException as Exception; +use think\Paginator; + +class Mongo extends BaseQuery +{ + /** + * 当前数据库连接对象 + * @var \think\db\connector\Mongo + */ + protected $connection; + + /** + * 执行指令 返回数据集 + * @access public + * @param Command $command 指令 + * @param string $dbName + * @param ReadPreference $readPreference readPreference + * @param string|array $typeMap 指定返回的typeMap + * @return mixed + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + */ + public function command(Command $command, string $dbName = '', ReadPreference $readPreference = null, $typeMap = null) + { + return $this->connection->command($command, $dbName, $readPreference, $typeMap); + } + + /** + * 执行command + * @access public + * @param string|array|object $command 指令 + * @param mixed $extra 额外参数 + * @param string $db 数据库名 + * @return array + */ + public function cmd($command, $extra = null, string $db = ''): array + { + $this->parseOptions(); + return $this->connection->cmd($this, $command, $extra, $db); + } + + /** + * 指定distinct查询 + * @access public + * @param string $field 字段名 + * @return array + */ + public function getDistinct(string $field) + { + $result = $this->cmd('distinct', $field); + return $result[0]['values']; + } + + /** + * 获取数据库的所有collection + * @access public + * @param string $db 数据库名称 留空为当前数据库 + * @throws Exception + */ + public function listCollections(string $db = '') + { + $cursor = $this->cmd('listCollections', null, $db); + $result = []; + foreach ($cursor as $collection) { + $result[] = $collection['name']; + } + + return $result; + } + + /** + * COUNT查询 + * @access public + * @param string $field 字段名 + * @return integer + */ + public function count(string $field = null): int + { + $result = $this->cmd('count'); + + return $result[0]['n']; + } + + /** + * 聚合查询 + * @access public + * @param string $aggregate 聚合指令 + * @param string $field 字段名 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + public function aggregate(string $aggregate, $field, bool $force = false) + { + $result = $this->cmd('aggregate', [strtolower($aggregate), $field]); + $value = $result[0]['aggregate'] ?? 0; + + if ($force) { + $value += 0; + } + + return $value; + } + + /** + * 多聚合操作 + * + * @param array $aggregate 聚合指令, 可以聚合多个参数, 如 ['sum' => 'field1', 'avg' => 'field2'] + * @param array $groupBy 类似mysql里面的group字段, 可以传入多个字段, 如 ['field_a', 'field_b', 'field_c'] + * @return array 查询结果 + */ + public function multiAggregate(array $aggregate, array $groupBy): array + { + $result = $this->cmd('multiAggregate', [$aggregate, $groupBy]); + + foreach ($result as &$row) { + if (isset($row['_id']) && !empty($row['_id'])) { + foreach ($row['_id'] as $k => $v) { + $row[$k] = $v; + } + unset($row['_id']); + } + } + + return $result; + } + + /** + * 字段值增长 + * @access public + * @param string $field 字段名 + * @param float $step 增长值 + * @return $this + */ + public function inc(string $field, float $step = 1) + { + $this->options['data'][$field] = ['$inc', $step]; + + return $this; + } + + /** + * 字段值减少 + * @access public + * @param string $field 字段名 + * @param float $step 减少值 + * @return $this + */ + public function dec(string $field, float $step = 1) + { + return $this->inc($field, -1 * $step); + } + + /** + * 指定当前操作的Collection + * @access public + * @param string $table 表名 + * @return $this + */ + public function table($table) + { + $this->options['table'] = $table; + + return $this; + } + + /** + * table方法的别名 + * @access public + * @param string $collection + * @return $this + */ + public function collection(string $collection) + { + return $this->table($collection); + } + + /** + * 设置typeMap + * @access public + * @param string|array $typeMap + * @return $this + */ + public function typeMap($typeMap) + { + $this->options['typeMap'] = $typeMap; + return $this; + } + + /** + * awaitData + * @access public + * @param bool $awaitData + * @return $this + */ + public function awaitData(bool $awaitData) + { + $this->options['awaitData'] = $awaitData; + return $this; + } + + /** + * batchSize + * @access public + * @param integer $batchSize + * @return $this + */ + public function batchSize(int $batchSize) + { + $this->options['batchSize'] = $batchSize; + return $this; + } + + /** + * exhaust + * @access public + * @param bool $exhaust + * @return $this + */ + public function exhaust(bool $exhaust) + { + $this->options['exhaust'] = $exhaust; + return $this; + } + + /** + * 设置modifiers + * @access public + * @param array $modifiers + * @return $this + */ + public function modifiers(array $modifiers) + { + $this->options['modifiers'] = $modifiers; + return $this; + } + + /** + * 设置noCursorTimeout + * @access public + * @param bool $noCursorTimeout + * @return $this + */ + public function noCursorTimeout(bool $noCursorTimeout) + { + $this->options['noCursorTimeout'] = $noCursorTimeout; + return $this; + } + + /** + * 设置oplogReplay + * @access public + * @param bool $oplogReplay + * @return $this + */ + public function oplogReplay(bool $oplogReplay) + { + $this->options['oplogReplay'] = $oplogReplay; + return $this; + } + + /** + * 设置partial + * @access public + * @param bool $partial + * @return $this + */ + public function partial(bool $partial) + { + $this->options['partial'] = $partial; + return $this; + } + + /** + * maxTimeMS + * @access public + * @param string $maxTimeMS + * @return $this + */ + public function maxTimeMS(string $maxTimeMS) + { + $this->options['maxTimeMS'] = $maxTimeMS; + return $this; + } + + /** + * collation + * @access public + * @param array $collation + * @return $this + */ + public function collation(array $collation) + { + $this->options['collation'] = $collation; + return $this; + } + + /** + * 设置是否REPLACE + * @access public + * @param bool $replace 是否使用REPLACE写入数据 + * @return $this + */ + public function replace(bool $replace = true) + { + return $this; + } + + /** + * 设置返回字段 + * @access public + * @param mixed $field 字段信息 + * @return $this + */ + public function field($field) + { + if (empty($field) || '*' == $field) { + return $this; + } + + if (is_string($field)) { + $field = array_map('trim', explode(',', $field)); + } + + $projection = []; + foreach ($field as $key => $val) { + if (is_numeric($key)) { + $projection[$val] = 1; + } else { + $projection[$key] = $val; + } + } + + $this->options['projection'] = $projection; + + return $this; + } + + /** + * 指定要排除的查询字段 + * @access public + * @param array|string $field 要排除的字段 + * @return $this + */ + public function withoutField($field) + { + if (empty($field) || '*' == $field) { + return $this; + } + + if (is_string($field)) { + $field = array_map('trim', explode(',', $field)); + } + + $projection = []; + foreach ($field as $key => $val) { + if (is_numeric($key)) { + $projection[$val] = 0; + } else { + $projection[$key] = $val; + } + } + + $this->options['projection'] = $projection; + return $this; + } + + /** + * 设置skip + * @access public + * @param integer $skip + * @return $this + */ + public function skip(int $skip) + { + $this->options['skip'] = $skip; + return $this; + } + + /** + * 设置slaveOk + * @access public + * @param bool $slaveOk + * @return $this + */ + public function slaveOk(bool $slaveOk) + { + $this->options['slaveOk'] = $slaveOk; + return $this; + } + + /** + * 指定查询数量 + * @access public + * @param int $offset 起始位置 + * @param int $length 查询数量 + * @return $this + */ + public function limit(int $offset, int $length = null) + { + if (is_null($length)) { + $length = $offset; + $offset = 0; + } + + $this->options['skip'] = $offset; + $this->options['limit'] = $length; + + return $this; + } + + /** + * 设置sort + * @access public + * @param array|string $field + * @param string $order + * @return $this + */ + public function order($field, string $order = '') + { + if (is_array($field)) { + $this->options['sort'] = $field; + } else { + $this->options['sort'][$field] = 'asc' == strtolower($order) ? 1 : -1; + } + return $this; + } + + /** + * 设置tailable + * @access public + * @param bool $tailable + * @return $this + */ + public function tailable(bool $tailable) + { + $this->options['tailable'] = $tailable; + return $this; + } + + /** + * 设置writeConcern对象 + * @access public + * @param WriteConcern $writeConcern + * @return $this + */ + public function writeConcern(WriteConcern $writeConcern) + { + $this->options['writeConcern'] = $writeConcern; + return $this; + } + + /** + * 获取当前数据表的主键 + * @access public + * @return string|array + */ + public function getPk() + { + return $this->pk ?: $this->connection->getConfig('pk'); + } + + /** + * 执行查询但只返回Cursor对象 + * @access public + * @return Cursor + */ + public function getCursor(): Cursor + { + $this->parseOptions(); + + return $this->connection->getCursor($this); + } + + /** + * 获取当前的查询标识 + * @access public + * @param mixed $data 要序列化的数据 + * @return string + */ + public function getQueryGuid($data = null): string + { + return md5($this->getConfig('database') . serialize(var_export($data ?: $this->options, true))); + } + + /** + * 分页查询 + * @access public + * @param int|array $listRows 每页数量 数组表示配置参数 + * @param int|bool $simple 是否简洁模式或者总记录数 + * @return Paginator + * @throws Exception + */ + public function paginate($listRows = null, $simple = false): Paginator + { + if (is_int($simple)) { + $total = $simple; + $simple = false; + } + + $defaultConfig = [ + 'query' => [], //url额外参数 + 'fragment' => '', //url锚点 + 'var_page' => 'page', //分页变量 + 'list_rows' => 15, //每页数量 + ]; + + if (is_array($listRows)) { + $config = array_merge($defaultConfig, $listRows); + $listRows = intval($config['list_rows']); + } else { + $config = $defaultConfig; + $listRows = intval($listRows ?: $config['list_rows']); + } + + $page = isset($config['page']) ? (int) $config['page'] : Paginator::getCurrentPage($config['var_page']); + + $page = $page < 1 ? 1 : $page; + + $config['path'] = $config['path'] ?? Paginator::getCurrentPath(); + + if (!isset($total) && !$simple) { + $options = $this->getOptions(); + + unset($this->options['order'], $this->options['limit'], $this->options['page'], $this->options['field']); + + $total = $this->count(); + $results = $this->options($options)->page($page, $listRows)->select(); + } elseif ($simple) { + $results = $this->limit(($page - 1) * $listRows, $listRows + 1)->select(); + $total = null; + } else { + $results = $this->page($page, $listRows)->select(); + } + + $this->removeOption('limit'); + $this->removeOption('page'); + + return Paginator::make($results, $listRows, $page, $total, $simple, $config); + } + + /** + * 分批数据返回处理 + * @access public + * @param integer $count 每次处理的数据数量 + * @param callable $callback 处理回调方法 + * @param string|array $column 分批处理的字段名 + * @param string $order 字段排序 + * @return bool + * @throws Exception + */ + public function chunk(int $count, callable $callback, $column = null, string $order = 'asc'): bool + { + $options = $this->getOptions(); + $column = $column ?: $this->getPk(); + + if (isset($options['order'])) { + unset($options['order']); + } + + if (is_array($column)) { + $times = 1; + $query = $this->options($options)->page($times, $count); + } else { + $query = $this->options($options)->limit($count); + + if (strpos($column, '.')) { + [$alias, $key] = explode('.', $column); + } else { + $key = $column; + } + } + + $resultSet = $query->order($column, $order)->select(); + + while (count($resultSet) > 0) { + if (false === call_user_func($callback, $resultSet)) { + return false; + } + + if (isset($times)) { + $times++; + $query = $this->options($options)->page($times, $count); + } else { + $end = $resultSet->pop(); + $lastId = is_array($end) ? $end[$key] : $end->getData($key); + + $query = $this->options($options) + ->limit($count) + ->where($column, 'asc' == strtolower($order) ? '>' : '<', $lastId); + } + + $resultSet = $query->order($column, $order)->select(); + } + + return true; + } + + /** + * 分析表达式(可用于查询或者写入操作) + * @access public + * @return array + */ + public function parseOptions(): array + { + $options = $this->options; + + // 获取数据表 + if (empty($options['table'])) { + $options['table'] = $this->getTable(); + } + + foreach (['where', 'data'] as $name) { + if (!isset($options[$name])) { + $options[$name] = []; + } + } + + $modifiers = empty($options['modifiers']) ? [] : $options['modifiers']; + if (isset($options['comment'])) { + $modifiers['$comment'] = $options['comment']; + } + + if (isset($options['maxTimeMS'])) { + $modifiers['$maxTimeMS'] = $options['maxTimeMS']; + } + + if (!empty($modifiers)) { + $options['modifiers'] = $modifiers; + } + + if (!isset($options['projection'])) { + $options['projection'] = []; + } + + if (!isset($options['typeMap'])) { + $options['typeMap'] = $this->getConfig('type_map'); + } + + if (!isset($options['limit'])) { + $options['limit'] = 0; + } + + foreach (['master', 'fetch_sql', 'fetch_cursor'] as $name) { + if (!isset($options[$name])) { + $options[$name] = false; + } + } + + if (isset($options['page'])) { + // 根据页数计算limit + [$page, $listRows] = $options['page']; + + $page = $page > 0 ? $page : 1; + $listRows = $listRows > 0 ? $listRows : (is_numeric($options['limit']) ? $options['limit'] : 20); + $offset = $listRows * ($page - 1); + $options['skip'] = intval($offset); + $options['limit'] = intval($listRows); + } + + $this->options = $options; + + return $options; + } + + /** + * 获取字段类型信息 + * @access public + * @return array + */ + public function getFieldsType(): array + { + if (!empty($this->options['field_type'])) { + return $this->options['field_type']; + } + + return []; + } + + /** + * 获取字段类型信息 + * @access public + * @param string $field 字段名 + * @return string|null + */ + public function getFieldType(string $field) + { + $fieldType = $this->getFieldsType(); + + return $fieldType[$field] ?? null; + } +} diff --git a/vendor/topthink/think-orm/src/db/PDOConnection.php b/vendor/topthink/think-orm/src/db/PDOConnection.php new file mode 100644 index 0000000..1a3d4a2 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/PDOConnection.php @@ -0,0 +1,1846 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db; + +use Closure; +use PDO; +use PDOStatement; +use think\db\exception\BindParamException; +use think\db\exception\DbEventException; +use think\db\exception\DbException; +use think\db\exception\PDOException; +use think\Model; + +/** + * 数据库连接基础类 + * @property PDO[] $links + * @property PDO $linkID + * @property PDO $linkRead + * @property PDO $linkWrite + */ +abstract class PDOConnection extends Connection +{ + const PARAM_FLOAT = 21; + + /** + * 数据库连接参数配置 + * @var array + */ + protected $config = [ + // 数据库类型 + 'type' => '', + // 服务器地址 + 'hostname' => '', + // 数据库名 + 'database' => '', + // 用户名 + 'username' => '', + // 密码 + 'password' => '', + // 端口 + 'hostport' => '', + // 连接dsn + 'dsn' => '', + // 数据库连接参数 + 'params' => [], + // 数据库编码默认采用utf8 + 'charset' => 'utf8', + // 数据库表前缀 + 'prefix' => '', + // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) + 'deploy' => 0, + // 数据库读写是否分离 主从式有效 + 'rw_separate' => false, + // 读写分离后 主服务器数量 + 'master_num' => 1, + // 指定从服务器序号 + 'slave_no' => '', + // 模型写入后自动读取主服务器 + 'read_master' => false, + // 是否严格检查字段是否存在 + 'fields_strict' => true, + // 开启字段缓存 + 'fields_cache' => false, + // 监听SQL + 'trigger_sql' => true, + // Builder类 + 'builder' => '', + // Query类 + 'query' => '', + // 是否需要断线重连 + 'break_reconnect' => false, + // 断线标识字符串 + 'break_match_str' => [], + ]; + + /** + * PDO操作实例 + * @var PDOStatement + */ + protected $PDOStatement; + + /** + * 当前SQL指令 + * @var string + */ + protected $queryStr = ''; + + /** + * 事务指令数 + * @var int + */ + protected $transTimes = 0; + + /** + * 重连次数 + * @var int + */ + protected $reConnectTimes = 0; + + /** + * 查询结果类型 + * @var int + */ + protected $fetchType = PDO::FETCH_ASSOC; + + /** + * 字段属性大小写 + * @var int + */ + protected $attrCase = PDO::CASE_LOWER; + + /** + * 数据表信息 + * @var array + */ + protected $info = []; + + /** + * 查询开始时间 + * @var float + */ + protected $queryStartTime; + + /** + * PDO连接参数 + * @var array + */ + protected $params = [ + PDO::ATTR_CASE => PDO::CASE_NATURAL, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, + PDO::ATTR_STRINGIFY_FETCHES => false, + PDO::ATTR_EMULATE_PREPARES => false, + ]; + + /** + * 参数绑定类型映射 + * @var array + */ + protected $bindType = [ + 'string' => PDO::PARAM_STR, + 'str' => PDO::PARAM_STR, + 'integer' => PDO::PARAM_INT, + 'int' => PDO::PARAM_INT, + 'boolean' => PDO::PARAM_BOOL, + 'bool' => PDO::PARAM_BOOL, + 'float' => self::PARAM_FLOAT, + 'datetime' => PDO::PARAM_STR, + 'timestamp' => PDO::PARAM_STR, + ]; + + /** + * 服务器断线标识字符 + * @var array + */ + protected $breakMatchStr = [ + 'server has gone away', + 'no connection to the server', + 'Lost connection', + 'is dead or not enabled', + 'Error while sending', + 'decryption failed or bad record mac', + 'server closed the connection unexpectedly', + 'SSL connection has been closed unexpectedly', + 'Error writing data to the connection', + 'Resource deadlock avoided', + 'failed with errno', + 'child connection forced to terminate due to client_idle_limit', + 'query_wait_timeout', + 'reset by peer', + 'Physical connection is not usable', + 'TCP Provider: Error code 0x68', + 'ORA-03114', + 'Packets out of order. Expected', + 'Adaptive Server connection failed', + 'Communication link failure', + 'connection is no longer usable', + 'Login timeout expired', + 'SQLSTATE[HY000] [2002] Connection refused', + 'running with the --read-only option so it cannot execute this statement', + 'The connection is broken and recovery is not possible. The connection is marked by the client driver as unrecoverable. No attempt was made to restore the connection.', + 'SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed: Try again', + 'SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed: Name or service not known', + 'SQLSTATE[HY000]: General error: 7 SSL SYSCALL error: EOF detected', + 'SQLSTATE[HY000] [2002] Connection timed out', + 'SSL: Connection timed out', + 'SQLSTATE[HY000]: General error: 1105 The last transaction was aborted due to Seamless Scaling. Please retry.', + ]; + + /** + * 绑定参数 + * @var array + */ + protected $bind = []; + + /** + * 获取当前连接器类对应的Query类 + * @access public + * @return string + */ + public function getQueryClass(): string + { + return $this->getConfig('query') ?: Query::class; + } + + /** + * 获取当前连接器类对应的Builder类 + * @access public + * @return string + */ + public function getBuilderClass(): string + { + return $this->getConfig('builder') ?: '\\think\\db\\builder\\' . ucfirst($this->getConfig('type')); + } + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + abstract protected function parseDsn(array $config): string; + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName 数据表名称 + * @return array + */ + abstract public function getFields(string $tableName): array; + + /** + * 取得数据库的表信息 + * @access public + * @param string $dbName 数据库名称 + * @return array + */ + abstract public function getTables(string $dbName = ''): array; + + /** + * 对返数据表字段信息进行大小写转换出来 + * @access public + * @param array $info 字段信息 + * @return array + */ + public function fieldCase(array $info): array + { + // 字段大小写转换 + switch ($this->attrCase) { + case PDO::CASE_LOWER: + $info = array_change_key_case($info); + break; + case PDO::CASE_UPPER: + $info = array_change_key_case($info, CASE_UPPER); + break; + case PDO::CASE_NATURAL: + default: + // 不做转换 + } + + return $info; + } + + /** + * 获取字段类型 + * @access protected + * @param string $type 字段类型 + * @return string + */ + protected function getFieldType(string $type): string + { + if (0 === strpos($type, 'set') || 0 === strpos($type, 'enum')) { + $result = 'string'; + } elseif (preg_match('/(double|float|decimal|real|numeric)/is', $type)) { + $result = 'float'; + } elseif (preg_match('/(int|serial|bit)/is', $type)) { + $result = 'int'; + } elseif (preg_match('/bool/is', $type)) { + $result = 'bool'; + } elseif (0 === strpos($type, 'timestamp')) { + $result = 'timestamp'; + } elseif (0 === strpos($type, 'datetime')) { + $result = 'datetime'; + } elseif (0 === strpos($type, 'date')) { + $result = 'date'; + } else { + $result = 'string'; + } + + return $result; + } + + /** + * 获取字段绑定类型 + * @access public + * @param string $type 字段类型 + * @return integer + */ + public function getFieldBindType(string $type): int + { + if (in_array($type, ['integer', 'string', 'float', 'boolean', 'bool', 'int', 'str'])) { + $bind = $this->bindType[$type]; + } elseif (0 === strpos($type, 'set') || 0 === strpos($type, 'enum')) { + $bind = PDO::PARAM_STR; + } elseif (preg_match('/(double|float|decimal|real|numeric)/is', $type)) { + $bind = self::PARAM_FLOAT; + } elseif (preg_match('/(int|serial|bit)/is', $type)) { + $bind = PDO::PARAM_INT; + } elseif (preg_match('/bool/is', $type)) { + $bind = PDO::PARAM_BOOL; + } else { + $bind = PDO::PARAM_STR; + } + + return $bind; + } + + /** + * 获取数据表信息缓存key + * @access protected + * @param string $schema 数据表名称 + * @return string + */ + protected function getSchemaCacheKey(string $schema): string + { + return $this->getConfig('hostname') . ':' . $this->getConfig('hostport') . '@' . $schema; + } + + /** + * @param string $tableName 数据表名称 + * @param bool $force 强制从数据库获取 + * @return array + */ + public function getSchemaInfo(string $tableName, $force = false) + { + if (!strpos($tableName, '.')) { + $schema = $this->getConfig('database') . '.' . $tableName; + } else { + $schema = $tableName; + } + + if (!isset($this->info[$schema]) || $force) { + // 读取字段缓存 + $cacheKey = $this->getSchemaCacheKey($schema); + $cacheField = $this->config['fields_cache'] && !empty($this->cache); + + if ($cacheField && !$force) { + $info = $this->cache->get($cacheKey); + } + + if (empty($info)) { + $info = $this->getTableFieldsInfo($tableName); + if ($cacheField) { + $this->cache->set($cacheKey, $info); + } + } + + $pk = $info['_pk'] ?? null; + $autoinc = $info['_autoinc'] ?? null; + unset($info['_pk'], $info['_autoinc']); + + $bind = []; + foreach ($info as $name => $val) { + $bind[$name] = $this->getFieldBindType($val); + } + + $this->info[$schema] = [ + 'fields' => array_keys($info), + 'type' => $info, + 'bind' => $bind, + 'pk' => $pk, + 'autoinc' => $autoinc, + ]; + } + + return $this->info[$schema]; + } + + /** + * 获取数据表信息 + * @access public + * @param mixed $tableName 数据表名 留空自动获取 + * @param string $fetch 获取信息类型 包括 fields type bind pk + * @return mixed + */ + public function getTableInfo($tableName, string $fetch = '') + { + if (is_array($tableName)) { + $tableName = key($tableName) ?: current($tableName); + } + + if (strpos($tableName, ',') || strpos($tableName, ')')) { + // 多表不获取字段信息 + return []; + } + + [$tableName] = explode(' ', $tableName); + + $info = $this->getSchemaInfo($tableName); + + return $fetch ? $info[$fetch] : $info; + } + + /** + * 获取数据表的字段信息 + * @access public + * @param string $tableName 数据表名 + * @return array + */ + public function getTableFieldsInfo(string $tableName): array + { + $fields = $this->getFields($tableName); + $info = []; + + foreach ($fields as $key => $val) { + // 记录字段类型 + $info[$key] = $this->getFieldType($val['type']); + + if (!empty($val['primary'])) { + $pk[] = $key; + } + + if (!empty($val['autoinc'])) { + $autoinc = $key; + } + } + + if (isset($pk)) { + // 设置主键 + $pk = count($pk) > 1 ? $pk : $pk[0]; + $info['_pk'] = $pk; + } + + if (isset($autoinc)) { + $info['_autoinc'] = $autoinc; + } + + return $info; + } + + /** + * 获取数据表的主键 + * @access public + * @param mixed $tableName 数据表名 + * @return string|array + */ + public function getPk($tableName) + { + return $this->getTableInfo($tableName, 'pk'); + } + + /** + * 获取数据表的自增主键 + * @access public + * @param mixed $tableName 数据表名 + * @return string + */ + public function getAutoInc($tableName) + { + return $this->getTableInfo($tableName, 'autoinc'); + } + + /** + * 获取数据表字段信息 + * @access public + * @param mixed $tableName 数据表名 + * @return array + */ + public function getTableFields($tableName): array + { + return $this->getTableInfo($tableName, 'fields'); + } + + /** + * 获取数据表字段类型 + * @access public + * @param mixed $tableName 数据表名 + * @param string $field 字段名 + * @return array|string + */ + public function getFieldsType($tableName, string $field = null) + { + $result = $this->getTableInfo($tableName, 'type'); + + if ($field && isset($result[$field])) { + return $result[$field]; + } + + return $result; + } + + /** + * 获取数据表绑定信息 + * @access public + * @param mixed $tableName 数据表名 + * @return array + */ + public function getFieldsBind($tableName): array + { + return $this->getTableInfo($tableName, 'bind'); + } + + /** + * 连接数据库方法 + * @access public + * @param array $config 连接参数 + * @param integer $linkNum 连接序号 + * @param array|bool $autoConnection 是否自动连接主数据库(用于分布式) + * @return PDO + * @throws PDOException + */ + public function connect(array $config = [], $linkNum = 0, $autoConnection = false): PDO + { + if (isset($this->links[$linkNum])) { + return $this->links[$linkNum]; + } + + if (empty($config)) { + $config = $this->config; + } else { + $config = array_merge($this->config, $config); + } + + // 连接参数 + if (isset($config['params']) && is_array($config['params'])) { + $params = $config['params'] + $this->params; + } else { + $params = $this->params; + } + + // 记录当前字段属性大小写设置 + $this->attrCase = $params[PDO::ATTR_CASE]; + + if (!empty($config['break_match_str'])) { + $this->breakMatchStr = array_merge($this->breakMatchStr, (array) $config['break_match_str']); + } + + try { + if (empty($config['dsn'])) { + $config['dsn'] = $this->parseDsn($config); + } + + $startTime = microtime(true); + + $this->links[$linkNum] = $this->createPdo($config['dsn'], $config['username'], $config['password'], $params); + + // SQL监控 + if (!empty($config['trigger_sql'])) { + $this->trigger('CONNECT:[ UseTime:' . number_format(microtime(true) - $startTime, 6) . 's ] ' . $config['dsn']); + } + + return $this->links[$linkNum]; + } catch (\PDOException $e) { + if ($autoConnection) { + $this->db->log($e->getMessage(), 'error'); + return $this->connect($autoConnection, $linkNum); + } else { + throw $e; + } + } + } + + /** + * 视图查询 + * @access public + * @param array $args + * @return BaseQuery + */ + public function view(...$args) + { + return $this->newQuery()->view(...$args); + } + + /** + * 创建PDO实例 + * @param $dsn + * @param $username + * @param $password + * @param $params + * @return PDO + */ + protected function createPdo($dsn, $username, $password, $params) + { + return new PDO($dsn, $username, $password, $params); + } + + /** + * 释放查询结果 + * @access public + */ + public function free(): void + { + $this->PDOStatement = null; + } + + /** + * 获取PDO对象 + * @access public + * @return PDO|false + */ + public function getPdo() + { + if (!$this->linkID) { + return false; + } + + return $this->linkID; + } + + /** + * 执行查询 使用生成器返回数据 + * @access public + * @param BaseQuery $query 查询对象 + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @param Model|null $model 模型对象实例 + * @param null $condition 查询条件 + * @return \Generator + * @throws DbException + */ + public function getCursor(BaseQuery $query, string $sql, array $bind = [], $model = null, $condition = null) + { + $this->queryPDOStatement($query, $sql, $bind); + + // 返回结果集 + while ($result = $this->PDOStatement->fetch($this->fetchType)) { + if ($model) { + yield $model->newInstance($result, $condition); + } else { + yield $result; + } + } + } + + /** + * 执行查询 返回数据集 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @param bool $master 主库读取 + * @return array + * @throws DbException + */ + public function query(string $sql, array $bind = [], bool $master = false): array + { + return $this->pdoQuery($this->newQuery(), $sql, $bind, $master); + } + + /** + * 执行语句 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @return int + * @throws DbException + */ + public function execute(string $sql, array $bind = []): int + { + return $this->pdoExecute($this->newQuery(), $sql, $bind, true); + } + + /** + * 执行查询 返回数据集 + * @access protected + * @param BaseQuery $query 查询对象 + * @param mixed $sql sql指令 + * @param array $bind 参数绑定 + * @param bool $master 主库读取 + * @return array + * @throws DbException + */ + protected function pdoQuery(BaseQuery $query, $sql, array $bind = [], bool $master = null): array + { + // 分析查询表达式 + $query->parseOptions(); + + if ($query->getOptions('cache')) { + // 检查查询缓存 + $cacheItem = $this->parseCache($query, $query->getOptions('cache')); + $key = $cacheItem->getKey(); + + $data = $this->cache->get($key); + + if (null !== $data) { + return $data; + } + } + + if ($sql instanceof Closure) { + $sql = $sql($query); + $bind = $query->getBind(); + } + + if (!isset($master)) { + $master = $query->getOptions('master') ? true : false; + } + + $procedure = $query->getOptions('procedure') ? true : in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']); + + $this->getPDOStatement($sql, $bind, $master, $procedure); + + $resultSet = $this->getResult($procedure); + + if (isset($cacheItem) && $resultSet) { + // 缓存数据集 + $cacheItem->set($resultSet); + $this->cacheData($cacheItem); + } + + return $resultSet; + } + + /** + * 执行查询但只返回PDOStatement对象 + * @access public + * @param BaseQuery $query 查询对象 + * @return \PDOStatement + * @throws DbException + */ + public function pdo(BaseQuery $query): PDOStatement + { + $bind = $query->getBind(); + // 生成查询SQL + $sql = $this->builder->select($query); + + return $this->queryPDOStatement($query, $sql, $bind); + } + + /** + * 执行查询但只返回PDOStatement对象 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @param bool $master 是否在主服务器读操作 + * @param bool $procedure 是否为存储过程调用 + * @return PDOStatement + * @throws DbException + */ + public function getPDOStatement(string $sql, array $bind = [], bool $master = false, bool $procedure = false): PDOStatement + { + try { + $this->initConnect($this->readMaster ?: $master); + // 记录SQL语句 + $this->queryStr = $sql; + $this->bind = $bind; + + $this->db->updateQueryTimes(); + $this->queryStartTime = microtime(true); + + // 预处理 + $this->PDOStatement = $this->linkID->prepare($sql); + + // 参数绑定 + if ($procedure) { + $this->bindParam($bind); + } else { + $this->bindValue($bind); + } + + // 执行查询 + $this->PDOStatement->execute(); + + // SQL监控 + if (!empty($this->config['trigger_sql'])) { + $this->trigger('', $master); + } + + $this->reConnectTimes = 0; + + return $this->PDOStatement; + } catch (\Throwable | \Exception $e) { + if ($this->transTimes > 0) { + // 事务活动中时不应该进行重试,应直接中断执行,防止造成污染。 + if ($this->isBreak($e)) { + // 尝试对事务计数进行重置 + $this->transTimes = 0; + } + } else { + if ($this->reConnectTimes < 4 && $this->isBreak($e)) { + ++$this->reConnectTimes; + return $this->close()->getPDOStatement($sql, $bind, $master, $procedure); + } + } + + if ($e instanceof \PDOException) { + throw new PDOException($e, $this->config, $this->getLastsql()); + } else { + throw $e; + } + } + } + + /** + * 执行语句 + * @access protected + * @param BaseQuery $query 查询对象 + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @param bool $origin 是否原生查询 + * @return int + * @throws DbException + */ + protected function pdoExecute(BaseQuery $query, string $sql, array $bind = [], bool $origin = false): int + { + if ($origin) { + $query->parseOptions(); + } + + $this->queryPDOStatement($query->master(true), $sql, $bind); + + if (!$origin && !empty($this->config['deploy']) && !empty($this->config['read_master'])) { + $this->readMaster = true; + } + + $this->numRows = $this->PDOStatement->rowCount(); + + if ($query->getOptions('cache')) { + // 清理缓存数据 + $cacheItem = $this->parseCache($query, $query->getOptions('cache')); + $key = $cacheItem->getKey(); + $tag = $cacheItem->getTag(); + + if (isset($key) && $this->cache->has($key)) { + $this->cache->delete($key); + } elseif (!empty($tag) && method_exists($this->cache, 'tag')) { + $this->cache->tag($tag)->clear(); + } + } + + return $this->numRows; + } + + /** + * @param BaseQuery $query + * @param string $sql + * @param array $bind + * @return PDOStatement + * @throws DbException + */ + protected function queryPDOStatement(BaseQuery $query, string $sql, array $bind = []): PDOStatement + { + $options = $query->getOptions(); + $master = !empty($options['master']) ? true : false; + $procedure = !empty($options['procedure']) ? true : in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']); + + return $this->getPDOStatement($sql, $bind, $master, $procedure); + } + + /** + * 查找单条记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return array + * @throws DbException + */ + public function find(BaseQuery $query): array + { + // 事件回调 + try { + $this->db->trigger('before_find', $query); + } catch (DbEventException $e) { + return []; + } + + // 执行查询 + $resultSet = $this->pdoQuery($query, function ($query) { + return $this->builder->select($query, true); + }); + + return $resultSet[0] ?? []; + } + + /** + * 使用游标查询记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return \Generator + */ + public function cursor(BaseQuery $query) + { + // 分析查询表达式 + $options = $query->parseOptions(); + + // 生成查询SQL + $sql = $this->builder->select($query); + + $condition = $options['where']['AND'] ?? null; + + // 执行查询操作 + return $this->getCursor($query, $sql, $query->getBind(), $query->getModel(), $condition); + } + + /** + * 查找记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return array + * @throws DbException + */ + public function select(BaseQuery $query): array + { + try { + $this->db->trigger('before_select', $query); + } catch (DbEventException $e) { + return []; + } + + // 执行查询操作 + return $this->pdoQuery($query, function ($query) { + return $this->builder->select($query); + }); + } + + /** + * 插入记录 + * @access public + * @param BaseQuery $query 查询对象 + * @param boolean $getLastInsID 返回自增主键 + * @return mixed + */ + public function insert(BaseQuery $query, bool $getLastInsID = false) + { + // 分析查询表达式 + $options = $query->parseOptions(); + + // 生成SQL语句 + $sql = $this->builder->insert($query); + + // 执行操作 + $result = '' == $sql ? 0 : $this->pdoExecute($query, $sql, $query->getBind()); + + if ($result) { + $sequence = $options['sequence'] ?? null; + $lastInsId = $this->getLastInsID($query, $sequence); + + $data = $options['data']; + + if ($lastInsId) { + $pk = $query->getAutoInc(); + if ($pk) { + $data[$pk] = $lastInsId; + } + } + + $query->setOption('data', $data); + + $this->db->trigger('after_insert', $query); + + if ($getLastInsID && $lastInsId) { + return $lastInsId; + } + } + + return $result; + } + + /** + * 批量插入记录 + * @access public + * @param BaseQuery $query 查询对象 + * @param mixed $dataSet 数据集 + * @param integer $limit 每次写入数据限制 + * @return integer + * @throws \Exception + * @throws \Throwable + */ + public function insertAll(BaseQuery $query, array $dataSet = [], int $limit = 0): int + { + if (!is_array(reset($dataSet))) { + return 0; + } + + $options = $query->parseOptions(); + $replace = !empty($options['replace']); + + if (0 === $limit && count($dataSet) >= 5000) { + $limit = 1000; + } + + if ($limit) { + // 分批写入 自动启动事务支持 + $this->startTrans(); + + try { + $array = array_chunk($dataSet, $limit, true); + $count = 0; + + foreach ($array as $item) { + $sql = $this->builder->insertAll($query, $item, $replace); + $count += $this->pdoExecute($query, $sql, $query->getBind()); + } + + // 提交事务 + $this->commit(); + } catch (\Exception | \Throwable $e) { + $this->rollback(); + throw $e; + } + + return $count; + } + + $sql = $this->builder->insertAll($query, $dataSet, $replace); + + return $this->pdoExecute($query, $sql, $query->getBind()); + } + + /** + * 通过Select方式插入记录 + * @access public + * @param BaseQuery $query 查询对象 + * @param array $fields 要插入的数据表字段名 + * @param string $table 要插入的数据表名 + * @return integer + * @throws PDOException + */ + public function selectInsert(BaseQuery $query, array $fields, string $table): int + { + // 分析查询表达式 + $query->parseOptions(); + + $sql = $this->builder->selectInsert($query, $fields, $table); + + return $this->pdoExecute($query, $sql, $query->getBind()); + } + + /** + * 更新记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return integer + * @throws PDOException + */ + public function update(BaseQuery $query): int + { + $query->parseOptions(); + + // 生成UPDATE SQL语句 + $sql = $this->builder->update($query); + + // 执行操作 + $result = '' == $sql ? 0 : $this->pdoExecute($query, $sql, $query->getBind()); + + if ($result) { + $this->db->trigger('after_update', $query); + } + + return $result; + } + + /** + * 删除记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return int + * @throws PDOException + */ + public function delete(BaseQuery $query): int + { + // 分析查询表达式 + $query->parseOptions(); + + // 生成删除SQL语句 + $sql = $this->builder->delete($query); + + // 执行操作 + $result = $this->pdoExecute($query, $sql, $query->getBind()); + + if ($result) { + $this->db->trigger('after_delete', $query); + } + + return $result; + } + + /** + * 得到某个字段的值 + * @access public + * @param BaseQuery $query 查询对象 + * @param string $field 字段名 + * @param mixed $default 默认值 + * @param bool $one 返回一个值 + * @return mixed + */ + public function value(BaseQuery $query, string $field, $default = null, bool $one = true) + { + $options = $query->parseOptions(); + + if (isset($options['field'])) { + $query->removeOption('field'); + } + + if (isset($options['group'])) { + $query->group(''); + } + + $query->setOption('field', (array) $field); + + if (!empty($options['cache'])) { + $cacheItem = $this->parseCache($query, $options['cache'], 'value'); + $key = $cacheItem->getKey(); + + if ($this->cache->has($key)) { + return $this->cache->get($key); + } + } + + // 生成查询SQL + $sql = $this->builder->select($query, $one); + + if (isset($options['field'])) { + $query->setOption('field', $options['field']); + } else { + $query->removeOption('field'); + } + + if (isset($options['group'])) { + $query->setOption('group', $options['group']); + } + + // 执行查询操作 + $pdo = $this->getPDOStatement($sql, $query->getBind(), $options['master']); + + $result = $pdo->fetchColumn(); + + if (isset($cacheItem)) { + // 缓存数据 + $cacheItem->set($result); + $this->cacheData($cacheItem); + } + + return false !== $result ? $result : $default; + } + + /** + * 得到某个字段的值 + * @access public + * @param BaseQuery $query 查询对象 + * @param string $aggregate 聚合方法 + * @param mixed $field 字段名 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + public function aggregate(BaseQuery $query, string $aggregate, $field, bool $force = false) + { + if (is_string($field) && 0 === stripos($field, 'DISTINCT ')) { + [$distinct, $field] = explode(' ', $field); + } + + $field = $aggregate . '(' . (!empty($distinct) ? 'DISTINCT ' : '') . $this->builder->parseKey($query, $field, true) . ') AS think_' . strtolower($aggregate); + + $result = $this->value($query, $field, 0); + + return $force ? (float) $result : $result; + } + + /** + * 得到某个列的数组 + * @access public + * @param BaseQuery $query 查询对象 + * @param string|array $column 字段名 多个字段用逗号分隔 + * @param string $key 索引 + * @return array + */ + public function column(BaseQuery $query, $column, string $key = ''): array + { + $options = $query->parseOptions(); + + if (isset($options['field'])) { + $query->removeOption('field'); + } + + if (empty($key) || trim($key) === '') { + $key = null; + } + + if (\is_string($column)) { + $column = \trim($column); + if ('*' !== $column) { + $column = \array_map('\trim', \explode(',', $column)); + } + } elseif (\is_array($column)) { + if (\in_array('*', $column)) { + $column = '*'; + } + } else { + throw new DbException('not support type'); + } + + $field = $column; + if ('*' !== $column && $key && !\in_array($key, $column)) { + $field[] = $key; + } + + $query->setOption('field', $field); + + if (!empty($options['cache'])) { + // 判断查询缓存 + $cacheItem = $this->parseCache($query, $options['cache'], 'column'); + $name = $cacheItem->getKey(); + + if ($this->cache->has($name)) { + return $this->cache->get($name); + } + } + + // 生成查询SQL + $sql = $this->builder->select($query); + + if (isset($options['field'])) { + $query->setOption('field', $options['field']); + } else { + $query->removeOption('field'); + } + + // 执行查询操作 + $pdo = $this->getPDOStatement($sql, $query->getBind(), $options['master']); + $resultSet = $pdo->fetchAll(PDO::FETCH_ASSOC); + + if (is_string($key) && strpos($key, '.')) { + [$alias, $key] = explode('.', $key); + } + + if (empty($resultSet)) { + $result = []; + } elseif ('*' !== $column && \count($column) === 1) { + $column = \array_shift($column); + if (\strpos($column, ' ')) { + $column = \substr(\strrchr(\trim($column), ' '), 1); + } + + if (\strpos($column, '.')) { + [$alias, $column] = \explode('.', $column); + } + + $result = \array_column($resultSet, $column, $key); + } elseif ($key) { + $result = \array_column($resultSet, null, $key); + } else { + $result = $resultSet; + } + + if (isset($cacheItem)) { + // 缓存数据 + $cacheItem->set($result); + $this->cacheData($cacheItem); + } + + return $result; + } + + /** + * 根据参数绑定组装最终的SQL语句 便于调试 + * @access public + * @param string $sql 带参数绑定的sql语句 + * @param array $bind 参数绑定列表 + * @return string + */ + public function getRealSql(string $sql, array $bind = []): string + { + foreach ($bind as $key => $val) { + $value = strval(is_array($val) ? $val[0] : $val); + $type = is_array($val) ? $val[1] : PDO::PARAM_STR; + + if (self::PARAM_FLOAT == $type || PDO::PARAM_STR == $type) { + $value = '\'' . addslashes($value) . '\''; + } elseif (PDO::PARAM_INT == $type && '' === $value) { + $value = '0'; + } + + // 判断占位符 + $sql = is_numeric($key) ? + substr_replace($sql, $value, strpos($sql, '?'), 1) : + substr_replace($sql, $value, strpos($sql, ':' . $key), strlen(':' . $key)); + } + + return rtrim($sql); + } + + /** + * 参数绑定 + * 支持 ['name'=>'value','id'=>123] 对应命名占位符 + * 或者 ['value',123] 对应问号占位符 + * @access public + * @param array $bind 要绑定的参数列表 + * @return void + * @throws BindParamException + */ + protected function bindValue(array $bind = []): void + { + foreach ($bind as $key => $val) { + // 占位符 + $param = is_numeric($key) ? $key + 1 : ':' . $key; + + if (is_array($val)) { + if (PDO::PARAM_INT == $val[1] && '' === $val[0]) { + $val[0] = 0; + } elseif (self::PARAM_FLOAT == $val[1]) { + $val[0] = is_string($val[0]) ? (float) $val[0] : $val[0]; + $val[1] = PDO::PARAM_STR; + } + + $result = $this->PDOStatement->bindValue($param, $val[0], $val[1]); + } else { + $result = $this->PDOStatement->bindValue($param, $val); + } + + if (!$result) { + throw new BindParamException( + "Error occurred when binding parameters '{$param}'", + $this->config, + $this->getLastsql(), + $bind + ); + } + } + } + + /** + * 存储过程的输入输出参数绑定 + * @access public + * @param array $bind 要绑定的参数列表 + * @return void + * @throws BindParamException + */ + protected function bindParam(array $bind): void + { + foreach ($bind as $key => $val) { + $param = is_numeric($key) ? $key + 1 : ':' . $key; + + if (is_array($val)) { + array_unshift($val, $param); + $result = call_user_func_array([$this->PDOStatement, 'bindParam'], $val); + } else { + $result = $this->PDOStatement->bindValue($param, $val); + } + + if (!$result) { + $param = array_shift($val); + + throw new BindParamException( + "Error occurred when binding parameters '{$param}'", + $this->config, + $this->getLastsql(), + $bind + ); + } + } + } + + /** + * 获得数据集数组 + * @access protected + * @param bool $procedure 是否存储过程 + * @return array + */ + protected function getResult(bool $procedure = false): array + { + if ($procedure) { + // 存储过程返回结果 + return $this->procedure(); + } + + $result = $this->PDOStatement->fetchAll($this->fetchType); + + $this->numRows = count($result); + + return $result; + } + + /** + * 获得存储过程数据集 + * @access protected + * @return array + */ + protected function procedure(): array + { + $item = []; + + do { + $result = $this->getResult(); + if (!empty($result)) { + $item[] = $result; + } + } while ($this->PDOStatement->nextRowset()); + + $this->numRows = count($item); + + return $item; + } + + /** + * 执行数据库事务 + * @access public + * @param callable $callback 数据操作方法回调 + * @return mixed + * @throws PDOException + * @throws \Exception + * @throws \Throwable + */ + public function transaction(callable $callback) + { + $this->startTrans(); + + try { + $result = null; + if (is_callable($callback)) { + $result = $callback($this); + } + + $this->commit(); + return $result; + } catch (\Exception | \Throwable $e) { + $this->rollback(); + throw $e; + } + } + + /** + * 启动事务 + * @access public + * @return void + * @throws \PDOException + * @throws \Exception + */ + public function startTrans(): void + { + try { + $this->initConnect(true); + + ++$this->transTimes; + + if (1 == $this->transTimes) { + $this->linkID->beginTransaction(); + } elseif ($this->transTimes > 1 && $this->supportSavepoint() && $this->linkID->inTransaction()) { + $this->linkID->exec( + $this->parseSavepoint('trans' . $this->transTimes) + ); + } + $this->reConnectTimes = 0; + } catch (\Throwable | \Exception $e) { + if (1 === $this->transTimes && $this->reConnectTimes < 4 && $this->isBreak($e)) { + --$this->transTimes; + ++$this->reConnectTimes; + $this->close()->startTrans(); + } else { + if ($this->isBreak($e)) { + // 尝试对事务计数进行重置 + $this->transTimes = 0; + } + throw $e; + } + } + } + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return void + * @throws \PDOException + */ + public function commit(): void + { + $this->initConnect(true); + + if (1 == $this->transTimes && $this->linkID->inTransaction()) { + $this->linkID->commit(); + } + + --$this->transTimes; + } + + /** + * 事务回滚 + * @access public + * @return void + * @throws \PDOException + */ + public function rollback(): void + { + $this->initConnect(true); + + if ($this->linkID->inTransaction()) { + if (1 == $this->transTimes) { + $this->linkID->rollBack(); + } elseif ($this->transTimes > 1 && $this->supportSavepoint()) { + $this->linkID->exec( + $this->parseSavepointRollBack('trans' . $this->transTimes) + ); + } + } + + $this->transTimes = max(0, $this->transTimes - 1); + } + + /** + * 是否支持事务嵌套 + * @return bool + */ + protected function supportSavepoint(): bool + { + return false; + } + + /** + * 生成定义保存点的SQL + * @access protected + * @param string $name 标识 + * @return string + */ + protected function parseSavepoint(string $name): string + { + return 'SAVEPOINT ' . $name; + } + + /** + * 生成回滚到保存点的SQL + * @access protected + * @param string $name 标识 + * @return string + */ + protected function parseSavepointRollBack(string $name): string + { + return 'ROLLBACK TO SAVEPOINT ' . $name; + } + + /** + * 批处理执行SQL语句 + * 批处理的指令都认为是execute操作 + * @access public + * @param BaseQuery $query 查询对象 + * @param array $sqlArray SQL批处理指令 + * @param array $bind 参数绑定 + * @return bool + */ + public function batchQuery(BaseQuery $query, array $sqlArray = [], array $bind = []): bool + { + // 自动启动事务支持 + $this->startTrans(); + + try { + foreach ($sqlArray as $sql) { + $this->pdoExecute($query, $sql, $bind); + } + // 提交事务 + $this->commit(); + } catch (\Exception $e) { + $this->rollback(); + throw $e; + } + + return true; + } + + /** + * 关闭数据库(或者重新连接) + * @access public + * @return $this + */ + public function close() + { + $this->linkID = null; + $this->linkWrite = null; + $this->linkRead = null; + $this->links = []; + $this->transTimes = 0; + + $this->free(); + + return $this; + } + + /** + * 是否断线 + * @access protected + * @param \PDOException|\Exception $e 异常对象 + * @return bool + */ + protected function isBreak($e): bool + { + if (!$this->config['break_reconnect']) { + return false; + } + + $error = $e->getMessage(); + + foreach ($this->breakMatchStr as $msg) { + if (false !== stripos($error, $msg)) { + return true; + } + } + + return false; + } + + /** + * 获取最近一次查询的sql语句 + * @access public + * @return string + */ + public function getLastSql(): string + { + return $this->getRealSql($this->queryStr, $this->bind); + } + + /** + * 获取最近插入的ID + * @access public + * @param BaseQuery $query 查询对象 + * @param string $sequence 自增序列名 + * @return mixed + */ + public function getLastInsID(BaseQuery $query, string $sequence = null) + { + try { + $insertId = $this->linkID->lastInsertId($sequence); + } catch (\Exception $e) { + $insertId = ''; + } + + return $this->autoInsIDType($query, $insertId); + } + + /** + * 获取最近插入的ID + * @access public + * @param BaseQuery $query 查询对象 + * @param string $insertId 自增ID + * @return mixed + */ + protected function autoInsIDType(BaseQuery $query, string $insertId) + { + $pk = $query->getAutoInc(); + + if ($pk) { + $type = $this->getFieldBindType($pk); + + if (PDO::PARAM_INT == $type) { + $insertId = (int) $insertId; + } elseif (self::PARAM_FLOAT == $type) { + $insertId = (float) $insertId; + } + } + + return $insertId; + } + + /** + * 获取最近的错误信息 + * @access public + * @return string + */ + public function getError(): string + { + if ($this->PDOStatement) { + $error = $this->PDOStatement->errorInfo(); + $error = $error[1] . ':' . $error[2]; + } else { + $error = ''; + } + + if ('' != $this->queryStr) { + $error .= "\n [ SQL语句 ] : " . $this->getLastsql(); + } + + return $error; + } + + /** + * 初始化数据库连接 + * @access protected + * @param boolean $master 是否主服务器 + * @return void + */ + protected function initConnect(bool $master = true): void + { + if (!empty($this->config['deploy'])) { + // 采用分布式数据库 + if ($master || $this->transTimes) { + if (!$this->linkWrite) { + $this->linkWrite = $this->multiConnect(true); + } + + $this->linkID = $this->linkWrite; + } else { + if (!$this->linkRead) { + $this->linkRead = $this->multiConnect(false); + } + + $this->linkID = $this->linkRead; + } + } elseif (!$this->linkID) { + // 默认单数据库 + $this->linkID = $this->connect(); + } + } + + /** + * 连接分布式服务器 + * @access protected + * @param boolean $master 主服务器 + * @return PDO + */ + protected function multiConnect(bool $master = false): PDO + { + $config = []; + + // 分布式数据库配置解析 + foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) { + $config[$name] = is_string($this->config[$name]) ? explode(',', $this->config[$name]) : $this->config[$name]; + } + + // 主服务器序号 + $m = floor(mt_rand(0, $this->config['master_num'] - 1)); + + if ($this->config['rw_separate']) { + // 主从式采用读写分离 + if ($master) // 主服务器写入 + { + $r = $m; + } elseif (is_numeric($this->config['slave_no'])) { + // 指定服务器读 + $r = $this->config['slave_no']; + } else { + // 读操作连接从服务器 每次随机连接的数据库 + $r = floor(mt_rand($this->config['master_num'], count($config['hostname']) - 1)); + } + } else { + // 读写操作不区分服务器 每次随机连接的数据库 + $r = floor(mt_rand(0, count($config['hostname']) - 1)); + } + $dbMaster = false; + + if ($m != $r) { + $dbMaster = []; + foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) { + $dbMaster[$name] = $config[$name][$m] ?? $config[$name][0]; + } + } + + $dbConfig = []; + + foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) { + $dbConfig[$name] = $config[$name][$r] ?? $config[$name][0]; + } + + return $this->connect($dbConfig, $r, $r == $m ? false : $dbMaster); + } + + /** + * 执行数据库Xa事务 + * @access public + * @param callable $callback 数据操作方法回调 + * @param array $dbs 多个查询对象或者连接对象 + * @return mixed + * @throws PDOException + * @throws \Exception + * @throws \Throwable + */ + public function transactionXa(callable $callback, array $dbs = []) + { + $xid = uniqid('xa'); + + if (empty($dbs)) { + $dbs[] = $this; + } + + foreach ($dbs as $key => $db) { + if ($db instanceof BaseQuery) { + $db = $db->getConnection(); + + $dbs[$key] = $db; + } + + $db->startTransXa($xid); + } + + try { + $result = null; + if (is_callable($callback)) { + $result = $callback($this); + } + + foreach ($dbs as $db) { + $db->prepareXa($xid); + } + + foreach ($dbs as $db) { + $db->commitXa($xid); + } + + return $result; + } catch (\Exception | \Throwable $e) { + foreach ($dbs as $db) { + $db->rollbackXa($xid); + } + throw $e; + } + } + + /** + * 启动XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function startTransXa(string $xid): void + {} + + /** + * 预编译XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function prepareXa(string $xid): void + {} + + /** + * 提交XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function commitXa(string $xid): void + {} + + /** + * 回滚XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function rollbackXa(string $xid): void + {} +} diff --git a/vendor/topthink/think-orm/src/db/Query.php b/vendor/topthink/think-orm/src/db/Query.php new file mode 100644 index 0000000..80e01cd --- /dev/null +++ b/vendor/topthink/think-orm/src/db/Query.php @@ -0,0 +1,451 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db; + +use PDOStatement; +use think\helper\Str; + +/** + * PDO数据查询类 + */ +class Query extends BaseQuery +{ + use concern\JoinAndViewQuery; + use concern\ParamsBind; + use concern\TableFieldInfo; + + /** + * 表达式方式指定Field排序 + * @access public + * @param string $field 排序字段 + * @param array $bind 参数绑定 + * @return $this + */ + public function orderRaw(string $field, array $bind = []) + { + $this->options['order'][] = new Raw($field, $bind); + + return $this; + } + + /** + * 表达式方式指定查询字段 + * @access public + * @param string $field 字段名 + * @return $this + */ + public function fieldRaw(string $field) + { + $this->options['field'][] = new Raw($field); + + return $this; + } + + /** + * 指定Field排序 orderField('id',[1,2,3],'desc') + * @access public + * @param string $field 排序字段 + * @param array $values 排序值 + * @param string $order 排序 desc/asc + * @return $this + */ + public function orderField(string $field, array $values, string $order = '') + { + if (!empty($values)) { + $values['sort'] = $order; + + $this->options['order'][$field] = $values; + } + + return $this; + } + + /** + * 随机排序 + * @access public + * @return $this + */ + public function orderRand() + { + $this->options['order'][] = '[rand]'; + return $this; + } + + /** + * 使用表达式设置数据 + * @access public + * @param string $field 字段名 + * @param string $value 字段值 + * @return $this + */ + public function exp(string $field, string $value) + { + $this->options['data'][$field] = new Raw($value); + return $this; + } + + /** + * 表达式方式指定当前操作的数据表 + * @access public + * @param mixed $table 表名 + * @return $this + */ + public function tableRaw(string $table) + { + $this->options['table'] = new Raw($table); + + return $this; + } + + /** + * 获取执行的SQL语句而不进行实际的查询 + * @access public + * @param bool $fetch 是否返回sql + * @return $this|Fetch + */ + public function fetchSql(bool $fetch = true) + { + $this->options['fetch_sql'] = $fetch; + + if ($fetch) { + return new Fetch($this); + } + + return $this; + } + + /** + * 批处理执行SQL语句 + * 批处理的指令都认为是execute操作 + * @access public + * @param array $sql SQL批处理指令 + * @return bool + */ + public function batchQuery(array $sql = []): bool + { + return $this->connection->batchQuery($this, $sql); + } + + /** + * USING支持 用于多表删除 + * @access public + * @param mixed $using USING + * @return $this + */ + public function using($using) + { + $this->options['using'] = $using; + return $this; + } + + /** + * 存储过程调用 + * @access public + * @param bool $procedure 是否为存储过程查询 + * @return $this + */ + public function procedure(bool $procedure = true) + { + $this->options['procedure'] = $procedure; + return $this; + } + + /** + * 指定group查询 + * @access public + * @param string|array $group GROUP + * @return $this + */ + public function group($group) + { + $this->options['group'] = $group; + return $this; + } + + /** + * 指定having查询 + * @access public + * @param string $having having + * @return $this + */ + public function having(string $having) + { + $this->options['having'] = $having; + return $this; + } + + /** + * 指定distinct查询 + * @access public + * @param bool $distinct 是否唯一 + * @return $this + */ + public function distinct(bool $distinct = true) + { + $this->options['distinct'] = $distinct; + return $this; + } + + /** + * 指定强制索引 + * @access public + * @param string $force 索引名称 + * @return $this + */ + public function force(string $force) + { + $this->options['force'] = $force; + return $this; + } + + /** + * 查询注释 + * @access public + * @param string $comment 注释 + * @return $this + */ + public function comment(string $comment) + { + $this->options['comment'] = $comment; + return $this; + } + + /** + * 设置是否REPLACE + * @access public + * @param bool $replace 是否使用REPLACE写入数据 + * @return $this + */ + public function replace(bool $replace = true) + { + $this->options['replace'] = $replace; + return $this; + } + + /** + * 设置当前查询所在的分区 + * @access public + * @param string|array $partition 分区名称 + * @return $this + */ + public function partition($partition) + { + $this->options['partition'] = $partition; + return $this; + } + + /** + * 设置DUPLICATE + * @access public + * @param array|string|Raw $duplicate DUPLICATE信息 + * @return $this + */ + public function duplicate($duplicate) + { + $this->options['duplicate'] = $duplicate; + return $this; + } + + /** + * 设置查询的额外参数 + * @access public + * @param string $extra 额外信息 + * @return $this + */ + public function extra(string $extra) + { + $this->options['extra'] = $extra; + return $this; + } + + /** + * 创建子查询SQL + * @access public + * @param bool $sub 是否添加括号 + * @return string + * @throws Exception + */ + public function buildSql(bool $sub = true): string + { + return $sub ? '( ' . $this->fetchSql()->select() . ' )' : $this->fetchSql()->select(); + } + + /** + * 获取当前数据表的主键 + * @access public + * @return string|array + */ + public function getPk() + { + if (empty($this->pk)) { + $this->pk = $this->connection->getPk($this->getTable()); + } + + return $this->pk; + } + + /** + * 指定数据表自增主键 + * @access public + * @param string $autoinc 自增键 + * @return $this + */ + public function autoinc(string $autoinc) + { + $this->autoinc = $autoinc; + return $this; + } + + /** + * 获取当前数据表的自增主键 + * @access public + * @return string|null + */ + public function getAutoInc() + { + $tableName = $this->getTable(); + + if (empty($this->autoinc) && $tableName) { + $this->autoinc = $this->connection->getAutoInc($tableName); + } + + return $this->autoinc; + } + + /** + * 字段值增长 + * @access public + * @param string $field 字段名 + * @param float $step 增长值 + * @return $this + */ + public function inc(string $field, float $step = 1) + { + $this->options['data'][$field] = ['INC', $step]; + + return $this; + } + + /** + * 字段值减少 + * @access public + * @param string $field 字段名 + * @param float $step 增长值 + * @return $this + */ + public function dec(string $field, float $step = 1) + { + $this->options['data'][$field] = ['DEC', $step]; + return $this; + } + + /** + * 获取当前的查询标识 + * @access public + * @param mixed $data 要序列化的数据 + * @return string + */ + public function getQueryGuid($data = null): string + { + return md5($this->getConfig('database') . serialize(var_export($data ?: $this->options, true)) . serialize($this->getBind(false))); + } + + /** + * 执行查询但只返回PDOStatement对象 + * @access public + * @return PDOStatement + */ + public function getPdo(): PDOStatement + { + return $this->connection->pdo($this); + } + + /** + * 使用游标查找记录 + * @access public + * @param mixed $data 数据 + * @return \Generator + */ + public function cursor($data = null) + { + if (!is_null($data)) { + // 主键条件分析 + $this->parsePkWhere($data); + } + + $this->options['data'] = $data; + + $connection = clone $this->connection; + + return $connection->cursor($this); + } + + /** + * 分批数据返回处理 + * @access public + * @param integer $count 每次处理的数据数量 + * @param callable $callback 处理回调方法 + * @param string|array $column 分批处理的字段名 + * @param string $order 字段排序 + * @return bool + * @throws Exception + */ + public function chunk(int $count, callable $callback, $column = null, string $order = 'asc'): bool + { + $options = $this->getOptions(); + $column = $column ?: $this->getPk(); + + if (isset($options['order'])) { + unset($options['order']); + } + + $bind = $this->bind; + + if (is_array($column)) { + $times = 1; + $query = $this->options($options)->page($times, $count); + } else { + $query = $this->options($options)->limit($count); + + if (strpos($column, '.')) { + [$alias, $key] = explode('.', $column); + } else { + $key = $column; + } + } + + $resultSet = $query->order($column, $order)->select(); + + while (count($resultSet) > 0) { + if (false === call_user_func($callback, $resultSet)) { + return false; + } + + if (isset($times)) { + $times++; + $query = $this->options($options)->page($times, $count); + } else { + $end = $resultSet->pop(); + $lastId = is_array($end) ? $end[$key] : $end->getData($key); + + $query = $this->options($options) + ->limit($count) + ->where($column, 'asc' == strtolower($order) ? '>' : '<', $lastId); + } + + $resultSet = $query->bind($bind)->order($column, $order)->select(); + } + + return true; + } +} diff --git a/vendor/topthink/think-orm/src/db/Raw.php b/vendor/topthink/think-orm/src/db/Raw.php new file mode 100644 index 0000000..833fbf0 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/Raw.php @@ -0,0 +1,71 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db; + +/** + * SQL Raw + */ +class Raw +{ + /** + * 查询表达式 + * + * @var string + */ + protected $value; + + /** + * 参数绑定 + * + * @var array + */ + protected $bind = []; + + /** + * 创建一个查询表达式 + * + * @param string $value + * @param array $bind + * @return void + */ + public function __construct(string $value, array $bind = []) + { + $this->value = $value; + $this->bind = $bind; + } + + /** + * 获取表达式 + * + * @return string + */ + public function getValue(): string + { + return $this->value; + } + + /** + * 获取参数绑定 + * + * @return string + */ + public function getBind(): array + { + return $this->bind; + } + + public function __toString() + { + return (string) $this->value; + } +} diff --git a/vendor/topthink/think-orm/src/db/Where.php b/vendor/topthink/think-orm/src/db/Where.php new file mode 100644 index 0000000..0880460 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/Where.php @@ -0,0 +1,182 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db; + +use ArrayAccess; + +/** + * 数组查询对象 + */ +class Where implements ArrayAccess +{ + /** + * 查询表达式 + * @var array + */ + protected $where = []; + + /** + * 是否需要把查询条件两边增加括号 + * @var bool + */ + protected $enclose = false; + + /** + * 创建一个查询表达式 + * + * @param array $where 查询条件数组 + * @param bool $enclose 是否增加括号 + */ + public function __construct(array $where = [], bool $enclose = false) + { + $this->where = $where; + $this->enclose = $enclose; + } + + /** + * 设置是否添加括号 + * @access public + * @param bool $enclose + * @return $this + */ + public function enclose(bool $enclose = true) + { + $this->enclose = $enclose; + return $this; + } + + /** + * 解析为Query对象可识别的查询条件数组 + * @access public + * @return array + */ + public function parse(): array + { + $where = []; + + foreach ($this->where as $key => $val) { + if ($val instanceof Raw) { + $where[] = [$key, 'exp', $val]; + } elseif (is_null($val)) { + $where[] = [$key, 'NULL', '']; + } elseif (is_array($val)) { + $where[] = $this->parseItem($key, $val); + } else { + $where[] = [$key, '=', $val]; + } + } + + return $this->enclose ? [$where] : $where; + } + + /** + * 分析查询表达式 + * @access protected + * @param string $field 查询字段 + * @param array $where 查询条件 + * @return array + */ + protected function parseItem(string $field, array $where = []): array + { + $op = $where[0]; + $condition = $where[1] ?? null; + + if (is_array($op)) { + // 同一字段多条件查询 + array_unshift($where, $field); + } elseif (is_null($condition)) { + if (is_string($op) && in_array(strtoupper($op), ['NULL', 'NOTNULL', 'NOT NULL'], true)) { + // null查询 + $where = [$field, $op, '']; + } elseif (is_null($op) || '=' == $op) { + $where = [$field, 'NULL', '']; + } elseif ('<>' == $op) { + $where = [$field, 'NOTNULL', '']; + } else { + // 字段相等查询 + $where = [$field, '=', $op]; + } + } else { + $where = [$field, $op, $condition]; + } + + return $where; + } + + /** + * 修改器 设置数据对象的值 + * @access public + * @param string $name 名称 + * @param mixed $value 值 + * @return void + */ + public function __set($name, $value) + { + $this->where[$name] = $value; + } + + /** + * 获取器 获取数据对象的值 + * @access public + * @param string $name 名称 + * @return mixed + */ + public function __get($name) + { + return $this->where[$name] ?? null; + } + + /** + * 检测数据对象的值 + * @access public + * @param string $name 名称 + * @return bool + */ + public function __isset($name) + { + return isset($this->where[$name]); + } + + /** + * 销毁数据对象的值 + * @access public + * @param string $name 名称 + * @return void + */ + public function __unset($name) + { + unset($this->where[$name]); + } + + // ArrayAccess + public function offsetSet($name, $value) + { + $this->__set($name, $value); + } + + public function offsetExists($name) + { + return $this->__isset($name); + } + + public function offsetUnset($name) + { + $this->__unset($name); + } + + public function offsetGet($name) + { + return $this->__get($name); + } + +} diff --git a/vendor/topthink/think-orm/src/db/builder/Mongo.php b/vendor/topthink/think-orm/src/db/builder/Mongo.php new file mode 100644 index 0000000..823156b --- /dev/null +++ b/vendor/topthink/think-orm/src/db/builder/Mongo.php @@ -0,0 +1,675 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); +namespace think\db\builder; + +use MongoDB\BSON\Javascript; +use MongoDB\BSON\ObjectID; +use MongoDB\BSON\Regex; +use MongoDB\Driver\BulkWrite; +use MongoDB\Driver\Command; +use MongoDB\Driver\Exception\InvalidArgumentException; +use MongoDB\Driver\Query as MongoQuery; +use think\db\connector\Mongo as Connection; +use think\db\exception\DbException as Exception; +use think\db\Mongo as Query; + +class Mongo +{ + // connection对象实例 + protected $connection; + // 最后插入ID + protected $insertId = []; + // 查询表达式 + protected $exp = ['<>' => 'ne', '=' => 'eq', '>' => 'gt', '>=' => 'gte', '<' => 'lt', '<=' => 'lte', 'in' => 'in', 'not in' => 'nin', 'nin' => 'nin', 'mod' => 'mod', 'exists' => 'exists', 'null' => 'null', 'notnull' => 'not null', 'not null' => 'not null', 'regex' => 'regex', 'type' => 'type', 'all' => 'all', '> time' => '> time', '< time' => '< time', 'between' => 'between', 'not between' => 'not between', 'between time' => 'between time', 'not between time' => 'not between time', 'notbetween time' => 'not between time', 'like' => 'like', 'near' => 'near', 'size' => 'size']; + + /** + * 架构函数 + * @access public + * @param Connection $connection 数据库连接对象实例 + */ + public function __construct(Connection $connection) + { + $this->connection = $connection; + } + + /** + * 获取当前的连接对象实例 + * @access public + * @return Connection + */ + public function getConnection(): Connection + { + return $this->connection; + } + + /** + * key分析 + * @access protected + * @param string $key + * @return string + */ + protected function parseKey(Query $query, string $key): string + { + if (0 === strpos($key, '__TABLE__.')) { + [$collection, $key] = explode('.', $key, 2); + } + + if ('id' == $key && $this->connection->getConfig('pk_convert_id')) { + $key = '_id'; + } + + return trim($key); + } + + /** + * value分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $value + * @param string $field + * @return string + */ + protected function parseValue(Query $query, $value, $field = '') + { + if ('_id' == $field && 'ObjectID' == $this->connection->getConfig('pk_type') && is_string($value)) { + try { + return new ObjectID($value); + } catch (InvalidArgumentException $e) { + return new ObjectID(); + } + } + + return $value; + } + + /** + * insert数据分析 + * @access protected + * @param Query $query 查询对象 + * @param array $data 数据 + * @return array + */ + protected function parseData(Query $query, array $data): array + { + if (empty($data)) { + return []; + } + + $result = []; + + foreach ($data as $key => $val) { + $item = $this->parseKey($query, $key); + + if (is_object($val)) { + $result[$item] = $val; + } elseif (isset($val[0]) && 'exp' == $val[0]) { + $result[$item] = $val[1]; + } elseif (is_null($val)) { + $result[$item] = 'NULL'; + } else { + $result[$item] = $this->parseValue($query, $val, $key); + } + } + + return $result; + } + + /** + * Set数据分析 + * @access protected + * @param Query $query 查询对象 + * @param array $data 数据 + * @return array + */ + protected function parseSet(Query $query, array $data): array + { + if (empty($data)) { + return []; + } + + $result = []; + + foreach ($data as $key => $val) { + $item = $this->parseKey($query, $key); + + if (is_array($val) && isset($val[0]) && is_string($val[0]) && 0 === strpos($val[0], '$')) { + $result[$val[0]][$item] = $this->parseValue($query, $val[1], $key); + } else { + $result['$set'][$item] = $this->parseValue($query, $val, $key); + } + } + + return $result; + } + + /** + * 生成查询过滤条件 + * @access public + * @param Query $query 查询对象 + * @param mixed $where + * @return array + */ + public function parseWhere(Query $query, array $where): array + { + if (empty($where)) { + $where = []; + } + + $filter = []; + foreach ($where as $logic => $val) { + $logic = '$' . strtolower($logic); + foreach ($val as $field => $value) { + if (is_array($value)) { + if (key($value) !== 0) { + throw new Exception('where express error:' . var_export($value, true)); + } + $field = array_shift($value); + } elseif (!($value instanceof \Closure)) { + throw new Exception('where express error:' . var_export($value, true)); + } + + if ($value instanceof \Closure) { + // 使用闭包查询 + $query = new Query($this->connection); + call_user_func_array($value, [ & $query]); + $filter[$logic][] = $this->parseWhere($query, $query->getOptions('where')); + } else { + if (strpos($field, '|')) { + // 不同字段使用相同查询条件(OR) + $array = explode('|', $field); + foreach ($array as $k) { + $filter['$or'][] = $this->parseWhereItem($query, $k, $value); + } + } elseif (strpos($field, '&')) { + // 不同字段使用相同查询条件(AND) + $array = explode('&', $field); + foreach ($array as $k) { + $filter['$and'][] = $this->parseWhereItem($query, $k, $value); + } + } else { + // 对字段使用表达式查询 + $field = is_string($field) ? $field : ''; + $filter[$logic][] = $this->parseWhereItem($query, $field, $value); + } + } + } + } + + $options = $query->getOptions(); + if (!empty($options['soft_delete'])) { + // 附加软删除条件 + [$field, $condition] = $options['soft_delete']; + $filter['$and'][] = $this->parseWhereItem($query, $field, $condition); + } + + return $filter; + } + + // where子单元分析 + protected function parseWhereItem(Query $query, $field, $val): array + { + $key = $field ? $this->parseKey($query, $field) : ''; + // 查询规则和条件 + if (!is_array($val)) { + $val = ['=', $val]; + } + [$exp, $value] = $val; + + // 对一个字段使用多个查询条件 + if (is_array($exp)) { + $data = []; + foreach ($val as $value) { + $exp = $value[0]; + $value = $value[1]; + if (!in_array($exp, $this->exp)) { + $exp = strtolower($exp); + if (isset($this->exp[$exp])) { + $exp = $this->exp[$exp]; + } + } + $k = '$' . $exp; + $data[$k] = $value; + } + $result[$key] = $data; + return $result; + } elseif (!in_array($exp, $this->exp)) { + $exp = strtolower($exp); + if (isset($this->exp[$exp])) { + $exp = $this->exp[$exp]; + } else { + throw new Exception('where express error:' . $exp); + } + } + + $result = []; + if ('=' == $exp) { + // 普通查询 + $result[$key] = $this->parseValue($query, $value, $key); + } elseif (in_array($exp, ['neq', 'ne', 'gt', 'egt', 'gte', 'lt', 'lte', 'elt', 'mod'])) { + // 比较运算 + $k = '$' . $exp; + $result[$key] = [$k => $this->parseValue($query, $value, $key)]; + } elseif ('null' == $exp) { + // NULL 查询 + $result[$key] = null; + } elseif ('not null' == $exp) { + $result[$key] = ['$ne' => null]; + } elseif ('all' == $exp) { + // 满足所有指定条件 + $result[$key] = ['$all', $this->parseValue($query, $value, $key)]; + } elseif ('between' == $exp) { + // 区间查询 + $value = is_array($value) ? $value : explode(',', $value); + $result[$key] = ['$gte' => $this->parseValue($query, $value[0], $key), '$lte' => $this->parseValue($query, $value[1], $key)]; + } elseif ('not between' == $exp) { + // 范围查询 + $value = is_array($value) ? $value : explode(',', $value); + $result[$key] = ['$lt' => $this->parseValue($query, $value[0], $key), '$gt' => $this->parseValue($query, $value[1], $key)]; + } elseif ('exists' == $exp) { + // 字段是否存在 + $result[$key] = ['$exists' => (bool) $value]; + } elseif ('type' == $exp) { + // 类型查询 + $result[$key] = ['$type' => intval($value)]; + } elseif ('exp' == $exp) { + // 表达式查询 + $result['$where'] = $value instanceof Javascript ? $value : new Javascript($value); + } elseif ('like' == $exp) { + // 模糊查询 采用正则方式 + $result[$key] = $value instanceof Regex ? $value : new Regex($value, 'i'); + } elseif (in_array($exp, ['nin', 'in'])) { + // IN 查询 + $value = is_array($value) ? $value : explode(',', $value); + foreach ($value as $k => $val) { + $value[$k] = $this->parseValue($query, $val, $key); + } + $result[$key] = ['$' . $exp => $value]; + } elseif ('regex' == $exp) { + $result[$key] = $value instanceof Regex ? $value : new Regex($value, 'i'); + } elseif ('< time' == $exp) { + $result[$key] = ['$lt' => $this->parseDateTime($query, $value, $field)]; + } elseif ('> time' == $exp) { + $result[$key] = ['$gt' => $this->parseDateTime($query, $value, $field)]; + } elseif ('between time' == $exp) { + // 区间查询 + $value = is_array($value) ? $value : explode(',', $value); + $result[$key] = ['$gte' => $this->parseDateTime($query, $value[0], $field), '$lte' => $this->parseDateTime($query, $value[1], $field)]; + } elseif ('not between time' == $exp) { + // 范围查询 + $value = is_array($value) ? $value : explode(',', $value); + $result[$key] = ['$lt' => $this->parseDateTime($query, $value[0], $field), '$gt' => $this->parseDateTime($query, $value[1], $field)]; + } elseif ('near' == $exp) { + // 经纬度查询 + $result[$key] = ['$near' => $this->parseValue($query, $value, $key)]; + } elseif ('size' == $exp) { + // 元素长度查询 + $result[$key] = ['$size' => intval($value)]; + } else { + // 普通查询 + $result[$key] = $this->parseValue($query, $value, $key); + } + + return $result; + } + + /** + * 日期时间条件解析 + * @access protected + * @param Query $query 查询对象 + * @param string $value + * @param string $key + * @return string + */ + protected function parseDateTime(Query $query, $value, $key) + { + // 获取时间字段类型 + $type = $query->getFieldType($key); + + if ($type) { + if (is_string($value)) { + $value = strtotime($value) ?: $value; + } + + if (is_int($value)) { + if (preg_match('/(datetime|timestamp)/is', $type)) { + // 日期及时间戳类型 + $value = date('Y-m-d H:i:s', $value); + } elseif (preg_match('/(date)/is', $type)) { + // 日期及时间戳类型 + $value = date('Y-m-d', $value); + } + } + } + + return $value; + } + + /** + * 获取最后写入的ID 如果是insertAll方法的话 返回所有写入的ID + * @access public + * @return mixed + */ + public function getLastInsID() + { + return $this->insertId; + } + + /** + * 生成insert BulkWrite对象 + * @access public + * @param Query $query 查询对象 + * @return BulkWrite + */ + public function insert(Query $query): BulkWrite + { + // 分析并处理数据 + $options = $query->getOptions(); + + $data = $this->parseData($query, $options['data']); + + $bulk = new BulkWrite; + + if ($insertId = $bulk->insert($data)) { + $this->insertId = $insertId; + } + + $this->log('insert', $data, $options); + + return $bulk; + } + + /** + * 生成insertall BulkWrite对象 + * @access public + * @param Query $query 查询对象 + * @param array $dataSet 数据集 + * @return BulkWrite + */ + public function insertAll(Query $query, array $dataSet): BulkWrite + { + $bulk = new BulkWrite; + $options = $query->getOptions(); + + $this->insertId = []; + foreach ($dataSet as $data) { + // 分析并处理数据 + $data = $this->parseData($query, $data); + if ($insertId = $bulk->insert($data)) { + $this->insertId[] = $insertId; + } + } + + $this->log('insert', $dataSet, $options); + + return $bulk; + } + + /** + * 生成update BulkWrite对象 + * @access public + * @param Query $query 查询对象 + * @return BulkWrite + */ + public function update(Query $query): BulkWrite + { + $options = $query->getOptions(); + + $data = $this->parseSet($query, $options['data']); + $where = $this->parseWhere($query, $options['where']); + + if (1 == $options['limit']) { + $updateOptions = ['multi' => false]; + } else { + $updateOptions = ['multi' => true]; + } + + $bulk = new BulkWrite; + + $bulk->update($where, $data, $updateOptions); + + $this->log('update', $data, $where); + + return $bulk; + } + + /** + * 生成delete BulkWrite对象 + * @access public + * @param Query $query 查询对象 + * @return BulkWrite + */ + public function delete(Query $query): BulkWrite + { + $options = $query->getOptions(); + $where = $this->parseWhere($query, $options['where']); + + $bulk = new BulkWrite; + + if (1 == $options['limit']) { + $deleteOptions = ['limit' => 1]; + } else { + $deleteOptions = ['limit' => 0]; + } + + $bulk->delete($where, $deleteOptions); + + $this->log('remove', $where, $deleteOptions); + + return $bulk; + } + + /** + * 生成Mongo查询对象 + * @access public + * @param Query $query 查询对象 + * @param bool $one 是否仅获取一个记录 + * @return MongoQuery + */ + public function select(Query $query, bool $one = false): MongoQuery + { + $options = $query->getOptions(); + + $where = $this->parseWhere($query, $options['where']); + + if ($one) { + $options['limit'] = 1; + } + + $query = new MongoQuery($where, $options); + + $this->log('find', $where, $options); + + return $query; + } + + /** + * 生成Count命令 + * @access public + * @param Query $query 查询对象 + * @return Command + */ + public function count(Query $query): Command + { + $options = $query->getOptions(); + + $cmd['count'] = $options['table']; + $cmd['query'] = (object) $this->parseWhere($query, $options['where']); + + foreach (['hint', 'limit', 'maxTimeMS', 'skip'] as $option) { + if (isset($options[$option])) { + $cmd[$option] = $options[$option]; + } + } + + $command = new Command($cmd); + $this->log('cmd', 'count', $cmd); + + return $command; + } + + /** + * 聚合查询命令 + * @access public + * @param Query $query 查询对象 + * @param array $extra 指令和字段 + * @return Command + */ + public function aggregate(Query $query, array $extra): Command + { + $options = $query->getOptions(); + [$fun, $field] = $extra; + + if ('id' == $field && $this->connection->getConfig('pk_convert_id')) { + $field = '_id'; + } + + $group = isset($options['group']) ? '$' . $options['group'] : null; + + $pipeline = [ + ['$match' => (object) $this->parseWhere($query, $options['where'])], + ['$group' => ['_id' => $group, 'aggregate' => ['$' . $fun => '$' . $field]]], + ]; + + $cmd = [ + 'aggregate' => $options['table'], + 'allowDiskUse' => true, + 'pipeline' => $pipeline, + 'cursor' => new \stdClass, + ]; + + foreach (['explain', 'collation', 'bypassDocumentValidation', 'readConcern'] as $option) { + if (isset($options[$option])) { + $cmd[$option] = $options[$option]; + } + } + + $command = new Command($cmd); + + $this->log('aggregate', $cmd); + + return $command; + } + + /** + * 多聚合查询命令, 可以对多个字段进行 group by 操作 + * + * @param Query $query 查询对象 + * @param array $extra 指令和字段 + * @return Command + */ + public function multiAggregate(Query $query, $extra): Command + { + $options = $query->getOptions(); + + [$aggregate, $groupBy] = $extra; + + $groups = ['_id' => []]; + + foreach ($groupBy as $field) { + $groups['_id'][$field] = '$' . $field; + } + + foreach ($aggregate as $fun => $field) { + $groups[$field . '_' . $fun] = ['$' . $fun => '$' . $field]; + } + + $pipeline = [ + ['$match' => (object) $this->parseWhere($query, $options['where'])], + ['$group' => $groups], + ]; + + $cmd = [ + 'aggregate' => $options['table'], + 'allowDiskUse' => true, + 'pipeline' => $pipeline, + 'cursor' => new \stdClass, + ]; + + foreach (['explain', 'collation', 'bypassDocumentValidation', 'readConcern'] as $option) { + if (isset($options[$option])) { + $cmd[$option] = $options[$option]; + } + } + + $command = new Command($cmd); + $this->log('group', $cmd); + + return $command; + } + + /** + * 生成distinct命令 + * @access public + * @param Query $query 查询对象 + * @param string $field 字段名 + * @return Command + */ + public function distinct(Query $query, $field): Command + { + $options = $query->getOptions(); + + $cmd = [ + 'distinct' => $options['table'], + 'key' => $field, + ]; + + if (!empty($options['where'])) { + $cmd['query'] = (object) $this->parseWhere($query, $options['where']); + } + + if (isset($options['maxTimeMS'])) { + $cmd['maxTimeMS'] = $options['maxTimeMS']; + } + + $command = new Command($cmd); + + $this->log('cmd', 'distinct', $cmd); + + return $command; + } + + /** + * 查询所有的collection + * @access public + * @return Command + */ + public function listcollections(): Command + { + $cmd = ['listCollections' => 1]; + $command = new Command($cmd); + + $this->log('cmd', 'listCollections', $cmd); + + return $command; + } + + /** + * 查询数据表的状态信息 + * @access public + * @param Query $query 查询对象 + * @return Command + */ + public function collStats(Query $query): Command + { + $options = $query->getOptions(); + + $cmd = ['collStats' => $options['table']]; + $command = new Command($cmd); + + $this->log('cmd', 'collStats', $cmd); + + return $command; + } + + protected function log($type, $data, $options = []) + { + $this->connection->mongoLog($type, $data, $options); + } +} diff --git a/vendor/topthink/think-orm/src/db/builder/Mysql.php b/vendor/topthink/think-orm/src/db/builder/Mysql.php new file mode 100644 index 0000000..136b0de --- /dev/null +++ b/vendor/topthink/think-orm/src/db/builder/Mysql.php @@ -0,0 +1,426 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\builder; + +use think\db\Builder; +use think\db\exception\DbException as Exception; +use think\db\Query; +use think\db\Raw; + +/** + * mysql数据库驱动 + */ +class Mysql extends Builder +{ + /** + * 查询表达式解析 + * @var array + */ + protected $parser = [ + 'parseCompare' => ['=', '<>', '>', '>=', '<', '<='], + 'parseLike' => ['LIKE', 'NOT LIKE'], + 'parseBetween' => ['NOT BETWEEN', 'BETWEEN'], + 'parseIn' => ['NOT IN', 'IN'], + 'parseExp' => ['EXP'], + 'parseRegexp' => ['REGEXP', 'NOT REGEXP'], + 'parseNull' => ['NOT NULL', 'NULL'], + 'parseBetweenTime' => ['BETWEEN TIME', 'NOT BETWEEN TIME'], + 'parseTime' => ['< TIME', '> TIME', '<= TIME', '>= TIME'], + 'parseExists' => ['NOT EXISTS', 'EXISTS'], + 'parseColumn' => ['COLUMN'], + 'parseFindInSet' => ['FIND IN SET'], + ]; + + /** + * SELECT SQL表达式 + * @var string + */ + protected $selectSql = 'SELECT%DISTINCT%%EXTRA% %FIELD% FROM %TABLE%%PARTITION%%FORCE%%JOIN%%WHERE%%GROUP%%HAVING%%UNION%%ORDER%%LIMIT% %LOCK%%COMMENT%'; + + /** + * INSERT SQL表达式 + * @var string + */ + protected $insertSql = '%INSERT%%EXTRA% INTO %TABLE%%PARTITION% SET %SET% %DUPLICATE%%COMMENT%'; + + /** + * INSERT ALL SQL表达式 + * @var string + */ + protected $insertAllSql = '%INSERT%%EXTRA% INTO %TABLE%%PARTITION% (%FIELD%) VALUES %DATA% %DUPLICATE%%COMMENT%'; + + /** + * UPDATE SQL表达式 + * @var string + */ + protected $updateSql = 'UPDATE%EXTRA% %TABLE%%PARTITION% %JOIN% SET %SET% %WHERE% %ORDER%%LIMIT% %LOCK%%COMMENT%'; + + /** + * DELETE SQL表达式 + * @var string + */ + protected $deleteSql = 'DELETE%EXTRA% FROM %TABLE%%PARTITION%%USING%%JOIN%%WHERE%%ORDER%%LIMIT% %LOCK%%COMMENT%'; + + /** + * 生成查询SQL + * @access public + * @param Query $query 查询对象 + * @param bool $one 是否仅获取一个记录 + * @return string + */ + public function select(Query $query, bool $one = false): string + { + $options = $query->getOptions(); + + return str_replace( + ['%TABLE%', '%PARTITION%', '%DISTINCT%', '%EXTRA%', '%FIELD%', '%JOIN%', '%WHERE%', '%GROUP%', '%HAVING%', '%ORDER%', '%LIMIT%', '%UNION%', '%LOCK%', '%COMMENT%', '%FORCE%'], + [ + $this->parseTable($query, $options['table']), + $this->parsePartition($query, $options['partition']), + $this->parseDistinct($query, $options['distinct']), + $this->parseExtra($query, $options['extra']), + $this->parseField($query, $options['field'] ?? '*'), + $this->parseJoin($query, $options['join']), + $this->parseWhere($query, $options['where']), + $this->parseGroup($query, $options['group']), + $this->parseHaving($query, $options['having']), + $this->parseOrder($query, $options['order']), + $this->parseLimit($query, $one ? '1' : $options['limit']), + $this->parseUnion($query, $options['union']), + $this->parseLock($query, $options['lock']), + $this->parseComment($query, $options['comment']), + $this->parseForce($query, $options['force']), + ], + $this->selectSql); + } + + /** + * 生成Insert SQL + * @access public + * @param Query $query 查询对象 + * @return string + */ + public function insert(Query $query): string + { + $options = $query->getOptions(); + + // 分析并处理数据 + $data = $this->parseData($query, $options['data']); + if (empty($data)) { + return ''; + } + + $set = []; + foreach ($data as $key => $val) { + $set[] = $key . ' = ' . $val; + } + + return str_replace( + ['%INSERT%', '%EXTRA%', '%TABLE%', '%PARTITION%', '%SET%', '%DUPLICATE%', '%COMMENT%'], + [ + !empty($options['replace']) ? 'REPLACE' : 'INSERT', + $this->parseExtra($query, $options['extra']), + $this->parseTable($query, $options['table']), + $this->parsePartition($query, $options['partition']), + implode(' , ', $set), + $this->parseDuplicate($query, $options['duplicate']), + $this->parseComment($query, $options['comment']), + ], + $this->insertSql); + } + + /** + * 生成insertall SQL + * @access public + * @param Query $query 查询对象 + * @param array $dataSet 数据集 + * @param bool $replace 是否replace + * @return string + */ + public function insertAll(Query $query, array $dataSet, bool $replace = false): string + { + $options = $query->getOptions(); + + // 获取绑定信息 + $bind = $query->getFieldsBindType(); + + // 获取合法的字段 + if (empty($options['field']) || '*' == $options['field']) { + $allowFields = array_keys($bind); + } else { + $allowFields = $options['field']; + } + + $fields = []; + $values = []; + + foreach ($dataSet as $data) { + $data = $this->parseData($query, $data, $allowFields, $bind); + + $values[] = '( ' . implode(',', array_values($data)) . ' )'; + + if (!isset($insertFields)) { + $insertFields = array_keys($data); + } + } + + foreach ($insertFields as $field) { + $fields[] = $this->parseKey($query, $field); + } + + return str_replace( + ['%INSERT%', '%EXTRA%', '%TABLE%', '%PARTITION%', '%FIELD%', '%DATA%', '%DUPLICATE%', '%COMMENT%'], + [ + $replace ? 'REPLACE' : 'INSERT', + $this->parseExtra($query, $options['extra']), + $this->parseTable($query, $options['table']), + $this->parsePartition($query, $options['partition']), + implode(' , ', $fields), + implode(' , ', $values), + $this->parseDuplicate($query, $options['duplicate']), + $this->parseComment($query, $options['comment']), + ], + $this->insertAllSql); + } + + /** + * 生成update SQL + * @access public + * @param Query $query 查询对象 + * @return string + */ + public function update(Query $query): string + { + $options = $query->getOptions(); + + $data = $this->parseData($query, $options['data']); + + if (empty($data)) { + return ''; + } + $set = []; + foreach ($data as $key => $val) { + $set[] = $key . ' = ' . $val; + } + + return str_replace( + ['%TABLE%', '%PARTITION%', '%EXTRA%', '%SET%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'], + [ + $this->parseTable($query, $options['table']), + $this->parsePartition($query, $options['partition']), + $this->parseExtra($query, $options['extra']), + implode(' , ', $set), + $this->parseJoin($query, $options['join']), + $this->parseWhere($query, $options['where']), + $this->parseOrder($query, $options['order']), + $this->parseLimit($query, $options['limit']), + $this->parseLock($query, $options['lock']), + $this->parseComment($query, $options['comment']), + ], + $this->updateSql); + } + + /** + * 生成delete SQL + * @access public + * @param Query $query 查询对象 + * @return string + */ + public function delete(Query $query): string + { + $options = $query->getOptions(); + + return str_replace( + ['%TABLE%', '%PARTITION%', '%EXTRA%', '%USING%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'], + [ + $this->parseTable($query, $options['table']), + $this->parsePartition($query, $options['partition']), + $this->parseExtra($query, $options['extra']), + !empty($options['using']) ? ' USING ' . $this->parseTable($query, $options['using']) . ' ' : '', + $this->parseJoin($query, $options['join']), + $this->parseWhere($query, $options['where']), + $this->parseOrder($query, $options['order']), + $this->parseLimit($query, $options['limit']), + $this->parseLock($query, $options['lock']), + $this->parseComment($query, $options['comment']), + ], + $this->deleteSql); + } + + /** + * 正则查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @return string + */ + protected function parseRegexp(Query $query, string $key, string $exp, $value, string $field): string + { + if ($value instanceof Raw) { + $value = $this->parseRaw($query, $value); + } + + return $key . ' ' . $exp . ' ' . $value; + } + + /** + * FIND_IN_SET 查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @return string + */ + protected function parseFindInSet(Query $query, string $key, string $exp, $value, string $field): string + { + if ($value instanceof Raw) { + $value = $this->parseRaw($query, $value); + } + + return 'FIND_IN_SET(' . $value . ', ' . $key . ')'; + } + + /** + * 字段和表名处理 + * @access public + * @param Query $query 查询对象 + * @param mixed $key 字段名 + * @param bool $strict 严格检测 + * @return string + */ + public function parseKey(Query $query, $key, bool $strict = false): string + { + if (is_int($key)) { + return (string) $key; + } elseif ($key instanceof Raw) { + return $this->parseRaw($query, $key); + } + + $key = trim($key); + + if (strpos($key, '->>') && false === strpos($key, '(')) { + // JSON字段支持 + [$field, $name] = explode('->>', $key, 2); + + return $this->parseKey($query, $field, true) . '->>\'$' . (strpos($name, '[') === 0 ? '' : '.') . str_replace('->>', '.', $name) . '\''; + } elseif (strpos($key, '->') && false === strpos($key, '(')) { + // JSON字段支持 + [$field, $name] = explode('->', $key, 2); + return 'json_extract(' . $this->parseKey($query, $field, true) . ', \'$' . (strpos($name, '[') === 0 ? '' : '.') . str_replace('->', '.', $name) . '\')'; + } elseif (strpos($key, '.') && !preg_match('/[,\'\"\(\)`\s]/', $key)) { + [$table, $key] = explode('.', $key, 2); + + $alias = $query->getOptions('alias'); + + if ('__TABLE__' == $table) { + $table = $query->getOptions('table'); + $table = is_array($table) ? array_shift($table) : $table; + } + + if (isset($alias[$table])) { + $table = $alias[$table]; + } + } + + if ($strict && !preg_match('/^[\w\.\*]+$/', $key)) { + throw new Exception('not support data:' . $key); + } + + if ('*' != $key && !preg_match('/[,\'\"\*\(\)`.\s]/', $key)) { + $key = '`' . $key . '`'; + } + + if (isset($table)) { + if (strpos($table, '.')) { + $table = str_replace('.', '`.`', $table); + } + + $key = '`' . $table . '`.' . $key; + } + + return $key; + } + + /** + * 随机排序 + * @access protected + * @param Query $query 查询对象 + * @return string + */ + protected function parseRand(Query $query): string + { + return 'rand()'; + } + + /** + * Partition 分析 + * @access protected + * @param Query $query 查询对象 + * @param string|array $partition 分区 + * @return string + */ + protected function parsePartition(Query $query, $partition): string + { + if ('' == $partition) { + return ''; + } + + if (is_string($partition)) { + $partition = explode(',', $partition); + } + + return ' PARTITION (' . implode(' , ', $partition) . ') '; + } + + /** + * ON DUPLICATE KEY UPDATE 分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $duplicate + * @return string + */ + protected function parseDuplicate(Query $query, $duplicate): string + { + if ('' == $duplicate) { + return ''; + } + + if ($duplicate instanceof Raw) { + return ' ON DUPLICATE KEY UPDATE ' . $this->parseRaw($query, $duplicate) . ' '; + } + + if (is_string($duplicate)) { + $duplicate = explode(',', $duplicate); + } + + $updates = []; + foreach ($duplicate as $key => $val) { + if (is_numeric($key)) { + $val = $this->parseKey($query, $val); + $updates[] = $val . ' = VALUES(' . $val . ')'; + } elseif ($val instanceof Raw) { + $updates[] = $this->parseKey($query, $key) . " = " . $this->parseRaw($query, $val); + } else { + $name = $query->bindValue($val, $query->getConnection()->getFieldBindType($key)); + $updates[] = $this->parseKey($query, $key) . " = :" . $name; + } + } + + return ' ON DUPLICATE KEY UPDATE ' . implode(' , ', $updates) . ' '; + } +} diff --git a/vendor/topthink/think-orm/src/db/builder/Oracle.php b/vendor/topthink/think-orm/src/db/builder/Oracle.php new file mode 100644 index 0000000..77ad3c8 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/builder/Oracle.php @@ -0,0 +1,95 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\builder; + +use think\db\Builder; +use think\db\Query; + +/** + * Oracle数据库驱动 + */ +class Oracle extends Builder +{ + protected $selectSql = 'SELECT * FROM (SELECT thinkphp.*, rownum AS numrow FROM (SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%%ORDER%) thinkphp ) %LIMIT%%COMMENT%'; + + /** + * limit分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $limit + * @return string + */ + protected function parseLimit(Query $query, string $limit): string + { + $limitStr = ''; + + if (!empty($limit)) { + $limit = explode(',', $limit); + + if (count($limit) > 1) { + $limitStr = "(numrow>" . $limit[0] . ") AND (numrow<=" . ($limit[0] + $limit[1]) . ")"; + } else { + $limitStr = "(numrow>0 AND numrow<=" . $limit[0] . ")"; + } + + } + + return $limitStr ? ' WHERE ' . $limitStr : ''; + } + + /** + * 设置锁机制 + * @access protected + * @param Query $query 查询对象 + * @param bool|false $lock + * @return string + */ + protected function parseLock(Query $query, $lock = false): string + { + if (!$lock) { + return ''; + } + + return ' FOR UPDATE NOWAIT '; + } + + /** + * 字段和表名处理 + * @access public + * @param Query $query 查询对象 + * @param string $key + * @param string $strict + * @return string + */ + public function parseKey(Query $query, $key, bool $strict = false): string + { + $key = trim($key); + + if (strpos($key, '->') && false === strpos($key, '(')) { + // JSON字段支持 + [$field, $name] = explode($key, '->'); + $key = $field . '."' . $name . '"'; + } + + return $key; + } + + /** + * 随机排序 + * @access protected + * @param Query $query 查询对象 + * @return string + */ + protected function parseRand(Query $query): string + { + return 'DBMS_RANDOM.value'; + } +} diff --git a/vendor/topthink/think-orm/src/db/builder/Pgsql.php b/vendor/topthink/think-orm/src/db/builder/Pgsql.php new file mode 100644 index 0000000..4eace0a --- /dev/null +++ b/vendor/topthink/think-orm/src/db/builder/Pgsql.php @@ -0,0 +1,118 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\builder; + +use think\db\Builder; +use think\db\Query; +use think\db\Raw; + +/** + * Pgsql数据库驱动 + */ +class Pgsql extends Builder +{ + /** + * INSERT SQL表达式 + * @var string + */ + protected $insertSql = 'INSERT INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; + + /** + * INSERT ALL SQL表达式 + * @var string + */ + protected $insertAllSql = 'INSERT INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; + + /** + * limit分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $limit + * @return string + */ + public function parseLimit(Query $query, string $limit): string + { + $limitStr = ''; + + if (!empty($limit)) { + $limit = explode(',', $limit); + if (count($limit) > 1) { + $limitStr .= ' LIMIT ' . $limit[1] . ' OFFSET ' . $limit[0] . ' '; + } else { + $limitStr .= ' LIMIT ' . $limit[0] . ' '; + } + } + + return $limitStr; + } + + /** + * 字段和表名处理 + * @access public + * @param Query $query 查询对象 + * @param mixed $key 字段名 + * @param bool $strict 严格检测 + * @return string + */ + public function parseKey(Query $query, $key, bool $strict = false): string + { + if (is_int($key)) { + return (string) $key; + } elseif ($key instanceof Raw) { + return $this->parseRaw($query, $key); + } + + $key = trim($key); + + if (strpos($key, '->') && false === strpos($key, '(')) { + // JSON字段支持 + [$field, $name] = explode('->', $key); + $key = '"' . $field . '"' . '->>\'' . $name . '\''; + } elseif (strpos($key, '.')) { + [$table, $key] = explode('.', $key, 2); + + $alias = $query->getOptions('alias'); + + if ('__TABLE__' == $table) { + $table = $query->getOptions('table'); + $table = is_array($table) ? array_shift($table) : $table; + } + + if (isset($alias[$table])) { + $table = $alias[$table]; + } + + if ('*' != $key && !preg_match('/[,\"\*\(\).\s]/', $key)) { + $key = '"' . $key . '"'; + } + } + + if (isset($table)) { + $key = $table . '.' . $key; + } + + return $key; + } + + /** + * 随机排序 + * @access protected + * @param Query $query 查询对象 + * @return string + */ + protected function parseRand(Query $query): string + { + return 'RANDOM()'; + } + +} diff --git a/vendor/topthink/think-orm/src/db/builder/Sqlite.php b/vendor/topthink/think-orm/src/db/builder/Sqlite.php new file mode 100644 index 0000000..40cab7f --- /dev/null +++ b/vendor/topthink/think-orm/src/db/builder/Sqlite.php @@ -0,0 +1,97 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\builder; + +use think\db\Builder; +use think\db\Query; +use think\db\Raw; + +/** + * Sqlite数据库驱动 + */ +class Sqlite extends Builder +{ + /** + * limit + * @access public + * @param Query $query 查询对象 + * @param mixed $limit + * @return string + */ + public function parseLimit(Query $query, string $limit): string + { + $limitStr = ''; + + if (!empty($limit)) { + $limit = explode(',', $limit); + if (count($limit) > 1) { + $limitStr .= ' LIMIT ' . $limit[1] . ' OFFSET ' . $limit[0] . ' '; + } else { + $limitStr .= ' LIMIT ' . $limit[0] . ' '; + } + } + + return $limitStr; + } + + /** + * 随机排序 + * @access protected + * @param Query $query 查询对象 + * @return string + */ + protected function parseRand(Query $query): string + { + return 'RANDOM()'; + } + + /** + * 字段和表名处理 + * @access public + * @param Query $query 查询对象 + * @param mixed $key 字段名 + * @param bool $strict 严格检测 + * @return string + */ + public function parseKey(Query $query, $key, bool $strict = false): string + { + if (is_int($key)) { + return (string) $key; + } elseif ($key instanceof Raw) { + return $this->parseRaw($query, $key); + } + + $key = trim($key); + + if (strpos($key, '.')) { + [$table, $key] = explode('.', $key, 2); + + $alias = $query->getOptions('alias'); + + if ('__TABLE__' == $table) { + $table = $query->getOptions('table'); + $table = is_array($table) ? array_shift($table) : $table; + } + + if (isset($alias[$table])) { + $table = $alias[$table]; + } + } + + if (isset($table)) { + $key = $table . '.' . $key; + } + + return $key; + } +} diff --git a/vendor/topthink/think-orm/src/db/builder/Sqlsrv.php b/vendor/topthink/think-orm/src/db/builder/Sqlsrv.php new file mode 100644 index 0000000..779b5e3 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/builder/Sqlsrv.php @@ -0,0 +1,184 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\builder; + +use think\db\Builder; +use think\db\exception\DbException as Exception; +use think\db\Query; +use think\db\Raw; + +/** + * Sqlsrv数据库驱动 + */ +class Sqlsrv extends Builder +{ + /** + * SELECT SQL表达式 + * @var string + */ + protected $selectSql = 'SELECT T1.* FROM (SELECT thinkphp.*, ROW_NUMBER() OVER (%ORDER%) AS ROW_NUMBER FROM (SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%) AS thinkphp) AS T1 %LIMIT%%COMMENT%'; + /** + * SELECT INSERT SQL表达式 + * @var string + */ + protected $selectInsertSql = 'SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%'; + + /** + * UPDATE SQL表达式 + * @var string + */ + protected $updateSql = 'UPDATE %TABLE% SET %SET% FROM %TABLE% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%'; + + /** + * DELETE SQL表达式 + * @var string + */ + protected $deleteSql = 'DELETE FROM %TABLE% %USING% FROM %TABLE% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%'; + + /** + * INSERT SQL表达式 + * @var string + */ + protected $insertSql = 'INSERT INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; + + /** + * INSERT ALL SQL表达式 + * @var string + */ + protected $insertAllSql = 'INSERT INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; + + /** + * order分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $order + * @return string + */ + protected function parseOrder(Query $query, array $order): string + { + if (empty($order)) { + return ' ORDER BY rand()'; + } + + $array = []; + + foreach ($order as $key => $val) { + if ($val instanceof Raw) { + $array[] = $this->parseRaw($query, $val); + } elseif ('[rand]' == $val) { + $array[] = $this->parseRand($query); + } else { + if (is_numeric($key)) { + [$key, $sort] = explode(' ', strpos($val, ' ') ? $val : $val . ' '); + } else { + $sort = $val; + } + + $sort = in_array(strtolower($sort), ['asc', 'desc'], true) ? ' ' . $sort : ''; + $array[] = $this->parseKey($query, $key, true) . $sort; + } + } + + return ' ORDER BY ' . implode(',', $array); + } + + /** + * 随机排序 + * @access protected + * @param Query $query 查询对象 + * @return string + */ + protected function parseRand(Query $query): string + { + return 'rand()'; + } + + /** + * 字段和表名处理 + * @access public + * @param Query $query 查询对象 + * @param mixed $key 字段名 + * @param bool $strict 严格检测 + * @return string + */ + public function parseKey(Query $query, $key, bool $strict = false): string + { + if (is_int($key)) { + return (string) $key; + } elseif ($key instanceof Raw) { + return $this->parseRaw($query, $key); + } + + $key = trim($key); + + if (strpos($key, '.') && !preg_match('/[,\'\"\(\)\[\s]/', $key)) { + [$table, $key] = explode('.', $key, 2); + + $alias = $query->getOptions('alias'); + + if ('__TABLE__' == $table) { + $table = $query->getOptions('table'); + $table = is_array($table) ? array_shift($table) : $table; + } + + if (isset($alias[$table])) { + $table = $alias[$table]; + } + } + + if ($strict && !preg_match('/^[\w\.\*]+$/', $key)) { + throw new Exception('not support data:' . $key); + } + + if ('*' != $key && !preg_match('/[,\'\"\*\(\)\[.\s]/', $key)) { + $key = '[' . $key . ']'; + } + + if (isset($table)) { + $key = '[' . $table . '].' . $key; + } + + return $key; + } + + /** + * limit + * @access protected + * @param Query $query 查询对象 + * @param mixed $limit + * @return string + */ + protected function parseLimit(Query $query, string $limit): string + { + if (empty($limit)) { + return ''; + } + + $limit = explode(',', $limit); + + if (count($limit) > 1) { + $limitStr = '(T1.ROW_NUMBER BETWEEN ' . $limit[0] . ' + 1 AND ' . $limit[0] . ' + ' . $limit[1] . ')'; + } else { + $limitStr = '(T1.ROW_NUMBER BETWEEN 1 AND ' . $limit[0] . ")"; + } + + return 'WHERE ' . $limitStr; + } + + public function selectInsert(Query $query, array $fields, string $table): string + { + $this->selectSql = $this->selectInsertSql; + + return parent::selectInsert($query, $fields, $table); + } + +} diff --git a/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php b/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php new file mode 100644 index 0000000..dabfb92 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php @@ -0,0 +1,107 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\concern; + +use think\db\Raw; + +/** + * 聚合查询 + */ +trait AggregateQuery +{ + /** + * 聚合查询 + * @access protected + * @param string $aggregate 聚合方法 + * @param string|Raw $field 字段名 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + protected function aggregate(string $aggregate, $field, bool $force = false) + { + return $this->connection->aggregate($this, $aggregate, $field, $force); + } + + /** + * COUNT查询 + * @access public + * @param string|Raw $field 字段名 + * @return int + */ + public function count(string $field = '*'): int + { + if (!empty($this->options['group'])) { + // 支持GROUP + $options = $this->getOptions(); + $subSql = $this->options($options) + ->field('count(' . $field . ') AS think_count') + ->bind($this->bind) + ->buildSql(); + + $query = $this->newQuery()->table([$subSql => '_group_count_']); + + $count = $query->aggregate('COUNT', '*'); + } else { + $count = $this->aggregate('COUNT', $field); + } + + return (int) $count; + } + + /** + * SUM查询 + * @access public + * @param string|Raw $field 字段名 + * @return float + */ + public function sum($field): float + { + return $this->aggregate('SUM', $field, true); + } + + /** + * MIN查询 + * @access public + * @param string|Raw $field 字段名 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + public function min($field, bool $force = true) + { + return $this->aggregate('MIN', $field, $force); + } + + /** + * MAX查询 + * @access public + * @param string|Raw $field 字段名 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + public function max($field, bool $force = true) + { + return $this->aggregate('MAX', $field, $force); + } + + /** + * AVG查询 + * @access public + * @param string|Raw $field 字段名 + * @return float + */ + public function avg($field): float + { + return $this->aggregate('AVG', $field, true); + } + +} diff --git a/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php b/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php new file mode 100644 index 0000000..c33d1ed --- /dev/null +++ b/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php @@ -0,0 +1,229 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\concern; + +use think\db\Raw; +use think\helper\Str; + +/** + * JOIN和VIEW查询 + */ +trait JoinAndViewQuery +{ + + /** + * 查询SQL组装 join + * @access public + * @param mixed $join 关联的表名 + * @param mixed $condition 条件 + * @param string $type JOIN类型 + * @param array $bind 参数绑定 + * @return $this + */ + public function join($join, string $condition = null, string $type = 'INNER', array $bind = []) + { + $table = $this->getJoinTable($join); + + if (!empty($bind) && $condition) { + $this->bindParams($condition, $bind); + } + + $this->options['join'][] = [$table, strtoupper($type), $condition]; + + return $this; + } + + /** + * LEFT JOIN + * @access public + * @param mixed $join 关联的表名 + * @param mixed $condition 条件 + * @param array $bind 参数绑定 + * @return $this + */ + public function leftJoin($join, string $condition = null, array $bind = []) + { + return $this->join($join, $condition, 'LEFT', $bind); + } + + /** + * RIGHT JOIN + * @access public + * @param mixed $join 关联的表名 + * @param mixed $condition 条件 + * @param array $bind 参数绑定 + * @return $this + */ + public function rightJoin($join, string $condition = null, array $bind = []) + { + return $this->join($join, $condition, 'RIGHT', $bind); + } + + /** + * FULL JOIN + * @access public + * @param mixed $join 关联的表名 + * @param mixed $condition 条件 + * @param array $bind 参数绑定 + * @return $this + */ + public function fullJoin($join, string $condition = null, array $bind = []) + { + return $this->join($join, $condition, 'FULL'); + } + + /** + * 获取Join表名及别名 支持 + * ['prefix_table或者子查询'=>'alias'] 'table alias' + * @access protected + * @param array|string|Raw $join JION表名 + * @param string $alias 别名 + * @return string|array + */ + protected function getJoinTable($join, &$alias = null) + { + if (is_array($join)) { + $table = $join; + $alias = array_shift($join); + return $table; + } elseif ($join instanceof Raw) { + return $join; + } + + $join = trim($join); + + if (false !== strpos($join, '(')) { + // 使用子查询 + $table = $join; + } else { + // 使用别名 + if (strpos($join, ' ')) { + // 使用别名 + [$table, $alias] = explode(' ', $join); + } else { + $table = $join; + if (false === strpos($join, '.')) { + $alias = $join; + } + } + + if ($this->prefix && false === strpos($table, '.') && 0 !== strpos($table, $this->prefix)) { + $table = $this->getTable($table); + } + } + + if (!empty($alias) && $table != $alias) { + $table = [$table => $alias]; + } + + return $table; + } + + /** + * 指定JOIN查询字段 + * @access public + * @param string|array $join 数据表 + * @param string|array $field 查询字段 + * @param string $on JOIN条件 + * @param string $type JOIN类型 + * @param array $bind 参数绑定 + * @return $this + */ + public function view($join, $field = true, $on = null, string $type = 'INNER', array $bind = []) + { + $this->options['view'] = true; + + $fields = []; + $table = $this->getJoinTable($join, $alias); + + if (true === $field) { + $fields = $alias . '.*'; + } else { + if (is_string($field)) { + $field = explode(',', $field); + } + + foreach ($field as $key => $val) { + if (is_numeric($key)) { + $fields[] = $alias . '.' . $val; + + $this->options['map'][$val] = $alias . '.' . $val; + } else { + if (preg_match('/[,=\.\'\"\(\s]/', $key)) { + $name = $key; + } else { + $name = $alias . '.' . $key; + } + + $fields[] = $name . ' AS ' . $val; + + $this->options['map'][$val] = $name; + } + } + } + + $this->field($fields); + + if ($on) { + $this->join($table, $on, $type, $bind); + } else { + $this->table($table); + } + + return $this; + } + + /** + * 视图查询处理 + * @access protected + * @param array $options 查询参数 + * @return void + */ + protected function parseView(array &$options): void + { + foreach (['AND', 'OR'] as $logic) { + if (isset($options['where'][$logic])) { + foreach ($options['where'][$logic] as $key => $val) { + if (array_key_exists($key, $options['map'])) { + array_shift($val); + array_unshift($val, $options['map'][$key]); + $options['where'][$logic][$options['map'][$key]] = $val; + unset($options['where'][$logic][$key]); + } + } + } + } + + if (isset($options['order'])) { + // 视图查询排序处理 + foreach ($options['order'] as $key => $val) { + if (is_numeric($key) && is_string($val)) { + if (strpos($val, ' ')) { + [$field, $sort] = explode(' ', $val); + if (array_key_exists($field, $options['map'])) { + $options['order'][$options['map'][$field]] = $sort; + unset($options['order'][$key]); + } + } elseif (array_key_exists($val, $options['map'])) { + $options['order'][$options['map'][$val]] = 'asc'; + unset($options['order'][$key]); + } + } elseif (array_key_exists($key, $options['map'])) { + $options['order'][$options['map'][$key]] = $val; + unset($options['order'][$key]); + } + } + } + } + +} diff --git a/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php b/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php new file mode 100644 index 0000000..ffb72de --- /dev/null +++ b/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php @@ -0,0 +1,524 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\concern; + +use Closure; +use think\helper\Str; +use think\Model; +use think\model\Collection as ModelCollection; + +/** + * 模型及关联查询 + */ +trait ModelRelationQuery +{ + + /** + * 当前模型对象 + * @var Model + */ + protected $model; + + /** + * 指定模型 + * @access public + * @param Model $model 模型对象实例 + * @return $this + */ + public function model(Model $model) + { + $this->model = $model; + return $this; + } + + /** + * 获取当前的模型对象 + * @access public + * @return Model|null + */ + public function getModel() + { + return $this->model; + } + + /** + * 设置需要隐藏的输出属性 + * @access public + * @param array $hidden 需要隐藏的字段名 + * @return $this + */ + public function hidden(array $hidden) + { + $this->options['hidden'] = $hidden; + return $this; + } + + /** + * 设置需要输出的属性 + * @access public + * @param array $visible 需要输出的属性 + * @return $this + */ + public function visible(array $visible) + { + $this->options['visible'] = $visible; + return $this; + } + + /** + * 设置需要追加输出的属性 + * @access public + * @param array $append 需要追加的属性 + * @return $this + */ + public function append(array $append) + { + $this->options['append'] = $append; + return $this; + } + + /** + * 添加查询范围 + * @access public + * @param array|string|Closure $scope 查询范围定义 + * @param array $args 参数 + * @return $this + */ + public function scope($scope, ...$args) + { + // 查询范围的第一个参数始终是当前查询对象 + array_unshift($args, $this); + + if ($scope instanceof Closure) { + call_user_func_array($scope, $args); + return $this; + } + + if (is_string($scope)) { + $scope = explode(',', $scope); + } + + if ($this->model) { + // 检查模型类的查询范围方法 + foreach ($scope as $name) { + $method = 'scope' . trim($name); + + if (method_exists($this->model, $method)) { + call_user_func_array([$this->model, $method], $args); + } + } + } + + return $this; + } + + /** + * 设置关联查询 + * @access public + * @param array $relation 关联名称 + * @return $this + */ + public function relation(array $relation) + { + if (!empty($relation)) { + $this->options['relation'] = $relation; + } + + return $this; + } + + /** + * 使用搜索器条件搜索字段 + * @access public + * @param string|array $fields 搜索字段 + * @param mixed $data 搜索数据 + * @param string $prefix 字段前缀标识 + * @return $this + */ + public function withSearch($fields, $data = [], string $prefix = '') + { + if (is_string($fields)) { + $fields = explode(',', $fields); + } + + $likeFields = $this->getConfig('match_like_fields') ?: []; + + foreach ($fields as $key => $field) { + if ($field instanceof Closure) { + $field($this, $data[$key] ?? null, $data, $prefix); + } elseif ($this->model) { + // 检测搜索器 + $fieldName = is_numeric($key) ? $field : $key; + $method = 'search' . Str::studly($fieldName) . 'Attr'; + + if (method_exists($this->model, $method)) { + $this->model->$method($this, $data[$field] ?? null, $data, $prefix); + } elseif (isset($data[$field])) { + $this->where($fieldName, in_array($fieldName, $likeFields) ? 'like' : '=', in_array($fieldName, $likeFields) ? '%' . $data[$field] . '%' : $data[$field]); + } + } + } + + return $this; + } + + /** + * 设置数据字段获取器 + * @access public + * @param string|array $name 字段名 + * @param callable $callback 闭包获取器 + * @return $this + */ + public function withAttr($name, callable $callback = null) + { + if (is_array($name)) { + $this->options['with_attr'] = $name; + } else { + $this->options['with_attr'][$name] = $callback; + } + + return $this; + } + + /** + * 关联预载入 In方式 + * @access public + * @param array|string $with 关联方法名称 + * @return $this + */ + public function with($with) + { + if (!empty($with)) { + $this->options['with'] = (array) $with; + } + + return $this; + } + + /** + * 关联预载入 JOIN方式 + * @access protected + * @param array|string $with 关联方法名 + * @param string $joinType JOIN方式 + * @return $this + */ + public function withJoin($with, string $joinType = '') + { + if (empty($with)) { + return $this; + } + + $with = (array) $with; + $first = true; + + foreach ($with as $key => $relation) { + $closure = null; + $field = true; + + if ($relation instanceof Closure) { + // 支持闭包查询过滤关联条件 + $closure = $relation; + $relation = $key; + } elseif (is_array($relation)) { + $field = $relation; + $relation = $key; + } elseif (is_string($relation) && strpos($relation, '.')) { + $relation = strstr($relation, '.', true); + } + + $result = $this->model->eagerly($this, $relation, $field, $joinType, $closure, $first); + + if (!$result) { + unset($with[$key]); + } else { + $first = false; + } + } + + $this->via(); + + $this->options['with_join'] = $with; + + return $this; + } + + /** + * 关联统计 + * @access protected + * @param array|string $relations 关联方法名 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + protected function withAggregate($relations, string $aggregate = 'count', $field = '*', bool $subQuery = true) + { + if (!$subQuery) { + $this->options['with_count'][] = [$relations, $aggregate, $field]; + } else { + if (!isset($this->options['field'])) { + $this->field('*'); + } + + $this->model->relationCount($this, (array) $relations, $aggregate, $field, true); + } + + return $this; + } + + /** + * 关联缓存 + * @access public + * @param string|array|bool $relation 关联方法名 + * @param mixed $key 缓存key + * @param integer|\DateTime $expire 缓存有效期 + * @param string $tag 缓存标签 + * @return $this + */ + public function withCache($relation = true, $key = true, $expire = null, string $tag = null) + { + if (false === $relation || false === $key || !$this->getConnection()->getCache()) { + return $this; + } + + if ($key instanceof \DateTimeInterface || $key instanceof \DateInterval || (is_int($key) && is_null($expire))) { + $expire = $key; + $key = true; + } + + if (true === $relation || is_numeric($relation)) { + $this->options['with_cache'] = $relation; + return $this; + } + + $relations = (array) $relation; + foreach ($relations as $name => $relation) { + if (!is_numeric($name)) { + $this->options['with_cache'][$name] = is_array($relation) ? $relation : [$key, $relation, $tag]; + } else { + $this->options['with_cache'][$relation] = [$key, $expire, $tag]; + } + } + + return $this; + } + + /** + * 关联统计 + * @access public + * @param string|array $relation 关联方法名 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withCount($relation, bool $subQuery = true) + { + return $this->withAggregate($relation, 'count', '*', $subQuery); + } + + /** + * 关联统计Sum + * @access public + * @param string|array $relation 关联方法名 + * @param string $field 字段 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withSum($relation, string $field, bool $subQuery = true) + { + return $this->withAggregate($relation, 'sum', $field, $subQuery); + } + + /** + * 关联统计Max + * @access public + * @param string|array $relation 关联方法名 + * @param string $field 字段 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withMax($relation, string $field, bool $subQuery = true) + { + return $this->withAggregate($relation, 'max', $field, $subQuery); + } + + /** + * 关联统计Min + * @access public + * @param string|array $relation 关联方法名 + * @param string $field 字段 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withMin($relation, string $field, bool $subQuery = true) + { + return $this->withAggregate($relation, 'min', $field, $subQuery); + } + + /** + * 关联统计Avg + * @access public + * @param string|array $relation 关联方法名 + * @param string $field 字段 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withAvg($relation, string $field, bool $subQuery = true) + { + return $this->withAggregate($relation, 'avg', $field, $subQuery); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $relation 关联方法名 + * @param mixed $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return $this + */ + public function has(string $relation, string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '') + { + return $this->model->has($relation, $operator, $count, $id, $joinType, $this); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $relation 关联方法名 + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @param string $joinType JOIN类型 + * @return $this + */ + public function hasWhere(string $relation, $where = [], string $fields = '*', string $joinType = '') + { + return $this->model->hasWhere($relation, $where, $fields, $joinType, $this); + } + + /** + * 查询数据转换为模型数据集对象 + * @access protected + * @param array $resultSet 数据集 + * @return ModelCollection + */ + protected function resultSetToModelCollection(array $resultSet): ModelCollection + { + if (empty($resultSet)) { + return $this->model->toCollection(); + } + + // 检查动态获取器 + if (!empty($this->options['with_attr'])) { + foreach ($this->options['with_attr'] as $name => $val) { + if (strpos($name, '.')) { + [$relation, $field] = explode('.', $name); + + $withRelationAttr[$relation][$field] = $val; + unset($this->options['with_attr'][$name]); + } + } + } + + $withRelationAttr = $withRelationAttr ?? []; + + foreach ($resultSet as $key => &$result) { + // 数据转换为模型对象 + $this->resultToModel($result, $this->options, true, $withRelationAttr); + } + + if (!empty($this->options['with'])) { + // 预载入 + $result->eagerlyResultSet($resultSet, $this->options['with'], $withRelationAttr, false, $this->options['with_cache'] ?? false); + } + + if (!empty($this->options['with_join'])) { + // 预载入 + $result->eagerlyResultSet($resultSet, $this->options['with_join'], $withRelationAttr, true, $this->options['with_cache'] ?? false); + } + + // 模型数据集转换 + return $this->model->toCollection($resultSet); + } + + /** + * 查询数据转换为模型对象 + * @access protected + * @param array $result 查询数据 + * @param array $options 查询参数 + * @param bool $resultSet 是否为数据集查询 + * @param array $withRelationAttr 关联字段获取器 + * @return void + */ + protected function resultToModel(array &$result, array $options = [], bool $resultSet = false, array $withRelationAttr = []): void + { + // 动态获取器 + if (!empty($options['with_attr']) && empty($withRelationAttr)) { + foreach ($options['with_attr'] as $name => $val) { + if (strpos($name, '.')) { + [$relation, $field] = explode('.', $name); + + $withRelationAttr[$relation][$field] = $val; + unset($options['with_attr'][$name]); + } + } + } + + // JSON 数据处理 + if (!empty($options['json'])) { + $this->jsonResult($result, $options['json'], $options['json_assoc'], $withRelationAttr); + } + + $result = $this->model + ->newInstance($result, $resultSet ? null : $this->getModelUpdateCondition($options)); + + // 动态获取器 + if (!empty($options['with_attr'])) { + $result->withAttribute($options['with_attr']); + } + + // 输出属性控制 + if (!empty($options['visible'])) { + $result->visible($options['visible']); + } elseif (!empty($options['hidden'])) { + $result->hidden($options['hidden']); + } + + if (!empty($options['append'])) { + $result->append($options['append']); + } + + // 关联查询 + if (!empty($options['relation'])) { + $result->relationQuery($options['relation'], $withRelationAttr); + } + + // 预载入查询 + if (!$resultSet && !empty($options['with'])) { + $result->eagerlyResult($result, $options['with'], $withRelationAttr, false, $options['with_cache'] ?? false); + } + + // JOIN预载入查询 + if (!$resultSet && !empty($options['with_join'])) { + $result->eagerlyResult($result, $options['with_join'], $withRelationAttr, true, $options['with_cache'] ?? false); + } + + // 关联统计 + if (!empty($options['with_count'])) { + foreach ($options['with_count'] as $val) { + $result->relationCount($this, (array) $val[0], $val[1], $val[2], false); + } + } + } + +} diff --git a/vendor/topthink/think-orm/src/db/concern/ParamsBind.php b/vendor/topthink/think-orm/src/db/concern/ParamsBind.php new file mode 100644 index 0000000..296e221 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/concern/ParamsBind.php @@ -0,0 +1,106 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\concern; + +use PDO; + +/** + * 参数绑定支持 + */ +trait ParamsBind +{ + /** + * 当前参数绑定 + * @var array + */ + protected $bind = []; + + /** + * 批量参数绑定 + * @access public + * @param array $value 绑定变量值 + * @return $this + */ + public function bind(array $value) + { + $this->bind = array_merge($this->bind, $value); + return $this; + } + + /** + * 单个参数绑定 + * @access public + * @param mixed $value 绑定变量值 + * @param integer $type 绑定类型 + * @param string $name 绑定标识 + * @return string + */ + public function bindValue($value, int $type = null, string $name = null) + { + $name = $name ?: 'ThinkBind_' . (count($this->bind) + 1) . '_' . mt_rand() . '_'; + + $this->bind[$name] = [$value, $type ?: PDO::PARAM_STR]; + return $name; + } + + /** + * 检测参数是否已经绑定 + * @access public + * @param string $key 参数名 + * @return bool + */ + public function isBind($key) + { + return isset($this->bind[$key]); + } + + /** + * 参数绑定 + * @access public + * @param string $sql 绑定的sql表达式 + * @param array $bind 参数绑定 + * @return void + */ + public function bindParams(string &$sql, array $bind = []): void + { + foreach ($bind as $key => $value) { + if (is_array($value)) { + $name = $this->bindValue($value[0], $value[1], $value[2] ?? null); + } else { + $name = $this->bindValue($value); + } + + if (is_numeric($key)) { + $sql = substr_replace($sql, ':' . $name, strpos($sql, '?'), 1); + } else { + $sql = str_replace(':' . $key, ':' . $name, $sql); + } + } + } + + /** + * 获取绑定的参数 并清空 + * @access public + * @param bool $clear 是否清空绑定数据 + * @return array + */ + public function getBind(bool $clear = true): array + { + $bind = $this->bind; + if ($clear) { + $this->bind = []; + } + + return $bind; + } +} diff --git a/vendor/topthink/think-orm/src/db/concern/ResultOperation.php b/vendor/topthink/think-orm/src/db/concern/ResultOperation.php new file mode 100644 index 0000000..77409d1 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/concern/ResultOperation.php @@ -0,0 +1,254 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\concern; + +use Closure; +use think\Collection; +use think\db\exception\DataNotFoundException; +use think\db\exception\DbException; +use think\db\exception\ModelNotFoundException; +use think\db\Query; +use think\helper\Str; +use think\Model; + +/** + * 查询数据处理 + */ +trait ResultOperation +{ + /** + * 是否允许返回空数据(或空模型) + * @access public + * @param bool $allowEmpty 是否允许为空 + * @return $this + */ + public function allowEmpty(bool $allowEmpty = true) + { + $this->options['allow_empty'] = $allowEmpty; + return $this; + } + + /** + * 设置查询数据不存在是否抛出异常 + * @access public + * @param bool $fail 数据不存在是否抛出异常 + * @return $this + */ + public function failException(bool $fail = true) + { + $this->options['fail'] = $fail; + return $this; + } + + /** + * 处理数据 + * @access protected + * @param array $result 查询数据 + * @return void + */ + protected function result(array &$result): void + { + if (!empty($this->options['json'])) { + $this->jsonResult($result, $this->options['json'], true); + } + + if (!empty($this->options['with_attr'])) { + $this->getResultAttr($result, $this->options['with_attr']); + } + + $this->filterResult($result); + } + + /** + * 处理数据集 + * @access public + * @param array $resultSet 数据集 + * @param bool $toCollection 是否转为对象 + * @return void + */ + protected function resultSet(array &$resultSet, bool $toCollection = true): void + { + if (!empty($this->options['json'])) { + foreach ($resultSet as &$result) { + $this->jsonResult($result, $this->options['json'], true); + } + } + + if (!empty($this->options['with_attr'])) { + foreach ($resultSet as &$result) { + $this->getResultAttr($result, $this->options['with_attr']); + } + } + + if (!empty($this->options['visible']) || !empty($this->options['hidden'])) { + foreach ($resultSet as &$result) { + $this->filterResult($result); + } + } + + // 返回Collection对象 + if ($toCollection) { + $resultSet = new Collection($resultSet); + } + } + + /** + * 处理数据的可见和隐藏 + * @access protected + * @param array $result 查询数据 + * @return void + */ + protected function filterResult(&$result): void + { + $array = []; + if (!empty($this->options['visible'])) { + foreach ($this->options['visible'] as $key) { + $array[] = $key; + } + $result = array_intersect_key($result, array_flip($array)); + } elseif (!empty($this->options['hidden'])) { + foreach ($this->options['hidden'] as $key) { + $array[] = $key; + } + $result = array_diff_key($result, array_flip($array)); + } + } + + /** + * 使用获取器处理数据 + * @access protected + * @param array $result 查询数据 + * @param array $withAttr 字段获取器 + * @return void + */ + protected function getResultAttr(array &$result, array $withAttr = []): void + { + foreach ($withAttr as $name => $closure) { + $name = Str::snake($name); + + if (strpos($name, '.')) { + // 支持JSON字段 获取器定义 + [$key, $field] = explode('.', $name); + + if (isset($result[$key])) { + $result[$key][$field] = $closure($result[$key][$field] ?? null, $result[$key]); + } + } else { + $result[$name] = $closure($result[$name] ?? null, $result); + } + } + } + + /** + * 处理空数据 + * @access protected + * @return array|Model|null|static + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + protected function resultToEmpty() + { + if (!empty($this->options['fail'])) { + $this->throwNotFound(); + } elseif (!empty($this->options['allow_empty'])) { + return !empty($this->model) ? $this->model->newInstance() : []; + } + } + + /** + * 查找单条记录 不存在返回空数据(或者空模型) + * @access public + * @param mixed $data 数据 + * @return array|Model|static + */ + public function findOrEmpty($data = null) + { + return $this->allowEmpty(true)->find($data); + } + + /** + * JSON字段数据转换 + * @access protected + * @param array $result 查询数据 + * @param array $json JSON字段 + * @param bool $assoc 是否转换为数组 + * @param array $withRelationAttr 关联获取器 + * @return void + */ + protected function jsonResult(array &$result, array $json = [], bool $assoc = false, array $withRelationAttr = []): void + { + foreach ($json as $name) { + if (!isset($result[$name])) { + continue; + } + + $result[$name] = json_decode($result[$name], true); + + if (isset($withRelationAttr[$name])) { + foreach ($withRelationAttr[$name] as $key => $closure) { + $result[$name][$key] = $closure($result[$name][$key] ?? null, $result[$name]); + } + } + + if (!$assoc) { + $result[$name] = (object) $result[$name]; + } + } + } + + /** + * 查询失败 抛出异常 + * @access protected + * @return void + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + protected function throwNotFound(): void + { + if (!empty($this->model)) { + $class = get_class($this->model); + throw new ModelNotFoundException('model data Not Found:' . $class, $class, $this->options); + } + + $table = $this->getTable(); + throw new DataNotFoundException('table data not Found:' . $table, $table, $this->options); + } + + /** + * 查找多条记录 如果不存在则抛出异常 + * @access public + * @param array|string|Query|Closure $data 数据 + * @return array|Collection|static[] + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function selectOrFail($data = null) + { + return $this->failException(true)->select($data); + } + + /** + * 查找单条记录 如果不存在则抛出异常 + * @access public + * @param array|string|Query|Closure $data 数据 + * @return array|Model|static + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function findOrFail($data = null) + { + return $this->failException(true)->find($data); + } + +} diff --git a/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php b/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php new file mode 100644 index 0000000..9070bef --- /dev/null +++ b/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php @@ -0,0 +1,99 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\concern; + +/** + * 数据字段信息 + */ +trait TableFieldInfo +{ + + /** + * 获取数据表字段信息 + * @access public + * @param string $tableName 数据表名 + * @return array + */ + public function getTableFields($tableName = ''): array + { + if ('' == $tableName) { + $tableName = $this->getTable(); + } + + return $this->connection->getTableFields($tableName); + } + + /** + * 获取详细字段类型信息 + * @access public + * @param string $tableName 数据表名称 + * @return array + */ + public function getFields(string $tableName = ''): array + { + return $this->connection->getFields($tableName ?: $this->getTable()); + } + + /** + * 获取字段类型信息 + * @access public + * @return array + */ + public function getFieldsType(): array + { + if (!empty($this->options['field_type'])) { + return $this->options['field_type']; + } + + return $this->connection->getFieldsType($this->getTable()); + } + + /** + * 获取字段类型信息 + * @access public + * @param string $field 字段名 + * @return string|null + */ + public function getFieldType(string $field) + { + $fieldType = $this->getFieldsType(); + + return $fieldType[$field] ?? null; + } + + /** + * 获取字段类型信息 + * @access public + * @return array + */ + public function getFieldsBindType(): array + { + $fieldType = $this->getFieldsType(); + + return array_map([$this->connection, 'getFieldBindType'], $fieldType); + } + + /** + * 获取字段类型信息 + * @access public + * @param string $field 字段名 + * @return int + */ + public function getFieldBindType(string $field): int + { + $fieldType = $this->getFieldType($field); + + return $this->connection->getFieldBindType($fieldType ?: ''); + } + +} diff --git a/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php b/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php new file mode 100644 index 0000000..69b7eae --- /dev/null +++ b/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php @@ -0,0 +1,214 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\concern; + +/** + * 时间查询支持 + */ +trait TimeFieldQuery +{ + /** + * 日期查询表达式 + * @var array + */ + protected $timeRule = [ + 'today' => ['today', 'tomorrow -1second'], + 'yesterday' => ['yesterday', 'today -1second'], + 'week' => ['this week 00:00:00', 'next week 00:00:00 -1second'], + 'last week' => ['last week 00:00:00', 'this week 00:00:00 -1second'], + 'month' => ['first Day of this month 00:00:00', 'first Day of next month 00:00:00 -1second'], + 'last month' => ['first Day of last month 00:00:00', 'first Day of this month 00:00:00 -1second'], + 'year' => ['this year 1/1', 'next year 1/1 -1second'], + 'last year' => ['last year 1/1', 'this year 1/1 -1second'], + ]; + + /** + * 添加日期或者时间查询规则 + * @access public + * @param array $rule 时间表达式 + * @return $this + */ + public function timeRule(array $rule) + { + $this->timeRule = array_merge($this->timeRule, $rule); + return $this; + } + + /** + * 查询日期或者时间 + * @access public + * @param string $field 日期字段名 + * @param string $op 比较运算符或者表达式 + * @param string|array $range 比较范围 + * @param string $logic AND OR + * @return $this + */ + public function whereTime(string $field, string $op, $range = null, string $logic = 'AND') + { + if (is_null($range)) { + if (isset($this->timeRule[$op])) { + $range = $this->timeRule[$op]; + } else { + $range = $op; + } + $op = is_array($range) ? 'between' : '>='; + } + + return $this->parseWhereExp($logic, $field, strtolower($op) . ' time', $range, [], true); + } + + /** + * 查询某个时间间隔数据 + * @access public + * @param string $field 日期字段名 + * @param string $start 开始时间 + * @param string $interval 时间间隔单位 day/month/year/week/hour/minute/second + * @param int $step 间隔 + * @param string $logic AND OR + * @return $this + */ + public function whereTimeInterval(string $field, string $start, string $interval = 'day', int $step = 1, string $logic = 'AND') + { + $startTime = strtotime($start); + $endTime = strtotime(($step > 0 ? '+' : '-') . abs($step) . ' ' . $interval . (abs($step) > 1 ? 's' : ''), $startTime); + + return $this->whereTime($field, 'between', $step > 0 ? [$startTime, $endTime - 1] : [$endTime, $startTime - 1], $logic); + } + + /** + * 查询月数据 whereMonth('time_field', '2018-1') + * @access public + * @param string $field 日期字段名 + * @param string $month 月份信息 + * @param int $step 间隔 + * @param string $logic AND OR + * @return $this + */ + public function whereMonth(string $field, string $month = 'this month', int $step = 1, string $logic = 'AND') + { + if (in_array($month, ['this month', 'last month'])) { + $month = date('Y-m', strtotime($month)); + } + + return $this->whereTimeInterval($field, $month, 'month', $step, $logic); + } + + /** + * 查询周数据 whereWeek('time_field', '2018-1-1') 从2018-1-1开始的一周数据 + * @access public + * @param string $field 日期字段名 + * @param string $week 周信息 + * @param int $step 间隔 + * @param string $logic AND OR + * @return $this + */ + public function whereWeek(string $field, string $week = 'this week', int $step = 1, string $logic = 'AND') + { + if (in_array($week, ['this week', 'last week'])) { + $week = date('Y-m-d', strtotime($week)); + } + + return $this->whereTimeInterval($field, $week, 'week', $step, $logic); + } + + /** + * 查询年数据 whereYear('time_field', '2018') + * @access public + * @param string $field 日期字段名 + * @param string $year 年份信息 + * @param int $step 间隔 + * @param string $logic AND OR + * @return $this + */ + public function whereYear(string $field, string $year = 'this year', int $step = 1, string $logic = 'AND') + { + if (in_array($year, ['this year', 'last year'])) { + $year = date('Y', strtotime($year)); + } + + return $this->whereTimeInterval($field, $year . '-1-1', 'year', $step, $logic); + } + + /** + * 查询日数据 whereDay('time_field', '2018-1-1') + * @access public + * @param string $field 日期字段名 + * @param string $day 日期信息 + * @param int $step 间隔 + * @param string $logic AND OR + * @return $this + */ + public function whereDay(string $field, string $day = 'today', int $step = 1, string $logic = 'AND') + { + if (in_array($day, ['today', 'yesterday'])) { + $day = date('Y-m-d', strtotime($day)); + } + + return $this->whereTimeInterval($field, $day, 'day', $step, $logic); + } + + /** + * 查询日期或者时间范围 whereBetweenTime('time_field', '2018-1-1','2018-1-15') + * @access public + * @param string $field 日期字段名 + * @param string|int $startTime 开始时间 + * @param string|int $endTime 结束时间 + * @param string $logic AND OR + * @return $this + */ + public function whereBetweenTime(string $field, $startTime, $endTime, string $logic = 'AND') + { + return $this->whereTime($field, 'between', [$startTime, $endTime], $logic); + } + + /** + * 查询日期或者时间范围 whereNotBetweenTime('time_field', '2018-1-1','2018-1-15') + * @access public + * @param string $field 日期字段名 + * @param string|int $startTime 开始时间 + * @param string|int $endTime 结束时间 + * @return $this + */ + public function whereNotBetweenTime(string $field, $startTime, $endTime) + { + return $this->whereTime($field, '<', $startTime) + ->whereTime($field, '>', $endTime, 'OR'); + } + + /** + * 查询当前时间在两个时间字段范围 whereBetweenTimeField('start_time', 'end_time') + * @access public + * @param string $startField 开始时间字段 + * @param string $endField 结束时间字段 + * @return $this + */ + public function whereBetweenTimeField(string $startField, string $endField) + { + return $this->whereTime($startField, '<=', time()) + ->whereTime($endField, '>=', time()); + } + + /** + * 查询当前时间不在两个时间字段范围 whereNotBetweenTimeField('start_time', 'end_time') + * @access public + * @param string $startField 开始时间字段 + * @param string $endField 结束时间字段 + * @return $this + */ + public function whereNotBetweenTimeField(string $startField, string $endField) + { + return $this->whereTime($startField, '>', time()) + ->whereTime($endField, '<', time(), 'OR'); + } + +} diff --git a/vendor/topthink/think-orm/src/db/concern/Transaction.php b/vendor/topthink/think-orm/src/db/concern/Transaction.php new file mode 100644 index 0000000..b586132 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/concern/Transaction.php @@ -0,0 +1,122 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\concern; + +/** + * 事务支持 + */ +trait Transaction +{ + + /** + * 执行数据库Xa事务 + * @access public + * @param callable $callback 数据操作方法回调 + * @param array $dbs 多个查询对象或者连接对象 + * @return mixed + * @throws PDOException + * @throws \Exception + * @throws \Throwable + */ + public function transactionXa(callable $callback, array $dbs = []) + { + return $this->connection->transactionXa($callback, $dbs); + } + + /** + * 执行数据库事务 + * @access public + * @param callable $callback 数据操作方法回调 + * @return mixed + */ + public function transaction(callable $callback) + { + return $this->connection->transaction($callback); + } + + /** + * 启动事务 + * @access public + * @return void + */ + public function startTrans(): void + { + $this->connection->startTrans(); + } + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return void + * @throws PDOException + */ + public function commit(): void + { + $this->connection->commit(); + } + + /** + * 事务回滚 + * @access public + * @return void + * @throws PDOException + */ + public function rollback(): void + { + $this->connection->rollback(); + } + + /** + * 启动XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function startTransXa(string $xid): void + { + $this->connection->startTransXa($xid); + } + + /** + * 预编译XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function prepareXa(string $xid): void + { + $this->connection->prepareXa($xid); + } + + /** + * 提交XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function commitXa(string $xid): void + { + $this->connection->commitXa($xid); + } + + /** + * 回滚XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function rollbackXa(string $xid): void + { + $this->connection->rollbackXa($xid); + } +} diff --git a/vendor/topthink/think-orm/src/db/concern/WhereQuery.php b/vendor/topthink/think-orm/src/db/concern/WhereQuery.php new file mode 100644 index 0000000..d2deb03 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/concern/WhereQuery.php @@ -0,0 +1,532 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\concern; + +use Closure; +use think\db\BaseQuery; +use think\db\Raw; + +trait WhereQuery +{ + /** + * 指定AND查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @return $this + */ + public function where($field, $op = null, $condition = null) + { + if ($field instanceof $this) { + $this->parseQueryWhere($field); + return $this; + } elseif (true === $field || 1 === $field) { + $this->options['where']['AND'][] = true; + return $this; + } + + $param = func_get_args(); + array_shift($param); + return $this->parseWhereExp('AND', $field, $op, $condition, $param); + } + + /** + * 解析Query对象查询条件 + * @access public + * @param BaseQuery $query 查询对象 + * @return void + */ + protected function parseQueryWhere(BaseQuery $query): void + { + $this->options['where'] = $query->getOptions('where') ?? []; + + if ($query->getOptions('via')) { + $via = $query->getOptions('via'); + foreach ($this->options['where'] as $logic => &$where) { + foreach ($where as $key => &$val) { + if (is_array($val) && !strpos($val[0], '.')) { + $val[0] = $via . '.' . $val[0]; + } + } + } + } + + $this->bind($query->getBind(false)); + } + + /** + * 指定OR查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @return $this + */ + public function whereOr($field, $op = null, $condition = null) + { + $param = func_get_args(); + array_shift($param); + return $this->parseWhereExp('OR', $field, $op, $condition, $param); + } + + /** + * 指定XOR查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @return $this + */ + public function whereXor($field, $op = null, $condition = null) + { + $param = func_get_args(); + array_shift($param); + return $this->parseWhereExp('XOR', $field, $op, $condition, $param); + } + + /** + * 指定Null查询条件 + * @access public + * @param mixed $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNull(string $field, string $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'NULL', null, [], true); + } + + /** + * 指定NotNull查询条件 + * @access public + * @param mixed $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotNull(string $field, string $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'NOTNULL', null, [], true); + } + + /** + * 指定Exists查询条件 + * @access public + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereExists($condition, string $logic = 'AND') + { + if (is_string($condition)) { + $condition = new Raw($condition); + } + + $this->options['where'][strtoupper($logic)][] = ['', 'EXISTS', $condition]; + return $this; + } + + /** + * 指定NotExists查询条件 + * @access public + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotExists($condition, string $logic = 'AND') + { + if (is_string($condition)) { + $condition = new Raw($condition); + } + + $this->options['where'][strtoupper($logic)][] = ['', 'NOT EXISTS', $condition]; + return $this; + } + + /** + * 指定In查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereIn(string $field, $condition, string $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'IN', $condition, [], true); + } + + /** + * 指定NotIn查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotIn(string $field, $condition, string $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'NOT IN', $condition, [], true); + } + + /** + * 指定Like查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereLike(string $field, $condition, string $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'LIKE', $condition, [], true); + } + + /** + * 指定NotLike查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotLike(string $field, $condition, string $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'NOT LIKE', $condition, [], true); + } + + /** + * 指定Between查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereBetween(string $field, $condition, string $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'BETWEEN', $condition, [], true); + } + + /** + * 指定NotBetween查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotBetween(string $field, $condition, string $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'NOT BETWEEN', $condition, [], true); + } + + /** + * 指定FIND_IN_SET查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereFindInSet(string $field, $condition, string $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'FIND IN SET', $condition, [], true); + } + + /** + * 比较两个字段 + * @access public + * @param string $field1 查询字段 + * @param string $operator 比较操作符 + * @param string $field2 比较字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereColumn(string $field1, string $operator, string $field2 = null, string $logic = 'AND') + { + if (is_null($field2)) { + $field2 = $operator; + $operator = '='; + } + + return $this->parseWhereExp($logic, $field1, 'COLUMN', [$operator, $field2], [], true); + } + + /** + * 设置软删除字段及条件 + * @access public + * @param string $field 查询字段 + * @param mixed $condition 查询条件 + * @return $this + */ + public function useSoftDelete(string $field, $condition = null) + { + if ($field) { + $this->options['soft_delete'] = [$field, $condition]; + } + + return $this; + } + + /** + * 指定Exp查询条件 + * @access public + * @param mixed $field 查询字段 + * @param string $where 查询条件 + * @param array $bind 参数绑定 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereExp(string $field, string $where, array $bind = [], string $logic = 'AND') + { + $this->options['where'][$logic][] = [$field, 'EXP', new Raw($where, $bind)]; + + return $this; + } + + /** + * 指定字段Raw查询 + * @access public + * @param string $field 查询字段表达式 + * @param mixed $op 查询表达式 + * @param string $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereFieldRaw(string $field, $op, $condition = null, string $logic = 'AND') + { + if (is_null($condition)) { + $condition = $op; + $op = '='; + } + + $this->options['where'][$logic][] = [new Raw($field), $op, $condition]; + return $this; + } + + /** + * 指定表达式查询条件 + * @access public + * @param string $where 查询条件 + * @param array $bind 参数绑定 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereRaw(string $where, array $bind = [], string $logic = 'AND') + { + $this->options['where'][$logic][] = new Raw($where, $bind); + + return $this; + } + + /** + * 指定表达式查询条件 OR + * @access public + * @param string $where 查询条件 + * @param array $bind 参数绑定 + * @return $this + */ + public function whereOrRaw(string $where, array $bind = []) + { + return $this->whereRaw($where, $bind, 'OR'); + } + + /** + * 分析查询表达式 + * @access protected + * @param string $logic 查询逻辑 and or xor + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @param array $param 查询参数 + * @param bool $strict 严格模式 + * @return $this + */ + protected function parseWhereExp(string $logic, $field, $op, $condition, array $param = [], bool $strict = false) + { + $logic = strtoupper($logic); + + if (is_string($field) && !empty($this->options['via']) && false === strpos($field, '.')) { + $field = $this->options['via'] . '.' . $field; + } + + if ($field instanceof Raw) { + return $this->whereRaw($field, is_array($op) ? $op : [], $logic); + } elseif ($strict) { + // 使用严格模式查询 + if ('=' == $op) { + $where = $this->whereEq($field, $condition); + } else { + $where = [$field, $op, $condition, $logic]; + } + } elseif (is_array($field)) { + // 解析数组批量查询 + return $this->parseArrayWhereItems($field, $logic); + } elseif ($field instanceof Closure) { + $where = $field; + } elseif (is_string($field)) { + if (preg_match('/[,=\<\'\"\(\s]/', $field)) { + return $this->whereRaw($field, is_array($op) ? $op : [], $logic); + } elseif (is_string($op) && strtolower($op) == 'exp' && !is_null($condition)) { + $bind = isset($param[2]) && is_array($param[2]) ? $param[2] : []; + return $this->whereExp($field, $condition, $bind, $logic); + } + + $where = $this->parseWhereItem($logic, $field, $op, $condition, $param); + } + + if (!empty($where)) { + $this->options['where'][$logic][] = $where; + } + + return $this; + } + + /** + * 分析查询表达式 + * @access protected + * @param string $logic 查询逻辑 and or xor + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @param array $param 查询参数 + * @return array + */ + protected function parseWhereItem(string $logic, $field, $op, $condition, array $param = []): array + { + if (is_array($op)) { + // 同一字段多条件查询 + array_unshift($param, $field); + $where = $param; + } elseif ($field && is_null($condition)) { + if (is_string($op) && in_array(strtoupper($op), ['NULL', 'NOTNULL', 'NOT NULL'], true)) { + // null查询 + $where = [$field, $op, '']; + } elseif ('=' === $op || is_null($op)) { + $where = [$field, 'NULL', '']; + } elseif ('<>' === $op) { + $where = [$field, 'NOTNULL', '']; + } else { + // 字段相等查询 + $where = $this->whereEq($field, $op); + } + } elseif (is_string($op) && in_array(strtoupper($op), ['EXISTS', 'NOT EXISTS', 'NOTEXISTS'], true)) { + $where = [$field, $op, is_string($condition) ? new Raw($condition) : $condition]; + } else { + $where = $field ? [$field, $op, $condition, $param[2] ?? null] : []; + } + + return $where; + } + + /** + * 相等查询的主键处理 + * @access protected + * @param string $field 字段名 + * @param mixed $value 字段值 + * @return array + */ + protected function whereEq(string $field, $value): array + { + if ($this->getPk() == $field) { + $this->options['key'] = $value; + } + + return [$field, '=', $value]; + } + + /** + * 数组批量查询 + * @access protected + * @param array $field 批量查询 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + protected function parseArrayWhereItems(array $field, string $logic) + { + if (key($field) !== 0) { + $where = []; + foreach ($field as $key => $val) { + if ($val instanceof Raw) { + $where[] = [$key, 'exp', $val]; + } else { + $where[] = is_null($val) ? [$key, 'NULL', ''] : [$key, is_array($val) ? 'IN' : '=', $val]; + } + } + } else { + // 数组批量查询 + $where = $field; + } + + if (!empty($where)) { + $this->options['where'][$logic] = isset($this->options['where'][$logic]) ? + array_merge($this->options['where'][$logic], $where) : $where; + } + + return $this; + } + + /** + * 去除某个查询条件 + * @access public + * @param string $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function removeWhereField(string $field, string $logic = 'AND') + { + $logic = strtoupper($logic); + + if (isset($this->options['where'][$logic])) { + foreach ($this->options['where'][$logic] as $key => $val) { + if (is_array($val) && $val[0] == $field) { + unset($this->options['where'][$logic][$key]); + } + } + } + + return $this; + } + + /** + * 条件查询 + * @access public + * @param mixed $condition 满足条件(支持闭包) + * @param Closure|array $query 满足条件后执行的查询表达式(闭包或数组) + * @param Closure|array $otherwise 不满足条件后执行 + * @return $this + */ + public function when($condition, $query, $otherwise = null) + { + if ($condition instanceof Closure) { + $condition = $condition($this); + } + + if ($condition) { + if ($query instanceof Closure) { + $query($this, $condition); + } elseif (is_array($query)) { + $this->where($query); + } + } elseif ($otherwise) { + if ($otherwise instanceof Closure) { + $otherwise($this, $condition); + } elseif (is_array($otherwise)) { + $this->where($otherwise); + } + } + + return $this; + } +} diff --git a/vendor/topthink/think-orm/src/db/connector/Mongo.php b/vendor/topthink/think-orm/src/db/connector/Mongo.php new file mode 100644 index 0000000..418dc50 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/connector/Mongo.php @@ -0,0 +1,1175 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\connector; + +use Closure; +use MongoDB\BSON\ObjectID; +use MongoDB\Driver\BulkWrite; +use MongoDB\Driver\Command; +use MongoDB\Driver\Cursor; +use MongoDB\Driver\Exception\AuthenticationException; +use MongoDB\Driver\Exception\BulkWriteException; +use MongoDB\Driver\Exception\ConnectionException; +use MongoDB\Driver\Exception\InvalidArgumentException; +use MongoDB\Driver\Exception\RuntimeException; +use MongoDB\Driver\Manager; +use MongoDB\Driver\Query as MongoQuery; +use MongoDB\Driver\ReadPreference; +use MongoDB\Driver\WriteConcern; +use think\db\BaseQuery; +use think\db\builder\Mongo as Builder; +use think\db\Connection; +use think\db\exception\DbEventException; +use think\db\exception\DbException as Exception; +use think\db\Mongo as Query; + +/** + * Mongo数据库驱动 + * @property Manager[] $links + * @property Manager $linkRead + * @property Manager $linkWrite + */ +class Mongo extends Connection +{ + + // 查询数据类型 + protected $dbName = ''; + protected $typeMap = 'array'; + protected $mongo; // MongoDb Object + protected $cursor; // MongoCursor Object + protected $session_uuid; // sessions会话列表当前会话数组key 随机生成 + protected $sessions = []; // 会话列表 + + /** @var Builder */ + protected $builder; + + // 数据库连接参数配置 + protected $config = [ + // 数据库类型 + 'type' => '', + // 服务器地址 + 'hostname' => '', + // 数据库名 + 'database' => '', + // 是否是复制集 + 'is_replica_set' => false, + // 用户名 + 'username' => '', + // 密码 + 'password' => '', + // 端口 + 'hostport' => '', + // 连接dsn + 'dsn' => '', + // 数据库连接参数 + 'params' => [], + // 数据库编码默认采用utf8 + 'charset' => 'utf8', + // 主键名 + 'pk' => '_id', + // 主键类型 + 'pk_type' => 'ObjectID', + // 数据库表前缀 + 'prefix' => '', + // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) + 'deploy' => 0, + // 数据库读写是否分离 主从式有效 + 'rw_separate' => false, + // 读写分离后 主服务器数量 + 'master_num' => 1, + // 指定从服务器序号 + 'slave_no' => '', + // 是否严格检查字段是否存在 + 'fields_strict' => true, + // 开启字段缓存 + 'fields_cache' => false, + // 监听SQL + 'trigger_sql' => true, + // 自动写入时间戳字段 + 'auto_timestamp' => false, + // 时间字段取出后的默认时间格式 + 'datetime_format' => 'Y-m-d H:i:s', + // 是否_id转换为id + 'pk_convert_id' => false, + // typeMap + 'type_map' => ['root' => 'array', 'document' => 'array'], + ]; + + /** + * 获取当前连接器类对应的Query类 + * @access public + * @return string + */ + public function getQueryClass(): string + { + return Query::class; + } + + /** + * 获取当前的builder实例对象 + * @access public + * @return Builder + */ + public function getBuilder() + { + return $this->builder; + } + + /** + * 获取当前连接器类对应的Builder类 + * @access public + * @return string + */ + public function getBuilderClass(): string + { + return Builder::class; + } + + /** + * 连接数据库方法 + * @access public + * @param array $config 连接参数 + * @param integer $linkNum 连接序号 + * @return Manager + * @throws InvalidArgumentException + * @throws RuntimeException + */ + public function connect(array $config = [], $linkNum = 0) + { + if (!isset($this->links[$linkNum])) { + if (empty($config)) { + $config = $this->config; + } else { + $config = array_merge($this->config, $config); + } + + $this->dbName = $config['database']; + $this->typeMap = $config['type_map']; + + if ($config['pk_convert_id'] && '_id' == $config['pk']) { + $this->config['pk'] = 'id'; + } + + if (empty($config['dsn'])) { + $config['dsn'] = 'mongodb://' . ($config['username'] ? "{$config['username']}" : '') . ($config['password'] ? ":{$config['password']}@" : '') . $config['hostname'] . ($config['hostport'] ? ":{$config['hostport']}" : ''); + } + + $startTime = microtime(true); + + $this->links[$linkNum] = new Manager($config['dsn'], $config['params']); + + if (!empty($config['trigger_sql'])) { + // 记录数据库连接信息 + $this->trigger('CONNECT:[ UseTime:' . number_format(microtime(true) - $startTime, 6) . 's ] ' . $config['dsn']); + } + + } + + return $this->links[$linkNum]; + } + + /** + * 获取Mongo Manager对象 + * @access public + * @return Manager|null + */ + public function getMongo() + { + return $this->mongo ?: null; + } + + /** + * 设置/获取当前操作的database + * @access public + * @param string $db db + * @return string + */ + public function db(string $db = null) + { + if (is_null($db)) { + return $this->dbName; + } else { + $this->dbName = $db; + } + } + + /** + * 执行查询但只返回Cursor对象 + * @access public + * @param Query $query 查询对象 + * @return Cursor + */ + public function cursor($query) + { + // 分析查询表达式 + $options = $query->parseOptions(); + + // 生成MongoQuery对象 + $mongoQuery = $this->builder->select($query); + + $master = $query->getOptions('master') ? true : false; + + // 执行查询操作 + return $this->getCursor($query, $mongoQuery, $master); + } + + /** + * 执行查询并返回Cursor对象 + * @access public + * @param BaseQuery $query 查询对象 + * @param MongoQuery|Closure $mongoQuery Mongo查询对象 + * @param bool $master 是否主库操作 + * @return Cursor + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + */ + public function getCursor(BaseQuery $query, $mongoQuery, bool $master = false): Cursor + { + $this->initConnect($master); + $this->db->updateQueryTimes(); + + $options = $query->getOptions(); + $namespace = $options['table']; + + if (false === strpos($namespace, '.')) { + $namespace = $this->dbName . '.' . $namespace; + } + + if (!empty($this->queryStr)) { + // 记录执行指令 + $this->queryStr = 'db' . strstr($namespace, '.') . '.' . $this->queryStr; + } + + if ($mongoQuery instanceof Closure) { + $mongoQuery = $mongoQuery($query); + } + + $readPreference = $options['readPreference'] ?? null; + $this->queryStartTime = microtime(true); + + if ($session = $this->getSession()) { + $this->cursor = $this->mongo->executeQuery($namespace, $query, [ + 'readPreference' => is_null($readPreference) ? new ReadPreference(ReadPreference::RP_PRIMARY) : $readPreference, + 'session' => $session, + ]); + } else { + $this->cursor = $this->mongo->executeQuery($namespace, $mongoQuery, $readPreference); + } + + // SQL监控 + if (!empty($this->config['trigger_sql'])) { + $this->trigger('', $master); + } + + return $this->cursor; + } + + /** + * 执行查询 返回数据集 + * @access public + * @param MongoQuery $query 查询对象 + * @return mixed + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + */ + public function query(MongoQuery $query) + { + return $this->mongoQuery($this->newQuery(), $query); + } + + /** + * 执行语句 + * @access public + * @param BulkWrite $bulk + * @return int + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + * @throws BulkWriteException + */ + public function execute(BulkWrite $bulk) + { + return $this->mongoExecute($this->newQuery(), $bulk); + } + + /** + * 执行查询 + * @access protected + * @param BaseQuery $query 查询对象 + * @param MongoQuery|Closure $mongoQuery Mongo查询对象 + * @return array + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + */ + protected function mongoQuery(BaseQuery $query, $mongoQuery): array + { + $options = $query->parseOptions(); + + if ($query->getOptions('cache')) { + // 检查查询缓存 + $cacheItem = $this->parseCache($query, $query->getOptions('cache')); + $key = $cacheItem->getKey(); + + if ($this->cache->has($key)) { + return $this->cache->get($key); + } + } + + if ($mongoQuery instanceof Closure) { + $mongoQuery = $mongoQuery($query); + } + + $master = $query->getOptions('master') ? true : false; + $this->getCursor($query, $mongoQuery, $master); + + $resultSet = $this->getResult($options['typeMap']); + + if (isset($cacheItem) && $resultSet) { + // 缓存数据集 + $cacheItem->set($resultSet); + $this->cacheData($cacheItem); + } + + return $resultSet; + } + + /** + * 执行写操作 + * @access protected + * @param BaseQuery $query + * @param BulkWrite $bulk + * + * @return WriteResult + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + * @throws BulkWriteException + */ + protected function mongoExecute(BaseQuery $query, BulkWrite $bulk) + { + $this->initConnect(true); + $this->db->updateQueryTimes(); + + $options = $query->getOptions(); + + $namespace = $options['table']; + if (false === strpos($namespace, '.')) { + $namespace = $this->dbName . '.' . $namespace; + } + + if (!empty($this->queryStr)) { + // 记录执行指令 + $this->queryStr = 'db' . strstr($namespace, '.') . '.' . $this->queryStr; + } + + $writeConcern = $options['writeConcern'] ?? null; + $this->queryStartTime = microtime(true); + + if ($session = $this->getSession()) { + $writeResult = $this->mongo->executeBulkWrite($namespace, $bulk, [ + 'session' => $session, + 'writeConcern' => is_null($writeConcern) ? new WriteConcern(1) : $writeConcern, + ]); + } else { + $writeResult = $this->mongo->executeBulkWrite($namespace, $bulk, $writeConcern); + } + + // SQL监控 + if (!empty($this->config['trigger_sql'])) { + $this->trigger(); + } + + $this->numRows = $writeResult->getMatchedCount(); + + if ($query->getOptions('cache')) { + // 清理缓存数据 + $cacheItem = $this->parseCache($query, $query->getOptions('cache')); + $key = $cacheItem->getKey(); + $tag = $cacheItem->getTag(); + + if (isset($key) && $this->cache->has($key)) { + $this->cache->delete($key); + } elseif (!empty($tag) && method_exists($this->cache, 'tag')) { + $this->cache->tag($tag)->clear(); + } + } + + return $writeResult; + } + + /** + * 执行指令 + * @access public + * @param Command $command 指令 + * @param string $dbName 当前数据库名 + * @param ReadPreference $readPreference readPreference + * @param string|array $typeMap 指定返回的typeMap + * @param bool $master 是否主库操作 + * @return array + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + */ + public function command(Command $command, string $dbName = '', ReadPreference $readPreference = null, $typeMap = null, bool $master = false): array + { + $this->initConnect($master); + $this->db->updateQueryTimes(); + + $this->queryStartTime = microtime(true); + + $dbName = $dbName ?: $this->dbName; + + if (!empty($this->queryStr)) { + $this->queryStr = 'db.' . $this->queryStr; + } + + if ($session = $this->getSession()) { + $this->cursor = $this->mongo->executeCommand($dbName, $command, [ + 'readPreference' => is_null($readPreference) ? new ReadPreference(ReadPreference::RP_PRIMARY) : $readPreference, + 'session' => $session, + ]); + } else { + $this->cursor = $this->mongo->executeCommand($dbName, $command, $readPreference); + } + + // SQL监控 + if (!empty($this->config['trigger_sql'])) { + $this->trigger('', $master); + } + + return $this->getResult($typeMap); + } + + /** + * 获得数据集 + * @access protected + * @param string|array $typeMap 指定返回的typeMap + * @return mixed + */ + protected function getResult($typeMap = null): array + { + // 设置结果数据类型 + if (is_null($typeMap)) { + $typeMap = $this->typeMap; + } + + $typeMap = is_string($typeMap) ? ['root' => $typeMap] : $typeMap; + + $this->cursor->setTypeMap($typeMap); + + // 获取数据集 + $result = $this->cursor->toArray(); + + if ($this->getConfig('pk_convert_id')) { + // 转换ObjectID 字段 + foreach ($result as &$data) { + $this->convertObjectID($data); + } + } + + $this->numRows = count($result); + + return $result; + } + + /** + * ObjectID处理 + * @access protected + * @param array $data 数据 + * @return void + */ + protected function convertObjectID(array &$data): void + { + if (isset($data['_id']) && is_object($data['_id'])) { + $data['id'] = $data['_id']->__toString(); + unset($data['_id']); + } + } + + /** + * 数据库日志记录(仅供参考) + * @access public + * @param string $type 类型 + * @param mixed $data 数据 + * @param array $options 参数 + * @return void + */ + public function mongoLog(string $type, $data, array $options = []) + { + if (!$this->config['trigger_sql']) { + return; + } + + if (is_array($data)) { + array_walk_recursive($data, function (&$value) { + if ($value instanceof ObjectID) { + $value = $value->__toString(); + } + }); + } + + switch (strtolower($type)) { + case 'aggregate': + $this->queryStr = 'runCommand(' . ($data ? json_encode($data) : '') . ');'; + break; + case 'find': + $this->queryStr = $type . '(' . ($data ? json_encode($data) : '') . ')'; + + if (isset($options['sort'])) { + $this->queryStr .= '.sort(' . json_encode($options['sort']) . ')'; + } + + if (isset($options['skip'])) { + $this->queryStr .= '.skip(' . $options['skip'] . ')'; + } + + if (isset($options['limit'])) { + $this->queryStr .= '.limit(' . $options['limit'] . ')'; + } + + $this->queryStr .= ';'; + break; + case 'insert': + case 'remove': + $this->queryStr = $type . '(' . ($data ? json_encode($data) : '') . ');'; + break; + case 'update': + $this->queryStr = $type . '(' . json_encode($options) . ',' . json_encode($data) . ');'; + break; + case 'cmd': + $this->queryStr = $data . '(' . json_encode($options) . ');'; + break; + } + + $this->options = $options; + } + + /** + * 获取最近执行的指令 + * @access public + * @return string + */ + public function getLastSql(): string + { + return $this->queryStr; + } + + /** + * 关闭数据库 + * @access public + */ + public function close() + { + $this->mongo = null; + $this->cursor = null; + $this->linkRead = null; + $this->linkWrite = null; + $this->links = []; + } + + /** + * 初始化数据库连接 + * @access protected + * @param boolean $master 是否主服务器 + * @return void + */ + protected function initConnect(bool $master = true): void + { + if (!empty($this->config['deploy'])) { + // 采用分布式数据库 + if ($master) { + if (!$this->linkWrite) { + $this->linkWrite = $this->multiConnect(true); + } + + $this->mongo = $this->linkWrite; + } else { + if (!$this->linkRead) { + $this->linkRead = $this->multiConnect(false); + } + + $this->mongo = $this->linkRead; + } + } elseif (!$this->mongo) { + // 默认单数据库 + $this->mongo = $this->connect(); + } + } + + /** + * 连接分布式服务器 + * @access protected + * @param boolean $master 主服务器 + * @return Manager + */ + protected function multiConnect(bool $master = false): Manager + { + $config = []; + // 分布式数据库配置解析 + foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn'] as $name) { + $config[$name] = is_string($this->config[$name]) ? explode(',', $this->config[$name]) : $this->config[$name]; + } + + // 主服务器序号 + $m = floor(mt_rand(0, $this->config['master_num'] - 1)); + + if ($this->config['rw_separate']) { + // 主从式采用读写分离 + if ($master) // 主服务器写入 + { + if ($this->config['is_replica_set']) { + return $this->replicaSetConnect(); + } else { + $r = $m; + } + } elseif (is_numeric($this->config['slave_no'])) { + // 指定服务器读 + $r = $this->config['slave_no']; + } else { + // 读操作连接从服务器 每次随机连接的数据库 + $r = floor(mt_rand($this->config['master_num'], count($config['hostname']) - 1)); + } + } else { + // 读写操作不区分服务器 每次随机连接的数据库 + $r = floor(mt_rand(0, count($config['hostname']) - 1)); + } + + $dbConfig = []; + + foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn'] as $name) { + $dbConfig[$name] = $config[$name][$r] ?? $config[$name][0]; + } + + return $this->connect($dbConfig, $r); + } + + /** + * 创建基于复制集的连接 + * @return Manager + */ + public function replicaSetConnect(): Manager + { + $this->dbName = $this->config['database']; + $this->typeMap = $this->config['type_map']; + + $startTime = microtime(true); + + $this->config['params']['replicaSet'] = $this->config['database']; + + $manager = new Manager($this->buildUrl(), $this->config['params']); + + // 记录数据库连接信息 + if (!empty($config['trigger_sql'])) { + $this->trigger('CONNECT:ReplicaSet[ UseTime:' . number_format(microtime(true) - $startTime, 6) . 's ] ' . $this->config['dsn']); + } + + return $manager; + } + + /** + * 根据配置信息 生成适用于连接复制集的 URL + * @return string + */ + private function buildUrl(): string + { + $url = 'mongodb://' . ($this->config['username'] ? "{$this->config['username']}" : '') . ($this->config['password'] ? ":{$this->config['password']}@" : ''); + + $hostList = is_string($this->config['hostname']) ? explode(',', $this->config['hostname']) : $this->config['hostname']; + $portList = is_string($this->config['hostport']) ? explode(',', $this->config['hostport']) : $this->config['hostport']; + + for ($i = 0; $i < count($hostList); $i++) { + $url = $url . $hostList[$i] . ':' . $portList[0] . ','; + } + + return rtrim($url, ",") . '/'; + } + + /** + * 插入记录 + * @access public + * @param BaseQuery $query 查询对象 + * @param boolean $getLastInsID 返回自增主键 + * @return mixed + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + * @throws BulkWriteException + */ + public function insert(BaseQuery $query, bool $getLastInsID = false) + { + // 分析查询表达式 + $options = $query->parseOptions(); + + if (empty($options['data'])) { + throw new Exception('miss data to insert'); + } + + // 生成bulk对象 + $bulk = $this->builder->insert($query); + + $writeResult = $this->mongoExecute($query, $bulk); + $result = $writeResult->getInsertedCount(); + + if ($result) { + $data = $options['data']; + $lastInsId = $this->getLastInsID($query); + + if ($lastInsId) { + $pk = $query->getPk(); + $data[$pk] = $lastInsId; + } + + $query->setOption('data', $data); + + $this->db->trigger('after_insert', $query); + + if ($getLastInsID) { + return $lastInsId; + } + } + + return $result; + } + + /** + * 获取最近插入的ID + * @access public + * @param BaseQuery $query 查询对象 + * @return mixed + */ + public function getLastInsID(BaseQuery $query) + { + $id = $this->builder->getLastInsID(); + + if (is_array($id)) { + array_walk($id, function (&$item, $key) { + if ($item instanceof ObjectID) { + $item = $item->__toString(); + } + }); + } elseif ($id instanceof ObjectID) { + $id = $id->__toString(); + } + + return $id; + } + + /** + * 批量插入记录 + * @access public + * @param BaseQuery $query 查询对象 + * @param array $dataSet 数据集 + * @return integer + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + * @throws BulkWriteException + */ + public function insertAll(BaseQuery $query, array $dataSet = []): int + { + // 分析查询表达式 + $query->parseOptions(); + + if (!is_array(reset($dataSet))) { + return 0; + } + + // 生成bulkWrite对象 + $bulk = $this->builder->insertAll($query, $dataSet); + + $writeResult = $this->mongoExecute($query, $bulk); + + return $writeResult->getInsertedCount(); + } + + /** + * 更新记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return int + * @throws Exception + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + * @throws BulkWriteException + */ + public function update(BaseQuery $query): int + { + $query->parseOptions(); + + // 生成bulkWrite对象 + $bulk = $this->builder->update($query); + + $writeResult = $this->mongoExecute($query, $bulk); + + $result = $writeResult->getModifiedCount(); + + if ($result) { + $this->db->trigger('after_update', $query); + } + + return $result; + } + + /** + * 删除记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return int + * @throws Exception + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + * @throws BulkWriteException + */ + public function delete(BaseQuery $query): int + { + // 分析查询表达式 + $query->parseOptions(); + + // 生成bulkWrite对象 + $bulk = $this->builder->delete($query); + + // 执行操作 + $writeResult = $this->mongoExecute($query, $bulk); + + $result = $writeResult->getDeletedCount(); + + if ($result) { + $this->db->trigger('after_delete', $query); + } + + return $result; + } + + /** + * 查找记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return array + * @throws ModelNotFoundException + * @throws DataNotFoundException + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + */ + public function select(BaseQuery $query): array + { + try { + $this->db->trigger('before_select', $query); + } catch (DbEventException $e) { + return []; + } + + return $this->mongoQuery($query, function ($query) { + return $this->builder->select($query); + }); + } + + /** + * 查找单条记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return array + * @throws ModelNotFoundException + * @throws DataNotFoundException + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + */ + public function find(BaseQuery $query): array + { + // 事件回调 + try { + $this->db->trigger('before_find', $query); + } catch (DbEventException $e) { + return []; + } + + // 执行查询 + $resultSet = $this->mongoQuery($query, function ($query) { + return $this->builder->select($query, true); + }); + + return $resultSet[0] ?? []; + } + + /** + * 得到某个字段的值 + * @access public + * @param string $field 字段名 + * @param mixed $default 默认值 + * @return mixed + */ + public function value(BaseQuery $query, string $field, $default = null) + { + $options = $query->parseOptions(); + + if (isset($options['projection'])) { + $query->removeOption('projection'); + } + + $query->setOption('projection', (array) $field); + + if (!empty($options['cache'])) { + $cacheItem = $this->parseCache($query, $options['cache']); + $key = $cacheItem->getKey(); + + if ($this->cache->has($key)) { + return $this->cache->get($key); + } + } + + $mongoQuery = $this->builder->select($query, true); + + if (isset($options['projection'])) { + $query->setOption('projection', $options['projection']); + } else { + $query->removeOption('projection'); + } + + // 执行查询操作 + $resultSet = $this->mongoQuery($query, $mongoQuery); + + if (!empty($resultSet)) { + $data = array_shift($resultSet); + $result = $data[$field]; + } else { + $result = false; + } + + if (isset($cacheItem) && false !== $result) { + // 缓存数据 + $cacheItem->set($result); + $this->cacheData($cacheItem); + } + + return false !== $result ? $result : $default; + } + + /** + * 得到某个列的数组 + * @access public + * @param BaseQuery $query + * @param string|array $field 字段名 多个字段用逗号分隔 + * @param string $key 索引 + * @return array + */ + public function column(BaseQuery $query, $field, string $key = ''): array + { + $options = $query->parseOptions(); + + if (isset($options['projection'])) { + $query->removeOption('projection'); + } + + if (is_array($field)) { + $field = implode(',', $field); + } + if ($key && '*' != $field) { + $projection = $key . ',' . $field; + } else { + $projection = $field; + } + + $query->field($projection); + + if (!empty($options['cache'])) { + // 判断查询缓存 + $cacheItem = $this->parseCache($query, $options['cache']); + $key = $cacheItem->getKey(); + + if ($this->cache->has($key)) { + return $this->cache->get($key); + } + } + + $mongoQuery = $this->builder->select($query); + + if (isset($options['projection'])) { + $query->setOption('projection', $options['projection']); + } else { + $query->removeOption('projection'); + } + + // 执行查询操作 + $resultSet = $this->mongoQuery($query, $mongoQuery); + + if (('*' == $field || strpos($field, ',')) && $key) { + $result = array_column($resultSet, null, $key); + } elseif (!empty($resultSet)) { + $result = array_column($resultSet, $field, $key); + } else { + $result = []; + } + + if (isset($cacheItem)) { + // 缓存数据 + $cacheItem->set($result); + $this->cacheData($cacheItem); + } + + return $result; + } + + /** + * 执行command + * @access public + * @param BaseQuery $query 查询对象 + * @param string|array|object $command 指令 + * @param mixed $extra 额外参数 + * @param string $db 数据库名 + * @return array + */ + public function cmd(BaseQuery $query, $command, $extra = null, string $db = ''): array + { + if (is_array($command) || is_object($command)) { + + $this->mongoLog('cmd', 'cmd', $command); + + // 直接创建Command对象 + $command = new Command($command); + } else { + // 调用Builder封装的Command对象 + $command = $this->builder->$command($query, $extra); + } + + return $this->command($command, $db); + } + + /** + * 获取数据库字段 + * @access public + * @param mixed $tableName 数据表名 + * @return array + */ + public function getTableFields($tableName): array + { + return []; + } + + /** + * 执行数据库事务 + * @access public + * @param callable $callback 数据操作方法回调 + * @return mixed + * @throws PDOException + * @throws \Exception + * @throws \Throwable + */ + public function transaction(callable $callback) + { + $this->startTrans(); + try { + $result = null; + if (is_callable($callback)) { + $result = call_user_func_array($callback, [$this]); + } + $this->commit(); + return $result; + } catch (\Exception $e) { + $this->rollback(); + throw $e; + } catch (\Throwable $e) { + $this->rollback(); + throw $e; + } + } + + /** + * 启动事务 + * @access public + * @return void + * @throws \PDOException + * @throws \Exception + */ + public function startTrans() + { + $this->initConnect(true); + $this->session_uuid = uniqid(); + $this->sessions[$this->session_uuid] = $this->getMongo()->startSession(); + + $this->sessions[$this->session_uuid]->startTransaction([]); + } + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return void + * @throws PDOException + */ + public function commit() + { + if ($session = $this->getSession()) { + $session->commitTransaction(); + $this->setLastSession(); + } + } + + /** + * 事务回滚 + * @access public + * @return void + * @throws PDOException + */ + public function rollback() + { + if ($session = $this->getSession()) { + $session->abortTransaction(); + $this->setLastSession(); + } + } + + /** + * 结束当前会话,设置上一个会话为当前会话 + * @author klinson + */ + protected function setLastSession() + { + if ($session = $this->getSession()) { + $session->endSession(); + unset($this->sessions[$this->session_uuid]); + if (empty($this->sessions)) { + $this->session_uuid = null; + } else { + end($this->sessions); + $this->session_uuid = key($this->sessions); + } + } + } + + /** + * 获取当前会话 + * @return \MongoDB\Driver\Session|null + * @author klinson + */ + public function getSession() + { + return ($this->session_uuid && isset($this->sessions[$this->session_uuid])) + ? $this->sessions[$this->session_uuid] + : null; + } +} diff --git a/vendor/topthink/think-orm/src/db/connector/Mysql.php b/vendor/topthink/think-orm/src/db/connector/Mysql.php new file mode 100644 index 0000000..fd9e63a --- /dev/null +++ b/vendor/topthink/think-orm/src/db/connector/Mysql.php @@ -0,0 +1,162 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\connector; + +use PDO; +use think\db\PDOConnection; + +/** + * mysql数据库驱动 + */ +class Mysql extends PDOConnection +{ + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn(array $config): string + { + if (!empty($config['socket'])) { + $dsn = 'mysql:unix_socket=' . $config['socket']; + } elseif (!empty($config['hostport'])) { + $dsn = 'mysql:host=' . $config['hostname'] . ';port=' . $config['hostport']; + } else { + $dsn = 'mysql:host=' . $config['hostname']; + } + $dsn .= ';dbname=' . $config['database']; + + if (!empty($config['charset'])) { + $dsn .= ';charset=' . $config['charset']; + } + + return $dsn; + } + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName + * @return array + */ + public function getFields(string $tableName): array + { + [$tableName] = explode(' ', $tableName); + + if (false === strpos($tableName, '`')) { + if (strpos($tableName, '.')) { + $tableName = str_replace('.', '`.`', $tableName); + } + $tableName = '`' . $tableName . '`'; + } + + $sql = 'SHOW FULL COLUMNS FROM ' . $tableName; + $pdo = $this->getPDOStatement($sql); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + if (!empty($result)) { + foreach ($result as $key => $val) { + $val = array_change_key_case($val); + + $info[$val['field']] = [ + 'name' => $val['field'], + 'type' => $val['type'], + 'notnull' => 'NO' == $val['null'], + 'default' => $val['default'], + 'primary' => strtolower($val['key']) == 'pri', + 'autoinc' => strtolower($val['extra']) == 'auto_increment', + 'comment' => $val['comment'], + ]; + } + } + + return $this->fieldCase($info); + } + + /** + * 取得数据库的表信息 + * @access public + * @param string $dbName + * @return array + */ + public function getTables(string $dbName = ''): array + { + $sql = !empty($dbName) ? 'SHOW TABLES FROM ' . $dbName : 'SHOW TABLES '; + $pdo = $this->getPDOStatement($sql); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + + return $info; + } + + protected function supportSavepoint(): bool + { + return true; + } + + /** + * 启动XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function startTransXa(string $xid): void + { + $this->initConnect(true); + $this->linkID->exec("XA START '$xid'"); + } + + /** + * 预编译XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function prepareXa(string $xid): void + { + $this->initConnect(true); + $this->linkID->exec("XA END '$xid'"); + $this->linkID->exec("XA PREPARE '$xid'"); + } + + /** + * 提交XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function commitXa(string $xid): void + { + $this->initConnect(true); + $this->linkID->exec("XA COMMIT '$xid'"); + } + + /** + * 回滚XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function rollbackXa(string $xid): void + { + $this->initConnect(true); + $this->linkID->exec("XA ROLLBACK '$xid'"); + } +} diff --git a/vendor/topthink/think-orm/src/db/connector/Oracle.php b/vendor/topthink/think-orm/src/db/connector/Oracle.php new file mode 100644 index 0000000..236d8bf --- /dev/null +++ b/vendor/topthink/think-orm/src/db/connector/Oracle.php @@ -0,0 +1,117 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\connector; + +use PDO; +use think\db\BaseQuery; +use think\db\PDOConnection; + +/** + * Oracle数据库驱动 + */ +class Oracle extends PDOConnection +{ + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn(array $config): string + { + $dsn = 'oci:dbname='; + + if (!empty($config['hostname'])) { + // Oracle Instant Client + $dsn .= '//' . $config['hostname'] . ($config['hostport'] ? ':' . $config['hostport'] : '') . '/'; + } + + $dsn .= $config['database']; + + if (!empty($config['charset'])) { + $dsn .= ';charset=' . $config['charset']; + } + + return $dsn; + } + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName + * @return array + */ + public function getFields(string $tableName): array + { + [$tableName] = explode(' ', $tableName); + $sql = "select a.column_name,data_type,DECODE (nullable, 'Y', 0, 1) notnull,data_default, DECODE (A .column_name,b.column_name,1,0) pk from all_tab_columns a,(select column_name from all_constraints c, all_cons_columns col where c.constraint_name = col.constraint_name and c.constraint_type = 'P' and c.table_name = '" . strtoupper($tableName) . "' ) b where table_name = '" . strtoupper($tableName) . "' and a.column_name = b.column_name (+)"; + + $pdo = $this->getPDOStatement($sql); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + if ($result) { + foreach ($result as $key => $val) { + $val = array_change_key_case($val); + + $info[$val['column_name']] = [ + 'name' => $val['column_name'], + 'type' => $val['data_type'], + 'notnull' => $val['notnull'], + 'default' => $val['data_default'], + 'primary' => $val['pk'], + 'autoinc' => $val['pk'], + ]; + } + } + + return $this->fieldCase($info); + } + + /** + * 取得数据库的表信息(暂时实现取得用户表信息) + * @access public + * @param string $dbName + * @return array + */ + public function getTables(string $dbName = ''): array + { + $sql = 'select table_name from all_tables'; + $pdo = $this->getPDOStatement($sql); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + + return $info; + } + + /** + * 获取最近插入的ID + * @access public + * @param BaseQuery $query 查询对象 + * @param string $sequence 自增序列名 + * @return mixed + */ + public function getLastInsID(BaseQuery $query, string $sequence = null) + { + $pdo = $this->linkID->query("select {$sequence}.currval as id from dual"); + $result = $pdo->fetchColumn(); + + return $result; + } + + protected function supportSavepoint(): bool + { + return true; + } +} diff --git a/vendor/topthink/think-orm/src/db/connector/Pgsql.php b/vendor/topthink/think-orm/src/db/connector/Pgsql.php new file mode 100644 index 0000000..fec8f84 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/connector/Pgsql.php @@ -0,0 +1,108 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\connector; + +use PDO; +use think\db\PDOConnection; + +/** + * Pgsql数据库驱动 + */ +class Pgsql extends PDOConnection +{ + + /** + * 默认PDO连接参数 + * @var array + */ + protected $params = [ + PDO::ATTR_CASE => PDO::CASE_NATURAL, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, + PDO::ATTR_STRINGIFY_FETCHES => false, + ]; + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn(array $config): string + { + $dsn = 'pgsql:dbname=' . $config['database'] . ';host=' . $config['hostname']; + + if (!empty($config['hostport'])) { + $dsn .= ';port=' . $config['hostport']; + } + + return $dsn; + } + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName + * @return array + */ + public function getFields(string $tableName): array + { + [$tableName] = explode(' ', $tableName); + $sql = 'select fields_name as "field",fields_type as "type",fields_not_null as "null",fields_key_name as "key",fields_default as "default",fields_default as "extra" from table_msg(\'' . $tableName . '\');'; + + $pdo = $this->getPDOStatement($sql); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + if (!empty($result)) { + foreach ($result as $key => $val) { + $val = array_change_key_case($val); + + $info[$val['field']] = [ + 'name' => $val['field'], + 'type' => $val['type'], + 'notnull' => (bool) ('' !== $val['null']), + 'default' => $val['default'], + 'primary' => !empty($val['key']), + 'autoinc' => (0 === strpos($val['extra'], 'nextval(')), + ]; + } + } + + return $this->fieldCase($info); + } + + /** + * 取得数据库的表信息 + * @access public + * @param string $dbName + * @return array + */ + public function getTables(string $dbName = ''): array + { + $sql = "select tablename as Tables_in_test from pg_tables where schemaname ='public'"; + $pdo = $this->getPDOStatement($sql); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + + return $info; + } + + protected function supportSavepoint(): bool + { + return true; + } +} diff --git a/vendor/topthink/think-orm/src/db/connector/Sqlite.php b/vendor/topthink/think-orm/src/db/connector/Sqlite.php new file mode 100644 index 0000000..c664f20 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/connector/Sqlite.php @@ -0,0 +1,96 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\connector; + +use PDO; +use think\db\PDOConnection; + +/** + * Sqlite数据库驱动 + */ +class Sqlite extends PDOConnection +{ + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn(array $config): string + { + $dsn = 'sqlite:' . $config['database']; + + return $dsn; + } + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName + * @return array + */ + public function getFields(string $tableName): array + { + [$tableName] = explode(' ', $tableName); + $sql = 'PRAGMA table_info( ' . $tableName . ' )'; + + $pdo = $this->getPDOStatement($sql); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + if (!empty($result)) { + foreach ($result as $key => $val) { + $val = array_change_key_case($val); + + $info[$val['name']] = [ + 'name' => $val['name'], + 'type' => $val['type'], + 'notnull' => 1 === $val['notnull'], + 'default' => $val['dflt_value'], + 'primary' => '1' == $val['pk'], + 'autoinc' => '1' == $val['pk'], + ]; + } + } + + return $this->fieldCase($info); + } + + /** + * 取得数据库的表信息 + * @access public + * @param string $dbName + * @return array + */ + public function getTables(string $dbName = ''): array + { + $sql = "SELECT name FROM sqlite_master WHERE type='table' " + . "UNION ALL SELECT name FROM sqlite_temp_master " + . "WHERE type='table' ORDER BY name"; + + $pdo = $this->getPDOStatement($sql); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + + return $info; + } + + protected function supportSavepoint(): bool + { + return true; + } +} diff --git a/vendor/topthink/think-orm/src/db/connector/Sqlsrv.php b/vendor/topthink/think-orm/src/db/connector/Sqlsrv.php new file mode 100644 index 0000000..10d944f --- /dev/null +++ b/vendor/topthink/think-orm/src/db/connector/Sqlsrv.php @@ -0,0 +1,122 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\connector; + +use PDO; +use think\db\PDOConnection; + +/** + * Sqlsrv数据库驱动 + */ +class Sqlsrv extends PDOConnection +{ + /** + * 默认PDO连接参数 + * @var array + */ + protected $params = [ + PDO::ATTR_CASE => PDO::CASE_NATURAL, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, + PDO::ATTR_STRINGIFY_FETCHES => false, + ]; + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn(array $config): string + { + $dsn = 'sqlsrv:Database=' . $config['database'] . ';Server=' . $config['hostname']; + + if (!empty($config['hostport'])) { + $dsn .= ',' . $config['hostport']; + } + + return $dsn; + } + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName + * @return array + */ + public function getFields(string $tableName): array + { + [$tableName] = explode(' ', $tableName); + strpos($tableName,'.') && $tableName = substr($tableName,strpos($tableName,'.') + 1); + $sql = "SELECT column_name, data_type, column_default, is_nullable + FROM information_schema.tables AS t + JOIN information_schema.columns AS c + ON t.table_catalog = c.table_catalog + AND t.table_schema = c.table_schema + AND t.table_name = c.table_name + WHERE t.table_name = '$tableName'"; + + $pdo = $this->getPDOStatement($sql); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + if (!empty($result)) { + foreach ($result as $key => $val) { + $val = array_change_key_case($val); + + $info[$val['column_name']] = [ + 'name' => $val['column_name'], + 'type' => $val['data_type'], + 'notnull' => (bool) ('' === $val['is_nullable']), // not null is empty, null is yes + 'default' => $val['column_default'], + 'primary' => false, + 'autoinc' => false, + ]; + } + } + + $sql = "SELECT column_name FROM information_schema.key_column_usage WHERE table_name='$tableName'"; + $pdo = $this->linkID->query($sql); + $result = $pdo->fetch(PDO::FETCH_ASSOC); + + if ($result) { + $info[$result['column_name']]['primary'] = true; + } + + return $this->fieldCase($info); + } + + /** + * 取得数据表的字段信息 + * @access public + * @param string $dbName + * @return array + */ + public function getTables(string $dbName = ''): array + { + $sql = "SELECT TABLE_NAME + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_TYPE = 'BASE TABLE' + "; + + $pdo = $this->getPDOStatement($sql); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + + return $info; + } + +} diff --git a/vendor/topthink/think-orm/src/db/connector/pgsql.sql b/vendor/topthink/think-orm/src/db/connector/pgsql.sql new file mode 100644 index 0000000..e1a09a3 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/connector/pgsql.sql @@ -0,0 +1,117 @@ +CREATE OR REPLACE FUNCTION pgsql_type(a_type varchar) RETURNS varchar AS +$BODY$ +DECLARE + v_type varchar; +BEGIN + IF a_type='int8' THEN + v_type:='bigint'; + ELSIF a_type='int4' THEN + v_type:='integer'; + ELSIF a_type='int2' THEN + v_type:='smallint'; + ELSIF a_type='bpchar' THEN + v_type:='char'; + ELSE + v_type:=a_type; + END IF; + RETURN v_type; +END; +$BODY$ +LANGUAGE PLPGSQL; + +CREATE TYPE "public"."tablestruct" AS ( + "fields_key_name" varchar(100), + "fields_name" VARCHAR(200), + "fields_type" VARCHAR(20), + "fields_length" BIGINT, + "fields_not_null" VARCHAR(10), + "fields_default" VARCHAR(500), + "fields_comment" VARCHAR(1000) +); + +CREATE OR REPLACE FUNCTION "public"."table_msg" (a_schema_name varchar, a_table_name varchar) RETURNS SETOF "public"."tablestruct" AS +$body$ +DECLARE + v_ret tablestruct; + v_oid oid; + v_sql varchar; + v_rec RECORD; + v_key varchar; +BEGIN + SELECT + pg_class.oid INTO v_oid + FROM + pg_class + INNER JOIN pg_namespace ON (pg_class.relnamespace = pg_namespace.oid AND lower(pg_namespace.nspname) = a_schema_name) + WHERE + pg_class.relname=a_table_name; + IF NOT FOUND THEN + RETURN; + END IF; + + v_sql=' + SELECT + pg_attribute.attname AS fields_name, + pg_attribute.attnum AS fields_index, + pgsql_type(pg_type.typname::varchar) AS fields_type, + pg_attribute.atttypmod-4 as fields_length, + CASE WHEN pg_attribute.attnotnull THEN ''not null'' + ELSE '''' + END AS fields_not_null, + pg_attrdef.adsrc AS fields_default, + pg_description.description AS fields_comment + FROM + pg_attribute + INNER JOIN pg_class ON pg_attribute.attrelid = pg_class.oid + INNER JOIN pg_type ON pg_attribute.atttypid = pg_type.oid + LEFT OUTER JOIN pg_attrdef ON pg_attrdef.adrelid = pg_class.oid AND pg_attrdef.adnum = pg_attribute.attnum + LEFT OUTER JOIN pg_description ON pg_description.objoid = pg_class.oid AND pg_description.objsubid = pg_attribute.attnum + WHERE + pg_attribute.attnum > 0 + AND attisdropped <> ''t'' + AND pg_class.oid = ' || v_oid || ' + ORDER BY pg_attribute.attnum' ; + + FOR v_rec IN EXECUTE v_sql LOOP + v_ret.fields_name=v_rec.fields_name; + v_ret.fields_type=v_rec.fields_type; + IF v_rec.fields_length > 0 THEN + v_ret.fields_length:=v_rec.fields_length; + ELSE + v_ret.fields_length:=NULL; + END IF; + v_ret.fields_not_null=v_rec.fields_not_null; + v_ret.fields_default=v_rec.fields_default; + v_ret.fields_comment=v_rec.fields_comment; + SELECT constraint_name INTO v_key FROM information_schema.key_column_usage WHERE table_schema=a_schema_name AND table_name=a_table_name AND column_name=v_rec.fields_name; + IF FOUND THEN + v_ret.fields_key_name=v_key; + ELSE + v_ret.fields_key_name=''; + END IF; + RETURN NEXT v_ret; + END LOOP; + RETURN ; +END; +$body$ +LANGUAGE 'plpgsql' VOLATILE CALLED ON NULL INPUT SECURITY INVOKER; + +COMMENT ON FUNCTION "public"."table_msg"(a_schema_name varchar, a_table_name varchar) +IS '获得表信息'; + +---重载一个函数 +CREATE OR REPLACE FUNCTION "public"."table_msg" (a_table_name varchar) RETURNS SETOF "public"."tablestruct" AS +$body$ +DECLARE + v_ret tablestruct; +BEGIN + FOR v_ret IN SELECT * FROM table_msg('public',a_table_name) LOOP + RETURN NEXT v_ret; + END LOOP; + RETURN; +END; +$body$ +LANGUAGE 'plpgsql' VOLATILE CALLED ON NULL INPUT SECURITY INVOKER; + +COMMENT ON FUNCTION "public"."table_msg"(a_table_name varchar) +IS '获得表信息'; \ No newline at end of file diff --git a/vendor/topthink/think-orm/src/db/exception/BindParamException.php b/vendor/topthink/think-orm/src/db/exception/BindParamException.php new file mode 100644 index 0000000..08bb388 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/exception/BindParamException.php @@ -0,0 +1,35 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\exception; + +/** + * PDO参数绑定异常 + */ +class BindParamException extends DbException +{ + + /** + * BindParamException constructor. + * @access public + * @param string $message + * @param array $config + * @param string $sql + * @param array $bind + * @param int $code + */ + public function __construct(string $message, array $config, string $sql, array $bind, int $code = 10502) + { + $this->setData('Bind Param', $bind); + parent::__construct($message, $config, $sql, $code); + } +} diff --git a/vendor/topthink/think-orm/src/db/exception/DataNotFoundException.php b/vendor/topthink/think-orm/src/db/exception/DataNotFoundException.php new file mode 100644 index 0000000..d10dd43 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/exception/DataNotFoundException.php @@ -0,0 +1,43 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\exception; + +class DataNotFoundException extends DbException +{ + protected $table; + + /** + * DbException constructor. + * @access public + * @param string $message + * @param string $table + * @param array $config + */ + public function __construct(string $message, string $table = '', array $config = []) + { + $this->message = $message; + $this->table = $table; + + $this->setData('Database Config', $config); + } + + /** + * 获取数据表名 + * @access public + * @return string + */ + public function getTable() + { + return $this->table; + } +} diff --git a/vendor/topthink/think-orm/src/db/exception/DbEventException.php b/vendor/topthink/think-orm/src/db/exception/DbEventException.php new file mode 100644 index 0000000..394a1e8 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/exception/DbEventException.php @@ -0,0 +1,19 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\exception; + +/** + * Db事件异常 + */ +class DbEventException extends DbException +{ +} diff --git a/vendor/topthink/think-orm/src/db/exception/DbException.php b/vendor/topthink/think-orm/src/db/exception/DbException.php new file mode 100644 index 0000000..f68b21c --- /dev/null +++ b/vendor/topthink/think-orm/src/db/exception/DbException.php @@ -0,0 +1,44 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\exception; + +use think\Exception; + +/** + * Database相关异常处理类 + */ +class DbException extends Exception +{ + /** + * DbException constructor. + * @access public + * @param string $message + * @param array $config + * @param string $sql + * @param int $code + */ + public function __construct(string $message, array $config = [], string $sql = '', int $code = 10500) + { + $this->message = $message; + $this->code = $code; + + $this->setData('Database Status', [ + 'Error Code' => $code, + 'Error Message' => $message, + 'Error SQL' => $sql, + ]); + + unset($config['username'], $config['password']); + $this->setData('Database Config', $config); + } +} diff --git a/vendor/topthink/think-orm/src/db/exception/InvalidArgumentException.php b/vendor/topthink/think-orm/src/db/exception/InvalidArgumentException.php new file mode 100644 index 0000000..047e45e --- /dev/null +++ b/vendor/topthink/think-orm/src/db/exception/InvalidArgumentException.php @@ -0,0 +1,21 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); +namespace think\db\exception; + +use Psr\SimpleCache\InvalidArgumentException as SimpleCacheInvalidArgumentInterface; + +/** + * 非法数据异常 + */ +class InvalidArgumentException extends \InvalidArgumentException implements SimpleCacheInvalidArgumentInterface +{ +} diff --git a/vendor/topthink/think-orm/src/db/exception/ModelEventException.php b/vendor/topthink/think-orm/src/db/exception/ModelEventException.php new file mode 100644 index 0000000..767bc1a --- /dev/null +++ b/vendor/topthink/think-orm/src/db/exception/ModelEventException.php @@ -0,0 +1,19 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\exception; + +/** + * 模型事件异常 + */ +class ModelEventException extends DbException +{ +} diff --git a/vendor/topthink/think-orm/src/db/exception/ModelNotFoundException.php b/vendor/topthink/think-orm/src/db/exception/ModelNotFoundException.php new file mode 100644 index 0000000..84a1525 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/exception/ModelNotFoundException.php @@ -0,0 +1,44 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\exception; + +class ModelNotFoundException extends DbException +{ + protected $model; + + /** + * 构造方法 + * @access public + * @param string $message + * @param string $model + * @param array $config + */ + public function __construct(string $message, string $model = '', array $config = []) + { + $this->message = $message; + $this->model = $model; + + $this->setData('Database Config', $config); + } + + /** + * 获取模型类名 + * @access public + * @return string + */ + public function getModel() + { + return $this->model; + } + +} diff --git a/vendor/topthink/think-orm/src/db/exception/PDOException.php b/vendor/topthink/think-orm/src/db/exception/PDOException.php new file mode 100644 index 0000000..efe78b9 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/exception/PDOException.php @@ -0,0 +1,44 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\exception; + +/** + * PDO异常处理类 + * 重新封装了系统的\PDOException类 + */ +class PDOException extends DbException +{ + /** + * PDOException constructor. + * @access public + * @param \PDOException $exception + * @param array $config + * @param string $sql + * @param int $code + */ + public function __construct(\PDOException $exception, array $config = [], string $sql = '', int $code = 10501) + { + $error = $exception->errorInfo; + $message = $exception->getMessage(); + + if (!empty($error)) { + $this->setData('PDO Error Info', [ + 'SQLSTATE' => $error[0], + 'Driver Error Code' => isset($error[1]) ? $error[1] : 0, + 'Driver Error Message' => isset($error[2]) ? $error[2] : '', + ]); + } + + parent::__construct($message, $config, $sql, $code); + } +} diff --git a/vendor/topthink/think-orm/src/facade/Db.php b/vendor/topthink/think-orm/src/facade/Db.php new file mode 100644 index 0000000..b0296c6 --- /dev/null +++ b/vendor/topthink/think-orm/src/facade/Db.php @@ -0,0 +1,31 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\DbManager + * @mixin \think\DbManager + */ +class Db extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'think\DbManager'; + } +} diff --git a/vendor/topthink/think-orm/src/model/Collection.php b/vendor/topthink/think-orm/src/model/Collection.php new file mode 100644 index 0000000..f017e32 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/Collection.php @@ -0,0 +1,265 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model; + +use think\Collection as BaseCollection; +use think\Model; +use think\Paginator; + +/** + * 模型数据集类 + */ +class Collection extends BaseCollection +{ + /** + * 延迟预载入关联查询 + * @access public + * @param array|string $relation 关联 + * @param mixed $cache 关联缓存 + * @return $this + */ + public function load($relation, $cache = false) + { + if (!$this->isEmpty()) { + $item = current($this->items); + $item->eagerlyResultSet($this->items, (array) $relation, [], false, $cache); + } + + return $this; + } + + /** + * 删除数据集的数据 + * @access public + * @return bool + */ + public function delete(): bool + { + $this->each(function (Model $model) { + $model->delete(); + }); + + return true; + } + + /** + * 更新数据 + * @access public + * @param array $data 数据数组 + * @param array $allowField 允许字段 + * @return bool + */ + public function update(array $data, array $allowField = []): bool + { + $this->each(function (Model $model) use ($data, $allowField) { + if (!empty($allowField)) { + $model->allowField($allowField); + } + + $model->save($data); + }); + + return true; + } + + /** + * 设置需要隐藏的输出属性 + * @access public + * @param array $hidden 属性列表 + * @return $this + */ + public function hidden(array $hidden) + { + $this->each(function (Model $model) use ($hidden) { + $model->hidden($hidden); + }); + + return $this; + } + + /** + * 设置需要输出的属性 + * @access public + * @param array $visible + * @return $this + */ + public function visible(array $visible) + { + $this->each(function (Model $model) use ($visible) { + $model->visible($visible); + }); + + return $this; + } + + /** + * 设置需要追加的输出属性 + * @access public + * @param array $append 属性列表 + * @return $this + */ + public function append(array $append) + { + $this->each(function (Model $model) use ($append) { + $model->append($append); + }); + + return $this; + } + + /** + * 设置模型输出场景 + * @access public + * @param string $scene 场景名称 + * @return $this + */ + public function scene(string $scene) + { + $this->each(function (Model $model) use ($scene) { + $model->scene($scene); + }); + + return $this; + } + + /** + * 设置父模型 + * @access public + * @param Model $parent 父模型 + * @return $this + */ + public function setParent(Model $parent) + { + $this->each(function (Model $model) use ($parent) { + $model->setParent($parent); + }); + + return $this; + } + + /** + * 设置数据字段获取器 + * @access public + * @param string|array $name 字段名 + * @param callable $callback 闭包获取器 + * @return $this + */ + public function withAttr($name, $callback = null) + { + $this->each(function (Model $model) use ($name, $callback) { + $model->withAttribute($name, $callback); + }); + + return $this; + } + + /** + * 绑定(一对一)关联属性到当前模型 + * @access protected + * @param string $relation 关联名称 + * @param array $attrs 绑定属性 + * @return $this + * @throws Exception + */ + public function bindAttr(string $relation, array $attrs = []) + { + $this->each(function (Model $model) use ($relation, $attrs) { + $model->bindAttr($relation, $attrs); + }); + + return $this; + } + + /** + * 按指定键整理数据 + * + * @access public + * @param mixed $items 数据 + * @param string $indexKey 键名 + * @return array + */ + public function dictionary($items = null, string &$indexKey = null) + { + if ($items instanceof self || $items instanceof Paginator) { + $items = $items->all(); + } + + $items = is_null($items) ? $this->items : $items; + + if ($items && empty($indexKey)) { + $indexKey = $items[0]->getPk(); + } + + if (isset($indexKey) && is_string($indexKey)) { + return array_column($items, null, $indexKey); + } + + return $items; + } + + /** + * 比较数据集,返回差集 + * + * @access public + * @param mixed $items 数据 + * @param string $indexKey 指定比较的键名 + * @return static + */ + public function diff($items, string $indexKey = null) + { + if ($this->isEmpty()) { + return new static($items); + } + + $diff = []; + $dictionary = $this->dictionary($items, $indexKey); + + if (is_string($indexKey)) { + foreach ($this->items as $item) { + if (!isset($dictionary[$item[$indexKey]])) { + $diff[] = $item; + } + } + } + + return new static($diff); + } + + /** + * 比较数据集,返回交集 + * + * @access public + * @param mixed $items 数据 + * @param string $indexKey 指定比较的键名 + * @return static + */ + public function intersect($items, string $indexKey = null) + { + if ($this->isEmpty()) { + return new static([]); + } + + $intersect = []; + $dictionary = $this->dictionary($items, $indexKey); + + if (is_string($indexKey)) { + foreach ($this->items as $item) { + if (isset($dictionary[$item[$indexKey]])) { + $intersect[] = $item; + } + } + } + + return new static($intersect); + } +} diff --git a/vendor/topthink/think-orm/src/model/Pivot.php b/vendor/topthink/think-orm/src/model/Pivot.php new file mode 100644 index 0000000..893c01b --- /dev/null +++ b/vendor/topthink/think-orm/src/model/Pivot.php @@ -0,0 +1,53 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model; + +use think\Model; + +/** + * 多对多中间表模型类 + */ +class Pivot extends Model +{ + + /** + * 父模型 + * @var Model + */ + public $parent; + + /** + * 是否时间自动写入 + * @var bool + */ + protected $autoWriteTimestamp = false; + + /** + * 架构函数 + * @access public + * @param array $data 数据 + * @param Model $parent 上级模型 + * @param string $table 中间数据表名 + */ + public function __construct(array $data = [], Model $parent = null, string $table = '') + { + $this->parent = $parent; + + if (is_null($this->name)) { + $this->name = $table; + } + + parent::__construct($data); + } + +} diff --git a/vendor/topthink/think-orm/src/model/Relation.php b/vendor/topthink/think-orm/src/model/Relation.php new file mode 100644 index 0000000..358842d --- /dev/null +++ b/vendor/topthink/think-orm/src/model/Relation.php @@ -0,0 +1,314 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model; + +use Closure; +use ReflectionFunction; +use think\db\BaseQuery as Query; +use think\db\exception\DbException as Exception; +use think\Model; + +/** + * 模型关联基础类 + * @package think\model + * @mixin Query + */ +abstract class Relation +{ + /** + * 父模型对象 + * @var Model + */ + protected $parent; + + /** + * 当前关联的模型类名 + * @var string + */ + protected $model; + + /** + * 关联模型查询对象 + * @var Query + */ + protected $query; + + /** + * 关联表外键 + * @var string + */ + protected $foreignKey; + + /** + * 关联表主键 + * @var string + */ + protected $localKey; + + /** + * 是否执行关联基础查询 + * @var bool + */ + protected $baseQuery; + + /** + * 是否为自关联 + * @var bool + */ + protected $selfRelation = false; + + /** + * 关联数据数量限制 + * @var int + */ + protected $withLimit; + + /** + * 关联数据字段限制 + * @var array + */ + protected $withField; + + /** + * 排除关联数据字段 + * @var array + */ + protected $withoutField; + + /** + * 默认数据 + * @var mixed + */ + protected $default; + + /** + * 获取关联的所属模型 + * @access public + * @return Model + */ + public function getParent(): Model + { + return $this->parent; + } + + /** + * 获取当前的关联模型类的Query实例 + * @access public + * @return Query + */ + public function getQuery() + { + return $this->query; + } + + /** + * 获取关联表外键 + * @access public + * @return string + */ + public function getForeignKey() + { + return $this->foreignKey; + } + + /** + * 获取关联表主键 + * @access public + * @return string + */ + public function getLocalKey() + { + return $this->localKey; + } + + /** + * 获取当前的关联模型类的实例 + * @access public + * @return Model + */ + public function getModel(): Model + { + return $this->query->getModel(); + } + + /** + * 当前关联是否为自关联 + * @access public + * @return bool + */ + public function isSelfRelation(): bool + { + return $this->selfRelation; + } + + /** + * 封装关联数据集 + * @access public + * @param array $resultSet 数据集 + * @param Model $parent 父模型 + * @return mixed + */ + protected function resultSetBuild(array $resultSet, Model $parent = null) + { + return (new $this->model)->toCollection($resultSet)->setParent($parent); + } + + protected function getQueryFields(string $model) + { + $fields = $this->query->getOptions('field'); + return $this->getRelationQueryFields($fields, $model); + } + + protected function getRelationQueryFields($fields, string $model) + { + if (empty($fields) || '*' == $fields) { + return $model . '.*'; + } + + if (is_string($fields)) { + $fields = explode(',', $fields); + } + + foreach ($fields as &$field) { + if (false === strpos($field, '.')) { + $field = $model . '.' . $field; + } + } + + return $fields; + } + + protected function getQueryWhere(array &$where, string $relation): void + { + foreach ($where as $key => &$val) { + if (is_string($key)) { + $where[] = [false === strpos($key, '.') ? $relation . '.' . $key : $key, '=', $val]; + unset($where[$key]); + } elseif (isset($val[0]) && false === strpos($val[0], '.')) { + $val[0] = $relation . '.' . $val[0]; + } + } + } + + /** + * 限制关联数据的数量 + * @access public + * @param int $limit 关联数量限制 + * @return $this + */ + public function withLimit(int $limit) + { + $this->withLimit = $limit; + return $this; + } + + /** + * 限制关联数据的字段 + * @access public + * @param array $field 关联字段限制 + * @return $this + */ + public function withField(array $field) + { + $this->withField = $field; + return $this; + } + + /** + * 排除关联数据的字段 + * @access public + * @param array|string $field 关联字段限制 + * @return $this + */ + public function withoutField($field) + { + if (is_string($field)) { + $field = array_map('trim', explode(',', $field)); + } + + $this->withoutField = $field; + return $this; + } + + /** + * 设置关联数据不存在的时候默认值 + * @access public + * @param mixed $data 默认值 + * @return $this + */ + public function withDefault($data = null) + { + $this->default = $data; + return $this; + } + + /** + * 获取关联数据默认值 + * @access protected + * @return mixed + */ + protected function getDefaultModel() + { + if (is_array($this->default)) { + $model = (new $this->model)->data($this->default); + } elseif ($this->default instanceof Closure) { + $closure = $this->default; + $model = new $this->model; + $closure($model); + } else { + $model = $this->default; + } + + return $model; + } + + /** + * 判断闭包的参数类型 + * @access protected + * @return mixed + */ + protected function getClosureType(Closure $closure) + { + $reflect = new ReflectionFunction($closure); + $params = $reflect->getParameters(); + + if (!empty($params)) { + $type = $params[0]->getType(); + return is_null($type) || Relation::class == $type->getName() ? $this : $this->query; + } + + return $this; + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery(): void + {} + + public function __call($method, $args) + { + if ($this->query) { + // 执行基础查询 + $this->baseQuery(); + + $result = call_user_func_array([$this->query, $method], $args); + + return $result === $this->query ? $this : $result; + } + + throw new Exception('method not exists:' . __CLASS__ . '->' . $method); + } +} diff --git a/vendor/topthink/think-orm/src/model/concern/Attribute.php b/vendor/topthink/think-orm/src/model/concern/Attribute.php new file mode 100644 index 0000000..34cb59a --- /dev/null +++ b/vendor/topthink/think-orm/src/model/concern/Attribute.php @@ -0,0 +1,657 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model\concern; + +use InvalidArgumentException; +use think\db\Raw; +use think\helper\Str; +use think\model\Relation; + +/** + * 模型数据处理 + */ +trait Attribute +{ + /** + * 数据表主键 复合主键使用数组定义 + * @var string|array + */ + protected $pk = 'id'; + + /** + * 数据表字段信息 留空则自动获取 + * @var array + */ + protected $schema = []; + + /** + * 当前允许写入的字段 + * @var array + */ + protected $field = []; + + /** + * 字段自动类型转换 + * @var array + */ + protected $type = []; + + /** + * 数据表废弃字段 + * @var array + */ + protected $disuse = []; + + /** + * 数据表只读字段 + * @var array + */ + protected $readonly = []; + + /** + * 当前模型数据 + * @var array + */ + private $data = []; + + /** + * 原始数据 + * @var array + */ + private $origin = []; + + /** + * JSON数据表字段 + * @var array + */ + protected $json = []; + + /** + * JSON数据表字段类型 + * @var array + */ + protected $jsonType = []; + + /** + * JSON数据取出是否需要转换为数组 + * @var bool + */ + protected $jsonAssoc = false; + + /** + * 是否严格字段大小写 + * @var bool + */ + protected $strict = true; + + /** + * 获取器数据 + * @var array + */ + private $get = []; + + /** + * 动态获取器 + * @var array + */ + private $withAttr = []; + + /** + * 获取模型对象的主键 + * @access public + * @return string|array + */ + public function getPk() + { + return $this->pk; + } + + /** + * 判断一个字段名是否为主键字段 + * @access public + * @param string $key 名称 + * @return bool + */ + protected function isPk(string $key): bool + { + $pk = $this->getPk(); + + if (is_string($pk) && $pk == $key) { + return true; + } elseif (is_array($pk) && in_array($key, $pk)) { + return true; + } + + return false; + } + + /** + * 获取模型对象的主键值 + * @access public + * @return mixed + */ + public function getKey() + { + $pk = $this->getPk(); + + if (is_string($pk) && array_key_exists($pk, $this->data)) { + return $this->data[$pk]; + } + + return; + } + + /** + * 设置允许写入的字段 + * @access public + * @param array $field 允许写入的字段 + * @return $this + */ + public function allowField(array $field) + { + $this->field = $field; + + return $this; + } + + /** + * 设置只读字段 + * @access public + * @param array $field 只读字段 + * @return $this + */ + public function readOnly(array $field) + { + $this->readonly = $field; + + return $this; + } + + /** + * 获取实际的字段名 + * @access protected + * @param string $name 字段名 + * @return string + */ + protected function getRealFieldName(string $name): string + { + if ($this->convertNameToCamel || !$this->strict) { + return Str::snake($name); + } + + return $name; + } + + /** + * 设置数据对象值 + * @access public + * @param array $data 数据 + * @param bool $set 是否调用修改器 + * @param array $allow 允许的字段名 + * @return $this + */ + public function data(array $data, bool $set = false, array $allow = []) + { + // 清空数据 + $this->data = []; + + // 废弃字段 + foreach ($this->disuse as $key) { + if (array_key_exists($key, $data)) { + unset($data[$key]); + } + } + + if (!empty($allow)) { + $result = []; + foreach ($allow as $name) { + if (isset($data[$name])) { + $result[$name] = $data[$name]; + } + } + $data = $result; + } + + if ($set) { + // 数据对象赋值 + $this->setAttrs($data); + } else { + $this->data = $data; + } + + return $this; + } + + /** + * 批量追加数据对象值 + * @access public + * @param array $data 数据 + * @param bool $set 是否需要进行数据处理 + * @return $this + */ + public function appendData(array $data, bool $set = false) + { + if ($set) { + $this->setAttrs($data); + } else { + $this->data = array_merge($this->data, $data); + } + + return $this; + } + + /** + * 获取对象原始数据 如果不存在指定字段返回null + * @access public + * @param string $name 字段名 留空获取全部 + * @return mixed + */ + public function getOrigin(string $name = null) + { + if (is_null($name)) { + return $this->origin; + } + + $fieldName = $this->getRealFieldName($name); + + return array_key_exists($fieldName, $this->origin) ? $this->origin[$fieldName] : null; + } + + /** + * 获取当前对象数据 如果不存在指定字段返回false + * @access public + * @param string $name 字段名 留空获取全部 + * @return mixed + * @throws InvalidArgumentException + */ + public function getData(string $name = null) + { + if (is_null($name)) { + return $this->data; + } + + $fieldName = $this->getRealFieldName($name); + + if (array_key_exists($fieldName, $this->data)) { + return $this->data[$fieldName]; + } elseif (array_key_exists($fieldName, $this->relation)) { + return $this->relation[$fieldName]; + } + + throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name); + } + + /** + * 获取变化的数据 并排除只读数据 + * @access public + * @return array + */ + public function getChangedData(): array + { + $data = $this->force ? $this->data : array_udiff_assoc($this->data, $this->origin, function ($a, $b) { + if ((empty($a) || empty($b)) && $a !== $b) { + return 1; + } + + return is_object($a) || $a != $b ? 1 : 0; + }); + + // 只读字段不允许更新 + foreach ($this->readonly as $key => $field) { + if (array_key_exists($field, $data)) { + unset($data[$field]); + } + } + + return $data; + } + + /** + * 直接设置数据对象值 + * @access public + * @param string $name 属性名 + * @param mixed $value 值 + * @return void + */ + public function set(string $name, $value): void + { + $name = $this->getRealFieldName($name); + + $this->data[$name] = $value; + unset($this->get[$name]); + } + + /** + * 通过修改器 批量设置数据对象值 + * @access public + * @param array $data 数据 + * @return void + */ + public function setAttrs(array $data): void + { + // 进行数据处理 + foreach ($data as $key => $value) { + $this->setAttr($key, $value, $data); + } + } + + /** + * 通过修改器 设置数据对象值 + * @access public + * @param string $name 属性名 + * @param mixed $value 属性值 + * @param array $data 数据 + * @return void + */ + public function setAttr(string $name, $value, array $data = []): void + { + $name = $this->getRealFieldName($name); + + // 检测修改器 + $method = 'set' . Str::studly($name) . 'Attr'; + + if (method_exists($this, $method)) { + $array = $this->data; + + $value = $this->$method($value, array_merge($this->data, $data)); + + if (is_null($value) && $array !== $this->data) { + return; + } + } elseif (isset($this->type[$name])) { + // 类型转换 + $value = $this->writeTransform($value, $this->type[$name]); + } + + // 设置数据对象属性 + $this->data[$name] = $value; + unset($this->get[$name]); + } + + /** + * 数据写入 类型转换 + * @access protected + * @param mixed $value 值 + * @param string|array $type 要转换的类型 + * @return mixed + */ + protected function writeTransform($value, $type) + { + if (is_null($value)) { + return; + } + + if ($value instanceof Raw) { + return $value; + } + + if (is_array($type)) { + [$type, $param] = $type; + } elseif (strpos($type, ':')) { + [$type, $param] = explode(':', $type, 2); + } + + switch ($type) { + case 'integer': + $value = (int) $value; + break; + case 'float': + if (empty($param)) { + $value = (float) $value; + } else { + $value = (float) number_format($value, (int) $param, '.', ''); + } + break; + case 'boolean': + $value = (bool) $value; + break; + case 'timestamp': + if (!is_numeric($value)) { + $value = strtotime($value); + } + break; + case 'datetime': + $value = is_numeric($value) ? $value : strtotime($value); + $value = $this->formatDateTime('Y-m-d H:i:s.u', $value, true); + break; + case 'object': + if (is_object($value)) { + $value = json_encode($value, JSON_FORCE_OBJECT); + } + break; + case 'array': + $value = (array) $value; + case 'json': + $option = !empty($param) ? (int) $param : JSON_UNESCAPED_UNICODE; + $value = json_encode($value, $option); + break; + case 'serialize': + $value = serialize($value); + break; + default: + if (is_object($value) && false !== strpos($type, '\\') && method_exists($value, '__toString')) { + // 对象类型 + $value = $value->__toString(); + } + } + + return $value; + } + + /** + * 获取器 获取数据对象的值 + * @access public + * @param string $name 名称 + * @return mixed + * @throws InvalidArgumentException + */ + public function getAttr(string $name) + { + try { + $relation = false; + $value = $this->getData($name); + } catch (InvalidArgumentException $e) { + $relation = $this->isRelationAttr($name); + $value = null; + } + + return $this->getValue($name, $value, $relation); + } + + /** + * 获取经过获取器处理后的数据对象的值 + * @access protected + * @param string $name 字段名称 + * @param mixed $value 字段值 + * @param bool|string $relation 是否为关联属性或者关联名 + * @return mixed + * @throws InvalidArgumentException + */ + protected function getValue(string $name, $value, $relation = false) + { + // 检测属性获取器 + $fieldName = $this->getRealFieldName($name); + + if (array_key_exists($fieldName, $this->get)) { + return $this->get[$fieldName]; + } + + $method = 'get' . Str::studly($name) . 'Attr'; + if (isset($this->withAttr[$fieldName])) { + if ($relation) { + $value = $this->getRelationValue($relation); + } + + if (in_array($fieldName, $this->json) && is_array($this->withAttr[$fieldName])) { + $value = $this->getJsonValue($fieldName, $value); + } else { + $closure = $this->withAttr[$fieldName]; + if ($closure instanceof \Closure) { + $value = $closure($value, $this->data); + } + } + } elseif (method_exists($this, $method)) { + if ($relation) { + $value = $this->getRelationValue($relation); + } + + $value = $this->$method($value, $this->data); + } elseif (isset($this->type[$fieldName])) { + // 类型转换 + $value = $this->readTransform($value, $this->type[$fieldName]); + } elseif ($this->autoWriteTimestamp && in_array($fieldName, [$this->createTime, $this->updateTime])) { + $value = $this->getTimestampValue($value); + } elseif ($relation) { + $value = $this->getRelationValue($relation); + // 保存关联对象值 + $this->relation[$name] = $value; + } + + $this->get[$fieldName] = $value; + + return $value; + } + + /** + * 获取JSON字段属性值 + * @access protected + * @param string $name 属性名 + * @param mixed $value JSON数据 + * @return mixed + */ + protected function getJsonValue($name, $value) + { + foreach ($this->withAttr[$name] as $key => $closure) { + if ($this->jsonAssoc) { + $value[$key] = $closure($value[$key], $value); + } else { + $value->$key = $closure($value->$key, $value); + } + } + + return $value; + } + + /** + * 获取关联属性值 + * @access protected + * @param string $relation 关联名 + * @return mixed + */ + protected function getRelationValue(string $relation) + { + $modelRelation = $this->$relation(); + + return $modelRelation instanceof Relation ? $this->getRelationData($modelRelation) : null; + } + + /** + * 数据读取 类型转换 + * @access protected + * @param mixed $value 值 + * @param string|array $type 要转换的类型 + * @return mixed + */ + protected function readTransform($value, $type) + { + if (is_null($value)) { + return; + } + + if (is_array($type)) { + [$type, $param] = $type; + } elseif (strpos($type, ':')) { + [$type, $param] = explode(':', $type, 2); + } + + switch ($type) { + case 'integer': + $value = (int) $value; + break; + case 'float': + if (empty($param)) { + $value = (float) $value; + } else { + $value = (float) number_format($value, (int) $param, '.', ''); + } + break; + case 'boolean': + $value = (bool) $value; + break; + case 'timestamp': + if (!is_null($value)) { + $format = !empty($param) ? $param : $this->dateFormat; + $value = $this->formatDateTime($format, $value, true); + } + break; + case 'datetime': + if (!is_null($value)) { + $format = !empty($param) ? $param : $this->dateFormat; + $value = $this->formatDateTime($format, $value); + } + break; + case 'json': + $value = json_decode($value, true); + break; + case 'array': + $value = empty($value) ? [] : json_decode($value, true); + break; + case 'object': + $value = empty($value) ? new \stdClass() : json_decode($value); + break; + case 'serialize': + try { + $value = unserialize($value); + } catch (\Exception $e) { + $value = null; + } + break; + default: + if (false !== strpos($type, '\\')) { + // 对象类型 + $value = new $type($value); + } + } + + return $value; + } + + /** + * 设置数据字段获取器 + * @access public + * @param string|array $name 字段名 + * @param callable $callback 闭包获取器 + * @return $this + */ + public function withAttribute($name, callable $callback = null) + { + if (is_array($name)) { + foreach ($name as $key => $val) { + $this->withAttribute($key, $val); + } + } else { + $name = $this->getRealFieldName($name); + + if (strpos($name, '.')) { + [$name, $key] = explode('.', $name); + + $this->withAttr[$name][$key] = $callback; + } else { + $this->withAttr[$name] = $callback; + } + } + + return $this; + } + +} diff --git a/vendor/topthink/think-orm/src/model/concern/Conversion.php b/vendor/topthink/think-orm/src/model/concern/Conversion.php new file mode 100644 index 0000000..35d96d0 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/concern/Conversion.php @@ -0,0 +1,358 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model\concern; + +use think\Collection; +use think\db\exception\DbException as Exception; +use think\helper\Str; +use think\Model; +use think\model\Collection as ModelCollection; +use think\model\relation\OneToOne; + +/** + * 模型数据转换处理 + */ +trait Conversion +{ + /** + * 数据输出显示的属性 + * @var array + */ + protected $visible = []; + + /** + * 数据输出隐藏的属性 + * @var array + */ + protected $hidden = []; + + /** + * 数据输出需要追加的属性 + * @var array + */ + protected $append = []; + + /** + * 场景 + * @var array + */ + protected $scene = []; + + /** + * 数据输出字段映射 + * @var array + */ + protected $mapping = []; + + /** + * 数据集对象名 + * @var string + */ + protected $resultSetType; + + /** + * 数据命名是否自动转为驼峰 + * @var bool + */ + protected $convertNameToCamel; + + /** + * 转换数据为驼峰命名(用于输出) + * @access public + * @param bool $toCamel 是否自动驼峰命名 + * @return $this + */ + public function convertNameToCamel(bool $toCamel = true) + { + $this->convertNameToCamel = $toCamel; + return $this; + } + + /** + * 设置需要附加的输出属性 + * @access public + * @param array $append 属性列表 + * @return $this + */ + public function append(array $append = []) + { + $this->append = $append; + + return $this; + } + + /** + * 设置输出层场景 + * @access public + * @param string $scene 场景名称 + * @return $this + */ + public function scene(string $scene) + { + if (isset($this->scene[$scene])) { + $data = $this->scene[$scene]; + foreach (['append', 'hidden', 'visible'] as $name) { + if (isset($data[$name])) { + $this->$name($data[$name]); + } + } + } + + return $this; + } + + /** + * 设置附加关联对象的属性 + * @access public + * @param string $attr 关联属性 + * @param string|array $append 追加属性名 + * @return $this + * @throws Exception + */ + public function appendRelationAttr(string $attr, array $append) + { + $relation = Str::camel($attr); + + if (isset($this->relation[$relation])) { + $model = $this->relation[$relation]; + } else { + $model = $this->getRelationData($this->$relation()); + } + + if ($model instanceof Model) { + foreach ($append as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + if (isset($this->data[$key])) { + throw new Exception('bind attr has exists:' . $key); + } + + $this->data[$key] = $model->$attr; + } + } + + return $this; + } + + /** + * 设置需要隐藏的输出属性 + * @access public + * @param array $hidden 属性列表 + * @return $this + */ + public function hidden(array $hidden = []) + { + $this->hidden = $hidden; + + return $this; + } + + /** + * 设置需要输出的属性 + * @access public + * @param array $visible + * @return $this + */ + public function visible(array $visible = []) + { + $this->visible = $visible; + + return $this; + } + + /** + * 设置属性的映射输出 + * @access public + * @param array $map + * @return $this + */ + public function mapping(array $map) + { + $this->mapping = $map; + + return $this; + } + + /** + * 转换当前模型对象为数组 + * @access public + * @return array + */ + public function toArray(): array + { + $item = []; + $hasVisible = false; + + foreach ($this->visible as $key => $val) { + if (is_string($val)) { + if (strpos($val, '.')) { + [$relation, $name] = explode('.', $val); + $this->visible[$relation][] = $name; + } else { + $this->visible[$val] = true; + $hasVisible = true; + } + unset($this->visible[$key]); + } + } + + foreach ($this->hidden as $key => $val) { + if (is_string($val)) { + if (strpos($val, '.')) { + [$relation, $name] = explode('.', $val); + $this->hidden[$relation][] = $name; + } else { + $this->hidden[$val] = true; + } + unset($this->hidden[$key]); + } + } + + // 合并关联数据 + $data = array_merge($this->data, $this->relation); + + foreach ($data as $key => $val) { + if ($val instanceof Model || $val instanceof ModelCollection) { + // 关联模型对象 + if (isset($this->visible[$key]) && is_array($this->visible[$key])) { + $val->visible($this->visible[$key]); + } elseif (isset($this->hidden[$key]) && is_array($this->hidden[$key])) { + $val->hidden($this->hidden[$key]); + } + // 关联模型对象 + if (!isset($this->hidden[$key]) || true !== $this->hidden[$key]) { + $item[$key] = $val->toArray(); + } + } elseif (isset($this->visible[$key])) { + $item[$key] = $this->getAttr($key); + } elseif (!isset($this->hidden[$key]) && !$hasVisible) { + $item[$key] = $this->getAttr($key); + } + + if (isset($this->mapping[$key])) { + // 检查字段映射 + $mapName = $this->mapping[$key]; + $item[$mapName] = $item[$key]; + unset($item[$key]); + } + } + + // 追加属性(必须定义获取器) + foreach ($this->append as $key => $name) { + $this->appendAttrToArray($item, $key, $name); + } + + if ($this->convertNameToCamel) { + foreach ($item as $key => $val) { + $name = Str::camel($key); + if ($name !== $key) { + $item[$name] = $val; + unset($item[$key]); + } + } + } + + return $item; + } + + protected function appendAttrToArray(array &$item, $key, $name) + { + if (is_array($name)) { + // 追加关联对象属性 + $relation = $this->getRelation($key, true); + $item[$key] = $relation ? $relation->append($name) + ->toArray() : []; + } elseif (strpos($name, '.')) { + [$key, $attr] = explode('.', $name); + // 追加关联对象属性 + $relation = $this->getRelation($key, true); + $item[$key] = $relation ? $relation->append([$attr]) + ->toArray() : []; + } else { + $value = $this->getAttr($name); + $item[$name] = $value; + + $this->getBindAttrValue($name, $value, $item); + } + } + + protected function getBindAttrValue(string $name, $value, array &$item = []) + { + $relation = $this->isRelationAttr($name); + if (!$relation) { + return false; + } + + $modelRelation = $this->$relation(); + + if ($modelRelation instanceof OneToOne) { + $bindAttr = $modelRelation->getBindAttr(); + + if (!empty($bindAttr)) { + unset($item[$name]); + } + + foreach ($bindAttr as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + + if (isset($item[$key])) { + throw new Exception('bind attr has exists:' . $key); + } + + $item[$key] = $value ? $value->getAttr($attr) : null; + } + } + } + + /** + * 转换当前模型对象为JSON字符串 + * @access public + * @param integer $options json参数 + * @return string + */ + public function toJson(int $options = JSON_UNESCAPED_UNICODE): string + { + return json_encode($this->toArray(), $options); + } + + public function __toString() + { + return $this->toJson(); + } + + // JsonSerializable + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * 转换数据集为数据集对象 + * @access public + * @param array|Collection $collection 数据集 + * @param string $resultSetType 数据集类 + * @return Collection + */ + public function toCollection(iterable $collection = [], string $resultSetType = null): Collection + { + $resultSetType = $resultSetType ?: $this->resultSetType; + + if ($resultSetType && false !== strpos($resultSetType, '\\')) { + $collection = new $resultSetType($collection); + } else { + $collection = new ModelCollection($collection); + } + + return $collection; + } + +} diff --git a/vendor/topthink/think-orm/src/model/concern/ModelEvent.php b/vendor/topthink/think-orm/src/model/concern/ModelEvent.php new file mode 100644 index 0000000..d2388ab --- /dev/null +++ b/vendor/topthink/think-orm/src/model/concern/ModelEvent.php @@ -0,0 +1,88 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model\concern; + +use think\db\exception\ModelEventException; +use think\helper\Str; + +/** + * 模型事件处理 + */ +trait ModelEvent +{ + + /** + * Event对象 + * @var object + */ + protected static $event; + + /** + * 是否需要事件响应 + * @var bool + */ + protected $withEvent = true; + + /** + * 设置Event对象 + * @access public + * @param object $event Event对象 + * @return void + */ + public static function setEvent($event) + { + self::$event = $event; + } + + /** + * 当前操作的事件响应 + * @access protected + * @param bool $event 是否需要事件响应 + * @return $this + */ + public function withEvent(bool $event) + { + $this->withEvent = $event; + return $this; + } + + /** + * 触发事件 + * @access protected + * @param string $event 事件名 + * @return bool + */ + protected function trigger(string $event): bool + { + if (!$this->withEvent) { + return true; + } + + $call = 'on' . Str::studly($event); + + try { + if (method_exists(static::class, $call)) { + $result = call_user_func([static::class, $call], $this); + } elseif (is_object(self::$event) && method_exists(self::$event, 'trigger')) { + $result = self::$event->trigger('model.' . static::class . '.' . $event, $this); + $result = empty($result) ? true : end($result); + } else { + $result = true; + } + + return false === $result ? false : true; + } catch (ModelEventException $e) { + return false; + } + } +} diff --git a/vendor/topthink/think-orm/src/model/concern/OptimLock.php b/vendor/topthink/think-orm/src/model/concern/OptimLock.php new file mode 100644 index 0000000..5e61318 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/concern/OptimLock.php @@ -0,0 +1,85 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model\concern; + +use think\db\exception\DbException as Exception; + +/** + * 乐观锁 + */ +trait OptimLock +{ + protected function getOptimLockField() + { + return property_exists($this, 'optimLock') && isset($this->optimLock) ? $this->optimLock : 'lock_version'; + } + + /** + * 数据检查 + * @access protected + * @return void + */ + protected function checkData(): void + { + $this->isExists() ? $this->updateLockVersion() : $this->recordLockVersion(); + } + + /** + * 记录乐观锁 + * @access protected + * @return void + */ + protected function recordLockVersion(): void + { + $optimLock = $this->getOptimLockField(); + + if ($optimLock) { + $this->set($optimLock, 0); + } + } + + /** + * 更新乐观锁 + * @access protected + * @return void + */ + protected function updateLockVersion(): void + { + $optimLock = $this->getOptimLockField(); + + if ($optimLock && $lockVer = $this->getOrigin($optimLock)) { + // 更新乐观锁 + $this->set($optimLock, $lockVer + 1); + } + } + + public function getWhere() + { + $where = parent::getWhere(); + $optimLock = $this->getOptimLockField(); + + if ($optimLock && $lockVer = $this->getOrigin($optimLock)) { + $where[] = [$optimLock, '=', $lockVer]; + } + + return $where; + } + + protected function checkResult($result): void + { + if (!$result) { + throw new Exception('record has update'); + } + } + +} diff --git a/vendor/topthink/think-orm/src/model/concern/RelationShip.php b/vendor/topthink/think-orm/src/model/concern/RelationShip.php new file mode 100644 index 0000000..33c1b89 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/concern/RelationShip.php @@ -0,0 +1,842 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model\concern; + +use Closure; +use think\Collection; +use think\db\BaseQuery as Query; +use think\db\exception\DbException as Exception; +use think\helper\Str; +use think\Model; +use think\model\Relation; +use think\model\relation\BelongsTo; +use think\model\relation\BelongsToMany; +use think\model\relation\HasMany; +use think\model\relation\HasManyThrough; +use think\model\relation\HasOne; +use think\model\relation\HasOneThrough; +use think\model\relation\MorphMany; +use think\model\relation\MorphOne; +use think\model\relation\MorphTo; +use think\model\relation\MorphToMany; +use think\model\relation\OneToOne; + +/** + * 模型关联处理 + */ +trait RelationShip +{ + /** + * 父关联模型对象 + * @var object + */ + private $parent; + + /** + * 模型关联数据 + * @var array + */ + private $relation = []; + + /** + * 关联写入定义信息 + * @var array + */ + private $together = []; + + /** + * 关联自动写入信息 + * @var array + */ + protected $relationWrite = []; + + /** + * 设置父关联对象 + * @access public + * @param Model $model 模型对象 + * @return $this + */ + public function setParent(Model $model) + { + $this->parent = $model; + + return $this; + } + + /** + * 获取父关联对象 + * @access public + * @return Model + */ + public function getParent(): Model + { + return $this->parent; + } + + /** + * 获取当前模型的关联模型数据 + * @access public + * @param string $name 关联方法名 + * @param bool $auto 不存在是否自动获取 + * @return mixed + */ + public function getRelation(string $name = null, bool $auto = false) + { + if (is_null($name)) { + return $this->relation; + } + + if (array_key_exists($name, $this->relation)) { + return $this->relation[$name]; + } elseif ($auto) { + $relation = Str::camel($name); + return $this->getRelationValue($relation); + } + } + + /** + * 设置关联数据对象值 + * @access public + * @param string $name 属性名 + * @param mixed $value 属性值 + * @param array $data 数据 + * @return $this + */ + public function setRelation(string $name, $value, array $data = []) + { + // 检测修改器 + $method = 'set' . Str::studly($name) . 'Attr'; + + if (method_exists($this, $method)) { + $value = $this->$method($value, array_merge($this->data, $data)); + } + + $this->relation[$this->getRealFieldName($name)] = $value; + + return $this; + } + + /** + * 查询当前模型的关联数据 + * @access public + * @param array $relations 关联名 + * @param array $withRelationAttr 关联获取器 + * @return void + */ + public function relationQuery(array $relations, array $withRelationAttr = []): void + { + foreach ($relations as $key => $relation) { + $subRelation = []; + $closure = null; + + if ($relation instanceof Closure) { + // 支持闭包查询过滤关联条件 + $closure = $relation; + $relation = $key; + } + + if (is_array($relation)) { + $subRelation = $relation; + $relation = $key; + } elseif (strpos($relation, '.')) { + [$relation, $subRelation] = explode('.', $relation, 2); + } + + $method = Str::camel($relation); + $relationName = Str::snake($relation); + + $relationResult = $this->$method(); + + if (isset($withRelationAttr[$relationName])) { + $relationResult->withAttr($withRelationAttr[$relationName]); + } + + $this->relation[$relation] = $relationResult->getRelation((array) $subRelation, $closure); + } + } + + /** + * 关联数据写入 + * @access public + * @param array $relation 关联 + * @return $this + */ + public function together(array $relation) + { + $this->together = $relation; + + $this->checkAutoRelationWrite(); + + return $this; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $relation 关联方法名 + * @param mixed $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public static function has(string $relation, string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', Query $query = null): Query + { + return (new static()) + ->$relation() + ->has($operator, $count, $id, $joinType, $query); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $relation 关联方法名 + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public static function hasWhere(string $relation, $where = [], string $fields = '*', string $joinType = '', Query $query = null): Query + { + return (new static()) + ->$relation() + ->hasWhere($where, $fields, $joinType, $query); + } + + /** + * 预载入关联查询 JOIN方式 + * @access public + * @param Query $query Query对象 + * @param string $relation 关联方法名 + * @param mixed $field 字段 + * @param string $joinType JOIN类型 + * @param Closure $closure 闭包 + * @param bool $first + * @return bool + */ + public function eagerly(Query $query, string $relation, $field, string $joinType = '', Closure $closure = null, bool $first = false): bool + { + $relation = Str::camel($relation); + $class = $this->$relation(); + + if ($class instanceof OneToOne) { + $class->eagerly($query, $relation, $field, $joinType, $closure, $first); + return true; + } else { + return false; + } + } + + /** + * 预载入关联查询 返回数据集 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 关联名 + * @param array $withRelationAttr 关联获取器 + * @param bool $join 是否为JOIN方式 + * @param mixed $cache 关联缓存 + * @return void + */ + public function eagerlyResultSet(array &$resultSet, array $relations, array $withRelationAttr = [], bool $join = false, $cache = false): void + { + foreach ($relations as $key => $relation) { + $subRelation = []; + $closure = null; + + if ($relation instanceof Closure) { + $closure = $relation; + $relation = $key; + } + + if (is_array($relation)) { + $subRelation = $relation; + $relation = $key; + } elseif (strpos($relation, '.')) { + [$relation, $subRelation] = explode('.', $relation, 2); + + $subRelation = [$subRelation]; + } + + $relationName = $relation; + $relation = Str::camel($relation); + + $relationResult = $this->$relation(); + + if (isset($withRelationAttr[$relationName])) { + $relationResult->withAttr($withRelationAttr[$relationName]); + } + + if (is_scalar($cache)) { + $relationCache = [$cache]; + } else { + $relationCache = $cache[$relationName] ?? $cache; + } + + $relationResult->eagerlyResultSet($resultSet, $relationName, $subRelation, $closure, $relationCache, $join); + } + } + + /** + * 预载入关联查询 返回模型对象 + * @access public + * @param Model $result 数据对象 + * @param array $relations 关联 + * @param array $withRelationAttr 关联获取器 + * @param bool $join 是否为JOIN方式 + * @param mixed $cache 关联缓存 + * @return void + */ + public function eagerlyResult(Model $result, array $relations, array $withRelationAttr = [], bool $join = false, $cache = false): void + { + foreach ($relations as $key => $relation) { + $subRelation = []; + $closure = null; + + if ($relation instanceof Closure) { + $closure = $relation; + $relation = $key; + } + + if (is_array($relation)) { + $subRelation = $relation; + $relation = $key; + } elseif (strpos($relation, '.')) { + [$relation, $subRelation] = explode('.', $relation, 2); + + $subRelation = [$subRelation]; + } + + $relationName = $relation; + $relation = Str::camel($relation); + + $relationResult = $this->$relation(); + + if (isset($withRelationAttr[$relationName])) { + $relationResult->withAttr($withRelationAttr[$relationName]); + } + + if (is_scalar($cache)) { + $relationCache = [$cache]; + } else { + $relationCache = $cache[$relationName] ?? []; + } + + $relationResult->eagerlyResult($result, $relationName, $subRelation, $closure, $relationCache, $join); + } + } + + /** + * 绑定(一对一)关联属性到当前模型 + * @access protected + * @param string $relation 关联名称 + * @param array $attrs 绑定属性 + * @return $this + * @throws Exception + */ + public function bindAttr(string $relation, array $attrs = []) + { + $relation = $this->getRelation($relation); + + foreach ($attrs as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + $value = $this->getOrigin($key); + + if (!is_null($value)) { + throw new Exception('bind attr has exists:' . $key); + } + + $this->set($key, $relation ? $relation->$attr : null); + } + + return $this; + } + + /** + * 关联统计 + * @access public + * @param Query $query 查询对象 + * @param array $relations 关联名 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param bool $useSubQuery 子查询 + * @return void + */ + public function relationCount(Query $query, array $relations, string $aggregate = 'sum', string $field = '*', bool $useSubQuery = true): void + { + foreach ($relations as $key => $relation) { + $closure = $name = null; + + if ($relation instanceof Closure) { + $closure = $relation; + $relation = $key; + } elseif (is_string($key)) { + $name = $relation; + $relation = $key; + } + + $relation = Str::camel($relation); + + if ($useSubQuery) { + $count = $this->$relation()->getRelationCountQuery($closure, $aggregate, $field, $name); + } else { + $count = $this->$relation()->relationCount($this, $closure, $aggregate, $field, $name); + } + + if (empty($name)) { + $name = Str::snake($relation) . '_' . $aggregate; + } + + if ($useSubQuery) { + $query->field(['(' . $count . ')' => $name]); + } else { + $this->setAttr($name, $count); + } + } + } + + /** + * HAS ONE 关联定义 + * @access public + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前主键 + * @return HasOne + */ + public function hasOne(string $model, string $foreignKey = '', string $localKey = ''): HasOne + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $localKey = $localKey ?: $this->getPk(); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + + return new HasOne($this, $model, $foreignKey, $localKey); + } + + /** + * BELONGS TO 关联定义 + * @access public + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 关联主键 + * @return BelongsTo + */ + public function belongsTo(string $model, string $foreignKey = '', string $localKey = ''): BelongsTo + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $foreignKey = $foreignKey ?: $this->getForeignKey((new $model)->getName()); + $localKey = $localKey ?: (new $model)->getPk(); + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); + $relation = Str::snake($trace[1]['function']); + + return new BelongsTo($this, $model, $foreignKey, $localKey, $relation); + } + + /** + * HAS MANY 关联定义 + * @access public + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前主键 + * @return HasMany + */ + public function hasMany(string $model, string $foreignKey = '', string $localKey = ''): HasMany + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $localKey = $localKey ?: $this->getPk(); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + + return new HasMany($this, $model, $foreignKey, $localKey); + } + + /** + * HAS MANY 远程关联定义 + * @access public + * @param string $model 模型名 + * @param string $through 中间模型名 + * @param string $foreignKey 关联外键 + * @param string $throughKey 关联外键 + * @param string $localKey 当前主键 + * @param string $throughPk 中间表主键 + * @return HasManyThrough + */ + public function hasManyThrough(string $model, string $through, string $foreignKey = '', string $throughKey = '', string $localKey = '', string $throughPk = ''): HasManyThrough + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $through = $this->parseModel($through); + $localKey = $localKey ?: $this->getPk(); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + $throughKey = $throughKey ?: $this->getForeignKey((new $through)->getName()); + $throughPk = $throughPk ?: (new $through)->getPk(); + + return new HasManyThrough($this, $model, $through, $foreignKey, $throughKey, $localKey, $throughPk); + } + + /** + * HAS ONE 远程关联定义 + * @access public + * @param string $model 模型名 + * @param string $through 中间模型名 + * @param string $foreignKey 关联外键 + * @param string $throughKey 关联外键 + * @param string $localKey 当前主键 + * @param string $throughPk 中间表主键 + * @return HasOneThrough + */ + public function hasOneThrough(string $model, string $through, string $foreignKey = '', string $throughKey = '', string $localKey = '', string $throughPk = ''): HasOneThrough + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $through = $this->parseModel($through); + $localKey = $localKey ?: $this->getPk(); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + $throughKey = $throughKey ?: $this->getForeignKey((new $through)->getName()); + $throughPk = $throughPk ?: (new $through)->getPk(); + + return new HasOneThrough($this, $model, $through, $foreignKey, $throughKey, $localKey, $throughPk); + } + + /** + * BELONGS TO MANY 关联定义 + * @access public + * @param string $model 模型名 + * @param string $middle 中间表/模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前模型关联键 + * @return BelongsToMany + */ + public function belongsToMany(string $model, string $middle = '', string $foreignKey = '', string $localKey = ''): BelongsToMany + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $name = Str::snake(class_basename($model)); + $middle = $middle ?: Str::snake($this->name) . '_' . $name; + $foreignKey = $foreignKey ?: $name . '_id'; + $localKey = $localKey ?: $this->getForeignKey($this->name); + + return new BelongsToMany($this, $model, $middle, $foreignKey, $localKey); + } + + /** + * MORPH One 关联定义 + * @access public + * @param string $model 模型名 + * @param string|array $morph 多态字段信息 + * @param string $type 多态类型 + * @return MorphOne + */ + public function morphOne(string $model, $morph = null, string $type = ''): MorphOne + { + // 记录当前关联信息 + $model = $this->parseModel($model); + + if (is_null($morph)) { + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); + $morph = Str::snake($trace[1]['function']); + } + + if (is_array($morph)) { + [$morphType, $foreignKey] = $morph; + } else { + $morphType = $morph . '_type'; + $foreignKey = $morph . '_id'; + } + + $type = $type ?: get_class($this); + + return new MorphOne($this, $model, $foreignKey, $morphType, $type); + } + + /** + * MORPH MANY 关联定义 + * @access public + * @param string $model 模型名 + * @param string|array $morph 多态字段信息 + * @param string $type 多态类型 + * @return MorphMany + */ + public function morphMany(string $model, $morph = null, string $type = ''): MorphMany + { + // 记录当前关联信息 + $model = $this->parseModel($model); + + if (is_null($morph)) { + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); + $morph = Str::snake($trace[1]['function']); + } + + $type = $type ?: get_class($this); + + if (is_array($morph)) { + [$morphType, $foreignKey] = $morph; + } else { + $morphType = $morph . '_type'; + $foreignKey = $morph . '_id'; + } + + return new MorphMany($this, $model, $foreignKey, $morphType, $type); + } + + /** + * MORPH TO 关联定义 + * @access public + * @param string|array $morph 多态字段信息 + * @param array $alias 多态别名定义 + * @return MorphTo + */ + public function morphTo($morph = null, array $alias = []): MorphTo + { + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); + $relation = Str::snake($trace[1]['function']); + + if (is_null($morph)) { + $morph = $relation; + } + + // 记录当前关联信息 + if (is_array($morph)) { + [$morphType, $foreignKey] = $morph; + } else { + $morphType = $morph . '_type'; + $foreignKey = $morph . '_id'; + } + + return new MorphTo($this, $morphType, $foreignKey, $alias, $relation); + } + + /** + * MORPH TO MANY关联定义 + * @access public + * @param string $model 模型名 + * @param string $middle 中间表名/模型名 + * @param string|array $morph 多态字段信息 + * @param string $localKey 当前模型关联键 + * @return MorphToMany + */ + public function morphToMany(string $model, string $middle, $morph = null, string $localKey = null): MorphToMany + { + if (is_null($morph)) { + $morph = $middle; + } + + // 记录当前关联信息 + if (is_array($morph)) { + [$morphType, $morphKey] = $morph; + } else { + $morphType = $morph . '_type'; + $morphKey = $morph . '_id'; + } + + $model = $this->parseModel($model); + $name = Str::snake(class_basename($model)); + $localKey = $localKey ?: $this->getForeignKey($name); + + return new MorphToMany($this, $model, $middle, $morphType, $morphKey, $localKey); + } + + /** + * MORPH BY MANY关联定义 + * @access public + * @param string $model 模型名 + * @param string $middle 中间表名/模型名 + * @param string|array $morph 多态字段信息 + * @param string $foreignKey 关联外键 + * @return MorphToMany + */ + public function morphByMany(string $model, string $middle, $morph = null, string $foreignKey = null): MorphToMany + { + if (is_null($morph)) { + $morph = $middle; + } + + // 记录当前关联信息 + if (is_array($morph)) { + [$morphType, $morphKey] = $morph; + } else { + $morphType = $morph . '_type'; + $morphKey = $morph . '_id'; + } + + $model = $this->parseModel($model); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + + return new MorphToMany($this, $model, $middle, $morphType, $morphKey, $foreignKey, true); + } + + /** + * 解析模型的完整命名空间 + * @access protected + * @param string $model 模型名(或者完整类名) + * @return string + */ + protected function parseModel(string $model): string + { + if (false === strpos($model, '\\')) { + $path = explode('\\', static::class); + array_pop($path); + array_push($path, Str::studly($model)); + $model = implode('\\', $path); + } + + return $model; + } + + /** + * 获取模型的默认外键名 + * @access protected + * @param string $name 模型名 + * @return string + */ + protected function getForeignKey(string $name): string + { + if (strpos($name, '\\')) { + $name = class_basename($name); + } + + return Str::snake($name) . '_id'; + } + + /** + * 检查属性是否为关联属性 如果是则返回关联方法名 + * @access protected + * @param string $attr 关联属性名 + * @return string|false + */ + protected function isRelationAttr(string $attr) + { + $relation = Str::camel($attr); + + if ((method_exists($this, $relation) && !method_exists('think\Model', $relation)) || isset(static::$macro[static::class][$relation])) { + return $relation; + } + + return false; + } + + /** + * 智能获取关联模型数据 + * @access protected + * @param Relation $modelRelation 模型关联对象 + * @return mixed + */ + protected function getRelationData(Relation $modelRelation) + { + if ($this->parent && !$modelRelation->isSelfRelation() + && get_class($this->parent) == get_class($modelRelation->getModel())) { + return $this->parent; + } + + // 获取关联数据 + return $modelRelation->getRelation(); + } + + /** + * 关联数据自动写入检查 + * @access protected + * @return void + */ + protected function checkAutoRelationWrite(): void + { + foreach ($this->together as $key => $name) { + if (is_array($name)) { + if (key($name) === 0) { + $this->relationWrite[$key] = []; + // 绑定关联属性 + foreach ($name as $val) { + if (isset($this->data[$val])) { + $this->relationWrite[$key][$val] = $this->data[$val]; + } + } + } else { + // 直接传入关联数据 + $this->relationWrite[$key] = $name; + } + } elseif (isset($this->relation[$name])) { + $this->relationWrite[$name] = $this->relation[$name]; + } elseif (isset($this->data[$name])) { + $this->relationWrite[$name] = $this->data[$name]; + unset($this->data[$name]); + } + } + } + + /** + * 自动关联数据更新(针对一对一关联) + * @access protected + * @return void + */ + protected function autoRelationUpdate(): void + { + foreach ($this->relationWrite as $name => $val) { + if ($val instanceof Model) { + $val->exists(true)->save(); + } else { + $model = $this->getRelation($name, true); + + if ($model instanceof Model) { + $model->exists(true)->save($val); + } + } + } + } + + /** + * 自动关联数据写入(针对一对一关联) + * @access protected + * @return void + */ + protected function autoRelationInsert(): void + { + foreach ($this->relationWrite as $name => $val) { + $method = Str::camel($name); + $this->$method()->save($val); + } + } + + /** + * 自动关联数据删除(支持一对一及一对多关联) + * @access protected + * @param bool $force 强制删除 + * @return void + */ + protected function autoRelationDelete($force = false): void + { + foreach ($this->relationWrite as $key => $name) { + $name = is_numeric($key) ? $name : $key; + $result = $this->getRelation($name, true); + + if ($result instanceof Model) { + $result->force($force)->delete(); + } elseif ($result instanceof Collection) { + foreach ($result as $model) { + $model->force($force)->delete(); + } + } + } + } + + /** + * 移除当前模型的关联属性 + * @access public + * @return $this + */ + public function removeRelation() + { + $this->relation = []; + return $this; + } +} diff --git a/vendor/topthink/think-orm/src/model/concern/SoftDelete.php b/vendor/topthink/think-orm/src/model/concern/SoftDelete.php new file mode 100644 index 0000000..8d76bb0 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/concern/SoftDelete.php @@ -0,0 +1,257 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model\concern; + +use think\db\BaseQuery as Query; +use think\Model; + +/** + * 数据软删除 + * @mixin Model + */ +trait SoftDelete +{ + /** + * 是否包含软删除数据 + * @var bool + */ + protected $withTrashed = false; + + /** + * 判断当前实例是否被软删除 + * @access public + * @return bool + */ + public function trashed(): bool + { + $field = $this->getDeleteTimeField(); + + if ($field && !empty($this->getOrigin($field))) { + return true; + } + + return false; + } + + /** + * 查询软删除数据 + * @access public + * @return Query + */ + public static function withTrashed(): Query + { + $model = new static(); + + return $model->withTrashedData(true)->db(); + } + + /** + * 是否包含软删除数据 + * @access protected + * @param bool $withTrashed 是否包含软删除数据 + * @return $this + */ + protected function withTrashedData(bool $withTrashed) + { + $this->withTrashed = $withTrashed; + return $this; + } + + /** + * 只查询软删除数据 + * @access public + * @return Query + */ + public static function onlyTrashed(): Query + { + $model = new static(); + $field = $model->getDeleteTimeField(true); + + if ($field) { + return $model + ->db() + ->useSoftDelete($field, $model->getWithTrashedExp()); + } + + return $model->db(); + } + + /** + * 获取软删除数据的查询条件 + * @access protected + * @return array + */ + protected function getWithTrashedExp(): array + { + return is_null($this->defaultSoftDelete) ? ['notnull', ''] : ['<>', $this->defaultSoftDelete]; + } + + /** + * 删除当前的记录 + * @access public + * @return bool + */ + public function delete(): bool + { + if (!$this->isExists() || $this->isEmpty() || false === $this->trigger('BeforeDelete')) { + return false; + } + + $name = $this->getDeleteTimeField(); + $force = $this->isForce(); + + if ($name && !$force) { + // 软删除 + $this->set($name, $this->autoWriteTimestamp($name)); + + $result = $this->exists()->withEvent(false)->save(); + + $this->withEvent(true); + } else { + // 读取更新条件 + $where = $this->getWhere(); + + // 删除当前模型数据 + $result = $this->db() + ->where($where) + ->removeOption('soft_delete') + ->delete(); + + $this->lazySave(false); + } + + // 关联删除 + if (!empty($this->relationWrite)) { + $this->autoRelationDelete($force); + } + + $this->trigger('AfterDelete'); + + $this->exists(false); + + return true; + } + + /** + * 删除记录 + * @access public + * @param mixed $data 主键列表 支持闭包查询条件 + * @param bool $force 是否强制删除 + * @return bool + */ + public static function destroy($data, bool $force = false): bool + { + // 传入空值(包括空字符串和空数组)的时候不会做任何的数据删除操作,但传入0则是有效的 + if(empty($data) && $data !== 0){ + return false; + } + // 仅当强制删除时包含软删除数据 + $model = (new static()); + if($force){ + $model->withTrashedData(true); + } + $query = $model->db(false); + + if (is_array($data) && key($data) !== 0) { + $query->where($data); + $data = null; + } elseif ($data instanceof \Closure) { + call_user_func_array($data, [ & $query]); + $data = null; + } elseif (is_null($data)) { + return false; + } + + $resultSet = $query->select($data); + + foreach ($resultSet as $result) { + $result->force($force)->delete(); + } + + return true; + } + + /** + * 恢复被软删除的记录 + * @access public + * @param array $where 更新条件 + * @return bool + */ + public function restore($where = []): bool + { + $name = $this->getDeleteTimeField(); + + if (!$name || false === $this->trigger('BeforeRestore')) { + return false; + } + + if (empty($where)) { + $pk = $this->getPk(); + if (is_string($pk)) { + $where[] = [$pk, '=', $this->getData($pk)]; + } + } + + // 恢复删除 + $this->db(false) + ->where($where) + ->useSoftDelete($name, $this->getWithTrashedExp()) + ->update([$name => $this->defaultSoftDelete]); + + $this->trigger('AfterRestore'); + + return true; + } + + /** + * 获取软删除字段 + * @access protected + * @param bool $read 是否查询操作 写操作的时候会自动去掉表别名 + * @return string|false + */ + protected function getDeleteTimeField(bool $read = false) + { + $field = property_exists($this, 'deleteTime') && isset($this->deleteTime) ? $this->deleteTime : 'delete_time'; + + if (false === $field) { + return false; + } + + if (false === strpos($field, '.')) { + $field = '__TABLE__.' . $field; + } + + if (!$read && strpos($field, '.')) { + $array = explode('.', $field); + $field = array_pop($array); + } + + return $field; + } + + /** + * 查询的时候默认排除软删除数据 + * @access protected + * @param Query $query + * @return void + */ + protected function withNoTrashed(Query $query): void + { + $field = $this->getDeleteTimeField(true); + + if ($field) { + $condition = is_null($this->defaultSoftDelete) ? ['null', ''] : ['=', $this->defaultSoftDelete]; + $query->useSoftDelete($field, $condition); + } + } +} diff --git a/vendor/topthink/think-orm/src/model/concern/TimeStamp.php b/vendor/topthink/think-orm/src/model/concern/TimeStamp.php new file mode 100644 index 0000000..2492e06 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/concern/TimeStamp.php @@ -0,0 +1,223 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model\concern; + +use DateTime; + +/** + * 自动时间戳 + */ +trait TimeStamp +{ + /** + * 是否需要自动写入时间戳 如果设置为字符串 则表示时间字段的类型 + * @var bool|string + */ + protected $autoWriteTimestamp; + + /** + * 创建时间字段 false表示关闭 + * @var false|string + */ + protected $createTime = 'create_time'; + + /** + * 更新时间字段 false表示关闭 + * @var false|string + */ + protected $updateTime = 'update_time'; + + /** + * 时间字段显示格式 + * @var string + */ + protected $dateFormat; + + /** + * 是否需要自动写入时间字段 + * @access public + * @param bool|string $auto + * @return $this + */ + public function isAutoWriteTimestamp($auto) + { + $this->autoWriteTimestamp = $this->checkTimeFieldType($auto); + + return $this; + } + + /** + * 检测时间字段的实际类型 + * @access public + * @param bool|string $type + * @return mixed + */ + protected function checkTimeFieldType($type) + { + if (true === $type) { + if (isset($this->type[$this->createTime])) { + $type = $this->type[$this->createTime]; + } elseif (isset($this->schema[$this->createTime]) && in_array($this->schema[$this->createTime], ['datetime', 'date', 'timestamp', 'int'])) { + $type = $this->schema[$this->createTime]; + } else { + $type = $this->getFieldType($this->createTime); + } + } + + return $type; + } + + /** + * 设置时间字段名称 + * @access public + * @param string $createTime + * @param string $updateTime + * @return $this + */ + public function setTimeField(string $createTime, string $updateTime) + { + $this->createTime = $createTime; + $this->updateTime = $updateTime; + + return $this; + } + + /** + * 获取自动写入时间字段 + * @access public + * @return bool|string + */ + public function getAutoWriteTimestamp() + { + return $this->autoWriteTimestamp; + } + + /** + * 设置时间字段格式化 + * @access public + * @param string|false $format + * @return $this + */ + public function setDateFormat($format) + { + $this->dateFormat = $format; + + return $this; + } + + /** + * 获取自动写入时间字段 + * @access public + * @return string|false + */ + public function getDateFormat() + { + return $this->dateFormat; + } + + /** + * 自动写入时间戳 + * @access protected + * @return mixed + */ + protected function autoWriteTimestamp() + { + // 检测时间字段类型 + $type = $this->checkTimeFieldType($this->autoWriteTimestamp); + + return is_string($type) ? $this->getTimeTypeValue($type) : time(); + } + + /** + * 获取指定类型的时间字段值 + * @access protected + * @param string $type 时间字段类型 + * @return mixed + */ + protected function getTimeTypeValue(string $type) + { + $value = time(); + + switch ($type) { + case 'datetime': + case 'date': + case 'timestamp': + $value = $this->formatDateTime('Y-m-d H:i:s.u'); + break; + default: + if (false !== strpos($type, '\\')) { + // 对象数据写入 + $obj = new $type(); + if (method_exists($obj, '__toString')) { + // 对象数据写入 + $value = $obj->__toString(); + } + } + } + + return $value; + } + + /** + * 时间日期字段格式化处理 + * @access protected + * @param mixed $format 日期格式 + * @param mixed $time 时间日期表达式 + * @param bool $timestamp 时间表达式是否为时间戳 + * @return mixed + */ + protected function formatDateTime($format, $time = 'now', bool $timestamp = false) + { + if (empty($time)) { + return; + } + + if (false === $format) { + return $time; + } elseif (false !== strpos($format, '\\')) { + return new $format($time); + } + + if ($time instanceof DateTime) { + $dateTime = $time; + } elseif ($timestamp) { + $dateTime = new DateTime(); + $dateTime->setTimestamp((int) $time); + } else { + $dateTime = new DateTime($time); + } + + return $dateTime->format($format); + } + + /** + * 获取时间字段值 + * @access protected + * @param mixed $value + * @return mixed + */ + protected function getTimestampValue($value) + { + $type = $this->checkTimeFieldType($this->autoWriteTimestamp); + + if (is_string($type) && in_array(strtolower($type), [ + 'datetime', 'date', 'timestamp', + ])) { + $value = $this->formatDateTime($this->dateFormat, $value); + } else { + $value = $this->formatDateTime($this->dateFormat, $value, true); + } + + return $value; + } +} diff --git a/vendor/topthink/think-orm/src/model/concern/Virtual.php b/vendor/topthink/think-orm/src/model/concern/Virtual.php new file mode 100644 index 0000000..66cdfb7 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/concern/Virtual.php @@ -0,0 +1,90 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model\concern; + +use think\db\BaseQuery as Query; +use think\db\exception\DbException as Exception; + +/** + * 虚拟模型 + */ +trait Virtual +{ + /** + * 获取当前模型的数据库查询对象 + * @access public + * @param array $scope 设置不使用的全局查询范围 + * @return Query + */ + public function db($scope = []): Query + { + throw new Exception('virtual model not support db query'); + } + + /** + * 获取字段类型信息 + * @access public + * @param string $field 字段名 + * @return string|null + */ + public function getFieldType(string $field) + {} + + /** + * 保存当前数据对象 + * @access public + * @param array $data 数据 + * @param string $sequence 自增序列名 + * @return bool + */ + public function save(array $data = [], string $sequence = null): bool + { + // 数据对象赋值 + $this->setAttrs($data); + + if ($this->isEmpty() || false === $this->trigger('BeforeWrite')) { + return false; + } + + // 写入回调 + $this->trigger('AfterWrite'); + + $this->exists(true); + + return true; + } + + /** + * 删除当前的记录 + * @access public + * @return bool + */ + public function delete(): bool + { + if (!$this->isExists() || $this->isEmpty() || false === $this->trigger('BeforeDelete')) { + return false; + } + + // 关联删除 + if (!empty($this->relationWrite)) { + $this->autoRelationDelete(); + } + + $this->trigger('AfterDelete'); + + $this->exists(false); + + return true; + } + +} diff --git a/vendor/topthink/think-orm/src/model/relation/BelongsTo.php b/vendor/topthink/think-orm/src/model/relation/BelongsTo.php new file mode 100644 index 0000000..941e1d4 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/relation/BelongsTo.php @@ -0,0 +1,334 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model\relation; + +use Closure; +use think\db\BaseQuery as Query; +use think\helper\Str; +use think\Model; + +/** + * BelongsTo关联类 + */ +class BelongsTo extends OneToOne +{ + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 关联主键 + * @param string $relation 关联名 + */ + public function __construct(Model $parent, string $model, string $foreignKey, string $localKey, string $relation = null) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->query = (new $model)->db(); + $this->relation = $relation; + + if (get_class($parent) == $model) { + $this->selfRelation = true; + } + } + + /** + * 延迟获取关联数据 + * @access public + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包查询条件 + * @return Model + */ + public function getRelation(array $subRelation = [], Closure $closure = null) + { + if ($closure) { + $closure($this->getClosureType($closure)); + } + + $foreignKey = $this->foreignKey; + + $relationModel = $this->query + ->removeWhereField($this->localKey) + ->where($this->localKey, $this->parent->$foreignKey) + ->relation($subRelation) + ->find(); + + if ($relationModel) { + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($this->parent, $relationModel); + } + + $relationModel->setParent(clone $this->parent); + } else { + $relationModel = $this->getDefaultModel(); + } + + return $relationModel; + } + + /** + * 创建关联统计子查询 + * @access public + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 聚合字段别名 + * @return string + */ + public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', &$name = ''): string + { + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->query + ->whereExp($this->localKey, '=' . $this->parent->getTable() . '.' . $this->foreignKey) + ->fetchSql() + ->$aggregate($field); + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return integer + */ + public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null) + { + $foreignKey = $this->foreignKey; + + if (!isset($result->$foreignKey)) { + return 0; + } + + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->query + ->where($this->localKey, '=', $result->$foreignKey) + ->$aggregate($field); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', Query $query = null): Query + { + $table = $this->query->getTable(); + $model = class_basename($this->parent); + $relation = class_basename($this->model); + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + $softDelete = $this->query->getOptions('soft_delete'); + $query = $query ?: $this->parent->db()->alias($model); + + return $query->whereExists(function ($query) use ($table, $model, $relation, $localKey, $foreignKey, $softDelete) { + $query->table([$table => $relation]) + ->field($relation . '.' . $localKey) + ->whereExp($model . '.' . $foreignKey, '=' . $relation . '.' . $localKey) + ->when($softDelete, function ($query) use ($softDelete, $relation) { + $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }); + }); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function hasWhere($where = [], $fields = null, string $joinType = '', Query $query = null): Query + { + $table = $this->query->getTable(); + $model = class_basename($this->parent); + $relation = class_basename($this->model); + + if (is_array($where)) { + $this->getQueryWhere($where, $relation); + } elseif ($where instanceof Query) { + $where->via($relation); + } elseif ($where instanceof Closure) { + $where($this->query->via($relation)); + $where = $this->query; + } + + $fields = $this->getRelationQueryFields($fields, $model); + $softDelete = $this->query->getOptions('soft_delete'); + $query = $query ?: $this->parent->db(); + + return $query->alias($model) + ->field($fields) + ->join([$table => $relation], $model . '.' . $this->foreignKey . '=' . $relation . '.' . $this->localKey, $joinType ?: $this->joinType) + ->when($softDelete, function ($query) use ($softDelete, $relation) { + $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }) + ->where($where); + } + + /** + * 预载入关联查询(数据集) + * @access protected + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + protected function eagerlySet(array &$resultSet, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$foreignKey)) { + $range[] = $result->$foreignKey; + } + } + + if (!empty($range)) { + $this->query->removeWhereField($localKey); + + $data = $this->eagerlyWhere([ + [$localKey, 'in', $range], + ], $localKey, $subRelation, $closure, $cache); + + // 关联数据封装 + foreach ($resultSet as $result) { + // 关联模型 + if (!isset($data[$result->$foreignKey])) { + $relationModel = $this->getDefaultModel(); + } else { + $relationModel = $data[$result->$foreignKey]; + $relationModel->setParent(clone $result); + $relationModel->exists(true); + } + + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($result, $relationModel); + } else { + // 设置关联属性 + $result->setRelation($relation, $relationModel); + } + } + } + } + + /** + * 预载入关联查询(数据) + * @access protected + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + protected function eagerlyOne(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $this->query->removeWhereField($localKey); + + $data = $this->eagerlyWhere([ + [$localKey, '=', $result->$foreignKey], + ], $localKey, $subRelation, $closure, $cache); + + // 关联模型 + if (!isset($data[$result->$foreignKey])) { + $relationModel = $this->getDefaultModel(); + } else { + $relationModel = $data[$result->$foreignKey]; + $relationModel->setParent(clone $result); + $relationModel->exists(true); + } + + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($result, $relationModel); + } else { + // 设置关联属性 + $result->setRelation($relation, $relationModel); + } + } + + /** + * 添加关联数据 + * @access public + * @param Model $model关联模型对象 + * @return Model + */ + public function associate(Model $model): Model + { + $this->parent->setAttr($this->foreignKey, $model->getKey()); + $this->parent->save(); + + return $this->parent->setRelation($this->relation, $model); + } + + /** + * 注销关联数据 + * @access public + * @return Model + */ + public function dissociate(): Model + { + $foreignKey = $this->foreignKey; + + $this->parent->setAttr($foreignKey, null); + $this->parent->save(); + + return $this->parent->setRelation($this->relation, null); + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery(): void + { + if (empty($this->baseQuery)) { + if (isset($this->parent->{$this->foreignKey})) { + // 关联查询带入关联条件 + $this->query->where($this->localKey, '=', $this->parent->{$this->foreignKey}); + } + + $this->baseQuery = true; + } + } +} diff --git a/vendor/topthink/think-orm/src/model/relation/BelongsToMany.php b/vendor/topthink/think-orm/src/model/relation/BelongsToMany.php new file mode 100644 index 0000000..5f177c1 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/relation/BelongsToMany.php @@ -0,0 +1,689 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use Closure; +use think\Collection; +use think\db\BaseQuery as Query; +use think\db\exception\DbException as Exception; +use think\db\Raw; +use think\Model; +use think\model\Pivot; +use think\model\Relation; +use think\Paginator; + +/** + * 多对多关联类 + */ +class BelongsToMany extends Relation +{ + /** + * 中间表表名 + * @var string + */ + protected $middle; + + /** + * 中间表模型名称 + * @var string + */ + protected $pivotName; + + /** + * 中间表模型对象 + * @var Pivot + */ + protected $pivot; + + /** + * 中间表数据名称 + * @var string + */ + protected $pivotDataName = 'pivot'; + + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $middle 中间表/模型名 + * @param string $foreignKey 关联模型外键 + * @param string $localKey 当前模型关联键 + */ + public function __construct(Model $parent, string $model, string $middle, string $foreignKey, string $localKey) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + + if (false !== strpos($middle, '\\')) { + $this->pivotName = $middle; + $this->middle = class_basename($middle); + } else { + $this->middle = $middle; + } + + $this->query = (new $model)->db(); + $this->pivot = $this->newPivot(); + } + + /** + * 设置中间表模型 + * @access public + * @param $pivot + * @return $this + */ + public function pivot(string $pivot) + { + $this->pivotName = $pivot; + return $this; + } + + /** + * 设置中间表数据名称 + * @access public + * @param string $name + * @return $this + */ + public function name(string $name) + { + $this->pivotDataName = $name; + return $this; + } + + /** + * 实例化中间表模型 + * @access public + * @param $data + * @return Pivot + * @throws Exception + */ + protected function newPivot(array $data = []): Pivot + { + $class = $this->pivotName ?: Pivot::class; + $pivot = new $class($data, $this->parent, $this->middle); + + if ($pivot instanceof Pivot) { + return $pivot; + } else { + throw new Exception('pivot model must extends: \think\model\Pivot'); + } + } + + /** + * 合成中间表模型 + * @access protected + * @param array|Collection|Paginator $models + */ + protected function hydratePivot(iterable $models) + { + foreach ($models as $model) { + $pivot = []; + + foreach ($model->getData() as $key => $val) { + if (strpos($key, '__')) { + [$name, $attr] = explode('__', $key, 2); + + if ('pivot' == $name) { + $pivot[$attr] = $val; + unset($model->$key); + } + } + } + + $model->setRelation($this->pivotDataName, $this->newPivot($pivot)); + } + } + + /** + * 延迟获取关联数据 + * @access public + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包查询条件 + * @return Collection + */ + public function getRelation(array $subRelation = [], Closure $closure = null): Collection + { + if ($closure) { + $closure($this->getClosureType($closure)); + } + + $result = $this->relation($subRelation) + ->select() + ->setParent(clone $this->parent); + + $this->hydratePivot($result); + + return $result; + } + + /** + * 重载select方法 + * @access public + * @param mixed $data + * @return Collection + */ + public function select($data = null): Collection + { + $this->baseQuery(); + $result = $this->query->select($data); + $this->hydratePivot($result); + + return $result; + } + + /** + * 重载paginate方法 + * @access public + * @param int|array $listRows + * @param int|bool $simple + * @return Paginator + */ + public function paginate($listRows = null, $simple = false): Paginator + { + $this->baseQuery(); + $result = $this->query->paginate($listRows, $simple); + $this->hydratePivot($result); + + return $result; + } + + /** + * 重载find方法 + * @access public + * @param mixed $data + * @return Model + */ + public function find($data = null) + { + $this->baseQuery(); + $result = $this->query->find($data); + + if ($result && !$result->isEmpty()) { + $this->hydratePivot([$result]); + } + + return $result; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Model + */ + public function has(string $operator = '>=', $count = 1, $id = '*', string $joinType = 'INNER', Query $query = null) + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + * @throws Exception + */ + public function hasWhere($where = [], $fields = null, string $joinType = '', Query $query = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 设置中间表的查询条件 + * @access public + * @param string $field + * @param string $op + * @param mixed $condition + * @return $this + */ + public function wherePivot($field, $op = null, $condition = null) + { + $this->query->where('pivot.' . $field, $op, $condition); + return $this; + } + + /** + * 预载入关联查询(数据集) + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void + { + $localKey = $this->localKey; + $pk = $resultSet[0]->getPk(); + $range = []; + + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$pk)) { + $range[] = $result->$pk; + } + } + + if (!empty($range)) { + // 查询关联数据 + $data = $this->eagerlyManyToMany([ + ['pivot.' . $localKey, 'in', $range], + ], $subRelation, $closure, $cache); + + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$pk])) { + $data[$result->$pk] = []; + } + + $result->setRelation($relation, $this->resultSetBuild($data[$result->$pk], clone $this->parent)); + } + } + } + + /** + * 预载入关联查询(单个数据) + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResult(Model $result, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void + { + $pk = $result->getPk(); + + if (isset($result->$pk)) { + $pk = $result->$pk; + // 查询管理数据 + $data = $this->eagerlyManyToMany([ + ['pivot.' . $this->localKey, '=', $pk], + ], $subRelation, $closure, $cache); + + // 关联数据封装 + if (!isset($data[$pk])) { + $data[$pk] = []; + } + + $result->setRelation($relation, $this->resultSetBuild($data[$pk], clone $this->parent)); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return integer + */ + public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): float + { + $pk = $result->getPk(); + + if (!isset($result->$pk)) { + return 0; + } + + $pk = $result->$pk; + + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [ + ['pivot.' . $this->localKey, '=', $pk], + ])->$aggregate($field); + } + + /** + * 获取关联统计子查询 + * @access public + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return string + */ + public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string + { + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [ + [ + 'pivot.' . $this->localKey, 'exp', new Raw('=' . $this->parent->db(false)->getTable() . '.' . $this->parent->getPk()), + ], + ])->fetchSql()->$aggregate($field); + } + + /** + * 多对多 关联模型预查询 + * @access protected + * @param array $where 关联预查询条件 + * @param array $subRelation 子关联 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return array + */ + protected function eagerlyManyToMany(array $where, array $subRelation = [], Closure $closure = null, array $cache = []): array + { + if ($closure) { + $closure($this->getClosureType($closure)); + } + + // 预载入关联查询 支持嵌套预载入 + $list = $this->belongsToManyQuery($this->foreignKey, $this->localKey, $where) + ->with($subRelation) + ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null) + ->select(); + + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $pivot = []; + foreach ($set->getData() as $key => $val) { + if (strpos($key, '__')) { + [$name, $attr] = explode('__', $key, 2); + if ('pivot' == $name) { + $pivot[$attr] = $val; + unset($set->$key); + } + } + } + $key = $pivot[$this->localKey]; + + if ($this->withLimit && isset($data[$key]) && count($data[$key]) >= $this->withLimit) { + continue; + } + + $set->setRelation($this->pivotDataName, $this->newPivot($pivot)); + + $data[$key][] = $set; + } + + return $data; + } + + /** + * BELONGS TO MANY 关联查询 + * @access protected + * @param string $foreignKey 关联模型关联键 + * @param string $localKey 当前模型关联键 + * @param array $condition 关联查询条件 + * @return Query + */ + protected function belongsToManyQuery(string $foreignKey, string $localKey, array $condition = []): Query + { + // 关联查询封装 + if (empty($this->baseQuery)) { + $tableName = $this->query->getTable(); + $table = $this->pivot->db()->getTable(); + + if ($this->withoutField) { + $this->query->withoutField($this->withoutField); + } + + $fields = $this->getQueryFields($tableName); + + if ($this->withLimit) { + $this->query->limit($this->withLimit); + } + + $this->query + ->field($fields) + ->tableField(true, $table, 'pivot', 'pivot__') + ->join([$table => 'pivot'], 'pivot.' . $foreignKey . '=' . $tableName . '.' . $this->query->getPk()) + ->where($condition); + + } + + return $this->query; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @param array $pivot 中间表额外数据 + * @return array|Pivot + */ + public function save($data, array $pivot = []) + { + // 保存关联表/中间表数据 + return $this->attach($data, $pivot); + } + + /** + * 批量保存当前关联数据对象 + * @access public + * @param iterable $dataSet 数据集 + * @param array $pivot 中间表额外数据 + * @param bool $samePivot 额外数据是否相同 + * @return array|false + */ + public function saveAll(iterable $dataSet, array $pivot = [], bool $samePivot = false) + { + $result = []; + + foreach ($dataSet as $key => $data) { + if (!$samePivot) { + $pivotData = $pivot[$key] ?? []; + } else { + $pivotData = $pivot; + } + + $result[] = $this->attach($data, $pivotData); + } + + return empty($result) ? false : $result; + } + + /** + * 附加关联的一个中间表数据 + * @access public + * @param mixed $data 数据 可以使用数组、关联模型对象 或者 关联对象的主键 + * @param array $pivot 中间表额外数据 + * @return array|Pivot + * @throws Exception + */ + public function attach($data, array $pivot = []) + { + if (is_array($data)) { + if (key($data) === 0) { + $id = $data; + } else { + // 保存关联表数据 + $model = new $this->model; + $id = $model->insertGetId($data); + } + } elseif (is_numeric($data) || is_string($data)) { + // 根据关联表主键直接写入中间表 + $id = $data; + } elseif ($data instanceof Model) { + // 根据关联表主键直接写入中间表 + $id = $data->getKey(); + } + + if (!empty($id)) { + // 保存中间表数据 + $pivot[$this->localKey] = $this->parent->getKey(); + + $ids = (array) $id; + foreach ($ids as $id) { + $pivot[$this->foreignKey] = $id; + $this->pivot->replace() + ->exists(false) + ->data([]) + ->save($pivot); + $result[] = $this->newPivot($pivot); + } + + if (count($result) == 1) { + // 返回中间表模型对象 + $result = $result[0]; + } + + return $result; + } else { + throw new Exception('miss relation data'); + } + } + + /** + * 判断是否存在关联数据 + * @access public + * @param mixed $data 数据 可以使用关联模型对象 或者 关联对象的主键 + * @return Pivot|false + */ + public function attached($data) + { + if ($data instanceof Model) { + $id = $data->getKey(); + } else { + $id = $data; + } + + $pivot = $this->pivot + ->where($this->localKey, $this->parent->getKey()) + ->where($this->foreignKey, $id) + ->find(); + + return $pivot ?: false; + } + + /** + * 解除关联的一个中间表数据 + * @access public + * @param integer|array $data 数据 可以使用关联对象的主键 + * @param bool $relationDel 是否同时删除关联表数据 + * @return integer + */ + public function detach($data = null, bool $relationDel = false): int + { + if (is_array($data)) { + $id = $data; + } elseif (is_numeric($data) || is_string($data)) { + // 根据关联表主键直接写入中间表 + $id = $data; + } elseif ($data instanceof Model) { + // 根据关联表主键直接写入中间表 + $id = $data->getKey(); + } + + // 删除中间表数据 + $pivot = []; + $pivot[] = [$this->localKey, '=', $this->parent->getKey()]; + + if (isset($id)) { + $pivot[] = [$this->foreignKey, is_array($id) ? 'in' : '=', $id]; + } + + $result = $this->pivot->where($pivot)->delete(); + + // 删除关联表数据 + if (isset($id) && $relationDel) { + $model = $this->model; + $model::destroy($id); + } + + return $result; + } + + /** + * 数据同步 + * @access public + * @param array $ids + * @param bool $detaching + * @return array + */ + public function sync(array $ids, bool $detaching = true): array + { + $changes = [ + 'attached' => [], + 'detached' => [], + 'updated' => [], + ]; + + $current = $this->pivot + ->where($this->localKey, $this->parent->getKey()) + ->column($this->foreignKey); + + $records = []; + + foreach ($ids as $key => $value) { + if (!is_array($value)) { + $records[$value] = []; + } else { + $records[$key] = $value; + } + } + + $detach = array_diff($current, array_keys($records)); + + if ($detaching && count($detach) > 0) { + $this->detach($detach); + $changes['detached'] = $detach; + } + + foreach ($records as $id => $attributes) { + if (!in_array($id, $current)) { + $this->attach($id, $attributes); + $changes['attached'][] = $id; + } elseif (count($attributes) > 0 && $this->attach($id, $attributes)) { + $changes['updated'][] = $id; + } + } + + return $changes; + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery(): void + { + if (empty($this->baseQuery)) { + $foreignKey = $this->foreignKey; + $localKey = $this->localKey; + + // 关联查询 + if (null === $this->parent->getKey()) { + $condition = ['pivot.' . $localKey, 'exp', new Raw('=' . $this->parent->getTable() . '.' . $this->parent->getPk())]; + } else { + $condition = ['pivot.' . $localKey, '=', $this->parent->getKey()]; + } + + $this->belongsToManyQuery($foreignKey, $localKey, [$condition]); + + $this->baseQuery = true; + } + } + +} diff --git a/vendor/topthink/think-orm/src/model/relation/HasMany.php b/vendor/topthink/think-orm/src/model/relation/HasMany.php new file mode 100644 index 0000000..77d9e4d --- /dev/null +++ b/vendor/topthink/think-orm/src/model/relation/HasMany.php @@ -0,0 +1,372 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model\relation; + +use Closure; +use think\Collection; +use think\db\BaseQuery as Query; +use think\helper\Str; +use think\Model; +use think\model\Relation; + +/** + * 一对多关联类 + */ +class HasMany extends Relation +{ + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前模型主键 + */ + public function __construct(Model $parent, string $model, string $foreignKey, string $localKey) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->query = (new $model)->db(); + + if (get_class($parent) == $model) { + $this->selfRelation = true; + } + } + + /** + * 延迟获取关联数据 + * @access public + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包查询条件 + * @return Collection + */ + public function getRelation(array $subRelation = [], Closure $closure = null): Collection + { + if ($closure) { + $closure($this->getClosureType($closure)); + } + + if ($this->withLimit) { + $this->query->limit($this->withLimit); + } + + return $this->query + ->where($this->foreignKey, $this->parent->{$this->localKey}) + ->relation($subRelation) + ->select() + ->setParent(clone $this->parent); + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void + { + $localKey = $this->localKey; + $range = []; + + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$localKey)) { + $range[] = $result->$localKey; + } + } + + if (!empty($range)) { + $data = $this->eagerlyOneToMany([ + [$this->foreignKey, 'in', $range], + ], $subRelation, $closure, $cache); + + // 关联数据封装 + foreach ($resultSet as $result) { + $pk = $result->$localKey; + if (!isset($data[$pk])) { + $data[$pk] = []; + } + + $result->setRelation($relation, $this->resultSetBuild($data[$pk], clone $this->parent)); + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $localKey = $this->localKey; + + if (isset($result->$localKey)) { + $pk = $result->$localKey; + $data = $this->eagerlyOneToMany([ + [$this->foreignKey, '=', $pk], + ], $subRelation, $closure, $cache); + + // 关联数据封装 + if (!isset($data[$pk])) { + $data[$pk] = []; + } + + $result->setRelation($relation, $this->resultSetBuild($data[$pk], clone $this->parent)); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return integer + */ + public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null) + { + $localKey = $this->localKey; + + if (!isset($result->$localKey)) { + return 0; + } + + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->query + ->where($this->foreignKey, '=', $result->$localKey) + ->$aggregate($field); + } + + /** + * 创建关联统计子查询 + * @access public + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return string + */ + public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string + { + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->query->alias($aggregate . '_table') + ->whereExp($aggregate . '_table.' . $this->foreignKey, '=' . $this->parent->getTable() . '.' . $this->localKey) + ->fetchSql() + ->$aggregate($field); + } + + /** + * 一对多 关联模型预查询 + * @access public + * @param array $where 关联预查询条件 + * @param array $subRelation 子关联 + * @param Closure $closure + * @param array $cache 关联缓存 + * @return array + */ + protected function eagerlyOneToMany(array $where, array $subRelation = [], Closure $closure = null, array $cache = []): array + { + $foreignKey = $this->foreignKey; + + $this->query->removeWhereField($this->foreignKey); + + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + $this->baseQuery = true; + $closure($this->getClosureType($closure)); + } + + if ($this->withoutField) { + $this->query->withoutField($this->withoutField); + } + + $list = $this->query + ->where($where) + ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null) + ->with($subRelation) + ->select(); + + // 组装模型数据 + $data = []; + + foreach ($list as $set) { + $key = $set->$foreignKey; + + if ($this->withLimit && isset($data[$key]) && count($data[$key]) >= $this->withLimit) { + continue; + } + + $data[$key][] = $set; + } + + return $data; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 + * @param boolean $replace 是否自动识别更新和写入 + * @return Model|false + */ + public function save($data, bool $replace = true) + { + $model = $this->make(); + + return $model->replace($replace)->save($data) ? $model : false; + } + + /** + * 创建关联对象实例 + * @param array|Model $data + * @return Model + */ + public function make($data = []): Model + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + // 保存关联表数据 + $data[$this->foreignKey] = $this->parent->{$this->localKey}; + + return new $this->model($data); + } + + /** + * 批量保存当前关联数据对象 + * @access public + * @param iterable $dataSet 数据集 + * @param boolean $replace 是否自动识别更新和写入 + * @return array|false + */ + public function saveAll(iterable $dataSet, bool $replace = true) + { + $result = []; + + foreach ($dataSet as $key => $data) { + $result[] = $this->save($data, $replace); + } + + return empty($result) ? false : $result; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = 'INNER', Query $query = null): Query + { + $table = $this->query->getTable(); + + $model = class_basename($this->parent); + $relation = class_basename($this->model); + + if ('*' != $id) { + $id = $relation . '.' . (new $this->model)->getPk(); + } + + $softDelete = $this->query->getOptions('soft_delete'); + $query = $query ?: $this->parent->db()->alias($model); + + return $query->field($model . '.*') + ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $joinType) + ->when($softDelete, function ($query) use ($softDelete, $relation) { + $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }) + ->group($relation . '.' . $this->foreignKey) + ->having('count(' . $id . ')' . $operator . $count); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function hasWhere($where = [], $fields = null, string $joinType = '', Query $query = null): Query + { + $table = $this->query->getTable(); + $model = class_basename($this->parent); + $relation = class_basename($this->model); + + if (is_array($where)) { + $this->getQueryWhere($where, $relation); + } elseif ($where instanceof Query) { + $where->via($relation); + } elseif ($where instanceof Closure) { + $where($this->query->via($relation)); + $where = $this->query; + } + + $fields = $this->getRelationQueryFields($fields, $model); + $softDelete = $this->query->getOptions('soft_delete'); + $query = $query ?: $this->parent->db(); + + return $query->alias($model) + ->group($model . '.' . $this->localKey) + ->field($fields) + ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $joinType) + ->when($softDelete, function ($query) use ($softDelete, $relation) { + $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }) + ->where($where); + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery(): void + { + if (empty($this->baseQuery)) { + if (isset($this->parent->{$this->localKey})) { + // 关联查询带入关联条件 + $this->query->where($this->foreignKey, '=', $this->parent->{$this->localKey}); + } + + $this->baseQuery = true; + } + } + +} diff --git a/vendor/topthink/think-orm/src/model/relation/HasManyThrough.php b/vendor/topthink/think-orm/src/model/relation/HasManyThrough.php new file mode 100644 index 0000000..d4b7d91 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/relation/HasManyThrough.php @@ -0,0 +1,388 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use Closure; +use think\Collection; +use think\db\BaseQuery as Query; +use think\helper\Str; +use think\Model; +use think\model\Relation; + +/** + * 远程一对多关联类 + */ +class HasManyThrough extends Relation +{ + /** + * 中间关联表外键 + * @var string + */ + protected $throughKey; + + /** + * 中间主键 + * @var string + */ + protected $throughPk; + + /** + * 中间表查询对象 + * @var Query + */ + protected $through; + + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 关联模型名 + * @param string $through 中间模型名 + * @param string $foreignKey 关联外键 + * @param string $throughKey 中间关联外键 + * @param string $localKey 当前模型主键 + * @param string $throughPk 中间模型主键 + */ + public function __construct(Model $parent, string $model, string $through, string $foreignKey, string $throughKey, string $localKey, string $throughPk) + { + $this->parent = $parent; + $this->model = $model; + $this->through = (new $through)->db(); + $this->foreignKey = $foreignKey; + $this->throughKey = $throughKey; + $this->localKey = $localKey; + $this->throughPk = $throughPk; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @access public + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包查询条件 + * @return Collection + */ + public function getRelation(array $subRelation = [], Closure $closure = null) + { + if ($closure) { + $closure($this->getClosureType($closure)); + } + + $this->baseQuery(); + + if ($this->withLimit) { + $this->query->limit($this->withLimit); + } + + return $this->query->relation($subRelation) + ->select() + ->setParent(clone $this->parent); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', Query $query = null): Query + { + $model = Str::snake(class_basename($this->parent)); + $throughTable = $this->through->getTable(); + $pk = $this->throughPk; + $throughKey = $this->throughKey; + $relation = new $this->model; + $relationTable = $relation->getTable(); + $softDelete = $this->query->getOptions('soft_delete'); + + if ('*' != $id) { + $id = $relationTable . '.' . $relation->getPk(); + } + $query = $query ?: $this->parent->db()->alias($model); + + return $query->field($model . '.*') + ->join($throughTable, $throughTable . '.' . $this->foreignKey . '=' . $model . '.' . $this->localKey) + ->join($relationTable, $relationTable . '.' . $throughKey . '=' . $throughTable . '.' . $this->throughPk) + ->when($softDelete, function ($query) use ($softDelete, $relationTable) { + $query->where($relationTable . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }) + ->group($relationTable . '.' . $this->throughKey) + ->having('count(' . $id . ')' . $operator . $count); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function hasWhere($where = [], $fields = null, $joinType = '', Query $query = null): Query + { + $model = Str::snake(class_basename($this->parent)); + $throughTable = $this->through->getTable(); + $pk = $this->throughPk; + $throughKey = $this->throughKey; + $modelTable = (new $this->model)->getTable(); + + if (is_array($where)) { + $this->getQueryWhere($where, $modelTable); + } elseif ($where instanceof Query) { + $where->via($modelTable); + } elseif ($where instanceof Closure) { + $where($this->query->via($modelTable)); + $where = $this->query; + } + + $fields = $this->getRelationQueryFields($fields, $model); + $softDelete = $this->query->getOptions('soft_delete'); + $query = $query ?: $this->parent->db(); + + return $query->alias($model) + ->join($throughTable, $throughTable . '.' . $this->foreignKey . '=' . $model . '.' . $this->localKey) + ->join($modelTable, $modelTable . '.' . $throughKey . '=' . $throughTable . '.' . $this->throughPk, $joinType) + ->when($softDelete, function ($query) use ($softDelete, $modelTable) { + $query->where($modelTable . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }) + ->group($modelTable . '.' . $this->throughKey) + ->where($where) + ->field($fields); + } + + /** + * 预载入关联查询(数据集) + * @access protected + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$localKey)) { + $range[] = $result->$localKey; + } + } + + if (!empty($range)) { + $this->query->removeWhereField($foreignKey); + + $data = $this->eagerlyWhere([ + [$this->foreignKey, 'in', $range], + ], $foreignKey, $subRelation, $closure, $cache); + + // 关联数据封装 + foreach ($resultSet as $result) { + $pk = $result->$localKey; + if (!isset($data[$pk])) { + $data[$pk] = []; + } + + // 设置关联属性 + $result->setRelation($relation, $this->resultSetBuild($data[$pk], clone $this->parent)); + } + } + } + + /** + * 预载入关联查询(数据) + * @access protected + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + $pk = $result->$localKey; + + $this->query->removeWhereField($foreignKey); + + $data = $this->eagerlyWhere([ + [$foreignKey, '=', $pk], + ], $foreignKey, $subRelation, $closure, $cache); + + // 关联数据封装 + if (!isset($data[$pk])) { + $data[$pk] = []; + } + + $result->setRelation($relation, $this->resultSetBuild($data[$pk], clone $this->parent)); + } + + /** + * 关联模型预查询 + * @access public + * @param array $where 关联预查询条件 + * @param string $key 关联键名 + * @param array $subRelation 子关联 + * @param Closure $closure + * @param array $cache 关联缓存 + * @return array + */ + protected function eagerlyWhere(array $where, string $key, array $subRelation = [], Closure $closure = null, array $cache = []): array + { + // 预载入关联查询 支持嵌套预载入 + $throughList = $this->through->where($where)->select(); + $keys = $throughList->column($this->throughPk, $this->throughPk); + + if ($closure) { + $this->baseQuery = true; + $closure($this->getClosureType($closure)); + } + + $list = $this->query + ->where($this->throughKey, 'in', $keys) + ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null) + ->select(); + + // 组装模型数据 + $data = []; + $keys = $throughList->column($this->foreignKey, $this->throughPk); + + foreach ($list as $set) { + $key = $keys[$set->{$this->throughKey}]; + + if ($this->withLimit && isset($data[$key]) && count($data[$key]) >= $this->withLimit) { + continue; + } + + $data[$key][] = $set; + } + + return $data; + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return mixed + */ + public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null) + { + $localKey = $this->localKey; + + if (!isset($result->$localKey)) { + return 0; + } + + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + $alias = Str::snake(class_basename($this->model)); + $throughTable = $this->through->getTable(); + $pk = $this->throughPk; + $throughKey = $this->throughKey; + $modelTable = $this->parent->getTable(); + + if (false === strpos($field, '.')) { + $field = $alias . '.' . $field; + } + + return $this->query + ->alias($alias) + ->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey) + ->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey) + ->where($throughTable . '.' . $this->foreignKey, $result->$localKey) + ->$aggregate($field); + } + + /** + * 创建关联统计子查询 + * @access public + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return string + */ + public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string + { + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + $alias = Str::snake(class_basename($this->model)); + $throughTable = $this->through->getTable(); + $pk = $this->throughPk; + $throughKey = $this->throughKey; + $modelTable = $this->parent->getTable(); + + if (false === strpos($field, '.')) { + $field = $alias . '.' . $field; + } + + return $this->query + ->alias($alias) + ->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey) + ->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey) + ->whereExp($throughTable . '.' . $this->foreignKey, '=' . $this->parent->getTable() . '.' . $this->localKey) + ->fetchSql() + ->$aggregate($field); + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery(): void + { + if (empty($this->baseQuery) && $this->parent->getData()) { + $alias = Str::snake(class_basename($this->model)); + $throughTable = $this->through->getTable(); + $pk = $this->throughPk; + $throughKey = $this->throughKey; + $modelTable = $this->parent->getTable(); + + if ($this->withoutField) { + $this->query->withoutField($this->withoutField); + } + + $fields = $this->getQueryFields($alias); + + $this->query + ->field($fields) + ->alias($alias) + ->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey) + ->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey) + ->where($throughTable . '.' . $this->foreignKey, $this->parent->{$this->localKey}); + + $this->baseQuery = true; + } + } + +} diff --git a/vendor/topthink/think-orm/src/model/relation/HasOne.php b/vendor/topthink/think-orm/src/model/relation/HasOne.php new file mode 100644 index 0000000..be4927b --- /dev/null +++ b/vendor/topthink/think-orm/src/model/relation/HasOne.php @@ -0,0 +1,303 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model\relation; + +use Closure; +use think\db\BaseQuery as Query; +use think\helper\Str; +use think\Model; + +/** + * HasOne 关联类 + */ +class HasOne extends OneToOne +{ + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前模型主键 + */ + public function __construct(Model $parent, string $model, string $foreignKey, string $localKey) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->query = (new $model)->db(); + + if (get_class($parent) == $model) { + $this->selfRelation = true; + } + } + + /** + * 延迟获取关联数据 + * @access public + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包查询条件 + * @return Model + */ + public function getRelation(array $subRelation = [], Closure $closure = null) + { + $localKey = $this->localKey; + + if ($closure) { + $closure($this->getClosureType($closure)); + } + + // 判断关联类型执行查询 + $relationModel = $this->query + ->removeWhereField($this->foreignKey) + ->where($this->foreignKey, $this->parent->$localKey) + ->relation($subRelation) + ->find(); + + if ($relationModel) { + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($this->parent, $relationModel); + } + + $relationModel->setParent(clone $this->parent); + } else { + $relationModel = $this->getDefaultModel(); + } + + return $relationModel; + } + + /** + * 创建关联统计子查询 + * @access public + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return string + */ + public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string + { + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->query + ->whereExp($this->foreignKey, '=' . $this->parent->getTable() . '.' . $this->localKey) + ->fetchSql() + ->$aggregate($field); + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return integer + */ + public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null) + { + $localKey = $this->localKey; + + if (!isset($result->$localKey)) { + return 0; + } + + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->query + ->where($this->foreignKey, '=', $result->$localKey) + ->$aggregate($field); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', Query $query = null): Query + { + $table = $this->query->getTable(); + $model = class_basename($this->parent); + $relation = class_basename($this->model); + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + $softDelete = $this->query->getOptions('soft_delete'); + $query = $query ?: $this->parent->db()->alias($model); + + return $query->whereExists(function ($query) use ($table, $model, $relation, $localKey, $foreignKey, $softDelete) { + $query->table([$table => $relation]) + ->field($relation . '.' . $foreignKey) + ->whereExp($model . '.' . $localKey, '=' . $relation . '.' . $foreignKey) + ->when($softDelete, function ($query) use ($softDelete, $relation) { + $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }); + }); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function hasWhere($where = [], $fields = null, string $joinType = '', Query $query = null): Query + { + $table = $this->query->getTable(); + $model = class_basename($this->parent); + $relation = class_basename($this->model); + + if (is_array($where)) { + $this->getQueryWhere($where, $relation); + } elseif ($where instanceof Query) { + $where->via($relation); + } elseif ($where instanceof Closure) { + $where($this->query->via($relation)); + $where = $this->query; + } + + $fields = $this->getRelationQueryFields($fields, $model); + $softDelete = $this->query->getOptions('soft_delete'); + $query = $query ?: $this->parent->db(); + + return $query->alias($model) + ->field($fields) + ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $joinType ?: $this->joinType) + ->when($softDelete, function ($query) use ($softDelete, $relation) { + $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }) + ->where($where); + } + + /** + * 预载入关联查询(数据集) + * @access protected + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + protected function eagerlySet(array &$resultSet, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$localKey)) { + $range[] = $result->$localKey; + } + } + + if (!empty($range)) { + $this->query->removeWhereField($foreignKey); + + $data = $this->eagerlyWhere([ + [$foreignKey, 'in', $range], + ], $foreignKey, $subRelation, $closure, $cache); + + // 关联数据封装 + foreach ($resultSet as $result) { + // 关联模型 + if (!isset($data[$result->$localKey])) { + $relationModel = $this->getDefaultModel(); + } else { + $relationModel = $data[$result->$localKey]; + $relationModel->setParent(clone $result); + $relationModel->exists(true); + } + + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($result, $relationModel); + } else { + // 设置关联属性 + $result->setRelation($relation, $relationModel); + } + } + } + } + + /** + * 预载入关联查询(数据) + * @access protected + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + protected function eagerlyOne(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $this->query->removeWhereField($foreignKey); + + $data = $this->eagerlyWhere([ + [$foreignKey, '=', $result->$localKey], + ], $foreignKey, $subRelation, $closure, $cache); + + // 关联模型 + if (!isset($data[$result->$localKey])) { + $relationModel = $this->getDefaultModel(); + } else { + $relationModel = $data[$result->$localKey]; + $relationModel->setParent(clone $result); + $relationModel->exists(true); + } + + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($result, $relationModel); + } else { + $result->setRelation($relation, $relationModel); + } + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery(): void + { + if (empty($this->baseQuery)) { + if (isset($this->parent->{$this->localKey})) { + // 关联查询带入关联条件 + $this->query->where($this->foreignKey, '=', $this->parent->{$this->localKey}); + } + + $this->baseQuery = true; + } + } +} diff --git a/vendor/topthink/think-orm/src/model/relation/HasOneThrough.php b/vendor/topthink/think-orm/src/model/relation/HasOneThrough.php new file mode 100644 index 0000000..0278533 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/relation/HasOneThrough.php @@ -0,0 +1,165 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use Closure; +use think\helper\Str; +use think\Model; + +/** + * 远程一对一关联类 + */ +class HasOneThrough extends HasManyThrough +{ + + /** + * 延迟获取关联数据 + * @access public + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包查询条件 + * @return Model + */ + public function getRelation(array $subRelation = [], Closure $closure = null) + { + if ($closure) { + $closure($this->getClosureType($closure)); + } + + $this->baseQuery(); + + $relationModel = $this->query->relation($subRelation)->find(); + + if ($relationModel) { + $relationModel->setParent(clone $this->parent); + } else { + $relationModel = $this->getDefaultModel(); + } + + return $relationModel; + } + + /** + * 预载入关联查询(数据集) + * @access protected + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$localKey)) { + $range[] = $result->$localKey; + } + } + + if (!empty($range)) { + $this->query->removeWhereField($foreignKey); + + $data = $this->eagerlyWhere([ + [$this->foreignKey, 'in', $range], + ], $foreignKey, $subRelation, $closure, $cache); + + // 关联数据封装 + foreach ($resultSet as $result) { + // 关联模型 + if (!isset($data[$result->$localKey])) { + $relationModel = $this->getDefaultModel(); + } else { + $relationModel = $data[$result->$localKey]; + $relationModel->setParent(clone $result); + $relationModel->exists(true); + } + + // 设置关联属性 + $result->setRelation($relation, $relationModel); + } + } + } + + /** + * 预载入关联查询(数据) + * @access protected + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $this->query->removeWhereField($foreignKey); + + $data = $this->eagerlyWhere([ + [$foreignKey, '=', $result->$localKey], + ], $foreignKey, $subRelation, $closure, $cache); + + // 关联模型 + if (!isset($data[$result->$localKey])) { + $relationModel = $this->getDefaultModel(); + } else { + $relationModel = $data[$result->$localKey]; + $relationModel->setParent(clone $result); + $relationModel->exists(true); + } + + $result->setRelation($relation, $relationModel); + } + + /** + * 关联模型预查询 + * @access public + * @param array $where 关联预查询条件 + * @param string $key 关联键名 + * @param array $subRelation 子关联 + * @param Closure $closure + * @param array $cache 关联缓存 + * @return array + */ + protected function eagerlyWhere(array $where, string $key, array $subRelation = [], Closure $closure = null, array $cache = []): array + { + // 预载入关联查询 支持嵌套预载入 + $keys = $this->through->where($where)->column($this->throughPk, $this->foreignKey); + + if ($closure) { + $closure($this->getClosureType($closure)); + } + + $list = $this->query + ->where($this->throughKey, 'in', $keys) + ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null) + ->select(); + + // 组装模型数据 + $data = []; + $keys = array_flip($keys); + + foreach ($list as $set) { + $data[$keys[$set->{$this->throughKey}]] = $set; + } + + return $data; + } + +} diff --git a/vendor/topthink/think-orm/src/model/relation/MorphMany.php b/vendor/topthink/think-orm/src/model/relation/MorphMany.php new file mode 100644 index 0000000..82910cb --- /dev/null +++ b/vendor/topthink/think-orm/src/model/relation/MorphMany.php @@ -0,0 +1,353 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use Closure; +use think\Collection; +use think\db\BaseQuery as Query; +use think\db\exception\DbException as Exception; +use think\helper\Str; +use think\Model; +use think\model\Relation; + +/** + * 多态一对多关联 + */ +class MorphMany extends Relation +{ + + /** + * 多态关联外键 + * @var string + */ + protected $morphKey; + /** + * 多态字段名 + * @var string + */ + protected $morphType; + + /** + * 多态类型 + * @var string + */ + protected $type; + + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $morphKey 关联外键 + * @param string $morphType 多态字段名 + * @param string $type 多态类型 + */ + public function __construct(Model $parent, string $model, string $morphKey, string $morphType, string $type) + { + $this->parent = $parent; + $this->model = $model; + $this->type = $type; + $this->morphKey = $morphKey; + $this->morphType = $morphType; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @access public + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包查询条件 + * @return Collection + */ + public function getRelation(array $subRelation = [], Closure $closure = null): Collection + { + if ($closure) { + $closure($this->getClosureType($closure)); + } + + $this->baseQuery(); + + if ($this->withLimit) { + $this->query->limit($this->withLimit); + } + + return $this->query->relation($subRelation) + ->select() + ->setParent(clone $this->parent); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', Query $query = null) + { + throw new Exception('relation not support: has'); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function hasWhere($where = [], $fields = null, string $joinType = '', Query $query = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void + { + $morphType = $this->morphType; + $morphKey = $this->morphKey; + $type = $this->type; + $range = []; + + foreach ($resultSet as $result) { + $pk = $result->getPk(); + // 获取关联外键列表 + if (isset($result->$pk)) { + $range[] = $result->$pk; + } + } + + if (!empty($range)) { + $where = [ + [$morphKey, 'in', $range], + [$morphType, '=', $type], + ]; + $data = $this->eagerlyMorphToMany($where, $subRelation, $closure, $cache); + + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$pk])) { + $data[$result->$pk] = []; + } + + $result->setRelation($relation, $this->resultSetBuild($data[$result->$pk], clone $this->parent)); + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $pk = $result->getPk(); + + if (isset($result->$pk)) { + $key = $result->$pk; + $data = $this->eagerlyMorphToMany([ + [$this->morphKey, '=', $key], + [$this->morphType, '=', $this->type], + ], $subRelation, $closure, $cache); + + if (!isset($data[$key])) { + $data[$key] = []; + } + + $result->setRelation($relation, $this->resultSetBuild($data[$key], clone $this->parent)); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return mixed + */ + public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null) + { + $pk = $result->getPk(); + + if (!isset($result->$pk)) { + return 0; + } + + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->query + ->where([ + [$this->morphKey, '=', $result->$pk], + [$this->morphType, '=', $this->type], + ]) + ->$aggregate($field); + } + + /** + * 获取关联统计子查询 + * @access public + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return string + */ + public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string + { + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->query + ->whereExp($this->morphKey, '=' . $this->parent->getTable() . '.' . $this->parent->getPk()) + ->where($this->morphType, '=', $this->type) + ->fetchSql() + ->$aggregate($field); + } + + /** + * 多态一对多 关联模型预查询 + * @access protected + * @param array $where 关联预查询条件 + * @param array $subRelation 子关联 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return array + */ + protected function eagerlyMorphToMany(array $where, array $subRelation = [], Closure $closure = null, array $cache = []): array + { + // 预载入关联查询 支持嵌套预载入 + $this->query->removeOption('where'); + + if ($closure) { + $this->baseQuery = true; + $closure($this->getClosureType($closure)); + } + + $list = $this->query + ->where($where) + ->with($subRelation) + ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null) + ->select(); + $morphKey = $this->morphKey; + + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $key = $set->$morphKey; + + if ($this->withLimit && isset($data[$key]) && count($data[$key]) >= $this->withLimit) { + continue; + } + + $data[$key][] = $set; + } + + return $data; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 + * @param bool $replace 是否自动识别更新和写入 + * @return Model|false + */ + public function save($data, bool $replace = true) + { + $model = $this->make(); + + return $model->replace($replace)->save($data) ? $model : false; + } + + /** + * 创建关联对象实例 + * @param array|Model $data + * @return Model + */ + public function make($data = []): Model + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + // 保存关联表数据 + $pk = $this->parent->getPk(); + + $data[$this->morphKey] = $this->parent->$pk; + $data[$this->morphType] = $this->type; + + return new $this->model($data); + } + + /** + * 批量保存当前关联数据对象 + * @access public + * @param iterable $dataSet 数据集 + * @param boolean $replace 是否自动识别更新和写入 + * @return array|false + */ + public function saveAll(iterable $dataSet, bool $replace = true) + { + $result = []; + + foreach ($dataSet as $key => $data) { + $result[] = $this->save($data, $replace); + } + + return empty($result) ? false : $result; + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery(): void + { + if (empty($this->baseQuery) && $this->parent->getData()) { + $pk = $this->parent->getPk(); + + $this->query->where([ + [$this->morphKey, '=', $this->parent->$pk], + [$this->morphType, '=', $this->type], + ]); + + $this->baseQuery = true; + } + } + +} diff --git a/vendor/topthink/think-orm/src/model/relation/MorphOne.php b/vendor/topthink/think-orm/src/model/relation/MorphOne.php new file mode 100644 index 0000000..788dd29 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/relation/MorphOne.php @@ -0,0 +1,349 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use Closure; +use think\db\BaseQuery as Query; +use think\db\exception\DbException as Exception; +use think\helper\Str; +use think\Model; +use think\model\Relation; + +/** + * 多态一对一关联类 + */ +class MorphOne extends Relation +{ + /** + * 多态关联外键 + * @var string + */ + protected $morphKey; + + /** + * 多态字段 + * @var string + */ + protected $morphType; + + /** + * 多态类型 + * @var string + */ + protected $type; + + /** + * 绑定的关联属性 + * @var array + */ + protected $bindAttr = []; + + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $morphKey 关联外键 + * @param string $morphType 多态字段名 + * @param string $type 多态类型 + */ + public function __construct(Model $parent, string $model, string $morphKey, string $morphType, string $type) + { + $this->parent = $parent; + $this->model = $model; + $this->type = $type; + $this->morphKey = $morphKey; + $this->morphType = $morphType; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @access public + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包查询条件 + * @return Model + */ + public function getRelation(array $subRelation = [], Closure $closure = null) + { + if ($closure) { + $closure($this->getClosureType($closure)); + } + + $this->baseQuery(); + + $relationModel = $this->query->relation($subRelation)->find(); + + if ($relationModel) { + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($this->parent, $relationModel); + } + + $relationModel->setParent(clone $this->parent); + } else { + $relationModel = $this->getDefaultModel(); + } + + return $relationModel; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', Query $query = null) + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function hasWhere($where = [], $fields = null, string $joinType = '', Query $query = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void + { + $morphType = $this->morphType; + $morphKey = $this->morphKey; + $type = $this->type; + $range = []; + + foreach ($resultSet as $result) { + $pk = $result->getPk(); + // 获取关联外键列表 + if (isset($result->$pk)) { + $range[] = $result->$pk; + } + } + + if (!empty($range)) { + $data = $this->eagerlyMorphToOne([ + [$morphKey, 'in', $range], + [$morphType, '=', $type], + ], $subRelation, $closure, $cache); + + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$pk])) { + $relationModel = $this->getDefaultModel(); + } else { + $relationModel = $data[$result->$pk]; + $relationModel->setParent(clone $result); + $relationModel->exists(true); + } + + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($result, $relationModel); + } else { + // 设置关联属性 + $result->setRelation($relation, $relationModel); + } + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $pk = $result->getPk(); + + if (isset($result->$pk)) { + $pk = $result->$pk; + $data = $this->eagerlyMorphToOne([ + [$this->morphKey, '=', $pk], + [$this->morphType, '=', $this->type], + ], $subRelation, $closure, $cache); + + if (isset($data[$pk])) { + $relationModel = $data[$pk]; + $relationModel->setParent(clone $result); + $relationModel->exists(true); + } else { + $relationModel = $this->getDefaultModel(); + } + + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($result, $relationModel); + } else { + // 设置关联属性 + $result->setRelation($relation, $relationModel); + } + } + } + + /** + * 多态一对一 关联模型预查询 + * @access protected + * @param array $where 关联预查询条件 + * @param array $subRelation 子关联 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return array + */ + protected function eagerlyMorphToOne(array $where, array $subRelation = [], $closure = null, array $cache = []): array + { + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + $this->baseQuery = true; + $closure($this->getClosureType($closure)); + } + + $list = $this->query + ->where($where) + ->with($subRelation) + ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null) + ->select(); + $morphKey = $this->morphKey; + + // 组装模型数据 + $data = []; + + foreach ($list as $set) { + $data[$set->$morphKey] = $set; + } + + return $data; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 + * @param boolean $replace 是否自动识别更新和写入 + * @return Model|false + */ + public function save($data, bool $replace = true) + { + $model = $this->make(); + return $model->replace($replace)->save($data) ? $model : false; + } + + /** + * 创建关联对象实例 + * @param array|Model $data + * @return Model + */ + public function make($data = []): Model + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + // 保存关联表数据 + $pk = $this->parent->getPk(); + + $data[$this->morphKey] = $this->parent->$pk; + $data[$this->morphType] = $this->type; + + return new $this->model($data); + } + + /** + * 执行基础查询(进执行一次) + * @access protected + * @return void + */ + protected function baseQuery(): void + { + if (empty($this->baseQuery) && $this->parent->getData()) { + $pk = $this->parent->getPk(); + + $this->query->where([ + [$this->morphKey, '=', $this->parent->$pk], + [$this->morphType, '=', $this->type], + ]); + $this->baseQuery = true; + } + } + + /** + * 绑定关联表的属性到父模型属性 + * @access public + * @param array $attr 要绑定的属性列表 + * @return $this + */ + public function bind(array $attr) + { + $this->bindAttr = $attr; + + return $this; + } + + /** + * 获取绑定属性 + * @access public + * @return array + */ + public function getBindAttr(): array + { + return $this->bindAttr; + } + + /** + * 绑定关联属性到父模型 + * @access protected + * @param Model $result 父模型对象 + * @param Model $model 关联模型对象 + * @return void + * @throws Exception + */ + protected function bindAttr(Model $result, Model $model = null): void + { + foreach ($this->bindAttr as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + $value = $result->getOrigin($key); + + if (!is_null($value)) { + throw new Exception('bind attr has exists:' . $key); + } + + $result->setAttr($key, $model ? $model->$attr : null); + } + } +} diff --git a/vendor/topthink/think-orm/src/model/relation/MorphTo.php b/vendor/topthink/think-orm/src/model/relation/MorphTo.php new file mode 100644 index 0000000..eaa9890 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/relation/MorphTo.php @@ -0,0 +1,332 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use Closure; +use think\db\exception\DbException as Exception; +use think\helper\Str; +use think\Model; +use think\model\Relation; + +/** + * 多态关联类 + */ +class MorphTo extends Relation +{ + /** + * 多态关联外键 + * @var string + */ + protected $morphKey; + + /** + * 多态字段 + * @var string + */ + protected $morphType; + + /** + * 多态别名 + * @var array + */ + protected $alias = []; + + /** + * 关联名 + * @var string + */ + protected $relation; + + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $morphType 多态字段名 + * @param string $morphKey 外键名 + * @param array $alias 多态别名定义 + * @param string $relation 关联名 + */ + public function __construct(Model $parent, string $morphType, string $morphKey, array $alias = [], string $relation = null) + { + $this->parent = $parent; + $this->morphType = $morphType; + $this->morphKey = $morphKey; + $this->alias = $alias; + $this->relation = $relation; + } + + /** + * 获取当前的关联模型类的实例 + * @access public + * @return Model + */ + public function getModel(): Model + { + $morphType = $this->morphType; + $model = $this->parseModel($this->parent->$morphType); + + return (new $model); + } + + /** + * 延迟获取关联数据 + * @access public + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包查询条件 + * @return Model + */ + public function getRelation(array $subRelation = [], Closure $closure = null) + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + + // 多态模型 + $model = $this->parseModel($this->parent->$morphType); + + // 主键数据 + $pk = $this->parent->$morphKey; + + $relationModel = (new $model)->relation($subRelation)->find($pk); + + if ($relationModel) { + $relationModel->setParent(clone $this->parent); + } + + return $relationModel; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', Query $query = null) + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function hasWhere($where = [], $fields = null, string $joinType = '', Query $query = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 解析模型的完整命名空间 + * @access protected + * @param string $model 模型名(或者完整类名) + * @return string + */ + protected function parseModel(string $model): string + { + if (isset($this->alias[$model])) { + $model = $this->alias[$model]; + } + + if (false === strpos($model, '\\')) { + $path = explode('\\', get_class($this->parent)); + array_pop($path); + array_push($path, Str::studly($model)); + $model = implode('\\', $path); + } + + return $model; + } + + /** + * 设置多态别名 + * @access public + * @param array $alias 别名定义 + * @return $this + */ + public function setAlias(array $alias) + { + $this->alias = $alias; + + return $this; + } + + /** + * 移除关联查询参数 + * @access public + * @return $this + */ + public function removeOption() + { + return $this; + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + * @throws Exception + */ + public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + $range = []; + + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (!empty($result->$morphKey)) { + $range[$result->$morphType][] = $result->$morphKey; + } + } + + if (!empty($range)) { + + foreach ($range as $key => $val) { + // 多态类型映射 + $model = $this->parseModel($key); + $obj = new $model; + $pk = $obj->getPk(); + $list = $obj->with($subRelation) + ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null) + ->select($val); + $data = []; + + foreach ($list as $k => $vo) { + $data[$vo->$pk] = $vo; + } + + foreach ($resultSet as $result) { + if ($key == $result->$morphType) { + // 关联模型 + if (!isset($data[$result->$morphKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$morphKey]; + $relationModel->setParent(clone $result); + $relationModel->exists(true); + } + + $result->setRelation($relation, $relationModel); + } + } + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + // 多态类型映射 + $model = $this->parseModel($result->{$this->morphType}); + + $this->eagerlyMorphToOne($model, $relation, $result, $subRelation, $cache); + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @return integer + */ + public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*') + {} + + /** + * 多态MorphTo 关联模型预查询 + * @access protected + * @param string $model 关联模型对象 + * @param string $relation 关联名 + * @param Model $result + * @param array $subRelation 子关联 + * @param array $cache 关联缓存 + * @return void + */ + protected function eagerlyMorphToOne(string $model, string $relation, Model $result, array $subRelation = [], array $cache = []): void + { + // 预载入关联查询 支持嵌套预载入 + $pk = $this->parent->{$this->morphKey}; + $data = (new $model)->with($subRelation) + ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null) + ->find($pk); + + if ($data) { + $data->setParent(clone $result); + $data->exists(true); + } + + $result->setRelation($relation, $data ?: null); + } + + /** + * 添加关联数据 + * @access public + * @param Model $model 关联模型对象 + * @param string $type 多态类型 + * @return Model + */ + public function associate(Model $model, string $type = ''): Model + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + $pk = $model->getPk(); + + $this->parent->setAttr($morphKey, $model->$pk); + $this->parent->setAttr($morphType, $type ?: get_class($model)); + $this->parent->save(); + + return $this->parent->setRelation($this->relation, $model); + } + + /** + * 注销关联数据 + * @access public + * @return Model + */ + public function dissociate(): Model + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + + $this->parent->setAttr($morphKey, null); + $this->parent->setAttr($morphType, null); + $this->parent->save(); + + return $this->parent->setRelation($this->relation, null); + } + +} diff --git a/vendor/topthink/think-orm/src/model/relation/MorphToMany.php b/vendor/topthink/think-orm/src/model/relation/MorphToMany.php new file mode 100644 index 0000000..32f853f --- /dev/null +++ b/vendor/topthink/think-orm/src/model/relation/MorphToMany.php @@ -0,0 +1,463 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use Closure; +use Exception; +use think\db\BaseQuery as Query; +use think\db\Raw; +use think\Model; +use think\model\Pivot; + +/** + * 多态多对多关联 + */ +class MorphToMany extends BelongsToMany +{ + + /** + * 多态字段名 + * @var string + */ + protected $morphType; + + /** + * 多态模型名 + * @var string + */ + protected $morphClass; + + /** + * 是否反向关联 + * @var bool + */ + protected $inverse; + + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $middle 中间表名/模型名 + * @param string $morphKey 关联外键 + * @param string $morphType 多态字段名 + * @param string $localKey 当前模型关联键 + * @param bool $inverse 反向关联 + */ + public function __construct(Model $parent, string $model, string $middle, string $morphType, string $morphKey, string $localKey, bool $inverse = false) + { + $this->morphType = $morphType; + $this->inverse = $inverse; + $this->morphClass = $inverse ? $model : get_class($parent); + + $foreignKey = $inverse ? $morphKey : $localKey; + $localKey = $inverse ? $localKey : $morphKey; + + parent::__construct($parent, $model, $middle, $foreignKey, $localKey); + } + + /** + * 预载入关联查询(数据集) + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void + { + $pk = $resultSet[0]->getPk(); + $range = []; + + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$pk)) { + $range[] = $result->$pk; + } + } + + if (!empty($range)) { + // 查询关联数据 + $data = $this->eagerlyManyToMany([ + ['pivot.' . $this->localKey, 'in', $range], + ['pivot.' . $this->morphType, '=', $this->morphClass], + ], $subRelation, $closure, $cache); + + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$pk])) { + $data[$result->$pk] = []; + } + + $result->setRelation($relation, $this->resultSetBuild($data[$result->$pk], clone $this->parent)); + } + } + } + + /** + * 预载入关联查询(单个数据) + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResult(Model $result, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void + { + $pk = $result->getPk(); + + if (isset($result->$pk)) { + $pk = $result->$pk; + // 查询管理数据 + $data = $this->eagerlyManyToMany([ + ['pivot.' . $this->localKey, '=', $pk], + ['pivot.' . $this->morphType, '=', $this->morphClass], + ], $subRelation, $closure, $cache); + + // 关联数据封装 + if (!isset($data[$pk])) { + $data[$pk] = []; + } + + $result->setRelation($relation, $this->resultSetBuild($data[$pk], clone $this->parent)); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return integer + */ + public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): float + { + $pk = $result->getPk(); + + if (!isset($result->$pk)) { + return 0; + } + + $pk = $result->$pk; + + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [ + ['pivot.' . $this->localKey, '=', $pk], + ['pivot.' . $this->morphType, '=', $this->morphClass], + ])->$aggregate($field); + } + + /** + * 获取关联统计子查询 + * @access public + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return string + */ + public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string + { + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [ + ['pivot.' . $this->localKey, 'exp', new Raw('=' . $this->parent->db(false)->getTable() . '.' . $this->parent->getPk())], + ['pivot.' . $this->morphType, '=', $this->morphClass], + ])->fetchSql()->$aggregate($field); + } + + /** + * BELONGS TO MANY 关联查询 + * @access protected + * @param string $foreignKey 关联模型关联键 + * @param string $localKey 当前模型关联键 + * @param array $condition 关联查询条件 + * @return Query + */ + protected function belongsToManyQuery(string $foreignKey, string $localKey, array $condition = []): Query + { + // 关联查询封装 + $tableName = $this->query->getTable(); + $table = $this->pivot->db()->getTable(); + + if ($this->withoutField) { + $this->query->withoutField($this->withoutField); + } + + $fields = $this->getQueryFields($tableName); + + if ($this->withLimit) { + $this->query->limit($this->withLimit); + } + + $query = $this->query + ->field($fields) + ->tableField(true, $table, 'pivot', 'pivot__'); + + if (empty($this->baseQuery)) { + $relationFk = $this->query->getPk(); + $query->join([$table => 'pivot'], 'pivot.' . $foreignKey . '=' . $tableName . '.' . $relationFk) + ->where($condition); + } + + return $query; + } + + /** + * 多对多 关联模型预查询 + * @access protected + * @param array $where 关联预查询条件 + * @param array $subRelation 子关联 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return array + */ + protected function eagerlyManyToMany(array $where, array $subRelation = [], Closure $closure = null, array $cache = []): array + { + if ($closure) { + $closure($this->getClosureType($closure)); + } + + // 预载入关联查询 支持嵌套预载入 + $list = $this->belongsToManyQuery($this->foreignKey, $this->localKey, $where) + ->with($subRelation) + ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null) + ->select(); + + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $pivot = []; + foreach ($set->getData() as $key => $val) { + if (strpos($key, '__')) { + [$name, $attr] = explode('__', $key, 2); + if ('pivot' == $name) { + $pivot[$attr] = $val; + unset($set->$key); + } + } + } + + $key = $pivot[$this->localKey]; + + if ($this->withLimit && isset($data[$key]) && count($data[$key]) >= $this->withLimit) { + continue; + } + + $set->setRelation($this->pivotDataName, $this->newPivot($pivot)); + + $data[$key][] = $set; + } + + return $data; + } + + /** + * 附加关联的一个中间表数据 + * @access public + * @param mixed $data 数据 可以使用数组、关联模型对象 或者 关联对象的主键 + * @param array $pivot 中间表额外数据 + * @return array|Pivot + */ + public function attach($data, array $pivot = []) + { + if (is_array($data)) { + if (key($data) === 0) { + $id = $data; + } else { + // 保存关联表数据 + $model = new $this->model; + $id = $model->insertGetId($data); + } + } else if (is_numeric($data) || is_string($data)) { + // 根据关联表主键直接写入中间表 + $id = $data; + } else if ($data instanceof Model) { + // 根据关联表主键直接写入中间表 + $id = $data->getKey(); + } + + if (!empty($id)) { + // 保存中间表数据 + $pivot[$this->localKey] = $this->parent->getKey(); + $pivot[$this->morphType] = $this->morphClass; + $ids = (array) $id; + + $result = []; + + foreach ($ids as $id) { + $pivot[$this->foreignKey] = $id; + + $this->pivot->replace() + ->exists(false) + ->data([]) + ->save($pivot); + $result[] = $this->newPivot($pivot); + } + + if (count($result) == 1) { + // 返回中间表模型对象 + $result = $result[0]; + } + + return $result; + } else { + throw new Exception('miss relation data'); + } + } + + /** + * 判断是否存在关联数据 + * @access public + * @param mixed $data 数据 可以使用关联模型对象 或者 关联对象的主键 + * @return Pivot|false + */ + public function attached($data) + { + if ($data instanceof Model) { + $id = $data->getKey(); + } else { + $id = $data; + } + + $pivot = $this->pivot + ->where($this->localKey, $this->parent->getKey()) + ->where($this->morphType, $this->morphClass) + ->where($this->foreignKey, $id) + ->find(); + + return $pivot ?: false; + } + + /** + * 解除关联的一个中间表数据 + * @access public + * @param integer|array $data 数据 可以使用关联对象的主键 + * @param bool $relationDel 是否同时删除关联表数据 + * @return integer + */ + public function detach($data = null, bool $relationDel = false): int + { + if (is_array($data)) { + $id = $data; + } else if (is_numeric($data) || is_string($data)) { + // 根据关联表主键直接写入中间表 + $id = $data; + } else if ($data instanceof Model) { + // 根据关联表主键直接写入中间表 + $id = $data->getKey(); + } + + // 删除中间表数据 + $pivot = [ + [$this->localKey, '=', $this->parent->getKey()], + [$this->morphType, '=', $this->morphClass], + ]; + + if (isset($id)) { + $pivot[] = [$this->foreignKey, is_array($id) ? 'in' : '=', $id]; + } + + $result = $this->pivot->where($pivot)->delete(); + + // 删除关联表数据 + if (isset($id) && $relationDel) { + $model = $this->model; + $model::destroy($id); + } + + return $result; + } + + /** + * 数据同步 + * @access public + * @param array $ids + * @param bool $detaching + * @return array + */ + public function sync(array $ids, bool $detaching = true): array + { + $changes = [ + 'attached' => [], + 'detached' => [], + 'updated' => [], + ]; + + $current = $this->pivot + ->where($this->localKey, $this->parent->getKey()) + ->where($this->morphType, $this->morphClass) + ->column($this->foreignKey); + + $records = []; + + foreach ($ids as $key => $value) { + if (!is_array($value)) { + $records[$value] = []; + } else { + $records[$key] = $value; + } + } + + $detach = array_diff($current, array_keys($records)); + + if ($detaching && count($detach) > 0) { + $this->detach($detach); + $changes['detached'] = $detach; + } + + foreach ($records as $id => $attributes) { + if (!in_array($id, $current)) { + $this->attach($id, $attributes); + $changes['attached'][] = $id; + } else if (count($attributes) > 0 && $this->attach($id, $attributes)) { + $changes['updated'][] = $id; + } + } + + return $changes; + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery(): void + { + if (empty($this->baseQuery)) { + $foreignKey = $this->foreignKey; + $localKey = $this->localKey; + + // 关联查询 + $this->belongsToManyQuery($foreignKey, $localKey, [ + ['pivot.' . $localKey, '=', $this->parent->getKey()], + ['pivot.' . $this->morphType, '=', $this->morphClass], + ]); + + $this->baseQuery = true; + } + } + +} diff --git a/vendor/topthink/think-orm/src/model/relation/OneToOne.php b/vendor/topthink/think-orm/src/model/relation/OneToOne.php new file mode 100644 index 0000000..d47d7f3 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/relation/OneToOne.php @@ -0,0 +1,330 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use Closure; +use think\db\BaseQuery as Query; +use think\db\exception\DbException as Exception; +use think\helper\Str; +use think\Model; +use think\model\Relation; + +/** + * 一对一关联基础类 + * @package think\model\relation + */ +abstract class OneToOne extends Relation +{ + /** + * JOIN类型 + * @var string + */ + protected $joinType = 'INNER'; + + /** + * 绑定的关联属性 + * @var array + */ + protected $bindAttr = []; + + /** + * 关联名 + * @var string + */ + protected $relation; + + /** + * 设置join类型 + * @access public + * @param string $type JOIN类型 + * @return $this + */ + public function joinType(string $type) + { + $this->joinType = $type; + return $this; + } + + /** + * 预载入关联查询(JOIN方式) + * @access public + * @param Query $query 查询对象 + * @param string $relation 关联名 + * @param mixed $field 关联字段 + * @param string $joinType JOIN方式 + * @param Closure $closure 闭包条件 + * @param bool $first + * @return void + */ + public function eagerly(Query $query, string $relation, $field = true, string $joinType = '', Closure $closure = null, bool $first = false): void + { + $name = Str::snake(class_basename($this->parent)); + + if ($first) { + $table = $query->getTable(); + $query->table([$table => $name]); + + if ($query->getOptions('field')) { + $masterField = $query->getOptions('field'); + $query->removeOption('field'); + } else { + $masterField = true; + } + + $query->tableField($masterField, $table, $name); + } + + // 预载入封装 + $joinTable = $this->query->getTable(); + $joinAlias = $relation; + $joinType = $joinType ?: $this->joinType; + + $query->via($joinAlias); + + if ($this instanceof BelongsTo) { + $joinOn = $name . '.' . $this->foreignKey . '=' . $joinAlias . '.' . $this->localKey; + } else { + $joinOn = $name . '.' . $this->localKey . '=' . $joinAlias . '.' . $this->foreignKey; + } + + if ($closure) { + // 执行闭包查询 + $closure($this->getClosureType($closure)); + + // 使用withField指定获取关联的字段 + if ($this->withField) { + $field = $this->withField; + } + } + + $query->join([$joinTable => $joinAlias], $joinOn, $joinType) + ->tableField($field, $joinTable, $joinAlias, $relation . '__'); + } + + /** + * 预载入关联查询(数据集) + * @access protected + * @param array $resultSet + * @param string $relation + * @param array $subRelation + * @param Closure $closure + * @return mixed + */ + abstract protected function eagerlySet(array &$resultSet, string $relation, array $subRelation = [], Closure $closure = null); + + /** + * 预载入关联查询(数据) + * @access protected + * @param Model $result + * @param string $relation + * @param array $subRelation + * @param Closure $closure + * @return mixed + */ + abstract protected function eagerlyOne(Model $result, string $relation, array $subRelation = [], Closure $closure = null); + + /** + * 预载入关联查询(数据集) + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @param bool $join 是否为JOIN方式 + * @return void + */ + public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation = [], Closure $closure = null, array $cache = [], bool $join = false): void + { + if ($join) { + // 模型JOIN关联组装 + foreach ($resultSet as $result) { + $this->match($this->model, $relation, $result); + } + } else { + // IN查询 + $this->eagerlySet($resultSet, $relation, $subRelation, $closure, $cache); + } + } + + /** + * 预载入关联查询(数据) + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @param bool $join 是否为JOIN方式 + * @return void + */ + public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = [], bool $join = false): void + { + if ($join) { + // 模型JOIN关联组装 + $this->match($this->model, $relation, $result); + } else { + // IN查询 + $this->eagerlyOne($result, $relation, $subRelation, $closure, $cache); + } + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 + * @param boolean $replace 是否自动识别更新和写入 + * @return Model|false + */ + public function save($data, bool $replace = true) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + $model = new $this->model; + // 保存关联表数据 + $data[$this->foreignKey] = $this->parent->{$this->localKey}; + + return $model->replace($replace)->save($data) ? $model : false; + } + + /** + * 绑定关联表的属性到父模型属性 + * @access public + * @param array $attr 要绑定的属性列表 + * @return $this + */ + public function bind(array $attr) + { + $this->bindAttr = $attr; + + return $this; + } + + /** + * 获取绑定属性 + * @access public + * @return array + */ + public function getBindAttr(): array + { + return $this->bindAttr; + } + + /** + * 一对一 关联模型预查询拼装 + * @access public + * @param string $model 模型名称 + * @param string $relation 关联名 + * @param Model $result 模型对象实例 + * @return void + */ + protected function match(string $model, string $relation, Model $result): void + { + // 重新组装模型数据 + foreach ($result->getData() as $key => $val) { + if (strpos($key, '__')) { + [$name, $attr] = explode('__', $key, 2); + if ($name == $relation) { + $list[$name][$attr] = $val; + unset($result->$key); + } + } + } + + if (isset($list[$relation])) { + $array = array_unique($list[$relation]); + + if (count($array) == 1 && null === current($array)) { + $relationModel = null; + } else { + $relationModel = new $model($list[$relation]); + $relationModel->setParent(clone $result); + $relationModel->exists(true); + } + + if (!empty($this->bindAttr)) { + $this->bindAttr($result, $relationModel); + } + } else { + $relationModel = null; + } + + $result->setRelation($relation, $relationModel); + } + + /** + * 绑定关联属性到父模型 + * @access protected + * @param Model $result 父模型对象 + * @param Model $model 关联模型对象 + * @return void + * @throws Exception + */ + protected function bindAttr(Model $result, Model $model = null): void + { + foreach ($this->bindAttr as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + $value = $result->getOrigin($key); + + if (!is_null($value)) { + throw new Exception('bind attr has exists:' . $key); + } + + $result->setAttr($key, $model ? $model->$attr : null); + } + } + + /** + * 一对一 关联模型预查询(IN方式) + * @access public + * @param array $where 关联预查询条件 + * @param string $key 关联键名 + * @param array $subRelation 子关联 + * @param Closure $closure + * @param array $cache 关联缓存 + * @return array + */ + protected function eagerlyWhere(array $where, string $key, array $subRelation = [], Closure $closure = null, array $cache = []) + { + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + $this->baseQuery = true; + $closure($this->getClosureType($closure)); + } + + if ($this->withField) { + $this->query->field($this->withField); + } elseif ($this->withoutField) { + $this->query->withoutField($this->withoutField); + } + + $list = $this->query + ->where($where) + ->with($subRelation) + ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null) + ->select(); + + // 组装模型数据 + $data = []; + + foreach ($list as $set) { + if (!isset($data[$set->$key])) { + $data[$set->$key] = $set; + } + } + + return $data; + } + +} diff --git a/vendor/topthink/think-orm/src/paginator/driver/Bootstrap.php b/vendor/topthink/think-orm/src/paginator/driver/Bootstrap.php new file mode 100644 index 0000000..6d55c39 --- /dev/null +++ b/vendor/topthink/think-orm/src/paginator/driver/Bootstrap.php @@ -0,0 +1,209 @@ + +// +---------------------------------------------------------------------- + +namespace think\paginator\driver; + +use think\Paginator; + +/** + * Bootstrap 分页驱动 + */ +class Bootstrap extends Paginator +{ + + /** + * 上一页按钮 + * @param string $text + * @return string + */ + protected function getPreviousButton(string $text = "«"): string + { + + if ($this->currentPage() <= 1) { + return $this->getDisabledTextWrapper($text); + } + + $url = $this->url( + $this->currentPage() - 1 + ); + + return $this->getPageLinkWrapper($url, $text); + } + + /** + * 下一页按钮 + * @param string $text + * @return string + */ + protected function getNextButton(string $text = '»'): string + { + if (!$this->hasMore) { + return $this->getDisabledTextWrapper($text); + } + + $url = $this->url($this->currentPage() + 1); + + return $this->getPageLinkWrapper($url, $text); + } + + /** + * 页码按钮 + * @return string + */ + protected function getLinks(): string + { + if ($this->simple) { + return ''; + } + + $block = [ + 'first' => null, + 'slider' => null, + 'last' => null, + ]; + + $side = 3; + $window = $side * 2; + + if ($this->lastPage < $window + 6) { + $block['first'] = $this->getUrlRange(1, $this->lastPage); + } elseif ($this->currentPage <= $window) { + $block['first'] = $this->getUrlRange(1, $window + 2); + $block['last'] = $this->getUrlRange($this->lastPage - 1, $this->lastPage); + } elseif ($this->currentPage > ($this->lastPage - $window)) { + $block['first'] = $this->getUrlRange(1, 2); + $block['last'] = $this->getUrlRange($this->lastPage - ($window + 2), $this->lastPage); + } else { + $block['first'] = $this->getUrlRange(1, 2); + $block['slider'] = $this->getUrlRange($this->currentPage - $side, $this->currentPage + $side); + $block['last'] = $this->getUrlRange($this->lastPage - 1, $this->lastPage); + } + + $html = ''; + + if (is_array($block['first'])) { + $html .= $this->getUrlLinks($block['first']); + } + + if (is_array($block['slider'])) { + $html .= $this->getDots(); + $html .= $this->getUrlLinks($block['slider']); + } + + if (is_array($block['last'])) { + $html .= $this->getDots(); + $html .= $this->getUrlLinks($block['last']); + } + + return $html; + } + + /** + * 渲染分页html + * @return mixed + */ + public function render() + { + if ($this->hasPages()) { + if ($this->simple) { + return sprintf( + '
          %s %s
        ', + $this->getPreviousButton(), + $this->getNextButton() + ); + } else { + return sprintf( + '
          %s %s %s
        ', + $this->getPreviousButton(), + $this->getLinks(), + $this->getNextButton() + ); + } + } + } + + /** + * 生成一个可点击的按钮 + * + * @param string $url + * @param string $page + * @return string + */ + protected function getAvailablePageWrapper(string $url, string $page): string + { + return '
      • ' . $page . '
      • '; + } + + /** + * 生成一个禁用的按钮 + * + * @param string $text + * @return string + */ + protected function getDisabledTextWrapper(string $text): string + { + return '
      • ' . $text . '
      • '; + } + + /** + * 生成一个激活的按钮 + * + * @param string $text + * @return string + */ + protected function getActivePageWrapper(string $text): string + { + return '
      • ' . $text . '
      • '; + } + + /** + * 生成省略号按钮 + * + * @return string + */ + protected function getDots(): string + { + return $this->getDisabledTextWrapper('...'); + } + + /** + * 批量生成页码按钮. + * + * @param array $urls + * @return string + */ + protected function getUrlLinks(array $urls): string + { + $html = ''; + + foreach ($urls as $page => $url) { + $html .= $this->getPageLinkWrapper($url, $page); + } + + return $html; + } + + /** + * 生成普通页码按钮 + * + * @param string $url + * @param string $page + * @return string + */ + protected function getPageLinkWrapper(string $url, string $page): string + { + if ($this->currentPage() == $page) { + return $this->getActivePageWrapper($page); + } + + return $this->getAvailablePageWrapper($url, $page); + } +} diff --git a/vendor/topthink/think-orm/stubs/Exception.php b/vendor/topthink/think-orm/stubs/Exception.php new file mode 100644 index 0000000..0fdba9c --- /dev/null +++ b/vendor/topthink/think-orm/stubs/Exception.php @@ -0,0 +1,59 @@ + +// +---------------------------------------------------------------------- +declare (strict_types=1); + +namespace think; + +/** + * 异常基础类 + * @package think + */ +class Exception extends \Exception +{ + /** + * 保存异常页面显示的额外Debug数据 + * @var array + */ + protected $data = []; + + /** + * 设置异常额外的Debug数据 + * 数据将会显示为下面的格式 + * + * Exception Data + * -------------------------------------------------- + * Label 1 + * key1 value1 + * key2 value2 + * Label 2 + * key1 value1 + * key2 value2 + * + * @access protected + * @param string $label 数据分类,用于异常页面显示 + * @param array $data 需要显示的数据,必须为关联数组 + */ + final protected function setData(string $label, array $data) + { + $this->data[$label] = $data; + } + + /** + * 获取异常额外Debug数据 + * 主要用于输出到异常页面便于调试 + * @access public + * @return array 由setData设置的Debug数据 + */ + final public function getData() + { + return $this->data; + } +} diff --git a/vendor/topthink/think-orm/stubs/Facade.php b/vendor/topthink/think-orm/stubs/Facade.php new file mode 100644 index 0000000..d801d8b --- /dev/null +++ b/vendor/topthink/think-orm/stubs/Facade.php @@ -0,0 +1,65 @@ + +// +---------------------------------------------------------------------- +declare(strict_types=1); + +namespace think; + +class Facade +{ + /** + * 始终创建新的对象实例 + * @var bool + */ + protected static $alwaysNewInstance; + + protected static $instance; + + /** + * 获取当前Facade对应类名 + * @access protected + * @return string + */ + protected static function getFacadeClass() + {} + + /** + * 创建Facade实例 + * @static + * @access protected + * @param bool $newInstance 是否每次创建新的实例 + * @return object + */ + protected static function createFacade(bool $newInstance = false) + { + $class = static::getFacadeClass() ?: 'think\DbManager'; + + if (static::$alwaysNewInstance) { + $newInstance = true; + } + + if ($newInstance) { + return new $class(); + } + + if (!self::$instance) { + self::$instance = new $class(); + } + + return self::$instance; + + } + + // 调用实际类的方法 + public static function __callStatic($method, $params) + { + return call_user_func_array([static::createFacade(), $method], $params); + } +} diff --git a/vendor/topthink/think-orm/stubs/load_stubs.php b/vendor/topthink/think-orm/stubs/load_stubs.php new file mode 100644 index 0000000..5854cda --- /dev/null +++ b/vendor/topthink/think-orm/stubs/load_stubs.php @@ -0,0 +1,9 @@ + composer require topthink/think-queue + +## 配置 + +> 配置文件位于 `config/queue.php` + +### 公共配置 + +``` +[ + 'default'=>'sync' //驱动类型,可选择 sync(默认):同步执行,database:数据库驱动,redis:Redis驱动//或其他自定义的完整的类名 +] +``` + + +## 创建任务类 +> 单模块项目推荐使用 `app\job` 作为任务类的命名空间 +> 多模块项目可用使用 `app\module\job` 作为任务类的命名空间 +> 也可以放在任意可以自动加载到的地方 + +任务类不需继承任何类,如果这个类只有一个任务,那么就只需要提供一个`fire`方法就可以了,如果有多个小任务,就写多个方法,下面发布任务的时候会有区别 +每个方法会传入两个参数 `think\queue\Job $job`(当前的任务对象) 和 `$data`(发布任务时自定义的数据) + +还有个可选的任务失败执行的方法 `failed` 传入的参数为`$data`(发布任务时自定义的数据) + +### 下面写两个例子 + +``` +namespace app\job; + +use think\queue\Job; + +class Job1{ + + public function fire(Job $job, $data){ + + //....这里执行具体的任务 + + if ($job->attempts() > 3) { + //通过这个方法可以检查这个任务已经重试了几次了 + } + + + //如果任务执行成功后 记得删除任务,不然这个任务会重复执行,直到达到最大重试次数后失败后,执行failed方法 + $job->delete(); + + // 也可以重新发布这个任务 + $job->release($delay); //$delay为延迟时间 + + } + + public function failed($data){ + + // ...任务达到最大重试次数后,失败了 + } + +} + +``` + +``` + +namespace app\lib\job; + +use think\queue\Job; + +class Job2{ + + public function task1(Job $job, $data){ + + + } + + public function task2(Job $job, $data){ + + + } + + public function failed($data){ + + + } + +} + +``` + + +## 发布任务 +> `think\facade\Queue::push($job, $data = '', $queue = null)` 和 `think\facade\Queue::later($delay, $job, $data = '', $queue = null)` 两个方法,前者是立即执行,后者是在`$delay`秒后执行 + +`$job` 是任务名 +单模块的,且命名空间是`app\job`的,比如上面的例子一,写`Job1`类名即可 +多模块的,且命名空间是`app\module\job`的,写`model/Job1`即可 +其他的需要些完整的类名,比如上面的例子二,需要写完整的类名`app\lib\job\Job2` +如果一个任务类里有多个小任务的话,如上面的例子二,需要用@+方法名`app\lib\job\Job2@task1`、`app\lib\job\Job2@task2` + +`$data` 是你要传到任务里的参数 + +`$queue` 队列名,指定这个任务是在哪个队列上执行,同下面监控队列的时候指定的队列名,可不填 + +## 监听任务并执行 + +> php think queue:listen + +> php think queue:work + +两种,具体的可选参数可以输入命令加 --help 查看 + +>可配合supervisor使用,保证进程常驻 diff --git a/vendor/topthink/think-queue/composer.json b/vendor/topthink/think-queue/composer.json new file mode 100644 index 0000000..0bd34ba --- /dev/null +++ b/vendor/topthink/think-queue/composer.json @@ -0,0 +1,46 @@ +{ + "name": "topthink/think-queue", + "description": "The ThinkPHP6 Queue Package", + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "license": "Apache-2.0", + "autoload": { + "psr-4": { + "think\\": "src" + }, + "files": [ + "src/common.php" + ] + }, + "autoload-dev": { + "psr-4": { + "think\\test\\queue\\": "tests" + } + }, + "minimum-stability": "dev", + "require": { + "ext-json": "*", + "topthink/framework": "^6.0", + "symfony/process": "^4.2", + "nesbot/carbon": "^2.16" + }, + "extra": { + "think": { + "services": [ + "think\\queue\\Service" + ], + "config": { + "queue": "src/config.php" + } + } + }, + "require-dev": { + "phpunit/phpunit": "^6.2", + "mockery/mockery": "^1.2", + "topthink/think-migration": "^3.0.0" + } +} diff --git a/vendor/topthink/think-queue/phpunit.xml.dist b/vendor/topthink/think-queue/phpunit.xml.dist new file mode 100644 index 0000000..0234fbc --- /dev/null +++ b/vendor/topthink/think-queue/phpunit.xml.dist @@ -0,0 +1,30 @@ + + + + + ./tests + + + + + ./src + + ./src/queue/Service.php + ./src/common.php + ./src/config.php + + + + diff --git a/vendor/topthink/think-queue/src/Queue.php b/vendor/topthink/think-queue/src/Queue.php new file mode 100644 index 0000000..b107ad6 --- /dev/null +++ b/vendor/topthink/think-queue/src/Queue.php @@ -0,0 +1,65 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\queue\Connector; +use think\queue\connector\Database; +use think\queue\connector\Redis; + +/** + * Class Queue + * @package think\queue + * + * @mixin Database + * @mixin Redis + */ +class Queue extends Manager +{ + protected $namespace = '\\think\\queue\\connector\\'; + + protected function resolveType(string $name) + { + return $this->app->config->get("queue.connections.{$name}.type", 'sync'); + } + + protected function resolveConfig(string $name) + { + return $this->app->config->get("queue.connections.{$name}"); + } + + protected function createDriver(string $name) + { + /** @var Connector $driver */ + $driver = parent::createDriver($name); + + return $driver->setApp($this->app) + ->setConnection($name); + } + + /** + * @param null|string $name + * @return Connector + */ + public function connection($name = null) + { + return $this->driver($name); + } + + /** + * 默认驱动 + * @return string + */ + public function getDefaultDriver() + { + return $this->app->config->get('queue.default'); + } +} diff --git a/vendor/topthink/think-queue/src/common.php b/vendor/topthink/think-queue/src/common.php new file mode 100644 index 0000000..207b0b8 --- /dev/null +++ b/vendor/topthink/think-queue/src/common.php @@ -0,0 +1,31 @@ + +// +---------------------------------------------------------------------- + +use think\facade\Queue; + +if (!function_exists('queue')) { + + /** + * 添加到队列 + * @param $job + * @param string $data + * @param int $delay + * @param null $queue + */ + function queue($job, $data = '', $delay = 0, $queue = null) + { + if ($delay > 0) { + Queue::later($delay, $job, $data, $queue); + } else { + Queue::push($job, $data, $queue); + } + } +} diff --git a/vendor/topthink/think-queue/src/config.php b/vendor/topthink/think-queue/src/config.php new file mode 100644 index 0000000..fb096a8 --- /dev/null +++ b/vendor/topthink/think-queue/src/config.php @@ -0,0 +1,39 @@ + +// +---------------------------------------------------------------------- + +return [ + 'default' => 'sync', + 'connections' => [ + 'sync' => [ + 'type' => 'sync', + ], + 'database' => [ + 'type' => 'database', + 'queue' => 'default', + 'table' => 'jobs', + 'connection' => null, + ], + 'redis' => [ + 'type' => 'redis', + 'queue' => 'default', + 'host' => '127.0.0.1', + 'port' => 6379, + 'password' => '', + 'select' => 0, + 'timeout' => 0, + 'persistent' => false, + ], + ], + 'failed' => [ + 'type' => 'none', + 'table' => 'failed_jobs', + ], +]; diff --git a/vendor/topthink/think-queue/src/facade/Queue.php b/vendor/topthink/think-queue/src/facade/Queue.php new file mode 100644 index 0000000..23c2c30 --- /dev/null +++ b/vendor/topthink/think-queue/src/facade/Queue.php @@ -0,0 +1,18 @@ + +// +---------------------------------------------------------------------- + +namespace think\queue; + +use think\App; + +class CallQueuedHandler +{ + protected $app; + + public function __construct(App $app) + { + $this->app = $app; + } + + public function call(Job $job, array $data) + { + $command = unserialize($data['command']); + + $this->app->invoke([$command, 'handle']); + + if (!$job->isDeletedOrReleased()) { + $job->delete(); + } + } + + public function failed(array $data) + { + $command = unserialize($data['command']); + + if (method_exists($command, 'failed')) { + $command->failed(); + } + } +} diff --git a/vendor/topthink/think-queue/src/queue/Connector.php b/vendor/topthink/think-queue/src/queue/Connector.php new file mode 100644 index 0000000..cf5f826 --- /dev/null +++ b/vendor/topthink/think-queue/src/queue/Connector.php @@ -0,0 +1,156 @@ + +// +---------------------------------------------------------------------- + +namespace think\queue; + +use DateTimeInterface; +use InvalidArgumentException; +use think\App; + +abstract class Connector +{ + /** @var App */ + protected $app; + + /** + * The connector name for the queue. + * + * @var string + */ + protected $connection; + + protected $options = []; + + abstract public function size($queue = null); + + abstract public function push($job, $data = '', $queue = null); + + public function pushOn($queue, $job, $data = '') + { + return $this->push($job, $data, $queue); + } + + abstract public function pushRaw($payload, $queue = null, array $options = []); + + abstract public function later($delay, $job, $data = '', $queue = null); + + public function laterOn($queue, $delay, $job, $data = '') + { + return $this->later($delay, $job, $data, $queue); + } + + public function bulk($jobs, $data = '', $queue = null) + { + foreach ((array) $jobs as $job) { + $this->push($job, $data, $queue); + } + } + + abstract public function pop($queue = null); + + protected function createPayload($job, $data = '') + { + $payload = $this->createPayloadArray($job, $data); + + $payload = json_encode($payload); + + if (JSON_ERROR_NONE !== json_last_error()) { + throw new InvalidArgumentException('Unable to create payload: ' . json_last_error_msg()); + } + + return $payload; + } + + protected function createPayloadArray($job, $data = '') + { + return is_object($job) + ? $this->createObjectPayload($job) + : $this->createPlainPayload($job, $data); + } + + protected function createPlainPayload($job, $data) + { + return [ + 'job' => $job, + 'maxTries' => null, + 'timeout' => null, + 'data' => $data, + ]; + } + + protected function createObjectPayload($job) + { + return [ + 'job' => 'think\queue\CallQueuedHandler@call', + 'maxTries' => $job->tries ?? null, + 'timeout' => $job->timeout ?? null, + 'timeoutAt' => $this->getJobExpiration($job), + 'data' => [ + 'commandName' => get_class($job), + 'command' => serialize(clone $job), + ], + ]; + } + + public function getJobExpiration($job) + { + if (!method_exists($job, 'retryUntil') && !isset($job->timeoutAt)) { + return; + } + + $expiration = $job->timeoutAt ?? $job->retryUntil(); + + return $expiration instanceof DateTimeInterface + ? $expiration->getTimestamp() : $expiration; + } + + protected function setMeta($payload, $key, $value) + { + $payload = json_decode($payload, true); + $payload[$key] = $value; + $payload = json_encode($payload); + + if (JSON_ERROR_NONE !== json_last_error()) { + throw new InvalidArgumentException('Unable to create payload: ' . json_last_error_msg()); + } + + return $payload; + } + + public function setApp(App $app) + { + $this->app = $app; + return $this; + } + + /** + * Get the connector name for the queue. + * + * @return string + */ + public function getConnection() + { + return $this->connection; + } + + /** + * Set the connector name for the queue. + * + * @param string $name + * @return $this + */ + public function setConnection($name) + { + $this->connection = $name; + + return $this; + } +} diff --git a/vendor/topthink/think-queue/src/queue/FailedJob.php b/vendor/topthink/think-queue/src/queue/FailedJob.php new file mode 100644 index 0000000..e03f816 --- /dev/null +++ b/vendor/topthink/think-queue/src/queue/FailedJob.php @@ -0,0 +1,47 @@ +parseDateInterval($delay); + + return $delay instanceof DateTimeInterface + ? max(0, $delay->getTimestamp() - $this->currentTime()) + : (int) $delay; + } + + /** + * Get the "available at" UNIX timestamp. + * + * @param DateTimeInterface|DateInterval|int $delay + * @return int + */ + protected function availableAt($delay = 0) + { + $delay = $this->parseDateInterval($delay); + + return $delay instanceof DateTimeInterface + ? $delay->getTimestamp() + : Carbon::now()->addRealSeconds($delay)->getTimestamp(); + } + + /** + * If the given value is an interval, convert it to a DateTime instance. + * + * @param DateTimeInterface|DateInterval|int $delay + * @return DateTimeInterface|int + */ + protected function parseDateInterval($delay) + { + if ($delay instanceof DateInterval) { + $delay = Carbon::now()->add($delay); + } + + return $delay; + } + + /** + * Get the current system time as a UNIX timestamp. + * + * @return int + */ + protected function currentTime() + { + return Carbon::now()->getTimestamp(); + } +} diff --git a/vendor/topthink/think-queue/src/queue/Job.php b/vendor/topthink/think-queue/src/queue/Job.php new file mode 100644 index 0000000..27357f3 --- /dev/null +++ b/vendor/topthink/think-queue/src/queue/Job.php @@ -0,0 +1,282 @@ + +// +---------------------------------------------------------------------- + +namespace think\queue; + +use Exception; +use think\App; + +abstract class Job +{ + + /** + * The job handler instance. + * @var mixed + */ + protected $instance; + + /** + * @var App + */ + protected $app; + + /** + * The name of the queue the job belongs to. + * @var string + */ + protected $queue; + + /** + * The name of the connection the job belongs to. + */ + protected $connection; + + /** + * Indicates if the job has been deleted. + * @var bool + */ + protected $deleted = false; + + /** + * Indicates if the job has been released. + * @var bool + */ + protected $released = false; + + /** + * Indicates if the job has failed. + * + * @var bool + */ + protected $failed = false; + + /** + * Get the decoded body of the job. + * + * @return array + */ + public function payload() + { + return json_decode($this->getRawBody(), true); + } + + /** + * Fire the job. + * @return void + */ + public function fire() + { + $payload = $this->payload(); + + list($class, $method) = $this->parseJob($payload['job']); + + $this->instance = $this->resolve($class); + if ($this->instance) { + $this->instance->{$method}($this, $payload['data']); + } + } + + /** + * Delete the job from the queue. + * @return void + */ + public function delete() + { + $this->deleted = true; + } + + /** + * Determine if the job has been deleted. + * @return bool + */ + public function isDeleted() + { + return $this->deleted; + } + + /** + * Release the job back into the queue. + * @param int $delay + * @return void + */ + public function release($delay = 0) + { + $this->released = true; + } + + /** + * Determine if the job was released back into the queue. + * @return bool + */ + public function isReleased() + { + return $this->released; + } + + /** + * Determine if the job has been deleted or released. + * @return bool + */ + public function isDeletedOrReleased() + { + return $this->isDeleted() || $this->isReleased(); + } + + /** + * Get the job identifier. + * + * @return string + */ + abstract public function getJobId(); + + /** + * Get the number of times the job has been attempted. + * @return int + */ + abstract public function attempts(); + + /** + * Get the raw body string for the job. + * @return string + */ + abstract public function getRawBody(); + + /** + * Parse the job declaration into class and method. + * @param string $job + * @return array + */ + protected function parseJob($job) + { + $segments = explode('@', $job); + + return count($segments) > 1 ? $segments : [$segments[0], 'fire']; + } + + /** + * Resolve the given job handler. + * @param string $name + * @return mixed + */ + protected function resolve($name) + { + if (strpos($name, '\\') === false) { + + if (strpos($name, '/') === false) { + $app = ''; + } else { + list($app, $name) = explode('/', $name, 2); + } + + $name = ($this->app->config->get('app.app_namespace') ?: 'app\\') . ($app ? strtolower($app) . '\\' : '') . 'job\\' . $name; + } + + return $this->app->make($name); + } + + /** + * Determine if the job has been marked as a failure. + * + * @return bool + */ + public function hasFailed() + { + return $this->failed; + } + + /** + * Mark the job as "failed". + * + * @return void + */ + public function markAsFailed() + { + $this->failed = true; + } + + /** + * Process an exception that caused the job to fail. + * + * @param Exception $e + * @return void + */ + public function failed($e) + { + $this->markAsFailed(); + + $payload = $this->payload(); + + list($class, $method) = $this->parseJob($payload['job']); + + if (method_exists($this->instance = $this->resolve($class), 'failed')) { + $this->instance->failed($payload['data'], $e); + } + } + + /** + * Get the number of times to attempt a job. + * + * @return int|null + */ + public function maxTries() + { + return $this->payload()['maxTries'] ?? null; + } + + /** + * Get the number of seconds the job can run. + * + * @return int|null + */ + public function timeout() + { + return $this->payload()['timeout'] ?? null; + } + + /** + * Get the timestamp indicating when the job should timeout. + * + * @return int|null + */ + public function timeoutAt() + { + return $this->payload()['timeoutAt'] ?? null; + } + + /** + * Get the name of the queued job class. + * + * @return string + */ + public function getName() + { + return $this->payload()['job']; + } + + /** + * Get the name of the connection the job belongs to. + * + * @return string + */ + public function getConnection() + { + return $this->connection; + } + + /** + * Get the name of the queue the job belongs to. + * @return string + */ + public function getQueue() + { + return $this->queue; + } +} diff --git a/vendor/topthink/think-queue/src/queue/Listener.php b/vendor/topthink/think-queue/src/queue/Listener.php new file mode 100644 index 0000000..632510d --- /dev/null +++ b/vendor/topthink/think-queue/src/queue/Listener.php @@ -0,0 +1,162 @@ + +// +---------------------------------------------------------------------- + +namespace think\queue; + +use Closure; +use Symfony\Component\Process\PhpExecutableFinder; +use Symfony\Component\Process\Process; +use think\App; + +class Listener +{ + + /** + * @var string + */ + protected $commandPath; + + /** + * @var string + */ + protected $workerCommand; + + /** + * @var \Closure|null + */ + protected $outputHandler; + + /** + * @param string $commandPath + */ + public function __construct($commandPath) + { + $this->commandPath = $commandPath; + } + + public static function __make(App $app) + { + return new self($app->getRootPath()); + } + + /** + * Get the PHP binary. + * + * @return string + */ + protected function phpBinary() + { + return (new PhpExecutableFinder)->find(false); + } + + /** + * @param string $connection + * @param string $queue + * @param int $delay + * @param int $sleep + * @param int $maxTries + * @param int $memory + * @param int $timeout + * @return void + */ + public function listen($connection, $queue, $delay = 0, $sleep = 3, $maxTries = 0, $memory = 128, $timeout = 60) + { + $process = $this->makeProcess($connection, $queue, $delay, $sleep, $maxTries, $memory, $timeout); + + while (true) { + $this->runProcess($process, $memory); + } + } + + /** + * @param string $connection + * @param string $queue + * @param int $delay + * @param int $sleep + * @param int $maxTries + * @param int $memory + * @param int $timeout + * @return Process + */ + public function makeProcess($connection, $queue, $delay, $sleep, $maxTries, $memory, $timeout) + { + $command = array_filter([ + $this->phpBinary(), + 'think', + 'queue:work', + $connection, + '--once', + "--queue={$queue}", + "--delay={$delay}", + "--memory={$memory}", + "--sleep={$sleep}", + "--tries={$maxTries}", + ], function ($value) { + return !is_null($value); + }); + + return new Process($command, $this->commandPath, null, null, $timeout); + } + + /** + * @param Process $process + * @param int $memory + */ + public function runProcess(Process $process, $memory) + { + $process->run(function ($type, $line) { + $this->handleWorkerOutput($type, $line); + }); + + if ($this->memoryExceeded($memory)) { + $this->stop(); + } + } + + /** + * @param int $type + * @param string $line + * @return void + */ + protected function handleWorkerOutput($type, $line) + { + if (isset($this->outputHandler)) { + call_user_func($this->outputHandler, $type, $line); + } + } + + /** + * @param int $memoryLimit + * @return bool + */ + public function memoryExceeded($memoryLimit) + { + return (memory_get_usage() / 1024 / 1024) >= $memoryLimit; + } + + /** + * @return void + */ + public function stop() + { + die; + } + + /** + * @param \Closure $outputHandler + * @return void + */ + public function setOutputHandler(Closure $outputHandler) + { + $this->outputHandler = $outputHandler; + } + +} diff --git a/vendor/topthink/think-queue/src/queue/Queueable.php b/vendor/topthink/think-queue/src/queue/Queueable.php new file mode 100644 index 0000000..8dc8166 --- /dev/null +++ b/vendor/topthink/think-queue/src/queue/Queueable.php @@ -0,0 +1,61 @@ + +// +---------------------------------------------------------------------- + +namespace think\queue; + +trait Queueable +{ + + /** @var string 连接 */ + public $connection; + + /** @var string 队列名称 */ + public $queue; + + /** @var integer 延迟时间 */ + public $delay; + + /** + * 设置连接名 + * @param $connection + * @return $this + */ + public function onConnection($connection) + { + $this->connection = $connection; + + return $this; + } + + /** + * 设置队列名 + * @param $queue + * @return $this + */ + public function onQueue($queue) + { + $this->queue = $queue; + + return $this; + } + + /** + * 设置延迟时间 + * @param $delay + * @return $this + */ + public function delay($delay) + { + $this->delay = $delay; + + return $this; + } +} diff --git a/vendor/topthink/think-queue/src/queue/Service.php b/vendor/topthink/think-queue/src/queue/Service.php new file mode 100644 index 0000000..a6b3f4c --- /dev/null +++ b/vendor/topthink/think-queue/src/queue/Service.php @@ -0,0 +1,50 @@ +app->bind('queue', Queue::class); + $this->app->bind('queue.failer', function () { + + $config = $this->app->config->get('queue.failed', []); + + $type = Arr::pull($config, 'type', 'none'); + + $class = false !== strpos($type, '\\') ? $type : '\\think\\queue\\failed\\' . Str::studly($type); + + return $this->app->invokeClass($class, [$config]); + }); + } + + public function boot() + { + $this->commands([ + FailedJob::class, + Table::class, + FlushFailed::class, + ForgetFailed::class, + ListFailed::class, + Retry::class, + Work::class, + Restart::class, + Listen::class, + FailedTable::class, + ]); + } +} diff --git a/vendor/topthink/think-queue/src/queue/ShouldQueue.php b/vendor/topthink/think-queue/src/queue/ShouldQueue.php new file mode 100644 index 0000000..cb49c12 --- /dev/null +++ b/vendor/topthink/think-queue/src/queue/ShouldQueue.php @@ -0,0 +1,17 @@ + +// +---------------------------------------------------------------------- + +namespace think\queue; + +interface ShouldQueue +{ + +} diff --git a/vendor/topthink/think-queue/src/queue/Worker.php b/vendor/topthink/think-queue/src/queue/Worker.php new file mode 100644 index 0000000..458623c --- /dev/null +++ b/vendor/topthink/think-queue/src/queue/Worker.php @@ -0,0 +1,419 @@ + +// +---------------------------------------------------------------------- + +namespace think\queue; + +use Carbon\Carbon; +use Exception; +use RuntimeException; +use think\Cache; +use think\Event; +use think\exception\Handle; +use think\Queue; +use think\queue\event\JobExceptionOccurred; +use think\queue\event\JobFailed; +use think\queue\event\JobProcessed; +use think\queue\event\JobProcessing; +use think\queue\event\WorkerStopping; +use think\queue\exception\MaxAttemptsExceededException; +use Throwable; + +class Worker +{ + /** @var Event */ + protected $event; + /** @var Handle */ + protected $handle; + /** @var Queue */ + protected $queue; + + /** @var Cache */ + protected $cache; + + /** + * Indicates if the worker should exit. + * + * @var bool + */ + public $shouldQuit = false; + + /** + * Indicates if the worker is paused. + * + * @var bool + */ + public $paused = false; + + public function __construct(Queue $queue, Event $event, Handle $handle, Cache $cache = null) + { + $this->queue = $queue; + $this->event = $event; + $this->handle = $handle; + $this->cache = $cache; + } + + /** + * @param string $connection + * @param string $queue + * @param int $delay + * @param int $sleep + * @param int $maxTries + * @param int $memory + * @param int $timeout + */ + public function daemon($connection, $queue, $delay = 0, $sleep = 3, $maxTries = 0, $memory = 128, $timeout = 60) + { + if ($this->supportsAsyncSignals()) { + $this->listenForSignals(); + } + + $lastRestart = $this->getTimestampOfLastQueueRestart(); + + while (true) { + + $job = $this->getNextJob( + $this->queue->connection($connection), + $queue + ); + + if ($this->supportsAsyncSignals()) { + $this->registerTimeoutHandler($job, $timeout); + } + + if ($job) { + $this->runJob($job, $connection, $maxTries, $delay); + } else { + $this->sleep($sleep); + } + + $this->stopIfNecessary($job, $lastRestart, $memory); + } + } + + protected function stopIfNecessary($job, $lastRestart, $memory) + { + if ($this->shouldQuit || $this->queueShouldRestart($lastRestart)) { + $this->stop(); + } elseif ($this->memoryExceeded($memory)) { + $this->stop(12); + } + } + + /** + * Determine if the queue worker should restart. + * + * @param int|null $lastRestart + * @return bool + */ + protected function queueShouldRestart($lastRestart) + { + return $this->getTimestampOfLastQueueRestart() != $lastRestart; + } + + /** + * Determine if the memory limit has been exceeded. + * + * @param int $memoryLimit + * @return bool + */ + public function memoryExceeded($memoryLimit) + { + return (memory_get_usage(true) / 1024 / 1024) >= $memoryLimit; + } + + /** + * 获取队列重启时间 + * @return mixed + */ + protected function getTimestampOfLastQueueRestart() + { + if ($this->cache) { + return $this->cache->get('think:queue:restart'); + } + } + + /** + * Register the worker timeout handler. + * + * @param Job|null $job + * @param int $timeout + * @return void + */ + protected function registerTimeoutHandler($job, $timeout) + { + pcntl_signal(SIGALRM, function () { + $this->kill(1); + }); + + pcntl_alarm( + max($this->timeoutForJob($job, $timeout), 0) + ); + } + + /** + * Stop listening and bail out of the script. + * + * @param int $status + * @return void + */ + public function stop($status = 0) + { + $this->event->trigger(new WorkerStopping($status)); + + exit($status); + } + + /** + * Kill the process. + * + * @param int $status + * @return void + */ + public function kill($status = 0) + { + $this->event->trigger(new WorkerStopping($status)); + + if (extension_loaded('posix')) { + posix_kill(getmypid(), SIGKILL); + } + + exit($status); + } + + /** + * Get the appropriate timeout for the given job. + * + * @param Job|null $job + * @param int $timeout + * @return int + */ + protected function timeoutForJob($job, $timeout) + { + return $job && !is_null($job->timeout()) ? $job->timeout() : $timeout; + } + + /** + * Determine if "async" signals are supported. + * + * @return bool + */ + protected function supportsAsyncSignals() + { + return extension_loaded('pcntl'); + } + + /** + * Enable async signals for the process. + * + * @return void + */ + protected function listenForSignals() + { + pcntl_async_signals(true); + + pcntl_signal(SIGTERM, function () { + $this->shouldQuit = true; + }); + + pcntl_signal(SIGUSR2, function () { + $this->paused = true; + }); + + pcntl_signal(SIGCONT, function () { + $this->paused = false; + }); + } + + /** + * 执行下个任务 + * @param string $connection + * @param string $queue + * @param int $delay + * @param int $sleep + * @param int $maxTries + * @return void + * @throws Exception + */ + public function runNextJob($connection, $queue, $delay = 0, $sleep = 3, $maxTries = 0) + { + + $job = $this->getNextJob($this->queue->connection($connection), $queue); + + if ($job) { + $this->runJob($job, $connection, $maxTries, $delay); + } else { + $this->sleep($sleep); + } + } + + /** + * 执行任务 + * @param Job $job + * @param string $connection + * @param int $maxTries + * @param int $delay + * @return void + */ + protected function runJob($job, $connection, $maxTries, $delay) + { + try { + $this->process($connection, $job, $maxTries, $delay); + } catch (Exception | Throwable $e) { + $this->handle->report($e); + } + } + + /** + * 获取下个任务 + * @param Connector $connector + * @param string $queue + * @return Job + */ + protected function getNextJob($connector, $queue) + { + try { + foreach (explode(',', $queue) as $queue) { + if (!is_null($job = $connector->pop($queue))) { + return $job; + } + } + } catch (Exception | Throwable $e) { + $this->handle->report($e); + $this->sleep(1); + } + } + + /** + * Process a given job from the queue. + * @param string $connection + * @param Job $job + * @param int $maxTries + * @param int $delay + * @return void + * @throws Exception + */ + public function process($connection, $job, $maxTries = 0, $delay = 0) + { + try { + $this->event->trigger(new JobProcessing($connection, $job)); + + $this->markJobAsFailedIfAlreadyExceedsMaxAttempts( + $connection, + $job, + (int) $maxTries + ); + + $job->fire(); + + $this->event->trigger(new JobProcessed($connection, $job)); + } catch (Exception | Throwable $e) { + try { + if (!$job->hasFailed()) { + $this->markJobAsFailedIfWillExceedMaxAttempts($connection, $job, (int) $maxTries, $e); + } + + $this->event->trigger(new JobExceptionOccurred($connection, $job, $e)); + } finally { + if (!$job->isDeleted() && !$job->isReleased() && !$job->hasFailed()) { + $job->release($delay); + } + } + + throw $e; + } + } + + /** + * @param string $connection + * @param Job $job + * @param int $maxTries + */ + protected function markJobAsFailedIfAlreadyExceedsMaxAttempts($connection, $job, $maxTries) + { + $maxTries = !is_null($job->maxTries()) ? $job->maxTries() : $maxTries; + + $timeoutAt = $job->timeoutAt(); + + if ($timeoutAt && Carbon::now()->getTimestamp() <= $timeoutAt) { + return; + } + + if (!$timeoutAt && (0 === $maxTries || $job->attempts() <= $maxTries)) { + return; + } + + $this->failJob($connection, $job, $e = new MaxAttemptsExceededException( + $job->getName() . ' has been attempted too many times or run too long. The job may have previously timed out.' + )); + + throw $e; + } + + /** + * @param string $connection + * @param Job $job + * @param int $maxTries + * @param Exception $e + */ + protected function markJobAsFailedIfWillExceedMaxAttempts($connection, $job, $maxTries, $e) + { + $maxTries = !is_null($job->maxTries()) ? $job->maxTries() : $maxTries; + + if ($job->timeoutAt() && $job->timeoutAt() <= Carbon::now()->getTimestamp()) { + $this->failJob($connection, $job, $e); + } + + if ($maxTries > 0 && $job->attempts() >= $maxTries) { + $this->failJob($connection, $job, $e); + } + } + + /** + * @param string $connection + * @param Job $job + * @param Exception $e + */ + protected function failJob($connection, $job, $e) + { + $job->markAsFailed(); + + if ($job->isDeleted()) { + return; + } + + try { + $job->delete(); + + $job->failed($e); + } finally { + $this->event->trigger(new JobFailed( + $connection, + $job, + $e ?: new RuntimeException('ManuallyFailed') + )); + } + } + + /** + * Sleep the script for a given number of seconds. + * @param int $seconds + * @return void + */ + public function sleep($seconds) + { + if ($seconds < 1) { + usleep($seconds * 1000000); + } else { + sleep($seconds); + } + } + +} diff --git a/vendor/topthink/think-queue/src/queue/command/FailedTable.php b/vendor/topthink/think-queue/src/queue/command/FailedTable.php new file mode 100644 index 0000000..620af93 --- /dev/null +++ b/vendor/topthink/think-queue/src/queue/command/FailedTable.php @@ -0,0 +1,46 @@ +setName('queue:failed-table') + ->setDescription('Create a migration for the failed queue jobs database table'); + } + + public function handle() + { + if (!$this->app->has('migration.creator')) { + $this->output->error('Install think-migration first please'); + return; + } + + $table = $this->app->config->get('queue.failed.table'); + + $className = Str::studly("create_{$table}_table"); + + /** @var Creator $creator */ + $creator = $this->app->get('migration.creator'); + + $path = $creator->create($className); + + // Load the alternative template if it is defined. + $contents = file_get_contents(__DIR__ . '/stubs/failed_jobs.stub'); + + // inject the class names appropriate to this migration + $contents = strtr($contents, [ + 'CreateFailedJobsTable' => $className, + '{{table}}' => $table, + ]); + + file_put_contents($path, $contents); + + $this->output->info('Migration created successfully!'); + } +} diff --git a/vendor/topthink/think-queue/src/queue/command/FlushFailed.php b/vendor/topthink/think-queue/src/queue/command/FlushFailed.php new file mode 100644 index 0000000..69ea8b4 --- /dev/null +++ b/vendor/topthink/think-queue/src/queue/command/FlushFailed.php @@ -0,0 +1,21 @@ +setName('queue:flush') + ->setDescription('Flush all of the failed queue jobs'); + } + + public function handle() + { + $this->app->get('queue.failer')->flush(); + + $this->output->info('All failed jobs deleted successfully!'); + } +} diff --git a/vendor/topthink/think-queue/src/queue/command/ForgetFailed.php b/vendor/topthink/think-queue/src/queue/command/ForgetFailed.php new file mode 100644 index 0000000..37d175d --- /dev/null +++ b/vendor/topthink/think-queue/src/queue/command/ForgetFailed.php @@ -0,0 +1,25 @@ +setName('queue:forget') + ->addArgument('id', Argument::REQUIRED, 'The ID of the failed job') + ->setDescription('Delete a failed queue job'); + } + + public function handle() + { + if ($this->app['queue.failer']->forget($this->input->getArgument('id'))) { + $this->output->info('Failed job deleted successfully!'); + } else { + $this->output->error('No failed job matches the given ID.'); + } + } +} diff --git a/vendor/topthink/think-queue/src/queue/command/ListFailed.php b/vendor/topthink/think-queue/src/queue/command/ListFailed.php new file mode 100644 index 0000000..66eb90a --- /dev/null +++ b/vendor/topthink/think-queue/src/queue/command/ListFailed.php @@ -0,0 +1,110 @@ +setName('queue:failed') + ->setDescription('List all of the failed queue jobs'); + } + + public function handle() + { + if (count($jobs = $this->getFailedJobs()) === 0) { + $this->output->info('No failed jobs!'); + return; + } + $this->displayFailedJobs($jobs); + } + + /** + * Display the failed jobs in the console. + * + * @param array $jobs + * @return void + */ + protected function displayFailedJobs(array $jobs) + { + $table = new Table(); + $table->setHeader($this->headers); + $table->setRows($jobs); + + $this->table($table); + } + + /** + * Compile the failed jobs into a displayable format. + * + * @return array + */ + protected function getFailedJobs() + { + $failed = $this->app['queue.failer']->all(); + + return collect($failed)->map(function ($failed) { + return $this->parseFailedJob((array) $failed); + })->filter()->all(); + } + + /** + * Parse the failed job row. + * + * @param array $failed + * @return array + */ + protected function parseFailedJob(array $failed) + { + $row = array_values(Arr::except($failed, ['payload', 'exception'])); + + array_splice($row, 3, 0, $this->extractJobName($failed['payload'])); + + return $row; + } + + /** + * Extract the failed job name from payload. + * + * @param string $payload + * @return string|null + */ + private function extractJobName($payload) + { + $payload = json_decode($payload, true); + + if ($payload && (!isset($payload['data']['command']))) { + return $payload['job'] ?? null; + } elseif ($payload && isset($payload['data']['command'])) { + return $this->matchJobName($payload); + } + } + + /** + * Match the job name from the payload. + * + * @param array $payload + * @return string + */ + protected function matchJobName($payload) + { + preg_match('/"([^"]+)"/', $payload['data']['command'], $matches); + + if (isset($matches[1])) { + return $matches[1]; + } + + return $payload['job'] ?? null; + } +} diff --git a/vendor/topthink/think-queue/src/queue/command/Listen.php b/vendor/topthink/think-queue/src/queue/command/Listen.php new file mode 100644 index 0000000..61e47c1 --- /dev/null +++ b/vendor/topthink/think-queue/src/queue/command/Listen.php @@ -0,0 +1,61 @@ + +// +---------------------------------------------------------------------- + +namespace think\queue\command; + +use think\console\Command; +use think\console\Input; +use think\console\input\Argument; +use think\console\input\Option; +use think\console\Output; +use think\queue\Listener; + +class Listen extends Command +{ + /** @var Listener */ + protected $listener; + + public function __construct(Listener $listener) + { + parent::__construct(); + $this->listener = $listener; + $this->listener->setOutputHandler(function ($type, $line) { + $this->output->write($line); + }); + } + + protected function configure() + { + $this->setName('queue:listen') + ->addArgument('connection', Argument::OPTIONAL, 'The name of the queue connection to work', null) + ->addOption('queue', null, Option::VALUE_OPTIONAL, 'The queue to listen on', null) + ->addOption('delay', null, Option::VALUE_OPTIONAL, 'Amount of time to delay failed jobs', 0) + ->addOption('memory', null, Option::VALUE_OPTIONAL, 'The memory limit in megabytes', 128) + ->addOption('timeout', null, Option::VALUE_OPTIONAL, 'Seconds a job may run before timing out', 60) + ->addOption('sleep', null, Option::VALUE_OPTIONAL, 'Seconds to wait before checking queue for jobs', 3) + ->addOption('tries', null, Option::VALUE_OPTIONAL, 'Number of times to attempt a job before logging it failed', 0) + ->setDescription('Listen to a given queue'); + } + + public function execute(Input $input, Output $output) + { + $connection = $input->getArgument('connection') ?: $this->app->config->get('queue.default'); + + $queue = $input->getOption('queue') ?: $this->app->config->get("queue.connections.{$connection}.queue", 'default'); + $delay = $input->getOption('delay'); + $memory = $input->getOption('memory'); + $timeout = $input->getOption('timeout'); + $sleep = $input->getOption('sleep'); + $tries = $input->getOption('tries'); + + $this->listener->listen($connection, $queue, $delay, $sleep, $tries, $memory, $timeout); + } +} diff --git a/vendor/topthink/think-queue/src/queue/command/Restart.php b/vendor/topthink/think-queue/src/queue/command/Restart.php new file mode 100644 index 0000000..f0d3b4d --- /dev/null +++ b/vendor/topthink/think-queue/src/queue/command/Restart.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- + +namespace think\queue\command; + +use think\Cache; +use think\console\Command; +use think\queue\InteractsWithTime; + +class Restart extends Command +{ + use InteractsWithTime; + + protected function configure() + { + $this->setName('queue:restart') + ->setDescription('Restart queue worker daemons after their current job'); + } + + public function handle(Cache $cache) + { + $cache->set('think:queue:restart', $this->currentTime()); + $this->output->info("Broadcasting queue restart signal."); + } +} diff --git a/vendor/topthink/think-queue/src/queue/command/Retry.php b/vendor/topthink/think-queue/src/queue/command/Retry.php new file mode 100644 index 0000000..dba94e2 --- /dev/null +++ b/vendor/topthink/think-queue/src/queue/command/Retry.php @@ -0,0 +1,84 @@ +setName('queue:retry') + ->addArgument('id', Argument::IS_ARRAY | Argument::REQUIRED, 'The ID of the failed job or "all" to retry all jobs') + ->setDescription('Retry a failed queue job'); + } + + public function handle() + { + foreach ($this->getJobIds() as $id) { + $job = $this->app['queue.failer']->find($id); + + if (is_null($job)) { + $this->output->error("Unable to find failed job with ID [{$id}]."); + } else { + $this->retryJob($job); + + $this->output->info("The failed job [{$id}] has been pushed back onto the queue!"); + + $this->app['queue.failer']->forget($id); + } + } + } + + /** + * Retry the queue job. + * + * @param stdClass $job + * @return void + */ + protected function retryJob($job) + { + $this->app['queue']->connection($job['connection'])->pushRaw( + $this->resetAttempts($job['payload']), + $job['queue'] + ); + } + + /** + * Reset the payload attempts. + * + * Applicable to Redis jobs which store attempts in their payload. + * + * @param string $payload + * @return string + */ + protected function resetAttempts($payload) + { + $payload = json_decode($payload, true); + + if (isset($payload['attempts'])) { + $payload['attempts'] = 0; + } + + return json_encode($payload); + } + + /** + * Get the job IDs to be retried. + * + * @return array + */ + protected function getJobIds() + { + $ids = (array) $this->input->getArgument('id'); + + if (count($ids) === 1 && $ids[0] === 'all') { + $ids = Arr::pluck($this->app['queue.failer']->all(), 'id'); + } + + return $ids; + } +} diff --git a/vendor/topthink/think-queue/src/queue/command/Table.php b/vendor/topthink/think-queue/src/queue/command/Table.php new file mode 100644 index 0000000..9354dc7 --- /dev/null +++ b/vendor/topthink/think-queue/src/queue/command/Table.php @@ -0,0 +1,46 @@ +setName('queue:table') + ->setDescription('Create a migration for the queue jobs database table'); + } + + public function handle() + { + if (!$this->app->has('migration.creator')) { + $this->output->error('Install think-migration first please'); + return; + } + + $table = $this->app->config->get('queue.connections.database.table'); + + $className = Str::studly("create_{$table}_table"); + + /** @var Creator $creator */ + $creator = $this->app->get('migration.creator'); + + $path = $creator->create($className); + + // Load the alternative template if it is defined. + $contents = file_get_contents(__DIR__ . '/stubs/jobs.stub'); + + // inject the class names appropriate to this migration + $contents = strtr($contents, [ + 'CreateJobsTable' => $className, + '{{table}}' => $table, + ]); + + file_put_contents($path, $contents); + + $this->output->info('Migration created successfully!'); + } +} diff --git a/vendor/topthink/think-queue/src/queue/command/Work.php b/vendor/topthink/think-queue/src/queue/command/Work.php new file mode 100644 index 0000000..276eb2a --- /dev/null +++ b/vendor/topthink/think-queue/src/queue/command/Work.php @@ -0,0 +1,154 @@ + +// +---------------------------------------------------------------------- +namespace think\queue\command; + +use think\console\Command; +use think\console\Input; +use think\console\input\Argument; +use think\console\input\Option; +use think\console\Output; +use think\queue\event\JobFailed; +use think\queue\event\JobProcessed; +use think\queue\event\JobProcessing; +use think\queue\Job; +use think\queue\Worker; + +class Work extends Command +{ + + /** + * The queue worker instance. + * @var Worker + */ + protected $worker; + + public function __construct(Worker $worker) + { + parent::__construct(); + $this->worker = $worker; + } + + protected function configure() + { + $this->setName('queue:work') + ->addArgument('connection', Argument::OPTIONAL, 'The name of the queue connection to work', null) + ->addOption('queue', null, Option::VALUE_OPTIONAL, 'The queue to listen on') + ->addOption('once', null, Option::VALUE_NONE, 'Only process the next job on the queue') + ->addOption('delay', null, Option::VALUE_OPTIONAL, 'Amount of time to delay failed jobs', 0) + ->addOption('force', null, Option::VALUE_NONE, 'Force the worker to run even in maintenance mode') + ->addOption('memory', null, Option::VALUE_OPTIONAL, 'The memory limit in megabytes', 128) + ->addOption('timeout', null, Option::VALUE_OPTIONAL, 'The number of seconds a child process can run', 60) + ->addOption('sleep', null, Option::VALUE_OPTIONAL, 'Number of seconds to sleep when no job is available', 3) + ->addOption('tries', null, Option::VALUE_OPTIONAL, 'Number of times to attempt a job before logging it failed', 0) + ->setDescription('Process the next job on a queue'); + } + + /** + * Execute the console command. + * @param Input $input + * @param Output $output + * @return int|null|void + */ + public function execute(Input $input, Output $output) + { + $connection = $input->getArgument('connection') ?: $this->app->config->get('queue.default'); + + $queue = $input->getOption('queue') ?: $this->app->config->get("queue.connections.{$connection}.queue", 'default'); + $delay = $input->getOption('delay'); + $sleep = $input->getOption('sleep'); + $tries = $input->getOption('tries'); + + $this->listenForEvents(); + + if ($input->getOption('once')) { + $this->worker->runNextJob($connection, $queue, $delay, $sleep, $tries); + } else { + $memory = $input->getOption('memory'); + $timeout = $input->getOption('timeout'); + $this->worker->daemon($connection, $queue, $delay, $sleep, $tries, $memory, $timeout); + } + } + + /** + * 注册事件 + */ + protected function listenForEvents() + { + $this->app->event->listen(JobProcessing::class, function (JobProcessing $event) { + $this->writeOutput($event->job, 'starting'); + }); + + $this->app->event->listen(JobProcessed::class, function (JobProcessed $event) { + $this->writeOutput($event->job, 'success'); + }); + + $this->app->event->listen(JobFailed::class, function (JobFailed $event) { + $this->writeOutput($event->job, 'failed'); + + $this->logFailedJob($event); + }); + } + + /** + * Write the status output for the queue worker. + * + * @param Job $job + * @param $status + */ + protected function writeOutput(Job $job, $status) + { + switch ($status) { + case 'starting': + $this->writeStatus($job, 'Processing', 'comment'); + break; + case 'success': + $this->writeStatus($job, 'Processed', 'info'); + break; + case 'failed': + $this->writeStatus($job, 'Failed', 'error'); + break; + } + } + + /** + * Format the status output for the queue worker. + * + * @param Job $job + * @param string $status + * @param string $type + * @return void + */ + protected function writeStatus(Job $job, $status, $type) + { + $this->output->writeln(sprintf( + "<{$type}>[%s][%s] %s %s", + date('Y-m-d H:i:s'), + $job->getJobId(), + str_pad("{$status}:", 11), + $job->getName() + )); + } + + /** + * 记录失败任务 + * @param JobFailed $event + */ + protected function logFailedJob(JobFailed $event) + { + $this->app['queue.failer']->log( + $event->connection, + $event->job->getQueue(), + $event->job->getRawBody(), + $event->exception + ); + } + +} diff --git a/vendor/topthink/think-queue/src/queue/command/stubs/failed_jobs.stub b/vendor/topthink/think-queue/src/queue/command/stubs/failed_jobs.stub new file mode 100644 index 0000000..948cebc --- /dev/null +++ b/vendor/topthink/think-queue/src/queue/command/stubs/failed_jobs.stub @@ -0,0 +1,18 @@ +table('{{table}}') + ->addColumn(Column::text('connection')) + ->addColumn(Column::text('queue')) + ->addColumn(Column::longText('payload')) + ->addColumn(Column::longText('exception')) + ->addColumn(Column::timestamp('fail_time')->setDefault('CURRENT_TIMESTAMP')) + ->create(); + } +} diff --git a/vendor/topthink/think-queue/src/queue/command/stubs/jobs.stub b/vendor/topthink/think-queue/src/queue/command/stubs/jobs.stub new file mode 100644 index 0000000..ab628aa --- /dev/null +++ b/vendor/topthink/think-queue/src/queue/command/stubs/jobs.stub @@ -0,0 +1,20 @@ +table('{{table}}') + ->addColumn(Column::string('queue')) + ->addColumn(Column::longText('payload')) + ->addColumn(Column::tinyInteger('attempts')->setUnsigned()) + ->addColumn(Column::unsignedInteger('reserve_time')->setNullable()) + ->addColumn(Column::unsignedInteger('available_time')) + ->addColumn(Column::unsignedInteger('create_time')) + ->addIndex('queue') + ->create(); + } +} diff --git a/vendor/topthink/think-queue/src/queue/connector/Database.php b/vendor/topthink/think-queue/src/queue/connector/Database.php new file mode 100644 index 0000000..7c1cf63 --- /dev/null +++ b/vendor/topthink/think-queue/src/queue/connector/Database.php @@ -0,0 +1,228 @@ + +// +---------------------------------------------------------------------- + +namespace think\queue\connector; + +use Carbon\Carbon; +use stdClass; +use think\Db; +use think\db\ConnectionInterface; +use think\db\Query; +use think\queue\Connector; +use think\queue\InteractsWithTime; +use think\queue\job\Database as DatabaseJob; + +class Database extends Connector +{ + + use InteractsWithTime; + + protected $db; + + /** + * The database table that holds the jobs. + * + * @var string + */ + protected $table; + + /** + * The name of the default queue. + * + * @var string + */ + protected $default; + + /** + * The expiration time of a job. + * + * @var int|null + */ + protected $retryAfter = 60; + + public function __construct(ConnectionInterface $db, $table, $default = 'default', $retryAfter = 60) + { + $this->db = $db; + $this->table = $table; + $this->default = $default; + $this->retryAfter = $retryAfter; + } + + public static function __make(Db $db, $config) + { + $connection = $db->connect($config['connection'] ?? null); + + return new self($connection, $config['table'], $config['queue'], $config['retry_after'] ?? 60); + } + + public function size($queue = null) + { + return $this->db + ->name($this->table) + ->where('queue', $this->getQueue($queue)) + ->count(); + } + + public function push($job, $data = '', $queue = null) + { + return $this->pushToDatabase($queue, $this->createPayload($job, $data)); + } + + public function pushRaw($payload, $queue = null, array $options = []) + { + return $this->pushToDatabase($queue, $payload); + } + + public function later($delay, $job, $data = '', $queue = null) + { + return $this->pushToDatabase($queue, $this->createPayload($job, $data), $delay); + } + + public function bulk($jobs, $data = '', $queue = null) + { + $queue = $this->getQueue($queue); + + $availableAt = $this->availableAt(); + + return $this->db->name($this->table)->insertAll(collect((array) $jobs)->map( + function ($job) use ($queue, $data, $availableAt) { + return [ + 'queue' => $queue, + 'attempts' => 0, + 'reserve_time' => null, + 'available_time' => $availableAt, + 'create_time' => $this->currentTime(), + 'payload' => $this->createPayload($job, $data), + ]; + } + )->all()); + } + + /** + * 重新发布任务 + * + * @param string $queue + * @param StdClass $job + * @param int $delay + * @return mixed + */ + public function release($queue, $job, $delay) + { + return $this->pushToDatabase($queue, $job->payload, $delay, $job->attempts); + } + + /** + * Push a raw payload to the database with a given delay. + * + * @param \DateTime|int $delay + * @param string|null $queue + * @param string $payload + * @param int $attempts + * @return mixed + */ + protected function pushToDatabase($queue, $payload, $delay = 0, $attempts = 0) + { + return $this->db->name($this->table)->insertGetId([ + 'queue' => $this->getQueue($queue), + 'attempts' => $attempts, + 'reserve_time' => null, + 'available_time' => $this->availableAt($delay), + 'create_time' => $this->currentTime(), + 'payload' => $payload, + ]); + } + + public function pop($queue = null) + { + $queue = $this->getQueue($queue); + + return $this->db->transaction(function () use ($queue) { + + if ($job = $this->getNextAvailableJob($queue)) { + + $job = $this->markJobAsReserved($job); + + return new DatabaseJob($this->app, $this, $job, $this->connection, $queue); + } + }); + } + + /** + * 获取下个有效任务 + * + * @param string|null $queue + * @return StdClass|null + */ + protected function getNextAvailableJob($queue) + { + + $job = $this->db + ->name($this->table) + ->lock(true) + ->where('queue', $this->getQueue($queue)) + ->where(function (Query $query) { + $query->where(function (Query $query) { + $query->whereNull('reserve_time') + ->where('available_time', '<=', $this->currentTime()); + }); + + //超时任务重试 + $expiration = Carbon::now()->subSeconds($this->retryAfter)->getTimestamp(); + + $query->whereOr(function (Query $query) use ($expiration) { + $query->where('reserve_time', '<=', $expiration); + }); + }) + ->order('id', 'asc') + ->find(); + + return $job ? (object) $job : null; + } + + /** + * 标记任务正在执行. + * + * @param stdClass $job + * @return stdClass + */ + protected function markJobAsReserved($job) + { + $this->db + ->name($this->table) + ->where('id', $job->id) + ->update([ + 'reserve_time' => $job->reserve_time = $this->currentTime(), + 'attempts' => ++$job->attempts, + ]); + + return $job; + } + + /** + * 删除任务 + * + * @param string $id + * @return void + */ + public function deleteReserved($id) + { + $this->db->transaction(function () use ($id) { + if ($this->db->name($this->table)->lock(true)->find($id)) { + $this->db->name($this->table)->where('id', $id)->delete(); + } + }); + } + + protected function getQueue($queue) + { + return $queue ?: $this->default; + } +} diff --git a/vendor/topthink/think-queue/src/queue/connector/Redis.php b/vendor/topthink/think-queue/src/queue/connector/Redis.php new file mode 100644 index 0000000..4b75701 --- /dev/null +++ b/vendor/topthink/think-queue/src/queue/connector/Redis.php @@ -0,0 +1,331 @@ + +// +---------------------------------------------------------------------- + +namespace think\queue\connector; + +use Closure; +use Exception; +use RedisException; +use think\helper\Str; +use think\queue\Connector; +use think\queue\InteractsWithTime; +use think\queue\job\Redis as RedisJob; + +class Redis extends Connector +{ + use InteractsWithTime; + + /** @var \Redis */ + protected $redis; + + /** + * The name of the default queue. + * + * @var string + */ + protected $default; + + /** + * The expiration time of a job. + * + * @var int|null + */ + protected $retryAfter = 60; + + /** + * The maximum number of seconds to block for a job. + * + * @var int|null + */ + protected $blockFor = null; + + public function __construct($redis, $default = 'default', $retryAfter = 60, $blockFor = null) + { + $this->redis = $redis; + $this->default = $default; + $this->retryAfter = $retryAfter; + $this->blockFor = $blockFor; + } + + public static function __make($config) + { + if (!extension_loaded('redis')) { + throw new Exception('redis扩展未安装'); + } + + $redis = new class($config) { + protected $config; + protected $client; + + public function __construct($config) + { + $this->config = $config; + $this->client = $this->createClient(); + } + + protected function createClient() + { + $config = $this->config; + $func = $config['persistent'] ? 'pconnect' : 'connect'; + + $client = new \Redis; + $client->$func($config['host'], $config['port'], $config['timeout']); + + if ('' != $config['password']) { + $client->auth($config['password']); + } + + if (0 != $config['select']) { + $client->select($config['select']); + } + return $client; + } + + public function __call($name, $arguments) + { + try { + return call_user_func_array([$this->client, $name], $arguments); + } catch (RedisException $e) { + if (Str::contains($e->getMessage(), 'went away')) { + $this->client = $this->createClient(); + } + + throw $e; + } + } + }; + + return new self($redis, $config['queue'], $config['retry_after'] ?? 60, $config['block_for'] ?? null); + } + + public function size($queue = null) + { + $queue = $this->getQueue($queue); + + return $this->redis->lLen($queue) + $this->redis->zCard("{$queue}:delayed") + $this->redis->zCard("{$queue}:reserved"); + } + + public function push($job, $data = '', $queue = null) + { + return $this->pushRaw($this->createPayload($job, $data), $queue); + } + + public function pushRaw($payload, $queue = null, array $options = []) + { + if ($this->redis->rPush($this->getQueue($queue), $payload)) { + return json_decode($payload, true)['id'] ?? null; + } + } + + public function later($delay, $job, $data = '', $queue = null) + { + return $this->laterRaw($delay, $this->createPayload($job, $data), $queue); + } + + protected function laterRaw($delay, $payload, $queue = null) + { + if ($this->redis->zadd( + $this->getQueue($queue) . ':delayed', + $this->availableAt($delay), + $payload + )) { + return json_decode($payload, true)['id'] ?? null; + } + } + + public function pop($queue = null) + { + $this->migrate($prefixed = $this->getQueue($queue)); + + if (empty($nextJob = $this->retrieveNextJob($prefixed))) { + return; + } + + [$job, $reserved] = $nextJob; + + if ($reserved) { + return new RedisJob($this->app, $this, $job, $reserved, $this->connection, $queue); + } + } + + /** + * Migrate any delayed or expired jobs onto the primary queue. + * + * @param string $queue + * @return void + */ + protected function migrate($queue) + { + $this->migrateExpiredJobs($queue . ':delayed', $queue); + + if (!is_null($this->retryAfter)) { + $this->migrateExpiredJobs($queue . ':reserved', $queue); + } + } + + /** + * 移动延迟任务 + * + * @param string $from + * @param string $to + * @param bool $attempt + */ + public function migrateExpiredJobs($from, $to, $attempt = true) + { + $this->redis->watch($from); + + $jobs = $this->redis->zRangeByScore($from, '-inf', $this->currentTime()); + + if (!empty($jobs)) { + $this->transaction(function () use ($from, $to, $jobs, $attempt) { + + $this->redis->zRemRangeByRank($from, 0, count($jobs) - 1); + + for ($i = 0; $i < count($jobs); $i += 100) { + + $values = array_slice($jobs, $i, 100); + + $this->redis->rPush($to, ...$values); + } + }); + } + + $this->redis->unwatch(); + } + + /** + * Retrieve the next job from the queue. + * + * @param string $queue + * @return array + */ + protected function retrieveNextJob($queue) + { + if (!is_null($this->blockFor)) { + return $this->blockingPop($queue); + } + + $job = $this->redis->lpop($queue); + $reserved = false; + + if ($job) { + $reserved = json_decode($job, true); + $reserved['attempts']++; + $reserved = json_encode($reserved); + $this->redis->zAdd($queue . ':reserved', $this->availableAt($this->retryAfter), $reserved); + } + + return [$job, $reserved]; + } + + /** + * Retrieve the next job by blocking-pop. + * + * @param string $queue + * @return array + */ + protected function blockingPop($queue) + { + $rawBody = $this->redis->blpop($queue, $this->blockFor); + + if (!empty($rawBody)) { + $payload = json_decode($rawBody[1], true); + + $payload['attempts']++; + + $reserved = json_encode($payload); + + $this->redis->zadd($queue . ':reserved', $this->availableAt($this->retryAfter), $reserved); + + return [$rawBody[1], $reserved]; + } + + return [null, null]; + } + + /** + * 删除任务 + * + * @param string $queue + * @param RedisJob $job + * @return void + */ + public function deleteReserved($queue, $job) + { + $this->redis->zRem($this->getQueue($queue) . ':reserved', $job->getReservedJob()); + } + + /** + * Delete a reserved job from the reserved queue and release it. + * + * @param string $queue + * @param RedisJob $job + * @param int $delay + * @return void + */ + public function deleteAndRelease($queue, $job, $delay) + { + $queue = $this->getQueue($queue); + + $reserved = $job->getReservedJob(); + + $this->redis->zRem($queue . ':reserved', $reserved); + + $this->redis->zAdd($queue . ':delayed', $this->availableAt($delay), $reserved); + } + + /** + * redis事务 + * @param Closure $closure + */ + protected function transaction(Closure $closure) + { + $this->redis->multi(); + try { + call_user_func($closure); + if (!$this->redis->exec()) { + $this->redis->discard(); + } + } catch (Exception $e) { + $this->redis->discard(); + } + } + + protected function createPayloadArray($job, $data = '') + { + return array_merge(parent::createPayloadArray($job, $data), [ + 'id' => $this->getRandomId(), + 'attempts' => 0, + ]); + } + + /** + * 随机id + * + * @return string + */ + protected function getRandomId() + { + return Str::random(32); + } + + /** + * 获取队列名 + * + * @param string|null $queue + * @return string + */ + protected function getQueue($queue) + { + $queue = $queue ?: $this->default; + return "{queues:{$queue}}"; + } +} diff --git a/vendor/topthink/think-queue/src/queue/connector/Sync.php b/vendor/topthink/think-queue/src/queue/connector/Sync.php new file mode 100644 index 0000000..3f4d420 --- /dev/null +++ b/vendor/topthink/think-queue/src/queue/connector/Sync.php @@ -0,0 +1,74 @@ + +// +---------------------------------------------------------------------- + +namespace think\queue\connector; + +use Exception; +use think\queue\Connector; +use think\queue\event\JobFailed; +use think\queue\event\JobProcessed; +use think\queue\event\JobProcessing; +use think\queue\job\Sync as SyncJob; +use Throwable; + +class Sync extends Connector +{ + + public function size($queue = null) + { + return 0; + } + + public function push($job, $data = '', $queue = null) + { + $queueJob = $this->resolveJob($this->createPayload($job, $data), $queue); + + try { + $this->triggerEvent(new JobProcessing($this->connection, $job)); + + $queueJob->fire(); + + $this->triggerEvent(new JobProcessed($this->connection, $job)); + } catch (Exception | Throwable $e) { + + $this->triggerEvent(new JobFailed($this->connection, $job, $e)); + + throw $e; + } + + return 0; + } + + protected function triggerEvent($event) + { + $this->app->event->trigger($event); + } + + public function pop($queue = null) + { + + } + + protected function resolveJob($payload, $queue) + { + return new SyncJob($this->app, $payload, $this->connection, $queue); + } + + public function pushRaw($payload, $queue = null, array $options = []) + { + + } + + public function later($delay, $job, $data = '', $queue = null) + { + return $this->push($job, $data, $queue); + } +} diff --git a/vendor/topthink/think-queue/src/queue/event/JobExceptionOccurred.php b/vendor/topthink/think-queue/src/queue/event/JobExceptionOccurred.php new file mode 100644 index 0000000..2e7d1eb --- /dev/null +++ b/vendor/topthink/think-queue/src/queue/event/JobExceptionOccurred.php @@ -0,0 +1,45 @@ +job = $job; + $this->exception = $exception; + $this->connectionName = $connectionName; + } +} diff --git a/vendor/topthink/think-queue/src/queue/event/JobFailed.php b/vendor/topthink/think-queue/src/queue/event/JobFailed.php new file mode 100644 index 0000000..9e3706a --- /dev/null +++ b/vendor/topthink/think-queue/src/queue/event/JobFailed.php @@ -0,0 +1,24 @@ +connection = $connection; + $this->job = $job; + $this->exception = $exception; + } +} diff --git a/vendor/topthink/think-queue/src/queue/event/JobProcessed.php b/vendor/topthink/think-queue/src/queue/event/JobProcessed.php new file mode 100644 index 0000000..9ae9fca --- /dev/null +++ b/vendor/topthink/think-queue/src/queue/event/JobProcessed.php @@ -0,0 +1,20 @@ +connection = $connection; + $this->job = $job; + } +} diff --git a/vendor/topthink/think-queue/src/queue/event/JobProcessing.php b/vendor/topthink/think-queue/src/queue/event/JobProcessing.php new file mode 100644 index 0000000..72ff3c5 --- /dev/null +++ b/vendor/topthink/think-queue/src/queue/event/JobProcessing.php @@ -0,0 +1,20 @@ +connection = $connection; + $this->job = $job; + } +} diff --git a/vendor/topthink/think-queue/src/queue/event/WorkerStopping.php b/vendor/topthink/think-queue/src/queue/event/WorkerStopping.php new file mode 100644 index 0000000..5f1fc75 --- /dev/null +++ b/vendor/topthink/think-queue/src/queue/event/WorkerStopping.php @@ -0,0 +1,24 @@ +status = $status; + } +} diff --git a/vendor/topthink/think-queue/src/queue/exception/MaxAttemptsExceededException.php b/vendor/topthink/think-queue/src/queue/exception/MaxAttemptsExceededException.php new file mode 100644 index 0000000..fb8440b --- /dev/null +++ b/vendor/topthink/think-queue/src/queue/exception/MaxAttemptsExceededException.php @@ -0,0 +1,10 @@ +db = $db; + $this->table = $table; + } + + public static function __make(Db $db, $config) + { + return new self($db, $config['table']); + } + + /** + * Log a failed job into storage. + * + * @param string $connection + * @param string $queue + * @param string $payload + * @param \Exception $exception + * @return int|null + */ + public function log($connection, $queue, $payload, $exception) + { + $fail_time = Carbon::now()->toDateTimeString(); + + $exception = (string) $exception; + + return $this->getTable()->insertGetId(compact( + 'connection', + 'queue', + 'payload', + 'exception', + 'fail_time' + )); + } + + /** + * Get a list of all of the failed jobs. + * + * @return array + */ + public function all() + { + return collect($this->getTable()->order('id', 'desc')->select())->all(); + } + + /** + * Get a single failed job. + * + * @param mixed $id + * @return object|null + */ + public function find($id) + { + return $this->getTable()->find($id); + } + + /** + * Delete a single failed job from storage. + * + * @param mixed $id + * @return bool + */ + public function forget($id) + { + return $this->getTable()->where('id', $id)->delete() > 0; + } + + /** + * Flush all of the failed jobs from storage. + * + * @return void + */ + public function flush() + { + $this->getTable()->delete(true); + } + + protected function getTable() + { + return $this->db->name($this->table); + } +} diff --git a/vendor/topthink/think-queue/src/queue/failed/None.php b/vendor/topthink/think-queue/src/queue/failed/None.php new file mode 100644 index 0000000..205dd2f --- /dev/null +++ b/vendor/topthink/think-queue/src/queue/failed/None.php @@ -0,0 +1,63 @@ + +// +---------------------------------------------------------------------- +namespace think\queue\job; + +use think\App; +use think\queue\connector\Database as DatabaseQueue; +use think\queue\Job; + +class Database extends Job +{ + /** + * The database queue instance. + * @var DatabaseQueue + */ + protected $database; + + /** + * The database job payload. + * @var Object + */ + protected $job; + + public function __construct(App $app, DatabaseQueue $database, $job, $connection, $queue) + { + $this->app = $app; + $this->job = $job; + $this->queue = $queue; + $this->database = $database; + $this->connection = $connection; + } + + /** + * 删除任务 + * @return void + */ + public function delete() + { + parent::delete(); + $this->database->deleteReserved($this->job->id); + } + + /** + * 重新发布任务 + * @param int $delay + * @return void + */ + public function release($delay = 0) + { + parent::release($delay); + + $this->delete(); + + $this->database->release($this->queue, $this->job, $delay); + } + + /** + * 获取当前任务尝试次数 + * @return int + */ + public function attempts() + { + return (int) $this->job->attempts; + } + + /** + * Get the raw body string for the job. + * @return string + */ + public function getRawBody() + { + return $this->job->payload; + } + + /** + * Get the job identifier. + * + * @return string + */ + public function getJobId() + { + return $this->job->id; + } +} diff --git a/vendor/topthink/think-queue/src/queue/job/Redis.php b/vendor/topthink/think-queue/src/queue/job/Redis.php new file mode 100644 index 0000000..79b6e40 --- /dev/null +++ b/vendor/topthink/think-queue/src/queue/job/Redis.php @@ -0,0 +1,121 @@ + +// +---------------------------------------------------------------------- + +namespace think\queue\job; + +use think\App; +use think\queue\connector\Redis as RedisQueue; +use think\queue\Job; + +class Redis extends Job +{ + + /** + * The redis queue instance. + * @var RedisQueue + */ + protected $redis; + + /** + * The database job payload. + * @var Object + */ + protected $job; + + /** + * The JSON decoded version of "$job". + * + * @var array + */ + protected $decoded; + + /** + * The Redis job payload inside the reserved queue. + * + * @var string + */ + protected $reserved; + + public function __construct(App $app, RedisQueue $redis, $job, $reserved, $connection, $queue) + { + $this->app = $app; + $this->job = $job; + $this->queue = $queue; + $this->connection = $connection; + $this->redis = $redis; + $this->reserved = $reserved; + + $this->decoded = $this->payload(); + } + + /** + * Get the number of times the job has been attempted. + * @return int + */ + public function attempts() + { + return ($this->decoded['attempts'] ?? null) + 1; + } + + /** + * Get the raw body string for the job. + * @return string + */ + public function getRawBody() + { + return $this->job; + } + + /** + * 删除任务 + * + * @return void + */ + public function delete() + { + parent::delete(); + + $this->redis->deleteReserved($this->queue, $this); + } + + /** + * 重新发布任务 + * + * @param int $delay + * @return void + */ + public function release($delay = 0) + { + parent::release($delay); + + $this->redis->deleteAndRelease($this->queue, $this, $delay); + } + + /** + * Get the job identifier. + * + * @return string + */ + public function getJobId() + { + return $this->decoded['id'] ?? null; + } + + /** + * Get the underlying reserved Redis job. + * + * @return string + */ + public function getReservedJob() + { + return $this->reserved; + } +} diff --git a/vendor/topthink/think-queue/src/queue/job/Sync.php b/vendor/topthink/think-queue/src/queue/job/Sync.php new file mode 100644 index 0000000..c2299d8 --- /dev/null +++ b/vendor/topthink/think-queue/src/queue/job/Sync.php @@ -0,0 +1,66 @@ + +// +---------------------------------------------------------------------- + +namespace think\queue\job; + +use think\App; +use think\queue\Job; + +class Sync extends Job +{ + /** + * The queue message data. + * + * @var string + */ + protected $payload; + + public function __construct(App $app, $payload, $connection, $queue) + { + $this->app = $app; + $this->connection = $connection; + $this->queue = $queue; + $this->payload = $payload; + } + + /** + * Get the number of times the job has been attempted. + * @return int + */ + public function attempts() + { + return 1; + } + + /** + * Get the raw body string for the job. + * @return string + */ + public function getRawBody() + { + return $this->payload; + } + + /** + * Get the job identifier. + * + * @return string + */ + public function getJobId() + { + return ''; + } + + public function getQueue() + { + return 'sync'; + } +} diff --git a/vendor/topthink/think-queue/tests/DatabaseConnectorTest.php b/vendor/topthink/think-queue/tests/DatabaseConnectorTest.php new file mode 100644 index 0000000..3856886 --- /dev/null +++ b/vendor/topthink/think-queue/tests/DatabaseConnectorTest.php @@ -0,0 +1,123 @@ +db = m::mock(Db::class); + $this->connector = new Database($this->db, 'table', 'default'); + } + + public function testPushProperlyPushesJobOntoDatabase() + { + $this->db->shouldReceive('name')->with('table')->andReturn($query = m::mock(stdClass::class)); + + $query->shouldReceive('insertGetId')->once()->andReturnUsing(function ($array) { + $this->assertEquals('default', $array['queue']); + $this->assertEquals(json_encode(['job' => 'foo', 'maxTries' => null, 'timeout' => null, 'data' => ['data']]), $array['payload']); + $this->assertEquals(0, $array['attempts']); + $this->assertNull($array['reserved_at']); + $this->assertInternalType('int', $array['available_at']); + }); + $this->connector->push('foo', ['data']); + } + + public function testDelayedPushProperlyPushesJobOntoDatabase() + { + $this->db->shouldReceive('name')->with('table')->andReturn($query = m::mock(stdClass::class)); + + $query->shouldReceive('insertGetId')->once()->andReturnUsing(function ($array) { + $this->assertEquals('default', $array['queue']); + $this->assertEquals(json_encode(['job' => 'foo', 'maxTries' => null, 'timeout' => null, 'data' => ['data']]), $array['payload']); + $this->assertEquals(0, $array['attempts']); + $this->assertNull($array['reserved_at']); + $this->assertInternalType('int', $array['available_at']); + }); + + $this->connector->later(10, 'foo', ['data']); + } + + public function testFailureToCreatePayloadFromObject() + { + $this->expectException('InvalidArgumentException'); + + $job = new stdClass; + $job->invalid = "\xc3\x28"; + + $queue = $this->getMockForAbstractClass(Connector::class); + $class = new ReflectionClass(Connector::class); + + $createPayload = $class->getMethod('createPayload'); + $createPayload->setAccessible(true); + $createPayload->invokeArgs($queue, [ + $job, + 'queue-name', + ]); + } + + public function testFailureToCreatePayloadFromArray() + { + $this->expectException('InvalidArgumentException'); + + $queue = $this->getMockForAbstractClass(Connector::class); + $class = new ReflectionClass(Connector::class); + + $createPayload = $class->getMethod('createPayload'); + $createPayload->setAccessible(true); + $createPayload->invokeArgs($queue, [ + ["\xc3\x28"], + 'queue-name', + ]); + } + + public function testBulkBatchPushesOntoDatabase() + { + + $this->db->shouldReceive('name')->with('table')->andReturn($query = m::mock(stdClass::class)); + + Carbon::setTestNow( + $now = Carbon::now()->addSeconds() + ); + + $query->shouldReceive('insertAll')->once()->andReturnUsing(function ($records) use ($now) { + $this->assertEquals([ + [ + 'queue' => 'queue', + 'payload' => json_encode(['job' => 'foo', 'maxTries' => null, 'timeout' => null, 'data' => ['data']]), + 'attempts' => 0, + 'reserved_at' => null, + 'available_at' => $now->getTimestamp(), + 'created_at' => $now->getTimestamp(), + ], [ + 'queue' => 'queue', + 'payload' => json_encode(['job' => 'bar', 'maxTries' => null, 'timeout' => null, 'data' => ['data']]), + 'attempts' => 0, + 'reserved_at' => null, + 'available_at' => $now->getTimestamp(), + 'created_at' => $now->getTimestamp(), + ], + ], $records); + }); + + $this->connector->bulk(['foo', 'bar'], ['data'], 'queue'); + } + +} diff --git a/vendor/topthink/think-queue/tests/ListenerTest.php b/vendor/topthink/think-queue/tests/ListenerTest.php new file mode 100644 index 0000000..883b033 --- /dev/null +++ b/vendor/topthink/think-queue/tests/ListenerTest.php @@ -0,0 +1,80 @@ +makePartial(); + $process->shouldReceive('run')->once(); + /** @var Listener|MockInterface $listener */ + $listener = m::mock(Listener::class)->makePartial(); + $listener->shouldReceive('memoryExceeded')->once()->with(1)->andReturn(false); + + $listener->runProcess($process, 1); + } + + public function testListenerStopsWhenMemoryIsExceeded() + { + /** @var Process|MockInterface $process */ + $process = m::mock(Process::class)->makePartial(); + $process->shouldReceive('run')->once(); + /** @var Listener|MockInterface $listener */ + $listener = m::mock(Listener::class)->makePartial(); + $listener->shouldReceive('memoryExceeded')->once()->with(1)->andReturn(true); + $listener->shouldReceive('stop')->once(); + + $listener->runProcess($process, 1); + } + + public function testMakeProcessCorrectlyFormatsCommandLine() + { + $listener = new Listener(__DIR__); + + $process = $listener->makeProcess('connection', 'queue', 1, 3, 0, 2, 3); + $escape = '\\' === DIRECTORY_SEPARATOR ? '"' : '\''; + + $this->assertInstanceOf(Process::class, $process); + $this->assertEquals(__DIR__, $process->getWorkingDirectory()); + $this->assertEquals(3, $process->getTimeout()); + $this->assertEquals($escape . PHP_BINARY . $escape . " {$escape}think{$escape} {$escape}queue:work{$escape} {$escape}connection{$escape} {$escape}--once{$escape} {$escape}--queue=queue{$escape} {$escape}--delay=1{$escape} {$escape}--memory=2{$escape} {$escape}--sleep=3{$escape} {$escape}--tries=0{$escape}", $process->getCommandLine()); + } + + public function testMakeProcessCorrectlyFormatsCommandLineWithAnEnvironmentSpecified() + { + $listener = new Listener(__DIR__); + + $process = $listener->makeProcess('connection', 'queue', 1, 3, 0, 2, 3); + $escape = '\\' === DIRECTORY_SEPARATOR ? '"' : '\''; + + $this->assertInstanceOf(Process::class, $process); + $this->assertEquals(__DIR__, $process->getWorkingDirectory()); + $this->assertEquals(3, $process->getTimeout()); + $this->assertEquals($escape . PHP_BINARY . $escape . " {$escape}think{$escape} {$escape}queue:work{$escape} {$escape}connection{$escape} {$escape}--once{$escape} {$escape}--queue=queue{$escape} {$escape}--delay=1{$escape} {$escape}--memory=2{$escape} {$escape}--sleep=3{$escape} {$escape}--tries=0{$escape}", $process->getCommandLine()); + } + + public function testMakeProcessCorrectlyFormatsCommandLineWhenTheConnectionIsNotSpecified() + { + $listener = new Listener(__DIR__); + + $process = $listener->makeProcess(null, 'queue', 1, 3, 0, 2, 3); + $escape = '\\' === DIRECTORY_SEPARATOR ? '"' : '\''; + + $this->assertInstanceOf(Process::class, $process); + $this->assertEquals(__DIR__, $process->getWorkingDirectory()); + $this->assertEquals(3, $process->getTimeout()); + $this->assertEquals($escape . PHP_BINARY . $escape . " {$escape}think{$escape} {$escape}queue:work{$escape} {$escape}--once{$escape} {$escape}--queue=queue{$escape} {$escape}--delay=1{$escape} {$escape}--memory=2{$escape} {$escape}--sleep=3{$escape} {$escape}--tries=0{$escape}", $process->getCommandLine()); + } +} diff --git a/vendor/topthink/think-queue/tests/QueueTest.php b/vendor/topthink/think-queue/tests/QueueTest.php new file mode 100644 index 0000000..a87c80d --- /dev/null +++ b/vendor/topthink/think-queue/tests/QueueTest.php @@ -0,0 +1,49 @@ +queue = new Queue($this->app); + } + + public function testDefaultConnectionCanBeResolved() + { + $sync = new Sync(); + + $this->app->shouldReceive('invokeClass')->once()->with('\think\queue\connector\Sync', [['driver' => 'sync']])->andReturn($sync); + + $config = m::mock(Config::class); + + $config->shouldReceive('get')->twice()->with('queue.connectors.sync', ['driver' => 'sync'])->andReturn(['driver' => 'sync']); + $config->shouldReceive('get')->once()->with('queue.default', 'sync')->andReturn('sync'); + + $this->app->shouldReceive('get')->times(3)->with('config')->andReturn($config); + + $this->assertSame($sync, $this->queue->driver('sync')); + $this->assertSame($sync, $this->queue->driver()); + } + + public function testNotSupportDriver() + { + $config = m::mock(Config::class); + + $config->shouldReceive('get')->once()->with('queue.connectors.hello', ['driver' => 'sync'])->andReturn(['driver' => 'hello']); + $this->app->shouldReceive('get')->once()->with('config')->andReturn($config); + + $this->expectException(InvalidArgumentException::class); + $this->queue->driver('hello'); + } +} diff --git a/vendor/topthink/think-queue/tests/TestCase.php b/vendor/topthink/think-queue/tests/TestCase.php new file mode 100644 index 0000000..5a9279d --- /dev/null +++ b/vendor/topthink/think-queue/tests/TestCase.php @@ -0,0 +1,23 @@ +app = m::mock(App::class)->makePartial(); + } +} diff --git a/vendor/topthink/think-queue/tests/WorkerTest.php b/vendor/topthink/think-queue/tests/WorkerTest.php new file mode 100644 index 0000000..87eeacc --- /dev/null +++ b/vendor/topthink/think-queue/tests/WorkerTest.php @@ -0,0 +1,420 @@ +queue = m::mock(Queue::class); + $this->handle = m::spy(Handle::class); + $this->event = m::spy(Event::class); + $this->cache = m::spy(Cache::class); + } + + public function testJobCanBeFired() + { + + $worker = $this->getWorker(['default' => [$job = new WorkerFakeJob]]); + + $this->event->shouldReceive('trigger')->with(m::type(JobProcessing::class))->once(); + $this->event->shouldReceive('trigger')->with(m::type(JobProcessed::class))->once(); + + $worker->runNextJob('sync', 'default'); + } + + public function testWorkerCanWorkUntilQueueIsEmpty() + { + $worker = $this->getWorker(['default' => [ + $firstJob = new WorkerFakeJob, + $secondJob = new WorkerFakeJob, + ]]); + + $this->expectException(LoopBreakerException::class); + + $worker->daemon('sync', 'default'); + + $this->assertTrue($firstJob->fired); + + $this->assertTrue($secondJob->fired); + + $this->assertSame(0, $worker->stoppedWithStatus); + + $this->event->shouldHaveReceived('trigger')->with(m::type(JobProcessing::class))->twice(); + + $this->event->shouldHaveReceived('trigger')->with(m::type(JobProcessed::class))->twice(); + } + + public function testJobCanBeFiredBasedOnPriority() + { + $worker = $this->getWorker([ + 'high' => [ + $highJob = new WorkerFakeJob, + $secondHighJob = new WorkerFakeJob, + ], + 'low' => [$lowJob = new WorkerFakeJob], + ]); + + $worker->runNextJob('sync', 'high,low'); + + $this->assertTrue($highJob->fired); + $this->assertFalse($secondHighJob->fired); + $this->assertFalse($lowJob->fired); + + $worker->runNextJob('sync', 'high,low'); + $this->assertTrue($secondHighJob->fired); + $this->assertFalse($lowJob->fired); + + $worker->runNextJob('sync', 'high,low'); + $this->assertTrue($lowJob->fired); + } + + public function testExceptionIsReportedIfConnectionThrowsExceptionOnJobPop() + { + $e = new RuntimeException(); + + $sync = m::mock(Sync::class); + + $sync->shouldReceive('pop')->andReturnUsing(function () use ($e) { + throw $e; + }); + + $this->queue->shouldReceive('driver')->with('sync')->andReturn($sync); + + $worker = new Worker($this->queue, $this->event, $this->handle); + + $worker->runNextJob('sync', 'default'); + + $this->handle->shouldHaveReceived('report')->with($e); + } + + public function testWorkerSleepsWhenQueueIsEmpty() + { + $worker = $this->getWorker(['default' => []]); + $worker->runNextJob('sync', 'default', 0, 5); + $this->assertEquals(5, $worker->sleptFor); + } + + public function testJobIsReleasedOnException() + { + $e = new RuntimeException; + + $job = new WorkerFakeJob(function () use ($e) { + throw $e; + }); + + $worker = $this->getWorker(['default' => [$job]]); + $worker->runNextJob('sync', 'default', 10); + + $this->assertEquals(10, $job->releaseAfter); + $this->assertFalse($job->deleted); + $this->handle->shouldHaveReceived('report')->with($e); + $this->event->shouldHaveReceived('trigger')->with(m::type(JobExceptionOccurred::class))->once(); + $this->event->shouldNotHaveReceived('trigger', [m::type(JobProcessed::class)]); + } + + public function testJobIsNotReleasedIfItHasExceededMaxAttempts() + { + $e = new RuntimeException; + + $job = new WorkerFakeJob(function ($job) use ($e) { + // In normal use this would be incremented by being popped off the queue + $job->attempts++; + + throw $e; + }); + $job->attempts = 1; + + $worker = $this->getWorker(['default' => [$job]]); + $worker->runNextJob('sync', 'default', 0, 3, 1); + + $this->assertNull($job->releaseAfter); + $this->assertTrue($job->deleted); + $this->assertEquals($e, $job->failedWith); + $this->handle->shouldHaveReceived('report')->with($e); + $this->event->shouldHaveReceived('trigger')->with(m::type(JobExceptionOccurred::class))->once(); + $this->event->shouldHaveReceived('trigger')->with(m::type(JobFailed::class))->once(); + $this->event->shouldNotHaveReceived('trigger', [m::type(JobProcessed::class)]); + } + + public function testJobIsNotReleasedIfItHasExpired() + { + $e = new RuntimeException; + + $job = new WorkerFakeJob(function ($job) use ($e) { + // In normal use this would be incremented by being popped off the queue + $job->attempts++; + + throw $e; + }); + + $job->timeoutAt = Carbon::now()->addSeconds(1)->getTimestamp(); + + $job->attempts = 0; + + Carbon::setTestNow( + Carbon::now()->addSeconds(1) + ); + + $worker = $this->getWorker(['default' => [$job]]); + $worker->runNextJob('sync', 'default'); + + $this->assertNull($job->releaseAfter); + $this->assertTrue($job->deleted); + $this->assertEquals($e, $job->failedWith); + $this->handle->shouldHaveReceived('report')->with($e); + $this->event->shouldHaveReceived('trigger')->with(m::type(JobExceptionOccurred::class))->once(); + $this->event->shouldHaveReceived('trigger')->with(m::type(JobFailed::class))->once(); + $this->event->shouldNotHaveReceived('trigger', [m::type(JobProcessed::class)]); + } + + public function testJobIsFailedIfItHasAlreadyExceededMaxAttempts() + { + $job = new WorkerFakeJob(function ($job) { + $job->attempts++; + }); + + $job->attempts = 2; + + $worker = $this->getWorker(['default' => [$job]]); + $worker->runNextJob('sync', 'default', 0, 3, 1); + + $this->assertNull($job->releaseAfter); + $this->assertTrue($job->deleted); + $this->assertInstanceOf(MaxAttemptsExceededException::class, $job->failedWith); + $this->handle->shouldHaveReceived('report')->with(m::type(MaxAttemptsExceededException::class)); + $this->event->shouldHaveReceived('trigger')->with(m::type(JobExceptionOccurred::class))->once(); + $this->event->shouldHaveReceived('trigger')->with(m::type(JobFailed::class))->once(); + $this->event->shouldNotHaveReceived('trigger', [m::type(JobProcessed::class)]); + } + + public function testJobIsFailedIfItHasAlreadyExpired() + { + $job = new WorkerFakeJob(function ($job) { + $job->attempts++; + }); + + $job->timeoutAt = Carbon::now()->addSeconds(2)->getTimestamp(); + + $job->attempts = 1; + + Carbon::setTestNow( + Carbon::now()->addSeconds(3) + ); + + $worker = $this->getWorker(['default' => [$job]]); + $worker->runNextJob('sync', 'default'); + + $this->assertNull($job->releaseAfter); + $this->assertTrue($job->deleted); + $this->assertInstanceOf(MaxAttemptsExceededException::class, $job->failedWith); + $this->handle->shouldHaveReceived('report')->with(m::type(MaxAttemptsExceededException::class)); + $this->event->shouldHaveReceived('trigger')->with(m::type(JobExceptionOccurred::class))->once(); + $this->event->shouldHaveReceived('trigger')->with(m::type(JobFailed::class))->once(); + $this->event->shouldNotHaveReceived('trigger', [m::type(JobProcessed::class)]); + } + + public function testJobBasedMaxRetries() + { + $job = new WorkerFakeJob(function ($job) { + $job->attempts++; + }); + + $job->attempts = 2; + + $job->maxTries = 10; + + $worker = $this->getWorker(['default' => [$job]]); + $worker->runNextJob('sync', 'default', 0, 3, 1); + + $this->assertFalse($job->deleted); + $this->assertNull($job->failedWith); + } + + protected function getWorker($jobs) + { + $sync = m::mock(Sync::class); + + $sync->shouldReceive('pop')->andReturnUsing(function ($queue) use (&$jobs) { + return array_shift($jobs[$queue]); + }); + + $this->queue->shouldReceive('driver')->with('sync')->andReturn($sync); + + return new Worker($this->queue, $this->event, $this->handle, $this->cache); + } +} + +class WorkerFakeConnector +{ + public $jobs = []; + + public function __construct($jobs) + { + $this->jobs = $jobs; + } + + public function pop($queue) + { + return array_shift($this->jobs[$queue]); + } +} + +class Worker extends \think\queue\Worker +{ + public $sleptFor; + + public $stoppedWithStatus; + + public function sleep($seconds) + { + $this->sleptFor = $seconds; + } + + public function stop($status = 0) + { + $this->stoppedWithStatus = $status; + + throw new LoopBreakerException; + } + + protected function stopIfNecessary($job, $lastRestart, $memory) + { + if (is_null($job)) { + $this->stop(); + } else { + parent::stopIfNecessary($job, $lastRestart, $memory); + } + } +} + +class WorkerFakeJob +{ + + public $fired = false; + public $callback; + public $deleted = false; + public $releaseAfter; + public $released = false; + public $maxTries; + public $timeoutAt; + public $attempts = 0; + public $failedWith; + public $failed = false; + public $connectionName; + + public function __construct($callback = null) + { + $this->callback = $callback ?: function () { + // + }; + } + + public function fire() + { + $this->fired = true; + $this->callback->__invoke($this); + } + + public function payload() + { + return []; + } + + public function maxTries() + { + return $this->maxTries; + } + + public function timeoutAt() + { + return $this->timeoutAt; + } + + public function delete() + { + $this->deleted = true; + } + + public function isDeleted() + { + return $this->deleted; + } + + public function release($delay) + { + $this->released = true; + + $this->releaseAfter = $delay; + } + + public function isReleased() + { + return $this->released; + } + + public function attempts() + { + return $this->attempts; + } + + public function markAsFailed() + { + $this->failed = true; + } + + public function failed($e) + { + $this->markAsFailed(); + + $this->failedWith = $e; + } + + public function hasFailed() + { + return $this->failed; + } + + public function timeout() + { + return time() + 60; + } + + public function getName() + { + return 'WorkerFakeJob'; + } +} + +class LoopBreakerException extends RuntimeException +{ + // +} diff --git a/vendor/topthink/think-queue/tests/bootstrap.php b/vendor/topthink/think-queue/tests/bootstrap.php new file mode 100644 index 0000000..401013c --- /dev/null +++ b/vendor/topthink/think-queue/tests/bootstrap.php @@ -0,0 +1,3 @@ + './template/', + 'cache_path' => './runtime/', + 'view_suffix' => 'html', +]; + +$template = new Template($config); +// 模板变量赋值 +$template->assign(['name' => 'think']); +// 读取模板文件渲染输出 +$template->fetch('index'); +// 完整模板文件渲染 +$template->fetch('./template/test.php'); +// 渲染内容输出 +$template->display($content); +~~~ + +支持静态调用 + +~~~ +use think\facade\Template; + +Template::config([ + 'view_path' => './template/', + 'cache_path' => './runtime/', + 'view_suffix' => 'html', +]); +Template::assign(['name' => 'think']); +Template::fetch('index',['name' => 'think']); +Template::display($content,['name' => 'think']); +~~~ + +详细用法参考[开发手册](https://www.kancloud.cn/manual/think-template/content) \ No newline at end of file diff --git a/vendor/topthink/think-template/composer.json b/vendor/topthink/think-template/composer.json new file mode 100644 index 0000000..f4e1205 --- /dev/null +++ b/vendor/topthink/think-template/composer.json @@ -0,0 +1,20 @@ +{ + "name": "topthink/think-template", + "description": "the php template engine", + "license": "Apache-2.0", + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "require": { + "php": ">=7.1.0", + "psr/simple-cache": "^1.0" + }, + "autoload": { + "psr-4": { + "think\\": "src" + } + } +} \ No newline at end of file diff --git a/vendor/topthink/think-template/src/Template.php b/vendor/topthink/think-template/src/Template.php new file mode 100644 index 0000000..0feca8e --- /dev/null +++ b/vendor/topthink/think-template/src/Template.php @@ -0,0 +1,1330 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use Exception; +use Psr\SimpleCache\CacheInterface; + +/** + * ThinkPHP分离出来的模板引擎 + * 支持XML标签和普通标签的模板解析 + * 编译型模板引擎 支持动态缓存 + */ +class Template +{ + /** + * 模板变量 + * @var array + */ + protected $data = []; + + /** + * 模板配置参数 + * @var array + */ + protected $config = [ + 'view_path' => '', // 模板路径 + 'view_suffix' => 'html', // 默认模板文件后缀 + 'view_depr' => DIRECTORY_SEPARATOR, + 'cache_path' => '', + 'cache_suffix' => 'php', // 默认模板缓存后缀 + 'tpl_deny_func_list' => 'echo,exit', // 模板引擎禁用函数 + 'tpl_deny_php' => false, // 默认模板引擎是否禁用PHP原生代码 + 'tpl_begin' => '{', // 模板引擎普通标签开始标记 + 'tpl_end' => '}', // 模板引擎普通标签结束标记 + 'strip_space' => false, // 是否去除模板文件里面的html空格与换行 + 'tpl_cache' => true, // 是否开启模板编译缓存,设为false则每次都会重新编译 + 'compile_type' => 'file', // 模板编译类型 + 'cache_prefix' => '', // 模板缓存前缀标识,可以动态改变 + 'cache_time' => 0, // 模板缓存有效期 0 为永久,(以数字为值,单位:秒) + 'layout_on' => false, // 布局模板开关 + 'layout_name' => 'layout', // 布局模板入口文件 + 'layout_item' => '{__CONTENT__}', // 布局模板的内容替换标识 + 'taglib_begin' => '{', // 标签库标签开始标记 + 'taglib_end' => '}', // 标签库标签结束标记 + 'taglib_load' => true, // 是否使用内置标签库之外的其它标签库,默认自动检测 + 'taglib_build_in' => 'cx', // 内置标签库名称(标签使用不必指定标签库名称),以逗号分隔 注意解析顺序 + 'taglib_pre_load' => '', // 需要额外加载的标签库(须指定标签库名称),多个以逗号分隔 + 'display_cache' => false, // 模板渲染缓存 + 'cache_id' => '', // 模板缓存ID + 'tpl_replace_string' => [], + 'tpl_var_identify' => 'array', // .语法变量识别,array|object|'', 为空时自动识别 + 'default_filter' => 'htmlentities', // 默认过滤方法 用于普通标签输出 + ]; + + /** + * 保留内容信息 + * @var array + */ + private $literal = []; + + /** + * 扩展解析规则 + * @var array + */ + private $extend = []; + + /** + * 模板包含信息 + * @var array + */ + private $includeFile = []; + + /** + * 模板存储对象 + * @var object + */ + protected $storage; + + /** + * 查询缓存对象 + * @var CacheInterface + */ + protected $cache; + + /** + * 架构函数 + * @access public + * @param array $config + */ + public function __construct(array $config = []) + { + $this->config = array_merge($this->config, $config); + + $this->config['taglib_begin_origin'] = $this->config['taglib_begin']; + $this->config['taglib_end_origin'] = $this->config['taglib_end']; + + $this->config['taglib_begin'] = preg_quote($this->config['taglib_begin'], '/'); + $this->config['taglib_end'] = preg_quote($this->config['taglib_end'], '/'); + $this->config['tpl_begin'] = preg_quote($this->config['tpl_begin'], '/'); + $this->config['tpl_end'] = preg_quote($this->config['tpl_end'], '/'); + + // 初始化模板编译存储器 + $type = $this->config['compile_type'] ? $this->config['compile_type'] : 'File'; + $class = false !== strpos($type, '\\') ? $type : '\\think\\template\\driver\\' . ucwords($type); + + $this->storage = new $class(); + } + + /** + * 模板变量赋值 + * @access public + * @param array $vars 模板变量 + * @return $this + */ + public function assign(array $vars = []) + { + $this->data = array_merge($this->data, $vars); + return $this; + } + + /** + * 模板引擎参数赋值 + * @access public + * @param string $name + * @param mixed $value + */ + public function __set($name, $value) + { + $this->config[$name] = $value; + } + + /** + * 设置缓存对象 + * @access public + * @param CacheInterface $cache 缓存对象 + * @return void + */ + public function setCache(CacheInterface $cache): void + { + $this->cache = $cache; + } + + /** + * 模板引擎配置 + * @access public + * @param array $config + * @return $this + */ + public function config(array $config) + { + $this->config = array_merge($this->config, $config); + return $this; + } + + /** + * 获取模板引擎配置项 + * @access public + * @param string $name + * @return mixed + */ + public function getConfig(string $name) + { + return $this->config[$name] ?? null; + } + + /** + * 模板变量获取 + * @access public + * @param string $name 变量名 + * @return mixed + */ + public function get(string $name = '') + { + if ('' == $name) { + return $this->data; + } + + $data = $this->data; + + foreach (explode('.', $name) as $key => $val) { + if (isset($data[$val])) { + $data = $data[$val]; + } else { + $data = null; + break; + } + } + + return $data; + } + + /** + * 扩展模板解析规则 + * @access public + * @param string $rule 解析规则 + * @param callable $callback 解析规则 + * @return void + */ + public function extend(string $rule, callable $callback = null): void + { + $this->extend[$rule] = $callback; + } + + /** + * 渲染模板文件 + * @access public + * @param string $template 模板文件 + * @param array $vars 模板变量 + * @return void + */ + public function fetch(string $template, array $vars = []): void + { + if ($vars) { + $this->data = array_merge($this->data, $vars); + } + + if (!empty($this->config['cache_id']) && $this->config['display_cache'] && $this->cache) { + // 读取渲染缓存 + if ($this->cache->has($this->config['cache_id'])) { + echo $this->cache->get($this->config['cache_id']); + return; + } + } + + $template = $this->parseTemplateFile($template); + + if ($template) { + $cacheFile = $this->config['cache_path'] . $this->config['cache_prefix'] . md5($this->config['layout_on'] . $this->config['layout_name'] . $template) . '.' . ltrim($this->config['cache_suffix'], '.'); + + if (!$this->checkCache($cacheFile)) { + // 缓存无效 重新模板编译 + $content = file_get_contents($template); + $this->compiler($content, $cacheFile); + } + + // 页面缓存 + ob_start(); + if (PHP_VERSION > 8.0) { + ob_implicit_flush(false); + } else { + ob_implicit_flush(0); + } + + // 读取编译存储 + $this->storage->read($cacheFile, $this->data); + + // 获取并清空缓存 + $content = ob_get_clean(); + + if (!empty($this->config['cache_id']) && $this->config['display_cache'] && $this->cache) { + // 缓存页面输出 + $this->cache->set($this->config['cache_id'], $content, $this->config['cache_time']); + } + + echo $content; + } + } + + /** + * 检查编译缓存是否存在 + * @access public + * @param string $cacheId 缓存的id + * @return boolean + */ + public function isCache(string $cacheId): bool + { + if ($cacheId && $this->cache && $this->config['display_cache']) { + // 缓存页面输出 + return $this->cache->has($cacheId); + } + + return false; + } + + /** + * 渲染模板内容 + * @access public + * @param string $content 模板内容 + * @param array $vars 模板变量 + * @return void + */ + public function display(string $content, array $vars = []): void + { + if ($vars) { + $this->data = array_merge($this->data, $vars); + } + + $cacheFile = $this->config['cache_path'] . $this->config['cache_prefix'] . md5($content) . '.' . ltrim($this->config['cache_suffix'], '.'); + + if (!$this->checkCache($cacheFile)) { + // 缓存无效 模板编译 + $this->compiler($content, $cacheFile); + } + + // 读取编译存储 + $this->storage->read($cacheFile, $this->data); + } + + /** + * 设置布局 + * @access public + * @param mixed $name 布局模板名称 false 则关闭布局 + * @param string $replace 布局模板内容替换标识 + * @return $this + */ + public function layout($name, string $replace = '') + { + if (false === $name) { + // 关闭布局 + $this->config['layout_on'] = false; + } else { + // 开启布局 + $this->config['layout_on'] = true; + + // 名称必须为字符串 + if (is_string($name)) { + $this->config['layout_name'] = $name; + } + + if (!empty($replace)) { + $this->config['layout_item'] = $replace; + } + } + + return $this; + } + + /** + * 检查编译缓存是否有效 + * 如果无效则需要重新编译 + * @access private + * @param string $cacheFile 缓存文件名 + * @return bool + */ + private function checkCache(string $cacheFile): bool + { + if (!$this->config['tpl_cache'] || !is_file($cacheFile) || !$handle = @fopen($cacheFile, "r")) { + return false; + } + + // 读取第一行 + $line = fgets($handle); + + if (false === $line) { + return false; + } + + preg_match('/\/\*(.+?)\*\//', $line, $matches); + + if (!isset($matches[1])) { + return false; + } + + $includeFile = unserialize($matches[1]); + + if (!is_array($includeFile)) { + return false; + } + + // 检查模板文件是否有更新 + foreach ($includeFile as $path => $time) { + if (is_file($path) && filemtime($path) > $time) { + // 模板文件如果有更新则缓存需要更新 + return false; + } + } + + // 检查编译存储是否有效 + return $this->storage->check($cacheFile, $this->config['cache_time']); + } + + /** + * 编译模板文件内容 + * @access private + * @param string $content 模板内容 + * @param string $cacheFile 缓存文件名 + * @return void + */ + private function compiler(string &$content, string $cacheFile): void + { + // 判断是否启用布局 + if ($this->config['layout_on']) { + if (false !== strpos($content, '{__NOLAYOUT__}')) { + // 可以单独定义不使用布局 + $content = str_replace('{__NOLAYOUT__}', '', $content); + } else { + // 读取布局模板 + $layoutFile = $this->parseTemplateFile($this->config['layout_name']); + + if ($layoutFile) { + // 替换布局的主体内容 + $content = str_replace($this->config['layout_item'], $content, file_get_contents($layoutFile)); + } + } + } else { + $content = str_replace('{__NOLAYOUT__}', '', $content); + } + + // 模板解析 + $this->parse($content); + + if ($this->config['strip_space']) { + /* 去除html空格与换行 */ + $find = ['~>\s+<~', '~>(\s+\n|\r)~']; + $replace = ['><', '>']; + $content = preg_replace($find, $replace, $content); + } + + // 优化生成的php代码 + $content = preg_replace('/\?>\s*<\?php\s(?!echo\b|\bend)/s', '', $content); + + // 模板过滤输出 + $replace = $this->config['tpl_replace_string']; + $content = str_replace(array_keys($replace), array_values($replace), $content); + + // 添加安全代码及模板引用记录 + $content = 'includeFile) . '*/ ?>' . "\n" . $content; + // 编译存储 + $this->storage->write($cacheFile, $content); + + $this->includeFile = []; + } + + /** + * 模板解析入口 + * 支持普通标签和TagLib解析 支持自定义标签库 + * @access public + * @param string $content 要解析的模板内容 + * @return void + */ + public function parse(string &$content): void + { + // 内容为空不解析 + if (empty($content)) { + return; + } + + // 替换literal标签内容 + $this->parseLiteral($content); + + // 解析继承 + $this->parseExtend($content); + + // 解析布局 + $this->parseLayout($content); + + // 检查include语法 + $this->parseInclude($content); + + // 替换包含文件中literal标签内容 + $this->parseLiteral($content); + + // 检查PHP语法 + $this->parsePhp($content); + + // 获取需要引入的标签库列表 + // 标签库只需要定义一次,允许引入多个一次 + // 一般放在文件的最前面 + // 格式: + // 当TAGLIB_LOAD配置为true时才会进行检测 + if ($this->config['taglib_load']) { + $tagLibs = $this->getIncludeTagLib($content); + + if (!empty($tagLibs)) { + // 对导入的TagLib进行解析 + foreach ($tagLibs as $tagLibName) { + $this->parseTagLib($tagLibName, $content); + } + } + } + + // 预先加载的标签库 无需在每个模板中使用taglib标签加载 但必须使用标签库XML前缀 + if ($this->config['taglib_pre_load']) { + $tagLibs = explode(',', $this->config['taglib_pre_load']); + + foreach ($tagLibs as $tag) { + $this->parseTagLib($tag, $content); + } + } + + // 内置标签库 无需使用taglib标签导入就可以使用 并且不需使用标签库XML前缀 + $tagLibs = explode(',', $this->config['taglib_build_in']); + + foreach ($tagLibs as $tag) { + $this->parseTagLib($tag, $content, true); + } + + // 解析普通模板标签 {$tagName} + $this->parseTag($content); + + // 还原被替换的Literal标签 + $this->parseLiteral($content, true); + } + + /** + * 检查PHP语法 + * @access private + * @param string $content 要解析的模板内容 + * @return void + * @throws Exception + */ + private function parsePhp(string &$content): void + { + // 短标签的情况要将' . "\n", $content); + + // PHP语法检查 + if ($this->config['tpl_deny_php'] && false !== strpos($content, 'getRegex('layout'), $content, $matches)) { + // 替换Layout标签 + $content = str_replace($matches[0], '', $content); + // 解析Layout标签 + $array = $this->parseAttr($matches[0]); + + if (!$this->config['layout_on'] || $this->config['layout_name'] != $array['name']) { + // 读取布局模板 + $layoutFile = $this->parseTemplateFile($array['name']); + + if ($layoutFile) { + $replace = isset($array['replace']) ? $array['replace'] : $this->config['layout_item']; + // 替换布局的主体内容 + $content = str_replace($replace, $content, file_get_contents($layoutFile)); + } + } + } else { + $content = str_replace('{__NOLAYOUT__}', '', $content); + } + } + + /** + * 解析模板中的include标签 + * @access private + * @param string $content 要解析的模板内容 + * @return void + */ + private function parseInclude(string &$content): void + { + $regex = $this->getRegex('include'); + $func = function ($template) use (&$func, &$regex, &$content) { + if (preg_match_all($regex, $template, $matches, PREG_SET_ORDER)) { + foreach ($matches as $match) { + $array = $this->parseAttr($match[0]); + $file = $array['file']; + unset($array['file']); + + // 分析模板文件名并读取内容 + $parseStr = $this->parseTemplateName($file); + + foreach ($array as $k => $v) { + // 以$开头字符串转换成模板变量 + if (0 === strpos($v, '$')) { + $v = $this->get(substr($v, 1)); + } + + $parseStr = str_replace('[' . $k . ']', $v, $parseStr); + } + + $content = str_replace($match[0], $parseStr, $content); + // 再次对包含文件进行模板分析 + $func($parseStr); + } + unset($matches); + } + }; + + // 替换模板中的include标签 + $func($content); + } + + /** + * 解析模板中的extend标签 + * @access private + * @param string $content 要解析的模板内容 + * @return void + */ + private function parseExtend(string &$content): void + { + $regex = $this->getRegex('extend'); + $array = $blocks = $baseBlocks = []; + $extend = ''; + + $func = function ($template) use (&$func, &$regex, &$array, &$extend, &$blocks, &$baseBlocks) { + if (preg_match($regex, $template, $matches)) { + if (!isset($array[$matches['name']])) { + $array[$matches['name']] = 1; + // 读取继承模板 + $extend = $this->parseTemplateName($matches['name']); + + // 递归检查继承 + $func($extend); + + // 取得block标签内容 + $blocks = array_merge($blocks, $this->parseBlock($template)); + + return; + } + } else { + // 取得顶层模板block标签内容 + $baseBlocks = $this->parseBlock($template, true); + + if (empty($extend)) { + // 无extend标签但有block标签的情况 + $extend = $template; + } + } + }; + + $func($content); + + if (!empty($extend)) { + if ($baseBlocks) { + $children = []; + foreach ($baseBlocks as $name => $val) { + $replace = $val['content']; + + if (!empty($children[$name])) { + // 如果包含有子block标签 + foreach ($children[$name] as $key) { + $replace = str_replace($baseBlocks[$key]['begin'] . $baseBlocks[$key]['content'] . $baseBlocks[$key]['end'], $blocks[$key]['content'], $replace); + } + } + + if (isset($blocks[$name])) { + // 带有{__block__}表示与所继承模板的相应标签合并,而不是覆盖 + $replace = str_replace(['{__BLOCK__}', '{__block__}'], $replace, $blocks[$name]['content']); + + if (!empty($val['parent'])) { + // 如果不是最顶层的block标签 + $parent = $val['parent']; + + if (isset($blocks[$parent])) { + $blocks[$parent]['content'] = str_replace($blocks[$name]['begin'] . $blocks[$name]['content'] . $blocks[$name]['end'], $replace, $blocks[$parent]['content']); + } + + $blocks[$name]['content'] = $replace; + $children[$parent][] = $name; + + continue; + } + } elseif (!empty($val['parent'])) { + // 如果子标签没有被继承则用原值 + $children[$val['parent']][] = $name; + $blocks[$name] = $val; + } + + if (!$val['parent']) { + // 替换模板中的顶级block标签 + $extend = str_replace($val['begin'] . $val['content'] . $val['end'], $replace, $extend); + } + } + } + + $content = $extend; + unset($blocks, $baseBlocks); + } + } + + /** + * 替换页面中的literal标签 + * @access private + * @param string $content 模板内容 + * @param boolean $restore 是否为还原 + * @return void + */ + private function parseLiteral(string &$content, bool $restore = false): void + { + $regex = $this->getRegex($restore ? 'restoreliteral' : 'literal'); + + if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) { + if (!$restore) { + $count = count($this->literal); + + // 替换literal标签 + foreach ($matches as $match) { + $this->literal[] = substr($match[0], strlen($match[1]), -strlen($match[2])); + $content = str_replace($match[0], "", $content); + $count++; + } + } else { + // 还原literal标签 + foreach ($matches as $match) { + $content = str_replace($match[0], $this->literal[$match[1]], $content); + } + + // 清空literal记录 + $this->literal = []; + } + + unset($matches); + } + } + + /** + * 获取模板中的block标签 + * @access private + * @param string $content 模板内容 + * @param boolean $sort 是否排序 + * @return array + */ + private function parseBlock(string &$content, bool $sort = false): array + { + $regex = $this->getRegex('block'); + $result = []; + + if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) { + $right = $keys = []; + + foreach ($matches as $match) { + if (empty($match['name'][0])) { + if (count($right) > 0) { + $tag = array_pop($right); + $start = $tag['offset'] + strlen($tag['tag']); + $length = $match[0][1] - $start; + + $result[$tag['name']] = [ + 'begin' => $tag['tag'], + 'content' => substr($content, $start, $length), + 'end' => $match[0][0], + 'parent' => count($right) ? end($right)['name'] : '', + ]; + + $keys[$tag['name']] = $match[0][1]; + } + } else { + // 标签头压入栈 + $right[] = [ + 'name' => $match[2][0], + 'offset' => $match[0][1], + 'tag' => $match[0][0], + ]; + } + } + + unset($right, $matches); + + if ($sort) { + // 按block标签结束符在模板中的位置排序 + array_multisort($keys, $result); + } + } + + return $result; + } + + /** + * 搜索模板页面中包含的TagLib库 + * 并返回列表 + * @access private + * @param string $content 模板内容 + * @return array|null + */ + private function getIncludeTagLib(string &$content) + { + // 搜索是否有TagLib标签 + if (preg_match($this->getRegex('taglib'), $content, $matches)) { + // 替换TagLib标签 + $content = str_replace($matches[0], '', $content); + + return explode(',', $matches['name']); + } + } + + /** + * TagLib库解析 + * @access public + * @param string $tagLib 要解析的标签库 + * @param string $content 要解析的模板内容 + * @param boolean $hide 是否隐藏标签库前缀 + * @return void + */ + public function parseTagLib(string $tagLib, string &$content, bool $hide = false): void + { + if (false !== strpos($tagLib, '\\')) { + // 支持指定标签库的命名空间 + $className = $tagLib; + $tagLib = substr($tagLib, strrpos($tagLib, '\\') + 1); + } else { + $className = '\\think\\template\\taglib\\' . ucwords($tagLib); + } + + $tLib = new $className($this); + + $tLib->parseTag($content, $hide ? '' : $tagLib); + } + + /** + * 分析标签属性 + * @access public + * @param string $str 属性字符串 + * @param string $name 不为空时返回指定的属性名 + * @return array + */ + public function parseAttr(string $str, string $name = null): array + { + $regex = '/\s+(?>(?P[\w-]+)\s*)=(?>\s*)([\"\'])(?P(?:(?!\\2).)*)\\2/is'; + $array = []; + + if (preg_match_all($regex, $str, $matches, PREG_SET_ORDER)) { + foreach ($matches as $match) { + $array[$match['name']] = $match['value']; + } + unset($matches); + } + + if (!empty($name) && isset($array[$name])) { + return $array[$name]; + } + + return $array; + } + + /** + * 模板标签解析 + * 格式: {TagName:args [|content] } + * @access private + * @param string $content 要解析的模板内容 + * @return void + */ + private function parseTag(string &$content): void + { + $regex = $this->getRegex('tag'); + + if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) { + foreach ($matches as $match) { + $str = stripslashes($match[1]); + $flag = substr($str, 0, 1); + + switch ($flag) { + case '$': + // 解析模板变量 格式 {$varName} + // 是否带有?号 + if (false !== $pos = strpos($str, '?')) { + $array = preg_split('/([!=]={1,2}|(?<]={0,1})/', substr($str, 0, $pos), 2, PREG_SPLIT_DELIM_CAPTURE); + $name = $array[0]; + + $this->parseVar($name); + //$this->parseVarFunction($name); + + $str = trim(substr($str, $pos + 1)); + $this->parseVar($str); + $first = substr($str, 0, 1); + + if (strpos($name, ')')) { + // $name为对象或是自动识别,或者含有函数 + if (isset($array[1])) { + $this->parseVar($array[2]); + $name .= $array[1] . $array[2]; + } + + switch ($first) { + case '?': + $this->parseVarFunction($name); + $str = ''; + break; + case '=': + $str = ''; + break; + default: + $str = ''; + } + } else { + if (isset($array[1])) { + $express = true; + $this->parseVar($array[2]); + $express = $name . $array[1] . $array[2]; + } else { + $express = false; + } + + if (in_array($first, ['?', '=', ':'])) { + $str = trim(substr($str, 1)); + if ('$' == substr($str, 0, 1)) { + $str = $this->parseVarFunction($str); + } + } + + // $name为数组 + switch ($first) { + case '?': + // {$varname??'xxx'} $varname有定义则输出$varname,否则输出xxx + $str = 'parseVarFunction($name) . ' : ' . $str . '; ?>'; + break; + case '=': + // {$varname?='xxx'} $varname为真时才输出xxx + $str = ''; + break; + case ':': + // {$varname?:'xxx'} $varname为真时输出$varname,否则输出xxx + $str = 'parseVarFunction($name) . ' : ' . $str . '; ?>'; + break; + default: + if (strpos($str, ':')) { + // {$varname ? 'a' : 'b'} $varname为真时输出a,否则输出b + $array = explode(':', $str, 2); + + $array[0] = '$' == substr(trim($array[0]), 0, 1) ? $this->parseVarFunction($array[0]) : $array[0]; + $array[1] = '$' == substr(trim($array[1]), 0, 1) ? $this->parseVarFunction($array[1]) : $array[1]; + + $str = implode(' : ', $array); + } + $str = ''; + } + } + } else { + $this->parseVar($str); + $this->parseVarFunction($str); + $str = ''; + } + break; + case ':': + // 输出某个函数的结果 + $str = substr($str, 1); + $this->parseVar($str); + $str = ''; + break; + case '~': + // 执行某个函数 + $str = substr($str, 1); + $this->parseVar($str); + $str = ''; + break; + case '-': + case '+': + // 输出计算 + $this->parseVar($str); + $str = ''; + break; + case '/': + // 注释标签 + $flag2 = substr($str, 1, 1); + if ('/' == $flag2 || ('*' == $flag2 && substr(rtrim($str), -2) == '*/')) { + $str = ''; + } + break; + default: + // 未识别的标签直接返回 + $str = $this->config['tpl_begin'] . $str . $this->config['tpl_end']; + break; + } + + $content = str_replace($match[0], $str, $content); + } + + unset($matches); + } + } + + /** + * 模板变量解析,支持使用函数 + * 格式: {$varname|function1|function2=arg1,arg2} + * @access public + * @param string $varStr 变量数据 + * @return void + */ + public function parseVar(string &$varStr): void + { + $varStr = trim($varStr); + + if (preg_match_all('/\$[a-zA-Z_](?>\w*)(?:[:\.][0-9a-zA-Z_](?>\w*))+/', $varStr, $matches, PREG_OFFSET_CAPTURE)) { + static $_varParseList = []; + + while ($matches[0]) { + $match = array_pop($matches[0]); + + //如果已经解析过该变量字串,则直接返回变量值 + if (isset($_varParseList[$match[0]])) { + $parseStr = $_varParseList[$match[0]]; + } else { + if (strpos($match[0], '.')) { + $vars = explode('.', $match[0]); + $first = array_shift($vars); + + if (isset($this->extend[$first])) { + $callback = $this->extend[$first]; + $parseStr = $callback($vars); + } elseif ('$Request' == $first) { + // 输出请求变量 + $parseStr = $this->parseRequestVar($vars); + } elseif ('$Think' == $first) { + // 所有以Think.打头的以特殊变量对待 无需模板赋值就可以输出 + $parseStr = $this->parseThinkVar($vars); + } else { + switch ($this->config['tpl_var_identify']) { + case 'array': // 识别为数组 + $parseStr = $first . '[\'' . implode('\'][\'', $vars) . '\']'; + break; + case 'obj': // 识别为对象 + $parseStr = $first . '->' . implode('->', $vars); + break; + default: // 自动判断数组或对象 + $parseStr = '(is_array(' . $first . ')?' . $first . '[\'' . implode('\'][\'', $vars) . '\']:' . $first . '->' . implode('->', $vars) . ')'; + } + } + } else { + $parseStr = str_replace(':', '->', $match[0]); + } + + $_varParseList[$match[0]] = $parseStr; + } + + $varStr = substr_replace($varStr, $parseStr, $match[1], strlen($match[0])); + } + unset($matches); + } + } + + /** + * 对模板中使用了函数的变量进行解析 + * 格式 {$varname|function1|function2=arg1,arg2} + * @access public + * @param string $varStr 变量字符串 + * @param bool $autoescape 自动转义 + * @return string + */ + public function parseVarFunction(string &$varStr, bool $autoescape = true): string + { + if (!$autoescape && false === strpos($varStr, '|')) { + return $varStr; + } elseif ($autoescape && !preg_match('/\|(\s)?raw(\||\s)?/i', $varStr)) { + $varStr .= '|' . $this->config['default_filter']; + } + + static $_varFunctionList = []; + + $_key = md5($varStr); + + //如果已经解析过该变量字串,则直接返回变量值 + if (isset($_varFunctionList[$_key])) { + $varStr = $_varFunctionList[$_key]; + } else { + $varArray = explode('|', $varStr); + + // 取得变量名称 + $name = trim(array_shift($varArray)); + + // 对变量使用函数 + $length = count($varArray); + + // 取得模板禁止使用函数列表 + $template_deny_funs = explode(',', $this->config['tpl_deny_func_list']); + + for ($i = 0; $i < $length; $i++) { + $args = explode('=', $varArray[$i], 2); + + // 模板函数过滤 + $fun = trim($args[0]); + if (in_array($fun, $template_deny_funs)) { + continue; + } + + switch (strtolower($fun)) { + case 'raw': + break; + case 'date': + $name = 'date(' . $args[1] . ',!is_numeric(' . $name . ')? strtotime(' . $name . ') : ' . $name . ')'; + break; + case 'first': + $name = 'current(' . $name . ')'; + break; + case 'last': + $name = 'end(' . $name . ')'; + break; + case 'upper': + $name = 'strtoupper(' . $name . ')'; + break; + case 'lower': + $name = 'strtolower(' . $name . ')'; + break; + case 'format': + $name = 'sprintf(' . $args[1] . ',' . $name . ')'; + break; + case 'default': // 特殊模板函数 + if (false === strpos($name, '(')) { + $name = '(isset(' . $name . ') && (' . $name . ' !== \'\')?' . $name . ':' . $args[1] . ')'; + } else { + $name = '(' . $name . ' ?: ' . $args[1] . ')'; + } + break; + default: // 通用模板函数 + if (isset($args[1])) { + if (strstr($args[1], '###')) { + $args[1] = str_replace('###', $name, $args[1]); + $name = "$fun($args[1])"; + } else { + $name = "$fun($name,$args[1])"; + } + } else { + if (!empty($args[0])) { + $name = "$fun($name)"; + } + } + } + } + + $_varFunctionList[$_key] = $name; + $varStr = $name; + } + return $varStr; + } + + /** + * 请求变量解析 + * 格式 以 $Request. 打头的变量属于请求变量 + * @access public + * @param array $vars 变量数组 + * @return string + */ + public function parseRequestVar(array $vars): string + { + $type = strtoupper(trim(array_shift($vars))); + $param = implode('.', $vars); + + switch ($type) { + case 'SERVER': + $parseStr = '$_SERVER[\'' . $param . '\']'; + break; + case 'GET': + $parseStr = '$_GET[\'' . $param . '\']'; + break; + case 'POST': + $parseStr = '$_POST[\'' . $param . '\']'; + break; + case 'COOKIE': + $parseStr = '$_COOKIE[\'' . $param . '\']'; + break; + case 'SESSION': + $parseStr = '$_SESSION[\'' . $param . '\']'; + break; + case 'ENV': + $parseStr = '$_ENV[\'' . $param . '\']'; + break; + case 'REQUEST': + $parseStr = '$_REQUEST[\'' . $param . '\']'; + break; + default: + $parseStr = '\'\''; + } + + return $parseStr; + } + + /** + * 特殊模板变量解析 + * 格式 以 $Think. 打头的变量属于特殊模板变量 + * @access public + * @param array $vars 变量数组 + * @return string + */ + public function parseThinkVar(array $vars): string + { + $type = strtoupper(trim(array_shift($vars))); + $param = implode('.', $vars); + + switch ($type) { + case 'CONST': + $parseStr = strtoupper($param); + break; + case 'NOW': + $parseStr = "date('Y-m-d g:i a',time())"; + break; + case 'LDELIM': + $parseStr = '\'' . ltrim($this->config['tpl_begin'], '\\') . '\''; + break; + case 'RDELIM': + $parseStr = '\'' . ltrim($this->config['tpl_end'], '\\') . '\''; + break; + default: + $parseStr = defined($type) ? $type : '\'\''; + } + + return $parseStr; + } + + /** + * 分析加载的模板文件并读取内容 支持多个模板文件读取 + * @access private + * @param string $templateName 模板文件名 + * @return string + */ + private function parseTemplateName(string $templateName): string + { + $array = explode(',', $templateName); + $parseStr = ''; + + foreach ($array as $templateName) { + if (empty($templateName)) { + continue; + } + + if (0 === strpos($templateName, '$')) { + //支持加载变量文件名 + $templateName = $this->get(substr($templateName, 1)); + } + + $template = $this->parseTemplateFile($templateName); + + if ($template) { + // 获取模板文件内容 + $parseStr .= file_get_contents($template); + } + } + + return $parseStr; + } + + /** + * 解析模板文件名 + * @access private + * @param string $template 文件名 + * @return string + */ + private function parseTemplateFile(string $template): string + { + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + + if (0 !== strpos($template, '/')) { + $template = str_replace(['/', ':'], $this->config['view_depr'], $template); + } else { + $template = str_replace(['/', ':'], $this->config['view_depr'], substr($template, 1)); + } + + $template = $this->config['view_path'] . $template . '.' . ltrim($this->config['view_suffix'], '.'); + } + + if (is_file($template)) { + // 记录模板文件的更新时间 + $this->includeFile[$template] = filemtime($template); + + return $template; + } + + throw new Exception('template not exists:' . $template); + } + + /** + * 按标签生成正则 + * @access private + * @param string $tagName 标签名 + * @return string + */ + private function getRegex(string $tagName): string + { + $regex = ''; + if ('tag' == $tagName) { + $begin = $this->config['tpl_begin']; + $end = $this->config['tpl_end']; + + if (strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1) { + $regex = $begin . '((?:[\$]{1,2}[a-wA-w_]|[\:\~][\$a-wA-w_]|[+]{2}[\$][a-wA-w_]|[-]{2}[\$][a-wA-w_]|\/[\*\/])(?>[^' . $end . ']*))' . $end; + } else { + $regex = $begin . '((?:[\$]{1,2}[a-wA-w_]|[\:\~][\$a-wA-w_]|[+]{2}[\$][a-wA-w_]|[-]{2}[\$][a-wA-w_]|\/[\*\/])(?>(?:(?!' . $end . ').)*))' . $end; + } + } else { + $begin = $this->config['taglib_begin']; + $end = $this->config['taglib_end']; + $single = strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1 ? true : false; + + switch ($tagName) { + case 'block': + if ($single) { + $regex = $begin . '(?:' . $tagName . '\b\s+(?>(?:(?!name=).)*)\bname=([\'\"])(?P[\$\w\-\/\.]+)\\1(?>[^' . $end . ']*)|\/' . $tagName . ')' . $end; + } else { + $regex = $begin . '(?:' . $tagName . '\b\s+(?>(?:(?!name=).)*)\bname=([\'\"])(?P[\$\w\-\/\.]+)\\1(?>(?:(?!' . $end . ').)*)|\/' . $tagName . ')' . $end; + } + break; + case 'literal': + if ($single) { + $regex = '(' . $begin . $tagName . '\b(?>[^' . $end . ']*)' . $end . ')'; + $regex .= '(?:(?>[^' . $begin . ']*)(?>(?!' . $begin . '(?>' . $tagName . '\b[^' . $end . ']*|\/' . $tagName . ')' . $end . ')' . $begin . '[^' . $begin . ']*)*)'; + $regex .= '(' . $begin . '\/' . $tagName . $end . ')'; + } else { + $regex = '(' . $begin . $tagName . '\b(?>(?:(?!' . $end . ').)*)' . $end . ')'; + $regex .= '(?:(?>(?:(?!' . $begin . ').)*)(?>(?!' . $begin . '(?>' . $tagName . '\b(?>(?:(?!' . $end . ').)*)|\/' . $tagName . ')' . $end . ')' . $begin . '(?>(?:(?!' . $begin . ').)*))*)'; + $regex .= '(' . $begin . '\/' . $tagName . $end . ')'; + } + break; + case 'restoreliteral': + $regex = ''; + break; + case 'include': + $name = 'file'; + case 'taglib': + case 'layout': + case 'extend': + if (empty($name)) { + $name = 'name'; + } + if ($single) { + $regex = $begin . $tagName . '\b\s+(?>(?:(?!' . $name . '=).)*)\b' . $name . '=([\'\"])(?P[\$\w\-\/\.\:@,\\\\]+)\\1(?>[^' . $end . ']*)' . $end; + } else { + $regex = $begin . $tagName . '\b\s+(?>(?:(?!' . $name . '=).)*)\b' . $name . '=([\'\"])(?P[\$\w\-\/\.\:@,\\\\]+)\\1(?>(?:(?!' . $end . ').)*)' . $end; + } + break; + } + } + + return '/' . $regex . '/is'; + } + + public function __debugInfo() + { + $data = get_object_vars($this); + unset($data['storage']); + + return $data; + } +} diff --git a/vendor/topthink/think-template/src/facade/Template.php b/vendor/topthink/think-template/src/facade/Template.php new file mode 100644 index 0000000..665a180 --- /dev/null +++ b/vendor/topthink/think-template/src/facade/Template.php @@ -0,0 +1,83 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +if (class_exists('think\Facade')) { + class Facade extends \think\Facade + {} +} else { + class Facade + { + /** + * 始终创建新的对象实例 + * @var bool + */ + protected static $alwaysNewInstance; + + protected static $instance; + + /** + * 获取当前Facade对应类名 + * @access protected + * @return string + */ + protected static function getFacadeClass() + {} + + /** + * 创建Facade实例 + * @static + * @access protected + * @return object + */ + protected static function createFacade() + { + $class = static::getFacadeClass() ?: 'think\Template'; + + if (static::$alwaysNewInstance) { + return new $class(); + } + + if (!self::$instance) { + self::$instance = new $class(); + } + + return self::$instance; + + } + + // 调用实际类的方法 + public static function __callStatic($method, $params) + { + return call_user_func_array([static::createFacade(), $method], $params); + } + } +} + +/** + * @see \think\Template + * @mixin \think\Template + */ +class Template extends Facade +{ + protected static $alwaysNewInstance = true; + + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'think\Template'; + } +} diff --git a/vendor/topthink/think-template/src/template/TagLib.php b/vendor/topthink/think-template/src/template/TagLib.php new file mode 100644 index 0000000..f6c8fbb --- /dev/null +++ b/vendor/topthink/think-template/src/template/TagLib.php @@ -0,0 +1,349 @@ + +// +---------------------------------------------------------------------- + +namespace think\template; + +use Exception; +use think\Template; + +/** + * ThinkPHP标签库TagLib解析基类 + * @category Think + * @package Think + * @subpackage Template + * @author liu21st + */ +class TagLib +{ + + /** + * 标签库定义XML文件 + * @var string + * @access protected + */ + protected $xml = ''; + protected $tags = []; // 标签定义 + /** + * 标签库名称 + * @var string + * @access protected + */ + protected $tagLib = ''; + + /** + * 标签库标签列表 + * @var array + * @access protected + */ + protected $tagList = []; + + /** + * 标签库分析数组 + * @var array + * @access protected + */ + protected $parse = []; + + /** + * 标签库是否有效 + * @var bool + * @access protected + */ + protected $valid = false; + + /** + * 当前模板对象 + * @var object + * @access protected + */ + protected $tpl; + + protected $comparison = [' nheq ' => ' !== ', ' heq ' => ' === ', ' neq ' => ' != ', ' eq ' => ' == ', ' egt ' => ' >= ', ' gt ' => ' > ', ' elt ' => ' <= ', ' lt ' => ' < ']; + + /** + * 架构函数 + * @access public + * @param Template $template 模板引擎对象 + */ + public function __construct(Template $template) + { + $this->tpl = $template; + } + + /** + * 按签标库替换页面中的标签 + * @access public + * @param string $content 模板内容 + * @param string $lib 标签库名 + * @return void + */ + public function parseTag(string &$content, string $lib = ''): void + { + $tags = []; + $lib = $lib ? strtolower($lib) . ':' : ''; + + foreach ($this->tags as $name => $val) { + $close = !isset($val['close']) || $val['close'] ? 1 : 0; + $tags[$close][$lib . $name] = $name; + if (isset($val['alias'])) { + // 别名设置 + $array = (array) $val['alias']; + foreach (explode(',', $array[0]) as $v) { + $tags[$close][$lib . $v] = $name; + } + } + } + + // 闭合标签 + if (!empty($tags[1])) { + $nodes = []; + $regex = $this->getRegex(array_keys($tags[1]), 1); + if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) { + $right = []; + foreach ($matches as $match) { + if ('' == $match[1][0]) { + $name = strtolower($match[2][0]); + // 如果有没闭合的标签头则取出最后一个 + if (!empty($right[$name])) { + // $match[0][1]为标签结束符在模板中的位置 + $nodes[$match[0][1]] = [ + 'name' => $name, + 'begin' => array_pop($right[$name]), // 标签开始符 + 'end' => $match[0], // 标签结束符 + ]; + } + } else { + // 标签头压入栈 + $right[strtolower($match[1][0])][] = $match[0]; + } + } + unset($right, $matches); + // 按标签在模板中的位置从后向前排序 + krsort($nodes); + } + + $break = ''; + if ($nodes) { + $beginArray = []; + // 标签替换 从后向前 + foreach ($nodes as $pos => $node) { + // 对应的标签名 + $name = $tags[1][$node['name']]; + $alias = $lib . $name != $node['name'] ? ($lib ? strstr($node['name'], $lib) : $node['name']) : ''; + + // 解析标签属性 + $attrs = $this->parseAttr($node['begin'][0], $name, $alias); + $method = 'tag' . $name; + + // 读取标签库中对应的标签内容 replace[0]用来替换标签头,replace[1]用来替换标签尾 + $replace = explode($break, $this->$method($attrs, $break)); + + if (count($replace) > 1) { + while ($beginArray) { + $begin = end($beginArray); + // 判断当前标签尾的位置是否在栈中最后一个标签头的后面,是则为子标签 + if ($node['end'][1] > $begin['pos']) { + break; + } else { + // 不为子标签时,取出栈中最后一个标签头 + $begin = array_pop($beginArray); + // 替换标签头部 + $content = substr_replace($content, $begin['str'], $begin['pos'], $begin['len']); + } + } + // 替换标签尾部 + $content = substr_replace($content, $replace[1], $node['end'][1], strlen($node['end'][0])); + // 把标签头压入栈 + $beginArray[] = ['pos' => $node['begin'][1], 'len' => strlen($node['begin'][0]), 'str' => $replace[0]]; + } + } + + while ($beginArray) { + $begin = array_pop($beginArray); + // 替换标签头部 + $content = substr_replace($content, $begin['str'], $begin['pos'], $begin['len']); + } + } + } + // 自闭合标签 + if (!empty($tags[0])) { + $regex = $this->getRegex(array_keys($tags[0]), 0); + $content = preg_replace_callback($regex, function ($matches) use (&$tags, &$lib) { + // 对应的标签名 + $name = $tags[0][strtolower($matches[1])]; + $alias = $lib . $name != $matches[1] ? ($lib ? strstr($matches[1], $lib) : $matches[1]) : ''; + // 解析标签属性 + $attrs = $this->parseAttr($matches[0], $name, $alias); + $method = 'tag' . $name; + return $this->$method($attrs, ''); + }, $content); + } + } + + /** + * 按标签生成正则 + * @access public + * @param array|string $tags 标签名 + * @param boolean $close 是否为闭合标签 + * @return string + */ + public function getRegex($tags, bool $close): string + { + $begin = $this->tpl->getConfig('taglib_begin'); + $end = $this->tpl->getConfig('taglib_end'); + $single = strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1 ? true : false; + $tagName = is_array($tags) ? implode('|', $tags) : $tags; + + if ($single) { + if ($close) { + // 如果是闭合标签 + $regex = $begin . '(?:(' . $tagName . ')\b(?>[^' . $end . ']*)|\/(' . $tagName . '))' . $end; + } else { + $regex = $begin . '(' . $tagName . ')\b(?>[^' . $end . ']*)' . $end; + } + } else { + if ($close) { + // 如果是闭合标签 + $regex = $begin . '(?:(' . $tagName . ')\b(?>(?:(?!' . $end . ').)*)|\/(' . $tagName . '))' . $end; + } else { + $regex = $begin . '(' . $tagName . ')\b(?>(?:(?!' . $end . ').)*)' . $end; + } + } + + return '/' . $regex . '/is'; + } + + /** + * 分析标签属性 正则方式 + * @access public + * @param string $str 标签属性字符串 + * @param string $name 标签名 + * @param string $alias 别名 + * @return array + */ + public function parseAttr(string $str, string $name, string $alias = ''): array + { + $regex = '/\s+(?>(?P[\w-]+)\s*)=(?>\s*)([\"\'])(?P(?:(?!\\2).)*)\\2/is'; + $result = []; + + if (preg_match_all($regex, $str, $matches)) { + foreach ($matches['name'] as $key => $val) { + $result[$val] = $matches['value'][$key]; + } + + if (!isset($this->tags[$name])) { + // 检测是否存在别名定义 + foreach ($this->tags as $key => $val) { + if (isset($val['alias'])) { + $array = (array) $val['alias']; + if (in_array($name, explode(',', $array[0]))) { + $tag = $val; + $type = !empty($array[1]) ? $array[1] : 'type'; + $result[$type] = $name; + break; + } + } + } + } else { + $tag = $this->tags[$name]; + // 设置了标签别名 + if (!empty($alias) && isset($tag['alias'])) { + $type = !empty($tag['alias'][1]) ? $tag['alias'][1] : 'type'; + $result[$type] = $alias; + } + } + + if (!empty($tag['must'])) { + $must = explode(',', $tag['must']); + foreach ($must as $name) { + if (!isset($result[$name])) { + throw new Exception('tag attr must:' . $name); + } + } + } + } else { + // 允许直接使用表达式的标签 + if (!empty($this->tags[$name]['expression'])) { + static $_taglibs; + if (!isset($_taglibs[$name])) { + $_taglibs[$name][0] = strlen($this->tpl->getConfig('taglib_begin_origin') . $name); + $_taglibs[$name][1] = strlen($this->tpl->getConfig('taglib_end_origin')); + } + $result['expression'] = substr($str, $_taglibs[$name][0], -$_taglibs[$name][1]); + // 清除自闭合标签尾部/ + $result['expression'] = rtrim($result['expression'], '/'); + $result['expression'] = trim($result['expression']); + } elseif (empty($this->tags[$name]) || !empty($this->tags[$name]['attr'])) { + throw new Exception('tag error:' . $name); + } + } + + return $result; + } + + /** + * 解析条件表达式 + * @access public + * @param string $condition 表达式标签内容 + * @return string + */ + public function parseCondition(string $condition): string + { + if (strpos($condition, ':')) { + $condition = ' ' . substr(strstr($condition, ':'), 1); + } + + $condition = str_ireplace(array_keys($this->comparison), array_values($this->comparison), $condition); + $this->tpl->parseVar($condition); + + return $condition; + } + + /** + * 自动识别构建变量 + * @access public + * @param string $name 变量描述 + * @return string + */ + public function autoBuildVar(string &$name): string + { + $flag = substr($name, 0, 1); + + if (':' == $flag) { + // 以:开头为函数调用,解析前去掉: + $name = substr($name, 1); + } elseif ('$' != $flag && preg_match('/[a-zA-Z_]/', $flag)) { + // XXX: 这句的写法可能还需要改进 + // 常量不需要解析 + if (defined($name)) { + return $name; + } + + // 不以$开头并且也不是常量,自动补上$前缀 + $name = '$' . $name; + } + + $this->tpl->parseVar($name); + $this->tpl->parseVarFunction($name, false); + + return $name; + } + + /** + * 获取标签列表 + * @access public + * @return array + */ + public function getTags(): array + { + return $this->tags; + } +} diff --git a/vendor/topthink/think-template/src/template/driver/File.php b/vendor/topthink/think-template/src/template/driver/File.php new file mode 100644 index 0000000..510d10a --- /dev/null +++ b/vendor/topthink/think-template/src/template/driver/File.php @@ -0,0 +1,83 @@ + +// +---------------------------------------------------------------------- + +namespace think\template\driver; + +use Exception; + +class File +{ + protected $cacheFile; + + /** + * 写入编译缓存 + * @access public + * @param string $cacheFile 缓存的文件名 + * @param string $content 缓存的内容 + * @return void + */ + public function write(string $cacheFile, string $content): void + { + // 检测模板目录 + $dir = dirname($cacheFile); + + if (!is_dir($dir)) { + mkdir($dir, 0755, true); + } + + // 生成模板缓存文件 + if (false === file_put_contents($cacheFile, $content)) { + throw new Exception('cache write error:' . $cacheFile, 11602); + } + } + + /** + * 读取编译编译 + * @access public + * @param string $cacheFile 缓存的文件名 + * @param array $vars 变量数组 + * @return void + */ + public function read(string $cacheFile, array $vars = []): void + { + $this->cacheFile = $cacheFile; + + if (!empty($vars) && is_array($vars)) { + // 模板阵列变量分解成为独立变量 + extract($vars, EXTR_OVERWRITE); + } + + //载入模版缓存文件 + include $this->cacheFile; + } + + /** + * 检查编译缓存是否有效 + * @access public + * @param string $cacheFile 缓存的文件名 + * @param int $cacheTime 缓存时间 + * @return bool + */ + public function check(string $cacheFile, int $cacheTime): bool + { + // 缓存文件不存在, 直接返回false + if (!file_exists($cacheFile)) { + return false; + } + + if (0 != $cacheTime && time() > filemtime($cacheFile) + $cacheTime) { + // 缓存是否在有效期 + return false; + } + + return true; + } +} diff --git a/vendor/topthink/think-template/src/template/exception/TemplateNotFoundException.php b/vendor/topthink/think-template/src/template/exception/TemplateNotFoundException.php new file mode 100644 index 0000000..dd88b32 --- /dev/null +++ b/vendor/topthink/think-template/src/template/exception/TemplateNotFoundException.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- + +namespace think\template\exception; + +class TemplateNotFoundException extends \RuntimeException +{ + protected $template; + + public function __construct(string $message, string $template = '') + { + $this->message = $message; + $this->template = $template; + } + + /** + * 获取模板文件 + * @access public + * @return string + */ + public function getTemplate(): string + { + return $this->template; + } +} diff --git a/vendor/topthink/think-template/src/template/taglib/Cx.php b/vendor/topthink/think-template/src/template/taglib/Cx.php new file mode 100644 index 0000000..bccafc1 --- /dev/null +++ b/vendor/topthink/think-template/src/template/taglib/Cx.php @@ -0,0 +1,715 @@ + +// +---------------------------------------------------------------------- + +namespace think\template\taglib; + +use think\template\TagLib; + +/** + * CX标签库解析类 + * @category Think + * @package Think + * @subpackage Driver.Taglib + * @author liu21st + */ +class Cx extends Taglib +{ + + // 标签定义 + protected $tags = [ + // 标签定义: attr 属性列表 close 是否闭合(0 或者1 默认1) alias 标签别名 level 嵌套层次 + 'php' => ['attr' => ''], + 'volist' => ['attr' => 'name,id,offset,length,key,mod', 'alias' => 'iterate'], + 'foreach' => ['attr' => 'name,id,item,key,offset,length,mod', 'expression' => true], + 'if' => ['attr' => 'condition', 'expression' => true], + 'elseif' => ['attr' => 'condition', 'close' => 0, 'expression' => true], + 'else' => ['attr' => '', 'close' => 0], + 'switch' => ['attr' => 'name', 'expression' => true], + 'case' => ['attr' => 'value,break', 'expression' => true], + 'default' => ['attr' => '', 'close' => 0], + 'compare' => ['attr' => 'name,value,type', 'alias' => ['eq,equal,notequal,neq,gt,lt,egt,elt,heq,nheq', 'type']], + 'range' => ['attr' => 'name,value,type', 'alias' => ['in,notin,between,notbetween', 'type']], + 'empty' => ['attr' => 'name'], + 'notempty' => ['attr' => 'name'], + 'present' => ['attr' => 'name'], + 'notpresent' => ['attr' => 'name'], + 'defined' => ['attr' => 'name'], + 'notdefined' => ['attr' => 'name'], + 'load' => ['attr' => 'file,href,type,value,basepath', 'close' => 0, 'alias' => ['import,css,js', 'type']], + 'assign' => ['attr' => 'name,value', 'close' => 0], + 'define' => ['attr' => 'name,value', 'close' => 0], + 'for' => ['attr' => 'start,end,name,comparison,step'], + 'url' => ['attr' => 'link,vars,suffix,domain', 'close' => 0, 'expression' => true], + 'function' => ['attr' => 'name,vars,use,call'], + ]; + + /** + * php标签解析 + * 格式: + * {php}echo $name{/php} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagPhp(array $tag, string $content): string + { + $parseStr = ''; + return $parseStr; + } + + /** + * volist标签解析 循环输出数据集 + * 格式: + * {volist name="userList" id="user" empty=""} + * {user.username} + * {user.email} + * {/volist} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagVolist(array $tag, string $content): string + { + $name = $tag['name']; + $id = $tag['id']; + $empty = isset($tag['empty']) ? $tag['empty'] : ''; + $key = !empty($tag['key']) ? $tag['key'] : 'i'; + $mod = isset($tag['mod']) ? $tag['mod'] : '2'; + $offset = !empty($tag['offset']) && is_numeric($tag['offset']) ? intval($tag['offset']) : 0; + $length = !empty($tag['length']) && is_numeric($tag['length']) ? intval($tag['length']) : 'null'; + // 允许使用函数设定数据集 {$vo.name} + $parseStr = 'autoBuildVar($name); + $parseStr .= '$_result=' . $name . ';'; + $name = '$_result'; + } else { + $name = $this->autoBuildVar($name); + } + + $parseStr .= 'if(is_array(' . $name . ') || ' . $name . ' instanceof \think\Collection || ' . $name . ' instanceof \think\Paginator): $' . $key . ' = 0;'; + + // 设置了输出数组长度 + if (0 != $offset || 'null' != $length) { + $parseStr .= '$__LIST__ = is_array(' . $name . ') ? array_slice(' . $name . ',' . $offset . ',' . $length . ', true) : ' . $name . '->slice(' . $offset . ',' . $length . ', true); '; + } else { + $parseStr .= ' $__LIST__ = ' . $name . ';'; + } + + $parseStr .= 'if( count($__LIST__)==0 ) : echo "' . $empty . '" ;'; + $parseStr .= 'else: '; + $parseStr .= 'foreach($__LIST__ as $key=>$' . $id . '): '; + $parseStr .= '$mod = ($' . $key . ' % ' . $mod . ' );'; + $parseStr .= '++$' . $key . ';?>'; + $parseStr .= $content; + $parseStr .= ''; + + return $parseStr; + } + + /** + * foreach标签解析 循环输出数据集 + * 格式: + * {foreach name="userList" id="user" key="key" index="i" mod="2" offset="3" length="5" empty=""} + * {user.username} + * {/foreach} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagForeach(array $tag, string $content): string + { + // 直接使用表达式 + if (!empty($tag['expression'])) { + $expression = ltrim(rtrim($tag['expression'], ')'), '('); + $expression = $this->autoBuildVar($expression); + $parseStr = ''; + $parseStr .= $content; + $parseStr .= ''; + return $parseStr; + } + + $name = $tag['name']; + $key = !empty($tag['key']) ? $tag['key'] : 'key'; + $item = !empty($tag['id']) ? $tag['id'] : $tag['item']; + $empty = isset($tag['empty']) ? $tag['empty'] : ''; + $offset = !empty($tag['offset']) && is_numeric($tag['offset']) ? intval($tag['offset']) : 0; + $length = !empty($tag['length']) && is_numeric($tag['length']) ? intval($tag['length']) : 'null'; + + $parseStr = 'autoBuildVar($name); + $parseStr .= $var . '=' . $name . '; '; + $name = $var; + } else { + $name = $this->autoBuildVar($name); + } + + $parseStr .= 'if(is_array(' . $name . ') || ' . $name . ' instanceof \think\Collection || ' . $name . ' instanceof \think\Paginator): '; + + // 设置了输出数组长度 + if (0 != $offset || 'null' != $length) { + if (!isset($var)) { + $var = '$_' . uniqid(); + } + $parseStr .= $var . ' = is_array(' . $name . ') ? array_slice(' . $name . ',' . $offset . ',' . $length . ', true) : ' . $name . '->slice(' . $offset . ',' . $length . ', true); '; + } else { + $var = &$name; + } + + $parseStr .= 'if( count(' . $var . ')==0 ) : echo "' . $empty . '" ;'; + $parseStr .= 'else: '; + + // 设置了索引项 + if (isset($tag['index'])) { + $index = $tag['index']; + $parseStr .= '$' . $index . '=0; '; + } + + $parseStr .= 'foreach(' . $var . ' as $' . $key . '=>$' . $item . '): '; + + // 设置了索引项 + if (isset($tag['index'])) { + $index = $tag['index']; + if (isset($tag['mod'])) { + $mod = (int) $tag['mod']; + $parseStr .= '$mod = ($' . $index . ' % ' . $mod . '); '; + } + $parseStr .= '++$' . $index . '; '; + } + + $parseStr .= '?>'; + // 循环体中的内容 + $parseStr .= $content; + $parseStr .= ''; + + return $parseStr; + } + + /** + * if标签解析 + * 格式: + * {if condition=" $a eq 1"} + * {elseif condition="$a eq 2" /} + * {else /} + * {/if} + * 表达式支持 eq neq gt egt lt elt == > >= < <= or and || && + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagIf(array $tag, string $content): string + { + $condition = !empty($tag['expression']) ? $tag['expression'] : $tag['condition']; + $condition = $this->parseCondition($condition); + $parseStr = '' . $content . ''; + + return $parseStr; + } + + /** + * elseif标签解析 + * 格式:见if标签 + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagElseif(array $tag, string $content): string + { + $condition = !empty($tag['expression']) ? $tag['expression'] : $tag['condition']; + $condition = $this->parseCondition($condition); + $parseStr = ''; + + return $parseStr; + } + + /** + * else标签解析 + * 格式:见if标签 + * @access public + * @param array $tag 标签属性 + * @return string + */ + public function tagElse(array $tag): string + { + $parseStr = ''; + + return $parseStr; + } + + /** + * switch标签解析 + * 格式: + * {switch name="a.name"} + * {case value="1" break="false"}1{/case} + * {case value="2" }2{/case} + * {default /}other + * {/switch} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagSwitch(array $tag, string $content): string + { + $name = !empty($tag['expression']) ? $tag['expression'] : $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = '' . $content . ''; + + return $parseStr; + } + + /** + * case标签解析 需要配合switch才有效 + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagCase(array $tag, string $content): string + { + $value = isset($tag['expression']) ? $tag['expression'] : $tag['value']; + $flag = substr($value, 0, 1); + + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($value); + $value = 'case ' . $value . ':'; + } elseif (strpos($value, '|')) { + $values = explode('|', $value); + $value = ''; + foreach ($values as $val) { + $value .= 'case "' . addslashes($val) . '":'; + } + } else { + $value = 'case "' . $value . '":'; + } + + $parseStr = '' . $content; + $isBreak = isset($tag['break']) ? $tag['break'] : ''; + + if ('' == $isBreak || $isBreak) { + $parseStr .= ''; + } + + return $parseStr; + } + + /** + * default标签解析 需要配合switch才有效 + * 使用: {default /}ddfdf + * @access public + * @param array $tag 标签属性 + * @return string + */ + public function tagDefault(array $tag): string + { + $parseStr = ''; + + return $parseStr; + } + + /** + * compare标签解析 + * 用于值的比较 支持 eq neq gt lt egt elt heq nheq 默认是eq + * 格式: {compare name="" type="eq" value="" }content{/compare} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagCompare(array $tag, string $content): string + { + $name = $tag['name']; + $value = $tag['value']; + $type = isset($tag['type']) ? $tag['type'] : 'eq'; // 比较类型 + $name = $this->autoBuildVar($name); + $flag = substr($value, 0, 1); + + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($value); + } else { + $value = '\'' . $value . '\''; + } + + switch ($type) { + case 'equal': + $type = 'eq'; + break; + case 'notequal': + $type = 'neq'; + break; + } + $type = $this->parseCondition(' ' . $type . ' '); + $parseStr = '' . $content . ''; + + return $parseStr; + } + + /** + * range标签解析 + * 如果某个变量存在于某个范围 则输出内容 type= in 表示在范围内 否则表示在范围外 + * 格式: {range name="var|function" value="val" type='in|notin' }content{/range} + * example: {range name="a" value="1,2,3" type='in' }content{/range} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagRange(array $tag, string $content): string + { + $name = $tag['name']; + $value = $tag['value']; + $type = isset($tag['type']) ? $tag['type'] : 'in'; // 比较类型 + + $name = $this->autoBuildVar($name); + $flag = substr($value, 0, 1); + + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($value); + $str = 'is_array(' . $value . ')?' . $value . ':explode(\',\',' . $value . ')'; + } else { + $value = '"' . $value . '"'; + $str = 'explode(\',\',' . $value . ')'; + } + + if ('between' == $type) { + $parseStr = '= $_RANGE_VAR_[0] && ' . $name . '<= $_RANGE_VAR_[1]):?>' . $content . ''; + } elseif ('notbetween' == $type) { + $parseStr = '$_RANGE_VAR_[1]):?>' . $content . ''; + } else { + $fun = ('in' == $type) ? 'in_array' : '!in_array'; + $parseStr = '' . $content . ''; + } + + return $parseStr; + } + + /** + * present标签解析 + * 如果某个变量已经设置 则输出内容 + * 格式: {present name="" }content{/present} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagPresent(array $tag, string $content): string + { + $name = $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = '' . $content . ''; + + return $parseStr; + } + + /** + * notpresent标签解析 + * 如果某个变量没有设置,则输出内容 + * 格式: {notpresent name="" }content{/notpresent} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagNotpresent(array $tag, string $content): string + { + $name = $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = '' . $content . ''; + + return $parseStr; + } + + /** + * empty标签解析 + * 如果某个变量为empty 则输出内容 + * 格式: {empty name="" }content{/empty} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagEmpty(array $tag, string $content): string + { + $name = $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = 'isEmpty())): ?>' . $content . ''; + + return $parseStr; + } + + /** + * notempty标签解析 + * 如果某个变量不为empty 则输出内容 + * 格式: {notempty name="" }content{/notempty} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagNotempty(array $tag, string $content): string + { + $name = $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = 'isEmpty()))): ?>' . $content . ''; + + return $parseStr; + } + + /** + * 判断是否已经定义了该常量 + * {defined name='TXT'}已定义{/defined} + * @access public + * @param array $tag + * @param string $content + * @return string + */ + public function tagDefined(array $tag, string $content): string + { + $name = $tag['name']; + $parseStr = '' . $content . ''; + + return $parseStr; + } + + /** + * 判断是否没有定义了该常量 + * {notdefined name='TXT'}已定义{/notdefined} + * @access public + * @param array $tag + * @param string $content + * @return string + */ + public function tagNotdefined(array $tag, string $content): string + { + $name = $tag['name']; + $parseStr = '' . $content . ''; + + return $parseStr; + } + + /** + * load 标签解析 {load file="/static/js/base.js" /} + * 格式:{load file="/static/css/base.css" /} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagLoad(array $tag, string $content): string + { + $file = isset($tag['file']) ? $tag['file'] : $tag['href']; + $type = isset($tag['type']) ? strtolower($tag['type']) : ''; + + $parseStr = ''; + $endStr = ''; + + // 判断是否存在加载条件 允许使用函数判断(默认为isset) + if (isset($tag['value'])) { + $name = $tag['value']; + $name = $this->autoBuildVar($name); + $name = 'isset(' . $name . ')'; + $parseStr .= ''; + $endStr = ''; + } + + // 文件方式导入 + $array = explode(',', $file); + + foreach ($array as $val) { + $type = strtolower(substr(strrchr($val, '.'), 1)); + switch ($type) { + case 'js': + $parseStr .= ''; + break; + case 'css': + $parseStr .= ''; + break; + case 'php': + $parseStr .= ''; + break; + } + } + + return $parseStr . $endStr; + } + + /** + * assign标签解析 + * 在模板中给某个变量赋值 支持变量赋值 + * 格式: {assign name="" value="" /} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagAssign(array $tag, string $content): string + { + $name = $this->autoBuildVar($tag['name']); + $flag = substr($tag['value'], 0, 1); + + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($tag['value']); + } else { + $value = '\'' . $tag['value'] . '\''; + } + + $parseStr = ''; + + return $parseStr; + } + + /** + * define标签解析 + * 在模板中定义常量 支持变量赋值 + * 格式: {define name="" value="" /} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagDefine(array $tag, string $content): string + { + $name = '\'' . $tag['name'] . '\''; + $flag = substr($tag['value'], 0, 1); + + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($tag['value']); + } else { + $value = '\'' . $tag['value'] . '\''; + } + + $parseStr = ''; + + return $parseStr; + } + + /** + * for标签解析 + * 格式: + * {for start="" end="" comparison="" step="" name=""} + * content + * {/for} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagFor(array $tag, string $content): string + { + //设置默认值 + $start = 0; + $end = 0; + $step = 1; + $comparison = 'lt'; + $name = 'i'; + $rand = rand(); //添加随机数,防止嵌套变量冲突 + + //获取属性 + foreach ($tag as $key => $value) { + $value = trim($value); + $flag = substr($value, 0, 1); + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($value); + } + + switch ($key) { + case 'start': + $start = $value; + break; + case 'end': + $end = $value; + break; + case 'step': + $step = $value; + break; + case 'comparison': + $comparison = $value; + break; + case 'name': + $name = $value; + break; + } + } + + $parseStr = 'parseCondition('$' . $name . ' ' . $comparison . ' $__FOR_END_' . $rand . '__') . ';$' . $name . '+=' . $step . '){ ?>'; + $parseStr .= $content; + $parseStr .= ''; + + return $parseStr; + } + + /** + * url函数的tag标签 + * 格式:{url link="模块/控制器/方法" vars="参数" suffix="true或者false 是否带有后缀" domain="true或者false 是否携带域名" /} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagUrl(array $tag, string $content): string + { + $url = isset($tag['link']) ? $tag['link'] : ''; + $vars = isset($tag['vars']) ? $tag['vars'] : ''; + $suffix = isset($tag['suffix']) ? $tag['suffix'] : 'true'; + $domain = isset($tag['domain']) ? $tag['domain'] : 'false'; + + return ''; + } + + /** + * function标签解析 匿名函数,可实现递归 + * 使用: + * {function name="func" vars="$data" call="$list" use="&$a,&$b"} + * {if is_array($data)} + * {foreach $data as $val} + * {~func($val) /} + * {/foreach} + * {else /} + * {$data} + * {/if} + * {/function} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagFunction(array $tag, string $content): string + { + $name = !empty($tag['name']) ? $tag['name'] : 'func'; + $vars = !empty($tag['vars']) ? $tag['vars'] : ''; + $call = !empty($tag['call']) ? $tag['call'] : ''; + $use = ['&$' . $name]; + + if (!empty($tag['use'])) { + foreach (explode(',', $tag['use']) as $val) { + $use[] = '&' . ltrim(trim($val), '&'); + } + } + + $parseStr = '' . $content . '' : '?>'; + + return $parseStr; + } +} diff --git a/vendor/topthink/think-trace/.gitignore b/vendor/topthink/think-trace/.gitignore new file mode 100644 index 0000000..485dee6 --- /dev/null +++ b/vendor/topthink/think-trace/.gitignore @@ -0,0 +1 @@ +.idea diff --git a/vendor/topthink/think-trace/LICENSE b/vendor/topthink/think-trace/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/vendor/topthink/think-trace/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/topthink/think-trace/README.md b/vendor/topthink/think-trace/README.md new file mode 100644 index 0000000..6ed63ec --- /dev/null +++ b/vendor/topthink/think-trace/README.md @@ -0,0 +1,15 @@ +# think-trace + +用于ThinkPHP6+的页面Trace扩展,支持Html页面和浏览器控制台两种方式输出。 + +## 安装 + +~~~ +composer require topthink/think-trace +~~~ + +## 配置 + +安装后config目录下会自带trace.php配置文件。 + +type参数用于指定trace类型,支持html和console两种方式。 \ No newline at end of file diff --git a/vendor/topthink/think-trace/composer.json b/vendor/topthink/think-trace/composer.json new file mode 100644 index 0000000..5af5854 --- /dev/null +++ b/vendor/topthink/think-trace/composer.json @@ -0,0 +1,31 @@ +{ + "name": "topthink/think-trace", + "description": "thinkphp debug trace", + "license": "Apache-2.0", + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "require": { + "php": ">=7.1.0", + "topthink/framework": "^6.0.0" + }, + "autoload": { + "psr-4": { + "think\\trace\\": "src" + } + }, + "extra": { + "think":{ + "services":[ + "think\\trace\\Service" + ], + "config":{ + "trace": "src/config.php" + } + } + }, + "minimum-stability": "dev" +} diff --git a/vendor/topthink/think-trace/src/Console.php b/vendor/topthink/think-trace/src/Console.php new file mode 100644 index 0000000..179d4ea --- /dev/null +++ b/vendor/topthink/think-trace/src/Console.php @@ -0,0 +1,173 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); +namespace think\trace; + +use think\App; +use think\Response; + +/** + * 浏览器调试输出 + */ +class Console +{ + protected $config = [ + 'tabs' => ['base' => '基本', 'file' => '文件', 'info' => '流程', 'notice|error' => '错误', 'sql' => 'SQL', 'debug|log' => '调试'], + ]; + + // 实例化并传入参数 + public function __construct(array $config = []) + { + $this->config = array_merge($this->config, $config); + } + + /** + * 调试输出接口 + * @access public + * @param Response $response Response对象 + * @param array $log 日志信息 + * @return string|bool + */ + public function output(App $app, Response $response, array $log = []) + { + $request = $app->request; + $contentType = $response->getHeader('Content-Type'); + + if ($request->isJson() || $request->isAjax()) { + return false; + } elseif (!empty($contentType) && strpos($contentType, 'html') === false) { + return false; + } elseif ($response->getCode() == 204) { + return false; + } + + // 获取基本信息 + $runtime = number_format(microtime(true) - $app->getBeginTime(), 10, '.', ''); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + $mem = number_format((memory_get_usage() - $app->getBeginMem()) / 1024, 2); + + if ($request->host()) { + $uri = $request->protocol() . ' ' . $request->method() . ' : ' . $request->url(true); + } else { + $uri = 'cmd:' . implode(' ', $_SERVER['argv']); + } + + // 页面Trace信息 + $base = [ + '请求信息' => date('Y-m-d H:i:s', $request->time() ?: time()) . ' ' . $uri, + '运行时间' => number_format((float) $runtime, 6) . 's [ 吞吐率:' . $reqs . 'req/s ] 内存消耗:' . $mem . 'kb 文件加载:' . count(get_included_files()), + '查询信息' => $app->db->getQueryTimes() . ' queries', + '缓存信息' => $app->cache->getReadTimes() . ' reads,' . $app->cache->getWriteTimes() . ' writes', + ]; + + if (isset($app->session)) { + $base['会话信息'] = 'SESSION_ID=' . $app->session->getId(); + } + + $info = $this->getFileInfo(); + + // 页面Trace信息 + $trace = []; + foreach ($this->config['tabs'] as $name => $title) { + $name = strtolower($name); + switch ($name) { + case 'base': // 基本信息 + $trace[$title] = $base; + break; + case 'file': // 文件信息 + $trace[$title] = $info; + break; + default: // 调试信息 + if (strpos($name, '|')) { + // 多组信息 + $names = explode('|', $name); + $result = []; + foreach ($names as $item) { + $result = array_merge($result, $log[$item] ?? []); + } + $trace[$title] = $result; + } else { + $trace[$title] = $log[$name] ?? ''; + } + } + } + + //输出到控制台 + $lines = ''; + foreach ($trace as $type => $msg) { + $lines .= $this->console($type, empty($msg) ? [] : $msg); + } + $js = << +{$lines} + +JS; + return $js; + } + + protected function console(string $type, $msg) + { + $type = strtolower($type); + $trace_tabs = array_values($this->config['tabs']); + $line = []; + $line[] = ($type == $trace_tabs[0] || '调试' == $type || '错误' == $type) + ? "console.group('{$type}');" + : "console.groupCollapsed('{$type}');"; + + foreach ((array) $msg as $key => $m) { + switch ($type) { + case '调试': + $var_type = gettype($m); + if (in_array($var_type, ['array', 'string'])) { + $line[] = "console.log(" . json_encode($m) . ");"; + } else { + $line[] = "console.log(" . json_encode(var_export($m, true)) . ");"; + } + break; + case '错误': + $msg = str_replace("\n", '\n', addslashes(is_scalar($m) ? $m : json_encode($m))); + $style = 'color:#F4006B;font-size:14px;'; + $line[] = "console.error(\"%c{$msg}\", \"{$style}\");"; + break; + case 'sql': + $msg = str_replace("\n", '\n', addslashes($m)); + $style = "color:#009bb4;"; + $line[] = "console.log(\"%c{$msg}\", \"{$style}\");"; + break; + default: + $m = is_string($key) ? $key . ' ' . $m : $key + 1 . ' ' . $m; + $msg = json_encode($m); + $line[] = "console.log({$msg});"; + break; + } + } + $line[] = "console.groupEnd();"; + return implode(PHP_EOL, $line); + } + + /** + * 获取文件加载信息 + * @access protected + * @return integer|array + */ + protected function getFileInfo() + { + $files = get_included_files(); + $info = []; + + foreach ($files as $key => $file) { + $info[] = $file . ' ( ' . number_format(filesize($file) / 1024, 2) . ' KB )'; + } + + return $info; + } +} diff --git a/vendor/topthink/think-trace/src/Html.php b/vendor/topthink/think-trace/src/Html.php new file mode 100644 index 0000000..bcb2f50 --- /dev/null +++ b/vendor/topthink/think-trace/src/Html.php @@ -0,0 +1,126 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); +namespace think\trace; + +use think\App; +use think\Response; + +/** + * 页面Trace调试 + */ +class Html +{ + protected $config = [ + 'file' => '', + 'tabs' => ['base' => '基本', 'file' => '文件', 'info' => '流程', 'notice|error' => '错误', 'sql' => 'SQL', 'debug|log' => '调试'], + ]; + + // 实例化并传入参数 + public function __construct(array $config = []) + { + $this->config = array_merge($this->config, $config); + } + + /** + * 调试输出接口 + * @access public + * @param App $app 应用实例 + * @param Response $response Response对象 + * @param array $log 日志信息 + * @return bool|string + */ + public function output(App $app, Response $response, array $log = []) + { + $request = $app->request; + $contentType = $response->getHeader('Content-Type'); + + if ($request->isJson() || $request->isAjax()) { + return false; + } elseif (!empty($contentType) && strpos($contentType, 'html') === false) { + return false; + } elseif ($response->getCode() == 204) { + return false; + } + + // 获取基本信息 + $runtime = number_format(microtime(true) - $app->getBeginTime(), 10, '.', ''); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + $mem = number_format((memory_get_usage() - $app->getBeginMem()) / 1024, 2); + + // 页面Trace信息 + if ($request->host()) { + $uri = $request->protocol() . ' ' . $request->method() . ' : ' . $request->url(true); + } else { + $uri = 'cmd:' . implode(' ', $_SERVER['argv']); + } + + $base = [ + '请求信息' => date('Y-m-d H:i:s', $request->time() ?: time()) . ' ' . $uri, + '运行时间' => number_format((float) $runtime, 6) . 's [ 吞吐率:' . $reqs . 'req/s ] 内存消耗:' . $mem . 'kb 文件加载:' . count(get_included_files()), + '查询信息' => $app->db->getQueryTimes() . ' queries', + '缓存信息' => $app->cache->getReadTimes() . ' reads,' . $app->cache->getWriteTimes() . ' writes', + ]; + + if (isset($app->session)) { + $base['会话信息'] = 'SESSION_ID=' . $app->session->getId(); + } + + $info = $this->getFileInfo(); + + // 页面Trace信息 + $trace = []; + foreach ($this->config['tabs'] as $name => $title) { + $name = strtolower($name); + switch ($name) { + case 'base': // 基本信息 + $trace[$title] = $base; + break; + case 'file': // 文件信息 + $trace[$title] = $info; + break; + default: // 调试信息 + if (strpos($name, '|')) { + // 多组信息 + $names = explode('|', $name); + $result = []; + foreach ($names as $item) { + $result = array_merge($result, $log[$item] ?? []); + } + $trace[$title] = $result; + } else { + $trace[$title] = $log[$name] ?? ''; + } + } + } + // 调用Trace页面模板 + ob_start(); + include $this->config['file'] ?: __DIR__ . '/tpl/page_trace.tpl'; + return ob_get_clean(); + } + + /** + * 获取文件加载信息 + * @access protected + * @return integer|array + */ + protected function getFileInfo() + { + $files = get_included_files(); + $info = []; + + foreach ($files as $key => $file) { + $info[] = $file . ' ( ' . number_format(filesize($file) / 1024, 2) . ' KB )'; + } + + return $info; + } +} diff --git a/vendor/topthink/think-trace/src/Service.php b/vendor/topthink/think-trace/src/Service.php new file mode 100644 index 0000000..3e78ecc --- /dev/null +++ b/vendor/topthink/think-trace/src/Service.php @@ -0,0 +1,21 @@ + +// +---------------------------------------------------------------------- +namespace think\trace; + +use think\Service as BaseService; + +class Service extends BaseService +{ + public function register() + { + $this->app->middleware->add(TraceDebug::class); + } +} diff --git a/vendor/topthink/think-trace/src/TraceDebug.php b/vendor/topthink/think-trace/src/TraceDebug.php new file mode 100644 index 0000000..5ed9cbf --- /dev/null +++ b/vendor/topthink/think-trace/src/TraceDebug.php @@ -0,0 +1,109 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\trace; + +use Closure; +use think\App; +use think\Config; +use think\event\LogWrite; +use think\Request; +use think\Response; +use think\response\Redirect; + +/** + * 页面Trace中间件 + */ +class TraceDebug +{ + + /** + * Trace日志 + * @var array + */ + protected $log = []; + + /** + * 配置参数 + * @var array + */ + protected $config = []; + + /** @var App */ + protected $app; + + public function __construct(App $app, Config $config) + { + $this->app = $app; + $this->config = $config->get('trace'); + } + + /** + * 页面Trace调试 + * @access public + * @param Request $request + * @param Closure $next + * @return void + */ + public function handle($request, Closure $next) + { + $debug = $this->app->isDebug(); + + // 注册日志监听 + if ($debug) { + $this->log = []; + $this->app->event->listen(LogWrite::class, function ($event) { + if (empty($this->config['channel']) || $this->config['channel'] == $event->channel) { + $this->log = array_merge_recursive($this->log, $event->log); + } + }); + } + + $response = $next($request); + + // Trace调试注入 + if ($debug) { + $data = $response->getContent(); + $this->traceDebug($response, $data); + $response->content($data); + } + + return $response; + } + + public function traceDebug(Response $response, &$content) + { + $config = $this->config; + $type = $config['type'] ?? 'Html'; + + unset($config['type']); + + $trace = App::factory($type, '\\think\\trace\\', $config); + + if ($response instanceof Redirect) { + //TODO 记录 + } else { + $log = $this->app->log->getLog($config['channel'] ?? ''); + $log = array_merge_recursive($this->log, $log); + $output = $trace->output($this->app, $response, $log); + if (is_string($output)) { + // trace调试信息注入 + $pos = strripos($content, ''); + if (false !== $pos) { + $content = substr($content, 0, $pos) . $output . substr($content, $pos); + } else { + $content = $content . $output; + } + } + } + } +} diff --git a/vendor/topthink/think-trace/src/config.php b/vendor/topthink/think-trace/src/config.php new file mode 100644 index 0000000..fad2392 --- /dev/null +++ b/vendor/topthink/think-trace/src/config.php @@ -0,0 +1,10 @@ + 'Html', + // 读取的日志通道名 + 'channel' => '', +]; diff --git a/vendor/topthink/think-trace/src/tpl/page_trace.tpl b/vendor/topthink/think-trace/src/tpl/page_trace.tpl new file mode 100644 index 0000000..6b1b9a1 --- /dev/null +++ b/vendor/topthink/think-trace/src/tpl/page_trace.tpl @@ -0,0 +1,71 @@ +
        + + +
        +
        +
        + +
        + + diff --git a/vendor/topthink/think-view/.gitignore b/vendor/topthink/think-view/.gitignore new file mode 100644 index 0000000..485dee6 --- /dev/null +++ b/vendor/topthink/think-view/.gitignore @@ -0,0 +1 @@ +.idea diff --git a/vendor/topthink/think-view/LICENSE b/vendor/topthink/think-view/LICENSE new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/vendor/topthink/think-view/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/topthink/think-view/README.md b/vendor/topthink/think-view/README.md new file mode 100644 index 0000000..4e52def --- /dev/null +++ b/vendor/topthink/think-view/README.md @@ -0,0 +1,36 @@ +# think-view + +ThinkPHP6.0 Think-Template模板引擎驱动 + + +## 安装 + +~~~php +composer require topthink/think-view +~~~ + +## 用法示例 + +本扩展不能单独使用,依赖ThinkPHP6.0+ + +首先配置config目录下的template.php配置文件,然后可以按照下面的用法使用。 + +~~~php + +use think\facade\View; + +// 模板变量赋值和渲染输出 +View::assign(['name' => 'think']) + // 输出过滤 + ->filter(function($content){ + return str_replace('search', 'replace', $content); + }) + // 读取模板文件渲染输出 + ->fetch('index'); + + +// 或者使用助手函数 +view('index', ['name' => 'think']); +~~~ + +具体的模板引擎配置请参考think-template库。 \ No newline at end of file diff --git a/vendor/topthink/think-view/composer.json b/vendor/topthink/think-view/composer.json new file mode 100644 index 0000000..f4e6431 --- /dev/null +++ b/vendor/topthink/think-view/composer.json @@ -0,0 +1,20 @@ +{ + "name": "topthink/think-view", + "description": "thinkphp template driver", + "license": "Apache-2.0", + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "require": { + "php": ">=7.1.0", + "topthink/think-template": "^2.0" + }, + "autoload": { + "psr-4": { + "think\\view\\driver\\": "src" + } + } +} diff --git a/vendor/topthink/think-view/src/Think.php b/vendor/topthink/think-view/src/Think.php new file mode 100644 index 0000000..562b54a --- /dev/null +++ b/vendor/topthink/think-view/src/Think.php @@ -0,0 +1,259 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\view\driver; + +use think\App; +use think\helper\Str; +use think\Template; +use think\template\exception\TemplateNotFoundException; + +class Think +{ + // 模板引擎实例 + private $template; + private $app; + + // 模板引擎参数 + protected $config = [ + // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 3 保持操作方法 + 'auto_rule' => 1, + // 视图目录名 + 'view_dir_name' => 'view', + // 模板起始路径 + 'view_path' => '', + // 模板文件后缀 + 'view_suffix' => 'html', + // 模板文件名分隔符 + 'view_depr' => DIRECTORY_SEPARATOR, + // 是否开启模板编译缓存,设为false则每次都会重新编译 + 'tpl_cache' => true, + ]; + + public function __construct(App $app, array $config = []) + { + $this->app = $app; + + $this->config = array_merge($this->config, (array) $config); + + if (empty($this->config['cache_path'])) { + $this->config['cache_path'] = $app->getRuntimePath() . 'temp' . DIRECTORY_SEPARATOR; + } + + $this->template = new Template($this->config); + $this->template->setCache($app->cache); + $this->template->extend('$Think', function (array $vars) { + $type = strtoupper(trim(array_shift($vars))); + $param = implode('.', $vars); + + switch ($type) { + case 'CONST': + $parseStr = strtoupper($param); + break; + case 'CONFIG': + $parseStr = 'config(\'' . $param . '\')'; + break; + case 'LANG': + $parseStr = 'lang(\'' . $param . '\')'; + break; + case 'NOW': + $parseStr = "date('Y-m-d g:i a',time())"; + break; + case 'LDELIM': + $parseStr = '\'' . ltrim($this->getConfig('tpl_begin'), '\\') . '\''; + break; + case 'RDELIM': + $parseStr = '\'' . ltrim($this->getConfig('tpl_end'), '\\') . '\''; + break; + default: + $parseStr = defined($type) ? $type : '\'\''; + } + + return $parseStr; + }); + + $this->template->extend('$Request', function (array $vars) { + // 获取Request请求对象参数 + $method = array_shift($vars); + if (!empty($vars)) { + $params = implode('.', $vars); + if ('true' != $params) { + $params = '\'' . $params . '\''; + } + } else { + $params = ''; + } + + return 'app(\'request\')->' . $method . '(' . $params . ')'; + }); + } + + /** + * 检测是否存在模板文件 + * @access public + * @param string $template 模板文件或者模板规则 + * @return bool + */ + public function exists(string $template): bool + { + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + // 获取模板文件名 + $template = $this->parseTemplate($template); + } + + return is_file($template); + } + + /** + * 渲染模板文件 + * @access public + * @param string $template 模板文件 + * @param array $data 模板变量 + * @return void + */ + public function fetch(string $template, array $data = []): void + { + if (empty($this->config['view_path'])) { + $view = $this->config['view_dir_name']; + + if (is_dir($this->app->getAppPath() . $view)) { + $path = $this->app->getAppPath() . $view . DIRECTORY_SEPARATOR; + } else { + $appName = $this->app->http->getName(); + $path = $this->app->getRootPath() . $view . DIRECTORY_SEPARATOR . ($appName ? $appName . DIRECTORY_SEPARATOR : ''); + } + + $this->config['view_path'] = $path; + $this->template->view_path = $path; + } + + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + // 获取模板文件名 + $template = $this->parseTemplate($template); + } + + // 模板不存在 抛出异常 + if (!is_file($template)) { + throw new TemplateNotFoundException('template not exists:' . $template, $template); + } + + $this->template->fetch($template, $data); + } + + /** + * 渲染模板内容 + * @access public + * @param string $template 模板内容 + * @param array $data 模板变量 + * @return void + */ + public function display(string $template, array $data = []): void + { + $this->template->display($template, $data); + } + + /** + * 自动定位模板文件 + * @access private + * @param string $template 模板文件规则 + * @return string + */ + private function parseTemplate(string $template): string + { + // 分析模板文件规则 + $request = $this->app['request']; + + // 获取视图根目录 + if (strpos($template, '@')) { + // 跨模块调用 + list($app, $template) = explode('@', $template); + } + + if (isset($app)) { + $view = $this->config['view_dir_name']; + $viewPath = $this->app->getBasePath() . $app . DIRECTORY_SEPARATOR . $view . DIRECTORY_SEPARATOR; + + if (is_dir($viewPath)) { + $path = $viewPath; + } else { + $path = $this->app->getRootPath() . $view . DIRECTORY_SEPARATOR . $app . DIRECTORY_SEPARATOR; + } + + $this->template->view_path = $path; + } else { + $path = $this->config['view_path']; + } + + $depr = $this->config['view_depr']; + + if (0 !== strpos($template, '/')) { + $template = str_replace(['/', ':'], $depr, $template); + $controller = $request->controller(); + + if (strpos($controller, '.')) { + $pos = strrpos($controller, '.'); + $controller = substr($controller, 0, $pos) . '.' . Str::snake(substr($controller, $pos + 1)); + } else { + $controller = Str::snake($controller); + } + + if ($controller) { + if ('' == $template) { + // 如果模板文件名为空 按照默认模板渲染规则定位 + if (2 == $this->config['auto_rule']) { + $template = $request->action(true); + } elseif (3 == $this->config['auto_rule']) { + $template = $request->action(); + } else { + $template = Str::snake($request->action()); + } + + $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $template; + } elseif (false === strpos($template, $depr)) { + $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $template; + } + } + } else { + $template = str_replace(['/', ':'], $depr, substr($template, 1)); + } + + return $path . ltrim($template, '/') . '.' . ltrim($this->config['view_suffix'], '.'); + } + + /** + * 配置模板引擎 + * @access private + * @param array $config 参数 + * @return void + */ + public function config(array $config): void + { + $this->template->config($config); + $this->config = array_merge($this->config, $config); + } + + /** + * 获取模板引擎配置 + * @access public + * @param string $name 参数名 + * @return void + */ + public function getConfig(string $name) + { + return $this->template->getConfig($name); + } + + public function __call($method, $params) + { + return call_user_func_array([$this->template, $method], $params); + } +} diff --git a/vendor/yansongda/pay/LICENSE b/vendor/yansongda/pay/LICENSE new file mode 100644 index 0000000..6ceb153 --- /dev/null +++ b/vendor/yansongda/pay/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2017 yansongda + +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. \ No newline at end of file diff --git a/vendor/yansongda/pay/README.md b/vendor/yansongda/pay/README.md new file mode 100644 index 0000000..73f0ee1 --- /dev/null +++ b/vendor/yansongda/pay/README.md @@ -0,0 +1,310 @@ +

        Pay

        + +

        +Scrutinizer Code Quality +Build Status +Latest Stable Version +Total Downloads +Latest Unstable Version +License +

        + +该文档为 v2.x 版本,如果您想找 v1.x 版本文档,请点击[https://github.com/yansongda/pay/tree/v1.x](https://github.com/yansongda/pay/tree/v1.x) + +**注意:v1.x 与 v2.x 版本不兼容** + + +开发了多次支付宝与微信支付后,很自然产生一种反感,惰性又来了,想在网上找相关的轮子,可是一直没有找到一款自己觉得逞心如意的,要么使用起来太难理解,要么文件结构太杂乱,只有自己撸起袖子干了。 + +**!!请先熟悉 支付宝/微信 说明文档!!请具有基本的 debug 能力!!** + +欢迎 Star,欢迎 PR! + +laravel 扩展包请 [传送至这里](https://github.com/yansongda/laravel-pay) +yii 扩展包请 [传送至这里](https://github.com/guanguans/yii-pay) + +QQ交流群:690027516 + +## 特点 +- 丰富的事件系统 +- 命名不那么乱七八糟 +- 隐藏开发者不需要关注的细节 +- 根据支付宝、微信最新 API 开发而成 +- 高度抽象的类,免去各种拼json与xml的痛苦 +- 符合 PSR 标准,你可以各种方便的与你的框架集成 +- 文件结构清晰易理解,可以随心所欲添加本项目中没有的支付网关 +- 方法使用更优雅,不必再去研究那些奇怪的的方法名或者类名是做啥用的 + + +## 运行环境 +- PHP 7.0+ (v2.8.0 开始 >= 7.1.3) +- composer + +> php5 请使用 v1.x 版本[https://github.com/yansongda/pay/tree/v1.x](https://github.com/yansongda/pay/tree/v1.x) + + +## 支持的支付方法 +### 1、支付宝 +- 电脑支付 +- 手机网站支付 +- APP 支付 +- 刷卡支付 +- 扫码支付 +- 账户转账 +- 小程序支付 + +| method | 描述 | +| :-------: | :-------: | +| web | 电脑支付 | +| wap | 手机网站支付 | +| app | APP 支付 | +| pos | 刷卡支付 | +| scan | 扫码支付 | +| transfer | 帐户转账 | +| mini | 小程序支付 | + +### 2、微信 +- 公众号支付 +- 小程序支付 +- H5 支付 +- 扫码支付 +- 刷卡支付 +- APP 支付 +- 企业付款 +- 普通红包 +- 分裂红包 + +| method | 描述 | +| :-----: | :-------: | +| mp | 公众号支付 | +| miniapp | 小程序支付 | +| wap | H5 支付 | +| scan | 扫码支付 | +| pos | 刷卡支付 | +| app | APP 支付 | +| transfer | 企业付款 | +| redpack | 普通红包 | +| groupRedpack | 分裂红包 | + +## 支持的方法 +所有网关均支持以下方法 + +- find(array/string $order) +说明:查找订单接口 +参数:`$order` 为 `string` 类型时,请传入系统订单号,对应支付宝或微信中的 `out_trade_no`; `array` 类型时,参数请参考支付宝或微信官方文档。 +返回:查询成功,返回 `Yansongda\Supports\Collection` 实例,可以通过 `$colletion->xxx` 或 `$collection['xxx']` 访问服务器返回的数据。 +异常:`GatewayException` 或 `InvalidSignException` + +- refund(array $order) +说明:退款接口 +参数:`$order` 数组格式,退款参数。 +返回:退款成功,返回 `Yansongda\Supports\Collection` 实例,可以通过 `$colletion->xxx` 或 `$collection['xxx']` 访问服务器返回的数据。 +异常:`GatewayException` 或 `InvalidSignException` + +- cancel(array/string $order) +说明:取消订单接口 +参数:`$order` 为 `string` 类型时,请传入系统订单号,对应支付宝或微信中的 `out_trade_no`; `array` 类型时,参数请参考支付宝或微信官方文档。 +返回:取消成功,返回 `Yansongda\Supports\Collection` 实例,可以通过 `$colletion->xxx` 或 `$collection['xxx']` 访问服务器返回的数据。 +异常:`GatewayException` 或 `InvalidSignException` + +- close(array/string $order) +说明:关闭订单接口 +参数:`$order` 为 `string` 类型时,请传入系统订单号,对应支付宝或微信中的 `out_trade_no`; `array` 类型时,参数请参考支付宝或微信官方文档。 +返回:关闭成功,返回 `Yansongda\Supports\Collection` 实例,可以通过 `$colletion->xxx` 或 `$collection['xxx']` 访问服务器返回的数据。 +异常:`GatewayException` 或 `InvalidSignException` + +- verify() +说明:验证服务器返回消息是否合法 +返回:验证成功,返回 `Yansongda\Supports\Collection` 实例,可以通过 `$colletion->xxx` 或 `$collection['xxx']` 访问服务器返回的数据。 +异常:`GatewayException` 或 `InvalidSignException` + +- PAYMETHOD(array $order) +说明:进行支付;具体支付方法名称请参考「支持的支付方法」一栏 +返回:成功,返回 `Yansongda\Supports\Collection` 实例,可以通过 `$colletion->xxx` 或 `$collection['xxx']` 访问服务器返回的数据或 `Symfony\Component\HttpFoundation\Response` 实例,可通过 `return $response->send()`(laravel 框架中直接 `return $response`) 返回,具体请参考文档。 +异常:`GatewayException` 或 `InvalidSignException` + +## 安装 +```shell +composer require yansongda/pay -vvv +``` + +## 使用说明 + +### 支付宝 +```php + '2016082000295641', + 'notify_url' => 'http://yansongda.cn/notify.php', + 'return_url' => 'http://yansongda.cn/return.php', + 'ali_public_key' => 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuWJKrQ6SWvS6niI+4vEVZiYfjkCfLQfoFI2nCp9ZLDS42QtiL4Ccyx8scgc3nhVwmVRte8f57TFvGhvJD0upT4O5O/lRxmTjechXAorirVdAODpOu0mFfQV9y/T9o9hHnU+VmO5spoVb3umqpq6D/Pt8p25Yk852/w01VTIczrXC4QlrbOEe3sr1E9auoC7rgYjjCO6lZUIDjX/oBmNXZxhRDrYx4Yf5X7y8FRBFvygIE2FgxV4Yw+SL3QAa2m5MLcbusJpxOml9YVQfP8iSurx41PvvXUMo49JG3BDVernaCYXQCoUJv9fJwbnfZd7J5YByC+5KM4sblJTq7bXZWQIDAQAB', + // 加密方式: **RSA2** + 'private_key' => 'MIIEpAIBAAKCAQEAs6+F2leOgOrvj9jTeDhb5q46GewOjqLBlGSs/bVL4Z3fMr3p+Q1Tux/6uogeVi/eHd84xvQdfpZ87A1SfoWnEGH5z15yorccxSOwWUI+q8gz51IWqjgZxhWKe31BxNZ+prnQpyeMBtE25fXp5nQZ/pftgePyUUvUZRcAUisswntobDQKbwx28VCXw5XB2A+lvYEvxmMv/QexYjwKK4M54j435TuC3UctZbnuynSPpOmCu45ZhEYXd4YMsGMdZE5/077ZU1aU7wx/gk07PiHImEOCDkzqsFo0Buc/knGcdOiUDvm2hn2y1XvwjyFOThsqCsQYi4JmwZdRa8kvOf57nwIDAQABAoIBAQCw5QCqln4VTrTvcW+msB1ReX57nJgsNfDLbV2dG8mLYQemBa9833DqDK6iynTLNq69y88ylose33o2TVtEccGp8Dqluv6yUAED14G6LexS43KtrXPgugAtsXE253ZDGUNwUggnN1i0MW2RcMqHdQ9ORDWvJUCeZj/AEafgPN8AyiLrZeL07jJz/uaRfAuNqkImCVIarKUX3HBCjl9TpuoMjcMhz/MsOmQ0agtCatO1eoH1sqv5Odvxb1i59c8Hvq/mGEXyRuoiDo05SE6IyXYXr84/Nf2xvVNHNQA6kTckj8shSi+HGM4mO1Y4Pbb7XcnxNkT0Inn6oJMSiy56P+CpAoGBAO1O+5FE1ZuVGuLb48cY+0lHCD+nhSBd66B5FrxgPYCkFOQWR7pWyfNDBlmO3SSooQ8TQXA25blrkDxzOAEGX57EPiipXr/hy5e+WNoukpy09rsO1TMsvC+v0FXLvZ+TIAkqfnYBgaT56ku7yZ8aFGMwdCPL7WJYAwUIcZX8wZ3dAoGBAMHWplAqhe4bfkGOEEpfs6VvEQxCqYMYVyR65K0rI1LiDZn6Ij8fdVtwMjGKFSZZTspmsqnbbuCE/VTyDzF4NpAxdm3cBtZACv1Lpu2Om+aTzhK2PI6WTDVTKAJBYegXaahBCqVbSxieR62IWtmOMjggTtAKWZ1P5LQcRwdkaB2rAoGAWnAPT318Kp7YcDx8whOzMGnxqtCc24jvk2iSUZgb2Dqv+3zCOTF6JUsV0Guxu5bISoZ8GdfSFKf5gBAo97sGFeuUBMsHYPkcLehM1FmLZk1Q+ljcx3P1A/ds3kWXLolTXCrlpvNMBSN5NwOKAyhdPK/qkvnUrfX8sJ5XK2H4J8ECgYAGIZ0HIiE0Y+g9eJnpUFelXvsCEUW9YNK4065SD/BBGedmPHRC3OLgbo8X5A9BNEf6vP7fwpIiRfKhcjqqzOuk6fueA/yvYD04v+Da2MzzoS8+hkcqF3T3pta4I4tORRdRfCUzD80zTSZlRc/h286Y2eTETd+By1onnFFe2X01mwKBgQDaxo4PBcLL2OyVT5DoXiIdTCJ8KNZL9+kV1aiBuOWxnRgkDjPngslzNa1bK+klGgJNYDbQqohKNn1HeFX3mYNfCUpuSnD2Yag53Dd/1DLO+NxzwvTu4D6DCUnMMMBVaF42ig31Bs0jI3JQZVqeeFzSET8fkoFopJf3G6UXlrIEAQ==', + // 使用公钥证书模式,请配置下面两个参数,同时修改ali_public_key为以.crt结尾的支付宝公钥证书路径,如(./cert/alipayCertPublicKey_RSA2.crt) + // 'app_cert_public_key' => './cert/appCertPublicKey.crt', //应用公钥证书路径 + // 'alipay_root_cert' => './cert/alipayRootCert.crt', //支付宝根证书路径 + 'log' => [ // optional + 'file' => './logs/alipay.log', + 'level' => 'info', // 建议生产环境等级调整为 info,开发环境为 debug + 'type' => 'single', // optional, 可选 daily. + 'max_file' => 30, // optional, 当 type 为 daily 时有效,默认 30 天 + ], + 'http' => [ // optional + 'timeout' => 5.0, + 'connect_timeout' => 5.0, + // 更多配置项请参考 [Guzzle](https://guzzle-cn.readthedocs.io/zh_CN/latest/request-options.html) + ], + 'mode' => 'dev', // optional,设置此参数,将进入沙箱模式 + ]; + + public function index() + { + $order = [ + 'out_trade_no' => time(), + 'total_amount' => '1', + 'subject' => 'test subject - 测试', + ]; + + $alipay = Pay::alipay($this->config)->web($order); + + return $alipay->send();// laravel 框架中请直接 `return $alipay` + } + + public function return() + { + $data = Pay::alipay($this->config)->verify(); // 是的,验签就这么简单! + + // 订单号:$data->out_trade_no + // 支付宝交易号:$data->trade_no + // 订单总金额:$data->total_amount + } + + public function notify() + { + $alipay = Pay::alipay($this->config); + + try{ + $data = $alipay->verify(); // 是的,验签就这么简单! + + // 请自行对 trade_status 进行判断及其它逻辑进行判断,在支付宝的业务通知中,只有交易通知状态为 TRADE_SUCCESS 或 TRADE_FINISHED 时,支付宝才会认定为买家付款成功。 + // 1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号; + // 2、判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额); + // 3、校验通知中的seller_id(或者seller_email) 是否为out_trade_no这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email); + // 4、验证app_id是否为该商户本身。 + // 5、其它业务逻辑情况 + + Log::debug('Alipay notify', $data->all()); + } catch (\Exception $e) { + // $e->getMessage(); + } + + return $alipay->success()->send();// laravel 框架中请直接 `return $alipay->success()` + } +} +``` + +### 微信 +```php + 'wxb3fxxxxxxxxxxx', // APP APPID + 'app_id' => 'wxb3fxxxxxxxxxxx', // 公众号 APPID + 'miniapp_id' => 'wxb3fxxxxxxxxxxx', // 小程序 APPID + 'mch_id' => '14577xxxx', + 'key' => 'mF2suE9sU6Mk1Cxxxxxxxxxxx', + 'notify_url' => 'http://yanda.net.cn/notify.php', + 'cert_client' => './cert/apiclient_cert.pem', // optional,退款等情况时用到 + 'cert_key' => './cert/apiclient_key.pem',// optional,退款等情况时用到 + 'log' => [ // optional + 'file' => './logs/wechat.log', + 'level' => 'info', // 建议生产环境等级调整为 info,开发环境为 debug + 'type' => 'single', // optional, 可选 daily. + 'max_file' => 30, // optional, 当 type 为 daily 时有效,默认 30 天 + ], + 'http' => [ // optional + 'timeout' => 5.0, + 'connect_timeout' => 5.0, + // 更多配置项请参考 [Guzzle](https://guzzle-cn.readthedocs.io/zh_CN/latest/request-options.html) + ], + 'mode' => 'dev', // optional, dev/hk;当为 `hk` 时,为香港 gateway。 + ]; + + public function index() + { + $order = [ + 'out_trade_no' => time(), + 'total_fee' => '1', // **单位:分** + 'body' => 'test body - 测试', + 'openid' => 'onkVf1FjWS5SBIixxxxxxx', + ]; + + $pay = Pay::wechat($this->config)->mp($order); + + // $pay->appId + // $pay->timeStamp + // $pay->nonceStr + // $pay->package + // $pay->signType + } + + public function notify() + { + $pay = Pay::wechat($this->config); + + try{ + $data = $pay->verify(); // 是的,验签就这么简单! + + Log::debug('Wechat notify', $data->all()); + } catch (\Exception $e) { + // $e->getMessage(); + } + + return $pay->success()->send();// laravel 框架中请直接 `return $pay->success()` + } +} +``` + +## 事件系统 +[请见详细文档](http://pay.yansongda.cn) + +## 详细文档 +[详细说明文档](http://pay.yansongda.cn) + +## 错误 +如果在调用相关支付网关 API 时有错误产生,会抛出 `GatewayException`,`InvalidSignException` 错误,可以通过 `$e->getMessage()` 查看,同时,也可通过 `$e->raw` 查看调用 API 后返回的原始数据,该值为数组格式。 + +### 所有异常 + +* Yansongda\Pay\Exceptions\InvalidGatewayException ,表示使用了除本 SDK 支持的支付网关。 +* Yansongda\Pay\Exceptions\InvalidSignException ,表示验签失败。 +* Yansongda\Pay\Exceptions\InvalidConfigException ,表示缺少配置参数,如,`ali_public_key`, `private_key` 等。 +* Yansongda\Pay\Exceptions\GatewayException ,表示支付宝/微信服务器返回的数据非正常结果,例如,参数错误,对账单不存在等。 + + +## 代码贡献 +由于测试及使用环境的限制,本项目中只开发了「支付宝」和「微信支付」的相关支付网关。 + +如果您有其它支付网关的需求,或者发现本项目中需要改进的代码,**_欢迎 Fork 并提交 PR!_** + +## 赏一杯咖啡吧 + +![pay](https://pay.yanda.net.cn/images/pay.jpg) + +## LICENSE +MIT diff --git a/vendor/yansongda/pay/composer.json b/vendor/yansongda/pay/composer.json new file mode 100644 index 0000000..17c0cf7 --- /dev/null +++ b/vendor/yansongda/pay/composer.json @@ -0,0 +1,43 @@ +{ + "name": "yansongda/pay", + "description": "专注 Alipay 和 WeChat 的支付扩展包", + "keywords": ["alipay", "wechat", "pay"], + "type": "library", + "support": { + "issues": "https://github.com/yansongda/pay/issues", + "source": "https://github.com/yansongda/pay" + }, + "authors": [ + { + "name": "yansongda", + "email": "me@yansongda.cn" + } + ], + "require": { + "php": ">=7.1.3", + "ext-openssl": "*", + "ext-simplexml":"*", + "ext-libxml": "*", + "ext-json": "*", + "ext-bcmath": "*", + "yansongda/supports": "^2.0", + "symfony/http-foundation": "^4.0 || ^5.0.7", + "symfony/event-dispatcher": "^4.0 || ^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.5", + "mockery/mockery": "^1.2", + "friendsofphp/php-cs-fixer": "^2.15" + }, + "autoload": { + "psr-4": { + "Yansongda\\Pay\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Yansongda\\Pay\\Tests\\": "tests" + } + }, + "license": "MIT" +} diff --git a/vendor/yansongda/pay/src/Contracts/GatewayApplicationInterface.php b/vendor/yansongda/pay/src/Contracts/GatewayApplicationInterface.php new file mode 100644 index 0000000..b4e296a --- /dev/null +++ b/vendor/yansongda/pay/src/Contracts/GatewayApplicationInterface.php @@ -0,0 +1,83 @@ + + * + * @param string $gateway + * @param array $params + * + * @return Collection|Response + */ + public function pay($gateway, $params); + + /** + * Query an order. + * + * @author yansongda + * + * @param string|array $order + * + * @return Collection + */ + public function find($order, string $type); + + /** + * Refund an order. + * + * @author yansongda + * + * @return Collection + */ + public function refund(array $order); + + /** + * Cancel an order. + * + * @author yansongda + * + * @param string|array $order + * + * @return Collection + */ + public function cancel($order); + + /** + * Close an order. + * + * @author yansongda + * + * @param string|array $order + * + * @return Collection + */ + public function close($order); + + /** + * Verify a request. + * + * @author yansongda + * + * @param string|array|null $content + * + * @return Collection + */ + public function verify($content, bool $refund); + + /** + * Echo success to server. + * + * @author yansongda + * + * @return Response + */ + public function success(); +} diff --git a/vendor/yansongda/pay/src/Contracts/GatewayInterface.php b/vendor/yansongda/pay/src/Contracts/GatewayInterface.php new file mode 100644 index 0000000..8286763 --- /dev/null +++ b/vendor/yansongda/pay/src/Contracts/GatewayInterface.php @@ -0,0 +1,20 @@ + + * + * @param string $endpoint + * + * @return Collection|Response + */ + public function pay($endpoint, array $payload); +} diff --git a/vendor/yansongda/pay/src/Events.php b/vendor/yansongda/pay/src/Events.php new file mode 100644 index 0000000..a747305 --- /dev/null +++ b/vendor/yansongda/pay/src/Events.php @@ -0,0 +1,98 @@ + + * + * @method static Event dispatch(Event $event) Dispatches an event to all registered listeners + * @method static array getListeners($eventName = null) Gets the listeners of a specific event or all listeners sorted by descending priority. + * @method static int|void getListenerPriority($eventName, $listener) Gets the listener priority for a specific event. + * @method static bool hasListeners($eventName = null) Checks whether an event has any registered listeners. + * @method static void addListener($eventName, $listener, $priority = 0) Adds an event listener that listens on the specified events. + * @method static removeListener($eventName, $listener) Removes an event listener from the specified events. + * @method static void addSubscriber(EventSubscriberInterface $subscriber) Adds an event subscriber. + * @method static void removeSubscriber(EventSubscriberInterface $subscriber) + */ +class Events +{ + /** + * dispatcher. + * + * @var EventDispatcher + */ + protected static $dispatcher; + + /** + * Forward call. + * + * @author yansongda + * + * @param string $method + * @param array $args + * + * @throws Exception + * + * @return mixed + */ + public static function __callStatic($method, $args) + { + return call_user_func_array([self::getDispatcher(), $method], $args); + } + + /** + * Forward call. + * + * @author yansongda + * + * @param string $method + * @param array $args + * + * @throws Exception + * + * @return mixed + */ + public function __call($method, $args) + { + return call_user_func_array([self::getDispatcher(), $method], $args); + } + + /** + * setDispatcher. + * + * @author yansongda + */ + public static function setDispatcher(EventDispatcher $dispatcher) + { + self::$dispatcher = $dispatcher; + } + + /** + * getDispatcher. + * + * @author yansongda + */ + public static function getDispatcher(): EventDispatcher + { + if (self::$dispatcher) { + return self::$dispatcher; + } + + return self::$dispatcher = self::createDispatcher(); + } + + /** + * createDispatcher. + * + * @author yansongda + */ + public static function createDispatcher(): EventDispatcher + { + return new EventDispatcher(); + } +} diff --git a/vendor/yansongda/pay/src/Events/ApiRequested.php b/vendor/yansongda/pay/src/Events/ApiRequested.php new file mode 100644 index 0000000..845ac87 --- /dev/null +++ b/vendor/yansongda/pay/src/Events/ApiRequested.php @@ -0,0 +1,31 @@ +endpoint = $endpoint; + $this->result = $result; + + parent::__construct($driver, $gateway); + } +} diff --git a/vendor/yansongda/pay/src/Events/ApiRequesting.php b/vendor/yansongda/pay/src/Events/ApiRequesting.php new file mode 100644 index 0000000..14c9fdd --- /dev/null +++ b/vendor/yansongda/pay/src/Events/ApiRequesting.php @@ -0,0 +1,31 @@ +endpoint = $endpoint; + $this->payload = $payload; + + parent::__construct($driver, $gateway); + } +} diff --git a/vendor/yansongda/pay/src/Events/Event.php b/vendor/yansongda/pay/src/Events/Event.php new file mode 100644 index 0000000..1ef883c --- /dev/null +++ b/vendor/yansongda/pay/src/Events/Event.php @@ -0,0 +1,40 @@ + + */ + public function __construct(string $driver, string $gateway) + { + $this->driver = $driver; + $this->gateway = $gateway; + } +} diff --git a/vendor/yansongda/pay/src/Events/MethodCalled.php b/vendor/yansongda/pay/src/Events/MethodCalled.php new file mode 100644 index 0000000..d199f23 --- /dev/null +++ b/vendor/yansongda/pay/src/Events/MethodCalled.php @@ -0,0 +1,33 @@ + + */ + public function __construct(string $driver, string $gateway, string $endpoint, array $payload = []) + { + $this->endpoint = $endpoint; + $this->payload = $payload; + + parent::__construct($driver, $gateway); + } +} diff --git a/vendor/yansongda/pay/src/Events/PayStarted.php b/vendor/yansongda/pay/src/Events/PayStarted.php new file mode 100644 index 0000000..845d93c --- /dev/null +++ b/vendor/yansongda/pay/src/Events/PayStarted.php @@ -0,0 +1,31 @@ +endpoint = $endpoint; + $this->payload = $payload; + + parent::__construct($driver, $gateway); + } +} diff --git a/vendor/yansongda/pay/src/Events/PayStarting.php b/vendor/yansongda/pay/src/Events/PayStarting.php new file mode 100644 index 0000000..ad73d9d --- /dev/null +++ b/vendor/yansongda/pay/src/Events/PayStarting.php @@ -0,0 +1,23 @@ +params = $params; + + parent::__construct($driver, $gateway); + } +} diff --git a/vendor/yansongda/pay/src/Events/RequestReceived.php b/vendor/yansongda/pay/src/Events/RequestReceived.php new file mode 100644 index 0000000..3620b12 --- /dev/null +++ b/vendor/yansongda/pay/src/Events/RequestReceived.php @@ -0,0 +1,25 @@ + + */ + public function __construct(string $driver, string $gateway, array $data) + { + $this->data = $data; + + parent::__construct($driver, $gateway); + } +} diff --git a/vendor/yansongda/pay/src/Events/SignFailed.php b/vendor/yansongda/pay/src/Events/SignFailed.php new file mode 100644 index 0000000..4e45932 --- /dev/null +++ b/vendor/yansongda/pay/src/Events/SignFailed.php @@ -0,0 +1,25 @@ + + */ + public function __construct(string $driver, string $gateway, array $data) + { + $this->data = $data; + + parent::__construct($driver, $gateway); + } +} diff --git a/vendor/yansongda/pay/src/Exceptions/BusinessException.php b/vendor/yansongda/pay/src/Exceptions/BusinessException.php new file mode 100644 index 0000000..8e94a4f --- /dev/null +++ b/vendor/yansongda/pay/src/Exceptions/BusinessException.php @@ -0,0 +1,19 @@ + + * + * @param string $message + * @param array|string $raw + */ + public function __construct($message, $raw = []) + { + parent::__construct('ERROR_BUSINESS: '.$message, $raw, self::ERROR_BUSINESS); + } +} diff --git a/vendor/yansongda/pay/src/Exceptions/Exception.php b/vendor/yansongda/pay/src/Exceptions/Exception.php new file mode 100644 index 0000000..9d007ad --- /dev/null +++ b/vendor/yansongda/pay/src/Exceptions/Exception.php @@ -0,0 +1,44 @@ + + * + * @param string $message + * @param array|string $raw + * @param int|string $code + */ + public function __construct($message = '', $raw = [], $code = self::UNKNOWN_ERROR) + { + $message = '' === $message ? 'Unknown Error' : $message; + $this->raw = is_array($raw) ? $raw : [$raw]; + + parent::__construct($message, intval($code)); + } +} diff --git a/vendor/yansongda/pay/src/Exceptions/GatewayException.php b/vendor/yansongda/pay/src/Exceptions/GatewayException.php new file mode 100644 index 0000000..e844801 --- /dev/null +++ b/vendor/yansongda/pay/src/Exceptions/GatewayException.php @@ -0,0 +1,20 @@ + + * + * @param string $message + * @param array|string $raw + * @param int $code + */ + public function __construct($message, $raw = [], $code = self::ERROR_GATEWAY) + { + parent::__construct('ERROR_GATEWAY: '.$message, $raw, $code); + } +} diff --git a/vendor/yansongda/pay/src/Exceptions/InvalidArgumentException.php b/vendor/yansongda/pay/src/Exceptions/InvalidArgumentException.php new file mode 100644 index 0000000..2ecb16b --- /dev/null +++ b/vendor/yansongda/pay/src/Exceptions/InvalidArgumentException.php @@ -0,0 +1,19 @@ + + * + * @param string $message + * @param array|string $raw + */ + public function __construct($message, $raw = []) + { + parent::__construct('INVALID_ARGUMENT: '.$message, $raw, self::INVALID_ARGUMENT); + } +} diff --git a/vendor/yansongda/pay/src/Exceptions/InvalidConfigException.php b/vendor/yansongda/pay/src/Exceptions/InvalidConfigException.php new file mode 100644 index 0000000..5719310 --- /dev/null +++ b/vendor/yansongda/pay/src/Exceptions/InvalidConfigException.php @@ -0,0 +1,19 @@ + + * + * @param string $message + * @param array|string $raw + */ + public function __construct($message, $raw = []) + { + parent::__construct('INVALID_CONFIG: '.$message, $raw, self::INVALID_CONFIG); + } +} diff --git a/vendor/yansongda/pay/src/Exceptions/InvalidGatewayException.php b/vendor/yansongda/pay/src/Exceptions/InvalidGatewayException.php new file mode 100644 index 0000000..3f4067d --- /dev/null +++ b/vendor/yansongda/pay/src/Exceptions/InvalidGatewayException.php @@ -0,0 +1,19 @@ + + * + * @param string $message + * @param array|string $raw + */ + public function __construct($message, $raw = []) + { + parent::__construct('INVALID_GATEWAY: '.$message, $raw, self::INVALID_GATEWAY); + } +} diff --git a/vendor/yansongda/pay/src/Exceptions/InvalidSignException.php b/vendor/yansongda/pay/src/Exceptions/InvalidSignException.php new file mode 100644 index 0000000..135c8a5 --- /dev/null +++ b/vendor/yansongda/pay/src/Exceptions/InvalidSignException.php @@ -0,0 +1,19 @@ + + * + * @param string $message + * @param array|string $raw + */ + public function __construct($message, $raw = []) + { + parent::__construct('INVALID_SIGN: '.$message, $raw, self::INVALID_SIGN); + } +} diff --git a/vendor/yansongda/pay/src/Gateways/Alipay.php b/vendor/yansongda/pay/src/Gateways/Alipay.php new file mode 100644 index 0000000..49b0e24 --- /dev/null +++ b/vendor/yansongda/pay/src/Gateways/Alipay.php @@ -0,0 +1,423 @@ + 'https://openapi.alipay.com/gateway.do?charset=utf-8', + self::MODE_SERVICE => 'https://openapi.alipay.com/gateway.do?charset=utf-8', + self::MODE_DEV => 'https://openapi.alipaydev.com/gateway.do?charset=utf-8', + ]; + + /** + * Alipay payload. + * + * @var array + */ + protected $payload; + + /** + * Alipay gateway. + * + * @var string + */ + protected $gateway; + + /** + * extends. + * + * @var array + */ + protected $extends; + + /** + * Bootstrap. + * + * @author yansongda + * + * @throws \Exception + */ + public function __construct(Config $config) + { + $this->gateway = Support::create($config)->getBaseUri(); + $this->payload = [ + 'app_id' => $config->get('app_id'), + 'method' => '', + 'format' => 'JSON', + 'charset' => 'utf-8', + 'sign_type' => 'RSA2', + 'version' => '1.0', + 'return_url' => $config->get('return_url'), + 'notify_url' => $config->get('notify_url'), + 'timestamp' => date('Y-m-d H:i:s'), + 'sign' => '', + 'biz_content' => '', + 'app_auth_token' => $config->get('app_auth_token'), + ]; + + if ($config->get('app_cert_public_key') && $config->get('alipay_root_cert')) { + $this->payload['app_cert_sn'] = Support::getCertSN($config->get('app_cert_public_key')); + $this->payload['alipay_root_cert_sn'] = Support::getRootCertSN($config->get('alipay_root_cert')); + } + } + + /** + * Magic pay. + * + * @author yansongda + * + * @param string $method + * @param array $params + * + * @throws GatewayException + * @throws InvalidArgumentException + * @throws InvalidConfigException + * @throws InvalidGatewayException + * @throws InvalidSignException + * + * @return Response|Collection + */ + public function __call($method, $params) + { + if (isset($this->extends[$method])) { + return $this->makeExtend($method, ...$params); + } + + return $this->pay($method, ...$params); + } + + /** + * Pay an order. + * + * @author yansongda + * + * @param string $gateway + * @param array $params + * + * @throws InvalidGatewayException + * + * @return Response|Collection + */ + public function pay($gateway, $params = []) + { + Events::dispatch(new Events\PayStarting('Alipay', $gateway, $params)); + + $this->payload['return_url'] = $params['return_url'] ?? $this->payload['return_url']; + $this->payload['notify_url'] = $params['notify_url'] ?? $this->payload['notify_url']; + + unset($params['return_url'], $params['notify_url']); + + $this->payload['biz_content'] = json_encode($params); + + $gateway = get_class($this).'\\'.Str::studly($gateway).'Gateway'; + + if (class_exists($gateway)) { + return $this->makePay($gateway); + } + + throw new InvalidGatewayException("Pay Gateway [{$gateway}] not exists"); + } + + /** + * Verify sign. + * + * @author yansongda + * + * @param array|null $data + * + * @throws InvalidSignException + * @throws InvalidConfigException + */ + public function verify($data = null, bool $refund = false): Collection + { + if (is_null($data)) { + $request = Request::createFromGlobals(); + + $data = $request->request->count() > 0 ? $request->request->all() : $request->query->all(); + } + + if (isset($data['fund_bill_list'])) { + $data['fund_bill_list'] = htmlspecialchars_decode($data['fund_bill_list']); + } + + Events::dispatch(new Events\RequestReceived('Alipay', '', $data)); + + if (Support::verifySign($data)) { + return new Collection($data); + } + + Events::dispatch(new Events\SignFailed('Alipay', '', $data)); + + throw new InvalidSignException('Alipay Sign Verify FAILED', $data); + } + + /** + * Query an order. + * + * @author yansongda + * + * @param string|array $order + * + * @throws GatewayException + * @throws InvalidConfigException + * @throws InvalidSignException + */ + public function find($order, string $type = 'wap'): Collection + { + $gateway = get_class($this).'\\'.Str::studly($type).'Gateway'; + + if (!class_exists($gateway) || !is_callable([new $gateway(), 'find'])) { + throw new GatewayException("{$gateway} Done Not Exist Or Done Not Has FIND Method"); + } + + $config = call_user_func([new $gateway(), 'find'], $order); + + $this->payload['method'] = $config['method']; + $this->payload['biz_content'] = $config['biz_content']; + $this->payload['sign'] = Support::generateSign($this->payload); + + Events::dispatch(new Events\MethodCalled('Alipay', 'Find', $this->gateway, $this->payload)); + + return Support::requestApi($this->payload); + } + + /** + * Refund an order. + * + * @author yansongda + * + * @throws GatewayException + * @throws InvalidConfigException + * @throws InvalidSignException + */ + public function refund(array $order): Collection + { + $this->payload['method'] = 'alipay.trade.refund'; + $this->payload['biz_content'] = json_encode($order); + $this->payload['sign'] = Support::generateSign($this->payload); + + Events::dispatch(new Events\MethodCalled('Alipay', 'Refund', $this->gateway, $this->payload)); + + return Support::requestApi($this->payload); + } + + /** + * Cancel an order. + * + * @author yansongda + * + * @param array|string $order + * + * @throws GatewayException + * @throws InvalidConfigException + * @throws InvalidSignException + */ + public function cancel($order): Collection + { + $this->payload['method'] = 'alipay.trade.cancel'; + $this->payload['biz_content'] = json_encode(is_array($order) ? $order : ['out_trade_no' => $order]); + $this->payload['sign'] = Support::generateSign($this->payload); + + Events::dispatch(new Events\MethodCalled('Alipay', 'Cancel', $this->gateway, $this->payload)); + + return Support::requestApi($this->payload); + } + + /** + * Close an order. + * + * @param string|array $order + * + * @author yansongda + * + * @throws GatewayException + * @throws InvalidConfigException + * @throws InvalidSignException + */ + public function close($order): Collection + { + $this->payload['method'] = 'alipay.trade.close'; + $this->payload['biz_content'] = json_encode(is_array($order) ? $order : ['out_trade_no' => $order]); + $this->payload['sign'] = Support::generateSign($this->payload); + + Events::dispatch(new Events\MethodCalled('Alipay', 'Close', $this->gateway, $this->payload)); + + return Support::requestApi($this->payload); + } + + /** + * Download bill. + * + * @author yansongda + * + * @param string|array $bill + * + * @throws GatewayException + * @throws InvalidConfigException + * @throws InvalidSignException + */ + public function download($bill): string + { + $this->payload['method'] = 'alipay.data.dataservice.bill.downloadurl.query'; + $this->payload['biz_content'] = json_encode(is_array($bill) ? $bill : ['bill_type' => 'trade', 'bill_date' => $bill]); + $this->payload['sign'] = Support::generateSign($this->payload); + + Events::dispatch(new Events\MethodCalled('Alipay', 'Download', $this->gateway, $this->payload)); + + $result = Support::requestApi($this->payload); + + return ($result instanceof Collection) ? $result->get('bill_download_url') : ''; + } + + /** + * Reply success to alipay. + * + * @author yansongda + */ + public function success(): Response + { + Events::dispatch(new Events\MethodCalled('Alipay', 'Success', $this->gateway)); + + return new Response('success'); + } + + /** + * extend. + * + * @author yansongda + * + * @throws GatewayException + * @throws InvalidConfigException + * @throws InvalidSignException + * @throws InvalidArgumentException + */ + public function extend(string $method, callable $function, bool $now = true, bool $response = false): ?Collection + { + if (!$now && !method_exists($this, $method)) { + $this->extends[$method] = $function; + + return null; + } + + $customize = $function($this->payload); + + if (!is_array($customize) && !($customize instanceof Collection)) { + throw new InvalidArgumentException('Return Type Must Be Array Or Collection'); + } + + Events::dispatch(new Events\MethodCalled('Alipay', 'extend', $this->gateway, $customize)); + + if (is_array($customize)) { + $this->payload = $customize; + $this->payload['sign'] = Support::generateSign($this->payload); + + return Support::requestApi($this->payload, $response); + } + + return $customize; + } + + /** + * Make pay gateway. + * + * @author yansongda + * + * @throws InvalidGatewayException + * + * @return Response|Collection + */ + protected function makePay(string $gateway) + { + $app = new $gateway(); + + if ($app instanceof GatewayInterface) { + return $app->pay($this->gateway, array_filter($this->payload, function ($value) { + return '' !== $value && !is_null($value); + })); + } + + throw new InvalidGatewayException("Pay Gateway [{$gateway}] Must Be An Instance Of GatewayInterface"); + } + + /** + * makeExtend. + * + * @author yansongda + * + * @throws GatewayException + * @throws InvalidArgumentException + * @throws InvalidConfigException + * @throws InvalidSignException + */ + protected function makeExtend(string $method, array ...$params): Collection + { + $params = count($params) >= 1 ? $params[0] : $params; + + $function = $this->extends[$method]; + + $customize = $function($this->payload, $params); + + if (!is_array($customize) && !($customize instanceof Collection)) { + throw new InvalidArgumentException('Return Type Must Be Array Or Collection'); + } + + Events::dispatch(new Events\MethodCalled( + 'Alipay', + 'extend - '.$method, + $this->gateway, + is_array($customize) ? $customize : $customize->toArray() + )); + + if (is_array($customize)) { + $this->payload = $customize; + $this->payload['sign'] = Support::generateSign($this->payload); + + return Support::requestApi($this->payload); + } + + return $customize; + } +} diff --git a/vendor/yansongda/pay/src/Gateways/Alipay/AppGateway.php b/vendor/yansongda/pay/src/Gateways/Alipay/AppGateway.php new file mode 100644 index 0000000..860b44b --- /dev/null +++ b/vendor/yansongda/pay/src/Gateways/Alipay/AppGateway.php @@ -0,0 +1,38 @@ + + * + * @param string $endpoint + * + * @throws InvalidConfigException + * @throws InvalidArgumentException + */ + public function pay($endpoint, array $payload): Response + { + $payload['method'] = 'alipay.trade.app.pay'; + + $biz_array = json_decode($payload['biz_content'], true); + if ((Alipay::MODE_SERVICE === $this->mode) && (!empty(Support::getInstance()->pid))) { + $biz_array['extend_params'] = is_array($biz_array['extend_params']) ? array_merge(['sys_service_provider_id' => Support::getInstance()->pid], $biz_array['extend_params']) : ['sys_service_provider_id' => Support::getInstance()->pid]; + } + $payload['biz_content'] = json_encode(array_merge($biz_array, ['product_code' => 'QUICK_MSECURITY_PAY'])); + $payload['sign'] = Support::generateSign($payload); + + Events::dispatch(new Events\PayStarted('Alipay', 'App', $endpoint, $payload)); + + return new Response(http_build_query($payload)); + } +} diff --git a/vendor/yansongda/pay/src/Gateways/Alipay/Gateway.php b/vendor/yansongda/pay/src/Gateways/Alipay/Gateway.php new file mode 100644 index 0000000..5f57cc3 --- /dev/null +++ b/vendor/yansongda/pay/src/Gateways/Alipay/Gateway.php @@ -0,0 +1,40 @@ + + * + * @throws InvalidArgumentException + */ + public function __construct() + { + $this->mode = Support::getInstance()->mode; + } + + /** + * Pay an order. + * + * @author yansongda + * + * @param string $endpoint + * + * @return Collection + */ + abstract public function pay($endpoint, array $payload); +} diff --git a/vendor/yansongda/pay/src/Gateways/Alipay/MiniGateway.php b/vendor/yansongda/pay/src/Gateways/Alipay/MiniGateway.php new file mode 100644 index 0000000..0198194 --- /dev/null +++ b/vendor/yansongda/pay/src/Gateways/Alipay/MiniGateway.php @@ -0,0 +1,46 @@ + + * + * @param string $endpoint + * + * @throws GatewayException + * @throws InvalidArgumentException + * @throws InvalidConfigException + * @throws InvalidSignException + * + * @see https://docs.alipay.com/mini/introduce/pay + */ + public function pay($endpoint, array $payload): Collection + { + $biz_array = json_decode($payload['biz_content'], true); + if (empty($biz_array['buyer_id'])) { + throw new InvalidArgumentException('buyer_id required'); + } + if ((Alipay::MODE_SERVICE === $this->mode) && (!empty(Support::getInstance()->pid))) { + $biz_array['extend_params'] = is_array($biz_array['extend_params']) ? array_merge(['sys_service_provider_id' => Support::getInstance()->pid], $biz_array['extend_params']) : ['sys_service_provider_id' => Support::getInstance()->pid]; + } + $payload['biz_content'] = json_encode($biz_array); + $payload['method'] = 'alipay.trade.create'; + $payload['sign'] = Support::generateSign($payload); + + Events::dispatch(new Events\PayStarted('Alipay', 'Mini', $endpoint, $payload)); + + return Support::requestApi($payload); + } +} diff --git a/vendor/yansongda/pay/src/Gateways/Alipay/PosGateway.php b/vendor/yansongda/pay/src/Gateways/Alipay/PosGateway.php new file mode 100644 index 0000000..de04761 --- /dev/null +++ b/vendor/yansongda/pay/src/Gateways/Alipay/PosGateway.php @@ -0,0 +1,47 @@ + + * + * @param string $endpoint + * + * @throws InvalidArgumentException + * @throws GatewayException + * @throws InvalidConfigException + * @throws InvalidSignException + */ + public function pay($endpoint, array $payload): Collection + { + $payload['method'] = 'alipay.trade.pay'; + $biz_array = json_decode($payload['biz_content'], true); + if ((Alipay::MODE_SERVICE === $this->mode) && (!empty(Support::getInstance()->pid))) { + $biz_array['extend_params'] = is_array($biz_array['extend_params']) ? array_merge(['sys_service_provider_id' => Support::getInstance()->pid], $biz_array['extend_params']) : ['sys_service_provider_id' => Support::getInstance()->pid]; + } + $payload['biz_content'] = json_encode(array_merge( + $biz_array, + [ + 'product_code' => 'FACE_TO_FACE_PAYMENT', + 'scene' => 'bar_code', + ] + )); + $payload['sign'] = Support::generateSign($payload); + + Events::dispatch(new Events\PayStarted('Alipay', 'Pos', $endpoint, $payload)); + + return Support::requestApi($payload); + } +} diff --git a/vendor/yansongda/pay/src/Gateways/Alipay/RefundGateway.php b/vendor/yansongda/pay/src/Gateways/Alipay/RefundGateway.php new file mode 100644 index 0000000..abbb71f --- /dev/null +++ b/vendor/yansongda/pay/src/Gateways/Alipay/RefundGateway.php @@ -0,0 +1,21 @@ + + * + * @param $order + */ + public function find($order): array + { + return [ + 'method' => 'alipay.trade.fastpay.refund.query', + 'biz_content' => json_encode(is_array($order) ? $order : ['out_trade_no' => $order]), + ]; + } +} diff --git a/vendor/yansongda/pay/src/Gateways/Alipay/ScanGateway.php b/vendor/yansongda/pay/src/Gateways/Alipay/ScanGateway.php new file mode 100644 index 0000000..9d6cbc3 --- /dev/null +++ b/vendor/yansongda/pay/src/Gateways/Alipay/ScanGateway.php @@ -0,0 +1,41 @@ + + * + * @param string $endpoint + * + * @throws GatewayException + * @throws InvalidArgumentException + * @throws InvalidConfigException + * @throws InvalidSignException + */ + public function pay($endpoint, array $payload): Collection + { + $payload['method'] = 'alipay.trade.precreate'; + $biz_array = json_decode($payload['biz_content'], true); + if ((Alipay::MODE_SERVICE === $this->mode) && (!empty(Support::getInstance()->pid))) { + $biz_array['extend_params'] = is_array($biz_array['extend_params']) ? array_merge(['sys_service_provider_id' => Support::getInstance()->pid], $biz_array['extend_params']) : ['sys_service_provider_id' => Support::getInstance()->pid]; + } + $payload['biz_content'] = json_encode(array_merge($biz_array, ['product_code' => ''])); + $payload['sign'] = Support::generateSign($payload); + + Events::dispatch(new Events\PayStarted('Alipay', 'Scan', $endpoint, $payload)); + + return Support::requestApi($payload); + } +} diff --git a/vendor/yansongda/pay/src/Gateways/Alipay/Support.php b/vendor/yansongda/pay/src/Gateways/Alipay/Support.php new file mode 100644 index 0000000..c496b4a --- /dev/null +++ b/vendor/yansongda/pay/src/Gateways/Alipay/Support.php @@ -0,0 +1,456 @@ + + * + * @property string app_id alipay app_id + * @property string ali_public_key + * @property string private_key + * @property array http http options + * @property string mode current mode + * @property array log log options + * @property string pid ali pid + */ +class Support +{ + use HasHttpRequest; + + /** + * Alipay gateway. + * + * @var string + */ + protected $baseUri; + + /** + * Config. + * + * @var Config + */ + protected $config; + + /** + * Instance. + * + * @var Support + */ + private static $instance; + + /** + * Bootstrap. + * + * @author yansongda + */ + private function __construct(Config $config) + { + $this->baseUri = Alipay::URL[$config->get('mode', Alipay::MODE_NORMAL)]; + $this->config = $config; + + $this->setHttpOptions(); + } + + /** + * __get. + * + * @author yansongda + * + * @param $key + * + * @return mixed|Config|null + */ + public function __get($key) + { + return $this->getConfig($key); + } + + /** + * create. + * + * @author yansongda + * + * @return Support + */ + public static function create(Config $config) + { + if ('cli' === php_sapi_name() || !(self::$instance instanceof self)) { + self::$instance = new self($config); + } + + return self::$instance; + } + + /** + * getInstance. + * + * @author yansongda + * + * @throws InvalidArgumentException + * + * @return Support + */ + public static function getInstance() + { + if (is_null(self::$instance)) { + throw new InvalidArgumentException('You Should [Create] First Before Using'); + } + + return self::$instance; + } + + /** + * clear. + * + * @author yansongda + */ + public function clear() + { + self::$instance = null; + } + + /** + * Get Alipay API result. + * + * @author yansongda + * + * @throws GatewayException + * @throws InvalidConfigException + * @throws InvalidSignException + */ + public static function requestApi(array $data, bool $response = false): Collection + { + Events::dispatch(new Events\ApiRequesting('Alipay', '', self::$instance->getBaseUri(), $data)); + + $data = array_filter($data, function ($value) { + return ('' == $value || is_null($value)) ? false : true; + }); + + $result = json_decode(self::$instance->post('', $data), true); + + Events::dispatch(new Events\ApiRequested('Alipay', '', self::$instance->getBaseUri(), $result)); + + return self::processingApiResult($data, $result, $response); + } + + /** + * Generate sign. + * + * @author yansongda + * + * @throws InvalidConfigException + */ + public static function generateSign(array $params): string + { + $privateKey = self::$instance->private_key; + + if (is_null($privateKey)) { + throw new InvalidConfigException('Missing Alipay Config -- [private_key]'); + } + + if (Str::endsWith($privateKey, '.pem')) { + $privateKey = openssl_pkey_get_private( + Str::startsWith($privateKey, 'file://') ? $privateKey : 'file://'.$privateKey + ); + } else { + $privateKey = "-----BEGIN RSA PRIVATE KEY-----\n". + wordwrap($privateKey, 64, "\n", true). + "\n-----END RSA PRIVATE KEY-----"; + } + + openssl_sign(self::getSignContent($params), $sign, $privateKey, OPENSSL_ALGO_SHA256); + + $sign = base64_encode($sign); + + Log::debug('Alipay Generate Sign', [$params, $sign]); + + if (is_resource($privateKey)) { + openssl_free_key($privateKey); + } + + return $sign; + } + + /** + * Verify sign. + * + * @author yansongda + * + * @param bool $sync + * @param string|null $sign + * + * @throws InvalidConfigException + */ + public static function verifySign(array $data, $sync = false, $sign = null): bool + { + $publicKey = self::$instance->ali_public_key; + + if (is_null($publicKey)) { + throw new InvalidConfigException('Missing Alipay Config -- [ali_public_key]'); + } + + if (Str::endsWith($publicKey, '.crt')) { + $publicKey = file_get_contents($publicKey); + } elseif (Str::endsWith($publicKey, '.pem')) { + $publicKey = openssl_pkey_get_public( + Str::startsWith($publicKey, 'file://') ? $publicKey : 'file://'.$publicKey + ); + } else { + $publicKey = "-----BEGIN PUBLIC KEY-----\n". + wordwrap($publicKey, 64, "\n", true). + "\n-----END PUBLIC KEY-----"; + } + + $sign = $sign ?? $data['sign']; + + $toVerify = $sync ? json_encode($data, JSON_UNESCAPED_UNICODE) : self::getSignContent($data, true); + + $isVerify = 1 === openssl_verify($toVerify, base64_decode($sign), $publicKey, OPENSSL_ALGO_SHA256); + + if (is_resource($publicKey)) { + openssl_free_key($publicKey); + } + + return $isVerify; + } + + /** + * Get signContent that is to be signed. + * + * @author yansongda + * + * @param bool $verify + */ + public static function getSignContent(array $data, $verify = false): string + { + ksort($data); + + $stringToBeSigned = ''; + foreach ($data as $k => $v) { + if ($verify && 'sign' != $k && 'sign_type' != $k) { + $stringToBeSigned .= $k.'='.$v.'&'; + } + if (!$verify && '' !== $v && !is_null($v) && 'sign' != $k && '@' != substr($v, 0, 1)) { + $stringToBeSigned .= $k.'='.$v.'&'; + } + } + + Log::debug('Alipay Generate Sign Content Before Trim', [$data, $stringToBeSigned]); + + return trim($stringToBeSigned, '&'); + } + + /** + * Convert encoding. + * + * @author yansongda + * + * @param string|array $data + * @param string $to + * @param string $from + */ + public static function encoding($data, $to = 'utf-8', $from = 'gb2312'): array + { + return Arr::encoding((array) $data, $to, $from); + } + + /** + * Get service config. + * + * @author yansongda + * + * @param string|null $key + * @param mixed|null $default + * + * @return mixed|null + */ + public function getConfig($key = null, $default = null) + { + if (is_null($key)) { + return $this->config->all(); + } + + if ($this->config->has($key)) { + return $this->config[$key]; + } + + return $default; + } + + /** + * Get Base Uri. + * + * @author yansongda + * + * @return string + */ + public function getBaseUri() + { + return $this->baseUri; + } + + /** + * 生成应用证书SN. + * + * @author 大冰 https://sbing.vip/archives/2019-new-alipay-php-docking.html + * + * @param $certPath + * + * @throws /Exception + */ + public static function getCertSN($certPath): string + { + if (!is_file($certPath)) { + throw new Exception('unknown certPath -- [getCertSN]'); + } + $x509data = file_get_contents($certPath); + if (false === $x509data) { + throw new Exception('Alipay CertSN Error -- [getCertSN]'); + } + openssl_x509_read($x509data); + $certdata = openssl_x509_parse($x509data); + if (empty($certdata)) { + throw new Exception('Alipay openssl_x509_parse Error -- [getCertSN]'); + } + $issuer_arr = []; + foreach ($certdata['issuer'] as $key => $val) { + $issuer_arr[] = $key.'='.$val; + } + $issuer = implode(',', array_reverse($issuer_arr)); + Log::debug('getCertSN:', [$certPath, $issuer, $certdata['serialNumber']]); + + return md5($issuer.$certdata['serialNumber']); + } + + /** + * 生成支付宝根证书SN. + * + * @author 大冰 https://sbing.vip/archives/2019-new-alipay-php-docking.html + * + * @param $certPath + * + * @return string + * + * @throws /Exception + */ + public static function getRootCertSN($certPath) + { + if (!is_file($certPath)) { + throw new Exception('unknown certPath -- [getRootCertSN]'); + } + $x509data = file_get_contents($certPath); + if (false === $x509data) { + throw new Exception('Alipay CertSN Error -- [getRootCertSN]'); + } + $kCertificateEnd = '-----END CERTIFICATE-----'; + $certStrList = explode($kCertificateEnd, $x509data); + $md5_arr = []; + foreach ($certStrList as $one) { + if (!empty(trim($one))) { + $_x509data = $one.$kCertificateEnd; + openssl_x509_read($_x509data); + $_certdata = openssl_x509_parse($_x509data); + if (in_array($_certdata['signatureTypeSN'], ['RSA-SHA256', 'RSA-SHA1'])) { + $issuer_arr = []; + foreach ($_certdata['issuer'] as $key => $val) { + $issuer_arr[] = $key.'='.$val; + } + $_issuer = implode(',', array_reverse($issuer_arr)); + if (0 === strpos($_certdata['serialNumber'], '0x')) { + $serialNumber = self::bchexdec($_certdata['serialNumber']); + } else { + $serialNumber = $_certdata['serialNumber']; + } + $md5_arr[] = md5($_issuer.$serialNumber); + Log::debug('getRootCertSN Sub:', [$certPath, $_issuer, $serialNumber]); + } + } + } + + return implode('_', $md5_arr); + } + + /** + * processingApiResult. + * + * @author yansongda + * + * @param $data + * @param $result + * + * @throws GatewayException + * @throws InvalidConfigException + * @throws InvalidSignException + */ + protected static function processingApiResult($data, $result, $response = false): Collection + { + if ($response) { + return new Collection($result); + } + + $method = str_replace('.', '_', $data['method']).'_response'; + + if (!isset($result['sign']) || '10000' != $result[$method]['code']) { + throw new GatewayException('Get Alipay API Error:'.$result[$method]['msg'].(isset($result[$method]['sub_code']) ? (' - '.$result[$method]['sub_code']) : ''), $result); + } + + if (self::verifySign($result[$method], true, $result['sign'])) { + return new Collection($result[$method]); + } + + Events::dispatch(new Events\SignFailed('Alipay', '', $result)); + + throw new InvalidSignException('Alipay Sign Verify FAILED', $result); + } + + /** + * Set Http options. + * + * @author yansongda + */ + protected function setHttpOptions(): self + { + if ($this->config->has('http') && is_array($this->config->get('http'))) { + $this->config->forget('http.base_uri'); + $this->httpOptions = $this->config->get('http'); + } + + return $this; + } + + /** + * 0x转高精度数字. + * + * @author 大冰 https://sbing.vip/archives/2019-new-alipay-php-docking.html + * + * @param $hex + * + * @return int|string + */ + private static function bchexdec($hex) + { + $dec = 0; + $len = strlen($hex); + for ($i = 1; $i <= $len; ++$i) { + if (ctype_xdigit($hex[$i - 1])) { + $dec = bcadd($dec, bcmul(strval(hexdec($hex[$i - 1])), bcpow('16', strval($len - $i)))); + } + } + + return str_replace('.00', '', $dec); + } +} diff --git a/vendor/yansongda/pay/src/Gateways/Alipay/TransferGateway.php b/vendor/yansongda/pay/src/Gateways/Alipay/TransferGateway.php new file mode 100644 index 0000000..1015ac3 --- /dev/null +++ b/vendor/yansongda/pay/src/Gateways/Alipay/TransferGateway.php @@ -0,0 +1,49 @@ + + * + * @param string $endpoint + * + * @throws GatewayException + * @throws InvalidConfigException + * @throws InvalidSignException + */ + public function pay($endpoint, array $payload): Collection + { + $payload['method'] = 'alipay.fund.trans.uni.transfer'; + $payload['sign'] = Support::generateSign($payload); + + Events::dispatch(new Events\PayStarted('Alipay', 'Transfer', $endpoint, $payload)); + + return Support::requestApi($payload); + } + + /** + * Find. + * + * @author yansongda + * + * @param $order + */ + public function find($order): array + { + return [ + 'method' => 'alipay.fund.trans.order.query', + 'biz_content' => json_encode(is_array($order) ? $order : ['out_biz_no' => $order]), + ]; + } +} diff --git a/vendor/yansongda/pay/src/Gateways/Alipay/WapGateway.php b/vendor/yansongda/pay/src/Gateways/Alipay/WapGateway.php new file mode 100644 index 0000000..dda8c65 --- /dev/null +++ b/vendor/yansongda/pay/src/Gateways/Alipay/WapGateway.php @@ -0,0 +1,26 @@ + + */ + protected function getMethod(): string + { + return 'alipay.trade.wap.pay'; + } + + /** + * Get productCode config. + * + * @author yansongda + */ + protected function getProductCode(): string + { + return 'QUICK_WAP_WAY'; + } +} diff --git a/vendor/yansongda/pay/src/Gateways/Alipay/WebGateway.php b/vendor/yansongda/pay/src/Gateways/Alipay/WebGateway.php new file mode 100644 index 0000000..37a0cd9 --- /dev/null +++ b/vendor/yansongda/pay/src/Gateways/Alipay/WebGateway.php @@ -0,0 +1,104 @@ + + * + * @param string $endpoint + * + * @throws InvalidConfigException + * @throws InvalidArgumentException + */ + public function pay($endpoint, array $payload): Response + { + $biz_array = json_decode($payload['biz_content'], true); + $biz_array['product_code'] = $this->getProductCode(); + + $method = $biz_array['http_method'] ?? 'POST'; + + unset($biz_array['http_method']); + if ((Alipay::MODE_SERVICE === $this->mode) && (!empty(Support::getInstance()->pid))) { + $biz_array['extend_params'] = is_array($biz_array['extend_params']) ? array_merge(['sys_service_provider_id' => Support::getInstance()->pid], $biz_array['extend_params']) : ['sys_service_provider_id' => Support::getInstance()->pid]; + } + $payload['method'] = $this->getMethod(); + $payload['biz_content'] = json_encode($biz_array); + $payload['sign'] = Support::generateSign($payload); + + Events::dispatch(new Events\PayStarted('Alipay', 'Web/Wap', $endpoint, $payload)); + + return $this->buildPayHtml($endpoint, $payload, $method); + } + + /** + * Find. + * + * @author yansongda + * + * @param $order + */ + public function find($order): array + { + return [ + 'method' => 'alipay.trade.query', + 'biz_content' => json_encode(is_array($order) ? $order : ['out_trade_no' => $order]), + ]; + } + + /** + * Build Html response. + * + * @author yansongda + * + * @param string $endpoint + * @param array $payload + * @param string $method + */ + protected function buildPayHtml($endpoint, $payload, $method = 'POST'): Response + { + if ('GET' === strtoupper($method)) { + return new RedirectResponse($endpoint.'&'.http_build_query($payload)); + } + + $sHtml = "
        "; + foreach ($payload as $key => $val) { + $val = str_replace("'", ''', $val); + $sHtml .= ""; + } + $sHtml .= "
        "; + $sHtml .= ""; + + return new Response($sHtml); + } + + /** + * Get method config. + * + * @author yansongda + */ + protected function getMethod(): string + { + return 'alipay.trade.page.pay'; + } + + /** + * Get productCode config. + * + * @author yansongda + */ + protected function getProductCode(): string + { + return 'FAST_INSTANT_TRADE_PAY'; + } +} diff --git a/vendor/yansongda/pay/src/Gateways/Wechat.php b/vendor/yansongda/pay/src/Gateways/Wechat.php new file mode 100644 index 0000000..9873da2 --- /dev/null +++ b/vendor/yansongda/pay/src/Gateways/Wechat.php @@ -0,0 +1,365 @@ + 'https://api.mch.weixin.qq.com/', + self::MODE_DEV => 'https://api.mch.weixin.qq.com/sandboxnew/', + self::MODE_HK => 'https://apihk.mch.weixin.qq.com/', + self::MODE_SERVICE => 'https://api.mch.weixin.qq.com/', + self::MODE_US => 'https://apius.mch.weixin.qq.com/', + ]; + + /** + * Wechat payload. + * + * @var array + */ + protected $payload; + + /** + * Wechat gateway. + * + * @var string + */ + protected $gateway; + + /** + * Bootstrap. + * + * @author yansongda + * + * @throws Exception + */ + public function __construct(Config $config) + { + $this->gateway = Support::create($config)->getBaseUri(); + $this->payload = [ + 'appid' => $config->get('app_id', ''), + 'mch_id' => $config->get('mch_id', ''), + 'nonce_str' => Str::random(), + 'notify_url' => $config->get('notify_url', ''), + 'sign' => '', + 'trade_type' => '', + 'spbill_create_ip' => Request::createFromGlobals()->getClientIp(), + ]; + + if ($config->get('mode', self::MODE_NORMAL) === static::MODE_SERVICE) { + $this->payload = array_merge($this->payload, [ + 'sub_mch_id' => $config->get('sub_mch_id'), + 'sub_appid' => $config->get('sub_app_id', ''), + ]); + } + } + + /** + * Magic pay. + * + * @author yansongda + * + * @param string $method + * @param string $params + * + * @throws InvalidGatewayException + * + * @return Response|Collection + */ + public function __call($method, $params) + { + return self::pay($method, ...$params); + } + + /** + * Pay an order. + * + * @author yansongda + * + * @param string $gateway + * @param array $params + * + * @throws InvalidGatewayException + * + * @return Response|Collection + */ + public function pay($gateway, $params = []) + { + Events::dispatch(new Events\PayStarting('Wechat', $gateway, $params)); + + $this->payload = array_merge($this->payload, $params); + + $gateway = get_class($this).'\\'.Str::studly($gateway).'Gateway'; + + if (class_exists($gateway)) { + return $this->makePay($gateway); + } + + throw new InvalidGatewayException("Pay Gateway [{$gateway}] Not Exists"); + } + + /** + * Verify data. + * + * @author yansongda + * + * @param string|null $content + * + * @throws InvalidSignException + * @throws InvalidArgumentException + */ + public function verify($content = null, bool $refund = false): Collection + { + $content = $content ?? Request::createFromGlobals()->getContent(); + + Events::dispatch(new Events\RequestReceived('Wechat', '', [$content])); + + $data = Support::fromXml($content); + if ($refund) { + $decrypt_data = Support::decryptRefundContents($data['req_info']); + $data = array_merge(Support::fromXml($decrypt_data), $data); + } + + Log::debug('Resolved The Received Wechat Request Data', $data); + + if ($refund || Support::generateSign($data) === $data['sign']) { + return new Collection($data); + } + + Events::dispatch(new Events\SignFailed('Wechat', '', $data)); + + throw new InvalidSignException('Wechat Sign Verify FAILED', $data); + } + + /** + * Query an order. + * + * @author yansongda + * + * @param string|array $order + * + * @throws GatewayException + * @throws InvalidSignException + * @throws InvalidArgumentException + */ + public function find($order, string $type = 'wap'): Collection + { + if ('wap' != $type) { + unset($this->payload['spbill_create_ip']); + } + + $gateway = get_class($this).'\\'.Str::studly($type).'Gateway'; + + if (!class_exists($gateway) || !is_callable([new $gateway(), 'find'])) { + throw new GatewayException("{$gateway} Done Not Exist Or Done Not Has FIND Method"); + } + + $config = call_user_func([new $gateway(), 'find'], $order); + + $this->payload = Support::filterPayload($this->payload, $config['order']); + + Events::dispatch(new Events\MethodCalled('Wechat', 'Find', $this->gateway, $this->payload)); + + return Support::requestApi( + $config['endpoint'], + $this->payload, + $config['cert'] + ); + } + + /** + * Refund an order. + * + * @author yansongda + * + * @throws GatewayException + * @throws InvalidSignException + * @throws InvalidArgumentException + */ + public function refund(array $order): Collection + { + $this->payload = Support::filterPayload($this->payload, $order, true); + + Events::dispatch(new Events\MethodCalled('Wechat', 'Refund', $this->gateway, $this->payload)); + + return Support::requestApi( + 'secapi/pay/refund', + $this->payload, + true + ); + } + + /** + * Cancel an order. + * + * @author yansongda + * + * @param array $order + * + * @throws GatewayException + * @throws InvalidSignException + * @throws InvalidArgumentException + */ + public function cancel($order): Collection + { + unset($this->payload['spbill_create_ip']); + + $this->payload = Support::filterPayload($this->payload, $order); + + Events::dispatch(new Events\MethodCalled('Wechat', 'Cancel', $this->gateway, $this->payload)); + + return Support::requestApi( + 'secapi/pay/reverse', + $this->payload, + true + ); + } + + /** + * Close an order. + * + * @author yansongda + * + * @param string|array $order + * + * @throws GatewayException + * @throws InvalidSignException + * @throws InvalidArgumentException + */ + public function close($order): Collection + { + unset($this->payload['spbill_create_ip']); + + $this->payload = Support::filterPayload($this->payload, $order); + + Events::dispatch(new Events\MethodCalled('Wechat', 'Close', $this->gateway, $this->payload)); + + return Support::requestApi('pay/closeorder', $this->payload); + } + + /** + * Echo success to server. + * + * @author yansongda + * + * @throws InvalidArgumentException + */ + public function success(): Response + { + Events::dispatch(new Events\MethodCalled('Wechat', 'Success', $this->gateway)); + + return new Response( + Support::toXml(['return_code' => 'SUCCESS', 'return_msg' => 'OK']), + 200, + ['Content-Type' => 'application/xml'] + ); + } + + /** + * Download the bill. + * + * @author yansongda + * + * @throws GatewayException + * @throws InvalidArgumentException + */ + public function download(array $params): string + { + unset($this->payload['spbill_create_ip']); + + $this->payload = Support::filterPayload($this->payload, $params, true); + + Events::dispatch(new Events\MethodCalled('Wechat', 'Download', $this->gateway, $this->payload)); + + $result = Support::getInstance()->post( + 'pay/downloadbill', + Support::getInstance()->toXml($this->payload) + ); + + if (is_array($result)) { + throw new GatewayException('Get Wechat API Error: '.$result['return_msg'], $result); + } + + return $result; + } + + /** + * Make pay gateway. + * + * @author yansongda + * + * @param string $gateway + * + * @throws InvalidGatewayException + * + * @return Response|Collection + */ + protected function makePay($gateway) + { + $app = new $gateway(); + + if ($app instanceof GatewayInterface) { + return $app->pay($this->gateway, array_filter($this->payload, function ($value) { + return '' !== $value && !is_null($value); + })); + } + + throw new InvalidGatewayException("Pay Gateway [{$gateway}] Must Be An Instance Of GatewayInterface"); + } +} diff --git a/vendor/yansongda/pay/src/Gateways/Wechat/AppGateway.php b/vendor/yansongda/pay/src/Gateways/Wechat/AppGateway.php new file mode 100644 index 0000000..b37b965 --- /dev/null +++ b/vendor/yansongda/pay/src/Gateways/Wechat/AppGateway.php @@ -0,0 +1,62 @@ + + * + * @param string $endpoint + * + * @throws GatewayException + * @throws InvalidArgumentException + * @throws InvalidSignException + * @throws Exception + */ + public function pay($endpoint, array $payload): Response + { + $payload['appid'] = Support::getInstance()->appid; + $payload['trade_type'] = $this->getTradeType(); + + if (Wechat::MODE_SERVICE === $this->mode) { + $payload['sub_appid'] = Support::getInstance()->sub_appid; + } + + $pay_request = [ + 'appid' => Wechat::MODE_SERVICE === $this->mode ? $payload['sub_appid'] : $payload['appid'], + 'partnerid' => Wechat::MODE_SERVICE === $this->mode ? $payload['sub_mch_id'] : $payload['mch_id'], + 'prepayid' => $this->preOrder($payload)->get('prepay_id'), + 'timestamp' => strval(time()), + 'noncestr' => Str::random(), + 'package' => 'Sign=WXPay', + ]; + $pay_request['sign'] = Support::generateSign($pay_request); + + Events::dispatch(new Events\PayStarted('Wechat', 'App', $endpoint, $pay_request)); + + return new JsonResponse($pay_request); + } + + /** + * Get trade type config. + * + * @author yansongda + */ + protected function getTradeType(): string + { + return 'APP'; + } +} diff --git a/vendor/yansongda/pay/src/Gateways/Wechat/Gateway.php b/vendor/yansongda/pay/src/Gateways/Wechat/Gateway.php new file mode 100644 index 0000000..439a908 --- /dev/null +++ b/vendor/yansongda/pay/src/Gateways/Wechat/Gateway.php @@ -0,0 +1,88 @@ + + * + * @throws InvalidArgumentException + */ + public function __construct() + { + $this->mode = Support::getInstance()->mode; + } + + /** + * Pay an order. + * + * @author yansongda + * + * @param string $endpoint + * + * @return Collection + */ + abstract public function pay($endpoint, array $payload); + + /** + * Find. + * + * @author yansongda + * + * @param string|array $order + */ + public function find($order): array + { + return [ + 'endpoint' => 'pay/orderquery', + 'order' => is_array($order) ? $order : ['out_trade_no' => $order], + 'cert' => false, + ]; + } + + /** + * Get trade type config. + * + * @author yansongda + * + * @return string + */ + abstract protected function getTradeType(); + + /** + * Schedule an order. + * + * @author yansongda + * + * @param array $payload + * + * @throws GatewayException + * @throws InvalidArgumentException + * @throws InvalidSignException + */ + protected function preOrder($payload): Collection + { + $payload['sign'] = Support::generateSign($payload); + + Events::dispatch(new Events\MethodCalled('Wechat', 'PreOrder', '', $payload)); + + return Support::requestApi('pay/unifiedorder', $payload); + } +} diff --git a/vendor/yansongda/pay/src/Gateways/Wechat/GroupRedpackGateway.php b/vendor/yansongda/pay/src/Gateways/Wechat/GroupRedpackGateway.php new file mode 100644 index 0000000..8918416 --- /dev/null +++ b/vendor/yansongda/pay/src/Gateways/Wechat/GroupRedpackGateway.php @@ -0,0 +1,73 @@ + + * + * @param string $endpoint + * + * @throws GatewayException + * @throws InvalidArgumentException + * @throws InvalidSignException + */ + public function pay($endpoint, array $payload): Collection + { + $payload['wxappid'] = $payload['appid']; + $payload['amt_type'] = 'ALL_RAND'; + + if (Wechat::MODE_SERVICE === $this->mode) { + $payload['msgappid'] = $payload['appid']; + } + + unset($payload['appid'], $payload['trade_type'], + $payload['notify_url'], $payload['spbill_create_ip']); + + $payload['sign'] = Support::generateSign($payload); + + Events::dispatch(new Events\PayStarted('Wechat', 'Group Redpack', $endpoint, $payload)); + + return Support::requestApi( + 'mmpaymkttransfers/sendgroupredpack', + $payload, + true + ); + } + + /** + * Find. + * + * @author yansongda + * + * @param $billno + */ + public function find($billno): array + { + return [ + 'endpoint' => 'mmpaymkttransfers/gethbinfo', + 'order' => ['mch_billno' => $billno, 'bill_type' => 'MCHT'], + 'cert' => true, + ]; + } + + /** + * Get trade type config. + * + * @author yansongda + */ + protected function getTradeType(): string + { + return ''; + } +} diff --git a/vendor/yansongda/pay/src/Gateways/Wechat/MiniappGateway.php b/vendor/yansongda/pay/src/Gateways/Wechat/MiniappGateway.php new file mode 100644 index 0000000..8878dd2 --- /dev/null +++ b/vendor/yansongda/pay/src/Gateways/Wechat/MiniappGateway.php @@ -0,0 +1,35 @@ + + * + * @param string $endpoint + * + * @throws GatewayException + * @throws InvalidArgumentException + * @throws InvalidSignException + */ + public function pay($endpoint, array $payload): Collection + { + $payload['appid'] = Support::getInstance()->miniapp_id; + + if (Wechat::MODE_SERVICE === $this->mode) { + $payload['sub_appid'] = Support::getInstance()->sub_miniapp_id; + $this->payRequestUseSubAppId = true; + } + + return parent::pay($endpoint, $payload); + } +} diff --git a/vendor/yansongda/pay/src/Gateways/Wechat/MpGateway.php b/vendor/yansongda/pay/src/Gateways/Wechat/MpGateway.php new file mode 100644 index 0000000..1c7fd9d --- /dev/null +++ b/vendor/yansongda/pay/src/Gateways/Wechat/MpGateway.php @@ -0,0 +1,59 @@ + + * + * @param string $endpoint + * + * @throws GatewayException + * @throws InvalidArgumentException + * @throws InvalidSignException + * @throws Exception + */ + public function pay($endpoint, array $payload): Collection + { + $payload['trade_type'] = $this->getTradeType(); + + $pay_request = [ + 'appId' => !$this->payRequestUseSubAppId ? $payload['appid'] : $payload['sub_appid'], + 'timeStamp' => strval(time()), + 'nonceStr' => Str::random(), + 'package' => 'prepay_id='.$this->preOrder($payload)->get('prepay_id'), + 'signType' => 'MD5', + ]; + $pay_request['paySign'] = Support::generateSign($pay_request); + + Events::dispatch(new Events\PayStarted('Wechat', 'JSAPI', $endpoint, $pay_request)); + + return new Collection($pay_request); + } + + /** + * Get trade type config. + * + * @author yansongda + */ + protected function getTradeType(): string + { + return 'JSAPI'; + } +} diff --git a/vendor/yansongda/pay/src/Gateways/Wechat/PosGateway.php b/vendor/yansongda/pay/src/Gateways/Wechat/PosGateway.php new file mode 100644 index 0000000..01061b6 --- /dev/null +++ b/vendor/yansongda/pay/src/Gateways/Wechat/PosGateway.php @@ -0,0 +1,44 @@ + + * + * @param string $endpoint + * + * @throws GatewayException + * @throws InvalidArgumentException + * @throws InvalidSignException + */ + public function pay($endpoint, array $payload): Collection + { + unset($payload['trade_type'], $payload['notify_url']); + + $payload['sign'] = Support::generateSign($payload); + + Events::dispatch(new Events\PayStarted('Wechat', 'Pos', $endpoint, $payload)); + + return Support::requestApi('pay/micropay', $payload); + } + + /** + * Get trade type config. + * + * @author yansongda + */ + protected function getTradeType(): string + { + return 'MICROPAY'; + } +} diff --git a/vendor/yansongda/pay/src/Gateways/Wechat/RedpackGateway.php b/vendor/yansongda/pay/src/Gateways/Wechat/RedpackGateway.php new file mode 100644 index 0000000..c84ab1d --- /dev/null +++ b/vendor/yansongda/pay/src/Gateways/Wechat/RedpackGateway.php @@ -0,0 +1,77 @@ + + * + * @param string $endpoint + * + * @throws GatewayException + * @throws InvalidArgumentException + * @throws InvalidSignException + */ + public function pay($endpoint, array $payload): Collection + { + $payload['wxappid'] = $payload['appid']; + + if ('cli' !== php_sapi_name()) { + $payload['client_ip'] = Request::createFromGlobals()->server->get('SERVER_ADDR'); + } + + if (Wechat::MODE_SERVICE === $this->mode) { + $payload['msgappid'] = $payload['appid']; + } + + unset($payload['appid'], $payload['trade_type'], + $payload['notify_url'], $payload['spbill_create_ip']); + + $payload['sign'] = Support::generateSign($payload); + + Events::dispatch(new Events\PayStarted('Wechat', 'Redpack', $endpoint, $payload)); + + return Support::requestApi( + 'mmpaymkttransfers/sendredpack', + $payload, + true + ); + } + + /** + * Find. + * + * @author yansongda + * + * @param $billno + */ + public function find($billno): array + { + return [ + 'endpoint' => 'mmpaymkttransfers/gethbinfo', + 'order' => ['mch_billno' => $billno, 'bill_type' => 'MCHT'], + 'cert' => true, + ]; + } + + /** + * Get trade type config. + * + * @author yansongda + */ + protected function getTradeType(): string + { + return ''; + } +} diff --git a/vendor/yansongda/pay/src/Gateways/Wechat/RefundGateway.php b/vendor/yansongda/pay/src/Gateways/Wechat/RefundGateway.php new file mode 100644 index 0000000..e354cf5 --- /dev/null +++ b/vendor/yansongda/pay/src/Gateways/Wechat/RefundGateway.php @@ -0,0 +1,50 @@ + + * + * @param $order + */ + public function find($order): array + { + return [ + 'endpoint' => 'pay/refundquery', + 'order' => is_array($order) ? $order : ['out_trade_no' => $order], + 'cert' => false, + ]; + } + + /** + * Pay an order. + * + * @author yansongda + * + * @param string $endpoint + * + * @throws InvalidArgumentException + */ + public function pay($endpoint, array $payload) + { + throw new InvalidArgumentException('Not Support Refund In Pay'); + } + + /** + * Get trade type config. + * + * @author yansongda + * + * @throws InvalidArgumentException + */ + protected function getTradeType() + { + throw new InvalidArgumentException('Not Support Refund In Pay'); + } +} diff --git a/vendor/yansongda/pay/src/Gateways/Wechat/ScanGateway.php b/vendor/yansongda/pay/src/Gateways/Wechat/ScanGateway.php new file mode 100644 index 0000000..1338665 --- /dev/null +++ b/vendor/yansongda/pay/src/Gateways/Wechat/ScanGateway.php @@ -0,0 +1,44 @@ + + * + * @param string $endpoint + * + * @throws GatewayException + * @throws InvalidArgumentException + * @throws InvalidSignException + */ + public function pay($endpoint, array $payload): Collection + { + $payload['spbill_create_ip'] = Request::createFromGlobals()->server->get('SERVER_ADDR'); + $payload['trade_type'] = $this->getTradeType(); + + Events::dispatch(new Events\PayStarted('Wechat', 'Scan', $endpoint, $payload)); + + return $this->preOrder($payload); + } + + /** + * Get trade type config. + * + * @author yansongda + */ + protected function getTradeType(): string + { + return 'NATIVE'; + } +} diff --git a/vendor/yansongda/pay/src/Gateways/Wechat/Support.php b/vendor/yansongda/pay/src/Gateways/Wechat/Support.php new file mode 100644 index 0000000..72cc5ad --- /dev/null +++ b/vendor/yansongda/pay/src/Gateways/Wechat/Support.php @@ -0,0 +1,451 @@ + + * + * @property string appid + * @property string app_id + * @property string miniapp_id + * @property string sub_appid + * @property string sub_app_id + * @property string sub_miniapp_id + * @property string mch_id + * @property string sub_mch_id + * @property string key + * @property string return_url + * @property string cert_client + * @property string cert_key + * @property array log + * @property array http + * @property string mode + */ +class Support +{ + use HasHttpRequest; + + /** + * Wechat gateway. + * + * @var string + */ + protected $baseUri; + + /** + * Config. + * + * @var Config + */ + protected $config; + + /** + * Instance. + * + * @var Support + */ + private static $instance; + + /** + * Bootstrap. + * + * @author yansongda + */ + private function __construct(Config $config) + { + $this->baseUri = Wechat::URL[$config->get('mode', Wechat::MODE_NORMAL)]; + $this->config = $config; + + $this->setHttpOptions(); + } + + /** + * __get. + * + * @author yansongda + * + * @param $key + * + * @return mixed|Config|null + */ + public function __get($key) + { + return $this->getConfig($key); + } + + /** + * create. + * + * @author yansongda + * + * @throws GatewayException + * @throws InvalidArgumentException + * @throws InvalidSignException + * + * @return Support + */ + public static function create(Config $config) + { + if ('cli' === php_sapi_name() || !(self::$instance instanceof self)) { + self::$instance = new self($config); + + self::setDevKey(); + } + + return self::$instance; + } + + /** + * getInstance. + * + * @author yansongda + * + * @throws InvalidArgumentException + * + * @return Support + */ + public static function getInstance() + { + if (is_null(self::$instance)) { + throw new InvalidArgumentException('You Should [Create] First Before Using'); + } + + return self::$instance; + } + + /** + * clear. + * + * @author yansongda + */ + public static function clear() + { + self::$instance = null; + } + + /** + * Request wechat api. + * + * @author yansongda + * + * @param string $endpoint + * @param array $data + * @param bool $cert + * + * @throws GatewayException + * @throws InvalidArgumentException + * @throws InvalidSignException + */ + public static function requestApi($endpoint, $data, $cert = false): Collection + { + Events::dispatch(new Events\ApiRequesting('Wechat', '', self::$instance->getBaseUri().$endpoint, $data)); + + $result = self::$instance->post( + $endpoint, + self::toXml($data), + $cert ? [ + 'cert' => self::$instance->cert_client, + 'ssl_key' => self::$instance->cert_key, + ] : [] + ); + $result = is_array($result) ? $result : self::fromXml($result); + + Events::dispatch(new Events\ApiRequested('Wechat', '', self::$instance->getBaseUri().$endpoint, $result)); + + return self::processingApiResult($endpoint, $result); + } + + /** + * Filter payload. + * + * @author yansongda + * + * @param array $payload + * @param array|string $params + * @param bool $preserve_notify_url + * + * @throws InvalidArgumentException + */ + public static function filterPayload($payload, $params, $preserve_notify_url = false): array + { + $type = self::getTypeName($params['type'] ?? ''); + + $payload = array_merge( + $payload, + is_array($params) ? $params : ['out_trade_no' => $params] + ); + $payload['appid'] = self::$instance->getConfig($type, ''); + + if (Wechat::MODE_SERVICE === self::$instance->getConfig('mode', Wechat::MODE_NORMAL)) { + $payload['sub_appid'] = self::$instance->getConfig('sub_'.$type, ''); + } + + unset($payload['trade_type'], $payload['type']); + if (!$preserve_notify_url) { + unset($payload['notify_url']); + } + + $payload['sign'] = self::generateSign($payload); + + return $payload; + } + + /** + * Generate wechat sign. + * + * @author yansongda + * + * @param array $data + * + * @throws InvalidArgumentException + */ + public static function generateSign($data): string + { + $key = self::$instance->key; + + if (is_null($key)) { + throw new InvalidArgumentException('Missing Wechat Config -- [key]'); + } + + ksort($data); + + $string = md5(self::getSignContent($data).'&key='.$key); + + Log::debug('Wechat Generate Sign Before UPPER', [$data, $string]); + + return strtoupper($string); + } + + /** + * Generate sign content. + * + * @author yansongda + * + * @param array $data + */ + public static function getSignContent($data): string + { + $buff = ''; + + foreach ($data as $k => $v) { + $buff .= ('sign' != $k && '' != $v && !is_array($v)) ? $k.'='.$v.'&' : ''; + } + + Log::debug('Wechat Generate Sign Content Before Trim', [$data, $buff]); + + return trim($buff, '&'); + } + + /** + * Decrypt refund contents. + * + * @author yansongda + * + * @param string $contents + */ + public static function decryptRefundContents($contents): string + { + return openssl_decrypt( + base64_decode($contents), + 'AES-256-ECB', + md5(self::$instance->key), + OPENSSL_RAW_DATA + ); + } + + /** + * Convert array to xml. + * + * @author yansongda + * + * @param array $data + * + * @throws InvalidArgumentException + */ + public static function toXml($data): string + { + if (!is_array($data) || count($data) <= 0) { + throw new InvalidArgumentException('Convert To Xml Error! Invalid Array!'); + } + + $xml = ''; + foreach ($data as $key => $val) { + $xml .= is_numeric($val) ? '<'.$key.'>'.$val.'' : + '<'.$key.'>'; + } + $xml .= ''; + + return $xml; + } + + /** + * Convert xml to array. + * + * @author yansongda + * + * @param string $xml + * + * @throws InvalidArgumentException + */ + public static function fromXml($xml): array + { + if (!$xml) { + throw new InvalidArgumentException('Convert To Array Error! Invalid Xml!'); + } + + if (\PHP_VERSION_ID < 80000) { + libxml_disable_entity_loader(true); + } + + return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA), JSON_UNESCAPED_UNICODE), true); + } + + /** + * Get service config. + * + * @author yansongda + * + * @param string|null $key + * @param mixed|null $default + * + * @return mixed|null + */ + public function getConfig($key = null, $default = null) + { + if (is_null($key)) { + return $this->config->all(); + } + + if ($this->config->has($key)) { + return $this->config[$key]; + } + + return $default; + } + + /** + * Get app id according to param type. + * + * @author yansongda + * + * @param string $type + */ + public static function getTypeName($type = ''): string + { + switch ($type) { + case '': + $type = 'app_id'; + break; + case 'app': + $type = 'appid'; + break; + default: + $type = $type.'_id'; + } + + return $type; + } + + /** + * Get Base Uri. + * + * @author yansongda + * + * @return string + */ + public function getBaseUri() + { + return $this->baseUri; + } + + /** + * processingApiResult. + * + * @author yansongda + * + * @param $endpoint + * + * @throws GatewayException + * @throws InvalidArgumentException + * @throws InvalidSignException + * + * @return Collection + */ + protected static function processingApiResult($endpoint, array $result) + { + if (!isset($result['return_code']) || 'SUCCESS' != $result['return_code']) { + throw new GatewayException('Get Wechat API Error:'.($result['return_msg'] ?? $result['retmsg'] ?? ''), $result); + } + + if (isset($result['result_code']) && 'SUCCESS' != $result['result_code']) { + throw new BusinessException('Wechat Business Error: '.$result['err_code'].' - '.$result['err_code_des'], $result); + } + + if ('pay/getsignkey' === $endpoint || + false !== strpos($endpoint, 'mmpaymkttransfers') || + self::generateSign($result) === $result['sign']) { + return new Collection($result); + } + + Events::dispatch(new Events\SignFailed('Wechat', '', $result)); + + throw new InvalidSignException('Wechat Sign Verify FAILED', $result); + } + + /** + * setDevKey. + * + * @author yansongda + * + * @throws GatewayException + * @throws InvalidArgumentException + * @throws InvalidSignException + * @throws Exception + * + * @return Support + */ + private static function setDevKey() + { + if (Wechat::MODE_DEV == self::$instance->mode) { + $data = [ + 'mch_id' => self::$instance->mch_id, + 'nonce_str' => Str::random(), + ]; + $data['sign'] = self::generateSign($data); + + $result = self::requestApi('pay/getsignkey', $data); + + self::$instance->config->set('key', $result['sandbox_signkey']); + } + + return self::$instance; + } + + /** + * Set Http options. + * + * @author yansongda + */ + private function setHttpOptions(): self + { + if ($this->config->has('http') && is_array($this->config->get('http'))) { + $this->config->forget('http.base_uri'); + $this->httpOptions = $this->config->get('http'); + } + + return $this; + } +} diff --git a/vendor/yansongda/pay/src/Gateways/Wechat/TransferGateway.php b/vendor/yansongda/pay/src/Gateways/Wechat/TransferGateway.php new file mode 100644 index 0000000..bbd97b3 --- /dev/null +++ b/vendor/yansongda/pay/src/Gateways/Wechat/TransferGateway.php @@ -0,0 +1,80 @@ + + * + * @param string $endpoint + * + * @throws GatewayException + * @throws InvalidArgumentException + * @throws InvalidSignException + */ + public function pay($endpoint, array $payload): Collection + { + if (Wechat::MODE_SERVICE === $this->mode) { + unset($payload['sub_mch_id'], $payload['sub_appid']); + } + + $type = Support::getTypeName($payload['type'] ?? ''); + + $payload['mch_appid'] = Support::getInstance()->getConfig($type, ''); + $payload['mchid'] = $payload['mch_id']; + + if ('cli' !== php_sapi_name() && !isset($payload['spbill_create_ip'])) { + $payload['spbill_create_ip'] = Request::createFromGlobals()->server->get('SERVER_ADDR'); + } + + unset($payload['appid'], $payload['mch_id'], $payload['trade_type'], + $payload['notify_url'], $payload['type']); + + $payload['sign'] = Support::generateSign($payload); + + Events::dispatch(new Events\PayStarted('Wechat', 'Transfer', $endpoint, $payload)); + + return Support::requestApi( + 'mmpaymkttransfers/promotion/transfers', + $payload, + true + ); + } + + /** + * Find. + * + * @author yansongda + * + * @param $order + */ + public function find($order): array + { + return [ + 'endpoint' => 'mmpaymkttransfers/gettransferinfo', + 'order' => is_array($order) ? $order : ['partner_trade_no' => $order], + 'cert' => true, + ]; + } + + /** + * Get trade type config. + * + * @author yansongda + */ + protected function getTradeType(): string + { + return ''; + } +} diff --git a/vendor/yansongda/pay/src/Gateways/Wechat/WapGateway.php b/vendor/yansongda/pay/src/Gateways/Wechat/WapGateway.php new file mode 100644 index 0000000..0c0c920 --- /dev/null +++ b/vendor/yansongda/pay/src/Gateways/Wechat/WapGateway.php @@ -0,0 +1,47 @@ + + * + * @param string $endpoint + * + * @throws GatewayException + * @throws InvalidArgumentException + * @throws InvalidSignException + */ + public function pay($endpoint, array $payload): RedirectResponse + { + $payload['trade_type'] = $this->getTradeType(); + + Events::dispatch(new Events\PayStarted('Wechat', 'Wap', $endpoint, $payload)); + + $mweb_url = $this->preOrder($payload)->get('mweb_url'); + + $url = is_null(Support::getInstance()->return_url) ? $mweb_url : $mweb_url. + '&redirect_url='.urlencode(Support::getInstance()->return_url); + + return new RedirectResponse($url); + } + + /** + * Get trade type config. + * + * @author yansongda + */ + protected function getTradeType(): string + { + return 'MWEB'; + } +} diff --git a/vendor/yansongda/pay/src/Listeners/KernelLogSubscriber.php b/vendor/yansongda/pay/src/Listeners/KernelLogSubscriber.php new file mode 100644 index 0000000..2250de3 --- /dev/null +++ b/vendor/yansongda/pay/src/Listeners/KernelLogSubscriber.php @@ -0,0 +1,114 @@ + 'methodName') + * * array('eventName' => array('methodName', $priority)) + * * array('eventName' => array(array('methodName1', $priority), array('methodName2'))) + * + * @return array The event names to listen to + */ + public static function getSubscribedEvents() + { + return [ + Events\PayStarting::class => ['writePayStartingLog', 256], + Events\PayStarted::class => ['writePayStartedLog', 256], + Events\ApiRequesting::class => ['writeApiRequestingLog', 256], + Events\ApiRequested::class => ['writeApiRequestedLog', 256], + Events\SignFailed::class => ['writeSignFailedLog', 256], + Events\RequestReceived::class => ['writeRequestReceivedLog', 256], + Events\MethodCalled::class => ['writeMethodCalledLog', 256], + ]; + } + + /** + * writePayStartingLog. + * + * @author yansongda + */ + public function writePayStartingLog(Events\PayStarting $event) + { + Log::debug("Starting To {$event->driver}", [$event->gateway, $event->params]); + } + + /** + * writePayStartedLog. + * + * @author yansongda + */ + public function writePayStartedLog(Events\PayStarted $event) + { + Log::info( + "{$event->driver} {$event->gateway} Has Started", + [$event->endpoint, $event->payload] + ); + } + + /** + * writeApiRequestingLog. + * + * @author yansongda + */ + public function writeApiRequestingLog(Events\ApiRequesting $event) + { + Log::debug("Requesting To {$event->driver} Api", [$event->endpoint, $event->payload]); + } + + /** + * writeApiRequestedLog. + * + * @author yansongda + */ + public function writeApiRequestedLog(Events\ApiRequested $event) + { + Log::debug("Result Of {$event->driver} Api", $event->result); + } + + /** + * writeSignFailedLog. + * + * @author yansongda + */ + public function writeSignFailedLog(Events\SignFailed $event) + { + Log::warning("{$event->driver} Sign Verify FAILED", $event->data); + } + + /** + * writeRequestReceivedLog. + * + * @author yansongda + */ + public function writeRequestReceivedLog(Events\RequestReceived $event) + { + Log::info("Received {$event->driver} Request", $event->data); + } + + /** + * writeMethodCalledLog. + * + * @author yansongda + */ + public function writeMethodCalledLog(Events\MethodCalled $event) + { + Log::info("{$event->driver} {$event->gateway} Method Has Called", [$event->endpoint, $event->payload]); + } +} diff --git a/vendor/yansongda/pay/src/Log.php b/vendor/yansongda/pay/src/Log.php new file mode 100644 index 0000000..c1f8c3e --- /dev/null +++ b/vendor/yansongda/pay/src/Log.php @@ -0,0 +1,49 @@ + + * + * @param string $method + * @param array $args + * + * @return mixed + */ + public static function __callStatic($method, $args) + { + return forward_static_call_array([BaseLog::class, $method], $args); + } + + /** + * Forward call. + * + * @author yansongda + * + * @param string $method + * @param array $args + * + * @return mixed + */ + public function __call($method, $args) + { + return call_user_func_array([BaseLog::class, $method], $args); + } +} diff --git a/vendor/yansongda/pay/src/Pay.php b/vendor/yansongda/pay/src/Pay.php new file mode 100644 index 0000000..eb33e3d --- /dev/null +++ b/vendor/yansongda/pay/src/Pay.php @@ -0,0 +1,131 @@ + + * + * @throws Exception + */ + public function __construct(array $config) + { + $this->config = new Config($config); + + $this->registerLogService(); + $this->registerEventService(); + } + + /** + * Magic static call. + * + * @author yansongda + * + * @param string $method + * @param array $params + * + * @throws InvalidGatewayException + * @throws Exception + */ + public static function __callStatic($method, $params): GatewayApplicationInterface + { + $app = new self(...$params); + + return $app->create($method); + } + + /** + * Create a instance. + * + * @author yansongda + * + * @param string $method + * + * @throws InvalidGatewayException + */ + protected function create($method): GatewayApplicationInterface + { + $gateway = __NAMESPACE__.'\\Gateways\\'.Str::studly($method); + + if (class_exists($gateway)) { + return self::make($gateway); + } + + throw new InvalidGatewayException("Gateway [{$method}] Not Exists"); + } + + /** + * Make a gateway. + * + * @author yansongda + * + * @param string $gateway + * + * @throws InvalidGatewayException + */ + protected function make($gateway): GatewayApplicationInterface + { + $app = new $gateway($this->config); + + if ($app instanceof GatewayApplicationInterface) { + return $app; + } + + throw new InvalidGatewayException("Gateway [{$gateway}] Must Be An Instance Of GatewayApplicationInterface"); + } + + /** + * Register log service. + * + * @author yansongda + * + * @throws Exception + */ + protected function registerLogService() + { + $config = $this->config->get('log'); + $config['identify'] = 'yansongda.pay'; + + $logger = new Logger(); + $logger->setConfig($config); + + Log::setInstance($logger); + } + + /** + * Register event service. + * + * @author yansongda + */ + protected function registerEventService() + { + Events::setDispatcher(Events::createDispatcher()); + + Events::addSubscriber(new KernelLogSubscriber()); + } +} diff --git a/vendor/yansongda/supports/.scrutinizer.yml b/vendor/yansongda/supports/.scrutinizer.yml new file mode 100644 index 0000000..954a615 --- /dev/null +++ b/vendor/yansongda/supports/.scrutinizer.yml @@ -0,0 +1,6 @@ +filter: + excluded_paths: + - tests/* + +checks: + php: true diff --git a/vendor/yansongda/supports/LICENSE b/vendor/yansongda/supports/LICENSE new file mode 100644 index 0000000..6ceb153 --- /dev/null +++ b/vendor/yansongda/supports/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2017 yansongda + +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. \ No newline at end of file diff --git a/vendor/yansongda/supports/README.md b/vendor/yansongda/supports/README.md new file mode 100644 index 0000000..c092ee8 --- /dev/null +++ b/vendor/yansongda/supports/README.md @@ -0,0 +1,70 @@ +

        Supports

        + +[![Linter Status](https://github.com/yansongda/supports/workflows/Linter/badge.svg)](https://github.com/yansongda/supports/actions) +[![Tester Status](https://github.com/yansongda/supports/workflows/Tester/badge.svg)](https://github.com/yansongda/supports/actions) +[![Latest Stable Version](https://poser.pugx.org/yansongda/supports/v/stable)](https://packagist.org/packages/yansongda/supports) +[![Total Downloads](https://poser.pugx.org/yansongda/supports/downloads)](https://packagist.org/packages/yansongda/supports) +[![Latest Unstable Version](https://poser.pugx.org/yansongda/supports/v/unstable)](https://packagist.org/packages/yansongda/supports) +[![License](https://poser.pugx.org/yansongda/supports/license)](https://packagist.org/packages/yansongda/supports) + + +handle with array/config/log/guzzle etc. + +## About log + +### Register + +#### Method 1 + +A application logger can extends `Yansongda\Supports\Log` and modify `createLogger` method, the method must return instance of `Monolog\Logger`. + +```PHP +use Yansongda\Supports\Log; +use Monolog\Logger; + +class APPLICATIONLOG extends Log +{ + /** + * Make a default log instance. + * + * @author yansongda + * + * @return Logger + */ + public static function createLogger() + { + $handler = new StreamHandler('./log.log'); + $handler->setFormatter(new LineFormatter("%datetime% > %level_name% > %message% %context% %extra%\n\n")); + + $logger = new Logger('yansongda.private_number'); + $logger->pushHandler($handler); + + return $logger; + } +} +``` + +#### Method 2 + +Or, just init the log service with: + +```PHP +use Yansongda\Supports\Log; + +protected function registerLog() +{ + $logger = Log::createLogger($file, $identify, $level); + + Log::setLogger($logger); +} +``` + +### Usage + +After registerLog, you can use Log service: + +```PHP +use Yansongda\Supports\Log; + +Log::debug('test', ['test log']); +``` diff --git a/vendor/yansongda/supports/composer.json b/vendor/yansongda/supports/composer.json new file mode 100644 index 0000000..4ee5822 --- /dev/null +++ b/vendor/yansongda/supports/composer.json @@ -0,0 +1,39 @@ +{ + "name": "yansongda/supports", + "description": "common components", + "keywords": ["support", "array", "collection", "config", "http", "guzzle", "throttle"], + "support": { + "issues": "https://github.com/yansongda/supports/issues", + "source": "https://github.com/yansongda/supports" + }, + "authors": [ + { + "name": "yansongda", + "email": "me@yansongda.cn" + } + ], + "require": { + "php": ">=7.1.3", + "monolog/monolog": "^1.23 || ^2.0", + "guzzlehttp/guzzle": "^6.2 || ^7.0" + }, + "require-dev": { + "predis/predis": "^1.1", + "phpunit/phpunit": "^7.5", + "friendsofphp/php-cs-fixer": "^2.15" + }, + "autoload": { + "psr-4": { + "Yansongda\\Supports\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Yansongda\\Supports\\Tests\\": "tests/" + } + }, + "suggest": { + "predis/predis": "Allows to use throttle feature" + }, + "license": "MIT" +} diff --git a/vendor/yansongda/supports/src/Arr.php b/vendor/yansongda/supports/src/Arr.php new file mode 100644 index 0000000..9f30ae4 --- /dev/null +++ b/vendor/yansongda/supports/src/Arr.php @@ -0,0 +1,605 @@ + $value) { + [$innerKey, $innerValue] = call_user_func($callback, $key, $value); + $results[$innerKey] = $innerValue; + } + + return $results; + } + + /** + * Divide an array into two arrays. One with keys and the other with values. + */ + public static function divide(array $array): array + { + return [ + array_keys($array), + array_values($array), + ]; + } + + /** + * Flatten a multi-dimensional associative array with dots. + */ + public static function dot(array $array, string $prepend = ''): array + { + $results = []; + + foreach ($array as $key => $value) { + if (is_array($value)) { + $results = array_merge($results, static::dot($value, $prepend.$key.'.')); + } else { + $results[$prepend.$key] = $value; + } + } + + return $results; + } + + /** + * Get all of the given array except for a specified array of items. + * + * @param array|string $keys + */ + public static function except(array $array, $keys): array + { + return array_diff_key($array, array_flip((array) $keys)); + } + + /** + * access array. + * + * if not array access, return original. + * + * @author yansongda + * + * @param mixed $data + * + * @return mixed + */ + public static function access($data) + { + if (!self::accessible($data) && + !(is_object($data) && method_exists($data, 'toArray'))) { + return $data; + } + + return is_object($data) ? $data->toArray() : $data; + } + + /** + * Determine if the given key exists in the provided array. + * + * @param \ArrayAccess|array $array + * @param string|int $key + * + * @return bool + */ + public static function exists($array, $key) + { + $array = self::access($array); + + if ($array instanceof ArrayAccess) { + return $array->offsetExists($key); + } + + return array_key_exists($key, $array); + } + + /** + * Check if an item or items exist in an array using "dot" notation. + * + * @param \ArrayAccess|array $array + * @param string|array $keys + * + * @return bool + */ + public static function has($array, $keys) + { + $array = self::access($array); + + $keys = (array) $keys; + + if (!$array || $keys === []) { + return false; + } + + foreach ($keys as $key) { + $subKeyArray = $array; + + if (static::exists($array, $key)) { + continue; + } + + foreach (explode('.', $key) as $segment) { + if (static::accessible($subKeyArray) && static::exists($subKeyArray, $segment)) { + $subKeyArray = $subKeyArray[$segment]; + } else { + return false; + } + } + } + + return true; + } + + /** + * Determine if any of the keys exist in an array using "dot" notation. + * + * @param \ArrayAccess|array $array + * @param string|array $keys + * + * @return bool + */ + public static function hasAny($array, $keys) + { + $array = self::access($array); + + if (is_null($keys)) { + return false; + } + + $keys = (array) $keys; + + if (!$array) { + return false; + } + + if ($keys === []) { + return false; + } + + foreach ($keys as $key) { + if (static::has($array, $key)) { + return true; + } + } + + return false; + } + + /** + * Fetch a flattened array of a nested array element. + */ + public static function fetch(array $array, string $key): array + { + $results = []; + + foreach (explode('.', $key) as $segment) { + $results = []; + foreach ($array as $value) { + $value = (array) $value; + $results[] = $value[$segment]; + } + $array = array_values($results); + } + + return array_values($results); + } + + /** + * Return the first element in an array passing a given truth test. + * + * @param mixed $default + * + * @return mixed + */ + public static function first(array $array, callable $callback, $default = null) + { + foreach ($array as $key => $value) { + if (call_user_func($callback, $key, $value)) { + return $value; + } + } + + return $default; + } + + /** + * Return the last element in an array passing a given truth test. + * + * @param mixed $default + * + * @return mixed + */ + public static function last(array $array, callable $callback, $default = null) + { + return static::first(array_reverse($array), $callback, $default); + } + + /** + * Flatten a multi-dimensional array into a single level. + */ + public static function flatten(array $array): array + { + $return = []; + array_walk_recursive( + $array, + function ($x) use (&$return) { + $return[] = $x; + } + ); + + return $return; + } + + /** + * Remove one or many array items from a given array using "dot" notation. + * + * @param array $array + * @param array|string $keys + */ + public static function forget(&$array, $keys) + { + $original = &$array; + + $keys = (array) $keys; + + if (0 === count($keys)) { + return; + } + + foreach ($keys as $key) { + // if the exact key exists in the top-level, remove it + if (static::exists($array, $key)) { + unset($array[$key]); + + continue; + } + + $parts = explode('.', $key); + + // clean up before each pass + $array = &$original; + + while (count($parts) > 1) { + $part = array_shift($parts); + + if (isset($array[$part]) && is_array($array[$part])) { + $array = &$array[$part]; + } else { + continue 2; + } + } + + unset($array[array_shift($parts)]); + } + } + + /** + * Get an item from an array using "dot" notation. + * + * @param mixed $default + * + * @return mixed + */ + public static function get(array $array, string $key, $default = null) + { + if (is_null($key)) { + return $array; + } + + if (isset($array[$key])) { + return $array[$key]; + } + + foreach (explode('.', $key) as $segment) { + if (!is_array($array) || !array_key_exists($segment, $array)) { + return $default; + } + $array = $array[$segment]; + } + + return $array; + } + + /** + * Get a subset of the items from the given array. + * + * @param array|string $keys + */ + public static function only(array $array, $keys): array + { + return array_intersect_key($array, array_flip((array) $keys)); + } + + /** + * Pluck an array of values from an array. + * + * @param string $key + */ + public static function pluck(array $array, string $value, string $key = null): array + { + $results = []; + + foreach ($array as $item) { + $itemValue = is_object($item) ? $item->{$value} : $item[$value]; + // If the key is "null", we will just append the value to the array and keep + // looping. Otherwise we will key the array using the value of the key we + // received from the developer. Then we'll return the final array form. + if (is_null($key)) { + $results[] = $itemValue; + } else { + $itemKey = is_object($item) ? $item->{$key} : $item[$key]; + $results[$itemKey] = $itemValue; + } + } + + return $results; + } + + /** + * Push an item onto the beginning of an array. + * + * @param mixed $value + * @param mixed $key + * + * @return array + */ + public static function prepend(array $array, $value, $key = null) + { + if (is_null($key)) { + array_unshift($array, $value); + } else { + $array = [$key => $value] + $array; + } + + return $array; + } + + /** + * Get a value from the array, and remove it. + * + * @param mixed $default + * + * @return mixed + */ + public static function pull(array &$array, string $key, $default = null) + { + $value = static::get($array, $key, $default); + + static::forget($array, $key); + + return $value; + } + + /** + * Get one or a specified number of random values from an array. + * + * @param array $array + * @param int|null $number + * + * @return mixed + * + * @throws \InvalidArgumentException + */ + public static function random(array $array, $number = null) + { + $requested = is_null($number) ? 1 : $number; + + $count = count($array); + + $number = $requested > $count ? $count : $requested; + + if (is_null($number)) { + return $array[array_rand($array)]; + } + + if (0 === (int) $number) { + return []; + } + + $keys = array_rand($array, $number); + + $results = []; + + foreach ((array) $keys as $key) { + $results[] = $array[$key]; + } + + return $results; + } + + /** + * Set an array item to a given value using "dot" notation. + * + * If no key is given to the method, the entire array will be replaced. + * + * @param mixed $value + */ + public static function set(array &$array, string $key, $value): array + { + if (is_null($key)) { + return $array = $value; + } + + $keys = explode('.', $key); + + while (count($keys) > 1) { + $key = array_shift($keys); + // If the key doesn't exist at this depth, we will just create an empty array + // to hold the next value, allowing us to create the arrays to hold final + // values at the correct depth. Then we'll keep digging into the array. + if (!isset($array[$key]) || !is_array($array[$key])) { + $array[$key] = []; + } + $array = &$array[$key]; + } + $array[array_shift($keys)] = $value; + + return $array; + } + + /** + * Sort the array using the given Closure. + */ + public static function sort(array $array, callable $callback): array + { + $results = []; + + foreach ($array as $key => $value) { + $results[$key] = $callback($value); + } + + return $results; + } + + /** + * Shuffle the given array and return the result. + * + * @param array $array + * @param int|null $seed + * + * @return array + */ + public static function shuffle(array $array, $seed = null): array + { + if (is_null($seed)) { + shuffle($array); + } else { + mt_srand($seed); + shuffle($array); + mt_srand(); + } + + return $array; + } + + /** + * Convert the array into a query string. + */ + public static function query(array $array): string + { + return http_build_query($array, null, '&', PHP_QUERY_RFC3986); + } + + /** + * Filter the array using the given callback. + */ + public static function where(array $array, ?callable $callback = null): array + { + return array_filter($array, $callback ?? function ($value) use ($callback) { + if (static::accessible($value)) { + $value = static::where($value, $callback); + } + + if (is_array($value) && 0 === count($value)) { + $value = null; + } + + return '' !== $value && !is_null($value); + }, ARRAY_FILTER_USE_BOTH); + } + + /** + * Convert encoding. + * + * @author yansongda + * + * @param string $from_encoding + */ + public static function encoding(array $array, string $to_encoding, $from_encoding = 'gb2312'): array + { + $encoded = []; + + foreach ($array as $key => $value) { + $encoded[$key] = is_array($value) ? self::encoding($value, $to_encoding, $from_encoding) : + mb_convert_encoding($value, $to_encoding, $from_encoding); + } + + return $encoded; + } + + /** + * camelCaseKey. + * + * @author yansongda + * + * @param mixed $data + * + * @return mixed + */ + public static function camelCaseKey($data) + { + if (!self::accessible($data) && + !(is_object($data) && method_exists($data, 'toArray'))) { + return $data; + } + + $result = []; + $data = self::access($data); + + foreach ($data as $key => $value) { + $result[is_string($key) ? Str::camel($key) : $key] = self::camelCaseKey($value); + } + + return $result; + } + + /** + * snakeCaseKey. + * + * @author yansongda + * + * @param mixed $data + * + * @return mixed + */ + public static function snakeCaseKey($data) + { + if (!self::accessible($data) && + !(is_object($data) && method_exists($data, 'toArray'))) { + return $data; + } + + $data = self::access($data); + $result = []; + + foreach ($data as $key => $value) { + $result[is_string($key) ? Str::snake($key) : $key] = self::snakeCaseKey($value); + } + + return $result; + } +} diff --git a/vendor/yansongda/supports/src/Collection.php b/vendor/yansongda/supports/src/Collection.php new file mode 100644 index 0000000..ba3a70b --- /dev/null +++ b/vendor/yansongda/supports/src/Collection.php @@ -0,0 +1,363 @@ + $value) { + $this->set($key, $value); + } + } + + /** + * To string. + */ + public function __toString(): string + { + return $this->toJson(); + } + + /** + * Get a data by key. + * + * @return mixed + */ + public function __get(string $key) + { + return $this->get($key); + } + + /** + * Assigns a value to the specified data. + * + * @param mixed $value + */ + public function __set(string $key, $value) + { + $this->set($key, $value); + } + + /** + * Whether or not an data exists by key. + */ + public function __isset(string $key): bool + { + return $this->has($key); + } + + /** + * Unsets an data by key. + */ + public function __unset(string $key) + { + $this->forget($key); + } + + /** + * Return all items. + */ + public function all(): array + { + return $this->items; + } + + /** + * Return specific items. + */ + public function only(array $keys): array + { + $return = []; + + foreach ($keys as $key) { + $value = $this->get($key); + + if (!is_null($value)) { + $return[$key] = $value; + } + } + + return $return; + } + + /** + * Get all items except for those with the specified keys. + * + * @param mixed $keys + * + * @return static + */ + public function except($keys) + { + $keys = is_array($keys) ? $keys : func_get_args(); + + return new static(Arr::except($this->items, $keys)); + } + + /** + * Merge data. + * + * @param Collection|array $items + */ + public function merge($items): array + { + foreach ($items as $key => $value) { + $this->set($key, $value); + } + + return $this->all(); + } + + /** + * To determine Whether the specified element exists. + */ + public function has(string $key): bool + { + return !is_null(Arr::get($this->items, $key)); + } + + /** + * Retrieve the first item. + * + * @return mixed + */ + public function first() + { + return reset($this->items); + } + + /** + * Retrieve the last item. + * + * @return mixed + */ + public function last() + { + $end = end($this->items); + + reset($this->items); + + return $end; + } + + /** + * add the item value. + * + * @param mixed $value + */ + public function add(string $key, $value) + { + Arr::set($this->items, $key, $value); + } + + /** + * Set the item value. + * + * @param mixed $value + */ + public function set(string $key, $value) + { + Arr::set($this->items, $key, $value); + } + + /** + * Retrieve item from Collection. + * + * @param string $key + * @param mixed $default + * + * @return mixed + */ + public function get(?string $key = null, $default = null) + { + return Arr::get($this->items, $key, $default); + } + + /** + * Remove item form Collection. + */ + public function forget(string $key) + { + Arr::forget($this->items, $key); + } + + /** + * Build to array. + */ + public function toArray(): array + { + return $this->all(); + } + + /** + * Build to json. + */ + public function toJson(int $option = JSON_UNESCAPED_UNICODE): string + { + return json_encode($this->all(), $option); + } + + /** + * (PHP 5 >= 5.4.0)
        + * Specify data which should be serialized to JSON. + * + * @see http://php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return mixed data which can be serialized by json_encode, + * which is a value of any type other than a resource + */ + public function jsonSerialize() + { + return $this->items; + } + + /** + * (PHP 5 >= 5.1.0)
        + * String representation of object. + * + * @see http://php.net/manual/en/serializable.serialize.php + * + * @return string the string representation of the object or null + */ + public function serialize() + { + return serialize($this->items); + } + + /** + * (PHP 5 >= 5.0.0)
        + * Retrieve an external iterator. + * + * @see http://php.net/manual/en/iteratoraggregate.getiterator.php + * + * @return ArrayIterator An instance of an object implementing Iterator or + * ArrayIterator + */ + public function getIterator() + { + return new ArrayIterator($this->items); + } + + /** + * (PHP 5 >= 5.1.0)
        + * Count elements of an object. + * + * @see http://php.net/manual/en/countable.count.php + * + * @return int The custom count as an integer. + *

        + *

        + * The return value is cast to an integer + */ + public function count() + { + return count($this->items); + } + + /** + * (PHP 5 >= 5.1.0)
        + * Constructs the object. + * + * @see http://php.net/manual/en/serializable.unserialize.php + * + * @param string $serialized

        + * The string representation of the object. + *

        + * + * @return mixed|void + */ + public function unserialize($serialized) + { + return $this->items = unserialize($serialized); + } + + /** + * (PHP 5 >= 5.0.0)
        + * Whether a offset exists. + * + * @see http://php.net/manual/en/arrayaccess.offsetexists.php + * + * @param mixed $offset

        + * An offset to check for. + *

        + * + * @return bool true on success or false on failure. + * The return value will be casted to boolean if non-boolean was returned + */ + public function offsetExists($offset) + { + return $this->has($offset); + } + + /** + * (PHP 5 >= 5.0.0)
        + * Offset to unset. + * + * @see http://php.net/manual/en/arrayaccess.offsetunset.php + * + * @param mixed $offset

        + * The offset to unset. + *

        + */ + public function offsetUnset($offset) + { + if ($this->offsetExists($offset)) { + $this->forget($offset); + } + } + + /** + * (PHP 5 >= 5.0.0)
        + * Offset to retrieve. + * + * @see http://php.net/manual/en/arrayaccess.offsetget.php + * + * @param mixed $offset

        + * The offset to retrieve. + *

        + * + * @return mixed Can return all value types + */ + public function offsetGet($offset) + { + return $this->offsetExists($offset) ? $this->get($offset) : null; + } + + /** + * (PHP 5 >= 5.0.0)
        + * Offset to set. + * + * @see http://php.net/manual/en/arrayaccess.offsetset.php + * + * @param mixed $offset

        + * The offset to assign the value to. + *

        + * @param mixed $value

        + * The value to set. + *

        + */ + public function offsetSet($offset, $value) + { + $this->set($offset, $value); + } +} diff --git a/vendor/yansongda/supports/src/Config.php b/vendor/yansongda/supports/src/Config.php new file mode 100644 index 0000000..a660bad --- /dev/null +++ b/vendor/yansongda/supports/src/Config.php @@ -0,0 +1,7 @@ + + * + * @param string $method + * @param array $args + * + * @throws \Exception + */ + public function __call($method, $args): void + { + call_user_func_array([self::getInstance(), $method], $args); + } + + /** + * __callStatic. + * + * @author yansongda + * + * @param string $method + * @param array $args + * + * @throws \Exception + */ + public static function __callStatic($method, $args): void + { + forward_static_call_array([self::getInstance(), $method], $args); + } + + /** + * getInstance. + * + * @author yansongda + * + * @return \Yansongda\Supports\Logger + */ + public static function getInstance(): Logger + { + if (is_null(self::$instance)) { + self::$instance = new Logger(); + } + + return self::$instance; + } + + /** + * setInstance. + * + * @author yansongda + * + * @param \Yansongda\Supports\Logger $logger + * + * @throws \Exception + */ + public static function setInstance(Logger $logger): void + { + self::$instance = $logger; + } +} diff --git a/vendor/yansongda/supports/src/Logger.php b/vendor/yansongda/supports/src/Logger.php new file mode 100644 index 0000000..7232c59 --- /dev/null +++ b/vendor/yansongda/supports/src/Logger.php @@ -0,0 +1,240 @@ + null, + 'identify' => 'yansongda.supports', + 'level' => BaseLogger::DEBUG, + 'type' => 'daily', + 'max_files' => 30, + ]; + + /** + * Forward call. + * + * @author yansongda + * + * @param string $method + * @param array $args + * + * @throws Exception + */ + public function __call($method, $args): void + { + call_user_func_array([$this->getLogger(), $method], $args); + } + + /** + * Set logger. + * + * @author yansongda + */ + public function setLogger(LoggerInterface $logger): Logger + { + $this->logger = $logger; + + return $this; + } + + /** + * Return the logger instance. + * + * @author yansongda + * + * @throws Exception + */ + public function getLogger(): LoggerInterface + { + if (is_null($this->logger)) { + $this->logger = $this->createLogger(); + } + + return $this->logger; + } + + /** + * Make a default log instance. + * + * @author yansongda + * + * @throws Exception + */ + public function createLogger(): BaseLogger + { + $handler = $this->getHandler(); + + $handler->setFormatter($this->getFormatter()); + + $logger = new BaseLogger($this->config['identify']); + + $logger->pushHandler($handler); + + return $logger; + } + + /** + * setFormatter. + * + * @author yansongda + * + * @return $this + */ + public function setFormatter(FormatterInterface $formatter): self + { + $this->formatter = $formatter; + + return $this; + } + + /** + * getFormatter. + * + * @author yansongda + */ + public function getFormatter(): FormatterInterface + { + if (is_null($this->formatter)) { + $this->formatter = $this->createFormatter(); + } + + return $this->formatter; + } + + /** + * createFormatter. + * + * @author yansongda + */ + public function createFormatter(): LineFormatter + { + return new LineFormatter( + "%datetime% > %channel%.%level_name% > %message% %context% %extra%\n\n", + null, + false, + true + ); + } + + /** + * setHandler. + * + * @author yansongda + * + * @return $this + */ + public function setHandler(AbstractHandler $handler): self + { + $this->handler = $handler; + + return $this; + } + + /** + * getHandler. + * + * @author yansongda + * + * @throws \Exception + */ + public function getHandler(): AbstractHandler + { + if (is_null($this->handler)) { + $this->handler = $this->createHandler(); + } + + return $this->handler; + } + + /** + * createHandler. + * + * @author yansongda + * + * @throws \Exception + * + * @return \Monolog\Handler\RotatingFileHandler|\Monolog\Handler\StreamHandler + */ + public function createHandler(): AbstractHandler + { + $file = $this->config['file'] ?? sys_get_temp_dir().'/logs/'.$this->config['identify'].'.log'; + + if ('single' === $this->config['type']) { + return new StreamHandler($file, $this->config['level']); + } + + return new RotatingFileHandler($file, $this->config['max_files'], $this->config['level']); + } + + /** + * setConfig. + * + * @author yansongda + * + * @return $this + */ + public function setConfig(array $config): self + { + $this->config = array_merge($this->config, $config); + + return $this; + } + + /** + * getConfig. + * + * @author yansongda + */ + public function getConfig(): array + { + return $this->config; + } +} diff --git a/vendor/yansongda/supports/src/Logger/StdoutHandler.php b/vendor/yansongda/supports/src/Logger/StdoutHandler.php new file mode 100644 index 0000000..bba384f --- /dev/null +++ b/vendor/yansongda/supports/src/Logger/StdoutHandler.php @@ -0,0 +1,36 @@ +output = $output ?? new ConsoleOutput(); + parent::__construct($level, $bubble); + } + + /** + * Writes the record down to the log of the implementing handler. + */ + protected function write(array $record): void + { + $this->output->writeln($record['formatted']); + } +} diff --git a/vendor/yansongda/supports/src/Str.php b/vendor/yansongda/supports/src/Str.php new file mode 100644 index 0000000..b540be6 --- /dev/null +++ b/vendor/yansongda/supports/src/Str.php @@ -0,0 +1,570 @@ + $val) { + $value = str_replace($val, $key, $value); + } + + return preg_replace('/[^\x20-\x7E]/u', '', $value); + } + + /** + * Get the portion of a string before a given value. + */ + public static function before(string $subject, string $search): string + { + return '' === $search ? $subject : explode($search, $subject)[0]; + } + + /** + * Convert a value to camel case. + */ + public static function camel(string $value): string + { + if (isset(static::$camelCache[$value])) { + return static::$camelCache[$value]; + } + + return static::$camelCache[$value] = lcfirst(static::studly($value)); + } + + /** + * Determine if a given string contains a given substring. + * + * @param string|array $needles + */ + public static function contains(string $haystack, $needles): bool + { + foreach ((array) $needles as $needle) { + if ('' !== $needle && false !== mb_strpos($haystack, $needle)) { + return true; + } + } + + return false; + } + + /** + * Determine if a given string ends with a given substring. + * + * @param string|array $needles + */ + public static function endsWith(string $haystack, $needles): bool + { + foreach ((array) $needles as $needle) { + if (substr($haystack, -strlen($needle)) === (string) $needle) { + return true; + } + } + + return false; + } + + /** + * Cap a string with a single instance of a given value. + */ + public static function finish(string $value, string $cap): string + { + $quoted = preg_quote($cap, '/'); + + return preg_replace('/(?:'.$quoted.')+$/u', '', $value).$cap; + } + + /** + * Determine if a given string matches a given pattern. + * + * @param string|array $pattern + */ + public static function is($pattern, string $value): bool + { + $patterns = is_array($pattern) ? $pattern : (array) $pattern; + + if (empty($patterns)) { + return false; + } + + foreach ($patterns as $pattern) { + // If the given value is an exact match we can of course return true right + // from the beginning. Otherwise, we will translate asterisks and do an + // actual pattern match against the two strings to see if they match. + if ($pattern == $value) { + return true; + } + + $pattern = preg_quote($pattern, '#'); + + // Asterisks are translated into zero-or-more regular expression wildcards + // to make it convenient to check if the strings starts with the given + // pattern such as "library/*", making any string check convenient. + $pattern = str_replace('\*', '.*', $pattern); + + if (1 === preg_match('#^'.$pattern.'\z#u', $value)) { + return true; + } + } + + return false; + } + + /** + * Convert a string to kebab case. + */ + public static function kebab(string $value): string + { + return static::snake($value, '-'); + } + + /** + * Return the length of the given string. + * + * @param string $encoding + */ + public static function length(string $value, ?string $encoding = null): int + { + if (null !== $encoding) { + return mb_strlen($value, $encoding); + } + + return mb_strlen($value); + } + + /** + * Limit the number of characters in a string. + */ + public static function limit(string $value, int $limit = 100, string $end = '...'): string + { + if (mb_strwidth($value, 'UTF-8') <= $limit) { + return $value; + } + + return rtrim(mb_strimwidth($value, 0, $limit, '', 'UTF-8')).$end; + } + + /** + * Convert the given string to lower-case. + */ + public static function lower(string $value): string + { + return mb_strtolower($value, 'UTF-8'); + } + + /** + * Limit the number of words in a string. + */ + public static function words(string $value, int $words = 100, string $end = '...'): string + { + preg_match('/^\s*+(?:\S++\s*+){1,'.$words.'}/u', $value, $matches); + + if (!isset($matches[0]) || static::length($value) === static::length($matches[0])) { + return $value; + } + + return rtrim($matches[0]).$end; + } + + /** + * Parse a Class. + */ + public static function parseCallback(string $callback, ?string $default = null): array + { + return static::contains($callback, '@') ? explode('@', $callback, 2) : [$callback, $default]; + } + + /** + * Generate a more truly "random" alpha-numeric string. + * + * @throws Exception + */ + public static function random(int $length = 16): string + { + $string = ''; + + while (($len = strlen($string)) < $length) { + $size = $length - $len; + + $bytes = function_exists('random_bytes') ? random_bytes($size) : mt_rand(); + + $string .= substr(str_replace(['/', '+', '='], '', base64_encode($bytes)), 0, $size); + } + + return $string; + } + + /** + * Replace a given value in the string sequentially with an array. + */ + public static function replaceArray(string $search, array $replace, string $subject): string + { + foreach ($replace as $value) { + $subject = static::replaceFirst($search, $value, $subject); + } + + return $subject; + } + + /** + * Replace the first occurrence of a given value in the string. + */ + public static function replaceFirst(string $search, string $replace, string $subject): string + { + if ('' == $search) { + return $subject; + } + + $position = strpos($subject, $search); + + if (false !== $position) { + return substr_replace($subject, $replace, $position, strlen($search)); + } + + return $subject; + } + + /** + * Replace the last occurrence of a given value in the string. + */ + public static function replaceLast(string $search, string $replace, string $subject): string + { + $position = strrpos($subject, $search); + + if (false !== $position) { + return substr_replace($subject, $replace, $position, strlen($search)); + } + + return $subject; + } + + /** + * Begin a string with a single instance of a given value. + */ + public static function start(string $value, string $prefix): string + { + $quoted = preg_quote($prefix, '/'); + + return $prefix.preg_replace('/^(?:'.$quoted.')+/u', '', $value); + } + + /** + * Convert the given string to upper-case. + */ + public static function upper(string $value): string + { + return mb_strtoupper($value, 'UTF-8'); + } + + /** + * Convert the given string to title case. + */ + public static function title(string $value): string + { + return mb_convert_case($value, MB_CASE_TITLE, 'UTF-8'); + } + + /** + * Generate a URL friendly "slug" from a given string. + */ + public static function slug(string $title, string $separator = '-', string $language = 'en'): string + { + $title = static::ascii($title, $language); + + // Convert all dashes/underscores into separator + $flip = '-' == $separator ? '_' : '-'; + + $title = preg_replace('!['.preg_quote($flip).']+!u', $separator, $title); + + // Replace @ with the word 'at' + $title = str_replace('@', $separator.'at'.$separator, $title); + + // Remove all characters that are not the separator, letters, numbers, or whitespace. + $title = preg_replace('![^'.preg_quote($separator).'\pL\pN\s]+!u', '', mb_strtolower($title)); + + // Replace all separator characters and whitespace by a single separator + $title = preg_replace('!['.preg_quote($separator).'\s]+!u', $separator, $title); + + return trim($title, $separator); + } + + /** + * Convert a string to snake case. + */ + public static function snake(string $value, string $delimiter = '_'): string + { + $key = $value; + + if (isset(static::$snakeCache[$key][$delimiter])) { + return static::$snakeCache[$key][$delimiter]; + } + + if (!ctype_lower($value)) { + $value = preg_replace('/\s+/u', '', ucwords($value)); + + $value = static::lower(preg_replace('/(.)(?=[A-Z])/u', '$1'.$delimiter, $value)); + } + + return static::$snakeCache[$key][$delimiter] = $value; + } + + /** + * Determine if a given string starts with a given substring. + * + * @param string|array $needles + */ + public static function startsWith(string $haystack, $needles): bool + { + foreach ((array) $needles as $needle) { + if ('' !== $needle && substr($haystack, 0, strlen($needle)) === (string) $needle) { + return true; + } + } + + return false; + } + + /** + * Convert a value to studly caps case. + */ + public static function studly(string $value): string + { + $key = $value; + + if (isset(static::$studlyCache[$key])) { + return static::$studlyCache[$key]; + } + + $value = ucwords(str_replace(['-', '_'], ' ', $value)); + + return static::$studlyCache[$key] = str_replace(' ', '', $value); + } + + /** + * Returns the portion of string specified by the start and length parameters. + */ + public static function substr(string $string, int $start, ?int $length = null): string + { + return mb_substr($string, $start, $length, 'UTF-8'); + } + + /** + * Make a string's first character uppercase. + */ + public static function ucfirst(string $string): string + { + return static::upper(static::substr($string, 0, 1)).static::substr($string, 1); + } + + /** + * Convert string's encoding. + * + * @author yansongda + */ + public static function encoding(string $string, string $to = 'utf-8', string $from = 'gb2312'): string + { + return mb_convert_encoding($string, $to, $from); + } + + /** + * Returns the replacements for the ascii method. + * + * Note: Adapted from Stringy\Stringy. + * + * @see https://github.com/danielstjules/Stringy/blob/3.1.0/LICENSE.txt + */ + protected static function charsArray(): array + { + static $charsArray; + + if (isset($charsArray)) { + return $charsArray; + } + + return $charsArray = [ + '0' => ['°', '₀', '۰', '0'], + '1' => ['¹', '₁', '۱', '1'], + '2' => ['²', '₂', '۲', '2'], + '3' => ['³', '₃', '۳', '3'], + '4' => ['⁴', '₄', '۴', '٤', '4'], + '5' => ['⁵', '₅', '۵', '٥', '5'], + '6' => ['⁶', '₆', '۶', '٦', '6'], + '7' => ['⁷', '₇', '۷', '7'], + '8' => ['⁸', '₈', '۸', '8'], + '9' => ['⁹', '₉', '۹', '9'], + 'a' => ['à', 'á', 'ả', 'ã', 'ạ', 'ă', 'ắ', 'ằ', 'ẳ', 'ẵ', 'ặ', 'â', 'ấ', 'ầ', 'ẩ', 'ẫ', 'ậ', 'ā', 'ą', 'å', 'α', 'ά', 'ἀ', 'ἁ', 'ἂ', 'ἃ', 'ἄ', 'ἅ', 'ἆ', 'ἇ', 'ᾀ', 'ᾁ', 'ᾂ', 'ᾃ', 'ᾄ', 'ᾅ', 'ᾆ', 'ᾇ', 'ὰ', 'ά', 'ᾰ', 'ᾱ', 'ᾲ', 'ᾳ', 'ᾴ', 'ᾶ', 'ᾷ', 'а', 'أ', 'အ', 'ာ', 'ါ', 'ǻ', 'ǎ', 'ª', 'ა', 'अ', 'ا', 'a', 'ä'], + 'b' => ['б', 'β', 'ب', 'ဗ', 'ბ', 'b'], + 'c' => ['ç', 'ć', 'č', 'ĉ', 'ċ', 'c'], + 'd' => ['ď', 'ð', 'đ', 'ƌ', 'ȡ', 'ɖ', 'ɗ', 'ᵭ', 'ᶁ', 'ᶑ', 'д', 'δ', 'د', 'ض', 'ဍ', 'ဒ', 'დ', 'd'], + 'e' => ['é', 'è', 'ẻ', 'ẽ', 'ẹ', 'ê', 'ế', 'ề', 'ể', 'ễ', 'ệ', 'ë', 'ē', 'ę', 'ě', 'ĕ', 'ė', 'ε', 'έ', 'ἐ', 'ἑ', 'ἒ', 'ἓ', 'ἔ', 'ἕ', 'ὲ', 'έ', 'е', 'ё', 'э', 'є', 'ə', 'ဧ', 'ေ', 'ဲ', 'ე', 'ए', 'إ', 'ئ', 'e'], + 'f' => ['ф', 'φ', 'ف', 'ƒ', 'ფ', 'f'], + 'g' => ['ĝ', 'ğ', 'ġ', 'ģ', 'г', 'ґ', 'γ', 'ဂ', 'გ', 'گ', 'g'], + 'h' => ['ĥ', 'ħ', 'η', 'ή', 'ح', 'ه', 'ဟ', 'ှ', 'ჰ', 'h'], + 'i' => ['í', 'ì', 'ỉ', 'ĩ', 'ị', 'î', 'ï', 'ī', 'ĭ', 'į', 'ı', 'ι', 'ί', 'ϊ', 'ΐ', 'ἰ', 'ἱ', 'ἲ', 'ἳ', 'ἴ', 'ἵ', 'ἶ', 'ἷ', 'ὶ', 'ί', 'ῐ', 'ῑ', 'ῒ', 'ΐ', 'ῖ', 'ῗ', 'і', 'ї', 'и', 'ဣ', 'ိ', 'ီ', 'ည်', 'ǐ', 'ი', 'इ', 'ی', 'i'], + 'j' => ['ĵ', 'ј', 'Ј', 'ჯ', 'ج', 'j'], + 'k' => ['ķ', 'ĸ', 'к', 'κ', 'Ķ', 'ق', 'ك', 'က', 'კ', 'ქ', 'ک', 'k'], + 'l' => ['ł', 'ľ', 'ĺ', 'ļ', 'ŀ', 'л', 'λ', 'ل', 'လ', 'ლ', 'l'], + 'm' => ['м', 'μ', 'م', 'မ', 'მ', 'm'], + 'n' => ['ñ', 'ń', 'ň', 'ņ', 'ʼn', 'ŋ', 'ν', 'н', 'ن', 'န', 'ნ', 'n'], + 'o' => ['ó', 'ò', 'ỏ', 'õ', 'ọ', 'ô', 'ố', 'ồ', 'ổ', 'ỗ', 'ộ', 'ơ', 'ớ', 'ờ', 'ở', 'ỡ', 'ợ', 'ø', 'ō', 'ő', 'ŏ', 'ο', 'ὀ', 'ὁ', 'ὂ', 'ὃ', 'ὄ', 'ὅ', 'ὸ', 'ό', 'о', 'و', 'θ', 'ို', 'ǒ', 'ǿ', 'º', 'ო', 'ओ', 'o', 'ö'], + 'p' => ['п', 'π', 'ပ', 'პ', 'پ', 'p'], + 'q' => ['ყ', 'q'], + 'r' => ['ŕ', 'ř', 'ŗ', 'р', 'ρ', 'ر', 'რ', 'r'], + 's' => ['ś', 'š', 'ş', 'с', 'σ', 'ș', 'ς', 'س', 'ص', 'စ', 'ſ', 'ს', 's'], + 't' => ['ť', 'ţ', 'т', 'τ', 'ț', 'ت', 'ط', 'ဋ', 'တ', 'ŧ', 'თ', 'ტ', 't'], + 'u' => ['ú', 'ù', 'ủ', 'ũ', 'ụ', 'ư', 'ứ', 'ừ', 'ử', 'ữ', 'ự', 'û', 'ū', 'ů', 'ű', 'ŭ', 'ų', 'µ', 'у', 'ဉ', 'ု', 'ူ', 'ǔ', 'ǖ', 'ǘ', 'ǚ', 'ǜ', 'უ', 'उ', 'u', 'ў', 'ü'], + 'v' => ['в', 'ვ', 'ϐ', 'v'], + 'w' => ['ŵ', 'ω', 'ώ', 'ဝ', 'ွ', 'w'], + 'x' => ['χ', 'ξ', 'x'], + 'y' => ['ý', 'ỳ', 'ỷ', 'ỹ', 'ỵ', 'ÿ', 'ŷ', 'й', 'ы', 'υ', 'ϋ', 'ύ', 'ΰ', 'ي', 'ယ', 'y'], + 'z' => ['ź', 'ž', 'ż', 'з', 'ζ', 'ز', 'ဇ', 'ზ', 'z'], + 'aa' => ['ع', 'आ', 'آ'], + 'ae' => ['æ', 'ǽ'], + 'ai' => ['ऐ'], + 'ch' => ['ч', 'ჩ', 'ჭ', 'چ'], + 'dj' => ['ђ', 'đ'], + 'dz' => ['џ', 'ძ'], + 'ei' => ['ऍ'], + 'gh' => ['غ', 'ღ'], + 'ii' => ['ई'], + 'ij' => ['ij'], + 'kh' => ['х', 'خ', 'ხ'], + 'lj' => ['љ'], + 'nj' => ['њ'], + 'oe' => ['ö', 'œ', 'ؤ'], + 'oi' => ['ऑ'], + 'oii' => ['ऒ'], + 'ps' => ['ψ'], + 'sh' => ['ш', 'შ', 'ش'], + 'shch' => ['щ'], + 'ss' => ['ß'], + 'sx' => ['ŝ'], + 'th' => ['þ', 'ϑ', 'ث', 'ذ', 'ظ'], + 'ts' => ['ц', 'ც', 'წ'], + 'ue' => ['ü'], + 'uu' => ['ऊ'], + 'ya' => ['я'], + 'yu' => ['ю'], + 'zh' => ['ж', 'ჟ', 'ژ'], + '(c)' => ['©'], + 'A' => ['Á', 'À', 'Ả', 'Ã', 'Ạ', 'Ă', 'Ắ', 'Ằ', 'Ẳ', 'Ẵ', 'Ặ', 'Â', 'Ấ', 'Ầ', 'Ẩ', 'Ẫ', 'Ậ', 'Å', 'Ā', 'Ą', 'Α', 'Ά', 'Ἀ', 'Ἁ', 'Ἂ', 'Ἃ', 'Ἄ', 'Ἅ', 'Ἆ', 'Ἇ', 'ᾈ', 'ᾉ', 'ᾊ', 'ᾋ', 'ᾌ', 'ᾍ', 'ᾎ', 'ᾏ', 'Ᾰ', 'Ᾱ', 'Ὰ', 'Ά', 'ᾼ', 'А', 'Ǻ', 'Ǎ', 'A', 'Ä'], + 'B' => ['Б', 'Β', 'ब', 'B'], + 'C' => ['Ç', 'Ć', 'Č', 'Ĉ', 'Ċ', 'C'], + 'D' => ['Ď', 'Ð', 'Đ', 'Ɖ', 'Ɗ', 'Ƌ', 'ᴅ', 'ᴆ', 'Д', 'Δ', 'D'], + 'E' => ['É', 'È', 'Ẻ', 'Ẽ', 'Ẹ', 'Ê', 'Ế', 'Ề', 'Ể', 'Ễ', 'Ệ', 'Ë', 'Ē', 'Ę', 'Ě', 'Ĕ', 'Ė', 'Ε', 'Έ', 'Ἐ', 'Ἑ', 'Ἒ', 'Ἓ', 'Ἔ', 'Ἕ', 'Έ', 'Ὲ', 'Е', 'Ё', 'Э', 'Є', 'Ə', 'E'], + 'F' => ['Ф', 'Φ', 'F'], + 'G' => ['Ğ', 'Ġ', 'Ģ', 'Г', 'Ґ', 'Γ', 'G'], + 'H' => ['Η', 'Ή', 'Ħ', 'H'], + 'I' => ['Í', 'Ì', 'Ỉ', 'Ĩ', 'Ị', 'Î', 'Ï', 'Ī', 'Ĭ', 'Į', 'İ', 'Ι', 'Ί', 'Ϊ', 'Ἰ', 'Ἱ', 'Ἳ', 'Ἴ', 'Ἵ', 'Ἶ', 'Ἷ', 'Ῐ', 'Ῑ', 'Ὶ', 'Ί', 'И', 'І', 'Ї', 'Ǐ', 'ϒ', 'I'], + 'J' => ['J'], + 'K' => ['К', 'Κ', 'K'], + 'L' => ['Ĺ', 'Ł', 'Л', 'Λ', 'Ļ', 'Ľ', 'Ŀ', 'ल', 'L'], + 'M' => ['М', 'Μ', 'M'], + 'N' => ['Ń', 'Ñ', 'Ň', 'Ņ', 'Ŋ', 'Н', 'Ν', 'N'], + 'O' => ['Ó', 'Ò', 'Ỏ', 'Õ', 'Ọ', 'Ô', 'Ố', 'Ồ', 'Ổ', 'Ỗ', 'Ộ', 'Ơ', 'Ớ', 'Ờ', 'Ở', 'Ỡ', 'Ợ', 'Ø', 'Ō', 'Ő', 'Ŏ', 'Ο', 'Ό', 'Ὀ', 'Ὁ', 'Ὂ', 'Ὃ', 'Ὄ', 'Ὅ', 'Ὸ', 'Ό', 'О', 'Θ', 'Ө', 'Ǒ', 'Ǿ', 'O', 'Ö'], + 'P' => ['П', 'Π', 'P'], + 'Q' => ['Q'], + 'R' => ['Ř', 'Ŕ', 'Р', 'Ρ', 'Ŗ', 'R'], + 'S' => ['Ş', 'Ŝ', 'Ș', 'Š', 'Ś', 'С', 'Σ', 'S'], + 'T' => ['Ť', 'Ţ', 'Ŧ', 'Ț', 'Т', 'Τ', 'T'], + 'U' => ['Ú', 'Ù', 'Ủ', 'Ũ', 'Ụ', 'Ư', 'Ứ', 'Ừ', 'Ử', 'Ữ', 'Ự', 'Û', 'Ū', 'Ů', 'Ű', 'Ŭ', 'Ų', 'У', 'Ǔ', 'Ǖ', 'Ǘ', 'Ǚ', 'Ǜ', 'U', 'Ў', 'Ü'], + 'V' => ['В', 'V'], + 'W' => ['Ω', 'Ώ', 'Ŵ', 'W'], + 'X' => ['Χ', 'Ξ', 'X'], + 'Y' => ['Ý', 'Ỳ', 'Ỷ', 'Ỹ', 'Ỵ', 'Ÿ', 'Ῠ', 'Ῡ', 'Ὺ', 'Ύ', 'Ы', 'Й', 'Υ', 'Ϋ', 'Ŷ', 'Y'], + 'Z' => ['Ź', 'Ž', 'Ż', 'З', 'Ζ', 'Z'], + 'AE' => ['Æ', 'Ǽ'], + 'Ch' => ['Ч'], + 'Dj' => ['Ђ'], + 'Dz' => ['Џ'], + 'Gx' => ['Ĝ'], + 'Hx' => ['Ĥ'], + 'Ij' => ['IJ'], + 'Jx' => ['Ĵ'], + 'Kh' => ['Х'], + 'Lj' => ['Љ'], + 'Nj' => ['Њ'], + 'Oe' => ['Œ'], + 'Ps' => ['Ψ'], + 'Sh' => ['Ш'], + 'Shch' => ['Щ'], + 'Ss' => ['ẞ'], + 'Th' => ['Þ'], + 'Ts' => ['Ц'], + 'Ya' => ['Я'], + 'Yu' => ['Ю'], + 'Zh' => ['Ж'], + ' ' => ["\xC2\xA0", "\xE2\x80\x80", "\xE2\x80\x81", "\xE2\x80\x82", "\xE2\x80\x83", "\xE2\x80\x84", "\xE2\x80\x85", "\xE2\x80\x86", "\xE2\x80\x87", "\xE2\x80\x88", "\xE2\x80\x89", "\xE2\x80\x8A", "\xE2\x80\xAF", "\xE2\x81\x9F", "\xE3\x80\x80", "\xEF\xBE\xA0"], + ]; + } + + /** + * Returns the language specific replacements for the ascii method. + * + * Note: Adapted from Stringy\Stringy. + * + * @see https://github.com/danielstjules/Stringy/blob/3.1.0/LICENSE.txt + */ + protected static function languageSpecificCharsArray(string $language): ?array + { + static $languageSpecific; + if (!isset($languageSpecific)) { + $languageSpecific = [ + 'bg' => [ + ['х', 'Х', 'щ', 'Щ', 'ъ', 'Ъ', 'ь', 'Ь'], + ['h', 'H', 'sht', 'SHT', 'a', 'А', 'y', 'Y'], + ], + 'de' => [ + ['ä', 'ö', 'ü', 'Ä', 'Ö', 'Ü'], + ['ae', 'oe', 'ue', 'AE', 'OE', 'UE'], + ], + ]; + } + + return isset($languageSpecific[$language]) ? $languageSpecific[$language] : null; + } +} diff --git a/vendor/yansongda/supports/src/Traits/Accessable.php b/vendor/yansongda/supports/src/Traits/Accessable.php new file mode 100644 index 0000000..a477561 --- /dev/null +++ b/vendor/yansongda/supports/src/Traits/Accessable.php @@ -0,0 +1,142 @@ + + * + * @return mixed + */ + public function __get(string $key) + { + return $this->get($key); + } + + /** + * __set. + * + * @author yansongda + * + * @param mixed $value + * + * @return mixed + */ + public function __set(string $key, $value) + { + return $this->set($key, $value); + } + + /** + * get. + * + * @author yansongda + * + * @param mixed $default + * + * @return mixed + */ + public function get(?string $key = null, $default = null) + { + if (is_null($key)) { + return method_exists($this, 'toArray') ? $this->toArray() : $default; + } + + $method = 'get'; + foreach (explode('_', $key) as $item) { + $method .= ucfirst($item); + } + + if (method_exists($this, $method)) { + return $this->{$method}(); + } + + return $default; + } + + /** + * set. + * + * @author yansongda + * + * @param mixed $value + * + * @return $this + */ + public function set(string $key, $value) + { + $method = 'set'; + foreach (explode('_', $key) as $item) { + $method .= ucfirst($item); + } + + if (method_exists($this, $method)) { + return $this->{$method}($value); + } + + return $this; + } + + /** + * Whether a offset exists. + * + * @see https://php.net/manual/en/arrayaccess.offsetexists.php + * + * @param mixed $offset an offset to check for + * + * @return bool true on success or false on failure. + * + * The return value will be casted to boolean if non-boolean was returned. + */ + public function offsetExists($offset) + { + return !is_null($this->get($offset)); + } + + /** + * Offset to retrieve. + * + * @see https://php.net/manual/en/arrayaccess.offsetget.php + * + * @param mixed $offset the offset to retrieve + * + * @return mixed can return all value types + */ + public function offsetGet($offset) + { + return $this->get($offset); + } + + /** + * Offset to set. + * + * @see https://php.net/manual/en/arrayaccess.offsetset.php + * + * @param mixed $offset the offset to assign the value to + * @param mixed $value the value to set + * + * @return void + */ + public function offsetSet($offset, $value) + { + $this->set($offset, $value); + } + + /** + * Offset to unset. + * + * @see https://php.net/manual/en/arrayaccess.offsetunset.php + * + * @param mixed $offset the offset to unset + * + * @return void + */ + public function offsetUnset($offset) + { + } +} diff --git a/vendor/yansongda/supports/src/Traits/Arrayable.php b/vendor/yansongda/supports/src/Traits/Arrayable.php new file mode 100644 index 0000000..eb73c6c --- /dev/null +++ b/vendor/yansongda/supports/src/Traits/Arrayable.php @@ -0,0 +1,32 @@ + + * + * @throws \ReflectionException + */ + public function toArray(): array + { + $result = []; + + foreach ((new ReflectionClass($this))->getProperties() as $item) { + $k = $item->getName(); + $method = 'get'.Str::studly($k); + + $result[Str::snake($k)] = method_exists($this, $method) ? $this->{$method}() : $this->{$k}; + } + + return $result; + } +} diff --git a/vendor/yansongda/supports/src/Traits/HasHttpRequest.php b/vendor/yansongda/supports/src/Traits/HasHttpRequest.php new file mode 100644 index 0000000..6e98d24 --- /dev/null +++ b/vendor/yansongda/supports/src/Traits/HasHttpRequest.php @@ -0,0 +1,229 @@ + + * + * @return array|string + */ + public function get(string $endpoint, array $query = [], array $headers = []) + { + return $this->request('get', $endpoint, [ + 'headers' => $headers, + 'query' => $query, + ]); + } + + /** + * Send a POST request. + * + * @author yansongda + * + * @param string|array $data + * + * @return array|string + */ + public function post(string $endpoint, $data, array $options = []) + { + if (!is_array($data)) { + $options['body'] = $data; + } else { + $options['form_params'] = $data; + } + + return $this->request('post', $endpoint, $options); + } + + /** + * Send request. + * + * @author yansongda + * + * @return array|string + */ + public function request(string $method, string $endpoint, array $options = []) + { + return $this->unwrapResponse($this->getHttpClient()->{$method}($endpoint, $options)); + } + + /** + * Set http client. + * + * @author yansongda + * + * @return $this + */ + public function setHttpClient(Client $client): self + { + $this->httpClient = $client; + + return $this; + } + + /** + * Return http client. + */ + public function getHttpClient(): Client + { + if (is_null($this->httpClient)) { + $this->httpClient = $this->getDefaultHttpClient(); + } + + return $this->httpClient; + } + + /** + * Get default http client. + * + * @author yansongda + */ + public function getDefaultHttpClient(): Client + { + return new Client($this->getOptions()); + } + + /** + * setBaseUri. + * + * @author yansongda + * + * @return $this + */ + public function setBaseUri(string $url): self + { + if (property_exists($this, 'baseUri')) { + $parsedUrl = parse_url($url); + + $this->baseUri = ($parsedUrl['scheme'] ?? 'http').'://'. + $parsedUrl['host'].(isset($parsedUrl['port']) ? (':'.$parsedUrl['port']) : ''); + } + + return $this; + } + + /** + * getBaseUri. + * + * @author yansongda + */ + public function getBaseUri(): string + { + return property_exists($this, 'baseUri') ? $this->baseUri : ''; + } + + public function getTimeout(): float + { + return property_exists($this, 'timeout') ? $this->timeout : 5.0; + } + + public function setTimeout(float $timeout): self + { + if (property_exists($this, 'timeout')) { + $this->timeout = $timeout; + } + + return $this; + } + + public function getConnectTimeout(): float + { + return property_exists($this, 'connectTimeout') ? $this->connectTimeout : 3.0; + } + + public function setConnectTimeout(float $connectTimeout): self + { + if (property_exists($this, 'connectTimeout')) { + $this->connectTimeout = $connectTimeout; + } + + return $this; + } + + /** + * Get default options. + * + * @author yansongda + */ + public function getOptions(): array + { + return array_merge([ + 'base_uri' => $this->getBaseUri(), + 'timeout' => $this->getTimeout(), + 'connect_timeout' => $this->getConnectTimeout(), + ], $this->getHttpOptions()); + } + + /** + * setOptions. + * + * @author yansongda + * + * @return $this + */ + public function setOptions(array $options): self + { + return $this->setHttpOptions($options); + } + + public function getHttpOptions(): array + { + return $this->httpOptions; + } + + public function setHttpOptions(array $httpOptions): self + { + $this->httpOptions = $httpOptions; + + return $this; + } + + /** + * Convert response. + * + * @author yansongda + * + * @return array|string + */ + public function unwrapResponse(ResponseInterface $response) + { + $contentType = $response->getHeaderLine('Content-Type'); + $contents = $response->getBody()->getContents(); + + if (false !== stripos($contentType, 'json') || stripos($contentType, 'javascript')) { + return json_decode($contents, true); + } elseif (false !== stripos($contentType, 'xml')) { + return json_decode(json_encode(simplexml_load_string($contents, 'SimpleXMLElement', LIBXML_NOCDATA), JSON_UNESCAPED_UNICODE), true); + } + + return $contents; + } +} diff --git a/vendor/yansongda/supports/src/Traits/Serializable.php b/vendor/yansongda/supports/src/Traits/Serializable.php new file mode 100644 index 0000000..5e2010b --- /dev/null +++ b/vendor/yansongda/supports/src/Traits/Serializable.php @@ -0,0 +1,85 @@ + + * + * @return string + */ + public function toJson() + { + return $this->serialize(); + } + + /** + * Specify data which should be serialized to JSON. + * + * @see https://php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return mixed data which can be serialized by json_encode, + * which is a value of any type other than a resource + * + * @since 5.4.0 + */ + public function jsonSerialize() + { + if (method_exists($this, 'toArray')) { + return $this->toArray(); + } + + return []; + } + + /** + * String representation of object. + * + * @see https://php.net/manual/en/serializable.serialize.php + * + * @return string the string representation of the object or null + * + * @since 5.1.0 + */ + public function serialize() + { + if (method_exists($this, 'toArray')) { + return json_encode($this->toArray()); + } + + return json_encode([]); + } + + /** + * Constructs the object. + * + * @see https://php.net/manual/en/serializable.unserialize.php + * + * @param string $serialized

        + * The string representation of the object. + *

        + * + * @since 5.1.0 + */ + public function unserialize($serialized) + { + $data = json_decode($serialized, true); + + if (JSON_ERROR_NONE !== json_last_error()) { + throw new RuntimeException('Invalid Json Format'); + } + + foreach ($data as $key => $item) { + if (method_exists($this, 'set')) { + $this->set($key, $item); + } + } + } +} diff --git a/vendor/yansongda/supports/src/Traits/ShouldThrottle.php b/vendor/yansongda/supports/src/Traits/ShouldThrottle.php new file mode 100644 index 0000000..cbdeb71 --- /dev/null +++ b/vendor/yansongda/supports/src/Traits/ShouldThrottle.php @@ -0,0 +1,147 @@ + 60, + 'period' => 60, + 'count' => 0, + 'reset_time' => 0, + ]; + + /** + * isThrottled. + * + * @author yansongda + * + * @param string $key + * @param int $limit + * @param int $period + * @param bool $auto_add + * + * @return bool + */ + public function isThrottled($key, $limit = 60, $period = 60, $auto_add = false) + { + if (-1 === $limit) { + return false; + } + + $now = microtime(true) * 1000; + + $this->redis->zremrangebyscore($key, 0, $now - $period * 1000); + + $this->_throttle = [ + 'limit' => $limit, + 'period' => $period, + 'count' => $this->getThrottleCounts($key, $period), + 'reset_time' => $this->getThrottleResetTime($key, $now), + ]; + + if ($this->_throttle['count'] < $limit) { + if ($auto_add) { + $this->throttleAdd($key, $period); + } + + return false; + } + + return true; + } + + /** + * 限流 + 1. + * + * @author yansongda + * + * @param string $key + * @param int $period + */ + public function throttleAdd($key, $period = 60) + { + $now = microtime(true) * 1000; + + $this->redis->zadd($key, [$now => $now]); + $this->redis->expire($key, $period * 2); + } + + /** + * getResetTime. + * + * @author yansongda + * + * @param $key + * @param $now + * + * @return int + */ + public function getThrottleResetTime($key, $now) + { + $data = $this->redis->zrangebyscore( + $key, + $now - $this->_throttle['period'] * 1000, + $now, + ['limit' => [0, 1]] + ); + + if (0 === count($data)) { + return $this->_throttle['reset_time'] = time() + $this->_throttle['period']; + } + + return intval($data[0] / 1000) + $this->_throttle['period']; + } + + /** + * 获取限流相关信息. + * + * @author yansongda + * + * @param string|null $key + * @param mixed|null $default + * + * @return array|null + */ + public function getThrottleInfo($key = null, $default = null) + { + if (is_null($key)) { + return $this->_throttle; + } + + if (isset($this->_throttle[$key])) { + return $this->_throttle[$key]; + } + + return $default; + } + + /** + * 获取已使用次数. + * + * @author yansongda + * + * @param string $key + * @param int $period + * + * @return string + */ + public function getThrottleCounts($key, $period = 60) + { + $now = microtime(true) * 1000; + + return $this->redis->zcount($key, $now - $period * 1000, $now); + } +} diff --git a/view/error/400.html b/view/error/400.html new file mode 100644 index 0000000..2f2d561 --- /dev/null +++ b/view/error/400.html @@ -0,0 +1,46 @@ + +{__NOLAYOUT__} + + + + + <?php echo $code ?? '非法请求';?> + + + + + +
        +

        +

        +

        +

        + 页面自动 跳转 等待时间: +

        +

        立即返回

        +
        + + + diff --git a/view/error/jump.html b/view/error/jump.html new file mode 100644 index 0000000..2fd59ff --- /dev/null +++ b/view/error/jump.html @@ -0,0 +1,54 @@ + +{__NOLAYOUT__} + + + + + 跳转提示 + + + + + +
        + + +

        :)

        +

        + + +

        +

        + + +

        +

        + 页面自动 跳转 等待时间: +

        +

        立即返回

        +
        + + + diff --git a/view/index/index.html b/view/index/index.html new file mode 100644 index 0000000..da539bc --- /dev/null +++ b/view/index/index.html @@ -0,0 +1,4 @@ +{layout name="layout"/} +
        + 这里是内容 +
        \ No newline at end of file diff --git a/view/layout.html b/view/layout.html new file mode 100644 index 0000000..8586e57 --- /dev/null +++ b/view/layout.html @@ -0,0 +1,36 @@ + +{php} +if(!isset($_token) || empty($_token)) { +$_token = session('_token') ?? ''; +} +{/php} + + + + {$seoTitle??''} + + + + + + + + + + + + + + + + + + + +{include file="public/menu"} +{__CONTENT__} +{include file="public/footer"} + + + + diff --git a/view/manager/account/index.html b/view/manager/account/index.html new file mode 100644 index 0000000..b96a5b6 --- /dev/null +++ b/view/manager/account/index.html @@ -0,0 +1,52 @@ +{layout name="manager/layout" /} +
        +
        +
        +
        +
        + 搜索信息 +
        +
        +
        +
        + +
        + +
        +
        + +
        + +
        + +
        +
        + + +
        + +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        + + + + + + + + + \ No newline at end of file diff --git a/view/manager/archives/add.html b/view/manager/archives/add.html new file mode 100644 index 0000000..0d2f687 --- /dev/null +++ b/view/manager/archives/add.html @@ -0,0 +1,159 @@ +{layout name="manager/layout" /} + +
        +
        +
        + +
        + +
        +
        +
        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        +
        + + +
        +
        +
        +
        +
        + \ No newline at end of file diff --git a/view/manager/archives/edit.html b/view/manager/archives/edit.html new file mode 100644 index 0000000..2ec057e --- /dev/null +++ b/view/manager/archives/edit.html @@ -0,0 +1,186 @@ +{layout name="manager/layout" /} + + +
        +
        +
        + +
        + +
        +
        +
        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        +
        + + +
        +
        +
        +
        +
        + + \ No newline at end of file diff --git a/view/manager/archives/index.html b/view/manager/archives/index.html new file mode 100644 index 0000000..debd24c --- /dev/null +++ b/view/manager/archives/index.html @@ -0,0 +1,172 @@ +{layout name="manager/layout" /} + + +
        + {if $categoryId == 0} +
        +
        +
        +
        内容分类
        +
        + +
          +
          +
          +
          +
          + {/if} +
          +
          +
          +
          + +
          +
          +
          +
          +
          +
          +
          + +
          + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/view/manager/archives_category/add.html b/view/manager/archives_category/add.html new file mode 100644 index 0000000..2b2082a --- /dev/null +++ b/view/manager/archives_category/add.html @@ -0,0 +1,128 @@ +{layout name="manager/layout" /} + +
          +
          +
          +
          + +
          +
          +
          +
          + +
          + +
          +
          +
          +
          +
          + +
          + +
          +
          +
          + +
          + +
          +
          + +
          + +
          + +
          +
          + +
          + +
          +
          +
          + +
          图片尺寸:亲上传合适尺寸的图片
          +
          +
          + + + + +
          +
            +
            +
            +
            + + +
            + +
            + +
            +
            "/"开头 只能填写英文字符和数字 中划线(-)和下划线(_)每个分段不能是纯数字
            +
            + +
            + +
            + +
            +
            + +
            + +
            + +
            +
            + + +
            + +
            + +
            +
            + +
            + +
            + +
            +
            + + +
            + +
            + +
            +
            + + + + +
            + +
            + +
            +
            + + + +
            +
            + +
            +
            +
            +
            +
            + \ No newline at end of file diff --git a/view/manager/archives_category/edit.html b/view/manager/archives_category/edit.html new file mode 100644 index 0000000..071bf50 --- /dev/null +++ b/view/manager/archives_category/edit.html @@ -0,0 +1,123 @@ +{layout name="manager/layout" /} + +
            +
            +
            +
            + +
            +
            +
            +
            +
            + +
            +
            +
            +
            +
            + +
            + +
            +
            + +
            + +
            + +
            +
            + + +
            + +
            + +
            +
            +
            + +
            +
            +
            + +
            图片尺寸:亲上传合适尺寸的图片
            +
            +
            + + + + +
            +
              +
              +
              +
              + +
              + +
              + +
              +
              "/"开头 只能填写英文字符和数字 中划线(-)和下划线(_)每个分段不能是纯数字
              +
              + +
              + +
              + +
              +
              + +
              + +
              + +
              +
              + + +
              + +
              + +
              +
              + +
              + +
              + +
              +
              + + +
              + +
              + +
              +
              + + +
              + +
              + +
              +
              + +
              +
              + +
              +
              +
              +
              +
              + \ No newline at end of file diff --git a/view/manager/archives_category/index.html b/view/manager/archives_category/index.html new file mode 100644 index 0000000..43c4ec2 --- /dev/null +++ b/view/manager/archives_category/index.html @@ -0,0 +1,29 @@ +{layout name="manager/layout" /} + +
              +
              +
              + +
              +
              +
              + + + + + + + + + + + \ No newline at end of file diff --git a/view/manager/archives_model/add.html b/view/manager/archives_model/add.html new file mode 100644 index 0000000..a692935 --- /dev/null +++ b/view/manager/archives_model/add.html @@ -0,0 +1,31 @@ +{layout name="manager/layout" /} +
              +
              +
              +
              + +
              + +
              +
              +
              + +
              + +
              +
              +
              + +
              + +
              +
              + +
              +
              + +
              +
              +
              +
              +
              \ No newline at end of file diff --git a/view/manager/archives_model/edit.html b/view/manager/archives_model/edit.html new file mode 100644 index 0000000..5d327a1 --- /dev/null +++ b/view/manager/archives_model/edit.html @@ -0,0 +1,31 @@ +{layout name="manager/layout" /} +
              +
              +
              +
              + +
              + +
              +
              +
              + +
              + +
              +
              +
              + +
              + +
              +
              + +
              +
              + +
              +
              +
              +
              +
              \ No newline at end of file diff --git a/view/manager/archives_model/index.html b/view/manager/archives_model/index.html new file mode 100644 index 0000000..b27bb9a --- /dev/null +++ b/view/manager/archives_model/index.html @@ -0,0 +1,33 @@ +{layout name="manager/layout" /} + +
              +
              +
              +
              +
              +
              +
              + + + + + + + + + + + + + + \ No newline at end of file diff --git a/view/manager/archives_model_field/add.html b/view/manager/archives_model_field/add.html new file mode 100644 index 0000000..1b040bb --- /dev/null +++ b/view/manager/archives_model_field/add.html @@ -0,0 +1,39 @@ +{layout name="manager/layout" /} +
              +
              +
              +
              + +
              + +
              +
              +
              + +
              + +
              +
              +
              + +
              + + +
              +
              当前表未实际存在的字段均为虚拟字段,如 关联病种 存在于其他关联表中; 目前仅支持添加虚拟字段
              +
              +
              + +
              + +
              +
              + +
              +
              + +
              +
              +
              +
              +
              \ No newline at end of file diff --git a/view/manager/archives_model_field/edit.html b/view/manager/archives_model_field/edit.html new file mode 100644 index 0000000..d327f2f --- /dev/null +++ b/view/manager/archives_model_field/edit.html @@ -0,0 +1,39 @@ +{layout name="manager/layout" /} +
              +
              +
              +
              + +
              + +
              +
              +
              + +
              + +
              +
              +
              + +
              + + +
              +
              当前表未实际存在的字段均为虚拟字段,如 关联病种 存在于其他关联表中; 目前仅支持添加虚拟字段
              +
              +
              + +
              + +
              +
              + +
              +
              + +
              +
              +
              +
              +
              \ No newline at end of file diff --git a/view/manager/archives_model_field/index.html b/view/manager/archives_model_field/index.html new file mode 100644 index 0000000..6408ad4 --- /dev/null +++ b/view/manager/archives_model_field/index.html @@ -0,0 +1,36 @@ +{layout name="manager/layout" /} + +
              +
              +
              +
              +
              +
              +
              + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/view/manager/attachment/add.html b/view/manager/attachment/add.html new file mode 100644 index 0000000..51fef2b --- /dev/null +++ b/view/manager/attachment/add.html @@ -0,0 +1,48 @@ +{layout name="manager/layout" /} +
              +
              +
              +
              + +
              + +
              +
              + +
              + +
              +
              +
              +
              + +
              + +
              + +
              +
              + +
              + +
              +
              +
              +
              + +
              + +
              + {:widget('manager.upload/image',['src' => $item.headimg??'', 'fullName' => 'headimg'])} +
              +
              + +
              +
              + +
              +
              +
              +
              +
              + \ No newline at end of file diff --git a/view/manager/attachment/add_folder.html b/view/manager/attachment/add_folder.html new file mode 100644 index 0000000..220b0ac --- /dev/null +++ b/view/manager/attachment/add_folder.html @@ -0,0 +1,20 @@ +{layout name="manager/layout" /} +
              +
              +
              +
              + +
              + +
              +
              +
              +
              + + +
              +
              +
              +
              +
              + \ No newline at end of file diff --git a/view/manager/attachment/edit.html b/view/manager/attachment/edit.html new file mode 100644 index 0000000..22f68e1 --- /dev/null +++ b/view/manager/attachment/edit.html @@ -0,0 +1,48 @@ +{layout name="manager/layout" /} +
              +
              +
              +
              + +
              + +
              +
              + +
              + +
              +
              +
              +
              + +
              + +
              + +
              +
              + +
              + +
              +
              +
              +
              + +
              + +
              + {:widget('manager.upload/image',['src' => $item.headimg??'', 'fullName' => 'headimg'])} +
              +
              + +
              +
              + +
              +
              +
              +
              +
              + \ No newline at end of file diff --git a/view/manager/attachment/file.html b/view/manager/attachment/file.html new file mode 100644 index 0000000..ea5ed26 --- /dev/null +++ b/view/manager/attachment/file.html @@ -0,0 +1,150 @@ +{layout name="manager/layout" /} + +
              +
              + +
              +
              +
              + 失效文件:本地不存在,oss也不存在的文件 +
              + +
              +
              +
              +
              + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/view/manager/attachment/image.html b/view/manager/attachment/image.html new file mode 100644 index 0000000..71d6a9a --- /dev/null +++ b/view/manager/attachment/image.html @@ -0,0 +1,108 @@ +{layout name="manager/layout" /} + +
              +
              +
              +
              +
              +
              +
              /storage/
              +
              + +
              + +
              +
              +
              + +
              +
              +
              +
              + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/view/manager/attachment/video.html b/view/manager/attachment/video.html new file mode 100644 index 0000000..5d66b6d --- /dev/null +++ b/view/manager/attachment/video.html @@ -0,0 +1,109 @@ +{layout name="manager/layout" /} + +
              +
              +
              +
              +
              +
              +
              /storage/
              +
              + +
              + +
              +
              +
              + +
              +
              +
              +
              + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/view/manager/backup/index.html b/view/manager/backup/index.html new file mode 100644 index 0000000..bf6280d --- /dev/null +++ b/view/manager/backup/index.html @@ -0,0 +1,41 @@ +{layout name="manager/layout" /} +
              +
              +
              + 备份数据 + +

              +
              +
              + + + + + + + + + + + + + + + + {foreach $items as $item} + + + + + + + + {/foreach} +
              备份文件存储位置备份日期操作
              {$item['file']}{$backupPath.$item['file']}{$item['time']} + + 删除 + +
              +
              +
              diff --git a/view/manager/block/add.html b/view/manager/block/add.html new file mode 100644 index 0000000..a4233c9 --- /dev/null +++ b/view/manager/block/add.html @@ -0,0 +1,288 @@ +{layout name="manager/layout" /} + +
              +
              + + +
              + +
              +
              +
              +
              + +
              + +
              + +
              +
              +
              + +
              + +
              +
              + +
              + +
              + +
              +
              + +
              + +
              + +
              +
              + +
              + +
              +
              + + +
              +
              + + + + + + + + + + + + + + + +
              + + +
              +
              + +
              +
              +
              + + +
              + + + diff --git a/view/manager/block/edit.html b/view/manager/block/edit.html new file mode 100644 index 0000000..4aca6d4 --- /dev/null +++ b/view/manager/block/edit.html @@ -0,0 +1,299 @@ +{layout name="manager/layout" /} + + +
              +
              + + +
              + +
              +
              +
              +
              + +
              + +
              + +
              +
              +
              + +
              + +
              +
              ! 非开发人员,请慎重修改
              +
              + +
              + +
              + +
              +
              + + +
              + +
              + +
              +
              + + + + + + + + + + + + + + + + + +
              + + +
              + +
              + +
              +
              +
              + + + + diff --git a/view/manager/block/index.html b/view/manager/block/index.html new file mode 100644 index 0000000..8469099 --- /dev/null +++ b/view/manager/block/index.html @@ -0,0 +1,103 @@ +{layout name="manager/layout" /} + +
              + + + +
              +
              +
              +
              +
              + 搜索信息 +
              +
              +
              +
              + +
              + +
              +
              +
              + +
              +
              +
              +
              + +
              + +
              +
              +
              +
              +
              +
              +
              +
              +
              +
              +
              +
              + +
              + + + + + + + + + + + + + + diff --git a/view/manager/config/alioss.html b/view/manager/config/alioss.html new file mode 100644 index 0000000..63b5e5b --- /dev/null +++ b/view/manager/config/alioss.html @@ -0,0 +1,45 @@ +{layout name="manager/layout" /} +
              +
              +
              +
              + +
              + +
              +
              +
              + +
              + +
              +
              + +
              + +
              + +
              +
              +
              + +
              + +
              +
              + +
              + +
              + +
              +
              + +
              +
              + +
              +
              +
              +
              +
              \ No newline at end of file diff --git a/view/manager/config/alipay.html b/view/manager/config/alipay.html new file mode 100644 index 0000000..10141cb --- /dev/null +++ b/view/manager/config/alipay.html @@ -0,0 +1,67 @@ +{layout name="manager/layout" /} +
              +
              +
              + +
              +
              + +
              + +
              +
              +
              +
              +
              +
              + +
              + +
              +
              +
              +
              +
              +
              + +
              + +
              +
              +
              +
              +
              +
              + +
              + +
              +
              +
              +
              +
              +
              + +
              + +
              +
              +
              +
              +
              +
              + +
              + +
              +
              +
              +
              +
              +
              + +
              +
              +
              +
              +
              \ No newline at end of file diff --git a/view/manager/config/base.html b/view/manager/config/base.html new file mode 100644 index 0000000..da6f671 --- /dev/null +++ b/view/manager/config/base.html @@ -0,0 +1,131 @@ +{layout name="manager/layout" /} +
              +
              +
              +
              + +
              + + +
              +
              + + + +
              + +
              + +
              +
              + +
              + +
              + +
              +
              + +
              + +
              + +
              +
              + +
              + +
              + +
              +
              + + +
              + +
              + +
              +
              + + +
              + +
              + +
              +
              + +
              + +
              + +
              +
              + +
              + +
              + +
              +
              + +
              + +
              + +
              +
              + +
              + +
              +
              +
              + +
              图片尺寸:亲上传合适尺寸的图片
              +
              +
              + + + + +
              +
                +
                +
                +
                + + +
                + +
                + +
                +
                + +
                + +
                + +
                +
                + +
                + +
                + +
                +
                + + +
                +
                + +
                +
                +
                +
                +
                \ No newline at end of file diff --git a/view/manager/config/wechat.html b/view/manager/config/wechat.html new file mode 100644 index 0000000..98428c0 --- /dev/null +++ b/view/manager/config/wechat.html @@ -0,0 +1,125 @@ +{layout name="manager/layout" /} + + +
                +
                +
                + +
                +
                微信小程序
                +
                +
                + +
                + +
                +
                + +
                + +
                + +
                +
                + +
                + +
                + +
                +
                +
                + +
                +
                微信公众号
                +
                +
                + +
                + +
                +
                + +
                + +
                + +
                +
                + +
                + +
                + +
                +
                +
                + +
                +
                微信支付
                +
                +
                + +
                + +
                +
                + +
                + +
                + +
                +
                + +
                + +
                + +
                +
                +
                + +
                +
                微信开放平台
                +
                +
                + +
                + +
                +
                + +
                + +
                + +
                +
                + + +
                + +
                + +
                +
                +
                + + +
                +
                + +
                + +
                +
                +
                + +
                +
                +
                + + \ No newline at end of file diff --git a/view/manager/error/jump.html b/view/manager/error/jump.html new file mode 100644 index 0000000..8b24281 --- /dev/null +++ b/view/manager/error/jump.html @@ -0,0 +1,111 @@ + + + + + 404 + + + + + + + + + + +
                +
                +
                +
                +
                +
                + +
                +
                +
                +
                + +
                +
                +
                +
                + +
                +
                +
                OH! + +
                +
                +

                {if isset($msg)}{$msg}{else/}很抱歉,你访问的页面找不到了{/if}

                + +
                +
                +
                + + + + \ No newline at end of file diff --git a/view/manager/error/jump1.html b/view/manager/error/jump1.html new file mode 100644 index 0000000..b70a95a --- /dev/null +++ b/view/manager/error/jump1.html @@ -0,0 +1,53 @@ +{__NOLAYOUT__} + + + + + 跳转提示 + + + + + +
                + + +

                :)

                +

                + + +

                +

                + + +

                +

                + 页面自动 跳转 等待时间: +

                +

                立即返回

                +
                + + + diff --git a/view/manager/feedback/index.html b/view/manager/feedback/index.html new file mode 100644 index 0000000..c9a8aa1 --- /dev/null +++ b/view/manager/feedback/index.html @@ -0,0 +1,44 @@ +{layout name="manager/layout" /} + +
                +
                +
                + 搜索信息 +
                +
                +
                +
                + +
                + +
                +
                + +
                + +
                +
                +
                +
                +
                + +
                +
                +
                +
                +
                + + + + + + + + + \ No newline at end of file diff --git a/view/manager/file/index.html b/view/manager/file/index.html new file mode 100644 index 0000000..62a61ad --- /dev/null +++ b/view/manager/file/index.html @@ -0,0 +1,41 @@ +{php} +use app\service\Image as FImage; +{/php} +{layout name="manager/layout" /} +
                +
                + + + + + + + + + + + + + + + + {foreach $items as $item} + + + + + + + {/foreach} +
                类型名称尺寸大小
                {$types[$item.type]}{$item.name} + {if $item['type'] == 'img'} +
                {$item.w_h}
                + {elseif $item['type'] == 'video'} +
                + {else /} + {$item.src} + {/if} +
                {:sizeToSTr($item.size)}
                +
                {$items->render()|raw}
                +
                +
                \ No newline at end of file diff --git a/view/manager/file/unuse.html b/view/manager/file/unuse.html new file mode 100644 index 0000000..b34f300 --- /dev/null +++ b/view/manager/file/unuse.html @@ -0,0 +1,96 @@ +{layout name="manager/layout" /} + +
                +
                  +
                • 磁盘上未使用的文件
                • +
                • 数据库中未使用的文件
                • +
                +
                +
                + + + + + + + + + + + + + + + + {foreach $uploadedNotInUseFiles as $key => $file} + + + + + + + {/foreach} +
                + + 文件大小是否存在数据库中
                + + + {if in_array($file['suffix'], ['jpg', 'jpeg', 'png', 'gif'])} +
                + {elseif in_array($file['suffix'], ['mp4'])} +
                + {else /} + {$file.path} + {/if} +
                {$file.sizeStr} + {if in_array($key, $bothNotInUseFilesKey)}是{else /}否{/if} +
                +
                +
                +
                + + + + + + + + + + + + + + + + + {foreach $dbUploadedNotInUseFiles as $key => $file} + + + + + + + + {/foreach} +
                + + 文件上传前名称大小是否存在磁盘中
                + + + {if in_array($key, $bothNotInUseFilesKey)} + {if $file['type'] == 'img'} +
                + {elseif $file['type'] == 'video'} +
                + {/if} + {else /} + {$file.src} + {/if} +
                {$file.name}{:sizeToStr($file['size'])} + {if in_array($key, $bothNotInUseFilesKey)}是{else /}否{/if} +
                +
                +
                +
                +
                diff --git a/view/manager/index/dashboard.html b/view/manager/index/dashboard.html new file mode 100644 index 0000000..7ae31e3 --- /dev/null +++ b/view/manager/index/dashboard.html @@ -0,0 +1,38 @@ +{layout name="manager/layout" /} +
                +
                +
                +
                +
                +
                +
                + +
                +
                +
                +

                文章总数

                + 388 +
                +
                +
                +
                + +
                +
                +
                +
                +
                + +
                +
                +
                +

                成员数量

                + 99 +
                +
                +
                +
                +
                +
                + + \ No newline at end of file diff --git a/view/manager/index/index.html b/view/manager/index/index.html new file mode 100644 index 0000000..2e88c69 --- /dev/null +++ b/view/manager/index/index.html @@ -0,0 +1,159 @@ + + + + + 后台管理 + + + + + + + + + + + + + + + + + + +
                + +
                + + +
                + +
                +
                + + +
                  +
                + + + + + +
                +
                + + +
                +
                + + +
                +
                +
                + + +
                + + +
                + +
                + +
                +
                  +
                • +
                +
                +
              • +
              • +
              • + +
              • +
                +
                +
                +
                +
                + +
                +
                + + + + + \ No newline at end of file diff --git a/view/manager/layout.html b/view/manager/layout.html new file mode 100644 index 0000000..baf7d13 --- /dev/null +++ b/view/manager/layout.html @@ -0,0 +1,23 @@ + + + + + {$title ?? '后台管理系统'} + + + + + + + + + + + + + + + +{__CONTENT__} + + \ No newline at end of file diff --git a/view/manager/link/add.html b/view/manager/link/add.html new file mode 100644 index 0000000..a828173 --- /dev/null +++ b/view/manager/link/add.html @@ -0,0 +1,51 @@ +{layout name="manager/layout" /} + +
                +
                +
                + + +
                + +
                + +
                +
                + + +
                + +
                + +
                +
                + +
                + +
                +
                +
                + +
                图片尺寸:请上传合适大小的图片
                +
                +
                + + + +
                +
                  +
                  +
                  +
                  + +
                  +
                  + +
                  +
                  + +
                  +
                  +
                  diff --git a/view/manager/link/edit.html b/view/manager/link/edit.html new file mode 100644 index 0000000..8df114f --- /dev/null +++ b/view/manager/link/edit.html @@ -0,0 +1,53 @@ +{layout name="manager/layout" /} + +
                  +
                  +
                  + + +
                  + +
                  + +
                  +
                  + + +
                  + +
                  + +
                  +
                  + +
                  + +
                  +
                  +
                  + +
                  图片尺寸:1960*820
                  +
                  +
                  + + + +
                  +
                    +
                    +
                    +
                    + +
                    +
                    + + +
                    +
                    + +
                    +
                    +
                    + \ No newline at end of file diff --git a/view/manager/link/index.html b/view/manager/link/index.html new file mode 100644 index 0000000..e63f84f --- /dev/null +++ b/view/manager/link/index.html @@ -0,0 +1,49 @@ +{layout name="manager/layout" /} + + +
                    +
                    +
                    +
                    +
                    +
                    +
                    +
                    +
                    +
                    +
                    +
                    +
                    + + + + + + + + + + \ No newline at end of file diff --git a/view/manager/log/index.html b/view/manager/log/index.html new file mode 100644 index 0000000..167da16 --- /dev/null +++ b/view/manager/log/index.html @@ -0,0 +1,54 @@ +{layout name="manager/layout" /} + +
                    +
                    +
                    + 搜索信息 +
                    +
                    +
                    +
                    + +
                    + +
                    +
                    + +
                    + +
                    + +
                    +
                    + +
                    + +
                    + +
                    +
                    + +
                    + +
                    +
                    +
                    +
                    +
                    +
                    +
                    +
                    +
                    +
                    + + + + + + + \ No newline at end of file diff --git a/view/manager/login/index.html b/view/manager/login/index.html new file mode 100644 index 0000000..65cb301 --- /dev/null +++ b/view/manager/login/index.html @@ -0,0 +1,173 @@ + + + + + 后台管理-登陆 + + + + + + + + + + + +
                    + +
                    + + + + + \ No newline at end of file diff --git a/view/manager/map/index.html b/view/manager/map/index.html new file mode 100644 index 0000000..6d04912 --- /dev/null +++ b/view/manager/map/index.html @@ -0,0 +1,75 @@ +{layout name="manager/layout" /} +
                    +
                    +
                    + + + + + + +
                    + +
                    +
                    + +
                    +
                    + +
                    +
                    +
                    +
                    + +
                    +
                    + +
                    +
                    +
                    +
                    +
                    + + + + \ No newline at end of file diff --git a/view/manager/member/add.html b/view/manager/member/add.html new file mode 100644 index 0000000..fb08b19 --- /dev/null +++ b/view/manager/member/add.html @@ -0,0 +1,50 @@ +{layout name="manager/layout" /} +
                    +
                    +
                    +
                    + +
                    + +
                    +
                    +
                    + +
                    + +
                    +
                    +
                    + +
                    + +
                    +
                    +
                    + +
                    + +
                    +
                    +
                    + +
                    +
                    +
                    +
                    +
                    + +
                    + +
                    +
                    + +
                    +
                    + +
                    +
                    +
                    +
                    +
                    + \ No newline at end of file diff --git a/view/manager/member/edit.html b/view/manager/member/edit.html new file mode 100644 index 0000000..e55dbdb --- /dev/null +++ b/view/manager/member/edit.html @@ -0,0 +1,44 @@ +{layout name="manager/layout" /} +
                    +
                    +
                    +
                    + +
                    + +
                    +
                    +
                    + +
                    + +
                    +
                    +
                    + +
                    + +
                    +
                    +
                    + +
                    +
                    +
                    +
                    +
                    + +
                    + +
                    +
                    + +
                    +
                    + +
                    +
                    +
                    +
                    +
                    + \ No newline at end of file diff --git a/view/manager/member/index.html b/view/manager/member/index.html new file mode 100644 index 0000000..842370a --- /dev/null +++ b/view/manager/member/index.html @@ -0,0 +1,63 @@ +{layout name="manager/layout" /} + +
                    +
                    + +
                    +
                    +
                    +
                    +
                    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/view/manager/member/my_password.html b/view/manager/member/my_password.html new file mode 100644 index 0000000..dae7483 --- /dev/null +++ b/view/manager/member/my_password.html @@ -0,0 +1,64 @@ +{layout name="manager/layout" /} +
                    +
                    +
                    + +
                    + +
                    + +
                    +
                    + +
                    + +
                    + +
                    +
                    + +
                    + +
                    + +
                    +
                    + +
                    + +
                    + +
                    +
                    + +
                    +
                    + +
                    +
                    +
                    +
                    +
                    + \ No newline at end of file diff --git a/view/manager/member/password.html b/view/manager/member/password.html new file mode 100644 index 0000000..64cfa04 --- /dev/null +++ b/view/manager/member/password.html @@ -0,0 +1,35 @@ +{layout name="manager/layout" /} +
                    +
                    +
                    + +
                    + +
                    + +
                    +
                    + +
                    + +
                    + +
                    +
                    + +
                    + +
                    + +
                    +
                    + +
                    +
                    + +
                    +
                    +
                    +
                    +
                    + \ No newline at end of file diff --git a/view/manager/member/profile.html b/view/manager/member/profile.html new file mode 100644 index 0000000..b2bdccf --- /dev/null +++ b/view/manager/member/profile.html @@ -0,0 +1,60 @@ +{layout name="manager/layout" /} +
                    +
                    +
                    +
                    + +
                    + +
                    +
                    +
                    + +
                    + +
                    +
                    +
                    + +
                    + +
                    +
                    +
                    + +
                    + +
                    +
                    + +
                    +
                    + +
                    +
                    +
                    +
                    +
                    + \ No newline at end of file diff --git a/view/manager/menu/add.html b/view/manager/menu/add.html new file mode 100644 index 0000000..9b273e6 --- /dev/null +++ b/view/manager/menu/add.html @@ -0,0 +1,81 @@ +{layout name="manager/layout" /} + +
                    +
                    +
                    +
                    + +
                    +
                    +
                    +
                    +
                    + +
                    + + +
                    +
                    +
                    + +
                    + +
                    +
                    +
                    + +
                    + +
                    +
                    +
                    + +
                    + +
                    +
                    +
                    + +
                    + +
                    +
                    +
                    + +
                    + + +
                    +
                    +
                    + +
                    + + +
                    +
                    +
                    + +
                    + + +
                    +
                    +
                    + +
                    + +
                    +
                    + +
                    +
                    + +
                    +
                    +
                    +
                    +
                    + \ No newline at end of file diff --git a/view/manager/menu/edit.html b/view/manager/menu/edit.html new file mode 100644 index 0000000..a147625 --- /dev/null +++ b/view/manager/menu/edit.html @@ -0,0 +1,81 @@ +{layout name="manager/layout" /} + +
                    +
                    +
                    +
                    + +
                    +
                    +
                    +
                    +
                    + +
                    + + +
                    +
                    +
                    + +
                    + +
                    +
                    +
                    + +
                    + +
                    +
                    +
                    + +
                    + +
                    +
                    +
                    + +
                    + +
                    +
                    +
                    + +
                    + + +
                    +
                    +
                    + +
                    + + +
                    +
                    +
                    + +
                    + + +
                    +
                    +
                    + +
                    + +
                    +
                    + +
                    +
                    + +
                    +
                    +
                    +
                    +
                    + \ No newline at end of file diff --git a/view/manager/menu/index.html b/view/manager/menu/index.html new file mode 100644 index 0000000..9cf406a --- /dev/null +++ b/view/manager/menu/index.html @@ -0,0 +1,27 @@ +{layout name="manager/layout" /} + +
                    +
                    +
                    + +
                    +
                    +
                    + + + + + + + \ No newline at end of file diff --git a/view/manager/public/file.html b/view/manager/public/file.html new file mode 100644 index 0000000..045b94c --- /dev/null +++ b/view/manager/public/file.html @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/view/manager/role/add.html b/view/manager/role/add.html new file mode 100644 index 0000000..ff439dc --- /dev/null +++ b/view/manager/role/add.html @@ -0,0 +1,25 @@ +{layout name="manager/layout" /} +
                    +
                    +
                    +
                    + +
                    + +
                    +
                    +
                    + +
                    + +
                    +
                    + +
                    +
                    + +
                    +
                    +
                    +
                    +
                    \ No newline at end of file diff --git a/view/manager/role/edit.html b/view/manager/role/edit.html new file mode 100644 index 0000000..b9a7beb --- /dev/null +++ b/view/manager/role/edit.html @@ -0,0 +1,25 @@ +{layout name="manager/layout" /} +
                    +
                    +
                    +
                    + +
                    + +
                    +
                    +
                    + +
                    + +
                    +
                    + +
                    +
                    + +
                    +
                    +
                    +
                    +
                    \ No newline at end of file diff --git a/view/manager/role/index.html b/view/manager/role/index.html new file mode 100644 index 0000000..862f1f0 --- /dev/null +++ b/view/manager/role/index.html @@ -0,0 +1,29 @@ +{layout name="manager/layout" /} + +
                    +
                    +
                    +
                    +
                    +
                    +
                    + + + + + + + + + + \ No newline at end of file diff --git a/view/manager/role/rule.html b/view/manager/role/rule.html new file mode 100644 index 0000000..58bccc3 --- /dev/null +++ b/view/manager/role/rule.html @@ -0,0 +1,80 @@ +{layout name="manager/layout" /} + + +
                    +
                    +
                    +
                    + +
                    + +
                    +
                    +
                    + +
                    + +
                      +
                      +
                      + +
                      +
                      + +
                      +
                      +
                      +
                      +
                      + + + + + \ No newline at end of file diff --git a/view/manager/rule/add.html b/view/manager/rule/add.html new file mode 100644 index 0000000..5bbfc29 --- /dev/null +++ b/view/manager/rule/add.html @@ -0,0 +1,45 @@ +
                      +
                      +
                      + +
                      + +
                      + {$parentId?$parent['title']:'顶级权限'} +
                      +
                      +
                      + +
                      + +
                      +
                      +
                      + +
                      + +
                      +
                      +
                      + +
                      + + +
                      +
                      +
                      + +
                      + + +
                      +
                      +
                      +
                      + + +
                      +
                      +
                      +
                      +
                      \ No newline at end of file diff --git a/view/manager/rule/edit.html b/view/manager/rule/edit.html new file mode 100644 index 0000000..6d3e44b --- /dev/null +++ b/view/manager/rule/edit.html @@ -0,0 +1,53 @@ +
                      +
                      + {empty name='item'} +

                      无效请求,该信息不存在

                      + {else /} +
                      + +
                      + +
                      + {if isset($parent) && !empty($parent)} + {$parent.title} + {else /} + 顶级权限 + {/if} +
                      +
                      +
                      + +
                      + +
                      +
                      +
                      + +
                      + +
                      +
                      +
                      + +
                      + + +
                      +
                      +
                      + +
                      + + +
                      +
                      +
                      +
                      + + +
                      +
                      +
                      + {/empty} +
                      +
                      \ No newline at end of file diff --git a/view/manager/rule/index.html b/view/manager/rule/index.html new file mode 100644 index 0000000..4f0bb20 --- /dev/null +++ b/view/manager/rule/index.html @@ -0,0 +1,90 @@ +{layout name="manager/layout" /} +
                      +
                      + + {empty name="items"} + 无记录 + {else /} + + + + + + + + + + + + + + {foreach $items as $item} + + + + + + + {if isset($item['children']) && !empty($item['children'])} + + + + {/if} + + + {/foreach} +
                      栏目路径操作
                      {$item.title}{$item.name} + + 添加下级权限 + + + 向上 + + + 向下 + + + 编辑 + + + 删除 + +
                      + + + + + + + {foreach $item['children'] as $child} + + + + + + {/foreach} +
                      {$child.title}{$child.name} + + 添加下级权限 + + + 向上 + + + 向下 + + + 编辑 + + + 删除 + +
                      +
                      + {/empty} +
                      +
                      \ No newline at end of file diff --git a/view/manager/slide/add.html b/view/manager/slide/add.html new file mode 100644 index 0000000..5cf1599 --- /dev/null +++ b/view/manager/slide/add.html @@ -0,0 +1,64 @@ +{layout name="manager/layout" /} + +
                      +
                      +
                      + +
                      + +
                      +
                      +
                      +
                      + +
                      + +
                      + +
                      +
                      + +
                      + +
                      + +
                      +
                      + +
                      + +
                      +
                      +
                      + +
                      图片尺寸:最低高度为200px
                      +
                      +
                      + + + +
                      +
                        +
                        +
                        +
                        + +
                        + +
                        + +
                        +
                        + +
                        +
                        + +
                        +
                        + +
                        +
                        +
                        + \ No newline at end of file diff --git a/view/manager/slide/add_position.html b/view/manager/slide/add_position.html new file mode 100644 index 0000000..6ff36c9 --- /dev/null +++ b/view/manager/slide/add_position.html @@ -0,0 +1,29 @@ +{layout name="manager/layout" /} + +
                        +
                        +
                        +
                        + +
                        + +
                        +
                        + +
                        + +
                        + +
                        +
                        + +
                        +
                        + +
                        +
                        + +
                        +
                        +
                        + \ No newline at end of file diff --git a/view/manager/slide/edit.html b/view/manager/slide/edit.html new file mode 100644 index 0000000..a954acb --- /dev/null +++ b/view/manager/slide/edit.html @@ -0,0 +1,65 @@ +{layout name="manager/layout" /} + +
                        +
                        +
                        + +
                        + +
                        +
                        +
                        +
                        + +
                        + +
                        + +
                        +
                        + +
                        + +
                        + +
                        +
                        + +
                        + +
                        +
                        +
                        + +
                        图片尺寸:最低高度为200px
                        +
                        +
                        + + + +
                        +
                          +
                          +
                          +
                          + +
                          + +
                          + +
                          +
                          + +
                          +
                          + + +
                          +
                          + +
                          +
                          +
                          + \ No newline at end of file diff --git a/view/manager/slide/edit_position.html b/view/manager/slide/edit_position.html new file mode 100644 index 0000000..f150c32 --- /dev/null +++ b/view/manager/slide/edit_position.html @@ -0,0 +1,30 @@ +{layout name="manager/layout" /} + +
                          +
                          +
                          +
                          + +
                          + +
                          +
                          + +
                          + +
                          + +
                          +
                          + +
                          +
                          + + +
                          +
                          + +
                          +
                          +
                          + \ No newline at end of file diff --git a/view/manager/slide/index.html b/view/manager/slide/index.html new file mode 100644 index 0000000..ea64b28 --- /dev/null +++ b/view/manager/slide/index.html @@ -0,0 +1,79 @@ +{layout name="manager/layout" /} + + +
                          +
                          +
                          +
                          +
                          + +
                          +
                          +
                          +
                          +
                          +
                          +
                          +
                          + + + + + + + + + + + + \ No newline at end of file diff --git a/view/manager/slide/position.html b/view/manager/slide/position.html new file mode 100644 index 0000000..6c45d49 --- /dev/null +++ b/view/manager/slide/position.html @@ -0,0 +1,31 @@ +{layout name="manager/layout" /} + +
                          +
                          +
                          +
                          +
                          +
                          +
                          +
                          +
                          +
                          +
                          +
                          +
                          + + + + + + + + + \ No newline at end of file diff --git a/view/page/archive_default.html b/view/page/archive_default.html new file mode 100644 index 0000000..7f2e7c9 --- /dev/null +++ b/view/page/archive_default.html @@ -0,0 +1,89 @@ +{layout name="layout"/} +{php} +use \app\model\Archives ; +use \app\model\ArchivesCategory ; +use app\service\Image as WImage; +$newsMenu = ArchivesCategory::findlist([["pid","=",ArchivesCategory::news_id]],[],1,0,null,['sort' =>'desc',"id"=>"asc"]); +if(!isset($newsMenu['list'])){ +$newsMenu['list']=[]; +} +{/php} + + + + +
                          +
                          +
                          +

                          {$archive['title']}

                          + + 发布时间:{$archive['created_at']|date="Y-m-d"} + + + 来源: + {if empty($archive['author'] )} + 本站原创 + {else/} + {$archive['author']??"本站原创"} + {/if} + + + 点击次数:{$archive['views']}次 + +
                          + {$archive['content']|raw} +
                          + + +
                          +
                          +
                          \ No newline at end of file diff --git a/view/page/archives_default.html b/view/page/archives_default.html new file mode 100644 index 0000000..4661bf6 --- /dev/null +++ b/view/page/archives_default.html @@ -0,0 +1,40 @@ +{layout name="layout"/} + +{php} +use \app\model\ArchivesCategory ; +use \app\model\Archives ; +use app\service\Image as WImage; + +{/php} + + + + +
                          +
                          +
                          +
                          +

                          {$category['title']}

                          + {$category['subtitle']} +
                          +
                            + + {foreach $archives as $archivesItem} +
                          • {$archivesItem["title"]} + {$archivesItem["created_at"]|date="Y.m.d"} +
                          • + {/foreach} +
                          +
                          + {$archives->render()|raw} + +
                          +
                          \ No newline at end of file diff --git a/view/page/page_default.html b/view/page/page_default.html new file mode 100644 index 0000000..58191a2 --- /dev/null +++ b/view/page/page_default.html @@ -0,0 +1,34 @@ +{layout name="layout"/} + +{php} +use \app\model\ArchivesCategory ; +use \app\model\Archives ; +use app\service\Image as WImage; +{/php} + + + + +
                          +
                          +
                          +
                          +

                          {$category['title']}

                          + {$category['subtitle']} +
                          +
                          + {if isset($blocks['content'])} + {$blocks['content']['content']|raw} + {/if} + +
                          +
                          +
                          +
                          \ No newline at end of file diff --git a/view/public/footer.html b/view/public/footer.html new file mode 100644 index 0000000..9a55809 --- /dev/null +++ b/view/public/footer.html @@ -0,0 +1,2 @@ +----------------------------------------------------- +
                          这里是底部
                          \ No newline at end of file diff --git a/view/public/menu.html b/view/public/menu.html new file mode 100644 index 0000000..6826fd2 --- /dev/null +++ b/view/public/menu.html @@ -0,0 +1,32 @@ +{php} +use app\model\ArchivesCategory as ArticleCategoryModel; +$menu = ArticleCategoryModel::getListForFrontMenu(); +//dump($menu); +{/php} + + + +
                          +
                          + + + + + + +
                          + + + +
                          +
                          +
                          \ No newline at end of file diff --git a/view/public/slide.html b/view/public/slide.html new file mode 100644 index 0000000..ff12e81 --- /dev/null +++ b/view/public/slide.html @@ -0,0 +1,8 @@ +
                          + + +
                          \ No newline at end of file